Adds Skia (σκιά, shade) as a Midround Antagonist (#4152)
* AAAAAAAAadds skia * Added to the spawn options table * Fix EOR, Added shatter ability, Fix Reroll popup * Fixed the death of the Skia * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed taco's silly mistake * Skia need pry-ability * Adds Goob Nightvision * Added some missing Protos * Appease the Yaml gods * Did the Delta Fixes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Lathes fix * Update attributions.yml * More yaml fixes * The Yaml Linter shall never be sated * More fixes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed objective reroll * Bring up to date * aaaaaaaaaaa * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed damageset, removed unneeded point light * I'm so sorry Deltandas * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Signed-off-by: William Lemon <William.Lemon2@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
|
|
@ -94,7 +94,9 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
|
|||
|
||||
protected virtual void OnRefreshEquipmentHud(Entity<T> ent, ref InventoryRelayedEvent<RefreshEquipmentHudEvent<T>> args)
|
||||
{
|
||||
OnRefreshComponentHud(ent, ref args.Args);
|
||||
// Goobstation edit
|
||||
args.Args.Active = true;
|
||||
args.Args.Components.Add(ent.Comp);
|
||||
}
|
||||
|
||||
protected virtual void OnRefreshComponentHud(Entity<T> ent, ref RefreshEquipmentHudEvent<T> args)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
using Content.Client._DV.Light;
|
||||
using Content.Shared._DV.Body;
|
||||
using Content.Shared.Alert;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._DV.Body;
|
||||
|
||||
public sealed class LightLevelHealthSystem : SharedLightLevelHealthSystem
|
||||
{
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly LightReactiveSystem _lightReactive = default!;
|
||||
|
||||
private ProtoId<AlertPrototype> _lightLevelDarkIcon = "LightLevelDarkIcon";
|
||||
private ProtoId<AlertPrototype> _lightLevelNeutralIcon = "LightLevelNeutralIcon";
|
||||
private ProtoId<AlertPrototype> _lightLevelBrightIcon = "LightLevelBrightIcon";
|
||||
private ProtoId<AlertCategoryPrototype> _lightAlertCategory = "Light";
|
||||
|
||||
private int _lastThreshold = 0;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
// If we're a LightLevelHealthComponent client, update our alerts.
|
||||
if (_player.LocalSession?.AttachedEntity is not { } ent)
|
||||
return;
|
||||
|
||||
if (!TryComp<LightLevelHealthComponent>(ent, out var lightLevelHealth))
|
||||
return;
|
||||
|
||||
var currentThreshold = CurrentThreshold(_lightReactive.GetLightLevelForPoint(ent), lightLevelHealth);
|
||||
var alertIcon = currentThreshold switch
|
||||
{
|
||||
-1 => _lightLevelDarkIcon,
|
||||
1 => _lightLevelBrightIcon,
|
||||
_ => _lightLevelNeutralIcon,
|
||||
};
|
||||
|
||||
if (currentThreshold != _lastThreshold)
|
||||
{
|
||||
var alertCategory = _prototype.Index(_lightAlertCategory);
|
||||
_alerts.ClearAlertCategory(ent, alertCategory);
|
||||
}
|
||||
_lastThreshold = currentThreshold;
|
||||
|
||||
var alertProto = _prototype.Index(alertIcon);
|
||||
_alerts.ShowAlert(ent, alertProto);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
using Content.Shared._DV.Light;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client._DV.Light;
|
||||
|
||||
public sealed partial class LightReactiveSystem : SharedLightReactiveSystem
|
||||
{
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
|
||||
private readonly HashSet<Entity<PointLightComponent>> _lightsInRange = new();
|
||||
private readonly HashSet<Entity<SharedPointLightComponent>> _validLightsInRange = new();
|
||||
public override HashSet<Entity<SharedPointLightComponent>> GetLights(EntityUid targetEntity)
|
||||
{
|
||||
_lightsInRange.Clear();
|
||||
_lookup.GetEntitiesInRange(Transform(targetEntity).Coordinates, 10f, _lightsInRange);
|
||||
_validLightsInRange.Clear();
|
||||
foreach (var light in _lightsInRange)
|
||||
{
|
||||
if(light.Comp.Enabled && !light.Comp.Deleted && light.Comp.NetSyncEnabled)
|
||||
_validLightsInRange.Add(new(light.Owner, light.Comp));
|
||||
}
|
||||
return _validLightsInRange;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Misandry <mary@thughunt.ing>
|
||||
// SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using System.Numerics;
|
||||
using Content.Shared._Goobstation.Overlays;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._Goobstation.Overlays;
|
||||
|
||||
public sealed class BaseSwitchableOverlay<TComp> : Overlay where TComp : SwitchableVisionOverlayComponent
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
public override bool RequestScreenTexture => true;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
public TComp? Comp = null;
|
||||
|
||||
public bool IsActive = true;
|
||||
|
||||
public BaseSwitchableOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_shader = _prototype.Index<ShaderPrototype>("NightVision").InstanceUnique();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (ScreenTexture is null || Comp is null || !IsActive)
|
||||
return;
|
||||
|
||||
_shader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
_shader.SetParameter("tint", Comp.Tint);
|
||||
_shader.SetParameter("luminance_threshold", Comp.Strength);
|
||||
_shader.SetParameter("noise_amount", Comp.Noise);
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
var accumulator = Math.Clamp(Comp.PulseAccumulator, 0f, Comp.PulseTime);
|
||||
var alpha = Comp.PulseTime <= 0f ? 1f : float.Lerp(1f, 0f, accumulator / Comp.PulseTime);
|
||||
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
worldHandle.UseShader(_shader);
|
||||
worldHandle.DrawRect(args.WorldBounds, Comp.Color.WithAlpha(alpha));
|
||||
worldHandle.UseShader(null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Misandry <mary@thughunt.ing>
|
||||
// SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Client.Overlays;
|
||||
using Content.Shared._Goobstation.Overlays;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client._Goobstation.Overlays;
|
||||
|
||||
public sealed class NightVisionSystem : EquipmentHudSystem<NightVisionComponent>
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
[Dependency] private readonly ILightManager _lightManager = default!;
|
||||
|
||||
private BaseSwitchableOverlay<NightVisionComponent> _overlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<NightVisionComponent, SwitchableOverlayToggledEvent>(OnToggle);
|
||||
|
||||
_overlay = new BaseSwitchableOverlay<NightVisionComponent>();
|
||||
}
|
||||
|
||||
protected override void OnRefreshComponentHud(Entity<NightVisionComponent> ent,
|
||||
ref RefreshEquipmentHudEvent<NightVisionComponent> args)
|
||||
{
|
||||
if (!ent.Comp.IsEquipment)
|
||||
base.OnRefreshComponentHud(ent, ref args);
|
||||
}
|
||||
|
||||
protected override void OnRefreshEquipmentHud(Entity<NightVisionComponent> ent,
|
||||
ref InventoryRelayedEvent<RefreshEquipmentHudEvent<NightVisionComponent>> args)
|
||||
{
|
||||
if (ent.Comp.IsEquipment)
|
||||
base.OnRefreshEquipmentHud(ent, ref args);
|
||||
}
|
||||
|
||||
private void OnToggle(Entity<NightVisionComponent> ent, ref SwitchableOverlayToggledEvent args)
|
||||
{
|
||||
RefreshOverlay();
|
||||
}
|
||||
|
||||
protected override void UpdateInternal(RefreshEquipmentHudEvent<NightVisionComponent> args)
|
||||
{
|
||||
base.UpdateInternal(args);
|
||||
|
||||
var active = false;
|
||||
NightVisionComponent? nvComp = null;
|
||||
foreach (var comp in args.Components)
|
||||
{
|
||||
if (comp.IsActive || comp.PulseTime > 0f && comp.PulseAccumulator < comp.PulseTime)
|
||||
active = true;
|
||||
else
|
||||
continue;
|
||||
|
||||
if (comp.DrawOverlay)
|
||||
{
|
||||
if (nvComp == null)
|
||||
nvComp = comp;
|
||||
else if (nvComp.PulseTime > 0f && comp.PulseTime <= 0f)
|
||||
nvComp = comp;
|
||||
}
|
||||
|
||||
if (active && nvComp is { PulseTime: <= 0 })
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateNightVision(active);
|
||||
UpdateOverlay(nvComp);
|
||||
}
|
||||
|
||||
protected override void DeactivateInternal()
|
||||
{
|
||||
base.DeactivateInternal();
|
||||
|
||||
UpdateNightVision(false);
|
||||
UpdateOverlay(null);
|
||||
}
|
||||
|
||||
private void UpdateNightVision(bool active)
|
||||
{
|
||||
_lightManager.DrawLighting = !active;
|
||||
}
|
||||
|
||||
private void UpdateOverlay(NightVisionComponent? nvComp)
|
||||
{
|
||||
_overlay.Comp = nvComp;
|
||||
|
||||
switch (nvComp)
|
||||
{
|
||||
case not null when !_overlayMan.HasOverlay<BaseSwitchableOverlay<NightVisionComponent>>():
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
break;
|
||||
case null:
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
break;
|
||||
}
|
||||
|
||||
if (_overlayMan.TryGetOverlay<BaseSwitchableOverlay<ThermalVisionComponent>>(out var overlay))
|
||||
overlay.IsActive = nvComp == null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Misandry <mary@thughunt.ing>
|
||||
// SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Stealth;
|
||||
using Content.Shared._Goobstation.Overlays;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Stealth.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client._Goobstation.Overlays;
|
||||
|
||||
public sealed class ThermalVisionOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private readonly TransformSystem _transform;
|
||||
private readonly StealthSystem _stealth;
|
||||
private readonly ContainerSystem _container;
|
||||
private readonly SharedPointLightSystem _light;
|
||||
|
||||
public override bool RequestScreenTexture => true;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
private readonly List<ThermalVisionRenderEntry> _entries = [];
|
||||
|
||||
private EntityUid? _lightEntity;
|
||||
|
||||
public float LightRadius;
|
||||
|
||||
public ThermalVisionComponent? Comp;
|
||||
|
||||
public ThermalVisionOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_container = _entity.System<ContainerSystem>();
|
||||
_transform = _entity.System<TransformSystem>();
|
||||
_stealth = _entity.System<StealthSystem>();
|
||||
_light = _entity.System<SharedPointLightSystem>();
|
||||
|
||||
ZIndex = -1;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (ScreenTexture is null || Comp is null)
|
||||
return;
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
var eye = args.Viewport.Eye;
|
||||
|
||||
if (eye == null)
|
||||
return;
|
||||
|
||||
var player = _player.LocalEntity;
|
||||
|
||||
if (!_entity.TryGetComponent(player, out TransformComponent? playerXform))
|
||||
return;
|
||||
|
||||
var accumulator = Math.Clamp(Comp.PulseAccumulator, 0f, Comp.PulseTime);
|
||||
var alpha = Comp.PulseTime <= 0f ? 1f : float.Lerp(1f, 0f, accumulator / Comp.PulseTime);
|
||||
|
||||
// Thermal vision grants some night vision (clientside light)
|
||||
if (LightRadius > 0)
|
||||
{
|
||||
_lightEntity ??= _entity.SpawnAttachedTo(null, playerXform.Coordinates);
|
||||
_transform.SetParent(_lightEntity.Value, player.Value);
|
||||
var light = _entity.EnsureComponent<PointLightComponent>(_lightEntity.Value);
|
||||
_light.SetRadius(_lightEntity.Value, LightRadius, light);
|
||||
_light.SetEnergy(_lightEntity.Value, alpha, light);
|
||||
_light.SetColor(_lightEntity.Value, Comp.Color, light);
|
||||
}
|
||||
else
|
||||
ResetLight();
|
||||
|
||||
var mapId = eye.Position.MapId;
|
||||
var eyeRot = eye.Rotation;
|
||||
|
||||
_entries.Clear();
|
||||
var entities = _entity.EntityQueryEnumerator<BodyComponent, SpriteComponent, TransformComponent>();
|
||||
while (entities.MoveNext(out var uid, out var body, out var sprite, out var xform))
|
||||
{
|
||||
if (!CanSee(uid, sprite))
|
||||
continue;
|
||||
|
||||
var entity = uid;
|
||||
|
||||
if (_container.TryGetOuterContainer(uid, xform, out var container))
|
||||
{
|
||||
var owner = container.Owner;
|
||||
if (_entity.TryGetComponent<SpriteComponent>(owner, out var ownerSprite)
|
||||
&& _entity.TryGetComponent<TransformComponent>(owner, out var ownerXform))
|
||||
{
|
||||
entity = owner;
|
||||
sprite = ownerSprite;
|
||||
xform = ownerXform;
|
||||
}
|
||||
}
|
||||
|
||||
if (_entries.Any(e => e.Ent.Owner == entity))
|
||||
continue;
|
||||
|
||||
_entries.Add(new ThermalVisionRenderEntry((entity, sprite, xform), mapId, eyeRot));
|
||||
}
|
||||
|
||||
foreach (var entry in _entries)
|
||||
{
|
||||
Render(entry.Ent, entry.Map, worldHandle, entry.EyeRot, Comp.Color, alpha);
|
||||
}
|
||||
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
private void Render(Entity<SpriteComponent, TransformComponent> ent,
|
||||
MapId? map,
|
||||
DrawingHandleWorld handle,
|
||||
Angle eyeRot,
|
||||
Color color,
|
||||
float alpha)
|
||||
{
|
||||
var (uid, sprite, xform) = ent;
|
||||
if (xform.MapID != map || !CanSee(uid, sprite))
|
||||
return;
|
||||
|
||||
var position = _transform.GetWorldPosition(xform);
|
||||
var rotation = _transform.GetWorldRotation(xform);
|
||||
|
||||
|
||||
var originalColor = sprite.Color;
|
||||
sprite.Color = color.WithAlpha(alpha);
|
||||
sprite.Render(handle, eyeRot, rotation, position: position);
|
||||
sprite.Color = originalColor;
|
||||
}
|
||||
|
||||
private bool CanSee(EntityUid uid, SpriteComponent sprite)
|
||||
{
|
||||
return sprite.Visible && (!_entity.TryGetComponent(uid, out StealthComponent? stealth) ||
|
||||
_stealth.GetVisibility(uid, stealth) > 0.5f);
|
||||
}
|
||||
|
||||
public void ResetLight(bool checkFirstTimePredicted = true)
|
||||
{
|
||||
if (_lightEntity == null || checkFirstTimePredicted && !_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
_entity.DeleteEntity(_lightEntity);
|
||||
_lightEntity = null;
|
||||
}
|
||||
}
|
||||
|
||||
public record struct ThermalVisionRenderEntry(
|
||||
Entity<SpriteComponent, TransformComponent> Ent,
|
||||
MapId? Map,
|
||||
Angle EyeRot);
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Misandry <mary@thughunt.ing>
|
||||
// SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Client.Overlays;
|
||||
using Content.Shared._Goobstation.Overlays;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client._Goobstation.Overlays;
|
||||
|
||||
public sealed class ThermalVisionSystem : EquipmentHudSystem<ThermalVisionComponent>
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
|
||||
private ThermalVisionOverlay _thermalOverlay = default!;
|
||||
private BaseSwitchableOverlay<ThermalVisionComponent> _overlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ThermalVisionComponent, SwitchableOverlayToggledEvent>(OnToggle);
|
||||
|
||||
_thermalOverlay = new ThermalVisionOverlay();
|
||||
_overlay = new BaseSwitchableOverlay<ThermalVisionComponent>();
|
||||
}
|
||||
|
||||
protected override void OnRefreshComponentHud(Entity<ThermalVisionComponent> ent,
|
||||
ref RefreshEquipmentHudEvent<ThermalVisionComponent> args)
|
||||
{
|
||||
if (!ent.Comp.IsEquipment)
|
||||
base.OnRefreshComponentHud(ent, ref args);
|
||||
}
|
||||
|
||||
protected override void OnRefreshEquipmentHud(Entity<ThermalVisionComponent> ent,
|
||||
ref InventoryRelayedEvent<RefreshEquipmentHudEvent<ThermalVisionComponent>> args)
|
||||
{
|
||||
if (ent.Comp.IsEquipment)
|
||||
base.OnRefreshEquipmentHud(ent, ref args);
|
||||
}
|
||||
|
||||
private void OnToggle(Entity<ThermalVisionComponent> ent, ref SwitchableOverlayToggledEvent args)
|
||||
{
|
||||
RefreshOverlay();
|
||||
}
|
||||
|
||||
protected override void UpdateInternal(RefreshEquipmentHudEvent<ThermalVisionComponent> args)
|
||||
{
|
||||
base.UpdateInternal(args);
|
||||
ThermalVisionComponent? tvComp = null;
|
||||
var lightRadius = 0f;
|
||||
foreach (var comp in args.Components)
|
||||
{
|
||||
if (!comp.IsActive && (comp.PulseTime <= 0f || comp.PulseAccumulator >= comp.PulseTime))
|
||||
continue;
|
||||
|
||||
if (tvComp == null)
|
||||
tvComp = comp;
|
||||
else if (!tvComp.DrawOverlay && comp.DrawOverlay)
|
||||
tvComp = comp;
|
||||
else if (tvComp.DrawOverlay == comp.DrawOverlay && tvComp.PulseTime > 0f && comp.PulseTime <= 0f)
|
||||
tvComp = comp;
|
||||
|
||||
lightRadius = MathF.Max(lightRadius, comp.LightRadius);
|
||||
}
|
||||
|
||||
UpdateThermalOverlay(tvComp, lightRadius);
|
||||
UpdateOverlay(tvComp);
|
||||
}
|
||||
|
||||
protected override void DeactivateInternal()
|
||||
{
|
||||
base.DeactivateInternal();
|
||||
|
||||
_thermalOverlay.ResetLight(false);
|
||||
UpdateOverlay(null);
|
||||
UpdateThermalOverlay(null, 0f);
|
||||
}
|
||||
|
||||
private void UpdateThermalOverlay(ThermalVisionComponent? comp, float lightRadius)
|
||||
{
|
||||
_thermalOverlay.LightRadius = lightRadius;
|
||||
_thermalOverlay.Comp = comp;
|
||||
|
||||
switch (comp)
|
||||
{
|
||||
case not null when !_overlayMan.HasOverlay<ThermalVisionOverlay>():
|
||||
_overlayMan.AddOverlay(_thermalOverlay);
|
||||
break;
|
||||
case null:
|
||||
_overlayMan.RemoveOverlay(_thermalOverlay);
|
||||
_thermalOverlay.ResetLight();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateOverlay(ThermalVisionComponent? tvComp)
|
||||
{
|
||||
_overlay.Comp = tvComp;
|
||||
|
||||
switch (tvComp)
|
||||
{
|
||||
case { DrawOverlay: true } when !_overlayMan.HasOverlay<BaseSwitchableOverlay<ThermalVisionComponent>>():
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
break;
|
||||
case null or { DrawOverlay: false }:
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
break;
|
||||
}
|
||||
|
||||
// Night vision overlay is prioritized
|
||||
_overlay.IsActive = !_overlayMan.HasOverlay<BaseSwitchableOverlay<NightVisionComponent>>();
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ using Robust.Shared.Audio;
|
|||
using Robust.Shared.Random;
|
||||
using InventoryComponent = Content.Shared.Inventory.InventoryComponent;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared._Goobstation.Flashbang;
|
||||
|
||||
namespace Content.Server.Flash
|
||||
{
|
||||
|
|
@ -132,8 +133,14 @@ namespace Content.Server.Flash
|
|||
if (attempt.Cancelled)
|
||||
return;
|
||||
|
||||
// Goobstation start
|
||||
var multiplierEv = new FlashDurationMultiplierEvent();
|
||||
RaiseLocalEvent(target, multiplierEv);
|
||||
var multiplier = multiplierEv.Multiplier;
|
||||
// Goobstation end
|
||||
|
||||
// don't paralyze, slowdown or convert to rev if the target is immune to flashes
|
||||
if (!_statusEffectsSystem.TryAddStatusEffect<FlashedComponent>(target, FlashedKey, TimeSpan.FromSeconds(flashDuration / 1000f), true) && !ignoreProtection) //DeltaV: allow flashing to ignore flash protection
|
||||
if (!_statusEffectsSystem.TryAddStatusEffect<FlashedComponent>(target, FlashedKey, TimeSpan.FromSeconds(flashDuration * multiplier / 1000f), true) && !ignoreProtection) //DeltaV: allow flashing to ignore flash protection
|
||||
return;
|
||||
|
||||
if (stunDuration != null)
|
||||
|
|
@ -142,7 +149,7 @@ namespace Content.Server.Flash
|
|||
}
|
||||
else
|
||||
{
|
||||
_stun.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration / 1000f), true,
|
||||
_stun.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration * multiplier / 1000f), true,
|
||||
slowTo, slowTo);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
using Content.Shared._DV.Abilities;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Server.Audio;
|
||||
|
||||
namespace Content.Server._DV.Abilities;
|
||||
|
||||
public sealed partial class PsychokineticScreamPowerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly ShatterLightsAbilitySystem _shatterLights = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PsychokineticScreamPowerComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<PsychokineticScreamPowerComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<PsychokineticScreamPowerComponent, ShatterLightsActionEvent>(OnShatterLightsAction);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<PsychokineticScreamPowerComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
_actions.AddAction(ent, ref ent.Comp.PsychokineticScreamActionEntity, ent.Comp.ShatterLightsActionId);
|
||||
_actions.StartUseDelay(ent.Comp.PsychokineticScreamActionEntity);
|
||||
|
||||
if (TryComp<PsionicComponent>(ent, out var psionic) && psionic.PsionicAbility == null)
|
||||
{
|
||||
psionic.PsionicAbility = ent.Comp.PsychokineticScreamActionEntity;
|
||||
psionic.ActivePowers.Add(ent.Comp);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShutdown(Entity<PsychokineticScreamPowerComponent> entity, ref ComponentShutdown args)
|
||||
{
|
||||
_actions.RemoveAction(entity.Owner, entity.Comp.PsychokineticScreamActionEntity);
|
||||
if (TryComp<PsionicComponent>(entity, out var psionic))
|
||||
{
|
||||
psionic.ActivePowers.Remove(entity.Comp);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShatterLightsAction(Entity<PsychokineticScreamPowerComponent> entity, ref ShatterLightsActionEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (entity.Comp.AbilitySound != null)
|
||||
_audio.PlayPvs(entity.Comp.AbilitySound, entity);
|
||||
|
||||
_shatterLights.ShatterLightsAround(entity.Owner, entity.Comp.Radius, entity.Comp.LineOfSight);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
using Content.Server.Light.Components;
|
||||
using Content.Server.Light.EntitySystems;
|
||||
using Content.Shared._DV.Abilities;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Server._DV.Abilities;
|
||||
|
||||
public sealed partial class ShatterLightsAbilitySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly PoweredLightSystem _light = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private readonly HashSet<Entity<PoweredLightComponent>> _lightsInRange = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ShatterLightsAbilityComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<ShatterLightsAbilityComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<ShatterLightsAbilityComponent, ShatterLightsActionEvent>(OnShatterLightsAction);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<ShatterLightsAbilityComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
_actions.AddAction(ent, ref ent.Comp.ShatterLightsActionEntity, ent.Comp.ShatterLightsActionId);
|
||||
_actions.StartUseDelay(ent.Comp.ShatterLightsActionEntity);
|
||||
}
|
||||
|
||||
private void OnShutdown(Entity<ShatterLightsAbilityComponent> entity, ref ComponentShutdown args)
|
||||
{
|
||||
_actions.RemoveAction(entity.Owner, entity.Comp.ShatterLightsActionEntity);
|
||||
}
|
||||
|
||||
private void OnShatterLightsAction(Entity<ShatterLightsAbilityComponent> entity, ref ShatterLightsActionEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (entity.Comp.AbilitySound != null)
|
||||
_audio.PlayPvs(entity.Comp.AbilitySound, entity);
|
||||
|
||||
ShatterLightsAround(entity.Owner, entity.Comp.Radius, entity.Comp.LineOfSight);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public void ShatterLightsAround(EntityUid center, float range, bool lineOfSight)
|
||||
{
|
||||
var pos = _transform.GetWorldPosition(center);
|
||||
|
||||
// Get all light entities within the specified radius
|
||||
_lightsInRange.Clear();
|
||||
_lookup.GetEntitiesInRange(Transform(center).Coordinates, range, _lightsInRange);
|
||||
foreach (var light in _lightsInRange)
|
||||
{
|
||||
if (lineOfSight) // If LoS is required, test it.
|
||||
{
|
||||
var lightPos = _transform.GetWorldPosition(light);
|
||||
var sqrDistance = Vector2.DistanceSquared(pos, lightPos);
|
||||
var ray = new CollisionRay(pos, (lightPos - pos).Normalized(), (int)CollisionGroup.Opaque);
|
||||
var hit = _physics.IntersectRay(_transform.GetMapId(center), ray, MathF.Sqrt(sqrDistance) - 0.5f, returnOnFirstHit: true);
|
||||
if (hit.Any() && hit.First().Distance != 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we reach here, the light is unobstructed and within range, break it.
|
||||
_light.TryDestroyBulb(light, light.Comp);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
using Content.Server.Emp;
|
||||
using Content.Shared._DV.Abilities;
|
||||
using Content.Shared.Actions;
|
||||
|
||||
namespace Content.Server._DV.Abilities;
|
||||
|
||||
public sealed partial class TechnokineticPulseAbilitySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly EmpSystem _emp = default!;
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TechnokineticPulseAbilityComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<TechnokineticPulseAbilityComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<TechnokineticPulseAbilityComponent, TechnokineticPulseActionEvent>(OnTechnokineticPulseAction);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<TechnokineticPulseAbilityComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
_actions.AddAction(ent, ref ent.Comp.TechnokineticPulseActionEntity, ent.Comp.TechnokineticPulseActionId);
|
||||
_actions.StartUseDelay(ent.Comp.TechnokineticPulseActionEntity);
|
||||
}
|
||||
|
||||
private void OnShutdown(Entity<TechnokineticPulseAbilityComponent> entity, ref ComponentShutdown args)
|
||||
{
|
||||
_actions.RemoveAction(entity.Owner, entity.Comp.TechnokineticPulseActionEntity);
|
||||
}
|
||||
|
||||
private void OnTechnokineticPulseAction(Entity<TechnokineticPulseAbilityComponent> entity, ref TechnokineticPulseActionEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
_emp.EmpPulse(_transform.GetMapCoordinates(entity), entity.Comp.Range, entity.Comp.EnergyConsumption, entity.Comp.DisableDuration);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
using Content.Shared._DV.Body;
|
||||
using Content.Shared._DV.Light;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._DV.Body;
|
||||
|
||||
public sealed class LightLevelHealthSystem : SharedLightLevelHealthSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
|
||||
[Dependency] private readonly SharedLightReactiveSystem _lightReactive = default!;
|
||||
|
||||
private TimeSpan _nextUpdate = TimeSpan.MinValue;
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
if (_timing.CurTime < _nextUpdate)
|
||||
return;
|
||||
_nextUpdate = _timing.CurTime + TimeSpan.FromSeconds(1);
|
||||
|
||||
var query = EntityQueryEnumerator<LightLevelHealthComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (_mobState.IsDead(uid))
|
||||
continue; // Don't apply damage if the mob is dead
|
||||
// Get the light level at the entity's position
|
||||
var lightLevel = _lightReactive.GetLightLevel(uid, true);
|
||||
|
||||
int currentThreshold = CurrentThreshold(lightLevel, comp);
|
||||
if (currentThreshold != comp.CurrentThreshold)
|
||||
{
|
||||
comp.CurrentThreshold = currentThreshold;
|
||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
|
||||
}
|
||||
|
||||
if (currentThreshold == -1)
|
||||
TryDealDamage(new(uid, comp), comp.DarkDamage);
|
||||
if (currentThreshold == 1)
|
||||
TryDealDamage(new(uid, comp), comp.LightDamage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
namespace Content.Server._DV.GameTicking.Rules.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class SkiaRuleComponent : Component;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
using Content.Server._DV.Abilities;
|
||||
using Content.Shared._DV.Light;
|
||||
|
||||
namespace Content.Server._DV.Light;
|
||||
|
||||
public sealed partial class BreakLightsOnSpawnSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ShatterLightsAbilitySystem _shatterLights = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<BreakLightsOnSpawnComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<BreakLightsOnSpawnComponent> entity, ref MapInitEvent args)
|
||||
{
|
||||
_shatterLights.ShatterLightsAround(entity.Owner, entity.Comp.Radius, entity.Comp.LineOfSight);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
using Content.Shared._DV.Light;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server._DV.Light;
|
||||
|
||||
public sealed partial class LightReactiveSystem : SharedLightReactiveSystem
|
||||
{
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
|
||||
private readonly HashSet<Entity<PointLightComponent>> _lightsInRange = new();
|
||||
private readonly HashSet<Entity<SharedPointLightComponent>> _validLightsInRange = new();
|
||||
public override HashSet<Entity<SharedPointLightComponent>> GetLights(EntityUid targetEntity)
|
||||
{
|
||||
_lightsInRange.Clear();
|
||||
_lookup.GetEntitiesInRange(Transform(targetEntity).Coordinates, 10f, _lightsInRange);
|
||||
_validLightsInRange.Clear();
|
||||
foreach (var light in _lightsInRange)
|
||||
{
|
||||
if(light.Comp.Enabled && !light.Comp.Deleted && light.Comp.NetSyncEnabled)
|
||||
_validLightsInRange.Add(new(light.Owner, light.Comp));
|
||||
}
|
||||
return _validLightsInRange;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._DV.Objectives.Components;
|
||||
|
||||
/// <summary>
|
||||
/// When this objective is completed, duplicate it with a new target.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class RerollAfterCompletionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// If true, the objective has already been rerolled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Ideally this shouldn't matter, as we delete the component once its rolled
|
||||
/// </remarks>
|
||||
public bool Rerolled;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks a reference of the owner of this objective.
|
||||
/// From what I can see, there is no normaly way to get a mind from an objective, as they're usually passed together.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid MindUid = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Prototype of the objective to use for rerolling.
|
||||
/// Probably the same as this entity (If you want a potentially infinite number of objectives), but could be different if you want it to be a different objective.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public EntProtoId RerollObjectivePrototype = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Message to display when the objective is rerolled.
|
||||
/// If null, no message will be displayed.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId? RerollObjectiveMessage;
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
using Content.Server._DV.Objectives.Components;
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Server.Roles.Jobs;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.Objectives.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._DV.Objectives.Systems;
|
||||
|
||||
public sealed class RerollAfterCompletionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly JobSystem _job = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
|
||||
private readonly HashSet<RerollAfterCompletionComponent> _objectivesToAdd = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RerollAfterCompletionComponent, ObjectiveAfterAssignEvent>(OnObjectiveAfterAssign);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
_objectivesToAdd.Clear();
|
||||
var query = EntityQueryEnumerator<RerollAfterCompletionComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var component))
|
||||
{
|
||||
if (component.Rerolled) // If already rerolled, skip.
|
||||
continue;
|
||||
|
||||
if (!HasComp<ObjectiveComponent>(uid))
|
||||
continue; // If the entity doesn't have an ObjectiveComponent, skip.
|
||||
|
||||
if (!TryComp<MindComponent>(component.MindUid, out var mind))
|
||||
continue; // If the mind component is missing, skip.
|
||||
|
||||
// Check that this objective has been completed.
|
||||
if (!_objectives.IsCompleted(uid, new(component.MindUid, mind)))
|
||||
continue;
|
||||
|
||||
// Destroy this commponent as it is no longer needed, and this will speed up the next check.
|
||||
RemCompDeferred<RerollAfterCompletionComponent>(uid);
|
||||
|
||||
component.Rerolled = true;
|
||||
|
||||
// I'd be a lot happier if I could do all the rerolling here
|
||||
// But creating the new objective causes the Query to freak out
|
||||
// And I need the objective to do everything else.
|
||||
_objectivesToAdd.Add(component);
|
||||
}
|
||||
|
||||
foreach (var component in _objectivesToAdd)
|
||||
{
|
||||
var mind = component.MindUid;
|
||||
if (!TryComp<MindComponent>(mind, out var mindComponent))
|
||||
continue;
|
||||
// Create a new objective with the specified prototype.
|
||||
if (_objectives.TryCreateObjective(mind, mindComponent, component.RerollObjectivePrototype) is not { } newObjUid)
|
||||
continue;
|
||||
if (component.RerollObjectiveMessage is null)
|
||||
continue;
|
||||
|
||||
var bodyUid = mindComponent.CurrentEntity ?? component.MindUid;
|
||||
|
||||
// Check if this has a target component, and if so, get it's name for Localization.
|
||||
if (TryComp<TargetObjectiveComponent>(newObjUid, out var targetComp) && TryComp<MindComponent>(targetComp.Target, out var targetMindComp))
|
||||
{
|
||||
var newTarget = targetMindComp.CharacterName ?? "Unknown";
|
||||
var targetJob = _job.MindTryGetJobName(targetComp.Target);
|
||||
_popup.PopupEntity(Loc.GetString(component.RerollObjectiveMessage, ("targetName", newTarget), ("job", targetJob)), bodyUid, bodyUid, PopupType.Large);
|
||||
}
|
||||
else
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString(component.RerollObjectiveMessage), bodyUid, bodyUid, PopupType.Large);
|
||||
}
|
||||
_mind.AddObjective(mind, mindComponent, newObjUid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnObjectiveAfterAssign(EntityUid uid, RerollAfterCompletionComponent comp, ref ObjectiveAfterAssignEvent args)
|
||||
{
|
||||
// If the objective is assigned, we can set the mind UID.
|
||||
if (args.Mind != null)
|
||||
comp.MindUid = args.MindId;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Shared._DV.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Added to mind role entities to tag that they are a Skia.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class SkiaRoleComponent : BaseMindRoleComponent;
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using Content.Shared.Actions;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._DV.Abilities;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class PsychokineticScreamPowerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The radius in which lights will be broken.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Radius = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// If true, lights will only be broken if the entity has line of sight to them.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool LineOfSight = false;
|
||||
|
||||
/// <summary>
|
||||
/// The action that triggers the psychokinetic scream ability.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId ShatterLightsActionId = "ActionPsychokineticScream";
|
||||
|
||||
/// <summary>
|
||||
/// Standing reference to the action entity, if it exists.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? PsychokineticScreamActionEntity;
|
||||
|
||||
/// <summary>
|
||||
/// The sound to play when the ability is used.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier AbilitySound = new SoundPathSpecifier("/Audio/_DV/Effects/creepyshriek.ogg");
|
||||
}
|
||||
|
||||
public sealed partial class ShatterLightsActionEvent : InstantActionEvent;
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using Content.Shared.Actions;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._DV.Abilities;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class ShatterLightsAbilityComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The radius in which lights will be broken.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Radius = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// If true, lights will only be broken if the entity has line of sight to them.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool LineOfSight = false;
|
||||
|
||||
/// <summary>
|
||||
/// The action that triggers the shatter lights ability.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId ShatterLightsActionId = "ActionShatterLights";
|
||||
|
||||
/// <summary>
|
||||
/// Standing reference to the action entity, if it exists.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? ShatterLightsActionEntity;
|
||||
|
||||
/// <summary>
|
||||
/// The sound to play when the ability is used.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier AbilitySound = new SoundPathSpecifier("/Audio/_DV/Effects/creepyshriek.ogg");
|
||||
}
|
||||
|
||||
public sealed partial class ShatterLightsActionEvent : InstantActionEvent;
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using Content.Shared.Actions;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._DV.Abilities;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class TechnokineticPulseAbilityComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The radius in which to EMP
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Range = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of power to drain from batteries
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float EnergyConsumption = 20000;
|
||||
|
||||
/// <summary>
|
||||
/// The duration for which devices are disabled.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float DisableDuration = 20f;
|
||||
|
||||
/// <summary>
|
||||
/// The action that triggers the technokinetic pulse ability.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId TechnokineticPulseActionId = "ActionTechnokineticPulse";
|
||||
|
||||
/// <summary>
|
||||
/// Standing reference to the action entity, if it exists.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? TechnokineticPulseActionEntity;
|
||||
}
|
||||
|
||||
public sealed partial class TechnokineticPulseActionEvent : InstantActionEvent;
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
using Content.Shared.Damage;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.Body;
|
||||
|
||||
/// <summary>
|
||||
/// Component that allows a body to deal or receive modified damage amounts based on their light level.
|
||||
/// Requires <see cref="LightLevelHealthComponent"/> to function.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class LightLevelDamageMultComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public float DarkReceivedMultiplier = 1.0f;
|
||||
[DataField]
|
||||
public float LightReceivedMultiplier = 1.0f;
|
||||
[DataField]
|
||||
public float LightDealtMultiplier = 1.0f;
|
||||
[DataField]
|
||||
public float DarkDealtMultiplier = 1.0f;
|
||||
|
||||
[DataField]
|
||||
public DamageModifierSet? DarkReceivedModifiers = default!;
|
||||
[DataField]
|
||||
public DamageModifierSet? LightReceivedModifiers = default!;
|
||||
[DataField]
|
||||
public DamageModifierSet? DarkDealtModifiers = default!;
|
||||
[DataField]
|
||||
public DamageModifierSet? LightDealtModifiers = default!;
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.Body;
|
||||
|
||||
/// <summary>
|
||||
/// Component that allows a body to have health that is affected by light levels.
|
||||
/// Either damaged or healed by certain light levels.
|
||||
/// This is used for the Skia, which is a creature that is harmed by light.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class LightLevelHealthComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Level of light that, when below, we are considered in darkness.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float DarkThreshold = 0.2f;
|
||||
/// <summary>
|
||||
/// Level of light that, when above, we are considered in light.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float LightThreshold = 0.8f;
|
||||
/// <summary>
|
||||
/// Amount of health or damage per second when in darkness. Positive values harm, negative values heal.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public DamageSpecifier DarkDamage = default!;
|
||||
/// <summary>
|
||||
/// Amount of health or damage per second when in light. Positive values harm, negative values heal.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public DamageSpecifier LightDamage = default!;
|
||||
/// <summary>
|
||||
/// Movement speed multiplier when in darkness.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float DarkMovementSpeedMultiplier = 1.0f;
|
||||
/// <summary>
|
||||
/// Movement speed multiplier when in light.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float LightMovementSpeedMultiplier = 1.0f;
|
||||
/// <summary>
|
||||
/// Sound to play when the entity is damaged by light or darkness.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier SizzleSoundPath = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// The current light threshhold for this component.
|
||||
/// -1 for darkness, 1 for light.
|
||||
/// 0 for neither.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int CurrentThreshold = 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
using Content.Shared._DV.Body;
|
||||
using Content.Shared._DV.Light;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared._DV.Body;
|
||||
|
||||
public abstract class SharedLightLevelHealthSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<LightLevelHealthComponent, RefreshMovementSpeedModifiersEvent>(OnGetMoveSpeedModifiers);
|
||||
SubscribeLocalEvent<LightLevelDamageMultComponent, DamageModifyEvent>(OnDamageModify);
|
||||
SubscribeLocalEvent<LightLevelDamageMultComponent, GetMeleeDamageEvent>(OnGetMeleeDamage);
|
||||
}
|
||||
|
||||
public int CurrentThreshold(float lightLevel, LightLevelHealthComponent comp)
|
||||
{
|
||||
bool lowLight = lightLevel < comp.DarkThreshold;
|
||||
bool highLight = lightLevel > comp.LightThreshold;
|
||||
return lowLight && !highLight ? -1
|
||||
: !lowLight && highLight ? 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
public void TryDealDamage(Entity<LightLevelHealthComponent> target, DamageSpecifier damage)
|
||||
{
|
||||
if (damage.AnyPositive())
|
||||
{
|
||||
_audio.PlayPvs(target.Comp.SizzleSoundPath, target);
|
||||
}
|
||||
_damageable.TryChangeDamage(target, damage, true, false);
|
||||
}
|
||||
|
||||
private void OnGetMoveSpeedModifiers(Entity<LightLevelHealthComponent> ent, ref RefreshMovementSpeedModifiersEvent args)
|
||||
{
|
||||
if (!TryComp<LightReactiveComponent>(ent, out var lightReactive))
|
||||
return;
|
||||
|
||||
if (lightReactive.CurrentLightLevel < ent.Comp.DarkThreshold)
|
||||
args.ModifySpeed(ent.Comp.DarkMovementSpeedMultiplier);
|
||||
else if (lightReactive.CurrentLightLevel > ent.Comp.LightThreshold)
|
||||
args.ModifySpeed(ent.Comp.LightMovementSpeedMultiplier);
|
||||
}
|
||||
|
||||
private void OnDamageModify(Entity<LightLevelDamageMultComponent> ent, ref DamageModifyEvent args)
|
||||
{
|
||||
if (!TryComp<LightLevelHealthComponent>(ent, out var lightHealth))
|
||||
return;
|
||||
|
||||
// On the receiving end.
|
||||
args.Damage *= lightHealth.CurrentThreshold switch
|
||||
{
|
||||
-1 => ent.Comp.DarkReceivedMultiplier,
|
||||
1 => ent.Comp.LightReceivedMultiplier,
|
||||
_ => 1.0f
|
||||
};
|
||||
|
||||
var modifiers = lightHealth.CurrentThreshold switch
|
||||
{
|
||||
-1 => ent.Comp.DarkReceivedModifiers,
|
||||
1 => ent.Comp.LightReceivedModifiers,
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (modifiers != null)
|
||||
args.Damage = DamageSpecifier.ApplyModifierSet(args.Damage, modifiers);
|
||||
}
|
||||
|
||||
private void OnGetMeleeDamage(Entity<LightLevelDamageMultComponent> ent, ref GetMeleeDamageEvent args)
|
||||
{
|
||||
if (!TryComp<LightLevelHealthComponent>(ent, out var lightHealth))
|
||||
return;
|
||||
|
||||
args.Damage *= lightHealth.CurrentThreshold switch
|
||||
{
|
||||
-1 => ent.Comp.DarkDealtMultiplier,
|
||||
1 => ent.Comp.LightDealtMultiplier,
|
||||
_ => 1.0f
|
||||
};
|
||||
|
||||
var modifiers = lightHealth.CurrentThreshold switch
|
||||
{
|
||||
-1 => ent.Comp.DarkDealtModifiers,
|
||||
1 => ent.Comp.LightDealtModifiers,
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (modifiers != null)
|
||||
args.Damage = DamageSpecifier.ApplyModifierSet(args.Damage, modifiers);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.Light;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class BreakLightsOnSpawnComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The radius in which lights will be broken.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Radius = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// If true, lights will only be broken if the entity has line of sight to them.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool LineOfSight = false;
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.Light;
|
||||
|
||||
/// <summary>
|
||||
/// A component that reacts to changes in light levels.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedLightReactiveSystem))]
|
||||
public sealed partial class LightReactiveComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The frequency at which the component checks for light level changes.
|
||||
/// There should be very little reason it should be higher than this.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan UpdateFrequency = TimeSpan.FromSeconds(1);
|
||||
|
||||
/// <summary>
|
||||
/// Whether the component should only update while the entity is alive.
|
||||
/// If false, it will update even if the entity is dead.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool OnlyWhileAlive = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should this update its light level automatically, or only when asked to by another system?
|
||||
/// If true, it will update its light level automatically.
|
||||
/// If false, it will only update when explicitly requested.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Manual = false;
|
||||
|
||||
/// <summary>
|
||||
/// The next time the component should update.
|
||||
/// </summary>
|
||||
[AutoNetworkedField]
|
||||
public TimeSpan NextUpdate = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// The current light level of this entity.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float CurrentLightLevel = 0f;
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Shared._DV.Light;
|
||||
|
||||
public abstract class SharedLightReactiveSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
|
||||
private EntityQuery<LightReactiveComponent> _lightReactive;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_lightReactive = GetEntityQuery<LightReactiveComponent>();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
|
||||
var query = EntityQueryEnumerator<LightReactiveComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (_timing.CurTime < comp.NextUpdate)
|
||||
return;
|
||||
comp.NextUpdate = _timing.CurTime + TimeSpan.FromSeconds(1);
|
||||
if (_mobState.IsDead(uid) && comp.OnlyWhileAlive)
|
||||
continue; // Don't apply damage / healing if the mob is dead
|
||||
// Get the light level at the entity's position
|
||||
comp.CurrentLightLevel = GetLightLevelForPoint(uid);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract HashSet<Entity<SharedPointLightComponent>> GetLights(EntityUid targetEntity);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current light level of an entity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a cached value that is updated periodically.
|
||||
/// I could add arguments to either: Force check if it's not a ReactiveComponent, or force update,
|
||||
/// but I don't need them so you don't get them. Add it if you want it.
|
||||
/// </remarks>
|
||||
public float GetLightLevel(EntityUid uid, bool forceUpdate = false)
|
||||
{
|
||||
if (_lightReactive.TryComp(uid, out LightReactiveComponent? comp))
|
||||
{
|
||||
if (forceUpdate)
|
||||
comp.CurrentLightLevel = GetLightLevelForPoint(uid);
|
||||
return comp.CurrentLightLevel;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the light level at a specific point in the world.
|
||||
/// Avoid calling this too often, as it can be expensive.
|
||||
/// </summary>
|
||||
public float GetLightLevelForPoint(EntityUid uid)
|
||||
{
|
||||
float val = 0.0f;
|
||||
// Get the current map entity so we can get a MapLightComponent from it if it has one
|
||||
var map = _transform.GetMap(uid);
|
||||
if (TryComp(map, out MapLightComponent? mapLight))
|
||||
val += (mapLight.AmbientLightColor.R + mapLight.AmbientLightColor.G + mapLight.AmbientLightColor.B) / 3f;
|
||||
var pos = _transform.GetWorldPosition(uid);
|
||||
|
||||
foreach (var (lightUid, lightComp) in GetLights(uid))
|
||||
{
|
||||
// Ensure we're on the same grid as the light source
|
||||
if (_transform.GetMap(lightUid) != map)
|
||||
continue;
|
||||
|
||||
// Ensure we're within the light's radius.
|
||||
var lightPos = _transform.GetWorldPosition(lightUid);
|
||||
var sqrDistance = Vector2.DistanceSquared(pos, lightPos);
|
||||
if (sqrDistance > lightComp.Radius * lightComp.Radius)
|
||||
continue;
|
||||
|
||||
if (sqrDistance < 0.01f)
|
||||
{
|
||||
// If we're right on top of the light, just add its full energy value.
|
||||
val += lightComp.Energy;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collision ray check from the entity to the light source
|
||||
var ray = new CollisionRay(pos, (lightPos - pos).Normalized(), (int)CollisionGroup.Opaque);
|
||||
var hit = _physics.IntersectRay(_transform.GetMapId(uid), ray, MathF.Sqrt(sqrDistance) - 0.5f, returnOnFirstHit: true);
|
||||
if (hit.Any() && hit.First().Distance != 0)
|
||||
continue;
|
||||
// If we reach here, the light is unobstructed and within range, calculate a light value to add.
|
||||
val += lightComp.Energy * (1.0f - sqrDistance / (lightComp.Radius * lightComp.Radius));
|
||||
}
|
||||
|
||||
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Misandry <mary@thughunt.ing>
|
||||
// SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared.Inventory;
|
||||
|
||||
namespace Content.Shared._Goobstation.Flashbang;
|
||||
|
||||
public sealed class FlashDurationMultiplierEvent : EntityEventArgs, IInventoryRelayEvent
|
||||
{
|
||||
public float Multiplier = 1f;
|
||||
|
||||
public SlotFlags TargetSlots => SlotFlags.EYES | SlotFlags.HEAD | SlotFlags.MASK;
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Misandry <mary@thughunt.ing>
|
||||
// SPDX-FileCopyrightText: 2025 Solstice <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 SolsticeOfTheWinter <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 pheenty <fedorlukin2006@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared._Goobstation.Flashbang;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
|
||||
namespace Content.Shared._Goobstation.Inventory;
|
||||
|
||||
public partial class GoobInventorySystem
|
||||
{
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
|
||||
public void InitializeRelays()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<InventoryComponent, FlashDurationMultiplierEvent>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<Overlays.NightVisionComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<Overlays.ThermalVisionComponent>>(RefRelayInventoryEvent);
|
||||
}
|
||||
|
||||
private void RefRelayInventoryEvent<T>(EntityUid uid, InventoryComponent component, ref T args) where T : IInventoryRelayEvent
|
||||
{
|
||||
_inventorySystem.RelayEvent((uid, component), ref args);
|
||||
}
|
||||
|
||||
private void RelayInventoryEvent<T>(EntityUid uid, InventoryComponent component, T args) where T : IInventoryRelayEvent
|
||||
{
|
||||
_inventorySystem.RelayEvent((uid, component), args);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Misandry <mary@thughunt.ing>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace Content.Shared._Goobstation.Inventory;
|
||||
|
||||
public sealed partial class GoobInventorySystem : EntitySystem
|
||||
{
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
InitializeRelays();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Misandry <mary@thughunt.ing>
|
||||
// SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace Content.Shared._Goobstation.Overlays;
|
||||
|
||||
public abstract partial class BaseVisionOverlayComponent : Component
|
||||
{
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public virtual Vector3 Tint { get; set; } = new(0.3f, 0.3f, 0.3f);
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public virtual float Strength { get; set; } = 2f;
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public virtual float Noise { get; set; } = 0.5f;
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public virtual Color Color { get; set; } = Color.White;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Misandry <mary@thughunt.ing>
|
||||
// SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._Goobstation.Overlays;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class NightVisionComponent : SwitchableVisionOverlayComponent
|
||||
{
|
||||
public override EntProtoId? ToggleAction { get; set; } = "ToggleNightVision";
|
||||
|
||||
public override Color Color { get; set; } = Color.FromHex("#98FB98");
|
||||
}
|
||||
|
||||
public sealed partial class ToggleNightVisionEvent : InstantActionEvent;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Misandry <mary@thughunt.ing>
|
||||
// SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace Content.Shared._Goobstation.Overlays;
|
||||
|
||||
public sealed class SharedNightVisionSystem : SwitchableOverlaySystem<NightVisionComponent, ToggleNightVisionEvent>;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Misandry <mary@thughunt.ing>
|
||||
// SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace Content.Shared._Goobstation.Overlays;
|
||||
|
||||
public sealed class SharedThermalVisionSystem : SwitchableOverlaySystem<ThermalVisionComponent, ToggleThermalVisionEvent>;
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Misandry <mary@thughunt.ing>
|
||||
// SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared._Goobstation.Flashbang;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared._Goobstation.Overlays;
|
||||
|
||||
public abstract class SwitchableOverlaySystem<TComp, TEvent> : EntitySystem // this should get move to a white module if we ever do anything with forks..
|
||||
where TComp : SwitchableVisionOverlayComponent
|
||||
where TEvent : InstantActionEvent
|
||||
{
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<TComp, TEvent>(OnToggle);
|
||||
SubscribeLocalEvent<TComp, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<TComp, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<TComp, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<TComp, GetItemActionsEvent>(OnGetItemActions);
|
||||
SubscribeLocalEvent<TComp, ComponentGetState>(OnGetState);
|
||||
SubscribeLocalEvent<TComp, ComponentHandleState>(OnHandleState);
|
||||
SubscribeLocalEvent<TComp, FlashDurationMultiplierEvent>(OnGetFlashMultiplier);
|
||||
SubscribeLocalEvent<TComp, InventoryRelayedEvent<FlashDurationMultiplierEvent>>(OnGetInventoryFlashMultiplier);
|
||||
}
|
||||
|
||||
private void OnGetFlashMultiplier(Entity<TComp> ent, ref FlashDurationMultiplierEvent args)
|
||||
{
|
||||
if (!ent.Comp.IsEquipment)
|
||||
args.Multiplier *= GetFlashMultiplier(ent);
|
||||
}
|
||||
|
||||
private void OnGetInventoryFlashMultiplier(Entity<TComp> ent,
|
||||
ref InventoryRelayedEvent<FlashDurationMultiplierEvent> args)
|
||||
{
|
||||
if (ent.Comp.IsEquipment)
|
||||
args.Args.Multiplier *= GetFlashMultiplier(ent);
|
||||
}
|
||||
|
||||
private float GetFlashMultiplier(TComp comp)
|
||||
{
|
||||
if (!comp.IsActive && (comp.PulseTime <= 0f || comp.PulseAccumulator >= comp.PulseTime))
|
||||
return 1f;
|
||||
|
||||
return comp.FlashDurationMultiplier;
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
if (_net.IsClient)
|
||||
ActiveTick(frameTime);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (_net.IsServer)
|
||||
ActiveTick(frameTime);
|
||||
}
|
||||
|
||||
private void ActiveTick(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<TComp>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (comp.PulseTime <= 0f || comp.PulseAccumulator >= comp.PulseTime)
|
||||
continue;
|
||||
|
||||
comp.PulseAccumulator += frameTime;
|
||||
|
||||
if (comp.PulseAccumulator < comp.PulseTime)
|
||||
continue;
|
||||
|
||||
Toggle(uid, comp, false, false);
|
||||
RaiseSwitchableOverlayToggledEvent(uid, uid, comp.IsActive);
|
||||
RaiseSwitchableOverlayToggledEvent(uid, Transform(uid).ParentUid, comp.IsActive);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetState(EntityUid uid, TComp component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new SwitchableVisionOverlayComponentState
|
||||
{
|
||||
Color = component.Color,
|
||||
IsActive = component.IsActive,
|
||||
FlashDurationMultiplier = component.FlashDurationMultiplier,
|
||||
ActivateSound = component.ActivateSound,
|
||||
DeactivateSound = component.DeactivateSound,
|
||||
ToggleAction = component.ToggleAction,
|
||||
LightRadius = component is ThermalVisionComponent thermal ? thermal.LightRadius : 0f,
|
||||
};
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, TComp component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not SwitchableVisionOverlayComponentState state)
|
||||
return;
|
||||
|
||||
component.Color = state.Color;
|
||||
component.FlashDurationMultiplier = state.FlashDurationMultiplier;
|
||||
component.ActivateSound = state.ActivateSound;
|
||||
component.DeactivateSound = state.DeactivateSound;
|
||||
|
||||
if (component.ToggleAction != state.ToggleAction)
|
||||
{
|
||||
_actions.RemoveAction(uid, component.ToggleActionEntity);
|
||||
component.ToggleAction = state.ToggleAction;
|
||||
if (component.ToggleAction != null)
|
||||
_actions.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
|
||||
if (component is ThermalVisionComponent thermal)
|
||||
thermal.LightRadius = state.LightRadius;
|
||||
|
||||
if (component.IsActive == state.IsActive)
|
||||
return;
|
||||
|
||||
component.IsActive = state.IsActive;
|
||||
|
||||
RaiseSwitchableOverlayToggledEvent(uid,
|
||||
component.IsEquipment ? Transform(uid).ParentUid : uid,
|
||||
component.IsActive);
|
||||
}
|
||||
|
||||
private void OnGetItemActions(Entity<TComp> ent, ref GetItemActionsEvent args)
|
||||
{
|
||||
if (ent.Comp.IsEquipment && ent.Comp.ToggleAction != null && args.SlotFlags is not SlotFlags.POCKET and not null)
|
||||
args.AddAction(ref ent.Comp.ToggleActionEntity, ent.Comp.ToggleAction);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, TComp component, ComponentShutdown args)
|
||||
{
|
||||
if (!component.IsEquipment)
|
||||
_actions.RemoveAction(uid, component.ToggleActionEntity);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, TComp component, ComponentInit args)
|
||||
{
|
||||
component.PulseAccumulator = component.PulseTime;
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, TComp component, MapInitEvent args)
|
||||
{
|
||||
if (component is { IsEquipment: false, ToggleActionEntity: null, ToggleAction: not null })
|
||||
_actions.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
|
||||
private void OnToggle(EntityUid uid, TComp component, TEvent args)
|
||||
{
|
||||
Toggle(uid, component, !component.IsActive);
|
||||
RaiseSwitchableOverlayToggledEvent(uid, args.Performer, component.IsActive);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void Toggle(EntityUid uid, TComp component, bool activate, bool playSound = true)
|
||||
{
|
||||
if (playSound && _net.IsClient && _timing.IsFirstTimePredicted)
|
||||
{
|
||||
_audio.PlayEntity(activate ? component.ActivateSound : component.DeactivateSound,
|
||||
Filter.Local(),
|
||||
uid,
|
||||
false);
|
||||
}
|
||||
|
||||
if (component.PulseTime > 0f)
|
||||
{
|
||||
component.PulseAccumulator = activate ? 0f : component.PulseTime;
|
||||
return;
|
||||
}
|
||||
|
||||
component.IsActive = activate;
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void RaiseSwitchableOverlayToggledEvent(EntityUid uid, EntityUid user, bool activated)
|
||||
{
|
||||
var ev = new SwitchableOverlayToggledEvent(user, activated);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
}
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct SwitchableOverlayToggledEvent(EntityUid User, bool Activated);
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Misandry <mary@thughunt.ing>
|
||||
// SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._Goobstation.Overlays;
|
||||
|
||||
public abstract partial class SwitchableVisionOverlayComponent : BaseVisionOverlayComponent
|
||||
{
|
||||
[DataField]
|
||||
public bool IsActive;
|
||||
|
||||
[DataField]
|
||||
public bool DrawOverlay = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether it should grant equipment enhanced vision or is it mob vision
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool IsEquipment;
|
||||
|
||||
/// <summary>
|
||||
/// If it is greater than 0, overlay isn't toggled but pulsed instead
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float PulseTime;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public float PulseAccumulator;
|
||||
|
||||
[DataField]
|
||||
public float FlashDurationMultiplier = 1f;
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier? ActivateSound = new SoundPathSpecifier("/Audio/_White/Items/Goggles/activate.ogg");
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier? DeactivateSound = new SoundPathSpecifier("/Audio/_White/Items/Goggles/deactivate.ogg");
|
||||
|
||||
[DataField]
|
||||
public virtual EntProtoId? ToggleAction { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid? ToggleActionEntity;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SwitchableVisionOverlayComponentState : IComponentState
|
||||
{
|
||||
public Color Color;
|
||||
public bool IsActive;
|
||||
public float FlashDurationMultiplier;
|
||||
public SoundSpecifier? ActivateSound;
|
||||
public SoundSpecifier? DeactivateSound;
|
||||
public EntProtoId? ToggleAction;
|
||||
public float LightRadius;
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Armok <155400926+ARMOKS@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Misandry <mary@thughunt.ing>
|
||||
// SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._Goobstation.Overlays;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class ThermalVisionComponent : SwitchableVisionOverlayComponent
|
||||
{
|
||||
public override EntProtoId? ToggleAction { get; set; } = "ToggleThermalVision";
|
||||
|
||||
public override Color Color { get; set; } = Color.FromHex("#d06764");
|
||||
|
||||
[DataField]
|
||||
public float LightRadius = 2f;
|
||||
}
|
||||
|
||||
public sealed partial class ToggleThermalVisionEvent : InstantActionEvent;
|
||||
|
|
@ -2,3 +2,7 @@
|
|||
license: "CC-BY-NC-3.0"
|
||||
copyright: "Freesound user BristolStories"
|
||||
source: "https://freesound.org/people/BristolStories/sounds/65915/"
|
||||
- files: ["creepyshriek.ogg"]
|
||||
license: "CC-BY-NC-3.0"
|
||||
copyright: "Aurorastation"
|
||||
source: "https://github.com/Aurorastation/Aurora.3/blob/master/sound/effects/creepyshriek.ogg"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
# SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
- files: ["activate.ogg"]
|
||||
license: "CC-BY-NC-SA-3.0"
|
||||
copyright: "Taken from TGstation"
|
||||
source: "https://github.com/tgstation/tgstation"
|
||||
|
||||
- files: ["deactivate.ogg"]
|
||||
license: "CC-BY-NC-SA-3.0"
|
||||
copyright: "Taken from TGstation"
|
||||
source: "https://github.com/tgstation/tgstation"
|
||||
|
|
@ -24,6 +24,7 @@ psionic-power-precognition-breaker-flip-result-message = You see torches snuff a
|
|||
psionic-power-precognition-bureaucratic-error-result-message = You see a vision of yourself trapped in a room, trying to solve a puzzle with both missing and duplicate pieces.
|
||||
psionic-power-precognition-clerical-error-result-message = You see faces you once knew being obscured in a fog of static, identities lost.
|
||||
psionic-power-precognition-closet-skeleton-result-message = You hear a crackling laugh echo and clinking bones in the dusty recesses of the station.
|
||||
psionic-power-precognition-skia-result-message = The shadows around you gnash and scratch at you, a great beast of the noösphere is stalking you. You feel its breath on your neck.
|
||||
psionic-power-precognition-dragon-spawn-result-message = Reality around you bulges and breaks as a great beast cries for war. The smell of salty sea and blood fills the air.
|
||||
psionic-power-precognition-colossus-spawn-result-message = You see the vast shadow of a monstrosity so large that it casts all beneath it into darkness. The noösphere shifts precariously in its wake.
|
||||
psionic-power-precognition-ninja-spawn-result-message = You see a vision of shadows brought to life, hounds of war howling their cries as they chase it through dark corners of the station.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
ghost-role-information-skia-name = Skia
|
||||
ghost-role-information-skia-description = The fates have cut the thread of a soul, stalk the shadows to find them and reap what is owed.
|
||||
ghost-role-information-skia-rules = You are a [color=red][bold]Solo Antagonist[/bold][/color].
|
||||
You may kill your target, you do not need to ensure they stay dead.
|
||||
You should avoid attacking those other than your target, except to protect yourself or to ensure your target dies.
|
||||
|
||||
skia-role-briefing =
|
||||
You are a Skia, a Shade of the Noösphere.
|
||||
You are bound to darkness and shadows, use the darkness to your advantage.
|
||||
Find your prey and reap their soul (kill them). Avoid ending the lives of innocents.
|
||||
skia-round-end-name = Skia
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
objective-issuer-skia = [color=#c7e1eb]The Fates[/color]
|
||||
objective-condition-reap-soul-title = Sever the soul of {$targetName}, {CAPITALIZE($job)}.
|
||||
objective-condition-reap-soul-reroll-message = The Fates have severed another soul. Your new target is {$targetName}, {CAPITALIZE($job)}.
|
||||
|
|
@ -22,5 +22,8 @@ roles-antag-nuclear-operative-corpsman-objective = The team's combat medic taske
|
|||
roles-antag-menace-skeleton-name = Menace Skeleton
|
||||
roles-antag-menace-skeleton-objective = Rattle 'Em
|
||||
|
||||
roles-antag-skia-name = Skia
|
||||
roles-antag-skia-objective = Rend souls from the living.
|
||||
|
||||
roles-antag-syndicate-armsdealer-name = Arms Dealer
|
||||
roles-antag-syndicate-armsdealer-objective = Sell your firearms and stay away from the law.
|
||||
|
|
|
|||
|
|
@ -34,3 +34,6 @@ construction-graph-tag-carp-plushie = carp plushie
|
|||
|
||||
# Moth plushie
|
||||
construction-graph-tag-mothroach-hide = mothroach hide
|
||||
|
||||
# Hoodies
|
||||
construction-graph-tag-hoodie = hoodie
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
research-technology-night-vision = Night Vision Technology
|
||||
research-technology-thermal-vision = Thermal Vision Technology
|
||||
|
|
@ -229,25 +229,6 @@
|
|||
- type: IdentityBlocker
|
||||
coverage: EYES
|
||||
|
||||
#Make a scanner category when these actually function and we get the trayson
|
||||
- type: entity
|
||||
parent: ClothingEyesBase
|
||||
id: ClothingEyesGlassesThermal
|
||||
name: optical thermal scanner
|
||||
description: Thermals in the shape of glasses.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Clothing/Eyes/Glasses/thermal.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Glasses/thermal.rsi
|
||||
- type: Armor
|
||||
modifiers:
|
||||
coefficients:
|
||||
Heat: 0.95
|
||||
- type: GroupExamine
|
||||
- type: IdentityBlocker
|
||||
coverage: EYES
|
||||
|
||||
- type: entity
|
||||
parent: ClothingEyesBase
|
||||
id: ClothingEyesGlassesChemical
|
||||
|
|
|
|||
|
|
@ -82,6 +82,10 @@
|
|||
sprite: Clothing/OuterClothing/Misc/black_hoodie.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/OuterClothing/Misc/black_hoodie.rsi
|
||||
- type: Tag # DeltaV - It's a hoodie
|
||||
tags:
|
||||
- WhitelistChameleon
|
||||
- Hoodie
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterBase
|
||||
|
|
@ -93,6 +97,10 @@
|
|||
sprite: Clothing/OuterClothing/Misc/grey_hoodie.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/OuterClothing/Misc/grey_hoodie.rsi
|
||||
- type: Tag # DeltaV - It's a hoodie
|
||||
tags:
|
||||
- WhitelistChameleon
|
||||
- Hoodie
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterBase
|
||||
|
|
@ -122,6 +130,10 @@
|
|||
sprite: Clothing/OuterClothing/Misc/chaplain_hoodie.rsi
|
||||
- type: ToggleableClothing
|
||||
clothingPrototype: ClothingHeadHatHoodChaplainHood
|
||||
- type: Tag # DeltaV - It's a hoodie
|
||||
tags:
|
||||
- WhitelistChameleon
|
||||
- Hoodie
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterBase
|
||||
|
|
|
|||
|
|
@ -228,6 +228,7 @@
|
|||
- Equipment
|
||||
- FauxTiles
|
||||
- Botany # DeltaV
|
||||
- SpecOpsGoogles # DeltaV
|
||||
- Surgery # Shitmed change
|
||||
- type: EmagLatheRecipes
|
||||
emagDynamicPacks:
|
||||
|
|
@ -442,6 +443,7 @@
|
|||
- SecurityRubberAmmoStatic
|
||||
- PrisonerSoftsuits
|
||||
- EVASuit
|
||||
- SpecOpsGoogles
|
||||
# End DeltaV Additions
|
||||
dynamicPacks:
|
||||
- SalvageSecurityBoards
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
TelegnosisPower: 1
|
||||
PsionicRegenerationPower: 1
|
||||
PrecognitionPower: 1 # DeltaV
|
||||
PsychokineticScreamPower: 1
|
||||
MassSleepPower: 0.3
|
||||
NoosphericZapPower: 0.3
|
||||
# PsionicInvisibilityPower: 0.15
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
jumpsuit: ClothingUniformJumpsuitOperative
|
||||
back: ClothingBackpackDuffelSyndicate
|
||||
mask: ClothingMaskGasSyndicate
|
||||
eyes: ClothingEyesHudSyndicate
|
||||
eyes: ClothingEyesNightVisionGogglesNukie # Goobstation
|
||||
ears: ClothingHeadsetAltSyndicate
|
||||
gloves: ClothingHandsGlovesCombat
|
||||
outerClothing: ClothingOuterHardsuitSyndie
|
||||
|
|
@ -115,7 +115,7 @@
|
|||
parent: SyndicateOperativeGearFull
|
||||
equipment:
|
||||
pocket2: AgentUplinkRadio45TC # DeltaV - allows them to buy Agent armor
|
||||
eyes: ClothingEyesHudSyndicateAgent
|
||||
eyes: ClothingEyesNightVisionGogglesNukie # Goobstation
|
||||
outerClothing: ClothingOuterCoatCybersunWindbreaker # DeltaV - removal of armor
|
||||
# shoes: ClothingShoesBootsMagSyndie # DeltaV - removal of armor
|
||||
id: SyndiAgentPDA
|
||||
|
|
|
|||
|
|
@ -211,3 +211,14 @@
|
|||
checkConsciousness: false
|
||||
- type: InstantAction
|
||||
event: !type:PrecognitionPowerActionEvent
|
||||
|
||||
- type: entity
|
||||
id: ActionPsychokineticScream
|
||||
name: Psychokinetic Scream
|
||||
description: Emit a blood-curdling scream that shatters all lights in the area.
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _DV/Actions/shatterlights.rsi, state: shatter-lights }
|
||||
useDelay: 60
|
||||
- type: InstantAction
|
||||
event: !type:ShatterLightsActionEvent
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
- type: entity
|
||||
id: ActionShatterLights
|
||||
name: Shatter Lights
|
||||
description: Emit a blood-curdling scream that shatters all lights in the area.
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _DV/Actions/shatterlights.rsi, state: shatter-lights }
|
||||
useDelay: 30
|
||||
- type: InstantAction
|
||||
event: !type:ShatterLightsActionEvent
|
||||
|
||||
- type: entity
|
||||
id: ActionTechnokineticPulse
|
||||
name: Technokinetic Pulse
|
||||
description: Unleash a burst of Technokinetic energy, disabling and destroying nearby electronics.
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _DV/Actions/technokineticpulse.rsi, state: technokinetic-pulse }
|
||||
useDelay: 60
|
||||
- type: InstantAction
|
||||
event: !type:TechnokineticPulseActionEvent
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
- type: alertCategory
|
||||
id: Light
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
- type: alert
|
||||
id: LightLevelDarkIcon
|
||||
category: Light
|
||||
icons:
|
||||
- sprite: /Textures/_DV/Interface/Alerts/light_level.rsi
|
||||
state: dark
|
||||
name: alerts-light-level-dark-name
|
||||
description: alerts-light-level-dark-desc
|
||||
|
||||
- type: alert
|
||||
id: LightLevelNeutralIcon
|
||||
category: Light
|
||||
icons:
|
||||
- sprite: /Textures/_DV/Interface/Alerts/light_level.rsi
|
||||
state: neutral
|
||||
name: alerts-light-level-neutral-name
|
||||
description: alerts-light-level-neutral-desc
|
||||
|
||||
- type: alert
|
||||
id: LightLevelBrightIcon
|
||||
category: Light
|
||||
icons:
|
||||
- sprite: /Textures/_DV/Interface/Alerts/light_level.rsi
|
||||
state: bright
|
||||
name: alerts-light-level-bright-name
|
||||
description: alerts-light-level-bright-desc
|
||||
|
|
@ -9,3 +9,21 @@
|
|||
sprite: _DV/Clothing/Head/Hoods/interdynechemhood.rsi
|
||||
- type: Clothing
|
||||
sprite: _DV/Clothing/Head/Hoods/interdynechemhood.rsi
|
||||
|
||||
- type: entity
|
||||
parent: ClothingHeadBase
|
||||
id: ClothingHeadHatHoodSkiaHood
|
||||
categories: [ HideSpawnMenu ]
|
||||
name: skia hood
|
||||
description: Smells like Tartarus in here.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _DV/Clothing/Head/Hoods/skia_hoodie.rsi
|
||||
- type: Clothing
|
||||
sprite: _DV/Clothing/Head/Hoods/skia_hoodie.rsi
|
||||
- type: Tag
|
||||
tags:
|
||||
- WhitelistChameleon
|
||||
- type: HideLayerClothing
|
||||
slots:
|
||||
- Hair
|
||||
|
|
|
|||
|
|
@ -30,3 +30,23 @@
|
|||
Caustic: 0.40
|
||||
- type: ToggleableClothing
|
||||
clothingPrototype: ClothingHeadHatInterdyneChemistryHood
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterBaseToggleable
|
||||
id: ClothingOuterHoodieSkia
|
||||
name: skia hoodie
|
||||
description: The robes worn by a shade.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _DV/Clothing/OuterClothing/Misc/skia_hoodie.rsi
|
||||
- type: Clothing
|
||||
sprite: _DV/Clothing/OuterClothing/Misc/skia_hoodie.rsi
|
||||
- type: ToggleableClothing
|
||||
clothingPrototype: ClothingHeadHatHoodSkiaHood
|
||||
- type: Construction
|
||||
graph: SkiaHoodie
|
||||
node: skiaHoodie
|
||||
- type: Tag
|
||||
tags:
|
||||
- Hoodie
|
||||
- WhitelistChameleon
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
- type: entity
|
||||
id: WeaponArcSkiaLunge
|
||||
parent: WeaponArcStatic
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: WeaponArcVisuals
|
||||
- type: Sprite
|
||||
scale: .5, .5
|
||||
layers:
|
||||
- sprite: _DV/Effects/arcs.rsi
|
||||
state: skialunge
|
||||
shader: unshaded
|
||||
- type: TimedDespawn
|
||||
lifetime: 0.399
|
||||
|
||||
- type: entity
|
||||
id: WeaponArcSkiaSwipe
|
||||
parent: WeaponArcSlash
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: WeaponArcVisuals
|
||||
- type: Sprite
|
||||
scale: .5, .5
|
||||
layers:
|
||||
- sprite: _DV/Effects/arcs.rsi
|
||||
state: skiaswipe
|
||||
shader: unshaded
|
||||
- type: TimedDespawn
|
||||
lifetime: 0.399
|
||||
|
|
@ -155,6 +155,22 @@
|
|||
- !type:OverallPlaytimeRequirement
|
||||
time: 172800 # 48h
|
||||
|
||||
- type: entity
|
||||
categories: [ HideSpawnMenu, Spawner ]
|
||||
parent: BaseAntagSpawner
|
||||
id: SpawnPointSkia
|
||||
name: skia spawn point
|
||||
components:
|
||||
- type: GhostRole
|
||||
name: ghost-role-information-skia-name
|
||||
description: ghost-role-information-skia-description
|
||||
rules: ghost-role-information-skia-rules
|
||||
mindRoles:
|
||||
- MindRoleSkia
|
||||
requirements: # keep in sync with the antag prototype
|
||||
- !type:OverallPlaytimeRequirement
|
||||
time: 172800 # 24h
|
||||
|
||||
- type: entity
|
||||
categories: [ HideSpawnMenu, Spawner ]
|
||||
parent: BaseAntagSpawner
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
- type: entity
|
||||
parent: [ SimpleSpaceMobBase, MobCombat ]
|
||||
id: MobSkia
|
||||
name: skia
|
||||
description: A shadow given form, lashing out at anything that comes too close.
|
||||
components:
|
||||
- type: Sprite
|
||||
drawdepth: Mobs
|
||||
sprite: _DV/Mobs/Animals/skia.rsi
|
||||
layers:
|
||||
- map: ["enum.DamageStateVisualLayers.Base"]
|
||||
state: skia
|
||||
- type: DamageStateVisuals
|
||||
states:
|
||||
Alive:
|
||||
Base: skia
|
||||
Dead:
|
||||
Base: dead
|
||||
- type: MobState
|
||||
allowedStates:
|
||||
- Alive
|
||||
- Dead
|
||||
- type: MobThresholds
|
||||
thresholds:
|
||||
0: Alive
|
||||
120: Dead
|
||||
- type: Damageable
|
||||
damageModifierSet: ManifestedSpirit
|
||||
damageContainer: BiologicalMetaphysical
|
||||
- type: Armor
|
||||
modifiers:
|
||||
coefficients:
|
||||
Blunt: 0.4
|
||||
Slash: 0.4
|
||||
Piercing: 0.4
|
||||
Heat: 0.8
|
||||
Radiation: 0.2
|
||||
Caustic: 0.2
|
||||
- type: MovementSpeedModifier
|
||||
baseWalkSpeed: 2.25
|
||||
baseSprintSpeed: 3.75
|
||||
- type: CombatMode
|
||||
- type: MeleeWeapon
|
||||
soundHit:
|
||||
path: /Audio/Weapons/Xeno/alien_claw_flesh3.ogg
|
||||
angle: 90
|
||||
animation: WeaponArcSkiaLunge
|
||||
wideAnimation: WeaponArcSkiaSwipe
|
||||
wideAnimationRotation: -90
|
||||
swingLeft: true
|
||||
attackRate: 1.0
|
||||
range: 2.0
|
||||
altDisarm: false
|
||||
damage:
|
||||
types:
|
||||
Slash: 19
|
||||
Pierce: 8
|
||||
Cold: 8
|
||||
- type: NpcFactionMember
|
||||
factions:
|
||||
- SimpleHostile
|
||||
- type: Tag
|
||||
tags:
|
||||
- DoorBumpOpener
|
||||
- type: Speech
|
||||
speechVerb: Ghost
|
||||
- type: Insulated
|
||||
- type: LightReactive
|
||||
manual: true
|
||||
- type: LightLevelHealth
|
||||
darkThreshold: 0.3
|
||||
lightThreshold: 0.7
|
||||
darkDamage:
|
||||
groups:
|
||||
Brute: -5
|
||||
Burn: -5
|
||||
Toxin: -5
|
||||
Airloss: -5
|
||||
Genetic: -5
|
||||
Metaphysical: -1
|
||||
lightDamage:
|
||||
groups:
|
||||
Brute: 5
|
||||
darkMovementSpeedMultiplier: 1.25
|
||||
lightMovementSpeedMultiplier: 1.0
|
||||
- type: LightLevelDamageMult
|
||||
lightReceivedMultiplier: 2.0
|
||||
lightDealtMultiplier: 0.5
|
||||
- type: Bloodstream
|
||||
maxBleedAmount: 0
|
||||
- type: ShatterLightsAbility
|
||||
lineOfSight: true
|
||||
- type: TechnokineticPulseAbility
|
||||
range: 5.0
|
||||
energyConsumption: 20000
|
||||
disableDuration: 20.0
|
||||
- type: Butcherable
|
||||
spawned:
|
||||
- id: Ectoplasm
|
||||
amount: 1
|
||||
- type: Prying
|
||||
pryPowered: true
|
||||
force: true
|
||||
speedModifier: 2.5 # needs to be fast because they'll get ganked otherwise
|
||||
useSound:
|
||||
path: /Audio/Items/crowbar.ogg
|
||||
- type: NightVision
|
||||
drawOverlay: false
|
||||
- type: Targeting
|
||||
|
|
@ -89,6 +89,7 @@
|
|||
- EngineeringWeapons
|
||||
- FauxTiles
|
||||
- Equipment
|
||||
- SpecOpsGoogles
|
||||
- UpgradeKits
|
||||
- UpgradeKits_Goob
|
||||
- EngineeringHardsuits
|
||||
|
|
@ -130,6 +131,7 @@
|
|||
dynamicPacks:
|
||||
- Equipment
|
||||
- Mining
|
||||
- SpecOpsGoogles
|
||||
- SalvageWeapons
|
||||
- SalvageHardsuits
|
||||
- type: Machine
|
||||
|
|
@ -216,6 +218,7 @@
|
|||
- ScienceEquipment
|
||||
- ScienceClothing
|
||||
- ScienceWeapons
|
||||
- SpecOpsGoogles
|
||||
- Chemistry
|
||||
- PowerCells
|
||||
- type: Machine
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
- id: ListeningPost
|
||||
- id: RoboNeuroticist
|
||||
- id: MenaceSkeleton
|
||||
- id: SkiaSpawn
|
||||
|
||||
# Replaces upstream meteor events until they're good
|
||||
- type: entity
|
||||
|
|
@ -300,3 +301,33 @@
|
|||
sound: /Audio/Effects/clang.ogg
|
||||
mindRoles:
|
||||
- MindRoleMenaceSkeleton
|
||||
|
||||
- type: entity
|
||||
parent: BaseMidRoundAntag
|
||||
id: SkiaSpawn
|
||||
components:
|
||||
- type: StationEvent
|
||||
weight: 6
|
||||
duration: null
|
||||
minimumPlayers: 30
|
||||
- type: PrecognitionResult
|
||||
message: psionic-power-precognition-skia-result-message
|
||||
- type: AntagSpawner
|
||||
prototype: MobSkia
|
||||
- type: AntagObjectives
|
||||
objectives:
|
||||
- SkiaReapObjective
|
||||
- type: AntagSelection
|
||||
agentName: skia-round-end-name
|
||||
definitions:
|
||||
- spawnerPrototype: SpawnPointSkia
|
||||
min: 1
|
||||
max: 1
|
||||
pickPlayer: false
|
||||
mindRoles:
|
||||
- MindRoleSkia
|
||||
components:
|
||||
- type: EmitSoundOnSpawn
|
||||
sound: /Audio/_DV/Effects/creepyshriek.ogg
|
||||
- type: BreakLightsOnSpawn
|
||||
radius: 10
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
- type: entity
|
||||
parent: BaseObjective
|
||||
id: SkiaReapObjective
|
||||
name: Reap the damned soul.
|
||||
description: Ensure this soul is released from its body at least once.
|
||||
components:
|
||||
- type: Objective
|
||||
difficulty: 1.5
|
||||
issuer: objective-issuer-skia
|
||||
unique: false
|
||||
icon:
|
||||
sprite: Mobs/Ghosts/ghost_human.rsi
|
||||
state: icon
|
||||
- type: RoleRequirement
|
||||
roles:
|
||||
- SkiaRole
|
||||
- type: KillPersonCondition
|
||||
requireDead: true
|
||||
- type: TargetObjective
|
||||
title: objective-condition-reap-soul-title
|
||||
- type: PickRandomPerson
|
||||
onlyChoosableJobs: true
|
||||
- type: RerollAfterCompletion
|
||||
rerollObjectivePrototype: SkiaReapObjective
|
||||
rerollObjectiveMessage: objective-condition-reap-soul-reroll-message
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
- type: constructionGraph
|
||||
id: SkiaHoodie
|
||||
start: start
|
||||
graph:
|
||||
- node: start
|
||||
edges:
|
||||
- to: skiaHoodie
|
||||
steps:
|
||||
- tag: Hoodie
|
||||
name: construction-graph-tag-hoodie
|
||||
icon:
|
||||
sprite: Clothing/OuterClothing/Misc/chaplain_hoodie.rsi
|
||||
state: icon
|
||||
- tag: Ectoplasm
|
||||
name: construction-graph-tag-ectoplasm
|
||||
icon:
|
||||
sprite: _DV/Mobs/Ghosts/revenant.rsi
|
||||
state: ectoplasm
|
||||
doAfter: 10
|
||||
- node: skiaHoodie
|
||||
entity: ClothingOuterHoodieSkia
|
||||
|
|
@ -45,3 +45,11 @@
|
|||
targetNode: sharkminnowShoes
|
||||
category: construction-category-clothing
|
||||
objectType: Item
|
||||
|
||||
- type: construction
|
||||
id: SkiaHoodie
|
||||
graph: SkiaHoodie
|
||||
startNode: start
|
||||
targetNode: skiaHoodie
|
||||
category: construction-category-clothing
|
||||
objectType: Item
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
jumpsuit: ClothingUniformJumpsuitOperative
|
||||
back: ClothingBackpackDuffelSyndicate
|
||||
mask: ClothingMaskGasSyndicate
|
||||
eyes: ClothingEyesHudSyndicate
|
||||
eyes: ClothingEyesNightVisionGogglesNukie # Goobstation
|
||||
ears: ClothingHeadsetAltSyndicate
|
||||
gloves: ClothingHandsGlovesCombat
|
||||
outerClothing: ClothingOuterVestWeb
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
- type: antag
|
||||
id: Skia
|
||||
name: roles-antag-skia-name
|
||||
objective: roles-antag-skia-objective
|
||||
antagonist: true
|
||||
requirements:
|
||||
- !type:OverallPlaytimeRequirement
|
||||
time: 172800 # 48 hours overall
|
||||
|
|
@ -74,6 +74,19 @@
|
|||
- type: RoleBriefing
|
||||
briefing: skeleton-role-briefing
|
||||
|
||||
- type: entity
|
||||
parent: BaseMindRoleAntag
|
||||
id: MindRoleSkia
|
||||
name: Skia Role
|
||||
components:
|
||||
- type: MindRole
|
||||
roleType: TeamAntagonist
|
||||
antagPrototype: Skia
|
||||
exclusiveAntag: true
|
||||
- type: SkiaRole
|
||||
- type: RoleBriefing
|
||||
briefing: skia-role-briefing
|
||||
|
||||
- type: entity
|
||||
parent: BaseMindRoleAntag
|
||||
id: MindRoleArmsdealer
|
||||
|
|
|
|||
|
|
@ -173,3 +173,6 @@
|
|||
|
||||
- type: Tag
|
||||
id: CleaningGrenade
|
||||
|
||||
- type: Tag
|
||||
id: Hoodie
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
# SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
# SPDX-FileCopyrightText: 2025 PunishedJoe <PunishedJoeseph@proton.me>
|
||||
# SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
# SPDX-FileCopyrightText: 2025 SX_7 <sn1.test.preria.2002@gmail.com>
|
||||
# SPDX-FileCopyrightText: 2025 Ted Lukin <66275205+pheenty@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 pheenty <fedorlukin2006@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
## Dynamic
|
||||
- type: latheRecipePack # WD
|
||||
id: SpecOpsGoogles
|
||||
recipes:
|
||||
- ClothingEyesNightVisionGoggles
|
||||
- ClothingEyesGlassesThermal
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
# SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
- type: entity
|
||||
id: ToggleNightVision
|
||||
name: Toggle Night Vision
|
||||
description: Toggles night vision.
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
itemIconStyle: BigAction
|
||||
priority: -20
|
||||
useDelay: 1
|
||||
icon:
|
||||
sprite: _White/Clothing/Eyes/Goggles/nightvision.rsi
|
||||
state: icon
|
||||
- type: InstantAction
|
||||
event: !type:ToggleNightVisionEvent
|
||||
|
||||
- type: entity
|
||||
id: ToggleThermalVision
|
||||
name: Toggle Thermal Vision
|
||||
description: Toggles thermal vision.
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
itemIconStyle: BigAction
|
||||
priority: -20
|
||||
useDelay: 1
|
||||
icon:
|
||||
sprite: _White/Clothing/Eyes/Goggles/thermal.rsi
|
||||
state: icon
|
||||
- type: InstantAction
|
||||
event: !type:ToggleThermalVisionEvent
|
||||
|
||||
- type: entity
|
||||
id: PulseThermalVision
|
||||
parent: ToggleThermalVision
|
||||
name: Pulse Thermal Vision
|
||||
description: Activate thermal vision temporarily.
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
useDelay: 4
|
||||
- type: InstantAction
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
# SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 Armok <155400926+ARMOKS@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# Night Vision Goggles
|
||||
|
||||
- type: entity
|
||||
parent: ClothingEyesBase
|
||||
id: ClothingEyesNightVisionGoggles
|
||||
name: night vision goggles
|
||||
description: Now you can see in the dark!
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _White/Clothing/Eyes/Goggles/nightvision.rsi
|
||||
- type: Clothing
|
||||
sprite: _White/Clothing/Eyes/Goggles/nightvision.rsi
|
||||
- type: NightVision
|
||||
flashDurationMultiplier: 2
|
||||
isEquipment: true
|
||||
- type: IdentityBlocker
|
||||
|
||||
- type: entity
|
||||
parent: [ClothingEyesBase,BaseSyndicateContraband]
|
||||
id: ClothingEyesNightVisionGogglesSyndie
|
||||
name: syndicate night vision goggles
|
||||
description: A high-tech pair of night vision goggles. Has medical analysis technology.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _White/Clothing/Eyes/Goggles/snightvision.rsi
|
||||
- type: Clothing
|
||||
sprite: _White/Clothing/Eyes/Goggles/snightvision.rsi
|
||||
- type: NightVision
|
||||
flashDurationMultiplier: 2
|
||||
isEquipment: true
|
||||
- type: IdentityBlocker
|
||||
coverage: EYES
|
||||
- type: ShowHealthBars
|
||||
damageContainers:
|
||||
- Biological
|
||||
|
||||
- type: entity
|
||||
parent: ClothingEyesNightVisionGogglesSyndie
|
||||
id: ClothingEyesNightVisionGogglesNukie
|
||||
suffix: "NukeOps"
|
||||
components:
|
||||
- type: ShowSyndicateIcons
|
||||
|
||||
# Thermal Vision Goggles
|
||||
|
||||
- type: entity
|
||||
parent: ClothingEyesBase
|
||||
id: ClothingEyesGlassesThermal
|
||||
name: thermal vision goggles
|
||||
description: Now you can see everyone!
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _White/Clothing/Eyes/Goggles/thermal.rsi
|
||||
- type: Clothing
|
||||
sprite: _White/Clothing/Eyes/Goggles/thermal.rsi
|
||||
- type: ThermalVision
|
||||
flashDurationMultiplier: 2
|
||||
pulseTime: 2
|
||||
isEquipment: true
|
||||
toggleAction: PulseThermalVision
|
||||
- type: IdentityBlocker
|
||||
coverage: EYES
|
||||
|
||||
- type: entity
|
||||
parent: [ClothingEyesBase, BaseSyndicateContraband]
|
||||
id: ClothingEyesThermalVisionGogglesSyndie
|
||||
name: thermal vision goggles
|
||||
description: A high-tech pair of thermal goggles.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _White/Clothing/Eyes/Goggles/sthermals.rsi
|
||||
- type: Clothing
|
||||
sprite: _White/Clothing/Eyes/Goggles/sthermals.rsi
|
||||
- type: ThermalVision
|
||||
flashDurationMultiplier: 2
|
||||
isEquipment: true
|
||||
toggleAction: ToggleThermalVision
|
||||
- type: IdentityBlocker
|
||||
coverage: EYES
|
||||
|
||||
- type: entity
|
||||
parent: ClothingEyesThermalVisionGogglesSyndie
|
||||
id: ClothingEyesThermalVisionGogglesNukie
|
||||
suffix: "NukeOps"
|
||||
components:
|
||||
- type: ShowSyndicateIcons
|
||||
- type: ShowJobIcons
|
||||
- type: ShowMindShieldIcons
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
- type: latheRecipe
|
||||
id: ClothingEyesNightVisionGoggles
|
||||
result: ClothingEyesNightVisionGoggles
|
||||
completetime: 2
|
||||
materials:
|
||||
Steel: 200
|
||||
Glass: 100
|
||||
Silver: 100
|
||||
Gold: 100
|
||||
|
||||
- type: latheRecipe
|
||||
id: ClothingEyesGlassesThermal
|
||||
result: ClothingEyesGlassesThermal
|
||||
completetime: 2
|
||||
materials:
|
||||
Steel: 200
|
||||
Glass: 100
|
||||
Silver: 100
|
||||
Gold: 100
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 Aiden <aiden@djkraz.com>
|
||||
# SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 FaDeOkno <143940725+FaDeOkno@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 FaDeOkno <logkedr18@gmail.com>
|
||||
# SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# Tier 2
|
||||
|
||||
- type: technology
|
||||
id: NightVisionTech
|
||||
name: research-technology-night-vision
|
||||
icon:
|
||||
sprite: _White/Clothing/Eyes/Goggles/nightvision.rsi
|
||||
state: icon
|
||||
discipline: Experimental
|
||||
tier: 2
|
||||
cost: 10000
|
||||
recipeUnlocks:
|
||||
- ClothingEyesNightVisionGoggles
|
||||
technologyPrerequisites:
|
||||
- MagnetsTech
|
||||
|
||||
- type: technology
|
||||
id: ThermalVisionTech
|
||||
name: research-technology-thermal-vision
|
||||
icon:
|
||||
sprite: _White/Clothing/Eyes/Goggles/thermal.rsi
|
||||
state: icon
|
||||
discipline: Experimental
|
||||
tier: 2
|
||||
cost: 10000
|
||||
recipeUnlocks:
|
||||
- ClothingEyesGlassesThermal
|
||||
technologyPrerequisites:
|
||||
- MagnetsTech
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 Eris <eris@erisws.com>
|
||||
# SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
# SPDX-FileCopyrightText: 2025 SX-7 <sn1.test.preria.2002@gmail.com>
|
||||
# SPDX-FileCopyrightText: 2025 Spatison <137375981+Spatison@users.noreply.github.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
- type: shader
|
||||
id: NightVision
|
||||
kind: source
|
||||
path: "/Textures/_White/Shaders/nightvision.swsl"
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Created by TehFlaminTaco (github) for SS14",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "shatter-lights"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 760 B |
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Created by TehFlaminTaco (github) for SS14",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "technokinetic-pulse"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 934 B |
|
After Width: | Height: | Size: 482 B |
|
After Width: | Height: | Size: 820 B |
|
After Width: | Height: | Size: 819 B |
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Created by TehFlaminTaco (github) for SS14",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "equipped-HELMET",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 729 B |
|
After Width: | Height: | Size: 667 B |
|
After Width: | Height: | Size: 678 B |
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Created by TehFlaminTaco (github) for SS14",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "equipped-OUTERCLOTHING",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"version": 1,
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Created by TehFlaminTaco (github) for SS14",
|
||||
"states": [
|
||||
{
|
||||
"name": "skialunge"
|
||||
},
|
||||
{
|
||||
"name": "skiaswipe"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 609 B |
|
After Width: | Height: | Size: 661 B |
|
After Width: | Height: | Size: 898 B |
|
After Width: | Height: | Size: 859 B |
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"version": 1,
|
||||
"size": {
|
||||
"x": 24,
|
||||
"y": 24
|
||||
},
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Original art by by TehFlaminTaco (github) for ss14",
|
||||
"states": [
|
||||
{
|
||||
"name": "dark"
|
||||
},
|
||||
{
|
||||
"name": "neutral"
|
||||
},
|
||||
{
|
||||
"name": "bright"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 746 B |
|
After Width: | Height: | Size: 613 B |
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"version": 1,
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Original art by by TehFlaminTaco (github) for ss14",
|
||||
"states": [
|
||||
{
|
||||
"name": "skia",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "dead"
|
||||
}
|
||||
]
|
||||
}
|
||||