/* * acm : an aerial combat simulator for X * Copyright (C) 1991-1997 Riley Rainey * * 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; version 2 dated June, 1991. * * 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., 675 Mass Ave., Cambridge, MA 02139, USA. */ #include #include #include #include "pm.h" #ifdef HAVE_DIS #include "dis.h" #endif #define BORE_CROSS_SIZE 7 #define MUZZLE_VELOCITY (3.2808 * 1036.0) #define TRACER_MOD 10 #define OFFSET_ANGLE (DEGtoRAD(6.0)) /* the gun points up at this angle */ #define RATE_OF_FIRE (3000.0 / 60.0) /* rounds per second */ #define FUZZ (DEGtoRAD(1.0)) /* distribution due to vibration */ int select_m61a1(craft *); int display_m61a1(craft *, craftType *, viewer *, int *, int *); int update_m61a1(craft *); int press_m61a1(craft *); int release_m61a1(craft *); extern FILE *acm_fopen(char *name, char *access); extern int acm_rand PARAMS((void)); void lcos(craft * c, viewer * u, int *x, int *y); extern int dis_fire_cannon(craft * c, VPoint * pos, VPoint * vel, int quantity, int rate); int isCannonHit(double min, craft * c); int cdebug = 0; /* set to 1 to debug cannon fire */ weaponDesc m61a1Desc = { WK_M61A1, select_m61a1, /* select */ update_m61a1, /* update */ display_m61a1, /* display procedure */ press_m61a1, /* fire */ release_m61a1, /* fire button release */ }; /* * We'll take a running average of our pitch and yaw rates to keep the * aiming reticle from jumping all over the screen. */ #define HIST 8 typedef struct { double pitch_hist[HIST]; double yaw_hist[HIST]; int cur; /* current entry in the histories */ int count; /* number of valid entries */ double pitch_total; double yaw_total; } hist_t; static hist_t history[MAXPLAYERS]; /* * m61a1 selection function * * A selection function normally determines whether there are any weapons * of this type on-board. If so, and the weapon system is functional * (in other words, undamaged) then return 1; otherwise return 0. */ int select_m61a1(craft * c) { hist_t *p; p = &history[c->pIndex]; p->count = p->cur = 0; p->pitch_total = p->yaw_total = 0.0; /* * The cannon must be located at station zero, for now. We should * change that. */ if (strcmp(c->station[0].type, "m61a1") == 0 && c->station[0].info > 0) { c->station[0].info4 = 0.0; return 1; } else return 0; } /* * m61a1 display function * * Update the HUD display strings associated with this weapon system. */ /*ARGSUSED */ int display_m61a1(craft * c, craftType * w, viewer * u, int *x, int *y) { char s[16]; register int tx, ty, m; XSegment seg[2]; hist_t *p; p = &history[c->pIndex]; if ((m = p->cur = p->cur + 1) >= HIST) m = p->cur = 0; if (p->count != HIST) (p->count)++; else { p->pitch_total -= p->pitch_hist[m]; p->yaw_total -= p->yaw_hist[m]; } p->pitch_hist[m] = c->q; p->yaw_hist[m] = c->r; p->pitch_total += c->q; p->yaw_total += c->r; strcpy(c->leftHUD[2], "LCOS"); sprintf(s, "%.3d M-61A1", c->station[0].info); strcpy(c->leftHUD[3], s); if (c->station[0].info3) strcpy(c->leftHUD[4], "FIRING"); else strcpy(c->leftHUD[4], ""); /* * Draw the boresight cross */ m = (int) ((double) BORE_CROSS_SIZE * u->scaleFactor); tx = u->xCenter; ty = u->yCenter - (int) (3.0 * 9.0 * u->scaleFactor + 0.5); seg[0].x1 = tx - m; seg[0].x2 = tx + m; seg[0].y1 = seg[0].y2 = ty; seg[1].x1 = seg[1].x2 = tx; seg[1].y1 = ty - m; seg[1].y2 = ty + m; VDrawSegments(u->v, seg, 2, (Color) u->v->pixel[HUDPixel]); /* * Plot the reticle. */ lcos(c, u, x, y); return 0; } int press_m61a1(craft * c) { c->station[0].info3 |= 1; if (c->station[0].info > 0) { playContinuousSound(c, SoundCannonFiring); } return 0; } int release_m61a1(craft * c) { c->station[0].info3 &= ~1; c->station[0].info4 = 0.0; stopSound(c, SoundCannonFiring); return 0; } int update_m61a1(craft * c) { craft *m; int i; VPoint tmp, tmp1, mvel; double fuzz, fuzzAngle, phiY, phiZ, tm, mv = MUZZLE_VELOCITY; if (c->station[0].info3 == 0) return 0; /* * Got any ammunition ? */ if (c->station[0].info <= 0) return -1; /* * It is possible (especially on today's faster machines) that no rounds * actually left the barrel during this time interval. */ if (c->station[0].info4 > deltaT) { if (cdebug) { printf("no rounds interval offset = %.3f, deltaT = %.3f\n", c->station[0].info4, deltaT); } c->station[0].info4 -= deltaT; } else { /* * Allocate a projectile record */ for ((i = 0, m = &mtbl[0]); i < MAXPROJECTILES; (++i, ++m)) if (m->type == CT_FREE) { m->type = CT_CANNON; break; } if (i == MAXPROJECTILES) return -1; /* * Determine how far we're fuzzed off the ideal boresight. */ fuzz = ((double) (acm_rand() & 32767) + (double) (acm_rand() & 32767)) / 32767.0 - 1.0; fuzz = fuzz * FUZZ; fuzzAngle = 2.0 * pi * (double) (acm_rand() & 511) / 511.0; /* * Determine the initial velocity of the projectile stream. */ phiZ = fuzz * sin(fuzzAngle); phiY = fuzz * cos(fuzzAngle); tm = mv * cos(OFFSET_ANGLE + phiZ); tmp.x = tm * cos(phiY); tmp.y = tm * sin(phiY); tmp.z = mv * sin(-OFFSET_ANGLE + phiZ); VTransform(&tmp, &(c->trihedral), &mvel); m->owner = c->pIndex; m->createTime = curTime; m->gvs_instance = (GVS_OBI) NULL; m->curRoll = c->curRoll; m->curPitch = c->curPitch; m->curHeading = c->curHeading; m->Cg = c->Cg; m->Cg.x += mvel.x; m->Cg.y += mvel.y; m->Cg.z += mvel.z; /* * Determine the initial position. */ VTransform_(&c->cinfo->wStation[0], &c->trihedral, &tmp1); VReverseTransform_(&tmp1, &c->XYZtoNED, &tmp); m->Sg.x = c->Sg.x + FEETtoMETERS(tmp.x); m->Sg.y = c->Sg.y + FEETtoMETERS(tmp.y); m->Sg.z = c->Sg.z + FEETtoMETERS(tmp.z); m->prevSg = m->Sg; DISGeocentricToWorldCoordinates ((dis_world_coordinates *) & m->Sg, &m->w); m->prevw = m->w; GenerateWorldToLocalMatrix(&m->w, &m->XYZtoNED); /* * Subtract the number of rounds fired. */ m->offset = c->station[0].info4; m->interval = deltaT; m->rounds = 1 + (int) (RATE_OF_FIRE * (deltaT - m->offset)); m->rounds = (m->rounds > c->station[0].info) ? c->station[0].info : m->rounds; if (cdebug) { printf("rounds = %d, offset = %.3f sec, interval = %.4f\n", m->rounds, m->offset, m->interval); } /* * Compute time interval until the next round leaves the barrel (in a * subsequent time interval). */ c->station[0].info4 = 1.0 / RATE_OF_FIRE - fmod(deltaT - m->offset, 1.0 / RATE_OF_FIRE); /* * include tracer information */ m->tracerMod = TRACER_MOD; m->tracerVal = c->station[0].info2 % m->tracerMod; if (arcadeMode == 0) { c->station[0].info -= m->rounds; } c->station[0].info2 += m->rounds; m->cinfo = lookupCraft("m61a1 cannon"); #ifdef HAVE_DIS dis_fire_cannon(c, &m->Sg, &m->Cg, m->rounds, (int) RATE_OF_FIRE); #endif } if (c->station[0].info <= 0) { stopSound(c, SoundCannonFiring); } return 0; } extern craftType *newCraft(void); int placeCannon(Viewport * vp, craft * c, VMatrix * m, VPolygon ** poly, long *cnt) { double t, startT; VPoint v, s; int i, j, k, n; VPoint *q, tmp; VPolygon **p; /* * Reduce the shell path to a set of parametric equations. */ v.x = c->Sg.x - c->prevSg.x; v.y = c->Sg.y - c->prevSg.y; v.z = c->Sg.z - c->prevSg.z; /* * Now add each tracer shell to the view. */ startT = (c->offset + (c->tracerVal / RATE_OF_FIRE)) / c->interval; for (t = startT; t <= 1.0; t += TRACER_MOD / (RATE_OF_FIRE * c->interval)) { s.x = -v.x * t; s.y = -v.y * t; s.z = -v.z * t; /* TODO: check should be based on local altitude */ /* underground? don't plot it */ if (c->w.z + s.z < 0.0) { continue; } n = c->cinfo->object->numPolys; p = c->cinfo->object->polygon; j = *cnt; for (i = 0; i < n; ++i) { poly[j] = VCopyPolygon(p[i]); for ((k = 0, q = poly[j]->vertex); k < poly[j]->numVtces; (++k, ++q)) { VTransform(q, m, &tmp); tmp.x += s.x; tmp.y += s.y; tmp.z += s.z; *q = tmp; } VTransformPolygon(poly[j], &vp->eyeSpace); ++j; } *cnt = j; } return 0; } void initm61a1(void) { craftType *c; FILE *f; dis_entity_type em1 = {2, 9, 225, 2, 1, 0, 0}; dis_entity_type em2 = {2, 1, 222, 2, 3, 0, 0}; wtbl[1] = m61a1Desc; c = newCraft(); c->name = strdup("m61a1 cannon"); c->entityType = em1; c->altEntityType = em2; c->placeProc = placeCannon; f = acm_fopen("tracer.obv", "r"); c->object = VReadObject(f); fclose(f); } int cannonCalculations(craft * c) { double dNorth, dEast, dmag; /* * Kill projectile streams after 10.0 seconds of flight or when * they strike the ground. */ if (curTime - c->createTime > 10.0) return 1; /* TODO */ if (c->w.z < 0.0) { return 1; } c->prevSg = c->Sg; c->prevw = c->w; dNorth = FEETtoMETERS(c->Cg.x * deltaT); dEast = FEETtoMETERS(c->Cg.y * deltaT); c->w.z -= FEETtoMETERS(c->Cg.z * deltaT + earth_g * halfDeltaTSquared); dmag = sqrt(dNorth * dNorth + dEast * dEast); DISUpdateWorldCoordinates(&c->w, dNorth / dmag, dEast / dmag, dmag); DISWorldCoordinatesToGeocentric(&c->w, (dis_world_coordinates *) & c->Sg); c->Cg.z += earth_g * deltaT; return 0; } /* * plotReticle : draws the aiming reticle onto the HUD. * * An aiming reticle is a circle with an inner arc that clues the * pilot as to the range of the target. Twelve tic marks are used * to demarcate the range -- each tic represents 1000 feet of * distance. */ static struct { double x, y; } ticTable[12] = { { 0.0, -1.0 }, { 0.5, -0.866 }, { 0.866, -0.5 }, { 1.0, 0.0 }, { 0.866, 0.5 }, { 0.5, 0.866 }, { 0.0, 1.0 }, { -0.5, 0.866 }, { -0.866, 0.5 }, { -1.0, 0.0 }, { -0.866, -0.5 }, { -0.5, -0.866 } }; /*ARGSUSED */ void plotReticle(craft * c, viewer * u, int x, int y, int range) { #define RETICLE_SIZE 65 #define RANGE_SIZE 59 #define TICK_SIZE (RETICLE_SIZE + 12) register int i, size, xt, yt, size1, xt1, yt1, nseg, arc; XSegment seg[16]; register double rs, rt; size = (int) (RETICLE_SIZE * u->scaleFactor); if ((size & 1) == 0) ++size; /* insure it is an odd value */ xt = x - size / 2; yt = y - size / 2; rs = size / 2; rt = ((TICK_SIZE + 1) / 2) * u->scaleFactor; for (i = 0; i < 12; i++) { seg[i].x1 = x + (int) (rs * ticTable[i].x + 0.5); seg[i].y1 = y + (int) (rs * ticTable[i].y + 0.5); seg[i].x2 = x + (int) (rt * ticTable[i].x + 0.5); seg[i].y2 = y + (int) (rt * ticTable[i].y + 0.5); } nseg = 12; VDrawArc(u->v, xt, yt, size, size, 0, 360 * 64, (Color) (u->v->pixel[HUDPixel])); if (range != -1) { if (range > 12000) range = 12000; arc = -range * 23040 / 12000; size1 = (int) (RANGE_SIZE * u->scaleFactor); if ((size1 & 1) == 0) ++size1; if (size == size1) --size1; xt1 = x - size1 / 2; yt1 = y - size1 / 2; VDrawArc(u->v, xt1, yt1, size1, size1, 90 * 64, arc, (Color) (u->v->pixel[HUDPixel])); } VDrawSegments(u->v, seg, nseg, (Color) (u->v->pixel[HUDPixel])); } void lcos(craft * c, viewer * u, int *x, int *y) { /* * How does this LCOS thing work, anyway? * * First, let me say that this may or, more probably, may not look * anything like the technique used in a real fighter's gun sighting * system. Having said that, here's the basic assumptions and steps * used to generate the reticle on the HUD: * * 1) Using radar, we know the distance to the target. Assuming that the * radar is in working order and is locked onto something. * * 2) We know the muzzle velocity of the shells we're firing and can * use that value to closely estimate the flight time of the shells * to a target that's a certain distance away (the target). Call * this value "fTime". * * 3) Use the muzzle velocity vector to determine the * relative position (w.r.t. our craft) of a shell "fTime" seconds into * its flight. Call this vector "pos". * * 4) Gravity will accelerate the shells. Use d = 0.5 * g * t ^ 2 * to add a distance (along the Z axis that will approximate the * effect of gravity. Add that to "pos". * * 5) We know our pitch and yaw rates. These rotations will have the * effect of visually "bending" the cannon stream, from the pilot's * perspective. Use the values to generate a matrix to transform * "pos" by an amount proportional to our "fTime" value. * * 6) Convert our "pos" vector to screen x,y coordinates and call * plotReticle. */ double fTime, range, gm, pitch_rate, yaw_rate; VPoint pos, tmp; VPoint zg, z; VMatrix rotation; int clue; hist_t *hist; hist = &history[c->pIndex]; /* Step 1 */ if (c->curRadarTarget >= 0) range = c->targetDistance; else range = 2500.0; /* Step 2 */ fTime = range / MUZZLE_VELOCITY; /* Step 3 */ pos.x = fTime * MUZZLE_VELOCITY * cos(OFFSET_ANGLE); pos.y = 0.0; pos.z = -fTime * MUZZLE_VELOCITY * sin(OFFSET_ANGLE); /* Step 4 */ zg.x = zg.y = 0.0; zg.z = 1.0; VReverseTransform_(&zg, &c->trihedral, &z); gm = 0.5 * earth_g * fTime * fTime; pos.x += z.x * gm; pos.y += z.y * gm; pos.z += z.z * gm; /* Step 5 */ VIdentMatrix(&rotation); pitch_rate = hist->pitch_total / (double) hist->count; yaw_rate = hist->yaw_total / (double) hist->count; if (pitch_rate != 0.0) VRotate(&rotation, YRotation, pitch_rate * fTime); if (yaw_rate != 0.0) VRotate(&rotation, ZRotation, yaw_rate * fTime); VTransform(&pos, &rotation, &tmp); /* Step 6 */ *x = (u->v->Middl.x + (int) (tmp.y * u->v->Scale.x / tmp.x)) >> 2; *y = (u->v->Middl.y + (int) (tmp.z * u->v->Scale.y / tmp.x)) >> 2; if (c->curRadarTarget >= 0) clue = (int) c->targetDistance; else clue = -1; plotReticle(c, u, *x, *y, clue); } /* * lookForCannonImpacts : Track cannon shells and look for impacts with * aircraft. This algorithm tracks each shell's * flight. */ typedef struct _entry { double time; double min; VPoint Sg; VPoint rvel; craft *c; struct _entry *next; } entry; extern int cdebug; void lookForCannonImpacts(craft * m) { craft *c; entry p[MAXPLAYERS], *list, *q, *r, *rprev; VPoint v, s0, prevSg, Vm, zeroVec = {0, 0, 0}; double t, d, roundT, startT, explosion_diameter_meters; int j; #ifdef HAVE_DIS double worldLocation[3], entityLocation[3]; #endif startT = m->offset / m->interval; Vm.x = m->Sg.x - m->prevSg.x; Vm.y = m->Sg.y - m->prevSg.y; Vm.z = m->Sg.z - m->prevSg.z; for (roundT = startT; roundT < 1.0; roundT += 1.0 / (RATE_OF_FIRE * m->interval)) { prevSg.x = m->prevSg.x - Vm.x * (1.0 - roundT); prevSg.y = m->prevSg.y - Vm.y * (1.0 - roundT); prevSg.z = m->prevSg.z - Vm.z * (1.0 - roundT); list = (entry *) NULL; for (c = ptbl, j = 0; j < MAXPLAYERS; ++j, ++c) { if (c->type == CT_FREE || m->owner == c->pIndex) continue; /* * Reduce the relative motion of this object to a the parametric system * of equations: * x(t) = vx * t + s0x * y(t) = vy * t + s0y * z(t) = vz * t + s0z * * We can then compute the time of perigee (closest pass) along with * the associated minimum distance. */ v.x = c->Sg.x - c->prevSg.x - Vm.x; v.y = c->Sg.y - c->prevSg.y - Vm.y; v.z = c->Sg.z - c->prevSg.z - Vm.z; s0.x = c->prevSg.x - prevSg.x; s0.y = c->prevSg.y - prevSg.y; s0.z = c->prevSg.z - prevSg.z; /* * Compute time of minimum distance between the two objects (note that units * here are UPDATE_INTERVAL seconds). */ t = -(v.x * s0.x + v.y * s0.y + v.z * s0.z) / (v.x * v.x + v.y * v.y + v.z * v.z); if (cdebug) { printf("perigee in %g seconds with player %d\n", t * deltaT, j); } /* * If the closest pass occurs during this update interval, check for a hit. * We'll build a linked list of all craft that this projectile may strike * during this period, arranged in ascending order by time of "perigee" * (closest pass). We'll then test for strikes. If a projectile misses * the first object, then it may have struck subsequent objects in the * list ... */ /* * One special case occurs when a target or missile's turn suddenly * changes the perigee time from positive to negative. If the missile * is within hitting range at t=0 and the time of perigee is negative, * then zap 'em. */ if (t < 0.0) { d = sqrt(s0.x * s0.x + s0.y * s0.y + s0.z * s0.z); if (isCannonHit(d, c)) { t = 0.0; } } if (t >= 0.0 && t <= 1.0) { q = &p[j]; q->Sg = prevSg; q->Sg.x += Vm.x * t; q->Sg.y += Vm.y * t; q->Sg.z += Vm.z * t; q->rvel = v; if (list == (entry *) NULL) { q->next = list; list = q; } else if (list->time > t) { q->next = list; list = q; } else { for (rprev = list, r = list->next; r != (entry *) NULL;) { if (r->time > t) break; rprev = r; r = r->next; } if (rprev != list) rprev->next = q; q->next = r; } q->time = t; q->c = c; q->min = sqrt(pow(v.x * t + s0.x, 2.0) + pow(v.y * t + s0.y, 2.0) + pow(v.z * t + s0.z, 2.0)); if (cdebug) { printf("perigee %g feet; craft %d.\n", q->min, j); } } } /* * Now look for cannon hits in the list of perigees. */ for (r = list; r != (entry *) NULL; r = r->next) if (isCannonHit(r->min, r->c)) { newExplosion(&(r->Sg), &zeroVec, 1.0, 2.0, 0.5); #ifdef HAVE_DIS /* can only damage local player */ if (r->c->type != CT_DIS_PLANE) #endif if (absorbDISDamage(r->c, &m->cinfo->entityType, 0, 0, 0.0, mag(r->rvel), &explosion_diameter_meters) == 0) { killPlayerEx(r->c, "Your aircraft was destroyed by cannon fire.", "No further information is available."); } #ifdef HAVE_DIS worldLocation[0] = r->Sg.x; worldLocation[1] = r->Sg.y; worldLocation[2] = r->Sg.z; entityLocation[0] = 0.0; entityLocation[1] = 0.0; entityLocation[2] = 0.0; if (disInUse) dis_detonation( &m->cinfo->entityType, c->disId, r->c->disId, DIS_ID_NONE, worldLocation, entityLocation, (double *) &r->rvel ); #endif break; } } } /*ARGSUSED */ int isCannonHit(double min, craft * c) { return (min < 3.0) ? 1 : 0; }