Station AI customizations (#34501)
Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
This commit is contained in:
@@ -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,25 +110,35 @@ 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;
|
||||
|
||||
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<StationAiOverlayComponent> 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<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; }
|
||||
}
|
||||
Reference in New Issue
Block a user