openmohaa/code/fgame/weaputils.cpp
2023-11-14 17:55:08 +01:00

3146 lines
85 KiB
C++

/*
===========================================================================
Copyright (C) 2015 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code 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.
OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// weaputils.cpp: General Weapon Utility Functions
#include "g_phys.h"
#include "g_spawn.h"
#include "weaputils.h"
#include "specialfx.h"
#include "sentient.h"
#include "actor.h"
#include "decals.h"
#include "weapon.h"
#include "player.h"
#include "VehicleCollisionEntity.h"
#include "weapturret.h"
#include "vehicleturret.h"
#include "earthquake.h"
#include "trigger.h"
#include "debuglines.h"
#include "smokegrenade.h"
constexpr unsigned long MAX_TRAVEL_DIST = 16216;
static void FlashPlayers(Vector org, float r, float g, float b, float a, float rad, float time, int type);
qboolean MeleeAttack(
Vector pos,
Vector end,
float damage,
Entity *attacker,
meansOfDeath_t means_of_death,
float attack_width,
float attack_min_height,
float attack_max_height,
float knockback,
qboolean hit_dead,
Container<Entity *> *victimlist
)
{
trace_t trace;
Entity *victim;
Vector dir;
float world_dist;
Vector new_pos;
Entity *skip_ent;
qboolean hit_something = false;
Vector mins;
Vector maxs;
Container<Entity *> potential_victimlist;
int i;
int num_traces;
Vector start;
// See how far the world is away
dir = end - pos;
world_dist = dir.length();
new_pos = pos;
skip_ent = attacker;
num_traces = 0;
while (new_pos != end) {
trace = G_Trace(pos, vec_zero, vec_zero, end, skip_ent, MASK_SOLID, false, "MeleeAttack - World test");
num_traces++;
if (trace.fraction < 1) {
if ((trace.entityNum == ENTITYNUM_WORLD)
|| (trace.ent && trace.ent->entity && !trace.ent->entity->takedamage)) {
dir = trace.endpos - pos;
world_dist = dir.length();
break;
} else {
// Make sure we don't go backwards any in our trace
if (Vector(new_pos - pos).length() + 0.001 >= Vector(trace.endpos - pos).length()) {
break;
}
if (num_traces > 10) {
// We have done too many traces, stop here
dir = trace.endpos - pos;
world_dist = dir.length();
break;
}
new_pos = trace.endpos;
if (trace.ent) {
skip_ent = trace.ent->entity;
}
}
} else {
break;
}
}
// Find things hit
dir = end - pos;
dir.normalize();
end = pos + (dir * world_dist);
start = pos - dir * (attack_width * 1.2f);
start.z = pos.z - dir.z * (end.z - start.z);
victim = NULL;
mins = Vector(-attack_width, -attack_width, attack_min_height);
maxs = Vector(attack_width, attack_width, attack_max_height);
G_TraceEntities(pos, mins, maxs, end, &potential_victimlist, MASK_MELEE);
/*int previous_contents = attacker->edict->r.contents;
attacker->edict->r.contents = 0;
trace = G_Trace( pos, mins, maxs, end, ( ( Sentient * )attacker )->GetActiveWeapon( WEAPON_MAIN ), MASK_MELEE, false, "MeleeAttack" );
if( trace.ent && trace.ent->entity )
{
gi.Printf( "HIT\n" );
potential_victimlist.AddObject( trace.ent->entity );
}
attacker->edict->r.contents = previous_contents;*/
for (i = 1; i <= potential_victimlist.NumObjects(); i++) {
victim = potential_victimlist.ObjectAt(i);
if (victim && victim->takedamage && victim != attacker) {
dir = end - pos;
dir.normalize();
if (dir == vec_zero) {
dir = victim->centroid - pos;
dir.normalize();
}
if (victim->IsSubclassOfSentient() && !victim->IsDead()) {
hit_something = true;
}
if (victim->health > 0 || hit_dead) {
if (victimlist && victim->IsSubclassOfSentient() && !victim->IsDead()) {
victimlist->AddObject(victim);
}
victim->Damage(attacker, attacker, damage, pos, dir, vec_zero, knockback, 0, means_of_death);
if (victim->edict->r.contents & CONTENTS_SOLID) {
victim->Sound("pistol_hit");
}
}
}
}
return hit_something;
}
#define DEFAULT_SWORD_DAMAGE 10
#define DEFAULT_SWORD_KNOCKBACK 50
Event EV_Projectile_Speed
(
"speed",
EV_DEFAULT,
"f",
"projectileSpeed",
"set the speed of the projectile",
EV_NORMAL
);
Event EV_Projectile_MinSpeed
(
"minspeed",
EV_DEFAULT,
"f",
"minspeed",
"set the minimum speed of the projectile (this is for charge up weapons)",
EV_NORMAL
);
Event EV_Projectile_ChargeSpeed
(
"chargespeed",
EV_DEFAULT,
NULL,
NULL,
"set the projectile's speed to be determined by the charge time",
EV_NORMAL
);
Event EV_Projectile_Damage
(
"hitdamage",
EV_DEFAULT,
"f",
"projectileHitDamage",
"set the damage a projectile does when it hits something",
EV_NORMAL
);
Event EV_Projectile_Life
(
"life",
EV_DEFAULT,
"f",
"projectileLife",
"set the life of the projectile",
EV_NORMAL
);
Event EV_Projectile_DMLife
(
"dmlife",
EV_DEFAULT,
"f",
"projectileLife",
"set the life of the projectile in DM",
EV_NORMAL
);
Event EV_Projectile_MinLife
(
"minlife",
EV_DEFAULT,
"f",
"minProjectileLife",
"set the minimum life of the projectile (this is for charge up weapons)",
EV_NORMAL
);
Event EV_Projectile_ChargeLife
(
"chargelife",
EV_DEFAULT,
NULL,
NULL,
"set the projectile's life to be determined by the charge time",
EV_NORMAL
);
// Added in 2.0
Event EV_Projectile_SetFuse
(
"fuse",
EV_DEFAULT,
"i",
"fuse",
"set the projectile's life to be determined by the charge time",
EV_NORMAL
);
Event EV_Projectile_Knockback
(
"knockback",
EV_DEFAULT,
"f",
"projectileKnockback",
"set the knockback of the projectile when it hits something",
EV_NORMAL
);
Event EV_Projectile_DLight
(
"dlight",
EV_DEFAULT,
"ffff",
"red green blue intensity",
"set the color and intensity of the dynamic light on the projectile",
EV_NORMAL
);
Event EV_Projectile_Avelocity
(
"avelocity",
EV_DEFAULT,
"SFSFSF",
"[random|crandom] yaw [random|crandom] pitch [random|crandom] roll",
"set the angular velocity of the projectile",
EV_NORMAL
);
Event EV_Projectile_MeansOfDeath
(
"meansofdeath",
EV_DEFAULT,
"s",
"meansOfDeath",
"set the meansOfDeath of the projectile",
EV_NORMAL
);
Event EV_Projectile_BeamCommand
(
"beam",
EV_DEFAULT,
"sSSSSSS",
"command arg1 arg2 arg3 arg4 arg5 arg6",
"send a command to the beam of this projectile",
EV_NORMAL
);
Event EV_Projectile_UpdateBeam
(
"updatebeam",
EV_DEFAULT,
NULL,
NULL,
"Update the attached beam",
EV_NORMAL
);
Event EV_Projectile_BounceTouch
(
"bouncetouch",
EV_DEFAULT,
NULL,
NULL,
"Make the projectile bounce when it hits a non-damageable solid",
EV_NORMAL
);
Event EV_Projectile_BounceSound
(
"bouncesound",
EV_DEFAULT,
NULL,
NULL,
"Set the name of the sound that is played when the projectile bounces",
EV_NORMAL
);
Event EV_Projectile_BounceSound_Metal
(
"bouncesound_metal",
EV_DEFAULT,
NULL,
NULL,
"Set the name of the sound that is played when the projectile bounces off metal",
EV_NORMAL
);
Event EV_Projectile_BounceSound_Hard
(
"bouncesound_hard",
EV_DEFAULT,
NULL,
NULL,
"Set the name of the sound that is played when the projectile bounces off hard surfaces",
EV_NORMAL
);
Event EV_Projectile_BounceSound_Water
(
"bouncesound_water",
EV_DEFAULT,
NULL,
NULL,
"Set the name of the sound that is played when the projectile bounces off water",
EV_NORMAL
);
Event EV_Projectile_Explode
(
"explode",
EV_DEFAULT,
NULL,
NULL,
"Make the projectile explode",
EV_NORMAL
);
Event EV_Projectile_ImpactMarkShader
(
"impactmarkshader",
EV_DEFAULT,
"s",
"shader",
"Set the impact mark of the shader",
EV_NORMAL
);
Event EV_Projectile_ImpactMarkRadius
(
"impactmarkradius",
EV_DEFAULT,
"f",
"radius",
"Set the radius of the impact mark",
EV_NORMAL
);
Event EV_Projectile_ImpactMarkOrientation
(
"impactmarkorientation",
EV_DEFAULT,
"f",
"degrees",
"Set the orientation of the impact mark",
EV_NORMAL
);
Event EV_Projectile_SetExplosionModel
(
"explosionmodel",
EV_DEFAULT,
"s",
"modelname",
"Set the modelname of the explosion to be spawned",
EV_NORMAL
);
Event EV_Projectile_SetAddVelocity
(
"addvelocity",
EV_DEFAULT,
"fff",
"velocity_x velocity_y velocity_z",
"Set a velocity to be added to the projectile when it is created",
EV_NORMAL
);
Event EV_Projectile_AddOwnerVelocity
(
"addownervelocity",
EV_DEFAULT,
"b",
"bool",
"Set whether or not the owner's velocity is added to the projectile's velocity",
EV_NORMAL
);
Event EV_Projectile_HeatSeek
(
"heatseek",
EV_DEFAULT,
NULL,
NULL,
"Make the projectile heat seek",
EV_NORMAL
);
Event EV_Projectile_Drunk
(
"drunk",
EV_DEFAULT,
"ff",
"amount rate",
"Make the projectile drunk",
EV_NORMAL
);
Event EV_Projectile_Prethink
(
"prethink",
EV_DEFAULT,
NULL,
NULL,
"Make the projectile think to update it's velocity",
EV_NORMAL
);
Event EV_Projectile_SetCanHitOwner
(
"canhitowner",
EV_DEFAULT,
NULL,
NULL,
"Make the projectile be able to hit its owner",
EV_NORMAL
);
Event EV_Projectile_ClearOwner
(
"clearowner",
EV_DEFAULT,
NULL,
NULL,
"Make the projectile be able to hit its owner now",
EV_NORMAL
);
Event EV_Projectile_RemoveWhenStopped
(
"removewhenstopped",
EV_DEFAULT,
NULL,
NULL,
"Make the projectile get removed when it stops",
EV_NORMAL
);
Event EV_Projectile_ExplodeOnTouch
(
"explodeontouch",
EV_DEFAULT,
NULL,
NULL,
"Make the projectile explode when it touches something damagable",
EV_NORMAL
);
Event EV_Projectile_NoTouchDamage
(
"notouchdamage",
EV_DEFAULT,
NULL,
NULL,
"Makes the projectile not blow up or deal damage when it touches a damagable object",
EV_NORMAL
);
Event EV_Projectile_SetSmashThroughGlass
(
"smashthroughglass",
EV_DEFAULT,
"i",
"speed",
"Makes the projectile smash through windows & other damageble glass objects when going above a set speed",
EV_NORMAL
);
Event EV_Projectile_SmashThroughGlass
(
"_smashthroughglass",
EV_DEFAULT,
NULL,
NULL,
"Think function for smashing through glass",
EV_NORMAL
);
// Added in 2.0
Event EV_Projectile_ArcToTarget
(
"arctotarget",
EV_DEFAULT,
NULL,
NULL,
"Make the projectile follow a normal arc on its way to its target",
EV_NORMAL
);
// Added in 2.0
Event EV_Projectile_BecomeBomb
(
"becomebomb",
EV_DEFAULT,
NULL,
NULL,
"Make the projectile into a bomb",
EV_NORMAL
);
// Added in 2.30
Event EV_Projectile_DieInWater
(
"dieinwater",
EV_DEFAULT,
NULL,
NULL,
"Make the projectile die when gets wet",
EV_NORMAL
);
CLASS_DECLARATION(Animate, Projectile, NULL) {
{&EV_Touch, &Projectile::Touch },
{&EV_Projectile_Speed, &Projectile::SetSpeed },
{&EV_Projectile_MinSpeed, &Projectile::SetMinSpeed },
{&EV_Projectile_ChargeSpeed, &Projectile::SetChargeSpeed },
{&EV_Projectile_Damage, &Projectile::SetDamage },
{&EV_Projectile_Life, &Projectile::SetLife },
{&EV_Projectile_DMLife, &Projectile::SetDMLife },
{&EV_Projectile_MinLife, &Projectile::SetMinLife },
{&EV_Projectile_ChargeLife, &Projectile::SetChargeLife },
{&EV_Projectile_SetFuse, &Projectile::SetFuse },
{&EV_Projectile_Knockback, &Projectile::SetKnockback },
{&EV_Projectile_DLight, &Projectile::SetDLight },
{&EV_Projectile_Avelocity, &Projectile::SetAvelocity },
{&EV_Projectile_MeansOfDeath, &Projectile::SetMeansOfDeath },
{&EV_Projectile_BounceTouch, &Projectile::SetBounceTouch },
{&EV_Projectile_BounceSound, &Projectile::SetBounceSound },
{&EV_Projectile_BounceSound_Metal, &Projectile::SetBounceSoundMetal },
{&EV_Projectile_BounceSound_Hard, &Projectile::SetBounceSoundHard },
{&EV_Projectile_BounceSound_Water, &Projectile::SetBounceSoundWater },
{&EV_Projectile_BeamCommand, &Projectile::BeamCommand },
{&EV_Projectile_UpdateBeam, &Projectile::UpdateBeam },
{&EV_Projectile_Explode, &Projectile::Explode },
{&EV_Projectile_ImpactMarkShader, &Projectile::SetImpactMarkShader },
{&EV_Projectile_ImpactMarkRadius, &Projectile::SetImpactMarkRadius },
{&EV_Projectile_ImpactMarkOrientation, &Projectile::SetImpactMarkOrientation},
{&EV_Projectile_SetExplosionModel, &Projectile::SetExplosionModel },
{&EV_Projectile_SetAddVelocity, &Projectile::SetAddVelocity },
{&EV_Projectile_AddOwnerVelocity, &Projectile::AddOwnerVelocity },
{&EV_Projectile_HeatSeek, &Projectile::HeatSeek },
{&EV_Projectile_Drunk, &Projectile::Drunk },
{&EV_Projectile_Prethink, &Projectile::Prethink },
{&EV_Projectile_SetCanHitOwner, &Projectile::SetCanHitOwner },
{&EV_Projectile_ClearOwner, &Projectile::ClearOwner },
{&EV_Projectile_RemoveWhenStopped, &Projectile::RemoveWhenStopped },
{&EV_Projectile_ExplodeOnTouch, &Projectile::ExplodeOnTouch },
{&EV_Projectile_NoTouchDamage, &Projectile::SetNoTouchDamage },
{&EV_Projectile_SetSmashThroughGlass, &Projectile::SetSmashThroughGlass },
{&EV_Projectile_SmashThroughGlass, &Projectile::SmashThroughGlassThink },
{&EV_Projectile_BecomeBomb, &Projectile::BecomeBomb },
{&EV_Killed, &Projectile::Explode },
{&EV_Stop, &Projectile::Stopped },
{&EV_Projectile_ArcToTarget, &Projectile::ArcToTarget },
{&EV_Projectile_DieInWater, &Projectile::DieInWater },
{NULL, NULL }
};
Projectile::Projectile()
{
entflags |= EF_PROJECTILE;
if (LoadingSavegame) {
// Archive function will setup all necessary data
return;
}
m_beam = NULL;
speed = 0;
minspeed = 0;
damage = 0;
life = 5;
dmlife = 0;
knockback = 0;
dlight_radius = 0;
dlight_color = Vector(1, 1, 1);
avelocity = Vector(0, 0, 0);
mins = Vector(-1, -1, -1);
maxs = Vector(1, 1, 1);
meansofdeath = MOD_NONE;
projFlags = 0;
fLastBounceTime = 0;
gravity = 0;
impactmarkradius = 10;
charge_fraction = 1.0;
target = NULL;
fDrunk = 0;
fDrunkRate = 0;
m_iSmashThroughGlass = 0;
addownervelocity = qtrue;
can_hit_owner = false;
remove_when_stopped = false;
m_bExplodeOnTouch = false;
m_bHurtOwnerOnly = false;
takedamage = DAMAGE_NO;
owner = ENTITYNUM_NONE;
edict->r.ownerNum = ENTITYNUM_NONE;
m_bArcToTarget = false;
m_bDieInWater = false;
// make this shootable but non-solid on the client
setContents(CONTENTS_SHOOTONLY);
//
// touch triggers by default
//
flags |= FL_TOUCH_TRIGGERS;
m_iTeam = 0;
m_bHadPlayerOwner = false;
}
float Projectile::ResolveMinimumDistance(Entity *potential_target, float currmin)
{
float currdist;
float dot;
Vector angle;
Vector delta;
Vector norm;
float sine = 0.4f;
delta = potential_target->centroid - this->origin;
norm = delta;
norm.normalize();
// Test if the target is in front of the missile
dot = norm * orientation[0];
if (dot < 0) {
return currmin;
}
// Test if we're within the rocket's viewcone (45 degree cone)
dot = norm * orientation[1];
if (fabs(dot) > sine) {
return currmin;
}
dot = norm * orientation[2];
if (fabs(dot) > sine) {
return currmin;
}
currdist = delta.length();
if (currdist < currmin) {
currmin = currdist;
target = potential_target;
}
return currmin;
}
float Projectile::AdjustAngle(float maxadjust, float currangle, float targetangle)
{
float dangle;
float magangle;
dangle = currangle - targetangle;
if (dangle) {
magangle = (float)fabs(dangle);
while (magangle >= 360.0f) {
magangle -= 360.0f;
}
if (magangle < maxadjust) {
currangle = targetangle;
} else {
if (magangle > 180.0f) {
maxadjust = -maxadjust;
}
if (dangle > 0) {
maxadjust = -maxadjust;
}
currangle += maxadjust;
}
}
while (currangle >= 360.0f) {
currangle -= 360.0f;
}
while (currangle < 0.0f) {
currangle += 360.0f;
}
return currangle;
}
void Projectile::Drunk(Event *ev)
{
if (fDrunk) {
return;
}
fDrunk = ev->GetFloat(1);
fDrunkRate = ev->GetFloat(2);
PostEvent(EV_Projectile_Prethink, 0);
}
void Projectile::HeatSeek(Event *ev)
{
float mindist;
Entity *ent;
trace_t trace;
Vector delta;
Vector v;
int n;
int i;
if ((!target) || (target == world)) {
mindist = 8192.0f;
n = SentientList.NumObjects();
for (i = 1; i <= n; i++) {
ent = SentientList.ObjectAt(i);
if (ent->entnum == owner) {
continue;
}
if (((ent->takedamage != DAMAGE_AIM) || (ent->health <= 0))) {
continue;
}
trace = G_Trace(
this->origin, vec_zero, vec_zero, ent->centroid, this, MASK_SHOT, qfalse, "DrunkMissile::HeatSeek"
);
if ((trace.fraction != 1.0) && (trace.ent != ent->edict)) {
continue;
}
mindist = ResolveMinimumDistance(ent, mindist);
}
} else {
float angspeed;
delta = target->centroid - this->origin;
v = delta.toAngles();
angspeed = 5.0f;
angles.x = AdjustAngle(angspeed, angles.x, v.x);
angles.y = AdjustAngle(angspeed, angles.y, v.y);
angles.z = AdjustAngle(angspeed, angles.z, v.z);
}
PostEvent(EV_Projectile_HeatSeek, 0.1f);
PostEvent(EV_Projectile_Prethink, 0);
}
void Projectile::Prethink(Event *ev)
{
Vector end;
if (fDrunk >= 0.0f) {
if (fDrunk) {
float rnd1 = G_Random();
float rnd2 = G_Random();
angles += Vector((rnd1 + rnd1 - 1.0f) * fDrunk, (rnd2 + rnd2 - 1.0f) * fDrunk, 0);
speed *= 0.98f;
fDrunk += fDrunkRate;
if (speed < 500.0f) {
fDrunk = -fDrunk;
}
}
setAngles(angles);
velocity = Vector(orientation[0]) * speed;
} else {
fDrunk *= 0.80f;
if (fDrunk > -1.0f) {
fDrunk = -1.0f;
}
//float rnd1 = rand() & 0x7FFF;
//float rnd2 = rand() & 0x7FFF;
//angles += Vector( ( rnd1 * 0.00003f + rnd1 * 0.00003f - 1.0f ) * fDrunk, ( rnd2 * 0.00003f + rnd2 * 0.00003f - 1.0f ) * fDrunk, 0 );
velocity[2] -= 0.05f * sv_gravity->integer * 0.15f;
angles = velocity.toAngles();
setAngles(angles);
}
PostEvent(EV_Projectile_Prethink, 0.05f);
}
void Projectile::AddOwnerVelocity(Event *ev)
{
addownervelocity = ev->GetBoolean(1);
}
void Projectile::SetAddVelocity(Event *ev)
{
addvelocity.x = ev->GetFloat(1);
addvelocity.y = ev->GetFloat(2);
addvelocity.z = ev->GetFloat(3);
}
void Projectile::SetExplosionModel(Event *ev)
{
explosionmodel = ev->GetString(1);
}
void Projectile::SetImpactMarkShader(Event *ev)
{
impactmarkshader = ev->GetString(1);
}
void Projectile::SetImpactMarkRadius(Event *ev)
{
impactmarkradius = ev->GetFloat(1);
}
void Projectile::SetImpactMarkOrientation(Event *ev)
{
impactmarkorientation = ev->GetString(1);
}
void Projectile::Explode(Event *ev)
{
Entity *owner;
Entity *ignoreEnt = NULL;
if (!CheckTeams()) {
PostEvent(EV_Remove, EV_REMOVE);
return;
}
if (ev->NumArgs() == 1) {
ignoreEnt = ev->GetEntity(1);
}
// Get the owner of this projectile
owner = G_GetEntity(this->owner);
// If the owner's not here, make the world the owner
if (!owner) {
owner = world;
}
if (owner->IsDead() || owner == world) {
weap = NULL;
}
health = 0;
deadflag = DEAD_DEAD;
takedamage = DAMAGE_NO;
// Spawn an explosion model
if (explosionmodel.length()) {
// Move the projectile back off the surface a bit so we can see
// explosion effects.
Vector dir, v;
v = velocity;
v.normalize();
dir = v;
v = origin - v * 36;
setOrigin(v);
ExplosionAttack(v, owner, explosionmodel, dir, ignoreEnt, 1.0f, weap, m_bHurtOwnerOnly);
}
CancelEventsOfType(EV_Projectile_UpdateBeam);
// Kill the beam
if (m_beam) {
m_beam->ProcessEvent(EV_Remove);
m_beam = NULL;
}
// Remove the projectile
PostEvent(EV_Remove, level.frametime);
}
void Projectile::SetBounceTouch(Event *ev)
{
projFlags |= P_BOUNCE_TOUCH;
setMoveType(MOVETYPE_BOUNCE);
}
void Projectile::SetNoTouchDamage(Event *ev)
{
projFlags |= P_NO_TOUCH_DAMAGE;
}
void Projectile::SetSmashThroughGlass(Event *ev)
{
m_iSmashThroughGlass = ev->GetInteger(1);
PostEvent(EV_Projectile_SmashThroughGlass, level.frametime);
}
void Projectile::SmashThroughGlassThink(Event *ev)
{
if (velocity.length() > m_iSmashThroughGlass) {
Vector vEnd;
trace_t trace;
Entity *ent = G_GetEntity(owner);
vEnd = velocity * level.frametime + velocity * level.frametime + origin;
trace = G_Trace(origin, vec_zero, vec_zero, vEnd, ent, MASK_BEAM, false, "SmashThroughGlassThink");
if ((trace.ent) && (trace.ent->entity != world)) {
Entity *obj = trace.ent->entity;
if ((trace.surfaceFlags & SURF_GLASS) && (obj->takedamage)) {
obj->Damage(
this, ent, obj->health + 1, origin, velocity, trace.plane.normal, knockback, 0, meansofdeath
);
}
}
}
PostEvent(EV_Projectile_SmashThroughGlass, level.frametime);
}
void Projectile::BeamCommand(Event *ev)
{
int i;
if (!m_beam) {
m_beam = new FuncBeam;
m_beam->setOrigin(this->origin);
m_beam->Ghost(NULL);
}
Event *beamev = new Event(ev->GetToken(1));
for (i = 2; i <= ev->NumArgs(); i++) {
beamev->AddToken(ev->GetToken(i));
}
m_beam->ProcessEvent(beamev);
PostEvent(EV_Projectile_UpdateBeam, level.frametime);
}
void Projectile::UpdateBeam(Event *ev)
{
if (m_beam) {
m_beam->setOrigin(this->origin);
PostEvent(EV_Projectile_UpdateBeam, level.frametime);
}
}
void Projectile::SetBounceSound(Event *ev)
{
bouncesound = ev->GetString(1);
}
void Projectile::SetBounceSoundMetal(Event *ev)
{
bouncesound_metal = ev->GetString(1);
}
void Projectile::SetBounceSoundHard(Event *ev)
{
bouncesound_hard = ev->GetString(1);
}
void Projectile::SetBounceSoundWater(Event *ev)
{
bouncesound_water = ev->GetString(1);
}
void Projectile::SetChargeLife(Event *ev)
{
projFlags |= P_CHARGE_LIFE;
}
void Projectile::SetFuse(Event *ev)
{
if (ev->GetInteger(1)) {
projFlags |= P_FUSE;
} else {
projFlags &= ~P_FUSE;
}
}
void Projectile::SetMinLife(Event *ev)
{
minlife = ev->GetFloat(1);
projFlags |= P_CHARGE_LIFE;
}
void Projectile::SetLife(Event *ev)
{
life = ev->GetFloat(1);
}
void Projectile::SetDMLife(Event *ev)
{
dmlife = ev->GetFloat(1);
}
void Projectile::SetSpeed(Event *ev)
{
speed = ev->GetFloat(1);
}
void Projectile::SetMinSpeed(Event *ev)
{
minspeed = ev->GetFloat(1);
projFlags |= P_CHARGE_SPEED;
}
void Projectile::SetChargeSpeed(Event *ev)
{
projFlags |= P_CHARGE_SPEED;
}
void Projectile::SetAvelocity(Event *ev)
{
int i = 1;
int j = 0;
str vel;
if (ev->NumArgs() < 3) {
warning("ClientGameCommandManager::SetAngularVelocity", "Expecting at least 3 args for command randvel");
}
while (j < 3) {
vel = ev->GetString(i++);
if (vel == "crandom") {
avelocity[j++] = ev->GetFloat(i++) * crandom();
} else if (vel == "random") {
avelocity[j++] = ev->GetFloat(i++) * random();
} else {
avelocity[j++] = atof(vel.c_str());
}
}
}
void Projectile::SetDamage(Event *ev)
{
damage = ev->GetFloat(1);
}
void Projectile::SetKnockback(Event *ev)
{
knockback = ev->GetFloat(1);
}
void Projectile::SetDLight(Event *ev)
{
dlight_color[0] = ev->GetFloat(1);
dlight_color[1] = ev->GetFloat(2);
dlight_color[2] = ev->GetFloat(3);
dlight_radius = ev->GetFloat(4);
}
void Projectile::SetMeansOfDeath(Event *ev)
{
meansofdeath = (meansOfDeath_t)MOD_string_to_int(ev->GetString(1));
}
void Projectile::DoDecal(void)
{
if (impactmarkshader.length()) {
Decal *decal = new Decal;
decal->setShader(impactmarkshader);
decal->setOrigin(level.impact_trace.endpos);
decal->setDirection(level.impact_trace.plane.normal);
decal->setOrientation(impactmarkorientation);
decal->setRadius(impactmarkradius);
}
}
void Projectile::Touch(Event *ev)
{
Entity *other;
Entity *owner;
str realname;
// Other is what got hit
other = ev->GetEntity(1);
assert(other);
// Don't touch teleporters
if (other->isSubclassOf(Teleporter)) {
return;
}
// Can't hit yourself with a projectile
if (other->entnum == this->owner) {
return;
}
// Remove it if we hit the sky
if (HitSky()) {
PostEvent(EV_Remove, 0);
return;
}
if (!CheckTeams()) {
PostEvent(EV_Remove, EV_REMOVE);
return;
}
// Bouncy Projectile
if ((projFlags & P_BOUNCE_TOUCH)) {
str snd;
if (level.time - fLastBounceTime < 0.1f) {
fLastBounceTime = level.time;
return;
}
if (level.impact_trace.surfaceFlags & SURF_PUDDLE
|| (gi.pointcontents(level.impact_trace.endpos, 0) & (CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA))) {
if (bouncesound_water.length()) {
this->Sound(bouncesound_water, CHAN_BODY);
}
} else {
if (bouncesound_metal.length()) {
snd = bouncesound_metal;
} else if (bouncesound_hard.length()) {
snd = bouncesound_hard;
} else {
snd = bouncesound;
}
int flags = level.impact_trace.surfaceFlags;
if (flags & SURF_MUD) {
if (bouncesound.length()) {
Sound(bouncesound, CHAN_BODY);
}
} else if (flags & SURF_ROCK) {
if (bouncesound_hard.length()) {
Sound(bouncesound_hard, CHAN_BODY);
}
} else if (flags & SURF_GRILL) {
if (bouncesound_metal.length()) {
Sound(bouncesound_metal, CHAN_BODY);
}
} else if (flags & SURF_WOOD) {
if (bouncesound_hard.length()) {
Sound(bouncesound_hard, CHAN_BODY);
}
} else if (flags & SURF_METAL) {
if (bouncesound_metal.length()) {
Sound(bouncesound_metal, CHAN_BODY);
}
} else if (flags & SURF_GLASS) {
if (bouncesound_hard.length()) {
Sound(bouncesound_hard, CHAN_BODY);
}
} else {
if (bouncesound.length()) {
Sound(bouncesound, CHAN_BODY);
}
}
}
BroadcastAIEvent(AI_EVENT_WEAPON_IMPACT);
return;
}
if (!m_bExplodeOnTouch && damage == 0.0f) {
return;
}
// Get the owner of this projectile
owner = G_GetEntity(this->owner);
// If the owner's not here, make the world the owner
if (!owner) {
owner = world;
}
if (owner->IsDead() || owner == world) {
weap = NULL;
}
// Damage the thing that got hit
if (other->takedamage) {
other->Damage(
this, owner, damage, origin, velocity, level.impact_trace.plane.normal, knockback, 0, meansofdeath
);
}
if (!g_gametype->integer && weap) {
if (other->IsSubclassOfPlayer() || other->IsSubclassOfVehicle() || other->IsSubclassOfVehicleTank()
|| other->isSubclassOf(VehicleCollisionEntity)) {
weap->m_iNumHits++;
weap->m_iNumTorsoShots++;
if (weap->IsSubclassOfVehicleTurretGun()) {
VehicleTurretGun *t = (VehicleTurretGun *)weap.Pointer();
Player *p = (Player *)t->GetRemoteOwner().Pointer();
if (p && p->IsSubclassOfPlayer()) {
p->m_iNumHits++;
p->m_iNumTorsoShots++;
}
}
}
}
if (!m_bExplodeOnTouch) {
return;
}
// Make the projectile not solid
setSolidType(SOLID_NOT);
setMoveType(MOVETYPE_NONE);
hideModel();
// Do a decal
DoDecal();
BroadcastAIEvent(AI_EVENT_WEAPON_FIRE);
// Remove the projectile
PostEvent(EV_Remove, 0);
// Call the explosion event
Event *explEv;
explEv = new Event(EV_Projectile_Explode);
explEv->AddEntity(other);
ProcessEvent(explEv);
}
void Projectile::SetCanHitOwner(Event *ev)
{
can_hit_owner = true;
}
void Projectile::ClearOwner(Event *ev)
{
this->owner = ENTITYNUM_NONE;
edict->r.ownerNum = ENTITYNUM_NONE;
}
void Projectile::RemoveWhenStopped(Event *ev)
{
remove_when_stopped = true;
}
void Projectile::ExplodeOnTouch(Event *ev)
{
m_bExplodeOnTouch = true;
}
Sentient *Projectile::GetOwner(void)
{
Sentient *pOwner = (Sentient *)G_GetEntity(owner);
if (!pOwner || !pOwner->IsSubclassOfSentient()) {
return NULL;
} else {
return pOwner;
}
}
void Projectile::SetOwner(Entity *owner)
{
if (owner) {
this->owner = owner->entnum;
edict->r.ownerNum = owner->entnum;
if (owner->IsSubclassOfPlayer()) {
Player *p = (Player *)owner;
m_iTeam = p->GetTeam();
// this was added in openmohaa to prevent glitches
// like when the player disconnects or when the player spectates
m_bHadPlayerOwner = true;
}
m_pOwnerPtr = owner;
}
}
void Projectile::ArcToTarget(Event *ev)
{
m_bArcToTarget = true;
PostEvent(EV_Projectile_Prethink, 0);
}
void Projectile::BecomeBomb(Event *ev)
{
if (ev->NumArgs() > 0) {
Entity *ent = ev->GetEntity(1);
if (ent) {
owner = ent->entnum;
setOrigin(ent->origin - Vector(0, 0, 48));
setAngles(ent->angles);
velocity = ent->velocity;
}
}
m_bExplodeOnTouch = true;
gravity = 1.f;
setMoveType(MOVETYPE_BOUNCE);
setSolidType(SOLID_BBOX);
edict->clipmask = MASK_PROJECTILE;
setSize(mins, maxs);
}
void Projectile::DieInWater(Event *ev)
{
m_bDieInWater = true;
}
void Projectile::Stopped(Event *ev)
{
if (remove_when_stopped) {
PostEvent(EV_Remove, 0);
}
}
void Projectile::Think()
{
float angle, spin;
avelocity.x *= 1.3f;
angle = AngleNormalize180(angles.x);
spin = avelocity.x * level.frametime + angle;
if ((angle >= 0 || spin <= 0) && (angle <= 0 || spin >= 0)) {
angle = AngleNormalize360(angles.x);
spin = avelocity.x * level.frametime + angle;
if ((angle < 180 && spin > 180) || (angle > 180 && spin < 180)) {
setAngles(Vector(0, angles.y + 180, angles.z));
avelocity = vec_zero;
flags &= ~FL_THINK;
}
} else {
setAngles(Vector(0, angles.y, angles.z));
avelocity = vec_zero;
flags &= ~FL_THINK;
}
}
bool Projectile::CheckTeams(void)
{
Player *pOwner;
if (g_gametype->integer == GT_SINGLE_PLAYER) {
// Ignore in single-player mode
return true;
}
pOwner = (Player *)m_pOwnerPtr.Pointer();
if (!pOwner) {
// Owner disconnected
if (m_bHadPlayerOwner) {
return false;
} else {
return true;
}
}
if (pOwner->IsSubclassOfPlayer()) {
if (m_iTeam != TEAM_NONE && m_iTeam != pOwner->GetTeam()) {
return false;
}
} else if (pOwner->IsSubclassOfSentient()) {
if (m_iTeam != pOwner->m_Team) {
return false;
}
}
return true;
}
Event EV_Explosion_Radius
(
"radius",
EV_DEFAULT,
"f",
"projectileRadius",
"set the radius for the explosion",
EV_NORMAL
);
Event EV_Explosion_ConstantDamage
(
"constantdamage",
EV_DEFAULT,
NULL,
NULL,
"Makes the explosion do constant damage over the radius",
EV_NORMAL
);
Event EV_Explosion_DamageEveryFrame
(
"damageeveryframe",
EV_DEFAULT,
NULL,
NULL,
"Makes the explosion damage every frame",
EV_NORMAL
);
Event EV_Explosion_DamageAgain
(
"damageagain",
EV_DEFAULT,
NULL,
NULL,
"This event is generated each frame if explosion is set to damage each frame",
EV_NORMAL
);
Event EV_Explosion_Flash
(
"flash",
EV_DEFAULT,
"fffff",
"time r g b radius",
"Flash player screens",
EV_NORMAL
);
Event EV_Explosion_RadiusDamage
(
"radiusdamage",
EV_DEFAULT,
"f",
"radiusDamage",
"set the radius damage an explosion does",
EV_NORMAL
);
Event EV_Explosion_Effect
(
"explosioneffect",
EV_DEFAULT,
"s",
"explosiontype",
"Make an explosionType explosion effect",
EV_NORMAL
);
CLASS_DECLARATION(Projectile, Explosion, NULL) {
{&EV_Explosion_Radius, &Explosion::SetRadius },
{&EV_Explosion_ConstantDamage, &Explosion::SetConstantDamage },
{&EV_Explosion_DamageEveryFrame, &Explosion::SetDamageEveryFrame},
{&EV_Explosion_DamageAgain, &Explosion::DamageAgain },
{&EV_Explosion_Flash, &Explosion::SetFlash },
{&EV_Explosion_RadiusDamage, &Explosion::SetRadiusDamage },
{&EV_Explosion_Effect, &Explosion::MakeExplosionEffect},
{NULL, NULL }
};
Explosion::Explosion()
{
if (LoadingSavegame) {
// Archive function will setup all necessary data
return;
}
radius = 0;
constant_damage = false;
damage_every_frame = false;
flash_time = 0;
flash_type = 0;
owner = ENTITYNUM_NONE;
hurtOwnerOnly = false;
}
void Explosion::SetFlash(Event *ev)
{
flash_time = ev->GetFloat(1);
flash_r = ev->GetFloat(2);
flash_g = ev->GetFloat(3);
flash_b = ev->GetFloat(4);
flash_a = ev->GetFloat(5);
flash_radius = ev->GetFloat(6);
flash_type = 0;
if (ev->NumArgs() > 6) {
str t = ev->GetString(7);
if (!t.icmp("addblend")) {
flash_type = 1;
} else if (!t.icmp("alphablend")) {
flash_type = 0;
}
}
}
void Explosion::SetRadius(Event *ev)
{
radius = ev->GetFloat(1);
}
void Explosion::SetRadiusDamage(Event *ev)
{
radius_damage = ev->GetFloat(1);
}
void Explosion::SetConstantDamage(Event *ev)
{
constant_damage = true;
}
void Explosion::SetDamageEveryFrame(Event *ev)
{
damage_every_frame = true;
}
void Explosion::DamageAgain(Event *ev)
{
Entity *owner_ent;
Weapon *weapon;
float dmg;
float rad;
if (!CheckTeams()) {
PostEvent(EV_Remove, EV_REMOVE);
return;
}
owner_ent = G_GetEntity(owner);
if (!owner_ent) {
owner_ent = world;
}
weapon = weap;
if (owner_ent == world || owner_ent->IsDead()) {
weap = NULL;
}
dmg = radius_damage;
rad = radius;
if (rad == 0.0f) {
rad = radius_damage + 60.0f;
}
RadiusDamage(origin, this, owner_ent, dmg, NULL, meansofdeath, rad, knockback, constant_damage, weapon, false);
PostEvent(EV_Explosion_DamageAgain, level.frametime);
}
void Explosion::MakeExplosionEffect(Event *ev)
{
str sEffect = ev->GetString(1);
gi.SetBroadcastAll();
if (!sEffect.icmp("grenade")) {
gi.MSG_StartCGM(BG_MapCGMToProtocol(g_protocol, CGM_EXPLOSION_EFFECT_1));
} else {
if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
if (!sEffect.icmp("heavyshell")) {
gi.MSG_StartCGM(BG_MapCGMToProtocol(g_protocol, CGM_EXPLOSION_EFFECT_3));
} else if (!sEffect.icmp("tank")) {
gi.MSG_StartCGM(BG_MapCGMToProtocol(g_protocol, CGM_EXPLOSION_EFFECT_4));
} else {
gi.MSG_StartCGM(BG_MapCGMToProtocol(g_protocol, CGM_EXPLOSION_EFFECT_2));
}
} else {
gi.MSG_StartCGM(BG_MapCGMToProtocol(g_protocol, CGM_EXPLOSION_EFFECT_2));
}
}
gi.MSG_WriteCoord(origin[0]);
gi.MSG_WriteCoord(origin[1]);
gi.MSG_WriteCoord(origin[2]);
gi.MSG_EndCGM();
}
Entity *FindDefusableObject(const Vector& dir, Entity *owner, float maxdist)
{
Vector startOrg;
Vector endOrg;
Vector mins, maxs;
float fCos;
int numAreaEntities;
int i;
int entNums[MAX_GENTITIES];
startOrg = owner->origin;
endOrg = startOrg + dir * maxdist;
if (startOrg.z > endOrg.z) {
endOrg.z -= 10;
startOrg.z += 10;
} else {
startOrg.z -= 10;
endOrg.z += 10;
}
fCos = cos(M_PI / 3);
for (i = 0; i < 3; i++) {
if (endOrg[i] > startOrg[i]) {
mins[i] = startOrg[i] - 1;
maxs[i] = endOrg[i] + 1;
} else {
mins[i] = endOrg[i] - 1;
maxs[i] = startOrg[i] + 1;
}
}
if (endOrg.x > startOrg.x) {
mins[0] = startOrg.x - 1;
maxs[0] = endOrg.x + 1;
} else {
mins[0] = endOrg.x - 1;
maxs[0] = startOrg.x + 1;
}
numAreaEntities = gi.AreaEntities(mins, maxs, entNums, ARRAY_LEN(entNums));
for (i = 0; i < numAreaEntities; i++) {
gentity_t *pgEnt = &g_entities[entNums[i]];
Entity *pEnt;
vec3_t delta;
if (!pgEnt->solid) {
continue;
}
pEnt = pgEnt->entity;
VectorSubtract(pEnt->centroid, startOrg, delta);
VectorNormalize(delta);
if (pEnt == owner) {
continue;
}
if (DotProduct(delta, dir) > fCos && pEnt->Vars()->VariableExists("defuseThread")) {
return pEnt;
}
}
return NULL;
}
void DefuseObject(const Vector& dir, Entity *owner, float maxdist)
{
Entity *defusableObj = FindDefusableObject(dir, owner, maxdist);
ScriptVariable *defuseThreadVar;
str defuseThreadName;
if (!defusableObj) {
return;
}
defuseThreadVar = defusableObj->Vars()->GetVariable("defuseThread");
if (defuseThreadVar) {
defuseThreadName = defuseThreadVar->stringValue();
}
ScriptThreadLabel label;
label.Set(defuseThreadName);
label.Execute(defusableObj, ListenerPtr());
}
qboolean CanPlaceLandmine(const Vector& origin, Entity *owner)
{
Vector vEnd;
Vector vDelta;
trace_t trace;
if (!level.RoundStarted()) {
gi.DPrintf("Can't place landmine until the round starts\n");
return qfalse;
}
vEnd = origin - Vector(0, 0, 256);
trace = G_Trace(origin, vec_zero, vec_zero, vEnd, owner, MASK_LANDMINE_PLACE, qfalse, "LandminePlace", qtrue);
vDelta = origin - trace.endpos;
if (vDelta.length() > 90) {
gi.DPrintf("Too high to place landmine\n");
return qfalse;
}
if (trace.surfaceFlags & SURF_WOOD) {
gi.DPrintf("Can't place landmine on wood\n");
return qfalse;
} else if (trace.surfaceFlags & SURF_METAL) {
gi.DPrintf("Can't place landmine on metal\n");
return qfalse;
} else if (trace.surfaceFlags & SURF_ROCK) {
gi.DPrintf("Can't place landmine on rock\n");
return qfalse;
} else if (trace.surfaceFlags & SURF_GRILL) {
gi.DPrintf("Can't place landmine on grill\n");
return qfalse;
} else if (trace.surfaceFlags & SURF_GLASS) {
gi.DPrintf("Can't place landmine on glass\n");
return qfalse;
} else if (trace.surfaceFlags & SURF_CARPET) {
gi.DPrintf("Can't place landmine on carpet\n");
return qfalse;
}
if (!(trace.contents & CONTENTS_SOLID)) {
gi.DPrintf("Can't place landmine on nonsolid things\n");
return qfalse;
}
return qtrue;
}
void PlaceLandmine(const Vector& origin, Entity *owner, const str& model, Weapon *weap)
{
SpawnArgs args;
Listener *l;
TriggerLandmine *trigger;
if (!model.length()) {
gi.DPrintf("PlaceLandmine : No model specified for PlaceLandmine");
return;
}
args.setArg("model", model.c_str());
args.setArg("setthread", "global/landmine.scr::steppedOn");
args.setArg("$defuseThread", "global/landmine.scr::defuse");
args.setArg("targetname", "landmine");
l = args.Spawn();
if (!l) {
gi.DPrintf("PlaceLandmine model '%s' not found\n", model.c_str());
return;
}
if (!l->inheritsFrom(&TriggerLandmine::ClassInfo)) {
gi.DPrintf("%s is not of class trigger_landmine\n", model.c_str());
return;
}
trigger = static_cast<TriggerLandmine *>(l);
trigger->droptofloor(256);
trigger->ProcessInitCommands();
trigger->SetDamageable(qtrue);
if (owner) {
trigger->edict->r.ownerNum = owner->entnum;
}
trigger->setOrigin(origin);
if (owner->inheritsFrom(&Player::ClassInfo)) {
if (g_gametype->integer >= GT_TEAM) {
Player *p = static_cast<Player *>(owner);
trigger->SetTeam(p->GetTeam());
} else {
trigger->SetTeam(0);
}
}
trigger->NewAnim("idle");
if (g_gametype->integer == GT_SINGLE_PLAYER) {
if (owner) {
if (owner->IsDead()) {
weap = NULL;
}
} else {
weap = NULL;
}
if (weap) {
weap->m_iNumShotsFired++;
if (owner) {
if (owner->IsSubclassOfPlayer() && weap->IsSubclassOfTurretGun()) {
TurretGun *turret = static_cast<TurretGun *>(weap);
// FIXME: find what to increment
}
}
}
}
}
Projectile *ProjectileAttack(
Vector start, Vector dir, Entity *owner, str projectileModel, float fraction, float real_speed, Weapon *weap
)
{
Event *ev;
Projectile *proj = NULL;
float newspeed, newlife;
SpawnArgs args;
Entity *obj;
float dot = 0;
if (!projectileModel.length()) {
gi.DPrintf("ProjectileAttack : No model specified for ProjectileAttack");
return NULL;
}
args.setArg("model", projectileModel);
obj = (Entity *)args.Spawn();
if (!obj) {
gi.DPrintf("projectile model '%s' not found\n", projectileModel.c_str());
return NULL;
}
if (obj->isSubclassOf(Projectile)) {
proj = (Projectile *)obj;
} else {
gi.DPrintf("%s is not of class projectile\n", projectileModel.c_str());
}
if (!proj) {
return NULL;
}
// Create a new projectile entity and set it off
proj->setMoveType(MOVETYPE_BOUNCE);
proj->ProcessInitCommands();
proj->SetOwner(owner);
proj->edict->r.ownerNum = owner->entnum;
proj->angles = dir.toAngles();
proj->charge_fraction = fraction;
if (!real_speed) {
if (proj->projFlags & P_CHARGE_SPEED) {
newspeed = proj->minspeed + (proj->speed - proj->minspeed) * fraction;
} else {
newspeed = proj->speed;
}
} else {
newspeed = real_speed;
}
if (proj->addownervelocity) {
dot = DotProduct(owner->velocity, dir);
if (dot < 0) {
dot = 0;
}
}
proj->velocity = dir * (newspeed + dot);
proj->velocity += proj->addvelocity;
proj->setAngles(proj->angles);
proj->setSolidType(SOLID_BBOX);
proj->edict->clipmask = MASK_PROJECTILE;
proj->setSize(proj->mins, proj->maxs);
proj->setOrigin(start);
proj->origin.copyTo(proj->edict->s.origin2);
if (proj->m_beam) {
proj->m_beam->setOrigin(start);
proj->m_beam->origin.copyTo(proj->m_beam->edict->s.origin2);
}
if (proj->dlight_radius) {
G_SetConstantLight(
&proj->edict->s.constantLight,
&proj->dlight_color[0],
&proj->dlight_color[1],
&proj->dlight_color[2],
&proj->dlight_radius
);
}
// Calc the life of the projectile
if (proj->projFlags & P_CHARGE_LIFE) {
if (g_gametype->integer != GT_SINGLE_PLAYER && proj->dmlife) {
newlife = proj->dmlife * (1 - fraction);
} else {
newlife = proj->life * (1 - fraction);
}
if (newlife < proj->minlife) {
newlife = proj->minlife;
}
} else {
if (g_gametype->integer != GT_SINGLE_PLAYER && proj->dmlife) {
newlife = proj->dmlife;
} else {
newlife = proj->life;
}
}
// Remove the projectile after it's life expires
ev = new Event(EV_Projectile_Explode);
proj->PostEvent(ev, newlife);
proj->NewAnim("idle");
// If can hit owner clear the owner of this projectile in a second
if (proj->can_hit_owner) {
proj->PostEvent(EV_Projectile_ClearOwner, 1);
}
if (owner) {
if (owner->IsDead() || owner == world) {
// clear the weapon as the owner died
weap = NULL;
}
} else {
// clear the weapon as there is no owner
weap = NULL;
}
if (g_gametype->integer == GT_SINGLE_PLAYER) {
if (weap) {
weap->m_iNumShotsFired++;
if (owner->IsSubclassOfPlayer() && weap->IsSubclassOfTurretGun()) {
Player *p = (Player *)owner;
p->m_iNumShotsFired++;
}
}
}
return proj;
}
void BulletAttack_Stat(Entity *owner, Entity *target, trace_t *trace, Weapon *weap)
{
Sentient *targetSen;
if (!target->IsSubclassOfSentient() || !weap) {
return;
}
targetSen = static_cast<Sentient *>(target);
targetSen->m_iLastHitTime = level.inttime;
weap->m_iNumHits++;
switch (trace->location) {
case HITLOC_HEAD:
case HITLOC_HELMET:
case HITLOC_NECK:
weap->m_iNumHeadShots++;
break;
case HITLOC_TORSO_UPPER:
case HITLOC_TORSO_MID:
case HITLOC_TORSO_LOWER:
weap->m_iNumTorsoShots++;
break;
case HITLOC_PELVIS:
weap->m_iNumGroinShots++;
break;
case HITLOC_R_ARM_UPPER:
case HITLOC_R_ARM_LOWER:
case HITLOC_R_HAND:
weap->m_iNumRightArmShots++;
break;
case HITLOC_L_ARM_UPPER:
case HITLOC_L_ARM_LOWER:
case HITLOC_L_HAND:
weap->m_iNumLeftArmShots++;
break;
case HITLOC_R_LEG_UPPER:
case HITLOC_R_LEG_LOWER:
case HITLOC_R_FOOT:
weap->m_iNumRightLegShots++;
break;
case HITLOC_L_LEG_UPPER:
case HITLOC_L_LEG_LOWER:
case HITLOC_L_FOOT:
weap->m_iNumLeftLegShots++;
break;
default:
weap->m_iNumTorsoShots++;
break;
}
if (owner && owner->IsSubclassOfPlayer() && weap->IsSubclassOfTurretGun()) {
Player *p = static_cast<Player *>(owner);
p->m_iNumHits++;
switch (trace->location) {
case HITLOC_HEAD:
case HITLOC_HELMET:
case HITLOC_NECK:
p->m_iNumHeadShots++;
break;
case HITLOC_TORSO_UPPER:
case HITLOC_TORSO_MID:
case HITLOC_TORSO_LOWER:
p->m_iNumTorsoShots++;
break;
case HITLOC_PELVIS:
p->m_iNumGroinShots++;
break;
case HITLOC_R_ARM_UPPER:
case HITLOC_R_ARM_LOWER:
case HITLOC_R_HAND:
p->m_iNumRightArmShots++;
break;
case HITLOC_L_ARM_UPPER:
case HITLOC_L_ARM_LOWER:
case HITLOC_L_HAND:
p->m_iNumLeftArmShots++;
break;
case HITLOC_R_LEG_UPPER:
case HITLOC_R_LEG_LOWER:
case HITLOC_R_FOOT:
p->m_iNumRightLegShots++;
break;
case HITLOC_L_LEG_UPPER:
case HITLOC_L_LEG_LOWER:
case HITLOC_L_FOOT:
p->m_iNumLeftLegShots++;
break;
default:
p->m_iNumTorsoShots++;
break;
}
}
}
float BulletAttack(
Vector start,
Vector vBarrel,
Vector dir,
Vector right,
Vector up,
float range,
float damage,
int bulletlarge,
float knockback,
int dflags,
int meansofdeath,
Vector spread,
int count,
Entity *owner,
int iTracerFrequency,
int *piTracerCount,
float bulletthroughwood,
float bulletthroughmetal,
Weapon *weap,
float tracerspeed
)
{
Vector vDir;
Vector vTmpEnd;
Vector vTraceStart;
Vector vTraceEnd;
int i;
int iTravelDist;
trace_t trace;
Entity *ent;
Entity *newowner;
//Entity *tmpSkipEnt;
float damage_total = 0;
float original_value;
qboolean bBulletDone;
qboolean bThroughThing;
int iContinueCount;
float vEndArray[64][3];
int iTracerCount = 0;
int iNumHit;
int lastSurfaceFlags;
float bulletdist;
float newdamage;
float throughThingFrac;
float oldfrac;
int bulletbits;
lastSurfaceFlags = 0;
iNumHit = 0;
if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
bulletbits = 2;
} else {
bulletlarge = damage >= 41.f;
bulletbits = 1;
}
if (count > 63) {
count = 63;
}
if (!owner || owner->IsDead() || owner == world) {
weap = NULL;
}
for (i = 0; i < count; i++) {
trace_t tracethrough;
vTraceEnd = start + (dir * range) + (right * grandom() * spread.x) + (up * grandom() * spread.y);
vDir = vTraceEnd - start;
VectorNormalizeFast(vDir);
iContinueCount = 0;
iTravelDist = 0;
bBulletDone = qfalse;
bThroughThing = qfalse;
newowner = owner;
newdamage = damage;
while (!bBulletDone && iTravelDist < MAX_TRAVEL_DIST) {
iTravelDist += MAX_TRAVEL_DIST;
vTraceStart = start;
vTraceEnd = start + vDir * iTravelDist;
memset(&trace, 0, sizeof(trace_t));
oldfrac = -1;
while (trace.fraction < 1.0f) {
trace = G_Trace(
vTraceStart, vec_zero, vec_zero, vTraceEnd, newowner, MASK_SHOT_TRIG, false, "BulletAttack", true
);
vTmpEnd = trace.endpos;
if (bThroughThing) {
bThroughThing = qfalse;
tracethrough = G_Trace(
vTmpEnd,
vec_zero,
vec_zero,
vTraceStart + vDir * -4,
newowner,
MASK_SHOT,
qfalse,
"BulletAttack2",
qtrue
);
if (!(tracethrough.surfaceFlags & (SURF_FOLIAGE | SURF_GLASS | SURF_PUDDLE | SURF_PAPER))
&& (!(tracethrough.surfaceFlags & SURF_WOOD) || !bulletthroughwood)
&& (!(tracethrough.surfaceFlags & (SURF_GRILL | SURF_METAL)) || !bulletthroughmetal)) {
vTmpEnd = vTraceStart + vDir * -4;
trace.fraction = 1.f;
bBulletDone = qtrue;
if (g_showbullettrace->integer) {
bThroughThing = qtrue;
}
break;
}
if (lastSurfaceFlags & SURF_WOOD) {
if (tracethrough.surfaceFlags & SURF_WOOD) {
throughThingFrac = 1.f / bulletthroughwood;
} else {
throughThingFrac = 2.f / (bulletthroughwood + bulletthroughmetal);
}
} else {
if (tracethrough.surfaceFlags & SURF_WOOD) {
throughThingFrac = 2.f / (bulletthroughwood + bulletthroughmetal);
} else {
throughThingFrac = 1.f / bulletthroughmetal;
}
}
bulletdist = (tracethrough.endpos - vTraceStart).length() + 4.f;
if (g_showbullettrace->integer) {
gi.Printf("Bullet damage: %.2f : %.2f -> ", bulletdist, damage);
}
newdamage -= damage * bulletdist * throughThingFrac;
if (newdamage < 1.f) {
vTmpEnd = vTraceStart + vDir * -4;
trace.fraction = 1.f;
bBulletDone = qtrue;
if (g_showbullettrace->integer) {
bThroughThing = qtrue;
}
break;
}
if (g_showbullettrace->integer) {
G_DebugLine(
tracethrough.endpos + Vector(8, 0, 0),
tracethrough.endpos - Vector(8, 0, 0),
0.5f,
0.5f,
1.f,
1.f
);
G_DebugLine(
tracethrough.endpos + Vector(0, 8, 0),
tracethrough.endpos - Vector(0, 8, 0),
0.5f,
0.5f,
1.f,
1.f
);
G_DebugLine(
tracethrough.endpos + Vector(0, 0, 8),
tracethrough.endpos - Vector(0, 0, 8),
0.5f,
0.5f,
1.f,
1.f
);
}
}
if (trace.ent) {
ent = trace.ent->entity;
} else {
ent = NULL;
}
if (ent && ent != world && ent != newowner) {
if (ent->takedamage) {
if (g_gametype->integer == GT_SINGLE_PLAYER && !iNumHit) {
BulletAttack_Stat(newowner, ent, &trace, weap);
}
iNumHit++;
// Get the original value of the victims health or water
original_value = ent->health;
ent->Damage(
world,
newowner,
newdamage,
trace.endpos,
dir,
trace.plane.normal,
knockback,
dflags,
meansofdeath,
trace.location
);
// Get the new value of the victims health or water
damage_total += original_value - ent->health;
}
if (ent->edict->solid == SOLID_BBOX && !(trace.contents & CONTENTS_CLAYPIDGEON)) {
if (trace.surfaceFlags & MASK_SURF_TYPE) {
gi.SetBroadcastVisible(vTmpEnd, NULL);
gi.MSG_StartCGM(CGM_BULLET_6);
gi.MSG_WriteCoord(vTmpEnd[0]);
gi.MSG_WriteCoord(vTmpEnd[1]);
gi.MSG_WriteCoord(vTmpEnd[2]);
gi.MSG_WriteDir(trace.plane.normal);
gi.MSG_WriteBits(bulletlarge, bulletbits);
gi.MSG_EndCGM();
} else if (trace.location >= 0 && ent->IsSubclassOfPlayer()) {
gi.SetBroadcastVisible(vTmpEnd, NULL);
gi.MSG_StartCGM(CGM_BULLET_8);
gi.MSG_WriteCoord(vTmpEnd[0]);
gi.MSG_WriteCoord(vTmpEnd[1]);
gi.MSG_WriteCoord(vTmpEnd[2]);
gi.MSG_WriteDir(trace.plane.normal);
gi.MSG_WriteBits(bulletlarge, bulletbits);
gi.MSG_EndCGM();
} else if (ent->edict->r.contents & CONTENTS_SOLID) {
gi.SetBroadcastVisible(vTmpEnd, NULL);
gi.MSG_StartCGM(CGM_BULLET_7);
gi.MSG_WriteCoord(vTmpEnd[0]);
gi.MSG_WriteCoord(vTmpEnd[1]);
gi.MSG_WriteCoord(vTmpEnd[2]);
gi.MSG_WriteDir(trace.plane.normal);
gi.MSG_WriteBits(bulletlarge, bulletbits);
gi.MSG_EndCGM();
}
} else if (ent->edict->solid == SOLID_BSP && !(trace.contents & CONTENTS_CLAYPIDGEON)) {
gi.SetBroadcastVisible(vTmpEnd, NULL);
gi.MSG_StartCGM(CGM_BULLET_6);
gi.MSG_WriteCoord(vTmpEnd[0]);
gi.MSG_WriteCoord(vTmpEnd[1]);
gi.MSG_WriteCoord(vTmpEnd[2]);
gi.MSG_WriteDir(trace.plane.normal);
gi.MSG_WriteBits(bulletlarge, bulletbits);
gi.MSG_EndCGM();
}
}
if (trace.fraction < 1.0f) {
if (trace.surfaceFlags & (SURF_FOLIAGE | SURF_GLASS | SURF_PUDDLE | SURF_PAPER)
|| trace.contents & (CONTENTS_CLAYPIDGEON | CONTENTS_WATER)
|| (bulletlarge && trace.ent && trace.ent->r.contents & CONTENTS_BBOX && !trace.ent->r.bmodel
&& trace.ent->entity->takedamage)
|| ((trace.surfaceFlags & SURF_WOOD) && bulletthroughwood)
|| ((trace.surfaceFlags & (SURF_GRILL | SURF_METAL)) && bulletthroughmetal)
&& iContinueCount < 5) {
if (((trace.surfaceFlags & SURF_WOOD) && bulletthroughwood)
|| ((trace.surfaceFlags & (SURF_GRILL | SURF_METAL)) && bulletthroughmetal)) {
if (trace.contents & CONTENTS_FENCE) {
float damageMultiplier;
if (lastSurfaceFlags & SURF_WOOD) {
damageMultiplier = 1.f / bulletthroughwood;
} else {
damageMultiplier = 1.f / bulletthroughmetal;
}
newdamage -= damageMultiplier * 2 * damage;
if (newdamage < 0) {
trace.fraction = 1;
bBulletDone = qtrue;
if (g_showbullettrace->integer) {
bThroughThing = qtrue;
VectorScale(vDir, 2, tracethrough.endpos);
VectorAdd(tracethrough.endpos, vTmpEnd, tracethrough.endpos);
}
} else {
trace.fraction = 1.f;
bBulletDone = qtrue;
if (g_showbullettrace->integer) {
bThroughThing = qtrue;
VectorScale(vDir, 2, tracethrough.endpos);
VectorAdd(tracethrough.endpos, vTmpEnd, tracethrough.endpos);
}
}
} else {
bThroughThing = qtrue;
lastSurfaceFlags = trace.surfaceFlags;
}
}
if (!bBulletDone) {
vTraceStart = vTmpEnd + vDir * 4;
if (trace.ent) {
newowner = trace.ent->entity;
} else {
newowner = NULL;
}
if (g_showbullettrace->integer) {
G_DebugLine(vTmpEnd + Vector(8, 0, 0), vTmpEnd - Vector(8, 0, 0), 1, 0.5f, 0.5f, 1.f);
G_DebugLine(vTmpEnd + Vector(0, 8, 0), vTmpEnd - Vector(0, 8, 0), 1, 0.5f, 0.5f, 1.f);
G_DebugLine(vTmpEnd + Vector(0, 0, 8), vTmpEnd - Vector(0, 0, 8), 1, 0.5f, 0.5f, 1.f);
}
iContinueCount++;
}
} else {
trace.fraction = 1.f;
bBulletDone = qtrue;
}
if (oldfrac != trace.fraction) {
oldfrac = trace.fraction;
} else {
trace.fraction = 1.f;
}
}
}
}
if (bBulletDone && g_showbullettrace->integer && bThroughThing) {
G_DebugLine(
tracethrough.endpos + Vector(8, 0, 0), tracethrough.endpos - Vector(8, 0, 0), 0.25f, 0.25f, 0.5f, 1.f
);
G_DebugLine(
tracethrough.endpos + Vector(0, 8, 0), tracethrough.endpos - Vector(0, 8, 0), 0.25f, 0.25f, 0.5f, 1.f
);
G_DebugLine(
tracethrough.endpos + Vector(0, 0, 8), tracethrough.endpos - Vector(0, 0, 8), 0.25f, 0.25f, 0.5f, 1.f
);
}
VectorCopy(vTraceEnd, vEndArray[i]);
if (iTracerFrequency && piTracerCount) {
(*piTracerCount)++;
if (*piTracerCount == iTracerFrequency) {
iTracerCount++;
*piTracerCount = 0;
}
}
// Draw a debug trace line to show bullet fire
if (g_showbullettrace->integer) {
G_DebugLine(start, vTmpEnd, 1, 1, 1, 1);
G_DebugLine(vTmpEnd + Vector(8, 0, 0), vTmpEnd - Vector(8, 0, 0), 0.5f, 0.25f, 0.25f, 1.f);
G_DebugLine(vTmpEnd + Vector(0, 8, 0), vTmpEnd - Vector(0, 8, 0), 0.5f, 0.25f, 0.25f, 1.f);
G_DebugLine(vTmpEnd + Vector(0, 0, 8), vTmpEnd - Vector(0, 0, 8), 0.5f, 0.25f, 0.25f, 1.f);
G_DebugLine(vTmpEnd, vTraceEnd, 0.4f, 0.4f, 0.4f, 1.f);
}
}
if (g_gametype->integer == GT_SINGLE_PLAYER && weap) {
weap->m_iNumShotsFired++;
if (owner && owner->IsSubclassOfPlayer() && weap->IsSubclassOfTurretGun()) {
Player *p = (Player *)owner;
p->m_iNumShotsFired++;
}
}
gi.SetBroadcastVisible(start, trace.endpos);
if (count == 1) {
if (iTracerCount) {
gi.MSG_StartCGM(CGM_BULLET_1);
gi.MSG_WriteCoord(vBarrel[0]);
gi.MSG_WriteCoord(vBarrel[1]);
gi.MSG_WriteCoord(vBarrel[2]);
} else {
gi.MSG_StartCGM(CGM_BULLET_2);
}
gi.MSG_WriteCoord(start[0]);
gi.MSG_WriteCoord(start[1]);
gi.MSG_WriteCoord(start[2]);
gi.MSG_WriteCoord(trace.endpos[0]);
gi.MSG_WriteCoord(trace.endpos[1]);
gi.MSG_WriteCoord(trace.endpos[2]);
gi.MSG_WriteBits(bulletlarge, bulletbits);
if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
if (tracerspeed == 1.f) {
gi.MSG_WriteBits(0, 1);
} else {
int speed;
speed = tracerspeed * (1 << 9);
gi.MSG_WriteBits(1, 1);
gi.MSG_WriteBits(Q_clamp(speed, 1, 1023), 10);
}
}
} else {
if (iTracerCount) {
gi.MSG_StartCGM(CGM_BULLET_3);
gi.MSG_WriteCoord(vBarrel[0]);
gi.MSG_WriteCoord(vBarrel[1]);
gi.MSG_WriteCoord(vBarrel[2]);
if (iTracerCount > 63) {
iTracerCount = 63;
}
gi.MSG_WriteBits(Q_min(iTracerCount, 63), 6);
} else {
gi.MSG_StartCGM(CGM_BULLET_4);
}
gi.MSG_WriteCoord(start[0]);
gi.MSG_WriteCoord(start[1]);
gi.MSG_WriteCoord(start[2]);
gi.MSG_WriteBits(bulletlarge, bulletbits);
if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
if (tracerspeed == 1.f) {
gi.MSG_WriteBits(0, 1);
} else {
gi.MSG_WriteBits(1, 1);
gi.MSG_WriteBits(Q_clamp(tracerspeed, 1, 1023), 10);
}
}
gi.MSG_WriteBits(count, 6);
for (int i = count; i > 0; i--) {
gi.MSG_WriteCoord(vEndArray[i][0]);
gi.MSG_WriteCoord(vEndArray[i][1]);
gi.MSG_WriteCoord(vEndArray[i][2]);
}
}
gi.MSG_EndCGM();
if (damage_total > 0) {
return damage_total;
} else {
return 0;
}
}
void FakeBulletAttack(
Vector start,
Vector vBarrel,
Vector dir,
Vector right,
Vector up,
float range,
float damage,
int large,
Vector spread,
int count,
Entity *owner,
int iTracerFrequency,
int *piTracerCount,
float tracerspeed
)
{
Vector vDir;
Vector vTraceEnd;
int i;
int bulletbits;
if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
bulletbits = 2;
} else {
large = damage >= 41.f;
bulletbits = 1;
}
for (i = 0; i < count; i++) {
vTraceEnd = start + (dir * range) + (right * grandom() * spread.x) + (up * grandom() * spread.y);
vDir = vTraceEnd - start;
VectorNormalize(vDir);
vTraceEnd = start + vDir * 9216.0f;
gi.SetBroadcastVisible(start, vTraceEnd);
if (iTracerFrequency && piTracerCount) {
(*piTracerCount)++;
if (*piTracerCount == iTracerFrequency) {
gi.MSG_StartCGM(BG_MapCGMToProtocol(g_protocol, CGM_BULLET_NO_BARREL_1));
gi.MSG_WriteCoord(vBarrel[0]);
gi.MSG_WriteCoord(vBarrel[1]);
gi.MSG_WriteCoord(vBarrel[2]);
*piTracerCount = 0;
} else {
gi.MSG_StartCGM(BG_MapCGMToProtocol(g_protocol, CGM_BULLET_NO_BARREL_2));
}
} else {
gi.MSG_StartCGM(BG_MapCGMToProtocol(g_protocol, CGM_BULLET_NO_BARREL_2));
}
gi.MSG_WriteCoord(start[0]);
gi.MSG_WriteCoord(start[1]);
gi.MSG_WriteCoord(start[2]);
gi.MSG_WriteCoord(vTraceEnd[0]);
gi.MSG_WriteCoord(vTraceEnd[1]);
gi.MSG_WriteCoord(vTraceEnd[2]);
gi.MSG_WriteBits(large, bulletbits);
if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
if (tracerspeed == 1.f) {
gi.MSG_WriteBits(0, 1);
} else {
gi.MSG_WriteBits(1, 1);
gi.MSG_WriteBits(Q_clamp(tracerspeed, 1, 1023), 10);
}
}
gi.MSG_EndCGM();
}
}
void ClickItemAttack(Vector vStart, Vector vForward, float fRange, Entity *pOwner)
{
Vector vEnd;
trace_t trace;
vEnd = vStart + vForward * fRange;
trace = G_Trace(vStart, vec_zero, vec_zero, vEnd, pOwner, MASK_ALL, qfalse, "ClickItemAttack");
if (g_showbullettrace->integer) {
G_DebugLine(vStart, vEnd, 1, 1, 1, 1);
}
if (trace.entityNum != ENTITYNUM_NONE && trace.ent && trace.ent->entity
&& trace.ent->entity->isSubclassOf(TriggerClickItem)) {
Event *ev = new Event(EV_Activate);
ev->AddEntity(pOwner);
trace.ent->entity->PostEvent(ev, 0);
} else {
ScriptThreadLabel failThread;
// Try to execute a fail thread
if (failThread.TrySet("clickitem_fail")) {
failThread.Execute();
}
}
}
Projectile *HeavyAttack(Vector start, Vector dir, str projectileModel, float real_speed, Entity *owner, Weapon *weap)
{
Event *ev;
Projectile *proj = NULL;
float newspeed, newlife;
SpawnArgs args;
Entity *obj;
float dot = 0;
if (!projectileModel.length()) {
gi.DPrintf("ProjectileAttack : No model specified for ProjectileAttack");
return NULL;
}
args.setArg("model", projectileModel);
obj = (Entity *)args.Spawn();
if (!obj) {
gi.DPrintf("projectile model '%s' not found\n", projectileModel.c_str());
return NULL;
}
if (obj->IsSubclassOfProjectile()) {
proj = (Projectile *)obj;
} else {
gi.DPrintf("%s is not of class projectile\n", projectileModel.c_str());
}
if (!proj) {
return NULL;
}
// Create a new projectile entity and set it off
proj->setModel(projectileModel);
proj->setMoveType(MOVETYPE_BOUNCE);
proj->ProcessInitCommands();
proj->SetOwner(owner);
proj->edict->r.ownerNum = owner->entnum;
proj->angles = dir.toAngles();
proj->charge_fraction = 1.0f;
newspeed = real_speed;
if (real_speed == 0.0f) {
newspeed = proj->speed;
}
if (proj->addownervelocity) {
dot = DotProduct(owner->velocity, dir);
if (dot < 0) {
dot = 0;
}
}
proj->velocity = dir * (newspeed + dot);
proj->velocity += proj->addvelocity;
proj->setAngles(proj->angles);
proj->setSolidType(SOLID_BBOX);
proj->edict->clipmask = MASK_PROJECTILE;
proj->setSize(proj->mins, proj->maxs);
proj->setOrigin(start);
proj->origin.copyTo(proj->edict->s.origin2);
if (proj->m_beam) {
proj->m_beam->setOrigin(start);
proj->m_beam->origin.copyTo(proj->m_beam->edict->s.origin2);
}
if (proj->dlight_radius) {
G_SetConstantLight(
&proj->edict->s.constantLight,
&proj->dlight_color[0],
&proj->dlight_color[1],
&proj->dlight_color[2],
&proj->dlight_radius
);
}
// Calc the life of the projectile
if (proj->projFlags & P_CHARGE_LIFE) {
if (g_gametype->integer && proj->dmlife) {
newlife = proj->dmlife;
} else {
newlife = proj->life;
}
if (newlife < proj->minlife) {
newlife = proj->minlife;
}
} else {
if (g_gametype->integer && proj->dmlife) {
newlife = proj->dmlife;
} else {
newlife = proj->life;
}
}
// Remove the projectile after it's life expires
ev = new Event(EV_Projectile_Explode);
proj->PostEvent(ev, newlife);
proj->NewAnim("idle");
if (!g_gametype->integer) {
if (weap) {
weap->m_iNumShotsFired++;
if (owner->IsSubclassOfPlayer() && weap->IsSubclassOfTurretGun()) {
Player *p = (Player *)owner;
p->m_iNumShotsFired++;
}
}
}
return proj;
}
void ExplosionAttack(
Vector pos,
Entity *owner,
str explosionModel,
Vector dir,
Entity *ignore,
float scale,
Weapon *weap,
bool hurtOwnerOnly
)
{
Explosion *explosion;
Event *ev;
if (!owner) {
owner = world;
}
if (owner->IsDead() || owner == world) {
weap = NULL;
}
if (explosionModel.length() && gi.modeltiki(CanonicalTikiName(explosionModel))) {
SpawnArgs sp;
ClassDef *c;
sp.setArg("model", explosionModel);
c = sp.getClassDef();
if (c && c != Explosion::classinfostatic() && checkInheritance(Entity::classinfostatic(), c)) {
Entity *newent = static_cast<Entity *>(c->newInstance());
newent->setModel(explosionModel);
newent->setSolidType(SOLID_NOT);
newent->setOrigin(pos);
newent->setAngles(dir.toAngles());
newent->ProcessInitCommands();
if (c == SmokeGrenade::classinfostatic()) {
//
// try to set the owner of the smoke grenade
//
SmokeGrenade *smoke = static_cast<SmokeGrenade *>(newent);
if (owner && owner->IsSubclassOfSentient()) {
smoke->setOwner(static_cast<Sentient *>(owner));
}
}
} else {
explosion = new Explosion;
// Create a new explosion entity and set it off
explosion->setModel(explosionModel);
explosion->setSolidType(SOLID_NOT);
// Process the INIT commands right away
explosion->ProcessInitCommands();
explosion->SetOwner(owner);
explosion->edict->r.ownerNum = owner->entnum;
explosion->angles = dir.toAngles();
explosion->velocity = dir * explosion->speed;
explosion->edict->s.scale = scale;
explosion->setAngles(explosion->angles);
explosion->setMoveType(MOVETYPE_FLYMISSILE);
explosion->edict->clipmask = MASK_PROJECTILE;
explosion->setSize(explosion->mins, explosion->maxs);
explosion->setOrigin(pos);
explosion->origin.copyTo(explosion->edict->s.origin2);
explosion->hurtOwnerOnly = hurtOwnerOnly;
if (explosion->dlight_radius) {
G_SetConstantLight(
&explosion->edict->s.constantLight,
&explosion->dlight_color[0],
&explosion->dlight_color[1],
&explosion->dlight_color[2],
&explosion->dlight_radius
);
}
explosion->BroadcastAIEvent(AI_EVENT_WEAPON_FIRE);
explosion->NewAnim("idle");
RadiusDamage(
explosion->origin,
explosion,
owner,
explosion->radius_damage * scale,
ignore,
explosion->meansofdeath,
explosion->radius * scale,
explosion->knockback,
explosion->constant_damage,
weap,
explosion->hurtOwnerOnly
);
if (explosion->flash_radius) {
FlashPlayers(
explosion->origin,
explosion->flash_r,
explosion->flash_g,
explosion->flash_b,
explosion->flash_a,
explosion->flash_radius * scale,
explosion->flash_time,
explosion->flash_type
);
}
if (explosion->damage_every_frame) {
explosion->PostEvent(EV_Explosion_DamageAgain, FRAMETIME);
}
// Remove explosion after the life has expired
if (explosion->life || (g_gametype->integer && explosion->dmlife)) {
ev = new Event(EV_Remove);
if (g_gametype->integer && explosion->dmlife) {
explosion->PostEvent(ev, explosion->dmlife);
} else {
explosion->PostEvent(ev, explosion->life);
}
}
}
}
}
static float radiusdamage_origin[3];
static int radiusdamage_compare(const void *elem1, const void *elem2)
{
Entity *e1, *e2;
float delta[3];
float d1, d2;
e1 = *(Entity **)elem1;
e2 = *(Entity **)elem2;
VectorSubtract(radiusdamage_origin, e1->origin, delta);
d1 = VectorLengthSquared(delta);
VectorSubtract(radiusdamage_origin, e2->origin, delta);
d2 = VectorLengthSquared(delta);
if (d2 <= d1) {
return d1 > d2;
} else {
return -1;
}
}
void RadiusDamage(
Vector origin,
Entity *inflictor,
Entity *attacker,
float damage,
Entity *ignore,
int mod,
float radius,
float knockback,
qboolean constant_damage,
Weapon *weap,
bool hurtOwnerOnly
)
{
float points;
Entity *ent;
Vector org;
Vector dir;
float dist;
int i;
Container<Entity *> ents;
if (g_showdamage->integer) {
Com_Printf("radiusdamage");
Com_Printf("{\n");
Com_Printf("origin: %f %f %f\n", origin[0], origin[1], origin[2]);
if (inflictor) {
Com_Printf("inflictor: (entnum %d, radnum %d)\n", inflictor->entnum, inflictor->radnum);
}
if (attacker) {
Com_Printf("attacker: (entnum %d, radnum %d)\n", attacker->entnum, attacker->radnum);
}
Com_Printf("damage: %f\n", damage);
if (ignore) {
Com_Printf("ignore: (entnum %d, radnum %d)\n", ignore->entnum, ignore->radnum);
}
Com_Printf("mod: %d\n", mod);
Com_Printf("radius: %f\n", radius);
Com_Printf("knockback: %f\n", knockback);
Com_Printf("constant_damage: %d\n", constant_damage);
if (weap) {
Com_Printf("weapon %s\n", weap->getName().c_str());
}
Com_Printf("hurtOwnerOnly: %d\n", hurtOwnerOnly);
Com_Printf("}\n");
}
ent = findradius(NULL, origin, radius);
while (ent) {
// Add ents that has contents
if (ent->getContents()) {
ents.AddObject(ent);
}
ent = findradius(ent, origin, radius);
}
// Sort by the nearest to the fartest entity
if (ents.NumObjects()) {
VectorCopy(origin, radiusdamage_origin);
ents.Sort(radiusdamage_compare);
}
for (i = 1; i <= ents.NumObjects(); i++) {
ent = ents.ObjectAt(i);
if (ent == ignore || !(ent->takedamage) || (hurtOwnerOnly && ent != attacker)) {
continue;
}
// Add this in for deathmatch maybe
if (ent->getContents() == CONTENTS_CLAYPIDGEON
|| G_SightTrace(
origin, vec_zero, vec_zero, ent->centroid, inflictor, ent, MASK_EXPLOSION, false, "RadiusDamage"
)) {
if (constant_damage) {
points = damage;
} else {
float ent_rad;
ent_rad = fabs(ent->maxs[0] - ent->mins[0]);
if (ent_rad < fabs(ent->maxs[1] - ent->mins[1])) {
ent_rad = fabs(ent->maxs[1] - ent->mins[1]);
}
if (ent_rad < fabs(ent->maxs[2] - ent->mins[2])) {
ent_rad = fabs(ent->maxs[2] - ent->mins[2]);
}
org = ent->centroid;
dir = org - origin;
dist = dir.length() - ent_rad;
if (dist < 0.0f) {
dist = 0.0f;
}
points = damage - damage * (dist / radius);
knockback -= knockback * (dist / radius);
if (points < 0) {
points = 0;
}
if (knockback < 0) {
knockback = 0;
}
}
// reduce the damage a little for self-damage
if (ent == attacker) {
points *= 0.9f;
}
if (points > 0) {
ent->Damage(inflictor, attacker, points, org, dir, vec_zero, knockback, DAMAGE_RADIUS, mod);
if (g_gametype->integer == GT_SINGLE_PLAYER && weap) {
if (ent->IsSubclassOfPlayer() || ent->IsSubclassOfVehicle() || ent->IsSubclassOfVehicleTank()
|| ent->isSubclassOf(VehicleCollisionEntity)) {
weap->m_iNumHits++;
weap->m_iNumTorsoShots++;
if (attacker && attacker->IsSubclassOfPlayer() && weap->IsSubclassOfTurretGun()) {
Player *player = static_cast<Player *>(attacker);
player->m_iNumHits++;
player->m_iNumTorsoShots++;
}
}
}
}
}
}
if (mod == MOD_EXPLOSION) {
// Create an earthquake
new ViewJitter(
origin, radius + 128.0f, 0.05f, Vector(damage * 0.05f, damage * 0.05f, damage * 0.06f), 0, vec_zero, 0
);
}
}
void FlashPlayers(Vector org, float r, float g, float b, float a, float radius, float time, int type)
{
trace_t trace;
Vector delta;
float length;
Player *player;
gentity_t *ed;
int i;
Entity *ent;
float newa = 1;
for (i = 0; i < game.maxclients; i++) {
ed = &g_entities[i];
if (!ed->inuse || !ed->entity) {
continue;
}
ent = ed->entity;
if (!ent->IsSubclassOfPlayer()) {
continue;
}
player = (Player *)ent;
if (!player->WithinDistance(org, radius)) {
continue;
}
trace = G_Trace(org, vec_zero, vec_zero, player->origin, player, MASK_OPAQUE, qfalse, "FlashPlayers");
if (trace.fraction != 1.0) {
continue;
}
delta = org - trace.endpos;
length = delta.length();
// If alpha is specified, then modify it by the amount of distance away from the flash the player is
if (a != -1) {
newa = a * (1 - length / radius);
}
level.m_fade_alpha = newa;
level.m_fade_color[0] = r;
level.m_fade_color[1] = g;
level.m_fade_color[2] = b;
level.m_fade_time = time;
level.m_fade_time_start = time;
if (type == 1) {
level.m_fade_style = additive;
} else {
level.m_fade_style = alphablend;
}
// gi.SendServerCommand( NULL, va( "fadein %0.2f %0.2f %0.2f %0.2f %i",time*1000,r*newa,g*newa,b*newa,type ) );
}
}
const char *G_LocationNumToDispString(int iLocation)
{
switch (iLocation) {
case -2:
case -1:
return "";
case 0:
return "head";
case 1:
return "helmet";
case 2:
return "neck";
case 3:
return "upper torso";
case 4:
return "middle torso";
case 5:
return "lower torso";
case 6:
return "pelvis";
case 7:
return "upper right arm";
case 8:
return "upper left arm";
case 9:
return "upper right leg";
case 10:
return "upper left leg";
case 11:
return "lower right arm";
case 12:
return "lower left arm";
case 13:
return "lower right leg";
case 14:
return "lower left leg";
case 15:
return "right hand";
case 16:
return "left hand";
case 17:
return "right foot";
case 18:
return "left foot";
default:
return "";
}
}