479 lines
16 KiB
C#
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();
|
|
}
|
|
}
|