Merge remote-tracking branch 'upstream/master' into ed-17-06-2024-upstream
# Conflicts: # Resources/Prototypes/Maps/oasis.yml
This commit is contained in:
@@ -9,6 +9,7 @@ using Content.Shared.Database;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Server.Kitchen.EntitySystems;
|
||||
|
||||
namespace Content.Server.Access.Systems;
|
||||
|
||||
@@ -18,6 +19,7 @@ public sealed class IdCardSystem : SharedIdCardSystem
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly MicrowaveSystem _microwave = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -27,12 +29,13 @@ public sealed class IdCardSystem : SharedIdCardSystem
|
||||
|
||||
private void OnMicrowaved(EntityUid uid, IdCardComponent component, BeingMicrowavedEvent args)
|
||||
{
|
||||
if (!component.CanMicrowave)
|
||||
return;
|
||||
if (!component.CanMicrowave || !TryComp<MicrowaveComponent>(args.Microwave, out var micro) || micro.Broken)
|
||||
return;
|
||||
|
||||
if (TryComp<AccessComponent>(uid, out var access))
|
||||
{
|
||||
float randomPick = _random.NextFloat();
|
||||
|
||||
// if really unlucky, burn card
|
||||
if (randomPick <= 0.15f)
|
||||
{
|
||||
@@ -49,6 +52,14 @@ public sealed class IdCardSystem : SharedIdCardSystem
|
||||
EntityManager.QueueDeleteEntity(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
//Explode if the microwave can't handle it
|
||||
if (!micro.CanMicrowaveIdsSafely)
|
||||
{
|
||||
_microwave.Explode((args.Microwave, micro));
|
||||
return;
|
||||
}
|
||||
|
||||
// If they're unlucky, brick their ID
|
||||
if (randomPick <= 0.25f)
|
||||
{
|
||||
@@ -73,6 +84,7 @@ public sealed class IdCardSystem : SharedIdCardSystem
|
||||
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(args.Microwave)} added {random.ID} access to {ToPrettyString(uid):entity}");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
namespace Content.Server.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("accumulator")]
|
||||
public float Accumulator = 0f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("enabled")]
|
||||
public bool Enabled { get; set; } = true;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("pressureResistance")]
|
||||
public float PressureResistance { get; set; } = 1f;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("moveResist")]
|
||||
public float MoveResist { get; set; } = 100f;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int LastHighPressureMovementAirCycle { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
@@ -135,6 +135,10 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("depressurizePressure")]
|
||||
public float DepressurizePressure = 0;
|
||||
|
||||
// When true, ignore under-pressure lockout. Used to re-fill rooms in air alarm "Fill" mode.
|
||||
[DataField]
|
||||
public bool PressureLockoutOverride = false;
|
||||
#endregion
|
||||
|
||||
public GasVentPumpData ToAirAlarmData()
|
||||
@@ -146,7 +150,8 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
PumpDirection = PumpDirection,
|
||||
PressureChecks = PressureChecks,
|
||||
ExternalPressureBound = ExternalPressureBound,
|
||||
InternalPressureBound = InternalPressureBound
|
||||
InternalPressureBound = InternalPressureBound,
|
||||
PressureLockoutOverride = PressureLockoutOverride
|
||||
};
|
||||
}
|
||||
|
||||
@@ -158,6 +163,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
PressureChecks = data.PressureChecks;
|
||||
ExternalPressureBound = data.ExternalPressureBound;
|
||||
InternalPressureBound = data.InternalPressureBound;
|
||||
PressureLockoutOverride = data.PressureLockoutOverride;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,8 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
// (ignoring temperature differences because I am lazy)
|
||||
var transferMoles = pressureDelta * environment.Volume / (pipe.Air.Temperature * Atmospherics.R);
|
||||
|
||||
if (vent.UnderPressureLockout)
|
||||
// Only run if the device is under lockout and not being overriden
|
||||
if (vent.UnderPressureLockout & !vent.PressureLockoutOverride)
|
||||
{
|
||||
// Leak only a small amount of gas as a proportion of supply pipe pressure.
|
||||
var pipeDelta = pipe.Air.Pressure - environment.Pressure;
|
||||
@@ -280,7 +281,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
return;
|
||||
if (args.IsInDetailsRange)
|
||||
{
|
||||
if (pumpComponent.UnderPressureLockout)
|
||||
if (pumpComponent.UnderPressureLockout & !pumpComponent.PressureLockoutOverride)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("gas-vent-pump-uvlo"));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Bed.Components;
|
||||
using Content.Server.Bed.Sleep;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace Content.Server.Bed.Sleep;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for the snoring trait.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class SnoringComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Sound;
|
||||
using Content.Shared.Sound.Components;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Examine;
|
||||
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.Slippery;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Bed.Sleep
|
||||
{
|
||||
public sealed class SleepingSystem : SharedSleepingSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
|
||||
[Dependency] private readonly EmitSoundSystem _emitSound = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>] public const string SleepActionId = "ActionSleep";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<MobStateComponent, SleepStateChangedEvent>(OnSleepStateChanged);
|
||||
SubscribeLocalEvent<SleepingComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
SubscribeLocalEvent<MobStateComponent, SleepActionEvent>(OnSleepAction);
|
||||
SubscribeLocalEvent<ActionsContainerComponent, SleepActionEvent>(OnBedSleepAction);
|
||||
SubscribeLocalEvent<MobStateComponent, WakeActionEvent>(OnWakeAction);
|
||||
SubscribeLocalEvent<SleepingComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
SubscribeLocalEvent<SleepingComponent, GetVerbsEvent<AlternativeVerb>>(AddWakeVerb);
|
||||
SubscribeLocalEvent<SleepingComponent, InteractHandEvent>(OnInteractHand);
|
||||
SubscribeLocalEvent<SleepingComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<SleepingComponent, SlipAttemptEvent>(OnSlip);
|
||||
SubscribeLocalEvent<SleepingComponent, ConsciousAttemptEvent>(OnConsciousAttempt);
|
||||
SubscribeLocalEvent<ForcedSleepingComponent, ComponentInit>(OnInit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// when sleeping component is added or removed, we do some stuff with other components.
|
||||
/// </summary>
|
||||
private void OnSleepStateChanged(EntityUid uid, MobStateComponent component, SleepStateChangedEvent args)
|
||||
{
|
||||
if (args.FellAsleep)
|
||||
{
|
||||
// Expiring status effects would remove the components needed for sleeping
|
||||
_statusEffectsSystem.TryRemoveStatusEffect(uid, "Stun");
|
||||
_statusEffectsSystem.TryRemoveStatusEffect(uid, "KnockedDown");
|
||||
|
||||
EnsureComp<StunnedComponent>(uid);
|
||||
EnsureComp<KnockedDownComponent>(uid);
|
||||
|
||||
if (TryComp<SleepEmitSoundComponent>(uid, out var sleepSound))
|
||||
{
|
||||
var emitSound = EnsureComp<SpamEmitSoundComponent>(uid);
|
||||
if (HasComp<SnoringComponent>(uid))
|
||||
{
|
||||
emitSound.Sound = sleepSound.Snore;
|
||||
}
|
||||
emitSound.MinInterval = sleepSound.Interval;
|
||||
emitSound.MaxInterval = sleepSound.MaxInterval;
|
||||
emitSound.PopUp = sleepSound.PopUp;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
RemComp<StunnedComponent>(uid);
|
||||
RemComp<KnockedDownComponent>(uid);
|
||||
RemComp<SpamEmitSoundComponent>(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wake up on taking an instance of damage at least the value of WakeThreshold.
|
||||
/// </summary>
|
||||
private void OnDamageChanged(EntityUid uid, SleepingComponent component, DamageChangedEvent args)
|
||||
{
|
||||
if (!args.DamageIncreased || args.DamageDelta == null)
|
||||
return;
|
||||
|
||||
if (args.DamageDelta.GetTotal() >= component.WakeThreshold)
|
||||
TryWaking(uid, component);
|
||||
}
|
||||
|
||||
private void OnSleepAction(EntityUid uid, MobStateComponent component, SleepActionEvent args)
|
||||
{
|
||||
TrySleeping(uid);
|
||||
}
|
||||
|
||||
private void OnBedSleepAction(EntityUid uid, ActionsContainerComponent component, SleepActionEvent args)
|
||||
{
|
||||
TrySleeping(args.Performer);
|
||||
}
|
||||
|
||||
private void OnWakeAction(EntityUid uid, MobStateComponent component, WakeActionEvent args)
|
||||
{
|
||||
if (!TryWakeCooldown(uid))
|
||||
return;
|
||||
|
||||
if (TryWaking(uid))
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
/// <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(EntityUid uid, SleepingComponent component, MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState == MobState.Dead)
|
||||
{
|
||||
RemComp<SpamEmitSoundComponent>(uid);
|
||||
RemComp<SleepingComponent>(uid);
|
||||
return;
|
||||
}
|
||||
if (TryComp<SpamEmitSoundComponent>(uid, out var spam))
|
||||
_emitSound.SetEnabled((uid, spam), args.NewMobState == MobState.Alive);
|
||||
}
|
||||
|
||||
private void AddWakeVerb(EntityUid uid, SleepingComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess)
|
||||
return;
|
||||
|
||||
AlternativeVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
if (!TryWakeCooldown(uid))
|
||||
return;
|
||||
|
||||
TryWaking(args.Target, user: args.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(EntityUid uid, SleepingComponent component, InteractHandEvent args)
|
||||
{
|
||||
args.Handled = true;
|
||||
|
||||
if (!TryWakeCooldown(uid))
|
||||
return;
|
||||
|
||||
TryWaking(args.Target, user: args.User);
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, SleepingComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (args.IsInDetailsRange)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("sleep-examined", ("target", Identity.Entity(uid, EntityManager))));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSlip(EntityUid uid, SleepingComponent component, SlipAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnConsciousAttempt(EntityUid uid, SleepingComponent component, ConsciousAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
|
||||
private void OnInit(EntityUid uid, ForcedSleepingComponent component, ComponentInit args)
|
||||
{
|
||||
TrySleeping(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try sleeping. Only mobs can sleep.
|
||||
/// </summary>
|
||||
public bool TrySleeping(EntityUid uid)
|
||||
{
|
||||
if (!HasComp<MobStateComponent>(uid))
|
||||
return false;
|
||||
|
||||
var tryingToSleepEvent = new TryingToSleepEvent(uid);
|
||||
RaiseLocalEvent(uid, ref tryingToSleepEvent);
|
||||
if (tryingToSleepEvent.Cancelled)
|
||||
return false;
|
||||
|
||||
EnsureComp<SleepingComponent>(uid);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryWakeCooldown(EntityUid uid, SleepingComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return false;
|
||||
|
||||
var curTime = _gameTiming.CurTime;
|
||||
|
||||
if (curTime < component.CoolDownEnd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
component.CoolDownEnd = curTime + component.Cooldown;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to wake up.
|
||||
/// </summary>
|
||||
public bool TryWaking(EntityUid uid, SleepingComponent? component = null, bool force = false, EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return false;
|
||||
|
||||
if (!force && HasComp<ForcedSleepingComponent>(uid))
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
_audio.PlayPvs("/Audio/Effects/thudswoosh.ogg", uid, AudioHelpers.WithVariation(0.05f, _robustRandom));
|
||||
_popupSystem.PopupEntity(Loc.GetString("wake-other-failure", ("target", Identity.Entity(uid, EntityManager))), uid, Filter.Entities(user.Value), true, Shared.Popups.PopupType.SmallCaution);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
_audio.PlayPvs("/Audio/Effects/thudswoosh.ogg", uid, AudioHelpers.WithVariation(0.05f, _robustRandom));
|
||||
_popupSystem.PopupEntity(Loc.GetString("wake-other-success", ("target", Identity.Entity(uid, EntityManager))), uid, Filter.Entities(user.Value), true);
|
||||
}
|
||||
RemComp<SleepingComponent>(uid);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -506,6 +506,7 @@ namespace Content.Server.Cargo.Systems
|
||||
"cargo-console-paper-print-text",
|
||||
("orderNumber", order.OrderId),
|
||||
("itemName", MetaData(item).EntityName),
|
||||
("orderQuantity", order.OrderQuantity),
|
||||
("requester", order.Requester),
|
||||
("reason", order.Reason),
|
||||
("approver", order.Approver ?? string.Empty)),
|
||||
|
||||
@@ -14,14 +14,6 @@ namespace Content.Server.Chemistry.Components;
|
||||
[RegisterComponent, Access(typeof(TransformableContainerSystem))]
|
||||
public sealed partial class TransformableContainerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the initial metadata name for the container.
|
||||
/// It will revert to this when emptied.
|
||||
/// It defaults to the name of the parent entity unless overwritten.
|
||||
/// </summary>
|
||||
[DataField("initialName")]
|
||||
public string? InitialName;
|
||||
|
||||
/// <summary>
|
||||
/// This is the initial metadata description for the container.
|
||||
/// It will revert to this when emptied.
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Server.Chemistry.Components;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Chemistry.EntitySystems;
|
||||
@@ -11,6 +12,7 @@ public sealed class TransformableContainerSystem : EntitySystem
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metadataSystem = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -18,15 +20,12 @@ public sealed class TransformableContainerSystem : EntitySystem
|
||||
|
||||
SubscribeLocalEvent<TransformableContainerComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<TransformableContainerComponent, SolutionContainerChangedEvent>(OnSolutionChange);
|
||||
SubscribeLocalEvent<TransformableContainerComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<TransformableContainerComponent> entity, ref MapInitEvent args)
|
||||
private void OnMapInit(Entity<TransformableContainerComponent> entity, ref MapInitEvent args)
|
||||
{
|
||||
var meta = MetaData(entity.Owner);
|
||||
if (string.IsNullOrEmpty(entity.Comp.InitialName))
|
||||
{
|
||||
entity.Comp.InitialName = meta.EntityName;
|
||||
}
|
||||
if (string.IsNullOrEmpty(entity.Comp.InitialDescription))
|
||||
{
|
||||
entity.Comp.InitialDescription = meta.EntityDescription;
|
||||
@@ -58,12 +57,20 @@ public sealed class TransformableContainerSystem : EntitySystem
|
||||
&& _prototypeManager.TryIndex(reagentId.Value.Prototype, out ReagentPrototype? proto))
|
||||
{
|
||||
var metadata = MetaData(entity.Owner);
|
||||
var val = Loc.GetString("transformable-container-component-glass", ("name", proto.LocalizedName));
|
||||
_metadataSystem.SetEntityName(entity.Owner, val, metadata);
|
||||
_metadataSystem.SetEntityDescription(entity.Owner, proto.LocalizedDescription, metadata);
|
||||
entity.Comp.CurrentReagent = proto;
|
||||
entity.Comp.Transformed = true;
|
||||
}
|
||||
|
||||
_nameMod.RefreshNameModifiers(entity.Owner);
|
||||
}
|
||||
|
||||
private void OnRefreshNameModifiers(Entity<TransformableContainerComponent> entity, ref RefreshNameModifiersEvent args)
|
||||
{
|
||||
if (entity.Comp.CurrentReagent is { } currentReagent)
|
||||
{
|
||||
args.AddModifier("transformable-container-component-glass", priority: -1, ("reagent", currentReagent.LocalizedName));
|
||||
}
|
||||
}
|
||||
|
||||
private void CancelTransformation(Entity<TransformableContainerComponent> entity)
|
||||
@@ -73,10 +80,8 @@ public sealed class TransformableContainerSystem : EntitySystem
|
||||
|
||||
var metadata = MetaData(entity);
|
||||
|
||||
if (!string.IsNullOrEmpty(entity.Comp.InitialName))
|
||||
{
|
||||
_metadataSystem.SetEntityName(entity.Owner, entity.Comp.InitialName, metadata);
|
||||
}
|
||||
_nameMod.RefreshNameModifiers(entity.Owner);
|
||||
|
||||
if (!string.IsNullOrEmpty(entity.Comp.InitialDescription))
|
||||
{
|
||||
_metadataSystem.SetEntityDescription(entity.Owner, entity.Comp.InitialDescription, metadata);
|
||||
|
||||
@@ -46,7 +46,9 @@ namespace Content.Server.Chemistry.ReactionEffects
|
||||
public override bool ShouldLog => true;
|
||||
|
||||
protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
=> Loc.GetString("reagent-effect-guidebook-missing");
|
||||
=> Loc.GetString("reagent-effect-guidebook-area-reaction",
|
||||
("duration", _duration)
|
||||
);
|
||||
|
||||
public override LogImpact LogImpact => LogImpact.High;
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Localizations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Content.Shared.Station;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Chemistry.ReagentEffectConditions
|
||||
{
|
||||
public sealed partial class JobCondition : ReagentEffectCondition
|
||||
{
|
||||
[DataField(required: true)] public List<ProtoId<JobPrototype>> Job;
|
||||
|
||||
public override bool Condition(ReagentEffectArgs args)
|
||||
{
|
||||
args.EntityManager.TryGetComponent<MindContainerComponent>(args.SolutionEntity, out var mindContainer);
|
||||
if (mindContainer != null && mindContainer.Mind != null)
|
||||
{
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
if (args.EntityManager.TryGetComponent<JobComponent>(mindContainer?.Mind, out var comp) && prototypeManager.TryIndex(comp.Prototype, out var prototype))
|
||||
{
|
||||
foreach (var jobId in Job)
|
||||
{
|
||||
if (prototype.ID == jobId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string GuidebookExplanation(IPrototypeManager prototype)
|
||||
{
|
||||
var localizedNames = Job.Select(jobId => prototype.Index(jobId).LocalizedName).ToList();
|
||||
return Loc.GetString("reagent-effect-condition-guidebook-job-condition", ("job", ContentLocalizationManager.FormatListToOr(localizedNames)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,6 @@ namespace Content.Server.Chemistry.ReagentEffects
|
||||
}
|
||||
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
|
||||
Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability));
|
||||
Loc.GetString("reagent-effect-guidebook-add-to-solution-reaction", ("chance", Probability));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Clothing;
|
||||
|
||||
namespace Content.Server.Clothing;
|
||||
|
||||
public sealed class MagbootsSystem : SharedMagbootsSystem
|
||||
{
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MagbootsComponent, ClothingGotEquippedEvent>(OnGotEquipped);
|
||||
SubscribeLocalEvent<MagbootsComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
|
||||
}
|
||||
|
||||
protected override void UpdateMagbootEffects(EntityUid parent, EntityUid uid, bool state, MagbootsComponent? component)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
state = state && component.On;
|
||||
|
||||
if (TryComp(parent, out MovedByPressureComponent? movedByPressure))
|
||||
{
|
||||
movedByPressure.Enabled = !state;
|
||||
}
|
||||
|
||||
if (state)
|
||||
{
|
||||
_alerts.ShowAlert(parent, component.MagbootsAlert);
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlert(parent, component.MagbootsAlert);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,8 @@ using Content.Server.Emoting.Systems;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
using Content.Shared.Cluwne;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
|
||||
namespace Content.Server.Cluwne;
|
||||
|
||||
@@ -30,6 +30,7 @@ public sealed class CluwneSystem : EntitySystem
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly AutoEmoteSystem _autoEmote = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -39,6 +40,7 @@ public sealed class CluwneSystem : EntitySystem
|
||||
SubscribeLocalEvent<CluwneComponent, MobStateChangedEvent>(OnMobState);
|
||||
SubscribeLocalEvent<CluwneComponent, EmoteEvent>(OnEmote, before:
|
||||
new[] { typeof(VocalSystem), typeof(BodyEmotesSystem) });
|
||||
SubscribeLocalEvent<CluwneComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -47,19 +49,19 @@ public sealed class CluwneSystem : EntitySystem
|
||||
private void OnMobState(EntityUid uid, CluwneComponent component, MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState == MobState.Dead)
|
||||
{
|
||||
{
|
||||
RemComp<CluwneComponent>(uid);
|
||||
RemComp<ClumsyComponent>(uid);
|
||||
RemComp<AutoEmoteComponent>(uid);
|
||||
var damageSpec = new DamageSpecifier(_prototypeManager.Index<DamageGroupPrototype>("Genetic"), 300);
|
||||
_damageableSystem.TryChangeDamage(uid, damageSpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public EmoteSoundsPrototype? EmoteSounds;
|
||||
|
||||
/// <summary>
|
||||
/// OnStartup gives the cluwne outfit, ensures clumsy, gives name prefix and makes sure emote sounds are laugh.
|
||||
/// OnStartup gives the cluwne outfit, ensures clumsy, and makes sure emote sounds are laugh.
|
||||
/// </summary>
|
||||
private void OnComponentStartup(EntityUid uid, CluwneComponent component, ComponentStartup args)
|
||||
{
|
||||
@@ -67,9 +69,6 @@ public sealed class CluwneSystem : EntitySystem
|
||||
return;
|
||||
_prototypeManager.TryIndex(component.EmoteSoundsId, out EmoteSounds);
|
||||
|
||||
var meta = MetaData(uid);
|
||||
var name = meta.EntityName;
|
||||
|
||||
EnsureComp<AutoEmoteComponent>(uid);
|
||||
_autoEmote.AddEmote(uid, "CluwneGiggle");
|
||||
EnsureComp<ClumsyComponent>(uid);
|
||||
@@ -77,7 +76,7 @@ public sealed class CluwneSystem : EntitySystem
|
||||
_popupSystem.PopupEntity(Loc.GetString("cluwne-transform", ("target", uid)), uid, PopupType.LargeCaution);
|
||||
_audio.PlayPvs(component.SpawnSound, uid);
|
||||
|
||||
_metaData.SetEntityName(uid, Loc.GetString("cluwne-name-prefix", ("target", name)), meta);
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
|
||||
SetOutfitCommand.SetOutfit(uid, "CluwneGear", EntityManager);
|
||||
}
|
||||
@@ -104,4 +103,12 @@ public sealed class CluwneSystem : EntitySystem
|
||||
_chat.TrySendInGameICMessage(uid, "spasms", InGameICChatType.Emote, ChatTransmitRange.Normal);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies "Cluwnified" prefix
|
||||
/// </summary>
|
||||
private void OnRefreshNameModifiers(Entity<CluwneComponent> entity, ref RefreshNameModifiersEvent args)
|
||||
{
|
||||
args.AddModifier("cluwne-name-prefix");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ namespace Content.Server.Construction
|
||||
}
|
||||
|
||||
var newEntityProto = graph.Nodes[edge.Target].Entity.GetId(null, user, new(EntityManager));
|
||||
var newEntity = Spawn(newEntityProto, _transformSystem.ToMapCoordinates(coords), rotation: angle);
|
||||
var newEntity = EntityManager.SpawnAttachedTo(newEntityProto, coords, rotation: angle);
|
||||
|
||||
if (!TryComp(newEntity, out ConstructionComponent? construction))
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.ForceSay;
|
||||
using Content.Shared.FixedPoint;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Systems;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Body.Systems;
|
||||
@@ -90,6 +91,16 @@ namespace Content.Server.Destructible
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetDestroyedAt(Entity<DestructibleComponent?> ent, [NotNullWhen(true)] out FixedPoint2? destroyedAt)
|
||||
{
|
||||
destroyedAt = null;
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
destroyedAt = DestroyedAt(ent, ent.Comp);
|
||||
return true;
|
||||
}
|
||||
|
||||
// FFS this shouldn't be this hard. Maybe this should just be a field of the destructible component. Its not
|
||||
// like there is currently any entity that is NOT just destroyed upon reaching a total-damage value.
|
||||
/// <summary>
|
||||
|
||||
@@ -6,9 +6,9 @@ using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Doors;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Doors.Systems
|
||||
@@ -20,6 +20,7 @@ namespace Content.Server.Doors.Systems
|
||||
[Dependency] private readonly AtmosphereSystem _atmosSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapping = default!;
|
||||
[Dependency] private readonly PointLightSystem _pointLight = default!;
|
||||
|
||||
private const int UpdateInterval = 30;
|
||||
private int _accumulatedTicks;
|
||||
@@ -53,6 +54,7 @@ namespace Content.Server.Doors.Systems
|
||||
var airtightQuery = GetEntityQuery<AirtightComponent>();
|
||||
var appearanceQuery = GetEntityQuery<AppearanceComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var pointLightQuery = GetEntityQuery<PointLightComponent>();
|
||||
|
||||
var query = EntityQueryEnumerator<FirelockComponent, DoorComponent>();
|
||||
while (query.MoveNext(out var uid, out var firelock, out var door))
|
||||
@@ -74,6 +76,11 @@ namespace Content.Server.Doors.Systems
|
||||
firelock.Temperature = fire;
|
||||
firelock.Pressure = pressure;
|
||||
Dirty(uid, firelock);
|
||||
|
||||
if (pointLightQuery.TryComp(uid, out var pointLight))
|
||||
{
|
||||
_pointLight.SetEnabled(uid, fire | pressure, pointLight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace Content.Server.Explosion.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Disallows starting the timer by hand, must be stuck or triggered by a system.
|
||||
/// Disallows starting the timer by hand, must be stuck or triggered by a system using <c>StartTimer</c>.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class AutomatedTimerComponent : Component
|
||||
|
||||
@@ -26,13 +26,7 @@ public sealed partial class TriggerSystem
|
||||
if (!component.StartOnStick)
|
||||
return;
|
||||
|
||||
HandleTimerTrigger(
|
||||
uid,
|
||||
args.User,
|
||||
component.Delay,
|
||||
component.BeepInterval,
|
||||
component.InitialBeepDelay,
|
||||
component.BeepSound);
|
||||
StartTimer((uid, component), args.User);
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, OnUseTimerTriggerComponent component, ExaminedEvent args)
|
||||
@@ -54,14 +48,7 @@ public sealed partial class TriggerSystem
|
||||
args.Verbs.Add(new AlternativeVerb()
|
||||
{
|
||||
Text = Loc.GetString("verb-start-detonation"),
|
||||
Act = () => HandleTimerTrigger(
|
||||
uid,
|
||||
args.User,
|
||||
component.Delay,
|
||||
component.BeepInterval,
|
||||
component.InitialBeepDelay,
|
||||
component.BeepSound
|
||||
),
|
||||
Act = () => StartTimer((uid, component), args.User),
|
||||
Priority = 2
|
||||
});
|
||||
}
|
||||
@@ -174,13 +161,7 @@ public sealed partial class TriggerSystem
|
||||
if (component.DoPopup)
|
||||
_popupSystem.PopupEntity(Loc.GetString("trigger-activated", ("device", uid)), args.User, args.User);
|
||||
|
||||
HandleTimerTrigger(
|
||||
uid,
|
||||
args.User,
|
||||
component.Delay,
|
||||
component.BeepInterval,
|
||||
component.InitialBeepDelay,
|
||||
component.BeepSound);
|
||||
StartTimer((uid, component), args.User);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -265,6 +265,18 @@ namespace Content.Server.Explosion.EntitySystems
|
||||
comp.TimeRemaining += amount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the timer for triggering the device.
|
||||
/// </summary>
|
||||
public void StartTimer(Entity<OnUseTimerTriggerComponent?> ent, EntityUid? user)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
var comp = ent.Comp;
|
||||
HandleTimerTrigger(ent, user, comp.Delay, comp.BeepInterval, comp.InitialBeepDelay, comp.BeepSound);
|
||||
}
|
||||
|
||||
public void HandleTimerTrigger(EntityUid uid, EntityUid? user, float delay, float beepInterval, float? initialBeepDelay, SoundSpecifier? beepSound)
|
||||
{
|
||||
if (delay <= 0)
|
||||
|
||||
@@ -29,6 +29,7 @@ using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.NameModifier.Components;
|
||||
|
||||
namespace Content.Server.Fax;
|
||||
|
||||
@@ -464,10 +465,11 @@ public sealed class FaxSystem : EntitySystem
|
||||
return;
|
||||
|
||||
TryComp<LabelComponent>(sendEntity, out var labelComponent);
|
||||
TryComp<NameModifierComponent>(sendEntity, out var nameMod);
|
||||
|
||||
// TODO: See comment in 'Send()' about not being able to copy whole entities
|
||||
var printout = new FaxPrintout(paper.Content,
|
||||
labelComponent?.OriginalName ?? metadata.EntityName,
|
||||
nameMod?.BaseName ?? metadata.EntityName,
|
||||
labelComponent?.CurrentLabel,
|
||||
metadata.EntityPrototype?.ID ?? DefaultPaperPrototypeId,
|
||||
paper.StampState,
|
||||
@@ -510,12 +512,14 @@ public sealed class FaxSystem : EntitySystem
|
||||
!TryComp<PaperComponent>(sendEntity, out var paper))
|
||||
return;
|
||||
|
||||
TryComp<NameModifierComponent>(sendEntity, out var nameMod);
|
||||
|
||||
TryComp<LabelComponent>(sendEntity, out var labelComponent);
|
||||
|
||||
var payload = new NetworkPayload()
|
||||
{
|
||||
{ DeviceNetworkConstants.Command, FaxConstants.FaxPrintCommand },
|
||||
{ FaxConstants.FaxPaperNameData, labelComponent?.OriginalName ?? metadata.EntityName },
|
||||
{ FaxConstants.FaxPaperNameData, nameMod?.BaseName ?? metadata.EntityName },
|
||||
{ FaxConstants.FaxPaperLabelData, labelComponent?.CurrentLabel },
|
||||
{ FaxConstants.FaxPaperContentData, paper.Content },
|
||||
};
|
||||
|
||||
@@ -251,7 +251,9 @@ namespace Content.Server.GameTicking
|
||||
// (If the mob survives, that's a bug. Ghosting is kept regardless.)
|
||||
var canReturn = canReturnGlobal && _mind.IsCharacterDeadPhysically(mind);
|
||||
|
||||
if (canReturnGlobal && TryComp(playerEntity, out MobStateComponent? mobState))
|
||||
if (_configurationManager.GetCVar(CCVars.GhostKillCrit) &&
|
||||
canReturnGlobal &&
|
||||
TryComp(playerEntity, out MobStateComponent? mobState))
|
||||
{
|
||||
if (_mobState.IsCritical(playerEntity.Value, mobState))
|
||||
{
|
||||
|
||||
@@ -41,11 +41,18 @@ public abstract partial class GameRuleSystem<T> : EntitySystem where T : ICompon
|
||||
if (args.Players.Length >= minPlayers)
|
||||
continue;
|
||||
|
||||
ChatManager.SendAdminAnnouncement(Loc.GetString("preset-not-enough-ready-players",
|
||||
("readyPlayersCount", args.Players.Length),
|
||||
("minimumPlayers", minPlayers),
|
||||
("presetName", ToPrettyString(uid))));
|
||||
args.Cancel();
|
||||
if (gameRule.CancelPresetOnTooFewPlayers)
|
||||
{
|
||||
ChatManager.SendAdminAnnouncement(Loc.GetString("preset-not-enough-ready-players",
|
||||
("readyPlayersCount", args.Players.Length),
|
||||
("minimumPlayers", minPlayers),
|
||||
("presetName", ToPrettyString(uid))));
|
||||
args.Cancel();
|
||||
}
|
||||
else
|
||||
{
|
||||
ForceEndSelf(uid, gameRule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ public sealed class SecretRuleSystem : GameRuleSystem<SecretRuleComponent>
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ruleComp.MinPlayers > players)
|
||||
if (ruleComp.MinPlayers > players && ruleComp.CancelPresetOnTooFewPlayers)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Shared.Hands;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
@@ -20,9 +21,9 @@ public sealed class GlueSystem : SharedGlueSystem
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly OpenableSystem _openable = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -32,6 +33,7 @@ public sealed class GlueSystem : SharedGlueSystem
|
||||
SubscribeLocalEvent<GluedComponent, ComponentInit>(OnGluedInit);
|
||||
SubscribeLocalEvent<GlueComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
|
||||
SubscribeLocalEvent<GluedComponent, GotEquippedHandEvent>(OnHandPickUp);
|
||||
SubscribeLocalEvent<GluedComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
|
||||
}
|
||||
|
||||
// When glue bottle is used on item it will apply the glued and unremoveable components.
|
||||
@@ -95,27 +97,22 @@ public sealed class GlueSystem : SharedGlueSystem
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<GluedComponent, UnremoveableComponent, MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out var glue, out var _, out var meta))
|
||||
var query = EntityQueryEnumerator<GluedComponent, UnremoveableComponent>();
|
||||
while (query.MoveNext(out var uid, out var glue, out var _))
|
||||
{
|
||||
if (_timing.CurTime < glue.Until)
|
||||
continue;
|
||||
|
||||
// Instead of string matching, just reconstruct the expected name and compare
|
||||
if (meta.EntityName == Loc.GetString("glued-name-prefix", ("target", glue.BeforeGluedEntityName)))
|
||||
_metaData.SetEntityName(uid, glue.BeforeGluedEntityName);
|
||||
|
||||
RemComp<UnremoveableComponent>(uid);
|
||||
RemComp<GluedComponent>(uid);
|
||||
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGluedInit(Entity<GluedComponent> entity, ref ComponentInit args)
|
||||
{
|
||||
var meta = MetaData(entity);
|
||||
var name = meta.EntityName;
|
||||
entity.Comp.BeforeGluedEntityName = meta.EntityName;
|
||||
_metaData.SetEntityName(entity.Owner, Loc.GetString("glued-name-prefix", ("target", name)));
|
||||
_nameMod.RefreshNameModifiers(entity.Owner);
|
||||
}
|
||||
|
||||
private void OnHandPickUp(Entity<GluedComponent> entity, ref GotEquippedHandEvent args)
|
||||
@@ -124,4 +121,9 @@ public sealed class GlueSystem : SharedGlueSystem
|
||||
comp.DeleteOnDrop = false;
|
||||
entity.Comp.Until = _timing.CurTime + entity.Comp.Duration;
|
||||
}
|
||||
|
||||
private void OnRefreshNameModifiers(Entity<GluedComponent> entity, ref RefreshNameModifiersEvent args)
|
||||
{
|
||||
args.AddModifier("glued-name-prefix");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Shared.Gravity;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Gravity
|
||||
{
|
||||
|
||||
@@ -18,22 +18,25 @@ public sealed class RulesManager
|
||||
public void Initialize()
|
||||
{
|
||||
_netManager.Connected += OnConnected;
|
||||
_netManager.RegisterNetMessage<ShowRulesPopupMessage>();
|
||||
_netManager.RegisterNetMessage<SendRulesInformationMessage>();
|
||||
_netManager.RegisterNetMessage<RulesAcceptedMessage>(OnRulesAccepted);
|
||||
}
|
||||
|
||||
private async void OnConnected(object? sender, NetChannelArgs e)
|
||||
{
|
||||
if (IPAddress.IsLoopback(e.Channel.RemoteEndPoint.Address) && _cfg.GetCVar(CCVars.RulesExemptLocal))
|
||||
return;
|
||||
var isLocalhost = IPAddress.IsLoopback(e.Channel.RemoteEndPoint.Address) &&
|
||||
_cfg.GetCVar(CCVars.RulesExemptLocal);
|
||||
|
||||
var lastRead = await _dbManager.GetLastReadRules(e.Channel.UserId);
|
||||
if (lastRead > LastValidReadTime)
|
||||
return;
|
||||
var hasCooldown = lastRead > LastValidReadTime;
|
||||
|
||||
var message = new ShowRulesPopupMessage();
|
||||
message.PopupTime = _cfg.GetCVar(CCVars.RulesWaitTime);
|
||||
_netManager.ServerSendMessage(message, e.Channel);
|
||||
var showRulesMessage = new SendRulesInformationMessage
|
||||
{
|
||||
PopupTime = _cfg.GetCVar(CCVars.RulesWaitTime),
|
||||
CoreRules = _cfg.GetCVar(CCVars.RulesFile),
|
||||
ShouldShowRules = !isLocalhost && !hasCooldown
|
||||
};
|
||||
_netManager.ServerSendMessage(showRulesMessage, e.Channel);
|
||||
}
|
||||
|
||||
private async void OnRulesAccepted(RulesAcceptedMessage message)
|
||||
|
||||
@@ -12,6 +12,10 @@ namespace Content.Server.Info;
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class ShowRulesCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
|
||||
public string Command => "showrules";
|
||||
public string Description => "Opens the rules popup for the specified player.";
|
||||
public string Help => "showrules <username> [seconds]";
|
||||
@@ -25,8 +29,7 @@ public sealed class ShowRulesCommand : IConsoleCommand
|
||||
case 1:
|
||||
{
|
||||
target = args[0];
|
||||
var configurationManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
seconds = configurationManager.GetCVar(CCVars.RulesWaitTime);
|
||||
seconds = _configuration.GetCVar(CCVars.RulesWaitTime);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
@@ -48,15 +51,14 @@ public sealed class ShowRulesCommand : IConsoleCommand
|
||||
}
|
||||
|
||||
|
||||
var message = new ShowRulesPopupMessage { PopupTime = seconds };
|
||||
|
||||
if (!IoCManager.Resolve<IPlayerManager>().TryGetSessionByUsername(target, out var player))
|
||||
if (!_player.TryGetSessionByUsername(target, out var player))
|
||||
{
|
||||
shell.WriteError("Unable to find a player with that name.");
|
||||
return;
|
||||
}
|
||||
|
||||
var netManager = IoCManager.Resolve<INetManager>();
|
||||
netManager.ServerSendMessage(message, player.Channel);
|
||||
var coreRules = _configuration.GetCVar(CCVars.RulesFile);
|
||||
var message = new SendRulesInformationMessage { PopupTime = seconds, CoreRules = coreRules, ShouldShowRules = true};
|
||||
_net.ServerSendMessage(message, player.Channel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,12 @@ namespace Content.Server.Kitchen.Components
|
||||
/// Chance of lightning occurring when we microwave a metallic object
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float LightningChance = .75f;
|
||||
|
||||
/// <summary>
|
||||
/// If this microwave can give ids accesses without exploding
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool CanMicrowaveIdsSafely = true;
|
||||
}
|
||||
|
||||
public sealed class BeingMicrowavedEvent : HandledEntityEventArgs
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Server.Construction;
|
||||
@@ -15,6 +16,7 @@ using Content.Shared.Body.Part;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Construction.EntitySystems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Interaction;
|
||||
@@ -36,6 +38,7 @@ using System.Linq;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Server.Construction.Components;
|
||||
|
||||
namespace Content.Server.Kitchen.EntitySystems
|
||||
{
|
||||
@@ -61,6 +64,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
[Dependency] private readonly SharedItemSystem _item = default!;
|
||||
[Dependency] private readonly SharedStackSystem _stack = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
private const string MalfunctionSpark = "Spark";
|
||||
@@ -408,6 +412,23 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
return component.Storage.ContainedEntities.Any();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explodes the microwave internally, turning it into a broken state, destroying its board, and spitting out its machine parts
|
||||
/// </summary>
|
||||
/// <param name="ent"></param>
|
||||
public void Explode(Entity<MicrowaveComponent> ent)
|
||||
{
|
||||
ent.Comp.Broken = true; // Make broken so we stop processing stuff
|
||||
_explosion.TriggerExplosive(ent);
|
||||
if (TryComp<MachineComponent>(ent, out var machine))
|
||||
{
|
||||
_container.CleanContainer(machine.BoardContainer);
|
||||
_container.EmptyContainer(machine.PartContainer);
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(ent)} exploded from unsafe cooking!");
|
||||
}
|
||||
/// <summary>
|
||||
/// Handles the attempted cooking of unsafe objects
|
||||
/// </summary>
|
||||
@@ -425,7 +446,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
ent.Comp1.MalfunctionTime = _gameTiming.CurTime + TimeSpan.FromSeconds(ent.Comp2.MalfunctionInterval);
|
||||
if (_random.Prob(ent.Comp2.ExplosionChance))
|
||||
{
|
||||
_explosion.TriggerExplosive(ent);
|
||||
Explode((ent, ent.Comp2));
|
||||
return; // microwave is fucked, stop the cooking.
|
||||
}
|
||||
|
||||
@@ -532,7 +553,8 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
activeComp.CookTimeRemaining = component.CurrentCookTimerTime * component.CookTimeMultiplier;
|
||||
activeComp.TotalTime = component.CurrentCookTimerTime; //this doesn't scale so that we can have the "actual" time
|
||||
activeComp.PortionedRecipe = portionedRecipe;
|
||||
component.CurrentCookTimeEnd = _gameTiming.CurTime + TimeSpan.FromSeconds(component.CurrentCookTimerTime);
|
||||
//Scale tiems with cook times
|
||||
component.CurrentCookTimeEnd = _gameTiming.CurTime + TimeSpan.FromSeconds(component.CurrentCookTimerTime * component.CookTimeMultiplier);
|
||||
if (malfunctioning)
|
||||
activeComp.MalfunctionTime = _gameTiming.CurTime + TimeSpan.FromSeconds(component.MalfunctionInterval);
|
||||
UpdateUserInterfaceState(uid, component);
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Shared.Examine;
|
||||
using Content.Shared.Labels;
|
||||
using Content.Shared.Labels.Components;
|
||||
using Content.Shared.Labels.EntitySystems;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
@@ -18,7 +19,7 @@ namespace Content.Server.Labels
|
||||
{
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
|
||||
public const string ContainerName = "paper_label";
|
||||
|
||||
@@ -41,6 +42,8 @@ namespace Content.Server.Labels
|
||||
component.CurrentLabel = Loc.GetString(component.CurrentLabel);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -52,30 +55,11 @@ namespace Content.Server.Labels
|
||||
/// <param name="metadata">metadata component for resolve</param>
|
||||
public override void Label(EntityUid uid, string? text, MetaDataComponent? metadata = null, LabelComponent? label = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metadata))
|
||||
return;
|
||||
if (!Resolve(uid, ref label, false))
|
||||
label = EnsureComp<LabelComponent>(uid);
|
||||
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
if (label.OriginalName is null)
|
||||
return;
|
||||
|
||||
// Remove label
|
||||
_metaData.SetEntityName(uid, label.OriginalName, metadata);
|
||||
label.CurrentLabel = null;
|
||||
label.OriginalName = null;
|
||||
|
||||
Dirty(uid, label);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Update label
|
||||
label.OriginalName ??= metadata.EntityName;
|
||||
label.CurrentLabel = text;
|
||||
_metaData.SetEntityName(uid, $"{label.OriginalName} ({text})", metadata);
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
|
||||
Dirty(uid, label);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Lube;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Shared.Containers;
|
||||
@@ -9,11 +10,11 @@ namespace Content.Server.Lube;
|
||||
|
||||
public sealed class LubedSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly ThrowingSystem _throwing = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -21,14 +22,12 @@ public sealed class LubedSystem : EntitySystem
|
||||
|
||||
SubscribeLocalEvent<LubedComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<LubedComponent, ContainerGettingInsertedAttemptEvent>(OnHandPickUp);
|
||||
SubscribeLocalEvent<LubedComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, LubedComponent component, ComponentInit args)
|
||||
{
|
||||
var meta = MetaData(uid);
|
||||
var name = meta.EntityName;
|
||||
component.BeforeLubedEntityName = meta.EntityName;
|
||||
_metaData.SetEntityName(uid, Loc.GetString("lubed-name-prefix", ("target", name)));
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
}
|
||||
|
||||
private void OnHandPickUp(EntityUid uid, LubedComponent component, ContainerGettingInsertedAttemptEvent args)
|
||||
@@ -36,7 +35,7 @@ public sealed class LubedSystem : EntitySystem
|
||||
if (component.SlipsLeft <= 0)
|
||||
{
|
||||
RemComp<LubedComponent>(uid);
|
||||
_metaData.SetEntityName(uid, component.BeforeLubedEntityName);
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
return;
|
||||
}
|
||||
component.SlipsLeft--;
|
||||
@@ -47,4 +46,9 @@ public sealed class LubedSystem : EntitySystem
|
||||
_throwing.TryThrow(uid, _random.NextVector2(), strength: component.SlipStrength);
|
||||
_popup.PopupEntity(Loc.GetString("lube-slip", ("target", Identity.Entity(uid, EntityManager))), user, user, PopupType.MediumCaution);
|
||||
}
|
||||
|
||||
private void OnRefreshNameModifiers(Entity<LubedComponent> entity, ref RefreshNameModifiersEvent args)
|
||||
{
|
||||
args.AddModifier("lubed-name-prefix");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ public sealed class SuitSensorSystem : EntitySystem
|
||||
return null;
|
||||
|
||||
// check if sensor is enabled and worn by user
|
||||
if (sensor.Mode == SuitSensorMode.SensorOff || sensor.User == null || transform.GridUid == null)
|
||||
if (sensor.Mode == SuitSensorMode.SensorOff || sensor.User == null || !HasComp<MobStateComponent>(sensor.User) || transform.GridUid == null)
|
||||
return null;
|
||||
|
||||
// try to get mobs id from ID slot
|
||||
|
||||
25
Content.Server/Mining/MeteorComponent.cs
Normal file
25
Content.Server/Mining/MeteorComponent.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Content.Shared.Damage;
|
||||
|
||||
namespace Content.Server.Mining;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for meteors which hit objects, dealing damage to destroy/kill the object and dealing equal damage back to itself.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(MeteorSystem))]
|
||||
public sealed partial class MeteorComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Damage specifier that is multiplied against the calculated damage amount to determine what damage is applied to the colliding entity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The values of this should add up to 1 or else the damage will be scaled.
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
public DamageSpecifier DamageTypes = new();
|
||||
|
||||
/// <summary>
|
||||
/// A list of entities that this meteor has collided with. used to ensure no double collisions occur.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HashSet<EntityUid> HitList = new();
|
||||
}
|
||||
65
Content.Server/Mining/MeteorSystem.cs
Normal file
65
Content.Server/Mining/MeteorSystem.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Destructible;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Mining;
|
||||
|
||||
public sealed class MeteorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly DestructibleSystem _destructible = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<MeteorComponent, StartCollideEvent>(OnCollide);
|
||||
}
|
||||
|
||||
private void OnCollide(EntityUid uid, MeteorComponent component, ref StartCollideEvent args)
|
||||
{
|
||||
if (TerminatingOrDeleted(args.OtherEntity) || TerminatingOrDeleted(uid))
|
||||
return;
|
||||
|
||||
if (component.HitList.Contains(args.OtherEntity))
|
||||
return;
|
||||
|
||||
FixedPoint2 threshold;
|
||||
if (_mobThreshold.TryGetDeadThreshold(args.OtherEntity, out var mobThreshold))
|
||||
{
|
||||
threshold = mobThreshold.Value;
|
||||
if (HasComp<ActorComponent>(args.OtherEntity))
|
||||
_adminLog.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(args.OtherEntity):player} was struck by meteor {ToPrettyString(uid):ent} and killed instantly.");
|
||||
}
|
||||
else if (_destructible.TryGetDestroyedAt(args.OtherEntity, out var destroyThreshold))
|
||||
{
|
||||
threshold = destroyThreshold.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
threshold = FixedPoint2.MaxValue;
|
||||
}
|
||||
var otherEntDamage = CompOrNull<DamageableComponent>(args.OtherEntity)?.TotalDamage ?? FixedPoint2.Zero;
|
||||
// account for the damage that the other entity has already taken: don't overkill
|
||||
threshold -= otherEntDamage;
|
||||
|
||||
// The max amount of damage our meteor can take before breaking.
|
||||
var maxMeteorDamage = _destructible.DestroyedAt(uid) - CompOrNull<DamageableComponent>(uid)?.TotalDamage ?? FixedPoint2.Zero;
|
||||
|
||||
// Cap damage so we don't overkill the meteor
|
||||
var trueDamage = FixedPoint2.Min(maxMeteorDamage, threshold);
|
||||
|
||||
var damage = component.DamageTypes * trueDamage;
|
||||
_damageable.TryChangeDamage(args.OtherEntity, damage, true, origin: uid);
|
||||
_damageable.TryChangeDamage(uid, damage);
|
||||
|
||||
if (!TerminatingOrDeleted(args.OtherEntity))
|
||||
component.HitList.Add(args.OtherEntity);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace Content.Server.Movement.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added to an entity that is ctrl-click moving their pulled object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This just exists so we don't have MoveEvent subs going off for every single mob constantly.
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
public sealed partial class PullMoverComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ using Robust.Shared.Physics.Controllers;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Movement.Systems;
|
||||
|
||||
@@ -91,7 +92,7 @@ public sealed class PullController : VirtualController
|
||||
|
||||
UpdatesAfter.Add(typeof(MoverController));
|
||||
SubscribeLocalEvent<PullMovingComponent, PullStoppedMessage>(OnPullStop);
|
||||
SubscribeLocalEvent<PullMoverComponent, MoveEvent>(OnPullerMove);
|
||||
SubscribeLocalEvent<ActivePullerComponent, MoveEvent>(OnPullerMove);
|
||||
|
||||
base.Initialize();
|
||||
}
|
||||
@@ -155,19 +156,22 @@ public sealed class PullController : VirtualController
|
||||
coords = fromUserCoords.WithEntityId(coords.EntityId);
|
||||
}
|
||||
|
||||
EnsureComp<PullMoverComponent>(player);
|
||||
var moving = EnsureComp<PullMovingComponent>(pulled!.Value);
|
||||
moving.MovingTo = coords;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnPullerMove(EntityUid uid, PullMoverComponent component, ref MoveEvent args)
|
||||
private void OnPullerMove(EntityUid uid, ActivePullerComponent component, ref MoveEvent args)
|
||||
{
|
||||
if (!_pullerQuery.TryComp(uid, out var puller))
|
||||
return;
|
||||
|
||||
if (puller.Pulling is not { } pullable)
|
||||
{
|
||||
DebugTools.Assert($"Failed to clean up puller: {ToPrettyString(uid)}");
|
||||
RemCompDeferred(uid, component);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePulledRotation(uid, pullable);
|
||||
|
||||
@@ -182,13 +186,7 @@ public sealed class PullController : VirtualController
|
||||
if (_physicsQuery.TryComp(uid, out var physics))
|
||||
PhysicsSystem.WakeBody(uid, body: physics);
|
||||
|
||||
StopMove(uid, pullable);
|
||||
}
|
||||
|
||||
private void StopMove(Entity<PullMoverComponent?> mover, Entity<PullMovingComponent?> moving)
|
||||
{
|
||||
RemCompDeferred<PullMoverComponent>(mover.Owner);
|
||||
RemCompDeferred<PullMovingComponent>(moving.Owner);
|
||||
RemCompDeferred<PullMovingComponent>(pullable);
|
||||
}
|
||||
|
||||
private void UpdatePulledRotation(EntityUid puller, EntityUid pulled)
|
||||
@@ -302,17 +300,5 @@ public sealed class PullController : VirtualController
|
||||
PhysicsSystem.ApplyLinearImpulse(puller, -impulse);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup PullMover
|
||||
var moverQuery = EntityQueryEnumerator<PullMoverComponent, PullerComponent>();
|
||||
|
||||
while (moverQuery.MoveNext(out var uid, out _, out var puller))
|
||||
{
|
||||
if (!HasComp<PullMovingComponent>(puller.Pulling))
|
||||
{
|
||||
RemCompDeferred<PullMoverComponent>(uid);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,12 @@ using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Content.Shared.Nutrition.AnimalHusbandry;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
@@ -29,12 +28,12 @@ public sealed class AnimalHusbandrySystem : EntitySystem
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
|
||||
private readonly HashSet<EntityUid> _failedAttempts = new();
|
||||
private readonly HashSet<EntityUid> _birthQueue = new();
|
||||
@@ -43,8 +42,7 @@ public sealed class AnimalHusbandrySystem : EntitySystem
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ReproductiveComponent, MindAddedMessage>(OnMindAdded);
|
||||
SubscribeLocalEvent<InfantComponent, ComponentStartup>(OnInfantStartup);
|
||||
SubscribeLocalEvent<InfantComponent, ComponentShutdown>(OnInfantShutdown);
|
||||
SubscribeLocalEvent<InfantComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
|
||||
}
|
||||
|
||||
// we express EZ-pass terminate the pregnancy if a player takes the role
|
||||
@@ -54,16 +52,11 @@ public sealed class AnimalHusbandrySystem : EntitySystem
|
||||
component.GestationEndTime = null;
|
||||
}
|
||||
|
||||
private void OnInfantStartup(EntityUid uid, InfantComponent component, ComponentStartup args)
|
||||
private void OnRefreshNameModifiers(Entity<InfantComponent> entity, ref RefreshNameModifiersEvent args)
|
||||
{
|
||||
var meta = MetaData(uid);
|
||||
component.OriginalName = meta.EntityName;
|
||||
_metaData.SetEntityName(uid, Loc.GetString("infant-name-prefix", ("name", meta.EntityName)), meta);
|
||||
}
|
||||
|
||||
private void OnInfantShutdown(EntityUid uid, InfantComponent component, ComponentShutdown args)
|
||||
{
|
||||
_metaData.SetEntityName(uid, component.OriginalName);
|
||||
// This check may seem redundant, but it makes sure that the prefix is removed before the component is removed
|
||||
if (_timing.CurTime < entity.Comp.InfantEndTime)
|
||||
args.AddModifier("infant-name-prefix");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -202,6 +195,8 @@ public sealed class AnimalHusbandrySystem : EntitySystem
|
||||
{
|
||||
var infant = AddComp<InfantComponent>(offspring);
|
||||
infant.InfantEndTime = _timing.CurTime + infant.InfantDuration;
|
||||
// Make sure the name prefix is applied
|
||||
_nameMod.RefreshNameModifiers(offspring);
|
||||
}
|
||||
_adminLog.Add(LogType.Action, $"{ToPrettyString(uid)} gave birth to {ToPrettyString(offspring)}.");
|
||||
}
|
||||
@@ -249,6 +244,8 @@ public sealed class AnimalHusbandrySystem : EntitySystem
|
||||
if (_timing.CurTime < infant.InfantEndTime)
|
||||
continue;
|
||||
RemCompDeferred(uid, infant);
|
||||
// Make sure the name prefix gets removed
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +43,19 @@ namespace Content.Server.PDA.Ringer
|
||||
SubscribeLocalEvent<RingerComponent, RingerPlayRingtoneMessage>(RingerPlayRingtone);
|
||||
SubscribeLocalEvent<RingerComponent, RingerRequestUpdateInterfaceMessage>(UpdateRingerUserInterfaceDriver);
|
||||
|
||||
SubscribeLocalEvent<RingerUplinkComponent, CurrencyInsertAttemptEvent>(OnCurrencyInsert);
|
||||
SubscribeLocalEvent<RingerComponent, CurrencyInsertAttemptEvent>(OnCurrencyInsert);
|
||||
}
|
||||
|
||||
//Event Functions
|
||||
|
||||
private void OnCurrencyInsert(EntityUid uid, RingerUplinkComponent uplink, CurrencyInsertAttemptEvent args)
|
||||
private void OnCurrencyInsert(EntityUid uid, RingerComponent ringer, CurrencyInsertAttemptEvent args)
|
||||
{
|
||||
if (!TryComp<RingerUplinkComponent>(uid, out var uplink))
|
||||
{
|
||||
args.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// if the store can be locked, it must be unlocked first before inserting currency. Stops traitor checking.
|
||||
if (!uplink.Unlocked)
|
||||
args.Cancel();
|
||||
|
||||
@@ -128,7 +128,6 @@ public sealed class TegSystem : EntitySystem
|
||||
// Shift ramp position based on demand and generation from previous tick.
|
||||
var curRamp = component.RampPosition;
|
||||
var lastDraw = supplier.CurrentSupply;
|
||||
// Limit amount lost/gained based on power factor.
|
||||
curRamp = MathHelper.Clamp(lastDraw, curRamp / component.RampFactor, curRamp * component.RampFactor);
|
||||
curRamp = MathF.Max(curRamp, component.RampMinimum);
|
||||
component.RampPosition = curRamp;
|
||||
@@ -138,17 +137,28 @@ public sealed class TegSystem : EntitySystem
|
||||
if (airA.Pressure > 0 && airB.Pressure > 0)
|
||||
{
|
||||
var hotA = airA.Temperature > airB.Temperature;
|
||||
var cHot = hotA ? cA : cB;
|
||||
|
||||
// Calculate maximum amount of energy to generate this tick based on ramping above.
|
||||
// This clamps the thermal energy transfer as well.
|
||||
var targetEnergy = curRamp / _atmosphere.AtmosTickRate;
|
||||
var transferMax = targetEnergy / (component.ThermalEfficiency * component.PowerFactor);
|
||||
|
||||
// Calculate thermal and electrical energy transfer between the two sides.
|
||||
var δT = MathF.Abs(airA.Temperature - airB.Temperature);
|
||||
var transfer = Math.Min(δT * cA * cB / (cA + cB - cHot * component.ThermalEfficiency), transferMax);
|
||||
electricalEnergy = transfer * component.ThermalEfficiency * component.PowerFactor;
|
||||
// Assume temperature equalizes, i.e. Ta*cA + Tb*cB = Tf*(cA+cB)
|
||||
var Tf = (airA.Temperature * cA + airB.Temperature * cB) / (cA + cB);
|
||||
// The maximum energy we can extract is (Ta - Tf)*cA, which is equal to (Tf - Tb)*cB
|
||||
var Wmax = MathF.Abs(airA.Temperature - Tf) * cA;
|
||||
|
||||
var N = component.ThermalEfficiency;
|
||||
|
||||
// Calculate Carnot efficiency
|
||||
var Thot = hotA ? airA.Temperature : airB.Temperature;
|
||||
var Tcold = hotA ? airB.Temperature : airA.Temperature;
|
||||
var Nmax = 1 - Tcold / Thot;
|
||||
N = MathF.Min(N, Nmax); // clamp by Carnot efficiency
|
||||
|
||||
// Reduce efficiency at low temperature differences to encourage burn chambers (instead
|
||||
// of just feeding the TEG room temperature gas from an infinite gas miner).
|
||||
var dT = Thot - Tcold;
|
||||
N *= MathF.Tanh(dT/700); // https://www.wolframalpha.com/input?i=tanh(x/700)+from+0+to+1000
|
||||
|
||||
var transfer = Wmax * N;
|
||||
electricalEnergy = transfer * component.PowerFactor;
|
||||
var outTransfer = transfer * (1 - component.ThermalEfficiency);
|
||||
|
||||
// Adjust thermal energy in transferred gas mixtures.
|
||||
@@ -169,7 +179,7 @@ public sealed class TegSystem : EntitySystem
|
||||
component.LastGeneration = electricalEnergy;
|
||||
|
||||
// Turn energy (at atmos tick rate) into wattage.
|
||||
var power = electricalEnergy * _atmosphere.AtmosTickRate;
|
||||
var power = electricalEnergy / args.dt;
|
||||
// Add ramp factor. This magics slight power into existence, but allows us to ramp up.
|
||||
supplier.MaxSupply = power * component.RampFactor;
|
||||
|
||||
|
||||
@@ -130,17 +130,20 @@ public sealed partial class SalvageSystem
|
||||
}
|
||||
|
||||
// Uhh yeah don't delete mobs or whatever
|
||||
var mobQuery = AllEntityQuery<HumanoidAppearanceComponent, MobStateComponent, TransformComponent>();
|
||||
var mobQuery = AllEntityQuery<MobStateComponent, TransformComponent>();
|
||||
_detachEnts.Clear();
|
||||
|
||||
while (mobQuery.MoveNext(out var mobUid, out _, out _, out var xform))
|
||||
while (mobQuery.MoveNext(out var mobUid, out _, out var xform))
|
||||
{
|
||||
if (xform.GridUid == null || !data.Comp.ActiveEntities.Contains(xform.GridUid.Value) || xform.MapUid == null)
|
||||
continue;
|
||||
|
||||
if (_salvMobQuery.HasComp(mobUid))
|
||||
continue;
|
||||
|
||||
// Can't parent directly to map as it runs grid traversal.
|
||||
_detachEnts.Add(((mobUid, xform), xform.MapUid.Value, _transform.GetWorldPosition(xform)));
|
||||
_transform.DetachParentToNull(mobUid, xform);
|
||||
_transform.DetachEntity(mobUid, xform);
|
||||
}
|
||||
|
||||
// Go and cleanup the active ents.
|
||||
|
||||
@@ -15,6 +15,7 @@ using Content.Server.Station.Events;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
@@ -63,6 +64,16 @@ public sealed class ArrivalsSystem : EntitySystem
|
||||
/// </summary>
|
||||
public bool Enabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flags if all players must arrive via the Arrivals system, or if they can spawn in other ways.
|
||||
/// </summary>
|
||||
public bool Forced { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flags if all players spawning at the departure terminal have godmode until they leave the terminal.
|
||||
/// </summary>
|
||||
public bool ArrivalsGodmode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The first arrival is a little early, to save everyone 10s
|
||||
/// </summary>
|
||||
@@ -94,7 +105,12 @@ public sealed class ArrivalsSystem : EntitySystem
|
||||
|
||||
// Don't invoke immediately as it will get set in the natural course of things.
|
||||
Enabled = _cfgManager.GetCVar(CCVars.ArrivalsShuttles);
|
||||
Subs.CVar(_cfgManager, CCVars.ArrivalsShuttles, SetArrivals);
|
||||
Forced = _cfgManager.GetCVar(CCVars.ForceArrivals);
|
||||
ArrivalsGodmode = _cfgManager.GetCVar(CCVars.GodmodeArrivals);
|
||||
|
||||
_cfgManager.OnValueChanged(CCVars.ArrivalsShuttles, SetArrivals);
|
||||
_cfgManager.OnValueChanged(CCVars.ForceArrivals, b => Forced = b);
|
||||
_cfgManager.OnValueChanged(CCVars.GodmodeArrivals, b => ArrivalsGodmode = b);
|
||||
|
||||
// Command so admins can set these for funsies
|
||||
_console.RegisterCommand("arrivals", ArrivalsCommand, ArrivalsCompletion);
|
||||
@@ -250,6 +266,9 @@ public sealed class ArrivalsSystem : EntitySystem
|
||||
// The player has successfully left arrivals and is also not on the shuttle. Remove their warp coupon.
|
||||
RemCompDeferred<PendingClockInComponent>(pUid);
|
||||
RemCompDeferred<AutoOrientComponent>(pUid);
|
||||
|
||||
if (ArrivalsGodmode)
|
||||
RemCompDeferred<GodmodeComponent>(pUid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,7 +328,7 @@ public sealed class ArrivalsSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// Only works on latejoin even if enabled.
|
||||
if (!Enabled || _ticker.RunLevel != GameRunLevel.InRound)
|
||||
if (!Enabled || !Forced && _ticker.RunLevel != GameRunLevel.InRound)
|
||||
return;
|
||||
|
||||
if (!HasComp<StationArrivalsComponent>(ev.Station))
|
||||
@@ -317,33 +336,37 @@ public sealed class ArrivalsSystem : EntitySystem
|
||||
|
||||
TryGetArrivals(out var arrivals);
|
||||
|
||||
if (TryComp(arrivals, out TransformComponent? arrivalsXform))
|
||||
if (!TryComp(arrivals, out TransformComponent? arrivalsXform))
|
||||
return;
|
||||
|
||||
var mapId = arrivalsXform.MapID;
|
||||
|
||||
var points = EntityQueryEnumerator<SpawnPointComponent, TransformComponent>();
|
||||
var possiblePositions = new List<EntityCoordinates>();
|
||||
while (points.MoveNext(out var uid, out var spawnPoint, out var xform))
|
||||
{
|
||||
var mapId = arrivalsXform.MapID;
|
||||
if (spawnPoint.SpawnType != SpawnPointType.LateJoin || xform.MapID != mapId)
|
||||
continue;
|
||||
|
||||
var points = EntityQueryEnumerator<SpawnPointComponent, TransformComponent>();
|
||||
var possiblePositions = new List<EntityCoordinates>();
|
||||
while (points.MoveNext(out var uid, out var spawnPoint, out var xform))
|
||||
{
|
||||
if (spawnPoint.SpawnType != SpawnPointType.LateJoin || xform.MapID != mapId)
|
||||
continue;
|
||||
|
||||
possiblePositions.Add(xform.Coordinates);
|
||||
}
|
||||
|
||||
if (possiblePositions.Count > 0)
|
||||
{
|
||||
var spawnLoc = _random.Pick(possiblePositions);
|
||||
ev.SpawnResult = _stationSpawning.SpawnPlayerMob(
|
||||
spawnLoc,
|
||||
ev.Job,
|
||||
ev.HumanoidCharacterProfile,
|
||||
ev.Station);
|
||||
|
||||
EnsureComp<PendingClockInComponent>(ev.SpawnResult.Value);
|
||||
EnsureComp<AutoOrientComponent>(ev.SpawnResult.Value);
|
||||
}
|
||||
possiblePositions.Add(xform.Coordinates);
|
||||
}
|
||||
|
||||
if (possiblePositions.Count <= 0)
|
||||
return;
|
||||
|
||||
var spawnLoc = _random.Pick(possiblePositions);
|
||||
ev.SpawnResult = _stationSpawning.SpawnPlayerMob(
|
||||
spawnLoc,
|
||||
ev.Job,
|
||||
ev.HumanoidCharacterProfile,
|
||||
ev.Station);
|
||||
|
||||
EnsureComp<PendingClockInComponent>(ev.SpawnResult.Value);
|
||||
EnsureComp<AutoOrientComponent>(ev.SpawnResult.Value);
|
||||
|
||||
// If you're forced to spawn, you're invincible until you leave wherever you were forced to spawn.
|
||||
if (ArrivalsGodmode)
|
||||
EnsureComp<GodmodeComponent>(ev.SpawnResult.Value);
|
||||
}
|
||||
|
||||
private bool TryTeleportToMapSpawn(EntityUid player, EntityUid stationId, TransformComponent? transform = null)
|
||||
|
||||
@@ -274,6 +274,23 @@ public sealed partial class BorgSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryComp<ItemBorgModuleComponent>(module, out var itemModuleComp))
|
||||
{
|
||||
foreach (var containedModuleUid in component.ModuleContainer.ContainedEntities)
|
||||
{
|
||||
if (!TryComp<ItemBorgModuleComponent>(containedModuleUid, out var containedItemModuleComp))
|
||||
continue;
|
||||
|
||||
if (containedItemModuleComp.Items.Count == itemModuleComp.Items.Count &&
|
||||
containedItemModuleComp.Items.All(itemModuleComp.Items.Contains))
|
||||
{
|
||||
if (user != null)
|
||||
Popup.PopupEntity(Loc.GetString("borg-module-duplicate"), uid, user.Value);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Robotics;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
@@ -26,6 +27,9 @@ public sealed partial class BorgSystem
|
||||
var query = EntityQueryEnumerator<BorgTransponderComponent, BorgChassisComponent, DeviceNetworkComponent, MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp, out var chassis, out var device, out var meta))
|
||||
{
|
||||
if (comp.NextDisable is {} nextDisable && now >= nextDisable)
|
||||
DoDisable((uid, comp, chassis, meta));
|
||||
|
||||
if (now < comp.NextBroadcast)
|
||||
continue;
|
||||
|
||||
@@ -33,13 +37,16 @@ public sealed partial class BorgSystem
|
||||
if (_powerCell.TryGetBatteryFromSlot(uid, out var battery))
|
||||
charge = battery.CurrentCharge / battery.MaxCharge;
|
||||
|
||||
var hasBrain = chassis.BrainEntity != null && !comp.FakeDisabled;
|
||||
var canDisable = comp.NextDisable == null && !comp.FakeDisabling;
|
||||
var data = new CyborgControlData(
|
||||
comp.Sprite,
|
||||
comp.Name,
|
||||
meta.EntityName,
|
||||
charge,
|
||||
chassis.ModuleCount,
|
||||
chassis.BrainEntity != null);
|
||||
hasBrain,
|
||||
canDisable);
|
||||
|
||||
var payload = new NetworkPayload()
|
||||
{
|
||||
@@ -52,6 +59,24 @@ public sealed partial class BorgSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void DoDisable(Entity<BorgTransponderComponent, BorgChassisComponent, MetaDataComponent> ent)
|
||||
{
|
||||
ent.Comp1.NextDisable = null;
|
||||
if (ent.Comp1.FakeDisabling)
|
||||
{
|
||||
ent.Comp1.FakeDisabled = true;
|
||||
ent.Comp1.FakeDisabling = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ent.Comp2.BrainEntity is not {} brain)
|
||||
return;
|
||||
|
||||
var message = Loc.GetString(ent.Comp1.DisabledPopup, ("name", Name(ent, ent.Comp3)));
|
||||
Popup.PopupEntity(message, ent);
|
||||
_container.Remove(brain, ent.Comp2.BrainContainer);
|
||||
}
|
||||
|
||||
private void OnPacketReceived(Entity<BorgTransponderComponent> ent, ref DeviceNetworkPacketEvent args)
|
||||
{
|
||||
var payload = args.Data;
|
||||
@@ -61,28 +86,28 @@ public sealed partial class BorgSystem
|
||||
if (command == RoboticsConsoleConstants.NET_DISABLE_COMMAND)
|
||||
Disable(ent);
|
||||
else if (command == RoboticsConsoleConstants.NET_DESTROY_COMMAND)
|
||||
Destroy(ent.Owner);
|
||||
Destroy(ent);
|
||||
}
|
||||
|
||||
private void Disable(Entity<BorgTransponderComponent, BorgChassisComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp2) || ent.Comp2.BrainEntity is not {} brain)
|
||||
if (!Resolve(ent, ref ent.Comp2) || ent.Comp2.BrainEntity == null || ent.Comp1.NextDisable != null)
|
||||
return;
|
||||
|
||||
// this won't exactly be stealthy but if you are malf its better than actually disabling you
|
||||
// update ui immediately
|
||||
ent.Comp1.NextBroadcast = _timing.CurTime;
|
||||
|
||||
// pretend the borg is being disabled forever now
|
||||
if (CheckEmagged(ent, "disabled"))
|
||||
return;
|
||||
ent.Comp1.FakeDisabling = true;
|
||||
else
|
||||
Popup.PopupEntity(Loc.GetString(ent.Comp1.DisablingPopup), ent);
|
||||
|
||||
var message = Loc.GetString(ent.Comp1.DisabledPopup, ("name", Name(ent)));
|
||||
Popup.PopupEntity(message, ent);
|
||||
_container.Remove(brain, ent.Comp2.BrainContainer);
|
||||
ent.Comp1.NextDisable = _timing.CurTime + ent.Comp1.DisableDelay;
|
||||
}
|
||||
|
||||
private void Destroy(Entity<ExplosiveComponent?> ent)
|
||||
private void Destroy(Entity<BorgTransponderComponent> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
// this is stealthy until someone realises you havent exploded
|
||||
if (CheckEmagged(ent, "destroyed"))
|
||||
{
|
||||
@@ -91,7 +116,12 @@ public sealed partial class BorgSystem
|
||||
return;
|
||||
}
|
||||
|
||||
_explosion.TriggerExplosive(ent, ent.Comp, delete: false);
|
||||
var message = Loc.GetString(ent.Comp.DestroyingPopup, ("name", Name(ent)));
|
||||
Popup.PopupEntity(message, ent);
|
||||
_trigger.StartTimer(ent.Owner, user: null);
|
||||
|
||||
// prevent a shitter borg running into people
|
||||
RemComp<InputMoverComponent>(ent);
|
||||
}
|
||||
|
||||
private bool CheckEmagged(EntityUid uid, string name)
|
||||
|
||||
@@ -43,8 +43,8 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!;
|
||||
[Dependency] private readonly ExplosionSystem _explosion = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly TriggerSystem _trigger = default!;
|
||||
[Dependency] private readonly HandsSystem _hands = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Singularity.Components;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Singularity.EntitySystems;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
@@ -26,6 +26,7 @@ using Content.Shared.StatusIcon;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -39,18 +40,18 @@ namespace Content.Server.Station.Systems;
|
||||
[PublicAPI]
|
||||
public sealed class StationSpawningSystem : SharedStationSpawningSystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
|
||||
[Dependency] private readonly IdCardSystem _cardSystem = default!;
|
||||
[Dependency] private readonly PdaSystem _pdaSystem = default!;
|
||||
[Dependency] private readonly SharedAccessSystem _accessSystem = default!;
|
||||
[Dependency] private readonly IdentitySystem _identity = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||
|
||||
[Dependency] private readonly ActorSystem _actors = default!;
|
||||
[Dependency] private readonly ArrivalsSystem _arrivalsSystem = default!;
|
||||
[Dependency] private readonly ContainerSpawnPointSystem _containerSpawnPointSystem = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
|
||||
[Dependency] private readonly IdCardSystem _cardSystem = default!;
|
||||
[Dependency] private readonly IdentitySystem _identity = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||
[Dependency] private readonly PdaSystem _pdaSystem = default!;
|
||||
[Dependency] private readonly SharedAccessSystem _accessSystem = default!;
|
||||
|
||||
private bool _randomizeCharacters;
|
||||
|
||||
@@ -65,7 +66,15 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
|
||||
_spawnerCallbacks = new Dictionary<SpawnPriorityPreference, Action<PlayerSpawningEvent>>()
|
||||
{
|
||||
{ SpawnPriorityPreference.Arrivals, _arrivalsSystem.HandlePlayerSpawning },
|
||||
{ SpawnPriorityPreference.Cryosleep, _containerSpawnPointSystem.HandlePlayerSpawning }
|
||||
{
|
||||
SpawnPriorityPreference.Cryosleep, ev =>
|
||||
{
|
||||
if (_arrivalsSystem.Forced)
|
||||
_arrivalsSystem.HandlePlayerSpawning(ev);
|
||||
else
|
||||
_containerSpawnPointSystem.HandlePlayerSpawning(ev);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -190,7 +199,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
|
||||
if (loadout == null)
|
||||
{
|
||||
loadout = new RoleLoadout(jobLoadout);
|
||||
loadout.SetDefault(_prototypeManager);
|
||||
loadout.SetDefault(profile, _actors.GetSession(entity), _prototypeManager);
|
||||
}
|
||||
|
||||
EquipRoleLoadout(entity.Value, loadout, roleProto);
|
||||
@@ -308,4 +317,4 @@ public sealed class PlayerSpawningEvent : EntityEventArgs
|
||||
HumanoidCharacterProfile = humanoidCharacterProfile;
|
||||
Station = station;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using Content.Shared.Random;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for running meteor swarm events at regular intervals.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(MeteorSchedulerSystem)), AutoGenerateComponentPause]
|
||||
public sealed partial class MeteorSchedulerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The weights for which swarms will be selected.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<WeightedRandomEntityPrototype> Config = "DefaultConfig";
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the next swarm occurs.
|
||||
/// </summary>
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan NextSwarmTime = TimeSpan.Zero;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
using Content.Shared.Destructible.Thresholds;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(MeteorSwarmSystem)), AutoGenerateComponentPause]
|
||||
public sealed partial class MeteorSwarmComponent : Component
|
||||
{
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan NextWaveTime;
|
||||
|
||||
/// <summary>
|
||||
/// We'll send a specific amount of waves of meteors towards the station per ending rather than using a timer.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int WaveCounter;
|
||||
|
||||
[DataField]
|
||||
public float MeteorVelocity = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// If true, meteors will be thrown from all angles instead of from a singular source
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool NonDirectional;
|
||||
|
||||
/// <summary>
|
||||
/// The announcement played when a meteor swarm begins.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId? Announcement = "station-event-meteor-swarm-start-announcement";
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier? AnnouncementSound = new SoundPathSpecifier("/Audio/Announcements/meteors.ogg")
|
||||
{
|
||||
Params = new()
|
||||
{
|
||||
Volume = -4
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Each meteor entity prototype and their corresponding weight in being picked.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<EntProtoId, float> Meteors = new();
|
||||
|
||||
[DataField]
|
||||
public MinMax Waves = new(3, 3);
|
||||
|
||||
[DataField]
|
||||
public MinMax MeteorsPerWave = new(3, 4);
|
||||
|
||||
[DataField]
|
||||
public MinMax WaveCooldown = new (10, 60);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(MeteorSwarmRule))]
|
||||
public sealed partial class MeteorSwarmRuleComponent : Component
|
||||
{
|
||||
[DataField("cooldown")]
|
||||
public float Cooldown;
|
||||
|
||||
/// <summary>
|
||||
/// We'll send a specific amount of waves of meteors towards the station per ending rather than using a timer.
|
||||
/// </summary>
|
||||
[DataField("waveCounter")]
|
||||
public int WaveCounter;
|
||||
|
||||
[DataField("minimumWaves")]
|
||||
public int MinimumWaves = 3;
|
||||
|
||||
[DataField("maximumWaves")]
|
||||
public int MaximumWaves = 8;
|
||||
|
||||
[DataField("minimumCooldown")]
|
||||
public float MinimumCooldown = 10f;
|
||||
|
||||
[DataField("maximumCooldown")]
|
||||
public float MaximumCooldown = 60f;
|
||||
|
||||
[DataField("meteorsPerWave")]
|
||||
public int MeteorsPerWave = 5;
|
||||
|
||||
[DataField("meteorVelocity")]
|
||||
public float MeteorVelocity = 10f;
|
||||
|
||||
[DataField("maxAngularVelocity")]
|
||||
public float MaxAngularVelocity = 0.25f;
|
||||
|
||||
[DataField("minAngularVelocity")]
|
||||
public float MinAngularVelocity = -0.25f;
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Spawners;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
{
|
||||
public sealed class MeteorSwarmRule : StationEventSystem<MeteorSwarmRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
|
||||
protected override void Started(EntityUid uid, MeteorSwarmRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
component.WaveCounter = RobustRandom.Next(component.MinimumWaves, component.MaximumWaves);
|
||||
}
|
||||
|
||||
protected override void ActiveTick(EntityUid uid, MeteorSwarmRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||
{
|
||||
if (component.WaveCounter <= 0)
|
||||
{
|
||||
ForceEndSelf(uid, gameRule);
|
||||
return;
|
||||
}
|
||||
|
||||
component.Cooldown -= frameTime;
|
||||
|
||||
if (component.Cooldown > 0f)
|
||||
return;
|
||||
|
||||
component.WaveCounter--;
|
||||
|
||||
component.Cooldown += (component.MaximumCooldown - component.MinimumCooldown) * RobustRandom.NextFloat() + component.MinimumCooldown;
|
||||
|
||||
Box2? playableArea = null;
|
||||
var mapId = GameTicker.DefaultMap;
|
||||
|
||||
var query = AllEntityQuery<MapGridComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var gridId, out _, out var xform))
|
||||
{
|
||||
if (xform.MapID != mapId)
|
||||
continue;
|
||||
|
||||
var aabb = _physics.GetWorldAABB(gridId);
|
||||
playableArea = playableArea?.Union(aabb) ?? aabb;
|
||||
}
|
||||
|
||||
if (playableArea == null)
|
||||
{
|
||||
ForceEndSelf(uid, gameRule);
|
||||
return;
|
||||
}
|
||||
|
||||
var minimumDistance = (playableArea.Value.TopRight - playableArea.Value.Center).Length() + 50f;
|
||||
var maximumDistance = minimumDistance + 100f;
|
||||
|
||||
var center = playableArea.Value.Center;
|
||||
|
||||
for (var i = 0; i < component.MeteorsPerWave; i++)
|
||||
{
|
||||
var angle = new Angle(RobustRandom.NextFloat() * MathF.Tau);
|
||||
var offset = angle.RotateVec(new Vector2((maximumDistance - minimumDistance) * RobustRandom.NextFloat() + minimumDistance, 0));
|
||||
var spawnPosition = new MapCoordinates(center + offset, mapId);
|
||||
var meteor = Spawn("MeteorLarge", spawnPosition);
|
||||
var physics = EntityManager.GetComponent<PhysicsComponent>(meteor);
|
||||
_physics.SetBodyStatus(meteor, physics, BodyStatus.InAir);
|
||||
_physics.SetLinearDamping(meteor, physics, 0f);
|
||||
_physics.SetAngularDamping(meteor, physics, 0f);
|
||||
_physics.ApplyLinearImpulse(meteor, -offset.Normalized() * component.MeteorVelocity * physics.Mass, body: physics);
|
||||
_physics.ApplyAngularImpulse(
|
||||
meteor,
|
||||
physics.Mass * ((component.MaxAngularVelocity - component.MinAngularVelocity) * RobustRandom.NextFloat() + component.MinAngularVelocity),
|
||||
body: physics);
|
||||
|
||||
EnsureComp<TimedDespawnComponent>(meteor).Lifetime = 120f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
Content.Server/StationEvents/Events/MeteorSwarmSystem.cs
Normal file
89
Content.Server/StationEvents/Events/MeteorSwarmSystem.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class MeteorSwarmSystem : GameRuleSystem<MeteorSwarmComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
|
||||
protected override void Added(EntityUid uid, MeteorSwarmComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||
{
|
||||
base.Added(uid, component, gameRule, args);
|
||||
|
||||
component.WaveCounter = component.Waves.Next(RobustRandom);
|
||||
|
||||
if (component.Announcement is { } locId)
|
||||
_chat.DispatchGlobalAnnouncement(Loc.GetString(locId), playSound: false, colorOverride: Color.Gold);
|
||||
_audio.PlayGlobal(component.AnnouncementSound, Filter.Broadcast(), true);
|
||||
}
|
||||
|
||||
protected override void ActiveTick(EntityUid uid, MeteorSwarmComponent component, GameRuleComponent gameRule, float frameTime)
|
||||
{
|
||||
if (Timing.CurTime < component.NextWaveTime)
|
||||
return;
|
||||
|
||||
component.NextWaveTime += TimeSpan.FromSeconds(component.WaveCooldown.Next(RobustRandom));
|
||||
|
||||
|
||||
if (_station.GetStations().Count == 0)
|
||||
return;
|
||||
|
||||
var station = RobustRandom.Pick(_station.GetStations());
|
||||
if (_station.GetLargestGrid(Comp<StationDataComponent>(station)) is not { } grid)
|
||||
return;
|
||||
|
||||
var mapId = Transform(grid).MapID;
|
||||
var playableArea = _physics.GetWorldAABB(grid);
|
||||
|
||||
var minimumDistance = (playableArea.TopRight - playableArea.Center).Length() + 50f;
|
||||
var maximumDistance = minimumDistance + 100f;
|
||||
|
||||
var center = playableArea.Center;
|
||||
|
||||
var meteorsToSpawn = component.MeteorsPerWave.Next(RobustRandom);
|
||||
for (var i = 0; i < meteorsToSpawn; i++)
|
||||
{
|
||||
var spawnProto = RobustRandom.Pick(component.Meteors);
|
||||
|
||||
var angle = component.NonDirectional
|
||||
? RobustRandom.NextAngle()
|
||||
: new Random(uid.Id).NextAngle();
|
||||
|
||||
var offset = angle.RotateVec(new Vector2((maximumDistance - minimumDistance) * RobustRandom.NextFloat() + minimumDistance, 0));
|
||||
|
||||
// the line at which spawns occur is perpendicular to the offset.
|
||||
// This means the meteors are less likely to bunch up and hit the same thing.
|
||||
var subOffsetAngle = RobustRandom.Prob(0.5f)
|
||||
? angle + Math.PI / 2
|
||||
: angle - Math.PI / 2;
|
||||
var subOffset = subOffsetAngle.RotateVec(new Vector2( (playableArea.TopRight - playableArea.Center).Length() / 3 * RobustRandom.NextFloat(), 0));
|
||||
|
||||
var spawnPosition = new MapCoordinates(center + offset + subOffset, mapId);
|
||||
var meteor = Spawn(spawnProto, spawnPosition);
|
||||
var physics = Comp<PhysicsComponent>(meteor);
|
||||
_physics.ApplyLinearImpulse(meteor, -offset.Normalized() * component.MeteorVelocity * physics.Mass, body: physics);
|
||||
}
|
||||
|
||||
component.WaveCounter--;
|
||||
if (component.WaveCounter <= 0)
|
||||
{
|
||||
ForceEndSelf(uid, gameRule);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Content.Server/StationEvents/MeteorSchedulerSystem.cs
Normal file
54
Content.Server/StationEvents/MeteorSchedulerSystem.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.StationEvents;
|
||||
|
||||
/// <summary>
|
||||
/// This handles scheduling and launching meteors at a station at regular intervals.
|
||||
/// TODO: there is 100% a world in which this is genericized and can be used for lots of basic event scheduling
|
||||
/// </summary>
|
||||
public sealed class MeteorSchedulerSystem : GameRuleSystem<MeteorSchedulerComponent>
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private TimeSpan _meteorMinDelay;
|
||||
private TimeSpan _meteorMaxDelay;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_cfg.OnValueChanged(CCVars.MeteorSwarmMinTime, f => { _meteorMinDelay = TimeSpan.FromMinutes(f); }, true);
|
||||
_cfg.OnValueChanged(CCVars.MeteorSwarmMaxTime, f => { _meteorMaxDelay = TimeSpan.FromMinutes(f); }, true);
|
||||
}
|
||||
|
||||
protected override void Started(EntityUid uid, MeteorSchedulerComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
component.NextSwarmTime = Timing.CurTime + RobustRandom.Next(_meteorMinDelay, _meteorMaxDelay);
|
||||
}
|
||||
|
||||
protected override void ActiveTick(EntityUid uid, MeteorSchedulerComponent component, GameRuleComponent gameRule, float frameTime)
|
||||
{
|
||||
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||
|
||||
if (Timing.CurTime < component.NextSwarmTime)
|
||||
return;
|
||||
RunSwarm((uid, component));
|
||||
|
||||
component.NextSwarmTime += RobustRandom.Next(_meteorMinDelay, _meteorMaxDelay);
|
||||
}
|
||||
|
||||
private void RunSwarm(Entity<MeteorSchedulerComponent> ent)
|
||||
{
|
||||
var swarmWeights = _prototypeManager.Index(ent.Comp.Config);
|
||||
GameTicker.StartGameRule(swarmWeights.Pick(RobustRandom));
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,11 @@ using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Store.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Linq;
|
||||
using Content.Shared.Store.Components;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Store.Systems;
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Content.Server.VendingMachines
|
||||
SubscribeLocalEvent<VendingMachineComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, BreakageEventArgs>(OnBreak);
|
||||
SubscribeLocalEvent<VendingMachineComponent, GotEmaggedEvent>(OnEmagged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, DamageChangedEvent>(OnDamage);
|
||||
SubscribeLocalEvent<VendingMachineComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, PriceCalculationEvent>(OnVendingPrice);
|
||||
SubscribeLocalEvent<VendingMachineComponent, EmpPulseEvent>(OnEmpPulse);
|
||||
|
||||
@@ -149,8 +149,15 @@ namespace Content.Server.VendingMachines
|
||||
args.Handled = component.EmaggedInventory.Count > 0;
|
||||
}
|
||||
|
||||
private void OnDamage(EntityUid uid, VendingMachineComponent component, DamageChangedEvent args)
|
||||
private void OnDamageChanged(EntityUid uid, VendingMachineComponent component, DamageChangedEvent args)
|
||||
{
|
||||
if (!args.DamageIncreased && component.Broken)
|
||||
{
|
||||
component.Broken = false;
|
||||
TryUpdateVisualState(uid, component);
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.Broken || component.DispenseOnHitCoolingDown ||
|
||||
component.DispenseOnHitChance == null || args.DamageDelta == null)
|
||||
return;
|
||||
|
||||
@@ -17,6 +17,7 @@ using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Content.Shared.Weapons.Reflect;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
@@ -202,6 +203,20 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
break;
|
||||
|
||||
var result = rayCastResults[0];
|
||||
|
||||
// Checks if the laser should pass over unless targeted by its user
|
||||
foreach (var collide in rayCastResults)
|
||||
{
|
||||
if (collide.HitEntity != gun.Target &&
|
||||
CompOrNull<RequireProjectileTargetComponent>(collide.HitEntity)?.Active == true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result = collide;
|
||||
break;
|
||||
}
|
||||
|
||||
var hit = result.HitEntity;
|
||||
lastHit = hit;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Server.Xenoarchaeology.Equipment.Components;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Verbs;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.Equipment.Systems;
|
||||
|
||||
@@ -14,23 +15,44 @@ public sealed class NodeScannerSystem : EntitySystem
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<NodeScannerComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<NodeScannerComponent, BeforeRangedInteractEvent>(OnBeforeRangedInteract);
|
||||
SubscribeLocalEvent<NodeScannerComponent, GetVerbsEvent<UtilityVerb>>(AddScanVerb);
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, NodeScannerComponent component, AfterInteractEvent args)
|
||||
private void OnBeforeRangedInteract(EntityUid uid, NodeScannerComponent component, BeforeRangedInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach || args.Target == null)
|
||||
if (args.Handled || !args.CanReach || args.Target is not {} target)
|
||||
return;
|
||||
|
||||
if (!TryComp<ArtifactComponent>(target, out var artifact) || artifact.CurrentNodeId == null)
|
||||
return;
|
||||
|
||||
CreatePopup(uid, target, artifact);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void AddScanVerb(EntityUid uid, NodeScannerComponent component, GetVerbsEvent<UtilityVerb> args)
|
||||
{
|
||||
if (!args.CanAccess)
|
||||
return;
|
||||
|
||||
if (!TryComp<ArtifactComponent>(args.Target, out var artifact) || artifact.CurrentNodeId == null)
|
||||
return;
|
||||
|
||||
if (args.Handled)
|
||||
return;
|
||||
args.Handled = true;
|
||||
var verb = new UtilityVerb()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
CreatePopup(uid, args.Target, artifact);
|
||||
},
|
||||
Text = Loc.GetString("node-scan-tooltip")
|
||||
};
|
||||
|
||||
var target = args.Target.Value;
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void CreatePopup(EntityUid uid, EntityUid target, ArtifactComponent artifact)
|
||||
{
|
||||
if (TryComp(uid, out UseDelayComponent? useDelay)
|
||||
&& !_useDelay.TryResetDelay((uid, useDelay), true))
|
||||
return;
|
||||
|
||||
@@ -222,9 +222,7 @@ namespace Content.Server.Zombies
|
||||
_faction.AddFaction(target, "Zombie");
|
||||
|
||||
//gives it the funny "Zombie ___" name.
|
||||
var meta = MetaData(target);
|
||||
zombiecomp.BeforeZombifiedEntityName = meta.EntityName;
|
||||
_metaData.SetEntityName(target, Loc.GetString("zombie-name-prefix", ("target", meta.EntityName)), meta);
|
||||
_nameMod.RefreshNameModifiers(target);
|
||||
|
||||
_identity.QueueIdentityUpdate(target);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Content.Shared.Zombies;
|
||||
@@ -34,9 +35,9 @@ namespace Content.Server.Zombies
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly AutoEmoteSystem _autoEmote = default!;
|
||||
[Dependency] private readonly EmoteOnDamageSystem _emoteOnDamage = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
|
||||
public const SlotFlags ProtectiveSlots =
|
||||
SlotFlags.FEET |
|
||||
@@ -281,7 +282,7 @@ namespace Content.Server.Zombies
|
||||
_humanoidAppearance.SetSkinColor(target, zombiecomp.BeforeZombifiedSkinColor, false);
|
||||
_bloodstream.ChangeBloodReagent(target, zombiecomp.BeforeZombifiedBloodReagent);
|
||||
|
||||
_metaData.SetEntityName(target, zombiecomp.BeforeZombifiedEntityName);
|
||||
_nameMod.RefreshNameModifiers(target);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user