/* * AI_FlagsForNode * check the world and set up node flags */ int AI_FlagsForNode( vec3_t origin, edict_t *passent ) { trace_t trace; int flagsmask = 0; int contents; contents = G_PointContents( origin ); //water if( contents & MASK_WATER ) flagsmask |= NODEFLAGS_WATER; if( contents & CONTENTS_DONOTENTER ) flagsmask |= NODEFLAGS_DONOTENTER; //floor G_Trace( &trace, origin, tv( -15, -15, 0 ), tv( 15, 15, 0 ), tv( origin[0], origin[1], origin[2] - AI_JUMPABLE_HEIGHT ), passent, MASK_NODESOLID ); if( trace.fraction < 1.0 ) flagsmask &= ~NODEFLAGS_FLOAT; //ok, it wasn't set, I know... else flagsmask |= NODEFLAGS_FLOAT; //ladder // G_Trace( &trace, origin, tv(-18, -18, -16), tv(18, 18, 16), origin, passent, MASK_ALL ); // if( trace.startsolid && trace.contents & CONTENTS_LADDER ) // flagsmask |= NODEFLAGS_LADDER; return flagsmask; }
//========================================== // BOT_DMclass_PredictProjectileShot // predict target movement //========================================== static void BOT_DMclass_PredictProjectileShot( edict_t *self, vec3_t fire_origin, float projectile_speed, vec3_t target, vec3_t target_velocity ) { vec3_t predictedTarget; vec3_t targetMovedir; float targetSpeed; float predictionTime; float distance; trace_t trace; int contents; if( projectile_speed <= 0.0f ) return; targetSpeed = VectorNormalize2( target_velocity, targetMovedir ); // ok, this is not going to be 100% precise, since we will find the // time our projectile will take to travel to enemy's CURRENT position, // and them find enemy's position given his CURRENT velocity and his CURRENT dir // after prediction time. The result will be much better if the player // is moving to the sides (relative to us) than in depth (relative to us). // And, of course, when the player moves in a curve upwards it will totally miss (ie, jumping). // but in general it does a great job, much better than any human player :) distance = DistanceFast( fire_origin, target ); predictionTime = distance/projectile_speed; VectorMA( target, predictionTime*targetSpeed, targetMovedir, predictedTarget ); // if this position is inside solid, try finding a position at half of the prediction time contents = G_PointContents( predictedTarget ); if( contents & CONTENTS_SOLID && !( contents & CONTENTS_PLAYERCLIP ) ) { VectorMA( target, ( predictionTime * 0.5f )*targetSpeed, targetMovedir, predictedTarget ); contents = G_PointContents( predictedTarget ); if( contents & CONTENTS_SOLID && !( contents & CONTENTS_PLAYERCLIP ) ) return; // INVALID } // if we can see this point, we use it, otherwise we keep the current position G_Trace( &trace, fire_origin, vec3_origin, vec3_origin, predictedTarget, self, MASK_SHOT ); if( trace.fraction == 1.0f || ( trace.ent && game.edicts[trace.ent].takedamage ) ) VectorCopy( predictedTarget, target ); }
void ThrowSmallPileOfGibs( edict_t *self, int damage ) { vec3_t origin; edict_t *event; int contents; int i; contents = G_PointContents( self->s.origin ); if( contents & CONTENTS_NODROP ) return; for( i = 0; i < 3; i++ ) origin[i] = self->s.origin[i] + ( 0.5f * ( self->r.maxs[i] + self->r.mins[i] ) ) + 24; event = G_SpawnEvent( EV_SPOG, damage, origin ); event->r.svflags |= SVF_TRANSMITORIGIN2; VectorCopy( self->velocity, event->s.origin2 ); }
void G_Gametype_GENERIC_PlayerKilled( edict_t *targ, edict_t *attacker, edict_t *inflictor ) { if( !attacker || GS_MatchState() != MATCH_STATE_PLAYTIME || ( targ->r.svflags & SVF_CORPSE ) ) return; if( !attacker->r.client || attacker == targ || attacker == world ) teamlist[targ->s.team].stats.score--; else { if( GS_InvidualGameType() ) teamlist[attacker->s.team].stats.score = attacker->r.client->level.stats.score; if( GS_IsTeamDamage( &targ->s, &attacker->s ) ) teamlist[attacker->s.team].stats.score--; else teamlist[attacker->s.team].stats.score++; } // drop items if( targ->r.client && !( G_PointContents( targ->s.origin ) & CONTENTS_NODROP ) ) { // drop the weapon if ( targ->r.client->ps.stats[STAT_WEAPON] > WEAP_GUNBLADE ) { gsitem_t *weaponItem = GS_FindItemByTag( targ->r.client->ps.stats[STAT_WEAPON] ); if( weaponItem ) { edict_t *drop = Drop_Item( targ, weaponItem ); if( drop ) { drop->count = targ->r.client->ps.inventory[ weaponItem->weakammo_tag ]; targ->r.client->ps.inventory[ weaponItem->weakammo_tag ] = 0; } } } // drop ammo pack (won't drop anything if player doesn't have any strong ammo) Drop_Item( targ, GS_FindItemByTag( AMMO_PACK ) ); } }
void BOT_DMclass_MoveWander( edict_t *self, usercmd_t *ucmd ) { vec3_t temp; if( self->deadflag ) return; if( !self->r.client->ps.pmove.stats[PM_STAT_MAXSPEED] ) { return; } // Special check for elevators, stand still until the ride comes to a complete stop. if( self->groundentity && self->groundentity->use == Use_Plat ) { if( self->groundentity->moveinfo.state != STATE_UP && self->groundentity->moveinfo.state != STATE_DOWN ) { self->velocity[0] = 0; self->velocity[1] = 0; self->velocity[2] = 0; return; } } // Move To Goal (Short Range Goal, not following paths) if( !AI_MoveToShortRangeGoalEntity( self, ucmd ) ) { // Swimming? VectorCopy( self->s.origin, temp ); temp[2] += 24; if( G_PointContents( temp ) & MASK_WATER ) { // If drowning and no node, move up if( self->r.client && self->r.client->resp.next_drown_time > 0 ) { ucmd->upmove = 1; self->s.angles[PITCH] = -45; } else ucmd->upmove = 1; ucmd->forwardmove = 1; } // else self->r.client->next_drown_time = 0; // probably shound not be messing with this, but // Lava? temp[2] -= 48; if( G_PointContents( temp ) & ( CONTENTS_LAVA|CONTENTS_SLIME ) ) { self->s.angles[YAW] += random() * 360 - 180; ucmd->forwardmove = 1; if( self->groundentity ) ucmd->upmove = 1; else ucmd->upmove = 0; return; } // Check for special movement if( VectorLengthFast( self->velocity ) < 37 ) { if( random() > 0.1 && AI_SpecialMove( self, ucmd ) ) //jumps, crouches, turns... return; self->s.angles[YAW] += random() * 180 - 90; if( !self->is_step ) // if there is ground continue otherwise wait for next move ucmd->forwardmove = 0; //0 else if( AI_CanMove( self, BOT_MOVE_FORWARD ) ) { ucmd->forwardmove = 1; ucmd->buttons |= BUTTON_WALK; } return; } // Otherwise move slowly, walking wondering what's going on ucmd->buttons |= BUTTON_WALK; } if( AI_CanMove( self, BOT_MOVE_FORWARD ) ) ucmd->forwardmove = 1; else ucmd->forwardmove = -1; }
void BOT_DMclass_Move( edict_t *self, usercmd_t *ucmd ) { #define BOT_FORWARD_EPSILON 0.5f int i; unsigned int linkType; bool printLink = false; bool nodeReached = false; bool specialMovement = false; vec3_t v1, v2; vec3_t lookdir, pathdir; float lookDot; if( self->ai->next_node == NODE_INVALID || self->ai->goal_node == NODE_INVALID ) { BOT_DMclass_MoveWander( self, ucmd ); return; } linkType = AI_CurrentLinkType( self ); specialMovement = ( self->ai->path.numNodes >= MIN_BUNNY_NODES ) ? true : false; if( AI_GetNodeFlags( self->ai->next_node ) & (NODEFLAGS_REACHATTOUCH|NODEFLAGS_ENTITYREACH) ) specialMovement = false; if( linkType & (LINK_JUMP|LINK_JUMPPAD|LINK_CROUCH|LINK_FALL|LINK_WATER|LINK_LADDER|LINK_ROCKETJUMP) ) specialMovement = false; if( self->ai->pers.skillLevel < 0.33f ) specialMovement = false; if( specialMovement == false || self->groundentity ) self->ai->is_bunnyhop = false; VectorSubtract( nodes[self->ai->next_node].origin, self->s.origin, self->ai->move_vector ); // 2D, normalized versions of look and path directions pathdir[0] = self->ai->move_vector[0]; pathdir[1] = self->ai->move_vector[1]; pathdir[2] = 0.0f; VectorNormalize( pathdir ); AngleVectors( self->s.angles, lookdir, NULL, NULL ); lookdir[2] = 0.0f; VectorNormalize( lookdir ); lookDot = DotProduct( lookdir, pathdir ); // Ladder movement if( self->is_ladder ) { ucmd->forwardmove = 0; ucmd->upmove = 1; ucmd->sidemove = 0; if( nav.debugMode && printLink ) G_PrintChasersf( self, "LINK_LADDER\n" ); nodeReached = AI_NodeReached_Generic( self ); } else if( linkType & LINK_JUMPPAD ) { VectorCopy( self->s.origin, v1 ); VectorCopy( nodes[self->ai->next_node].origin, v2 ); v1[2] = v2[2] = 0; if( DistanceFast( v1, v2 ) > 32 && lookDot > BOT_FORWARD_EPSILON ) { ucmd->forwardmove = 1; // push towards destination ucmd->buttons |= BUTTON_WALK; } nodeReached = self->groundentity != NULL && AI_NodeReached_Generic( self ); } // Platform riding - No move, riding elevator else if( linkType & LINK_PLATFORM ) { VectorCopy( self->s.origin, v1 ); VectorCopy( nodes[self->ai->next_node].origin, v2 ); v1[2] = v2[2] = 0; if( DistanceFast( v1, v2 ) > 32 && lookDot > BOT_FORWARD_EPSILON ) ucmd->forwardmove = 1; // walk to center ucmd->buttons |= BUTTON_WALK; ucmd->upmove = 0; ucmd->sidemove = 0; if( nav.debugMode && printLink ) G_PrintChasersf( self, "LINK_PLATFORM (riding)\n" ); self->ai->move_vector[2] = 0; // put view horizontal nodeReached = AI_NodeReached_PlatformEnd( self ); } // entering platform else if( AI_GetNodeFlags( self->ai->next_node ) & NODEFLAGS_PLATFORM ) { ucmd->forwardmove = 1; ucmd->upmove = 0; ucmd->sidemove = 0; if( lookDot <= BOT_FORWARD_EPSILON ) ucmd->buttons |= BUTTON_WALK; if( nav.debugMode && printLink ) G_PrintChasersf( self, "NODEFLAGS_PLATFORM (moving to plat)\n" ); // is lift down? for( i = 0; i < nav.num_navigableEnts; i++ ) { if( nav.navigableEnts[i].node == self->ai->next_node ) { //testing line //vec3_t tPoint; //int j; //for(j=0; j<3; j++)//center of the ent // tPoint[j] = nav.ents[i].ent->s.origin[j] + 0.5*(nav.ents[i].ent->r.mins[j] + nav.ents[i].ent->r.maxs[j]); //tPoint[2] = nav.ents[i].ent->s.origin[2] + nav.ents[i].ent->r.maxs[2]; //tPoint[2] += 8; //AITools_DrawLine( self->s.origin, tPoint ); //if not reachable, wait for it (only height matters) if( ( nav.navigableEnts[i].ent->s.origin[2] + nav.navigableEnts[i].ent->r.maxs[2] ) > ( self->s.origin[2] + self->r.mins[2] + AI_JUMPABLE_HEIGHT ) && nav.navigableEnts[i].ent->moveinfo.state != STATE_BOTTOM ) { self->ai->blocked_timeout = level.time + 10000; ucmd->forwardmove = 0; } } } nodeReached = AI_NodeReached_PlatformStart( self ); } // Falling off ledge or jumping else if( !self->groundentity && !self->is_step && !self->is_swim && !self->ai->is_bunnyhop ) { ucmd->upmove = 0; ucmd->sidemove = 0; ucmd->forwardmove = 0; if( lookDot > BOT_FORWARD_EPSILON ) { ucmd->forwardmove = 1; // add fake strafe accel if( !(linkType & LINK_FALL) || linkType & (LINK_JUMP|LINK_ROCKETJUMP) ) { if( linkType & LINK_JUMP ) { if( AI_AttemptWalljump( self ) ) { ucmd->buttons |= BUTTON_SPECIAL; } if( VectorLengthFast( tv( self->velocity[0], self->velocity[1], 0 ) ) < 600 ) VectorMA( self->velocity, 6.0f, lookdir, self->velocity ); } else { if( VectorLengthFast( tv( self->velocity[0], self->velocity[1], 0 ) ) < 450 ) VectorMA( self->velocity, 1.0f, lookdir, self->velocity ); } } } else if( lookDot < -BOT_FORWARD_EPSILON ) ucmd->forwardmove = -1; if( nav.debugMode && printLink ) G_PrintChasersf( self, "FLY MOVE\n" ); nodeReached = AI_NodeReached_Generic( self ); } else // standard movement { ucmd->forwardmove = 1; ucmd->upmove = 0; ucmd->sidemove = 0; // starting a jump if( ( linkType & LINK_JUMP ) ) { if( self->groundentity ) { trace_t trace; vec3_t v1, v2; if( nav.debugMode && printLink ) G_PrintChasersf( self, "LINK_JUMP\n" ); //check floor in front, if there's none... Jump! VectorCopy( self->s.origin, v1 ); VectorNormalize2( self->ai->move_vector, v2 ); VectorMA( v1, 18, v2, v1 ); v1[2] += self->r.mins[2]; VectorCopy( v1, v2 ); v2[2] -= AI_JUMPABLE_HEIGHT; G_Trace( &trace, v1, vec3_origin, vec3_origin, v2, self, MASK_AISOLID ); if( !trace.startsolid && trace.fraction == 1.0 ) { //jump! // prevent double jumping on crates VectorCopy( self->s.origin, v1 ); v1[2] += self->r.mins[2]; G_Trace( &trace, v1, tv( -12, -12, -8 ), tv( 12, 12, 0 ), v1, self, MASK_AISOLID ); if( trace.startsolid ) ucmd->upmove = 1; } } nodeReached = AI_NodeReached_Generic( self ); } // starting a rocket jump else if( ( linkType & LINK_ROCKETJUMP ) ) { if( nav.debugMode && printLink ) G_PrintChasersf( self, "LINK_ROCKETJUMP\n" ); if( !self->ai->rj_triggered && self->groundentity && ( self->s.weapon == WEAP_ROCKETLAUNCHER ) ) { self->s.angles[PITCH] = 170; ucmd->upmove = 1; ucmd->buttons |= BUTTON_ATTACK; self->ai->rj_triggered = true; } nodeReached = AI_NodeReached_Generic( self ); } else { // Move To Short Range goal (not following paths) // plats, grapple, etc have higher priority than SR Goals, cause the bot will // drop from them and have to repeat the process from the beginning if( AI_MoveToShortRangeGoalEntity( self, ucmd ) ) { nodeReached = AI_NodeReached_Generic( self ); } else if( specialMovement && !self->is_swim ) // bunny-hopping movement here { BOT_DMclass_SpecialMove( self, lookdir, pathdir, ucmd ); nodeReached = AI_NodeReached_Special( self ); } else { nodeReached = AI_NodeReached_Generic( self ); } } // if static assume blocked and try to get free if( VectorLengthFast( self->velocity ) < 37 && ( ucmd->forwardmove || ucmd->sidemove || ucmd->upmove ) ) { if( random() > 0.1 && AI_SpecialMove( self, ucmd ) ) // jumps, crouches, turns... return; self->s.angles[YAW] += brandom( -90, 90 ); } } // swimming if( self->is_swim ) { if( !( G_PointContents( nodes[self->ai->next_node].origin ) & MASK_WATER ) ) // Exit water ucmd->upmove = 1; } AI_ChangeAngle( self ); if( nodeReached ) AI_NodeReached( self ); #undef BOT_FORWARD_EPSILON }
/* * SV_Physics_Toss * * Toss, bounce, and fly movement. When onground, do nothing. * * FIXME: This function needs a serious rewrite */ static void SV_Physics_Toss( edict_t *ent ) { trace_t trace; vec3_t move; float backoff; edict_t *slave; bool wasinwater; bool isinwater; vec3_t old_origin; float oldSpeed; // if not a team captain, so movement will be handled elsewhere if( ent->flags & FL_TEAMSLAVE ) { return; } // refresh the ground entity if( ent->movetype == MOVETYPE_BOUNCE || ent->movetype == MOVETYPE_BOUNCEGRENADE ) { if( ent->velocity[2] > 0.1f ) { ent->groundentity = NULL; } } if( ent->groundentity && ent->groundentity != world && !ent->groundentity->r.inuse ) { ent->groundentity = NULL; } oldSpeed = VectorLength( ent->velocity ); if( ent->groundentity ) { if( !oldSpeed ) { return; } if( ent->movetype == MOVETYPE_TOSS ) { if( ent->velocity[2] >= 8 ) { ent->groundentity = NULL; } else { VectorClear( ent->velocity ); VectorClear( ent->avelocity ); G_CallStop( ent ); return; } } } VectorCopy( ent->s.origin, old_origin ); if( ent->accel != 0 ) { if( ent->accel < 0 && VectorLength( ent->velocity ) < 50 ) { VectorClear( ent->velocity ); } else { vec3_t acceldir; VectorNormalize2( ent->velocity, acceldir ); VectorScale( acceldir, ent->accel * FRAMETIME, acceldir ); VectorAdd( ent->velocity, acceldir, ent->velocity ); } } SV_CheckVelocity( ent ); // add gravity if( ent->movetype != MOVETYPE_FLY && !ent->groundentity ) { SV_AddGravity( ent ); } // move angles VectorMA( ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles ); // move origin VectorScale( ent->velocity, FRAMETIME, move ); trace = SV_PushEntity( ent, move ); if( !ent->r.inuse ) { return; } if( trace.fraction < 1.0f ) { if( ent->movetype == MOVETYPE_BOUNCE ) { backoff = 1.5; } else if( ent->movetype == MOVETYPE_BOUNCEGRENADE ) { backoff = 1.5; } else { backoff = 1; } GS_ClipVelocity( ent->velocity, trace.plane.normal, ent->velocity, backoff ); // stop if on ground if( ent->movetype == MOVETYPE_BOUNCE || ent->movetype == MOVETYPE_BOUNCEGRENADE ) { // stop dead on allsolid // LA: hopefully will fix grenades bouncing down slopes // method taken from Darkplaces sourcecode if( trace.allsolid || ( ISWALKABLEPLANE( &trace.plane ) && fabs( DotProduct( trace.plane.normal, ent->velocity ) ) < 40 ) ) { ent->groundentity = &game.edicts[trace.ent]; ent->groundentity_linkcount = ent->groundentity->linkcount; VectorClear( ent->velocity ); VectorClear( ent->avelocity ); G_CallStop( ent ); } } else { // in movetype_toss things stop dead when touching ground #if 0 G_CheckGround( ent ); if( ent->groundentity ) { #else // walkable or trapped inside solid brush if( trace.allsolid || ISWALKABLEPLANE( &trace.plane ) ) { ent->groundentity = trace.ent < 0 ? world : &game.edicts[trace.ent]; ent->groundentity_linkcount = ent->groundentity->linkcount; #endif VectorClear( ent->velocity ); VectorClear( ent->avelocity ); G_CallStop( ent ); } } } // check for water transition wasinwater = ( ent->watertype & MASK_WATER ) ? true : false; ent->watertype = G_PointContents( ent->s.origin ); isinwater = ent->watertype & MASK_WATER ? true : false; // never allow items in CONTENTS_NODROP if( ent->item && ( ent->watertype & CONTENTS_NODROP ) ) { G_FreeEdict( ent ); return; } if( isinwater ) { ent->waterlevel = 1; } else { ent->waterlevel = 0; } if( !wasinwater && isinwater ) { G_PositionedSound( old_origin, CHAN_AUTO, trap_SoundIndex( S_HIT_WATER ), ATTN_IDLE ); } else if( wasinwater && !isinwater ) { G_PositionedSound( ent->s.origin, CHAN_AUTO, trap_SoundIndex( S_HIT_WATER ), ATTN_IDLE ); } // move teamslaves for( slave = ent->teamchain; slave; slave = slave->teamchain ) { VectorCopy( ent->s.origin, slave->s.origin ); GClip_LinkEntity( slave ); } } //============================================================================ void SV_Physics_LinearProjectile( edict_t *ent ) { vec3_t start, end; int mask; float startFlyTime, endFlyTime; trace_t trace; int old_waterLevel; // if not a team captain movement will be handled elsewhere if( ent->flags & FL_TEAMSLAVE ) { return; } old_waterLevel = ent->waterlevel; mask = ( ent->r.clipmask ) ? ent->r.clipmask : MASK_SOLID; // find it's current position given the starting timeStamp startFlyTime = (float)( max( game.prevServerTime - ent->s.linearMovementTimeStamp, 0 ) ) * 0.001f; endFlyTime = (float)( game.serverTime - ent->s.linearMovementTimeStamp ) * 0.001f; VectorMA( ent->s.linearMovementBegin, startFlyTime, ent->s.linearMovementVelocity, start ); VectorMA( ent->s.linearMovementBegin, endFlyTime, ent->s.linearMovementVelocity, end ); G_Trace4D( &trace, start, ent->r.mins, ent->r.maxs, end, ent, mask, ent->timeDelta ); VectorCopy( trace.endpos, ent->s.origin ); GClip_LinkEntity( ent ); SV_Impact( ent, &trace ); if( !ent->r.inuse ) { // the projectile may be freed if touched something return; } // update some data required for the transmission //VectorCopy( ent->velocity, ent->s.linearMovementVelocity ); GClip_TouchTriggers( ent ); ent->groundentity = NULL; // projectiles never have ground entity ent->waterlevel = ( G_PointContents4D( ent->s.origin, ent->timeDelta ) & MASK_WATER ) ? true : false; if( !old_waterLevel && ent->waterlevel ) { G_PositionedSound( start, CHAN_AUTO, trap_SoundIndex( S_HIT_WATER ), ATTN_IDLE ); } else if( old_waterLevel && !ent->waterlevel ) { G_PositionedSound( ent->s.origin, CHAN_AUTO, trap_SoundIndex( S_HIT_WATER ), ATTN_IDLE ); } }
/* * CopyToBodyQue */ static edict_t *CopyToBodyQue( edict_t *ent, edict_t *attacker, int damage ) { edict_t *body; int contents; if( GS_RaceGametype() ) return NULL; contents = G_PointContents( ent->s.origin ); if( contents & CONTENTS_NODROP ) return NULL; G_Client_UnlinkBodies( ent ); // grab a body que and cycle to the next one body = &game.edicts[gs.maxclients + level.body_que + 1]; level.body_que = ( level.body_que + 1 ) % BODY_QUEUE_SIZE; // send an effect on the removed body if( body->s.modelindex && body->s.type == ET_CORPSE ) ThrowSmallPileOfGibs( body, 10 ); GClip_UnlinkEntity( body ); memset( body, 0, sizeof( edict_t ) ); //clean up garbage //init body edict G_InitEdict( body ); body->classname = "body"; body->health = ent->health; body->mass = ent->mass; body->r.owner = ent->r.owner; body->s.type = ent->s.type; body->s.team = ent->s.team; body->s.effects = 0; body->r.svflags = SVF_CORPSE; body->r.svflags &= ~SVF_NOCLIENT; body->activator = ent; if( g_deadbody_followkiller->integer ) body->enemy = attacker; //use flat yaw body->s.angles[PITCH] = 0; body->s.angles[ROLL] = 0; body->s.angles[YAW] = ent->s.angles[YAW]; body->s.modelindex2 = 0; // <- is bodyOwner when in ET_CORPSE, but not in ET_GENERIC or ET_PLAYER body->s.weapon = 0; //copy player position and box size VectorCopy( ent->s.old_origin, body->s.old_origin ); VectorCopy( ent->s.origin, body->s.origin ); VectorCopy( ent->s.origin, body->olds.origin ); VectorCopy( ent->r.mins, body->r.mins ); VectorCopy( ent->r.maxs, body->r.maxs ); VectorCopy( ent->r.absmin, body->r.absmin ); VectorCopy( ent->r.absmax, body->r.absmax ); VectorCopy( ent->r.size, body->r.size ); VectorCopy( ent->velocity, body->velocity ); body->r.maxs[2] = body->r.mins[2] + 8; body->r.solid = SOLID_YES; body->takedamage = DAMAGE_YES; body->r.clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; body->movetype = MOVETYPE_TOSS; body->die = body_die; body->think = body_think; // body self destruction countdown if( ent->health < GIB_HEALTH || meansOfDeath == MOD_ELECTROBOLT_S /* electrobolt always gibs */ ) { ThrowSmallPileOfGibs( body, damage ); // reset gib impulse VectorClear( body->velocity ); ThrowClientHead( body, damage ); // sets ET_GIB body->s.frame = 0; body->nextThink = level.time + 3000 + random() * 3000; body->deadflag = DEAD_DEAD; } else if( ent->s.type == ET_PLAYER ) { // copy the model body->s.type = ET_CORPSE; body->s.modelindex = ent->s.modelindex; body->s.bodyOwner = ent->s.number; // bodyOwner is the same as modelindex2 body->s.skinnum = ent->s.skinnum; body->s.teleported = true; // launch the death animation on the body { static int i; i = ( i+1 )%3; G_AddEvent( body, EV_DIE, i, true ); switch( i ) { default: case 0: body->s.frame = ( ( BOTH_DEAD1&0x3F )|( BOTH_DEAD1&0x3F )<<6|( 0 &0xF )<<12 ); break; case 1: body->s.frame = ( ( BOTH_DEAD2&0x3F )|( BOTH_DEAD2&0x3F )<<6|( 0 &0xF )<<12 ); break; case 2: body->s.frame = ( ( BOTH_DEAD3&0x3F )|( BOTH_DEAD3&0x3F )<<6|( 0 &0xF )<<12 ); break; } } body->think = body_ready; body->takedamage = DAMAGE_NO; body->r.solid = SOLID_NOT; body->nextThink = level.time + 500; // make damageable in 0.5 seconds } else // wasn't a player, just copy it's model { VectorClear( body->velocity ); body->s.modelindex = ent->s.modelindex; body->s.frame = ent->s.frame; body->nextThink = level.time + 5000 + random()*10000; } GClip_LinkEntity( body ); return body; }