Merge remote-tracking branch 'upstream/stable' into ed-27-05-2025-upstream-sync

# Conflicts:
#	.github/CODEOWNERS
#	Content.Client/Guidebook/Controls/GuideReagentReaction.xaml.cs
#	Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs
#	Content.Server/Procedural/DungeonJob/DungeonJob.OreDunGen.cs
#	Resources/Prototypes/Entities/Effects/chemistry_effects.yml
#	Resources/Prototypes/Entities/Mobs/Customization/Markings/human_hair.yml
#	Resources/Prototypes/GameRules/meteorswarms.yml
#	Resources/Prototypes/Procedural/dungeon_configs.yml
This commit is contained in:
Ed
2025-05-27 12:21:14 +03:00
1165 changed files with 76878 additions and 44718 deletions

View File

@@ -10,9 +10,6 @@ namespace Content.Shared.Access.Components;
[Access(typeof(SharedIdCardConsoleSystem))]
public sealed partial class IdCardConsoleComponent : Component
{
public const int MaxFullNameLength = 30;
public const int MaxJobTitleLength = 30;
public static string PrivilegedIdCardSlotId = "IdCardConsole-privilegedId";
public static string TargetIdCardSlotId = "IdCardConsole-targetId";

View File

@@ -1,6 +1,7 @@
using System.Globalization;
using Content.Shared.Access.Components;
using Content.Shared.Administration.Logs;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Hands.Components;
using Content.Shared.IdentityManagement;
@@ -8,6 +9,7 @@ using Content.Shared.Inventory;
using Content.Shared.PDA;
using Content.Shared.Roles;
using Content.Shared.StatusIcon;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
@@ -15,6 +17,7 @@ namespace Content.Shared.Access.Systems;
public abstract class SharedIdCardSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAccessSystem _access = default!;
@@ -22,6 +25,10 @@ public abstract class SharedIdCardSystem : EntitySystem
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
// CCVar.
private int _maxNameLength;
private int _maxIdJobLength;
public override void Initialize()
{
base.Initialize();
@@ -29,6 +36,9 @@ public abstract class SharedIdCardSystem : EntitySystem
SubscribeLocalEvent<IdCardComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<TryGetIdentityShortInfoEvent>(OnTryGetIdentityShortInfo);
SubscribeLocalEvent<EntityRenamedEvent>(OnRename);
Subs.CVar(_cfgManager, CCVars.MaxNameLength, value => _maxNameLength = value, true);
Subs.CVar(_cfgManager, CCVars.MaxIdJobLength, value => _maxIdJobLength = value, true);
}
private void OnRename(ref EntityRenamedEvent ev)
@@ -131,8 +141,8 @@ public abstract class SharedIdCardSystem : EntitySystem
{
jobTitle = jobTitle.Trim();
if (jobTitle.Length > IdCardConsoleComponent.MaxJobTitleLength)
jobTitle = jobTitle[..IdCardConsoleComponent.MaxJobTitleLength];
if (jobTitle.Length > _maxIdJobLength)
jobTitle = jobTitle[.._maxIdJobLength];
}
else
{
@@ -209,8 +219,8 @@ public abstract class SharedIdCardSystem : EntitySystem
if (!string.IsNullOrWhiteSpace(fullName))
{
fullName = fullName.Trim();
if (fullName.Length > IdCardConsoleComponent.MaxFullNameLength)
fullName = fullName[..IdCardConsoleComponent.MaxFullNameLength];
if (fullName.Length > _maxNameLength)
fullName = fullName[.._maxNameLength];
}
else
{

View File

@@ -51,13 +51,18 @@ public abstract class SharedAnomalySystem : EntitySystem
return;
// anomalies are static by default, so we have set them to dynamic to be throwable
_physics.SetBodyType(ent, BodyType.Dynamic, body: body);
// only regular anomalies are static, so the check is meant to filter out things such as infection anomalies, which affect players
if (TryComp<PhysicsComponent>(ent, out var physics) && physics.BodyType == BodyType.Static)
_physics.SetBodyType(ent, BodyType.Dynamic, body: body);
ChangeAnomalyStability(ent, Random.NextFloat(corePowered.StabilityPerThrow.X, corePowered.StabilityPerThrow.Y), ent.Comp);
}
private void OnLand(Entity<AnomalyComponent> ent, ref LandEvent args)
{
// revert back to static
// revert back to static, but only if the object was dynamic (such as thrown anomalies, but not anomaly infected players)
if (!TryComp<PhysicsComponent>(ent, out var body) || body.BodyType != BodyType.Dynamic)
return;
_physics.SetBodyType(ent, BodyType.Static);
}
@@ -439,7 +444,7 @@ public abstract class SharedAnomalySystem : EntitySystem
}
[DataRecord]
public record struct AnomalySpawnSettings()
public partial record struct AnomalySpawnSettings()
{
/// <summary>
/// should entities block spawning?

View File

@@ -91,7 +91,7 @@ public sealed partial class GasTankComponent : Component, IGasMixtureHolder
/// Increases explosion for each scale kPa above threshold.
/// </summary>
[DataField]
public float TankFragmentScale = 2 * Atmospherics.OneAtmosphere;
public float TankFragmentScale = 2.25f * Atmospherics.OneAtmosphere;
[DataField]
public EntProtoId ToggleAction = "ActionToggleInternals";

View File

@@ -37,7 +37,7 @@ public interface IAtmosDeviceData
[Serializable, NetSerializable]
public sealed class AirAlarmUIState : BoundUserInterfaceState
{
public AirAlarmUIState(string address, int deviceCount, float pressureAverage, float temperatureAverage, List<(string, IAtmosDeviceData)> deviceData, AirAlarmMode mode, AtmosAlarmType alarmType, bool autoMode)
public AirAlarmUIState(string address, int deviceCount, float pressureAverage, float temperatureAverage, List<(string, IAtmosDeviceData)> deviceData, AirAlarmMode mode, AtmosAlarmType alarmType, bool autoMode, bool panicWireCut)
{
Address = address;
DeviceCount = deviceCount;
@@ -47,6 +47,7 @@ public sealed class AirAlarmUIState : BoundUserInterfaceState
Mode = mode;
AlarmType = alarmType;
AutoMode = autoMode;
PanicWireCut = panicWireCut;
}
public string Address { get; }
@@ -64,6 +65,7 @@ public sealed class AirAlarmUIState : BoundUserInterfaceState
public AirAlarmMode Mode { get; }
public AtmosAlarmType AlarmType { get; }
public bool AutoMode { get; }
public bool PanicWireCut { get; }
}
[Serializable, NetSerializable]

View File

@@ -0,0 +1,36 @@
using Content.Shared.Atmos.Piping.Binary.Systems;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
namespace Content.Shared.Atmos.Piping.Binary.Components;
/// <summary>
/// Component for manual atmospherics pumps that can open or close to let gas through.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedGasValveSystem))]
public sealed partial class GasValveComponent : Component
{
/// <summary>
/// Whether the valve is currently open and letting gas through.
/// </summary>
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadOnly)]
public bool Open = true;
/// <summary>
/// Inlet for the nodecontainer.
/// </summary>
[DataField("inlet")]
public string InletName = "inlet";
/// <summary>
/// Outlet for the nodecontainer.
/// </summary>
[DataField("outlet")]
public string OutletName = "outlet";
/// <summary>
/// Sound when <see cref="Open"/> is toggled.
/// </summary>
[DataField]
public SoundSpecifier ValveSound = new SoundCollectionSpecifier("valveSqueak");
}

View File

@@ -0,0 +1,68 @@
using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
namespace Content.Shared.Atmos.Piping.Binary.Systems;
public abstract class SharedGasValveSystem : EntitySystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasValveComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<GasValveComponent, ActivateInWorldEvent>(OnActivate);
SubscribeLocalEvent<GasValveComponent, ExaminedEvent>(OnExamined);
}
private void OnStartup(Entity<GasValveComponent> ent, ref ComponentStartup args)
{
// We call set in startup so it sets the appearance, node state, etc.
Set(ent.Owner, ent.Comp, ent.Comp.Open);
}
public virtual void Set(EntityUid uid, GasValveComponent component, bool value)
{
component.Open = value;
Dirty(uid, component);
if (TryComp<AppearanceComponent>(uid, out var appearance))
{
_appearance.SetData(uid, FilterVisuals.Enabled, component.Open, appearance);
}
}
public void Toggle(EntityUid uid, GasValveComponent component)
{
Set(uid, component, !component.Open);
}
private void OnActivate(Entity<GasValveComponent> ent, ref ActivateInWorldEvent args)
{
if (args.Handled || !args.Complex)
return;
Toggle(ent.Owner, ent.Comp);
_audio.PlayPredicted(ent.Comp.ValveSound, ent.Owner, args.User, AudioParams.Default.WithVariation(0.25f));
args.Handled = true;
}
private void OnExamined(Entity<GasValveComponent> ent, ref ExaminedEvent args)
{
var valve = ent.Comp;
if (!Transform(ent).Anchored)
return;
if (Loc.TryGetString("gas-valve-system-examined", out var str,
("statusColor", valve.Open ? "green" : "orange"),
("open", valve.Open)))
{
args.PushMarkup(str);
}
}
}

View File

@@ -0,0 +1,80 @@
using Content.Shared.Atmos;
using Content.Shared.Guidebook;
using Robust.Shared.GameStates;
namespace Content.Shared.Atmos.Piping.Unary.Components
{
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
public sealed partial class GasThermoMachineComponent : Component
{
[DataField("inlet")]
public string InletName = "pipe";
/// <summary>
/// Current electrical power consumption, in watts. Increasing power increases the ability of the
/// thermomachine to heat or cool air.
/// </summary>
[DataField]
[GuidebookData]
public float HeatCapacity = 5000;
[DataField, AutoNetworkedField]
public float TargetTemperature = Atmospherics.T20C;
/// <summary>
/// Tolerance for temperature setpoint hysteresis.
/// </summary>
[GuidebookData]
[DataField, ViewVariables(VVAccess.ReadOnly)]
public float TemperatureTolerance = 2f;
/// <summary>
/// Implements setpoint hysteresis to prevent heater from rapidly cycling on and off at setpoint.
/// If true, add Sign(Cp)*TemperatureTolerance to the temperature setpoint.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public bool HysteresisState;
/// <summary>
/// Coefficient of performance. Output power / input power.
/// Positive for heaters, negative for freezers.
/// </summary>
[DataField("coefficientOfPerformance")]
public float Cp = 0.9f; // output power / input power, positive is heat
/// <summary>
/// Current minimum temperature
/// Ignored if heater.
/// </summary>
[DataField, AutoNetworkedField]
[GuidebookData]
public float MinTemperature = 73.15f;
/// <summary>
/// Current maximum temperature
/// Ignored if freezer.
/// </summary>
[DataField, AutoNetworkedField]
[GuidebookData]
public float MaxTemperature = 593.15f;
/// <summary>
/// Last amount of energy added/removed from the attached pipe network
/// </summary>
[DataField]
public float LastEnergyDelta;
/// <summary>
/// An percentage of the energy change that is leaked into the surrounding environment rather than the inlet pipe.
/// </summary>
[DataField]
[GuidebookData]
public float EnergyLeakPercentage;
/// <summary>
/// If true, heat is exclusively exchanged with the local atmosphere instead of the inlet pipe air
/// </summary>
[DataField]
public bool Atmospheric;
}
}

View File

@@ -7,7 +7,7 @@ public sealed record GasThermoMachineData(float EnergyDelta);
[Serializable]
[NetSerializable]
public enum ThermomachineUiKey
public enum ThermomachineUiKey : byte
{
Key
}
@@ -29,23 +29,3 @@ public sealed class GasThermomachineChangeTemperatureMessage : BoundUserInterfac
Temperature = temperature;
}
}
[Serializable]
[NetSerializable]
public sealed class GasThermomachineBoundUserInterfaceState : BoundUserInterfaceState
{
public float MinTemperature { get; }
public float MaxTemperature { get; }
public float Temperature { get; }
public bool Enabled { get; }
public bool IsHeater { get; }
public GasThermomachineBoundUserInterfaceState(float minTemperature, float maxTemperature, float temperature, bool enabled, bool isHeater)
{
MinTemperature = minTemperature;
MaxTemperature = maxTemperature;
Temperature = temperature;
Enabled = enabled;
IsHeater = isHeater;
}
}

View File

@@ -13,6 +13,7 @@ namespace Content.Shared.Atmos.Piping.Unary.Components
public ScrubberPumpDirection PumpDirection { get; set; } = ScrubberPumpDirection.Scrubbing;
public float VolumeRate { get; set; } = 200f;
public bool WideNet { get; set; } = false;
public bool AirAlarmPanicWireCut { get; set; }
public static HashSet<Gas> DefaultFilterGases = new()
{

View File

@@ -0,0 +1,61 @@
using Content.Shared.Administration.Logs;
using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.Power.EntitySystems;
namespace Content.Shared.Atmos.Piping.Unary.Systems;
public abstract class SharedGasThermoMachineSystem : EntitySystem
{
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedPowerReceiverSystem _receiver = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasThermoMachineComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<GasThermoMachineComponent, GasThermomachineToggleMessage>(OnToggleMessage);
SubscribeLocalEvent<GasThermoMachineComponent, GasThermomachineChangeTemperatureMessage>(OnChangeTemperature);
}
private void OnExamined(EntityUid uid, GasThermoMachineComponent thermoMachine, ExaminedEvent args)
{
if (Loc.TryGetString("gas-thermomachine-system-examined",
out var str,
("machineName", !IsHeater(thermoMachine) ? "freezer" : "heater"),
("tempColor", !IsHeater(thermoMachine) ? "deepskyblue" : "red"),
("temp", Math.Round(thermoMachine.TargetTemperature, 2))
))
{
args.PushMarkup(str);
}
}
public bool IsHeater(GasThermoMachineComponent comp)
{
return comp.Cp >= 0;
}
private void OnToggleMessage(EntityUid uid, GasThermoMachineComponent thermoMachine, GasThermomachineToggleMessage args)
{
var powerState = _receiver.TogglePower(uid, user: args.Actor);
_adminLogger.Add(LogType.AtmosPowerChanged, $"{ToPrettyString(args.Actor)} turned {(powerState ? "On" : "Off")} {ToPrettyString(uid)}");
DirtyUI(uid, thermoMachine);
}
private void OnChangeTemperature(EntityUid uid, GasThermoMachineComponent thermoMachine, GasThermomachineChangeTemperatureMessage args)
{
if (IsHeater(thermoMachine))
thermoMachine.TargetTemperature = MathF.Min(args.Temperature, thermoMachine.MaxTemperature);
else
thermoMachine.TargetTemperature = MathF.Max(args.Temperature, thermoMachine.MinTemperature);
thermoMachine.TargetTemperature = MathF.Max(thermoMachine.TargetTemperature, Atmospherics.TCMB);
_adminLogger.Add(LogType.AtmosTemperatureChanged, $"{ToPrettyString(args.Actor)} set temperature on {ToPrettyString(uid)} to {thermoMachine.TargetTemperature}");
Dirty(uid, thermoMachine);
DirtyUI(uid, thermoMachine);
}
protected virtual void DirtyUI(EntityUid uid, GasThermoMachineComponent? thermoMachine, UserInterfaceComponent? ui=null) {}
}

View File

@@ -1,10 +0,0 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Atmos.Visuals
{
[Serializable, NetSerializable]
public enum AtmosPlaqueVisuals
{
State
}
}

View File

@@ -0,0 +1,40 @@
using Content.Shared.Damage;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Bed.Components
{
[RegisterComponent, NetworkedComponent, AutoGenerateComponentPause, AutoGenerateComponentState]
public sealed partial class HealOnBuckleComponent : Component
{
/// <summary>
/// Damage to apply to entities that are strapped to this entity.
/// </summary>
[DataField(required: true)]
public DamageSpecifier Damage = default!;
/// <summary>
/// How frequently the damage should be applied, in seconds.
/// </summary>
[DataField(required: false)]
public float HealTime = 1f;
/// <summary>
/// Damage multiplier that gets applied if the entity is sleeping.
/// </summary>
[DataField]
public float SleepMultiplier = 3f;
/// <summary>
/// Next time that <see cref="Damage"/> will be applied.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField, AutoNetworkedField]
public TimeSpan NextHealTime = TimeSpan.Zero; //Next heal
/// <summary>
/// Action for the attached entity to be able to sleep.
/// </summary>
[DataField, AutoNetworkedField]
public EntityUid? SleepAction;
}
}

View File

@@ -0,0 +1,7 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Bed.Components;
// TODO rename this component
[RegisterComponent, NetworkedComponent]
public sealed partial class HealOnBuckleHealingComponent : Component;

View File

@@ -7,7 +7,6 @@ using Content.Shared.Mind.Components;
using Content.Shared.Mobs.Systems;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Timing;

View File

@@ -0,0 +1,49 @@
using Content.Shared.Actions;
using Content.Shared.Bed.Components;
using Content.Shared.Bed.Sleep;
using Content.Shared.Buckle.Components;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Shared.Bed;
public abstract class SharedBedSystem : EntitySystem
{
[Dependency] protected readonly IGameTiming Timing = default!;
[Dependency] private readonly ActionContainerSystem _actConts = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly SleepingSystem _sleepingSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<HealOnBuckleComponent, MapInitEvent>(OnHealMapInit);
SubscribeLocalEvent<HealOnBuckleComponent, StrappedEvent>(OnStrapped);
SubscribeLocalEvent<HealOnBuckleComponent, UnstrappedEvent>(OnUnstrapped);
}
private void OnHealMapInit(Entity<HealOnBuckleComponent> ent, ref MapInitEvent args)
{
_actConts.EnsureAction(ent.Owner, ref ent.Comp.SleepAction, SleepingSystem.SleepActionId);
Dirty(ent);
}
private void OnStrapped(Entity<HealOnBuckleComponent> bed, ref StrappedEvent args)
{
EnsureComp<HealOnBuckleHealingComponent>(bed);
bed.Comp.NextHealTime = Timing.CurTime + TimeSpan.FromSeconds(bed.Comp.HealTime);
_actionsSystem.AddAction(args.Buckle, ref bed.Comp.SleepAction, SleepingSystem.SleepActionId, bed);
Dirty(bed);
// Single action entity, cannot strap multiple entities to the same bed.
DebugTools.AssertEqual(args.Strap.Comp.BuckledEntities.Count, 1);
}
private void OnUnstrapped(Entity<HealOnBuckleComponent> bed, ref UnstrappedEvent args)
{
_actionsSystem.RemoveAction(args.Buckle, bed.Comp.SleepAction);
_sleepingSystem.TryWaking(args.Buckle.Owner);
RemComp<HealOnBuckleHealingComponent>(bed);
}
}

View File

@@ -25,14 +25,8 @@ public sealed class ProximityBeeperSystem : EntitySystem
{
if (!TryComp<BeeperComponent>(owner, out var beeper))
return;
if (args.Target == null)
{
_beeper.SetMute(owner, true, beeper);
return;
}
_beeper.SetIntervalScaling(owner, args.Distance / args.Detector.Range, beeper);
_beeper.SetMute(owner, false, beeper);
_beeper.SetIntervalScaling(owner, args.Distance / args.Detector.Comp.Range, beeper);
}
private void OnNewProximityTarget(EntityUid owner, ProximityBeeperComponent proxBeeper, ref NewProximityTargetEvent args)

View File

@@ -1,4 +1,5 @@
using Robust.Shared.Configuration;
using Robust.Shared.Maths;
namespace Content.Shared.CCVar;
@@ -72,4 +73,24 @@ public sealed partial class CCVars
/// </summary>
public static readonly CVarDef<float> DiscordWatchlistConnectionBufferTime =
CVarDef.Create("discord.watchlist_connection_buffer_time", 5f, CVar.SERVERONLY);
/// <summary>
/// URL of the Discord webhook which will receive station news acticles at the round end.
/// If left empty, disables the webhook.
/// </summary>
public static readonly CVarDef<string> DiscordNewsWebhook =
CVarDef.Create("discord.news_webhook", string.Empty, CVar.SERVERONLY);
/// <summary>
/// HEX color of station news discord webhook's embed.
/// </summary>
public static readonly CVarDef<string> DiscordNewsWebhookEmbedColor =
CVarDef.Create("discord.news_webhook_embed_color", Color.LawnGreen.ToHex(), CVar.SERVERONLY);
/// <summary>
/// Whether or not articles should be sent mid-round instead of all at once at the round's end
/// </summary>
public static readonly CVarDef<bool> DiscordNewsWebhookSendDuringRound =
CVarDef.Create("discord.news_webhook_send_during_round", false, CVar.SERVERONLY);
}

View File

@@ -11,11 +11,35 @@ public sealed partial class CCVars
CVarDef.Create("ic.restricted_names", true, CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Allows flavor text (character descriptions)
/// Sets the maximum IC name length.
/// </summary>
public static readonly CVarDef<int> MaxNameLength =
CVarDef.Create("ic.name_length", 32, CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Sets the maximum name length for a loadout name (e.g. cyborg name).
/// </summary>
public static readonly CVarDef<int> MaxLoadoutNameLength =
CVarDef.Create("ic.loadout_name_length", 32, CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Allows flavor text (character descriptions).
/// </summary>
public static readonly CVarDef<bool> FlavorText =
CVarDef.Create("ic.flavor_text", false, CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Sets the maximum length for flavor text (character descriptions).
/// </summary>
public static readonly CVarDef<int> MaxFlavorTextLength =
CVarDef.Create("ic.flavor_text_length", 512, CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Sets the maximum character length of a job on an ID.
/// </summary>
public static readonly CVarDef<int> MaxIdJobLength =
CVarDef.Create("ic.id_job_length", 30, CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Adds a period at the end of a sentence if the sentence ends in a letter.
/// </summary>

View File

@@ -194,4 +194,92 @@ public sealed partial class CCVars
/// </summary>
public static readonly CVarDef<float> GridImpulseMultiplier =
CVarDef.Create("shuttle.grid_impulse_multiplier", 0.01f, CVar.SERVERONLY);
#region impacts
/// <summary>
/// Whether shuttle impacts should do anything beyond produce a sound.
/// </summary>
[CVarControl(AdminFlags.VarEdit)]
public static readonly CVarDef<bool> ImpactEnabled =
CVarDef.Create("shuttle.impact.enabled", true, CVar.SERVERONLY);
/// <summary>
/// Minimum impact inertia to trigger special shuttle impact behaviors when impacting slower than MinimumImpactVelocity.
/// </summary>
[CVarControl(AdminFlags.VarEdit)]
public static readonly CVarDef<float> MinimumImpactInertia =
CVarDef.Create("shuttle.impact.minimum_inertia", 5f * 50f, CVar.SERVERONLY); // 100tile grid (cargo shuttle) going at 5 m/s
/// <summary>
/// Minimum velocity difference between 2 bodies for a shuttle impact to be guaranteed to trigger any special behaviors like damage.
/// </summary>
[CVarControl(AdminFlags.VarEdit)]
public static readonly CVarDef<float> MinimumImpactVelocity =
CVarDef.Create("shuttle.impact.minimum_velocity", 15f, CVar.SERVERONLY); // needed so that random space debris can be rammed
/// <summary>
/// Multiplier of Kinetic energy required to dismantle a single tile in relation to its mass
/// </summary>
[CVarControl(AdminFlags.VarEdit)]
public static readonly CVarDef<float> TileBreakEnergyMultiplier =
CVarDef.Create("shuttle.impact.tile_break_energy", 3000f, CVar.SERVERONLY);
/// <summary>
/// Multiplier of damage done to entities on colliding areas
/// </summary>
[CVarControl(AdminFlags.VarEdit)]
public static readonly CVarDef<float> ImpactDamageMultiplier =
CVarDef.Create("shuttle.impact.damage_multiplier", 0.00005f, CVar.SERVERONLY);
/// <summary>
/// Multiplier of additional structural damage to do
/// </summary>
[CVarControl(AdminFlags.VarEdit)]
public static readonly CVarDef<float> ImpactStructuralDamage =
CVarDef.Create("shuttle.impact.structural_damage", 5f, CVar.SERVERONLY);
/// <summary>
/// Kinetic energy required to spawn sparks
/// </summary>
[CVarControl(AdminFlags.VarEdit)]
public static readonly CVarDef<float> SparkEnergy =
CVarDef.Create("shuttle.impact.spark_energy", 2000000f, CVar.SERVERONLY);
/// <summary>
/// Area to consider for impact calculations
/// </summary>
[CVarControl(AdminFlags.VarEdit)]
public static readonly CVarDef<float> ImpactRadius =
CVarDef.Create("shuttle.impact.radius", 4f, CVar.SERVERONLY);
/// <summary>
/// Affects slowdown on impact
/// </summary>
[CVarControl(AdminFlags.VarEdit)]
public static readonly CVarDef<float> ImpactSlowdown =
CVarDef.Create("shuttle.impact.slowdown", 8f, CVar.SERVERONLY);
/// <summary>
/// Minimum velocity change from impact for special throw effects (e.g. stuns, beakers breaking) to occur
/// </summary>
[CVarControl(AdminFlags.VarEdit)]
public static readonly CVarDef<float> ImpactMinThrowVelocity =
CVarDef.Create("shuttle.impact.min_throw_velocity", 1f, CVar.SERVERONLY); // due to how it works this is about 16 m/s for cargo shuttle
/// <summary>
/// Affects how much damage reduction to give to grids with higher mass
/// </summary>
[CVarControl(AdminFlags.VarEdit)]
public static readonly CVarDef<float> ImpactMassBias =
CVarDef.Create("shuttle.impact.mass_bias", 0.65f, CVar.SERVERONLY);
/// <summary>
/// How much should total grid inertia affect our collision damage
/// </summary>
[CVarControl(AdminFlags.VarEdit)]
public static readonly CVarDef<float> ImpactInertiaScaling =
CVarDef.Create("shuttle.impact.inertia_scaling", 0.5f, CVar.SERVERONLY);
#endregion
}

View File

@@ -1,3 +1,5 @@
using Content.Shared.Cargo.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.Cargo.BUI;
@@ -10,13 +12,15 @@ public sealed class CargoConsoleInterfaceState : BoundUserInterfaceState
public int Capacity;
public NetEntity Station;
public List<CargoOrderData> Orders;
public List<ProtoId<CargoProductPrototype>> Products;
public CargoConsoleInterfaceState(string name, int count, int capacity, NetEntity station, List<CargoOrderData> orders)
public CargoConsoleInterfaceState(string name, int count, int capacity, NetEntity station, List<CargoOrderData> orders, List<ProtoId<CargoProductPrototype>> products)
{
Name = name;
Count = count;
Capacity = capacity;
Station = station;
Orders = orders;
Products = products;
}
}

View File

@@ -78,7 +78,13 @@ public sealed partial class CargoOrderConsoleComponent : Component
/// All of the <see cref="CargoProductPrototype.Group"/>s that are supported.
/// </summary>
[DataField, AutoNetworkedField]
public List<string> AllowedGroups = new() { "market" };
public List<ProtoId<CargoMarketPrototype>> AllowedGroups = new()
{
"market",
"SalvageJobReward2",
"SalvageJobReward3",
"SalvageJobRewardMAX",
};
/// <summary>
/// Access needed to toggle the limit on this console.

View File

@@ -11,7 +11,7 @@ public sealed partial class CargoAccountPrototype : IPrototype
{
/// <inheritdoc/>
[IdDataField]
public string ID { get; } = default!;
public string ID { get; private set; } = default!;
/// <summary>
/// Full IC name of the account.

View File

@@ -0,0 +1,14 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Cargo.Prototypes;
/// <summary>
/// Used to categorize bounties for different purposes
/// </summary>
[Prototype]
public sealed partial class CargoBountyGroupPrototype : IPrototype
{
/// <inheritdoc/>
[IdDataField]
public string ID { get; private set; } = default!;
}

View File

@@ -1,6 +1,7 @@
using Content.Shared.Whitelist;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Cargo.Prototypes;
@@ -39,6 +40,18 @@ public sealed partial class CargoBountyPrototype : IPrototype
/// </summary>
[DataField]
public string IdPrefix = "NT";
/// <summary>
/// A group used for categorizing this bounty.
/// </summary>
[DataField]
public ProtoId<CargoBountyGroupPrototype> Group = "StationBounty";
/// <summary>
/// Optional sprite representing this bounty.
/// </summary>
[DataField]
public SpriteSpecifier? Sprite;
}
[DataDefinition, Serializable, NetSerializable]

View File

@@ -0,0 +1,14 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Cargo.Prototypes;
/// <summary>
/// Defines a "market" that a cargo computer can access and make orders from.
/// </summary>
[Prototype]
public sealed partial class CargoMarketPrototype : IPrototype
{
/// <inheritdoc/>
[IdDataField]
public string ID { get; private set; } = default!;
}

View File

@@ -93,6 +93,6 @@ namespace Content.Shared.Cargo.Prototypes
/// The prototype group of the product. (e.g. Contraband)
/// </summary>
[DataField]
public string Group { get; private set; } = "market";
public ProtoId<CargoMarketPrototype> Group { get; private set; } = "market";
}
}

View File

@@ -13,7 +13,7 @@ public interface INanoTaskUiMessagePayload
/// Dispatched when a new task is created
/// </summary>
[Serializable, NetSerializable, DataRecord]
public sealed class NanoTaskAddTask : INanoTaskUiMessagePayload
public sealed partial class NanoTaskAddTask : INanoTaskUiMessagePayload
{
/// <summary>
/// The newly created task
@@ -30,7 +30,7 @@ public sealed class NanoTaskAddTask : INanoTaskUiMessagePayload
/// Dispatched when an existing task is modified
/// </summary>
[Serializable, NetSerializable, DataRecord]
public sealed class NanoTaskUpdateTask : INanoTaskUiMessagePayload
public sealed partial class NanoTaskUpdateTask : INanoTaskUiMessagePayload
{
/// <summary>
/// The task that was updated and its ID
@@ -47,7 +47,7 @@ public sealed class NanoTaskUpdateTask : INanoTaskUiMessagePayload
/// Dispatched when an existing task is deleted
/// </summary>
[Serializable, NetSerializable, DataRecord]
public sealed class NanoTaskDeleteTask : INanoTaskUiMessagePayload
public sealed partial class NanoTaskDeleteTask : INanoTaskUiMessagePayload
{
/// <summary>
/// The ID of the task to delete
@@ -64,7 +64,7 @@ public sealed class NanoTaskDeleteTask : INanoTaskUiMessagePayload
/// Dispatched when a task is requested to be printed
/// </summary>
[Serializable, NetSerializable, DataRecord]
public sealed class NanoTaskPrintTask : INanoTaskUiMessagePayload
public sealed partial class NanoTaskPrintTask : INanoTaskUiMessagePayload
{
/// <summary>
/// The NanoTask to print

View File

@@ -17,7 +17,7 @@ public enum NanoTaskPriority : byte
/// The data relating to a single NanoTask item, but not its identifier
/// </summary>
[Serializable, NetSerializable, DataRecord]
public sealed class NanoTaskItem
public sealed partial class NanoTaskItem
{
/// <summary>
/// The maximum length of the Description and TaskIsFor fields
@@ -61,7 +61,7 @@ public sealed class NanoTaskItem
/// Pairs a NanoTask item and its identifier
/// </summary>
[Serializable, NetSerializable, DataRecord]
public sealed class NanoTaskItemAndId
public sealed partial class NanoTaskItemAndId
{
public readonly int Id;
public readonly NanoTaskItem Data;

View File

@@ -1,7 +1,11 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Chemistry.Components;
[RegisterComponent]
public sealed partial class SolutionScannerComponent : Component
{
}
/// <summary>
/// Allows an entity to examine reagents inside of containers, puddles and similiar via the examine verb.
/// Works when added either directly to an entity or to piece of clothing worn by that entity.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class SolutionScannerComponent : Component;

View File

@@ -1,23 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Chemistry.Dispenser
{
/// <summary>
/// Is simply a list of reagents defined in yaml. This can then be set as a
/// <see cref="SharedReagentDispenserComponent"/>s <c>pack</c> value (also in yaml),
/// to define which reagents it's able to dispense. Based off of how vending
/// machines define their inventory.
/// </summary>
[Serializable, NetSerializable, Prototype]
public sealed partial class ReagentDispenserInventoryPrototype : IPrototype
{
[DataField("inventory", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public List<string> Inventory = new();
[ViewVariables, IdDataField]
public string ID { get; private set; } = default!;
}
}

View File

@@ -161,7 +161,7 @@ namespace Content.Shared.Chemistry.Reagent
public float PricePerUnit;
[DataField]
public SoundSpecifier FootstepSound = new SoundCollectionSpecifier("FootstepWater", AudioParams.Default.WithVolume(6));
public SoundSpecifier FootstepSound = new SoundCollectionSpecifier("FootstepPuddle");
public FixedPoint2 ReactionTile(TileRef tile, FixedPoint2 reactVolume, IEntityManager entityManager, List<ReagentData>? data)
{

View File

@@ -1,5 +1,6 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Content.Shared.Storage;
using Robust.Shared.Serialization;
namespace Content.Shared.Chemistry
@@ -66,11 +67,25 @@ namespace Content.Shared.Chemistry
[Serializable, NetSerializable]
public sealed class ReagentDispenserDispenseReagentMessage : BoundUserInterfaceMessage
{
public readonly string SlotId;
public readonly ItemStorageLocation StorageLocation;
public ReagentDispenserDispenseReagentMessage(string slotId)
public ReagentDispenserDispenseReagentMessage(ItemStorageLocation storageLocation)
{
SlotId = slotId;
StorageLocation = storageLocation;
}
}
/// <summary>
/// Message sent by the user interface to ask the reagent dispenser to eject a container
/// </summary>
[Serializable, NetSerializable]
public sealed class ReagentDispenserEjectContainerMessage : BoundUserInterfaceMessage
{
public readonly ItemStorageLocation StorageLocation;
public ReagentDispenserEjectContainerMessage(ItemStorageLocation storageLocation)
{
StorageLocation = storageLocation;
}
}
@@ -94,9 +109,9 @@ namespace Content.Shared.Chemistry
}
[Serializable, NetSerializable]
public sealed class ReagentInventoryItem(string storageSlotId, string reagentLabel, FixedPoint2 quantity, Color reagentColor)
public sealed class ReagentInventoryItem(ItemStorageLocation storageLocation, string reagentLabel, FixedPoint2 quantity, Color reagentColor)
{
public string StorageSlotId = storageSlotId;
public ItemStorageLocation StorageLocation = storageLocation;
public string ReagentLabel = reagentLabel;
public FixedPoint2 Quantity = quantity;
public Color ReagentColor = reagentColor;

View File

@@ -19,11 +19,11 @@ public sealed partial class CloningSettingsPrototype : IPrototype, IInheritingPr
public string ID { get; private set; } = default!;
[ParentDataField(typeof(PrototypeIdArraySerializer<CloningSettingsPrototype>))]
public string[]? Parents { get; }
public string[]? Parents { get; private set; }
[AbstractDataField]
[NeverPushInheritance]
public bool Abstract { get; }
public bool Abstract { get; private set; }
/// <summary>
/// Determines if cloning can be prevented by traits etc.

View File

@@ -65,14 +65,14 @@ public abstract class ClothingSystem : EntitySystem
if (!_invSystem.TryUnequip(userEnt, slotDef.Name, true, inventory: userEnt, checkDoafter: true))
continue;
if (!_invSystem.TryEquip(userEnt, toEquipEnt, slotDef.Name, true, inventory: userEnt, clothing: toEquipEnt, checkDoafter: true, triggerHandContact: true))
if (!_invSystem.TryEquip(userEnt, toEquipEnt, slotDef.Name, inventory: userEnt, clothing: toEquipEnt, checkDoafter: true, triggerHandContact: true))
continue;
_handsSystem.PickupOrDrop(userEnt, slotEntity.Value, handsComp: userEnt);
}
else
{
if (!_invSystem.TryEquip(userEnt, toEquipEnt, slotDef.Name, true, inventory: userEnt, clothing: toEquipEnt, checkDoafter: true, triggerHandContact: true))
if (!_invSystem.TryEquip(userEnt, toEquipEnt, slotDef.Name, inventory: userEnt, clothing: toEquipEnt, checkDoafter: true, triggerHandContact: true))
continue;
}

View File

@@ -13,7 +13,6 @@ namespace Content.Shared.Clothing.EntitySystems;
public abstract class SharedChameleonClothingSystem : EntitySystem
{
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ClothingSystem _clothingSystem = default!;
[Dependency] private readonly ContrabandSystem _contraband = default!;
@@ -66,7 +65,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
// item sprite logic
if (TryComp(uid, out ItemComponent? item) &&
proto.TryGetComponent(out ItemComponent? otherItem, _factory))
proto.TryGetComponent(out ItemComponent? otherItem, Factory))
{
_itemSystem.CopyVisuals(uid, otherItem, item);
}
@@ -126,7 +125,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
return false;
// check if it is marked as valid chameleon target
if (!proto.TryGetComponent(out TagComponent? tag, _factory) || !_tag.HasTag(tag, WhitelistChameleonTag))
if (!proto.TryGetComponent(out TagComponent? tag, Factory) || !_tag.HasTag(tag, WhitelistChameleonTag))
return false;
if (requiredTag != null && !_tag.HasTag(tag, requiredTag))

View File

@@ -40,7 +40,6 @@ namespace Content.Shared.Cuffs
// TODO remove all the IsServer() checks.
public abstract partial class SharedCuffableSystem : EntitySystem
{
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLog = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
@@ -146,7 +145,7 @@ namespace Content.Shared.Cuffs
private void OnStartup(EntityUid uid, CuffableComponent component, ComponentInit args)
{
component.Container = _container.EnsureContainer<Container>(uid, _componentFactory.GetComponentName(component.GetType()));
component.Container = _container.EnsureContainer<Container>(uid, Factory.GetComponentName(component.GetType()));
}
private void OnRejuvenate(EntityUid uid, CuffableComponent component, RejuvenateEvent args)

View File

@@ -1,5 +1,6 @@
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Events;
using Content.Shared.Destructible;
using Content.Shared.Rejuvenate;
using Content.Shared.Slippery;
using Content.Shared.StatusEffect;
@@ -18,6 +19,7 @@ public abstract class SharedGodmodeSystem : EntitySystem
SubscribeLocalEvent<GodmodeComponent, BeforeStatusEffectAddedEvent>(OnBeforeStatusEffect);
SubscribeLocalEvent<GodmodeComponent, BeforeStaminaDamageEvent>(OnBeforeStaminaDamage);
SubscribeLocalEvent<GodmodeComponent, SlipAttemptEvent>(OnSlipAttempt);
SubscribeLocalEvent<GodmodeComponent, DestructionAttemptEvent>(OnDestruction);
}
private void OnSlipAttempt(EntityUid uid, GodmodeComponent component, SlipAttemptEvent args)
@@ -40,6 +42,11 @@ public abstract class SharedGodmodeSystem : EntitySystem
args.Cancelled = true;
}
private void OnDestruction(Entity<GodmodeComponent> ent, ref DestructionAttemptEvent args)
{
args.Cancel();
}
public virtual void EnableGodmode(EntityUid uid, GodmodeComponent? godmode = null)
{
godmode ??= EnsureComp<GodmodeComponent>(uid);

View File

@@ -0,0 +1,65 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Delivery;
/// <summary>
/// Component given to deliveries.
/// This delivery will "prime" based on circumstances defined in the datafield.
/// When primed, it will attempt to explode every few seconds, with the chance increasing each time it fails to do so.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
[Access(typeof(DeliveryModifierSystem))]
public sealed partial class DeliveryBombComponent : Component
{
/// <summary>
/// How often will this bomb retry to explode.
/// </summary>
[DataField]
public TimeSpan ExplosionRetryDelay = TimeSpan.FromSeconds(5);
/// <summary>
/// The time at which the next retry will happen
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField]
public TimeSpan NextExplosionRetry;
/// <summary>
/// The chance this bomb explodes each time it attempts to do so.
/// </summary>
[DataField, AutoNetworkedField]
public float ExplosionChance = 0.01f;
/// <summary>
/// How much should the chance of explosion increase each failed retry?
/// </summary>
[DataField]
public float ExplosionChanceRetryIncrease = 0.01f;
/// <summary>
/// Should this bomb get primed when the delivery is unlocked?
/// </summary>
[DataField]
public bool PrimeOnUnlock = true;
/// <summary>
/// Should this bomb get primed when the delivery is broken?
/// Requires to be fragile as well.
/// </summary>
[DataField]
public bool PrimeOnBreakage = true;
/// <summary>
/// Should this bomb get primed when the delivery expires?
/// Requires to be priority as well.
/// </summary>
[DataField]
public bool PrimeOnExpire = true;
/// <summary>
/// Multiplier to choose when a crazy person actually opens it.
/// Multiplicative, not additive.
/// </summary>
[DataField]
public float SpesoMultiplier = 1.5f;
}

View File

@@ -1,6 +1,10 @@
using Content.Shared.Audio;
using Content.Shared.Destructible;
using Content.Shared.Examine;
using Content.Shared.Explosion.EntitySystems;
using Content.Shared.NameModifier.EntitySystems;
using JetBrains.Annotations;
using Robust.Shared.Network;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
@@ -14,8 +18,11 @@ public sealed partial class DeliveryModifierSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly NameModifierSystem _nameModifier = default!;
[Dependency] private readonly SharedDeliverySystem _delivery = default!;
[Dependency] private readonly SharedExplosionSystem _explosion = default!;
[Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!;
public override void Initialize()
{
@@ -33,6 +40,14 @@ public sealed partial class DeliveryModifierSystem : EntitySystem
SubscribeLocalEvent<DeliveryFragileComponent, BreakageEventArgs>(OnFragileBreakage);
SubscribeLocalEvent<DeliveryFragileComponent, ExaminedEvent>(OnFragileExamine);
SubscribeLocalEvent<DeliveryFragileComponent, GetDeliveryMultiplierEvent>(OnGetFragileMultiplier);
SubscribeLocalEvent<DeliveryBombComponent, ComponentStartup>(OnExplosiveStartup);
SubscribeLocalEvent<PrimedDeliveryBombComponent, MapInitEvent>(OnPrimedExplosiveMapInit);
SubscribeLocalEvent<DeliveryBombComponent, ExaminedEvent>(OnExplosiveExamine);
SubscribeLocalEvent<DeliveryBombComponent, GetDeliveryMultiplierEvent>(OnGetExplosiveMultiplier);
SubscribeLocalEvent<DeliveryBombComponent, DeliveryUnlockedEvent>(OnExplosiveUnlock);
SubscribeLocalEvent<DeliveryBombComponent, DeliveryPriorityExpiredEvent>(OnExplosiveExpire);
SubscribeLocalEvent<DeliveryBombComponent, BreakageEventArgs>(OnExplosiveBreak);
}
#region Random
@@ -119,12 +134,80 @@ public sealed partial class DeliveryModifierSystem : EntitySystem
}
#endregion
#region Explosive
private void OnExplosiveStartup(Entity<DeliveryBombComponent> ent, ref ComponentStartup args)
{
_delivery.UpdateBombVisuals(ent);
}
private void OnPrimedExplosiveMapInit(Entity<PrimedDeliveryBombComponent> ent, ref MapInitEvent args)
{
if (!TryComp<DeliveryBombComponent>(ent, out var bomb))
return;
bomb.NextExplosionRetry = _timing.CurTime;
}
private void OnExplosiveExamine(Entity<DeliveryBombComponent> ent, ref ExaminedEvent args)
{
var trueName = _nameModifier.GetBaseName(ent.Owner);
var isPrimed = HasComp<PrimedDeliveryBombComponent>(ent);
if (isPrimed)
args.PushMarkup(Loc.GetString("delivery-bomb-primed-examine", ("type", trueName)));
else
args.PushMarkup(Loc.GetString("delivery-bomb-examine", ("type", trueName)));
}
private void OnGetExplosiveMultiplier(Entity<DeliveryBombComponent> ent, ref GetDeliveryMultiplierEvent args)
{
// Big danger for big rewards
args.MultiplicativeMultiplier += ent.Comp.SpesoMultiplier;
}
private void OnExplosiveUnlock(Entity<DeliveryBombComponent> ent, ref DeliveryUnlockedEvent args)
{
if (!ent.Comp.PrimeOnUnlock)
return;
PrimeBombDelivery(ent);
}
private void OnExplosiveExpire(Entity<DeliveryBombComponent> ent, ref DeliveryPriorityExpiredEvent args)
{
if (!ent.Comp.PrimeOnExpire)
return;
PrimeBombDelivery(ent);
}
private void OnExplosiveBreak(Entity<DeliveryBombComponent> ent, ref BreakageEventArgs args)
{
if (!ent.Comp.PrimeOnBreakage)
return;
PrimeBombDelivery(ent);
}
[PublicAPI]
public void PrimeBombDelivery(Entity<DeliveryBombComponent> ent)
{
EnsureComp<PrimedDeliveryBombComponent>(ent);
_delivery.UpdateBombVisuals(ent);
_ambientSound.SetAmbience(ent, true);
}
#endregion
#region Update Loops
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdatePriorty(frameTime);
UpdateBomb(frameTime);
}
private void UpdatePriorty(float frameTime)
@@ -148,6 +231,27 @@ public sealed partial class DeliveryModifierSystem : EntitySystem
}
}
}
private void UpdateBomb(float frameTime)
{
var bombQuery = EntityQueryEnumerator<PrimedDeliveryBombComponent, DeliveryBombComponent>();
var curTime = _timing.CurTime;
while (bombQuery.MoveNext(out var uid, out _, out var bombData))
{
if (bombData.NextExplosionRetry > curTime)
continue;
bombData.NextExplosionRetry += bombData.ExplosionRetryDelay;
// Explosions cannot be predicted.
if (_net.IsServer && _random.NextFloat() < bombData.ExplosionChance)
_explosion.TriggerExplosive(uid);
bombData.ExplosionChance += bombData.ExplosionChanceRetryIncrease;
Dirty(uid, bombData);
}
}
#endregion
}

View File

@@ -9,6 +9,7 @@ public enum DeliveryVisuals : byte
IsTrash,
IsBroken,
IsFragile,
IsBomb,
PriorityState,
JobIcon,
}
@@ -21,6 +22,14 @@ public enum DeliveryPriorityState : byte
Inactive,
}
[Serializable, NetSerializable]
public enum DeliveryBombState : byte
{
Off,
Inactive,
Primed,
}
[Serializable, NetSerializable]
public enum DeliverySpawnerVisuals : byte
{

View File

@@ -0,0 +1,11 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Delivery;
/// <summary>
/// Component given to deliveries.
/// Indicates this bomb delivery is primed.
/// </summary>
[RegisterComponent, NetworkedComponent]
[Access(typeof(DeliveryModifierSystem))]
public sealed partial class PrimedDeliveryBombComponent : Component;

View File

@@ -259,6 +259,13 @@ public abstract class SharedDeliverySystem : EntitySystem
_appearance.SetData(ent, DeliveryVisuals.IsFragile, isFragile);
}
public void UpdateBombVisuals(Entity<DeliveryBombComponent> ent)
{
var isPrimed = HasComp<PrimedDeliveryBombComponent>(ent);
_appearance.SetData(ent, DeliveryVisuals.IsBomb, isPrimed ? DeliveryBombState.Primed : DeliveryBombState.Inactive);
}
protected void UpdateDeliverySpawnerVisuals(EntityUid uid, int contents)
{
_appearance.SetData(uid, DeliverySpawnerVisuals.Contents, contents > 0);

View File

@@ -5,12 +5,18 @@ public abstract class SharedDestructibleSystem : EntitySystem
/// <summary>
/// Force entity to be destroyed and deleted.
/// </summary>
public void DestroyEntity(EntityUid owner)
public bool DestroyEntity(EntityUid owner)
{
var eventArgs = new DestructionEventArgs();
var ev = new DestructionAttemptEvent();
RaiseLocalEvent(owner, ev);
if (ev.Cancelled)
return false;
var eventArgs = new DestructionEventArgs();
RaiseLocalEvent(owner, eventArgs);
QueueDel(owner);
return true;
}
/// <summary>
@@ -23,6 +29,14 @@ public abstract class SharedDestructibleSystem : EntitySystem
}
}
/// <summary>
/// Raised before an entity is about to be destroyed and deleted
/// </summary>
public sealed class DestructionAttemptEvent : CancellableEntityEventArgs
{
}
/// <summary>
/// Raised when entity is destroyed and about to be deleted.
/// </summary>

View File

@@ -40,3 +40,15 @@ public enum LogicGateLayers : byte
InputB,
Output
}
/// <summary>
/// The possible states of a logic-capable signal.
/// Stored in network payload data of device network messages.
/// </summary>
[Serializable, NetSerializable]
public enum SignalState : byte
{
Momentary, // Instantaneous pulse high, compatibility behavior
Low,
High
}

View File

@@ -1,22 +0,0 @@
using System.Collections.Immutable;
using Content.Shared.Storage;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Shared.EntityList;
[Prototype]
public sealed partial class EntityLootTablePrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
[DataField("entries")]
public ImmutableList<EntitySpawnEntry> Entries = ImmutableList<EntitySpawnEntry>.Empty;
/// <inheritdoc cref="EntitySpawnCollection.GetSpawns"/>
public List<string> GetSpawns(IRobustRandom random)
{
return EntitySpawnCollection.GetSpawns(Entries, random);
}
}

View File

@@ -9,6 +9,12 @@ public sealed class EntityTableSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public IEnumerable<EntProtoId> GetSpawns(EntityTablePrototype entTableProto, System.Random? rand = null)
{
// convenient
return GetSpawns(entTableProto.Table, rand);
}
public IEnumerable<EntProtoId> GetSpawns(EntityTableSelector? table, System.Random? rand = null)
{
if (table == null)

View File

@@ -0,0 +1,54 @@
using System.Globalization;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
using Robust.Shared.Utility;
namespace Content.Shared.EntityTable.ValueSelector;
[TypeSerializer]
public sealed class NumberSelectorTypeSerializer :
ITypeReader<NumberSelector, ValueDataNode>
{
public ValidationNode Validate(ISerializationManager serializationManager,
ValueDataNode node,
IDependencyCollection dependencies,
ISerializationContext? context = null)
{
// ConstantNumberSelector validation
if (int.TryParse(node.Value, out _))
return new ValidatedValueNode(node);
// RangeNumberSelector validation
if (VectorSerializerUtility.TryParseArgs(node.Value, 2, out _))
{
return new ValidatedValueNode(node);
}
return new ErrorNode(node, "Custom validation not supported! Please specify the type manually!");
}
public NumberSelector Read(ISerializationManager serializationManager,
ValueDataNode node,
IDependencyCollection dependencies,
SerializationHookContext hookCtx,
ISerializationContext? context = null,
ISerializationManager.InstantiationDelegate<NumberSelector>? instanceProvider = null)
{
var type = typeof(NumberSelector);
if (int.TryParse(node.Value, out var result))
return new ConstantNumberSelector(result);
if (VectorSerializerUtility.TryParseArgs(node.Value, 2, out var args))
{
var x = int.Parse(args[0], CultureInfo.InvariantCulture);
var y = int.Parse(args[1], CultureInfo.InvariantCulture);
return new RangeNumberSelector(new Vector2i(x, y));
}
return (NumberSelector) serializationManager.Read(type, node, context)!;
}
}

View File

@@ -8,6 +8,11 @@ public sealed partial class RangeNumberSelector : NumberSelector
[DataField]
public Vector2i Range = new(1, 1);
public RangeNumberSelector(Vector2i range)
{
Range = range;
}
public override int Get(System.Random rand)
{
// rand.Next() is inclusive on the first number and exclusive on the second number,

View File

@@ -6,8 +6,6 @@ namespace Content.Shared.Examine
{
public abstract partial class ExamineSystemShared : EntitySystem
{
[Dependency] private readonly IComponentFactory _componentFactory = default!;
public const string DefaultIconTexture = "/Textures/Interface/examine-star.png";
public override void Initialize()
@@ -55,7 +53,7 @@ namespace Content.Shared.Examine
{
foreach (var comp in components)
{
if (!_componentFactory.TryGetRegistration(comp, out var componentRegistration))
if (!Factory.TryGetRegistration(comp, out var componentRegistration))
continue;
if (!HasComp(uid, componentRegistration.Type))
@@ -117,7 +115,7 @@ namespace Content.Shared.Examine
if (TryComp<GroupExamineComponent>(verbsEvent.Target, out var groupExamine))
{
// Make sure we have the component name as a string
var componentName = _componentFactory.GetComponentName(component.GetType());
var componentName = Factory.GetComponentName(component.GetType());
foreach (var examineGroup in groupExamine.Group)
{
@@ -177,7 +175,7 @@ namespace Content.Shared.Examine
/// </summary>
public void AddDetailedExamineVerb(GetVerbsEvent<ExamineVerb> verbsEvent, Component component, FormattedMessage message, string verbText, string iconTexture = DefaultIconTexture, string hoverMessage = "", bool isHoverExamine = false)
{
var componentName = _componentFactory.GetComponentName(component.GetType());
var componentName = Factory.GetComponentName(component.GetType());
AddDetailedExamineVerb(verbsEvent, component, new ExamineEntry(componentName, 0f, message), verbText, iconTexture, hoverMessage, isHoverExamine);
}

View File

@@ -0,0 +1,80 @@
using Content.Shared.Atmos;
using Content.Shared.Explosion.EntitySystems;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Explosion.Components.OnTrigger;
/// <summary>
/// Contains a GasMixture that will release its contents to the atmosphere when triggered.
/// </summary>
[RegisterComponent, NetworkedComponent]
[AutoGenerateComponentPause]
[Access(typeof(SharedReleaseGasOnTriggerSystem))]
public sealed partial class ReleaseGasOnTriggerComponent : Component
{
/// <summary>
/// Whether this grenade is active and releasing gas.
/// Set to true when triggered, which starts gas release.
/// </summary>
[DataField]
public bool Active;
/// <summary>
/// The gas mixture that will be released to the current tile atmosphere when triggered.
/// </summary>
[DataField]
public GasMixture Air;
/// <summary>
/// Time at which the next release will occur.
/// This is automatically set when the grenade activates.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan NextReleaseTime = TimeSpan.Zero;
/// <summary>
/// The cap at which this grenade can fill the exposed atmosphere to.
/// This component automatically removes itself when the pressure limit is reached.
/// </summary>
/// <example>If set to 101.325, the grenade will only fill the exposed
/// atmosphere up to 101.325 kPa.</example>
/// <remarks>If zero, this limit won't be respected.</remarks>
[DataField]
public float PressureLimit;
/// <summary>
/// How often the grenade will release gas.
/// </summary>
[DataField]
public TimeSpan ReleaseInterval = TimeSpan.FromSeconds(1);
/// <summary>
/// A float from 0 to 1, representing a partial portion of the moles
/// of the gas mixture that will be
/// released to the current tile atmosphere when triggered.
/// </summary>
/// <remarks>If undefined on the prototype, the entire molar amount will be transferred.</remarks>
[DataField]
public float RemoveFraction = 1;
/// <summary>
/// Stores the total moles initially in the grenade upon activation.
/// Used to calculate the moles released over time.
/// </summary>
/// <remarks>Set when the grenade is activated.</remarks>
[DataField(readOnly: true)]
public float StartingTotalMoles;
}
/// <summary>
/// Represents visual states for whatever visuals that need to be applied
/// on state changes.
/// </summary>
[Serializable, NetSerializable]
public enum ReleaseGasOnTriggerVisuals : byte
{
Key,
}

View File

@@ -0,0 +1,5 @@
namespace Content.Shared.Explosion.EntitySystems;
public abstract partial class SharedReleaseGasOnTriggerSystem : EntitySystem;
// I have dreams of Atmos in shared.

View File

@@ -36,14 +36,14 @@ public sealed partial class ExplosionPrototype : IPrototype
/// explosion intensity to a tile break chance via linear interpolation.
/// </summary>
[DataField("tileBreakChance")]
private float[] _tileBreakChance = { 0f, 1f };
public float[] _tileBreakChance = { 0f, 1f };
/// <summary>
/// This set of points, together with <see cref="_tileBreakChance"/> define a function that maps the
/// explosion intensity to a tile break chance via linear interpolation.
/// </summary>
[DataField("tileBreakIntensity")]
private float[] _tileBreakIntensity = {0f, 15f };
public float[] _tileBreakIntensity = { 0f, 15f };
/// <summary>
/// When a tile is broken by an explosion, the intensity is reduced by this amount and is used to try and
@@ -115,19 +115,13 @@ public sealed partial class ExplosionPrototype : IPrototype
/// </summary>
public float TileBreakChance(float intensity)
{
if (_tileBreakChance.Length == 0 || _tileBreakChance.Length != _tileBreakIntensity.Length)
{
Logger.Error($"Malformed tile break chance definitions for explosion prototype: {ID}");
return 0;
}
if (intensity >= _tileBreakIntensity[^1] || _tileBreakIntensity.Length == 1)
return _tileBreakChance[^1];
if (intensity <= _tileBreakIntensity[0])
return _tileBreakChance[0];
int i = Array.FindIndex(_tileBreakIntensity, k => k >= intensity);
var i = Array.FindIndex(_tileBreakIntensity, k => k >= intensity);
var slope = (_tileBreakChance[i] - _tileBreakChance[i - 1]) / (_tileBreakIntensity[i] - _tileBreakIntensity[i - 1]);
return _tileBreakChance[i - 1] + slope * (intensity - _tileBreakIntensity[i - 1]);

View File

@@ -118,8 +118,11 @@ namespace Content.Shared.Friction
// You may think you can just pass the body.LinearVelocity to the Friction function and edit it there!
// But doing so is unpredicted! And you will doom yourself to 1000 years of rubber banding!
var velocity = body.LinearVelocity;
var angVelocity = body.AngularVelocity;
_mover.Friction(0f, frameTime, friction, ref velocity);
_mover.Friction(0f, frameTime, friction, ref angVelocity);
PhysicsSystem.SetLinearVelocity(uid, velocity, body: body);
PhysicsSystem.SetAngularVelocity(uid, angVelocity, body: body);
}
}

View File

@@ -31,6 +31,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
.Bind(ContentKeyFunctions.UseItemInHand, InputCmdHandler.FromDelegate(HandleUseItem, handle: false, outsidePrediction: false))
.Bind(ContentKeyFunctions.AltUseItemInHand, InputCmdHandler.FromDelegate(HandleAltUseInHand, handle: false, outsidePrediction: false))
.Bind(ContentKeyFunctions.SwapHands, InputCmdHandler.FromDelegate(SwapHandsPressed, handle: false, outsidePrediction: false))
.Bind(ContentKeyFunctions.SwapHandsReverse, InputCmdHandler.FromDelegate(SwapHandsReversePressed, handle: false, outsidePrediction: false))
.Bind(ContentKeyFunctions.Drop, new PointerInputCmdHandler(DropPressed))
.Register<SharedHandsSystem>();
}
@@ -79,6 +80,16 @@ public abstract partial class SharedHandsSystem : EntitySystem
}
private void SwapHandsPressed(ICommonSession? session)
{
SwapHands(session, false);
}
private void SwapHandsReversePressed(ICommonSession? session)
{
SwapHands(session, true);
}
private void SwapHands(ICommonSession? session, bool reverse)
{
if (!TryComp(session?.AttachedEntity, out HandsComponent? component))
return;
@@ -89,8 +100,9 @@ public abstract partial class SharedHandsSystem : EntitySystem
if (component.ActiveHand == null || component.Hands.Count < 2)
return;
var newActiveIndex = component.SortedHands.IndexOf(component.ActiveHand.Name) + 1;
var nextHand = component.SortedHands[newActiveIndex % component.Hands.Count];
var currentIndex = component.SortedHands.IndexOf(component.ActiveHand.Name);
var newActiveIndex = (currentIndex + (reverse ? -1 : 1) + component.Hands.Count) % component.Hands.Count;
var nextHand = component.SortedHands[newActiveIndex];
TrySetActiveHand(session.AttachedEntity.Value, nextHand, component);
}

View File

@@ -9,5 +9,5 @@ public sealed partial class HolographicAvatarComponent : Component
/// The prototype sprite layer data for the hologram
/// </summary>
[DataField, AutoNetworkedField]
public PrototypeLayerData[] LayerData;
public PrototypeLayerData[]? LayerData = null;
}

View File

@@ -14,7 +14,6 @@ namespace Content.Shared.Implants;
public abstract class SharedSubdermalImplantSystem : EntitySystem
{
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly TagSystem _tag = default!;
@@ -38,7 +37,7 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
private void OnInsert(EntityUid uid, SubdermalImplantComponent component, EntGotInsertedIntoContainerMessage args)
{
if (component.ImplantedEntity == null || _net.IsClient)
if (component.ImplantedEntity == null)
return;
if (!string.IsNullOrWhiteSpace(component.ImplantAction))
@@ -54,7 +53,7 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
if (_tag.HasTag(implant, MicroBombTag))
{
_container.Remove(implant, implantContainer);
QueueDel(implant);
PredictedQueueDel(implant);
}
}
}

View File

@@ -35,6 +35,7 @@ namespace Content.Shared.Input
public static readonly BoundKeyFunction OpenBelt = "OpenBelt";
public static readonly BoundKeyFunction OpenAHelp = "OpenAHelp";
public static readonly BoundKeyFunction SwapHands = "SwapHands";
public static readonly BoundKeyFunction SwapHandsReverse = "SwapHandsReverse";
public static readonly BoundKeyFunction MoveStoredItem = "MoveStoredItem";
public static readonly BoundKeyFunction RotateStoredItem = "RotateStoredItem";
public static readonly BoundKeyFunction SaveItemLocation = "SaveItemLocation";

View File

@@ -34,6 +34,7 @@ using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
@@ -53,21 +54,22 @@ namespace Content.Shared.Interaction
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly ISharedChatManager _chat = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly PullingSystem _pullSystem = default!;
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedPhysicsSystem _broadphase = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedVerbSystem _verbSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly UseDelaySystem _useDelay = default!;
[Dependency] private readonly PullingSystem _pullSystem = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
[Dependency] private readonly SharedStrippableSystem _strippable = default!;
[Dependency] private readonly SharedPlayerRateLimitManager _rateLimit = default!;
[Dependency] private readonly ISharedChatManager _chat = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly UseDelaySystem _useDelay = default!;
private EntityQuery<IgnoreUIRangeComponent> _ignoreUiRangeQuery;
private EntityQuery<FixturesComponent> _fixtureQuery;
@@ -854,7 +856,20 @@ namespace Content.Shared.Interaction
{
// If the target is an item, we ignore any colliding entities. Currently done so that if items get stuck
// inside of walls, users can still pick them up.
ignored.UnionWith(_broadphase.GetEntitiesIntersectingBody(target, (int) collisionMask, false, physics)); // Note: This also bypasses items underneath doors, which may be problematic if it'd cause undesirable behavior.
// TODO: Bandaid, alloc spam
// We use 0.01 range just in case it's perfectly in between 2 walls and 1 gets missed.
foreach (var otherEnt in _lookup.GetEntitiesInRange(target, 0.01f, flags: LookupFlags.Static))
{
if (target == otherEnt ||
!_physicsQuery.TryComp(otherEnt, out var otherBody) ||
!otherBody.CanCollide ||
((int) collisionMask & otherBody.CollisionLayer) == 0x0)
{
continue;
}
ignored.Add(otherEnt);
}
}
else if (_wallMountQuery.TryComp(target, out var wallMount))
{

View File

@@ -1,60 +1,55 @@
namespace Content.Shared.Inventory.Events;
public abstract class EquipAttemptBase : CancellableEntityEventArgs
public abstract class EquipAttemptBase(EntityUid equipee, EntityUid equipTarget, EntityUid equipment,
SlotDefinition slotDefinition) : CancellableEntityEventArgs, IInventoryRelayEvent
{
public SlotFlags TargetSlots { get; } = SlotFlags.WITHOUT_POCKET;
/// <summary>
/// The entity performing the action. NOT necessarily the one actually "receiving" the equipment.
/// </summary>
public readonly EntityUid Equipee;
public readonly EntityUid Equipee = equipee;
/// <summary>
/// The entity being equipped to.
/// </summary>
public readonly EntityUid EquipTarget;
public readonly EntityUid EquipTarget = equipTarget;
/// <summary>
/// The entity to be equipped.
/// </summary>
public readonly EntityUid Equipment;
public readonly EntityUid Equipment = equipment;
/// <summary>
/// The slotFlags of the slot to equip the entity into.
/// </summary>
public readonly SlotFlags SlotFlags;
public readonly SlotFlags SlotFlags = slotDefinition.SlotFlags;
/// <summary>
/// The slot the entity is being equipped to.
/// </summary>
public readonly string Slot;
public readonly string Slot = slotDefinition.Name;
/// <summary>
/// If cancelling and wanting to provide a custom reason, use this field. Not that this expects a loc-id.
/// </summary>
public string? Reason;
public EquipAttemptBase(EntityUid equipee, EntityUid equipTarget, EntityUid equipment,
SlotDefinition slotDefinition)
{
EquipTarget = equipTarget;
Equipment = equipment;
Equipee = equipee;
SlotFlags = slotDefinition.SlotFlags;
Slot = slotDefinition.Name;
}
}
public sealed class BeingEquippedAttemptEvent : EquipAttemptBase
{
public BeingEquippedAttemptEvent(EntityUid equipee, EntityUid equipTarget, EntityUid equipment,
SlotDefinition slotDefinition) : base(equipee, equipTarget, equipment, slotDefinition)
{
}
}
/// <summary>
/// Raised on the item that is being equipped.
/// </summary>
public sealed class BeingEquippedAttemptEvent(EntityUid equipee, EntityUid equipTarget, EntityUid equipment,
SlotDefinition slotDefinition) : EquipAttemptBase(equipee, equipTarget, equipment, slotDefinition);
public sealed class IsEquippingAttemptEvent : EquipAttemptBase
{
public IsEquippingAttemptEvent(EntityUid equipee, EntityUid equipTarget, EntityUid equipment,
SlotDefinition slotDefinition) : base(equipee, equipTarget, equipment, slotDefinition)
{
}
}
/// <summary>
/// Raised on the entity that is equipping an item.
/// </summary>
public sealed class IsEquippingAttemptEvent(EntityUid equipee, EntityUid equipTarget, EntityUid equipment,
SlotDefinition slotDefinition) : EquipAttemptBase(equipee, equipTarget, equipment, slotDefinition);
/// <summary>
/// Raised on the entity on who item is being equipped.
/// </summary>
public sealed class IsEquippingTargetAttemptEvent(EntityUid equipee, EntityUid equipTarget, EntityUid equipment,
SlotDefinition slotDefinition) : EquipAttemptBase(equipee, equipTarget, equipment, slotDefinition);

View File

@@ -1,60 +1,55 @@
namespace Content.Shared.Inventory.Events;
public abstract class UnequipAttemptEventBase : CancellableEntityEventArgs
public abstract class UnequipAttemptEventBase(EntityUid unequipee, EntityUid unEquipTarget, EntityUid equipment,
SlotDefinition slotDefinition) : CancellableEntityEventArgs, IInventoryRelayEvent
{
public SlotFlags TargetSlots { get; } = SlotFlags.WITHOUT_POCKET;
/// <summary>
/// The entity performing the action. NOT necessarily the same as the entity whose equipment is being removed..
/// </summary>
public readonly EntityUid Unequipee;
public readonly EntityUid Unequipee = unequipee;
/// <summary>
/// The entity being unequipped from.
/// </summary>
public readonly EntityUid UnEquipTarget;
public readonly EntityUid UnEquipTarget = unEquipTarget;
/// <summary>
/// The entity to be unequipped.
/// </summary>
public readonly EntityUid Equipment;
public readonly EntityUid Equipment = equipment;
/// <summary>
/// The slotFlags of the slot this item is being removed from.
/// </summary>
public readonly SlotFlags SlotFlags;
public readonly SlotFlags SlotFlags = slotDefinition.SlotFlags;
/// <summary>
/// The slot the entity is being unequipped from.
/// </summary>
public readonly string Slot;
public readonly string Slot = slotDefinition.Name;
/// <summary>
/// If cancelling and wanting to provide a custom reason, use this field. Not that this expects a loc-id.
/// </summary>
public string? Reason;
public UnequipAttemptEventBase(EntityUid unequipee, EntityUid unEquipTarget, EntityUid equipment,
SlotDefinition slotDefinition)
{
UnEquipTarget = unEquipTarget;
Equipment = equipment;
Unequipee = unequipee;
SlotFlags = slotDefinition.SlotFlags;
Slot = slotDefinition.Name;
}
}
public sealed class BeingUnequippedAttemptEvent : UnequipAttemptEventBase
{
public BeingUnequippedAttemptEvent(EntityUid unequipee, EntityUid unEquipTarget, EntityUid equipment,
SlotDefinition slotDefinition) : base(unequipee, unEquipTarget, equipment, slotDefinition)
{
}
}
/// <summary>
/// Raised on the item that is being unequipped.
/// </summary>
public sealed class BeingUnequippedAttemptEvent(EntityUid unequipee, EntityUid unEquipTarget, EntityUid equipment,
SlotDefinition slotDefinition) : UnequipAttemptEventBase(unequipee, unEquipTarget, equipment, slotDefinition);
public sealed class IsUnequippingAttemptEvent : UnequipAttemptEventBase
{
public IsUnequippingAttemptEvent(EntityUid unequipee, EntityUid unEquipTarget, EntityUid equipment,
SlotDefinition slotDefinition) : base(unequipee, unEquipTarget, equipment, slotDefinition)
{
}
}
/// <summary>
/// Raised on the entity that is unequipping an item.
/// </summary>
public sealed class IsUnequippingAttemptEvent(EntityUid unequipee, EntityUid unEquipTarget, EntityUid equipment,
SlotDefinition slotDefinition) : UnequipAttemptEventBase(unequipee, unEquipTarget, equipment, slotDefinition);
/// <summary>
/// Raised on the entity from who item is being unequipped.
/// </summary>
public sealed class IsUnequippingTargetAttemptEvent(EntityUid unequipee, EntityUid unEquipTarget, EntityUid equipment,
SlotDefinition slotDefinition) : UnequipAttemptEventBase(unequipee, unEquipTarget, equipment, slotDefinition);

View File

@@ -286,23 +286,21 @@ public abstract partial class InventorySystem
}
var attemptEvent = new IsEquippingAttemptEvent(actor, target, itemUid, slotDefinition);
RaiseLocalEvent(target, attemptEvent, true);
RaiseLocalEvent(actor, attemptEvent, true);
if (attemptEvent.Cancelled)
{
reason = attemptEvent.Reason ?? reason;
return false;
}
if (actor != target)
var targetAttemptEvent = new IsEquippingTargetAttemptEvent(actor, target, itemUid, slotDefinition);
RaiseLocalEvent(target, targetAttemptEvent, true);
if (targetAttemptEvent.Cancelled)
{
//reuse the event. this is gucci, right?
attemptEvent.Reason = null;
RaiseLocalEvent(actor, attemptEvent, true);
if (attemptEvent.Cancelled)
{
reason = attemptEvent.Reason ?? reason;
return false;
}
reason = targetAttemptEvent.Reason ?? reason;
return false;
}
var itemAttemptEvent = new BeingEquippedAttemptEvent(actor, target, itemUid, slotDefinition);
@@ -524,23 +522,21 @@ public abstract partial class InventorySystem
}
var attemptEvent = new IsUnequippingAttemptEvent(actor, target, itemUid, slotDefinition);
RaiseLocalEvent(target, attemptEvent, true);
RaiseLocalEvent(actor, attemptEvent, true);
if (attemptEvent.Cancelled)
{
reason = attemptEvent.Reason ?? reason;
return false;
}
if (actor != target)
var targetAttemptEvent = new IsUnequippingTargetAttemptEvent(actor, target, itemUid, slotDefinition);
RaiseLocalEvent(target, targetAttemptEvent, true);
if (targetAttemptEvent.Cancelled)
{
//reuse the event. this is gucci, right?
attemptEvent.Reason = null;
RaiseLocalEvent(actor, attemptEvent, true);
if (attemptEvent.Cancelled)
{
reason = attemptEvent.Reason ?? reason;
return false;
}
reason = targetAttemptEvent.Reason ?? reason;
return false;
}
var itemAttemptEvent = new BeingUnequippedAttemptEvent(actor, target, itemUid, slotDefinition);

View File

@@ -55,6 +55,8 @@ public partial class InventorySystem
SubscribeLocalEvent<InventoryComponent, SelfBeforeClimbEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, CoefficientQueryEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, ZombificationResistanceQueryEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, IsEquippingTargetAttemptEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, IsUnequippingTargetAttemptEvent>(RelayInventoryEvent);
// by-ref events
SubscribeLocalEvent<InventoryComponent, RefreshFrictionModifiersEvent>(RefRelayInventoryEvent);

View File

@@ -0,0 +1,16 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Inventory;
/// <summary>
/// Used to prevent items from being unequipped and equipped from slots that are listed in <see cref="Slots"/>.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SlotBlockSystem))]
public sealed partial class SlotBlockComponent : Component
{
/// <summary>
/// Slots that this entity should block.
/// </summary>
[DataField(required: true), AutoNetworkedField]
public SlotFlags Slots = SlotFlags.NONE;
}

View File

@@ -0,0 +1,35 @@
using Content.Shared.Inventory.Events;
namespace Content.Shared.Inventory;
/// <summary>
/// Handles prevention of items being unequipped and equipped from slots that are blocked by <see cref="SlotBlockComponent"/>.
/// </summary>
public sealed partial class SlotBlockSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SlotBlockComponent, InventoryRelayedEvent<IsEquippingTargetAttemptEvent>>(OnEquipAttempt);
SubscribeLocalEvent<SlotBlockComponent, InventoryRelayedEvent<IsUnequippingTargetAttemptEvent>>(OnUnequipAttempt);
}
private void OnEquipAttempt(Entity<SlotBlockComponent> ent, ref InventoryRelayedEvent<IsEquippingTargetAttemptEvent> args)
{
if (args.Args.Cancelled || (args.Args.SlotFlags & ent.Comp.Slots) == 0)
return;
args.Args.Reason = Loc.GetString("slot-block-component-blocked", ("item", ent));
args.Args.Cancel();
}
private void OnUnequipAttempt(Entity<SlotBlockComponent> ent, ref InventoryRelayedEvent<IsUnequippingTargetAttemptEvent> args)
{
if (args.Args.Cancelled || (args.Args.SlotFlags & ent.Comp.Slots) == 0)
return;
args.Args.Reason = Loc.GetString("slot-block-component-blocked", ("item", ent));
args.Args.Cancel();
}
}

View File

@@ -16,13 +16,26 @@ public sealed class ComponentTogglerSystem : EntitySystem
private void OnToggled(Entity<ComponentTogglerComponent> ent, ref ItemToggledEvent args)
{
var target = ent.Comp.Parent ? Transform(ent).ParentUid : ent.Owner;
if (TerminatingOrDeleted(target))
return;
if (args.Activated)
{
var target = ent.Comp.Parent ? Transform(ent).ParentUid : ent.Owner;
if (TerminatingOrDeleted(target))
return;
ent.Comp.Target = target;
EntityManager.AddComponents(target, ent.Comp.Components);
}
else
EntityManager.RemoveComponents(target, ent.Comp.RemoveComponents ?? ent.Comp.Components);
{
if (ent.Comp.Target == null)
return;
if (TerminatingOrDeleted(ent.Comp.Target.Value))
return;
EntityManager.RemoveComponents(ent.Comp.Target.Value, ent.Comp.RemoveComponents ?? ent.Comp.Components);
}
}
}

View File

@@ -29,4 +29,10 @@ public sealed partial class ComponentTogglerComponent : Component
/// </summary>
[DataField]
public bool Parent;
// <summary>
// It holds the entity that the component gave the component to, so it can remove from it even if it changes parent.
// </summary>
[DataField]
public EntityUid? Target;
}

View File

@@ -5,6 +5,7 @@ using Content.Shared.Examine;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Storage;
using JetBrains.Annotations;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
@@ -206,15 +207,21 @@ public abstract class SharedItemSystem : EntitySystem
public IReadOnlyList<Box2i> GetAdjustedItemShape(Entity<ItemComponent?> entity, Angle rotation, Vector2i position)
{
if (!Resolve(entity, ref entity.Comp))
return new Box2i[] { };
return [];
var adjustedShapes = new List<Box2i>();
GetAdjustedItemShape(adjustedShapes, entity, rotation, position);
return adjustedShapes;
}
public void GetAdjustedItemShape(List<Box2i> adjustedShapes, Entity<ItemComponent?> entity, Angle rotation, Vector2i position)
{
var shapes = GetItemShape(entity);
var boundingShape = shapes.GetBoundingBox();
var boundingCenter = ((Box2) boundingShape).Center;
var matty = Matrix3Helpers.CreateTransform(boundingCenter, rotation);
var drift = boundingShape.BottomLeft - matty.TransformBox(boundingShape).BottomLeft;
var adjustedShapes = new List<Box2i>();
foreach (var shape in shapes)
{
var transformed = matty.TransformBox(shape).Translated(drift);
@@ -223,8 +230,6 @@ public abstract class SharedItemSystem : EntitySystem
adjustedShapes.Add(translated);
}
return adjustedShapes;
}
/// <summary>

View File

@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Examine;
using Content.Shared.Labels.Components;
@@ -150,6 +151,25 @@ public sealed partial class LabelSystem : EntitySystem
if (TryComp<PaperLabelTypeComponent>(slot.Item, out var type))
_appearance.SetData(ent, PaperLabelVisuals.LabelType, type.PaperType, ent.Comp2);
}
/// <summary>
/// Retrieves a label with the specified component from the default label slot.
/// </summary>
public bool TryGetLabel<T>(Entity<PaperLabelComponent?> ent, [NotNullWhen(true)] out Entity<T>? label) where T : Component
{
label = null;
if (!Resolve(ent, ref ent.Comp, false))
return false;
if (ent.Comp.LabelSlot.Item is not { } labelEnt)
return false;
if (!TryComp<T>(labelEnt, out var labelComp))
return false;
label = (labelEnt, labelComp);
return true;
}
}
//CP14 Labeling Event

View File

@@ -0,0 +1,13 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Light.Components;
/// <summary>
/// Assumes the entire attached grid is rooved.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ImplicitRoofComponent : Component
{
[DataField, AutoNetworkedField]
public Color Color = Color.Black;
}

View File

@@ -43,7 +43,6 @@ namespace Content.Shared.Magic;
public abstract class SharedMagicSystem : EntitySystem
{
[Dependency] private readonly ISerializationManager _seriMan = default!;
[Dependency] private readonly IComponentFactory _compFact = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
@@ -276,13 +275,9 @@ public abstract class SharedMagicSystem : EntitySystem
// If applicable, this ensures the projectile is parented to grid on spawn, instead of the map.
var fromMap = _transform.ToMapCoordinates(fromCoords);
var spawnCoords = _mapManager.TryFindGridAt(fromMap, out var gridUid, out _)
? _transform.WithEntityId(fromCoords, gridUid)
: new(_mapManager.GetMapEntityId(fromMap.MapId), fromMap.Position);
var ent = Spawn(ev.Prototype, spawnCoords);
var ent = Spawn(ev.Prototype, fromMap);
var direction = _transform.ToMapCoordinates(toCoords).Position -
_transform.ToMapCoordinates(spawnCoords).Position;
fromMap.Position;
_gunSystem.ShootProjectile(ent, direction, userVelocity, ev.Performer, ev.Performer);
}
// End Projectile Spells
@@ -360,7 +355,7 @@ public abstract class SharedMagicSystem : EntitySystem
if (HasComp(target, data.Component.GetType()))
continue;
var component = (Component)_compFact.GetComponent(name);
var component = (Component)Factory.GetComponent(name);
var temp = (object)component;
_seriMan.CopyTo(data.Component, ref temp);
EntityManager.AddComponent(target, (Component)temp!);
@@ -371,7 +366,7 @@ public abstract class SharedMagicSystem : EntitySystem
{
foreach (var toRemove in comps)
{
if (_compFact.TryGetRegistration(toRemove, out var registration))
if (Factory.TryGetRegistration(toRemove, out var registration))
RemComp(target, registration.Type);
}
}

View File

@@ -47,6 +47,12 @@ namespace Content.Shared.Maps
[DataField]
public PrototypeFlags<ToolQualityPrototype> DeconstructTools { get; set; } = new();
/// <summary>
/// Effective mass of this tile for grid impacts.
/// </summary>
[DataField]
public float Mass = 800f;
/// <remarks>
/// Legacy AF but nice to have.
/// </remarks>
@@ -69,6 +75,11 @@ namespace Content.Shared.Maps
[DataField("variants")] public byte Variants { get; set; } = 1;
/// <summary>
/// Allows the tile to be rotated/mirrored when placed on a grid.
/// </summary>
[DataField] public bool AllowRotationMirror { get; set; } = false;
/// <summary>
/// This controls what variants the `variantize` command is allowed to use.
/// </summary>

View File

@@ -10,6 +10,7 @@ public struct TileFrictionEvent
public TileFrictionEvent(float modifier)
{
// TODO: If something ever uses different angular and linear modifiers, split this into two modifiers
Modifier = modifier;
}
}

View File

@@ -5,10 +5,12 @@ using Content.Shared.Buckle.Components;
using Content.Shared.Cuffs.Components;
using Content.Shared.Database;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement;
using Content.Shared.Input;
using Content.Shared.Interaction;
using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Item;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Systems;
@@ -28,6 +30,7 @@ using Robust.Shared.Physics.Events;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Shared.Movement.Pulling.Systems;
@@ -48,6 +51,7 @@ public sealed class PullingSystem : EntitySystem
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly HeldSpeedModifierSystem _clothingMoveSpeed = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedVirtualItemSystem _virtual = default!;
public override void Initialize()
{
@@ -73,6 +77,9 @@ public sealed class PullingSystem : EntitySystem
SubscribeLocalEvent<PullerComponent, DropHandItemsEvent>(OnDropHandItems);
SubscribeLocalEvent<PullerComponent, StopPullingAlertEvent>(OnStopPullingAlert);
SubscribeLocalEvent<HandsComponent, PullStartedMessage>(HandlePullStarted);
SubscribeLocalEvent<HandsComponent, PullStoppedMessage>(HandlePullStopped);
SubscribeLocalEvent<PullableComponent, StrappedEvent>(OnBuckled);
SubscribeLocalEvent<PullableComponent, BuckledEvent>(OnGotBuckled);
@@ -81,6 +88,41 @@ public sealed class PullingSystem : EntitySystem
.Register<PullingSystem>();
}
private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStartedMessage args)
{
if (args.PullerUid != uid)
return;
if (TryComp(args.PullerUid, out PullerComponent? pullerComp) && !pullerComp.NeedsHands)
return;
if (!_virtual.TrySpawnVirtualItemInHand(args.PulledUid, uid))
{
DebugTools.Assert("Unable to find available hand when starting pulling??");
}
}
private void HandlePullStopped(EntityUid uid, HandsComponent component, PullStoppedMessage args)
{
if (args.PullerUid != uid)
return;
// Try find hand that is doing this pull.
// and clear it.
foreach (var hand in component.Hands.Values)
{
if (hand.HeldEntity == null
|| !TryComp(hand.HeldEntity, out VirtualItemComponent? virtualItem)
|| virtualItem.BlockingEntity != args.PulledUid)
{
continue;
}
_handsSystem.TryDrop(args.PullerUid, hand, handsComp: component);
break;
}
}
private void OnStateChanged(EntityUid uid, PullerComponent component, ref UpdateMobStateEvent args)
{
if (component.Pulling == null)

View File

@@ -210,12 +210,12 @@ namespace Content.Shared.Movement.Systems
var diff = relativeRot - oldRelativeRot;
// If we're going from a grid -> map then preserve the relative rotation so it's seamless if they go into space and back.
if (HasComp<MapComponent>(relative) && HasComp<MapGridComponent>(mover.RelativeEntity))
if (MapQuery.HasComp(relative) && MapGridQuery.HasComp(mover.RelativeEntity))
{
mover.TargetRelativeRotation -= diff;
}
// Snap to nearest cardinal if map -> grid
else if (HasComp<MapGridComponent>(relative) && HasComp<MapComponent>(mover.RelativeEntity))
// Snap to nearest cardinal if map -> grid or grid -> grid
else if (MapGridQuery.HasComp(relative) && (MapQuery.HasComp(mover.RelativeEntity) || MapGridQuery.HasComp(mover.RelativeEntity)))
{
var targetDir = mover.TargetRelativeRotation - diff;
targetDir = targetDir.GetCardinalDir().ToAngle().Reduced();

View File

@@ -50,18 +50,19 @@ public abstract partial class SharedMoverController : VirtualController
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly TagSystem _tags = default!;
protected EntityQuery<CanMoveInAirComponent> CanMoveInAirQuery;
protected EntityQuery<FootstepModifierComponent> FootstepModifierQuery;
protected EntityQuery<InputMoverComponent> MoverQuery;
protected EntityQuery<MapComponent> MapQuery;
protected EntityQuery<MapGridComponent> MapGridQuery;
protected EntityQuery<MobMoverComponent> MobMoverQuery;
protected EntityQuery<MovementRelayTargetComponent> RelayTargetQuery;
protected EntityQuery<MovementSpeedModifierComponent> ModifierQuery;
protected EntityQuery<NoRotateOnMoveComponent> NoRotateQuery;
protected EntityQuery<PhysicsComponent> PhysicsQuery;
protected EntityQuery<RelayInputMoverComponent> RelayQuery;
protected EntityQuery<PullableComponent> PullableQuery;
protected EntityQuery<TransformComponent> XformQuery;
protected EntityQuery<CanMoveInAirComponent> CanMoveInAirQuery;
protected EntityQuery<NoRotateOnMoveComponent> NoRotateQuery;
protected EntityQuery<FootstepModifierComponent> FootstepModifierQuery;
protected EntityQuery<MapGridComponent> MapGridQuery;
private static readonly ProtoId<TagPrototype> FootstepSoundTag = "FootstepSound";
@@ -91,6 +92,7 @@ public abstract partial class SharedMoverController : VirtualController
CanMoveInAirQuery = GetEntityQuery<CanMoveInAirComponent>();
FootstepModifierQuery = GetEntityQuery<FootstepModifierComponent>();
MapGridQuery = GetEntityQuery<MapGridComponent>();
MapQuery = GetEntityQuery<MapComponent>();
SubscribeLocalEvent<MovementSpeedModifierComponent, TileFrictionEvent>(OnTileFriction);
@@ -412,6 +414,17 @@ public abstract partial class SharedMoverController : VirtualController
}
public void Friction(float minimumFrictionSpeed, float frameTime, float friction, ref float velocity)
{
if (velocity < minimumFrictionSpeed)
return;
// This equation is lifted from the Physics Island solver.
// We re-use it here because Kinematic Controllers can't/shouldn't use the Physics Friction
velocity *= Math.Clamp(1.0f - frameTime * friction, 0.0f, 1.0f);
}
/// <summary>
/// Adjusts the current velocity to the target velocity based on the specified acceleration.
/// </summary>

View File

@@ -113,7 +113,7 @@ public sealed class SpeedModifierContactsSystem : EntitySystem
var evSlippery = new GetSlowedOverSlipperyModifierEvent();
RaiseLocalEvent(uid, ref evSlippery);
if (MathHelper.CloseTo(evSlippery.SlowdownModifier, 1))
if (!MathHelper.CloseTo(evSlippery.SlowdownModifier, 1))
{
walkSpeed += evSlippery.SlowdownModifier;
sprintSpeed += evSlippery.SlowdownModifier;

View File

@@ -0,0 +1,6 @@
namespace Content.Shared.NodeContainer;
public abstract class SharedNodeContainerSystem : EntitySystem
{
}

View File

@@ -8,5 +8,16 @@ namespace Content.Shared.Nuke;
[RegisterComponent, NetworkedComponent]
public sealed partial class NukeDiskComponent : Component
{
/// <summary>
/// Used to modify the nuke's countdown timer.
/// </summary>
[DataField]
public TimeSpan? TimeModifier;
[DataField]
public TimeSpan MicrowaveMean = TimeSpan.Zero;
[DataField]
public TimeSpan MicrowaveStd = TimeSpan.FromSeconds(27.35);
// STD of 27.36s means theres an 90% chance the time is between +-45s, and a ~99% chance its between +-70s
}

View File

@@ -25,10 +25,8 @@ public abstract class SharedRingerSystem : EntitySystem
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedPdaSystem _pda = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedRoleSystem _role = default!;
[Dependency] private readonly SharedTransformSystem _xform = default!;
[Dependency] protected readonly SharedUserInterfaceSystem UI = default!;

View File

@@ -12,7 +12,7 @@ public sealed partial class PaperComponent : Component
public string Content { get; set; } = "";
[DataField("contentSize")]
public int ContentSize { get; set; } = 6000;
public int ContentSize { get; set; } = 10000;
[DataField("stampedBy"), AutoNetworkedField]
public List<StampDisplayInfo> StampedBy { get; set; } = new();

View File

@@ -122,7 +122,7 @@ public sealed class PaperSystem : EntitySystem
if (entity.Comp.EditingDisabled)
{
var paperEditingDisabledMessage = Loc.GetString("paper-tamper-proof-modified-message");
_popupSystem.PopupEntity(paperEditingDisabledMessage, entity, args.User);
_popupSystem.PopupClient(paperEditingDisabledMessage, entity, args.User);
args.Handled = true;
return;
@@ -281,6 +281,12 @@ public sealed class PaperSystem : EntitySystem
}
}
public void SetContent(EntityUid entity, string content)
{
if (!TryComp<PaperComponent>(entity, out var paper))
return;
SetContent((entity, paper), content);
}
public void SetContent(Entity<PaperComponent> entity, string content)
{

View File

@@ -17,6 +17,7 @@ public abstract class SharedBiomeSystem : EntitySystem
[Dependency] private readonly ISerializationManager _serManager = default!;
[Dependency] protected readonly ITileDefinitionManager TileDefManager = default!;
[Dependency] private readonly TileSystem _tile = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
protected const byte ChunkSize = 8;
@@ -69,7 +70,7 @@ public abstract class SharedBiomeSystem : EntitySystem
public bool TryGetBiomeTile(EntityUid uid, MapGridComponent grid, Vector2i indices, [NotNullWhen(true)] out Tile? tile)
{
if (grid.TryGetTileRef(indices, out var tileRef) && !tileRef.Tile.IsEmpty)
if (_map.TryGetTileRef(uid, grid, indices, out var tileRef) && !tileRef.Tile.IsEmpty)
{
tile = tileRef.Tile;
return true;
@@ -81,15 +82,15 @@ public abstract class SharedBiomeSystem : EntitySystem
return false;
}
return TryGetBiomeTile(indices, biome.Layers, biome.Seed, grid, out tile);
return TryGetBiomeTile(indices, biome.Layers, biome.Seed, (uid, grid), out tile);
}
/// <summary>
/// Tries to get the tile, real or otherwise, for the specified indices.
/// </summary>
public bool TryGetBiomeTile(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile)
public bool TryGetBiomeTile(Vector2i indices, List<IBiomeLayer> layers, int seed, Entity<MapGridComponent>? grid, [NotNullWhen(true)] out Tile? tile)
{
if (grid?.TryGetTileRef(indices, out var tileRef) == true && !tileRef.Tile.IsEmpty)
if (grid is { } gridEnt && _map.TryGetTileRef(gridEnt, gridEnt.Comp, indices, out var tileRef) && !tileRef.Tile.IsEmpty)
{
tile = tileRef.Tile;
return true;
@@ -98,10 +99,19 @@ public abstract class SharedBiomeSystem : EntitySystem
return TryGetTile(indices, layers, seed, grid, out tile);
}
/// <summary>
/// Tries to get the tile, real or otherwise, for the specified indices.
/// </summary>
[Obsolete("Use the Entity<MapGridComponent>? overload")]
public bool TryGetBiomeTile(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile)
{
return TryGetBiomeTile(indices, layers, seed, grid == null ? null : (grid.Owner, grid), out tile);
}
/// <summary>
/// Gets the underlying biome tile, ignoring any existing tile that may be there.
/// </summary>
public bool TryGetTile(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile)
public bool TryGetTile(Vector2i indices, List<IBiomeLayer> layers, int seed, Entity<MapGridComponent>? grid, [NotNullWhen(true)] out Tile? tile)
{
for (var i = layers.Count - 1; i >= 0; i--)
{
@@ -140,6 +150,15 @@ public abstract class SharedBiomeSystem : EntitySystem
return false;
}
/// <summary>
/// Gets the underlying biome tile, ignoring any existing tile that may be there.
/// </summary>
[Obsolete("Use the Entity<MapGridComponent>? overload")]
public bool TryGetTile(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile)
{
return TryGetTile(indices, layers, seed, grid == null ? null : (grid.Owner, grid), out tile);
}
/// <summary>
/// Gets the underlying biome tile, ignoring any existing tile that may be there.
/// </summary>
@@ -161,7 +180,7 @@ public abstract class SharedBiomeSystem : EntitySystem
if (variantCount > 1)
{
var variantValue = (noise.GetNoise(indices.X * 8, indices.Y * 8, variantCount) + 1f) * 100;
variant = _tile.PickVariant(tileDef, (int) variantValue);
variant = _tile.PickVariant(tileDef, (int)variantValue);
}
tile = new Tile(tileDef.TileId, variant);
@@ -171,7 +190,7 @@ public abstract class SharedBiomeSystem : EntitySystem
/// <summary>
/// Tries to get the relevant entity for this tile.
/// </summary>
public bool TryGetEntity(Vector2i indices, BiomeComponent component, MapGridComponent grid,
public bool TryGetEntity(Vector2i indices, BiomeComponent component, Entity<MapGridComponent>? grid,
[NotNullWhen(true)] out string? entity)
{
if (!TryGetBiomeTile(indices, component.Layers, component.Seed, grid, out var tile))
@@ -183,8 +202,17 @@ public abstract class SharedBiomeSystem : EntitySystem
return TryGetEntity(indices, component.Layers, tile.Value, component.Seed, grid, out entity);
}
/// <summary>
/// Tries to get the relevant entity for this tile.
/// </summary>
[Obsolete("Use the Entity<MapGridComponent>? overload")]
public bool TryGetEntity(Vector2i indices, BiomeComponent component, MapGridComponent grid,
[NotNullWhen(true)] out string? entity)
{
return TryGetEntity(indices, component, grid == null ? null : (grid.Owner, grid), out entity);
}
public bool TryGetEntity(Vector2i indices, List<IBiomeLayer> layers, Tile tileRef, int seed, MapGridComponent grid,
public bool TryGetEntity(Vector2i indices, List<IBiomeLayer> layers, Tile tileRef, int seed, Entity<MapGridComponent>? grid,
[NotNullWhen(true)] out string? entity)
{
var tileId = TileDefManager[tileRef.TypeId].ID;
@@ -243,10 +271,17 @@ public abstract class SharedBiomeSystem : EntitySystem
return false;
}
[Obsolete("Use the Entity<MapGridComponent>? overload")]
public bool TryGetEntity(Vector2i indices, List<IBiomeLayer> layers, Tile tileRef, int seed, MapGridComponent grid,
[NotNullWhen(true)] out string? entity)
{
return TryGetEntity(indices, layers, tileRef, seed, grid == null ? null : (grid.Owner, grid), out entity);
}
/// <summary>
/// Tries to get the relevant decals for this tile.
/// </summary>
public bool TryGetDecals(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent grid,
public bool TryGetDecals(Vector2i indices, List<IBiomeLayer> layers, int seed, Entity<MapGridComponent>? grid,
[NotNullWhen(true)] out List<(string ID, Vector2 Position)>? decals)
{
if (!TryGetBiomeTile(indices, layers, seed, grid, out var tileRef))
@@ -330,6 +365,16 @@ public abstract class SharedBiomeSystem : EntitySystem
return false;
}
/// <summary>
/// Tries to get the relevant decals for this tile.
/// </summary>
[Obsolete("Use the Entity<MapGridComponent>? overload")]
public bool TryGetDecals(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent grid,
[NotNullWhen(true)] out List<(string ID, Vector2 Position)>? decals)
{
return TryGetDecals(indices, layers, seed, grid == null ? null : (grid.Owner, grid), out decals);
}
private FastNoiseLite GetNoise(FastNoiseLite seedNoise, int seed)
{
var noiseCopy = new FastNoiseLite();

View File

@@ -12,17 +12,4 @@ public sealed partial class ParallaxComponent : Component
// I wish I could use a typeserializer here but parallax is extremely client-dependent.
[DataField, AutoNetworkedField]
public string Parallax = "Default";
[UsedImplicitly, ViewVariables(VVAccess.ReadWrite)]
// ReSharper disable once InconsistentNaming
public string ParallaxVV
{
get => Parallax;
set
{
if (value.Equals(Parallax)) return;
Parallax = value;
IoCManager.Resolve<IEntityManager>().Dirty(this);
}
}
}

View File

@@ -78,6 +78,8 @@ public enum CollisionGroup
WallLayer = Opaque | Impassable | HighImpassable | MidImpassable | LowImpassable | BulletImpassable | InteractImpassable,
GlassLayer = Impassable | HighImpassable | MidImpassable | LowImpassable | BulletImpassable | InteractImpassable,
HalfWallLayer = MidImpassable | LowImpassable,
// Allows people to interact past and target players inside of this
SpecialWallLayer = Opaque | HighImpassable | MidImpassable | LowImpassable | BulletImpassable,
// Statue, monument, airlock, window
FullTileMask = Impassable | HighImpassable | MidImpassable | LowImpassable | InteractImpassable,

View File

@@ -1,6 +1,21 @@
using Content.Shared.Power.Components;
namespace Content.Shared.Power.EntitySystems;
public abstract class SharedPowerNetSystem : EntitySystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
public abstract bool IsPoweredCalculate(SharedApcPowerReceiverComponent comp);
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AppearanceComponent, PowerChangedEvent>(OnPowerAppearance);
}
private void OnPowerAppearance(Entity<AppearanceComponent> ent, ref PowerChangedEvent args)
{
_appearance.SetData(ent, PowerDeviceVisuals.Powered, args.Powered, ent.Comp);
}
}

View File

@@ -4,13 +4,16 @@ using Content.Shared.Database;
using Content.Shared.Power.Components;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Network;
namespace Content.Shared.Power.EntitySystems;
public abstract class SharedPowerReceiverSystem : EntitySystem
{
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPowerNetSystem _net = default!;
public abstract bool ResolveApc(EntityUid entity, [NotNullWhen(true)] ref SharedApcPowerReceiverComponent? component);
@@ -44,6 +47,15 @@ public abstract class SharedPowerReceiverSystem : EntitySystem
// it'll save a lot of confusion if 'always powered' means 'always powered'
if (!receiver.NeedsPower)
{
var powered = _net.IsPoweredCalculate(receiver);
// Server won't raise it here as it can raise the load event later with NeedsPower?
// This is mostly here for clientside predictions.
if (receiver.Powered != powered)
{
RaisePower((uid, receiver));
}
SetPowerDisabled(uid, false, receiver);
return true;
}
@@ -59,6 +71,19 @@ public abstract class SharedPowerReceiverSystem : EntitySystem
AudioParams.Default.WithVolume(-2f));
}
if (_netMan.IsClient && receiver.PowerDisabled)
{
var powered = _net.IsPoweredCalculate(receiver);
// Server won't raise it here as it can raise the load event later with NeedsPower?
// This is mostly here for clientside predictions.
if (receiver.Powered != powered)
{
receiver.Powered = powered;
RaisePower((uid, receiver));
}
}
return !receiver.PowerDisabled; // i.e. PowerEnabled
}

View File

@@ -26,7 +26,7 @@ public abstract class SharedPowerCellSystem : EntitySystem
private void OnMapInit(Entity<PowerCellDrawComponent> ent, ref MapInitEvent args)
{
QueueUpdate((ent, ent.Comp));
ent.Comp.NextUpdateTime = Timing.CurTime + ent.Comp.Delay;
}
private void OnRejuvenate(EntityUid uid, PowerCellSlotComponent component, RejuvenateEvent args)
@@ -71,20 +71,14 @@ public abstract class SharedPowerCellSystem : EntitySystem
RaiseLocalEvent(uid, new PowerCellChangedEvent(true), false);
}
/// <summary>
/// Makes the draw logic update in the next tick.
/// </summary>
public void QueueUpdate(Entity<PowerCellDrawComponent?> ent)
{
if (Resolve(ent, ref ent.Comp))
ent.Comp.NextUpdateTime = Timing.CurTime;
}
public void SetDrawEnabled(Entity<PowerCellDrawComponent?> ent, bool enabled)
{
if (!Resolve(ent, ref ent.Comp, false) || ent.Comp.Enabled == enabled)
return;
if (enabled)
ent.Comp.NextUpdateTime = Timing.CurTime;
ent.Comp.Enabled = enabled;
Dirty(ent, ent.Comp);
}

View File

@@ -38,7 +38,6 @@ public sealed class ToggleCellDrawSystem : EntitySystem
{
var uid = ent.Owner;
var draw = Comp<PowerCellDrawComponent>(uid);
_cell.QueueUpdate((uid, draw));
_cell.SetDrawEnabled((uid, draw), args.Activated);
}

View File

@@ -28,10 +28,6 @@ namespace Content.Shared.Preferences
private static readonly Regex RestrictedNameRegex = new("[^А-Я,а-я,A-Z,a-z,0-9, ,\\-,']"); //CP14 Cyrillic add
private static readonly Regex ICNameCaseRegex = new(@"^(?<word>\w)|\b(?<word>\w)(?=\w*$)");
public const int MaxNameLength = 32;
public const int MaxLoadoutNameLength = 32;
public const int MaxDescLength = 512;
/// <summary>
/// Job preferences for initial spawn.
/// </summary>
@@ -508,13 +504,14 @@ namespace Content.Shared.Preferences
};
string name;
var maxNameLength = configManager.GetCVar(CCVars.MaxNameLength);
if (string.IsNullOrEmpty(Name))
{
name = GetName(Species, gender);
}
else if (Name.Length > MaxNameLength)
else if (Name.Length > maxNameLength)
{
name = Name[..MaxNameLength];
name = Name[..maxNameLength];
}
else
{
@@ -540,9 +537,10 @@ namespace Content.Shared.Preferences
}
string flavortext;
if (FlavorText.Length > MaxDescLength)
var maxFlavorTextLength = configManager.GetCVar(CCVars.MaxFlavorTextLength);
if (FlavorText.Length > maxFlavorTextLength)
{
flavortext = FormattedMessage.RemoveMarkupOrThrow(FlavorText)[..MaxDescLength];
flavortext = FormattedMessage.RemoveMarkupOrThrow(FlavorText)[..maxFlavorTextLength];
}
else
{

View File

@@ -1,8 +1,10 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.CCVar;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Random;
using Robust.Shared.Collections;
using Robust.Shared.Configuration;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
@@ -59,6 +61,7 @@ public sealed partial class RoleLoadout : IEquatable<RoleLoadout>
{
var groupRemove = new ValueList<string>();
var protoManager = collection.Resolve<IPrototypeManager>();
var configManager = collection.Resolve<IConfigurationManager>();
if (!protoManager.TryIndex(Role, out var roleProto))
{
@@ -78,10 +81,11 @@ public sealed partial class RoleLoadout : IEquatable<RoleLoadout>
if (EntityName != null)
{
var name = EntityName.Trim();
var maxNameLength = configManager.GetCVar(CCVars.MaxNameLength);
if (name.Length > HumanoidCharacterProfile.MaxNameLength)
if (name.Length > maxNameLength)
{
EntityName = name[..HumanoidCharacterProfile.MaxNameLength];
EntityName = name[..maxNameLength];
}
if (name.Length == 0)

View File

@@ -0,0 +1,36 @@
using Content.Shared.Construction.Prototypes;
using Lidgren.Network;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.Preferences;
/// <summary>
/// The client sends this to update their construction favorites.
/// </summary>
public sealed class MsgUpdateConstructionFavorites : NetMessage
{
public override MsgGroups MsgGroup => MsgGroups.Command;
public List<ProtoId<ConstructionPrototype>> Favorites = [];
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
{
var length = buffer.ReadVariableInt32();
Favorites.Clear();
for (var i = 0; i < length; i++)
{
Favorites.Add(new ProtoId<ConstructionPrototype>(buffer.ReadString()));
}
}
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
{
buffer.WriteVariableInt32(Favorites.Count);
foreach (var favorite in Favorites)
{
buffer.Write(favorite);
}
}
}

View File

@@ -1,3 +1,5 @@
using Content.Shared.Construction.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
@@ -13,11 +15,12 @@ namespace Content.Shared.Preferences
{
private Dictionary<int, ICharacterProfile> _characters;
public PlayerPreferences(IEnumerable<KeyValuePair<int, ICharacterProfile>> characters, int selectedCharacterIndex, Color adminOOCColor)
public PlayerPreferences(IEnumerable<KeyValuePair<int, ICharacterProfile>> characters, int selectedCharacterIndex, Color adminOOCColor, List<ProtoId<ConstructionPrototype>> constructionFavorites)
{
_characters = new Dictionary<int, ICharacterProfile>(characters);
SelectedCharacterIndex = selectedCharacterIndex;
AdminOOCColor = adminOOCColor;
ConstructionFavorites = constructionFavorites;
}
/// <summary>
@@ -42,6 +45,11 @@ namespace Content.Shared.Preferences
public Color AdminOOCColor { get; set; }
/// <summary>
/// List of favorite items in the construction menu.
/// </summary>
public List<ProtoId<ConstructionPrototype>> ConstructionFavorites { get; set; } = [];
public int IndexOfCharacter(ICharacterProfile profile)
{
return _characters.FirstOrNull(p => p.Value == profile)?.Key ?? -1;

View File

@@ -5,12 +5,6 @@ namespace Content.Shared.Procedural;
[Virtual, DataDefinition]
public partial class DungeonConfig
{
/// <summary>
/// <see cref="Data"/>
/// </summary>
[DataField]
public DungeonData Data = DungeonData.Empty;
/// <summary>
/// The secret sauce, procedural generation layers that get run.
/// </summary>

View File

@@ -1,105 +0,0 @@
using System.Linq;
using Content.Shared.Maps;
using Content.Shared.Storage;
using Content.Shared.Whitelist;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared.Procedural;
/// <summary>
/// Used to set dungeon values for all layers.
/// </summary>
/// <remarks>
/// This lets us share data between different dungeon configs without having to repeat entire configs.
/// </remarks>
[DataRecord]
public sealed partial class DungeonData
{
// I hate this but it also significantly reduces yaml bloat if we add like 10 variations on the same set of layers
// e.g. science rooms, engi rooms, cargo rooms all under PlanetBase for example.
// without having to do weird nesting. It also means we don't need to copy-paste the same prototype across several layers
// The alternative is doing like,
// 2 layer prototype, 1 layer with the specified data, 3 layer prototype, 2 layers with specified data, etc.
// As long as we just keep the code clean over time it won't be bad to maintain.
public static DungeonData Empty = new();
public Dictionary<DungeonDataKey, Color> Colors = new();
public Dictionary<DungeonDataKey, EntProtoId> Entities = new();
public Dictionary<DungeonDataKey, ProtoId<EntitySpawnEntryPrototype>> SpawnGroups = new();
public Dictionary<DungeonDataKey, ProtoId<ContentTileDefinition>> Tiles = new();
public Dictionary<DungeonDataKey, EntityWhitelist> Whitelists = new();
/// <summary>
/// Applies the specified data to this data.
/// </summary>
public void Apply(DungeonData data)
{
// Copy-paste moment.
foreach (var color in data.Colors)
{
Colors[color.Key] = color.Value;
}
foreach (var color in data.Entities)
{
Entities[color.Key] = color.Value;
}
foreach (var color in data.SpawnGroups)
{
SpawnGroups[color.Key] = color.Value;
}
foreach (var color in data.Tiles)
{
Tiles[color.Key] = color.Value;
}
foreach (var color in data.Whitelists)
{
Whitelists[color.Key] = color.Value;
}
}
public DungeonData Clone()
{
return new DungeonData
{
// Only shallow clones but won't matter for DungeonJob purposes.
Colors = Colors.ShallowClone(),
Entities = Entities.ShallowClone(),
SpawnGroups = SpawnGroups.ShallowClone(),
Tiles = Tiles.ShallowClone(),
Whitelists = Whitelists.ShallowClone(),
};
}
}
public enum DungeonDataKey : byte
{
// Colors
Decals,
// Entities
Cabling,
CornerWalls,
Fill,
Junction,
Walls,
// SpawnGroups
CornerClutter,
Entrance,
EntranceFlank,
WallMounts,
Window,
// Tiles
FallbackTile,
WidenTile,
// Whitelists
Rooms,
}

View File

@@ -1,3 +1,5 @@
using Content.Shared.Maps;
using Content.Shared.Whitelist;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.DungeonGenerators;
@@ -6,10 +8,6 @@ namespace Content.Shared.Procedural.DungeonGenerators;
/// Places rooms in pre-selected pack layouts. Chooses rooms from the specified whitelist.
/// </summary>
/// <remarks>
/// DungeonData keys are:
/// - FallbackTile
/// - Rooms
/// </remarks>
public sealed partial class PrefabDunGen : IDunGenLayer
{
/// <summary>
@@ -17,4 +15,10 @@ public sealed partial class PrefabDunGen : IDunGenLayer
/// </summary>
[DataField(required: true)]
public List<ProtoId<DungeonPresetPrototype>> Presets = new();
[DataField]
public EntityWhitelist? RoomWhitelist;
[DataField]
public ProtoId<ContentTileDefinition>? FallbackTile;
}

View File

@@ -8,6 +8,30 @@ namespace Content.Shared.Procedural.DungeonGenerators;
/// </summary>
public sealed partial class PrototypeDunGen : IDunGenLayer
{
/// <summary>
/// Should we pass in the current level's dungeons to the prototype.
/// </summary>
[DataField]
public DungeonInheritance InheritDungeons = DungeonInheritance.None;
[DataField(required: true)]
public ProtoId<DungeonConfigPrototype> Proto;
}
public enum DungeonInheritance : byte
{
/// <summary>
/// Don't inherit any of the current layer's dungeons for this <see cref="PrototypeDunGen"/>
/// </summary>
None,
/// <summary>
/// Inherit only the last dungeon ran.
/// </summary>
Last,
/// <summary>
/// Inherit all of the current layer's dungeons.
/// </summary>
All,
}

View File

@@ -18,4 +18,10 @@ public sealed partial class EntityTableDunGen : IDunGenLayer
[DataField(required: true)]
public EntityTableSelector Table;
/// <summary>
/// Should the count be per dungeon or across all dungeons.
/// </summary>
[DataField]
public bool PerDungeon;
}

View File

@@ -1,7 +1,7 @@
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.DungeonGenerators;
namespace Content.Shared.Procedural.DungeonLayers;
/// <summary>
/// Fills unreserved tiles with the specified entity prototype.
@@ -17,4 +17,7 @@ public sealed partial class FillGridDunGen : IDunGenLayer
/// </summary>
[DataField]
public HashSet<ProtoId<ContentTileDefinition>>? AllowedTiles;
[DataField(required: true)]
public EntProtoId Entity;
}

View File

@@ -1,4 +1,6 @@
using Content.Shared.EntityTable;
using Content.Shared.Storage;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.DungeonLayers;
@@ -17,5 +19,5 @@ public sealed partial class MobsDunGen : IDunGenLayer
public int MaxCount = 1;
[DataField(required: true)]
public List<EntitySpawnEntry> Groups = new();
public ProtoId<EntityTablePrototype> Contents;
}

Some files were not shown because too many files have changed in this diff Show More