304 lines
10 KiB
Plaintext
304 lines
10 KiB
Plaintext
/*======================================================================
|
|
/*QUAKED func_insidevolume (0 .5 .8) ? PLAYER MONSTER ITEM PUSHABLE x x STARTOFF x Not_Easy Not_Normal Not_Hard Not_DM
|
|
Function to test if an entity bounding box is inside of this volume
|
|
-------- KEYS --------
|
|
targetname : trigger entity (works with entity state system)
|
|
target : Name of target(s) to trigger when fully INSIDE (fires every 0.1s)
|
|
target2 : Name of target(s) to trigger when partial INSIDE (fires every 0.1s)
|
|
message : Name of target(s) to trigger when exit VOLUME (fires on exit only)
|
|
wait : -1 = Fire the fully/exit volume target(s) once
|
|
delay : Delay time before volume controller becomes active
|
|
message2: Name of the only entity that can interact with this volume
|
|
no_deadbody : 1 = Monster dead bodies don't work with volume test
|
|
-------- SPAWNFLAGS --------
|
|
PLAYER : Will react to player(s)
|
|
MONSTER : Will react to monster(s)
|
|
ITEM : Will react to item(s)
|
|
PUSHABLE : Will react to pushable(s)
|
|
STARTOFF : Starts off and waits for trigger
|
|
-------- NOTES --------
|
|
Function to test if an entity bounding box is inside of this volume
|
|
|
|
======================================================================*/
|
|
|
|
float FUNC_INSVOLPLAYER = 1;
|
|
float FUNC_INSVOLMONSTER = 2;
|
|
float FUNC_INSVOLITEM = 4;
|
|
float FUNC_INSVOLPUSHABLE = 8;
|
|
|
|
//----------------------------------------------------------------------
|
|
float(entity targ) func_insvolume_check =
|
|
{
|
|
local vector targ_mins, targ_maxs;
|
|
|
|
// Is the touching entity linked to anthing?
|
|
if (targ.touching == world) return TRUE;
|
|
|
|
// Is the touching entity linked to this inside volume?
|
|
// Don't reset the touching entity, its being tested by something else
|
|
if (targ.touching != self.owner) return TRUE;
|
|
|
|
// Has the touching entity been switch off/disabled
|
|
if (targ.estate & ESTATE_BLOCK) { targ.touching = world; return TRUE; }
|
|
|
|
// Check for dead monsters and removed them from entity list
|
|
if (targ.flags & FL_MONSTER) {
|
|
if (self.owner.no_deadbody == 1 && targ.health < 1) {
|
|
targ.touching = world;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
// Find entity origin
|
|
if (targ.bsporigin) {
|
|
// Bmodel origin is stored differently
|
|
self.owner.pos3 = bmodel_origin(targ);
|
|
// Min/max are only updated when spawned, use size instead
|
|
targ_mins = self.owner.pos3 - (targ.size*0.5);
|
|
targ_maxs = self.owner.pos3 + (targ.size*0.5);
|
|
}
|
|
else {
|
|
// Point entity uses origin key
|
|
self.owner.pos3 = targ.origin;
|
|
// Min/max are created when spawned
|
|
targ_mins = self.owner.pos3 + targ.mins;
|
|
targ_maxs = self.owner.pos3 + targ.maxs;
|
|
}
|
|
|
|
// Test OUTSIDE space (targ max < trigger min)
|
|
if (targ_maxs_x < self.owner.dest1_x || targ_maxs_y < self.owner.dest1_y ||
|
|
targ_maxs_z < self.owner.dest1_z) {
|
|
//dprint("ENT outside MINS\n");
|
|
targ.touching = world;
|
|
self.owner.targetnumber = NEGATIVE;
|
|
return TRUE;
|
|
}
|
|
// Test OUTSIDE space (targ min > trigger max)
|
|
if (targ_mins_x > self.owner.dest2_x || targ_mins_y > self.owner.dest2_y ||
|
|
targ_mins_z > self.owner.dest2_z) {
|
|
//dprint("ENT outside MAXS\n");
|
|
targ.touching = world;
|
|
self.owner.targetnumber = NEGATIVE;
|
|
return TRUE;
|
|
}
|
|
|
|
// Test INSIDE space (targ min < trigger min)
|
|
if (targ_mins_x < self.owner.dest1_x) return FALSE;
|
|
if (targ_mins_y < self.owner.dest1_y) return FALSE;
|
|
if (targ_mins_z < self.owner.dest1_z) return FALSE;
|
|
|
|
// Test INSIDE space (targ max > trigger max)
|
|
if (targ_maxs_x > self.owner.dest2_x) return FALSE;
|
|
if (targ_maxs_y > self.owner.dest2_y) return FALSE;
|
|
if (targ_maxs_z > self.owner.dest2_z) return FALSE;
|
|
|
|
// Finally something INSIDE the volume!
|
|
self.owner.targetnumber = TRUE;
|
|
return FALSE;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void(entity targ) func_insvolume_add =
|
|
{
|
|
if (self.tno1 == world) { self.tno1 = targ; targ.touching = self; }
|
|
else if (self.tno2 == world) { self.tno2 = targ; targ.touching = self; }
|
|
else if (self.tno3 == world) { self.tno3 = targ; targ.touching = self; }
|
|
else if (self.tno4 == world) { self.tno4 = targ; targ.touching = self; }
|
|
else if (self.tno5 == world) { self.tno5 = targ; targ.touching = self; }
|
|
else {
|
|
// A warning message for mappers to ignore! :P
|
|
if (self.pain_finished < time) {
|
|
self.pain_finished = time + 3;
|
|
dprint("\b[INSVOL]\b More than 5 entities touching!\n");
|
|
}
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() func_insvolume_touch =
|
|
{
|
|
// Block functionality if state wrong
|
|
if (self.estate & ESTATE_BLOCK) return;
|
|
|
|
// Already checking this (other) entity?
|
|
if (other.touching == self) return;
|
|
|
|
// Check for an exact entity condition
|
|
if (self.message2 != "") {
|
|
// Is the touching entity the correct entity?
|
|
if (self.message2 != other.targetname) return;
|
|
}
|
|
|
|
// Default = no touch
|
|
self.lefty = FALSE;
|
|
|
|
// Test for all the different entity types
|
|
if (self.spawnflags & FUNC_INSVOLPLAYER && other.flags & FL_CLIENT)
|
|
self.lefty = TRUE;
|
|
else if (self.spawnflags & FUNC_INSVOLMONSTER && other.flags & FL_MONSTER) {
|
|
// Test for dead/living monsters?
|
|
if (self.no_deadbody == 0) self.lefty = TRUE;
|
|
else if (self.no_deadbody == 1 && other.health > 0)
|
|
self.lefty = TRUE;
|
|
}
|
|
else if (self.spawnflags & FUNC_INSVOLITEM && other.flags & FL_ITEM)
|
|
self.lefty = TRUE;
|
|
else if (self.spawnflags & FUNC_INSVOLPUSHABLE && other.classtype == CT_FUNCPUSHABLE)
|
|
self.lefty = TRUE;
|
|
|
|
// If wrong type touching volume?
|
|
if (self.lefty == FALSE) return;
|
|
|
|
// Add entity to volume list to keep checking
|
|
if (other.touching == world)
|
|
func_insvolume_add(other);
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() func_insvolume_on =
|
|
{
|
|
// Stop re-triggering ON state
|
|
if (self.estate == ESTATE_ON) return;
|
|
|
|
// No longer need this spawnflag, remove it
|
|
self.spawnflags = self.spawnflags - (self.spawnflags & ENT_STARTOFF);
|
|
|
|
self.estate = ESTATE_ON;
|
|
self.movetype = MOVETYPE_NONE;
|
|
self.solid = SOLID_TRIGGER;
|
|
setsize (self, self.bbmins, self.bbmaxs);
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() func_insvolume_off =
|
|
{
|
|
// Stop re-triggering OFF state
|
|
if (self.estate == ESTATE_OFF) return;
|
|
|
|
self.estate = ESTATE_OFF;
|
|
self.movetype = MOVETYPE_NONE;
|
|
self.solid = SOLID_NOT;
|
|
setsize (self, VEC_ORIGIN, VEC_ORIGIN);
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() func_insvolume_controller_think =
|
|
{
|
|
// Keep the controller looping
|
|
self.think = func_insvolume_controller_think;
|
|
self.nextthink = time + 0.1;
|
|
self.owner.targetnumber = FALSE;
|
|
|
|
// If switched off/disabled
|
|
if (self.owner.estate & ESTATE_BLOCK) return;
|
|
|
|
// If no entities touching volume, do nothing
|
|
// This is a way to slow down the constant triggering
|
|
if (self.owner.tno1 != world || self.owner.tno2 != world ||
|
|
self.owner.tno3 != world || self.owner.tno4 != world ||
|
|
self.owner.tno5 != world) {
|
|
|
|
// Go through the entities touching this volume
|
|
if (func_insvolume_check(self.owner.tno1) == TRUE) self.owner.tno1 = world;
|
|
if (func_insvolume_check(self.owner.tno2) == TRUE) self.owner.tno2 = world;
|
|
if (func_insvolume_check(self.owner.tno3) == TRUE) self.owner.tno3 = world;
|
|
if (func_insvolume_check(self.owner.tno4) == TRUE) self.owner.tno4 = world;
|
|
if (func_insvolume_check(self.owner.tno5) == TRUE) self.owner.tno5 = world;
|
|
|
|
// Any entities pass the volume test? (FULL, PARTIAL and EXIT)
|
|
if (self.owner.targetnumber == TRUE) {
|
|
// FULLY inside the volume
|
|
if (self.owner.target != "") {
|
|
trigger_strs(self.owner.target, self.owner);
|
|
if (self.owner.wait < 0) self.owner.target = "";
|
|
}
|
|
}
|
|
else if (self.owner.targetnumber == NEGATIVE) {
|
|
// Just EXIT the volume
|
|
if (self.owner.message != "") {
|
|
trigger_strs(self.owner.message, self.owner);
|
|
if (self.owner.wait < 0) self.owner.message = "";
|
|
}
|
|
}
|
|
else {
|
|
// PARTIALLY inside the volume
|
|
if (self.owner.target2 != "") {
|
|
trigger_strs(self.owner.target2, self.owner);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() func_insvolume_controller_setup =
|
|
{
|
|
// Check if controller entity been setup
|
|
if (!self.attachment) self.attachment = spawn();
|
|
|
|
// Setup link back to inside volume
|
|
self.attachment.owner = self;
|
|
self.attachment.classtype = CT_CONTROL;
|
|
self.attachment.movetype = MOVETYPE_NONE;
|
|
self.attachment.solid = SOLID_NOT;
|
|
|
|
// Setup controller above model origin location
|
|
self.oldorigin = bmodel_origin(self);
|
|
setorigin(self.attachment, self.oldorigin + '0 0 32');
|
|
setsize (self.attachment, VEC_ORIGIN, VEC_ORIGIN);
|
|
|
|
// Start controller think loop
|
|
self.attachment.think = func_insvolume_controller_think;
|
|
self.attachment.nextthink = time + 0.1 + self.delay;
|
|
};
|
|
|
|
//======================================================================
|
|
void() func_insidevolume =
|
|
{
|
|
if (check_bmodel_keys()) return; // Check for bmodel errors
|
|
|
|
// No trigger/movement angles
|
|
self.angles = '0 0 0'; self.movedir = '0 0 0';
|
|
|
|
self.classtype = CT_FUNCINSVOLUME;
|
|
self.nodebuginfo = TRUE;
|
|
|
|
// Setup like a trigger in the map
|
|
InitTrigger ();
|
|
setsize (self, self.bbmins, self.bbmaxs);
|
|
|
|
// Work out actual world origin coordinates
|
|
self.pos1 = self.size * 0.5;
|
|
self.pos2 = bmodel_origin(self);
|
|
|
|
// Setup trigger boundary edges
|
|
self.dest1 = self.pos2 - self.pos1;
|
|
self.dest2 = self.pos2 + self.pos1;
|
|
|
|
// Block all trigger extra parameters (not needed)
|
|
self.health = self.max_health = 0;
|
|
self.takedamage = DAMAGE_NO;
|
|
if (self.delay < 0) self.delay = 0;
|
|
|
|
// Reset all touch entities to monitor
|
|
self.tno1 = self.tno2 = self.tno3 = self.tno4 = self.tno5 = world;
|
|
|
|
// Check for spawning conditions (nightmare, coop)
|
|
// Needs to exist after entity has been added to work for BSPorigin
|
|
if (check_nightmare() == TRUE) return;
|
|
if (check_coop() == TRUE) return;
|
|
|
|
// Setup Entity State functionality
|
|
if (self.targetname != "") self.use = entity_state_use;
|
|
self.estate_on = func_insvolume_on;
|
|
self.estate_off = func_insvolume_off;
|
|
self.touch = func_insvolume_touch;
|
|
|
|
// Check for starting off or default=on
|
|
if (self.spawnflags & ENT_STARTOFF) self.estate_off();
|
|
else self.estate_on();
|
|
|
|
// Setup volume controller (tests volumes)
|
|
self.think = func_insvolume_controller_setup;
|
|
self.nextthink = time + 0.2;
|
|
};
|