Files
quakemapping/mod_ad/my_progs/func_insvolume.qc
2019-12-30 22:24:44 +01:00

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;
};