/* c_combatant.cc 1.22 95/12/24 23:23:55 */ // xspacewarp by Greg Walker (gow@math.orst.edu) // This is free software. Non-profit redistribution and/or modification // is allowed and welcome. // combatant methods. contains the complicated faser/torpedo methods. // this version fires fasers in a long continuous line rather than pulses #include "c_combatant.hh" #include #include #include "common.hh" #include "params.hh" #include "globals.hh" // toplevel for display #include "c_sector.hh" #include "space_objects.hh" // return percentage levels of thrusters, warpdrive, fasers and shields. int Combatant::getthrusters() const { return ((int)(((float)100 * (float)energy.thrusters) / (float)getmaxerg())); } int Combatant::getwarpdrive() const { return ((int)(((float)100 * (float)energy.warpdrive) / (float)getmaxerg())); } int Combatant::getfasers() const { return ((int)(((float)100 * (float)energy.fasers) / (float)getmaxerg())); } int Combatant::getshields() const { return ((int)(((float)100 * (float)energy.shields) / (float)getmaxerg())); } // The faser/torpedo timeouts, faser_to() and torpedo_to(), call // moveshot() every "interval" milliseconds. moveshot() resets // the timer and passes the "this" pointer as client_data. Must // call initshot() before moveshot(). void Combatant::moveshot(int length, int margin, unsigned long interval, GC gc, GC gc_rv, XtTimerCallbackProc proc) { Point pnt, oldto; HitReport hitrep; // describe anything that got hit long sqmag1, sqmag2; // squares of vector magnitudes // update shot.to, but keep it fixed if shot.fixend is true. if (!shot.fixend) { oldto = shot.to; // save old value shot.f_to.x = shot.f_to.x + shot.delta.x; // compute new positions with shot.f_to.y = shot.f_to.y + shot.delta.y; // floats to resolve 1 deg angles shot.to.x = (int) shot.f_to.x; shot.to.y = (int) shot.f_to.y; hitrep = universe[urow-1][ucol-1].detect_hit(shot); switch (hitrep.what) // what got hit { case NOTHING: if (snip(margin)) // snip the float positions too. { shot.f_to.x = (float) shot.to.x; shot.f_to.y = (float) shot.to.y; } break; case STAR: mustmove = true; // flag used by jovians for ai decisions shot.fixend = true; shot.to = hitrep.newto; shot.f_to.x = (float) shot.to.x; shot.f_to.y = (float) shot.to.y; break; case BLACKHOLE: shot.fixend = true; shot.to = hitrep.newto; shot.f_to.x = (float) shot.to.x; shot.f_to.y = (float) shot.to.y; break; case JOVIAN: mustmove = true; // fall through case BASE: case ENDEVER: if (hitrep.damage == FATAL) // done { shot.to = oldto; if (visible()) cleanup(gc_rv); shot.inprogress = false; return; } shot.fixend = true; shot.to = hitrep.newto; shot.f_to.x = (float) shot.to.x; shot.f_to.y = (float) shot.to.y; break; } if (visible()) { sqmag1 = sqdist(shot.start, oldto); sqmag2 = sqdist(shot.start, shot.to); if (sqmag2 <= sqmag1) // newto closer to source than old "to" { // remove excess faser/torpedo crap if something crosses middle of shot XDrawLine(DISPLAY, WINDOW, gc_rv, shot.to.x, shot.to.y, oldto.x, oldto.y); } else { XDrawLine(DISPLAY, WINDOW, gc, oldto.x, oldto.y, shot.to.x, shot.to.y); } } } if (shot.fixend || shot.weapon == TORPEDO) { // update shot.from shot.f_from.x = shot.f_from.x + shot.delta.x; shot.f_from.y = shot.f_from.y + shot.delta.y; pnt.x = (int) shot.f_from.x; pnt.y = (int) shot.f_from.y; sqmag1 = sqdist(shot.start, shot.to); sqmag2 = sqdist(shot.start, pnt); if (sqmag2 >= sqmag1) // shot done { if (visible()) cleanup(gc_rv); shot.inprogress = false; return; } // Erase tail of shot. To avoid stray lit pixels, erase 3 pixels inward // towards the center of the shooting source. if (visible()) { XDrawLine(DISPLAY, XtWindow(widget), gc_rv, shot.from.x - (int)(3*shot.cos), shot.from.y + (int)(3*shot.sin), pnt.x, pnt.y); } shot.from = pnt; } // set timer for next call on this function shot.id = XtAppAddTimeOut(app_context, interval, (XtTimerCallbackProc) proc, (XtPointer) this); } // Initialize Shot struct and start timeout timer. void Combatant::initshot(int length, int margin, unsigned long interval, GC gc, GC gc_rv, XtTimerCallbackProc proc) { long from_sqmag, to_sqmag; // squares of magnitudes of the vectors // start->from and start->to HitReport hitrep; // initialize shot.fixend shot.fixend = false; // determine position for tail of shot shot.f_from.x = (float)shot.start.x + (float)RADIUS * shot.cos; shot.f_from.y = (float)shot.start.y - (float)RADIUS * shot.sin; shot.from.x = (int) shot.f_from.x; shot.from.y = (int) shot.f_from.y; // do not shoot anything if ship too close to sector border if ((shot.from.x < SECTMINX + margin) || (shot.from.x > SECTMAXX - margin) || (shot.from.y < SECTMINY + margin) || (shot.from.y > SECTMAXY - margin)) { shot.inprogress = false; return; } // determine position for head of shot shot.f_to.x = shot.f_from.x + (float)length * shot.cos; shot.f_to.y = shot.f_from.y - (float)length * shot.sin; shot.to.x = (int) shot.f_to.x; shot.to.y = (int) shot.f_to.y; // check if hits and if necessary, truncate line and set shot.fixend to true hitrep = universe[urow-1][ucol-1].detect_hit(shot); switch (hitrep.what) { case NOTHING: if (snip(margin)) // snip the float positions too. { shot.f_to.x = (float) shot.to.x; shot.f_to.y = (float) shot.to.y; } break; case STAR: mustmove = true; shot.fixend = true; shot.to = hitrep.newto; shot.f_to.x = (float) shot.to.x; shot.f_to.y = (float) shot.to.y; break; case BLACKHOLE: shot.fixend = true; shot.to = hitrep.newto; shot.f_to.x = (float) shot.to.x; shot.f_to.y = (float) shot.to.y; break; case JOVIAN: mustmove = true; // fall through case BASE: case ENDEVER: shot.fixend = true; if (hitrep.damage == FATAL) // done { shot.inprogress = false; return; } shot.to = hitrep.newto; shot.f_to.x = (float) shot.to.x; shot.f_to.y = (float) shot.to.y; break; } // finished if "to" end is closer to shot source than "from" end from_sqmag = sqdist(shot.start, shot.from); to_sqmag = sqdist(shot.start, shot.to); if (to_sqmag <= from_sqmag) { shot.inprogress = false; return; } if (visible()) { XDrawLine(DISPLAY, WINDOW, gc, shot.from.x, shot.from.y, shot.to.x, shot.to.y); } shot.id = XtAppAddTimeOut(app_context, interval, (XtTimerCallbackProc) proc, (XtPointer) this); } // snip the segment shot.from -> shot.to short by changing // shot.to if the segment crosses into the margin with thickness // "margin" surrounding the inside of the sector border. Return // true if snip() changed the value of shot.to and return false // otherwise. snip() sets the shot.fixend member to true if the // line gets snipped and does nothing to shot.fixend otherwise. bool Combatant::snip(int margin) { bool val = false; Point pnt; // "if" statements are done in sequence to handle the case where // the line gets too close to a corner. if (shot.to.x > SECTMAXX-margin) // hit right boundary { pnt.x = SECTMAXX-margin; pnt.y = (int)((float)(pnt.x - shot.from.x)* ((float)(shot.to.y - shot.from.y)/ (float)(shot.to.x - shot.from.x))) + shot.from.y; shot.to = pnt; shot.fixend = true; val = true; } if (shot.to.x < SECTMINX+margin) // hit left boundary { pnt.x = SECTMINX+margin; pnt.y = (int)((float)(pnt.x - shot.from.x)* ((float)(shot.to.y - shot.from.y)/ (float)(shot.to.x - shot.from.x))) + shot.from.y; shot.to = pnt; shot.fixend = true; val = true; } if (shot.to.y < SECTMINY+margin) // hit top boundary { pnt.y = SECTMINY+margin; pnt.x = (int)((float)(pnt.y - shot.from.y)* ((float)(shot.to.x - shot.from.x)/ (float)(shot.to.y - shot.from.y))) + shot.from.x; shot.to = pnt; shot.fixend = true; val = true; } if (shot.to.y > SECTMAXY-margin) // hit bottom boundary { pnt.y = SECTMAXY-margin; pnt.x = (int)((float)(pnt.y - shot.from.y)* ((float)(shot.to.x - shot.from.x)/ (float)(shot.to.y - shot.from.y))) + shot.from.x; shot.to = pnt; shot.fixend = true; val = true; } return (val); } // erase a shot and remove any stray pixels. pass a reverse video GC. void Combatant::cleanup(GC gc_rv) { Point nw, se; // nw, se corners of cleanup rectangle XDrawLine(DISPLAY, WINDOW, gc_rv, shot.from.x - (int)(3*shot.cos), shot.from.y + (int)(3*shot.sin), shot.to.x, shot.to.y); // remove stray pixels around shot.to nw.x = max(shot.to.x - CLEANUPW/2, SECTMINX); nw.y = max(shot.to.y - CLEANUPH/2, SECTMINY); se.x = min(shot.to.x + CLEANUPW/2, SECTMAXX); se.y = min(shot.to.y + CLEANUPH/2, SECTMAXY); XCopyArea(DISPLAY, pixmap, XtWindow(widget), def_GC, nw.x, nw.y, se.x - nw.x + 1, se.y - nw.y + 1, nw.x, nw.y); } // end