diff options
Diffstat (limited to 'source/baseq2/g_target.c')
-rw-r--r-- | source/baseq2/g_target.c | 809 |
1 files changed, 809 insertions, 0 deletions
diff --git a/source/baseq2/g_target.c b/source/baseq2/g_target.c new file mode 100644 index 0000000..03a2264 --- /dev/null +++ b/source/baseq2/g_target.c @@ -0,0 +1,809 @@ +/* +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. + +*/ +#include "g_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 always 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")) + Q_snprintf (buffer, sizeof(buffer), "%s.wav", st.noise); + else + strncpy (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; // already 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) +{ + int effect; + + if (self->spawnflags & 2) + effect = 0; + else if (self->spawnflags & 1) + effect = EF_HYPERBLASTER; + else + effect = EF_BLASTER; + + 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 qtrue. Delay, target and +killtarget 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"); +} |