Files
ProjectEleri/Plugins/StevesUEHelpers/Source/StevesUEHelpers/Private/StevesLightFlicker.cpp

203 lines
5.7 KiB
C++

// Copyright Steve Streeting
// Licensed under the MIT License (see License.txt)
#include "StevesLightFlicker.h"
#include "Net/UnrealNetwork.h"
TMap<EStevesLightFlickerPattern, FRichCurve> UStevesLightFlickerHelper::Curves;
TMap<FString, FRichCurve> UStevesLightFlickerHelper::CustomCurves;
FCriticalSection UStevesLightFlickerHelper::CriticalSection;
const TMap<EStevesLightFlickerPattern, FString> UStevesLightFlickerHelper::StandardPatterns {
// Quake lighting flicker functions
// https://github.com/id-Software/Quake/blob/bf4ac424ce754894ac8f1dae6a3981954bc9852d/qw-qc/world.qc#L328-L372
{ EStevesLightFlickerPattern::Flicker1, TEXT("mmnmmommommnonmmonqnmmo") },
{ EStevesLightFlickerPattern::SlowStrongPulse, TEXT("abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba") },
{ EStevesLightFlickerPattern::Candle1, TEXT("mmmmmaaaaammmmmaaaaaabcdefgabcdefg") },
{ EStevesLightFlickerPattern::FastStrobe, TEXT("mamamamamama") },
{ EStevesLightFlickerPattern::GentlePulse1, TEXT("jklmnopqrstuvwxyzyxwvutsrqponmlkj") },
{ EStevesLightFlickerPattern::Flicker2, TEXT("nmonqnmomnmomomno") },
{ EStevesLightFlickerPattern::Candle2, TEXT("mmmaaaabcdefgmmmmaaaammmaamm") },
{ EStevesLightFlickerPattern::Candle3, TEXT("mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa") },
{ EStevesLightFlickerPattern::SlowStrobe, TEXT("aaaaaaaazzzzzzzz") },
{ EStevesLightFlickerPattern::FlourescentFlicker, TEXT("mmamammmmammamamaaamammma") },
{ EStevesLightFlickerPattern::SlowPulseNoBlack, TEXT("abcdefghijklmnopqrrqponmlkjihgfedcba") },
/// Eniko's torch pattern from Kitsune Tails
{ EStevesLightFlickerPattern::Torch1, TEXT("mnkjcfcdafdehifkjlm") },
/// No black version of Torch1
{ EStevesLightFlickerPattern::Torch2, TEXT("mnkjcfcecfdehifkjlm") }
};
float UStevesLightFlickerHelper::EvaluateLightCurve(EStevesLightFlickerPattern CurveType, float Time)
{
return GetLightCurve(CurveType).Eval(Time);
}
const FRichCurve& UStevesLightFlickerHelper::GetLightCurve(EStevesLightFlickerPattern CurveType)
{
FScopeLock ScopeLock(&CriticalSection);
if (auto pCurve = Curves.Find(CurveType))
{
return *pCurve;
}
auto& Curve = Curves.Emplace(CurveType);
BuildCurve(CurveType, Curve);
return Curve;
}
const FRichCurve& UStevesLightFlickerHelper::GetLightCurve(const FString& CurveStr)
{
FScopeLock ScopeLock(&CriticalSection);
if (auto pCurve = CustomCurves.Find(CurveStr))
{
return *pCurve;
}
auto& Curve = CustomCurves.Emplace(CurveStr);
BuildCurve(CurveStr, Curve);
return Curve;
}
void UStevesLightFlickerHelper::BuildCurve(EStevesLightFlickerPattern CurveType, FRichCurve& OutCurve)
{
if (auto pTxt = StandardPatterns.Find(CurveType))
{
BuildCurve(*pTxt, OutCurve);
}
}
void UStevesLightFlickerHelper::BuildCurve(const FString& QuakeCurveChars, FRichCurve& OutCurve)
{
OutCurve.Reset();
for (int i = 0; i < QuakeCurveChars.Len(); ++i)
{
// We actually build the curve a..z = 0..1, and then use a default max value of 2 to restore the original behaviour.
// Actually the curve is 0..1.04 due to original behaviour that z is 2.08 not 2
const int CharIndex = QuakeCurveChars[i] - 'a';
const float Val = (float)CharIndex / 24.f; // to ensure m==1, z==2.08 (rescaled to half that so 0..1.04)
// Quake default was each character was 0.1s
OutCurve.AddKey(i * 0.1f, Val);
}
// To catch empty
if (QuakeCurveChars.IsEmpty())
{
OutCurve.AddKey(0, 1);
}
}
UStevesLightFlickerComponent::UStevesLightFlickerComponent(const FObjectInitializer& Initializer):
Super(Initializer),
TimePos(0),
CurrentValue(0),
Curve(nullptr)
{
PrimaryComponentTick.bCanEverTick = true;
PrimaryComponentTick.bTickEvenWhenPaused = false;
PrimaryComponentTick.bStartWithTickEnabled = false;
}
void UStevesLightFlickerComponent::BeginPlay()
{
Super::BeginPlay();
GenerateCurveAndPlay();
}
void UStevesLightFlickerComponent::GenerateCurveAndPlay()
{
if (FlickerPattern == EStevesLightFlickerPattern::Custom)
{
Curve = &UStevesLightFlickerHelper::GetLightCurve(CustomFlickerPattern);
}
else
{
Curve = &UStevesLightFlickerHelper::GetLightCurve(FlickerPattern);
}
TimePos = 0;
if (bAutoPlay)
{
Play();
}
}
void UStevesLightFlickerComponent::ValueUpdate()
{
CurrentValue = FMath::Lerp(MinValue, MaxValue, Curve->Eval(TimePos));
OnLightFlickerUpdate.Broadcast(CurrentValue);
}
void UStevesLightFlickerComponent::Play(bool bResetTime)
{
if (GetOwnerRole() == ROLE_Authority || !GetIsReplicated())
{
if (bResetTime)
{
TimePos = 0;
}
ValueUpdate();
PrimaryComponentTick.SetTickFunctionEnable(true);
}
}
void UStevesLightFlickerComponent::Pause()
{
if (GetOwnerRole() == ROLE_Authority || !GetIsReplicated())
{
PrimaryComponentTick.SetTickFunctionEnable(false);
}
}
float UStevesLightFlickerComponent::GetCurrentValue() const
{
return CurrentValue;
}
void UStevesLightFlickerComponent::OnRep_TimePos()
{
ValueUpdate();
}
void UStevesLightFlickerComponent::TickComponent(float DeltaTime,
ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
TimePos += DeltaTime * Speed;
const float MaxTime = Curve->GetLastKey().Time;
while (TimePos > MaxTime)
{
TimePos -= MaxTime;
}
ValueUpdate();
}
void UStevesLightFlickerComponent::SetFlickerPattern(EStevesLightFlickerPattern Pattern,
const FString& CustomPatternString)
{
FlickerPattern = Pattern;
CustomFlickerPattern = CustomPatternString;
GenerateCurveAndPlay();
}
EStevesLightFlickerPattern UStevesLightFlickerComponent::GetFlickerPattern(FString& CustomString)
{
CustomString = CustomFlickerPattern;
return FlickerPattern;
}
void UStevesLightFlickerComponent::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UStevesLightFlickerComponent, TimePos);
}