Merge remote-tracking branch 'upstream/master' into upstream-sync

# Conflicts:
#	Resources/Prototypes/Maps/saltern.yml
#	Resources/ServerInfo/Guidebook/Science/Xenoarchaeology.xml
This commit is contained in:
Morb0
2023-05-13 10:39:34 +03:00
203 changed files with 73632 additions and 49182 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

3
.gitignore vendored
View File

@@ -306,3 +306,6 @@ Resources/MapImages
/Content.Docfx/*site
*.bak
# Direnv stuff
.direnv/

View File

@@ -130,7 +130,7 @@ public sealed class BackgroundAudioSystem : EntitySystem
{
if (_playMan.LocalPlayer is null
|| _playMan.LocalPlayer.ControlledEntity != message.Entity
|| !_timing.IsFirstTimePredicted)
|| !(_timing.IsFirstTimePredicted || _timing.ApplyingState))
return;
// Check if we traversed to grid.

View File

@@ -4,8 +4,8 @@ namespace Content.Client.Botany.Components;
public sealed class PotencyVisualsComponent : Component
{
[DataField("minimumScale")]
public float MinimumScale = 0.5f;
public float MinimumScale = 1f;
[DataField("maximumScale")]
public float MaximumScale = 1.5f;
public float MaximumScale = 2f;
}

View File

@@ -70,7 +70,7 @@ internal sealed class BuckleSystem : SharedBuckleSystem
!buckled ||
args.Sprite == null)
{
_rotationVisualizerSystem.SetHorizontalAngle(uid, RotationVisualsComponent.DefaultRotation, rotVisuals);
_rotationVisualizerSystem.SetHorizontalAngle(uid, rotVisuals.DefaultRotation, rotVisuals);
return;
}

View File

@@ -1,9 +0,0 @@
using Content.Shared.Chemistry.Reaction;
namespace Content.Client.Chemistry
{
public sealed class ChemicalReactionSystem : SharedChemicalReactionSystem
{
}
}

View File

@@ -134,7 +134,6 @@ namespace Content.Client.Entry
_componentFactory.GenerateNetIds();
_adminManager.Initialize();
_stylesheetManager.Initialize();
_screenshotHook.Initialize();
_changelogManager.Initialize();
_rulesManager.Initialize();
@@ -154,6 +153,9 @@ namespace Content.Client.Entry
public override void PostInit()
{
base.PostInit();
_stylesheetManager.Initialize();
// Setup key contexts
ContentContexts.SetupContexts(_inputManager.Contexts);

View File

@@ -2,11 +2,10 @@ using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Pulling.Components;
using Robust.Client.GameObjects;
using Robust.Client.Physics;
using Robust.Client.Player;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.Physics.Controllers
{
@@ -22,16 +21,51 @@ namespace Content.Client.Physics.Controllers
SubscribeLocalEvent<RelayInputMoverComponent, PlayerDetachedEvent>(OnRelayPlayerDetached);
SubscribeLocalEvent<InputMoverComponent, PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<InputMoverComponent, PlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<InputMoverComponent, UpdateIsPredictedEvent>(OnUpdatePredicted);
SubscribeLocalEvent<MovementRelayTargetComponent, UpdateIsPredictedEvent>(OnUpdateRelayTargetPredicted);
SubscribeLocalEvent<SharedPullableComponent, UpdateIsPredictedEvent>(OnUpdatePullablePredicted);
}
private void OnUpdatePredicted(EntityUid uid, InputMoverComponent component, ref UpdateIsPredictedEvent args)
{
// Enable prediction if an entity is controlled by the player
if (uid == _playerManager.LocalPlayer?.ControlledEntity)
args.IsPredicted = true;
}
private void OnUpdateRelayTargetPredicted(EntityUid uid, MovementRelayTargetComponent component, ref UpdateIsPredictedEvent args)
{
if (component.Source == _playerManager.LocalPlayer?.ControlledEntity)
args.IsPredicted = true;
}
private void OnUpdatePullablePredicted(EntityUid uid, SharedPullableComponent component, ref UpdateIsPredictedEvent args)
{
// Enable prediction if an entity is being pulled by the player.
// Disable prediction if an entity is being pulled by some non-player entity.
if (component.Puller == _playerManager.LocalPlayer?.ControlledEntity)
args.IsPredicted = true;
else if (component.Puller != null)
args.BlockPrediction = true;
// TODO recursive pulling checks?
// What if the entity is being pulled by a vehicle controlled by the player?
}
private void OnRelayPlayerAttached(EntityUid uid, RelayInputMoverComponent component, PlayerAttachedEvent args)
{
Physics.UpdateIsPredicted(uid);
Physics.UpdateIsPredicted(component.RelayEntity);
if (TryComp<InputMoverComponent>(component.RelayEntity, out var inputMover))
SetMoveInput(inputMover, MoveButtons.None);
}
private void OnRelayPlayerDetached(EntityUid uid, RelayInputMoverComponent component, PlayerDetachedEvent args)
{
Physics.UpdateIsPredicted(uid);
Physics.UpdateIsPredicted(component.RelayEntity);
if (TryComp<InputMoverComponent>(component.RelayEntity, out var inputMover))
SetMoveInput(inputMover, MoveButtons.None);
}
@@ -53,12 +87,8 @@ namespace Content.Client.Physics.Controllers
if (_playerManager.LocalPlayer?.ControlledEntity is not {Valid: true} player)
return;
if (TryComp<RelayInputMoverComponent>(player, out var relayMover)
&& TryComp(relayMover.RelayEntity, out MovementRelayTargetComponent? targetComp))
{
DebugTools.Assert(targetComp.Entities.Count <= 1, "Multiple relayed movers are not supported at the moment");
HandleClientsideMovement(relayMover.RelayEntity.Value, frameTime);
}
if (TryComp<RelayInputMoverComponent>(player, out var relayMover))
HandleClientsideMovement(relayMover.RelayEntity, frameTime);
HandleClientsideMovement(player, frameTime);
}
@@ -70,7 +100,6 @@ namespace Content.Client.Physics.Controllers
var relayTargetQuery = GetEntityQuery<MovementRelayTargetComponent>();
var mobMoverQuery = GetEntityQuery<MobMoverComponent>();
var pullableQuery = GetEntityQuery<SharedPullableComponent>();
var physicsQuery = GetEntityQuery<PhysicsComponent>();
var modifierQuery = GetEntityQuery<MovementSpeedModifierComponent>();
if (!moverQuery.TryGetComponent(player, out var mover) ||
@@ -98,42 +127,6 @@ namespace Content.Client.Physics.Controllers
return;
}
// Essentially we only want to set our mob to predicted so every other entity we just interpolate
// (i.e. only see what the server has sent us).
// The exception to this is joints.
body.Predict = true;
// We set joints to predicted given these can affect how our mob moves.
// I would only recommend disabling this if you make pulling not use joints anymore (someday maybe?)
if (TryComp(player, out JointComponent? jointComponent))
{
foreach (var joint in jointComponent.GetJoints.Values)
{
if (physicsQuery.TryGetComponent(joint.BodyAUid, out var physics))
physics.Predict = true;
if (physicsQuery.TryGetComponent(joint.BodyBUid, out physics))
physics.Predict = true;
}
}
// If we're being pulled then we won't predict anything and will receive server lerps so it looks way smoother.
if (pullableQuery.TryGetComponent(player, out var pullableComp))
{
if (pullableComp.Puller is {Valid: true} puller && TryComp<PhysicsComponent>(puller, out var pullerBody))
{
pullerBody.Predict = false;
body.Predict = false;
if (TryComp<SharedPullerComponent>(player, out var playerPuller) && playerPuller.Pulling != null &&
physicsQuery.TryGetComponent(playerPuller.Pulling, out var pulledBody))
{
pulledBody.Predict = false;
}
}
}
// Server-side should just be handled on its own so we'll just do this shizznit
HandleMobMovement(
player,

View File

@@ -1,3 +1,4 @@
using System.Linq;
using Content.Shared.GameTicking;
using Content.Shared.Popups;
using Robust.Client.Graphics;
@@ -122,6 +123,9 @@ namespace Content.Client.Popups
public override void PopupEntity(string message, EntityUid uid, Filter filter, bool recordReplay, PopupType type=PopupType.Small)
{
if (!filter.Recipients.Contains(_playerManager.LocalPlayer?.Session))
return;
PopupEntity(message, uid, type);
}

View File

@@ -3,12 +3,16 @@ namespace Content.Client.Rotation;
[RegisterComponent]
public sealed class RotationVisualsComponent : Component
{
public static readonly Angle DefaultRotation = Angle.FromDegrees(90);
[DataField("defaultRotation")]
[ViewVariables(VVAccess.ReadOnly)]
public readonly Angle DefaultRotation = Angle.FromDegrees(90);
[ViewVariables(VVAccess.ReadWrite)]
public Angle VerticalRotation = 0;
[ViewVariables(VVAccess.ReadWrite)] public Angle HorizontalRotation = DefaultRotation;
[DataField("horizontalRotation")]
[ViewVariables(VVAccess.ReadWrite)]
public Angle HorizontalRotation = Angle.FromDegrees(90);
[ViewVariables(VVAccess.ReadWrite)]
public float AnimationTime = 0.125f;

View File

@@ -1,9 +1,9 @@
using Content.Client.Clickable;
using Content.Client.Gameplay;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Physics;
using Robust.Client.State;
using Robust.Shared.Input;
using Robust.Shared.Map;
@@ -18,6 +18,7 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly InputSystem _inputSystem = default!;
[Dependency] private readonly PhysicsSystem _physics = default!;
public bool Enabled { get; set; }
@@ -34,6 +35,13 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
base.Initialize();
SubscribeNetworkEvent<PredictTetherEvent>(OnPredictTether);
SubscribeNetworkEvent<TetherGunToggleMessage>(OnTetherGun);
SubscribeLocalEvent<UpdateIsPredictedEvent>(OnUpdatePrediction);
}
private void OnUpdatePrediction(ref UpdateIsPredictedEvent ev)
{
if (ev.Uid == _dragging || ev.Uid == _tether)
ev.IsPredicted = true;
}
private void OnTetherGun(TetherGunToggleMessage ev)
@@ -43,22 +51,13 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
private void OnPredictTether(PredictTetherEvent ev)
{
if (_dragging != ev.Entity) return;
if (_dragging != ev.Entity || _tether == ev.Entity)
return;
var oldTether = _tether;
_tether = ev.Entity;
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
if (!TryComp<PhysicsComponent>(_dragging, out var body)) return;
body.Predict = true;
if (TryComp<PhysicsComponent>(_tether, out var tetherBody))
{
tetherBody.Predict = true;
}
_physics.UpdateIsPredicted(oldTether);
_physics.UpdateIsPredicted(_tether);
}
public override void Update(float frameTime)
@@ -102,13 +101,6 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
return;
}
body.Predict = true;
if (TryComp<PhysicsComponent>(_tether, out var tetherBody))
{
tetherBody.Predict = true;
}
if (_lastMousePosition.Value.Position.EqualsApprox(mousePos.Position)) return;
_lastMousePosition = mousePos;
@@ -123,10 +115,15 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
{
if (_dragging == null) return;
var oldDrag = _dragging;
var oldTether = _tether;
RaiseNetworkEvent(new StopTetherEvent());
_dragging = null;
_lastMousePosition = null;
_tether = null;
_physics.UpdateIsPredicted(oldDrag);
_physics.UpdateIsPredicted(oldTether);
}
private void StartDragging(EntityUid uid, MapCoordinates coordinates)
@@ -138,5 +135,8 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
Entity = _dragging!.Value,
Coordinates = coordinates,
});
_physics.UpdateIsPredicted(uid);
}
}

View File

@@ -1,16 +0,0 @@
namespace Content.Server.Actions.Events
{
public sealed class DisarmAttemptEvent : CancellableEntityEventArgs
{
public readonly EntityUid TargetUid;
public readonly EntityUid DisarmerUid;
public readonly EntityUid? TargetItemInHandUid;
public DisarmAttemptEvent(EntityUid targetUid, EntityUid disarmerUid, EntityUid? targetItemInHandUid = null)
{
TargetUid = targetUid;
DisarmerUid = disarmerUid;
TargetItemInHandUid = targetItemInHandUid;
}
}
}

View File

@@ -20,14 +20,18 @@ public sealed class FrezonProductionReaction : IGasReactionEffect
var efficiency = mixture.Temperature / Atmospherics.FrezonProductionMaxEfficiencyTemperature;
var loss = 1 - efficiency;
// How much the catalyst (N2) will allow us to produce
// Less N2 is required the more efficient it is.
var minimumN2 = (initialOxy + initialTrit) / (Atmospherics.FrezonProductionNitrogenRatio * efficiency);
var catalystLimit = initialN2 * (Atmospherics.FrezonProductionNitrogenRatio / efficiency);
var oxyLimit = Math.Min(initialOxy, catalystLimit) / Atmospherics.FrezonProductionTritRatio;
if (initialN2 < minimumN2)
return ReactionResult.NoReaction;
// Amount of tritium & oxygen that are reacting
var tritBurned = Math.Min(oxyLimit, initialTrit);
var oxyBurned = tritBurned * Atmospherics.FrezonProductionTritRatio;
var burnRatio = tritBurned / initialTrit;
var oxyConversion = initialOxy / Atmospherics.FrezonProductionConversionRate;
var tritConversion = initialTrit / Atmospherics.FrezonProductionConversionRate;
var oxyConversion = oxyBurned / Atmospherics.FrezonProductionConversionRate;
var tritConversion = tritBurned / Atmospherics.FrezonProductionConversionRate;
var total = oxyConversion + tritConversion;
mixture.AdjustMoles(Gas.Oxygen, -oxyConversion);

View File

@@ -181,7 +181,7 @@ namespace Content.Server.Body.Systems
if (effect.ShouldLog)
{
_adminLogger.Add(LogType.ReagentEffect, effect.LogImpact,
$"Metabolism effect {effect.GetType().Name:effect} of reagent {args.Reagent.LocalizedName:reagent} applied on entity {actualEntity:entity} at {Transform(actualEntity).Coordinates:coordinates}");
$"Metabolism effect {effect.GetType().Name:effect} of reagent {proto.LocalizedName:reagent} applied on entity {actualEntity:entity} at {Transform(actualEntity).Coordinates:coordinates}");
}
effect.Effect(args);

View File

@@ -100,8 +100,7 @@ public sealed class CardboardBoxSystem : SharedCardboardBoxSystem
RemComp<RelayInputMoverComponent>(component.Mover.Value);
}
var relay = EnsureComp<RelayInputMoverComponent>(args.Entity);
_mover.SetRelay(args.Entity, uid, relay);
_mover.SetRelay(args.Entity, uid);
component.Mover = args.Entity;
}

View File

@@ -1,25 +0,0 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
using Content.Shared.FixedPoint;
using Robust.Shared.Audio;
using Robust.Shared.Player;
namespace Content.Server.Chemistry.EntitySystems
{
public sealed class ChemicalReactionSystem : SharedChemicalReactionSystem
{
protected override void OnReaction(Solution solution, ReactionPrototype reaction, ReagentPrototype randomReagent, EntityUid owner, FixedPoint2 unitReactions)
{
base.OnReaction(solution, reaction, randomReagent, owner, unitReactions);
var coordinates = Transform(owner).Coordinates;
AdminLogger.Add(LogType.ChemicalReaction, reaction.Impact,
$"Chemical reaction {reaction.ID:reaction} occurred with strength {unitReactions:strength} on entity {ToPrettyString(owner):metabolizer} at {coordinates}");
SoundSystem.Play(reaction.Sound.GetSound(), Filter.Pvs(owner, entityManager:EntityManager), owner);
}
}
}

View File

@@ -34,7 +34,7 @@ public sealed class SolutionChangedEvent : EntityEventArgs
public sealed partial class SolutionContainerSystem : EntitySystem
{
[Dependency]
private readonly SharedChemicalReactionSystem _chemistrySystem = default!;
private readonly ChemicalReactionSystem _chemistrySystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;

View File

@@ -63,7 +63,7 @@ namespace Content.Server.Chemistry.EntitySystems
_physics.SetLinearDamping(physics, 0f);
_physics.SetAngularDamping(physics, 0f);
_throwing.TryThrow(vapor.Owner, dir, speed, user: user, pushbackRatio: 50f);
_throwing.TryThrow(vapor.Owner, dir, speed, user: user, pushbackRatio: ThrowingSystem.PushbackDefault * 10f);
var distance = (target.Position - vaporXform.WorldPosition).Length;
var time = (distance / physics.LinearVelocity.Length);

View File

@@ -23,13 +23,14 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
public override bool Condition(ReagentEffectArgs args)
{
if (Reagent == null)
Reagent = args.Reagent.ID;
var reagent = Reagent ?? args.Reagent?.ID;
if (reagent == null)
return true; // No condition to apply.
var quant = FixedPoint2.Zero;
if (args.Source != null && args.Source.ContainsReagent(Reagent))
if (args.Source != null && args.Source.ContainsReagent(reagent))
{
quant = args.Source.GetReagentQuantity(args.Reagent.ID);
quant = args.Source.GetReagentQuantity(reagent);
}
return quant >= Min && quant <= Max;

View File

@@ -14,7 +14,7 @@ namespace Content.Server.Chemistry.ReactionEffects
public float CleanseRate = 3.0f;
public override void Effect(ReagentEffectArgs args)
{
if (args.Source == null)
if (args.Source == null || args.Reagent == null)
return;
var cleanseRate = CleanseRate;

View File

@@ -18,9 +18,10 @@ public sealed class Electrocute : ReagentEffect
public override void Effect(ReagentEffectArgs args)
{
EntitySystem.Get<ElectrocutionSystem>().TryDoElectrocution(args.SolutionEntity, null,
args.EntityManager.System<ElectrocutionSystem>().TryDoElectrocution(args.SolutionEntity, null,
Math.Max((args.Quantity * ElectrocuteDamageScale).Int(), 1), TimeSpan.FromSeconds(ElectrocuteTime), Refresh, ignoreInsulation: true);
args.Source?.RemoveReagent(args.Reagent.ID, args.Quantity);
if (args.Reagent != null)
args.Source?.RemoveReagent(args.Reagent.ID, args.Quantity);
}
}

View File

@@ -17,10 +17,13 @@ namespace Content.Server.Chemistry.ReagentEffects
public override void Effect(ReagentEffectArgs args)
{
if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out FlammableComponent? flammable)) return;
if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out FlammableComponent? flammable))
return;
EntitySystem.Get<FlammableSystem>().AdjustFireStacks(args.SolutionEntity, args.Quantity.Float() * Multiplier, flammable);
args.Source?.RemoveReagent(args.Reagent.ID, args.Quantity);
args.EntityManager.System<FlammableSystem>().AdjustFireStacks(args.SolutionEntity, args.Quantity.Float() * Multiplier, flammable);
if (args.Reagent != null)
args.Source?.RemoveReagent(args.Reagent.ID, args.Quantity);
}
}
}

View File

@@ -1,7 +1,7 @@
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.MachineLinking.Components
namespace Content.Server.DeviceLinking.Components
{
[RegisterComponent]
public sealed class ActiveSignalTimerComponent : Component

View File

@@ -1,8 +1,6 @@
using Content.Server.MachineLinking.Events;
using Content.Shared.MachineLinking;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.MachineLinking.Components;
namespace Content.Server.DeviceLinking.Components;
[RegisterComponent]
public sealed class OrGateComponent : Component
@@ -26,3 +24,11 @@ public sealed class OrGateComponent : Component
[ViewVariables]
public SignalState LastO2 = SignalState.Low;
}
public enum SignalState
{
Momentary, // Instantaneous pulse high, compatibility behavior
Low,
High
}

View File

@@ -2,7 +2,7 @@ using Content.Shared.MachineLinking;
using Robust.Shared.Audio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.MachineLinking.Components;
namespace Content.Server.DeviceLinking.Components;
[RegisterComponent]
public sealed class SignalTimerComponent : Component

View File

@@ -1,8 +1,6 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceNetwork;
using Content.Server.Doors.Systems;
using Content.Server.MachineLinking.Events;
using Content.Server.MachineLinking.System;
using Content.Shared.Doors.Components;
using Content.Shared.Doors;
using JetBrains.Annotations;
@@ -17,8 +15,6 @@ namespace Content.Server.DeviceLinking.Systems
[Dependency] private readonly DoorSystem _doorSystem = default!;
[Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
private const string DoorSignalState = "DoorState";
public override void Initialize()
{
base.Initialize();
@@ -40,7 +36,7 @@ namespace Content.Server.DeviceLinking.Systems
return;
var state = SignalState.Momentary;
args.Data?.TryGetValue(DoorSignalState, out state);
args.Data?.TryGetValue(DeviceNetworkConstants.LogicState, out state);
if (args.Port == component.OpenPort)
@@ -85,12 +81,12 @@ namespace Content.Server.DeviceLinking.Systems
{
var data = new NetworkPayload()
{
{ DoorSignalState, SignalState.Momentary }
{ DeviceNetworkConstants.LogicState, SignalState.Momentary }
};
if (args.State == DoorState.Closed)
{
data[DoorSignalState] = SignalState.Low;
data[DeviceNetworkConstants.LogicState] = SignalState.Low;
_signalSystem.InvokePort(uid, door.OutOpen, data);
}
else if (args.State == DoorState.Open
@@ -98,7 +94,7 @@ namespace Content.Server.DeviceLinking.Systems
|| args.State == DoorState.Closing
|| args.State == DoorState.Emagging)
{
data[DoorSignalState] = SignalState.High;
data[DeviceNetworkConstants.LogicState] = SignalState.High;
_signalSystem.InvokePort(uid, door.OutOpen, data);
}
}

View File

@@ -0,0 +1,84 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceNetwork;
using Content.Server.MachineLinking.Events;
using JetBrains.Annotations;
using Robust.Shared.Utility;
using SignalReceivedEvent = Content.Server.DeviceLinking.Events.SignalReceivedEvent;
namespace Content.Server.DeviceLinking.Systems
{
[UsedImplicitly]
public sealed class OrGateSystem : EntitySystem
{
[Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<OrGateComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<OrGateComponent, SignalReceivedEvent>(OnSignalReceived);
}
private void OnInit(EntityUid uid, OrGateComponent component, ComponentInit args)
{
_signalSystem.EnsureSinkPorts(uid, "A1", "B1", "A2", "B2");
_signalSystem.EnsureSourcePorts(uid, "O1", "O2");
}
private void OnSignalReceived(EntityUid uid, OrGateComponent component, ref SignalReceivedEvent args)
{
var state = SignalState.Momentary;
args.Data?.TryGetValue(DeviceNetworkConstants.LogicState, out state);
switch (args.Port)
{
case "A1":
component.StateA1 = state;
break;
case "B1":
component.StateB1 = state;
break;
case "A2":
component.StateA2 = state;
break;
case "B2":
component.StateB2 = state;
break;
}
// O1 = A1 || B1
var v1 = SignalState.Low;
if (component.StateA1 == SignalState.High || component.StateB1 == SignalState.High)
v1 = SignalState.High;
if (v1 != component.LastO1)
{
var data = new NetworkPayload
{
[DeviceNetworkConstants.LogicState] = v1
};
_signalSystem.InvokePort(uid, "O1", data);
}
component.LastO1 = v1;
// O2 = A2 || B2
var v2 = SignalState.Low;
if (component.StateA2 == SignalState.High || component.StateB2 == SignalState.High)
v2 = SignalState.High;
if (v2 != component.LastO2)
{
var data = new NetworkPayload
{
[DeviceNetworkConstants.LogicState] = v2
};
_signalSystem.InvokePort(uid, "O2", data);
}
component.LastO2 = v2;
}
}
}

View File

@@ -1,19 +1,19 @@
using Robust.Shared.Timing;
using Content.Server.MachineLinking.Components;
using Content.Shared.TextScreen;
using Robust.Server.GameObjects;
using Content.Shared.MachineLinking;
using Content.Server.DeviceLinking.Components;
using Content.Server.Interaction;
using Content.Server.UserInterface;
using Content.Shared.Access.Systems;
using Content.Server.Interaction;
using Content.Shared.MachineLinking;
using Content.Shared.TextScreen;
using Robust.Server.GameObjects;
using Robust.Shared.Timing;
namespace Content.Server.MachineLinking.System;
namespace Content.Server.DeviceLinking.Systems;
public sealed class SignalTimerSystem : EntitySystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
[Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly AccessReaderSystem _accessReader = default!;

View File

@@ -8,6 +8,11 @@ namespace Content.Server.DeviceNetwork
/// </summary>
public static class DeviceNetworkConstants
{
/// <summary>
/// Used by logic gates to transmit the state of their ports
/// </summary>
public const string LogicState = "logic_state";
#region Commands
/// <summary>

View File

@@ -148,17 +148,19 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
return;
}
if (HasComp<DeviceLinkSourceComponent>(target) && HasComp<DeviceLinkSourceComponent>(configurator.ActiveDeviceLink)
|| HasComp<DeviceLinkSinkComponent>(target) && HasComp<DeviceLinkSinkComponent>(configurator.ActiveDeviceLink))
if (configurator.ActiveDeviceLink.HasValue
&& (HasComp<DeviceLinkSourceComponent>(target)
&& HasComp<DeviceLinkSinkComponent>(configurator.ActiveDeviceLink)
|| HasComp<DeviceLinkSinkComponent>(target)
&& HasComp<DeviceLinkSourceComponent>(configurator.ActiveDeviceLink)))
{
OpenDeviceLinkUi(uid, target, user, configurator);
return;
}
if (configurator.ActiveDeviceLink.HasValue)
{
OpenDeviceLinkUi( uid, target, user, configurator);
if (HasComp<DeviceLinkSourceComponent>(target) && HasComp<DeviceLinkSourceComponent>(configurator.ActiveDeviceLink)
|| HasComp<DeviceLinkSinkComponent>(target) && HasComp<DeviceLinkSinkComponent>(configurator.ActiveDeviceLink))
return;
}
_popupSystem.PopupEntity(Loc.GetString("network-configurator-link-mode-started", ("device", Name(target.Value))), target.Value, user);
configurator.ActiveDeviceLink = target;
@@ -498,15 +500,31 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
if (!configurator.ActiveDeviceLink.HasValue || !configurator.DeviceLinkTarget.HasValue)
return;
if (HasComp<DeviceLinkSourceComponent>(configurator.ActiveDeviceLink))
if (HasComp<DeviceLinkSourceComponent>(configurator.ActiveDeviceLink) && HasComp<DeviceLinkSinkComponent>(configurator.DeviceLinkTarget))
{
_deviceLinkSystem.RemoveSinkFromSource(configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value);
UpdateLinkUiState(uid, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value);
_deviceLinkSystem.RemoveSinkFromSource(
configurator.ActiveDeviceLink.Value,
configurator.DeviceLinkTarget.Value
);
UpdateLinkUiState(
uid,
configurator.ActiveDeviceLink.Value,
configurator.DeviceLinkTarget.Value
);
}
else if (HasComp<DeviceLinkSourceComponent>(configurator.DeviceLinkTarget))
else if (HasComp<DeviceLinkSourceComponent>(configurator.DeviceLinkTarget) && HasComp<DeviceLinkSinkComponent>(configurator.ActiveDeviceLink))
{
_deviceLinkSystem.RemoveSinkFromSource(configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value);
UpdateLinkUiState(uid, configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value);
_deviceLinkSystem.RemoveSinkFromSource(
configurator.DeviceLinkTarget.Value,
configurator.ActiveDeviceLink.Value
);
UpdateLinkUiState(
uid,
configurator.DeviceLinkTarget.Value,
configurator.ActiveDeviceLink.Value
);
}
}
@@ -515,15 +533,33 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
if (!configurator.ActiveDeviceLink.HasValue || !configurator.DeviceLinkTarget.HasValue)
return;
if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource))
if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource) && TryComp(configurator.DeviceLinkTarget, out DeviceLinkSinkComponent? targetSink))
{
_deviceLinkSystem.ToggleLink(args.Session.AttachedEntity, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value, args.Source, args.Sink, activeSource);
_deviceLinkSystem.ToggleLink(
args.Session.AttachedEntity,
configurator.ActiveDeviceLink.Value,
configurator.DeviceLinkTarget.Value,
args.Source, args.Sink,
activeSource, targetSink);
UpdateLinkUiState(uid, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value, activeSource);
}
else if (TryComp(configurator.DeviceLinkTarget, out DeviceLinkSourceComponent? targetSource))
else if (TryComp(configurator.DeviceLinkTarget, out DeviceLinkSourceComponent? targetSource) && TryComp(configurator.ActiveDeviceLink, out DeviceLinkSinkComponent? activeSink))
{
_deviceLinkSystem.ToggleLink(args.Session.AttachedEntity, configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value, args.Source, args.Sink, targetSource);
UpdateLinkUiState(uid, configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value, targetSource);
_deviceLinkSystem.ToggleLink(
args.Session.AttachedEntity,
configurator.DeviceLinkTarget.Value,
configurator.ActiveDeviceLink.Value,
args.Source, args.Sink,
targetSource, activeSink
);
UpdateLinkUiState(
uid,
configurator.DeviceLinkTarget.Value,
configurator.ActiveDeviceLink.Value,
targetSource
);
}
}
@@ -535,15 +571,41 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
if (!configurator.ActiveDeviceLink.HasValue || !configurator.DeviceLinkTarget.HasValue)
return;
if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource))
if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource) && TryComp(configurator.DeviceLinkTarget, out DeviceLinkSinkComponent? targetSink))
{
_deviceLinkSystem.SaveLinks(args.Session.AttachedEntity, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value, args.Links, activeSource);
UpdateLinkUiState(uid, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value, activeSource);
_deviceLinkSystem.SaveLinks(
args.Session.AttachedEntity,
configurator.ActiveDeviceLink.Value,
configurator.DeviceLinkTarget.Value,
args.Links,
activeSource,
targetSink
);
UpdateLinkUiState(
uid,
configurator.ActiveDeviceLink.Value,
configurator.DeviceLinkTarget.Value,
activeSource
);
}
else if (TryComp(configurator.DeviceLinkTarget, out DeviceLinkSourceComponent? targetSource))
else if (TryComp(configurator.DeviceLinkTarget, out DeviceLinkSourceComponent? targetSource) && TryComp(configurator.ActiveDeviceLink, out DeviceLinkSinkComponent? activeSink))
{
_deviceLinkSystem.SaveLinks(args.Session.AttachedEntity, configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value, args.Links, targetSource);
UpdateLinkUiState(uid, configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value, targetSource);
_deviceLinkSystem.SaveLinks(
args.Session.AttachedEntity,
configurator.DeviceLinkTarget.Value,
configurator.ActiveDeviceLink.Value,
args.Links,
targetSource,
activeSink
);
UpdateLinkUiState(
uid,
configurator.DeviceLinkTarget.Value,
configurator.ActiveDeviceLink.Value,
targetSource
);
}
}
@@ -585,7 +647,7 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
_popupSystem.PopupCursor(Loc.GetString(resultText), args.Session, PopupType.Medium);
_uiSystem.TrySetUiState(
component.Owner,
uid,
NetworkConfiguratorUiKey.Configure,
new DeviceListUserInterfaceState(
_deviceListSystem.GetDeviceList(component.ActiveDeviceList.Value)

View File

@@ -103,6 +103,9 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem
if (!_solutionSystem.TryGetSolution(used, AbsorbentComponent.SolutionName, out var absorberSoln))
return;
if (_useDelay.ActiveDelay(used))
return;
// If it's a puddle try to grab from
if (!TryPuddleInteract(user, used, target, component, absorberSoln))
{

View File

@@ -255,7 +255,8 @@ public sealed partial class GameTicker
private CompletionResult EndGameRuleCompletions(IConsoleShell shell, string[] args)
{
return CompletionResult.FromHintOptions(GetAddedGameRules().Select(u => u.ToString()), "<added rule>");
var opts = GetAddedGameRules().Select(ent => new CompletionOption(ent.ToString(), ToPrettyString(ent))).ToList();
return CompletionResult.FromHintOptions(opts, "<added rule>");
}
[AdminCommand(AdminFlags.Fun)]

View File

@@ -255,7 +255,7 @@ namespace Content.Server.GameTicking
return;
}
_sawmill.Warning($"Exception caught while trying to start the round! Restarting round...");
_sawmill.Error($"Exception caught while trying to start the round! Restarting round...");
_runtimeLog.LogException(e, nameof(GameTicker));
_startingRound = false;
RestartRound();

View File

@@ -18,13 +18,13 @@ public sealed class NukeopsRuleComponent : Component
/// The minimum needed amount of players
/// </summary>
[DataField("minPlayers")]
public int MinPlayers = 15;
public int MinPlayers = 20;
/// <summary>
/// This INCLUDES the operatives. So a value of 3 is satisfied by 2 players & 1 operative
/// </summary>
[DataField("playersPerOperative")]
public int PlayersPerOperative = 5;
public int PlayersPerOperative = 10;
[DataField("maxOps")]
public int MaxOperatives = 5;

View File

@@ -0,0 +1,10 @@
namespace Content.Server.Gatherable.Components;
/// <summary>
/// Destroys a gatherable entity when colliding with it.
/// </summary>
[RegisterComponent]
public sealed class GatheringProjectileComponent : Component
{
}

View File

@@ -0,0 +1,28 @@
using Content.Server.Gatherable.Components;
using Content.Server.Projectiles;
using Content.Shared.Projectiles;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Shared.Physics.Events;
namespace Content.Server.Gatherable;
public sealed partial class GatherableSystem
{
private void InitializeProjectile()
{
SubscribeLocalEvent<GatheringProjectileComponent, StartCollideEvent>(OnProjectileCollide);
}
private void OnProjectileCollide(EntityUid uid, GatheringProjectileComponent component, ref StartCollideEvent args)
{
if (!args.OtherFixture.Hard ||
args.OurFixture.ID != SharedProjectileSystem.ProjectileFixture ||
!TryComp<GatherableComponent>(args.OtherEntity, out var gatherable))
{
return;
}
Gather(args.OtherEntity, uid, gatherable);
QueueDel(uid);
}
}

View File

@@ -5,18 +5,20 @@ using Content.Shared.EntityList;
using Content.Shared.Gatherable;
using Content.Shared.Interaction;
using Content.Shared.Tag;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Gatherable;
public sealed class GatherableSystem : EntitySystem
public sealed partial class GatherableSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly DestructibleSystem _destructible = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
public override void Initialize()
@@ -25,6 +27,7 @@ public sealed class GatherableSystem : EntitySystem
SubscribeLocalEvent<GatherableComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<GatherableComponent, GatherableDoAfterEvent>(OnDoAfter);
InitializeProjectile();
}
private void OnInteractUsing(EntityUid uid, GatherableComponent component, InteractUsingEvent args)
@@ -53,36 +56,44 @@ public sealed class GatherableSystem : EntitySystem
private void OnDoAfter(EntityUid uid, GatherableComponent component, GatherableDoAfterEvent args)
{
if(!TryComp<GatheringToolComponent>(args.Args.Used, out var tool) || args.Args.Target == null)
if(!TryComp<GatheringToolComponent>(args.Args.Used, out var tool))
return;
tool.GatheringEntities.Remove(args.Args.Target.Value);
tool.GatheringEntities.Remove(uid);
if (args.Handled || args.Cancelled)
return;
Gather(uid, args.Args.Used, component, tool.GatheringSound);
args.Handled = true;
}
public void Gather(EntityUid gatheredUid, EntityUid? gatherer = null, GatherableComponent? component = null, SoundSpecifier? sound = null)
{
if (!Resolve(gatheredUid, ref component))
return;
// Complete the gathering process
_destructible.DestroyEntity(args.Args.Target.Value);
_audio.PlayPvs(tool.GatheringSound, args.Args.Target.Value);
_destructible.DestroyEntity(gatheredUid);
_audio.PlayPvs(sound, gatheredUid);
// Spawn the loot!
if (component.MappedLoot == null)
return;
var playerPos = Transform(args.Args.User).MapPosition;
var pos = Transform(gatheredUid).MapPosition;
foreach (var (tag, table) in component.MappedLoot)
{
if (tag != "All")
{
if (!_tagSystem.HasTag(tool.Owner, tag))
if (gatherer != null && !_tagSystem.HasTag(gatherer.Value, tag))
continue;
}
var getLoot = _prototypeManager.Index<EntityLootTablePrototype>(table);
var spawnLoot = getLoot.GetSpawns();
var spawnPos = playerPos.Offset(_random.NextVector2(0.3f));
var spawnPos = pos.Offset(_random.NextVector2(0.3f));
Spawn(spawnLoot[0], spawnPos);
}
args.Handled = true;
}
}

View File

@@ -1,3 +1,4 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.MachineLinking.Events;
using Content.Server.MachineLinking.System;

View File

@@ -1,12 +1,7 @@
using Content.Server.DeviceLinking.Components;
namespace Content.Server.MachineLinking.Events
{
public enum SignalState
{
Momentary, // Instantaneous pulse high, compatibility behavior
Low,
High
}
public sealed class SignalReceivedEvent : EntityEventArgs
{
public readonly string Port;

View File

@@ -1,63 +0,0 @@
using Content.Server.MachineLinking.Components;
using Content.Server.MachineLinking.Events;
using JetBrains.Annotations;
namespace Content.Server.MachineLinking.System
{
[UsedImplicitly]
public sealed class OrGateSystem : EntitySystem
{
[Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<OrGateComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<OrGateComponent, SignalReceivedEvent>(OnSignalReceived);
}
private void OnInit(EntityUid uid, OrGateComponent component, ComponentInit args)
{
_signalSystem.EnsureReceiverPorts(uid, "A1", "B1", "A2", "B2");
_signalSystem.EnsureTransmitterPorts(uid, "O1", "O2");
}
private void OnSignalReceived(EntityUid uid, OrGateComponent component, SignalReceivedEvent args)
{
if (args.Port == "A1")
{
component.StateA1 = args.State;
}
else if (args.Port == "B1")
{
component.StateB1 = args.State;
}
else if (args.Port == "A2")
{
component.StateA2 = args.State;
}
else if (args.Port == "B2")
{
component.StateB2 = args.State;
}
// O1 = A1 || B1
var v1 = SignalState.Low;
if (component.StateA1 == SignalState.High || component.StateB1 == SignalState.High)
v1 = SignalState.High;
if (v1 != component.LastO1)
_signalSystem.InvokePort(uid, "O1", v1);
component.LastO1 = v1;
// O2 = A2 || B2
var v2 = SignalState.Low;
if (component.StateA2 == SignalState.High || component.StateB2 == SignalState.High)
v2 = SignalState.High;
if (v2 != component.LastO2)
_signalSystem.InvokePort(uid, "O2", v2);
component.LastO2 = v2;
}
}
}

View File

@@ -1,5 +1,6 @@
using System.Linq;
using System.Diagnostics.CodeAnalysis;
using Content.Server.DeviceLinking.Components;
using Content.Server.MachineLinking.Components;
using Content.Server.MachineLinking.Events;
using Content.Server.Power.Components;

View File

@@ -21,6 +21,7 @@ namespace Content.Server.Nutrition.Components
internal bool DefaultToOpened;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("transferAmount")]
public FixedPoint2 TransferAmount { get; [UsedImplicitly] private set; } = FixedPoint2.New(5);
[ViewVariables(VVAccess.ReadWrite)]

View File

@@ -155,7 +155,7 @@ namespace Content.Server.Nutrition.EntitySystems
if (component.Pressurized &&
!component.Opened &&
_random.Prob(0.25f) &&
_solutionContainerSystem.TryGetDrainableSolution(uid, out var interactions))
_solutionContainerSystem.TryGetSolution(uid, component.SolutionName, out var interactions))
{
component.Opened = true;
UpdateAppearance(component);
@@ -229,7 +229,7 @@ namespace Content.Server.Nutrition.EntitySystems
return true;
}
if (!_solutionContainerSystem.TryGetDrainableSolution(item, out var drinkSolution) ||
if (!_solutionContainerSystem.TryGetSolution(item, drink.SolutionName, out var drinkSolution) ||
drinkSolution.Volume <= 0)
{
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-is-empty",

View File

@@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Content.Server.Database;
@@ -350,6 +351,19 @@ public sealed class PlayTimeTrackingManager
return GetPlayTimeForTracker(id, PlayTimeTrackingShared.TrackerOverall);
}
public bool TryGetTrackerTimes(IPlayerSession id, [NotNullWhen(true)] out Dictionary<string, TimeSpan>? time)
{
time = null;
if (!_playTimeData.TryGetValue(id, out var data) || !data.Initialized)
{
return false;
}
time = data.TrackerTimes;
return true;
}
public Dictionary<string, TimeSpan> GetTrackerTimes(IPlayerSession id)
{
if (!_playTimeData.TryGetValue(id, out var data) || !data.Initialized)

View File

@@ -201,7 +201,12 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
return;
var player = _playerManager.GetSessionByUserId(userId);
var playTimes = _tracking.GetTrackerTimes(player);
if (!_tracking.TryGetTrackerTimes(player, out var playTimes))
{
// Sorry mate but your playtimes haven't loaded.
Logger.ErrorS("playtime", $"Playtimes weren't ready yet for {player} on roundstart!");
playTimes ??= new Dictionary<string, TimeSpan>();
}
for (var i = 0; i < jobs.Count; i++)
{

View File

@@ -22,7 +22,7 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
SubscribeLocalEvent<HeadsetComponent, EncryptionChannelsChangedEvent>(OnKeysChanged);
SubscribeLocalEvent<WearingHeadsetComponent, EntitySpokeEvent>(OnSpeak);
SubscribeLocalEvent<HeadsetComponent, EmpPulseEvent>(OnEmpPulse);
}
@@ -33,7 +33,8 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
private void UpdateRadioChannels(EntityUid uid, HeadsetComponent headset, EncryptionKeyHolderComponent? keyHolder = null)
{
if (!headset.Enabled)
// make sure to not add ActiveRadioComponent when headset is being deleted
if (!headset.Enabled || MetaData(uid).EntityLifeStage >= EntityLifeStage.Terminating)
return;
if (!Resolve(uid, ref keyHolder))

View File

@@ -1,14 +1,62 @@
using Content.Server.StationEvents.Events;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Audio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Server.StationEvents.Components;
[RegisterComponent, Access(typeof(VentClogRule))]
public sealed class VentClogRuleComponent : Component
{
[DataField("safeishVentChemicals")]
/// <summary>
/// Somewhat safe chemicals to put in foam that probably won't instantly kill you.
/// There is a small chance of using any reagent, ignoring this.
/// </summary>
[DataField("safeishVentChemicals", customTypeSerializer: typeof(PrototypeIdListSerializer<ReagentPrototype>))]
public readonly IReadOnlyList<string> SafeishVentChemicals = new[]
{
"Water", "Blood", "Slime", "SpaceDrugs", "SpaceCleaner", "Nutriment", "Sugar", "SpaceLube", "Ephedrine", "Ale", "Beer"
};
/// <summary>
/// Sound played when foam is being created.
/// </summary>
[DataField("sound")]
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/extinguish.ogg");
/// <summary>
/// The standard reagent quantity to put in the foam, modfied by event severity.
/// </summary>
[DataField("reagentQuantity"), ViewVariables(VVAccess.ReadWrite)]
public int ReagentQuantity = 200;
/// <summary>
/// The standard spreading of the foam, not modfied by event severity.
/// </summary>
[DataField("spread"), ViewVariables(VVAccess.ReadWrite)]
public int Spread = 20;
/// <summary>
/// How long the foam lasts for
/// </summary>
[DataField("time"), ViewVariables(VVAccess.ReadWrite)]
public float Time = 20f;
/// <summary>
/// Reagents that gets the weak numbers used instead of regular ones.
/// </summary>
[DataField("weakReagents", customTypeSerializer: typeof(PrototypeIdListSerializer<ReagentPrototype>))]
public IReadOnlyList<string> WeakReagents = new[] { "SpaceLube" };
/// <summary>
/// Quantity of weak reagents to put in the foam.
/// </summary>
[DataField("weakReagentQuantity"), ViewVariables(VVAccess.ReadWrite)]
public int WeakReagentQuantity = 60;
/// <summary>
/// Spread of the foam for weak reagents.
/// </summary>
[DataField("weakSpread"), ViewVariables(VVAccess.ReadWrite)]
public int WeakSpread = 2;
}

View File

@@ -31,8 +31,7 @@ public sealed class VentClogRule : StationEventSystem<VentClogRuleComponent>
.Where(x => !x.Abstract)
.Select(x => x.ID).ToList();
// This is gross, but not much can be done until event refactor, which needs Dynamic.
var sound = new SoundPathSpecifier("/Audio/Effects/extinguish.ogg");
// TODO: This is gross, but not much can be done until event refactor, which needs Dynamic.
var mod = (float) Math.Sqrt(GetSeverityModifier());
foreach (var (_, transform) in EntityManager.EntityQuery<GasVentPumpComponent, TransformComponent>())
@@ -47,20 +46,18 @@ public sealed class VentClogRule : StationEventSystem<VentClogRuleComponent>
if (!RobustRandom.Prob(Math.Min(0.33f * mod, 1.0f)))
continue;
if (RobustRandom.Prob(Math.Min(0.05f * mod, 1.0f)))
{
solution.AddReagent(RobustRandom.Pick(allReagents), 200);
}
else
{
solution.AddReagent(RobustRandom.Pick(component.SafeishVentChemicals), 200);
}
var pickAny = RobustRandom.Prob(Math.Min(0.05f * mod, 1.0f));
var reagent = RobustRandom.Pick(pickAny ? allReagents : component.SafeishVentChemicals);
var weak = component.WeakReagents.Contains(reagent);
var quantity = (weak ? component.WeakReagentQuantity : component.ReagentQuantity) * mod;
solution.AddReagent(reagent, quantity);
var foamEnt = Spawn("Foam", transform.Coordinates);
var smoke = EnsureComp<SmokeComponent>(foamEnt);
smoke.SpreadAmount = 20;
_smoke.Start(foamEnt, smoke, solution, 20f);
Audio.PlayPvs(sound, transform.Coordinates);
smoke.SpreadAmount = weak ? component.WeakSpread : component.Spread;
_smoke.Start(foamEnt, smoke, solution, component.Time);
Audio.PlayPvs(component.Sound, transform.Coordinates);
}
}
}

View File

@@ -1,4 +1,6 @@
using Content.Server.GameTicking;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Traits;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
@@ -9,6 +11,7 @@ public sealed class TraitSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ISerializationManager _serializationManager = default!;
[Dependency] private readonly SharedHandsSystem _sharedHandsSystem = default!;
public override void Initialize()
{
@@ -42,6 +45,18 @@ public sealed class TraitSystem : EntitySystem
comp.Owner = args.Mob;
EntityManager.AddComponent(args.Mob, comp);
}
// Add item required by the trait
if (traitPrototype.TraitGear != null)
{
if (!TryComp(args.Mob, out HandsComponent? handsComponent))
continue;
var coords = Transform(args.Mob).Coordinates;
var inhandEntity = EntityManager.SpawnEntity(traitPrototype.TraitGear, coords);
_sharedHandsSystem.TryPickup(args.Mob, inhandEntity, checkActionBlocker: false,
handsComp: handsComponent);
}
}
}
}

View File

@@ -1,5 +1,4 @@
using System.Linq;
using Content.Server.Actions.Events;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Chemistry.Components;
@@ -8,6 +7,7 @@ using Content.Server.CombatMode.Disarm;
using Content.Server.Contests;
using Content.Server.Examine;
using Content.Server.Movement.Systems;
using Content.Shared.Actions.Events;
using Content.Shared.Administration.Components;
using Content.Shared.CombatMode;
using Content.Shared.Damage;
@@ -25,7 +25,6 @@ using Content.Shared.Weapons.Melee.Events;
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Random;

View File

@@ -83,7 +83,7 @@ public sealed partial class GunSystem : SharedGunSystem
// Apply salt to the wound ("Honk!")
Audio.PlayPvs(new SoundPathSpecifier("/Audio/Weapons/Guns/Gunshots/bang.ogg"), gunUid);
Audio.PlayPvs(new SoundPathSpecifier("/Audio/Items/bikehorn.ogg"), gunUid);
Audio.PlayPvs(clumsy.ClumsySound, gunUid);
PopupSystem.PopupEntity(Loc.GetString("gun-clumsy"), user.Value);
_adminLogger.Add(LogType.EntityDelete, LogImpact.Medium, $"Clumsy fire by {ToPrettyString(user.Value)} deleted {ToPrettyString(gunUid)}");

View File

@@ -1,11 +0,0 @@
using Content.Shared.Damage;
namespace Content.Server.Wieldable.Components
{
[RegisterComponent, Access(typeof(WieldableSystem))]
public sealed class IncreaseDamageOnWieldComponent : Component
{
[DataField("damage", required: true)]
public DamageSpecifier BonusDamage = default!;
}
}

View File

@@ -1,34 +0,0 @@
using Robust.Shared.Audio;
namespace Content.Server.Wieldable.Components
{
/// <summary>
/// Used for objects that can be wielded in two or more hands,
/// </summary>
[RegisterComponent, Access(typeof(WieldableSystem))]
public sealed class WieldableComponent : Component
{
[DataField("wieldSound")]
public SoundSpecifier? WieldSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg");
[DataField("unwieldSound")]
public SoundSpecifier? UnwieldSound;
/// <summary>
/// Number of free hands required (excluding the item itself) required
/// to wield it
/// </summary>
[DataField("freeHandsRequired")]
public int FreeHandsRequired = 1;
public bool Wielded = false;
[DataField("wieldedInhandPrefix")]
public string WieldedInhandPrefix = "wielded";
public string? OldInhandPrefix = null;
[DataField("wieldTime")]
public float WieldTime = 1.5f;
}
}

View File

@@ -1,263 +0,0 @@
using Content.Server.Actions.Events;
using Content.Server.Hands.Systems;
using Content.Server.Wieldable.Components;
using Content.Shared.DoAfter;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Wieldable;
using Robust.Shared.Player;
namespace Content.Server.Wieldable
{
public sealed class WieldableSystem : EntitySystem
{
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly HandVirtualItemSystem _virtualItemSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly SharedItemSystem _itemSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<WieldableComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<WieldableComponent, WieldableDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<WieldableComponent, ItemUnwieldedEvent>(OnItemUnwielded);
SubscribeLocalEvent<WieldableComponent, GotUnequippedHandEvent>(OnItemLeaveHand);
SubscribeLocalEvent<WieldableComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
SubscribeLocalEvent<WieldableComponent, GetVerbsEvent<InteractionVerb>>(AddToggleWieldVerb);
SubscribeLocalEvent<WieldableComponent, DisarmAttemptEvent>(OnDisarmAttemptEvent);
SubscribeLocalEvent<IncreaseDamageOnWieldComponent, MeleeHitEvent>(OnMeleeHit);
}
private void OnDisarmAttemptEvent(EntityUid uid, WieldableComponent component, DisarmAttemptEvent args)
{
if (component.Wielded)
args.Cancel();
}
private void AddToggleWieldVerb(EntityUid uid, WieldableComponent component, GetVerbsEvent<InteractionVerb> args)
{
if (args.Hands == null || !args.CanAccess || !args.CanInteract)
return;
if (!_handsSystem.IsHolding(args.User, uid, out _, args.Hands))
return;
// TODO VERB TOOLTIPS Make CanWield or some other function return string, set as verb tooltip and disable
// verb. Or just don't add it to the list if the action is not executable.
// TODO VERBS ICON + localization
InteractionVerb verb = new()
{
Text = component.Wielded ? Loc.GetString("wieldable-verb-text-unwield") : Loc.GetString("wieldable-verb-text-wield"),
Act = component.Wielded
? () => AttemptUnwield(component.Owner, component, args.User)
: () => AttemptWield(component.Owner, component, args.User)
};
args.Verbs.Add(verb);
}
private void OnUseInHand(EntityUid uid, WieldableComponent component, UseInHandEvent args)
{
if (args.Handled)
return;
if(!component.Wielded)
AttemptWield(uid, component, args.User);
else
AttemptUnwield(uid, component, args.User);
}
public bool CanWield(EntityUid uid, WieldableComponent component, EntityUid user, bool quiet=false)
{
// Do they have enough hands free?
if (!EntityManager.TryGetComponent<HandsComponent>(user, out var hands))
{
if(!quiet)
_popupSystem.PopupEntity(Loc.GetString("wieldable-component-no-hands"), user, user);
return false;
}
// Is it.. actually in one of their hands?
if (!_handsSystem.IsHolding(user, uid, out _, hands))
{
if (!quiet)
_popupSystem.PopupEntity(Loc.GetString("wieldable-component-not-in-hands", ("item", uid)), user, user);
return false;
}
if (hands.CountFreeHands() < component.FreeHandsRequired)
{
if (!quiet)
{
var message = Loc.GetString("wieldable-component-not-enough-free-hands",
("number", component.FreeHandsRequired), ("item", uid));
_popupSystem.PopupEntity(message, user, user);
}
return false;
}
// Seems legit.
return true;
}
/// <summary>
/// Attempts to wield an item, creating a DoAfter..
/// </summary>
public void AttemptWield(EntityUid used, WieldableComponent component, EntityUid user)
{
if (!CanWield(used, component, user))
return;
var ev = new BeforeWieldEvent();
RaiseLocalEvent(used, ev);
if (ev.Cancelled)
return;
var doargs = new DoAfterArgs(user, component.WieldTime, new WieldableDoAfterEvent(), used, used: used)
{
BreakOnUserMove = false,
BreakOnDamage = true
};
_doAfter.TryStartDoAfter(doargs);
}
/// <summary>
/// Attempts to unwield an item, with no DoAfter.
/// </summary>
public void AttemptUnwield(EntityUid used, WieldableComponent component, EntityUid user)
{
var ev = new BeforeUnwieldEvent();
RaiseLocalEvent(used, ev);
if (ev.Cancelled)
return;
var targEv = new ItemUnwieldedEvent(user);
RaiseLocalEvent(used, targEv);
}
private void OnDoAfter(EntityUid uid, WieldableComponent component, DoAfterEvent args)
{
if (args.Handled || args.Cancelled || !CanWield(uid, component, args.Args.User) || component.Wielded)
return;
if (TryComp<ItemComponent>(uid, out var item))
{
component.OldInhandPrefix = item.HeldPrefix;
_itemSystem.SetHeldPrefix(uid, component.WieldedInhandPrefix, item);
}
component.Wielded = true;
if (component.WieldSound != null)
_audioSystem.PlayPvs(component.WieldSound, uid);
for (int i = 0; i < component.FreeHandsRequired; i++)
{
_virtualItemSystem.TrySpawnVirtualItemInHand(uid, args.Args.User);
}
_popupSystem.PopupEntity(Loc.GetString("wieldable-component-successful-wield", ("item", uid)), args.Args.User, args.Args.User);
_popupSystem.PopupEntity(Loc.GetString("wieldable-component-successful-wield-other", ("user", args.Args.User),("item", uid)), args.Args.User, Filter.PvsExcept(args.Args.User), true);
args.Handled = true;
}
private void OnItemUnwielded(EntityUid uid, WieldableComponent component, ItemUnwieldedEvent args)
{
if (args.User == null)
return;
if (!component.Wielded)
return;
if (TryComp<ItemComponent>(uid, out var item))
{
_itemSystem.SetHeldPrefix(uid, component.OldInhandPrefix, item);
}
component.Wielded = false;
if (!args.Force) // don't play sound/popup if this was a forced unwield
{
if (component.UnwieldSound != null)
_audioSystem.PlayPvs(component.UnwieldSound, uid);
_popupSystem.PopupEntity(Loc.GetString("wieldable-component-failed-wield",
("item", uid)), args.User.Value, args.User.Value);
_popupSystem.PopupEntity(Loc.GetString("wieldable-component-failed-wield-other",
("user", args.User.Value), ("item", uid)), args.User.Value, Filter.PvsExcept(args.User.Value), true);
}
_virtualItemSystem.DeleteInHandsMatching(args.User.Value, uid);
}
private void OnItemLeaveHand(EntityUid uid, WieldableComponent component, GotUnequippedHandEvent args)
{
if (!component.Wielded || component.Owner != args.Unequipped)
return;
RaiseLocalEvent(uid, new ItemUnwieldedEvent(args.User, force: true), true);
}
private void OnVirtualItemDeleted(EntityUid uid, WieldableComponent component, VirtualItemDeletedEvent args)
{
if (args.BlockingEntity == uid && component.Wielded)
AttemptUnwield(args.BlockingEntity, component, args.User);
}
private void OnMeleeHit(EntityUid uid, IncreaseDamageOnWieldComponent component, MeleeHitEvent args)
{
if (EntityManager.TryGetComponent<WieldableComponent>(uid, out var wield))
{
if (!wield.Wielded)
return;
}
if (args.Handled)
return;
args.BonusDamage += component.BonusDamage;
}
}
#region Events
public sealed class BeforeWieldEvent : CancellableEntityEventArgs
{
}
public sealed class BeforeUnwieldEvent : CancellableEntityEventArgs
{
}
/// <summary>
/// Raised on the item that has been unwielded.
/// </summary>
public sealed class ItemUnwieldedEvent : EntityEventArgs
{
public EntityUid? User;
/// <summary>
/// Whether the item is being forced to be unwielded, or if the player chose to unwield it themselves.
/// </summary>
public bool Force;
public ItemUnwieldedEvent(EntityUid? user = null, bool force=false)
{
User = user;
Force = force;
}
}
#endregion
}

View File

@@ -0,0 +1,15 @@
namespace Content.Shared.Actions.Events;
public sealed class DisarmAttemptEvent : CancellableEntityEventArgs
{
public readonly EntityUid TargetUid;
public readonly EntityUid DisarmerUid;
public readonly EntityUid? TargetItemInHandUid;
public DisarmAttemptEvent(EntityUid targetUid, EntityUid disarmerUid, EntityUid? targetItemInHandUid = null)
{
TargetUid = targetUid;
DisarmerUid = disarmerUid;
TargetItemInHandUid = targetItemInHandUid;
}
}

View File

@@ -225,6 +225,14 @@ namespace Content.Shared.Atmos
/// </summary>
public const float FrezonProductionNitrogenRatio = 10f;
/// <summary>
/// 1 mol of Tritium is required per X mol of oxygen.
/// </summary>
public const float FrezonProductionTritRatio = 50.0f;
/// <summary>
/// 1 / X of the tritium is converted into Frezon each tick
/// </summary>
public const float FrezonProductionConversionRate = 50f;
/// <summary>

View File

@@ -114,6 +114,10 @@ namespace Content.Shared.Chemistry.Components
return _heatCapacity;
}
public float GetThermalEnergy(IPrototypeManager? protoMan)
{
return GetHeatCapacity(protoMan) * Temperature;
}
/// <summary>
/// Constructs an empty solution (ex. an empty beaker).

View File

@@ -1,27 +1,23 @@
using System.Linq;
using Content.Shared.Administration;
using Content.Shared.Administration.Logs;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction.Events;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Shared.Chemistry.Reaction
{
public abstract class SharedChemicalReactionSystem : EntitySystem
public sealed class ChemicalReactionSystem : EntitySystem
{
/// <summary>
/// The maximum number of reactions that may occur when a solution is changed.
/// </summary>
private const int MaxReactionIterations = 20;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] protected readonly ISharedAdminLogManager AdminLogger = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
/// <summary>
/// A cache of all existant chemical reactions indexed by one of their
@@ -170,13 +166,12 @@ namespace Content.Shared.Chemistry.Reaction
/// <summary>
/// Perform a reaction on a solution. This assumes all reaction criteria are met.
/// Removes the reactants from the solution, then returns a solution with all products.
/// Removes the reactants from the solution, adds products, and returns a list of products.
/// </summary>
private Solution PerformReaction(Solution solution, EntityUid owner, ReactionPrototype reaction, FixedPoint2 unitReactions)
private List<string> PerformReaction(Solution solution, EntityUid owner, ReactionPrototype reaction, FixedPoint2 unitReactions)
{
// We do this so that ReagentEffect can have something to work with, even if it's
// a little meaningless.
var randomReagent = _prototypeManager.Index<ReagentPrototype>(_random.Pick(reaction.Reactants).Key);
var energy = reaction.ConserveEnergy ? solution.GetThermalEnergy(_prototypeManager) : 0;
//Remove reactants
foreach (var reactant in reaction.Reactants)
{
@@ -188,24 +183,35 @@ namespace Content.Shared.Chemistry.Reaction
}
//Create products
var products = new Solution();
var products = new List<string>();
foreach (var product in reaction.Products)
{
products.AddReagent(product.Key, product.Value * unitReactions);
products.Add(product.Key);
solution.AddReagent(product.Key, product.Value * unitReactions);
}
// Trigger reaction effects
OnReaction(solution, reaction, randomReagent, owner, unitReactions);
if (reaction.ConserveEnergy)
{
var newCap = solution.GetHeatCapacity(_prototypeManager);
if (newCap > 0)
solution.Temperature = energy / newCap;
}
OnReaction(solution, reaction, null, owner, unitReactions);
return products;
}
protected virtual void OnReaction(Solution solution, ReactionPrototype reaction, ReagentPrototype randomReagent, EntityUid owner, FixedPoint2 unitReactions)
private void OnReaction(Solution solution, ReactionPrototype reaction, ReagentPrototype? reagent, EntityUid owner, FixedPoint2 unitReactions)
{
var args = new ReagentEffectArgs(owner, null, solution,
randomReagent,
reagent,
unitReactions, EntityManager, null, 1f);
var coordinates = Transform(owner).Coordinates;
_adminLogger.Add(LogType.ChemicalReaction, reaction.Impact,
$"Chemical reaction {reaction.ID:reaction} occurred with strength {unitReactions:strength} on entity {ToPrettyString(owner):metabolizer} at {coordinates}");
foreach (var effect in reaction.Effects)
{
if (!effect.ShouldApply(args))
@@ -214,12 +220,14 @@ namespace Content.Shared.Chemistry.Reaction
if (effect.ShouldLog)
{
var entity = args.SolutionEntity;
AdminLogger.Add(LogType.ReagentEffect, effect.LogImpact,
_adminLogger.Add(LogType.ReagentEffect, effect.LogImpact,
$"Reaction effect {effect.GetType().Name:effect} of reaction ${reaction.ID:reaction} applied on entity {ToPrettyString(entity):entity} at {Transform(entity).Coordinates:coordinates}");
}
effect.Effect(args);
}
_audio.PlayPvs(reaction.Sound, owner);
}
/// <summary>
@@ -230,7 +238,7 @@ namespace Content.Shared.Chemistry.Reaction
private bool ProcessReactions(Solution solution, EntityUid owner, FixedPoint2 maxVolume, SortedSet<ReactionPrototype> reactions, ReactionMixerComponent? mixerComponent)
{
HashSet<ReactionPrototype> toRemove = new();
Solution? products = null;
List<string>? products = null;
// attempt to perform any applicable reaction
foreach (var reaction in reactions)
@@ -249,30 +257,23 @@ namespace Content.Shared.Chemistry.Reaction
if (products == null)
return false;
// Remove any reactions that were not applicable. Avoids re-iterating over them in future.
foreach (var proto in toRemove)
{
reactions.Remove(proto);
}
if (products.Volume <= 0)
if (products.Count == 0)
return true;
// remove excess product
// TODO spill excess?
var excessVolume = solution.Volume + products.Volume - maxVolume;
var excessVolume = solution.Volume - maxVolume;
if (excessVolume > 0)
products.RemoveSolution(excessVolume);
solution.RemoveSolution(excessVolume);
// Add any reactions associated with the new products. This may re-add reactions that were already iterated
// over previously. The new product may mean the reactions are applicable again and need to be processed.
foreach (var reactant in products.Contents)
foreach (var product in products)
{
if (_reactions.TryGetValue(reactant.ReagentId, out var reactantReactions))
if (_reactions.TryGetValue(product, out var reactantReactions))
reactions.UnionWith(reactantReactions);
}
solution.AddSolution(products, _prototypeManager);
return true;
}

View File

@@ -32,6 +32,12 @@ namespace Content.Shared.Chemistry.Reaction
[DataField("minTemp")]
public float MinimumTemperature = 0.0f;
/// <summary>
/// If true, this reaction will attempt to conserve thermal energy.
/// </summary>
[DataField("conserveEnergy")]
public bool ConserveEnergy = true;
/// <summary>
/// The maximum temperature the reaction can occur at.
/// </summary>

View File

@@ -79,7 +79,7 @@ namespace Content.Shared.Chemistry.Reagent
EntityUid SolutionEntity,
EntityUid? OrganEntity,
Solution? Source,
ReagentPrototype Reagent,
ReagentPrototype? Reagent,
FixedPoint2 Quantity,
IEntityManager EntityManager,
ReactionMethod? Method,

View File

@@ -9,7 +9,7 @@ public abstract class SharedDestructibleSystem : EntitySystem
{
var eventArgs = new DestructionEventArgs();
RaiseLocalEvent(owner, eventArgs, false);
RaiseLocalEvent(owner, eventArgs);
QueueDel(owner);
}
@@ -19,7 +19,7 @@ public abstract class SharedDestructibleSystem : EntitySystem
public void BreakEntity(EntityUid owner)
{
var eventArgs = new BreakageEventArgs();
RaiseLocalEvent(owner, eventArgs, false);
RaiseLocalEvent(owner, eventArgs);
}
}

View File

@@ -5,11 +5,11 @@ using Content.Shared.DoAfter;
using Content.Shared.Doors.Components;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.Physics;
using Content.Shared.Stunnable;
using Content.Shared.Tag;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
using Robust.Shared.Physics.Systems;
@@ -446,6 +446,10 @@ public abstract class SharedDoorSystem : EntitySystem
if (!otherPhysics.CanCollide)
continue;
//If the colliding entity is a slippable item ignore it by the airlock
if (otherPhysics.CollisionLayer == (int) CollisionGroup.SlipLayer && otherPhysics.CollisionMask == (int) CollisionGroup.ItemMask)
continue;
if ((physics.CollisionMask & otherPhysics.CollisionLayer) == 0 && (otherPhysics.CollisionMask & physics.CollisionLayer) == 0)
continue;

View File

@@ -1,4 +1,5 @@
using Content.Shared.Damage;
using Robust.Shared.Audio;
namespace Content.Shared.Interaction.Components
{
@@ -11,5 +12,11 @@ namespace Content.Shared.Interaction.Components
[DataField("clumsyDamage", required: true)]
[ViewVariables(VVAccess.ReadWrite)]
public DamageSpecifier ClumsyDamage = default!;
/// <summary>
/// Sound to play when clumsy interactions fail
/// </summary>
[DataField("clumsySound")]
public SoundSpecifier ClumsySound = new SoundPathSpecifier("/Audio/Items/bikehorn.ogg");
}
}

View File

@@ -169,12 +169,11 @@ public abstract class SharedMechSystem : EntitySystem
return;
var rider = EnsureComp<MechPilotComponent>(pilot);
var relay = EnsureComp<RelayInputMoverComponent>(pilot);
// Warning: this bypasses most normal interaction blocking components on the user, like drone laws and the like.
var irelay = EnsureComp<InteractionRelayComponent>(pilot);
_mover.SetRelay(pilot, mech, relay);
_mover.SetRelay(pilot, mech);
_interaction.SetRelay(pilot, mech, irelay);
rider.Mech = mech;
Dirty(rider);

View File

@@ -1,17 +1,15 @@
using Content.Shared.Movement.Systems;
using Robust.Shared.GameStates;
namespace Content.Shared.Movement.Components;
[RegisterComponent, NetworkedComponent]
public sealed class MovementRelayTargetComponent : Component
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
[Access(typeof(SharedMoverController))]
public sealed partial class MovementRelayTargetComponent : Component
{
// This really shouldn't be a list at the moment. Its just not supported.
// Neither movement updating, nor HandleDirChange() support more than one mover.
// Its currently possible for the direction to be set by one mover and the relative rotation to be set by a separate unrelated mover.
// AAAAA
/// <summary>
/// Entities that are relaying to us.
/// The entity that is relaying to this entity.
/// </summary>
[ViewVariables] public readonly List<EntityUid> Entities = new();
[ViewVariables, AutoNetworkedField]
public EntityUid Source;
}

View File

@@ -6,10 +6,10 @@ namespace Content.Shared.Movement.Components;
/// <summary>
/// Raises the engine movement inputs for a particular entity onto the designated entity
/// </summary>
[RegisterComponent, NetworkedComponent]
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
[Access(typeof(SharedMoverController))]
public sealed class RelayInputMoverComponent : Component
public sealed partial class RelayInputMoverComponent : Component
{
[ViewVariables]
public EntityUid? RelayEntity;
[ViewVariables, AutoNetworkedField]
public EntityUid RelayEntity;
}

View File

@@ -101,8 +101,7 @@ public abstract class SharedJetpackSystem : EntitySystem
private void SetupUser(EntityUid uid, JetpackComponent component)
{
var user = EnsureComp<JetpackUserComponent>(uid);
var relay = EnsureComp<RelayInputMoverComponent>(uid);
_mover.SetRelay(uid, component.Owner, relay);
_mover.SetRelay(uid, component.Owner);
user.Jetpack = component.Owner;
}

View File

@@ -274,11 +274,8 @@ namespace Content.Shared.Movement.Systems
if (TryComp<InputMoverComponent>(entity, out var mover))
SetMoveInput(mover, MoveButtons.None);
DebugTools.Assert(TryComp(relayMover.RelayEntity, out MovementRelayTargetComponent? targetComp) && targetComp.Entities.Count == 1,
"Multiple relayed movers are not supported at the moment");
if (relayMover.RelayEntity != null && !_mobState.IsIncapacitated(entity))
HandleDirChange(relayMover.RelayEntity.Value, dir, subTick, state);
if (!_mobState.IsIncapacitated(entity))
HandleDirChange(relayMover.RelayEntity, dir, subTick, state);
return;
}
@@ -328,9 +325,7 @@ namespace Content.Shared.Movement.Systems
SetMoveInput(moverComp, MoveButtons.None);
}
if (relayMover.RelayEntity == null) return;
HandleRunChange(relayMover.RelayEntity.Value, subTick, walking);
HandleRunChange(relayMover.RelayEntity, subTick, walking);
return;
}

View File

@@ -1,7 +1,4 @@
using Content.Shared.Movement.Components;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Movement.Systems;
@@ -9,136 +6,85 @@ public abstract partial class SharedMoverController
{
private void InitializeRelay()
{
SubscribeLocalEvent<RelayInputMoverComponent, ComponentGetState>(OnRelayGetState);
SubscribeLocalEvent<RelayInputMoverComponent, ComponentHandleState>(OnRelayHandleState);
SubscribeLocalEvent<RelayInputMoverComponent, ComponentShutdown>(OnRelayShutdown);
SubscribeLocalEvent<MovementRelayTargetComponent, ComponentGetState>(OnTargetRelayGetState);
SubscribeLocalEvent<MovementRelayTargetComponent, ComponentHandleState>(OnTargetRelayHandleState);
SubscribeLocalEvent<MovementRelayTargetComponent, ComponentShutdown>(OnTargetRelayShutdown);
SubscribeLocalEvent<MovementRelayTargetComponent, AfterAutoHandleStateEvent>(OnAfterRelayTargetState);
SubscribeLocalEvent<RelayInputMoverComponent, AfterAutoHandleStateEvent>(OnAfterRelayState);
}
private void OnAfterRelayTargetState(EntityUid uid, MovementRelayTargetComponent component, ref AfterAutoHandleStateEvent args)
{
Physics.UpdateIsPredicted(uid);
}
private void OnAfterRelayState(EntityUid uid, RelayInputMoverComponent component, ref AfterAutoHandleStateEvent args)
{
Physics.UpdateIsPredicted(uid);
}
/// <summary>
/// Sets the relay entity and marks the component as dirty. This only exists because people have previously
/// forgotten to Dirty(), so fuck you, you have to use this method now.
/// </summary>
public void SetRelay(EntityUid uid, EntityUid relayEntity, RelayInputMoverComponent? component = null)
public void SetRelay(EntityUid uid, EntityUid relayEntity)
{
if (!Resolve(uid, ref component) || component.RelayEntity == relayEntity)
return;
if (uid == relayEntity)
{
Logger.Error($"An entity attempted to relay movement to itself. Entity:{ToPrettyString(uid)}");
return;
}
if (TryComp<MovementRelayTargetComponent>(relayEntity, out var targetComp))
{
targetComp.Entities.Remove(uid);
var component = EnsureComp<RelayInputMoverComponent>(uid);
if (component.RelayEntity == relayEntity)
return;
if (targetComp.Entities.Count == 0)
RemComp<MovementRelayTargetComponent>(relayEntity);
if (TryComp(component.RelayEntity, out MovementRelayTargetComponent? oldTarget))
{
oldTarget.Source = EntityUid.Invalid;
RemComp(component.RelayEntity, oldTarget);
Physics.UpdateIsPredicted(component.RelayEntity);
}
var targetComp = EnsureComp<MovementRelayTargetComponent>(relayEntity);
if (TryComp(targetComp.Source, out RelayInputMoverComponent? oldRelay))
{
oldRelay.RelayEntity = EntityUid.Invalid;
RemComp(targetComp.Source, oldRelay);
Physics.UpdateIsPredicted(targetComp.Source);
}
Physics.UpdateIsPredicted(uid);
Physics.UpdateIsPredicted(relayEntity);
component.RelayEntity = relayEntity;
targetComp = EnsureComp<MovementRelayTargetComponent>(relayEntity);
targetComp.Entities.Add(uid);
DebugTools.Assert(targetComp.Entities.Count <= 1, "Multiple relayed movers are not supported at the moment");
targetComp.Source = uid;
Dirty(component);
Dirty(targetComp);
}
private void OnRelayShutdown(EntityUid uid, RelayInputMoverComponent component, ComponentShutdown args)
{
// If relay is removed then cancel all inputs.
if (!TryComp<InputMoverComponent>(component.RelayEntity, out var inputMover))
Physics.UpdateIsPredicted(uid);
Physics.UpdateIsPredicted(component.RelayEntity);
if (TryComp<InputMoverComponent>(component.RelayEntity, out var inputMover))
SetMoveInput(inputMover, MoveButtons.None);
if (Timing.ApplyingState)
return;
if (TryComp<MovementRelayTargetComponent>(component.RelayEntity, out var targetComp) &&
targetComp.LifeStage < ComponentLifeStage.Stopping)
{
targetComp.Entities.Remove(uid);
if (targetComp.Entities.Count == 0)
RemCompDeferred<MovementRelayTargetComponent>(component.RelayEntity.Value);
else
Dirty(targetComp);
}
SetMoveInput(inputMover, MoveButtons.None);
if (TryComp(component.RelayEntity, out MovementRelayTargetComponent? target) && target.LifeStage <= ComponentLifeStage.Running)
RemComp(component.RelayEntity, target);
}
private void OnRelayHandleState(EntityUid uid, RelayInputMoverComponent component, ref ComponentHandleState args)
{
if (args.Current is not RelayInputMoverComponentState state) return;
DebugTools.Assert(state.Entity != uid);
component.RelayEntity = state.Entity;
}
private void OnRelayGetState(EntityUid uid, RelayInputMoverComponent component, ref ComponentGetState args)
{
args.State = new RelayInputMoverComponentState()
{
Entity = component.RelayEntity,
};
}
#region Target Relay
private void OnTargetRelayShutdown(EntityUid uid, MovementRelayTargetComponent component, ComponentShutdown args)
{
if (component.Entities.Count == 0)
Physics.UpdateIsPredicted(uid);
Physics.UpdateIsPredicted(component.Source);
if (Timing.ApplyingState)
return;
var relayQuery = GetEntityQuery<RelayInputMoverComponent>();
foreach (var ent in component.Entities)
{
if (!relayQuery.TryGetComponent(ent, out var relay))
continue;
DebugTools.Assert(relay.RelayEntity == uid);
if (relay.RelayEntity != uid)
continue;
RemCompDeferred<RelayInputMoverComponent>(ent);
}
}
private void OnTargetRelayHandleState(EntityUid uid, MovementRelayTargetComponent component, ref ComponentHandleState args)
{
if (args.Current is not MovementRelayTargetComponentState state)
return;
component.Entities.Clear();
component.Entities.AddRange(state.Entities);
DebugTools.Assert(component.Entities.Count <= 1, "Multiple relayed movers are not supported at the moment");
}
private void OnTargetRelayGetState(EntityUid uid, MovementRelayTargetComponent component, ref ComponentGetState args)
{
args.State = new MovementRelayTargetComponentState(component.Entities);
}
#endregion
[Serializable, NetSerializable]
private sealed class RelayInputMoverComponentState : ComponentState
{
public EntityUid? Entity;
}
[Serializable, NetSerializable]
private sealed class MovementRelayTargetComponentState : ComponentState
{
public List<EntityUid> Entities;
public MovementRelayTargetComponentState(List<EntityUid> entities)
{
Entities = entities;
}
if (TryComp(component.Source, out RelayInputMoverComponent? relay) && relay.LifeStage <= ComponentLifeStage.Running)
RemComp(component.Source, relay);
}
}

View File

@@ -30,6 +30,7 @@ namespace Content.Shared.Movement.Systems
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] protected readonly IGameTiming Timing = default!;
[Dependency] protected readonly SharedPhysicsSystem Physics = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
@@ -108,25 +109,19 @@ namespace Content.Shared.Movement.Systems
EntityQuery<MovementSpeedModifierComponent> modifierQuery)
{
var canMove = mover.CanMove;
if (relayTargetQuery.TryGetComponent(uid, out var relayTarget) && relayTarget.Entities.Count > 0)
if (relayTargetQuery.TryGetComponent(uid, out var relayTarget))
{
DebugTools.Assert(relayTarget.Entities.Count <= 1, "Multiple relayed movers are not supported at the moment");
var found = false;
foreach (var ent in relayTarget.Entities)
if (_mobState.IsIncapacitated(relayTarget.Source) ||
!moverQuery.TryGetComponent(relayTarget.Source, out var relayedMover))
{
canMove = false;
}
else
{
if (_mobState.IsIncapacitated(ent) || !moverQuery.TryGetComponent(ent, out var relayedMover))
continue;
found = true;
mover.RelativeEntity = relayedMover.RelativeEntity;
mover.RelativeRotation = relayedMover.RelativeRotation;
mover.TargetRelativeRotation = relayedMover.TargetRelativeRotation;
break;
}
// lets just hope that this is the same entity that set the movement keys/direction.
canMove &= found;
}
// Update relative movement
@@ -267,10 +262,7 @@ namespace Content.Shared.Movement.Systems
// If we're a relay target then predict the sound for all relays.
if (relayTarget != null)
{
foreach (var ent in relayTarget.Entities)
{
_audio.PlayPredicted(sound, uid, ent, audioParams);
}
_audio.PlayPredicted(sound, uid, relayTarget.Source, audioParams);
}
else
{

View File

@@ -12,7 +12,9 @@ namespace Content.Shared.Throwing;
public sealed class ThrowingSystem : EntitySystem
{
public const float ThrowAngularImpulse = 1.5f;
public const float ThrowAngularImpulse = 5f;
public const float PushbackDefault = 1f;
/// <summary>
/// The minimum amount of time an entity needs to be thrown before the timer can be run.
@@ -37,7 +39,7 @@ public sealed class ThrowingSystem : EntitySystem
Vector2 direction,
float strength = 1.0f,
EntityUid? user = null,
float pushbackRatio = 5.0f)
float pushbackRatio = PushbackDefault)
{
var physicsQuery = GetEntityQuery<PhysicsComponent>();
if (!physicsQuery.TryGetComponent(uid, out var physics))
@@ -73,7 +75,7 @@ public sealed class ThrowingSystem : EntitySystem
EntityQuery<TagComponent> tagQuery,
float strength = 1.0f,
EntityUid? user = null,
float pushbackRatio = 5.0f)
float pushbackRatio = PushbackDefault)
{
if (strength <= 0 || direction == Vector2.Infinity || direction == Vector2.NaN || direction == Vector2.Zero)
return;
@@ -92,7 +94,7 @@ public sealed class ThrowingSystem : EntitySystem
// Give it a l'il spin.
if (!tagQuery.TryGetComponent(uid, out var tag) || !_tagSystem.HasTag(tag, "NoSpinOnThrow"))
_physics.ApplyAngularImpulse(uid, ThrowAngularImpulse, body: physics);
_physics.ApplyAngularImpulse(uid, ThrowAngularImpulse / physics.InvI, body: physics);
else
transform.LocalRotation = direction.ToWorldAngle() - Math.PI;
@@ -124,7 +126,8 @@ public sealed class ThrowingSystem : EntitySystem
// Give thrower an impulse in the other direction
if (user != null &&
pushbackRatio > 0.0f &&
pushbackRatio != 0.0f &&
physics.Mass > 0f &&
TryComp(user.Value, out PhysicsComponent? userPhysics) &&
_gravity.IsWeightless(user.Value, userPhysics))
{
@@ -132,7 +135,7 @@ public sealed class ThrowingSystem : EntitySystem
RaiseLocalEvent(uid, msg);
if (!msg.Cancelled)
_physics.ApplyLinearImpulse(user.Value, -impulseVector * pushbackRatio, body: userPhysics);
_physics.ApplyLinearImpulse(user.Value, -impulseVector * pushbackRatio * physics.Mass, body: userPhysics);
}
}
}

View File

@@ -1,5 +1,6 @@
using Content.Shared.Whitelist;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using static Robust.Shared.Prototypes.EntityPrototype; // don't worry about it
namespace Content.Shared.Traits
@@ -43,5 +44,11 @@ namespace Content.Shared.Traits
/// </summary>
[DataField("components")]
public ComponentRegistry Components { get; } = default!;
/// <summary>
/// Gear that is given to the player, when they pick this trait.
/// </summary>
[DataField("traitGear", required: false, customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
public string TraitGear = string.Empty;
}
}

View File

@@ -121,8 +121,7 @@ public abstract partial class SharedVehicleSystem : EntitySystem
Dirty(component);
Appearance.SetData(uid, VehicleVisuals.HideRider, true);
var relay = EnsureComp<RelayInputMoverComponent>(args.BuckledEntity);
_mover.SetRelay(args.BuckledEntity, uid, relay);
_mover.SetRelay(args.BuckledEntity, uid);
rider.Vehicle = uid;
// Update appearance stuff, add actions

View File

@@ -0,0 +1,5 @@
namespace Content.Shared.Wieldable;
public sealed class BeforeUnwieldEvent : CancellableEntityEventArgs
{
}

View File

@@ -0,0 +1,5 @@
namespace Content.Shared.Wieldable;
public sealed class BeforeWieldEvent : CancellableEntityEventArgs
{
}

View File

@@ -0,0 +1,10 @@
using Content.Shared.Damage;
namespace Content.Shared.Wieldable.Components;
[RegisterComponent, Access(typeof(WieldableSystem))]
public sealed class IncreaseDamageOnWieldComponent : Component
{
[DataField("damage", required: true)]
public DamageSpecifier BonusDamage = default!;
}

View File

@@ -0,0 +1,35 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
namespace Content.Shared.Wieldable.Components;
/// <summary>
/// Used for objects that can be wielded in two or more hands,
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(WieldableSystem)), AutoGenerateComponentState]
public sealed partial class WieldableComponent : Component
{
[DataField("wieldSound")]
public SoundSpecifier? WieldSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg");
[DataField("unwieldSound")]
public SoundSpecifier? UnwieldSound;
/// <summary>
/// Number of free hands required (excluding the item itself) required
/// to wield it
/// </summary>
[DataField("freeHandsRequired")]
public int FreeHandsRequired = 1;
[AutoNetworkedField, DataField("wielded")]
public bool Wielded = false;
[DataField("wieldedInhandPrefix")]
public string WieldedInhandPrefix = "wielded";
public string? OldInhandPrefix = null;
[DataField("wieldTime")]
public float WieldTime = 1.5f;
}

View File

@@ -0,0 +1,23 @@
namespace Content.Shared.Wieldable;
#region Events
/// <summary>
/// Raised on the item that has been unwielded.
/// </summary>
public sealed class ItemUnwieldedEvent : EntityEventArgs
{
public EntityUid? User;
/// <summary>
/// Whether the item is being forced to be unwielded, or if the player chose to unwield it themselves.
/// </summary>
public bool Force;
public ItemUnwieldedEvent(EntityUid? user = null, bool force=false)
{
User = user;
Force = force;
}
}
#endregion

View File

@@ -0,0 +1,232 @@
using Content.Shared.Actions.Events;
using Content.Shared.DoAfter;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Wieldable.Components;
using Robust.Shared.Player;
namespace Content.Shared.Wieldable;
public sealed class WieldableSystem : EntitySystem
{
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedHandVirtualItemSystem _virtualItemSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly SharedItemSystem _itemSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<WieldableComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<WieldableComponent, WieldableDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<WieldableComponent, ItemUnwieldedEvent>(OnItemUnwielded);
SubscribeLocalEvent<WieldableComponent, GotUnequippedHandEvent>(OnItemLeaveHand);
SubscribeLocalEvent<WieldableComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
SubscribeLocalEvent<WieldableComponent, GetVerbsEvent<InteractionVerb>>(AddToggleWieldVerb);
SubscribeLocalEvent<WieldableComponent, DisarmAttemptEvent>(OnDisarmAttemptEvent);
SubscribeLocalEvent<IncreaseDamageOnWieldComponent, MeleeHitEvent>(OnMeleeHit);
}
private void OnDisarmAttemptEvent(EntityUid uid, WieldableComponent component, DisarmAttemptEvent args)
{
if (component.Wielded)
args.Cancel();
}
private void AddToggleWieldVerb(EntityUid uid, WieldableComponent component, GetVerbsEvent<InteractionVerb> args)
{
if (args.Hands == null || !args.CanAccess || !args.CanInteract)
return;
if (!_handsSystem.IsHolding(args.User, uid, out _, args.Hands))
return;
// TODO VERB TOOLTIPS Make CanWield or some other function return string, set as verb tooltip and disable
// verb. Or just don't add it to the list if the action is not executable.
// TODO VERBS ICON
InteractionVerb verb = new()
{
Text = component.Wielded ? Loc.GetString("wieldable-verb-text-unwield") : Loc.GetString("wieldable-verb-text-wield"),
Act = component.Wielded
? () => AttemptUnwield(uid, component, args.User)
: () => AttemptWield(uid, component, args.User)
};
args.Verbs.Add(verb);
}
private void OnUseInHand(EntityUid uid, WieldableComponent component, UseInHandEvent args)
{
if (args.Handled)
return;
if(!component.Wielded)
AttemptWield(uid, component, args.User);
else
AttemptUnwield(uid, component, args.User);
}
public bool CanWield(EntityUid uid, WieldableComponent component, EntityUid user, bool quiet=false)
{
// Do they have enough hands free?
if (!EntityManager.TryGetComponent<HandsComponent>(user, out var hands))
{
if(!quiet)
_popupSystem.PopupClient(Loc.GetString("wieldable-component-no-hands"), user, user);
return false;
}
// Is it.. actually in one of their hands?
if (!_handsSystem.IsHolding(user, uid, out _, hands))
{
if (!quiet)
_popupSystem.PopupClient(Loc.GetString("wieldable-component-not-in-hands", ("item", uid)), user, user);
return false;
}
if (hands.CountFreeHands() < component.FreeHandsRequired)
{
if (!quiet)
{
var message = Loc.GetString("wieldable-component-not-enough-free-hands",
("number", component.FreeHandsRequired), ("item", uid));
_popupSystem.PopupClient(message, user, user);
}
return false;
}
// Seems legit.
return true;
}
/// <summary>
/// Attempts to wield an item, creating a DoAfter..
/// </summary>
public void AttemptWield(EntityUid used, WieldableComponent component, EntityUid user)
{
if (!CanWield(used, component, user))
return;
var ev = new BeforeWieldEvent();
RaiseLocalEvent(used, ev);
if (ev.Cancelled)
return;
var doargs = new DoAfterArgs(user, component.WieldTime, new WieldableDoAfterEvent(), used, used: used)
{
BreakOnUserMove = false,
BreakOnDamage = true
};
_doAfter.TryStartDoAfter(doargs);
}
/// <summary>
/// Attempts to unwield an item, with no DoAfter.
/// </summary>
public void AttemptUnwield(EntityUid used, WieldableComponent component, EntityUid user)
{
var ev = new BeforeUnwieldEvent();
RaiseLocalEvent(used, ev);
if (ev.Cancelled)
return;
var targEv = new ItemUnwieldedEvent(user);
RaiseLocalEvent(used, targEv);
}
private void OnDoAfter(EntityUid uid, WieldableComponent component, DoAfterEvent args)
{
if (args.Handled || args.Cancelled || !CanWield(uid, component, args.Args.User) || component.Wielded)
return;
if (TryComp<ItemComponent>(uid, out var item))
{
component.OldInhandPrefix = item.HeldPrefix;
_itemSystem.SetHeldPrefix(uid, component.WieldedInhandPrefix, item);
}
component.Wielded = true;
if (component.WieldSound != null)
_audioSystem.PlayPredicted(component.WieldSound, uid, args.User);
for (var i = 0; i < component.FreeHandsRequired; i++)
{
_virtualItemSystem.TrySpawnVirtualItemInHand(uid, args.Args.User);
}
_popupSystem.PopupClient(Loc.GetString("wieldable-component-successful-wield", ("item", uid)), args.Args.User, args.Args.User);
_popupSystem.PopupEntity(Loc.GetString("wieldable-component-successful-wield-other", ("user", args.Args.User),("item", uid)), args.Args.User, Filter.PvsExcept(args.Args.User), true);
Dirty(component);
args.Handled = true;
}
private void OnItemUnwielded(EntityUid uid, WieldableComponent component, ItemUnwieldedEvent args)
{
if (args.User == null)
return;
if (!component.Wielded)
return;
if (TryComp<ItemComponent>(uid, out var item))
{
_itemSystem.SetHeldPrefix(uid, component.OldInhandPrefix, item);
}
component.Wielded = false;
if (!args.Force) // don't play sound/popup if this was a forced unwield
{
if (component.UnwieldSound != null)
_audioSystem.PlayPredicted(component.UnwieldSound, uid, args.User);
_popupSystem.PopupClient(Loc.GetString("wieldable-component-failed-wield",
("item", uid)), args.User.Value, args.User.Value);
_popupSystem.PopupEntity(Loc.GetString("wieldable-component-failed-wield-other",
("user", args.User.Value), ("item", uid)), args.User.Value, Filter.PvsExcept(args.User.Value), true);
}
Dirty(component);
_virtualItemSystem.DeleteInHandsMatching(args.User.Value, uid);
}
private void OnItemLeaveHand(EntityUid uid, WieldableComponent component, GotUnequippedHandEvent args)
{
if (!component.Wielded || uid != args.Unequipped)
return;
RaiseLocalEvent(uid, new ItemUnwieldedEvent(args.User, force: true), true);
}
private void OnVirtualItemDeleted(EntityUid uid, WieldableComponent component, VirtualItemDeletedEvent args)
{
if (args.BlockingEntity == uid && component.Wielded)
AttemptUnwield(args.BlockingEntity, component, args.User);
}
private void OnMeleeHit(EntityUid uid, IncreaseDamageOnWieldComponent component, MeleeHitEvent args)
{
if (EntityManager.TryGetComponent<WieldableComponent>(uid, out var wield))
{
if (!wield.Wielded)
return;
}
if (args.Handled)
return;
args.BonusDamage += component.BonusDamage;
}
}

View File

@@ -0,0 +1,4 @@
- files: ["female_sigh.ogg", "male_sigh.ogg"]
license: "CC-BY-3.0"
copyright: "https://github.com/ss220-space/Paradise/"
source: "https://github.com/ss220-space/Paradise/commit/89943a231ced9fc8db0aef65ff87093efee16b4e"

Binary file not shown.

Binary file not shown.

View File

@@ -1,201 +1,4 @@
Entries:
- author: adamsogm
changes:
- {message: Changed how full solution containers appear based on their actual fill
level., type: Tweak}
- {message: Fixed water cup being perpetually empty., type: Fix}
id: 3211
time: '2023-03-23T03:49:42.0000000+00:00'
- author: Alekshhh
changes:
- {message: Changed PKA to have a consistent firerate of 1.5 seconds, type: Tweak}
id: 3212
time: '2023-03-23T04:22:01.0000000+00:00'
- author: EmoGarbage404
changes:
- {message: 'Hair, facial hair, markings, etc are now sorted alphabetically in the
character creator menu.', type: Tweak}
id: 3213
time: '2023-03-23T05:03:20.0000000+00:00'
- author: metalgearsloth
changes:
- {message: Cargo shuttles now use FTL and not their own system. They have to FTL
back and forth to sell / purchase goods., type: Tweak}
id: 3214
time: '2023-03-23T05:10:49.0000000+00:00'
- author: metalgearsloth
changes:
- {message: Fix some shuttle console rotations flipping the viewport., type: Fix}
id: 3215
time: '2023-03-23T05:52:21.0000000+00:00'
- author: metalgearsloth
changes:
- {message: Fix arrivals stranding you at terminal., type: Fix}
id: 3216
time: '2023-03-23T06:41:42.0000000+00:00'
- author: metalgearsloth
changes:
- {message: Fix docking, type: Fix}
id: 3217
time: '2023-03-23T06:50:52.0000000+00:00'
- author: Slava0135
changes:
- {message: you can now place floor tiles under directional windows (or any other
non-full wall)., type: Fix}
id: 3218
time: '2023-03-23T06:55:51.0000000+00:00'
- author: metalgearsloth
changes:
- {message: Fix some instances of NPCs getting stuck., type: Fix}
id: 3219
time: '2023-03-23T12:53:17.0000000+00:00'
- author: deltanedas
changes:
- {message: The Emag has received an update for disabling safety overrides on disposal
units., type: Tweak}
id: 3220
time: '2023-03-23T14:55:50.0000000+00:00'
- author: Potato1234_x
changes:
- {message: 'Changed sprites of the seed extractor, hydroponics bags and hydroponics
leather gloves.', type: Tweak}
- {message: Fixed inconsistent hydroponics bag naming., type: Fix}
id: 3221
time: '2023-03-23T15:10:18.0000000+00:00'
- author: Slava0135
changes:
- {message: 'added explosive banana peel to clown uplink (2 TC, 30 total damage)',
type: Add}
id: 3222
time: '2023-03-23T15:54:15.0000000+00:00'
- author: Alekshhh
changes:
- {message: Tweaked baseball bats and spears damage and weight values., type: Tweak}
id: 3223
time: '2023-03-23T16:06:09.0000000+00:00'
- author: Nekomancer
changes:
- {message: Added small light post state sprites, type: Add}
- {message: Tweaked small light post empty sprite, type: Tweak}
id: 3224
time: '2023-03-23T16:12:33.0000000+00:00'
- author: Whisper
changes:
- {message: 'Zombies drop their headsets, preventing them from hearing comms.',
type: Tweak}
id: 3225
time: '2023-03-23T16:17:33.0000000+00:00'
- author: deltanedas
changes:
- {message: Round end screen now lists traitor codewords., type: Tweak}
id: 3226
time: '2023-03-23T16:29:40.0000000+00:00'
- author: Alekshhh
changes:
- {message: Changed dame dane guy set to be less blue. That was a baka mistake.,
type: Tweak}
id: 3227
time: '2023-03-23T16:51:50.0000000+00:00'
- author: EmoGarbage404
changes:
- {message: Paperwork now looks the same as he did before, type: Tweak}
id: 3228
time: '2023-03-23T16:53:27.0000000+00:00'
- author: 08A
changes:
- {message: Fixed welder interaction when it has a fuel shortage problem, type: Fix}
id: 3229
time: '2023-03-23T17:08:46.0000000+00:00'
- author: Flareguy
changes:
- {message: The energy sword now does 30 damage per hit and swings slower., type: Tweak}
- {message: The energy sword can now break structures., type: Tweak}
- {message: 'The energy sword has received its iconic hitsound back, making it much
more obvious and threatening.', type: Tweak}
id: 3230
time: '2023-03-23T17:17:07.0000000+00:00'
- author: deltanedas
changes:
- {message: Dehydrated carp will fit in lockers now., type: Fix}
id: 3231
time: '2023-03-23T17:44:36.0000000+00:00'
- author: deltanedas
changes:
- {message: Syndicate shoulder holsters can no longer hold melee weapons., type: Tweak}
- {message: Suspicious toolbox now comes with a utility belt in place of a holster.,
type: Tweak}
id: 3232
time: '2023-03-23T18:14:55.0000000+00:00'
- author: Slava0135
changes:
- {message: 'added Telecom System. Sending messages through headsets (but handheld
radios / intercoms and etc.) now requires powered telecom server with channel
encryption key inserted. Source, receiver and server should be on the same map.
Centcom and syndicate channels are available without server and their messages
not limited to map.', type: Add}
id: 3233
time: '2023-03-24T00:02:41.0000000+00:00'
- author: Slava0135
changes:
- {message: maintenance panel needs to be opened to insert/remove keys in telecom
server, type: Tweak}
- {message: telecom server now can be deconstructed with crowbar, type: Fix}
id: 3234
time: '2023-03-24T00:09:46.0000000+00:00'
- author: Alekshhh
changes:
- {message: Shells and cartridges now appear over tables, type: Fix}
id: 3235
time: '2023-03-24T01:05:55.0000000+00:00'
- author: lapatison
changes:
- {message: Biosuit crates now can be ordered from cargo, type: Add}
id: 3236
time: '2023-03-24T01:40:27.0000000+00:00'
- author: TimmyTwoHands
changes:
- {message: Removed combat bonuses from Boxers., type: Tweak}
id: 3237
time: '2023-03-24T01:50:29.0000000+00:00'
- author: metalgearsloth
changes:
- {message: Fix static melee offsets not showing the correct position., type: Fix}
id: 3238
time: '2023-03-24T02:13:16.0000000+00:00'
- author: MisterMecky
changes:
- {message: modified and added stack sprites for wood, type: Tweak}
id: 3239
time: '2023-03-24T02:26:20.0000000+00:00'
- author: Mr0maks
changes:
- {message: Fix freedom implant charges not being used., type: Fix}
id: 3240
time: '2023-03-24T03:07:21.0000000+00:00'
- author: Rane
changes:
- {message: Fixed exploits that would let you continue controlling a cardboard box
after exiting it., type: Fix}
id: 3241
time: '2023-03-24T05:09:58.0000000+00:00'
- author: metalgearsloth
changes:
- {message: Remove range check for FTL., type: Tweak}
id: 3242
time: '2023-03-24T06:17:09.0000000+00:00'
- author: Potato1234_x
changes:
- {message: Added 11 new food/drink vending machines., type: Add}
- {message: Added Robotech Deluxe filled with robotics equipment., type: Add}
- {message: Added CentDrobe filled with centcom drip., type: Add}
id: 3243
time: '2023-03-24T08:54:48.0000000+00:00'
- author: Slava0135
changes:
- {message: fluid can now spread through directional windows (through not occupied
sides)., type: Fix}
id: 3244
time: '2023-03-24T14:09:52.0000000+00:00'
- author: deltanedas
changes:
- {message: The rumors of dylovene's overdose turned out to be true! Anything above
@@ -2910,3 +2713,187 @@ Entries:
- {message: Adds a pet Pun Pun for bartenders ghost role., type: Add}
id: 3710
time: '2023-05-09T07:39:43.0000000+00:00'
- author: Tyzemol
changes:
- {message: Fixed not being able to link airlocks using network configurator, type: Fix}
id: 3711
time: '2023-05-09T22:50:35.0000000+00:00'
- author: Lomcastar
changes:
- {message: Added Proximity Mine disguised as a wet floor sign., type: Add}
id: 3712
time: '2023-05-09T23:50:03.0000000+00:00'
- author: deltanedas
changes:
- {message: 'Due to a recent economic boom, NT is now able to supply ERT cleanup
crews with mops.', type: Add}
id: 3713
time: '2023-05-09T23:51:48.0000000+00:00'
- author: Mumohan
changes:
- {message: Airlocks are no longer scared of puddles and soap. They should now close
properly., type: Fix}
id: 3714
time: '2023-05-09T23:54:43.0000000+00:00'
- author: notquitehadouken
changes:
- {message: Fixed the recipes for radiation shutters and glass shutters not appearing
in the construction menu, type: Fix}
id: 3715
time: '2023-05-09T23:56:07.0000000+00:00'
- author: metalgearsloth
changes:
- {message: Fix popup spam in some instances., type: Fix}
id: 3716
time: '2023-05-10T00:00:59.0000000+00:00'
- author: metalgearsloth
changes:
- {message: Wielding is now predicted., type: Tweak}
id: 3717
time: '2023-05-10T00:01:23.0000000+00:00'
- author: tom-leys
changes:
- {message: 'Frezon is slightly harder to make. The reaction always consumes Tritium
and Oxygen at a 1:50 ratio. But the reaction is easier to initiate, requiring
only Nitrogen, Oxygen, Tritium and <73.15K.', type: Tweak}
id: 3718
time: '2023-05-10T03:27:23.0000000+00:00'
- author: deltanedas
changes:
- {message: Chef's vinegar bottles now hold 30u instead of 15u., type: Tweak}
id: 3719
time: '2023-05-10T11:36:47.0000000+00:00'
- author: mirrorcult
changes:
- {message: Survival gamemode has been temporarily disabled pending a rework, type: Tweak}
id: 3720
time: '2023-05-11T00:08:45.0000000+00:00'
- author: mirrorcult
changes:
- {message: 'Monkeys are now clumsy, like clowns', type: Tweak}
id: 3721
time: '2023-05-11T00:27:48.0000000+00:00'
- author: Owai-Seek
changes:
- {message: Dermaline now heals 3 Heat/Shock/Cold per unit instead of 2., type: Tweak}
id: 3722
time: '2023-05-11T08:29:23.0000000+00:00'
- author: lzk
changes:
- {message: Added sigh sound, type: Add}
id: 3723
time: '2023-05-11T09:17:25.0000000+00:00'
- author: EmoGarbage404
changes:
- {message: Botany's produce is now larger on average., type: Tweak}
id: 3724
time: '2023-05-11T11:15:21.0000000+00:00'
- author: themias
changes:
- {message: Mob sprites no longer rotate on death, type: Fix}
id: 3725
time: '2023-05-11T11:20:42.0000000+00:00'
- author: metalgearsloth
changes:
- {message: PKA bullets can now destroy gatherable materials such as rocks or trees.,
type: Add}
id: 3726
time: '2023-05-11T13:19:09.0000000+00:00'
- author: juliangiebel
changes:
- {message: Fixed not being able to link blast doors and shutters, type: Fix}
id: 3727
time: '2023-05-11T22:16:02.0000000+00:00'
- author: freeman2651
changes:
- {message: Added ChemVends for (limited) bulk chemical storage and dispensing.,
type: Add}
id: 3728
time: '2023-05-12T09:31:20.0000000+00:00'
- author: crazybrain
changes:
- {message: You can now examine the ID cards worn by monkeys., type: Fix}
id: 3729
time: '2023-05-12T13:52:09.0000000+00:00'
- author: ElectroJr
changes:
- {message: Entering and leaving kudzu or other slowdown causing entities should
no longer cause the screen to stutter/flicker., type: Fix}
id: 3730
time: '2023-05-12T14:02:51.0000000+00:00'
- author: MisterMecky
changes:
- {message: Fixed several bottles not correctly displaying contained solutions.,
type: Fix}
id: 3731
time: '2023-05-12T15:29:39.0000000+00:00'
- author: deltanedas
changes:
- {message: 'Due to a recession on the clown planet, NT can no longer afford to
fill your scrubbers with quite as much foam.', type: Tweak}
id: 3732
time: '2023-05-12T18:37:09.0000000+00:00'
- author: crazybrain
changes:
- {message: 'The Captain''s ID card can now be thrown against an airlock to open
it, just like any other ID.', type: Fix}
id: 3733
time: '2023-05-12T20:07:57.0000000+00:00'
- author: lzk
changes:
- {message: hop now has detective acces, type: Fix}
id: 3734
time: '2023-05-12T20:16:29.0000000+00:00'
- author: Whisper
changes:
- {message: 'Syringes are now size 3, down from 5.', type: Tweak}
id: 3735
time: '2023-05-12T21:10:45.0000000+00:00'
- author: Cripes
changes:
- {message: Space tunes should now only be heard in space, type: Fix}
id: 3736
time: '2023-05-12T22:07:04.0000000+00:00'
- author: deltanedas
changes:
- {message: New safety standards have made bicaridine overdoses less fatal than
before., type: Tweak}
id: 3737
time: '2023-05-12T22:40:55.0000000+00:00'
- author: OctoRocket
changes:
- {message: 1 nukeop per 10 people instead of per 5., type: Tweak}
- {message: minimum of 20 people for nukeops., type: Tweak}
id: 3738
time: '2023-05-12T22:55:47.0000000+00:00'
- author: DEATHB4DEFEAT
changes:
- {message: Changed the portal sprite., type: Tweak}
id: 3739
time: '2023-05-12T23:36:43.0000000+00:00'
- author: KEEYNy
changes:
- {message: "Equal values \u200B\u200Bfor ERT Leader jumpsuit and ERT Security\
\ jumpsuit", type: Tweak}
id: 3740
time: '2023-05-12T23:43:27.0000000+00:00'
- author: metalgearsloth
changes:
- {message: Scale throwing pushback by mass., type: Tweak}
id: 3741
time: '2023-05-12T23:54:38.0000000+00:00'
- author: mirrorcult
changes:
- {message: You can now drinky puddles (inefficiently), type: Add}
id: 3742
time: '2023-05-13T01:32:57.0000000+00:00'
- author: ElectroJr
changes:
- {message: Chemical reactions should now conserve thermal energy., type: Tweak}
id: 3743
time: '2023-05-13T03:10:32.0000000+00:00'
- author: Tristan Thomas
changes:
- {message: 'Added a white cane, which spawns on blind players.', type: Add}
id: 3744
time: '2023-05-13T03:11:35.0000000+00:00'

View File

@@ -3,6 +3,7 @@
lobbyenabled = false
# Dev map for faster loading & convenience
map = "Dev"
role_timers = false
[physics]
# Makes mapping annoying

View File

@@ -29,6 +29,7 @@ holiday-name-labor-day = Labor Day
holiday-name-firefighter-day = Firefighter's Day
holiday-name-mothers-day = Mother's Day
holiday-name-owl-and-pussycat-day = Owl and Pussycat Day
holiday-name-towel-day = Towel Day
holiday-name-mommi-day = MoMMI Day
holiday-name-garbage-day = Garbage Day
holiday-name-international-picnic-day = International Picnic Day

View File

@@ -40,9 +40,6 @@ ent-CrateVendingMachineRestockSeeds = { ent-CrateVendingMachineRestockSeedsFille
ent-CrateVendingMachineRestockSmokes = { ent-CrateVendingMachineRestockSmokesFilled }
.desc = { ent-CrateVendingMachineRestockSmokesFilled.desc }
ent-CrateVendingMachineRestockSnacks = { ent-CrateVendingMachineRestockSnacksFilled }
.desc = { ent-CrateVendingMachineRestockSnacksFilled.desc }
ent-CrateVendingMachineRestockVendomat = { ent-CrateVendingMachineRestockVendomatFilled }
.desc = { ent-CrateVendingMachineRestockVendomatFilled.desc }
@@ -54,3 +51,15 @@ ent-CrateVendingMachineRestockTankDispenser = { ent-CrateVendingMachineRestockTa
ent-CrateVendingMachineRestockHappyHonk = { ent-CrateVendingMachineRestockHappyHonkFilled }
.desc = { ent-CrateVendingMachineRestockHappyHonkFilled.desc }
ent-CrateVendingMachineRestockGetmoreChocolateCorp = { ent-CrateVendingMachineRestockGetmoreChocolateCorpFilled }
.desc = { ent-CrateVendingMachineRestockGetmoreChocolateCorpFilled.desc }
ent-CrateVendingMachineRestockChang = { ent-CrateVendingMachineRestockChangFilled }
.desc = { ent-CrateVendingMachineRestockChangFilled.desc }
ent-CrateVendingMachineRestockDiscountDans = { ent-CrateVendingMachineRestockDiscountDansFilled }
.desc = { ent-CrateVendingMachineRestockDiscountDansFilled.desc }
ent-CrateVendingMachineRestockDonut = { ent-CrateVendingMachineRestockDonutFilled }
.desc = { ent-CrateVendingMachineRestockDonutFilled.desc }

View File

@@ -40,9 +40,6 @@ ent-CrateVendingMachineRestockSeedsFilled = MegaSeed restock crate
ent-CrateVendingMachineRestockSmokesFilled = ShadyCigs restock crate
.desc = Contains two restock boxes for the ShadyCigs vending machine.
ent-CrateVendingMachineRestockSnacksFilled = Snack restock crate
.desc = Contains four restock boxes, each covering a different snack vendor. Mr. Chang's, Discount Dans, Robust Donuts, and Getmore Chocolate are featured on the advertisement.
ent-CrateVendingMachineRestockVendomatFilled = Vendomat restock crate
.desc = Contains a restock box for a Vendomat vending machine.
@@ -55,3 +52,14 @@ ent-CrateVendingMachineRestockTankDispenserFilled = Tank dispenser restock crate
ent-CrateVendingMachineRestockHappyHonkFilled = Happy honk restock crate
.desc = Contains a restock box for a happy honk dispenser.
ent-CrateVendingMachineRestockGetmoreChocolateCorpFilled = Getmore Chocolate Corp restock crate
.desc = Contains a restock box for a Getmore Chocolate Corp dispenser.
ent-CrateVendingMachineRestockChangFilled = Chang restock crate
.desc = Contains a restock box for a Mr. Chang dispenser.
ent-CrateVendingMachineRestockDiscountDansFilled = Discount Dans restock crate
.desc = Contains a restock box for a Discount Dan's dispenser.
ent-CrateVendingMachineRestockDonutFilled = Donut restock crate
.desc = Contains a restock box for a Monkin' Donuts dispenser.

View File

@@ -169,6 +169,9 @@ uplink-hot-potato-desc = Once activated, this time bomb can't be dropped - only
uplink-chimp-ammo-name = Box of 10 Omega Cartridges.
uplink-chimp-ammo-desc = A box of 10 omega particle cartridges for the CHIMP. Omega particles inflict severe burns and cause anomalies to go supercritical.
uplink-proximity-mine-name = Proximity Mine
uplink-proximity-mine-desc = A mine disguised as a wet floor sign.
# Armor
uplink-chameleon-name = Chameleon Kit
uplink-chameleon-desc = A backpack full of items that contain chameleon technology allowing you to disguise as pretty much anything on the station, and more!

File diff suppressed because it is too large Load Diff

View File

@@ -14025,7 +14025,7 @@ entities:
- pos: -34.5,17.5
parent: 8364
type: Transform
- proto: BaseBigBox
- proto: BigBox
entities:
- uid: 25967
components:

File diff suppressed because it is too large Load Diff

View File

@@ -27622,7 +27622,7 @@ entities:
- pos: -23.5,33.5
parent: 13329
type: Transform
- proto: BaseBigBox
- proto: BigBox
entities:
- uid: 11622
components:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -76,6 +76,16 @@
category: Medical
group: market
- type: cargoProduct
id: CrateVendingMachineRestockChemVend
icon:
sprite: Objects/Specific/Service/vending_machine_restock.rsi
state: base
product: CrateVendingMachineRestockChemVendFilled
cost: 3500
category: Medical
group: market
- type: cargoProduct
id: CrateVendingMachineRestockNutriMax
icon:
@@ -146,16 +156,6 @@
category: Service
group: market
- type: cargoProduct
id: CrateVendingMachineRestockSnacks
icon:
sprite: Objects/Specific/Service/vending_machine_restock.rsi
state: base
product: CrateVendingMachineRestockSnacksFilled
cost: 3000
category: Service
group: market
- type: cargoProduct
id: CrateVendingMachineRestockVendomat
icon:
@@ -195,3 +195,43 @@
cost: 2100
category: Service
group: market
- type: cargoProduct
id: CrateVendingMachineRestockGetmoreChocolateCorp
icon:
sprite: Objects/Specific/Service/vending_machine_restock.rsi
state: base
product: CrateVendingMachineRestockGetmoreChocolateCorpFilled
cost: 1200
category: Service
group: market
- type: cargoProduct
id: CrateVendingMachineRestockChang
icon:
sprite: Objects/Specific/Service/vending_machine_restock.rsi
state: base
product: CrateVendingMachineRestockChangFilled
cost: 1200
category: Service
group: market
- type: cargoProduct
id: CrateVendingMachineRestockDiscountDans
icon:
sprite: Objects/Specific/Service/vending_machine_restock.rsi
state: base
product: CrateVendingMachineRestockDiscountDansFilled
cost: 1200
category: Service
group: market
- type: cargoProduct
id: CrateVendingMachineRestockDonut
icon:
sprite: Objects/Specific/Service/vending_machine_restock.rsi
state: base
product: CrateVendingMachineRestockDonutFilled
cost: 1200
category: Service
group: market

View File

@@ -279,6 +279,7 @@
- id: BoxLightMixed
- id: Soap
- id: CrowbarRed
- id: AdvMopItem
- type: entity
noSpawn: true

View File

@@ -56,6 +56,14 @@
contents:
- id: VendingMachineRestockMedical
- type: entity
id: CrateVendingMachineRestockChemVendFilled
parent: CrateMedicalSecure
components:
- type: StorageFill
contents:
- id: VendingMachineRestockChemVend
- type: entity
id: CrateVendingMachineRestockNutriMaxFilled
parent: CrateHydroSecure
@@ -114,17 +122,6 @@
- id: VendingMachineRestockSmokes
amount: 2
- type: entity
id: CrateVendingMachineRestockSnacksFilled
parent: CratePlastic
components:
- type: StorageFill
contents:
- id: VendingMachineRestockChang
- id: VendingMachineRestockDiscountDans
- id: VendingMachineRestockDonut
- id: VendingMachineRestockGetmoreChocolateCorp
- type: entity
id: CrateVendingMachineRestockVendomatFilled
parent: CratePlastic
@@ -157,3 +154,39 @@
contents:
- id: VendingMachineRestockHappyHonk
amount: 2
- type: entity
id: CrateVendingMachineRestockGetmoreChocolateCorpFilled
parent: CratePlastic
components:
- type: StorageFill
contents:
- id: VendingMachineRestockGetmoreChocolateCorp
amount: 2
- type: entity
id: CrateVendingMachineRestockChangFilled
parent: CratePlastic
components:
- type: StorageFill
contents:
- id: VendingMachineRestockChang
amount: 2
- type: entity
id: CrateVendingMachineRestockDiscountDansFilled
parent: CratePlastic
components:
- type: StorageFill
contents:
- id: VendingMachineRestockDiscountDans
amount: 2
- type: entity
id: CrateVendingMachineRestockDonutFilled
parent: CratePlastic
components:
- type: StorageFill
contents:
- id: VendingMachineRestockDonut
amount: 2

View File

@@ -118,101 +118,94 @@
- id: ClothingMaskGas
- type: entity
parent: GunSafe
id: GunSafeDisabler
suffix: Disabler
parent: GunSafe
name: gun safe disabler
name: disabler safe
components:
- type: StorageFill
contents:
- id: WeaponDisabler
amount: 5
- id: WeaponDisabler
amount: 5
- type: entity
parent: GunSafe
id: GunSafePistolMk58
suffix: MK58
parent: GunSafe
name: gun safe Mk58
name: mk58 safe
components:
- type: StorageFill
contents:
- id: WeaponPistolMk58
amount: 4
- id: MagazinePistol
amount: 8
- id: WeaponPistolMk58
amount: 4
- id: MagazinePistol
amount: 8
- type: entity
parent: GunSafe
id: GunSafeRifleLecter
suffix: Lecter
parent: GunSafe
name: gun safe lecter
name: lecter safe
components:
- type: StorageFill
contents:
- id: WeaponRifleLecter
amount: 2
- id: MagazineRifle
amount: 4
- id: WeaponRifleLecter
amount: 2
- id: MagazineRifle
amount: 4
- type: entity
id: GunSafeSubMachineGunVector
suffix: Vector
parent: GunSafe
name: gun safe vector
id: GunSafeSubMachineGunDrozd
name: drozd safe
components:
- type: StorageFill
contents:
- id: WeaponSubMachineGunVector
amount: 2
- id: MagazineMagnumSubMachineGun
amount: 4
- id: WeaponSubMachineGunDrozd
amount: 2
- id: MagazinePistolSubMachineGun
amount: 4
- type: entity
parent: GunSafe
id: GunSafeShotgunEnforcer
suffix: Enforcer
parent: GunSafe
name: gun safe enforcer
name: enforcer safe
components:
- type: StorageFill
contents:
- id: WeaponShotgunEnforcer
amount: 2
- id: MagazineShotgun
amount: 4
- id: WeaponShotgunEnforcer
amount: 2
- id: MagazineShotgun
amount: 4
- type: entity
id: GunSafeShotgunKammerer
suffix: Kammerer
parent: GunSafe
name: gun safe kammerer
id: GunSafeShotgunKammerer
name: kammerer safe
components:
- type: StorageFill
contents:
- id: WeaponShotgunKammerer
amount: 2
- id: MagazineShotgun
amount: 4
- id: WeaponShotgunKammerer
amount: 2
- id: MagazineShotgun
amount: 4
- type: entity
id: GunSafeSubMachineGunWt550
suffix: Wt550
parent: GunSafe
name: gun safe Wt550
name: wt550 safe
components:
- type: StorageFill
contents:
- id: WeaponSubMachineGunWt550
amount: 2
- id: MagazinePistolSubMachineGun
amount: 4
- id: WeaponSubMachineGunWt550
amount: 2
- id: MagazinePistolSubMachineGun
amount: 4
- type: entity
id: GunSafeLaserCarbine
suffix: Laser Carbine
parent: GunSafe
name: gun safe laser carbine
id: GunSafeLaserCarbine
name: laser safe
components:
- type: StorageFill
contents:
- id: WeaponLaserCarbine
amount: 3
- id: WeaponLaserCarbine
amount: 3

View File

@@ -10,7 +10,7 @@
FoodCondimentBottleHotsauce: 1
FoodCondimentBottleKetchup: 1
FoodCondimentBottleBBQ: 1
FoodCondimentBottleSmallVinegar: 2
FoodCondimentBottleVinegar: 2
ReagentContainerOliveoil: 2
MonkeyCubeBox: 1
FoodContainerEgg: 1
@@ -18,4 +18,4 @@
ReagentContainerMilkSoy: 1
FoodButter: 4
FoodCheese: 1
FoodMeat: 6
FoodMeat: 6

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