void G_AwardPlayerPickup( edict_t *self, edict_t *item ) { if( GS_RaceGametype() ) //racesow: no awards for item timings; will be included in basewsw return; if( !item ) return; // MH control if( item->item->tag == HEALTH_MEGA ) { self->r.client->resp.awardInfo.mh_control_award++; if( self->r.client->resp.awardInfo.mh_control_award % 5 == 0 ) G_PlayerAward( self, S_COLOR_CYAN "Mega-Health Control!" ); } // UH control if( item->item->tag == HEALTH_ULTRA ) { self->r.client->resp.awardInfo.uh_control_award++; if( self->r.client->resp.awardInfo.uh_control_award % 5 == 0 ) G_PlayerAward( self, S_COLOR_CYAN "Ultra-Health Control!" ); } // RA control if( item->item->tag == ARMOR_RA ) { self->r.client->resp.awardInfo.ra_control_award++; if( self->r.client->resp.awardInfo.ra_control_award % 5 == 0 ) G_PlayerAward( self, S_COLOR_CYAN "Red Armor Control!" ); } }
/* * W_Plasma_Backtrace */ void W_Plasma_Backtrace( edict_t *ent, const vec3_t start ) { trace_t tr; vec3_t oldorigin; vec3_t mins = { -2, -2, -2 }, maxs = { 2, 2, 2 }; if( GS_RaceGametype() ) return; VectorCopy( ent->s.origin, oldorigin ); VectorCopy( start, ent->s.origin ); do { G_Trace4D( &tr, ent->s.origin, mins, maxs, oldorigin, ent, ( CONTENTS_BODY|CONTENTS_CORPSE ), ent->timeDelta ); VectorCopy( tr.endpos, ent->s.origin ); if( tr.ent == -1 ) break; if( tr.allsolid || tr.startsolid ) W_Touch_Plasma( ent, &game.edicts[tr.ent], NULL, 0 ); else if( tr.fraction != 1.0 ) W_Touch_Plasma( ent, &game.edicts[tr.ent], &tr.plane, tr.surfFlags ); else break; } while( ent->r.inuse && ent->s.type == ET_PLASMA && !VectorCompare( ent->s.origin, oldorigin ) ); if( ent->r.inuse && ent->s.type == ET_PLASMA ) VectorCopy( oldorigin, ent->s.origin ); }
/* * W_Fire_LinearProjectile - Spawn a generic linear projectile without a model, touch func, sound nor mod */ static edict_t *W_Fire_LinearProjectile( edict_t *self, vec3_t start, vec3_t angles, int speed, float damage, int minKnockback, int maxKnockback, int stun, int minDamage, int radius, int timeout, int timeDelta ) { edict_t *projectile; vec3_t dir; projectile = G_Spawn(); VectorCopy( start, projectile->s.origin ); VectorCopy( start, projectile->s.old_origin ); VectorCopy( start, projectile->olds.origin ); VectorCopy( angles, projectile->s.angles ); AngleVectors( angles, dir, NULL, NULL ); VectorScale( dir, speed, projectile->velocity ); GS_SnapVelocity( projectile->velocity ); projectile->movetype = MOVETYPE_LINEARPROJECTILE; projectile->s.linearProjectile = qtrue; projectile->r.solid = SOLID_YES; projectile->r.clipmask = ( !GS_RaceGametype() ) ? MASK_SHOT : MASK_SOLID; projectile->r.svflags = SVF_PROJECTILE; // enable me when drawing exception is added to cgame projectile->r.svflags |= SVF_TRANSMITORIGIN2; VectorClear( projectile->r.mins ); VectorClear( projectile->r.maxs ); projectile->s.modelindex = 0; projectile->r.owner = self; projectile->s.ownerNum = ENTNUM( self ); projectile->touch = W_Touch_Projectile; //generic one. Should be replaced after calling this func projectile->nextThink = level.time + timeout; projectile->think = G_FreeEdict; projectile->classname = NULL; // should be replaced after calling this func. projectile->style = 0; projectile->s.sound = 0; projectile->timeStamp = level.time; projectile->s.linearProjectileTimeStamp = game.serverTime; projectile->timeDelta = timeDelta; projectile->projectileInfo.minDamage = min( minDamage, damage ); projectile->projectileInfo.maxDamage = damage; projectile->projectileInfo.minKnockback = min( minKnockback, maxKnockback ); projectile->projectileInfo.maxKnockback = maxKnockback; projectile->projectileInfo.stun = stun; projectile->projectileInfo.radius = radius; GClip_LinkEntity( projectile ); // update some data required for the transmission VectorCopy( projectile->velocity, projectile->s.linearProjectileVelocity ); projectile->s.team = self->s.team; projectile->s.modelindex2 = ( abs( timeDelta ) > 255 ) ? 255 : (unsigned int)abs( timeDelta ); return projectile; }
/* * Touch_Item */ void Touch_Item( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags ) { bool taken; const gsitem_t *item = ent->item; if( !other->r.client || G_ISGHOSTING( other ) ) return; if( !( other->r.client->ps.pmove.stats[PM_STAT_FEATURES] & PMFEAT_ITEMPICK ) ) return; if( !item || !( item->flags & ITFLAG_PICKABLE ) ) return; // not a grabbable item if( !G_Gametype_CanPickUpItem( item ) ) return; taken = G_PickupItem( other, item, ent->spawnflags, ent->count, ent->invpak ); if( !( ent->spawnflags & ITEM_TARGETS_USED ) ) { G_UseTargets( ent, other ); ent->spawnflags |= ITEM_TARGETS_USED; } if( !taken ) return; if( ent->spawnflags & ITEM_TIMED ) ent->r.owner = other; // flash the screen G_AddPlayerStateEvent( other->r.client, PSEV_PICKUP, ( item->flags & IT_WEAPON ? item->tag : 0 ) ); G_AwardPlayerPickup( other, ent ); // for messages other->r.client->teamstate.last_pickup = ent; // show icon and name on status bar other->r.client->ps.stats[STAT_PICKUP_ITEM] = item->tag; other->r.client->resp.pickup_msg_time = level.time + 3000; if( ent->attenuation ) Touch_ItemSound( other, item ); if( !( ent->spawnflags & DROPPED_ITEM ) && G_Gametype_CanRespawnItem( item ) ) { if( (item->type & IT_WEAPON ) && GS_RaceGametype() ) return; // weapons stay in race SetRespawn( ent, G_Gametype_RespawnTimeForItem( item ) ); return; } G_FreeEdict( ent ); }
/* * W_Fire_TossProjectile - Spawn a generic projectile without a model, touch func, sound nor mod */ static edict_t *W_Fire_TossProjectile( edict_t *self, vec3_t start, vec3_t angles, int speed, float damage, int minKnockback, int maxKnockback, int stun, int minDamage, int radius, int timeout, int timeDelta ) { edict_t *projectile; vec3_t dir; projectile = G_Spawn(); VectorCopy( start, projectile->s.origin ); VectorCopy( start, projectile->s.old_origin ); VectorCopy( start, projectile->olds.origin ); VectorCopy( angles, projectile->s.angles ); AngleVectors( angles, dir, NULL, NULL ); VectorScale( dir, speed, projectile->velocity ); GS_SnapVelocity( projectile->velocity ); projectile->movetype = MOVETYPE_BOUNCEGRENADE; // make missile fly through players in race if( GS_RaceGametype() ) projectile->r.clipmask = MASK_SOLID; else projectile->r.clipmask = MASK_SHOT; projectile->r.solid = SOLID_YES; projectile->r.svflags = SVF_PROJECTILE; VectorClear( projectile->r.mins ); VectorClear( projectile->r.maxs ); //projectile->s.modelindex = trap_ModelIndex ("models/objects/projectile/plasmagun/proj_plasmagun2.md3"); projectile->s.modelindex = 0; projectile->r.owner = self; projectile->touch = W_Touch_Projectile; //generic one. Should be replaced after calling this func projectile->nextThink = level.time + timeout; projectile->think = G_FreeEdict; projectile->classname = NULL; // should be replaced after calling this func. projectile->style = 0; projectile->s.sound = 0; projectile->timeStamp = level.time; projectile->timeDelta = timeDelta; projectile->s.team = self->s.team; projectile->projectileInfo.minDamage = min( minDamage, damage ); projectile->projectileInfo.maxDamage = damage; projectile->projectileInfo.minKnockback = min( minKnockback, maxKnockback ); projectile->projectileInfo.maxKnockback = maxKnockback; projectile->projectileInfo.stun = stun; projectile->projectileInfo.radius = radius; GClip_LinkEntity( projectile ); return projectile; }
/* * Items may be spawned above other entities and they need them spawned before */ void G_Items_FinishSpawningItems( void ) { int num_timers, num_opts; edict_t *ent; edict_t *ops[MAX_EDICTS]; num_timers = num_opts = 0; for( ent = game.edicts + 1 + gs.maxclients; ENTNUM( ent ) < game.numentities; ent++ ) { if( !ent->r.inuse || !ent->item || ent->s.type != ET_ITEM ) continue; Finish_SpawningItem( ent ); // spawned inside solid if( !ent->r.inuse ) continue; if( G_ItemTimerNeeded( ent->item ) && !GS_RaceGametype() ) //racesow: no item timers; will be included in basewsw if not, use the AS fix from ticket #246#comment:6 { if( Spawn_ItemTimer( ent ) ) num_timers++; } else if( G_ItemTimerUnimportant( ent->item ) ) { ops[num_opts++] = ent; } } ops[num_opts] = NULL; // if there are less timers than MAX_IMPORTANT_ITEMS_THRESHOLD, spawn // timers for less important items if( num_timers < MAX_IMPORTANT_ITEMS_THRESHOLD && !GS_RaceGametype() ) //racesow: no item timers; will be included in basewsw { for( ; num_opts > 0; num_opts-- ) Spawn_ItemTimer( ops[num_opts-1] ); } }
/* * ClientDisconnect * Called when a player drops from the server. * Will not be called between levels. */ void ClientDisconnect( edict_t *ent, const char *reason ) { int team; if( !ent->r.client || !ent->r.inuse ) return; // always report in RACE mode if( GS_RaceGametype() || ( ent->r.client->team != TEAM_SPECTATOR && ( GS_MatchState() == MATCH_STATE_PLAYTIME || GS_MatchState() == MATCH_STATE_POSTMATCH ) ) ) G_AddPlayerReport( ent, GS_MatchState() == MATCH_STATE_POSTMATCH ); for( team = TEAM_PLAYERS; team < GS_MAX_TEAMS; team++ ) G_Teams_UnInvitePlayer( team, ent ); if( !level.gametype.disableObituaries || !(ent->r.svflags & SVF_FAKECLIENT ) ) { if( !reason ) G_PrintMsg( NULL, "%s" S_COLOR_WHITE " disconnected\n", ent->r.client->netname ); else G_PrintMsg( NULL, "%s" S_COLOR_WHITE " disconnected (%s" S_COLOR_WHITE ")\n", ent->r.client->netname, reason ); } // send effect if( ent->s.team > TEAM_SPECTATOR ) G_TeleportEffect( ent, false ); ent->r.client->team = TEAM_SPECTATOR; G_ClientRespawn( ent, true ); // respawn as ghost ent->movetype = MOVETYPE_NOCLIP; // allow freefly // let the gametype scripts know this client just disconnected G_Gametype_ScoreEvent( ent->r.client, "disconnect", NULL ); G_FreeAI( ent ); AI_EnemyRemoved( ent ); ent->r.inuse = false; ent->r.svflags = SVF_NOCLIENT; memset( ent->r.client, 0, sizeof( *ent->r.client ) ); ent->r.client->ps.playerNum = PLAYERNUM( ent ); trap_ConfigString( CS_PLAYERINFOS+PLAYERNUM( ent ), "" ); GClip_UnlinkEntity( ent ); G_Match_CheckReadys(); }
/* * Cmd_Kill_f */ static void Cmd_Kill_f( edict_t *ent ) { if( ent->r.solid == SOLID_NOT ) return; // can suicide after 5 seconds if( level.time < ent->r.client->resp.timeStamp + ( GS_RaceGametype() ? 1000 : 5000 ) ) return; ent->flags &= ~FL_GODMODE; ent->health = 0; meansOfDeath = MOD_SUICIDE; // wsw : pb : fix /kill command G_Killed( ent, ent, ent, 100000, vec3_origin, MOD_SUICIDE ); }
void G_AwardPlayerPickup( edict_t *self, edict_t *item ) { if( GS_RaceGametype() ) //racesow: no awards for item timings; will be included in basewsw //FIXME: Doesn't seem like it was included ... -K1ll return; if( !item ) return; // MH control if( item->item->tag == HEALTH_MEGA ) { self->r.client->level.stats.mh_taken++; self->r.client->resp.awardInfo.mh_control_award++; if( self->r.client->resp.awardInfo.mh_control_award % 5 == 0 ) G_PlayerAward( self, S_COLOR_CYAN "Mega-Health Control!" ); } // UH control else if( item->item->tag == HEALTH_ULTRA ) { self->r.client->level.stats.uh_taken++; self->r.client->resp.awardInfo.uh_control_award++; if( self->r.client->resp.awardInfo.uh_control_award % 5 == 0 ) G_PlayerAward( self, S_COLOR_CYAN "Ultra-Health Control!" ); } // RA control else if( item->item->tag == ARMOR_RA ) { self->r.client->level.stats.ra_taken++; self->r.client->resp.awardInfo.ra_control_award++; if( self->r.client->resp.awardInfo.ra_control_award % 5 == 0 ) G_PlayerAward( self, S_COLOR_CYAN "Red Armor Control!" ); } // Other items counts else if( item->item->tag == ARMOR_GA ) self->r.client->level.stats.ga_taken++; else if( item->item->tag == ARMOR_YA ) self->r.client->level.stats.ya_taken++; else if( item->item->tag == POWERUP_QUAD ) self->r.client->level.stats.quads_taken++; else if( item->item->tag == POWERUP_REGEN ) self->r.client->level.stats.regens_taken++; else if( item->item->tag == POWERUP_SHELL ) self->r.client->level.stats.shells_taken++; }
/* * G_TriggerWait * * Called always when using a trigger that supports wait flag * Returns true if the trigger shouldn't be activated */ static bool G_TriggerWait( edict_t *ent, edict_t *other ) { if( GS_RaceGametype() ) { if( other->trigger_entity == ent && other->trigger_timeout && other->trigger_timeout >= level.time ) return true; other->trigger_entity = ent; other->trigger_timeout = level.time + 1000 * ent->wait; return false; } if( ent->timeStamp >= level.time ) return true; // the wait time has passed, so set back up for another activation ent->timeStamp = level.time + ( ent->wait * 1000 ); return false; }
/* * W_Fire_Blade */ void W_Fire_Blade( edict_t *self, int range, vec3_t start, vec3_t angles, float damage, int knockback, int stun, int mod, int timeDelta ) { edict_t *event, *other = NULL; vec3_t end; trace_t trace; int mask = MASK_SHOT; vec3_t dir; int dmgflags = 0; if( GS_Instagib() ) damage = 9999; AngleVectors( angles, dir, NULL, NULL ); VectorMA( start, range, dir, end ); if( GS_RaceGametype() ) mask = MASK_SOLID; G_Trace4D( &trace, start, NULL, NULL, end, self, MASK_SHOT, timeDelta ); if( trace.ent == -1 ) //didn't touch anything return; // find out what touched other = &game.edicts[trace.ent]; if( !other->takedamage ) // it was the world { // wall impact VectorMA( trace.endpos, -0.02, dir, end ); event = G_SpawnEvent( EV_BLADE_IMPACT, 0, end ); event->s.ownerNum = ENTNUM( self ); VectorScale( trace.plane.normal, 1024, event->s.origin2 ); event->r.svflags = SVF_TRANSMITORIGIN2; return; } // it was a player G_Damage( other, self, self, dir, dir, other->s.origin, damage, knockback, stun, dmgflags, mod ); }
/* * Pickup_Weapon */ bool Pickup_Weapon( edict_t *other, const gsitem_t *item, int flags, int ammo_count ) { int ammo_tag; gs_weapon_definition_t *weapondef; weapondef = GS_GetWeaponDef( item->tag ); if( !(flags & DROPPED_ITEM) ) { // weapons stay in race if( GS_RaceGametype() && ( other->r.client->ps.inventory[item->tag] != 0 ) ) return false; } other->r.client->ps.inventory[item->tag]++; // never allow the player to carry more than 2 copies of the same weapon if( other->r.client->ps.inventory[item->tag] > item->inventory_max ) other->r.client->ps.inventory[item->tag] = item->inventory_max; if( !(flags & DROPPED_ITEM) ) { // give them some ammo with it ammo_tag = item->ammo_tag; if( ammo_tag ) Add_Ammo( other->r.client, GS_FindItemByTag( ammo_tag ), weapondef->firedef.weapon_pickup, true ); } else { // it's a dropped weapon ammo_tag = item->ammo_tag; if( ammo_count && ammo_tag ) Add_Ammo( other->r.client, GS_FindItemByTag( ammo_tag ), ammo_count, true ); } return true; }
/* * G_Damage * targ entity that is being damaged * inflictor entity that is causing the damage * attacker entity that caused the inflictor to damage targ * example: targ=enemy, inflictor=rocket, attacker=player * * dir direction of the attack * point point at which the damage is being inflicted * normal normal vector from that point * damage amount of damage being inflicted * knockback force to be applied against targ as a result of the damage * * dflags these flags are used to control how T_Damage works */ void G_Damage( edict_t *targ, edict_t *inflictor, edict_t *attacker, const vec3_t pushdir, const vec3_t dmgdir, const vec3_t point, float damage, float knockback, float stun, int dflags, int mod ) { gclient_t *client; float take; float save; float asave; qboolean statDmg; if( !targ || !targ->takedamage ) return; if( !attacker ) { attacker = world; mod = MOD_TRIGGER_HURT; } meansOfDeath = mod; client = targ->r.client; // Cgg - race mode: players don't interact with one another if( GS_RaceGametype() ) { if( attacker->r.client && targ->r.client && attacker != targ ) return; } // push if( !( dflags & DAMAGE_NO_KNOCKBACK ) ) G_KnockBackPush( targ, attacker, pushdir, knockback, dflags ); // stun if( g_allow_stun->integer && !( dflags & (DAMAGE_NO_STUN|FL_GODMODE) ) && (int)stun > 0 && targ->r.client && targ->r.client->resp.takeStun && !GS_IsTeamDamage( &targ->s, &attacker->s ) && ( targ != attacker ) ) { if( dflags & DAMAGE_STUN_CLAMP ) { if( targ->r.client->ps.pmove.stats[PM_STAT_STUN] < (int)stun ) targ->r.client->ps.pmove.stats[PM_STAT_STUN] = (int)stun; } else targ->r.client->ps.pmove.stats[PM_STAT_STUN] += (int)stun; clamp( targ->r.client->ps.pmove.stats[PM_STAT_STUN], 0, MAX_STUN_TIME ); } // dont count self-damage cause it just adds the same to both stats statDmg = ( attacker != targ ) && ( mod != MOD_TELEFRAG ); // apply handicap on the damage given if( statDmg && attacker->r.client && !GS_Instagib() ) { // handicap is a percentage value if( attacker->r.client->handicap != 0 ) damage *= 1.0 - (attacker->r.client->handicap * 0.01f); } take = damage; save = 0; // check for cases where damage is protected if( !( dflags & DAMAGE_NO_PROTECTION ) ) { // check for godmode if( targ->flags & FL_GODMODE ) { take = 0; save = damage; } // never damage in timeout else if( GS_MatchPaused() ) { take = save = 0; } // ca has self splash damage disabled else if( ( dflags & DAMAGE_RADIUS ) && attacker == targ && !GS_SelfDamage() ) { take = save = 0; } // don't get damage from players in race else if( ( GS_RaceGametype() ) && attacker->r.client && targ->r.client && ( attacker->r.client != targ->r.client ) ) { take = save = 0; } // team damage avoidance else if( GS_IsTeamDamage( &targ->s, &attacker->s ) && !G_Gametype_CanTeamDamage( dflags ) ) { take = save = 0; } // apply warShell powerup protection else if( targ->r.client && targ->r.client->ps.inventory[POWERUP_SHELL] > 0 ) { // warshell offers full protection in instagib if( GS_Instagib() ) { take = 0; save = damage; } else { take = ( damage * 0.25f ); save = damage - take; } // todo : add protection sound } } asave = G_CheckArmor( targ, take, dflags ); take -= asave; //treat cheat/powerup savings the same as armor asave += save; // APPLY THE DAMAGES if( !take && !asave ) return; // do the damage if( take <= 0 ) return; // adding damage given/received to stats if( statDmg && attacker->r.client && !targ->deadflag && targ->movetype != MOVETYPE_PUSH && targ->s.type != ET_CORPSE ) { attacker->r.client->level.stats.total_damage_given += take + asave; teamlist[attacker->s.team].stats.total_damage_given += take + asave; if( GS_IsTeamDamage( &targ->s, &attacker->s ) ) { attacker->r.client->level.stats.total_teamdamage_given += take + asave; teamlist[attacker->s.team].stats.total_teamdamage_given += take + asave; } } G_Gametype_ScoreEvent( attacker->r.client, "dmg", va( "%i %f %i", targ->s.number, damage, attacker->s.number ) ); if( statDmg && client ) { client->level.stats.total_damage_received += take + asave; teamlist[targ->s.team].stats.total_damage_received += take + asave; if( GS_IsTeamDamage( &targ->s, &attacker->s ) ) { client->level.stats.total_teamdamage_received += take + asave; teamlist[targ->s.team].stats.total_teamdamage_received += take + asave; } } // accumulate received damage for snapshot effects { vec3_t dorigin; if( inflictor == world && mod == MOD_FALLING ) // it's fall damage targ->snap.damage_fall += take + save; if( point[0] != 0.0f || point[1] != 0.0f || point[2] != 0.0f ) VectorCopy( point, dorigin ); else VectorSet( dorigin, targ->s.origin[0], targ->s.origin[1], targ->s.origin[2] + targ->viewheight ); G_BlendFrameDamage( targ, take, &targ->snap.damage_taken, dorigin, dmgdir, targ->snap.damage_at, targ->snap.damage_dir ); G_BlendFrameDamage( targ, save, &targ->snap.damage_saved, dorigin, dmgdir, targ->snap.damage_at, targ->snap.damage_dir ); if( targ->r.client ) { if( mod != MOD_FALLING && mod != MOD_TELEFRAG && mod != MOD_SUICIDE ) { if( inflictor == world || attacker == world ) { // for world inflicted damage use always 'frontal' G_ClientAddDamageIndicatorImpact( targ->r.client, take + save, NULL ); } else if( dflags & DAMAGE_RADIUS ) { // for splash hits the direction is from the inflictor origin G_ClientAddDamageIndicatorImpact( targ->r.client, take + save, pushdir ); } else { // for direct hits the direction is the projectile direction G_ClientAddDamageIndicatorImpact( targ->r.client, take + save, dmgdir ); } } } } targ->health = targ->health - take; // add damage done to stats if( !GS_IsTeamDamage( &targ->s, &attacker->s ) && statDmg && G_ModToAmmo( mod ) != AMMO_NONE && client && attacker->r.client ) { attacker->r.client->level.stats.accuracy_hits[G_ModToAmmo( mod )-AMMO_GUNBLADE]++; attacker->r.client->level.stats.accuracy_damage[G_ModToAmmo( mod )-AMMO_GUNBLADE] += damage; teamlist[attacker->s.team].stats.accuracy_hits[G_ModToAmmo( mod )-AMMO_GUNBLADE]++; teamlist[attacker->s.team].stats.accuracy_damage[G_ModToAmmo( mod )-AMMO_GUNBLADE] += damage; G_AwardPlayerHit( targ, attacker, mod ); } // accumulate given damage for hit sounds if( ( take || asave ) && targ != attacker && client && !targ->deadflag ) { if( attacker ) { if( GS_IsTeamDamage( &targ->s, &attacker->s ) ) attacker->snap.damageteam_given += take + asave; // we want to know how good our hit was, so saved also matters else attacker->snap.damage_given += take + asave; } } if( G_IsDead( targ ) ) { if( client ) targ->flags |= FL_NO_KNOCKBACK; G_Killed( targ, inflictor, attacker, HEALTH_TO_INT( take ), point, mod ); } else { G_CallPain( targ, attacker, knockback, take ); } }
/* * G_Match_LaunchState */ void G_Match_LaunchState( int matchState ) { static bool advance_queue = false; if( game.asEngine != NULL ) { // give the gametype a chance to refuse the state change, or to set up things for it if( !GT_asCallMatchStateFinished( matchState ) ) return; } else { // There isn't any script, run generic fuction if( !G_Gametype_GENERIC_MatchStateFinished( matchState ) ) return; } GS_GamestatSetFlag( GAMESTAT_FLAG_MATCHEXTENDED, false ); GS_GamestatSetFlag( GAMESTAT_FLAG_WAITING, false ); if( matchState == MATCH_STATE_POSTMATCH ) { level.finalMatchDuration = game.serverTime - GS_MatchStartTime(); } if( ( matchState == MATCH_STATE_POSTMATCH && GS_RaceGametype() ) || ( matchState != MATCH_STATE_POSTMATCH && gs.gameState.stats[GAMESTAT_MATCHSTATE] == MATCH_STATE_POSTMATCH ) ) { // entering postmatch in race or leaving postmatch in normal gt G_Match_SendReport(); trap_MM_GameState( false ); } switch( matchState ) { default: case MATCH_STATE_WARMUP: { advance_queue = false; level.forceStart = false; gs.gameState.stats[GAMESTAT_MATCHSTATE] = MATCH_STATE_WARMUP; gs.gameState.longstats[GAMELONG_MATCHDURATION] = (unsigned int)( fabs( g_warmup_timelimit->value * 60 ) * 1000 ); gs.gameState.longstats[GAMELONG_MATCHSTART] = game.serverTime; // race has playtime in warmup too, so flag the matchmaker about this if( GS_RaceGametype() ) trap_MM_GameState( true ); break; } case MATCH_STATE_COUNTDOWN: { advance_queue = true; gs.gameState.stats[GAMESTAT_MATCHSTATE] = MATCH_STATE_COUNTDOWN; gs.gameState.longstats[GAMELONG_MATCHDURATION] = (unsigned int)( fabs( g_countdown_time->value ) * 1000 ); gs.gameState.longstats[GAMELONG_MATCHSTART] = game.serverTime; break; } case MATCH_STATE_PLAYTIME: { // ch : should clear some statcollection memory from warmup? advance_queue = true; // shouldn't be needed here level.forceStart = false; gs.gameState.stats[GAMESTAT_MATCHSTATE] = MATCH_STATE_PLAYTIME; gs.gameState.longstats[GAMELONG_MATCHDURATION] = (unsigned int)( fabs( 60 * g_timelimit->value )*1000 ); gs.gameState.longstats[GAMELONG_MATCHSTART] = game.serverTime; // request a new match UUID trap_ConfigString( CS_MATCHUUID, "" ); // tell matchmaker that the game is on, so if // client disconnects before SendReport, it is flagged // as 'purgable' on MM side trap_MM_GameState( true ); } break; case MATCH_STATE_POSTMATCH: { gs.gameState.stats[GAMESTAT_MATCHSTATE] = MATCH_STATE_POSTMATCH; gs.gameState.longstats[GAMELONG_MATCHDURATION] = (unsigned int)fabs( g_postmatch_timelimit->value * 1000 ); // postmatch time in seconds gs.gameState.longstats[GAMELONG_MATCHSTART] = game.serverTime; G_Timeout_Reset(); level.teamlock = false; level.forceExit = false; G_Match_Autorecord_Stats(); } break; case MATCH_STATE_WAITEXIT: { if( advance_queue ) { G_Teams_AdvanceChallengersQueue(); advance_queue = true; } gs.gameState.stats[GAMESTAT_MATCHSTATE] = MATCH_STATE_WAITEXIT; gs.gameState.longstats[GAMELONG_MATCHDURATION] = 25000; gs.gameState.longstats[GAMELONG_MATCHSTART] = game.serverTime; level.exitNow = false; } break; } // give the gametype the chance to setup for the new state if( game.asEngine != NULL ) GT_asCallMatchStateStarted(); else G_Gametype_GENERIC_MatchStateStarted(); G_UpdatePlayersMatchMsgs(); }
/* * G_UpdateServerInfo * update the cvars which show the match state at server browsers */ static void G_UpdateServerInfo( void ) { // g_match_time if( GS_MatchState() <= MATCH_STATE_WARMUP ) { trap_Cvar_ForceSet( "g_match_time", "Warmup" ); } else if( GS_MatchState() == MATCH_STATE_COUNTDOWN ) { trap_Cvar_ForceSet( "g_match_time", "Countdown" ); } else if( GS_MatchState() == MATCH_STATE_PLAYTIME ) { // partly from G_GetMatchState char extra[MAX_INFO_VALUE]; int clocktime, timelimit, mins, secs; if( GS_MatchDuration() ) timelimit = ( ( GS_MatchDuration() ) * 0.001 ) / 60; else timelimit = 0; clocktime = (float)( game.serverTime - GS_MatchStartTime() ) * 0.001f; if( clocktime <= 0 ) { mins = 0; secs = 0; } else { mins = clocktime / 60; secs = clocktime - mins * 60; } extra[0] = 0; if( GS_MatchExtended() ) { if( timelimit ) Q_strncatz( extra, " overtime", sizeof( extra ) ); else Q_strncatz( extra, " suddendeath", sizeof( extra ) ); } if( GS_MatchPaused() ) Q_strncatz( extra, " (in timeout)", sizeof( extra ) ); if( timelimit ) trap_Cvar_ForceSet( "g_match_time", va( "%02i:%02i / %02i:00%s", mins, secs, timelimit, extra ) ); else trap_Cvar_ForceSet( "g_match_time", va( "%02i:%02i%s", mins, secs, extra ) ); } else { trap_Cvar_ForceSet( "g_match_time", "Finished" ); } // g_match_score if( GS_MatchState() >= MATCH_STATE_PLAYTIME && GS_TeamBasedGametype() ) { char score[MAX_INFO_STRING]; score[0] = 0; Q_strncatz( score, va( " %s: %i", GS_TeamName( TEAM_ALPHA ), teamlist[TEAM_ALPHA].stats.score ), sizeof( score ) ); Q_strncatz( score, va( " %s: %i", GS_TeamName( TEAM_BETA ), teamlist[TEAM_BETA].stats.score ), sizeof( score ) ); if( strlen( score ) >= MAX_INFO_VALUE ) { // prevent "invalid info cvar value" flooding score[0] = '\0'; } trap_Cvar_ForceSet( "g_match_score", score ); } else { trap_Cvar_ForceSet( "g_match_score", "" ); } // g_needpass if( password->modified ) { if( password->string && strlen( password->string ) ) { trap_Cvar_ForceSet( "g_needpass", "1" ); } else { trap_Cvar_ForceSet( "g_needpass", "0" ); } password->modified = false; } // g_gametypes_available if( g_votable_gametypes->modified || g_disable_vote_gametype->modified ) { if( g_disable_vote_gametype->integer || !g_votable_gametypes->string || !strlen( g_votable_gametypes->string ) ) { trap_Cvar_ForceSet( "g_gametypes_available", "" ); } else { char *votable; char *name; size_t len; int count; len = 0; for( count = 0; ( name = G_ListNameForPosition( g_gametypes_list->string, count, CHAR_GAMETYPE_SEPARATOR ) ) != NULL; count++ ) { if( G_Gametype_IsVotable( name ) ) len += strlen( name ) + 1; } len++; votable = ( char * )G_Malloc( len ); votable[0] = 0; for( count = 0; ( name = G_ListNameForPosition( g_gametypes_list->string, count, CHAR_GAMETYPE_SEPARATOR ) ) != NULL; count++ ) { if( G_Gametype_IsVotable( name ) ) { Q_strncatz( votable, name, len ); Q_strncatz( votable, " ", len ); } } //votable[ strlen( votable )-2 ] = 0; // remove the last space trap_Cvar_ForceSet( "g_gametypes_available", votable ); G_Free( votable ); } g_votable_gametypes->modified = false; g_disable_vote_gametype->modified = false; } if( GS_RaceGametype() ) { trap_Cvar_ForceSet( "g_race_gametype", "1" ); } else { trap_Cvar_ForceSet( "g_race_gametype", "0" ); } }
/* * G_FireWeapon */ void G_FireWeapon( edict_t *ent, int parm ) { gs_weapon_definition_t *weapondef; firedef_t *firedef; edict_t *projectile; vec3_t origin, angles; vec3_t viewoffset = { 0, 0, 0 }; int ucmdSeed; // racesow float prestep; // !racesow weapondef = GS_GetWeaponDef( ( parm & ~EV_INVERSE ) ); firedef = ( parm & EV_INVERSE ) ? &weapondef->firedef : &weapondef->firedef_weak; // find this shot projection source if( ent->r.client ) { viewoffset[2] += ent->r.client->ps.viewheight; VectorCopy( ent->r.client->ps.viewangles, angles ); is_quad = ( ent->r.client->ps.inventory[POWERUP_QUAD] > 0 ); ucmdSeed = ent->r.client->ucmd.serverTimeStamp & 255; } else { VectorCopy( ent->s.angles, angles ); is_quad = qfalse; ucmdSeed = rand() & 255; } VectorAdd( ent->s.origin, viewoffset, origin ); // racesow prestep=g_projectile_prestep->value; // !racesow // shoot projectile = NULL; switch( weapondef->weapon_id ) { default: case WEAP_NONE: break; case WEAP_GUNBLADE: if( firedef->fire_mode == FIRE_MODE_STRONG ) projectile = G_Fire_Gunblade_Blast( origin, angles, firedef, ent, ucmdSeed ); else projectile = G_Fire_Gunblade_Knife( origin, angles, firedef, ent ); break; case WEAP_MACHINEGUN: projectile = G_Fire_Machinegun( origin, angles, firedef, ent, ucmdSeed ); break; case WEAP_RIOTGUN: projectile = G_Fire_Riotgun( origin, angles, firedef, ent, ucmdSeed ); break; case WEAP_GRENADELAUNCHER: projectile = G_Fire_Grenade( origin, angles, firedef, ent, ucmdSeed ); // racesow if( GS_RaceGametype() ) //prestep/=2; // racesow 0.42 had default prestep=48 and genade prestep=24 prestep=trap_Cvar_Get( "rs_grenade_prestep", "90", CVAR_ARCHIVE )->integer; // !racesow break; case WEAP_ROCKETLAUNCHER: projectile = G_Fire_Rocket( origin, angles, firedef, ent, ucmdSeed ); // racesow if( GS_RaceGametype() ) //prestep=0; // racesow 0.42 had rocket prestep=0 prestep=trap_Cvar_Get( "rs_rocket_prestep", "90", CVAR_ARCHIVE )->integer; // !racesow break; case WEAP_PLASMAGUN: projectile = G_Fire_Plasma( origin, angles, firedef, ent, ucmdSeed ); // racesow if( GS_RaceGametype() ) //prestep*=2/3; // racesow 0.42 had plasma prestep=32 prestep=trap_Cvar_Get( "rs_plasma_prestep", "90", CVAR_ARCHIVE )->integer; // !racesow break; case WEAP_LASERGUN: projectile = G_Fire_Lasergun( origin, angles, firedef, ent, ucmdSeed ); break; case WEAP_ELECTROBOLT: projectile = G_Fire_StrongBolt( origin, angles, firedef, ent, ucmdSeed ); break; case WEAP_INSTAGUN: projectile = G_Fire_Instagun( origin, angles, firedef, ent, ucmdSeed ); break; } // add stats if( ent->r.client && weapondef->weapon_id != WEAP_NONE ) ent->r.client->level.stats.accuracy_shots[firedef->ammo_id - AMMO_GUNBLADE] += firedef->projectile_count; if( projectile ) { //if( projectile->s.linearProjectile ) // convert distance to time for linear projectiles // G_ProjectileTimePrestep( projectile, 1000.0f * ( g_projectile_prestep->value / VectorLengthFast( projectile->velocity ) ) ); //else //G_ProjectileDistancePrestep( projectile, g_projectile_prestep->value ); //racesow Seems like this was added in warsow 0.7 -K1ll // racesow: modified prestep G_ProjectileDistancePrestep( projectile, prestep ); // !racesow } // racesow: enable skipping no_antilag if rs_rocket_antilag is 1 if ( GS_RaceGametype() && ((trap_Cvar_Get( "rs_rocket_antilag", "0", CVAR_ARCHIVE )->integer==1 && projectile->s.type == ET_ROCKET))) return; // !racesow #ifdef NO_ROCKET_ANTILAG // hack for disabling antilag on rockets if( projectile && (projectile->s.type == ET_ROCKET || projectile->s.type == ET_PLASMA) )//racesow { int timeOffset; timeOffset = -projectile->timeDelta; projectile->timeDelta = 0; if( projectile->s.linearProjectile ) projectile->s.modelindex2 = 0; // racesow: testing .42 time prestep function, because im really not sure it is equivalent to the .5 one; the difference is not that major anyway.. // G_ProjectileTimePrestep( projectile, timeOffset ); rs_TimeDeltaPrestepProjectile(projectile,-timeOffset); // !racesow } #endif }
/* * CopyToBodyQue */ static edict_t *CopyToBodyQue( edict_t *ent, edict_t *attacker, int damage ) { edict_t *body; int contents; if( GS_RaceGametype() ) return NULL; contents = G_PointContents( ent->s.origin ); if( contents & CONTENTS_NODROP ) return NULL; G_Client_UnlinkBodies( ent ); // grab a body que and cycle to the next one body = &game.edicts[gs.maxclients + level.body_que + 1]; level.body_que = ( level.body_que + 1 ) % BODY_QUEUE_SIZE; // send an effect on the removed body if( body->s.modelindex && body->s.type == ET_CORPSE ) ThrowSmallPileOfGibs( body, 10 ); GClip_UnlinkEntity( body ); memset( body, 0, sizeof( edict_t ) ); //clean up garbage //init body edict G_InitEdict( body ); body->classname = "body"; body->health = ent->health; body->mass = ent->mass; body->r.owner = ent->r.owner; body->s.type = ent->s.type; body->s.team = ent->s.team; body->s.effects = 0; body->r.svflags = SVF_CORPSE; body->r.svflags &= ~SVF_NOCLIENT; body->activator = ent; if( g_deadbody_followkiller->integer ) body->enemy = attacker; //use flat yaw body->s.angles[PITCH] = 0; body->s.angles[ROLL] = 0; body->s.angles[YAW] = ent->s.angles[YAW]; body->s.modelindex2 = 0; // <- is bodyOwner when in ET_CORPSE, but not in ET_GENERIC or ET_PLAYER body->s.weapon = 0; //copy player position and box size VectorCopy( ent->s.old_origin, body->s.old_origin ); VectorCopy( ent->s.origin, body->s.origin ); VectorCopy( ent->s.origin, body->olds.origin ); VectorCopy( ent->r.mins, body->r.mins ); VectorCopy( ent->r.maxs, body->r.maxs ); VectorCopy( ent->r.absmin, body->r.absmin ); VectorCopy( ent->r.absmax, body->r.absmax ); VectorCopy( ent->r.size, body->r.size ); VectorCopy( ent->velocity, body->velocity ); body->r.maxs[2] = body->r.mins[2] + 8; body->r.solid = SOLID_YES; body->takedamage = DAMAGE_YES; body->r.clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; body->movetype = MOVETYPE_TOSS; body->die = body_die; body->think = body_think; // body self destruction countdown if( ent->health < GIB_HEALTH || meansOfDeath == MOD_ELECTROBOLT_S /* electrobolt always gibs */ ) { ThrowSmallPileOfGibs( body, damage ); // reset gib impulse VectorClear( body->velocity ); ThrowClientHead( body, damage ); // sets ET_GIB body->s.frame = 0; body->nextThink = level.time + 3000 + random() * 3000; body->deadflag = DEAD_DEAD; } else if( ent->s.type == ET_PLAYER ) { // copy the model body->s.type = ET_CORPSE; body->s.modelindex = ent->s.modelindex; body->s.bodyOwner = ent->s.number; // bodyOwner is the same as modelindex2 body->s.skinnum = ent->s.skinnum; body->s.teleported = true; // launch the death animation on the body { static int i; i = ( i+1 )%3; G_AddEvent( body, EV_DIE, i, true ); switch( i ) { default: case 0: body->s.frame = ( ( BOTH_DEAD1&0x3F )|( BOTH_DEAD1&0x3F )<<6|( 0 &0xF )<<12 ); break; case 1: body->s.frame = ( ( BOTH_DEAD2&0x3F )|( BOTH_DEAD2&0x3F )<<6|( 0 &0xF )<<12 ); break; case 2: body->s.frame = ( ( BOTH_DEAD3&0x3F )|( BOTH_DEAD3&0x3F )<<6|( 0 &0xF )<<12 ); break; } } body->think = body_ready; body->takedamage = DAMAGE_NO; body->r.solid = SOLID_NOT; body->nextThink = level.time + 500; // make damageable in 0.5 seconds } else // wasn't a player, just copy it's model { VectorClear( body->velocity ); body->s.modelindex = ent->s.modelindex; body->s.frame = ent->s.frame; body->nextThink = level.time + 5000 + random()*10000; } GClip_LinkEntity( body ); return body; }
/* * G_SplashFrac */ void G_SplashFrac( const vec3_t origin, const vec3_t mins, const vec3_t maxs, const vec3_t point, float maxradius, vec3_t pushdir, float *kickFrac, float *dmgFrac ) { #define VERTICALBIAS 0.65f // 0...1 #define CAPSULEDISTANCE #define SPLASH_HDIST_CLAMP 53 vec3_t boxcenter = { 0, 0, 0 }; vec3_t hitpoint, vec; float distance; int i; float innerradius; float outerradius; float refdistance; if( maxradius <= 0 ) { if( kickFrac ) *kickFrac = 0; if( dmgFrac ) *dmgFrac = 0; if( pushdir ) VectorClear( pushdir ); return; } VectorCopy( point, hitpoint ); innerradius = ( maxs[0] + maxs[1] - mins[0] - mins[1] ) * 0.25; outerradius = ( sqrt( maxs[0]*maxs[0] + maxs[1]*maxs[1] ) + sqrt( mins[0]*mins[0] + mins[1]*mins[1] ) ) * 0.5; #ifdef CAPSULEDISTANCE // Find the distance to the closest point in the capsule contained in the player bbox // modify the origin so the inner sphere acts as a capsule VectorCopy( origin, boxcenter ); boxcenter[2] = hitpoint[2]; clamp( boxcenter[2], ( origin[2] + mins[2] ) + innerradius, ( origin[2] + maxs[2] ) - innerradius ); #else // find center of the box for( i = 0; i < 3; i++ ) boxcenter[i] = origin[i] + ( 0.5f * ( maxs[i] + mins[i] ) ); #endif // find push intensity distance = DistanceFast( boxcenter, hitpoint ); if( distance >= maxradius ) { if( kickFrac ) *kickFrac = 0; if( dmgFrac ) *dmgFrac = 0; if( pushdir ) VectorClear( pushdir ); return; } refdistance = innerradius; if( refdistance >= maxradius ) { if( kickFrac ) *kickFrac = 0; if( dmgFrac ) *dmgFrac = 0; if( pushdir ) VectorClear( pushdir ); return; } maxradius -= refdistance; distance -= refdistance; if( distance < 0 ) distance = 0; distance = maxradius - distance; clamp( distance, 0, maxradius ); if( dmgFrac ) { // soft sin curve *dmgFrac = sin( DEG2RAD( ( distance / maxradius ) * 80 ) ); clamp( *dmgFrac, 0.0f, 1.0f ); } if( kickFrac ) { // linear kick fraction float kick = ( distance / maxradius ); // half linear half exponential //*kickFrac = ( kick + ( kick * kick ) ) * 0.5f; // linear *kickFrac = kick; clamp( *kickFrac, 0.0f, 1.0f ); } //if( dmgFrac && kickFrac ) // G_Printf( "SPLASH: dmgFrac %.2f kickFrac %.2f\n", *dmgFrac, *kickFrac ); // find push direction if( pushdir ) { #ifdef CAPSULEDISTANCE // find real center of the box again for( i = 0; i < 3; i++ ) boxcenter[i] = origin[i] + ( 0.5f * ( maxs[i] + mins[i] ) ); #endif #ifdef VERTICALBIAS // move the center up for the push direction if( ( origin[2] + maxs[2] > boxcenter[2] ) && !GS_RaceGametype())// racesow don't do this in racesow boxcenter[2] += VERTICALBIAS * ( ( origin[2] + maxs[2] ) - boxcenter[2] ); #endif // VERTICALBIAS #ifdef SPLASH_HDIST_CLAMP // if pushed from below, hack the hitpoint to limit the side push direction if( hitpoint[2] < boxcenter[2] && SPLASH_HDIST_CLAMP > 0 ) { // do not allow the hitpoint to be further away // than SPLASH_HDIST_CLAMP in the horizontal axis vec[0] = hitpoint[0]; vec[1] = hitpoint[1]; vec[2] = boxcenter[2]; if( DistanceFast( boxcenter, vec ) > SPLASH_HDIST_CLAMP ) { VectorSubtract( vec, boxcenter, pushdir ); VectorNormalizeFast( pushdir ); VectorMA( boxcenter, SPLASH_HDIST_CLAMP, pushdir, hitpoint ); hitpoint[2] = point[2]; // restore the original hitpoint height } } #endif // SPLASH_HDIST_CLAMP VectorSubtract( boxcenter, hitpoint, pushdir ); VectorNormalizeFast( pushdir ); } #undef VERTICALBIAS #undef CAPSULEDISTANCE #undef SPLASH_HDIST_CLAMP }
/* * W_Fire_Instagun_Strong */ void W_Fire_Instagun( edict_t *self, vec3_t start, vec3_t angles, float damage, int knockback, int stun, int radius, int range, int mod, int timeDelta ) { vec3_t from, end, dir; trace_t tr; edict_t *ignore, *event; int mask; qboolean missed = qtrue; int dmgflags = 0; if( GS_Instagib() ) damage = 9999; AngleVectors( angles, dir, NULL, NULL ); VectorMA( start, range, dir, end ); VectorCopy( start, from ); ignore = self; mask = MASK_SHOT; if( GS_RaceGametype() ) mask = MASK_SOLID; tr.ent = -1; while( ignore ) { G_Trace4D( &tr, from, NULL, NULL, end, ignore, mask, timeDelta ); VectorCopy( tr.endpos, from ); ignore = NULL; if( tr.ent == -1 ) break; // some entity was touched if( tr.ent == world->s.number || game.edicts[tr.ent].movetype == MOVETYPE_NONE || game.edicts[tr.ent].movetype == MOVETYPE_PUSH ) { if( g_instajump->integer && self && self->r.client ) { // create a temporary inflictor entity edict_t *inflictor; inflictor = G_Spawn(); inflictor->s.solid = SOLID_NOT; inflictor->timeDelta = 0; VectorCopy( tr.endpos, inflictor->s.origin ); inflictor->s.ownerNum = ENTNUM( self ); inflictor->projectileInfo.maxDamage = 0; inflictor->projectileInfo.minDamage = 0; inflictor->projectileInfo.maxKnockback = knockback; inflictor->projectileInfo.minKnockback = 1; inflictor->projectileInfo.stun = 0; inflictor->projectileInfo.radius = radius; G_RadiusDamage( inflictor, self, &tr.plane, NULL, mod ); G_FreeEdict( inflictor ); } break; } // allow trail to go through SOLID_BBOX entities (players, gibs, etc) if( !ISBRUSHMODEL( game.edicts[tr.ent].s.modelindex ) ) ignore = &game.edicts[tr.ent]; if( ( &game.edicts[tr.ent] != self ) && ( game.edicts[tr.ent].takedamage ) ) { G_Damage( &game.edicts[tr.ent], self, self, dir, dir, tr.endpos, damage, knockback, stun, dmgflags, mod ); // spawn a impact event on each damaged ent event = G_SpawnEvent( EV_INSTA_EXPLOSION, DirToByte( tr.plane.normal ), tr.endpos ); event->s.firemode = FIRE_MODE_STRONG; if( game.edicts[tr.ent].r.client ) missed = qfalse; } } if( missed && self->r.client ) G_AwardPlayerMissedElectrobolt( self, mod ); // send the weapon fire effect event = G_SpawnEvent( EV_INSTATRAIL, ENTNUM( self ), start ); event->r.svflags = SVF_TRANSMITORIGIN2; VectorScale( dir, 1024, event->s.origin2 ); }
void W_Fire_Electrobolt_FullInstant( edict_t *self, vec3_t start, vec3_t angles, float maxdamage, float mindamage, int maxknockback, int minknockback, int stun, int range, int minDamageRange, int mod, int timeDelta ) { vec3_t from, end, dir; trace_t tr; edict_t *ignore, *event, *hit, *damaged; int mask; qboolean missed = qtrue; int dmgflags = 0; #define FULL_DAMAGE_RANGE g_projectile_prestep->value if( GS_Instagib() ) maxdamage = mindamage = 9999; AngleVectors( angles, dir, NULL, NULL ); VectorMA( start, range, dir, end ); VectorCopy( start, from ); ignore = self; hit = damaged = NULL; mask = MASK_SHOT; if( GS_RaceGametype() ) mask = MASK_SOLID; clamp_high( mindamage, maxdamage ); clamp_high( minknockback, maxknockback ); clamp_high( minDamageRange, range ); if( minDamageRange <= FULL_DAMAGE_RANGE ) minDamageRange = FULL_DAMAGE_RANGE + 1; if( range <= FULL_DAMAGE_RANGE + 1 ) range = FULL_DAMAGE_RANGE + 1; tr.ent = -1; while( ignore ) { G_Trace4D( &tr, from, NULL, NULL, end, ignore, mask, timeDelta ); VectorCopy( tr.endpos, from ); ignore = NULL; if( tr.ent == -1 ) break; // some entity was touched hit = &game.edicts[tr.ent]; if( hit == world ) // stop dead if hit the world break; if( hit->movetype == MOVETYPE_NONE || hit->movetype == MOVETYPE_PUSH ) break; // allow trail to go through BBOX entities (players, gibs, etc) if( !ISBRUSHMODEL( hit->s.modelindex ) ) ignore = hit; if( ( hit != self ) && ( hit->takedamage ) ) { float frac, damage, knockback, dist; dist = DistanceFast( tr.endpos, start ); if( dist <= FULL_DAMAGE_RANGE ) frac = 0.0f; else { frac = ( dist - FULL_DAMAGE_RANGE ) / (float)( minDamageRange - FULL_DAMAGE_RANGE ); clamp( frac, 0.0f, 1.0f ); } damage = maxdamage - ( ( maxdamage - mindamage ) * frac ); knockback = maxknockback - ( ( maxknockback - minknockback ) * frac ); //G_Printf( "mindamagerange %i frac %.1f damage %i\n", minDamageRange, 1.0f - frac, (int)damage ); G_Damage( hit, self, self, dir, dir, tr.endpos, damage, knockback, stun, dmgflags, mod ); // spawn a impact event on each damaged ent event = G_SpawnEvent( EV_BOLT_EXPLOSION, DirToByte( tr.plane.normal ), tr.endpos ); event->s.firemode = FIRE_MODE_STRONG; if( hit->r.client ) missed = qfalse; damaged = hit; } } if( missed && self->r.client ) G_AwardPlayerMissedElectrobolt( self, mod ); // send the weapon fire effect event = G_SpawnEvent( EV_ELECTROTRAIL, ENTNUM( self ), start ); event->r.svflags = SVF_TRANSMITORIGIN2; VectorScale( dir, 1024, event->s.origin2 ); event->s.firemode = FIRE_MODE_STRONG; #undef FULL_DAMAGE_RANGE }
/* * W_Fire_Electrobolt_Combined */ void W_Fire_Electrobolt_Combined( edict_t *self, vec3_t start, vec3_t angles, float maxdamage, float mindamage, float maxknockback, float minknockback, int stun, int range, int mod, int timeDelta ) { vec3_t from, end, dir; trace_t tr; edict_t *ignore, *event, *hit, *damaged; int mask; qboolean missed = qtrue; int dmgflags = 0; int fireMode; #ifdef ELECTROBOLT_TEST fireMode = FIRE_MODE_WEAK; #else fireMode = FIRE_MODE_STRONG; #endif if( GS_Instagib() ) maxdamage = mindamage = 9999; AngleVectors( angles, dir, NULL, NULL ); VectorMA( start, range, dir, end ); VectorCopy( start, from ); ignore = self; hit = damaged = NULL; mask = MASK_SHOT; if( GS_RaceGametype() ) mask = MASK_SOLID; clamp_high( mindamage, maxdamage ); clamp_high( minknockback, maxknockback ); tr.ent = -1; while( ignore ) { G_Trace4D( &tr, from, NULL, NULL, end, ignore, mask, timeDelta ); VectorCopy( tr.endpos, from ); ignore = NULL; if( tr.ent == -1 ) break; // some entity was touched hit = &game.edicts[tr.ent]; if( hit == world ) // stop dead if hit the world break; if( hit->movetype == MOVETYPE_NONE || hit->movetype == MOVETYPE_PUSH ) break; // allow trail to go through BBOX entities (players, gibs, etc) if( !ISBRUSHMODEL( hit->s.modelindex ) ) ignore = hit; if( ( hit != self ) && ( hit->takedamage ) ) { float frac, damage, knockback; frac = DistanceFast( tr.endpos, start ) / (float)range; clamp( frac, 0.0f, 1.0f ); damage = maxdamage - ( ( maxdamage - mindamage ) * frac ); knockback = maxknockback - ( ( maxknockback - minknockback ) * frac ); G_Damage( hit, self, self, dir, dir, tr.endpos, damage, knockback, stun, dmgflags, mod ); // spawn a impact event on each damaged ent event = G_SpawnEvent( EV_BOLT_EXPLOSION, DirToByte( tr.plane.normal ), tr.endpos ); event->s.firemode = fireMode; if( hit->r.client ) missed = qfalse; damaged = hit; } } if( missed && self->r.client ) G_AwardPlayerMissedElectrobolt( self, mod ); // send the weapon fire effect event = G_SpawnEvent( EV_ELECTROTRAIL, ENTNUM( self ), start ); event->r.svflags = SVF_TRANSMITORIGIN2; VectorScale( dir, 1024, event->s.origin2 ); event->s.firemode = fireMode; if( !GS_Instagib() && tr.ent == -1 ) // didn't touch anything, not even a wall { edict_t *bolt; gs_weapon_definition_t *weapondef = GS_GetWeaponDef( self->s.weapon ); // fire a weak EB from the end position bolt = W_Fire_Electrobolt_Weak( self, end, angles, weapondef->firedef_weak.speed, mindamage, minknockback, minknockback, stun, weapondef->firedef_weak.timeout, mod, timeDelta ); bolt->enemy = damaged; } }