/* c_jovian.cc 1.17 95/12/23 03:11:28 */


// xspacewarp by Greg Walker (gow@math.orst.edu)

// This is free software. Non-profit redistribution and/or modification
// is allowed and welcome.


// class jovian methods

#include "c_jovian.hh"
#include "c_body.hh"
#include <stdlib.h>
#include <iostream.h>
#include <math.h>		// trig
#include "globals.hh"		// GCs etc, to erase after fatal hit
#include "space_objects.hh"

extern void draw_winmesg(void);
extern void faser_to(XtPointer client_data, XtIntervalId *id);

// these AI functions are kept in separate file c_jovian_ai.cc

extern Action Jovian::pick_action();
extern Direction Jovian::pick_direction();
extern Ucoors Jovian::pick_sector();
extern Point Jovian::pick_target();


Jovian::Jovian(): Combatant()
{
  energy.thrusters = maxerg;
  energy.warpdrive = maxerg;
  energy.fasers = maxerg;
  energy.shields = maxerg;
  shot.inprogress = false;
  init_ai_flags();			// initialize the ai non-static data
}


// each jovian has a "serial number" (actually an index into an array)
// useful for removing jovians from a sector.

void Jovian::setid(int i)
{
  if (i < 0)
  {
    cerr << "xspacewarp: class jovian: invalid id." << endl;
    exit(1);
  }
  id = i;
}


// What jovians do when destroyed.

void Jovian::die(Identity cause)
{
  Jovian_rec *jr_pt;

  if (jr_pt = universe[urow-1][ucol-1].rm_jovian(id))
     delete jr_pt;		// so no memory leaks
  draw(pixmap, defrv_GC);

  switch (cause)
  {
  case ENDEVER:			// killed by the endever
  case JOVIAN:			// accidentally killed by another jovian
    if (gamestate.visual == SECTOR)
       explode(EXPLODERAD);
    break;
  }

  if (--pop == 0)  // game won
  {
    if (gamestate.visual != SECTOR)
       draw_winmesg();
    gamestate.input = REPLAY;
    gamestate.visual = WIN;
  }
}


void Jovian::draw(Drawable drawable, GC gc) const
{
  Body *bdy = (Body *)this;

  bdy->draw(drawable, gc, icon, icon_len);
}


// To run ion thrusters.

void Jovian::move(Direction dir)
{
  int nc, newrow = row, newcol = col;
  Body* occupant;

  if (energy.thrusters < JOVMINTHRUSTERG)  // no energy to move
     return;

  if (shot.inprogress)		// prevent over-running your own faser
     return;

  switch (dir)
  {
  case STAY:
    return;
  case UP:
    newrow--;
    break;
  case DOWN:
    newrow++;
    break;
  case LEFT:
    newcol--;
    break;
  case RIGHT:
    newcol++;
    break;
  }

  // do not move if new position is out of bounds

  if (newrow < 0 || newrow >= SECTROWS ||
      newcol < icon_len/2 || newcol >= SECTCOLS-icon_len/2) return;

  // check if new position collides with another object.
  // need to check if *any* part of the icon touches the other object;
  // not just the center of the icon.

  for (nc = newcol-icon_len/2; nc <= newcol+icon_len/2; nc++)
  {
    if (occupant = universe[urow-1][ucol-1].occupant(newrow, nc))
    {
      switch (occupant->what())
      {
      case BASE:			// do not move if already occupied
	return;
      case BLACKHOLE:		// collide with blackhole
	draw(XtWindow(widget), defrv_GC);
	draw(pixmap, defrv_GC);
	col = newcol;
	row = newrow;
	die(BLACKHOLE);
	return;		// although dead, need to return to AppMainLoop
      case ENDEVER:		// do not move if already occupied
	return;
      case JOVIAN:
	if (occupant != (Body *)this) // space occupied if contains jovian
	   return;		      // other than this jovian
	break;
      case STAR:			// blocked by star
	mustmove = true;
	return;
      }
    }
  }

  // if the "for" loop completes itself then the new location is unoccupied

  if (visible())
  {
    draw(XtWindow(widget), defrv_GC);
    draw(pixmap, defrv_GC);
  }

  col = newcol;			// move
  row = newrow;
  setrow(row);
  setcol(col);

  mustmove = false;		// a move was done. now reset the flag.

  if (visible())
  {
    draw(XtWindow(widget), jovianGC);
    draw(pixmap, jovianGC);
  }
}


// To run warpdrive.

void Jovian::leap(Ucoors uc)
{
  Jovian_rec *jr_pt;

  if (uc.urow < 0)
     return;
  if (energy.warpdrive < JOVMINWARPERG)	// no energy to leap
     return;

  if (!(jr_pt = universe[urow-1][ucol-1].rm_jovian(id)))
  {
    cerr << "xspacewarp: Jovian::leap: bad id number." << endl;
    exit (1);
  }

  // erase if necessary

  if (visible())
  {
    draw(XtWindow(widget), defrv_GC);
    draw(pixmap, defrv_GC);
  }

  // leap

  urow = uc.urow;
  ucol = uc.ucol;
  universe[urow-1][ucol-1].add_jovian(jr_pt);

  // draw

  if ((universe[urow-1][ucol-1].getendever().x >= 0) &&
      (gamestate.visual == SECTOR))
  {
    draw(pixmap, def_GC);
    draw(WINDOW, def_GC);
  }
}


// fire faser toward the pixel at "to"

void Jovian::shoot(Point to)
{
  Point from = center();

  // cannot shoot if no energy. also, no more than one shot at a time.

  if ((energy.fasers < JOVMINFASERG) || shot.inprogress)
     return;

  double opp = (double)(from.y - to.y);
  double adj = (double)(to.x - from.x);
  double hyp = sqrt(opp*opp + adj*adj);

  shot.weapon = FASER;
  shot.source = JOVIAN;
  shot.start = from;
  shot.sin = (float)(opp / hyp);
  shot.cos = (float)(adj / hyp);
  shot.energy = ((float)100 * (float)energy.fasers) / (float)maxerg;
  shot.delta.x = (float)FASERSPD * shot.cos;
  shot.delta.y = -(float)FASERSPD * shot.sin;
  shot.inprogress = true;

  initshot(FASERLEN, FASERMARGIN, FASERINT, faserGC, faserGC_rv,
	   (XtTimerCallbackProc)faser_to);
}


// re-energize at a rate that varies linearly from JOVMINERGRATE
// (when least energized) to JOVMAXERGRATE (when fully
// energized).  That is, newenergy =
// (maxrate-minrate)*oldenergy/maxerg + minrate.  This rate is
// then perturbed by an amount varying from -JOVERGVAR to
// +JOVERGVAR. This is called by energize_to().

void Jovian::energize()
{
  int newerg, perturb;

  newerg = ((JOVMAXERGRATE-JOVMINERGRATE) * energy.thrusters)/maxerg
             + JOVMINERGRATE;
  // rand perturbation of newerg
  perturb = (rand() % (2*JOVERGVAR)) - JOVERGVAR;
  newerg = max(newerg+perturb, 0);
  energy.thrusters = min(maxerg, energy.thrusters+newerg);
  
  newerg = ((JOVMAXERGRATE-JOVMINERGRATE) * energy.warpdrive)/maxerg
             + JOVMINERGRATE;
  // rand perturbation of newerg
  perturb = (rand() % (2*JOVERGVAR)) - JOVERGVAR;
  newerg = max(newerg+perturb, 0);
  energy.warpdrive = min(maxerg, energy.warpdrive+newerg);
  
  newerg = ((JOVMAXERGRATE-JOVMINERGRATE) * energy.fasers)/maxerg
             + JOVMINERGRATE;
  // rand perturbation of newerg
  perturb = (rand() % (2*JOVERGVAR)) - JOVERGVAR;
  newerg = max(newerg+perturb, 0);
  energy.fasers = min(maxerg, energy.fasers+newerg);
  
  newerg = ((JOVMAXERGRATE-JOVMINERGRATE) * energy.shields)/maxerg
             + JOVMINERGRATE;
  // rand perturbation of newerg
  perturb = (rand() % (2*JOVERGVAR)) - JOVERGVAR;
  newerg = max(newerg+perturb, 0);
  energy.shields = min(maxerg, energy.shields+newerg);
}


// Make jovian do something.

void Jovian::update()
{
  act(decide());
}


// Have jovian decide an action. Fill in the Decision struct.

Decision Jovian::decide()
{
  Decision decision;

  switch (decision.action = pick_action())
  {
  case NONE:
    break;
  case MOVE:
    decision.details.direct = pick_direction();
    break;
  case LEAP:
    decision.details.sector = pick_sector();
    break;
  case SHOOT:
    decision.details.target = pick_target();
    break;
  }
  return (decision);
}


// Execute plans in Decision struct

void Jovian::act(Decision decision)
{
  switch (decision.action)
  {
  case NONE:
    break;
  case MOVE:
    move(decision.details.direct);
    break;
  case LEAP:
    leap(decision.details.sector);
    break;
  case SHOOT:
    shoot(decision.details.target);
    break;
  }
}
  

/******************* static members *********************/

// define static data

const int Jovian::icon_len = 3;
char Jovian::icon[icon_len+1];
const int Jovian::maxerg = JOVMAXERG;
int Jovian::pop = 0;
bool Jovian::leapflag;
bool Jovian::raidflag;
int Jovian::leapable = 0;
int Jovian::raidable = 0;


// define static functions

void Jovian::seticon(const char *str)
{
  if (strlen(str) != icon_len)
  {
    cerr << "xspacewarp: bad jovian icon in X resources." << endl;
    exit(1);
  }
  (void) strcpy(icon, str);
}

void Jovian::setpop(int p)
{
  if (p >= 0)			// changes nothing if neg arg
    pop = p;
}

int Jovian::getpop()
{
  return (pop);
}

void Jovian::setleapable(int n)
{
  if (n >= 0)
     leapable = n;
}

// add an int to leapable

void Jovian::addleapable(int n)
{
  leapable += n;
  leapable = max(leapable, 0);
}

void Jovian::setraidable(int n)
{
  if (n >= 0)
     raidable = n;
}

// add an int to raidable

void Jovian::addraidable(int n)
{
  raidable += n;
  raidable = max(raidable, 0);
}

int Jovian::geticon_len()
{
  return (icon_len);
}

// end
