Merge remote-tracking branch 'upstream/master' into ed-06-11-2024-upstream
# Conflicts: # Resources/Changelog/Changelog.yml # Resources/Credits/GitHub.txt # Resources/Prototypes/Entities/Mobs/Player/silicon.yml
This commit is contained in:
@@ -461,6 +461,18 @@ namespace Content.Shared.CCVar
|
||||
* Discord
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// The role that will get mentioned if a new SOS ahelp comes in.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<string> DiscordAhelpMention =
|
||||
CVarDef.Create("discord.on_call_ping", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL);
|
||||
|
||||
/// <summary>
|
||||
/// URL of the discord webhook to relay unanswered ahelp messages.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<string> DiscordOnCallWebhook =
|
||||
CVarDef.Create("discord.on_call_webhook", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL);
|
||||
|
||||
/// <summary>
|
||||
/// URL of the Discord webhook which will relay all ahelp messages.
|
||||
/// </summary>
|
||||
@@ -1472,7 +1484,7 @@ namespace Content.Shared.CCVar
|
||||
/// Config for when the votekick should be allowed to be called based on number of eligible voters.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> VotekickEligibleNumberRequirement =
|
||||
CVarDef.Create("votekick.eligible_number", 10, CVar.SERVERONLY);
|
||||
CVarDef.Create("votekick.eligible_number", 5, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Whether a votekick initiator must be a ghost or not.
|
||||
@@ -1480,6 +1492,18 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<bool> VotekickInitiatorGhostRequirement =
|
||||
CVarDef.Create("votekick.initiator_ghost_requirement", true, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Should the initiator be whitelisted to initiate a votekick?
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> VotekickInitiatorWhitelistedRequirement =
|
||||
CVarDef.Create("votekick.initiator_whitelist_requirement", true, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Should the initiator be able to start a votekick if they are bellow the votekick.voter_playtime requirement?
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> VotekickInitiatorTimeRequirement =
|
||||
CVarDef.Create("votekick.initiator_time_requirement", false, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Whether a votekick voter must be a ghost or not.
|
||||
/// </summary>
|
||||
@@ -1496,7 +1520,7 @@ namespace Content.Shared.CCVar
|
||||
/// Config for how many seconds a player must have been dead to initiate a votekick / be able to vote on a votekick.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> VotekickEligibleVoterDeathtime =
|
||||
CVarDef.Create("votekick.voter_deathtime", 180, CVar.REPLICATED | CVar.SERVER);
|
||||
CVarDef.Create("votekick.voter_deathtime", 30, CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// The required ratio of eligible voters that must agree for a votekick to go through.
|
||||
@@ -1540,6 +1564,12 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<int> VotekickBanDuration =
|
||||
CVarDef.Create("votekick.ban_duration", 180, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Whether the ghost requirement settings for votekicks should be ignored for the lobby.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> VotekickIgnoreGhostReqInLobby =
|
||||
CVarDef.Create("votekick.ignore_ghost_req_in_lobby", true, CVar.SERVERONLY);
|
||||
|
||||
/*
|
||||
* BAN
|
||||
*/
|
||||
|
||||
@@ -33,7 +33,7 @@ public sealed partial class CartridgeLoaderComponent : Component
|
||||
/// The maximum amount of programs that can be installed on the cartridge loader entity
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int DiskSpace = 5;
|
||||
public int DiskSpace = 8;
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether the cartridge loader will play notifications if it supports it at all
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Content.Shared.Chemistry.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a container that also contains a solution.
|
||||
/// This means that reactive entities react when inserted into the container.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class ReactiveContainerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The container that holds the solution.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public string Container = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The solution in the container.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public string Solution = default!;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared.Chemistry.EntitySystems;
|
||||
|
||||
public sealed class ReactiveContainerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly ReactiveSystem _reactiveSystem = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ReactiveContainerComponent, EntInsertedIntoContainerMessage>(OnInserted);
|
||||
SubscribeLocalEvent<ReactiveContainerComponent, SolutionContainerChangedEvent>(OnSolutionChange);
|
||||
}
|
||||
|
||||
private void OnInserted(EntityUid uid, ReactiveContainerComponent comp, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
// Only reactive entities can react with the solution
|
||||
if (!HasComp<ReactiveComponent>(args.Entity))
|
||||
return;
|
||||
|
||||
if (!_solutionContainerSystem.TryGetSolution(uid, comp.Solution, out _, out var solution))
|
||||
return;
|
||||
if (solution.Volume == 0)
|
||||
return;
|
||||
|
||||
_reactiveSystem.DoEntityReaction(args.Entity, solution, ReactionMethod.Touch);
|
||||
}
|
||||
|
||||
private void OnSolutionChange(EntityUid uid, ReactiveContainerComponent comp, SolutionContainerChangedEvent args)
|
||||
{
|
||||
if (!_solutionContainerSystem.TryGetSolution(uid, comp.Solution, out _, out var solution))
|
||||
return;
|
||||
if (solution.Volume == 0)
|
||||
return;
|
||||
if (!TryComp<ContainerManagerComponent>(uid, out var manager))
|
||||
return;
|
||||
if (!_containerSystem.TryGetContainer(uid, comp.Container, out var container))
|
||||
return;
|
||||
|
||||
foreach (var entity in container.ContainedEntities)
|
||||
{
|
||||
if (!HasComp<ReactiveComponent>(entity))
|
||||
continue;
|
||||
_reactiveSystem.DoEntityReaction(entity, solution, ReactionMethod.Touch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
}
|
||||
|
||||
#region ComponentManagement
|
||||
|
||||
/// <summary>
|
||||
/// Spawn in starting items for any item slots that should have one.
|
||||
/// </summary>
|
||||
@@ -70,7 +71,8 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
if (slot.HasItem || string.IsNullOrEmpty(slot.StartingItem))
|
||||
continue;
|
||||
|
||||
var item = EntityManager.SpawnEntity(slot.StartingItem, EntityManager.GetComponent<TransformComponent>(uid).Coordinates);
|
||||
var item = Spawn(slot.StartingItem, Transform(uid).Coordinates);
|
||||
|
||||
if (slot.ContainerSlot != null)
|
||||
_containers.Insert(item, slot.ContainerSlot);
|
||||
}
|
||||
@@ -99,7 +101,8 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
if (itemSlots.Slots.TryGetValue(id, out var existing))
|
||||
{
|
||||
if (existing.Local)
|
||||
Log.Error($"Duplicate item slot key. Entity: {EntityManager.GetComponent<MetaDataComponent>(uid).EntityName} ({uid}), key: {id}");
|
||||
Log.Error(
|
||||
$"Duplicate item slot key. Entity: {EntityManager.GetComponent<MetaDataComponent>(uid).EntityName} ({uid}), key: {id}");
|
||||
else
|
||||
// server state takes priority
|
||||
slot.CopyFrom(existing);
|
||||
@@ -134,7 +137,10 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
Dirty(uid, itemSlots);
|
||||
}
|
||||
|
||||
public bool TryGetSlot(EntityUid uid, string slotId, [NotNullWhen(true)] out ItemSlot? itemSlot, ItemSlotsComponent? component = null)
|
||||
public bool TryGetSlot(EntityUid uid,
|
||||
string slotId,
|
||||
[NotNullWhen(true)] out ItemSlot? itemSlot,
|
||||
ItemSlotsComponent? component = null)
|
||||
{
|
||||
itemSlot = null;
|
||||
|
||||
@@ -143,9 +149,11 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
|
||||
return component.Slots.TryGetValue(slotId, out itemSlot);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Interactions
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to take an item from a slot, if any are set to EjectOnInteract.
|
||||
/// </summary>
|
||||
@@ -201,20 +209,50 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
if (!EntityManager.TryGetComponent(args.User, out HandsComponent? hands))
|
||||
return;
|
||||
|
||||
if (itemSlots.Slots.Count == 0)
|
||||
return;
|
||||
|
||||
// If any slot can be inserted into don't show popup.
|
||||
// If any whitelist passes, but slot is locked, then show locked.
|
||||
// If whitelist fails all, show whitelist fail.
|
||||
|
||||
// valid, insertable slots (if any)
|
||||
var slots = new List<ItemSlot>();
|
||||
|
||||
string? whitelistFailPopup = null;
|
||||
string? lockedFailPopup = null;
|
||||
foreach (var slot in itemSlots.Slots.Values)
|
||||
{
|
||||
if (!slot.InsertOnInteract)
|
||||
continue;
|
||||
|
||||
if (!CanInsert(uid, args.Used, args.User, slot, swap: slot.Swap, popup: args.User))
|
||||
continue;
|
||||
if (CanInsert(uid, args.Used, args.User, slot, slot.Swap))
|
||||
{
|
||||
slots.Add(slot);
|
||||
}
|
||||
else
|
||||
{
|
||||
var allowed = CanInsertWhitelist(args.Used, slot);
|
||||
if (lockedFailPopup == null && slot.LockedFailPopup != null && allowed && slot.Locked)
|
||||
lockedFailPopup = slot.LockedFailPopup;
|
||||
|
||||
slots.Add(slot);
|
||||
if (whitelistFailPopup == null && slot.WhitelistFailPopup != null)
|
||||
whitelistFailPopup = slot.WhitelistFailPopup;
|
||||
}
|
||||
}
|
||||
|
||||
if (slots.Count == 0)
|
||||
{
|
||||
// it's a bit weird that the popupMessage is stored with the item slots themselves, but in practice
|
||||
// the popup messages will just all be the same, so it's probably fine.
|
||||
//
|
||||
// doing a check to make sure that they're all the same or something is probably frivolous
|
||||
if (lockedFailPopup != null)
|
||||
_popupSystem.PopupClient(Loc.GetString(lockedFailPopup), uid, args.User);
|
||||
else if (whitelistFailPopup != null)
|
||||
_popupSystem.PopupClient(Loc.GetString(whitelistFailPopup), uid, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
// Drop the held item onto the floor. Return if the user cannot drop.
|
||||
if (!_handsSystem.TryDrop(args.User, args.Used, handsComp: hands))
|
||||
@@ -236,23 +274,31 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Insert
|
||||
|
||||
/// <summary>
|
||||
/// Insert an item into a slot. This does not perform checks, so make sure to also use <see
|
||||
/// cref="CanInsert"/> or just use <see cref="TryInsert"/> instead.
|
||||
/// </summary>
|
||||
/// <param name="excludeUserAudio">If true, will exclude the user when playing sound. Does nothing client-side.
|
||||
/// Useful for predicted interactions</param>
|
||||
private void Insert(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user, bool excludeUserAudio = false)
|
||||
private void Insert(EntityUid uid,
|
||||
ItemSlot slot,
|
||||
EntityUid item,
|
||||
EntityUid? user,
|
||||
bool excludeUserAudio = false)
|
||||
{
|
||||
bool? inserted = slot.ContainerSlot != null ? _containers.Insert(item, slot.ContainerSlot) : null;
|
||||
// ContainerSlot automatically raises a directed EntInsertedIntoContainerMessage
|
||||
|
||||
// Logging
|
||||
if (inserted != null && inserted.Value && user != null)
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user.Value)} inserted {ToPrettyString(item)} into {slot.ContainerSlot?.ID + " slot of "}{ToPrettyString(uid)}");
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(user.Value)} inserted {ToPrettyString(item)} into {slot.ContainerSlot?.ID + " slot of "}{ToPrettyString(uid)}");
|
||||
|
||||
_audioSystem.PlayPredicted(slot.InsertSound, uid, excludeUserAudio ? user : null);
|
||||
}
|
||||
@@ -261,46 +307,53 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
/// Check whether a given item can be inserted into a slot. Unless otherwise specified, this will return
|
||||
/// false if the slot is already filled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If a popup entity is given, and if the item slot is set to generate a popup message when it fails to
|
||||
/// pass the whitelist or due to slot being locked, then this will generate an appropriate popup.
|
||||
/// </remarks>
|
||||
public bool CanInsert(EntityUid uid, EntityUid usedUid, EntityUid? user, ItemSlot slot, bool swap = false, EntityUid? popup = null)
|
||||
public bool CanInsert(EntityUid uid,
|
||||
EntityUid usedUid,
|
||||
EntityUid? user,
|
||||
ItemSlot slot,
|
||||
bool swap = false)
|
||||
{
|
||||
if (slot.ContainerSlot == null)
|
||||
return false;
|
||||
|
||||
if (_whitelistSystem.IsWhitelistFail(slot.Whitelist, usedUid) || _whitelistSystem.IsBlacklistPass(slot.Blacklist, usedUid))
|
||||
{
|
||||
if (popup.HasValue && slot.WhitelistFailPopup.HasValue)
|
||||
_popupSystem.PopupClient(Loc.GetString(slot.WhitelistFailPopup), uid, popup.Value);
|
||||
if (slot.HasItem && (!swap || swap && !CanEject(uid, user, slot)))
|
||||
return false;
|
||||
|
||||
if (!CanInsertWhitelist(usedUid, slot))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (slot.Locked)
|
||||
{
|
||||
if (popup.HasValue && slot.LockedFailPopup.HasValue)
|
||||
_popupSystem.PopupClient(Loc.GetString(slot.LockedFailPopup), uid, popup.Value);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (slot.HasItem && (!swap || (swap && !CanEject(uid, user, slot))))
|
||||
return false;
|
||||
|
||||
var ev = new ItemSlotInsertAttemptEvent(uid, usedUid, user, slot);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
RaiseLocalEvent(usedUid, ref ev);
|
||||
if (ev.Cancelled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _containers.CanInsert(usedUid, slot.ContainerSlot, assumeEmpty: swap);
|
||||
}
|
||||
|
||||
private bool CanInsertWhitelist(EntityUid usedUid, ItemSlot slot)
|
||||
{
|
||||
if (_whitelistSystem.IsWhitelistFail(slot.Whitelist, usedUid)
|
||||
|| _whitelistSystem.IsBlacklistPass(slot.Blacklist, usedUid))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to insert item into a specific slot.
|
||||
/// </summary>
|
||||
/// <returns>False if failed to insert item</returns>
|
||||
public bool TryInsert(EntityUid uid, string id, EntityUid item, EntityUid? user, ItemSlotsComponent? itemSlots = null, bool excludeUserAudio = false)
|
||||
public bool TryInsert(EntityUid uid,
|
||||
string id,
|
||||
EntityUid item,
|
||||
EntityUid? user,
|
||||
ItemSlotsComponent? itemSlots = null,
|
||||
bool excludeUserAudio = false)
|
||||
{
|
||||
if (!Resolve(uid, ref itemSlots))
|
||||
return false;
|
||||
@@ -315,7 +368,11 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
/// Tries to insert item into a specific slot.
|
||||
/// </summary>
|
||||
/// <returns>False if failed to insert item</returns>
|
||||
public bool TryInsert(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user, bool excludeUserAudio = false)
|
||||
public bool TryInsert(EntityUid uid,
|
||||
ItemSlot slot,
|
||||
EntityUid item,
|
||||
EntityUid? user,
|
||||
bool excludeUserAudio = false)
|
||||
{
|
||||
if (!CanInsert(uid, item, user, slot))
|
||||
return false;
|
||||
@@ -329,7 +386,11 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
/// Does not check action blockers.
|
||||
/// </summary>
|
||||
/// <returns>False if failed to insert item</returns>
|
||||
public bool TryInsertFromHand(EntityUid uid, ItemSlot slot, EntityUid user, HandsComponent? hands = null, bool excludeUserAudio = false)
|
||||
public bool TryInsertFromHand(EntityUid uid,
|
||||
ItemSlot slot,
|
||||
EntityUid user,
|
||||
HandsComponent? hands = null,
|
||||
bool excludeUserAudio = false)
|
||||
{
|
||||
if (!Resolve(user, ref hands, false))
|
||||
return false;
|
||||
@@ -443,6 +504,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Eject
|
||||
@@ -462,7 +524,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
return false;
|
||||
}
|
||||
|
||||
if (slot.ContainerSlot?.ContainedEntity is not {} item)
|
||||
if (slot.ContainerSlot?.ContainedEntity is not { } item)
|
||||
return false;
|
||||
|
||||
var ev = new ItemSlotEjectAttemptEvent(uid, item, user, slot);
|
||||
@@ -487,7 +549,9 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
|
||||
// Logging
|
||||
if (ejected != null && ejected.Value && user != null)
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user.Value)} ejected {ToPrettyString(item)} from {slot.ContainerSlot?.ID + " slot of "}{ToPrettyString(uid)}");
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(user.Value)} ejected {ToPrettyString(item)} from {slot.ContainerSlot?.ID + " slot of "}{ToPrettyString(uid)}");
|
||||
|
||||
_audioSystem.PlayPredicted(slot.EjectSound, uid, excludeUserAudio ? user : null);
|
||||
}
|
||||
@@ -496,7 +560,11 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
/// Try to eject an item from a slot.
|
||||
/// </summary>
|
||||
/// <returns>False if item slot is locked or has no item inserted</returns>
|
||||
public bool TryEject(EntityUid uid, ItemSlot slot, EntityUid? user, [NotNullWhen(true)] out EntityUid? item, bool excludeUserAudio = false)
|
||||
public bool TryEject(EntityUid uid,
|
||||
ItemSlot slot,
|
||||
EntityUid? user,
|
||||
[NotNullWhen(true)] out EntityUid? item,
|
||||
bool excludeUserAudio = false)
|
||||
{
|
||||
item = null;
|
||||
|
||||
@@ -518,8 +586,12 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
/// Try to eject item from a slot.
|
||||
/// </summary>
|
||||
/// <returns>False if the id is not valid, the item slot is locked, or it has no item inserted</returns>
|
||||
public bool TryEject(EntityUid uid, string id, EntityUid? user,
|
||||
[NotNullWhen(true)] out EntityUid? item, ItemSlotsComponent? itemSlots = null, bool excludeUserAudio = false)
|
||||
public bool TryEject(EntityUid uid,
|
||||
string id,
|
||||
EntityUid? user,
|
||||
[NotNullWhen(true)] out EntityUid? item,
|
||||
ItemSlotsComponent? itemSlots = null,
|
||||
bool excludeUserAudio = false)
|
||||
{
|
||||
item = null;
|
||||
|
||||
@@ -550,12 +622,16 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Verbs
|
||||
private void AddAlternativeVerbs(EntityUid uid, ItemSlotsComponent itemSlots, GetVerbsEvent<AlternativeVerb> args)
|
||||
|
||||
private void AddAlternativeVerbs(EntityUid uid,
|
||||
ItemSlotsComponent itemSlots,
|
||||
GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (args.Hands == null || !args.CanAccess ||!args.CanInteract)
|
||||
if (args.Hands == null || !args.CanAccess || !args.CanInteract)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -649,7 +725,9 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
}
|
||||
}
|
||||
|
||||
private void AddInteractionVerbsVerbs(EntityUid uid, ItemSlotsComponent itemSlots, GetVerbsEvent<InteractionVerb> args)
|
||||
private void AddInteractionVerbsVerbs(EntityUid uid,
|
||||
ItemSlotsComponent itemSlots,
|
||||
GetVerbsEvent<InteractionVerb> args)
|
||||
{
|
||||
if (args.Hands == null || !args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
@@ -708,7 +786,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
new SpriteSpecifier.Texture(
|
||||
new ResPath("/Textures/Interface/VerbIcons/insert.svg.192dpi.png"));
|
||||
}
|
||||
else if(slot.EjectOnInteract)
|
||||
else if (slot.EjectOnInteract)
|
||||
{
|
||||
// Inserting/ejecting is a primary interaction for this entity. Instead of using the insert
|
||||
// category, we will use a single "Place <item>" verb.
|
||||
@@ -727,9 +805,11 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
args.Verbs.Add(insertVerb);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BUIs
|
||||
|
||||
private void HandleButtonPressed(EntityUid uid, ItemSlotsComponent component, ItemSlotButtonPressedEvent args)
|
||||
{
|
||||
if (!component.Slots.TryGetValue(args.SlotId, out var slot))
|
||||
@@ -740,6 +820,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
else if (args.TryInsert && !slot.HasItem)
|
||||
TryInsertFromHand(uid, slot, args.Actor);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -47,7 +47,7 @@ public abstract partial class InventorySystem
|
||||
|
||||
private void OnEntRemoved(EntityUid uid, InventoryComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if(!TryGetSlot(uid, args.Container.ID, out var slotDef, inventory: component))
|
||||
if (!TryGetSlot(uid, args.Container.ID, out var slotDef, inventory: component))
|
||||
return;
|
||||
|
||||
var unequippedEvent = new DidUnequipEvent(uid, args.Entity, slotDef);
|
||||
@@ -59,8 +59,8 @@ public abstract partial class InventorySystem
|
||||
|
||||
private void OnEntInserted(EntityUid uid, InventoryComponent component, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
if(!TryGetSlot(uid, args.Container.ID, out var slotDef, inventory: component))
|
||||
return;
|
||||
if (!TryGetSlot(uid, args.Container.ID, out var slotDef, inventory: component))
|
||||
return;
|
||||
|
||||
var equippedEvent = new DidEquipEvent(uid, args.Entity, slotDef);
|
||||
RaiseLocalEvent(uid, equippedEvent, true);
|
||||
@@ -118,7 +118,7 @@ public abstract partial class InventorySystem
|
||||
|
||||
RaiseLocalEvent(held.Value, new HandDeselectedEvent(actor));
|
||||
|
||||
TryEquip(actor, actor, held.Value, ev.Slot, predicted: true, inventory: inventory, force: true, checkDoafter:true);
|
||||
TryEquip(actor, actor, held.Value, ev.Slot, predicted: true, inventory: inventory, force: true, checkDoafter: true);
|
||||
}
|
||||
|
||||
public bool TryEquip(EntityUid uid, EntityUid itemUid, string slot, bool silent = false, bool force = false, bool predicted = false,
|
||||
@@ -365,6 +365,25 @@ public abstract partial class InventorySystem
|
||||
ClothingComponent? clothing = null,
|
||||
bool reparent = true,
|
||||
bool checkDoafter = false)
|
||||
{
|
||||
var itemsDropped = 0;
|
||||
return TryUnequip(actor, target, slot, out removedItem, ref itemsDropped,
|
||||
silent, force, predicted, inventory, clothing, reparent, checkDoafter);
|
||||
}
|
||||
|
||||
private bool TryUnequip(
|
||||
EntityUid actor,
|
||||
EntityUid target,
|
||||
string slot,
|
||||
[NotNullWhen(true)] out EntityUid? removedItem,
|
||||
ref int itemsDropped,
|
||||
bool silent = false,
|
||||
bool force = false,
|
||||
bool predicted = false,
|
||||
InventoryComponent? inventory = null,
|
||||
ClothingComponent? clothing = null,
|
||||
bool reparent = true,
|
||||
bool checkDoafter = false)
|
||||
{
|
||||
removedItem = null;
|
||||
|
||||
@@ -423,17 +442,27 @@ public abstract partial class InventorySystem
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_containerSystem.Remove(removedItem.Value, slotContainer, force: force, reparent: reparent))
|
||||
return false;
|
||||
|
||||
// this is in order to keep track of whether this is the first instance of a recursion call
|
||||
var firstRun = itemsDropped == 0;
|
||||
++itemsDropped;
|
||||
|
||||
foreach (var slotDef in inventory.Slots)
|
||||
{
|
||||
if (slotDef != slotDefinition && slotDef.DependsOn == slotDefinition.Name)
|
||||
{
|
||||
//this recursive call might be risky
|
||||
TryUnequip(actor, target, slotDef.Name, true, true, predicted, inventory, reparent: reparent);
|
||||
TryUnequip(actor, target, slotDef.Name, out _, ref itemsDropped, true, true, predicted, inventory, reparent: reparent);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_containerSystem.Remove(removedItem.Value, slotContainer, force: force, reparent: reparent))
|
||||
return false;
|
||||
// we check if any items were dropped, and make a popup if they were.
|
||||
// the reason we check for > 1 is because the first item is always the one we are trying to unequip,
|
||||
// whereas we only want to notify for extra dropped items.
|
||||
if (!silent && _gameTiming.IsFirstTimePredicted && firstRun && itemsDropped > 1)
|
||||
_popup.PopupClient(Loc.GetString("inventory-component-dropped-from-unequip", ("items", itemsDropped - 1)), target, target);
|
||||
|
||||
// TODO: Inventory needs a hot cleanup hoo boy
|
||||
// Check if something else (AKA toggleable) dumped it into a container.
|
||||
@@ -466,7 +495,7 @@ public abstract partial class InventorySystem
|
||||
if ((containerSlot == null || slotDefinition == null) && !TryGetSlotContainer(target, slot, out containerSlot, out slotDefinition, inventory))
|
||||
return false;
|
||||
|
||||
if (containerSlot.ContainedEntity is not {} itemUid)
|
||||
if (containerSlot.ContainedEntity is not { } itemUid)
|
||||
return false;
|
||||
|
||||
if (!_containerSystem.CanRemove(itemUid, containerSlot))
|
||||
|
||||
@@ -81,6 +81,24 @@ public sealed partial class NpcFactionSystem : EntitySystem
|
||||
return ent.Comp.Factions.Contains(faction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether an entity is a member of any listed faction.
|
||||
/// If the list is empty this returns false.
|
||||
/// </summary>
|
||||
public bool IsMemberOfAny(Entity<NpcFactionMemberComponent?> ent, IEnumerable<ProtoId<NpcFactionPrototype>> factions)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
foreach (var faction in factions)
|
||||
{
|
||||
if (ent.Comp.Factions.Contains(faction))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds this entity to the particular faction.
|
||||
/// </summary>
|
||||
|
||||
@@ -33,18 +33,10 @@ public sealed class HungerSystem : EntitySystem
|
||||
[ValidatePrototypeId<SatiationIconPrototype>]
|
||||
private const string HungerIconStarvingId = "HungerIconStarving";
|
||||
|
||||
private SatiationIconPrototype? _hungerIconOverfed;
|
||||
private SatiationIconPrototype? _hungerIconPeckish;
|
||||
private SatiationIconPrototype? _hungerIconStarving;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
DebugTools.Assert(_prototype.TryIndex(HungerIconOverfedId, out _hungerIconOverfed) &&
|
||||
_prototype.TryIndex(HungerIconPeckishId, out _hungerIconPeckish) &&
|
||||
_prototype.TryIndex(HungerIconStarvingId, out _hungerIconStarving));
|
||||
|
||||
SubscribeLocalEvent<HungerComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<HungerComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<HungerComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovespeed);
|
||||
@@ -221,13 +213,13 @@ public sealed class HungerSystem : EntitySystem
|
||||
switch (component.CurrentThreshold)
|
||||
{
|
||||
case HungerThreshold.Overfed:
|
||||
prototype = _hungerIconOverfed;
|
||||
_prototype.TryIndex(HungerIconOverfedId, out prototype);
|
||||
break;
|
||||
case HungerThreshold.Peckish:
|
||||
prototype = _hungerIconPeckish;
|
||||
_prototype.TryIndex(HungerIconPeckishId, out prototype);
|
||||
break;
|
||||
case HungerThreshold.Starving:
|
||||
prototype = _hungerIconStarving;
|
||||
_prototype.TryIndex(HungerIconStarvingId, out prototype);
|
||||
break;
|
||||
default:
|
||||
prototype = null;
|
||||
|
||||
@@ -9,6 +9,7 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
@@ -31,18 +32,10 @@ public sealed class ThirstSystem : EntitySystem
|
||||
[ValidatePrototypeId<SatiationIconPrototype>]
|
||||
private const string ThirstIconParchedId = "ThirstIconParched";
|
||||
|
||||
private SatiationIconPrototype? _thirstIconOverhydrated = null;
|
||||
private SatiationIconPrototype? _thirstIconThirsty = null;
|
||||
private SatiationIconPrototype? _thirstIconParched = null;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
DebugTools.Assert(_prototype.TryIndex(ThirstIconOverhydratedId, out _thirstIconOverhydrated) &&
|
||||
_prototype.TryIndex(ThirstIconThirstyId, out _thirstIconThirsty) &&
|
||||
_prototype.TryIndex(ThirstIconParchedId, out _thirstIconParched));
|
||||
|
||||
SubscribeLocalEvent<ThirstComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovespeed);
|
||||
SubscribeLocalEvent<ThirstComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<ThirstComponent, RejuvenateEvent>(OnRejuvenate);
|
||||
@@ -128,26 +121,28 @@ public sealed class ThirstSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetStatusIconPrototype(ThirstComponent component, out SatiationIconPrototype? prototype)
|
||||
public bool TryGetStatusIconPrototype(ThirstComponent component, [NotNullWhen(true)] out SatiationIconPrototype? prototype)
|
||||
{
|
||||
switch (component.CurrentThirstThreshold)
|
||||
{
|
||||
case ThirstThreshold.OverHydrated:
|
||||
prototype = _thirstIconOverhydrated;
|
||||
return true;
|
||||
_prototype.TryIndex(ThirstIconOverhydratedId, out prototype);
|
||||
break;
|
||||
|
||||
case ThirstThreshold.Thirsty:
|
||||
prototype = _thirstIconThirsty;
|
||||
return true;
|
||||
_prototype.TryIndex(ThirstIconThirstyId, out prototype);
|
||||
break;
|
||||
|
||||
case ThirstThreshold.Parched:
|
||||
prototype = _thirstIconParched;
|
||||
return true;
|
||||
_prototype.TryIndex(ThirstIconParchedId, out prototype);
|
||||
break;
|
||||
|
||||
default:
|
||||
prototype = null;
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
return prototype != null;
|
||||
}
|
||||
|
||||
private void UpdateEffects(EntityUid uid, ThirstComponent component)
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Overlays;
|
||||
|
||||
@@ -15,8 +14,11 @@ public sealed partial class ShowHealthBarsComponent : Component
|
||||
/// <summary>
|
||||
/// Displays health bars of the damage containers.
|
||||
/// </summary>
|
||||
[DataField("damageContainers", customTypeSerializer: typeof(PrototypeIdListSerializer<DamageContainerPrototype>))]
|
||||
public List<string> DamageContainers = new();
|
||||
[DataField]
|
||||
public List<ProtoId<DamageContainerPrototype>> DamageContainers = new()
|
||||
{
|
||||
"Biological"
|
||||
};
|
||||
|
||||
[DataField]
|
||||
public ProtoId<HealthIconPrototype>? HealthStatusIcon = "HealthIconFine";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Overlays;
|
||||
|
||||
@@ -13,6 +13,9 @@ public sealed partial class ShowHealthIconsComponent : Component
|
||||
/// <summary>
|
||||
/// Displays health status icons of the damage containers.
|
||||
/// </summary>
|
||||
[DataField("damageContainers", customTypeSerializer: typeof(PrototypeIdListSerializer<DamageContainerPrototype>))]
|
||||
public List<string> DamageContainers = new();
|
||||
[DataField]
|
||||
public List<ProtoId<DamageContainerPrototype>> DamageContainers = new()
|
||||
{
|
||||
"Biological"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public sealed partial class SiliconLawProviderComponent : Component
|
||||
|
||||
/// <summary>
|
||||
/// The sound that plays for the Silicon player
|
||||
/// when the particular lawboard has been inserted.
|
||||
/// when the law change is processed for the provider.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier? LawUploadSound = new SoundPathSpecifier("/Audio/Misc/cryo_warning.ogg");
|
||||
|
||||
@@ -324,6 +324,7 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
|
||||
if (TryComp(user, out EyeComponent? eyeComp))
|
||||
{
|
||||
_eye.SetDrawFov(user, false, eyeComp);
|
||||
_eye.SetTarget(user, ent.Comp.RemoteEntity.Value, eyeComp);
|
||||
}
|
||||
|
||||
@@ -356,6 +357,7 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
|
||||
if (TryComp(args.Entity, out EyeComponent? eyeComp))
|
||||
{
|
||||
_eye.SetDrawFov(args.Entity, true, eyeComp);
|
||||
_eye.SetTarget(args.Entity, null, eyeComp);
|
||||
}
|
||||
ClearEye(ent);
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Shared.Examine;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Inventory.VirtualItem;
|
||||
using Content.Shared.Item;
|
||||
@@ -125,7 +126,7 @@ public sealed class WieldableSystem : EntitySystem
|
||||
|
||||
private void OnExamine(EntityUid uid, GunWieldBonusComponent component, ref ExaminedEvent args)
|
||||
{
|
||||
if (HasComp<GunRequiresWieldComponent>(uid))
|
||||
if (HasComp<GunRequiresWieldComponent>(uid))
|
||||
return;
|
||||
|
||||
if (component.WieldBonusExamineMessage != null)
|
||||
@@ -253,7 +254,7 @@ public sealed class WieldableSystem : EntitySystem
|
||||
return false;
|
||||
|
||||
var selfMessage = Loc.GetString("wieldable-component-successful-wield", ("item", used));
|
||||
var othersMessage = Loc.GetString("wieldable-component-successful-wield-other", ("user", user), ("item", used));
|
||||
var othersMessage = Loc.GetString("wieldable-component-successful-wield-other", ("user", Identity.Entity(user, EntityManager)), ("item", used));
|
||||
_popupSystem.PopupPredicted(selfMessage, othersMessage, user, user);
|
||||
|
||||
var targEv = new ItemWieldedEvent();
|
||||
@@ -298,7 +299,7 @@ public sealed class WieldableSystem : EntitySystem
|
||||
_audioSystem.PlayPredicted(component.UnwieldSound, uid, args.User);
|
||||
|
||||
var selfMessage = Loc.GetString("wieldable-component-failed-wield", ("item", uid));
|
||||
var othersMessage = Loc.GetString("wieldable-component-failed-wield-other", ("user", args.User.Value), ("item", uid));
|
||||
var othersMessage = Loc.GetString("wieldable-component-failed-wield-other", ("user", Identity.Entity(args.User.Value, EntityManager)), ("item", uid));
|
||||
_popupSystem.PopupPredicted(selfMessage, othersMessage, args.User.Value, args.User.Value);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user