]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Predict Flashes (#37640)
authorslarticodefast <161409025+slarticodefast@users.noreply.github.com>
Mon, 23 Jun 2025 11:32:56 +0000 (13:32 +0200)
committerGitHub <noreply@github.com>
Mon, 23 Jun 2025 11:32:56 +0000 (13:32 +0200)
Co-authored-by: ScarKy0 <scarky0@onet.eu>
23 files changed:
Content.Client/Flash/FlashSystem.cs
Content.Server/EntityEffects/EntityEffectSystem.cs
Content.Server/Explosion/EntitySystems/TriggerSystem.cs
Content.Server/Flash/Components/DamagedByFlashingComponent.cs [deleted file]
Content.Server/Flash/Components/FlashImmunityComponent.cs [deleted file]
Content.Server/Flash/FlashSystem.cs
Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs
Content.Shared/EntityEffects/Effects/FlashReactionEffect.cs
Content.Shared/Flash/Components/ActiveFlashComponent.cs [new file with mode: 0644]
Content.Shared/Flash/Components/DamagedByFlashingComponent.cs [new file with mode: 0644]
Content.Shared/Flash/Components/FlashComponent.cs
Content.Shared/Flash/Components/FlashImmunityComponent.cs [new file with mode: 0644]
Content.Shared/Flash/Components/FlashOnTriggerComponent.cs
Content.Shared/Flash/Components/FlashedComponent.cs
Content.Shared/Flash/DamagedByFlashingSystem.cs [moved from Content.Server/Flash/DamagedByFlashingSystem.cs with 53% similarity]
Content.Shared/Flash/FlashEvents.cs [new file with mode: 0644]
Content.Shared/Flash/FlashVisuals.cs [new file with mode: 0644]
Content.Shared/Flash/SharedFlashSystem.cs
Content.Shared/Inventory/InventorySystem.Relay.cs
Content.Shared/Light/SharedHandheldLightSystem.cs
Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml
Resources/Prototypes/Entities/Objects/Tools/lantern.yml
Resources/Prototypes/Entities/Objects/Weapons/security.yml

index 146d84b990f5562552e4aebcfbcfa00e988a4577..ffabab945393d2666ab9ee22ecfd771ceac62864 100644 (file)
@@ -1,6 +1,5 @@
 using Content.Shared.Flash;
 using Content.Shared.Flash.Components;
-using Content.Shared.StatusEffect;
 using Robust.Client.Graphics;
 using Robust.Client.Player;
 using Robust.Shared.Player;
index 03a0c8bb2b7d89784932d7fdf146ade179007e15..49ed9866d4e58c4c30345639da6cd233e0a704f1 100644 (file)
@@ -10,7 +10,6 @@ using Content.Server.Botany;
 using Content.Server.Chat.Systems;
 using Content.Server.Emp;
 using Content.Server.Explosion.EntitySystems;
-using Content.Server.Flash;
 using Content.Server.Fluids.EntitySystems;
 using Content.Server.Ghost.Roles.Components;
 using Content.Server.Medical;
@@ -23,13 +22,12 @@ using Content.Server.Temperature.Systems;
 using Content.Server.Traits.Assorted;
 using Content.Server.Zombies;
 using Content.Shared.Atmos;
-using Content.Shared.Audio;
 using Content.Shared.Coordinates.Helpers;
 using Content.Shared.EntityEffects.EffectConditions;
 using Content.Shared.EntityEffects.Effects.PlantMetabolism;
-using Content.Shared.EntityEffects.Effects.StatusEffects;
 using Content.Shared.EntityEffects.Effects;
 using Content.Shared.EntityEffects;
+using Content.Shared.Flash;
 using Content.Shared.Maps;
 using Content.Shared.Mind.Components;
 using Content.Shared.Popups;
@@ -38,7 +36,6 @@ using Content.Shared.Zombies;
 using Robust.Server.GameObjects;
 using Robust.Shared.Audio;
 using Robust.Shared.Audio.Systems;
-using Robust.Shared.GameObjects;
 using Robust.Shared.Map;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
@@ -56,7 +53,7 @@ public sealed class EntityEffectSystem : EntitySystem
     [Dependency] private readonly EmpSystem _emp = default!;
     [Dependency] private readonly ExplosionSystem _explosion = default!;
     [Dependency] private readonly FlammableSystem _flammable = default!;
-    [Dependency] private readonly FlashSystem _flash = default!;
+    [Dependency] private readonly SharedFlashSystem _flash = default!;
     [Dependency] private readonly IMapManager _mapManager = default!;
     [Dependency] private readonly IPrototypeManager _protoManager = default!;
     [Dependency] private readonly IRobustRandom _random = default!;
@@ -711,7 +708,7 @@ public sealed class EntityEffectSystem : EntitySystem
             args.Args.TargetEntity,
             null,
             range,
-            args.Effect.Duration * 1000,
+            args.Effect.Duration,
             slowTo: args.Effect.SlowTo,
             sound: args.Effect.Sound);
 
index 894408d275e928a861155f46b0ea2deba5d114f2..f052eadbd54d767d23d2857bdc93d2e2cd4ad4c7 100644 (file)
@@ -1,7 +1,7 @@
 using Content.Server.Administration.Logs;
 using Content.Server.Body.Systems;
 using Content.Server.Explosion.Components;
-using Content.Server.Flash;
+using Content.Shared.Flash;
 using Content.Server.Electrocution;
 using Content.Server.Pinpointer;
 using Content.Shared.Chemistry.EntitySystems;
@@ -69,7 +69,7 @@ namespace Content.Server.Explosion.EntitySystems
     {
         [Dependency] private readonly ExplosionSystem _explosions = default!;
         [Dependency] private readonly FixtureSystem _fixtures = default!;
-        [Dependency] private readonly FlashSystem _flashSystem = default!;
+        [Dependency] private readonly SharedFlashSystem _flashSystem = default!;
         [Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
         [Dependency] private readonly IAdminLogManager _adminLogger = default!;
         [Dependency] private readonly SharedContainerSystem _container = default!;
@@ -196,8 +196,7 @@ namespace Content.Server.Explosion.EntitySystems
 
         private void HandleFlashTrigger(EntityUid uid, FlashOnTriggerComponent component, TriggerEvent args)
         {
-            // TODO Make flash durations sane ffs.
-            _flashSystem.FlashArea(uid, args.User, component.Range, component.Duration * 1000f, probability: component.Probability);
+            _flashSystem.FlashArea(uid, args.User, component.Range, component.Duration, probability: component.Probability);
             args.Handled = true;
         }
 
diff --git a/Content.Server/Flash/Components/DamagedByFlashingComponent.cs b/Content.Server/Flash/Components/DamagedByFlashingComponent.cs
deleted file mode 100644 (file)
index ef33454..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-using Content.Shared.Damage;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Flash.Components;
-
-[RegisterComponent, Access(typeof(DamagedByFlashingSystem))]
-public sealed partial class DamagedByFlashingComponent : Component
-{
-    /// <summary>
-    /// damage from flashing
-    /// </summary>
-    [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
-    public DamageSpecifier FlashDamage = new();
-}
diff --git a/Content.Server/Flash/Components/FlashImmunityComponent.cs b/Content.Server/Flash/Components/FlashImmunityComponent.cs
deleted file mode 100644 (file)
index a982a90..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-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
-{
-    [ViewVariables(VVAccess.ReadWrite)]
-    [DataField("enabled")]
-    public bool Enabled { get; set; } = true;
-}
index 30c6491f62b6a272f1e4fb2934b733056f5d83c1..14549c54d9a49054313fd19df4c13d08c25ccffa 100644 (file)
@@ -1,257 +1,5 @@
-using System.Linq;
-using Content.Server.Flash.Components;
-using Content.Shared.Flash.Components;
-using Content.Server.Light.EntitySystems;
-using Content.Server.Popups;
-using Content.Server.Stunnable;
-using Content.Shared.Charges.Components;
-using Content.Shared.Charges.Systems;
-using Content.Shared.Eye.Blinding.Components;
 using Content.Shared.Flash;
-using Content.Shared.IdentityManagement;
-using Content.Shared.Interaction.Events;
-using Content.Shared.Inventory;
-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 InventoryComponent = Content.Shared.Inventory.InventoryComponent;
-using Robust.Shared.Prototypes;
 
-namespace Content.Server.Flash
-{
-    internal sealed class FlashSystem : SharedFlashSystem
-    {
-        [Dependency] private readonly AppearanceSystem _appearance = default!;
-        [Dependency] private readonly AudioSystem _audio = default!;
-        [Dependency] private readonly SharedChargesSystem _sharedCharges = default!;
-        [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
-        [Dependency] private readonly SharedTransformSystem _transform = 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!;
+namespace Content.Server.Flash;
 
-        private static readonly ProtoId<TagPrototype> TrashTag = "Trash";
-
-        public override void Initialize()
-        {
-            base.Initialize();
-            SubscribeLocalEvent<FlashImmunityComponent, ExaminedEvent>(OnExamine);
-            SubscribeLocalEvent<FlashComponent, MeleeHitEvent>(OnFlashMeleeHit);
-            // ran before toggling light for extra-bright lantern
-            SubscribeLocalEvent<FlashComponent, UseInHandEvent>(OnFlashUseInHand, before: new[] { typeof(HandheldLightSystem) });
-            SubscribeLocalEvent<InventoryComponent, FlashAttemptEvent>(OnInventoryFlashAttempt);
-            SubscribeLocalEvent<FlashImmunityComponent, FlashAttemptEvent>(OnFlashImmunityFlashAttempt);
-            SubscribeLocalEvent<PermanentBlindnessComponent, FlashAttemptEvent>(OnPermanentBlindnessFlashAttempt);
-            SubscribeLocalEvent<TemporaryBlindnessComponent, FlashAttemptEvent>(OnTemporaryBlindnessFlashAttempt);
-        }
-        
-        private void OnExamine(Entity<FlashImmunityComponent> ent, ref ExaminedEvent args)
-
-        {
-            args.PushMarkup(Loc.GetString("flash-protection"));
-        }
-        
-        private void OnFlashMeleeHit(EntityUid uid, FlashComponent comp, MeleeHitEvent args)
-        {
-            if (!args.IsHit ||
-                !args.HitEntities.Any() ||
-                !UseFlash(uid, comp, args.User))
-            {
-                return;
-            }
-
-            args.Handled = true;
-            foreach (var e in args.HitEntities)
-            {
-                Flash(e, args.User, uid, comp.FlashDuration, comp.SlowTo, melee: true, stunDuration: comp.MeleeStunDuration);
-            }
-        }
-
-        private void OnFlashUseInHand(EntityUid uid, FlashComponent comp, UseInHandEvent args)
-        {
-            if (args.Handled || !UseFlash(uid, comp, args.User))
-                return;
-
-            args.Handled = true;
-            FlashArea(uid, args.User, comp.Range, comp.AoeFlashDuration, comp.SlowTo, true, comp.Probability);
-        }
-
-        private bool UseFlash(EntityUid uid, FlashComponent comp, EntityUid user)
-        {
-            if (comp.Flashing)
-                return false;
-
-            TryComp<LimitedChargesComponent>(uid, out var charges);
-            if (_sharedCharges.IsEmpty((uid, charges)))
-                return false;
-
-            _sharedCharges.TryUseCharge((uid, charges));
-            _audio.PlayPvs(comp.Sound, uid);
-            comp.Flashing = true;
-            _appearance.SetData(uid, FlashVisuals.Flashing, true);
-
-            if (_sharedCharges.IsEmpty((uid, charges)))
-            {
-                _appearance.SetData(uid, FlashVisuals.Burnt, true);
-                _tag.AddTag(uid, TrashTag);
-                _popup.PopupEntity(Loc.GetString("flash-component-becomes-empty"), user);
-            }
-
-            uid.SpawnTimer(400, () =>
-            {
-                _appearance.SetData(uid, FlashVisuals.Flashing, false);
-                comp.Flashing = false;
-            });
-
-            return true;
-        }
-
-        public void Flash(EntityUid target,
-            EntityUid? user,
-            EntityUid? used,
-            float flashDuration,
-            float slowTo,
-            bool displayPopup = true,
-            bool melee = false,
-            TimeSpan? stunDuration = null)
-        {
-            var attempt = new FlashAttemptEvent(target, user, used);
-            RaiseLocalEvent(target, attempt, true);
-
-            if (attempt.Cancelled)
-                return;
-
-            // 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)
-            {
-                _stun.TryParalyze(target, stunDuration.Value, true);
-            }
-            else
-            {
-                _stun.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration / 1000f), true,
-                slowTo, slowTo);
-            }
-
-            if (displayPopup && user != null && target != user && Exists(user.Value))
-            {
-                _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 override 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 statusEffectsQuery = GetEntityQuery<StatusEffectsComponent>();
-            var damagedByFlashingQuery = GetEntityQuery<DamagedByFlashingComponent>();
-
-            foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, range))
-            {
-                if (!_random.Prob(probability))
-                    continue;
-
-                // 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 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);
-            }
-
-            _audio.PlayPvs(sound, source, AudioParams.Default.WithVolume(1f).WithMaxDistance(3f));
-        }
-
-        private void OnInventoryFlashAttempt(EntityUid uid, InventoryComponent component, FlashAttemptEvent args)
-        {
-            foreach (var slot in new[] { "head", "eyes", "mask" })
-            {
-                if (args.Cancelled)
-                    break;
-                if (_inventory.TryGetSlotEntity(uid, slot, out var item, component))
-                    RaiseLocalEvent(item.Value, args, true);
-            }
-        }
-
-        private void OnFlashImmunityFlashAttempt(EntityUid uid, FlashImmunityComponent component, FlashAttemptEvent args)
-        {
-            if (component.Enabled)
-                args.Cancel();
-        }
-
-        private void OnPermanentBlindnessFlashAttempt(EntityUid uid, PermanentBlindnessComponent component, FlashAttemptEvent args)
-        {
-            // check for total blindness
-            if (component.Blindness == 0)
-                args.Cancel();
-        }
-
-        private void OnTemporaryBlindnessFlashAttempt(EntityUid uid, TemporaryBlindnessComponent component, FlashAttemptEvent args)
-        {
-            args.Cancel();
-        }
-    }
-
-    /// <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;
-        public readonly EntityUid? User;
-        public readonly EntityUid? Used;
-
-        public FlashAttemptEvent(EntityUid target, EntityUid? user, EntityUid? used)
-        {
-            Target = target;
-            User = user;
-            Used = used;
-        }
-    }
-    /// <summary>
-    ///     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
-    {
-        public readonly EntityUid Target;
-        public readonly EntityUid? User;
-        public readonly EntityUid? Used;
-
-        public AfterFlashedEvent(EntityUid target, EntityUid? user, EntityUid? used)
-        {
-            Target = target;
-            User = user;
-            Used = used;
-        }
-    }
-}
+public sealed class FlashSystem : SharedFlashSystem;
index e6485a723fbcecbe9a21103102ed783a958a8ad5..d7a548bf0fff3843e7f677b323bf15f25b15c3da 100644 (file)
@@ -1,7 +1,6 @@
 using Content.Server.Administration.Logs;
 using Content.Server.Antag;
 using Content.Server.EUI;
-using Content.Server.Flash;
 using Content.Server.GameTicking.Rules.Components;
 using Content.Server.Mind;
 using Content.Server.Popups;
@@ -12,6 +11,7 @@ using Content.Server.RoundEnd;
 using Content.Server.Shuttles.Systems;
 using Content.Server.Station.Systems;
 using Content.Shared.Database;
+using Content.Shared.Flash;
 using Content.Shared.GameTicking.Components;
 using Content.Shared.Humanoid;
 using Content.Shared.IdentityManagement;
@@ -131,6 +131,9 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
     /// </summary>
     private void OnPostFlash(EntityUid uid, HeadRevolutionaryComponent comp, ref AfterFlashedEvent ev)
     {
+        if (uid != ev.User || !ev.Melee)
+            return;
+
         var alwaysConvertible = HasComp<AlwaysRevolutionaryConvertibleComponent>(ev.Target);
 
         if (!_mind.TryGetMind(ev.Target, out var mindId, out var mind) && !alwaysConvertible)
index 5cd7f06c5260a4de5910d276fa8c914954682059..c238e9401063a55c54388f090b7ba80d7e7487d6 100644 (file)
@@ -25,11 +25,11 @@ public sealed partial class FlashReactionEffect : EventEntityEffect<FlashReactio
     public float SlowTo = 0.5f;
 
     /// <summary>
-    ///     The time entities will be flashed in seconds.
+    ///     The time entities will be flashed.
     ///     The default is chosen to be better than the hand flash so it is worth using it for grenades etc.
     /// </summary>
     [DataField]
-    public float Duration = 4f;
+    public TimeSpan Duration = TimeSpan.FromSeconds(4);
 
     /// <summary>
     ///     The prototype ID used for the visual effect.
diff --git a/Content.Shared/Flash/Components/ActiveFlashComponent.cs b/Content.Shared/Flash/Components/ActiveFlashComponent.cs
new file mode 100644 (file)
index 0000000..18994f1
--- /dev/null
@@ -0,0 +1,24 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Flash.Components;
+
+/// <summary>
+/// Marks an entity with the <see cref="FlashComponent"/> as currently flashing.
+/// Only used for an Update loop for resetting the visuals.
+/// </summary>
+/// <remarks>
+/// TODO: Replace this with something like sprite flick once that exists to get rid of the update loop.
+/// </remarks>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
+[Access(typeof(SharedFlashSystem))]
+public sealed partial class ActiveFlashComponent : Component
+{
+    /// <summary>
+    /// Time at which this flash will be considered no longer active.
+    /// At this time this component will be removed.
+    /// </summary>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    [AutoNetworkedField, AutoPausedField]
+    public TimeSpan ActiveUntil = TimeSpan.Zero;
+}
diff --git a/Content.Shared/Flash/Components/DamagedByFlashingComponent.cs b/Content.Shared/Flash/Components/DamagedByFlashingComponent.cs
new file mode 100644 (file)
index 0000000..766ee30
--- /dev/null
@@ -0,0 +1,18 @@
+using Content.Shared.Damage;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Flash.Components;
+
+/// <summary>
+/// This entity will take damage from flashes.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(DamagedByFlashingSystem))]
+public sealed partial class DamagedByFlashingComponent : Component
+{
+    /// <summary>
+    /// How much damage it will take.
+    /// </summary>
+    [DataField(required: true)]
+    public DamageSpecifier FlashDamage = new();
+}
index 29f92eb94fc51d2cbcce5989af27811fa91eda6d..d1a8b882d9ad3261ea9a785f410619e48de474fa 100644 (file)
@@ -1,55 +1,79 @@
 using Robust.Shared.Audio;
 using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
 
-namespace Content.Shared.Flash.Components
+namespace Content.Shared.Flash.Components;
+
+/// <summary>
+/// Allows this entity to flash someone by using it or melee attacking with it.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedFlashSystem))]
+public sealed partial class FlashComponent : Component
 {
-    [RegisterComponent, NetworkedComponent, Access(typeof(SharedFlashSystem))]
-    public sealed partial class FlashComponent : Component
-    {
+    /// <summary>
+    /// Flash the area around the entity when used in hand?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool FlashOnUse = true;
 
-        [DataField("duration")]
-        [ViewVariables(VVAccess.ReadWrite)]
-        public int FlashDuration { get; set; } = 5000;
+    /// <summary>
+    /// Flash the target when melee attacking them?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool FlashOnMelee = true;
 
-        /// <summary>
-        /// How long a target is stunned when a melee flash is used.
-        /// If null, melee flashes will not stun at all
-        /// </summary>
-        [DataField]
-        public TimeSpan? MeleeStunDuration = TimeSpan.FromSeconds(1.5);
+    /// <summary>
+    /// Time the Flash will be visually flashing after use.
+    /// For the actual interaction delay use UseDelayComponent.
+    /// These two times should be the same.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TimeSpan FlashingTime = TimeSpan.FromSeconds(4);
 
-        [DataField("range")]
-        [ViewVariables(VVAccess.ReadWrite)]
-        public float Range { get; set; } = 7f;
+    /// <summary>
+    /// For how long the target will lose vision when melee attacked with the flash.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TimeSpan MeleeDuration = TimeSpan.FromSeconds(5);
 
-        [ViewVariables(VVAccess.ReadWrite)]
-        [DataField("aoeFlashDuration")]
-        public int AoeFlashDuration { get; set; } = 2000;
+    /// <summary>
+    /// For how long the target will lose vision when used in hand.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TimeSpan AoeFlashDuration = TimeSpan.FromSeconds(2);
 
-        [DataField("slowTo")]
-        [ViewVariables(VVAccess.ReadWrite)]
-        public float SlowTo { get; set; } = 0.5f;
+    /// <summary>
+    /// How long a target is stunned when a melee flash is used.
+    /// If null, melee flashes will not stun at all.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TimeSpan? MeleeStunDuration = TimeSpan.FromSeconds(1.5);
 
-        [ViewVariables(VVAccess.ReadWrite)]
-        [DataField("sound")]
-        public SoundSpecifier Sound { get; set; } = new SoundPathSpecifier("/Audio/Weapons/flash.ogg")
-        {
-            Params = AudioParams.Default.WithVolume(1f).WithMaxDistance(3f)
-        };
+    /// <summary>
+    /// Range of the flash when using it.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float Range = 7f;
 
-        public bool Flashing;
+    /// <summary>
+    /// Movement speed multiplier for slowing down the target while they are flashed.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float SlowTo = 0.5f;
 
-        [DataField]
-        public float Probability = 1f;
-    }
+    /// <summary>
+    /// The sound to play when flashing.
+    /// </summary>
 
-    [Serializable, NetSerializable]
-    public enum FlashVisuals : byte
+    [DataField, AutoNetworkedField]
+    public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Weapons/flash.ogg")
     {
-        BaseLayer,
-        LightLayer,
-        Burnt,
-        Flashing,
-    }
+        Params = AudioParams.Default.WithVolume(1f).WithMaxDistance(3f)
+    };
+
+    /// <summary>
+    /// The probability of sucessfully flashing someone.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float Probability = 1f;
 }
diff --git a/Content.Shared/Flash/Components/FlashImmunityComponent.cs b/Content.Shared/Flash/Components/FlashImmunityComponent.cs
new file mode 100644 (file)
index 0000000..149c27c
--- /dev/null
@@ -0,0 +1,18 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.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, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedFlashSystem))]
+public sealed partial class FlashImmunityComponent : Component
+{
+    /// <summary>
+    /// Is this component currently enabled?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Enabled = true;
+}
index 7658ca0ae5aae55ffd551beca2ec821113c0ac15..e735b3784a686df175859bd2cd2bf48adfd3aba0 100644 (file)
@@ -7,7 +7,12 @@ namespace Content.Shared.Flash.Components;
 [RegisterComponent, NetworkedComponent]
 public sealed partial class FlashOnTriggerComponent : Component
 {
-    [DataField] public float Range = 1.0f;
-    [DataField] public float Duration = 8.0f;
-    [DataField] public float Probability = 1.0f;
+    [DataField]
+    public float Range = 1.0f;
+
+    [DataField]
+    public TimeSpan Duration = TimeSpan.FromSeconds(8);
+
+    [DataField]
+    public float Probability = 1.0f;
 }
index 75bbb12304ac4a00b901c97424f66fada5385733..e6c623b9ac96fa472e209ac64f1405ca5959d9b5 100644 (file)
@@ -3,7 +3,7 @@ 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.
+/// 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 { }
+public sealed partial class FlashedComponent : Component;
similarity index 53%
rename from Content.Server/Flash/DamagedByFlashingSystem.cs
rename to Content.Shared/Flash/DamagedByFlashingSystem.cs
index 5b4c19b8e547953c5d0dcb5b4180611fa5c1a5a6..103dfb663cc37116b48899c02d85004bc6edffe8 100644 (file)
@@ -1,7 +1,8 @@
-using Content.Server.Flash.Components;
+using Content.Shared.Flash.Components;
 using Content.Shared.Damage;
 
-namespace Content.Server.Flash;
+namespace Content.Shared.Flash;
+
 public sealed class DamagedByFlashingSystem : EntitySystem
 {
     [Dependency] private readonly DamageableSystem _damageable = default!;
@@ -12,11 +13,14 @@ public sealed class DamagedByFlashingSystem : EntitySystem
 
         SubscribeLocalEvent<DamagedByFlashingComponent, FlashAttemptEvent>(OnFlashAttempt);
     }
+
+    // TODO: Attempt events should not be doing state changes. But using AfterFlashedEvent does not work because this entity cannot get the status effect.
+    // Best wait for Ed's status effect system rewrite.
     private void OnFlashAttempt(Entity<DamagedByFlashingComponent> ent, ref FlashAttemptEvent args)
     {
         _damageable.TryChangeDamage(ent, ent.Comp.FlashDamage);
 
-        //TODO: It would be more logical if different flashes had different power,
-        //and the damage would be inflicted depending on the strength of the flash.
+        // TODO: It would be more logical if different flashes had different power,
+        // and the damage would be inflicted depending on the strength of the flash.
     }
 }
diff --git a/Content.Shared/Flash/FlashEvents.cs b/Content.Shared/Flash/FlashEvents.cs
new file mode 100644 (file)
index 0000000..1c18ca1
--- /dev/null
@@ -0,0 +1,21 @@
+using Content.Shared.Inventory;
+
+namespace Content.Shared.Flash;
+
+/// <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 and their inventory items.
+/// </summary>
+[ByRefEvent]
+public record struct FlashAttemptEvent(EntityUid Target, EntityUid? User, EntityUid? Used, bool Cancelled = false) : IInventoryRelayEvent
+{
+    SlotFlags IInventoryRelayEvent.TargetSlots => SlotFlags.HEAD | SlotFlags.EYES | SlotFlags.MASK;
+}
+
+/// <summary>
+/// Called when a player is successfully flashed.
+/// Raised on the target hit by the flash, the user of the flash and the flash used.
+/// The Melee parameter is used to check for rev conversion.
+/// </summary>
+[ByRefEvent]
+public record struct AfterFlashedEvent(EntityUid Target, EntityUid? User, EntityUid? Used, bool Melee);
diff --git a/Content.Shared/Flash/FlashVisuals.cs b/Content.Shared/Flash/FlashVisuals.cs
new file mode 100644 (file)
index 0000000..f43780f
--- /dev/null
@@ -0,0 +1,17 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Flash;
+
+[Serializable, NetSerializable]
+public enum FlashVisuals : byte
+{
+    Burnt,
+    Flashing,
+}
+
+[Serializable, NetSerializable]
+public enum FlashVisualLayers : byte
+{
+    BaseLayer,
+    LightLayer,
+}
index b7788098870b1f845b1453c73937ee65d7ac89bd..50652f940888debd53a6bd98b76c2577215b3d5d 100644 (file)
+using Content.Shared.Charges.Components;
+using Content.Shared.Charges.Systems;
+using Content.Shared.Examine;
+using Content.Shared.Eye.Blinding.Components;
 using Content.Shared.Flash.Components;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Inventory;
+using Content.Shared.Light;
+using Content.Shared.Popups;
 using Content.Shared.StatusEffect;
+using Content.Shared.Stunnable;
+using Content.Shared.Tag;
+using Content.Shared.Timing;
+using Content.Shared.Traits.Assorted;
+using Content.Shared.Weapons.Melee.Events;
 using Robust.Shared.Audio;
+using Robust.Shared.Audio.Systems;
 using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+using System.Linq;
 
 namespace Content.Shared.Flash;
 
 public abstract class SharedFlashSystem : EntitySystem
 {
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly SharedChargesSystem _sharedCharges = default!;
+    [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+    [Dependency] private readonly ExamineSystemShared _examine = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+    [Dependency] private readonly SharedStunSystem _stun = default!;
+    [Dependency] private readonly TagSystem _tag = default!;
+    [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly UseDelaySystem _useDelay = default!;
+
+    private EntityQuery<StatusEffectsComponent> _statusEffectsQuery;
+    private EntityQuery<DamagedByFlashingComponent> _damagedByFlashingQuery;
+    private HashSet<EntityUid> _entSet = new();
+
+    // The tag to add when a flash has no charges left.
+    private static readonly ProtoId<TagPrototype> TrashTag = "Trash";
+    // The key string for the status effect.
     public ProtoId<StatusEffectPrototype> FlashedKey = "Flashed";
 
-    public virtual void FlashArea(Entity<FlashComponent?> source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, float probability = 1f, SoundSpecifier? sound = null)
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<FlashComponent, MeleeHitEvent>(OnFlashMeleeHit);
+        SubscribeLocalEvent<FlashComponent, UseInHandEvent>(OnFlashUseInHand);
+        SubscribeLocalEvent<FlashComponent, LightToggleEvent>(OnLightToggle);
+        SubscribeLocalEvent<PermanentBlindnessComponent, FlashAttemptEvent>(OnPermanentBlindnessFlashAttempt);
+        SubscribeLocalEvent<TemporaryBlindnessComponent, FlashAttemptEvent>(OnTemporaryBlindnessFlashAttempt);
+        Subs.SubscribeWithRelay<FlashImmunityComponent, FlashAttemptEvent>(OnFlashImmunityFlashAttempt, held: false);
+        SubscribeLocalEvent<FlashImmunityComponent, ExaminedEvent>(OnExamine);
+
+        _statusEffectsQuery = GetEntityQuery<StatusEffectsComponent>();
+        _damagedByFlashingQuery = GetEntityQuery<DamagedByFlashingComponent>();
+    }
+
+    private void OnFlashMeleeHit(Entity<FlashComponent> ent, ref MeleeHitEvent args)
+    {
+        if (!ent.Comp.FlashOnMelee ||
+            !args.IsHit ||
+            !args.HitEntities.Any() ||
+            !UseFlash(ent, args.User))
+        {
+            return;
+        }
+
+        args.Handled = true;
+        foreach (var target in args.HitEntities)
+        {
+            Flash(target, args.User, ent.Owner, ent.Comp.MeleeDuration, ent.Comp.SlowTo, melee: true, stunDuration: ent.Comp.MeleeStunDuration);
+        }
+    }
+
+    private void OnFlashUseInHand(Entity<FlashComponent> ent, ref UseInHandEvent args)
+    {
+        if (!ent.Comp.FlashOnUse || args.Handled || !UseFlash(ent, args.User))
+            return;
+
+        args.Handled = true;
+        FlashArea(ent.Owner, args.User, ent.Comp.Range, ent.Comp.AoeFlashDuration, ent.Comp.SlowTo, true, ent.Comp.Probability);
+    }
+
+    // needed for the flash lantern and interrogator lamp
+    // TODO: This is awful and all the different components for toggleable lights need to be unified and changed to use Itemtoggle
+    private void OnLightToggle(Entity<FlashComponent> ent, ref LightToggleEvent args)
+    {
+        if (!args.IsOn || !UseFlash(ent, null))
+            return;
+
+        FlashArea(ent.Owner, null, ent.Comp.Range, ent.Comp.AoeFlashDuration, ent.Comp.SlowTo, true, ent.Comp.Probability);
+    }
+
+    /// <summary>
+    /// Use charges and set the visuals.
+    /// </summary>
+    /// <returns>False if no charges are left or the flash is currently in use.</returns>
+    private bool UseFlash(Entity<FlashComponent> ent, EntityUid? user)
+    {
+        if (_useDelay.IsDelayed(ent.Owner))
+            return false;
+
+        if (TryComp<LimitedChargesComponent>(ent.Owner, out var charges)
+            && _sharedCharges.IsEmpty((ent.Owner, charges)))
+            return false;
+
+        _sharedCharges.TryUseCharge((ent.Owner, charges));
+        _audio.PlayPredicted(ent.Comp.Sound, ent.Owner, user);
+
+        var active = EnsureComp<ActiveFlashComponent>(ent.Owner);
+        active.ActiveUntil = _timing.CurTime + ent.Comp.FlashingTime;
+        Dirty(ent.Owner, active);
+        _appearance.SetData(ent.Owner, FlashVisuals.Flashing, true);
+
+        if (_sharedCharges.IsEmpty((ent.Owner, charges)))
+        {
+            _appearance.SetData(ent.Owner, FlashVisuals.Burnt, true);
+            _tag.AddTag(ent.Owner, TrashTag);
+            _popup.PopupClient(Loc.GetString("flash-component-becomes-empty"), user);
+        }
+
+        return true;
+    }
+
+    /// <summary>
+    /// Cause an entity to be flashed, obstructing their vision, slowing them down and stunning them.
+    /// In case of a melee attack this will do a check for revolutionary conversion.
+    /// </summary>
+    /// <param name="target">The mob to be flashed.</param>
+    /// <param name="user">The mob causing the flash, if any.</param>
+    /// <param name="used">The item causing the flash, if any.</param>
+    /// <param name="flashDuration">The time target will be affected by the flash.</param>
+    /// <param name="slowTo">Movement speed modifier applied to the flashed target. Between 0 and 1.</param>
+    /// <param name="displayPopup">Whether or not to show a popup to the target player.</param>
+    /// <param name="melee">Was this flash caused by a melee attack? Used for checking for revolutionary conversion.</param>
+    /// <param name="stunDuration">The time the target will be stunned. If null the target will be slowed down instead.</param>
+    public void Flash(
+        EntityUid target,
+        EntityUid? user,
+        EntityUid? used,
+        TimeSpan flashDuration,
+        float slowTo,
+        bool displayPopup = true,
+        bool melee = false,
+        TimeSpan? stunDuration = null)
+    {
+        var attempt = new FlashAttemptEvent(target, user, used);
+        RaiseLocalEvent(target, ref attempt, true);
+
+        if (attempt.Cancelled)
+            return;
+
+        // don't paralyze, slowdown or convert to rev if the target is immune to flashes
+        if (!_statusEffectsSystem.TryAddStatusEffect<FlashedComponent>(target, FlashedKey, flashDuration, true))
+            return;
+
+        if (stunDuration != null)
+            _stun.TryParalyze(target, stunDuration.Value, true);
+        else
+            _stun.TrySlowdown(target, flashDuration, true, slowTo, slowTo);
+
+        if (displayPopup && user != null && target != user && Exists(user.Value))
+        {
+            _popup.PopupEntity(Loc.GetString("flash-component-user-blinds-you",
+                ("user", Identity.Entity(user.Value, EntityManager))), target, target);
+        }
+
+        var ev = new AfterFlashedEvent(target, user, used, melee);
+        RaiseLocalEvent(target, ref ev);
+
+        if (user != null)
+            RaiseLocalEvent(user.Value, ref ev);
+        if (used != null)
+            RaiseLocalEvent(used.Value, ref ev);
+    }
+
+    /// <summary>
+    /// Cause all entities in range of a source entity to be flashed.
+    /// </summary>
+    /// <param name="source">The source of the flash, which will be at the epicenter.</param>
+    /// <param name="user">The mob causing the flash, if any.</param>
+    /// <param name="flashDuration">The time target will be affected by the flash.</param>
+    /// <param name="slowTo">Movement speed modifier applied to the flashed target. Between 0 and 1.</param>
+    /// <param name="displayPopup">Whether or not to show a popup to the target player.</param>
+    /// <param name="probability">Chance to be flashed. Rolled separately for each target in range.</param>
+    /// <param name="sound">Additional sound to play at the source.</param>
+    public void FlashArea(EntityUid source, EntityUid? user, float range, TimeSpan flashDuration, float slowTo = 0.8f, bool displayPopup = false, float probability = 1f, SoundSpecifier? sound = null)
+    {
+        var transform = Transform(source);
+        var mapPosition = _transform.GetMapCoordinates(transform);
+
+        _entSet.Clear();
+        _entityLookup.GetEntitiesInRange(transform.Coordinates, range, _entSet);
+        foreach (var entity in _entSet)
+        {
+            // TODO: Use RandomPredicted https://github.com/space-wizards/RobustToolbox/pull/5849
+            var rand = new System.Random((int)_timing.CurTick.Value + GetNetEntity(entity).Id);
+            if (!rand.Prob(probability))
+                continue;
+
+            // 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 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;
+
+            Flash(entity, user, source, flashDuration, slowTo, displayPopup);
+        }
+
+        _audio.PlayPredicted(sound, source, user, AudioParams.Default.WithVolume(1f).WithMaxDistance(3f));
+    }
+
+    // Handle the flash visuals
+    // TODO: Replace this with something like sprite flick once that exists to get rid of the update loop.
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        var curTime = _timing.CurTime;
+        var query = EntityQueryEnumerator<ActiveFlashComponent>();
+        while (query.MoveNext(out var uid, out var active))
+        {
+            // reset the visuals and remove the component
+            if (active.ActiveUntil < curTime)
+            {
+                _appearance.SetData(uid, FlashVisuals.Flashing, false);
+                RemCompDeferred<ActiveFlashComponent>(uid);
+            }
+        }
+    }
+
+    private void OnPermanentBlindnessFlashAttempt(Entity<PermanentBlindnessComponent> ent, ref FlashAttemptEvent args)
+    {
+        // check for total blindness
+        if (ent.Comp.Blindness == 0)
+            args.Cancelled = true;
+    }
+
+    private void OnTemporaryBlindnessFlashAttempt(Entity<TemporaryBlindnessComponent> ent, ref FlashAttemptEvent args)
+    {
+        args.Cancelled = true;
+    }
+
+    private void OnFlashImmunityFlashAttempt(Entity<FlashImmunityComponent> ent, ref FlashAttemptEvent args)
+    {
+        if (ent.Comp.Enabled)
+            args.Cancelled = true;
+    }
+
+    private void OnExamine(Entity<FlashImmunityComponent> ent, ref ExaminedEvent args)
     {
+        args.PushMarkup(Loc.GetString("flash-protection"));
     }
 }
index 7973af35ab96111ca800d4fb37c7872153d2c938..f6a719a59beae5f6c57c79153b59a650595e8ba9 100644 (file)
@@ -10,6 +10,7 @@ using Content.Shared.Damage.Events;
 using Content.Shared.Electrocution;
 using Content.Shared.Explosion;
 using Content.Shared.Eye.Blinding.Systems;
+using Content.Shared.Flash;
 using Content.Shared.Gravity;
 using Content.Shared.IdentityManagement.Components;
 using Content.Shared.Implants;
@@ -66,6 +67,7 @@ public partial class InventorySystem
         SubscribeLocalEvent<InventoryComponent, ProjectileReflectAttemptEvent>(RefRelayInventoryEvent);
         SubscribeLocalEvent<InventoryComponent, HitScanReflectAttemptEvent>(RefRelayInventoryEvent);
         SubscribeLocalEvent<InventoryComponent, GetContrabandDetailsEvent>(RefRelayInventoryEvent);
+        SubscribeLocalEvent<InventoryComponent, FlashAttemptEvent>(RefRelayInventoryEvent);
         SubscribeLocalEvent<InventoryComponent, WieldAttemptEvent>(RefRelayInventoryEvent);
         SubscribeLocalEvent<InventoryComponent, UnwieldAttemptEvent>(RefRelayInventoryEvent);
 
index e93ca0a041b04b0a95eea78dd4575fdbe53c0320..0f507e1365f254a0251dc924df8df015741d26ce 100644 (file)
@@ -1,10 +1,10 @@
 using Content.Shared.Actions;
 using Content.Shared.Clothing.EntitySystems;
 using Content.Shared.Item;
+using Content.Shared.Light;
 using Content.Shared.Light.Components;
 using Content.Shared.Toggleable;
 using Content.Shared.Verbs;
-using Robust.Shared.Audio;
 using Robust.Shared.Audio.Systems;
 using Robust.Shared.GameStates;
 using Robust.Shared.Utility;
@@ -63,6 +63,9 @@ public abstract class SharedHandheldLightSystem : EntitySystem
 
         Dirty(uid, component);
         UpdateVisuals(uid, component);
+
+        var ev = new LightToggleEvent(activated);
+        RaiseLocalEvent(uid, ev);
     }
 
     public void UpdateVisuals(EntityUid uid, HandheldLightComponent? component = null, AppearanceComponent? appearance = null)
index e2a200084472cc5671fbef2538e10fb490df42f8..eaae4d410a4d42ec222bf3f67aa0f0a79cfbce9b 100644 (file)
     sprite: Objects/Misc/Lights/lampint.rsi
     layers:
       - state: lamp-int
-        map: [ "enum.FlashVisuals.BaseLayer" ]
+        map: [ "enum.FlashVisualLayers.BaseLayer" ]
       - state: lamp-int-on
         shader: unshaded
         visible: false
         map: [ "light" ]
       - state: flashing
-        map: [ "enum.FlashVisuals.LightLayer" ]
+        map: [ "enum.FlashVisualLayers.LightLayer" ]
         visible: false
   - type: Item
     sprite: Objects/Misc/Lights/lampint.rsi
     energy: 0.5
     color: "#FFFFEE"
   - type: Flash
+    flashOnMelee: false
+    flashOnUse: false
+  - type: UseDelay
+    delay: 1
   - type: LimitedCharges
     maxCharges: 3
   - type: AutoRecharge
   - type: GenericVisualizer
     visuals:
       enum.FlashVisuals.Burnt:
-        enum.FlashVisuals.BaseLayer:
+        enum.FlashVisualLayers.BaseLayer:
           True: {state: burnt}
       enum.FlashVisuals.Flashing:
-        enum.FlashVisuals.LightLayer:
+        enum.FlashVisualLayers.LightLayer:
           True: {visible: true}
           False: {visible: false}
 
index 3d25957851b3cf02a32d0ecebb908ee55eccd4b5..24fdb88ed55f8ba97007261ef2ba7e920d9349f6 100644 (file)
       radiatingBehaviourId: radiating
     - type: LightBehaviour
       behaviours:
-        - !type:FadeBehaviour
-          id: radiating
-          maxDuration: 2.0
-          startValue: 3.0
-          endValue: 2.0
-          isLooped: true
-          reverseWhenFinished: true
-        - !type:PulseBehaviour
-          id: blinking
-          interpolate: Nearest
-          maxDuration: 1.0
-          minValue: 0.1
-          maxValue: 2.0
-          isLooped: true
+      - !type:FadeBehaviour
+        id: radiating
+        maxDuration: 2.0
+        startValue: 3.0
+        endValue: 2.0
+        isLooped: true
+        reverseWhenFinished: true
+      - !type:PulseBehaviour
+        id: blinking
+        interpolate: Nearest
+        maxDuration: 1.0
+        minValue: 0.1
+        maxValue: 2.0
+        isLooped: true
     - type: Sprite
       sprite: Objects/Tools/lantern.rsi
       layers:
-        - state: lantern
-        - state: lantern-on
-          shader: unshaded
-          visible: false
-          map: [ "light" ]
+      - state: lantern
+      - state: lantern-on
+        shader: unshaded
+        visible: false
+        map: [ "light" ]
     - type: Item
       sprite: Objects/Tools/lantern.rsi
       heldPrefix: off
+    - type: UseDelay
+      delay: 1
     - type: PointLight
       enabled: false
       radius: 3
@@ -62,7 +64,7 @@
       equippedPrefix: off
       quickEquip: false
       slots:
-        - Belt
+      - Belt
     - type: Tag
       tags:
       - Flashlight
       sprite: Objects/Tools/lantern.rsi
       layers:
       - state: lantern
-        map: [ "enum.FlashVisuals.BaseLayer" ]
+        map: [ "enum.FlashVisualLayers.BaseLayer" ]
       - state: lantern-on
         shader: unshaded
         visible: false
         map: [ "light" ]
       - state: flashing
-        map: [ "enum.FlashVisuals.LightLayer" ]
+        map: [ "enum.FlashVisualLayers.LightLayer" ]
         visible: false
     - type: PointLight
       radius: 5
       energy: 10
     - type: Flash
+      flashOnMelee: false
+      flashOnUse: false
     - type: LimitedCharges
       maxCharges: 15
     - type: MeleeWeapon
     - type: GenericVisualizer
       visuals:
         enum.FlashVisuals.Burnt:
-          enum.FlashVisuals.BaseLayer:
+          enum.FlashVisualLayers.BaseLayer:
             True: {state: burnt}
         enum.FlashVisuals.Flashing:
-          enum.FlashVisuals.LightLayer:
+          enum.FlashVisualLayers.LightLayer:
             True: {visible: true}
             False: {visible: false}
index ade0107670a15be074430cbb8d88743d9483ff31..7f69d77f93e2efd7d7e759034a7d4a783e216996 100644 (file)
       sprite: Objects/Weapons/Melee/flash.rsi
       layers:
       - state: flash
-        map: [ "enum.FlashVisuals.BaseLayer" ]
+        map: [ "enum.FlashVisualLayers.BaseLayer" ]
       - state: flashing
-        map: [ "enum.FlashVisuals.LightLayer" ]
+        map: [ "enum.FlashVisualLayers.LightLayer" ]
         visible: false
         shader: unshaded
     - type: Flash
       size: Small
       sprite: Objects/Weapons/Melee/flash.rsi
     - type: UseDelay
+      delay: 4 # has to be the same as the FlashingTime datafield in FlashComponent
+    - type: UseDelayOnMeleeHit
     - type: StaticPrice
       price: 40
     - type: Appearance
     - type: GenericVisualizer
       visuals:
         enum.FlashVisuals.Burnt:
-          enum.FlashVisuals.BaseLayer:
+          enum.FlashVisualLayers.BaseLayer:
             True: {state: burnt}
         enum.FlashVisuals.Flashing:
-          enum.FlashVisuals.LightLayer:
+          enum.FlashVisualLayers.LightLayer:
             True: {visible: true}
             False: {visible: false}
     - type: GuideHelp