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

# Conflicts:
#	Content.Server/Nuke/NukeComponent.cs
#	Resources/Prototypes/Voice/disease_emotes.yml
This commit is contained in:
Morbo
2023-04-29 14:53:32 +03:00
375 changed files with 4017 additions and 2083 deletions

1
.gitignore vendored
View File

@@ -305,3 +305,4 @@ Resources/MapImages
/Content.Docfx/api/
/Content.Docfx/*site
*.bak

View File

@@ -1,5 +1,6 @@
using Content.Client.Eui;
using Content.Shared.Administration;
using Content.Shared.Eui;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Utility;
@@ -12,7 +13,7 @@ namespace Content.Client.Administration.UI
public AdminAnnounceEui()
{
_window = new AdminAnnounceWindow();
_window.OnClose += () => SendMessage(new AdminAnnounceEuiMsg.Close());
_window.OnClose += () => SendMessage(new CloseEuiMessage());
_window.AnnounceButton.OnPressed += AnnounceButtonOnOnPressed;
}

View File

@@ -1,6 +1,7 @@
using Content.Client.Eui;
using Content.Shared.Administration.BanList;
using Content.Shared.Eui;
using Content.Shared.Ghost.Roles;
namespace Content.Client.Administration.UI.BanList;
@@ -9,9 +10,21 @@ public sealed class BanListEui : BaseEui
public BanListEui()
{
BanWindow = new BanListWindow();
BanWindow.OnClose += OnClosed;
BanControl = BanWindow.BanList;
}
private void OnClosed()
{
SendMessage(new CloseEuiMessage());
}
public override void Closed()
{
base.Closed();
BanWindow.Close();
}
private BanListWindow BanWindow { get; }
private BanListControl BanControl { get; }

View File

@@ -19,6 +19,7 @@ public sealed class AdminLogsEui : BaseEui
public AdminLogsEui()
{
LogsWindow = new AdminLogsWindow();
LogsWindow.OnClose += OnCloseWindow;
LogsControl = LogsWindow.Logs;
LogsControl.LogSearch.OnTextEntered += _ => RequestLogs();
@@ -39,7 +40,13 @@ public sealed class AdminLogsEui : BaseEui
private void OnRequestClosed(WindowRequestClosedEventArgs args)
{
SendMessage(new Close());
SendMessage(new CloseEuiMessage());
}
private void OnCloseWindow()
{
if (ClydeWindow == null)
SendMessage(new CloseEuiMessage());
}
private void RequestLogs()
@@ -74,10 +81,6 @@ public sealed class AdminLogsEui : BaseEui
return;
}
LogsControl.Orphan();
LogsWindow.Dispose();
LogsWindow = null;
var monitor = _clyde.EnumerateMonitors().First();
ClydeWindow = _clyde.CreateWindow(new WindowCreateParameters
@@ -89,6 +92,10 @@ public sealed class AdminLogsEui : BaseEui
Height = 400
});
LogsControl.Orphan();
LogsWindow.Dispose();
LogsWindow = null;
ClydeWindow.RequestClosed += OnRequestClosed;
ClydeWindow.DisposeOnClose = true;

View File

@@ -16,7 +16,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
public EditSolutionsEui()
{
_window = new EditSolutionsWindow();
_window.OnClose += () => SendMessage(new EditSolutionsEuiMsg.Close());
_window.OnClose += () => SendMessage(new CloseEuiMessage());
}
public override void Opened()
@@ -28,7 +28,6 @@ namespace Content.Client.Administration.UI.ManageSolutions
public override void Closed()
{
base.Closed();
_window.OnClose -= () => SendMessage(new EditSolutionsEuiMsg.Close());
_window.Close();
}

View File

@@ -17,6 +17,18 @@ public sealed class AdminNotesEui : BaseEui
NoteControl.OnNoteChanged += (id, text) => SendMessage(new EditNoteRequest(id, text));
NoteControl.OnNewNoteEntered += text => SendMessage(new CreateNoteRequest(text));
NoteControl.OnNoteDeleted += id => SendMessage(new DeleteNoteRequest(id));
NoteWindow.OnClose += OnClosed;
}
private void OnClosed()
{
SendMessage(new CloseEuiMessage());
}
public override void Closed()
{
base.Closed();
NoteWindow.Close();
}
private AdminNotesWindow NoteWindow { get; }

View File

@@ -45,6 +45,7 @@ namespace Content.Client.Administration.UI
{
base.Closed();
SendMessage(new CloseEuiMessage());
CloseEverything();
}

View File

@@ -12,6 +12,12 @@ namespace Content.Client.Administration.UI.SetOutfit
public SetOutfitEui()
{
_window = new SetOutfitMenu();
_window.OnClose += OnClosed;
}
private void OnClosed()
{
SendMessage(new CloseEuiMessage());
}
public override void Opened()

View File

@@ -38,7 +38,7 @@ public sealed class SpawnExplosionEui : BaseEui
public void SendClosedMessage()
{
SendMessage(new SpawnExplosionEuiMsg.Close());
SendMessage(new CloseEuiMessage());
}
public void ClearOverlay()

View File

@@ -1,13 +1,13 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'anomaly-generator-ui-title'}"
MinSize="270 180"
SetSize="360 180">
<BoxContainer Margin="10 0 10 0"
Resizable="False">
<BoxContainer Margin="0 0"
Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True">
<BoxContainer Orientation="Horizontal">
<BoxContainer Margin="10 0 10 0" Orientation="Horizontal">
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
@@ -30,6 +30,7 @@
<RichTextLabel Name="CooldownLabel" StyleClasses="StatusFieldTitle" />
<RichTextLabel Name="ReadyLabel" StyleClasses="StatusFieldTitle" />
</BoxContainer>
<!--Sprite View-->
<PanelContainer Margin="12 0 0 0"
StyleClasses="Inset"
VerticalAlignment="Center">
@@ -40,9 +41,22 @@
</BoxContainer>
<BoxContainer VerticalExpand="True"
HorizontalAlignment="Center"
VerticalAlignment="Center">
VerticalAlignment="Center"
Margin="0 10">
<Button Name="GenerateButton"
Text="{Loc 'anomaly-generator-generate'}"></Button>
</BoxContainer>
<!-- Footer -->
<BoxContainer Orientation="Vertical">
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer Orientation="Horizontal" Margin="10 2 5 0" VerticalAlignment="Bottom">
<Label Text="{Loc 'anomaly-generator-flavor-left'}" StyleClasses="WindowFooterText" />
<Label Text="{Loc 'anomaly-generator-flavor-right'}" StyleClasses="WindowFooterText"
HorizontalAlignment="Right" HorizontalExpand="True" Margin="0 0 5 0" />
<TextureRect StyleClasses="NTLogoDark" Stretch="KeepAspectCentered"
VerticalAlignment="Center" HorizontalAlignment="Right" SetSize="19 19"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>

View File

@@ -25,6 +25,7 @@ public sealed partial class AnomalyGeneratorWindow : FancyWindow
IoCManager.InjectDependencies(this);
EntityView.Sprite = _entityManager.GetComponent<SpriteComponent>(gen);
EntityView.SpriteOffset = false;
GenerateButton.OnPressed += _ => OnGenerateButtonPressed?.Invoke();
}

View File

@@ -0,0 +1,24 @@
using Content.Shared.Chemistry.Components;
using Robust.Client.GameObjects;
namespace Content.Client.Chemistry.EntitySystems;
public sealed class PillSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PillComponent, AfterAutoHandleStateEvent>(OnHandleState);
}
private void OnHandleState(EntityUid uid, PillComponent component, ref AfterAutoHandleStateEvent args)
{
if (!TryComp(uid, out SpriteComponent? sprite))
return;
if (!sprite.TryGetLayer(0, out var layer))
return;
layer.SetState($"pill{component.PillType + 1}");
}
}

View File

@@ -20,6 +20,8 @@ namespace Content.Client.Cloning.UI
_window.Close();
};
_window.OnClose += () => SendMessage(new AcceptCloningChoiceMessage(AcceptCloningUiButton.Deny));
_window.AcceptButton.OnPressed += _ =>
{
SendMessage(new AcceptCloningChoiceMessage(AcceptCloningUiButton.Accept));

View File

@@ -16,7 +16,7 @@ public sealed class CrewManifestEui : BaseEui
_window.OnClose += () =>
{
SendMessage(new CrewManifestEuiClosed());
SendMessage(new CloseEuiMessage());
};
}

View File

@@ -15,6 +15,7 @@ using Robust.Shared.Map;
using Robust.Shared.Utility;
using System.Linq;
using System.Threading;
using Content.Shared.Eye.Blinding.Components;
using static Content.Shared.Interaction.SharedInteractionSystem;
using static Robust.Client.UserInterface.Controls.BoxContainer;

View File

@@ -4,6 +4,7 @@ using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Content.Shared.Eye.Blinding;
using Content.Shared.Eye.Blinding.Components;
namespace Content.Client.Eye.Blinding
{
@@ -46,7 +47,7 @@ namespace Content.Client.Eye.Blinding
_blindableComponent = blindComp;
var blind = _blindableComponent.Sources > 0;
var blind = _blindableComponent.IsBlind;
if (!blind && _blindableComponent.LightSetup) // Turn FOV back on if we can see again
{

View File

@@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Linq;
using Content.Shared.Administration;
using Content.Shared.Administration.Events;
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.GameTicking;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;

View File

@@ -4,24 +4,21 @@ using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Content.Shared.Eye.Blinding;
using Content.Shared.Eye.Blinding.Components;
namespace Content.Client.Eye.Blinding
{
public sealed class BlurryVisionOverlay : Overlay
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
public override bool RequestScreenTexture => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _dim;
private BlurryVisionComponent _blurryVisionComponent = default!;
private float _magnitude;
public BlurryVisionOverlay()
{
IoCManager.InjectDependencies(this);
_dim = _prototypeManager.Index<ShaderPrototype>("Dim").InstanceUnique();
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
@@ -40,33 +37,29 @@ namespace Content.Client.Eye.Blinding
if (!_entityManager.TryGetComponent<BlurryVisionComponent>(playerEntity, out var blurComp))
return false;
if (!blurComp.Active)
if (blurComp.Magnitude <= 0)
return false;
if (_entityManager.TryGetComponent<BlindableComponent>(playerEntity, out var blindComp)
&& blindComp.Sources > 0)
&& blindComp.IsBlind)
return false;
_blurryVisionComponent = blurComp;
_magnitude = blurComp.Magnitude;
return true;
}
protected override void Draw(in OverlayDrawArgs args)
{
if (ScreenTexture == null)
return;
var opacity = -(_blurryVisionComponent.Magnitude / 15) + 0.9f;
_dim.SetParameter("DAMAGE_AMOUNT", opacity);
// TODO make this better.
// This is a really shitty effect.
// Maybe gradually shrink the view-size?
// Make the effect only apply to the edge of the viewport?
// Actually make it blurry??
var opacity = 0.5f * _magnitude / BlurryVisionComponent.MaxMagnitude;
var worldHandle = args.WorldHandle;
var viewport = args.WorldBounds;
worldHandle.UseShader(_dim);
worldHandle.SetTransform(Matrix3.Identity);
worldHandle.DrawRect(viewport, Color.Black);
worldHandle.UseShader(null);
worldHandle.DrawRect(viewport, Color.White.WithAlpha(opacity));
}
}
}

View File

@@ -1,4 +1,5 @@
using Content.Shared.Eye.Blinding;
using Content.Shared.Eye.Blinding.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
@@ -22,8 +23,6 @@ public sealed class BlurryVisionSystem : EntitySystem
SubscribeLocalEvent<BlurryVisionComponent, PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<BlurryVisionComponent, PlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<BlurryVisionComponent, ComponentHandleState>(OnHandleState);
_overlay = new();
}
@@ -50,12 +49,4 @@ public sealed class BlurryVisionSystem : EntitySystem
_overlayMan.RemoveOverlay(_overlay);
}
}
private void OnHandleState(EntityUid uid, BlurryVisionComponent component, ref ComponentHandleState args)
{
if (args.Current is not BlurryVisionComponentState state)
return;
component.Magnitude = state.Magnitude;
}
}

View File

@@ -36,7 +36,7 @@ public sealed class PuddleSystem : SharedPuddleSystem
args.Sprite.LayerSetState(0, $"{smooth.StateBase}a");
_smooth.SetEnabled(uid, false, smooth);
}
else if (volume < 0.6f)
else if (volume < MediumThreshold)
{
args.Sprite.LayerSetState(0, $"{smooth.StateBase}b");
_smooth.SetEnabled(uid, false, smooth);

View File

@@ -16,6 +16,7 @@ namespace Content.Client.Hands
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
private HandsSystem? _hands;
private readonly IRenderTexture _renderBackbuffer;
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
@@ -58,9 +59,10 @@ namespace Content.Client.Hands
return;
}
var handEntity = EntityOverride ?? EntitySystem.Get<HandsSystem>().GetActiveHandEntity();
_hands ??= _entMan.System<HandsSystem>();
var handEntity = _hands.GetActiveHandEntity();
if (handEntity == null || !_entMan.HasComponent<SpriteComponent>(handEntity))
if (handEntity == null || !_entMan.TryGetComponent(handEntity, out SpriteComponent? sprite))
return;
var halfSize = _renderBackbuffer.Size / 2;
@@ -68,7 +70,7 @@ namespace Content.Client.Hands
screen.RenderInRenderTarget(_renderBackbuffer, () =>
{
screen.DrawEntity(handEntity.Value, halfSize, new Vector2(1f, 1f) * uiScale, Direction.South);
screen.DrawEntity(handEntity.Value, halfSize, new Vector2(1f, 1f) * uiScale, Angle.Zero, Angle.Zero, Direction.South, sprite);
}, Color.Transparent);
screen.DrawTexture(_renderBackbuffer.Texture, mousePos - halfSize + offset, Color.White.WithAlpha(0.75f));

View File

@@ -14,8 +14,10 @@ namespace Content.Client.Input
var common = contexts.GetContext("common");
common.AddFunction(ContentKeyFunctions.FocusChat);
common.AddFunction(ContentKeyFunctions.FocusLocalChat);
common.AddFunction(ContentKeyFunctions.FocusEmote);
common.AddFunction(ContentKeyFunctions.FocusWhisperChat);
common.AddFunction(ContentKeyFunctions.FocusRadio);
common.AddFunction(ContentKeyFunctions.FocusLOOC);
common.AddFunction(ContentKeyFunctions.FocusOOC);
common.AddFunction(ContentKeyFunctions.FocusAdminChat);
common.AddFunction(ContentKeyFunctions.FocusConsoleChat);

View File

@@ -40,9 +40,14 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
}
}
public override bool TryInsertMaterialEntity(EntityUid user, EntityUid toInsert, EntityUid receiver, MaterialStorageComponent? component = null)
public override bool TryInsertMaterialEntity(EntityUid user,
EntityUid toInsert,
EntityUid receiver,
MaterialStorageComponent? storage = null,
MaterialComponent? material = null,
PhysicalCompositionComponent? composition = null)
{
if (!base.TryInsertMaterialEntity(user, toInsert, receiver, component))
if (!base.TryInsertMaterialEntity(user, toInsert, receiver, storage, material, composition))
return false;
_transform.DetachParentToNull(toInsert, Transform(toInsert));
return true;

View File

@@ -1,4 +1,5 @@
using Content.Client.Eui;
using Content.Shared.Eui;
namespace Content.Client.NPC;
@@ -11,6 +12,12 @@ public sealed class NPCEui : BaseEui
base.Opened();
_window = new NPCWindow();
_window.OpenCentered();
_window.OnClose += OnClosed;
}
private void OnClosed()
{
SendMessage(new CloseEuiMessage());
}
public override void Closed()

View File

@@ -3,12 +3,11 @@
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="NPC debug"
MinSize="200 200">
<BoxContainer Name="Options" Orientation="Vertical">
<BoxContainer Name="Options" Orientation="Vertical" Margin="8 8">
<controls:StripeBack>
<Label Text="NPC" HorizontalAlignment="Center"/>
</controls:StripeBack>
<BoxContainer Name="NPCBox" Orientation="Vertical">
<CheckBox Name="NPCPath" Text="Path"/>
<CheckBox Name="NPCThonk" Text="Thonk"/>
</BoxContainer>
<controls:StripeBack>

View File

@@ -1,3 +1,4 @@
using Content.Client.NPC.HTN;
using Content.Client.UserInterface.Controls;
using Content.Shared.NPC;
using Robust.Client.AutoGenerated;
@@ -13,14 +14,17 @@ public sealed partial class NPCWindow : FancyWindow
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
var sysManager = IoCManager.Resolve<IEntitySystemManager>();
var htn = sysManager.GetEntitySystem<HTNSystem>();
var path = sysManager.GetEntitySystem<PathfindingSystem>();
NPCThonk.Pressed = htn.EnableOverlay;
PathCrumbs.Pressed = (path.Modes & PathfindingDebugMode.Breadcrumbs) != 0x0;
PathPolys.Pressed = (path.Modes & PathfindingDebugMode.Polys) != 0x0;
PathNeighbors.Pressed = (path.Modes & PathfindingDebugMode.PolyNeighbors) != 0x0;
PathRouteCosts.Pressed = (path.Modes & PathfindingDebugMode.RouteCosts) != 0x0;
PathRoutes.Pressed = (path.Modes & PathfindingDebugMode.Routes) != 0x0;
NPCThonk.OnToggled += args => htn.EnableOverlay = args.Pressed;
PathCrumbs.OnToggled += args => path.Modes ^= PathfindingDebugMode.Breadcrumbs;
PathPolys.OnToggled += args => path.Modes ^= PathfindingDebugMode.Polys;
PathNeighbors.OnToggled += args => path.Modes ^= PathfindingDebugMode.PolyNeighbors;

View File

@@ -129,8 +129,10 @@ namespace Content.Client.Options.UI.Tabs
AddHeader("ui-options-header-ui");
AddButton(ContentKeyFunctions.FocusChat);
AddButton(ContentKeyFunctions.FocusLocalChat);
AddButton(ContentKeyFunctions.FocusEmote);
AddButton(ContentKeyFunctions.FocusWhisperChat);
AddButton(ContentKeyFunctions.FocusRadio);
AddButton(ContentKeyFunctions.FocusLOOC);
AddButton(ContentKeyFunctions.FocusOOC);
AddButton(ContentKeyFunctions.FocusAdminChat);
AddButton(ContentKeyFunctions.FocusDeadChat);

View File

@@ -1,79 +0,0 @@
using Content.Shared.Power;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Client.PowerCell
{
[UsedImplicitly]
public sealed class PowerChargerVisualizer : AppearanceVisualizer
{
[Obsolete("Subscribe to your component being initialised instead.")]
public override void InitializeEntity(EntityUid entity)
{
base.InitializeEntity(entity);
var sprite = IoCManager.Resolve<IEntityManager>().GetComponent<SpriteComponent>(entity);
// Base item
sprite.LayerMapSet(Layers.Base, sprite.AddLayerState("empty"));
// Light
sprite.LayerMapSet(Layers.Light, sprite.AddLayerState("light-off"));
sprite.LayerSetShader(Layers.Light, "unshaded");
}
[Obsolete("Subscribe to AppearanceChangeEvent instead.")]
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
var sprite = IoCManager.Resolve<IEntityManager>().GetComponent<SpriteComponent>(component.Owner);
// Update base item
if (component.TryGetData(CellVisual.Occupied, out bool occupied))
{
// TODO: don't throw if it doesn't have a full state
sprite.LayerSetState(Layers.Base, occupied ? "full" : "empty");
}
else
{
sprite.LayerSetState(Layers.Base, "empty");
}
// Update lighting
if (component.TryGetData(CellVisual.Light, out CellChargerStatus status))
{
switch (status)
{
case CellChargerStatus.Off:
sprite.LayerSetState(Layers.Light, "light-off");
break;
case CellChargerStatus.Empty:
sprite.LayerSetState(Layers.Light, "light-empty");
break;
case CellChargerStatus.Charging:
sprite.LayerSetState(Layers.Light, "light-charging");
break;
case CellChargerStatus.Charged:
sprite.LayerSetState(Layers.Light, "light-charged");
break;
default:
sprite.LayerSetState(Layers.Light, "light-off");
break;
}
}
else
{
sprite.LayerSetState(Layers.Light, "light-off");
}
}
enum Layers : byte
{
Base,
Light,
}
}
}

View File

@@ -0,0 +1,39 @@
using Content.Shared.Power;
namespace Content.Client.PowerCell;
[RegisterComponent]
[Access(typeof(PowerChargerVisualizerSystem))]
public sealed class PowerChargerVisualsComponent : Component
{
/// <summary>
/// The base sprite state used if the power cell charger does not contain a power cell.
/// </summary>
[DataField("emptyState")]
[ViewVariables(VVAccess.ReadWrite)]
public string EmptyState = "empty";
/// <summary>
/// The base sprite state used if the power cell charger contains a power cell.
/// </summary>
[DataField("occupiedState")]
[ViewVariables(VVAccess.ReadWrite)]
public string OccupiedState = "full";
/// <summary>
/// A mapping of the indicator light overlays for the power cell charger.
/// <see cref="CellChargerStatus.Off"/> Maps to the state used when the charger is out of power/disabled.
/// <see cref="CellChargerStatus.Empty"/> Maps to the state used when the charger does not contain a power cell.
/// <see cref="CellChargerStatus.Charging"/> Maps to the state used when the charger is charging a power cell.
/// <see cref="CellChargerStatus.Charged"/> Maps to the state used when the charger contains a fully charged power cell.
/// </summary>
[DataField("lightStates")]
[ViewVariables(VVAccess.ReadWrite)]
public readonly Dictionary<CellChargerStatus, string> LightStates = new()
{
[CellChargerStatus.Off] = "light-off",
[CellChargerStatus.Empty] = "light-empty",
[CellChargerStatus.Charging] = "light-charging",
[CellChargerStatus.Charged] = "light-charged",
};
}

View File

@@ -0,0 +1,41 @@
using Content.Shared.Power;
using Robust.Client.GameObjects;
namespace Content.Client.PowerCell;
public sealed class PowerChargerVisualizerSystem : VisualizerSystem<PowerChargerVisualsComponent>
{
protected override void OnAppearanceChange(EntityUid uid, PowerChargerVisualsComponent comp, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
// Update base item
if (AppearanceSystem.TryGetData<bool>(uid, CellVisual.Occupied, out var occupied, args.Component) && occupied)
{
// TODO: don't throw if it doesn't have a full state
args.Sprite.LayerSetState(PowerChargerVisualLayers.Base, comp.OccupiedState);
}
else
{
args.Sprite.LayerSetState(PowerChargerVisualLayers.Base, comp.EmptyState);
}
// Update lighting
if (AppearanceSystem.TryGetData<CellChargerStatus>(uid, CellVisual.Light, out var status, args.Component)
&& comp.LightStates.TryGetValue(status, out var lightState))
{
args.Sprite.LayerSetState(PowerChargerVisualLayers.Light, lightState);
args.Sprite.LayerSetVisible(PowerChargerVisualLayers.Light, true);
}
else
//
args.Sprite.LayerSetVisible(PowerChargerVisualLayers.Light, false);
}
}
enum PowerChargerVisualLayers : byte
{
Base,
Light,
}

View File

@@ -1,49 +0,0 @@
using Content.Shared.Smoking;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Client.Smoking
{
[UsedImplicitly]
public sealed class BurnStateVisualizer : AppearanceVisualizer, ISerializationHooks
{
[Dependency] private readonly IEntityManager _entMan = default!;
[DataField("burntIcon")]
private string _burntIcon = "burnt-icon";
[DataField("litIcon")]
private string _litIcon = "lit-icon";
[DataField("unlitIcon")]
private string _unlitIcon = "icon";
void ISerializationHooks.AfterDeserialization()
{
IoCManager.InjectDependencies(this);
}
[Obsolete("Subscribe to AppearanceChangeEvent instead.")]
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (!_entMan.TryGetComponent(component.Owner, out SpriteComponent? sprite))
return;
if (!component.TryGetData<SmokableState>(SmokingVisuals.Smoking, out var burnState))
return;
var state = burnState switch
{
SmokableState.Lit => _litIcon,
SmokableState.Burnt => _burntIcon,
_ => _unlitIcon
};
sprite.LayerSetState(0, state);
}
}
}

View File

@@ -0,0 +1,25 @@
using Robust.Client.GameObjects;
using Content.Shared.Smoking;
namespace Content.Client.Smoking;
public sealed class BurnStateVisualizerSystem : VisualizerSystem<BurnStateVisualsComponent>
{
protected override void OnAppearanceChange(EntityUid uid, BurnStateVisualsComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
if (!args.AppearanceData.TryGetValue(SmokingVisuals.Smoking, out var burnState))
return;
var state = burnState switch
{
SmokableState.Lit => component.LitIcon,
SmokableState.Burnt => component.BurntIcon,
_ => component.UnlitIcon
};
args.Sprite.LayerSetState(0, state);
}
}

View File

@@ -0,0 +1,13 @@
namespace Content.Client.Smoking;
[RegisterComponent]
public sealed class BurnStateVisualsComponent : Component
{
[DataField("burntIcon")]
public string BurntIcon = "burnt-icon";
[DataField("litIcon")]
public string LitIcon = "lit-icon";
[DataField("unlitIcon")]
public string UnlitIcon = "icon";
}

View File

@@ -0,0 +1,8 @@
using Content.Shared.Speech.EntitySystems;
namespace Content.Client.Speech.EntitySystems;
public sealed class RatvarianLanguageSystem : SharedRatvarianLanguageSystem
{
}

View File

@@ -161,9 +161,15 @@ public sealed class ChatUIController : UIController
_input.SetInputCommand(ContentKeyFunctions.FocusLocalChat,
InputCmdHandler.FromDelegate(_ => FocusChannel(ChatSelectChannel.Local)));
_input.SetInputCommand(ContentKeyFunctions.FocusEmote,
InputCmdHandler.FromDelegate(_ => FocusChannel(ChatSelectChannel.Emotes)));
_input.SetInputCommand(ContentKeyFunctions.FocusWhisperChat,
InputCmdHandler.FromDelegate(_ => FocusChannel(ChatSelectChannel.Whisper)));
_input.SetInputCommand(ContentKeyFunctions.FocusLOOC,
InputCmdHandler.FromDelegate(_ => FocusChannel(ChatSelectChannel.LOOC)));
_input.SetInputCommand(ContentKeyFunctions.FocusOOC,
InputCmdHandler.FromDelegate(_ => FocusChannel(ChatSelectChannel.OOC)));

View File

@@ -40,7 +40,7 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
_window.OnClose += () =>
{
SendMessage(new GhostRoleWindowCloseMessage());
SendMessage(new CloseEuiMessage());
};
}

View File

@@ -70,7 +70,7 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
private void OnClose()
{
base.Closed();
SendMessage(new MakeGhostRoleWindowClosedMessage());
SendMessage(new CloseEuiMessage());
}
}
}

View File

@@ -33,8 +33,6 @@ public abstract partial class InteractionTest
protected const string Cap4 = "QuadraticCapacitorStockPart";
protected const string Manipulator1 = "MicroManipulatorStockPart";
protected const string Manipulator4 = "FemtoManipulatorStockPart";
protected const string Laser1 = "MicroLaserStockPart";
protected const string Laser2 = "QuadUltraMicroLaserStockPart";
protected const string Battery1 = "PowerCellSmall";
protected const string Battery4 = "PowerCellHyper";
}

View File

@@ -107,11 +107,12 @@ public sealed class MaterialArbitrageTest
var stackProto = protoManager.Index<StackPrototype>(materialStep.MaterialPrototypeId);
var spawnProto = protoManager.Index<EntityPrototype>(stackProto.Spawn);
if (!spawnProto.Components.TryGetValue(materialName, out var matreg))
if (!spawnProto.Components.ContainsKey(materialName) ||
!spawnProto.Components.TryGetValue(compositionName, out var compositionReg))
continue;
var mat = (MaterialComponent) matreg.Component;
foreach (var (matId, amount) in mat.Materials)
var mat = (PhysicalCompositionComponent) compositionReg.Component;
foreach (var (matId, amount) in mat.MaterialComposition)
{
materials[matId] = materialStep.Amount * amount + materials.GetValueOrDefault(matId);
}
@@ -156,11 +157,13 @@ public sealed class MaterialArbitrageTest
var spawnProto = protoManager.Index<EntityPrototype>(key);
// get the amount of each material included in the entity
if (!spawnProto.Components.TryGetValue(materialName, out var matreg))
continue;
var mat = (MaterialComponent) matreg.Component;
foreach (var (matId, amount) in mat.Materials)
if (!spawnProto.Components.ContainsKey(materialName) ||
!spawnProto.Components.TryGetValue(compositionName, out var compositionReg))
continue;
var mat = (PhysicalCompositionComponent) compositionReg.Component;
foreach (var (matId, amount) in mat.MaterialComposition)
{
spawnedMats[matId] = value.Max * amount + spawnedMats.GetValueOrDefault(matId);
}
@@ -235,11 +238,12 @@ public sealed class MaterialArbitrageTest
var spawnProto = protoManager.Index<EntityPrototype>(spawnCompletion.Prototype);
if (!spawnProto.Components.TryGetValue(materialName, out var matreg))
if (!spawnProto.Components.ContainsKey(materialName) ||
!spawnProto.Components.TryGetValue(compositionName, out var compositionReg))
continue;
var mat = (MaterialComponent) matreg.Component;
foreach (var (matId, amount) in mat.Materials)
var mat = (PhysicalCompositionComponent) compositionReg.Component;
foreach (var (matId, amount) in mat.MaterialComposition)
{
materials[matId] = spawnCompletion.Amount * amount + materials.GetValueOrDefault(matId);
}

View File

@@ -43,7 +43,7 @@ namespace Content.MapRenderer.Painters
{
var path = _sTileDefinitionManager[tile.Tile.TypeId].Sprite.ToString();
if (path == null)
if (string.IsNullOrWhiteSpace(path))
return;
var x = (int) (tile.X + xOffset);
@@ -72,7 +72,7 @@ namespace Content.MapRenderer.Painters
{
var path = definition.Sprite.ToString();
if (path == null)
if (string.IsNullOrWhiteSpace(path))
continue;
images[path] = new List<Image>(definition.Variants);

View File

@@ -460,6 +460,14 @@ namespace Content.Server.Database
/// Ban is a datacenter range, connections usually imply usage of a VPN service.
/// </summary>
Datacenter = 1 << 0,
/// <summary>
/// Ban only matches the IP.
/// </summary>
/// <remarks>
/// Intended use is for users with shared connections. This should not be used as an alternative to <see cref="Datacenter"/>.
/// </remarks>
IP = 1 << 1,
// @formatter:on
}

View File

@@ -98,7 +98,7 @@ public sealed class BanExemptionGetCommand : LocalizedCommands
{
var mask = (ServerBanExemptFlags) (1 << i);
if ((mask & flags) == 0)
break;
continue;
if (!first)
joined.Append(", ");

View File

@@ -92,6 +92,8 @@ public sealed class AdminLogsEui : BaseEui
public override async void HandleMessage(EuiMessageBase msg)
{
base.HandleMessage(msg);
if (!_adminManager.HasAdminFlag(Player, AdminFlags.Logs))
{
return;
@@ -99,11 +101,6 @@ public sealed class AdminLogsEui : BaseEui
switch (msg)
{
case Close _:
{
Close();
break;
}
case LogsRequest request:
{
_sawmill.Info($"Admin log request from admin with id {Player.UserId.UserId} and name {Player.Name}");

View File

@@ -58,11 +58,6 @@ public sealed class AdminNotesEui : BaseEui
switch (msg)
{
case Close _:
{
Close();
break;
}
case CreateNoteRequest {Message: var message}:
{
if (!_notesMan.CanCreate(Player))

View File

@@ -32,11 +32,10 @@ namespace Content.Server.Administration.UI
public override void HandleMessage(EuiMessageBase msg)
{
base.HandleMessage(msg);
switch (msg)
{
case AdminAnnounceEuiMsg.Close:
Close();
break;
case AdminAnnounceEuiMsg.DoAnnounce doAnnounce:
if (!_adminManager.HasAdminFlag(Player, AdminFlags.Admin))
{

View File

@@ -38,15 +38,5 @@ namespace Content.Server.Administration.UI
var solutions = _entityManager.GetComponentOrNull<SolutionContainerManagerComponent>(Target)?.Solutions;
return new EditSolutionsEuiState(Target, solutions);
}
public override void HandleMessage(EuiMessageBase msg)
{
switch (msg)
{
case EditSolutionsEuiMsg.Close:
Close();
break;
}
}
}
}

View File

@@ -86,14 +86,10 @@ namespace Content.Server.Administration.UI
public override async void HandleMessage(EuiMessageBase msg)
{
base.HandleMessage(msg);
switch (msg)
{
case Close _:
{
Close();
break;
}
case AddAdmin ca:
{
await HandleCreateAdmin(ca);

View File

@@ -14,11 +14,7 @@ public sealed class SpawnExplosionEui : BaseEui
{
public override void HandleMessage(EuiMessageBase msg)
{
if (msg is SpawnExplosionEuiMsg.Close)
{
Close();
return;
}
base.HandleMessage(msg);
if (msg is not SpawnExplosionEuiMsg.PreviewRequest request)
return;

View File

@@ -144,8 +144,7 @@ public sealed partial class AnomalySystem
msg.AddMarkup(stateLoc);
msg.PushNewline();
var points = GetAnomalyPointValue(anomaly, anomalyComp) / 10 * 10; //round to tens place
msg.AddMarkup(Loc.GetString("anomaly-scanner-point-output", ("point", points)));
msg.AddMarkup(Loc.GetString("anomaly-scanner-point-output", ("point", GetAnomalyPointValue(anomaly, anomalyComp))));
msg.PushNewline();
msg.PushNewline();

View File

@@ -32,7 +32,7 @@ public sealed class AnomalyVesselComponent : Component
/// The machine part that affects the point multiplier of the vessel
/// </summary>
[DataField("machinePartPointModifier", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
public string MachinePartPointModifier = "ScanningModule";
public string MachinePartPointModifier = "Capacitor";
/// <summary>
/// A value used to scale the point multiplier

View File

@@ -26,7 +26,7 @@ namespace Content.Server.Atmos.Piping.Binary.Components
public float BaseMinTemp = 300 + Atmospherics.T0C;
[DataField("machinePartMinTemp", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
public string MachinePartMinTemp = "Laser";
public string MachinePartMinTemp = "Capacitor";
[DataField("partRatingMinTempMultiplier")]
public float PartRatingMinTempMultiplier = 0.95f;

View File

@@ -91,7 +91,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
/// The machine part that affects the temperature range.
/// </summary>
[DataField("machinePartTemperature", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
public string MachinePartTemperature = "Laser";
public string MachinePartTemperature = "Capacitor";
}
}

View File

@@ -16,6 +16,6 @@ namespace Content.Server.Bed.Components
public float Multiplier = 10f;
[DataField("machinePartMetabolismModifier", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
public string MachinePartMetabolismModifier = "Manipulator";
public string MachinePartMetabolismModifier = "Capacitor";
}
}

View File

@@ -4,12 +4,16 @@ using Content.Server.Mind.Components;
using Content.Shared.Body.Components;
using Content.Shared.Body.Events;
using Content.Shared.Body.Organ;
using Content.Shared.Body.Part;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
namespace Content.Server.Body.Systems
{
public sealed class BrainSystem : EntitySystem
{
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
public override void Initialize()
{
base.Initialize();
@@ -42,7 +46,14 @@ namespace Content.Server.Body.Systems
Comp<GhostOnMoveComponent>(newEntity).MustBeDead = true;
// TODO: This is an awful solution.
EnsureComp<InputMoverComponent>(newEntity);
// Our greatest minds still can't figure out how to allow brains/heads to ghost without giving them the
// ability to move first. I hate this with a passion.
if (!HasComp<InputMoverComponent>(newEntity))
{
AddComp<InputMoverComponent>(newEntity);
var move = EnsureComp<MovementSpeedModifierComponent>(newEntity);
_movementSpeed.ChangeBaseSpeed(newEntity, 0, 0 , 0, move);
}
oldMind.Mind?.TransferTo(newEntity);
}

View File

@@ -11,12 +11,6 @@ public sealed partial class BotanySystem
if (!TryGetSeed(produce, out var seed))
return;
if (TryComp(uid, out SpriteComponent? sprite))
{
sprite.LayerSetRSI(0, seed.PlantRsi);
sprite.LayerSetState(0, seed.PlantIconState);
}
var solutionContainer = _solutionContainerSystem.EnsureSolution(uid, produce.SolutionName);
solutionContainer.RemoveAllSolution();

View File

@@ -99,13 +99,6 @@ public sealed partial class BotanySystem : EntitySystem
var seedComp = EnsureComp<SeedComponent>(seed);
seedComp.Seed = proto;
if (TryComp(seed, out SpriteComponent? sprite))
{
// TODO visualizer
// SeedPrototype state will always be seed. Blame the spriter if that's not the case!
sprite.LayerSetSprite(0, new SpriteSpecifier.Rsi(proto.PlantRsi, "seed"));
}
var name = Loc.GetString(proto.Name);
var noun = Loc.GetString(proto.Noun);
var val = Loc.GetString("botany-seed-packet-name", ("seedName", name), ("seedNoun", noun));

View File

@@ -121,10 +121,10 @@ public sealed class PricingSystem : EntitySystem
return price;
}
private double GetMaterialPrice(MaterialComponent component)
private double GetMaterialPrice(PhysicalCompositionComponent component)
{
double price = 0;
foreach (var (id, quantity) in component.Materials)
foreach (var (id, quantity) in component.MaterialComposition)
{
price += _prototypeManager.Index<MaterialPrototype>(id).Price * quantity;
}
@@ -213,9 +213,10 @@ public sealed class PricingSystem : EntitySystem
{
double price = 0;
if (TryComp<MaterialComponent>(uid, out var material))
if (HasComp<MaterialComponent>(uid) &&
TryComp<PhysicalCompositionComponent>(uid, out var composition))
{
var matPrice = GetMaterialPrice(material);
var matPrice = GetMaterialPrice(composition);
if (TryComp<StackComponent>(uid, out var stack))
matPrice *= stack.Count;
@@ -229,10 +230,11 @@ public sealed class PricingSystem : EntitySystem
{
double price = 0;
if (prototype.Components.TryGetValue(_factory.GetComponentName(typeof(MaterialComponent)), out var materials))
if (prototype.Components.ContainsKey(_factory.GetComponentName(typeof(MaterialComponent))) &&
prototype.Components.TryGetValue(_factory.GetComponentName(typeof(PhysicalCompositionComponent)), out var composition))
{
var materialsComp = (MaterialComponent) materials.Component;
var matPrice = GetMaterialPrice(materialsComp);
var compositionComp = (PhysicalCompositionComponent) composition.Component;
var matPrice = GetMaterialPrice(compositionComp);
if (prototype.Components.TryGetValue(_factory.GetComponentName(typeof(StackComponent)), out var stackProto))
{

View File

@@ -120,7 +120,8 @@ namespace Content.Server.Chat.Managers
public void SendAdminAlert(EntityUid player, string message, MindComponent? mindComponent = null)
{
if(mindComponent == null && !_entityManager.TryGetComponent(player, out mindComponent))
if((mindComponent == null && !_entityManager.TryGetComponent(player, out mindComponent))
|| mindComponent.Mind == null)
{
SendAdminAlert(message);
return;

View File

@@ -12,7 +12,7 @@ public sealed class SolutionHeaterComponent : Component
public float HeatMultiplier = 1;
[DataField("machinePartHeatPerSecond")]
public string MachinePartHeatPerSecond = "Laser";
public string MachinePartHeatPerSecond = "Capacitor";
[DataField("partRatingHeatMultiplier")]
public float PartRatingHeatMultiplier = 1.5f;

View File

@@ -34,6 +34,7 @@ namespace Content.Server.Chemistry.EntitySystems
[Dependency] private readonly StorageSystem _storageSystem = default!;
[Dependency] private readonly LabelSystem _labelSystem = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly AppearanceSystem _appearance = default!;
private const string PillPrototypeId = "Pill";
@@ -206,8 +207,9 @@ namespace Content.Server.Chemistry.EntitySystems
_solutionContainerSystem.TryAddSolution(
item, itemSolution, withdrawal.SplitSolution(message.Dosage));
if (TryComp<SpriteComponent>(item, out var spriteComp))
spriteComp.LayerSetState(0, "pill" + (chemMaster.PillType + 1));
var pill = EnsureComp<PillComponent>(item);
pill.PillType = chemMaster.PillType;
Dirty(pill);
if (user.HasValue)
{

View File

@@ -1,27 +1,28 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Eye.Blinding;
using Content.Shared.Eye.Blinding.Systems;
using JetBrains.Annotations;
namespace Content.Server.Chemistry.ReagentEffects
{
/// <summary>
/// Heal eye damage (or deal)
/// Heal or apply eye damage
/// </summary>
[UsedImplicitly]
public sealed class ChemHealEyeDamage : ReagentEffect
{
/// <summary>
/// How much eye damage to remove.
/// How much eye damage to add.
/// </summary>
[DataField("amount")]
public int Amount = -1;
public override void Effect(ReagentEffectArgs args)
{
if (args.Scale != 1f)
if (args.Scale != 1f) // huh?
return;
args.EntityManager.EntitySysManager.GetEntitySystem<SharedBlindingSystem>().AdjustEyeDamage(args.SolutionEntity, Amount);
args.EntityManager.EntitySysManager.GetEntitySystem<BlindableSystem>().AdjustEyeDamage(args.SolutionEntity, Amount);
}
}
}

View File

@@ -19,6 +19,7 @@ using Content.Server.Construction;
using Content.Server.Materials;
using Content.Server.Stack;
using Content.Server.Jobs;
using Content.Shared.Emag.Components;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Zombies;
@@ -32,6 +33,11 @@ using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.Physics.Components;
using Content.Shared.Humanoid;
using Content.Shared.Doors.Components;
using Content.Shared.Emag.Systems;
using Robust.Shared.Audio;
using System.Runtime.InteropServices;
using Content.Server.Popups;
namespace Content.Server.Cloning
{
@@ -52,8 +58,10 @@ namespace Content.Server.Cloning
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly PuddleSystem _puddleSystem = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly MaterialStorageSystem _material = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
public readonly Dictionary<Mind.Mind, EntityUid> ClonesWaitingForMind = new();
public const float EasyModeCloningCost = 0.7f;
@@ -70,6 +78,7 @@ namespace Content.Server.Cloning
SubscribeLocalEvent<CloningPodComponent, PortDisconnectedEvent>(OnPortDisconnected);
SubscribeLocalEvent<CloningPodComponent, AnchorStateChangedEvent>(OnAnchor);
SubscribeLocalEvent<CloningPodComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<CloningPodComponent, GotEmaggedEvent>(OnEmagged);
}
private void OnComponentInit(EntityUid uid, CloningPodComponent clonePod, ComponentInit args)
@@ -280,6 +289,19 @@ namespace Content.Server.Cloning
}
}
/// <summary>
/// On emag, spawns a failed clone when cloning process fails which attacks nearby crew.
/// </summary>
private void OnEmagged(EntityUid uid, CloningPodComponent clonePod, ref GotEmaggedEvent args)
{
if (!this.IsPowered(uid, EntityManager))
return;
_audio.PlayPvs(clonePod.SparkSound, uid);
_popupSystem.PopupEntity(Loc.GetString("cloning-pod-component-upgrade-emag-requirement"), uid);
args.Handled = true;
}
public void Eject(EntityUid uid, CloningPodComponent? clonePod)
{
if (!Resolve(uid, ref clonePod))
@@ -306,6 +328,12 @@ namespace Content.Server.Cloning
var tileMix = _atmosphereSystem.GetTileMixture(transform.GridUid, null, indices, true);
if (HasComp<EmaggedComponent>(uid))
{
_audio.PlayPvs(clonePod.ScreamSound, uid);
Spawn(clonePod.MobSpawnId, transform.Coordinates);
}
Solution bloodSolution = new();
int i = 0;
@@ -318,7 +346,10 @@ namespace Content.Server.Cloning
}
_puddleSystem.TrySpillAt(uid, bloodSolution, out _);
if (!HasComp<EmaggedComponent>(uid))
{
_material.SpawnMultipleFromMaterial(_robustRandom.Next(1, (int) (clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates);
}
clonePod.UsedBiomass = 0;
RemCompDeferred<ActiveCloningPodComponent>(uid);

View File

@@ -1,6 +1,7 @@
using Content.Shared.Cloning;
using Content.Shared.Construction.Prototypes;
using Content.Shared.Materials;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
@@ -49,7 +50,7 @@ namespace Content.Server.Cloning.Components
/// The machine part that affects cloning speed
/// </summary>
[DataField("machinePartCloningSpeed", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
public string MachinePartCloningSpeed = "ScanningModule";
public string MachinePartCloningSpeed = "Manipulator";
/// <summary>
/// The current amount of time it takes to clone a body
@@ -57,6 +58,27 @@ namespace Content.Server.Cloning.Components
[ViewVariables(VVAccess.ReadWrite)]
public float CloningTime = 30f;
/// <summary>
/// The mob to spawn on emag
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("mobSpawnId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string MobSpawnId = "MobAbomination";
/// <summary>
/// Emag sound effects.
/// </summary>
[DataField("sparkSound")]
public SoundSpecifier SparkSound = new SoundCollectionSpecifier("sparks")
{
Params = AudioParams.Default.WithVolume(8),
};
[DataField("screamSound")]
public SoundSpecifier ScreamSound = new SoundCollectionSpecifier("ZombieScreams")
{
Params = AudioParams.Default.WithVolume(4),
};
/// <summary>
/// The machine part that affects how much biomass is needed to clone a body.
/// </summary>
@@ -74,7 +96,7 @@ namespace Content.Server.Cloning.Components
/// The machine part that decreases the amount of material needed for cloning
/// </summary>
[DataField("machinePartMaterialUse", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
public string MachinePartMaterialUse = "Manipulator";
public string MachinePartMaterialUse = "MatterBin";
[ViewVariables(VVAccess.ReadWrite)]
public CloningPodStatus Status;

View File

@@ -1,30 +0,0 @@
using Content.Shared.Construction;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Utility;
namespace Content.Server.Construction.Completions
{
[UsedImplicitly]
[DataDefinition]
public sealed class SpriteChange : IGraphAction
{
[DataField("layer")] public int Layer { get; private set; } = 0;
[DataField("specifier")] public SpriteSpecifier? SpriteSpecifier { get; private set; } = SpriteSpecifier.Invalid;
public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager)
{
if (SpriteSpecifier == null || SpriteSpecifier == SpriteSpecifier.Invalid)
return;
if (!entityManager.TryGetComponent(uid, out SpriteComponent? sprite))
return;
// That layer doesn't exist, we do nothing.
if (sprite.LayerCount <= Layer)
return;
sprite.LayerSetSprite(Layer, SpriteSpecifier);
}
}
}

View File

@@ -33,18 +33,6 @@ public sealed class CrewManifestEui : BaseEui
return new(name, entries);
}
public override void HandleMessage(EuiMessageBase msg)
{
base.HandleMessage(msg);
switch (msg)
{
case CrewManifestEuiClosed:
Closed();
break;
}
}
public override void Closed()
{
base.Closed();

View File

@@ -119,7 +119,7 @@ namespace Content.Server.Database
query = query == null ? newQ : query.Union(newQ);
}
if (address != null)
if (address != null && !exemptFlags.GetValueOrDefault(ServerBanExemptFlags.None).HasFlag(ServerBanExemptFlags.IP))
{
var newQ = db.PgDbContext.Ban
.Include(p => p.Unban)

View File

@@ -74,7 +74,7 @@ namespace Content.Server.Database
// So just pull down the whole list into memory.
var bans = await GetAllBans(db.SqliteDbContext, includeUnbanned: false, exempt);
return bans.FirstOrDefault(b => BanMatches(b, address, userId, hwId)) is { } foundBan
return bans.FirstOrDefault(b => BanMatches(b, address, userId, hwId, exempt)) is { } foundBan
? ConvertBan(foundBan)
: null;
}
@@ -92,7 +92,7 @@ namespace Content.Server.Database
var queryBans = await GetAllBans(db.SqliteDbContext, includeUnbanned, exempt);
return queryBans
.Where(b => BanMatches(b, address, userId, hwId))
.Where(b => BanMatches(b, address, userId, hwId, exempt))
.Select(ConvertBan)
.ToList()!;
}
@@ -117,13 +117,14 @@ namespace Content.Server.Database
return await query.ToListAsync();
}
private static bool BanMatches(
ServerBan ban,
private static bool BanMatches(ServerBan ban,
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId)
ImmutableArray<byte>? hwId,
ServerBanExemptFlags? exemptFlags)
{
if (address != null && ban.Address is not null && IPAddressExt.IsInSubnet(address, ban.Address.Value))
if (!exemptFlags.GetValueOrDefault(ServerBanExemptFlags.None).HasFlag(ServerBanExemptFlags.IP)
&& address != null && ban.Address is not null && IPAddressExt.IsInSubnet(address, ban.Address.Value))
{
return true;
}

View File

@@ -0,0 +1,39 @@
using Content.Shared.Chat.Prototypes;
using Content.Shared.Disease;
using JetBrains.Annotations;
using Robust.Shared.Audio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Disease
{
/// <summary>
/// Makes the diseased honk.
/// or neither.
/// </summary>
[UsedImplicitly]
public sealed class DiseaseHonk : DiseaseEffect
{
/// <summary>
/// Message to play when honking.
/// </summary>
[DataField("honkMessage")]
public string HonkMessage = "disease-honk";
/// <summary>
/// Emote to play when honking.
/// </summary>
[DataField("emote", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EmotePrototype>))]
public string EmoteId = String.Empty;
/// <summary>
/// Whether to spread the disease through the air.
/// </summary>
[DataField("airTransmit")]
public bool AirTransmit = false;
public override void Effect(DiseaseEffectArgs args)
{
EntitySystem.Get<DiseaseSystem>().SneezeCough(args.DiseasedEntity, args.Disease, EmoteId, AirTransmit);
}
}
}

View File

@@ -1,6 +1,7 @@
using System.Linq;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Station.Components;
using Content.Shared.Dragon;
using Robust.Server.GameObjects;
using Robust.Shared.Map.Components;
@@ -30,13 +31,14 @@ public sealed partial class DragonSystem
{
base.Started(uid, component, gameRule, args);
var spawnLocations = EntityQuery<MapGridComponent, TransformComponent>().ToList();
if (spawnLocations.Count == 0)
if (!_station.Stations.Any())
return;
var location = _random.Pick(spawnLocations);
Spawn("MobDragon", location.Item2.MapPosition);
var station = _random.Pick(_station.Stations);
if (_station.GetLargestGrid(EntityManager.GetComponent<StationDataComponent>(station)) is not { } grid)
return;
Spawn("MobDragon", Transform(grid).MapPosition);
}
private void OnRiftRoundEnd(RoundEndTextAppendEvent args)

View File

@@ -17,6 +17,7 @@ using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Random;
using Content.Server.NPC.Systems;
using Content.Server.Station.Systems;
using Content.Shared.DoAfter;
using Content.Shared.Humanoid;
using Content.Shared.Mobs;
@@ -37,6 +38,7 @@ namespace Content.Server.Dragon
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly NPCSystem _npc = default!;
/// <summary>

View File

@@ -49,6 +49,8 @@ namespace Content.Server.EUI
/// </summary>
public virtual void HandleMessage(EuiMessageBase msg)
{
if (msg is CloseEuiMessage)
Close();
}
/// <summary>

View File

@@ -1,10 +1,12 @@
using System.Linq;
using Content.Server.Administration.Logs;
using Content.Server.Atmos.Components;
using Content.Server.Chat.Managers;
using Content.Server.Explosion.Components;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NPC.Pathfinding;
using Content.Shared.Camera;
using Content.Shared.CCVar;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.Explosion;
@@ -38,6 +40,7 @@ public sealed partial class ExplosionSystem : EntitySystem
[Dependency] private readonly PathfindingSystem _pathfindingSystem = default!;
[Dependency] private readonly SharedCameraRecoilSystem _recoilSystem = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly ThrowingSystem _throwingSystem = default!;
[Dependency] private readonly PVSOverrideSystem _pvsSys = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
@@ -227,11 +230,18 @@ public sealed partial class ExplosionSystem : EntitySystem
return;
if (user == null)
{
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(uid):entity} exploded ({typeId}) at {pos.Coordinates:coordinates} with intensity {totalIntensity} slope {slope}");
}
else
{
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(user.Value):user} caused {ToPrettyString(uid):entity} to explode ({typeId}) at {pos.Coordinates:coordinates} with intensity {totalIntensity} slope {slope}");
var alertMinExplosionIntensity = _cfg.GetCVar(CCVars.AdminAlertExplosionMinIntensity);
if (alertMinExplosionIntensity > -1 && totalIntensity >= alertMinExplosionIntensity)
_chat.SendAdminAlert(user.Value, $"caused {ToPrettyString(uid)} to explode ({typeId}:{totalIntensity}) at {pos.Coordinates:coordinates}");
}
}
/// <summary>

View File

@@ -1,6 +1,8 @@
using Content.Shared.Eye.Blinding;
using Content.Server.UserInterface;
using Content.Server.Popups;
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.Eye.Blinding.Systems;
using Robust.Shared.Player;
using Robust.Server.GameObjects;
@@ -23,14 +25,14 @@ public sealed class ActivatableUIRequiresVisionSystem : EntitySystem
if (args.Cancelled)
return;
if (TryComp<BlindableComponent>(args.User, out var blindable) && blindable.Sources > 0)
if (TryComp<BlindableComponent>(args.User, out var blindable) && blindable.IsBlind)
{
_popupSystem.PopupCursor(Loc.GetString("blindness-fail-attempt"), args.User, Shared.Popups.PopupType.MediumCaution);
args.Cancel();
}
}
private void OnBlindnessChanged(EntityUid uid, BlindableComponent component, BlindnessChangedEvent args)
private void OnBlindnessChanged(EntityUid uid, BlindableComponent component, ref BlindnessChangedEvent args)
{
if (!args.Blind)
return;

View File

@@ -1,10 +1,9 @@
using Content.Shared.Eye.Blinding.EyeProtection; // why aren't tools predicted 🙂
using Content.Shared.Eye.Blinding;
using Content.Shared.StatusEffect;
using Content.Shared.Clothing.Components;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Server.Tools;
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.Eye.Blinding.Systems;
using Content.Shared.Tools.Components;
namespace Content.Server.Eye.Blinding.EyeProtection
@@ -12,15 +11,26 @@ namespace Content.Server.Eye.Blinding.EyeProtection
public sealed class EyeProtectionSystem : EntitySystem
{
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
[Dependency] private readonly SharedBlindingSystem _blindingSystem = default!;
[Dependency] private readonly BlindableSystem _blindingSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RequiresEyeProtectionComponent, ToolUseAttemptEvent>(OnUseAttempt);
SubscribeLocalEvent<RequiresEyeProtectionComponent, WelderToggledEvent>(OnWelderToggled);
SubscribeLocalEvent<EyeProtectionComponent, GotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<EyeProtectionComponent, GotUnequippedEvent>(OnUnequipped);
SubscribeLocalEvent<EyeProtectionComponent, GetEyeProtectionEvent>(OnGetProtection);
SubscribeLocalEvent<EyeProtectionComponent, InventoryRelayedEvent<GetEyeProtectionEvent>>(OnGetRelayedProtection);
}
private void OnGetRelayedProtection(EntityUid uid, EyeProtectionComponent component,
InventoryRelayedEvent<GetEyeProtectionEvent> args)
{
OnGetProtection(uid, component, args.Args);
}
private void OnGetProtection(EntityUid uid, EyeProtectionComponent component, GetEyeProtectionEvent args)
{
args.Protection += component.ProtectionTime;
}
private void OnUseAttempt(EntityUid uid, RequiresEyeProtectionComponent component, ToolUseAttemptEvent args)
@@ -28,51 +38,26 @@ namespace Content.Server.Eye.Blinding.EyeProtection
if (!component.Toggled)
return;
if (!HasComp<StatusEffectsComponent>(args.User) || !TryComp<BlindableComponent>(args.User, out var blindable))
if (!TryComp<BlindableComponent>(args.User, out var blindable) || blindable.IsBlind)
return;
if (blindable.Sources > 0)
var ev = new GetEyeProtectionEvent();
RaiseLocalEvent(args.User, ev);
var time = (float) (component.StatusEffectTime- ev.Protection).TotalSeconds;
if (time <= 0)
return;
var statusTime = (float) component.StatusEffectTime.TotalSeconds - blindable.BlindResistance;
if (statusTime <= 0)
return;
var statusTimeSpan = TimeSpan.FromSeconds(statusTime * (blindable.EyeDamage + 1));
// Add permanent eye damage if they had zero protection, also scale their temporary blindness by how much they already accumulated.
if (_statusEffectsSystem.TryAddStatusEffect(args.User, SharedBlindingSystem.BlindingStatusEffect, statusTimeSpan, false, "TemporaryBlindness") && blindable.BlindResistance <= 0)
_blindingSystem.AdjustEyeDamage(args.User, 1, blindable);
// Add permanent eye damage if they had zero protection, also somewhat scale their temporary blindness by
// how much damage they already accumulated.
_blindingSystem.AdjustEyeDamage(args.User, 1, blindable);
var statusTimeSpan = TimeSpan.FromSeconds(time * MathF.Sqrt(blindable.EyeDamage));
_statusEffectsSystem.TryAddStatusEffect(args.User, TemporaryBlindnessSystem.BlindingStatusEffect,
statusTimeSpan, false, TemporaryBlindnessSystem.BlindingStatusEffect);
}
private void OnWelderToggled(EntityUid uid, RequiresEyeProtectionComponent component, WelderToggledEvent args)
{
component.Toggled = args.WelderOn;
}
private void OnEquipped(EntityUid uid, EyeProtectionComponent component, GotEquippedEvent args)
{
if (!TryComp<ClothingComponent>(uid, out var clothing) || clothing.Slots == SlotFlags.PREVENTEQUIP)
return;
if (!clothing.Slots.HasFlag(args.SlotFlags))
return;
component.IsActive = true;
if (!TryComp<BlindableComponent>(args.Equipee, out var blindComp))
return;
blindComp.BlindResistance += (float) component.ProtectionTime.TotalSeconds;
}
private void OnUnequipped(EntityUid uid, EyeProtectionComponent component, GotUnequippedEvent args)
{
if (!component.IsActive)
return;
component.IsActive = false;
if (!TryComp<BlindableComponent>(args.Equipee, out var blindComp))
return;
blindComp.BlindResistance -= (float) component.ProtectionTime.TotalSeconds;
}
}
}

View File

@@ -38,13 +38,10 @@ public sealed class AdminFaxEui : BaseEui
public override void HandleMessage(EuiMessageBase msg)
{
base.HandleMessage(msg);
switch (msg)
{
case AdminFaxEuiMsg.Close:
{
Close();
break;
}
case AdminFaxEuiMsg.Follow followData:
{
if (Player.AttachedEntity == null ||

View File

@@ -1,9 +1,10 @@
using System.Linq;
using Content.Server.Flash.Components;
using Content.Server.Light.EntitySystems;
using Content.Server.Popups;
using Content.Server.Stunnable;
using Content.Shared.Examine;
using Content.Shared.Eye.Blinding;
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.Flash;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
@@ -28,9 +29,11 @@ namespace Content.Server.Flash
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly StunSystem _stunSystem = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly AppearanceSystem _appearance = default!;
public override void Initialize()
{
@@ -50,7 +53,7 @@ namespace Content.Server.Flash
{
if (!args.IsHit ||
!args.HitEntities.Any() ||
!UseFlash(comp, args.User))
!UseFlash(uid, comp, args.User))
{
return;
}
@@ -64,46 +67,37 @@ namespace Content.Server.Flash
private void OnFlashUseInHand(EntityUid uid, FlashComponent comp, UseInHandEvent args)
{
if (args.Handled || !UseFlash(comp, args.User))
if (args.Handled || !UseFlash(uid, comp, args.User))
return;
args.Handled = true;
FlashArea(uid, args.User, comp.Range, comp.AoeFlashDuration, comp.SlowTo, true);
}
private bool UseFlash(FlashComponent comp, EntityUid user)
private bool UseFlash(EntityUid uid, FlashComponent comp, EntityUid user)
{
if (comp.HasUses)
if (!comp.HasUses || comp.Flashing)
return false;
comp.Uses--;
_audio.PlayPvs(comp.Sound, uid);
comp.Flashing = true;
_appearance.SetData(uid, FlashVisuals.Flashing, true);
if (comp.Uses == 0)
{
// TODO flash visualizer
if (!EntityManager.TryGetComponent<SpriteComponent?>(comp.Owner, out var sprite))
return false;
if (--comp.Uses == 0)
{
sprite.LayerSetState(0, "burnt");
_tagSystem.AddTag(comp.Owner, "Trash");
comp.Owner.PopupMessage(user, Loc.GetString("flash-component-becomes-empty"));
}
else if (!comp.Flashing)
{
int animLayer = sprite.AddLayerWithState("flashing");
comp.Flashing = true;
comp.Owner.SpawnTimer(400, () =>
{
sprite.RemoveLayer(animLayer);
comp.Flashing = false;
});
}
SoundSystem.Play(comp.Sound.GetSound(), Filter.Pvs(comp.Owner), comp.Owner, AudioParams.Default);
return true;
_appearance.SetData(uid, FlashVisuals.Burnt, true);
_tagSystem.AddTag(uid, "Trash");
_popup.PopupEntity(Loc.GetString("flash-component-becomes-empty"), user);
}
return false;
uid.SpawnTimer(400, () =>
{
_appearance.SetData(uid, FlashVisuals.Flashing, false);
comp.Flashing = false;
});
return true;
}
public void Flash(EntityUid target, EntityUid? user, EntityUid? used, float flashDuration, float slowTo, bool displayPopup = true, FlashableComponent? flashable = null)

View File

@@ -105,7 +105,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
continue;
}
var remaining = neighborSolution.Volume - puddle.OverflowVolume;
var remaining = puddle.OverflowVolume - neighborSolution.Volume;
if (remaining <= FixedPoint2.Zero)
continue;
@@ -129,20 +129,13 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
}
}
// Then we go to free tiles -> only overflow if we can go up to capacity at least.
// Then we go to free tiles.
// Need to go even if we have a little remainder to avoid solution sploshing around internally
// for ages.
if (args.NeighborFreeTiles.Count > 0 && args.Updates > 0)
{
// We'll only spill if we have the minimum threshold per tile.
var spillCount = (int) Math.Floor(overflow.Volume.Float() / component.OverflowVolume.Float());
if (spillCount == 0)
{
return;
}
_random.Shuffle(args.NeighborFreeTiles);
spillCount = Math.Min(args.NeighborFreeTiles.Count, spillCount);
var spillAmount = overflow.Volume / spillCount;
var spillAmount = overflow.Volume / args.NeighborFreeTiles.Count;
foreach (var neighbor in args.NeighborFreeTiles)
{
@@ -165,12 +158,10 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
foreach (var neighbor in args.Neighbors)
{
// Overflow to neighbours but not if they're already at the cap
// This is to avoid diluting solutions too much.
// Overflow to neighbours (unless it's pure water)
if (!puddleQuery.TryGetComponent(neighbor, out var puddle) ||
!_solutionContainerSystem.TryGetSolution(neighbor, puddle.SolutionName, out var neighborSolution) ||
CanFullyEvaporate(neighborSolution) ||
neighborSolution.Volume >= puddle.OverflowVolume)
CanFullyEvaporate(neighborSolution))
{
continue;
}

View File

@@ -173,6 +173,11 @@ namespace Content.Server.GameTicking
if (!_userDb.IsLoadComplete(player))
return;
if (RunLevel != GameRunLevel.PreRoundLobby)
{
return;
}
var status = ready ? PlayerGameStatus.ReadyToPlay : PlayerGameStatus.NotReadyToPlay;
_playerGameStatuses[player.UserId] = ready ? PlayerGameStatus.ReadyToPlay : PlayerGameStatus.NotReadyToPlay;
RaiseNetworkEvent(GetStatusMsg(player), player.ConnectedClient);

View File

@@ -4,7 +4,7 @@ namespace Content.Server.GameTicking.Rules;
public abstract class GameRuleSystem<T> : EntitySystem where T : Component
{
[Dependency] protected GameTicker GameTicker = default!;
[Dependency] protected readonly GameTicker GameTicker = default!;
public override void Initialize()
{

View File

@@ -210,8 +210,9 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
if (traitorRule == null)
{
//todo fuck me this shit is awful
GameTicker.StartGameRule("traitor", out var ruleEntity);
traitorRule = EntityManager.GetComponent<TraitorRuleComponent>(ruleEntity);
//no i wont fuck you, erp is against rules
GameTicker.StartGameRule("Traitor", out var ruleEntity);
traitorRule = Comp<TraitorRuleComponent>(ruleEntity);
}
var mind = traitor.Data.ContentData()?.Mind;

View File

@@ -23,9 +23,6 @@ namespace Content.Server.Ghost.Roles.UI
case GhostRoleFollowRequestMessage req:
EntitySystem.Get<GhostRoleSystem>().Follow(Player, req.Identifier);
break;
case GhostRoleWindowCloseMessage _:
Closed();
break;
}
}

View File

@@ -18,18 +18,6 @@ namespace Content.Server.Ghost.Roles.UI
return new MakeGhostRoleEuiState(EntityUid);
}
public override void HandleMessage(EuiMessageBase msg)
{
base.HandleMessage(msg);
switch (msg)
{
case MakeGhostRoleWindowClosedMessage _:
Closed();
break;
}
}
public override void Closed()
{
base.Closed();

View File

@@ -1,9 +1,13 @@
using Content.Server.Cuffs;
using Content.Server.Store.Components;
using Content.Server.Store.Systems;
using Content.Shared.Cuffs.Components;
using Content.Shared.Implants;
using Content.Shared.Implants.Components;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Mobs;
using Content.Shared.Popups;
using Robust.Shared.Containers;
namespace Content.Server.Implants;
@@ -12,6 +16,8 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem
{
[Dependency] private readonly CuffableSystem _cuffable = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly StoreSystem _store = default!;
public override void Initialize()
{
@@ -19,7 +25,10 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem
SubscribeLocalEvent<SubdermalImplantComponent, UseFreedomImplantEvent>(OnFreedomImplant);
SubscribeLocalEvent<StoreComponent, AfterInteractUsingEvent>(OnUplinkInteractUsing);
SubscribeLocalEvent<ImplantedComponent, MobStateChangedEvent>(RelayToImplantEvent);
SubscribeLocalEvent<ImplantedComponent, AfterInteractUsingEvent>(RelayToImplantEvent);
SubscribeLocalEvent<ImplantedComponent, SuicideEvent>(RelayToImplantEvent);
}
@@ -32,6 +41,26 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem
args.Handled = true;
}
private void OnUplinkInteractUsing(EntityUid uid, StoreComponent store, AfterInteractUsingEvent args)
{
// can only insert into yourself to prevent uplink checking with renault
if (args.Target != args.User)
return;
if (!TryComp<CurrencyComponent>(args.Used, out var currency))
return;
// same as store code, but message is only shown to yourself
args.Handled = _store.TryAddCurrency(_store.GetCurrencyValue(args.Used, currency), uid, store);
if (!args.Handled)
return;
var msg = Loc.GetString("store-currency-inserted-implant", ("used", args.Used));
_popup.PopupEntity(msg, args.User, args.User);
QueueDel(args.Used);
}
#region Relays
//Relays from the implanted to the implant

View File

@@ -12,7 +12,7 @@ namespace Content.Server.Kitchen.Components
[DataField("cookTimeMultiplier"), ViewVariables(VVAccess.ReadWrite)]
public float CookTimeMultiplier = 1;
[DataField("machinePartCookTimeMultiplier", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
public string MachinePartCookTimeMultiplier = "Laser";
public string MachinePartCookTimeMultiplier = "Capacitor";
[DataField("cookTimeScalingConstant")]
public float CookTimeScalingConstant = 0.5f;

View File

@@ -40,15 +40,20 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
}
}
public override bool TryInsertMaterialEntity(EntityUid user, EntityUid toInsert, EntityUid receiver, MaterialStorageComponent? component = null)
public override bool TryInsertMaterialEntity(EntityUid user,
EntityUid toInsert,
EntityUid receiver,
MaterialStorageComponent? storage = null,
MaterialComponent? material = null,
PhysicalCompositionComponent? composition = null)
{
if (!Resolve(receiver, ref component))
if (!Resolve(receiver, ref storage) || !Resolve(toInsert, ref material, ref composition, false))
return false;
if (TryComp<ApcPowerReceiverComponent>(receiver, out var power) && !power.Powered)
return false;
if (!base.TryInsertMaterialEntity(user, toInsert, receiver, component))
if (!base.TryInsertMaterialEntity(user, toInsert, receiver, storage, material, composition))
return false;
_audio.PlayPvs(component.InsertingSound, receiver);
_audio.PlayPvs(storage.InsertingSound, receiver);
_popup.PopupEntity(Loc.GetString("machine-insert-item", ("user", user), ("machine", receiver),
("item", toInsert)), receiver);
QueueDel(toInsert);
@@ -116,10 +121,10 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
return new List<EntityUid>();
var entProto = _prototypeManager.Index<EntityPrototype>(materialProto.StackEntity);
if (!entProto.TryGetComponent<MaterialComponent>(out var material))
if (!entProto.TryGetComponent<PhysicalCompositionComponent>(out var composition))
return new List<EntityUid>();
var materialPerStack = material.Materials[materialProto.ID];
var materialPerStack = composition.MaterialComposition[materialProto.ID];
var amountToSpawn = amount / materialPerStack;
overflowMaterial = amount - amountToSpawn * materialPerStack;
return _stackSystem.SpawnMultiple(materialProto.StackEntity, amountToSpawn, coordinates);

View File

@@ -63,7 +63,7 @@ namespace Content.Server.Medical.BiomassReclaimer
/// Machine part whose rating modifies the yield per mass.
/// </summary>
[DataField("machinePartYieldAmount", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
public string MachinePartYieldAmount = "Manipulator";
public string MachinePartYieldAmount = "MatterBin";
/// <summary>
/// How much the machine part quality affects the yield.
@@ -89,7 +89,7 @@ namespace Content.Server.Medical.BiomassReclaimer
/// The machine part that increses the processing speed.
/// </summary>
[DataField("machinePartProcessSpeed", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
public string MachinePartProcessingSpeed = "Laser";
public string MachinePartProcessingSpeed = "Manipulator";
/// <summary>
/// How much the machine part quality affects the yield.

View File

@@ -17,7 +17,7 @@ namespace Content.Server.Medical.Components
public float CloningFailChanceMultiplier = 1f;
[DataField("machinePartCloningFailChance", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
public string MachinePartCloningFailChance = "ScanningModule";
public string MachinePartCloningFailChance = "Capacitor";
[DataField("partRatingCloningFailChanceMultiplier")]
public float PartRatingFailMultiplier = 0.75f;

View File

@@ -151,7 +151,10 @@ public sealed class HealingSystem : EntitySystem
if (!TryComp<StackComponent>(uid, out var stack) || stack.Count < 1)
return false;
if (!HasDamage(targetDamage, component))
if (!TryComp<BloodstreamComponent>(target, out var bloodstream))
return false;
if (!HasDamage(targetDamage, component) && !(bloodstream.BloodSolution.Volume < bloodstream.BloodSolution.MaxVolume && component.ModifyBloodLevel > 0))
{
_popupSystem.PopupEntity(Loc.GetString("medical-item-cant-use", ("item", uid)), uid);
return false;

View File

@@ -289,12 +289,12 @@ namespace Content.Server.Mind
}
/// <summary>
/// Removes an objective to this mind.
/// Removes an objective from this mind.
/// </summary>
/// <returns>Returns true if the removal succeeded.</returns>
public bool TryRemoveObjective(int index)
{
if (_objectives.Count >= index) return false;
if (index < 0 || index >= _objectives.Count) return false;
var objective = _objectives[index];

View File

@@ -223,11 +223,10 @@ public sealed class HTNSystem : EntitySystem
{
text.AppendLine($"BTR: {string.Join(", ", comp.Plan.BranchTraversalRecord)}");
text.AppendLine($"tasks:");
foreach (var task in comp.Plan.Tasks)
{
text.AppendLine($"- {task.ID}");
}
var root = _prototypeManager.Index<HTNCompoundTask>(comp.RootTask);
var btr = new List<int>();
var level = -1;
AppendDebugText(root, text, comp.Plan.BranchTraversalRecord, btr, ref level);
}
RaiseNetworkEvent(new HTNMessage()
@@ -247,6 +246,49 @@ public sealed class HTNSystem : EntitySystem
}
}
private void AppendDebugText(HTNTask task, StringBuilder text, List<int> planBtr, List<int> btr, ref int level)
{
// If it's the selected BTR then highlight.
for (var i = 0; i < btr.Count; i++)
{
text.Append('-');
}
text.Append(' ');
if (task is HTNPrimitiveTask primitive)
{
text.AppendLine(primitive.ID);
return;
}
if (task is HTNCompoundTask compound)
{
level++;
text.AppendLine(compound.ID);
var branches = _compoundBranches[compound];
for (var i = 0; i < branches.Length; i++)
{
var branch = branches[i];
btr.Add(i);
text.AppendLine($" branch {string.Join(" ", btr)}:");
foreach (var sub in branch)
{
AppendDebugText(sub, text, planBtr, btr, ref level);
}
btr.RemoveAt(btr.Count - 1);
}
level--;
return;
}
throw new NotImplementedException();
}
private void Update(HTNComponent component, float frameTime)
{
// If we're not planning then countdown to next one.

View File

@@ -37,7 +37,7 @@ public sealed class FatExtractorComponent : Component
/// Which machine part affects the nutrition rate
/// </summary>
[DataField("machinePartNutritionRate", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
public string MachinePartNutritionRate = "Laser";
public string MachinePartNutritionRate = "Manipulator";
/// <summary>
/// The increase in rate per each rating above 1.

View File

@@ -42,7 +42,7 @@ namespace Content.Server.Objectives.Commands
}
for (var i = 0; i < objectives.Count; i++)
{
shell.WriteLine($"- [{i + 1}] {objectives[i].Conditions[0].Title}");
shell.WriteLine($"- [{i}] {objectives[i].Conditions[0].Title}");
}
}

View File

@@ -310,22 +310,14 @@ namespace Content.Server.Physics.Controllers
angularInput /= count;
brakeInput /= count;
/*
* So essentially:
* 1. We do the same calcs for braking as we do for linear thrust so it's similar to a player pressing it
* but we also need to handle when they get close to 0 hence why it sets velocity directly.
*
* 2. We do a similar calculation to mob movement where the closer you are to your speed cap the slower you accelerate
*
* TODO: Could combine braking linear input and thrust more but my brain was just not working debugging
* TODO: Need to have variable speed caps based on thruster count or whatever
*/
// Handle shuttle movement
if (brakeInput > 0f)
{
if (body.LinearVelocity.Length > 0f)
{
// Minimum brake velocity for a direction to show its thrust appearance.
var appearanceThreshold = 0.1f;
// Get velocity relative to the shuttle so we know which thrusters to fire
var shuttleVelocity = (-shuttleNorthAngle).RotateVec(body.LinearVelocity);
var force = Vector2.Zero;
@@ -333,7 +325,9 @@ namespace Content.Server.Physics.Controllers
if (shuttleVelocity.X < 0f)
{
_thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.West);
_thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.East);
if (shuttleVelocity.X < -appearanceThreshold)
_thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.East);
var index = (int) Math.Log2((int) DirectionFlag.East);
force.X += shuttle.LinearThrust[index];
@@ -341,7 +335,9 @@ namespace Content.Server.Physics.Controllers
else if (shuttleVelocity.X > 0f)
{
_thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.East);
_thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.West);
if (shuttleVelocity.X > appearanceThreshold)
_thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.West);
var index = (int) Math.Log2((int) DirectionFlag.West);
force.X -= shuttle.LinearThrust[index];
@@ -350,7 +346,9 @@ namespace Content.Server.Physics.Controllers
if (shuttleVelocity.Y < 0f)
{
_thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.South);
_thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.North);
if (shuttleVelocity.Y < -appearanceThreshold)
_thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.North);
var index = (int) Math.Log2((int) DirectionFlag.North);
force.Y += shuttle.LinearThrust[index];
@@ -358,47 +356,24 @@ namespace Content.Server.Physics.Controllers
else if (shuttleVelocity.Y > 0f)
{
_thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.North);
_thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.South);
if (shuttleVelocity.Y > appearanceThreshold)
_thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.South);
var index = (int) Math.Log2((int) DirectionFlag.South);
force.Y -= shuttle.LinearThrust[index];
}
var impulse = force * brakeInput;
var wishDir = impulse.Normalized;
// TODO: Adjust max possible speed based on total thrust in particular direction.
var wishSpeed = 20f;
var impulse = force * brakeInput * ShuttleComponent.BrakeCoefficient;
var maxImpulse = shuttleVelocity * body.Mass;
var currentSpeed = Vector2.Dot(shuttleVelocity, wishDir);
var addSpeed = wishSpeed - currentSpeed;
if (addSpeed > 0f)
if ((impulse * frameTime).LengthSquared > maxImpulse.LengthSquared)
{
var accelSpeed = impulse.Length * frameTime;
accelSpeed = MathF.Min(accelSpeed, addSpeed);
impulse = impulse.Normalized * accelSpeed * body.InvMass;
// Cap inputs
if (shuttleVelocity.X < 0f)
{
impulse.X = MathF.Min(impulse.X, -shuttleVelocity.X);
}
else if (shuttleVelocity.X > 0f)
{
impulse.X = MathF.Max(impulse.X, -shuttleVelocity.X);
}
if (shuttleVelocity.Y < 0f)
{
impulse.Y = MathF.Min(impulse.Y, -shuttleVelocity.Y);
}
else if (shuttleVelocity.Y > 0f)
{
impulse.Y = MathF.Max(impulse.Y, -shuttleVelocity.Y);
}
PhysicsSystem.SetLinearVelocity(shuttle.Owner, body.LinearVelocity + shuttleNorthAngle.RotateVec(impulse), body: body);
impulse = -maxImpulse;
}
PhysicsSystem.ApplyForce(shuttle.Owner, shuttleNorthAngle.RotateVec(impulse), body: body);
}
else
{
@@ -407,32 +382,20 @@ namespace Content.Server.Physics.Controllers
if (body.AngularVelocity != 0f)
{
var impulse = shuttle.AngularThrust * brakeInput * (body.AngularVelocity > 0f ? -1f : 1f);
var wishSpeed = MathF.PI;
var impulse = shuttle.AngularThrust * brakeInput * (body.AngularVelocity > 0f ? -1f : 1f) * ShuttleComponent.BrakeCoefficient;
var maxImpulse = body.AngularVelocity * body.Inertia;
if (impulse < 0f)
wishSpeed *= -1f;
var currentSpeed = body.AngularVelocity;
var addSpeed = wishSpeed - currentSpeed;
if (!addSpeed.Equals(0f))
if (Math.Abs(impulse * frameTime) > Math.Abs(maxImpulse))
{
var accelSpeed = impulse * body.InvI * frameTime;
if (accelSpeed < 0f)
accelSpeed = MathF.Max(accelSpeed, addSpeed);
else
accelSpeed = MathF.Min(accelSpeed, addSpeed);
if (body.AngularVelocity < 0f && body.AngularVelocity + accelSpeed > 0f)
accelSpeed = -body.AngularVelocity;
else if (body.AngularVelocity > 0f && body.AngularVelocity + accelSpeed < 0f)
accelSpeed = -body.AngularVelocity;
PhysicsSystem.SetAngularVelocity(shuttle.Owner, body.AngularVelocity + accelSpeed, body: body);
_thruster.SetAngularThrust(shuttle, true);
impulse = -maxImpulse;
}
PhysicsSystem.ApplyTorque(shuttle.Owner, impulse, body: body);
_thruster.SetAngularThrust(shuttle, true);
}
else
{
_thruster.SetAngularThrust(shuttle, false);
}
}
@@ -442,11 +405,6 @@ namespace Content.Server.Physics.Controllers
if (brakeInput.Equals(0f))
_thruster.DisableLinearThrusters(shuttle);
if (body.LinearVelocity.Length < 0.08)
{
PhysicsSystem.SetLinearVelocity(shuttle.Owner, Vector2.Zero, body: body);
}
}
else
{
@@ -454,8 +412,7 @@ namespace Content.Server.Physics.Controllers
var angle = linearInput.ToWorldAngle();
var linearDir = angle.GetDir();
var dockFlag = linearDir.AsFlag();
var totalForce = new Vector2();
var totalForce = Vector2.Zero;
// Won't just do cardinal directions.
foreach (DirectionFlag dir in Enum.GetValues(typeof(DirectionFlag)))
@@ -478,83 +435,62 @@ namespace Content.Server.Physics.Controllers
continue;
}
var force = Vector2.Zero;
var index = (int) Math.Log2((int) dir);
var thrust = shuttle.LinearThrust[index];
switch (dir)
{
case DirectionFlag.North:
totalForce.Y += thrust;
force.Y += thrust;
break;
case DirectionFlag.South:
totalForce.Y -= thrust;
force.Y -= thrust;
break;
case DirectionFlag.East:
totalForce.X += thrust;
force.X += thrust;
break;
case DirectionFlag.West:
totalForce.X -= thrust;
force.X -= thrust;
break;
default:
throw new ArgumentOutOfRangeException();
}
_thruster.EnableLinearThrustDirection(shuttle, dir);
var impulse = force * linearInput.Length;
totalForce += impulse;
}
// We don't want to touch damping if no inputs are given
// so we'll just add an artifical drag to the velocity input.
var shuttleVelocity = (-shuttleNorthAngle).RotateVec(body.LinearVelocity);
totalForce = shuttleNorthAngle.RotateVec(totalForce);
var wishDir = totalForce.Normalized;
// TODO: Adjust max possible speed based on total thrust in particular direction.
var wishSpeed = 20f;
var currentSpeed = Vector2.Dot(shuttleVelocity, wishDir);
var addSpeed = wishSpeed - currentSpeed;
if (addSpeed > 0f)
if ((body.LinearVelocity + totalForce / body.Mass * frameTime).Length <= ShuttleComponent.MaxLinearVelocity)
{
var accelSpeed = totalForce.Length * frameTime;
accelSpeed = MathF.Min(accelSpeed, addSpeed);
PhysicsSystem.ApplyLinearImpulse(shuttle.Owner, shuttleNorthAngle.RotateVec(totalForce.Normalized * accelSpeed), body: body);
PhysicsSystem.ApplyForce(shuttle.Owner, totalForce, body: body);
}
}
if (MathHelper.CloseTo(angularInput, 0f))
{
_thruster.SetAngularThrust(shuttle, false);
PhysicsSystem.SetSleepingAllowed(shuttle.Owner, body, true);
if (Math.Abs(body.AngularVelocity) < 0.01f)
{
PhysicsSystem.SetAngularVelocity(shuttle.Owner, 0f, body: body);
}
if (brakeInput <= 0f)
_thruster.SetAngularThrust(shuttle, false);
}
else
{
PhysicsSystem.SetSleepingAllowed(shuttle.Owner, body, false);
var impulse = shuttle.AngularThrust * -angularInput;
var wishSpeed = MathF.PI;
var tickChange = impulse * frameTime * body.InvI;
if (impulse < 0f)
wishSpeed *= -1f;
var currentSpeed = body.AngularVelocity;
var addSpeed = wishSpeed - currentSpeed;
if (!addSpeed.Equals(0f))
// If the rotation brings it above speedcap then noop.
if (Math.Sign(body.AngularVelocity) != Math.Sign(tickChange) ||
Math.Abs(body.AngularVelocity + tickChange) <= ShuttleComponent.MaxAngularVelocity)
{
var accelSpeed = impulse * body.InvI * frameTime;
if (accelSpeed < 0f)
accelSpeed = MathF.Max(accelSpeed, addSpeed);
else
accelSpeed = MathF.Min(accelSpeed, addSpeed);
PhysicsSystem.SetAngularVelocity(shuttle.Owner, body.AngularVelocity + accelSpeed, body: body);
_thruster.SetAngularThrust(shuttle, true);
PhysicsSystem.ApplyTorque(shuttle.Owner, impulse, body: body);
}
_thruster.SetAngularThrust(shuttle, true);
}
}
}

View File

@@ -6,6 +6,18 @@ namespace Content.Server.Shuttles.Components
[ViewVariables]
public bool Enabled = true;
[ViewVariables]
public Vector2[] CenterOfThrust = new Vector2[4];
/// <summary>
/// Thrust gets multiplied by this value if it's for braking.
/// </summary>
public const float BrakeCoefficient = 1.5f;
public const float MaxLinearVelocity = 10f;
public const float MaxAngularVelocity = 1f;
/// <summary>
/// The cached thrust available for each cardinal direction
/// </summary>

View File

@@ -46,10 +46,10 @@ namespace Content.Server.Shuttles.Components
// Need to serialize this because RefreshParts isn't called on Init and this will break post-mapinit maps!
[ViewVariables(VVAccess.ReadWrite), DataField("thrust")]
public float Thrust;
public float Thrust = 100f;
[DataField("baseThrust"), ViewVariables(VVAccess.ReadWrite)]
public float BaseThrust = 750f;
public float BaseThrust = 100f;
[DataField("thrusterType")]
public ThrusterType Type = ThrusterType.Linear;
@@ -83,7 +83,7 @@ namespace Content.Server.Shuttles.Components
public TimeSpan NextFire;
[DataField("machinePartThrust", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
public string MachinePartThrust = "Laser";
public string MachinePartThrust = "Capacitor";
[DataField("partRatingThrustMultiplier")]
public float PartRatingThrustMultiplier = 1.5f;

View File

@@ -13,6 +13,7 @@ using Content.Shared.Shuttles.Components;
using Content.Shared.Temperature;
using Robust.Server.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
@@ -295,6 +296,38 @@ public sealed class ThrusterSystem : EntitySystem
}
_ambient.SetAmbience(uid, true);
RefreshCenter(uid, shuttleComponent);
}
/// <summary>
/// Refreshes the center of thrust for movement calculations.
/// </summary>
private void RefreshCenter(EntityUid uid, ShuttleComponent shuttle)
{
// TODO: Only refresh relevant directions.
var center = Vector2.Zero;
var thrustQuery = GetEntityQuery<ThrusterComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
foreach (var dir in new[]
{ Direction.South, Direction.East, Direction.North, Direction.West })
{
var index = (int) dir / 2;
var pop = shuttle.LinearThrusters[index];
var totalThrust = 0f;
foreach (var ent in pop)
{
if (!thrustQuery.TryGetComponent(ent, out var thruster) || !xformQuery.TryGetComponent(ent, out var xform))
continue;
center += xform.LocalPosition * thruster.Thrust;
totalThrust += thruster.Thrust;
}
center /= pop.Count * totalThrust;
shuttle.CenterOfThrust[index] = center;
}
}
public void DisableThruster(EntityUid uid, ThrusterComponent component, TransformComponent? xform = null, Angle? angle = null)
@@ -358,6 +391,7 @@ public sealed class ThrusterSystem : EntitySystem
}
component.Colliding.Clear();
RefreshCenter(uid, shuttleComponent);
}
public bool CanEnable(EntityUid uid, ThrusterComponent component)

View File

@@ -179,11 +179,8 @@ namespace Content.Server.Singularity.EntitySystems
private void OnRefreshParts(EntityUid uid, EmitterComponent component, RefreshPartsEvent args)
{
var powerUseRating = args.PartRatings[component.MachinePartPowerUse];
var fireRateRating = args.PartRatings[component.MachinePartFireRate];
component.PowerUseActive = (int) (component.BasePowerUseActive * MathF.Pow(component.PowerUseMultiplier, powerUseRating - 1));
component.FireInterval = component.BaseFireInterval * MathF.Pow(component.FireRateMultiplier, fireRateRating - 1);
component.FireBurstDelayMin = component.BaseFireBurstDelayMin * MathF.Pow(component.FireRateMultiplier, fireRateRating - 1);
component.FireBurstDelayMax = component.BaseFireBurstDelayMax * MathF.Pow(component.FireRateMultiplier, fireRateRating - 1);
@@ -192,8 +189,6 @@ namespace Content.Server.Singularity.EntitySystems
private void OnUpgradeExamine(EntityUid uid, EmitterComponent component, UpgradeExamineEvent args)
{
args.AddPercentageUpgrade("emitter-component-upgrade-fire-rate", (float) (component.BaseFireInterval.TotalSeconds / component.FireInterval.TotalSeconds));
// TODO: Remove this and use UpgradePowerDrawComponent instead.
args.AddPercentageUpgrade("upgrade-power-draw", component.PowerUseActive / (float) component.BasePowerUseActive);
}
public void SwitchOff(EmitterComponent component)

View File

@@ -0,0 +1,115 @@
using System.Text;
using System.Text.RegularExpressions;
using Content.Shared.Speech.Components;
using Content.Shared.Speech.EntitySystems;
using Content.Shared.StatusEffect;
namespace Content.Server.Speech.EntitySystems;
public sealed class RatvarianLanguageSystem : SharedRatvarianLanguageSystem
{
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
private const string RatvarianKey = "RatvarianLanguage";
// This is the word of Ratvar and those who speak it shall abide by His rules:
/*
* Any time the word "of" occurs, it's linked to the previous word by a hyphen: "I am-of Ratvar"
* Any time "th", followed by any two letters occurs, you add a grave (`) between those two letters: "Thi`s"
* In the same vein, any time "ti" followed by one letter occurs, you add a grave (`) between "i" and the letter: "Ti`me"
* Wherever "te" or "et" appear and there is another letter next to the "e", add a hyphen between "e" and the letter: "M-etal/Greate-r"
* Where "gua" appears, add a hyphen between "gu" and "a": "Gu-ard"
* Where the word "and" appears it's linked to all surrounding words by hyphens: "Sword-and-shield"
* Where the word "to" appears, it's linked to the following word by a hyphen: "to-use"
* Where the word "my" appears, it's linked to the following word by a hyphen: "my-light"
* Any Ratvarian proper noun is not translated: Ratvar, Nezbere, Sevtug, Nzcrentr and Inath-neq
* This only applies if they're being used as a proper noun: armorer/Nezbere
*/
private static Regex THPattern = new Regex(@"th\w\B", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static Regex ETPattern = new Regex(@"\Bet", RegexOptions.Compiled);
private static Regex TEPattern = new Regex(@"te\B",RegexOptions.Compiled);
private static Regex OFPattern = new Regex(@"(\s)(of)");
private static Regex TIPattern = new Regex(@"ti\B", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static Regex GUAPattern = new Regex(@"(gu)(a)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static Regex ANDPattern = new Regex(@"\b(\s)(and)(\s)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static Regex TOMYPattern = new Regex(@"(to|my)\s", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static Regex ProperNouns = new Regex(@"(ratvar)|(nezbere)|(sevtuq)|(nzcrentr)|(inath-neq)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public override void Initialize()
{
// Activate before other modifications so translation works properly
SubscribeLocalEvent<RatvarianLanguageComponent, AccentGetEvent>(OnAccent, before: new[] {typeof(SharedSlurredSystem), typeof(SharedStutteringSystem)});
}
public override void DoRatvarian(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null)
{
if (!Resolve(uid, ref status, false))
return;
_statusEffects.TryAddStatusEffect<RatvarianLanguageComponent>(uid, RatvarianKey, time, refresh, status);
}
private void OnAccent(EntityUid uid, RatvarianLanguageComponent component, AccentGetEvent args)
{
args.Message = Translate(args.Message);
}
private string Translate(string message)
{
var ruleTranslation = message;
var finalMessage = new StringBuilder();
var newWord = new StringBuilder();
ruleTranslation = THPattern.Replace(ruleTranslation, "$&`");
ruleTranslation = TEPattern.Replace(ruleTranslation, "$&-");
ruleTranslation = ETPattern.Replace(ruleTranslation, "-$&");
ruleTranslation = OFPattern.Replace(ruleTranslation, "-$2");
ruleTranslation = TIPattern.Replace(ruleTranslation, "$&`");
ruleTranslation = GUAPattern.Replace(ruleTranslation, "$1-$2");
ruleTranslation = ANDPattern.Replace(ruleTranslation, "-$2-");
ruleTranslation = TOMYPattern.Replace(ruleTranslation, "$1-");
var temp = ruleTranslation.Split(' ');
foreach (var word in temp)
{
newWord.Clear();
if (ProperNouns.IsMatch(word))
newWord.Append(word);
else
{
for (int i = 0; i < word.Length; i++)
{
var letter = word[i];
if (letter >= 97 && letter <= 122)
{
var letterRot = letter + 13;
if (letterRot > 122)
letterRot -= 26;
newWord.Append((char) letterRot);
}
else if (letter >= 65 && letter <= 90)
{
var letterRot = letter + 13;
if (letterRot > 90)
letterRot -= 26;
newWord.Append((char) letterRot);
}
else
{
newWord.Append(word[i]);
}
}
}
finalMessage.Append(newWord + " ");
}
return finalMessage.ToString().Trim();
}
}

View File

@@ -5,21 +5,36 @@ namespace Content.Server.StationEvents.Components;
[RegisterComponent, Access(typeof(MeteorSwarmRule))]
public sealed class MeteorSwarmRuleComponent : Component
{
public float _cooldown;
[DataField("cooldown")]
public float Cooldown;
/// <summary>
/// We'll send a specific amount of waves of meteors towards the station per ending rather than using a timer.
/// </summary>
public int _waveCounter;
[DataField("waveCounter")]
public int WaveCounter;
[DataField("minimumWaves")]
public int MinimumWaves = 3;
[DataField("maximumWaves")]
public int MaximumWaves = 8;
[DataField("minimumCooldown")]
public float MinimumCooldown = 10f;
[DataField("maximumCooldown")]
public float MaximumCooldown = 60f;
[DataField("meteorsPerWave")]
public int MeteorsPerWave = 5;
[DataField("meteorVelocity")]
public float MeteorVelocity = 10f;
[DataField("maxAngularVelocity")]
public float MaxAngularVelocity = 0.25f;
[DataField("minAngularVelocity")]
public float MinAngularVelocity = -0.25f;
}

View File

@@ -52,7 +52,7 @@ public sealed class StationEventComponent : Component
/// How long the event lasts.
/// </summary>
[DataField("duration")]
public TimeSpan Duration = TimeSpan.FromSeconds(1);
public TimeSpan? Duration = TimeSpan.FromSeconds(1);
/// <summary>
/// The max amount of time the event lasts.
@@ -85,5 +85,5 @@ public sealed class StationEventComponent : Component
/// When the station event starts.
/// </summary>
[DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan EndTime;
public TimeSpan? EndTime;
}

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