/* * 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"); }