Station AI customizations (#34501)
Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
@@ -0,0 +1,40 @@
|
||||
using Content.Shared.Silicons.StationAi;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Silicons.StationAi;
|
||||
|
||||
public sealed class StationAiCustomizationBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private StationAiCustomizationMenu? _menu;
|
||||
|
||||
public StationAiCustomizationBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = new StationAiCustomizationMenu(Owner);
|
||||
_menu.OpenCentered();
|
||||
_menu.OnClose += Close;
|
||||
|
||||
_menu.SendStationAiCustomizationMessageAction += SendStationAiCustomizationMessage;
|
||||
}
|
||||
|
||||
public void SendStationAiCustomizationMessage(ProtoId<StationAiCustomizationGroupPrototype> groupProtoId, ProtoId<StationAiCustomizationPrototype> customizationProtoId)
|
||||
{
|
||||
SendPredictedMessage(new StationAiCustomizationMessage(groupProtoId, customizationProtoId));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_menu?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
SetSize="600 600"
|
||||
MinSize="600 220">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="10 5 10 -5">
|
||||
<controls:StripeBack HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Control SetWidth="200">
|
||||
<Label Text="{Loc 'station-ai-customization-categories'}" Margin="0 5 0 5" HorizontalExpand="True" HorizontalAlignment="Center" />
|
||||
</Control>
|
||||
<Label Text="{Loc 'station-ai-customization-options'}" Margin="0 5 0 5" HorizontalExpand="True" HorizontalAlignment="Center"/>
|
||||
</BoxContainer>
|
||||
</controls:StripeBack>
|
||||
</BoxContainer>
|
||||
<VerticalTabContainer Name="CustomizationGroupsContainer"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
</VerticalTabContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
@@ -0,0 +1,176 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Silicons.StationAi;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Client.Silicons.StationAi;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class StationAiCustomizationMenu : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
|
||||
private Dictionary<ProtoId<StationAiCustomizationGroupPrototype>, StationAiCustomizationGroupContainer> _groupContainers = new();
|
||||
private Dictionary<ProtoId<StationAiCustomizationGroupPrototype>, ButtonGroup> _buttonGroups = new();
|
||||
|
||||
public event Action<ProtoId<StationAiCustomizationGroupPrototype>, ProtoId<StationAiCustomizationPrototype>>? SendStationAiCustomizationMessageAction;
|
||||
|
||||
private const float IconScale = 1.75f;
|
||||
|
||||
public StationAiCustomizationMenu(EntityUid owner)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
var stationAiSystem = _entManager.System<SharedStationAiSystem>();
|
||||
|
||||
// Load customziation data
|
||||
_entManager.TryGetComponent<StationAiCoreComponent>(owner, out var stationAiCore);
|
||||
stationAiSystem.TryGetHeld((owner, stationAiCore), out var insertedAi);
|
||||
_entManager.TryGetComponent<StationAiCustomizationComponent>(insertedAi, out var stationAiCustomization);
|
||||
|
||||
// Create UI entires for each group of customizations
|
||||
var groupPrototypes = _protoManager.EnumeratePrototypes<StationAiCustomizationGroupPrototype>();
|
||||
groupPrototypes = groupPrototypes.OrderBy(x => x.ID); // To ensure consistency in presentation
|
||||
|
||||
foreach (var groupPrototype in groupPrototypes)
|
||||
{
|
||||
StationAiCustomizationPrototype? selectedPrototype = null;
|
||||
|
||||
if (stationAiCustomization?.ProtoIds.TryGetValue(groupPrototype, out var selectedProtoId) == true)
|
||||
_protoManager.TryIndex(selectedProtoId, out selectedPrototype);
|
||||
|
||||
_buttonGroups[groupPrototype] = new ButtonGroup();
|
||||
_groupContainers[groupPrototype] = new StationAiCustomizationGroupContainer(groupPrototype, selectedPrototype, _buttonGroups[groupPrototype], this, _protoManager);
|
||||
CustomizationGroupsContainer.AddTab(_groupContainers[groupPrototype], Loc.GetString(groupPrototype.Name));
|
||||
}
|
||||
|
||||
Title = Loc.GetString("station-ai-customization-menu");
|
||||
}
|
||||
|
||||
public void OnSendStationAiCustomizationMessage
|
||||
(ProtoId<StationAiCustomizationGroupPrototype> groupProtoId, ProtoId<StationAiCustomizationPrototype> customizationProtoId)
|
||||
{
|
||||
SendStationAiCustomizationMessageAction?.Invoke(groupProtoId, customizationProtoId);
|
||||
}
|
||||
|
||||
private sealed class StationAiCustomizationGroupContainer : BoxContainer
|
||||
{
|
||||
public StationAiCustomizationGroupContainer
|
||||
(StationAiCustomizationGroupPrototype groupPrototype,
|
||||
StationAiCustomizationPrototype? selectedPrototype,
|
||||
ButtonGroup buttonGroup,
|
||||
StationAiCustomizationMenu menu,
|
||||
IPrototypeManager protoManager)
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical;
|
||||
HorizontalExpand = true;
|
||||
VerticalExpand = true;
|
||||
|
||||
// Create UI entries for all customization in the group
|
||||
foreach (var protoId in groupPrototype.ProtoIds)
|
||||
{
|
||||
if (!protoManager.TryIndex(protoId, out var prototype))
|
||||
continue;
|
||||
|
||||
var entry = new StationAiCustomizationEntryContainer(groupPrototype, prototype, buttonGroup, menu);
|
||||
AddChild(entry);
|
||||
|
||||
if (prototype == selectedPrototype)
|
||||
entry.SelectButton.Pressed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class StationAiCustomizationEntryContainer : BoxContainer
|
||||
{
|
||||
public ProtoId<StationAiCustomizationPrototype> ProtoId;
|
||||
public Button SelectButton;
|
||||
|
||||
public StationAiCustomizationEntryContainer
|
||||
(StationAiCustomizationGroupPrototype groupPrototype,
|
||||
StationAiCustomizationPrototype prototype,
|
||||
ButtonGroup buttonGroup,
|
||||
StationAiCustomizationMenu menu)
|
||||
{
|
||||
ProtoId = prototype;
|
||||
|
||||
Orientation = LayoutOrientation.Horizontal;
|
||||
HorizontalExpand = true;
|
||||
|
||||
// Create a selection button
|
||||
SelectButton = new Button
|
||||
{
|
||||
Text = Loc.GetString(prototype.Name),
|
||||
HorizontalExpand = true,
|
||||
ToggleMode = true,
|
||||
Group = buttonGroup,
|
||||
};
|
||||
|
||||
SelectButton.OnPressed += args =>
|
||||
{
|
||||
menu.OnSendStationAiCustomizationMessage(groupPrototype, prototype);
|
||||
};
|
||||
|
||||
AddChild(SelectButton);
|
||||
|
||||
// Creat a background for the preview
|
||||
var background = new AnimatedTextureRect
|
||||
{
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SetWidth = 56,
|
||||
SetHeight = 56,
|
||||
Margin = new Thickness(10f, 2f),
|
||||
};
|
||||
|
||||
background.DisplayRect.TextureScale = new Vector2(IconScale, IconScale);
|
||||
|
||||
if (prototype.PreviewBackground != null)
|
||||
{
|
||||
background.SetFromSpriteSpecifier(prototype.PreviewBackground);
|
||||
}
|
||||
|
||||
AddChild(background);
|
||||
|
||||
// Create a preview icon
|
||||
var icon = new AnimatedTextureRect
|
||||
{
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SetWidth = 56,
|
||||
SetHeight = 56,
|
||||
};
|
||||
|
||||
icon.DisplayRect.TextureScale = new Vector2(IconScale, IconScale);
|
||||
|
||||
// Default RSI path/state
|
||||
var rsiPath = prototype.LayerData.FirstOrNull()?.Value.RsiPath;
|
||||
var rsiState = prototype.LayerData.FirstOrNull()?.Value.State;
|
||||
|
||||
// Specified RSI path/state
|
||||
if (!string.IsNullOrEmpty(prototype.PreviewKey) && prototype.LayerData.TryGetValue(prototype.PreviewKey, out var layerData))
|
||||
{
|
||||
rsiPath = layerData.RsiPath;
|
||||
rsiState = layerData.State;
|
||||
}
|
||||
|
||||
// Update icon
|
||||
if (rsiPath != null && rsiState != null)
|
||||
{
|
||||
var specifier = new SpriteSpecifier.Rsi(new ResPath(rsiPath), rsiState);
|
||||
icon.SetFromSpriteSpecifier(specifier);
|
||||
}
|
||||
|
||||
background.AddChild(icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Silicons.StationAi;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Player;
|
||||
@@ -9,6 +10,7 @@ public sealed partial class StationAiSystem : SharedStationAiSystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMgr = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
private StationAiOverlay? _overlay;
|
||||
|
||||
@@ -22,6 +24,7 @@ public sealed partial class StationAiSystem : SharedStationAiSystem
|
||||
SubscribeLocalEvent<StationAiOverlayComponent, LocalPlayerDetachedEvent>(OnAiDetached);
|
||||
SubscribeLocalEvent<StationAiOverlayComponent, ComponentInit>(OnAiOverlayInit);
|
||||
SubscribeLocalEvent<StationAiOverlayComponent, ComponentRemove>(OnAiOverlayRemove);
|
||||
SubscribeLocalEvent<StationAiCoreComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
}
|
||||
|
||||
private void OnAiOverlayInit(Entity<StationAiOverlayComponent> ent, ref ComponentInit args)
|
||||
@@ -72,6 +75,17 @@ public sealed partial class StationAiSystem : SharedStationAiSystem
|
||||
RemoveOverlay();
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(Entity<StationAiCoreComponent> entity, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
if (_appearance.TryGetData<PrototypeLayerData>(entity.Owner, StationAiVisualState.Key, out var layerData, args.Component))
|
||||
args.Sprite.LayerSetData(StationAiVisualState.Key, layerData);
|
||||
|
||||
args.Sprite.LayerSetVisible(StationAiVisualState.Key, layerData != null);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
@@ -9,5 +9,5 @@ public sealed partial class HolographicAvatarComponent : Component
|
||||
/// The prototype sprite layer data for the hologram
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public PrototypeLayerData[] LayerData;
|
||||
public PrototypeLayerData[]? LayerData = null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
using Content.Shared.Holopad;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Silicons.StationAi;
|
||||
|
||||
public abstract partial class SharedStationAiSystem
|
||||
{
|
||||
private ProtoId<StationAiCustomizationGroupPrototype> _stationAiCoreCustomGroupProtoId = "StationAiCoreIconography";
|
||||
private ProtoId<StationAiCustomizationGroupPrototype> _stationAiHologramCustomGroupProtoId = "StationAiHolograms";
|
||||
|
||||
private void InitializeCustomization()
|
||||
{
|
||||
SubscribeLocalEvent<StationAiCoreComponent, StationAiCustomizationMessage>(OnStationAiCustomization);
|
||||
}
|
||||
|
||||
private void OnStationAiCustomization(Entity<StationAiCoreComponent> entity, ref StationAiCustomizationMessage args)
|
||||
{
|
||||
if (!_protoManager.TryIndex(args.GroupProtoId, out var groupPrototype) || !_protoManager.TryIndex(args.CustomizationProtoId, out var customizationProto))
|
||||
return;
|
||||
|
||||
if (!TryGetHeld((entity, entity.Comp), out var held))
|
||||
return;
|
||||
|
||||
if (!TryComp<StationAiCustomizationComponent>(held, out var stationAiCustomization))
|
||||
return;
|
||||
|
||||
if (stationAiCustomization.ProtoIds.TryGetValue(args.GroupProtoId, out var protoId) && protoId == args.CustomizationProtoId)
|
||||
return;
|
||||
|
||||
stationAiCustomization.ProtoIds[args.GroupProtoId] = args.CustomizationProtoId;
|
||||
|
||||
Dirty(held, stationAiCustomization);
|
||||
|
||||
// Update hologram
|
||||
if (groupPrototype.Category == StationAiCustomizationType.Hologram)
|
||||
UpdateHolographicAvatar((held, stationAiCustomization));
|
||||
|
||||
// Update core iconography
|
||||
if (groupPrototype.Category == StationAiCustomizationType.CoreIconography && TryComp<StationAiHolderComponent>(entity, out var stationAiHolder))
|
||||
UpdateAppearance((entity, stationAiHolder));
|
||||
}
|
||||
|
||||
private void UpdateHolographicAvatar(Entity<StationAiCustomizationComponent> entity)
|
||||
{
|
||||
if (!TryComp<HolographicAvatarComponent>(entity, out var avatar))
|
||||
return;
|
||||
|
||||
if (!entity.Comp.ProtoIds.TryGetValue(_stationAiHologramCustomGroupProtoId, out var protoId))
|
||||
return;
|
||||
|
||||
if (!_protoManager.TryIndex(protoId, out var prototype))
|
||||
return;
|
||||
|
||||
if (!prototype.LayerData.TryGetValue(StationAiState.Hologram.ToString(), out var layerData))
|
||||
return;
|
||||
|
||||
avatar.LayerData = [layerData];
|
||||
Dirty(entity, avatar);
|
||||
}
|
||||
|
||||
private void CustomizeAppearance(Entity<StationAiCoreComponent> entity, StationAiState state)
|
||||
{
|
||||
var stationAi = GetInsertedAI(entity);
|
||||
|
||||
if (stationAi == null)
|
||||
{
|
||||
_appearance.RemoveData(entity.Owner, StationAiVisualState.Key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<StationAiCustomizationComponent>(stationAi, out var stationAiCustomization) ||
|
||||
!stationAiCustomization.ProtoIds.TryGetValue(_stationAiCoreCustomGroupProtoId, out var protoId) ||
|
||||
!_protoManager.TryIndex(protoId, out var prototype) ||
|
||||
!prototype.LayerData.TryGetValue(state.ToString(), out var layerData))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// This data is handled manually in the client StationAiSystem
|
||||
_appearance.SetData(entity.Owner, StationAiVisualState.Key, layerData);
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Silicons.StationAi;
|
||||
|
||||
@@ -56,6 +57,7 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
[Dependency] private readonly SharedTransformSystem _xforms = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly StationAiVisionSystem _vision = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
|
||||
// StationAiHeld is added to anything inside of an AI core.
|
||||
// StationAiHolder indicates it can hold an AI positronic brain (e.g. holocard / core).
|
||||
@@ -82,6 +84,7 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
InitializeAirlock();
|
||||
InitializeHeld();
|
||||
InitializeLight();
|
||||
InitializeCustomization();
|
||||
|
||||
SubscribeLocalEvent<StationAiWhitelistComponent, BoundUserInterfaceCheckRangeEvent>(OnAiBuiCheck);
|
||||
|
||||
@@ -107,14 +110,12 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
|
||||
private void OnCoreVerbs(Entity<StationAiCoreComponent> ent, ref GetVerbsEvent<Verb> args)
|
||||
{
|
||||
if (!_admin.IsAdmin(args.User) ||
|
||||
TryGetHeld((ent.Owner, ent.Comp), out _))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var user = args.User;
|
||||
|
||||
// Admin option to take over the station AI core
|
||||
if (_admin.IsAdmin(args.User) &&
|
||||
!TryGetHeld((ent.Owner, ent.Comp), out _))
|
||||
{
|
||||
args.Verbs.Add(new Verb()
|
||||
{
|
||||
Text = Loc.GetString("station-ai-takeover"),
|
||||
@@ -128,6 +129,18 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
});
|
||||
}
|
||||
|
||||
// Option to open the station AI customization menu
|
||||
if (TryGetHeld((ent, ent.Comp), out var insertedAi) && insertedAi == user)
|
||||
{
|
||||
args.Verbs.Add(new Verb()
|
||||
{
|
||||
Text = Loc.GetString("station-ai-customization-menu"),
|
||||
Act = () => _uiSystem.TryOpenUi(ent.Owner, StationAiCustomizationUiKey.Key, insertedAi),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/emotes.svg.192dpi.png")),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAiAccessible(Entity<StationAiOverlayComponent> ent, ref AccessibleOverrideEvent args)
|
||||
{
|
||||
args.Handled = true;
|
||||
@@ -494,14 +507,21 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
if (!Resolve(entity.Owner, ref entity.Comp, false))
|
||||
return;
|
||||
|
||||
if (!_containers.TryGetContainer(entity.Owner, StationAiHolderComponent.Container, out var container) ||
|
||||
container.Count == 0)
|
||||
// Todo: when AIs can die, add a check to see if the AI is in the 'dead' state
|
||||
var state = StationAiState.Empty;
|
||||
|
||||
if (_containers.TryGetContainer(entity.Owner, StationAiHolderComponent.Container, out var container) && container.Count > 0)
|
||||
state = StationAiState.Occupied;
|
||||
|
||||
// If the entity is a station AI core, attempt to customize its appearance
|
||||
if (TryComp<StationAiCoreComponent>(entity, out var stationAiCore))
|
||||
{
|
||||
_appearance.SetData(entity.Owner, StationAiVisualState.Key, StationAiState.Empty);
|
||||
CustomizeAppearance((entity, stationAiCore), state);
|
||||
return;
|
||||
}
|
||||
|
||||
_appearance.SetData(entity.Owner, StationAiVisualState.Key, StationAiState.Occupied);
|
||||
// Otherwise let generic visualizers handle the appearance update
|
||||
_appearance.SetData(entity.Owner, StationAiVisualState.Key, state);
|
||||
}
|
||||
|
||||
public virtual void AnnounceIntellicardUsage(EntityUid uid, SoundSpecifier? cue = null) { }
|
||||
@@ -550,17 +570,23 @@ public sealed partial class JumpToCoreEvent : InstantActionEvent
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class IntellicardDoAfterEvent : SimpleDoAfterEvent;
|
||||
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum StationAiVisualState : byte
|
||||
{
|
||||
Key,
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum StationAiSpriteState : byte
|
||||
{
|
||||
Key,
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum StationAiState : byte
|
||||
{
|
||||
Empty,
|
||||
Occupied,
|
||||
Dead,
|
||||
Hologram,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Silicons.StationAi;
|
||||
|
||||
/// <summary>
|
||||
/// Holds data for altering the appearance of station AIs.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class StationAiCustomizationComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Dictionary of the prototype data used for customizing the appearance of the entity.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public Dictionary<ProtoId<StationAiCustomizationGroupPrototype>, ProtoId<StationAiCustomizationPrototype>> ProtoIds = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Message sent to server that contains a station AI customization that the client has selected
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class StationAiCustomizationMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly ProtoId<StationAiCustomizationGroupPrototype> GroupProtoId;
|
||||
public readonly ProtoId<StationAiCustomizationPrototype> CustomizationProtoId;
|
||||
|
||||
public StationAiCustomizationMessage(ProtoId<StationAiCustomizationGroupPrototype> groupProtoId, ProtoId<StationAiCustomizationPrototype> customizationProtoId)
|
||||
{
|
||||
GroupProtoId = groupProtoId;
|
||||
CustomizationProtoId = customizationProtoId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Key for opening the station AI customization UI
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum StationAiCustomizationUiKey : byte
|
||||
{
|
||||
Key,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The different catagories of station Ai customizations available
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum StationAiCustomizationType : byte
|
||||
{
|
||||
CoreIconography,
|
||||
Hologram,
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Silicons.StationAi;
|
||||
|
||||
/// <summary>
|
||||
/// Holds data for customizing the appearance of station AIs.
|
||||
/// </summary>
|
||||
[Prototype]
|
||||
public sealed partial class StationAiCustomizationGroupPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The localized name of the customization.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public LocId Name;
|
||||
|
||||
/// <summary>
|
||||
/// The type of customization that is associated with this group.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public StationAiCustomizationType Category = StationAiCustomizationType.CoreIconography;
|
||||
|
||||
/// <summary>
|
||||
/// The list of prototypes associated with the customization group.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public List<ProtoId<StationAiCustomizationPrototype>> ProtoIds = new();
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Silicons.StationAi;
|
||||
|
||||
/// <summary>
|
||||
/// Holds data for customizing the appearance of station AIs.
|
||||
/// </summary>
|
||||
[Prototype]
|
||||
public sealed partial class StationAiCustomizationPrototype : IPrototype, IInheritingPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The (unlocalized) name of the customization.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public LocId Name;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the data which is used to modify the appearance of the station AI.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public Dictionary<string, PrototypeLayerData> LayerData = new();
|
||||
|
||||
/// <summary>
|
||||
/// Key used to index the prototype layer data and extract a preview of the customization (for menus, etc)
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string PreviewKey = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies a background to use for previewing the customization (for menus, etc)
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SpriteSpecifier? PreviewBackground;
|
||||
|
||||
/// <summary>
|
||||
/// The prototype we inherit from.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[ParentDataFieldAttribute(typeof(AbstractPrototypeIdArraySerializer<StationAiCustomizationPrototype>))]
|
||||
public string[]? Parents { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether the prototype is abstract.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[NeverPushInheritance]
|
||||
[AbstractDataField]
|
||||
public bool Abstract { get; }
|
||||
}
|
||||
@@ -22,3 +22,25 @@ toggle-light = Toggle light
|
||||
ai-device-not-responding = Device is not responding
|
||||
|
||||
ai-consciousness-download-warning = Your consciousness is being downloaded.
|
||||
|
||||
# UI
|
||||
station-ai-customization-menu = AI customization
|
||||
station-ai-customization-categories = Categories
|
||||
station-ai-customization-options = Options (choice of one)
|
||||
station-ai-customization-core = AI core displays
|
||||
station-ai-customization-hologram = Holographic avatars
|
||||
|
||||
# Customizations
|
||||
station-ai-icon-ai = Ghost in the machine
|
||||
station-ai-icon-angel = Guardian angel
|
||||
station-ai-icon-bliss = Simpler times
|
||||
station-ai-icon-clown = Clownin' around
|
||||
station-ai-icon-dorf = Adventure awaits
|
||||
station-ai-icon-heartline = Lifeline
|
||||
station-ai-icon-smiley = All smiles
|
||||
|
||||
station-ai-hologram-female = Female appearance
|
||||
station-ai-hologram-male = Male appearance
|
||||
station-ai-hologram-face = Disembodied head
|
||||
station-ai-hologram-cat = Cat form
|
||||
station-ai-hologram-dog = Corgi form
|
||||
163
Resources/Prototypes/AppearanceCustomization/station_ai.yml
Normal file
@@ -0,0 +1,163 @@
|
||||
# Groups
|
||||
- type: stationAiCustomizationGroup
|
||||
id: StationAiCoreIconography
|
||||
name: station-ai-customization-core
|
||||
category: CoreIconography
|
||||
protoIds:
|
||||
- StationAiIconAi
|
||||
- StationAiIconAngel
|
||||
- StationAiIconBliss
|
||||
- StationAiIconClown
|
||||
- StationAiIconDorf
|
||||
- StationAiIconHeartline
|
||||
- StationAiIconSmiley
|
||||
|
||||
- type: stationAiCustomizationGroup
|
||||
id: StationAiHolograms
|
||||
name: station-ai-customization-hologram
|
||||
category: Hologram
|
||||
protoIds:
|
||||
- StationAiHologramFemale
|
||||
- StationAiHologramMale
|
||||
- StationAiHologramFace
|
||||
- StationAiHologramCat
|
||||
- StationAiHologramDog
|
||||
|
||||
# Iconography
|
||||
- type: stationAiCustomization
|
||||
abstract: true
|
||||
id: StationAiIconBase
|
||||
previewKey: Occupied
|
||||
previewBackground:
|
||||
sprite: Mobs/Silicon/station_ai.rsi
|
||||
state: base
|
||||
|
||||
- type: stationAiCustomization
|
||||
parent: StationAiIconBase
|
||||
id: StationAiIconAi
|
||||
name: station-ai-icon-ai
|
||||
layerData:
|
||||
Occupied:
|
||||
sprite: Mobs/Silicon/station_ai.rsi
|
||||
state: ai
|
||||
Dead:
|
||||
sprite: Mobs/Silicon/station_ai.rsi
|
||||
state: ai_dead
|
||||
|
||||
- type: stationAiCustomization
|
||||
parent: StationAiIconBase
|
||||
id: StationAiIconAngel
|
||||
name: station-ai-icon-angel
|
||||
layerData:
|
||||
Occupied:
|
||||
sprite: Mobs/Silicon/station_ai.rsi
|
||||
state: ai_angel
|
||||
Dead:
|
||||
sprite: Mobs/Silicon/station_ai.rsi
|
||||
state: ai_angel_dead
|
||||
|
||||
- type: stationAiCustomization
|
||||
parent: StationAiIconBase
|
||||
id: StationAiIconBliss
|
||||
name: station-ai-icon-bliss
|
||||
layerData:
|
||||
Occupied:
|
||||
sprite: Mobs/Silicon/station_ai.rsi
|
||||
state: ai_bliss
|
||||
Dead:
|
||||
sprite: Mobs/Silicon/station_ai.rsi
|
||||
state: ai_dead
|
||||
|
||||
- type: stationAiCustomization
|
||||
parent: StationAiIconBase
|
||||
id: StationAiIconClown
|
||||
name: station-ai-icon-clown
|
||||
layerData:
|
||||
Occupied:
|
||||
sprite: Mobs/Silicon/station_ai.rsi
|
||||
state: ai_clown
|
||||
Dead:
|
||||
sprite: Mobs/Silicon/station_ai.rsi
|
||||
state: ai_clown_dead
|
||||
|
||||
- type: stationAiCustomization
|
||||
parent: StationAiIconBase
|
||||
id: StationAiIconDorf
|
||||
name: station-ai-icon-dorf
|
||||
layerData:
|
||||
Occupied:
|
||||
sprite: Mobs/Silicon/station_ai.rsi
|
||||
state: ai_dorf
|
||||
Dead:
|
||||
sprite: Mobs/Silicon/station_ai.rsi
|
||||
state: ai_dead
|
||||
|
||||
- type: stationAiCustomization
|
||||
parent: StationAiIconBase
|
||||
id: StationAiIconHeartline
|
||||
name: station-ai-icon-heartline
|
||||
layerData:
|
||||
Occupied:
|
||||
sprite: Mobs/Silicon/station_ai.rsi
|
||||
state: "ai_heartline"
|
||||
Dead:
|
||||
sprite: Mobs/Silicon/station_ai.rsi
|
||||
state: "ai_heartline_dead"
|
||||
|
||||
- type: stationAiCustomization
|
||||
parent: StationAiIconBase
|
||||
id: StationAiIconSmiley
|
||||
name: station-ai-icon-smiley
|
||||
layerData:
|
||||
Occupied:
|
||||
sprite: Mobs/Silicon/station_ai.rsi
|
||||
state: "ai_smiley"
|
||||
Dead:
|
||||
sprite: Mobs/Silicon/station_ai.rsi
|
||||
state: "ai_dead"
|
||||
|
||||
# Holograms
|
||||
- type: stationAiCustomization
|
||||
id: StationAiHologramFemale
|
||||
name: station-ai-hologram-female
|
||||
previewKey: Hologram
|
||||
layerData:
|
||||
Hologram:
|
||||
sprite: Mobs/Silicon/holograms.rsi
|
||||
state: ai_female
|
||||
|
||||
- type: stationAiCustomization
|
||||
id: StationAiHologramMale
|
||||
name: station-ai-hologram-male
|
||||
previewKey: Hologram
|
||||
layerData:
|
||||
Hologram:
|
||||
sprite: Mobs/Silicon/holograms.rsi
|
||||
state: ai_male
|
||||
|
||||
- type: stationAiCustomization
|
||||
id: StationAiHologramFace
|
||||
name: station-ai-hologram-face
|
||||
previewKey: Hologram
|
||||
layerData:
|
||||
Hologram:
|
||||
sprite: Mobs/Silicon/holograms.rsi
|
||||
state: ai_face
|
||||
|
||||
- type: stationAiCustomization
|
||||
id: StationAiHologramCat
|
||||
name: station-ai-hologram-cat
|
||||
previewKey: Hologram
|
||||
layerData:
|
||||
Hologram:
|
||||
sprite: Mobs/Silicon/holograms.rsi
|
||||
state: ai_cat
|
||||
|
||||
- type: stationAiCustomization
|
||||
id: StationAiHologramDog
|
||||
name: station-ai-hologram-dog
|
||||
previewKey: Hologram
|
||||
layerData:
|
||||
Hologram:
|
||||
sprite: Mobs/Silicon/holograms.rsi
|
||||
state: ai_dog
|
||||
@@ -70,10 +70,6 @@
|
||||
canShuttle: false
|
||||
title: comms-console-announcement-title-station-ai
|
||||
color: "#5ed7aa"
|
||||
- type: HolographicAvatar
|
||||
layerData:
|
||||
- sprite: Mobs/Silicon/station_ai.rsi
|
||||
state: default
|
||||
- type: ShowJobIcons
|
||||
|
||||
- type: entity
|
||||
@@ -202,8 +198,11 @@
|
||||
layers:
|
||||
- state: base
|
||||
- state: ai_empty
|
||||
map: ["unshaded"]
|
||||
shader: unshaded
|
||||
- state: ai
|
||||
map: ["enum.StationAiVisualState.Key"]
|
||||
shader: unshaded
|
||||
visible: false
|
||||
- type: Appearance
|
||||
- type: InteractionPopup
|
||||
interactSuccessString: petting-success-station-ai
|
||||
@@ -211,12 +210,6 @@
|
||||
messagePerceivedByOthers: petting-success-station-ai-others # Otherwise AI cannot tell its being pet as It's just a brain inside of the core, not the core itself.
|
||||
interactSuccessSound:
|
||||
path: /Audio/Ambience/Objects/periodic_beep.ogg
|
||||
- type: GenericVisualizer
|
||||
visuals:
|
||||
enum.StationAiVisualState.Key:
|
||||
unshaded:
|
||||
Empty: { state: ai_empty }
|
||||
Occupied: { state: ai }
|
||||
- type: Telephone
|
||||
compatibleRanges:
|
||||
- Grid
|
||||
@@ -234,6 +227,8 @@
|
||||
type: HolopadBoundUserInterface
|
||||
enum.HolopadUiKey.AiActionWindow:
|
||||
type: HolopadBoundUserInterface
|
||||
enum.StationAiCustomizationUiKey.Key:
|
||||
type: StationAiCustomizationBoundUserInterface
|
||||
|
||||
# The job-ready version of an AI spawn.
|
||||
- type: entity
|
||||
@@ -244,12 +239,6 @@
|
||||
- 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
|
||||
@@ -294,6 +283,14 @@
|
||||
- type: StartingMindRole
|
||||
mindRole: "MindRoleSiliconBrain"
|
||||
silent: true
|
||||
- type: StationAiCustomization
|
||||
protoIds:
|
||||
StationAiCoreIconography: StationAiIconAi
|
||||
StationAiHolograms: StationAiHologramFemale
|
||||
- type: HolographicAvatar
|
||||
layerData:
|
||||
- sprite: Mobs/Silicon/holograms.rsi
|
||||
state: ai_female
|
||||
- type: NameIdentifier
|
||||
group: StationAi
|
||||
|
||||
|
||||
BIN
Resources/Textures/Mobs/Silicon/holograms.rsi/ai_cat.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
Resources/Textures/Mobs/Silicon/holograms.rsi/ai_dog.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
Resources/Textures/Mobs/Silicon/holograms.rsi/ai_face.png
Normal file
|
After Width: | Height: | Size: 625 B |
BIN
Resources/Textures/Mobs/Silicon/holograms.rsi/ai_female.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
Resources/Textures/Mobs/Silicon/holograms.rsi/ai_male.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
38
Resources/Textures/Mobs/Silicon/holograms.rsi/meta.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from vgstation at https://github.com/vgstation-coders/vgstation13/blob/e2923df112df8aa025846d0764697bad6506586a/icons/mob/AI.dmi - modified by chromiumboy.",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "ai_female"
|
||||
},
|
||||
{
|
||||
"name": "ai_male"
|
||||
},
|
||||
{
|
||||
"name": "ai_face",
|
||||
"delays": [
|
||||
[
|
||||
2.3,
|
||||
0.2
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ai_cat",
|
||||
"delays": [
|
||||
[
|
||||
0.75,
|
||||
0.75
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ai_dog"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_angel.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_angel_dead.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_bliss.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_clown.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_clown_dead.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_dorf.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_heartline.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
BIN
Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_smiley.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"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",
|
||||
"copyright": "Taken from vgstation at https://github.com/vgstation-coders/vgstation13/blob/e2923df112df8aa025846d0764697bad6506586a/icons/mob/AI.dmi - modified by chromiumboy.",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
@@ -29,6 +29,160 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ai_angel",
|
||||
"delays": [
|
||||
[
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08,
|
||||
0.08
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ai_angel_dead",
|
||||
"delays": [
|
||||
[
|
||||
1.00,
|
||||
0.08,
|
||||
0.50,
|
||||
0.20
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ai_bliss"
|
||||
},
|
||||
{
|
||||
"name": "ai_clown",
|
||||
"delays": [
|
||||
[
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ai_clown_dead",
|
||||
"delays": [
|
||||
[
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ai_dorf",
|
||||
"delays": [
|
||||
[
|
||||
0.5,
|
||||
0.5
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ai_heartline",
|
||||
"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
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ai_heartline_dead",
|
||||
"delays": [
|
||||
[
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ai_smiley",
|
||||
"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
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ai_camera",
|
||||
"delays": [
|
||||
|
||||