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

# Conflicts:
#	Content.Client/Entry/EntryPoint.cs
#	Content.Client/Preferences/ClientPreferencesManager.cs
#	Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs
#	Resources/Prototypes/Entities/Clothing/Shoes/boots.yml
#	Resources/Prototypes/Entities/Mobs/Species/base.yml
This commit is contained in:
Morb0
2023-09-19 11:41:15 +03:00
588 changed files with 104347 additions and 97138 deletions

View File

@@ -29,7 +29,7 @@ internal sealed class BuckleSystem : SharedBuckleSystem
component.LastEntityBuckledTo = EnsureEntity<BuckleComponent>(state.LastEntityBuckledTo, uid);
component.DontCollide = state.DontCollide;
ActionBlockerSystem.UpdateCanMove(uid);
ActionBlocker.UpdateCanMove(uid);
if (!TryComp<SpriteComponent>(uid, out var ownerSprite))
return;
@@ -65,8 +65,8 @@ internal sealed class BuckleSystem : SharedBuckleSystem
if (!TryComp<RotationVisualsComponent>(uid, out var rotVisuals))
return;
if (!AppearanceSystem.TryGetData<int>(uid, StrapVisuals.RotationAngle, out var angle, args.Component) ||
!AppearanceSystem.TryGetData<bool>(uid, BuckleVisuals.Buckled, out var buckled, args.Component) ||
if (!Appearance.TryGetData<int>(uid, StrapVisuals.RotationAngle, out var angle, args.Component) ||
!Appearance.TryGetData<bool>(uid, BuckleVisuals.Buckled, out var buckled, args.Component) ||
!buckled ||
args.Sprite == null)
{

View File

@@ -59,7 +59,7 @@ public sealed class CharacterInfoSystem : EntitySystem
public readonly record struct CharacterData(
EntityUid Entity,
string Job,
Dictionary<string, List<ConditionInfo>> Objectives,
Dictionary<string, List<ObjectiveInfo>> Objectives,
string? Briefing,
string EntityName
);

View File

@@ -1,6 +1,7 @@
using System.Linq;
using Content.Client.Administration.Managers;
using Content.Client.Administration.Systems;
using Content.Client.UserInterface;
using Content.Shared.Administration;
using Content.Shared.IdentityManagement;
using Robust.Client.GameObjects;
@@ -8,7 +9,7 @@ using Robust.Client.Player;
namespace Content.Client.ContextMenu.UI
{
public sealed partial class EntityMenuElement : ContextMenuElement
public sealed partial class EntityMenuElement : ContextMenuElement, IEntityControl
{
[Dependency] private readonly IClientAdminManager _adminManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
@@ -117,5 +118,7 @@ namespace Content.Client.ContextMenu.UI
Text = GetEntityDescription(entity.Value);
}
}
EntityUid? IEntityControl.UiEntity => Entity;
}
}

View File

@@ -14,6 +14,7 @@
<ColorSelectorSliders Name="ColorPicker" IsAlphaVisible="True" />
<Button Name="PickerOpen" Text="{Loc 'decal-placer-window-palette'}" />
</BoxContainer>
<CheckBox Name="EnableAuto" Text="{Loc 'decal-placer-window-enable-auto'}" Margin="0 0 0 10"/>
<CheckBox Name="EnableColor" Text="{Loc 'decal-placer-window-use-color'}" />
<CheckBox Name="EnableSnap" Text="{Loc 'decal-placer-window-enable-snap'}" />
<CheckBox Name="EnableCleanable" Text="{Loc 'decal-placer-window-enable-cleanable'}" />

View File

@@ -7,7 +7,7 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Utility;
using Robust.Shared.Graphics;
using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BaseButton;
namespace Content.Client.Decals.UI;
@@ -15,6 +15,8 @@ namespace Content.Client.Decals.UI;
[GenerateTypedNameReferences]
public sealed partial class DecalPlacerWindow : DefaultWindow
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
private readonly DecalPlacementSystem _decalPlacementSystem;
public FloatSpinBox RotationSpinBox;
@@ -30,9 +32,12 @@ public sealed partial class DecalPlacerWindow : DefaultWindow
private bool _cleanable;
private int _zIndex;
private bool _auto;
public DecalPlacerWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_decalPlacementSystem = EntitySystem.Get<DecalPlacementSystem>();
@@ -78,6 +83,12 @@ public sealed partial class DecalPlacerWindow : DefaultWindow
_rotation = args.Value;
UpdateDecalPlacementInfo();
};
EnableAuto.OnToggled += args =>
{
_auto = args.Pressed;
if (_selected != null)
SelectDecal(_selected);
};
EnableColor.OnToggled += args =>
{
_useColor = args.Pressed;
@@ -160,9 +171,28 @@ public sealed partial class DecalPlacerWindow : DefaultWindow
private void ButtonOnPressed(ButtonEventArgs obj)
{
if (obj.Button.Name == null) return;
if (obj.Button.Name == null)
return;
_selected = obj.Button.Name;
SelectDecal(obj.Button.Name);
}
private void SelectDecal(string decalId)
{
if (!_prototype.TryIndex<DecalPrototype>(decalId, out var decal))
return;
_selected = decalId;
if (_auto)
{
EnableCleanable.Pressed = decal.DefaultCleanable;
EnableColor.Pressed = decal.DefaultCustomColor;
EnableSnap.Pressed = decal.DefaultSnap;
_cleanable = decal.DefaultCleanable;
_useColor = decal.DefaultCustomColor;
_snap = decal.DefaultSnap;
}
UpdateDecalPlacementInfo();
RefreshList();
}

View File

@@ -169,6 +169,7 @@ namespace Content.Client.Entry
_euiManager.Initialize();
_voteManager.Initialize();
_userInterfaceManager.SetDefaultTheme("SS14DefaultTheme");
_userInterfaceManager.SetActiveTheme(_configManager.GetCVar(CVars.InterfaceTheme));
_sponsorsManager.Initialize(); // Corvax-Sponsors
_queueManager.Initialize(); // Corvax-Queue
_ttsManager.Initialize(); // Corvax-TTS

View File

@@ -30,7 +30,7 @@ public sealed partial class TriggerSystem
{
ComponentType = typeof(PointLightComponent),
InterpolationMode = AnimationInterpolationMode.Nearest,
Property = nameof(PointLightComponent.Radius),
Property = nameof(PointLightComponent.AnimatedRadius),
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(0.1f, 0),

View File

@@ -89,9 +89,10 @@ namespace Content.Client.GameTicking.Managers
private void UpdateJobsAvailable(TickerJobsAvailableEvent message)
{
_jobsAvailable.Clear();
foreach (var (job, data) in message.JobsAvailableByStation)
{
_jobsAvailable.Clear();
_jobsAvailable[job] = data;
}

View File

@@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Content.Client.Clickable;
using Content.Client.ContextMenu.UI;
using Content.Client.UserInterface;
using Content.Shared.Input;
using Robust.Client.ComponentTrees;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@@ -11,10 +10,13 @@ using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Containers;
using Robust.Shared.Console;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
using Robust.Shared.Players;
using Robust.Shared.Timing;
namespace Content.Client.Gameplay
@@ -34,28 +36,36 @@ namespace Content.Client.Gameplay
[Dependency] protected readonly IUserInterfaceManager UserInterfaceManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IViewVariablesManager _vvm = default!;
[Dependency] private readonly IConsoleHost _conHost = default!;
private ClickableEntityComparer _comparer = default!;
private (ViewVariablesPath? path, string[] segments) ResolveVVHoverObject(string path)
private (ViewVariablesPath? path, string[] segments) ResolveVvHoverObject(string path)
{
// VVs the currently hovered entity. For a nifty vv keybinding you can use:
//
// /bind v command "vv /c/enthover"
// /svbind
//
// Though you probably want to include a modifier like alt, as otherwise this would open VV even when typing
// a message into chat containing the letter v.
var segments = path.Split('/');
var uid = RecursivelyFindUiEntity(UserInterfaceManager.CurrentlyHovered);
var netUid = _entityManager.GetNetEntity(uid);
return (netUid != null ? new ViewVariablesInstancePath(netUid) : null, segments);
}
EntityUid? uid = null;
if (UserInterfaceManager.CurrentlyHovered is IViewportControl vp && _inputManager.MouseScreenPosition.IsValid)
uid = GetClickedEntity(vp.PixelToMap(_inputManager.MouseScreenPosition.Position));
else if (UserInterfaceManager.CurrentlyHovered is EntityMenuElement element)
uid = element.Entity;
private EntityUid? RecursivelyFindUiEntity(Control? control)
{
if (control == null)
return null;
return (uid != null ? new ViewVariablesInstancePath(uid) : null, segments);
switch (control)
{
case IViewportControl vp:
if (_inputManager.MouseScreenPosition.IsValid)
return GetClickedEntity(vp.PixelToMap(_inputManager.MouseScreenPosition.Position));
return null;
case SpriteView sprite:
return sprite.Entity;
case IEntityControl ui:
return ui.UiEntity;
}
return RecursivelyFindUiEntity(control.Parent);
}
private IEnumerable<string>? ListVVHoverPaths(string[] segments)
@@ -65,15 +75,25 @@ namespace Content.Client.Gameplay
protected override void Startup()
{
_vvm.RegisterDomain("enthover", ResolveVVHoverObject, ListVVHoverPaths);
_vvm.RegisterDomain("enthover", ResolveVvHoverObject, ListVVHoverPaths);
_inputManager.KeyBindStateChanged += OnKeyBindStateChanged;
_comparer = new ClickableEntityComparer();
CommandBinds.Builder
.Bind(ContentKeyFunctions.InspectEntity, new PointerInputCmdHandler(HandleInspect, outsidePrediction: true))
.Register<GameplayStateBase>();
}
protected override void Shutdown()
{
_vvm.UnregisterDomain("enthover");
_inputManager.KeyBindStateChanged -= OnKeyBindStateChanged;
CommandBinds.Unregister<GameplayStateBase>();
}
private bool HandleInspect(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
_conHost.ExecuteCommand($"vv /c/enthover");
return true;
}
public EntityUid? GetClickedEntity(MapCoordinates coordinates)

View File

@@ -20,7 +20,7 @@ public sealed class GatewayBoundUserInterface : BoundUserInterface
_window = new GatewayWindow();
_window.OpenPortal += destination =>
{
SendMessage(new GatewayOpenPortalMessage(EntMan.GetNetEntity(destination)));
SendMessage(new GatewayOpenPortalMessage(destination));
};
_window.OnClose += Close;
_window?.OpenCentered();
@@ -29,8 +29,11 @@ public sealed class GatewayBoundUserInterface : BoundUserInterface
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_window?.Dispose();
_window = null;
if (disposing)
{
_window?.Dispose();
_window = null;
}
}
protected override void UpdateState(BoundUserInterfaceState state)

View File

@@ -16,12 +16,11 @@ namespace Content.Client.Gateway.UI;
public sealed partial class GatewayWindow : FancyWindow,
IComputerWindow<EmergencyConsoleBoundUserInterfaceState>
{
private readonly IEntityManager _entManager;
private readonly IGameTiming _timing;
public event Action<EntityUid>? OpenPortal;
public event Action<NetEntity>? OpenPortal;
private List<(NetEntity, string, TimeSpan, bool)> _destinations = default!;
private EntityUid? _current;
private NetEntity? _current;
private TimeSpan _nextClose;
private TimeSpan _lastOpen;
private List<Label> _readyLabels = default!;
@@ -31,14 +30,13 @@ public sealed partial class GatewayWindow : FancyWindow,
{
RobustXamlLoader.Load(this);
var dependencies = IoCManager.Instance!;
_entManager = dependencies.Resolve<IEntityManager>();
_timing = dependencies.Resolve<IGameTiming>();
}
public void UpdateState(GatewayBoundUserInterfaceState state)
{
_destinations = state.Destinations;
_current = _entManager.GetEntity(state.Current);
_current = state.Current;
_nextClose = state.NextClose;
_lastOpen = state.LastOpen;
@@ -67,7 +65,7 @@ public sealed partial class GatewayWindow : FancyWindow,
var now = _timing.CurTime;
foreach (var dest in _destinations)
{
var uid = _entManager.GetEntity(dest.Item1);
var ent = dest.Item1;
var name = dest.Item2;
var nextReady = dest.Item3;
var busy = dest.Item4;
@@ -85,7 +83,7 @@ public sealed partial class GatewayWindow : FancyWindow,
var readyLabel = new Label
{
Text = ReadyText(now, nextReady),
Text = ReadyText(now, nextReady, busy),
Margin = new Thickness(10f, 0f, 0f, 0f)
};
_readyLabels.Add(readyLabel);
@@ -94,17 +92,17 @@ public sealed partial class GatewayWindow : FancyWindow,
var openButton = new Button()
{
Text = Loc.GetString("gateway-window-open-portal"),
Pressed = uid == _current,
Pressed = ent == _current,
ToggleMode = true,
Disabled = _current != null || busy || now < nextReady
};
openButton.OnPressed += args =>
{
OpenPortal?.Invoke(uid);
OpenPortal?.Invoke(ent);
};
if (uid == _entManager.GetEntity(state.Current))
if (ent == _current)
{
openButton.AddStyleClass(StyleBase.ButtonCaution);
}
@@ -165,13 +163,16 @@ public sealed partial class GatewayWindow : FancyWindow,
var dest = _destinations[i];
var nextReady = dest.Item3;
var busy = dest.Item4;
_readyLabels[i].Text = ReadyText(now, nextReady);
_readyLabels[i].Text = ReadyText(now, nextReady, busy);
_openButtons[i].Disabled = _current != null || busy || now < nextReady;
}
}
private string ReadyText(TimeSpan now, TimeSpan nextReady)
private string ReadyText(TimeSpan now, TimeSpan nextReady, bool busy)
{
if (busy)
return Loc.GetString("gateway-window-already-active");
if (now < nextReady)
{
var time = nextReady - now;

View File

@@ -33,6 +33,7 @@ namespace Content.Client.Input
common.AddFunction(ContentKeyFunctions.ZoomOut);
common.AddFunction(ContentKeyFunctions.ZoomIn);
common.AddFunction(ContentKeyFunctions.ResetZoom);
common.AddFunction(ContentKeyFunctions.InspectEntity);
// Not in engine, because engine cannot check for sanbox/admin status before starting placement.
common.AddFunction(ContentKeyFunctions.EditorCopyObject);

View File

@@ -24,7 +24,7 @@ namespace Content.Client.Light.Components
[DataField("id")] public string ID { get; set; } = string.Empty;
[DataField("property")]
public virtual string Property { get; protected set; } = "Radius";
public virtual string Property { get; protected set; } = nameof(PointLightComponent.AnimatedRadius);
[DataField("isLooped")] public bool IsLooped { get; set; }
@@ -119,7 +119,7 @@ namespace Content.Client.Light.Components
var playingTime = prevPlayingTime + frameTime;
var interpolateValue = playingTime / MaxTime;
if (Property == "Enabled") // special case for boolean
if (Property == nameof(PointLightComponent.AnimatedEnable)) // special case for boolean
{
ApplyProperty(interpolateValue < 0.5f);
return (-1, playingTime);
@@ -181,7 +181,7 @@ namespace Content.Client.Light.Components
var playingTime = prevPlayingTime + frameTime;
var interpolateValue = playingTime / MaxTime;
if (Property == "Enabled") // special case for boolean
if (Property == nameof(PointLightComponent.AnimatedEnable)) // special case for boolean
{
ApplyProperty(interpolateValue < EndValue);
return (-1, playingTime);
@@ -245,7 +245,7 @@ namespace Content.Client.Light.Components
public override void OnStart()
{
if (Property == "Enabled") // special case for boolean, we randomize it
if (Property == nameof(PointLightComponent.AnimatedEnable)) // special case for boolean, we randomize it
{
ApplyProperty(_random.NextDouble() < 0.5);
return;
@@ -267,7 +267,7 @@ namespace Content.Client.Light.Components
var playingTime = prevPlayingTime + frameTime;
var interpolateValue = playingTime / MaxTime;
if (Property == "Enabled")
if (Property == nameof(PointLightComponent.AnimatedEnable))
{
return (-1, playingTime);
}
@@ -298,7 +298,7 @@ namespace Content.Client.Light.Components
public sealed partial class ColorCycleBehaviour : LightBehaviourAnimationTrack, ISerializationHooks
{
[DataField("property")]
public override string Property { get; protected set; } = "Color";
public override string Property { get; protected set; } = nameof(PointLightComponent.Color);
[DataField("colors")] public List<Color> ColorsToCycle { get; set; } = new();

View File

@@ -89,7 +89,7 @@ public sealed class PoweredLightVisualizerSystem : VisualizerSystem<PoweredLight
{
ComponentType = typeof(PointLightComponent),
InterpolationMode = AnimationInterpolationMode.Nearest,
Property = nameof(PointLightComponent.Enabled),
Property = nameof(PointLightComponent.AnimatedEnable),
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(false, 0),

View File

@@ -0,0 +1,7 @@
using Content.Shared.Objectives.Systems;
namespace Content.Client.Objectives.Systems;
public sealed class ObjectivesSystem : SharedObjectivesSystem
{
}

View File

@@ -26,6 +26,8 @@ namespace Content.Client.Options.UI.Tabs
2f
};
private Dictionary<string, int> hudThemeIdToIndex = new();
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -56,6 +58,7 @@ namespace Content.Client.Options.UI.Tabs
foreach (var gear in _prototypeManager.EnumeratePrototypes<HudThemePrototype>())
{
HudThemeOption.AddItem(Loc.GetString(gear.Name));
hudThemeIdToIndex.Add(gear.ID, HudThemeOption.GetItemId(HudThemeOption.ItemCount - 1));
}
HudThemeOption.OnItemSelected += OnHudThemeChanged;
@@ -109,7 +112,7 @@ namespace Content.Client.Options.UI.Tabs
FullscreenCheckBox.Pressed = ConfigIsFullscreen;
LightingPresetOption.SelectId(GetConfigLightingQuality());
UIScaleOption.SelectId(GetConfigUIScalePreset(ConfigUIScale));
HudThemeOption.SelectId(_cfg.GetCVar(CCVars.HudTheme));
HudThemeOption.SelectId(hudThemeIdToIndex.GetValueOrDefault(_cfg.GetCVar(CVars.InterfaceTheme), 0));
ViewportScaleSlider.Value = _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
ViewportStretchCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportStretch);
IntegerScalingCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportSnapToleranceMargin) != 0;
@@ -145,9 +148,13 @@ namespace Content.Client.Options.UI.Tabs
{
_cfg.SetCVar(CVars.DisplayVSync, VSyncCheckBox.Pressed);
SetConfigLightingQuality(LightingPresetOption.SelectedId);
if (HudThemeOption.SelectedId != _cfg.GetCVar(CCVars.HudTheme)) // Don't unnecessarily redraw the HUD
foreach (var theme in _prototypeManager.EnumeratePrototypes<HudThemePrototype>())
{
_cfg.SetCVar(CCVars.HudTheme, HudThemeOption.SelectedId);
if (hudThemeIdToIndex[theme.ID] != HudThemeOption.SelectedId)
continue;
_cfg.SetCVar(CVars.InterfaceTheme, theme.ID);
break;
}
_cfg.SetCVar(CVars.DisplayWindowMode,
@@ -189,7 +196,7 @@ namespace Content.Client.Options.UI.Tabs
var isVSyncSame = VSyncCheckBox.Pressed == _cfg.GetCVar(CVars.DisplayVSync);
var isFullscreenSame = FullscreenCheckBox.Pressed == ConfigIsFullscreen;
var isLightingQualitySame = LightingPresetOption.SelectedId == GetConfigLightingQuality();
var isHudThemeSame = HudThemeOption.SelectedId == _cfg.GetCVar(CCVars.HudTheme);
var isHudThemeSame = HudThemeOption.SelectedId == hudThemeIdToIndex.GetValueOrDefault(_cfg.GetCVar(CVars.InterfaceTheme), 0);
var isUIScaleSame = MathHelper.CloseToPercent(UIScaleOptions[UIScaleOption.SelectedId], ConfigUIScale);
var isVPStretchSame = ViewportStretchCheckBox.Pressed == _cfg.GetCVar(CCVars.ViewportStretch);
var isVPScaleSame = (int) ViewportScaleSlider.Value == _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);

View File

@@ -192,6 +192,7 @@ namespace Content.Client.Options.UI.Tabs
AddButton(EngineKeyFunctions.ShowDebugConsole);
AddButton(EngineKeyFunctions.ShowDebugMonitors);
AddButton(EngineKeyFunctions.HideUI);
AddButton(ContentKeyFunctions.InspectEntity);
foreach (var control in _keyControls.Values)
{

View File

@@ -29,6 +29,9 @@ public sealed class NavMapSystem : SharedNavMapSystem
TileData = data,
});
}
component.Beacons.Clear();
component.Beacons.AddRange(state.Beacons);
}
}

View File

@@ -2,7 +2,9 @@ using System.Numerics;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Shared.Pinpointer;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input;
@@ -20,18 +22,19 @@ namespace Content.Client.Pinpointer.UI;
public sealed class NavMapControl : MapGridControl
{
[Dependency] private readonly IEntityManager _entManager = default!;
private SharedTransformSystem _transform;
public EntityUid? MapUid;
public Dictionary<EntityCoordinates, (bool Visible, Color Color)> TrackedCoordinates = new();
private Vector2 _offset;
private bool _draggin;
private bool _recentering = false;
private float _recenterMinimum = 0.05f;
private readonly float _recenterMinimum = 0.05f;
private readonly Font _font;
private static readonly Color TileColor = new(30, 67, 30);
private static readonly Color BeaconColor = Color.FromSrgb(TileColor.WithAlpha(0.8f));
// TODO: https://github.com/space-wizards/RobustToolbox/issues/3818
private readonly Label _zoom = new()
@@ -52,6 +55,11 @@ public sealed class NavMapControl : MapGridControl
public NavMapControl() : base(8f, 128f, 48f)
{
IoCManager.InjectDependencies(this);
_transform = _entManager.System<SharedTransformSystem>();
var cache = IoCManager.Resolve<IResourceCache>();
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 16);
RectClipContent = true;
HorizontalExpand = true;
VerticalExpand = true;
@@ -175,7 +183,6 @@ public sealed class NavMapControl : MapGridControl
}
var offset = _offset;
var tileColor = new Color(30, 67, 30);
var lineColor = new Color(102, 217, 102);
if (_entManager.TryGetComponent<PhysicsComponent>(MapUid, out var physics))
@@ -200,7 +207,7 @@ public sealed class NavMapControl : MapGridControl
verts[i] = Scale(new Vector2(vert.X, -vert.Y));
}
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..poly.VertexCount], tileColor);
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..poly.VertexCount], TileColor);
}
}
@@ -333,6 +340,24 @@ public sealed class NavMapControl : MapGridControl
}
}
}
// Beacons
var labelOffset = new Vector2(0.5f, 0.5f) * MinimapScale;
var rectBuffer = new Vector2(5f, 3f);
foreach (var beacon in navMap.Beacons)
{
var position = beacon.Position - offset;
position = Scale(position with { Y = -position.Y });
handle.DrawCircle(position, MinimapScale / 2f, beacon.Color);
var textDimensions = handle.GetDimensions(_font, beacon.Text, 1f);
var labelPosition = position + labelOffset;
handle.DrawRect(new UIBox2(labelPosition, labelPosition + textDimensions + rectBuffer * 2), BeaconColor);
handle.DrawString(_font, labelPosition + rectBuffer, beacon.Text, beacon.Color);
}
}
private Vector2 Scale(Vector2 position)

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Content.Client.Corvax.Sponsors;
using Content.Shared.Preferences;
using Robust.Client;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Utility;
@@ -17,6 +18,7 @@ namespace Content.Client.Preferences
public sealed class ClientPreferencesManager : IClientPreferencesManager
{
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IBaseClient _baseClient = default!;
[Dependency] private readonly SponsorsManager _sponsorsManager = default!; // Corvax-Sponsors
public event Action? OnServerDataLoaded;
@@ -30,6 +32,17 @@ namespace Content.Client.Preferences
_netManager.RegisterNetMessage<MsgUpdateCharacter>();
_netManager.RegisterNetMessage<MsgSelectCharacter>();
_netManager.RegisterNetMessage<MsgDeleteCharacter>();
_baseClient.RunLevelChanged += BaseClientOnRunLevelChanged;
}
private void BaseClientOnRunLevelChanged(object? sender, RunLevelChangedEventArgs e)
{
if (e.NewLevel == ClientRunLevel.Initialize)
{
Settings = default!;
Preferences = default!;
}
}
public void SelectCharacter(ICharacterProfile profile)

View File

@@ -1,10 +1,11 @@
using Content.Client.Gameplay;
using Content.Client.Storage.Systems;
using Content.Shared.Storage;
using Robust.Client.UserInterface.Controllers;
namespace Content.Client.Storage.UI;
public sealed class StorageUIController : UIController, IOnSystemChanged<StorageSystem>
public sealed class StorageUIController : UIController, IOnSystemChanged<StorageSystem>, IOnStateExited<GameplayState>
{
// This is mainly to keep legacy functionality for now.
private readonly Dictionary<EntityUid, StorageWindow> _storageWindows = new();
@@ -57,4 +58,14 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
{
system.StorageUpdated -= OnStorageUpdate;
}
public void OnStateExited(GameplayState state)
{
foreach (var window in _storageWindows.Values)
{
window.Dispose();
}
_storageWindows.Clear();
}
}

View File

@@ -344,7 +344,7 @@ public sealed class ListContainer : Control
}
}
public sealed class ListContainerButton : ContainerButton
public sealed class ListContainerButton : ContainerButton, IEntityControl
{
public readonly ListData Data;
// public PanelContainer Background;
@@ -359,6 +359,8 @@ public sealed class ListContainerButton : ContainerButton
// PanelOverride = new StyleBoxFlat {BackgroundColor = new Color(55, 55, 68)}
// });
}
public EntityUid? UiEntity => (Data as EntityListData)?.Uid;
}
#region Data

View File

@@ -8,7 +8,7 @@ using Robust.Shared.Input;
namespace Content.Client.UserInterface.Controls
{
[Virtual]
public abstract class SlotControl : Control
public abstract class SlotControl : Control, IEntityControl
{
public static int DefaultButtonSize = 64;
@@ -20,7 +20,7 @@ namespace Content.Client.UserInterface.Controls
public TextureButton StorageButton { get; }
public CooldownGraphic CooldownDisplay { get; }
public EntityUid? Entity => SpriteView.Sprite?.Owner;
public EntityUid? Entity => SpriteView.Entity;
private bool _slotNameSet;
@@ -232,5 +232,7 @@ namespace Content.Client.UserInterface.Controls
ButtonRect.Texture = Theme.ResolveTextureOrNull(_buttonTexturePath)?.Texture;
HighlightRect.Texture = Theme.ResolveTextureOrNull(_highlightTexturePath)?.Texture;
}
EntityUid? IEntityControl.UiEntity => Entity;
}
}

View File

@@ -0,0 +1,10 @@
namespace Content.Client.UserInterface;
/// <summary>
/// Simple interface that indicates that the given control is associated with some entity.
/// This is primarily intended to be used with VV, so that you can easily open the VV window to examine an entity.
/// </summary>
public interface IEntityControl
{
EntityUid? UiEntity { get; }
}

View File

@@ -19,7 +19,7 @@ using Direction = Robust.Shared.Maths.Direction;
namespace Content.Client.UserInterface.Systems.Actions.Controls;
public sealed class ActionButton : Control
public sealed class ActionButton : Control, IEntityControl
{
private IEntityManager? _entities;
@@ -197,7 +197,7 @@ public sealed class ActionButton : Control
private void UpdateItemIcon()
{
if (!Actions.TryGetActionData(ActionId, out var action) ||
action is not { EntityIcon: { } entity } ||
action is not {EntityIcon: { } entity} ||
!Entities.HasComponent<SpriteComponent>(entity))
{
_bigItemSpriteView.Visible = false;
@@ -344,7 +344,7 @@ public sealed class ActionButton : Control
public void Depress(GUIBoundKeyEventArgs args, bool depress)
{
// action can still be toggled if it's allowed to stay selected
if (!Actions.TryGetActionData(ActionId, out var action) || action is not { Enabled: true })
if (!Actions.TryGetActionData(ActionId, out var action) || action is not {Enabled: true})
return;
if (_depressed && !depress)
@@ -401,4 +401,6 @@ public sealed class ActionButton : Control
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassNormal);
}
EntityUid? IEntityControl.UiEntity => ActionId;
}

View File

@@ -478,7 +478,7 @@ public sealed class UserAHelpUIHandler : IAHelpUIHandler
TitleClass="windowTitleAlert",
HeaderClass="windowHeaderAlert",
Title=Loc.GetString("bwoink-user-title"),
MinSize = new Vector2(500, 200),
MinSize = new Vector2(500, 300),
};
_window.OnClose += () => { OnClose?.Invoke(); };
_window.OnOpen += () => { OnOpen?.Invoke(); };

View File

@@ -128,7 +128,7 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
foreach (var condition in conditions)
{
var conditionControl = new ObjectiveConditionsControl();
conditionControl.ProgressTexture.Texture = _sprite.Frame0(condition.SpriteSpecifier);
conditionControl.ProgressTexture.Texture = _sprite.Frame0(condition.Icon);
conditionControl.ProgressTexture.Progress = condition.Progress;
var titleMessage = new FormattedMessage();
var descriptionMessage = new FormattedMessage();

View File

@@ -1,4 +1,4 @@
using Content.Client.Alerts;
using Content.Client.Alerts;
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Content.Shared.Mobs;
@@ -79,9 +79,15 @@ public sealed class DamageOverlayUiController : UIController
damageable == null && !EntityManager.TryGetComponent(entity, out damageable))
return;
if (!_mobThresholdSystem.TryGetIncapThreshold(entity, out var foundThreshold, thresholds))
return; //this entity cannot die or crit!!
if (!thresholds.ShowOverlays)
{
ClearOverlay();
return; //this entity intentionally has no overlays
}
var critThreshold = foundThreshold.Value;
_overlay.State = mobState.CurrentState;

View File

@@ -216,21 +216,25 @@ namespace Content.Client.Verbs.UI
return;
}
if (verb.ConfirmationPopup)
{
if (verbElement.SubMenu == null)
{
var popupElement = new ConfirmationMenuElement(verb, "Confirm");
verbElement.SubMenu = new ContextMenuPopup(_context, verbElement);
_context.AddElement(verbElement.SubMenu, popupElement);
}
_context.OpenSubMenu(verbElement);
}
else
#if DEBUG
// No confirmation pop-ups in debug mode.
ExecuteVerb(verb);
#else
if (!verb.ConfirmationPopup)
{
ExecuteVerb(verb);
return;
}
if (verbElement.SubMenu == null)
{
var popupElement = new ConfirmationMenuElement(verb, "Confirm");
verbElement.SubMenu = new ContextMenuPopup(_context, verbElement);
_context.AddElement(verbElement.SubMenu, popupElement);
}
_context.OpenSubMenu(verbElement);
#endif
}
private void Close()

View File

@@ -334,7 +334,7 @@ public sealed partial class GunSystem : SharedGunSystem
new AnimationTrackComponentProperty
{
ComponentType = typeof(PointLightComponent),
Property = nameof(PointLightComponent.Enabled),
Property = nameof(PointLightComponent.AnimatedEnable),
InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames =
{

View File

@@ -0,0 +1,56 @@
using Content.Server.GameTicking;
using Content.Shared.CCVar;
using Robust.Shared;
using Robust.Shared.Replays;
namespace Content.IntegrationTests.Tests.Replays;
[TestFixture]
public sealed class ReplayTests
{
/// <summary>
/// Simple test that just makes sure that automatic replay recording on round restarts works without any issues.
/// </summary>
[Test]
public async Task AutoRecordReplayTest()
{
var settings = new PoolSettings {DummyTicker = false};
await using var pair = await PoolManager.GetServerClient(settings);
var server = pair.Server;
Assert.That(server.CfgMan.GetCVar(CVars.ReplayServerRecordingEnabled), Is.False);
var recordMan = server.ResolveDependency<IReplayRecordingManager>();
Assert.That(recordMan.IsRecording, Is.False);
// Setup cvars.
var autoRec = server.CfgMan.GetCVar(CCVars.ReplayAutoRecord);
var autoRecName = server.CfgMan.GetCVar(CCVars.ReplayAutoRecordName);
var tempDir = server.CfgMan.GetCVar(CCVars.ReplayAutoRecordTempDir);
server.CfgMan.SetCVar(CVars.ReplayServerRecordingEnabled, true);
server.CfgMan.SetCVar(CCVars.ReplayAutoRecord, true);
server.CfgMan.SetCVar(CCVars.ReplayAutoRecordTempDir, "/a/b/");
server.CfgMan.SetCVar(CCVars.ReplayAutoRecordName, $"c/d/{autoRecName}");
// Restart the round a few times
var ticker = server.System<GameTicker>();
await server.WaitPost(() => ticker.RestartRound());
await pair.RunTicksSync(25);
Assert.That(recordMan.IsRecording, Is.True);
await server.WaitPost(() => ticker.RestartRound());
await pair.RunTicksSync(25);
Assert.That(recordMan.IsRecording, Is.True);
// Reset cvars
server.CfgMan.SetCVar(CVars.ReplayServerRecordingEnabled, false);
server.CfgMan.SetCVar(CCVars.ReplayAutoRecord, autoRec);
server.CfgMan.SetCVar(CCVars.ReplayAutoRecordTempDir, tempDir);
server.CfgMan.SetCVar(CCVars.ReplayAutoRecordName, autoRecName);
// Restart the round again to disable the current recording.
await server.WaitPost(() => ticker.RestartRound());
await pair.RunTicksSync(25);
Assert.That(recordMan.IsRecording, Is.False);
await pair.CleanReturnAsync();
}
}

View File

@@ -16,6 +16,9 @@ namespace Content.Server.Atmos.EntitySystems
public bool MonstermosDepressurization { get; private set; }
public bool MonstermosRipTiles { get; private set; }
public bool GridImpulse { get; private set; }
public float SpacingEscapeRatio { get; private set; }
public float SpacingMinGas { get; private set; }
public float SpacingMaxWind { get; private set; }
public bool Superconduction { get; private set; }
public bool ExcitedGroups { get; private set; }
public bool ExcitedGroupsSpaceIsAllConsuming { get; private set; }
@@ -40,6 +43,9 @@ namespace Content.Server.Atmos.EntitySystems
_cfg.OnValueChanged(CCVars.MonstermosDepressurization, value => MonstermosDepressurization = value, true);
_cfg.OnValueChanged(CCVars.MonstermosRipTiles, value => MonstermosRipTiles = value, true);
_cfg.OnValueChanged(CCVars.AtmosGridImpulse, value => GridImpulse = value, true);
_cfg.OnValueChanged(CCVars.AtmosSpacingEscapeRatio, value => SpacingEscapeRatio = value, true);
_cfg.OnValueChanged(CCVars.AtmosSpacingMinGas, value => SpacingMinGas = value, true);
_cfg.OnValueChanged(CCVars.AtmosSpacingMaxWind, value => SpacingMaxWind = value, true);
_cfg.OnValueChanged(CCVars.Superconduction, value => Superconduction = value, true);
_cfg.OnValueChanged(CCVars.AtmosMaxProcessTime, value => AtmosMaxProcessTime = value, true);
_cfg.OnValueChanged(CCVars.AtmosTickRate, value => AtmosTickRate = value, true);

View File

@@ -467,19 +467,20 @@ namespace Content.Server.Atmos.EntitySystems
otherTile.Air.Temperature = Atmospherics.TCMB;
continue;
}
// Pressure as a multiple of normal air pressure (takes temperature into account)
float pressureMultiple = (otherTile.Air.Pressure / 110.0f);
var sum = otherTile.Air.TotalMoles * Atmospherics.SpacingEscapeRatio * pressureMultiple;
if (sum < Atmospherics.SpacingMinGas)
var sum = otherTile.Air.TotalMoles;
if (SpacingEscapeRatio < 1f)
{
// Boost the last bit of air draining from the tile.
sum = Math.Min(Atmospherics.SpacingMinGas, otherTile.Air.TotalMoles);
}
if (sum + otherTile.MonstermosInfo.CurrentTransferAmount > Atmospherics.SpacingMaxWind * pressureMultiple)
{
// Limit the flow of air out of tiles which have air flowing into them from elsewhere.
sum = Math.Max(Atmospherics.SpacingMinGas, Atmospherics.SpacingMaxWind * pressureMultiple - otherTile.MonstermosInfo.CurrentTransferAmount);
sum *= SpacingEscapeRatio;
if (sum < SpacingMinGas)
{
// Boost the last bit of air draining from the tile.
sum = Math.Min(SpacingMinGas, otherTile.Air.TotalMoles);
}
if (sum + otherTile.MonstermosInfo.CurrentTransferAmount > SpacingMaxWind)
{
// Limit the flow of air out of tiles which have air flowing into them from elsewhere.
sum = Math.Max(SpacingMinGas, SpacingMaxWind - otherTile.MonstermosInfo.CurrentTransferAmount);
}
}
totalMolesRemoved += sum;
otherTile.MonstermosInfo.CurrentTransferAmount += sum;
@@ -493,7 +494,7 @@ namespace Content.Server.Atmos.EntitySystems
otherTile2.PressureDirection = otherTile.MonstermosInfo.CurrentTransferDirection;
}
if (otherTile.Air != null && otherTile.Air.Pressure - sum > Atmospherics.SpacingMinGas * 0.1f)
if (otherTile.Air != null && otherTile.Air.Pressure - sum > SpacingMinGas * 0.1f)
{
// Transfer the air into the other tile (space wind :)
ReleaseGasTo(otherTile.Air!, otherTile2.Air!, sum);
@@ -652,7 +653,7 @@ namespace Content.Server.Atmos.EntitySystems
if (!MonstermosRipTiles)
return;
var chance = MathHelper.Clamp(0.01f + (sum / Atmospherics.SpacingMaxWind) * 0.3f, 0.003f, 0.3f);
var chance = MathHelper.Clamp(0.01f + (sum / SpacingMaxWind) * 0.3f, 0.003f, 0.3f);
if (sum > 20 && _robustRandom.Prob(chance))
PryTile(mapGrid, tile.GridIndices);

View File

@@ -168,7 +168,7 @@ public partial class SeedData
[DataField("seedless")] public bool Seedless = false;
/// <summary>
/// If true, rapidly decrease health while growing. Used to kill off
/// If false, rapidly decrease health while growing. Used to kill off
/// plants with "bad" mutations.
/// </summary>
[DataField("viable")] public bool Viable = true;
@@ -228,6 +228,12 @@ public partial class SeedData
#endregion
/// <summary>
/// The seed prototypes this seed may mutate into when prompted to.
/// </summary>
[DataField("mutationPrototypes", customTypeSerializer: typeof(PrototypeIdListSerializer<SeedPrototype>))]
public List<string> MutationPrototypes = new();
public SeedData Clone()
{
DebugTools.Assert(!Immutable, "There should be no need to clone an immutable seed.");
@@ -241,6 +247,7 @@ public partial class SeedData
PacketPrototype = PacketPrototype,
ProductPrototypes = new List<string>(ProductPrototypes),
MutationPrototypes = new List<string>(MutationPrototypes),
Chemicals = new Dictionary<string, SeedChemQuantity>(Chemicals),
ConsumeGasses = new Dictionary<Gas, float>(ConsumeGasses),
ExudeGasses = new Dictionary<Gas, float>(ExudeGasses),

View File

@@ -27,7 +27,7 @@ public sealed class MutationSystem : EntitySystem
///
/// You MUST clone() seed before mutating it!
/// </summary>
public void MutateSeed(SeedData seed, float severity)
public void MutateSeed(ref SeedData seed, float severity)
{
if (!seed.Unique)
{
@@ -36,43 +36,45 @@ public sealed class MutationSystem : EntitySystem
}
// Add up everything in the bits column and put the number here.
const int totalbits = 270;
const int totalbits = 265;
// Tolerances (55)
MutateFloat(ref seed.NutrientConsumption , 0.05f , 1.2f , 5 , totalbits , severity);
MutateFloat(ref seed.WaterConsumption , 3f , 9f , 5 , totalbits , severity);
MutateFloat(ref seed.IdealHeat , 263f , 323f , 5 , totalbits , severity);
MutateFloat(ref seed.HeatTolerance , 2f , 25f , 5 , totalbits , severity);
MutateFloat(ref seed.IdealLight , 0f , 14f , 5 , totalbits , severity);
MutateFloat(ref seed.LightTolerance , 1f , 5f , 5 , totalbits , severity);
MutateFloat(ref seed.ToxinsTolerance , 1f , 10f , 5 , totalbits , severity);
MutateFloat(ref seed.LowPressureTolerance , 60f , 100f , 5 , totalbits , severity);
MutateFloat(ref seed.HighPressureTolerance , 100f , 140f , 5 , totalbits , severity);
MutateFloat(ref seed.PestTolerance , 0f , 15f , 5 , totalbits , severity);
MutateFloat(ref seed.WeedTolerance , 0f , 15f , 5 , totalbits , severity);
MutateFloat(ref seed.NutrientConsumption , 0.05f, 1.2f, 5, totalbits, severity);
MutateFloat(ref seed.WaterConsumption , 3f , 9f , 5, totalbits, severity);
MutateFloat(ref seed.IdealHeat , 263f , 323f, 5, totalbits, severity);
MutateFloat(ref seed.HeatTolerance , 2f , 25f , 5, totalbits, severity);
MutateFloat(ref seed.IdealLight , 0f , 14f , 5, totalbits, severity);
MutateFloat(ref seed.LightTolerance , 1f , 5f , 5, totalbits, severity);
MutateFloat(ref seed.ToxinsTolerance , 1f , 10f , 5, totalbits, severity);
MutateFloat(ref seed.LowPressureTolerance , 60f , 100f, 5, totalbits, severity);
MutateFloat(ref seed.HighPressureTolerance, 100f , 140f, 5, totalbits, severity);
MutateFloat(ref seed.PestTolerance , 0f , 15f , 5, totalbits, severity);
MutateFloat(ref seed.WeedTolerance , 0f , 15f , 5, totalbits, severity);
// Stats (30*2 = 60)
MutateFloat(ref seed.Endurance , 50f , 150f , 5 , totalbits , 2*severity);
MutateInt(ref seed.Yield , 3 , 10 , 5 , totalbits , 2*severity);
MutateFloat(ref seed.Lifespan , 10f , 80f , 5 , totalbits , 2*severity);
MutateFloat(ref seed.Maturation , 3f , 8f , 5 , totalbits , 2*severity);
MutateFloat(ref seed.Production , 1f , 10f , 5 , totalbits , 2*severity);
MutateFloat(ref seed.Potency , 30f , 100f , 5 , totalbits , 2*severity);
MutateFloat(ref seed.Endurance , 50f , 150f, 5, totalbits, 2 * severity);
MutateInt(ref seed.Yield , 3 , 10 , 5, totalbits, 2 * severity);
MutateFloat(ref seed.Lifespan , 10f , 80f , 5, totalbits, 2 * severity);
MutateFloat(ref seed.Maturation , 3f , 8f , 5, totalbits, 2 * severity);
MutateFloat(ref seed.Production , 1f , 10f , 5, totalbits, 2 * severity);
MutateFloat(ref seed.Potency , 30f , 100f, 5, totalbits, 2 * severity);
// Kill the plant (30)
MutateBool(ref seed.Viable , false , 30 , totalbits , severity);
MutateBool(ref seed.Viable , false, 30, totalbits, severity);
// Fun (90)
MutateBool(ref seed.Seedless , true , 10 , totalbits , severity);
MutateBool(ref seed.Slip , true , 10 , totalbits , severity);
MutateBool(ref seed.Sentient , true , 10 , totalbits , severity);
MutateBool(ref seed.Ligneous , true , 10 , totalbits , severity);
MutateBool(ref seed.Bioluminescent , true , 10 , totalbits , severity);
MutateBool(ref seed.TurnIntoKudzu , true , 10 , totalbits , severity);
MutateBool(ref seed.CanScream , true , 10 , totalbits , severity);
MutateBool(ref seed.Seedless , true , 10, totalbits, severity);
MutateBool(ref seed.Slip , true , 10, totalbits, severity);
MutateBool(ref seed.Sentient , true , 10, totalbits, severity);
MutateBool(ref seed.Ligneous , true , 10, totalbits, severity);
MutateBool(ref seed.Bioluminescent, true , 10, totalbits, severity);
// Kudzu disabled until superkudzu bug is fixed
// MutateBool(ref seed.TurnIntoKudzu , true , 10, totalbits, severity);
MutateBool(ref seed.CanScream , true , 10, totalbits, severity);
seed.BioluminescentColor = RandomColor(seed.BioluminescentColor, 10, totalbits, severity);
// ConstantUpgade (10)
MutateHarvestType(ref seed.HarvestRepeat , 10 , totalbits , severity);
MutateHarvestType(ref seed.HarvestRepeat, 10, totalbits, severity);
// Gas (5)
MutateGasses(ref seed.ExudeGasses, 0.01f, 0.5f, 4, totalbits, severity);
@@ -80,6 +82,9 @@ public sealed class MutationSystem : EntitySystem
// Chems (20)
MutateChemicals(ref seed.Chemicals, 5, 20, totalbits, severity);
// Species (5)
MutateSpecies(ref seed, 5, totalbits, severity);
}
public SeedData Cross(SeedData a, SeedData b)
@@ -113,8 +118,9 @@ public sealed class MutationSystem : EntitySystem
CrossBool(ref result.Sentient, a.Sentient);
CrossBool(ref result.Ligneous, a.Ligneous);
CrossBool(ref result.Bioluminescent, a.Bioluminescent);
CrossBool(ref result.TurnIntoKudzu, a.TurnIntoKudzu);
// CrossBool(ref result.TurnIntoKudzu, a.TurnIntoKudzu);
CrossBool(ref result.CanScream, a.CanScream);
CrossGasses(ref result.ExudeGasses, a.ExudeGasses);
CrossGasses(ref result.ConsumeGasses, a.ConsumeGasses);
@@ -138,94 +144,93 @@ public sealed class MutationSystem : EntitySystem
// one bit gets flipped.
private void MutateFloat(ref float val, float min, float max, int bits, int totalbits, float mult)
{
// Probability that a bit flip happens for this value.
float p = mult*bits/totalbits;
p = Math.Clamp(p, 0, 1);
if (!Random(p))
{
// Probability that a bit flip happens for this value's representation in thermometer code.
float probBitflip = mult * bits / totalbits;
probBitflip = Math.Clamp(probBitflip, 0, 1);
if (!Random(probBitflip))
return;
}
// Starting number of bits that are high, between 0 and bits.
int n = (int)Math.Round((val - min) / (max - min) * bits);
// val may be outside the range of min/max due to starting prototype values, so clamp
n = Math.Clamp(n, 0, bits);
// In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
// val may be outside the range of min/max due to starting prototype values, so clamp.
valInt = Math.Clamp(valInt, 0, bits);
// Probability that the bit flip increases n.
float p_increase = 1-(float)n/bits;
int np;
if (Random(p_increase))
// The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasive it it.
// In other words, it tends to go to the middle.
float probIncrease = 1 - (float)valInt / bits;
int valIntMutated;
if (Random(probIncrease))
{
np = n + 1;
valIntMutated = valInt + 1;
}
else
{
np = n - 1;
valIntMutated = valInt - 1;
}
// Set value based on mutated thermometer code.
float nval = MathF.Min(MathF.Max((float)np/bits * (max - min) + min, min), max);
val = nval;
float valMutated = Math.Clamp((float)valIntMutated / bits * (max - min) + min, min, max);
val = valMutated;
}
private void MutateInt(ref int n, int min, int max, int bits, int totalbits, float mult)
private void MutateInt(ref int val, int min, int max, int bits, int totalbits, float mult)
{
// Probability that a bit flip happens for this value.
float p = mult*bits/totalbits;
p = Math.Clamp(p, 0, 1);
if (!Random(p))
{
// Probability that a bit flip happens for this value's representation in thermometer code.
float probBitflip = mult * bits / totalbits;
probBitflip = Math.Clamp(probBitflip, 0, 1);
if (!Random(probBitflip))
return;
}
// Probability that the bit flip increases n.
float p_increase = 1-(float)n/bits;
int np;
if (Random(p_increase))
// The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasive it it.
// In other words, it tends to go to the middle.
float probIncrease = 1 - (float)val / bits;
int valMutated;
if (Random(probIncrease))
{
np = n + 1;
valMutated = val + 1;
}
else
{
np = n - 1;
valMutated = val - 1;
}
np = Math.Min(Math.Max(np, min), max);
n = np;
valMutated = Math.Clamp(valMutated, min, max);
val = valMutated;
}
private void MutateBool(ref bool val, bool polarity, int bits, int totalbits, float mult)
{
// Probability that a bit flip happens for this value.
float p = mult * bits / totalbits;
p = Math.Clamp(p, 0, 1);
if (!Random(p))
{
float probSet = mult * bits / totalbits;
probSet = Math.Clamp(probSet, 0, 1);
if (!Random(probSet))
return;
}
val = polarity;
}
private void MutateHarvestType(ref HarvestType val, int bits, int totalbits, float mult)
{
float p = mult * bits/totalbits;
p = Math.Clamp(p, 0, 1);
if (!Random(p))
float probModify = mult * bits / totalbits;
probModify = Math.Clamp(probModify, 0, 1);
if (!Random(probModify))
return;
if (val == HarvestType.NoRepeat)
val = HarvestType.Repeat;
else if (val == HarvestType.Repeat)
val = HarvestType.SelfHarvest;
}
private void MutateGasses(ref Dictionary<Gas, float> gasses, float min, float max, int bits, int totalbits, float mult)
{
float p = mult * bits / totalbits;
p = Math.Clamp(p, 0, 1);
if (!Random(p))
float probModify = mult * bits / totalbits;
probModify = Math.Clamp(probModify, 0, 1);
if (!Random(probModify))
return;
// Add a random amount of a random gas to this gas dictionary
@@ -242,42 +247,64 @@ public sealed class MutationSystem : EntitySystem
}
private void MutateChemicals(ref Dictionary<string, SeedChemQuantity> chemicals, int max, int bits, int totalbits, float mult)
{
float probModify = mult * bits / totalbits;
probModify = Math.Clamp(probModify, 0, 1);
if (!Random(probModify))
return;
// Add a random amount of a random chemical to this set of chemicals
ReagentPrototype selectedChemical = _robustRandom.Pick(_allChemicals);
if (selectedChemical != null)
{
string chemicalId = selectedChemical.ID;
int amount = _robustRandom.Next(1, max);
SeedChemQuantity seedChemQuantity = new SeedChemQuantity();
if (chemicals.ContainsKey(chemicalId))
{
seedChemQuantity.Min = chemicals[chemicalId].Min;
seedChemQuantity.Max = chemicals[chemicalId].Max + amount;
}
else
{
seedChemQuantity.Min = 1;
seedChemQuantity.Max = 1 + amount;
}
int potencyDivisor = (int) Math.Ceiling(100.0f / seedChemQuantity.Max);
seedChemQuantity.PotencyDivisor = potencyDivisor;
chemicals[chemicalId] = seedChemQuantity;
}
}
private void MutateSpecies(ref SeedData seed, int bits, int totalbits, float mult)
{
float p = mult * bits / totalbits;
p = Math.Clamp(p, 0, 1);
if (!Random(p))
return;
// Add a random amount of a random chemical to this set of chemicals
ReagentPrototype selected_chemical = _robustRandom.Pick(_allChemicals);
if (selected_chemical != null)
if (seed.MutationPrototypes.Count == 0)
return;
var targetProto = _robustRandom.Pick(seed.MutationPrototypes);
_prototypeManager.TryIndex(targetProto, out SeedPrototype? protoSeed);
if (protoSeed == null)
{
string chemical_id = selected_chemical.ID;
int amount = _robustRandom.Next(1, max);
SeedChemQuantity seed_chem_quantity = new SeedChemQuantity();
if (chemicals.ContainsKey(chemical_id))
{
seed_chem_quantity.Min = chemicals[chemical_id].Min;
seed_chem_quantity.Max = chemicals[chemical_id].Max + amount;
int potency = (int) Math.Ceiling(100.0f / (float) seed_chem_quantity.Max);
seed_chem_quantity.PotencyDivisor = potency;
chemicals[chemical_id] = seed_chem_quantity;
}
else
{
seed_chem_quantity.Min = 1;
seed_chem_quantity.Max = 1 + amount;
int potency = (int) Math.Ceiling(100.0f / (float) seed_chem_quantity.Max);
seed_chem_quantity.PotencyDivisor = potency;
chemicals.Add(chemical_id, seed_chem_quantity);
}
Log.Error($"Seed prototype could not be found: {targetProto}!");
return;
}
var oldSeed = seed.Clone();
seed = protoSeed.Clone();
seed.Potency = oldSeed.Potency;
seed.Yield = oldSeed.Yield;
}
private Color RandomColor(Color color, int bits, int totalbits, float mult)
{
float p = mult*bits/totalbits;
if (Random(p))
float probModify = mult * bits / totalbits;
if (Random(probModify))
{
var colors = new List<Color>{
Color.White,
@@ -296,33 +323,33 @@ public sealed class MutationSystem : EntitySystem
private void CrossChemicals(ref Dictionary<string, SeedChemQuantity> val, Dictionary<string, SeedChemQuantity> other)
{
// Go through chemicals from the pollen in swab
foreach (var other_chem in other)
foreach (var otherChem in other)
{
// if both have same chemical, randomly pick potency ratio from the two.
if (val.ContainsKey(other_chem.Key))
if (val.ContainsKey(otherChem.Key))
{
val[other_chem.Key] = Random(0.5f) ? other_chem.Value : val[other_chem.Key];
val[otherChem.Key] = Random(0.5f) ? otherChem.Value : val[otherChem.Key];
}
// if target plant doesn't have this chemical, has 50% chance to add it.
else
{
if (Random(0.5f))
{
val.Add(other_chem.Key, other_chem.Value);
val.Add(otherChem.Key, otherChem.Value);
}
}
}
// if the target plant has chemical that the pollen in swab does not, 50% chance to remove it.
foreach (var this_chem in val)
foreach (var thisChem in val)
{
if (!other.ContainsKey(this_chem.Key))
if (!other.ContainsKey(thisChem.Key))
{
if (Random(0.5f))
{
if (val.Count > 1)
{
val.Remove(this_chem.Key);
val.Remove(thisChem.Key);
}
}
}
@@ -332,30 +359,30 @@ public sealed class MutationSystem : EntitySystem
private void CrossGasses(ref Dictionary<Gas, float> val, Dictionary<Gas, float> other)
{
// Go through gasses from the pollen in swab
foreach (var other_gas in other)
foreach (var otherGas in other)
{
// if both have same gas, randomly pick ammount from the two.
if (val.ContainsKey(other_gas.Key))
if (val.ContainsKey(otherGas.Key))
{
val[other_gas.Key] = Random(0.5f) ? other_gas.Value : val[other_gas.Key];
val[otherGas.Key] = Random(0.5f) ? otherGas.Value : val[otherGas.Key];
}
// if target plant doesn't have this gas, has 50% chance to add it.
else
{
if (Random(0.5f))
{
val.Add(other_gas.Key, other_gas.Value);
val.Add(otherGas.Key, otherGas.Value);
}
}
}
// if the target plant has gas that the pollen in swab does not, 50% chance to remove it.
foreach (var this_gas in val)
foreach (var thisGas in val)
{
if (!other.ContainsKey(this_gas.Key))
if (!other.ContainsKey(thisGas.Key))
{
if (Random(0.5f))
{
val.Remove(this_gas.Key);
val.Remove(thisGas.Key);
}
}
}

View File

@@ -347,6 +347,7 @@ public sealed class PlantHolderSystem : EntitySystem
if (component.MutationLevel > 0)
{
Mutate(uid, Math.Min(component.MutationLevel, 25), component);
component.UpdateSpriteAfterUpdate = true;
component.MutationLevel = 0;
}
@@ -844,7 +845,7 @@ public sealed class PlantHolderSystem : EntitySystem
if (component.Seed != null)
{
EnsureUniqueSeed(uid, component);
_mutation.MutateSeed(component.Seed, severity);
_mutation.MutateSeed(ref component.Seed, severity);
}
}

View File

@@ -3,6 +3,8 @@ using Content.Server.Roles;
using Content.Server.Roles.Jobs;
using Content.Shared.CharacterInfo;
using Content.Shared.Objectives;
using Content.Shared.Objectives.Components;
using Content.Shared.Objectives.Systems;
namespace Content.Server.CharacterInfo;
@@ -11,6 +13,7 @@ public sealed class CharacterInfoSystem : EntitySystem
[Dependency] private readonly JobSystem _jobs = default!;
[Dependency] private readonly MindSystem _minds = default!;
[Dependency] private readonly RoleSystem _roles = default!;
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
public override void Initialize()
{
@@ -27,7 +30,7 @@ public sealed class CharacterInfoSystem : EntitySystem
var entity = args.SenderSession.AttachedEntity.Value;
var conditions = new Dictionary<string, List<ConditionInfo>>();
var objectives = new Dictionary<string, List<ObjectiveInfo>>();
var jobTitle = "No Profession";
string? briefing = null;
if (_minds.TryGetMind(entity, out var mindId, out var mind))
@@ -35,13 +38,15 @@ public sealed class CharacterInfoSystem : EntitySystem
// Get objectives
foreach (var objective in mind.AllObjectives)
{
if (!conditions.ContainsKey(objective.Prototype.Issuer))
conditions[objective.Prototype.Issuer] = new List<ConditionInfo>();
foreach (var condition in objective.Conditions)
{
conditions[objective.Prototype.Issuer].Add(new ConditionInfo(condition.Title,
condition.Description, condition.Icon, condition.Progress));
}
var info = _objectives.GetInfo(objective, mindId, mind);
if (info == null)
continue;
// group objectives by their issuer
var issuer = Comp<ObjectiveComponent>(objective).Issuer;
if (!objectives.ContainsKey(issuer))
objectives[issuer] = new List<ObjectiveInfo>();
objectives[issuer].Add(info.Value);
}
if (_jobs.MindTryGetJobName(mindId, out var jobName))
@@ -51,6 +56,6 @@ public sealed class CharacterInfoSystem : EntitySystem
briefing = _roles.MindGetBriefing(mindId);
}
RaiseNetworkEvent(new CharacterInfoEvent(GetNetEntity(entity), jobTitle, conditions, briefing), args.SenderSession);
RaiseNetworkEvent(new CharacterInfoEvent(GetNetEntity(entity), jobTitle, objectives, briefing), args.SenderSession);
}
}

View File

@@ -136,7 +136,7 @@ public sealed class DefusableSystem : SharedDefusableSystem
{
_trigger.HandleTimerTrigger(
uid,
null,
user,
timerTrigger.Delay,
timerTrigger.BeepInterval,
timerTrigger.InitialBeepDelay,
@@ -147,14 +147,12 @@ public sealed class DefusableSystem : SharedDefusableSystem
RaiseLocalEvent(uid, new BombArmedEvent(uid));
_appearance.SetData(uid, DefusableVisuals.Active, comp.Activated);
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(user):entity} begun a countdown on {ToPrettyString(uid):entity}");
if (TryComp<WiresPanelComponent>(uid, out var wiresPanelComponent))
_wiresSystem.TogglePanel(uid, wiresPanelComponent, false);
}
public void TryDetonateBomb(EntityUid uid, DefusableComponent comp)
public void TryDetonateBomb(EntityUid uid, EntityUid detonator, DefusableComponent comp)
{
if (!comp.Activated)
return;
@@ -163,13 +161,10 @@ public sealed class DefusableSystem : SharedDefusableSystem
RaiseLocalEvent(uid, new BombDetonatedEvent(uid));
_explosion.TriggerExplosive(uid);
_explosion.TriggerExplosive(uid, user:detonator);
QueueDel(uid);
_appearance.SetData(uid, DefusableVisuals.Active, comp.Activated);
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(uid):entity} has been detonated.");
}
public void TryDefuseBomb(EntityUid uid, DefusableComponent comp)
@@ -204,8 +199,6 @@ public sealed class DefusableSystem : SharedDefusableSystem
_transform.Unanchor(uid, xform);
_appearance.SetData(uid, DefusableVisuals.Active, comp.Activated);
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(uid):entity} has been defused!");
}
// jesus christ
@@ -249,9 +242,6 @@ public sealed class DefusableSystem : SharedDefusableSystem
_trigger.TryDelay(wire.Owner, 30f);
_popup.PopupEntity(Loc.GetString("defusable-popup-wire-chirp", ("name", wire.Owner)), wire.Owner);
comp.DelayWireUsed = true;
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(user):user} pulsed the DeLAY wire of {ToPrettyString(wire.Owner):entity}.");
}
public bool ProceedWireCut(EntityUid user, Wire wire, DefusableComponent comp)
@@ -262,9 +252,6 @@ public sealed class DefusableSystem : SharedDefusableSystem
_popup.PopupEntity(Loc.GetString("defusable-popup-wire-proceed-pulse", ("name", wire.Owner)), wire.Owner);
SetDisplayTime(comp, false);
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(user):user} cut the PRoCeeD wire of {ToPrettyString(wire.Owner):entity}.");
comp.ProceedWireCut = true;
return true;
}
@@ -277,9 +264,6 @@ public sealed class DefusableSystem : SharedDefusableSystem
_trigger.TryDelay(wire.Owner, -15f);
}
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(user):user} pulsed the PRoCeeD wire of {ToPrettyString(wire.Owner):entity}.");
_popup.PopupEntity(Loc.GetString("defusable-popup-wire-proceed-pulse", ("name", wire.Owner)), wire.Owner);
}
@@ -295,9 +279,6 @@ public sealed class DefusableSystem : SharedDefusableSystem
$"{ToPrettyString(user):user} has defused {ToPrettyString(wire.Owner):entity}!");
}
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(user):user} cut the LIVE wire of {ToPrettyString(wire.Owner):entity}.");
return true;
}
@@ -313,15 +294,11 @@ public sealed class DefusableSystem : SharedDefusableSystem
_trigger.TryDelay(wire.Owner, 30f);
_popup.PopupEntity(Loc.GetString("defusable-popup-wire-chirp", ("name", wire.Owner)), wire.Owner);
comp.ActivatedWireUsed = true;
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(user):user} pulsed the LIVE wire of {ToPrettyString(wire.Owner):entity}.");
}
}
else
{
TryStartCountdown(wire.Owner, user, comp);
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(user):user} pulsed the LIVE wire of {ToPrettyString(wire.Owner):entity} and begun the countdown.");
}
}
@@ -329,15 +306,11 @@ public sealed class DefusableSystem : SharedDefusableSystem
{
if (comp.Activated)
{
EntityManager.System<DefusableSystem>().TryDetonateBomb(wire.Owner, comp);
_adminLogger.Add(LogType.Explosion, LogImpact.Extreme,
$"{ToPrettyString(user):user} cut the BOOM wire of {ToPrettyString(wire.Owner):entity} and caused it to detonate!");
TryDetonateBomb(wire.Owner, user, comp);
}
else
{
EntityManager.System<DefusableSystem>().SetUsable(comp, false);
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(user):user} cut the BOOM wire of {ToPrettyString(wire.Owner):entity}.");
SetUsable(comp, false);
}
return true;
}
@@ -347,9 +320,6 @@ public sealed class DefusableSystem : SharedDefusableSystem
if (comp is { Activated: false, Usable: false })
{
SetUsable(comp, true);
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(user):user} mended the BOOM wire of {ToPrettyString(wire.Owner):entity}.");
}
// you're already dead lol
return true;
@@ -359,10 +329,8 @@ public sealed class DefusableSystem : SharedDefusableSystem
{
if (comp.Activated)
{
TryDetonateBomb(wire.Owner, comp);
TryDetonateBomb(wire.Owner, user, comp);
}
_adminLogger.Add(LogType.Explosion, LogImpact.Extreme,
$"{ToPrettyString(user):user} pulsed the BOOM wire of {ToPrettyString(wire.Owner):entity} and caused it to detonate!");
}
public bool BoltWireMend(EntityUid user, Wire wire, DefusableComponent comp)
@@ -374,9 +342,6 @@ public sealed class DefusableSystem : SharedDefusableSystem
_audio.PlayPvs(comp.BoltSound, wire.Owner);
_popup.PopupEntity(Loc.GetString("defusable-popup-wire-bolt-pulse", ("name", wire.Owner)), wire.Owner);
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(user):user} mended the BOLT wire of {ToPrettyString(wire.Owner):entity}!");
return true;
}
@@ -389,18 +354,12 @@ public sealed class DefusableSystem : SharedDefusableSystem
_audio.PlayPvs(comp.BoltSound, wire.Owner);
_popup.PopupEntity(Loc.GetString("defusable-popup-wire-bolt-pulse", ("name", wire.Owner)), wire.Owner);
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(user):user} cut the BOLT wire of {ToPrettyString(wire.Owner):entity}!");
return true;
}
public void BoltWirePulse(EntityUid user, Wire wire, DefusableComponent comp)
{
_popup.PopupEntity(Loc.GetString("defusable-popup-wire-bolt-pulse", ("name", wire.Owner)), wire.Owner);
_adminLogger.Add(LogType.Explosion, LogImpact.High,
$"{ToPrettyString(user):user} pulsed the BOLT wire of {ToPrettyString(wire.Owner):entity}!");
}
#endregion

View File

@@ -9,6 +9,7 @@ using Content.Shared.Doors.Systems;
using Content.Shared.Interaction;
using Robust.Server.GameObjects;
using Content.Shared.Wires;
using Robust.Shared.Prototypes;
namespace Content.Server.Doors.Systems;
@@ -17,6 +18,7 @@ public sealed class AirlockSystem : SharedAirlockSystem
[Dependency] private readonly WiresSystem _wiresSystem = default!;
[Dependency] private readonly PowerReceiverSystem _power = default!;
[Dependency] private readonly DoorBoltSystem _bolts = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public override void Initialize()
{
@@ -150,8 +152,11 @@ public sealed class AirlockSystem : SharedAirlockSystem
private void OnActivate(EntityUid uid, AirlockComponent component, ActivateInWorldEvent args)
{
if (TryComp<WiresPanelComponent>(uid, out var panel) && panel.Open && panel.WiresAccessible
&& TryComp<ActorComponent>(args.User, out var actor))
if (TryComp<WiresPanelComponent>(uid, out var panel) &&
panel.Open &&
_prototypeManager.TryIndex<WiresPanelSecurityLevelPrototype>(panel.CurrentSecurityLevelID, out var securityLevelPrototype) &&
securityLevelPrototype.WiresAccessible &&
TryComp<ActorComponent>(args.User, out var actor))
{
_wiresSystem.OpenUserInterface(uid, actor.PlayerSession);
args.Handled = true;

View File

@@ -27,30 +27,59 @@ public sealed class EmpSystem : SharedEmpSystem
SubscribeLocalEvent<EmpDisabledComponent, SurveillanceCameraSetActiveAttemptEvent>(OnCameraSetActive);
}
/// <summary>
/// Triggers an EMP pulse at the given location, by first raising an <see cref="EmpAttemptEvent"/>, then a raising <see cref="EmpPulseEvent"/> on all entities in range.
/// </summary>
/// <param name="coordinates">The location to trigger the EMP pulse at.</param>
/// <param name="range">The range of the EMP pulse.</param>
/// <param name="energyConsumption">The amount of energy consumed by the EMP pulse.</param>
/// <param name="duration">The duration of the EMP effects.</param>
public void EmpPulse(MapCoordinates coordinates, float range, float energyConsumption, float duration)
{
foreach (var uid in _lookup.GetEntitiesInRange(coordinates, range))
{
var attemptEv = new EmpAttemptEvent();
RaiseLocalEvent(uid, attemptEv);
if (attemptEv.Cancelled)
continue;
var ev = new EmpPulseEvent(energyConsumption, false, false);
RaiseLocalEvent(uid, ref ev);
if (ev.Affected)
{
Spawn(EmpDisabledEffectPrototype, Transform(uid).Coordinates);
}
if (ev.Disabled)
{
var disabled = EnsureComp<EmpDisabledComponent>(uid);
disabled.DisabledUntil = Timing.CurTime + TimeSpan.FromSeconds(duration);
}
TryEmpEffects(uid, energyConsumption, duration);
}
Spawn(EmpPulseEffectPrototype, coordinates);
}
/// <summary>
/// Attempts to apply the effects of an EMP pulse onto an entity by first raising an <see cref="EmpAttemptEvent"/>, followed by raising a <see cref="EmpPulseEvent"/> on it.
/// </summary>
/// <param name="uid">The entity to apply the EMP effects on.</param>
/// <param name="energyConsumption">The amount of energy consumed by the EMP.</param>
/// <param name="duration">The duration of the EMP effects.</param>
public void TryEmpEffects(EntityUid uid, float energyConsumption, float duration)
{
var attemptEv = new EmpAttemptEvent();
RaiseLocalEvent(uid, attemptEv);
if (attemptEv.Cancelled)
return;
DoEmpEffects(uid, energyConsumption, duration);
}
/// <summary>
/// Applies the effects of an EMP pulse onto an entity by raising a <see cref="EmpPulseEvent"/> on it.
/// </summary>
/// <param name="uid">The entity to apply the EMP effects on.</param>
/// <param name="energyConsumption">The amount of energy consumed by the EMP.</param>
/// <param name="duration">The duration of the EMP effects.</param>
public void DoEmpEffects(EntityUid uid, float energyConsumption, float duration)
{
var ev = new EmpPulseEvent(energyConsumption, false, false, TimeSpan.FromSeconds(duration));
RaiseLocalEvent(uid, ref ev);
if (ev.Affected)
{
Spawn(EmpDisabledEffectPrototype, Transform(uid).Coordinates);
}
if (ev.Disabled)
{
var disabled = EnsureComp<EmpDisabledComponent>(uid);
disabled.DisabledUntil = Timing.CurTime + TimeSpan.FromSeconds(duration);
}
}
public override void Update(float frameTime)
{
base.Update(frameTime);
@@ -113,7 +142,7 @@ public sealed partial class EmpAttemptEvent : CancellableEntityEventArgs
}
[ByRefEvent]
public record struct EmpPulseEvent(float EnergyConsumption, bool Affected, bool Disabled);
public record struct EmpPulseEvent(float EnergyConsumption, bool Affected, bool Disabled, TimeSpan Duration);
[ByRefEvent]
public record struct EmpDisabledRemoved();

View File

@@ -152,7 +152,7 @@ public sealed partial class ExplosionSystem : EntitySystem
continue;
var ev = new GetExplosionResistanceEvent(explosionType.ID);
RaiseLocalEvent(uid, ev, false);
RaiseLocalEvent(uid, ref ev);
damagePerIntensity += value * Math.Max(0, ev.DamageCoefficient);
}

View File

@@ -390,7 +390,7 @@ public sealed partial class ExplosionSystem : EntitySystem
if (damage != null && damageQuery.TryGetComponent(uid, out var damageable))
{
var ev = new GetExplosionResistanceEvent(id);
RaiseLocalEvent(uid, ev, false);
RaiseLocalEvent(uid, ref ev, false);
ev.DamageCoefficient = Math.Max(0, ev.DamageCoefficient);

View File

@@ -76,7 +76,7 @@ public sealed partial class ExplosionSystem : EntitySystem
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<ExplosionResistanceComponent, InventoryRelayedEvent<GetExplosionResistanceEvent>>(RelayedResistance);
SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);
@@ -112,10 +112,17 @@ public sealed partial class ExplosionSystem : EntitySystem
_pathfindingSystem.PauseUpdating = false;
}
private void OnGetResistance(EntityUid uid, ExplosionResistanceComponent component, GetExplosionResistanceEvent args)
private void RelayedResistance(EntityUid uid, ExplosionResistanceComponent component,
InventoryRelayedEvent<GetExplosionResistanceEvent> args)
{
var a = args.Args;
OnGetResistance(uid, component, ref a);
}
private void OnGetResistance(EntityUid uid, ExplosionResistanceComponent component, ref GetExplosionResistanceEvent args)
{
args.DamageCoefficient *= component.DamageCoefficient;
if (component.Modifiers.TryGetValue(args.ExplotionPrototype, out var modifier))
if (component.Modifiers.TryGetValue(args.ExplosionPrototype, out var modifier))
args.DamageCoefficient *= modifier;
}

View File

@@ -123,7 +123,7 @@ public sealed class DrainSystem : SharedDrainSystem
if (!drain.AutoDrain)
{
_ambientSoundSystem.SetAmbience(drain.Owner, false);
return;
continue;
}
if (!managerQuery.TryGetComponent(drain.Owner, out var manager))

View File

@@ -72,13 +72,14 @@ public sealed partial class GameTicker
if (data.State is not ReplayRecordState state)
return;
if (state.MoveToPath != null)
{
_sawmillReplays.Info($"Moving replay into final position: {state.MoveToPath}");
if (state.MoveToPath == null)
return;
_taskManager.BlockWaitOnTask(_replays.WaitWriteTasks());
data.Directory.Rename(data.Path, state.MoveToPath.Value);
}
_sawmillReplays.Info($"Moving replay into final position: {state.MoveToPath}");
_taskManager.BlockWaitOnTask(_replays.WaitWriteTasks());
DebugTools.Assert(!_replays.IsWriting());
data.Directory.CreateDir(state.MoveToPath.Value.Directory);
data.Directory.Rename(data.Path, state.MoveToPath.Value);
}
private ResPath GetAutoReplayPath()

View File

@@ -1,6 +1,5 @@
using Content.Server.Ninja.Systems;
using Content.Shared.Communications;
using Content.Shared.Objectives;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
@@ -18,9 +17,9 @@ public sealed partial class NinjaRuleComponent : Component
public List<EntityUid> Minds = new();
/// <summary>
/// List of objective prototype ids to add
/// List of objective entity prototypes to add
/// </summary>
[DataField("objectives", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<ObjectivePrototype>))]
[DataField("objectives", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public List<string> Objectives = new();
/// <summary>

View File

@@ -154,11 +154,10 @@ public sealed partial class NukeopsRuleComponent : Component
/// <summary>
/// Players who played as an operative at some point in the round.
/// Stores the session as well as the entity name
/// Stores the mind as well as the entity name
/// </summary>
/// todo: don't store sessions, dingus
[DataField("operativePlayers")]
public Dictionary<string, ICommonSession> OperativePlayers = new();
public Dictionary<string, EntityUid> OperativePlayers = new();
[DataField("faction", customTypeSerializer: typeof(PrototypeIdSerializer<NpcFactionPrototype>), required: true)]
public string Faction = default!;

View File

@@ -56,29 +56,29 @@ namespace Content.Server.GameTicking.Rules;
public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
{
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergency = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IServerPreferencesManager _prefs = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPlayerManager _playerSystem = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergency = default!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
[Dependency] private readonly StationSpawningSystem _stationSpawningSystem = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly MapLoaderSystem _map = default!;
[Dependency] private readonly ShuttleSystem _shuttle = default!;
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly StoreSystem _storeSystem = default!;
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly WarDeclaratorSystem _warDeclaratorSystem = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly ShuttleSystem _shuttle = default!;
[Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
[Dependency] private readonly StoreSystem _store = default!;
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly WarDeclaratorSystem _warDeclarator = default!;
[ValidatePrototypeId<CurrencyPrototype>]
private const string TelecrystalCurrencyPrototype = "Telecrystal";
@@ -123,7 +123,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
if (!GameTicker.IsGameRuleAdded(ruleEnt, gameRule))
continue;
var found = nukeops.OperativePlayers.Values.Any(v => v.AttachedEntity == opUid);
var found = nukeops.OperativePlayers.Values.Any(v => v == opUid);
if (found)
{
comps = (nukeops, gameRule);
@@ -194,9 +194,9 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
var nukieRule = comps.Value.Item1;
nukieRule.WarDeclaredTime = _gameTiming.CurTime;
_chatSystem.DispatchGlobalAnnouncement(msg, title, announcementSound: announcementSound, colorOverride: colorOverride);
_chat.DispatchGlobalAnnouncement(msg, title, announcementSound: announcementSound, colorOverride: colorOverride);
DistributeExtraTC(nukieRule);
_warDeclaratorSystem.RefreshAllUI(comps.Value.Item1, comps.Value.Item2);
_warDeclarator.RefreshAllUI(comps.Value.Item1, comps.Value.Item2);
}
private void DistributeExtraTC(NukeopsRuleComponent nukieRule)
@@ -213,7 +213,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
if (Transform(uid).MapID != Transform(nukieRule.NukieOutpost.Value).MapID) // Will receive bonus TC only on their start outpost
continue;
_storeSystem.TryAddCurrency(new () { { TelecrystalCurrencyPrototype, nukieRule.WarTCAmountPerNukie } }, uid, component);
_store.TryAddCurrency(new () { { TelecrystalCurrencyPrototype, nukieRule.WarTCAmountPerNukie } }, uid, component);
var msg = Loc.GetString("store-currency-war-boost-given", ("target", uid));
_popupSystem.PopupEntity(msg, uid);
@@ -229,13 +229,11 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
continue;
// If entity has a prior mind attached, add them to the players list.
if (!_mindSystem.TryGetMind(uid, out _, out var mind))
if (!_mind.TryGetMind(uid, out var mind, out _))
continue;
var session = mind?.Session;
var name = MetaData(uid).EntityName;
if (session != null)
nukeops.OperativePlayers.Add(name, session);
nukeops.OperativePlayers.Add(name, mind);
RemComp<PacifiedComponent>(uid); // Corvax-DionaPacifist: Allow dionas nukes to harm
}
}
@@ -347,7 +345,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
while (query.MoveNext(out _, out var nukeops, out var actor))
{
_chatManager.DispatchServerMessage(actor.PlayerSession, Loc.GetString("nukeops-welcome", ("station", component.TargetStation.Value)));
_audioSystem.PlayGlobal(nukeops.GreetSoundNotification, actor.PlayerSession);
_audio.PlayGlobal(nukeops.GreetSoundNotification, actor.PlayerSession);
filter.AddPlayer(actor.PlayerSession);
}
}
@@ -395,14 +393,28 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
}
var allAlive = true;
foreach (var (_, state) in EntityQuery<NukeOperativeComponent, MobStateComponent>())
var mindQuery = GetEntityQuery<MindComponent>();
var mobStateQuery = GetEntityQuery<MobStateComponent>();
foreach (var (_, mindId) in component.OperativePlayers)
{
if (state.CurrentState is MobState.Alive)
// mind got deleted somehow so ignore it
if (!mindQuery.TryGetComponent(mindId, out var mind))
continue;
// check if player got gibbed or ghosted or something - count as dead
if (mind.OwnedEntity != null &&
// if the player somehow isn't a mob anymore that also counts as dead
mobStateQuery.TryGetComponent(mind.OwnedEntity.Value, out var mobState) &&
// have to be alive, not crit or dead
mobState.CurrentState is MobState.Alive)
{
continue;
}
allAlive = false;
break;
}
// If all nuke ops were alive at the end of the round,
// the nuke ops win. This is to prevent people from
// running away the moment nuke ops appear.
@@ -445,6 +457,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
private void OnRoundEndText(RoundEndTextAppendEvent ev)
{
var mindQuery = GetEntityQuery<MindComponent>();
foreach (var nukeops in EntityQuery<NukeopsRuleComponent>())
{
var winText = Loc.GetString($"nukeops-{nukeops.WinType.ToString().ToLower()}");
@@ -459,10 +472,16 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
}
ev.AddLine(Loc.GetString("nukeops-list-start"));
foreach (var (name, session) in nukeops.OperativePlayers)
foreach (var (name, mindId) in nukeops.OperativePlayers)
{
var listing = Loc.GetString("nukeops-list-name", ("name", name), ("user", session.Name));
ev.AddLine(listing);
if (mindQuery.TryGetComponent(mindId, out var mind) && mind.Session != null)
{
ev.AddLine(Loc.GetString("nukeops-list-name-user", ("name", name), ("user", mind.Session.Name)));
}
else
{
ev.AddLine(Loc.GetString("nukeops-list-name", ("name", name)));
}
}
}
}
@@ -550,7 +569,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
private void OnMobStateChanged(EntityUid uid, NukeOperativeComponent component, MobStateChangedEvent ev)
{
if(ev.NewMobState == MobState.Dead)
if (ev.NewMobState == MobState.Dead)
CheckRoundShouldEnd();
}
@@ -603,7 +622,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
}
}
var numNukies = MathHelper.Clamp(_playerSystem.PlayerCount / playersPerOperative, 1, maxOperatives);
var numNukies = MathHelper.Clamp(_playerManager.PlayerCount / playersPerOperative, 1, maxOperatives);
for (var i = 0; i < numNukies; i++)
{
@@ -695,11 +714,14 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
{
ev.PlayerPool.Remove(session);
GameTicker.PlayerJoinGame(session);
if (!_mind.TryGetMind(session, out var mind, out _))
continue;
var name = session.AttachedEntity == null
? string.Empty
: MetaData(session.AttachedEntity.Value).EntityName;
// TODO: Fix this being able to have duplicates
nukeops.OperativePlayers[name] = session;
: Name(session.AttachedEntity.Value);
nukeops.OperativePlayers[name] = mind;
}
}
}
@@ -735,7 +757,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
private void OnMindAdded(EntityUid uid, NukeOperativeComponent component, MindAddedMessage args)
{
if (!_mindSystem.TryGetMind(uid, out var mindId, out var mind))
if (!_mind.TryGetMind(uid, out var mindId, out var mind))
return;
foreach (var (nukeops, gameRule) in EntityQuery<NukeopsRuleComponent, GameRuleComponent>())
@@ -750,13 +772,11 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
if (mind.Session is not { } playerSession)
return;
if (nukeops.OperativePlayers.ContainsValue(playerSession))
if (nukeops.OperativePlayers.ContainsValue(mindId))
return;
var name = MetaData(uid).EntityName;
nukeops.OperativePlayers.Add(name, playerSession);
_warDeclaratorSystem.RefreshAllUI(nukeops, gameRule);
nukeops.OperativePlayers.Add(Name(uid), mindId);
_warDeclarator.RefreshAllUI(nukeops, gameRule);
if (GameTicker.RunLevel != GameRunLevel.InRound)
return;
@@ -766,7 +786,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
_chatManager.DispatchServerMessage(playerSession, Loc.GetString("nukeops-welcome", ("station", nukeops.TargetStation.Value)));
// Notificate player about new role assignment
_audioSystem.PlayGlobal(component.GreetSoundNotification, playerSession);
_audio.PlayGlobal(component.GreetSoundNotification, playerSession);
}
}
}
@@ -868,11 +888,11 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
if (profile != null)
{
_humanoidSystem.LoadProfile(mob, profile);
_humanoid.LoadProfile(mob, profile);
}
if (component.StartingGearPrototypes.TryGetValue(gear, out var gearPrototype))
_stationSpawningSystem.EquipStartingGear(mob, gearPrototype, profile);
_stationSpawning.EquipStartingGear(mob, gearPrototype, profile);
_npcFaction.RemoveFaction(mob, "NanoTrasen", false);
_npcFaction.AddFaction(mob, "Syndicate");
@@ -887,7 +907,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
var spawns = new List<EntityCoordinates>();
// Forgive me for hardcoding prototypes
foreach (var (_, meta, xform) in EntityManager.EntityQuery<SpawnPointComponent, MetaDataComponent, TransformComponent>(true))
foreach (var (_, meta, xform) in EntityQuery<SpawnPointComponent, MetaDataComponent, TransformComponent>(true))
{
if (meta.EntityPrototype?.ID != component.SpawnPointPrototype)
continue;
@@ -901,7 +921,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
if (spawns.Count == 0)
{
spawns.Add(EntityManager.GetComponent<TransformComponent>(outpostUid).Coordinates);
spawns.Add(Transform(outpostUid).Coordinates);
Logger.WarningS("nukies", $"Fell back to default spawn for nukies!");
}
@@ -919,17 +939,17 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
species = _prototypeManager.Index<SpeciesPrototype>(SharedHumanoidAppearanceSystem.DefaultSpecies);
}
var mob = EntityManager.SpawnEntity(species.Prototype, _random.Pick(spawns));
var mob = Spawn(species.Prototype, _random.Pick(spawns));
SetupOperativeEntity(mob, spawnDetails.Name, spawnDetails.Gear, profile, component);
var newMind = _mindSystem.CreateMind(session.UserId, spawnDetails.Name);
_mindSystem.SetUserId(newMind, session.UserId);
var newMind = _mind.CreateMind(session.UserId, spawnDetails.Name);
_mind.SetUserId(newMind, session.UserId);
_roles.MindAddRole(newMind, new NukeopsRoleComponent { PrototypeId = spawnDetails.Role });
_mindSystem.TransferTo(newMind, mob);
_mind.TransferTo(newMind, mob);
}
else if (addSpawnPoints)
{
var spawnPoint = EntityManager.SpawnEntity(component.GhostSpawnPointProto, _random.Pick(spawns));
var spawnPoint = Spawn(component.GhostSpawnPointProto, _random.Pick(spawns));
var ghostRole = EnsureComp<GhostRoleComponent>(spawnPoint);
EnsureComp<GhostRoleMobSpawnerComponent>(spawnPoint);
ghostRole.RoleName = Loc.GetString(nukeOpsAntag.Name);
@@ -957,7 +977,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
var playersPerOperative = component.PlayersPerOperative;
var maxOperatives = component.MaxOperatives;
var playerPool = _playerSystem.ServerSessions.ToList();
var playerPool = _playerManager.ServerSessions.ToList();
var numNukies = MathHelper.Clamp(playerPool.Count / playersPerOperative, 1, maxOperatives);
var operatives = new List<IPlayerSession>();
@@ -1052,7 +1072,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
nukeops.LeftOutpost = true;
if (TryGetRuleFromGrid(gridUid.Value, out var comps))
_warDeclaratorSystem.RefreshAllUI(comps.Value.Item1, comps.Value.Item2);
_warDeclarator.RefreshAllUI(comps.Value.Item1, comps.Value.Item2);
}
}
}
@@ -1098,13 +1118,13 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
var query = EntityQuery<NukeOperativeComponent, MindContainerComponent, MetaDataComponent>(true);
foreach (var (_, mindComp, metaData) in query)
{
if (!mindComp.HasMind || !_mindSystem.TryGetSession(mindComp.Mind.Value, out var session))
if (!mindComp.HasMind)
continue;
component.OperativePlayers.Add(metaData.EntityName, session);
component.OperativePlayers.Add(metaData.EntityName, mindComp.Mind.Value);
}
if (GameTicker.RunLevel == GameRunLevel.InRound)
SpawnOperativesForGhostRoles(uid, component);
}
}

View File

@@ -12,6 +12,7 @@ using Content.Shared.CCVar;
using Content.Shared.Dataset;
using Content.Shared.Mind;
using Content.Shared.Mobs.Systems;
using Content.Shared.Objectives.Components;
using Content.Shared.PDA;
using Content.Shared.Preferences;
using Content.Shared.Roles;
@@ -299,8 +300,8 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
if (objective == null)
continue;
if (_mindSystem.TryAddObjective(mindId, mind, objective))
difficulty += objective.Difficulty;
_mindSystem.AddObjective(mindId, mind, objective.Value);
difficulty += Comp<ObjectiveComponent>(objective.Value).Difficulty;
}
}

View File

@@ -11,30 +11,45 @@ namespace Content.Server.Gateway.Components;
public sealed partial class GatewayComponent : Component
{
/// <summary>
/// Sound to play when opening or closing the portal.
/// Sound to play when opening the portal.
/// </summary>
/// <remarks>
/// Originally named PortalSound as it was used for opening and closing.
/// </remarks>
[DataField("portalSound")]
public SoundSpecifier PortalSound = new SoundPathSpecifier("/Audio/Effects/Lightning/lightningbolt.ogg");
public SoundSpecifier OpenSound = new SoundPathSpecifier("/Audio/Effects/Lightning/lightningbolt.ogg");
/// <summary>
/// Sound to play when closing the portal.
/// </summary>
[DataField]
public SoundSpecifier CloseSound = new SoundPathSpecifier("/Audio/Effects/Lightning/lightningbolt.ogg");
/// <summary>
/// Sound to play when trying to open or close the portal and missing access.
/// </summary>
[DataField]
public SoundSpecifier AccessDeniedSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
/// <summary>
/// Every other gateway destination on the server.
/// </summary>
/// <remarks>
/// Added on
/// Added on startup and when a new destination portal is created.
/// </remarks>
[ViewVariables]
[DataField]
public HashSet<EntityUid> Destinations = new();
/// <summary>
/// The time at which the portal will be closed.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("nextClose", customTypeSerializer:typeof(TimeOffsetSerializer))]
[ViewVariables(VVAccess.ReadWrite), DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan NextClose;
/// <summary>
/// The time at which the portal was last opened.
/// Only used for UI.
/// </summary>
[ViewVariables]
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan LastOpen;
}

View File

@@ -13,30 +13,36 @@ public sealed partial class GatewayDestinationComponent : Component
/// Whether this destination is shown in the gateway ui.
/// If you are making a gateway for an admeme set this once you are ready for players to select it.
/// </summary>
[DataField("enabled"), ViewVariables(VVAccess.ReadWrite)]
[DataField, ViewVariables(VVAccess.ReadWrite)]
public bool Enabled;
/// <summary>
/// Name as it shows up on the ui of station gateways.
/// </summary>
[DataField("name"), ViewVariables(VVAccess.ReadWrite)]
[DataField, ViewVariables(VVAccess.ReadWrite)]
public string Name = string.Empty;
/// <summary>
/// Time at which this destination is ready to be linked to.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("nextReady", customTypeSerializer:typeof(TimeOffsetSerializer))]
[ViewVariables(VVAccess.ReadWrite), DataField(customTypeSerializer:typeof(TimeOffsetSerializer))]
public TimeSpan NextReady;
/// <summary>
/// How long the portal will be open for after linking.
/// </summary>
[DataField("openTime"), ViewVariables(VVAccess.ReadWrite)]
[DataField, ViewVariables(VVAccess.ReadWrite)]
public TimeSpan OpenTime = TimeSpan.FromSeconds(600);
/// <summary>
/// How long the destination is not ready for after the portal closes.
/// </summary>
[DataField("cooldown"), ViewVariables(VVAccess.ReadWrite)]
[DataField, ViewVariables(VVAccess.ReadWrite)]
public TimeSpan Cooldown = TimeSpan.FromSeconds(60);
/// <summary>
/// If true, the portal can be closed by alt clicking it.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public bool Closeable;
}

View File

@@ -1,22 +1,25 @@
using Content.Server.Gateway.Components;
using Content.Shared.Access.Systems;
using Content.Shared.Gateway;
using Content.Shared.Popups;
using Content.Shared.Teleportation.Components;
using Content.Shared.Teleportation.Systems;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Timing;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Content.Server.Gateway.Systems;
public sealed class GatewaySystem : EntitySystem
{
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly LinkedEntitySystem _linkedEntity = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
public override void Initialize()
@@ -29,6 +32,7 @@ public sealed class GatewaySystem : EntitySystem
SubscribeLocalEvent<GatewayDestinationComponent, ComponentStartup>(OnDestinationStartup);
SubscribeLocalEvent<GatewayDestinationComponent, ComponentShutdown>(OnDestinationShutdown);
SubscribeLocalEvent<GatewayDestinationComponent, GetVerbsEvent<AlternativeVerb>>(OnDestinationGetVerbs);
}
public override void Update(float frameTime)
@@ -66,7 +70,7 @@ public sealed class GatewaySystem : EntitySystem
private void UpdateUserInterface(EntityUid uid, GatewayComponent comp)
{
var destinations = new List<(NetEntity, String, TimeSpan, bool)>();
var destinations = new List<(NetEntity, string, TimeSpan, bool)>();
foreach (var destUid in comp.Destinations)
{
var dest = Comp<GatewayDestinationComponent>(destUid);
@@ -76,7 +80,7 @@ public sealed class GatewaySystem : EntitySystem
destinations.Add((GetNetEntity(destUid), dest.Name, dest.NextReady, HasComp<PortalComponent>(destUid)));
}
GetDestination(uid, out var current);
_linkedEntity.GetLink(uid, out var current);
var state = new GatewayBoundUserInterfaceState(destinations, GetNetEntity(current), comp.NextClose, comp.LastOpen);
_ui.TrySetUiState(uid, GatewayUiKey.Key, state);
}
@@ -88,6 +92,14 @@ public sealed class GatewaySystem : EntitySystem
private void OnOpenPortal(EntityUid uid, GatewayComponent comp, GatewayOpenPortalMessage args)
{
if (args.Session.AttachedEntity == null)
return;
// if the gateway has an access reader check it before allowing opening
var user = args.Session.AttachedEntity.Value;
if (CheckAccess(user, uid))
return;
// can't link if portal is already open on either side, the destination is invalid or on cooldown
var desto = GetEntity(args.Destination);
@@ -113,18 +125,21 @@ public sealed class GatewaySystem : EntitySystem
// close automatically after time is up
comp.NextClose = comp.LastOpen + destComp.OpenTime;
_audio.PlayPvs(comp.PortalSound, uid);
_audio.PlayPvs(comp.PortalSound, dest);
_audio.PlayPvs(comp.OpenSound, uid);
_audio.PlayPvs(comp.OpenSound, dest);
UpdateUserInterface(uid, comp);
UpdateAppearance(uid);
UpdateAppearance(dest);
}
private void ClosePortal(EntityUid uid, GatewayComponent comp)
private void ClosePortal(EntityUid uid, GatewayComponent? comp = null)
{
if (!Resolve(uid, ref comp))
return;
RemComp<PortalComponent>(uid);
if (!GetDestination(uid, out var dest))
if (!_linkedEntity.GetLink(uid, out var dest))
return;
if (TryComp<GatewayDestinationComponent>(dest, out var destComp))
@@ -133,8 +148,8 @@ public sealed class GatewaySystem : EntitySystem
destComp.NextReady = _timing.CurTime + destComp.Cooldown;
}
_audio.PlayPvs(comp.PortalSound, uid);
_audio.PlayPvs(comp.PortalSound, dest.Value);
_audio.PlayPvs(comp.CloseSound, uid);
_audio.PlayPvs(comp.CloseSound, dest.Value);
_linkedEntity.TryUnlink(uid, dest.Value);
RemComp<PortalComponent>(dest.Value);
@@ -143,22 +158,6 @@ public sealed class GatewaySystem : EntitySystem
UpdateAppearance(dest.Value);
}
private bool GetDestination(EntityUid uid, [NotNullWhen(true)] out EntityUid? dest)
{
dest = null;
if (TryComp<LinkedEntityComponent>(uid, out var linked))
{
var first = linked.LinkedEntities.FirstOrDefault();
if (first != EntityUid.Invalid)
{
dest = first;
return true;
}
}
return false;
}
private void OnDestinationStartup(EntityUid uid, GatewayDestinationComponent comp, ComponentStartup args)
{
var query = EntityQueryEnumerator<GatewayComponent>();
@@ -180,4 +179,47 @@ public sealed class GatewaySystem : EntitySystem
UpdateUserInterface(gatewayUid, gateway);
}
}
private void OnDestinationGetVerbs(EntityUid uid, GatewayDestinationComponent comp, GetVerbsEvent<AlternativeVerb> args)
{
if (!comp.Closeable || !args.CanInteract || !args.CanAccess)
return;
// a portal is open so add verb to close it
args.Verbs.Add(new AlternativeVerb()
{
Act = () => TryClose(uid, args.User),
Text = Loc.GetString("gateway-close-portal")
});
}
private void TryClose(EntityUid uid, EntityUid user)
{
// portal already closed so cant close it
if (!_linkedEntity.GetLink(uid, out var source))
return;
// not allowed to close it
if (CheckAccess(user, source.Value))
return;
ClosePortal(source.Value);
}
/// <summary>
/// Checks the user's access. Makes popup and plays sound if missing access.
/// Returns whether access was missing.
/// </summary>
private bool CheckAccess(EntityUid user, EntityUid uid, GatewayComponent? comp = null)
{
if (!Resolve(uid, ref comp))
return false;
if (_accessReader.IsAllowed(user, uid))
return false;
_popup.PopupEntity(Loc.GetString("gateway-access-denied"), user);
_audio.PlayPvs(comp.AccessDeniedSound, uid);
return true;
}
}

View File

@@ -26,21 +26,22 @@ using Robust.Shared.Timing;
namespace Content.Server.Ghost
{
public sealed partial class GhostSystem : SharedGhostSystem
public sealed class GhostSystem : SharedGhostSystem
{
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedEyeSystem _eye = default!;
[Dependency] private readonly FollowerSystem _followerSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly JobSystem _jobs = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly MindSystem _minds = default!;
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly GameTicker _ticker = default!;
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
[Dependency] private readonly VisibilitySystem _visibilitySystem = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly FollowerSystem _followerSystem = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedEyeSystem _eye = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly MindSystem _minds = default!;
[Dependency] private readonly JobSystem _jobs = default!;
public override void Initialize()
{
@@ -72,10 +73,10 @@ namespace Content.Server.Ghost
if (args.Handled)
return;
var ents = _lookup.GetEntitiesInRange(args.Performer, component.BooRadius);
var entities = _lookup.GetEntitiesInRange(args.Performer, component.BooRadius);
var booCounter = 0;
foreach (var ent in ents)
foreach (var ent in entities)
{
var handled = DoGhostBooEvent(ent);
@@ -92,7 +93,7 @@ namespace Content.Server.Ghost
private void OnRelayMoveInput(EntityUid uid, GhostOnMoveComponent component, ref MoveInputEvent args)
{
// Let's not ghost if our mind is visiting...
if (EntityManager.HasComponent<VisitingMindComponent>(uid))
if (HasComp<VisitingMindComponent>(uid))
return;
if (!_minds.TryGetMind(uid, out var mindId, out var mind) || mind.IsVisitingEntity)
@@ -107,19 +108,16 @@ namespace Content.Server.Ghost
private void OnGhostStartup(EntityUid uid, GhostComponent component, ComponentStartup args)
{
// Allow this entity to be seen by other ghosts.
var visibility = EntityManager.EnsureComponent<VisibilityComponent>(uid);
var visibility = EnsureComp<VisibilityComponent>(uid);
if (_ticker.RunLevel != GameRunLevel.PostRound)
{
_visibilitySystem.AddLayer(visibility, (int) VisibilityFlags.Ghost, false);
_visibilitySystem.RemoveLayer(visibility, (int) VisibilityFlags.Normal, false);
_visibilitySystem.RefreshVisibility(visibility);
_visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Ghost, false);
_visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false);
_visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility);
}
if (EntityManager.TryGetComponent(uid, out EyeComponent? eye))
{
_eye.SetVisibilityMask(uid, eye.VisibilityMask | (int) VisibilityFlags.Ghost, eye);
}
SetCanSeeGhosts(uid, true);
var time = _gameTiming.CurTime;
component.TimeOfDeath = time;
@@ -140,24 +138,32 @@ namespace Content.Server.Ghost
private void OnGhostShutdown(EntityUid uid, GhostComponent component, ComponentShutdown args)
{
// Perf: If the entity is deleting itself, no reason to change these back.
if (!Terminating(uid))
if (Terminating(uid))
return;
// Entity can't be seen by ghosts anymore.
if (TryComp(uid, out VisibilityComponent? visibility))
{
// Entity can't be seen by ghosts anymore.
if (EntityManager.TryGetComponent(uid, out VisibilityComponent? visibility))
{
_visibilitySystem.RemoveLayer(visibility, (int) VisibilityFlags.Ghost, false);
_visibilitySystem.AddLayer(visibility, (int) VisibilityFlags.Normal, false);
_visibilitySystem.RefreshVisibility(visibility);
}
// Entity can't see ghosts anymore.
if (EntityManager.TryGetComponent(uid, out EyeComponent? eye))
{
_eye.SetVisibilityMask(uid, eye.VisibilityMask & ~(int) VisibilityFlags.Ghost, eye);
}
_actions.RemoveAction(uid, component.ActionEntity);
_visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Ghost, false);
_visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Normal, false);
_visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility);
}
// Entity can't see ghosts anymore.
SetCanSeeGhosts(uid, false);
_actions.RemoveAction(uid, component.ActionEntity);
}
private void SetCanSeeGhosts(EntityUid uid, bool canSee, EyeComponent? eyeComponent = null)
{
if (!Resolve(uid, ref eyeComponent, false))
return;
if (canSee)
_eye.SetVisibilityMask(uid, eyeComponent.VisibilityMask | (int) VisibilityFlags.Ghost, eyeComponent);
else
_eye.SetVisibilityMask(uid, eyeComponent.VisibilityMask & ~(int) VisibilityFlags.Ghost, eyeComponent);
}
private void OnGhostExamine(EntityUid uid, GhostComponent component, ExaminedEvent args)
@@ -170,6 +176,8 @@ namespace Content.Server.Ghost
args.PushMarkup(deathTimeInfo);
}
#region Ghost Deletion
private void OnMindRemovedMessage(EntityUid uid, GhostComponent component, MindRemovedMessage args)
{
DeleteEntity(uid);
@@ -185,10 +193,36 @@ namespace Content.Server.Ghost
DeleteEntity(uid);
}
private void DeleteEntity(EntityUid uid)
{
if (Deleted(uid) || Terminating(uid))
return;
QueueDel(uid);
}
#endregion
private void OnGhostReturnToBodyRequest(GhostReturnToBodyRequest msg, EntitySessionEventArgs args)
{
if (args.SenderSession.AttachedEntity is not {Valid: true} attached
|| !TryComp(attached, out GhostComponent? ghost)
|| !ghost.CanReturnToBody
|| !TryComp(attached, out ActorComponent? actor))
{
Log.Warning($"User {args.SenderSession.Name} sent an invalid {nameof(GhostReturnToBodyRequest)}");
return;
}
_mindSystem.UnVisit(actor.PlayerSession);
}
#region Warp
private void OnGhostWarpsRequest(GhostWarpsRequestEvent msg, EntitySessionEventArgs args)
{
if (args.SenderSession.AttachedEntity is not {Valid: true} entity ||
!EntityManager.HasComponent<GhostComponent>(entity))
if (args.SenderSession.AttachedEntity is not {Valid: true} entity
|| !HasComp<GhostComponent>(entity))
{
Log.Warning($"User {args.SenderSession.Name} sent a {nameof(GhostWarpsRequestEvent)} without being a ghost.");
return;
@@ -198,24 +232,10 @@ namespace Content.Server.Ghost
RaiseNetworkEvent(response, args.SenderSession.ConnectedClient);
}
private void OnGhostReturnToBodyRequest(GhostReturnToBodyRequest msg, EntitySessionEventArgs args)
{
if (args.SenderSession.AttachedEntity is not {Valid: true} attached ||
!EntityManager.TryGetComponent(attached, out GhostComponent? ghost) ||
!ghost.CanReturnToBody ||
!EntityManager.TryGetComponent(attached, out ActorComponent? actor))
{
Log.Warning($"User {args.SenderSession.Name} sent an invalid {nameof(GhostReturnToBodyRequest)}");
return;
}
_mindSystem.UnVisit(actor.PlayerSession);
}
private void OnGhostWarpToTargetRequest(GhostWarpToTargetRequestEvent msg, EntitySessionEventArgs args)
{
if (args.SenderSession.AttachedEntity is not {Valid: true} attached ||
!EntityManager.TryGetComponent(attached, out GhostComponent? ghost))
if (args.SenderSession.AttachedEntity is not {Valid: true} attached
|| !TryComp(attached, out GhostComponent? _))
{
Log.Warning($"User {args.SenderSession.Name} tried to warp to {msg.Target} without being a ghost.");
return;
@@ -223,34 +243,25 @@ namespace Content.Server.Ghost
var target = GetEntity(msg.Target);
if (!EntityManager.EntityExists(target))
if (!Exists(target))
{
Log.Warning($"User {args.SenderSession.Name} tried to warp to an invalid entity id: {msg.Target}");
return;
}
if (TryComp(target, out WarpPointComponent? warp) && warp.Follow
|| HasComp<MobStateComponent>(target))
if ((TryComp(target, out WarpPointComponent? warp) && warp.Follow) || HasComp<MobStateComponent>(target))
{
_followerSystem.StartFollowingEntity(attached, target);
return;
_followerSystem.StartFollowingEntity(attached, target);
return;
}
var xform = Transform(attached);
xform.Coordinates = Transform(target).Coordinates;
xform.AttachToGridOrMap();
_transformSystem.SetCoordinates(attached, xform, Transform(target).Coordinates);
_transformSystem.AttachToGridOrMap(attached, xform);
if (TryComp(attached, out PhysicsComponent? physics))
_physics.SetLinearVelocity(attached, Vector2.Zero, body: physics);
}
private void DeleteEntity(EntityUid uid)
{
if (Deleted(uid) || Terminating(uid))
return;
QueueDel(uid);
}
private IEnumerable<GhostWarp> GetLocationWarps()
{
var allQuery = AllEntityQuery<WarpPointComponent>();
@@ -258,9 +269,7 @@ namespace Content.Server.Ghost
while (allQuery.MoveNext(out var uid, out var warp))
{
if (warp.Location != null)
{
yield return new GhostWarp(GetNetEntity(uid), warp.Location, true);
}
}
}
@@ -268,21 +277,23 @@ namespace Content.Server.Ghost
{
foreach (var player in _playerManager.Sessions)
{
if (player.AttachedEntity is {Valid: true} attached)
{
if (attached == except) continue;
if (player.AttachedEntity is not {Valid: true} attached)
continue;
TryComp<MindContainerComponent>(attached, out var mind);
if (attached == except) continue;
var jobName = _jobs.MindTryGetJobName(mind?.Mind);
var playerInfo = $"{EntityManager.GetComponent<MetaDataComponent>(attached).EntityName} ({jobName})";
TryComp<MindContainerComponent>(attached, out var mind);
if (_mobState.IsAlive(attached) || _mobState.IsCritical(attached))
yield return new GhostWarp(GetNetEntity(attached), playerInfo, false);
}
var jobName = _jobs.MindTryGetJobName(mind?.Mind);
var playerInfo = $"{Comp<MetaDataComponent>(attached).EntityName} ({jobName})";
if (_mobState.IsAlive(attached) || _mobState.IsCritical(attached))
yield return new GhostWarp(GetNetEntity(attached), playerInfo, false);
}
}
#endregion
private void OnEntityStorageInsertAttempt(EntityUid uid, GhostComponent comp, ref InsertIntoEntityStorageAttemptEvent args)
{
args.Cancelled = true;
@@ -293,19 +304,20 @@ namespace Content.Server.Ghost
/// </summary>
public void MakeVisible(bool visible)
{
foreach (var (_, vis) in EntityQuery<GhostComponent, VisibilityComponent>())
var entityQuery = EntityQueryEnumerator<GhostComponent, VisibilityComponent>();
while (entityQuery.MoveNext(out var uid, out _, out var vis))
{
if (visible)
{
_visibilitySystem.AddLayer(vis, (int) VisibilityFlags.Normal, false);
_visibilitySystem.RemoveLayer(vis, (int) VisibilityFlags.Ghost, false);
_visibilitySystem.AddLayer(uid, vis, (int) VisibilityFlags.Normal, false);
_visibilitySystem.RemoveLayer(uid, vis, (int) VisibilityFlags.Ghost, false);
}
else
{
_visibilitySystem.AddLayer(vis, (int) VisibilityFlags.Ghost, false);
_visibilitySystem.RemoveLayer(vis, (int) VisibilityFlags.Normal, false);
_visibilitySystem.AddLayer(uid, vis, (int) VisibilityFlags.Ghost, false);
_visibilitySystem.RemoveLayer(uid, vis, (int) VisibilityFlags.Normal, false);
}
_visibilitySystem.RefreshVisibility(vis);
_visibilitySystem.RefreshVisibility(uid, visibilityComponent: vis);
}
}
@@ -324,6 +336,7 @@ namespace Content.Server.Ghost
public string Command => "toggleghosts";
public string Description => "Toggles ghost visibility";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (shell.Player == null)
@@ -335,9 +348,7 @@ namespace Content.Server.Ghost
if (uid == null
|| !entityManager.HasComponent<GhostComponent>(uid)
|| !entityManager.TryGetComponent<EyeComponent>(uid, out var eyeComponent))
{
return;
}
entityManager.System<EyeSystem>().SetVisibilityMask(uid.Value, eyeComponent.VisibilityMask ^ (int) VisibilityFlags.Ghost, eyeComponent);
}

View File

@@ -124,6 +124,7 @@ public sealed class ToggleableGhostRoleSystem : EntitySystem
{
if (component.Deleted || !HasComp<GhostTakeoverAvailableComponent>(uid))
return;
RemCompDeferred<GhostTakeoverAvailableComponent>(uid);
RemCompDeferred<GhostRoleComponent>(uid);
_popup.PopupEntity(Loc.GetString(component.StopSearchVerbPopup), uid, args.User);
@@ -133,4 +134,26 @@ public sealed class ToggleableGhostRoleSystem : EntitySystem
args.Verbs.Add(verb);
}
}
/// <summary>
/// If there is a player present, kicks it out.
/// If not, prevents future ghosts taking it.
/// No popups are made, but appearance is updated.
/// </summary>
public void Wipe(EntityUid uid)
{
if (TryComp<MindContainerComponent>(uid, out var mindContainer) &&
mindContainer.HasMind &&
_mind.TryGetMind(uid, out var mindId, out var mind))
{
_mind.TransferTo(mindId, null, mind: mind);
}
if (!HasComp<GhostTakeoverAvailableComponent>(uid))
return;
RemCompDeferred<GhostTakeoverAvailableComponent>(uid);
RemCompDeferred<GhostRoleComponent>(uid);
UpdateAppearance(uid, ToggleableGhostRoleStatus.Off);
}
}

View File

@@ -300,11 +300,6 @@ namespace Content.Server.Guardian
RetractGuardian(hostUid, hostComponent, guardianUid, guardianComponent);
}
private bool CanRelease(EntityUid guardian)
{
return HasComp<ActorComponent>(guardian);
}
private void ReleaseGuardian(EntityUid host, GuardianHostComponent hostComponent, EntityUid guardian, GuardianComponent guardianComponent)
{
if (guardianComponent.GuardianLoose)
@@ -313,12 +308,6 @@ namespace Content.Server.Guardian
return;
}
if (!CanRelease(guardian))
{
_popupSystem.PopupEntity(Loc.GetString("guardian-no-soul"), host, host);
return;
}
DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardian));
hostComponent.GuardianContainer.Remove(guardian);
DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardian));

View File

@@ -1,4 +1,5 @@
using Content.Server.Administration.Logs;
using Content.Server.Clothing.Components;
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.Ghost;
@@ -22,6 +23,7 @@ using Content.Shared.DoAfter;
using Content.Server.Emp;
using Content.Server.DeviceLinking.Events;
using Content.Server.DeviceLinking.Systems;
using Content.Shared.Inventory;
namespace Content.Server.Light.EntitySystems
{
@@ -43,6 +45,7 @@ namespace Content.Server.Light.EntitySystems
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly PointLightSystem _pointLight = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
private static readonly TimeSpan ThunkDelay = TimeSpan.FromSeconds(2);
public const string LightBulbContainer = "light_bulb";
@@ -105,11 +108,15 @@ namespace Content.Server.Light.EntitySystems
// check if it's possible to apply burn damage to user
var userUid = args.User;
if (EntityManager.TryGetComponent(userUid, out HeatResistanceComponent? heatResist) &&
EntityManager.TryGetComponent(bulbUid.Value, out LightBulbComponent? lightBulb))
if (EntityManager.TryGetComponent(bulbUid.Value, out LightBulbComponent? lightBulb))
{
// get users heat resistance
var res = heatResist.GetHeatResistance();
var res = int.MinValue;
if (_inventory.TryGetSlotEntity(userUid, "gloves", out var slotEntity) &&
TryComp<GloveHeatResistanceComponent>(slotEntity, out var gloves))
{
res = gloves.HeatResistance;
}
// check heat resistance against user
var burnedHand = light.CurrentLit && res < lightBulb.BurningTemperature;

View File

@@ -10,6 +10,7 @@ using Content.Server.NPC.Systems;
using Content.Shared.Administration;
using Content.Shared.Mobs;
using Content.Shared.NPC;
using Content.Shared.NPC;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Server.Player;

View File

@@ -558,10 +558,13 @@ public sealed partial class PathfindingSystem
}
}
/*This is causing too many issues and I'd rather just ignore it until pathfinder refactor
to just get tiles at runtime.
if ((flags & PathfindingBreadcrumbFlag.Space) != 0x0)
{
DebugTools.Assert(tileEntities.Count == 0);
// DebugTools.Assert(tileEntities.Count == 0);
}
*/
var crumb = new PathfindingBreadcrumb()
{

View File

@@ -3,6 +3,7 @@ using Content.Server.NPC.Components;
using Content.Server.NPC.Events;
using Content.Shared.CombatMode;
using Content.Shared.NPC;
using Content.Shared.NPC;
using Content.Shared.Weapons.Melee;
using Robust.Shared.Map;
using Robust.Shared.Physics.Components;

View File

@@ -15,6 +15,7 @@ using Content.Shared.Interaction;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.NPC;
using Content.Shared.NPC;
using Content.Shared.NPC.Events;
using Content.Shared.Physics;
using Content.Shared.Weapons.Melee;

View File

@@ -4,6 +4,7 @@ using Content.Server.NPC.HTN;
using Content.Shared.CCVar;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Systems;
using Content.Shared.NPC;
using Robust.Server.GameObjects;
using Robust.Shared.Configuration;

View File

@@ -229,7 +229,7 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
// assign objectives - must happen after spider charge target so that the obj requirement works
foreach (var objective in config.Objectives)
{
if (!_mind.TryAddObjective(mindId, objective, mind))
if (!_mind.TryAddObjective(mindId, mind, objective))
{
Log.Error($"Failed to add {objective} to ninja {mind.OwnedEntity.Value}");
}

View File

@@ -1,7 +1,7 @@
using Content.Server.Administration;
using Content.Shared.Administration;
using Content.Shared.Mind;
using Content.Shared.Objectives;
using Content.Shared.Objectives.Components;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.Prototypes;
@@ -39,16 +39,17 @@ namespace Content.Server.Objectives.Commands
}
if (!IoCManager.Resolve<IPrototypeManager>()
.TryIndex<ObjectivePrototype>(args[1], out var objectivePrototype))
.TryIndex<EntityPrototype>(args[1], out var proto) ||
!proto.TryGetComponent<ObjectiveComponent>(out _))
{
shell.WriteLine($"Can't find matching ObjectivePrototype {objectivePrototype}");
shell.WriteLine($"Can't find matching objective prototype {args[1]}");
return;
}
var mindSystem = _entityManager.System<SharedMindSystem>();
if (!mindSystem.TryAddObjective(mindId, mind, objectivePrototype))
if (!minds.TryAddObjective(mindId, mind, args[1]))
{
shell.WriteLine("Objective requirements dont allow that objective to be added.");
// can fail for other reasons so dont pretend to be right
shell.WriteLine("Failed to add the objective. Maybe requirements dont allow that objective to be added.");
}
}
}

View File

@@ -2,6 +2,7 @@ using System.Linq;
using Content.Server.Administration;
using Content.Shared.Administration;
using Content.Shared.Mind;
using Content.Shared.Objectives.Systems;
using Robust.Server.Player;
using Robust.Shared.Console;
@@ -25,7 +26,7 @@ namespace Content.Server.Objectives.Commands
}
var minds = _entities.System<SharedMindSystem>();
if (!minds.TryGetMind(player, out _, out var mind))
if (!minds.TryGetMind(player, out var mindId, out var mind))
{
shell.WriteError(LocalizationManager.GetString("shell-target-entity-does-not-have-message", ("missing", "mind")));
return;
@@ -38,9 +39,20 @@ namespace Content.Server.Objectives.Commands
shell.WriteLine("None.");
}
var objectivesSystem = _entities.System<SharedObjectivesSystem>();
for (var i = 0; i < objectives.Count; i++)
{
shell.WriteLine($"- [{i}] {objectives[i].Conditions[0].Title}");
var info = objectivesSystem.GetInfo(objectives[i], mindId, mind);
if (info == null)
{
shell.WriteLine($"- [{i}] {objectives[i]} - INVALID");
}
else
{
var progress = (int) (info.Value.Progress * 100f);
shell.WriteLine($"- [{i}] {objectives[i]} ({info.Value.Title}) ({progress}%)");
}
}
}

View File

@@ -30,7 +30,7 @@ namespace Content.Server.Objectives.Commands
return;
}
if (!minds.TryGetMind(session, out _, out var mind))
if (!minds.TryGetMind(session, out var mindId, out var mind))
{
shell.WriteLine("Can't find the mind.");
return;
@@ -39,7 +39,7 @@ namespace Content.Server.Objectives.Commands
if (int.TryParse(args[1], out var i))
{
var mindSystem = _entityManager.System<SharedMindSystem>();
shell.WriteLine(mindSystem.TryRemoveObjective(mind, i)
shell.WriteLine(mindSystem.TryRemoveObjective(mindId, mind, i)
? "Objective successfully removed!"
: "Objective removing failed. Maybe the index is out of bounds? Check lsobjectives!");
}

View File

@@ -0,0 +1,11 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Requires that the player dies to be complete.
/// </summary>
[RegisterComponent, Access(typeof(DieConditionSystem))]
public sealed partial class DieConditionComponent : Component
{
}

View File

@@ -0,0 +1,12 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Objective condition that requires the player to be a ninja and have doorjacked at least a random number of airlocks.
/// Requires <see cref="NumberObjectiveComponent"/> to function.
/// </summary>
[RegisterComponent, Access(typeof(NinjaConditionsSystem))]
public sealed partial class DoorjackConditionComponent : Component
{
}

View File

@@ -0,0 +1,11 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Requires that the player is on the emergency shuttle's grid when docking to CentCom.
/// </summary>
[RegisterComponent, Access(typeof(EscapeShuttleConditionSystem))]
public sealed partial class EscapeShuttleConditionComponent : Component
{
}

View File

@@ -0,0 +1,12 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Requires that a target completes half of their objectives.
/// Depends on <see cref="TargetObjectiveComponent"/> to function.
/// </summary>
[RegisterComponent, Access(typeof(HelpProgressConditionSystem))]
public sealed partial class HelpProgressConditionComponent : Component
{
}

View File

@@ -0,0 +1,12 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Requires that a target stays alive.
/// Depends on <see cref="TargetObjectiveComponent"/> to function.
/// </summary>
[RegisterComponent, Access(typeof(KeepAliveConditionSystem))]
public sealed partial class KeepAliveConditionComponent : Component
{
}

View File

@@ -0,0 +1,17 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Requires that a target dies or, if <see cref="RequireDead"/> is false, is not on the emergency shuttle.
/// Depends on <see cref="TargetObjectiveComponent"/> to function.
/// </summary>
[RegisterComponent, Access(typeof(KillPersonConditionSystem))]
public sealed partial class KillPersonConditionComponent : Component
{
/// <summary>
/// Whether the target must be truly dead, ignores missing evac.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public bool RequireDead = false;
}

View File

@@ -0,0 +1,16 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Requires that there are a certain number of other traitors alive for this objective to be given.
/// </summary>
[RegisterComponent, Access(typeof(MultipleTraitorsRequirementSystem))]
public sealed partial class MultipleTraitorsRequirementComponent : Component
{
/// <summary>
/// Number of traitors, excluding yourself, that have to exist.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public int Traitors = 2;
}

View File

@@ -0,0 +1,11 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Requires that the player is not a member of command.
/// </summary>
[RegisterComponent, Access(typeof(NotCommandRequirementSystem))]
public sealed partial class NotCommandRequirementComponent : Component
{
}

View File

@@ -0,0 +1,17 @@
using Content.Server.Objectives.Systems;
using Content.Shared.Roles;
using Content.Shared.Roles.Jobs;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
/// <summary>
/// Requires that the player not have a certain job to have this objective.
/// </summary>
[RegisterComponent, Access(typeof(NotJobRequirementSystem))]
public sealed partial class NotJobRequirementComponent : Component
{
/// <summary>
/// ID of the job to ban from having this objective.
/// </summary>
[DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<JobPrototype>))]
public string Job = string.Empty;
}

View File

@@ -0,0 +1,41 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Objective has a target number of something.
/// When the objective is assigned it randomly picks this target from a minimum to a maximum.
/// </summary>
[RegisterComponent, Access(typeof(NumberObjectiveSystem))]
public sealed partial class NumberObjectiveComponent : Component
{
/// <summary>
/// Number to use in the objective condition.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public int Target;
/// <summary>
/// Minimum number for target to roll.
/// </summary>
[DataField(required: true)]
public int Min;
/// <summary>
/// Maximum number for target to roll.
/// </summary>
[DataField(required: true)]
public int Max;
/// <summary>
/// Optional title locale id, passed "count" with <see cref="Target"/>.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public string? Title;
/// <summary>
/// Optional description locale id, passed "count" with <see cref="Target"/>.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public string? Description;
}

View File

@@ -0,0 +1,15 @@
using Content.Server.Objectives.Systems;
using Content.Shared.Whitelist;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Requires that the objective entity has no blacklisted components.
/// Lets you check for incompatible objectives.
/// </summary>
[RegisterComponent, Access(typeof(ObjectiveBlacklistRequirementSystem))]
public sealed partial class ObjectiveBlacklistRequirementComponent : Component
{
[DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
public EntityWhitelist Blacklist = new();
}

View File

@@ -0,0 +1,12 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Sets the target for <see cref="TargetObjectiveComponent"/> to a random head.
/// If there are no heads it will fallback to any person.
/// </summary>
[RegisterComponent, Access(typeof(KillPersonConditionSystem))]
public sealed partial class PickRandomHeadComponent : Component
{
}

View File

@@ -0,0 +1,11 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Sets the target for <see cref="TargetObjectiveComponent"/> to a random person.
/// </summary>
[RegisterComponent, Access(typeof(KillPersonConditionSystem))]
public sealed partial class PickRandomPersonComponent : Component
{
}

View File

@@ -0,0 +1,11 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Sets the target for <see cref="KeepAliveConditionComponent"/> to a random traitor.
/// </summary>
[RegisterComponent, Access(typeof(KeepAliveConditionSystem))]
public sealed partial class RandomTraitorAliveComponent : Component
{
}

View File

@@ -0,0 +1,11 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Sets the target for <see cref="HelpProgressConditionComponent"/> to a random traitor.
/// </summary>
[RegisterComponent, Access(typeof(HelpProgressConditionSystem))]
public sealed partial class RandomTraitorProgressComponent : Component
{
}

View File

@@ -0,0 +1,15 @@
using Content.Server.Objectives.Systems;
using Content.Shared.Whitelist;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Requires that the player's mind matches a whitelist.
/// Typical use is checking for (antagonist) roles.
/// </summary>
[RegisterComponent, Access(typeof(RoleRequirementSystem))]
public sealed partial class RoleRequirementComponent : Component
{
[DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
public EntityWhitelist Roles = new();
}

View File

@@ -0,0 +1,9 @@
namespace Content.Server.Objectives.Components;
/// <summary>
/// Marker component for social objectives and kill objectives to be mutually exclusive.
/// </summary>
[RegisterComponent]
public sealed partial class SocialObjectiveComponent : Component
{
}

View File

@@ -0,0 +1,11 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Requires that the player is a ninja and blew up their spider charge at its target location.
/// </summary>
[RegisterComponent, Access(typeof(NinjaConditionsSystem))]
public sealed partial class SpiderChargeConditionComponent : Component
{
}

View File

@@ -0,0 +1,11 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Requires the player to be a ninja that has a spider charge target assigned, which is almost always the case.
/// </summary>
[RegisterComponent, Access(typeof(SpiderChargeTargetRequirementSystem))]
public sealed partial class SpiderChargeTargetRequirementComponent : Component
{
}

View File

@@ -0,0 +1,28 @@
using Content.Server.Objectives.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Requires that you steal a certain item.
/// </summary>
[RegisterComponent, Access(typeof(StealConditionSystem))]
public sealed partial class StealConditionComponent : Component
{
/// <summary>
/// The id of the item to steal.
/// </summary>
/// <remarks>
/// Works by prototype id not tags or anything so it has to be the exact item.
/// </remarks>
[DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>)), ViewVariables(VVAccess.ReadWrite)]
public string Prototype = string.Empty;
/// <summary>
/// Help newer players by saying e.g. "steal the chief engineer's advanced magboots"
/// instead of "steal advanced magboots. Should be a loc string.
/// </summary>
[DataField("owner"), ViewVariables(VVAccess.ReadWrite)]
public string? OwnerText;
}

View File

@@ -0,0 +1,12 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Objective condition that requires the player to be a ninja and have stolen at least a random number of technologies.
/// Requires <see cref="NumberObjectiveComponent"/> to function.
/// </summary>
[RegisterComponent, Access(typeof(NinjaConditionsSystem))]
public sealed partial class StealResearchConditionComponent : Component
{
}

View File

@@ -0,0 +1,11 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Just requires that the player is not dead, ignores evac and what not.
/// </summary>
[RegisterComponent, Access(typeof(SurviveConditionSystem))]
public sealed partial class SurviveConditionComponent : Component
{
}

View File

@@ -0,0 +1,21 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
[RegisterComponent, Access(typeof(TargetObjectiveSystem))]
public sealed partial class TargetObjectiveComponent : Component
{
/// <summary>
/// Locale id for the objective title.
/// It is passed "targetName" and "job" arguments.
/// </summary>
[DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
public string Title = string.Empty;
/// <summary>
/// Mind entity id of the target.
/// This must be set by another system using <see cref="TargetObjectiveSystem.SetTarget"/>.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public EntityUid? Target;
}

View File

@@ -0,0 +1,11 @@
using Content.Server.Objectives.Systems;
namespace Content.Server.Objectives.Components;
/// <summary>
/// Requires that the player is a ninja and has called in a threat.
/// </summary>
[RegisterComponent, Access(typeof(NinjaConditionsSystem))]
public sealed partial class TerrorConditionComponent : Component
{
}

View File

@@ -1,55 +0,0 @@
using Content.Shared.Mind;
using Content.Shared.Objectives.Interfaces;
using JetBrains.Annotations;
using Robust.Shared.Utility;
namespace Content.Server.Objectives.Conditions
{
[UsedImplicitly]
[DataDefinition]
public sealed partial class DieCondition : IObjectiveCondition
{
private MindComponent? _mind;
public IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind)
{
return new DieCondition { _mind = mind };
}
public string Title => Loc.GetString("objective-condition-die-title");
public string Description => Loc.GetString("objective-condition-die-description");
public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ("Mobs/Ghosts/ghost_human.rsi"), "icon");
public float Progress
{
get
{
var entityManager = IoCManager.Resolve<EntityManager>();
var mindSystem = entityManager.System<SharedMindSystem>();
return _mind == null || mindSystem.IsCharacterDeadIc(_mind) ? 1f : 0f;
}
}
public float Difficulty => 0.5f;
public bool Equals(IObjectiveCondition? other)
{
return other is DieCondition condition && Equals(_mind, condition._mind);
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((DieCondition) obj);
}
public override int GetHashCode()
{
return (_mind != null ? _mind.GetHashCode() : 0);
}
}
}

View File

@@ -1,70 +0,0 @@
using Content.Server.Roles;
using Content.Shared.Mind;
using Content.Shared.Objectives.Interfaces;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.Objectives.Conditions;
/// <summary>
/// Objective condition that requires the player to be a ninja and have doorjacked at least a random number of airlocks.
/// </summary>
[DataDefinition]
public sealed partial class DoorjackCondition : IObjectiveCondition
{
private EntityUid? _mind;
private int _target;
public IObjectiveCondition GetAssigned(EntityUid uid, MindComponent mind)
{
// TODO: clamp to number of doors on station incase its somehow a shittle or something
return new DoorjackCondition {
_mind = uid,
_target = IoCManager.Resolve<IRobustRandom>().Next(15, 40)
};
}
public string Title => Loc.GetString("objective-condition-doorjack-title", ("count", _target));
public string Description => Loc.GetString("objective-condition-doorjack-description", ("count", _target));
public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ResPath("Objects/Tools/emag.rsi"), "icon");
public float Progress
{
get
{
// prevent divide-by-zero
if (_target == 0)
return 1f;
var entMan = IoCManager.Resolve<IEntityManager>();
if (!entMan.TryGetComponent<NinjaRoleComponent>(_mind, out var role))
return 0f;
if (role.DoorsJacked >= _target)
return 1f;
return (float) role.DoorsJacked / (float) _target;
}
}
public float Difficulty => 1.5f;
public bool Equals(IObjectiveCondition? other)
{
return other is DoorjackCondition cond && Equals(_mind, cond._mind) && _target == cond._target;
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return obj is DoorjackCondition cond && cond.Equals(this);
}
public override int GetHashCode()
{
return HashCode.Combine(_mind?.GetHashCode() ?? 0, _target);
}
}

View File

@@ -1,80 +0,0 @@
using Content.Server.Shuttles.Systems;
using Content.Shared.Cuffs.Components;
using Content.Shared.Mind;
using Content.Shared.Objectives.Interfaces;
using JetBrains.Annotations;
using Robust.Shared.Utility;
namespace Content.Server.Objectives.Conditions
{
[UsedImplicitly]
[DataDefinition]
public sealed partial class EscapeShuttleCondition : IObjectiveCondition
{
// TODO refactor all of this to be ecs
private MindComponent? _mind;
public IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind)
{
return new EscapeShuttleCondition
{
_mind = mind,
};
}
public string Title => Loc.GetString("objective-condition-escape-shuttle-title");
public string Description => Loc.GetString("objective-condition-escape-shuttle-description");
public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ("Structures/Furniture/chairs.rsi"), "shuttle");
public float Progress
{
get {
var entMan = IoCManager.Resolve<IEntityManager>();
var mindSystem = entMan.System<SharedMindSystem>();
if (_mind?.OwnedEntity == null
|| !entMan.TryGetComponent<TransformComponent>(_mind.OwnedEntity, out var xform))
return 0f;
if (mindSystem.IsCharacterDeadIc(_mind))
return 0f;
if (entMan.TryGetComponent<CuffableComponent>(_mind.OwnedEntity, out var cuffed)
&& cuffed.CuffedHandCount > 0)
{
// You're not escaping if you're restrained!
return 0f;
}
// Any emergency shuttle counts for this objective, but not pods.
var emergencyShuttle = entMan.System<EmergencyShuttleSystem>();
if (!emergencyShuttle.IsTargetEscaping(_mind.OwnedEntity.Value))
return 0f;
return 1f;
}
}
public float Difficulty => 1.3f;
public bool Equals(IObjectiveCondition? other)
{
return other is EscapeShuttleCondition esc && Equals(_mind, esc._mind);
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((EscapeShuttleCondition) obj);
}
public override int GetHashCode()
{
return _mind != null ? _mind.GetHashCode() : 0;
}
}
}

View File

@@ -1,101 +0,0 @@
using Content.Server.Shuttles.Systems;
using Content.Shared.CCVar;
using Content.Shared.Mind;
using Content.Shared.Mobs.Systems;
using Content.Shared.Objectives.Interfaces;
using Content.Shared.Roles.Jobs;
using Robust.Shared.Configuration;
using Robust.Shared.Utility;
namespace Content.Server.Objectives.Conditions
{
public abstract class KillPersonCondition : IObjectiveCondition
{
// TODO refactor all of this to be ecs
protected IEntityManager EntityManager => IoCManager.Resolve<IEntityManager>();
protected SharedMindSystem Minds => EntityManager.System<SharedMindSystem>();
protected SharedJobSystem Jobs => EntityManager.System<SharedJobSystem>();
protected MobStateSystem MobStateSystem => EntityManager.System<MobStateSystem>();
protected EntityUid? TargetMindId;
protected MindComponent? TargetMind => EntityManager.GetComponentOrNull<MindComponent>(TargetMindId);
public abstract IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind);
/// <summary>
/// Whether the target must be truly dead, ignores missing evac.
/// </summary>
protected bool RequireDead = false;
public string Title
{
get
{
var mind = TargetMind;
var targetName = mind?.CharacterName ?? "Unknown";
var jobName = Jobs.MindTryGetJobName(TargetMindId);
if (TargetMind == null)
return Loc.GetString("objective-condition-kill-person-title", ("targetName", targetName), ("job", jobName));
return Loc.GetString("objective-condition-kill-person-title", ("targetName", targetName), ("job", jobName));
}
}
public string Description => Loc.GetString("objective-condition-kill-person-description");
public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ("Objects/Weapons/Guns/Pistols/viper.rsi"), "icon");
public float Progress
{
get
{
if (TargetMindId == null || TargetMind?.OwnedEntity == null)
return 1f;
var entMan = IoCManager.Resolve<EntityManager>();
var mindSystem = entMan.System<SharedMindSystem>();
if (mindSystem.IsCharacterDeadIc(TargetMind))
return 1f;
if (RequireDead)
return 0f;
// if evac is disabled then they really do have to be dead
var configMan = IoCManager.Resolve<IConfigurationManager>();
if (!configMan.GetCVar(CCVars.EmergencyShuttleEnabled))
return 0f;
// target is escaping so you fail
var emergencyShuttle = entMan.System<EmergencyShuttleSystem>();
if (emergencyShuttle.IsTargetEscaping(TargetMind.OwnedEntity.Value))
return 0f;
// evac has left without the target, greentext since the target is afk in space with a full oxygen tank and coordinates off.
if (emergencyShuttle.ShuttlesLeft)
return 1f;
// if evac is still here and target hasn't boarded, show 50% to give you an indicator that you are doing good
return emergencyShuttle.EmergencyShuttleArrived ? 0.5f : 0f;
}
}
public float Difficulty => 1.75f;
public bool Equals(IObjectiveCondition? other)
{
return other is KillPersonCondition kpc && Equals(TargetMindId, kpc.TargetMindId);
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((KillPersonCondition) obj);
}
public override int GetHashCode()
{
return TargetMindId?.GetHashCode() ?? 0;
}
}
}

View File

@@ -1,44 +0,0 @@
using System.Linq;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs.Components;
using Content.Shared.Objectives.Interfaces;
using Robust.Shared.Random;
namespace Content.Server.Objectives.Conditions;
[DataDefinition]
public sealed partial class KillRandomHeadCondition : KillPersonCondition
{
// TODO refactor all of this to be ecs
public override IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind)
{
RequireDead = true;
var allHumans = EntityManager.EntityQuery<MindContainerComponent>(true).Where(mc =>
{
var entity = EntityManagerExt.GetComponentOrNull<MindComponent>(EntityManager, (EntityUid?) mc.Mind)?.OwnedEntity;
if (entity == default)
return false;
return EntityManager.TryGetComponent(entity, out MobStateComponent? mobState) &&
MobStateSystem.IsAlive(entity.Value, mobState) &&
mc.Mind != mindId;
}).Select(mc => mc.Mind).ToList();
if (allHumans.Count == 0)
return new DieCondition(); // I guess I'll die
var allHeads = allHumans
.Where(mind => Jobs.MindTryGetJob(mind, out _, out var prototype) && prototype.RequireAdminNotify)
.ToList();
if (allHeads.Count == 0)
allHeads = allHumans; // fallback to non-head target
return new KillRandomHeadCondition { TargetMindId = IoCManager.Resolve<IRobustRandom>().Pick(allHeads) };
}
public string Description => Loc.GetString("objective-condition-kill-head-description");
}

View File

@@ -1,36 +0,0 @@
using Content.Shared.Humanoid;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs.Components;
using Content.Shared.Objectives.Interfaces;
using Robust.Shared.Random;
namespace Content.Server.Objectives.Conditions;
[DataDefinition]
public sealed partial class KillRandomPersonCondition : KillPersonCondition
{
public override IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind)
{
var allHumans = new List<EntityUid>();
var query = EntityManager.EntityQuery<MindContainerComponent, HumanoidAppearanceComponent>(true);
foreach (var (mc, _) in query)
{
var entity = EntityManager.GetComponentOrNull<MindComponent>(mc.Mind)?.OwnedEntity;
if (entity == default)
continue;
if (EntityManager.TryGetComponent(entity, out MobStateComponent? mobState) &&
MobStateSystem.IsAlive(entity.Value, mobState) &&
mc.Mind != mindId && mc.Mind != null)
{
allHumans.Add(mc.Mind.Value);
}
}
if (allHumans.Count == 0)
return new DieCondition(); // I guess I'll die
return new KillRandomPersonCondition {TargetMindId = IoCManager.Resolve<IRobustRandom>().Pick(allHumans)};
}
}

View File

@@ -1,85 +0,0 @@
using System.Linq;
using Content.Server.GameTicking.Rules;
using Content.Shared.Mind;
using Content.Shared.Objectives.Interfaces;
using Content.Shared.Roles.Jobs;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.Objectives.Conditions
{
[DataDefinition]
public sealed partial class RandomTraitorAliveCondition : IObjectiveCondition
{
private EntityUid? _targetMind;
public IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind)
{
var entityMgr = IoCManager.Resolve<IEntityManager>();
var traitors = Enumerable.ToList<(EntityUid Id, MindComponent Mind)>(entityMgr.System<TraitorRuleSystem>().GetOtherTraitorMindsAliveAndConnected(mind));
if (traitors.Count == 0)
return new EscapeShuttleCondition(); //You were made a traitor by admins, and are the first/only.
return new RandomTraitorAliveCondition { _targetMind = IoCManager.Resolve<IRobustRandom>().Pick(traitors).Id };
}
public string Title
{
get
{
var targetName = string.Empty;
var ents = IoCManager.Resolve<IEntityManager>();
var jobs = ents.System<SharedJobSystem>();
var jobName = jobs.MindTryGetJobName(_targetMind);
if (_targetMind == null)
return Loc.GetString("objective-condition-other-traitor-alive-title", ("targetName", targetName), ("job", jobName));
if (ents.TryGetComponent(_targetMind, out MindComponent? mind) &&
mind.OwnedEntity is {Valid: true} owned)
{
targetName = ents.GetComponent<MetaDataComponent>(owned).EntityName;
}
return Loc.GetString("objective-condition-other-traitor-alive-title", ("targetName", targetName), ("job", jobName));
}
}
public string Description => Loc.GetString("objective-condition-other-traitor-alive-description");
public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ("Objects/Misc/bureaucracy.rsi"), "folder-white");
public float Progress
{
get
{
var entityManager = IoCManager.Resolve<EntityManager>();
var mindSystem = entityManager.System<SharedMindSystem>();
return !entityManager.TryGetComponent(_targetMind, out MindComponent? mind) ||
!mindSystem.IsCharacterDeadIc(mind)
? 1f
: 0f;
}
}
public float Difficulty => 1.75f;
public bool Equals(IObjectiveCondition? other)
{
return other is RandomTraitorAliveCondition kpc && Equals(_targetMind, kpc._targetMind);
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return obj is RandomTraitorAliveCondition alive && alive.Equals(this);
}
public override int GetHashCode()
{
return _targetMind?.GetHashCode() ?? 0;
}
}
}

View File

@@ -1,134 +0,0 @@
using System.Linq;
using Content.Server.GameTicking.Rules;
using Content.Shared.Mind;
using Content.Shared.Objectives.Interfaces;
using Content.Shared.Roles.Jobs;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.Objectives.Conditions
{
[DataDefinition]
public sealed partial class RandomTraitorProgressCondition : IObjectiveCondition
{
// TODO ecs all of this
private EntityUid? _targetMind;
public IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind)
{
//todo shit of a fuck
var entityMgr = IoCManager.Resolve<IEntityManager>();
var traitors = entityMgr.System<TraitorRuleSystem>().GetOtherTraitorMindsAliveAndConnected(mind).ToList();
List<EntityUid> removeList = new();
foreach (var traitor in traitors)
{
foreach (var objective in traitor.Mind.AllObjectives)
{
foreach (var condition in objective.Conditions)
{
if (condition is RandomTraitorProgressCondition)
{
removeList.Add(traitor.Id);
}
}
}
}
foreach (var traitor in removeList)
{
traitors.RemoveAll(t => t.Id == traitor);
}
if (traitors.Count == 0) return new EscapeShuttleCondition{}; //You were made a traitor by admins, and are the first/only.
return new RandomTraitorProgressCondition { _targetMind = IoCManager.Resolve<IRobustRandom>().Pick(traitors).Id };
}
public string Title
{
get
{
var targetName = string.Empty;
var entities = IoCManager.Resolve<IEntityManager>();
var jobs = entities.System<SharedJobSystem>();
var jobName = jobs.MindTryGetJobName(_targetMind);
if (_targetMind == null)
return Loc.GetString("objective-condition-other-traitor-progress-title", ("targetName", targetName), ("job", jobName));
if (entities.TryGetComponent(_targetMind, out MindComponent? mind) &&
mind.OwnedEntity is {Valid: true} owned)
{
targetName = entities.GetComponent<MetaDataComponent>(owned).EntityName;
}
return Loc.GetString("objective-condition-other-traitor-progress-title", ("targetName", targetName), ("job", jobName));
}
}
public string Description => Loc.GetString("objective-condition-other-traitor-progress-description");
public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ("Objects/Misc/bureaucracy.rsi"), "folder-white");
public float Progress
{
get
{
float total = 0f; // how much progress they have
float max = 0f; // how much progress is needed for 100%
if (_targetMind == null)
{
Logger.Error("Null target on RandomTraitorProgressCondition.");
return 1f;
}
var entities = IoCManager.Resolve<IEntityManager>();
if (entities.TryGetComponent(_targetMind, out MindComponent? mind))
{
foreach (var objective in mind.AllObjectives)
{
foreach (var condition in objective.Conditions)
{
max++; // things can only be up to 100% complete yeah
total += condition.Progress;
}
}
}
if (max == 0f)
{
Logger.Error("RandomTraitorProgressCondition assigned someone with no objectives to be helped.");
return 1f;
}
var completion = total / max;
if (completion >= 0.5f)
return 1f;
else
return completion / 0.5f;
}
}
public float Difficulty => 2.5f;
public bool Equals(IObjectiveCondition? other)
{
return other is RandomTraitorProgressCondition kpc && Equals(_targetMind, kpc._targetMind);
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return obj is RandomTraitorProgressCondition alive && alive.Equals(this);
}
public override int GetHashCode()
{
return _targetMind?.GetHashCode() ?? 0;
}
}
}

View File

@@ -1,75 +0,0 @@
using Content.Server.Roles;
using Content.Server.Warps;
using Content.Shared.Mind;
using Content.Shared.Objectives.Interfaces;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.Objectives.Conditions;
/// <summary>
/// Objective condition that requires the player to be a ninja and have detonated their spider charge.
/// </summary>
[DataDefinition]
public sealed partial class SpiderChargeCondition : IObjectiveCondition
{
private EntityUid? _mind;
public IObjectiveCondition GetAssigned(EntityUid uid, MindComponent mind)
{
return new SpiderChargeCondition {
_mind = uid
};
}
public string Title
{
get
{
var entMan = IoCManager.Resolve<IEntityManager>();
if (!entMan.TryGetComponent<NinjaRoleComponent>(_mind, out var role)
|| role.SpiderChargeTarget == null
|| !entMan.TryGetComponent<WarpPointComponent>(role.SpiderChargeTarget, out var warp)
|| warp.Location == null)
// this should never really happen but eh
return Loc.GetString("objective-condition-spider-charge-no-target");
return Loc.GetString("objective-condition-spider-charge-title", ("location", warp.Location));
}
}
public string Description => Loc.GetString("objective-condition-spider-charge-description");
public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ResPath("Objects/Weapons/Bombs/spidercharge.rsi"), "icon");
public float Progress
{
get
{
var entMan = IoCManager.Resolve<EntityManager>();
if (!entMan.TryGetComponent<NinjaRoleComponent>(_mind, out var role))
return 0f;
return role.SpiderChargeDetonated ? 1f : 0f;
}
}
public float Difficulty => 2.5f;
public bool Equals(IObjectiveCondition? other)
{
return other is SpiderChargeCondition cond && Equals(_mind, cond._mind);
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return obj is SpiderChargeCondition cond && cond.Equals(this);
}
public override int GetHashCode()
{
return _mind?.GetHashCode() ?? 0;
}
}

View File

@@ -1,115 +0,0 @@
using Content.Shared.Mind;
using Content.Shared.Objectives.Interfaces;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Server.Objectives.Conditions
{
// Oh god my eyes
[UsedImplicitly]
[DataDefinition]
public sealed partial class StealCondition : IObjectiveCondition, ISerializationHooks
{
private EntityUid? _mind;
[DataField("prototype")] private string _prototypeId = string.Empty;
/// <summary>
/// Help newer players by saying e.g. "steal the chief engineer's advanced magboots"
/// instead of "steal advanced magboots. Should be a loc string.
/// </summary>
[DataField("owner")] private string? _owner = null;
public IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind)
{
return new StealCondition
{
_mind = mindId,
_prototypeId = _prototypeId,
_owner = _owner
};
}
private string PrototypeName =>
IoCManager.Resolve<IPrototypeManager>().TryIndex<EntityPrototype>(_prototypeId, out var prototype)
? prototype.Name
: "[CANNOT FIND NAME]";
public string Title =>
_owner == null
? Loc.GetString("objective-condition-steal-title-no-owner", ("itemName", Loc.GetString(PrototypeName)))
: Loc.GetString("objective-condition-steal-title", ("owner", Loc.GetString(_owner)), ("itemName", Loc.GetString(PrototypeName)));
public string Description => Loc.GetString("objective-condition-steal-description",("itemName", Loc.GetString(PrototypeName)));
public SpriteSpecifier Icon => new SpriteSpecifier.EntityPrototype(_prototypeId);
public float Progress
{
get
{
var entMan = IoCManager.Resolve<IEntityManager>();
// TODO make this a container system function
// or: just iterate through transform children, instead of containers?
var metaQuery = entMan.GetEntityQuery<MetaDataComponent>();
var managerQuery = entMan.GetEntityQuery<ContainerManagerComponent>();
var stack = new Stack<ContainerManagerComponent>();
if (!entMan.TryGetComponent(_mind, out MindComponent? mind))
return 0;
if (!metaQuery.TryGetComponent(mind.OwnedEntity, out var meta))
return 0;
if (meta.EntityPrototype?.ID == _prototypeId)
return 1;
if (!managerQuery.TryGetComponent(mind.OwnedEntity, out var currentManager))
return 0;
do
{
foreach (var container in currentManager.Containers.Values)
{
foreach (var entity in container.ContainedEntities)
{
if (metaQuery.GetComponent(entity).EntityPrototype?.ID == _prototypeId)
return 1;
if (!managerQuery.TryGetComponent(entity, out var containerManager))
continue;
stack.Push(containerManager);
}
}
} while (stack.TryPop(out currentManager));
return 0;
}
}
public float Difficulty => 2.25f;
public bool Equals(IObjectiveCondition? other)
{
return other is StealCondition stealCondition &&
Equals(_mind, stealCondition._mind) &&
_prototypeId == stealCondition._prototypeId;
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((StealCondition) obj);
}
public override int GetHashCode()
{
return HashCode.Combine(_mind, _prototypeId);
}
}
}

View File

@@ -1,70 +0,0 @@
using Content.Server.Roles;
using Content.Shared.Mind;
using Content.Shared.Objectives.Interfaces;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.Objectives.Conditions;
/// <summary>
/// Objective condition that requires the player to be a ninja and have stolen at least a random number of technologies.
/// </summary>
[DataDefinition]
public sealed partial class StealResearchCondition : IObjectiveCondition
{
private EntityUid? _mind;
private int _target;
public IObjectiveCondition GetAssigned(EntityUid uid, MindComponent mind)
{
// TODO: clamp to number of research nodes in a single discipline maybe so easily maintainable
return new StealResearchCondition {
_mind = uid,
_target = IoCManager.Resolve<IRobustRandom>().Next(5, 10)
};
}
public string Title => Loc.GetString("objective-condition-steal-research-title", ("count", _target));
public string Description => Loc.GetString("objective-condition-steal-research-description");
public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ResPath("Structures/Machines/server.rsi"), "server");
public float Progress
{
get
{
// prevent divide-by-zero
if (_target == 0)
return 1f;
var entMan = IoCManager.Resolve<IEntityManager>();
if (!entMan.TryGetComponent<NinjaRoleComponent>(_mind, out var role))
return 0f;
if (role.DownloadedNodes.Count >= _target)
return 1f;
return (float) role.DownloadedNodes.Count / (float) _target;
}
}
public float Difficulty => 2.5f;
public bool Equals(IObjectiveCondition? other)
{
return other is StealResearchCondition cond && Equals(_mind, cond._mind) && _target == cond._target;
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return obj is StealResearchCondition cond && cond.Equals(this);
}
public override int GetHashCode()
{
return HashCode.Combine(_mind?.GetHashCode() ?? 0, _target);
}
}

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