Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs
2025-04-16 12:46:39 +03:00

479 lines
16 KiB
C#

using Microsoft.Xna.Framework;
using System;
using Barotrauma.Networking;
using Barotrauma.Extensions;
#if CLIENT
using Microsoft.Xna.Framework.Graphics;
using Barotrauma.Lights;
#endif
namespace Barotrauma.Items.Components
{
partial class LightComponent : Powered, IServerSerializable, IDrawableComponent
{
private Color lightColor;
/// <summary>
/// The current brightness of the light source, affected by powerconsumption/voltage
/// </summary>
private float lightBrightness;
private float blinkFrequency;
private float pulseFrequency, pulseAmount;
private float range;
private float flicker, flickerSpeed;
private bool castShadows;
private bool drawBehindSubs;
private double lastToggleSignalTime;
private string prevColorSignal;
public PhysicsBody ParentBody;
private bool isOn;
private Turret turret;
[Serialize(100.0f, IsPropertySaveable.Yes, description: "The range of the emitted light. Higher values are more performance-intensive.", alwaysUseInstanceValues: true),
Editable(MinValueFloat = 0.0f, MaxValueFloat = 2048.0f)]
public float Range
{
get { return range; }
set
{
range = MathHelper.Clamp(value, 0.0f, 4096.0f);
#if CLIENT
item.ResetCachedVisibleSize();
if (Light != null) { Light.Range = range; }
#endif
}
}
private float rotation;
public float Rotation
{
get { return rotation; }
set
{
rotation = value;
SetLightSourceTransformProjSpecific();
}
}
[Editable, Serialize(true, IsPropertySaveable.Yes, description: "Should structures cast shadows when light from this light source hits them. " +
"Disabling shadows increases the performance of the game, and is recommended for lights with a short range. Lights that are set to be drawn behind subs don't cast shadows, regardless of this setting.", alwaysUseInstanceValues: true)]
public bool CastShadows
{
get { return castShadows; }
set
{
castShadows = value;
#if CLIENT
if (Light != null) Light.CastShadows = value;
#endif
}
}
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Lights drawn behind submarines don't cast any shadows and are much faster to draw than shadow-casting lights. " +
"It's recommended to enable this on decorative lights outside the submarine's hull.", alwaysUseInstanceValues: true)]
public bool DrawBehindSubs
{
get { return drawBehindSubs; }
set
{
drawBehindSubs = value;
#if CLIENT
if (Light != null) Light.IsBackground = drawBehindSubs;
#endif
}
}
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Is the light currently on.", alwaysUseInstanceValues: true)]
public bool IsOn
{
get { return isOn; }
set
{
if (isOn == value && IsActive == value) { return; }
IsActive = isOn = value;
bool isLightOn = isOn && item.Condition > 0;
SetLightSourceState(isLightOn, isLightOn ? lightBrightness : 0.0f);
OnStateChanged();
}
}
[Editable, Serialize(0.0f, IsPropertySaveable.No, description: "How heavily the light flickers. 0 = no flickering, 1 = the light will alternate between completely dark and full brightness.")]
public float Flicker
{
get { return flicker; }
set
{
flicker = MathHelper.Clamp(value, 0.0f, 1.0f);
#if CLIENT
if (Light != null) { Light.LightSourceParams.Flicker = flicker; }
#endif
}
}
[Editable, Serialize(1.0f, IsPropertySaveable.No, description: "How fast the light flickers.")]
public float FlickerSpeed
{
get { return flickerSpeed; }
set
{
flickerSpeed = value;
#if CLIENT
if (Light != null) { Light.LightSourceParams.FlickerSpeed = flickerSpeed; }
#endif
}
}
[Editable, Serialize(0.0f, IsPropertySaveable.Yes, description: "How rapidly the light pulsates (in Hz). 0 = no blinking.")]
public float PulseFrequency
{
get { return pulseFrequency; }
set
{
pulseFrequency = MathHelper.Clamp(value, 0.0f, 60.0f);
#if CLIENT
if (Light != null) { Light.LightSourceParams.PulseFrequency = pulseFrequency; }
#endif
}
}
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f, DecimalCount = 2), Serialize(0.0f, IsPropertySaveable.Yes, description: "How much light pulsates (in Hz). 0 = not at all, 1 = alternates between full brightness and off.")]
public float PulseAmount
{
get { return pulseAmount; }
set
{
pulseAmount = MathHelper.Clamp(value, 0.0f, 1.0f);
#if CLIENT
if (Light != null) { Light.LightSourceParams.PulseAmount = pulseAmount; }
#endif
}
}
[Editable, Serialize(0.0f, IsPropertySaveable.Yes, description: "How rapidly the light blinks on and off (in Hz). 0 = no blinking.")]
public float BlinkFrequency
{
get { return blinkFrequency; }
set
{
blinkFrequency = MathHelper.Clamp(value, 0.0f, 60.0f);
#if CLIENT
if (Light != null) { Light.LightSourceParams.BlinkFrequency = blinkFrequency; }
#endif
}
}
[InGameEditable(FallBackTextTag = "connection.setcolor"), Serialize("255,255,255,255", IsPropertySaveable.Yes, description: "The color of the emitted light (R,G,B,A).", alwaysUseInstanceValues: true)]
public Color LightColor
{
get { return lightColor; }
set
{
lightColor = value;
//reset previously received signal to force updating the color if we receive a set_color signal after the color has been modified manually
prevColorSignal = string.Empty;
#if CLIENT
if (Light != null)
{
Light.Color = IsOn ? lightColor.Multiply(lightColorMultiplier) : Color.Transparent;
}
#endif
}
}
[Serialize(false, IsPropertySaveable.No, description: "If enabled, the component will ignore continuous signals received in the toggle input (i.e. a continuous signal will only toggle it once).")]
public bool IgnoreContinuousToggle
{
get;
set;
}
[Serialize(true, IsPropertySaveable.No, description: "Should the light sprite be drawn on the item using alpha blending, in addition to being rendered in the light map? Can be used to make the light sprite stand out more.")]
public bool AlphaBlend
{
get;
set;
}
[Serialize("0,0", IsPropertySaveable.No, description: "Offset of the light from the position of the item (in pixels).")]
public Vector2 LightOffset
{
get;
set;
}
/// <summary>
/// Returns true if the red component of the light is twice as bright as the blue and green. Can be used by StatusEffects.
/// </summary>
public bool IsRed => ColorExtensions.IsRedDominant(LightColor);
/// <summary>
/// Returns true if the green component of the light is twice as bright as the red and blue. Can be used by StatusEffects.
/// </summary>
public bool IsGreen => ColorExtensions.IsGreenDominant(LightColor);
/// <summary>
/// Returns true if the blue component of the light is twice as bright as the red and green. Can be used by StatusEffects.
/// </summary>
public bool IsBlue => ColorExtensions.IsBlueDominant(LightColor);
public float TemporaryFlickerTimer;
public override void Move(Vector2 amount, bool ignoreContacts = false)
{
#if CLIENT
Light.Position += amount;
#endif
}
public override bool IsActive
{
get
{
return base.IsActive;
}
set
{
if (base.IsActive == value) { return; }
base.IsActive = isOn = value;
SetLightSourceState(value, value ? lightBrightness : 0.0f);
}
}
public LightComponent(Item item, ContentXElement element)
: base(item, element)
{
#if CLIENT
Light = new LightSource(element)
{
ParentSub = item.CurrentHull?.Submarine,
Position = item.Position,
CastShadows = castShadows,
IsBackground = drawBehindSubs,
SpriteScale = Vector2.One * item.Scale * LightSpriteScale,
Range = range
};
Light.LightSourceParams.Flicker = flicker;
Light.LightSourceParams.FlickerSpeed = FlickerSpeed;
Light.LightSourceParams.PulseAmount = pulseAmount;
Light.LightSourceParams.PulseFrequency = pulseFrequency;
Light.LightSourceParams.BlinkFrequency = blinkFrequency;
#endif
IsActive = IsOn;
}
public override void OnItemLoaded()
{
base.OnItemLoaded();
SetLightSourceState(IsActive, lightBrightness);
turret = item.GetComponent<Turret>();
if (item.body != null)
{
item.body.FarseerBody.OnEnabled += CheckIfNeedsUpdate;
item.body.FarseerBody.OnDisabled += CheckIfNeedsUpdate;
}
#if CLIENT
Drawable = AlphaBlend && Light.LightSprite != null;
if (Screen.Selected.IsEditor)
{
OnMapLoaded();
}
#endif
}
public override void OnMapLoaded()
{
#if CLIENT
if (item.IsHidden)
{
Light.Enabled = false;
}
#endif
CheckIfNeedsUpdate();
}
public void CheckIfNeedsUpdate()
{
if (!IsOn)
{
base.IsActive = false;
return;
}
if ((item.body == null || !item.body.Enabled) &&
powerConsumption <= 0.0f && Parent == null && turret == null &&
(statusEffectLists == null || !statusEffectLists.ContainsKey(ActionType.OnActive)) &&
(IsActiveConditionals == null || IsActiveConditionals.Count == 0))
{
PhysicsBody body = ParentBody ?? item.body;
if ((body == null || !body.Enabled) && !IsVisibleInInventory())
{
lightBrightness = 0.0f;
SetLightSourceState(false, 0.0f);
}
else
{
lightBrightness = 1.0f;
SetLightSourceState(true, lightBrightness);
}
isOn = true;
SetLightSourceTransformProjSpecific();
base.IsActive = false;
#if CLIENT
Light.ParentSub = item.Submarine;
#endif
}
else
{
base.IsActive = true;
}
}
/// <summary>
/// Is the item currently in an inventory, and visible in that inventory? E.g. held by a character or on a shelf that shows the contained items.
/// </summary>
/// <returns></returns>
private bool IsVisibleInInventory()
{
if (item.GetRootInventoryOwner() is Character ownerCharacter && item.RootContainer?.GetComponent<Holdable>() is not { IsActive: true })
{
//if the item is in a character inventory, the light should only be visible if the character is holding the item
//(not if it's e.q. inside a wearable item, or in a rifle worn on the back)
return false;
}
else
{
return item.FindParentInventory(static it => it is ItemInventory { Container.HideItems: true }) == null;
}
}
public override void Update(float deltaTime, Camera cam)
{
if (item.AiTarget != null)
{
UpdateAITarget(item.AiTarget);
}
UpdateOnActiveEffects(deltaTime);
//something in UpdateOnActiveEffects may deactivate the light -> return so we don't turn it back on
if (!IsActive) { return; }
#if CLIENT
Light.ParentSub = item.Submarine;
#endif
bool isVisibleInInventory = IsVisibleInInventory();
var ownerCharacter = item.GetRootInventoryOwner() as Character;
if ((item.Container != null && !isVisibleInInventory && ownerCharacter == null) ||
(ownerCharacter != null && ownerCharacter.InvisibleTimer > 0.0f))
{
lightBrightness = 0.0f;
SetLightSourceState(false, 0.0f);
return;
}
SetLightSourceTransformProjSpecific();
PhysicsBody body = ParentBody ?? item.body;
if ((body == null || !body.Enabled) && !isVisibleInInventory)
{
lightBrightness = 0.0f;
SetLightSourceState(false, 0.0f);
return;
}
TemporaryFlickerTimer -= deltaTime;
//currPowerConsumption = powerConsumption;
if (Rand.Range(0.0f, 1.0f) < 0.05f && (Voltage < Rand.Range(0.0f, MinVoltage) || TemporaryFlickerTimer > 0.0f))
{
#if CLIENT
if (Voltage > 0.1f)
{
SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull);
}
#endif
lightBrightness = 0.0f;
}
else
{
lightBrightness = MathHelper.Lerp(lightBrightness, powerConsumption <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f), 0.1f);
}
SetLightSourceState(true, lightBrightness);
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
SetLightSourceState(false, 0.0f);
}
public override bool Use(float deltaTime, Character character = null)
{
return true;
}
partial void OnStateChanged();
public override void ReceiveSignal(Signal signal, Connection connection)
{
switch (connection.Name)
{
case "toggle":
if (signal.value != "0")
{
if (!IgnoreContinuousToggle || lastToggleSignalTime < Timing.TotalTime - 0.1)
{
IsOn = !IsOn;
}
lastToggleSignalTime = Timing.TotalTime;
}
break;
case "set_state":
IsOn = signal.value != "0";
break;
case "set_color":
if (signal.value != prevColorSignal)
{
LightColor = XMLExtensions.ParseColor(signal.value, false);
#if CLIENT
SetLightSourceState(Light.Enabled, lightColorMultiplier);
#endif
prevColorSignal = signal.value;
}
break;
}
}
private void UpdateAITarget(AITarget target)
{
if (!IsActive) { return; }
if (target.MaxSightRange <= 0)
{
target.MaxSightRange = Range * 5;
}
target.SightRange = Math.Max(target.SightRange, target.MaxSightRange * lightBrightness);
}
public override void Drop(Character dropper, bool setTransform = true)
{
SetLightSourceTransform();
}
partial void SetLightSourceState(bool enabled, float brightness);
public void SetLightSourceTransform()
{
SetLightSourceTransformProjSpecific();
}
partial void SetLightSourceTransformProjSpecific();
}
}