Interesting, I hadn't thought of room brushes. Seems like that could potentially cause issues with audio propagation when placed as tripwires tend to be placed.
Well, I ended up going with the S&R setup. I figure the engine is far more optimized for figuring out when the player is near and sending a stim to it than any Squirrel code code be, and a stim lets me throttle how often it even checks. The Squirrel code then does a terrain raycast on initial activation to make sure it's not being triggered through a floor or ceiling, since it creates effectively a spherical rather than cuboid tripwire volume.
Biggest pain in the ass writing this was trying to figure out how to access the source object of a received stim. sLink(message().source).source, go figure.
Code:
// Tripwire substitute that uses stims instead of a tripwire. Use sparingly, only
// where a conventional tripwire prevents mantling.
// Required Setup:
// - Stim types TripwireTx and TripwireRx must exist.
// - Player archetype has receptron TripwireTx, No Min, No Max, Effect: Stim Object,
// Stimulus TripwireRX, Target Source, Agent Me
// - Tripwire has source TripwireTx, Radius, Intensity 1, Radius 10 (or as desired),
// No line of sight, No max firings, Period 200
// - Tripwire has receptron TripwireRx, No Min, No Max, Effect: Send to Scripts
//
// Usage:
// - On the problematic tripwire, set Physics/Misc/Collision Type: [None] (uncheck
// everything). This will make the tripwire ignore player collisions, but still
// inform AIs that they're allowed to open the door, and still generate TurnOff
// messages to close doors behind AIs (AIs will only automatically open doors for
// themselves, not close them).
// - Create a Stim Tripwire in the same location as the tripwire and switchlink it
// to the door to be controlled.
class scpStimTripwire extends SqRootScript {
function OnTripwireRxStimulus() {
// don't send multiple TurnOn messages and don't trigger if stimmed through a floor or ceiling
// (do a raycast from the tripwire to the return stim source)
if (!GetData("LastOn") && !Engine.PortalRaycast(Object.Position(self), Object.Position(sLink(message().source).source), vector())) {
Link.BroadcastOnAllLinks(self, "TurnOn", "SwitchLink");
SetData("LastOn", true);
}
// keep restarting timer while player is in range
// timer duration must be at least double the stim period
if (IsDataSet("StimTimer")) {
KillTimer(GetData("StimTimer"));
}
SetData("StimTimer", SetOneShotTimer("StimCheck", 0.4));
}
function OnTimer() {
if (message().name == "StimCheck") {
// timer completed, so player must be out of range
Link.BroadcastOnAllLinks(self, "TurnOff", "SwitchLink");
SetData("LastOn", false);
}
}
}