You got me. The original version of that script was just piggybacking on the radiation check timer that's already on the SS2 player object. So it was running about... 10 times per second I think.
Here's an updated version that now does object raycasting, and runs ~30 times per second. Somewhat amazingly it does both without bogging down the engine. The objraycast service is freakishly accurate too-- seems to be per-polygon, which has to be an expensive operation. This version also implements distance attenuation of the light and creating its own light emitter object.
Dark's vertex lighting is so dodgy on large, low-poly objects that I had to implement directly adding extra light to objects to ensure that they would light up at all, which about doubled the length and complexity of the function, yet it's still very prone to visual inconsistencies. Oh well, not like the Half Life or Deus Ex flashlights were much better.
Code:
function OnBeginScript() {
SetOneShotTimer("flashlight", 0.5);
}
function OnTimer() {
if (message().name == "flashlight") {
// some constants
local maxDist = 60;
local minRadius = 3;
local maxRadius = 17;
local maxLight = 250;
local deg2rad = PI / 180;
local lightObj = GetData("flob");
local lastObjLit = GetData("lastObjLit");
// ensure flashlight object exists and ID saved
if (!lightObj) {
if (!Object.Named("flob")) {
lightObj = Object.Create("Marker");
Object.SetName(lightObj, "flob")
}
else {
lightObj = Object.Named("flob");
}
SetData("flob", lightObj);
SetData("lastObjLit", 0);
lastObjLit = 0;
}
// get camera position/facing
local pos = Camera.GetPosition();
local face = Camera.GetFacing();
local faceY = face.y * deg2rad;
local faceZ = face.z * deg2rad;
// find out if there's a surface within range
local testPos = vector();
testPos.x = pos.x + maxDist * cos(faceY) * cos(faceZ);
testPos.y = pos.y + maxDist * cos(faceY) * sin(faceZ);
testPos.z = pos.z + maxDist * sin(faceY - PI);
local hitPos = vector();
local hitObj = object();
local hitObjID = 0;
// return values: 0 nothing, 1 terrain, 2 object, 3 mesh
local rayHit = Engine.ObjRaycast(pos, testPos, hitPos, hitObj, 0, 0, lightObj, 0);
if (rayHit) {
// calc distance to impact
local v = (hitPos - pos);
local d = sqrt(v.Dot(v));
local lightRad = (maxRadius - minRadius) * (d / maxDist) + minRadius;
local lightBright = maxLight * (1 - (d / maxDist));
// step back from impact by radius of the light
local d2 = d - lightRad / 2;
// never position light behind player
if (d2 < 0) {
d2 = 0;
}
// calc actual light position
// (there's probably a better way to do this)
local lightPos = vector();
lightPos.x = pos.x + d2 * cos(faceY) * cos(faceZ);
lightPos.y = pos.y + d2 * cos(faceY) * sin(faceZ);
lightPos.z = pos.z + d2 * sin(faceY - PI);
// apply updated light params
Property.SetSimple(lightObj, "SelfLit", lightBright);
Property.SetSimple(lightObj, "SelfLitRad", lightRad);
Object.Teleport(lightObj, lightPos, face); // facing doesn't matter
// handle object lighting
// (objects often need some help due to vertex lighting plus low poly counts)
if (rayHit == 2) {
hitObjID = hitObj.tointeger();
// check for object focus change
if (hitObjID != abs(lastObjLit)) {
killObjLight(lastObjLit);
// don't mess with objects that already have Extra Light
if (Property.Possessed(hitObjID, "ExtraLight")) {
lastObjLit = -hitObjID;
}
else {
lastObjLit = hitObjID;
Property.Set(hitObjID, "ExtraLight", "Additive?", TRUE);
}
SetData("lastObjLit", lastObjLit);
}
if (lastObjLit > 0) {
// fade to target brightness
local curLum = Property.Get(hitObjID, "ExtraLight", "Amount (-1..1)");
// (lightBright / maxLight) / 2) is the target extra light
Property.Set(hitObjID, "ExtraLight", "Amount (-1..1)", curLum + (((lightBright / maxLight) / 2) - curLum) / 3);
}
}
else {
killObjLight(lastObjLit);
}
}
else {
// light didn't reach anything
Property.SetSimple(lightObj, "SelfLit", 0);
killObjLight(lastObjLit);
}
// again!
SetOneShotTimer("flashlight", 0.0333);
}
}
// remove extra light from last lit object
function killObjLight(lastObjLit) {
if (lastObjLit != 0) {
SetData("lastObjLit", 0);
if (lastObjLit > 0) {
Property.Remove(lastObjLit, "ExtraLight");
}
}
}