/* * acm : an aerial combat simulator for X * Copyright (C) 1991-1998 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 "pm.h" #ifdef HAVE_DIS #include "dis.h" #endif typedef struct _entry { double time; double min; craft *c; VPoint Sg; VPoint rvel; struct _entry *next; } entry; extern int mdebug; extern void lookForCannonImpacts(craft * m); extern int absorbDamage(); int isMissileHit(double min, craft * c); int inCloud(craft * c) { int state; if (c->w.z > ctop) { state = 0; } else if (c->w.z > cbase) { state = 1; } else { state = 2; } return state; } int fireMissile(craft * c, int ind) { register craft *m; register int i; VPoint s, s1; VPoint cY, mX, mY, mZ; double v; #ifdef HAVE_DIS double disLocation[3]; double disVelocity[3]; double disZeroVec[3]; double disOrientation[3]; #endif for ((i = 0, m = &mtbl[0]); i < MAXPROJECTILES; (++i, ++m)) if (m->type == CT_FREE) { m->type = CT_MISSILE; break; } if (i == MAXPROJECTILES) return -1; m->cinfo = lookupCraft("aim-9m"); m->fuel = m->cinfo->maxFuel; m->curThrust = m->cinfo->maxThrust; m->owner = c->pIndex; m->gvs_instance = (GVS_OBI) NULL; /* * Build trihedral based on the launching aircraft's current velocity vector * rather than simply it's current direction vector. * * (1) build a unit velocity vector. * (2) calculate missiles local Z axis from * plane's-y-axis CROSS missile's-unit-velocity-vector * (3) calculate missile's Y axis. */ if ((v = mag(c->Cg)) < 1.0) { m->trihedral = c->trihedral; m->curRoll = c->curRoll; m->curPitch = c->curPitch; m->curHeading = c->curHeading; } else { mX = c->Cg; mX.x /= v; mX.y /= v; mX.z /= v; cY.x = c->trihedral.m[0][1]; cY.y = c->trihedral.m[1][1]; cY.z = c->trihedral.m[2][1]; VCrossProd(&mX, &cY, &mZ); VCrossProd(&mZ, &mX, &mY); m->trihedral.m[0][0] = mX.x; m->trihedral.m[1][0] = mX.y; m->trihedral.m[2][0] = mX.z; m->trihedral.m[0][1] = mY.x; m->trihedral.m[1][1] = mY.y; m->trihedral.m[2][1] = mY.z; m->trihedral.m[0][2] = mZ.x; m->trihedral.m[1][2] = mZ.y; m->trihedral.m[2][2] = mZ.z; euler(m); } m->Cg = c->Cg; VTransform(&(c->cinfo->wStation[ind]), &(c->trihedral), &s1); VReverseTransform_(&s1, &c->XYZtoNED, &s); m->Sg.x = c->prevSg.x + FEETtoMETERS(s.x); m->Sg.y = c->prevSg.y + FEETtoMETERS(s.y); m->Sg.z = c->prevSg.z + FEETtoMETERS(s.z); DISGeocentricToWorldCoordinates ((dis_world_coordinates *) & m->Sg, &m->w); m->prevw = m->w; GenerateWorldToLocalMatrix(&m->w, &m->XYZtoNED); m->armTimer = m->cinfo->armDelay; m->flags = FL_HAS_GYRO; m->createTime = curTime; /* * kludge */ m->curRadarTarget = c->curRadarTarget; #ifdef HAVE_DIS /* * ACM missiles are DIS "tracked munitions", so we are * responsible for sending entity state PDU's for them */ if (disInUse) { VPoint tmp; disLocation[0] = m->Sg.x; disLocation[1] = m->Sg.y; disLocation[2] = m->Sg.z; tmp.x = FEETtoMETERS(m->Cg.x); tmp.y = FEETtoMETERS(m->Cg.y); tmp.z = FEETtoMETERS(m->Cg.z); VReverseTransform_(&tmp, &m->XYZtoNED, (VPoint *) & disVelocity[0]); disZeroVec[0] = 0.0; disZeroVec[1] = 0.0; disZeroVec[2] = 0.0; disOrientation[0] = m->curHeading; disOrientation[1] = m->curPitch; disOrientation[2] = m->curRoll; dis_entityEnter(c->team, m, &m->cinfo->entityType, &m->cinfo->altEntityType, disLocation, disVelocity, disZeroVec, disOrientation, disZeroVec, &m->disId); #ifdef DIS_DEBUG printf("Missile Entering m%d %d\n", i, m->disId); #endif } #endif return 0; } int killMissile(craft * c, craft * target) { #ifdef HAVE_DIS double worldLocation[3], entityLocation[3]; int target_eid, missile_eid; VPoint worldVel, tmp; if (c->type == CT_MISSILE || c->type == CT_CANNON) { if (target == NULL) { target_eid = DIS_ID_NONE; } else { target_eid = target->disId; } if (c->type == CT_CANNON) { missile_eid = DIS_ID_NONE; } else { missile_eid = c->disId; } worldLocation[0] = c->Sg.x; worldLocation[1] = c->Sg.y; worldLocation[2] = c->Sg.z; /* * killMissile's calling sequence needs to be updated to allow for the * entity detonation location to be passed. */ entityLocation[0] = 0.0; entityLocation[1] = 0.0; entityLocation[2] = 0.0; tmp.x = FEETtoMETERS(c->Cg.x); tmp.y = FEETtoMETERS(c->Cg.y); tmp.z = FEETtoMETERS(c->Cg.z); VReverseTransform_(&tmp, &c->XYZtoNED, &worldVel); if (disInUse) { dis_detonation(&c->cinfo->entityType, ptbl[c->owner].disId, target_eid, missile_eid, worldLocation, entityLocation, (double *) &worldVel); } } #endif c->type = CT_FREE; return 0; } int lookForImpacts(void) { craft *c, *m; entry p[MAXPLAYERS], *list, *q, *r, *rprev; VPoint v, s0; double t, d, explosion_diameter_meters; int i, j; for (m = mtbl, i = 0; i < MAXPROJECTILES; ++i, ++m) { if (m->type == CT_CANNON) { lookForCannonImpacts(m); continue; } else if (m->type != CT_MISSILE || m->armTimer > 0.0) continue; list = (entry *) NULL; for (c = ptbl, j = 0; j < MAXPLAYERS; ++j, ++c) { if (c->type == CT_FREE) 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 - m->Sg.x + m->prevSg.x; v.y = c->Sg.y - c->prevSg.y - m->Sg.y + m->prevSg.y; v.z = c->Sg.z - c->prevSg.z - m->Sg.z + m->prevSg.z; s0.x = c->prevSg.x - m->prevSg.x; s0.y = c->prevSg.y - m->prevSg.y; s0.z = c->prevSg.z - m->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 (mdebug) 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 (isMissileHit(d, c)) { t = 0.0; } } if (t >= 0.0 && t <= 1.0) { q = &p[j]; q->Sg = m->prevSg; q->Sg.x += (m->Sg.x - m->prevSg.x) * t; q->Sg.y += (m->Sg.y - m->prevSg.y) * t; q->Sg.z += (m->Sg.z - m->prevSg.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; } 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)); #ifdef FLIGHTDEBUG if (mdebug) printf("perigee %g feet; craft %d.\n", q->min, j); #endif } } /* * Now look for missile hits in the list of perigees. */ for (r = list; r != (entry *) NULL; r = r->next) if (isMissileHit(r->min, r->c)) { m->Sg = r->Sg; /* Set detonation point for killMissile() */ killMissile(m, r->c); #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, r->min, mag(r->rvel), &explosion_diameter_meters) == 0) { killPlayerEx(r->c, "Your aircraft was destroyed by an air-to-air missile.", "The missile's warhead detonated %.1f meters from your plane.", r->min); } newExplosion(&(r->Sg), &r->rvel, 5.0, 10.0, 3.0); break; } } return (0); } /*ARGSUSED */ int isMissileHit(double min, craft * c) { return (min < 15.0) ? 1 : 0; } #define IRMaxRange FEETtoMETERS(15.0 * NM) int isIRVisible(craft * m, craft * c, VPoint * t, double IRScanSlope) { VPoint relPos, tmp; int cstate, mstate; if (c->type == CT_FREE) return 0; /* * If the seeker is in clouds, or the target is not at the same level * (e.g seeker is above clouds, but target is below), then the target is * not IR visible. */ if ((mstate = inCloud(m)) == 1) { return 0; } if ((cstate = inCloud(c)) != mstate) { return 0; } VTransform(&c->prevSg, &m->XYZtoNED, &tmp); VReverseTransform_(&tmp, &m->trihedral, t); if (sqrt(t->x * t->x + t->y * t->y + t->z * t->z) > IRMaxRange) return 0; if (t->x <= 0.0) return 0; relPos.z = t->z / (t->x * IRScanSlope); relPos.y = t->y / (t->x * IRScanSlope); return (sqrt(relPos.z * relPos.z + relPos.y * relPos.y) > 1.0) ? 0 : 1; } int getIRTarget(craft * c, VPoint * t, double scanSlope) { int i, n; craft *p; VPoint tNew, tMin; double m1, min; if (c->curRadarTarget != -1 && isIRVisible(c, &ptbl[c->curRadarTarget], t, scanSlope)) return c->curRadarTarget; /* * Look for a target. Designate the closest one as a new target. */ min = 1000000.0; n = -1; for (i = 0, p = ptbl; i < MAXPLAYERS; ++i, ++p) { if (p == c) continue; if (p->type != CT_FREE) if (isIRVisible(c, p, &tNew, scanSlope)) { m1 = mag(tNew); if (m1 < min) { n = i; min = m1; tMin = tNew; } } } *t = tMin; return n; } /* * Track target using proportional navigation guidance (N = 4). */ #define AIM9SLOPE 0.57735 void trackTarget(craft * c) { VMatrix mtx, mtx1; VPoint t, t1, v, vrel, zeroVec = {0,0,0}; double h, maxTurn, omegap, omegay; double hs; double deltaRoll, deltaPitch, deltaYaw; craft *target; /* * Now let's get to target tracking; the missile won't start tracking until * 0.60 seconds has elapsed. Then, if we don't already have a target * designated, get one. */ if (curTime - c->createTime < 0.60) { deltaPitch = 0.0; deltaYaw = 0.0; goto change; } else if ((c->curRadarTarget = getIRTarget(c, &t, AIM9SLOPE)) == -1) { /* * Not target; missile goes ballistic */ deltaPitch = 0.0; deltaYaw = 0.0; goto change; #ifdef FLIGHTDEBUG if (mdebug) printf("Missile elects to self-destruct\n"); #endif newExplosion(&(c->Sg), &zeroVec, 5.0, 10.0, 3.0); killMissile(c, (craft *) NULL); return; } /* * We'll steer towards the target at a rate proportional to the * rate of change of the target's position in the missile's XZ (pitch) * and XY (yaw) planes. */ target = &ptbl[c->curRadarTarget]; v.x = target->Cg.x - c->Cg.x; v.y = target->Cg.y - c->Cg.y; v.z = target->Cg.z - c->Cg.z; t.x = METERStoFEET(t.x); t.y = METERStoFEET(t.y); t.z = METERStoFEET(t.z); VReverseTransform_( &v, &c->trihedral, &vrel ); hs = t.x * t.x + t.y * t.y; omegay = (vrel.y * t.x - vrel.x * t.y) / hs; omegap = (vrel.z * hs - t.z * (vrel.x * t.x + vrel.y * t.y)) / (sqrt(hs) * (hs + t.z * t.z)); deltaPitch = omegap * 4.0 * deltaT; deltaYaw = omegay * 4.0 * deltaT; h = sqrt( deltaPitch * deltaPitch + deltaYaw * deltaYaw ); /* * We'll constrain missile turns to about 20 degree/second unless it's velocity * would make that greater than a 25g load factor. */ if ( c->VT > 0.0 ) { maxTurn = (earth_g / c->VT) * sqrt( 25.0 * 25.0 - 1.0 ); } else { maxTurn = 0.0; } if (maxTurn > DEGtoRAD(20.0)) { maxTurn = DEGtoRAD(20.0); } maxTurn *= deltaT; #ifdef FLIGHTDEBUG if (mdebug) printf("\nturn rate = %g; maxTurn = %g\n", h, maxTurn); #endif if (h > maxTurn) { deltaPitch *= maxTurn / h; deltaYaw *= maxTurn / h; } /* * Re-orient the missile and velocity vector. */ change: deltaRoll = 0.0; #ifdef FLIGHTDEBUG if (mdebug) { printf("Missile changes: pitch/yaw: %g %g (deg).\n", RADtoDEG(deltaPitch), RADtoDEG(deltaYaw)); printf("position [%g, %g, %g]\n", t.x, t.y, t.z); printf("target pitch/yaw rates: %g, %g (deg/sec)\n", RADtoDEG(omegap), RADtoDEG(omegay)); } #endif buildEulerMatrix(deltaRoll, -deltaPitch, deltaYaw, &mtx); VReverseTransform_(&c->Cg, &c->trihedral, &t); VTransform(&t, &mtx, &t1); VTransform(&t1, &c->trihedral, &c->Cg); VMatrixMultByRank(&mtx, &c->trihedral, &mtx1, 3); c->trihedral = mtx1; euler(c); }