rott/kf_sources/KFMod/Classes/Effect_ShadowController.uc
2026-07-14 20:27:09 +07:00

259 lines
7.1 KiB
Ucode

//=============================================================================
// © 2004 Matt 'SquirrelZero' Farber
//=============================================================================
// This is a master class for controlling all shadows. This is needed to spawn
// and determine the visibility of all shadows. It also keeps track of all the
// nearby lights.
//=============================================================================
// JCBDigger
// I have made all the functions simulated
// I have included a line to destroy the shadows if the Pawn no longer exists
class Effect_ShadowController extends Actor
config(User);
// the type of shadow projector we're going to use
var class<Effect_ShadowProjector> ShadowClass;
// an array of shadows, the maximum allowed per player can be specified below
var array<Effect_ShadowProjector> Shadows;
// a special variable type, lastlight is used for fading
struct LightGroup
{
var Actor CurrentLight;
var Actor LastLight;
};
// an array of lightgroups, filled when spawned
var array<LightGroup> Lights;
// the maximum distance a light can be from a player to cast a shadow
var float MaxLightDistance;
// the sun
var SunLight SunLightActor;
// turns on/off all shadows
var bool bShadowActive;
// maximum allowed shadows per player, configurable
var globalconfig int MaxShadows;
// how crisp (detailed) the shadows should be. The higher, the better
// they look, but the worse they perform.
// Low = 128x128
// Medium = 256
// High = 512
// Maximum = 1024 <- can any modern video technology even run it at maximum?
var globalconfig enum CrispnessEnum
{
Low,
Medium,
High,
Maximum
}ShadowCrispness;
simulated function Initialize()
{
local SunLight LightActor;
local array<SunLight> SunLights;
local int i;
// set the sunlight, pick the brightest one if we have multiple
foreach AllActors(class'SunLight',LightActor)
{
if (LightActor == None)
continue;
SunLights[i] = LightActor;
i++;
}
for (i=0;i<SunLights.Length;i++)
{
if (SunLightActor == None || SunLightActor.LightBrightness < SunLights[i].LightBrightness)
SunLightActor = SunLights[i];
}
// fill the arrays with placeholders
Shadows.Insert(0,MaxShadows);
Lights.Insert(0,MaxShadows);
// enable
bShadowActive = true;
// build light array
FillLights();
}
simulated function Timer()
{
FillLights();
}
simulated function FillLights()
{
local int i;
local actor LightActor;
local array<Actor> OrigLight;
// clear array of lights, leave LastLight alone to fade
for (i=0;i<MaxShadows;i++)
{
OrigLight[i] = Lights[i].CurrentLight;
Lights[i].CurrentLight = None;
}
if ( Instigator != None ) // JCBDigger
{
// set the location of the controller, for light detection purposes
SetLocation(Instigator.Location);
// shadow is off
if (!bShadowActive)
return;
// build the array of lights, we prioritize by both brightness and distance
foreach RadiusActors(class'Actor',LightActor,MaxLightDistance)
{
// if we are within the light's radius, and it's shining on at least one part of us, then add it to the array
if (LightActor != None && LightActor.LightType != LT_None && (LightActor.bStatic || LightActor.bDynamicLight) && LightActor.LightEffect != LE_Sunlight && IsVisible(LightActor.Location) && LightActor.LightBrightness > 1 && LightActor.LightRadius >= (VSize(LightActor.Location-Instigator.Location)*0.041)) // (0.041 is not perfect)
{
if (Lights[0].CurrentLight != None && LightPriority(Lights[0].CurrentLight.LightBrightness,Lights[0].CurrentLight.LightRadius,VSize(Lights[0].CurrentLight.Location-Instigator.Location)) > LightPriority(LightActor.LightBrightness,LightActor.LightRadius,VSize(LightActor.Location-Instigator.Location)))
continue;
// this puts them in order of priority
for (i=1;i<MaxShadows;i++)
Lights[i].CurrentLight = Lights[i-1].CurrentLight;
Lights[0].CurrentLight = LightActor;
}
}
// we'll use the sunlight as the very lowest priority of lights
for (i=0;i<MaxShadows;i++)
if (Lights[i].CurrentLight == None && SunlightActor != None && (IsVisible(SunLightActor.Location) || Lights[0].CurrentLight == None))
{
Lights[i].CurrentLight = SunLightActor;
break;
}
// set up last light for fading
for (i=0;i<MaxShadows;i++)
if (OrigLight[i] != Lights[i].CurrentLight)
Lights[i].LastLight = OrigLight[i];
// loop it, loop it good
SetTimer(0.05,false);
}
else
{
// if we don't have a pawn we should not exist
Destroy(); // JCBDigger
}
}
// simple function for determining the priority of a light
simulated function float LightPriority(float Brightness, float Radius, float Distance)
{
local float Priority;
// brightness takes higher priority
Priority = (Brightness*10)/Distance;
// lights that are very close get higher priority
if (Distance < 0.1 * MaxLightDistance)
Priority *= 1.5;
// lights with very small radii shouldn't really cast shadows
if (Radius < 2.0)
Priority *= (Radius*0.38);
return Priority;
}
simulated function bool IsVisible(vector Loc)
{
local vector FootLocation;
if ( Instigator != None ) // JCBDigger
{
// get a location near the feet
FootLocation = Instigator.Location;
FootLocation.Z -= Instigator.CollisionHeight*0.49;
// not very clean, returns true if either the head, feet, or middle torso of the player is visible to Loc
if (FastTrace(Loc,Instigator.Location) || FastTrace(Loc,Instigator.GetBoneCoords('head').Origin) || FastTrace(Loc,FootLocation))
return true;
else return false;
}
else return false;
}
simulated function Tick(float dt)
{
// fallback
if (Instigator == None)
return;
// update all shadows
UpdateShadows(dt);
}
simulated function UpdateShadows(float dt)
{
local int i;
for (i=0;i<Lights.Length;i++)
{
// disable the shadow attached to this slot if light no longer active, or if manually made inactive
if ((Lights[i].CurrentLight == None && Lights[i].LastLight == None) || !bShadowActive)
{
if (Shadows[i] != None)
Shadows[i].DisableShadow();
continue;
}
// spawn a new shadow if it doesn't already exist
if (Shadows[i] == None)
Shadows[i] = SpawnShadow(rotator(Lights[i].CurrentLight.Location-Location));
// update each shadow
Shadows[i].UpdateShadow(dt,i,self);
}
}
simulated function Effect_ShadowProjector SpawnShadow(rotator LightRotation)
{
local Effect_ShadowProjector NewShadow;
// spawn and initialize a shadow projector
NewShadow = spawn(ShadowClass,Instigator,,Location,LightRotation);
NewShadow.Disable('Tick');
NewShadow.InitializeFor(self);
return NewShadow;
}
simulated function Destroyed()
{
local int i;
Disable('Tick');
// destroy all shadows
for (i=0;i<MaxShadows;i++)
if (Shadows[i] != None)
Shadows[i].Destroy();
}
defaultproperties
{
ShadowClass=Class'KFMod.Effect_ShadowProjector'
MaxLightDistance=1000.000000
MaxShadows=2
DrawType=DT_None
bHidden=True
bAlwaysRelevant=True
RemoteRole=ROLE_SimulatedProxy
}