/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // g_combat.c #include "g_local.h" #include "g_gametypes.h" /* ============ G_ModToAmmo Helper function for weapon stat system ============ */ int G_ModToAmmo( int mod ) { if( mod == MOD_GUNBLADE_W ) return AMMO_WEAK_GUNBLADE; else if( mod == MOD_GUNBLADE_S ) return AMMO_CELLS; /*else if( mod == MOD_SHOCKWAVE_W ) return AMMO_WEAK_WAVES; else if( mod == MOD_SHOCKWAVE_S ) return AMMO_WAVES;*/ else if( mod == MOD_RIOTGUN_W ) return AMMO_WEAK_SHELLS; else if( mod == MOD_RIOTGUN_S ) return AMMO_SHELLS; else if( mod == MOD_GRENADE_W || mod == MOD_GRENADE_SPLASH_W ) return AMMO_WEAK_GRENADES; else if( mod == MOD_GRENADE_S || mod == MOD_GRENADE_SPLASH_S ) return AMMO_GRENADES; else if( mod == MOD_ROCKET_W || mod == MOD_ROCKET_SPLASH_W ) return AMMO_WEAK_ROCKETS; else if( mod == MOD_ROCKET_S || mod == MOD_ROCKET_SPLASH_S ) return AMMO_ROCKETS; else if( mod == MOD_PLASMA_W || mod == MOD_PLASMA_SPLASH_W ) return AMMO_WEAK_PLASMA; else if( mod == MOD_PLASMA_S || mod == MOD_PLASMA_SPLASH_S ) return AMMO_PLASMA; else if( mod == MOD_ELECTROBOLT_W ) return AMMO_WEAK_BOLTS; else if( mod == MOD_ELECTROBOLT_S ) return AMMO_BOLTS; else if( mod == MOD_LASERGUN_W ) return AMMO_WEAK_LASERS; else if( mod == MOD_LASERGUN_S ) return AMMO_LASERS; else return AMMO_NONE; } //============ //CanDamage // //Returns true if the inflictor can directly damage the target. Used for //explosions and melee attacks. //============ qboolean CanDamage( edict_t *targ, edict_t *inflictor ) { vec3_t dest; trace_t trace; // bmodels need special checking because their origin is 0,0,0 if (targ->movetype == MOVETYPE_PUSH) { // NOT FOR PLAYERS only for entities that can push the players VectorAdd( targ->r.absmin, targ->r.absmax, dest ); VectorScale( dest, 0.5, dest ); trap_Trace( &trace, inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID ); if( trace.fraction == 1.0 ) return qtrue; if( &game.edicts[trace.ent] == targ ) return qtrue; return qfalse; } // This is for players trap_Trace( &trace, inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID ); if( trace.fraction == 1.0 ) return qtrue; VectorCopy( targ->s.origin, dest ); dest[0] += 15.0; dest[1] += 15.0; trap_Trace( &trace, inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID ); if( trace.fraction == 1.0 ) return qtrue; VectorCopy( targ->s.origin, dest ); dest[0] += 15.0; dest[1] -= 15.0; trap_Trace( &trace, inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID ); if( trace.fraction == 1.0 ) return qtrue; VectorCopy( targ->s.origin, dest ); dest[0] -= 15.0; dest[1] += 15.0; trap_Trace( &trace, inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID ); if (trace.fraction == 1.0) return qtrue; VectorCopy( targ->s.origin, dest ); dest[0] -= 15.0; dest[1] -= 15.0; trap_Trace( &trace, inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID ); if( trace.fraction == 1.0 ) return qtrue; return qfalse; } qboolean CanSplashDamage( edict_t *targ, edict_t *inflictor, cplane_t *plane ) { vec3_t dest, origin; trace_t trace; if( plane == NULL ) { VectorCopy( inflictor->s.origin, origin ); } else { VectorMA( inflictor->s.origin, 4, plane->normal, origin ); } // bmodels need special checking because their origin is 0,0,0 if (targ->movetype == MOVETYPE_PUSH) { // NOT FOR PLAYERS only for entities that can push the players VectorAdd( targ->r.absmin, targ->r.absmax, dest ); VectorScale( dest, 0.5, dest ); trap_Trace( &trace, origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID ); if( trace.fraction == 1.0 ) return qtrue; if( &game.edicts[trace.ent] == targ ) return qtrue; return qfalse; } // This is for players trap_Trace( &trace, origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID ); if( trace.fraction == 1.0 ) return qtrue; VectorCopy( targ->s.origin, dest ); dest[0] += 15.0; dest[1] += 15.0; trap_Trace( &trace, origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID ); if( trace.fraction == 1.0 ) return qtrue; VectorCopy( targ->s.origin, dest ); dest[0] += 15.0; dest[1] -= 15.0; trap_Trace( &trace, origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID ); if( trace.fraction == 1.0 ) return qtrue; VectorCopy( targ->s.origin, dest ); dest[0] -= 15.0; dest[1] += 15.0; trap_Trace( &trace, origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID ); if (trace.fraction == 1.0) return qtrue; VectorCopy( targ->s.origin, dest ); dest[0] -= 15.0; dest[1] -= 15.0; trap_Trace( &trace, origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID ); if( trace.fraction == 1.0 ) return qtrue; return qfalse; } //============ //Killed //============ void Killed( edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point, int mod ) { if (targ->health < -999) targ->health = -999; targ->enemy = attacker; //newgametypes[start] if( G_Gametype_Killed(targ, inflictor, attacker, damage, point, mod) ) return; //newgametypes[end] if( (targ->r.svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD) ) { // targ->r.svflags |= SVF_CORPSE; // now treat as a different content type } if( targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE ) { // doors, triggers, etc targ->die( targ, inflictor, attacker, damage, point ); return; } targ->die( targ, inflictor, attacker, damage, point ); } static float CheckArmor( edict_t *ent, int damage, int dflags ) { gclient_t *client; gitem_t *armor; float save; if( !damage ) return 0; client = ent->r.client; if( !client ) return 0; if( dflags & DAMAGE_NO_ARMOR ) return 0; if( client->armortag == ARMOR_NONE ) return 0; armor = game.items[client->armortag]; #ifdef UNBALANCED_ARMORS save = min(damage, client->armor) * ((gitem_armor_t *)armor->info)->protection; client->armor -= min(ARMOR_DEGRADATION * (float)damage, client->armor); #else save = ((gitem_armor_t *)armor->info)->protection * (float)damage; if( save > client->armor ) save = client->armor; if( !save ) return 0; client->armor -= save; #endif if( client->armor == 0 ) client->armortag = ARMOR_NONE; #ifdef ARMOR_SYSTEM_OF_THE_DAY // if using the constant protection/degradation settings, convert the armortag // according to armor count, so the more representative color is shown at HUD else { gitem_armor_t *armorinfo; client->armortag = ARMOR_GA; armorinfo = (gitem_armor_t *)game.items[ARMOR_YA]->info; if( client->armor > armorinfo->base_count ) client->armortag = ARMOR_YA; if( client->armor > armorinfo->max_count ) client->armortag = ARMOR_RA; } #endif return save; } //================ //G_IsTeamDamage - moveme to g_gameteams? //================ qboolean G_IsTeamDamage( edict_t *targ, edict_t *attacker ) { if( !GS_Gametype_IsTeamBased( game.gametype ) ) return qfalse; if( targ->s.team && attacker->s.team && targ->s.team == attacker->s.team && targ != attacker ) return qtrue; return qfalse; } //================ //G_BlendFrameDamage //================ void G_BlendFrameDamage( edict_t *ent, float damage, float *old_damage, vec3_t point, vec3_t dir, vec3_t old_point, vec3_t old_dir ) { vec3_t offset; float frac; int i; if( !dir ) { } if( !point ) VectorSet( offset, 0, 0, ent->viewheight ); else VectorSubtract( point, ent->s.origin, offset ); VectorNormalize( dir ); if( *old_damage == 0 ) { VectorCopy( offset, old_point ); VectorCopy( dir, old_dir ); *old_damage = damage; return; } frac = damage / (damage + *old_damage); for( i = 0; i < 3; i++ ) { old_point[i] = (old_point[i] * (1.0f - frac)) + offset[i] * frac; old_dir[i] = (old_dir[i] * (1.0f - frac)) + dir[i] * frac; } *old_damage += damage; } void T_KnockBackPush( edict_t *targ, vec3_t dir, int knockback ) { float mass = 50.0; float push; if( targ->flags & FL_NO_KNOCKBACK ) knockback = 0; if( knockback <= 0 ) return; if( (targ->movetype == MOVETYPE_NONE) || (targ->movetype == MOVETYPE_PUSH) || (targ->movetype == MOVETYPE_STOP) || (targ->movetype == MOVETYPE_BOUNCE) ) return; if( targ->r.client ) { targ->r.client->ps.pmove.stats[PM_STAT_KNOCKBACK] = 2 * knockback; //G_Printf( "KNOCK TIME:%i\n", targ->r.client->ps.pmove.stats[PM_STAT_KNOCKBACK] ); clamp( targ->r.client->ps.pmove.stats[PM_STAT_KNOCKBACK], 50, 200 ); } if( targ->mass > 50 ) mass = targ->mass; push = 1000.0f * ((float)knockback / mass); VectorNormalizeFast( dir ); VectorMA( targ->velocity, push, dir, targ->velocity ); // wsw: pb midair knock back hack // vel=vel+push*dir; // we want to scale Z part by a factor if( game.gametype == GAMETYPE_MIDAIR ) targ->velocity[2]+=1.75f*push*dir[2]; // add 175% } //============ //T_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=monster, 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 // DAMAGE_RADIUS damage was indirect (from a nearby explosion) // DAMAGE_NO_ARMOR armor does not protect from this damage // DAMAGE_ENERGY damage is from an energy based weapon // DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles // DAMAGE_BULLET damage is from a bullet (used for ricochets) // DAMAGE_NO_PROTECTION kills godmode, armor, everything //============ void T_Damage( edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, float damage, float knockback, int dflags, int mod ) { gclient_t *client; float take; float save; float asave; if( !targ->takedamage ) return; meansOfDeath = mod; client = targ->r.client; // push if( !(dflags & DAMAGE_NO_KNOCKBACK) ) { // dont apply pushback in those case // race: only allow rockets jump and damage by world if( (game.gametype == GAMETYPE_RACE && (attacker!=targ && attacker!=world)) ) return; T_KnockBackPush( targ, dir, knockback ); } // don't apply damage in this cases... // race: only accept damage by world (for void/kill) if( (game.gametype == GAMETYPE_RACE && attacker!=world) || (game.gametype == GAMETYPE_MIDAIR && (dflags & DAMAGE_RADIUS) ) ) return; // pb: midair hack // world is there for lava if( game.gametype == GAMETYPE_MIDAIR && inflictor != world) { trace_t trace; float height; trap_Trace( &trace, targ->s.origin, targ->r.mins, targ->r.maxs, tv(targ->s.origin[0], targ->s.origin[1], targ->s.origin[2] - 16000), targ, MASK_PLAYERSOLID ); if( trace.fraction == 1 || trace.allsolid ) return; // nothing touched height=targ->s.origin[2] - trace.endpos[2]; //G_Printf("Height: %f\n",height); if( height<32 // only damage if the target is at least 32 units from the ground /*targ->groundentity != NULL // only apply damage if not on ground */ ) return; } // pb: end of midair hack take = damage; save = 0; // check for godmode if( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) ) { take = 0; save = damage; } asave = CheckArmor( targ, take, dflags); take -= asave; //treat cheat/powerup savings the same as armor asave += save; // team damage avoidance if( G_IsTeamDamage(targ, attacker) && !G_Gametype_CanTeamDamage(dflags) ) return; // add damage done to stats if( !G_IsTeamDamage(targ, attacker) && attacker != targ && G_ModToAmmo(mod) != AMMO_NONE && targ->r.client ) { attacker->r.client->resp.accuracy_hits[G_ModToAmmo(mod)-AMMO_CELLS]++; attacker->r.client->resp.accuracy_damage[G_ModToAmmo(mod)-AMMO_CELLS] += damage; } //newgametypes G_Gametype_CTF_CheckHurtCarrier( targ, attacker ); // do the damage if( take > 0 ) { //G_SIMPLE_DAMAGE_FEEDBACK [start] if( targ->movetype != MOVETYPE_PUSH ) { // doors don't bleed vec3_t dorigin, ddir; if( attacker ) VectorSubtract( targ->s.origin, attacker->s.origin, ddir ); else if( inflictor ) VectorSubtract( targ->s.origin, inflictor->s.origin, ddir ); else VectorCopy( normal, ddir ); 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->frame_damage_taken, dorigin, ddir, targ->frame_damage_at, targ->frame_damage_dir ); G_BlendFrameDamage( targ, save, &targ->frame_damage_saved, dorigin, ddir, targ->frame_damage_at, targ->frame_damage_dir ); } //[end] of simple damage feedback targ->health = targ->health - take; // wsw : jal : accumulate taken damage for color blends if( targ->r.client ) { targ->r.client->damage_taken += take; targ->r.client->damage_saved += save; } // wsw : jal : accumulate given damage for hit sounds if( (take) && targ != attacker && targ->r.client && !targ->deadflag ) { if( attacker && attacker->r.client ) { if( G_IsTeamDamage(targ, attacker) ) attacker->r.client->damageteam_given += take; else attacker->r.client->damage_given += take; } } if( G_IsDead(targ) ) { if( (targ->r.svflags & SVF_MONSTER) || (client) ) targ->flags |= FL_NO_KNOCKBACK; Killed( targ, inflictor, attacker, HEALTH_TO_INT(take), point, mod ); return; } } if( client ) { if( !(targ->flags & FL_GODMODE) && (take) ) targ->pain( targ, attacker, knockback, take ); } else if( take ) { if( targ->pain ) targ->pain( targ, attacker, knockback, take ); } } float G_KnockbackPushFrac( vec3_t pushorigin, vec3_t origin, vec3_t mins, vec3_t maxs, vec3_t pushdir, float pushradius ) { vec3_t boxcenter = { 0,0,0 }; float distance; int i; #define BOXRADIUS #ifdef BOXRADIUS float boxradius = 0.0f; if(1) { // inner radius boxradius = (maxs[0] + maxs[1] - mins[0] - mins[1]) * 0.25; } else { // outer radius boxradius = (sqrt( maxs[0]*maxs[0] + maxs[1]*maxs[1] ) + sqrt( mins[0]*mins[0] + mins[1]*mins[1] )) * 0.5; } // find center of the box for( i = 0; i < 3; i++ ) boxcenter[i] = origin[i] + maxs[i] + mins[i]; // find box radius to explosion origin direction VectorSubtract( boxcenter, pushorigin, pushdir ); distance = VectorNormalize( pushdir ); if( distance - boxradius > pushradius ) return 0; return 1.0 - ((distance - boxradius) / pushradius); #else // find center of the box for( i = 0; i < 3; i++ ) boxcenter[i] = origin[i] + maxs[i] + mins[i]; // find box radius to explosion origin direction VectorSubtract( boxcenter, pushorigin, pushdir ); distance = VectorNormalize( pushdir ); if( distance > pushradius ) return 0; return 1.0 - (distance / pushradius); #endif } /* ============ T_RadiusDamage ============ */ void T_RadiusDamage( edict_t *inflictor, edict_t *attacker, cplane_t *plane, float maxdamage, float maxknockback, float mindamage, edict_t *ignore, float radius, int mod ) { float points; edict_t *ent = NULL; vec3_t dir; float knockback; float pushFrac; if( radius <= 0 ) return; while( (ent = G_FindBoxInRadius(ent, inflictor->s.origin, radius)) != NULL ) { if( ent == ignore ) continue; if( !ent->takedamage ) continue; pushFrac = G_KnockbackPushFrac( inflictor->s.origin, ent->s.origin, ent->r.mins, ent->r.maxs, dir, radius ); knockback = (float)maxknockback * pushFrac; points = maxdamage * pushFrac; //G_Printf( "DAMAGE:%f MINDAMAGE:%f KNOCKBACK:%f\n", points, mindamage, knockback ); if( points < mindamage ) points = mindamage; if( points > 0 ) { if( CanSplashDamage(ent, inflictor, plane) ) { if( ent == attacker && ent->r.client ) { // ROCKET JUMP HACK!!! firedef_t *firedef = NULL; // when doing weapon jumps, we always get the push from the strong fire definitions and ignore quad if( inflictor->s.type == ET_ROCKET ) firedef = g_weaponInfos[WEAP_ROCKETLAUNCHER].firedef; else if( inflictor->s.type == ET_GRENADE ) firedef = g_weaponInfos[WEAP_GRENADELAUNCHER].firedef; else if( inflictor->s.type == ET_PLASMA ) firedef = g_weaponInfos[WEAP_PLASMAGUN].firedef; if( firedef ) { pushFrac = G_KnockbackPushFrac( inflictor->s.origin, ent->s.origin, ent->r.mins, ent->r.maxs, dir, firedef->splash_radius ); knockback = (float)firedef->knockback * pushFrac * 1.35; } points = points * 0.5; // half the damage on self } T_Damage( ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, points, knockback, DAMAGE_RADIUS, mod ); } } } }