quakec/source/server/entities/triggers.qc

346 lines
7.7 KiB
C++

/*
server/entities/triggers.qc
Misc. Trigger functions
Copyright (C) 2021 NZ:P Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#define SPAWNFLAG_NOMESSAGE 1
#define SPAWNFLAG_NOTOUCH 1
// the wait time has passed, so set back up for another activation
void() multi_wait =
{
if (self.max_health)
{
self.health = self.max_health;
self.takedamage = DAMAGE_YES;
self.solid = SOLID_BBOX;
}
};
// the trigger was just touched/killed/used
// self.enemy should be set to the activator so it can be held through a delay
// so wait for the delay time before firing
void() multi_trigger =
{
if (self.nextthink > time)
{
return; // already been triggered
}
if (self.noise)
sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
// don't trigger again until reset
self.takedamage = DAMAGE_NO;
activator = self.enemy;
SUB_UseTargets();
if (self.wait > 0)
{
self.think = multi_wait;
self.nextthink = time + self.wait;
}
else
{
// we can't just remove(self) here, because this is a touch function
// called while C code is looping through area links...
self.touch = SUB_Null;
self.nextthink = time + 0.1;
self.think = SUB_Remove;
}
};
void() multi_killed =
{
// motolegacy - FIXME
//self.enemy = damage_attacker;
multi_trigger();
};
void() multi_use =
{
self.enemy = activator;
multi_trigger();
};
void() multi_touch =
{
if (other.classname != "player")
return;
// if the trigger has an angles field, check player's facing direction
if (self.movedir != '0 0 0')
{
makevectors (other.angles);
if (v_forward * self.movedir < 0)
return; // not facing the right way
}
self.enemy = other;
multi_trigger();
};
void() SetMovedir =
{
if (self.angles == '0 -1 0')
self.movedir = '0 0 1';
else if (self.angles == '0 -2 0')
self.movedir = '0 0 -1';
else
{
makevectors (self.angles);
self.movedir = v_forward;
}
self.angles = '0 0 0';
};
void() InitTrigger =
{
// trigger angles are used for one-way touches. An angle of 0 is assumed
// to mean no restrictions, so use a yaw of 360 instead.
if (self.angles != '0 0 0')
SetMovedir ();
self.solid = SOLID_TRIGGER;
setmodel (self, self.model); // set size and link into world
self.movetype = MOVETYPE_NONE;
self.modelindex = 0;
self.model = "";
};
entity last_act_trigger;
void() trigger_activator_touch =
{
other.cost = other.cost +1; //hack, we can only touch one of thease at the time
if (other.classname != "player" || last_act_trigger == self || other.cost > 1)
return;
last_act_trigger = self;
entity t;
float tempcount, temptotal,breakthis;
string tempstring;
temptotal = 0;
breakthis = 0;
tempcount = 1;
tempstring = "";
t = world;
do
{
t = find (t, classname, "spawn_zombie");
if (!t)
{
breakthis = 1;
}
if (t.classname == "spawn_zombie")
{
t.classname = "spawn_zombie_away";
/*if (cvar("developer"))
setmodel(t, "progs/player.mdl");*/
}
} while (!breakthis);
if (self.target2)
tempcount =+ 1;
if (self.target3)
tempcount =+ 1;
if (self.target4)
tempcount =+ 1;
if (self.target5)
tempcount =+ 1;
if (self.target6)
tempcount =+ 1;
if (self.target7)
tempcount =+ 1;
if (self.target8)
tempcount =+ 1;
if (self.target2)
tempcount = tempcount + 1;
if (self.target3)
tempcount = tempcount + 1;
if (self.target4)
tempcount = tempcount + 1;
if (self.target5)
tempcount = tempcount + 1;
if (self.target6)
tempcount = tempcount + 1;
if (self.target7)
tempcount = tempcount + 1;
if (self.target8)
tempcount = tempcount + 1;
while(tempcount > temptotal)
{
temptotal = temptotal + 1;
if (temptotal == 1)
tempstring = self.target;
if (temptotal == 2)
tempstring = self.target2;
if (temptotal == 3)
tempstring = self.target3;
if (temptotal == 4)
tempstring = self.target4;
if (temptotal == 5)
tempstring = self.target5;
if (temptotal == 6)
tempstring = self.target6;
if (temptotal == 7)
tempstring = self.target7;
if (temptotal == 8)
tempstring = self.target8;
if (tempstring)
{
t = find (world, targetname, tempstring);
breakthis = 0;
while (!breakthis)
{
if (!t)
{
breakthis = true;
}
if (t.classname == "spawn_zombie_away")
{
t.classname = "spawn_zombie";
/*if (cvar("developer"))
setmodel(t, "progs/ai/zfull.mdl");*/
}
t = find (t, targetname, tempstring);
}
}
}
}
void() trigger_activator =
{
InitTrigger ();
self.touch = trigger_activator_touch;
}
#define SPAWNFLAG_TRIGGERCMD_NOPLAYER 1
#define SPAWNFLAG_TRIGGERCMD_NOSPEC 2
void() trigger_stuffcmd_touch =
{
// First, lets make sure this is a client
if (other.classname != "player" && other.classname != "spectator")
return;
// Don't interact with players
if ((self.spawnflags & SPAWNFLAG_TRIGGERCMD_NOPLAYER) && other.classname == "player")
return;
// Don't interact with spectators
if ((self.spawnflags & SPAWNFLAG_TRIGGERCMD_NOSPEC) && other.classname == "spectator")
return;
// Run the command.
stuffcmd(other, strcat(self.message, "\n"));
}
void() trigger_stuffcmd =
{
InitTrigger();
self.touch = trigger_stuffcmd_touch;
}
/* ===================
Custom Teddy Triggers
===================*/
void() teddy_react =
{
local entity t;
if (self.spawnflags & 1) {
t = find (world, teddyremovetarget, self.target);
if (t)
Ent_FakeRemove(t);
} else if (self.target) {
SUB_UseTargets();
}
entity dummy = spawn();
setorigin(dummy, self.origin);
dummy.think = SUB_Remove;
dummy.nextthink = time + 10;
if (self.noise)
sound (dummy, CHAN_ITEM, self.noise, 1, ATTN_NORM);
Ent_FakeRemove(self);
}
/*
Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time.
If "delay" is set, the trigger waits some time after activating before firing.
"wait" : Seconds between triggerings. (.2 default)
If notouch is set, the trigger is only fired by other entities, not by touching.
NOTOUCH has been obsoleted by trigger_relay!
set "message" to text string
*/
void() trigger_multiple =
{
if (!self.wait)
self.wait = 0.2;
self.use = multi_use;
InitTrigger ();
if(self.health)
{
if (self.spawnflags & SPAWNFLAG_NOTOUCH)
objerror("health and notouch don't make sense\n");
self.max_health = self.health;
self.th_die = multi_killed;
self.takedamage = DAMAGE_YES;
self.solid = SOLID_BBOX;
setorigin(self, self.origin); // make sure it links into the world
}
else
{
if (!(self.spawnflags & SPAWNFLAG_NOTOUCH))
{
self.touch = multi_touch;
}
}
};
/*
Variable sized trigger. Triggers once, then removes itself. You must set the key "target" to the name of another object in the level that has a matching
"targetname". If "health" is set, the trigger must be killed to activate.
If notouch is set, the trigger is only fired by other entities, not by touching.
if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0.
set "message" to text string
*/
void() trigger_once =
{
self.wait = -1;
trigger_multiple();
}