Merge branch 'master' of https://github.com/space-wizards/space-station-14 into map-load-refactor
This commit is contained in:
@@ -76,7 +76,7 @@ public sealed partial class AccessReaderComponent : Component
|
||||
/// Whether or not emag interactions have an effect on this.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool BreakOnEmag = true;
|
||||
public bool BreakOnAccessBreaker = true;
|
||||
}
|
||||
|
||||
[DataDefinition, Serializable, NetSerializable]
|
||||
|
||||
@@ -22,6 +22,8 @@ public sealed partial class IdCardComponent : Component
|
||||
[Access(typeof(SharedIdCardSystem), typeof(SharedPdaSystem), typeof(SharedAgentIdCardSystem), Other = AccessPermissions.ReadWrite)]
|
||||
public LocId? JobTitle;
|
||||
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
private string? _jobTitle;
|
||||
|
||||
[Access(typeof(SharedIdCardSystem), typeof(SharedPdaSystem), typeof(SharedAgentIdCardSystem), Other = AccessPermissions.ReadWriteExecute)]
|
||||
|
||||
@@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.DeviceLinking.Events;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Inventory;
|
||||
@@ -24,6 +23,7 @@ public sealed class AccessReaderSystem : EntitySystem
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly SharedGameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
@@ -71,17 +71,28 @@ public sealed class AccessReaderSystem : EntitySystem
|
||||
{
|
||||
if (args.User == null) // AutoLink (and presumably future external linkers) have no user.
|
||||
return;
|
||||
if (!HasComp<EmaggedComponent>(uid) && !IsAllowed(args.User.Value, uid, component))
|
||||
if (!IsAllowed(args.User.Value, uid, component))
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnEmagged(EntityUid uid, AccessReaderComponent reader, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!reader.BreakOnEmag)
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Access))
|
||||
return;
|
||||
|
||||
if (!reader.BreakOnAccessBreaker)
|
||||
return;
|
||||
|
||||
if (!GetMainAccessReader(uid, out var accessReader))
|
||||
return;
|
||||
|
||||
if (accessReader.Value.Comp.AccessLists.Count < 1)
|
||||
return;
|
||||
|
||||
args.Repeatable = true;
|
||||
args.Handled = true;
|
||||
reader.Enabled = false;
|
||||
reader.AccessLog.Clear();
|
||||
accessReader.Value.Comp.AccessLists.Clear();
|
||||
accessReader.Value.Comp.AccessLog.Clear();
|
||||
Dirty(uid, reader);
|
||||
}
|
||||
|
||||
@@ -135,6 +146,7 @@ public sealed class AccessReaderSystem : EntitySystem
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,3 +45,6 @@ namespace Content.Shared.Access.Systems
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct OnAccessOverriderAccessUpdatedEvent(EntityUid UserUid, bool Handled = false);
|
||||
|
||||
@@ -199,7 +199,8 @@ namespace Content.Shared.ActionBlocker
|
||||
{
|
||||
var containerEv = new CanAttackFromContainerEvent(uid, target);
|
||||
RaiseLocalEvent(uid, containerEv);
|
||||
return containerEv.CanAttack;
|
||||
if (!containerEv.CanAttack)
|
||||
return false;
|
||||
}
|
||||
|
||||
var ev = new AttackAttemptEvent(uid, target, weapon, disarm);
|
||||
|
||||
@@ -167,6 +167,14 @@ public abstract partial class BaseActionComponent : Component
|
||||
[DataField]
|
||||
public bool RaiseOnUser;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this will cause the the action event to always be raised directed at the action itself instead of the action's container/provider.
|
||||
/// Takes priority over RaiseOnUser.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[Obsolete("This datafield will be reworked in an upcoming action refactor")]
|
||||
public bool RaiseOnAction;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to automatically add this action to the action bar when it becomes available.
|
||||
/// </summary>
|
||||
@@ -212,6 +220,7 @@ public abstract class BaseActionComponentState : ComponentState
|
||||
public int Priority;
|
||||
public NetEntity? AttachedEntity;
|
||||
public bool RaiseOnUser;
|
||||
public bool RaiseOnAction;
|
||||
public bool AutoPopulate;
|
||||
public bool Temporary;
|
||||
public ItemActionIconStyle ItemIconStyle;
|
||||
@@ -223,6 +232,7 @@ public abstract class BaseActionComponentState : ComponentState
|
||||
EntityIcon = entManager.GetNetEntity(component.EntIcon);
|
||||
AttachedEntity = entManager.GetNetEntity(component.AttachedEntity);
|
||||
RaiseOnUser = component.RaiseOnUser;
|
||||
RaiseOnAction = component.RaiseOnAction;
|
||||
Icon = component.Icon;
|
||||
IconOn = component.IconOn;
|
||||
IconColor = component.IconColor;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Whitelist;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
@@ -25,6 +25,12 @@ public sealed partial class EntityTargetActionComponent : BaseTargetActionCompon
|
||||
/// <remarks>No whitelist check when null.</remarks>
|
||||
[DataField("whitelist")] public EntityWhitelist? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// Determines which entities are NOT valid targets for this action.
|
||||
/// </summary>
|
||||
/// <remarks>No blacklist check when null.</remarks>
|
||||
[DataField] public EntityWhitelist? Blacklist;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this action considers the user as a valid target entity when using this action.
|
||||
/// </summary>
|
||||
@@ -35,11 +41,13 @@ public sealed partial class EntityTargetActionComponent : BaseTargetActionCompon
|
||||
public sealed class EntityTargetActionComponentState : BaseActionComponentState
|
||||
{
|
||||
public EntityWhitelist? Whitelist;
|
||||
public EntityWhitelist? Blacklist;
|
||||
public bool CanTargetSelf;
|
||||
|
||||
public EntityTargetActionComponentState(EntityTargetActionComponent component, IEntityManager entManager) : base(component, entManager)
|
||||
{
|
||||
Whitelist = component.Whitelist;
|
||||
Blacklist = component.Blacklist;
|
||||
CanTargetSelf = component.CanTargetSelf;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,6 +538,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
if (!ValidateEntityTargetBase(user,
|
||||
target,
|
||||
comp.Whitelist,
|
||||
comp.Blacklist,
|
||||
comp.CheckCanInteract,
|
||||
comp.CanTargetSelf,
|
||||
comp.CheckCanAccess,
|
||||
@@ -552,6 +553,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
private bool ValidateEntityTargetBase(EntityUid user,
|
||||
EntityUid? targetEntity,
|
||||
EntityWhitelist? whitelist,
|
||||
EntityWhitelist? blacklist,
|
||||
bool checkCanInteract,
|
||||
bool canTargetSelf,
|
||||
bool checkCanAccess,
|
||||
@@ -563,6 +565,9 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
if (_whitelistSystem.IsWhitelistFail(whitelist, target))
|
||||
return false;
|
||||
|
||||
if (_whitelistSystem.IsBlacklistPass(blacklist, target))
|
||||
return false;
|
||||
|
||||
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, target))
|
||||
return false;
|
||||
|
||||
@@ -637,6 +642,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
var entityValidated = ValidateEntityTargetBase(user,
|
||||
entity,
|
||||
comp.Whitelist,
|
||||
null,
|
||||
comp.CheckCanInteract,
|
||||
comp.CanTargetSelf,
|
||||
comp.CheckCanAccess,
|
||||
@@ -679,6 +685,9 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
if (!action.RaiseOnUser && action.Container != null && !HasComp<MindComponent>(action.Container))
|
||||
target = action.Container.Value;
|
||||
|
||||
if (action.RaiseOnAction)
|
||||
target = actionId;
|
||||
|
||||
RaiseLocalEvent(target, (object) actionEvent, broadcast: true);
|
||||
handled = actionEvent.Handled;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
@@ -32,7 +31,6 @@ public sealed class UdderSystem : EntitySystem
|
||||
SubscribeLocalEvent<UdderComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<UdderComponent, GetVerbsEvent<AlternativeVerb>>(AddMilkVerb);
|
||||
SubscribeLocalEvent<UdderComponent, MilkingDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<UdderComponent, ExaminedEvent>(OnExamine);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, UdderComponent component, MapInitEvent args)
|
||||
@@ -140,50 +138,4 @@ public sealed class UdderSystem : EntitySystem
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the text provided on examine.
|
||||
/// Changes depending on the amount of hunger the target has.
|
||||
/// </summary>
|
||||
private void OnExamine(Entity<UdderComponent> entity, ref ExaminedEvent args)
|
||||
{
|
||||
|
||||
var entityIdentity = Identity.Entity(args.Examined, EntityManager);
|
||||
|
||||
string message;
|
||||
|
||||
// Check if the target has hunger, otherwise return not hungry.
|
||||
if (!TryComp<HungerComponent>(entity, out var hunger))
|
||||
{
|
||||
message = Loc.GetString("udder-system-examine-none", ("entity", entityIdentity));
|
||||
args.PushMarkup(message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Choose the correct examine string based on HungerThreshold.
|
||||
switch (_hunger.GetHungerThreshold(hunger))
|
||||
{
|
||||
case >= HungerThreshold.Overfed:
|
||||
message = Loc.GetString("udder-system-examine-overfed", ("entity", entityIdentity));
|
||||
break;
|
||||
|
||||
case HungerThreshold.Okay:
|
||||
message = Loc.GetString("udder-system-examine-okay", ("entity", entityIdentity));
|
||||
break;
|
||||
|
||||
case HungerThreshold.Peckish:
|
||||
message = Loc.GetString("udder-system-examine-hungry", ("entity", entityIdentity));
|
||||
break;
|
||||
|
||||
// There's a final hunger threshold called "dead" but animals don't actually die so we'll re-use this.
|
||||
case <= HungerThreshold.Starving:
|
||||
message = Loc.GetString("udder-system-examine-starved", ("entity", entityIdentity));
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
args.PushMarkup(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -30,3 +31,24 @@ public sealed partial class ArmorComponent : Component
|
||||
/// <param name="Msg"></param>
|
||||
[ByRefEvent]
|
||||
public record struct ArmorExamineEvent(FormattedMessage Msg);
|
||||
|
||||
/// <summary>
|
||||
/// A Relayed inventory event, gets the total Armor for all Inventory slots defined by the Slotflags in TargetSlots
|
||||
/// </summary>
|
||||
public sealed class CoefficientQueryEvent : EntityEventArgs, IInventoryRelayEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// All slots to relay to
|
||||
/// </summary>
|
||||
public SlotFlags TargetSlots { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Total of all Coefficients.
|
||||
/// </summary>
|
||||
public DamageModifierSet DamageModifiers { get; set; } = new DamageModifierSet();
|
||||
|
||||
public CoefficientQueryEvent(SlotFlags slots)
|
||||
{
|
||||
TargetSlots = slots;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,25 @@ public abstract class SharedArmorSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ArmorComponent, InventoryRelayedEvent<CoefficientQueryEvent>>(OnCoefficientQuery);
|
||||
SubscribeLocalEvent<ArmorComponent, InventoryRelayedEvent<DamageModifyEvent>>(OnDamageModify);
|
||||
SubscribeLocalEvent<ArmorComponent, BorgModuleRelayedEvent<DamageModifyEvent>>(OnBorgDamageModify);
|
||||
SubscribeLocalEvent<ArmorComponent, GetVerbsEvent<ExamineVerb>>(OnArmorVerbExamine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the total Damage reduction value of all equipment caught by the relay.
|
||||
/// </summary>
|
||||
/// <param name="ent">The item that's being relayed to</param>
|
||||
/// <param name="args">The event, contains the running count of armor percentage as a coefficient</param>
|
||||
private void OnCoefficientQuery(Entity<ArmorComponent> ent, ref InventoryRelayedEvent<CoefficientQueryEvent> args)
|
||||
{
|
||||
foreach (var armorCoefficient in ent.Comp.Modifiers.Coefficients)
|
||||
{
|
||||
args.Args.DamageModifiers.Coefficients[armorCoefficient.Key] = args.Args.DamageModifiers.Coefficients.TryGetValue(armorCoefficient.Key, out var coefficient) ? coefficient * armorCoefficient.Value : armorCoefficient.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDamageModify(EntityUid uid, ArmorComponent component, InventoryRelayedEvent<DamageModifyEvent> args)
|
||||
{
|
||||
args.Args.Damage = DamageSpecifier.ApplyModifierSet(args.Args.Damage, component.Modifiers);
|
||||
|
||||
@@ -40,6 +40,12 @@ namespace Content.Shared.Atmos
|
||||
/// </summary>
|
||||
public const float T20C = 293.15f;
|
||||
|
||||
/// <summary>
|
||||
/// -38.15ºC in K.
|
||||
/// This is used to initialize roundstart freezer rooms.
|
||||
/// </summary>
|
||||
public const float FreezerTemp = 235f;
|
||||
|
||||
/// <summary>
|
||||
/// Do not allow any gas mixture temperatures to exceed this number. It is occasionally possible
|
||||
/// to have very small heat capacity (e.g. room that was just unspaced) and for large amounts of
|
||||
@@ -65,6 +71,12 @@ namespace Content.Shared.Atmos
|
||||
/// </summary>
|
||||
public const float MolesCellStandard = (OneAtmosphere * CellVolume / (T20C * R));
|
||||
|
||||
/// <summary>
|
||||
/// Moles in a 2.5 m^3 cell at 101.325 kPa and -38.15ºC.
|
||||
/// This is used in fix atmos freezer markers to ensure the air is at the correct atmospheric pressure while still being cold.
|
||||
/// </summary>
|
||||
public const float MolesCellFreezer = (OneAtmosphere * CellVolume / (FreezerTemp * R));
|
||||
|
||||
/// <summary>
|
||||
/// Moles in a 2.5 m^3 cell at GasMinerDefaultMaxExternalPressure kPa and 20ºC
|
||||
/// </summary>
|
||||
@@ -81,6 +93,9 @@ namespace Content.Shared.Atmos
|
||||
public const float OxygenMolesStandard = MolesCellStandard * OxygenStandard;
|
||||
public const float NitrogenMolesStandard = MolesCellStandard * NitrogenStandard;
|
||||
|
||||
public const float OxygenMolesFreezer = MolesCellFreezer * OxygenStandard;
|
||||
public const float NitrogenMolesFreezer = MolesCellFreezer * NitrogenStandard;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Atmos.Components;
|
||||
@@ -21,5 +22,6 @@ public sealed partial class GasPressurePumpComponent : Component
|
||||
/// Max pressure of the target gas (NOT relative to source).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[GuidebookData]
|
||||
public float MaxTargetPressure = Atmospherics.MaxOutputPressure;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
@@ -13,12 +14,12 @@ namespace Content.Shared.Atmos
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataDefinition]
|
||||
public sealed partial class GasMixture : IEquatable<GasMixture>, ISerializationHooks
|
||||
public sealed partial class GasMixture : IEquatable<GasMixture>, ISerializationHooks, IEnumerable<(Gas gas, float moles)>
|
||||
{
|
||||
public static GasMixture SpaceGas => new() {Volume = Atmospherics.CellVolume, Temperature = Atmospherics.TCMB, Immutable = true};
|
||||
|
||||
// No access, to ensure immutable mixtures are never accidentally mutated.
|
||||
[Access(typeof(SharedAtmosphereSystem), typeof(SharedAtmosDebugOverlaySystem), Other = AccessPermissions.None)]
|
||||
[Access(typeof(SharedAtmosphereSystem), typeof(SharedAtmosDebugOverlaySystem), typeof(GasEnumerator), Other = AccessPermissions.None)]
|
||||
[DataField]
|
||||
public float[] Moles = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
|
||||
@@ -32,10 +33,9 @@ namespace Content.Shared.Atmos
|
||||
public bool Immutable { get; private set; }
|
||||
|
||||
[ViewVariables]
|
||||
public readonly Dictionary<GasReaction, float> ReactionResults = new()
|
||||
public readonly float[] ReactionResults =
|
||||
{
|
||||
// We initialize the dictionary here.
|
||||
{ GasReaction.Fire, 0f }
|
||||
0f,
|
||||
};
|
||||
|
||||
[ViewVariables]
|
||||
@@ -249,6 +249,16 @@ namespace Content.Shared.Atmos
|
||||
return new GasMixtureStringRepresentation(TotalMoles, Temperature, Pressure, molesPerGas);
|
||||
}
|
||||
|
||||
GasEnumerator GetEnumerator()
|
||||
{
|
||||
return new GasEnumerator(this);
|
||||
}
|
||||
|
||||
IEnumerator<(Gas gas, float moles)> IEnumerable<(Gas gas, float moles)>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is GasMixture mix)
|
||||
@@ -289,6 +299,11 @@ namespace Content.Shared.Atmos
|
||||
return hashCode.ToHashCode();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public GasMixture Clone()
|
||||
{
|
||||
if (Immutable)
|
||||
@@ -302,5 +317,28 @@ namespace Content.Shared.Atmos
|
||||
};
|
||||
return newMixture;
|
||||
}
|
||||
|
||||
public struct GasEnumerator(GasMixture mixture) : IEnumerator<(Gas gas, float moles)>
|
||||
{
|
||||
private int _idx = -1;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
return ++_idx < Atmospherics.TotalNumberOfGases;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_idx = -1;
|
||||
}
|
||||
|
||||
public (Gas gas, float moles) Current => ((Gas)_idx, mixture.Moles[_idx]);
|
||||
object? IEnumerator.Current => Current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,10 +253,57 @@ public sealed partial class AtmosAlarmThreshold
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates through the changes that these threshold settings would make from a
|
||||
/// previous instance. Basically, diffs the two settings.
|
||||
/// </summary>
|
||||
public IEnumerable<AtmosAlarmThresholdChange> GetChanges(AtmosAlarmThreshold previous)
|
||||
{
|
||||
if (LowerBound != previous.LowerBound)
|
||||
yield return new AtmosAlarmThresholdChange(AtmosMonitorLimitType.LowerDanger, previous.LowerBound, LowerBound);
|
||||
|
||||
if (LowerWarningBound != previous.LowerWarningBound)
|
||||
yield return new AtmosAlarmThresholdChange(AtmosMonitorLimitType.LowerWarning, previous.LowerWarningBound, LowerWarningBound);
|
||||
|
||||
if (UpperBound != previous.UpperBound)
|
||||
yield return new AtmosAlarmThresholdChange(AtmosMonitorLimitType.UpperDanger, previous.UpperBound, UpperBound);
|
||||
|
||||
if (UpperWarningBound != previous.UpperWarningBound)
|
||||
yield return new AtmosAlarmThresholdChange(AtmosMonitorLimitType.UpperWarning, previous.UpperWarningBound, UpperWarningBound);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A change of a single value between two AtmosAlarmThreshold, for a given AtmosMonitorLimitType
|
||||
/// </summary>
|
||||
public readonly struct AtmosAlarmThresholdChange
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of change between the two threshold sets
|
||||
/// </summary>
|
||||
public readonly AtmosMonitorLimitType Type;
|
||||
|
||||
/// <summary>
|
||||
/// The value in the old threshold set
|
||||
/// </summary>
|
||||
public readonly AlarmThresholdSetting? Previous;
|
||||
|
||||
/// <summary>
|
||||
/// The value in the new threshold set
|
||||
/// </summary>
|
||||
public readonly AlarmThresholdSetting Current;
|
||||
|
||||
public AtmosAlarmThresholdChange(AtmosMonitorLimitType type, AlarmThresholdSetting? previous, AlarmThresholdSetting current)
|
||||
{
|
||||
Type = type;
|
||||
Previous = previous;
|
||||
Current = current;
|
||||
}
|
||||
}
|
||||
|
||||
[DataDefinition, Serializable]
|
||||
public readonly partial struct AlarmThresholdSetting
|
||||
public readonly partial struct AlarmThresholdSetting: IEquatable<AlarmThresholdSetting>
|
||||
{
|
||||
[DataField("enabled")]
|
||||
public bool Enabled { get; init; } = true;
|
||||
@@ -289,6 +336,32 @@ public readonly partial struct AlarmThresholdSetting
|
||||
{
|
||||
return this with {Enabled = enabled};
|
||||
}
|
||||
|
||||
public bool Equals(AlarmThresholdSetting other)
|
||||
{
|
||||
if (Enabled != other.Enabled)
|
||||
return false;
|
||||
|
||||
if (Value != other.Value)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool operator ==(AlarmThresholdSetting lhs, AlarmThresholdSetting rhs)
|
||||
{
|
||||
return lhs.Equals(rhs);
|
||||
}
|
||||
|
||||
public static bool operator !=(AlarmThresholdSetting lhs, AlarmThresholdSetting rhs)
|
||||
{
|
||||
return !lhs.Equals(rhs);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Enabled, Value);
|
||||
}
|
||||
}
|
||||
|
||||
public enum AtmosMonitorThresholdBound
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Bed.Sleep;
|
||||
|
||||
@@ -39,4 +41,11 @@ public sealed partial class SleepingComponent : Component
|
||||
{
|
||||
Params = AudioParams.Default.WithVariation(0.05f)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The fluent string prefix to use when picking a random suffix
|
||||
/// This is only active for those who have the sleeping component
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<LocalizedDatasetPrototype> ForceSaySleepDataset = "ForceSaySleepDataset";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Events;
|
||||
using Content.Shared.Damage.ForceSay;
|
||||
using Content.Shared.Emoting;
|
||||
using Content.Shared.Examine;
|
||||
@@ -18,6 +19,7 @@ using Content.Shared.Sound.Components;
|
||||
using Content.Shared.Speech;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Traits.Assorted;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -63,6 +65,8 @@ public sealed partial class SleepingSystem : EntitySystem
|
||||
SubscribeLocalEvent<ForcedSleepingComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<SleepingComponent, UnbuckleAttemptEvent>(OnUnbuckleAttempt);
|
||||
SubscribeLocalEvent<SleepingComponent, EmoteAttemptEvent>(OnEmoteAttempt);
|
||||
|
||||
SubscribeLocalEvent<SleepingComponent, BeforeForceSayEvent>(OnChangeForceSay, after: new []{typeof(PainNumbnessSystem)});
|
||||
}
|
||||
|
||||
private void OnUnbuckleAttempt(Entity<SleepingComponent> ent, ref UnbuckleAttemptEvent args)
|
||||
@@ -317,6 +321,11 @@ public sealed partial class SleepingSystem : EntitySystem
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnChangeForceSay(Entity<SleepingComponent> ent, ref BeforeForceSayEvent args)
|
||||
{
|
||||
args.Prefix = ent.Comp.ForceSaySleepDataset;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -89,6 +89,12 @@ public sealed partial class CCVars
|
||||
public static readonly CVarDef<bool> ServerBanErasePlayer =
|
||||
CVarDef.Create("admin.server_ban_erase_player", false, CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/// <summary>
|
||||
/// If true, will reset the last time the player has read the rules. This will mean on their next login they will be shown the rules again.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> ServerBanResetLastReadRules =
|
||||
CVarDef.Create("admin.server_ban_reset_last_read_rules", true, CVar.ARCHIVE | CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// Minimum players sharing a connection required to create an alert. -1 to disable the alert.
|
||||
/// </summary>
|
||||
@@ -176,4 +182,11 @@ public sealed partial class CCVars
|
||||
|
||||
public static readonly CVarDef<bool> BanHardwareIds =
|
||||
CVarDef.Create("ban.hardware_ids", true, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// If true, players are allowed to connect to multiple game servers at once.
|
||||
/// If false, they will be kicked from the first when connecting to another.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> AdminAllowMultiServerPlay =
|
||||
CVarDef.Create("admin.allow_multi_server_play", true, CVar.SERVERONLY);
|
||||
}
|
||||
|
||||
@@ -51,4 +51,23 @@ public sealed partial class CCVars
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> OpaqueStorageWindow =
|
||||
CVarDef.Create("control.opaque_storage_background", false, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the storage window has a title of the entity name.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> StorageWindowTitle =
|
||||
CVarDef.Create("control.storage_window_title", false, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// How many storage windows are allowed to be open at once.
|
||||
/// Recommended that you utilise this in conjunction with <see cref="StaticStorageUI"/>
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> StorageLimit =
|
||||
CVarDef.Create("control.storage_limit", 1, CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not storage can be opened recursively.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> NestedStorage =
|
||||
CVarDef.Create("control.nested_storage", true, CVar.REPLICATED | CVar.SERVER);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Movement.Systems;
|
||||
|
||||
namespace Content.Shared.Camera;
|
||||
@@ -17,3 +18,15 @@ namespace Content.Shared.Camera;
|
||||
/// </remarks>
|
||||
[ByRefEvent]
|
||||
public record struct GetEyeOffsetEvent(Vector2 Offset);
|
||||
|
||||
/// <summary>
|
||||
/// Raised on any equipped and in-hand items that may modify the eye offset.
|
||||
/// Pockets and suitstorage are excluded.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public sealed class GetEyeOffsetRelayedEvent : EntityEventArgs, IInventoryRelayEvent
|
||||
{
|
||||
public SlotFlags TargetSlots { get; } = ~(SlotFlags.POCKET & SlotFlags.SUITSTORAGE);
|
||||
|
||||
public Vector2 Offset;
|
||||
}
|
||||
|
||||
33
Content.Shared/Camera/GetEyePvsScaleEvent.cs
Normal file
33
Content.Shared/Camera/GetEyePvsScaleEvent.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Movement.Systems;
|
||||
|
||||
namespace Content.Shared.Camera;
|
||||
|
||||
/// <summary>
|
||||
/// Raised directed by-ref when <see cref="SharedContentEyeSystem.UpdatePvsScale"/> is called.
|
||||
/// Should be subscribed to by any systems that want to modify an entity's eye PVS scale,
|
||||
/// so that they do not override each other. Keep in mind that this should be done serverside;
|
||||
/// the client may set a new PVS scale, but the server won't provide the data if it isn't done on the server.
|
||||
/// </summary>
|
||||
/// <param name="Scale">
|
||||
/// The total scale to apply.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// Note that in most cases <see cref="Scale"/> should be incremented or decremented by subscribers, not set.
|
||||
/// Otherwise, any offsets applied by previous subscribing systems will be overridden.
|
||||
/// </remarks>
|
||||
[ByRefEvent]
|
||||
public record struct GetEyePvsScaleEvent(float Scale);
|
||||
|
||||
/// <summary>
|
||||
/// Raised on any equipped and in-hand items that may modify the eye offset.
|
||||
/// Pockets and suitstorage are excluded.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public sealed class GetEyePvsScaleRelayedEvent : EntityEventArgs, IInventoryRelayEvent
|
||||
{
|
||||
public SlotFlags TargetSlots { get; } = ~(SlotFlags.POCKET & SlotFlags.SUITSTORAGE);
|
||||
|
||||
public float Scale;
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -28,7 +30,7 @@ public abstract class SharedCameraRecoilSystem : EntitySystem
|
||||
/// </summary>
|
||||
protected const float KickMagnitudeMax = 1f;
|
||||
|
||||
[Dependency] private readonly SharedEyeSystem _eye = default!;
|
||||
[Dependency] private readonly SharedContentEyeSystem _eye = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -81,9 +83,7 @@ public abstract class SharedCameraRecoilSystem : EntitySystem
|
||||
continue;
|
||||
|
||||
recoil.LastKick = recoil.CurrentKick;
|
||||
var ev = new GetEyeOffsetEvent();
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
_eye.SetOffset(uid, ev.Offset, eye);
|
||||
_eye.UpdateEyeOffset((uid, eye));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
67
Content.Shared/Cargo/CargoBountyHistoryData.cs
Normal file
67
Content.Shared/Cargo/CargoBountyHistoryData.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Cargo;
|
||||
|
||||
/// <summary>
|
||||
/// A data structure for storing historical information about bounties.
|
||||
/// </summary>
|
||||
[DataDefinition, NetSerializable, Serializable]
|
||||
public readonly partial record struct CargoBountyHistoryData
|
||||
{
|
||||
/// <summary>
|
||||
/// A unique id used to identify the bounty
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this bounty was completed or skipped.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public BountyResult Result { get; init; } = BountyResult.Completed;
|
||||
|
||||
/// <summary>
|
||||
/// Optional name of the actor that completed/skipped the bounty.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? ActorName { get; init; } = default;
|
||||
|
||||
/// <summary>
|
||||
/// Time when this bounty was completed or skipped
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan Timestamp { get; init; } = TimeSpan.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// The prototype containing information about the bounty.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ProtoId<CargoBountyPrototype> Bounty { get; init; } = string.Empty;
|
||||
|
||||
public CargoBountyHistoryData(CargoBountyData bounty, BountyResult result, TimeSpan timestamp, string? actorName)
|
||||
{
|
||||
Bounty = bounty.Bounty;
|
||||
Result = result;
|
||||
Id = bounty.Id;
|
||||
ActorName = actorName;
|
||||
Timestamp = timestamp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Covers how a bounty was actually finished.
|
||||
/// </summary>
|
||||
public enum BountyResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Bounty was actually fulfilled and the goods sold
|
||||
/// </summary>
|
||||
Completed = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Bounty was explicitly skipped by some actor
|
||||
/// </summary>
|
||||
Skipped = 1,
|
||||
}
|
||||
}
|
||||
@@ -50,11 +50,13 @@ public sealed partial class CargoBountyConsoleComponent : Component
|
||||
public sealed class CargoBountyConsoleState : BoundUserInterfaceState
|
||||
{
|
||||
public List<CargoBountyData> Bounties;
|
||||
public List<CargoBountyHistoryData> History;
|
||||
public TimeSpan UntilNextSkip;
|
||||
|
||||
public CargoBountyConsoleState(List<CargoBountyData> bounties, TimeSpan untilNextSkip)
|
||||
public CargoBountyConsoleState(List<CargoBountyData> bounties, List<CargoBountyHistoryData> history, TimeSpan untilNextSkip)
|
||||
{
|
||||
Bounties = bounties;
|
||||
History = history;
|
||||
UntilNextSkip = untilNextSkip;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ public sealed class SolutionSpikerSystem : EntitySystem
|
||||
|
||||
private void OnInteractUsing(Entity<RefillableSolutionComponent> entity, ref InteractUsingEvent args)
|
||||
{
|
||||
TrySpike(args.Used, args.Target, args.User, entity.Comp);
|
||||
if (TrySpike(args.Used, args.Target, args.User, entity.Comp))
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -36,7 +37,7 @@ public sealed class SolutionSpikerSystem : EntitySystem
|
||||
/// <param name="source">Source of the solution.</param>
|
||||
/// <param name="target">Target to spike with the solution from source.</param>
|
||||
/// <param name="user">User spiking the target solution.</param>
|
||||
private void TrySpike(EntityUid source, EntityUid target, EntityUid user, RefillableSolutionComponent? spikableTarget = null,
|
||||
private bool TrySpike(EntityUid source, EntityUid target, EntityUid user, RefillableSolutionComponent? spikableTarget = null,
|
||||
SolutionSpikerComponent? spikableSource = null,
|
||||
SolutionContainerManagerComponent? managerSource = null,
|
||||
SolutionContainerManagerComponent? managerTarget = null)
|
||||
@@ -46,21 +47,23 @@ public sealed class SolutionSpikerSystem : EntitySystem
|
||||
|| !_solution.TryGetRefillableSolution((target, spikableTarget, managerTarget), out var targetSoln, out var targetSolution)
|
||||
|| !_solution.TryGetSolution((source, managerSource), spikableSource.SourceSolution, out _, out var sourceSolution))
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetSolution.Volume == 0 && !spikableSource.IgnoreEmpty)
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString(spikableSource.PopupEmpty, ("spiked-entity", target), ("spike-entity", source)), user, user);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_solution.ForceAddSolution(targetSoln.Value, sourceSolution))
|
||||
return;
|
||||
return false;
|
||||
|
||||
_popup.PopupClient(Loc.GetString(spikableSource.Popup, ("spiked-entity", target), ("spike-entity", source)), user, user);
|
||||
sourceSolution.RemoveAllSolution();
|
||||
if (spikableSource.Delete)
|
||||
QueueDel(source);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,15 +46,6 @@ public sealed partial class CloningPodComponent : Component
|
||||
[DataField("mobSpawnId"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntProtoId MobSpawnId = "MobAbomination";
|
||||
|
||||
/// <summary>
|
||||
/// Emag sound effects.
|
||||
/// </summary>
|
||||
[DataField("sparkSound")]
|
||||
public SoundSpecifier SparkSound = new SoundCollectionSpecifier("sparks")
|
||||
{
|
||||
Params = AudioParams.Default.WithVolume(8),
|
||||
};
|
||||
|
||||
// TODO: Remove this from here when cloning and/or zombies are refactored
|
||||
[DataField("screamSound")]
|
||||
public SoundSpecifier ScreamSound = new SoundCollectionSpecifier("ZombieScreams")
|
||||
|
||||
@@ -20,8 +20,11 @@ public sealed partial class MaskComponent : Component
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool IsToggled;
|
||||
|
||||
/// <summary>
|
||||
/// Equipped prefix to use after the mask was pulled down.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public string EquippedPrefix = "toggled";
|
||||
public string EquippedPrefix = "up";
|
||||
|
||||
/// <summary>
|
||||
/// When <see langword="true"/> will function normally, otherwise will not react to events
|
||||
|
||||
@@ -5,8 +5,9 @@ using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Clothing.EntitySystems;
|
||||
|
||||
@@ -14,19 +15,20 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
[Dependency] private readonly ClothingSystem _clothingSystem = default!;
|
||||
[Dependency] private readonly ContrabandSystem _contraband = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly SharedItemSystem _itemSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
[Dependency] protected readonly SharedUserInterfaceSystem UI = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ChameleonClothingComponent, GotEquippedEvent>(OnGotEquipped);
|
||||
SubscribeLocalEvent<ChameleonClothingComponent, GotUnequippedEvent>(OnGotUnequipped);
|
||||
SubscribeLocalEvent<ChameleonClothingComponent, GetVerbsEvent<InteractionVerb>>(OnVerb);
|
||||
}
|
||||
|
||||
private void OnGotEquipped(EntityUid uid, ChameleonClothingComponent component, GotEquippedEvent args)
|
||||
@@ -94,6 +96,22 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnVerb(Entity<ChameleonClothingComponent> ent, ref GetVerbsEvent<InteractionVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || ent.Comp.User != args.User)
|
||||
return;
|
||||
|
||||
// Can't pass args from a ref event inside of lambdas
|
||||
var user = args.User;
|
||||
|
||||
args.Verbs.Add(new InteractionVerb()
|
||||
{
|
||||
Text = Loc.GetString("chameleon-component-verb-text"),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
|
||||
Act = () => UI.TryToggleUi(ent.Owner, ChameleonUiKey.Key, user)
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual void UpdateSprite(EntityUid uid, EntityPrototype proto) { }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Content.Shared.Construction
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
||||
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Get predicate for construction obstruction checks.
|
||||
|
||||
@@ -40,7 +40,7 @@ public sealed class ContainerFillSystem : EntitySystem
|
||||
if (!_containerSystem.Insert(ent, container, containerXform: xform))
|
||||
{
|
||||
Log.Error($"Entity {ToPrettyString(uid)} with a {nameof(ContainerFillComponent)} failed to insert an entity: {ToPrettyString(ent)}.");
|
||||
Transform(ent).AttachToGridOrMap();
|
||||
_transform.AttachToGridOrMap(ent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,4 +17,16 @@ public sealed partial class DragInsertContainerComponent : Component
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool UseVerbs = true;
|
||||
|
||||
/// <summary>
|
||||
/// The delay in seconds before a drag will be completed.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan EntryDelay = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// If entry delay isn't zero, this sets whether an entity dragging itself into the container should be delayed.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool DelaySelfEntry = false;
|
||||
}
|
||||
|
||||
@@ -2,24 +2,28 @@ using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Climbing.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Containers;
|
||||
|
||||
public sealed class DragInsertContainerSystem : EntitySystem
|
||||
public sealed partial class DragInsertContainerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] private readonly ClimbSystem _climb = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DragInsertContainerComponent, DragDropTargetEvent>(OnDragDropOn, before: new []{ typeof(ClimbSystem)});
|
||||
SubscribeLocalEvent<DragInsertContainerComponent, DragInsertContainerDoAfterEvent>(OnDragFinished);
|
||||
SubscribeLocalEvent<DragInsertContainerComponent, CanDropTargetEvent>(OnCanDragDropOn);
|
||||
SubscribeLocalEvent<DragInsertContainerComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAlternativeVerb);
|
||||
}
|
||||
@@ -33,7 +37,34 @@ public sealed class DragInsertContainerSystem : EntitySystem
|
||||
if (!_container.TryGetContainer(ent, comp.ContainerId, out var container))
|
||||
return;
|
||||
|
||||
args.Handled = Insert(args.Dragged, args.User, ent, container);
|
||||
if (comp.EntryDelay <= TimeSpan.Zero ||
|
||||
!comp.DelaySelfEntry && args.User == args.Dragged)
|
||||
{
|
||||
//instant insertion
|
||||
args.Handled = Insert(args.Dragged, args.User, ent, container);
|
||||
return;
|
||||
}
|
||||
|
||||
//delayed insertion
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, comp.EntryDelay, new DragInsertContainerDoAfterEvent(), ent, args.Dragged, ent)
|
||||
{
|
||||
BreakOnDamage = true,
|
||||
BreakOnMove = true,
|
||||
NeedHand = false,
|
||||
};
|
||||
_doAfter.TryStartDoAfter(doAfterArgs);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnDragFinished(Entity<DragInsertContainerComponent> ent, ref DragInsertContainerDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled || args.Args.Target == null)
|
||||
return;
|
||||
|
||||
if (!_container.TryGetContainer(ent, ent.Comp.ContainerId, out var container))
|
||||
return;
|
||||
|
||||
Insert(args.Args.Target.Value, args.User, ent, container);
|
||||
}
|
||||
|
||||
private void OnCanDragDropOn(Entity<DragInsertContainerComponent> ent, ref CanDropTargetEvent args)
|
||||
@@ -117,4 +148,9 @@ public sealed class DragInsertContainerSystem : EntitySystem
|
||||
_adminLog.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(user):player} inserted {ToPrettyString(target):player} into container {ToPrettyString(containerEntity)}");
|
||||
return true;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class DragInsertContainerDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,5 +24,13 @@ public sealed partial class ContrabandComponent : Component
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
public HashSet<ProtoId<DepartmentPrototype>>? AllowedDepartments = ["Security"];
|
||||
public HashSet<ProtoId<DepartmentPrototype>> AllowedDepartments = new();
|
||||
|
||||
/// <summary>
|
||||
/// Which jobs is this item restricted to?
|
||||
/// If empty, no jobs are allowed to use this beyond the allowed departments.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
public HashSet<ProtoId<JobPrototype>> AllowedJobs = new();
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ public sealed partial class ContrabandSeverityPrototype : IPrototype
|
||||
public LocId ExamineText;
|
||||
|
||||
/// <summary>
|
||||
/// When examining the contraband, should this take into account the viewer's departments?
|
||||
/// When examining the contraband, should this take into account the viewer's departments and job?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool ShowDepartments;
|
||||
public bool ShowDepartmentsAndJobs;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ using Content.Shared.CCVar;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Localizations;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Contraband;
|
||||
|
||||
@@ -17,22 +19,18 @@ public sealed class ContrabandSystem : EntitySystem
|
||||
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly SharedIdCardSystem _id = default!;
|
||||
[Dependency] private readonly ExamineSystemShared _examine = default!;
|
||||
|
||||
private bool _contrabandExamineEnabled;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ContrabandComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<ContrabandComponent, GetVerbsEvent<ExamineVerb>>(OnDetailedExamine);
|
||||
|
||||
Subs.CVar(_configuration, CCVars.ContrabandExamine, SetContrabandExamine, true);
|
||||
}
|
||||
|
||||
private void SetContrabandExamine(bool val)
|
||||
{
|
||||
_contrabandExamineEnabled = val;
|
||||
}
|
||||
|
||||
public void CopyDetails(EntityUid uid, ContrabandComponent other, ContrabandComponent? contraband = null)
|
||||
{
|
||||
if (!Resolve(uid, ref contraband))
|
||||
@@ -40,52 +38,84 @@ public sealed class ContrabandSystem : EntitySystem
|
||||
|
||||
contraband.Severity = other.Severity;
|
||||
contraband.AllowedDepartments = other.AllowedDepartments;
|
||||
contraband.AllowedJobs = other.AllowedJobs;
|
||||
Dirty(uid, contraband);
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<ContrabandComponent> ent, ref ExaminedEvent args)
|
||||
private void OnDetailedExamine(EntityUid ent,ContrabandComponent component, ref GetVerbsEvent<ExamineVerb> args)
|
||||
{
|
||||
|
||||
if (!_contrabandExamineEnabled)
|
||||
return;
|
||||
|
||||
// CanAccess is not used here, because we want people to be able to examine legality in strip menu.
|
||||
if (!args.CanInteract)
|
||||
return;
|
||||
|
||||
// two strings:
|
||||
// one, the actual informative 'this is restricted'
|
||||
// then, the 'you can/shouldn't carry this around' based on the ID the user is wearing
|
||||
|
||||
using (args.PushGroup(nameof(ContrabandComponent)))
|
||||
var localizedDepartments = component.AllowedDepartments.Select(p => Loc.GetString("contraband-department-plural", ("department", Loc.GetString(_proto.Index(p).Name))));
|
||||
var localizedJobs = component.AllowedJobs.Select(p => Loc.GetString("contraband-job-plural", ("job", _proto.Index(p).LocalizedName)));
|
||||
var severity = _proto.Index(component.Severity);
|
||||
String departmentExamineMessage;
|
||||
if (severity.ShowDepartmentsAndJobs)
|
||||
{
|
||||
var severity = _proto.Index(ent.Comp.Severity);
|
||||
if (severity.ShowDepartments && ent.Comp is { AllowedDepartments: not null })
|
||||
{
|
||||
// TODO shouldn't department prototypes have a localized name instead of just using the ID for this?
|
||||
var list = ContentLocalizationManager.FormatList(ent.Comp.AllowedDepartments.Select(p => Loc.GetString($"department-{p.Id}")).ToList());
|
||||
|
||||
// department restricted text
|
||||
args.PushMarkup(Loc.GetString("contraband-examine-text-Restricted-department", ("departments", list)));
|
||||
}
|
||||
else
|
||||
{
|
||||
args.PushMarkup(Loc.GetString(severity.ExamineText));
|
||||
}
|
||||
|
||||
// text based on ID card
|
||||
List<ProtoId<DepartmentPrototype>>? departments = null;
|
||||
if (_id.TryFindIdCard(args.Examiner, out var id))
|
||||
{
|
||||
departments = id.Comp.JobDepartments;
|
||||
}
|
||||
|
||||
// either its fully restricted, you have no departments, or your departments dont intersect with the restricted departments
|
||||
if (ent.Comp.AllowedDepartments is null
|
||||
|| departments is null
|
||||
|| !departments.Intersect(ent.Comp.AllowedDepartments).Any())
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("contraband-examine-text-avoid-carrying-around"));
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise fine to use :tm:
|
||||
args.PushMarkup(Loc.GetString("contraband-examine-text-in-the-clear"));
|
||||
//creating a combined list of jobs and departments for the restricted text
|
||||
var list = ContentLocalizationManager.FormatList(localizedDepartments.Concat(localizedJobs).ToList());
|
||||
// department restricted text
|
||||
departmentExamineMessage = Loc.GetString("contraband-examine-text-Restricted-department", ("departments", list));
|
||||
}
|
||||
else
|
||||
{
|
||||
departmentExamineMessage = Loc.GetString(severity.ExamineText);
|
||||
}
|
||||
|
||||
// text based on ID card
|
||||
List<ProtoId<DepartmentPrototype>> departments = new();
|
||||
var jobId = "";
|
||||
if (_id.TryFindIdCard(args.User, out var id))
|
||||
{
|
||||
departments = id.Comp.JobDepartments;
|
||||
if (id.Comp.LocalizedJobTitle is not null)
|
||||
{
|
||||
jobId = id.Comp.LocalizedJobTitle;
|
||||
}
|
||||
}
|
||||
|
||||
String carryingMessage;
|
||||
// either its fully restricted, you have no departments, or your departments dont intersect with the restricted departments
|
||||
if (departments.Intersect(component.AllowedDepartments).Any()
|
||||
|| localizedJobs.Contains(jobId))
|
||||
{
|
||||
carryingMessage = Loc.GetString("contraband-examine-text-in-the-clear");
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise fine to use :tm:
|
||||
carryingMessage = Loc.GetString("contraband-examine-text-avoid-carrying-around");
|
||||
}
|
||||
|
||||
var examineMarkup = GetContrabandExamine(departmentExamineMessage, carryingMessage);
|
||||
_examine.AddDetailedExamineVerb(args,
|
||||
component,
|
||||
examineMarkup,
|
||||
Loc.GetString("contraband-examinable-verb-text"),
|
||||
"/Textures/Interface/VerbIcons/lock.svg.192dpi.png",
|
||||
Loc.GetString("contraband-examinable-verb-message"));
|
||||
}
|
||||
|
||||
private FormattedMessage GetContrabandExamine(String deptMessage, String carryMessage)
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddMarkupOrThrow(deptMessage);
|
||||
msg.PushNewline();
|
||||
msg.AddMarkupOrThrow(carryMessage);
|
||||
return msg;
|
||||
}
|
||||
|
||||
private void SetContrabandExamine(bool val)
|
||||
{
|
||||
_contrabandExamineEnabled = val;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using Content.Shared.CriminalRecords.Systems;
|
||||
using Content.Shared.CriminalRecords.Components;
|
||||
using Content.Shared.CriminalRecords;
|
||||
using Content.Shared.Radio;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Security;
|
||||
|
||||
namespace Content.Shared.CriminalRecords.Components;
|
||||
|
||||
@@ -31,6 +34,12 @@ public sealed partial class CriminalRecordsConsoleComponent : Component
|
||||
[DataField]
|
||||
public StationRecordsFilter? Filter;
|
||||
|
||||
/// <summary>
|
||||
/// Current seleced security status for the filter by criminal status dropdown.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SecurityStatus FilterStatus;
|
||||
|
||||
/// <summary>
|
||||
/// Channel to send messages to when someone's status gets changed.
|
||||
/// </summary>
|
||||
|
||||
@@ -35,9 +35,9 @@ public sealed class CriminalRecordsConsoleState : BoundUserInterfaceState
|
||||
/// Currently selected crewmember record key.
|
||||
/// </summary>
|
||||
public uint? SelectedKey = null;
|
||||
|
||||
public CriminalRecord? CriminalRecord = null;
|
||||
public GeneralStationRecord? StationRecord = null;
|
||||
public SecurityStatus FilterStatus = SecurityStatus.None;
|
||||
public readonly Dictionary<uint, string>? RecordListing;
|
||||
public readonly StationRecordsFilter? Filter;
|
||||
|
||||
@@ -100,3 +100,20 @@ public sealed class CriminalRecordDeleteHistory : BoundUserInterfaceMessage
|
||||
Index = index;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to set what status to filter by index.
|
||||
///
|
||||
/// </summary>
|
||||
///
|
||||
[Serializable, NetSerializable]
|
||||
|
||||
public sealed class CriminalRecordSetStatusFilter : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly SecurityStatus FilterStatus;
|
||||
public CriminalRecordSetStatusFilter(SecurityStatus newFilterStatus)
|
||||
{
|
||||
FilterStatus = newFilterStatus;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,15 +84,18 @@ namespace Content.Shared.Damage
|
||||
public sealed class DamageableComponentState : ComponentState
|
||||
{
|
||||
public readonly Dictionary<string, FixedPoint2> DamageDict;
|
||||
public readonly string? DamageContainerId;
|
||||
public readonly string? ModifierSetId;
|
||||
public readonly FixedPoint2? HealthBarThreshold;
|
||||
|
||||
public DamageableComponentState(
|
||||
Dictionary<string, FixedPoint2> damageDict,
|
||||
string? damageContainerId,
|
||||
string? modifierSetId,
|
||||
FixedPoint2? healthBarThreshold)
|
||||
{
|
||||
DamageDict = damageDict;
|
||||
DamageContainerId = damageContainerId;
|
||||
ModifierSetId = modifierSetId;
|
||||
HealthBarThreshold = healthBarThreshold;
|
||||
}
|
||||
|
||||
14
Content.Shared/Damage/Events/BeforeForceSayEvent.cs
Normal file
14
Content.Shared/Damage/Events/BeforeForceSayEvent.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Content.Shared.Dataset;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Damage.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event for interrupting and changing the prefix for when an entity is being forced to say something
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BeforeForceSayEvent(ProtoId<LocalizedDatasetPrototype> prefixDataset) : EntityEventArgs
|
||||
{
|
||||
public ProtoId<LocalizedDatasetPrototype> Prefix = prefixDataset;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
|
||||
namespace Content.Shared.Damage.ForceSay;
|
||||
|
||||
@@ -30,14 +30,7 @@ public sealed partial class DamageForceSayComponent : Component
|
||||
/// The fluent string prefix to use when picking a random suffix
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string ForceSayStringPrefix = "damage-force-say-";
|
||||
|
||||
/// <summary>
|
||||
/// The number of suffixes that exist for use with <see cref="ForceSayStringPrefix"/>.
|
||||
/// i.e. (prefix)-1 through (prefix)-(count)
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int ForceSayStringCount = 7;
|
||||
public ProtoId<LocalizedDatasetPrototype> ForceSayStringDataset = "ForceSayStringDataset";
|
||||
|
||||
/// <summary>
|
||||
/// The amount of total damage between <see cref="ValidDamageGroups"/> that needs to be taken before
|
||||
|
||||
@@ -228,12 +228,12 @@ namespace Content.Shared.Damage
|
||||
{
|
||||
if (_netMan.IsServer)
|
||||
{
|
||||
args.State = new DamageableComponentState(component.Damage.DamageDict, component.DamageModifierSetId, component.HealthBarThreshold);
|
||||
args.State = new DamageableComponentState(component.Damage.DamageDict, component.DamageContainerID, component.DamageModifierSetId, component.HealthBarThreshold);
|
||||
}
|
||||
else
|
||||
{
|
||||
// avoid mispredicting damage on newly spawned entities.
|
||||
args.State = new DamageableComponentState(component.Damage.DamageDict.ShallowClone(), component.DamageModifierSetId, component.HealthBarThreshold);
|
||||
args.State = new DamageableComponentState(component.Damage.DamageDict.ShallowClone(), component.DamageContainerID, component.DamageModifierSetId, component.HealthBarThreshold);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,6 +266,7 @@ namespace Content.Shared.Damage
|
||||
return;
|
||||
}
|
||||
|
||||
component.DamageContainerID = state.DamageContainerId;
|
||||
component.DamageModifierSetId = state.ModifierSetId;
|
||||
component.HealthBarThreshold = state.HealthBarThreshold;
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ namespace Content.Shared.Decals
|
||||
node.TryGetValue(new ValueDataNode("version"), out var versionNode);
|
||||
var version = ((ValueDataNode?) versionNode)?.AsInt() ?? 1;
|
||||
Dictionary<Vector2i, DecalChunk> dictionary;
|
||||
uint nextIndex = 0;
|
||||
var ids = new HashSet<uint>();
|
||||
|
||||
// TODO: Dump this when we don't need support anymore.
|
||||
if (version > 1)
|
||||
@@ -53,22 +55,31 @@ namespace Content.Shared.Decals
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(coords, SharedDecalSystem.ChunkSize);
|
||||
var chunk = dictionary.GetOrNew(chunkOrigin);
|
||||
var decal = new Decal(coords, data.Id, data.Color, data.Angle, data.ZIndex, data.Cleanable);
|
||||
chunk.Decals.Add(dUid, decal);
|
||||
|
||||
nextIndex = Math.Max(nextIndex, dUid);
|
||||
|
||||
// Re-used ID somehow
|
||||
// This will bump all IDs by up to 1 but will ensure the map is still readable.
|
||||
if (!ids.Add(dUid))
|
||||
{
|
||||
dUid = nextIndex++;
|
||||
ids.Add(dUid);
|
||||
}
|
||||
|
||||
chunk.Decals[dUid] = decal;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dictionary = serializationManager.Read<Dictionary<Vector2i, DecalChunk>>(node, hookCtx, context, notNullableOverride: true);
|
||||
}
|
||||
|
||||
uint nextIndex = 0;
|
||||
|
||||
foreach (var decals in dictionary.Values)
|
||||
{
|
||||
foreach (var uid in decals.Decals.Keys)
|
||||
foreach (var decals in dictionary.Values)
|
||||
{
|
||||
nextIndex = Math.Max(uid, nextIndex);
|
||||
foreach (var uid in decals.Decals.Keys)
|
||||
{
|
||||
nextIndex = Math.Max(uid, nextIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Dice;
|
||||
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Dice;
|
||||
|
||||
public abstract class SharedDiceSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -14,76 +20,67 @@ public abstract class SharedDiceSystem : EntitySystem
|
||||
SubscribeLocalEvent<DiceComponent, UseInHandEvent>(OnUseInHand);
|
||||
SubscribeLocalEvent<DiceComponent, LandEvent>(OnLand);
|
||||
SubscribeLocalEvent<DiceComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<DiceComponent, AfterAutoHandleStateEvent>(OnDiceAfterHandleState);
|
||||
}
|
||||
|
||||
private void OnDiceAfterHandleState(EntityUid uid, DiceComponent component, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
UpdateVisuals(uid, component);
|
||||
}
|
||||
|
||||
private void OnUseInHand(EntityUid uid, DiceComponent component, UseInHandEvent args)
|
||||
private void OnUseInHand(Entity<DiceComponent> entity, ref UseInHandEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
Roll(entity, args.User);
|
||||
args.Handled = true;
|
||||
Roll(uid, component);
|
||||
}
|
||||
|
||||
private void OnLand(EntityUid uid, DiceComponent component, ref LandEvent args)
|
||||
private void OnLand(Entity<DiceComponent> entity, ref LandEvent args)
|
||||
{
|
||||
Roll(uid, component);
|
||||
Roll(entity);
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, DiceComponent dice, ExaminedEvent args)
|
||||
private void OnExamined(Entity<DiceComponent> entity, ref ExaminedEvent args)
|
||||
{
|
||||
//No details check, since the sprite updates to show the side.
|
||||
using (args.PushGroup(nameof(DiceComponent)))
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("dice-component-on-examine-message-part-1", ("sidesAmount", dice.Sides)));
|
||||
args.PushMarkup(Loc.GetString("dice-component-on-examine-message-part-1", ("sidesAmount", entity.Comp.Sides)));
|
||||
args.PushMarkup(Loc.GetString("dice-component-on-examine-message-part-2",
|
||||
("currentSide", dice.CurrentValue)));
|
||||
("currentSide", entity.Comp.CurrentValue)));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCurrentSide(EntityUid uid, int side, DiceComponent? die = null)
|
||||
private void SetCurrentSide(Entity<DiceComponent> entity, int side)
|
||||
{
|
||||
if (!Resolve(uid, ref die))
|
||||
return;
|
||||
|
||||
if (side < 1 || side > die.Sides)
|
||||
if (side < 1 || side > entity.Comp.Sides)
|
||||
{
|
||||
Log.Error($"Attempted to set die {ToPrettyString(uid)} to an invalid side ({side}).");
|
||||
Log.Error($"Attempted to set die {ToPrettyString(entity)} to an invalid side ({side}).");
|
||||
return;
|
||||
}
|
||||
|
||||
die.CurrentValue = (side - die.Offset) * die.Multiplier;
|
||||
Dirty(uid, die);
|
||||
UpdateVisuals(uid, die);
|
||||
entity.Comp.CurrentValue = (side - entity.Comp.Offset) * entity.Comp.Multiplier;
|
||||
Dirty(entity);
|
||||
}
|
||||
|
||||
public void SetCurrentValue(EntityUid uid, int value, DiceComponent? die = null)
|
||||
public void SetCurrentValue(Entity<DiceComponent> entity, int value)
|
||||
{
|
||||
if (!Resolve(uid, ref die))
|
||||
return;
|
||||
|
||||
if (value % die.Multiplier != 0 || value/ die.Multiplier + die.Offset < 1)
|
||||
if (value % entity.Comp.Multiplier != 0 || value / entity.Comp.Multiplier + entity.Comp.Offset < 1)
|
||||
{
|
||||
Log.Error($"Attempted to set die {ToPrettyString(uid)} to an invalid value ({value}).");
|
||||
Log.Error($"Attempted to set die {ToPrettyString(entity)} to an invalid value ({value}).");
|
||||
return;
|
||||
}
|
||||
|
||||
SetCurrentSide(uid, value / die.Multiplier + die.Offset, die);
|
||||
SetCurrentSide(entity, value / entity.Comp.Multiplier + entity.Comp.Offset);
|
||||
}
|
||||
|
||||
protected virtual void UpdateVisuals(EntityUid uid, DiceComponent? die = null)
|
||||
private void Roll(Entity<DiceComponent> entity, EntityUid? user = null)
|
||||
{
|
||||
// See client system.
|
||||
}
|
||||
var rand = new System.Random((int)_timing.CurTick.Value);
|
||||
|
||||
public virtual void Roll(EntityUid uid, DiceComponent? die = null)
|
||||
{
|
||||
// See the server system, client cannot predict rolling.
|
||||
var roll = rand.Next(1, entity.Comp.Sides + 1);
|
||||
SetCurrentSide(entity, roll);
|
||||
|
||||
var popupString = Loc.GetString("dice-component-on-roll-land",
|
||||
("die", entity),
|
||||
("currentSide", entity.Comp.CurrentValue));
|
||||
_popup.PopupPredicted(popupString, entity, user);
|
||||
_audio.PlayPredicted(entity.Comp.Sound, entity, user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ public sealed partial class DisposalDoAfterEvent : SimpleDoAfterEvent
|
||||
public abstract class SharedDisposalUnitSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IGameTiming GameTiming = default!;
|
||||
[Dependency] protected readonly EmagSystem _emag = default!;
|
||||
[Dependency] protected readonly MetaDataSystem Metadata = default!;
|
||||
[Dependency] protected readonly SharedJointSystem Joints = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
@@ -102,6 +103,12 @@ public abstract class SharedDisposalUnitSystem : EntitySystem
|
||||
|
||||
protected void OnEmagged(EntityUid uid, SharedDisposalUnitComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (component.DisablePressure == true)
|
||||
return;
|
||||
|
||||
component.DisablePressure = true;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -314,7 +314,6 @@ public enum DoorState : byte
|
||||
public enum DoorVisuals : byte
|
||||
{
|
||||
State,
|
||||
Powered,
|
||||
BoltLights,
|
||||
EmergencyLights,
|
||||
ClosedLights,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Doors.Components
|
||||
@@ -23,12 +24,14 @@ namespace Content.Shared.Doors.Components
|
||||
/// Maximum pressure difference before the firelock will refuse to open, in kPa.
|
||||
/// </summary>
|
||||
[DataField("pressureThreshold"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float PressureThreshold = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum temperature difference before the firelock will refuse to open, in k.
|
||||
/// </summary>
|
||||
[DataField("temperatureThreshold"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float TemperatureThreshold = 330;
|
||||
// this used to check for hot-spots, but because accessing that data is a a mess this now just checks
|
||||
// temperature. This does mean a cold room will trigger hot-air pop-ups
|
||||
|
||||
@@ -23,7 +23,6 @@ using Robust.Shared.Timing;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Content.Shared.Doors.Systems;
|
||||
|
||||
@@ -34,6 +33,7 @@ public abstract partial class SharedDoorSystem : EntitySystem
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] protected readonly SharedPhysicsSystem PhysicsSystem = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
|
||||
[Dependency] protected readonly TagSystem Tags = default!;
|
||||
[Dependency] protected readonly SharedAudioSystem Audio = default!;
|
||||
@@ -77,8 +77,6 @@ public abstract partial class SharedDoorSystem : EntitySystem
|
||||
SubscribeLocalEvent<DoorComponent, WeldableAttemptEvent>(OnWeldAttempt);
|
||||
SubscribeLocalEvent<DoorComponent, WeldableChangedEvent>(OnWeldChanged);
|
||||
SubscribeLocalEvent<DoorComponent, GetPryTimeModifierEvent>(OnPryTimeModifier);
|
||||
|
||||
SubscribeLocalEvent<DoorComponent, OnAttemptEmagEvent>(OnAttemptEmag);
|
||||
SubscribeLocalEvent<DoorComponent, GotEmaggedEvent>(OnEmagged);
|
||||
}
|
||||
|
||||
@@ -118,31 +116,24 @@ public abstract partial class SharedDoorSystem : EntitySystem
|
||||
_activeDoors.Remove(door);
|
||||
}
|
||||
|
||||
private void OnAttemptEmag(EntityUid uid, DoorComponent door, ref OnAttemptEmagEvent args)
|
||||
{
|
||||
if (!TryComp<AirlockComponent>(uid, out var airlock))
|
||||
{
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsBolted(uid) || !airlock.Powered)
|
||||
{
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (door.State != DoorState.Closed)
|
||||
{
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEmagged(EntityUid uid, DoorComponent door, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Access))
|
||||
return;
|
||||
|
||||
if (!TryComp<AirlockComponent>(uid, out var airlock))
|
||||
return;
|
||||
|
||||
if (IsBolted(uid) || !airlock.Powered)
|
||||
return;
|
||||
|
||||
if (door.State != DoorState.Closed)
|
||||
return;
|
||||
|
||||
if (!SetState(uid, DoorState.Emagging, door))
|
||||
return;
|
||||
Audio.PlayPredicted(door.SparkSound, uid, args.UserUid, AudioParams.Default.WithVolume(8));
|
||||
|
||||
args.Repeatable = true;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,32 +9,42 @@ namespace Content.Shared.DrawDepth
|
||||
/// <summary>
|
||||
/// This is for sub-floors, the floors you see after prying off a tile.
|
||||
/// </summary>
|
||||
LowFloors = DrawDepthTag.Default - 11,
|
||||
LowFloors = DrawDepthTag.Default - 14,
|
||||
|
||||
// various entity types that require different
|
||||
// draw depths, as to avoid hiding
|
||||
#region SubfloorEntities
|
||||
ThickPipe = DrawDepthTag.Default - 10,
|
||||
ThickWire = DrawDepthTag.Default - 9,
|
||||
ThinPipe = DrawDepthTag.Default - 8,
|
||||
ThinWire = DrawDepthTag.Default - 7,
|
||||
ThickPipe = DrawDepthTag.Default - 13,
|
||||
ThickWire = DrawDepthTag.Default - 12,
|
||||
ThinPipe = DrawDepthTag.Default - 11,
|
||||
ThinWire = DrawDepthTag.Default - 10,
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Things that are beneath regular floors.
|
||||
/// </summary>
|
||||
BelowFloor = DrawDepthTag.Default - 7,
|
||||
BelowFloor = DrawDepthTag.Default - 9,
|
||||
|
||||
/// <summary>
|
||||
/// Used for entities like carpets.
|
||||
/// </summary>
|
||||
FloorTiles = DrawDepthTag.Default - 6,
|
||||
FloorTiles = DrawDepthTag.Default - 8,
|
||||
|
||||
/// <summary>
|
||||
/// Things that are actually right on the floor, like puddles. This does not mean objects like
|
||||
/// Things that are actually right on the floor, like ice crust or atmos devices. This does not mean objects like
|
||||
/// tables, even though they are technically "on the floor".
|
||||
/// </summary>
|
||||
FloorObjects = DrawDepthTag.Default - 5,
|
||||
FloorObjects = DrawDepthTag.Default - 7,
|
||||
|
||||
/// <summary>
|
||||
// Discrete drawdepth to avoid z-fighting with other FloorObjects but also above floor entities.
|
||||
/// </summary>
|
||||
Puddles = DrawDepthTag.Default - 6,
|
||||
|
||||
/// <summary>
|
||||
// Objects that are on the floor, but should render above puddles. This includes kudzu, holopads, telepads and levers.
|
||||
/// </summary>
|
||||
HighFloorObjects = DrawDepthTag.Default - 5,
|
||||
|
||||
DeadMobs = DrawDepthTag.Default - 4,
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
@@ -14,7 +16,21 @@ public sealed partial class EmagComponent : Component
|
||||
/// <summary>
|
||||
/// The tag that marks an entity as immune to emags
|
||||
/// </summary>
|
||||
[DataField("emagImmuneTag", customTypeSerializer: typeof(PrototypeIdSerializer<TagPrototype>)), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
public string EmagImmuneTag = "EmagImmune";
|
||||
public ProtoId<TagPrototype> EmagImmuneTag = "EmagImmune";
|
||||
|
||||
/// <summary>
|
||||
/// What type of emag effect this device will do
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
public EmagType EmagType = EmagType.Interaction;
|
||||
|
||||
/// <summary>
|
||||
/// What sound should the emag play when used
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
public SoundSpecifier EmagSound = new SoundCollectionSpecifier("sparks");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Emag.Components;
|
||||
@@ -5,7 +6,12 @@ namespace Content.Shared.Emag.Components;
|
||||
/// <summary>
|
||||
/// Marker component for emagged entities
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class EmaggedComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The EmagType flags that were used to emag this device
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EmagType EmagType = EmagType.None;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@ using Content.Shared.Emag.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Emag.Systems;
|
||||
|
||||
@@ -23,88 +24,124 @@ public sealed class EmagSystem : EntitySystem
|
||||
[Dependency] private readonly SharedChargesSystem _charges = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<EmagComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<EmaggedComponent, OnAccessOverriderAccessUpdatedEvent>(OnAccessOverriderAccessUpdated);
|
||||
}
|
||||
|
||||
private void OnAccessOverriderAccessUpdated(Entity<EmaggedComponent> entity, ref OnAccessOverriderAccessUpdatedEvent args)
|
||||
{
|
||||
if (!CompareFlag(entity.Comp.EmagType, EmagType.Access))
|
||||
return;
|
||||
|
||||
entity.Comp.EmagType &= ~EmagType.Access;
|
||||
Dirty(entity);
|
||||
}
|
||||
private void OnAfterInteract(EntityUid uid, EmagComponent comp, AfterInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach || args.Target is not { } target)
|
||||
return;
|
||||
|
||||
args.Handled = TryUseEmag(uid, args.User, target, comp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to use the emag on a target entity
|
||||
/// </summary>
|
||||
public bool TryUseEmag(EntityUid uid, EntityUid user, EntityUid target, EmagComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp, false))
|
||||
return false;
|
||||
|
||||
if (_tag.HasTag(target, comp.EmagImmuneTag))
|
||||
return false;
|
||||
|
||||
TryComp<LimitedChargesComponent>(uid, out var charges);
|
||||
if (_charges.IsEmpty(uid, charges))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("emag-no-charges"), user, user);
|
||||
return false;
|
||||
}
|
||||
|
||||
var handled = DoEmagEffect(user, target);
|
||||
if (!handled)
|
||||
return false;
|
||||
|
||||
_popup.PopupClient(Loc.GetString("emag-success", ("target", Identity.Entity(target, EntityManager))), user,
|
||||
user, PopupType.Medium);
|
||||
|
||||
_adminLogger.Add(LogType.Emag, LogImpact.High, $"{ToPrettyString(user):player} emagged {ToPrettyString(target):target}");
|
||||
|
||||
if (charges != null)
|
||||
_charges.UseCharge(uid, charges);
|
||||
return true;
|
||||
args.Handled = TryEmagEffect((uid, comp), args.User, target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the emag effect on a specified entity
|
||||
/// </summary>
|
||||
public bool DoEmagEffect(EntityUid user, EntityUid target)
|
||||
public bool TryEmagEffect(Entity<EmagComponent?> ent, EntityUid user, EntityUid target)
|
||||
{
|
||||
// prevent emagging twice
|
||||
if (HasComp<EmaggedComponent>(target))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
var onAttemptEmagEvent = new OnAttemptEmagEvent(user);
|
||||
RaiseLocalEvent(target, ref onAttemptEmagEvent);
|
||||
|
||||
// prevent emagging if attempt fails
|
||||
if (onAttemptEmagEvent.Handled)
|
||||
if (_tag.HasTag(target, ent.Comp.EmagImmuneTag))
|
||||
return false;
|
||||
|
||||
var emaggedEvent = new GotEmaggedEvent(user);
|
||||
TryComp<LimitedChargesComponent>(ent, out var charges);
|
||||
if (_charges.IsEmpty(ent, charges))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("emag-no-charges"), user, user);
|
||||
return false;
|
||||
}
|
||||
|
||||
var emaggedEvent = new GotEmaggedEvent(user, ent.Comp.EmagType);
|
||||
RaiseLocalEvent(target, ref emaggedEvent);
|
||||
|
||||
if (emaggedEvent.Handled && !emaggedEvent.Repeatable)
|
||||
EnsureComp<EmaggedComponent>(target);
|
||||
if (!emaggedEvent.Handled)
|
||||
return false;
|
||||
|
||||
_popup.PopupPredicted(Loc.GetString("emag-success", ("target", Identity.Entity(target, EntityManager))), user, user, PopupType.Medium);
|
||||
|
||||
_audio.PlayPredicted(ent.Comp.EmagSound, ent, ent);
|
||||
|
||||
_adminLogger.Add(LogType.Emag, LogImpact.High, $"{ToPrettyString(user):player} emagged {ToPrettyString(target):target} with flag(s): {ent.Comp.EmagType}");
|
||||
|
||||
if (charges != null && emaggedEvent.Handled)
|
||||
_charges.UseCharge(ent, charges);
|
||||
|
||||
if (!emaggedEvent.Repeatable)
|
||||
{
|
||||
EnsureComp<EmaggedComponent>(target, out var emaggedComp);
|
||||
|
||||
emaggedComp.EmagType |= ent.Comp.EmagType;
|
||||
Dirty(target, emaggedComp);
|
||||
}
|
||||
|
||||
return emaggedEvent.Handled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether an entity has the EmaggedComponent with a set flag.
|
||||
/// </summary>
|
||||
/// <param name="target">The target entity to check for the flag.</param>
|
||||
/// <param name="flag">The EmagType flag to check for.</param>
|
||||
/// <returns>True if entity has EmaggedComponent and the provided flag. False if the entity lacks EmaggedComponent or provided flag.</returns>
|
||||
public bool CheckFlag(EntityUid target, EmagType flag)
|
||||
{
|
||||
if (!TryComp<EmaggedComponent>(target, out var comp))
|
||||
return false;
|
||||
|
||||
if ((comp.EmagType & flag) == flag)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares a flag to the target.
|
||||
/// </summary>
|
||||
/// <param name="target">The target flag to check.</param>
|
||||
/// <param name="flag">The flag to check for within the target.</param>
|
||||
/// <returns>True if target contains flag. Otherwise false.</returns>
|
||||
public bool CompareFlag(EmagType target, EmagType flag)
|
||||
{
|
||||
if ((target & flag) == flag)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Flags]
|
||||
[Serializable, NetSerializable]
|
||||
public enum EmagType : byte
|
||||
{
|
||||
None = 0,
|
||||
Interaction = 1 << 1,
|
||||
Access = 1 << 2
|
||||
}
|
||||
/// <summary>
|
||||
/// Shows a popup to emag user (client side only!) and adds <see cref="EmaggedComponent"/> to the entity when handled
|
||||
/// </summary>
|
||||
/// <param name="UserUid">Emag user</param>
|
||||
/// <param name="Type">The emag type to use</param>
|
||||
/// <param name="Handled">Did the emagging succeed? Causes a user-only popup to show on client side</param>
|
||||
/// <param name="Repeatable">Can the entity be emagged more than once? Prevents adding of <see cref="EmaggedComponent"/></param>
|
||||
/// <remarks>Needs to be handled in shared/client, not just the server, to actually show the emagging popup</remarks>
|
||||
[ByRefEvent]
|
||||
public record struct GotEmaggedEvent(EntityUid UserUid, bool Handled = false, bool Repeatable = false);
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct OnAttemptEmagEvent(EntityUid UserUid, bool Handled = false);
|
||||
public record struct GotEmaggedEvent(EntityUid UserUid, EmagType Type, bool Handled = false, bool Repeatable = false);
|
||||
|
||||
@@ -13,13 +13,13 @@ public sealed partial class EnsnareableComponent : Component
|
||||
/// <summary>
|
||||
/// How much should this slow down the entities walk?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float WalkSpeed = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How much should this slow down the entities sprint?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float SprintSpeed = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -38,6 +38,12 @@ public sealed partial class EnsnaringComponent : Component
|
||||
[DataField]
|
||||
public float StaminaDamage = 55f;
|
||||
|
||||
/// <summary>
|
||||
/// How many times can the ensnare be applied to the same target?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float MaxEnsnares = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Should this ensnare someone when thrown?
|
||||
/// </summary>
|
||||
|
||||
@@ -256,23 +256,18 @@ public abstract class SharedEnsnareableSystem : EntitySystem
|
||||
if (!TryComp<EnsnareableComponent>(target, out var ensnareable))
|
||||
return false;
|
||||
|
||||
// Need to insert before free legs check.
|
||||
Container.Insert(ensnare, ensnareable.Container);
|
||||
var numEnsnares = ensnareable.Container.ContainedEntities.Count;
|
||||
|
||||
var legs = _body.GetBodyChildrenOfType(target, BodyPartType.Leg).Count();
|
||||
var ensnaredLegs = (2 * ensnareable.Container.ContainedEntities.Count);
|
||||
var freeLegs = legs - ensnaredLegs;
|
||||
|
||||
if (freeLegs > 0)
|
||||
//Don't do anything if the maximum number of ensnares is applied.
|
||||
if (numEnsnares >= component.MaxEnsnares)
|
||||
return false;
|
||||
|
||||
// Apply stamina damage to target if they weren't ensnared before.
|
||||
if (ensnareable.IsEnsnared != true)
|
||||
Container.Insert(ensnare, ensnareable.Container);
|
||||
|
||||
// Apply stamina damage to target
|
||||
if (TryComp<StaminaComponent>(target, out var stamina))
|
||||
{
|
||||
if (TryComp<StaminaComponent>(target, out var stamina))
|
||||
{
|
||||
_stamina.TakeStaminaDamage(target, component.StaminaDamage, with: ensnare, component: stamina);
|
||||
}
|
||||
_stamina.TakeStaminaDamage(target, component.StaminaDamage, with: ensnare, component: stamina);
|
||||
}
|
||||
|
||||
component.Ensnared = target;
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed partial class ScatteringGrenadeComponent : Component
|
||||
/// <summary>
|
||||
/// If we have a pre-fill how many more can we spawn.
|
||||
/// </summary>
|
||||
[AutoNetworkedField]
|
||||
[ViewVariables(VVAccess.ReadOnly), AutoNetworkedField]
|
||||
public int UnspawnedCount;
|
||||
|
||||
/// <summary>
|
||||
@@ -36,6 +36,12 @@ public sealed partial class ScatteringGrenadeComponent : Component
|
||||
[DataField]
|
||||
public int Capacity = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Number of grenades currently contained in the cluster (both spawned and unspawned)
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public int Count => UnspawnedCount + Container.ContainedEntities.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Decides if contained entities trigger after getting launched
|
||||
/// </summary>
|
||||
|
||||
@@ -49,6 +49,10 @@ public abstract class SharedScatteringGrenadeSystem : EntitySystem
|
||||
if (entity.Comp.Whitelist == null)
|
||||
return;
|
||||
|
||||
// Make sure there's room for another grenade to be added
|
||||
if (entity.Comp.Count >= entity.Comp.Capacity)
|
||||
return;
|
||||
|
||||
if (args.Handled || !_whitelistSystem.IsValid(entity.Comp.Whitelist, args.Used))
|
||||
return;
|
||||
|
||||
@@ -65,6 +69,6 @@ public abstract class SharedScatteringGrenadeSystem : EntitySystem
|
||||
if (!TryComp<AppearanceComponent>(entity, out var appearanceComponent))
|
||||
return;
|
||||
|
||||
_appearance.SetData(entity, ClusterGrenadeVisuals.GrenadesCounter, entity.Comp.UnspawnedCount + entity.Comp.Container.ContainedEntities.Count, appearanceComponent);
|
||||
_appearance.SetData(entity, ClusterGrenadeVisuals.GrenadesCounter, entity.Comp.Count, appearanceComponent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,12 +59,6 @@ public sealed partial class FaxMachineComponent : Component
|
||||
[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>
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Content.Shared.Follower.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
[Access(typeof(FollowerSystem))]
|
||||
[NetworkedComponent, AutoGenerateComponentState]
|
||||
[NetworkedComponent, AutoGenerateComponentState(RaiseAfterAutoHandleState = true)]
|
||||
public sealed partial class FollowerComponent : Component
|
||||
{
|
||||
[AutoNetworkedField, DataField("following")]
|
||||
|
||||
@@ -21,10 +21,4 @@ public sealed partial class OrbitVisualsComponent : Component
|
||||
/// How long should the orbit stop animation last in seconds?
|
||||
/// </summary>
|
||||
public float OrbitStopLength = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How far along in the orbit, from 0 to 1, is this entity?
|
||||
/// </summary>
|
||||
[Animatable]
|
||||
public float Orbit { get; set; } = 0.0f;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Shared.Ghost;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Content.Shared.Movement.Pulling.Events;
|
||||
using Content.Shared.Polymorph;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
@@ -39,11 +40,13 @@ public sealed class FollowerSystem : EntitySystem
|
||||
SubscribeLocalEvent<FollowerComponent, MoveInputEvent>(OnFollowerMove);
|
||||
SubscribeLocalEvent<FollowerComponent, PullStartedMessage>(OnPullStarted);
|
||||
SubscribeLocalEvent<FollowerComponent, EntityTerminatingEvent>(OnFollowerTerminating);
|
||||
SubscribeLocalEvent<FollowerComponent, AfterAutoHandleStateEvent>(OnAfterHandleState);
|
||||
|
||||
SubscribeLocalEvent<FollowedComponent, ComponentGetStateAttemptEvent>(OnFollowedAttempt);
|
||||
SubscribeLocalEvent<FollowerComponent, GotEquippedHandEvent>(OnGotEquippedHand);
|
||||
SubscribeLocalEvent<FollowedComponent, EntityTerminatingEvent>(OnFollowedTerminating);
|
||||
SubscribeLocalEvent<BeforeSerializationEvent>(OnBeforeSave);
|
||||
SubscribeLocalEvent<FollowedComponent, PolymorphedEvent>(OnFollowedPolymorphed);
|
||||
}
|
||||
|
||||
private void OnFollowedAttempt(Entity<FollowedComponent> ent, ref ComponentGetStateAttemptEvent args)
|
||||
@@ -142,6 +145,11 @@ public sealed class FollowerSystem : EntitySystem
|
||||
StopFollowingEntity(uid, component.Following, deparent: false);
|
||||
}
|
||||
|
||||
private void OnAfterHandleState(Entity<FollowerComponent> entity, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
StartFollowingEntity(entity, entity.Comp.Following);
|
||||
}
|
||||
|
||||
// Since we parent our observer to the followed entity, we need to detach
|
||||
// before they get deleted so that we don't get recursively deleted too.
|
||||
private void OnFollowedTerminating(EntityUid uid, FollowedComponent component, ref EntityTerminatingEvent args)
|
||||
@@ -149,6 +157,15 @@ public sealed class FollowerSystem : EntitySystem
|
||||
StopAllFollowers(uid, component);
|
||||
}
|
||||
|
||||
private void OnFollowedPolymorphed(Entity<FollowedComponent> entity, ref PolymorphedEvent args)
|
||||
{
|
||||
foreach (var follower in entity.Comp.Following)
|
||||
{
|
||||
// Stop following the target's old entity and start following the new one
|
||||
StartFollowingEntity(follower, args.NewEntity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes an entity follow another entity, by parenting to it.
|
||||
/// </summary>
|
||||
@@ -209,6 +226,7 @@ public sealed class FollowerSystem : EntitySystem
|
||||
RaiseLocalEvent(follower, followerEv);
|
||||
RaiseLocalEvent(entity, entityEv);
|
||||
Dirty(entity, followedComp);
|
||||
Dirty(follower, followerComp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -220,7 +238,7 @@ public sealed class FollowerSystem : EntitySystem
|
||||
if (!Resolve(target, ref followed, false))
|
||||
return;
|
||||
|
||||
if (!HasComp<FollowerComponent>(uid))
|
||||
if (!TryComp<FollowerComponent>(uid, out var followerComp) || followerComp.Following != target)
|
||||
return;
|
||||
|
||||
followed.Following.Remove(uid);
|
||||
|
||||
13
Content.Shared/Forensics/Components/DnaComponent.cs
Normal file
13
Content.Shared/Forensics/Components/DnaComponent.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Forensics.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This component is for mobs that have DNA.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class DnaComponent : Component
|
||||
{
|
||||
[DataField("dna"), AutoNetworkedField]
|
||||
public string DNA = String.Empty;
|
||||
}
|
||||
@@ -41,7 +41,10 @@ namespace Content.Shared.GameTicking
|
||||
|
||||
private void OnRecordingStart(MappingDataNode metadata, List<object> events)
|
||||
{
|
||||
metadata["roundId"] = new ValueDataNode(RoundId.ToString());
|
||||
if (RoundId != 0)
|
||||
{
|
||||
metadata["roundId"] = new ValueDataNode(RoundId.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan RoundDuration()
|
||||
|
||||
@@ -188,11 +188,13 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
// TODO: this pattern is super uncommon, but it might be worth changing GetUsedEntityEvent to be recursive.
|
||||
if (TryComp<VirtualItemComponent>(component.ActiveHandEntity, out var virtualItem))
|
||||
args.Used = virtualItem.BlockingEntity;
|
||||
else
|
||||
args.Used = component.ActiveHandEntity;
|
||||
if (component.ActiveHandEntity.HasValue)
|
||||
{
|
||||
// allow for the item to return a different entity, e.g. virtual items
|
||||
RaiseLocalEvent(component.ActiveHandEntity.Value, ref args);
|
||||
}
|
||||
|
||||
args.Used ??= component.ActiveHandEntity;
|
||||
}
|
||||
|
||||
//TODO: Actually shows all items/clothing/etc.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Camera;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
|
||||
@@ -7,6 +8,8 @@ public abstract partial class SharedHandsSystem
|
||||
{
|
||||
private void InitializeRelay()
|
||||
{
|
||||
SubscribeLocalEvent<HandsComponent, GetEyeOffsetRelayedEvent>(RelayEvent);
|
||||
SubscribeLocalEvent<HandsComponent, GetEyePvsScaleRelayedEvent>(RelayEvent);
|
||||
SubscribeLocalEvent<HandsComponent, RefreshMovementSpeedModifiersEvent>(RelayEvent);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@ using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Holopad;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class HolographicAvatarComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The prototype sprite layer data for the hologram
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public PrototypeLayerData[] LayerData;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Shared.Holopad;
|
||||
@@ -6,7 +7,7 @@ namespace Content.Shared.Holopad;
|
||||
/// <summary>
|
||||
/// Holds data pertaining to holopad holograms
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class HolopadHologramComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
@@ -64,8 +65,8 @@ public sealed partial class HolopadHologramComponent : Component
|
||||
public Vector2 Offset = new Vector2();
|
||||
|
||||
/// <summary>
|
||||
/// A user that are linked to this hologram
|
||||
/// An entity that is linked to this hologram
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Entity<HolopadComponent>? LinkedHolopad;
|
||||
[ViewVariables, AutoNetworkedField]
|
||||
public EntityUid? LinkedEntity = null;
|
||||
}
|
||||
|
||||
@@ -20,29 +20,6 @@ public sealed partial class HolopadUserComponent : Component
|
||||
public HashSet<Entity<HolopadComponent>> LinkedHolopads = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A networked event raised when the visual state of a hologram is being updated
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class HolopadHologramVisualsUpdateEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The hologram being updated
|
||||
/// </summary>
|
||||
public readonly NetEntity Hologram;
|
||||
|
||||
/// <summary>
|
||||
/// The target the hologram is copying
|
||||
/// </summary>
|
||||
public readonly NetEntity? Target;
|
||||
|
||||
public HolopadHologramVisualsUpdateEvent(NetEntity hologram, NetEntity? target = null)
|
||||
{
|
||||
Hologram = hologram;
|
||||
Target = target;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A networked event raised when the visual state of a hologram is being updated
|
||||
/// </summary>
|
||||
@@ -65,40 +42,3 @@ public sealed class HolopadUserTypingChangedEvent : EntityEventArgs
|
||||
IsTyping = isTyping;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A networked event raised by the server to request the current visual state of a target player entity
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class PlayerSpriteStateRequest : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The player entity in question
|
||||
/// </summary>
|
||||
public readonly NetEntity TargetPlayer;
|
||||
|
||||
public PlayerSpriteStateRequest(NetEntity targetPlayer)
|
||||
{
|
||||
TargetPlayer = targetPlayer;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The client's response to a <see cref="PlayerSpriteStateRequest"/>
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class PlayerSpriteStateMessage : EntityEventArgs
|
||||
{
|
||||
public readonly NetEntity SpriteEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Data needed to reconstruct the player's sprite component layers
|
||||
/// </summary>
|
||||
public readonly PrototypeLayerData[]? SpriteLayerData;
|
||||
|
||||
public PlayerSpriteStateMessage(NetEntity spriteEntity, PrototypeLayerData[]? spriteLayerData = null)
|
||||
{
|
||||
SpriteEntity = spriteEntity;
|
||||
SpriteLayerData = spriteLayerData;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +132,34 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem
|
||||
Dirty(uid, humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clones a humanoid's appearance to a target mob, provided they both have humanoid components.
|
||||
/// </summary>
|
||||
/// <param name="source">Source entity to fetch the original appearance from.</param>
|
||||
/// <param name="target">Target entity to apply the source entity's appearance to.</param>
|
||||
/// <param name="sourceHumanoid">Source entity's humanoid component.</param>
|
||||
/// <param name="targetHumanoid">Target entity's humanoid component.</param>
|
||||
public void CloneAppearance(EntityUid source, EntityUid target, HumanoidAppearanceComponent? sourceHumanoid = null,
|
||||
HumanoidAppearanceComponent? targetHumanoid = null)
|
||||
{
|
||||
if (!Resolve(source, ref sourceHumanoid) || !Resolve(target, ref targetHumanoid))
|
||||
return;
|
||||
|
||||
targetHumanoid.Species = sourceHumanoid.Species;
|
||||
targetHumanoid.SkinColor = sourceHumanoid.SkinColor;
|
||||
targetHumanoid.EyeColor = sourceHumanoid.EyeColor;
|
||||
targetHumanoid.Age = sourceHumanoid.Age;
|
||||
SetSex(target, sourceHumanoid.Sex, false, targetHumanoid);
|
||||
targetHumanoid.CustomBaseLayers = new(sourceHumanoid.CustomBaseLayers);
|
||||
targetHumanoid.MarkingSet = new(sourceHumanoid.MarkingSet);
|
||||
|
||||
targetHumanoid.Gender = sourceHumanoid.Gender;
|
||||
if (TryComp<GrammarComponent>(target, out var grammar))
|
||||
grammar.Gender = sourceHumanoid.Gender;
|
||||
|
||||
Dirty(target, targetHumanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the visibility for multiple layers at once on a humanoid's sprite.
|
||||
/// </summary>
|
||||
|
||||
@@ -10,5 +10,6 @@ namespace Content.Shared.Implants.Components;
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class ImplantedComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public Container ImplantContainer = default!;
|
||||
}
|
||||
|
||||
37
Content.Shared/Implants/Components/RadioImplantComponent.cs
Normal file
37
Content.Shared/Implants/Components/RadioImplantComponent.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Content.Shared.Radio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Implants.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Gives the user access to a given channel without the need for a headset.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class RadioImplantComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The radio channel(s) to grant access to.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public HashSet<ProtoId<RadioChannelPrototype>> RadioChannels = new();
|
||||
|
||||
/// <summary>
|
||||
/// The radio channels that have been added by the implant to a user's ActiveRadioComponent.
|
||||
/// Used to track which channels were successfully added (not already in user)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Should not be modified outside RadioImplantSystem.cs
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
public HashSet<ProtoId<RadioChannelPrototype>> ActiveAddedChannels = new();
|
||||
|
||||
/// <summary>
|
||||
/// The radio channels that have been added by the implant to a user's IntrinsicRadioTransmitterComponent.
|
||||
/// Used to track which channels were successfully added (not already in user)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Should not be modified outside RadioImplantSystem.cs
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
public HashSet<ProtoId<RadioChannelPrototype>> TransmitterAddedChannels = new();
|
||||
}
|
||||
@@ -52,7 +52,14 @@ public abstract class SharedImplanterSystem : EntitySystem
|
||||
|
||||
args.PushMarkup(Loc.GetString("implanter-contained-implant-text", ("desc", component.ImplantData.Item2)));
|
||||
}
|
||||
public bool CheckSameImplant(EntityUid target, EntityUid implant)
|
||||
{
|
||||
if (!TryComp<ImplantedComponent>(target, out var implanted))
|
||||
return false;
|
||||
|
||||
var implantPrototype = Prototype(implant);
|
||||
return implanted.ImplantContainer.ContainedEntities.Any(entity => Prototype(entity) == implantPrototype);
|
||||
}
|
||||
//Instantly implant something and add all necessary components and containers.
|
||||
//Set to draw mode if not implant only
|
||||
public void Implant(EntityUid user, EntityUid target, EntityUid implanter, ImplanterComponent component)
|
||||
@@ -60,6 +67,16 @@ public abstract class SharedImplanterSystem : EntitySystem
|
||||
if (!CanImplant(user, target, implanter, component, out var implant, out var implantComp))
|
||||
return;
|
||||
|
||||
// Check if we are trying to implant a implant which is already implanted
|
||||
// Check AFTER the doafter to prevent "is it a fake?" metagaming against deceptive implants
|
||||
if (!component.AllowMultipleImplants && CheckSameImplant(target, implant.Value))
|
||||
{
|
||||
var name = Identity.Name(target, EntityManager, user);
|
||||
var msg = Loc.GetString("implanter-component-implant-already", ("implant", implant), ("target", name));
|
||||
_popup.PopupEntity(msg, target, user);
|
||||
return;
|
||||
}
|
||||
|
||||
//If the target doesn't have the implanted component, add it.
|
||||
var implantedComp = EnsureComp<ImplantedComponent>(target);
|
||||
var implantContainer = implantedComp.ImplantContainer;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Interaction;
|
||||
@@ -8,6 +7,7 @@ using Content.Shared.Tag;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Network;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Shared.Implants;
|
||||
|
||||
@@ -17,6 +17,7 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
|
||||
public const string BaseStorageId = "storagebase";
|
||||
|
||||
@@ -75,16 +76,11 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
|
||||
if (!_container.TryGetContainer(uid, BaseStorageId, out var storageImplant))
|
||||
return;
|
||||
|
||||
var entCoords = Transform(component.ImplantedEntity.Value).Coordinates;
|
||||
|
||||
var containedEntites = storageImplant.ContainedEntities.ToArray();
|
||||
|
||||
foreach (var entity in containedEntites)
|
||||
{
|
||||
if (Terminating(entity))
|
||||
continue;
|
||||
|
||||
_container.RemoveEntity(storageImplant.Owner, entity, force: true, destination: entCoords);
|
||||
_transformSystem.DropNextTo(entity, uid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ using Content.Shared.UserInterface;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Wall;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
@@ -38,7 +37,6 @@ using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -65,12 +63,10 @@ namespace Content.Shared.Interaction
|
||||
[Dependency] private readonly UseDelaySystem _useDelay = default!;
|
||||
[Dependency] private readonly PullingSystem _pullSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly SharedStrippableSystem _strippable = default!;
|
||||
[Dependency] private readonly SharedPlayerRateLimitManager _rateLimit = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly ISharedChatManager _chat = default!;
|
||||
|
||||
private EntityQuery<IgnoreUIRangeComponent> _ignoreUiRangeQuery;
|
||||
@@ -667,7 +663,8 @@ namespace Content.Shared.Interaction
|
||||
float range = InteractionRange,
|
||||
CollisionGroup collisionMask = InRangeUnobstructedMask,
|
||||
Ignored? predicate = null,
|
||||
bool popup = false)
|
||||
bool popup = false,
|
||||
bool overlapCheck = true)
|
||||
{
|
||||
if (!Resolve(other, ref other.Comp))
|
||||
return false;
|
||||
@@ -687,7 +684,8 @@ namespace Content.Shared.Interaction
|
||||
range,
|
||||
collisionMask,
|
||||
predicate,
|
||||
popup);
|
||||
popup,
|
||||
overlapCheck);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -717,6 +715,7 @@ namespace Content.Shared.Interaction
|
||||
/// <returns>
|
||||
/// True if the two points are within a given range without being obstructed.
|
||||
/// </returns>
|
||||
/// <param name="overlapCheck">If true, if the broadphase query returns an overlap (0f distance) this function will early out true with no raycast made.</param>
|
||||
public bool InRangeUnobstructed(
|
||||
Entity<TransformComponent?> origin,
|
||||
Entity<TransformComponent?> other,
|
||||
@@ -725,7 +724,8 @@ namespace Content.Shared.Interaction
|
||||
float range = InteractionRange,
|
||||
CollisionGroup collisionMask = InRangeUnobstructedMask,
|
||||
Ignored? predicate = null,
|
||||
bool popup = false)
|
||||
bool popup = false,
|
||||
bool overlapCheck = true)
|
||||
{
|
||||
Ignored combinedPredicate = e => e == origin.Owner || (predicate?.Invoke(e) ?? false);
|
||||
var inRange = true;
|
||||
@@ -748,7 +748,7 @@ namespace Content.Shared.Interaction
|
||||
fixtureB.FixtureCount > 0 &&
|
||||
Resolve(origin, ref origin.Comp))
|
||||
{
|
||||
var (worldPosA, worldRotA) = origin.Comp.GetWorldPositionRotation();
|
||||
var (worldPosA, worldRotA) = _transform.GetWorldPositionRotation(origin.Comp);
|
||||
var xfA = new Transform(worldPosA, worldRotA);
|
||||
var parentRotB = _transform.GetWorldRotation(otherCoordinates.EntityId);
|
||||
var xfB = new Transform(targetPos.Position, parentRotB + otherAngle);
|
||||
@@ -768,7 +768,7 @@ namespace Content.Shared.Interaction
|
||||
inRange = false;
|
||||
}
|
||||
// Overlap, early out and no raycast.
|
||||
else if (distance.Equals(0f))
|
||||
else if (overlapCheck && distance.Equals(0f))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -821,7 +821,7 @@ namespace Content.Shared.Interaction
|
||||
Ignored? predicate = null)
|
||||
{
|
||||
var transform = Transform(target);
|
||||
var (position, rotation) = transform.GetWorldPositionRotation();
|
||||
var (position, rotation) = _transform.GetWorldPositionRotation(transform);
|
||||
var mapPos = new MapCoordinates(position, transform.MapID);
|
||||
var combinedPredicate = GetPredicate(origin, target, mapPos, rotation, collisionMask, predicate);
|
||||
|
||||
@@ -1409,7 +1409,7 @@ namespace Content.Shared.Interaction
|
||||
/// <returns>If there is an entity being used.</returns>
|
||||
public bool TryGetUsedEntity(EntityUid user, [NotNullWhen(true)] out EntityUid? used, bool checkCanUse = true)
|
||||
{
|
||||
var ev = new GetUsedEntityEvent();
|
||||
var ev = new GetUsedEntityEvent(user);
|
||||
RaiseLocalEvent(user, ref ev);
|
||||
|
||||
used = ev.Used;
|
||||
@@ -1460,8 +1460,9 @@ namespace Content.Shared.Interaction
|
||||
/// Raised directed by-ref on an entity to determine what item will be used in interactions.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct GetUsedEntityEvent()
|
||||
public record struct GetUsedEntityEvent(EntityUid User)
|
||||
{
|
||||
public EntityUid User = User;
|
||||
public EntityUid? Used = null;
|
||||
|
||||
public bool Handled => Used != null;
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
namespace Content.Shared.Inventory.Events;
|
||||
|
||||
public sealed class RefreshEquipmentHudEvent<T> : EntityEventArgs, IInventoryRelayEvent where T : IComponent
|
||||
[ByRefEvent]
|
||||
public record struct RefreshEquipmentHudEvent<T>(SlotFlags TargetSlots) : IInventoryRelayEvent
|
||||
where T : IComponent
|
||||
{
|
||||
public SlotFlags TargetSlots { get; init; }
|
||||
public SlotFlags TargetSlots { get; } = TargetSlots;
|
||||
public bool Active = false;
|
||||
public List<T> Components = new();
|
||||
|
||||
public RefreshEquipmentHudEvent(SlotFlags targetSlots)
|
||||
{
|
||||
TargetSlots = targetSlots;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ public partial class InventorySystem
|
||||
/// </summary>
|
||||
public bool TryGetContainingSlot(Entity<TransformComponent?, MetaDataComponent?> entity, [NotNullWhen(true)] out SlotDefinition? slot)
|
||||
{
|
||||
if (!_containerSystem.TryGetContainingContainer(entity.Owner, out var container, entity.Comp2, entity.Comp1))
|
||||
if (!_containerSystem.TryGetContainingContainer(entity, out var container))
|
||||
{
|
||||
slot = null;
|
||||
return false;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Armor;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Chemistry.Hypospray.Events;
|
||||
@@ -40,6 +41,7 @@ public partial class InventorySystem
|
||||
SubscribeLocalEvent<InventoryComponent, TargetBeforeHyposprayInjectsEvent>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, SelfBeforeGunShotEvent>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, SelfBeforeClimbEvent>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, CoefficientQueryEvent>(RelayInventoryEvent);
|
||||
|
||||
// by-ref events
|
||||
SubscribeLocalEvent<InventoryComponent, GetExplosionResistanceEvent>(RefRelayInventoryEvent);
|
||||
@@ -55,14 +57,14 @@ public partial class InventorySystem
|
||||
SubscribeLocalEvent<InventoryComponent, SolutionScanEvent>(RelayInventoryEvent);
|
||||
|
||||
// ComponentActivatedClientSystems
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowJobIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHealthBarsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHealthIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHungerIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowThirstIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowMindShieldIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowSyndicateIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowCriminalRecordIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowJobIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHealthBarsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHealthIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHungerIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowThirstIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowMindShieldIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowSyndicateIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowCriminalRecordIconsComponent>>(RefRelayInventoryEvent);
|
||||
|
||||
SubscribeLocalEvent<InventoryComponent, GetVerbsEvent<EquipmentVerb>>(OnGetEquipmentVerbs);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ public abstract class SharedVirtualItemSystem : EntitySystem
|
||||
|
||||
SubscribeLocalEvent<VirtualItemComponent, BeforeRangedInteractEvent>(OnBeforeRangedInteract);
|
||||
SubscribeLocalEvent<VirtualItemComponent, GettingInteractedWithAttemptEvent>(OnGettingInteractedWithAttemptEvent);
|
||||
|
||||
SubscribeLocalEvent<VirtualItemComponent, GetUsedEntityEvent>(OnGetUsedEntity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -81,6 +83,23 @@ public abstract class SharedVirtualItemSystem : EntitySystem
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnGetUsedEntity(Entity<VirtualItemComponent> ent, ref GetUsedEntityEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
// if the user is holding the real item the virtual item points to,
|
||||
// we allow them to use it in the interaction
|
||||
foreach (var hand in _handsSystem.EnumerateHands(args.User))
|
||||
{
|
||||
if (hand.HeldEntity == ent.Comp.BlockingEntity)
|
||||
{
|
||||
args.Used = ent.Comp.BlockingEntity;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Hands
|
||||
|
||||
/// <summary>
|
||||
|
||||
43
Content.Shared/ItemRecall/ItemRecallComponent.cs
Normal file
43
Content.Shared/ItemRecall/ItemRecallComponent.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.ItemRecall;
|
||||
|
||||
/// <summary>
|
||||
/// Component for the ItemRecall action.
|
||||
/// Used for marking a held item and recalling it back into your hand with second action use.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedItemRecallSystem))]
|
||||
public sealed partial class ItemRecallComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The name the action should have while an entity is marked.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId? WhileMarkedName = "item-recall-marked-name";
|
||||
|
||||
/// <summary>
|
||||
/// The description the action should have while an entity is marked.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId? WhileMarkedDescription = "item-recall-marked-description";
|
||||
|
||||
/// <summary>
|
||||
/// The name the action starts with.
|
||||
/// This shouldn't be set in yaml.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? InitialName;
|
||||
|
||||
/// <summary>
|
||||
/// The description the action starts with.
|
||||
/// This shouldn't be set in yaml.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? InitialDescription;
|
||||
|
||||
/// <summary>
|
||||
/// The entity currently marked to be recalled by this action.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? MarkedEntity;
|
||||
}
|
||||
9
Content.Shared/ItemRecall/ItemRecallEvents.cs
Normal file
9
Content.Shared/ItemRecall/ItemRecallEvents.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Content.Shared.Actions;
|
||||
|
||||
namespace Content.Shared.ItemRecall;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when using the ItemRecall action.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public sealed partial class OnItemRecallActionEvent : InstantActionEvent;
|
||||
18
Content.Shared/ItemRecall/RecallMarkerComponent.cs
Normal file
18
Content.Shared/ItemRecall/RecallMarkerComponent.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.ItemRecall;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Component used as a marker for an item marked by the ItemRecall ability.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedItemRecallSystem))]
|
||||
public sealed partial class RecallMarkerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The action that marked this item.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? MarkedByAction;
|
||||
}
|
||||
187
Content.Shared/ItemRecall/SharedItemRecallSystem.cs
Normal file
187
Content.Shared/ItemRecall/SharedItemRecallSystem.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Projectiles;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Shared.ItemRecall;
|
||||
|
||||
/// <summary>
|
||||
/// System for handling the ItemRecall ability for wizards.
|
||||
/// </summary>
|
||||
public abstract partial class SharedItemRecallSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||
[Dependency] private readonly SharedPvsOverrideSystem _pvs = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popups = default!;
|
||||
[Dependency] private readonly SharedProjectileSystem _proj = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ItemRecallComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<ItemRecallComponent, OnItemRecallActionEvent>(OnItemRecallActionUse);
|
||||
|
||||
SubscribeLocalEvent<RecallMarkerComponent, ComponentShutdown>(OnRecallMarkerShutdown);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<ItemRecallComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
ent.Comp.InitialName = Name(ent);
|
||||
ent.Comp.InitialDescription = Description(ent);
|
||||
}
|
||||
|
||||
private void OnItemRecallActionUse(Entity<ItemRecallComponent> ent, ref OnItemRecallActionEvent args)
|
||||
{
|
||||
if (ent.Comp.MarkedEntity == null)
|
||||
{
|
||||
if (!TryComp<HandsComponent>(args.Performer, out var hands))
|
||||
return;
|
||||
|
||||
var markItem = _hands.GetActiveItem((args.Performer, hands));
|
||||
|
||||
if (markItem == null)
|
||||
{
|
||||
_popups.PopupClient(Loc.GetString("item-recall-item-mark-empty"), args.Performer, args.Performer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasComp<RecallMarkerComponent>(markItem))
|
||||
{
|
||||
_popups.PopupClient(Loc.GetString("item-recall-item-already-marked", ("item", markItem)), args.Performer, args.Performer);
|
||||
return;
|
||||
}
|
||||
|
||||
_popups.PopupClient(Loc.GetString("item-recall-item-marked", ("item", markItem.Value)), args.Performer, args.Performer);
|
||||
TryMarkItem(ent, markItem.Value);
|
||||
return;
|
||||
}
|
||||
|
||||
RecallItem(ent.Comp.MarkedEntity.Value);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void RecallItem(Entity<RecallMarkerComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent.Owner, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
if (!TryComp<InstantActionComponent>(ent.Comp.MarkedByAction, out var instantAction))
|
||||
return;
|
||||
|
||||
var actionOwner = instantAction.AttachedEntity;
|
||||
|
||||
if (actionOwner == null)
|
||||
return;
|
||||
|
||||
if (TryComp<EmbeddableProjectileComponent>(ent, out var projectile))
|
||||
_proj.EmbedDetach(ent, projectile, actionOwner.Value);
|
||||
|
||||
_popups.PopupPredicted(Loc.GetString("item-recall-item-summon", ("item", ent)), actionOwner.Value, actionOwner.Value);
|
||||
|
||||
_hands.TryForcePickupAnyHand(actionOwner.Value, ent);
|
||||
}
|
||||
|
||||
private void OnRecallMarkerShutdown(Entity<RecallMarkerComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
TryUnmarkItem(ent);
|
||||
}
|
||||
|
||||
private void TryMarkItem(Entity<ItemRecallComponent> ent, EntityUid item)
|
||||
{
|
||||
if (!TryComp<InstantActionComponent>(ent, out var instantAction))
|
||||
return;
|
||||
|
||||
var actionOwner = instantAction.AttachedEntity;
|
||||
|
||||
if (actionOwner == null)
|
||||
return;
|
||||
|
||||
AddToPvsOverride(item, actionOwner.Value);
|
||||
|
||||
var marker = AddComp<RecallMarkerComponent>(item);
|
||||
ent.Comp.MarkedEntity = item;
|
||||
Dirty(ent);
|
||||
|
||||
marker.MarkedByAction = ent.Owner;
|
||||
|
||||
UpdateActionAppearance(ent);
|
||||
Dirty(item, marker);
|
||||
}
|
||||
|
||||
private void TryUnmarkItem(EntityUid item)
|
||||
{
|
||||
if (!TryComp<RecallMarkerComponent>(item, out var marker))
|
||||
return;
|
||||
|
||||
if (!TryComp<InstantActionComponent>(marker.MarkedByAction, out var instantAction))
|
||||
return;
|
||||
|
||||
if (TryComp<ItemRecallComponent>(marker.MarkedByAction, out var action))
|
||||
{
|
||||
// For some reason client thinks the station grid owns the action on client and this doesn't work. It doesn't work in PopupEntity(mispredicts) and PopupPredicted either(doesnt show).
|
||||
// I don't have the heart to move this code to server because of this small thing.
|
||||
// This line will only do something once that is fixed.
|
||||
if (instantAction.AttachedEntity != null)
|
||||
{
|
||||
_popups.PopupClient(Loc.GetString("item-recall-item-unmark", ("item", item)), instantAction.AttachedEntity.Value, instantAction.AttachedEntity.Value, PopupType.MediumCaution);
|
||||
RemoveFromPvsOverride(item, instantAction.AttachedEntity.Value);
|
||||
}
|
||||
|
||||
action.MarkedEntity = null;
|
||||
UpdateActionAppearance((marker.MarkedByAction.Value, action));
|
||||
Dirty(marker.MarkedByAction.Value, action);
|
||||
}
|
||||
|
||||
RemCompDeferred<RecallMarkerComponent>(item);
|
||||
}
|
||||
|
||||
private void UpdateActionAppearance(Entity<ItemRecallComponent> action)
|
||||
{
|
||||
if (!TryComp<InstantActionComponent>(action, out var instantAction))
|
||||
return;
|
||||
|
||||
if (action.Comp.MarkedEntity == null)
|
||||
{
|
||||
if (action.Comp.InitialName != null)
|
||||
_metaData.SetEntityName(action, action.Comp.InitialName);
|
||||
if (action.Comp.InitialDescription != null)
|
||||
_metaData.SetEntityDescription(action, action.Comp.InitialDescription);
|
||||
_actions.SetEntityIcon(action, null, instantAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (action.Comp.WhileMarkedName != null)
|
||||
_metaData.SetEntityName(action, Loc.GetString(action.Comp.WhileMarkedName,
|
||||
("item", action.Comp.MarkedEntity.Value)));
|
||||
|
||||
if (action.Comp.WhileMarkedDescription != null)
|
||||
_metaData.SetEntityDescription(action, Loc.GetString(action.Comp.WhileMarkedDescription,
|
||||
("item", action.Comp.MarkedEntity.Value)));
|
||||
|
||||
_actions.SetEntityIcon(action, action.Comp.MarkedEntity, instantAction);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddToPvsOverride(EntityUid uid, EntityUid user)
|
||||
{
|
||||
if (!_player.TryGetSessionByEntity(user, out var mindSession))
|
||||
return;
|
||||
|
||||
_pvs.AddSessionOverride(uid, mindSession);
|
||||
}
|
||||
|
||||
private void RemoveFromPvsOverride(EntityUid uid, EntityUid user)
|
||||
{
|
||||
if (!_player.TryGetSessionByEntity(user, out var mindSession))
|
||||
return;
|
||||
|
||||
_pvs.RemoveSessionOverride(uid, mindSession);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Research.Prototypes;
|
||||
using Content.Shared.Lathe.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
@@ -9,15 +9,15 @@ namespace Content.Shared.Lathe
|
||||
public sealed partial class EmagLatheRecipesComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// All of the dynamic recipes that the lathe is capable to get using EMAG
|
||||
/// All of the dynamic recipe packs that the lathe is capable to get using EMAG
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public List<ProtoId<LatheRecipePrototype>> EmagDynamicRecipes = new();
|
||||
public List<ProtoId<LatheRecipePackPrototype>> EmagDynamicPacks = new();
|
||||
|
||||
/// <summary>
|
||||
/// All of the static recipes that the lathe is capable to get using EMAG
|
||||
/// All of the static recipe packs that the lathe is capable to get using EMAG
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public List<ProtoId<LatheRecipePrototype>> EmagStaticRecipes = new();
|
||||
public List<ProtoId<LatheRecipePackPrototype>> EmagStaticPacks = new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Lathe.Prototypes;
|
||||
using Content.Shared.Research.Prototypes;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
@@ -10,16 +11,16 @@ namespace Content.Shared.Lathe
|
||||
public sealed partial class LatheComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// All of the recipes that the lathe has by default
|
||||
/// All of the recipe packs that the lathe has by default
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<ProtoId<LatheRecipePrototype>> StaticRecipes = new();
|
||||
public List<ProtoId<LatheRecipePackPrototype>> StaticPacks = new();
|
||||
|
||||
/// <summary>
|
||||
/// All of the recipes that the lathe is capable of researching
|
||||
/// All of the recipe packs that the lathe is capable of researching
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<ProtoId<LatheRecipePrototype>> DynamicRecipes = new();
|
||||
public List<ProtoId<LatheRecipePackPrototype>> DynamicPacks = new();
|
||||
|
||||
/// <summary>
|
||||
/// The lathe's construction queue
|
||||
|
||||
31
Content.Shared/Lathe/Prototypes/LatheRecipePackPrototype.cs
Normal file
31
Content.Shared/Lathe/Prototypes/LatheRecipePackPrototype.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Content.Shared.Research.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
||||
|
||||
namespace Content.Shared.Lathe.Prototypes;
|
||||
|
||||
/// <summary>
|
||||
/// A pack of lathe recipes that one or more lathes can use.
|
||||
/// Packs will inherit the parents recipes when using inheritance, so you don't need to copy paste them.
|
||||
/// </summary>
|
||||
[Prototype]
|
||||
public sealed partial class LatheRecipePackPrototype : IPrototype, IInheritingPrototype
|
||||
{
|
||||
[ViewVariables]
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
[ParentDataField(typeof(AbstractPrototypeIdArraySerializer<LatheRecipePackPrototype>))]
|
||||
public string[]? Parents { get; }
|
||||
|
||||
[NeverPushInheritance]
|
||||
[AbstractDataField]
|
||||
public bool Abstract { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The lathe recipes contained by this pack.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
[AlwaysPushInheritance]
|
||||
public HashSet<ProtoId<LatheRecipePrototype>> Recipes = new();
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Lathe.Prototypes;
|
||||
using Content.Shared.Localizations;
|
||||
using Content.Shared.Materials;
|
||||
using Content.Shared.Research.Prototypes;
|
||||
@@ -18,6 +19,7 @@ public abstract class SharedLatheSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly SharedMaterialStorageSystem _materialStorage = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
public readonly Dictionary<string, List<LatheRecipePrototype>> InverseRecipes = new();
|
||||
|
||||
@@ -32,6 +34,18 @@ public abstract class SharedLatheSystem : EntitySystem
|
||||
BuildInverseRecipeDictionary();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add every recipe in the list of recipe packs to a single hashset.
|
||||
/// </summary>
|
||||
public void AddRecipesFromPacks(HashSet<ProtoId<LatheRecipePrototype>> recipes, IEnumerable<ProtoId<LatheRecipePackPrototype>> packs)
|
||||
{
|
||||
foreach (var id in packs)
|
||||
{
|
||||
var pack = _proto.Index(id);
|
||||
recipes.UnionWith(pack.Recipes);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<LatheComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
@@ -66,6 +80,12 @@ public abstract class SharedLatheSystem : EntitySystem
|
||||
|
||||
private void OnEmagged(EntityUid uid, EmagLatheRecipesComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Shared.Actions;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Content.Shared.Toggleable;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
@@ -22,6 +23,7 @@ public sealed class UnpoweredFlashlightSystem : EntitySystem
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _light = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -78,6 +80,9 @@ public sealed class UnpoweredFlashlightSystem : EntitySystem
|
||||
|
||||
private void OnGotEmagged(EntityUid uid, UnpoweredFlashlightComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (!_light.TryGetLight(uid, out var light))
|
||||
return;
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace Content.Shared.Localizations
|
||||
public static string FormatPlaytime(TimeSpan time)
|
||||
{
|
||||
var hours = (int)time.TotalHours;
|
||||
var minutes = time.Minutes;
|
||||
var minutes = (int)Math.Ceiling(time.TotalMinutes);
|
||||
return Loc.GetString($"zzzz-fmt-playtime", ("hours", hours), ("minutes", minutes));
|
||||
}
|
||||
|
||||
|
||||
@@ -54,9 +54,9 @@ public sealed partial class LockComponent : Component
|
||||
/// <summary>
|
||||
/// Whether or not an emag disables it.
|
||||
/// </summary>
|
||||
[DataField("breakOnEmag")]
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
public bool BreakOnEmag = true;
|
||||
public bool BreakOnAccessBreaker = true;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of do-after time needed to lock the entity.
|
||||
|
||||
@@ -28,6 +28,7 @@ public sealed class LockSystem : EntitySystem
|
||||
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] private readonly ActivatableUISystem _activatableUI = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _sharedPopupSystem = default!;
|
||||
@@ -295,7 +296,10 @@ public sealed class LockSystem : EntitySystem
|
||||
|
||||
private void OnEmagged(EntityUid uid, LockComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!component.Locked || !component.BreakOnEmag)
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Access))
|
||||
return;
|
||||
|
||||
if (!component.Locked || !component.BreakOnAccessBreaker)
|
||||
return;
|
||||
|
||||
_audio.PlayPredicted(component.UnlockSound, uid, args.UserUid);
|
||||
@@ -307,7 +311,7 @@ public sealed class LockSystem : EntitySystem
|
||||
var ev = new LockToggledEvent(false);
|
||||
RaiseLocalEvent(uid, ref ev, true);
|
||||
|
||||
RemComp<LockComponent>(uid); //Literally destroys the lock as a tell it was emagged
|
||||
args.Repeatable = true;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
11
Content.Shared/Magic/Components/AnimateableComponent.cs
Normal file
11
Content.Shared/Magic/Components/AnimateableComponent.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Magic.Components;
|
||||
|
||||
// Used on whitelist for animate spell/wand
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class AnimateableComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
16
Content.Shared/Magic/Events/AnimateSpellEvent.cs
Normal file
16
Content.Shared/Magic/Events/AnimateSpellEvent.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Magic.Events;
|
||||
|
||||
public sealed partial class AnimateSpellEvent : EntityTargetActionEvent, ISpeakSpell
|
||||
{
|
||||
[DataField]
|
||||
public string? Speech { get; private set; }
|
||||
|
||||
[DataField]
|
||||
public ComponentRegistry AddComponents = new();
|
||||
|
||||
[DataField]
|
||||
public HashSet<string> RemoveComponents = new();
|
||||
}
|
||||
24
Content.Shared/Magic/Events/VoidApplauseSpellEvent.cs
Normal file
24
Content.Shared/Magic/Events/VoidApplauseSpellEvent.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Magic.Events;
|
||||
|
||||
public sealed partial class VoidApplauseSpellEvent : EntityTargetActionEvent, ISpeakSpell
|
||||
{
|
||||
[DataField]
|
||||
public string? Speech { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Emote to use.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<EmotePrototype> Emote = "ClapSingle";
|
||||
|
||||
/// <summary>
|
||||
/// Visual effect entity that is spawned at both the user's and the target's location.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId Effect = "EffectVoidBlink";
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Body.Components;
|
||||
@@ -7,7 +8,6 @@ using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Lock;
|
||||
@@ -15,9 +15,6 @@ using Content.Shared.Magic.Components;
|
||||
using Content.Shared.Magic.Events;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Speech.Muting;
|
||||
@@ -30,13 +27,20 @@ using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Spawners;
|
||||
|
||||
namespace Content.Shared.Magic;
|
||||
|
||||
// TODO: Move BeforeCast & Prerequirements (like Wizard clothes) to action comp
|
||||
// Alt idea - make it its own comp and split, like the Charge PR
|
||||
// TODO: Move speech to actionComp or again, its own ECS
|
||||
// TODO: Use the MagicComp just for pure backend things like spawning patterns?
|
||||
/// <summary>
|
||||
/// Handles learning and using spells (actions)
|
||||
/// </summary>
|
||||
@@ -60,7 +64,6 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
[Dependency] private readonly LockSystem _lock = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
@@ -80,79 +83,8 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
SubscribeLocalEvent<ChargeSpellEvent>(OnChargeSpell);
|
||||
SubscribeLocalEvent<RandomGlobalSpawnSpellEvent>(OnRandomGlobalSpawnSpell);
|
||||
SubscribeLocalEvent<MindSwapSpellEvent>(OnMindSwapSpell);
|
||||
|
||||
// Spell wishlist
|
||||
// A wishlish of spells that I'd like to implement or planning on implementing in a future PR
|
||||
|
||||
// TODO: InstantDoAfterSpell and WorldDoafterSpell
|
||||
// Both would be an action that take in an event, that passes an event to trigger once the doafter is done
|
||||
// This would be three events:
|
||||
// 1 - Event that triggers from the action that starts the doafter
|
||||
// 2 - The doafter event itself, which passes the event with it
|
||||
// 3 - The event to trigger once the do-after finishes
|
||||
|
||||
// TODO: Inanimate objects to life ECS
|
||||
// AI sentience
|
||||
|
||||
// TODO: Flesh2Stone
|
||||
// Entity Target spell
|
||||
// Synergy with Inanimate object to life (detects player and allows player to move around)
|
||||
|
||||
// TODO: Lightning Spell
|
||||
// Should just fire lightning, try to prevent arc back to caster
|
||||
|
||||
// TODO: Magic Missile (homing projectile ecs)
|
||||
// Instant action, target any player (except self) on screen
|
||||
|
||||
// TODO: Random projectile ECS for magic-carp, wand of magic
|
||||
|
||||
// TODO: Recall Spell
|
||||
// mark any item in hand to recall
|
||||
// ItemRecallComponent
|
||||
// Event adds the component if it doesn't exist and the performer isn't stored in the comp
|
||||
// 2nd firing of the event checks to see if the recall comp has this uid, and if it does it calls it
|
||||
// if no free hands, summon at feet
|
||||
// if item deleted, clear stored item
|
||||
|
||||
// TODO: Jaunt (should be its own ECS)
|
||||
// Instant action
|
||||
// When clicked, disappear/reappear (goes to paused map)
|
||||
// option to restrict to tiles
|
||||
// option for requiring entry/exit (blood jaunt)
|
||||
// speed option
|
||||
|
||||
// TODO: Summon Events
|
||||
// List of wizard events to add into the event pool that frequently activate
|
||||
// floor is lava
|
||||
// change places
|
||||
// ECS that when triggered, will periodically trigger a random GameRule
|
||||
// Would need a controller/controller entity?
|
||||
|
||||
// TODO: Summon Guns
|
||||
// Summon a random gun at peoples feet
|
||||
// Get every alive player (not in cryo, not a simplemob)
|
||||
// TODO: After Antag Rework - Rare chance of giving gun collector status to people
|
||||
|
||||
// TODO: Summon Magic
|
||||
// Summon a random magic wand at peoples feet
|
||||
// Get every alive player (not in cryo, not a simplemob)
|
||||
// TODO: After Antag Rework - Rare chance of giving magic collector status to people
|
||||
|
||||
// TODO: Bottle of Blood
|
||||
// Summons Slaughter Demon
|
||||
// TODO: Slaughter Demon
|
||||
// Also see Jaunt
|
||||
|
||||
// TODO: Field Spells
|
||||
// Should be able to specify a grid of tiles (3x3 for example) that it effects
|
||||
// Timed despawn - so it doesn't last forever
|
||||
// Ignore caster - for spells that shouldn't effect the caster (ie if timestop should effect the caster)
|
||||
|
||||
// TODO: Touch toggle spell
|
||||
// 1 - When toggled on, show in hand
|
||||
// 2 - Block hand when toggled on
|
||||
// - Require free hand
|
||||
// 3 - use spell event when toggled & click
|
||||
SubscribeLocalEvent<VoidApplauseSpellEvent>(OnVoidApplause);
|
||||
SubscribeLocalEvent<AnimateSpellEvent>(OnAnimateSpell);
|
||||
}
|
||||
|
||||
private void OnBeforeCastSpell(Entity<MagicComponent> ent, ref BeforeCastSpellEvent args)
|
||||
@@ -371,22 +303,8 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
ev.Handled = true;
|
||||
Speak(ev);
|
||||
|
||||
foreach (var toRemove in ev.ToRemove)
|
||||
{
|
||||
if (_compFact.TryGetRegistration(toRemove, out var registration))
|
||||
RemComp(ev.Target, registration.Type);
|
||||
}
|
||||
|
||||
foreach (var (name, data) in ev.ToAdd)
|
||||
{
|
||||
if (HasComp(ev.Target, data.Component.GetType()))
|
||||
continue;
|
||||
|
||||
var component = (Component)_compFact.GetComponent(name);
|
||||
var temp = (object)component;
|
||||
_seriMan.CopyTo(data.Component, ref temp);
|
||||
EntityManager.AddComponent(ev.Target, (Component)temp!);
|
||||
}
|
||||
RemoveComponents(ev.Target, ev.ToRemove);
|
||||
AddComponents(ev.Target, ev.ToAdd);
|
||||
}
|
||||
// End Change Component Spells
|
||||
#endregion
|
||||
@@ -402,8 +320,7 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var transform = Transform(args.Performer);
|
||||
|
||||
if (transform.MapID != args.Target.GetMapId(EntityManager) || !_interaction.InRangeUnobstructed(args.Performer, args.Target, range: 1000F, collisionMask: CollisionGroup.Opaque, popup: true))
|
||||
if (transform.MapID != _transform.GetMapId(args.Target) || !_interaction.InRangeUnobstructed(args.Performer, args.Target, range: 1000F, collisionMask: CollisionGroup.Opaque, popup: true))
|
||||
return;
|
||||
|
||||
_transform.SetCoordinates(args.Performer, args.Target);
|
||||
@@ -411,6 +328,17 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
Speak(args);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public virtual void OnVoidApplause(VoidApplauseSpellEvent ev)
|
||||
{
|
||||
if (ev.Handled || !PassesSpellPrerequisites(ev.Action, ev.Performer))
|
||||
return;
|
||||
|
||||
ev.Handled = true;
|
||||
Speak(ev);
|
||||
|
||||
_transform.SwapPositions(ev.Performer, ev.Target);
|
||||
}
|
||||
// End Teleport Spells
|
||||
#endregion
|
||||
#region Spell Helpers
|
||||
@@ -433,9 +361,32 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
comp.Uid = performer;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddComponents(EntityUid target, ComponentRegistry comps)
|
||||
{
|
||||
foreach (var (name, data) in comps)
|
||||
{
|
||||
if (HasComp(target, data.Component.GetType()))
|
||||
continue;
|
||||
|
||||
var component = (Component)_compFact.GetComponent(name);
|
||||
var temp = (object)component;
|
||||
_seriMan.CopyTo(data.Component, ref temp);
|
||||
EntityManager.AddComponent(target, (Component)temp!);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveComponents(EntityUid target, HashSet<string> comps)
|
||||
{
|
||||
foreach (var toRemove in comps)
|
||||
{
|
||||
if (_compFact.TryGetRegistration(toRemove, out var registration))
|
||||
RemComp(target, registration.Type);
|
||||
}
|
||||
}
|
||||
// End Spell Helpers
|
||||
#endregion
|
||||
#region Smite Spells
|
||||
#region Touch Spells
|
||||
private void OnSmiteSpell(SmiteSpellEvent ev)
|
||||
{
|
||||
if (ev.Handled || !PassesSpellPrerequisites(ev.Action, ev.Performer))
|
||||
@@ -454,7 +405,8 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
|
||||
_body.GibBody(ev.Target, true, body);
|
||||
}
|
||||
// End Smite Spells
|
||||
|
||||
// End Touch Spells
|
||||
#endregion
|
||||
#region Knock Spells
|
||||
/// <summary>
|
||||
@@ -576,6 +528,33 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
_stun.TryParalyze(ev.Performer, ev.PerformerStunDuration, true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region Animation Spells
|
||||
|
||||
private void OnAnimateSpell(AnimateSpellEvent ev)
|
||||
{
|
||||
if (ev.Handled || !PassesSpellPrerequisites(ev.Action, ev.Performer) || !TryComp<FixturesComponent>(ev.Target, out var fixtures) ||
|
||||
!TryComp<PhysicsComponent>(ev.Target, out var physics))
|
||||
return;
|
||||
|
||||
ev.Handled = true;
|
||||
//Speak(ev);
|
||||
|
||||
RemoveComponents(ev.Target, ev.RemoveComponents);
|
||||
AddComponents(ev.Target, ev.AddComponents);
|
||||
|
||||
var xform = Transform(ev.Target);
|
||||
var fixture = fixtures.Fixtures.First();
|
||||
|
||||
_transform.Unanchor(ev.Target);
|
||||
_physics.SetCanCollide(ev.Target, true, true, false, fixtures, physics);
|
||||
_physics.SetCollisionMask(ev.Target, fixture.Key, fixture.Value, (int)CollisionGroup.FlyingMobMask, fixtures, physics);
|
||||
_physics.SetCollisionLayer(ev.Target, fixture.Key, fixture.Value, (int)CollisionGroup.FlyingMobLayer, fixtures, physics);
|
||||
_physics.SetBodyType(ev.Target, BodyType.KinematicController, fixtures, physics, xform);
|
||||
_physics.SetBodyStatus(ev.Target, physics, BodyStatus.InAir, true);
|
||||
_physics.SetFixedRotation(ev.Target, false, true, fixtures, physics);
|
||||
}
|
||||
|
||||
#endregion
|
||||
// End Spells
|
||||
#endregion
|
||||
|
||||
@@ -120,10 +120,11 @@ namespace Content.Shared.Maps
|
||||
private static bool GetWorldTileBox(TileRef turf, out Box2Rotated res)
|
||||
{
|
||||
var entManager = IoCManager.Resolve<IEntityManager>();
|
||||
var xformSystem = entManager.System<SharedTransformSystem>();
|
||||
|
||||
if (entManager.TryGetComponent<MapGridComponent>(turf.GridUid, out var tileGrid))
|
||||
{
|
||||
var gridRot = entManager.GetComponent<TransformComponent>(turf.GridUid).WorldRotation;
|
||||
var gridRot = xformSystem.GetWorldRotation(turf.GridUid);
|
||||
|
||||
// This is scaled to 90 % so it doesn't encompass walls on other tiles.
|
||||
var tileBox = Box2.UnitCentered.Scale(0.9f);
|
||||
|
||||
@@ -29,6 +29,7 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] protected readonly SharedContainerSystem Container = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
public const string ActiveReclaimerContainerId = "active-material-reclaimer-container";
|
||||
|
||||
@@ -60,6 +61,12 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem
|
||||
|
||||
private void OnEmagged(EntityUid uid, MaterialReclaimerComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -207,7 +214,7 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem
|
||||
component.Enabled &&
|
||||
!component.Broken &&
|
||||
HasComp<BodyComponent>(victim) &&
|
||||
HasComp<EmaggedComponent>(uid);
|
||||
_emag.CheckFlag(uid, EmagType.Interaction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -20,6 +20,7 @@ public abstract partial class SharedCryoPodSystem: EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly StandingStateSystem _standingStateSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
@@ -156,9 +157,13 @@ public abstract partial class SharedCryoPodSystem: EntitySystem
|
||||
protected void OnEmagged(EntityUid uid, CryoPodComponent? cryoPodComponent, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!Resolve(uid, ref cryoPodComponent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (cryoPodComponent.PermaLocked && cryoPodComponent.Locked)
|
||||
return;
|
||||
|
||||
cryoPodComponent.PermaLocked = true;
|
||||
cryoPodComponent.Locked = true;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Mindshield.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class FakeMindShieldImplantComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Mindshield.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class FakeMindShieldComponent : Component
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The state of the Fake mindshield, if true the owning entity will display a mindshield effect on their job icon
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool IsEnabled { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The Security status icon displayed to the security officer. Should be a duplicate of the one the mindshield uses since it's spoofing that
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public ProtoId<SecurityIconPrototype> MindShieldStatusIcon = "MindShieldIcon";
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Mindshield.Components;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared.Mindshield.FakeMindShield;
|
||||
|
||||
public sealed class SharedFakeMindShieldImplantSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SubdermalImplantComponent, FakeMindShieldToggleEvent>(OnFakeMindShieldToggle);
|
||||
SubscribeLocalEvent<FakeMindShieldImplantComponent, ImplantImplantedEvent>(ImplantCheck);
|
||||
SubscribeLocalEvent<FakeMindShieldImplantComponent, EntGotRemovedFromContainerMessage>(ImplantDraw);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raise the Action of a Implanted user toggling their implant to the FakeMindshieldComponent on their entity
|
||||
/// </summary>
|
||||
private void OnFakeMindShieldToggle(Entity<SubdermalImplantComponent> entity, ref FakeMindShieldToggleEvent ev)
|
||||
{
|
||||
ev.Handled = true;
|
||||
if (entity.Comp.ImplantedEntity is not { } ent)
|
||||
return;
|
||||
|
||||
if (!TryComp<FakeMindShieldComponent>(ent, out var comp))
|
||||
return;
|
||||
_actionsSystem.SetToggled(ev.Action, !comp.IsEnabled); // Set it to what the Mindshield component WILL be after this
|
||||
RaiseLocalEvent(ent, ev); //this reraises the action event to support an eventual future Changeling Antag which will also be using this component for it's "mindshield" ability
|
||||
}
|
||||
private void ImplantCheck(EntityUid uid, FakeMindShieldImplantComponent component ,ref ImplantImplantedEvent ev)
|
||||
{
|
||||
if (ev.Implanted != null)
|
||||
EnsureComp<FakeMindShieldComponent>(ev.Implanted.Value);
|
||||
}
|
||||
|
||||
private void ImplantDraw(Entity<FakeMindShieldImplantComponent> ent, ref EntGotRemovedFromContainerMessage ev)
|
||||
{
|
||||
RemComp<FakeMindShieldComponent>(ev.Container.Owner);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user