yquake2remaster/original/ctf/g_target.c

1022 lines
21 KiB
C

/*
* Copyright (C) 1997-2001 Id Software, Inc.
*
* 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 the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Targets.
*
* =======================================================================
*/
#include "header/local.h"
/*
* QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8)
* Fire an origin based temp entity event to the clients.
* "style" type byte
*/
void
Use_Target_Tent(edict_t *ent, edict_t *other, edict_t *activator)
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(ent->style);
gi.WritePosition(ent->s.origin);
gi.multicast(ent->s.origin, MULTICAST_PVS);
}
void
SP_target_temp_entity(edict_t *ent)
{
ent->use = Use_Target_Tent;
}
/* ========================================================== */
/*
* QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable
* "noise" wav file to play
*
* "attenuation"
* -1 = none, send to whole level
* 1 = normal fighting sounds
* 2 = idle sound level
* 3 = ambient sound level
*
* "volume" 0.0 to 1.0
*
* Normal sounds play each time the target is used. The reliable flag
* can be set for crucial voiceovers.
*
* Looped sounds are allways atten 3 / vol 1, and the use function toggles
* it on/off. Multiple identical looping sounds will just increase volume
* without any speed cost.
*/
void
Use_Target_Speaker(edict_t *ent, edict_t *other, edict_t *activator)
{
int chan;
if (ent->spawnflags & 3)
{
/* looping sound toggles */
if (ent->s.sound)
{
ent->s.sound = 0; /* turn it off */
}
else
{
ent->s.sound = ent->noise_index; /* start it */
}
}
else
{
/* normal sound */
if (ent->spawnflags & 4)
{
chan = CHAN_VOICE | CHAN_RELIABLE;
}
else
{
chan = CHAN_VOICE;
}
/* use a positioned_sound, because this entity won't
normally be sent to any clients because it is invisible */
gi.positioned_sound(ent->s.origin, ent, chan, ent->noise_index,
ent->volume, ent->attenuation, 0);
}
}
void
SP_target_speaker(edict_t *ent)
{
char buffer[MAX_QPATH];
if (!st.noise)
{
gi.dprintf("target_speaker with no noise set at %s\n",
vtos(ent->s.origin));
return;
}
if (!strstr(st.noise, ".wav"))
{
Com_sprintf(buffer, sizeof(buffer), "%s.wav", st.noise);
}
else
{
Q_strlcpy(buffer, st.noise, sizeof(buffer));
}
ent->noise_index = gi.soundindex(buffer);
if (!ent->volume)
{
ent->volume = 1.0;
}
if (!ent->attenuation)
{
ent->attenuation = 1.0;
}
else if (ent->attenuation == -1) /* use -1 so 0 defaults to 1 */
{
ent->attenuation = 0;
}
/* check for prestarted looping sound */
if (ent->spawnflags & 1)
{
ent->s.sound = ent->noise_index;
}
ent->use = Use_Target_Speaker;
/* must link the entity so we get areas and clusters so
the server can determine who to send updates to */
gi.linkentity(ent);
}
/* ========================================================== */
void
Use_Target_Help(edict_t *ent, edict_t *other, edict_t *activator)
{
if (ent->spawnflags & 1)
{
strncpy(game.helpmessage1, ent->message, sizeof(game.helpmessage2) - 1);
}
else
{
strncpy(game.helpmessage2, ent->message, sizeof(game.helpmessage1) - 1);
}
game.helpchanged++;
}
/*
* QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1
* When fired, the "message" key becomes the current personal computer
* string, and the message light will be set on all clients status bars.
*/
void
SP_target_help(edict_t *ent)
{
if (deathmatch->value)
{
/* auto-remove for deathmatch */
G_FreeEdict(ent);
return;
}
if (!ent->message)
{
gi.dprintf("%s with no message at %s\n", ent->classname,
vtos(ent->s.origin));
G_FreeEdict(ent);
return;
}
ent->use = Use_Target_Help;
}
/* ========================================================== */
/*
* QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8)
* Counts a secret found.
* These are single use targets.
*/
void
use_target_secret(edict_t *ent, edict_t *other, edict_t *activator)
{
gi.sound(ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
level.found_secrets++;
G_UseTargets(ent, activator);
G_FreeEdict(ent);
}
void
SP_target_secret(edict_t *ent)
{
if (deathmatch->value)
{
/* auto-remove for deathmatch */
G_FreeEdict(ent);
return;
}
ent->use = use_target_secret;
if (!st.noise)
{
st.noise = "misc/secret.wav";
}
ent->noise_index = gi.soundindex(st.noise);
ent->svflags = SVF_NOCLIENT;
level.total_secrets++;
/* map bug hack */
if (!Q_stricmp(level.mapname, "mine3") && (ent->s.origin[0] == 280) &&
(ent->s.origin[1] == -2048) &&
(ent->s.origin[2] == -624))
{
ent->message = "You have found a secret area.";
}
}
/* ========================================================== */
/*
* QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8)
* Counts a goal completed.
* These are single use targets.
*/
void
use_target_goal(edict_t *ent, edict_t *other, edict_t *activator)
{
gi.sound(ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
level.found_goals++;
if (level.found_goals == level.total_goals)
{
gi.configstring(CS_CDTRACK, "0");
}
G_UseTargets(ent, activator);
G_FreeEdict(ent);
}
void
SP_target_goal(edict_t *ent)
{
if (deathmatch->value)
{
/* auto-remove for deathmatch */
G_FreeEdict(ent);
return;
}
ent->use = use_target_goal;
if (!st.noise)
{
st.noise = "misc/secret.wav";
}
ent->noise_index = gi.soundindex(st.noise);
ent->svflags = SVF_NOCLIENT;
level.total_goals++;
}
/* ========================================================== */
/*
* QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8)
* Spawns an explosion temporary entity when used.
*
* "delay" wait this long before going off
* "dmg" how much radius damage should be done, defaults to 0
*/
void
target_explosion_explode(edict_t *self)
{
float save;
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1);
gi.WritePosition(self->s.origin);
gi.multicast(self->s.origin, MULTICAST_PHS);
T_RadiusDamage(self, self->activator, self->dmg,
NULL, self->dmg + 40, MOD_EXPLOSIVE);
save = self->delay;
self->delay = 0;
G_UseTargets(self, self->activator);
self->delay = save;
}
void
use_target_explosion(edict_t *self, edict_t *other, edict_t *activator)
{
self->activator = activator;
if (!self->delay)
{
target_explosion_explode(self);
return;
}
self->think = target_explosion_explode;
self->nextthink = level.time + self->delay;
}
void
SP_target_explosion(edict_t *ent)
{
ent->use = use_target_explosion;
ent->svflags = SVF_NOCLIENT;
}
/* ========================================================== */
/*
* QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8)
* Changes level to "map" when fired
*/
void
use_target_changelevel(edict_t *self, edict_t *other, edict_t *activator)
{
if (level.intermissiontime)
{
return; /* allready activated */
}
if (!deathmatch->value && !coop->value)
{
if (g_edicts[1].health <= 0)
{
return;
}
}
/* if noexit, do a ton of damage to other */
if (deathmatch->value && !((int)dmflags->value & DF_ALLOW_EXIT) &&
(other != world))
{
T_Damage(other, self, self, vec3_origin, other->s.origin,
vec3_origin, 10 * other->max_health, 1000, 0,
MOD_EXIT);
return;
}
/* if multiplayer, let everyone know who hit the exit */
if (deathmatch->value)
{
if (activator && activator->client)
{
gi.bprintf(PRINT_HIGH, "%s exited the level.\n",
activator->client->pers.netname);
}
}
/* if going to a new unit, clear cross triggers */
if (strstr(self->map, "*"))
{
game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK);
}
BeginIntermission(self);
}
void
SP_target_changelevel(edict_t *ent)
{
if (!ent->map)
{
gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin));
G_FreeEdict(ent);
return;
}
/* ugly hack because *SOMEBODY* screwed up their map */
if ((Q_stricmp(level.mapname, "fact1") == 0) && (Q_stricmp(ent->map, "fact3") == 0))
{
ent->map = "fact3$secret1";
}
ent->use = use_target_changelevel;
ent->svflags = SVF_NOCLIENT;
}
/* ========================================================== */
/*
* QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8)
* Creates a particle splash effect when used.
*
* Set "sounds" to one of the following:
* 1) sparks
* 2) blue water
* 3) brown water
* 4) slime
* 5) lava
* 6) blood
*
* "count" how many pixels in the splash
* "dmg" if set, does a radius damage at this location when it splashes
* useful for lava/sparks
*/
void
use_target_splash(edict_t *self, edict_t *other, edict_t *activator)
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_SPLASH);
gi.WriteByte(self->count);
gi.WritePosition(self->s.origin);
gi.WriteDir(self->movedir);
gi.WriteByte(self->sounds);
gi.multicast(self->s.origin, MULTICAST_PVS);
if (self->dmg)
{
T_RadiusDamage(self, activator, self->dmg, NULL,
self->dmg + 40, MOD_SPLASH);
}
}
void
SP_target_splash(edict_t *self)
{
self->use = use_target_splash;
G_SetMovedir(self->s.angles, self->movedir);
if (!self->count)
{
self->count = 32;
}
self->svflags = SVF_NOCLIENT;
}
/* ========================================================== */
/*
* QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8)
* Set target to the type of entity you want spawned.
* Useful for spawning monsters and gibs in the factory levels.
*
* For monsters:
* Set direction to the facing you want it to have.
*
* For gibs:
* Set direction if you want it moving and
* speed how fast it should be moving otherwise it
* will just be dropped
*/
void ED_CallSpawn(edict_t *ent);
void
use_target_spawner(edict_t *self, edict_t *other, edict_t *activator)
{
edict_t *ent;
ent = G_Spawn();
ent->classname = self->target;
VectorCopy(self->s.origin, ent->s.origin);
VectorCopy(self->s.angles, ent->s.angles);
ED_CallSpawn(ent);
gi.unlinkentity(ent);
KillBox(ent);
gi.linkentity(ent);
if (self->speed)
{
VectorCopy(self->movedir, ent->velocity);
}
}
void
SP_target_spawner(edict_t *self)
{
self->use = use_target_spawner;
self->svflags = SVF_NOCLIENT;
if (self->speed)
{
G_SetMovedir(self->s.angles, self->movedir);
VectorScale(self->movedir, self->speed, self->movedir);
}
}
/* ========================================================== */
/*
* QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
* Fires a blaster bolt in the set direction when triggered.
*
* dmg default is 15
* speed default is 1000
*/
void
use_target_blaster(edict_t *self, edict_t *other, edict_t *activator)
{
fire_blaster(self, self->s.origin, self->movedir, self->dmg,
self->speed, EF_BLASTER, MOD_TARGET_BLASTER);
gi.sound(self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
}
void
SP_target_blaster(edict_t *self)
{
self->use = use_target_blaster;
G_SetMovedir(self->s.angles, self->movedir);
self->noise_index = gi.soundindex("weapons/laser2.wav");
if (!self->dmg)
{
self->dmg = 15;
}
if (!self->speed)
{
self->speed = 1000;
}
self->svflags = SVF_NOCLIENT;
}
/* ========================================================== */
/*
* QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
*
* Once this trigger is touched/used, any trigger_crosslevel_target with the
* same trigger number is automatically used when a level is started within
* the same unit. It is OK to check multiple triggers. Message, delay, target,
* and killtarget also work.
*/
void
trigger_crosslevel_trigger_use(edict_t *self, edict_t *other,
edict_t *activator)
{
game.serverflags |= self->spawnflags;
G_FreeEdict(self);
}
void
SP_target_crosslevel_trigger(edict_t *self)
{
self->svflags = SVF_NOCLIENT;
self->use = trigger_crosslevel_trigger_use;
}
/*
* QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
* Triggered by a trigger_crosslevel elsewhere within a unit. If multiple triggers
* are checked, all must be true. Delay, target andkilltarget also work.
*
* "delay" delay before using targets if the trigger has been activated (default 1)
*/
void
target_crosslevel_target_think(edict_t *self)
{
if (self->spawnflags ==
(game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags))
{
G_UseTargets(self, self);
G_FreeEdict(self);
}
}
void
SP_target_crosslevel_target(edict_t *self)
{
if (!self->delay)
{
self->delay = 1;
}
self->svflags = SVF_NOCLIENT;
self->think = target_crosslevel_target_think;
self->nextthink = level.time + self->delay;
}
/* ========================================================== */
/*
* QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT
* When triggered, fires a laser. You can either set a target
* or a direction.
*/
void
target_laser_think(edict_t *self)
{
edict_t *ignore;
vec3_t start;
vec3_t end;
trace_t tr;
vec3_t point;
vec3_t last_movedir;
int count;
if (self->spawnflags & 0x80000000)
{
count = 8;
}
else
{
count = 4;
}
if (self->enemy)
{
VectorCopy(self->movedir, last_movedir);
VectorMA(self->enemy->absmin, 0.5, self->enemy->size, point);
VectorSubtract(point, self->s.origin, self->movedir);
VectorNormalize(self->movedir);
if (!VectorCompare(self->movedir, last_movedir))
{
self->spawnflags |= 0x80000000;
}
}
ignore = self;
VectorCopy(self->s.origin, start);
VectorMA(start, 2048, self->movedir, end);
while (1)
{
tr = gi.trace(start, NULL, NULL, end, ignore,
CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_DEADMONSTER);
if (!tr.ent)
{
break;
}
/* hurt it if we can */
if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER))
{
T_Damage(tr.ent, self, self->activator, self->movedir, tr.endpos,
vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER);
}
/* if we hit something that's not a monster
or player or is immune to lasers, we're done */
if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
{
if (self->spawnflags & 0x80000000)
{
self->spawnflags &= ~0x80000000;
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_LASER_SPARKS);
gi.WriteByte(count);
gi.WritePosition(tr.endpos);
gi.WriteDir(tr.plane.normal);
gi.WriteByte(self->s.skinnum);
gi.multicast(tr.endpos, MULTICAST_PVS);
}
break;
}
ignore = tr.ent;
VectorCopy(tr.endpos, start);
}
VectorCopy(tr.endpos, self->s.old_origin);
self->nextthink = level.time + FRAMETIME;
}
void
target_laser_on(edict_t *self)
{
if (!self->activator)
{
self->activator = self;
}
self->spawnflags |= 0x80000001;
self->svflags &= ~SVF_NOCLIENT;
target_laser_think(self);
}
void
target_laser_off(edict_t *self)
{
self->spawnflags &= ~1;
self->svflags |= SVF_NOCLIENT;
self->nextthink = 0;
}
void
target_laser_use(edict_t *self, edict_t *other, edict_t *activator)
{
self->activator = activator;
if (self->spawnflags & 1)
{
target_laser_off(self);
}
else
{
target_laser_on(self);
}
}
void
target_laser_start(edict_t *self)
{
edict_t *ent;
self->movetype = MOVETYPE_NONE;
self->solid = SOLID_NOT;
self->s.renderfx |= RF_BEAM | RF_TRANSLUCENT;
self->s.modelindex = 1; /* must be non-zero */
/* set the beam diameter */
if (self->spawnflags & 64)
{
self->s.frame = 16;
}
else
{
self->s.frame = 4;
}
/* set the color */
if (self->spawnflags & 2)
{
self->s.skinnum = 0xf2f2f0f0;
}
else if (self->spawnflags & 4)
{
self->s.skinnum = 0xd0d1d2d3;
}
else if (self->spawnflags & 8)
{
self->s.skinnum = 0xf3f3f1f1;
}
else if (self->spawnflags & 16)
{
self->s.skinnum = 0xdcdddedf;
}
else if (self->spawnflags & 32)
{
self->s.skinnum = 0xe0e1e2e3;
}
if (!self->enemy)
{
if (self->target)
{
ent = G_Find(NULL, FOFS(targetname), self->target);
if (!ent)
{
gi.dprintf("%s at %s: %s is a bad target\n", self->classname,
vtos(self->s.origin), self->target);
}
self->enemy = ent;
}
else
{
G_SetMovedir(self->s.angles, self->movedir);
}
}
self->use = target_laser_use;
self->think = target_laser_think;
if (!self->dmg)
{
self->dmg = 1;
}
VectorSet(self->mins, -8, -8, -8);
VectorSet(self->maxs, 8, 8, 8);
gi.linkentity(self);
if (self->spawnflags & 1)
{
target_laser_on(self);
}
else
{
target_laser_off(self);
}
}
void
SP_target_laser(edict_t *self)
{
/* let everything else get spawned before we start firing */
self->think = target_laser_start;
self->nextthink = level.time + 1;
}
/* ========================================================== */
/*
* QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE
* speed How many seconds the ramping will take
* message two letters; starting lightlevel and ending lightlevel
*/
void
target_lightramp_think(edict_t *self)
{
char style[2];
style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2];
style[1] = 0;
gi.configstring(CS_LIGHTS + self->enemy->style, style);
if ((level.time - self->timestamp) < self->speed)
{
self->nextthink = level.time + FRAMETIME;
}
else if (self->spawnflags & 1)
{
char temp;
temp = self->movedir[0];
self->movedir[0] = self->movedir[1];
self->movedir[1] = temp;
self->movedir[2] *= -1;
}
}
void
target_lightramp_use(edict_t *self, edict_t *other, edict_t *activator)
{
if (!self->enemy)
{
edict_t *e;
/* check all the targets */
e = NULL;
while (1)
{
e = G_Find(e, FOFS(targetname), self->target);
if (!e)
{
break;
}
if (strcmp(e->classname, "light") != 0)
{
gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin));
gi.dprintf("target %s (%s at %s) is not a light\n",
self->target, e->classname, vtos(e->s.origin));
}
else
{
self->enemy = e;
}
}
if (!self->enemy)
{
gi.dprintf("%s target %s not found at %s\n",
self->classname, self->target, vtos(self->s.origin));
G_FreeEdict(self);
return;
}
}
self->timestamp = level.time;
target_lightramp_think(self);
}
void
SP_target_lightramp(edict_t *self)
{
if (!self->message || (strlen(self->message) != 2) ||
(self->message[0] < 'a') || (self->message[0] > 'z') ||
(self->message[1] < 'a') || (self->message[1] > 'z') ||
(self->message[0] == self->message[1]))
{
gi.dprintf("target_lightramp has bad ramp (%s) at %s\n",
self->message, vtos(self->s.origin));
G_FreeEdict(self);
return;
}
if (deathmatch->value)
{
G_FreeEdict(self);
return;
}
if (!self->target)
{
gi.dprintf("%s with no target at %s\n", self->classname,
vtos(self->s.origin));
G_FreeEdict(self);
return;
}
self->svflags |= SVF_NOCLIENT;
self->use = target_lightramp_use;
self->think = target_lightramp_think;
self->movedir[0] = self->message[0] - 'a';
self->movedir[1] = self->message[1] - 'a';
self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME);
}
/* ========================================================== */
/* QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8)
* When triggered, this initiates a level-wide earthquake.
* All players and monsters are affected.
* "speed" severity of the quake (default:200)
* "count" duration of the quake (default:5)
*/
void
target_earthquake_think(edict_t *self)
{
int i;
edict_t *e;
if (self->last_move_time < level.time)
{
gi.positioned_sound(self->s.origin, self, CHAN_AUTO,
self->noise_index, 1.0, ATTN_NONE, 0);
self->last_move_time = level.time + 0.5;
}
for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++)
{
if (!e->inuse)
{
continue;
}
if (!e->client)
{
continue;
}
if (!e->groundentity)
{
continue;
}
e->groundentity = NULL;
e->velocity[0] += crandom() * 150;
e->velocity[1] += crandom() * 150;
e->velocity[2] = self->speed * (100.0 / e->mass);
}
if (level.time < self->timestamp)
{
self->nextthink = level.time + FRAMETIME;
}
}
void
target_earthquake_use(edict_t *self, edict_t *other, edict_t *activator)
{
self->timestamp = level.time + self->count;
self->nextthink = level.time + FRAMETIME;
self->activator = activator;
self->last_move_time = 0;
}
void
SP_target_earthquake(edict_t *self)
{
if (!self->targetname)
{
gi.dprintf("untargeted %s at %s\n", self->classname,
vtos(self->s.origin));
}
if (!self->count)
{
self->count = 5;
}
if (!self->speed)
{
self->speed = 200;
}
self->svflags |= SVF_NOCLIENT;
self->think = target_earthquake_think;
self->use = target_earthquake_use;
self->noise_index = gi.soundindex("world/quake.wav");
}