diff --git a/Content.Client/Store/Ui/StoreMenu.xaml.cs b/Content.Client/Store/Ui/StoreMenu.xaml.cs index b7a2c285fe5..7eb597f2f39 100644 --- a/Content.Client/Store/Ui/StoreMenu.xaml.cs +++ b/Content.Client/Store/Ui/StoreMenu.xaml.cs @@ -3,6 +3,7 @@ using Content.Client.Message; using Content.Shared.FixedPoint; using Content.Shared.Store; +using Content.Client.Stylesheets; using Robust.Client.AutoGenerated; using Robust.Client.GameObjects; using Robust.Client.Graphics; @@ -147,6 +148,10 @@ private void AddListingGui(ListingData listing) } var newListing = new StoreListingControl(listing, GetListingPriceString(listing), hasBalance, texture); + + if (listing.DiscountValue > 0) + newListing.StoreItemBuyButton.AddStyleClass(StyleNano.ButtonColorDangerDefault.ToString()); + newListing.StoreItemBuyButton.OnButtonDown += args => OnListingButtonPressed?.Invoke(args, listing); diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/MachineConstruction.cs b/Content.IntegrationTests/Tests/Construction/Interaction/MachineConstruction.cs index f52f820a4ce..cd95a85f205 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/MachineConstruction.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/MachineConstruction.cs @@ -57,4 +57,3 @@ public async Task ChangeMachine() AssertPrototype("Autolathe"); } } - diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs index 11381fb8ccd..a915e5d47d1 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs @@ -1,3 +1,4 @@ + namespace Content.IntegrationTests.Tests.Interaction; // This partial class contains various constant prototype IDs common to interaction tests. @@ -27,8 +28,6 @@ public abstract partial class InteractionTest // Parts protected const string Bin1 = "MatterBinStockPart"; - protected const string Cap1 = "CapacitorStockPart"; protected const string Manipulator1 = "MicroManipulatorStockPart"; - protected const string Battery1 = "PowerCellSmall"; - protected const string Battery4 = "PowerCellHyper"; } + diff --git a/Content.Server/Abilities/Psionics/Abilities/HealOtherPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/HealOtherPowerSystem.cs new file mode 100644 index 00000000000..85bae78dc6b --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/HealOtherPowerSystem.cs @@ -0,0 +1,152 @@ +using Robust.Shared.Player; +using Content.Server.DoAfter; +using Content.Shared.Abilities.Psionics; +using Content.Shared.Damage; +using Content.Shared.DoAfter; +using Content.Shared.Popups; +using Content.Shared.Psionics.Events; +using Content.Shared.Examine; +using static Content.Shared.Examine.ExamineSystemShared; +using Robust.Shared.Timing; +using Content.Shared.Actions.Events; +using Robust.Server.Audio; +using Content.Server.Atmos.Rotting; +using Content.Shared.Mobs.Systems; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Psionics.Glimmer; + +namespace Content.Server.Abilities.Psionics; + +public sealed class RevivifyPowerSystem : EntitySystem +{ + [Dependency] private readonly AudioSystem _audioSystem = default!; + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly ExamineSystemShared _examine = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly RottingSystem _rotting = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly GlimmerSystem _glimmer = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPowerUsed); + SubscribeLocalEvent(OnDispelled); + SubscribeLocalEvent(OnDoAfter); + } + + + private void OnPowerUsed(EntityUid uid, PsionicComponent component, PsionicHealOtherPowerActionEvent args) + { + if (component.DoAfter is not null) + return; + + if (!args.Immediate) + AttemptDoAfter(uid, component, args); + else ActivatePower(uid, component, args); + + if (args.PopupText is not null + && _glimmer.Glimmer > args.GlimmerObviousPopupThreshold * component.CurrentDampening) + _popupSystem.PopupEntity(Loc.GetString(args.PopupText, ("entity", uid)), uid, + Filter.Pvs(uid).RemoveWhereAttachedEntity(entity => !_examine.InRangeUnOccluded(uid, entity, ExamineRange, null)), + true, + args.PopupType); + + if (args.PlaySound + && _glimmer.Glimmer > args.GlimmerObviousSoundThreshold * component.CurrentDampening) + _audioSystem.PlayPvs(args.SoundUse, uid, args.AudioParams); + + // Sanitize the Glimmer inputs because otherwise the game will crash if someone makes MaxGlimmer lower than MinGlimmer. + var minGlimmer = (int) Math.Round(MathF.MinMagnitude(args.MinGlimmer, args.MaxGlimmer) + + component.CurrentAmplification - component.CurrentDampening); + var maxGlimmer = (int) Math.Round(MathF.MaxMagnitude(args.MinGlimmer, args.MaxGlimmer) + + component.CurrentAmplification - component.CurrentDampening); + + _psionics.LogPowerUsed(uid, args.PowerName, minGlimmer, maxGlimmer); + args.Handled = true; + } + + private void AttemptDoAfter(EntityUid uid, PsionicComponent component, PsionicHealOtherPowerActionEvent args) + { + var ev = new PsionicHealOtherDoAfterEvent(_gameTiming.CurTime); + ev.HealingAmount = args.HealingAmount; + ev.RotReduction = args.RotReduction; + ev.DoRevive = args.DoRevive; + var doAfterArgs = new DoAfterArgs(EntityManager, uid, args.UseDelay, ev, uid, target: args.Target) + { + BreakOnUserMove = args.BreakOnUserMove, + BreakOnTargetMove = args.BreakOnTargetMove, + }; + + if (!_doAfterSystem.TryStartDoAfter(doAfterArgs, out var doAfterId)) + return; + + component.DoAfter = doAfterId; + } + + private void OnDispelled(EntityUid uid, PsionicComponent component, DispelledEvent args) + { + if (component.DoAfter is null) + return; + + _doAfterSystem.Cancel(component.DoAfter); + component.DoAfter = null; + args.Handled = true; + } + + private void OnDoAfter(EntityUid uid, PsionicComponent component, PsionicHealOtherDoAfterEvent args) + { + // It's entirely possible for the caster to stop being Psionic(due to mindbreaking) mid cast + if (component is null) + return; + component.DoAfter = null; + + // The target can also cease existing mid-cast + if (args.Target is null) + return; + + _rotting.ReduceAccumulator(args.Target.Value, TimeSpan.FromSeconds(args.RotReduction * component.CurrentAmplification)); + + if (!TryComp(args.Target.Value, out var damageableComponent)) + return; + + _damageable.TryChangeDamage(args.Target.Value, args.HealingAmount * component.CurrentAmplification, true, false, damageableComponent, uid); + + if (!args.DoRevive + || !TryComp(args.Target, out var mob) + || !_mobThreshold.TryGetThresholdForState(args.Target.Value, MobState.Dead, out var threshold) + || damageableComponent.TotalDamage > threshold) + return; + + _mobState.ChangeMobState(args.Target.Value, MobState.Critical, mob, uid); + } + + // This would be the same thing as OnDoAfter, except that here the target isn't nullable, so I have to reuse code with different arguments. + private void ActivatePower(EntityUid uid, PsionicComponent component, PsionicHealOtherPowerActionEvent args) + { + if (component is null) + return; + + _rotting.ReduceAccumulator(args.Target, TimeSpan.FromSeconds(args.RotReduction * component.CurrentAmplification)); + + if (!TryComp(args.Target, out var damageableComponent)) + return; + + _damageable.TryChangeDamage(args.Target, args.HealingAmount * component.CurrentAmplification, true, false, damageableComponent, uid); + + if (!args.DoRevive + || !TryComp(args.Target, out var mob) + || !_mobThreshold.TryGetThresholdForState(args.Target, MobState.Dead, out var threshold) + || damageableComponent.TotalDamage > threshold) + return; + + _mobState.ChangeMobState(args.Target, MobState.Critical, mob, uid); + } +} diff --git a/Content.Server/Anomaly/AnomalySystem.Vessel.cs b/Content.Server/Anomaly/AnomalySystem.Vessel.cs index 35de5c1a646..9a6c99f8208 100644 --- a/Content.Server/Anomaly/AnomalySystem.Vessel.cs +++ b/Content.Server/Anomaly/AnomalySystem.Vessel.cs @@ -1,4 +1,5 @@ using Content.Server.Anomaly.Components; +using Content.Server.Construction; using Content.Server.Power.EntitySystems; using Content.Shared.Anomaly; using Content.Shared.Anomaly.Components; @@ -20,6 +21,7 @@ private void InitializeVessel() { SubscribeLocalEvent(OnVesselShutdown); SubscribeLocalEvent(OnVesselMapInit); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnVesselInteractUsing); SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnVesselGetPointsPerSecond); @@ -65,6 +67,11 @@ private void OnVesselMapInit(EntityUid uid, AnomalyVesselComponent component, Ma UpdateVesselAppearance(uid, component); } + private void OnUpgradeExamine(EntityUid uid, AnomalyVesselComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("anomaly-vessel-component-upgrade-output", component.PointMultiplier); + } + private void OnVesselInteractUsing(EntityUid uid, AnomalyVesselComponent component, InteractUsingEvent args) { if (component.Anomaly != null || diff --git a/Content.Server/Atmos/Piping/Binary/Components/GasRecyclerComponent.cs b/Content.Server/Atmos/Piping/Binary/Components/GasRecyclerComponent.cs index e1eb0072b9d..aa7e3e0b360 100644 --- a/Content.Server/Atmos/Piping/Binary/Components/GasRecyclerComponent.cs +++ b/Content.Server/Atmos/Piping/Binary/Components/GasRecyclerComponent.cs @@ -1,11 +1,12 @@ using Content.Shared.Atmos; +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Atmos.Piping.Binary.Components { [RegisterComponent] public sealed partial class GasRecyclerComponent : Component { - [ViewVariables(VVAccess.ReadOnly)] [DataField("reacting")] public Boolean Reacting { get; set; } = false; @@ -17,10 +18,28 @@ public sealed partial class GasRecyclerComponent : Component [DataField("outlet")] public string OutletName { get; set; } = "outlet"; - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float MinTemp = 300 + Atmospherics.T0C; - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float BaseMinTemp = 300 + Atmospherics.T0C; + + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartMinTemp = "Capacitor"; + + [DataField] + public float PartRatingMinTempMultiplier = 0.95f; + + [ViewVariables(VVAccess.ReadWrite)] public float MinPressure = 30 * Atmospherics.OneAtmosphere; + + [DataField] + public float BaseMinPressure = 30 * Atmospherics.OneAtmosphere; + + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartMinPressure = "Manipulator"; + + [DataField] + public float PartRatingMinPressureMultiplier = 0.8f; } } diff --git a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasRecyclerSystem.cs b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasRecyclerSystem.cs index 3ebc5094926..40b9d88846f 100644 --- a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasRecyclerSystem.cs +++ b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasRecyclerSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.Piping.Binary.Components; using Content.Server.Atmos.Piping.Components; +using Content.Server.Construction; using Content.Server.NodeContainer; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.Nodes; @@ -28,6 +29,8 @@ public override void Initialize() SubscribeLocalEvent(OnUpdate); SubscribeLocalEvent(OnDisabled); SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); } private void OnEnabled(EntityUid uid, GasRecyclerComponent comp, ref AtmosDeviceEnabledEvent args) @@ -116,5 +119,20 @@ private void UpdateAppearance(EntityUid uid, GasRecyclerComponent? comp = null) _appearance.SetData(uid, PumpVisuals.Enabled, comp.Reacting); } + + private void OnRefreshParts(EntityUid uid, GasRecyclerComponent component, RefreshPartsEvent args) + { + var ratingTemp = args.PartRatings[component.MachinePartMinTemp]; + var ratingPressure = args.PartRatings[component.MachinePartMinPressure]; + + component.MinTemp = component.BaseMinTemp * MathF.Pow(component.PartRatingMinTempMultiplier, ratingTemp - 1); + component.MinPressure = component.BaseMinPressure * MathF.Pow(component.PartRatingMinPressureMultiplier, ratingPressure - 1); + } + + private void OnUpgradeExamine(EntityUid uid, GasRecyclerComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("gas-recycler-upgrade-min-temp", component.MinTemp / component.BaseMinTemp); + args.AddPercentageUpgrade("gas-recycler-upgrade-min-pressure", component.MinPressure / component.BaseMinPressure); + } } } diff --git a/Content.Server/Atmos/Portable/PortableScrubberComponent.cs b/Content.Server/Atmos/Portable/PortableScrubberComponent.cs index ae9a5da9639..fbe2d3f95a0 100644 --- a/Content.Server/Atmos/Portable/PortableScrubberComponent.cs +++ b/Content.Server/Atmos/Portable/PortableScrubberComponent.cs @@ -1,4 +1,6 @@ using Content.Shared.Atmos; +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Atmos.Portable { @@ -37,13 +39,51 @@ public sealed partial class PortableScrubberComponent : Component /// /// Maximum internal pressure before it refuses to take more. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float MaxPressure = 2500; /// - /// The speed at which gas is scrubbed from the environment. + /// The base amount of maximum internal pressure /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float BaseMaxPressure = 2500; + + /// + /// The machine part that modifies the maximum internal pressure + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartMaxPressure = "MatterBin"; + + /// + /// How much the will affect the pressure. + /// The value will be multiplied by this amount for each increasing part tier. + /// + [DataField] + public float PartRatingMaxPressureModifier = 1.5f; + + /// + /// The speed at which gas is scrubbed from the environment. + /// + [ViewVariables(VVAccess.ReadWrite)] public float TransferRate = 800; + + /// + /// The base speed at which gas is scrubbed from the environment. + /// + [DataField] + public float BaseTransferRate = 800; + + /// + /// The machine part which modifies the speed of + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartTransferRate = "Manipulator"; + + /// + /// How much the will modify the rate. + /// The value will be multiplied by this amount for each increasing part tier. + /// + [DataField] + public float PartRatingTransferRateModifier = 1.4f; } } diff --git a/Content.Server/Atmos/Portable/PortableScrubberSystem.cs b/Content.Server/Atmos/Portable/PortableScrubberSystem.cs index f9043c091a8..f657d713d28 100644 --- a/Content.Server/Atmos/Portable/PortableScrubberSystem.cs +++ b/Content.Server/Atmos/Portable/PortableScrubberSystem.cs @@ -12,6 +12,7 @@ using Content.Server.NodeContainer.NodeGroups; using Content.Server.Audio; using Content.Server.Administration.Logs; +using Content.Server.Construction; using Content.Server.NodeContainer.EntitySystems; using Content.Shared.Database; @@ -38,6 +39,8 @@ public override void Initialize() SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnDestroyed); SubscribeLocalEvent(OnScrubberAnalyzed); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); } private bool IsFull(PortableScrubberComponent component) @@ -156,5 +159,20 @@ private void OnScrubberAnalyzed(EntityUid uid, PortableScrubberComponent compone if (_nodeContainer.TryGetNode(uid, component.PortName, out PipeNode? port)) args.GasMixtures.Add(component.PortName, port.Air); } + + private void OnRefreshParts(EntityUid uid, PortableScrubberComponent component, RefreshPartsEvent args) + { + var pressureRating = args.PartRatings[component.MachinePartMaxPressure]; + var transferRating = args.PartRatings[component.MachinePartTransferRate]; + + component.MaxPressure = component.BaseMaxPressure * MathF.Pow(component.PartRatingMaxPressureModifier, pressureRating - 1); + component.TransferRate = component.BaseTransferRate * MathF.Pow(component.PartRatingTransferRateModifier, transferRating - 1); + } + + private void OnUpgradeExamine(EntityUid uid, PortableScrubberComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("portable-scrubber-component-upgrade-max-pressure", component.MaxPressure / component.BaseMaxPressure); + args.AddPercentageUpgrade("portable-scrubber-component-upgrade-transfer-rate", component.TransferRate / component.BaseTransferRate); + } } } diff --git a/Content.Server/Bed/BedSystem.cs b/Content.Server/Bed/BedSystem.cs index f1bd9482e5e..976ef5139c3 100644 --- a/Content.Server/Bed/BedSystem.cs +++ b/Content.Server/Bed/BedSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Bed.Components; using Content.Server.Bed.Sleep; using Content.Server.Body.Systems; +using Content.Server.Construction; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.Bed; @@ -9,6 +10,7 @@ using Content.Shared.Body.Components; using Content.Shared.Buckle.Components; using Content.Shared.Damage; +using Content.Shared.Emag.Components; using Content.Shared.Emag.Systems; using Content.Shared.Mobs.Systems; using Robust.Shared.Timing; @@ -32,6 +34,8 @@ public override void Initialize() SubscribeLocalEvent(OnBuckleChange); SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnEmagged); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); } private void ManageUpdateList(EntityUid uid, HealOnBuckleComponent component, ref BuckleChangeEvent args) @@ -66,7 +70,7 @@ public override void Update(float frameTime) foreach (var healedEntity in strapComponent.BuckledEntities) { - if (_mobStateSystem.IsDead(healedEntity) + if (_mobStateSystem.IsDead(healedEntity) || HasComp(healedEntity)) continue; @@ -126,5 +130,18 @@ private void UpdateMetabolisms(EntityUid uid, StasisBedComponent component, bool RaiseLocalEvent(buckledEntity, ref metabolicEvent); } } + + private void OnRefreshParts(EntityUid uid, StasisBedComponent component, RefreshPartsEvent args) + { + var metabolismRating = args.PartRatings[component.MachinePartMetabolismModifier]; + component.Multiplier = component.BaseMultiplier * metabolismRating; // Linear scaling so it's not OP + if (HasComp(uid)) + component.Multiplier = 1f / component.Multiplier; + } + + private void OnUpgradeExamine(EntityUid uid, StasisBedComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("stasis-bed-component-upgrade-stasis", component.Multiplier / component.BaseMultiplier); + } } } diff --git a/Content.Server/Bed/Components/StasisBedComponent.cs b/Content.Server/Bed/Components/StasisBedComponent.cs index e2175d6e646..bb4096a2a5e 100644 --- a/Content.Server/Bed/Components/StasisBedComponent.cs +++ b/Content.Server/Bed/Components/StasisBedComponent.cs @@ -1,12 +1,21 @@ +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + namespace Content.Server.Bed.Components { [RegisterComponent] public sealed partial class StasisBedComponent : Component { + [DataField] + public float BaseMultiplier = 10f; + /// - /// What the metabolic update rate will be multiplied by (higher = slower metabolism) + /// What the metabolic update rate will be multiplied by (higher = slower metabolism) /// [ViewVariables(VVAccess.ReadWrite)] public float Multiplier = 10f; + + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartMetabolismModifier = "Capacitor"; } } diff --git a/Content.Server/Botany/Components/SeedExtractorComponent.cs b/Content.Server/Botany/Components/SeedExtractorComponent.cs index ddb04f213d1..d765e079cec 100644 --- a/Content.Server/Botany/Components/SeedExtractorComponent.cs +++ b/Content.Server/Botany/Components/SeedExtractorComponent.cs @@ -1,4 +1,7 @@ using Content.Server.Botany.Systems; +using Content.Server.Construction; +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Botany.Components; @@ -7,14 +10,33 @@ namespace Content.Server.Botany.Components; public sealed partial class SeedExtractorComponent : Component { /// - /// The minimum amount of seed packets dropped. + /// The minimum amount of seed packets dropped with no machine upgrades. /// - [DataField("baseMinSeeds"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public int BaseMinSeeds = 1; /// - /// The maximum amount of seed packets dropped. + /// The maximum amount of seed packets dropped with no machine upgrades. /// - [DataField("baseMaxSeeds"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public int BaseMaxSeeds = 3; + + /// + /// Modifier to the amount of seeds outputted, set on . + /// + [ViewVariables(VVAccess.ReadWrite)] + public float SeedAmountMultiplier; + + /// + /// Machine part whose rating modifies the amount of seed packets dropped. + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartSeedAmount = "Manipulator"; + + /// + /// How much the machine part quality affects the amount of seeds outputted. + /// Going up a tier will multiply the seed output by this amount. + /// + [DataField] + public float PartRatingSeedAmountMultiplier = 1.5f; } diff --git a/Content.Server/Botany/Systems/SeedExtractorSystem.cs b/Content.Server/Botany/Systems/SeedExtractorSystem.cs index f1ae6c9f11a..4c547b96f09 100644 --- a/Content.Server/Botany/Systems/SeedExtractorSystem.cs +++ b/Content.Server/Botany/Systems/SeedExtractorSystem.cs @@ -1,8 +1,10 @@ using Content.Server.Botany.Components; +using Content.Server.Construction; using Content.Server.Popups; using Content.Server.Power.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Popups; +using Robust.Shared.Player; using Robust.Shared.Random; namespace Content.Server.Botany.Systems; @@ -18,6 +20,8 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); } private void OnInteractUsing(EntityUid uid, SeedExtractorComponent seedExtractor, InteractUsingEvent args) @@ -25,8 +29,7 @@ private void OnInteractUsing(EntityUid uid, SeedExtractorComponent seedExtractor if (!this.IsPowered(uid, EntityManager)) return; - if (!TryComp(args.Used, out ProduceComponent? produce)) - return; + if (!TryComp(args.Used, out ProduceComponent? produce)) return; if (!_botanySystem.TryGetSeed(produce, out var seed) || seed.Seedless) { _popupSystem.PopupCursor(Loc.GetString("seed-extractor-component-no-seeds",("name", args.Used)), @@ -39,7 +42,7 @@ private void OnInteractUsing(EntityUid uid, SeedExtractorComponent seedExtractor QueueDel(args.Used); - var amount = _random.Next(seedExtractor.BaseMinSeeds, seedExtractor.BaseMaxSeeds + 1); + var amount = (int) _random.NextFloat(seedExtractor.BaseMinSeeds, seedExtractor.BaseMaxSeeds + 1) * seedExtractor.SeedAmountMultiplier; var coords = Transform(uid).Coordinates; if (amount > 1) @@ -50,4 +53,15 @@ private void OnInteractUsing(EntityUid uid, SeedExtractorComponent seedExtractor _botanySystem.SpawnSeedPacket(seed, coords, args.User); } } + + private void OnRefreshParts(EntityUid uid, SeedExtractorComponent seedExtractor, RefreshPartsEvent args) + { + var manipulatorQuality = args.PartRatings[seedExtractor.MachinePartSeedAmount]; + seedExtractor.SeedAmountMultiplier = MathF.Pow(seedExtractor.PartRatingSeedAmountMultiplier, manipulatorQuality - 1); + } + + private void OnUpgradeExamine(EntityUid uid, SeedExtractorComponent seedExtractor, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("seed-extractor-component-upgrade-seed-yield", seedExtractor.SeedAmountMultiplier); + } } diff --git a/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs b/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs index 42aabf2578e..1c25b0e79d2 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs @@ -1,4 +1,6 @@ using Content.Server.Cargo.Components; +using Content.Server.Construction; +using Content.Server.Paper; using Content.Server.Power.Components; using Content.Shared.Cargo; using Content.Shared.Cargo.Components; @@ -13,6 +15,8 @@ public sealed partial class CargoSystem private void InitializeTelepad() { SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnTelepadPowerChange); // Shouldn't need re-anchored event SubscribeLocalEvent(OnTelepadAnchorChange); @@ -79,6 +83,17 @@ private void OnInit(EntityUid uid, CargoTelepadComponent telepad, ComponentInit _linker.EnsureSinkPorts(uid, telepad.ReceiverPort); } + private void OnRefreshParts(EntityUid uid, CargoTelepadComponent component, RefreshPartsEvent args) + { + var rating = args.PartRatings[component.MachinePartTeleportDelay] - 1; + component.Delay = component.BaseDelay * MathF.Pow(component.PartRatingTeleportDelay, rating); + } + + private void OnUpgradeExamine(EntityUid uid, CargoTelepadComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("cargo-telepad-delay-upgrade", component.Delay / component.BaseDelay); + } + private void SetEnabled(EntityUid uid, CargoTelepadComponent component, ApcPowerReceiverComponent? receiver = null, TransformComponent? xform = null) { diff --git a/Content.Server/Chemistry/Components/SolutionHeaterComponent.cs b/Content.Server/Chemistry/Components/SolutionHeaterComponent.cs index c1841e022c7..bc1d44e82f1 100644 --- a/Content.Server/Chemistry/Components/SolutionHeaterComponent.cs +++ b/Content.Server/Chemistry/Components/SolutionHeaterComponent.cs @@ -4,8 +4,26 @@ namespace Content.Server.Chemistry.Components; public sealed partial class SolutionHeaterComponent : Component { /// - /// How much heat is added per second to the solution, taking upgrades into account. + /// How much heat is added per second to the solution, with no upgrades. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float BaseHeatPerSecond = 120; + + /// + /// How much heat is added per second to the solution, taking upgrades into account. + /// + [ViewVariables(VVAccess.ReadWrite)] public float HeatPerSecond; + + /// + /// The machine part that affects the heat multiplier. + /// + [DataField] + public string MachinePartHeatMultiplier = "Capacitor"; + + /// + /// How much each upgrade multiplies the heat by. + /// + [DataField] + public float PartRatingHeatMultiplier = 1.5f; } diff --git a/Content.Server/Chemistry/EntitySystems/SolutionHeaterSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionHeaterSystem.cs index 6e6373e10bf..1ef589ab5cb 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionHeaterSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionHeaterSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Chemistry.Components; using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Server.Construction; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.Chemistry; @@ -20,6 +21,8 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnItemPlaced); SubscribeLocalEvent(OnItemRemoved); } @@ -61,6 +64,18 @@ private void OnPowerChanged(Entity entity, ref PowerCha } } + private void OnRefreshParts(Entity entity, ref RefreshPartsEvent args) + { + var heatRating = args.PartRatings[entity.Comp.MachinePartHeatMultiplier] - 1; + + entity.Comp.HeatPerSecond = entity.Comp.BaseHeatPerSecond * MathF.Pow(entity.Comp.PartRatingHeatMultiplier, heatRating); + } + + private void OnUpgradeExamine(Entity entity, ref UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("solution-heater-upgrade-heat", entity.Comp.HeatPerSecond / entity.Comp.BaseHeatPerSecond); + } + private void OnItemPlaced(Entity entity, ref ItemPlacedEvent args) { TryTurnOn(entity); diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 7931fae4778..72104bc381f 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Chat.Systems; using Content.Server.Cloning.Components; +using Content.Server.Construction; using Content.Server.DeviceLinking.Systems; using Content.Server.EUI; using Content.Server.Fluids.EntitySystems; @@ -9,6 +10,10 @@ using Content.Server.Materials; using Content.Server.Popups; using Content.Server.Power.EntitySystems; +using Content.Server.Traits.Assorted; +using Content.Shared.Atmos; +using Content.Shared.CCVar; +using Content.Shared.Chemistry.Components; using Content.Shared.Cloning; using Content.Shared.Damage; using Content.Shared.DeviceLinking.Events; @@ -90,8 +95,23 @@ public override void Initialize() SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnEmagged); SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnPartsRefreshed); + SubscribeLocalEvent(OnUpgradeExamine); + } + private void OnPartsRefreshed(EntityUid uid, CloningPodComponent component, RefreshPartsEvent args) + { + var materialRating = args.PartRatings[component.MachinePartMaterialUse]; + var speedRating = args.PartRatings[component.MachinePartCloningSpeed]; + + component.BiomassCostMultiplier = MathF.Pow(component.PartRatingMaterialMultiplier, materialRating - 1); + component.CloningTime = component.CloningTime * MathF.Pow(component.PartRatingSpeedMultiplier, speedRating - 1); } + private void OnUpgradeExamine(EntityUid uid, CloningPodComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("cloning-pod-component-upgrade-speed", component.CloningTime / component.CloningTime); + args.AddPercentageUpgrade("cloning-pod-component-upgrade-biomass-requirement", component.BiomassCostMultiplier); + } private void OnPortDisconnected(EntityUid uid, CloningPodComponent pod, PortDisconnectedEvent args) { pod.ConnectedConsole = null; diff --git a/Content.Server/Construction/ConstructionSystem.Machine.cs b/Content.Server/Construction/ConstructionSystem.Machine.cs index 2e670dbe40d..65b0b704761 100644 --- a/Content.Server/Construction/ConstructionSystem.Machine.cs +++ b/Content.Server/Construction/ConstructionSystem.Machine.cs @@ -5,6 +5,7 @@ using Content.Shared.Construction.Prototypes; using Content.Shared.Verbs; using Robust.Shared.Containers; +using Robust.Shared.Map.Components; using Robust.Shared.Utility; namespace Content.Server.Construction; @@ -143,7 +144,7 @@ private void CreateBoardAndStockParts(EntityUid uid, MachineComponent component) var p = EntityManager.SpawnEntity(partProto.StockPartPrototype, xform.Coordinates); if (!_container.Insert(p, partContainer)) - throw new Exception($"Couldn't insert machine part of type {part} to machine with prototype {partProto.StockPartPrototype}!"); + throw new Exception($"Couldn't insert machine part of type {part} to machine with prototype {partProto.StockPartPrototype ?? "N/A"}!"); } } @@ -183,7 +184,7 @@ public sealed class RefreshPartsEvent : EntityEventArgs { public IReadOnlyList Parts = new List(); - public Dictionary PartRatings = new(); + public Dictionary PartRatings = new Dictionary(); } public sealed class UpgradeExamineEvent : EntityEventArgs diff --git a/Content.Server/Construction/PartExchangerSystem.cs b/Content.Server/Construction/PartExchangerSystem.cs index ee5edcbd0a0..f364d1b547d 100644 --- a/Content.Server/Construction/PartExchangerSystem.cs +++ b/Content.Server/Construction/PartExchangerSystem.cs @@ -10,6 +10,7 @@ using Robust.Shared.Containers; using Robust.Shared.Utility; using Content.Shared.Wires; +using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Collections; @@ -42,7 +43,7 @@ private void OnDoAfter(EntityUid uid, PartExchangerComponent component, DoAfterE if (args.Handled || args.Args.Target == null) return; - if (!TryComp(uid, out var storage)) + if (!TryComp(uid, out var storage) || storage.Container == null) return; //the parts are stored in here var machinePartQuery = GetEntityQuery(); diff --git a/Content.Server/Gravity/GravityGeneratorComponent.cs b/Content.Server/Gravity/GravityGeneratorComponent.cs index f9462920384..f47d3979391 100644 --- a/Content.Server/Gravity/GravityGeneratorComponent.cs +++ b/Content.Server/Gravity/GravityGeneratorComponent.cs @@ -37,6 +37,9 @@ public sealed partial class GravityGeneratorComponent : SharedGravityGeneratorCo // 0 -> 1 [ViewVariables(VVAccess.ReadWrite)] [DataField("charge")] public float Charge { get; set; } = 1; + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartMaxChargeMultiplier = "Capacitor"; + /// /// Is the gravity generator currently "producing" gravity? /// diff --git a/Content.Server/Gravity/GravityGeneratorSystem.cs b/Content.Server/Gravity/GravityGeneratorSystem.cs index ec5646457e2..b1696e6a713 100644 --- a/Content.Server/Gravity/GravityGeneratorSystem.cs +++ b/Content.Server/Gravity/GravityGeneratorSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Administration.Logs; using Content.Server.Audio; +using Content.Server.Construction; using Content.Server.Power.Components; using Content.Server.Emp; using Content.Shared.Database; @@ -27,6 +28,7 @@ public override void Initialize() SubscribeLocalEvent(OnComponentShutdown); SubscribeLocalEvent(OnParentChanged); // Or just anchor changed? SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnRefreshParts); SubscribeLocalEvent( OnSwitchGenerator); @@ -257,6 +259,12 @@ public void UpdateState(Entity ent, AppearanceComponent? appearance) { _ambientSoundSystem.SetAmbience(ent, false); diff --git a/Content.Server/Kitchen/Components/MicrowaveComponent.cs b/Content.Server/Kitchen/Components/MicrowaveComponent.cs index 815ba8f5213..1e343e5e332 100644 --- a/Content.Server/Kitchen/Components/MicrowaveComponent.cs +++ b/Content.Server/Kitchen/Components/MicrowaveComponent.cs @@ -11,95 +11,99 @@ namespace Content.Server.Kitchen.Components [RegisterComponent] public sealed partial class MicrowaveComponent : Component { - [DataField("cookTimeMultiplier"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float CookTimeMultiplier = 1; - - [DataField("baseHeatMultiplier"), ViewVariables(VVAccess.ReadWrite)] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartCookTimeMultiplier = "Capacitor"; + [DataField] + public float CookTimeScalingConstant = 0.5f; + [DataField] public float BaseHeatMultiplier = 100; - [DataField("objectHeatMultiplier"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float ObjectHeatMultiplier = 100; - [DataField("failureResult", customTypeSerializer: typeof(PrototypeIdSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string BadRecipeEntityId = "FoodBadRecipe"; #region audio - [DataField("beginCookingSound")] + [DataField] public SoundSpecifier StartCookingSound = new SoundPathSpecifier("/Audio/Machines/microwave_start_beep.ogg"); - [DataField("foodDoneSound")] + [DataField] public SoundSpecifier FoodDoneSound = new SoundPathSpecifier("/Audio/Machines/microwave_done_beep.ogg"); - [DataField("clickSound")] + [DataField] public SoundSpecifier ClickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"); - [DataField("ItemBreakSound")] + [DataField] public SoundSpecifier ItemBreakSound = new SoundPathSpecifier("/Audio/Effects/clang.ogg"); public EntityUid? PlayingStream; - [DataField("loopingSound")] + [DataField] public SoundSpecifier LoopingSound = new SoundPathSpecifier("/Audio/Machines/microwave_loop.ogg"); #endregion [ViewVariables] public bool Broken; - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public ProtoId OnPort = "On"; /// - /// This is a fixed offset of 5. - /// The cook times for all recipes should be divisible by 5,with a minimum of 1 second. - /// For right now, I don't think any recipe cook time should be greater than 60 seconds. + /// This is a fixed offset of 5. + /// The cook times for all recipes should be divisible by 5,with a minimum of 1 second. + /// For right now, I don't think any recipe cook time should be greater than 60 seconds. /// - [DataField("currentCookTimerTime"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public uint CurrentCookTimerTime = 0; /// - /// Tracks the elapsed time of the current cook timer. + /// Tracks the elapsed time of the current cook timer. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public TimeSpan CurrentCookTimeEnd = TimeSpan.Zero; /// - /// The maximum number of seconds a microwave can be set to. - /// This is currently only used for validation and the client does not check this. + /// The maximum number of seconds a microwave can be set to. + /// This is currently only used for validation and the client does not check this. /// - [DataField("maxCookTime"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public uint MaxCookTime = 30; /// /// The max temperature that this microwave can heat objects to. /// - [DataField("temperatureUpperThreshold")] + [DataField] public float TemperatureUpperThreshold = 373.15f; public int CurrentCookTimeButtonIndex; public Container Storage = default!; - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public int Capacity = 10; - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public ProtoId MaxItemSize = "Normal"; /// - /// How frequently the microwave can malfunction. + /// How frequently the microwave can malfunction. /// [DataField] public float MalfunctionInterval = 1.0f; /// - /// Chance of an explosion occurring when we microwave a metallic object + /// Chance of an explosion occurring when we microwave a metallic object /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float ExplosionChance = .1f; /// - /// Chance of lightning occurring when we microwave a metallic object - [DataField, ViewVariables(VVAccess.ReadWrite)] + /// Chance of lightning occurring when we microwave a metallic object + /// + [DataField] public float LightningChance = .75f; } diff --git a/Content.Server/Kitchen/Components/ReagentGrinderComponent.cs b/Content.Server/Kitchen/Components/ReagentGrinderComponent.cs index 5bbbe2dc8da..4f4531206c7 100644 --- a/Content.Server/Kitchen/Components/ReagentGrinderComponent.cs +++ b/Content.Server/Kitchen/Components/ReagentGrinderComponent.cs @@ -1,6 +1,8 @@ using Content.Shared.Kitchen; using Content.Server.Kitchen.EntitySystems; +using Content.Shared.Construction.Prototypes; using Robust.Shared.Audio; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Kitchen.Components { @@ -13,15 +15,30 @@ namespace Content.Server.Kitchen.Components [Access(typeof(ReagentGrinderSystem)), RegisterComponent] public sealed partial class ReagentGrinderComponent : Component { - [DataField] + [ViewVariables(VVAccess.ReadWrite)] public int StorageMaxEntities = 6; [DataField] - public TimeSpan WorkTime = TimeSpan.FromSeconds(3.5); // Roughly matches the grind/juice sounds. + public int BaseStorageMaxEntities = 4; + + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartStorageMax = "MatterBin"; + + [DataField] + public int StoragePerPartRating = 4; [DataField] + public TimeSpan WorkTime = TimeSpan.FromSeconds(3.5); // Roughly matches the grind/juice sounds. + + [ViewVariables(VVAccess.ReadWrite)] public float WorkTimeMultiplier = 1; + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartWorkTime = "Manipulator"; + + [DataField] + public float PartRatingWorkTimerMulitplier = 0.6f; + [DataField] public SoundSpecifier ClickSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"); diff --git a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs index 212383c463a..3de7051f54d 100644 --- a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs @@ -76,6 +76,8 @@ public override void Initialize() SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnAnchorChanged); SubscribeLocalEvent(OnSuicide); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnSignalReceived); @@ -342,6 +344,17 @@ private void OnAnchorChanged(EntityUid uid, MicrowaveComponent component, ref An _container.EmptyContainer(component.Storage); } + private void OnRefreshParts(Entity ent, ref RefreshPartsEvent args) + { + var cookRating = args.PartRatings[ent.Comp.MachinePartCookTimeMultiplier]; + ent.Comp.CookTimeMultiplier = MathF.Pow(ent.Comp.CookTimeScalingConstant, cookRating - 1); + } + + private void OnUpgradeExamine(Entity ent, ref UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("microwave-component-upgrade-cook-time", ent.Comp.CookTimeMultiplier); + } + private void OnSignalReceived(Entity ent, ref SignalReceivedEvent args) { if (args.Port != ent.Comp.OnPort) diff --git a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs index e8ee4539860..aad33fea678 100644 --- a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Server.Construction; using Content.Server.Kitchen.Components; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; @@ -48,6 +49,8 @@ public override void Initialize() SubscribeLocalEvent((uid, _, _) => UpdateUiState(uid)); SubscribeLocalEvent((EntityUid uid, ReagentGrinderComponent _, ref PowerChangedEvent _) => UpdateUiState(uid)); SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnContainerModified); SubscribeLocalEvent(OnContainerModified); @@ -197,6 +200,24 @@ private void OnInteractUsing(Entity entity, ref Interac args.Handled = true; } + /// + /// Gotta be efficient, you know? you're saving a whole extra second here and everything. + /// + private void OnRefreshParts(Entity entity, ref RefreshPartsEvent args) + { + var ratingWorkTime = args.PartRatings[entity.Comp.MachinePartWorkTime]; + var ratingStorage = args.PartRatings[entity.Comp.MachinePartStorageMax]; + + entity.Comp.WorkTimeMultiplier = MathF.Pow(entity.Comp.PartRatingWorkTimerMulitplier, ratingWorkTime - 1); + entity.Comp.StorageMaxEntities = entity.Comp.BaseStorageMaxEntities + (int) (entity.Comp.StoragePerPartRating * (ratingStorage - 1)); + } + + private void OnUpgradeExamine(Entity entity, ref UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("reagent-grinder-component-upgrade-work-time", entity.Comp.WorkTimeMultiplier); + args.AddNumberUpgrade("reagent-grinder-component-upgrade-storage", entity.Comp.StorageMaxEntities - entity.Comp.BaseStorageMaxEntities); + } + private void UpdateUiState(EntityUid uid) { ReagentGrinderComponent? grinderComp = null; diff --git a/Content.Server/Materials/MaterialReclaimerSystem.cs b/Content.Server/Materials/MaterialReclaimerSystem.cs index aa24fde44b7..de82f125985 100644 --- a/Content.Server/Materials/MaterialReclaimerSystem.cs +++ b/Content.Server/Materials/MaterialReclaimerSystem.cs @@ -1,4 +1,6 @@ using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Server.Chemistry.EntitySystems; +using Content.Server.Construction; using Content.Server.Fluids.EntitySystems; using Content.Server.GameTicking; using Content.Server.Popups; @@ -45,6 +47,8 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnInteractUsing, before: new []{typeof(WiresSystem), typeof(SolutionTransferSystem)}); @@ -56,6 +60,18 @@ private void OnStartup(Entity entity, ref ComponentS _solutionContainer.EnsureSolution(entity.Owner, entity.Comp.SolutionContainerId); } + private void OnUpgradeExamine(Entity entity, ref UpgradeExamineEvent args) + { + args.AddPercentageUpgrade(Loc.GetString("material-reclaimer-upgrade-process-rate"), entity.Comp.MaterialProcessRate / entity.Comp.BaseMaterialProcessRate); + } + + private void OnRefreshParts(Entity entity, ref RefreshPartsEvent args) + { + var rating = args.PartRatings[entity.Comp.MachinePartProcessRate] - 1; + entity.Comp.MaterialProcessRate = entity.Comp.BaseMaterialProcessRate * MathF.Pow(entity.Comp.PartRatingProcessRateMultiplier, rating); + Dirty(entity); + } + private void OnPowerChanged(Entity entity, ref PowerChangedEvent args) { AmbientSound.SetAmbience(entity.Owner, entity.Comp.Enabled && args.Powered); diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerComponent.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerComponent.cs index 61d36f98b96..1358bfbcbbc 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerComponent.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerComponent.cs @@ -1,4 +1,8 @@ +using System.Threading; +using Content.Shared.Construction.Prototypes; using Content.Shared.Storage; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Medical.BiomassReclaimer { @@ -6,72 +10,111 @@ namespace Content.Server.Medical.BiomassReclaimer public sealed partial class BiomassReclaimerComponent : Component { /// - /// This gets set for each mob it processes. - /// When it hits 0, there is a chance for the reclaimer to either spill blood or throw an item. + /// This gets set for each mob it processes. + /// When it hits 0, there is a chance for the reclaimer to either spill blood or throw an item. /// [ViewVariables] public float RandomMessTimer = 0f; /// - /// The interval for . + /// The interval for . /// - [ViewVariables(VVAccess.ReadWrite), DataField] + [DataField] public TimeSpan RandomMessInterval = TimeSpan.FromSeconds(5); /// - /// This gets set for each mob it processes. - /// When it hits 0, spit out biomass. + /// This gets set for each mob it processes. + /// When it hits 0, spit out biomass. /// [ViewVariables] - public float ProcessingTimer = default; + public float ProcessingTimer; /// - /// Amount of biomass that the mob being processed will yield. - /// This is calculated from the YieldPerUnitMass. - /// Also stores non-integer leftovers. + /// Amount of biomass that the mob being processed will yield. + /// This is calculated from the YieldPerUnitMass. + /// Also stores non-integer leftovers. /// [ViewVariables] - public float CurrentExpectedYield = 0f; + public float CurrentExpectedYield; /// - /// The reagent that will be spilled while processing a mob. + /// The reagent that will be spilled while processing a mob. /// [ViewVariables] public string? BloodReagent; /// - /// Entities that can be randomly spawned while processing a mob. + /// Entities that can be randomly spawned while processing a mob. /// public List SpawnedEntities = new(); /// - /// How many units of biomass it produces for each unit of mass. + /// How many units of biomass it produces for each unit of mass. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float YieldPerUnitMass = 0.4f; + [ViewVariables(VVAccess.ReadWrite)] + public float YieldPerUnitMass = default; /// - /// How many seconds to take to insert an entity per unit of its mass. + /// The base yield per mass unit when no components are upgraded. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float BaseYieldPerUnitMass = 0.4f; + + /// + /// Machine part whose rating modifies the yield per mass. + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartYieldAmount = "MatterBin"; + + /// + /// How much the machine part quality affects the yield. + /// Going up a tier will multiply the yield by this amount. + /// + [DataField] + public float PartRatingYieldAmountMultiplier = 1.25f; + + /// + /// How many seconds to take to insert an entity per unit of its mass. + /// + [DataField] public float BaseInsertionDelay = 0.1f; /// - /// How much to multiply biomass yield from botany produce. + /// How much to multiply biomass yield from botany produce. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float ProduceYieldMultiplier = 0.25f; /// - /// The time it takes to process a mob, per mass. + /// The time it takes to process a mob, per mass. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float ProcessingTimePerUnitMass; + + /// + /// The base time per mass unit that it takes to process a mob + /// when no components are upgraded. + /// + [DataField] + public float BaseProcessingTimePerUnitMass = 0.5f; + + /// + /// The machine part that increses the processing speed. + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartProcessingSpeed = "Manipulator"; + + /// + /// How much the machine part quality affects the yield. + /// Going up a tier will multiply the speed by this amount. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float ProcessingTimePerUnitMass = 0.5f; + [DataField] + public float PartRatingSpeedMultiplier = 1.35f; /// - /// Will this refuse to gib a living mob? + /// Will this refuse to gib a living mob? /// - [ViewVariables(VVAccess.ReadWrite), DataField] + [DataField] public bool SafetyEnabled = true; } } diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs index eaf04d64b2b..97a758a5ed3 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs @@ -1,6 +1,7 @@ using System.Numerics; using Content.Server.Body.Components; using Content.Server.Botany.Components; +using Content.Server.Construction; using Content.Server.Fluids.EntitySystems; using Content.Server.Materials; using Content.Server.Power.Components; @@ -99,6 +100,8 @@ public override void Initialize() SubscribeLocalEvent(OnUnanchorAttempt); SubscribeLocalEvent(OnAfterInteractUsing); SubscribeLocalEvent(OnClimbedOn); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnSuicide); SubscribeLocalEvent(OnDoAfter); @@ -173,6 +176,26 @@ private void OnClimbedOn(Entity reclaimer, ref Climbe StartProcessing(args.Climber, reclaimer); } + private void OnRefreshParts(EntityUid uid, BiomassReclaimerComponent component, RefreshPartsEvent args) + { + var laserRating = args.PartRatings[component.MachinePartProcessingSpeed]; + var manipRating = args.PartRatings[component.MachinePartYieldAmount]; + + // Processing time slopes downwards with part rating. + component.ProcessingTimePerUnitMass = + component.BaseProcessingTimePerUnitMass / MathF.Pow(component.PartRatingSpeedMultiplier, laserRating - 1); + + // Yield slopes upwards with part rating. + component.YieldPerUnitMass = + component.BaseYieldPerUnitMass * MathF.Pow(component.PartRatingYieldAmountMultiplier, manipRating - 1); + } + + private void OnUpgradeExamine(EntityUid uid, BiomassReclaimerComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("biomass-reclaimer-component-upgrade-speed", component.BaseProcessingTimePerUnitMass / component.ProcessingTimePerUnitMass); + args.AddPercentageUpgrade("biomass-reclaimer-component-upgrade-biomass-yield", component.YieldPerUnitMass / component.BaseYieldPerUnitMass); + } + private void OnDoAfter(Entity reclaimer, ref ReclaimerDoAfterEvent args) { if (args.Handled diff --git a/Content.Server/Medical/Components/MedicalScannerComponent.cs b/Content.Server/Medical/Components/MedicalScannerComponent.cs index 96de6499875..15ca6cd2bd7 100644 --- a/Content.Server/Medical/Components/MedicalScannerComponent.cs +++ b/Content.Server/Medical/Components/MedicalScannerComponent.cs @@ -1,5 +1,4 @@ using Content.Shared.Construction.Prototypes; -using Content.Shared.DragDrop; using Content.Shared.MedicalScanner; using Robust.Shared.Containers; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; @@ -13,10 +12,15 @@ public sealed partial class MedicalScannerComponent : SharedMedicalScannerCompon public ContainerSlot BodyContainer = default!; public EntityUid? ConnectedConsole; - [DataField, ViewVariables(VVAccess.ReadWrite)] + [ViewVariables(VVAccess.ReadWrite)] public float CloningFailChanceMultiplier = 1f; - - // Nyano, needed for Metem Machine. + public float MetemKarmaBonus = 0.25f; + + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartCloningFailChance = "Capacitor"; + + [DataField] + public float PartRatingFailMultiplier = 0.75f; } } diff --git a/Content.Server/Medical/MedicalScannerSystem.cs b/Content.Server/Medical/MedicalScannerSystem.cs index a6ce43c4081..ab6918e373b 100644 --- a/Content.Server/Medical/MedicalScannerSystem.cs +++ b/Content.Server/Medical/MedicalScannerSystem.cs @@ -7,6 +7,7 @@ using Content.Shared.Verbs; using Robust.Shared.Containers; using Content.Server.Cloning.Components; +using Content.Server.Construction; using Content.Server.DeviceLinking.Systems; using Content.Shared.DeviceLinking.Events; using Content.Server.Power.EntitySystems; @@ -44,6 +45,8 @@ public override void Initialize() SubscribeLocalEvent(OnDragDropOn); SubscribeLocalEvent(OnPortDisconnected); SubscribeLocalEvent(OnAnchorChanged); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnCanDragDropOn); } @@ -220,5 +223,17 @@ public void EjectBody(EntityUid uid, MedicalScannerComponent? scannerComponent) _climbSystem.ForciblySetClimbing(contained, uid); UpdateAppearance(uid, scannerComponent); } + + private void OnRefreshParts(EntityUid uid, MedicalScannerComponent component, RefreshPartsEvent args) + { + var ratingFail = args.PartRatings[component.MachinePartCloningFailChance]; + + component.CloningFailChanceMultiplier = MathF.Pow(component.PartRatingFailMultiplier, ratingFail - 1); + } + + private void OnUpgradeExamine(EntityUid uid, MedicalScannerComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("medical-scanner-upgrade-cloning", component.CloningFailChanceMultiplier); + } } } diff --git a/Content.Server/Nutrition/Components/FatExtractorComponent.cs b/Content.Server/Nutrition/Components/FatExtractorComponent.cs index e23c557236c..fa6edc911e1 100644 --- a/Content.Server/Nutrition/Components/FatExtractorComponent.cs +++ b/Content.Server/Nutrition/Components/FatExtractorComponent.cs @@ -1,4 +1,5 @@ using Content.Server.Nutrition.EntitySystems; +using Content.Shared.Construction.Prototypes; using Content.Shared.Nutrition.Components; using Robust.Shared.Audio; using Robust.Shared.Prototypes; @@ -8,67 +9,87 @@ namespace Content.Server.Nutrition.Components; /// -/// This is used for a machine that extracts hunger from entities and creates meat. Yum! +/// This is used for a machine that extracts hunger from entities and creates meat. Yum! /// [RegisterComponent, Access(typeof(FatExtractorSystem)), AutoGenerateComponentPause] public sealed partial class FatExtractorComponent : Component { /// - /// Whether or not the extractor is currently extracting fat from someone + /// Whether or not the extractor is currently extracting fat from someone /// - [DataField("processing")] + [DataField] public bool Processing = true; /// - /// How much nutrition is extracted per second. + /// How much nutrition is extracted per second. /// - [DataField("nutritionPerSecond"), ViewVariables(VVAccess.ReadWrite)] + [ViewVariables(VVAccess.ReadWrite)] public int NutritionPerSecond = 10; /// - /// An accumulator which tracks extracted nutrition to determine - /// when to spawn a meat. + /// The base rate of extraction /// - [DataField("nutrientAccumulator"), ViewVariables(VVAccess.ReadWrite)] + [DataField] + public int BaseNutritionPerSecond = 10; + + #region Machine Upgrade + /// + /// Which machine part affects the nutrition rate + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartNutritionRate = "Manipulator"; + + /// + /// The increase in rate per each rating above 1. + /// + [DataField] + public float PartRatingRateMultiplier = 10; + #endregion + + /// + /// An accumulator which tracks extracted nutrition to determine + /// when to spawn a meat. + /// + [DataField] public int NutrientAccumulator; /// - /// How high has to be to spawn meat + /// How high has to be to spawn meat /// - [DataField("nutrientPerMeat"), ViewVariables(VVAccess.ReadWrite)] - public int NutrientPerMeat = 30; + [DataField] + public int NutrientPerMeat = 60; /// - /// Meat spawned by the extractor. + /// Meat spawned by the extractor. /// - [DataField("meatPrototype", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string MeatPrototype = "FoodMeat"; /// - /// When the next update will occur + /// When the next update will occur /// - [DataField("nextUpdate", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] [AutoPausedField] public TimeSpan NextUpdate; /// - /// How long each update takes + /// How long each update takes /// - [DataField("updateTime"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public TimeSpan UpdateTime = TimeSpan.FromSeconds(1); /// - /// The sound played when extracting + /// The sound played when extracting /// - [DataField("processSound")] + [DataField] public SoundSpecifier? ProcessSound; public EntityUid? Stream; /// - /// A minium hunger threshold for extracting nutrition. - /// Ignored when emagged. + /// A minium hunger threshold for extracting nutrition. + /// Ignored when emagged. /// - [DataField("minHungerThreshold")] + [DataField] public HungerThreshold MinHungerThreshold = HungerThreshold.Okay; } diff --git a/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs b/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs index 180e40d1e42..dc1f67c7400 100644 --- a/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; +using Content.Server.Construction; using Content.Server.Nutrition.Components; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; @@ -9,6 +10,7 @@ using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Storage.Components; +using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Timing; @@ -27,12 +29,25 @@ public sealed class FatExtractorSystem : EntitySystem /// public override void Initialize() { + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnGotEmagged); SubscribeLocalEvent(OnClosed); SubscribeLocalEvent(OnOpen); SubscribeLocalEvent(OnPowerChanged); } + private void OnRefreshParts(EntityUid uid, FatExtractorComponent component, RefreshPartsEvent args) + { + var rating = args.PartRatings[component.MachinePartNutritionRate] - 1; + component.NutritionPerSecond = component.BaseNutritionPerSecond + (int) (component.PartRatingRateMultiplier * rating); + } + + private void OnUpgradeExamine(EntityUid uid, FatExtractorComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("fat-extractor-component-rate", (float) component.NutritionPerSecond / component.BaseNutritionPerSecond); + } + private void OnGotEmagged(EntityUid uid, FatExtractorComponent component, ref GotEmaggedEvent args) { args.Handled = true; diff --git a/Content.Server/Power/Components/UpgradeBatteryComponent.cs b/Content.Server/Power/Components/UpgradeBatteryComponent.cs new file mode 100644 index 00000000000..b676883b711 --- /dev/null +++ b/Content.Server/Power/Components/UpgradeBatteryComponent.cs @@ -0,0 +1,28 @@ +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Power.Components +{ + + [RegisterComponent] + public sealed partial class UpgradeBatteryComponent : Component + { + /// + /// The machine part that affects the power capacity. + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartPowerCapacity = "PowerCell"; + + /// + /// The machine part rating is raised to this power when calculating power gain + /// + [DataField] + public float MaxChargeMultiplier = 2f; + + /// + /// Power gain scaling + /// + [DataField] + public float BaseMaxCharge = 8000000; + } +} diff --git a/Content.Server/Power/Components/UpgradePowerDrawComponent.cs b/Content.Server/Power/Components/UpgradePowerDrawComponent.cs new file mode 100644 index 00000000000..23db4905cc5 --- /dev/null +++ b/Content.Server/Power/Components/UpgradePowerDrawComponent.cs @@ -0,0 +1,41 @@ +using Content.Server.Construction.Components; +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Power.Components; + +/// +/// This is used for machines whose power draw +/// can be decreased through machine part upgrades. +/// +[RegisterComponent] +public sealed partial class UpgradePowerDrawComponent : Component +{ + /// + /// The base power draw of the machine. + /// Prioritizes hv/mv draw over lv draw. + /// Value is initializezd on map init from + /// + [ViewVariables(VVAccess.ReadWrite)] + public float BaseLoad; + + /// + /// The machine part that affects the power draw. + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] + public string MachinePartPowerDraw = "Capacitor"; + + /// + /// The multiplier used for scaling the power draw. + /// + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] + public float PowerDrawMultiplier = 1f; + + /// + /// What type of scaling is being used? + /// + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] + public MachineUpgradeScalingType Scaling; +} + + diff --git a/Content.Server/Power/Components/UpgradePowerSupplierComponent.cs b/Content.Server/Power/Components/UpgradePowerSupplierComponent.cs new file mode 100644 index 00000000000..012c38a6b90 --- /dev/null +++ b/Content.Server/Power/Components/UpgradePowerSupplierComponent.cs @@ -0,0 +1,36 @@ +using Content.Server.Construction.Components; +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Power.Components; + +[RegisterComponent] +public sealed partial class UpgradePowerSupplierComponent : Component +{ + [ViewVariables(VVAccess.ReadWrite)] + public float BaseSupplyRate; + + /// + /// The machine part that affects the power supplu. + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartPowerSupply = "Capacitor"; + + /// + /// The multiplier used for scaling the power supply. + /// + [DataField(required: true)] + public float PowerSupplyMultiplier = 1f; + + /// + /// What type of scaling is being used? + /// + [DataField(required: true)] + public MachineUpgradeScalingType Scaling; + + /// + /// The current value that the power supply is being scaled by, + /// + [DataField] + public float ActualScalar = 1f; +} diff --git a/Content.Server/Power/Components/UpgradePowerSupplyRampingComponent.cs b/Content.Server/Power/Components/UpgradePowerSupplyRampingComponent.cs new file mode 100644 index 00000000000..61a654b383b --- /dev/null +++ b/Content.Server/Power/Components/UpgradePowerSupplyRampingComponent.cs @@ -0,0 +1,36 @@ +using Content.Server.Construction.Components; +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Power.Components; + +[RegisterComponent] +public sealed partial class UpgradePowerSupplyRampingComponent : Component +{ + [ViewVariables(VVAccess.ReadWrite)] + public float BaseRampRate; + + /// + /// The machine part that affects the power supply ramping + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartRampRate = "Capacitor"; + + /// + /// The multiplier used for scaling the power supply ramping + /// + [DataField] + public float SupplyRampingMultiplier = 1f; + + /// + /// What type of scaling is being used? + /// + [DataField(required: true)] + public MachineUpgradeScalingType Scaling; + + /// + /// The current value that the power supply is being scaled by + /// + [DataField] + public float ActualScalar = 1f; +} diff --git a/Content.Server/Power/EntitySystems/UpgradeBatterySystem.cs b/Content.Server/Power/EntitySystems/UpgradeBatterySystem.cs new file mode 100644 index 00000000000..734cf9d89ce --- /dev/null +++ b/Content.Server/Power/EntitySystems/UpgradeBatterySystem.cs @@ -0,0 +1,39 @@ +using Content.Server.Construction; +using Content.Server.Power.Components; +using JetBrains.Annotations; + +namespace Content.Server.Power.EntitySystems +{ + [UsedImplicitly] + public sealed class UpgradeBatterySystem : EntitySystem + { + [Dependency] private readonly BatterySystem _batterySystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); + } + + public void OnRefreshParts(EntityUid uid, UpgradeBatteryComponent component, RefreshPartsEvent args) + { + var powerCellRating = args.PartRatings[component.MachinePartPowerCapacity]; + + if (TryComp(uid, out var batteryComp)) + { + _batterySystem.SetMaxCharge(uid, MathF.Pow(component.MaxChargeMultiplier, powerCellRating - 1) * component.BaseMaxCharge, batteryComp); + } + } + + private void OnUpgradeExamine(EntityUid uid, UpgradeBatteryComponent component, UpgradeExamineEvent args) + { + // UpgradeBatteryComponent.MaxChargeMultiplier is not the actual multiplier, so we have to do this. + if (TryComp(uid, out var batteryComp)) + { + args.AddPercentageUpgrade("upgrade-max-charge", batteryComp.MaxCharge / component.BaseMaxCharge); + } + } + } +} diff --git a/Content.Server/Power/EntitySystems/UpgradePowerSystem.cs b/Content.Server/Power/EntitySystems/UpgradePowerSystem.cs new file mode 100644 index 00000000000..d2f6ee4f568 --- /dev/null +++ b/Content.Server/Power/EntitySystems/UpgradePowerSystem.cs @@ -0,0 +1,151 @@ +using Content.Server.Construction; +using Content.Server.Construction.Components; +using Content.Server.Power.Components; + +namespace Content.Server.Power.EntitySystems; + +/// +/// This handles using upgraded machine parts +/// to modify the power supply/generation of a machine. +/// +public sealed class UpgradePowerSystem : EntitySystem +{ + /// + public override void Initialize() + { + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); + + SubscribeLocalEvent(OnSupplierMapInit); + SubscribeLocalEvent(OnSupplierRefreshParts); + SubscribeLocalEvent(OnSupplierUpgradeExamine); + + SubscribeLocalEvent(OnSupplyRampingMapInit); + SubscribeLocalEvent(OnSupplyRampingRefreshParts); + SubscribeLocalEvent(OnSupplyRampingUpgradeExamine); + } + + private void OnMapInit(EntityUid uid, UpgradePowerDrawComponent component, MapInitEvent args) + { + if (TryComp(uid, out var powa)) + component.BaseLoad = powa.DrawRate; + else if (TryComp(uid, out var powa2)) + component.BaseLoad = powa2.Load; + } + + private void OnRefreshParts(EntityUid uid, UpgradePowerDrawComponent component, RefreshPartsEvent args) + { + var load = component.BaseLoad; + var rating = args.PartRatings[component.MachinePartPowerDraw]; + switch (component.Scaling) + + { + case MachineUpgradeScalingType.Linear: + load += component.PowerDrawMultiplier * (rating - 1); + break; + case MachineUpgradeScalingType.Exponential: + load *= MathF.Pow(component.PowerDrawMultiplier, rating - 1); + break; + default: + Log.Error($"invalid power scaling type for {ToPrettyString(uid)}."); + load = 0; + break; + } + + if (TryComp(uid, out var powa)) + powa.Load = load; + if (TryComp(uid, out var powa2)) + powa2.DrawRate = load; + } + + private void OnUpgradeExamine(EntityUid uid, UpgradePowerDrawComponent component, UpgradeExamineEvent args) + { + // UpgradePowerDrawComponent.PowerDrawMultiplier is not the actual multiplier, so we have to do this. + var powerDrawMultiplier = CompOrNull(uid)?.Load / component.BaseLoad + ?? CompOrNull(uid)?.DrawRate / component.BaseLoad; + + if (powerDrawMultiplier is not null) + args.AddPercentageUpgrade("upgrade-power-draw", powerDrawMultiplier.Value); + } + + private void OnSupplierMapInit(EntityUid uid, UpgradePowerSupplierComponent component, MapInitEvent args) + { + if (!TryComp(uid, out var supplier)) + return; + + component.BaseSupplyRate = supplier.MaxSupply; + } + + private void OnSupplierRefreshParts(EntityUid uid, UpgradePowerSupplierComponent component, RefreshPartsEvent args) + { + var supply = component.BaseSupplyRate; + var rating = args.PartRatings[component.MachinePartPowerSupply]; + switch (component.Scaling) + + { + case MachineUpgradeScalingType.Linear: + supply += component.PowerSupplyMultiplier * component.BaseSupplyRate * (rating - 1); + break; + case MachineUpgradeScalingType.Exponential: + supply *= MathF.Pow(component.PowerSupplyMultiplier, rating - 1); + break; + default: + Log.Error($"invalid power scaling type for {ToPrettyString(uid)}."); + supply = component.BaseSupplyRate; + break; + } + + component.ActualScalar = supply / component.BaseSupplyRate; + + if (!TryComp(uid, out var powa)) + return; + + powa.MaxSupply = supply; + } + + private void OnSupplierUpgradeExamine(EntityUid uid, UpgradePowerSupplierComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("upgrade-power-supply", component.ActualScalar); + } + + private void OnSupplyRampingMapInit(EntityUid uid, UpgradePowerSupplyRampingComponent component, MapInitEvent args) + { + if (!TryComp(uid, out var battery)) + return; + + component.BaseRampRate = battery.SupplyRampRate; + } + + private void OnSupplyRampingRefreshParts(EntityUid uid, UpgradePowerSupplyRampingComponent component, RefreshPartsEvent args) + { + var rampRate = component.BaseRampRate; + var rating = args.PartRatings[component.MachinePartRampRate]; + switch (component.Scaling) + + { + case MachineUpgradeScalingType.Linear: + rampRate += component.SupplyRampingMultiplier * component.BaseRampRate * (rating - 1); + break; + case MachineUpgradeScalingType.Exponential: + rampRate *= MathF.Pow(component.SupplyRampingMultiplier, rating - 1); + break; + default: + Log.Error($"invalid power supply ramping type for {ToPrettyString(uid)}."); + rampRate = component.BaseRampRate; + break; + } + + component.ActualScalar = rampRate / component.BaseRampRate; + + if (!TryComp(uid, out var battery)) + return; + + battery.SupplyRampRate = rampRate; + } + + private void OnSupplyRampingUpgradeExamine(EntityUid uid, UpgradePowerSupplyRampingComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("upgrade-power-supply-ramping", component.ActualScalar); + } +} diff --git a/Content.Server/Power/Generator/GeneratorSystem.cs b/Content.Server/Power/Generator/GeneratorSystem.cs index a75d1e4113d..721a959820b 100644 --- a/Content.Server/Power/Generator/GeneratorSystem.cs +++ b/Content.Server/Power/Generator/GeneratorSystem.cs @@ -26,8 +26,11 @@ public sealed class GeneratorSystem : SharedGeneratorSystem [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly PuddleSystem _puddle = default!; + private EntityQuery _upgradeQuery; + public override void Initialize() { + _upgradeQuery = GetEntityQuery(); UpdatesBefore.Add(typeof(PowerNetSystem)); @@ -225,7 +228,9 @@ public override void Update(float frameTime) supplier.Enabled = true; - supplier.MaxSupply = gen.TargetPower; + var upgradeMultiplier = _upgradeQuery.CompOrNull(uid)?.ActualScalar ?? 1f; + + supplier.MaxSupply = gen.TargetPower * upgradeMultiplier; var eff = 1 / CalcFuelEfficiency(gen.TargetPower, gen.OptimalPower, gen); var consumption = gen.OptimalBurnRate * frameTime * eff; diff --git a/Content.Server/Shuttles/Components/ThrusterComponent.cs b/Content.Server/Shuttles/Components/ThrusterComponent.cs index 3bba9b5a7f0..f64d9bdcbf0 100644 --- a/Content.Server/Shuttles/Components/ThrusterComponent.cs +++ b/Content.Server/Shuttles/Components/ThrusterComponent.cs @@ -1,8 +1,10 @@ using System.Numerics; using Content.Server.Shuttles.Systems; +using Content.Shared.Construction.Prototypes; using Content.Shared.Damage; using Robust.Shared.GameStates; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Shuttles.Components { @@ -13,7 +15,7 @@ public sealed partial class ThrusterComponent : Component /// /// Whether the thruster has been force to be enabled / disabled (e.g. VV, interaction, etc.) /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public bool Enabled { get; set; } = true; /// @@ -22,9 +24,12 @@ public sealed partial class ThrusterComponent : Component public bool IsOn; // Need to serialize this because RefreshParts isn't called on Init and this will break post-mapinit maps! - [ViewVariables(VVAccess.ReadWrite), DataField("thrust")] + [DataField] public float Thrust = 100f; + [DataField] + public float BaseThrust = 100f; + [DataField("thrusterType")] public ThrusterType Type = ThrusterType.Linear; @@ -37,24 +42,31 @@ public sealed partial class ThrusterComponent : Component }; /// - /// How much damage is done per second to anything colliding with our thrust. + /// How much damage is done per second to anything colliding with our thrust. /// - [DataField("damage")] public DamageSpecifier? Damage = new(); + [DataField] + public DamageSpecifier? Damage = new(); - [DataField("requireSpace")] + [DataField] public bool RequireSpace = true; // Used for burns public List Colliding = new(); - public bool Firing = false; + public bool Firing; /// - /// Next time we tick damage for anyone colliding. + /// Next time we tick damage for anyone colliding. /// - [ViewVariables(VVAccess.ReadWrite), DataField("nextFire", customTypeSerializer:typeof(TimeOffsetSerializer))] + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] public TimeSpan NextFire; + + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartThrust = "Capacitor"; + + [DataField] + public float PartRatingThrustMultiplier = 1.5f; } public enum ThrusterType diff --git a/Content.Server/Shuttles/Systems/ThrusterSystem.cs b/Content.Server/Shuttles/Systems/ThrusterSystem.cs index be55cd9a62a..ee131d7857a 100644 --- a/Content.Server/Shuttles/Systems/ThrusterSystem.cs +++ b/Content.Server/Shuttles/Systems/ThrusterSystem.cs @@ -1,5 +1,6 @@ using System.Numerics; using Content.Server.Audio; +using Content.Server.Construction; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Shuttles.Components; @@ -51,6 +52,9 @@ public override void Initialize() SubscribeLocalEvent(OnThrusterExamine); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); + SubscribeLocalEvent(OnShuttleTileChange); } @@ -580,6 +584,24 @@ public void SetAngularThrust(ShuttleComponent component, bool on) } } + private void OnRefreshParts(EntityUid uid, ThrusterComponent component, RefreshPartsEvent args) + { + if (component.IsOn) // safely disable thruster to prevent negative thrust + DisableThruster(uid, component); + + var thrustRating = args.PartRatings[component.MachinePartThrust]; + + component.Thrust = component.BaseThrust * MathF.Pow(component.PartRatingThrustMultiplier, thrustRating - 1); + + if (component.Enabled && CanEnable(uid, component)) + EnableThruster(uid, component); + } + + private void OnUpgradeExamine(EntityUid uid, ThrusterComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("thruster-comp-upgrade-thrust", component.Thrust / component.BaseThrust); + } + #endregion private int GetFlagIndex(DirectionFlag flag) diff --git a/Content.Server/Singularity/EntitySystems/EmitterSystem.cs b/Content.Server/Singularity/EntitySystems/EmitterSystem.cs index a9763b64d90..652ca236e44 100644 --- a/Content.Server/Singularity/EntitySystems/EmitterSystem.cs +++ b/Content.Server/Singularity/EntitySystems/EmitterSystem.cs @@ -1,6 +1,7 @@ using System.Numerics; using System.Threading; using Content.Server.Administration.Logs; +using Content.Server.Construction; using Content.Server.DeviceLinking.Events; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; @@ -47,6 +48,8 @@ public override void Initialize() SubscribeLocalEvent(OnInteractHand); SubscribeLocalEvent>(OnGetVerb); SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnAnchorStateChanged); SubscribeLocalEvent(OnSignalReceived); } @@ -176,6 +179,20 @@ private void OnApcChanged(EntityUid uid, EmitterComponent component, ref PowerCh } } + private void OnRefreshParts(EntityUid uid, EmitterComponent component, RefreshPartsEvent args) + { + var fireRateRating = args.PartRatings[component.MachinePartFireRate]; + + component.FireInterval = component.BaseFireInterval * MathF.Pow(component.FireRateMultiplier, fireRateRating - 1); + component.FireBurstDelayMin = component.BaseFireBurstDelayMin * MathF.Pow(component.FireRateMultiplier, fireRateRating - 1); + component.FireBurstDelayMax = component.BaseFireBurstDelayMax * MathF.Pow(component.FireRateMultiplier, fireRateRating - 1); + } + + private void OnUpgradeExamine(EntityUid uid, EmitterComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("emitter-component-upgrade-fire-rate", (float) (component.BaseFireInterval.TotalSeconds / component.FireInterval.TotalSeconds)); + } + public void SwitchOff(EntityUid uid, EmitterComponent component) { component.IsOn = false; diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs index 25f64ba4b64..7187a704768 100644 --- a/Content.Server/Store/Systems/StoreSystem.Ui.cs +++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs @@ -265,6 +265,12 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi listing.PurchaseAmount++; //track how many times something has been purchased _audio.PlayEntity(component.BuySuccessSound, msg.Session, uid); //cha-ching! + if (listing.SaleLimit != 0 && listing.DiscountValue > 0 && listing.PurchaseAmount >= listing.SaleLimit) + { + listing.DiscountValue = 0; + listing.Cost = listing.OldCost; + } + UpdateUserInterface(buyer, uid, component); } diff --git a/Content.Server/Store/Systems/StoreSystem.cs b/Content.Server/Store/Systems/StoreSystem.cs index 72aeb29d195..a86dffef633 100644 --- a/Content.Server/Store/Systems/StoreSystem.cs +++ b/Content.Server/Store/Systems/StoreSystem.cs @@ -10,6 +10,7 @@ using Robust.Server.GameObjects; using Robust.Shared.Prototypes; using System.Linq; +using Content.Server.StoreDiscount; using Robust.Shared.Utility; namespace Content.Server.Store.Systems; @@ -22,6 +23,7 @@ public sealed partial class StoreSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly StoreDiscountSystem _storeDiscount = default!; public override void Initialize() { @@ -199,6 +201,8 @@ public void InitializeFromPreset(StorePresetPrototype preset, EntityUid uid, Sto if (component.Balance == new Dictionary() && preset.InitialBalance != null) //if we don't have a value stored, use the preset TryAddCurrency(preset.InitialBalance, uid, component); + _storeDiscount.ApplyDiscounts(component.Listings, preset); + var ui = _ui.GetUiOrNull(uid, StoreUiKey.Key); if (ui != null) { @@ -225,7 +229,7 @@ public CurrencyInsertAttemptEvent(EntityUid user, EntityUid target, EntityUid us /// -/// Nyano/DeltaV Code. For penguin bombs and what not. +/// Nyano/DeltaV Code. For penguin bombs and what not. /// Raised on an item when it is purchased. /// An item may need to set it upself up for its purchaser. /// For example, to make sure it isn't hostile to them or diff --git a/Content.Server/StoreDiscount/StoreDiscountSystem.cs b/Content.Server/StoreDiscount/StoreDiscountSystem.cs new file mode 100644 index 00000000000..8f77eba7801 --- /dev/null +++ b/Content.Server/StoreDiscount/StoreDiscountSystem.cs @@ -0,0 +1,55 @@ +using System.Linq; +using Content.Shared.FixedPoint; +using Content.Shared.Store; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.StoreDiscount; + +public sealed class StoreDiscountSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + + public void ApplyDiscounts(IEnumerable listings, StorePresetPrototype store) + { + if (!store.Sales.Enabled) + return; + + var count = _random.Next(store.Sales.MinItems, store.Sales.MaxItems + 1); + + listings = listings + .Where(l => + !l.SaleBlacklist + && l.Cost.Any(x => x.Value > 1) + && store.Categories.Overlaps(ChangedFormatCategories(l.Categories))) + .OrderBy(_ => _random.Next()).Take(count).ToList(); + + foreach (var listing in listings) + { + var sale = GetDiscount(store.Sales.MinMultiplier, store.Sales.MaxMultiplier); + var newCost = listing.Cost.ToDictionary(x => x.Key, + x => FixedPoint2.New(Math.Max(1, (int) MathF.Round(x.Value.Float() * sale)))); + + if (listing.Cost.All(x => x.Value.Int() == newCost[x.Key].Int())) + continue; + + var key = listing.Cost.First(x => x.Value > 0).Key; + listing.OldCost = listing.Cost; + listing.DiscountValue = 100 - (newCost[key] / listing.Cost[key] * 100).Int(); + listing.Cost = newCost; + listing.Categories = new() {store.Sales.SalesCategory}; + } + } + + private IEnumerable ChangedFormatCategories(List> categories) + { + var modified = from p in categories select p.Id; + + return modified; + } + + private float GetDiscount(float minMultiplier, float maxMultiplier) + { + return _random.NextFloat() * (maxMultiplier - minMultiplier) + minMultiplier; + } +} diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/TraversalDistorterComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/TraversalDistorterComponent.cs index ec16083c538..d6a39fe4f40 100644 --- a/Content.Server/Xenoarchaeology/Equipment/Components/TraversalDistorterComponent.cs +++ b/Content.Server/Xenoarchaeology/Equipment/Components/TraversalDistorterComponent.cs @@ -1,4 +1,7 @@ -namespace Content.Server.Xenoarchaeology.Equipment.Components; +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Xenoarchaeology.Equipment.Components; /// /// This is used for a machine that biases @@ -7,6 +10,18 @@ [RegisterComponent] public sealed partial class TraversalDistorterComponent : Component { + [ViewVariables(VVAccess.ReadWrite)] + public float BiasChance; + + [DataField] + public float BaseBiasChance = 0.7f; + + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartBiasChance = "Manipulator"; + + [DataField] + public float PartRatingBiasChance = 1.1f; + [ViewVariables(VVAccess.ReadWrite)] public BiasDirection BiasDirection = BiasDirection.In; diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs index 230e639af49..bb662925a92 100644 --- a/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs +++ b/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs @@ -1,9 +1,12 @@ -using Content.Server.Popups; +using Content.Server.Construction; +using Content.Server.Popups; using Content.Server.Power.EntitySystems; using Content.Server.Xenoarchaeology.Equipment.Components; +using Content.Server.Xenoarchaeology.XenoArtifacts; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Placeable; +using Robust.Shared.Player; using Robust.Shared.Timing; namespace Content.Server.Xenoarchaeology.Equipment.Systems; @@ -20,6 +23,8 @@ public override void Initialize() SubscribeLocalEvent(OnInteract); SubscribeLocalEvent(OnExamine); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnItemPlaced); SubscribeLocalEvent(OnItemRemoved); @@ -68,10 +73,21 @@ private void OnExamine(EntityUid uid, TraversalDistorterComponent component, Exa examine = Loc.GetString("traversal-distorter-desc-out"); break; } - args.PushMarkup(examine); } + private void OnRefreshParts(EntityUid uid, TraversalDistorterComponent component, RefreshPartsEvent args) + { + var biasRating = args.PartRatings[component.MachinePartBiasChance]; + + component.BiasChance = component.BaseBiasChance * MathF.Pow(component.PartRatingBiasChance, biasRating - 1); + } + + private void OnUpgradeExamine(EntityUid uid, TraversalDistorterComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("traversal-distorter-upgrade-bias", component.BiasChance / component.BaseBiasChance); + } + private void OnItemPlaced(EntityUid uid, TraversalDistorterComponent component, ref ItemPlacedEvent args) { var bias = EnsureComp(args.OtherEntity); diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs index 955fe827d72..a7948aa7ff9 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs @@ -206,6 +206,7 @@ public void ForceActivateArtifact(EntityUid uid, EntityUid? user = null, Artifac if (TryComp(uid, out var bias) && TryComp(bias.Provider, out var trav) && + _random.Prob(trav.BiasChance) && this.IsPowered(bias.Provider, EntityManager)) { switch (trav.BiasDirection) diff --git a/Content.Shared/Actions/Events/PsionicHealOtherPowerActionEvent.cs b/Content.Shared/Actions/Events/PsionicHealOtherPowerActionEvent.cs new file mode 100644 index 00000000000..8cf11b9e66d --- /dev/null +++ b/Content.Shared/Actions/Events/PsionicHealOtherPowerActionEvent.cs @@ -0,0 +1,59 @@ +using Robust.Shared.Audio; +using Content.Shared.Damage; +using Content.Shared.Popups; + +namespace Content.Shared.Actions.Events; +public sealed partial class PsionicHealOtherPowerActionEvent : EntityTargetActionEvent +{ + [DataField] + public DamageSpecifier HealingAmount = default!; + + [DataField] + public string PowerName; + + /// Controls whether or not a power fires immediately and with no DoAfter + [DataField] + public bool Immediate; + + [DataField] + public string? PopupText; + + [DataField] + public float RotReduction; + + [DataField] + public bool DoRevive; + + [DataField] + public bool BreakOnUserMove = true; + + [DataField] + public bool BreakOnTargetMove = false; + + [DataField] + public float UseDelay = 8f; + + [DataField] + public int MinGlimmer = 8; + + [DataField] + public int MaxGlimmer = 12; + + [DataField] + public int GlimmerObviousSoundThreshold; + + [DataField] + public int GlimmerObviousPopupThreshold; + + [DataField] + public PopupType PopupType = PopupType.Medium; + + [DataField] + public AudioParams AudioParams = default!; + + [DataField] + public bool PlaySound; + + [DataField] + public SoundSpecifier SoundUse = new SoundPathSpecifier("/Audio/Psionics/heartbeat_fast.ogg"); +} diff --git a/Content.Shared/Cargo/Components/SharedCargoTelepadComponent.cs b/Content.Shared/Cargo/Components/SharedCargoTelepadComponent.cs index 911ea41cca5..7c8a3625227 100644 --- a/Content.Shared/Cargo/Components/SharedCargoTelepadComponent.cs +++ b/Content.Shared/Cargo/Components/SharedCargoTelepadComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Construction.Prototypes; using Content.Shared.DeviceLinking; using Robust.Shared.Audio; using Robust.Shared.GameStates; @@ -13,29 +14,47 @@ namespace Content.Shared.Cargo.Components; public sealed partial class CargoTelepadComponent : Component { /// - /// The actual amount of time it takes to teleport from the telepad + /// The base amount of time it takes to teleport from the telepad /// - [DataField("delay"), ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float BaseDelay = 10f; + + /// + /// The actual amount of time it takes to teleport from the telepad + /// + [DataField] public float Delay = 10f; /// - /// How much time we've accumulated until next teleport. + /// The machine part that affects + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] + public string MachinePartTeleportDelay = "Capacitor"; + + /// + /// A multiplier applied to for each level of + /// + [DataField] + public float PartRatingTeleportDelay = 0.8f; + + /// + /// How much time we've accumulated until next teleport. /// - [DataField("accumulator"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float Accumulator; - [DataField("currentState")] + [DataField] public CargoTelepadState CurrentState = CargoTelepadState.Unpowered; - [DataField("teleportSound")] + [DataField] public SoundSpecifier TeleportSound = new SoundPathSpecifier("/Audio/Machines/phasein.ogg"); /// /// The paper-type prototype to spawn with the order information. /// - [DataField("printerOutput", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] public string PrinterOutput = "PaperCargoInvoice"; - [DataField("receiverPort", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] public string ReceiverPort = "OrderReceiver"; } diff --git a/Content.Shared/Cloning/CloningPodComponent.cs b/Content.Shared/Cloning/CloningPodComponent.cs index 082b92e8b14..c9a6fd4500b 100644 --- a/Content.Shared/Cloning/CloningPodComponent.cs +++ b/Content.Shared/Cloning/CloningPodComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Construction.Prototypes; using Content.Shared.DeviceLinking; using Content.Shared.Materials; using Content.Shared.Random; @@ -38,6 +39,18 @@ public sealed partial class CloningPodComponent : Component [DataField] public ProtoId RequiredMaterial = "Biomass"; + /// + /// The multiplier for cloning duration + /// + [DataField] + public float PartRatingSpeedMultiplier = 0.75f; + + /// + /// The machine part that affects cloning speed + /// + [DataField] + public ProtoId MachinePartCloningSpeed = "Manipulator"; + /// /// The current amount of time it takes to clone a body /// @@ -66,6 +79,18 @@ public sealed partial class CloningPodComponent : Component Params = AudioParams.Default.WithVolume(4), }; + /// + /// The machine part that affects how much biomass is needed to clone a body. + /// + [DataField] + public float PartRatingMaterialMultiplier = 0.85f; + + /// + /// The machine part that decreases the amount of material needed for cloning + /// + [DataField] + public ProtoId MachinePartMaterialUse = "MatterBin"; + [ViewVariables(VVAccess.ReadWrite)] public CloningPodStatus Status; diff --git a/Content.Shared/Construction/MachinePartSystem.cs b/Content.Shared/Construction/MachinePartSystem.cs index 1a19040b410..01db7fbade3 100644 --- a/Content.Shared/Construction/MachinePartSystem.cs +++ b/Content.Shared/Construction/MachinePartSystem.cs @@ -21,6 +21,7 @@ public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnMachineBoardExamined); + SubscribeLocalEvent(OnMachinePartExamined); } private void OnMachineBoardExamined(EntityUid uid, MachineBoardComponent component, ExaminedEvent args) @@ -61,6 +62,20 @@ private void OnMachineBoardExamined(EntityUid uid, MachineBoardComponent compone } } + private void OnMachinePartExamined(EntityUid uid, MachinePartComponent component, ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + using (args.PushGroup(nameof(MachinePartComponent))) + { + args.PushMarkup(Loc.GetString("machine-part-component-on-examine-rating-text", + ("rating", component.Rating))); + args.PushMarkup(Loc.GetString("machine-part-component-on-examine-type-text", ("type", + Loc.GetString(_prototype.Index(component.PartType).Name)))); + } + } + public Dictionary GetMachineBoardMaterialCost(Entity entity, int coefficient = 1) { var (_, comp) = entity; diff --git a/Content.Shared/Materials/MaterialReclaimerComponent.cs b/Content.Shared/Materials/MaterialReclaimerComponent.cs index 3e72baf6041..2fd6cd6fc82 100644 --- a/Content.Shared/Materials/MaterialReclaimerComponent.cs +++ b/Content.Shared/Materials/MaterialReclaimerComponent.cs @@ -1,100 +1,121 @@ -using Content.Shared.Whitelist; +using Content.Shared.Construction.Prototypes; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.Materials; /// -/// This is a machine that handles converting entities -/// into the raw materials and chemicals that make them up. +/// This is a machine that handles converting entities +/// into the raw materials and chemicals that make them up. /// [RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] [Access(typeof(SharedMaterialReclaimerSystem))] public sealed partial class MaterialReclaimerComponent : Component { /// - /// Whether or not the machine has power. We put it here - /// so we can network and predict it. + /// Whether or not the machine has power. We put it here + /// so we can network and predict it. /// - [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + [DataField, AutoNetworkedField] public bool Powered; /// - /// An "enable" toggle for things like interfacing with machine linking + /// An "enable" toggle for things like interfacing with machine linking /// - [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + [DataField, AutoNetworkedField] public bool Enabled = true; /// - /// How efficiently the materials are reclaimed. - /// In practice, a multiplier per material when calculating the output of the reclaimer. + /// How efficiently the materials are reclaimed. + /// In practice, a multiplier per material when calculating the output of the reclaimer. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float Efficiency = 1f; /// - /// Whether or not the process - /// speed scales with the amount of materials being processed - /// or if it's just + /// Whether or not the process + /// speed scales with the amount of materials being processed + /// or if it's just /// [DataField] public bool ScaleProcessSpeed = true; /// - /// How quickly it takes to consume X amount of materials per second. - /// For example, with a rate of 50, an entity with 100 total material takes 2 seconds to process. + /// How quickly it takes to consume X amount of materials per second. + /// For example, with a rate of 50, an entity with 100 total material takes 2 seconds to process. /// - [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float BaseMaterialProcessRate = 100f; + + /// + /// How quickly it takes to consume X amount of materials per second. + /// For example, with a rate of 50, an entity with 100 total material takes 2 seconds to process. + /// + [DataField, AutoNetworkedField] public float MaterialProcessRate = 100f; /// - /// The minimum amount fo time it can take to process an entity. - /// this value supercedes the calculated one using + /// Machine part whose rating modifies + /// + [DataField] + public ProtoId MachinePartProcessRate = "Manipulator"; + + /// + /// How much the machine part quality affects the /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float PartRatingProcessRateMultiplier = 1.5f; + + /// + /// The minimum amount fo time it can take to process an entity. + /// this value supercedes the calculated one using + /// + [DataField] public TimeSpan MinimumProcessDuration = TimeSpan.FromSeconds(0.5f); /// - /// The id of our output solution + /// The id of our output solution /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public string SolutionContainerId = "output"; /// - /// a whitelist for what entities can be inserted into this reclaimer + /// a whitelist for what entities can be inserted into this reclaimer /// [DataField] public EntityWhitelist? Whitelist; /// - /// a blacklist for what entities cannot be inserted into this reclaimer + /// a blacklist for what entities cannot be inserted into this reclaimer /// [DataField] public EntityWhitelist? Blacklist; /// - /// The sound played when something is being processed. + /// The sound played when something is being processed. /// [DataField] public SoundSpecifier? Sound; /// - /// whether or not we cut off the sound early when the reclaiming ends. + /// whether or not we cut off the sound early when the reclaiming ends. /// [DataField] public bool CutOffSound = true; /// - /// When the next sound will be allowed to be played. Used to prevent spam. + /// When the next sound will be allowed to be played. Used to prevent spam. /// [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] [AutoPausedField] public TimeSpan NextSound; /// - /// Minimum time inbetween each + /// Minimum time inbetween each /// [DataField] public TimeSpan SoundCooldown = TimeSpan.FromSeconds(0.8f); @@ -102,10 +123,10 @@ public sealed partial class MaterialReclaimerComponent : Component public EntityUid? Stream; /// - /// A counter of how many items have been processed + /// A counter of how many items have been processed /// /// - /// I saw this on the recycler and i'm porting it because it's cute af + /// I saw this on the recycler and i'm porting it because it's cute af /// [DataField, AutoNetworkedField] public int ItemsProcessed; diff --git a/Content.Shared/Psionics/Events.cs b/Content.Shared/Psionics/Events.cs index cf9a50c6e18..f110c9c405f 100644 --- a/Content.Shared/Psionics/Events.cs +++ b/Content.Shared/Psionics/Events.cs @@ -1,28 +1,59 @@ using Robust.Shared.Serialization; +using Content.Shared.Damage; using Content.Shared.DoAfter; -namespace Content.Shared.Psionics.Events +namespace Content.Shared.Psionics.Events; + +[Serializable, NetSerializable] +public sealed partial class PsionicRegenerationDoAfterEvent : DoAfterEvent { - [Serializable, NetSerializable] - public sealed partial class PsionicRegenerationDoAfterEvent : DoAfterEvent + [DataField("startedAt", required: true)] + public TimeSpan StartedAt; + + public PsionicRegenerationDoAfterEvent(TimeSpan startedAt) { - [DataField("startedAt", required: true)] - public TimeSpan StartedAt; + StartedAt = startedAt; + } + + public override DoAfterEvent Clone() => this; +} - private PsionicRegenerationDoAfterEvent() - { - } +[Serializable, NetSerializable] +public sealed partial class GlimmerWispDrainDoAfterEvent : SimpleDoAfterEvent { } - public PsionicRegenerationDoAfterEvent(TimeSpan startedAt) - { - StartedAt = startedAt; - } +[Serializable, NetSerializable] +public sealed partial class HealingWordDoAfterEvent : DoAfterEvent +{ + [DataField(required: true)] + public TimeSpan StartedAt; - public override DoAfterEvent Clone() => this; + public HealingWordDoAfterEvent(TimeSpan startedAt) + { + StartedAt = startedAt; } - [Serializable, NetSerializable] - public sealed partial class GlimmerWispDrainDoAfterEvent : SimpleDoAfterEvent + public override DoAfterEvent Clone() => this; +} + +[Serializable, NetSerializable] +public sealed partial class PsionicHealOtherDoAfterEvent : DoAfterEvent +{ + [DataField(required: true)] + public TimeSpan StartedAt; + + [DataField] + public DamageSpecifier HealingAmount = default!; + + [DataField] + public float RotReduction; + + [DataField] + public bool DoRevive; + + public PsionicHealOtherDoAfterEvent(TimeSpan startedAt) { + StartedAt = startedAt; } + + public override DoAfterEvent Clone() => this; } diff --git a/Content.Shared/Psionics/PsionicComponent.cs b/Content.Shared/Psionics/PsionicComponent.cs index 85b7e380fea..37d0a9a7ef4 100644 --- a/Content.Shared/Psionics/PsionicComponent.cs +++ b/Content.Shared/Psionics/PsionicComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.DoAfter; using Content.Shared.Psionics; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -166,6 +167,14 @@ private set /// unneccesary subs for unique psionic entities like e.g. Oracle. /// [DataField] - public List? PsychognomicDescriptors = null; + public List? PsychognomicDescriptors = null; + + /// Used for tracking what spell a Psion is actively casting + [DataField] + public DoAfterId? DoAfter; + + /// Popup to play if a Psion attempts to start casting a power while already casting one + [DataField] + public string AlreadyCasting = "already-casting"; } } diff --git a/Content.Shared/Singularity/Components/SharedEmitterComponent.cs b/Content.Shared/Singularity/Components/SharedEmitterComponent.cs index c2e7af717b1..cc6e8aeede2 100644 --- a/Content.Shared/Singularity/Components/SharedEmitterComponent.cs +++ b/Content.Shared/Singularity/Components/SharedEmitterComponent.cs @@ -1,4 +1,5 @@ using System.Threading; +using Content.Shared.Construction.Prototypes; using Content.Shared.DeviceLinking; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -14,90 +15,130 @@ public sealed partial class EmitterComponent : Component { public CancellationTokenSource? TimerCancel; - // whether the power switch is in "on" - [ViewVariables] public bool IsOn; - // Whether the power switch is on AND the machine has enough power (so is actively firing) - [ViewVariables] public bool IsPowered; + /// + /// Whether the power switch is on + /// + [ViewVariables] + public bool IsOn; /// - /// counts the number of consecutive shots fired. + /// Whether the power switch is on AND the machine has enough power (so is actively firing) + /// + [ViewVariables] + public bool IsPowered; + + /// + /// counts the number of consecutive shots fired. /// [ViewVariables] public int FireShotCounter; /// - /// The entity that is spawned when the emitter fires. + /// The entity that is spawned when the emitter fires. /// - [DataField("boltType", customTypeSerializer: typeof(PrototypeIdSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string BoltType = "EmitterBolt"; - [DataField("selectableTypes", customTypeSerializer: typeof(PrototypeIdListSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdListSerializer))] public List SelectableTypes = new(); /// - /// The current amount of power being used. + /// The current amount of power being used. /// - [DataField("powerUseActive")] + [DataField] public int PowerUseActive = 600; /// - /// The amount of shots that are fired in a single "burst" + /// The amount of shots that are fired in a single "burst" /// - [DataField("fireBurstSize")] + [DataField] public int FireBurstSize = 3; /// - /// The time between each shot during a burst. + /// The time between each shot during a burst. /// - [DataField("fireInterval")] + [DataField] public TimeSpan FireInterval = TimeSpan.FromSeconds(2); /// - /// The current minimum delay between bursts. + /// The base amount of time between each shot during a burst. /// - [DataField("fireBurstDelayMin")] + [DataField] + public TimeSpan BaseFireInterval = TimeSpan.FromSeconds(2); + + /// + /// The current minimum delay between bursts. + /// + [DataField] public TimeSpan FireBurstDelayMin = TimeSpan.FromSeconds(4); /// - /// The current maximum delay between bursts. + /// The current maximum delay between bursts. /// - [DataField("fireBurstDelayMax")] + [DataField] public TimeSpan FireBurstDelayMax = TimeSpan.FromSeconds(10); /// - /// The visual state that is set when the emitter is turned on + /// The base minimum delay between shot bursts. + /// Used for machine part rating calculations. + /// + [DataField] + public TimeSpan BaseFireBurstDelayMin = TimeSpan.FromSeconds(4); + + /// + /// The base maximum delay between shot bursts. + /// Used for machine part rating calculations. + /// + [DataField] + public TimeSpan BaseFireBurstDelayMax = TimeSpan.FromSeconds(10); + + /// + /// The multiplier for the base delay between shot bursts as well as + /// the fire interval + /// + [DataField] + public float FireRateMultiplier = 0.8f; + + /// + /// The machine part that affects burst delay. + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartFireRate = "Capacitor"; + + /// + /// The visual state that is set when the emitter is turned on /// - [DataField("onState")] + [DataField] public string? OnState = "beam"; /// - /// The visual state that is set when the emitter doesn't have enough power. + /// The visual state that is set when the emitter doesn't have enough power. /// - [DataField("underpoweredState")] + [DataField] public string? UnderpoweredState = "underpowered"; /// - /// Signal port that turns on the emitter. + /// Signal port that turns on the emitter. /// - [DataField("onPort", customTypeSerializer: typeof(PrototypeIdSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string OnPort = "On"; /// - /// Signal port that turns off the emitter. + /// Signal port that turns off the emitter. /// - [DataField("offPort", customTypeSerializer: typeof(PrototypeIdSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string OffPort = "Off"; /// - /// Signal port that toggles the emitter on or off. + /// Signal port that toggles the emitter on or off. /// - [DataField("togglePort", customTypeSerializer: typeof(PrototypeIdSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string TogglePort = "Toggle"; /// - /// Map of signal ports to entity prototype IDs of the entity that will be fired. + /// Map of signal ports to entity prototype IDs of the entity that will be fired. /// - [DataField("setTypePorts", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] public Dictionary SetTypePorts = new(); } diff --git a/Content.Shared/Store/ListingLocalisationHelpers.cs b/Content.Shared/Store/ListingLocalisationHelpers.cs index 882300109ce..19cd029488f 100644 --- a/Content.Shared/Store/ListingLocalisationHelpers.cs +++ b/Content.Shared/Store/ListingLocalisationHelpers.cs @@ -18,6 +18,11 @@ public static string GetLocalisedNameOrEntityName(ListingData listingData, IProt else if (listingData.ProductEntity != null) name = prototypeManager.Index(listingData.ProductEntity.Value).Name; + if (listingData.DiscountValue > 0) + name += " " + Loc.GetString("store-sales-amount", ("amount", listingData.DiscountValue)); + else if (listingData.OldCost.Count > 0) + name += " " + Loc.GetString("store-sales-over"); + return name; } diff --git a/Content.Shared/Store/ListingPrototype.cs b/Content.Shared/Store/ListingPrototype.cs index d3d2e13cdfd..0b59ab48cb6 100644 --- a/Content.Shared/Store/ListingPrototype.cs +++ b/Content.Shared/Store/ListingPrototype.cs @@ -109,6 +109,19 @@ public partial class ListingData : IEquatable, ICloneable [DataField] public TimeSpan RestockTime = TimeSpan.Zero; + [DataField] + public int SaleLimit = 3; + + [DataField] + public bool SaleBlacklist; + + public int DiscountValue; + + public Dictionary, FixedPoint2> OldCost = new(); + + [DataField] + public List Components = new(); + public bool Equals(ListingData? listing) { if (listing == null) @@ -166,6 +179,11 @@ public object Clone() ProductEvent = ProductEvent, PurchaseAmount = PurchaseAmount, RestockTime = RestockTime, + SaleLimit = SaleLimit, + SaleBlacklist = SaleBlacklist, + DiscountValue = DiscountValue, + OldCost = OldCost, + Components = Components, }; } } diff --git a/Content.Shared/Store/StorePresetPrototype.cs b/Content.Shared/Store/StorePresetPrototype.cs index ce7f0312b60..41ee510bd8e 100644 --- a/Content.Shared/Store/StorePresetPrototype.cs +++ b/Content.Shared/Store/StorePresetPrototype.cs @@ -1,3 +1,4 @@ +using Content.Shared.StoreDiscount; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; @@ -38,4 +39,7 @@ public sealed partial class StorePresetPrototype : IPrototype /// [DataField("currencyWhitelist", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] public HashSet CurrencyWhitelist { get; private set; } = new(); + + [DataField] + public SalesSpecifier Sales { get; private set; } = new(); } diff --git a/Content.Shared/StoreDiscount/SalesSpecifier.cs b/Content.Shared/StoreDiscount/SalesSpecifier.cs new file mode 100644 index 00000000000..21539f85d41 --- /dev/null +++ b/Content.Shared/StoreDiscount/SalesSpecifier.cs @@ -0,0 +1,38 @@ +namespace Content.Shared.StoreDiscount; + +[DataDefinition] +public sealed partial class SalesSpecifier +{ + [DataField] + public bool Enabled { get; private set; } + + [DataField] + public float MinMultiplier { get; private set; } + + [DataField] + public float MaxMultiplier { get; private set; } + + [DataField] + public int MinItems { get; private set; } + + [DataField] + public int MaxItems { get; private set; } + + [DataField] + public string SalesCategory { get; private set; } = string.Empty; + + public SalesSpecifier() + { + } + + public SalesSpecifier(bool enabled, float minMultiplier, float maxMultiplier, int minItems, int maxItems, + string salesCategory) + { + Enabled = enabled; + MinMultiplier = minMultiplier; + MaxMultiplier = maxMultiplier; + MinItems = minItems; + MaxItems = maxItems; + SalesCategory = salesCategory; + } +} diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index f4bc13c1173..f85816dfafa 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -6576,3 +6576,54 @@ Entries: id: 6381 time: '2024-09-20T21:47:16.0000000+00:00' url: https://github.com/Simple-Station/Einstein-Engines/pull/940 +- author: VMSolidus + changes: + - type: Add + message: Part Upgrading has returned! + id: 6382 + time: '2024-09-21T22:46:49.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/917 +- author: Spatison + changes: + - type: Add + message: Added discounts in uplink / Добавлены скидки в аплинк + id: 6383 + time: '2024-09-21T22:46:59.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/930 +- author: VMSolidus + changes: + - type: Add + message: >- + Healing Word has been added as a new Psionic Power. When cast on another + person, it heals a small amount of every damage type(scaling with + Casting Stats), while also reducing rot timers. Healing Word has a very + short cooldown, and a fairly low Glimmer cost. + - type: Add + message: >- + Breath of Life has been added as a new extremely rare Psionic Power. + When cast on another person, it heals a large amount of damage(scaling + with Casting Stats), while also substantially reducing rot timers. + Additionally, it will revive the target if it is possible to do so. + Breath of Life has an incredibly long cooldown, a long interuptable cast + time, and an extraordinarily high glimmer cost(A typical Psion will + spike glimmer by more than 50 points when casting it). + - type: Add + message: The Chaplain now starts with the Healing Word power. + id: 6384 + time: '2024-09-21T22:50:01.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/942 +- author: VMSolidus + changes: + - type: Add + message: >- + Bottles, Drink Glasses, Plates, and all liquid containers are now struck + by bullets(and most likely destroyed in the process). We hope that this + will offer both a small tactical advantage/disadvantage, as well as + contribute to making gunfights around the bar more "Cinematic". + - type: Add + message: >- + Chairs are now hit by projectiles if a shooter clicks on them, in + addition to Tables. + id: 6385 + time: '2024-09-21T23:22:43.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/943 diff --git a/Resources/Locale/en-US/anomaly/anomaly.ftl b/Resources/Locale/en-US/anomaly/anomaly.ftl index da5882fa62f..1609d77d914 100644 --- a/Resources/Locale/en-US/anomaly/anomaly.ftl +++ b/Resources/Locale/en-US/anomaly/anomaly.ftl @@ -3,6 +3,7 @@ anomaly-component-contact-damage = The anomaly sears off your skin! anomaly-vessel-component-anomaly-assigned = Anomaly assigned to vessel. anomaly-vessel-component-not-assigned = This vessel is not assigned to any anomaly. Try using a scanner on it. anomaly-vessel-component-assigned = This vessel is currently assigned to an anomaly. +anomaly-vessel-component-upgrade-output = point output anomaly-particles-delta = Delta particles anomaly-particles-epsilon = Epsilon particles diff --git a/Resources/Locale/en-US/atmos/gas-recycler-system.ftl b/Resources/Locale/en-US/atmos/gas-recycler-system.ftl index cc527adf5c8..a72e137732c 100644 --- a/Resources/Locale/en-US/atmos/gas-recycler-system.ftl +++ b/Resources/Locale/en-US/atmos/gas-recycler-system.ftl @@ -1,3 +1,6 @@ gas-recycler-reacting = It is [color=green]converting[/color] waste gases. gas-recycler-low-pressure = The input pressure is [color=darkred]too low[/color]. gas-recycler-low-temperature = The input temperature is [color=darkred]too low[/color]. + +gas-recycler-upgrade-min-temp = Minimum temperature +gas-recycler-upgrade-min-pressure = Minimum pressure diff --git a/Resources/Locale/en-US/atmos/portable-scrubber.ftl b/Resources/Locale/en-US/atmos/portable-scrubber.ftl index c4071b4acce..8aadf076d96 100644 --- a/Resources/Locale/en-US/atmos/portable-scrubber.ftl +++ b/Resources/Locale/en-US/atmos/portable-scrubber.ftl @@ -1 +1,4 @@ portable-scrubber-fill-level = It's at about [color=yellow]{$percent}%[/color] of its maximum internal pressure. + +portable-scrubber-component-upgrade-max-pressure = max pressure +portable-scrubber-component-upgrade-transfer-rate = transfer rate diff --git a/Resources/Locale/en-US/botany/components/seed-extractor-component.ftl b/Resources/Locale/en-US/botany/components/seed-extractor-component.ftl index 84d5a5ed283..c586a594a9a 100644 --- a/Resources/Locale/en-US/botany/components/seed-extractor-component.ftl +++ b/Resources/Locale/en-US/botany/components/seed-extractor-component.ftl @@ -2,3 +2,5 @@ seed-extractor-component-interact-message = You extract some seeds from the { THE($name) }. seed-extractor-component-no-seeds = { CAPITALIZE(THE($name)) } has no seeds! + +seed-extractor-component-upgrade-seed-yield = seed yield diff --git a/Resources/Locale/en-US/cargo/cargo-console-component.ftl b/Resources/Locale/en-US/cargo/cargo-console-component.ftl index b56f4730ccd..1caa810f1b7 100644 --- a/Resources/Locale/en-US/cargo/cargo-console-component.ftl +++ b/Resources/Locale/en-US/cargo/cargo-console-component.ftl @@ -45,3 +45,5 @@ cargo-shuttle-console-station-unknown = Unknown cargo-shuttle-console-shuttle-not-found = Not found cargo-shuttle-console-organics = Detected organic lifeforms on the shuttle cargo-no-shuttle = No cargo shuttle found! + +cargo-telepad-delay-upgrade = Teleport delay diff --git a/Resources/Locale/en-US/chemistry/components/solution-heater-component.ftl b/Resources/Locale/en-US/chemistry/components/solution-heater-component.ftl new file mode 100644 index 00000000000..cecf15550c7 --- /dev/null +++ b/Resources/Locale/en-US/chemistry/components/solution-heater-component.ftl @@ -0,0 +1 @@ +solution-heater-upgrade-heat = Heat strength diff --git a/Resources/Locale/en-US/construction/components/machine-part-component.ftl b/Resources/Locale/en-US/construction/components/machine-part-component.ftl new file mode 100644 index 00000000000..0613f505161 --- /dev/null +++ b/Resources/Locale/en-US/construction/components/machine-part-component.ftl @@ -0,0 +1,2 @@ +machine-part-component-on-examine-rating-text = [color=white]Rating:[/color] [color=cyan]{$rating}[/color] +machine-part-component-on-examine-type-text = [color=white]Type:[/color] [color=cyan]{$type}[/color] diff --git a/Resources/Locale/en-US/kitchen/components/microwave-component.ftl b/Resources/Locale/en-US/kitchen/components/microwave-component.ftl index 0603b3c8463..12346ee75dc 100644 --- a/Resources/Locale/en-US/kitchen/components/microwave-component.ftl +++ b/Resources/Locale/en-US/kitchen/components/microwave-component.ftl @@ -9,6 +9,7 @@ microwave-component-suicide-multi-head-others-message = {$victim} is trying to c microwave-component-suicide-others-message = {$victim} is trying to cook their head! microwave-component-suicide-multi-head-message = You cook your heads! microwave-component-suicide-message = You cook your head! +microwave-component-upgrade-cook-time = cook time microwave-component-interact-full = It's full. microwave-component-interact-item-too-big = { CAPITALIZE(THE($item)) } is too big to fit in the microwave! diff --git a/Resources/Locale/en-US/kitchen/components/reagent-grinder-component.ftl b/Resources/Locale/en-US/kitchen/components/reagent-grinder-component.ftl index 8a3ca9eef85..34951282745 100644 --- a/Resources/Locale/en-US/kitchen/components/reagent-grinder-component.ftl +++ b/Resources/Locale/en-US/kitchen/components/reagent-grinder-component.ftl @@ -4,6 +4,9 @@ reagent-grinder-bound-user-interface-instant-button = INSTANT reagent-grinder-bound-user-interface-cook-time-label = COOK TIME reagent-grinder-component-cannot-put-entity-message = You can't put this in the reagent grinder! +reagent-grinder-component-upgrade-work-time = Work time +reagent-grinder-component-upgrade-storage = Storage + grinder-menu-title = All-In-One Grinder 3000 grinder-menu-grind-button = Grind grinder-menu-juice-button = Juice diff --git a/Resources/Locale/en-US/machine/machine.ftl b/Resources/Locale/en-US/machine/machine.ftl index ce8873df6f8..26059505160 100644 --- a/Resources/Locale/en-US/machine/machine.ftl +++ b/Resources/Locale/en-US/machine/machine.ftl @@ -13,6 +13,11 @@ machine-part-name-manipulator = Manipulator machine-part-name-matter-bin = Matter Bin machine-part-name-power-cell = Power Cell +upgrade-power-draw = power draw +upgrade-max-charge = max charge +upgrade-power-supply = power supply +upgrade-power-supply-ramping = power ramp rate + two-way-lever-left = push left two-way-lever-right = push right two-way-lever-cant = can't push the lever that way! diff --git a/Resources/Locale/en-US/materials/materials.ftl b/Resources/Locale/en-US/materials/materials.ftl index dca520b5b49..5a4413348e8 100644 --- a/Resources/Locale/en-US/materials/materials.ftl +++ b/Resources/Locale/en-US/materials/materials.ftl @@ -35,3 +35,6 @@ materials-raw-plasma = raw plasma materials-raw-uranium = raw uranium materials-raw-bananium = raw bananium materials-raw-salt = raw salt + +# Material Reclaimer +material-reclaimer-upgrade-process-rate = process rate diff --git a/Resources/Locale/en-US/medical/components/biomass-reclaimer-component.ftl b/Resources/Locale/en-US/medical/components/biomass-reclaimer-component.ftl index 443429c1ef3..0c0b8faf59e 100644 --- a/Resources/Locale/en-US/medical/components/biomass-reclaimer-component.ftl +++ b/Resources/Locale/en-US/medical/components/biomass-reclaimer-component.ftl @@ -1 +1,4 @@ biomass-reclaimer-suicide-others = {CAPITALIZE(THE($victim))} threw themselves into the biomass reclaimer! + +biomass-reclaimer-component-upgrade-speed = speed +biomass-reclaimer-component-upgrade-biomass-yield = biomass yield diff --git a/Resources/Locale/en-US/medical/components/cloning-pod-component.ftl b/Resources/Locale/en-US/medical/components/cloning-pod-component.ftl index b222d707a0a..e92ac86a1e4 100644 --- a/Resources/Locale/en-US/medical/components/cloning-pod-component.ftl +++ b/Resources/Locale/en-US/medical/components/cloning-pod-component.ftl @@ -1,3 +1,5 @@ cloning-pod-biomass = It currently has [color=red]{$number}[/color] units of biomass. +cloning-pod-component-upgrade-speed = cloning speed +cloning-pod-component-upgrade-biomass-requirement = biomass requirement cloning-pod-component-upgrade-emag-requirement = The card zaps something inside the cloning pod. diff --git a/Resources/Locale/en-US/medical/components/medical-scanner-component.ftl b/Resources/Locale/en-US/medical/components/medical-scanner-component.ftl index c4b19426545..da4dc7a3847 100644 --- a/Resources/Locale/en-US/medical/components/medical-scanner-component.ftl +++ b/Resources/Locale/en-US/medical/components/medical-scanner-component.ftl @@ -2,3 +2,5 @@ medical-scanner-verb-enter = Enter medical-scanner-verb-noun-occupant = occupant + +medical-scanner-upgrade-cloning = Cloning fail chance diff --git a/Resources/Locale/en-US/medical/components/stasis-bed-component.ftl b/Resources/Locale/en-US/medical/components/stasis-bed-component.ftl new file mode 100644 index 00000000000..2d8a18c263e --- /dev/null +++ b/Resources/Locale/en-US/medical/components/stasis-bed-component.ftl @@ -0,0 +1 @@ +stasis-bed-component-upgrade-stasis = stasis effect diff --git a/Resources/Locale/en-US/nutrition/components/fat-extractor.ftl b/Resources/Locale/en-US/nutrition/components/fat-extractor.ftl index 20a31cd8c40..611b8ef5406 100644 --- a/Resources/Locale/en-US/nutrition/components/fat-extractor.ftl +++ b/Resources/Locale/en-US/nutrition/components/fat-extractor.ftl @@ -1,3 +1,5 @@ +fat-extractor-component-rate = extraction rate + fat-extractor-fact-1 = Fats are triglycerides made up of a combination of different building blocks; glycerol and fatty acids. fat-extractor-fact-2 = Adults should get a recommended 20-35% of their energy intake from fat. fat-extractor-fact-3 = Being overweight or obese puts you at an increased risk of chronic diseases, such as cardiovascular diseases, metabolic syndrome, type 2 diabetes, and some types of cancers. diff --git a/Resources/Locale/en-US/psionics/psionic-powers.ftl b/Resources/Locale/en-US/psionics/psionic-powers.ftl index c68bb2a4968..01b50cce326 100644 --- a/Resources/Locale/en-US/psionics/psionic-powers.ftl +++ b/Resources/Locale/en-US/psionics/psionic-powers.ftl @@ -1,4 +1,5 @@ generic-power-initialization-feedback = I Awaken. +arleady-casting = I cannot channel more than one power at a time. # Dispel dispel-power-description = Dispel summoned entities such as familiars or forcewalls. @@ -48,6 +49,28 @@ psionic-regeneration-power-initialization-feedback = I look within myself, finding a wellspring of life. psionic-regeneration-power-metapsionic-feedback = {CAPITALIZE($entity)} possesses an overwhelming will to live +# Healing Word +action-name-healing-word = Healing Word +action-description-healing-word = Speak the Lesser Secret Of Life, and restore health to another. +healing-word-power-description = Speak the Lesser Secret Of Life, and restore health to another. +healing-word-power-initialization-feedback = + At the beginning of time, a word was spoken that brought life into the Spheres. + Though it taxes my mind to know it, this Secret is known to me now. + I need only speak it. +healing-word-power-metapsionic-feedback = {CAPITALIZE($entity)} bears the Lesser Secret of Life. +healing-word-begin = {CAPITALIZE($entity)} mutters a word that brings both joy and pain alike to those who hear it. + +# Revivify +action-name-revivify = Breath of Life +action-description-revivify = Speak the Greater Secret of Life, and restore another to life. +revivify-power-description = Speak the Greater Secret of Life, and restore another to life. +revivify-power-initialization-feedback = + For a moment, my soul journeys across time and space to the beginning of it all, there I hear it. + The Secret of Life in its fullness. I feel my entire existence burning out from within, merely by knowing it. + Power flows through me as a mighty river, begging to be released with a simple spoken word. +revivify-power-metapsionic-feedback = {CAPITALIZE($entity)} bears the Greater Secret of Life. +revivify-word-begin = {CAPITALIZE($entity)} enunciates a word of such divine power, that those who hear it weep from joy. + # Telegnosis telegnosis-power-description = Create a telegnostic projection to remotely observe things. telegnosis-power-initialization-feedback = diff --git a/Resources/Locale/en-US/shuttles/thruster.ftl b/Resources/Locale/en-US/shuttles/thruster.ftl index 94035811c73..faed6e8dd28 100644 --- a/Resources/Locale/en-US/shuttles/thruster.ftl +++ b/Resources/Locale/en-US/shuttles/thruster.ftl @@ -3,3 +3,5 @@ thruster-comp-disabled = The thruster is turned [color=red]off[/color]. thruster-comp-nozzle-direction = The nozzle is facing [color=yellow]{$direction}[/color]. thruster-comp-nozzle-exposed = The nozzle [color=green]exposed[/color] to space. thruster-comp-nozzle-not-exposed = The nozzle [color=red]is not exposed[/color] to space. + +thruster-comp-upgrade-thrust = Thrust strength diff --git a/Resources/Locale/en-US/singularity/components/emitter-component.ftl b/Resources/Locale/en-US/singularity/components/emitter-component.ftl index c71b3d6bdfd..c7db1a93bba 100644 --- a/Resources/Locale/en-US/singularity/components/emitter-component.ftl +++ b/Resources/Locale/en-US/singularity/components/emitter-component.ftl @@ -11,5 +11,8 @@ comp-emitter-turned-off = The {$target} turns off. # Shows if the user attempts to activate the emitter while it's un-anchored. comp-emitter-not-anchored = The {$target} isn't anchored to the ground! +# Upgrades +emitter-component-upgrade-fire-rate = fire rate + emitter-component-current-type = The current selected type is: {$type}. emitter-component-type-set = Type set to: {$type} diff --git a/Resources/Locale/en-US/store/sales.ftl b/Resources/Locale/en-US/store/sales.ftl new file mode 100644 index 00000000000..7223a8a0dcd --- /dev/null +++ b/Resources/Locale/en-US/store/sales.ftl @@ -0,0 +1,2 @@ +store-sales-amount = [DISCOUNT] { $amount }%! +store-sales-over = [The sale is over] diff --git a/Resources/Locale/en-US/xenoarchaeology/traversal-distorter.ftl b/Resources/Locale/en-US/xenoarchaeology/traversal-distorter.ftl index 5c9eac57a5d..af3039e864e 100644 --- a/Resources/Locale/en-US/xenoarchaeology/traversal-distorter.ftl +++ b/Resources/Locale/en-US/xenoarchaeology/traversal-distorter.ftl @@ -3,3 +3,5 @@ traversal-distorter-set-out = Traversal bias set to "out" traversal-distorter-desc-in = The affected artifact's traversal now favors moving inwards to the beginning. traversal-distorter-desc-out = The affected artifact's traversal now favors moving outwards towards more dangerous nodes. + +traversal-distorter-upgrade-bias = Bias effectiveness diff --git a/Resources/Locale/ru-RU/store/sales.ftl b/Resources/Locale/ru-RU/store/sales.ftl new file mode 100644 index 00000000000..93f1798fe79 --- /dev/null +++ b/Resources/Locale/ru-RU/store/sales.ftl @@ -0,0 +1,2 @@ +store-sales-amount = [СКИДКА] { $amount }%! +store-sales-over = [Скидка закончилась] diff --git a/Resources/Migrations/migration.yml b/Resources/Migrations/migration.yml index 7f19699ad8a..3b4a7178cf8 100644 --- a/Resources/Migrations/migration.yml +++ b/Resources/Migrations/migration.yml @@ -126,28 +126,6 @@ DrinkGoldschlagerGlass: DrinkGildlagerGlass ClosetBase: ClosetSteelBase MonkeyCubeBox: VariantCubeBox -# 2024-01-08 -SalvagePartsT4Spawner: SalvageLootSpawner -SalvagePartsT3Spawner: SalvageLootSpawner -SalvagePartsT3T4Spawner: SalvageLootSpawner -SalvagePartsT2Spawner: SalvageLootSpawner -AdvancedCapacitorStockPart: CapacitorStockPart -SuperCapacitorStockPart: CapacitorStockPart -QuadraticCapacitorStockPart: CapacitorStockPart -NanoManipulatorStockPart: MicroManipulatorStockPart -PicoManipulatorStockPart: MicroManipulatorStockPart -FemtoManipulatorStockPart: MicroManipulatorStockPart -AdvancedMatterBinStockPart: MatterBinStockPart -SuperMatterBinStockPart: MatterBinStockPart -BluespaceMatterBinStockPart: MatterBinStockPart -AnsibleSubspaceStockPart: null -FilterSubspaceStockPart: null -AmplifierSubspaceStockPart: null -TreatmentSubspaceStockPart: null -AnalyzerSubspaceStockPart: null -CrystalSubspaceStockPart: null -TransmitterSubspaceStockPart: null - # 2024-01-10 ClothingHeadHatHoodRad: null diff --git a/Resources/Prototypes/Actions/psionics.yml b/Resources/Prototypes/Actions/psionics.yml index 981d53884ea..d38608a469b 100644 --- a/Resources/Prototypes/Actions/psionics.yml +++ b/Resources/Prototypes/Actions/psionics.yml @@ -146,3 +146,76 @@ icon: Interface/VerbIcons/psionic_invisibility_off.png event: !type:RemovePsionicInvisibilityOffPowerActionEvent +- type: entity + id: ActionHealingWord + name: action-name-healing-word + description: action-description-healing-word + noSpawn: true + components: + - type: EntityTargetAction + icon: { sprite : Interface/Actions/psionics.rsi, state: healing_word } + useDelay: 10 + checkCanAccess: false + range: 6 + itemIconStyle: BigAction + canTargetSelf: true + blacklist: + components: + - PsionicInsulation + - Mindbroken + event: !type:PsionicHealOtherPowerActionEvent + healingAmount: + groups: # These all get divided by the number of damage types in the group. So they're all -2.5. + Genetic: -2.5 + Toxin: -5 + Airloss: -5 + Brute: -7.5 + Burn: -10 + rotReduction: 10 + useDelay: 1 + doRevive: true + powerName: Healing Word + popupText: healing-word-begin + playSound: true + minGlimmer: 2 + maxGlimmer: 4 + glimmerObviousSoundThreshold: 100 + glimmerObviousPopupThreshold: 200 + +- type: entity + id: ActionRevivify + name: action-name-revivify + description: action-description-revivify + noSpawn: true + components: + - type: EntityTargetAction + icon: { sprite : Interface/Actions/psionics.rsi, state: revivify } + useDelay: 120 + checkCanAccess: false + range: 2 + itemIconStyle: BigAction + canTargetSelf: false + blacklist: + components: + - PsionicInsulation + - Mindbroken + event: !type:PsionicHealOtherPowerActionEvent + healingAmount: + # These all get divided by the number of damage types in the group. So they're all -15 + # Additionally, they're multiplied by the caster's Amplification, which, + # assuming this is the only power they have, the multiplier is between 2.9-3.9 + groups: + Genetic: -15 + Toxin: -30 + Airloss: -60 # Except airloss, which heals 30 per type + Brute: -45 + Burn: -60 + rotReduction: 60 + doRevive: true + powerName: Revivify + popupText: revivify-begin + playSound: true + minGlimmer: 10 # These also get multiplied by caster stats. So, + maxGlimmer: 15 # keeping in mind the ~3.5x multiplier, this spikes glimmer by as much as 60 points. + glimmerObviousSoundThreshold: 50 + glimmerObviousPopupThreshold: 100 diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml index 552d81a5326..168e48a9634 100644 --- a/Resources/Prototypes/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/Catalog/uplink_catalog.yml @@ -10,6 +10,7 @@ Telecrystal: 3 categories: - UplinkWeapons + saleLimit: 1 - type: listing id: UplinkRevolverPython @@ -20,6 +21,7 @@ Telecrystal: 8 # Originally was 13 TC but was not used due to high cost categories: - UplinkWeapons + saleLimit: 1 # Inbuilt suppressor so it's sneaky + more expensive. - type: listing @@ -31,6 +33,7 @@ Telecrystal: 4 categories: - UplinkWeapons + saleLimit: 1 # Poor accuracy, slow to fire, cheap option - type: listing @@ -42,6 +45,7 @@ Telecrystal: 1 categories: - UplinkWeapons + saleLimit: 1 - type: listing id: UplinkEsword @@ -53,6 +57,7 @@ Telecrystal: 8 categories: - UplinkWeapons + saleLimit: 2 - type: listing id: UplinkEnergyDagger @@ -64,6 +69,7 @@ Telecrystal: 2 categories: - UplinkWeapons + saleLimit: 1 - type: listing id: UplinkThrowingKnivesKit @@ -85,6 +91,7 @@ Telecrystal: 8 categories: - UplinkWeapons + saleLimit: 1 - type: listing id: UplinkDisposableTurret @@ -100,6 +107,7 @@ blacklist: tags: - NukeOpsUplink + saleLimit: 2 - type: listing id: BaseBallBatHomeRun @@ -112,6 +120,7 @@ Telecrystal: 16 categories: - UplinkWeapons + saleLimit: 1 # Explosives @@ -214,6 +223,7 @@ whitelist: tags: - NukeOpsUplink + saleLimit: 1 - type: listing id: UplinkC4Bundle @@ -224,6 +234,7 @@ Telecrystal: 12 #you're buying bulk so its a 25% discount categories: - UplinkExplosives + saleLimit: 1 - type: listing id: UplinkEmpGrenade @@ -261,6 +272,7 @@ blacklist: tags: - NukeOpsUplink + saleLimit: 1 - type: listing id: UplinkSyndicateBombNukie @@ -276,6 +288,7 @@ whitelist: tags: - NukeOpsUplink + saleLimit: 1 - type: listing id: UplinkClusterGrenade @@ -451,6 +464,7 @@ blacklist: tags: - NukeOpsUplink + saleLimit: 1 - type: listing id: UplinkReinforcementRadioSyndicateNukeops # Version for Nukeops that spawns an agent with the NukeOperative component. @@ -467,6 +481,7 @@ whitelist: tags: - NukeOpsUplink + saleLimit: 1 - type: listing id: UplinkReinforcementRadioSyndicateCyborgAssault @@ -483,6 +498,7 @@ whitelist: tags: - NukeOpsUplink + saleLimit: 1 - type: listing id: UplinkReinforcementRadioSyndicateMonkey @@ -515,6 +531,7 @@ whitelist: tags: - NukeOpsUplink + saleLimit: 1 - type: listing id: UplinkStealthBox @@ -545,7 +562,6 @@ productEntity: EncryptionKeyBinary cost: Telecrystal: 1 - categories: - UplinkUtility @@ -748,6 +764,7 @@ blacklist: tags: - NukeOpsUplink + saleBlacklist: true - type: listing id: UplinkDeathRattle @@ -821,6 +838,7 @@ blacklist: components: - SurplusBundle + saleLimit: 1 - type: listing id: UplinkChemistryKitBundle @@ -862,6 +880,7 @@ blacklist: components: - SurplusBundle + saleLimit: 1 - type: listing id: UplinkSniperBundle @@ -873,6 +892,7 @@ Telecrystal: 12 categories: - UplinkBundles + saleLimit: 1 - type: listing id: UplinkC20RBundle @@ -884,6 +904,7 @@ Telecrystal: 17 categories: - UplinkBundles + saleLimit: 1 - type: listing id: UplinkBulldogBundle @@ -895,6 +916,7 @@ Telecrystal: 20 categories: - UplinkBundles + saleLimit: 1 - type: listing id: UplinkGrenadeLauncherBundle @@ -906,6 +928,7 @@ Telecrystal: 25 categories: - UplinkBundles + saleLimit: 1 - type: listing id: UplinkL6SawBundle @@ -917,6 +940,7 @@ Telecrystal: 30 categories: - UplinkBundles + saleLimit: 1 - type: listing id: UplinkZombieBundle @@ -937,6 +961,7 @@ blacklist: components: - SurplusBundle + saleLimit: 1 - type: listing id: UplinkSurplusBundle @@ -956,6 +981,7 @@ blacklist: components: - SurplusBundle + saleBlacklist: true - type: listing id: UplinkSuperSurplusBundle @@ -975,6 +1001,7 @@ blacklist: components: - SurplusBundle + saleBlacklist: true # Tools @@ -1113,6 +1140,7 @@ - !type:BuyerJobCondition whitelist: - Chaplain + saleLimit: 1 - type: listing id: uplinkRevolverCapGunFake @@ -1128,6 +1156,7 @@ whitelist: - Mime - Clown + saleLimit: 1 - type: listing id: uplinkBananaPeelExplosive @@ -1172,6 +1201,7 @@ - !type:BuyerJobCondition whitelist: - Clown + saleLimit: 1 - type: listing id: uplinkHotPotato @@ -1335,6 +1365,7 @@ Telecrystal: 8 categories: - UplinkArmor + saleLimit: 1 - type: listing id: UplinkHardsuitSyndieElite @@ -1346,6 +1377,7 @@ Telecrystal: 10 categories: - UplinkArmor + saleLimit: 1 - type: listing id: UplinkClothingOuterHardsuitJuggernaut @@ -1357,6 +1389,7 @@ Telecrystal: 12 categories: - UplinkArmor + saleLimit: 1 # Misc @@ -1425,6 +1458,7 @@ whitelist: tags: - NukeOpsUplink + saleLimit: 1 - type: listing id: UplinkSoapSyndie @@ -1515,6 +1549,7 @@ Telecrystal: 12 categories: - UplinkMisc + saleLimit: 1 - type: listing id: UplinkBribe @@ -1541,6 +1576,7 @@ whitelist: tags: - NukeOpsUplink + saleLimit: 1 - type: listing id: UplinkBackpackSyndicate diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/salvage.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/salvage.yml index 34bf32d8d7b..4beffbc9c22 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/salvage.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/salvage.yml @@ -63,23 +63,78 @@ offset: 0.0 - type: entity - name: salvage loot spawner - id: SalvageLootSpawner + name: Salvage T2 Machine Parts Spawner + id: SalvagePartsT2Spawner parent: MarkerBase components: - type: Sprite layers: - state: red - - sprite: Objects/Weapons/Melee/crusher.rsi - state: icon + - sprite: Objects/Misc/stock_parts.rsi + state: advanced_matter_bin - type: RandomSpawner prototypes: - - WeaponCrusher - - WeaponCrusherDagger - - WeaponCrusherGlaive - - MiningDrill + - AdvancedCapacitorStockPart + - NanoManipulatorStockPart + - AdvancedMatterBinStockPart offset: 0.0 +- type: entity + parent: MarkerBase + id: SalvagePartsT3T4Spawner + name: tier 3/4 machine part + components: + - type: Sprite + layers: + - sprite: Objects/Misc/stock_parts.rsi + state: super_matter_bin + - type: RandomSpawner + rarePrototypes: + - QuadraticCapacitorStockPart + - FemtoManipulatorStockPart + - BluespaceMatterBinStockPart + rareChance: 0.05 + prototypes: + - SuperCapacitorStockPart + - PicoManipulatorStockPart + - SuperMatterBinStockPart + chance: 0.95 + offset: 0.0 + +- type: entity + parent: MarkerBase + id: SalvagePartsT3Spawner + name: tier 3 machine part + suffix: Spawner + components: + - type: Sprite + layers: + - sprite: Objects/Misc/stock_parts.rsi + state: super_matter_bin + - type: RandomSpawner + prototypes: + - SuperCapacitorStockPart + - PicoManipulatorStockPart + - SuperMatterBinStockPart + offset: 0.0 + +- type: entity + parent: MarkerBase + id: SalvagePartsT4Spawner + name: tier 4 machine part + suffix: Spawner + components: + - type: Sprite + layers: + - sprite: Objects/Misc/stock_parts.rsi + state: bluespace_matter_bin + - type: RandomSpawner + prototypes: + - QuadraticCapacitorStockPart + - PicoManipulatorStockPart + - BluespaceMatterBinStockPart + offset: 0.0 + - type: entity name: Salvage Mob Spawner id: SalvageMobSpawner @@ -228,3 +283,25 @@ - MobFleshLoverSalvage chance: 1 offset: 0.2 + +- type: entity + name: Salvage Loot Spawner + id: SalvageLootSpawner + parent: MarkerBase + components: + - type: Sprite + layers: + - state: red + - sprite: Structures/Storage/Crates/generic.rsi + state: icon + - type: RandomSpawner + rarePrototypes: + - SalvagePartsT2Spawner + - SalvagePartsT3Spawner + - SalvagePartsT3T4Spawner + - SalvagePartsT4Spawner + rareChance: 0.4 + prototypes: + - CrateSalvageAssortedGoodies + chance: 0.9 + offset: 0.2 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml index 93d4b957fe7..743d9a17925 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml @@ -48,6 +48,31 @@ path: /Audio/SimpleStation14/Items/Handling/drinkglass_drop.ogg params: volume: -2 + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.25,-0.25,0.25,0.25" + density: 30 + mask: + - ItemMask + layer: + - BulletImpassable # Ever seen a John Woo movie? + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 5 + behaviors: + - !type:PlaySoundBehavior + sound: + collection: MetalBreak + params: + volume: -6 + - !type:SpillBehavior { } + - !type:DoActsBehavior + acts: [ "Destruction" ] - type: entity parent: DrinkBase diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml index a6752286dd2..b2489325e0a 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml @@ -31,6 +31,9 @@ !type:DamageTrigger damage: 5 behaviors: + - !type:PlaySoundBehavior + sound: + collection: GlassBreak - !type:SpillBehavior { } - !type:DoActsBehavior acts: [ "Destruction" ] diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/plate.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/plate.yml index 63c47df67e7..3bd66e8a481 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/plate.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/plate.yml @@ -54,6 +54,17 @@ materialComposition: Glass: 60 - type: SpaceGarbage + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.25,-0.25,0.25,0.25" + density: 25 + mask: + - ItemMask + layer: + - BulletImpassable # Ever seen a John Woo movie? - type: entity name: broken plate diff --git a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml index 341acb52f0b..d90b03528e5 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml @@ -16,6 +16,10 @@ sound: /Audio/SimpleStation14/Items/Handling/component_drop.ogg - type: EmitSoundOnLand sound: /Audio/SimpleStation14/Items/Handling/component_drop.ogg +# Rating 1 + - type: GuideHelp + guides: + - MachineUpgrading # Rating 1 @@ -69,3 +73,218 @@ - type: ReverseEngineering # Nyano recipes: - MatterBinStockPart + +# Rating 2 + +- type: entity + id: AdvancedCapacitorStockPart + name: advanced capacitor + parent: CapacitorStockPart + description: An advanced capacitor used in the construction of a variety of devices. + suffix: Rating 2 + components: + - type: Sprite + state: adv_capacitor + - type: MachinePart + rating: 2 + - type: ReverseEngineering # Nyano + difficulty: 2 + recipes: + - AdvancedCapacitorStockPart + +- type: entity + id: NanoManipulatorStockPart + name: advanced manipulator + parent: MicroManipulatorStockPart + description: An advanced manipulator used in the construction of a variety of devices. + suffix: Rating 2 + components: + - type: Sprite + state: nano_mani + - type: MachinePart + rating: 2 + - type: ReverseEngineering # Nyano + difficulty: 2 + recipes: + - NanoManipulatorStockPart + +- type: entity + id: AdvancedMatterBinStockPart + name: advanced matter bin + parent: MatterBinStockPart + description: An advanced matter bin used in the construction of a variety of devices. + suffix: Rating 2 + components: + - type: Sprite + state: advanced_matter_bin + - type: MachinePart + rating: 2 + - type: ReverseEngineering # Nyano + difficulty: 2 + recipes: + - AdvancedMatterBinStockPart + +# Rating 3 + +- type: entity + id: SuperCapacitorStockPart + name: super capacitor + parent: CapacitorStockPart + description: A super capacitor used in the construction of a variety of devices. + suffix: Rating 3 + components: + - type: Sprite + state: super_capacitor + - type: MachinePart + rating: 3 + - type: ReverseEngineering # Nyano + difficulty: 3 + recipes: + - SuperCapacitorStockPart + +- type: entity + id: PicoManipulatorStockPart + name: super manipulator + parent: MicroManipulatorStockPart + description: A super manipulator used in the construction of a variety of devices. + suffix: Rating 3 + components: + - type: Sprite + state: pico_mani + - type: MachinePart + rating: 3 + - type: ReverseEngineering # Nyano + difficulty: 3 + recipes: + - PicoManipulatorStockPart + +- type: entity + id: SuperMatterBinStockPart + name: super matter bin + parent: MatterBinStockPart + description: A super matter bin used in the construction of a variety of devices. + suffix: Rating 3 + components: + - type: Sprite + state: super_matter_bin + - type: MachinePart + rating: 3 + - type: ReverseEngineering # Nyano + difficulty: 3 + recipes: + - SuperMatterBinStockPart + +# Rating 4 + +- type: entity + id: QuadraticCapacitorStockPart + name: bluespace capacitor + parent: CapacitorStockPart + description: A bluespace capacitor used in the construction of a variety of devices. + suffix: Rating 4 + components: + - type: Sprite + state: quadratic_capacitor + - type: MachinePart + rating: 4 + - type: ReverseEngineering # Nyano + difficulty: 4 + recipes: + - QuadraticCapacitorStockPart + +- type: entity + id: FemtoManipulatorStockPart + name: bluespace manipulator + parent: MicroManipulatorStockPart + description: A bluespace manipulator used in the construction of a variety of devices. + suffix: Rating 4 + components: + - type: Sprite + state: femto_mani + - type: MachinePart + rating: 4 + - type: ReverseEngineering # Nyano + difficulty: 4 + recipes: + - FemtoManipulatorStockPart + +- type: entity + id: BluespaceMatterBinStockPart + name: bluespace matter bin + parent: MatterBinStockPart + description: A bluespace matter bin used in the construction of a variety of devices. + suffix: Rating 4 + components: + - type: Sprite + state: bluespace_matter_bin + - type: MachinePart + rating: 4 + - type: ReverseEngineering # Nyano + difficulty: 4 + recipes: + - BluespaceMatterBinStockPart + +# Subspace stock parts (REMOVE THESE) + +- type: entity + id: AnsibleSubspaceStockPart + name: subspace ansible + parent: BaseStockPart + description: A compact module capable of sensing extradimensional activity. + components: + - type: Sprite + state: subspace_ansible + +- type: entity + id: FilterSubspaceStockPart + name: hyperwave filter + parent: BaseStockPart + description: A tiny device capable of filtering and converting super-intense radiowaves. + components: + - type: Sprite + state: hyperwave_filter + +- type: entity + id: AmplifierSubspaceStockPart + name: subspace amplifier + parent: BaseStockPart + description: A compact micro-machine capable of amplifying weak subspace transmissions. + components: + - type: Sprite + state: subspace_amplifier + +- type: entity + id: TreatmentSubspaceStockPart + name: subspace treatment disk + parent: BaseStockPart + description: A compact micro-machine capable of stretching out hyper-compressed radio waves. + components: + - type: Sprite + state: treatment_disk + +- type: entity + id: AnalyzerSubspaceStockPart + name: subspace wavelength analyzer + parent: BaseStockPart + description: A sophisticated analyzer capable of analyzing cryptic subspace wavelengths. + components: + - type: Sprite + state: wavelength_analyzer + +- type: entity + id: CrystalSubspaceStockPart + name: ansible crystal + parent: BaseStockPart + description: A crystal made from pure glass used to transmit laser databursts to subspace. + components: + - type: Sprite + state: ansible_crystal + +- type: entity + id: TransmitterSubspaceStockPart + name: subspace transmitter + parent: BaseStockPart + description: A large piece of equipment used to open a window into the subspace dimension. + components: + - type: Sprite + state: subspace_transmitter diff --git a/Resources/Prototypes/Entities/Objects/Specific/Research/rped.yml b/Resources/Prototypes/Entities/Objects/Specific/Research/rped.yml index 84a51b4f345..fca8c8ae85f 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Research/rped.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Research/rped.yml @@ -10,6 +10,9 @@ - type: Item sprite: Objects/Specific/Research/rped.rsi size: Normal + - type: GuideHelp + guides: + - MachineUpgrading - type: PartExchanger - type: Storage grid: diff --git a/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml b/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml index 681f0a390c8..6e331a13a8c 100644 --- a/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml +++ b/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml @@ -31,6 +31,9 @@ acts: ["Destruction"] - type: Machine board: ChemDispenserMachineCircuitboard + - type: UpgradePowerDraw + powerDrawMultiplier: 0.75 + scaling: Exponential - type: GuideHelp guides: - Chemicals diff --git a/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml b/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml index 0fb69b4fdbd..14b3270ba88 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml @@ -33,7 +33,7 @@ thresholds: - trigger: !type:DamageTrigger - damage: 100 + damage: 25 behaviors: - !type:DoActsBehavior acts: [ "Destruction" ] @@ -48,6 +48,7 @@ collection: MetalBreak - type: StaticPrice price: 10 + - type: RequireProjectileTarget #Starts unanchored, cannot be rotated while anchored - type: entity diff --git a/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml b/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml index ab09a03fef7..e6f08fe8467 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml @@ -176,8 +176,8 @@ SetParticleZeta: AnomalousParticleZeta SetParticleSigma: AnomalousParticleSigma fireBurstSize: 1 - fireBurstDelayMin: 2 - fireBurstDelayMax: 6 + baseFireBurstDelayMin: 2 + baseFireBurstDelayMax: 6 - type: ApcPowerReceiver powerLoad: 100 - type: GuideHelp diff --git a/Resources/Prototypes/Entities/Structures/Machines/anomaly_sync.yml b/Resources/Prototypes/Entities/Structures/Machines/anomaly_sync.yml index 3019b8adf83..eeaae611c37 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/anomaly_sync.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/anomaly_sync.yml @@ -41,7 +41,7 @@ bounds: "-0.35,-0.35,0.35,0.35" density: 100 mask: - - ItemMask + - ItemMask hard: True - type: Transform anchored: true @@ -49,6 +49,9 @@ - type: ApcPowerReceiver powerLoad: 2500 needsPower: true + - type: UpgradePowerDraw + powerDrawMultiplier: 0.80 + scaling: Exponential - type: ItemPlacer whitelist: components: diff --git a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml index 18999a6ab2a..2bea530e908 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml @@ -107,6 +107,9 @@ hard: False - type: Transform noRot: false + - type: UpgradePowerDraw + powerDrawMultiplier: 0.80 + scaling: Exponential - type: TraversalDistorter - type: ItemPlacer # don't limit the number of artifacts that can be biased diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index e5a9cb35417..52e9096791b 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -294,6 +294,12 @@ - Implanter - PillCanister - ChemistryEmptyBottle01 + - AdvancedCapacitorStockPart + - AdvancedMatterBinStockPart + - NanoManipulatorStockPart + - SuperCapacitorStockPart + - SuperMatterBinStockPart + - PicoManipulatorStockPart - AdvMopItem - WeaponSprayNozzle - ClothingBackpackWaterTank diff --git a/Resources/Prototypes/Entities/Structures/Machines/seed_extractor.yml b/Resources/Prototypes/Entities/Structures/Machines/seed_extractor.yml index ec07294bfb1..4ecd1c29e27 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/seed_extractor.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/seed_extractor.yml @@ -35,3 +35,6 @@ - type: SeedExtractor - type: Machine board: SeedExtractorMachineCircuitboard + - type: UpgradePowerDraw + powerDrawMultiplier: 0.75 + scaling: Exponential diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml index 4fa1bcbd918..35d65ffe87a 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml @@ -171,6 +171,9 @@ materialWhiteList: [Plasma] - type: PortableGenerator startChance: 0.8 + - type: UpgradePowerSupplier + powerSupplyMultiplier: 1.25 + scaling: Exponential - type: GeneratorExhaustGas gasType: CarbonDioxide # 2 moles of gas for every sheet of plasma. @@ -228,6 +231,9 @@ storageLimit: 3000 materialWhiteList: [Uranium] - type: PortableGenerator + - type: UpgradePowerSupplier + powerSupplyMultiplier: 1.25 + scaling: Exponential - type: PowerMonitoringDevice group: Generator loadNodes: diff --git a/Resources/Prototypes/Entities/Structures/Power/smes.yml b/Resources/Prototypes/Entities/Structures/Power/smes.yml index 1e3559e5a95..762e8d375f6 100644 --- a/Resources/Prototypes/Entities/Structures/Power/smes.yml +++ b/Resources/Prototypes/Entities/Structures/Power/smes.yml @@ -29,10 +29,15 @@ state: "smes-op1" shader: unshaded - type: Smes + - type: UpgradeBattery + maxChargeMultiplier: 2 + baseMaxCharge: 8000000 + - type: UpgradePowerSupplyRamping + scaling: Linear + supplyRampingMultiplier: 1 - type: Appearance - type: Battery startingCharge: 0 - maxCharge: 8000000 - type: ExaminableBattery - type: NodeContainer examinable: true diff --git a/Resources/Prototypes/Entities/Structures/Power/substation.yml b/Resources/Prototypes/Entities/Structures/Power/substation.yml index 347b18ecaee..489cfff6597 100644 --- a/Resources/Prototypes/Entities/Structures/Power/substation.yml +++ b/Resources/Prototypes/Entities/Structures/Power/substation.yml @@ -17,8 +17,13 @@ shader: unshaded - state: full shader: unshaded + - type: UpgradeBattery + maxChargeMultiplier: 2 + baseMaxCharge: 2500000 + - type: UpgradePowerSupplyRamping + scaling: Linear + supplyRampingMultiplier: 1 - type: Battery - maxCharge: 2500000 startingCharge: 0 - type: ExaminableBattery - type: PointLight diff --git a/Resources/Prototypes/Entities/Structures/Shuttles/thrusters.yml b/Resources/Prototypes/Entities/Structures/Shuttles/thrusters.yml index f87be426598..eb299e3f3a6 100644 --- a/Resources/Prototypes/Entities/Structures/Shuttles/thrusters.yml +++ b/Resources/Prototypes/Entities/Structures/Shuttles/thrusters.yml @@ -143,6 +143,7 @@ - type: Thruster thrusterType: Angular requireSpace: false + baseThrust: 2000 thrust: 2000 machinePartThrust: Manipulator - type: Sprite @@ -192,6 +193,9 @@ collection: MetalBreak - !type:ChangeConstructionNodeBehavior node: machineFrame + - type: UpgradePowerDraw + powerDrawMultiplier: 0.75 + scaling: Exponential - type: Damageable damageContainer: Inorganic damageModifierSet: Electronic @@ -216,6 +220,7 @@ - type: Thruster thrusterType: Angular requireSpace: false + baseThrust: 100 thrust: 100 - type: ApcPowerReceiver needsPower: false diff --git a/Resources/Prototypes/Guidebook/science.yml b/Resources/Prototypes/Guidebook/science.yml index a21be1678ce..c73b666f687 100644 --- a/Resources/Prototypes/Guidebook/science.yml +++ b/Resources/Prototypes/Guidebook/science.yml @@ -7,6 +7,7 @@ - AnomalousResearch - Xenoarchaeology - Robotics + - MachineUpgrading - Psionics # Nyanotrasen - Psionics guidebook # - AltarsGolemancy # When it's added # Nyanotrasen - Golemancy guidebook - ReverseEngineering # Nyanotrasen - Reverse Engineering guidebook @@ -60,6 +61,11 @@ name: guide-entry-traversal-distorter text: "/ServerInfo/Guidebook/Science/TraversalDistorter.xml" +- type: guideEntry + id: MachineUpgrading + name: guide-entry-machine-upgrading + text: "/ServerInfo/Guidebook/Science/MachineUpgrading.xml" + - type: guideEntry id: Cyborgs name: guide-entry-cyborgs diff --git a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml b/Resources/Prototypes/Nyanotrasen/psionicPowers.yml index 0781122b8ec..2aee2273a18 100644 --- a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml +++ b/Resources/Prototypes/Nyanotrasen/psionicPowers.yml @@ -11,3 +11,5 @@ # PsionicInvisibilityPower: 0.15 MindSwapPower: 0.15 TelepathyPower: 1 + HealingWordPower: 0.85 + RevivifyPower: 0.1 diff --git a/Resources/Prototypes/Psionics/psionics.yml b/Resources/Prototypes/Psionics/psionics.yml index 461098eab21..b881f1f11fb 100644 --- a/Resources/Prototypes/Psionics/psionics.yml +++ b/Resources/Prototypes/Psionics/psionics.yml @@ -140,3 +140,25 @@ initializationFeedback: telepathy-power-initialization-feedback metapsionicFeedback: psionic-language-power-feedback # Reuse for telepathy, clairaudience, etc powerSlotCost: 0 + +- type: psionicPower + id: HealingWordPower + name: HealingWord + description: healing-word-power-description + actions: + - ActionHealingWord + initializationFeedback: healing-word-power-initialization-feedback + metapsionicFeedback: healing-word-power-feedback + amplificationModifier: 0.5 + dampeningModifier: 0.5 + +- type: psionicPower + id: RevivifyPower + name: Revivify + description: revivify-power-description + actions: + - ActionRevivify + initializationFeedback: revivify-power-initialization-feedback + metapsionicFeedback: revivify-power-feedback + amplificationModifier: 2.5 # An extremely rare and dangerous power + powerSlotCost: 2 diff --git a/Resources/Prototypes/Recipes/Lathes/Parts.yml b/Resources/Prototypes/Recipes/Lathes/Parts.yml index 90cff2174d6..496bc3a8a48 100644 --- a/Resources/Prototypes/Recipes/Lathes/Parts.yml +++ b/Resources/Prototypes/Recipes/Lathes/Parts.yml @@ -1,3 +1,4 @@ +#Rating 1 - type: latheRecipe id: CapacitorStockPart result: CapacitorStockPart @@ -24,3 +25,62 @@ materials: Steel: 50 Plastic: 50 + +#Rating 2 +- type: latheRecipe + id: AdvancedCapacitorStockPart + result: AdvancedCapacitorStockPart + completetime: 3 + materials: + Steel: 80 + Plastic: 80 + Plasma: 75 + +- type: latheRecipe + id: AdvancedMatterBinStockPart + result: AdvancedMatterBinStockPart + completetime: 3 + materials: + Steel: 80 + Plastic: 80 + Plasma: 75 + +- type: latheRecipe + id: NanoManipulatorStockPart + result: NanoManipulatorStockPart + completetime: 3 + materials: + Steel: 80 + Plastic: 80 + Plasma: 75 + +#Rating 3 +- type: latheRecipe + id: SuperCapacitorStockPart + result: SuperCapacitorStockPart + completetime: 3 + materials: + Steel: 150 + Plastic: 150 + Plasma: 75 + Gold: 75 + +- type: latheRecipe + id: SuperMatterBinStockPart + result: SuperMatterBinStockPart + completetime: 3 + materials: + Steel: 150 + Plastic: 150 + Plasma: 75 + Gold: 75 + +- type: latheRecipe + id: PicoManipulatorStockPart + result: PicoManipulatorStockPart + completetime: 3 + materials: + Steel: 150 + Plastic: 150 + Plasma: 75 + Gold: 75 diff --git a/Resources/Prototypes/Research/experimental.yml b/Resources/Prototypes/Research/experimental.yml index d46e1db144e..85523033f86 100644 --- a/Resources/Prototypes/Research/experimental.yml +++ b/Resources/Prototypes/Research/experimental.yml @@ -84,6 +84,20 @@ # Tier 2 +- type: technology + id: AdvancedParts + name: research-technology-advanced-parts + icon: + sprite: Objects/Misc/stock_parts.rsi + state: advanced_matter_bin + discipline: Experimental + tier: 2 + cost: 10000 + recipeUnlocks: + - AdvancedCapacitorStockPart + - AdvancedMatterBinStockPart + - NanoManipulatorStockPart + - type: technology id: AbnormalArtifactManipulation name: research-technology-abnormal-artifact-manipulation @@ -155,6 +169,20 @@ # Tier 3 +- type: technology + id: SuperParts + name: research-technology-super-parts + icon: + sprite: Objects/Misc/stock_parts.rsi + state: super_matter_bin + discipline: Experimental + tier: 3 + cost: 15000 + recipeUnlocks: + - SuperCapacitorStockPart + - SuperMatterBinStockPart + - PicoManipulatorStockPart + #- type: technology # DeltaV - LRP # id: GravityManipulation # name: research-technology-gravity-manipulation diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml b/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml index c7395ff3e2d..d4f8bdb0671 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml @@ -5,8 +5,8 @@ playTimeTracker: JobChaplain requirements: - !type:CharacterDepartmentTimeRequirement - department: Epistemics # DeltaV - Epistemics Department replacing Science - min: 14400 #DeltaV 4 hours + department: Epistemics # Chaplain is now one of the station's "Crew-Aligned Wizards" + min: 14400 # 4 hours - !type:CharacterLogicOrRequirement requirements: - !type:CharacterSpeciesRequirement @@ -21,7 +21,7 @@ supervisors: job-supervisors-rd access: - Chapel - - Research # DeltaV - Move Chaplain into Epistemics + - Research - Maintenance special: - !type:AddComponentSpecial @@ -32,6 +32,7 @@ - type: InnatePsionicPowers powersToAdd: - TelepathyPower + - HealingWordPower - type: startingGear id: ChaplainGear @@ -40,7 +41,7 @@ back: ClothingBackpackChaplainFilled shoes: ClothingShoesColorBlack id: ChaplainPDA - ears: ClothingHeadsetScience # DeltaV - Move Chaplain into Epistemics + ears: ClothingHeadsetScience innerClothingSkirt: ClothingUniformJumpskirtChaplain satchel: ClothingBackpackSatchelChaplainFilled duffelbag: ClothingBackpackDuffelChaplainFilled diff --git a/Resources/Prototypes/Store/presets.yml b/Resources/Prototypes/Store/presets.yml index e623f4c8cd6..bbf96997956 100644 --- a/Resources/Prototypes/Store/presets.yml +++ b/Resources/Prototypes/Store/presets.yml @@ -13,5 +13,13 @@ - UplinkJob - UplinkArmor - UplinkPointless + - UplinkSales currencyWhitelist: - Telecrystal + sales: + enabled: true + minMultiplier: 0.2 + maxMultiplier: 0.8 + minItems: 3 + maxItems: 8 + salesCategory: UplinkSales diff --git a/Resources/Prototypes/XenoArch/Effects/normal_effects.yml b/Resources/Prototypes/XenoArch/Effects/normal_effects.yml index b9564c0366b..0a0b9bcbcc3 100644 --- a/Resources/Prototypes/XenoArch/Effects/normal_effects.yml +++ b/Resources/Prototypes/XenoArch/Effects/normal_effects.yml @@ -584,6 +584,24 @@ messages: - shuffle-artifact-popup +- type: artifactEffect + id: EffectT4PartsSpawn + targetDepth: 3 + effectHint: artifact-effect-hint-creation + components: + - type: SpawnArtifact + maxSpawns: 10 + spawns: + - id: QuadraticCapacitorStockPart + prob: 0.5 + maxAmount: 3 + - id: FemtoManipulatorStockPart + prob: 0.5 + maxAmount: 3 + - id: BluespaceMatterBinStockPart + prob: 0.5 + maxAmount: 3 + - type: artifactEffect id: EffectFoamDangerous targetDepth: 3 diff --git a/Resources/Prototypes/_White/Store/categories.yml b/Resources/Prototypes/_White/Store/categories.yml new file mode 100644 index 00000000000..cb9cfbc88f8 --- /dev/null +++ b/Resources/Prototypes/_White/Store/categories.yml @@ -0,0 +1,4 @@ +- type: storeCategory + id: UplinkSales + name: Sales + priority: 10 diff --git a/Resources/ServerInfo/Guidebook/Science/MachineUpgrading.xml b/Resources/ServerInfo/Guidebook/Science/MachineUpgrading.xml new file mode 100644 index 00000000000..286219b4d9e --- /dev/null +++ b/Resources/ServerInfo/Guidebook/Science/MachineUpgrading.xml @@ -0,0 +1,38 @@ + +# Machine Upgrading + +Machines help the station run smoothly, and as a scientist, you can help them run even better! + +## Parts +Stock Parts: + + + + + +You can examine each machine part to see both the type and the rating, which range from 1 to 4. + +Parts of higher levels can be researched as well as found through artifacts or salvage. + + + + + + +## Upgrading +To know if a machine can be upgraded, you can examine it and check for the [color=#a4885c]lightning bolt[/color] icon in the lower right corner. Clicking on it will allow you to see what kinds of upgrades the machine has. + +To check what parts a machine needs, you can examine its board. Try it here: + + + + + + +You can use any rating part for any part requirement. Using higher rated parts will increase how effective the machine is. + +If you want to upgrade an existing machine, simply deconstruct it with a screwdriver and crowbar, and replace the existing parts with parts of a higher level. + +You can also quickly upgrade machines by using an RPED, loading it with machine parts, and then clicking on a machine. It will quickly be upgraded with whatever parts were inserted. + + diff --git a/Resources/Textures/Interface/Actions/psionics.rsi/healing_word.png b/Resources/Textures/Interface/Actions/psionics.rsi/healing_word.png new file mode 100644 index 00000000000..800e2a46e61 Binary files /dev/null and b/Resources/Textures/Interface/Actions/psionics.rsi/healing_word.png differ diff --git a/Resources/Textures/Interface/Actions/psionics.rsi/meta.json b/Resources/Textures/Interface/Actions/psionics.rsi/meta.json new file mode 100644 index 00000000000..085cc412814 --- /dev/null +++ b/Resources/Textures/Interface/Actions/psionics.rsi/meta.json @@ -0,0 +1,17 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Created by leonardo_dabepis (discord)", + "size": { + "x": 64, + "y": 64 + }, + "states": [ + { + "name": "healing_word" + }, + { + "name": "revivify" + } + ] +} diff --git a/Resources/Textures/Interface/Actions/psionics.rsi/revivify.png b/Resources/Textures/Interface/Actions/psionics.rsi/revivify.png new file mode 100644 index 00000000000..087f40b4c98 Binary files /dev/null and b/Resources/Textures/Interface/Actions/psionics.rsi/revivify.png differ diff --git a/Resources/Textures/Interface/VerbIcons/ATTRIBUTION.txt b/Resources/Textures/Interface/VerbIcons/ATTRIBUTION.txt index 0b8ae856bd8..69aa4650b32 100644 --- a/Resources/Textures/Interface/VerbIcons/ATTRIBUTION.txt +++ b/Resources/Textures/Interface/VerbIcons/ATTRIBUTION.txt @@ -7,3 +7,6 @@ https://game-icons.net/1x1/lorc/padlock.html unlock.svg by Delapouite under CC BY 3.0 https://game-icons.net/1x1/delapouite/padlock-open.html + +healing_word.png by LeonardoDaBepis under CC BY 3.0 +revivify.png by LeonardoDaBepis under CC BY 3.0 \ No newline at end of file