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

# Conflicts:
#	Resources/Prototypes/Entities/Markers/Spawners/Random/posters.yml
#	Resources/Prototypes/Maps/origin.yml
#	Resources/Textures/Interface/Misc/job_icons.rsi/meta.json
#	Resources/Textures/Tiles/dark.png
#	Resources/Textures/Tiles/white.png
This commit is contained in:
Morbo 2022-10-28 17:19:32 +03:00
commit 228c19c52c
302 changed files with 431791 additions and 373490 deletions

View file

@ -1,6 +1,7 @@
name: Update Wiki
on:
workflow_dispatch:
push:
branches: [ master, jsondump ]
paths:

View file

@ -32,7 +32,7 @@ public sealed class AccessOverlay : Overlay
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
foreach (var ent in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldAABB,
LookupFlags.Anchored | LookupFlags.Approximate))
LookupFlags.Static | LookupFlags.Approximate))
{
if (!readerQuery.TryGetComponent(ent, out var reader) ||
!xformQuery.TryGetComponent(ent, out var xform))

View file

@ -172,7 +172,7 @@ namespace Content.Client.Audio
var ambientQuery = GetEntityQuery<AmbientSoundComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
foreach (var entity in _lookup.GetEntitiesInRange(coordinates, _maxAmbientRange + RangeBuffer, LookupFlags.Anchored | LookupFlags.Approximate))
foreach (var entity in _lookup.GetEntitiesInRange(coordinates, _maxAmbientRange + RangeBuffer))
{
if (!ambientQuery.TryGetComponent(entity, out var ambientComp) ||
!ambientComp.Enabled ||

View file

@ -1,7 +1,13 @@
using Content.Shared.Body.Systems;
using Content.Shared.Body.Components;
using Content.Shared.Body.Prototypes;
using Content.Shared.Body.Systems;
namespace Content.Client.Body.Systems;
public sealed class BodySystem : SharedBodySystem
{
protected override void InitBody(BodyComponent body, BodyPrototype prototype)
{
return;
}
}

View file

@ -39,6 +39,7 @@ namespace Content.Client.Inventory
public override void Initialize()
{
UpdatesOutsidePrediction = true;
base.Initialize();
SubscribeLocalEvent<ClientInventoryComponent, PlayerAttachedEvent>(OnPlayerAttached);
@ -162,7 +163,7 @@ namespace Content.Client.Inventory
public void ReloadInventory(ClientInventoryComponent? component = null)
{
var player = _playerManager.LocalPlayer?.ControlledEntity;
if (player == null || !Resolve(player.Value, ref component))
if (player == null || !Resolve(player.Value, ref component, false))
{
return;
}

View file

@ -1,16 +1,11 @@
using System.Collections.Generic;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Kitchen.Components;
using Content.Shared.Kitchen;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
namespace Content.Client.Kitchen.UI
@ -68,15 +63,15 @@ namespace Content.Client.Kitchen.UI
{
switch (message)
{
case SharedReagentGrinderComponent.ReagentGrinderWorkStartedMessage workStarted:
case ReagentGrinderWorkStartedMessage workStarted:
GrindButton.Disabled = true;
GrindButton.Modulate = workStarted.GrinderProgram == SharedReagentGrinderComponent.GrinderProgram.Grind ? Color.Green : Color.White;
GrindButton.Modulate = workStarted.GrinderProgram == GrinderProgram.Grind ? Color.Green : Color.White;
JuiceButton.Disabled = true;
JuiceButton.Modulate = workStarted.GrinderProgram == SharedReagentGrinderComponent.GrinderProgram.Juice ? Color.Green : Color.White;
JuiceButton.Modulate = workStarted.GrinderProgram == GrinderProgram.Juice ? Color.Green : Color.White;
BeakerContentBox.EjectButton.Disabled = true;
ChamberContentBox.EjectButton.Disabled = true;
break;
case SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage:
case ReagentGrinderWorkCompleteMessage:
GrindButton.Disabled = false;
JuiceButton.Disabled = false;
GrindButton.Modulate = Color.White;

View file

@ -1,11 +1,8 @@
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Kitchen.Components;
using Content.Shared.Kitchen;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using static Content.Shared.Chemistry.Components.Solution;
namespace Content.Client.Kitchen.UI
{
@ -55,10 +52,10 @@ namespace Content.Client.Kitchen.UI
_menu?.HandleMessage(message);
}
public void StartGrinding(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage());
public void StartJuicing(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderJuiceStartMessage());
public void EjectAll(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage());
public void EjectBeaker(BaseButton.ButtonEventArgs? args = null) => SendMessage(new ItemSlotButtonPressedEvent(SharedReagentGrinderComponent.BeakerSlotId));
public void EjectChamberContent(EntityUid uid) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberContentMessage(uid));
public void StartGrinding(BaseButton.ButtonEventArgs? args = null) => SendMessage(new ReagentGrinderStartMessage(GrinderProgram.Grind));
public void StartJuicing(BaseButton.ButtonEventArgs? args = null) => SendMessage(new ReagentGrinderStartMessage(GrinderProgram.Juice));
public void EjectAll(BaseButton.ButtonEventArgs? args = null) => SendMessage(new ReagentGrinderEjectChamberAllMessage());
public void EjectBeaker(BaseButton.ButtonEventArgs? args = null) => SendMessage(new ItemSlotButtonPressedEvent(SharedReagentGrinder.BeakerSlotId));
public void EjectChamberContent(EntityUid uid) => SendMessage(new ReagentGrinderEjectChamberContentMessage(uid));
}
}

View file

@ -1,7 +1,5 @@
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using static Content.Shared.Kitchen.Components.SharedReagentGrinderComponent;
using Content.Shared.Kitchen;
namespace Content.Client.Kitchen.Visualizers
{

View file

@ -4,6 +4,9 @@
x:Class="Content.Client.Options.UI.Tabs.NetworkTab">
<BoxContainer Orientation="Vertical" >
<BoxContainer Orientation="Vertical" Margin="8 8 8 8" VerticalExpand="True">
<BoxContainer Orientation="Horizontal" Margin="4 10 4 0">
<CheckBox Name="NetPredictCheckbox" Text="{Loc 'ui-options-net-predict'}" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="4 10 4 0">
<Label Text="{Loc 'ui-options-net-interp-ratio'}" />
<Control MinSize="8 0" />
@ -31,12 +34,26 @@
<Label Name="NetPredictTickBiasLabel" MinSize="48 0" Align="Right" />
<Control MinSize="4 0"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="4 10 4 0">
<Label Text="{Loc 'ui-options-net-pvs-spawn'}" />
<Control MinSize="8 0" />
<Slider Name="NetPvsSpawnSlider"
ToolTip="{Loc 'ui-options-net-pvs-spawn-tooltip'}"
MaxValue="150"
MinValue="20"
HorizontalExpand="True"
MinSize="80 0"
Rounded="True" />
<Control MinSize="8 0" />
<Label Name="NetPvsSpawnLabel" MinSize="48 0" Align="Right" />
<Control MinSize="4 0"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="4 10 4 0">
<Label Text="{Loc 'ui-options-net-pvs-entry'}" />
<Control MinSize="8 0" />
<Slider Name="NetPvsEntrySlider"
ToolTip="{Loc 'ui-options-net-pvs-entry-tooltip'}"
MaxValue="150"
MaxValue="500"
MinValue="20"
HorizontalExpand="True"
MinSize="80 0"

View file

@ -24,9 +24,11 @@ namespace Content.Client.Options.UI.Tabs
ApplyButton.OnPressed += OnApplyButtonPressed;
ResetButton.OnPressed += OnResetButtonPressed;
DefaultButton.OnPressed += OnDefaultButtonPressed;
NetPredictCheckbox.OnToggled += OnPredictToggled;
NetInterpRatioSlider.OnValueChanged += OnSliderChanged;
NetInterpRatioSlider.MinValue = _stateMan.MinBufferSize;
NetPredictTickBiasSlider.OnValueChanged += OnSliderChanged;
NetPvsSpawnSlider.OnValueChanged += OnSliderChanged;
NetPvsEntrySlider.OnValueChanged += OnSliderChanged;
NetPvsLeaveSlider.OnValueChanged += OnSliderChanged;
@ -38,13 +40,20 @@ namespace Content.Client.Options.UI.Tabs
ApplyButton.OnPressed -= OnApplyButtonPressed;
ResetButton.OnPressed -= OnResetButtonPressed;
DefaultButton.OnPressed -= OnDefaultButtonPressed;
NetPredictCheckbox.OnToggled -= OnPredictToggled;
NetInterpRatioSlider.OnValueChanged -= OnSliderChanged;
NetPredictTickBiasSlider.OnValueChanged -= OnSliderChanged;
NetPvsSpawnSlider.OnValueChanged -= OnSliderChanged;
NetPvsEntrySlider.OnValueChanged -= OnSliderChanged;
NetPvsLeaveSlider.OnValueChanged -= OnSliderChanged;
base.Dispose(disposing);
}
private void OnPredictToggled(BaseButton.ButtonToggledEventArgs obj)
{
UpdateChanges();
}
private void OnSliderChanged(Robust.Client.UserInterface.Controls.Range range)
{
UpdateChanges();
@ -54,8 +63,10 @@ namespace Content.Client.Options.UI.Tabs
{
_cfg.SetCVar(CVars.NetBufferSize, (int) NetInterpRatioSlider.Value - _stateMan.MinBufferSize);
_cfg.SetCVar(CVars.NetPredictTickBias, (int) NetPredictTickBiasSlider.Value);
_cfg.SetCVar(CVars.NetPVSEntityBudget, (int) NetPvsEntrySlider.Value);
_cfg.SetCVar(CVars.NetPVSEntityBudget, (int) NetPvsSpawnSlider.Value);
_cfg.SetCVar(CVars.NetPVSEntityEnterBudget, (int) NetPvsEntrySlider.Value);
_cfg.SetCVar(CVars.NetPVSEntityExitBudget, (int) NetPvsLeaveSlider.Value);
_cfg.SetCVar(CVars.NetPredict, NetPredictCheckbox.Pressed);
_cfg.SaveToFile();
UpdateChanges();
@ -69,7 +80,8 @@ namespace Content.Client.Options.UI.Tabs
private void OnDefaultButtonPressed(BaseButton.ButtonEventArgs obj)
{
NetPredictTickBiasSlider.Value = CVars.NetPredictTickBias.DefaultValue;
NetPvsEntrySlider.Value = CVars.NetPVSEntityBudget.DefaultValue;
NetPvsSpawnSlider.Value = CVars.NetPVSEntityBudget.DefaultValue;
NetPvsEntrySlider.Value = CVars.NetPVSEntityEnterBudget.DefaultValue;
NetPvsLeaveSlider.Value = CVars.NetPVSEntityExitBudget.DefaultValue;
// Apparently default value doesn't get updated when using override defaults, so using a const
@ -82,8 +94,10 @@ namespace Content.Client.Options.UI.Tabs
{
NetInterpRatioSlider.Value = _cfg.GetCVar(CVars.NetBufferSize) + _stateMan.MinBufferSize;
NetPredictTickBiasSlider.Value = _cfg.GetCVar(CVars.NetPredictTickBias);
NetPvsEntrySlider.Value = _cfg.GetCVar(CVars.NetPVSEntityBudget);
NetPvsSpawnSlider.Value = _cfg.GetCVar(CVars.NetPVSEntityBudget);
NetPvsEntrySlider.Value = _cfg.GetCVar(CVars.NetPVSEntityEnterBudget);
NetPvsLeaveSlider.Value = _cfg.GetCVar(CVars.NetPVSEntityExitBudget);
NetPredictCheckbox.Pressed = _cfg.GetCVar(CVars.NetPredict);
UpdateChanges();
}
@ -92,15 +106,21 @@ namespace Content.Client.Options.UI.Tabs
var isEverythingSame =
NetInterpRatioSlider.Value == _cfg.GetCVar(CVars.NetBufferSize) + _stateMan.MinBufferSize &&
NetPredictTickBiasSlider.Value == _cfg.GetCVar(CVars.NetPredictTickBias) &&
NetPvsEntrySlider.Value == _cfg.GetCVar(CVars.NetPVSEntityBudget) &&
NetPredictCheckbox.Pressed == _cfg.GetCVar(CVars.NetPredict) &&
NetPvsSpawnSlider.Value == _cfg.GetCVar(CVars.NetPVSEntityBudget) &&
NetPvsEntrySlider.Value == _cfg.GetCVar(CVars.NetPVSEntityEnterBudget) &&
NetPvsLeaveSlider.Value == _cfg.GetCVar(CVars.NetPVSEntityExitBudget);
ApplyButton.Disabled = isEverythingSame;
ResetButton.Disabled = isEverythingSame;
NetInterpRatioLabel.Text = NetInterpRatioSlider.Value.ToString();
NetPredictTickBiasLabel.Text = NetPredictTickBiasSlider.Value.ToString();
NetPvsSpawnLabel.Text = NetPvsSpawnSlider.Value.ToString();
NetPvsEntryLabel.Text = NetPvsEntrySlider.Value.ToString();
NetPvsLeaveLabel.Text = NetPvsLeaveSlider.Value.ToString();
// TODO disable / grey-out the predict and interp sliders if prediction is disabled.
// Currently no option to do this, but should be added to the slider control in general
}
}
}

View file

@ -1099,6 +1099,7 @@ namespace Content.Client.Preferences.UI
private StripeBack _lockStripe;
private Label _requirementsLabel;
private Label _jobTitle;
public JobPrioritySelector(JobPrototype job)
{
@ -1156,13 +1157,25 @@ namespace Content.Client.Preferences.UI
}
};
_jobTitle = new Label()
{
Text = job.LocalizedName,
MinSize = (175, 0)
};
if (job.LocalizedDescription != null)
{
_jobTitle.ToolTip = job.LocalizedDescription;
_jobTitle.TooltipDelay = 0.2f;
}
AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
icon,
new Label {Text = job.LocalizedName, MinSize = (175, 0)},
_jobTitle,
_optionButton,
_lockStripe,
}
@ -1227,6 +1240,12 @@ namespace Content.Client.Preferences.UI
_checkBox = new CheckBox {Text = $"{antag.Name}"};
_checkBox.OnToggled += OnCheckBoxToggled;
if (antag.Description != null)
{
_checkBox.ToolTip = antag.Description;
_checkBox.TooltipDelay = 0.2f;
}
AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
@ -1263,6 +1282,12 @@ namespace Content.Client.Preferences.UI
_checkBox = new CheckBox {Text = $"{trait.Name}"};
_checkBox.OnToggled += OnCheckBoxToggled;
if (trait.Description != null)
{
_checkBox.ToolTip = trait.Description;
_checkBox.TooltipDelay = 0.2f;
}
AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,

View file

@ -1,4 +1,5 @@
using Content.Shared.Chat;
using Content.Shared.Input;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.UserInterface.Systems.Chat.Controls;
@ -32,7 +33,7 @@ public class ChatInputBox : PanelContainer
Input = new HistoryLineEdit
{
Name = "Input",
PlaceHolder = Loc.GetString("hud-chatbox-info"),
PlaceHolder = Loc.GetString("hud-chatbox-info", ("talk-key", BoundKeyHelper.ShortKeyName(ContentKeyFunctions.FocusChat)), ("cycle-key", BoundKeyHelper.ShortKeyName(ContentKeyFunctions.CycleChatChannelForward))),
HorizontalExpand = true,
StyleClasses = {"chatLineEdit"}
};

View file

@ -263,21 +263,6 @@ namespace Content.Client.Verbs
EntityManager.RaisePredictiveEvent(new ExecuteVerbEvent(target, verb));
}
public override void ExecuteVerb(Verb verb, EntityUid user, EntityUid target, bool forced = false)
{
// invoke any relevant actions
verb.Act?.Invoke();
// Maybe raise a local event
if (verb.ExecutionEventArgs != null)
{
if (verb.EventTarget.IsValid())
RaiseLocalEvent(verb.EventTarget, verb.ExecutionEventArgs, true);
else
RaiseLocalEvent(verb.ExecutionEventArgs);
}
}
private void HandleVerbResponse(VerbsResponseEvent msg)
{
if (!VerbMenu.RootMenu.Visible || VerbMenu.CurrentTarget != msg.Entity)

View file

@ -1,4 +1,5 @@
using Content.Shared.Examine;
using Content.Shared.Weapons.Ranged;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events;
using Robust.Shared.Containers;
@ -12,16 +13,18 @@ public sealed partial class GunSystem
base.InitializeChamberMagazine();
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, AmmoCounterControlEvent>(OnChamberMagazineCounter);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, UpdateAmmoCounterEvent>(OnChamberMagazineAmmoUpdate);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, EntRemovedFromContainerMessage>(OnChamberEntRemove);
}
private void OnChamberEntRemove(EntityUid uid, ChamberMagazineAmmoProviderComponent component, EntRemovedFromContainerMessage args)
protected override void OnMagazineSlotChange(EntityUid uid, MagazineAmmoProviderComponent component, ContainerModifiedMessage args)
{
if (args.Container.ID != ChamberSlot) return;
base.OnMagazineSlotChange(uid, component, args);
if (ChamberSlot != args.Container.ID || args is not EntRemovedFromContainerMessage removedArgs)
return;
// This is dirty af. Prediction moment.
// We may be predicting spawning entities and the engine just removes them from the container so we'll just delete them.
if (args.Entity.IsClientSide())
if (removedArgs.Entity.IsClientSide())
QueueDel(args.Entity);
// AFAIK the only main alternative is having some client-specific handling via a bool or otherwise for the state.

View file

@ -34,28 +34,30 @@ namespace Content.IntegrationTests.Tests.Chemistry
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
var testMap = await PoolManager.CreateTestMap(pairTracker);
var coordinates = testMap.GridCoords;
var solutionSystem = server.ResolveDependency<IEntitySystemManager>()
.GetEntitySystem<SolutionContainerSystem>();
foreach (var reactionPrototype in prototypeManager.EnumeratePrototypes<ReactionPrototype>())
{
//since i have no clue how to isolate each loop assert-wise im just gonna throw this one in for good measure
Console.WriteLine($"Testing {reactionPrototype.ID}");
EntityUid beaker;
EntityUid beaker = default;
Solution component = null;
await server.WaitAssertion(() =>
{
beaker = entityManager.SpawnEntity("TestSolutionContainer", coordinates);
Assert.That(EntitySystem.Get<SolutionContainerSystem>()
Assert.That(solutionSystem
.TryGetSolution(beaker, "beaker", out component));
foreach (var (id, reactant) in reactionPrototype.Reactants)
{
Assert.That(EntitySystem.Get<SolutionContainerSystem>()
Assert.That(solutionSystem
.TryAddReagent(beaker, component, id, reactant.Amount, out var quantity));
Assert.That(reactant.Amount, Is.EqualTo(quantity));
}
EntitySystem.Get<SolutionContainerSystem>().SetTemperature(beaker, component, reactionPrototype.MinimumTemperature);
solutionSystem.SetTemperature(beaker, component, reactionPrototype.MinimumTemperature);
});
await server.WaitIdleAsync();

View file

@ -1,7 +1,8 @@
using Content.Shared.Damage;
using Content.Shared.Damage;
using Content.Server.Examine;
using Content.Shared.Verbs;
using Robust.Shared.Utility;
using Content.Shared.Inventory;
namespace Content.Server.Armor
{
@ -14,13 +15,13 @@ namespace Content.Server.Armor
{
base.Initialize();
SubscribeLocalEvent<ArmorComponent, DamageModifyEvent>(OnDamageModify);
SubscribeLocalEvent<ArmorComponent, InventoryRelayedEvent<DamageModifyEvent>>(OnDamageModify);
SubscribeLocalEvent<ArmorComponent, GetVerbsEvent<ExamineVerb>>(OnArmorVerbExamine);
}
private void OnDamageModify(EntityUid uid, ArmorComponent component, DamageModifyEvent args)
private void OnDamageModify(EntityUid uid, ArmorComponent component, InventoryRelayedEvent<DamageModifyEvent> args)
{
args.Damage = DamageSpecifier.ApplyModifierSet(args.Damage, component.Modifiers);
args.Args.Damage = DamageSpecifier.ApplyModifierSet(args.Args.Damage, component.Modifiers);
}
private void OnArmorVerbExamine(EntityUid uid, ArmorComponent component, GetVerbsEvent<ExamineVerb> args)

View file

@ -136,7 +136,7 @@ public sealed class BeamSystem : SharedBeamSystem
/// <param name="controller"></param>
public void TryCreateBeam(EntityUid user, EntityUid target, string bodyPrototype, string? bodyState = null, string shader = "unshaded", EntityUid? controller = null)
{
if (!user.IsValid() || !target.IsValid())
if (Deleted(user) || Deleted(target))
return;
var userMapPos = Transform(user).MapPosition;

View file

@ -6,7 +6,9 @@ using Content.Server.Kitchen.Components;
using Content.Server.Mind.Components;
using Content.Shared.Body.Components;
using Content.Shared.Body.Part;
using Content.Shared.Body.Prototypes;
using Content.Shared.Body.Systems;
using Content.Shared.Coordinates;
using Content.Shared.Humanoid;
using Content.Shared.MobState.Components;
using Content.Shared.Movement.Events;
@ -113,6 +115,21 @@ public sealed class BodySystem : SharedBodySystem
return true;
}
protected override void InitBody(BodyComponent body, BodyPrototype prototype)
{
var root = prototype.Slots[prototype.Root];
var bodyId = Spawn(root.Part, body.Owner.ToCoordinates());
var partComponent = Comp<BodyPartComponent>(bodyId);
var slot = new BodyPartSlot(root.Part, body.Owner, partComponent.PartType);
body.Root = slot;
partComponent.Body = bodyId;
Containers.EnsureContainer<Container>(body.Owner, BodyContainerId);
AttachPart(bodyId, slot, partComponent);
InitPart(partComponent, prototype, prototype.Root);
}
public override HashSet<EntityUid> GibBody(EntityUid? bodyId, bool gibOrgans = false, BodyComponent? body = null)
{
if (bodyId == null || !Resolve(bodyId.Value, ref body, false))

View file

@ -54,7 +54,7 @@ public sealed partial class ChatSystem : SharedChatSystem
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
private const int VoiceRange = 7; // how far voice goes in world units
private const int VoiceRange = 10; // how far voice goes in world units
private const int WhisperRange = 2; // how far whisper goes in world units
private const string DefaultAnnouncementSound = "/Audio/Announcements/announce.ogg";

View file

@ -1,6 +1,9 @@
using System.Threading;
using Content.Shared.Chemistry.Components;
using Content.Shared.FixedPoint;
using Content.Server.UserInterface;
using Robust.Server.GameObjects;
using Content.Shared.Chemistry;
namespace Content.Server.Chemistry.Components
{
@ -22,6 +25,20 @@ namespace Content.Server.Chemistry.Components
[DataField("injectOnly")]
public bool InjectOnly;
/// <summary>
/// The minimum amount of solution that can be transferred at once from this solution.
/// </summary>
[DataField("minTransferAmount")]
[ViewVariables(VVAccess.ReadWrite)]
public FixedPoint2 MinimumTransferAmount { get; set; } = FixedPoint2.New(5);
/// <summary>
/// The maximum amount of solution that can be transferred at once from this solution.
/// </summary>
[DataField("maxTransferAmount")]
[ViewVariables(VVAccess.ReadWrite)]
public FixedPoint2 MaximumTransferAmount { get; set; } = FixedPoint2.New(50);
/// <summary>
/// Amount to inject or draw on each usage. If the injector is inject only, it will
/// attempt to inject it's entire contents upon use.

View file

@ -145,9 +145,12 @@ namespace Content.Server.Chemistry.Components
/// with the other area effects from the inception.</param>
public void React(float averageExposures)
{
if (!_entities.EntitySysManager.GetEntitySystem<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
if (!_entities.EntitySysManager.GetEntitySystem<SolutionContainerSystem>()
.TryGetSolution(Owner, SolutionName, out var solution) ||
solution.Contents.Count == 0)
{
return;
}
var xform = _entities.GetComponent<TransformComponent>(Owner);
if (!MapManager.TryGetGrid(xform.GridUid, out var mapGrid))
@ -158,6 +161,7 @@ namespace Content.Server.Chemistry.Components
var lookup = _entities.EntitySysManager.GetEntitySystem<EntityLookupSystem>();
var solutionFraction = 1 / Math.Floor(averageExposures);
var ents = lookup.GetEntitiesIntersecting(tile, LookupFlags.Uncontained).ToArray();
foreach (var reagentQuantity in solution.Contents.ToArray())
{
@ -173,14 +177,14 @@ namespace Content.Server.Chemistry.Components
}
// Touch every entity on the tile
foreach (var entity in lookup.GetEntitiesIntersecting(tile).ToArray())
foreach (var entity in ents)
{
chemistry.ReactionEntity(entity, ReactionMethod.Touch, reagent,
reagentQuantity.Quantity * solutionFraction, solution);
}
}
foreach (var entity in lookup.GetEntitiesIntersecting(tile).ToArray())
foreach (var entity in ents)
{
ReactWithEntity(entity, solutionFraction);
}

View file

@ -15,13 +15,22 @@ using Content.Shared.MobState.Components;
using Robust.Shared.GameStates;
using Robust.Shared.Player;
using System.Threading;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Content.Shared.Popups;
namespace Content.Server.Chemistry.EntitySystems;
public sealed partial class ChemistrySystem
{
/// <summary>
/// Default transfer amounts for the set-transfer verb.
/// </summary>
public static readonly List<int> TransferAmounts = new() { 5, 10, 15};
private void InitializeInjector()
{
SubscribeLocalEvent<InjectorComponent, GetVerbsEvent<AlternativeVerb>>(AddSetTransferVerbs);
SubscribeLocalEvent<InjectorComponent, SolutionChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<InjectorComponent, HandDeselectedEvent>(OnInjectorDeselected);
SubscribeLocalEvent<InjectorComponent, ComponentStartup>(OnInjectorStartup);
@ -33,6 +42,38 @@ public sealed partial class ChemistrySystem
SubscribeLocalEvent<InjectionCancelledEvent>(OnInjectionCancelled);
}
private void AddSetTransferVerbs(EntityUid uid, InjectorComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess || !args.CanInteract || args.Hands == null)
return;
if (!EntityManager.TryGetComponent<ActorComponent?>(args.User, out var actor))
return;
// Add specific transfer verbs according to the container's size
var priority = 0;
foreach (var amount in TransferAmounts)
{
if ( amount < component.MinimumTransferAmount.Int() || amount > component.MaximumTransferAmount.Int())
continue;
AlternativeVerb verb = new();
verb.Text = Loc.GetString("comp-solution-transfer-verb-amount", ("amount", amount));
verb.Category = VerbCategory.SetTransferAmount;
verb.Act = () =>
{
component.TransferAmount = FixedPoint2.New(amount);
args.User.PopupMessage(Loc.GetString("comp-solution-transfer-set-amount", ("amount", amount)));
};
// we want to sort by size, not alphabetically by the verb text.
verb.Priority = priority;
priority--;
args.Verbs.Add(verb);
}
}
private static void OnInjectionCancelled(InjectionCancelledEvent ev)
{
ev.Component.CancelToken = null;
@ -196,6 +237,10 @@ public sealed partial class ChemistrySystem
return;
var actualDelay = MathF.Max(component.Delay, 1f);
// Injections take 1 second longer per additional 5u
actualDelay += (float) component.TransferAmount / component.Delay - 1;
if (user != target)
{
// Create a pop-up for the target

View file

@ -4,13 +4,6 @@ namespace Content.Server.Coordinates.Helpers
{
public static class SnapgridHelper
{
public static void SnapToGrid(this EntityUid entity, IEntityManager? entMan = null, IMapManager? mapManager = null)
{
IoCManager.Resolve(ref entMan, ref mapManager);
var transform = entMan.GetComponent<TransformComponent>(entity);
transform.Coordinates = transform.Coordinates.SnapToGrid(entMan, mapManager);
}
public static EntityCoordinates SnapToGrid(this EntityCoordinates coordinates, IEntityManager? entMan = null, IMapManager? mapManager = null)
{
IoCManager.Resolve(ref entMan, ref mapManager);

View file

@ -56,6 +56,7 @@ namespace Content.Server.Cuffs
Verb verb = new()
{
Act = () => component.TryUncuff(args.User),
DoContactInteraction = true,
Text = Loc.GetString("uncuff-verb-get-data-text")
};
//TODO VERB ICON add uncuffing symbol? may re-use the alert symbol showing that you are currently cuffed?

View file

@ -11,9 +11,9 @@ using Content.Shared.Disease.Components;
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Item;
using Content.Shared.MobState.Components;
using Content.Shared.Rejuvenate;
using Robust.Shared.Player;
@ -46,8 +46,7 @@ namespace Content.Server.Disease
SubscribeLocalEvent<DiseaseCarrierComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<DiseaseCarrierComponent, CureDiseaseAttemptEvent>(OnTryCureDisease);
SubscribeLocalEvent<DiseaseCarrierComponent, RejuvenateEvent>(OnRejuvenate);
SubscribeLocalEvent<DiseasedComponent, UserInteractedWithItemEvent>(OnUserInteractDiseased);
SubscribeLocalEvent<DiseasedComponent, ItemInteractedWithEvent>(OnTargetInteractDiseased);
SubscribeLocalEvent<DiseasedComponent, ContactInteractionEvent>(OnContactInteraction);
SubscribeLocalEvent<DiseasedComponent, EntitySpokeEvent>(OnEntitySpeak);
SubscribeLocalEvent<DiseaseProtectionComponent, GotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<DiseaseProtectionComponent, GotUnequippedEvent>(OnUnequipped);
@ -256,17 +255,9 @@ namespace Content.Server.Disease
/// <summary>
/// When a diseased person interacts with something, check infection.
/// </summary>
private void OnUserInteractDiseased(EntityUid uid, DiseasedComponent component, UserInteractedWithItemEvent args)
private void OnContactInteraction(EntityUid uid, DiseasedComponent component, ContactInteractionEvent args)
{
InteractWithDiseased(args.User, args.Item);
}
/// <summary>
/// When a diseased person is interacted with, check infection.
/// </summary>
private void OnTargetInteractDiseased(EntityUid uid, DiseasedComponent component, ItemInteractedWithEvent args)
{
InteractWithDiseased(args.Item, args.User);
InteractWithDiseased(uid, args.Other);
}
private void OnEntitySpeak(EntityUid uid, DiseasedComponent component, EntitySpokeEvent args)

View file

@ -123,6 +123,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems
Verb verb = new()
{
Act = () => TryInsert(component.Owner, args.User, args.User),
DoContactInteraction = true,
Text = Loc.GetString("disposal-self-insert-verb-get-data-text")
};
// TODO VERN ICON

View file

@ -6,6 +6,7 @@ using Content.Shared.Physics;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Random;
using Robust.Shared.Timing;
@ -188,7 +189,7 @@ public sealed partial class ExplosionSystem : EntitySystem
/// Find entities on a grid tile using the EntityLookupComponent and apply explosion effects.
/// </summary>
/// <returns>True if the underlying tile can be uprooted, false if the tile is blocked by a dense entity</returns>
internal bool ExplodeTile(EntityLookupComponent lookup,
internal bool ExplodeTile(BroadphaseComponent lookup,
IMapGrid grid,
Vector2i tile,
float throwForce,
@ -209,7 +210,10 @@ public sealed partial class ExplosionSystem : EntitySystem
var state = (list, processed, xformQuery);
// get entities:
lookup.Tree.QueryAabb(ref state, GridQueryCallback, gridBox, true);
lookup.DynamicTree.QueryAabb(ref state, GridQueryCallback, gridBox, true);
lookup.StaticTree.QueryAabb(ref state, GridQueryCallback, gridBox, true);
lookup.SundriesTree.QueryAabb(ref state, GridQueryCallback, gridBox, true);
lookup.StaticSundriesTree.QueryAabb(ref state, GridQueryCallback, gridBox, true);
// process those entities
foreach (var xform in list)
@ -250,7 +254,8 @@ public sealed partial class ExplosionSystem : EntitySystem
return !tileBlocked;
list.Clear();
lookup.Tree.QueryAabb(ref state, GridQueryCallback, gridBox, true);
lookup.DynamicTree.QueryAabb(ref state, GridQueryCallback, gridBox, true);
lookup.SundriesTree.QueryAabb(ref state, GridQueryCallback, gridBox, true);
foreach (var xform in list)
{
@ -272,10 +277,18 @@ public sealed partial class ExplosionSystem : EntitySystem
return true;
}
private bool GridQueryCallback(
ref (List<TransformComponent> List, HashSet<EntityUid> Processed, EntityQuery<TransformComponent> XformQuery) state,
in FixtureProxy proxy)
{
var owner = proxy.Fixture.Body.Owner;
return GridQueryCallback(ref state, in owner);
}
/// <summary>
/// Same as <see cref="ExplodeTile"/>, but for SPAAAAAAACE.
/// </summary>
internal void ExplodeSpace(EntityLookupComponent lookup,
internal void ExplodeSpace(BroadphaseComponent lookup,
Matrix3 spaceMatrix,
Matrix3 invSpaceMatrix,
Vector2i tile,
@ -295,7 +308,10 @@ public sealed partial class ExplosionSystem : EntitySystem
var state = (list, processed, invSpaceMatrix, lookup.Owner, xformQuery, gridBox);
// get entities:
lookup.Tree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true);
lookup.DynamicTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true);
lookup.StaticTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true);
lookup.SundriesTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true);
lookup.StaticSundriesTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true);
foreach (var xform in state.Item1)
{
@ -309,7 +325,8 @@ public sealed partial class ExplosionSystem : EntitySystem
// Also, throw any entities that were spawned as shrapnel. Compared to entity spawning & destruction, this extra
// lookup is relatively minor computational cost, and throwing is disabled for nukes anyways.
list.Clear();
lookup.Tree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true);
lookup.DynamicTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true);
lookup.SundriesTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true);
foreach (var xform in list)
{
@ -342,6 +359,14 @@ public sealed partial class ExplosionSystem : EntitySystem
return true;
}
private bool SpaceQueryCallback(
ref (List<TransformComponent> List, HashSet<EntityUid> Processed, Matrix3 InvSpaceMatrix, EntityUid LookupOwner, EntityQuery<TransformComponent> XformQuery, Box2 GridBox) state,
in FixtureProxy proxy)
{
var owner = proxy.Fixture.Body.Owner;
return SpaceQueryCallback(ref state, in owner);
}
/// <summary>
/// This function actually applies the explosion affects to an entity.
/// </summary>
@ -460,7 +485,7 @@ sealed class Explosion
/// <summary>
/// Lookup component for this grid (or space/map).
/// </summary>
public EntityLookupComponent Lookup;
public BroadphaseComponent Lookup;
/// <summary>
/// The actual grid that this corresponds to. If null, this implies space.
@ -513,7 +538,7 @@ sealed class Explosion
// Variables used for enumerating over tiles, grids, etc
private DamageSpecifier _currentDamage = default!;
private EntityLookupComponent _currentLookup = default!;
private BroadphaseComponent _currentLookup = default!;
private IMapGrid? _currentGrid;
private float _currentIntensity;
private float _currentThrowForce;
@ -600,7 +625,7 @@ sealed class Explosion
_explosionData.Add(new()
{
TileLists = spaceData.TileLists,
Lookup = entMan.GetComponent<EntityLookupComponent>(mapUid),
Lookup = entMan.GetComponent<BroadphaseComponent>(mapUid),
MapGrid = null
});
@ -613,7 +638,7 @@ sealed class Explosion
_explosionData.Add(new()
{
TileLists = grid.TileLists,
Lookup = entMan.GetComponent<EntityLookupComponent>(grid.Grid.GridEntityId),
Lookup = entMan.GetComponent<BroadphaseComponent>(grid.Grid.GridEntityId),
MapGrid = grid.Grid
});
}

View file

@ -8,6 +8,7 @@ using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.Explosion;
using Content.Shared.GameTicking;
using Content.Shared.Inventory;
using Content.Shared.Throwing;
using Robust.Server.Player;
using Robust.Shared.Audio;
@ -63,6 +64,10 @@ public sealed partial class ExplosionSystem : EntitySystem
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved);
SubscribeLocalEvent<GridStartupEvent>(OnGridStartup);
SubscribeLocalEvent<ExplosionResistanceComponent, GetExplosionResistanceEvent>(OnGetResistance);
// as long as explosion-resistance mice are never added, this should be fine (otherwise a mouse-hat will transfer it's power to the wearer).
SubscribeLocalEvent<ExplosionResistanceComponent, InventoryRelayedEvent<GetExplosionResistanceEvent>>((e, c, ev) => OnGetResistance(e, c, ev.Args));
SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnReset);

View file

@ -159,6 +159,7 @@ public sealed class SpillableSystem : EntitySystem
};
}
verb.Impact = LogImpact.Medium; // dangerous reagent reaction are logged separately.
verb.DoContactInteraction = true;
args.Verbs.Add(verb);
}

View file

@ -1,5 +1,5 @@
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Item;
using Robust.Shared.Random;
namespace Content.Server.Forensics
@ -10,13 +10,13 @@ namespace Content.Server.Forensics
[Dependency] private readonly InventorySystem _inventory = default!;
public override void Initialize()
{
SubscribeLocalEvent<FingerprintComponent, UserInteractedWithItemEvent>(OnInteract);
SubscribeLocalEvent<FingerprintComponent, ContactInteractionEvent>(OnInteract);
SubscribeLocalEvent<FingerprintComponent, ComponentInit>(OnInit);
}
private void OnInteract(EntityUid uid, FingerprintComponent component, UserInteractedWithItemEvent args)
private void OnInteract(EntityUid uid, FingerprintComponent component, ContactInteractionEvent args)
{
ApplyEvidence(args.User, args.Item);
ApplyEvidence(uid, args.Other);
}
private void OnInit(EntityUid uid, FingerprintComponent component, ComponentInit args)

View file

@ -46,6 +46,7 @@ namespace Content.Server.Ghost
SubscribeLocalEvent<GhostComponent, MindRemovedMessage>(OnMindRemovedMessage);
SubscribeLocalEvent<GhostComponent, MindUnvisitedMessage>(OnMindUnvisitedMessage);
SubscribeLocalEvent<GhostComponent, PlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<GhostOnMoveComponent, MoveInputEvent>(OnRelayMoveInput);
@ -58,6 +59,7 @@ namespace Content.Server.Ghost
SubscribeLocalEvent<RoundEndTextAppendEvent>(_ => MakeVisible(true));
}
private void OnActionPerform(EntityUid uid, GhostComponent component, BooActionEvent args)
{
if (args.Handled)
@ -160,6 +162,11 @@ namespace Content.Server.Ghost
DeleteEntity(uid);
}
private void OnPlayerDetached(EntityUid uid, GhostComponent component, PlayerDetachedEvent args)
{
QueueDel(uid);
}
private void OnGhostWarpsRequest(GhostWarpsRequestEvent msg, EntitySessionEventArgs args)
{
if (args.SenderSession.AttachedEntity is not {Valid: true} entity ||

View file

@ -18,8 +18,6 @@ namespace Content.Server.Inventory
{
base.Initialize();
SubscribeLocalEvent<InventoryComponent, ModifyChangedTemperatureEvent>(RelayInventoryEvent);
SubscribeLocalEvent<ClothingComponent, UseInHandEvent>(OnUseInHand);
SubscribeNetworkEvent<OpenSlotStorageNetworkMessage>(OnOpenSlotStorage);

View file

@ -1,8 +1,6 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Kitchen.Components;
using Content.Shared.Kitchen;
using Content.Server.Kitchen.EntitySystems;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
namespace Content.Server.Kitchen.Components
{
@ -12,32 +10,36 @@ namespace Content.Server.Kitchen.Components
/// converting something into its single juice form. E.g, grind an apple and get the nutriment and sugar
/// it contained, juice an apple and get "apple juice".
/// </summary>
[RegisterComponent]
public sealed class ReagentGrinderComponent : SharedReagentGrinderComponent
[Access(typeof(ReagentGrinderSystem)), RegisterComponent]
public sealed class ReagentGrinderComponent : Component
{
//YAML serialization vars
[DataField("storageMaxEntities"), ViewVariables(VVAccess.ReadWrite)]
public int StorageMaxEntities = 16;
[DataField("workTime"), ViewVariables(VVAccess.ReadWrite)]
public TimeSpan WorkTime = TimeSpan.FromSeconds(3.5); // Roughly matches the grind/juice sounds.
[DataField("clickSound"), ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier ClickSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg");
[DataField("grindSound"), ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier GrindSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/blender.ogg");
[DataField("juiceSound"), ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier JuiceSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/juicer.ogg");
}
[Access(typeof(ReagentGrinderSystem)), RegisterComponent]
public sealed class ActiveReagentGrinderComponent : Component
{
/// <summary>
/// Can be null since we won't always have a beaker in the grinder.
/// Remaining time until the grinder finishes grinding/juicing.
/// </summary>
[ViewVariables] public Solution? BeakerSolution;
[ViewVariables]
public float WorkTimer;
/// <summary>
/// Contains the things that are going to be ground or juiced.
/// </summary>
[ViewVariables] public Container Chamber = default!;
/// <summary>
/// Is the machine actively doing something and can't be used right now?
/// </summary>
public bool Busy;
//YAML serialization vars
[ViewVariables(VVAccess.ReadWrite)] [DataField("chamberCapacity")] public int StorageCap = 16;
[ViewVariables(VVAccess.ReadWrite)] [DataField("workTime")] public int WorkTime = 3500; //3.5 seconds, completely arbitrary for now.
[DataField("clickSound")] public SoundSpecifier ClickSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg");
[DataField("grindSound")] public SoundSpecifier GrindSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/blender.ogg");
[DataField("juiceSound")] public SoundSpecifier JuiceSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/juicer.ogg");
[DataField("beakerSlot")]
public ItemSlot BeakerSlot = new();
[ViewVariables]
public GrinderProgram Program;
}
}

View file

@ -1,14 +1,15 @@
using System.Linq;
using Content.Server.Chemistry.EntitySystems;
using Content.Server.Kitchen.Components;
using Content.Server.Kitchen.Events;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Server.Stack;
using Content.Server.UserInterface;
using Content.Shared.Chemistry.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction;
using Content.Shared.Kitchen.Components;
using Content.Shared.Kitchen;
using Content.Shared.Popups;
using Content.Shared.Random.Helpers;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
@ -24,287 +25,272 @@ namespace Content.Server.Kitchen.EntitySystems
{
[Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
private Queue<ReagentGrinderComponent> _uiUpdateQueue = new();
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly StackSystem _stackSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ReagentGrinderComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<ReagentGrinderComponent, ComponentRemove>(OnComponentRemove);
SubscribeLocalEvent<ReagentGrinderComponent, PowerChangedEvent>(OnPowerChange);
SubscribeLocalEvent<ReagentGrinderComponent, ComponentStartup>((uid, component, _) => UpdateUiState(uid, component));
SubscribeLocalEvent<ReagentGrinderComponent, PowerChangedEvent>(
(EntityUid uid, ReagentGrinderComponent component, ref PowerChangedEvent _) => UpdateUiState(uid, component));
SubscribeLocalEvent<ReagentGrinderComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<StackComponent, ExtractableScalingEvent>(ExtractableScaling);
SubscribeLocalEvent<ReagentGrinderComponent, EntInsertedIntoContainerMessage>(OnContainerModified);
SubscribeLocalEvent<ReagentGrinderComponent, EntRemovedFromContainerMessage>(OnContainerModified);
SubscribeLocalEvent<ReagentGrinderComponent, ContainerIsRemovingAttemptEvent>(OnEntRemoveAttempt);
}
private void OnPowerChange(EntityUid uid, ReagentGrinderComponent component, ref PowerChangedEvent args)
{
EnqueueUiUpdate(component);
}
private void OnEntRemoveAttempt(EntityUid uid, ReagentGrinderComponent component, ContainerIsRemovingAttemptEvent args)
{
if (component.Busy)
args.Cancel();
}
private void OnContainerModified(EntityUid uid, ReagentGrinderComponent component, ContainerModifiedMessage args)
{
EnqueueUiUpdate(component);
if (args.Container.ID != SharedReagentGrinderComponent.BeakerSlotId)
return;
if (TryComp(component.Owner, out AppearanceComponent? appearance))
appearance.SetData(SharedReagentGrinderComponent.ReagentGrinderVisualState.BeakerAttached, component.BeakerSlot.HasItem);
component.BeakerSolution = null;
if (component.BeakerSlot.Item != null)
_solutionsSystem.TryGetFitsInDispenser(component.BeakerSlot.Item.Value, out component.BeakerSolution);
}
private void ExtractableScaling(EntityUid uid, StackComponent component, ExtractableScalingEvent args)
{
args.Scalar *= component.Count; // multiply scalar by amount of items in stack
}
private void OnInteractUsing(EntityUid uid, ReagentGrinderComponent component, InteractUsingEvent args)
{
if (args.Handled) return;
var heldEnt = args.Used;
//See if the user is trying to insert something they want to be ground/juiced.
if (!HasComp<ExtractableComponent>(heldEnt))
{
//Entity did NOT pass the whitelist for grind/juice.
//Wouldn't want the clown grinding up the Captain's ID card now would you?
//Why am I asking you? You're biased.
return;
}
//Cap the chamber. Don't want someone putting in 500 entities and ejecting them all at once.
//Maybe I should have done that for the microwave too?
if (component.Chamber.ContainedEntities.Count >= component.StorageCap)
return;
if (!component.Chamber.Insert(heldEnt, EntityManager))
return;
args.Handled = true;
}
private void EnqueueUiUpdate(ReagentGrinderComponent component)
{
if (!_uiUpdateQueue.Contains(component)) _uiUpdateQueue.Enqueue(component);
}
private void OnComponentInit(EntityUid uid, ReagentGrinderComponent component, ComponentInit args)
{
EnqueueUiUpdate(component);
_itemSlotsSystem.AddItemSlot(uid, SharedReagentGrinderComponent.BeakerSlotId, component.BeakerSlot);
//A container for the things that WILL be ground/juiced. Useful for ejecting them instead of deleting them from the hands of the user.
component.Chamber =
ContainerHelpers.EnsureContainer<Container>(component.Owner,
$"{component.Name}-entityContainerContainer");
// TODO just directly subscribe to UI events.
var bui = component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key);
if (bui != null)
{
bui.OnReceiveMessage += msg => OnUIMessageReceived(uid, component, msg);
}
}
private void OnComponentRemove(EntityUid uid, ReagentGrinderComponent component, ComponentRemove args)
{
_itemSlotsSystem.RemoveItemSlot(uid, component.BeakerSlot);
}
private void OnUIMessageReceived(EntityUid uid, ReagentGrinderComponent component,
ServerBoundUserInterfaceMessage message)
{
if (component.Busy || message.Session.AttachedEntity is not {} attached)
{
return;
}
switch (message.Message)
{
case SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage msg:
if (!this.IsPowered(component.Owner, EntityManager)) break;
ClickSound(component);
DoWork(component, attached,
SharedReagentGrinderComponent.GrinderProgram.Grind);
break;
case SharedReagentGrinderComponent.ReagentGrinderJuiceStartMessage msg:
if (!this.IsPowered(component.Owner, EntityManager)) break;
ClickSound(component);
DoWork(component, attached,
SharedReagentGrinderComponent.GrinderProgram.Juice);
break;
case SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage msg:
if (component.Chamber.ContainedEntities.Count > 0)
{
ClickSound(component);
for (var i = component.Chamber.ContainedEntities.Count - 1; i >= 0; i--)
{
var entity = component.Chamber.ContainedEntities[i];
component.Chamber.Remove(entity);
entity.RandomOffset(0.4f);
}
EnqueueUiUpdate(component);
}
break;
case SharedReagentGrinderComponent.ReagentGrinderEjectChamberContentMessage msg:
if (component.Chamber.ContainedEntities.TryFirstOrNull(x => x == msg.EntityID, out var ent))
{
component.Chamber.Remove(ent.Value);
SharedEntityExtensions.RandomOffset(ent.Value, 0.4f);
EnqueueUiUpdate(component);
ClickSound(component);
}
break;
}
SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderStartMessage>(OnStartMessage);
SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderEjectChamberAllMessage>(OnEjectChamberAllMessage);
SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderEjectChamberContentMessage>(OnEjectChamberContentMessage);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
while (_uiUpdateQueue.TryDequeue(out var comp))
foreach (var (active, reagentGrinder) in EntityQuery<ActiveReagentGrinderComponent, ReagentGrinderComponent>())
{
if (comp.Deleted)
var uid = reagentGrinder.Owner;
active.WorkTimer -= frameTime;
if (active.WorkTimer > 0)
continue;
bool canJuice = false;
bool canGrind = false;
if (comp.BeakerSlot.HasItem)
{
foreach (var entity in comp.Chamber.ContainedEntities)
{
if (canJuice || !EntityManager.TryGetComponent(entity, out ExtractableComponent? component)) continue;
RemCompDeferred<ActiveReagentGrinderComponent>(uid);
canJuice = component.JuiceSolution != null;
canGrind = component.GrindableSolution != null
&& _solutionsSystem.TryGetSolution(entity, component.GrindableSolution, out _);
var inputContainer = _containerSystem.EnsureContainer<Container>(uid, SharedReagentGrinder.InputContainerId);
var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId);
if (outputContainer is null || !_solutionsSystem.TryGetFitsInDispenser(outputContainer.Value, out var containerSolution))
continue;
foreach (var item in inputContainer.ContainedEntities.ToList())
{
var solution = active.Program switch
{
GrinderProgram.Grind => GetGrindSolution(item),
GrinderProgram.Juice => CompOrNull<ExtractableComponent>(item)?.JuiceSolution,
_ => null,
};
if (solution is null)
continue;
if (TryComp<StackComponent>(item, out var stack))
{
var totalVolume = solution.TotalVolume * stack.Count;
if (totalVolume <= 0)
continue;
// Maximum number of items we can process in the stack without going over AvailableVolume
// We add a small tolerance, because floats are inaccurate.
var fitsCount = (int) (stack.Count * FixedPoint2.Min(containerSolution.AvailableVolume / totalVolume + 0.01, 1));
if (fitsCount <= 0)
continue;
solution.ScaleSolution(fitsCount);
_stackSystem.SetCount(item, stack.Count - fitsCount); // Setting to 0 will QueueDel
}
else
{
if (solution.TotalVolume > containerSolution.AvailableVolume)
continue;
QueueDel(item);
}
_solutionsSystem.TryAddSolution(outputContainer.Value, containerSolution, solution);
}
comp.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key)?.SetState(
new ReagentGrinderInterfaceState
(
comp.Busy,
comp.BeakerSlot.HasItem,
this.IsPowered(comp.Owner, EntityManager),
canJuice,
canGrind,
comp.Chamber.ContainedEntities.Select(item => item).ToArray(),
//Remember the beaker can be null!
comp.BeakerSolution?.Contents.ToArray()
));
_userInterfaceSystem.TrySendUiMessage(uid, ReagentGrinderUiKey.Key,
new ReagentGrinderWorkCompleteMessage());
UpdateUiState(uid, reagentGrinder);
}
}
private void OnEntRemoveAttempt(EntityUid uid, ReagentGrinderComponent reagentGrinder, ContainerIsRemovingAttemptEvent args)
{
if (HasComp<ActiveReagentGrinderComponent>(uid))
args.Cancel();
}
private void OnContainerModified(EntityUid uid, ReagentGrinderComponent reagentGrinder, ContainerModifiedMessage args)
{
UpdateUiState(uid, reagentGrinder);
var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId);
_appearanceSystem.SetData(uid, ReagentGrinderVisualState.BeakerAttached, outputContainer.HasValue);
}
private void OnInteractUsing(EntityUid uid, ReagentGrinderComponent reagentGrinder, InteractUsingEvent args)
{
var heldEnt = args.Used;
var inputContainer = _containerSystem.EnsureContainer<Container>(uid, SharedReagentGrinder.InputContainerId);
if (!HasComp<ExtractableComponent>(heldEnt))
{
if (!HasComp<FitsInDispenserComponent>(heldEnt))
{
// This is ugly but we can't use whitelistFailPopup because there are 2 containers with different whitelists.
_popupSystem.PopupEntity(Loc.GetString("reagent-grinder-component-cannot-put-entity-message"), uid, Filter.Entities(args.User));
}
// Entity did NOT pass the whitelist for grind/juice.
// Wouldn't want the clown grinding up the Captain's ID card now would you?
// Why am I asking you? You're biased.
return;
}
if (args.Handled)
return;
// Cap the chamber. Don't want someone putting in 500 entities and ejecting them all at once.
// Maybe I should have done that for the microwave too?
if (inputContainer.ContainedEntities.Count >= reagentGrinder.StorageMaxEntities)
return;
if (!inputContainer.Insert(heldEnt, EntityManager))
return;
args.Handled = true;
}
private void UpdateUiState(EntityUid uid, ReagentGrinderComponent reagentGrinder)
{
var inputContainer = _containerSystem.EnsureContainer<Container>(uid, SharedReagentGrinder.InputContainerId);
var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId);
Solution? containerSolution = null;
var isBusy = HasComp<ActiveReagentGrinderComponent>(uid);
var canJuice = false;
var canGrind = false;
if (outputContainer is not null
&& _solutionsSystem.TryGetFitsInDispenser(outputContainer.Value, out containerSolution)
&& inputContainer.ContainedEntities.Count > 0)
{
canGrind = inputContainer.ContainedEntities.All(CanGrind);
canJuice = inputContainer.ContainedEntities.All(CanJuice);
}
var state = new ReagentGrinderInterfaceState(
isBusy,
outputContainer.HasValue,
this.IsPowered(uid, EntityManager),
canJuice,
canGrind,
inputContainer.ContainedEntities.Select(item => item).ToArray(),
containerSolution?.Contents.ToArray()
);
_userInterfaceSystem.TrySetUiState(uid, ReagentGrinderUiKey.Key, state);
}
private void OnStartMessage(EntityUid uid, ReagentGrinderComponent reagentGrinder, ReagentGrinderStartMessage message)
{
if (!this.IsPowered(uid, EntityManager) || HasComp<ActiveReagentGrinderComponent>(uid))
return;
DoWork(uid, reagentGrinder, message.Session.AttachedEntity, message.Program);
}
private void OnEjectChamberAllMessage(EntityUid uid, ReagentGrinderComponent reagentGrinder, ReagentGrinderEjectChamberAllMessage message)
{
var inputContainer = _containerSystem.EnsureContainer<Container>(uid, SharedReagentGrinder.InputContainerId);
if (HasComp<ActiveReagentGrinderComponent>(uid) || inputContainer.ContainedEntities.Count <= 0)
return;
ClickSound(uid, reagentGrinder);
foreach (var entity in inputContainer.ContainedEntities.ToList())
{
inputContainer.Remove(entity);
entity.RandomOffset(0.4f);
}
UpdateUiState(uid, reagentGrinder);
}
private void OnEjectChamberContentMessage(EntityUid uid, ReagentGrinderComponent reagentGrinder, ReagentGrinderEjectChamberContentMessage message)
{
if (HasComp<ActiveReagentGrinderComponent>(uid))
return;
var inputContainer = _containerSystem.EnsureContainer<Container>(uid, SharedReagentGrinder.InputContainerId);
if (inputContainer.Remove(message.EntityId))
{
message.EntityId.RandomOffset(0.4f);
ClickSound(uid, reagentGrinder);
UpdateUiState(uid, reagentGrinder);
}
}
/// <summary>
/// The wzhzhzh of the grinder. Processes the contents of the grinder and puts the output in the beaker.
/// </summary>
/// <param name="isJuiceIntent">true for wanting to juice, false for wanting to grind.</param>
private void DoWork(ReagentGrinderComponent component, EntityUid user,
SharedReagentGrinderComponent.GrinderProgram program)
/// <param name="program">Which program, such as grind or juice</param>
private void DoWork(EntityUid uid, ReagentGrinderComponent reagentGrinder, EntityUid? user, GrinderProgram program)
{
//Have power, are we busy, chamber has anything to grind, a beaker for the grounds to go?
if (!this.IsPowered(component.Owner, EntityManager) ||
component.Busy || component.Chamber.ContainedEntities.Count <= 0 ||
component.BeakerSlot.Item is not EntityUid beakerEntity ||
component.BeakerSolution == null)
{
var inputContainer = _containerSystem.EnsureContainer<Container>(uid, SharedReagentGrinder.InputContainerId);
var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId);
// Do we have anything to grind/juice and a container to put the reagents in?
if (inputContainer.ContainedEntities.Count <= 0 || !HasComp<FitsInDispenserComponent>(outputContainer))
return;
}
component.Busy = true;
var bui = component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key);
bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkStartedMessage(program));
SoundSpecifier? sound = null;
switch (program)
{
case SharedReagentGrinderComponent.GrinderProgram.Grind:
SoundSystem.Play(component.GrindSound.GetSound(), Filter.Pvs(component.Owner), component.Owner, AudioParams.Default);
// Get each item inside the chamber and get the reagents it contains.
// Transfer those reagents to the beaker, given we have one in.
component.Owner.SpawnTimer(component.WorkTime, () =>
{
foreach (var item in component.Chamber.ContainedEntities.ToList())
{
if (!EntityManager.TryGetComponent(item, out ExtractableComponent? extract)
|| extract.GrindableSolution == null
|| !_solutionsSystem.TryGetSolution(item, extract.GrindableSolution, out var solution)) continue;
var juiceEvent = new ExtractableScalingEvent(); // default of scalar is always 1.0
RaiseLocalEvent(item, juiceEvent, false);
if (component.BeakerSolution.CurrentVolume + solution.CurrentVolume * juiceEvent.Scalar >
component.BeakerSolution.MaxVolume) continue;
solution.ScaleSolution(juiceEvent.Scalar);
_solutionsSystem.TryAddSolution(beakerEntity, component.BeakerSolution, solution);
EntityManager.DeleteEntity(item);
}
component.Busy = false;
EnqueueUiUpdate(component);
bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage());
});
case GrinderProgram.Grind when inputContainer.ContainedEntities.All(CanGrind):
sound = reagentGrinder.GrindSound;
break;
case SharedReagentGrinderComponent.GrinderProgram.Juice:
SoundSystem.Play(component.JuiceSound.GetSound(), Filter.Pvs(component.Owner), component.Owner, AudioParams.Default);
component.Owner.SpawnTimer(component.WorkTime, () =>
{
foreach (var item in component.Chamber.ContainedEntities.ToList())
{
if (!EntityManager.TryGetComponent<ExtractableComponent?>(item, out var juiceMe)
|| juiceMe.JuiceSolution == null)
{
Logger.Warning("Couldn't find a juice solution on entityUid:{0}", item);
continue;
}
var juiceEvent = new ExtractableScalingEvent(); // default of scalar is always 1.0
if (EntityManager.HasComponent<StackComponent>(item))
{
RaiseLocalEvent(item, juiceEvent, true);
}
if (component.BeakerSolution.CurrentVolume + juiceMe.JuiceSolution.TotalVolume * juiceEvent.Scalar > component.BeakerSolution.MaxVolume)
continue;
juiceMe.JuiceSolution.ScaleSolution(juiceEvent.Scalar);
_solutionsSystem.TryAddSolution(beakerEntity, component.BeakerSolution, juiceMe.JuiceSolution);
EntityManager.DeleteEntity(item);
}
bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage());
component.Busy = false;
EnqueueUiUpdate(component);
});
case GrinderProgram.Juice when inputContainer.ContainedEntities.All(CanJuice):
sound = reagentGrinder.JuiceSound;
break;
default:
return;
}
var active = AddComp<ActiveReagentGrinderComponent>(uid);
active.WorkTimer = (float) reagentGrinder.WorkTime.TotalSeconds;
active.Program = program;
_audioSystem.PlayPvs(sound, uid);
_userInterfaceSystem.TrySendUiMessage(uid, ReagentGrinderUiKey.Key,
new ReagentGrinderWorkStartedMessage(program));
}
private void ClickSound(ReagentGrinderComponent component)
private void ClickSound(EntityUid uid, ReagentGrinderComponent reagentGrinder)
{
SoundSystem.Play(component.ClickSound.GetSound(), Filter.Pvs(component.Owner), component.Owner, AudioParams.Default.WithVolume(-2f));
_audioSystem.PlayPvs(reagentGrinder.ClickSound, uid, AudioParams.Default.WithVolume(-2f));
}
private Solution? GetGrindSolution(EntityUid uid)
{
if (TryComp<ExtractableComponent>(uid, out var extractable)
&& extractable.GrindableSolution is not null
&& _solutionsSystem.TryGetSolution(uid, extractable.GrindableSolution, out var solution))
{
return solution;
}
else
return null;
}
private bool CanGrind(EntityUid uid)
{
var solutionName = CompOrNull<ExtractableComponent>(uid)?.GrindableSolution;
return solutionName is not null && _solutionsSystem.TryGetSolution(uid, solutionName, out _);
}
private bool CanJuice(EntityUid uid)
{
return CompOrNull<ExtractableComponent>(uid)?.JuiceSolution is not null;
}
}
}

View file

@ -1,21 +0,0 @@
namespace Content.Server.Kitchen.Events
{
/// <summary>
/// Used in scaling amount of solution to extract in juicing
/// </summary>
public sealed class ExtractableScalingEvent : EntityEventArgs
{
public ExtractableScalingEvent()
{
Scalar = 1f;
}
public float Scalar
{
get;
set;
}
}
}

View file

@ -57,6 +57,9 @@ public sealed class LagCompensationSystem : EntitySystem
private void OnLagMove(EntityUid uid, LagCompensationComponent component, ref MoveEvent args)
{
if (!args.NewPosition.EntityId.IsValid())
return; // probably being sent to nullspace for deletion.
EnsureComp<ActiveLagCompensationComponent>(uid);
component.Positions.Enqueue((_timing.CurTime, args.NewPosition, args.NewRotation));
}

View file

@ -243,8 +243,10 @@ public sealed partial class PathfindingSystem
return;
}
var oldGridUid = ev.OldPosition.GetGridUid(EntityManager);
var gridUid = ev.NewPosition.GetGridUid(EntityManager);
var gridUid = ev.Component.GridUid;
var oldGridUid = ev.OldPosition.EntityId == ev.NewPosition.EntityId
? gridUid
: ev.OldPosition.GetGridUid(EntityManager);
// Not on a grid at all so just ignore.
if (oldGridUid == gridUid && oldGridUid == null)

View file

@ -168,6 +168,9 @@ namespace Content.Server.Physics.Controllers
private void OnConveyorShutdown(EntityUid uid, ConveyorComponent component, ComponentShutdown args)
{
if (MetaData(uid).EntityLifeStage >= EntityLifeStage.Terminating)
return;
RemComp<ActiveConveyorComponent>(uid);
if (!TryComp<PhysicsComponent>(uid, out var body))

View file

@ -303,6 +303,7 @@ namespace Content.Server.PneumaticCannon
Verb ejectItems = new();
ejectItems.Act = () => TryEjectAllItems(component, args.User);
ejectItems.Text = Loc.GetString("pneumatic-cannon-component-verb-eject-items-name");
ejectItems.DoContactInteraction = true;
args.Verbs.Add(ejectItems);
}

View file

@ -24,6 +24,7 @@ namespace Content.Server.Rotatable
Verb verb = new();
verb.Act = () => TryFlip(component, args.User);
verb.Text = Loc.GetString("flippable-verb-get-data-text");
verb.DoContactInteraction = true;
// TODO VERB ICONS Add Uno reverse card style icon?
args.Verbs.Add(verb);
}
@ -43,6 +44,7 @@ namespace Content.Server.Rotatable
Verb resetRotation = new ()
{
DoContactInteraction = true,
Act = () => EntityManager.GetComponent<TransformComponent>(component.Owner).LocalRotation = Angle.Zero,
Category = VerbCategory.Rotate,
IconTexture = "/Textures/Interface/VerbIcons/refresh.svg.192dpi.png",

View file

@ -1,4 +1,4 @@
using Content.Server.DoAfter;
using Content.Server.DoAfter;
using Content.Server.Popups;
using Content.Server.Sticky.Components;
using Content.Server.Sticky.Events;
@ -53,6 +53,7 @@ public sealed class StickySystem : EntitySystem
args.Verbs.Add(new Verb
{
DoContactInteraction = true,
Text = Loc.GetString("comp-sticky-unstick-verb-text"),
IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png",
Act = () => StartUnsticking(uid, args.User, component)

View file

@ -163,7 +163,7 @@ public sealed class EntityStorageSystem : EntitySystem
var targetCoordinates = new EntityCoordinates(uid, component.EnteringOffset);
var entities = _lookup.GetEntitiesInRange(targetCoordinates, component.EnteringRange, LookupFlags.Approximate);
var entities = _lookup.GetEntitiesInRange(targetCoordinates, component.EnteringRange, LookupFlags.Approximate | LookupFlags.Dynamic | LookupFlags.Sundries);
var ev = new StorageBeforeCloseEvent(uid, entities);
RaiseLocalEvent(uid, ev, true);

View file

@ -298,7 +298,7 @@ namespace Content.Server.Storage.EntitySystems
var validStorables = new List<EntityUid>();
var itemQuery = GetEntityQuery<ItemComponent>();
foreach (var entity in _entityLookupSystem.GetEntitiesInRange(args.ClickLocation, storageComp.AreaInsertRadius, LookupFlags.None))
foreach (var entity in _entityLookupSystem.GetEntitiesInRange(args.ClickLocation, storageComp.AreaInsertRadius, LookupFlags.Dynamic | LookupFlags.Sundries))
{
if (entity == args.User
|| !itemQuery.HasComponent(entity)

View file

@ -9,6 +9,7 @@ using Content.Shared.Alert;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.Inventory;
using Content.Shared.Temperature;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@ -39,7 +40,7 @@ namespace Content.Server.Temperature.Systems
SubscribeLocalEvent<TemperatureComponent, OnTemperatureChangeEvent>(EnqueueDamage);
SubscribeLocalEvent<TemperatureComponent, AtmosExposedUpdateEvent>(OnAtmosExposedUpdate);
SubscribeLocalEvent<AlertsComponent, OnTemperatureChangeEvent>(ServerAlert);
SubscribeLocalEvent<TemperatureProtectionComponent, ModifyChangedTemperatureEvent>(OnTemperatureChangeAttempt);
SubscribeLocalEvent<TemperatureProtectionComponent, InventoryRelayedEvent<ModifyChangedTemperatureEvent>>(OnTemperatureChangeAttempt);
}
public override void Update(float frameTime)
@ -204,9 +205,9 @@ namespace Content.Server.Temperature.Systems
}
}
private void OnTemperatureChangeAttempt(EntityUid uid, TemperatureProtectionComponent component, ModifyChangedTemperatureEvent args)
private void OnTemperatureChangeAttempt(EntityUid uid, TemperatureProtectionComponent component, InventoryRelayedEvent<ModifyChangedTemperatureEvent> args)
{
args.TemperatureDelta *= component.Coefficient;
args.Args.TemperatureDelta *= component.Coefficient;
}
}
@ -223,16 +224,4 @@ namespace Content.Server.Temperature.Systems
TemperatureDelta = delta;
}
}
public sealed class ModifyChangedTemperatureEvent : EntityEventArgs, IInventoryRelayEvent
{
public SlotFlags TargetSlots { get; } = ~SlotFlags.POCKET;
public float TemperatureDelta;
public ModifyChangedTemperatureEvent(float temperature)
{
TemperatureDelta = temperature;
}
}
}

View file

@ -84,17 +84,7 @@ namespace Content.Server.Verbs
// first, lets log the verb. Just in case it ends up crashing the server or something.
LogVerb(verb, user, target, forced);
// then invoke any relevant actions
verb.Act?.Invoke();
// Maybe raise a local event
if (verb.ExecutionEventArgs != null)
{
if (verb.EventTarget.IsValid())
RaiseLocalEvent(verb.EventTarget, verb.ExecutionEventArgs, true);
else
RaiseLocalEvent(verb.ExecutionEventArgs);
}
base.ExecuteVerb(verb, user, target, forced);
}
public void LogVerb(Verb verb, EntityUid user, EntityUid target, bool forced)

View file

@ -150,6 +150,13 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
ev.Target.Value
};
_interaction.DoContactInteraction(ev.Weapon, ev.Target);
_interaction.DoContactInteraction(user, ev.Weapon);
// If the user is using a long-range weapon, this probably shouldn't be happening? But I'll interpret melee as a
// somewhat messy scuffle. See also, heavy attacks.
_interaction.DoContactInteraction(user, ev.Target);
// For stuff that cares about it being attacked.
RaiseLocalEvent(ev.Target.Value, new AttackedEvent(component.Owner, user, targetXform.Coordinates));
@ -249,9 +256,17 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
var modifiers = itemDamage.ModifiersList;
modifiers.AddRange(hitEvent.ModifiersList);
_interaction.DoContactInteraction(user, ev.Weapon);
// For stuff that cares about it being attacked.
foreach (var target in targets)
{
_interaction.DoContactInteraction(ev.Weapon, target);
// If the user is using a long-range weapon, this probably shouldn't be happening? But I'll interpret melee as a
// somewhat messy scuffle. See also, light attacks.
_interaction.DoContactInteraction(user, target);
RaiseLocalEvent(target, new AttackedEvent(component.Owner, user, Transform(target).Coordinates));
}
@ -338,6 +353,8 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
inTargetHand = targetHandsComponent.ActiveHand.HeldEntity!.Value;
}
_interaction.DoContactInteraction(user, ev.Target);
var attemptEvent = new DisarmAttemptEvent(target, user, inTargetHand);
if (inTargetHand != null)

View file

@ -78,9 +78,6 @@ namespace Content.Shared.ActionBlocker
var targetEv = new GettingInteractedWithAttemptEvent(user, target);
RaiseLocalEvent(target.Value, targetEv);
if (!targetEv.Cancelled)
InteractWithItem(user, target.Value);
return !targetEv.Cancelled;
}
@ -136,11 +133,7 @@ namespace Content.Shared.ActionBlocker
var itemEv = new GettingPickedUpAttemptEvent(user, item);
RaiseLocalEvent(item, itemEv);
if (!itemEv.Cancelled)
InteractWithItem(user, item);
return !itemEv.Cancelled;
}
public bool CanEmote(EntityUid uid)
@ -186,13 +179,5 @@ namespace Content.Shared.ActionBlocker
return !ev.Cancelled;
}
private void InteractWithItem(EntityUid user, EntityUid item)
{
var userEvent = new UserInteractedWithItemEvent(user, item);
RaiseLocalEvent(user, userEvent);
var itemEvent = new ItemInteractedWithEvent(user, item);
RaiseLocalEvent(item, itemEvent);
}
}
}

View file

@ -14,23 +14,12 @@ public partial class SharedBodySystem
{
public void InitializeBody()
{
SubscribeLocalEvent<BodyComponent, MapInitEvent>(OnBodyMapInit);
SubscribeLocalEvent<BodyComponent, ComponentInit>(OnBodyInit);
SubscribeLocalEvent<BodyComponent, ComponentGetState>(OnBodyGetState);
SubscribeLocalEvent<BodyComponent, ComponentHandleState>(OnBodyHandleState);
}
private void OnBodyMapInit(EntityUid bodyId, BodyComponent body, MapInitEvent args)
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (body.Prototype == null || body.Root != null)
return;
var prototype = Prototypes.Index<BodyPrototype>(body.Prototype);
InitBody(body, prototype);
}
private void OnBodyInit(EntityUid bodyId, BodyComponent body, ComponentInit args)
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
@ -75,22 +64,9 @@ public partial class SharedBodySystem
return true;
}
private void InitBody(BodyComponent body, BodyPrototype prototype)
{
var root = prototype.Slots[prototype.Root];
var bodyId = Spawn(root.Part, body.Owner.ToCoordinates());
var partComponent = Comp<BodyPartComponent>(bodyId);
var slot = new BodyPartSlot(root.Part, body.Owner, partComponent.PartType);
body.Root = slot;
partComponent.Body = bodyId;
protected abstract void InitBody(BodyComponent body, BodyPrototype prototype);
Containers.EnsureContainer<Container>(body.Owner, BodyContainerId);
AttachPart(bodyId, slot, partComponent);
InitPart(partComponent, prototype, prototype.Root);
}
private void InitPart(BodyPartComponent parent, BodyPrototype prototype, string slotId, HashSet<string>? initialized = null)
protected void InitPart(BodyPartComponent parent, BodyPrototype prototype, string slotId, HashSet<string>? initialized = null)
{
initialized ??= new HashSet<string>();

View file

@ -7,7 +7,7 @@ namespace Content.Shared.Body.Systems;
public abstract partial class SharedBodySystem : EntitySystem
{
private const string BodyContainerId = "BodyContainer";
protected const string BodyContainerId = "BodyContainer";
[Dependency] protected readonly IPrototypeManager Prototypes = default!;

View file

@ -1,4 +1,5 @@
using Content.Shared.Examine;
using Content.Shared.Examine;
using Content.Shared.Inventory;
using Content.Shared.Movement.Systems;
using Content.Shared.Verbs;
using Robust.Shared.Containers;
@ -19,7 +20,7 @@ public sealed class ClothingSpeedModifierSystem : EntitySystem
SubscribeLocalEvent<ClothingSpeedModifierComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<ClothingSpeedModifierComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<ClothingSpeedModifierComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMoveSpeed);
SubscribeLocalEvent<ClothingSpeedModifierComponent, InventoryRelayedEvent<RefreshMovementSpeedModifiersEvent>>(OnRefreshMoveSpeed);
SubscribeLocalEvent<ClothingSpeedModifierComponent, GetVerbsEvent<ExamineVerb>>(OnClothingVerbExamine);
}
@ -65,12 +66,12 @@ public sealed class ClothingSpeedModifierSystem : EntitySystem
}
}
private void OnRefreshMoveSpeed(EntityUid uid, ClothingSpeedModifierComponent component, RefreshMovementSpeedModifiersEvent args)
private void OnRefreshMoveSpeed(EntityUid uid, ClothingSpeedModifierComponent component, InventoryRelayedEvent<RefreshMovementSpeedModifiersEvent> args)
{
if (!component.Enabled)
return;
args.ModifySpeed(component.WalkModifier, component.SprintModifier);
args.Args.ModifySpeed(component.WalkModifier, component.SprintModifier);
}
private void OnClothingVerbExamine(EntityUid uid, ClothingSpeedModifierComponent component, GetVerbsEvent<ExamineVerb> args)

View file

@ -24,7 +24,7 @@ public abstract class SharedMagbootsSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<MagbootsComponent, GetVerbsEvent<ActivationVerb>>(AddToggleVerb);
SubscribeLocalEvent<MagbootsComponent, SlipAttemptEvent>(OnSlipAttempt);
SubscribeLocalEvent<MagbootsComponent, InventoryRelayedEvent<SlipAttemptEvent>>(OnSlipAttempt);
SubscribeLocalEvent<MagbootsComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<MagbootsComponent, ToggleActionEvent>(OnToggleAction);
}
@ -72,10 +72,10 @@ public abstract class SharedMagbootsSystem : EntitySystem
args.Verbs.Add(verb);
}
private void OnSlipAttempt(EntityUid uid, MagbootsComponent component, SlipAttemptEvent args)
private void OnSlipAttempt(EntityUid uid, MagbootsComponent component, InventoryRelayedEvent<SlipAttemptEvent> args)
{
if (component.On)
args.Cancel();
args.Args.Cancel();
}
private void OnGetActions(EntityUid uid, MagbootsComponent component, GetItemActionsEvent args)

View file

@ -1,3 +1,4 @@
using Content.Shared.Inventory;
using Robust.Shared.GameStates;
namespace Content.Shared.Electrocution
@ -9,6 +10,8 @@ namespace Content.Shared.Electrocution
base.Initialize();
SubscribeLocalEvent<InsulatedComponent, ElectrocutionAttemptEvent>(OnInsulatedElectrocutionAttempt);
// as long as legally distinct electric-mice are never added, this should be fine (otherwise a mouse-hat will transfer it's power to the wearer).
SubscribeLocalEvent<InsulatedComponent, InventoryRelayedEvent<ElectrocutionAttemptEvent>>((e, c, ev) => OnInsulatedElectrocutionAttempt(e, c, ev.Args));
SubscribeLocalEvent<InsulatedComponent, ComponentGetState>(OnInsulatedGetState);
SubscribeLocalEvent<InsulatedComponent, ComponentHandleState>(OnInsulatedHandleState);
}

View file

@ -1,4 +1,5 @@
using Content.Shared.IdentityManagement.Components;
using Content.Shared.IdentityManagement.Components;
using Content.Shared.Inventory;
using Robust.Shared.Containers;
namespace Content.Shared.IdentityManagement;
@ -14,6 +15,7 @@ public abstract class SharedIdentitySystem : EntitySystem
SubscribeLocalEvent<IdentityComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<IdentityBlockerComponent, SeeIdentityAttemptEvent>(OnSeeIdentity);
SubscribeLocalEvent<IdentityBlockerComponent, InventoryRelayedEvent<SeeIdentityAttemptEvent>>((e, c, ev) => OnSeeIdentity(e, c, ev.Args));
}
private void OnSeeIdentity(EntityUid uid, IdentityBlockerComponent component, SeeIdentityAttemptEvent args)

View file

@ -0,0 +1,17 @@
namespace Content.Shared.Interaction.Events;
/// <summary>
/// Raised directed at two entities to indicate that they came into contact, usually as a result of some other interaction.
/// </summary>
/// <remarks>
/// This is currently used by the forensics and disease systems to perform on-contact interactions.
/// </remarks>
public sealed class ContactInteractionEvent : HandledEntityEventArgs
{
public readonly EntityUid Other;
public ContactInteractionEvent(EntityUid other)
{
Other = other;
}
}

View file

@ -261,7 +261,8 @@ namespace Content.Shared.Interaction
if (target != null)
{
var ev = new InteractNoHandEvent(user, target.Value);
RaiseLocalEvent(user, ev, true);
RaiseLocalEvent(user, ev);
DoContactInteraction(user, target.Value, ev);
}
return;
}
@ -316,6 +317,7 @@ namespace Content.Shared.Interaction
var message = new InteractHandEvent(user, target);
RaiseLocalEvent(target, message, true);
_adminLogger.Add(LogType.InteractHand, LogImpact.Low, $"{ToPrettyString(user):user} interacted with {ToPrettyString(target):target}");
DoContactInteraction(user, target, message);
if (message.Handled)
return;
@ -337,6 +339,9 @@ namespace Content.Shared.Interaction
var rangedMsg = new RangedInteractEvent(user, used, target.Value, clickLocation);
RaiseLocalEvent(target.Value, rangedMsg, true);
// We contact the USED entity, but not the target.
DoContactInteraction(user, used, rangedMsg);
if (rangedMsg.Handled)
return;
}
@ -722,6 +727,9 @@ namespace Content.Shared.Interaction
{
var ev = new BeforeRangedInteractEvent(user, used, target, clickLocation, canReach);
RaiseLocalEvent(used, ev);
// We contact the USED entity, but not the target.
DoContactInteraction(user, used, ev);
return ev.Handled;
}
@ -750,6 +758,9 @@ namespace Content.Shared.Interaction
// all interactions should only happen when in range / unobstructed, so no range check is needed
var interactUsingEvent = new InteractUsingEvent(user, used, target, clickLocation);
RaiseLocalEvent(target, interactUsingEvent, true);
DoContactInteraction(user, used, interactUsingEvent);
DoContactInteraction(user, target, interactUsingEvent);
DoContactInteraction(used, target, interactUsingEvent);
if (interactUsingEvent.Handled)
return;
@ -765,7 +776,14 @@ namespace Content.Shared.Interaction
target = null;
var afterInteractEvent = new AfterInteractEvent(user, used, target, clickLocation, canReach);
RaiseLocalEvent(used, afterInteractEvent, false);
RaiseLocalEvent(used, afterInteractEvent);
DoContactInteraction(user, used, afterInteractEvent);
if (canReach)
{
DoContactInteraction(user, target, afterInteractEvent);
DoContactInteraction(used, target, afterInteractEvent);
}
if (afterInteractEvent.Handled)
return;
@ -774,6 +792,13 @@ namespace Content.Shared.Interaction
var afterInteractUsingEvent = new AfterInteractUsingEvent(user, used, target, clickLocation, canReach);
RaiseLocalEvent(target.Value, afterInteractUsingEvent);
DoContactInteraction(user, used, afterInteractUsingEvent);
if (canReach)
{
DoContactInteraction(user, target, afterInteractUsingEvent);
DoContactInteraction(used, target, afterInteractUsingEvent);
}
}
#region ActivateItemInWorld
@ -832,6 +857,7 @@ namespace Content.Shared.Interaction
if (!activateMsg.Handled)
return false;
DoContactInteraction(user, used, activateMsg);
_useDelay.BeginDelay(used, delayComponent);
_adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}");
return true;
@ -869,6 +895,7 @@ namespace Content.Shared.Interaction
RaiseLocalEvent(used, useMsg, true);
if (useMsg.Handled)
{
DoContactInteraction(user, used, useMsg);
_useDelay.BeginDelay(used, delayComponent);
return true;
}
@ -977,6 +1004,18 @@ namespace Content.Shared.Interaction
return true;
}
/// <summary>
/// Simple convenience function to raise contact events (disease, forensics, etc).
/// </summary>
public void DoContactInteraction(EntityUid uidA, EntityUid? uidB, HandledEntityEventArgs? args = null)
{
if (uidB == null || args?.Handled == false)
return;
RaiseLocalEvent(uidA, new ContactInteractionEvent(uidB.Value));
RaiseLocalEvent(uidB.Value, new ContactInteractionEvent(uidA));
}
}
/// <summary>

View file

@ -5,6 +5,7 @@ using Content.Shared.IdentityManagement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Slippery;
using Content.Shared.Strip.Components;
using Content.Shared.Temperature;
namespace Content.Shared.Inventory;
@ -19,19 +20,43 @@ public partial class InventorySystem
SubscribeLocalEvent<InventoryComponent, GetExplosionResistanceEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, BeforeStripEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, SeeIdentityAttemptEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, ModifyChangedTemperatureEvent>(RelayInventoryEvent);
}
protected void RelayInventoryEvent<T>(EntityUid uid, InventoryComponent component, T args) where T : EntityEventArgs, IInventoryRelayEvent
{
if (args.TargetSlots == SlotFlags.NONE)
return;
var containerEnumerator = new ContainerSlotEnumerator(uid, component.TemplateId, _prototypeManager, this, args.TargetSlots);
while(containerEnumerator.MoveNext(out var container))
var ev = new InventoryRelayedEvent<T>(args);
while (containerEnumerator.MoveNext(out var container))
{
if(!container.ContainedEntity.HasValue) continue;
RaiseLocalEvent(container.ContainedEntity.Value, args, false);
if (!container.ContainedEntity.HasValue) continue;
RaiseLocalEvent(container.ContainedEntity.Value, ev, false);
}
}
}
/// <summary>
/// Event wrapper for relayed events.
/// </summary>
/// <remarks>
/// This avoids nested inventory relays, and makes it easy to have certain events only handled by the initial
/// target entity. E.g. health based movement speed modifiers should not be handled by a hat, even if that hat
/// happens to be a dead mouse. Clothing that wishes to modify movement speed must subscribe to
/// InventoryRelayedEvent<RefreshMovementSpeedModifiersEvent>
/// </remarks>
public sealed class InventoryRelayedEvent<TEvent> : EntityEventArgs where TEvent : EntityEventArgs, IInventoryRelayEvent
{
public readonly TEvent Args;
public InventoryRelayedEvent(TEvent args)
{
Args = args;
}
}
/// <summary>
/// Events that should be relayed to inventory slots should implement this interface.
/// </summary>

View file

@ -1,17 +0,0 @@
namespace Content.Shared.Item;
/// <summary>
/// Raised on the item after they do any sort of interaction with an item,
/// useful for when you want a component on the user to do something to the user
/// E.g. forensics, disease, etc.
/// </summary>
public sealed class ItemInteractedWithEvent : EntityEventArgs
{
public EntityUid User;
public EntityUid Item;
public ItemInteractedWithEvent(EntityUid user, EntityUid item)
{
User = user;
Item = item;
}
}

View file

@ -111,14 +111,13 @@ public abstract class SharedItemSystem : EntitySystem
if (args.Hands == null ||
args.Using != null ||
!args.CanAccess ||
!args.CanInteract) //||
//!_handsSystem.CanPickupAnyHand(args.User, args.Target, handsComp: args.Hands, item: component))
!args.CanInteract ||
!_handsSystem.CanPickupAnyHand(args.User, args.Target, handsComp: args.Hands, item: component))
return;
InteractionVerb verb = new();
// TODO ITEM
//verb.Act = () => _handsSystem.TryPickupAnyHand(args.User, args.Target, checkActionBlocker: false,
// handsComp: args.Hands, item: component);
verb.Act = () => _handsSystem.TryPickupAnyHand(args.User, args.Target, checkActionBlocker: false,
handsComp: args.Hands, item: component);
verb.IconTexture = "/Textures/Interface/VerbIcons/pickup.svg.192dpi.png";
// if the item already in a container (that is not the same as the user's), then change the text.

View file

@ -1,17 +0,0 @@
namespace Content.Shared.Item;
/// <summary>
/// Raised on the user after they do any sort of interaction with an item,
/// useful for when you want a component on the user to do something to the item.
/// E.g. forensics, disease, etc.
/// </summary>
public sealed class UserInteractedWithItemEvent : EntityEventArgs
{
public EntityUid User;
public EntityUid Item;
public UserInteractedWithItemEvent(EntityUid user, EntityUid item)
{
User = user;
Item = item;
}
}

View file

@ -1,104 +0,0 @@
using Content.Shared.Chemistry.Components;
using Robust.Shared.Serialization;
namespace Content.Shared.Kitchen.Components
{
public abstract class SharedReagentGrinderComponent : Component
{
public static string BeakerSlotId = "ReagentGrinder-reagentContainerContainer";
[Serializable, NetSerializable]
public sealed class ReagentGrinderGrindStartMessage : BoundUserInterfaceMessage
{
public ReagentGrinderGrindStartMessage()
{
}
}
[Serializable, NetSerializable]
public sealed class ReagentGrinderJuiceStartMessage : BoundUserInterfaceMessage
{
public ReagentGrinderJuiceStartMessage()
{
}
}
[Serializable, NetSerializable]
public sealed class ReagentGrinderEjectChamberAllMessage : BoundUserInterfaceMessage
{
public ReagentGrinderEjectChamberAllMessage()
{
}
}
[Serializable, NetSerializable]
public sealed class ReagentGrinderEjectChamberContentMessage : BoundUserInterfaceMessage
{
public EntityUid EntityID;
public ReagentGrinderEjectChamberContentMessage(EntityUid entityID)
{
EntityID = entityID;
}
}
[Serializable, NetSerializable]
public sealed class ReagentGrinderWorkStartedMessage : BoundUserInterfaceMessage
{
public GrinderProgram GrinderProgram;
public ReagentGrinderWorkStartedMessage(GrinderProgram grinderProgram)
{
GrinderProgram = grinderProgram;
}
}
[Serializable, NetSerializable]
public sealed class ReagentGrinderWorkCompleteMessage : BoundUserInterfaceMessage
{
public ReagentGrinderWorkCompleteMessage()
{
}
}
[Serializable, NetSerializable]
public enum ReagentGrinderVisualState : byte
{
BeakerAttached
}
[NetSerializable, Serializable]
public enum ReagentGrinderUiKey : byte
{
Key
}
[Serializable, NetSerializable]
public enum GrinderProgram : byte
{
Grind,
Juice
}
}
[NetSerializable, Serializable]
public sealed class ReagentGrinderInterfaceState : BoundUserInterfaceState
{
public bool IsBusy;
public bool HasBeakerIn;
public bool Powered;
public bool CanJuice;
public bool CanGrind;
public EntityUid[] ChamberContents;
public Solution.ReagentQuantity[]? ReagentQuantities;
public ReagentGrinderInterfaceState(bool isBusy, bool hasBeaker, bool powered, bool canJuice, bool canGrind, EntityUid[] chamberContents, Solution.ReagentQuantity[]? heldBeakerContents)
{
IsBusy = isBusy;
HasBeakerIn = hasBeaker;
Powered = powered;
CanJuice = canJuice;
CanGrind = canGrind;
ChamberContents = chamberContents;
ReagentQuantities = heldBeakerContents;
}
}
}

View file

@ -0,0 +1,99 @@
using Content.Shared.Chemistry.Components;
using Robust.Shared.Serialization;
namespace Content.Shared.Kitchen
{
public sealed class SharedReagentGrinder
{
public static string BeakerSlotId = "beakerSlot";
public static string InputContainerId = "inputContainer";
}
[Serializable, NetSerializable]
public sealed class ReagentGrinderStartMessage : BoundUserInterfaceMessage
{
public readonly GrinderProgram Program;
public ReagentGrinderStartMessage(GrinderProgram program)
{
Program = program;
}
}
[Serializable, NetSerializable]
public sealed class ReagentGrinderEjectChamberAllMessage : BoundUserInterfaceMessage
{
public ReagentGrinderEjectChamberAllMessage()
{
}
}
[Serializable, NetSerializable]
public sealed class ReagentGrinderEjectChamberContentMessage : BoundUserInterfaceMessage
{
public EntityUid EntityId;
public ReagentGrinderEjectChamberContentMessage(EntityUid entityId)
{
EntityId = entityId;
}
}
[Serializable, NetSerializable]
public sealed class ReagentGrinderWorkStartedMessage : BoundUserInterfaceMessage
{
public GrinderProgram GrinderProgram;
public ReagentGrinderWorkStartedMessage(GrinderProgram grinderProgram)
{
GrinderProgram = grinderProgram;
}
}
[Serializable, NetSerializable]
public sealed class ReagentGrinderWorkCompleteMessage : BoundUserInterfaceMessage
{
public ReagentGrinderWorkCompleteMessage()
{
}
}
[Serializable, NetSerializable]
public enum ReagentGrinderVisualState : byte
{
BeakerAttached
}
[Serializable, NetSerializable]
public enum GrinderProgram : byte
{
Grind,
Juice
}
[NetSerializable, Serializable]
public enum ReagentGrinderUiKey : byte
{
Key
}
[NetSerializable, Serializable]
public sealed class ReagentGrinderInterfaceState : BoundUserInterfaceState
{
public bool IsBusy;
public bool HasBeakerIn;
public bool Powered;
public bool CanJuice;
public bool CanGrind;
public EntityUid[] ChamberContents;
public Solution.ReagentQuantity[]? ReagentQuantities;
public ReagentGrinderInterfaceState(bool isBusy, bool hasBeaker, bool powered, bool canJuice, bool canGrind, EntityUid[] chamberContents, Solution.ReagentQuantity[]? heldBeakerContents)
{
IsBusy = isBusy;
HasBeakerIn = hasBeaker;
Powered = powered;
CanJuice = canJuice;
CanGrind = canGrind;
ChamberContents = chamberContents;
ReagentQuantities = heldBeakerContents;
}
}
}

View file

@ -75,7 +75,7 @@ public sealed class SlowContactsSystem : EntitySystem
if (!TryComp<SlowContactsComponent>(ent, out var slowContactsComponent))
continue;
if (slowContactsComponent.IgnoreWhitelist != null && slowContactsComponent.IgnoreWhitelist.IsValid(ent))
if (slowContactsComponent.IgnoreWhitelist != null && slowContactsComponent.IgnoreWhitelist.IsValid(uid))
continue;
walkSpeed = Math.Min(walkSpeed, slowContactsComponent.WalkSpeedModifier);

View file

@ -1,6 +1,7 @@
using Content.Shared.ActionBlocker;
using Content.Shared.Buckle.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Physics.Pull;
using Content.Shared.Pulling.Components;
using Content.Shared.Pulling.Events;
@ -16,6 +17,7 @@ namespace Content.Shared.Pulling
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
public bool CanPull(EntityUid puller, EntityUid pulled)
{
@ -189,6 +191,8 @@ namespace Content.Shared.Pulling
if (pullAttempt.Cancelled)
return false;
_interaction.DoContactInteraction(pullable.Owner, puller.Owner);
_pullSm.ForceRelationship(puller, pullable);
pullable.PrevFixedRotation = pullablePhysics.FixedRotation;
pullablePhysics.FixedRotation = pullable.FixedRotationOnPull;

View file

@ -93,6 +93,7 @@ namespace Content.Shared.Pulling
Verb verb = new();
verb.Text = Loc.GetString("pulling-verb-get-data-text-stop-pulling");
verb.Act = () => TryStopPull(component, args.User);
verb.DoContactInteraction = false; // pulling handle its own contact interaction.
args.Verbs.Add(verb);
}
else if (CanPull(args.User, args.Target))
@ -100,6 +101,7 @@ namespace Content.Shared.Pulling
Verb verb = new();
verb.Text = Loc.GetString("pulling-verb-get-data-text");
verb.Act = () => TryStartPull(args.User, args.Target);
verb.DoContactInteraction = false; // pulling handle its own contact interaction.
args.Verbs.Add(verb);
}
}

View file

@ -10,6 +10,7 @@ namespace Content.Shared.Roles
{
private string _name = string.Empty;
private string _objective = string.Empty;
private string? _description = string.Empty;
[ViewVariables]
[IdDataFieldAttribute]
@ -25,6 +26,16 @@ namespace Content.Shared.Roles
private set => _name = Loc.GetString(value);
}
/// <summary>
/// The description of this antag shown in a tooltip.
/// </summary>
[DataField("description")]
public string? Description
{
get => _description;
private set => _description = value is null ? null : Loc.GetString(value);
}
/// <summary>
/// The antag's objective, displayed at round-start to the player.
/// </summary>

View file

@ -31,6 +31,15 @@ namespace Content.Shared.Roles
[ViewVariables(VVAccess.ReadOnly)]
public string LocalizedName => Loc.GetString(Name);
/// <summary>
/// The name of this job as displayed to players.
/// </summary>
[DataField("description")]
public string? Description { get; }
[ViewVariables(VVAccess.ReadOnly)]
public string? LocalizedDescription => Description is null ? null : Loc.GetString(Description);
[DataField("requirements")]
public HashSet<JobRequirement>? Requirements;

View file

@ -30,6 +30,8 @@ namespace Content.Shared.Slippery
SubscribeLocalEvent<SlipperyComponent, StepTriggerAttemptEvent>(HandleAttemptCollide);
SubscribeLocalEvent<SlipperyComponent, StepTriggeredEvent>(HandleStepTrigger);
SubscribeLocalEvent<NoSlipComponent, SlipAttemptEvent>(OnNoSlipAttempt);
// as long as slip-resistant mice are never added, this should be fine (otherwise a mouse-hat will transfer it's power to the wearer).
SubscribeLocalEvent<NoSlipComponent, InventoryRelayedEvent<SlipAttemptEvent>>((e, c, ev) => OnNoSlipAttempt(e, c, ev.Args));
SubscribeLocalEvent<SlipperyComponent, ComponentGetState>(OnSlipperyGetState);
SubscribeLocalEvent<SlipperyComponent, ComponentHandleState>(OnSlipperyHandleState);
}

View file

@ -1,10 +1,12 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Stealth.Components
{
/// <summary>
/// When added to an entity with stealth component, this component will change the visibility
/// based on the entity's (lack of) movement.
/// </summary>
[RegisterComponent]
[RegisterComponent, NetworkedComponent]
public sealed class StealthOnMoveComponent : Component
{
/// <summary>

View file

@ -11,6 +11,7 @@ public sealed class ThievingSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<ThievingComponent, BeforeStripEvent>(OnBeforeStrip);
SubscribeLocalEvent<ThievingComponent, InventoryRelayedEvent<BeforeStripEvent>>((e, c, ev) => OnBeforeStrip(e, c, ev.Args));
}
private void OnBeforeStrip(EntityUid uid, ThievingComponent component, BeforeStripEvent args)

View file

@ -0,0 +1,15 @@
using Content.Shared.Inventory;
namespace Content.Shared.Temperature;
public sealed class ModifyChangedTemperatureEvent : EntityEventArgs, IInventoryRelayEvent
{
public SlotFlags TargetSlots { get; } = ~SlotFlags.POCKET;
public float TemperatureDelta;
public ModifyChangedTemperatureEvent(float temperature)
{
TemperatureDelta = temperature;
}
}

View file

@ -19,6 +19,12 @@ namespace Content.Shared.Traits
[DataField("name")]
public string Name { get; } = string.Empty;
/// <summary>
/// The description of this trait.
/// </summary>
[DataField("description")]
public string? Description { get; }
/// <summary>
/// The components that get added to the player, when they pick this trait.
/// </summary>

View file

@ -1,23 +0,0 @@
namespace Content.Shared.Transform
{
public static class TransformExtensions
{
public static void AttachToGrandparent(this TransformComponent transform)
{
var grandParent = transform.Parent?.Parent;
if (grandParent == null)
{
transform.AttachToGridOrMap();
return;
}
transform.AttachParent(grandParent);
}
public static void AttachToGrandparent(this EntityUid entity)
{
AttachToGrandparent(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(entity));
}
}
}

View file

@ -154,6 +154,23 @@ namespace Content.Shared.Verbs
/// <remarks>
/// This will try to call the action delegates and raise the local events for the given verb.
/// </remarks>
public abstract void ExecuteVerb(Verb verb, EntityUid user, EntityUid target, bool forced = false);
public virtual void ExecuteVerb(Verb verb, EntityUid user, EntityUid target, bool forced = false)
{
// invoke any relevant actions
verb.Act?.Invoke();
// Maybe raise a local event
if (verb.ExecutionEventArgs != null)
{
if (verb.EventTarget.IsValid())
RaiseLocalEvent(verb.EventTarget, verb.ExecutionEventArgs);
else
RaiseLocalEvent(verb.ExecutionEventArgs);
}
// Perform any contact interactions
if (verb.DoContactInteraction ?? (verb.DefaultDoContactInteraction && _interactionSystem.InRangeUnobstructed(user, target)))
_interactionSystem.DoContactInteraction(user, target);
}
}
}

View file

@ -146,6 +146,15 @@ namespace Content.Shared.Verbs
/// </summary>
public bool ConfirmationPopup = false;
/// <summary>
/// If true, this verb will raise <see cref="ContactInteractionEvent"/>s when executed. If not explicitly
/// specified, this will just default to raising the event if <see cref="DefaultDoContactInteraction"/> is
/// true and the user is in range.
/// </summary>
public bool? DoContactInteraction;
public virtual bool DefaultDoContactInteraction => false;
/// <summary>
/// Compares two verbs based on their <see cref="Priority"/>, <see cref="Category"/>, <see cref="Text"/>,
/// and <see cref="IconTexture"/>.
@ -235,6 +244,7 @@ namespace Content.Shared.Verbs
{
public new static string DefaultTextStyleClass = "InteractionVerb";
public override int TypePriority => 4;
public override bool DefaultDoContactInteraction => true;
public InteractionVerb() : base()
{
@ -256,6 +266,7 @@ namespace Content.Shared.Verbs
public sealed class UtilityVerb : Verb
{
public override int TypePriority => 3;
public override bool DefaultDoContactInteraction => true;
public UtilityVerb() : base()
{
@ -293,6 +304,7 @@ namespace Content.Shared.Verbs
{
public override int TypePriority => 2;
public new static string DefaultTextStyleClass = "AlternativeVerb";
public override bool DefaultDoContactInteraction => true;
public AlternativeVerb() : base()
{
@ -314,6 +326,7 @@ namespace Content.Shared.Verbs
{
public override int TypePriority => 1;
public new static string DefaultTextStyleClass = "ActivationVerb";
public override bool DefaultDoContactInteraction => true;
public ActivationVerb() : base()
{

View file

@ -47,7 +47,11 @@ public abstract class SharedFlyBySoundSystem : EntitySystem
private void OnShutdown(EntityUid uid, FlyBySoundComponent component, ComponentShutdown args)
{
if (!TryComp<PhysicsComponent>(uid, out var body)) return;
if (!TryComp<PhysicsComponent>(uid, out var body) ||
MetaData(uid).EntityLifeStage >= EntityLifeStage.Terminating)
{
return;
}
_fixtures.DestroyFixture(body, FlyByFixture);
}

View file

@ -1,7 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Verbs;
using Content.Shared.Weapons.Ranged.Components;
@ -19,6 +17,7 @@ public abstract partial class SharedGunSystem
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, TakeAmmoEvent>(OnChamberMagazineTakeAmmo);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, GetVerbsEvent<Verb>>(OnMagazineVerb);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, EntInsertedIntoContainerMessage>(OnMagazineSlotChange);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, EntRemovedFromContainerMessage>(OnMagazineSlotChange);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, UseInHandEvent>(OnMagazineUse);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, ExaminedEvent>(OnChamberMagazineExamine);
}

View file

@ -1,6 +1,4 @@
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Verbs;
using Content.Shared.Weapons.Ranged.Components;
@ -18,6 +16,7 @@ public abstract partial class SharedGunSystem
SubscribeLocalEvent<MagazineAmmoProviderComponent, TakeAmmoEvent>(OnMagazineTakeAmmo);
SubscribeLocalEvent<MagazineAmmoProviderComponent, GetVerbsEvent<Verb>>(OnMagazineVerb);
SubscribeLocalEvent<MagazineAmmoProviderComponent, EntInsertedIntoContainerMessage>(OnMagazineSlotChange);
SubscribeLocalEvent<MagazineAmmoProviderComponent, EntRemovedFromContainerMessage>(OnMagazineSlotChange);
SubscribeLocalEvent<MagazineAmmoProviderComponent, UseInHandEvent>(OnMagazineUse);
SubscribeLocalEvent<MagazineAmmoProviderComponent, ExaminedEvent>(OnMagazineExamine);
}
@ -37,7 +36,7 @@ public abstract partial class SharedGunSystem
if (magEnt == null) return;
RaiseLocalEvent(magEnt.Value, args, false);
RaiseLocalEvent(magEnt.Value, args);
UpdateAmmoCount(uid);
UpdateMagazineAppearance(component, magEnt.Value);
}
@ -50,13 +49,16 @@ public abstract partial class SharedGunSystem
if (magEnt != null)
{
RaiseLocalEvent(magEnt.Value, args, false);
RaiseLocalEvent(magEnt.Value, args);
UpdateMagazineAppearance(component, magEnt.Value);
}
}
private void OnMagazineSlotChange(EntityUid uid, MagazineAmmoProviderComponent component, EntInsertedIntoContainerMessage args)
protected virtual void OnMagazineSlotChange(EntityUid uid, MagazineAmmoProviderComponent component, ContainerModifiedMessage args)
{
if (MagazineSlot != args.Container.ID)
return;
UpdateAmmoCount(uid);
if (!TryComp<AppearanceComponent>(uid, out var appearance))
return;

View file

@ -1,142 +1,4 @@
Entries:
- author: zerorulez
changes:
- {message: Entities without hands can no longer open/close storage and toggle locks.,
type: Fix}
id: 2015
time: '2022-07-26T01:31:25.0000000+00:00'
- author: BurninDreamer
changes:
- {message: The Syndicate headset no longer misinforms you in the description!,
type: Fix}
id: 2016
time: '2022-07-26T01:38:30.0000000+00:00'
- author: DogZeroX
changes:
- {message: The bread has been unplated., type: Remove}
id: 2017
time: '2022-07-26T02:37:51.0000000+00:00'
- author: moony
changes:
- {message: Waystation has been pulled from rotation., type: Remove}
id: 2018
time: '2022-07-26T02:41:06.0000000+00:00'
- author: DogZeroX
changes:
- {message: Lizards and slimes now have different speech sounds!, type: Add}
id: 2019
time: '2022-07-26T09:13:12.0000000+00:00'
- author: Pancake
changes:
- {message: Syndicate layoffs., type: Tweak}
id: 2020
time: '2022-07-26T09:50:18.0000000+00:00'
- author: DogZeroX
changes:
- {message: The emergency boxes now have tinned meat instead of chocolate., type: Tweak}
id: 2021
time: '2022-07-26T09:56:56.0000000+00:00'
- author: Rane
changes:
- {message: Observers now have a warp for the nuke disk., type: Add}
id: 2022
time: '2022-07-26T13:55:15.0000000+00:00'
- author: metalgearsloth
changes:
- {message: do_after bar no longer flickers for a frame., type: Fix}
id: 2023
time: '2022-07-26T13:57:31.0000000+00:00'
- author: Elijahrane
changes:
- {message: 'If OOC is disabled in the middle of a round, dead players lose the
ability to talk in LOOC.', type: Add}
id: 2024
time: '2022-07-26T13:58:19.0000000+00:00'
- author: metalgearsloth
changes:
- {message: Bumped pulling modified speed from 0.75 to 0.9, type: Tweak}
id: 2025
time: '2022-07-26T13:58:40.0000000+00:00'
- author: mirrorcult
changes:
- {message: People with empty-name ID cards no longer show up as having an empty
identity., type: Fix}
id: 2026
time: '2022-07-26T23:49:15.0000000+00:00'
- author: mirrorcult
changes:
- {message: Radio messages will properly capitalize the first letter of your message
again., type: Fix}
- {message: Sending a radio message will now make you whisper locally instead of
speak., type: Add}
id: 2027
time: '2022-07-26T23:49:23.0000000+00:00'
- author: EmoGarbage404
changes:
- {message: Zombies now move slower than normal people., type: Tweak}
- {message: Zombies now do a bit of structure damage., type: Tweak}
- {message: Zombies now do further decreased damage to other zombies., type: Tweak}
- {message: Zombies that were cuffed before turning now behave properly after being
uncuffed., type: Fix}
- {message: Zombies no longer take damage over time from rotting., type: Fix}
id: 2028
time: '2022-07-27T00:35:35.0000000+00:00'
- author: Rane
changes:
- {message: 'Boxers and boxing gloves have been ported from Nyanotrasen MRP! The
role is only selectable on Lighthouse, but all boxing gloves work.', type: Add}
- {message: Map-specific jobs no longer clog up the preference menu., type: Fix}
id: 2029
time: '2022-07-27T01:34:20.0000000+00:00'
- author: Rane
changes:
- {message: Added sleeping., type: Add}
- {message: Added nitrous oxide., type: Add}
- {message: Slimes exhale nitrous oxide., type: Tweak}
- {message: Disease interaction infection checks now use the same robust method
that forensics do. No more edge cases where you don't check anything., type: Fix}
id: 2030
time: '2022-07-27T04:46:24.0000000+00:00'
- author: mirrorcult
changes:
- {message: Transylvanian vampire cloak is now available rarely in the maint fluff
spawner., type: Add}
id: 2031
time: '2022-07-27T08:15:47.0000000+00:00'
- author: mirrorcult
changes:
- {message: 'Freon has been added. It is a refrigerant gas which reacts with nitrogen
to rapidly cool down areas, increasing in effectiveness with temperature. It''s
also a hallucinogenic, and you may feel woozy (and just a little brain damaged)
after breathing it in.', type: Add}
- {message: 'Freon can be produced by combining tritium, oxygen, and nitrogen as
catalyst at temperatures below 73.15 kelvin.', type: Add}
- {message: Miasma reacts with freon to subsume and convert it into more miasma.,
type: Add}
id: 2032
time: '2022-07-27T09:55:28.0000000+00:00'
- author: Peptide90
changes:
- {message: 'Ramens description points out you need to use boiling water, not just
any water.', type: Fix}
id: 2033
time: '2022-07-27T14:18:16.0000000+00:00'
- author: FunkySphere
changes:
- {message: Fixed name of mime jumpskirt (previously "mime suit"), type: Fix}
id: 2034
time: '2022-07-27T23:15:04.0000000+00:00'
- author: EmoGarbage404
changes:
- {message: Fixed missing inhand sprites for various syndicate duffel bags., type: Fix}
id: 2035
time: '2022-07-27T23:32:43.0000000+00:00'
- author: ElectroJr
changes:
- {message: Fixed a bug preventing the Identity system from working for most players.,
type: Fix}
id: 2036
time: '2022-07-28T23:21:48.0000000+00:00'
- author: Rane
changes:
- {message: Latejoiners are now eligible for traitor., type: Tweak}
@ -2961,3 +2823,126 @@ Entries:
- {message: Fixed portable scrubbers not scrubbing from adjacent tiles., type: Fix}
id: 2514
time: '2022-10-22T22:49:30.0000000+00:00'
- author: Nairod
changes:
- {message: Added the head's mantles to the uniform printer., type: Add}
id: 2515
time: '2022-10-23T17:56:59.0000000+00:00'
- author: checkraze
changes:
- {message: Added the Zookeeper. Find them patrolling and caring for Kettle station's
various wildlife soon!, type: Add}
id: 2516
time: '2022-10-23T19:42:25.0000000+00:00'
- author: Cheackraze
changes:
- {message: Added a Zoo and a few animals to Kettle!, type: Add}
id: 2517
time: '2022-10-24T02:30:37.0000000+00:00'
- author: Peptide90
changes:
- {message: 'Pillar map major update. Atmos is now in engi, corridors are now smaller
and more alive, maint has been spruced up and lots of spaces are bigger, more
decorated and more loot sprawled.', type: Tweak}
id: 2518
time: '2022-10-24T17:30:50.0000000+00:00'
- author: Morb0
changes:
- {message: Added two new jester suits in AutoDrobe, type: Add}
id: 2519
time: '2022-10-24T18:04:55.0000000+00:00'
- author: metalgearsloth
changes:
- {message: Fix clientside organs being left around., type: Fix}
id: 2520
time: '2022-10-25T00:08:41.0000000+00:00'
- author: EmoGarbage404
changes:
- {message: Some miscellaneous electronics have been additionally added to the circuit
imprinter., type: Add}
id: 2521
time: '2022-10-25T00:22:12.0000000+00:00'
- author: ElectroJr
changes:
- {message: Nanotrasen's experimental precrime police program has been defunded
(fixed forensic evidence being applied and diseases spreading by simply examining
or being in the vicinity of items)., type: Fix}
id: 2522
time: '2022-10-26T01:15:48.0000000+00:00'
- author: 25203
changes:
- {message: Reagent grinder can now process partial stacks if the solution of the
entire stack won't fit in the beaker., type: Fix}
id: 2523
time: '2022-10-26T06:34:56.0000000+00:00'
- author: JustinTrotter
changes:
- {message: Syringes can now set how much solution to transfer., type: Add}
id: 2524
time: '2022-10-26T06:40:06.0000000+00:00'
- author: exincore
changes:
- {message: added keybind awareness to chatbox hint, type: Add}
id: 2525
time: '2022-10-26T14:40:12.0000000+00:00'
- author: metalgearsloth
changes:
- {message: Fix the item status for chambered guns flickering., type: Fix}
id: 2526
time: '2022-10-26T14:46:13.0000000+00:00'
- author: ElectroJr
changes:
- {message: Fixed a bug that was causing bullets and other physics objects to not
work properly., type: Fix}
- {message: Fixed a bug that was causing crashes when attempting to follow or teleport
to entities., type: Fix}
id: 2527
time: '2022-10-27T02:35:44.0000000+00:00'
- author: checkraze
changes:
- {message: Nanotrasen has updated their Dart ERT shuttle for faster response times!,
type: Tweak}
id: 2528
time: '2022-10-27T11:54:12.0000000+00:00'
- author: metalgearsloth
changes:
- {message: Fix entity storage closing and some other erroneous flags., type: Fix}
id: 2529
time: '2022-10-27T14:09:16.0000000+00:00'
- author: ShadowCommander
changes:
- {message: Ghostly bodies have been limited to one per person., type: Remove}
id: 2530
time: '2022-10-27T14:09:36.0000000+00:00'
- author: moony
changes:
- {message: Removed split-station due to map rotation limits., type: Remove}
id: 2531
time: '2022-10-27T14:31:20.0000000+00:00'
- author: JustinTrotter
changes:
- {message: Mice scream properly, type: Tweak}
id: 2532
time: '2022-10-28T00:42:36.0000000+00:00'
- author: tsp01
changes:
- {message: The range from which you can hear players has been increased from 7
to 10 units., type: Tweak}
id: 2533
time: '2022-10-28T03:25:48.0000000+00:00'
- author: ElectroJr
changes:
- {message: Fixed a bug causing some entities not being interactable., type: Fix}
id: 2534
time: '2022-10-28T03:30:48.0000000+00:00'
- author: Nairod
changes:
- {message: Changed it so lamps now spawn with power cells., type: Tweak}
id: 2535
time: '2022-10-28T05:22:09.0000000+00:00'
- author: ElectroJr
changes:
- {message: Fixed being unable to interact with construction outlines / ghosts.,
type: Fix}
id: 2536
time: '2022-10-28T06:08:47.0000000+00:00'

View file

@ -1,4 +1,4 @@
hud-chatbox-info = T to talk, Tab to cycle channels.
hud-chatbox-info = {$talk-key} to talk, {$cycle-key} to cycle channels.
hud-chatbox-select-name-prefixed = {$prefix} {$name}
hud-chatbox-select-channel-Admin = Admin

View file

@ -178,6 +178,8 @@ ui-options-function-shuttle-brake = Brake
## Network menu
ui-options-net-predict = Client-side prediction
ui-options-net-interp-ratio = State buffer size
ui-options-net-interp-ratio-tooltip = Increasing this will generally make the game more resistant
to server->client packet-loss, however in doing so it
@ -190,11 +192,16 @@ ui-options-net-predict-tick-bias-tooltip = Increasing this will generally make t
effectively adds slightly more latency and requires the
client to predict more future ticks.
ui-options-net-pvs-entry = PVS entity budget
ui-options-net-pvs-entry-tooltip = This limits the rate at which the server will send new
ui-options-net-pvs-spawn = PVS entity spawn budget
ui-options-net-pvs-spawn-tooltip = This limits the rate at which the server will send newly spawned
entities to the client. Lowering this can help reduce
stuttering due to entity spawning, but can lead to pop-in.
ui-options-net-pvs-entry = PVS entity budget
ui-options-net-pvs-entry-tooltip = This limits the rate at which the server will send newly visible
entities to the client. Lowering this can help reduce
stuttering, but can lead to pop-in.
ui-options-net-pvs-leave = PVS detach rate
ui-options-net-pvs-leave-tooltip = This limits the rate at which the client will remove
out-of-view entities. Lowering this can help reduce

View file

@ -39,6 +39,7 @@ job-name-ertsecurity = ERT security
job-name-ertmedic = ERT medic
job-name-ertjanitor = ERT janitor
job-name-boxer = boxer
job-name-zookeeper = zookeeper
# Role timers - Make these alphabetical or I cut you
JobAtmosphericTechnician = atmospheric technician
@ -82,3 +83,4 @@ JobStationEngineer = station engineer
JobTechnicalAssistant = technical assistant
JobWarden = warden
JobBoxer = boxer
JobZookeeper = zookeeper

View file

@ -2,7 +2,8 @@
reagent-grinder-bound-user-interface-instant-button = INSTANT
reagent-grinder-bound-user-interface-cook-time-label = COOK TIME
reagent-grinder-component-cannot-put-entity-message = You can't put this in the reagent grinder!
grinder-menu-title = All-In-One Grinder 3000
grinder-menu-grind-button = Grind
grinder-menu-juice-button = Juice

View file

@ -6,6 +6,10 @@ tiles-steel-floor-mini = steel mini-tile
tiles-steel-floor-pavement = steel pavement
tiles-steel-floor-diagonal = steel diagonal tile
tiles-steel-floor-offset = offset steel tile
tiles-steel-floor-mono = steel mono tile
tiles-steel-floor-pavement-vertical = steel vertical pavement
tiles-steel-floor-herringbone = steel herringbone
tiles-steel-floor-diagonal-mini = steel diagonal mini-tile
tiles-plastic-floor = plastic tile
tiles-wood = wood
tiles-white-floor = white steel tile
@ -13,12 +17,20 @@ tiles-white-floor-mini = white steel mini-tile
tiles-white-floor-pavement = white steel pavement
tiles-white-floor-diagonal = white steel diagonal tile
tiles-white-floor-offset = offset white steel tile
tiles-white-floor-mono = white steel mono tile
tiles-white-floor-pavement-vertical = white steel vertical pavement
tiles-white-floor-herringbone = white steel herringbone
tiles-white-floor-diagonal-mini = white steel diagonal mini-tile
tiles-plastic-white-floor = white plastic tile
tiles-dark-floor = dark steel tile
tiles-dark-floor-mini = dark steel mini-tile
tiles-dark-floor-pavement = dark steel pavement
tiles-dark-floor-diagonal = dark steel diagonal tile
tiles-dark-floor-offset = offset dark steel tile
tiles-dark-floor-mono = dark steel mono tile
tiles-dark-floor-pavement-vertical = dark steel vertical pavement
tiles-dark-floor-herringbone = dark steel herringbone
tiles-dark-floor-diagonal-mini = dark steel diagonal mini-tile
tiles-plastic-dark-floor = dark plastic tile
tiles-techmaint-floor = techmaint floor
tiles-reinforced-floor = reinforced floor
@ -70,3 +82,6 @@ tiles-asteroid-ironsand-pebbles = asteroid ironsand pebbles
tiles-asteroid-ironsand-rock = asteroid ironsand rock
tiles-cave = cave
tiles-cave-drought = cave drought
tiles-techmaint3-floor = grated maintenance floor
tiles-techmaint2-floor = steel maintenance floor
tiles-wood2 = wood pattern floor

View file

@ -663,7 +663,6 @@ entities:
parent: 943
type: Transform
- type: MapGrid
- type: EntityLookup
- type: Broadphase
- id: Bagel
type: BecomesStation
@ -31751,7 +31750,6 @@ entities:
- type: Transform
- index: 4
type: Map
- type: EntityLookup
- type: Broadphase
- type: PhysicsMap
- type: OccluderTree

View file

@ -5535,7 +5535,6 @@ entities:
- type: Transform
- index: 5
type: Map
- type: EntityLookup
- type: Broadphase
- type: PhysicsMap
- type: OccluderTree
@ -56709,7 +56708,6 @@ entities:
parent: 780
type: Transform
- type: MapGrid
- type: EntityLookup
- type: Broadphase
- id: Boxstation
type: BecomesStation
@ -113447,7 +113445,6 @@ entities:
type: Transform
- index: 1
type: MapGrid
- type: EntityLookup
- type: Broadphase
- angularDamping: 0.05
linearDamping: 0.05

View file

@ -134,7 +134,6 @@ entities:
- parent: null
type: Transform
- type: MapGrid
- type: EntityLookup
- type: Broadphase
- id: centcomm
type: BecomesStation

View file

@ -918,7 +918,6 @@ entities:
- type: Transform
- index: 7
type: Map
- type: EntityLookup
- type: Broadphase
- type: PhysicsMap
- type: OccluderTree
@ -953,7 +952,6 @@ entities:
parent: 122
type: Transform
- type: MapGrid
- type: EntityLookup
- type: Broadphase
- angularDamping: 0.05
linearDamping: 0.05

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,11 @@
- type: vendingMachineInventory
id: AutoDrobeInventory
startingInventory:
ClothingHeadHatJester: 1
ClothingUniformJumpsuitJester: 1
ClothingHeadHatJesterAlt: 1
ClothingUniformJumpsuitJesterAlt: 1
ClothingShoesJester: 1
ClothingOuterWinterClown: 1
ClothingOuterWinterMime: 1
ClothingOuterWinterMusician: 1

View file

@ -102,3 +102,143 @@
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: checkerNWSE
- type: decal
id: DiagonalOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: diagonal
- type: decal
id: DiagonalCheckerAOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: diagonal_checker_a
- type: decal
id: DiagonalCheckerBOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: diagonal_checker_b
- type: decal
id: HerringboneOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: herringbone
- type: decal
id: MiniTileOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: minitile
- type: decal
id: MiniTileCheckerAOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: minitile_checker_a
- type: decal
id: MiniTileCheckerBOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: minitile_checker_b
- type: decal
id: MiniTileDiagonalOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: minitile_diagonal
- type: decal
id: MiniTileDiagonalCheckerAOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: minitile_diagonal_a
- type: decal
id: MiniTileDiagonalCheckerBOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: minitile_diagonal_b
- type: decal
id: MonoOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: mono
- type: decal
id: OffsetOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: offset
- type: decal
id: OffsetCheckerAOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: offset_checker_a
- type: decal
id: OffsetCheckerBOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: offset_checker_b
- type: decal
id: PavementOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: pavement
- type: decal
id: PavementCheckerAOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: pavement_checker_a
- type: decal
id: PavementCheckerBOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: pavement_checker_b
- type: decal
id: PavementVerticalOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: pavement_vertical
- type: decal
id: PavementVerticalCheckerAOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: pavement_vertical_checker_a
- type: decal
id: PavementVerticalCheckerBOverlay
tags: ["station", "overlay"]
sprite:
sprite: Decals/Overlays/greyscale.rsi
state: pavement_vertical_checker_b

View file

@ -0,0 +1,118 @@
- type: decal
id: BrickTileDarkBox
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: dark_box
- type: decal
id: BrickTileDarkCornerNe
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: dark_corner_ne
- type: decal
id: BrickTileDarkCornerSe
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: dark_corner_se
- type: decal
id: BrickTileDarkCornerNw
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: dark_corner_nw
- type: decal
id: BrickTileDarkCornerSw
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: dark_corner_sw
- type: decal
id: BrickTileDarkInnerNe
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: dark_inner_ne
- type: decal
id: BrickTileDarkInnerSe
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: dark_inner_se
- type: decal
id: BrickTileDarkInnerNw
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: dark_inner_nw
- type: decal
id: BrickTileDarkInnerSw
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: dark_inner_sw
- type: decal
id: BrickTileDarkEndN
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: dark_end_n
- type: decal
id: BrickTileDarkEndE
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: dark_end_e
- type: decal
id: BrickTileDarkEndS
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: dark_end_s
- type: decal
id: BrickTileDarkEndW
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: dark_end_w
- type: decal
id: BrickTileDarkLineN
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: dark_line_n
- type: decal
id: BrickTileDarkLineE
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: dark_line_e
- type: decal
id: BrickTileDarkLineS
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: dark_line_s
- type: decal
id: BrickTileDarkLineW
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: dark_line_w

View file

@ -0,0 +1,118 @@
- type: decal
id: BrickTileSteelBox
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: steel_box
- type: decal
id: BrickTileSteelCornerNe
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: steel_corner_ne
- type: decal
id: BrickTileSteelCornerSe
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: steel_corner_se
- type: decal
id: BrickTileSteelCornerNw
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: steel_corner_nw
- type: decal
id: BrickTileSteelCornerSw
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: steel_corner_sw
- type: decal
id: BrickTileSteelInnerNe
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: steel_inner_ne
- type: decal
id: BrickTileSteelInnerSe
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: steel_inner_se
- type: decal
id: BrickTileSteelInnerNw
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: steel_inner_nw
- type: decal
id: BrickTileSteelInnerSw
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: steel_inner_sw
- type: decal
id: BrickTileSteelEndN
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: steel_end_n
- type: decal
id: BrickTileSteelEndE
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: steel_end_e
- type: decal
id: BrickTileSteelEndS
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: steel_end_s
- type: decal
id: BrickTileSteelEndW
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: steel_end_w
- type: decal
id: BrickTileSteelLineN
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: steel_line_n
- type: decal
id: BrickTileSteelLineE
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: steel_line_e
- type: decal
id: BrickTileSteelLineS
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: steel_line_s
- type: decal
id: BrickTileSteelLineW
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: steel_line_w

View file

@ -0,0 +1,118 @@
- type: decal
id: BrickTileWhiteBox
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: white_box
- type: decal
id: BrickTileWhiteCornerNe
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: white_corner_ne
- type: decal
id: BrickTileWhiteCornerSe
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: white_corner_se
- type: decal
id: BrickTileWhiteCornerNw
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: white_corner_nw
- type: decal
id: BrickTileWhiteCornerSw
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: white_corner_sw
- type: decal
id: BrickTileWhiteInnerNe
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: white_inner_ne
- type: decal
id: BrickTileWhiteInnerSe
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: white_inner_se
- type: decal
id: BrickTileWhiteInnerNw
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: white_inner_nw
- type: decal
id: BrickTileWhiteInnerSw
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: white_inner_sw
- type: decal
id: BrickTileWhiteEndN
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: white_end_n
- type: decal
id: BrickTileWhiteEndE
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: white_end_e
- type: decal
id: BrickTileWhiteEndS
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: white_end_s
- type: decal
id: BrickTileWhiteEndW
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: white_end_w
- type: decal
id: BrickTileWhiteLineN
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: white_line_n
- type: decal
id: BrickTileWhiteLineE
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: white_line_e
- type: decal
id: BrickTileWhiteLineS
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: white_line_s
- type: decal
id: BrickTileWhiteLineW
tags: ["station", "markings"]
sprite:
sprite: Decals/bricktile.rsi
state: white_line_w

View file

@ -0,0 +1,92 @@
- type: decal
id: OriginStationSign1
tags: ["station"]
sprite:
sprite: Decals/originsign.rsi
state: origin1
- type: decal
id: OriginStationSign2
tags: ["station"]
sprite:
sprite: Decals/originsign.rsi
state: origin2
- type: decal
id: OriginStationSign3
tags: ["station"]
sprite:
sprite: Decals/originsign.rsi
state: origin3
- type: decal
id: OriginStationSign4
tags: ["station"]
sprite:
sprite: Decals/originsign.rsi
state: origin4
- type: decal
id: OriginStationSign5
tags: ["station"]
sprite:
sprite: Decals/originsign.rsi
state: origin5
- type: decal
id: OriginStationSign6
tags: ["station"]
sprite:
sprite: Decals/originsign.rsi
state: origin6
- type: decal
id: OriginStationSign7
tags: ["station"]
sprite:
sprite: Decals/originsign.rsi
state: origin7
- type: decal
id: OriginStationSign8
tags: ["station"]
sprite:
sprite: Decals/originsign.rsi
state: origin8
- type: decal
id: OriginStationSign9
tags: ["station"]
sprite:
sprite: Decals/originsign.rsi
state: origin9
- type: decal
id: OriginStationSign10
tags: ["station"]
sprite:
sprite: Decals/originsign.rsi
state: origin10
- type: decal
id: OriginStationSign11
tags: ["station"]
sprite:
sprite: Decals/originsign.rsi
state: origin11
- type: decal
id: OriginStationSign12
tags: ["station"]
sprite:
sprite: Decals/originsign.rsi
state: origin12
- type: decal
id: OriginStationSign13
tags: ["station"]
sprite:
sprite: Decals/originsign.rsi
state: origin13

View file

@ -478,3 +478,34 @@
sprite: Clothing/Head/Hats/rasta.rsi
- type: Clothing
sprite: Clothing/Head/Hats/rasta.rsi
- type: entity
parent: ClothingHeadBase
id: ClothingHeadSafari
name: safari hat
description: Keeps the sun out of your eyes. Makes you a target for the locals.
components:
- type: Sprite
sprite: Clothing/Head/Hats/safarihat.rsi
- type: Clothing
sprite: Clothing/Head/Hats/safarihat.rsi
- type: entity
parent: ClothingHeadBase
id: ClothingHeadHatJester
name: jester hat
description: A hat with bells, to add some merriness to the suit.
components:
- type: Sprite
sprite: Clothing/Head/Hats/jester.rsi
- type: Clothing
sprite: Clothing/Head/Hats/jester.rsi
- type: entity
parent: ClothingHeadHatJester
id: ClothingHeadHatJesterAlt
components:
- type: Sprite
sprite: Clothing/Head/Hats/jester2.rsi
- type: Clothing
sprite: Clothing/Head/Hats/jester2.rsi

View file

@ -130,3 +130,14 @@
description: Stylish black shoes.
components:
- type: NoSlip
- type: entity
parent: ClothingShoesClown
id: ClothingShoesJester
name: jester shoes
description: A court jester's shoes, updated with modern squeaking technology.
components:
- type: Sprite
sprite: Clothing/Shoes/Specific/jester.rsi
- type: Clothing
sprite: Clothing/Shoes/Specific/jester.rsi

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