File: game\g_turret.c

    1 /*
    2 Copyright (C) 1997-2001 Id Software, Inc.
    3 
    4 This program is free software; you can redistribute it and/or
    5 modify it under the terms of the GNU General Public License
    6 as published by the Free Software Foundation; either version 2
    7 of the License, or (at your option) any later version.
    8 
    9 This program is distributed in the hope that it will be useful,
   10 but WITHOUT ANY WARRANTY; without even the implied warranty of
   11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
   12 
   13 See the GNU General Public License for more details.
   14 
   15 You should have received a copy of the GNU General Public License
   16 along with this program; if not, write to the Free Software
   17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   18 
   19 */
   20 // g_turret.c
   21 
   22 #include "g_local.h"
   23 
   24 
   25 void AnglesNormalize(vec3_t vec)
   26 {
   27         while(vec[0] > 360)
   28                 vec[0] -= 360;
   29         while(vec[0] < 0)
   30                 vec[0] += 360;
   31         while(vec[1] > 360)
   32                 vec[1] -= 360;
   33         while(vec[1] < 0)
   34                 vec[1] += 360;
   35 }
   36 
   37 float SnapToEights(float x)
   38 {
   39         x *= 8.0;
   40         if (x > 0.0)
   41                 x += 0.5;
   42         else
   43                 x -= 0.5;
   44         return 0.125 * (int)x;
   45 }
   46 
   47 
   48 void turret_blocked(edict_t *self, edict_t *other)
   49 {
   50         edict_t *attacker;
   51 
   52         if (other->takedamage)
   53         {
   54                 if (self->teammaster->owner)
   55                         attacker = self->teammaster->owner;
   56                 else
   57                         attacker = self->teammaster;
   58                 T_Damage (other, self, attacker, vec3_origin, other->s.origin, vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH);
   59         }
   60 }
   61 
   62 /*QUAKED turret_breach (0 0 0) ?
   63 This portion of the turret can change both pitch and yaw.
   64 The model  should be made with a flat pitch.
   65 It (and the associated base) need to be oriented towards 0.
   66 Use "angle" to set the starting angle.
   67 
   68 "speed"         default 50
   69 "dmg"           default 10
   70 "angle"         point this forward
   71 "target"        point this at an info_notnull at the muzzle tip
   72 "minpitch"      min acceptable pitch angle : default -30
   73 "maxpitch"      max acceptable pitch angle : default 30
   74 "minyaw"        min acceptable yaw angle   : default 0
   75 "maxyaw"        max acceptable yaw angle   : default 360
   76 */
   77 
   78 void turret_breach_fire (edict_t *self)
   79 {
   80         vec3_t  f, r, u;
   81         vec3_t  start;
   82         int             damage;
   83         int             speed;
   84 
   85         AngleVectors (self->s.angles, f, r, u);
   86         VectorMA (self->s.origin, self->move_origin[0], f, start);
   87         VectorMA (start, self->move_origin[1], r, start);
   88         VectorMA (start, self->move_origin[2], u, start);
   89 
   90         damage = 100 + random() * 50;
   91         speed = 550 + 50 * skill->value;
   92         fire_rocket (self->teammaster->owner, start, f, damage, speed, 150, damage);
   93         gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
   94 }
   95 
   96 void turret_breach_think (edict_t *self)
   97 {
   98         edict_t *ent;
   99         vec3_t  current_angles;
  100         vec3_t  delta;
  101 
  102         VectorCopy (self->s.angles, current_angles);
  103         AnglesNormalize(current_angles);
  104 
  105         AnglesNormalize(self->move_angles);
  106         if (self->move_angles[PITCH] > 180)
  107                 self->move_angles[PITCH] -= 360;
  108 
  109         // clamp angles to mins & maxs
  110         if (self->move_angles[PITCH] > self->pos1[PITCH])
  111                 self->move_angles[PITCH] = self->pos1[PITCH];
  112         else if (self->move_angles[PITCH] < self->pos2[PITCH])
  113                 self->move_angles[PITCH] = self->pos2[PITCH];
  114 
  115         if ((self->move_angles[YAW] < self->pos1[YAW]) || (self->move_angles[YAW] > self->pos2[YAW]))
  116         {
  117                 float   dmin, dmax;
  118 
  119                 dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]);
  120                 if (dmin < -180)
  121                         dmin += 360;
  122                 else if (dmin > 180)
  123                         dmin -= 360;
  124                 dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]);
  125                 if (dmax < -180)
  126                         dmax += 360;
  127                 else if (dmax > 180)
  128                         dmax -= 360;
  129                 if (fabs(dmin) < fabs(dmax))
  130                         self->move_angles[YAW] = self->pos1[YAW];
  131                 else
  132                         self->move_angles[YAW] = self->pos2[YAW];
  133         }
  134 
  135         VectorSubtract (self->move_angles, current_angles, delta);
  136         if (delta[0] < -180)
  137                 delta[0] += 360;
  138         else if (delta[0] > 180)
  139                 delta[0] -= 360;
  140         if (delta[1] < -180)
  141                 delta[1] += 360;
  142         else if (delta[1] > 180)
  143                 delta[1] -= 360;
  144         delta[2] = 0;
  145 
  146         if (delta[0] > self->speed * FRAMETIME)
  147                 delta[0] = self->speed * FRAMETIME;
  148         if (delta[0] < -1 * self->speed * FRAMETIME)
  149                 delta[0] = -1 * self->speed * FRAMETIME;
  150         if (delta[1] > self->speed * FRAMETIME)
  151                 delta[1] = self->speed * FRAMETIME;
  152         if (delta[1] < -1 * self->speed * FRAMETIME)
  153                 delta[1] = -1 * self->speed * FRAMETIME;
  154 
  155         VectorScale (delta, 1.0/FRAMETIME, self->avelocity);
  156 
  157         self->nextthink = level.time + FRAMETIME;
  158 
  159         for (ent = self->teammaster; ent; ent = ent->teamchain)
  160                 ent->avelocity[1] = self->avelocity[1];
  161 
  162         // if we have adriver, adjust his velocities
  163         if (self->owner)
  164         {
  165                 float   angle;
  166                 float   target_z;
  167                 float   diff;
  168                 vec3_t  target;
  169                 vec3_t  dir;
  170 
  171                 // angular is easy, just copy ours
  172                 self->owner->avelocity[0] = self->avelocity[0];
  173                 self->owner->avelocity[1] = self->avelocity[1];
  174 
  175                 // x & y
  176                 angle = self->s.angles[1] + self->owner->move_origin[1];
  177                 angle *= (M_PI*2 / 360);
  178                 target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]);
  179                 target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]);
  180                 target[2] = self->owner->s.origin[2];
  181 
  182                 VectorSubtract (target, self->owner->s.origin, dir);
  183                 self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME;
  184                 self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME;
  185 
  186                 // z
  187                 angle = self->s.angles[PITCH] * (M_PI*2 / 360);
  188                 target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]);
  189 
  190                 diff = target_z - self->owner->s.origin[2];
  191                 self->owner->velocity[2] = diff * 1.0 / FRAMETIME;
  192 
  193                 if (self->spawnflags & 65536)
  194                 {
  195                         turret_breach_fire (self);
  196                         self->spawnflags &= ~65536;
  197                 }
  198         }
  199 }
  200 
  201 void turret_breach_finish_init (edict_t *self)
  202 {
  203         // get and save info for muzzle location
  204         if (!self->target)
  205         {
  206                 gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin));
  207         }
  208         else
  209         {
  210                 self->target_ent = G_PickTarget (self->target);
  211                 VectorSubtract (self->target_ent->s.origin, self->s.origin, self->move_origin);
  212                 G_FreeEdict(self->target_ent);
  213         }
  214 
  215         self->teammaster->dmg = self->dmg;
  216         self->think = turret_breach_think;
  217         self->think (self);
  218 }
  219 
  220 void SP_turret_breach (edict_t *self)
  221 {
  222         self->solid = SOLID_BSP;
  223         self->movetype = MOVETYPE_PUSH;
  224         gi.setmodel (self, self->model);
  225 
  226         if (!self->speed)
  227                 self->speed = 50;
  228         if (!self->dmg)
  229                 self->dmg = 10;
  230 
  231         if (!st.minpitch)
  232                 st.minpitch = -30;
  233         if (!st.maxpitch)
  234                 st.maxpitch = 30;
  235         if (!st.maxyaw)
  236                 st.maxyaw = 360;
  237 
  238         self->pos1[PITCH] = -1 * st.minpitch;
  239         self->pos1[YAW]   = st.minyaw;
  240         self->pos2[PITCH] = -1 * st.maxpitch;
  241         self->pos2[YAW]   = st.maxyaw;
  242 
  243         self->ideal_yaw = self->s.angles[YAW];
  244         self->move_angles[YAW] = self->ideal_yaw;
  245 
  246         self->blocked = turret_blocked;
  247 
  248         self->think = turret_breach_finish_init;
  249         self->nextthink = level.time + FRAMETIME;
  250         gi.linkentity (self);
  251 }
  252 
  253 
  254 /*QUAKED turret_base (0 0 0) ?
  255 This portion of the turret changes yaw only.
  256 MUST be teamed with a turret_breach.
  257 */
  258 
  259 void SP_turret_base (edict_t *self)
  260 {
  261         self->solid = SOLID_BSP;
  262         self->movetype = MOVETYPE_PUSH;
  263         gi.setmodel (self, self->model);
  264         self->blocked = turret_blocked;
  265         gi.linkentity (self);
  266 }
  267 
  268 
  269 /*QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32)
  270 Must NOT be on the team with the rest of the turret parts.
  271 Instead it must target the turret_breach.
  272 */
  273 
  274 void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage);
  275 void infantry_stand (edict_t *self);
  276 void monster_use (edict_t *self, edict_t *other, edict_t *activator);
  277 
  278 void turret_driver_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
  279 {
  280         edict_t *ent;
  281 
  282         // level the gun
  283         self->target_ent->move_angles[0] = 0;
  284 
  285         // remove the driver from the end of them team chain
  286         for (ent = self->target_ent->teammaster; ent->teamchain != self; ent = ent->teamchain)
  287                 ;
  288         ent->teamchain = NULL;
  289         self->teammaster = NULL;
  290         self->flags &= ~FL_TEAMSLAVE;
  291 
  292         self->target_ent->owner = NULL;
  293         self->target_ent->teammaster->owner = NULL;
  294 
  295         infantry_die (self, inflictor, attacker, damage);
  296 }
  297 
  298 qboolean FindTarget (edict_t *self);
  299 
  300 void turret_driver_think (edict_t *self)
  301 {
  302         vec3_t  target;
  303         vec3_t  dir;
  304         float   reaction_time;
  305 
  306         self->nextthink = level.time + FRAMETIME;
  307 
  308         if (self->enemy && (!self->enemy->inuse || self->enemy->health <= 0))
  309                 self->enemy = NULL;
  310 
  311         if (!self->enemy)
  312         {
  313                 if (!FindTarget (self))
  314                         return;
  315                 self->monsterinfo.trail_time = level.time;
  316                 self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
  317         }
  318         else
  319         {
  320                 if (visible (self, self->enemy))
  321                 {
  322                         if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
  323                         {
  324                                 self->monsterinfo.trail_time = level.time;
  325                                 self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
  326                         }
  327                 }
  328                 else
  329                 {
  330                         self->monsterinfo.aiflags |= AI_LOST_SIGHT;
  331                         return;
  332                 }
  333         }
  334 
  335         // let the turret know where we want it to aim
  336         VectorCopy (self->enemy->s.origin, target);
  337         target[2] += self->enemy->viewheight;
  338         VectorSubtract (target, self->target_ent->s.origin, dir);
  339         vectoangles (dir, self->target_ent->move_angles);
  340 
  341         // decide if we should shoot
  342         if (level.time < self->monsterinfo.attack_finished)
  343                 return;
  344 
  345         reaction_time = (3 - skill->value) * 1.0;
  346         if ((level.time - self->monsterinfo.trail_time) < reaction_time)
  347                 return;
  348 
  349         self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
  350         //FIXME how do we really want to pass this along?
  351         self->target_ent->spawnflags |= 65536;
  352 }
  353 
  354 void turret_driver_link (edict_t *self)
  355 {
  356         vec3_t  vec;
  357         edict_t *ent;
  358 
  359         self->think = turret_driver_think;
  360         self->nextthink = level.time + FRAMETIME;
  361 
  362         self->target_ent = G_PickTarget (self->target);
  363         self->target_ent->owner = self;
  364         self->target_ent->teammaster->owner = self;
  365         VectorCopy (self->target_ent->s.angles, self->s.angles);
  366 
  367         vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
  368         vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
  369         vec[2] = 0;
  370         self->move_origin[0] = VectorLength(vec);
  371 
  372         VectorSubtract (self->s.origin, self->target_ent->s.origin, vec);
  373         vectoangles (vec, vec);
  374         AnglesNormalize(vec);
  375         self->move_origin[1] = vec[1];
  376 
  377         self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
  378 
  379         // add the driver to the end of them team chain
  380         for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
  381                 ;
  382         ent->teamchain = self;
  383         self->teammaster = self->target_ent->teammaster;
  384         self->flags |= FL_TEAMSLAVE;
  385 }
  386 
  387 void SP_turret_driver (edict_t *self)
  388 {
  389         if (deathmatch->value)
  390         {
  391                 G_FreeEdict (self);
  392                 return;
  393         }
  394 
  395         self->movetype = MOVETYPE_PUSH;
  396         self->solid = SOLID_BBOX;
  397         self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
  398         VectorSet (self->mins, -16, -16, -24);
  399         VectorSet (self->maxs, 16, 16, 32);
  400 
  401         self->health = 100;
  402         self->gib_health = 0;
  403         self->mass = 200;
  404         self->viewheight = 24;
  405 
  406         self->die = turret_driver_die;
  407         self->monsterinfo.stand = infantry_stand;
  408 
  409         self->flags |= FL_NO_KNOCKBACK;
  410 
  411         level.total_monsters++;
  412 
  413         self->svflags |= SVF_MONSTER;
  414         self->s.renderfx |= RF_FRAMELERP;
  415         self->takedamage = DAMAGE_AIM;
  416         self->use = monster_use;
  417         self->clipmask = MASK_MONSTERSOLID;
  418         VectorCopy (self->s.origin, self->s.old_origin);
  419         self->monsterinfo.aiflags |= AI_STAND_GROUND|AI_DUCKED;
  420 
  421         if (st.item)
  422         {
  423                 self->item = FindItemByClassname (st.item);
  424                 if (!self->item)
  425                         gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
  426         }
  427 
  428         self->think = turret_driver_link;
  429         self->nextthink = level.time + FRAMETIME;
  430 
  431         gi.linkentity (self);
  432 }
  433