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;
// an array of static lights
var array<Light> LightList;
// 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;
// update frequency for filling light array
var float UpdateFrequency;
// the sun
var SunLight SunLightActor;
// turns on/off all shadows
var bool bShadowActive;
// maximum allowed shadows per player, configurable
var globalconfig int MaxShadows;
simulated function Initialize() {
local Light LightActor;
local array<SunLight> SunLights;
local int i;
// set the sunlight, pick the brightest one if we have multiple
foreach AllActors(class'Light',LightActor) {
if (SunLight(LightActor) != None) {
SunLights[i] = SunLight(LightActor);
i++;
}
else if (LightActor.LightType != LT_None && LightActor.LightBrightness > 1) {
LightList[LightList.Length] = LightActor;
}
}
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();
//Owner.AttachToBone(self, 'shadow');
}
simulated function Timer() {
FillLights();
}
simulated function FillLights() {
local int i, j;
local actor LightActor;
local array<Actor> OrigLight;
local float Diff;
if(Pawn(Owner) != None && Pawn(Owner).Health < 1)
return;
// clear array of lights, leave LastLight alone to fade
for (i=0;i<MaxShadows;i++) {
OrigLight[i] = Lights[i].CurrentLight;
Lights[i].CurrentLight = None;
}
// set the location of the controller, for light detection purposes
SetLocation(Owner.Location);
// shadow is off
if (!bShadowActive)
return;
// build the array of lights, we prioritize by both brightness and distance
for (i=0;i<LightList.Length;i++) {
if( (LightList[i].bStatic || LightList[i].bDynamicLight)) {
Diff = VSize(LightList[i].Location-Owner.Location);
if(Diff < MaxLightDistance && Diff > (Owner.CollisionRadius * 0.5) &&
IsVisible(LightList[i].Location)) {
if (Lights[0].CurrentLight != None &&
LightPriority(Lights[0].CurrentLight.LightBrightness,Lights[0].CurrentLight.LightRadius,VSize(Lights[0].CurrentLight.Location-Owner.Location)) > LightPriority(LightList[i].LightBrightness,LightList[i].LightRadius,Diff))
continue;
// this puts them in order of priority
for (j=1;j<MaxShadows;j++)
Lights[j].CurrentLight = Lights[j-1].CurrentLight;
Lights[0].CurrentLight = LightList[i];
}
}
}
foreach DynamicActors(class'Actor',LightActor) {
// 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.bUnlit &&
(LightActor.bStatic || LightActor.bDynamicLight) &&
LightActor.LightEffect != LE_SUNLIGHT &&
LightActor.LightBrightness > 1) {
Diff = VSize(LightActor.Location - Owner.Location);
if(Diff < MaxLightDistance &&
LightActor.LightRadius >= (Diff * 0.041) &&
Diff > (Owner.CollisionRadius * 0.5) &&
IsVisible(LightActor.Location)) {
if (Lights[0].CurrentLight != None &&
LightPriority(Lights[0].CurrentLight.LightBrightness,Lights[0].CurrentLight.LightRadius,VSize(Lights[0].CurrentLight.Location-Owner.Location)) > LightPriority(LightActor.LightBrightness,LightActor.LightRadius,Diff))
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) {
if (i > 0) {
if (OrigLight[i] == Lights[i-1].CurrentLight || OrigLight[i] == OrigLight[i-1])
OrigLight[i] = None;
}
Lights[i].LastLight = OrigLight[i];
}
}
// loop it, loop it good
SetTimer(UpdateFrequency,false);
}
// simple function for determining the priority of a light
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;
}
function bool IsVisible(vector Loc) {
local vector FootLocation;
// get a location near the feet
FootLocation = Owner.Location;
FootLocation.Z -= Owner.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,Owner.Location) || FastTrace(Loc,Owner.GetBoneCoords('head').Origin) || FastTrace(Loc,FootLocation))
return true;
}
function Tick(float dt) {
// fallback
if (Owner == None)
return;
// update all shadows
UpdateShadows(dt);
}
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);
}
}
function Effect_ShadowProjector SpawnShadow(rotator LightRotation) {
local Effect_ShadowProjector NewShadow;
// spawn and initialize a shadow projector
NewShadow = spawn(ShadowClass,Owner,,Location,LightRotation);
NewShadow.Disable('Tick');
NewShadow.InitializeFor(self);
return NewShadow;
}
function Destroyed() {
local int i;
Disable('Tick');
// destroy all shadows
for (i=0;i<MaxShadows;i++) {
if (Shadows[i] != None)
Shadows[i].Destroy();
}
}
defaultproperties {
// internal
UpdateFrequency=0.20
MaxShadows=1
MaxLightDistance=1000.0
ShadowClass=class'Effect_ShadowProjector'
// settings
bNoDelete=false
bStatic=false
bHidden=true
DrawType=DT_None
RemoteRole=ROLE_None
Physics=PHYS_None
}