Merge remote-tracking branch 'upstream/master' into ed-17-06-2024-upstream
# Conflicts: # Resources/Prototypes/Maps/oasis.yml
This commit is contained in:
31
Content.Shared/Atmos/Components/MovedByPressureComponent.cs
Normal file
31
Content.Shared/Atmos/Components/MovedByPressureComponent.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace Content.Shared.Atmos.Components;
|
||||
|
||||
// Unfortunately can't be friends yet due to magboots.
|
||||
[RegisterComponent]
|
||||
public sealed partial class MovedByPressureComponent : Component
|
||||
{
|
||||
public const float MoveForcePushRatio = 1f;
|
||||
public const float MoveForceForcePushRatio = 1f;
|
||||
public const float ProbabilityOffset = 25f;
|
||||
public const float ProbabilityBasePercent = 10f;
|
||||
public const float ThrowForce = 100f;
|
||||
|
||||
/// <summary>
|
||||
/// Accumulates time when yeeted by high pressure deltas.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Accumulator;
|
||||
|
||||
[DataField]
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
[DataField]
|
||||
public float PressureResistance { get; set; } = 1f;
|
||||
|
||||
[DataField]
|
||||
public float MoveResist { get; set; } = 100f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int LastHighPressureMovementAirCycle { get; set; } = 0;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Content.Shared.Atmos.Piping.Unary.Components
|
||||
public VentPressureBound PressureChecks { get; set; } = VentPressureBound.ExternalBound;
|
||||
public float ExternalPressureBound { get; set; } = Atmospherics.OneAtmosphere;
|
||||
public float InternalPressureBound { get; set; } = 0f;
|
||||
public bool PressureLockoutOverride { get; set; } = false;
|
||||
|
||||
// Presets for 'dumb' air alarm modes
|
||||
|
||||
@@ -22,7 +23,8 @@ namespace Content.Shared.Atmos.Piping.Unary.Components
|
||||
PumpDirection = VentPumpDirection.Releasing,
|
||||
PressureChecks = VentPressureBound.ExternalBound,
|
||||
ExternalPressureBound = Atmospherics.OneAtmosphere,
|
||||
InternalPressureBound = 0f
|
||||
InternalPressureBound = 0f,
|
||||
PressureLockoutOverride = false
|
||||
};
|
||||
|
||||
public static GasVentPumpData FillModePreset = new GasVentPumpData
|
||||
@@ -32,7 +34,8 @@ namespace Content.Shared.Atmos.Piping.Unary.Components
|
||||
PumpDirection = VentPumpDirection.Releasing,
|
||||
PressureChecks = VentPressureBound.ExternalBound,
|
||||
ExternalPressureBound = Atmospherics.OneAtmosphere * 50,
|
||||
InternalPressureBound = 0f
|
||||
InternalPressureBound = 0f,
|
||||
PressureLockoutOverride = true
|
||||
};
|
||||
|
||||
public static GasVentPumpData PanicModePreset = new GasVentPumpData
|
||||
@@ -42,7 +45,8 @@ namespace Content.Shared.Atmos.Piping.Unary.Components
|
||||
PumpDirection = VentPumpDirection.Releasing,
|
||||
PressureChecks = VentPressureBound.ExternalBound,
|
||||
ExternalPressureBound = Atmospherics.OneAtmosphere,
|
||||
InternalPressureBound = 0f
|
||||
InternalPressureBound = 0f,
|
||||
PressureLockoutOverride = false
|
||||
};
|
||||
|
||||
public static GasVentPumpData ReplaceModePreset = new GasVentPumpData
|
||||
@@ -53,7 +57,8 @@ namespace Content.Shared.Atmos.Piping.Unary.Components
|
||||
PumpDirection = VentPumpDirection.Releasing,
|
||||
PressureChecks = VentPressureBound.ExternalBound,
|
||||
ExternalPressureBound = Atmospherics.OneAtmosphere,
|
||||
InternalPressureBound = 0f
|
||||
InternalPressureBound = 0f,
|
||||
PressureLockoutOverride = false
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Damage.ForceSay;
|
||||
using Content.Shared.Eye.Blinding.Systems;
|
||||
using Content.Shared.Pointing;
|
||||
using Content.Shared.Speech;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Bed.Sleep
|
||||
{
|
||||
public abstract class SharedSleepingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly BlindableSystem _blindableSystem = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>] private const string WakeActionId = "ActionWake";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SleepingComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SleepingComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<SleepingComponent, SpeakAttemptEvent>(OnSpeakAttempt);
|
||||
SubscribeLocalEvent<SleepingComponent, CanSeeAttemptEvent>(OnSeeAttempt);
|
||||
SubscribeLocalEvent<SleepingComponent, PointAttemptEvent>(OnPointAttempt);
|
||||
}
|
||||
|
||||
|
||||
private void OnMapInit(EntityUid uid, SleepingComponent component, MapInitEvent args)
|
||||
{
|
||||
var ev = new SleepStateChangedEvent(true);
|
||||
RaiseLocalEvent(uid, ev);
|
||||
_blindableSystem.UpdateIsBlind(uid);
|
||||
_actionsSystem.AddAction(uid, ref component.WakeAction, WakeActionId, uid);
|
||||
|
||||
// TODO remove hardcoded time.
|
||||
_actionsSystem.SetCooldown(component.WakeAction, _gameTiming.CurTime, _gameTiming.CurTime + TimeSpan.FromSeconds(2f));
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, SleepingComponent component, ComponentShutdown args)
|
||||
{
|
||||
_actionsSystem.RemoveAction(uid, component.WakeAction);
|
||||
var ev = new SleepStateChangedEvent(false);
|
||||
RaiseLocalEvent(uid, ev);
|
||||
_blindableSystem.UpdateIsBlind(uid);
|
||||
}
|
||||
|
||||
private void OnSpeakAttempt(EntityUid uid, SleepingComponent component, SpeakAttemptEvent args)
|
||||
{
|
||||
// TODO reduce duplication of this behavior with MobStateSystem somehow
|
||||
if (HasComp<AllowNextCritSpeechComponent>(uid))
|
||||
{
|
||||
RemCompDeferred<AllowNextCritSpeechComponent>(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnSeeAttempt(EntityUid uid, SleepingComponent component, CanSeeAttemptEvent args)
|
||||
{
|
||||
if (component.LifeStage <= ComponentLifeStage.Running)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnPointAttempt(EntityUid uid, SleepingComponent component, PointAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public sealed partial class SleepActionEvent : InstantActionEvent {}
|
||||
|
||||
public sealed partial class WakeActionEvent : InstantActionEvent {}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity when they fall asleep or wake up.
|
||||
/// </summary>
|
||||
public sealed class SleepStateChangedEvent : EntityEventArgs
|
||||
{
|
||||
public bool FellAsleep = false;
|
||||
|
||||
public SleepStateChangedEvent(bool fellAsleep)
|
||||
{
|
||||
FellAsleep = fellAsleep;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,42 @@
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared.Bed.Sleep;
|
||||
|
||||
/// <summary>
|
||||
/// Added to entities when they go to sleep.
|
||||
/// </summary>
|
||||
[NetworkedComponent, RegisterComponent, AutoGenerateComponentPause(Dirty = true)]
|
||||
[NetworkedComponent, RegisterComponent]
|
||||
[AutoGenerateComponentState, AutoGenerateComponentPause(Dirty = true)]
|
||||
public sealed partial class SleepingComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How much damage of any type it takes to wake this entity.
|
||||
/// </summary>
|
||||
[DataField("wakeThreshold")]
|
||||
[DataField]
|
||||
public FixedPoint2 WakeThreshold = FixedPoint2.New(2);
|
||||
|
||||
/// <summary>
|
||||
/// Cooldown time between users hand interaction.
|
||||
/// </summary>
|
||||
[DataField("cooldown")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public TimeSpan Cooldown = TimeSpan.FromSeconds(1f);
|
||||
|
||||
[DataField("cooldownEnd", customTypeSerializer:typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
public TimeSpan CoolDownEnd;
|
||||
[DataField]
|
||||
[AutoNetworkedField, AutoPausedField]
|
||||
public TimeSpan CooldownEnd;
|
||||
|
||||
[DataField("wakeAction")] public EntityUid? WakeAction;
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
public EntityUid? WakeAction;
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when another player attempts to wake this entity.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier WakeAttemptSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg")
|
||||
{
|
||||
Params = AudioParams.Default.WithVariation(0.05f)
|
||||
};
|
||||
}
|
||||
|
||||
314
Content.Shared/Bed/Sleep/SleepingSystem.cs
Normal file
314
Content.Shared/Bed/Sleep/SleepingSystem.cs
Normal file
@@ -0,0 +1,314 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.ForceSay;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Eye.Blinding.Systems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Pointing;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Slippery;
|
||||
using Content.Shared.Sound;
|
||||
using Content.Shared.Sound.Components;
|
||||
using Content.Shared.Speech;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Bed.Sleep;
|
||||
|
||||
public sealed partial class SleepingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly BlindableSystem _blindableSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedEmitSoundSystem _emitSound = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
|
||||
|
||||
public static readonly ProtoId<EntityPrototype> SleepActionId = "ActionSleep";
|
||||
public static readonly ProtoId<EntityPrototype> WakeActionId = "ActionWake";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ActionsContainerComponent, SleepActionEvent>(OnBedSleepAction);
|
||||
|
||||
SubscribeLocalEvent<MobStateComponent, SleepStateChangedEvent>(OnSleepStateChanged);
|
||||
SubscribeLocalEvent<MobStateComponent, WakeActionEvent>(OnWakeAction);
|
||||
SubscribeLocalEvent<MobStateComponent, SleepActionEvent>(OnSleepAction);
|
||||
|
||||
SubscribeLocalEvent<SleepingComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
SubscribeLocalEvent<SleepingComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
SubscribeLocalEvent<SleepingComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SleepingComponent, SpeakAttemptEvent>(OnSpeakAttempt);
|
||||
SubscribeLocalEvent<SleepingComponent, CanSeeAttemptEvent>(OnSeeAttempt);
|
||||
SubscribeLocalEvent<SleepingComponent, PointAttemptEvent>(OnPointAttempt);
|
||||
SubscribeLocalEvent<SleepingComponent, SlipAttemptEvent>(OnSlip);
|
||||
SubscribeLocalEvent<SleepingComponent, ConsciousAttemptEvent>(OnConsciousAttempt);
|
||||
SubscribeLocalEvent<SleepingComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<SleepingComponent, GetVerbsEvent<AlternativeVerb>>(AddWakeVerb);
|
||||
SubscribeLocalEvent<SleepingComponent, InteractHandEvent>(OnInteractHand);
|
||||
|
||||
SubscribeLocalEvent<ForcedSleepingComponent, ComponentInit>(OnInit);
|
||||
}
|
||||
|
||||
private void OnBedSleepAction(Entity<ActionsContainerComponent> ent, ref SleepActionEvent args)
|
||||
{
|
||||
TrySleeping(args.Performer);
|
||||
}
|
||||
|
||||
private void OnWakeAction(Entity<MobStateComponent> ent, ref WakeActionEvent args)
|
||||
{
|
||||
if (TryWakeWithCooldown(ent.Owner))
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnSleepAction(Entity<MobStateComponent> ent, ref SleepActionEvent args)
|
||||
{
|
||||
TrySleeping((ent, ent.Comp));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// when sleeping component is added or removed, we do some stuff with other components.
|
||||
/// </summary>
|
||||
private void OnSleepStateChanged(Entity<MobStateComponent> ent, ref SleepStateChangedEvent args)
|
||||
{
|
||||
if (args.FellAsleep)
|
||||
{
|
||||
// Expiring status effects would remove the components needed for sleeping
|
||||
_statusEffectsSystem.TryRemoveStatusEffect(ent.Owner, "Stun");
|
||||
_statusEffectsSystem.TryRemoveStatusEffect(ent.Owner, "KnockedDown");
|
||||
|
||||
EnsureComp<StunnedComponent>(ent);
|
||||
EnsureComp<KnockedDownComponent>(ent);
|
||||
|
||||
if (TryComp<SleepEmitSoundComponent>(ent, out var sleepSound))
|
||||
{
|
||||
var emitSound = EnsureComp<SpamEmitSoundComponent>(ent);
|
||||
if (HasComp<SnoringComponent>(ent))
|
||||
{
|
||||
emitSound.Sound = sleepSound.Snore;
|
||||
}
|
||||
emitSound.MinInterval = sleepSound.Interval;
|
||||
emitSound.MaxInterval = sleepSound.MaxInterval;
|
||||
emitSound.PopUp = sleepSound.PopUp;
|
||||
Dirty(ent.Owner, emitSound);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
RemComp<StunnedComponent>(ent);
|
||||
RemComp<KnockedDownComponent>(ent);
|
||||
RemComp<SpamEmitSoundComponent>(ent);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<SleepingComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
var ev = new SleepStateChangedEvent(true);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
_blindableSystem.UpdateIsBlind(ent.Owner);
|
||||
_actionsSystem.AddAction(ent, ref ent.Comp.WakeAction, WakeActionId, ent);
|
||||
|
||||
// TODO remove hardcoded time.
|
||||
_actionsSystem.SetCooldown(ent.Comp.WakeAction, _gameTiming.CurTime, _gameTiming.CurTime + TimeSpan.FromSeconds(2f));
|
||||
}
|
||||
|
||||
private void OnSpeakAttempt(Entity<SleepingComponent> ent, ref SpeakAttemptEvent args)
|
||||
{
|
||||
// TODO reduce duplication of this behavior with MobStateSystem somehow
|
||||
if (HasComp<AllowNextCritSpeechComponent>(ent))
|
||||
{
|
||||
RemCompDeferred<AllowNextCritSpeechComponent>(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnSeeAttempt(Entity<SleepingComponent> ent, ref CanSeeAttemptEvent args)
|
||||
{
|
||||
if (ent.Comp.LifeStage <= ComponentLifeStage.Running)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnPointAttempt(Entity<SleepingComponent> ent, ref PointAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnSlip(Entity<SleepingComponent> ent, ref SlipAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnConsciousAttempt(Entity<SleepingComponent> ent, ref ConsciousAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<SleepingComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
if (args.IsInDetailsRange)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("sleep-examined", ("target", Identity.Entity(ent, EntityManager))));
|
||||
}
|
||||
}
|
||||
|
||||
private void AddWakeVerb(Entity<SleepingComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess)
|
||||
return;
|
||||
|
||||
var target = args.Target;
|
||||
var user = args.User;
|
||||
AlternativeVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
TryWakeWithCooldown((ent, ent.Comp), user: user);
|
||||
},
|
||||
Text = Loc.GetString("action-name-wake"),
|
||||
Priority = 2
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When you click on a sleeping person with an empty hand, try to wake them.
|
||||
/// </summary>
|
||||
private void OnInteractHand(Entity<SleepingComponent> ent, ref InteractHandEvent args)
|
||||
{
|
||||
args.Handled = true;
|
||||
|
||||
TryWakeWithCooldown((ent, ent.Comp), args.User);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wake up on taking an instance of damage at least the value of WakeThreshold.
|
||||
/// </summary>
|
||||
private void OnDamageChanged(Entity<SleepingComponent> ent, ref DamageChangedEvent args)
|
||||
{
|
||||
if (!args.DamageIncreased || args.DamageDelta == null)
|
||||
return;
|
||||
|
||||
if (args.DamageDelta.GetTotal() >= ent.Comp.WakeThreshold)
|
||||
TryWaking((ent, ent.Comp));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In crit, we wake up if we are not being forced to sleep.
|
||||
/// And, you can't sleep when dead...
|
||||
/// </summary>
|
||||
private void OnMobStateChanged(Entity<SleepingComponent> ent, ref MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState == MobState.Dead)
|
||||
{
|
||||
RemComp<SpamEmitSoundComponent>(ent);
|
||||
RemComp<SleepingComponent>(ent);
|
||||
return;
|
||||
}
|
||||
if (TryComp<SpamEmitSoundComponent>(ent, out var spam))
|
||||
_emitSound.SetEnabled((ent, spam), args.NewMobState == MobState.Alive);
|
||||
}
|
||||
|
||||
private void OnInit(Entity<ForcedSleepingComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
TrySleeping(ent.Owner);
|
||||
}
|
||||
|
||||
private void Wake(Entity<SleepingComponent> ent)
|
||||
{
|
||||
RemComp<SleepingComponent>(ent);
|
||||
_actionsSystem.RemoveAction(ent, ent.Comp.WakeAction);
|
||||
|
||||
var ev = new SleepStateChangedEvent(false);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
|
||||
_blindableSystem.UpdateIsBlind(ent.Owner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try sleeping. Only mobs can sleep.
|
||||
/// </summary>
|
||||
public bool TrySleeping(Entity<MobStateComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, logMissing: false))
|
||||
return false;
|
||||
|
||||
var tryingToSleepEvent = new TryingToSleepEvent(ent);
|
||||
RaiseLocalEvent(ent, ref tryingToSleepEvent);
|
||||
if (tryingToSleepEvent.Cancelled)
|
||||
return false;
|
||||
|
||||
EnsureComp<SleepingComponent>(ent);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to wake up <paramref name="ent"/>, with a cooldown between attempts to prevent spam.
|
||||
/// </summary>
|
||||
public bool TryWakeWithCooldown(Entity<SleepingComponent?> ent, EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
var curTime = _gameTiming.CurTime;
|
||||
|
||||
if (curTime < ent.Comp.CooldownEnd)
|
||||
return false;
|
||||
|
||||
ent.Comp.CooldownEnd = curTime + ent.Comp.Cooldown;
|
||||
Dirty(ent, ent.Comp);
|
||||
return TryWaking(ent, user: user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to wake up <paramref name="ent"/>.
|
||||
/// </summary>
|
||||
public bool TryWaking(Entity<SleepingComponent?> ent, bool force = false, EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
if (!force && HasComp<ForcedSleepingComponent>(ent))
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
_audio.PlayPredicted(ent.Comp.WakeAttemptSound, ent, user);
|
||||
_popupSystem.PopupClient(Loc.GetString("wake-other-failure", ("target", Identity.Entity(ent, EntityManager))), ent, user, PopupType.SmallCaution);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
_audio.PlayPredicted(ent.Comp.WakeAttemptSound, ent, user);
|
||||
_popupSystem.PopupClient(Loc.GetString("wake-other-success", ("target", Identity.Entity(ent, EntityManager))), ent, user);
|
||||
}
|
||||
|
||||
Wake((ent, ent.Comp));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public sealed partial class SleepActionEvent : InstantActionEvent;
|
||||
|
||||
public sealed partial class WakeActionEvent : InstantActionEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity when they fall asleep or wake up.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct SleepStateChangedEvent(bool FellAsleep);
|
||||
12
Content.Shared/Bed/Sleep/SnoringComponent.cs
Normal file
12
Content.Shared/Bed/Sleep/SnoringComponent.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Bed.Sleep;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for the snoring trait.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class SnoringComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -124,6 +124,18 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<float>
|
||||
EventsRampingAverageChaos = CVarDef.Create("events.ramping_average_chaos", 6f, CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Minimum time between meteor swarms in minutes.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float>
|
||||
MeteorSwarmMinTime = CVarDef.Create("events.meteor_swarm_min_time", 7.5f, CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum time between meteor swarms in minutes.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float>
|
||||
MeteorSwarmMaxTime = CVarDef.Create("events.meteor_swarm_max_time", 12.5f, CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
|
||||
/*
|
||||
* Game
|
||||
*/
|
||||
@@ -1428,6 +1440,18 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<bool> ArrivalsReturns =
|
||||
CVarDef.Create("shuttle.arrivals_returns", false, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Should all players be forced to spawn at departures, even on roundstart, even if their loadout says they spawn in cryo?
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> ForceArrivals =
|
||||
CVarDef.Create("shuttle.force_arrivals", false, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Should all players who spawn at arrivals have godmode until they leave the map?
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> GodmodeArrivals =
|
||||
CVarDef.Create("shuttle.godmode_arrivals", false, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Whether to automatically spawn escape shuttles.
|
||||
/// </summary>
|
||||
@@ -1847,7 +1871,7 @@ namespace Content.Shared.CCVar
|
||||
/// Don't show rules to localhost/loopback interface.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> RulesExemptLocal =
|
||||
CVarDef.Create("rules.exempt_local", false, CVar.SERVERONLY);
|
||||
CVarDef.Create("rules.exempt_local", true, CVar.SERVERONLY);
|
||||
|
||||
|
||||
/*
|
||||
@@ -1927,6 +1951,12 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<float> GhostRoleTime =
|
||||
CVarDef.Create("ghost.role_time", 3f, CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to kill the player's mob on ghosting, when it is in a critical health state.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> GhostKillCrit =
|
||||
CVarDef.Create("ghost.kill_crit", true, CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
/*
|
||||
* Fire alarm
|
||||
*/
|
||||
|
||||
@@ -193,7 +193,7 @@ public sealed class SolutionTransferSystem : EntitySystem
|
||||
var actualAmount = FixedPoint2.Min(amount, FixedPoint2.Min(sourceSolution.Volume, targetSolution.AvailableVolume));
|
||||
|
||||
var solution = _solution.SplitSolution(source, actualAmount);
|
||||
_solution.Refill(targetEntity, target, solution);
|
||||
_solution.AddSolution(target, solution);
|
||||
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(user):player} transferred {SharedSolutionContainerSystem.ToPrettyString(solution)} to {ToPrettyString(targetEntity):target}, which now contains {SharedSolutionContainerSystem.ToPrettyString(targetSolution)}");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Database;
|
||||
@@ -28,7 +28,7 @@ namespace Content.Shared.Chemistry.Reagent
|
||||
|
||||
public virtual string ReagentEffectFormat => "guidebook-reagent-effect-description";
|
||||
|
||||
protected abstract string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys); // => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability));
|
||||
protected abstract string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys);
|
||||
|
||||
/// <summary>
|
||||
/// What's the chance, from 0 to 1, that this effect will occur?
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Clothing.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for clothing that makes an entity weightless when worn.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class AntiGravityClothingComponent : Component;
|
||||
@@ -16,9 +16,14 @@ namespace Content.Shared.Clothing.Components;
|
||||
public sealed partial class ClothingComponent : Component
|
||||
{
|
||||
[DataField("clothingVisuals")]
|
||||
[Access(typeof(ClothingSystem), typeof(InventorySystem), Other = AccessPermissions.ReadExecute)] // TODO remove execute permissions.
|
||||
public Dictionary<string, List<PrototypeLayerData>> ClothingVisuals = new();
|
||||
|
||||
/// <summary>
|
||||
/// The name of the layer in the user that this piece of clothing will map to
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? MappedLayer;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("quickEquip")]
|
||||
public bool QuickEquip = true;
|
||||
@@ -124,4 +129,3 @@ public sealed partial class ClothingUnequipDoAfterEvent : DoAfterEvent
|
||||
|
||||
public override DoAfterEvent Clone() => this;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.NPC.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Clothing.Components;
|
||||
|
||||
/// <summary>
|
||||
/// When equipped, adds the wearer to a faction.
|
||||
/// When removed, removes the wearer from a faction.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(FactionClothingSystem))]
|
||||
public sealed partial class FactionClothingComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Faction to add and remove.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ProtoId<NpcFactionPrototype> Faction = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the wearer was already part of the faction.
|
||||
/// This prevents wrongly removing them after removing the item.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool AlreadyMember;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Inventory;
|
||||
|
||||
namespace Content.Shared.Clothing.EntitySystems;
|
||||
|
||||
public sealed class AntiGravityClothingSystem : EntitySystem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<AntiGravityClothingComponent, InventoryRelayedEvent<IsWeightlessEvent>>(OnIsWeightless);
|
||||
}
|
||||
|
||||
private void OnIsWeightless(Entity<AntiGravityClothingComponent> ent, ref InventoryRelayedEvent<IsWeightlessEvent> args)
|
||||
{
|
||||
if (args.Args.Handled)
|
||||
return;
|
||||
|
||||
args.Args.Handled = true;
|
||||
args.Args.IsWeightless = true;
|
||||
}
|
||||
}
|
||||
@@ -92,26 +92,29 @@ public abstract class ClothingSystem : EntitySystem
|
||||
InventorySystem.InventorySlotEnumerator enumerator = _invSystem.GetSlotEnumerator(equipee);
|
||||
|
||||
bool shouldLayerShow = true;
|
||||
while (enumerator.NextItem(out EntityUid item))
|
||||
while (enumerator.NextItem(out EntityUid item, out SlotDefinition? slot))
|
||||
{
|
||||
if (TryComp(item, out HideLayerClothingComponent? comp))
|
||||
{
|
||||
if (comp.Slots.Contains(layer))
|
||||
{
|
||||
//Checks for mask toggling. TODO: Make a generic system for this
|
||||
if (comp.HideOnToggle && TryComp(item, out MaskComponent? mask) && TryComp(item, out ClothingComponent? clothing))
|
||||
if (TryComp(item, out ClothingComponent? clothing) && clothing.Slots == slot.SlotFlags)
|
||||
{
|
||||
if (clothing.EquippedPrefix != mask.EquippedPrefix)
|
||||
//Checks for mask toggling. TODO: Make a generic system for this
|
||||
if (comp.HideOnToggle && TryComp(item, out MaskComponent? mask))
|
||||
{
|
||||
if (clothing.EquippedPrefix != mask.EquippedPrefix)
|
||||
{
|
||||
shouldLayerShow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldLayerShow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldLayerShow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,9 +241,6 @@ public abstract class ClothingSystem : EntitySystem
|
||||
|
||||
public void SetLayerColor(ClothingComponent clothing, string slot, string mapKey, Color? color)
|
||||
{
|
||||
if (clothing.ClothingVisuals == null)
|
||||
return;
|
||||
|
||||
foreach (var layer in clothing.ClothingVisuals[slot])
|
||||
{
|
||||
if (layer.MapKeys == null)
|
||||
@@ -254,9 +254,6 @@ public abstract class ClothingSystem : EntitySystem
|
||||
}
|
||||
public void SetLayerState(ClothingComponent clothing, string slot, string mapKey, string state)
|
||||
{
|
||||
if (clothing.ClothingVisuals == null)
|
||||
return;
|
||||
|
||||
foreach (var layer in clothing.ClothingVisuals[slot])
|
||||
{
|
||||
if (layer.MapKeys == null)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.NPC.Components;
|
||||
using Content.Shared.NPC.Systems;
|
||||
|
||||
namespace Content.Shared.Clothing.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles <see cref="FactionClothingComponent"/> faction adding and removal.
|
||||
/// </summary>
|
||||
public sealed class FactionClothingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly NpcFactionSystem _faction = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<FactionClothingComponent, GotEquippedEvent>(OnEquipped);
|
||||
SubscribeLocalEvent<FactionClothingComponent, GotUnequippedEvent>(OnUnequipped);
|
||||
}
|
||||
|
||||
private void OnEquipped(Entity<FactionClothingComponent> ent, ref GotEquippedEvent args)
|
||||
{
|
||||
TryComp<NpcFactionMemberComponent>(args.Equipee, out var factionComp);
|
||||
var faction = (args.Equipee, factionComp);
|
||||
ent.Comp.AlreadyMember = _faction.IsMember(faction, ent.Comp.Faction);
|
||||
|
||||
_faction.AddFaction(faction, ent.Comp.Faction);
|
||||
}
|
||||
|
||||
private void OnUnequipped(Entity<FactionClothingComponent> ent, ref GotUnequippedEvent args)
|
||||
{
|
||||
if (ent.Comp.AlreadyMember)
|
||||
{
|
||||
ent.Comp.AlreadyMember = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_faction.RemoveFaction(args.Equipee, ent.Comp.Faction);
|
||||
}
|
||||
}
|
||||
@@ -33,32 +33,32 @@ public sealed class FoldableClothingSystem : EntitySystem
|
||||
|
||||
private void OnFolded(Entity<FoldableClothingComponent> ent, ref FoldedEvent args)
|
||||
{
|
||||
if (TryComp<ClothingComponent>(ent.Owner, out var clothingComp) &&
|
||||
TryComp<ItemComponent>(ent.Owner, out var itemComp))
|
||||
if (!TryComp<ClothingComponent>(ent.Owner, out var clothingComp) ||
|
||||
!TryComp<ItemComponent>(ent.Owner, out var itemComp))
|
||||
return;
|
||||
|
||||
if (args.IsFolded)
|
||||
{
|
||||
if (args.IsFolded)
|
||||
{
|
||||
if (ent.Comp.FoldedSlots.HasValue)
|
||||
_clothingSystem.SetSlots(ent.Owner, ent.Comp.FoldedSlots.Value, clothingComp);
|
||||
if (ent.Comp.FoldedSlots.HasValue)
|
||||
_clothingSystem.SetSlots(ent.Owner, ent.Comp.FoldedSlots.Value, clothingComp);
|
||||
|
||||
if (ent.Comp.FoldedEquippedPrefix != null)
|
||||
_clothingSystem.SetEquippedPrefix(ent.Owner, ent.Comp.FoldedEquippedPrefix, clothingComp);
|
||||
if (ent.Comp.FoldedEquippedPrefix != null)
|
||||
_clothingSystem.SetEquippedPrefix(ent.Owner, ent.Comp.FoldedEquippedPrefix, clothingComp);
|
||||
|
||||
if (ent.Comp.FoldedHeldPrefix != null)
|
||||
_itemSystem.SetHeldPrefix(ent.Owner, ent.Comp.FoldedHeldPrefix, false, itemComp);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ent.Comp.UnfoldedSlots.HasValue)
|
||||
_clothingSystem.SetSlots(ent.Owner, ent.Comp.UnfoldedSlots.Value, clothingComp);
|
||||
if (ent.Comp.FoldedHeldPrefix != null)
|
||||
_itemSystem.SetHeldPrefix(ent.Owner, ent.Comp.FoldedHeldPrefix, false, itemComp);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ent.Comp.UnfoldedSlots.HasValue)
|
||||
_clothingSystem.SetSlots(ent.Owner, ent.Comp.UnfoldedSlots.Value, clothingComp);
|
||||
|
||||
if (ent.Comp.FoldedEquippedPrefix != null)
|
||||
_clothingSystem.SetEquippedPrefix(ent.Owner, null, clothingComp);
|
||||
if (ent.Comp.FoldedEquippedPrefix != null)
|
||||
_clothingSystem.SetEquippedPrefix(ent.Owner, null, clothingComp);
|
||||
|
||||
if (ent.Comp.FoldedHeldPrefix != null)
|
||||
_itemSystem.SetHeldPrefix(ent.Owner, null, false, itemComp);
|
||||
if (ent.Comp.FoldedHeldPrefix != null)
|
||||
_itemSystem.SetHeldPrefix(ent.Owner, null, false, itemComp);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Station;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -15,6 +18,7 @@ public sealed class LoadoutSystem : EntitySystem
|
||||
{
|
||||
// Shared so we can predict it for placement manager.
|
||||
|
||||
[Dependency] private readonly ActorSystem _actors = default!;
|
||||
[Dependency] private readonly SharedStationSpawningSystem _station = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
@@ -125,7 +129,17 @@ public sealed class LoadoutSystem : EntitySystem
|
||||
var id = _random.Pick(component.RoleLoadout);
|
||||
var proto = _protoMan.Index(id);
|
||||
var loadout = new RoleLoadout(id);
|
||||
loadout.SetDefault(_protoMan, true);
|
||||
loadout.SetDefault(GetProfile(uid), _actors.GetSession(uid), _protoMan, true);
|
||||
_station.EquipRoleLoadout(uid, loadout, proto);
|
||||
}
|
||||
|
||||
public HumanoidCharacterProfile GetProfile(EntityUid? uid)
|
||||
{
|
||||
if (TryComp(uid, out HumanoidAppearanceComponent? appearance))
|
||||
{
|
||||
return HumanoidCharacterProfile.DefaultWithSpecies(appearance.Species);
|
||||
}
|
||||
|
||||
return HumanoidCharacterProfile.Random();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,4 +20,10 @@ public sealed partial class MagbootsComponent : Component
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> MagbootsAlert = "Magboots";
|
||||
|
||||
/// <summary>
|
||||
/// If true, the user must be standing on a grid or planet map to experience the weightlessness-canceling effect
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool RequiresGrid = true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Slippery;
|
||||
@@ -9,10 +12,12 @@ using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared.Clothing;
|
||||
|
||||
public abstract class SharedMagbootsSystem : EntitySystem
|
||||
public sealed class SharedMagbootsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly ClothingSpeedModifierSystem _clothingSpeedModifier = default!;
|
||||
[Dependency] private readonly ClothingSystem _clothing = default!;
|
||||
[Dependency] private readonly SharedGravitySystem _gravity = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _sharedActions = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionContainer = default!;
|
||||
@@ -29,6 +34,11 @@ public abstract class SharedMagbootsSystem : EntitySystem
|
||||
SubscribeLocalEvent<MagbootsComponent, GetItemActionsEvent>(OnGetActions);
|
||||
SubscribeLocalEvent<MagbootsComponent, ToggleMagbootsEvent>(OnToggleMagboots);
|
||||
SubscribeLocalEvent<MagbootsComponent, MapInitEvent>(OnMapInit);
|
||||
|
||||
SubscribeLocalEvent<MagbootsComponent, ClothingGotEquippedEvent>(OnGotEquipped);
|
||||
SubscribeLocalEvent<MagbootsComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
|
||||
|
||||
SubscribeLocalEvent<MagbootsComponent, InventoryRelayedEvent<IsWeightlessEvent>>(OnIsWeightless);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, MagbootsComponent component, MapInitEvent args)
|
||||
@@ -37,6 +47,16 @@ public abstract class SharedMagbootsSystem : EntitySystem
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void OnGotUnequipped(EntityUid uid, MagbootsComponent component, ref ClothingGotUnequippedEvent args)
|
||||
{
|
||||
UpdateMagbootEffects(args.Wearer, uid, false, component);
|
||||
}
|
||||
|
||||
private void OnGotEquipped(EntityUid uid, MagbootsComponent component, ref ClothingGotEquippedEvent args)
|
||||
{
|
||||
UpdateMagbootEffects(args.Wearer, uid, true, component);
|
||||
}
|
||||
|
||||
private void OnToggleMagboots(EntityUid uid, MagbootsComponent component, ToggleMagbootsEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
@@ -51,9 +71,11 @@ public abstract class SharedMagbootsSystem : EntitySystem
|
||||
{
|
||||
magboots.On = !magboots.On;
|
||||
|
||||
if (_sharedContainer.TryGetContainingContainer(uid, out var container) &&
|
||||
if (_sharedContainer.TryGetContainingContainer((uid, Transform(uid)), out var container) &&
|
||||
_inventory.TryGetSlotEntity(container.Owner, "shoes", out var entityUid) && entityUid == uid)
|
||||
{
|
||||
UpdateMagbootEffects(container.Owner, uid, true, magboots);
|
||||
}
|
||||
|
||||
if (TryComp<ItemComponent>(uid, out var item))
|
||||
{
|
||||
@@ -66,9 +88,28 @@ public abstract class SharedMagbootsSystem : EntitySystem
|
||||
Dirty(uid, magboots);
|
||||
}
|
||||
|
||||
protected virtual void UpdateMagbootEffects(EntityUid parent, EntityUid uid, bool state, MagbootsComponent? component) { }
|
||||
public void UpdateMagbootEffects(EntityUid parent, EntityUid uid, bool state, MagbootsComponent? component)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
state = state && component.On;
|
||||
|
||||
protected void OnChanged(EntityUid uid, MagbootsComponent component)
|
||||
if (TryComp(parent, out MovedByPressureComponent? movedByPressure))
|
||||
{
|
||||
movedByPressure.Enabled = !state;
|
||||
}
|
||||
|
||||
if (state)
|
||||
{
|
||||
_alerts.ShowAlert(parent, component.MagbootsAlert);
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlert(parent, component.MagbootsAlert);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnChanged(EntityUid uid, MagbootsComponent component)
|
||||
{
|
||||
_sharedActions.SetToggled(component.ToggleActionEntity, component.On);
|
||||
_clothingSpeedModifier.SetClothingSpeedModifierEnabled(uid, component.On);
|
||||
@@ -79,10 +120,12 @@ public abstract class SharedMagbootsSystem : EntitySystem
|
||||
if (!args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
ActivationVerb verb = new();
|
||||
verb.Text = Loc.GetString("toggle-magboots-verb-get-data-text");
|
||||
verb.Act = () => ToggleMagboots(uid, component);
|
||||
// TODO VERB ICON add toggle icon? maybe a computer on/off symbol?
|
||||
ActivationVerb verb = new()
|
||||
{
|
||||
Text = Loc.GetString("toggle-magboots-verb-get-data-text"),
|
||||
Act = () => ToggleMagboots(uid, component),
|
||||
// TODO VERB ICON add toggle icon? maybe a computer on/off symbol?
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
@@ -96,6 +139,22 @@ public abstract class SharedMagbootsSystem : EntitySystem
|
||||
{
|
||||
args.AddAction(ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
|
||||
private void OnIsWeightless(Entity<MagbootsComponent> ent, ref InventoryRelayedEvent<IsWeightlessEvent> args)
|
||||
{
|
||||
if (args.Args.Handled)
|
||||
return;
|
||||
|
||||
if (!ent.Comp.On)
|
||||
return;
|
||||
|
||||
// do not cancel weightlessness if the person is in off-grid.
|
||||
if (ent.Comp.RequiresGrid && !_gravity.EntityOnGravitySupportingGridOrMap(ent.Owner))
|
||||
return;
|
||||
|
||||
args.Args.IsWeightless = false;
|
||||
args.Args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class ToggleMagbootsEvent : InstantActionEvent {}
|
||||
public sealed partial class ToggleMagbootsEvent : InstantActionEvent;
|
||||
|
||||
@@ -105,3 +105,9 @@ public record struct UncuffAttemptEvent(EntityUid User, EntityUid Target)
|
||||
public readonly EntityUid Target = Target;
|
||||
public bool Cancelled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised on an entity being uncuffed to determine any modifiers to the amount of time it takes to uncuff them.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct ModifyUncuffDurationEvent(EntityUid User, EntityUid Target, float Duration);
|
||||
|
||||
@@ -561,7 +561,10 @@ namespace Content.Shared.Cuffs
|
||||
return;
|
||||
}
|
||||
|
||||
var uncuffTime = isOwner ? cuff.BreakoutTime : cuff.UncuffTime;
|
||||
|
||||
var ev = new ModifyUncuffDurationEvent(user, target, isOwner ? cuff.BreakoutTime : cuff.UncuffTime);
|
||||
RaiseLocalEvent(user, ref ev);
|
||||
var uncuffTime = ev.Duration;
|
||||
|
||||
if (isOwner)
|
||||
{
|
||||
|
||||
@@ -23,6 +23,13 @@ public sealed partial class GameRuleComponent : Component
|
||||
[DataField]
|
||||
public int MinPlayers;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this rule not having enough players will cancel the preset selection.
|
||||
/// If false, it will simply not run silently.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool CancelPresetOnTooFewPlayers = true;
|
||||
|
||||
/// <summary>
|
||||
/// A delay for when the rule the is started and when the starting logic actually runs.
|
||||
/// </summary>
|
||||
|
||||
@@ -6,11 +6,6 @@ namespace Content.Shared.Glue;
|
||||
[Access(typeof(SharedGlueSystem))]
|
||||
public sealed partial class GluedComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Reverts name to before prefix event (essentially removes prefix).
|
||||
/// </summary>
|
||||
[DataField("beforeGluedEntityName"), ViewVariables(VVAccess.ReadOnly)]
|
||||
public string BeforeGluedEntityName = string.Empty;
|
||||
|
||||
[DataField("until", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan Until;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -15,11 +13,12 @@ namespace Content.Shared.Gravity
|
||||
{
|
||||
[Dependency] protected readonly IGameTiming Timing = default!;
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
|
||||
[ValidatePrototypeId<AlertPrototype>]
|
||||
public const string WeightlessAlert = "Weightless";
|
||||
|
||||
private EntityQuery<GravityComponent> _gravityQuery;
|
||||
|
||||
public bool IsWeightless(EntityUid uid, PhysicsComponent? body = null, TransformComponent? xform = null)
|
||||
{
|
||||
Resolve(uid, ref body, false);
|
||||
@@ -30,31 +29,44 @@ namespace Content.Shared.Gravity
|
||||
if (TryComp<MovementIgnoreGravityComponent>(uid, out var ignoreGravityComponent))
|
||||
return ignoreGravityComponent.Weightless;
|
||||
|
||||
var ev = new IsWeightlessEvent(uid);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
if (ev.Handled)
|
||||
return ev.IsWeightless;
|
||||
|
||||
if (!Resolve(uid, ref xform))
|
||||
return true;
|
||||
|
||||
// If grid / map has gravity
|
||||
if (TryComp<GravityComponent>(xform.GridUid, out var gravity) && gravity.Enabled ||
|
||||
TryComp<GravityComponent>(xform.MapUid, out var mapGravity) && mapGravity.Enabled)
|
||||
{
|
||||
if (EntityGridOrMapHaveGravity((uid, xform)))
|
||||
return false;
|
||||
}
|
||||
|
||||
var hasGrav = gravity != null || mapGravity != null;
|
||||
|
||||
// Check for something holding us down
|
||||
// If the planet has gravity component and no gravity it will still give gravity
|
||||
// If there's no gravity comp at all (i.e. space) then they don't work.
|
||||
if (hasGrav && _inventory.TryGetSlotEntity(uid, "shoes", out var ent))
|
||||
{
|
||||
// TODO this should just be a event that gets relayed instead of a specific slot & component check.
|
||||
if (TryComp<MagbootsComponent>(ent, out var boots) && boots.On)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given entity is currently standing on a grid or map that supports having gravity at all.
|
||||
/// </summary>
|
||||
public bool EntityOnGravitySupportingGridOrMap(Entity<TransformComponent?> entity)
|
||||
{
|
||||
entity.Comp ??= Transform(entity);
|
||||
|
||||
return _gravityQuery.HasComp(entity.Comp.GridUid) ||
|
||||
_gravityQuery.HasComp(entity.Comp.MapUid);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given entity is currently standing on a grid or map that has gravity of some kind.
|
||||
/// </summary>
|
||||
public bool EntityGridOrMapHaveGravity(Entity<TransformComponent?> entity)
|
||||
{
|
||||
entity.Comp ??= Transform(entity);
|
||||
|
||||
return _gravityQuery.TryComp(entity.Comp.GridUid, out var gravity) && gravity.Enabled ||
|
||||
_gravityQuery.TryComp(entity.Comp.MapUid, out var mapGravity) && mapGravity.Enabled;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -64,6 +76,8 @@ namespace Content.Shared.Gravity
|
||||
SubscribeLocalEvent<GravityChangedEvent>(OnGravityChange);
|
||||
SubscribeLocalEvent<GravityComponent, ComponentGetState>(OnGetState);
|
||||
SubscribeLocalEvent<GravityComponent, ComponentHandleState>(OnHandleState);
|
||||
|
||||
_gravityQuery = GetEntityQuery<GravityComponent>();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
@@ -74,9 +88,11 @@ namespace Content.Shared.Gravity
|
||||
|
||||
private void OnHandleState(EntityUid uid, GravityComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not GravityComponentState state) return;
|
||||
if (args.Current is not GravityComponentState state)
|
||||
return;
|
||||
|
||||
if (component.EnabledVV == state.Enabled) return;
|
||||
if (component.EnabledVV == state.Enabled)
|
||||
return;
|
||||
component.EnabledVV = state.Enabled;
|
||||
var ev = new GravityChangedEvent(uid, component.EnabledVV);
|
||||
RaiseLocalEvent(uid, ref ev, true);
|
||||
@@ -90,9 +106,10 @@ namespace Content.Shared.Gravity
|
||||
private void OnGravityChange(ref GravityChangedEvent ev)
|
||||
{
|
||||
var alerts = AllEntityQuery<AlertsComponent, TransformComponent>();
|
||||
while(alerts.MoveNext(out var uid, out var comp, out var xform))
|
||||
while(alerts.MoveNext(out var uid, out _, out var xform))
|
||||
{
|
||||
if (xform.GridUid != ev.ChangedGridIndex) continue;
|
||||
if (xform.GridUid != ev.ChangedGridIndex)
|
||||
continue;
|
||||
|
||||
if (!ev.HasGravity)
|
||||
{
|
||||
@@ -145,4 +162,10 @@ namespace Content.Shared.Gravity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct IsWeightlessEvent(EntityUid Entity, bool IsWeightless = false, bool Handled = false) : IInventoryRelayEvent
|
||||
{
|
||||
SlotFlags IInventoryRelayEvent.TargetSlots => ~SlotFlags.POCKET;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,22 +5,28 @@ using Robust.Shared.Serialization;
|
||||
namespace Content.Shared.Info;
|
||||
|
||||
/// <summary>
|
||||
/// Sent by the server to show the rules to the client instantly.
|
||||
/// Sent by the server when the client connects to sync the client rules and displaying a popup with them if necessitated.
|
||||
/// </summary>
|
||||
public sealed class ShowRulesPopupMessage : NetMessage
|
||||
public sealed class SendRulesInformationMessage : NetMessage
|
||||
{
|
||||
public override MsgGroups MsgGroup => MsgGroups.Command;
|
||||
|
||||
public float PopupTime { get; set; }
|
||||
public string CoreRules { get; set; } = string.Empty;
|
||||
public bool ShouldShowRules { get; set; }
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
PopupTime = buffer.ReadFloat();
|
||||
CoreRules = buffer.ReadString();
|
||||
ShouldShowRules = buffer.ReadBoolean();
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
buffer.Write(PopupTime);
|
||||
buffer.Write(CoreRules);
|
||||
buffer.Write(ShouldShowRules);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,11 @@ using Content.Shared.Damage;
|
||||
using Content.Shared.Electrocution;
|
||||
using Content.Shared.Explosion;
|
||||
using Content.Shared.Eye.Blinding.Systems;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.IdentityManagement.Components;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Content.Shared.Overlays;
|
||||
using Content.Shared.Radio;
|
||||
using Content.Shared.Slippery;
|
||||
@@ -28,9 +30,11 @@ public partial class InventorySystem
|
||||
SubscribeLocalEvent<InventoryComponent, SeeIdentityAttemptEvent>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, ModifyChangedTemperatureEvent>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, GetDefaultRadioChannelEvent>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshNameModifiersEvent>(RelayInventoryEvent);
|
||||
|
||||
// by-ref events
|
||||
SubscribeLocalEvent<InventoryComponent, GetExplosionResistanceEvent>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, IsWeightlessEvent>(RefRelayInventoryEvent);
|
||||
|
||||
// Eye/vision events
|
||||
SubscribeLocalEvent<InventoryComponent, CanSeeAttemptEvent>(RelayInventoryEvent);
|
||||
|
||||
@@ -14,11 +14,4 @@ public sealed partial class LabelComponent : Component
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public string? CurrentLabel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The original name of the entity
|
||||
/// Used for reverting the modified entity name when the label is removed
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public string? OriginalName { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Labels.Components;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Labels.EntitySystems;
|
||||
@@ -11,6 +12,7 @@ public abstract partial class SharedLabelSystem : EntitySystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<LabelComponent, ExaminedEvent>(OnExamine);
|
||||
SubscribeLocalEvent<LabelComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
|
||||
}
|
||||
|
||||
public virtual void Label(EntityUid uid, string? text, MetaDataComponent? metadata = null, LabelComponent? label = null){}
|
||||
@@ -27,4 +29,10 @@ public abstract partial class SharedLabelSystem : EntitySystem
|
||||
message.AddText(Loc.GetString("hand-labeler-has-label", ("label", label.CurrentLabel)));
|
||||
args.PushMessage(message);
|
||||
}
|
||||
|
||||
private void OnRefreshNameModifiers(Entity<LabelComponent> entity, ref RefreshNameModifiersEvent args)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(entity.Comp.CurrentLabel))
|
||||
args.AddModifier("comp-label-format", extraArgs: ("label", entity.Comp.CurrentLabel));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +129,20 @@ namespace Content.Shared.Localizations
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a list as per english grammar rules, but uses or instead of and.
|
||||
/// </summary>
|
||||
public static string FormatListToOr(List<string> list)
|
||||
{
|
||||
return list.Count switch
|
||||
{
|
||||
<= 0 => string.Empty,
|
||||
1 => list[0],
|
||||
2 => $"{list[0]} or {list[1]}",
|
||||
_ => $"{string.Join(" or ", list)}"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a direction struct as a human-readable string.
|
||||
/// </summary>
|
||||
|
||||
@@ -3,12 +3,6 @@ namespace Content.Shared.Lube;
|
||||
[RegisterComponent]
|
||||
public sealed partial class LubedComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Reverts name to before prefix event (essentially removes prefix).
|
||||
/// </summary>
|
||||
[DataField("beforeLubedEntityName")]
|
||||
public string BeforeLubedEntityName = string.Empty;
|
||||
|
||||
[DataField("slipsLeft"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public int SlipsLeft;
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ public partial class MobStateSystem
|
||||
/// <returns>If the entity can be set to that MobState</returns>
|
||||
public bool HasState(EntityUid entity, MobState mobState, MobStateComponent? component = null)
|
||||
{
|
||||
return Resolve(entity, ref component, false) && component.AllowedStates.Contains(mobState);
|
||||
return _mobStateQuery.Resolve(entity, ref component, false) &&
|
||||
component.AllowedStates.Contains(mobState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -27,7 +28,7 @@ public partial class MobStateSystem
|
||||
/// <param name="origin">Entity that caused the state update (if applicable)</param>
|
||||
public void UpdateMobState(EntityUid entity, MobStateComponent? component = null, EntityUid? origin = null)
|
||||
{
|
||||
if (!Resolve(entity, ref component))
|
||||
if (!_mobStateQuery.Resolve(entity, ref component))
|
||||
return;
|
||||
|
||||
var ev = new UpdateMobStateEvent {Target = entity, Component = component, Origin = origin};
|
||||
@@ -46,7 +47,7 @@ public partial class MobStateSystem
|
||||
public void ChangeMobState(EntityUid entity, MobState mobState, MobStateComponent? component = null,
|
||||
EntityUid? origin = null)
|
||||
{
|
||||
if (!Resolve(entity, ref component))
|
||||
if (!_mobStateQuery.Resolve(entity, ref component))
|
||||
return;
|
||||
|
||||
ChangeState(entity, component, mobState, origin: origin);
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Standing;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -20,9 +19,12 @@ public partial class MobStateSystem : EntitySystem
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private EntityQuery<MobStateComponent> _mobStateQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_sawmill = _logManager.GetSawmill("MobState");
|
||||
_mobStateQuery = GetEntityQuery<MobStateComponent>();
|
||||
base.Initialize();
|
||||
SubscribeEvents();
|
||||
}
|
||||
@@ -37,7 +39,7 @@ public partial class MobStateSystem : EntitySystem
|
||||
/// <returns>If the entity is alive</returns>
|
||||
public bool IsAlive(EntityUid target, MobStateComponent? component = null)
|
||||
{
|
||||
if (!Resolve(target, ref component, false))
|
||||
if (!_mobStateQuery.Resolve(target, ref component, false))
|
||||
return false;
|
||||
return component.CurrentState == MobState.Alive;
|
||||
}
|
||||
@@ -50,7 +52,7 @@ public partial class MobStateSystem : EntitySystem
|
||||
/// <returns>If the entity is Critical</returns>
|
||||
public bool IsCritical(EntityUid target, MobStateComponent? component = null)
|
||||
{
|
||||
if (!Resolve(target, ref component, false))
|
||||
if (!_mobStateQuery.Resolve(target, ref component, false))
|
||||
return false;
|
||||
return component.CurrentState == MobState.Critical;
|
||||
}
|
||||
@@ -63,7 +65,7 @@ public partial class MobStateSystem : EntitySystem
|
||||
/// <returns>If the entity is Dead</returns>
|
||||
public bool IsDead(EntityUid target, MobStateComponent? component = null)
|
||||
{
|
||||
if (!Resolve(target, ref component, false))
|
||||
if (!_mobStateQuery.Resolve(target, ref component, false))
|
||||
return false;
|
||||
return component.CurrentState == MobState.Dead;
|
||||
}
|
||||
@@ -76,7 +78,7 @@ public partial class MobStateSystem : EntitySystem
|
||||
/// <returns>If the entity is Critical or Dead</returns>
|
||||
public bool IsIncapacitated(EntityUid target, MobStateComponent? component = null)
|
||||
{
|
||||
if (!Resolve(target, ref component, false))
|
||||
if (!_mobStateQuery.Resolve(target, ref component, false))
|
||||
return false;
|
||||
return component.CurrentState is MobState.Critical or MobState.Dead;
|
||||
}
|
||||
@@ -89,14 +91,10 @@ public partial class MobStateSystem : EntitySystem
|
||||
/// <returns>If the entity is in an Invalid State</returns>
|
||||
public bool IsInvalidState(EntityUid target, MobStateComponent? component = null)
|
||||
{
|
||||
if (!Resolve(target, ref component, false))
|
||||
if (!_mobStateQuery.Resolve(target, ref component, false))
|
||||
return false;
|
||||
return component.CurrentState is MobState.Invalid;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Implementation
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ public sealed class MobThresholdSystem : EntitySystem
|
||||
MobThresholdsComponent? thresholdComponent = null)
|
||||
{
|
||||
threshold = null;
|
||||
if (!Resolve(target, ref thresholdComponent))
|
||||
if (!Resolve(target, ref thresholdComponent, false))
|
||||
return false;
|
||||
|
||||
return TryGetThresholdForState(target, MobState.Dead, out threshold, thresholdComponent);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Shared.Movement.Pulling.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component that indicates that an entity is currently pulling some other entity.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class ActivePullerComponent : Component;
|
||||
@@ -9,7 +9,7 @@ namespace Content.Shared.Movement.Pulling.Components;
|
||||
/// <summary>
|
||||
/// Specifies an entity as being able to pull another entity with <see cref="PullableComponent"/>
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||
[Access(typeof(PullingSystem))]
|
||||
public sealed partial class PullerComponent : Component
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Cuffs.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
@@ -12,6 +13,7 @@ using Content.Shared.Movement.Events;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Movement.Pulling.Events;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Pulling.Events;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.Throwing;
|
||||
@@ -43,6 +45,7 @@ public sealed class PullingSystem : EntitySystem
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -56,7 +59,9 @@ public sealed class PullingSystem : EntitySystem
|
||||
SubscribeLocalEvent<PullableComponent, JointRemovedEvent>(OnJointRemoved);
|
||||
SubscribeLocalEvent<PullableComponent, GetVerbsEvent<Verb>>(AddPullVerbs);
|
||||
SubscribeLocalEvent<PullableComponent, EntGotInsertedIntoContainerMessage>(OnPullableContainerInsert);
|
||||
SubscribeLocalEvent<PullableComponent, ModifyUncuffDurationEvent>(OnModifyUncuffDuration);
|
||||
|
||||
SubscribeLocalEvent<PullerComponent, AfterAutoHandleStateEvent>(OnAfterState);
|
||||
SubscribeLocalEvent<PullerComponent, EntGotInsertedIntoContainerMessage>(OnPullerContainerInsert);
|
||||
SubscribeLocalEvent<PullerComponent, EntityUnpausedEvent>(OnPullerUnpaused);
|
||||
SubscribeLocalEvent<PullerComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
|
||||
@@ -68,6 +73,14 @@ public sealed class PullingSystem : EntitySystem
|
||||
.Register<PullingSystem>();
|
||||
}
|
||||
|
||||
private void OnAfterState(Entity<PullerComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (ent.Comp.Pulling == null)
|
||||
RemComp<ActivePullerComponent>(ent.Owner);
|
||||
else
|
||||
EnsureComp<ActivePullerComponent>(ent.Owner);
|
||||
}
|
||||
|
||||
private void OnDropHandItems(EntityUid uid, PullerComponent pullerComp, DropHandItemsEvent args)
|
||||
{
|
||||
if (pullerComp.Pulling == null || pullerComp.NeedsHands)
|
||||
@@ -94,6 +107,18 @@ public sealed class PullingSystem : EntitySystem
|
||||
TryStopPull(ent.Owner, ent.Comp);
|
||||
}
|
||||
|
||||
private void OnModifyUncuffDuration(Entity<PullableComponent> ent, ref ModifyUncuffDurationEvent args)
|
||||
{
|
||||
if (!ent.Comp.BeingPulled)
|
||||
return;
|
||||
|
||||
// We don't care if the person is being uncuffed by someone else
|
||||
if (args.User != args.Target)
|
||||
return;
|
||||
|
||||
args.Duration *= 2;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
@@ -212,6 +237,9 @@ public sealed class PullingSystem : EntitySystem
|
||||
}
|
||||
|
||||
var oldPuller = pullableComp.Puller;
|
||||
if (oldPuller != null)
|
||||
RemComp<ActivePullerComponent>(oldPuller.Value);
|
||||
|
||||
pullableComp.PullJointId = null;
|
||||
pullableComp.Puller = null;
|
||||
Dirty(pullableUid, pullableComp);
|
||||
@@ -394,6 +422,7 @@ public sealed class PullingSystem : EntitySystem
|
||||
// Use net entity so it's consistent across client and server.
|
||||
pullableComp.PullJointId = $"pull-joint-{GetNetEntity(pullableUid)}";
|
||||
|
||||
EnsureComp<ActivePullerComponent>(pullerUid);
|
||||
pullerComp.Pulling = pullableUid;
|
||||
pullableComp.Puller = pullerUid;
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.NameModifier.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a modifier to the wearer's name when this item is equipped,
|
||||
/// and removes it when it is unequipped.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[AutoGenerateComponentState]
|
||||
public sealed partial class ModifyWearerNameComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The localization ID of the text to be used as the modifier.
|
||||
/// The base name will be passed in as <c>$baseName</c>
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public LocId LocId = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Priority of the modifier. See <see cref="EntitySystems.RefreshNameModifiersEvent"/> for more information.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public int Priority;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.NameModifier.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Used to manage modifiers on an entity's name and handle renaming in a way
|
||||
/// that survives being renamed by multiple systems.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(NameModifierSystem))]
|
||||
public sealed partial class NameModifierComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity's name without any modifiers applied.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public string BaseName = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.NameModifier.Components;
|
||||
|
||||
namespace Content.Shared.NameModifier.EntitySystems;
|
||||
|
||||
public sealed partial class ModifyWearerNameSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ModifyWearerNameComponent, InventoryRelayedEvent<RefreshNameModifiersEvent>>(OnRefreshNameModifiers);
|
||||
SubscribeLocalEvent<ModifyWearerNameComponent, ClothingGotEquippedEvent>(OnGotEquipped);
|
||||
SubscribeLocalEvent<ModifyWearerNameComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
|
||||
}
|
||||
|
||||
private void OnGotEquipped(Entity<ModifyWearerNameComponent> entity, ref ClothingGotEquippedEvent args)
|
||||
{
|
||||
_nameMod.RefreshNameModifiers(args.Wearer);
|
||||
}
|
||||
|
||||
private void OnGotUnequipped(Entity<ModifyWearerNameComponent> entity, ref ClothingGotUnequippedEvent args)
|
||||
{
|
||||
_nameMod.RefreshNameModifiers(args.Wearer);
|
||||
}
|
||||
|
||||
private void OnRefreshNameModifiers(Entity<ModifyWearerNameComponent> entity, ref InventoryRelayedEvent<RefreshNameModifiersEvent> args)
|
||||
{
|
||||
args.Args.AddModifier(entity.Comp.LocId, entity.Comp.Priority);
|
||||
}
|
||||
}
|
||||
143
Content.Shared/NameModifier/EntitySystems/NameModifierSystem.cs
Normal file
143
Content.Shared/NameModifier/EntitySystems/NameModifierSystem.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.NameModifier.Components;
|
||||
|
||||
namespace Content.Shared.NameModifier.EntitySystems;
|
||||
|
||||
/// <inheritdoc cref="NameModifierComponent"/>
|
||||
public sealed partial class NameModifierSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<NameModifierComponent, EntityRenamedEvent>(OnEntityRenamed);
|
||||
}
|
||||
|
||||
private void OnEntityRenamed(Entity<NameModifierComponent> entity, ref EntityRenamedEvent args)
|
||||
{
|
||||
SetBaseName((entity, entity.Comp), args.NewName);
|
||||
RefreshNameModifiers((entity, entity.Comp));
|
||||
}
|
||||
|
||||
private void SetBaseName(Entity<NameModifierComponent> entity, string name)
|
||||
{
|
||||
if (name == entity.Comp.BaseName)
|
||||
return;
|
||||
|
||||
// Set the base name to the new name
|
||||
entity.Comp.BaseName = name;
|
||||
Dirty(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises a <see cref="RefreshNameModifiersEvent"/> to gather modifiers and
|
||||
/// updates the entity's name to its base name with modifiers applied.
|
||||
/// This will add a <see cref="NameModifierComponent"/> if any modifiers are added.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Call this to update the entity's name when adding or removing a modifier.
|
||||
/// </remarks>
|
||||
public void RefreshNameModifiers(Entity<NameModifierComponent?> entity)
|
||||
{
|
||||
var meta = MetaData(entity);
|
||||
var baseName = meta.EntityName;
|
||||
if (Resolve(entity, ref entity.Comp, logMissing: false))
|
||||
baseName = entity.Comp.BaseName;
|
||||
|
||||
// Raise an event to get any modifiers
|
||||
// If the entity already has the component, use its BaseName, otherwise use the entity's name from metadata
|
||||
var modifierEvent = new RefreshNameModifiersEvent(baseName);
|
||||
RaiseLocalEvent(entity, ref modifierEvent);
|
||||
|
||||
// Nothing added a modifier, so we can just use the base name
|
||||
if (modifierEvent.ModifierCount == 0)
|
||||
{
|
||||
// If the entity doesn't have the component, we're done
|
||||
if (entity.Comp == null)
|
||||
return;
|
||||
|
||||
// Restore the base name
|
||||
_metaData.SetEntityName(entity, entity.Comp.BaseName, meta, raiseEvents: false);
|
||||
// The component isn't doing anything anymore, so remove it
|
||||
RemComp<NameModifierComponent>(entity);
|
||||
return;
|
||||
}
|
||||
// We have at least one modifier, so we need to apply it to the entity.
|
||||
|
||||
// Get the final name with modifiers applied
|
||||
var modifiedName = modifierEvent.GetModifiedName();
|
||||
|
||||
// Add the component if needed, and initialize it with the base name
|
||||
if (!EnsureComp<NameModifierComponent>(entity, out var comp))
|
||||
SetBaseName((entity, comp), meta.EntityName);
|
||||
|
||||
// Set the entity's name with modifiers applied
|
||||
_metaData.SetEntityName(entity, modifiedName, meta, raiseEvents: false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity when <see cref="NameModifierSystem.RefreshNameModifiers"/> is called.
|
||||
/// Subscribe to this event and use its methods to add modifiers to the entity's name.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public sealed class RefreshNameModifiersEvent : IInventoryRelayEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity's name without any modifiers applied.
|
||||
/// If you want to base a modifier on the entity's name, use
|
||||
/// this so you don't include other modifiers.
|
||||
/// </summary>
|
||||
public readonly string BaseName;
|
||||
|
||||
private readonly List<(LocId LocId, int Priority, (string, object)[] ExtraArgs)> _modifiers = [];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SlotFlags TargetSlots => ~SlotFlags.POCKET;
|
||||
|
||||
/// <summary>
|
||||
/// How many modifiers have been added to this event.
|
||||
/// </summary>
|
||||
public int ModifierCount => _modifiers.Count;
|
||||
|
||||
public RefreshNameModifiersEvent(string baseName)
|
||||
{
|
||||
BaseName = baseName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a modifier to the entity's name.
|
||||
/// The original name will be passed to Fluent as <c>$baseName</c> along with any <paramref name="extraArgs"/>.
|
||||
/// Modifiers with a higher <paramref name="priority"/> will be applied later.
|
||||
/// </summary>
|
||||
public void AddModifier(LocId locId, int priority = 0, params (string, object)[] extraArgs)
|
||||
{
|
||||
_modifiers.Add((locId, priority, extraArgs));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the final name with all modifiers applied.
|
||||
/// </summary>
|
||||
public string GetModifiedName()
|
||||
{
|
||||
// Start out with the entity's name name
|
||||
var name = BaseName;
|
||||
|
||||
// Iterate through all the modifiers in priority order
|
||||
foreach (var modifier in _modifiers.OrderBy(n => n.Priority))
|
||||
{
|
||||
// Grab any extra args needed by the Loc string
|
||||
var args = modifier.ExtraArgs;
|
||||
// Add the current version of the entity name as an arg
|
||||
Array.Resize(ref args, args.Length + 1);
|
||||
args[^1] = ("baseName", name);
|
||||
// Resolve the Loc string and use the result as the base in the next iteration.
|
||||
name = Loc.GetString(modifier.LocId, args);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -35,10 +35,4 @@ public sealed partial class InfantComponent : Component
|
||||
[DataField("infantEndTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
public TimeSpan InfantEndTime;
|
||||
|
||||
/// <summary>
|
||||
/// The entity's name before the "baby" prefix is added.
|
||||
/// </summary>
|
||||
[DataField("originalName")]
|
||||
public string OriginalName = string.Empty;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ public sealed partial class OpenableSystem : EntitySystem
|
||||
if (args.Handled || !ent.Comp.OpenableByHand)
|
||||
return;
|
||||
|
||||
args.Handled = TryToggle(ent, args.User);
|
||||
args.Handled = TryOpen(ent, ent, args.User);
|
||||
}
|
||||
|
||||
private void OnActivated(Entity<OpenableComponent> ent, ref ActivateInWorldEvent args)
|
||||
|
||||
@@ -690,15 +690,15 @@ namespace Content.Shared.Preferences
|
||||
return profile;
|
||||
}
|
||||
|
||||
public RoleLoadout GetLoadoutOrDefault(string id, ProtoId<SpeciesPrototype>? species, IEntityManager entManager, IPrototypeManager protoManager)
|
||||
public RoleLoadout GetLoadoutOrDefault(string id, ICommonSession? session, ProtoId<SpeciesPrototype>? species, IEntityManager entManager, IPrototypeManager protoManager)
|
||||
{
|
||||
if (!_loadouts.TryGetValue(id, out var loadout))
|
||||
{
|
||||
loadout = new RoleLoadout(id);
|
||||
loadout.SetDefault(protoManager, force: true);
|
||||
loadout.SetDefault(this, session, protoManager, force: true);
|
||||
}
|
||||
|
||||
loadout.SetDefault(protoManager);
|
||||
loadout.SetDefault(this, session, protoManager);
|
||||
return loadout;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,17 +13,20 @@ public sealed partial class GroupLoadoutEffect : LoadoutEffect
|
||||
[DataField(required: true)]
|
||||
public ProtoId<LoadoutEffectGroupPrototype> Proto;
|
||||
|
||||
public override bool Validate(HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
|
||||
public override bool Validate(HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession? session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
|
||||
{
|
||||
var effectsProto = collection.Resolve<IPrototypeManager>().Index(Proto);
|
||||
|
||||
var reasons = new List<string>();
|
||||
foreach (var effect in effectsProto.Effects)
|
||||
{
|
||||
if (!effect.Validate(profile, loadout, session, collection, out reason))
|
||||
return false;
|
||||
if (effect.Validate(profile, loadout, session, collection, out reason))
|
||||
continue;
|
||||
|
||||
reasons.Add(reason.ToMarkup());
|
||||
}
|
||||
|
||||
reason = null;
|
||||
return true;
|
||||
reason = reasons.Count == 0 ? null : FormattedMessage.FromMarkup(string.Join('\n', reasons));
|
||||
return reason == null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,14 @@ public sealed partial class JobRequirementLoadoutEffect : LoadoutEffect
|
||||
[DataField(required: true)]
|
||||
public JobRequirement Requirement = default!;
|
||||
|
||||
public override bool Validate(HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
|
||||
public override bool Validate(HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession? session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
|
||||
{
|
||||
if (session == null)
|
||||
{
|
||||
reason = FormattedMessage.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
var manager = collection.Resolve<ISharedPlaytimeManager>();
|
||||
var playtimes = manager.GetPlayTimes(session);
|
||||
return JobRequirements.TryRequirementMet(Requirement, playtimes, out reason,
|
||||
|
||||
@@ -13,7 +13,7 @@ public abstract partial class LoadoutEffect
|
||||
public abstract bool Validate(
|
||||
HumanoidCharacterProfile profile,
|
||||
RoleLoadout loadout,
|
||||
ICommonSession session,
|
||||
ICommonSession? session,
|
||||
IDependencyCollection collection,
|
||||
[NotNullWhen(false)] out FormattedMessage? reason);
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ public sealed partial class PointsCostLoadoutEffect : LoadoutEffect
|
||||
public override bool Validate(
|
||||
HumanoidCharacterProfile profile,
|
||||
RoleLoadout loadout,
|
||||
ICommonSession session,
|
||||
ICommonSession? session,
|
||||
IDependencyCollection collection,
|
||||
[NotNullWhen(false)] out FormattedMessage? reason)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed partial class SpeciesLoadoutEffect : LoadoutEffect
|
||||
[DataField(required: true)]
|
||||
public List<ProtoId<SpeciesPrototype>> Species = new();
|
||||
|
||||
public override bool Validate(HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection,
|
||||
public override bool Validate(HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession? session, IDependencyCollection collection,
|
||||
[NotNullWhen(false)] out FormattedMessage? reason)
|
||||
{
|
||||
if (Species.Contains(profile.Species))
|
||||
|
||||
@@ -7,8 +7,25 @@ namespace Content.Shared.Preferences.Loadouts;
|
||||
/// Specifies the selected prototype and custom data for a loadout.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable, DataDefinition]
|
||||
public sealed partial class Loadout
|
||||
public sealed partial class Loadout : IEquatable<Loadout>
|
||||
{
|
||||
[DataField]
|
||||
public ProtoId<LoadoutPrototype> Prototype;
|
||||
|
||||
public bool Equals(Loadout? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Prototype.Equals(other.Prototype);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return ReferenceEquals(this, obj) || obj is Loadout other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Prototype.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,12 +166,15 @@ public sealed partial class RoleLoadout : IEquatable<RoleLoadout>
|
||||
/// <summary>
|
||||
/// Resets the selected loadouts to default if no data is present.
|
||||
/// </summary>
|
||||
/// <param name="force">Clear existing data first</param>
|
||||
public void SetDefault(IPrototypeManager protoManager, bool force = false)
|
||||
public void SetDefault(HumanoidCharacterProfile? profile, ICommonSession? session, IPrototypeManager protoManager, bool force = false)
|
||||
{
|
||||
if (profile == null)
|
||||
return;
|
||||
|
||||
if (force)
|
||||
SelectedLoadouts.Clear();
|
||||
|
||||
var collection = IoCManager.Instance!;
|
||||
var roleProto = protoManager.Index(Role);
|
||||
|
||||
for (var i = roleProto.Groups.Count - 1; i >= 0; i--)
|
||||
@@ -184,14 +187,28 @@ public sealed partial class RoleLoadout : IEquatable<RoleLoadout>
|
||||
if (SelectedLoadouts.ContainsKey(group))
|
||||
continue;
|
||||
|
||||
SelectedLoadouts[group] = new List<Loadout>();
|
||||
var loadouts = new List<Loadout>();
|
||||
SelectedLoadouts[group] = loadouts;
|
||||
|
||||
if (groupProto.MinLimit > 0)
|
||||
{
|
||||
// Apply any loadouts we can.
|
||||
for (var j = 0; j < Math.Min(groupProto.MinLimit, groupProto.Loadouts.Count); j++)
|
||||
{
|
||||
AddLoadout(group, groupProto.Loadouts[j], protoManager);
|
||||
if (!protoManager.TryIndex(groupProto.Loadouts[j], out var loadoutProto))
|
||||
continue;
|
||||
|
||||
var defaultLoadout = new Loadout()
|
||||
{
|
||||
Prototype = loadoutProto.ID,
|
||||
};
|
||||
|
||||
// Not valid so don't default to it anyway.
|
||||
if (!IsValid(profile, session, defaultLoadout.Prototype, collection, out _))
|
||||
continue;
|
||||
|
||||
loadouts.Add(defaultLoadout);
|
||||
Apply(loadoutProto);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,7 +217,7 @@ public sealed partial class RoleLoadout : IEquatable<RoleLoadout>
|
||||
/// <summary>
|
||||
/// Returns whether a loadout is valid or not.
|
||||
/// </summary>
|
||||
public bool IsValid(HumanoidCharacterProfile profile, ICommonSession session, ProtoId<LoadoutPrototype> loadout, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
|
||||
public bool IsValid(HumanoidCharacterProfile profile, ICommonSession? session, ProtoId<LoadoutPrototype> loadout, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
|
||||
{
|
||||
reason = null;
|
||||
|
||||
@@ -295,7 +312,25 @@ public sealed partial class RoleLoadout : IEquatable<RoleLoadout>
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Role.Equals(other.Role) && SelectedLoadouts.SequenceEqual(other.SelectedLoadouts) && Points == other.Points;
|
||||
|
||||
if (!Role.Equals(other.Role) ||
|
||||
SelectedLoadouts.Count != other.SelectedLoadouts.Count ||
|
||||
Points != other.Points)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tried using SequenceEqual but it stinky so.
|
||||
foreach (var (key, value) in SelectedLoadouts)
|
||||
{
|
||||
if (!other.SelectedLoadouts.TryGetValue(key, out var otherValue) ||
|
||||
!otherValue.SequenceEqual(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
|
||||
@@ -36,7 +36,7 @@ public sealed partial class RoboticsConsoleComponent : Component
|
||||
/// Radio message sent when destroying a borg.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId DestroyMessage = "robotics-console-cyborg-destroyed";
|
||||
public LocId DestroyMessage = "robotics-console-cyborg-destroying";
|
||||
|
||||
/// <summary>
|
||||
/// Cooldown on destroying borgs to prevent complete abuse.
|
||||
|
||||
@@ -97,6 +97,13 @@ public record struct CyborgControlData
|
||||
[DataField]
|
||||
public bool HasBrain;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the borg can currently be disabled if the brain is installed,
|
||||
/// if on cooldown then can't queue up multiple disables.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool CanDisable;
|
||||
|
||||
/// <summary>
|
||||
/// When this cyborg's data will be deleted.
|
||||
/// Set by the console when receiving the packet.
|
||||
@@ -104,7 +111,7 @@ public record struct CyborgControlData
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan Timeout = TimeSpan.Zero;
|
||||
|
||||
public CyborgControlData(SpriteSpecifier? chassisSprite, string chassisName, string name, float charge, int moduleCount, bool hasBrain)
|
||||
public CyborgControlData(SpriteSpecifier? chassisSprite, string chassisName, string name, float charge, int moduleCount, bool hasBrain, bool canDisable)
|
||||
{
|
||||
ChassisSprite = chassisSprite;
|
||||
ChassisName = chassisName;
|
||||
@@ -112,6 +119,7 @@ public record struct CyborgControlData
|
||||
Charge = charge;
|
||||
ModuleCount = moduleCount;
|
||||
HasBrain = hasBrain;
|
||||
CanDisable = canDisable;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,12 +23,25 @@ public sealed partial class BorgTransponderComponent : Component
|
||||
public string Name = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Popup shown to everyone when a borg is disabled.
|
||||
/// Popup shown to everyone after a borg is disabled.
|
||||
/// Gets passed a string "name".
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId DisabledPopup = "borg-transponder-disabled-popup";
|
||||
|
||||
/// <summary>
|
||||
/// Popup shown to the borg when it is being disabled.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId DisablingPopup = "borg-transponder-disabling-popup";
|
||||
|
||||
/// <summary>
|
||||
/// Popup shown to everyone when a borg is being destroyed.
|
||||
/// Gets passed a string "name".
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId DestroyingPopup = "borg-transponder-destroying-popup";
|
||||
|
||||
/// <summary>
|
||||
/// How long to wait between each broadcast.
|
||||
/// </summary>
|
||||
@@ -40,4 +53,28 @@ public sealed partial class BorgTransponderComponent : Component
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan NextBroadcast = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// When to next disable the borg.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan? NextDisable;
|
||||
|
||||
/// <summary>
|
||||
/// How long to wait to disable the borg after RD has ordered it.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan DisableDelay = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// Pretend that the borg cannot be disabled due to being on delay.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool FakeDisabling;
|
||||
|
||||
/// <summary>
|
||||
/// Pretend that the borg has no brain inserted.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool FakeDisabled;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
|
||||
namespace Content.Shared.Zombies;
|
||||
|
||||
@@ -10,6 +11,7 @@ public abstract class SharedZombieSystem : EntitySystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ZombieComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshSpeed);
|
||||
SubscribeLocalEvent<ZombieComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
|
||||
}
|
||||
|
||||
private void OnRefreshSpeed(EntityUid uid, ZombieComponent component, RefreshMovementSpeedModifiersEvent args)
|
||||
@@ -17,4 +19,9 @@ public abstract class SharedZombieSystem : EntitySystem
|
||||
var mod = component.ZombieMovementSpeedDebuff;
|
||||
args.ModifySpeed(mod, mod);
|
||||
}
|
||||
|
||||
private void OnRefreshNameModifiers(Entity<ZombieComponent> entity, ref RefreshNameModifiersEvent args)
|
||||
{
|
||||
args.AddModifier("zombie-name-prefix");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,12 +62,6 @@ public sealed partial class ZombieComponent : Component
|
||||
[DataField("zombieRoleId", customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
|
||||
public string ZombieRoleId = "Zombie";
|
||||
|
||||
/// <summary>
|
||||
/// The EntityName of the humanoid to restore in case of cloning
|
||||
/// </summary>
|
||||
[DataField("beforeZombifiedEntityName"), ViewVariables(VVAccess.ReadOnly)]
|
||||
public string BeforeZombifiedEntityName = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The CustomBaseLayers of the humanoid to restore in case of cloning
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user