Re-organize all projects (#4166)
This commit is contained in:
182
Content.Server/Light/Components/EmergencyLightComponent.cs
Normal file
182
Content.Server/Light/Components/EmergencyLightComponent.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Battery.Components;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Examine;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Light.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that represents an emergency light, it has an internal battery that charges when the power is on.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class EmergencyLightComponent : Component, IExamine
|
||||
{
|
||||
public override string Name => "EmergencyLight";
|
||||
|
||||
[ViewVariables]
|
||||
private EmergencyLightState State
|
||||
{
|
||||
get => _state;
|
||||
set
|
||||
{
|
||||
if (_state == value)
|
||||
return;
|
||||
|
||||
_state = value;
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new EmergencyLightMessage(this, _state));
|
||||
}
|
||||
}
|
||||
|
||||
private EmergencyLightState _state = EmergencyLightState.Empty;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("wattage")]
|
||||
private float _wattage = 5;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("chargingWattage")]
|
||||
private float _chargingWattage = 60;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("chargingEfficiency")]
|
||||
private float _chargingEfficiency = 0.85f;
|
||||
|
||||
/// <summary>
|
||||
/// For attaching UpdateState() to events.
|
||||
/// </summary>
|
||||
public void UpdateState(PowerChangedMessage e)
|
||||
{
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the light's power drain, battery drain, sprite and actual light state.
|
||||
/// </summary>
|
||||
public void UpdateState()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out PowerReceiverComponent? receiver))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (receiver.Powered)
|
||||
{
|
||||
receiver.Load = (int) Math.Abs(_wattage);
|
||||
TurnOff();
|
||||
State = EmergencyLightState.Charging;
|
||||
}
|
||||
else
|
||||
{
|
||||
TurnOn();
|
||||
State = EmergencyLightState.On;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnUpdate(float frameTime)
|
||||
{
|
||||
if (Owner.Deleted || !Owner.TryGetComponent(out BatteryComponent? battery))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(State == EmergencyLightState.On)
|
||||
{
|
||||
if (!battery.TryUseCharge(_wattage * frameTime))
|
||||
{
|
||||
State = EmergencyLightState.Empty;
|
||||
TurnOff();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
battery.CurrentCharge += _chargingWattage * frameTime * _chargingEfficiency;
|
||||
if (battery.BatteryState == BatteryState.Full)
|
||||
{
|
||||
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
|
||||
{
|
||||
receiver.Load = 1;
|
||||
}
|
||||
|
||||
State = EmergencyLightState.Full;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TurnOff()
|
||||
{
|
||||
if (Owner.TryGetComponent(out SpriteComponent? sprite))
|
||||
{
|
||||
sprite.LayerSetState(0, "emergency_light_off");
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out PointLightComponent? light))
|
||||
{
|
||||
light.Enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void TurnOn()
|
||||
{
|
||||
if (Owner.TryGetComponent(out SpriteComponent? sprite))
|
||||
{
|
||||
sprite.LayerSetState(0, "emergency_light_on");
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out PointLightComponent? light))
|
||||
{
|
||||
light.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
switch (message)
|
||||
{
|
||||
case PowerChangedMessage powerChanged:
|
||||
UpdateState(powerChanged);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
message.AddMarkup(Loc.GetString($"The battery indicator displays: {BatteryStateText[State]}."));
|
||||
}
|
||||
|
||||
public enum EmergencyLightState
|
||||
{
|
||||
Charging,
|
||||
Full,
|
||||
Empty,
|
||||
On
|
||||
}
|
||||
|
||||
public Dictionary<EmergencyLightState, string> BatteryStateText = new()
|
||||
{
|
||||
{ EmergencyLightState.Full, "[color=darkgreen]Full[/color]"},
|
||||
{ EmergencyLightState.Empty, "[color=darkred]Empty[/color]"},
|
||||
{ EmergencyLightState.Charging, "[color=darkorange]Charging[/color]"},
|
||||
{ EmergencyLightState.On, "[color=darkorange]Discharging[/color]"}
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class EmergencyLightMessage : EntityEventArgs
|
||||
{
|
||||
public EmergencyLightComponent Component { get; }
|
||||
|
||||
public EmergencyLightComponent.EmergencyLightState State { get; }
|
||||
|
||||
public EmergencyLightMessage(EmergencyLightComponent component, EmergencyLightComponent.EmergencyLightState state)
|
||||
{
|
||||
Component = component;
|
||||
State = state;
|
||||
}
|
||||
}
|
||||
}
|
||||
218
Content.Server/Light/Components/ExpendableLightComponent.cs
Normal file
218
Content.Server/Light/Components/ExpendableLightComponent.cs
Normal file
@@ -0,0 +1,218 @@
|
||||
using Content.Server.Clothing.Components;
|
||||
using Content.Server.Items;
|
||||
using Content.Server.Sound;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Light.Component;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Light.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that represents a handheld expendable light which can be activated and eventually dies over time.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class ExpendableLightComponent : SharedExpendableLightComponent, IUse
|
||||
{
|
||||
private static readonly AudioParams LoopedSoundParams = new(0, 1, "Master", 62.5f, 1, true, 0.3f);
|
||||
|
||||
/// <summary>
|
||||
/// Status of light, whether or not it is emitting light.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool Activated => CurrentState == ExpendableLightState.Lit || CurrentState == ExpendableLightState.Fading;
|
||||
|
||||
[ViewVariables]
|
||||
private float _stateExpiryTime = default;
|
||||
private AppearanceComponent? _appearance = default;
|
||||
|
||||
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
|
||||
{
|
||||
return TryActivate();
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (Owner.TryGetComponent<ItemComponent>(out var item))
|
||||
{
|
||||
item.EquippedPrefix = "unlit";
|
||||
}
|
||||
|
||||
CurrentState = ExpendableLightState.BrandNew;
|
||||
Owner.EnsureComponent<PointLightComponent>();
|
||||
Owner.TryGetComponent(out _appearance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables the light if it is not active. Once active it cannot be turned off.
|
||||
/// </summary>
|
||||
private bool TryActivate()
|
||||
{
|
||||
if (!Activated)
|
||||
{
|
||||
if (Owner.TryGetComponent<ItemComponent>(out var item))
|
||||
{
|
||||
item.EquippedPrefix = "lit";
|
||||
}
|
||||
|
||||
CurrentState = ExpendableLightState.Lit;
|
||||
_stateExpiryTime = GlowDuration;
|
||||
|
||||
UpdateSpriteAndSounds(Activated);
|
||||
UpdateVisualizer();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void UpdateVisualizer()
|
||||
{
|
||||
switch (CurrentState)
|
||||
{
|
||||
case ExpendableLightState.Lit:
|
||||
_appearance?.SetData(ExpendableLightVisuals.State, TurnOnBehaviourID);
|
||||
break;
|
||||
|
||||
case ExpendableLightState.Fading:
|
||||
_appearance?.SetData(ExpendableLightVisuals.State, FadeOutBehaviourID);
|
||||
break;
|
||||
|
||||
case ExpendableLightState.Dead:
|
||||
_appearance?.SetData(ExpendableLightVisuals.State, string.Empty);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSpriteAndSounds(bool on)
|
||||
{
|
||||
if (Owner.TryGetComponent(out SpriteComponent? sprite))
|
||||
{
|
||||
switch (CurrentState)
|
||||
{
|
||||
case ExpendableLightState.Lit:
|
||||
|
||||
if (LoopedSound != string.Empty && Owner.TryGetComponent<LoopingLoopingSoundComponent>(out var loopingSound))
|
||||
{
|
||||
loopingSound.Play(LoopedSound, LoopedSoundParams);
|
||||
}
|
||||
|
||||
if (LitSound != string.Empty)
|
||||
{
|
||||
SoundSystem.Play(Filter.Pvs(Owner), LitSound, Owner);
|
||||
}
|
||||
|
||||
if (IconStateLit != string.Empty)
|
||||
{
|
||||
sprite.LayerSetState(2, IconStateLit);
|
||||
sprite.LayerSetShader(2, "shaded");
|
||||
}
|
||||
|
||||
sprite.LayerSetVisible(1, true);
|
||||
break;
|
||||
|
||||
case ExpendableLightState.Fading:
|
||||
break;
|
||||
|
||||
default:
|
||||
case ExpendableLightState.Dead:
|
||||
|
||||
if (DieSound != string.Empty)
|
||||
{
|
||||
SoundSystem.Play(Filter.Pvs(Owner), DieSound, Owner);
|
||||
}
|
||||
|
||||
if (LoopedSound != string.Empty && Owner.TryGetComponent<LoopingLoopingSoundComponent>(out var loopSound))
|
||||
{
|
||||
loopSound.StopAllSounds();
|
||||
}
|
||||
|
||||
sprite.LayerSetState(0, IconStateSpent);
|
||||
sprite.LayerSetShader(0, "shaded");
|
||||
sprite.LayerSetVisible(1, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out ClothingComponent? clothing))
|
||||
{
|
||||
clothing.ClothingEquippedPrefix = on ? "Activated" : string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
if (!Activated) return;
|
||||
|
||||
_stateExpiryTime -= frameTime;
|
||||
|
||||
if (_stateExpiryTime <= 0f)
|
||||
{
|
||||
switch (CurrentState)
|
||||
{
|
||||
case ExpendableLightState.Lit:
|
||||
|
||||
CurrentState = ExpendableLightState.Fading;
|
||||
_stateExpiryTime = FadeOutDuration;
|
||||
|
||||
UpdateVisualizer();
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
case ExpendableLightState.Fading:
|
||||
|
||||
CurrentState = ExpendableLightState.Dead;
|
||||
Owner.Name = SpentName;
|
||||
Owner.Description = SpentDesc;
|
||||
|
||||
UpdateSpriteAndSounds(Activated);
|
||||
UpdateVisualizer();
|
||||
|
||||
if (Owner.TryGetComponent<ItemComponent>(out var item))
|
||||
{
|
||||
item.EquippedPrefix = "unlit";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Verb]
|
||||
public sealed class ActivateVerb : Verb<ExpendableLightComponent>
|
||||
{
|
||||
protected override void GetData(IEntity user, ExpendableLightComponent component, VerbData data)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanInteract(user))
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.CurrentState == ExpendableLightState.BrandNew)
|
||||
{
|
||||
data.Text = "Activate";
|
||||
data.Visibility = VerbVisibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, ExpendableLightComponent component)
|
||||
{
|
||||
component.TryActivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
299
Content.Server/Light/Components/HandheldLightComponent.cs
Normal file
299
Content.Server/Light/Components/HandheldLightComponent.cs
Normal file
@@ -0,0 +1,299 @@
|
||||
#nullable enable
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Clothing.Components;
|
||||
using Content.Server.Items;
|
||||
using Content.Server.PowerCell.Components;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.Behaviors.Item;
|
||||
using Content.Shared.Actions.Components;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Light.Component;
|
||||
using Content.Shared.Notification;
|
||||
using Content.Shared.Rounding;
|
||||
using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Light.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that represents a powered handheld light source which can be toggled on and off.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
internal sealed class HandheldLightComponent : SharedHandheldLightComponent, IUse, IExamine, IInteractUsing
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("wattage")] public float Wattage { get; set; } = 3f;
|
||||
[ViewVariables] private PowerCellSlotComponent _cellSlot = default!;
|
||||
private PowerCellComponent? Cell => _cellSlot.Cell;
|
||||
|
||||
/// <summary>
|
||||
/// Status of light, whether or not it is emitting light.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool Activated { get; private set; }
|
||||
|
||||
[ViewVariables] protected override bool HasCell => _cellSlot.HasCell;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("turnOnSound")] public string? TurnOnSound = "/Audio/Items/flashlight_toggle.ogg";
|
||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("turnOnFailSound")] public string? TurnOnFailSound = "/Audio/Machines/button.ogg";
|
||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("turnOffSound")] public string? TurnOffSound = "/Audio/Items/flashlight_toggle.ogg";
|
||||
|
||||
[ComponentDependency] private readonly ItemActionsComponent? _itemActions = null;
|
||||
|
||||
/// <summary>
|
||||
/// Client-side ItemStatus level
|
||||
/// </summary>
|
||||
private byte? _lastLevel;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Owner.EnsureComponent<PointLightComponent>();
|
||||
_cellSlot = Owner.EnsureComponent<PowerCellSlotComponent>();
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
Owner.EntityManager.EventBus.QueueEvent(EventSource.Local, new DeactivateHandheldLightMessage(this));
|
||||
}
|
||||
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanInteract(eventArgs.User)) return false;
|
||||
if (!_cellSlot.InsertCell(eventArgs.Using)) return false;
|
||||
Dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
if (Activated)
|
||||
{
|
||||
message.AddMarkup(Loc.GetString("The light is currently [color=darkgreen]on[/color]."));
|
||||
}
|
||||
else
|
||||
{
|
||||
message.AddMarkup(Loc.GetString("The light is currently [color=darkred]off[/color]."));
|
||||
}
|
||||
}
|
||||
|
||||
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
|
||||
{
|
||||
return ToggleStatus(eventArgs.User);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Illuminates the light if it is not active, extinguishes it if it is active.
|
||||
/// </summary>
|
||||
/// <returns>True if the light's status was toggled, false otherwise.</returns>
|
||||
public bool ToggleStatus(IEntity user)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanUse(user)) return false;
|
||||
return Activated ? TurnOff() : TurnOn(user);
|
||||
}
|
||||
|
||||
private bool TurnOff(bool makeNoise = true)
|
||||
{
|
||||
if (!Activated)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SetState(false);
|
||||
Activated = false;
|
||||
UpdateLightAction();
|
||||
Owner.EntityManager.EventBus.QueueEvent(EventSource.Local, new DeactivateHandheldLightMessage(this));
|
||||
|
||||
if (makeNoise)
|
||||
{
|
||||
if (TurnOffSound != null) SoundSystem.Play(Filter.Pvs(Owner), TurnOffSound, Owner);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TurnOn(IEntity user)
|
||||
{
|
||||
if (Activated)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Cell == null)
|
||||
{
|
||||
if (TurnOnFailSound != null) SoundSystem.Play(Filter.Pvs(Owner), TurnOnFailSound, Owner);
|
||||
Owner.PopupMessage(user, Loc.GetString("Cell missing..."));
|
||||
UpdateLightAction();
|
||||
return false;
|
||||
}
|
||||
|
||||
// To prevent having to worry about frame time in here.
|
||||
// Let's just say you need a whole second of charge before you can turn it on.
|
||||
// Simple enough.
|
||||
if (Wattage > Cell.CurrentCharge)
|
||||
{
|
||||
if (TurnOnFailSound != null) SoundSystem.Play(Filter.Pvs(Owner), TurnOnFailSound, Owner);
|
||||
Owner.PopupMessage(user, Loc.GetString("Dead cell..."));
|
||||
UpdateLightAction();
|
||||
return false;
|
||||
}
|
||||
|
||||
Activated = true;
|
||||
UpdateLightAction();
|
||||
SetState(true);
|
||||
Owner.EntityManager.EventBus.QueueEvent(EventSource.Local, new ActivateHandheldLightMessage(this));
|
||||
|
||||
if (TurnOnSound != null) SoundSystem.Play(Filter.Pvs(Owner), TurnOnSound, Owner);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetState(bool on)
|
||||
{
|
||||
if (Owner.TryGetComponent(out SpriteComponent? sprite))
|
||||
{
|
||||
sprite.LayerSetVisible(1, on);
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out PointLightComponent? light))
|
||||
{
|
||||
light.Enabled = on;
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out ClothingComponent? clothing))
|
||||
{
|
||||
clothing.ClothingEquippedPrefix = on ? "on" : "off";
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out ItemComponent? item))
|
||||
{
|
||||
item.EquippedPrefix = on ? "on" : "off";
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLightAction()
|
||||
{
|
||||
_itemActions?.Toggle(ItemActionType.ToggleLight, Activated);
|
||||
}
|
||||
|
||||
public void OnUpdate(float frameTime)
|
||||
{
|
||||
if (Cell == null)
|
||||
{
|
||||
TurnOff(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var appearanceComponent = Owner.GetComponent<AppearanceComponent>();
|
||||
|
||||
if (Cell.MaxCharge - Cell.CurrentCharge < Cell.MaxCharge * 0.70)
|
||||
{
|
||||
appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.FullPower);
|
||||
}
|
||||
else if (Cell.MaxCharge - Cell.CurrentCharge < Cell.MaxCharge * 0.90)
|
||||
{
|
||||
appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.LowPower);
|
||||
}
|
||||
else
|
||||
{
|
||||
appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.Dying);
|
||||
}
|
||||
|
||||
if (Activated && !Cell.TryUseCharge(Wattage * frameTime)) TurnOff(false);
|
||||
|
||||
var level = GetLevel();
|
||||
|
||||
if (level != _lastLevel)
|
||||
{
|
||||
_lastLevel = level;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
// Curently every single flashlight has the same number of levels for status and that's all it uses the charge for
|
||||
// Thus we'll just check if the level changes.
|
||||
private byte? GetLevel()
|
||||
{
|
||||
if (Cell == null)
|
||||
return null;
|
||||
|
||||
var currentCharge = Cell.CurrentCharge;
|
||||
|
||||
if (MathHelper.CloseTo(currentCharge, 0) || Wattage > currentCharge)
|
||||
return 0;
|
||||
|
||||
return (byte?) ContentHelpers.RoundToNearestLevels(currentCharge / Cell.MaxCharge * 255, 255, StatusLevels);
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState(ICommonSession player)
|
||||
{
|
||||
return new HandheldLightComponentState(GetLevel());
|
||||
}
|
||||
|
||||
[Verb]
|
||||
public sealed class ToggleLightVerb : Verb<HandheldLightComponent>
|
||||
{
|
||||
protected override void GetData(IEntity user, HandheldLightComponent component, VerbData data)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanInteract(user))
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
data.Text = Loc.GetString("Toggle light");
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, HandheldLightComponent component)
|
||||
{
|
||||
component.ToggleStatus(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
[DataDefinition]
|
||||
public class ToggleLightAction : IToggleItemAction
|
||||
{
|
||||
public bool DoToggleAction(ToggleItemActionEventArgs args)
|
||||
{
|
||||
if (!args.Item.TryGetComponent<HandheldLightComponent>(out var lightComponent)) return false;
|
||||
if (lightComponent.Activated == args.ToggledOn) return false;
|
||||
return lightComponent.ToggleStatus(args.Performer);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ActivateHandheldLightMessage : EntityEventArgs
|
||||
{
|
||||
public HandheldLightComponent Component { get; }
|
||||
|
||||
public ActivateHandheldLightMessage(HandheldLightComponent component)
|
||||
{
|
||||
Component = component;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DeactivateHandheldLightMessage : EntityEventArgs
|
||||
{
|
||||
public HandheldLightComponent Component { get; }
|
||||
|
||||
public DeactivateHandheldLightMessage(HandheldLightComponent component)
|
||||
{
|
||||
Component = component;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Content.Server/Light/Components/LightBehaviourComponent.cs
Normal file
14
Content.Server/Light/Components/LightBehaviourComponent.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Content.Shared.Light.Component;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Light.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A component which applies a specific behaviour to a PointLightComponent on its owner.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class LightBehaviourComponent : SharedLightBehaviourComponent
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
138
Content.Server/Light/Components/LightBulbComponent.cs
Normal file
138
Content.Server/Light/Components/LightBulbComponent.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Shared.Acts;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Light.Components
|
||||
{
|
||||
public enum LightBulbState
|
||||
{
|
||||
Normal,
|
||||
Broken,
|
||||
Burned,
|
||||
}
|
||||
|
||||
public enum LightBulbType
|
||||
{
|
||||
Bulb,
|
||||
Tube,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Component that represents a light bulb. Can be broken, or burned, which turns them mostly useless.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class LightBulbComponent : Component, ILand, IBreakAct
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked whenever the state of the light bulb changes.
|
||||
/// </summary>
|
||||
public event EventHandler<EventArgs>? OnLightBulbStateChange;
|
||||
public event EventHandler<EventArgs?>? OnLightColorChange;
|
||||
|
||||
[DataField("color")]
|
||||
private Color _color = Color.White;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] public Color Color
|
||||
{
|
||||
get { return _color; }
|
||||
set
|
||||
{
|
||||
_color = value;
|
||||
OnLightColorChange?.Invoke(this, null);
|
||||
UpdateColor();
|
||||
}
|
||||
}
|
||||
|
||||
public override string Name => "LightBulb";
|
||||
|
||||
[DataField("bulb")]
|
||||
public LightBulbType Type = LightBulbType.Tube;
|
||||
|
||||
[DataField("BurningTemperature")]
|
||||
private int _burningTemperature = 1400;
|
||||
public int BurningTemperature => _burningTemperature;
|
||||
|
||||
[DataField("PowerUse")]
|
||||
private int _powerUse = 40;
|
||||
public int PowerUse => _powerUse;
|
||||
|
||||
/// <summary>
|
||||
/// The current state of the light bulb. Invokes the OnLightBulbStateChange event when set.
|
||||
/// It also updates the bulb's sprite accordingly.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)] public LightBulbState State
|
||||
{
|
||||
get { return _state; }
|
||||
set
|
||||
{
|
||||
var sprite = Owner.GetComponent<SpriteComponent>();
|
||||
OnLightBulbStateChange?.Invoke(this, EventArgs.Empty);
|
||||
_state = value;
|
||||
switch (value)
|
||||
{
|
||||
case LightBulbState.Normal:
|
||||
sprite.LayerSetState(0, "normal");
|
||||
break;
|
||||
case LightBulbState.Broken:
|
||||
sprite.LayerSetState(0, "broken");
|
||||
break;
|
||||
case LightBulbState.Burned:
|
||||
sprite.LayerSetState(0, "burned");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LightBulbState _state = LightBulbState.Normal;
|
||||
|
||||
public void UpdateColor()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SpriteComponent? sprite))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sprite.Color = Color;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
UpdateColor();
|
||||
}
|
||||
|
||||
void ILand.Land(LandEventArgs eventArgs)
|
||||
{
|
||||
PlayBreakSound();
|
||||
State = LightBulbState.Broken;
|
||||
}
|
||||
|
||||
public void OnBreak(BreakageEventArgs eventArgs)
|
||||
{
|
||||
State = LightBulbState.Broken;
|
||||
}
|
||||
|
||||
public void PlayBreakSound()
|
||||
{
|
||||
var soundCollection = _prototypeManager.Index<SoundCollectionPrototype>("GlassBreak");
|
||||
var file = _random.Pick(soundCollection.PickFiles);
|
||||
|
||||
SoundSystem.Play(Filter.Pvs(Owner), file, Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
163
Content.Server/Light/Components/LightReplacerComponent.cs
Normal file
163
Content.Server/Light/Components/LightReplacerComponent.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Shared.NetIDs;
|
||||
using Content.Shared.Notification;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Light.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Device that allows user to quikly change bulbs in <see cref="PoweredLightComponent"/>
|
||||
/// Can be reloaded by new light tubes or light bulbs
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class LightReplacerComponent : Component
|
||||
{
|
||||
public override string Name => "LightReplacer";
|
||||
public override uint? NetID => ContentNetIDs.LIGHT_REPLACER;
|
||||
|
||||
[DataField("sound")] private string _sound = "/Audio/Weapons/click.ogg";
|
||||
|
||||
// bulbs that were inside light replacer when it spawned
|
||||
[DataField("contents")] private List<LightReplacerEntity> _contents = new();
|
||||
// bulbs that were inserted inside light replacer
|
||||
[ViewVariables] private IContainer _insertedBulbs = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_insertedBulbs = ContainerHelpers.EnsureContainer<Container>(Owner, "light_replacer_storage");
|
||||
}
|
||||
|
||||
public bool TryReplaceBulb(PoweredLightComponent fixture, IEntity? user = null)
|
||||
{
|
||||
// check if light bulb is broken or missing
|
||||
if (fixture.LightBulb != null && fixture.LightBulb.State == LightBulbState.Normal) return false;
|
||||
|
||||
// try get first inserted bulb of the same type as targeted light fixtutre
|
||||
var bulb = _insertedBulbs.ContainedEntities.FirstOrDefault(
|
||||
(e) => e.GetComponentOrNull<LightBulbComponent>()?.Type == fixture.BulbType);
|
||||
|
||||
// found bulb in inserted storage
|
||||
if (bulb != null)
|
||||
{
|
||||
// try to remove it
|
||||
var hasRemoved = _insertedBulbs.Remove(bulb);
|
||||
if (!hasRemoved)
|
||||
return false;
|
||||
}
|
||||
// try to create new instance of bulb from LightReplacerEntity
|
||||
else
|
||||
{
|
||||
var bulbEnt = _contents.FirstOrDefault((e) => e.Type == fixture.BulbType && e.Amount > 0);
|
||||
|
||||
// found right bulb, let's spawn it
|
||||
if (bulbEnt != null)
|
||||
{
|
||||
bulb = Owner.EntityManager.SpawnEntity(bulbEnt.PrototypeName, Owner.Transform.Coordinates);
|
||||
bulbEnt.Amount--;
|
||||
}
|
||||
// not found any light bulbs
|
||||
else
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
var msg = Loc.GetString("comp-light-replacer-missing-light", ("light-replacer", Owner));
|
||||
user.PopupMessage(msg);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// insert it into fixture
|
||||
var wasReplaced = fixture.ReplaceBulb(bulb);
|
||||
if (wasReplaced)
|
||||
{
|
||||
EntitySystem.Get<AudioSystem>().Play(Filter.Broadcast(), _sound,
|
||||
Owner, AudioParams.Default.WithVolume(-4f));
|
||||
}
|
||||
|
||||
|
||||
return wasReplaced;
|
||||
}
|
||||
|
||||
public bool TryInsertBulb(LightBulbComponent bulb, IEntity? user = null, bool showTooltip = false)
|
||||
{
|
||||
// only normal lights can be inserted inside light replacer
|
||||
if (bulb.State != LightBulbState.Normal)
|
||||
{
|
||||
if (showTooltip && user != null)
|
||||
{
|
||||
var msg = Loc.GetString("comp-light-replacer-insert-broken-light");
|
||||
user.PopupMessage(msg);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// try insert light and show message
|
||||
var hasInsert = _insertedBulbs.Insert(bulb.Owner);
|
||||
if (hasInsert && showTooltip && user != null)
|
||||
{
|
||||
var msg = Loc.GetString("comp-light-replacer-insert-light",
|
||||
("light-replacer", Owner), ("bulb", bulb.Owner));
|
||||
user.PopupMessage(msg);
|
||||
}
|
||||
|
||||
|
||||
return hasInsert;
|
||||
}
|
||||
|
||||
public bool TryInsertBulb(ServerStorageComponent storage, IEntity? user = null)
|
||||
{
|
||||
if (storage.StoredEntities == null)
|
||||
return false;
|
||||
|
||||
var insertedBulbs = 0;
|
||||
var storagedEnts = storage.StoredEntities.ToArray();
|
||||
foreach (var ent in storagedEnts)
|
||||
{
|
||||
if (ent.TryGetComponent(out LightBulbComponent? bulb))
|
||||
{
|
||||
if (TryInsertBulb(bulb))
|
||||
insertedBulbs++;
|
||||
}
|
||||
}
|
||||
|
||||
// show some message if success
|
||||
if (insertedBulbs > 0 && user != null)
|
||||
{
|
||||
var msg = Loc.GetString("comp-light-replacer-refill-from-storage", ("light-replacer", Owner));
|
||||
user.PopupMessage(msg);
|
||||
}
|
||||
|
||||
return insertedBulbs > 0;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
[DataDefinition]
|
||||
public class LightReplacerEntity
|
||||
{
|
||||
[DataField("name", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? PrototypeName;
|
||||
|
||||
[DataField("amount")]
|
||||
public int Amount;
|
||||
|
||||
[DataField("type")]
|
||||
public LightBulbType Type;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Content.Server/Light/Components/MatchboxComponent.cs
Normal file
29
Content.Server/Light/Components/MatchboxComponent.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Smoking;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Light.Components
|
||||
{
|
||||
// TODO make changes in icons when different threshold reached
|
||||
// e.g. different icons for 10% 50% 100%
|
||||
[RegisterComponent]
|
||||
public class MatchboxComponent : Component, IInteractUsing
|
||||
{
|
||||
public override string Name => "Matchbox";
|
||||
|
||||
int IInteractUsing.Priority => 1;
|
||||
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Using.TryGetComponent<MatchstickComponent>(out var matchstick)
|
||||
&& matchstick.CurrentState == SharedBurningStates.Unlit)
|
||||
{
|
||||
matchstick.Ignite(eventArgs.User);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
96
Content.Server/Light/Components/MatchstickComponent.cs
Normal file
96
Content.Server/Light/Components/MatchstickComponent.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
#nullable enable
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Smoking;
|
||||
using Content.Shared.Temperature;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Light.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IHotItem))]
|
||||
public class MatchstickComponent : Component, IHotItem, IInteractUsing
|
||||
{
|
||||
public override string Name => "Matchstick";
|
||||
|
||||
private SharedBurningStates _currentState = SharedBurningStates.Unlit;
|
||||
|
||||
/// <summary>
|
||||
/// How long will matchstick last in seconds.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)] [DataField("duration")]
|
||||
private int _duration = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Sound played when you ignite the matchstick.
|
||||
/// </summary>
|
||||
[DataField("igniteSound")] private string? _igniteSound;
|
||||
|
||||
/// <summary>
|
||||
/// Point light component. Gives matches a glow in dark effect.
|
||||
/// </summary>
|
||||
[ComponentDependency]
|
||||
private readonly PointLightComponent? _pointLightComponent = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Current state to matchstick. Can be <code>Unlit</code>, <code>Lit</code> or <code>Burnt</code>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public SharedBurningStates CurrentState
|
||||
{
|
||||
get => _currentState;
|
||||
private set
|
||||
{
|
||||
_currentState = value;
|
||||
|
||||
if (_pointLightComponent != null)
|
||||
{
|
||||
_pointLightComponent.Enabled = _currentState == SharedBurningStates.Lit;
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
appearance.SetData(SmokingVisuals.Smoking, _currentState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IHotItem.IsCurrentlyHot()
|
||||
{
|
||||
return CurrentState == SharedBurningStates.Lit;
|
||||
}
|
||||
|
||||
public void Ignite(IEntity user)
|
||||
{
|
||||
// Play Sound
|
||||
if (!string.IsNullOrEmpty(_igniteSound))
|
||||
{
|
||||
SoundSystem.Play(Filter.Pvs(Owner), _igniteSound, Owner,
|
||||
AudioHelpers.WithVariation(0.125f).WithVolume(-0.125f));
|
||||
}
|
||||
|
||||
// Change state
|
||||
CurrentState = SharedBurningStates.Lit;
|
||||
Owner.SpawnTimer(_duration * 1000, () => CurrentState = SharedBurningStates.Burnt);
|
||||
}
|
||||
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Target.TryGetComponent<IHotItem>(out var hotItem)
|
||||
&& hotItem.IsCurrentlyHot()
|
||||
&& CurrentState == SharedBurningStates.Unlit)
|
||||
{
|
||||
Ignite(eventArgs.User);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
339
Content.Server/Light/Components/PoweredLightComponent.cs
Normal file
339
Content.Server/Light/Components/PoweredLightComponent.cs
Normal file
@@ -0,0 +1,339 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.Items;
|
||||
using Content.Server.MachineLinking.Components;
|
||||
using Content.Server.MachineLinking.Signals;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Temperature.Components;
|
||||
using Content.Shared.Actions.Behaviors;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Light;
|
||||
using Content.Shared.Notification;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Light.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that represents a wall light. It has a light bulb that can be replaced when broken.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class PoweredLightComponent : Component, IInteractHand, IInteractUsing, IMapInit, ISignalReceiver<bool>, ISignalReceiver<ToggleSignal>, IGhostBooAffected
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
public override string Name => "PoweredLight";
|
||||
|
||||
private static readonly TimeSpan _thunkDelay = TimeSpan.FromSeconds(2);
|
||||
// time to blink light when ghost made boo nearby
|
||||
private static readonly TimeSpan ghostBlinkingTime = TimeSpan.FromSeconds(10);
|
||||
private static readonly TimeSpan ghostBlinkingCooldown = TimeSpan.FromSeconds(60);
|
||||
|
||||
[ComponentDependency]
|
||||
private readonly AppearanceComponent? _appearance;
|
||||
|
||||
private TimeSpan _lastThunk;
|
||||
private TimeSpan? _lastGhostBlink;
|
||||
|
||||
[DataField("hasLampOnSpawn")]
|
||||
private bool _hasLampOnSpawn = true;
|
||||
|
||||
[ViewVariables] [DataField("on")]
|
||||
private bool _on = true;
|
||||
|
||||
[ViewVariables]
|
||||
private bool _currentLit;
|
||||
|
||||
[ViewVariables]
|
||||
private bool _isBlinking;
|
||||
|
||||
[ViewVariables] [DataField("ignoreGhostsBoo")]
|
||||
private bool _ignoreGhostsBoo;
|
||||
|
||||
[DataField("bulb")] private LightBulbType _bulbType = LightBulbType.Tube;
|
||||
public LightBulbType BulbType => _bulbType;
|
||||
|
||||
[ViewVariables] private ContainerSlot _lightBulbContainer = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public LightBulbComponent? LightBulb
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_lightBulbContainer.ContainedEntity == null) return null;
|
||||
|
||||
_lightBulbContainer.ContainedEntity.TryGetComponent(out LightBulbComponent? bulb);
|
||||
|
||||
return bulb;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO CONSTRUCTION make this use a construction graph
|
||||
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
return InsertBulb(eventArgs.Using);
|
||||
}
|
||||
|
||||
bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out IDamageableComponent? damageableComponent))
|
||||
{
|
||||
Eject();
|
||||
return false;
|
||||
}
|
||||
if(eventArgs.User.TryGetComponent(out HeatResistanceComponent? heatResistanceComponent))
|
||||
{
|
||||
if(CanBurn(heatResistanceComponent.GetHeatResistance()))
|
||||
{
|
||||
Burn();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Eject();
|
||||
return true;
|
||||
|
||||
bool CanBurn(int heatResistance)
|
||||
{
|
||||
if (LightBulb == null)
|
||||
return false;
|
||||
|
||||
return _currentLit && heatResistance < LightBulb.BurningTemperature;
|
||||
}
|
||||
|
||||
void Burn()
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("You burn your hand!"));
|
||||
damageableComponent.ChangeDamage(DamageType.Heat, 20, false, Owner);
|
||||
SoundSystem.Play(Filter.Pvs(Owner), "/Audio/Effects/lightburn.ogg", Owner);
|
||||
}
|
||||
|
||||
void Eject()
|
||||
{
|
||||
EjectBulb(eventArgs.User);
|
||||
UpdateLight();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to replace current bulb with a new one
|
||||
/// </summary>
|
||||
public bool ReplaceBulb(IEntity bulb)
|
||||
{
|
||||
EjectBulb();
|
||||
return InsertBulb(bulb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts the bulb if possible.
|
||||
/// </summary>
|
||||
/// <returns>True if it could insert it, false if it couldn't.</returns>
|
||||
private bool InsertBulb(IEntity bulb)
|
||||
{
|
||||
if (LightBulb != null) return false;
|
||||
if (!bulb.TryGetComponent(out LightBulbComponent? lightBulb)) return false;
|
||||
if (lightBulb.Type != _bulbType) return false;
|
||||
|
||||
var inserted = _lightBulbContainer.Insert(bulb);
|
||||
|
||||
lightBulb.OnLightBulbStateChange += UpdateLight;
|
||||
lightBulb.OnLightColorChange += UpdateLight;
|
||||
|
||||
UpdateLight();
|
||||
|
||||
return inserted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ejects the bulb to a mob's hand if possible.
|
||||
/// </summary>
|
||||
private void EjectBulb(IEntity? user = null)
|
||||
{
|
||||
if (LightBulb == null) return;
|
||||
|
||||
var bulb = LightBulb;
|
||||
|
||||
bulb.OnLightBulbStateChange -= UpdateLight;
|
||||
bulb.OnLightColorChange -= UpdateLight;
|
||||
|
||||
if (!_lightBulbContainer.Remove(bulb.Owner)) return;
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
if (!user.TryGetComponent(out HandsComponent? hands)
|
||||
|| !hands.PutInHand(bulb.Owner.GetComponent<ItemComponent>()))
|
||||
bulb.Owner.Transform.Coordinates = user.Transform.Coordinates;
|
||||
}
|
||||
else
|
||||
{
|
||||
bulb.Owner.Transform.Coordinates = Owner.Transform.Coordinates;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For attaching UpdateLight() to events.
|
||||
/// </summary>
|
||||
public void UpdateLight(object? sender, EventArgs? e)
|
||||
{
|
||||
UpdateLight();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the light's power drain, sprite and actual light state.
|
||||
/// </summary>
|
||||
public void UpdateLight()
|
||||
{
|
||||
var powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
|
||||
|
||||
if (LightBulb == null) // No light bulb.
|
||||
{
|
||||
_currentLit = false;
|
||||
powerReceiver.Load = 0;
|
||||
_appearance?.SetData(PoweredLightVisuals.BulbState, PoweredLightState.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (LightBulb.State)
|
||||
{
|
||||
case LightBulbState.Normal:
|
||||
if (powerReceiver.Powered && _on)
|
||||
{
|
||||
_currentLit = true;
|
||||
powerReceiver.Load = LightBulb.PowerUse;
|
||||
_appearance?.SetData(PoweredLightVisuals.BulbState, PoweredLightState.On);
|
||||
_appearance?.SetData(PoweredLightVisuals.BulbColor, LightBulb.Color);
|
||||
var time = _gameTiming.CurTime;
|
||||
if (time > _lastThunk + _thunkDelay)
|
||||
{
|
||||
_lastThunk = time;
|
||||
SoundSystem.Play(Filter.Pvs(Owner), "/Audio/Machines/light_tube_on.ogg", Owner, AudioParams.Default.WithVolume(-10f));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentLit = false;
|
||||
_appearance?.SetData(PoweredLightVisuals.BulbState, PoweredLightState.Off);
|
||||
}
|
||||
break;
|
||||
case LightBulbState.Broken:
|
||||
_currentLit = false;
|
||||
_appearance?.SetData(PoweredLightVisuals.BulbState, PoweredLightState.Broken);
|
||||
break;
|
||||
case LightBulbState.Burned:
|
||||
_currentLit = false;
|
||||
_appearance?.SetData(PoweredLightVisuals.BulbState, PoweredLightState.Burned);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_lightBulbContainer = ContainerHelpers.EnsureContainer<ContainerSlot>(Owner, "light_bulb");
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
switch (message)
|
||||
{
|
||||
case PowerChangedMessage:
|
||||
UpdateLight();
|
||||
break;
|
||||
case DamageChangedMessage msg:
|
||||
TryDestroyBulb(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void TryDestroyBulb(DamageChangedMessage msg)
|
||||
{
|
||||
if (!msg.TookDamage)
|
||||
return;
|
||||
|
||||
if (LightBulb == null || LightBulb.State == LightBulbState.Broken)
|
||||
return;
|
||||
|
||||
LightBulb.State = LightBulbState.Broken;
|
||||
LightBulb.PlayBreakSound();
|
||||
UpdateLight();
|
||||
}
|
||||
|
||||
void IMapInit.MapInit()
|
||||
{
|
||||
if (_hasLampOnSpawn)
|
||||
{
|
||||
var prototype = _bulbType switch
|
||||
{
|
||||
LightBulbType.Bulb => "LightBulb",
|
||||
LightBulbType.Tube => "LightTube",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
var entity = Owner.EntityManager.SpawnEntity(prototype, Owner.Transform.Coordinates);
|
||||
_lightBulbContainer.Insert(entity);
|
||||
}
|
||||
|
||||
// need this to update visualizers
|
||||
UpdateLight();
|
||||
}
|
||||
|
||||
public void TriggerSignal(bool signal)
|
||||
{
|
||||
_on = signal;
|
||||
UpdateLight();
|
||||
}
|
||||
|
||||
public void TriggerSignal(ToggleSignal signal)
|
||||
{
|
||||
_on = !_on;
|
||||
UpdateLight();
|
||||
}
|
||||
|
||||
public void ToggleBlinkingLight(bool isNowBlinking)
|
||||
{
|
||||
if (_isBlinking == isNowBlinking)
|
||||
return;
|
||||
|
||||
_isBlinking = isNowBlinking;
|
||||
_appearance?.SetData(PoweredLightVisuals.Blinking, _isBlinking);
|
||||
}
|
||||
|
||||
public bool AffectedByGhostBoo(InstantActionEventArgs args)
|
||||
{
|
||||
if (_ignoreGhostsBoo)
|
||||
return false;
|
||||
|
||||
// check cooldown first to prevent abuse
|
||||
var time = _gameTiming.CurTime;
|
||||
if (_lastGhostBlink != null)
|
||||
{
|
||||
if (time <= _lastGhostBlink + ghostBlinkingCooldown)
|
||||
return false;
|
||||
}
|
||||
_lastGhostBlink = time;
|
||||
|
||||
ToggleBlinkingLight(true);
|
||||
Owner.SpawnTimer(ghostBlinkingTime, () => {
|
||||
ToggleBlinkingLight(false);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
Content.Server/Light/EntitySystems/EmergencyLightSystem.cs
Normal file
45
Content.Server/Light/EntitySystems/EmergencyLightSystem.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Light.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Light.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class EmergencyLightSystem : EntitySystem
|
||||
{
|
||||
private readonly HashSet<EmergencyLightComponent> _activeLights = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<EmergencyLightMessage>(HandleEmergencyLightMessage);
|
||||
}
|
||||
|
||||
private void HandleEmergencyLightMessage(EmergencyLightMessage message)
|
||||
{
|
||||
switch (message.State)
|
||||
{
|
||||
case EmergencyLightComponent.EmergencyLightState.On:
|
||||
case EmergencyLightComponent.EmergencyLightState.Charging:
|
||||
_activeLights.Add(message.Component);
|
||||
break;
|
||||
case EmergencyLightComponent.EmergencyLightState.Full:
|
||||
case EmergencyLightComponent.EmergencyLightState.Empty:
|
||||
_activeLights.Remove(message.Component);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var activeLight in _activeLights)
|
||||
{
|
||||
activeLight.OnUpdate(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Content.Server/Light/EntitySystems/ExpendableLightSystem.cs
Normal file
18
Content.Server/Light/EntitySystems/ExpendableLightSystem.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Content.Server.Light.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Light.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class ExpendableLightSystem : EntitySystem
|
||||
{
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var light in ComponentManager.EntityQuery<ExpendableLightComponent>(true))
|
||||
{
|
||||
light.Update(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
Content.Server/Light/EntitySystems/HandHeldLightSystem.cs
Normal file
50
Content.Server/Light/EntitySystems/HandHeldLightSystem.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Light.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Light.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class HandHeldLightSystem : EntitySystem
|
||||
{
|
||||
// TODO: Ideally you'd be able to subscribe to power stuff to get events at certain percentages.. or something?
|
||||
// But for now this will be better anyway.
|
||||
private HashSet<HandheldLightComponent> _activeLights = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ActivateHandheldLightMessage>(HandleActivate);
|
||||
SubscribeLocalEvent<DeactivateHandheldLightMessage>(HandleDeactivate);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_activeLights.Clear();
|
||||
UnsubscribeLocalEvent<ActivateHandheldLightMessage>();
|
||||
UnsubscribeLocalEvent<DeactivateHandheldLightMessage>();
|
||||
}
|
||||
|
||||
private void HandleActivate(ActivateHandheldLightMessage message)
|
||||
{
|
||||
_activeLights.Add(message.Component);
|
||||
}
|
||||
|
||||
private void HandleDeactivate(DeactivateHandheldLightMessage message)
|
||||
{
|
||||
_activeLights.Remove(message.Component);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var handheld in _activeLights.ToArray())
|
||||
{
|
||||
if (handheld.Deleted || handheld.Paused) continue;
|
||||
handheld.OnUpdate(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
64
Content.Server/Light/EntitySystems/LightReplacerSystem.cs
Normal file
64
Content.Server/Light/EntitySystems/LightReplacerSystem.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
#nullable enable
|
||||
using Content.Server.Light.Components;
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Interaction;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Light.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class LightReplacerSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<LightReplacerComponent, InteractUsingEvent>(HandleInteract);
|
||||
SubscribeLocalEvent<LightReplacerComponent, AfterInteractEvent>(HandleAfterInteract);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
UnsubscribeLocalEvent<LightReplacerComponent, InteractUsingEvent>(HandleInteract);
|
||||
UnsubscribeLocalEvent<LightReplacerComponent, AfterInteractEvent>(HandleAfterInteract);
|
||||
}
|
||||
|
||||
private void HandleAfterInteract(EntityUid uid, LightReplacerComponent component, AfterInteractEvent eventArgs)
|
||||
{
|
||||
// standard interaction checks
|
||||
if (!ActionBlockerSystem.CanUse(eventArgs.User)) return;
|
||||
if (!eventArgs.CanReach) return;
|
||||
|
||||
// behaviour will depends on target type
|
||||
if (eventArgs.Target != null)
|
||||
{
|
||||
// replace broken light in fixture?
|
||||
if (eventArgs.Target.TryGetComponent(out PoweredLightComponent? fixture))
|
||||
component.TryReplaceBulb(fixture, eventArgs.User);
|
||||
// add new bulb to light replacer container?
|
||||
else if (eventArgs.Target.TryGetComponent(out LightBulbComponent? bulb))
|
||||
component.TryInsertBulb(bulb, eventArgs.User, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleInteract(EntityUid uid, LightReplacerComponent component, InteractUsingEvent eventArgs)
|
||||
{
|
||||
// standard interaction checks
|
||||
if (!ActionBlockerSystem.CanInteract(eventArgs.User)) return;
|
||||
|
||||
if (eventArgs.Used != null)
|
||||
{
|
||||
// want to insert a new light bulb?
|
||||
if (eventArgs.Used.TryGetComponent(out LightBulbComponent? bulb))
|
||||
component.TryInsertBulb(bulb, eventArgs.User, true);
|
||||
// add bulbs from storage?
|
||||
else if (eventArgs.Used.TryGetComponent(out ServerStorageComponent? storage))
|
||||
component.TryInsertBulb(storage, eventArgs.User);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user