/* * 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. * * ======================================================================= * * Level functions. Platforms, buttons, dooors and so on. * * ======================================================================= */ #include "header/local.h" /* * ========================================================= * * PLATS * * movement options: * * linear * smooth start, hard stop * smooth start, smooth stop * * start * end * acceleration * speed * deceleration * begin sound * end sound * target fired when reaching end * wait at end * * object characteristics that use move segments * --------------------------------------------- * movetype_push, or movetype_stop * action when touched * action when blocked * action when used * disabled? * auto trigger spawning * * * ========================================================= */ #define PLAT_LOW_TRIGGER 1 #define STATE_TOP 0 #define STATE_BOTTOM 1 #define STATE_UP 2 #define STATE_DOWN 3 #define DOOR_START_OPEN 1 #define DOOR_REVERSE 2 #define DOOR_CRUSHER 4 #define DOOR_NOMONSTER 8 #define DOOR_TOGGLE 32 #define DOOR_X_AXIS 64 #define DOOR_Y_AXIS 128 /* Support routines for movement (changes in origin using velocity) */ void Think_AccelMove(edict_t *ent); void plat_go_down(edict_t *ent); void Move_Done(edict_t *ent) { if (!ent) { return; } VectorClear(ent->velocity); ent->moveinfo.endfunc(ent); } void Move_Final(edict_t *ent) { if (!ent) { return; } if (ent->moveinfo.remaining_distance == 0) { Move_Done(ent); return; } VectorScale(ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity); ent->think = Move_Done; ent->nextthink = level.time + FRAMETIME; } void Move_Begin(edict_t *ent) { float frames; if (!ent) { return; } if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance) { Move_Final(ent); return; } VectorScale(ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity); frames = floor( (ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME); ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME; ent->nextthink = level.time + (frames * FRAMETIME); ent->think = Move_Final; } void Move_Calc(edict_t *ent, vec3_t dest, void (*func)(edict_t *)) { if (!ent || !func) { return; } VectorClear(ent->velocity); VectorSubtract(dest, ent->s.origin, ent->moveinfo.dir); ent->moveinfo.remaining_distance = VectorNormalize(ent->moveinfo.dir); ent->moveinfo.endfunc = func; if ((ent->moveinfo.speed == ent->moveinfo.accel) && (ent->moveinfo.speed == ent->moveinfo.decel)) { if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) { Move_Begin(ent); } else { ent->nextthink = level.time + FRAMETIME; ent->think = Move_Begin; } } else { /* accelerative */ ent->moveinfo.current_speed = 0; ent->think = Think_AccelMove; ent->nextthink = level.time + FRAMETIME; } } /* Support routines for angular movement (changes in angle using avelocity) */ void AngleMove_Done(edict_t *ent) { if (!ent) { return; } VectorClear(ent->avelocity); ent->moveinfo.endfunc(ent); } void AngleMove_Final(edict_t *ent) { vec3_t move; if (!ent) { return; } if (ent->moveinfo.state == STATE_UP) { VectorSubtract(ent->moveinfo.end_angles, ent->s.angles, move); } else { VectorSubtract(ent->moveinfo.start_angles, ent->s.angles, move); } if (VectorCompare(move, vec3_origin)) { AngleMove_Done(ent); return; } VectorScale(move, 1.0 / FRAMETIME, ent->avelocity); ent->think = AngleMove_Done; ent->nextthink = level.time + FRAMETIME; } void AngleMove_Begin(edict_t *ent) { vec3_t destdelta; float len; float traveltime; float frames; if (!ent) { return; } /* set destdelta to the vector needed to move */ if (ent->moveinfo.state == STATE_UP) { VectorSubtract(ent->moveinfo.end_angles, ent->s.angles, destdelta); } else { VectorSubtract(ent->moveinfo.start_angles, ent->s.angles, destdelta); } /* calculate length of vector */ len = VectorLength(destdelta); /* divide by speed to get time to reach dest */ traveltime = len / ent->moveinfo.speed; if (traveltime < FRAMETIME) { AngleMove_Final(ent); return; } frames = floor(traveltime / FRAMETIME); /* scale the destdelta vector by the time spent traveling to get velocity */ VectorScale(destdelta, 1.0 / traveltime, ent->avelocity); /* set nextthink to trigger a think when dest is reached */ ent->nextthink = level.time + frames * FRAMETIME; ent->think = AngleMove_Final; } void AngleMove_Calc(edict_t *ent, void (*func)(edict_t *)) { if (!ent || !func) { return; } VectorClear(ent->avelocity); ent->moveinfo.endfunc = func; if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) { AngleMove_Begin(ent); } else { ent->nextthink = level.time + FRAMETIME; ent->think = AngleMove_Begin; } } #define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2) void plat_CalcAcceleratedMove(moveinfo_t *moveinfo) { float accel_dist; float decel_dist; if (!moveinfo) { return; } moveinfo->move_speed = moveinfo->speed; if (moveinfo->remaining_distance < moveinfo->accel) { moveinfo->current_speed = moveinfo->remaining_distance; return; } accel_dist = AccelerationDistance(moveinfo->speed, moveinfo->accel); decel_dist = AccelerationDistance(moveinfo->speed, moveinfo->decel); if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0) { float f; f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel); moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f); decel_dist = AccelerationDistance(moveinfo->move_speed, moveinfo->decel); } moveinfo->decel_distance = decel_dist; } void plat_Accelerate(moveinfo_t *moveinfo) { if (!moveinfo) { return; } /* are we decelerating? */ if (moveinfo->remaining_distance <= moveinfo->decel_distance) { if (moveinfo->remaining_distance < moveinfo->decel_distance) { if (moveinfo->next_speed) { moveinfo->current_speed = moveinfo->next_speed; moveinfo->next_speed = 0; return; } if (moveinfo->current_speed > moveinfo->decel) { moveinfo->current_speed -= moveinfo->decel; } } return; } /* are we at full speed and need to start decelerating during this move? */ if (moveinfo->current_speed == moveinfo->move_speed) { if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance) { float p1_distance; float p2_distance; float distance; p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed)); distance = p1_distance + p2_distance; moveinfo->current_speed = moveinfo->move_speed; moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); return; } } /* are we accelerating? */ if (moveinfo->current_speed < moveinfo->speed) { float old_speed; float p1_distance; float p1_speed; float p2_distance; float distance; old_speed = moveinfo->current_speed; /* figure simple acceleration up to move_speed */ moveinfo->current_speed += moveinfo->accel; if (moveinfo->current_speed > moveinfo->speed) { moveinfo->current_speed = moveinfo->speed; } /* are we accelerating throughout this entire move? */ if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance) { return; } /* during this move we will accelrate from current_speed to move_speed and cross over the decel_distance; figure the average speed for the entire move */ p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; p1_speed = (old_speed + moveinfo->move_speed) / 2.0; p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed)); distance = p1_distance + p2_distance; moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance)); moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); return; } /* we are at constant velocity (move_speed) */ return; } /* * The team has completed a frame of movement, * so change the speed for the next frame */ void Think_AccelMove(edict_t *ent) { if (!ent) { return; } ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed; if (ent->moveinfo.current_speed == 0) /* starting or blocked */ { plat_CalcAcceleratedMove(&ent->moveinfo); } plat_Accelerate(&ent->moveinfo); /* will the entire move complete on next frame? */ if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed) { Move_Final(ent); return; } VectorScale(ent->moveinfo.dir, ent->moveinfo.current_speed * 10, ent->velocity); ent->nextthink = level.time + FRAMETIME; ent->think = Think_AccelMove; } void plat_hit_top(edict_t *ent) { if (!ent) { return; } if (!(ent->flags & FL_TEAMSLAVE)) { if (ent->moveinfo.sound_end) { gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); } ent->s.sound = 0; } ent->moveinfo.state = STATE_TOP; ent->think = plat_go_down; ent->nextthink = level.time + 3; } void plat_hit_bottom(edict_t *ent) { if (!ent) { return; } if (!(ent->flags & FL_TEAMSLAVE)) { if (ent->moveinfo.sound_end) { gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); } ent->s.sound = 0; } ent->moveinfo.state = STATE_BOTTOM; } void plat_go_down(edict_t *ent) { if (!ent) { return; } if (!(ent->flags & FL_TEAMSLAVE)) { if (ent->moveinfo.sound_start) { gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); } ent->s.sound = ent->moveinfo.sound_middle; } ent->moveinfo.state = STATE_DOWN; Move_Calc(ent, ent->moveinfo.end_origin, plat_hit_bottom); } void plat_go_up(edict_t *ent) { if (!ent) { return; } if (!(ent->flags & FL_TEAMSLAVE)) { if (ent->moveinfo.sound_start) { gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); } ent->s.sound = ent->moveinfo.sound_middle; } ent->moveinfo.state = STATE_UP; Move_Calc(ent, ent->moveinfo.start_origin, plat_hit_top); } void plat_blocked(edict_t *self, edict_t *other) { if (!self || !other) { return; } if (!(other->svflags & SVF_MONSTER) && (!other->client)) { /* give it a chance to go away on it's own terms (like gibs) */ T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); /* if it's still there, nuke it */ if (other) { /* Hack for entity without it's origin near the model */ VectorMA (other->absmin, 0.5, other->size, other->s.origin); BecomeExplosion1(other); } return; } T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); if (self->moveinfo.state == STATE_UP) { plat_go_down(self); } else if (self->moveinfo.state == STATE_DOWN) { plat_go_up(self); } } void Use_Plat(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */) { if (!ent) { return; } if (ent->think) { return; /* already down */ } plat_go_down(ent); } void Touch_Plat_Center(edict_t *ent, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) { if (!ent || !other) { return; } if (!other->client) { return; } if (other->health <= 0) { return; } ent = ent->enemy; /* now point at the plat, not the trigger */ if (ent->moveinfo.state == STATE_BOTTOM) { plat_go_up(ent); } else if (ent->moveinfo.state == STATE_TOP) { /* the player is still on the plat, so delay going down */ ent->nextthink = level.time + 1; } } void plat_spawn_inside_trigger(edict_t *ent) { if (!ent) { return; } edict_t *trigger; vec3_t tmin, tmax; /* middle trigger */ trigger = G_Spawn(); trigger->touch = Touch_Plat_Center; trigger->movetype = MOVETYPE_NONE; trigger->solid = SOLID_TRIGGER; trigger->enemy = ent; tmin[0] = ent->mins[0] + 25; tmin[1] = ent->mins[1] + 25; tmin[2] = ent->mins[2]; tmax[0] = ent->maxs[0] - 25; tmax[1] = ent->maxs[1] - 25; tmax[2] = ent->maxs[2] + 8; tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip); if (ent->spawnflags & PLAT_LOW_TRIGGER) { tmax[2] = tmin[2] + 8; } if (tmax[0] - tmin[0] <= 0) { tmin[0] = (ent->mins[0] + ent->maxs[0]) * 0.5; tmax[0] = tmin[0] + 1; } if (tmax[1] - tmin[1] <= 0) { tmin[1] = (ent->mins[1] + ent->maxs[1]) * 0.5; tmax[1] = tmin[1] + 1; } VectorCopy(tmin, trigger->mins); VectorCopy(tmax, trigger->maxs); gi.linkentity(trigger); } /* * QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER * * speed -> default 150 * * Plats are always drawn in the extended position, * so they will light correctly. * * If the plat is the target of another trigger or button, * it will start out disabled in the extended position until * it is trigger, when it will lower and become a normal plat. * * "speed" overrides default 200. * "accel" overrides default 500 * "lip" overrides default 8 pixel lip * * If the "height" key is set, that will determine the amount * the plat moves, instead of being implicitly determoveinfoned * by the model's height. * * Set "sounds" to one of the following: * 1) base fast * 2) chain slow */ void SP_func_plat(edict_t *ent) { if (!ent) { return; } VectorClear(ent->s.angles); ent->solid = SOLID_BSP; ent->movetype = MOVETYPE_PUSH; gi.setmodel(ent, ent->model); ent->blocked = plat_blocked; if (!ent->speed) { ent->speed = 20; } else { ent->speed *= 0.1; } if (!ent->accel) { ent->accel = 5; } else { ent->accel *= 0.1; } if (!ent->decel) { ent->decel = 5; } else { ent->decel *= 0.1; } if (!ent->dmg) { ent->dmg = 2; } if (!st.lip) { st.lip = 8; } /* pos1 is the top position, pos2 is the bottom */ VectorCopy(ent->s.origin, ent->pos1); VectorCopy(ent->s.origin, ent->pos2); if (st.height) { ent->pos2[2] -= st.height; } else { ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip; } ent->use = Use_Plat; plat_spawn_inside_trigger(ent); /* the "start moving" trigger */ if (ent->targetname) { ent->moveinfo.state = STATE_UP; } else { VectorCopy(ent->pos2, ent->s.origin); gi.linkentity(ent); ent->moveinfo.state = STATE_BOTTOM; } ent->moveinfo.speed = ent->speed; ent->moveinfo.accel = ent->accel; ent->moveinfo.decel = ent->decel; ent->moveinfo.wait = ent->wait; VectorCopy(ent->pos1, ent->moveinfo.start_origin); VectorCopy(ent->s.angles, ent->moveinfo.start_angles); VectorCopy(ent->pos2, ent->moveinfo.end_origin); VectorCopy(ent->s.angles, ent->moveinfo.end_angles); ent->moveinfo.sound_start = gi.soundindex("plats/pt1_strt.wav"); ent->moveinfo.sound_middle = gi.soundindex("plats/pt1_mid.wav"); ent->moveinfo.sound_end = gi.soundindex("plats/pt1_end.wav"); } /* ==================================================================== */ /* * QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS * TOUCH_PAIN STOP ANIMATED ANIMATED_FAST * * You need to have an origin brush as part of this entity. * The center of that brush will be the point around which it * is rotated. It will rotate around the Z axis by default. * You can check either the X_AXIS or Y_AXIS box to change that. * * "speed" determines how fast it moves; default value is 100. * "dmg" damage to inflict when blocked (2 default) * * REVERSE will cause the it to rotate in the opposite direction. * STOP mean it will stop moving instead of pushing entities */ void rotating_blocked(edict_t *self, edict_t *other) { if (!self || !other) { return; } T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); } void rotating_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) { if (!self || !other) { return; } if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2]) { T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); } } void rotating_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) { if (!self) { return; } if (!VectorCompare(self->avelocity, vec3_origin)) { self->s.sound = 0; VectorClear(self->avelocity); self->touch = NULL; } else { self->s.sound = self->moveinfo.sound_middle; VectorScale(self->movedir, self->speed, self->avelocity); if (self->spawnflags & 16) { self->touch = rotating_touch; } } } void SP_func_rotating(edict_t *ent) { if (!ent) { return; } ent->solid = SOLID_BSP; if (ent->spawnflags & 32) { ent->movetype = MOVETYPE_STOP; } else { ent->movetype = MOVETYPE_PUSH; } /* set the axis of rotation */ VectorClear(ent->movedir); if (ent->spawnflags & 4) { ent->movedir[2] = 1.0; } else if (ent->spawnflags & 8) { ent->movedir[0] = 1.0; } else /* Z_AXIS */ { ent->movedir[1] = 1.0; } /* check for reverse rotation */ if (ent->spawnflags & 2) { VectorNegate(ent->movedir, ent->movedir); } if (!ent->speed) { ent->speed = 100; } if (!ent->dmg) { ent->dmg = 2; } ent->use = rotating_use; if (ent->dmg) { ent->blocked = rotating_blocked; } if (ent->spawnflags & 1) { ent->use(ent, NULL, NULL); } if (ent->spawnflags & 64) { ent->s.effects |= EF_ANIM_ALL; } if (ent->spawnflags & 128) { ent->s.effects |= EF_ANIM_ALLFAST; } gi.setmodel(ent, ent->model); gi.linkentity(ent); } /* ==================================================================== */ /* BUTTONS */ /* * QUAKED func_button (0 .5 .8) ? * * When a button is touched, it moves some distance * in the direction of it's angle, triggers all of it's * targets, waits some time, then returns to it's original * position where it can be triggered again. * * "angle" determines the opening direction * "target" all entities with a matching targetname will be used * "speed" override the default 40 speed * "wait" override the default 1 second wait (-1 = never return) * "lip" override the default 4 pixel lip remaining at end of move * "health" if set, the button must be killed instead of touched * "sounds" * 1) silent * 2) steam metal * 3) wooden clunk * 4) metallic click * 5) in-out */ void button_done(edict_t *self) { if (!self) { return; } self->moveinfo.state = STATE_BOTTOM; self->s.effects &= ~EF_ANIM23; self->s.effects |= EF_ANIM01; } void button_return(edict_t *self) { if (!self) { return; } self->moveinfo.state = STATE_DOWN; Move_Calc(self, self->moveinfo.start_origin, button_done); self->s.frame = 0; if (self->health) { self->takedamage = DAMAGE_YES; } } void button_wait(edict_t *self) { if (!self) { return; } self->moveinfo.state = STATE_TOP; self->s.effects &= ~EF_ANIM01; self->s.effects |= EF_ANIM23; G_UseTargets(self, self->activator); self->s.frame = 1; if (self->moveinfo.wait >= 0) { self->nextthink = level.time + self->moveinfo.wait; self->think = button_return; } } void button_fire(edict_t *self) { if (!self) { return; } if ((self->moveinfo.state == STATE_UP) || (self->moveinfo.state == STATE_TOP)) { return; } self->moveinfo.state = STATE_UP; if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE)) { gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); } Move_Calc(self, self->moveinfo.end_origin, button_wait); } void button_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) { if (!self ||!activator) { return; } self->activator = activator; button_fire(self); } void button_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) { if (!self || !other) { return; } if (!other->client) { return; } if (other->health <= 0) { return; } self->activator = other; button_fire(self); } void button_killed(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unsued */, int damage /* unused */, vec3_t point /* unused */) { if (!self) { return; } self->activator = attacker; self->health = self->max_health; self->takedamage = DAMAGE_NO; button_fire(self); } void SP_func_button(edict_t *ent) { vec3_t abs_movedir; float dist; if (!ent) { return; } G_SetMovedir(ent->s.angles, ent->movedir); ent->movetype = MOVETYPE_STOP; ent->solid = SOLID_BSP; gi.setmodel(ent, ent->model); if (ent->sounds != 1) { ent->moveinfo.sound_start = gi.soundindex("switches/butn2.wav"); } if (!ent->speed) { ent->speed = 40; } if (!ent->accel) { ent->accel = ent->speed; } if (!ent->decel) { ent->decel = ent->speed; } if (!ent->wait) { ent->wait = 3; } if (!st.lip) { st.lip = 4; } VectorCopy(ent->s.origin, ent->pos1); abs_movedir[0] = fabs(ent->movedir[0]); abs_movedir[1] = fabs(ent->movedir[1]); abs_movedir[2] = fabs(ent->movedir[2]); dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; VectorMA(ent->pos1, dist, ent->movedir, ent->pos2); ent->use = button_use; ent->s.effects |= EF_ANIM01; if (ent->health) { ent->max_health = ent->health; ent->die = button_killed; ent->takedamage = DAMAGE_YES; } else if (!ent->targetname) { ent->touch = button_touch; } ent->moveinfo.state = STATE_BOTTOM; ent->moveinfo.speed = ent->speed; ent->moveinfo.accel = ent->accel; ent->moveinfo.decel = ent->decel; ent->moveinfo.wait = ent->wait; VectorCopy(ent->pos1, ent->moveinfo.start_origin); VectorCopy(ent->s.angles, ent->moveinfo.start_angles); VectorCopy(ent->pos2, ent->moveinfo.end_origin); VectorCopy(ent->s.angles, ent->moveinfo.end_angles); gi.linkentity(ent); } /* ==================================================================== */ /* * DOORS * * spawn a trigger surrounding the entire team * unless it is already targeted by another */ void door_go_down(edict_t *self); /* * QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST * * TOGGLE wait in both the start and end states for a trigger event. * START_OPEN the door to moves to its destination when spawned, and operate in reverse. * It is used to temporarily or permanently close off an area when triggered * (not useful for touch or takedamage doors). * NOMONSTER monsters will not trigger this door * * "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet * "angle" determines the opening direction * "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. * "health" if set, door must be shot open * "speed" movement speed (100 default) * "wait" wait before returning (3 default, -1 = never return) * "lip" lip remaining at end of move (8 default) * "dmg" damage to inflict when blocked (2 default) * "sounds" * 1) silent * 2) light * 3) medium * 4) heavy */ void door_use_areaportals(edict_t *self, qboolean open) { edict_t *t = NULL; if (!self) { return; } if (!self->target) { return; } while ((t = G_Find(t, FOFS(targetname), self->target))) { if (Q_stricmp(t->classname, "func_areaportal") == 0) { gi.SetAreaPortalState(t->style, open); } } } void door_hit_top(edict_t *self) { if (!self) { return; } if (!(self->flags & FL_TEAMSLAVE)) { if (self->moveinfo.sound_end) { gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); } self->s.sound = 0; } self->moveinfo.state = STATE_TOP; if (self->spawnflags & DOOR_TOGGLE) { return; } if (self->moveinfo.wait >= 0) { self->think = door_go_down; self->nextthink = level.time + self->moveinfo.wait; } } void door_hit_bottom(edict_t *self) { if (!self) { return; } if (!(self->flags & FL_TEAMSLAVE)) { if (self->moveinfo.sound_end) { gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); } self->s.sound = 0; } self->moveinfo.state = STATE_BOTTOM; door_use_areaportals(self, false); } void door_go_down(edict_t *self) { if (!self) { return; } if (!(self->flags & FL_TEAMSLAVE)) { if (self->moveinfo.sound_start) { gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); } self->s.sound = self->moveinfo.sound_middle; } if (self->max_health) { self->takedamage = DAMAGE_YES; self->health = self->max_health; } self->moveinfo.state = STATE_DOWN; if (strcmp(self->classname, "func_door") == 0) { Move_Calc(self, self->moveinfo.start_origin, door_hit_bottom); } else if (strcmp(self->classname, "func_door_rotating") == 0) { AngleMove_Calc(self, door_hit_bottom); } } void door_go_up(edict_t *self, edict_t *activator) { if (!self || !activator) { return; } if (self->moveinfo.state == STATE_UP) { return; /* already going up */ } if (self->moveinfo.state == STATE_TOP) { /* reset top wait time */ if (self->moveinfo.wait >= 0) { self->nextthink = level.time + self->moveinfo.wait; } return; } if (!(self->flags & FL_TEAMSLAVE)) { if (self->moveinfo.sound_start) { gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); } self->s.sound = self->moveinfo.sound_middle; } self->moveinfo.state = STATE_UP; if (strcmp(self->classname, "func_door") == 0) { Move_Calc(self, self->moveinfo.end_origin, door_hit_top); } else if (strcmp(self->classname, "func_door_rotating") == 0) { AngleMove_Calc(self, door_hit_top); } G_UseTargets(self, activator); door_use_areaportals(self, true); } void door_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) { if (!self || !activator) { return; } edict_t *ent; if (self->flags & FL_TEAMSLAVE) { return; } if (self->spawnflags & DOOR_TOGGLE) { if ((self->moveinfo.state == STATE_UP) || (self->moveinfo.state == STATE_TOP)) { /* trigger all paired doors */ for (ent = self; ent; ent = ent->teamchain) { ent->message = NULL; ent->touch = NULL; door_go_down(ent); } return; } } /* trigger all paired doors */ for (ent = self; ent; ent = ent->teamchain) { ent->message = NULL; ent->touch = NULL; door_go_up(ent, activator); } } void Touch_DoorTrigger(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) { if (!self || !other) { return; } if (other->health <= 0) { return; } if (!(other->svflags & SVF_MONSTER) && (!other->client)) { return; } if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER)) { return; } if (level.time < self->touch_debounce_time) { return; } self->touch_debounce_time = level.time + 1.0; door_use(self->owner, other, other); } void Think_CalcMoveSpeed(edict_t *self) { edict_t *ent; float min; float time; float newspeed; float ratio; float dist; if (!self) { return; } if (self->flags & FL_TEAMSLAVE) { return; /* only the team master does this */ } /* find the smallest distance any member of the team will be moving */ min = fabs(self->moveinfo.distance); for (ent = self->teamchain; ent; ent = ent->teamchain) { dist = fabs(ent->moveinfo.distance); if (dist < min) { min = dist; } } time = min / self->moveinfo.speed; /* adjust speeds so they will all complete at the same time */ for (ent = self; ent; ent = ent->teamchain) { newspeed = fabs(ent->moveinfo.distance) / time; ratio = newspeed / ent->moveinfo.speed; if (ent->moveinfo.accel == ent->moveinfo.speed) { ent->moveinfo.accel = newspeed; } else { ent->moveinfo.accel *= ratio; } if (ent->moveinfo.decel == ent->moveinfo.speed) { ent->moveinfo.decel = newspeed; } else { ent->moveinfo.decel *= ratio; } ent->moveinfo.speed = newspeed; } } void Think_SpawnDoorTrigger(edict_t *ent) { edict_t *other; vec3_t mins, maxs; if (!ent) { return; } if (ent->flags & FL_TEAMSLAVE) { return; /* only the team leader spawns a trigger */ } VectorCopy(ent->absmin, mins); VectorCopy(ent->absmax, maxs); for (other = ent->teamchain; other; other = other->teamchain) { AddPointToBounds(other->absmin, mins, maxs); AddPointToBounds(other->absmax, mins, maxs); } /* expand */ mins[0] -= 60; mins[1] -= 60; maxs[0] += 60; maxs[1] += 60; other = G_Spawn(); VectorCopy(mins, other->mins); VectorCopy(maxs, other->maxs); other->owner = ent; other->solid = SOLID_TRIGGER; other->movetype = MOVETYPE_NONE; other->touch = Touch_DoorTrigger; gi.linkentity(other); if (ent->spawnflags & DOOR_START_OPEN) { door_use_areaportals(ent, true); } Think_CalcMoveSpeed(ent); } void door_blocked(edict_t *self, edict_t *other) { edict_t *ent; if (!self || !other) { return; } if (!(other->svflags & SVF_MONSTER) && (!other->client)) { /* give it a chance to go away on it's own terms (like gibs) */ T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); /* if it's still there, nuke it */ if (other) { /* Hack for entitiy without their origin near the model */ VectorMA (other->absmin, 0.5, other->size, other->s.origin); BecomeExplosion1(other); } return; } T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); if (self->spawnflags & DOOR_CRUSHER) { return; } /* if a door has a negative wait, it would never come back if blocked, so let it just squash the object to death real fast */ if (self->moveinfo.wait >= 0) { if (self->moveinfo.state == STATE_DOWN) { for (ent = self->teammaster; ent; ent = ent->teamchain) { door_go_up(ent, ent->activator); } } else { for (ent = self->teammaster; ent; ent = ent->teamchain) { door_go_down(ent); } } } } void door_killed(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker, int damage /* unused */, vec3_t point /* unused */) { edict_t *ent; if (!self || !attacker) { return; } for (ent = self->teammaster; ent; ent = ent->teamchain) { ent->health = ent->max_health; ent->takedamage = DAMAGE_NO; } door_use(self->teammaster, attacker, attacker); } void door_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) { if (!self || !other) { return; } if (!other->client) { return; } if (level.time < self->touch_debounce_time) { return; } self->touch_debounce_time = level.time + 5.0; gi.centerprintf(other, "%s", self->message); gi.sound(other, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0); } void SP_func_door(edict_t *ent) { vec3_t abs_movedir; if (!ent) { return; } if (ent->sounds != 1) { ent->moveinfo.sound_start = gi.soundindex("doors/dr1_strt.wav"); ent->moveinfo.sound_middle = gi.soundindex("doors/dr1_mid.wav"); ent->moveinfo.sound_end = gi.soundindex("doors/dr1_end.wav"); } G_SetMovedir(ent->s.angles, ent->movedir); ent->movetype = MOVETYPE_PUSH; ent->solid = SOLID_BSP; gi.setmodel(ent, ent->model); ent->blocked = door_blocked; ent->use = door_use; if (!ent->speed) { ent->speed = 100; } if (deathmatch->value) { ent->speed *= 2; } if (!ent->accel) { ent->accel = ent->speed; } if (!ent->decel) { ent->decel = ent->speed; } if (!ent->wait) { ent->wait = 3; } if (!st.lip) { st.lip = 8; } if (!ent->dmg) { ent->dmg = 2; } /* calculate second position */ VectorCopy(ent->s.origin, ent->pos1); abs_movedir[0] = fabs(ent->movedir[0]); abs_movedir[1] = fabs(ent->movedir[1]); abs_movedir[2] = fabs(ent->movedir[2]); ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; VectorMA(ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2); /* if it starts open, switch the positions */ if (ent->spawnflags & DOOR_START_OPEN) { VectorCopy(ent->pos2, ent->s.origin); VectorCopy(ent->pos1, ent->pos2); VectorCopy(ent->s.origin, ent->pos1); } ent->moveinfo.state = STATE_BOTTOM; if (ent->health) { ent->takedamage = DAMAGE_YES; ent->die = door_killed; ent->max_health = ent->health; } else if (ent->targetname && ent->message) { gi.soundindex("misc/talk.wav"); ent->touch = door_touch; } ent->moveinfo.speed = ent->speed; ent->moveinfo.accel = ent->accel; ent->moveinfo.decel = ent->decel; ent->moveinfo.wait = ent->wait; VectorCopy(ent->pos1, ent->moveinfo.start_origin); VectorCopy(ent->s.angles, ent->moveinfo.start_angles); VectorCopy(ent->pos2, ent->moveinfo.end_origin); VectorCopy(ent->s.angles, ent->moveinfo.end_angles); if (ent->spawnflags & 16) { ent->s.effects |= EF_ANIM_ALL; } if (ent->spawnflags & 64) { ent->s.effects |= EF_ANIM_ALLFAST; } /* to simplify logic elsewhere, make non-teamed doors into a team of one */ if (!ent->team) { ent->teammaster = ent; } gi.linkentity(ent); ent->nextthink = level.time + FRAMETIME; if (ent->health || ent->targetname) { ent->think = Think_CalcMoveSpeed; } else { ent->think = Think_SpawnDoorTrigger; } } /* * QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS * * TOGGLE causes the door to wait in both the start and end states for a trigger event. * START_OPEN the door to moves to its destination when spawned, and operate in reverse. * It is used to temporarily or permanently close off an area when triggered * (not useful for touch or takedamage doors). * NOMONSTER monsters will not trigger this door * * You need to have an origin brush as part of this entity. The center of that brush will be * the point around which it is rotated. It will rotate around the Z axis by default. You can * check either the X_AXIS or Y_AXIS box to change that. * * "distance" is how many degrees the door will be rotated. * "speed" determines how fast the door moves; default value is 100. * * REVERSE will cause the door to rotate in the opposite direction. * * "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet * "angle" determines the opening direction * "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. * "health" if set, door must be shot open * "speed" movement speed (100 default) * "wait" wait before returning (3 default, -1 = never return) * "dmg" damage to inflict when blocked (2 default) * "sounds" * 1) silent * 2) light * 3) medium * 4) heavy */ void SP_func_door_rotating(edict_t *ent) { if (!ent) { return; } VectorClear(ent->s.angles); /* set the axis of rotation */ VectorClear(ent->movedir); if (ent->spawnflags & DOOR_X_AXIS) { ent->movedir[2] = 1.0; } else if (ent->spawnflags & DOOR_Y_AXIS) { ent->movedir[0] = 1.0; } else /* Z_AXIS */ { ent->movedir[1] = 1.0; } /* check for reverse rotation */ if (ent->spawnflags & DOOR_REVERSE) { VectorNegate(ent->movedir, ent->movedir); } if (!st.distance) { gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin)); st.distance = 90; } VectorCopy(ent->s.angles, ent->pos1); VectorMA(ent->s.angles, st.distance, ent->movedir, ent->pos2); ent->moveinfo.distance = st.distance; ent->movetype = MOVETYPE_PUSH; ent->solid = SOLID_BSP; gi.setmodel(ent, ent->model); ent->blocked = door_blocked; ent->use = door_use; if (!ent->speed) { ent->speed = 100; } if (!ent->accel) { ent->accel = ent->speed; } if (!ent->decel) { ent->decel = ent->speed; } if (!ent->wait) { ent->wait = 3; } if (!ent->dmg) { ent->dmg = 2; } if (ent->sounds != 1) { ent->moveinfo.sound_start = gi.soundindex("doors/dr1_strt.wav"); ent->moveinfo.sound_middle = gi.soundindex("doors/dr1_mid.wav"); ent->moveinfo.sound_end = gi.soundindex("doors/dr1_end.wav"); } /* if it starts open, switch the positions */ if (ent->spawnflags & DOOR_START_OPEN) { VectorCopy(ent->pos2, ent->s.angles); VectorCopy(ent->pos1, ent->pos2); VectorCopy(ent->s.angles, ent->pos1); VectorNegate(ent->movedir, ent->movedir); } if (ent->health) { ent->takedamage = DAMAGE_YES; ent->die = door_killed; ent->max_health = ent->health; } if (ent->targetname && ent->message) { gi.soundindex("misc/talk.wav"); ent->touch = door_touch; } ent->moveinfo.state = STATE_BOTTOM; ent->moveinfo.speed = ent->speed; ent->moveinfo.accel = ent->accel; ent->moveinfo.decel = ent->decel; ent->moveinfo.wait = ent->wait; VectorCopy(ent->s.origin, ent->moveinfo.start_origin); VectorCopy(ent->pos1, ent->moveinfo.start_angles); VectorCopy(ent->s.origin, ent->moveinfo.end_origin); VectorCopy(ent->pos2, ent->moveinfo.end_angles); if (ent->spawnflags & 16) { ent->s.effects |= EF_ANIM_ALL; } /* to simplify logic elsewhere, make non-teamed doors into a team of one */ if (!ent->team) { ent->teammaster = ent; } gi.linkentity(ent); ent->nextthink = level.time + FRAMETIME; if (ent->health || ent->targetname) { ent->think = Think_CalcMoveSpeed; } else { ent->think = Think_SpawnDoorTrigger; } } /* ==================================================================== */ /* * QUAKED func_water (0 .5 .8) ? START_OPEN * * func_water is a moveable water brush. It must be targeted to operate. * Use a non-water texture at your own risk. * * START_OPEN causes the water to move to its destination when spawned * and operate in reverse. * * "angle" determines the opening direction (up or down only) * "speed" movement speed (25 default) * "wait" wait before returning (-1 default, -1 = TOGGLE) * "lip" lip remaining at end of move (0 default) * "sounds" (yes, these need to be changed) * 0) no sound * 1) water * 2) lava */ void SP_func_water(edict_t *self) { vec3_t abs_movedir; if (!self) { return; } G_SetMovedir(self->s.angles, self->movedir); self->movetype = MOVETYPE_PUSH; self->solid = SOLID_BSP; gi.setmodel(self, self->model); switch (self->sounds) { default: break; case 1: /* water */ self->moveinfo.sound_start = gi.soundindex("world/mov_watr.wav"); self->moveinfo.sound_end = gi.soundindex("world/stp_watr.wav"); break; case 2: /* lava */ self->moveinfo.sound_start = gi.soundindex("world/mov_watr.wav"); self->moveinfo.sound_end = gi.soundindex("world/stp_watr.wav"); break; } /* calculate second position */ VectorCopy(self->s.origin, self->pos1); abs_movedir[0] = fabs(self->movedir[0]); abs_movedir[1] = fabs(self->movedir[1]); abs_movedir[2] = fabs(self->movedir[2]); self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip; VectorMA(self->pos1, self->moveinfo.distance, self->movedir, self->pos2); /* if it starts open, switch the positions */ if (self->spawnflags & DOOR_START_OPEN) { VectorCopy(self->pos2, self->s.origin); VectorCopy(self->pos1, self->pos2); VectorCopy(self->s.origin, self->pos1); } VectorCopy(self->pos1, self->moveinfo.start_origin); VectorCopy(self->s.angles, self->moveinfo.start_angles); VectorCopy(self->pos2, self->moveinfo.end_origin); VectorCopy(self->s.angles, self->moveinfo.end_angles); self->moveinfo.state = STATE_BOTTOM; if (!self->speed) { self->speed = 25; } self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed; if (!self->wait) { self->wait = -1; } self->moveinfo.wait = self->wait; self->use = door_use; if (self->wait == -1) { self->spawnflags |= DOOR_TOGGLE; } self->classname = "func_door"; gi.linkentity(self); } /* ==================================================================== */ #define TRAIN_START_ON 1 #define TRAIN_TOGGLE 2 #define TRAIN_BLOCK_STOPS 4 void train_next(edict_t *self); /* * QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS * * Trains are moving platforms that players can ride. * The targets origin specifies the min point of the train * at each corner. The train spawns at the first target it * is pointing at. If the train is the target of a button * or trigger, it will not begin moving until activated. * * speed default 100 * dmg default 2 * noise looping sound to play when the train is in motion * */ void train_blocked(edict_t *self, edict_t *other) { if (!self || !other) { return; } if (!(other->svflags & SVF_MONSTER) && (!other->client)) { /* give it a chance to go away on it's own terms (like gibs) */ T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); /* if it's still there, nuke it */ if (other) { /* Hack for entity without an origin near the model */ VectorMA (other->absmin, 0.5, other->size, other->s.origin); BecomeExplosion1(other); } return; } if (level.time < self->touch_debounce_time) { return; } if (!self->dmg) { return; } self->touch_debounce_time = level.time + 0.5; T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); } void train_wait(edict_t *self) { if (!self) { return; } if (self->target_ent->pathtarget) { char *savetarget; edict_t *ent; ent = self->target_ent; savetarget = ent->target; ent->target = ent->pathtarget; G_UseTargets(ent, self->activator); ent->target = savetarget; /* make sure we didn't get killed by a killtarget */ if (!self->inuse) { return; } } if (self->moveinfo.wait) { if (self->moveinfo.wait > 0) { self->nextthink = level.time + self->moveinfo.wait; self->think = train_next; } else if (self->spawnflags & TRAIN_TOGGLE) { train_next(self); self->spawnflags &= ~TRAIN_START_ON; VectorClear(self->velocity); self->nextthink = 0; } if (!(self->flags & FL_TEAMSLAVE)) { if (self->moveinfo.sound_end) { gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); } self->s.sound = 0; } } else { train_next(self); } } void train_next(edict_t *self) { if (!self) { return; } edict_t *ent; vec3_t dest; qboolean first; first = true; again: if (!self->target) { return; } ent = G_PickTarget(self->target); if (!ent) { gi.dprintf("train_next: bad target %s\n", self->target); return; } self->target = ent->target; /* check for a teleport path_corner */ if (ent->spawnflags & 1) { if (!first) { gi.dprintf("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin)); return; } first = false; VectorSubtract(ent->s.origin, self->mins, self->s.origin); VectorCopy(self->s.origin, self->s.old_origin); self->s.event = EV_OTHER_TELEPORT; gi.linkentity(self); goto again; } self->moveinfo.wait = ent->wait; self->target_ent = ent; if (!(self->flags & FL_TEAMSLAVE)) { if (self->moveinfo.sound_start) { gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); } self->s.sound = self->moveinfo.sound_middle; } VectorSubtract(ent->s.origin, self->mins, dest); self->moveinfo.state = STATE_TOP; VectorCopy(self->s.origin, self->moveinfo.start_origin); VectorCopy(dest, self->moveinfo.end_origin); Move_Calc(self, dest, train_wait); self->spawnflags |= TRAIN_START_ON; } void train_resume(edict_t *self) { edict_t *ent; vec3_t dest; if (!self) { return; } ent = self->target_ent; VectorSubtract(ent->s.origin, self->mins, dest); self->moveinfo.state = STATE_TOP; VectorCopy(self->s.origin, self->moveinfo.start_origin); VectorCopy(dest, self->moveinfo.end_origin); Move_Calc(self, dest, train_wait); self->spawnflags |= TRAIN_START_ON; } void func_train_find(edict_t *self) { edict_t *ent; if (!self) { return; } if (!self->target) { gi.dprintf("train_find: no target\n"); return; } ent = G_PickTarget(self->target); if (!ent) { gi.dprintf("train_find: target %s not found\n", self->target); return; } self->target = ent->target; VectorSubtract(ent->s.origin, self->mins, self->s.origin); gi.linkentity(self); /* if not triggered, start immediately */ if (!self->targetname) { self->spawnflags |= TRAIN_START_ON; } if (self->spawnflags & TRAIN_START_ON) { self->nextthink = level.time + FRAMETIME; self->think = train_next; self->activator = self; } } void train_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) { if (!self || !activator) { return; } self->activator = activator; if (self->spawnflags & TRAIN_START_ON) { if (!(self->spawnflags & TRAIN_TOGGLE)) { return; } self->spawnflags &= ~TRAIN_START_ON; VectorClear(self->velocity); self->nextthink = 0; } else { if (self->target_ent) { train_resume(self); } else { train_next(self); } } } void SP_func_train(edict_t *self) { if (!self) { return; } self->movetype = MOVETYPE_PUSH; VectorClear(self->s.angles); self->blocked = train_blocked; if (self->spawnflags & TRAIN_BLOCK_STOPS) { self->dmg = 0; } else { if (!self->dmg) { self->dmg = 100; } } self->solid = SOLID_BSP; gi.setmodel(self, self->model); if (st.noise) { self->moveinfo.sound_middle = gi.soundindex(st.noise); } if (!self->speed) { self->speed = 100; } self->moveinfo.speed = self->speed; self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed; self->use = train_use; gi.linkentity(self); if (self->target) { /* start trains on the second frame, to make sure their targets have had a chance to spawn */ self->nextthink = level.time + FRAMETIME; self->think = func_train_find; } else { gi.dprintf("func_train without a target at %s\n", vtos(self->absmin)); } } /* ==================================================================== */ /* * QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) */ void trigger_elevator_use(edict_t *self, edict_t *other, edict_t *activator /* unused */) { edict_t *target; if (!self || !other) { return; } if (self->movetarget->nextthink) { return; } if (!other->pathtarget) { gi.dprintf("elevator used with no pathtarget\n"); return; } target = G_PickTarget(other->pathtarget); if (!target) { gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget); return; } self->movetarget->target_ent = target; train_resume(self->movetarget); } void trigger_elevator_init(edict_t *self) { if (!self) { return; } if (!self->target) { gi.dprintf("trigger_elevator has no target\n"); return; } self->movetarget = G_PickTarget(self->target); if (!self->movetarget) { gi.dprintf("trigger_elevator unable to find target %s\n", self->target); return; } if (strcmp(self->movetarget->classname, "func_train") != 0) { gi.dprintf("trigger_elevator target %s is not a train\n", self->target); return; } self->use = trigger_elevator_use; self->svflags = SVF_NOCLIENT; } void SP_trigger_elevator(edict_t *self) { if (!self) { return; } self->think = trigger_elevator_init; self->nextthink = level.time + FRAMETIME; } /* ==================================================================== */ /* * QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON * * "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) * * "delay" delay before first firing when turned on, default is 0 * "pausetime" additional delay used only the very first time * and only if spawned with START_ON * * These can used but not touched. */ void func_timer_think(edict_t *self) { if (!self) { return; } G_UseTargets(self, self->activator); self->nextthink = level.time + self->wait + crandom() * self->random; } void func_timer_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) { if (!self || !activator) { return; } self->activator = activator; /* if on, turn it off */ if (self->nextthink) { self->nextthink = 0; return; } /* turn it on */ if (self->delay) { self->nextthink = level.time + self->delay; } else { func_timer_think(self); } } void SP_func_timer(edict_t *self) { if (!self) { return; } if (!self->wait) { self->wait = 1.0; } self->use = func_timer_use; self->think = func_timer_think; if (self->random >= self->wait) { self->random = self->wait - FRAMETIME; gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin)); } if (self->spawnflags & 1) { self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random; self->activator = self; } self->svflags = SVF_NOCLIENT; } /* ==================================================================== */ /* * QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE * * Conveyors are stationary brushes that move what's on them. * The brush should be have a surface with at least one current * content enabled. * * speed default 100 */ void func_conveyor_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) { if (!self) { return; } if (self->spawnflags & 1) { self->speed = 0; self->spawnflags &= ~1; } else { self->speed = self->count; self->spawnflags |= 1; } if (!(self->spawnflags & 2)) { self->count = 0; } } void SP_func_conveyor(edict_t *self) { if (!self) { return; } if (!self->speed) { self->speed = 100; } if (!(self->spawnflags & 1)) { self->count = self->speed; self->speed = 0; } self->use = func_conveyor_use; gi.setmodel(self, self->model); self->solid = SOLID_BSP; gi.linkentity(self); } /* ==================================================================== */ /* * QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down * A secret door. Slide back and then to the side. * * open_once doors never closes * 1st_left 1st move is left of arrow * 1st_down 1st move is down from arrow * always_shoot door is shootebale even if targeted * * "angle" determines the direction * "dmg" damage to inflic when blocked (default 2) * "wait" how long to hold in the open position (default 5, -1 means hold) */ #define SECRET_ALWAYS_SHOOT 1 #define SECRET_1ST_LEFT 2 #define SECRET_1ST_DOWN 4 void door_secret_move1(edict_t *self); void door_secret_move2(edict_t *self); void door_secret_move3(edict_t *self); void door_secret_move4(edict_t *self); void door_secret_move5(edict_t *self); void door_secret_move6(edict_t *self); void door_secret_done(edict_t *self); void door_secret_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) { if (!self) { return; } /* make sure we're not already moving */ if (!VectorCompare(self->s.origin, vec3_origin)) { return; } Move_Calc(self, self->pos1, door_secret_move1); door_use_areaportals(self, true); } void door_secret_move1(edict_t *self) { if (!self) { return; } self->nextthink = level.time + 1.0; self->think = door_secret_move2; } void door_secret_move2(edict_t *self) { if (!self) { return; } Move_Calc(self, self->pos2, door_secret_move3); } void door_secret_move3(edict_t *self) { if (!self) { return; } if (self->wait == -1) { return; } self->nextthink = level.time + self->wait; self->think = door_secret_move4; } void door_secret_move4(edict_t *self) { if (!self) { return; } Move_Calc(self, self->pos1, door_secret_move5); } void door_secret_move5(edict_t *self) { if (!self) { return; } self->nextthink = level.time + 1.0; self->think = door_secret_move6; } void door_secret_move6(edict_t *self) { if (!self) { return; } Move_Calc(self, vec3_origin, door_secret_done); } void door_secret_done(edict_t *self) { if (!self) { return; } if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT)) { self->health = 0; self->takedamage = DAMAGE_YES; } door_use_areaportals(self, false); } void door_secret_blocked(edict_t *self, edict_t *other) { if (!self || !other) { return; } if (!(other->svflags & SVF_MONSTER) && (!other->client)) { /* give it a chance to go away on it's own terms (like gibs) */ T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); /* if it's still there, nuke it */ if (other) { /* Hack for entities without their origin near the model */ VectorMA (other->absmin, 0.5, other->size, other->s.origin); BecomeExplosion1(other); } return; } if (level.time < self->touch_debounce_time) { return; } self->touch_debounce_time = level.time + 0.5; T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); } void door_secret_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker, int damage /* unused */, vec3_t point /* unused */) { if (!self || !attacker) { return; } self->takedamage = DAMAGE_NO; door_secret_use(self, attacker, attacker); } void SP_func_door_secret(edict_t *ent) { vec3_t forward, right, up; float side; float width; float length; if (!ent) { return; } ent->moveinfo.sound_start = gi.soundindex("doors/dr1_strt.wav"); ent->moveinfo.sound_middle = gi.soundindex("doors/dr1_mid.wav"); ent->moveinfo.sound_end = gi.soundindex("doors/dr1_end.wav"); ent->movetype = MOVETYPE_PUSH; ent->solid = SOLID_BSP; gi.setmodel(ent, ent->model); ent->blocked = door_secret_blocked; ent->use = door_secret_use; if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT)) { ent->health = 0; ent->takedamage = DAMAGE_YES; ent->die = door_secret_die; } if (!ent->dmg) { ent->dmg = 2; } if (!ent->wait) { ent->wait = 5; } ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = 50; /* calculate positions */ AngleVectors(ent->s.angles, forward, right, up); VectorClear(ent->s.angles); side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT); if (ent->spawnflags & SECRET_1ST_DOWN) { width = fabs(DotProduct(up, ent->size)); } else { width = fabs(DotProduct(right, ent->size)); } length = fabs(DotProduct(forward, ent->size)); if (ent->spawnflags & SECRET_1ST_DOWN) { VectorMA(ent->s.origin, -1 * width, up, ent->pos1); } else { VectorMA(ent->s.origin, side * width, right, ent->pos1); } VectorMA(ent->pos1, length, forward, ent->pos2); if (ent->health) { ent->takedamage = DAMAGE_YES; ent->die = door_killed; ent->max_health = ent->health; } else if (ent->targetname && ent->message) { gi.soundindex("misc/talk.wav"); ent->touch = door_touch; } ent->classname = "func_door"; gi.linkentity(ent); } /* ==================================================================== */ /* * QUAKED func_killbox (1 0 0) ? * * Kills everything inside when fired, * irrespective of protection. */ void use_killbox(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) { if (!self) { return; } KillBox(self); /* Hack to make sure that really everything is killed */ self->count--; if (!self->count) { self->think = G_FreeEdict; self->nextthink = level.time + 1; } } void SP_func_killbox(edict_t *ent) { if (!ent) { return; } gi.setmodel(ent, ent->model); ent->use = use_killbox; ent->svflags = SVF_NOCLIENT; }