_parallaxManager.LoadDefaultParallax();
_overlayManager.AddOverlay(new SingularityOverlay());
- _overlayManager.AddOverlay(new FlashOverlay());
_overlayManager.AddOverlay(new RadiationPulseOverlay());
_chatManager.Initialize();
_clientPreferencesManager.Initialize();
-using System.Numerics;
+using Content.Shared.Flash;
+using Content.Shared.Flash.Components;
+using Content.Shared.StatusEffect;
using Content.Client.Viewport;
using Robust.Client.Graphics;
using Robust.Client.State;
using Robust.Client.Player;
using Robust.Shared.Enums;
-using Robust.Shared.Graphics;
-using Robust.Shared.IoC;
-using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using SixLabors.ImageSharp.PixelFormats;
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IClyde _displayManager = default!;
- [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ private readonly StatusEffectsSystem _statusSys;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _shader;
- private double _startTime = -1;
- private double _lastsFor = 1;
- private Texture? _screenshotTexture;
+ public float PercentComplete = 0.0f;
+ public Texture? ScreenshotTexture;
public FlashOverlay()
{
IoCManager.InjectDependencies(this);
- _shader = _prototypeManager.Index<ShaderPrototype>("FlashedEffect").Instance().Duplicate();
+ _shader = _prototypeManager.Index<ShaderPrototype>("FlashedEffect").InstanceUnique();
+ _statusSys = _entityManager.System<StatusEffectsSystem>();
}
- public void ReceiveFlash(double duration)
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ var playerEntity = _playerManager.LocalEntity;
+
+ if (playerEntity == null)
+ return;
+
+ if (!_entityManager.HasComponent<FlashedComponent>(playerEntity)
+ || !_entityManager.TryGetComponent<StatusEffectsComponent>(playerEntity, out var status))
+ return;
+
+ if (!_statusSys.TryGetTime(playerEntity.Value, SharedFlashSystem.FlashedKey, out var time, status))
+ return;
+
+ var curTime = _timing.CurTime;
+ var lastsFor = (float) (time.Value.Item2 - time.Value.Item1).TotalSeconds;
+ var timeDone = (float) (curTime - time.Value.Item1).TotalSeconds;
+
+ PercentComplete = timeDone / lastsFor;
+ }
+
+ public void ReceiveFlash()
{
if (_stateManager.CurrentState is IMainViewportState state)
{
+ // take a screenshot
+ // note that the callback takes a while and ScreenshotTexture will be null the first few Draws
state.Viewport.Viewport.Screenshot(image =>
{
var rgba32Image = image.CloneAs<Rgba32>(SixLabors.ImageSharp.Configuration.Default);
- _screenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image);
+ ScreenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image);
});
}
-
- _startTime = _gameTiming.CurTime.TotalSeconds;
- _lastsFor = duration;
}
- protected override void Draw(in OverlayDrawArgs args)
+ protected override bool BeforeDraw(in OverlayDrawArgs args)
{
if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp))
- return;
-
+ return false;
if (args.Viewport.Eye != eyeComp.Eye)
- return;
+ return false;
+
+ return PercentComplete < 1.0f;
+ }
- var percentComplete = (float) ((_gameTiming.CurTime.TotalSeconds - _startTime) / _lastsFor);
- if (percentComplete >= 1.0f)
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ if (ScreenshotTexture == null)
return;
var worldHandle = args.WorldHandle;
+ _shader.SetParameter("percentComplete", PercentComplete);
worldHandle.UseShader(_shader);
- _shader.SetParameter("percentComplete", percentComplete);
-
- if (_screenshotTexture != null)
- {
- worldHandle.DrawTextureRectRegion(_screenshotTexture, args.WorldBounds);
- }
-
+ worldHandle.DrawTextureRectRegion(ScreenshotTexture, args.WorldBounds);
worldHandle.UseShader(null);
}
protected override void DisposeBehavior()
{
base.DisposeBehavior();
- _screenshotTexture = null;
+ ScreenshotTexture = null;
+ PercentComplete = 1.0f;
}
}
}
using Content.Shared.Flash;
+using Content.Shared.Flash.Components;
+using Content.Shared.StatusEffect;
using Robust.Client.Graphics;
using Robust.Client.Player;
-using Robust.Shared.GameStates;
-using Robust.Shared.Timing;
+using Robust.Shared.Player;
-namespace Content.Client.Flash
+namespace Content.Client.Flash;
+
+public sealed class FlashSystem : SharedFlashSystem
{
- public sealed class FlashSystem : SharedFlashSystem
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] private readonly IOverlayManager _overlayMan = default!;
+
+ private FlashOverlay _overlay = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<FlashedComponent, ComponentInit>(OnInit);
+ SubscribeLocalEvent<FlashedComponent, ComponentShutdown>(OnShutdown);
+ SubscribeLocalEvent<FlashedComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
+ SubscribeLocalEvent<FlashedComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
+ SubscribeLocalEvent<FlashedComponent, StatusEffectAddedEvent>(OnStatusAdded);
+
+ _overlay = new();
+ }
+
+ private void OnPlayerAttached(EntityUid uid, FlashedComponent component, LocalPlayerAttachedEvent args)
{
- [Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IOverlayManager _overlayManager = default!;
+ _overlayMan.AddOverlay(_overlay);
+ }
- public override void Initialize()
+ private void OnPlayerDetached(EntityUid uid, FlashedComponent component, LocalPlayerDetachedEvent args)
+ {
+ _overlay.PercentComplete = 1.0f;
+ _overlay.ScreenshotTexture = null;
+ _overlayMan.RemoveOverlay(_overlay);
+ }
+
+ private void OnInit(EntityUid uid, FlashedComponent component, ComponentInit args)
+ {
+ if (_player.LocalEntity == uid)
{
- base.Initialize();
+ _overlayMan.AddOverlay(_overlay);
+ }
+ }
- SubscribeLocalEvent<FlashableComponent, ComponentHandleState>(OnFlashableHandleState);
+ private void OnShutdown(EntityUid uid, FlashedComponent component, ComponentShutdown args)
+ {
+ if (_player.LocalEntity == uid)
+ {
+ _overlay.PercentComplete = 1.0f;
+ _overlay.ScreenshotTexture = null;
+ _overlayMan.RemoveOverlay(_overlay);
}
+ }
- private void OnFlashableHandleState(EntityUid uid, FlashableComponent component, ref ComponentHandleState args)
+ private void OnStatusAdded(EntityUid uid, FlashedComponent component, StatusEffectAddedEvent args)
+ {
+ if (_player.LocalEntity == uid && args.Key == FlashedKey)
{
- if (args.Current is not FlashableComponentState state)
- return;
-
- // Yes, this code is awful. I'm just porting it to an entity system so don't blame me.
- if (_playerManager.LocalEntity != uid)
- {
- return;
- }
-
- if (state.Time == default)
- {
- return;
- }
-
- // Few things here:
- // 1. If a shorter duration flash is applied then don't do anything
- // 2. If the client-side time is later than when the flash should've ended don't do anything
- var currentTime = _gameTiming.CurTime.TotalSeconds;
- var newEndTime = state.Time.TotalSeconds + state.Duration;
- var currentEndTime = component.LastFlash.TotalSeconds + component.Duration;
-
- if (currentEndTime > newEndTime)
- {
- return;
- }
-
- if (currentTime > newEndTime)
- {
- return;
- }
-
- component.LastFlash = state.Time;
- component.Duration = state.Duration;
-
- var overlay = _overlayManager.GetOverlay<FlashOverlay>();
- overlay.ReceiveFlash(component.Duration);
+ _overlay.ReceiveFlash();
}
}
}
namespace Content.Server.Flash.Components;
-// Also needed FlashableComponent on entity to work
[RegisterComponent, Access(typeof(DamagedByFlashingSystem))]
public sealed partial class DamagedByFlashingComponent : Component
{
/// damage from flashing
/// </summary>
[DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
- public DamageSpecifier FlashDamage = new ();
+ public DamageSpecifier FlashDamage = new();
}
-namespace Content.Server.Flash.Components
+namespace Content.Server.Flash.Components;
+
+/// <summary>
+/// Makes the entity immune to being flashed.
+/// When given to clothes in the "head", "eyes" or "mask" slot it protects the wearer.
+/// </summary>
+[RegisterComponent, Access(typeof(FlashSystem))]
+public sealed partial class FlashImmunityComponent : Component
{
- [RegisterComponent, Access(typeof(FlashSystem))]
- public sealed partial class FlashImmunityComponent : Component
- {
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("enabled")]
- public bool Enabled { get; set; } = true;
- }
+ [ViewVariables(VVAccess.ReadWrite)]
+ [DataField("enabled")]
+ public bool Enabled { get; set; } = true;
}
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.Flash;
using Content.Shared.IdentityManagement;
-using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
-using Content.Shared.Physics;
using Content.Shared.Tag;
using Content.Shared.Traits.Assorted;
using Content.Shared.Weapons.Melee.Events;
+using Content.Shared.StatusEffect;
+using Content.Shared.Examine;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Random;
-using Robust.Shared.Timing;
using InventoryComponent = Content.Shared.Inventory.InventoryComponent;
namespace Content.Server.Flash
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly SharedChargesSystem _charges = default!;
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
- [Dependency] private readonly SharedInteractionSystem _interaction = default!;
+ [Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly StunSystem _stun = default!;
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
public override void Initialize()
{
SubscribeLocalEvent<FlashComponent, MeleeHitEvent>(OnFlashMeleeHit);
// ran before toggling light for extra-bright lantern
- SubscribeLocalEvent<FlashComponent, UseInHandEvent>(OnFlashUseInHand, before: new []{ typeof(HandheldLightSystem) });
+ SubscribeLocalEvent<FlashComponent, UseInHandEvent>(OnFlashUseInHand, before: new[] { typeof(HandheldLightSystem) });
SubscribeLocalEvent<InventoryComponent, FlashAttemptEvent>(OnInventoryFlashAttempt);
SubscribeLocalEvent<FlashImmunityComponent, FlashAttemptEvent>(OnFlashImmunityFlashAttempt);
SubscribeLocalEvent<PermanentBlindnessComponent, FlashAttemptEvent>(OnPermanentBlindnessFlashAttempt);
float flashDuration,
float slowTo,
bool displayPopup = true,
- FlashableComponent? flashable = null,
bool melee = false,
TimeSpan? stunDuration = null)
{
- if (!Resolve(target, ref flashable, false))
- return;
-
var attempt = new FlashAttemptEvent(target, user, used);
RaiseLocalEvent(target, attempt, true);
if (attempt.Cancelled)
return;
- if (melee)
- {
- var ev = new AfterFlashedEvent(target, user, used);
- if (user != null)
- RaiseLocalEvent(user.Value, ref ev);
- if (used != null)
- RaiseLocalEvent(used.Value, ref ev);
- }
-
- flashable.LastFlash = _timing.CurTime;
- flashable.Duration = flashDuration / 1000f; // TODO: Make this sane...
- Dirty(target, flashable);
+ // 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))
+ return;
if (stunDuration != null)
{
}
else
{
- _stun.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration/1000f), true,
+ _stun.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration / 1000f), true,
slowTo, slowTo);
}
_popup.PopupEntity(Loc.GetString("flash-component-user-blinds-you",
("user", Identity.Entity(user.Value, EntityManager))), target, target);
}
+
+ if (melee)
+ {
+ var ev = new AfterFlashedEvent(target, user, used);
+ if (user != null)
+ RaiseLocalEvent(user.Value, ref ev);
+ if (used != null)
+ RaiseLocalEvent(used.Value, ref ev);
+ }
}
public void FlashArea(Entity<FlashComponent?> source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, float probability = 1f, SoundSpecifier? sound = null)
{
var transform = Transform(source);
var mapPosition = _transform.GetMapCoordinates(transform);
- var flashableQuery = GetEntityQuery<FlashableComponent>();
+ var statusEffectsQuery = GetEntityQuery<StatusEffectsComponent>();
+ var damagedByFlashingQuery = GetEntityQuery<DamagedByFlashingComponent>();
foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, range))
{
if (!_random.Prob(probability))
continue;
- if (!flashableQuery.TryGetComponent(entity, out var flashable))
+ // Is the entity affected by the flash either through status effects or by taking damage?
+ if (!statusEffectsQuery.HasComponent(entity) && !damagedByFlashingQuery.HasComponent(entity))
continue;
- // Check for unobstructed entities while ignoring the mobs with flashable components.
- if (!_interaction.InRangeUnobstructed(entity, mapPosition, range, flashable.CollisionGroup, predicate: (e) => flashableQuery.HasComponent(e) || e == source.Owner))
+ // Check for entites in view
+ // put damagedByFlashingComponent in the predicate because shadow anomalies block vision.
+ if (!_examine.InRangeUnOccluded(entity, mapPosition, range, predicate: (e) => damagedByFlashingQuery.HasComponent(e)))
continue;
// They shouldn't have flash removed in between right?
- Flash(entity, user, source, duration, slowTo, displayPopup, flashableQuery.GetComponent(entity));
+ Flash(entity, user, source, duration, slowTo, displayPopup);
}
_audio.PlayPvs(sound, source, AudioParams.Default.WithVolume(1f).WithMaxDistance(3f));
private void OnFlashImmunityFlashAttempt(EntityUid uid, FlashImmunityComponent component, FlashAttemptEvent args)
{
- if(component.Enabled)
+ if (component.Enabled)
args.Cancel();
}
private void OnPermanentBlindnessFlashAttempt(EntityUid uid, PermanentBlindnessComponent component, FlashAttemptEvent args)
{
- args.Cancel();
+ // check for total blindness
+ if (component.Blindness == 0)
+ args.Cancel();
}
private void OnTemporaryBlindnessFlashAttempt(EntityUid uid, TemporaryBlindnessComponent component, FlashAttemptEvent args)
}
}
+ /// <summary>
+ /// Called before a flash is used to check if the attempt is cancelled by blindness, items or FlashImmunityComponent.
+ /// Raised on the target hit by the flash, the user of the flash and the flash used.
+ /// </summary>
public sealed class FlashAttemptEvent : CancellableEntityEventArgs
{
public readonly EntityUid Target;
}
}
/// <summary>
- /// Called after a flash is used via melee on another person to check for rev conversion.
- /// Raised on the user of the flash, the target hit by the flash, and the flash used.
+ /// Called after a flash is used via melee on another person to check for rev conversion.
+ /// Raised on the target hit by the flash, the user of the flash and the flash used.
/// </summary>
[ByRefEvent]
public readonly struct AfterFlashedEvent
Used = used;
}
}
-
-
}
-using Content.Shared.Flash;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
namespace Content.Shared.Flash.Components
{
[DataField]
public float Probability = 1f;
}
+
+ [Serializable, NetSerializable]
+ public enum FlashVisuals : byte
+ {
+ BaseLayer,
+ LightLayer,
+ Burnt,
+ Flashing,
+ }
}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Flash.Components;
+
+/// <summary>
+/// Exists for use as a status effect. Adds a shader to the client that obstructs vision.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class FlashedComponent : Component { }
+++ /dev/null
-using Content.Shared.Physics;
-using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Flash
-{
- [RegisterComponent, NetworkedComponent]
- public sealed partial class FlashableComponent : Component
- {
- public float Duration;
- public TimeSpan LastFlash;
-
- [DataField]
- public CollisionGroup CollisionGroup = CollisionGroup.Opaque;
-
- public override bool SendOnlyToOwner => true;
- }
-
- [Serializable, NetSerializable]
- public sealed class FlashableComponentState : ComponentState
- {
- public float Duration { get; }
- public TimeSpan Time { get; }
-
- public FlashableComponentState(float duration, TimeSpan time)
- {
- Duration = duration;
- Time = time;
- }
- }
-
- [Serializable, NetSerializable]
- public enum FlashVisuals : byte
- {
- BaseLayer,
- LightLayer,
- Burnt,
- Flashing,
- }
-}
-using Robust.Shared.GameStates;
+using Content.Shared.StatusEffect;
namespace Content.Shared.Flash
{
public abstract class SharedFlashSystem : EntitySystem
{
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<FlashableComponent, ComponentGetState>(OnFlashableGetState);
- }
-
- private static void OnFlashableGetState(EntityUid uid, FlashableComponent component, ref ComponentGetState args)
- {
- args.State = new FlashableComponentState(component.Duration, component.LastFlash);
- }
+ [ValidatePrototypeId<StatusEffectPrototype>]
+ public const string FlashedKey = "Flashed";
}
}
- Stun
- KnockedDown
- SlowedDown
+ - Flashed
- type: TypingIndicator
proto: robot
- type: Speech
locked: true
- type: ActivatableUIRequiresLock
- type: LockedWiresPanel
- - type: Flashable
- type: Damageable
damageContainer: Silicon
- type: Destructible
sprite: Mobs/Effects/onfire.rsi
normalState: Generic_mob_burning
- type: Climbing
- - type: Flashable
- type: NameIdentifier
group: GenericNumber
amount: 3
- id: DrinkTequilaBottleFull
amount: 1
- - type: Flashable
- type: Tag
tags:
- CannotSuicide
# name: ghost-role-information-tropico-name
# description: ghost-role-information-tropico-description
# - type: GhostTakeoverAvailable
-# - type: Flashable
- type: Tag
tags:
- VimPilot
- ForcedSleep
- TemporaryBlindness
- Pacified
+ - Flashed
- type: Buckle
- type: StandingState
- type: Tag
- TemporaryBlindness
- Pacified
- StaminaModifier
+ - Flashed
- type: Bloodstream
bloodMaxVolume: 150
- type: MobPrice
- TemporaryBlindness
- Pacified
- StaminaModifier
+ - Flashed
- type: Reflect
enabled: false
reflectProb: 0
id: BaseMobSpeciesOrganic
abstract: true
components:
- - type: Flashable
- type: Barotrauma
damage:
types:
Heat: -0.07
groups:
Brute: -0.07
- # Organs
- - type: StatusEffects
- allowed:
- - Stun
- - KnockedDown
- - SlowedDown
- - Stutter
- - SeeingRainbows
- - Electrocution
- - ForcedSleep
- - TemporaryBlindness
- - Drunk
- - SlurredSpeech
- - RatvarianLanguage
- - PressureImmunity
- - Muted
- - Pacified
- - StaminaModifier
+
- type: Blindable
# Other
- type: Temperature
path: /Audio/Items/hiss.ogg
params:
variation: 0.08
- - type: Flashable
- collisionGroup:
- - None
- type: DamagedByFlashing
flashDamage:
types:
- type: statusEffect
id: StaminaModifier
+
+- type: statusEffect
+ id: Flashed
highp vec4 textureMix = mix(tex1, tex2, 0.5);
- // Gradually mixes between the texture mix and a full-white texture, causing the "blinding" effect
+ // Gradually mixes between the texture mix and a full-black texture, causing the "blinding" effect
highp vec4 mixed = mix(vec4(0.0, 0.0, 0.0, 1.0), textureMix, percentComplete);
COLOR = vec4(mixed.rgb, remaining);