/* * 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" * oint 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); }