66 Commits

Author SHA1 Message Date
Morbo
d36345cf53 Update locale 2023-04-02 08:47:44 +03:00
Morbo
959e695315 Merge remote-tracking branch 'upstream/april-fools-release' into april-fools 2023-04-02 08:40:12 +03:00
GoodWheatley
4434b3752c Chat Filter (April Fools) (#14999) 2023-04-01 14:47:21 -07:00
DrSmugleaf
8ca2182a7c Hotfix the scalpel from the underground 2023-04-01 14:43:23 -07:00
DrSmugleaf
bd954ca01a Add changelog for surgery 2023-04-01 13:18:15 -07:00
DrSmugleaf
10ecfd4df1 Surgery (#3939) 2023-04-01 13:11:53 -07:00
metalgearsloth
bba7e96927 Update submodule to 0.96.0.4 (#15039) 2023-04-01 20:53:42 +11:00
Morbo
fdc79e4d2a Trait adaptation 2023-04-01 10:02:21 +03:00
Morbo
cce22a32f3 Poor translation 2023-04-01 10:02:13 +03:00
Morbo
0dc500bbff Update locale 2023-04-01 09:23:50 +03:00
Morbo
5e6581ce6a Merge remote-tracking branch 'upstream/april-fools-release' into april-fools
# Conflicts:
#	Resources/Server Info/Guidebook/Science/Xenoarchaeology.xml
#	Resources/Textures/Clothing/Back/Duffels/chemistry.rsi/icon.png
#	Resources/Textures/Objects/Storage/boxes.rsi/box_security.png
#	Resources/Textures/Objects/Storage/boxes.rsi/meta.json
#	Resources/Textures/Structures/Power/Generation/Singularity/singularity_1.rsi/meta.json
#	Resources/Textures/Structures/Power/Generation/Singularity/singularity_1.rsi/singularity_1.png
#	Resources/Textures/Structures/Power/Generation/Singularity/singularity_2.rsi/meta.json
#	Resources/Textures/Structures/Power/Generation/Singularity/singularity_2.rsi/singularity_2.png
#	Resources/Textures/Structures/Power/Generation/Singularity/singularity_3.rsi/meta.json
#	Resources/Textures/Structures/Power/Generation/Singularity/singularity_3.rsi/singularity_3.png
#	Resources/Textures/Structures/Power/Generation/Singularity/singularity_4.rsi/meta.json
#	Resources/Textures/Structures/Power/Generation/Singularity/singularity_4.rsi/singularity_4.png
#	Resources/Textures/Structures/Power/Generation/Singularity/singularity_5.rsi/meta.json
#	Resources/Textures/Structures/Power/Generation/Singularity/singularity_5.rsi/singularity_5.png
#	Resources/Textures/Structures/Power/Generation/Singularity/singularity_6.rsi/meta.json
#	Resources/Textures/Structures/Power/Generation/Singularity/singularity_6.rsi/singularity_6.png
2023-04-01 09:22:31 +03:00
Moony
2c2f2e0045 Moony z level hack (#15033)
* save work

* Adds Z levels

* a

* ladders + parallax scroll

* zoom out not in

* oops, sandbox

* oops i broke the law

* run ci

* fuck

---------

Co-authored-by: moonheart08 <moonheart08@users.noreply.github.com>
2023-03-31 21:51:06 -05:00
Moony
a9f7ea5185 Adds Z Levels to the game (#15009)
* save work

* Adds Z levels

* a

* ladders + parallax scroll

* zoom out not in

* oops, sandbox

* oops i broke the law

* run ci

---------

Co-authored-by: moonheart08 <moonheart08@users.noreply.github.com>
2023-03-31 14:41:38 -05:00
csqrb
c7df69d853 The Pizza Lord! [April Fools] (#15012)
* pizza stuff

* goodies

* singularity does not exist
2023-03-31 14:18:29 -05:00
Moony
b1c6fce89c Merge branch 'master' into april-fools-release 2023-03-31 14:07:42 -05:00
PJBot
60839d7917 Automatic changelog update 2023-03-31 02:29:12 -04:00
metalgearsloth
acde17d87b Update submodule to 0.96.0.3 (#15008) 2023-03-31 17:28:08 +11:00
metalgearsloth
b6a735774b Add more dungeon layouts (#14924) 2023-03-30 22:54:17 -07:00
PJBot
732cb8b0d6 Automatic changelog update 2023-03-31 01:54:11 -04:00
MilenVolf
404bbbf309 Wall Weapon Capacitor Recharger Fix (#14982) 2023-03-30 22:53:07 -07:00
TemporalOroboros
76212b877e Resolves GravityGeneratorVisualizer is Obsolete (#13885) 2023-03-31 16:04:53 +11:00
PJBot
ad02129045 Automatic changelog update 2023-03-31 00:50:29 -04:00
faint
8b6996cbae DNA basics (#14724)
* DNA component

* Commit numba 2

* Added DNA into Station Records Computer

* commit numba 3

* commit numba 4

* Vomit also contain DNA component now

* fixed DNA field not clearing after scanning another item

* commit numba 10
Drinking leaves DNA on an object. Breaking glasses, bottles and beakers leave DNA and leave fingerprints/fibers with 40% chance on glass shards. + lotta fixes

* 11

* 12

* 14

* Added DNA guide entry

* FIX
2023-03-30 22:49:25 -06:00
PJBot
dfcb7b3c97 Automatic changelog update 2023-03-31 00:47:10 -04:00
potato1234_x
6bfd1d8f11 Missing box icons (#14891)
* boxes

* holo box
2023-03-30 22:46:06 -06:00
metalgearsloth
40deda74ab Fix docking config in some instances (#15005) 2023-03-31 15:45:14 +11:00
metalgearsloth
ce34252cd3 Revert "fuckyou (#14960)" (#15006)
* Revert "fuckyou (#14960)"

This reverts commit e29c54d64e.

* Use volume for it
2023-03-30 22:44:39 -06:00
PJBot
a9aa5011c1 Automatic changelog update 2023-03-31 00:34:25 -04:00
PJBot
25e7acaa9b Automatic changelog update 2023-03-31 00:33:23 -04:00
potato1234_x
9ab3523871 butcherable bandanas and caps (#14893) 2023-03-30 22:33:20 -06:00
brainfood1183
ad31749b55 Desk Bell fix, Poster Rise fix, Happyhonk Mime inhand fix. (#14973)
* deskbell, poster rise fix, happyhonk mime inhand fix.

* cancollide: false
2023-03-30 22:32:57 -06:00
Alekshhh
e29c54d64e fuckyou (#14960) 2023-03-30 22:32:19 -06:00
potato1234_x
dbf9f71ee8 vend tweaks and clothes (#14961) 2023-03-30 22:31:51 -06:00
PJBot
be2d3c4277 Automatic changelog update 2023-03-31 00:31:10 -04:00
Puro
a487ed9df7 [Spawn] Plush Dion now spawns (#14986)
* [Spawn] Diona Plush now spawns

shkibididopdop

* compiled code fun.yml

removed "amount: 1", since it initially appears in the amount of 1 piece.
2023-03-30 22:30:06 -06:00
PJBot
a5b06cb96f Automatic changelog update 2023-03-31 00:26:50 -04:00
T-Stalker
3b9fd4867e Re-adds skub noise [Not april fools] (#14978) 2023-03-31 15:25:46 +11:00
PJBot
00035721e4 Automatic changelog update 2023-03-31 00:22:58 -04:00
Lei Yunxing
cf61150ebd Adds advanced mop and research (#14917)
* add advanced mop

* make advmop clean faster

* works now

* tweak stats

* tweak speed again

* typo!!!

* copyright change for nerds
2023-03-30 22:21:54 -06:00
PJBot
3b46e649ee Automatic changelog update 2023-03-31 00:21:46 -04:00
metalgearsloth
cefc37903e Random emergency shuttle time (#10047)
* Random emergency shuttle time

60 to 180 seconds. Rounds up to nearest 10.
All other FTL will go to the default of 30s.

* fix
2023-03-30 22:20:43 -06:00
Moony
f8a8e7bafc Set lizard's popcap for the day. 2023-03-30 23:00:00 -05:00
Leon Friedrich
59eb53d4f7 Fix resource &prototype upload recording (#15003) 2023-03-30 22:55:06 -05:00
PJBot
18581a01ca Automatic changelog update 2023-03-30 23:41:42 -04:00
metalgearsloth
763089570d Make trays clientside (#14826) 2023-03-31 14:40:38 +11:00
Interrobang01
50fe4337da Added Rights [April Fools] (#15002)
* everyone gets a mk58

* added mk58 ammo to sec dispenser
2023-03-30 22:34:03 -05:00
Nemanja
3c6e67adee addcurrency command (#15000) 2023-03-30 23:02:39 -04:00
Scribbles0
ce1d616f87 pop change (#14998) 2023-03-30 20:51:47 -05:00
Flareguy
b2299a2e5b They have absolutely fucking had it (#14980) 2023-03-30 19:16:16 -05:00
Skye
c9be773c85 Oops, All Captains! (#14995) 2023-03-30 19:02:30 -05:00
Tyzemol
a6aaa6464b Cowtools worldwide (#14981)
* the funny

* cow toolbox has normal tools

---------

Co-authored-by: BuildTools <unconfigured@null.spigotmc.org>
2023-03-30 18:56:22 -05:00
Ablankmann
d6b8e5e243 Nefarious WizOps [April Fools] (#14991)
* fix a typo in xenoarch guide (#14984)

* Wardrobe restock box hotfix (Removes CentCom gear) (#14985)

* initial code

* GOODBYE CENTCOM GEARgit add -A!

* Automatic changelog update

* Magic Ends

* final tweaks

---------

Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: Whisper <121047731+QuietlyWhisper@users.noreply.github.com>
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
2023-03-30 18:55:59 -05:00
PJBot
7e53ef32e2 Automatic changelog update 2023-03-30 19:55:43 -04:00
Moony
5cc78c2c75 Revert "Oops, All Captains! (#14943)" (#14994)
This reverts commit 8128759ea8.
2023-03-30 18:55:09 -05:00
Skye
8128759ea8 Oops, All Captains! (#14943) 2023-03-30 18:54:38 -05:00
deltanedas
3f3c163591 honk! (#14958)
Co-authored-by: deltanedas <@deltanedas:kde.org>
2023-03-30 18:54:31 -05:00
Nemanja
6878b89a1c Truly Good and Honest Pure of Heart Christian Boy trait (#14972) 2023-03-30 18:54:22 -05:00
Scribbles0
84f24d57ef inflatable walls for EVERYONE (#14974) 2023-03-30 18:54:14 -05:00
Skye
ef9934c61d April Fool's Day: Microwave Haywire Mode (#14975)
* Microwave Haywire Mode

* Fixed microwave board not being ejected
2023-03-30 18:53:56 -05:00
Flipp Syder
7ea9588172 Foxes (#14976)
* foxes

* oops

* oop

* oop

* license/copyright on fox markings
2023-03-30 18:53:50 -05:00
Nemanja
f2b6dcf329 Reenable skeletons [April Fools] (#14977) 2023-03-30 18:53:36 -05:00
Leon Friedrich
ceba6f1585 Improve random tip UI (#14979) 2023-03-30 18:53:13 -05:00
Emisse
b71c8b7ec3 mild bagel updatey (#14993) 2023-03-30 16:49:03 -06:00
PJBot
eeb8d0f630 Automatic changelog update 2023-03-30 12:16:14 -04:00
Whisper
979bdfcfa6 Wardrobe restock box hotfix (Removes CentCom gear) (#14985)
* initial code

* GOODBYE CENTCOM GEARgit add -A!
2023-03-30 11:15:10 -05:00
deltanedas
311a027797 fix a typo in xenoarch guide (#14984) 2023-03-30 22:59:32 +11:00
391 changed files with 18374 additions and 13807 deletions

View File

@@ -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();

View File

@@ -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();
}
}

View File

@@ -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
}
}
}

View File

@@ -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
}

View File

@@ -0,0 +1,7 @@
using Content.Shared.Medical.Surgery;
namespace Content.Client.Medical.Surgery;
public sealed class SurgeryRealmSystem : SharedSurgeryRealmSystem
{
}

View File

@@ -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;

View File

@@ -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")))
}
};

View File

@@ -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?

View 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
{
}

View 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);
}
}

View 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>

View 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,
}
}

View 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);
}
}

View 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);
}
}

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}

View File

@@ -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?

View File

@@ -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();
}

View 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;
}
}

View File

@@ -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);

View File

@@ -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)

View File

@@ -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)

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View 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;
}

View File

@@ -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>

View File

@@ -8,5 +8,8 @@ namespace Content.Server.Forensics
[DataField("fibers")]
public HashSet<string> Fibers = new();
[DataField("dnas")]
public HashSet<string> DNAs = new();
}
}

View File

@@ -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);

View File

@@ -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);

View 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);
}
}

View File

@@ -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.

View File

@@ -16,4 +16,10 @@ public sealed class ActiveMicrowaveComponent : Component
[ViewVariables]
public (FoodRecipePrototype?, int) PortionedRecipe;
[ViewVariables]
public bool Haywire;
[ViewVariables]
public float HaywireTimeRemaining;
}

View File

@@ -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)

View File

@@ -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>

View 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;
}
}

View File

@@ -0,0 +1,7 @@
namespace Content.Server.Medical.Surgery;
[RegisterComponent]
public sealed class SurgeryRealmAntiProjectileComponent : Component
{
}

View File

@@ -0,0 +1,9 @@
namespace Content.Server.Medical.Surgery;
[RegisterComponent]
public sealed class SurgeryRealmCameraComponent : Component
{
[ViewVariables] public EntityUid? OldEntity;
[ViewVariables] public Mind.Mind? Mind;
}

View File

@@ -0,0 +1,7 @@
namespace Content.Server.Medical.Surgery;
[RegisterComponent]
public sealed class SurgeryRealmEdgeComponent : Component
{
}

View File

@@ -0,0 +1,7 @@
namespace Content.Server.Medical.Surgery;
[RegisterComponent]
public sealed class SurgeryRealmLightningComponent : Component
{
}

View File

@@ -0,0 +1,8 @@
namespace Content.Server.Medical.Surgery;
public enum SurgeryRealmMusic
{
Midi,
Megalovania,
Undermale
}

View File

@@ -0,0 +1,7 @@
namespace Content.Server.Medical.Surgery;
[RegisterComponent]
public sealed class SurgeryRealmOrangeProjectileComponent : Component
{
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.Medical.Surgery;
[RegisterComponent]
public sealed class SurgeryRealmProjectileComponent : Component
{
}

View 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));
}
}

View 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;
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.Medical.Surgery;
[RegisterComponent]
public sealed class SurgeryRealmToolDuelComponent : Component
{
}

View File

@@ -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;
}

View File

@@ -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));

View File

@@ -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)

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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)

View File

@@ -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()

View File

@@ -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);

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,9 @@
namespace Content.Server.Speech.Components;
// #####. ##### ### ###. #####? ### #####!
[RegisterComponent]
public sealed class ChatFilterAccentComponent : Component
{
}

View File

@@ -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
{
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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
}

View File

@@ -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);

View 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;
}
}

View File

@@ -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)

View File

@@ -0,0 +1,8 @@
using Content.Shared.SubFloor;
namespace Content.Server.SubFloor;
public sealed class TrayScannerSystem : SharedTrayScannerSystem
{
}

View File

@@ -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()

View 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";
}

View 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);
}
}

View 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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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>

View File

@@ -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);
}

View 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;
// }
// }
// }
}
}

View 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;
}
}

View File

@@ -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;
}
}

View 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
{
}

View File

@@ -62,4 +62,10 @@ public sealed class GeneralStationRecord
/// </summary>
[ViewVariables]
public string? Fingerprint;
/// <summary>
/// DNA of the person.
/// </summary>
[ViewVariables]
public string? DNA;
}

View File

@@ -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

View 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
}

View File

@@ -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();
}
}

View File

@@ -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]

View File

@@ -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
}

View 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;
}

View 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;
}
}
}

View 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;
}
}

View 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);
}

View File

@@ -1 +1,2 @@
"weh" and "muffled_weh" are licensed under CC0.
"weh" and "muffled_weh" are licensed under CC0.
"skub" is licensed under CC0.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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