Re-organize all projects (#4166)

This commit is contained in:
DrSmugleaf
2021-06-09 22:19:39 +02:00
committed by GitHub
parent 9f50e4061b
commit ff1a2d97ea
1773 changed files with 5258 additions and 5508 deletions

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

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

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

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

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

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

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

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

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

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

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

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

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