diff --git a/Content.Client/Silicons/StationAi/StationAiCustomizationBoundUserInterface.cs b/Content.Client/Silicons/StationAi/StationAiCustomizationBoundUserInterface.cs new file mode 100644 index 0000000000..1094d2f4fa --- /dev/null +++ b/Content.Client/Silicons/StationAi/StationAiCustomizationBoundUserInterface.cs @@ -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 groupProtoId, ProtoId customizationProtoId) + { + SendPredictedMessage(new StationAiCustomizationMessage(groupProtoId, customizationProtoId)); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (!disposing) + return; + + _menu?.Dispose(); + } +} diff --git a/Content.Client/Silicons/StationAi/StationAiCustomizationMenu.xaml b/Content.Client/Silicons/StationAi/StationAiCustomizationMenu.xaml new file mode 100644 index 0000000000..c03bc40e39 --- /dev/null +++ b/Content.Client/Silicons/StationAi/StationAiCustomizationMenu.xaml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/Content.Client/Silicons/StationAi/StationAiCustomizationMenu.xaml.cs b/Content.Client/Silicons/StationAi/StationAiCustomizationMenu.xaml.cs new file mode 100644 index 0000000000..009969196b --- /dev/null +++ b/Content.Client/Silicons/StationAi/StationAiCustomizationMenu.xaml.cs @@ -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, StationAiCustomizationGroupContainer> _groupContainers = new(); + private Dictionary, ButtonGroup> _buttonGroups = new(); + + public event Action, ProtoId>? SendStationAiCustomizationMessageAction; + + private const float IconScale = 1.75f; + + public StationAiCustomizationMenu(EntityUid owner) + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + var stationAiSystem = _entManager.System(); + + // Load customziation data + _entManager.TryGetComponent(owner, out var stationAiCore); + stationAiSystem.TryGetHeld((owner, stationAiCore), out var insertedAi); + _entManager.TryGetComponent(insertedAi, out var stationAiCustomization); + + // Create UI entires for each group of customizations + var groupPrototypes = _protoManager.EnumeratePrototypes(); + 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 groupProtoId, ProtoId 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 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); + } + } +} + + diff --git a/Content.Client/Silicons/StationAi/StationAiSystem.cs b/Content.Client/Silicons/StationAi/StationAiSystem.cs index ab9ace3c1d..75588eda39 100644 --- a/Content.Client/Silicons/StationAi/StationAiSystem.cs +++ b/Content.Client/Silicons/StationAi/StationAiSystem.cs @@ -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(OnAiDetached); SubscribeLocalEvent(OnAiOverlayInit); SubscribeLocalEvent(OnAiOverlayRemove); + SubscribeLocalEvent(OnAppearanceChange); } private void OnAiOverlayInit(Entity ent, ref ComponentInit args) @@ -72,6 +75,17 @@ public sealed partial class StationAiSystem : SharedStationAiSystem RemoveOverlay(); } + private void OnAppearanceChange(Entity entity, ref AppearanceChangeEvent args) + { + if (args.Sprite == null) + return; + + if (_appearance.TryGetData(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(); diff --git a/Content.Shared/Holopad/HolographicAvatarComponent.cs b/Content.Shared/Holopad/HolographicAvatarComponent.cs index cff71f4fb2..0e475ff691 100644 --- a/Content.Shared/Holopad/HolographicAvatarComponent.cs +++ b/Content.Shared/Holopad/HolographicAvatarComponent.cs @@ -9,5 +9,5 @@ public sealed partial class HolographicAvatarComponent : Component /// The prototype sprite layer data for the hologram /// [DataField, AutoNetworkedField] - public PrototypeLayerData[] LayerData; + public PrototypeLayerData[]? LayerData = null; } diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Customization.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Customization.cs new file mode 100644 index 0000000000..d3f3fe4297 --- /dev/null +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Customization.cs @@ -0,0 +1,82 @@ +using Content.Shared.Holopad; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Silicons.StationAi; + +public abstract partial class SharedStationAiSystem +{ + private ProtoId _stationAiCoreCustomGroupProtoId = "StationAiCoreIconography"; + private ProtoId _stationAiHologramCustomGroupProtoId = "StationAiHolograms"; + + private void InitializeCustomization() + { + SubscribeLocalEvent(OnStationAiCustomization); + } + + private void OnStationAiCustomization(Entity 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(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(entity, out var stationAiHolder)) + UpdateAppearance((entity, stationAiHolder)); + } + + private void UpdateHolographicAvatar(Entity entity) + { + if (!TryComp(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 entity, StationAiState state) + { + var stationAi = GetInsertedAI(entity); + + if (stationAi == null) + { + _appearance.RemoveData(entity.Owner, StationAiVisualState.Key); + return; + } + + if (!TryComp(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); + } +} diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index 372a758f80..1615ab0fa3 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -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(OnAiBuiCheck); @@ -107,25 +110,35 @@ public abstract partial class SharedStationAiSystem : EntitySystem 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() + // Admin option to take over the station AI core + if (_admin.IsAdmin(args.User) && + !TryGetHeld((ent.Owner, ent.Comp), out _)) { - Text = Loc.GetString("station-ai-takeover"), - Category = VerbCategory.Debug, - Act = () => + args.Verbs.Add(new Verb() { - var brain = SpawnInContainerOrDrop(DefaultAi, ent.Owner, StationAiCoreComponent.Container); - _mind.ControlMob(user, brain); - }, - Impact = LogImpact.High, - }); + 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, + }); + } + + // 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 ent, ref AccessibleOverrideEvent args) @@ -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(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, } diff --git a/Content.Shared/Silicons/StationAi/StationAiCustomizationComponent.cs b/Content.Shared/Silicons/StationAi/StationAiCustomizationComponent.cs new file mode 100644 index 0000000000..a2b713edfe --- /dev/null +++ b/Content.Shared/Silicons/StationAi/StationAiCustomizationComponent.cs @@ -0,0 +1,53 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Silicons.StationAi; + +/// +/// Holds data for altering the appearance of station AIs. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class StationAiCustomizationComponent : Component +{ + /// + /// Dictionary of the prototype data used for customizing the appearance of the entity. + /// + [DataField, AutoNetworkedField] + public Dictionary, ProtoId> ProtoIds = new(); +} + +/// +/// Message sent to server that contains a station AI customization that the client has selected +/// +[Serializable, NetSerializable] +public sealed class StationAiCustomizationMessage : BoundUserInterfaceMessage +{ + public readonly ProtoId GroupProtoId; + public readonly ProtoId CustomizationProtoId; + + public StationAiCustomizationMessage(ProtoId groupProtoId, ProtoId customizationProtoId) + { + GroupProtoId = groupProtoId; + CustomizationProtoId = customizationProtoId; + } +} + +/// +/// Key for opening the station AI customization UI +/// +[Serializable, NetSerializable] +public enum StationAiCustomizationUiKey : byte +{ + Key, +} + +/// +/// The different catagories of station Ai customizations available +/// +[Serializable, NetSerializable] +public enum StationAiCustomizationType : byte +{ + CoreIconography, + Hologram, +} diff --git a/Content.Shared/Silicons/StationAi/StationAiCustomizationGroupPrototype.cs b/Content.Shared/Silicons/StationAi/StationAiCustomizationGroupPrototype.cs new file mode 100644 index 0000000000..bbb687e636 --- /dev/null +++ b/Content.Shared/Silicons/StationAi/StationAiCustomizationGroupPrototype.cs @@ -0,0 +1,31 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Silicons.StationAi; + +/// +/// Holds data for customizing the appearance of station AIs. +/// +[Prototype] +public sealed partial class StationAiCustomizationGroupPrototype : IPrototype +{ + [IdDataField] + public string ID { get; } = string.Empty; + + /// + /// The localized name of the customization. + /// + [DataField(required: true)] + public LocId Name; + + /// + /// The type of customization that is associated with this group. + /// + [DataField] + public StationAiCustomizationType Category = StationAiCustomizationType.CoreIconography; + + /// + /// The list of prototypes associated with the customization group. + /// + [DataField(required: true)] + public List> ProtoIds = new(); +} diff --git a/Content.Shared/Silicons/StationAi/StationAiCustomizationPrototype.cs b/Content.Shared/Silicons/StationAi/StationAiCustomizationPrototype.cs new file mode 100644 index 0000000000..5fe3891b0d --- /dev/null +++ b/Content.Shared/Silicons/StationAi/StationAiCustomizationPrototype.cs @@ -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; + +/// +/// Holds data for customizing the appearance of station AIs. +/// +[Prototype] +public sealed partial class StationAiCustomizationPrototype : IPrototype, IInheritingPrototype +{ + [IdDataField] + public string ID { get; } = string.Empty; + + /// + /// The (unlocalized) name of the customization. + /// + [DataField(required: true)] + public LocId Name; + + /// + /// Stores the data which is used to modify the appearance of the station AI. + /// + [DataField(required: true)] + public Dictionary LayerData = new(); + + /// + /// Key used to index the prototype layer data and extract a preview of the customization (for menus, etc) + /// + [DataField] + public string PreviewKey = string.Empty; + + /// + /// Specifies a background to use for previewing the customization (for menus, etc) + /// + [DataField] + public SpriteSpecifier? PreviewBackground; + + /// + /// The prototype we inherit from. + /// + [ViewVariables] + [ParentDataFieldAttribute(typeof(AbstractPrototypeIdArraySerializer))] + public string[]? Parents { get; } + + /// + /// Specifies whether the prototype is abstract. + /// + [ViewVariables] + [NeverPushInheritance] + [AbstractDataField] + public bool Abstract { get; } +} diff --git a/Resources/Locale/en-US/silicons/station-ai.ftl b/Resources/Locale/en-US/silicons/station-ai.ftl index 76c30eb101..abdddbe1e0 100644 --- a/Resources/Locale/en-US/silicons/station-ai.ftl +++ b/Resources/Locale/en-US/silicons/station-ai.ftl @@ -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 \ No newline at end of file diff --git a/Resources/Prototypes/AppearanceCustomization/station_ai.yml b/Resources/Prototypes/AppearanceCustomization/station_ai.yml new file mode 100644 index 0000000000..465cfdf648 --- /dev/null +++ b/Resources/Prototypes/AppearanceCustomization/station_ai.yml @@ -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 diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 9a1a4c88cf..d70c13187f 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -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 @@ -230,10 +223,12 @@ - type: StationAiWhitelist - type: UserInterface interfaces: - enum.HolopadUiKey.AiRequestWindow: - type: HolopadBoundUserInterface - enum.HolopadUiKey.AiActionWindow: - type: HolopadBoundUserInterface + enum.HolopadUiKey.AiRequestWindow: + 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 diff --git a/Resources/Textures/Mobs/Silicon/holograms.rsi/ai_cat.png b/Resources/Textures/Mobs/Silicon/holograms.rsi/ai_cat.png new file mode 100644 index 0000000000..6413df0bf1 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/holograms.rsi/ai_cat.png differ diff --git a/Resources/Textures/Mobs/Silicon/holograms.rsi/ai_dog.png b/Resources/Textures/Mobs/Silicon/holograms.rsi/ai_dog.png new file mode 100644 index 0000000000..de652a87a1 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/holograms.rsi/ai_dog.png differ diff --git a/Resources/Textures/Mobs/Silicon/holograms.rsi/ai_face.png b/Resources/Textures/Mobs/Silicon/holograms.rsi/ai_face.png new file mode 100644 index 0000000000..f3a12a55c7 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/holograms.rsi/ai_face.png differ diff --git a/Resources/Textures/Mobs/Silicon/holograms.rsi/ai_female.png b/Resources/Textures/Mobs/Silicon/holograms.rsi/ai_female.png new file mode 100644 index 0000000000..c871354f34 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/holograms.rsi/ai_female.png differ diff --git a/Resources/Textures/Mobs/Silicon/holograms.rsi/ai_male.png b/Resources/Textures/Mobs/Silicon/holograms.rsi/ai_male.png new file mode 100644 index 0000000000..aedd762bee Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/holograms.rsi/ai_male.png differ diff --git a/Resources/Textures/Mobs/Silicon/holograms.rsi/meta.json b/Resources/Textures/Mobs/Silicon/holograms.rsi/meta.json new file mode 100644 index 0000000000..8eaf9165d3 --- /dev/null +++ b/Resources/Textures/Mobs/Silicon/holograms.rsi/meta.json @@ -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" + } + ] +} diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_angel.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_angel.png new file mode 100644 index 0000000000..fd9a601d21 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_angel.png differ diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_angel_dead.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_angel_dead.png new file mode 100644 index 0000000000..2d959b8414 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_angel_dead.png differ diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_bliss.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_bliss.png new file mode 100644 index 0000000000..d087d3baa4 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_bliss.png differ diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_clown.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_clown.png new file mode 100644 index 0000000000..842b3f4387 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_clown.png differ diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_clown_dead.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_clown_dead.png new file mode 100644 index 0000000000..1f1050ecc3 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_clown_dead.png differ diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_dorf.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_dorf.png new file mode 100644 index 0000000000..e25aa6d1d2 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_dorf.png differ diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_heartline.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_heartline.png new file mode 100644 index 0000000000..d4518fd423 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_heartline.png differ diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_heartline_dead.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_heartline_dead.png new file mode 100644 index 0000000000..4b9f3f9d81 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_heartline_dead.png differ diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_smiley.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_smiley.png new file mode 100644 index 0000000000..6e3c3e7e8f Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_smiley.png differ diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json b/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json index c8d174c57b..bdce787a48 100644 --- a/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json +++ b/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json @@ -1,61 +1,215 @@ { - "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 - ] - ] + "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 }, - { - "name": "ai_camera", - "delays": [ - [ - 1.0, - 1.0 - ] - ] - }, - { - "name": "ai_dead" - }, - { - "name": "ai_empty", - "delays": [ - [ - 0.7, - 0.7 - ] - ] - }, - { - "name": "default", - "directions": 4 - }, - { - "name": "base" - } - ] -} \ No newline at end of file + "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_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": [ + [ + 1.0, + 1.0 + ] + ] + }, + { + "name": "ai_dead" + }, + { + "name": "ai_empty", + "delays": [ + [ + 0.7, + 0.7 + ] + ] + }, + { + "name": "default", + "directions": 4 + }, + { + "name": "base" + } + ] +}