void FinishSpawningItem( gentity_t *ent ) { trace_t tr; vec3_t dest; gitem_t *item; int itemNum; itemNum=1; for ( item = bg_itemlist + 1 ; item->classname ; item++,itemNum++) { if (!strcmp(item->classname,ent->classname)) { break; } } // Set bounding box for item VectorSet( ent->mins, item->mins[0],item->mins[1] ,item->mins[2]); VectorSet( ent->maxs, item->maxs[0],item->maxs[1] ,item->maxs[2]); if ((!ent->mins[0] && !ent->mins[1] && !ent->mins[2]) && (!ent->maxs[0] && !ent->maxs[1] && !ent->maxs[2])) { VectorSet (ent->mins, -ITEM_RADIUS, -ITEM_RADIUS, -2);//to match the comments in the items.dat file! VectorSet (ent->maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS); } if ((item->quantity) && (item->giType == IT_AMMO)) { ent->count = item->quantity; } if ((item->quantity) && (item->giType == IT_BATTERY)) { ent->count = item->quantity; } // if ( item->giType == IT_WEAPON ) // NOTE: james thought it was ok to just always do this? { ent->s.radius = 20; VectorSet( ent->s.modelScale, 1.0f, 1.0f, 1.0f ); gi.G2API_InitGhoul2Model( ent->ghoul2, ent->item->world_model, G_ModelIndex( ent->item->world_model ), NULL, NULL, 0, 0); } // Set crystal ammo amount based on skill level /* if ((itemNum == ITM_AMMO_CRYSTAL_BORG) || (itemNum == ITM_AMMO_CRYSTAL_DN) || (itemNum == ITM_AMMO_CRYSTAL_FORGE) || (itemNum == ITM_AMMO_CRYSTAL_SCAVENGER) || (itemNum == ITM_AMMO_CRYSTAL_STASIS)) { CrystalAmmoSettings(ent); } */ ent->s.eType = ET_ITEM; ent->s.modelindex = ent->item - bg_itemlist; // store item number in modelindex ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item ent->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;//CONTENTS_BODY;//CONTENTS_TRIGGER| ent->e_TouchFunc = touchF_Touch_Item; // useing an item causes it to respawn ent->e_UseFunc = useF_Use_Item; ent->svFlags |= SVF_PLAYER_USABLE;//so player can pick it up // Hang in air? ent->s.origin[2] += 1;//just to get it off the damn ground because coplanar = insolid if ( ent->spawnflags & ITMSF_SUSPEND) { // suspended G_SetOrigin( ent, ent->s.origin ); } else { // drop to floor VectorSet( dest, ent->s.origin[0], ent->s.origin[1], MIN_WORLD_COORD ); gi.trace( &tr, ent->s.origin, ent->mins, ent->maxs, dest, ent->s.number, MASK_SOLID|CONTENTS_PLAYERCLIP, G2_NOCOLLIDE, 0 ); if ( tr.startsolid ) { if ( &g_entities[tr.entityNum] != NULL ) { gi.Printf (S_COLOR_RED"FinishSpawningItem: removing %s startsolid at %s (in a %s)\n", ent->classname, vtos(ent->s.origin), g_entities[tr.entityNum].classname ); } else { gi.Printf (S_COLOR_RED"FinishSpawningItem: removing %s startsolid at %s (in a %s)\n", ent->classname, vtos(ent->s.origin) ); } assert( 0 && "item starting in solid"); #ifndef FINAL_BUILD if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region delayedShutDown = level.time + 100; } #endif G_FreeEntity( ent ); return; } // allow to ride movers ent->s.groundEntityNum = tr.entityNum; G_SetOrigin( ent, tr.endpos ); } /* ? don't need this // team slaves and targeted items aren't present at start if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) { ent->s.eFlags |= EF_NODRAW; ent->contents = 0; return; } */ if ( ent->spawnflags & ITMSF_INVISIBLE ) // invisible { ent->s.eFlags |= EF_NODRAW; ent->contents = 0; } if ( ent->spawnflags & ITMSF_NOTSOLID ) // not solid { ent->contents = 0; } gi.linkentity (ent); }
/*QUAKED target_gravity_change (1 0 0) (-4 -4 -4) (4 4 4) GLOBAL "gravity" - Normal = 800, Valid range: any GLOBAL - Apply to entire world, not just the activator */ void SP_target_gravity_change( gentity_t *self ) { G_SetOrigin( self, self->s.origin ); G_SpawnFloat( "gravity", "0", &self->speed ); self->e_UseFunc = useF_target_gravity_change_use; }
/* ================ FinishSpawningItem Traces down to find where an item should rest, instead of letting them free fall from their spawn points ================ */ void FinishSpawningItem( gentity_t *ent ) { trace_t tr; vec3_t dest; VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS ); VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS ); ent->s.eType = ET_ITEM; ent->s.modelindex = ent->item - bg_itemlist; // store item number in modelindex ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item ent->r.contents = CONTENTS_TRIGGER; ent->touch = Touch_Item; // useing an item causes it to respawn ent->use = Use_Item; if ( ent->spawnflags & 1 ) { // suspended G_SetOrigin( ent, ent->s.origin ); } else { // drop to floor VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 ); trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID ); if ( tr.startsolid ) { G_Printf ("FinishSpawningItem: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); G_FreeEntity( ent ); return; } // allow to ride movers ent->s.groundEntityNum = tr.entityNum; G_SetOrigin( ent, tr.endpos ); } // team slaves and targeted items aren't present at start if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) { ent->s.eFlags |= EF_NODRAW; ent->r.contents = 0; return; } // powerups don't spawn in for a while if ( ent->item->giType == IT_POWERUP ) { float respawn; respawn = 45 + crandom() * 15; ent->s.eFlags |= EF_NODRAW; ent->r.contents = 0; ent->nextthink = level.time + respawn * 1000; ent->think = RespawnItem; return; } if ( ent->item->giType == IT_HOLDABLE ) { if ( ( ent->item->giTag == HI_BAMBAM ) && ( g_gametype.integer != GT_CTF ) ) { return; } else if ( ( ent->item->giTag == HI_BOOMIES ) && ( ( g_gametype.integer != GT_CTF ) && ( g_gametype.integer != GT_BALLOON ) ) ) { return; } } trap_LinkEntity (ent); }
void CRailTrack::Update() { mNextUpdateTime = level.time + mNextUpdateDelay; // Now, Attempt To Add A Number Of Movers To The Track //----------------------------------------------------- int attempt; int startCol; int stopCol; int atCol; int testColIndex; for (attempt=0; attempt<mNumMoversPerRow; attempt++) { // Randomly Select A Mover And Test To See If It Is Active //--------------------------------------------------------- CRailMover* mover = mMovers[Q_irand(0, mMovers.size()-1)]; if (mover->Active()) { continue; } // Don't Spawn Until Start Time Has Expired //------------------------------------------ if (level.time < ((mover->mLane)?(mover->mLane->mStartTime):(mStartTime))) { continue; } // If Center Locked, Stop Spawning Center Track Movers //----------------------------------------------------- if (mover->mCenter && mCenterLocked) { continue; } // Restrict It To A Lane //----------------------- if (mover->mLane) { startCol = mover->mLane->mMinCol; stopCol = mover->mLane->mMaxCol+1; } // Or Let It Go Anywhere On The Track //------------------------------------ else { startCol = 0; stopCol = mCols; } stopCol -= (mover->mCols-1); // If The Mover Is Too Big To Fit In The Lane, Go On To Next Attempt //------------------------------------------------------------------- if (stopCol<=startCol) { assert(0); // Should Not Happen continue; } // Force It To Center //-------------------- if (mover->mCenter && stopCol!=(startCol+1)) { startCol = ((mCols/2) - (mover->mCols/2)); stopCol = startCol+1; } // Construct A List Of Columns To Test For Insertion //--------------------------------------------------- mTestCols.clear(); for (int i=startCol; i<stopCol; i++) { mTestCols.push_back(i); } // Now Try All The Cols To See If The Building Can Fit //----------------------------------------------------- while (!mTestCols.empty()) { // Randomly Pick A Column, Then Remove It From The Vector //-------------------------------------------------------- testColIndex = Q_irand(0, mTestCols.size()-1); atCol = mTestCols[testColIndex]; mTestCols.erase_swap(testColIndex); if (TestMoverInCells(mover, atCol)) { // Ok, We've Found A Safe Column To Insert This Mover //---------------------------------------------------- InsertMoverInCells(mover, atCol); // Now Transport The Actual Mover Entity Into Position, Link It & Send It Off //---------------------------------------------------------------------------- CVec3 StartPos(mGridBottomLeftCorner); StartPos[mWAxis] += ((atCol * mGridCellSize) + ((mover->mCols/2.0f) * mGridCellSize)); StartPos[mHAxis] += (((mover->mRows/2.0f) * mGridCellSize) * ((mNegative)?(1):(-1))); StartPos[2] = 0; // If Centered, Actually Put It At EXACTLY The Right Position On The Width Axis //------------------------------------------------------------------------------ if (mover->mCenter) { StartPos[mWAxis] = mGridCenter[mWAxis]; float deltaOffset = mGridCenter[mWAxis] - mover->mOriginOffset[mWAxis]; if (deltaOffset<(mGridCellSize*0.5f) ) { StartPos[mWAxis] -= deltaOffset; } } StartPos -= mover->mOriginOffset; G_SetOrigin(mover->mEnt, StartPos.v); // Start It Moving //----------------- VectorCopy(StartPos.v, mover->mEnt->s.pos.trBase); VectorCopy(mVelocity.v, mover->mEnt->s.pos.trDelta); mover->mEnt->s.pos.trTime = level.time; mover->mEnt->s.pos.trDuration = mTravelTimeMilliseconds + (mNextUpdateDelay*mover->mRows); mover->mEnt->s.pos.trType = TR_LINEAR_STOP; mover->mEnt->s.eFlags &= ~EF_NODRAW; mover->mSoundPlayed = false; // Successfully Inserted This Mover. Now Move On To The Next Mover //------------------------------------------------------------------ break; } } } // Incriment The Current Row //--------------------------- mRow++; if (mRow>=mRows) { mRow = 0; } // Erase The Erase Row //--------------------- int EraseRow = mRow - MAX_ROW_HISTORY; if (EraseRow<0) { EraseRow += mRows; } for (int col=0; col<mCols; col++) { mCells.get(col, EraseRow) = 0; } }
/*QUAKED target_position (0 0.5 0) (-4 -4 -4) (4 4 4) Used as a positional target for in-game calculation, like jumppad targets. info_notnull does the same thing */ void SP_target_position( gentity_t *self ){ G_SetOrigin( self, self->s.origin ); }
/** * @brief SP_target_smoke * @param[in,out] ent */ void SP_target_smoke(gentity_t *ent) { char *buffer; if (G_SpawnString("shader", "", &buffer)) { ent->s.modelindex2 = G_ShaderIndex(buffer); } else { ent->s.modelindex2 = 0; } // modified this a lot to be sent to the client as one entity and then is shown at the client if (ent->delay == 0.f) { ent->delay = 100; } ent->use = smoke_toggle; ent->think = smoke_init; ent->nextthink = level.time + FRAMETIME; G_SetOrigin(ent, ent->s.origin); ent->r.svFlags = 0; ent->s.eType = ET_SMOKER; if (ent->spawnflags & 2) { ent->s.density = 4; } else { ent->s.density = 0; } // using "time" ent->s.time = ent->speed; if (!ent->s.time) { ent->s.time = 5000; // 5 seconds } ent->s.time2 = ent->duration; if (!ent->s.time2) { ent->s.time2 = 2000; } ent->s.angles2[0] = ent->start_size; if (ent->s.angles2[0] == 0.f) { ent->s.angles2[0] = 24; } ent->s.angles2[1] = ent->end_size; if (ent->s.angles2[1] == 0.f) { ent->s.angles2[1] = 96; } ent->s.angles2[2] = ent->wait; if (ent->s.angles2[2] == 0.f) { ent->s.angles2[2] = 50; } // idiot check if (ent->s.time < ent->s.time2) { ent->s.time = ent->s.time2 + 100; } if (ent->spawnflags & 8) { ent->s.frame = 1; } ent->s.dl_intensity = ent->health; ent->s.constantLight = ent->delay; if (ent->spawnflags & 4) { trap_LinkEntity(ent); } }
gentity_t *SpawnObelisk( vec3_t origin, int team, int spawnflags) { trace_t tr; vec3_t dest; gentity_t *ent; ent = G_Spawn(); VectorCopy( origin, ent->s.origin ); VectorCopy( origin, ent->s.pos.trBase ); VectorCopy( origin, ent->r.currentOrigin ); VectorSet( ent->r.mins, -15, -15, 0 ); VectorSet( ent->r.maxs, 15, 15, 87 ); ent->s.eType = ET_GENERAL; ent->flags = FL_NO_KNOCKBACK; if( g_gametype.integer == GT_OBELISK ) { ent->r.contents = CONTENTS_SOLID; ent->takedamage = qtrue; ent->health = g_obeliskHealth.integer; ent->die = ObeliskDie; ent->pain = ObeliskPain; ent->think = ObeliskRegen; ent->nextthink = level.time + g_obeliskRegenPeriod.integer * 1000; } if( g_gametype.integer == GT_HARVESTER ) { ent->r.contents = CONTENTS_TRIGGER; ent->touch = ObeliskTouch; } if ( spawnflags & 1 ) { // suspended G_SetOrigin( ent, ent->s.origin ); } else { // mappers like to put them exactly on the floor, but being coplanar // will sometimes show up as starting in solid, so lif it up one pixel ent->s.origin[2] += 1; // drop to floor VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 ); trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID ); if ( tr.startsolid ) { ent->s.origin[2] -= 1; G_Printf( "SpawnObelisk: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin) ); ent->s.groundEntityNum = ENTITYNUM_NONE; G_SetOrigin( ent, ent->s.origin ); } else { // allow to ride movers ent->s.groundEntityNum = tr.entityNum; G_SetOrigin( ent, tr.endpos ); } } ent->spawnflags = team; trap_LinkEntity( ent ); return ent; }
/** * @brief Traces down to find where an item should rest, instead of letting them * free fall from their spawn points. */ void FinishSpawningItem(gentity_t *ent) { trace_t tr; vec3_t dest; vec3_t maxs; if (ent->spawnflags & 1) // suspended { VectorSet(ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS); VectorSet(ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS); VectorCopy(ent->r.maxs, maxs); } else { // had to modify this so that items would spawn in shelves VectorSet(ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, 0); VectorSet(ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS); VectorCopy(ent->r.maxs, maxs); maxs[2] /= 2; } ent->r.contents = CONTENTS_TRIGGER | CONTENTS_ITEM; ent->touch = Touch_Item_Auto; ent->s.eType = ET_ITEM; ent->s.modelindex = ent->item - bg_itemlist; // store item number in modelindex ent->s.otherEntityNum2 = 0; // takes modelindex2's place in signaling a dropped item // we don't use this (yet, anyway) so I'm taking it so you can specify a model for treasure items and clipboards //ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item if (ent->model) { ent->s.modelindex2 = G_ModelIndex(ent->model); } // using an item causes it to respawn ent->use = Use_Item; // moved this up so it happens for suspended items too (and made it a function) G_SetAngle(ent, ent->s.angles); if (ent->spawnflags & 1) // suspended { G_SetOrigin(ent, ent->s.origin); } else { VectorSet(dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096); trap_Trace(&tr, ent->s.origin, ent->r.mins, maxs, dest, ent->s.number, MASK_SOLID); if (tr.startsolid) { vec3_t temp; VectorCopy(ent->s.origin, temp); temp[2] -= ITEM_RADIUS; VectorSet(dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096); trap_Trace(&tr, temp, ent->r.mins, maxs, dest, ent->s.number, MASK_SOLID); } if (tr.startsolid) { G_Printf("FinishSpawningItem: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); G_FreeEntity(ent); return; } // allow to ride movers ent->s.groundEntityNum = tr.entityNum; G_SetOrigin(ent, tr.endpos); } if (ent->spawnflags & 2) // spin { ent->s.eFlags |= EF_SPINNING; } // team slaves and targeted items aren't present at start if ((ent->flags & FL_TEAMSLAVE) || ent->targetname) { ent->flags |= FL_NODRAW; //ent->s.eFlags |= EF_NODRAW; ent->r.contents = 0; return; } // health/ammo can potentially be multi-stage (multiple use) if (ent->item->giType == IT_HEALTH || ent->item->giType == IT_AMMO) { int i; // having alternate models defined in bg_misc.c for a health or ammo item specify it as "multi-stage" // - left-hand operand of comma expression has no effect // initial line: for(i=0;i<4,ent->item->world_model[i];i++) {} for (i = 0; i < 4 && ent->item->world_model[i] ; i++) { } ent->s.density = i - 1; // store number of stages in 'density' for client (most will have '1') } trap_LinkEntity(ent); }
/** * @brief Spawns an item and tosses it forward. */ gentity_t *LaunchItem(gitem_t *item, vec3_t origin, vec3_t velocity, int ownerNum) { gentity_t *dropped = G_Spawn(); trace_t tr; vec3_t vec, temp; dropped->s.eType = ET_ITEM; dropped->s.modelindex = item - bg_itemlist; // store item number in modelindex dropped->s.otherEntityNum2 = 1; // this is taking modelindex2's place for a dropped item dropped->classname = item->classname; dropped->item = item; VectorSet(dropped->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, 0); // so items sit on the ground VectorSet(dropped->r.maxs, ITEM_RADIUS, ITEM_RADIUS, 2 * ITEM_RADIUS); // so items sit on the ground dropped->r.contents = CONTENTS_TRIGGER | CONTENTS_ITEM; dropped->clipmask = CONTENTS_SOLID | CONTENTS_MISSILECLIP; // fix for items falling through grates dropped->touch = Touch_Item_Auto; trap_Trace(&tr, origin, dropped->r.mins, dropped->r.maxs, origin, ownerNum, MASK_SOLID); if (tr.startsolid) { int i; VectorSubtract(g_entities[ownerNum].s.origin, origin, temp); VectorNormalize(temp); for (i = 16; i <= 48; i += 16) { VectorScale(temp, i, vec); VectorAdd(origin, vec, origin); trap_Trace(&tr, origin, dropped->r.mins, dropped->r.maxs, origin, ownerNum, MASK_SOLID); if (!tr.startsolid) { break; } } } G_SetOrigin(dropped, origin); dropped->s.pos.trType = TR_GRAVITY; dropped->s.pos.trTime = level.time; VectorCopy(velocity, dropped->s.pos.trDelta); // set yaw to parent angles temp[PITCH] = 0; temp[YAW] = g_entities[ownerNum].s.apos.trBase[YAW]; temp[ROLL] = 0; G_SetAngle(dropped, temp); dropped->s.eFlags |= EF_BOUNCE_HALF; if (item->giType == IT_TEAM) // Special case for CTF flags { gentity_t *flag = &g_entities[g_entities[ownerNum].client->flagParent]; dropped->s.otherEntityNum = g_entities[ownerNum].client->flagParent; // store the entitynum of our original flag spawner dropped->s.density = 1; dropped->think = Team_DroppedFlagThink; dropped->nextthink = level.time + 30000; if (level.gameManager) { G_Script_ScriptEvent(level.gameManager, "trigger", flag->item->giTag == PW_REDFLAG ? "allied_object_dropped" : "axis_object_dropped"); } G_Script_ScriptEvent(flag, "trigger", "dropped"); } else // auto-remove after 30 seconds { dropped->think = G_FreeEntity; dropped->nextthink = level.time + 30000; } dropped->flags = FL_DROPPED_ITEM; trap_LinkEntity(dropped); return dropped; }
/* ============= SpawnCorpse A player is respawning, so make an entity that looks just like the existing corpse to leave behind. ============= */ void SpawnCorpse( gentity_t *ent ) { gentity_t *body; int contents; vec3_t origin, dest; trace_t tr; float vDiff; //just return right away so bodies never appear and its cleaner + faster return; VectorCopy( ent->r.currentOrigin, origin ); trap_UnlinkEntity( ent ); // if client is in a nodrop area, don't leave the body contents = trap_PointContents( origin, -1 ); if( contents & CONTENTS_NODROP ) return; body = G_Spawn( ); VectorCopy( ent->s.apos.trBase, body->s.angles ); body->s.eFlags = EF_DEAD; body->s.eType = ET_CORPSE; body->s.number = body - g_entities; body->timestamp = level.time; body->s.event = 0; body->r.contents = CONTENTS_CORPSE; body->s.clientNum = ent->client->ps.stats[ STAT_PCLASS ]; body->nonSegModel = ent->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL; if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) body->classname = "humanCorpse"; else body->classname = "alienCorpse"; body->s.powerups = MAX_CLIENTS; body->think = BodySink; body->nextthink = level.time + 20000; body->s.legsAnim = ent->s.legsAnim; if( !body->nonSegModel ) { switch( body->s.legsAnim & ~ANIM_TOGGLEBIT ) { case BOTH_DEATH1: case BOTH_DEAD1: body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1; break; case BOTH_DEATH2: case BOTH_DEAD2: body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2; break; case BOTH_DEATH3: case BOTH_DEAD3: default: body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3; break; } } else { switch( body->s.legsAnim & ~ANIM_TOGGLEBIT ) { case NSPA_DEATH1: case NSPA_DEAD1: body->s.legsAnim = NSPA_DEAD1; break; case NSPA_DEATH2: case NSPA_DEAD2: body->s.legsAnim = NSPA_DEAD2; break; case NSPA_DEATH3: case NSPA_DEAD3: default: body->s.legsAnim = NSPA_DEAD3; break; } } body->takedamage = qfalse; body->health = ent->health = ent->client->ps.stats[ STAT_HEALTH ]; ent->health = 0; //change body dimensions BG_FindBBoxForClass( ent->client->ps.stats[ STAT_PCLASS ], NULL, NULL, NULL, body->r.mins, body->r.maxs ); vDiff = body->r.mins[ 2 ] - ent->r.mins[ 2 ]; //drop down to match the *model* origins of ent and body VectorSet( dest, origin[ 0 ], origin[ 1 ], origin[ 2 ] - vDiff ); trap_Trace( &tr, origin, body->r.mins, body->r.maxs, dest, body->s.number, body->clipmask ); VectorCopy( tr.endpos, origin ); G_SetOrigin( body, origin ); VectorCopy( origin, body->s.origin ); body->s.pos.trType = TR_GRAVITY; body->s.pos.trTime = level.time; VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); trap_LinkEntity( body ); }
/* =========== ClientSpawn Called every time a client is placed fresh in the world: after the first ClientBegin, and after each respawn Initializes all non-persistant parts of playerState ============ */ void ClientSpawn(gentity_t *ent) { int index; vec3_t spawn_origin, spawn_angles; gclient_t *client; int i; clientPersistant_t saved; clientSession_t savedSess; int persistant[MAX_PERSISTANT]; gentity_t *spawnPoint; int flags; int savedPing; // char *savedAreaBits; int accuracy_hits, accuracy_shots; int eventSequence; // char userinfo[MAX_INFO_STRING]; forcedata_t savedForce; void *ghoul2save; int saveSaberNum = ENTITYNUM_NONE; int wDisable = 0; index = ent - g_entities; client = ent->client; if (client->ps.fd.forceDoInit) { //force a reread of force powers WP_InitForcePowers( ent ); client->ps.fd.forceDoInit = 0; } // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { spawnPoint = SelectSpectatorSpawnPoint ( spawn_origin, spawn_angles); } else if (g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY) { // all base oriented team games use the CTF spawn points spawnPoint = SelectCTFSpawnPoint ( client->sess.sessionTeam, client->pers.teamState.state, spawn_origin, spawn_angles); } else if (g_gametype.integer == GT_SAGA) { spawnPoint = SelectSagaSpawnPoint ( client->sess.sessionTeam, client->pers.teamState.state, spawn_origin, spawn_angles); } else { do { // the first spawn should be at a good looking spot if ( !client->pers.initialSpawn && client->pers.localClient ) { client->pers.initialSpawn = qtrue; spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles ); } else { // don't spawn near existing origin if possible spawnPoint = SelectSpawnPoint ( client->ps.origin, spawn_origin, spawn_angles); } // Tim needs to prevent bots from spawning at the initial point // on q3dm0... if ( ( spawnPoint->flags & FL_NO_BOTS ) && ( ent->r.svFlags & SVF_BOT ) ) { continue; // try again } // just to be symetric, we have a nohumans option... if ( ( spawnPoint->flags & FL_NO_HUMANS ) && !( ent->r.svFlags & SVF_BOT ) ) { continue; // try again } break; } while ( 1 ); } client->pers.teamState.state = TEAM_ACTIVE; // toggle the teleport bit so the client knows to not lerp // and never clear the voted flag flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED); flags ^= EF_TELEPORT_BIT; // clear everything but the persistant data saved = client->pers; savedSess = client->sess; savedPing = client->ps.ping; // savedAreaBits = client->areabits; accuracy_hits = client->accuracy_hits; accuracy_shots = client->accuracy_shots; for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { persistant[i] = client->ps.persistant[i]; } eventSequence = client->ps.eventSequence; savedForce = client->ps.fd; ghoul2save = client->ghoul2; saveSaberNum = client->ps.saberEntityNum; memset (client, 0, sizeof(*client)); // bk FIXME: Com_Memset? //rww - Don't wipe the ghoul2 instance or the animation data client->ghoul2 = ghoul2save; //or the saber ent num client->ps.saberEntityNum = saveSaberNum; client->ps.fd = savedForce; client->ps.duelIndex = ENTITYNUM_NONE; client->pers = saved; client->sess = savedSess; client->ps.ping = savedPing; // client->areabits = savedAreaBits; client->accuracy_hits = accuracy_hits; client->accuracy_shots = accuracy_shots; client->lastkilled_client = -1; for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { client->ps.persistant[i] = persistant[i]; } client->ps.eventSequence = eventSequence; // increment the spawncount so the client will detect the respawn client->ps.persistant[PERS_SPAWN_COUNT]++; client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; client->airOutTime = level.time + 12000; // trap_GetUserinfo( index, userinfo, sizeof(userinfo) ); // set max health client->pers.maxHealth = 100;//atoi( Info_ValueForKey( userinfo, "handicap" ) ); if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { client->pers.maxHealth = 100; } // clear entity values client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; client->ps.eFlags = flags; ent->s.groundEntityNum = ENTITYNUM_NONE; ent->client = &level.clients[index]; ent->takedamage = qtrue; ent->inuse = qtrue; ent->classname = "player"; ent->r.contents = CONTENTS_BODY; ent->clipmask = MASK_PLAYERSOLID; ent->die = player_die; ent->waterlevel = 0; ent->watertype = 0; ent->flags = 0; VectorCopy (playerMins, ent->r.mins); VectorCopy (playerMaxs, ent->r.maxs); client->ps.clientNum = index; //give default weapons client->ps.stats[STAT_WEAPONS] = ( 1 << WP_NONE ); if (g_gametype.integer == GT_HOLOCRON) { //always get free saber level 1 in holocron client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_SABER ); //these are precached in g_items, ClearRegisteredItems() } else { if (client->ps.fd.forcePowerLevel[FP_SABERATTACK]) { client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_SABER ); //these are precached in g_items, ClearRegisteredItems() } else { //if you don't have saber attack rank then you don't get a saber client->ps.stats[STAT_WEAPONS] |= (1 << WP_STUN_BATON); } } if (g_gametype.integer == GT_TOURNAMENT) { wDisable = g_duelWeaponDisable.integer; } else { wDisable = g_weaponDisable.integer; } if (!wDisable || !(wDisable & (1 << WP_BRYAR_PISTOL))) { client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BRYAR_PISTOL ); } else if (g_gametype.integer == GT_JEDIMASTER) { client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BRYAR_PISTOL ); } if (g_gametype.integer == GT_JEDIMASTER) { client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_SABER); client->ps.stats[STAT_WEAPONS] |= (1 << WP_STUN_BATON); } if (client->ps.stats[STAT_WEAPONS] & (1 << WP_BRYAR_PISTOL)) { client->ps.weapon = WP_BRYAR_PISTOL; } else if (client->ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) { client->ps.weapon = WP_SABER; } else { client->ps.weapon = WP_STUN_BATON; } /* client->ps.stats[STAT_HOLDABLE_ITEMS] |= ( 1 << HI_BINOCULARS ); client->ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_BINOCULARS, IT_HOLDABLE); */ client->ps.stats[STAT_HOLDABLE_ITEMS] = 0; client->ps.stats[STAT_HOLDABLE_ITEM] = 0; if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { client->ps.stats[STAT_WEAPONS] = 0; client->ps.stats[STAT_HOLDABLE_ITEMS] = 0; client->ps.stats[STAT_HOLDABLE_ITEM] = 0; } client->ps.ammo[AMMO_BLASTER] = 100; //ammoData[AMMO_BLASTER].max; //100 seems fair. // client->ps.ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max; // client->ps.ammo[AMMO_FORCE] = ammoData[AMMO_FORCE].max; // client->ps.ammo[AMMO_METAL_BOLTS] = ammoData[AMMO_METAL_BOLTS].max; // client->ps.ammo[AMMO_ROCKETS] = ammoData[AMMO_ROCKETS].max; /* client->ps.stats[STAT_WEAPONS] = ( 1 << WP_BRYAR_PISTOL); if ( g_gametype.integer == GT_TEAM ) { client->ps.ammo[WP_BRYAR_PISTOL] = 50; } else { client->ps.ammo[WP_BRYAR_PISTOL] = 100; } */ client->ps.rocketLockIndex = MAX_CLIENTS; client->ps.rocketLockTime = 0; //rww - Set here to initialize the circling seeker drone to off. //A quick note about this so I don't forget how it works again: //ps.genericEnemyIndex is kept in sync between the server and client. //When it gets set then an entitystate value of the same name gets //set along with an entitystate flag in the shared bg code. Which //is why a value needs to be both on the player state and entity state. //(it doesn't seem to just carry over the entitystate value automatically //because entity state value is derived from player state data or some //such) client->ps.genericEnemyIndex = -1; client->ps.isJediMaster = qfalse; client->ps.fallingToDeath = 0; //Do per-spawn force power initialization WP_SpawnInitForcePowers( ent ); // health will count down towards max_health ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH];// * 1.25; Boot - no extra 25 hp on start. // Start with a small amount of armor as well. client->ps.stats[STAT_ARMOR] = client->ps.stats[STAT_MAX_HEALTH] * 0.25; G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); // the respawned flag will be cleared after the attack and jump keys come up client->ps.pm_flags |= PMF_RESPAWNED; trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); SetClientViewAngle( ent, spawn_angles ); if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { } else { G_KillBox( ent ); trap_LinkEntity (ent); // force the base weapon up client->ps.weapon = WP_BRYAR_PISTOL; client->ps.weaponstate = FIRST_WEAPON; } // don't allow full run speed for a bit client->ps.pm_flags |= PMF_TIME_KNOCKBACK; client->ps.pm_time = 100; client->respawnTime = level.time; client->inactivityTime = level.time + g_inactivity.integer * 1000; client->latched_buttons = 0; // set default animations client->ps.torsoAnim = WeaponReadyAnim[client->ps.weapon]; client->ps.legsAnim = WeaponReadyAnim[client->ps.weapon]; if ( level.intermissiontime ) { MoveClientToIntermission( ent ); } else { // fire the targets of the spawn point G_UseTargets( spawnPoint, ent ); // select the highest weapon number available, after any // spawn given items have fired client->ps.weapon = 1; for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) { if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) { client->ps.weapon = i; break; } } } // run a client frame to drop exactly to the floor, // initialize animations and other things client->ps.commandTime = level.time - 100; ent->client->pers.cmd.serverTime = level.time; ClientThink( ent-g_entities ); // positively link the client, even if the command times are weird if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); } if (g_spawnInvulnerability.integer) { ent->client->ps.eFlags |= EF_INVULNERABLE; ent->client->invulnerableTimer = level.time + g_spawnInvulnerability.integer; } // run the presend to set anything else ClientEndFrame( ent ); // clear entity state values BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); }
/* ================ G_MissileImpact ================ */ void G_MissileImpact( gentity_t *ent, trace_t *trace ) { gentity_t *other, *attacker; qboolean returnAfterDamage = qfalse; vec3_t dir; other = &g_entities[ trace->entityNum ]; attacker = &g_entities[ ent->r.ownerNum ]; // check for bounce if( !other->takedamage && ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) ) { G_BounceMissile( ent, trace ); //only play a sound if requested if( !( ent->s.eFlags & EF_NO_BOUNCE_SOUND ) ) G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 ); return; } if( !strcmp( ent->classname, "grenade" ) ) { //grenade doesn't explode on impact G_BounceMissile( ent, trace ); //only play a sound if requested if( !( ent->s.eFlags & EF_NO_BOUNCE_SOUND ) ) G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 ); return; } else if( !strcmp( ent->classname, "lockblob" ) ) { if( other->client && other->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { other->client->ps.stats[ STAT_STATE ] |= SS_BLOBLOCKED; other->client->lastLockTime = level.time; AngleVectors( other->client->ps.viewangles, dir, NULL, NULL ); other->client->ps.stats[ STAT_VIEWLOCK ] = DirToByte( dir ); } } else if( !strcmp( ent->classname, "slowblob" ) ) { if( other->client && other->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { other->client->ps.stats[ STAT_STATE ] |= SS_SLOWLOCKED; other->client->lastSlowTime = level.time; AngleVectors( other->client->ps.viewangles, dir, NULL, NULL ); other->client->ps.stats[ STAT_VIEWLOCK ] = DirToByte( dir ); } } else if( !strcmp( ent->classname, "hive" ) ) { if( other->s.eType == ET_BUILDABLE && other->s.modelindex == BA_A_HIVE ) { if( !ent->parent ) G_Printf( S_COLOR_YELLOW "WARNING: hive entity has no parent in G_MissileImpact\n" ); else ent->parent->active = qfalse; G_FreeEntity( ent ); return; } else { //prevent collision with the client when returning ent->r.ownerNum = other->s.number; ent->think = G_ExplodeMissile; ent->nextthink = level.time + FRAMETIME; //only damage humans if( other->client && other->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) returnAfterDamage = qtrue; else return; } } // impact damage if( other->takedamage ) { // FIXME: wrong damage direction? if( ent->damage ) { vec3_t velocity; BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity ); if( VectorLength( velocity ) == 0 ) velocity[ 2 ] = 1; // stepped on a grenade G_Damage( other, ent, attacker, velocity, ent->s.origin, ent->damage, DAMAGE_NO_LOCDAMAGE, ent->methodOfDeath ); } } if( returnAfterDamage ) return; // is it cheaper in bandwidth to just remove this ent and create a new // one, rather than changing the missile into the explosion? if( other->takedamage && ( other->s.eType == ET_PLAYER || other->s.eType == ET_BUILDABLE ) ) { G_AddEvent( ent, EV_MISSILE_HIT, DirToByte( trace->plane.normal ) ); ent->s.otherEntityNum = other->s.number; } else if( trace->surfaceFlags & SURF_METALSTEPS ) G_AddEvent( ent, EV_MISSILE_MISS_METAL, DirToByte( trace->plane.normal ) ); else G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( trace->plane.normal ) ); ent->freeAfterEvent = qtrue; // change over to a normal entity right at the point of impact ent->s.eType = ET_GENERAL; SnapVectorTowards( trace->endpos, ent->s.pos.trBase ); // save net bandwidth G_SetOrigin( ent, trace->endpos ); // splash damage (doesn't apply to person directly hit) if( ent->splashDamage ) G_RadiusDamage( trace->endpos, ent->parent, ent->splashDamage, ent->splashRadius, other, ent->splashMethodOfDeath ); trap_LinkEntity( ent ); }
// links the trigger to it's objective, determining if it's a func_explosive // of func_constructible and spawning the right indicator void Think_SetupObjectiveInfo(gentity_t *ent) { ent->target_ent = G_FindByTargetname(NULL, ent->target); if (!ent->target_ent) { G_Error("'trigger_objective_info' has a missing target '%s'\n", ent->target); } if (ent->target_ent->s.eType == ET_EXPLOSIVE) { // this is for compass usage if ((ent->spawnflags & AXIS_OBJECTIVE) || (ent->spawnflags & ALLIED_OBJECTIVE)) { gentity_t *e = G_Spawn(); e->r.svFlags = SVF_BROADCAST; e->classname = "explosive_indicator"; if (ent->spawnflags & 8) { e->s.eType = ET_TANK_INDICATOR; } else { e->s.eType = ET_EXPLOSIVE_INDICATOR; } e->parent = ent; e->s.pos.trType = TR_STATIONARY; if (ent->spawnflags & AXIS_OBJECTIVE) { e->s.teamNum = 1; } else if (ent->spawnflags & ALLIED_OBJECTIVE) { e->s.teamNum = 2; } G_SetOrigin(e, ent->r.currentOrigin); e->s.modelindex2 = ent->s.teamNum; e->r.ownerNum = ent->s.number; e->think = explosive_indicator_think; e->nextthink = level.time + FRAMETIME; e->s.effect1Time = ent->target_ent->constructibleStats.weaponclass; if (ent->tagParent) { e->tagParent = ent->tagParent; Q_strncpyz(e->tagName, ent->tagName, MAX_QPATH); } else { VectorCopy(ent->r.absmin, e->s.pos.trBase); VectorAdd(ent->r.absmax, e->s.pos.trBase, e->s.pos.trBase); VectorScale(e->s.pos.trBase, 0.5, e->s.pos.trBase); } SnapVector(e->s.pos.trBase); trap_LinkEntity(e); ent->target_ent->parent = ent; } } else if (ent->target_ent->s.eType == ET_CONSTRUCTIBLE) { gentity_t *constructibles[2]; int team[2] = { 0 }; ent->target_ent->parent = ent; constructibles[0] = ent->target_ent; constructibles[1] = G_FindByTargetname(constructibles[0], ent->target); // see if we are targetting a 2nd one for two team constructibles team[0] = constructibles[0]->spawnflags & AXIS_CONSTRUCTIBLE ? TEAM_AXIS : TEAM_ALLIES; constructibles[0]->s.otherEntityNum2 = ent->s.teamNum; if (constructibles[1]) { team[1] = constructibles[1]->spawnflags & AXIS_CONSTRUCTIBLE ? TEAM_AXIS : TEAM_ALLIES; if (constructibles[1]->s.eType != ET_CONSTRUCTIBLE) { G_Error("'trigger_objective_info' targets multiple entities with targetname '%s', the second one isn't a 'func_constructible' [%d]\n", ent->target, constructibles[1]->s.eType); } if (team[0] == team[1]) { G_Error("'trigger_objective_info' targets two 'func_constructible' entities with targetname '%s' that are constructible by the same team\n", ent->target); } constructibles[1]->s.otherEntityNum2 = ent->s.teamNum; ent->chain = constructibles[1]; ent->chain->parent = ent; constructibles[0]->chain = constructibles[1]; constructibles[1]->chain = constructibles[0]; } else { constructibles[0]->chain = NULL; } // if already constructed (in case of START_BUILT) if (constructibles[0]->s.angles2[1] == 0) { // spawn a constructible icon - this is for compass usage gentity_t *e = G_Spawn(); e->r.svFlags = SVF_BROADCAST; e->classname = "constructible_indicator"; if (ent->spawnflags & 8) { e->s.eType = ET_TANK_INDICATOR_DEAD; } else { e->s.eType = ET_CONSTRUCTIBLE_INDICATOR; } e->s.pos.trType = TR_STATIONARY; if (constructibles[1]) { // see if one of the two is still partially built (happens when a multistage destructible construction blows up for the first time) if (constructibles[0]->count2 && constructibles[0]->grenadeFired > 1) { e->s.teamNum = team[0]; } else if (constructibles[1]->count2 && constructibles[1]->grenadeFired > 1) { e->s.teamNum = team[1]; } else { e->s.teamNum = 3; // both teams } } else { e->s.teamNum = team[0]; } e->s.modelindex2 = ent->s.teamNum; e->r.ownerNum = ent->s.number; ent->count2 = (e - g_entities); e->think = constructible_indicator_think; e->nextthink = level.time + FRAMETIME; e->parent = ent; if (ent->tagParent) { e->tagParent = ent->tagParent; Q_strncpyz(e->tagName, ent->tagName, MAX_QPATH); } else { VectorCopy(ent->r.absmin, e->s.pos.trBase); VectorAdd(ent->r.absmax, e->s.pos.trBase, e->s.pos.trBase); VectorScale(e->s.pos.trBase, 0.5, e->s.pos.trBase); } SnapVector(e->s.pos.trBase); trap_LinkEntity(e); // moved down } ent->touch = Touch_ObjectiveInfo; } else if (ent->target_ent->s.eType == ET_COMMANDMAP_MARKER) { ent->target_ent->parent = ent; } trap_LinkEntity(ent); }
// Set up snapshot merge based on this portal qboolean G_smvRunCamera( gentity_t *ent ) { int id = ent->TargetFlag; int chargeTime, sprintTime, hintTime, weapHeat; playerState_t *tps, *ps; // Opt out if not a real MV portal if ( ent->tagParent == NULL || ent->tagParent->client == NULL ) { return( qfalse ); } if ( ( ps = &ent->tagParent->client->ps ) == NULL ) { return( qfalse ); } // If viewing client is no longer connected, delete this camera if ( ent->tagParent->client->pers.connected != CON_CONNECTED ) { G_FreeEntity( ent ); return( qtrue ); } // Also remove if the target player is no longer in the game playing if ( ent->target_ent->client->pers.connected != CON_CONNECTED || ent->target_ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { G_smvLocateEntityInMVList( ent->tagParent, ent->target_ent - g_entities, qtrue ); return( qtrue ); } // Seems that depending on player's state, we pull from either r.currentOrigin, or s.origin // if(!spec) then use: r.currentOrigin // if(spec) then use: s.origin // // This is true for both the portal origin and its target (origin2) // VectorCopy( ent->tagParent->s.origin, ent->s.origin ); G_SetOrigin( ent, ent->s.origin ); VectorCopy( ent->target_ent->r.currentOrigin, ent->s.origin2 ); trap_LinkEntity( ent ); // Only allow client ids 0 to (MAX_MVCLIENTS-1) to be updated with extra info if ( id >= MAX_MVCLIENTS ) { return( qtrue ); } tps = &ent->target_ent->client->ps; if ( tps->stats[STAT_PLAYER_CLASS] == PC_ENGINEER ) { chargeTime = g_engineerChargeTime.value; } else if ( tps->stats[STAT_PLAYER_CLASS] == PC_MEDIC ) { chargeTime = g_medicChargeTime.value; } else if ( tps->stats[STAT_PLAYER_CLASS] == PC_FIELDOPS ) { chargeTime = g_LTChargeTime.value; } else if ( tps->stats[STAT_PLAYER_CLASS] == PC_COVERTOPS ) { chargeTime = g_covertopsChargeTime.value; } else { chargeTime = g_soldierChargeTime.value;} chargeTime = ( level.time - tps->classWeaponTime >= (int)chargeTime ) ? 0 : ( 1 + floor( 15.0f * (float)( level.time - tps->classWeaponTime ) / chargeTime ) ); sprintTime = ( ent->target_ent->client->pmext.sprintTime >= 20000 ) ? 0.0f : ( 1 + floor( 7.0f * (float)ent->target_ent->client->pmext.sprintTime / 20000.0f ) ); weapHeat = floor( (float)tps->curWeapHeat * 15.0f / 255.0f ); hintTime = ( tps->serverCursorHint != HINT_BUILD && ( tps->serverCursorHintVal >= 255 || tps->serverCursorHintVal == 0 ) ) ? 0 : ( 1 + floor( 15.0f * (float)tps->serverCursorHintVal / 255.0f ) ); // (Remaining bits) // ammo : 0 // ammo-1 : 0 // ammiclip : 0 // ammoclip-1: 16 id = MAX_WEAPONS - 1 - ( id * 2 ); if ( tps->pm_flags & PMF_LIMBO ) { ps->ammo[id] = 0; ps->ammo[id - 1] = 0; ps->ammoclip[id - 1] = 0; } else { ps->ammo[id] = ( ( ( ent->target_ent->health > 0 ) ? ent->target_ent->health : 0 ) & 0xFF ); // Meds up to 140 :( ps->ammo[id] |= ( hintTime & 0x0F ) << 8; // 4 bits for work on current item (dynamite, weapon repair, etc.) ps->ammo[id] |= ( weapHeat & 0x0F ) << 12; // 4 bits for weapon heat info ps->ammo[id - 1] = tps->ammo[BG_FindAmmoForWeapon( tps->weapon )] & 0x3FF; // 11 bits needed to cover 1500 Venom ammo ps->ammo[id - 1] |= ( BG_simpleWeaponState( tps->weaponstate ) & 0x03 ) << 11; // 2 bits for current weapon state ps->ammo[id - 1] |= ( ( tps->persistant[PERS_HWEAPON_USE] ) ? 1 : 0 ) << 13; // 1 bit for mg42 use ps->ammo[id - 1] |= ( BG_simpleHintsCollapse( tps->serverCursorHint, hintTime ) & 0x03 ) << 14; // 2 bits for cursor hints // G_Printf("tps->hint: %d, dr: %d, collapse: %d\n", tps->serverCursorHint, HINT_DOOR_ROTATING, G_simpleHintsCollapse(tps->serverCursorHint, hintTime)); ps->ammoclip[id - 1] = tps->ammoclip[BG_FindClipForWeapon( tps->weapon )] & 0x1FF; // 9 bits to cover 500 Venom ammo clip ps->ammoclip[id - 1] |= ( chargeTime & 0x0F ) << 9; // 4 bits for weapon charge time ps->ammoclip[id - 1] |= ( sprintTime & 0x07 ) << 13; // 3 bits for fatigue } return( qtrue ); }
//------------------ void Cmd_Fx( gentity_t *ent ) { vec3_t dir; gentity_t *fx_ent = NULL; if ( Q_stricmp( gi.argv(1), "play" ) == 0 ) { if ( gi.argc() == 3 ) { // I guess, only allow one active at a time while (( fx_ent = G_Find( fx_ent, FOFS(classname), "cmd_fx")) != NULL ) { G_FreeEntity( fx_ent ); } fx_ent = G_Spawn(); fx_ent->fxFile = gi.argv( 2 ); // Move out in front of the person spawning the effect AngleVectors( ent->currentAngles, dir, NULL, NULL ); VectorMA( ent->currentOrigin, 32, dir, fx_ent->s.origin ); extern void SP_fx_runner( gentity_t *ent ); SP_fx_runner( fx_ent ); fx_ent->delay = 2000; // adjusting delay fx_ent->classname = "cmd_fx"; // and classname return; } } else if ( Q_stricmp( gi.argv(1), "stop" ) == 0 ) { while (( fx_ent = G_Find( fx_ent, FOFS(classname), "cmd_fx")) != NULL ) { G_FreeEntity( fx_ent ); } return; } else if ( Q_stricmp( gi.argv(1), "delay" ) == 0 ) { while (( fx_ent = G_Find( fx_ent, FOFS(classname), "cmd_fx")) != NULL ) { if ( gi.argc() == 3 ) { fx_ent->delay = atoi( gi.argv( 2 )); } else { gi.Printf( S_COLOR_GREEN"FX: current delay is: %i\n", fx_ent->delay ); } return; } } else if ( Q_stricmp( gi.argv(1), "random" ) == 0 ) { while (( fx_ent = G_Find( fx_ent, FOFS(classname), "cmd_fx")) != NULL ) { if ( gi.argc() == 3 ) { fx_ent->random = atoi( gi.argv( 2 )); } else { gi.Printf( S_COLOR_GREEN"FX: current random is: %6.2f\n", fx_ent->random ); } return; } } else if ( Q_stricmp( gi.argv(1), "origin" ) == 0 ) { while (( fx_ent = G_Find( fx_ent, FOFS(classname), "cmd_fx")) != NULL ) { if ( gi.argc() == 5 ) { fx_ent->s.origin[0] = atof( gi.argv( 2 )); fx_ent->s.origin[1] = atof( gi.argv( 3 )); fx_ent->s.origin[2] = atof( gi.argv( 4 )); G_SetOrigin( fx_ent, fx_ent->s.origin ); } else { gi.Printf( S_COLOR_GREEN"FX: current origin is: <%6.2f %6.2f %6.2f>\n", fx_ent->currentOrigin[0], fx_ent->currentOrigin[1], fx_ent->currentOrigin[2] ); } return; } } else if ( Q_stricmp( gi.argv(1), "dir" ) == 0 ) { while (( fx_ent = G_Find( fx_ent, FOFS(classname), "cmd_fx")) != NULL ) { if ( gi.argc() == 5 ) { fx_ent->s.angles[0] = atof( gi.argv( 2 )); fx_ent->s.angles[1] = atof( gi.argv( 3 )); fx_ent->s.angles[2] = atof( gi.argv( 4 )); if ( !VectorNormalize( fx_ent->s.angles )) { // must have been zero length fx_ent->s.angles[2] = 1; } } else { gi.Printf( S_COLOR_GREEN"FX: current dir is: <%6.2f %6.2f %6.2f>\n", fx_ent->s.angles[0], fx_ent->s.angles[1], fx_ent->s.angles[2] ); } return; } } gi.Printf( S_COLOR_CYAN"Fx--------------------------------------------------------\n" ); gi.Printf( S_COLOR_CYAN"commands: sample usage:\n" ); gi.Printf( S_COLOR_CYAN"----------------------------------------------------------\n" ); gi.Printf( S_COLOR_CYAN"fx play <filename> fx play sparks, fx play env/fire\n" ); gi.Printf( S_COLOR_CYAN"fx stop fx stop\n" ); gi.Printf( S_COLOR_CYAN"fx delay <#> fx delay 1000\n" ); gi.Printf( S_COLOR_CYAN"fx random <#> fx random 200\n" ); gi.Printf( S_COLOR_CYAN"fx origin <#><#><#> fx origin 10 20 30\n" ); gi.Printf( S_COLOR_CYAN"fx dir <#><#><#> fx dir 0 0 -1\n\n" ); }
//Run physics on the object (purely origin-related) using custom epVelocity entity //state value. Origin smoothing on the client is expected to compensate for choppy //movement. void G_RunExPhys( gentity_t *ent, float gravity, float mass, float bounce, qboolean autoKill, int *g2Bolts, int numG2Bolts ) { trace_t tr; vector3 projectedOrigin; vector3 vNorm; vector3 ground; float velScaling = 0.1f; float vTotal = 0.0f; assert( mass <= 1.0f && mass >= 0.01f ); if ( gravity ) { //factor it in before we do anything. VectorCopy( &ent->r.currentOrigin, &ground ); ground.z -= 0.1f; trap->Trace( &tr, &ent->r.currentOrigin, &ent->r.mins, &ent->r.maxs, &ground, ent->s.number, ent->clipmask, qfalse, 0, 0 ); if ( tr.fraction == 1.0f ) { ent->s.groundEntityNum = ENTITYNUM_NONE; } else { ent->s.groundEntityNum = tr.entityNum; } if ( ent->s.groundEntityNum == ENTITYNUM_NONE ) { ent->epGravFactor += gravity; if ( ent->epGravFactor > MAX_GRAVITY_PULL ) { //cap it off if needed ent->epGravFactor = MAX_GRAVITY_PULL; } ent->epVelocity.z -= ent->epGravFactor; } else { //if we're sitting on something then reset the gravity factor. ent->epGravFactor = 0; } } if ( !ent->epVelocity.x && !ent->epVelocity.y && !ent->epVelocity.z ) { //nothing to do if we have no velocity even after gravity. if ( ent->touch ) { //call touch if we're in something trap->Trace( &tr, &ent->r.currentOrigin, &ent->r.mins, &ent->r.maxs, &ent->r.currentOrigin, ent->s.number, ent->clipmask, qfalse, 0, 0 ); if ( tr.startsolid || tr.allsolid ) { ent->touch( ent, &g_entities[tr.entityNum], &tr ); } } return; } //get the projected origin based on velocity. VectorMA( &ent->r.currentOrigin, velScaling, &ent->epVelocity, &projectedOrigin ); VectorScale( &ent->epVelocity, 1.0f - mass, &ent->epVelocity ); //scale it down based on mass VectorCopy( &ent->epVelocity, &vNorm ); vTotal = VectorNormalize( &vNorm ); if ( vTotal < 1 && ent->s.groundEntityNum != ENTITYNUM_NONE ) { //we've pretty much stopped moving anyway, just clear it out then. VectorClear( &ent->epVelocity ); ent->epGravFactor = 0; trap->LinkEntity( (sharedEntity_t *)ent ); return; } if ( ent->ghoul2 && g2Bolts ) { //Have we been passed a bolt index array to clip against points on the skeleton? vector3 tMins, tMaxs; vector3 trajDif; vector3 gbmAngles; vector3 boneOrg; vector3 projectedBoneOrg; vector3 collisionRootPos; mdxaBone_t matrix; trace_t bestCollision; qboolean hasFirstCollision = qfalse; int i = 0; //Maybe we could use a trap call and get the default radius for the bone specified, //but this will do at least for now. VectorSet( &tMins, -3, -3, -3 ); VectorSet( &tMaxs, 3, 3, 3 ); gbmAngles.pitch = gbmAngles.roll = 0; gbmAngles.yaw = ent->s.apos.trBase.yaw; //Get the difference relative to the entity origin and projected origin, to add to each bolt position. VectorSubtract( &ent->r.currentOrigin, &projectedOrigin, &trajDif ); while ( i < numG2Bolts ) { //Get the position of the actual bolt for this frame trap->G2API_GetBoltMatrix( ent->ghoul2, 0, g2Bolts[i], &matrix, &gbmAngles, &ent->r.currentOrigin, level.time, NULL, &ent->modelScale ); BG_GiveMeVectorFromMatrix( &matrix, ORIGIN, &boneOrg ); //Now add the projected positional difference into the result VectorAdd( &boneOrg, &trajDif, &projectedBoneOrg ); trap->Trace( &tr, &boneOrg, &tMins, &tMaxs, &projectedBoneOrg, ent->s.number, ent->clipmask, qfalse, 0, 0 ); if ( tr.fraction != 1.0f || tr.startsolid || tr.allsolid ) { //we've hit something //Store the "deepest" collision we have if ( !hasFirstCollision ) { //don't have one yet so just use this one bestCollision = tr; VectorCopy( &boneOrg, &collisionRootPos ); hasFirstCollision = qtrue; } else { if ( tr.allsolid && !bestCollision.allsolid ) { //If the whole trace is solid then this one is deeper bestCollision = tr; VectorCopy( &boneOrg, &collisionRootPos ); } else if ( tr.startsolid && !bestCollision.startsolid && !bestCollision.allsolid ) { //Next deepest is if it's startsolid bestCollision = tr; VectorCopy( &boneOrg, &collisionRootPos ); } else if ( !bestCollision.startsolid && !bestCollision.allsolid && tr.fraction < bestCollision.fraction ) { //and finally, if neither is startsolid/allsolid, but the new one has a smaller fraction, then it's closer to an impact point so we will use it bestCollision = tr; VectorCopy( &boneOrg, &collisionRootPos ); } } } i++; } if ( hasFirstCollision ) { //at least one bolt collided //We'll get the offset between the collided bolt and endpos, then trace there //from the origin so that our desired position becomes that point. VectorSubtract( &collisionRootPos, &bestCollision.endpos, &trajDif ); VectorAdd( &ent->r.currentOrigin, &trajDif, &projectedOrigin ); } } //If we didn't collide with any bolts projectedOrigin will still be the original desired //projected position so all is well. If we did then projectedOrigin will be modified //to provide us with a relative position which does not place the bolt in a solid. trap->Trace( &tr, &ent->r.currentOrigin, &ent->r.mins, &ent->r.maxs, &projectedOrigin, ent->s.number, ent->clipmask, qfalse, 0, 0 ); if ( tr.startsolid || tr.allsolid ) { //can't go anywhere from here #ifdef _DEBUG Com_Printf( "ExPhys object in solid (%i)\n", ent->s.number ); #endif if ( autoKill ) { ent->think = G_FreeEntity; ent->nextthink = level.time; } return; } //Go ahead and set it to the trace endpoint regardless of what it hit G_SetOrigin( ent, &tr.endpos ); trap->LinkEntity( (sharedEntity_t *)ent ); if ( tr.fraction == 1.0f ) { //Nothing was in the way. return; } if ( bounce ) { vTotal *= bounce; //scale it by bounce VectorScale( &tr.plane.normal, vTotal, &vNorm ); //scale the trace plane normal by the bounce factor if ( vNorm.z > 0 ) { ent->epGravFactor -= vNorm.z*(1.0f - mass); //The lighter it is the more gravity will be reduced by bouncing vertically. if ( ent->epGravFactor < 0 ) { ent->epGravFactor = 0; } } //call touch first so we can check velocity upon impact if we want if ( tr.entityNum != ENTITYNUM_NONE && ent->touch ) { //then call the touch function ent->touch( ent, &g_entities[tr.entityNum], &tr ); } VectorAdd( &ent->epVelocity, &vNorm, &ent->epVelocity ); //add it into the existing velocity. } else { //if no bounce, kill when it hits something. ent->epVelocity.x = 0; ent->epVelocity.y = 0; if ( !gravity ) { ent->epVelocity.z = 0; } } }
/* ================ LaunchItem Spawns an item and tosses it forward ================ */ gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity ) { gentity_t *dropped; dropped = G_Spawn(); dropped->s.eType = ET_ITEM; dropped->s.modelindex = item - bg_itemlist; // store item number in modelindex dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item dropped->classname = item->classname; dropped->item = item; VectorSet (dropped->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS); VectorSet (dropped->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS); dropped->r.contents = CONTENTS_TRIGGER; dropped->touch = Touch_Item; G_SetOrigin( dropped, origin ); dropped->s.pos.trType = TR_GRAVITY; dropped->s.pos.trTime = level.time; VectorCopy( velocity, dropped->s.pos.trDelta ); dropped->s.eFlags |= EF_BOUNCE_HALF; #ifdef MISSIONPACK if ((g_gametype.integer == GT_CTF || g_gametype.integer == GT_1FCTF) && item->giType == IT_TEAM) { // Special case for CTF flags #else if (g_gametype.integer == GT_CTF && item->giType == IT_TEAM) { // Special case for CTF flags #endif dropped->think = Team_DroppedFlagThink; dropped->nextthink = level.time + 30000; Team_CheckDroppedItem( dropped ); } else { // auto-remove after 30 seconds dropped->think = G_FreeEntity; dropped->nextthink = level.time + 30000; } dropped->flags = FL_DROPPED_ITEM; trap_LinkEntity (dropped); return dropped; } /* ================ Drop_Item Spawns an item and tosses it forward ================ */ gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle ) { vec3_t velocity; vec3_t angles; VectorCopy( ent->s.apos.trBase, angles ); angles[YAW] += angle; angles[PITCH] = 0; // always forward AngleVectors( angles, velocity, NULL, NULL ); VectorScale( velocity, 150, velocity ); velocity[2] += 200 + crandom() * 50; return LaunchItem( item, ent->s.pos.trBase, velocity ); }
/* =========== ClientSpawn Called every time a client is placed fresh in the world: after the first ClientBegin, and after each respawn Initializes all non-persistant parts of playerState ============ */ void ClientSpawn(gentity_t *ent) { int index; vec3_t spawn_origin, spawn_angles; gclient_t *client; int i; clientPersistant_t saved; clientSession_t savedSess; int persistant[MAX_PERSISTANT]; gentity_t *spawnPoint; int flags; int savedPing; // char *savedAreaBits; int accuracy_hits, accuracy_shots; int eventSequence; char userinfo[MAX_INFO_STRING]; index = ent - g_entities; client = ent->client; // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { spawnPoint = SelectSpectatorSpawnPoint ( spawn_origin, spawn_angles); } else if (g_gametype.integer >= GT_CTF ) { // all base oriented team games use the CTF spawn points spawnPoint = SelectCTFSpawnPoint ( client->sess.sessionTeam, client->pers.teamState.state, spawn_origin, spawn_angles); } else { do { // the first spawn should be at a good looking spot if ( !client->pers.initialSpawn && client->pers.localClient ) { client->pers.initialSpawn = qtrue; spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles ); } else { // don't spawn near existing origin if possible spawnPoint = SelectSpawnPoint ( client->ps.origin, spawn_origin, spawn_angles); } // Tim needs to prevent bots from spawning at the initial point // on q3dm0... if ( ( spawnPoint->flags & FL_NO_BOTS ) && ( ent->r.svFlags & SVF_BOT ) ) { continue; // try again } // just to be symetric, we have a nohumans option... if ( ( spawnPoint->flags & FL_NO_HUMANS ) && !( ent->r.svFlags & SVF_BOT ) ) { continue; // try again } break; } while ( 1 ); } client->pers.teamState.state = TEAM_ACTIVE; // always clear the kamikaze flag ent->s.eFlags &= ~EF_KAMIKAZE; // toggle the teleport bit so the client knows to not lerp // and never clear the voted flag flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED); flags ^= EF_TELEPORT_BIT; // clear everything but the persistant data saved = client->pers; savedSess = client->sess; savedPing = client->ps.ping; // savedAreaBits = client->areabits; accuracy_hits = client->accuracy_hits; accuracy_shots = client->accuracy_shots; for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { persistant[i] = client->ps.persistant[i]; } eventSequence = client->ps.eventSequence; memset (client, 0, sizeof(*client)); // bk FIXME: Com_Memset? client->pers = saved; client->sess = savedSess; client->ps.ping = savedPing; // client->areabits = savedAreaBits; client->accuracy_hits = accuracy_hits; client->accuracy_shots = accuracy_shots; client->lastkilled_client = -1; for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { client->ps.persistant[i] = persistant[i]; } client->ps.eventSequence = eventSequence; // increment the spawncount so the client will detect the respawn client->ps.persistant[PERS_SPAWN_COUNT]++; client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; client->airOutTime = level.time + 12000; trap_GetUserinfo( index, userinfo, sizeof(userinfo) ); // set max health client->pers.maxHealth = atoi( Info_ValueForKey( userinfo, "handicap" ) ); if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { client->pers.maxHealth = 100; } // clear entity values client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; client->ps.eFlags = flags; ent->s.groundEntityNum = ENTITYNUM_NONE; ent->client = &level.clients[index]; ent->takedamage = qtrue; ent->inuse = qtrue; ent->classname = "player"; ent->r.contents = CONTENTS_BODY; ent->clipmask = MASK_PLAYERSOLID; ent->die = player_die; ent->waterlevel = 0; ent->watertype = 0; ent->flags = 0; VectorCopy (playerMins, ent->r.mins); VectorCopy (playerMaxs, ent->r.maxs); client->ps.clientNum = index; client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN ); if ( g_gametype.integer == GT_TEAM ) { client->ps.ammo[WP_MACHINEGUN] = 50; } else { client->ps.ammo[WP_MACHINEGUN] = 100; } client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET ); client->ps.ammo[WP_GAUNTLET] = -1; client->ps.ammo[WP_GRAPPLING_HOOK] = -1; // health will count down towards max_health ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] + 25; G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); // the respawned flag will be cleared after the attack and jump keys come up client->ps.pm_flags |= PMF_RESPAWNED; trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); SetClientViewAngle( ent, spawn_angles ); if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { } else { G_KillBox( ent ); trap_LinkEntity (ent); // force the base weapon up client->ps.weapon = WP_MACHINEGUN; client->ps.weaponstate = WEAPON_READY; } // don't allow full run speed for a bit client->ps.pm_flags |= PMF_TIME_KNOCKBACK; client->ps.pm_time = 100; client->respawnTime = level.time; client->inactivityTime = level.time + g_inactivity.integer * 1000; client->latched_buttons = 0; // set default animations client->ps.torsoAnim = TORSO_STAND; client->ps.legsAnim = LEGS_IDLE; if ( level.intermissiontime ) { MoveClientToIntermission( ent ); } else { // fire the targets of the spawn point G_UseTargets( spawnPoint, ent ); // select the highest weapon number available, after any // spawn given items have fired client->ps.weapon = 1; for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) { if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) { client->ps.weapon = i; break; } } } // run a client frame to drop exactly to the floor, // initialize animations and other things client->ps.commandTime = level.time - 100; ent->client->pers.cmd.serverTime = level.time; ClientThink( ent-g_entities ); // positively link the client, even if the command times are weird if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); } // run the presend to set anything else ClientEndFrame( ent ); // clear entity state values BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); }
/*QUAKED misc_model_breakable (1 0 0) (-16 -16 -16) (16 16 16) SOLID AUTOANIMATE DEADSOLID NO_DMODEL NO_SMOKE USE_MODEL USE_NOT_BREAK PLAYER_USE NO_EXPLOSION SOLID - Movement is blocked by it, if not set, can still be broken by explosions and shots if it has health AUTOANIMATE - Will cycle it's anim DEADSOLID - Stay solid even when destroyed (in case damage model is rather large). NO_DMODEL - Makes it NOT display a damage model when destroyed, even if one exists USE_MODEL - When used, will toggle to it's usemodel (model name + "_u1.md3")... this obviously does nothing if USE_NOT_BREAK is not checked USE_NOT_BREAK - Using it, doesn't make it break, still can be destroyed by damage PLAYER_USE - Player can use it with the use button NO_EXPLOSION - By default, will explode when it dies...this is your override. "model" arbitrary .md3 file to display "health" how much health to have - default is zero (not breakable) If you don't set the SOLID flag, but give it health, it can be shot but will not block NPCs or players from moving "targetname" when used, dies and displays damagemodel, if any (if not, removes itself) "target" What to use when it dies "target2" What to use when it's repaired "target3" What to use when it's used while it's broken "paintarget" target to fire when hit (but not destroyed) "count" the amount of armor/health/ammo given (default 50) "gravity" if set to 1, this will be affected by gravity "radius" Chunk code tries to pick a good volume of chunks, but you can alter this to scale the number of spawned chunks. (default 1) (.5) is half as many chunks, (2) is twice as many chunks Damage: default is none "splashDamage" - damage to do (will make it explode on death) "splashRadius" - radius for above damage "team" - This cannot take damage from members of this team: "player" "neutral" "enemy" "material" - default is "8 - MAT_NONE" - choose from this list: 0 = MAT_METAL (grey metal) 1 = MAT_GLASS 2 = MAT_ELECTRICAL (sparks only) 3 = MAT_ELEC_METAL (METAL chunks and sparks) 4 = MAT_DRK_STONE (brown stone chunks) 5 = MAT_LT_STONE (tan stone chunks) 6 = MAT_GLASS_METAL (glass and METAL chunks) 7 = MAT_METAL2 (blue/grey metal) 8 = MAT_NONE (no chunks-DEFAULT) 9 = MAT_GREY_STONE (grey colored stone) 10 = MAT_METAL3 (METAL and METAL2 chunk combo) 11 = MAT_CRATE1 (yellow multi-colored crate chunks) 12 = MAT_GRATE1 (grate chunks--looks horrible right now) 13 = MAT_ROPE (for yavin_trial, no chunks, just wispy bits ) 14 = MAT_CRATE2 (red multi-colored crate chunks) 15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout ) FIXME/TODO: set size better? multiple damage models? custom explosion effect/sound? */ void SP_misc_model_breakable( gentity_t *ent ) { char damageModel[MAX_QPATH]; char chunkModel[MAX_QPATH]; char useModel[MAX_QPATH]; int len; // Chris F. requested default for misc_model_breakable to be NONE...so don't arbitrarily change this. G_SpawnInt( "material", "8", (int*)&ent->material ); G_SpawnFloat( "radius", "1", &ent->radius ); // used to scale chunk code if desired by a designer CacheChunkEffects( ent->material ); misc_model_breakable_init( ent ); len = strlen( ent->model ) - 4; strncpy( damageModel, ent->model, len ); damageModel[len] = 0; //chop extension strncpy( chunkModel, damageModel, sizeof(chunkModel)); strncpy( useModel, damageModel, sizeof(useModel)); if (ent->takedamage) { //Dead/damaged model if( !(ent->spawnflags & 8) ) { //no dmodel strcat( damageModel, "_d1.md3" ); ent->s.modelindex2 = G_ModelIndex( damageModel ); } //Chunk model strcat( chunkModel, "_c1.md3" ); ent->s.modelindex3 = G_ModelIndex( chunkModel ); } //Use model if( ent->spawnflags & 32 ) { //has umodel strcat( useModel, "_u1.md3" ); ent->sound1to2 = G_ModelIndex( useModel ); } if ( !ent->mins[0] && !ent->mins[1] && !ent->mins[2] ) { VectorSet (ent->mins, -16, -16, -16); } if ( !ent->maxs[0] && !ent->maxs[1] && !ent->maxs[2] ) { VectorSet (ent->maxs, 16, 16, 16); } if ( ent->spawnflags & 2 ) { ent->s.eFlags |= EF_ANIM_ALLFAST; } G_SetOrigin( ent, ent->s.origin ); G_SetAngles( ent, ent->s.angles ); gi.linkentity (ent); if ( ent->spawnflags & 128 ) {//Can be used by the player's BUTTON_USE ent->svFlags |= SVF_PLAYER_USABLE; } if ( ent->team && ent->team[0] ) { ent->noDamageTeam = TranslateTeamName( ent->team ); if ( ent->noDamageTeam == TEAM_FREE ) { G_Error("team name %s not recognized\n", ent->team); } } ent->team = NULL; //HACK if ( ent->model && Q_stricmp( "models/map_objects/ships/tie_fighter.md3", ent->model ) == 0 ) {//run a think G_EffectIndex( "fighter_explosion2" ); G_SoundIndex( "sound/weapons/tie_fighter/tiepass1.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tiepass2.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tiepass3.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tiepass4.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tiepass5.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tie_fire.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tie_fire2.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tie_fire3.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" ); ent->e_ThinkFunc = thinkF_TieFighterThink; ent->nextthink = level.time + FRAMETIME; } float grav = 0; G_SpawnFloat( "gravity", "0", &grav ); if ( grav ) {//affected by gravity G_SetAngles( ent, ent->s.angles ); G_SetOrigin( ent, ent->currentOrigin ); misc_model_breakable_gravity_init( ent, qtrue ); } }
/*QUAKED target_deactivate (1 0 0) (-4 -4 -4) (4 4 4) Will set the target(s) to be non-usable/triggerable */ void SP_target_deactivate( gentity_t *self ) { G_SetOrigin( self, self->s.origin ); self->use = target_deactivate_use; }
//----------------------------------------------------- void finish_spawning_turretG2( gentity_t *base ) { vec3_t fwd; int t; if ( (base->spawnflags&2) ) { base->s.angles[ROLL] += 180; base->s.origin[2] -= 22.0f; } G_SetAngles( base, base->s.angles ); AngleVectors( base->r.currentAngles, fwd, NULL, NULL ); G_SetOrigin(base, base->s.origin); base->s.eType = ET_GENERAL; if ( base->team && base->team[0] && //g_gametype.integer == GT_SIEGE && !base->teamnodmg) { base->teamnodmg = atoi(base->team); } base->team = NULL; // Set up our explosion effect for the ExplodeDeath code.... G_EffectIndex( "turret/explode" ); G_EffectIndex( "sparks/spark_exp_nosnd" ); base->use = turretG2_base_use; base->pain = TurretG2Pain; // don't start working right away base->think = turretG2_base_think; base->nextthink = level.time + FRAMETIME * 5; // this is really the pitch angle..... base->speed = 0; // respawn time defaults to 20 seconds if ( (base->spawnflags&SPF_TURRETG2_CANRESPAWN) && !base->count ) { base->count = 20000; } G_SpawnFloat( "shotspeed", "0", &base->mass ); if ( (base->spawnflags&SPF_TURRETG2_TURBO) ) { if ( !base->random ) {//error worked into projectile direction base->random = 2.0f; } if ( !base->mass ) {//misnomer: speed of projectile base->mass = 20000; } if ( !base->health ) { base->health = 2000; } // search radius if ( !base->radius ) { base->radius = 32768; } // How quickly to fire if ( !base->wait ) { base->wait = 1000;// + random() * 500; } if ( !base->splashDamage ) { base->splashDamage = 200; } if ( !base->splashRadius ) { base->splashRadius = 500; } // how much damage each shot does if ( !base->damage ) { base->damage = 500; } if ( (base->spawnflags&SPF_TURRETG2_TURBO) ) { VectorSet( base->r.maxs, 64.0f, 64.0f, 30.0f ); VectorSet( base->r.mins, -64.0f, -64.0f, -30.0f ); } //start in "off" anim TurboLaser_SetBoneAnim( base, 4, 5 ); } else { if ( !base->random ) {//error worked into projectile direction base->random = 2.0f; } if ( !base->mass ) {//misnomer: speed of projectile base->mass = 1100; } if ( !base->health ) { base->health = 100; } // search radius if ( !base->radius ) { base->radius = 512; } // How quickly to fire if ( !base->wait ) { base->wait = 150 + random() * 55; } if ( !base->splashDamage ) { base->splashDamage = 10; } if ( !base->splashRadius ) { base->splashRadius = 25; } // how much damage each shot does if ( !base->damage ) { base->damage = 5; } if ( base->spawnflags & 2 ) {//upside-down, invert r.mins and maxe VectorSet( base->r.maxs, 10.0f, 10.0f, 30.0f ); VectorSet( base->r.mins, -10.0f, -10.0f, 0.0f ); } else { VectorSet( base->r.maxs, 10.0f, 10.0f, 0.0f ); VectorSet( base->r.mins, -10.0f, -10.0f, -30.0f ); } } //stash health off for respawn. NOTE: cannot use maxhealth because that might not be set if not showing the health bar base->genericValue6 = base->health; G_SpawnInt( "showhealth", "0", &t ); if (t) { //a non-0 maxhealth value will mean we want to show the health on the hud base->maxHealth = base->health; G_ScaleNetHealth(base); base->s.shouldtarget = qtrue; //base->s.owner = MAX_CLIENTS; //not owned by any client } if (base->s.iModelScale) { //let's scale the bbox too... float fScale = base->s.iModelScale/100.0f; VectorScale(base->r.mins, fScale, base->r.mins); VectorScale(base->r.maxs, fScale, base->r.maxs); } // Precache special FX and moving sounds if ( (base->spawnflags&SPF_TURRETG2_TURBO) ) { base->genericValue13 = G_EffectIndex( "turret/turb_muzzle_flash" ); base->genericValue14 = G_EffectIndex( "turret/turb_shot" ); base->genericValue15 = G_EffectIndex( "turret/turb_impact" ); //FIXME: Turbo Laser Cannon sounds! G_SoundIndex( "sound/vehicles/weapons/turbolaser/turn.wav" ); } else { G_SoundIndex( "sound/chars/turret/startup.wav" ); G_SoundIndex( "sound/chars/turret/shutdown.wav" ); G_SoundIndex( "sound/chars/turret/ping.wav" ); G_SoundIndex( "sound/chars/turret/move.wav" ); } base->r.contents = CONTENTS_BODY|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_SHOTCLIP; //base->max_health = base->health; base->takedamage = qtrue; base->die = turretG2_die; base->material = MAT_METAL; //base->r.svFlags |= SVF_NO_TELEPORT|SVF_NONNPC_ENEMY|SVF_SELF_ANIMATING; // Register this so that we can use it for the missile effect RegisterItem( BG_FindItemForWeapon( WP_BLASTER )); // But set us as a turret so that we can be identified as a turret base->s.weapon = WP_TURRET; trap_LinkEntity( base ); }
/*QUAKED info_camp (0 0.5 0) (-4 -4 -4) (4 4 4) Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay. */ void SP_info_camp(gentity_t * self) { G_SetOrigin(self, self->s.origin); }
/*QUAKED target_autosave (1 0 0) (-4 -4 -4) (4 4 4) Auto save the game in two frames. Make sure it won't trigger during dialogue or cinematic or it will stutter! */ void SP_target_autosave( gentity_t *self ) { G_SetOrigin( self, self->s.origin ); self->e_UseFunc = useF_target_autosave_use; }
/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) Used as a positional target for in-game calculation, like jumppad targets. target_position does the same thing */ void SP_info_notnull(gentity_t * self) { G_SetOrigin(self, self->s.origin); }
/*QUAKED target_location (0 0.5 0) (-8 -8 -8) (8 8 8) Set "message" to the name of this location. Set "count" to 0-7 for color. 0:white 1:red 2:green 3:yellow 4:blue 5:cyan 6:magenta 7:white Closest target_location in sight used for the location, if none in site, closest in distance */ void SP_target_location( gentity_t *self ){ self->e_ThinkFunc = thinkF_target_location_linkup; self->nextthink = level.time + 1000; // Let them all spawn first G_SetOrigin( self, self->s.origin ); }
/* ============= SpawnCorpse A player is respawning, so make an entity that looks just like the existing corpse to leave behind. ============= */ static void SpawnCorpse( gentity_t *ent ) { gentity_t *body; int contents; vec3_t origin, mins; VectorCopy( ent->r.currentOrigin, origin ); trap_UnlinkEntity( ent ); // if client is in a nodrop area, don't leave the body contents = trap_PointContents( origin, -1 ); if ( contents & CONTENTS_NODROP ) { return; } body = G_NewEntity(); VectorCopy( ent->s.apos.trBase, body->s.angles ); body->s.eFlags = EF_DEAD; body->s.eType = ET_CORPSE; body->timestamp = level.time; body->s.event = 0; body->r.contents = CONTENTS_CORPSE; body->clipmask = MASK_DEADSOLID; body->s.clientNum = ent->client->ps.stats[ STAT_CLASS ]; body->nonSegModel = ent->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL; if ( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { body->classname = "humanCorpse"; } else { body->classname = "alienCorpse"; } body->s.misc = MAX_CLIENTS; body->think = BodySink; body->nextthink = level.time + 20000; body->s.legsAnim = ent->s.legsAnim; if ( !body->nonSegModel ) { switch ( body->s.legsAnim & ~ANIM_TOGGLEBIT ) { case BOTH_DEATH1: case BOTH_DEAD1: body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1; break; case BOTH_DEATH2: case BOTH_DEAD2: body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2; break; case BOTH_DEATH3: case BOTH_DEAD3: default: body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3; break; } } else { switch ( body->s.legsAnim & ~ANIM_TOGGLEBIT ) { case NSPA_DEATH1: case NSPA_DEAD1: body->s.legsAnim = NSPA_DEAD1; break; case NSPA_DEATH2: case NSPA_DEAD2: body->s.legsAnim = NSPA_DEAD2; break; case NSPA_DEATH3: case NSPA_DEAD3: default: body->s.legsAnim = NSPA_DEAD3; break; } } body->takedamage = qfalse; body->health = ent->health = ent->client->ps.stats[ STAT_HEALTH ]; ent->health = 0; //change body dimensions BG_ClassBoundingBox( ent->client->ps.stats[ STAT_CLASS ], mins, NULL, NULL, body->r.mins, body->r.maxs ); //drop down to match the *model* origins of ent and body origin[2] += mins[ 2 ] - body->r.mins[ 2 ]; G_SetOrigin( body, origin ); body->s.pos.trType = TR_GRAVITY; body->s.pos.trTime = level.time; VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); trap_LinkEntity( body ); }
/*QUAKED target_friction_change (1 0 0) (-4 -4 -4) (4 4 4) "friction" Normal = 6, Valid range 0 - 10 */ void SP_target_friction_change( gentity_t *self ) { G_SetOrigin( self, self->s.origin ); self->e_UseFunc = useF_target_friction_change_use; }
static gentity_t *SpawnModelOnVictoryPad( gentity_t *pad, vec3_t offset, gentity_t *ent, int place ) { gentity_t *body; vec3_t vec; vec3_t f, r, u; body = G_Spawn(); if ( !body ) { G_Printf( S_COLOR_RED "ERROR: out of gentities\n" ); return NULL; } body->classname = ent->client->pers.netname; body->client = ent->client; body->s = ent->s; body->s.eType = ET_PLAYER; // could be ET_INVISIBLE body->s.eFlags = 0; // clear EF_TALK, etc body->s.powerups = 0; // clear powerups body->s.loopSound = 0; // clear lava burning body->s.number = body - g_entities; body->timestamp = level.time; body->physicsObject = qtrue; body->physicsBounce = 0; // don't bounce body->s.event = 0; body->s.pos.trType = TR_STATIONARY; body->s.groundEntityNum = ENTITYNUM_WORLD; body->s.legsAnim = LEGS_IDLE; body->s.torsoAnim = TORSO_STAND; if( body->s.weapon == WP_NONE ) { body->s.weapon = WP_MACHINEGUN; } if( body->s.weapon == WP_GAUNTLET) { body->s.torsoAnim = TORSO_STAND2; } body->s.event = 0; body->r.svFlags = ent->r.svFlags; 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); body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; body->r.contents = CONTENTS_BODY; body->r.ownerNum = ent->r.ownerNum; body->takedamage = qfalse; VectorSubtract( level.intermission_origin, pad->r.currentOrigin, vec ); vectoangles( vec, body->s.apos.trBase ); body->s.apos.trBase[PITCH] = 0; body->s.apos.trBase[ROLL] = 0; AngleVectors( body->s.apos.trBase, f, r, u ); VectorMA( pad->r.currentOrigin, offset[0], f, vec ); VectorMA( vec, offset[1], r, vec ); VectorMA( vec, offset[2], u, vec ); G_SetOrigin( body, vec ); trap_LinkEntity (body); body->count = place; return body; }
/* =========== ClientSpawn Called every time a client is placed fresh in the world: after the first ClientBegin, and after each respawn Initializes all non-persistant parts of playerState ============ */ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ) { int index; vec3_t spawn_origin, spawn_angles; gclient_t *client; int i; clientPersistant_t saved; clientSession_t savedSess; int persistant[ MAX_PERSISTANT ]; gentity_t *spawnPoint = NULL; int flags; int savedPing; int teamLocal; int eventSequence; char userinfo[ MAX_INFO_STRING ]; vec3_t up = { 0.0f, 0.0f, 1.0f }; int maxAmmo, maxClips; weapon_t weapon; index = ent - g_entities; client = ent->client; teamLocal = client->pers.teamSelection; //if client is dead and following teammate, stop following before spawning if( client->sess.spectatorClient != -1 ) { client->sess.spectatorClient = -1; client->sess.spectatorState = SPECTATOR_FREE; } // only start client if chosen a class and joined a team if( client->pers.classSelection == PCL_NONE && teamLocal == TEAM_NONE ) client->sess.spectatorState = SPECTATOR_FREE; else if( client->pers.classSelection == PCL_NONE ) client->sess.spectatorState = SPECTATOR_LOCKED; // if client is dead and following teammate, stop following before spawning if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) G_StopFollowing( ent ); if( origin != NULL ) VectorCopy( origin, spawn_origin ); if( angles != NULL ) VectorCopy( angles, spawn_angles ); // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client if( client->sess.spectatorState != SPECTATOR_NOT ) { if( teamLocal == TEAM_NONE ) spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); else if( teamLocal == TEAM_ALIENS ) spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); else if( teamLocal == TEAM_HUMANS ) spawnPoint = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); } else { if( spawn == NULL ) { G_Error( "ClientSpawn: spawn is NULL\n" ); return; } spawnPoint = spawn; if( ent != spawn ) { //start spawn animation on spawnPoint G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, qtrue ); if( spawnPoint->buildableTeam == TEAM_ALIENS ) spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; else if( spawnPoint->buildableTeam == TEAM_HUMANS ) spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; } } // toggle the teleport bit so the client knows to not lerp flags = ( ent->client->ps.eFlags & EF_TELEPORT_BIT ) ^ EF_TELEPORT_BIT; G_UnlaggedClear( ent ); // clear everything but the persistant data saved = client->pers; savedSess = client->sess; savedPing = client->ps.ping; for( i = 0; i < MAX_PERSISTANT; i++ ) persistant[ i ] = client->ps.persistant[ i ]; eventSequence = client->ps.eventSequence; memset( client, 0, sizeof( *client ) ); client->pers = saved; client->sess = savedSess; client->ps.ping = savedPing; client->lastkilled_client = -1; for( i = 0; i < MAX_PERSISTANT; i++ ) client->ps.persistant[ i ] = persistant[ i ]; client->ps.eventSequence = eventSequence; // increment the spawncount so the client will detect the respawn client->ps.persistant[ PERS_SPAWN_COUNT ]++; client->ps.persistant[ PERS_SPECSTATE ] = client->sess.spectatorState; client->airOutTime = level.time + 12000; trap_GetUserinfo( index, userinfo, sizeof( userinfo ) ); client->ps.eFlags = flags; //Com_Printf( "ent->client->pers->pclass = %i\n", ent->client->pers.classSelection ); ent->s.groundEntityNum = ENTITYNUM_NONE; ent->client = &level.clients[ index ]; ent->takedamage = qtrue; ent->inuse = qtrue; ent->classname = "player"; ent->r.contents = CONTENTS_BODY; ent->clipmask = MASK_PLAYERSOLID; ent->die = player_die; ent->waterlevel = 0; ent->watertype = 0; ent->flags = 0; // calculate each client's acceleration ent->evaluateAcceleration = qtrue; client->ps.stats[ STAT_MISC ] = 0; client->ps.eFlags = flags; client->ps.clientNum = index; BG_ClassBoundingBox( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL ); if( client->sess.spectatorState == SPECTATOR_NOT ) client->ps.stats[ STAT_MAX_HEALTH ] = BG_Class( ent->client->pers.classSelection )->health; else client->ps.stats[ STAT_MAX_HEALTH ] = 100; // clear entity values if( ent->client->pers.classSelection == PCL_HUMAN ) { BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats ); weapon = client->pers.humanItemSelection; } else if( client->sess.spectatorState == SPECTATOR_NOT ) weapon = BG_Class( ent->client->pers.classSelection )->startWeapon; else weapon = WP_NONE; maxAmmo = BG_Weapon( weapon )->maxAmmo; maxClips = BG_Weapon( weapon )->maxClips; client->ps.stats[ STAT_WEAPON ] = weapon; client->ps.ammo = maxAmmo; client->ps.clips = maxClips; // We just spawned, not changing weapons client->ps.persistant[ PERS_NEWWEAPON ] = 0; ent->client->ps.stats[ STAT_CLASS ] = ent->client->pers.classSelection; ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; ent->client->ps.stats[ STAT_STATE ] = 0; VectorSet( ent->client->ps.grapplePoint, 0.0f, 0.0f, 1.0f ); // health will count down towards max_health ent->health = client->ps.stats[ STAT_HEALTH ] = client->ps.stats[ STAT_MAX_HEALTH ]; //* 1.25; //if evolving scale health if( ent == spawn ) { ent->health *= ent->client->pers.evolveHealthFraction; client->ps.stats[ STAT_HEALTH ] *= ent->client->pers.evolveHealthFraction; } //clear the credits array for( i = 0; i < MAX_CLIENTS; i++ ) ent->credits[ i ] = 0; client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); #define UP_VEL 150.0f #define F_VEL 50.0f //give aliens some spawn velocity if( client->sess.spectatorState == SPECTATOR_NOT && client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) { if( ent == spawn ) { //evolution particle system G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) ); } else { spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); if( spawnPoint->s.origin2[ 2 ] > 0.0f ) { vec3_t forward, dir; AngleVectors( spawn_angles, forward, NULL, NULL ); VectorScale( forward, F_VEL, forward ); VectorAdd( spawnPoint->s.origin2, forward, dir ); VectorNormalize( dir ); VectorScale( dir, UP_VEL, client->ps.velocity ); } G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); } } else if( client->sess.spectatorState == SPECTATOR_NOT && client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); } // the respawned flag will be cleared after the attack and jump keys come up client->ps.pm_flags |= PMF_RESPAWNED; trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); G_SetClientViewAngle( ent, spawn_angles ); if( client->sess.spectatorState == SPECTATOR_NOT ) { trap_LinkEntity( ent ); // force the base weapon up if( client->pers.teamSelection == TEAM_HUMANS ) G_ForceWeaponChange( ent, weapon ); client->ps.weaponstate = WEAPON_READY; } // don't allow full run speed for a bit client->ps.pm_flags |= PMF_TIME_KNOCKBACK; client->ps.pm_time = 100; client->respawnTime = level.time; ent->nextRegenTime = level.time; client->inactivityTime = level.time + g_inactivity.integer * 1000; client->latched_buttons = 0; // set default animations client->ps.torsoAnim = TORSO_STAND; client->ps.legsAnim = LEGS_IDLE; if( level.intermissiontime ) MoveClientToIntermission( ent ); else { // fire the targets of the spawn point if( !spawn ) G_UseTargets( spawnPoint, ent ); // select the highest weapon number available, after any // spawn given items have fired client->ps.weapon = 1; for( i = WP_NUM_WEAPONS - 1; i > 0 ; i-- ) { if( BG_InventoryContainsWeapon( i, client->ps.stats ) ) { client->ps.weapon = i; break; } } } // run a client frame to drop exactly to the floor, // initialize animations and other things client->ps.commandTime = level.time - 100; ent->client->pers.cmd.serverTime = level.time; ClientThink( ent-g_entities ); // positively link the client, even if the command times are weird if( client->sess.spectatorState == SPECTATOR_NOT ) { BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); } // must do this here so the number of active clients is calculated CalculateRanks( ); // run the presend to set anything else ClientEndFrame( ent ); // clear entity state values BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); }
/* ================ LaunchItem Spawns an item and tosses it forward ================ */ gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity, char *target ) { gentity_t *dropped; dropped = G_Spawn(); dropped->s.eType = ET_ITEM; dropped->s.modelindex = item - bg_itemlist; // store item number in modelindex dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item dropped->classname = item->classname; dropped->item = item; // try using the "correct" mins/maxs first VectorSet( dropped->mins, item->mins[0], item->mins[1], item->mins[2] ); VectorSet( dropped->maxs, item->maxs[0], item->maxs[1], item->maxs[2] ); if ((!dropped->mins[0] && !dropped->mins[1] && !dropped->mins[2]) && (!dropped->maxs[0] && !dropped->maxs[1] && !dropped->maxs[2])) { VectorSet( dropped->maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS ); VectorScale( dropped->maxs, -1, dropped->mins ); } dropped->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;//CONTENTS_TRIGGER;//not CONTENTS_BODY for dropped items, don't need to ID them if ( target && target[0] ) { dropped->target = G_NewString( target ); } else { // if not targeting something, auto-remove after 30 seconds // only if it's NOT a security or goodie key if (dropped->item->giTag != INV_SECURITY_KEY ) { dropped->e_ThinkFunc = thinkF_G_FreeEntity; dropped->nextthink = level.time + 30000; } if ( dropped->item->giType == IT_AMMO && dropped->item->giTag == AMMO_FORCE ) { dropped->nextthink = -1; dropped->e_ThinkFunc = thinkF_NULL; } } dropped->e_TouchFunc = touchF_Touch_Item; if ( item->giType == IT_WEAPON ) { // give weapon items zero pitch, a random yaw, and rolled onto their sides...but would be bad to do this for a bowcaster if ( item->giTag != WP_BOWCASTER && item->giTag != WP_THERMAL && item->giTag != WP_TRIP_MINE && item->giTag != WP_DET_PACK ) { VectorSet( dropped->s.angles, 0, crandom() * 180, 90.0f ); G_SetAngles( dropped, dropped->s.angles ); } } G_SetOrigin( dropped, origin ); dropped->s.pos.trType = TR_GRAVITY; dropped->s.pos.trTime = level.time; VectorCopy( velocity, dropped->s.pos.trDelta ); dropped->s.eFlags |= EF_BOUNCE_HALF; dropped->flags = FL_DROPPED_ITEM; gi.linkentity (dropped); return dropped; }