/* =========================================================================== Copyright (C) 1997-2001 Id Software, Inc. Copyright (C) 2000-2002 Mr. Hyde and Mad Dog This file is part of Lazarus Quake 2 Mod source code. Lazarus Quake 2 Mod source code 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. Lazarus Quake 2 Mod source code 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 Lazarus Quake 2 Mod source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "g_local.h" #define TRIGGER_MONSTER 1 #define TRIGGER_NOT_PLAYER 2 #define TRIGGER_START_OFF 4 #define TRIGGER_NEED_USE 8 #define TRIGGER_CAMOWNER 16 #define TRIGGER_LOOKTARGET 32 #define TRIGGER_TARGET_OFF 64 void InitTrigger (edict_t *self) { if (!VectorCompare (self->s.angles, vec3_origin)) G_SetMovedir (self->s.angles, self->movedir); self->solid = SOLID_TRIGGER; self->movetype = MOVETYPE_NONE; gi.setmodel (self, self->model); self->svflags = SVF_NOCLIENT; } // Knightmare- same as above, but for bbox triggers void InitTriggerBbox (edict_t *self) { if (!VectorCompare (self->s.angles, vec3_origin)) G_SetMovedir (self->s.angles, self->movedir); self->solid = SOLID_TRIGGER; self->movetype = MOVETYPE_NONE; if ( (!VectorLength(self->bleft)) && (!VectorLength(self->tright)) ) { VectorSet(self->bleft, -16, -16, -16); VectorSet(self->tright, 16, 16, 16); } VectorCopy (self->bleft, self->mins); VectorCopy (self->tright, self->maxs); self->svflags = SVF_NOCLIENT; } // the wait time has passed, so set back up for another activation void multi_wait (edict_t *ent) { ent->nextthink = 0; } // the trigger was just activated // ent->activator 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 (edict_t *ent) { if (ent->nextthink) return; // already been triggered G_UseTargets (ent, ent->activator); if (ent->wait > 0) { ent->think = multi_wait; ent->nextthink = level.time + ent->wait; } else { // we can't just remove (self) here, because this is a touch function // called while looping through area links... ent->touch = NULL; ent->nextthink = level.time + FRAMETIME; ent->think = G_FreeEdict; } } void Use_Multi (edict_t *ent, edict_t *other, edict_t *activator) { ent->activator = activator; multi_trigger (ent); } void Touch_Multi (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { if (other->client || (other->flags & FL_ROBOT)) { if (self->spawnflags & TRIGGER_NOT_PLAYER) return; } else if (other->svflags & SVF_MONSTER) { if (!(self->spawnflags & TRIGGER_MONSTER)) return; } else return; if ( (self->spawnflags & TRIGGER_CAMOWNER) && (!other->client || !other->client->spycam)) return; if (!VectorCompare(self->movedir, vec3_origin)) { vec3_t forward; AngleVectors(other->s.angles, forward, NULL, NULL); if (_DotProduct(forward, self->movedir) < 0) return; } self->activator = other; multi_trigger (self); } /*QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED Variable sized repeatable trigger. Must be targeted at one or more entities. If "delay" is set, the trigger waits some time after activating before firing. "wait" : Seconds between triggerings. (.2 default) sounds 1) secret 2) beep beep 3) large switch 4) set "message" to text string */ void trigger_enable (edict_t *self, edict_t *other, edict_t *activator) { self->solid = SOLID_TRIGGER; self->use = Use_Multi; gi.linkentity (self); } void SP_trigger_multiple (edict_t *ent) { ent->class_id = ENTITY_TRIGGER_MULTIPLE; if (ent->sounds == 1) ent->noise_index = gi.soundindex ("misc/secret.wav"); else if (ent->sounds == 2) ent->noise_index = gi.soundindex ("misc/talk.wav"); else if (ent->sounds == 3) // DWH - should be silent // ent->noise_index = gi.soundindex ("misc/trigger1.wav"); ent->noise_index = -1; if (!ent->wait) ent->wait = 0.2; ent->touch = Touch_Multi; ent->movetype = MOVETYPE_NONE; ent->svflags |= SVF_NOCLIENT; if (ent->spawnflags & TRIGGER_CAMOWNER) ent->svflags |= SVF_TRIGGER_CAMOWNER; if (ent->spawnflags & TRIGGER_START_OFF) { ent->solid = SOLID_NOT; ent->use = trigger_enable; } else { ent->solid = SOLID_TRIGGER; ent->use = Use_Multi; } if (!VectorCompare(ent->s.angles, vec3_origin)) G_SetMovedir (ent->s.angles, ent->movedir); gi.setmodel (ent, ent->model); gi.linkentity (ent); } /*QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED 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 TRIGGERED, this trigger must be triggered before it is live. sounds 1) secret 2) beep beep 3) large switch 4) "message" string to be displayed when triggered */ void SP_trigger_once(edict_t *ent) { // make old maps work because I messed up on flag assignments here // triggered was on bit 1 when it should have been on bit 4 if (ent->spawnflags & 1) { vec3_t v; VectorMA (ent->mins, 0.5, ent->size, v); ent->spawnflags &= ~1; ent->spawnflags |= TRIGGER_START_OFF; gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v)); } ent->wait = -1; SP_trigger_multiple (ent); ent->class_id = ENTITY_TRIGGER_ONCE; } /*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) This fixed size trigger cannot be touched, it can only be fired by other events. */ void trigger_relay_use (edict_t *self, edict_t *other, edict_t *activator) { self->count--; if (!self->count) { self->think = G_FreeEdict; self->nextthink = level.time + FRAMETIME; } G_UseTargets (self, activator); } void SP_trigger_relay (edict_t *self) { self->class_id = ENTITY_TRIGGER_RELAY; // DWH - gives trigger_relay same message-displaying, sound-playing capabilities // as trigger_multiple and trigger_once if (self->sounds == 1) self->noise_index = gi.soundindex ("misc/secret.wav"); else if (self->sounds == 2) self->noise_index = gi.soundindex ("misc/talk.wav"); else if (self->sounds == 3) self->noise_index = -1; if (!self->count) self->count=-1; // end DWH self->use = trigger_relay_use; } /* ============================================================================== trigger_key Lazarus editions: Spawnflags 1 = Multi-use. If set, item is required EVERY time trigger_key is targeted 2 = Keep key. If set, player doesn't give up the key when trigger_key is used. 4 = Silent. If set, neither the "You need" message and sound nor keyuse.wav are played. This is useful for trigger_keys used only to remove items from the player. Lazarus also removes non-multi-use trigger_keys once used, to free up space in the edicts array. ============================================================================== */ /*QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8) A relay trigger that only fires it's targets if player has the proper key. Use "item" to specify the required key, for example "key_data_cd" */ void trigger_key_use (edict_t *self, edict_t *other, edict_t *activator) { int index; if (!self->item) return; if (!activator->client) return; index = ITEM_INDEX(self->item); if (!activator->client->pers.inventory[index]) { if (level.time < self->touch_debounce_time) return; self->touch_debounce_time = level.time + 5.0; if (!(self->spawnflags & 4)) { // Knightmare- implement key_message if (self->key_message && strlen(self->key_message)) safe_centerprintf (activator, "%s", self->key_message); else safe_centerprintf (activator, "You need the %s", self->item->pickup_name); gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keytry.wav"), 1, ATTN_NORM, 0); } return; } if (!(self->spawnflags & 4)) gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keyuse.wav"), 1, ATTN_NORM, 0); if (coop->value) { int player; edict_t *ent; if (strcmp(self->item->classname, "key_power_cube") == 0) { int cube; for (cube = 0; cube < 8; cube++) if (activator->client->pers.power_cubes & (1 << cube)) break; for (player = 1; player <= game.maxclients; player++) { ent = &g_edicts[player]; if (!ent->inuse) continue; if (!ent->client) continue; // DWH: keep key if (!(self->spawnflags & 2)) { if (ent->client->pers.power_cubes & (1 << cube)) { ent->client->pers.inventory[index]--; ent->client->pers.power_cubes &= ~(1 << cube); } } } } else { for (player = 1; player <= game.maxclients; player++) { ent = &g_edicts[player]; if (!ent->inuse) continue; if (!ent->client) continue; // DWH: keep key if (!(self->spawnflags & 2)) ent->client->pers.inventory[index] = 0; } } } // DWH: keep key else if (!(self->spawnflags & 2)) { activator->client->pers.inventory[index]--; } G_UseTargets (self, activator); // DWH - multi-use if (!(self->spawnflags & 1)) { self->use = NULL; self->think = G_FreeEdict; self->nextthink = level.time + FRAMETIME; gi.linkentity(self); } } void SP_trigger_key (edict_t *self) { if (!st.item) { gi.dprintf("no key item for trigger_key at %s\n", vtos(self->s.origin)); return; } self->item = FindItemByClassname (st.item); if (!self->item) { gi.dprintf("item %s not found for trigger_key at %s\n", st.item, vtos(self->s.origin)); return; } if (!self->target) { gi.dprintf("%s at %s has no target\n", self->classname, vtos(self->s.origin)); return; } self->class_id = ENTITY_TRIGGER_KEY; gi.soundindex ("misc/keytry.wav"); gi.soundindex ("misc/keyuse.wav"); self->use = trigger_key_use; } /* ============================================================================== trigger_counter ============================================================================== */ /*QUAKED trigger_counter (.5 .5 .5) ? nomessage Acts as an intermediary for an action that takes multiple inputs. If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished. After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself. */ void trigger_counter_use(edict_t *self, edict_t *other, edict_t *activator) { if (self->count == 0) { G_FreeEdict(self); // DWH return; } self->count--; if (self->count) { if (! (self->spawnflags & 1)) { safe_centerprintf(activator, "%i more to go...", self->count); gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); } return; } if (! (self->spawnflags & 1)) { safe_centerprintf(activator, "Sequence completed!"); gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); } self->activator = activator; multi_trigger (self); // DWH if (self->count == 0) { self->think = G_FreeEdict; self->nextthink = level.time + 1; } } void SP_trigger_counter (edict_t *self) { self->class_id = ENTITY_TRIGGER_COUNTER; self->wait = -1; if (!self->count) self->count = 2; self->use = trigger_counter_use; } /* ============================================================================== trigger_always ============================================================================== */ /*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8) This trigger will always fire. It is activated by the world. */ void SP_trigger_always (edict_t *ent) { ent->class_id = ENTITY_TRIGGER_ALWAYS; // we must have some delay to make sure our use targets are present if (ent->delay < 0.2) ent->delay = 0.2; G_UseTargets(ent, ent); } /* ============================================================================== trigger_push ============================================================================== */ #define PUSH_ONCE 1 #define PUSH_CUSTOM_SND 2 #define PUSH_START_OFF 4 static int windsound; void trigger_push_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { if (strcmp(other->classname, "grenade") == 0) { VectorScale (self->movedir, self->speed * 10, other->velocity); } // Lazarus else if (other->movetype == MOVETYPE_PUSHABLE) { vec3_t v; VectorScale (self->movedir, self->speed * 2000 / (float)(other->mass), v); VectorAdd(other->velocity,v,other->velocity); } else if (other->health > 0) { VectorScale (self->movedir, self->speed * 10, other->velocity); if (other->client) { // don't take falling damage immediately from this VectorCopy (other->velocity, other->client->oldvelocity); if (other->fly_sound_debounce_time < level.time) { other->fly_sound_debounce_time = level.time + 1.5; if (self->spawnflags & PUSH_CUSTOM_SND) { if (self->noise_index) gi.sound(other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0); } else gi.sound (other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0); } } } if (self->spawnflags & PUSH_ONCE) G_FreeEdict (self); } void trigger_push_use (edict_t *self, edict_t *other, edict_t *activator) { if (self->solid == SOLID_TRIGGER) self->solid = SOLID_NOT; else // if (self->solid == SOLID_NOT) self->solid = SOLID_TRIGGER; gi.linkentity (self); } /*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE CUSTOM_SOUND START_OFF Pushes the player "speed" defaults to 1000 "noise" (path/file.wav) */ void SP_trigger_push (edict_t *self) { self->class_id = ENTITY_TRIGGER_PUSH; InitTrigger (self); // DWH: Custom (or no) sound if (self->spawnflags & PUSH_CUSTOM_SND) { if (st.noise) self->noise_index = gi.soundindex(st.noise); else self->noise_index = 0; } else { windsound = gi.soundindex ("misc/windfly.wav"); } self->touch = trigger_push_touch; if (!self->speed) self->speed = 1000; // Knightmare- switchable option if (self->targetname) { self->use = trigger_push_use; if (self->spawnflags & PUSH_START_OFF) self->solid = SOLID_NOT; } else if (self->spawnflags & PUSH_START_OFF) { gi.dprintf ("trigger_push is START_OFF but has no targetname!\n"); self->spawnflags &= ~PUSH_START_OFF; self->spawnflags |= PUSH_CUSTOM_SND; if (st.noise) self->noise_index = gi.soundindex(st.noise); else self->noise_index = 0; } // end Knightmare gi.linkentity (self); } /*QUAKED trigger_push_bbox (.5 .5 .5) ? PUSH_ONCE CUSTOM_SOUND START_OFF Pushes the player "speed" defaults to 1000 "noise" (path/file.wav) */ void SP_trigger_push_bbox (edict_t *self) { self->class_id = ENTITY_TRIGGER_PUSH; InitTriggerBbox (self); // DWH: Custom (or no) sound if (self->spawnflags & PUSH_CUSTOM_SND) { if (st.noise) self->noise_index = gi.soundindex(st.noise); else self->noise_index = 0; } else { windsound = gi.soundindex ("misc/windfly.wav"); } self->touch = trigger_push_touch; if (!self->speed) self->speed = 1000; // Knightmare- switchable option if (self->targetname) { self->use = trigger_push_use; if (self->spawnflags & PUSH_START_OFF) self->solid = SOLID_NOT; } else if (self->spawnflags & PUSH_START_OFF) { gi.dprintf ("trigger_push is START_OFF but has no targetname!\n"); self->spawnflags &= ~PUSH_START_OFF; self->spawnflags |= PUSH_CUSTOM_SND; if (st.noise) self->noise_index = gi.soundindex(st.noise); else self->noise_index = 0; } // end Knightmare gi.linkentity (self); } /* ============================================================================== trigger_hurt ============================================================================== */ /*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW Any entity that touches this will be hurt. It does dmg points of damage each server frame "dmg" default 5 (whole numbers only) */ #define SF_HURT_START_OFF 1 #define SF_HURT_TOGGLE 2 #define SF_HURT_SILENT 4 // supresses playing the sound #define SF_HURT_NO_PROTECTION 8 // *nothing* stops the damage #define SF_HURT_SLOW 16 // changes the damage rate to once per second #define SF_HURT_NOGIB 32 // Lazarus: won't gib entity #define SF_HURT_ENVIRONMENT 64 // Lazarus: environment suit protects from damage void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); void hurt_use (edict_t *self, edict_t *other, edict_t *activator) { if (self->solid == SOLID_NOT) { int i, num; edict_t *touch[MAX_EDICTS], *hurtme; self->solid = SOLID_TRIGGER; // Lazaurs: Add check for non-moving (i.e. idle monsters) within trigger_hurt // at first activation num = gi.BoxEdicts (self->absmin, self->absmax, touch, MAX_EDICTS, AREA_SOLID); for (i=0 ; isolid = SOLID_NOT; gi.linkentity (self); if (!(self->spawnflags & SF_HURT_TOGGLE)) self->use = NULL; } void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { int dflags; if (!other->takedamage) return; if (self->timestamp > level.time) return; // DWH - don't "heal" other if he's at max_health if ( (self->dmg < 0) && (other->health >= other->max_health)) return; if (self->spawnflags & SF_HURT_SLOW) self->timestamp = level.time + 1; else self->timestamp = level.time + FRAMETIME; if ( !(self->spawnflags & SF_HURT_SILENT) ) { // DWH - Original code would fail to play a sound for // SF=16 unless player just HAPPENED to hit // trigger_hurt at framenum = an integral number of // full seconds. if ( ((level.framenum % 10) == 0 ) || (self->spawnflags & SF_HURT_SLOW) ) gi.sound (other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0); } if (self->spawnflags & SF_HURT_NO_PROTECTION) dflags = DAMAGE_NO_PROTECTION; else dflags = 0; // Lazarus: healing, no gib, and environment suit protection if (self->dmg > 0) { int damage = self->dmg; if (self->spawnflags & SF_HURT_NOGIB) { if (skill->value > 0) damage = min(damage, other->health - other->gib_health - 1); else damage = min(damage, 2*(other->health - other->gib_health - 1)); if (damage < 0) damage = 0; } if (other->client && (self->spawnflags & SF_HURT_ENVIRONMENT) && (other->client->enviro_framenum > level.framenum)) damage = 0; if (damage > 0) T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, damage, self->dmg, dflags, MOD_TRIGGER_HURT); } else { other->health -= self->dmg; if (other->health > other->max_health) other->health = other->max_health; } } void SP_trigger_hurt (edict_t *self) { self->class_id = ENTITY_TRIGGER_HURT; InitTrigger (self); self->touch = hurt_touch; if (!self->dmg) self->dmg = 5; // DWH - play different sound for healing if (self->dmg > 0) self->noise_index = gi.soundindex ("world/electro.wav"); else self->noise_index = gi.soundindex ("items/s_health.wav"); if (self->spawnflags & SF_HURT_START_OFF) self->solid = SOLID_NOT; else self->solid = SOLID_TRIGGER; if (self->spawnflags & SF_HURT_TOGGLE) self->use = hurt_use; gi.linkentity (self); } /*QUAKED trigger_hurt_bbox (.5 .5 .5) (-8 -8 -8) (8 8 8) START_OFF TOGGLE SILENT NO_PROTECTION SLOW Any entity that touches this will be hurt. Same as trigger_hurt, except it doesn't use a model. It does dmg points of damage each server frame SILENT supresses playing the sound SLOW changes the damage rate to once per second NO_PROTECTION *nothing* stops the damage "dmg" default 5 (whole numbers only) bleft Min b-box coords XYZ. Default = -16 -16 -16 tright Max b-box coords XYZ. Default = 16 16 16 */ void SP_trigger_hurt_bbox (edict_t *self) { self->class_id = ENTITY_TRIGGER_HURT; InitTriggerBbox (self); self->touch = hurt_touch; if (!self->dmg) self->dmg = 5; // DWH - play different sound for healing if (self->dmg > 0) self->noise_index = gi.soundindex ("world/electro.wav"); else self->noise_index = gi.soundindex ("items/s_health.wav"); if (self->spawnflags & SF_HURT_START_OFF) self->solid = SOLID_NOT; else self->solid = SOLID_TRIGGER; if (self->spawnflags & SF_HURT_TOGGLE) self->use = hurt_use; gi.linkentity (self); } /* ============================================================================== trigger_gravity ============================================================================== */ void trigger_gravity_use (edict_t *self, edict_t *other, edict_t *activator) { if (self->solid == SOLID_TRIGGER) self->solid = SOLID_NOT; else // if (self->solid == SOLID_NOT) self->solid = SOLID_TRIGGER; gi.linkentity (self); } void trigger_gravity_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { other->gravity = self->gravity; } /*QUAKED trigger_gravity (.5 .5 .5) ? TOGGLE START_OFF Changes the touching entites gravity to the value of "gravity". 1.0 is standard gravity for the level. TOGGLE - Can be turned on and off START_OFF - Starts turned off */ void SP_trigger_gravity (edict_t *self) { if (st.gravity == 0) { gi.dprintf("trigger_gravity without gravity set at %s\n", vtos(self->s.origin)); G_FreeEdict (self); return; } InitTrigger (self); self->class_id = ENTITY_TRIGGER_GRAVITY; self->gravity = atoi(st.gravity); // Knightmare added if (self->spawnflags & 1) self->use = trigger_gravity_use; if (self->spawnflags & 2) { self->solid = SOLID_NOT; self->use = trigger_gravity_use; } // end Knightmare self->touch = trigger_gravity_touch; } /*QUAKED trigger_gravity_bbox (.5 .5 .5) ? TOGGLE START_OFF Changes the touching entites gravity to the value of "gravity". 1.0 is standard gravity for the level. Same as trigger_gravity, except that it doesn't use a model. TOGGLE - Can be turned on and off START_OFF - Starts turned off bleft Min b-box coords XYZ. Default = -16 -16 -16 tright Max b-box coords XYZ. Default = 16 16 16 */ void SP_trigger_gravity_bbox (edict_t *self) { if (st.gravity == 0) { gi.dprintf("trigger_gravity_bbox without gravity set at %s\n", vtos(self->s.origin)); G_FreeEdict (self); return; } InitTriggerBbox (self); self->class_id = ENTITY_TRIGGER_GRAVITY; self->gravity = atoi(st.gravity); // Knightmare added if (self->spawnflags & 1) self->use = trigger_gravity_use; if (self->spawnflags & 2) { self->solid = SOLID_NOT; self->use = trigger_gravity_use; } // end Knightmare self->touch = trigger_gravity_touch; } /* ============================================================================== trigger_monsterjump ============================================================================== */ /*QUAKED trigger_monsterjump (.5 .5 .5) ? Walking monsters that touch this will jump in the direction of the trigger's angle "speed" default to 200, the speed thrown forward "height" default to 200, the speed thrown upwards */ void trigger_monsterjump_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { if (other->flags & (FL_FLY | FL_SWIM) ) return; if (other->svflags & SVF_DEADMONSTER) return; if ( !(other->svflags & SVF_MONSTER)) return; // set XY even if not on ground, so the jump will clear lips other->velocity[0] = self->movedir[0] * self->speed; other->velocity[1] = self->movedir[1] * self->speed; if (!other->groundentity) return; other->groundentity = NULL; other->velocity[2] = self->movedir[2]; } void SP_trigger_monsterjump (edict_t *self) { self->class_id = ENTITY_TRIGGER_MONSTERJUMP; if (!self->speed) self->speed = 200; if (!st.height) st.height = 200; if (self->s.angles[YAW] == 0) self->s.angles[YAW] = 360; InitTrigger (self); self->touch = trigger_monsterjump_touch; self->movedir[2] = st.height; } /*QUAKED trigger_monsterjump_bbox (.5 .5 .5) ? Walking monsters that touch this will jump in the direction of the trigger's angle Same as trigger_monsterjump, except that it doesn't use a model. "speed" default to 200, the speed thrown forward "height" default to 200, the speed thrown upwards bleft Min b-box coords XYZ. Default = -16 -16 -16 tright Max b-box coords XYZ. Default = 16 16 16 */ void SP_trigger_monsterjump_bbox (edict_t *self) { self->class_id = ENTITY_TRIGGER_MONSTERJUMP; if (!self->speed) self->speed = 200; if (!st.height) st.height = 200; if (self->s.angles[YAW] == 0) self->s.angles[YAW] = 360; InitTriggerBbox (self); self->touch = trigger_monsterjump_touch; self->movedir[2] = st.height; } //=============================================================== // DWH additions //=============================================================== /*QUAKED tremor_trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED Variable sized repeatable trigger. Must be targeted at one or more entities. If "delay" is set, the trigger waits some time after activating before firing. "wait" : Seconds between triggerings. (.2 default) Same as trigger_multiple, but toggles on/off when targeted. sounds 1) secret 2) beep beep 3) large switch 4) set "message" to text string */ void tremor_trigger_enable (edict_t *self, edict_t *other, edict_t *activator); void Use_tremor_Multi (edict_t *self, edict_t *other, edict_t *activator) { self->count--; if (!self->count) { self->think = G_FreeEdict; self->nextthink = level.time + 1; } else { self->use = tremor_trigger_enable; self->solid = SOLID_NOT; gi.linkentity(self); } } void tremor_trigger_enable (edict_t *self, edict_t *other, edict_t *activator) { self->solid = SOLID_TRIGGER; self->use = Use_tremor_Multi; gi.linkentity (self); } void SP_tremor_trigger_multiple (edict_t *ent) { ent->class_id = ENTITY_TREMOR_TRIGGER_MULTIPLE; if (ent->sounds == 1) ent->noise_index = gi.soundindex ("misc/secret.wav"); else if (ent->sounds == 2) ent->noise_index = gi.soundindex ("misc/talk.wav"); else if (ent->sounds == 3) // DWH - should be silent // ent->noise_index = gi.soundindex ("misc/trigger1.wav"); ent->noise_index = -1; if (!ent->wait) ent->wait = 0.2; ent->touch = Touch_Multi; ent->movetype = MOVETYPE_NONE; ent->svflags |= SVF_NOCLIENT; if (ent->spawnflags & TRIGGER_CAMOWNER) ent->svflags |= SVF_TRIGGER_CAMOWNER; if (ent->spawnflags & TRIGGER_START_OFF) { ent->solid = SOLID_NOT; ent->use = tremor_trigger_enable; } else { ent->solid = SOLID_TRIGGER; ent->use = Use_tremor_Multi; } if (!VectorCompare(ent->s.angles, vec3_origin)) G_SetMovedir (ent->s.angles, ent->movedir); gi.setmodel (ent, ent->model); gi.linkentity (ent); } //========================================================================================= // TRIGGER_MASS - triggers its targets when touched by any entity with mass >= mass value // of trigger //========================================================================================= void trigger_mass_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { if (other->mass < self->mass) return; self->activator = other; multi_trigger (self); } void SP_trigger_mass (edict_t *self) { self->class_id = ENTITY_TRIGGER_MASS; // Fires its target if touched by an entity weighing at least // self->mass if (self->sounds == 1) self->noise_index = gi.soundindex ("misc/secret.wav"); else if (self->sounds == 2) self->noise_index = gi.soundindex ("misc/talk.wav"); else if (self->sounds == 3) // DWH - should be silent // self->noise_index = gi.soundindex ("misc/trigger1.wav"); self->noise_index = -1; if (!self->wait) self->wait = 0.2; self->touch = trigger_mass_touch; self->movetype = MOVETYPE_NONE; self->svflags |= SVF_NOCLIENT; if (self->spawnflags & TRIGGER_START_OFF) { self->solid = SOLID_NOT; self->use = trigger_enable; } else { self->solid = SOLID_TRIGGER; self->use = Use_Multi; } if (!self->mass) self->mass = 100; gi.setmodel (self, self->model); gi.linkentity (self); } void SP_trigger_mass_bbox (edict_t *self) { self->class_id = ENTITY_TRIGGER_MASS; // Fires its target if touched by an entity weighing at least // self->mass if (self->sounds == 1) self->noise_index = gi.soundindex ("misc/secret.wav"); else if (self->sounds == 2) self->noise_index = gi.soundindex ("misc/talk.wav"); else if (self->sounds == 3) // DWH - should be silent // self->noise_index = gi.soundindex ("misc/trigger1.wav"); self->noise_index = -1; if (!self->wait) self->wait = 0.2; self->touch = trigger_mass_touch; InitTriggerBbox (self); if (self->spawnflags & TRIGGER_START_OFF) { self->solid = SOLID_NOT; self->use = trigger_enable; } else { self->solid = SOLID_TRIGGER; self->use = Use_Multi; } if (!self->mass) self->mass = 100; gi.linkentity (self); } //======================================================================================= // TRIGGER_INSIDE - triggers its targets when the bounding box for its pathtarget is // completely inside the trigger field //======================================================================================= void trigger_inside_think (edict_t *self) { int i, num; edict_t *touch[MAX_EDICTS], *hit; num = gi.BoxEdicts (self->absmin, self->absmax, touch, MAX_EDICTS, AREA_SOLID); for (i=0 ; iinuse) continue; if (!hit->targetname) continue; if (Q_stricmp(self->pathtarget, hit->targetname)) continue; // must be COMPLETELY inside if (hit->absmin[0] < self->absmin[0]) continue; if (hit->absmin[1] < self->absmin[1]) continue; if (hit->absmin[2] < self->absmin[2]) continue; if (hit->absmax[0] > self->absmax[0]) continue; if (hit->absmax[1] > self->absmax[1]) continue; if (hit->absmax[2] > self->absmax[2]) continue; G_UseTargets (self, hit); if (self->wait > 0) self->nextthink = level.time + self->wait; else { self->nextthink = level.time + FRAMETIME; self->think = G_FreeEdict; } gi.linkentity(self); return; } self->nextthink = level.time + FRAMETIME; gi.linkentity(self); } void SP_trigger_inside (edict_t *self) { vec3_t v; VectorMA (self->mins, 0.5, self->size, v); if (!self->target) { gi.dprintf("trigger_inside with no target at %s.\n",vtos(v)); G_FreeEdict(self); return; } if (!self->pathtarget) { gi.dprintf("trigger_inside with no pathtarget at %s.\n",vtos(v)); G_FreeEdict(self); return; } self->class_id = ENTITY_TRIGGER_INSIDE; self->movetype = MOVETYPE_NONE; self->svflags |= SVF_NOCLIENT; self->solid = SOLID_TRIGGER; if (!self->wait) self->wait = 0.2; gi.setmodel (self,self->model); self->think = trigger_inside_think; self->nextthink = level.time + 1.0; gi.linkentity(self); } void SP_trigger_inside_bbox (edict_t *self) { vec3_t v; VectorMA (self->mins, 0.5, self->size, v); if (!self->target) { gi.dprintf("trigger_inside_bbox with no target at %s.\n",vtos(v)); G_FreeEdict(self); return; } if (!self->pathtarget) { gi.dprintf("trigger_inside_bbox with no pathtarget at %s.\n",vtos(v)); G_FreeEdict(self); return; } InitTriggerBbox (self); self->class_id = ENTITY_TRIGGER_INSIDE; if (!self->wait) self->wait = 0.2; self->think = trigger_inside_think; self->nextthink = level.time + 1.0; gi.linkentity(self); } //================================================================================== // TRIGGER_SCALES - coupled with target_characters, displays the weight of all // entities that are "standing on" the trigger. //================================================================================== float weight_on_top(edict_t *ent) { float weight; int i; edict_t *e; weight = 0.0; for (i=1, e=g_edicts+i; iinuse) continue; if (e->groundentity == ent) { weight += e->mass; weight += weight_on_top(e); } } return weight; } void trigger_scales_think (edict_t *self) { float f, fx, fy; int i, num; int weight; edict_t *e, *touch[MAX_EDICTS], *hit; num = gi.BoxEdicts (self->absmin, self->absmax, touch, MAX_EDICTS, AREA_SOLID); weight = 0; for (i=0 ; iinuse) continue; if (!hit->mass) continue; fx = fy = 0.0; if (hit->absmin[0] < self->absmin[0]) fx += (self->absmin[0] - hit->absmin[0])/hit->size[0]; if (hit->absmax[0] > self->absmax[0]) fx += (hit->absmax[0] - self->absmax[0])/hit->size[0]; if (hit->absmin[1] < self->absmin[1]) fy += (self->absmin[1] - hit->absmin[1])/hit->size[1]; if (hit->absmax[1] > self->absmax[1]) fy += (hit->absmax[1] - self->absmax[1])/hit->size[1]; f = (1.0 - fx - fy + fx*fy); if (f > 0) weight += f * hit->mass; weight += f*weight_on_top(hit); } if (weight != self->mass) { self->mass = weight; for (e = self->teammaster; e; e = e->teamchain) { if (!e->count) continue; num = e->count; if (weight < pow(10,num-1)) e->s.frame = 12; else e->s.frame = ( weight % (int)pow(10,num) ) / ( pow(10,num-1) ); } } self->nextthink = level.time + FRAMETIME; gi.linkentity(self); } void SP_trigger_scales (edict_t *self) { vec3_t v; VectorMA (self->mins, 0.5, self->size, v); if (!self->team) { gi.dprintf("trigger_scales with no team at %s.\n",vtos(v)); G_FreeEdict(self); return; } self->class_id = ENTITY_TRIGGER_SCALES; self->movetype = MOVETYPE_NONE; self->svflags |= SVF_NOCLIENT; self->solid = SOLID_TRIGGER; gi.setmodel (self,self->model); self->think = trigger_scales_think; self->nextthink = level.time + 1.0; self->mass = 0; gi.linkentity(self); } void SP_trigger_scales_bbox (edict_t *self) { vec3_t v; VectorMA (self->mins, 0.5, self->size, v); if (!self->team) { gi.dprintf("trigger_scales_bbox with no team at %s.\n",vtos(v)); G_FreeEdict(self); return; } InitTriggerBbox (self); self->class_id = ENTITY_TRIGGER_SCALES; self->think = trigger_scales_think; self->nextthink = level.time + 1.0; self->mass = 0; gi.linkentity(self); } //====================================================================================== // TRIGGER_BBOX - Exactly like a tremor_trigger_multiple, but uses bleft, tright fields // to define extents of trigger field rather than a brush model. This // helps lower the total brush model count, which in turn helps head off // Index Overflow errors. //====================================================================================== void trigger_bbox_reset (edict_t *self) { self->takedamage = DAMAGE_YES; self->health = self->max_health; gi.linkentity(self); } void trigger_bbox_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { self->activator = attacker; self->takedamage = DAMAGE_NO; G_UseTargets (self, self->activator); self->count--; if (!self->count) { self->think = G_FreeEdict; self->nextthink = level.time + self->delay + FRAMETIME; return; } if (self->wait >= 0) { self->nextthink = level.time + self->wait; self->think = trigger_bbox_reset; } gi.linkentity(self); } void trigger_bbox_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { if (self->nextthink) return; // already "touched" and waiting if ((other->client) && (self->spawnflags & 2)) return; if ((other->svflags & SVF_MONSTER) && !(self->spawnflags & 1)) return; if (!other->client && !(other->svflags & SVF_MONSTER)) return; if (other->client && other->client->spycam && !(self->svflags & SVF_TRIGGER_CAMOWNER)) return; if ((self->svflags & SVF_TRIGGER_CAMOWNER) && (!other->client || !other->client->spycam)) return; self->activator = other; G_UseTargets(self,self->activator); if (self->wait > 0) { self->count--; if (!self->count) { self->think = G_FreeEdict; self->nextthink = level.time + FRAMETIME; } else { self->think = multi_wait; self->nextthink = level.time + self->wait; } } else { self->think = G_FreeEdict; self->nextthink = level.time + FRAMETIME; } } void trigger_bbox_use (edict_t *self, edict_t *other, edict_t *activator) { self->count--; if (!self->count) { self->think = G_FreeEdict; self->nextthink = level.time + 1; } else { if (self->solid == SOLID_NOT) { if (self->max_health > 0) { self->solid = SOLID_BBOX; self->touch = NULL; } else { self->solid = SOLID_TRIGGER; self->touch = trigger_bbox_touch; } } else self->solid = SOLID_NOT; gi.linkentity(self); } } void SP_trigger_bbox (edict_t *ent) { ent->class_id = ENTITY_TRIGGER_BBOX; if (ent->sounds == 1) ent->noise_index = gi.soundindex ("misc/secret.wav"); else if (ent->sounds == 2) ent->noise_index = gi.soundindex ("misc/talk.wav"); else if (ent->sounds == 3) ent->noise_index = -1; if (!ent->wait) ent->wait = 0.2; ent->movetype = MOVETYPE_NONE; if (ent->spawnflags & TRIGGER_CAMOWNER) ent->svflags |= SVF_TRIGGER_CAMOWNER; if ( (!VectorLength(ent->bleft)) && (!VectorLength(ent->tright)) ) { VectorSet(ent->bleft,-16,-16,-16); VectorSet(ent->tright,16, 16, 16); } VectorCopy(ent->bleft,ent->mins); VectorCopy(ent->tright,ent->maxs); ent->max_health = ent->health; if (ent->health > 0) { ent->svflags |= SVF_DEADMONSTER; ent->die = trigger_bbox_die; ent->takedamage = DAMAGE_YES; } else ent->svflags |= SVF_NOCLIENT; if (ent->spawnflags & TRIGGER_START_OFF) ent->solid = SOLID_NOT; else { if (ent->health) { ent->solid = SOLID_BBOX; ent->touch = NULL; } else { ent->solid = SOLID_TRIGGER; ent->touch = trigger_bbox_touch; } } ent->use = trigger_bbox_use; gi.linkentity (ent); } //============================================================================================ // TRIGGER_LOOK // Serves the same function as trigger_multiple, but: // 1) Is only usable by players // 2) Player must be looking at a point within bleft-tright of the origin of the trigger_look // 3) If USE spawnflag (=8) is set, player must be pressing +use to trigger void trigger_look_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { trace_t tr; vec_t dist; vec3_t dir, forward, left, up, end, start; if (!other->client) return; if (self->nextthink) return; // already been triggered if ( (self->spawnflags & TRIGGER_NEED_USE) && !(other->client->use)) return; if ( (self->spawnflags & TRIGGER_CAMOWNER) && !other->client->spycam) return; if ( self->spawnflags & 32 ) { // Then trigger only fires if looking at TARGET, not trigger bbox edict_t *target; int num_triggered=0; edict_t *what; vec3_t endpos; target = G_Find(NULL,FOFS(targetname),self->target); while (target && !num_triggered) { what = LookingAt(other,0,endpos,NULL); if (target->inuse && (LookingAt(other,0,NULL,NULL) == target)) { num_triggered++; self->activator = other; G_UseTarget (self, other, target); } else target = G_Find(target,FOFS(targetname),self->target); } if (!num_triggered) return; } else { if (other->client->spycam) { vec3_t f1, l1, u1; AngleVectors(other->client->spycam->s.angles, forward, left, up); VectorScale(forward, other->client->spycam->move_origin[0],f1); VectorScale(left, -other->client->spycam->move_origin[1],l1); VectorScale(up, other->client->spycam->move_origin[2],u1); VectorAdd(other->client->spycam->s.origin,f1,start); VectorAdd(start,l1,start); VectorAdd(start,u1,start); } else { AngleVectors(other->client->v_angle, forward, NULL, NULL); VectorCopy(other->s.origin,start); start[2] += other->viewheight; } VectorSubtract(self->s.origin,start,dir); dist = VectorLength(dir); VectorMA(start,dist,forward,end); tr = gi.trace(start,vec3_origin,vec3_origin,end,other,MASK_OPAQUE); // See if we're looking at origin, within bleft, tright // FIXME: The following is more or less accurate if the // bleft-tright box is roughly a cube. If it's considerably // longer in one direction we'll get false misses. if (end[0] < self->s.origin[0] + self->bleft[0]) return; if (end[1] < self->s.origin[1] + self->bleft[1]) return; if (end[2] < self->s.origin[2] + self->bleft[2]) return; if (end[0] > self->s.origin[0] + self->tright[0]) return; if (end[1] > self->s.origin[1] + self->tright[1]) return; if (end[2] > self->s.origin[2] + self->tright[2]) return; self->activator = other; G_UseTargets (self, other); } if (self->wait > 0) { self->think = multi_wait; self->nextthink = level.time + self->wait; } else { // we can't just remove (self) here, because this is a touch function // called while looping through area links... self->touch = NULL; self->nextthink = level.time + FRAMETIME; self->think = G_FreeEdict; } } void trigger_look_enable (edict_t *self, edict_t *other, edict_t *activator); void trigger_look_disable (edict_t *self, edict_t *other, edict_t *activator) { self->count--; if (!self->count) { self->think = G_FreeEdict; self->nextthink = level.time + 1; } else { self->solid = SOLID_NOT; self->use = trigger_look_enable; gi.linkentity (self); } } void trigger_look_enable (edict_t *self, edict_t *other, edict_t *activator) { self->solid = SOLID_TRIGGER; self->use = trigger_look_disable; gi.linkentity (self); } void SP_trigger_look (edict_t *self) { InitTriggerBbox (self); self->class_id = ENTITY_TRIGGER_LOOK; if (self->sounds == 1) self->noise_index = gi.soundindex ("misc/secret.wav"); else if (self->sounds == 2) self->noise_index = gi.soundindex ("misc/talk.wav"); else if (self->sounds == 3) self->noise_index = -1; if (!self->wait) self->wait = 0.2; if (self->spawnflags & TRIGGER_START_OFF) { self->solid = SOLID_NOT; self->use = trigger_look_enable; } else { self->solid = SOLID_TRIGGER; self->use = trigger_look_disable; } gi.setmodel (self, self->model); // self->movetype = MOVETYPE_NONE; // self->svflags = SVF_NOCLIENT; if (self->spawnflags & TRIGGER_CAMOWNER) self->svflags |= SVF_TRIGGER_CAMOWNER; /* if ( (VectorLength(self->bleft) == 0) && (VectorLength(self->tright) == 0)) { VectorSet(self->bleft,-16,-16,-16); VectorSet(self->tright,16,16,16); }*/ self->touch = trigger_look_touch; } void trigger_speaker_think (edict_t *self) { int i; edict_t *touching; edict_t *player; touching = NULL; for (i = 1; i <= maxclients->value && !touching; i++) { player = &g_edicts[i]; if (!player->inuse) continue; if (player->s.origin[0] < self->s.origin[0] + self->bleft[0]) continue; if (player->s.origin[1] < self->s.origin[1] + self->bleft[1]) continue; if (player->s.origin[2] < self->s.origin[2] + self->bleft[2]) continue; if (player->s.origin[0] > self->s.origin[0] + self->tright[0]) continue; if (player->s.origin[1] > self->s.origin[1] + self->tright[1]) continue; if (player->s.origin[2] > self->s.origin[2] + self->tright[2]) continue; touching = player; } if (touching) gi.sound (touching, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0); self->nextthink = level.time + FRAMETIME; } void trigger_speaker_enable (edict_t *self, edict_t *other, edict_t *activator); void trigger_speaker_disable (edict_t *self, edict_t *other, edict_t *activator) { self->use = trigger_speaker_enable; self->think = NULL; self->nextthink = 0; } void trigger_speaker_enable (edict_t *self, edict_t *other, edict_t *activator) { self->use = trigger_speaker_disable; self->think = trigger_speaker_think; self->think(self); } void SP_trigger_speaker (edict_t *self) { char buffer[MAX_QPATH]; if (!st.noise) { gi.dprintf("trigger_speaker with no noise set at %s\n", vtos(self->s.origin)); return; } self->class_id = ENTITY_TRIGGER_SPEAKER; if (!strstr (st.noise, ".wav")) Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise); else // strncpy (buffer, st.noise, sizeof(buffer)); Q_strncpyz (buffer, sizeof(buffer), st.noise); self->noise_index = gi.soundindex (buffer); if (self->spawnflags & 1) { self->use = trigger_speaker_disable; self->think = trigger_speaker_think; self->nextthink = level.time + FRAMETIME; } else { self->use = trigger_speaker_enable; } if ( (!VectorLength(self->bleft)) && (!VectorLength(self->tright)) ) { VectorSet(self->bleft,-16,-16,-16); VectorSet(self->tright,16, 16, 16); } } //============================================================================== // trigger_transition is a HL-like box that defines what entities will be // moved from one map to another when a target_changelevel with the same // targetname is fired. Brush models may NOT be moved. //============================================================================== void WriteEdict (FILE *f, edict_t *ent); qboolean HasSpawnFunction(edict_t *ent) { spawn_t *s; gitem_t *item; int i; if (!ent->classname) return false; // check item spawn functions for (i=0,item=itemlist ; iclassname) continue; if (!strcmp(item->classname, ent->classname)) return true; } // check normal spawn functions for (s=spawns ; s->name ; s++) { if (!strcmp(s->name, ent->classname)) return true; } return false; } void WriteTransitionEdict (FILE *f, edict_t *changelevel, edict_t *ent) { byte *temp; edict_t e; field_t *field; void *p; memcpy(&e,ent,sizeof(edict_t)); if (!Q_stricmp(e.classname,"target_laser") || !Q_stricmp(e.classname,"target_blaster") ) vectoangles(e.movedir,e.s.angles); if (!Q_stricmp(e.classname,"target_speaker")) e.spawnflags |= 8; // indicates that "message" contains noise if (changelevel->s.angles[YAW]) { vec3_t angles; vec3_t forward, right, v; vec3_t spawn_offset; VectorSubtract(e.s.origin,changelevel->s.origin,spawn_offset); angles[PITCH] = angles[ROLL] = 0.; angles[YAW] = changelevel->s.angles[YAW]; AngleVectors(angles,forward,right,NULL); VectorNegate(right,right); VectorCopy(spawn_offset,v); G_ProjectSource (vec3_origin, v, forward, right, spawn_offset); VectorCopy(spawn_offset,e.s.origin); VectorCopy(e.velocity,v); G_ProjectSource (vec3_origin, v, forward, right, e.velocity); e.s.angles[YAW] += angles[YAW]; } else { VectorSubtract(e.s.origin,changelevel->s.origin,e.s.origin); } // wipe out all edict_t and function members, since // they won't be valid in the next map and might otherwise // cause.... umm... big crash temp = (byte *)&e; for (field=fields ; field->name ; field++) { if ((field->type == F_EDICT) || (field->type == F_FUNCTION)) { p = (void *)(temp + field->ofs); *(edict_t **)p = NULL; } } // Clean out a few more things e.s.number = 0; memset (&e.moveinfo, 0,sizeof(moveinfo_t)); memset (&e.area, 0, sizeof(e.area)); e.linkcount = 0; e.nextthink = 0; e.groundentity_linkcount = 0; e.s.modelindex = 0; e.s.modelindex2 = 0; e.s.modelindex3 = 0; e.s.modelindex4 = 0; #ifdef KMQUAKE2_ENGINE_MOD e.s.modelindex5 = 0; e.s.modelindex6 = 0; #endif e.noise_index = 0; // If the ent is a live bad guy monster, remove him from the total // monster count. He'll be added back in in the new map. if ((e.svflags & SVF_MONSTER) && !(e.monsterinfo.aiflags & AI_GOOD_GUY)) { if (e.health > 0) level.total_monsters--; else e.max_health = -1; } // Enemy isn't preserved... let's try a new flag for // single-player only that tells monster to find // the player again at startup if (!coop->value && !deathmatch->value) { if (ent->enemy == &g_edicts[1] && ent->health > 0) e.monsterinfo.aiflags = AI_RESPAWN_FINDPLAYER; } if (e.classname && ( !Q_stricmp(e.classname,"misc_actor") || strstr(e.classname,"monster_") ) && (e.svflags & SVF_GIB) ) //(e.health <= e.gib_health) ) e.classname = "gibhead"; WriteEdict(f,&e); } entlist_t DoNotMove[] = { {"crane_reset"}, {"func_clock"}, {"func_timer"}, {"hint_path"}, {"info_player_coop"}, {"info_player_deathmatch"}, {"info_player_intermission"}, {"info_player_start"}, {"light"}, {"light_mine1"}, {"light_mine2"}, {"misc_strogg_ship"}, {"misc_viper"}, {"misc_viper_bomb"}, {"model_train"}, {"path_corner"}, {"path_track"}, {"point_combat"}, {"target_actor"}, {"target_changelevel"}, {"target_character"}, {"target_crosslevel_target"}, {"target_crosslevel_trigger"}, {"target_goal"}, {"target_help"}, {"target_lightramp"}, {"target_locator"}, {"target_lock"}, {"target_lock_clue"}, {"target_lock_code"}, {"target_lock_digit"}, {"target_rotation"}, {"target_secret"}, {"target_string"}, {"trigger_always"}, {"trigger_counter"}, {"trigger_elevator"}, {"trigger_key"}, {"trigger_relay"}, {"turret_driver"}, {NULL}}; void trans_ent_filename (char *filename, size_t filenameSize) { #if defined (_M_X64) || defined (_M_AMD64) || defined (__x86_64__) SavegameDirRelativePath("save_x64/trans.ent", filename, filenameSize); #else SavegameDirRelativePath("save/trans.ent", filename, filenameSize); #endif } int trigger_transition_ents (edict_t *changelevel, edict_t *self) { char t_file[MAX_OSPATH]; int i, j; int total=0; qboolean nogo; edict_t *ent; entlist_t *p; FILE *f; trans_ent_filename(t_file, sizeof(t_file)); f = fopen(t_file,"wb"); if (!f) { gi.dprintf("Error opening %s for writing\n",t_file); return 0; } // First scan entities for brush models that SHOULD change levels, e.g. func_tracktrain, // which had better have a partner train in the next map... or we'll bitch loudly for (i=game.maxclients+1; iinuse) continue; if (ent->solid != SOLID_BSP) continue; if (ent->s.origin[0] > self->maxs[0]) continue; if (ent->s.origin[1] > self->maxs[1]) continue; if (ent->s.origin[2] > self->maxs[2]) continue; if (ent->s.origin[0] < self->mins[0]) continue; if (ent->s.origin[1] < self->mins[1]) continue; if (ent->s.origin[2] < self->mins[2]) continue; if (!Q_stricmp(ent->classname,"func_tracktrain") && !(ent->spawnflags & 8) && ent->targetname) { edict_t *e; size_t classSize, targetnameSize, targetSize; e = G_Spawn(); classSize = 17; e->classname = gi.TagMalloc(classSize, TAG_LEVEL); Q_strncpyz (e->classname, classSize, "info_train_start"); targetnameSize = strlen(ent->targetname)+1; e->targetname = gi.TagMalloc(targetnameSize, TAG_LEVEL); Q_strncpyz (e->targetname, targetnameSize, ent->targetname); targetSize = strlen(ent->target)+1; e->target = gi.TagMalloc(targetSize, TAG_LEVEL); Q_strncpyz (e->target, targetSize, ent->target); e->spawnflags = ent->spawnflags; VectorCopy(ent->s.origin,e->s.origin); VectorCopy(ent->s.angles,e->s.angles); VectorCopy(ent->offset, e->offset); VectorCopy(ent->bleft, e->bleft); VectorCopy(ent->tright, e->tright); e->sounds = ent->sounds; e->viewheight = ent->viewheight; e->speed = ent->moveinfo.speed; // misuse/abuse a couple of entries to copy moveinfo stuff: e->count = ent->moveinfo.state; e->radius = ent->moveinfo.distance; e->solid = SOLID_NOT; e->svflags |= SVF_NOCLIENT; if (ent->owner) e->style = ent->owner - g_edicts; else e->style = 0; gi.linkentity(e); ent->owner = NULL; ent->spawnflags |= 24; // SF_TRACKTRAIN_OTHERMAP | SF_TRACKTRAIN_DISABLED VectorClear(ent->velocity); VectorClear(ent->avelocity); ent->moveinfo.state = ent->moveinfo.prevstate = 0; // STOP gi.linkentity(ent); } } for (i=game.maxclients+1; iid = 0; if (!ent->inuse) continue; // Pass up owned entities not owned by the player on this pass... // get 'em next pass so we'll know whether owner is in our list if (ent->owner && !ent->owner->client) continue; if (ent->movewith) continue; if (ent->s.origin[0] > self->maxs[0]) continue; if (ent->s.origin[1] > self->maxs[1]) continue; if (ent->s.origin[2] > self->maxs[2]) continue; if (ent->s.origin[0] < self->mins[0]) continue; if (ent->s.origin[1] < self->mins[1]) continue; if (ent->s.origin[2] < self->mins[2]) continue; if (ent->solid == SOLID_BSP) continue; if ((ent->solid == SOLID_TRIGGER) && !FindItemByClassname(ent->classname)) continue; // Do not under any circumstances move these entities: for (p=DoNotMove, nogo=false; p->name && !nogo; p++) if (!Q_stricmp(ent->classname,p->name)) nogo = true; if (nogo) continue; if (!HasSpawnFunction(ent)) continue; total++; ent->id = total; if (ent->owner) ent->owner_id = -(ent->owner - g_edicts); else ent->owner_id = 0; WriteTransitionEdict(f,changelevel,ent); gi.unlinkentity(ent); ent->inuse = false; } // Repeat, ONLY for ents owned by non-players for (i=game.maxclients+1; iinuse) continue; if (!ent->owner) continue; if (ent->owner->client) continue; if (ent->movewith) continue; if (ent->solid == SOLID_BSP) continue; if ((ent->solid == SOLID_TRIGGER) && !FindItemByClassname(ent->classname)) continue; // Do not under any circumstances move these entities: for (p=DoNotMove, nogo=false; p->name && !nogo; p++) if (!Q_stricmp(ent->classname,p->name)) nogo = true; if (nogo) continue; if (!HasSpawnFunction(ent)) continue; if (ent->s.origin[0] > self->maxs[0]) continue; if (ent->s.origin[1] > self->maxs[1]) continue; if (ent->s.origin[2] > self->maxs[2]) continue; if (ent->s.origin[0] < self->mins[0]) continue; if (ent->s.origin[1] < self->mins[1]) continue; if (ent->s.origin[2] < self->mins[2]) continue; ent->owner_id = 0; for (j=game.maxclients+1; jowner_id; j++) { if (ent->owner == &g_edicts[j]) ent->owner_id = g_edicts[j].id; } if (!ent->owner_id) continue; total++; ent->id = total; WriteTransitionEdict(f,changelevel,ent); gi.unlinkentity(ent); ent->inuse = false; } fflush(f); fclose(f); return total; } void SP_trigger_transition (edict_t *self) { if (!self->targetname) { gi.dprintf("trigger_transition w/o a targetname\n"); G_FreeEdict(self); } self->class_id = ENTITY_TRIGGER_TRANSITION; self->solid = SOLID_NOT; self->movetype = MOVETYPE_NONE; gi.setmodel (self, self->model); self->svflags = SVF_NOCLIENT; } void SP_trigger_transition_bbox (edict_t *self) { if (!self->targetname) { gi.dprintf("trigger_transition_bbox w/o a targetname\n"); G_FreeEdict(self); } self->class_id = ENTITY_TRIGGER_TRANSITION; self->solid = SOLID_NOT; self->movetype = MOVETYPE_NONE; if ( (!VectorLength(self->bleft)) && (!VectorLength(self->tright)) ) { VectorSet(self->bleft, -16, -16, -16); VectorSet(self->tright, 16, 16, 16); } VectorCopy (self->bleft, self->mins); VectorCopy (self->tright, self->maxs); self->svflags = SVF_NOCLIENT; } /* ============================ TRIGGER_DISGUISE ============================ */ /*QUAKED trigger_disguise (.5 .5 .5) ? Toggle StartOn Remove A 'Notarget' effect. Allows the player to sneak around without being noticed. Any player who touches this when it's on will have their disguise flag set. Toggle - trigger turns on and off when fired StartOn - trigger is on at map load Remove - trigger removes disguise "count" - number of times it can be used, 0 = unlimited */ #define TRIGGER_DISGUISE_START_ON 2 #define TRIGGER_DISGUISE_REMOVE 4 void touch_trigger_disguise (edict_t *trigger, edict_t *other, cplane_t *plane, csurface_t *surf) { if (!other->client) // this only works for players return; trigger->count--; if (trigger->count == 0) { trigger->nextthink = level.time + 0.1; trigger->think = G_FreeEdict; } if (trigger->spawnflags & TRIGGER_DISGUISE_REMOVE) other->flags &= ~FL_DISGUISED; else other->flags |= FL_DISGUISED; } void use_trigger_disguise (edict_t *trigger, edict_t *other, edict_t *activator) { if (trigger->solid == SOLID_TRIGGER) trigger->solid = SOLID_NOT; else // SOLID_NOT trigger->solid = SOLID_TRIGGER; gi.linkentity(trigger); } void SP_trigger_disguise (edict_t *trigger) { trigger->class_id = ENTITY_TRIGGER_DISGUISE; trigger->movetype = MOVETYPE_NONE; trigger->svflags |= SVF_NOCLIENT; gi.setmodel (trigger, trigger->model); if (trigger->spawnflags & TRIGGER_DISGUISE_START_ON) trigger->solid = SOLID_TRIGGER; else trigger->solid = SOLID_NOT; trigger->use = use_trigger_disguise; trigger->touch = touch_trigger_disguise; gi.linkentity(trigger); } /* ============================================================================ TRIGGER_SWITCH Identical to trigger_multiple in just about every way, except that it only turns entities ON if they're currently OFF (default operation) or OFF if they're currently ON and TRIGGER_TARGET_OFF spawnflag is set. ==============================================================================*/ void trigger_switch_usetargets (edict_t *ent, edict_t *activator); void trigger_switch_delay (edict_t *ent) { trigger_switch_usetargets (ent, ent->activator); G_FreeEdict (ent); } void trigger_switch_usetargets (edict_t *ent, edict_t *activator) { edict_t *t; // // check for a delay // if (ent->delay) { // create a temp object to fire at a later time t = G_Spawn(); t->classname = "DelayedUse"; t->nextthink = level.time + ent->delay; t->think = trigger_switch_delay; t->activator = activator; if (!activator) gi.dprintf ("Delay with no activator\n"); t->message = ent->message; t->target = ent->target; t->killtarget = ent->killtarget; t->noise_index = ent->noise_index; return; } // // print the message // if ((ent->message) && !(activator->svflags & SVF_MONSTER)) { // Lazarus - change so that noise_index < 0 means no sound safe_centerprintf (activator, "%s", ent->message); if (ent->noise_index > 0) gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0); else if (ent->noise_index == 0) gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); } // // kill killtargets // if (ent->killtarget) { t = NULL; while ((t = G_Find (t, FOFS(targetname), ent->killtarget))) { // Lazarus: remove LIVE killtargeted monsters from total_monsters if ((t->svflags & SVF_MONSTER) && (t->deadflag == DEAD_NO)) { if (!t->dmgteam || strcmp(t->dmgteam,"player")) if (!(t->monsterinfo.aiflags & AI_GOOD_GUY)) level.total_monsters--; } // and decrement secret count if target_secret is removed else if (t->class_id == ENTITY_TARGET_SECRET) level.total_secrets--; // same deal with target_goal, but also turn off CD music if applicable else if (t->class_id == ENTITY_TARGET_GOAL) { level.total_goals--; if (level.found_goals >= level.total_goals) gi.configstring (CS_CDTRACK, "0"); } G_FreeEdict (t); if (!ent->inuse) { gi.dprintf("entity was removed while using killtargets\n"); return; } } } // // fire targets // if (ent->target) { int on; t = NULL; while ((t = G_Find (t, FOFS(targetname), ent->target))) { if (t == ent) { gi.dprintf ("WARNING: Entity used itself.\n"); } else if (t->use) { on = 0; switch(t->class_id) { case ENTITY_FUNC_CONVEYOR: case ENTITY_FUNC_FORCE_WALL: case ENTITY_FUNC_WALL: if (t->solid == SOLID_BSP) on=1; break; case ENTITY_FUNC_PENDULUM: case ENTITY_FUNC_TRAIN: case ENTITY_MISC_STROGG_SHIP: case ENTITY_MISC_VIPER: case ENTITY_MODEL_TRAIN: case ENTITY_TARGET_ATTRACTOR: case ENTITY_TARGET_FOG: case ENTITY_TARGET_FOUNTAIN: case ENTITY_TARGET_LASER: case ENTITY_TARGET_PRECIPITATION: if (t->spawnflags & 1) on=1; break; case ENTITY_FUNC_REFLECT: if (!(t->spawnflags & 1)) on=1; break; case ENTITY_FUNC_ROTATING: on = VectorCompare (t->avelocity, vec3_origin); break; case ENTITY_FUNC_TIMER: if (t->nextthink) on=1; break; case ENTITY_FUNC_TRACKTRAIN: if (!(t->spawnflags & 128)) on=1; break; case ENTITY_MODEL_TURRET: case ENTITY_TURRET_BREACH: if (t->spawnflags & 16) on=1; break; case ENTITY_TARGET_EFFECT: if (t->spawnflags & 3) { if (t->spawnflags & 1) on=1; } else on = -1; break; case ENTITY_TARGET_SPEAKER: if (t->spawnflags & 3) { if (t->s.sound) on=1; } else on=-1; break; default: on=-1; } if (ent->spawnflags & TRIGGER_TARGET_OFF) { // Only use target if it is currently ON if (on==1) t->use (t, ent, activator); } else if (on==0) { // Only use target if it is currently OFF t->use (t, ent, activator); } } if (!ent->inuse) { gi.dprintf("entity was removed while using targets\n"); return; } } } } void trigger_switch (edict_t *ent) { if (ent->nextthink) return; // already been triggered trigger_switch_usetargets (ent, ent->activator); if (ent->wait > 0) { ent->think = multi_wait; ent->nextthink = level.time + ent->wait; } else { // we can't just remove (self) here, because this is a touch function // called while looping through area links... ent->touch = NULL; ent->nextthink = level.time + FRAMETIME; ent->think = G_FreeEdict; } } void touch_trigger_switch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { if (other->client || (other->flags & FL_ROBOT)) { if (self->spawnflags & TRIGGER_NOT_PLAYER) return; } else if (other->svflags & SVF_MONSTER) { if (!(self->spawnflags & TRIGGER_MONSTER)) return; } else return; if ( (self->spawnflags & TRIGGER_CAMOWNER) && (!other->client || !other->client->spycam)) return; if (!VectorCompare(self->movedir, vec3_origin)) { vec3_t forward; AngleVectors(other->s.angles, forward, NULL, NULL); if (_DotProduct(forward, self->movedir) < 0) return; } self->activator = other; trigger_switch (self); } void use_trigger_switch (edict_t *ent, edict_t *other, edict_t *activator) { ent->activator = activator; trigger_switch (ent); } void SP_trigger_switch (edict_t *ent) { ent->class_id = ENTITY_TRIGGER_SWITCH; if (ent->sounds == 1) ent->noise_index = gi.soundindex ("misc/secret.wav"); else if (ent->sounds == 2) ent->noise_index = gi.soundindex ("misc/talk.wav"); else if (ent->sounds == 3) ent->noise_index = -1; if (!ent->wait) ent->wait = 0.2; ent->touch = touch_trigger_switch; ent->movetype = MOVETYPE_NONE; ent->svflags |= SVF_NOCLIENT; if (ent->spawnflags & TRIGGER_CAMOWNER) ent->svflags |= SVF_TRIGGER_CAMOWNER; if (ent->spawnflags & TRIGGER_START_OFF) { ent->solid = SOLID_NOT; ent->use = trigger_enable; } else { ent->solid = SOLID_TRIGGER; ent->use = use_trigger_switch; } if (!VectorCompare(ent->s.angles, vec3_origin)) G_SetMovedir (ent->s.angles, ent->movedir); gi.setmodel (ent, ent->model); gi.linkentity (ent); } // end DWH