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; }
void weapon_railgun_fire (gentity_t *ent) { vec3_t end; trace_t trace; gentity_t *tent; gentity_t *traceEnt; int damage; int i; int hits; int unlinked; int passent; gentity_t *unlinkedEntities[MAX_RAIL_HITS]; damage = 100 * s_quadFactor; VectorMA (muzzle, 8192, forward, end); // trace only against the solids, so the railgun will go through people unlinked = 0; hits = 0; passent = ent->s.number; do { trap_Trace (&trace, muzzle, NULL, NULL, end, passent, MASK_SHOT ); if ( trace.entityNum >= ENTITYNUM_MAX_NORMAL ) { break; } traceEnt = &g_entities[ trace.entityNum ]; if ( traceEnt->takedamage ) { if( LogAccuracyHit( traceEnt, ent ) ) { hits++; } G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_RAILGUN); } if ( trace.contents & CONTENTS_SOLID ) { break; // we hit something solid enough to stop the beam } // unlink this entity, so the next trace will go past it trap_UnlinkEntity( traceEnt ); unlinkedEntities[unlinked] = traceEnt; unlinked++; } while ( unlinked < MAX_RAIL_HITS ); // link back in any entities we unlinked for ( i = 0 ; i < unlinked ; i++ ) { trap_LinkEntity( unlinkedEntities[i] ); } // the final trace endpos will be the terminal point of the rail trail // snap the endpos to integers to save net bandwidth, but nudged towards the line SnapVectorTowards( trace.endpos, muzzle ); // send railgun beam effect tent = G_TempEntity( trace.endpos, EV_RAILTRAIL ); // set player number for custom colors on the railtrail tent->s.clientNum = ent->s.clientNum; VectorCopy( muzzle, tent->s.origin2 ); // move origin a bit to come closer to the drawn gun muzzle VectorMA( tent->s.origin2, 4, right, tent->s.origin2 ); VectorMA( tent->s.origin2, -1, up, tent->s.origin2 ); // no explosion at end if SURF_NOIMPACT, but still make the trail if ( trace.surfaceFlags & SURF_NOIMPACT ) { tent->s.eventParm = 255; // don't make the explosion at the end } else { tent->s.eventParm = DirToByte( trace.plane.normal ); } tent->s.clientNum = ent->s.clientNum; // give the shooter a reward sound if they have made two railgun hits in a row if ( hits == 0 ) { // complete miss ent->client->accurateCount = 0; } else { // check for "impressive" reward sound ent->client->accurateCount += hits; ent->client->accuracy_hits++; } }
/* ============= CopyToBodyQue A player is respawning, so make an entity that looks just like the existing corpse to leave behind. ============= */ void CopyToBodyQue( gentity_t *ent ) { #ifdef MISSIONPACK gentity_t *e; int i; #endif gentity_t *body; int contents; trap_UnlinkEntity (ent); // if client is in a nodrop area, don't leave the body contents = trap_PointContents( ent->s.origin, -1 ); if ( contents & CONTENTS_NODROP ) { return; } // grab a body que and cycle to the next one body = level.bodyQue[ level.bodyQueIndex ]; level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE; body->s = ent->s; body->s.eFlags = EF_DEAD; // clear EF_TALK, etc #ifdef MISSIONPACK if ( ent->s.eFlags & EF_KAMIKAZE ) { body->s.eFlags |= EF_KAMIKAZE; // check if there is a kamikaze timer around for this owner for (i = 0; i < MAX_GENTITIES; i++) { e = &g_entities[i]; if (!e->inuse) continue; if (e->activator != ent) continue; if (strcmp(e->classname, "kamikaze timer")) continue; e->activator = body; break; } } #endif 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 if ( body->s.groundEntityNum == ENTITYNUM_NONE ) { body->s.pos.trType = TR_GRAVITY; body->s.pos.trTime = level.time; VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); } else { body->s.pos.trType = TR_STATIONARY; } body->s.event = 0; // change the animation to the last-frame only, so the sequence // doesn't repeat anew for the body 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; } body->r.svFlags = ent->r.svFlags; VectorCopy (ent->s.mins, body->s.mins); VectorCopy (ent->s.maxs, body->s.maxs); VectorCopy (ent->r.absmin, body->r.absmin); VectorCopy (ent->r.absmax, body->r.absmax); body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; body->s.contents = CONTENTS_CORPSE; body->r.ownerNum = ent->s.number; body->nextthink = level.time + 5000; body->think = BodySink; body->die = body_die; // don't take more damage if already gibbed if ( ent->health <= GIB_HEALTH ) { body->takedamage = qfalse; } else { body->takedamage = qtrue; } VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); trap_LinkEntity (body); }
//QED comment is in bg_misc //------------------------------------------------------------ void SP_misc_shield_floor_unit( gentity_t *ent ) { vec3_t dest; trace_t tr; if (g_gametype.integer != GT_CTF && g_gametype.integer != GT_CTY) { G_FreeEntity( ent ); return; } VectorSet( ent->r.mins, -16, -16, 0 ); VectorSet( ent->r.maxs, 16, 16, 40 ); ent->s.origin[2] += 0.1; ent->r.maxs[2] -= 0.1; 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 ("SP_misc_shield_floor_unit: misc_shield_floor_unit startsolid at %s\n", vtos(ent->s.origin)); G_FreeEntity( ent ); return; } //add the 0.1 back after the trace ent->r.maxs[2] += 0.1; // allow to ride movers ent->s.groundEntityNum = tr.entityNum; G_SetOrigin( ent, tr.endpos ); if (!ent->health) { ent->health = 60; } if (!ent->model || !ent->model[0]) { ent->model = "/models/items/a_shield_converter.md3"; } ent->s.modelindex = G_ModelIndex( ent->model ); ent->s.eFlags = 0; ent->r.svFlags |= SVF_PLAYER_USABLE; ent->r.contents = CONTENTS_SOLID; ent->clipmask = MASK_SOLID; EnergyShieldStationSettings(ent); ent->boltpoint4 = ent->count; //initial value ent->think = check_recharge; ent->nextthink = level.time + STATION_RECHARGE_TIME; ent->use = shield_power_converter_use; VectorCopy( ent->s.angles, ent->s.apos.trBase ); trap_LinkEntity (ent); G_SoundIndex("sound/interface/shieldcon_run.wav"); G_SoundIndex("sound/interface/shieldcon_done.mp3"); G_SoundIndex("sound/interface/shieldcon_empty.mp3"); }
/* ================ G_MissileImpact ================ */ void G_MissileImpact(gentity_t *ent, trace_t *trace) { gentity_t *other, *attacker; vec3_t dir; other = &g_entities[trace->entityNum]; attacker = &g_entities[ent->r.ownerNum]; if (ent->s.weapon == WP_MINE) { 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 (!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_PTEAM] == PTE_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_PTEAM] == PTE_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 = AHive_ReturnToHive; ent->nextthink = level.time + FRAMETIME; // //only damage humans // if (other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_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, 0, 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->client) { 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 { if (other->r.svFlags & SVF_BOT) { G_AddEvent(ent, EV_MISSILE_HIT, DirToByte(trace->plane.normal)); if(ent->s.weapon == WP_AXE && ent->s.generic1 == WPM_SECONDARY) { return; } } 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); }
/* ================ LaunchItem Spawns an item and tosses it forward ================ */ gentity_t *LaunchItem(gitem_t *item, vec3_t origin, vec3_t velocity, qboolean DontFree) { 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 if (g_gametype.integer == GT_INVASION) { dropped->nextthink = 0; //Too: don't bring back dropped flag } else { dropped->think = Team_DroppedFlagThink; dropped->nextthink = level.time + 30000; } Team_CheckDroppedItem(dropped); } else if (DontFree) { dropped->s.eFlags |= EF_NODRAW; dropped->r.svFlags |= SVF_NOCLIENT; dropped->touch = Touch_Item_Dummy; dropped->think = Touch_Item_On; dropped->nextthink = level.time + 700; } 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, qboolean DontFree) { 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; //if (g_gametype.integer == GT_CTF) // DontFree = qfalse; return LaunchItem(item, ent->s.pos.trBase, velocity, DontFree); }
void HolocronThink(gentity_t *ent) { if (ent->pos2[0] && (!ent->enemy || !ent->enemy->client || ent->enemy->health < 1)) { if (ent->enemy && ent->enemy->client) { HolocronRespawn(ent); VectorCopy(ent->enemy->client->ps.origin, ent->s.pos.trBase); VectorCopy(ent->enemy->client->ps.origin, ent->s.origin); VectorCopy(ent->enemy->client->ps.origin, ent->r.currentOrigin); //copy to person carrying's origin before popping out of them HolocronPopOut(ent); ent->enemy->client->ps.holocronsCarried[ent->count] = 0; ent->enemy = NULL; goto justthink; } } else if (ent->pos2[0] && ent->enemy && ent->enemy->client) { ent->pos2[1] = level.time + HOLOCRON_RESPAWN_TIME; } if (ent->enemy && ent->enemy->client) { if (!ent->enemy->client->ps.holocronsCarried[ent->count]) { ent->enemy->client->ps.holocronCantTouch = ent->s.number; ent->enemy->client->ps.holocronCantTouchTime = level.time + 5000; HolocronRespawn(ent); VectorCopy(ent->enemy->client->ps.origin, ent->s.pos.trBase); VectorCopy(ent->enemy->client->ps.origin, ent->s.origin); VectorCopy(ent->enemy->client->ps.origin, ent->r.currentOrigin); //copy to person carrying's origin before popping out of them HolocronPopOut(ent); ent->enemy = NULL; goto justthink; } if (!ent->enemy->inuse || (ent->enemy->client && ent->enemy->client->ps.fallingToDeath)) { if (ent->enemy->inuse && ent->enemy->client) { ent->enemy->client->ps.holocronBits &= ~(1 << ent->count); ent->enemy->client->ps.holocronsCarried[ent->count] = 0; } ent->enemy = NULL; HolocronRespawn(ent); VectorCopy(ent->s.origin2, ent->s.pos.trBase); VectorCopy(ent->s.origin2, ent->s.origin); VectorCopy(ent->s.origin2, ent->r.currentOrigin); ent->s.pos.trTime = level.time; ent->pos2[0] = 0; trap_LinkEntity(ent); goto justthink; } } if (ent->pos2[0] && ent->pos2[1] < level.time) { //isn't in original place and has been there for (HOLOCRON_RESPAWN_TIME) seconds without being picked up, so respawn VectorCopy(ent->s.origin2, ent->s.pos.trBase); VectorCopy(ent->s.origin2, ent->s.origin); VectorCopy(ent->s.origin2, ent->r.currentOrigin); ent->s.pos.trTime = level.time; ent->pos2[0] = 0; trap_LinkEntity(ent); } justthink: ent->nextthink = level.time + 50; if (ent->s.pos.trDelta[0] || ent->s.pos.trDelta[1] || ent->s.pos.trDelta[2]) { G_RunObject(ent); } }
void G_PlayerDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int meansOfDeath ) { gentity_t *ent; int anim; int killer; int i; const char *killerName, *obit; const gentity_t *assistantEnt; int assistant = ENTITYNUM_NONE; const char *assistantName = nullptr; team_t assistantTeam = TEAM_NONE; if ( self->client->ps.pm_type == PM_DEAD ) { return; } if ( level.intermissiontime ) { return; } self->client->ps.pm_type = PM_DEAD; self->suicideTime = 0; if ( attacker ) { killer = attacker->s.number; if ( attacker->client ) { killerName = attacker->client->pers.netname; } else { killerName = "<world>"; } } else { killer = ENTITYNUM_WORLD; killerName = "<world>"; } assistantEnt = G_FindKillAssist( self, attacker, &assistantTeam ); if ( assistantEnt ) { assistant = assistantEnt->s.number; if ( assistantEnt->client ) { assistantName = assistantEnt->client->pers.netname; } } if ( meansOfDeath < 0 || meansOfDeath >= (int) ARRAY_LEN( modNames ) ) { // fall back on the number obit = va( "%d", meansOfDeath ); } else { obit = modNames[ meansOfDeath ]; } if ( assistant != ENTITYNUM_NONE ) { G_LogPrintf( "Die: %d %d %s %d %d: %s^* killed %s^*; %s^* assisted", killer, ( int )( self - g_entities ), obit, assistant, assistantTeam, killerName, self->client->pers.netname, assistantName ); } else { G_LogPrintf( "Die: %d %d %s: %s^* killed %s", killer, ( int )( self - g_entities ), obit, killerName, self->client->pers.netname ); } // deactivate all upgrades for ( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) { BG_DeactivateUpgrade( i, self->client->ps.stats ); } // broadcast the death event to everyone ent = G_NewTempEntity( self->r.currentOrigin, EV_OBITUARY ); ent->s.eventParm = meansOfDeath; ent->s.otherEntityNum = self->s.number; ent->s.otherEntityNum2 = killer; ent->s.otherEntityNum3 = assistant; ent->s.generic1 = assistantTeam; ent->r.svFlags = SVF_BROADCAST; // send to everyone if ( attacker && attacker->client ) { if ( ( attacker == self || G_OnSameTeam( self, attacker ) ) ) { //punish team kills and suicides if ( attacker->client->pers.team == TEAM_ALIENS ) { G_AddCreditToClient( attacker->client, -ALIEN_TK_SUICIDE_PENALTY, true ); G_AddCreditsToScore( attacker, -ALIEN_TK_SUICIDE_PENALTY ); } else if ( attacker->client->pers.team == TEAM_HUMANS ) { G_AddCreditToClient( attacker->client, -HUMAN_TK_SUICIDE_PENALTY, true ); G_AddCreditsToScore( attacker, -HUMAN_TK_SUICIDE_PENALTY ); } } else if ( g_showKillerHP.integer ) { trap_SendServerCommand( self - g_entities, va( "print_tr %s %s %3i", QQ( N_("Your killer, $1$^7, had $2$ HP.\n") ), Quote( killerName ), (int)std::ceil(attacker->entity->Get<HealthComponent>()->Health()) ) ); } } else if ( attacker->s.eType != entityType_t::ET_BUILDABLE ) { if ( self->client->pers.team == TEAM_ALIENS ) { G_AddCreditsToScore( self, -ALIEN_TK_SUICIDE_PENALTY ); } else if ( self->client->pers.team == TEAM_HUMANS ) { G_AddCreditsToScore( self, -HUMAN_TK_SUICIDE_PENALTY ); } } // give credits for killing this player G_RewardAttackers( self ); ScoreboardMessage( self ); // show scores // send updated scores to any clients that are following this one, // or they would get stale scoreboards for ( i = 0; i < level.maxclients; i++ ) { gclient_t *client; client = &level.clients[ i ]; if ( client->pers.connected != CON_CONNECTED ) { continue; } if ( client->sess.spectatorState == SPECTATOR_NOT ) { continue; } if ( client->sess.spectatorClient == self->s.number ) { ScoreboardMessage( g_entities + i ); } } VectorCopy( self->s.origin, self->client->pers.lastDeathLocation ); self->s.weapon = WP_NONE; if ( self->client->noclip ) { self->client->cliprcontents = CONTENTS_CORPSE; } else { self->r.contents = CONTENTS_CORPSE; } self->s.angles[ PITCH ] = 0; self->s.angles[ ROLL ] = 0; self->s.angles[ YAW ] = self->s.apos.trBase[ YAW ]; LookAtKiller( self, inflictor, attacker ); VectorCopy( self->s.angles, self->client->ps.viewangles ); self->s.loopSound = 0; self->r.maxs[ 2 ] = -8; // don't allow respawn until the death anim is done // g_forcerespawn may force spawning at some later time self->client->respawnTime = level.time + 1700; // clear misc memset( self->client->ps.misc, 0, sizeof( self->client->ps.misc ) ); { static int i; if ( !( self->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) { switch ( i ) { case 0: anim = BOTH_DEATH1; break; case 1: anim = BOTH_DEATH2; break; case 2: default: anim = BOTH_DEATH3; break; } } else { switch ( i ) { case 0: anim = NSPA_DEATH1; break; case 1: anim = NSPA_DEATH2; break; case 2: default: anim = NSPA_DEATH3; break; } } self->client->ps.legsAnim = ( ( self->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; if ( !( self->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) { self->client->ps.torsoAnim = ( ( self->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; } // use own entityid if killed by non-client to prevent uint8_t overflow G_AddEvent( self, EV_DEATH1 + i, ( killer < MAX_CLIENTS ) ? killer : self - g_entities ); // globally cycle through the different death animations i = ( i + 1 ) % 3; } Beacon::DetachTags( self ); trap_LinkEntity( self ); self->client->pers.infoChangeTime = level.time; }
//----------------------------------------------------- qboolean turret_base_spawn_top( gentity_t *base ) { vec3_t org; int t; gentity_t *top = G_Spawn(); if ( !top ) { return qfalse; } top->s.modelindex = G_ModelIndex( "models/map_objects/hoth/turret_top_new.md3" ); top->s.modelindex2 = G_ModelIndex( "models/map_objects/hoth/turret_top.md3" ); G_SetAngles( top, base->s.angles ); VectorCopy( base->s.origin, org ); org[2] += 128; G_SetOrigin( top, org ); base->r.ownerNum = top->s.number; top->r.ownerNum = base->s.number; if ( base->team && base->team[0] && //g_gametype.integer == GT_SIEGE && !base->teamnodmg) { base->teamnodmg = atoi(base->team); } base->team = NULL; top->teamnodmg = base->teamnodmg; top->alliedTeam = base->alliedTeam; base->s.eType = ET_GENERAL; // Set up our explosion effect for the ExplodeDeath code.... G_EffectIndex( "turret/explode" ); G_EffectIndex( "sparks/spark_exp_nosnd" ); G_EffectIndex( "turret/hoth_muzzle_flash" ); // this is really the pitch angle..... top->speed = 0; // this is a random time offset for the no-enemy-search-around-mode top->count = random() * 9000; if ( !base->health ) { base->health = 3000; } top->health = 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 top->maxHealth = base->health; //acts as "maxhealth" G_ScaleNetHealth(top); base->maxHealth = base->health; G_ScaleNetHealth(base); } base->takedamage = qtrue; base->pain = TurretBasePain; base->die = bottom_die; //design specified shot speed G_SpawnFloat( "shotspeed", "1100", &base->mass ); top->mass = base->mass; //even if we don't want to show health, let's at least light the crosshair up properly over ourself if ( !top->s.teamowner ) { top->s.teamowner = top->alliedTeam; } base->alliedTeam = top->alliedTeam; base->s.teamowner = top->s.teamowner; base->s.shouldtarget = qtrue; top->s.shouldtarget = qtrue; //link them to each other base->target_ent = top; top->target_ent = base; //top->s.owner = MAX_CLIENTS; //not owned by any client // search radius if ( !base->radius ) { base->radius = 1024; } top->radius = base->radius; // How quickly to fire if ( !base->wait ) { base->wait = 300 + random() * 55; } top->wait = base->wait; if ( !base->splashDamage ) { base->splashDamage = 300; } top->splashDamage = base->splashDamage; if ( !base->splashRadius ) { base->splashRadius = 128; } top->splashRadius = base->splashRadius; // how much damage each shot does if ( !base->damage ) { base->damage = 100; } top->damage = base->damage; // how fast it turns if ( !base->speed ) { base->speed = 20; } top->speed = base->speed; VectorSet( top->r.maxs, 48.0f, 48.0f, 16.0f ); VectorSet( top->r.mins, -48.0f, -48.0f, 0.0f ); // Precache moving sounds //G_SoundIndex( "sound/chars/turret/startup.wav" ); //G_SoundIndex( "sound/chars/turret/shutdown.wav" ); //G_SoundIndex( "sound/chars/turret/ping.wav" ); G_SoundIndex( "sound/vehicles/weapons/hoth_turret/turn.wav" ); top->genericValue13 = G_EffectIndex( "turret/hoth_muzzle_flash" ); top->genericValue14 = G_EffectIndex( "turret/hoth_shot" ); top->genericValue15 = G_EffectIndex( "turret/hoth_impact" ); top->r.contents = CONTENTS_BODY; //base->max_health = base->health; top->takedamage = qtrue; top->pain = TurretPain; top->die = auto_turret_die; top->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_EMPLACED_GUN )); // But set us as a turret so that we can be identified as a turret top->s.weapon = WP_EMPLACED_GUN; trap_LinkEntity( top ); return qtrue; }
void G_RunMissile( gentity_t *ent ) { vec3_t origin; trace_t tr; int passent; bool impact = false; // get current position BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); // ignore interactions with the missile owner passent = ent->r.ownerNum; // general trace to see if we hit anything at all trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, passent, ent->clipmask, 0 ); if ( tr.startsolid || tr.allsolid ) { tr.fraction = 0.0f; VectorCopy( ent->r.currentOrigin, tr.endpos ); } if ( tr.fraction < 1.0f ) { if ( !ent->pointAgainstWorld || (tr.contents & CONTENTS_BODY) ) { // We hit an entity or we don't care impact = true; } else { trap_Trace( &tr, ent->r.currentOrigin, nullptr, nullptr, origin, passent, ent->clipmask, 0 ); if ( tr.fraction < 1.0f ) { // Hit the world with point trace impact = true; } else { if ( tr.contents & CONTENTS_BODY ) { // Hit an entity impact = true; } else { trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, passent, CONTENTS_BODY, 0 ); if ( tr.fraction < 1.0f ) { impact = true; } } } } } VectorCopy( tr.endpos, ent->r.currentOrigin ); if ( impact ) { // Never explode or bounce when hitting the sky. if ( tr.surfaceFlags & SURF_NOIMPACT ) { G_FreeEntity( ent ); return; } // Check for impact damage and effects. MissileImpact( ent, &tr ); // Check if the entity was freed during impact. if ( !ent->inuse ) { return; } // HACK: The missile has turned into an explosion and will free itself later. // See MissileImpact for more. if ( ent->s.eType != ET_MISSILE ) { return; } } ent->r.contents = CONTENTS_SOLID; //trick trap_LinkEntity into... trap_LinkEntity( ent ); ent->r.contents = 0; //...encoding bbox information if ( ent->flightSplashDamage ) { G_RadiusDamage( tr.endpos, ent->parent, ent->flightSplashDamage, ent->flightSplashRadius, ent->parent, 0, ent->splashMethodOfDeath ); } // check think function after bouncing G_RunThink( ent ); }
void weapon_railgun_fire (gentity_t *ent) { vec3_t end; #ifdef MISSIONPACK vec3_t impactpoint, bouncedir; #endif trace_t trace; gentity_t *tent; gentity_t *traceEnt; int damage; int i; int hits; int unlinked; int passent; gentity_t *unlinkedEntities[MAX_RAIL_HITS]; damage = 100 * s_quadFactor; VectorMA (muzzle, 8192, forward, end); // trace only against the solids, so the railgun will go through people unlinked = 0; hits = 0; passent = ent->s.number; do { trap_Trace (&trace, muzzle, NULL, NULL, end, passent, MASK_SHOT ); if ( trace.entityNum >= ENTITYNUM_MAX_NORMAL ) { break; } traceEnt = &g_entities[ trace.entityNum ]; if ( traceEnt->takedamage ) { #ifdef MISSIONPACK if ( traceEnt->client && traceEnt->client->invulnerabilityTime > level.time ) { if ( G_InvulnerabilityEffect( traceEnt, forward, trace.endpos, impactpoint, bouncedir ) ) { G_BounceProjectile( muzzle, impactpoint, bouncedir, end ); // snap the endpos to integers to save net bandwidth, but nudged towards the line SnapVectorTowards( trace.endpos, muzzle ); // send railgun beam effect tent = G_TempEntity( trace.endpos, EV_RAILTRAIL ); // set player number for custom colors on the railtrail tent->s.clientNum = ent->s.clientNum; VectorCopy( muzzle, tent->s.origin2 ); // move origin a bit to come closer to the drawn gun muzzle VectorMA( tent->s.origin2, 4, right, tent->s.origin2 ); VectorMA( tent->s.origin2, -1, up, tent->s.origin2 ); tent->s.eventParm = 255; // don't make the explosion at the end // VectorCopy( impactpoint, muzzle ); // the player can hit him/herself with the bounced rail passent = ENTITYNUM_NONE; } } else { if( LogAccuracyHit( traceEnt, ent ) ) { hits++; } G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_RAILGUN); } #else if( LogAccuracyHit( traceEnt, ent ) ) { hits++; } G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_RAILGUN); #endif } if ( trace.contents & CONTENTS_SOLID ) { break; // we hit something solid enough to stop the beam } // unlink this entity, so the next trace will go past it trap_UnlinkEntity( traceEnt ); unlinkedEntities[unlinked] = traceEnt; unlinked++; } while ( unlinked < MAX_RAIL_HITS ); // link back in any entities we unlinked for ( i = 0 ; i < unlinked ; i++ ) { trap_LinkEntity( unlinkedEntities[i] ); } // the final trace endpos will be the terminal point of the rail trail // snap the endpos to integers to save net bandwidth, but nudged towards the line SnapVectorTowards( trace.endpos, muzzle ); // send railgun beam effect tent = G_TempEntity( trace.endpos, EV_RAILTRAIL ); // set player number for custom colors on the railtrail tent->s.clientNum = ent->s.clientNum; VectorCopy( muzzle, tent->s.origin2 ); // move origin a bit to come closer to the drawn gun muzzle VectorMA( tent->s.origin2, 4, right, tent->s.origin2 ); VectorMA( tent->s.origin2, -1, up, tent->s.origin2 ); // no explosion at end if SURF_NOIMPACT, but still make the trail if ( trace.surfaceFlags & SURF_NOIMPACT ) { tent->s.eventParm = 255; // don't make the explosion at the end } else { tent->s.eventParm = DirToByte( trace.plane.normal ); } tent->s.clientNum = ent->s.clientNum; // give the shooter a reward sound if they have made two railgun hits in a row if ( hits == 0 ) { // complete miss ent->client->accurateCount = 0; } else { // check for "impressive" reward sound ent->client->accurateCount += hits; if ( ent->client->accurateCount >= 2 ) { ent->client->accurateCount -= 1; ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++; // add the sprite over the player's head ent->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP ); ent->client->ps.eFlags |= EF_AWARD_IMPRESSIVE; ent->client->rewardTime = level.time + REWARD_SPRITE_TIME; } ent->client->accuracy_hits++; } }
static void MissileImpact( gentity_t *ent, trace_t *trace ) { int dirAsByte, impactFlags; const missileAttributes_t *ma = BG_Missile( ent->s.modelindex ); gentity_t *hitEnt = &g_entities[ trace->entityNum ]; gentity_t *attacker = &g_entities[ ent->r.ownerNum ]; // Returns whether damage and hit effects should be done and played. std::function<int(gentity_t*, trace_t*, gentity_t*)> impactFunc; // Check for bounce. if ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) && !HasComponents<HealthComponent>(*hitEnt->entity) ) { BounceMissile( ent, trace ); if ( !( ent->s.eFlags & EF_NO_BOUNCE_SOUND ) ) { G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 ); } return; } // Call missile specific impact functions. switch( ent->s.modelindex ) { case MIS_GRENADE: impactFunc = ImpactGrenade; break; case MIS_FIREBOMB: impactFunc = ImpactGrenade; break; case MIS_FLAMER: impactFunc = ImpactFlamer; break; case MIS_FIREBOMB_SUB: impactFunc = ImpactFirebombSub; break; case MIS_LOCKBLOB: impactFunc = ImpactLockblock; break; case MIS_SLOWBLOB: impactFunc = ImpactSlowblob; break; case MIS_HIVE: impactFunc = ImpactHive; break; default: impactFunc = DefaultImpactFunc; break; } impactFlags = impactFunc( ent, trace, hitEnt ); // Deal impact damage. if ( !( impactFlags & MIF_NO_DAMAGE ) ) { if ( ent->damage && G_Alive( hitEnt ) ) { vec3_t dir; BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, dir ); if ( VectorNormalize( dir ) == 0 ) { dir[ 2 ] = 1; // stepped on a grenade } int dflags = 0; if ( !ma->doLocationalDamage ) dflags |= DAMAGE_NO_LOCDAMAGE; if ( ma->doKnockback ) dflags |= DAMAGE_KNOCKBACK; hitEnt->entity->Damage(ent->damage * MissileTimeDmgMod(ent), attacker, Vec3::Load(trace->endpos), Vec3::Load(dir), dflags, (meansOfDeath_t)ent->methodOfDeath); } // splash damage (doesn't apply to person directly hit) if ( ent->splashDamage ) { G_RadiusDamage( trace->endpos, ent->parent, ent->splashDamage * MissileTimeSplashDmgMod( ent ), ent->splashRadius, hitEnt, ( ma->doKnockback ? DAMAGE_KNOCKBACK : 0 ), ent->splashMethodOfDeath ); } } // Play hit effects and remove the missile. if ( !( impactFlags & MIF_NO_EFFECT ) ) { // Use either the trajectory direction or the surface normal for the hit event. if ( ma->impactFlightDirection ) { vec3_t trajDir; BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, trajDir ); VectorNormalize( trajDir ); dirAsByte = DirToByte( trajDir ); } else { dirAsByte = DirToByte( trace->plane.normal ); } // Add hit event. if ( HasComponents<HealthComponent>(*hitEnt->entity) ) { G_AddEvent( ent, EV_MISSILE_HIT_ENTITY, dirAsByte ); ent->s.otherEntityNum = hitEnt->s.number; } else if ( trace->surfaceFlags & SURF_METAL ) { G_AddEvent( ent, EV_MISSILE_HIT_METAL, dirAsByte ); } else { G_AddEvent( ent, EV_MISSILE_HIT_ENVIRONMENT, dirAsByte ); } ent->freeAfterEvent = true; // HACK: Change over to a general entity at the point of impact. ent->s.eType = ET_GENERAL; // Prevent map models from appearing at impact point. ent->s.modelindex = 0; // Save net bandwith. G_SnapVectorTowards( trace->endpos, ent->s.pos.trBase ); G_SetOrigin( ent, trace->endpos ); trap_LinkEntity( ent ); } // If no impact happened, check if we should continue or free ourselves. else if ( !( impactFlags & MIF_NO_FREE ) ) { G_FreeEntity( ent ); } }
/* ================== player_die ================== */ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) { gentity_t *ent; int anim; int killer; int i; char *killerName, *obit; if( self->client->ps.pm_type == PM_DEAD ) return; if( level.intermissiontime ) return; self->client->ps.pm_type = PM_DEAD; self->suicideTime = 0; if( attacker ) { killer = attacker->s.number; if( attacker->client ) killerName = attacker->client->pers.netname; else killerName = "<world>"; } else { killer = ENTITYNUM_WORLD; killerName = "<world>"; } if( meansOfDeath < 0 || meansOfDeath >= ARRAY_LEN( modNames ) ) // fall back on the number obit = va( "%d", meansOfDeath ); else obit = modNames[ meansOfDeath ]; G_LogPrintf( "Die: %d %d %s: %s" S_COLOR_WHITE " killed %s\n", killer, (int)( self - g_entities ), obit, killerName, self->client->pers.netname ); // deactivate all upgrades for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) BG_DeactivateUpgrade( i, self->client->ps.stats ); // broadcast the death event to everyone ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY ); ent->s.eventParm = meansOfDeath; ent->s.otherEntityNum = self->s.number; ent->s.otherEntityNum2 = killer; ent->r.svFlags = SVF_BROADCAST; // send to everyone self->enemy = attacker; self->client->ps.persistant[ PERS_KILLED ]++; if( attacker && attacker->client ) { attacker->client->lastkilled_client = self->s.number; if( ( attacker == self || OnSameTeam( self, attacker ) ) && meansOfDeath != MOD_HSPAWN ) { //punish team kills and suicides if( attacker->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) { G_AddCreditToClient( attacker->client, -ALIEN_TK_SUICIDE_PENALTY, qtrue ); AddScore( attacker, -ALIEN_TK_SUICIDE_PENALTY ); } else if( attacker->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { G_AddCreditToClient( attacker->client, -HUMAN_TK_SUICIDE_PENALTY, qtrue ); AddScore( attacker, -HUMAN_TK_SUICIDE_PENALTY ); } } } else if( attacker->s.eType != ET_BUILDABLE ) { if( self->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) AddScore( self, -ALIEN_TK_SUICIDE_PENALTY ); else if( self->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) AddScore( self, -HUMAN_TK_SUICIDE_PENALTY ); } // give credits for killing this player G_RewardAttackers( self ); ScoreboardMessage( self ); // show scores // send updated scores to any clients that are following this one, // or they would get stale scoreboards for( i = 0 ; i < level.maxclients ; i++ ) { gclient_t *client; client = &level.clients[ i ]; if( client->pers.connected != CON_CONNECTED ) continue; if( client->sess.spectatorState == SPECTATOR_NOT ) continue; if( client->sess.spectatorClient == self->s.number ) ScoreboardMessage( g_entities + i ); } VectorCopy( self->s.origin, self->client->pers.lastDeathLocation ); self->takedamage = qfalse; // can still be gibbed self->s.weapon = WP_NONE; self->r.contents = CONTENTS_CORPSE; self->s.angles[ PITCH ] = 0; self->s.angles[ ROLL ] = 0; self->s.angles[ YAW ] = self->s.apos.trBase[ YAW ]; LookAtKiller( self, inflictor, attacker ); VectorCopy( self->s.angles, self->client->ps.viewangles ); self->s.loopSound = 0; self->r.maxs[ 2 ] = -8; // don't allow respawn until the death anim is done // g_forcerespawn may force spawning at some later time self->client->respawnTime = level.time + 1700; // clear misc memset( self->client->ps.misc, 0, sizeof( self->client->ps.misc ) ); { // normal death static int i; if( !( self->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) { switch( i ) { case 0: anim = BOTH_DEATH1; break; case 1: anim = BOTH_DEATH2; break; case 2: default: anim = BOTH_DEATH3; break; } } else { switch( i ) { case 0: anim = NSPA_DEATH1; break; case 1: anim = NSPA_DEATH2; break; case 2: default: anim = NSPA_DEATH3; break; } } self->client->ps.legsAnim = ( ( self->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; if( !( self->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) { self->client->ps.torsoAnim = ( ( self->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; } // use own entityid if killed by non-client to prevent uint8_t overflow G_AddEvent( self, EV_DEATH1 + i, ( killer < MAX_CLIENTS ) ? killer : self - g_entities ); // globally cycle through the different death animations i = ( i + 1 ) % 3; } trap_LinkEntity( self ); self->client->pers.infoChangeTime = level.time; }
/* ================== player_die ================== */ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) { static int rndAnim; gentity_t *ent; int anim; int contents; int killer; int i; char *killerName, *obit; qboolean gibPlayer; qboolean headshot = qfalse; if ( self->player->ps.pm_type == PM_DEAD ) { return; } if ( level.intermissiontime ) { return; } // make sure the body shows up in the player's current position G_UnTimeShiftClient( self ); // check for an almost capture CheckAlmostCapture( self, attacker ); // check for a player that almost brought in cubes CheckAlmostScored( self, attacker ); if (self->player && self->player->hook) { Weapon_HookFree(self->player->hook); } #ifdef MISSIONPACK if ((self->player->ps.eFlags & EF_TICKING) && self->activator) { self->player->ps.eFlags &= ~EF_TICKING; self->activator->think = G_FreeEntity; self->activator->nextthink = level.time; } #endif self->player->ps.pm_type = PM_DEAD; if ( attacker ) { killer = attacker->s.number; if ( attacker->player ) { killerName = attacker->player->pers.netname; } else { killerName = "<non-player>"; } } else { killer = ENTITYNUM_WORLD; killerName = "<world>"; } if ( killer < 0 || killer >= MAX_CLIENTS ) { killer = ENTITYNUM_WORLD; killerName = "<world>"; } if ( meansOfDeath < 0 || meansOfDeath >= ARRAY_LEN( modNames ) ) { obit = "<bad obituary>"; } else { obit = modNames[meansOfDeath]; } G_LogPrintf("Kill: %i %i %i: %s killed %s by %s\n", killer, self->s.number, meansOfDeath, killerName, self->player->pers.netname, obit ); self->enemy = attacker; self->player->ps.persistant[PERS_KILLED]++; if (attacker && attacker->player) { attacker->player->lastkilled_player = self->s.number; if ( attacker == self || OnSameTeam (self, attacker ) ) { if ( ((level.time - self->player->lasthurt_time) <= ASSISTED_SUICIDE_TIME) && g_entities[self->player->lasthurt_player2].player && (self->player->lasthurt_player2 != self->s.number) ) { AddScore( &g_entities[self->player->lasthurt_player2], self->r.currentOrigin, 1 ); meansOfDeath = MOD_ASSISTED_SUICIDE; } else AddScore( attacker, self->r.currentOrigin, -1 ); } else { AddScore( attacker, self->r.currentOrigin, 1 ); if( meansOfDeath == MOD_GAUNTLET ) { // play humiliation on player attacker->player->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++; // add the sprite over the player's head attacker->player->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP ); attacker->player->ps.eFlags |= EF_AWARD_GAUNTLET; attacker->player->rewardTime = level.time + REWARD_SPRITE_TIME; // also play humiliation on target self->player->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_GAUNTLETREWARD; } // check for two kills in a short amount of time // if this is close enough to the last kill, give a reward sound if ( level.time - attacker->player->lastKillTime < CARNAGE_REWARD_TIME ) { // play excellent on player attacker->player->ps.persistant[PERS_EXCELLENT_COUNT]++; // add the sprite over the player's head attacker->player->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP ); attacker->player->ps.eFlags |= EF_AWARD_EXCELLENT; attacker->player->rewardTime = level.time + REWARD_SPRITE_TIME; } attacker->player->lastKillTime = level.time; } } else { if ( ((level.time - self->player->lasthurt_time) <= ASSISTED_SUICIDE_TIME) && g_entities[self->player->lasthurt_player2].player && (self->player->lasthurt_player2 != self->s.number) ) { AddScore( &g_entities[self->player->lasthurt_player2], self->r.currentOrigin, 1 ); meansOfDeath = MOD_ASSISTED_SUICIDE; } else AddScore( self, self->r.currentOrigin, -1 ); } // don't send death obituary when swiching teams // allow mod to be changed by death type if (meansOfDeath != MOD_SUICIDE_TEAM_CHANGE) { // broadcast the death event to everyone ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY ); ent->s.eventParm = meansOfDeath; ent->s.otherEntityNum = self->s.number; ent->s.otherEntityNum2 = killer; ent->r.svFlags = SVF_BROADCAST; // send to everyone if (self->player && attacker->player && ((self->player->lasthurt_location & LOCATION_HEAD) || (self->player->lasthurt_location & LOCATION_FACE))) { ent->s.eFlags |= EF_HEADSHOT; headshot = qtrue; } } // Add team bonuses Team_FragBonuses(self, inflictor, attacker); // if I committed suicide, the flag does not fall, it returns. if (meansOfDeath == MOD_SUICIDE || meansOfDeath == MOD_SUICIDE_TEAM_CHANGE) { if ( self->player->ps.powerups[PW_NEUTRALFLAG] ) { // only happens in One Flag CTF Team_ReturnFlag( TEAM_FREE ); self->player->ps.powerups[PW_NEUTRALFLAG] = 0; } else if ( self->player->ps.powerups[PW_REDFLAG] ) { // only happens in standard CTF Team_ReturnFlag( TEAM_RED ); self->player->ps.powerups[PW_REDFLAG] = 0; } else if ( self->player->ps.powerups[PW_BLUEFLAG] ) { // only happens in standard CTF Team_ReturnFlag( TEAM_BLUE ); self->player->ps.powerups[PW_BLUEFLAG] = 0; } } TossPlayerItems( self ); #ifdef MISSIONPACK TossPlayerPersistantPowerups( self ); if( g_gametype.integer == GT_HARVESTER ) { TossPlayerCubes( self ); } #endif Cmd_Score_f( self ); // show scores // send updated scores to any clients that are following this one, // or they would get stale scoreboards for ( i = 0 ; i < level.maxplayers ; i++ ) { gplayer_t *player; player = &level.players[i]; if ( player->pers.connected != CON_CONNECTED ) { continue; } if ( player->sess.sessionTeam != TEAM_SPECTATOR ) { continue; } if ( player->sess.spectatorPlayer == self->s.number ) { Cmd_Score_f( g_entities + i ); } } self->takedamage = qtrue; // can still be gibbed self->s.weapon = WP_NONE; self->s.powerups = 0; self->player->ps.contents = self->s.contents = CONTENTS_CORPSE; self->s.angles[0] = 0; self->s.angles[2] = 0; LookAtKiller (self, inflictor, attacker); VectorCopy( self->s.angles, self->player->ps.viewangles ); self->s.loopSound = 0; self->player->ps.maxs[2] = self->s.maxs[2] = -8; // don't allow respawn until the death anim is done // g_forcerespawn may force spawning at some later time self->player->respawnTime = level.time + 1700; // remove powerups memset( self->player->ps.powerups, 0, sizeof(self->player->ps.powerups) ); // never gib in a nodrop contents = trap_PointContents( self->r.currentOrigin, -1 ); gibPlayer = ( (self->health <= GIB_HEALTH && !(contents & CONTENTS_NODROP)) || meansOfDeath == MOD_SUICIDE ); if ( gibPlayer ) { // gib death GibEntity( self, qfalse ); // do normal death for clients with gibs disable } else { if (headshot) gibPlayer = 3; // the body can still be gibbed self->die = body_die; #ifdef MISSIONPACK if (self->s.eFlags & EF_KAMIKAZE) { Kamikaze_DeathTimer( self ); } #endif } // normal death switch ( rndAnim ) { case 0: anim = BOTH_DEATH1; break; case 1: anim = BOTH_DEATH2; break; case 2: default: anim = BOTH_DEATH3; break; } self->player->ps.legsAnim = ( ( self->player->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; self->player->ps.torsoAnim = ( ( self->player->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; G_AddEvent( self, EV_DEATH1 + rndAnim, gibPlayer ); if ( headshot ) GibEntity( self, qtrue ); else self->player->headless = qfalse; // globally cycle through the different death animations rndAnim = ( rndAnim + 1 ) % 3; trap_LinkEntity (self); }
/* =============== RespawnItem =============== */ void RespawnItem(gentity_t *ent) { // randomly select from teamed entities if (ent->team) { gentity_t *master; int count; int choice; if (!ent->teammaster) { G_Error("RespawnItem: bad teammaster"); } master = ent->teammaster; for (count = 0, ent = master; ent; ent = ent->teamchain, count++) ; choice = rand() % count; for (count = 0, ent = master; count < choice; ent = ent->teamchain, count++) ; } ent->r.contents = CONTENTS_TRIGGER; ent->s.eFlags &= ~EF_NODRAW; ent->r.svFlags &= ~SVF_NOCLIENT; trap_LinkEntity (ent); if (ent->item->giType == IT_POWERUP) { // play powerup spawn sound to all clients gentity_t *te; // if the powerup respawn sound should Not be global if (ent->speed) { te = G_TempEntity(ent->s.pos.trBase, EV_GENERAL_SOUND); } else { te = G_TempEntity(ent->s.pos.trBase, EV_GLOBAL_SOUND); } te->s.eventParm = G_SoundIndex("sound/items/poweruprespawn.wav"); te->r.svFlags |= SVF_BROADCAST; } if (ent->item->giType == IT_HOLDABLE && ent->item->giTag == HI_KAMIKAZE) { // play powerup spawn sound to all clients gentity_t *te; // if the powerup respawn sound should Not be global if (ent->speed) { te = G_TempEntity(ent->s.pos.trBase, EV_GENERAL_SOUND); } else { te = G_TempEntity(ent->s.pos.trBase, EV_GLOBAL_SOUND); } te->s.eventParm = G_SoundIndex("sound/items/kamikazerespawn.wav"); te->r.svFlags |= SVF_BROADCAST; } // play the normal respawn sound only to nearby clients G_AddEvent(ent, EV_ITEM_RESPAWN, 0); ent->nextthink = 0; }
// Arnout: 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) { // Arnout: this is for compass usage if((ent->spawnflags & AXIS_OBJECTIVE) || (ent->spawnflags & ALLIED_OBJECTIVE)) { gentity_t *e; 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]; 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'\n", ent->target); } 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) { // trap_UnlinkEntity( ent ); // return; } else { // Arnout: spawn a constructible icon - this is for compass usage gentity_t *e; 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]) { team[1] = constructibles[1]->spawnflags & AXIS_CONSTRUCTIBLE ? TEAM_AXIS : TEAM_ALLIES; // 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); }
/* =============== Touch_Item =============== */ void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) { int respawn; qboolean predict; if (!other->client) return; if (other->health < 1) return; // dead people can't pickup // the same pickup rules are used for client side and server side if (!BG_CanItemBeGrabbed(g_gametype.integer, &ent->s, &other->client->ps, &level.InvasionInfo)) { return; } G_LogPrintf("Item: %i %s\n", other->s.number, ent->item->classname); predict = other->client->pers.predictItemPickup; // call the item-specific pickup function switch (ent->item->giType) { case IT_WEAPON: respawn = Pickup_Weapon(ent, other); // predict = qfalse; break; case IT_AMMO: respawn = Pickup_Ammo(ent, other); // predict = qfalse; break; case IT_ARMOR: respawn = Pickup_Armor(ent, other); break; case IT_HEALTH: respawn = Pickup_Health(ent, other); break; case IT_POWERUP: respawn = Pickup_Powerup(ent, other); predict = qfalse; break; #ifdef MISSIONPACK case IT_PERSISTANT_POWERUP: respawn = Pickup_PersistantPowerup(ent, other); break; #endif case IT_TEAM: respawn = Pickup_Team(ent, other); break; case IT_HOLDABLE: respawn = Pickup_Holdable(ent, other); break; default: return; } if (!respawn) { return; } // play the normal pickup sound if (predict) { G_AddPredictableEvent(other, EV_ITEM_PICKUP, ent->s.modelindex); } else { G_AddEvent(other, EV_ITEM_PICKUP, ent->s.modelindex); } // powerup pickups are global broadcasts if (ent->item->giType == IT_POWERUP || ent->item->giType == IT_TEAM) { // if we want the global sound to play if (!ent->speed) { gentity_t *te; te = G_TempEntity(ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP); te->s.eventParm = ent->s.modelindex; te->r.svFlags |= SVF_BROADCAST; } else { gentity_t *te; te = G_TempEntity(ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP); te->s.eventParm = ent->s.modelindex; // only send this temp entity to a single client te->r.svFlags |= SVF_SINGLECLIENT; te->r.singleClient = other->s.number; } } // fire item targets G_UseTargets (ent, other); // wait of -1 will not respawn if (ent->wait == -1) { ent->r.svFlags |= SVF_NOCLIENT; ent->s.eFlags |= EF_NODRAW; ent->r.contents = 0; ent->unlinkAfterEvent = qtrue; return; } // non zero wait overrides respawn time if (ent->wait) { respawn = ent->wait; } // random can be used to vary the respawn time if (ent->random) { respawn += crandom() * ent->random; if (respawn < 1) { respawn = 1; } } // dropped items will not respawn if (ent->flags & FL_DROPPED_ITEM) { ent->freeAfterEvent = qtrue; } // picked up items still stay around, they just don't // draw anything. This allows respawnable items // to be placed on movers. ent->r.svFlags |= SVF_NOCLIENT; ent->s.eFlags |= EF_NODRAW; ent->r.contents = 0; // ZOID // A negative respawn times means to never respawn this item (but don't // delete it). This is used by items that are respawned by third party // events such as ctf flags if (respawn <= 0) { ent->nextthink = 0; ent->think = 0; } else { ent->nextthink = level.time + respawn * 1000; ent->think = RespawnItem; } trap_LinkEntity(ent); }
void SP_trigger_objective_info( gentity_t *ent ) { char *scorestring; char* customimage; int cix, cia, objflags; if ( !ent->track ) G_Error ("'trigger_objective_info' does not have a 'track' \n"); /* if ( !ent->message ) G_Error ("'trigger_objective_info' does not have a 'shortname' \n");*/ if(ent->spawnflags & MESSAGE_OVERRIDE) { if ( !ent->spawnitem ) G_Error ("'trigger_objective_info' has override flag set but no override text\n"); } // Gordon: for specifying which commandmap objectives this entity "belongs" to G_SpawnInt( "objflags", "0", &objflags ); if(G_SpawnString( "customimage", "", &customimage )) { cix = cia = G_ShaderIndex( customimage ); } else { if(G_SpawnString( "customaxisimage", "", &customimage )) { cix = G_ShaderIndex( customimage ); } else { cix = 0; } if(G_SpawnString( "customalliesimage", "", &customimage )) { cia = G_ShaderIndex( customimage ); } else if(G_SpawnString( "customalliedimage", "", &customimage )) { cia = G_ShaderIndex( customimage ); } else { cia = 0; } } G_SetConfigStringValue( CS_OID_DATA + level.numOidTriggers, "e", va( "%li", (long)(ent-g_entities) ) ); G_SetConfigStringValue( CS_OID_DATA + level.numOidTriggers, "o", va( "%i", objflags ) ); G_SetConfigStringValue( CS_OID_DATA + level.numOidTriggers, "cix", va( "%i", cix ) ); G_SetConfigStringValue( CS_OID_DATA + level.numOidTriggers, "cia", va( "%i", cia ) ); G_SetConfigStringValue( CS_OID_DATA + level.numOidTriggers, "s", va( "%i", ent->spawnflags ) ); G_SetConfigStringValue( CS_OID_DATA + level.numOidTriggers, "n", ent->message ? ent->message : "" ); if( level.numOidTriggers >= MAX_OID_TRIGGERS ) { G_Error ("Exceeded maximum number of 'trigger_objective_info' entities\n"); } // JPW NERVE -- if this trigger has a "score" field set, then blowing up an objective // inside of this field will add "score" to the right player team. storing this // in ent->accuracy since that's unused. G_SpawnString ("score", "0", &scorestring); ent->accuracy = atof (scorestring); trap_SetConfigstring( CS_OID_TRIGGERS + level.numOidTriggers, ent->track ); InitTrigger( ent ); if( ent->s.origin[0] || ent->s.origin[1] || ent->s.origin[2] ) { G_SetConfigStringValue( CS_OID_DATA + level.numOidTriggers, "x", va( "%i", (int)ent->s.origin[0] ) ); G_SetConfigStringValue( CS_OID_DATA + level.numOidTriggers, "y", va( "%i", (int)ent->s.origin[1] ) ); G_SetConfigStringValue( CS_OID_DATA + level.numOidTriggers, "z", va( "%i", (int)ent->s.origin[2] ) ); } else { vec3_t mid; VectorAdd( ent->r.absmin, ent->r.absmax, mid ); VectorScale( mid, 0.5f, mid ); G_SetConfigStringValue( CS_OID_DATA + level.numOidTriggers, "x", va( "%i", (int)mid[0] ) ); G_SetConfigStringValue( CS_OID_DATA + level.numOidTriggers, "y", va( "%i", (int)mid[1] ) ); G_SetConfigStringValue( CS_OID_DATA + level.numOidTriggers, "z", va( "%i", (int)mid[2] ) ); } ent->s.teamNum = level.numOidTriggers++; // unlike other triggers, we need to send this one to the client ent->r.svFlags &= ~SVF_NOCLIENT; ent->s.eType = ET_OID_TRIGGER; if( !ent->target ) { // no target - just link and go trap_LinkEntity (ent); } else { // Arnout: finalize spawing on fourth frame to allow for proper linking with targets ent->nextthink = level.time + (3*FRAMETIME); ent->think = Think_SetupObjectiveInfo; } }
/* ================ 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; qboolean Goal = qfalse; 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 (g_gametype.integer == GT_DESTROY && !Q_stricmp(ent->classname, "team_CTF_alienegg")) { ent->touch = NULL; ent->r.contents = CONTENTS_SOLID; ent->takedamage = qtrue; ent->die = Inv_EggDie; Goal = qtrue; } else { VectorSet(ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS); VectorSet(ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS); } 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); } if (Goal) Inv_SetDestroyableEgg(ent); // 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; } trap_LinkEntity (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); }
void SP_misc_holocron(gentity_t *ent) { vec3_t dest; trace_t tr; if (g_gametype.integer != GT_HOLOCRON) { G_FreeEntity(ent); return; } if (HasSetSaberOnly()) { if (ent->count == FP_SABERATTACK || ent->count == FP_SABERDEFEND || ent->count == FP_SABERTHROW) { //having saber holocrons in saber only mode is pointless G_FreeEntity(ent); return; } } ent->s.isJediMaster = qtrue; VectorSet( ent->r.maxs, 8, 8, 8 ); VectorSet( ent->r.mins, -8, -8, -8 ); ent->s.origin[2] += 0.1; ent->r.maxs[2] -= 0.1; 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 ("SP_misc_holocron: misc_holocron startsolid at %s\n", vtos(ent->s.origin)); G_FreeEntity( ent ); return; } //add the 0.1 back after the trace ent->r.maxs[2] += 0.1; // allow to ride movers // ent->s.groundEntityNum = tr.entityNum; G_SetOrigin( ent, tr.endpos ); if (ent->count < 0) { ent->count = 0; } if (ent->count >= NUM_FORCE_POWERS) { ent->count = NUM_FORCE_POWERS-1; } /* if (g_forcePowerDisable.integer && (g_forcePowerDisable.integer & (1 << ent->count))) { G_FreeEntity(ent); return; } */ //No longer doing this, causing too many complaints about accidentally setting no force powers at all //and starting a holocron game (making it basically just FFA) ent->enemy = NULL; ent->s.eFlags = EF_BOUNCE_HALF; ent->s.modelindex = (ent->count - 128);//G_ModelIndex(holocronTypeModels[ent->count]); ent->s.eType = ET_HOLOCRON; ent->s.pos.trType = TR_GRAVITY; ent->s.pos.trTime = level.time; ent->r.contents = CONTENTS_TRIGGER; ent->clipmask = MASK_SOLID; ent->s.trickedentindex4 = ent->count; if (forcePowerDarkLight[ent->count] == FORCE_DARKSIDE) { ent->s.trickedentindex3 = 1; } else if (forcePowerDarkLight[ent->count] == FORCE_LIGHTSIDE) { ent->s.trickedentindex3 = 2; } else { ent->s.trickedentindex3 = 3; } ent->physicsObject = qtrue; VectorCopy(ent->s.pos.trBase, ent->s.origin2); //remember the spawn spot ent->touch = HolocronTouch; trap_LinkEntity(ent); ent->think = HolocronThink; ent->nextthink = level.time + 50; }
/** * @brief Run item. */ void G_RunItem(gentity_t *ent) { vec3_t origin; trace_t tr; int contents; int mask; // if groundentity has been set to -1, it may have been pushed off an edge if (ent->s.groundEntityNum == -1) { if (ent->s.pos.trType != TR_GRAVITY) { ent->s.pos.trType = TR_GRAVITY; ent->s.pos.trTime = level.time; } } if (ent->s.pos.trType == TR_STATIONARY || ent->s.pos.trType == TR_GRAVITY_PAUSED) // check think function { G_RunThink(ent); return; } if (ent->s.pos.trType == TR_LINEAR && (!ent->clipmask && !ent->r.contents)) { // check think function G_RunThink(ent); return; } // get current position BG_EvaluateTrajectory(&ent->s.pos, level.time, origin, qfalse, ent->s.effect2Time); // trace a line from the previous position to the current position if (ent->clipmask) { mask = ent->clipmask; } else { mask = MASK_SOLID; } trap_Trace(&tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->r.ownerNum, mask); if (ent->isProp && ent->takedamage) { G_RunItemProp(ent, origin); } VectorCopy(tr.endpos, ent->r.currentOrigin); if (tr.startsolid) { tr.fraction = 0; } trap_LinkEntity(ent); // FIXME: avoid this for stationary? // check think function G_RunThink(ent); if (tr.fraction == 1) { return; } // if it is in a nodrop volume, remove it contents = trap_PointContents(ent->r.currentOrigin, -1); if (contents & CONTENTS_NODROP) { if (ent->item && ent->item->giType == IT_TEAM) { Team_ReturnFlag(ent); } else { G_FreeEntity(ent); } return; } G_BounceItem(ent, &tr); }
void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ) { gentity_t *tent; // use temp events at source and destination to prevent the effect // from getting dropped by a second player event #ifndef SMOKINGUNS if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) { #else if ( player->client->sess.sessionTeam < TEAM_SPECTATOR ) { #endif tent = G_TempEntity( player->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); tent->s.clientNum = player->s.clientNum; tent = G_TempEntity( origin, EV_PLAYER_TELEPORT_IN ); tent->s.clientNum = player->s.clientNum; } // unlink to make sure it can't possibly interfere with G_KillBox trap_UnlinkEntity (player); VectorCopy ( origin, player->client->ps.origin ); player->client->ps.origin[2] += 1; // spit the player out AngleVectors( angles, player->client->ps.velocity, NULL, NULL ); VectorScale( player->client->ps.velocity, 400, player->client->ps.velocity ); player->client->ps.pm_time = 160; // hold time player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; // toggle the teleport bit so the client knows to not lerp player->client->ps.eFlags ^= EF_TELEPORT_BIT; #ifdef SMOKINGUNS //unlagged - backward reconciliation #3 // we don't want players being backward-reconciled back through teleporters G_ResetHistory( player ); //unlagged - backward reconciliation #3 #endif // set angles SetClientViewAngle( player, angles ); // kill anything at the destination #ifndef SMOKINGUNS if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) { #else if ( player->client->sess.sessionTeam < TEAM_SPECTATOR ) { #endif G_KillBox (player); #ifdef SMOKINGUNS // Tequila comment: G_KillBox will set dontTelefrag as needed if (player->client->dontTelefrag) { #ifdef DEBUG_TELEFRAG_CASE G_Printf(S_COLOR_MAGENTA "TeleportPlayer: Telefrag case delayed at respawn for %s...\n",player->client->pers.netname); #endif trap_SendServerCommand( player->s.clientNum, va("print \"Go away %s\n\"",player->client->pers.netname) ); // So we will link the player entity later with normal content player->r.contents = 0; } #endif } // save results of pmove BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue ); // use the precise origin for linking VectorCopy( player->client->ps.origin, player->r.currentOrigin ); #ifndef SMOKINGUNS if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) { #else if ( player->client->sess.sessionTeam < TEAM_SPECTATOR ) { #endif trap_LinkEntity (player); } } /*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) Point teleporters at these. Now that we don't have teleport destination pads, this is just an info_notnull */ void SP_misc_teleporter_dest( gentity_t *ent ) { } #ifdef SMOKINGUNS // Imported from WoP OpenSource project //=========================================================== /*QUAKED misc_externalmodel (1 0 0) (-16 -16 -16) (16 16 16) "model" arbitrary .md3 file to display "wait" time in seconds before the animation begins */ #define ANIMATION_THINKTIME 50 static void Think_AnimationExternalmodel( gentity_t *ent ) { if(ent->animationEnd>ent->animationStart) { ent->s.frame = (int)((float)level.time*0.001f*ent->animationFPS)%(ent->animationEnd-ent->animationStart); ent->s.frame += ent->animationStart; ent->nextthink = level.time + ANIMATION_THINKTIME; } } void SP_misc_externalmodel( gentity_t *ent ) { ent->s.modelindex = G_ModelIndex( ent->model ); // VectorSet (ent->mins, -16, -16, -16); // VectorSet (ent->maxs, 16, 16, 16); trap_LinkEntity (ent); G_SetOrigin( ent, ent->s.origin ); VectorCopy( ent->s.angles, ent->s.apos.trBase ); if(ent->animationEnd>ent->animationStart && ent->animationFPS>0.0f) { ent->think = Think_AnimationExternalmodel; ent->nextthink = level.time + ANIMATION_THINKTIME; // Tequila: Support for new entity features if (ent->wait>0.0f) ent->nextthink += (int)(ent->wait*1000); } } #endif //=========================================================== /*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16) "model" arbitrary .md3 file to display */ void SP_misc_model( gentity_t *ent ) { #if 0 ent->s.modelindex = G_ModelIndex( ent->model ); VectorSet (ent->mins, -16, -16, -16); VectorSet (ent->maxs, 16, 16, 16); trap_LinkEntity (ent); G_SetOrigin( ent, ent->s.origin ); VectorCopy( ent->s.angles, ent->s.apos.trBase ); #else G_FreeEntity( ent ); #endif } //=========================================================== void locateCamera( gentity_t *ent ) { vec3_t dir; gentity_t *target; gentity_t *owner; owner = G_PickTarget( ent->target ); if ( !owner ) { G_Printf( "Couldn't find target for misc_portal_surface\n" ); G_FreeEntity( ent ); return; } ent->r.ownerNum = owner->s.number; // frame holds the rotate speed if ( owner->spawnflags & 1 ) { ent->s.frame = 25; } else if ( owner->spawnflags & 2 ) { ent->s.frame = 75; } #ifndef SMOKINGUNS // swing camera ? if ( owner->spawnflags & 4 ) { // set to 0 for no rotation at all ent->s.powerups = 0; } else { ent->s.powerups = 1; } #else // set to 0 for no rotation at all ent->s.powerups = 1; #endif // clientNum holds the rotate offset ent->s.clientNum = owner->s.clientNum; VectorCopy( owner->s.origin, ent->s.origin2 ); // see if the portal_camera has a target target = G_PickTarget( owner->target ); if ( target ) { VectorSubtract( target->s.origin, owner->s.origin, dir ); VectorNormalize( dir ); } else { G_SetMovedir( owner->s.angles, dir ); } ent->s.eventParm = DirToByte( dir ); }
/** * @brief Action when touching an item. */ void Touch_Item(gentity_t *ent, gentity_t *other, trace_t *trace) { int respawn; // only activated items can be picked up if (!ent->active) { return; } else { // need to set active to false if player is maxed out ent->active = qfalse; } if (!other->client) { return; } if (other->health <= 0) { return; // dead people can't pickup } // the same pickup rules are used for client side and server side if (!BG_CanItemBeGrabbed(&ent->s, &other->client->ps, other->client->sess.skill, other->client->sess.sessionTeam)) { return; } if (g_gamestate.integer == GS_PLAYING) { G_LogPrintf("Item: %i %s\n", other->s.number, ent->item->classname); } else { // Don't let them pickup winning stuff in warmup if (ent->item->giType != IT_WEAPON && ent->item->giType != IT_AMMO && ent->item->giType != IT_HEALTH) { return; } } //G_LogPrintf( "Calling item pickup function for %s\n", ent->item->classname ); // call the item-specific pickup function switch (ent->item->giType) { case IT_WEAPON: respawn = Pickup_Weapon(ent, other); break; case IT_HEALTH: respawn = Pickup_Health(ent, other); break; case IT_TEAM: respawn = Pickup_Team(ent, other); break; default: return; } //G_LogPrintf( "Finished pickup function\n" ); if (!respawn) { return; } // play sounds if (ent->noise_index) { // a sound was specified in the entity, so play that sound // (this G_AddEvent) and send the pickup as "EV_ITEM_PICKUP_QUIET" // so it doesn't make the default pickup sound when the pickup event is received G_AddEvent(other, EV_GENERAL_SOUND, ent->noise_index); G_AddEvent(other, EV_ITEM_PICKUP_QUIET, ent->s.modelindex); } else { G_AddEvent(other, EV_ITEM_PICKUP, ent->s.modelindex); } // powerup pickups are global broadcasts if (ent->item->giType == IT_TEAM) { gentity_t *te = G_TempEntity(ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP); te->s.eventParm = ent->s.modelindex; te->r.svFlags |= SVF_BROADCAST; } //G_LogPrintf( "Firing item targets\n" ); // fire item targets G_UseTargets(ent, other); // dropped items will not respawn if (ent->flags & FL_DROPPED_ITEM) { ent->freeAfterEvent = qtrue; } // picked up items still stay around, they just don't // draw anything. This allows respawnable items // to be placed on movers. ent->r.svFlags |= SVF_NOCLIENT; ent->flags |= FL_NODRAW; ent->r.contents = 0; // A negative respawn times means to never respawn this item (but don't // delete it). This is used by items that are respawned by third party // events such as ctf flags if (respawn <= 0) { ent->nextthink = 0; ent->think = 0; } else { ent->nextthink = level.time + respawn * 1000; ent->think = RespawnItem; } trap_LinkEntity(ent); }
/* =========== 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 { 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->s.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; Com_Memset (client, 0, sizeof(*client)); client->ps.stats[STAT_ATTACKERCLIENT] = -1; client->ps.stats[STAT_INFOCLIENT] = -1; 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; 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; ent->s.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; // 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); } // 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; if ( level.intermissiontime ) { MoveClientToIntermission( ent ); } else { // fire the targets of the spawn point G_UseTargets( spawnPoint, ent ); } // 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 ); ent->s.modelindex = G_ModelIndex("models/human/coxswain.tik"); }
/** * @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; }
/* =========== 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; gentity_t *tent; int flags; int savedPing; // char *savedAreaBits; int accuracy_hits, accuracy_shots; int eventSequence; index = ent - g_entities; client = ent->client; VectorClear(spawn_origin); // 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, !!(ent->r.svFlags & SVF_BOT)); } else { // 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, !!(ent->r.svFlags & SVF_BOT)); } else { // don't spawn near existing origin if possible spawnPoint = SelectSpawnPoint ( client->ps.origin, spawn_origin, spawn_angles, !!(ent->r.svFlags & SVF_BOT)); } } 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; Com_Memset (client, 0, sizeof(*client)); 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; // set max health client->pers.maxHealth = ClientHandicap( client ); // clear entity values client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; client->ps.eFlags = flags; client->ps.contents = CONTENTS_BODY; client->ps.capsule = qtrue; ent->s.groundEntityNum = ENTITYNUM_NONE; ent->client = &level.clients[index]; ent->takedamage = qtrue; ent->inuse = qtrue; ent->classname = "player"; ent->clipmask = MASK_PLAYERSOLID; ent->die = player_die; ent->waterlevel = 0; ent->watertype = 0; ent->flags = 0; VectorCopy (playerMins, client->ps.mins); VectorCopy (playerMaxs, client->ps.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 ); // 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) { if (ent->client->sess.sessionTeam != TEAM_SPECTATOR) { G_KillBox(ent); // force the base weapon up client->ps.weapon = WP_MACHINEGUN; client->ps.weaponstate = WEAPON_READY; // 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; } } // positively link the client, even if the command times are weird VectorCopy(ent->client->ps.origin, ent->r.currentOrigin); tent = G_TempEntity(ent->client->ps.origin, EV_PLAYER_TELEPORT_IN); tent->s.clientNum = ent->s.clientNum; trap_LinkEntity (ent); } } else { // move players to intermission MoveClientToIntermission(ent); } // 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 ); // run the presend to set anything else, follow spectators wait // until all clients have been reconnected after map_restart if ( ent->client->sess.spectatorState != SPECTATOR_FOLLOW ) { ClientEndFrame( ent ); } // clear entity state values BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); }
/* ================ G_RunItem ================ */ void G_RunItem(gentity_t *ent) { vec3_t origin; trace_t tr; int contents; int mask; // if groundentity has been set to -1, it may have been pushed off an edge if (ent->s.groundEntityNum == -1) { if (ent->s.pos.trType != TR_GRAVITY) { ent->s.pos.trType = TR_GRAVITY; ent->s.pos.trTime = level.time; } } if (ent->s.pos.trType == TR_STATIONARY) { // check think function G_RunThink(ent); return; } // get current position BG_EvaluateTrajectory(&ent->s.pos, level.time, origin); // trace a line from the previous position to the current position if (ent->clipmask) { mask = ent->clipmask; } else { mask = MASK_PLAYERSOLID & ~CONTENTS_BODY;//MASK_SOLID; } trap_Trace(&tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->r.ownerNum, mask); VectorCopy(tr.endpos, ent->r.currentOrigin); if (tr.startsolid) { tr.fraction = 0; } trap_LinkEntity(ent); // FIXME: avoid this for stationary? // check think function G_RunThink(ent); if (tr.fraction == 1) { return; } // if it is in a nodrop volume, remove it contents = trap_PointContents(ent->r.currentOrigin, -1); if (contents & CONTENTS_NODROP) { if (ent->item && ent->item->giType == IT_TEAM) { if (g_gametype.integer == GT_INVASION && (ent->item->giTag == PW_REDFLAG || ent->item->giTag == PW_BLUEFLAG)) { InvasionWin(level.AlienTeam, qtrue); } else Team_FreeEntity(ent); } else { G_FreeEntity(ent); } return; } G_BounceItem(ent, &tr); }
/* ================= G_TimeShiftClient Move a client back to where he was at the specified "time" ================= */ void G_TimeShiftClient( gentity_t *ent, int time, qboolean debug, gentity_t *debugger ) { int j, k; //char msg[2048]; // this will dump out the head index, and the time for all the stored positions /* if ( debug ) { char str[MAX_STRING_CHARS]; Com_sprintf(str, sizeof(str), "print \"head: %d, %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n\"", ent->client->historyHead, ent->client->history[0].leveltime, ent->client->history[1].leveltime, ent->client->history[2].leveltime, ent->client->history[3].leveltime, ent->client->history[4].leveltime, ent->client->history[5].leveltime, ent->client->history[6].leveltime, ent->client->history[7].leveltime, ent->client->history[8].leveltime, ent->client->history[9].leveltime, ent->client->history[10].leveltime, ent->client->history[11].leveltime, ent->client->history[12].leveltime, ent->client->history[13].leveltime, ent->client->history[14].leveltime, ent->client->history[15].leveltime, ent->client->history[16].leveltime); trap_SendServerCommand( debugger - g_entities, str ); } */ // find two entries in the history whose times sandwich "time" // assumes no two adjacent records have the same timestamp j = k = ent->client->historyHead; do { if ( ent->client->history[j].leveltime <= time ) break; k = j; j--; if ( j < 0 ) { j = NUM_CLIENT_HISTORY - 1; } } while ( j != ent->client->historyHead ); // if we got past the first iteration above, we've sandwiched (or wrapped) if ( j != k ) { // make sure it doesn't get re-saved if ( ent->client->saved.leveltime != level.time ) { // save the current origin and bounding box VectorCopy( ent->r.mins, ent->client->saved.mins ); VectorCopy( ent->r.maxs, ent->client->saved.maxs ); VectorCopy( ent->r.currentOrigin, ent->client->saved.currentOrigin ); ent->client->saved.leveltime = level.time; } // if we haven't wrapped back to the head, we've sandwiched, so // we shift the client's position back to where he was at "time" if ( j != ent->client->historyHead ) { float frac = (float)(time - ent->client->history[j].leveltime) / (float)(ent->client->history[k].leveltime - ent->client->history[j].leveltime); // interpolate between the two origins to give position at time index "time" TimeShiftLerp( frac, ent->client->history[j].currentOrigin, ent->client->history[k].currentOrigin, ent->r.currentOrigin ); // lerp these too, just for fun (and ducking) TimeShiftLerp( frac, ent->client->history[j].mins, ent->client->history[k].mins, ent->r.mins ); TimeShiftLerp( frac, ent->client->history[j].maxs, ent->client->history[k].maxs, ent->r.maxs ); /*if ( debug && debugger != NULL ) { // print some debugging stuff exactly like what the client does // it starts with "Rec:" to let you know it backward-reconciled Com_sprintf( msg, sizeof(msg), "print \"^1Rec: time: %d, j: %d, k: %d, origin: %0.2f %0.2f %0.2f\n" "^2frac: %0.4f, origin1: %0.2f %0.2f %0.2f, origin2: %0.2f %0.2f %0.2f\n" "^7level.time: %d, est time: %d, level.time delta: %d, est real ping: %d\n\"", time, ent->client->history[j].leveltime, ent->client->history[k].leveltime, ent->r.currentOrigin[0], ent->r.currentOrigin[1], ent->r.currentOrigin[2], frac, ent->client->history[j].currentOrigin[0], ent->client->history[j].currentOrigin[1], ent->client->history[j].currentOrigin[2], ent->client->history[k].currentOrigin[0], ent->client->history[k].currentOrigin[1], ent->client->history[k].currentOrigin[2], level.time, level.time + debugger->client->frameOffset, level.time - time, level.time + debugger->client->frameOffset - time); trap_SendServerCommand( debugger - g_entities, msg ); }*/ // this will recalculate absmin and absmax trap_LinkEntity( ent ); } else { // we wrapped, so grab the earliest VectorCopy( ent->client->history[k].currentOrigin, ent->r.currentOrigin ); VectorCopy( ent->client->history[k].mins, ent->r.mins ); VectorCopy( ent->client->history[k].maxs, ent->r.maxs ); // this will recalculate absmin and absmax trap_LinkEntity( ent ); } } else { // this only happens when the client is using a negative timenudge, because that // number is added to the command time // print some debugging stuff exactly like what the client does // it starts with "No rec:" to let you know it didn't backward-reconcile //Sago: This code looks wierd } }
/* ======================== LaunchGatling by Spoon ======================== */ gentity_t *LaunchGatling( gentity_t *ent ) { gentity_t *gatling; //vec3_t velocity; vec3_t angles, origin; // set aiming origin VectorCopy( ent->client->ps.viewangles, angles ); angles[ROLL] = 0; angles[PITCH] = 0; // always forward VectorClear(origin); AngleVectors( angles, origin, NULL, NULL); VectorScale(origin,30,origin); VectorAdd(ent->s.pos.trBase, origin, origin); //origin[2] -= 10; // snap to integer coordinates for more efficient network bandwidth usage SnapVector( origin); //spawning of the gatling gatling = G_Spawn(); gatling->s.eType = ET_TURRET; gatling->classname = "gatling"; gatling->touch = Touch_Item; G_SetOrigin( gatling, origin ); gatling->s.pos.trType = TR_GRAVITY; gatling->s.pos.trTime = level.time; // VectorCopy( velocity, gatling->s.pos.trDelta ); gatling->s.eFlags |= EF_BOUNCE_HALF; VectorCopy(angles, gatling->r.currentAngles); VectorCopy(gatling->r.currentAngles, gatling->s.apos.trBase); gatling->clipmask = MASK_SHOT; gatling->r.contents = 0; // will be set to MASK_SHOT if no player controls it //gatling->r.ownerNum = ent->s.number; //gatling->parent = ent; gatling->s.pos.trTime = level.time - 50; // move a bit on the very first frame //VectorScale( velocity, 200, gatling->s.pos.trDelta ); // 700 SnapVector( gatling->s.pos.trDelta ); // save net bandwidth gatling->physicsBounce= 0.05f; gatling->physicsObject = qtrue; //marks object as unused gatling->s.eventParm = -1; gatling->r.svFlags |= SVF_BROADCAST; //object thinks gatling->think = Gatling_Think; gatling->nextthink = level.time + 100; //add ammo into it gatling->count = ent->client->ps.ammo[WP_GATLING]; // save ammo count in case player also has a gatling in his inventory ent->client->carriedGatlingAmmo = ent->client->ps.ammo[WP_GATLING]; ent->client->ps.ammo[WP_GATLING] = 0; //build up anims gatling->s.time2 = level.time + TRIPOD_TIME + 4*STAGE_TIME; trap_LinkEntity (gatling); return gatling; }