forked from space-syndicate/space-station-14
Compare commits
66 Commits
master
...
april-fool
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d36345cf53 | ||
|
|
959e695315 | ||
|
|
4434b3752c | ||
|
|
8ca2182a7c | ||
|
|
bd954ca01a | ||
|
|
10ecfd4df1 | ||
|
|
bba7e96927 | ||
|
|
fdc79e4d2a | ||
|
|
cce22a32f3 | ||
|
|
0dc500bbff | ||
|
|
5e6581ce6a | ||
|
|
2c2f2e0045 | ||
|
|
a9f7ea5185 | ||
|
|
c7df69d853 | ||
|
|
b1c6fce89c | ||
|
|
60839d7917 | ||
|
|
acde17d87b | ||
|
|
b6a735774b | ||
|
|
732cb8b0d6 | ||
|
|
404bbbf309 | ||
|
|
76212b877e | ||
|
|
ad02129045 | ||
|
|
8b6996cbae | ||
|
|
dfcb7b3c97 | ||
|
|
6bfd1d8f11 | ||
|
|
40deda74ab | ||
|
|
ce34252cd3 | ||
|
|
a9aa5011c1 | ||
|
|
25e7acaa9b | ||
|
|
9ab3523871 | ||
|
|
ad31749b55 | ||
|
|
e29c54d64e | ||
|
|
dbf9f71ee8 | ||
|
|
be2d3c4277 | ||
|
|
a487ed9df7 | ||
|
|
a5b06cb96f | ||
|
|
3b9fd4867e | ||
|
|
00035721e4 | ||
|
|
cf61150ebd | ||
|
|
3b46e649ee | ||
|
|
cefc37903e | ||
|
|
f8a8e7bafc | ||
|
|
59eb53d4f7 | ||
|
|
18581a01ca | ||
|
|
763089570d | ||
|
|
50fe4337da | ||
|
|
3c6e67adee | ||
|
|
ce1d616f87 | ||
|
|
b2299a2e5b | ||
|
|
c9be773c85 | ||
|
|
a6aaa6464b | ||
|
|
d6b8e5e243 | ||
|
|
7e53ef32e2 | ||
|
|
5cc78c2c75 | ||
|
|
8128759ea8 | ||
|
|
3f3c163591 | ||
|
|
6878b89a1c | ||
|
|
84f24d57ef | ||
|
|
ef9934c61d | ||
|
|
7ea9588172 | ||
|
|
f2b6dcf329 | ||
|
|
ceba6f1585 | ||
|
|
b71c8b7ec3 | ||
|
|
eeb8d0f630 | ||
|
|
979bdfcfa6 | ||
|
|
311a027797 |
@@ -203,7 +203,7 @@ public sealed class BackgroundAudioSystem : EntitySystem
|
||||
_ambientParams.WithVolume(_ambientParams.Volume + _configManager.GetCVar(CCVars.AmbienceVolume)));
|
||||
}
|
||||
|
||||
private void EndAmbience()
|
||||
public void EndAmbience()
|
||||
{
|
||||
_playingCollection = null;
|
||||
_ambientStream?.Stop();
|
||||
|
||||
@@ -52,6 +52,12 @@ namespace Content.Client.Forensics
|
||||
{
|
||||
text.AppendLine(fiber);
|
||||
}
|
||||
text.AppendLine();
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-dnas"));
|
||||
foreach (var dna in msg.DNAs)
|
||||
{
|
||||
text.AppendLine(dna);
|
||||
}
|
||||
Diagnostics.Text = text.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Gravity;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Gravity
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class GravityGeneratorVisualizer : AppearanceVisualizer
|
||||
{
|
||||
[DataField("spritemap")]
|
||||
private Dictionary<string, string> _rawSpriteMap
|
||||
{
|
||||
get => _spriteMap.ToDictionary(x => x.Key.ToString().ToLower(), x => x.Value);
|
||||
set
|
||||
{
|
||||
_spriteMap.Clear();
|
||||
// Get Sprites for each status
|
||||
foreach (var status in (GravityGeneratorStatus[]) Enum.GetValues(typeof(GravityGeneratorStatus)))
|
||||
{
|
||||
if (value.TryGetValue(status.ToString().ToLower(), out var sprite))
|
||||
{
|
||||
_spriteMap[status] = sprite;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<GravityGeneratorStatus, string> _spriteMap = new();
|
||||
|
||||
[Obsolete("Subscribe to your component being initialised instead.")]
|
||||
public override void InitializeEntity(EntityUid entity)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
|
||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(entity, out SpriteComponent? sprite))
|
||||
return;
|
||||
|
||||
sprite.LayerMapReserveBlank(GravityGeneratorVisualLayers.Base);
|
||||
sprite.LayerMapReserveBlank(GravityGeneratorVisualLayers.Core);
|
||||
}
|
||||
|
||||
[Obsolete("Subscribe to AppearanceChangeEvent instead.")]
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
var sprite = IoCManager.Resolve<IEntityManager>().GetComponent<SpriteComponent>(component.Owner);
|
||||
|
||||
if (component.TryGetData(GravityGeneratorVisuals.State, out GravityGeneratorStatus state))
|
||||
{
|
||||
if (_spriteMap.TryGetValue(state, out var spriteState))
|
||||
{
|
||||
var layer = sprite.LayerMapGet(GravityGeneratorVisualLayers.Base);
|
||||
sprite.LayerSetState(layer, spriteState);
|
||||
}
|
||||
}
|
||||
|
||||
if (component.TryGetData(GravityGeneratorVisuals.Charge, out float charge))
|
||||
{
|
||||
var layer = sprite.LayerMapGet(GravityGeneratorVisualLayers.Core);
|
||||
switch (charge)
|
||||
{
|
||||
case < 0.2f:
|
||||
sprite.LayerSetVisible(layer, false);
|
||||
break;
|
||||
case >= 0.2f and < 0.4f:
|
||||
sprite.LayerSetVisible(layer, true);
|
||||
sprite.LayerSetState(layer, "startup");
|
||||
break;
|
||||
case >= 0.4f and < 0.6f:
|
||||
sprite.LayerSetVisible(layer, true);
|
||||
sprite.LayerSetState(layer, "idle");
|
||||
break;
|
||||
case >= 0.6f and < 0.8f:
|
||||
sprite.LayerSetVisible(layer, true);
|
||||
sprite.LayerSetState(layer, "activating");
|
||||
break;
|
||||
default:
|
||||
sprite.LayerSetVisible(layer, true);
|
||||
sprite.LayerSetState(layer, "activated");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum GravityGeneratorVisualLayers : byte
|
||||
{
|
||||
Base,
|
||||
Core
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,66 @@
|
||||
using Content.Shared.Gravity;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Gravity;
|
||||
|
||||
public sealed partial class GravitySystem : SharedGravitySystem
|
||||
{
|
||||
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SharedGravityGeneratorComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
InitializeShake();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the visible state of gravity generators are synced with their sprites.
|
||||
/// </summary>
|
||||
private void OnAppearanceChange(EntityUid uid, SharedGravityGeneratorComponent comp, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
if (_appearanceSystem.TryGetData<GravityGeneratorStatus>(uid, GravityGeneratorVisuals.State, out var state, args.Component))
|
||||
{
|
||||
if (comp.SpriteMap.TryGetValue(state, out var spriteState))
|
||||
{
|
||||
var layer = args.Sprite.LayerMapGet(GravityGeneratorVisualLayers.Base);
|
||||
args.Sprite.LayerSetState(layer, spriteState);
|
||||
}
|
||||
}
|
||||
|
||||
if (_appearanceSystem.TryGetData<float>(uid, GravityGeneratorVisuals.Charge, out var charge, args.Component))
|
||||
{
|
||||
var layer = args.Sprite.LayerMapGet(GravityGeneratorVisualLayers.Core);
|
||||
switch (charge)
|
||||
{
|
||||
case < 0.2f:
|
||||
args.Sprite.LayerSetVisible(layer, false);
|
||||
break;
|
||||
case >= 0.2f and < 0.4f:
|
||||
args.Sprite.LayerSetVisible(layer, true);
|
||||
args.Sprite.LayerSetState(layer, comp.CoreStartupState);
|
||||
break;
|
||||
case >= 0.4f and < 0.6f:
|
||||
args.Sprite.LayerSetVisible(layer, true);
|
||||
args.Sprite.LayerSetState(layer, comp.CoreIdleState);
|
||||
break;
|
||||
case >= 0.6f and < 0.8f:
|
||||
args.Sprite.LayerSetVisible(layer, true);
|
||||
args.Sprite.LayerSetState(layer, comp.CoreActivatingState);
|
||||
break;
|
||||
default:
|
||||
args.Sprite.LayerSetVisible(layer, true);
|
||||
args.Sprite.LayerSetState(layer, comp.CoreActivatedState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum GravityGeneratorVisualLayers : byte
|
||||
{
|
||||
Base,
|
||||
Core
|
||||
}
|
||||
|
||||
7
Content.Client/Medical/Surgery/SurgeryRealmSystem.cs
Normal file
7
Content.Client/Medical/Surgery/SurgeryRealmSystem.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using Content.Shared.Medical.Surgery;
|
||||
|
||||
namespace Content.Client.Medical.Surgery;
|
||||
|
||||
public sealed class SurgeryRealmSystem : SharedSurgeryRealmSystem
|
||||
{
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.Shared._Afterlight.ThirdDimension;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -19,6 +20,7 @@ public sealed class ParallaxOverlay : Overlay
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IParallaxManager _manager = default!;
|
||||
private readonly ParallaxSystem _parallax;
|
||||
private readonly SharedZLevelSystem _zlevel = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowWorld;
|
||||
|
||||
@@ -27,11 +29,12 @@ public sealed class ParallaxOverlay : Overlay
|
||||
ZIndex = ParallaxSystem.ParallaxZIndex;
|
||||
IoCManager.InjectDependencies(this);
|
||||
_parallax = _entManager.System<ParallaxSystem>();
|
||||
_zlevel = _entManager.System<SharedZLevelSystem>();
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace || _entManager.HasComponent<BiomeComponent>(_mapManager.GetMapEntityId(args.MapId)))
|
||||
if (args.MapId == MapId.Nullspace || _entManager.HasComponent<BiomeComponent>(_mapManager.GetMapEntityId(args.MapId)) || _zlevel.MapBelow[(int)args.MapId] != null)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -118,7 +118,11 @@ public sealed partial class GeneralStationRecordConsoleWindow : DefaultWindow
|
||||
},
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("general-station-record-console-record-fingerprint", ("fingerprint", record.Fingerprint is null ? Loc.GetString("generic-not-available-shorthand") : record.Fingerprint))
|
||||
Text = Loc.GetString("general-station-record-console-record-fingerprint", ("fingerprint", record.Fingerprint ?? Loc.GetString("generic-not-available-shorthand")))
|
||||
},
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("general-station-record-console-record-dna", ("dna", record.DNA ?? Loc.GetString("generic-not-available-shorthand")))
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -40,16 +40,12 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
|
||||
scannerRevealed &= !ShowAll; // no transparency for show-subfloor mode.
|
||||
|
||||
var revealed = !covered || ShowAll || scannerRevealed;
|
||||
var transparency = scannerRevealed ? component.ScannerTransparency : 1f;
|
||||
|
||||
// set visibility & color of each layer
|
||||
foreach (var layer in args.Sprite.AllLayers)
|
||||
{
|
||||
// pipe connection visuals are updated AFTER this, and may re-hide some layers
|
||||
layer.Visible = revealed;
|
||||
|
||||
if (layer.Visible)
|
||||
layer.Color = layer.Color.WithAlpha(transparency);
|
||||
}
|
||||
|
||||
// Is there some layer that is always visible?
|
||||
|
||||
10
Content.Client/SubFloor/TrayRevealedComponent.cs
Normal file
10
Content.Client/SubFloor/TrayRevealedComponent.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Content.Client.SubFloor;
|
||||
|
||||
/// <summary>
|
||||
/// Added clientside if an entity is revealed for TRay.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class TrayRevealedComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
144
Content.Client/SubFloor/TrayScannerSystem.cs
Normal file
144
Content.Client/SubFloor/TrayScannerSystem.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using Content.Client.Hands;
|
||||
using Content.Shared.SubFloor;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.SubFloor;
|
||||
|
||||
public sealed class TrayScannerSystem : SharedTrayScannerSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private const string TRayAnimationKey = "trays";
|
||||
private const double AnimationLength = 0.5;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
// TODO: Multiple viewports or w/e
|
||||
var player = _player.LocalPlayer?.ControlledEntity;
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
if (!xformQuery.TryGetComponent(player, out var playerXform))
|
||||
return;
|
||||
|
||||
var playerPos = _transform.GetWorldPosition(playerXform, xformQuery);
|
||||
var playerMap = playerXform.MapID;
|
||||
var range = 0f;
|
||||
|
||||
if (TryComp<HandsComponent>(player, out var playerHands) &&
|
||||
TryComp<TrayScannerComponent>(playerHands.ActiveHandEntity, out var scanner) && scanner.Enabled)
|
||||
{
|
||||
range = scanner.Range;
|
||||
|
||||
foreach (var comp in _lookup.GetComponentsInRange<SubFloorHideComponent>(playerMap, playerPos, range))
|
||||
{
|
||||
var uid = comp.Owner;
|
||||
if (!comp.IsUnderCover || !comp.BlockAmbience | !comp.BlockInteractions)
|
||||
continue;
|
||||
|
||||
EnsureComp<TrayRevealedComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
var revealedQuery = AllEntityQuery<TrayRevealedComponent, SpriteComponent, TransformComponent>();
|
||||
var subfloorQuery = GetEntityQuery<SubFloorHideComponent>();
|
||||
|
||||
while (revealedQuery.MoveNext(out var uid, out _, out var sprite, out var xform))
|
||||
{
|
||||
var worldPos = _transform.GetWorldPosition(xform, xformQuery);
|
||||
|
||||
// Revealing
|
||||
// Add buffer range to avoid flickers.
|
||||
if (subfloorQuery.HasComponent(uid) &&
|
||||
xform.MapID != MapId.Nullspace &&
|
||||
xform.MapID == playerMap &&
|
||||
xform.Anchored &&
|
||||
range != 0f &&
|
||||
(playerPos - worldPos).Length <= range + 0.5f)
|
||||
{
|
||||
// Due to the fact client is predicting this server states will reset it constantly
|
||||
if ((!_appearance.TryGetData(uid, SubFloorVisuals.ScannerRevealed, out bool value) || !value) &&
|
||||
sprite.Color.A > SubfloorRevealAlpha)
|
||||
{
|
||||
sprite.Color = sprite.Color.WithAlpha(0f);
|
||||
}
|
||||
|
||||
SetRevealed(uid, true);
|
||||
|
||||
if (sprite.Color.A >= SubfloorRevealAlpha || _animation.HasRunningAnimation(uid, TRayAnimationKey))
|
||||
continue;
|
||||
|
||||
_animation.Play(uid, new Animation()
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(AnimationLength),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(SpriteComponent),
|
||||
Property = nameof(SpriteComponent.Color),
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(sprite.Color.WithAlpha(0f), 0f),
|
||||
new AnimationTrackProperty.KeyFrame(sprite.Color.WithAlpha(SubfloorRevealAlpha), (float) AnimationLength)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, TRayAnimationKey);
|
||||
}
|
||||
// Hiding
|
||||
else
|
||||
{
|
||||
// Hidden completely so unreveal and reset the alpha.
|
||||
if (sprite.Color.A <= 0f)
|
||||
{
|
||||
SetRevealed(uid, false);
|
||||
RemCompDeferred<TrayRevealedComponent>(uid);
|
||||
sprite.Color = sprite.Color.WithAlpha(1f);
|
||||
continue;
|
||||
}
|
||||
|
||||
SetRevealed(uid, true);
|
||||
|
||||
if (_animation.HasRunningAnimation(uid, TRayAnimationKey))
|
||||
continue;
|
||||
|
||||
_animation.Play(uid, new Animation()
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(AnimationLength),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(SpriteComponent),
|
||||
Property = nameof(SpriteComponent.Color),
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(sprite.Color, 0f),
|
||||
new AnimationTrackProperty.KeyFrame(sprite.Color.WithAlpha(0f), (float) AnimationLength)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, TRayAnimationKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetRevealed(EntityUid uid, bool value)
|
||||
{
|
||||
_appearance.SetData(uid, SubFloorVisuals.ScannerRevealed, value);
|
||||
}
|
||||
}
|
||||
11
Content.Client/Tips/ClippyUI.xaml
Normal file
11
Content.Client/Tips/ClippyUI.xaml
Normal file
@@ -0,0 +1,11 @@
|
||||
<tips:ClippyUI xmlns="https://spacestation14.io"
|
||||
xmlns:tips="clr-namespace:Content.Client.Tips"
|
||||
MinSize="64 64"
|
||||
Visible="False">
|
||||
<PanelContainer Name="LabelPanel" Access="Public" Visible="False" MaxWidth="300" MaxHeight="200">
|
||||
<ScrollContainer Name="ScrollingContents" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalExpand="True" VerticalExpand="True" HScrollEnabled="False" ReturnMeasure="True">
|
||||
<RichTextLabel Name="Label" Access="Public"/>
|
||||
</ScrollContainer>
|
||||
</PanelContainer>
|
||||
<SpriteView Name="Entity" Access="Public" MinSize="128 128"/>
|
||||
</tips:ClippyUI>
|
||||
53
Content.Client/Tips/ClippyUI.xaml.cs
Normal file
53
Content.Client/Tips/ClippyUI.xaml.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Content.Client.Paper;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Tips;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ClippyUI : UIWidget
|
||||
{
|
||||
public ClippyState State = ClippyState.Hidden;
|
||||
|
||||
public ClippyUI()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void InitLabel(PaperVisualsComponent? visuals, IResourceCache resCache)
|
||||
{
|
||||
if (visuals == null)
|
||||
return;
|
||||
|
||||
Label.ModulateSelfOverride = visuals.FontAccentColor;
|
||||
|
||||
if (visuals.BackgroundImagePath == null)
|
||||
return;
|
||||
|
||||
LabelPanel.ModulateSelfOverride = visuals.BackgroundModulate;
|
||||
var backgroundImage = resCache.GetResource<TextureResource>(visuals.BackgroundImagePath);
|
||||
var backgroundImageMode = visuals.BackgroundImageTile ? StyleBoxTexture.StretchMode.Tile : StyleBoxTexture.StretchMode.Stretch;
|
||||
var backgroundPatchMargin = visuals.BackgroundPatchMargin;
|
||||
LabelPanel.PanelOverride = new StyleBoxTexture
|
||||
{
|
||||
Texture = backgroundImage,
|
||||
TextureScale = visuals.BackgroundScale,
|
||||
Mode = backgroundImageMode,
|
||||
PatchMarginLeft = backgroundPatchMargin.Left,
|
||||
PatchMarginBottom = backgroundPatchMargin.Bottom,
|
||||
PatchMarginRight = backgroundPatchMargin.Right,
|
||||
PatchMarginTop = backgroundPatchMargin.Top
|
||||
};
|
||||
}
|
||||
|
||||
public enum ClippyState : byte
|
||||
{
|
||||
Hidden,
|
||||
Revealing,
|
||||
Speaking,
|
||||
Hiding,
|
||||
}
|
||||
}
|
||||
244
Content.Client/Tips/ClippyUIController.cs
Normal file
244
Content.Client/Tips/ClippyUIController.cs
Normal file
@@ -0,0 +1,244 @@
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Paper;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Tips;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using static Content.Client.Tips.ClippyUI;
|
||||
|
||||
namespace Content.Client.Tips;
|
||||
|
||||
public sealed class ClippyUIController : UIController
|
||||
{
|
||||
[Dependency] private readonly IStateManager _state = default!;
|
||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IResourceCache _resCache = default!;
|
||||
|
||||
[UISystemDependency] private readonly AudioSystem? _audio = default;
|
||||
|
||||
public const float Padding = 50;
|
||||
public static Angle WaddleRotation = Angle.FromDegrees(10);
|
||||
|
||||
private EntityUid _entity;
|
||||
private float _secondsUntilNextState;
|
||||
private int _previousStep = 0;
|
||||
private ClippyEvent? _currentMessage;
|
||||
private readonly Queue<ClippyEvent> _queuedMessages = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_conHost.RegisterCommand("local_clippy", ClippyCommand);
|
||||
UIManager.OnScreenChanged += OnScreenChanged;
|
||||
}
|
||||
|
||||
private void ClippyCommand(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
shell.WriteLine("usage: clippy <message> [entity prototype] [speak time] [animate time] [waddle]");
|
||||
return;
|
||||
}
|
||||
|
||||
var ev = new ClippyEvent(args[0]);
|
||||
|
||||
string proto;
|
||||
if (args.Length > 1)
|
||||
{
|
||||
ev.Proto = args[1];
|
||||
|
||||
if (!_protoMan.HasIndex<EntityPrototype>(ev.Proto))
|
||||
{
|
||||
shell.WriteError($"Unknown prototype: {ev.Proto}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (args.Length > 2)
|
||||
ev.SpeakTime = float.Parse(args[2]);
|
||||
|
||||
if (args.Length > 3)
|
||||
ev.SlideTime = float.Parse(args[3]);
|
||||
|
||||
if (args.Length > 4)
|
||||
ev.WaddleInterval = float.Parse(args[4]);
|
||||
|
||||
AddMessage(ev);
|
||||
}
|
||||
|
||||
public void AddMessage(ClippyEvent ev)
|
||||
{
|
||||
_queuedMessages.Enqueue(ev);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
var screen = UIManager.ActiveScreen;
|
||||
if (screen == null)
|
||||
{
|
||||
_queuedMessages.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
var clippy = screen.GetOrAddWidget<ClippyUI>();
|
||||
_secondsUntilNextState -= args.DeltaSeconds;
|
||||
|
||||
if (_secondsUntilNextState <= 0)
|
||||
NextState(clippy);
|
||||
else
|
||||
{
|
||||
var pos = UpdatePosition(clippy, screen.Size, args); ;
|
||||
LayoutContainer.SetPosition(clippy, pos);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 UpdatePosition(ClippyUI clippy, Vector2 screenSize, FrameEventArgs args)
|
||||
{
|
||||
if (_currentMessage == null)
|
||||
return default;
|
||||
|
||||
var slideTime = _currentMessage.SlideTime;
|
||||
|
||||
var offset = clippy.State switch
|
||||
{
|
||||
ClippyState.Hidden => 0,
|
||||
ClippyState.Revealing => Math.Clamp(1 - _secondsUntilNextState / slideTime, 0, 1),
|
||||
ClippyState.Hiding => Math.Clamp(_secondsUntilNextState / slideTime, 0, 1),
|
||||
_ => 1,
|
||||
};
|
||||
|
||||
var waddle = _currentMessage.WaddleInterval;
|
||||
|
||||
if (_currentMessage == null
|
||||
|| waddle <= 0
|
||||
|| clippy.State == ClippyState.Hidden
|
||||
|| clippy.State == ClippyState.Speaking
|
||||
|| !EntityManager.TryGetComponent(_entity, out SpriteComponent? sprite))
|
||||
{
|
||||
return (screenSize.X - offset * (clippy.DesiredSize.X + Padding), (screenSize.Y - clippy.DesiredSize.Y) / 2);
|
||||
}
|
||||
|
||||
var numSteps = (int) Math.Ceiling(slideTime / waddle);
|
||||
var curStep = (int) Math.Floor(numSteps * offset);
|
||||
var stepSize = (clippy.DesiredSize.X + Padding) / numSteps;
|
||||
|
||||
if (curStep != _previousStep)
|
||||
{
|
||||
_previousStep = curStep;
|
||||
sprite.Rotation = sprite.Rotation > 0
|
||||
? -WaddleRotation
|
||||
: WaddleRotation;
|
||||
|
||||
if (EntityManager.TryGetComponent(_entity, out FootstepModifierComponent? step))
|
||||
{
|
||||
var audioParams = step.Sound.Params
|
||||
.AddVolume(-7f)
|
||||
.WithVariation(0.1f);
|
||||
_audio?.PlayGlobal(step.Sound, EntityUid.Invalid, audioParams);
|
||||
}
|
||||
}
|
||||
|
||||
return (screenSize.X - stepSize * curStep, (screenSize.Y - clippy.DesiredSize.Y) / 2);
|
||||
}
|
||||
|
||||
private void NextState(ClippyUI clippy)
|
||||
{
|
||||
SpriteComponent? sprite;
|
||||
switch (clippy.State)
|
||||
{
|
||||
case ClippyState.Hidden:
|
||||
if (!_queuedMessages.TryDequeue(out var next))
|
||||
return;
|
||||
|
||||
_entity = EntityManager.SpawnEntity(next.Proto ?? _cfg.GetCVar(CCVars.ClippyEntity), MapCoordinates.Nullspace);
|
||||
if (!EntityManager.TryGetComponent(_entity, out sprite))
|
||||
return;
|
||||
clippy.InitLabel(EntityManager.GetComponentOrNull<PaperVisualsComponent>(_entity), _resCache);
|
||||
|
||||
var scale = sprite.Scale;
|
||||
sprite.Scale = Vector2.One;
|
||||
clippy.Entity.Sprite = sprite;
|
||||
clippy.Entity.Scale = scale;
|
||||
|
||||
_currentMessage = next;
|
||||
_secondsUntilNextState = next.SlideTime;
|
||||
clippy.State = ClippyState.Revealing;
|
||||
_previousStep = 0;
|
||||
sprite.LayerSetAnimationTime("revealing", 0);
|
||||
sprite.LayerSetVisible("revealing", true);
|
||||
sprite.LayerSetVisible("speaking", false);
|
||||
sprite.LayerSetVisible("hiding", false);
|
||||
sprite.Rotation = 0;
|
||||
clippy.Label.SetMarkup(_currentMessage.Msg);
|
||||
clippy.LabelPanel.Visible = false;
|
||||
clippy.Visible = true;
|
||||
sprite.Visible = true;
|
||||
break;
|
||||
|
||||
case ClippyState.Revealing:
|
||||
clippy.State = ClippyState.Speaking;
|
||||
if (!EntityManager.TryGetComponent(_entity, out sprite))
|
||||
return;
|
||||
sprite.Rotation = 0;
|
||||
_previousStep = 0;
|
||||
sprite.LayerSetAnimationTime("speaking", 0);
|
||||
sprite.LayerSetVisible("revealing", false);
|
||||
sprite.LayerSetVisible("speaking", true);
|
||||
sprite.LayerSetVisible("hiding", false);
|
||||
clippy.LabelPanel.Visible = true;
|
||||
clippy.InvalidateArrange();
|
||||
clippy.InvalidateMeasure();
|
||||
if (_currentMessage != null)
|
||||
_secondsUntilNextState = _currentMessage.SpeakTime;
|
||||
|
||||
break;
|
||||
|
||||
case ClippyState.Speaking:
|
||||
clippy.State = ClippyState.Hiding;
|
||||
if (!EntityManager.TryGetComponent(_entity, out sprite))
|
||||
return;
|
||||
sprite.LayerSetAnimationTime("hiding", 0);
|
||||
sprite.LayerSetVisible("revealing", false);
|
||||
sprite.LayerSetVisible("speaking", false);
|
||||
sprite.LayerSetVisible("hiding", true);
|
||||
clippy.LabelPanel.Visible = false;
|
||||
if (_currentMessage != null)
|
||||
_secondsUntilNextState = _currentMessage.SlideTime;
|
||||
break;
|
||||
|
||||
default: // finished hiding
|
||||
|
||||
EntityManager.DeleteEntity(_entity);
|
||||
_entity = default;
|
||||
clippy.Visible = false;
|
||||
_currentMessage = null;
|
||||
_secondsUntilNextState = 0;
|
||||
clippy.State = ClippyState.Hidden;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnScreenChanged((UIScreen? Old, UIScreen? New) ev)
|
||||
{
|
||||
ev.Old?.RemoveWidget<ClippyUI>();
|
||||
_currentMessage = null;
|
||||
EntityManager.DeleteEntity(_entity);
|
||||
}
|
||||
}
|
||||
20
Content.Client/Tips/TipsSystem.cs
Normal file
20
Content.Client/Tips/TipsSystem.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Content.Shared.Tips;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Tips;
|
||||
|
||||
public sealed class TipsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IUserInterfaceManager _uiMan = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<ClippyEvent>(OnClippyEv);
|
||||
}
|
||||
|
||||
private void OnClippyEv(ClippyEvent ev)
|
||||
{
|
||||
_uiMan.GetUIController<ClippyUIController>().AddMessage(ev);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="You feel like you are going to have a bad time"
|
||||
MinWidth="500">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Label Text="Begin surgery on yourself to heal all injuries?" HorizontalExpand="True" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="AcceptButton" Access="Public" Text="Accept" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
@@ -0,0 +1,14 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Surgery;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SelfRequestWindow : DefaultWindow
|
||||
{
|
||||
public SelfRequestWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Content.Shared.Medical.Surgery;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Surgery;
|
||||
|
||||
public sealed class SurgeryRealmOverlay : Overlay
|
||||
{
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly Font _font;
|
||||
|
||||
public SurgeryRealmOverlay(IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache)
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
ZIndex = 200;
|
||||
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (!EntitySystem.TryGet(out EntityLookupSystem? entityLookup))
|
||||
return;
|
||||
|
||||
var viewport = args.WorldAABB;
|
||||
|
||||
foreach (var heart in _entityManager.EntityQuery<SurgeryRealmHeartComponent>())
|
||||
{
|
||||
// if not on the same map, continue
|
||||
if (_entityManager.GetComponent<TransformComponent>(heart.Owner).MapID != _eyeManager.CurrentMap)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var aabb = entityLookup.GetWorldAABB(heart.Owner);
|
||||
|
||||
// if not on screen, continue
|
||||
if (!aabb.Intersects(in viewport))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var lineoffset = new Vector2(0f, 11f);
|
||||
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
|
||||
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
|
||||
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + lineoffset * 2, $"Health: {heart.Health}", Color.OrangeRed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Client.Audio;
|
||||
using Content.Client.Instruments;
|
||||
using Content.Shared.Medical.Surgery;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Surgery;
|
||||
|
||||
public sealed class SurgeryRealmUIController : UIController
|
||||
{
|
||||
[UISystemDependency] private readonly BackgroundAudioSystem? _backgroundAudio = default!;
|
||||
|
||||
private SelfRequestWindow _selfRequestWindow = default!;
|
||||
private SurgeryRealmOverlay _overlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_overlay = new SurgeryRealmOverlay(EntityManager, IoCManager.Resolve<IEyeManager>(),
|
||||
IoCManager.Resolve<IResourceCache>());
|
||||
IoCManager.Resolve<IOverlayManager>().AddOverlay(_overlay);
|
||||
|
||||
SubscribeNetworkEvent<SurgeryRealmRequestSelfEvent>(OnSurgeryRequestSelf);
|
||||
|
||||
// pjb dont look
|
||||
SubscribeNetworkEvent<SurgeryRealmStartEvent>((msg, args) => _ = OnSurgeryRealmStart(msg, args));
|
||||
}
|
||||
|
||||
private void OnSurgeryRequestSelf(SurgeryRealmRequestSelfEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
_selfRequestWindow?.Dispose();
|
||||
_selfRequestWindow = new SelfRequestWindow();
|
||||
_selfRequestWindow.OpenCentered();
|
||||
_selfRequestWindow.AcceptButton.OnPressed += buttonArgs =>
|
||||
{
|
||||
_selfRequestWindow.Dispose();
|
||||
IoCManager.Resolve<IEntityNetworkManager>().SendSystemNetworkMessage(new SurgeryRealmAcceptSelfEvent());
|
||||
};
|
||||
}
|
||||
|
||||
private async Task OnSurgeryRealmStart(SurgeryRealmStartEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
for (var i = 500; i < 10000; i += 500)
|
||||
{
|
||||
// I LOVE ambience code
|
||||
Timer.Spawn(i, () => _backgroundAudio?.EndAmbience());
|
||||
}
|
||||
|
||||
var file = IoCManager.Resolve<IResourceManager>()
|
||||
.ContentFileRead(new ResourcePath("/Audio/Surgery/midilovania.mid"));
|
||||
await using var memStream = new MemoryStream((int) file.Length);
|
||||
// 100ms delay is due to a race condition or something idk.
|
||||
// While we're waiting, load it into memory.
|
||||
await Task.WhenAll(Timer.Delay(100), file.CopyToAsync(memStream));
|
||||
|
||||
EntitySystem.Get<InstrumentSystem>()
|
||||
.OpenMidi(msg.Camera, memStream.GetBuffer().AsSpan(0, (int)memStream.Length));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Linq;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Systems.Gameplay;
|
||||
using Content.Shared._Afterlight.ThirdDimension;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -77,16 +79,28 @@ public sealed class ViewportUIController : UIController
|
||||
|
||||
base.FrameUpdate(e);
|
||||
|
||||
|
||||
Viewport.Viewport.Eye = _eyeManager.CurrentEye;
|
||||
|
||||
// verify that the current eye is not "null". Fuck IEyeManager.
|
||||
|
||||
var ent = _playerMan.LocalPlayer?.ControlledEntity;
|
||||
if (_entMan.TryGetComponent(ent, out ZViewComponent? view))
|
||||
{
|
||||
Viewport.Viewport.LowerEyes = view.DownViewEnts.Select(x =>
|
||||
{
|
||||
var eye = _entMan.GetComponent<EyeComponent>(x);
|
||||
eye.Rotation = _eyeManager.CurrentEye.Rotation;
|
||||
eye.DrawFov = false; // We're z leveling, no FoV.
|
||||
return eye.Eye!;
|
||||
}).ToArray();
|
||||
}
|
||||
if (_eyeManager.CurrentEye.Position != default || ent == null)
|
||||
return;
|
||||
|
||||
_entMan.TryGetComponent(ent, out EyeComponent? eye);
|
||||
|
||||
|
||||
if (eye?.Eye == _eyeManager.CurrentEye
|
||||
&& _entMan.GetComponent<TransformComponent>(ent.Value).WorldPosition == default)
|
||||
return; // nothing to worry about, the player is just in null space... actually that is probably a problem?
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -24,7 +25,9 @@ namespace Content.Client.Viewport
|
||||
|
||||
// Internal viewport creation is deferred.
|
||||
private IClydeViewport? _viewport;
|
||||
private List<IClydeViewport> _lowerPorts = new();
|
||||
private IEye? _eye;
|
||||
private IEye[] _lowerEyes = new IEye[] {};
|
||||
private Vector2i _viewportSize;
|
||||
private int _curRenderScale;
|
||||
private ScalingViewportStretchMode _stretchMode = ScalingViewportStretchMode.Bilinear;
|
||||
@@ -50,6 +53,27 @@ namespace Content.Client.Viewport
|
||||
}
|
||||
}
|
||||
|
||||
public IEye[] LowerEyes
|
||||
{
|
||||
get => _lowerEyes;
|
||||
set
|
||||
{
|
||||
var old = value;
|
||||
_lowerEyes = value;
|
||||
if (old.Length != value.Length)
|
||||
{
|
||||
InvalidateViewport();
|
||||
Logger.Debug("Eyes updated..");
|
||||
}
|
||||
|
||||
|
||||
foreach (var (eye, port) in _lowerEyes.Zip(_lowerPorts))
|
||||
{
|
||||
port.Eye = eye;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The size, in unscaled pixels, of the internal viewport.
|
||||
/// </summary>
|
||||
@@ -137,6 +161,11 @@ namespace Content.Client.Viewport
|
||||
|
||||
_viewport!.Render();
|
||||
|
||||
foreach (var viewport in _lowerPorts)
|
||||
{
|
||||
viewport.Render();
|
||||
}
|
||||
|
||||
if (_queuedScreenshots.Count != 0)
|
||||
{
|
||||
var callbacks = _queuedScreenshots.ToArray();
|
||||
@@ -155,6 +184,10 @@ namespace Content.Client.Viewport
|
||||
var drawBox = GetDrawBox();
|
||||
var drawBoxGlobal = drawBox.Translated(GlobalPixelPosition);
|
||||
_viewport.RenderScreenOverlaysBelow(handle, this, drawBoxGlobal);
|
||||
foreach (var viewport in _lowerPorts.AsEnumerable().Reverse())
|
||||
{
|
||||
handle.DrawTextureRect(viewport.RenderTarget.Texture, drawBox);
|
||||
}
|
||||
handle.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox);
|
||||
_viewport.RenderScreenOverlaysAbove(handle, this, drawBoxGlobal);
|
||||
}
|
||||
@@ -224,6 +257,23 @@ namespace Content.Client.Viewport
|
||||
{
|
||||
Filter = StretchMode == ScalingViewportStretchMode.Bilinear,
|
||||
});
|
||||
_viewport.ClearColor = Color.Blue.WithAlpha(0.02f);
|
||||
|
||||
_lowerPorts.Clear();
|
||||
for (var i = 0; i < _lowerEyes.Length; i++)
|
||||
{
|
||||
_lowerPorts.Add(_clyde.CreateViewport(
|
||||
ViewportSize * renderScale,
|
||||
new TextureSampleParameters
|
||||
{
|
||||
Filter = StretchMode == ScalingViewportStretchMode.Bilinear,
|
||||
}));
|
||||
_lowerPorts[i].RenderScale = (renderScale, renderScale);
|
||||
_lowerPorts[i].ClearColor = Color.Blue.WithAlpha(0.02f);
|
||||
|
||||
_lowerPorts[i].Eye = _lowerEyes[i];
|
||||
_lowerPorts[i].Eye!.Zoom = _lowerPorts[i].Eye!.Zoom * (1.02f + i * 0.02f);
|
||||
}
|
||||
|
||||
_viewport.RenderScale = (renderScale, renderScale);
|
||||
|
||||
@@ -241,6 +291,11 @@ namespace Content.Client.Viewport
|
||||
{
|
||||
_viewport?.Dispose();
|
||||
_viewport = null;
|
||||
foreach (var port in _lowerPorts)
|
||||
{
|
||||
port.Dispose();
|
||||
}
|
||||
_lowerPorts = new();
|
||||
}
|
||||
|
||||
public MapCoordinates ScreenToMap(Vector2 coords)
|
||||
@@ -291,7 +346,7 @@ namespace Content.Client.Viewport
|
||||
|
||||
private void EnsureViewportCreated()
|
||||
{
|
||||
if (_viewport == null)
|
||||
if (_viewport == null || _lowerPorts.Count != _lowerEyes.Length)
|
||||
{
|
||||
RegenerateViewport();
|
||||
}
|
||||
|
||||
17
Content.Client/zlevels/ZViewSystem.cs
Normal file
17
Content.Client/zlevels/ZViewSystem.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Content.Shared._Afterlight.ThirdDimension;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Client.zlevels;
|
||||
|
||||
public sealed class ZViewSystem : SharedZViewSystem
|
||||
{
|
||||
public override EntityUid SpawnViewEnt(EntityUid source, MapCoordinates loc)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override bool CanSetup(EntityUid source)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ namespace Content.IntegrationTests.Tests
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
config.SetCVar(CCVars.GameLobbyEnabled, true);
|
||||
config.SetCVar(CCVars.EmergencyShuttleTransitTime, 1f);
|
||||
config.SetCVar(CCVars.EmergencyShuttleMinTransitTime, 1f);
|
||||
config.SetCVar(CCVars.EmergencyShuttleDockTime, 1f);
|
||||
|
||||
roundEndSystem.DefaultCooldownDuration = TimeSpan.FromMilliseconds(100);
|
||||
@@ -116,7 +116,7 @@ namespace Content.IntegrationTests.Tests
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
config.SetCVar(CCVars.GameLobbyEnabled, false);
|
||||
config.SetCVar(CCVars.EmergencyShuttleTransitTime, CCVars.EmergencyShuttleTransitTime.DefaultValue);
|
||||
config.SetCVar(CCVars.EmergencyShuttleMinTransitTime, CCVars.EmergencyShuttleMinTransitTime.DefaultValue);
|
||||
config.SetCVar(CCVars.EmergencyShuttleDockTime, CCVars.EmergencyShuttleDockTime.DefaultValue);
|
||||
|
||||
roundEndSystem.DefaultCooldownDuration = TimeSpan.FromSeconds(30);
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed class GamePrototypeLoadManager : IGamePrototypeLoadManager
|
||||
{
|
||||
_netManager.RegisterNetMessage<GamePrototypeLoadMessage>(ClientLoadsPrototype);
|
||||
_netManager.Connected += NetManagerOnConnected;
|
||||
//_replay.OnRecordingStarted += OnStartReplayRecording;
|
||||
_replay.OnRecordingStarted += OnStartReplayRecording;
|
||||
}
|
||||
|
||||
private void OnStartReplayRecording((MappingDataNode, List<object>) initReplayData)
|
||||
|
||||
@@ -33,7 +33,7 @@ public sealed class NetworkResourceManager : SharedNetworkResourceManager
|
||||
_cfgManager.OnValueChanged(CCVars.ResourceUploadingStoreEnabled, value => StoreUploaded = value, true);
|
||||
|
||||
AutoDelete(_cfgManager.GetCVar(CCVars.ResourceUploadingStoreDeletionDays));
|
||||
//_replay.OnRecordingStarted += OnStartReplayRecording;
|
||||
_replay.OnRecordingStarted += OnStartReplayRecording;
|
||||
}
|
||||
|
||||
private void OnStartReplayRecording((MappingDataNode, List<object>) initReplayData)
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Server.Body.Components;
|
||||
using Content.Server.Chemistry.EntitySystems;
|
||||
using Content.Server.Chemistry.ReactionEffects;
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.Forensics;
|
||||
using Content.Server.HealthExaminable;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
@@ -292,7 +293,14 @@ public sealed class BloodstreamSystem : EntitySystem
|
||||
// Pass some of the chemstream into the spilled blood.
|
||||
var temp = component.ChemicalSolution.SplitSolution(component.BloodTemporarySolution.Volume / 10);
|
||||
component.BloodTemporarySolution.AddSolution(temp, _prototypeManager);
|
||||
_spillableSystem.SpillAt(uid, component.BloodTemporarySolution, "PuddleBlood", false);
|
||||
var puddle = _spillableSystem.SpillAt(uid, component.BloodTemporarySolution, "PuddleBlood", false);
|
||||
if (puddle != null)
|
||||
{
|
||||
var comp = EnsureComp<ForensicsComponent>(puddle.Owner); //TODO: Get rid of .Owner
|
||||
if (TryComp<DnaComponent>(uid, out var dna))
|
||||
comp.DNAs.Add(dna.DNA);
|
||||
}
|
||||
|
||||
component.BloodTemporarySolution.RemoveAllSolution();
|
||||
}
|
||||
|
||||
@@ -331,6 +339,13 @@ public sealed class BloodstreamSystem : EntitySystem
|
||||
component.BloodTemporarySolution.RemoveAllSolution();
|
||||
tempSol.AddSolution(component.ChemicalSolution, _prototypeManager);
|
||||
component.ChemicalSolution.RemoveAllSolution();
|
||||
_spillableSystem.SpillAt(uid, tempSol, "PuddleBlood", true);
|
||||
var puddle = _spillableSystem.SpillAt(uid, tempSol, "PuddleBlood", true);
|
||||
|
||||
if (puddle != null)
|
||||
{
|
||||
var comp = EnsureComp<ForensicsComponent>(puddle.Owner); //TODO: Get rid of .Owner
|
||||
if (TryComp<DnaComponent>(uid, out var dna))
|
||||
comp.DNAs.Add(dna.DNA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using Content.Server.Forensics;
|
||||
using Content.Server.Stack;
|
||||
using Content.Shared.Prototypes;
|
||||
using Content.Shared.Stacks;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
|
||||
namespace Content.Server.Destructible.Thresholds.Behaviors
|
||||
@@ -19,6 +21,9 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
|
||||
[DataField("offset")]
|
||||
public float Offset { get; set; } = 0.5f;
|
||||
|
||||
[DataField("transferForensics")]
|
||||
public bool DoTransferForensics = false;
|
||||
|
||||
public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
|
||||
{
|
||||
var position = system.EntityManager.GetComponent<TransformComponent>(owner).MapPosition;
|
||||
@@ -37,15 +42,34 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
|
||||
{
|
||||
var spawned = system.EntityManager.SpawnEntity(entityId, position.Offset(getRandomVector()));
|
||||
system.StackSystem.SetCount(spawned, count);
|
||||
|
||||
TransferForensics(spawned, system, owner);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
system.EntityManager.SpawnEntity(entityId, position.Offset(getRandomVector()));
|
||||
var spawned = system.EntityManager.SpawnEntity(entityId, position.Offset(getRandomVector()));
|
||||
|
||||
TransferForensics(spawned, system, owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void TransferForensics(EntityUid spawned, DestructibleSystem system, EntityUid owner)
|
||||
{
|
||||
if (!DoTransferForensics ||
|
||||
!system.EntityManager.TryGetComponent<ForensicsComponent>(owner, out var forensicsComponent))
|
||||
return;
|
||||
|
||||
var comp = system.EntityManager.EnsureComponent<ForensicsComponent>(spawned);
|
||||
comp.DNAs = forensicsComponent.DNAs;
|
||||
|
||||
if (!system.Random.Prob(0.4f))
|
||||
return;
|
||||
comp.Fingerprints = forensicsComponent.Fingerprints;
|
||||
comp.Fibers = forensicsComponent.Fibers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
Content.Server/Forensics/Components/DnaComponent.cs
Normal file
11
Content.Server/Forensics/Components/DnaComponent.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Content.Server.Forensics;
|
||||
|
||||
/// <summary>
|
||||
/// This component is for mobs that have DNA.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class DnaComponent : Component
|
||||
{
|
||||
[DataField("dna"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public string DNA = String.Empty;
|
||||
}
|
||||
@@ -20,6 +20,12 @@ namespace Content.Server.Forensics
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public List<string> Fibers = new();
|
||||
|
||||
/// <summary>
|
||||
/// DNA that the forensic scanner found from the <see cref="DNAComponent"/> on an entity.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public List<string> DNAs = new();
|
||||
|
||||
/// <summary>
|
||||
/// What is the name of the entity that was scanned last?
|
||||
/// </summary>
|
||||
|
||||
@@ -8,5 +8,8 @@ namespace Content.Server.Forensics
|
||||
|
||||
[DataField("fibers")]
|
||||
public HashSet<string> Fibers = new();
|
||||
|
||||
[DataField("dnas")]
|
||||
public HashSet<string> DNAs = new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace Content.Server.Forensics
|
||||
var state = new ForensicScannerBoundUserInterfaceState(
|
||||
component.Fingerprints,
|
||||
component.Fibers,
|
||||
component.DNAs,
|
||||
component.LastScannedName,
|
||||
component.PrintCooldown,
|
||||
component.PrintReadyAt);
|
||||
@@ -69,12 +70,14 @@ namespace Content.Server.Forensics
|
||||
{
|
||||
scanner.Fingerprints = new();
|
||||
scanner.Fibers = new();
|
||||
scanner.DNAs = new();
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
scanner.Fingerprints = forensics.Fingerprints.ToList();
|
||||
scanner.Fibers = forensics.Fibers.ToList();
|
||||
scanner.DNAs = forensics.DNAs.ToList();
|
||||
}
|
||||
|
||||
scanner.LastScannedName = MetaData(args.Args.Target.Value).EntityName;
|
||||
@@ -211,6 +214,12 @@ namespace Content.Server.Forensics
|
||||
{
|
||||
text.AppendLine(fiber);
|
||||
}
|
||||
text.AppendLine();
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-dnas"));
|
||||
foreach (var dna in component.DNAs)
|
||||
{
|
||||
text.AppendLine(dna);
|
||||
}
|
||||
|
||||
_paperSystem.SetContent(printed, text.ToString());
|
||||
_audioSystem.PlayPvs(component.SoundPrint, uid,
|
||||
@@ -230,6 +239,7 @@ namespace Content.Server.Forensics
|
||||
|
||||
component.Fingerprints = new();
|
||||
component.Fibers = new();
|
||||
component.DNAs = new();
|
||||
component.LastScannedName = string.Empty;
|
||||
|
||||
UpdateUserInterface(uid, component);
|
||||
|
||||
@@ -11,7 +11,8 @@ namespace Content.Server.Forensics
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<FingerprintComponent, ContactInteractionEvent>(OnInteract);
|
||||
SubscribeLocalEvent<FingerprintComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<FingerprintComponent, ComponentInit>(OnFingeprintInit);
|
||||
SubscribeLocalEvent<DnaComponent, ComponentInit>(OnDNAInit);
|
||||
}
|
||||
|
||||
private void OnInteract(EntityUid uid, FingerprintComponent component, ContactInteractionEvent args)
|
||||
@@ -19,11 +20,16 @@ namespace Content.Server.Forensics
|
||||
ApplyEvidence(uid, args.Other);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, FingerprintComponent component, ComponentInit args)
|
||||
private void OnFingeprintInit(EntityUid uid, FingerprintComponent component, ComponentInit args)
|
||||
{
|
||||
component.Fingerprint = GenerateFingerprint();
|
||||
}
|
||||
|
||||
private void OnDNAInit(EntityUid uid, DnaComponent component, ComponentInit args)
|
||||
{
|
||||
component.DNA = GenerateDNA();
|
||||
}
|
||||
|
||||
private string GenerateFingerprint()
|
||||
{
|
||||
byte[] fingerprint = new byte[16];
|
||||
@@ -31,6 +37,19 @@ namespace Content.Server.Forensics
|
||||
return Convert.ToHexString(fingerprint);
|
||||
}
|
||||
|
||||
private string GenerateDNA()
|
||||
{
|
||||
var letters = new List<string> { "A", "C", "G", "T" };
|
||||
string DNA = String.Empty;
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
DNA += letters[_random.Next(letters.Count)];
|
||||
}
|
||||
|
||||
return DNA;
|
||||
}
|
||||
|
||||
private void ApplyEvidence(EntityUid user, EntityUid target)
|
||||
{
|
||||
var component = EnsureComp<ForensicsComponent>(target);
|
||||
|
||||
73
Content.Server/GameTicking/Rules/AllCaptainsRuleSystem.cs
Normal file
73
Content.Server/GameTicking/Rules/AllCaptainsRuleSystem.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking.Presets;
|
||||
using Content.Server.GameTicking.Rules.Configurations;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Configuration;
|
||||
using Content.Shared.CCVar;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
public sealed class AllCaptainsRuleSystem : GameRuleSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly GameTicker _ticker = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
public override string Prototype => "AllCaptains";
|
||||
|
||||
private EntityUid holdJobs; // dunno if there should only be one reference like this because I'm a ss14 noob but hey it's only gotta work one day :P
|
||||
|
||||
public override void Added()
|
||||
{
|
||||
}
|
||||
|
||||
public override void Started()
|
||||
{
|
||||
// temporarily disable role timers -- super hacky way workaround for client to be aware that role timers aren't required
|
||||
// without having to set up some kind of replication
|
||||
_cfg.SetCVar(CCVars.GameRoleTimers, false);
|
||||
}
|
||||
|
||||
public override void Ended()
|
||||
{
|
||||
_cfg.SetCVar(CCVars.GameRoleTimers, true);
|
||||
}
|
||||
|
||||
public StationJobsComponent GetJobs(EntityUid station)
|
||||
{
|
||||
if (!holdJobs.IsValid() || !HasComp<StationJobsComponent>(holdJobs)) // this doesn't check station parameter since all captains mode is the same for all stations.
|
||||
{
|
||||
holdJobs = Spawn(null, new EntityCoordinates(station, Vector2.Zero));
|
||||
var stationJobs = AddComp<StationJobsComponent>(holdJobs);
|
||||
|
||||
// Create captains-only specific job list
|
||||
var mapJobList = new Dictionary<string, List<int?>> {{"Captain", new List<int?>{int.MaxValue, int.MaxValue}}};
|
||||
|
||||
stationJobs.RoundStartTotalJobs = mapJobList.Values.Where(x => x[0] is not null && x[0] > 0).Sum(x => x[0]!.Value);
|
||||
stationJobs.MidRoundTotalJobs = mapJobList.Values.Where(x => x[1] is not null && x[1] > 0).Sum(x => x[1]!.Value);
|
||||
stationJobs.TotalJobs = stationJobs.MidRoundTotalJobs;
|
||||
stationJobs.JobList = mapJobList.ToDictionary(x => x.Key, x =>
|
||||
{
|
||||
if (x.Value[1] <= -1)
|
||||
return null;
|
||||
return (uint?) x.Value[1];
|
||||
});
|
||||
stationJobs.RoundStartJobList = mapJobList.ToDictionary(x => x.Key, x =>
|
||||
{
|
||||
if (x.Value[0] <= -1)
|
||||
return null;
|
||||
return (uint?) x.Value[0];
|
||||
});
|
||||
stationJobs.OverflowJobs = new HashSet<string>{"Captain"}; //stationData.StationConfig.OverflowJobs.ToHashSet();
|
||||
}
|
||||
|
||||
return Comp<StationJobsComponent>(holdJobs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,6 +36,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly UplinkSystem _uplink = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly AllCaptainsRuleSystem _allCaptainsRule = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
@@ -178,7 +179,8 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
||||
foreach (var player in candidates.Keys)
|
||||
{
|
||||
// Role prevents antag.
|
||||
if (!(player.Data.ContentData()?.Mind?.AllRoles.All(role => role is not Job { CanBeAntag: false }) ?? false))
|
||||
if (!(_allCaptainsRule != null && _allCaptainsRule.RuleStarted) && // all captains mode lets some of the captains be traitors :3
|
||||
!(player.Data.ContentData()?.Mind?.AllRoles.All(role => role is not Job { CanBeAntag: false }) ?? false))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -296,7 +298,8 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
||||
if (ev.JobId == null || !_prototypeManager.TryIndex<JobPrototype>(ev.JobId, out var job))
|
||||
return;
|
||||
|
||||
if (!job.CanBeAntag)
|
||||
if (!job.CanBeAntag &&
|
||||
!(_allCaptainsRule != null && _allCaptainsRule.RuleStarted)) // all captains mode lets some of the captains be traitors :3
|
||||
return;
|
||||
|
||||
// Before the announcement is made, late-joiners are considered the same as players who readied.
|
||||
|
||||
@@ -16,4 +16,10 @@ public sealed class ActiveMicrowaveComponent : Component
|
||||
|
||||
[ViewVariables]
|
||||
public (FoodRecipePrototype?, int) PortionedRecipe;
|
||||
|
||||
[ViewVariables]
|
||||
public bool Haywire;
|
||||
|
||||
[ViewVariables]
|
||||
public float HaywireTimeRemaining;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ using Content.Server.Body.Systems;
|
||||
using Content.Server.Chemistry.Components.SolutionManager;
|
||||
using Content.Server.Chemistry.EntitySystems;
|
||||
using Content.Server.Construction;
|
||||
using Content.Server.Damage.Systems;
|
||||
using Content.Server.Explosion.Components;
|
||||
using Content.Server.Explosion.EntitySystems;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Kitchen.Components;
|
||||
using Content.Server.Power.Components;
|
||||
@@ -20,11 +23,13 @@ using Content.Shared.Kitchen.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Kitchen.EntitySystems
|
||||
{
|
||||
@@ -42,6 +47,8 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
[Dependency] private readonly TemperatureSystem _temperature = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||
[Dependency] private readonly HandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly ExplosionSystem _explosionSystem = default!;
|
||||
[Dependency] private readonly ThrowingSystem _throwingSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -255,7 +262,30 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
{
|
||||
component.Broken = true;
|
||||
SetAppearance(component, MicrowaveVisualState.Broken);
|
||||
|
||||
if (TryComp<ActiveMicrowaveComponent>(uid, out var activeMicrowareComponent))
|
||||
{
|
||||
if (activeMicrowareComponent.Haywire)
|
||||
{
|
||||
_audio.PlayPvs(component.FoodDoneSound, component.Owner, AudioParams.Default.WithVolume(-1));
|
||||
var random = new RobustRandom();
|
||||
|
||||
// Make it look like the microwave exploded
|
||||
var machineFrame = Spawn("MachineFrameDestroyed", Transform(uid).Coordinates);
|
||||
Comp<TransformComponent>(machineFrame).Anchored = false;
|
||||
_throwingSystem.TryThrow(machineFrame, new Vector2(random.NextFloat()*20-10, random.NextFloat()*20-10), 10);
|
||||
|
||||
var board = Spawn("MicrowaveMachineCircuitboard", Transform(uid).Coordinates);
|
||||
_throwingSystem.TryThrow(board, new Vector2(random.NextFloat()*20-10, random.NextFloat()*20-10), 10);
|
||||
|
||||
_explosionSystem.QueueExplosion(component.Owner, "Default", 5, 0.5f, 10, canCreateVacuum: false);
|
||||
|
||||
EntityManager.QueueDeleteEntity(uid);
|
||||
}
|
||||
}
|
||||
|
||||
RemComp<ActiveMicrowaveComponent>(uid);
|
||||
|
||||
_sharedContainer.EmptyContainer(component.Storage);
|
||||
UpdateUserInterfaceState(uid, component);
|
||||
}
|
||||
@@ -265,7 +295,13 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
if (!args.Powered)
|
||||
{
|
||||
SetAppearance(component, MicrowaveVisualState.Idle);
|
||||
RemComp<ActiveMicrowaveComponent>(uid);
|
||||
|
||||
if (TryComp<ActiveMicrowaveComponent>(uid, out var activeMicrowareComponent))
|
||||
{
|
||||
if (!activeMicrowareComponent.Haywire) // haywire microwaves don't need power! They're *very* haywire.
|
||||
RemComp<ActiveMicrowaveComponent>(uid);
|
||||
}
|
||||
|
||||
_sharedContainer.EmptyContainer(component.Storage);
|
||||
}
|
||||
UpdateUserInterfaceState(uid, component);
|
||||
@@ -321,6 +357,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
|
||||
var solidsDict = new Dictionary<string, int>();
|
||||
var reagentDict = new Dictionary<string, FixedPoint2>();
|
||||
bool beginHaywire = false;
|
||||
foreach (var item in component.Storage.ContainedEntities)
|
||||
{
|
||||
// special behavior when being microwaved ;)
|
||||
@@ -338,8 +375,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
{
|
||||
component.Broken = true;
|
||||
SetAppearance(component, MicrowaveVisualState.Broken);
|
||||
_audio.PlayPvs(component.ItemBreakSound, uid);
|
||||
return;
|
||||
beginHaywire = true;
|
||||
}
|
||||
|
||||
if (_tag.HasTag(item, "MicrowaveSelfUnsafe") || _tag.HasTag(item, "Plastic"))
|
||||
@@ -386,6 +422,21 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
activeComp.CookTimeRemaining = component.CurrentCookTimerTime * component.CookTimeMultiplier;
|
||||
activeComp.TotalTime = component.CurrentCookTimerTime; //this doesn't scale so that we can have the "actual" time
|
||||
activeComp.PortionedRecipe = portionedRecipe;
|
||||
if (beginHaywire)
|
||||
{
|
||||
activeComp.Haywire = true;
|
||||
activeComp.HaywireTimeRemaining = 2;
|
||||
|
||||
// unanchor from floor so it can fly
|
||||
Comp<TransformComponent>(activeComp.Owner).Anchored = false;
|
||||
|
||||
// Don't explode itself
|
||||
// (Ideally microwave would only be invulnerable to explosions while in haywire mode, but I wasn't sure
|
||||
// how to do this as ExplosionResistanceComponent has access permissions, so I just set it to always be
|
||||
// invulnerable via the yml.)
|
||||
//var explodeResist = AddComp<ExplosionResistanceComponent>(activeComp.Owner);
|
||||
//explodeResist.DamageCoefficient = 0f;
|
||||
}
|
||||
UpdateUserInterfaceState(uid, component);
|
||||
}
|
||||
|
||||
@@ -434,6 +485,45 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
base.Update(frameTime);
|
||||
foreach (var (active, microwave) in EntityManager.EntityQuery<ActiveMicrowaveComponent, MicrowaveComponent>())
|
||||
{
|
||||
// Haywire microwaves bounce around a bit
|
||||
if (active.Haywire)
|
||||
{
|
||||
active.HaywireTimeRemaining -= frameTime;
|
||||
if (active.HaywireTimeRemaining < 0)
|
||||
{
|
||||
// If interested in using this post-april fools, this might potentially be a way to
|
||||
// nerf the microwave a bit -- could have a random chance of ending early/continuing
|
||||
// instead of going until attacked.
|
||||
bool completeEarly = false;
|
||||
|
||||
if (completeEarly)
|
||||
{
|
||||
/* (Post april fools?)
|
||||
// Finish haywire mode with a bang
|
||||
// (Re-use/call code from OnBreak)
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
// not complete, going for another round
|
||||
_audio.PlayPvs(microwave.ItemBreakSound, microwave.Owner);
|
||||
_audio.PlayPvs(microwave.StartCookingSound, microwave.Owner);
|
||||
|
||||
// Small explosion
|
||||
_explosionSystem.QueueExplosion(microwave.Owner, "Default", 5, 0.5f, 10, canCreateVacuum: false);
|
||||
|
||||
// Go flying in a random direction
|
||||
// (Sometimes this happens to be in the direction of a player and that is Very Exciting.)
|
||||
var random = new RobustRandom();
|
||||
_throwingSystem.TryThrow(microwave.Owner, new Vector2(random.NextFloat()*20-10, random.NextFloat()*20-10), 10);
|
||||
|
||||
active.HaywireTimeRemaining = .2f + random.NextFloat() * 4.8f; // Next event in .2~5 sec
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
//check if there's still cook time left
|
||||
active.CookTimeRemaining -= frameTime;
|
||||
if (active.CookTimeRemaining > 0)
|
||||
|
||||
@@ -5,7 +5,6 @@ using Content.Server.Lightning.Components;
|
||||
using Content.Shared.Lightning;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -67,10 +66,10 @@ public sealed class LightningSystem : SharedLightningSystem
|
||||
/// <param name="user">Where the lightning fires from</param>
|
||||
/// <param name="target">Where the lightning fires to</param>
|
||||
/// <param name="lightningPrototype">The prototype for the lightning to be created</param>
|
||||
public void ShootLightning(EntityUid user, EntityUid target, string lightningPrototype = "Lightning")
|
||||
public void ShootLightning(EntityUid user, EntityUid target, string lightningPrototype = "Lightning", EntityUid? controller = null)
|
||||
{
|
||||
var spriteState = LightningRandomizer();
|
||||
_beam.TryCreateBeam(user, target, lightningPrototype, spriteState);
|
||||
_beam.TryCreateBeam(user, target, lightningPrototype, spriteState, controller: controller);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
131
Content.Server/Medical/Surgery/SansCommand.cs
Normal file
131
Content.Server/Medical/Surgery/SansCommand.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Medical.Surgery;
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class SansCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
|
||||
public override string Command => "sans";
|
||||
public override string Description => "You feel like you are going to have a bad time.";
|
||||
public override string Help => $"Usage: {Command} | {Command} <player> | {Command} <player1> <player2> | {Command} <player> <music> | {Command} <player1> <player2> <music>";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
if (shell.Player == null)
|
||||
{
|
||||
shell.WriteError("Specify a player to send to the sans dimension");
|
||||
return;
|
||||
}
|
||||
|
||||
SansUndertale(new List<IPlayerSession> {(IPlayerSession) shell.Player});
|
||||
shell.WriteLine("You are about to have a bad time");
|
||||
return;
|
||||
}
|
||||
|
||||
var players = new List<IPlayerSession>();
|
||||
foreach (var arg in args.SkipLast(1))
|
||||
{
|
||||
if (_players.TryGetSessionByUsername(arg, out var target))
|
||||
{
|
||||
players.Add(target);
|
||||
}
|
||||
}
|
||||
|
||||
SurgeryRealmMusic? music = args[^1].ToLowerInvariant() switch
|
||||
{
|
||||
"midi" => SurgeryRealmMusic.Midi,
|
||||
"megalovania" => SurgeryRealmMusic.Megalovania,
|
||||
"undermale" => SurgeryRealmMusic.Undermale,
|
||||
_ => null
|
||||
};
|
||||
|
||||
SansUndertale(players, music);
|
||||
shell.WriteLine($"{string.Join(", ", players.Select(player => player.Name))} are about to have a bad time");
|
||||
}
|
||||
|
||||
private void SansUndertale(List<IPlayerSession> sessions, SurgeryRealmMusic? music = null)
|
||||
{
|
||||
if (sessions.Any(session => session.AttachedEntity == null))
|
||||
return;
|
||||
|
||||
foreach (var session in sessions)
|
||||
{
|
||||
EntitySystem.Get<PopupSystem>().PopupEntity("You feel like you are going to have a bad time", session.AttachedEntity!.Value, PopupType.LargeCaution);
|
||||
}
|
||||
|
||||
Timer.Spawn(4000, () =>
|
||||
{
|
||||
EntitySystem.Get<SurgeryRealmSystem>().StartDuel(sessions, null, music);
|
||||
});
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
return CompletionResult.Empty;
|
||||
|
||||
var options = _players.ServerSessions.OrderBy(c => c.Name).Select(c => c.Name).ToList();
|
||||
options.Add("midi");
|
||||
options.Add("megalovania");
|
||||
options.Add("undermale");
|
||||
|
||||
return CompletionResult.FromHintOptions(options, "<PlayerIndex | Music>");
|
||||
}
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class UnSansCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
|
||||
public override string Command => "unsans";
|
||||
public override string Description => "Stop someone from having a bad time..";
|
||||
public override string Help => $"Usage: {Command} | {Command} <player>";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
if (shell.Player == null)
|
||||
{
|
||||
shell.WriteError("Specify a player to retrieve from the sans dimension");
|
||||
return;
|
||||
}
|
||||
|
||||
EntitySystem.Get<SurgeryRealmSystem>().StopOperation((IPlayerSession) shell.Player);
|
||||
shell.WriteLine("You stopped having a bad time");
|
||||
return;
|
||||
}
|
||||
|
||||
var name = args[0];
|
||||
|
||||
if (_players.TryGetSessionByUsername(name, out var target))
|
||||
{
|
||||
EntitySystem.Get<SurgeryRealmSystem>().StopOperation(target);
|
||||
shell.WriteLine($"Stopped {target.Name} from having a bad time");
|
||||
}
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
var options = _players.ServerSessions.OrderBy(c => c.Name).Select(c => c.Name).ToArray();
|
||||
|
||||
return CompletionResult.FromHintOptions(options, "<PlayerIndex>");
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.Medical.Surgery;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class SurgeryRealmAntiProjectileComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Content.Server.Medical.Surgery;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class SurgeryRealmCameraComponent : Component
|
||||
{
|
||||
[ViewVariables] public EntityUid? OldEntity;
|
||||
|
||||
[ViewVariables] public Mind.Mind? Mind;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.Medical.Surgery;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class SurgeryRealmEdgeComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.Medical.Surgery;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class SurgeryRealmLightningComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
8
Content.Server/Medical/Surgery/SurgeryRealmMusic.cs
Normal file
8
Content.Server/Medical/Surgery/SurgeryRealmMusic.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Content.Server.Medical.Surgery;
|
||||
|
||||
public enum SurgeryRealmMusic
|
||||
{
|
||||
Midi,
|
||||
Megalovania,
|
||||
Undermale
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.Medical.Surgery;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class SurgeryRealmOrangeProjectileComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Content.Server.Medical.Surgery;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class SurgeryRealmProjectileComponent : Component
|
||||
{
|
||||
}
|
||||
690
Content.Server/Medical/Surgery/SurgeryRealmSystem.cs
Normal file
690
Content.Server/Medical/Surgery/SurgeryRealmSystem.cs
Normal file
@@ -0,0 +1,690 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Damage.Systems;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.Lightning;
|
||||
using Content.Server.Lightning.Components;
|
||||
using Content.Server.Mind.Components;
|
||||
using Content.Server.Physics.Controllers;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Medical.Surgery;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Content.Shared.Rejuvenate;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Medical.Surgery;
|
||||
|
||||
public sealed class SurgeryRealmSystem : SharedSurgeryRealmSystem
|
||||
{
|
||||
private const int SectionSeparation = 100;
|
||||
|
||||
[Dependency] private readonly IConsoleHost _console = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IMapManager _maps = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly LightningSystem _lightning = default!;
|
||||
[Dependency] private readonly GodmodeSystem _godmode = default!;
|
||||
[Dependency] private readonly MoverController _mover = default!;
|
||||
[Dependency] private readonly ViewSubscriberSystem _viewSubscriber = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
|
||||
private MapId _surgeryRealmMap = MapId.Nullspace;
|
||||
private int _sections = 1;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
|
||||
|
||||
SubscribeLocalEvent<InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<SurgeryRealmHeartComponent, CanWeightlessMoveEvent>(OnHeartCanWeightlessMove);
|
||||
SubscribeLocalEvent<SurgeryRealmProjectileComponent, StartCollideEvent>(OnProjectileCollide);
|
||||
SubscribeLocalEvent<SurgeryRealmAntiProjectileComponent, StartCollideEvent>(OnAntiProjectileCollide);
|
||||
SubscribeLocalEvent<SurgeryRealmOrangeProjectileComponent, StartCollideEvent>(OnOrangeProjectileCollide);
|
||||
SubscribeLocalEvent<SurgeryRealmHeartComponent, StartCollideEvent>(OnHeartCollide);
|
||||
|
||||
SubscribeNetworkEvent<SurgeryRealmAcceptSelfEvent>(OnSurgeryRealmAcceptSelf);
|
||||
}
|
||||
|
||||
private void OnOrangeProjectileCollide(EntityUid uid, SurgeryRealmOrangeProjectileComponent component, ref StartCollideEvent args)
|
||||
{
|
||||
if (HasComp<SurgeryRealmEdgeComponent>(args.OtherFixture.Body.Owner))
|
||||
{
|
||||
if (_timing.CurTick > MetaData(uid).CreationTick + 60)
|
||||
QueueDel(uid);
|
||||
}
|
||||
|
||||
if (!TryComp(args.OtherFixture.Body.Owner, out SurgeryRealmHeartComponent? heart))
|
||||
return;
|
||||
|
||||
if (!TryComp(heart.Owner, out InputMoverComponent? input) ||
|
||||
input.HeldMoveButtons != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SubtractHealth(heart);
|
||||
}
|
||||
|
||||
private void SubtractHealth(SurgeryRealmHeartComponent heart)
|
||||
{
|
||||
if (heart.Health == 0)
|
||||
return;
|
||||
|
||||
heart.Health--;
|
||||
Dirty(heart);
|
||||
|
||||
if (heart.Health > 0)
|
||||
return;
|
||||
|
||||
heart.Health = 0;
|
||||
|
||||
if (!TryComp(heart.Camera, out ActorComponent? actor))
|
||||
return;
|
||||
|
||||
StopOperation(actor.PlayerSession);
|
||||
QueueDel(heart.Owner);
|
||||
}
|
||||
|
||||
private void OnHeartCollide(EntityUid uid, SurgeryRealmHeartComponent component, ref StartCollideEvent args)
|
||||
{
|
||||
if (!HasComp<LightningComponent>(args.OtherFixture.Body.Owner))
|
||||
return;
|
||||
|
||||
SubtractHealth(component);
|
||||
}
|
||||
|
||||
private void OnSurgeryRealmAcceptSelf(SurgeryRealmAcceptSelfEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (args.SenderSession.AttachedEntity is not { } playerEntity)
|
||||
return;
|
||||
|
||||
if (!TryComp(playerEntity, out HandsComponent? hands) ||
|
||||
!hands.Hands.TryFirstOrNull(hand => hand.Value.HeldEntity != null && HasComp<SurgeryRealmToolComponent>(hand.Value.HeldEntity), out var tool))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StartOperation((IPlayerSession) args.SenderSession, tool.Value.Value.HeldEntity!.Value);
|
||||
}
|
||||
|
||||
protected override void Fire(SurgeryRealmSlidingComponent sliding)
|
||||
{
|
||||
base.Fire(sliding);
|
||||
|
||||
sliding.Fired = true;
|
||||
|
||||
_audio.Play(new SoundPathSpecifier("/Audio/Surgery/blast.ogg"), Filter.Empty().AddInRange(sliding.SectionPos, 10), sliding.Owner, true, AudioParams.Default.WithVolume(10));
|
||||
|
||||
var slidingPos = _transform.GetWorldPosition(sliding.Owner);
|
||||
var y = sliding.SectionPos.Y + sliding.FinalY;
|
||||
_transform.SetWorldPosition(sliding.Owner, (slidingPos.X, y));
|
||||
|
||||
Timer.Spawn(1000, () =>
|
||||
{
|
||||
var x = sliding.SectionPos.X - (slidingPos.X - sliding.SectionPos.X);
|
||||
var opposite = Spawn("", new MapCoordinates(x, y, sliding.SectionPos.MapId));
|
||||
var controller = Spawn("SurgeryRealmVirtualBeamEntityController", sliding.SectionPos);
|
||||
|
||||
_lightning.ShootLightning(sliding.Owner, opposite, "SurgeryRealmLightning", controller);
|
||||
|
||||
var physX = slidingPos.X > sliding.SectionPos.X ? 10 : -10;
|
||||
Physics.SetLinearVelocity(sliding.Owner, (physX, 0));
|
||||
|
||||
Timer.Spawn(500, () =>
|
||||
{
|
||||
QueueDel(sliding.Owner);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void OnHeartCanWeightlessMove(EntityUid uid, SurgeryRealmHeartComponent component, ref CanWeightlessMoveEvent args)
|
||||
{
|
||||
args.CanMove = true;
|
||||
}
|
||||
|
||||
private void OnProjectileCollide(EntityUid uid, SurgeryRealmProjectileComponent component, ref StartCollideEvent args)
|
||||
{
|
||||
if (HasComp<SurgeryRealmEdgeComponent>(args.OtherFixture.Body.Owner))
|
||||
{
|
||||
if (_timing.CurTick > MetaData(uid).CreationTick + 60)
|
||||
QueueDel(uid);
|
||||
}
|
||||
|
||||
if (!TryComp(args.OtherFixture.Body.Owner, out SurgeryRealmHeartComponent? heart))
|
||||
return;
|
||||
|
||||
SubtractHealth(heart);
|
||||
}
|
||||
|
||||
private void OnAntiProjectileCollide(EntityUid uid, SurgeryRealmAntiProjectileComponent component, ref StartCollideEvent args)
|
||||
{
|
||||
if (HasComp<SurgeryRealmEdgeComponent>(args.OtherFixture.Body.Owner))
|
||||
{
|
||||
if (_timing.CurTick > MetaData(uid).CreationTick + 60)
|
||||
QueueDel(uid);
|
||||
}
|
||||
|
||||
if (!TryComp(args.OtherFixture.Body.Owner, out SurgeryRealmHeartComponent? heart))
|
||||
return;
|
||||
|
||||
if (!TryComp(heart.Owner, out InputMoverComponent? input) ||
|
||||
input.HeldMoveButtons == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SubtractHealth(heart);
|
||||
}
|
||||
|
||||
private void OnInteractUsing(InteractUsingEvent args)
|
||||
{
|
||||
if (!HasComp<SurgeryRealmToolComponent>(args.Used))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.User != args.Target)
|
||||
{
|
||||
if (!HasComp<SurgeryRealmToolDuelComponent>(args.Used))
|
||||
return;
|
||||
|
||||
if (!TryComp(args.User, out ActorComponent? userActor) ||
|
||||
!TryComp(args.Target, out ActorComponent? targetActor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StartDuel(new List<IPlayerSession> {userActor.PlayerSession, targetActor.PlayerSession}, args.Used);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
if (HasComp<SurgeryRealmVictimComponent>(args.User) ||
|
||||
!TryComp(args.User, out ActorComponent? userActor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var ev = new SurgeryRealmRequestSelfEvent();
|
||||
RaiseNetworkEvent(ev, userActor.PlayerSession);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void OnRoundRestart(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
if (_surgeryRealmMap == MapId.Nullspace || !_maps.MapExists(_surgeryRealmMap))
|
||||
return;
|
||||
|
||||
_maps.DeleteMap(_surgeryRealmMap);
|
||||
_surgeryRealmMap = MapId.Nullspace;
|
||||
_sections = 1;
|
||||
}
|
||||
|
||||
public void StartOperation(IPlayerSession victimPlayer, EntityUid? toolId, SurgeryRealmMusic? music = null)
|
||||
{
|
||||
if (victimPlayer.AttachedEntity is not { } victimEntity)
|
||||
return;
|
||||
|
||||
toolId ??= Spawn("Scalpel", Transform(victimEntity).Coordinates);
|
||||
var tool = EnsureComp<SurgeryRealmToolComponent>(toolId.Value);
|
||||
var victim = EnsureComp<SurgeryRealmVictimComponent>(victimEntity);
|
||||
victim.Tool = toolId.Value;
|
||||
|
||||
EnsureMap();
|
||||
|
||||
if (tool.Position == null || tool.Victims.Count == 0)
|
||||
tool.Position = new MapCoordinates(GetNextPosition(), _surgeryRealmMap);
|
||||
|
||||
tool.Fight++;
|
||||
var fight = tool.Fight;
|
||||
|
||||
tool.Victims.Add(victimPlayer);
|
||||
|
||||
victim.Heart = Spawn(tool.HeartPrototype, tool.Position.Value.Offset(0, -5));
|
||||
_console.ExecuteCommand($"scale {victim.Heart} 2.5");
|
||||
|
||||
var clown = Spawn("SurgeryRealmClown", tool.Position.Value.Offset(0, 3));
|
||||
_console.ExecuteCommand($"scale {clown} 5");
|
||||
|
||||
SpawnEdges(tool.Position.Value);
|
||||
|
||||
var camera = EntityManager.SpawnEntity("SurgeryRealmCamera", tool.Position.Value);
|
||||
EnsureComp<SurgeryRealmHeartComponent>(victim.Heart).Camera = camera;
|
||||
|
||||
var mind = EnsureComp<MindComponent>(victimEntity);
|
||||
var cameraComp = EnsureComp<SurgeryRealmCameraComponent>(camera);
|
||||
cameraComp.OldEntity = victimEntity;
|
||||
cameraComp.Mind = mind.Mind;
|
||||
|
||||
var eyeComponent = EnsureComp<EyeComponent>(camera);
|
||||
|
||||
eyeComponent.DrawFov = false;
|
||||
_viewSubscriber.AddViewSubscriber(camera, victimPlayer);
|
||||
|
||||
_mover.SetRelay(camera, victim.Heart);
|
||||
|
||||
_godmode.EnableGodmode(victimEntity);
|
||||
|
||||
mind.Mind?.Visit(camera);
|
||||
|
||||
if (music == null)
|
||||
{
|
||||
switch (_random.NextFloat())
|
||||
{
|
||||
// case var x when x < 0.25:
|
||||
// RaiseNetworkEvent(new SurgeryRealmStartEvent(camera), victimPlayer);
|
||||
// break;
|
||||
case var x when x < 0.95:
|
||||
_audio.PlayEntity(new SoundPathSpecifier("/Audio/Surgery/megalovania.ogg"), camera, camera, AudioParams.Default.WithVolume(2));
|
||||
break;
|
||||
default:
|
||||
_audio.PlayEntity(new SoundPathSpecifier("/Audio/Surgery/undermale.ogg"), camera, camera, AudioParams.Default.WithVolume(6));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (music)
|
||||
{
|
||||
// case SurgeryRealmMusic.Midi:
|
||||
// RaiseNetworkEvent(new SurgeryRealmStartEvent(camera), victimPlayer);
|
||||
// break;
|
||||
case SurgeryRealmMusic.Megalovania:
|
||||
_audio.PlayEntity(new SoundPathSpecifier("/Audio/Surgery/megalovania.ogg"), camera, camera, AudioParams.Default.WithVolume(2));
|
||||
break;
|
||||
case SurgeryRealmMusic.Undermale:
|
||||
_audio.PlayEntity(new SoundPathSpecifier("/Audio/Surgery/undermale.ogg"), camera, camera, AudioParams.Default.WithVolume(6));
|
||||
break;
|
||||
case null:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(music), music, null);
|
||||
}
|
||||
}
|
||||
|
||||
Timer.Spawn(2000, () =>
|
||||
{
|
||||
if (tool.Position == null || fight != tool.Fight)
|
||||
return;
|
||||
|
||||
SpawnOppositeBananaWallsHoles(tool.Position.Value);
|
||||
});
|
||||
|
||||
Timer.Spawn(17000, () =>
|
||||
{
|
||||
if (tool.Position == null || fight != tool.Fight)
|
||||
return;
|
||||
|
||||
SpawnAlternatingBananaPillars(tool.Position.Value);
|
||||
});
|
||||
|
||||
Timer.Spawn(32000, () =>
|
||||
{
|
||||
if (tool.Position == null || fight != tool.Fight)
|
||||
return;
|
||||
|
||||
SpawnVerticallySlidingPdas(tool.Position.Value);
|
||||
});
|
||||
|
||||
Timer.Spawn(45000, () =>
|
||||
{
|
||||
if (tool.Position == null || fight != tool.Fight)
|
||||
return;
|
||||
|
||||
StopOperation(victimPlayer, true);
|
||||
});
|
||||
}
|
||||
|
||||
public void StartDuel(List<IPlayerSession> victimPlayers, EntityUid? toolId, SurgeryRealmMusic? music = null)
|
||||
{
|
||||
if (victimPlayers.Count == 0)
|
||||
return;
|
||||
|
||||
if (victimPlayers.Any(player => player.AttachedEntity == null))
|
||||
return;
|
||||
|
||||
var victimEntities = victimPlayers.Select(player => player.AttachedEntity!.Value).ToArray();
|
||||
|
||||
var firstPlayerEntity = victimEntities[0];
|
||||
toolId ??= Spawn("Scalpel", Transform(firstPlayerEntity).Coordinates);
|
||||
var tool = EnsureComp<SurgeryRealmToolComponent>(toolId.Value);
|
||||
tool.Fight++;
|
||||
var fight = tool.Fight;
|
||||
|
||||
EnsureMap();
|
||||
|
||||
if (tool.Position == null || tool.Victims.Count == 0)
|
||||
tool.Position = new MapCoordinates(GetNextPosition(), _surgeryRealmMap);
|
||||
|
||||
tool.Victims.UnionWith(victimPlayers);
|
||||
|
||||
var clown = Spawn("SurgeryRealmClown", tool.Position.Value.Offset(0, 3));
|
||||
_console.ExecuteCommand($"scale {clown} 5");
|
||||
|
||||
SpawnEdges(tool.Position.Value);
|
||||
|
||||
for (var i = 0; i < victimEntities.Length; i++)
|
||||
{
|
||||
var victimEntity = victimEntities[i];
|
||||
var victimPlayer = victimPlayers[i];
|
||||
var victim = EnsureComp<SurgeryRealmVictimComponent>(victimEntity);
|
||||
victim.Tool = toolId.Value;
|
||||
victim.Heart = Spawn(tool.HeartPrototype, tool.Position.Value.Offset(0, -5));
|
||||
_console.ExecuteCommand($"scale {victim.Heart} 2.5");
|
||||
|
||||
var camera = EntityManager.SpawnEntity("SurgeryRealmCamera", tool.Position.Value);
|
||||
EnsureComp<SurgeryRealmHeartComponent>(victim.Heart).Camera = camera;
|
||||
|
||||
var mind = EnsureComp<MindComponent>(victimEntity);
|
||||
var cameraComp = EnsureComp<SurgeryRealmCameraComponent>(camera);
|
||||
cameraComp.OldEntity = victimEntity;
|
||||
cameraComp.Mind = mind.Mind;
|
||||
|
||||
var eyeComponent = EnsureComp<EyeComponent>(camera);
|
||||
eyeComponent.DrawFov = false;
|
||||
_viewSubscriber.AddViewSubscriber(camera, victimPlayer);
|
||||
_mover.SetRelay(camera, victim.Heart);
|
||||
_godmode.EnableGodmode(victimEntity);
|
||||
|
||||
mind.Mind?.Visit(camera);
|
||||
|
||||
if (music == null)
|
||||
{
|
||||
switch (_random.NextFloat())
|
||||
{
|
||||
// case var x when x < 0.25:
|
||||
// if (i == 0)
|
||||
// RaiseNetworkEvent(new SurgeryRealmStartEvent(camera), victimPlayer);
|
||||
// break;
|
||||
case var x when x < 0.95:
|
||||
_audio.PlayEntity(new SoundPathSpecifier("/Audio/Surgery/megalovania.ogg"), camera, camera, AudioParams.Default.WithVolume(2));
|
||||
break;
|
||||
default:
|
||||
_audio.PlayEntity(new SoundPathSpecifier("/Audio/Surgery/undermale.ogg"), camera, camera, AudioParams.Default.WithVolume(6));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (music)
|
||||
{
|
||||
// case SurgeryRealmMusic.Midi:
|
||||
// if (i == 0)
|
||||
// RaiseNetworkEvent(new SurgeryRealmStartEvent(camera), victimPlayer);
|
||||
// break;
|
||||
case SurgeryRealmMusic.Megalovania:
|
||||
_audio.PlayEntity(new SoundPathSpecifier("/Audio/Surgery/megalovania.ogg"), camera, camera, AudioParams.Default.WithVolume(2));
|
||||
break;
|
||||
case SurgeryRealmMusic.Undermale:
|
||||
_audio.PlayEntity(new SoundPathSpecifier("/Audio/Surgery/undermale.ogg"), camera, camera, AudioParams.Default.WithVolume(6));
|
||||
break;
|
||||
case null:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(music), music, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void A(float speedMultiplier)
|
||||
{
|
||||
Timer.Spawn(2000, () =>
|
||||
{
|
||||
if (tool.Position == null || fight != tool.Fight)
|
||||
return;
|
||||
|
||||
SpawnOppositeBananaWallsHoles(tool.Position.Value, speedMultiplier);
|
||||
});
|
||||
|
||||
Timer.Spawn(17000, () =>
|
||||
{
|
||||
if (tool.Position == null || fight != tool.Fight)
|
||||
return;
|
||||
|
||||
SpawnAlternatingBananaPillars(tool.Position.Value, speedMultiplier);
|
||||
});
|
||||
|
||||
var thirdStage = (int) (17000 + 15000 / speedMultiplier);
|
||||
Timer.Spawn(thirdStage, () =>
|
||||
{
|
||||
if (tool.Position == null || fight != tool.Fight)
|
||||
return;
|
||||
|
||||
SpawnVerticallySlidingPdas(tool.Position.Value, speedMultiplier);
|
||||
});
|
||||
|
||||
Timer.Spawn(thirdStage + 13000, () =>
|
||||
{
|
||||
if (fight != tool.Fight)
|
||||
return;
|
||||
|
||||
if (tool.Victims.Count > 1 && tool.Position != null && fight == tool.Fight)
|
||||
{
|
||||
A(speedMultiplier * 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var toolVictim in tool.Victims)
|
||||
{
|
||||
if (tool.Victims.Count == 1)
|
||||
{
|
||||
StopOperation(toolVictim, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
StopOperation(toolVictim);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
A(1);
|
||||
}
|
||||
|
||||
private void SpawnEdges(MapCoordinates coordinates)
|
||||
{
|
||||
for (var x = -5; x < 6; x++)
|
||||
{
|
||||
Spawn("SurgeryRealmEdge", coordinates.Offset(x, -1));
|
||||
Spawn("SurgeryRealmEdge", coordinates.Offset(x, -7));
|
||||
}
|
||||
|
||||
for (var y = -7; y < 0; y++)
|
||||
{
|
||||
Spawn("SurgeryRealmEdge", coordinates.Offset(6, y));
|
||||
Spawn("SurgeryRealmEdge", coordinates.Offset(-6, y));
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnOppositeBananaWallsHoles(MapCoordinates coordinates, float speedMultiplier = 1, bool chain = true)
|
||||
{
|
||||
var xSpeed = 4f * speedMultiplier;
|
||||
var skip = _random.Next(-5, -1);
|
||||
|
||||
for (var y = -6; y < -1; y++)
|
||||
{
|
||||
if (y == skip)
|
||||
continue;
|
||||
|
||||
var projectile1 = Spawn("SurgeryRealmBananaProjectile", coordinates.Offset(-6, y - 0.25f));
|
||||
var projectile2 = Spawn("SurgeryRealmBananaProjectile", coordinates.Offset(-6, y + 0.25f));
|
||||
|
||||
Physics.SetLinearVelocity(projectile1, new Vector2(xSpeed, 0));
|
||||
Physics.SetLinearVelocity(projectile2, new Vector2(xSpeed, 0));
|
||||
}
|
||||
|
||||
for (var y = -6; y < -1; y++)
|
||||
{
|
||||
if (y == skip)
|
||||
continue;
|
||||
|
||||
var projectile1 = Spawn("SurgeryRealmBananaProjectile", coordinates.Offset(6, y - 0.25f));
|
||||
var projectile2 = Spawn("SurgeryRealmBananaProjectile", coordinates.Offset(6, y + 0.25f));
|
||||
|
||||
Physics.SetLinearVelocity(projectile1, new Vector2(-xSpeed, 0));
|
||||
Physics.SetLinearVelocity(projectile2, new Vector2(-xSpeed, 0));
|
||||
}
|
||||
|
||||
if (chain)
|
||||
{
|
||||
for (var i = 1; i < 9; i++)
|
||||
{
|
||||
Timer.Spawn(1500 * i, () => SpawnOppositeBananaWallsHoles(coordinates, speedMultiplier, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnAlternatingBananaPillars(MapCoordinates coordinates, float speedMultiplier = 1, bool chain = true)
|
||||
{
|
||||
var xSpeed = 8f * speedMultiplier;
|
||||
|
||||
for (var y = -6; y < -1; y++)
|
||||
{
|
||||
var projectile1 = Spawn("SurgeryRealmBananaBlueProjectile", coordinates.Offset(6, y - 0.25f));
|
||||
var projectile2 = Spawn("SurgeryRealmBananaBlueProjectile", coordinates.Offset(6, y + 0.25f));
|
||||
|
||||
Physics.SetLinearVelocity(projectile1, new Vector2(-xSpeed, 0));
|
||||
Physics.SetLinearVelocity(projectile2, new Vector2(-xSpeed, 0));
|
||||
}
|
||||
|
||||
Timer.Spawn(500, () =>
|
||||
{
|
||||
for (var y = -6; y < -1; y++)
|
||||
{
|
||||
var projectile1 = Spawn("SurgeryRealmBananaOrangeProjectile", coordinates.Offset(6, y - 0.25f));
|
||||
var projectile2 = Spawn("SurgeryRealmBananaOrangeProjectile", coordinates.Offset(6, y + 0.25f));
|
||||
|
||||
Physics.SetLinearVelocity(projectile1, new Vector2(-xSpeed, 0));
|
||||
Physics.SetLinearVelocity(projectile2, new Vector2(-xSpeed, 0));
|
||||
}
|
||||
});
|
||||
|
||||
if (chain)
|
||||
{
|
||||
for (var i = 1; i < 9; i++)
|
||||
{
|
||||
Timer.Spawn(1500 * i, () => SpawnAlternatingBananaPillars(coordinates, speedMultiplier, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnVerticallySlidingPdas(MapCoordinates coordinates, float speedMultiplier = 1)
|
||||
{
|
||||
var yPositions = new[] { -5, -5, -4, -5, -5, -4, -3, -3, -4 };
|
||||
for (var i = 1; i < 10; i++)
|
||||
{
|
||||
var i1 = i;
|
||||
Timer.Spawn((int) (1000 * i1 / speedMultiplier), () =>
|
||||
{
|
||||
var x = i1 % 2 == 0 ? 8 : -8;
|
||||
SpawnSinglePda(coordinates, (x, yPositions[i1 - 1]));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnSinglePda(MapCoordinates coordinates, Vector2 pos)
|
||||
{
|
||||
var pda = Spawn("SurgeryRealmPDA", coordinates.Offset(pos.X, 20));
|
||||
_console.ExecuteCommand($"scale {pda} 2");
|
||||
|
||||
var sliding = EnsureComp<SurgeryRealmSlidingComponent>(pda);
|
||||
sliding.FinalY = pos.Y;
|
||||
sliding.SectionPos = coordinates;
|
||||
|
||||
Physics.SetLinearVelocity(pda, new Vector2(0, -20));
|
||||
}
|
||||
|
||||
public void StopOperation(IPlayerSession victimPlayer, bool successful = false)
|
||||
{
|
||||
if (victimPlayer.AttachedEntity is not { } victimEntity)
|
||||
return;
|
||||
|
||||
if (!TryComp(victimEntity, out SurgeryRealmCameraComponent? camera))
|
||||
return;
|
||||
|
||||
if (Deleted(camera.OldEntity))
|
||||
return;
|
||||
|
||||
camera.Mind?.UnVisit();
|
||||
|
||||
victimEntity = victimPlayer.AttachedEntity.Value;
|
||||
_godmode.DisableGodmode(victimEntity);
|
||||
|
||||
if (TryComp(victimEntity, out SurgeryRealmVictimComponent? victim))
|
||||
{
|
||||
if (successful)
|
||||
RaiseLocalEvent(victimEntity, new RejuvenateEvent());
|
||||
|
||||
if (TryComp(victim.Tool, out SurgeryRealmToolComponent? tool))
|
||||
{
|
||||
tool.Victims.Remove(victimPlayer);
|
||||
if (tool.Victims.Count == 0)
|
||||
tool.Position = null;
|
||||
}
|
||||
}
|
||||
|
||||
RemComp<SurgeryRealmVictimComponent>(victimEntity);
|
||||
|
||||
camera.OldEntity = null;
|
||||
QueueDel(camera.Owner);
|
||||
}
|
||||
|
||||
private void EnsureMap()
|
||||
{
|
||||
if (_surgeryRealmMap != MapId.Nullspace && _maps.MapExists(_surgeryRealmMap))
|
||||
return;
|
||||
|
||||
_surgeryRealmMap = _maps.CreateMap();
|
||||
var map = Comp<MapComponent>(_maps.GetMapEntityId(_surgeryRealmMap));
|
||||
|
||||
map.LightingEnabled = false;
|
||||
Dirty(map);
|
||||
}
|
||||
|
||||
// Copied from TabletopSystem
|
||||
private Vector2 GetNextPosition()
|
||||
{
|
||||
return UlamSpiral(_sections++) * SectionSeparation;
|
||||
}
|
||||
|
||||
private Vector2i UlamSpiral(int n)
|
||||
{
|
||||
var k = (int)MathF.Ceiling(MathF.Sqrt(n) - 1) / 2;
|
||||
var t = 2 * k + 1;
|
||||
var m = (int)MathF.Pow(t, 2);
|
||||
t--;
|
||||
|
||||
if (n >= m - t)
|
||||
return new Vector2i(k - (m - n), -k);
|
||||
|
||||
m -= t;
|
||||
|
||||
if (n >= m - t)
|
||||
return new Vector2i(-k, -k + (m - n));
|
||||
|
||||
m -= t;
|
||||
|
||||
if (n >= m - t)
|
||||
return new Vector2i(-k + (m - n), k);
|
||||
|
||||
return new Vector2i(k, k - (m - n - t));
|
||||
}
|
||||
}
|
||||
17
Content.Server/Medical/Surgery/SurgeryRealmToolComponent.cs
Normal file
17
Content.Server/Medical/Surgery/SurgeryRealmToolComponent.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Medical.Surgery;
|
||||
|
||||
[RegisterComponent]
|
||||
[Access(typeof(SurgeryRealmSystem))]
|
||||
public sealed class SurgeryRealmToolComponent : Component
|
||||
{
|
||||
[ViewVariables] public readonly HashSet<IPlayerSession> Victims = new();
|
||||
|
||||
[ViewVariables] public MapCoordinates? Position;
|
||||
|
||||
[DataField("heart")] public string HeartPrototype = "SurgeryRealmHeart";
|
||||
|
||||
public int Fight;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Content.Server.Medical.Surgery;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class SurgeryRealmToolDuelComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Content.Server.Medical.Surgery;
|
||||
|
||||
[RegisterComponent]
|
||||
[Access(typeof(SurgeryRealmSystem))]
|
||||
public sealed class SurgeryRealmVictimComponent : Component
|
||||
{
|
||||
[ViewVariables] public EntityUid Heart;
|
||||
|
||||
[ViewVariables] public EntityUid Tool;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using Content.Server.Body.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Chemistry.EntitySystems;
|
||||
using Content.Server.Fluids.Components;
|
||||
using Content.Server.Forensics;
|
||||
using Content.Server.Nutrition.Components;
|
||||
using Content.Server.Nutrition.EntitySystems;
|
||||
using Content.Server.Popups;
|
||||
@@ -49,6 +50,10 @@ namespace Content.Server.Medical
|
||||
|
||||
var puddle = EntityManager.SpawnEntity("PuddleVomit", Transform(uid).Coordinates);
|
||||
|
||||
var forensics = EnsureComp<ForensicsComponent>(puddle);
|
||||
if (TryComp<DnaComponent>(uid, out var dna))
|
||||
forensics.DNAs.Add(dna.DNA);
|
||||
|
||||
var puddleComp = Comp<PuddleComponent>(puddle);
|
||||
|
||||
SoundSystem.Play("/Audio/Effects/Fluids/splat.ogg", Filter.Pvs(uid), uid, AudioHelpers.WithVariation(0.2f).WithVolume(-4f));
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.Chemistry.Components.SolutionManager;
|
||||
using Content.Server.Chemistry.EntitySystems;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.Forensics;
|
||||
using Content.Server.Nutrition.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Administration.Logs;
|
||||
@@ -377,6 +378,10 @@ namespace Content.Server.Nutrition.EntitySystems
|
||||
|
||||
component.ForceDrink = false;
|
||||
args.Handled = true;
|
||||
|
||||
var comp = EnsureComp<ForensicsComponent>(uid);
|
||||
if (TryComp<DnaComponent>(args.Args.Target, out var dna))
|
||||
comp.DNAs.Add(dna.DNA);
|
||||
}
|
||||
|
||||
private void AddDrinkVerb(EntityUid uid, DrinkComponent component, GetVerbsEvent<AlternativeVerb> ev)
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Linq;
|
||||
using Content.Server.Afk;
|
||||
using Content.Server.Afk.Events;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
@@ -28,6 +29,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly PlayTimeTrackingManager _tracking = default!;
|
||||
[Dependency] private readonly AllCaptainsRuleSystem _allCaptainsRule = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -159,7 +161,8 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
||||
{
|
||||
if (!_prototypes.TryIndex<JobPrototype>(role, out var job) ||
|
||||
job.Requirements == null ||
|
||||
!_cfg.GetCVar(CCVars.GameRoleTimers))
|
||||
!_cfg.GetCVar(CCVars.GameRoleTimers) ||
|
||||
(_allCaptainsRule != null && _allCaptainsRule.RuleStarted))
|
||||
return true;
|
||||
|
||||
var playTimes = _tracking.GetTrackerTimes(player);
|
||||
@@ -170,7 +173,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
||||
public HashSet<string> GetDisallowedJobs(IPlayerSession player)
|
||||
{
|
||||
var roles = new HashSet<string>();
|
||||
if (!_cfg.GetCVar(CCVars.GameRoleTimers))
|
||||
if (!_cfg.GetCVar(CCVars.GameRoleTimers) || (_allCaptainsRule != null && _allCaptainsRule.RuleStarted))
|
||||
return roles;
|
||||
|
||||
var playTimes = _tracking.GetTrackerTimes(player);
|
||||
@@ -197,7 +200,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
||||
|
||||
public void RemoveDisallowedJobs(NetUserId userId, ref List<string> jobs)
|
||||
{
|
||||
if (!_cfg.GetCVar(CCVars.GameRoleTimers))
|
||||
if (!_cfg.GetCVar(CCVars.GameRoleTimers) || (_allCaptainsRule != null && _allCaptainsRule.RuleStarted))
|
||||
return;
|
||||
|
||||
var player = _playerManager.GetSessionByUserId(userId);
|
||||
|
||||
@@ -101,4 +101,131 @@ public sealed partial class DungeonSystem
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Mapping)]
|
||||
private void DungeonPackVis(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
if (args.Length != 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var mapInt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
|
||||
if (!_prototype.TryIndex<DungeonRoomPackPrototype>(args[1], out var pack))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var grid = EnsureComp<MapGridComponent>(mapUid);
|
||||
var tile = new Tile(_tileDefManager["FloorSteel"].TileId);
|
||||
var tiles = new List<(Vector2i, Tile)>();
|
||||
|
||||
foreach (var room in pack.Rooms)
|
||||
{
|
||||
for (var x = room.Left; x < room.Right; x++)
|
||||
{
|
||||
for (var y = room.Bottom; y < room.Top; y++)
|
||||
{
|
||||
var index = new Vector2i(x, y);
|
||||
tiles.Add((index, tile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the rest out with a blank tile to make it easier to see
|
||||
var dummyTile = new Tile(_tileDefManager["FloorAsteroidIronsand1"].TileId);
|
||||
|
||||
for (var x = 0; x < pack.Size.X; x++)
|
||||
{
|
||||
for (var y = 0; y < pack.Size.Y; y++)
|
||||
{
|
||||
var index = new Vector2i(x, y);
|
||||
if (tiles.Contains((index, tile)))
|
||||
continue;
|
||||
|
||||
tiles.Add((index, dummyTile));
|
||||
}
|
||||
}
|
||||
|
||||
grid.SetTiles(tiles);
|
||||
shell.WriteLine(Loc.GetString("cmd-dungen_pack_vis"));
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Mapping)]
|
||||
private void DungeonPresetVis(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
if (args.Length != 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var mapInt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
|
||||
if (!_prototype.TryIndex<DungeonPresetPrototype>(args[1], out var preset))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var grid = EnsureComp<MapGridComponent>(mapUid);
|
||||
var tile = new Tile(_tileDefManager["FloorSteel"].TileId);
|
||||
var tiles = new List<(Vector2i, Tile)>();
|
||||
|
||||
foreach (var room in preset.RoomPacks)
|
||||
{
|
||||
for (var x = room.Left; x < room.Right; x++)
|
||||
{
|
||||
for (var y = room.Bottom; y < room.Top; y++)
|
||||
{
|
||||
var index = new Vector2i(x, y);
|
||||
tiles.Add((index, tile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grid.SetTiles(tiles);
|
||||
shell.WriteLine(Loc.GetString("cmd-dungen_pack_vis"));
|
||||
}
|
||||
|
||||
private CompletionResult PresetCallback(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.MapIds(EntityManager), Loc.GetString("cmd-dungen-hint-map"));
|
||||
}
|
||||
|
||||
if (args.Length == 2)
|
||||
{
|
||||
return CompletionResult.FromOptions(CompletionHelper.PrototypeIDs<DungeonPresetPrototype>(proto: _prototype));
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
|
||||
private CompletionResult PackCallback(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.MapIds(EntityManager), Loc.GetString("cmd-dungen-hint-map"));
|
||||
}
|
||||
|
||||
if (args.Length == 2)
|
||||
{
|
||||
return CompletionResult.FromOptions(CompletionHelper.PrototypeIDs<DungeonRoomPackPrototype>(proto: _prototype));
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ public sealed partial class DungeonSystem : EntitySystem
|
||||
base.Initialize();
|
||||
_sawmill = Logger.GetSawmill("dungen");
|
||||
_console.RegisterCommand("dungen", Loc.GetString("cmd-dungen-desc"), Loc.GetString("cmd-dungen-help"), GenerateDungeon, CompletionCallback);
|
||||
_console.RegisterCommand("dungen_preset_vis", Loc.GetString("cmd-dungen_preset_vis-desc"), Loc.GetString("cmd-dungen_preset_vis-help"), DungeonPresetVis, PresetCallback);
|
||||
_console.RegisterCommand("dungen_pack_vis", Loc.GetString("cmd-dungen_pack_vis-desc"), Loc.GetString("cmd-dungen_pack_vis-help"), DungeonPackVis, PackCallback);
|
||||
_prototype.PrototypesReloaded += PrototypeReload;
|
||||
SubscribeLocalEvent<RoundStartingEvent>(OnRoundStart);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ public sealed partial class DockingSystem
|
||||
if (!ValidSpawn(grid, shuttleDockedAABB.Value))
|
||||
return false;
|
||||
|
||||
gridRotation = targetGridRotation + gridDockAngle - shuttleDockAngle;
|
||||
gridRotation = (targetGridRotation + gridDockAngle - shuttleDockAngle).Reduced();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ public sealed partial class DockingSystem
|
||||
var spawnPosition = new EntityCoordinates(targetGrid, matty.Transform(Vector2.Zero));
|
||||
spawnPosition = new EntityCoordinates(targetGridXform.MapUid!.Value, spawnPosition.ToMapPos(EntityManager, _transform));
|
||||
|
||||
var dockedBounds = new Box2Rotated(shuttleAABB.Translated(spawnPosition.Position), targetGridAngle, spawnPosition.Position);
|
||||
var dockedBounds = new Box2Rotated(shuttleAABB.Translated(spawnPosition.Position), targetAngle, spawnPosition.Position);
|
||||
|
||||
// Check if there's no intersecting grids (AKA oh god it's docking at cargo).
|
||||
if (_mapManager.FindGridsIntersecting(targetGridXform.MapID,
|
||||
@@ -180,8 +180,6 @@ public sealed partial class DockingSystem
|
||||
(dockUid, gridDockUid, shuttleDock, gridDock),
|
||||
};
|
||||
|
||||
// TODO: Check shuttle orientation as the tiebreaker.
|
||||
|
||||
foreach (var (otherUid, other) in shuttleDocks)
|
||||
{
|
||||
if (other == shuttleDock)
|
||||
|
||||
@@ -42,7 +42,17 @@ public sealed partial class EmergencyShuttleSystem
|
||||
private readonly TimeSpan _bufferTime = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="CCVars.EmergencyShuttleTransitTime"/>
|
||||
/// <see cref="CCVars.EmergencyShuttleMinTransitTime"/>
|
||||
/// </summary>
|
||||
public float MinimumTransitTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="CCVars.EmergencyShuttleMaxTransitTime"/>
|
||||
/// </summary>
|
||||
public float MaximumTransitTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// How long it will take for the emergency shuttle to arrive at CentComm.
|
||||
/// </summary>
|
||||
public float TransitTime { get; private set; }
|
||||
|
||||
@@ -70,7 +80,8 @@ public sealed partial class EmergencyShuttleSystem
|
||||
|
||||
private void InitializeEmergencyConsole()
|
||||
{
|
||||
_configManager.OnValueChanged(CCVars.EmergencyShuttleTransitTime, SetTransitTime, true);
|
||||
_configManager.OnValueChanged(CCVars.EmergencyShuttleMinTransitTime, SetMinTransitTime, true);
|
||||
_configManager.OnValueChanged(CCVars.EmergencyShuttleMaxTransitTime, SetMaxTransitTime, true);
|
||||
_configManager.OnValueChanged(CCVars.EmergencyShuttleAuthorizeTime, SetAuthorizeTime, true);
|
||||
SubscribeLocalEvent<EmergencyShuttleConsoleComponent, ComponentStartup>(OnEmergencyStartup);
|
||||
SubscribeLocalEvent<EmergencyShuttleConsoleComponent, EmergencyShuttleAuthorizeMessage>(OnEmergencyAuthorize);
|
||||
@@ -105,15 +116,22 @@ public sealed partial class EmergencyShuttleSystem
|
||||
_authorizeTime = obj;
|
||||
}
|
||||
|
||||
private void SetTransitTime(float obj)
|
||||
private void SetMinTransitTime(float obj)
|
||||
{
|
||||
TransitTime = obj;
|
||||
MinimumTransitTime = obj;
|
||||
MaximumTransitTime = Math.Max(MaximumTransitTime, MinimumTransitTime);
|
||||
}
|
||||
|
||||
private void SetMaxTransitTime(float obj)
|
||||
{
|
||||
MaximumTransitTime = Math.Max(MinimumTransitTime, obj);
|
||||
}
|
||||
|
||||
private void ShutdownEmergencyConsole()
|
||||
{
|
||||
_configManager.UnsubValueChanged(CCVars.EmergencyShuttleAuthorizeTime, SetAuthorizeTime);
|
||||
_configManager.UnsubValueChanged(CCVars.EmergencyShuttleTransitTime, SetTransitTime);
|
||||
_configManager.UnsubValueChanged(CCVars.EmergencyShuttleMinTransitTime, SetMinTransitTime);
|
||||
_configManager.UnsubValueChanged(CCVars.EmergencyShuttleMaxTransitTime, SetMaxTransitTime);
|
||||
}
|
||||
|
||||
private void OnEmergencyStartup(EntityUid uid, EmergencyShuttleConsoleComponent component, ComponentStartup args)
|
||||
@@ -293,6 +311,9 @@ public sealed partial class EmergencyShuttleSystem
|
||||
_consoleAccumulator = float.MinValue;
|
||||
EarlyLaunchAuthorized = false;
|
||||
EmergencyShuttleArrived = false;
|
||||
TransitTime = MinimumTransitTime + (MaximumTransitTime - MinimumTransitTime) * _random.NextFloat();
|
||||
// Round to nearest 10
|
||||
TransitTime = MathF.Round(TransitTime / 10f) * 10f;
|
||||
}
|
||||
|
||||
private void UpdateAllEmergencyConsoles()
|
||||
|
||||
@@ -43,9 +43,15 @@ public sealed partial class ShuttleSystem
|
||||
|
||||
// I'm too lazy to make CVars.
|
||||
|
||||
private readonly SoundSpecifier _startupSound = new SoundPathSpecifier("/Audio/Effects/Shuttle/hyperspace_begin.ogg");
|
||||
private readonly SoundSpecifier _startupSound = new SoundPathSpecifier("/Audio/Effects/Shuttle/hyperspace_begin.ogg")
|
||||
{
|
||||
Params = AudioParams.Default.WithVolume(-5f),
|
||||
};
|
||||
// private SoundSpecifier _travelSound = new SoundPathSpecifier();
|
||||
private readonly SoundSpecifier _arrivalSound = new SoundPathSpecifier("/Audio/Effects/Shuttle/hyperspace_end.ogg");
|
||||
private readonly SoundSpecifier _arrivalSound = new SoundPathSpecifier("/Audio/Effects/Shuttle/hyperspace_end.ogg")
|
||||
{
|
||||
Params = AudioParams.Default.WithVolume(-5f),
|
||||
};
|
||||
|
||||
private readonly TimeSpan _hyperspaceKnockdownTime = TimeSpan.FromSeconds(5);
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Singularity.PizzaLord.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Makes funny announcement when object with this component is absorbed by Pizza Lord (not the Singulo Lord!)
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class AnnounceOnAbsorbComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Fluent ID for the announcement title
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("title")]
|
||||
public string Title = "pizza-lord-itself";
|
||||
|
||||
/// <summary>
|
||||
/// Fluent ID for the announcement title
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("text")]
|
||||
public string Text = "pizza-lord-speech";
|
||||
|
||||
/// <summary>
|
||||
/// Announcement color
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("color")]
|
||||
public Color AnnouncementColor = Color.OrangeRed;
|
||||
|
||||
/// <summary>
|
||||
/// Announce sound file path
|
||||
/// </summary>
|
||||
[DataField("sound")]
|
||||
public SoundSpecifier AnnouncementSound = new SoundPathSpecifier("/Audio/Announcements/announce.ogg");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Singularity.Events;
|
||||
using Content.Server.Singularity.PizzaLord.Components;
|
||||
|
||||
namespace Content.Server.Singularity.PizzaLord.Systems;
|
||||
|
||||
public sealed class AnnounceOnAbsorbSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AnnounceOnAbsorbComponent, EventHorizonConsumedEntityEvent>(OnAbsorb);
|
||||
}
|
||||
|
||||
private void OnAbsorb(EntityUid uid, AnnounceOnAbsorbComponent comp, EventHorizonConsumedEntityEvent args)
|
||||
{
|
||||
var msg = Loc.GetString(comp.Text, ("object", Name(uid)));
|
||||
var title = Loc.GetString(comp.Title);
|
||||
_chatSystem.DispatchGlobalAnnouncement(msg, title, announcementSound: comp.AnnouncementSound, colorOverride: comp.AnnouncementColor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Content.Server.Speech.Components;
|
||||
|
||||
// #####. ##### ### ###. #####? ### #####!
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class ChatFilterAccentComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Content.Server.Speech.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for affirming oneself to the beauty and splendor of god.
|
||||
/// with this component, you can be assured of your spot in heaven and live
|
||||
/// life free of sin, greed, and the language of the devil.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class TrulyGoodAndHonestPureOfHeartChristianBoyAccentComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Server.Speech.EntitySystems;
|
||||
|
||||
public sealed class ChatFilterAccentSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private static readonly Dictionary<string, string> DirectReplacements = new()
|
||||
{
|
||||
{ "fuck", "####" },
|
||||
{ "shit", "####" },
|
||||
{ "ass", "###" },
|
||||
{ "dick", "####" },
|
||||
{ "bitch", "#####" },
|
||||
{ "piss", "####" },
|
||||
{ "damn", "####" },
|
||||
{ "kill", "####" },
|
||||
{ "hurt", "####" },
|
||||
{ "god", "###" },
|
||||
{ "hell", "####" },
|
||||
{ "nuke", "####" },
|
||||
{ "dang", "####" },
|
||||
{ "lol", "###" },
|
||||
{ "nukies", "#####" },
|
||||
{ "nukie", "#####" },
|
||||
{ "gun", "###" },
|
||||
{ "ammo", "####" },
|
||||
{ "bridge", "#####" },
|
||||
{ "med", "###" },
|
||||
{ "clown", "#####" },
|
||||
{ "admin", "#####" },
|
||||
{ "badmin", "######" },
|
||||
{ "admeme", "######" },
|
||||
{ "badmeme", "######" },
|
||||
{ "centcom", "#######" },
|
||||
{ "singulo", "#######" },
|
||||
{ "singuloose", "##########" },
|
||||
{ "sword", "#####" },
|
||||
{ "dead", "####" },
|
||||
{ "died", "####" },
|
||||
{ "sus", "###" },
|
||||
{ "aos", "###" },
|
||||
{ "kos", "###" },
|
||||
{ "anomaly", "#######" },
|
||||
{ "shuttle", "#######" },
|
||||
{ "hos", "###" },
|
||||
{ "drunk", "#####" },
|
||||
{ "bar", "###" },
|
||||
{ "arrest", "#####" },
|
||||
{ "bomb", "####" },
|
||||
{ "I", "#" },
|
||||
{ "traitor", "#######" },
|
||||
{ "tator", "#####" },
|
||||
{ "bad", "###" },
|
||||
};
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ChatFilterAccentComponent, AccentGetEvent>(OnAccentGet);
|
||||
}
|
||||
|
||||
public string Accentuate(string message)
|
||||
{
|
||||
var msg = message;
|
||||
|
||||
if (_cfg.GetCVar(CCVars.GameChatFilter) == true)
|
||||
{
|
||||
|
||||
foreach (var (first, replace) in DirectReplacements)
|
||||
{
|
||||
msg = Regex.Replace(msg, $@"{first}", replace, RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
private void OnAccentGet(EntityUid uid, ChatFilterAccentComponent component, AccentGetEvent args)
|
||||
{
|
||||
args.Message = Accentuate(args.Message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Content.Server.Speech.Components;
|
||||
namespace Content.Server.Speech.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// This handles the logic and function by which the possesor of the
|
||||
/// <see cref="TrulyGoodAndHonestPureOfHeartChristianBoyAccentComponent"/>
|
||||
/// can use to absolve themselves of the ability to commit sin and hate unto
|
||||
/// god's pure and lovely world.
|
||||
/// </summary>
|
||||
public sealed class TrulyGoodAndHonestPureOfHeartChristianBoyAccentSystem : EntitySystem
|
||||
{
|
||||
private static readonly Dictionary<string, string> DirectReplacements = new()
|
||||
{
|
||||
// Corvax-Localization-Start
|
||||
{ "блять", "непотребная женщина" },
|
||||
{ "блядь", "непотребная женщина" },
|
||||
{ "бля", "уф" },
|
||||
{ "сука", "негодяй" },
|
||||
{ "пиздец", "катастрофа" },
|
||||
{ "ебать", "осуществлять половой акт" },
|
||||
{ "блядский", "чрезвычайно" },
|
||||
{ "ёбанный", "исключительно" },
|
||||
{ "ебанный", "крайне" },
|
||||
{ "ебучий", "весьма" },
|
||||
{ "хуево", "плохо" },
|
||||
{ "хуёво", "отвратительно" },
|
||||
{ "хуй", "мужской половой орган" },
|
||||
{ "мудак", "неприятный человек" },
|
||||
{ "еблан", "неприятный человек" },
|
||||
{ "хуйло", "неприятный человек" },
|
||||
{ "долбаеб", "неприятный человек" },
|
||||
{ "пиздато", "отлично" },
|
||||
{ "заебись", "очень хорошо" },
|
||||
{ "охуено", "замечательно" },
|
||||
{ "охуительно", "невероятно" },
|
||||
{ "заебал", "заколебал" },
|
||||
{ "доебал", "задолбал" },
|
||||
{ "отъебись", "отстань" },
|
||||
{ "убить", "полюбить" },
|
||||
{ "убей", "полюби" },
|
||||
{ "бог", "Бог" },
|
||||
{ "аду", "злом доме врага бога" },
|
||||
// Corvax-Localization-End
|
||||
{ "fuck", "frick" },
|
||||
{ "shit", "poop" },
|
||||
{ "ass", "butt" },
|
||||
{ "dick", "peter-pecker" },
|
||||
{ "bitch", "nice woman" },
|
||||
{ "piss", "pee" },
|
||||
{ "damn", "beaver dam" },
|
||||
{ "kill", "love" },
|
||||
{ "hurt", "love" },
|
||||
{ "god", "God" },
|
||||
{ "hell", "the evil home of the enemy of god" }
|
||||
};
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TrulyGoodAndHonestPureOfHeartChristianBoyAccentComponent, AccentGetEvent>(OnAccentGet);
|
||||
}
|
||||
|
||||
public string Accentuate(string message)
|
||||
{
|
||||
var msg = message;
|
||||
|
||||
foreach (var (first, replace) in DirectReplacements)
|
||||
{
|
||||
msg = Regex.Replace(msg, $@"{first}", replace, RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
private void OnAccentGet(EntityUid uid, TrulyGoodAndHonestPureOfHeartChristianBoyAccentComponent component, AccentGetEvent args)
|
||||
{
|
||||
args.Message = Accentuate(args.Message);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Roles;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
@@ -9,7 +10,7 @@ namespace Content.Server.Station.Components;
|
||||
/// <summary>
|
||||
/// Stores information about a station's job selection.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(StationJobsSystem)), PublicAPI]
|
||||
[RegisterComponent, Access(typeof(StationJobsSystem), typeof(AllCaptainsRuleSystem)), PublicAPI]
|
||||
public sealed class StationJobsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
@@ -25,6 +26,7 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly AllCaptainsRuleSystem _allCaptainsRule = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
@@ -33,6 +35,8 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
SubscribeLocalEvent<StationJobsComponent, StationRenamedEvent>(OnStationRenamed);
|
||||
SubscribeLocalEvent<StationJobsComponent, ComponentShutdown>(OnStationDeletion);
|
||||
SubscribeLocalEvent<PlayerJoinedLobbyEvent>(OnPlayerJoinedLobby);
|
||||
SubscribeLocalEvent<GameRuleStartedEvent>(OnGameRuleStarted);
|
||||
SubscribeLocalEvent<GameRuleEndedEvent>(OnGameRuleEnded);
|
||||
_configurationManager.OnValueChanged(CCVars.GameDisallowLateJoins, _ => UpdateJobsAvailable(), true);
|
||||
}
|
||||
|
||||
@@ -136,6 +140,9 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
|
||||
var jobList = stationJobs.JobList;
|
||||
|
||||
if (_allCaptainsRule != null && _allCaptainsRule.RuleStarted)
|
||||
jobList = _allCaptainsRule.GetJobs(station).JobList;
|
||||
|
||||
// This should:
|
||||
// - Return true when zero slots are added/removed.
|
||||
// - Return true when you add.
|
||||
@@ -214,6 +221,11 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
|
||||
var jobList = stationJobs.JobList;
|
||||
|
||||
// If all captains mode, override job list with the allcaptains job list -- prevents modifying the "real" job list
|
||||
// in case mode changes later.
|
||||
if (_allCaptainsRule != null && _allCaptainsRule.RuleStarted)
|
||||
jobList = _allCaptainsRule.GetJobs(station).JobList;
|
||||
|
||||
switch (jobList.ContainsKey(jobPrototypeId))
|
||||
{
|
||||
case false:
|
||||
@@ -313,7 +325,11 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
if (!Resolve(station, ref stationJobs))
|
||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||
|
||||
if (stationJobs.JobList.TryGetValue(jobPrototypeId, out var job))
|
||||
var jobList = stationJobs.JobList;
|
||||
if (_allCaptainsRule != null && _allCaptainsRule.RuleStarted)
|
||||
jobList = _allCaptainsRule.GetJobs(station).JobList;
|
||||
|
||||
if (jobList.TryGetValue(jobPrototypeId, out var job))
|
||||
{
|
||||
slots = job;
|
||||
return true;
|
||||
@@ -337,6 +353,9 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
if (!Resolve(station, ref stationJobs))
|
||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||
|
||||
if (_allCaptainsRule != null && _allCaptainsRule.RuleStarted)
|
||||
return _allCaptainsRule.GetJobs(station).JobList.Where(x => x.Value != 0).Select(x => x.Key).ToHashSet();
|
||||
|
||||
return stationJobs.JobList.Where(x => x.Value != 0).Select(x => x.Key).ToHashSet();
|
||||
}
|
||||
|
||||
@@ -352,6 +371,9 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
if (!Resolve(station, ref stationJobs))
|
||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||
|
||||
if (_allCaptainsRule != null && _allCaptainsRule.RuleStarted)
|
||||
return _allCaptainsRule.GetJobs(station).OverflowJobs.ToHashSet();
|
||||
|
||||
return stationJobs.OverflowJobs.ToHashSet();
|
||||
}
|
||||
|
||||
@@ -367,6 +389,9 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
if (!Resolve(station, ref stationJobs))
|
||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||
|
||||
if (_allCaptainsRule != null && _allCaptainsRule.RuleStarted)
|
||||
return _allCaptainsRule.GetJobs(station).JobList;
|
||||
|
||||
return stationJobs.JobList;
|
||||
}
|
||||
|
||||
@@ -382,6 +407,9 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
if (!Resolve(station, ref stationJobs))
|
||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||
|
||||
if (_allCaptainsRule != null && _allCaptainsRule.RuleStarted)
|
||||
return _allCaptainsRule.GetJobs(station).RoundStartJobList;
|
||||
|
||||
return stationJobs.RoundStartJobList;
|
||||
}
|
||||
|
||||
@@ -467,6 +495,8 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
foreach (var station in _stationSystem.Stations)
|
||||
{
|
||||
var list = Comp<StationJobsComponent>(station).JobList.ToDictionary(x => x.Key, x => x.Value);
|
||||
if (_allCaptainsRule != null && _allCaptainsRule.RuleStarted)
|
||||
list = _allCaptainsRule.GetJobs(station).JobList.ToDictionary(x => x.Key, x => x.Value);
|
||||
jobs.Add(station, list);
|
||||
stationNames.Add(station, Name(station));
|
||||
}
|
||||
@@ -491,5 +521,17 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
UpdateJobsAvailable();
|
||||
}
|
||||
|
||||
private void OnGameRuleStarted(GameRuleStartedEvent msg)
|
||||
{
|
||||
if (msg.Rule.ID == "AllCaptains")
|
||||
UpdateJobsAvailable();
|
||||
}
|
||||
|
||||
private void OnGameRuleEnded(GameRuleEndedEvent msg)
|
||||
{
|
||||
if (msg.Rule.ID == "AllCaptains")
|
||||
UpdateJobsAvailable();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -74,8 +74,9 @@ public sealed class StationRecordsSystem : EntitySystem
|
||||
}
|
||||
|
||||
TryComp<FingerprintComponent>(player, out var fingerprintComponent);
|
||||
TryComp<DnaComponent>(player, out var dnaComponent);
|
||||
|
||||
CreateGeneralRecord(station, idUid.Value, profile.Name, profile.Age, profile.Species, profile.Gender, jobId, fingerprintComponent?.Fingerprint, profile, records);
|
||||
CreateGeneralRecord(station, idUid.Value, profile.Name, profile.Age, profile.Species, profile.Gender, jobId, fingerprintComponent?.Fingerprint, dnaComponent?.DNA, profile, records);
|
||||
}
|
||||
|
||||
|
||||
@@ -97,13 +98,16 @@ public sealed class StationRecordsSystem : EntitySystem
|
||||
/// this call will cause an exception. Ensure that a general record starts out with a job
|
||||
/// that is currently a valid job prototype.
|
||||
/// </param>
|
||||
/// <param name="mobFingerprint">Fingerprint of the character.</param>
|
||||
/// <param name="dna">DNA of the character.</param>
|
||||
///
|
||||
/// <param name="profile">
|
||||
/// Profile for the related player. This is so that other systems can get further information
|
||||
/// about the player character.
|
||||
/// Optional - other systems should anticipate this.
|
||||
/// </param>
|
||||
/// <param name="records">Station records component.</param>
|
||||
public void CreateGeneralRecord(EntityUid station, EntityUid? idUid, string name, int age, string species, Gender gender, string jobId, string? mobFingerprint, HumanoidCharacterProfile? profile = null,
|
||||
public void CreateGeneralRecord(EntityUid station, EntityUid? idUid, string name, int age, string species, Gender gender, string jobId, string? mobFingerprint, string? dna, HumanoidCharacterProfile? profile = null,
|
||||
StationRecordsComponent? records = null)
|
||||
{
|
||||
if (!Resolve(station, ref records))
|
||||
@@ -126,7 +130,8 @@ public sealed class StationRecordsSystem : EntitySystem
|
||||
Species = species,
|
||||
Gender = gender,
|
||||
DisplayPriority = jobPrototype.Weight,
|
||||
Fingerprint = mobFingerprint
|
||||
Fingerprint = mobFingerprint,
|
||||
DNA = dna
|
||||
};
|
||||
|
||||
var key = AddRecord(station, records);
|
||||
|
||||
64
Content.Server/Store/Systems/StoreSystem.Command.cs
Normal file
64
Content.Server/Store/Systems/StoreSystem.Command.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Store.Systems;
|
||||
|
||||
public sealed partial class StoreSystem
|
||||
{
|
||||
[Dependency] private readonly IConsoleHost _consoleHost = default!;
|
||||
|
||||
public void InitializeCommand()
|
||||
{
|
||||
_consoleHost.RegisterCommand("addcurrency", "Adds currency to the specified store", "addcurrency <uid> <currency prototype> <amount>",
|
||||
AddCurrencyCommand,
|
||||
AddCurrencyCommandCompletions);
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
private void AddCurrencyCommand(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
if (args.Length != 3)
|
||||
{
|
||||
shell.WriteError("Argument length must be 3");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityUid.TryParse(args[0], out var uid) || !float.TryParse(args[2], out var id))
|
||||
return;
|
||||
|
||||
if (!TryComp<StoreComponent>(uid, out var store))
|
||||
return;
|
||||
|
||||
var currency = new Dictionary<string, FixedPoint2>
|
||||
{
|
||||
{ args[1], id }
|
||||
};
|
||||
|
||||
TryAddCurrency(currency, uid, store);
|
||||
}
|
||||
|
||||
private CompletionResult AddCurrencyCommandCompletions(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
var query = EntityQueryEnumerator<StoreComponent>();
|
||||
var allStores = new List<string>();
|
||||
while (query.MoveNext(out var storeuid, out _))
|
||||
{
|
||||
allStores.Add(storeuid.ToString());
|
||||
}
|
||||
return CompletionResult.FromHintOptions(allStores, "<uid>");
|
||||
}
|
||||
|
||||
if (args.Length == 2 && EntityUid.TryParse(args[0], out var uid))
|
||||
{
|
||||
if (TryComp<StoreComponent>(uid, out var store))
|
||||
return CompletionResult.FromHintOptions(store.CurrencyWhitelist, "<currency prototype>");
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ public sealed partial class StoreSystem : EntitySystem
|
||||
SubscribeLocalEvent<StoreComponent, ComponentShutdown>(OnShutdown);
|
||||
|
||||
InitializeUi();
|
||||
InitializeCommand();
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, StoreComponent component, MapInitEvent args)
|
||||
|
||||
8
Content.Server/SubFloor/TrayScannerSystem.cs
Normal file
8
Content.Server/SubFloor/TrayScannerSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.SubFloor;
|
||||
|
||||
namespace Content.Server.SubFloor;
|
||||
|
||||
public sealed class TrayScannerSystem : SharedTrayScannerSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -3,7 +3,11 @@ using Content.Server.GameTicking;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Tips;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
@@ -23,6 +27,7 @@ public sealed class TipsSystem : EntitySystem
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly GameTicker _ticker = default!;
|
||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||
|
||||
private bool _tipsEnabled;
|
||||
private float _tipTimeOutOfRound;
|
||||
@@ -43,8 +48,65 @@ public sealed class TipsSystem : EntitySystem
|
||||
_cfg.OnValueChanged(CCVars.TipsDataset, SetDataset, true);
|
||||
|
||||
RecalculateNextTipTime();
|
||||
_conHost.RegisterCommand("clippy", SendClippy);
|
||||
_conHost.RegisterCommand("tip", SendTip);
|
||||
}
|
||||
|
||||
private void SendTip(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
AnnounceRandomTip();
|
||||
RecalculateNextTipTime();
|
||||
}
|
||||
|
||||
private void SendClippy(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
shell.WriteLine(
|
||||
"usage: clippy <player Uid | broadcast> <message> [entity prototype] [speak time] [slide time] [waddle]");
|
||||
return;
|
||||
}
|
||||
|
||||
ActorComponent? actor = null;
|
||||
if (args[0] != "broadcast" && args[0] != "all")
|
||||
{
|
||||
if (!EntityUid.TryParse(args[0], out var uid)
|
||||
|| !TryComp(uid, out actor))
|
||||
{
|
||||
shell.WriteError($"Could not find player {args[0]}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var ev = new ClippyEvent(args[1]);
|
||||
|
||||
string proto;
|
||||
if (args.Length > 2)
|
||||
{
|
||||
ev.Proto = args[2];
|
||||
if (!_prototype.HasIndex<EntityPrototype>(args[2]))
|
||||
{
|
||||
shell.WriteError($"Unknown prototype: {args[2]}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (args.Length > 3)
|
||||
ev.SpeakTime = float.Parse(args[3]);
|
||||
|
||||
if (args.Length > 4)
|
||||
ev.SlideTime = float.Parse(args[4]);
|
||||
|
||||
if (args.Length > 5)
|
||||
ev.WaddleInterval = float.Parse(args[5]);
|
||||
|
||||
if (actor != null)
|
||||
RaiseNetworkEvent(ev, actor.PlayerSession);
|
||||
else
|
||||
RaiseNetworkEvent(ev);
|
||||
}
|
||||
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
@@ -88,10 +150,11 @@ public sealed class TipsSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var tip = _random.Pick(tips.Values);
|
||||
var msg = Loc.GetString("tips-system-chat-message-wrap", ("tip", tip));
|
||||
var msg = $"Tippy the Clown says:\n\n{tip}";
|
||||
|
||||
_chat.ChatMessageToManyFiltered(Filter.Broadcast(), ChatChannel.OOC, tip, msg,
|
||||
EntityUid.Invalid, false, false, Color.MediumPurple);
|
||||
var ev = new ClippyEvent(msg);
|
||||
ev.SpeakTime = 1 + tip.Length * 0.05f;
|
||||
RaiseNetworkEvent(ev);
|
||||
}
|
||||
|
||||
private void RecalculateNextTipTime()
|
||||
|
||||
20
Content.Server/zlevels/ThirdDimension/LadderComponent.cs
Normal file
20
Content.Server/zlevels/ThirdDimension/LadderComponent.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server._Afterlight.ThirdDimension;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for ladders and traversing them.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class LadderComponent : Component
|
||||
{
|
||||
[DataField("primary")]
|
||||
public bool Primary = false;
|
||||
|
||||
[DataField("otherHalf")]
|
||||
public EntityUid? OtherHalf = EntityUid.Invalid;
|
||||
|
||||
[DataField("otherHalfProto", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string OtherHalfProto = "LadderLower";
|
||||
}
|
||||
56
Content.Server/zlevels/ThirdDimension/LadderSystem.cs
Normal file
56
Content.Server/zlevels/ThirdDimension/LadderSystem.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using Content.Shared._Afterlight.ThirdDimension;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server._Afterlight.ThirdDimension;
|
||||
|
||||
/// <summary>
|
||||
/// This handles...
|
||||
/// </summary>
|
||||
public sealed class LadderSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedZLevelSystem _zLevel = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<LadderComponent, InteractHandEvent>(OnInteractHand);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<LadderComponent, TransformComponent>();
|
||||
|
||||
while (query.MoveNext(out _, out var ladder, out var xform))
|
||||
{
|
||||
if (!ladder.Primary || Deleted(ladder.OtherHalf))
|
||||
return;
|
||||
|
||||
// Track it, it "hangs" from above.
|
||||
_transform.SetWorldPosition(ladder.OtherHalf.Value, _transform.GetWorldPosition(xform));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInteractHand(EntityUid uid, LadderComponent component, InteractHandEvent args)
|
||||
{
|
||||
EnsureOpposing(uid, component);
|
||||
_zLevel.TryTraverse(!component.Primary, args.User);
|
||||
}
|
||||
|
||||
private void EnsureOpposing(EntityUid uid, LadderComponent ladder)
|
||||
{
|
||||
if (!ladder.Primary || !Deleted(ladder.OtherHalf))
|
||||
return;
|
||||
|
||||
var parentXform = Transform(uid);
|
||||
var maybeBelow = _zLevel.MapBelow[(int) parentXform.MapID];
|
||||
|
||||
if (maybeBelow is not {} below)
|
||||
return;
|
||||
|
||||
var newCoords = new MapCoordinates(_transform.GetWorldPosition(parentXform), below);
|
||||
ladder.OtherHalf = Spawn(ladder.OtherHalfProto, newCoords);
|
||||
}
|
||||
}
|
||||
34
Content.Server/zlevels/ThirdDimension/ZViewSystem.cs
Normal file
34
Content.Server/zlevels/ThirdDimension/ZViewSystem.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Content.Shared._Afterlight.ThirdDimension;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Server._Afterlight.ThirdDimension;
|
||||
|
||||
public sealed class ZViewSystem : SharedZViewSystem
|
||||
{
|
||||
[Dependency] private readonly ViewSubscriberSystem _view = default!;
|
||||
[Dependency] private readonly SharedZLevelSystem _zLevel = default!;
|
||||
[Dependency] private readonly IServerNetManager _serverNet = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_serverNet.Connected += (sender, args) => _zLevel.UpdateMapList();
|
||||
}
|
||||
|
||||
public override EntityUid SpawnViewEnt(EntityUid source, MapCoordinates loc)
|
||||
{
|
||||
var ent = Spawn(null, loc);
|
||||
EnsureComp<EyeComponent>(ent);
|
||||
var actor = Comp<ActorComponent>(source);
|
||||
_view.AddViewSubscriber(ent, actor.PlayerSession);
|
||||
return ent;
|
||||
}
|
||||
|
||||
public override bool CanSetup(EntityUid source)
|
||||
{
|
||||
return TryComp<ActorComponent>(source, out var actor) && actor.PlayerSession.AttachedEntity == source;
|
||||
}
|
||||
}
|
||||
@@ -276,6 +276,12 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<bool> GameTableBonk =
|
||||
CVarDef.Create("game.table_bonk", false, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Enables the Roblox chat filter. True by default.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> GameChatFilter =
|
||||
CVarDef.Create("game.chat_filter", true, CVar.SERVERONLY);
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
/// <summary>
|
||||
/// Amount of times round start must fail before the server is shut down.
|
||||
@@ -1108,16 +1114,22 @@ namespace Content.Shared.CCVar
|
||||
CVarDef.Create("shuttle.emergency_authorize_time", 10f, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// How long after the console is authorized for the shuttle to early launch.
|
||||
/// The minimum time for the emergency shuttle to arrive at centcomm.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> EmergencyShuttleTransitTime =
|
||||
CVarDef.Create("shuttle.emergency_transit_time", 60f, CVar.SERVERONLY);
|
||||
public static readonly CVarDef<float> EmergencyShuttleMinTransitTime =
|
||||
CVarDef.Create("shuttle.emergency_transit_time_min", 60f, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// The maximum time for the emergency shuttle to arrive at centcomm.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> EmergencyShuttleMaxTransitTime =
|
||||
CVarDef.Create("shuttle.emergency_transit_time_max", 180f, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Whether the emergency shuttle is enabled or should the round just end.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> EmergencyShuttleEnabled =
|
||||
CVarDef.Create("shuttle.emergency_enabled", true, CVar.SERVERONLY);
|
||||
CVarDef.Create("shuttle.emergency", true, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// The percentage of time passed from the initial call to when the shuttle can no longer be recalled.
|
||||
@@ -1549,5 +1561,9 @@ namespace Content.Shared.CCVar
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> ConfigPresetDebug =
|
||||
CVarDef.Create("config.preset_debug", true, CVar.SERVERONLY);
|
||||
|
||||
// april fools
|
||||
public static readonly CVarDef<string> ClippyEntity =
|
||||
CVarDef.Create("clippy.entity", "Tippy", CVar.SERVER | CVar.REPLICATED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Content.Shared.Forensics
|
||||
{
|
||||
public readonly List<string> Fingerprints = new();
|
||||
public readonly List<string> Fibers = new();
|
||||
public readonly List<string> DNAs = new();
|
||||
public readonly string LastScannedName = string.Empty;
|
||||
public readonly TimeSpan PrintCooldown = TimeSpan.Zero;
|
||||
public readonly TimeSpan PrintReadyAt = TimeSpan.Zero;
|
||||
@@ -14,12 +15,14 @@ namespace Content.Shared.Forensics
|
||||
public ForensicScannerBoundUserInterfaceState(
|
||||
List<string> fingerprints,
|
||||
List<string> fibers,
|
||||
List<string> dnas,
|
||||
string lastScannedName,
|
||||
TimeSpan printCooldown,
|
||||
TimeSpan printReadyAt)
|
||||
{
|
||||
Fingerprints = fingerprints;
|
||||
Fibers = fibers;
|
||||
DNAs = dnas;
|
||||
LastScannedName = lastScannedName;
|
||||
PrintCooldown = printCooldown;
|
||||
PrintReadyAt = printReadyAt;
|
||||
|
||||
@@ -7,6 +7,41 @@ namespace Content.Shared.Gravity
|
||||
[Virtual]
|
||||
public class SharedGravityGeneratorComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// A map of the sprites used by the gravity generator given its status.
|
||||
/// </summary>
|
||||
[DataField("spriteMap")]
|
||||
[Access(typeof(SharedGravitySystem))]
|
||||
public Dictionary<GravityGeneratorStatus, string> SpriteMap = new();
|
||||
|
||||
/// <summary>
|
||||
/// The sprite used by the core of the gravity generator when the gravity generator is starting up.
|
||||
/// </summary>
|
||||
[DataField("coreStartupState")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string CoreStartupState = "startup";
|
||||
|
||||
/// <summary>
|
||||
/// The sprite used by the core of the gravity generator when the gravity generator is idle.
|
||||
/// </summary>
|
||||
[DataField("coreIdleState")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string CoreIdleState = "idle";
|
||||
|
||||
/// <summary>
|
||||
/// The sprite used by the core of the gravity generator when the gravity generator is activating.
|
||||
/// </summary>
|
||||
[DataField("coreActivatingState")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string CoreActivatingState = "activating";
|
||||
|
||||
/// <summary>
|
||||
/// The sprite used by the core of the gravity generator when the gravity generator is active.
|
||||
/// </summary>
|
||||
[DataField("coreActivatedState")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string CoreActivatedState = "activated";
|
||||
|
||||
/// <summary>
|
||||
/// Sent to the server to set whether the generator should be on or off
|
||||
/// </summary>
|
||||
|
||||
@@ -88,17 +88,18 @@ namespace Content.Shared.Gravity
|
||||
|
||||
private void OnGravityChange(ref GravityChangedEvent ev)
|
||||
{
|
||||
foreach (var (comp, xform) in EntityQuery<AlertsComponent, TransformComponent>(true))
|
||||
var alerts = AllEntityQuery<AlertsComponent, TransformComponent>();
|
||||
while(alerts.MoveNext(out var uid, out var comp, out var xform))
|
||||
{
|
||||
if (xform.GridUid != ev.ChangedGridIndex) continue;
|
||||
|
||||
if (!ev.HasGravity)
|
||||
{
|
||||
_alerts.ShowAlert(comp.Owner, AlertType.Weightless);
|
||||
_alerts.ShowAlert(uid, AlertType.Weightless);
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlert(comp.Owner, AlertType.Weightless);
|
||||
_alerts.ClearAlert(uid, AlertType.Weightless);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,7 +118,7 @@ namespace Content.Shared.Gravity
|
||||
|
||||
private void OnAlertsParentChange(EntityUid uid, AlertsComponent component, ref EntParentChangedMessage args)
|
||||
{
|
||||
if (IsWeightless(component.Owner))
|
||||
if (IsWeightless(uid))
|
||||
{
|
||||
_alerts.ShowAlert(uid, AlertType.Weightless);
|
||||
}
|
||||
|
||||
105
Content.Shared/Medical/Surgery/SharedSurgeryRealmSystem.cs
Normal file
105
Content.Shared/Medical/Surgery/SharedSurgeryRealmSystem.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
|
||||
namespace Content.Shared.Medical.Surgery;
|
||||
|
||||
public abstract class SharedSurgeryRealmSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
|
||||
[Dependency] protected readonly SharedPhysicsSystem Physics = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
UpdatesBefore.Add(typeof(SharedInputSystem));
|
||||
|
||||
SubscribeLocalEvent<SurgeryRealmSlidingComponent, ComponentGetState>(OnSlidingGetState);
|
||||
SubscribeLocalEvent<SurgeryRealmSlidingComponent, ComponentHandleState>(OnSlidingHandleState);
|
||||
|
||||
SubscribeLocalEvent<SurgeryRealmHeartComponent, ComponentGetState>(OnHeartGetState);
|
||||
SubscribeLocalEvent<SurgeryRealmHeartComponent, ComponentHandleState>(OnHeartHandleState);
|
||||
}
|
||||
|
||||
private void OnSlidingGetState(EntityUid uid, SurgeryRealmSlidingComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new SurgeryRealmSlidingComponentState(component.FinalY, component.SectionPos);
|
||||
}
|
||||
|
||||
private void OnSlidingHandleState(EntityUid uid, SurgeryRealmSlidingComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not SurgeryRealmSlidingComponentState state)
|
||||
return;
|
||||
|
||||
component.FinalY = state.FinalY;
|
||||
component.SectionPos = state.SectionPos;
|
||||
}
|
||||
|
||||
private void OnHeartGetState(EntityUid uid, SurgeryRealmHeartComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new SurgeryRealmHeartComponentState(component.Health);
|
||||
}
|
||||
|
||||
private void OnHeartHandleState(EntityUid uid, SurgeryRealmHeartComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not SurgeryRealmHeartComponentState state)
|
||||
return;
|
||||
|
||||
component.Health = state.Health;
|
||||
}
|
||||
|
||||
protected virtual void Fire(SurgeryRealmSlidingComponent sliding)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var sliding in EntityQuery<SurgeryRealmSlidingComponent>())
|
||||
{
|
||||
if (!sliding.Fired &&
|
||||
_transform.GetWorldPosition(sliding.Owner).Y - sliding.SectionPos.Y < sliding.FinalY)
|
||||
{
|
||||
Physics.SetLinearVelocity(sliding.Owner, (0, 0));
|
||||
Fire(sliding);
|
||||
}
|
||||
}
|
||||
|
||||
// foreach (var heart in EntityQuery<SurgeryRealmHeartComponent>())
|
||||
// {
|
||||
// if (!TryComp(heart.Owner, out InputMoverComponent? input))
|
||||
// continue;
|
||||
//
|
||||
// var newY = _transform.GetWorldPosition(heart.Owner).Y;
|
||||
//
|
||||
// if (heart.Falling)
|
||||
// {
|
||||
// if (Math.Abs(heart.LastY - newY) < 0.0001)
|
||||
// {
|
||||
// heart.Falling = false;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// heart.LastY = newY;
|
||||
//
|
||||
// if (heart.Falling)
|
||||
// {
|
||||
// input.HeldMoveButtons &= ~MoveButtons.Up;
|
||||
// input.HeldMoveButtons |= MoveButtons.Down;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if ((input.HeldMoveButtons & MoveButtons.Up) != 0)
|
||||
// {
|
||||
// input.HeldMoveButtons &= ~MoveButtons.Down;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// heart.Falling = true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
24
Content.Shared/Medical/Surgery/SurgeryRealmHeartComponent.cs
Normal file
24
Content.Shared/Medical/Surgery/SurgeryRealmHeartComponent.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Medical.Surgery;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedSurgeryRealmSystem))]
|
||||
public sealed class SurgeryRealmHeartComponent : Component
|
||||
{
|
||||
[ViewVariables] public int Health = 5;
|
||||
|
||||
[ViewVariables] public EntityUid Camera;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SurgeryRealmHeartComponentState : ComponentState
|
||||
{
|
||||
public int Health { get; }
|
||||
|
||||
public SurgeryRealmHeartComponentState(int health)
|
||||
{
|
||||
Health = health;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Medical.Surgery;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedSurgeryRealmSystem))]
|
||||
public sealed class SurgeryRealmSlidingComponent : Component
|
||||
{
|
||||
public float FinalY;
|
||||
public MapCoordinates SectionPos;
|
||||
public bool Fired;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SurgeryRealmSlidingComponentState : ComponentState
|
||||
{
|
||||
public float FinalY { get; }
|
||||
public MapCoordinates SectionPos { get; }
|
||||
|
||||
public SurgeryRealmSlidingComponentState(float finalY, MapCoordinates sectionPos)
|
||||
{
|
||||
FinalY = finalY;
|
||||
SectionPos = sectionPos;
|
||||
}
|
||||
}
|
||||
24
Content.Shared/Medical/Surgery/SurgeryRealmStartEvent.cs
Normal file
24
Content.Shared/Medical/Surgery/SurgeryRealmStartEvent.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Medical.Surgery;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SurgeryRealmStartEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Camera { get; }
|
||||
|
||||
public SurgeryRealmStartEvent(EntityUid camera)
|
||||
{
|
||||
Camera = camera;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SurgeryRealmRequestSelfEvent : EntityEventArgs
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SurgeryRealmAcceptSelfEvent : EntityEventArgs
|
||||
{
|
||||
}
|
||||
@@ -62,4 +62,10 @@ public sealed class GeneralStationRecord
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string? Fingerprint;
|
||||
|
||||
/// <summary>
|
||||
/// DNA of the person.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string? DNA;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace Content.Shared.SubFloor
|
||||
{
|
||||
[Dependency] protected readonly IMapManager MapManager = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly TrayScannerSystem _trayScannerSystem = default!;
|
||||
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
|
||||
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
|
||||
|
||||
@@ -70,7 +69,6 @@ namespace Content.Shared.SubFloor
|
||||
if (args.Anchored)
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
_trayScannerSystem.OnSubfloorAnchored(uid, component, xform);
|
||||
UpdateFloorCover(uid, component, xform);
|
||||
}
|
||||
else if (component.IsUnderCover)
|
||||
@@ -139,37 +137,6 @@ namespace Content.Shared.SubFloor
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function is used by T-Ray scanners or other sub-floor revealing entities to toggle visibility.
|
||||
/// </summary>
|
||||
public void SetEntitiesRevealed(IEnumerable<EntityUid> entities, EntityUid revealer, bool visible)
|
||||
{
|
||||
foreach (var uid in entities)
|
||||
{
|
||||
SetEntityRevealed(uid, revealer, visible);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function is used by T-Ray scanners or other sub-floor revealing entities to toggle visibility.
|
||||
/// </summary>
|
||||
public void SetEntityRevealed(EntityUid uid, EntityUid revealer, bool visible, SubFloorHideComponent? hideComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref hideComp, false))
|
||||
return;
|
||||
|
||||
if (visible)
|
||||
{
|
||||
if (hideComp.RevealedBy.Add(revealer) && hideComp.RevealedBy.Count == 1)
|
||||
UpdateAppearance(uid, hideComp);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (hideComp.RevealedBy.Remove(revealer) && hideComp.RevealedBy.Count == 0)
|
||||
UpdateAppearance(uid, hideComp);
|
||||
}
|
||||
|
||||
public void UpdateAppearance(
|
||||
EntityUid uid,
|
||||
SubFloorHideComponent? hideComp = null,
|
||||
@@ -186,7 +153,6 @@ namespace Content.Shared.SubFloor
|
||||
if (Resolve(uid, ref appearance, false))
|
||||
{
|
||||
Appearance.SetData(uid, SubFloorVisuals.Covered, hideComp.IsUnderCover, appearance);
|
||||
Appearance.SetData(uid, SubFloorVisuals.ScannerRevealed, hideComp.RevealedBy.Count != 0, appearance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,8 +160,15 @@ namespace Content.Shared.SubFloor
|
||||
[Serializable, NetSerializable]
|
||||
public enum SubFloorVisuals : byte
|
||||
{
|
||||
Covered, // is there a floor tile over this entity
|
||||
ScannerRevealed, // is this entity revealed by a scanner or some other entity?
|
||||
/// <summary>
|
||||
/// Is there a floor tile over this entity
|
||||
/// </summary>
|
||||
Covered,
|
||||
|
||||
/// <summary>
|
||||
/// Is this entity revealed by a scanner or some other entity?
|
||||
/// </summary>
|
||||
ScannerRevealed,
|
||||
}
|
||||
|
||||
public enum SubfloorLayers : byte
|
||||
|
||||
69
Content.Shared/SubFloor/SharedTrayScannerSystem.cs
Normal file
69
Content.Shared/SubFloor/SharedTrayScannerSystem.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Shared.SubFloor;
|
||||
|
||||
public abstract class SharedTrayScannerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
public const float SubfloorRevealAlpha = 0.8f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TrayScannerComponent, ComponentGetState>(OnTrayScannerGetState);
|
||||
SubscribeLocalEvent<TrayScannerComponent, ComponentHandleState>(OnTrayScannerHandleState);
|
||||
SubscribeLocalEvent<TrayScannerComponent, ActivateInWorldEvent>(OnTrayScannerActivate);
|
||||
}
|
||||
|
||||
private void OnTrayScannerActivate(EntityUid uid, TrayScannerComponent scanner, ActivateInWorldEvent args)
|
||||
{
|
||||
SetScannerEnabled(uid, !scanner.Enabled, scanner);
|
||||
}
|
||||
|
||||
private void SetScannerEnabled(EntityUid uid, bool enabled, TrayScannerComponent? scanner = null)
|
||||
{
|
||||
if (!Resolve(uid, ref scanner) || scanner.Enabled == enabled)
|
||||
return;
|
||||
|
||||
scanner.Enabled = enabled;
|
||||
Dirty(scanner);
|
||||
|
||||
// We don't remove from _activeScanners on disabled, because the update function will handle that, as well as
|
||||
// managing the revealed subfloor entities
|
||||
|
||||
if (TryComp<AppearanceComponent>(uid, out var appearance))
|
||||
{
|
||||
_appearance.SetData(uid, TrayScannerVisual.Visual, scanner.Enabled ? TrayScannerVisual.On : TrayScannerVisual.Off, appearance);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTrayScannerGetState(EntityUid uid, TrayScannerComponent scanner, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new TrayScannerState(scanner.Enabled);
|
||||
}
|
||||
|
||||
private void OnTrayScannerHandleState(EntityUid uid, TrayScannerComponent scanner, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not TrayScannerState state)
|
||||
return;
|
||||
|
||||
SetScannerEnabled(uid, state.Enabled, scanner);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum TrayScannerVisual : sbyte
|
||||
{
|
||||
Visual,
|
||||
On,
|
||||
Off
|
||||
}
|
||||
@@ -39,24 +39,11 @@ namespace Content.Shared.SubFloor
|
||||
[DataField("blockAmbience")]
|
||||
public bool BlockAmbience { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// When revealed using some scanning tool, what transparency should be used to draw this item?
|
||||
/// </summary>
|
||||
[DataField("scannerTransparency")]
|
||||
public float ScannerTransparency = 0.8f;
|
||||
|
||||
/// <summary>
|
||||
/// Sprite layer keys for the layers that are always visible, even if the entity is below a floor tile. E.g.,
|
||||
/// the vent part of a vent is always visible, even though the piping is hidden.
|
||||
/// </summary>
|
||||
[DataField("visibleLayers")]
|
||||
public HashSet<Enum> VisibleLayers = new() { SubfloorLayers.FirstLayer };
|
||||
|
||||
/// <summary>
|
||||
/// The entities this subfloor is revealed by.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[Access(typeof(SharedSubFloorHideSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
|
||||
public HashSet<EntityUid> RevealedBy { get; set; } = new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,33 +3,19 @@ using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.SubFloor;
|
||||
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent]
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed class TrayScannerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the scanner is currently on.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Last position of the scanner. Rounded to integers to avoid excessive entity lookups when moving.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Vector2i? LastLocation { get; set; }
|
||||
[ViewVariables, DataField("enabled")] public bool Enabled;
|
||||
|
||||
/// <summary>
|
||||
/// Radius in which the scanner will reveal entities. Centered on the <see cref="LastLocation"/>.
|
||||
/// </summary>
|
||||
[DataField("range")]
|
||||
public float Range { get; set; } = 2.5f;
|
||||
|
||||
/// <summary>
|
||||
/// The sub-floor entities that this scanner is currently revealing.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public HashSet<EntityUid> RevealedSubfloors = new();
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("range")]
|
||||
public float Range = 4f;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Shared.SubFloor;
|
||||
|
||||
public sealed class TrayScannerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedSubFloorHideSystem _subfloorSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private HashSet<EntityUid> _activeScanners = new();
|
||||
private RemQueue<EntityUid> _invalidScanners = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TrayScannerComponent, ComponentShutdown>(OnComponentShutdown);
|
||||
SubscribeLocalEvent<TrayScannerComponent, ComponentGetState>(OnTrayScannerGetState);
|
||||
SubscribeLocalEvent<TrayScannerComponent, ComponentHandleState>(OnTrayScannerHandleState);
|
||||
SubscribeLocalEvent<TrayScannerComponent, ActivateInWorldEvent>(OnTrayScannerActivate);
|
||||
}
|
||||
|
||||
private void OnTrayScannerActivate(EntityUid uid, TrayScannerComponent scanner, ActivateInWorldEvent args)
|
||||
{
|
||||
SetScannerEnabled(uid, !scanner.Enabled, scanner);
|
||||
}
|
||||
|
||||
private void SetScannerEnabled(EntityUid uid, bool enabled, TrayScannerComponent? scanner = null)
|
||||
{
|
||||
if (!Resolve(uid, ref scanner))
|
||||
return;
|
||||
|
||||
scanner.Enabled = enabled;
|
||||
Dirty(scanner);
|
||||
|
||||
if (scanner.Enabled)
|
||||
_activeScanners.Add(uid);
|
||||
|
||||
// We don't remove from _activeScanners on disabled, because the update function will handle that, as well as
|
||||
// managing the revealed subfloor entities
|
||||
|
||||
if (EntityManager.TryGetComponent<AppearanceComponent>(uid, out var appearance))
|
||||
{
|
||||
_appearance.SetData(uid, TrayScannerVisual.Visual, scanner.Enabled ? TrayScannerVisual.On : TrayScannerVisual.Off, appearance);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTrayScannerGetState(EntityUid uid, TrayScannerComponent scanner, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new TrayScannerState(scanner.Enabled);
|
||||
}
|
||||
|
||||
private void OnTrayScannerHandleState(EntityUid uid, TrayScannerComponent scanner, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not TrayScannerState state)
|
||||
return;
|
||||
|
||||
SetScannerEnabled(uid, state.Enabled, scanner);
|
||||
|
||||
// This is hacky and somewhat inefficient for the client. But when resetting predicted entities we have to unset
|
||||
// last position. This is because appearance data gets reset, but if the position isn't reset the scanner won't
|
||||
// re-reveal entities leading to odd visuals.
|
||||
scanner.LastLocation = null;
|
||||
}
|
||||
|
||||
public void OnComponentShutdown(EntityUid uid, TrayScannerComponent scanner, ComponentShutdown args)
|
||||
{
|
||||
_subfloorSystem.SetEntitiesRevealed(scanner.RevealedSubfloors, uid, false);
|
||||
_activeScanners.Remove(uid);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
if (!_gameTiming.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
if (!_activeScanners.Any())
|
||||
return;
|
||||
|
||||
foreach (var scanner in _activeScanners)
|
||||
{
|
||||
if (_invalidScanners.List != null
|
||||
&& _invalidScanners.List.Contains(scanner))
|
||||
continue;
|
||||
|
||||
if (!UpdateTrayScanner(scanner))
|
||||
_invalidScanners.Add(scanner);
|
||||
}
|
||||
|
||||
foreach (var invalidScanner in _invalidScanners)
|
||||
{
|
||||
_activeScanners.Remove(invalidScanner);
|
||||
}
|
||||
|
||||
_invalidScanners.List?.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a subfloor entity gets anchored (which includes spawning & coming into PVS range), Check for nearby scanners.
|
||||
/// </summary>
|
||||
public void OnSubfloorAnchored(EntityUid uid, SubFloorHideComponent? hideComp = null, TransformComponent? xform = null)
|
||||
{
|
||||
if (!Resolve(uid, ref hideComp, ref xform))
|
||||
return;
|
||||
|
||||
var pos = xform.MapPosition;
|
||||
|
||||
foreach (var entity in _activeScanners)
|
||||
{
|
||||
if (!TryComp(entity, out TrayScannerComponent? scanner))
|
||||
continue;
|
||||
|
||||
if (!Transform(entity).MapPosition.InRange(pos, scanner.Range))
|
||||
continue;
|
||||
|
||||
hideComp.RevealedBy.Add(entity);
|
||||
scanner.RevealedSubfloors.Add(uid);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a T-Ray scanner. Should be called on immediate
|
||||
/// state change (turned on/off), or during the update
|
||||
/// loop.
|
||||
/// </summary>
|
||||
/// <returns>true if the update was successful, false otherwise</returns>
|
||||
private bool UpdateTrayScanner(EntityUid uid, TrayScannerComponent? scanner = null, TransformComponent? transform = null)
|
||||
{
|
||||
if (!Resolve(uid, ref scanner, ref transform))
|
||||
return false;
|
||||
|
||||
// if the scanner was toggled off recently,
|
||||
// set all the known subfloor to invisible,
|
||||
// and return false so it's removed from
|
||||
// the active scanner list
|
||||
if (!scanner.Enabled || transform.MapID == MapId.Nullspace)
|
||||
{
|
||||
_subfloorSystem.SetEntitiesRevealed(scanner.RevealedSubfloors, uid, false);
|
||||
scanner.LastLocation = null;
|
||||
scanner.RevealedSubfloors.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
var pos = transform.LocalPosition;
|
||||
var parent = _transform.GetParent(transform);
|
||||
|
||||
// zero vector implies container
|
||||
//
|
||||
// this means we should get the entity transform's parent
|
||||
if (pos == Vector2.Zero
|
||||
&& parent != null
|
||||
&& _containerSystem.ContainsEntity(transform.ParentUid, uid))
|
||||
{
|
||||
pos = parent.LocalPosition;
|
||||
|
||||
// if this is also zero, we can check one more time
|
||||
//
|
||||
// could recurse through fully but i think that's useless,
|
||||
// just attempt to check through the gp's transform and if
|
||||
// that doesn't work, just don't bother any further
|
||||
if (pos == Vector2.Zero)
|
||||
{
|
||||
var gpTransform = _transform.GetParent(parent);
|
||||
if (gpTransform != null
|
||||
&& _containerSystem.ContainsEntity(gpTransform.Owner, transform.ParentUid))
|
||||
{
|
||||
pos = gpTransform.LocalPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// is the position still logically zero? just clear,
|
||||
// but we need to keep it as 'true' since this t-ray
|
||||
// is still technically on
|
||||
if (pos == Vector2.Zero)
|
||||
{
|
||||
_subfloorSystem.SetEntitiesRevealed(scanner.RevealedSubfloors, uid, false);
|
||||
scanner.RevealedSubfloors.Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
// get the rounded position so that small movements don't cause this to
|
||||
// update every time
|
||||
var flooredPos = (Vector2i) pos;
|
||||
|
||||
// MAYBE redo this. Currently different players can see different entities
|
||||
//
|
||||
// Here we avoid the entity lookup & return early if the scanner's position hasn't appreciably changed. However,
|
||||
// if a new player enters PVS-range, they will update the in-range entities on their end and use that to set
|
||||
// LastLocation. This means that different players can technically see different entities being revealed by the
|
||||
// same scanner. The correct fix for this is probably just to network the revealed entity set.... But I CBF
|
||||
// doing that right now....
|
||||
if (flooredPos == scanner.LastLocation
|
||||
|| float.IsNaN(flooredPos.X) && float.IsNaN(flooredPos.Y))
|
||||
return true;
|
||||
|
||||
scanner.LastLocation = flooredPos;
|
||||
|
||||
// Update entities in Range
|
||||
HashSet<EntityUid> nearby = new();
|
||||
var coords = transform.MapPosition;
|
||||
var worldBox = Box2.CenteredAround(coords.Position, (scanner.Range * 2, scanner.Range * 2));
|
||||
|
||||
// For now, limiting to the scanner's own grid. We could do a grid-lookup, but then what do we do if one grid
|
||||
// flies away, while the scanner's local-position remains unchanged?
|
||||
if (_mapManager.TryGetGrid(transform.GridUid, out var grid))
|
||||
{
|
||||
foreach (var entity in grid.GetAnchoredEntities(worldBox))
|
||||
{
|
||||
if (!Transform(entity).MapPosition.InRange(coords, scanner.Range))
|
||||
continue;
|
||||
|
||||
if (!TryComp(entity, out SubFloorHideComponent? hideComp))
|
||||
continue; // Not a hide-able entity.
|
||||
|
||||
nearby.Add(entity);
|
||||
|
||||
if (scanner.RevealedSubfloors.Add(entity))
|
||||
_subfloorSystem.SetEntityRevealed(entity, uid, true, hideComp);
|
||||
}
|
||||
}
|
||||
|
||||
// get all the old elements that are no longer detected
|
||||
HashSet<EntityUid> missing = new(scanner.RevealedSubfloors.Except(nearby));
|
||||
|
||||
// remove those from the list
|
||||
scanner.RevealedSubfloors.ExceptWith(missing);
|
||||
|
||||
// and hide them
|
||||
_subfloorSystem.SetEntitiesRevealed(missing, uid, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum TrayScannerVisual : sbyte
|
||||
{
|
||||
Visual,
|
||||
On,
|
||||
Off
|
||||
}
|
||||
19
Content.Shared/Tips/ClippyEvent.cs
Normal file
19
Content.Shared/Tips/ClippyEvent.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Tips;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ClippyEvent : EntityEventArgs
|
||||
{
|
||||
public ClippyEvent(string msg)
|
||||
{
|
||||
Msg = msg;
|
||||
}
|
||||
|
||||
public string Msg;
|
||||
public string? Proto;
|
||||
public float SpeakTime = 5;
|
||||
public float SlideTime = 3;
|
||||
public float WaddleInterval = 0.5f;
|
||||
}
|
||||
239
Content.Shared/zlevels/ThirdDimension/SharedZLevelSystem.cs
Normal file
239
Content.Shared/zlevels/ThirdDimension/SharedZLevelSystem.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._Afterlight.ThirdDimension;
|
||||
|
||||
/// <summary>
|
||||
/// This handles Z levels. I'm sorry to everyone who has to witness this.
|
||||
/// </summary>
|
||||
public sealed class SharedZLevelSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly SharedGravitySystem _gravity = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||
[Dependency] private readonly ISharedAdminManager _admin = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private List<MapId?> _mapAbove = new();
|
||||
[ViewVariables]
|
||||
private List<MapId?> _mapBelow = new();
|
||||
|
||||
public IReadOnlyList<MapId?> MapAbove => _mapAbove;
|
||||
public IReadOnlyList<MapId?> MapBelow => _mapBelow;
|
||||
|
||||
private bool DontDrop = false; //HACK: oh god
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<MapChangedEvent>(OnMapChanged);
|
||||
SubscribeNetworkEvent<MapListChangedEvent>(OnMapListChanged);
|
||||
if (_net.IsServer)
|
||||
{
|
||||
SubscribeLocalEvent<MoveEvent>(OnMove); // Sloth forgive me.
|
||||
_conHost.RegisterCommand("ztool", ZTool);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMove(ref MoveEvent ev)
|
||||
{
|
||||
if (DontDrop || _mapBelow[(int) ev.Component.MapID] == null || HasComp<MapGridComponent>(ev.Sender) || _gravity.IsWeightless(ev.Sender) || HasComp<SharedGhostComponent>(ev.Sender) || !HasComp<PhysicsComponent>(ev.Sender))
|
||||
return; // get out!
|
||||
|
||||
var mapEid = _map.GetMapEntityId(ev.Component.MapID);
|
||||
|
||||
if (ev.Component.Coordinates.EntityId != mapEid)
|
||||
return; // Can't fall through the map if we're not on it!
|
||||
|
||||
if (TryComp<MapGridComponent>(mapEid, out var grid))
|
||||
{
|
||||
if (grid.TryGetTileRef(_xformSystem.GetWorldPosition(ev.Component), out var tile) && !tile.Tile.IsEmpty)
|
||||
{
|
||||
return; // Can't fall through grids.
|
||||
}
|
||||
}
|
||||
|
||||
var phys = Comp<PhysicsComponent>(ev.Sender);
|
||||
if (phys.Momentum.Length < 0.2 || HasComp<InputMoverComponent>(ev.Sender) || HasComp<MobMoverComponent>(ev.Sender))
|
||||
{
|
||||
TryTraverse(false, ev.Sender);
|
||||
// THWACK
|
||||
_damageableSystem.TryChangeDamage(ev.Sender,
|
||||
new DamageSpecifier(_prototype.Index<DamageTypePrototype>("Blunt"), FixedPoint2.New(69)));
|
||||
}
|
||||
}
|
||||
|
||||
[AnyCommand]
|
||||
private void ZTool(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
if (shell.Player?.AttachedEntity is not {} ent)
|
||||
return;
|
||||
|
||||
if (!_admin.IsAdmin(ent))
|
||||
{
|
||||
shell.WriteLine("This code may be bad but it's not gonna let you break things.");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (args[0].ToLowerInvariant())
|
||||
{
|
||||
case "above":
|
||||
{
|
||||
var xform = Transform(ent);
|
||||
var map = MapAbove[(int) xform.MapID];
|
||||
shell.WriteLine($"The map above you is {(map is not null ? ToPrettyString(_map.GetMapEntityId(map.Value)) : "none")}");
|
||||
break;
|
||||
}
|
||||
case "below":
|
||||
{
|
||||
var xform = Transform(ent);
|
||||
var map = MapBelow[(int) xform.MapID];
|
||||
shell.WriteLine($"The map below you is {(map is not null ? ToPrettyString(_map.GetMapEntityId(map.Value)) : "none")}");
|
||||
break;
|
||||
}
|
||||
case "link":
|
||||
{
|
||||
var dir = args[2].ToLowerInvariant();
|
||||
var baseMap = new MapId(int.Parse(args[1]));
|
||||
var linkedMap = new MapId(int.Parse(args[3]));
|
||||
|
||||
if (dir != "above" && dir != "below")
|
||||
return;
|
||||
|
||||
if (dir != "above")
|
||||
{
|
||||
LinkMaps(baseMap, linkedMap);
|
||||
}
|
||||
else
|
||||
{
|
||||
LinkMaps(linkedMap, baseMap);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "traverse":
|
||||
{
|
||||
var dir = args[1].ToLowerInvariant();
|
||||
|
||||
if (dir != "above" && dir != "below")
|
||||
return;
|
||||
|
||||
TryTraverse(dir == "above", ent);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int AllMapsBelow(MapId map, ref MapId[] maps)
|
||||
{
|
||||
var curr = map;
|
||||
var idx = 0;
|
||||
|
||||
while (MapBelow[(int)curr] is { } below && idx < maps.Length)
|
||||
{
|
||||
maps[idx++] = below;
|
||||
curr = below;
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
public bool TryTraverse(bool direction, EntityUid traverser, TransformComponent? xform = default!)
|
||||
{
|
||||
if (!Resolve(traverser, ref xform))
|
||||
return false;
|
||||
|
||||
var worldPosition = _xformSystem.GetWorldPosition(xform);
|
||||
MapId? newMap;
|
||||
if (direction) // Going up!
|
||||
{
|
||||
newMap = MapAbove[(int)xform.MapID];
|
||||
}
|
||||
else
|
||||
{
|
||||
newMap = MapBelow[(int)xform.MapID];
|
||||
}
|
||||
Logger.Debug($"Traversing to {newMap}..");
|
||||
|
||||
if (newMap is null)
|
||||
return false;
|
||||
|
||||
var coords = EntityCoordinates.FromMap(_map, new MapCoordinates(worldPosition, newMap.Value));
|
||||
DontDrop = true;
|
||||
_xformSystem.SetCoordinates(traverser, coords);
|
||||
DontDrop = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void LinkMaps(MapId below, MapId above)
|
||||
{
|
||||
_mapBelow[(int)above] = below;
|
||||
_mapAbove[(int)below] = above;
|
||||
UpdateMapList();
|
||||
}
|
||||
|
||||
private void OnMapListChanged(MapListChangedEvent ev)
|
||||
{
|
||||
if (!_net.IsClient)
|
||||
return;
|
||||
|
||||
//yoink
|
||||
_mapAbove = ev.MapAbove.ToList();
|
||||
_mapBelow = ev.MapBelow.ToList();
|
||||
}
|
||||
|
||||
private void OnMapChanged(MapChangedEvent ev)
|
||||
{
|
||||
if (!ev.Created)
|
||||
return;
|
||||
|
||||
while ((int) ev.Map + 1 > _mapAbove.Count)
|
||||
{
|
||||
// Resize time.
|
||||
_mapAbove.Add(null);
|
||||
_mapBelow.Add(null);
|
||||
}
|
||||
UpdateMapList();
|
||||
}
|
||||
|
||||
public void UpdateMapList()
|
||||
{
|
||||
if (!_net.IsServer)
|
||||
return;
|
||||
|
||||
RaiseNetworkEvent(new MapListChangedEvent(_mapAbove.ToArray(), _mapBelow.ToArray()));
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class MapListChangedEvent : EntityEventArgs
|
||||
{
|
||||
public MapId?[] MapAbove;
|
||||
public MapId?[] MapBelow;
|
||||
|
||||
public MapListChangedEvent(MapId?[] mapAbove, MapId?[] mapBelow)
|
||||
{
|
||||
MapAbove = mapAbove;
|
||||
MapBelow = mapBelow;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Content.Shared/zlevels/ThirdDimension/ZViewComponent.cs
Normal file
25
Content.Shared/zlevels/ThirdDimension/ZViewComponent.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._Afterlight.ThirdDimension;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for...
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed class ZViewComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
public List<EntityUid> DownViewEnts = new();
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ZViewComponentState : ComponentState
|
||||
{
|
||||
public List<EntityUid> DownViewEnts;
|
||||
|
||||
public ZViewComponentState(List<EntityUid> downViewEnts)
|
||||
{
|
||||
DownViewEnts = downViewEnts;
|
||||
}
|
||||
}
|
||||
113
Content.Shared/zlevels/ThirdDimension/ZViewSystem.cs
Normal file
113
Content.Shared/zlevels/ThirdDimension/ZViewSystem.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Body.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
namespace Content.Shared._Afterlight.ThirdDimension;
|
||||
|
||||
/// <summary>
|
||||
/// This handles view between z levels
|
||||
/// </summary>
|
||||
public abstract class SharedZViewSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
|
||||
[Dependency] private readonly SharedZLevelSystem _zLevel = default!;
|
||||
|
||||
private const int ViewDepth = 3;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ZViewComponent, ComponentHandleState>(ZViewComponentHandleState);
|
||||
SubscribeLocalEvent<ZViewComponent, ComponentGetState>(ZViewComponentGetState);
|
||||
|
||||
}
|
||||
|
||||
private void ZViewComponentGetState(EntityUid uid, ZViewComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new ZViewComponentState(component.DownViewEnts);
|
||||
}
|
||||
|
||||
private void ZViewComponentHandleState(EntityUid uid, ZViewComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not ZViewComponentState state)
|
||||
return;
|
||||
|
||||
component.DownViewEnts = state.DownViewEnts;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
if (_net.IsServer)
|
||||
FrameUpdate(frameTime);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<SharedEyeComponent>();
|
||||
var toUpdate = new List<EntityUid>();
|
||||
while (query.MoveNext(out var uid, out _))
|
||||
{
|
||||
var view = EnsureComp<ZViewComponent>(uid);
|
||||
var xform = Transform(uid);
|
||||
var maps = new MapId[ViewDepth];
|
||||
var amt = _zLevel.AllMapsBelow(xform.MapID, ref maps);
|
||||
if (amt == 0)
|
||||
continue;
|
||||
|
||||
var currPos = _xformSystem.GetWorldPosition(xform);
|
||||
|
||||
if (view.DownViewEnts.Count != amt)
|
||||
{
|
||||
if (_net.IsClient || !CanSetup(uid))
|
||||
continue;
|
||||
toUpdate.Add(uid);
|
||||
Logger.Debug("Queued Z view update.");
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var (ent, map) in view.DownViewEnts.Zip(maps))
|
||||
{
|
||||
if (map == MapId.Nullspace)
|
||||
continue;
|
||||
|
||||
var coords = EntityCoordinates.FromMap(_map, new MapCoordinates(currPos, map));
|
||||
_xformSystem.SetCoordinates(ent, coords);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var uid in toUpdate)
|
||||
{
|
||||
Logger.Debug("Did z view update.");
|
||||
var view = EnsureComp<ZViewComponent>(uid);
|
||||
var xform = Transform(uid);
|
||||
foreach (var e in view.DownViewEnts)
|
||||
{
|
||||
QueueDel(e);
|
||||
}
|
||||
view.DownViewEnts.Clear();
|
||||
var maps = new MapId[ViewDepth];
|
||||
var amt = _zLevel.AllMapsBelow(xform.MapID, ref maps);
|
||||
if (amt == 0)
|
||||
continue;
|
||||
var currPos = _xformSystem.GetWorldPosition(xform);
|
||||
foreach (var map in maps)
|
||||
{
|
||||
if (map == MapId.Nullspace)
|
||||
continue;
|
||||
view.DownViewEnts.Add(SpawnViewEnt(uid, new MapCoordinates(currPos, map)));
|
||||
}
|
||||
|
||||
Dirty(view);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract EntityUid SpawnViewEnt(EntityUid source, MapCoordinates loc);
|
||||
public abstract bool CanSetup(EntityUid source);
|
||||
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
"weh" and "muffled_weh" are licensed under CC0.
|
||||
"weh" and "muffled_weh" are licensed under CC0.
|
||||
"skub" is licensed under CC0.
|
||||
BIN
Resources/Audio/Items/Toys/skub.ogg
Normal file
BIN
Resources/Audio/Items/Toys/skub.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Items/Toys/skub2.ogg
Normal file
BIN
Resources/Audio/Items/Toys/skub2.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Items/Toys/skub3.ogg
Normal file
BIN
Resources/Audio/Items/Toys/skub3.ogg
Normal file
Binary file not shown.
@@ -29,6 +29,6 @@
|
||||
source: "https://github.com/tgstation/tgstation/blob/529d97cb1c105bcd548e95a9c9070bbf5253dd81/sound/items/AirHorn.ogg"
|
||||
|
||||
- files: ["desk_bell_ring.ogg"]
|
||||
license: "CC0-1.0"
|
||||
copyright: "Created by NicholasJudy567, converted to OGG and mono"
|
||||
source: "https://freesound.org/people/NicholasJudy567/sounds/672185/"
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Created by SamKolber, shortened and converted to OGG and mono"
|
||||
source: "https://freesound.org/people/SamKolber/sounds/210022/"
|
||||
|
||||
Binary file not shown.
BIN
Resources/Audio/Surgery/blast.ogg
Normal file
BIN
Resources/Audio/Surgery/blast.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Surgery/megalovania.ogg
Normal file
BIN
Resources/Audio/Surgery/megalovania.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Surgery/midilovania.mid
Normal file
BIN
Resources/Audio/Surgery/midilovania.mid
Normal file
Binary file not shown.
BIN
Resources/Audio/Surgery/undermale.ogg
Normal file
BIN
Resources/Audio/Surgery/undermale.ogg
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user