/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "g_local.h" void G_AssignMoverSounds( edict_t *ent, char *start, char *move, char *stop ) { if( st.noise && Q_stricmp(st.noise, "default") ) { if( Q_stricmp(st.noise, "silent") ) ent->moveinfo.sound_middle = trap_SoundIndex( st.noise ); } else if( move ) ent->moveinfo.sound_middle = trap_SoundIndex( move ); if( st.noise_start && Q_stricmp(st.noise_start, "default") ) { if( Q_stricmp(st.noise_start, "silent") ) ent->moveinfo.sound_start = trap_SoundIndex( st.noise_start ); } else if( start ) ent->moveinfo.sound_start = trap_SoundIndex( start ); if( st.noise_stop && Q_stricmp(st.noise_stop, "default") ) { if( Q_stricmp(st.noise_stop, "silent") ) ent->moveinfo.sound_end = trap_SoundIndex( st.noise_stop ); } else if( stop ) ent->moveinfo.sound_end = trap_SoundIndex( stop ); } //========================================================= // // 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 STATE_STOPPED 0 #define STATE_ACCEL 1 #define STATE_FULLSPEED 2 #define STATE_DECEL 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 //wsw #define DOOR_DIE_ONCE 1024 // auto set when health is > 0 // // Support routines for movement (changes in origin using velocity) // void Move_Done( edict_t *ent ) { VectorClear( ent->velocity ); ent->moveinfo.endfunc(ent); } void Move_Final( edict_t *ent ) { 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.timemsec + game.framemsec; } void Move_Begin( edict_t *ent ) { float frames; float traveltime; if( (ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance ) { Move_Final(ent); return; } // set up velocity vector VectorScale( ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity ); //frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME); // divide dist by speed to get time to reach dest traveltime = ent->moveinfo.remaining_distance / ent->moveinfo.speed; if( traveltime < FRAMETIME ) { Move_Final(ent); return; } // find number of frames to be run within the give time (quite useless now) frames = floor(traveltime / FRAMETIME); ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME; // set nextthink to trigger a think when dest is reached ent->nextthink = level.timemsec + (frames * game.framemsec); ent->think = Move_Final; } void Think_AccelMove( edict_t *ent ); void Move_Calc( edict_t *ent, vec3_t dest, void(*func)(edict_t*) ) { 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.timemsec + game.framemsec; ent->think = Move_Begin; } } else { // accelerative ent->moveinfo.current_speed = 0; ent->think = Think_AccelMove; ent->nextthink = level.timemsec + game.framemsec; } } // // Support routines for angular movement (changes in angle using avelocity) // void AngleMove_Done( edict_t *ent ) { VectorClear( ent->avelocity ); ent->moveinfo.endfunc(ent); } void AngleMove_Final( edict_t *ent ) { vec3_t move; 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.timemsec + game.framemsec; } void AngleMove_Begin( edict_t *ent ) { vec3_t destdelta; float len; float traveltime; float frames; // 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.timemsec + frames * game.framemsec; ent->think = AngleMove_Final; } void AngleMove_Calc( edict_t *ent, void(*func)(edict_t*) ) { 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.timemsec + game.framemsec; ent->think = AngleMove_Begin; } } //============== //Think_AccelMove // //The team has completed a frame of movement, so //change the speed for the next frame //============== #define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2) void plat_CalcAcceleratedMove( moveinfo_t *moveinfo ) { float accel_dist; float decel_dist; moveinfo->move_speed = moveinfo->speed; if( moveinfo->remaining_distance < moveinfo->accel * FRAMETIME ) { moveinfo->current_speed = moveinfo->remaining_distance; return; } accel_dist = AccelerationDistance (moveinfo->speed * FRAMETIME, moveinfo->accel * FRAMETIME); decel_dist = AccelerationDistance (moveinfo->speed * FRAMETIME, moveinfo->decel * FRAMETIME); // jal : wsw : I don't understand very well what's this case doing :/ 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 ) { // 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; } void Think_AccelMove( edict_t *ent ) { ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed * FRAMETIME; 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 * FRAMETIME ) { Move_Final(ent); return; } VectorScale( ent->moveinfo.dir, ent->moveinfo.current_speed, ent->velocity ); ent->nextthink = level.timemsec + game.framemsec; ent->think = Think_AccelMove; } void plat_go_down( edict_t *ent ); void plat_hit_top( edict_t *ent ) { if( !(ent->flags & FL_TEAMSLAVE) ) { if( ent->moveinfo.sound_end ) G_Sound( ent, CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC ); ent->s.sound = 0; } ent->moveinfo.state = STATE_TOP; ent->think = plat_go_down; ent->nextthink = level.timemsec + 3000; } void plat_hit_bottom( edict_t *ent ) { if( !(ent->flags & FL_TEAMSLAVE) ) { if( ent->moveinfo.sound_end ) G_Sound (ent, CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC); ent->s.sound = 0; } ent->moveinfo.state = STATE_BOTTOM; } void plat_go_down( edict_t *ent ) { if( !(ent->flags & FL_TEAMSLAVE) ) { if( ent->moveinfo.sound_start ) G_Sound( ent, CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC ); 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->flags & FL_TEAMSLAVE) ) { if( ent->moveinfo.sound_start ) G_Sound( ent, CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC ); 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( !(other->r.svflags & SVF_MONSTER) && (!other->r.client) ) { // give it a chance to go away on its 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 ) 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, edict_t *activator ) { if( ent->think ) return; // already down plat_go_down (ent); } void Touch_Plat_Center( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags ) { if( !other->r.client ) return; if( G_IsDead(other) ) 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 ) ent->nextthink = level.timemsec + 1000; // the player is still on the plat, so delay going down } void plat_spawn_inside_trigger( edict_t *ent ) { edict_t *trigger; vec3_t tmin, tmax; // // middle trigger // trigger = G_Spawn(); trigger->touch = Touch_Plat_Center; trigger->movetype = MOVETYPE_NONE; trigger->r.solid = SOLID_TRIGGER; trigger->enemy = ent; tmin[0] = ent->r.mins[0] + 25; tmin[1] = ent->r.mins[1] + 25; tmin[2] = ent->r.mins[2]; tmax[0] = ent->r.maxs[0] - 25; tmax[1] = ent->r.maxs[1] - 25; tmax[2] = ent->r.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->r.mins[0] + ent->r.maxs[0]) * 0.5; tmax[0] = tmin[0] + 1; } if (tmax[1] - tmin[1] <= 0) { tmin[1] = (ent->r.mins[1] + ent->r.maxs[1]) * 0.5; tmax[1] = tmin[1] + 1; } VectorCopy( tmin, trigger->r.mins ); VectorCopy( tmax, trigger->r.maxs ); trap_LinkEntity( trigger ); } /*QUAKED func_plat (0 .5 .8) ? LOW_TRIGGER Rising platform the player can ride to reach higher places. Plats must always be drawn in the raised position, so they will operate and be lighted correctly but they spawn in the lowered position. The plat will stay in the raised position until the player steps off. -------- KEYS -------- speed : determines how fast the plat moves (default 300). accel : default 20 deccel : default 20 lip : lip remaining at end of move (default 8). height : if set, this will determine the total amount of vertical travel of the plat, instead of being implicitly assigned by the model's height. dmg : damage to inflict on player when he blocks operation of plat (default 2). Plat will reverse direction when blocked. targetname : if set, the trigger that points to this will raise the plat each time it fires. noise_start : overrides default start moving sound noise : overrides default movement sound noise_stop : overrides default stop moving sound light : constantLight radius of .md3 model included with entity. Has no effect on the entity's brushes (default 0). color : constantLight color of .md3 model included with entity. Has no effect on the entity's brushes (default 1 1 1). model2 : path/name of model to include (eg: models/mapobjects/pipe/pipe02.md3). origin : alternate method of setting XYZ origin of .md3 model included with entity (See Notes). notsingle : when set to 1, entity will not spawn in Single Player mode notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) -------- SPAWNFLAGS -------- LOW_TRIGGER : &1 the plat is triggered from its lower side -------- NOTES -------- By default, the total amount of vertical travel of a platform is implicitly determined by the overall vertical size of the brushes of which it's made minus the lip value. But if the "height" key is used, then the total amount of vertical travel of the plat will be exactly that value regardless of the shape and size of the plat and regardless of the value of the "lip" key. Using the "height" key is the best method for any kind of platforms and the only possible one for thin plats which need to travel vertical distances many times their own thickness. Setting the origin key is simply an alternate method to using an origin brush. When using the model2 key, the origin point of the model will correspond to the origin point defined by either the origin brush or the origin coordinate value. */ void SP_func_plat( edict_t *ent ) { G_InitMover( ent ); VectorClear( ent->s.angles ); ent->blocked = plat_blocked; if( !ent->speed ) ent->speed = 300; if( !ent->accel ) ent->accel = 20; if( !ent->decel ) ent->decel = 20; 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->r.maxs[2] - ent->r.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 ); trap_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 ); G_AssignMoverSounds( ent, S_PLAT_START, S_PLAT_MOVE, S_PLAT_STOP ); } //==================================================================== /*QUAKED func_rotating (0 .5 .8) ? START_OFF REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP - - 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. -------- KEYS -------- targetname : name to be targeted target : names to target. speed : determines how fast entity rotates (default 100). noise_start : overrides default start moving sound noise : overrides default movement sound noise_stop : overrides default stop moving sound model2 : path/name of model to include (eg: models/mapobjects/bitch/fembotbig.md3). origin : alternate method of setting XYZ origin of entity's rotation axis and .md3 model included with entity (default "0 0 0" - See Notes). light : constantLight radius of .md3 model included with entity. Has no effect on the entity's brushes (default 0). color : constantLight color of .md3 model included with entity. Has no effect on the entity's brushes (default 1 1 1). notsingle : when set to 1, entity will not spawn in Single Player mode notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) -------- SPAWNFLAGS -------- START_OFF : &1 must be triggered to start moving. REVERSE : &2 will cause the it to rotate in the opposite direction. X_AXIS : &4 entity will rotate along the X axis. Y_AXIS : &8 entity will rotate along the Y axis. TOUCH_PAIN : &16 dunno what it does (jal) STOP : &32 it will stop moving instead when blocked, instead of pushing or crushing them. -------- NOTES -------- You need to have an origin brush as part of this entity. The center of that brush will be the point through which the rotation axis passes. Setting the origin key is simply an alternate method to using an origin brush. It will rotate along the Z axis by default. You can check either the X_AXIS or Y_AXIS box to change that. When using the model2 key, the origin point of the model will correspond to the origin point defined by either the origin brush or the origin coordinate value.*/ void rotating_blocked( edict_t *self, edict_t *other ) { 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, int surfFlags ) { 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 Think_RotateAccel( edict_t *self ) { if( self->moveinfo.current_speed >= self->speed ) { // has reached full speed // if calculation causes it to go a little over, readjust if( self->moveinfo.current_speed != self->speed ) VectorScale( self->movedir, self->speed, self->avelocity ); self->think = NULL; self->moveinfo.state = STATE_FULLSPEED; return; } // has reached full speed // if here, some more acceleration needs to be done // add acceleration value to current speed to cause accel self->moveinfo.current_speed += self->accel; VectorScale( self->movedir, self->moveinfo.current_speed, self->avelocity ); self->nextthink = level.timemsec + game.framemsec; } void Think_RotateDecel( edict_t *self ) { if( self->moveinfo.current_speed <= 0 ) { // has reached full speed // if calculation cause it to go a little under, readjust if( self->moveinfo.current_speed != 0 ) { VectorClear( self->avelocity ); self->moveinfo.current_speed = 0; } self->think = NULL; self->moveinfo.state = STATE_STOPPED; return; } // has reached full stop // if here, some more deceleration needs to be done // subtract deceleration value from current speed to cause decel self->moveinfo.current_speed -= self->decel; VectorScale( self->movedir, self->moveinfo.current_speed, self->avelocity ); self->nextthink = level.timemsec + game.framemsec; } void rotating_use( edict_t *self, edict_t *other, edict_t *activator ) { // first, figure out what state we are in if( self->moveinfo.state == STATE_ACCEL || self->moveinfo.state == STATE_FULLSPEED ) { // if decel is 0 then just stop if( self->decel == 0 ) { VectorClear( self->avelocity ); self->moveinfo.current_speed = 0; self->touch = NULL; self->think = NULL; self->moveinfo.state = STATE_STOPPED; } else { // otherwise decelerate self->think = Think_RotateDecel; self->nextthink = level.timemsec + game.framemsec; self->moveinfo.state = STATE_DECEL; } // decelerate } else { self->s.sound = self->moveinfo.sound_middle; // check if accel is 0. If so, just start the rotation if( self->accel == 0 ) { VectorScale( self->movedir, self->speed, self->avelocity ); self->moveinfo.state = STATE_FULLSPEED; } else { // accelerate baybee self->think = Think_RotateAccel; self->nextthink = level.timemsec + game.framemsec; self->moveinfo.state = STATE_ACCEL; } } // setup touch function if needed if( self->spawnflags & 16) self->touch = rotating_touch; } void SP_func_rotating( edict_t *ent ) { G_InitMover( ent ); if( ent->spawnflags & 32 ) ent->movetype = MOVETYPE_STOP; else ent->movetype = MOVETYPE_PUSH; ent->moveinfo.state = STATE_STOPPED; // rotating thingy starts out idle // 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; if( ent->accel < 0 ) // sanity check ent->accel = 0; else ent->accel *= 0.1f; if( ent->decel < 0 ) // sanity check ent->decel = 0; else ent->decel *= 0.1f; ent->moveinfo.current_speed = 0; ent->use = rotating_use; if( ent->dmg ) ent->blocked = rotating_blocked; G_AssignMoverSounds( ent, S_FUNC_ROTATING_START, S_FUNC_ROTATING_MOVE, S_FUNC_ROTATING_STOP ); if( !(ent->spawnflags & 1) ) ent->use (ent, NULL, NULL); trap_LinkEntity( ent ); } //====================================================================== // //BUTTONS // //====================================================================== /*QUAKED func_button (0 .5 .8) ? When a button is touched by a player, it moves in the direction set by the "angle" key, triggers all its targets, stays pressed by an amount of time set by the "wait" key, then returns to it's original position where it can be operated again. -------- KEYS -------- angle : determines the direction in which the button will move (up = -1, down = -2). target : all entities with a matching targetname will be triggered. speed : speed of button's displacement (default 40). wait : number of seconds button stays pressed (default 1, -1 = return immediately). lip : lip remaining at end of move (default 4 units). health : (default 0) if set to any non-zero value, the button must take damage (any amount) to activate. noise : custom noise to be played when activated light : constantLight radius of .md3 model included with entity. Has no effect on the entity's brushes (default 0). color : constantLight color of .md3 model included with entity. Has no effect on the entity's brushes (default 1 1 1). model2 : path/name of model to include (eg: models/mapobjects/pipe/pipe02.md3). origin : alternate method of setting XYZ origin of .md3 model included with entity (See Notes). notsingle : when set to 1, entity will not spawn in Single Player mode notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) nobot : bots can't trigger it (jal: todo) nomonster : monsters can't trigger it (jal: todo) -------- NOTES -------- Setting the origin key is simply an alternate method to using an origin brush. When using the model2 key, the origin point of the model will correspond to the origin point defined by either the origin brush or the origin coordinate value.*/ void button_done( edict_t *self ) { self->moveinfo.state = STATE_BOTTOM; } void button_return( edict_t *self ) { 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 ) { self->moveinfo.state = STATE_TOP; G_UseTargets( self, self->activator ); self->s.frame = 1; if( self->moveinfo.wait >= 0 ) { self->nextthink = level.timemsec + (self->moveinfo.wait * 1000); self->think = button_return; } } void button_fire( edict_t *self ) { 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) ) G_Sound( self, CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC ); Move_Calc( self, self->moveinfo.end_origin, button_wait ); } void button_use( edict_t *self, edict_t *other, edict_t *activator ) { self->activator = activator; button_fire( self ); } void button_touch( edict_t *self, edict_t *other, cplane_t *plane, int surfFlags ) { if( !other->r.client ) return; if( G_IsDead(other) ) return; self->activator = other; button_fire( self ); } void button_killed( edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point ) { 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; G_InitMover( ent ); G_SetMovedir( ent->s.angles, ent->movedir ); if( st.noise && Q_stricmp(st.noise, "default") ) { if( Q_stricmp( st.noise, "silent" ) != 0 ) ent->moveinfo.sound_start = trap_SoundIndex( st.noise ); } else ent->moveinfo.sound_start = trap_SoundIndex( S_BUTTON_START ); 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->r.size[0] + abs_movedir[1] * ent->r.size[1] + abs_movedir[2] * ent->r.size[2] - st.lip; VectorMA( ent->pos1, dist, ent->movedir, ent->pos2 ); ent->use = button_use; 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 ); trap_LinkEntity(ent); } //====================================================================== // //DOORS // // spawn a trigger surrounding the entire team unless it is // already targeted by another // //====================================================================== /*QUAKED func_door (0 .5 .8) ? START_OPEN - CRUSHER NOMONSTER - TOGGLE - Normal sliding door entity. By default, the door will activate when player walks close to it or when damage is inflicted to it. -------- KEYS -------- 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 of door (up = -1, down = -2). speed : determines how fast the door moves (default 600). wait : number of seconds before door returns (2 default, -1 = never return) lip : lip remaining at end of move (default 8) targetname : if set, a func_button or trigger is required to activate the door. target : fire entities with this targetname when activated health : (default 0) if set to any non-zero value, the button must take damage (any amount) to activate. dmg : damage to inflict on player when he blocks operation of door (default 2). Door will reverse direction when blocked unless CRUSHER spawnflag is set. team: assign the same team name to multiple doors that should operate together (see Notes). noise_start : overrides default start moving sound noise : overrides default movement sound noise_stop : overrides default stop moving sound light : constantLight radius of .md3 model included with entity. Has no effect on the entity's brushes (default 0). color : constantLight color of .md3 model included with entity. Has no effect on the entity's brushes (default 1 1 1). model2 : path/name of model to include (eg: models/mapobjects/pipe/pipe02.md3). origin : alternate method of setting XYZ origin of .md3 model included with entity (See Notes). notsingle : when set to 1, entity will not spawn in Single Player mode notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) -------- SPAWNFLAGS -------- START_OPEN : &1 the door will spawn in the open state and operate in reverse. CRUSHER : &4 door will not reverse direction when blocked and will keep damaging player until he dies or gets out of the way. NOMONSTER : &8 monsters will not trigger this door TOGGLE : &32 wait in both the start and end states for a trigger event -------- NOTES -------- */ void door_use_areaportals( edict_t *self, qboolean open ) { if( self->flags & FL_TEAMSLAVE ) return; // only the team master does this trap_SetAreaPortalState( self, open ); } void door_go_down( edict_t *self ); void door_hit_top( edict_t *self ) { if( !(self->flags & FL_TEAMSLAVE) ) { if( self->moveinfo.sound_end ) G_Sound( self, CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC ); 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.timemsec + (self->moveinfo.wait * 1000); } } void door_hit_bottom( edict_t *self ) { if( !(self->flags & FL_TEAMSLAVE) ) { if( self->moveinfo.sound_end ) G_Sound( self, CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC ); self->s.sound = 0; } self->moveinfo.state = STATE_BOTTOM; door_use_areaportals( self, qfalse ); } void door_go_down( edict_t *self ) { if( !(self->flags & FL_TEAMSLAVE) ) { if( self->moveinfo.sound_start ) G_Sound( self, CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC ); 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( !Q_stricmp(self->classname, "func_door") ) Move_Calc( self, self->moveinfo.start_origin, door_hit_bottom ); else if( !Q_stricmp(self->classname, "func_door_rotating") ) AngleMove_Calc( self, door_hit_bottom ); } void door_go_up( edict_t *self, edict_t *activator ) { 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.timemsec + (self->moveinfo.wait * 1000); return; } if( !(self->flags & FL_TEAMSLAVE) ) { if( self->moveinfo.sound_start ) G_Sound( self, CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC ); self->s.sound = self->moveinfo.sound_middle; } self->moveinfo.state = STATE_UP; if( !Q_stricmp(self->classname, "func_door") ) Move_Calc( self, self->moveinfo.end_origin, door_hit_top ); else if( !Q_stricmp(self->classname, "func_door_rotating") ) AngleMove_Calc( self, door_hit_top ); G_UseTargets( self, activator ); door_use_areaportals( self, qtrue ); } void door_use( edict_t *self, edict_t *other, edict_t *activator ) { 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, int surfFlags ) { if( G_IsDead(other) ) return; if( self->s.team && other->s.team != self->s.team ) return; //MBotgame[start] (allow monsters to open standard doors) if( !(other->r.svflags & SVF_MONSTER) && (!other->r.client) && (other->ai.type != AI_ISMONSTER) ) return; //MBotgame[end] if( (self->r.owner->spawnflags & DOOR_NOMONSTER) && (other->r.svflags & SVF_MONSTER) ) return; if( level.time < self->touch_debounce_time ) return; self->touch_debounce_time = level.time + 1.0; door_use( self->r.owner, other, other ); } void Think_CalcMoveSpeed( edict_t *self ) { edict_t *ent; float min; float time; float newspeed; float ratio; float dist; 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; float expand_size = 80; // was 60 if( ent->flags & FL_TEAMSLAVE ) return; // only the team leader spawns a trigger VectorCopy( ent->r.absmin, mins ); VectorCopy( ent->r.absmax, maxs ); for( other = ent->teamchain ; other ; other=other->teamchain ) { AddPointToBounds( other->r.absmin, mins, maxs ); AddPointToBounds( other->r.absmax, mins, maxs ); } // expand mins[0] -= expand_size; mins[1] -= expand_size; maxs[0] += expand_size; maxs[1] += expand_size; other = G_Spawn(); VectorCopy( mins, other->r.mins ); VectorCopy( maxs, other->r.maxs ); other->r.owner = ent; other->r.solid = SOLID_TRIGGER; other->movetype = MOVETYPE_NONE; other->touch = Touch_DoorTrigger; other->s.team = ent->s.team; trap_LinkEntity(other); if( ent->spawnflags & DOOR_START_OPEN ) { door_use_areaportals( ent, qtrue ); } Think_CalcMoveSpeed(ent); } void door_blocked( edict_t *self, edict_t *other ) { edict_t *ent; if( !(other->r.svflags & SVF_MONSTER) && (!other->r.client) ) { // give it a chance to go away on its 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 ) 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, edict_t *attacker, int damage, vec3_t point ) { edict_t *ent; for( ent = self->teammaster ; ent ; ent = ent->teamchain ) { ent->health = ent->max_health; if( ent->spawnflags & DOOR_DIE_ONCE ) ent->takedamage = DAMAGE_NO; } if( !self->s.team || self->s.team == attacker->s.team ) door_use( self->teammaster, attacker, attacker ); } void door_touch( edict_t *self, edict_t *other, cplane_t *plane, int surfFlags ) { if( !other->r.client ) return; if( level.time < self->touch_debounce_time ) return; self->touch_debounce_time = level.time + 5.0; G_CenterPrintMsg( other, "%s", self->message ); G_Sound( other, CHAN_AUTO, trap_SoundIndex (S_WORLD_MESSAGE), 1, ATTN_NORM ); } void SP_func_door( edict_t *ent ) { vec3_t abs_movedir; G_InitMover( ent ); G_SetMovedir( ent->s.angles, ent->movedir ); G_AssignMoverSounds( ent, S_DOOR_START, S_DOOR_MOVE, S_DOOR_STOP ); ent->blocked = door_blocked; ent->use = door_use; if( !ent->speed ) ent->speed = 600; if( !ent->accel ) ent->accel = ent->speed; if( !ent->decel ) ent->decel = ent->speed; if( !ent->wait ) ent->wait = 2; if( !st.lip ) st.lip = 8; if( !ent->dmg ) ent->dmg = 2; if( ent->health < 0 ) { ent->health = 0; } else if( !ent->health ) { ent->health = 1; } else ent->spawnflags |= DOOR_DIE_ONCE; // not used by the editor if( st.gameteam >= TEAM_SPECTATOR && st.gameteam < GS_MAX_TEAMS ) { ent->s.team = st.gameteam; } else { ent->s.team = TEAM_SPECTATOR; } // 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->r.size[0] + abs_movedir[1] * ent->r.size[1] + abs_movedir[2] * ent->r.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->max_health = ent->health; ent->takedamage = DAMAGE_YES; ent->die = door_killed; } else if( ent->targetname && ent->message ) { trap_SoundIndex( S_WORLD_MESSAGE ); // precache 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 ); // to simplify logic elsewhere, make non-teamed doors into a team of one if( !ent->team ) ent->teammaster = ent; trap_LinkEntity(ent); ent->nextthink = level.timemsec + game.framemsec; if( ent->targetname ) ent->think = Think_CalcMoveSpeed; else ent->think = Think_SpawnDoorTrigger; } /*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER - TOGGLE X_AXIS Y_AXIS Door which opens by rotating in one axis. You need to have an origin brush as part of this entity. -------- KEYS -------- message : is printed when the door is touched if it is a trigger door and it hasn't been fired yet distance : is how many degrees the door will be rotated. angle : determines the opening direction of door (up = -1, down = -2). speed : determines how fast the door moves (default 100). wait : wait before returning (3 default, -1 = never return) targetname : if set, a func_button or trigger is required to activate the door. target : fire entities with this targetname when activated health : if set, door must be shot open (default 0). dmg : damage to inflict on player when he blocks operation of door (default 2). Door will reverse direction when blocked unless CRUSHER spawnflag is set. team: assign the same team name to multiple doors that should operate together. noise_start : overrides default start moving sound noise : overrides default movement sound noise_stop : overrides default stop moving sound light : constantLight radius of .md3 model included with entity. Has no effect on the entity's brushes (default 0). color : constantLight color of .md3 model included with entity. Has no effect on the entity's brushes (default 1 1 1). model2 : path/name of model to include (eg: models/mapobjects/pipe/pipe02.md3). origin : alternate method of setting XYZ origin of .md3 model included with entity (See Notes). notsingle : when set to 1, entity will not spawn in Single Player mode notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) -------- SPAWNFLAGS -------- START_OPEN : &1 the door will spawn in the open state and operate in reverse. REVERSE : &2 will cause the door to rotate in the opposite direction. CRUSHER : &4 door will not reverse direction when blocked and will keep damaging player until he dies or gets out of the way. NOMONSTER : &8 monsters can't trigger this door. TOGGLE : &32 wait in both the start and end states for a trigger event X_AXIS : &64 rotate in the X axis Y_AXIS : &128 rotate in the Y axis -------- NOTES -------- The center of the origin brush determines 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. */ void SP_func_door_rotating( edict_t *ent ) { G_InitMover( ent ); 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 ) { if( developer->integer ) G_Printf( "%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->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; G_AssignMoverSounds( ent, S_DOOR_ROTATING_START, S_DOOR_ROTATING_MOVE, S_DOOR_ROTATING_STOP ); // 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 ) { trap_SoundIndex( S_WORLD_MESSAGE ); // precache 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 ); // to simplify logic elsewhere, make non-teamed doors into a team of one if( !ent->team ) ent->teammaster = ent; trap_LinkEntity(ent); ent->nextthink = level.timemsec + game.framemsec; 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. -------- KEYS -------- angle : determines the opening direction. Up or down only (up = -1, down = -2). speed : determines how fast it moves (default 25). wait : wait before returning (-1 default, -1 = TOGGLE) lip : lip remaining at end of move (default 0) targetname : set targetname so it can be targeted target : fire entities with this targetname when activated team: assign the same team name to multiple funcs that should operate together. noise_start : overrides default (silent) start moving sound noise : overrides default (silent) movement sound noise_stop : overrides default (silent) stop moving sound notsingle : when set to 1, entity will not spawn in Single Player mode notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) -------- SPAWNFLAGS -------- START_OPEN : &1 Causes the water to move to its destination when spawned and operate in reverse -------- NOTES -------- Use a non-water texture at your own risk. */ void SP_func_water( edict_t *self ) { vec3_t abs_movedir; G_InitMover( self ); // 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->r.size[0] + abs_movedir[1] * self->r.size[1] + abs_movedir[2] * self->r.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"; //sounds G_AssignMoverSounds( self, NULL, NULL, NULL ); trap_LinkEntity(self); } #define TRAIN_START_ON 1 #define TRAIN_TOGGLE 2 #define TRAIN_BLOCK_STOPS 4 /*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. -------- KEYS -------- speed : speed of displacement of train (default 100). dmg : default 2 target : point to first entity in the path targetname : train's targetname noise_start : start moving sound (default silent) noise : movement sound (default silent) noise_stop : stop moving sound (default silent) model2 : path/name of model to include (eg: models/mapobjects/pipe/pipe02.md3). origin : alternate method of setting XYZ origin of the train's brush(es) and .md3 model included with entity (See Notes). light : constantLight radius of .md3 model included with entity. Has no effect on the entity's brushes (default 0). color : constantLight color of .md3 model included with entity. Has no effect on the entity's brushes (default 1 1 1). notsingle : when set to 1, entity will not spawn in Single Player mode notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) -------- SPAWNFLAGS -------- START_ON : &1 TOGGLE : &2 BLOCK_STOPS : &4 -------- NOTES -------- Setting the origin key is simply an alternate method to using an origin brush. When using the model2 key, the origin point of the model will correspond to the origin point defined by either the origin brush or the origin coordinate value.*/ void train_next( edict_t *self ); void train_blocked( edict_t *self, edict_t *other ) { if( !(other->r.svflags & SVF_MONSTER) && (!other->r.client) ) { // give it a chance to go away on its 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 ) 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->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->r.inuse ) return; } if( self->moveinfo.wait ) { if( self->moveinfo.wait > 0 ) { self->nextthink = level.timemsec + (self->moveinfo.wait * 1000); self->think = train_next; } else if( self->spawnflags & TRAIN_TOGGLE ) // && wait < 0 { train_next(self); self->spawnflags &= ~TRAIN_START_ON; VectorClear( self->velocity ); self->nextthink = 0; } if( !(self->flags & FL_TEAMSLAVE) ) { if( self->moveinfo.sound_end ) G_Sound( self, CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC ); self->s.sound = 0; } } else { train_next(self); } } void train_next( edict_t *self ) { edict_t *ent; vec3_t dest; qboolean first; first = qtrue; again: if( !self->target ) { // G_Printf ("train_next: no next target\n"); return; } ent = G_PickTarget( self->target ); if( !ent ) { if( developer->integer ) G_Printf( "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 ) { if( developer->integer ) G_Printf( "connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin) ); return; } first = qfalse; VectorSubtract( ent->s.origin, self->r.mins, self->s.origin ); VectorCopy( self->s.origin, self->s.old_origin ); trap_LinkEntity(self); G_AddEvent( self, EV_TELEPORT, 0, qtrue ); goto again; } self->moveinfo.wait = ent->wait; self->target_ent = ent; if( !(self->flags & FL_TEAMSLAVE) ) { if( self->moveinfo.sound_start ) G_Sound( self, CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC ); self->s.sound = self->moveinfo.sound_middle; } VectorSubtract( ent->s.origin, self->r.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; ent = self->target_ent; VectorSubtract( ent->s.origin, self->r.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->target ) { if( developer->integer ) G_Printf( "train_find: no target\n" ); return; } ent = G_PickTarget( self->target ); if( !ent ) { if( developer->integer ) G_Printf( "train_find: target %s not found\n", self->target ); return; } self->target = ent->target; VectorSubtract( ent->s.origin, self->r.mins, self->s.origin ); trap_LinkEntity(self); // if not triggered, start immediately if( !self->targetname ) self->spawnflags |= TRAIN_START_ON; if( self->spawnflags & TRAIN_START_ON ) { self->nextthink = level.timemsec + game.framemsec; self->think = train_next; self->activator = self; } } void train_use( edict_t *self, edict_t *other, edict_t *activator ) { 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 ) { G_InitMover( self ); VectorClear( self->s.angles ); self->blocked = train_blocked; if( self->spawnflags & TRAIN_BLOCK_STOPS ) self->dmg = 0; else { if( !self->dmg ) self->dmg = 100; } G_AssignMoverSounds( self, NULL, NULL, NULL ); if( !self->speed ) self->speed = 100; self->moveinfo.speed = self->speed; self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed; self->use = train_use; trap_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.timemsec + game.framemsec; self->think = func_train_find; } else { if( developer->integer ) G_Printf( "func_train without a target at %s\n", vtos(self->r.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 ) { edict_t *target; if( self->movetarget->nextthink ) { // G_Printf( "elevator busy\n" ); return; } if( !other->pathtarget ) { if( developer->integer ) G_Printf( "elevator used with no pathtarget\n" ); return; } target = G_PickTarget( other->pathtarget ); if( !target ) { if( developer->integer ) G_Printf( "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->target ) { if( developer->integer ) G_Printf( "trigger_elevator has no target\n" ); return; } self->movetarget = G_PickTarget( self->target ); if( !self->movetarget ) { if( developer->integer ) G_Printf( "trigger_elevator unable to find target %s\n", self->target ); return; } if( Q_stricmp(self->movetarget->classname, "func_train") ) { if( developer->integer ) G_Printf( "trigger_elevator target %s is not a train\n", self->target ); return; } self->use = trigger_elevator_use; self->r.svflags = SVF_NOCLIENT; } void SP_trigger_elevator( edict_t *self ) { self->think = trigger_elevator_init; self->nextthink = level.timemsec + game.framemsec; } /*QUAKED func_timer (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON Time delay trigger that will continuously fire its targets after a preset time delay. When triggered, the timer will toggle on/off. -------- KEYS -------- wait : delay in seconds between each triggering of its targets (default 1). random : random time variance in seconds added or subtracted from "wait" delay (default 0 - see Notes). 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 target : this points to the entities to trigger. targetname : a func_button or trigger that points to this will toggle the timer on/off when activated. notsingle : when set to 1, entity will not spawn in Single Player mode notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) -------- SPAWNFLAGS -------- START_ON : &1 timer will start on in the game and continuously fire its targets. -------- NOTES -------- When the random key is set, its value is used to calculate a minimum and a maximum delay. The final time delay will be a random value anywhere between the minimum and maximum values: (min delay = wait - random) (max delay = wait + random).*/ void func_timer_think( edict_t *self ) { G_UseTargets( self, self->activator ); self->nextthink = level.timemsec + (self->wait * 1000) + ( crandom() * self->random * 1000 ); } void func_timer_use( edict_t *self, edict_t *other, edict_t *activator ) { self->activator = activator; // if on, turn it off if( self->nextthink ) { self->nextthink = 0; return; } // turn it on if( self->delay ) self->nextthink = level.timemsec + ( self->delay * 1000 ); else func_timer_think( self ); } void SP_func_timer( edict_t *self ) { if( !self->random ) self->random = 1.0; 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; if( developer->integer ) G_Printf( "func_timer at %s has random >= wait\n", vtos(self->s.origin) ); } if( self->spawnflags & 1 ) { self->nextthink = level.timemsec + 1000 + ( st.pausetime * 1000 ) + ( self->delay * 1000 ) + ( self->wait * 1000 ) + ( crandom() * self->random * 1000 ); self->activator = self; } self->r.svflags = SVF_NOCLIENT; } /*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE Conveyors are stationary brushes that move what's on them. -------- KEYS -------- speed : speed of displacement (default 100). target : targetname to activate when used targetname : to be targeted with noise_start : start moving sound (default silent) noise : movement sound (default silent) noise_stop : stop moving sound (default silent) notsingle : when set to 1, entity will not spawn in Single Player mode notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) -------- SPAWNFLAGS -------- START_ON : &1 start activated TOGGLE : &2 must be triggered again to toogle it's state -------- NOTES -------- The brush should be have a surface with at least one current content enabled.*/ void func_conveyor_use( edict_t *self, edict_t *other, edict_t *activator ) { 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 ) { G_InitMover(self); if( !self->speed ) self->speed = 100; if( !(self->spawnflags & 1) ) { self->count = self->speed; self->speed = 0; } self->use = func_conveyor_use; G_AssignMoverSounds( self, NULL, NULL, NULL ); } /*QUAKED func_door_secret (0 .5 .8) ? ALWAYS_SHOOT 1ST_LEFT 1ST_DOWN A secret door. Slide back and then to the side. -------- KEYS -------- 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 of door (up = -1, down = -2). speed : determines how fast the door moves (default 50). wait : number of seconds before door returns (5 default, -1 = never return) targetname : if set, a func_button or trigger is required to activate the door. target : fire entities with this targetname when activated health : (default 0) if set to any non-zero value, the button must take damage (any amount) to activate. dmg : damage to inflict on player when he blocks operation of door (default 2). Door will reverse direction when blocked unless CRUSHER spawnflag is set. team: assign the same team name to multiple doors that should operate together (see Notes). noise_start : overrides default start moving sound noise : overrides default movement sound noise_stop : overrides default stop moving sound light : constantLight radius of .md3 model included with entity. Has no effect on the entity's brushes (default 0). color : constantLight color of .md3 model included with entity. Has no effect on the entity's brushes (default 1 1 1). model2 : path/name of model to include (eg: models/mapobjects/pipe/pipe02.md3). origin : alternate method of setting XYZ origin of .md3 model included with entity (See Notes). notsingle : when set to 1, entity will not spawn in Single Player mode notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) -------- SPAWNFLAGS -------- ALWAYS_SHOOT : &1 door is shooteable even if targeted 1ST_LEFT : &2 1st move is left of arrow 1ST_DOWN : &4 1st move is down from arrow -------- NOTES -------- Slide back and then to the side. */ #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, edict_t *activator ) { // 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, qtrue ); } void door_secret_move1( edict_t *self ) { self->nextthink = level.timemsec + 1000; self->think = door_secret_move2; } void door_secret_move2( edict_t *self ) { Move_Calc( self, self->pos2, door_secret_move3 ); } void door_secret_move3( edict_t *self ) { if (self->wait == -1) return; self->nextthink = level.timemsec + ( self->wait * 1000 ); self->think = door_secret_move4; } void door_secret_move4( edict_t *self ) { Move_Calc( self, self->pos1, door_secret_move5 ); } void door_secret_move5( edict_t *self ) { self->nextthink = level.timemsec + 1000; self->think = door_secret_move6; } void door_secret_move6( edict_t *self ) { Move_Calc( self, vec3_origin, door_secret_done ); } void door_secret_done( edict_t *self ) { if( !(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT) ) { self->health = 0; self->takedamage = DAMAGE_YES; } door_use_areaportals( self, qfalse ); } void door_secret_blocked( edict_t *self, edict_t *other ) { if( !(other->r.svflags & SVF_MONSTER) && (!other->r.client) ) { // give it a chance to go away on its 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 ) 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, edict_t *attacker, int damage, vec3_t point ) { 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; G_InitMover(ent); G_SetMovedir( ent->s.angles, ent->movedir ); G_AssignMoverSounds( ent, NULL, NULL, NULL ); 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->r.size) ); else width = fabs( DotProduct(right, ent->r.size) ); length = fabs( DotProduct(forward, ent->r.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 ) { trap_SoundIndex( S_WORLD_MESSAGE ); // precache ent->touch = door_touch; } ent->classname = "func_door"; trap_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, edict_t *activator ) { KillBox(self); } void SP_func_killbox( edict_t *ent ) { ent->use = use_killbox; ent->r.svflags = SVF_NOCLIENT; } /*QUAKED func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS Solid entity that oscillates back and forth in a linear motion. Entity bobs on the Z axis (up-down) by default. It can also emit sound if the "noise" key is set. Will crush the player when blocked. -------- KEYS -------- speed : amount of time in seconds for one complete oscillation cycle (default 4). height : sets the amount of travel of the oscillation movement (default 32). phase : sets the start offset of the oscillation cycle. Values must be 0 < phase < 1. Any integer phase value is the same as no offset (default 0). noise_start : start moving sound (default is silent) noise : path/name of .wav file to play (default is silent). Use looping sounds only (eg. sounds/world/drone6.wav - See Notes). noise_stop : stop moving sound (default is silent) model2 : path/name of model to include. origin : alternate method of setting XYZ origin of sound and .md3 model included with entity (See Notes). light : constantLight radius of .md3 model included with entity. Has no effect on the entity's brushes (default 0). color : constantLight color of .md3 model included with entity. Has no effect on the entity's brushes (default 1 1 1). notsingle : when set to 1, entity will not spawn in Single Player mode notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) -------- SPAWNFLAGS -------- X_AXIS : &1 entity will bob along the X axis. Y_AXIS : &2 entity will bob along the Y axis. -------- NOTES -------- In order for the sound to be emitted from the entity, it is recommended to include a brush with an origin shader at its center, otherwise the sound will not follow the entity as it moves. Setting the origin key is simply an alternate method to using an origin brush. When using the model2 key, the origin point of the model will correspond to the origin point defined by either the origin brush or the origin coordinate value. Start and stop sounds will only be played if the entity is set to be triggered*/ //================ //func_bobbing_blocked //================ void func_bobbing_blocked( edict_t *self, edict_t *other ) { T_Damage( other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH ); } //================ //func_bobbing_use //================ void func_bobbing_use( edict_t *self, edict_t *other, edict_t *activator ) { if( self->flags & FL_TEAMSLAVE ) { G_UseTargets( self->teammaster, activator ); return; } self->activator = activator; G_UseTargets( self, self->activator ); } //================ //func_bobbing_think //================ void func_bobbing_think( edict_t *ent ) { float delta; float phase; delta = ( level.time - ent->speed * ent->moveinfo.phase ) / ent->speed; delta = delta - (int)delta; phase = sin( delta * M_TWOPI ); VectorMA( ent->moveinfo.start_origin, phase, ent->moveinfo.dir, ent->velocity ); VectorSubtract( ent->velocity, ent->s.origin, ent->velocity ); ent->nextthink = level.timemsec + game.framemsec; } //================ //SP_func_bobbing //================ void SP_func_bobbing( edict_t *ent ) { G_InitMover(ent); if ( !ent->speed ) ent->speed = 4; if ( !ent->dmg ) ent->dmg = 2; if ( !st.height ) st.height = 32; ent->moveinfo.phase = st.phase; VectorClear( ent->moveinfo.dir ); // set the axis of bobbing if( ent->spawnflags & 1 ) ent->moveinfo.dir[0] = st.height; else if ( ent->spawnflags & 2 ) ent->moveinfo.dir[1] = st.height; else ent->moveinfo.dir[2] = st.height; VectorClear( ent->s.angles ); VectorClear( ent->velocity ); VectorCopy( ent->s.origin, ent->moveinfo.start_origin ); ent->think = func_bobbing_think; ent->nextthink = level.timemsec + game.framemsec; ent->blocked = func_bobbing_blocked; ent->use = func_bobbing_use; G_AssignMoverSounds( ent, NULL, NULL, NULL ); trap_LinkEntity(ent); } //=============================================================================== // //PENDULUM // //=============================================================================== //================ //func_pendulum_blocked //================ void func_pendulum_blocked( edict_t *self, edict_t *other ) { T_Damage( other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH ); } //================ //func_pendulum_use //================ void func_pendulum_use( edict_t *self, edict_t *other, edict_t *activator ) { if( self->flags & FL_TEAMSLAVE ) { G_UseTargets( self->teammaster, activator ); return; } self->activator = activator; G_UseTargets( self, self->activator ); } //================ //func_pendulum_think //================ void func_pendulum_think( edict_t *ent ) { float delta; float phase; delta = ( level.time + ent->moveinfo.wait ) * ent->moveinfo.phase; delta = delta - (int)delta; phase = sin( delta * M_TWOPI ); VectorMA( ent->moveinfo.start_angles, phase, ent->moveinfo.dir, ent->avelocity ); VectorSubtract( ent->avelocity, ent->s.angles, ent->avelocity ); ent->nextthink = level.timemsec + game.framemsec; } /*QUAKED func_pendulum (0 .5 .8) ? You need to have an origin brush as part of this entity. Solid entity that describes a pendulum back and forth rotation movement. Rotates on the X axis by default. Pendulum frequency is a physical constant based on the length of the beam and gravity. Blocking the pendulum instantly kills a player. -------- KEYS -------- angle : angle offset of axis of rotation from default X axis (default 0). speed : angle of swing arc in either direction from initial position (default 30). phase : sets the start offset of the swinging cycle. Values must be 0 < phase < 1. Any integer phase value is the same as no offset (default 0). noise_start : start moving sound to be played only if triggered (default is silent) noise : path/name of .wav file to play. Use looping sounds only (default is silent). noise_stop : stop moving sound to be played only if triggered (default is silent) model2 : path/name of model to include (eg: models/mapobjects/jets/jets01.md3). origin : alternate method of setting XYZ origin of entity's rotation axis and .md3 model included with entity (default "0 0 0" - See Notes). light : constantLight radius of .md3 model included with entity. Has no effect on the entity's brushes (default 0). color : constantLight color of .md3 model included with entity. Has no effect on the entity's brushes (default 1 1 1). notsingle : when set to 1, entity will not spawn in Single Player mode notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) -------- NOTES -------- You need to have an origin brush as part of this entity. The center of that brush will be the point through which the rotation axis passes. Setting the origin key is simply an alternate method to using an origin brush. Pendulum will rotate along the X axis by default. Very crude operation: pendulum cannot rotate along Z axis, the speed of swing (frequency) is not adjustable. When using the model2 key, the origin point of the model will correspond to the origin point defined by either the origin brush or the origin coordinate value. Pendulums always swing north / south on unrotated models. Add an angles field to the model to allow rotation in other directions. Pendulum frequency is a physical constant based on the length of the beam and gravity.*/ void SP_func_pendulum( edict_t *ent ) { float freq; float length; G_InitMover(ent); if( !ent->speed ) ent->speed = 30; if( !ent->dmg ) ent->dmg = 2; // find pendulum length length = fabs ( ent->r.mins[2] ); if( length < 8 ) { length = 8; } freq = 1 / ( M_PI * 2 ) * sqrt( g_gravity->value / ( 3 * length ) ); VectorCopy( ent->s.angles, ent->moveinfo.start_angles ); VectorClear( ent->moveinfo.dir ); ent->moveinfo.phase = freq; ent->moveinfo.wait = st.phase / ent->moveinfo.phase; ent->moveinfo.dir[2] = ent->speed; ent->think = func_pendulum_think; ent->nextthink = level.timemsec + game.framemsec; ent->blocked = func_pendulum_blocked; ent->use = func_pendulum_use; G_AssignMoverSounds( ent, NULL, NULL, NULL ); trap_LinkEntity(ent); }