You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
432 lines
11 KiB
432 lines
11 KiB
/* |
|
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_turret.c |
|
|
|
#include "g_local.h" |
|
|
|
|
|
void AnglesNormalize(vec3_t vec) |
|
{ |
|
while(vec[0] > 360) |
|
vec[0] -= 360; |
|
while(vec[0] < 0) |
|
vec[0] += 360; |
|
while(vec[1] > 360) |
|
vec[1] -= 360; |
|
while(vec[1] < 0) |
|
vec[1] += 360; |
|
} |
|
|
|
float SnapToEights(float x) |
|
{ |
|
x *= 8.0; |
|
if (x > 0.0) |
|
x += 0.5; |
|
else |
|
x -= 0.5; |
|
return 0.125 * (int)x; |
|
} |
|
|
|
|
|
void turret_blocked(edict_t *self, edict_t *other) |
|
{ |
|
edict_t *attacker; |
|
|
|
if (other->takedamage) |
|
{ |
|
if (self->teammaster->owner) |
|
attacker = self->teammaster->owner; |
|
else |
|
attacker = self->teammaster; |
|
T_Damage (other, self, attacker, vec3_origin, other->s.origin, vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH); |
|
} |
|
} |
|
|
|
/*QUAKED turret_breach (0 0 0) ? |
|
This portion of the turret can change both pitch and yaw. |
|
The model should be made with a flat pitch. |
|
It (and the associated base) need to be oriented towards 0. |
|
Use "angle" to set the starting angle. |
|
|
|
"speed" default 50 |
|
"dmg" default 10 |
|
"angle" point this forward |
|
"target" point this at an info_notnull at the muzzle tip |
|
"minpitch" min acceptable pitch angle : default -30 |
|
"maxpitch" max acceptable pitch angle : default 30 |
|
"minyaw" min acceptable yaw angle : default 0 |
|
"maxyaw" max acceptable yaw angle : default 360 |
|
*/ |
|
|
|
void turret_breach_fire (edict_t *self) |
|
{ |
|
vec3_t f, r, u; |
|
vec3_t start; |
|
int damage; |
|
int speed; |
|
|
|
AngleVectors (self->s.angles, f, r, u); |
|
VectorMA (self->s.origin, self->move_origin[0], f, start); |
|
VectorMA (start, self->move_origin[1], r, start); |
|
VectorMA (start, self->move_origin[2], u, start); |
|
|
|
damage = 100 + random() * 50; |
|
speed = 550 + 50 * skill->value; |
|
fire_rocket (self->teammaster->owner, start, f, damage, speed, 150, damage); |
|
gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0); |
|
} |
|
|
|
void turret_breach_think (edict_t *self) |
|
{ |
|
edict_t *ent; |
|
vec3_t current_angles; |
|
vec3_t delta; |
|
|
|
VectorCopy (self->s.angles, current_angles); |
|
AnglesNormalize(current_angles); |
|
|
|
AnglesNormalize(self->move_angles); |
|
if (self->move_angles[PITCH] > 180) |
|
self->move_angles[PITCH] -= 360; |
|
|
|
// clamp angles to mins & maxs |
|
if (self->move_angles[PITCH] > self->pos1[PITCH]) |
|
self->move_angles[PITCH] = self->pos1[PITCH]; |
|
else if (self->move_angles[PITCH] < self->pos2[PITCH]) |
|
self->move_angles[PITCH] = self->pos2[PITCH]; |
|
|
|
if ((self->move_angles[YAW] < self->pos1[YAW]) || (self->move_angles[YAW] > self->pos2[YAW])) |
|
{ |
|
float dmin, dmax; |
|
|
|
dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]); |
|
if (dmin < -180) |
|
dmin += 360; |
|
else if (dmin > 180) |
|
dmin -= 360; |
|
dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]); |
|
if (dmax < -180) |
|
dmax += 360; |
|
else if (dmax > 180) |
|
dmax -= 360; |
|
if (fabs(dmin) < fabs(dmax)) |
|
self->move_angles[YAW] = self->pos1[YAW]; |
|
else |
|
self->move_angles[YAW] = self->pos2[YAW]; |
|
} |
|
|
|
VectorSubtract (self->move_angles, current_angles, delta); |
|
if (delta[0] < -180) |
|
delta[0] += 360; |
|
else if (delta[0] > 180) |
|
delta[0] -= 360; |
|
if (delta[1] < -180) |
|
delta[1] += 360; |
|
else if (delta[1] > 180) |
|
delta[1] -= 360; |
|
delta[2] = 0; |
|
|
|
if (delta[0] > self->speed * FRAMETIME) |
|
delta[0] = self->speed * FRAMETIME; |
|
if (delta[0] < -1 * self->speed * FRAMETIME) |
|
delta[0] = -1 * self->speed * FRAMETIME; |
|
if (delta[1] > self->speed * FRAMETIME) |
|
delta[1] = self->speed * FRAMETIME; |
|
if (delta[1] < -1 * self->speed * FRAMETIME) |
|
delta[1] = -1 * self->speed * FRAMETIME; |
|
|
|
VectorScale (delta, 1.0/FRAMETIME, self->avelocity); |
|
|
|
self->nextthink = level.time + FRAMETIME; |
|
|
|
for (ent = self->teammaster; ent; ent = ent->teamchain) |
|
ent->avelocity[1] = self->avelocity[1]; |
|
|
|
// if we have adriver, adjust his velocities |
|
if (self->owner) |
|
{ |
|
float angle; |
|
float target_z; |
|
float diff; |
|
vec3_t target; |
|
vec3_t dir; |
|
|
|
// angular is easy, just copy ours |
|
self->owner->avelocity[0] = self->avelocity[0]; |
|
self->owner->avelocity[1] = self->avelocity[1]; |
|
|
|
// x & y |
|
angle = self->s.angles[1] + self->owner->move_origin[1]; |
|
angle *= (M_PI*2 / 360); |
|
target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]); |
|
target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]); |
|
target[2] = self->owner->s.origin[2]; |
|
|
|
VectorSubtract (target, self->owner->s.origin, dir); |
|
self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME; |
|
self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME; |
|
|
|
// z |
|
angle = self->s.angles[PITCH] * (M_PI*2 / 360); |
|
target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]); |
|
|
|
diff = target_z - self->owner->s.origin[2]; |
|
self->owner->velocity[2] = diff * 1.0 / FRAMETIME; |
|
|
|
if (self->spawnflags & 65536) |
|
{ |
|
turret_breach_fire (self); |
|
self->spawnflags &= ~65536; |
|
} |
|
} |
|
} |
|
|
|
void turret_breach_finish_init (edict_t *self) |
|
{ |
|
// get and save info for muzzle location |
|
if (!self->target) |
|
{ |
|
gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin)); |
|
} |
|
else |
|
{ |
|
self->target_ent = G_PickTarget (self->target); |
|
VectorSubtract (self->target_ent->s.origin, self->s.origin, self->move_origin); |
|
G_FreeEdict(self->target_ent); |
|
} |
|
|
|
self->teammaster->dmg = self->dmg; |
|
self->think = turret_breach_think; |
|
self->think (self); |
|
} |
|
|
|
void SP_turret_breach (edict_t *self) |
|
{ |
|
self->solid = SOLID_BSP; |
|
self->movetype = MOVETYPE_PUSH; |
|
gi.setmodel (self, self->model); |
|
|
|
if (!self->speed) |
|
self->speed = 50; |
|
if (!self->dmg) |
|
self->dmg = 10; |
|
|
|
if (!st.minpitch) |
|
st.minpitch = -30; |
|
if (!st.maxpitch) |
|
st.maxpitch = 30; |
|
if (!st.maxyaw) |
|
st.maxyaw = 360; |
|
|
|
self->pos1[PITCH] = -1 * st.minpitch; |
|
self->pos1[YAW] = st.minyaw; |
|
self->pos2[PITCH] = -1 * st.maxpitch; |
|
self->pos2[YAW] = st.maxyaw; |
|
|
|
self->ideal_yaw = self->s.angles[YAW]; |
|
self->move_angles[YAW] = self->ideal_yaw; |
|
|
|
self->blocked = turret_blocked; |
|
|
|
self->think = turret_breach_finish_init; |
|
self->nextthink = level.time + FRAMETIME; |
|
gi.linkentity (self); |
|
} |
|
|
|
|
|
/*QUAKED turret_base (0 0 0) ? |
|
This portion of the turret changes yaw only. |
|
MUST be teamed with a turret_breach. |
|
*/ |
|
|
|
void SP_turret_base (edict_t *self) |
|
{ |
|
self->solid = SOLID_BSP; |
|
self->movetype = MOVETYPE_PUSH; |
|
gi.setmodel (self, self->model); |
|
self->blocked = turret_blocked; |
|
gi.linkentity (self); |
|
} |
|
|
|
|
|
/*QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32) |
|
Must NOT be on the team with the rest of the turret parts. |
|
Instead it must target the turret_breach. |
|
*/ |
|
|
|
void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage); |
|
void infantry_stand (edict_t *self); |
|
void monster_use (edict_t *self, edict_t *other, edict_t *activator); |
|
|
|
void turret_driver_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) |
|
{ |
|
edict_t *ent; |
|
|
|
// level the gun |
|
self->target_ent->move_angles[0] = 0; |
|
|
|
// remove the driver from the end of them team chain |
|
for (ent = self->target_ent->teammaster; ent->teamchain != self; ent = ent->teamchain) |
|
; |
|
ent->teamchain = NULL; |
|
self->teammaster = NULL; |
|
self->flags &= ~FL_TEAMSLAVE; |
|
|
|
self->target_ent->owner = NULL; |
|
self->target_ent->teammaster->owner = NULL; |
|
|
|
infantry_die (self, inflictor, attacker, damage); |
|
} |
|
|
|
qboolean FindTarget (edict_t *self); |
|
|
|
void turret_driver_think (edict_t *self) |
|
{ |
|
vec3_t target; |
|
vec3_t dir; |
|
float reaction_time; |
|
|
|
self->nextthink = level.time + FRAMETIME; |
|
|
|
if (self->enemy && (!self->enemy->inuse || self->enemy->health <= 0)) |
|
self->enemy = NULL; |
|
|
|
if (!self->enemy) |
|
{ |
|
if (!FindTarget (self)) |
|
return; |
|
self->monsterinfo.trail_time = level.time; |
|
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; |
|
} |
|
else |
|
{ |
|
if (visible (self, self->enemy)) |
|
{ |
|
if (self->monsterinfo.aiflags & AI_LOST_SIGHT) |
|
{ |
|
self->monsterinfo.trail_time = level.time; |
|
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; |
|
} |
|
} |
|
else |
|
{ |
|
self->monsterinfo.aiflags |= AI_LOST_SIGHT; |
|
return; |
|
} |
|
} |
|
|
|
// let the turret know where we want it to aim |
|
VectorCopy (self->enemy->s.origin, target); |
|
target[2] += self->enemy->viewheight; |
|
VectorSubtract (target, self->target_ent->s.origin, dir); |
|
vectoangles (dir, self->target_ent->move_angles); |
|
|
|
// decide if we should shoot |
|
if (level.time < self->monsterinfo.attack_finished) |
|
return; |
|
|
|
reaction_time = (3 - skill->value) * 1.0; |
|
if ((level.time - self->monsterinfo.trail_time) < reaction_time) |
|
return; |
|
|
|
self->monsterinfo.attack_finished = level.time + reaction_time + 1.0; |
|
//FIXME how do we really want to pass this along? |
|
self->target_ent->spawnflags |= 65536; |
|
} |
|
|
|
void turret_driver_link (edict_t *self) |
|
{ |
|
vec3_t vec; |
|
edict_t *ent; |
|
|
|
self->think = turret_driver_think; |
|
self->nextthink = level.time + FRAMETIME; |
|
|
|
self->target_ent = G_PickTarget (self->target); |
|
self->target_ent->owner = self; |
|
self->target_ent->teammaster->owner = self; |
|
VectorCopy (self->target_ent->s.angles, self->s.angles); |
|
|
|
vec[0] = self->target_ent->s.origin[0] - self->s.origin[0]; |
|
vec[1] = self->target_ent->s.origin[1] - self->s.origin[1]; |
|
vec[2] = 0; |
|
self->move_origin[0] = VectorLength(vec); |
|
|
|
VectorSubtract (self->s.origin, self->target_ent->s.origin, vec); |
|
vectoangles (vec, vec); |
|
AnglesNormalize(vec); |
|
self->move_origin[1] = vec[1]; |
|
|
|
self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2]; |
|
|
|
// add the driver to the end of them team chain |
|
for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain) |
|
; |
|
ent->teamchain = self; |
|
self->teammaster = self->target_ent->teammaster; |
|
self->flags |= FL_TEAMSLAVE; |
|
} |
|
|
|
void SP_turret_driver (edict_t *self) |
|
{ |
|
if (deathmatch->value) |
|
{ |
|
G_FreeEdict (self); |
|
return; |
|
} |
|
|
|
self->movetype = MOVETYPE_PUSH; |
|
self->solid = SOLID_BBOX; |
|
self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2"); |
|
VectorSet (self->mins, -16, -16, -24); |
|
VectorSet (self->maxs, 16, 16, 32); |
|
|
|
self->health = 100; |
|
self->gib_health = 0; |
|
self->mass = 200; |
|
self->viewheight = 24; |
|
|
|
self->die = turret_driver_die; |
|
self->monsterinfo.stand = infantry_stand; |
|
|
|
self->flags |= FL_NO_KNOCKBACK; |
|
|
|
level.total_monsters++; |
|
|
|
self->svflags |= SVF_MONSTER; |
|
self->s.renderfx |= RF_FRAMELERP; |
|
self->takedamage = DAMAGE_AIM; |
|
self->use = monster_use; |
|
self->clipmask = MASK_MONSTERSOLID; |
|
VectorCopy (self->s.origin, self->s.old_origin); |
|
self->monsterinfo.aiflags |= AI_STAND_GROUND|AI_DUCKED; |
|
|
|
if (st.item) |
|
{ |
|
self->item = FindItemByClassname (st.item); |
|
if (!self->item) |
|
gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item); |
|
} |
|
|
|
self->think = turret_driver_link; |
|
self->nextthink = level.time + FRAMETIME; |
|
|
|
gi.linkentity (self); |
|
}
|
|
|