mirror of
https://github.com/wega-team/ss14-wega.git
synced 2026-02-15 03:31:44 +01:00
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:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'}" />
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
7
Content.Client/Objectives/Systems/ObjectivesSystem.cs
Normal file
7
Content.Client/Objectives/Systems/ObjectivesSystem.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using Content.Shared.Objectives.Systems;
|
||||
|
||||
namespace Content.Client.Objectives.Systems;
|
||||
|
||||
public sealed class ObjectivesSystem : SharedObjectivesSystem
|
||||
{
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -29,6 +29,9 @@ public sealed class NavMapSystem : SharedNavMapSystem
|
||||
TileData = data,
|
||||
});
|
||||
}
|
||||
|
||||
component.Beacons.Clear();
|
||||
component.Beacons.AddRange(state.Beacons);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
10
Content.Client/UserInterface/IEntityControl.cs
Normal file
10
Content.Client/UserInterface/IEntityControl.cs
Normal 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; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(); };
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 =
|
||||
{
|
||||
|
||||
56
Content.IntegrationTests/Tests/Replays/ReplayTests.cs
Normal file
56
Content.IntegrationTests/Tests/Replays/ReplayTests.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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!;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}%)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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)};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user