📄 g_mover.c
字号:
ent->s.pos.trType = TR_LINEAR_STOP;
break;
}
BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin );
trap_LinkEntity( ent );
}
/*
================
MatchTeam
All entities in a mover team will move from pos1 to pos2
in the same amount of time
================
*/
void MatchTeam( gentity_t *teamLeader, int moverState, int time ) {
gentity_t *slave;
for ( slave = teamLeader ; slave ; slave = slave->teamchain ) {
SetMoverState( slave, moverState, time );
}
}
/*
================
ReturnToPos1
================
*/
void ReturnToPos1( gentity_t *ent ) {
MatchTeam( ent, MOVER_2TO1, level.time );
// looping sound
ent->s.loopSound = ent->soundLoop;
// starting sound
if ( ent->sound2to1 ) {
G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 );
}
}
/*
================
Reached_BinaryMover
================
*/
void Reached_BinaryMover( gentity_t *ent ) {
// stop the looping sound
ent->s.loopSound = ent->soundLoop;
if ( ent->moverState == MOVER_1TO2 ) {
// reached pos2
SetMoverState( ent, MOVER_POS2, level.time );
// play sound
if ( ent->soundPos2 ) {
G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 );
}
// return to pos1 after a delay
ent->think = ReturnToPos1;
ent->nextthink = level.time + ent->wait;
// fire targets
if ( !ent->activator ) {
ent->activator = ent;
}
G_UseTargets( ent, ent->activator );
} else if ( ent->moverState == MOVER_2TO1 ) {
// reached pos1
SetMoverState( ent, MOVER_POS1, level.time );
// play sound
if ( ent->soundPos1 ) {
G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 );
}
// close areaportals
if ( ent->teammaster == ent || !ent->teammaster ) {
trap_AdjustAreaPortalState( ent, qfalse );
}
} else {
G_Error( "Reached_BinaryMover: bad moverState" );
}
}
/*
================
Use_BinaryMover
================
*/
void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
int total;
int partial;
// only the master should be used
if ( ent->flags & FL_TEAMSLAVE ) {
Use_BinaryMover( ent->teammaster, other, activator );
return;
}
ent->activator = activator;
if ( ent->moverState == MOVER_POS1 ) {
// start moving 50 msec later, becase if this was player
// triggered, level.time hasn't been advanced yet
MatchTeam( ent, MOVER_1TO2, level.time + 50 );
// starting sound
if ( ent->sound1to2 ) {
G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 );
}
// looping sound
ent->s.loopSound = ent->soundLoop;
// open areaportal
if ( ent->teammaster == ent || !ent->teammaster ) {
trap_AdjustAreaPortalState( ent, qtrue );
}
return;
}
// if all the way up, just delay before coming down
if ( ent->moverState == MOVER_POS2 ) {
ent->nextthink = level.time + ent->wait;
return;
}
// only partway down before reversing
if ( ent->moverState == MOVER_2TO1 ) {
total = ent->s.pos.trDuration;
partial = level.time - ent->s.pos.trTime;
if ( partial > total ) {
partial = total;
}
MatchTeam( ent, MOVER_1TO2, level.time - ( total - partial ) );
if ( ent->sound1to2 ) {
G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 );
}
return;
}
// only partway up before reversing
if ( ent->moverState == MOVER_1TO2 ) {
total = ent->s.pos.trDuration;
partial = level.time - ent->s.pos.trTime;
if ( partial > total ) {
partial = total;
}
MatchTeam( ent, MOVER_2TO1, level.time - ( total - partial ) );
if ( ent->sound2to1 ) {
G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 );
}
return;
}
}
/*
================
InitMover
"pos1", "pos2", and "speed" should be set before calling,
so the movement delta can be calculated
================
*/
void InitMover( gentity_t *ent ) {
vec3_t move;
float distance;
float light;
vec3_t color;
qboolean lightSet, colorSet;
char *sound;
// if the "model2" key is set, use a seperate model
// for drawing, but clip against the brushes
if ( ent->model2 ) {
ent->s.modelindex2 = G_ModelIndex( ent->model2 );
}
// if the "loopsound" key is set, use a constant looping sound when moving
if ( G_SpawnString( "noise", "100", &sound ) ) {
ent->s.loopSound = G_SoundIndex( sound );
}
// if the "color" or "light" keys are set, setup constantLight
lightSet = G_SpawnFloat( "light", "100", &light );
colorSet = G_SpawnVector( "color", "1 1 1", color );
if ( lightSet || colorSet ) {
int r, g, b, i;
r = color[0] * 255;
if ( r > 255 ) {
r = 255;
}
g = color[1] * 255;
if ( g > 255 ) {
g = 255;
}
b = color[2] * 255;
if ( b > 255 ) {
b = 255;
}
i = light / 4;
if ( i > 255 ) {
i = 255;
}
ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
}
ent->use = Use_BinaryMover;
ent->reached = Reached_BinaryMover;
ent->moverState = MOVER_POS1;
ent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
ent->s.eType = ET_MOVER;
VectorCopy (ent->pos1, ent->r.currentOrigin);
trap_LinkEntity (ent);
ent->s.pos.trType = TR_STATIONARY;
VectorCopy( ent->pos1, ent->s.pos.trBase );
// calculate time to reach second position from speed
VectorSubtract( ent->pos2, ent->pos1, move );
distance = VectorLength( move );
if ( ! ent->speed ) {
ent->speed = 100;
}
VectorScale( move, ent->speed, ent->s.pos.trDelta );
ent->s.pos.trDuration = distance * 1000 / ent->speed;
if ( ent->s.pos.trDuration <= 0 ) {
ent->s.pos.trDuration = 1;
}
}
/*
===============================================================================
DOOR
A use can be triggered either by a touch function, by being shot, or by being
targeted by another entity.
===============================================================================
*/
/*
================
Blocked_Door
================
*/
void Blocked_Door( gentity_t *ent, gentity_t *other ) {
// remove anything other than a client
if ( !other->client ) {
// except CTF flags!!!!
if( other->s.eType == ET_ITEM && other->item->giType == IT_TEAM ) {
Team_DroppedFlagThink( other );
return;
}
G_TempEntity( other->s.origin, EV_ITEM_POP );
G_FreeEntity( other );
return;
}
if ( ent->damage ) {
G_Damage( other, ent, ent, NULL, NULL, ent->damage, 0, MOD_CRUSH );
}
if ( ent->spawnflags & 4 ) {
return; // crushers don't reverse
}
// reverse direction
Use_BinaryMover( ent, ent, other );
}
/*
================
Touch_DoorTriggerSpectator
================
*/
static void Touch_DoorTriggerSpectator( gentity_t *ent, gentity_t *other, trace_t *trace ) {
int i, axis;
vec3_t origin, dir, angles;
axis = ent->count;
VectorClear(dir);
if (fabs(other->s.origin[axis] - ent->r.absmax[axis]) <
fabs(other->s.origin[axis] - ent->r.absmin[axis])) {
origin[axis] = ent->r.absmin[axis] - 10;
dir[axis] = -1;
}
else {
origin[axis] = ent->r.absmax[axis] + 10;
dir[axis] = 1;
}
for (i = 0; i < 3; i++) {
if (i == axis) continue;
origin[i] = (ent->r.absmin[i] + ent->r.absmax[i]) * 0.5;
}
vectoangles(dir, angles);
TeleportPlayer(other, origin, angles );
}
/*
================
Touch_DoorTrigger
================
*/
void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace ) {
if ( other->client && other->client->sess.sessionTeam == TEAM_SPECTATOR ) {
// if the door is not open and not opening
if ( ent->parent->moverState != MOVER_1TO2 &&
ent->parent->moverState != MOVER_POS2) {
Touch_DoorTriggerSpectator( ent, other, trace );
}
}
else if ( ent->parent->moverState != MOVER_1TO2 ) {
Use_BinaryMover( ent->parent, ent, other );
}
}
/*
======================
Think_SpawnNewDoorTrigger
All of the parts of a door have been spawned, so create
a trigger that encloses all of them
======================
*/
void Think_SpawnNewDoorTrigger( gentity_t *ent ) {
gentity_t *other;
vec3_t mins, maxs;
int i, best;
// set all of the slaves as shootable
for ( other = ent ; other ; other = other->teamchain ) {
other->takedamage = qtrue;
}
// find the bounds of everything on the team
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);
}
// find the thinnest axis, which will be the one we expand
best = 0;
for ( i = 1 ; i < 3 ; i++ ) {
if ( maxs[i] - mins[i] < maxs[best] - mins[best] ) {
best = i;
}
}
maxs[best] += 120;
mins[best] -= 120;
// create a trigger with this size
other = G_Spawn ();
other->classname = "door_trigger";
VectorCopy (mins, other->r.mins);
VectorCopy (maxs, other->r.maxs);
other->parent = ent;
other->r.contents = CONTENTS_TRIGGER;
other->touch = Touch_DoorTrigger;
// remember the thinnest axis
other->count = best;
trap_LinkEntity (other);
MatchTeam( ent, ent->moverState, level.time );
}
void Think_MatchTeam( gentity_t *ent ) {
MatchTeam( ent, ent->moverState, level.time );
}
/*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER
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
"model2" .md3 model to also draw
"angle" determines the opening direction
"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
"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)
"color" constantLight color
"light" constantLight radius
"health" if set, the door must be shot open
*/
void SP_func_door (gentity_t *ent) {
vec3_t abs_movedir;
float distance;
vec3_t size;
float lip;
ent->sound1to2 = ent->sound2to1 = G_SoundIndex("sound/movers/doors/dr1_strt.wav");
ent->soundPos1 = ent->soundPos2 = G_SoundIndex("sound/movers/doors/dr1_end.wav");
ent->blocked = Blocked_Door;
// default speed of 400
if (!ent->speed)
ent->speed = 400;
// default wait of 2 seconds
if (!ent->wait)
ent->wait = 2;
ent->wait *= 1000;
// default lip of 8 units
G_SpawnFloat( "lip", "8", &lip );
// default damage of 2 points
G_SpawnInt( "dmg", "2", &ent->damage );
// first position at start
VectorCopy( ent->s.origin, ent->pos1 );
// calculate second position
trap_SetBrushModel( ent, ent->model );
G_SetMovedir (ent->s.angles, ent->movedir);
abs_movedir[0] = fabs(ent->movedir[0]);
abs_movedir[1] = fabs(ent->movedir[1]);
abs_movedir[2] = fabs(ent->movedir[2]);
VectorSubtract( ent->r.maxs, ent->r.mins, size );
distance = DotProduct( abs_movedir, size ) - lip;
VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 );
// if "start_open", reverse position 1 and 2
if ( ent->spawnflags & 1 ) {
vec3_t temp;
VectorCopy( ent->pos2, temp );
VectorCopy( ent->s.origin, ent->pos2 );
VectorCopy( temp, ent->pos1 );
}
InitMover( ent );
ent->nextthink = level.time + FRAMETIME;
if ( ! (ent->flags & FL_TEAMSLAVE ) ) {
int health;
G_SpawnInt( "health", "0", &health );
if ( health ) {
ent->takedamage = qtrue;
}
if ( ent->targetname || health ) {
// non touch/shoot doors
ent->think = Think_MatchTeam;
} else {
ent->think = Think_SpawnNewDoorTrigger;
}
}
}
/*
===============================================================================
PLAT
===============================================================================
*/
/*
==============
Touch_Plat
Don't allow decent if a living player is on it
===============
*/
void Touch_Plat( gentity_t *ent, gentity_t *other, trace_t *trace ) {
if ( !other->client || other->client->ps.stats[STAT_HEALTH] <= 0 ) {
return;
}
// delay return-to-pos1 by one second
if ( ent->moverState == MOVER_POS2 ) {
ent->nextthink = level.time + 1000;
}
}
/*
==============
Touch_PlatCenterTrigger
If the plat is at the bottom position, start it going up
===============
*/
void Touch_PlatCenterTrigger(gentity_t *ent, gentity_t *other, trace_t *trace ) {
if ( !other->client ) {
return;
}
if ( ent->parent->moverState == MOVER_POS1 ) {
Use_BinaryMover( ent->parent, ent, other );
}
}
/*
================
SpawnPlatTrigger
Spawn a trigger in the middle of the plat's low position
Elevator cars require that the trigger extend through the entire low position,
not just sit on top of it.
================
*/
void SpawnPlatTrigger( gentity_t *ent ) {
gentity_t *trigger;
vec3_t tmin, tmax;
// the middle trigger will be a thin trigger just
// above the starting position
trigger = G_Spawn();
trigger->classname = "plat_trigger";
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -