//----------------------------------------------------------------------------- // // $Id$ // //----------------------------------------------------------------------------- // // $Log$ // Revision 1.33 2005/02/15 16:33:39 makro // Tons of updates (entity tree attachment system, UI vectors) // // Revision 1.32 2004/03/13 01:17:32 makro // no message // // Revision 1.31 2004/03/12 15:56:46 makro // no message // // Revision 1.30 2003/09/18 00:05:06 makro // Lens flares. Opendoor trigger_multiple fixes // // Revision 1.29 2003/09/16 23:25:32 makro // trigger_multiple - new spawnflag, 3 new keys // // Revision 1.28 2003/09/08 19:19:20 makro // New code for respawning entities in TP // // Revision 1.27 2003/07/30 16:05:46 makro // no message // // Revision 1.26 2003/04/26 22:33:07 jbravo // Wratted all calls to G_FreeEnt() to avoid crashing and provide debugging // // Revision 1.25 2003/03/22 20:29:26 jbravo // wrapping linkent and unlinkent calls // // Revision 1.24 2002/07/22 06:32:27 niceass // cleaned up the powerup code // // Revision 1.23 2002/06/16 20:06:14 jbravo // Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap" // // Revision 1.22 2002/06/06 18:08:01 makro // Removed pathtarget code for trigger_pushes for now // // Revision 1.21 2002/06/04 21:35:40 makro // Updated trigger_push code // // Revision 1.20 2002/06/04 20:53:19 makro // Trigger_pushes can now be teamed up // // Revision 1.19 2002/05/30 21:18:28 makro // Bots should reload/bandage when roaming around // Added "pathtarget" key to all the entities // // Revision 1.18 2002/05/29 13:49:26 makro // Elevators/doors // // Revision 1.17 2002/05/23 18:37:50 makro // Bots should crouch more often when they attack with a SSG // Made this depend on skill. Also, elevator stuff // // Revision 1.16 2002/05/23 15:55:25 makro // Elevators // // Revision 1.15 2002/05/22 04:19:45 blaze // Sound entity changes as per Sze // // Revision 1.14 2002/05/18 14:52:16 makro // Bot stuff. Other stuff. Just... stuff :p // // Revision 1.13 2002/05/16 06:57:54 makro // Doors with health (again !), bot-only trigger_pushes // // Revision 1.12 2002/05/11 22:01:41 makro // Trigger_hurt // // Revision 1.11 2002/05/11 17:59:49 makro // Trigger_hurt update // // Revision 1.10 2002/05/11 00:38:47 blaze // trigger_push and target_push default to no noise when the noise flag is not set. // // Revision 1.9 2002/05/05 15:18:03 makro // Fixed some crash bugs. Bot stuff. Triggerable func_statics. // Made flags only spawn in CTF mode // // Revision 1.8 2002/03/18 06:20:39 blaze // noise tag will play sounds for trigger_push and target_push // // Revision 1.7 2002/01/11 19:48:30 jbravo // Formatted the source in non DOS format. // // Revision 1.6 2001/12/31 16:28:42 jbravo // I made a Booboo with the Log tag. // // //----------------------------------------------------------------------------- // Copyright (C) 1999-2000 Id Software, Inc. // #include "g_local.h" void InitTrigger(gentity_t * self) { if (!VectorCompare(self->s.angles, vec3_origin)) G_SetMovedir(self->s.angles, self->movedir); trap_SetBrushModel(self, self->model); self->r.contents = CONTENTS_TRIGGER; // replaces the -1 from trap_SetBrushModel self->r.svFlags = SVF_NOCLIENT; } /*QUAKED trigger_multiple (.5 .5 .5) ? DOOR "wait" : Seconds between triggerings, 0.5 default, -1 = one time only. "random" wait variance, default is 0 Variable sized repeatable trigger. Must be targeted at one or more entities. so, the basic time between firing is a random time between (wait - random) and (wait + random) */ #define SF_TRIGGER_MULTIPLE_RED 1 #define SF_TRIGGER_MULTIPLE_BLUE 2 #define SF_TRIGGER_MULTIPLE_DOOR 4 // the wait time has passed, so set back up for another activation void multi_wait(gentity_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(gentity_t * ent, gentity_t * activator) { ent->activator = activator; if (ent->nextthink) { return; // can't retrigger until the wait is over } //Makro - inactive trigger ? if (ent->inactive) { //note - since the trigger is not sent to the clients, we cannot send the event //either, so we'll just play the sound locally if (ent->soundInactive) G_AddEvent(ent->activator, EV_GENERAL_SOUND, ent->soundInactive); if (ent->targetInactive) G_UseEntities(ent->activator, ent->targetInactive, activator); ent->think = multi_wait; ent->nextthink = level.time + (ent->wait + ent->random * crandom()) * 1000; return; } //Makro - added check; Q3 crashed in archives when playing //with .dll's and shooting one of the barrels if (activator != NULL) { if (activator->client) { if ((ent->spawnflags & SF_TRIGGER_MULTIPLE_RED) && activator->client->sess.sessionTeam != TEAM_RED) { return; } if ((ent->spawnflags & SF_TRIGGER_MULTIPLE_BLUE) && activator->client->sess.sessionTeam != TEAM_BLUE) { return; } } } if (ent->sound1to2) G_AddEvent(ent->activator, EV_GENERAL_SOUND, ent->sound1to2); G_UseTargets(ent, ent->activator); if (ent->wait > 0) { ent->think = multi_wait; ent->nextthink = level.time + (ent->wait + ent->random * crandom()) * 1000; } else { // we can't just remove (self) here, because this is a touch function // called while looping through area links... //Makro - we no longer remove the entity, we just make it inactive //otherwise, we would only be able to use it during the first //round in TP games /* ent->touch = 0; ent->nextthink = level.time + FRAMETIME; ent->think = G_FreeEntity; */ //On second thought, now that I've added those soundInactive/targetInactive keys //I think just setting touch to 0 will do //ent->inactive = 1; ent->touch = 0; } } void Use_Multi(gentity_t * ent, gentity_t * other, gentity_t * activator) { multi_trigger(ent, activator); } void Touch_Multi(gentity_t * self, gentity_t * other, trace_t * trace) { if (!other || !other->client) { return; } if (self->spawnflags & SF_TRIGGER_MULTIPLE_DOOR) { if (!other->client->openDoor || other->client->openDoorTime <= self->timestamp) return; self->timestamp = other->client->openDoorTime; } multi_trigger(self, other); } void Reset_Multi(gentity_t *ent) { ent->inactive = ent->unbreakable; ent->think = 0; ent->nextthink = 0; ent->touch = Touch_Multi; } void SP_trigger_multiple(gentity_t * ent) { char *s; G_SpawnFloat("wait", "0.5", &ent->wait); G_SpawnFloat("random", "0", &ent->random); if (ent->random >= ent->wait && ent->wait >= 0) { ent->random = ent->wait - FRAMETIME; G_Printf("trigger_multiple has random >= wait\n"); } //Makro - added if (G_SpawnString("soundInactive", "", &s)) ent->soundInactive = G_SoundIndex(s); if (G_SpawnString("noise", "", &s)) ent->sound1to2 = G_SoundIndex(s); else if (G_SpawnString("sound", "", &s)) ent->sound1to2 = G_SoundIndex(s); ent->touch = Touch_Multi; ent->use = Use_Multi; ent->unbreakable = ent->inactive; ent->reset = Reset_Multi; InitTrigger(ent); trap_LinkEntity(ent); } /* ============================================================================== trigger_always ============================================================================== */ void trigger_always_think(gentity_t * ent) { G_UseTargets(ent, ent); //Makro - we want to be able to re-use this entity (round-based gametypes) //so we're not going to free it //G_FreeEntity(ent); trap_UnlinkEntity(ent); } //Makro - reset function void trigger_always_reset(gentity_t *ent) { trap_LinkEntity(ent); ent->nextthink = level.time + 300; ent->think = trigger_always_think; } /*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(gentity_t * ent) { // we must have some delay to make sure our use targets are present ent->nextthink = level.time + 300; ent->think = trigger_always_think; //Makro - reset function ent->reset = trigger_always_reset; } /* ============================================================================== trigger_push ============================================================================== */ void trigger_push_touch(gentity_t * self, gentity_t * other, trace_t * trace) { if (!other || !self) return; if (!other->client) { return; } //Makro - too soon to activate ? /*if (level.time < self->s.legsAnim) { return; } */ //Makro - bot only triggers if (self->spawnflags & 1) { if (!(other->r.svFlags & SVF_BOT)) { return; } } BG_TouchJumpPad(&other->client->ps, &self->s); //Makro - "team up" trigger_pushes //something is broken here :/ /* if (self->pathtarget) { if (self->pathtarget[0]) { gentity_t *loop = NULL; for (loop = G_Find2(NULL, FOFS(classname), self->classname, FOFS(pathtarget), self->pathtarget); loop; G_Find2(loop, FOFS(classname), self->classname, FOFS(pathtarget), self->pathtarget)) { //Makro - delay 5 seconds before triggering another trigger_push from the same "team" if (self->distance) { loop->s.legsAnim = level.time + self->distance * 1000; } else { loop->s.legsAnim = level.time + 5 * 1000; } } } } */ } /* ================= AimAtTarget Calculate origin2 so the target apogee will be hit ================= */ void AimAtTarget(gentity_t * self) { gentity_t *ent; vec3_t origin; float height, gravity, time, forward; float dist; VectorAdd(self->r.absmin, self->r.absmax, origin); VectorScale(origin, 0.5, origin); ent = G_PickTarget(self->target); if (!ent) { G_FreeEntity(self); return; } height = ent->s.origin[2] - origin[2]; gravity = g_gravity.value; time = sqrt(height / (.5 * gravity)); if (!time) { G_FreeEntity(self); return; } // set s.origin2 to the push velocity VectorSubtract(ent->s.origin, origin, self->s.origin2); self->s.origin2[2] = 0; dist = VectorNormalize(self->s.origin2); forward = dist / time; VectorScale(self->s.origin2, forward, self->s.origin2); self->s.origin2[2] = time * gravity; } /*QUAKED trigger_push (.5 .5 .5) ? BOT_ONLY Must point at a target_position, which will be the apex of the leap. This will be client side predicted, unlike target_push */ void SP_trigger_push(gentity_t * self) { char *sound; InitTrigger(self); // unlike other triggers, we need to send this one to the client // NiceAss: Added for custom push sounds. Default is none. Q3 is "sounds/world/bouncepad.wav" //Changed from noise to sound as per Sze if (G_SpawnString("sound", "sound/misc/silence.wav", &sound)) { self->s.generic1 = G_SoundIndex(sound); } //Makro - for bot-only triggers if (!(self->spawnflags & 1)) { self->r.svFlags &= ~SVF_NOCLIENT; } self->s.powerups = (self->spawnflags & 1); self->s.eType = ET_PUSH_TRIGGER; self->touch = trigger_push_touch; self->think = AimAtTarget; self->nextthink = level.time + FRAMETIME; trap_LinkEntity(self); } void Use_target_push(gentity_t * self, gentity_t * other, gentity_t * activator) { if (!activator->client) { return; } if (activator->client->ps.pm_type != PM_NORMAL) { return; } VectorCopy(self->s.origin2, activator->client->ps.velocity); // play fly sound every 1.5 seconds if (activator->fly_sound_debounce_time < level.time) { activator->fly_sound_debounce_time = level.time + 1500; //Elder: added to check noise_index if (self->noise_index) G_Sound(activator, CHAN_AUTO, self->noise_index); } } /*QUAKED target_push (.5 .5 .5) (-8 -8 -8) (8 8 8) bouncepad Pushes the activator in the direction.of angle, or towards a target apex. "speed" defaults to 1000 if "bouncepad", play bounce noise instead of windfly */ void SP_target_push(gentity_t * self) { char *sound; if (!self->speed) { self->speed = 1000; } G_SetMovedir(self->s.angles, self->s.origin2); VectorScale(self->s.origin2, self->speed, self->s.origin2); //Changed from noise to sound as per Sze if (G_SpawnString("sound", "sound/misc/silence.wav", &sound)) { //Makro - debug message, no longer needed //G_Printf("^2Sound was %s\n",sound); self->noise_index = G_SoundIndex(sound); } if (self->spawnflags & 1) { //Elder: edited for now TODO: let mappers use a key pair //self->noise_index = G_SoundIndex("sound/world/jumppad.wav"); } else { //Elder: edited for now TODO: let mappers use a key pair //self->noise_index = G_SoundIndex("sound/misc/windfly.wav"); } if (self->target) { VectorCopy(self->s.origin, self->r.absmin); VectorCopy(self->s.origin, self->r.absmax); self->think = AimAtTarget; self->nextthink = level.time + FRAMETIME; } self->use = Use_target_push; } /* ============================================================================== trigger_teleport ============================================================================== */ void trigger_teleporter_touch(gentity_t * self, gentity_t * other, trace_t * trace) { gentity_t *dest; if (!other->client) { return; } if (other->client->ps.pm_type == PM_DEAD) { return; } // Spectators only? if ((self->spawnflags & 1) && other->client->sess.sessionTeam != TEAM_SPECTATOR) { return; } dest = G_PickTarget(self->target); if (!dest) { G_Printf("Couldn't find teleporter destination\n"); return; } TeleportPlayer(other, dest->s.origin, dest->s.angles); } /*QUAKED trigger_teleport (.5 .5 .5) ? SPECTATOR Allows client side prediction of teleportation events. Must point at a target_position, which will be the teleport destination. If spectator is set, only spectators can use this teleport Spectator teleporters are not normally placed in the editor, but are created automatically near doors to allow spectators to move through them */ void SP_trigger_teleport(gentity_t * self) { InitTrigger(self); // unlike other triggers, we need to send this one to the client // unless is a spectator trigger if (self->spawnflags & 1) { self->r.svFlags |= SVF_NOCLIENT; } else { self->r.svFlags &= ~SVF_NOCLIENT; } // make sure the client precaches this sound G_SoundIndex("sound/world/jumppad.wav"); self->s.eType = ET_TELEPORT_TRIGGER; self->touch = trigger_teleporter_touch; trap_LinkEntity(self); } /* ============================================================================== trigger_hurt ============================================================================== */ /*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF - SILENT NO_PROTECTION SLOW Any entity that touches this will be hurt. It does dmg points of damage each server frame Targeting the trigger will toggle its on / off state. 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) */ //Makro - reset function void hurt_reset(gentity_t *ent) { ent->timestamp = level.time; if (!(ent->spawnflags & 1)) { trap_LinkEntity(ent); } else { //Makro - added trap_UnlinkEntity(ent); } } void hurt_use(gentity_t * self, gentity_t * other, gentity_t * activator) { if (self->r.linked) { trap_UnlinkEntity(self); } else { trap_LinkEntity(self); } } void hurt_touch(gentity_t * self, gentity_t * other, trace_t * trace) { int dflags; if (!other->takedamage) { return; } if (self->timestamp > level.time) { return; } if (self->spawnflags & 16) { self->timestamp = level.time + 1000; } else { self->timestamp = level.time + FRAMETIME; } // play sound if (!(self->spawnflags & 4)) { G_Sound(other, CHAN_AUTO, self->noise_index); } if (self->spawnflags & 8) dflags = DAMAGE_NO_PROTECTION; else dflags = 0; if (self->methodOfDeath) { G_Damage(other, self, self, NULL, NULL, self->damage, dflags, MOD_CUSTOM+self->methodOfDeath-1); } else { G_Damage(other, self, self, NULL, NULL, self->damage, dflags, MOD_TRIGGER_HURT); } } void SP_trigger_hurt(gentity_t * self) { InitTrigger(self); self->noise_index = G_SoundIndex("sound/world/electro.wav"); self->touch = hurt_touch; if (!self->damage) { self->damage = 5; } //self->r.contents = CONTENTS_TRIGGER; //Makro - custom death message G_InitCustomDeathMessage(self, &self->methodOfDeath); //Makro - removed this check //if ( self->spawnflags & 2 ) { self->use = hurt_use; //} //Makro - reset function self->reset = hurt_reset; // link in to the world if starting active if (!(self->spawnflags & 1)) { trap_LinkEntity(self); } else { //Makro - added trap_UnlinkEntity(self); } } /* ============================================================================== timer ============================================================================== */ /*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON This should be renamed trigger_timer... Repeatedly fires its targets. Can be turned on or off by using. "wait" base time between triggering all targets, default is 1 "random" wait variance, default is 0 so, the basic time between firing is a random time between (wait - random) and (wait + random) */ void func_timer_think(gentity_t * self) { G_UseTargets(self, self->activator); // set time before next firing self->nextthink = level.time + 1000 * (self->wait + crandom() * self->random); } void func_timer_use(gentity_t * self, gentity_t * other, gentity_t * activator) { self->activator = activator; // if on, turn it off if (self->nextthink) { self->nextthink = 0; return; } // turn it on func_timer_think(self); } void SP_func_timer(gentity_t * self) { G_SpawnFloat("random", "1", &self->random); G_SpawnFloat("wait", "1", &self->wait); self->use = func_timer_use; self->think = func_timer_think; if (self->random >= self->wait) { self->random = self->wait - FRAMETIME; G_Printf("func_timer at %s has random >= wait\n", vtos(self->s.origin)); } if (self->spawnflags & 1) { self->nextthink = level.time + FRAMETIME; self->activator = self; } self->r.svFlags = SVF_NOCLIENT; }