Merge remote-tracking branch 'space-station-14/master' into 29-04-2024-upstream
# Conflicts: # Resources/Prototypes/Entities/Mobs/Customization/Markings/human_hair.yml
This commit is contained in:
@@ -68,9 +68,10 @@ public sealed class GetItemActionsEvent : EntityEventArgs
|
||||
AddAction(ref actionId, prototypeId, Provider);
|
||||
}
|
||||
|
||||
public void AddAction(EntityUid actionId)
|
||||
public void AddAction(EntityUid? actionId)
|
||||
{
|
||||
Actions.Add(actionId);
|
||||
if (actionId != null)
|
||||
Actions.Add(actionId.Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,11 @@
|
||||
/// </summary>
|
||||
Stealth = 1 << 16,
|
||||
|
||||
///<summary>
|
||||
/// Allows you to use Admin chat
|
||||
///</summary>
|
||||
Adminchat = 1 << 17,
|
||||
|
||||
/// <summary>
|
||||
/// Dangerous host permissions like scsi.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Body.Prototypes
|
||||
{
|
||||
@@ -7,5 +7,11 @@ namespace Content.Shared.Body.Prototypes
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
[DataField("name", required: true)]
|
||||
private LocId Name { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public string LocalizedName => Loc.GetString(Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Body.Prototypes
|
||||
{
|
||||
@@ -9,6 +9,9 @@ namespace Content.Shared.Body.Prototypes
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
[DataField("name", required: true)]
|
||||
public string Name { get; private set; } = default!;
|
||||
private LocId Name { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public string LocalizedName => Loc.GetString(Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,6 +435,12 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<string> LoginTipsDataset =
|
||||
CVarDef.Create("tips.login_dataset", "Tips");
|
||||
|
||||
/// <summary>
|
||||
/// The chance for Tippy to replace a normal tip message.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> TipsTippyChance =
|
||||
CVarDef.Create("tips.tippy_chance", 0.01f);
|
||||
|
||||
/*
|
||||
* Console
|
||||
*/
|
||||
@@ -1994,6 +2000,10 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<bool> GatewayGeneratorEnabled =
|
||||
CVarDef.Create("gateway.generator_enabled", true);
|
||||
|
||||
// Clippy!
|
||||
public static readonly CVarDef<string> TippyEntity =
|
||||
CVarDef.Create("tippy.entity", "Tippy", CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/*
|
||||
* DEBUG
|
||||
*/
|
||||
|
||||
11
Content.Shared/Chat/EmotesEvents.cs
Normal file
11
Content.Shared/Chat/EmotesEvents.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Chat;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class PlayEmoteMessage(ProtoId<EmotePrototype> protoId) : EntityEventArgs
|
||||
{
|
||||
public readonly ProtoId<EmotePrototype> ProtoId = protoId;
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Chat.Prototypes;
|
||||
|
||||
/// <summary>
|
||||
/// IC emotes (scream, smile, clapping, etc).
|
||||
/// Entities can activate emotes by chat input or code.
|
||||
/// Entities can activate emotes by chat input, radial or code.
|
||||
/// </summary>
|
||||
[Prototype("emote")]
|
||||
public sealed partial class EmotePrototype : IPrototype
|
||||
@@ -13,18 +15,50 @@ public sealed partial class EmotePrototype : IPrototype
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Localization string for the emote name. Displayed in the radial UI.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public string Name = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if emote available to all by default
|
||||
/// <see cref="Whitelist"/> check comes after this setting
|
||||
/// <see cref="Content.Shared.Speech.SpeechComponent.AllowedEmotes"/> can ignore this setting
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Available = true;
|
||||
|
||||
/// <summary>
|
||||
/// Different emote categories may be handled by different systems.
|
||||
/// Also may be used for filtering.
|
||||
/// </summary>
|
||||
[DataField("category")]
|
||||
[DataField]
|
||||
public EmoteCategory Category = EmoteCategory.General;
|
||||
|
||||
/// <summary>
|
||||
/// An icon used to visually represent the emote in radial UI.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SpriteSpecifier Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/Actions/scream.png"));
|
||||
|
||||
/// <summary>
|
||||
/// Determines conditions to this emote be available to use
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// Determines conditions to this emote be unavailable to use
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist? Blacklist;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of words that will be sent to chat if emote activates.
|
||||
/// Will be picked randomly from list.
|
||||
/// </summary>
|
||||
[DataField("chatMessages")]
|
||||
[DataField]
|
||||
public List<string> ChatMessages = new();
|
||||
|
||||
/// <summary>
|
||||
@@ -32,7 +66,7 @@ public sealed partial class EmotePrototype : IPrototype
|
||||
/// When typed into players chat they will activate emote event.
|
||||
/// All words should be unique across all emote prototypes.
|
||||
/// </summary>
|
||||
[DataField("chatTriggers")]
|
||||
[DataField]
|
||||
public HashSet<string> ChatTriggers = new();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
|
||||
namespace Content.Shared.Chat.Prototypes;
|
||||
@@ -8,8 +9,8 @@ namespace Content.Shared.Chat.Prototypes;
|
||||
/// Sounds collection for each <see cref="EmotePrototype"/>.
|
||||
/// Different entities may use different sounds collections.
|
||||
/// </summary>
|
||||
[Prototype("emoteSounds")]
|
||||
public sealed partial class EmoteSoundsPrototype : IPrototype
|
||||
[Prototype("emoteSounds"), Serializable, NetSerializable]
|
||||
public sealed class EmoteSoundsPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
@@ -96,20 +96,23 @@ public abstract class ClothingSystem : EntitySystem
|
||||
{
|
||||
if (TryComp(item, out HideLayerClothingComponent? comp))
|
||||
{
|
||||
//Checks for mask toggling. TODO: Make a generic system for this
|
||||
if (comp.HideOnToggle && TryComp(item, out MaskComponent? mask) && TryComp(item, out ClothingComponent? clothing))
|
||||
if (comp.Slots.Contains(layer))
|
||||
{
|
||||
if (clothing.EquippedPrefix != mask.EquippedPrefix)
|
||||
//Checks for mask toggling. TODO: Make a generic system for this
|
||||
if (comp.HideOnToggle && TryComp(item, out MaskComponent? mask) && TryComp(item, out ClothingComponent? clothing))
|
||||
{
|
||||
if (clothing.EquippedPrefix != mask.EquippedPrefix)
|
||||
{
|
||||
shouldLayerShow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldLayerShow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldLayerShow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_humanoidSystem.SetLayerVisibility(equipee, layer, shouldLayerShow);
|
||||
|
||||
@@ -45,7 +45,7 @@ public sealed class MaskSystem : EntitySystem
|
||||
|
||||
var dir = mask.IsToggled ? "down" : "up";
|
||||
var msg = $"action-mask-pull-{dir}-popup-message";
|
||||
_popupSystem.PopupEntity(Loc.GetString(msg, ("mask", uid)), args.Performer, args.Performer);
|
||||
_popupSystem.PopupClient(Loc.GetString(msg, ("mask", uid)), args.Performer, args.Performer);
|
||||
|
||||
ToggleMaskComponents(uid, mask, args.Performer, mask.EquippedPrefix);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@ namespace Content.Shared.Damage.Prototypes
|
||||
{
|
||||
[IdDataField] public string ID { get; } = default!;
|
||||
|
||||
[DataField(required: true)]
|
||||
private LocId Name { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public string LocalizedName => Loc.GetString(Name);
|
||||
|
||||
[DataField("damageTypes", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<DamageTypePrototype>))]
|
||||
public List<string> DamageTypes { get; private set; } = default!;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@ namespace Content.Shared.Damage.Prototypes
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
[DataField(required: true)]
|
||||
private LocId Name { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public string LocalizedName => Loc.GetString(Name);
|
||||
|
||||
/// <summary>
|
||||
/// The price for each 1% damage reduction in armors
|
||||
/// </summary>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Events;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Damage.Systems;
|
||||
@@ -10,6 +12,7 @@ namespace Content.Shared.Damage.Systems;
|
||||
public sealed class DamageExamineSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ExamineSystemShared _examine = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -66,7 +69,7 @@ public sealed class DamageExamineSystem : EntitySystem
|
||||
if (damage.Value != FixedPoint2.Zero)
|
||||
{
|
||||
msg.PushNewline();
|
||||
msg.AddMarkup(Loc.GetString("damage-value", ("type", damage.Key), ("amount", damage.Value)));
|
||||
msg.AddMarkup(Loc.GetString("damage-value", ("type", _prototype.Index<DamageTypePrototype>(damage.Key).LocalizedName), ("amount", damage.Value)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,13 +36,6 @@ public abstract partial class SharedDisposalUnitComponent : Component
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("soundInsert")]
|
||||
public SoundSpecifier? InsertSound = new SoundPathSpecifier("/Audio/Effects/trashbag1.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Sound played when an item is thrown and misses the disposal unit.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("soundMiss")]
|
||||
public SoundSpecifier? MissSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg");
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// State for this disposals unit.
|
||||
/// </summary>
|
||||
|
||||
166
Content.Shared/Fax/Components/FaxMachineComponent.cs
Normal file
166
Content.Shared/Fax/Components/FaxMachineComponent.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Paper;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Fax.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class FaxMachineComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Name with which the fax will be visible to others on the network
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("name")]
|
||||
public string FaxName { get; set; } = "Unknown";
|
||||
|
||||
/// <summary>
|
||||
/// Sprite to use when inserting an object.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField, AutoNetworkedField]
|
||||
public string InsertingState = "inserting";
|
||||
|
||||
/// <summary>
|
||||
/// Device address of fax in network to which data will be send
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("destinationAddress")]
|
||||
public string? DestinationFaxAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Contains the item to be sent, assumes it's paper...
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ItemSlot PaperSlot = new();
|
||||
|
||||
/// <summary>
|
||||
/// Is fax machine should respond to pings in network
|
||||
/// This will make it visible to others on the network
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public bool ResponsePings { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should admins be notified on message receive
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public bool NotifyAdmins { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Should that fax receive nuke codes send by admins. Probably should be captain fax only
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public bool ReceiveNukeCodes { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when fax has been emagged
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier EmagSound = new SoundCollectionSpecifier("sparks");
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when fax printing new message
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier PrintSound = new SoundPathSpecifier("/Audio/Machines/printer.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when fax successfully send message
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier SendSound = new SoundPathSpecifier("/Audio/Machines/high_tech_confirm.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Known faxes in network by address with fax names
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Dictionary<string, string> KnownFaxes { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Print queue of the incoming message
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField]
|
||||
public Queue<FaxPrintout> PrintingQueue { get; private set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Message sending timeout
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField]
|
||||
public float SendTimeoutRemaining;
|
||||
|
||||
/// <summary>
|
||||
/// Message sending timeout
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField]
|
||||
public float SendTimeout = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// Remaining time of inserting animation
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float InsertingTimeRemaining;
|
||||
|
||||
/// <summary>
|
||||
/// How long the inserting animation will play
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float InsertionTime = 1.88f; // 0.02 off for correct animation
|
||||
|
||||
/// <summary>
|
||||
/// Remaining time of printing animation
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float PrintingTimeRemaining;
|
||||
|
||||
/// <summary>
|
||||
/// How long the printing animation will play
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float PrintingTime = 2.3f;
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class FaxPrintout
|
||||
{
|
||||
[DataField(required: true)]
|
||||
public string Name { get; private set; } = default!;
|
||||
|
||||
[DataField]
|
||||
public string? Label { get; private set; }
|
||||
|
||||
[DataField(required: true)]
|
||||
public string Content { get; private set; } = default!;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>), required: true)]
|
||||
public string PrototypeId { get; private set; } = default!;
|
||||
|
||||
[DataField("stampState")]
|
||||
public string? StampState { get; private set; }
|
||||
|
||||
[DataField("stampedBy")]
|
||||
public List<StampDisplayInfo> StampedBy { get; private set; } = new();
|
||||
|
||||
private FaxPrintout()
|
||||
{
|
||||
}
|
||||
|
||||
public FaxPrintout(string content, string name, string? label = null, string? prototypeId = null, string? stampState = null, List<StampDisplayInfo>? stampedBy = null)
|
||||
{
|
||||
Content = content;
|
||||
Name = name;
|
||||
Label = label;
|
||||
PrototypeId = prototypeId ?? "";
|
||||
StampState = stampState;
|
||||
StampedBy = stampedBy ?? new List<StampDisplayInfo>();
|
||||
}
|
||||
}
|
||||
16
Content.Shared/Fax/Components/FaxableObjectComponent.cs
Normal file
16
Content.Shared/Fax/Components/FaxableObjectComponent.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Fax.Components;
|
||||
/// <summary>
|
||||
/// Entity with this component can be faxed.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class FaxableObjectComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Sprite to use when inserting an object.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField, AutoNetworkedField]
|
||||
public string InsertingState = "inserting";
|
||||
}
|
||||
19
Content.Shared/Fax/Components/FaxecuteComponent.cs
Normal file
19
Content.Shared/Fax/Components/FaxecuteComponent.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Fax.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A fax component which stores a damage specifier for attempting to fax a mob.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class FaxecuteComponent : Component
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Type of damage dealt when entity is faxecuted.
|
||||
/// </summary>
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public DamageSpecifier Damage = new();
|
||||
}
|
||||
|
||||
9
Content.Shared/Fax/DamageOnFaxecuteEvent.cs
Normal file
9
Content.Shared/Fax/DamageOnFaxecuteEvent.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
namespace Content.Shared.Fax.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Event for killing any mob within the fax machine.
|
||||
/// </summary
|
||||
[ByRefEvent]
|
||||
public record struct DamageOnFaxecuteEvent(FaxMachineComponent? Action);
|
||||
|
||||
@@ -37,11 +37,13 @@ public sealed class FaxUiState : BoundUserInterfaceState
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class FaxFileMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public string? Label;
|
||||
public string Content;
|
||||
public bool OfficePaper;
|
||||
|
||||
public FaxFileMessage(string content, bool officePaper)
|
||||
public FaxFileMessage(string? label, string content, bool officePaper)
|
||||
{
|
||||
Label = label;
|
||||
Content = content;
|
||||
OfficePaper = officePaper;
|
||||
}
|
||||
@@ -49,6 +51,7 @@ public sealed class FaxFileMessage : BoundUserInterfaceMessage
|
||||
|
||||
public static class FaxFileMessageValidation
|
||||
{
|
||||
public const int MaxLabelSize = 50; // parity with Content.Server.Labels.Components.HandLabelerComponent.MaxLabelChars
|
||||
public const int MaxContentSize = 10000;
|
||||
}
|
||||
|
||||
|
||||
34
Content.Shared/Fax/Systems/FaxecuteSystem.cs
Normal file
34
Content.Shared/Fax/Systems/FaxecuteSystem.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Fax.Components;
|
||||
|
||||
namespace Content.Shared.Fax.Systems;
|
||||
/// <summary>
|
||||
/// System for handling execution of a mob within fax when copy or send attempt is made.
|
||||
/// </summary>
|
||||
public sealed class FaxecuteSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
}
|
||||
|
||||
public void Faxecute(EntityUid uid, FaxMachineComponent component, DamageOnFaxecuteEvent? args = null)
|
||||
{
|
||||
var sendEntity = component.PaperSlot.Item;
|
||||
if (sendEntity == null)
|
||||
return;
|
||||
|
||||
if (!TryComp<FaxecuteComponent>(uid, out var faxecute))
|
||||
return;
|
||||
|
||||
var damageSpec = faxecute.Damage;
|
||||
_damageable.TryChangeDamage(sendEntity, damageSpec);
|
||||
_popupSystem.PopupEntity(Loc.GetString("fax-machine-popup-error", ("target", uid)), uid, PopupType.LargeCaution);
|
||||
return;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,6 @@ public abstract partial class SharedHandsSystem
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="handsComp"></param>
|
||||
|
||||
public void RemoveHands(EntityUid uid, HandsComponent? handsComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp))
|
||||
@@ -137,6 +136,43 @@ public abstract partial class SharedHandsSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetActiveHand(Entity<HandsComponent?> entity, [NotNullWhen(true)] out Hand? hand)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
{
|
||||
hand = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
hand = entity.Comp.ActiveHand;
|
||||
return hand != null;
|
||||
}
|
||||
|
||||
public bool TryGetActiveItem(Entity<HandsComponent?> entity, [NotNullWhen(true)] out EntityUid? item)
|
||||
{
|
||||
if (!TryGetActiveHand(entity, out var hand))
|
||||
{
|
||||
item = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
item = hand.HeldEntity;
|
||||
return item != null;
|
||||
}
|
||||
|
||||
public Hand? GetActiveHand(Entity<HandsComponent?> entity)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp))
|
||||
return null;
|
||||
|
||||
return entity.Comp.ActiveHand;
|
||||
}
|
||||
|
||||
public EntityUid? GetActiveItem(Entity<HandsComponent?> entity)
|
||||
{
|
||||
return GetActiveHand(entity)?.HeldEntity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerate over hands, starting with the currently active hand.
|
||||
/// </summary>
|
||||
@@ -227,9 +263,17 @@ public abstract partial class SharedHandsSystem
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsHolding(EntityUid uid, EntityUid? entity, [NotNullWhen(true)] out Hand? inHand, HandsComponent? handsComp = null)
|
||||
public bool IsHolding(Entity<HandsComponent?> entity, [NotNullWhen(true)] EntityUid? item)
|
||||
{
|
||||
return IsHolding(entity, item, out _, entity);
|
||||
}
|
||||
|
||||
public bool IsHolding(EntityUid uid, [NotNullWhen(true)] EntityUid? entity, [NotNullWhen(true)] out Hand? inHand, HandsComponent? handsComp = null)
|
||||
{
|
||||
inHand = null;
|
||||
if (entity == null)
|
||||
return false;
|
||||
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Content.Shared.Input
|
||||
public static readonly BoundKeyFunction CycleChatChannelBackward = "CycleChatChannelBackward";
|
||||
public static readonly BoundKeyFunction EscapeContext = "EscapeContext";
|
||||
public static readonly BoundKeyFunction OpenCharacterMenu = "OpenCharacterMenu";
|
||||
public static readonly BoundKeyFunction OpenEmotesMenu = "OpenEmotesMenu";
|
||||
public static readonly BoundKeyFunction OpenCraftingMenu = "OpenCraftingMenu";
|
||||
public static readonly BoundKeyFunction OpenGuidebook = "OpenGuidebook";
|
||||
public static readonly BoundKeyFunction OpenInventoryMenu = "OpenInventoryMenu";
|
||||
|
||||
@@ -123,6 +123,11 @@ public sealed class InventoryRelayedEvent<TEvent> : EntityEventArgs
|
||||
}
|
||||
}
|
||||
|
||||
public interface IClothingSlots
|
||||
{
|
||||
SlotFlags Slots { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Events that should be relayed to inventory slots should implement this interface.
|
||||
/// </summary>
|
||||
|
||||
@@ -27,6 +27,31 @@ public partial class InventorySystem : EntitySystem
|
||||
.RemoveHandler(HandleViewVariablesSlots, ListViewVariablesSlots);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find an entity in the specified slot with the specified component.
|
||||
/// </summary>
|
||||
public bool TryGetInventoryEntity<T>(Entity<InventoryComponent?> entity, out EntityUid targetUid)
|
||||
where T : IComponent, IClothingSlots
|
||||
{
|
||||
if (TryGetContainerSlotEnumerator(entity.Owner, out var containerSlotEnumerator))
|
||||
{
|
||||
while (containerSlotEnumerator.NextItem(out var item, out var slot))
|
||||
{
|
||||
if (!TryComp<T>(item, out var required))
|
||||
continue;
|
||||
|
||||
if ((((IClothingSlots) required).Slots & slot.SlotFlags) == 0x0)
|
||||
continue;
|
||||
|
||||
targetUid = item;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
targetUid = EntityUid.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual void OnInit(EntityUid uid, InventoryComponent component, ComponentInit args)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(component.TemplateId, out InventoryTemplatePrototype? invTemplate))
|
||||
|
||||
@@ -32,4 +32,6 @@ public enum SlotFlags
|
||||
CLOAK = 1 << 19,
|
||||
KEYS = 1 << 20,
|
||||
All = ~NONE,
|
||||
|
||||
WITHOUT_POCKET = All & ~POCKET
|
||||
}
|
||||
|
||||
30
Content.Shared/Labels/Components/HandLabelerComponent.cs
Normal file
30
Content.Shared/Labels/Components/HandLabelerComponent.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Content.Shared.Labels.EntitySystems;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Labels.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedHandLabelerSystem))]
|
||||
public sealed partial class HandLabelerComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite), Access(Other = AccessPermissions.ReadWriteExecute)]
|
||||
[DataField]
|
||||
public string AssignedLabel = string.Empty;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public int MaxLabelChars = 50;
|
||||
|
||||
[DataField]
|
||||
public EntityWhitelist Whitelist = new();
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class HandLabelerComponentState(string assignedLabel) : IComponentState
|
||||
{
|
||||
public string AssignedLabel = assignedLabel;
|
||||
|
||||
public int MaxLabelChars;
|
||||
}
|
||||
129
Content.Shared/Labels/EntitySystems/SharedHandLabelerSystem.cs
Normal file
129
Content.Shared/Labels/EntitySystems/SharedHandLabelerSystem.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Labels.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Shared.Labels.EntitySystems;
|
||||
|
||||
public abstract class SharedHandLabelerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly SharedUserInterfaceSystem UserInterfaceSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedLabelSystem _labelSystem = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HandLabelerComponent, AfterInteractEvent>(AfterInteractOn);
|
||||
SubscribeLocalEvent<HandLabelerComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
|
||||
// Bound UI subscriptions
|
||||
SubscribeLocalEvent<HandLabelerComponent, HandLabelerLabelChangedMessage>(OnHandLabelerLabelChanged);
|
||||
SubscribeLocalEvent<HandLabelerComponent, ComponentGetState>(OnGetState);
|
||||
SubscribeLocalEvent<HandLabelerComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnGetState(Entity<HandLabelerComponent> ent, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new HandLabelerComponentState(ent.Comp.AssignedLabel)
|
||||
{
|
||||
MaxLabelChars = ent.Comp.MaxLabelChars,
|
||||
};
|
||||
}
|
||||
|
||||
private void OnHandleState(Entity<HandLabelerComponent> ent, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not HandLabelerComponentState state)
|
||||
return;
|
||||
|
||||
ent.Comp.MaxLabelChars = state.MaxLabelChars;
|
||||
|
||||
if (ent.Comp.AssignedLabel == state.AssignedLabel)
|
||||
return;
|
||||
|
||||
ent.Comp.AssignedLabel = state.AssignedLabel;
|
||||
UpdateUI(ent);
|
||||
}
|
||||
|
||||
protected virtual void UpdateUI(Entity<HandLabelerComponent> ent)
|
||||
{
|
||||
}
|
||||
|
||||
private void AddLabelTo(EntityUid uid, HandLabelerComponent? handLabeler, EntityUid target, out string? result)
|
||||
{
|
||||
if (!Resolve(uid, ref handLabeler))
|
||||
{
|
||||
result = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (handLabeler.AssignedLabel == string.Empty)
|
||||
{
|
||||
if (_netManager.IsServer)
|
||||
_labelSystem.Label(target, null);
|
||||
result = Loc.GetString("hand-labeler-successfully-removed");
|
||||
return;
|
||||
}
|
||||
if (_netManager.IsServer)
|
||||
_labelSystem.Label(target, handLabeler.AssignedLabel);
|
||||
result = Loc.GetString("hand-labeler-successfully-applied");
|
||||
}
|
||||
|
||||
private void OnUtilityVerb(EntityUid uid, HandLabelerComponent handLabeler, GetVerbsEvent<UtilityVerb> args)
|
||||
{
|
||||
if (args.Target is not { Valid: true } target || !handLabeler.Whitelist.IsValid(target) || !args.CanAccess)
|
||||
return;
|
||||
|
||||
var labelerText = handLabeler.AssignedLabel == string.Empty ? Loc.GetString("hand-labeler-remove-label-text") : Loc.GetString("hand-labeler-add-label-text");
|
||||
|
||||
var verb = new UtilityVerb()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
Labeling(uid, target, args.User, handLabeler);
|
||||
},
|
||||
Text = labelerText
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void AfterInteractOn(EntityUid uid, HandLabelerComponent handLabeler, AfterInteractEvent args)
|
||||
{
|
||||
if (args.Target is not { Valid: true } target || !handLabeler.Whitelist.IsValid(target) || !args.CanReach)
|
||||
return;
|
||||
|
||||
Labeling(uid, target, args.User, handLabeler);
|
||||
}
|
||||
|
||||
private void Labeling(EntityUid uid, EntityUid target, EntityUid User, HandLabelerComponent handLabeler)
|
||||
{
|
||||
AddLabelTo(uid, handLabeler, target, out var result);
|
||||
if (result == null)
|
||||
return;
|
||||
|
||||
_popupSystem.PopupClient(result, User, User);
|
||||
|
||||
// Log labeling
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low,
|
||||
$"{ToPrettyString(User):user} labeled {ToPrettyString(target):target} with {ToPrettyString(uid):labeler}");
|
||||
}
|
||||
|
||||
private void OnHandLabelerLabelChanged(EntityUid uid, HandLabelerComponent handLabeler, HandLabelerLabelChangedMessage args)
|
||||
{
|
||||
var label = args.Label.Trim();
|
||||
handLabeler.AssignedLabel = label[..Math.Min(handLabeler.MaxLabelChars, label.Length)];
|
||||
UpdateUI((uid, handLabeler));
|
||||
Dirty(uid, handLabeler);
|
||||
|
||||
// Log label change
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low,
|
||||
$"{ToPrettyString(args.Actor):user} set {ToPrettyString(uid):labeler} to apply label \"{handLabeler.AssignedLabel}\"");
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ public abstract partial class SharedLabelSystem : EntitySystem
|
||||
SubscribeLocalEvent<LabelComponent, ExaminedEvent>(OnExamine);
|
||||
}
|
||||
|
||||
public virtual void Label(EntityUid uid, string? text, MetaDataComponent? metadata = null, LabelComponent? label = null){}
|
||||
|
||||
private void OnExamine(EntityUid uid, LabelComponent? label, ExaminedEvent args)
|
||||
{
|
||||
if (!Resolve(uid, ref label))
|
||||
|
||||
@@ -1,47 +1,27 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Labels
|
||||
namespace Content.Shared.Labels;
|
||||
|
||||
/// <summary>
|
||||
/// Key representing which <see cref="PlayerBoundUserInterface"/> is currently open.
|
||||
/// Useful when there are multiple UI for an object. Here it's future-proofing only.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum HandLabelerUiKey
|
||||
{
|
||||
/// <summary>
|
||||
/// Key representing which <see cref="PlayerBoundUserInterface"/> is currently open.
|
||||
/// Useful when there are multiple UI for an object. Here it's future-proofing only.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum HandLabelerUiKey
|
||||
{
|
||||
Key,
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum PaperLabelVisuals : byte
|
||||
{
|
||||
Layer,
|
||||
HasLabel,
|
||||
LabelType
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a <see cref="HandLabelerComponent"/> state that can be sent to the client
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class HandLabelerBoundUserInterfaceState : BoundUserInterfaceState
|
||||
{
|
||||
public string CurrentLabel { get; }
|
||||
|
||||
public HandLabelerBoundUserInterfaceState(string currentLabel)
|
||||
{
|
||||
CurrentLabel = currentLabel;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class HandLabelerLabelChangedMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public string Label { get; }
|
||||
|
||||
public HandLabelerLabelChangedMessage(string label)
|
||||
{
|
||||
Label = label;
|
||||
}
|
||||
}
|
||||
Key,
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum PaperLabelVisuals : byte
|
||||
{
|
||||
Layer,
|
||||
HasLabel,
|
||||
LabelType
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class HandLabelerLabelChangedMessage(string label) : BoundUserInterfaceMessage
|
||||
{
|
||||
public string Label { get; } = label;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Shared.Decals;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Light.Components;
|
||||
|
||||
@@ -17,7 +16,7 @@ public sealed partial class UnpoweredFlashlightComponent : Component
|
||||
public SoundSpecifier ToggleSound = new SoundPathSpecifier("/Audio/Items/flashlight_pda.ogg");
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool LightOn = false;
|
||||
public bool LightOn;
|
||||
|
||||
[DataField]
|
||||
public EntProtoId ToggleAction = "ActionToggleLight";
|
||||
|
||||
122
Content.Shared/Light/EntitySystems/UnpoweredFlashlightSystem.cs
Normal file
122
Content.Shared/Light/EntitySystems/UnpoweredFlashlightSystem.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Toggleable;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Light.EntitySystems;
|
||||
|
||||
public sealed class UnpoweredFlashlightSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _light = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<UnpoweredFlashlightComponent, GetVerbsEvent<ActivationVerb>>(AddToggleLightVerbs);
|
||||
SubscribeLocalEvent<UnpoweredFlashlightComponent, GetItemActionsEvent>(OnGetActions);
|
||||
SubscribeLocalEvent<UnpoweredFlashlightComponent, ToggleActionEvent>(OnToggleAction);
|
||||
SubscribeLocalEvent<UnpoweredFlashlightComponent, MindAddedMessage>(OnMindAdded);
|
||||
SubscribeLocalEvent<UnpoweredFlashlightComponent, GotEmaggedEvent>(OnGotEmagged);
|
||||
SubscribeLocalEvent<UnpoweredFlashlightComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, UnpoweredFlashlightComponent component, MapInitEvent args)
|
||||
{
|
||||
_actionContainer.EnsureAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void OnToggleAction(EntityUid uid, UnpoweredFlashlightComponent component, ToggleActionEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
TryToggleLight((uid, component), args.Performer);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnGetActions(EntityUid uid, UnpoweredFlashlightComponent component, GetItemActionsEvent args)
|
||||
{
|
||||
args.AddAction(component.ToggleActionEntity);
|
||||
}
|
||||
|
||||
private void AddToggleLightVerbs(EntityUid uid, UnpoweredFlashlightComponent component, GetVerbsEvent<ActivationVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
ActivationVerb verb = new()
|
||||
{
|
||||
Text = Loc.GetString("toggle-flashlight-verb-get-data-text"),
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/light.svg.192dpi.png")),
|
||||
Act = () => TryToggleLight((uid, component), args.User),
|
||||
Priority = -1 // For things like PDA's, Open-UI and other verbs that should be higher priority.
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void OnMindAdded(EntityUid uid, UnpoweredFlashlightComponent component, MindAddedMessage args)
|
||||
{
|
||||
_actionsSystem.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
|
||||
private void OnGotEmagged(EntityUid uid, UnpoweredFlashlightComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_light.TryGetLight(uid, out var light))
|
||||
return;
|
||||
|
||||
if (_prototypeManager.TryIndex(component.EmaggedColorsPrototype, out var possibleColors))
|
||||
{
|
||||
var pick = _random.Pick(possibleColors.Colors.Values);
|
||||
_light.SetColor(uid, pick, light);
|
||||
}
|
||||
|
||||
args.Repeatable = true;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public void TryToggleLight(Entity<UnpoweredFlashlightComponent?> ent, EntityUid? user = null, bool quiet = false)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
SetLight(ent, !ent.Comp.LightOn, user, quiet);
|
||||
}
|
||||
|
||||
public void SetLight(Entity<UnpoweredFlashlightComponent?> ent, bool value, EntityUid? user = null, bool quiet = false)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
if (ent.Comp.LightOn == value)
|
||||
return;
|
||||
|
||||
if (!_light.TryGetLight(ent, out var light))
|
||||
return;
|
||||
|
||||
Dirty(ent);
|
||||
ent.Comp.LightOn = value;
|
||||
_light.SetEnabled(ent, value, light);
|
||||
_appearance.SetData(ent, UnpoweredFlashlightVisuals.LightOn, value);
|
||||
|
||||
if (!quiet)
|
||||
_audioSystem.PlayPredicted(ent.Comp.ToggleSound, ent, user);
|
||||
|
||||
_actionsSystem.SetToggled(ent.Comp.ToggleActionEntity, value);
|
||||
RaiseLocalEvent(ent, new LightToggleEvent(value));
|
||||
}
|
||||
}
|
||||
6
Content.Shared/Light/LightToggleEvent.cs
Normal file
6
Content.Shared/Light/LightToggleEvent.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Content.Shared.Light;
|
||||
|
||||
public sealed class LightToggleEvent(bool isOn) : EntityEventArgs
|
||||
{
|
||||
public bool IsOn = isOn;
|
||||
}
|
||||
@@ -79,7 +79,7 @@ namespace Content.Shared.Localizations
|
||||
var maxDecimals = (int)Math.Floor(((LocValueNumber) args.Args[1]).Value);
|
||||
var formatter = (NumberFormatInfo)NumberFormatInfo.GetInstance(CultureInfo.GetCultureInfo(Culture)).Clone();
|
||||
formatter.NumberDecimalDigits = maxDecimals;
|
||||
return new LocValueString(string.Format(formatter, "{0:N}", number).TrimEnd('0').TrimEnd('.') + "%");
|
||||
return new LocValueString(string.Format(formatter, "{0:N}", number).TrimEnd('0').TrimEnd(char.Parse(formatter.NumberDecimalSeparator)) + "%");
|
||||
}
|
||||
|
||||
private ILocValue FormatNaturalFixed(LocArgs args)
|
||||
@@ -88,7 +88,7 @@ namespace Content.Shared.Localizations
|
||||
var maxDecimals = (int)Math.Floor(((LocValueNumber) args.Args[1]).Value);
|
||||
var formatter = (NumberFormatInfo)NumberFormatInfo.GetInstance(CultureInfo.GetCultureInfo(Culture)).Clone();
|
||||
formatter.NumberDecimalDigits = maxDecimals;
|
||||
return new LocValueString(string.Format(formatter, "{0:N}", number).TrimEnd('0').TrimEnd('.'));
|
||||
return new LocValueString(string.Format(formatter, "{0:N}", number).TrimEnd('0').TrimEnd(char.Parse(formatter.NumberDecimalSeparator)));
|
||||
}
|
||||
|
||||
private static readonly Regex PluralEsRule = new("^.*(s|sh|ch|x|z)$");
|
||||
|
||||
@@ -259,7 +259,7 @@ namespace Content.Shared.Movement.Systems
|
||||
}
|
||||
|
||||
var oldMapId = args.OldMapId;
|
||||
var mapId = args.Transform.MapID;
|
||||
var mapId = args.Transform.MapUid;
|
||||
|
||||
// If we change maps then reset eye rotation entirely.
|
||||
if (oldMapId != mapId)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Content.Shared.Preferences.Loadouts.Effects;
|
||||
|
||||
public sealed class SpeciesLoadoutEffect
|
||||
{
|
||||
|
||||
}
|
||||
53
Content.Shared/Speech/Components/VocalComponent.cs
Normal file
53
Content.Shared/Speech/Components/VocalComponent.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Humanoid;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
|
||||
namespace Content.Shared.Speech.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component required for entities to be able to do vocal emotions.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[AutoGenerateComponentState]
|
||||
public sealed partial class VocalComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Emote sounds prototype id for each sex (not gender).
|
||||
/// Entities without <see cref="HumanoidComponent"/> considered to be <see cref="Sex.Unsexed"/>.
|
||||
/// </summary>
|
||||
[DataField("sounds", customTypeSerializer: typeof(PrototypeIdValueDictionarySerializer<Sex, EmoteSoundsPrototype>))]
|
||||
[AutoNetworkedField]
|
||||
public Dictionary<Sex, string>? Sounds;
|
||||
|
||||
[DataField("screamId", customTypeSerializer: typeof(PrototypeIdSerializer<EmotePrototype>))]
|
||||
[AutoNetworkedField]
|
||||
public string ScreamId = "Scream";
|
||||
|
||||
[DataField("wilhelm")]
|
||||
[AutoNetworkedField]
|
||||
public SoundSpecifier Wilhelm = new SoundPathSpecifier("/Audio/Voice/Human/wilhelm_scream.ogg");
|
||||
|
||||
[DataField("wilhelmProbability")]
|
||||
[AutoNetworkedField]
|
||||
public float WilhelmProbability = 0.0002f;
|
||||
|
||||
[DataField("screamAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
[AutoNetworkedField]
|
||||
public string ScreamAction = "ActionScream";
|
||||
|
||||
[DataField("screamActionEntity")]
|
||||
[AutoNetworkedField]
|
||||
public EntityUid? ScreamActionEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Currently loaded emote sounds prototype, based on entity sex.
|
||||
/// Null if no valid prototype for entity sex was found.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[AutoNetworkedField]
|
||||
public EmoteSoundsPrototype? EmoteSounds = null;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -26,6 +27,13 @@ namespace Content.Shared.Speech
|
||||
[DataField]
|
||||
public ProtoId<SpeechVerbPrototype> SpeechVerb = "Default";
|
||||
|
||||
/// <summary>
|
||||
/// What emotes allowed to use event if emote <see cref="EmotePrototype.Available"/> is false
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public List<ProtoId<EmotePrototype>> AllowedEmotes = new();
|
||||
|
||||
/// <summary>
|
||||
/// A mapping from chat suffixes loc strings to speech verb prototypes that should be conditionally used.
|
||||
/// For things like '?' changing to 'asks' or '!!' making text bold and changing to 'yells'. Can be overridden if necessary.
|
||||
|
||||
@@ -113,7 +113,7 @@ public abstract class SharedStealthSystem : EntitySystem
|
||||
|
||||
private void OnMove(EntityUid uid, StealthOnMoveComponent component, ref MoveEvent args)
|
||||
{
|
||||
if (args.FromStateHandling)
|
||||
if (_timing.ApplyingState)
|
||||
return;
|
||||
|
||||
if (args.NewPosition.EntityId != args.OldPosition.EntityId)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.StepTrigger.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for marking step trigger events that require the user to wear shoes, such as for glass shards.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class ClothingRequiredStepTriggerComponent : Component;
|
||||
@@ -0,0 +1,16 @@
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.StepTrigger.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.StepTrigger.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for cancelling step trigger events if the user is wearing clothing in a valid slot.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(StepTriggerImmuneSystem))]
|
||||
public sealed partial class ClothingRequiredStepTriggerImmuneComponent : Component, IClothingSlots
|
||||
{
|
||||
[DataField]
|
||||
public SlotFlags Slots { get; set; } = SlotFlags.FEET;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.StepTrigger.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for cancelling step trigger events if the user is wearing shoes, such as for glass shards.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class ShoesRequiredStepTriggerComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.StepTrigger.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Grants the attached entity to step triggers.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class StepTriggerImmuneComponent : Component;
|
||||
@@ -1,41 +0,0 @@
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.StepTrigger.Components;
|
||||
using Content.Shared.Tag;
|
||||
|
||||
namespace Content.Shared.StepTrigger.Systems;
|
||||
|
||||
public sealed class ShoesRequiredStepTriggerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ShoesRequiredStepTriggerComponent, StepTriggerAttemptEvent>(OnStepTriggerAttempt);
|
||||
SubscribeLocalEvent<ShoesRequiredStepTriggerComponent, ExaminedEvent>(OnExamined);
|
||||
}
|
||||
|
||||
private void OnStepTriggerAttempt(EntityUid uid, ShoesRequiredStepTriggerComponent component, ref StepTriggerAttemptEvent args)
|
||||
{
|
||||
if (_tagSystem.HasTag(args.Tripper, "ShoesRequiredStepTriggerImmune"))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<InventoryComponent>(args.Tripper, out var inventory))
|
||||
return;
|
||||
|
||||
if (_inventory.TryGetSlotEntity(args.Tripper, "shoes", out _, inventory))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, ShoesRequiredStepTriggerComponent component, ExaminedEvent args)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("shoes-required-step-trigger-examine"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.StepTrigger.Components;
|
||||
using Content.Shared.Tag;
|
||||
|
||||
namespace Content.Shared.StepTrigger.Systems;
|
||||
|
||||
public sealed class StepTriggerImmuneSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<StepTriggerImmuneComponent, StepTriggerAttemptEvent>(OnStepTriggerAttempt);
|
||||
SubscribeLocalEvent<ClothingRequiredStepTriggerComponent, StepTriggerAttemptEvent>(OnStepTriggerClothingAttempt);
|
||||
SubscribeLocalEvent<ClothingRequiredStepTriggerComponent, ExaminedEvent>(OnExamined);
|
||||
}
|
||||
|
||||
private void OnStepTriggerAttempt(Entity<StepTriggerImmuneComponent> ent, ref StepTriggerAttemptEvent args)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnStepTriggerClothingAttempt(EntityUid uid, ClothingRequiredStepTriggerComponent component, ref StepTriggerAttemptEvent args)
|
||||
{
|
||||
if (_inventory.TryGetInventoryEntity<ClothingRequiredStepTriggerImmuneComponent>(args.Tripper, out _))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, ClothingRequiredStepTriggerComponent component, ExaminedEvent args)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("clothing-required-step-trigger-examine"));
|
||||
}
|
||||
}
|
||||
@@ -145,27 +145,14 @@ public sealed class SwapTeleporterSystem : EntitySystem
|
||||
}
|
||||
|
||||
var teleEnt = GetTeleportingEntity((uid, xform));
|
||||
var teleEntXform = Transform(teleEnt);
|
||||
var otherTeleEnt = GetTeleportingEntity((linkedEnt, Transform(linkedEnt)));
|
||||
var otherTeleEntXform = Transform(otherTeleEnt);
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("swap-teleporter-popup-teleport-other",
|
||||
("entity", Identity.Entity(linkedEnt, EntityManager))),
|
||||
otherTeleEnt,
|
||||
otherTeleEnt,
|
||||
PopupType.MediumCaution);
|
||||
var pos = teleEntXform.Coordinates;
|
||||
var otherPos = otherTeleEntXform.Coordinates;
|
||||
|
||||
if (_transform.ContainsEntity(teleEnt, (otherTeleEnt, otherTeleEntXform)) ||
|
||||
_transform.ContainsEntity(otherTeleEnt, (teleEnt, teleEntXform)))
|
||||
{
|
||||
Log.Error($"Invalid teleport swap attempt between {ToPrettyString(teleEnt)} and {ToPrettyString(otherTeleEnt)}");
|
||||
return;
|
||||
}
|
||||
|
||||
_transform.SetCoordinates(teleEnt, otherPos);
|
||||
_transform.SetCoordinates(otherTeleEnt, pos);
|
||||
_transform.SwapPositions(teleEnt, otherTeleEnt);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
|
||||
19
Content.Shared/Tips/TippyEvent.cs
Normal file
19
Content.Shared/Tips/TippyEvent.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Tips;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class TippyEvent : EntityEventArgs
|
||||
{
|
||||
public TippyEvent(string msg)
|
||||
{
|
||||
Msg = msg;
|
||||
}
|
||||
|
||||
public string Msg;
|
||||
public string? Proto;
|
||||
public float SpeakTime = 5;
|
||||
public float SlideTime = 3;
|
||||
public float WaddleInterval = 0.5f;
|
||||
}
|
||||
@@ -4,22 +4,26 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
|
||||
namespace Content.Shared.UserInterface
|
||||
{
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class ActivatableUIComponent : Component
|
||||
{
|
||||
[DataField(required: true, customTypeSerializer: typeof(EnumSerializer))]
|
||||
public Enum Key { get; set; } = default!;
|
||||
public Enum? Key;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the item must be held in one of the user's hands to work.
|
||||
/// This is ignored unless <see cref="RequireHands"/> is true.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public bool InHandsOnly;
|
||||
|
||||
[DataField]
|
||||
public bool SingleUser;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public bool InHandsOnly { get; set; } = false;
|
||||
|
||||
[DataField]
|
||||
public bool SingleUser { get; set; } = false;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public bool AdminOnly { get; set; } = false;
|
||||
public bool AdminOnly;
|
||||
|
||||
[DataField]
|
||||
public LocId VerbText = "ui-verb-toggle-open";
|
||||
@@ -38,16 +42,15 @@ namespace Content.Shared.UserInterface
|
||||
/// <summary>
|
||||
/// Entities that are required to open this UI.
|
||||
/// </summary>
|
||||
[DataField("allowedItems")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityWhitelist? AllowedItems = null;
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityWhitelist? RequiredItems;
|
||||
|
||||
/// <summary>
|
||||
/// Whether you can activate this ui with activateinhand or not
|
||||
/// If true, then this UI can only be opened via verbs. I.e., normal interactions/activations will not open
|
||||
/// the UI.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public bool RightClickOnly;
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool VerbOnly;
|
||||
|
||||
/// <summary>
|
||||
/// Whether spectators (non-admin ghosts) should be allowed to view this UI.
|
||||
@@ -57,17 +60,18 @@ namespace Content.Shared.UserInterface
|
||||
public bool AllowSpectator = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the UI should close when the item is deselected due to a hand swap or drop
|
||||
/// Whether the item must be in the user's currently selected/active hand.
|
||||
/// This is ignored unless <see cref="InHandsOnly"/> is true.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public bool CloseOnHandDeselect = true;
|
||||
public bool RequireActiveHand = true;
|
||||
|
||||
/// <summary>
|
||||
/// The client channel currently using the object, or null if there's none/not single user.
|
||||
/// NOTE: DO NOT DIRECTLY SET, USE ActivatableUISystem.SetCurrentSingleUser
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? CurrentSingleUser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,19 @@ public sealed partial class ActivatableUISystem
|
||||
{
|
||||
_cell.SetPowerCellDrawEnabled(uid, false);
|
||||
|
||||
if (HasComp<ActivatableUIRequiresPowerCellComponent>(uid) &&
|
||||
TryComp<ActivatableUIComponent>(uid, out var activatable))
|
||||
if (!HasComp<ActivatableUIRequiresPowerCellComponent>(uid) ||
|
||||
!TryComp(uid, out ActivatableUIComponent? activatable))
|
||||
{
|
||||
_uiSystem.CloseUi(uid, activatable.Key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (activatable.Key == null)
|
||||
{
|
||||
Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(uid)}");
|
||||
return;
|
||||
}
|
||||
|
||||
_uiSystem.CloseUi(uid, activatable.Key);
|
||||
}
|
||||
|
||||
private void OnBatteryOpened(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, BoundUIOpenedEvent args)
|
||||
@@ -57,6 +65,12 @@ public sealed partial class ActivatableUISystem
|
||||
if (!Resolve(uid, ref component, ref draw, ref active, false))
|
||||
return;
|
||||
|
||||
if (active.Key == null)
|
||||
{
|
||||
Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(uid)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_cell.HasActivatableCharge(uid))
|
||||
return;
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared.UserInterface;
|
||||
|
||||
@@ -17,23 +17,31 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
[Dependency] private readonly ActionBlockerSystem _blockerSystem = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
|
||||
private readonly List<EntityUid> _toClose = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ActivatableUIComponent, ActivateInWorldEvent>(OnActivate);
|
||||
SubscribeLocalEvent<ActivatableUIComponent, UseInHandEvent>(OnUseInHand);
|
||||
SubscribeLocalEvent<ActivatableUIComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<ActivatableUIComponent, HandDeselectedEvent>(OnHandDeselected);
|
||||
SubscribeLocalEvent<ActivatableUIComponent, GotUnequippedHandEvent>((uid, aui, _) => CloseAll(uid, aui));
|
||||
// *THIS IS A BLATANT WORKAROUND!* RATIONALE: Microwaves need it
|
||||
SubscribeLocalEvent<ActivatableUIComponent, EntParentChangedMessage>(OnParentChanged);
|
||||
SubscribeLocalEvent<ActivatableUIComponent, GotUnequippedHandEvent>(OnHandUnequipped);
|
||||
SubscribeLocalEvent<ActivatableUIComponent, BoundUIClosedEvent>(OnUIClose);
|
||||
SubscribeLocalEvent<ActivatableUIComponent, GetVerbsEvent<ActivationVerb>>(GetActivationVerb);
|
||||
SubscribeLocalEvent<ActivatableUIComponent, GetVerbsEvent<Verb>>(GetVerb);
|
||||
|
||||
// TODO ActivatableUI
|
||||
// Add UI-user component, and listen for user container changes.
|
||||
// I.e., should lose a computer UI if a player gets shut into a locker.
|
||||
SubscribeLocalEvent<ActivatableUIComponent, EntGotInsertedIntoContainerMessage>(OnGotInserted);
|
||||
SubscribeLocalEvent<ActivatableUIComponent, EntGotRemovedFromContainerMessage>(OnGotRemoved);
|
||||
|
||||
SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundInterfaceInteractAttempt);
|
||||
|
||||
SubscribeLocalEvent<ActivatableUIComponent, GetVerbsEvent<ActivationVerb>>(AddOpenUiVerb);
|
||||
|
||||
SubscribeLocalEvent<UserInterfaceComponent, OpenUiActionEvent>(OnActionPerform);
|
||||
|
||||
InitializePower();
|
||||
@@ -59,25 +67,54 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
args.Handled = _uiSystem.TryToggleUi(uid, args.Key, args.Performer);
|
||||
}
|
||||
|
||||
private void AddOpenUiVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent<ActivationVerb> args)
|
||||
|
||||
private void GetActivationVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent<ActivationVerb> args)
|
||||
{
|
||||
if (component.VerbOnly || !ShouldAddVerb(uid, component, args))
|
||||
return;
|
||||
|
||||
args.Verbs.Add(new ActivationVerb
|
||||
{
|
||||
// TODO VERBS add "open UI" icon
|
||||
Act = () => InteractUI(args.User, uid, component),
|
||||
Text = Loc.GetString(component.VerbText)
|
||||
});
|
||||
}
|
||||
|
||||
private void GetVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent<Verb> args)
|
||||
{
|
||||
if (!component.VerbOnly || !ShouldAddVerb(uid, component, args))
|
||||
return;
|
||||
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
// TODO VERBS add "open UI" icon
|
||||
Act = () => InteractUI(args.User, uid, component),
|
||||
Text = Loc.GetString(component.VerbText)
|
||||
});
|
||||
}
|
||||
|
||||
private bool ShouldAddVerb<T>(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent<T> args) where T : Verb
|
||||
{
|
||||
if (!args.CanAccess)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (component.RequireHands && args.Hands == null)
|
||||
return;
|
||||
if (component.RequireHands)
|
||||
{
|
||||
if (args.Hands == null)
|
||||
return false;
|
||||
|
||||
if (component.InHandsOnly && args.Using != uid)
|
||||
return;
|
||||
if (component.InHandsOnly)
|
||||
{
|
||||
if (!_hands.IsHolding(args.User, uid, out var hand, args.Hands))
|
||||
return false;
|
||||
|
||||
if (!args.CanInteract && (!component.AllowSpectator || !HasComp<GhostComponent>(args.User)))
|
||||
return;
|
||||
if (component.RequireActiveHand && args.Hands.ActiveHand != hand)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ActivationVerb verb = new();
|
||||
verb.Act = () => InteractUI(args.User, uid, component);
|
||||
verb.Text = Loc.GetString(component.VerbText);
|
||||
// TODO VERBS add "open UI" icon?
|
||||
args.Verbs.Add(verb);
|
||||
return args.CanInteract || component.AllowSpectator && HasComp<GhostComponent>(args.User);
|
||||
}
|
||||
|
||||
private void OnActivate(EntityUid uid, ActivatableUIComponent component, ActivateInWorldEvent args)
|
||||
@@ -85,24 +122,10 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (component.InHandsOnly)
|
||||
if (component.VerbOnly)
|
||||
return;
|
||||
|
||||
if (component.AllowedItems != null)
|
||||
return;
|
||||
|
||||
args.Handled = InteractUI(args.User, uid, component);
|
||||
}
|
||||
|
||||
private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (component.RightClickOnly)
|
||||
return;
|
||||
|
||||
if (component.AllowedItems != null)
|
||||
if (component.RequiredItems != null)
|
||||
return;
|
||||
|
||||
args.Handled = InteractUI(args.User, uid, component);
|
||||
@@ -110,15 +133,19 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
|
||||
private void OnInteractUsing(EntityUid uid, ActivatableUIComponent component, InteractUsingEvent args)
|
||||
{
|
||||
if (args.Handled) return;
|
||||
if (component.AllowedItems == null) return;
|
||||
if (!component.AllowedItems.IsValid(args.Used, EntityManager)) return;
|
||||
args.Handled = InteractUI(args.User, uid, component);
|
||||
}
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
private void OnParentChanged(EntityUid uid, ActivatableUIComponent aui, ref EntParentChangedMessage args)
|
||||
{
|
||||
CloseAll(uid, aui);
|
||||
if (component.VerbOnly)
|
||||
return;
|
||||
|
||||
if (component.RequiredItems == null)
|
||||
return;
|
||||
|
||||
if (!component.RequiredItems.IsValid(args.Used, EntityManager))
|
||||
return;
|
||||
|
||||
args.Handled = InteractUI(args.User, uid, component);
|
||||
}
|
||||
|
||||
private void OnUIClose(EntityUid uid, ActivatableUIComponent component, BoundUIClosedEvent args)
|
||||
@@ -136,7 +163,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
|
||||
private bool InteractUI(EntityUid user, EntityUid uiEntity, ActivatableUIComponent aui)
|
||||
{
|
||||
if (!_uiSystem.HasUi(uiEntity, aui.Key))
|
||||
if (aui.Key == null || !_uiSystem.HasUi(uiEntity, aui.Key))
|
||||
return false;
|
||||
|
||||
if (_uiSystem.IsUiOpen(uiEntity, aui.Key, user))
|
||||
@@ -148,22 +175,33 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
if (!_blockerSystem.CanInteract(user, uiEntity) && (!aui.AllowSpectator || !HasComp<GhostComponent>(user)))
|
||||
return false;
|
||||
|
||||
if (aui.RequireHands && !HasComp<HandsComponent>(user))
|
||||
return false;
|
||||
if (aui.RequireHands)
|
||||
{
|
||||
if (!TryComp(user, out HandsComponent? hands))
|
||||
return false;
|
||||
|
||||
if (aui.InHandsOnly)
|
||||
{
|
||||
if (!_hands.IsHolding(user, uiEntity, out var hand, hands))
|
||||
return false;
|
||||
|
||||
if (aui.RequireActiveHand && hands.ActiveHand != hand)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (aui.AdminOnly && !_adminManager.IsAdmin(user))
|
||||
return false;
|
||||
|
||||
if (aui.SingleUser && aui.CurrentSingleUser != null && user != aui.CurrentSingleUser)
|
||||
{
|
||||
string message = Loc.GetString("machine-already-in-use", ("machine", uiEntity));
|
||||
var message = Loc.GetString("machine-already-in-use", ("machine", uiEntity));
|
||||
_popupSystem.PopupEntity(message, uiEntity, user);
|
||||
|
||||
// If we get here, supposedly, the object is in use.
|
||||
// Check with BUI that it's ACTUALLY in use just in case.
|
||||
// Since this could brick the object if it goes wrong.
|
||||
if (_uiSystem.IsUiOpen(uiEntity, aui.Key))
|
||||
return false;
|
||||
return true;
|
||||
|
||||
Log.Error($"Activatable UI has user without being opened? Entity: {ToPrettyString(uiEntity)}. User: {aui.CurrentSingleUser}, Key: {aui.Key}");
|
||||
}
|
||||
|
||||
// If we've gotten this far, fire a cancellable event that indicates someone is about to activate this.
|
||||
@@ -199,6 +237,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
return;
|
||||
|
||||
aui.CurrentSingleUser = user;
|
||||
Dirty(uid, aui);
|
||||
|
||||
RaiseLocalEvent(uid, new ActivatableUIPlayerChangedEvent());
|
||||
}
|
||||
@@ -208,17 +247,67 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
if (!Resolve(uid, ref aui, false))
|
||||
return;
|
||||
|
||||
if (aui.Key == null)
|
||||
{
|
||||
Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(uid)}");
|
||||
return;
|
||||
}
|
||||
|
||||
_uiSystem.CloseUi(uid, aui.Key);
|
||||
}
|
||||
|
||||
private void OnHandDeselected(EntityUid uid, ActivatableUIComponent? aui, HandDeselectedEvent args)
|
||||
private void OnHandDeselected(Entity<ActivatableUIComponent> ent, ref HandDeselectedEvent args)
|
||||
{
|
||||
if (!Resolve(uid, ref aui, false))
|
||||
if (ent.Comp.RequireHands && ent.Comp.InHandsOnly && ent.Comp.RequireActiveHand)
|
||||
CloseAll(ent, ent);
|
||||
}
|
||||
|
||||
private void OnHandUnequipped(Entity<ActivatableUIComponent> ent, ref GotUnequippedHandEvent args)
|
||||
{
|
||||
if (ent.Comp.RequireHands && ent.Comp.InHandsOnly)
|
||||
CloseAll(ent, ent);
|
||||
}
|
||||
|
||||
private void OnGotInserted(Entity<ActivatableUIComponent> ent, ref EntGotInsertedIntoContainerMessage args)
|
||||
{
|
||||
CheckAccess((ent, ent));
|
||||
}
|
||||
|
||||
private void OnGotRemoved(Entity<ActivatableUIComponent> ent, ref EntGotRemovedFromContainerMessage args)
|
||||
{
|
||||
CheckAccess((ent, ent));
|
||||
}
|
||||
|
||||
public void CheckAccess(Entity<ActivatableUIComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
if (!aui.CloseOnHandDeselect)
|
||||
if (ent.Comp.Key == null)
|
||||
{
|
||||
Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(ent)}");
|
||||
return;
|
||||
}
|
||||
|
||||
CloseAll(uid, aui);
|
||||
foreach (var user in _uiSystem.GetActors(ent.Owner, ent.Comp.Key))
|
||||
{
|
||||
if (!_container.IsInSameOrParentContainer(user, ent)
|
||||
&& !_interaction.CanAccessViaStorage(user, ent))
|
||||
{
|
||||
_toClose.Add(user);
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
if (!_interaction.InRangeUnobstructed(user, ent))
|
||||
_toClose.Add(user);
|
||||
}
|
||||
|
||||
foreach (var user in _toClose)
|
||||
{
|
||||
_uiSystem.CloseUi(ent.Owner, ent.Comp.Key, user);
|
||||
}
|
||||
|
||||
_toClose.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,13 +55,21 @@ namespace Content.Shared.Verbs
|
||||
return GetLocalVerbs(target, user, new List<Type>() { type }, force);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetLocalVerbs(Robust.Shared.GameObjects.EntityUid,Robust.Shared.GameObjects.EntityUid,System.Type,bool)"/>
|
||||
public SortedSet<Verb> GetLocalVerbs(EntityUid target, EntityUid user, List<Type> types, bool force = false)
|
||||
{
|
||||
return GetLocalVerbs(target, user, types, out _, force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises a number of events in order to get all verbs of the given type(s) defined in local systems. This
|
||||
/// does not request verbs from the server.
|
||||
/// </summary>
|
||||
public SortedSet<Verb> GetLocalVerbs(EntityUid target, EntityUid user, List<Type> types, bool force = false)
|
||||
public SortedSet<Verb> GetLocalVerbs(EntityUid target, EntityUid user, List<Type> types,
|
||||
out List<VerbCategory> extraCategories, bool force = false)
|
||||
{
|
||||
SortedSet<Verb> verbs = new();
|
||||
extraCategories = new();
|
||||
|
||||
// accessibility checks
|
||||
bool canAccess = false;
|
||||
@@ -108,7 +116,7 @@ namespace Content.Shared.Verbs
|
||||
// TODO: fix this garbage and use proper generics or reflection or something else, not this.
|
||||
if (types.Contains(typeof(InteractionVerb)))
|
||||
{
|
||||
var verbEvent = new GetVerbsEvent<InteractionVerb>(user, target, @using, hands, canInteract, canAccess);
|
||||
var verbEvent = new GetVerbsEvent<InteractionVerb>(user, target, @using, hands, canInteract, canAccess, extraCategories);
|
||||
RaiseLocalEvent(target, verbEvent, true);
|
||||
verbs.UnionWith(verbEvent.Verbs);
|
||||
}
|
||||
@@ -117,35 +125,35 @@ namespace Content.Shared.Verbs
|
||||
&& @using != null
|
||||
&& @using != target)
|
||||
{
|
||||
var verbEvent = new GetVerbsEvent<UtilityVerb>(user, target, @using, hands, canInteract, canAccess);
|
||||
var verbEvent = new GetVerbsEvent<UtilityVerb>(user, target, @using, hands, canInteract, canAccess, extraCategories);
|
||||
RaiseLocalEvent(@using.Value, verbEvent, true); // directed at used, not at target
|
||||
verbs.UnionWith(verbEvent.Verbs);
|
||||
}
|
||||
|
||||
if (types.Contains(typeof(InnateVerb)))
|
||||
{
|
||||
var verbEvent = new GetVerbsEvent<InnateVerb>(user, target, @using, hands, canInteract, canAccess);
|
||||
var verbEvent = new GetVerbsEvent<InnateVerb>(user, target, @using, hands, canInteract, canAccess, extraCategories);
|
||||
RaiseLocalEvent(user, verbEvent, true);
|
||||
verbs.UnionWith(verbEvent.Verbs);
|
||||
}
|
||||
|
||||
if (types.Contains(typeof(AlternativeVerb)))
|
||||
{
|
||||
var verbEvent = new GetVerbsEvent<AlternativeVerb>(user, target, @using, hands, canInteract, canAccess);
|
||||
var verbEvent = new GetVerbsEvent<AlternativeVerb>(user, target, @using, hands, canInteract, canAccess, extraCategories);
|
||||
RaiseLocalEvent(target, verbEvent, true);
|
||||
verbs.UnionWith(verbEvent.Verbs);
|
||||
}
|
||||
|
||||
if (types.Contains(typeof(ActivationVerb)))
|
||||
{
|
||||
var verbEvent = new GetVerbsEvent<ActivationVerb>(user, target, @using, hands, canInteract, canAccess);
|
||||
var verbEvent = new GetVerbsEvent<ActivationVerb>(user, target, @using, hands, canInteract, canAccess, extraCategories);
|
||||
RaiseLocalEvent(target, verbEvent, true);
|
||||
verbs.UnionWith(verbEvent.Verbs);
|
||||
}
|
||||
|
||||
if (types.Contains(typeof(ExamineVerb)))
|
||||
{
|
||||
var verbEvent = new GetVerbsEvent<ExamineVerb>(user, target, @using, hands, canInteract, canAccess);
|
||||
var verbEvent = new GetVerbsEvent<ExamineVerb>(user, target, @using, hands, canInteract, canAccess, extraCategories);
|
||||
RaiseLocalEvent(target, verbEvent, true);
|
||||
verbs.UnionWith(verbEvent.Verbs);
|
||||
}
|
||||
@@ -153,7 +161,7 @@ namespace Content.Shared.Verbs
|
||||
// generic verbs
|
||||
if (types.Contains(typeof(Verb)))
|
||||
{
|
||||
var verbEvent = new GetVerbsEvent<Verb>(user, target, @using, hands, canInteract, canAccess);
|
||||
var verbEvent = new GetVerbsEvent<Verb>(user, target, @using, hands, canInteract, canAccess, extraCategories);
|
||||
RaiseLocalEvent(target, verbEvent, true);
|
||||
verbs.UnionWith(verbEvent.Verbs);
|
||||
}
|
||||
@@ -161,7 +169,7 @@ namespace Content.Shared.Verbs
|
||||
if (types.Contains(typeof(EquipmentVerb)))
|
||||
{
|
||||
var access = canAccess || _interactionSystem.CanAccessEquipment(user, target);
|
||||
var verbEvent = new GetVerbsEvent<EquipmentVerb>(user, target, @using, hands, canInteract, access);
|
||||
var verbEvent = new GetVerbsEvent<EquipmentVerb>(user, target, @using, hands, canInteract, access, extraCategories);
|
||||
RaiseLocalEvent(target, verbEvent);
|
||||
verbs.UnionWith(verbEvent.Verbs);
|
||||
}
|
||||
|
||||
@@ -77,6 +77,13 @@ namespace Content.Shared.Verbs
|
||||
/// </summary>
|
||||
public readonly SortedSet<TVerb> Verbs = new();
|
||||
|
||||
/// <summary>
|
||||
/// Additional verb categories to show in the pop-up menu, even if there are no verbs currently associated
|
||||
/// with that category. This is mainly useful to prevent verb menu pop-in. E.g., admins will get admin/debug
|
||||
/// related verbs on entities, even though most of those verbs are all defined server-side.
|
||||
/// </summary>
|
||||
public readonly List<VerbCategory> ExtraCategories;
|
||||
|
||||
/// <summary>
|
||||
/// Can the user physically access the target?
|
||||
/// </summary>
|
||||
@@ -123,7 +130,7 @@ namespace Content.Shared.Verbs
|
||||
/// </remarks>
|
||||
public readonly EntityUid? Using;
|
||||
|
||||
public GetVerbsEvent(EntityUid user, EntityUid target, EntityUid? @using, HandsComponent? hands, bool canInteract, bool canAccess)
|
||||
public GetVerbsEvent(EntityUid user, EntityUid target, EntityUid? @using, HandsComponent? hands, bool canInteract, bool canAccess, List<VerbCategory> extraCategories)
|
||||
{
|
||||
User = user;
|
||||
Target = target;
|
||||
@@ -131,6 +138,7 @@ namespace Content.Shared.Verbs
|
||||
Hands = hands;
|
||||
CanAccess = canAccess;
|
||||
CanInteract = canInteract;
|
||||
ExtraCategories = extraCategories;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,6 @@ public sealed partial class WaggingComponent : Component
|
||||
[DataField]
|
||||
public EntityUid? ActionEntity;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<EmotePrototype> EmoteId = "WagTail";
|
||||
|
||||
/// <summary>
|
||||
/// Suffix to add to get the animated marking.
|
||||
/// </summary>
|
||||
|
||||
@@ -57,7 +57,7 @@ public sealed class ReflectSystem : EntitySystem
|
||||
if (args.Reflected)
|
||||
return;
|
||||
|
||||
foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.All & ~SlotFlags.POCKET))
|
||||
foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.WITHOUT_POCKET))
|
||||
{
|
||||
if (!TryReflectHitscan(uid, ent, args.Shooter, args.SourceItem, args.Direction, out var dir))
|
||||
continue;
|
||||
@@ -70,7 +70,7 @@ public sealed class ReflectSystem : EntitySystem
|
||||
|
||||
private void OnReflectUserCollide(EntityUid uid, ReflectUserComponent component, ref ProjectileReflectAttemptEvent args)
|
||||
{
|
||||
foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.All & ~SlotFlags.POCKET))
|
||||
foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.WITHOUT_POCKET))
|
||||
{
|
||||
if (!TryReflectProjectile(uid, ent, args.ProjUid))
|
||||
continue;
|
||||
@@ -222,7 +222,7 @@ public sealed class ReflectSystem : EntitySystem
|
||||
/// </summary>
|
||||
private void RefreshReflectUser(EntityUid user)
|
||||
{
|
||||
foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(user, SlotFlags.All & ~SlotFlags.POCKET))
|
||||
foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(user, SlotFlags.WITHOUT_POCKET))
|
||||
{
|
||||
if (!HasComp<ReflectComponent>(ent))
|
||||
continue;
|
||||
|
||||
@@ -94,6 +94,9 @@ namespace Content.Shared.Whitelist
|
||||
return RequireAll ? tagSystem.HasAllTags(tags, Tags) : tagSystem.HasAnyTag(tags, Tags);
|
||||
}
|
||||
|
||||
if (RequireAll)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user