/* c_endever.cc 1.26 95/12/28 23:23:43 */


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

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


// define the class endever methods 

#include "c_endever.hh"
#include <iostream.h>		// cerr
#include <stdlib.h>		// exit(), rand()
#include <string.h>
#include <math.h>
#include "common.hh"
#include "params.hh"
#include "globals.hh"           // toplevel for display
#include "c_block.hh"
#include "c_body.hh"
#include "c_ship.hh"
#include "space_objects.hh"
#include "messages.hh"

extern void draw_sector(void);
extern void destroy_universe(void); // deallocation
extern void faser_to(XtPointer client_data, XtIntervalId *id);
extern void torpedo_to(XtPointer client_data, XtIntervalId *id);
extern void draw_losemesg(void);
extern void echo(const char *, int);
extern void clear_echo(void);
extern void draw_summary(void);
extern void draw_summary_numbers(Drawable);
extern void erase_summary_numbers(Drawable);


Endever::Endever(): Combatant()
{
  energy.thrusters = maxerg;
  energy.warpdrive = maxerg;
  energy.fasers = maxerg;
  energy.shields = maxerg;
  docked = false;
  nomoveflag = false;
  shot.inprogress = false;
}


// reset private data for program restarts.

void Endever::erase()
{
  if (gamestate.input != REPLAY)
  {
    cerr << "xspacewarp: attempt to erase endever while game in progress."
         << endl;
    exit(1);
  }

  energy.thrusters = maxerg;
  energy.warpdrive = maxerg;
  energy.fasers = maxerg;
  energy.shields = maxerg;
  torpedoes = MAXTORPEDOES;
  docked = false;
  nomoveflag = false;
  shot.inprogress = false;
}


// Energy redistribution between shields and the other three
// energy sources adheres to conservation of energy. New energy
// levels will always lie between 0 and maxerg.  If the shield
// level is dropped, then the faser, warpdrive and thruster
// energy levels are increased according to need; ie the
// component with the lowest energy gets the largest share of
// the redistributed shield energy. Conversely, if the shield
// level is increased, then the faser, warpdrive and thruster
// energies are transferred to the shields with the most
// completely energized component contributing the most energy.

static int newerg(int erg, int delta, int sumerg, int maxerg)
{
  if (delta > 0)
  {
    if (sumerg == 0)
       return (-delta/3);
    else
       return(min(max(erg - (erg*delta)/sumerg, 0), maxerg));
  }
  else
  {
    if (3*maxerg-sumerg == 0)
       return (erg);
    else
       return(min(max(erg - ((maxerg-erg)*delta)/(3*maxerg-sumerg),0),maxerg));
  }
}

// Shield energy redistribution.

void Endever::setshields(int s)
{
  int reqsh;			// requested shields level
  int deltash;			// change in shields level
  int sumerg;			// sum of energies (excluding shields)

  reqsh = (s*maxerg)/100;
  deltash = reqsh - energy.shields;
  sumerg = energy.thrusters + energy.warpdrive + energy.fasers;
  
  energy.thrusters = newerg(energy.thrusters, deltash, sumerg, maxerg);
  energy.warpdrive = newerg(energy.warpdrive, deltash, sumerg, maxerg);
  energy.fasers = newerg(energy.fasers, deltash, sumerg, maxerg);

  energy.shields -= energy.thrusters + energy.warpdrive + energy.fasers -
                    sumerg;
  energy.shields = min(max(energy.shields, 0), maxerg);

  if (gamestate.visual == SUMMARY)
     draw_summary();
}


// draw icon on screen

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

  bdy->draw(drawable, gc, icon, icon_len); // access Body::draw()
}


// What to do when endever destroyed.

void Endever::die(Identity cause)
{
  Block block;
  Point origin;

  clear_echo();
  switch (cause)
  {
  case BLACKHOLE:		// fell into blackhole
    echo(die_blackhole_str, XtNumber(die_blackhole_str)-1);
    draw_losemesg();
    break;
  case ENDEVER:			// self-destruction
    if (gamestate.visual == SECTOR) // endever is visible
       explode(SDESTRUCTRAD);
    else
       draw_losemesg();
    echo(die_destruct_str, XtNumber(die_destruct_str)-1);
    break;
  case JOVIAN:			// killed by jovian
    if (gamestate.visual == SECTOR) // endever is visible
       explode(EXPLODERAD);
    else
       draw_losemesg();
    echo(die_jovian_str, XtNumber(die_jovian_str)-1);
    break;
  case STAR:			// ran into star
    echo(die_star_str, XtNumber(die_star_str)-1);
    block.setrow(SECTROW+row);
    block.setcol(SECTCOL+col);
    origin = block.origin();
    XDrawString(DISPLAY, XtWindow(widget), def_GC,
		origin.x, origin.y, "*", 1);
    draw_losemesg();
    break;
  }
  gamestate.input = REPLAY;
  gamestate.visual = LOSE;
}


// Ion thrusters.

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

  if (energy.thrusters < ENDMINTHRUSTERG) // not enough ergs to move
     return;

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

  switch (dir)
  {
  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 JOVIAN:		// do not move if already occupied
	return;
      case STAR:			// collide with star
	draw(XtWindow(widget), defrv_GC);
	draw(pixmap, defrv_GC);
	col = newcol;
	row = newrow;
	die(STAR);
	return;
      }
    }
  }

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

  draw(XtWindow(widget), defrv_GC);
  draw(pixmap, defrv_GC);

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

  draw(XtWindow(widget), endeverGC);
  draw(pixmap, endeverGC);

  dock_check();
}


// Warpdrive.

void Endever::leap(int ur, int uc)
{
  if (ur < 1 || ur > UROWS || uc < 1 || uc > UCOLS)
  {
    cerr << "xspacewarp: leap: row/col out of range." << endl;
    exit(1);
  }
  if ((ur == urow) && (uc == ucol)) // already there
     return;
  if (energy.warpdrive < ENDMINWARPERG)	// not enough ergs to leap
     return;

  // okay to leap

  universe[urow-1][ucol-1].rm_endever();
  urow = ur;
  ucol = uc;
  universe[urow-1][ucol-1].add_endever();
  draw_sector();
  gamestate.visual = SECTOR;
}


// Fasers/torpedoes.

void Endever::shoot(Weapon weapon, double angle) // angle in radians
{
  if (shot.inprogress)		// do not shoot if a shot already in progress
     return;
  shot.weapon = weapon;
  shot.source = ENDEVER;
  shot.start = center();
  shot.sin = (float) sin(angle);
  shot.cos = (float) cos(angle);

  switch (weapon)
  {
  case FASER:
    if (energy.fasers < ENDMINFASERG) // not enough faser energy to shoot
       return;
    shot.inprogress = true;
    shot.energy = ((float)100 * (float)energy.fasers) / (float)maxerg;
    shot.delta.x = (float)FASERSPD * shot.cos;
    shot.delta.y = -(float)FASERSPD * shot.sin;
    initshot(FASERLEN, FASERMARGIN, FASERINT, faserGC, faserGC_rv,
	     (XtTimerCallbackProc)faser_to);
    break;
  case TORPEDO:
    if (torpedoes == 0)		// all out of torpedoes
       return;
    shot.inprogress = true;
    torpedoes--;
    shot.delta.x = (float)TORPSPD * shot.cos;
    shot.delta.y = -(float)TORPSPD * shot.sin;
    initshot(TORPLEN, TORPMARGIN, TORPINT, torpGC, torpGC_rv,
	     (XtTimerCallbackProc)torpedo_to);
    break;
  }
}


// 7 1-2-3.

void Endever::selfdestruct()
{
  die(ENDEVER);
}


// check if adjacent to a base (docked). Updates "docked" flag
// appropriately.

void Endever::dock_check()
{
  Body *bp;

  if ((bp = universe[urow-1][ucol-1].occupant(row-1, col)) &&
      (bp->what() == BASE) && (bp->getcol() == col))
  {
    docked = true;
    clear_echo();
    echo(docked_str, XtNumber(docked_str)-1);
  }
  else if ((bp = universe[urow-1][ucol-1].occupant(row+1, col)) &&
	   (bp->what() == BASE) && (bp->getcol() == col))
  {
    docked = true;
    clear_echo();
    echo(docked_str, XtNumber(docked_str)-1);
  }
  else if ((bp = universe[urow-1][ucol-1].occupant(row, col-2)) &&
	   (bp->what() == BASE))
  {
    docked = true;
    clear_echo();
    echo(docked_str, XtNumber(docked_str)-1);
  }
  else if ((bp = universe[urow-1][ucol-1].occupant(row, col+2)) &&
	   (bp->what() == BASE))
  {
    docked = true;
    clear_echo();
    echo(docked_str, XtNumber(docked_str)-1);
  }
  else
  {
    docked = false;
    clear_echo();
  }
}


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

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

  if (docked)
  {
     newerg = ((ENDMAXERGRATEDOCK-ENDMINERGRATEDOCK) * energy.thrusters)/maxerg
              + ENDMINERGRATEDOCK;
     // rand perturbation of newerg
     perturb = (rand() % (2*ENDERGVARDOCK)) - ENDERGVARDOCK;
     newerg = max(newerg+perturb, 0);
     energy.thrusters = min(maxerg, energy.thrusters+newerg);

     newerg = ((ENDMAXERGRATEDOCK-ENDMINERGRATEDOCK) * energy.warpdrive)/maxerg
              + ENDMINERGRATEDOCK;
     // rand perturbation of newerg
     perturb = (rand() % (2*ENDERGVARDOCK)) - ENDERGVARDOCK;
     newerg = max(newerg+perturb, 0);
     energy.warpdrive = min(maxerg, energy.warpdrive+newerg);

     newerg = ((ENDMAXERGRATEDOCK-ENDMINERGRATEDOCK) * energy.fasers)/maxerg
              + ENDMINERGRATEDOCK;
     // rand perturbation of newerg
     perturb = (rand() % (2*ENDERGVARDOCK)) - ENDERGVARDOCK;
     newerg = max(newerg+perturb, 0);
     energy.fasers = min(maxerg, energy.fasers+newerg);

     newerg = ((ENDMAXERGRATEDOCK-ENDMINERGRATEDOCK) * energy.shields)/maxerg
              + ENDMINERGRATEDOCK;
     // rand perturbation of newerg
     perturb = (rand() % (2*ENDERGVARDOCK)) - ENDERGVARDOCK;
     newerg = max(newerg+perturb, 0);
     energy.shields = min(maxerg, energy.shields+newerg);
   }
  else
  {
     newerg = ((ENDMAXERGRATE-ENDMINERGRATE) * energy.thrusters)/maxerg
                + ENDMINERGRATE;
     // rand perturbation of newerg
     perturb = (rand() % (2*ENDERGVAR)) - ENDERGVAR;
     newerg = max(newerg+perturb, 0);
     energy.thrusters = min(maxerg, energy.thrusters+newerg);

     newerg = ((ENDMAXERGRATE-ENDMINERGRATE) * energy.warpdrive)/maxerg
                + ENDMINERGRATE;
     // rand perturbation of newerg
     perturb = (rand() % (2*ENDERGVAR)) - ENDERGVAR;
     newerg = max(newerg+perturb, 0);
     energy.warpdrive = min(maxerg, energy.warpdrive+newerg);

     newerg = ((ENDMAXERGRATE-ENDMINERGRATE) * energy.fasers)/maxerg
                + ENDMINERGRATE;
     // rand perturbation of newerg
     perturb = (rand() % (2*ENDERGVAR)) - ENDERGVAR;
     newerg = max(newerg+perturb, 0);
     energy.fasers = min(maxerg, energy.fasers+newerg);

     newerg = ((ENDMAXERGRATE-ENDMINERGRATE) * energy.shields)/maxerg
                + ENDMINERGRATE;
     // rand perturbation of newerg
     perturb = (rand() % (2*ENDERGVAR)) - ENDERGVAR;
     newerg = max(newerg+perturb, 0);
     energy.shields = min(maxerg, energy.shields+newerg);
  }

  if (gamestate.visual == SUMMARY)
  {
    erase_summary_numbers(pixmap);
    erase_summary_numbers(WINDOW);
    draw_summary_numbers(pixmap);
    draw_summary_numbers(WINDOW);
  }
}



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

const int Endever::icon_len = 3; // number of non-null chars in icon
char Endever::icon[icon_len+1];
const int Endever::maxerg = ENDMAXERG;

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

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

// end
