/* 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 <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#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
