mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-02-15 01:15:13 +01:00
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:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -306,3 +306,6 @@ Resources/MapImages
|
||||
/Content.Docfx/*site
|
||||
|
||||
*.bak
|
||||
|
||||
# Direnv stuff
|
||||
.direnv/
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
|
||||
namespace Content.Client.Chemistry
|
||||
{
|
||||
public sealed class ChemicalReactionSystem : SharedChemicalReactionSystem
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
84
Content.Server/DeviceLinking/Systems/OrGateSystem.cs
Normal file
84
Content.Server/DeviceLinking/Systems/OrGateSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
28
Content.Server/Gatherable/GatherableSystem.Projectile.cs
Normal file
28
Content.Server/Gatherable/GatherableSystem.Projectile.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.DeviceLinking.Components;
|
||||
using Content.Server.MachineLinking.Events;
|
||||
using Content.Server.MachineLinking.System;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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++)
|
||||
{
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)}");
|
||||
|
||||
@@ -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!;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
15
Content.Shared/Actions/Events/DisarmAttemptEvent.cs
Normal file
15
Content.Shared/Actions/Events/DisarmAttemptEvent.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
5
Content.Shared/Wieldable/BeforeUnwieldEvent.cs
Normal file
5
Content.Shared/Wieldable/BeforeUnwieldEvent.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace Content.Shared.Wieldable;
|
||||
|
||||
public sealed class BeforeUnwieldEvent : CancellableEntityEventArgs
|
||||
{
|
||||
}
|
||||
5
Content.Shared/Wieldable/BeforeWieldEvent.cs
Normal file
5
Content.Shared/Wieldable/BeforeWieldEvent.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace Content.Shared.Wieldable;
|
||||
|
||||
public sealed class BeforeWieldEvent : CancellableEntityEventArgs
|
||||
{
|
||||
}
|
||||
@@ -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!;
|
||||
}
|
||||
35
Content.Shared/Wieldable/Components/WieldableComponent.cs
Normal file
35
Content.Shared/Wieldable/Components/WieldableComponent.cs
Normal 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;
|
||||
}
|
||||
23
Content.Shared/Wieldable/ItemUnwieldedEvent.cs
Normal file
23
Content.Shared/Wieldable/ItemUnwieldedEvent.cs
Normal 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
|
||||
232
Content.Shared/Wieldable/WieldableSystem.cs
Normal file
232
Content.Shared/Wieldable/WieldableSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
4
Resources/Audio/Voice/Human/attributions.yml
Normal file
4
Resources/Audio/Voice/Human/attributions.yml
Normal 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"
|
||||
BIN
Resources/Audio/Voice/Human/female_sigh.ogg
Normal file
BIN
Resources/Audio/Voice/Human/female_sigh.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Voice/Human/male_sigh.ogg
Normal file
BIN
Resources/Audio/Voice/Human/male_sigh.ogg
Normal file
Binary file not shown.
@@ -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'
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
lobbyenabled = false
|
||||
# Dev map for faster loading & convenience
|
||||
map = "Dev"
|
||||
role_timers = false
|
||||
|
||||
[physics]
|
||||
# Makes mapping annoying
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
@@ -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.
|
||||
@@ -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!
|
||||
|
||||
21699
Resources/Maps/aspid.yml
21699
Resources/Maps/aspid.yml
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
@@ -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
|
||||
@@ -279,6 +279,7 @@
|
||||
- id: BoxLightMixed
|
||||
- id: Soap
|
||||
- id: CrowbarRed
|
||||
- id: AdvMopItem
|
||||
|
||||
- type: entity
|
||||
noSpawn: true
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user