/* c_ship.cc 1.12 95/12/23 03:11:29 */


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

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


// methods for class Ship

#include "c_ship.hh"
#include <iostream.h>
#include <stdlib.h>		// rand()
#include <stdio.h>		// sprintf()
#include <time.h>		// seed
#include <math.h>
#include "common.hh"
#include "params.hh"
#include "globals.hh"
#include "c_block.hh"
#include "c_sector.hh"
#include "space_objects.hh"

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


// how ship reacts to a faser/torpedo hit

Damage Ship::hit(const Shot& sh)
{
  // torpedoes are sure kills and as well as faser hits when shield energy 0

  if (sh.weapon == TORPEDO || energy.shields == 0)
  {
    die(sh.source);
    return (FATAL);
  }

  // change ship energy levels reflecting damage from faser hit.

  energy.thrusters = (int)((double)energy.thrusters * severity(sh));
  energy.warpdrive = (int)((double)energy.warpdrive * severity(sh));
  energy.fasers = (int)((double)energy.fasers * severity(sh));
  energy.shields = (int)((double)energy.shields * severity(sh));
}


// return a random number between 0 and 1 indicating the
// fractional energy loss.  the current model being used is
// this:
// 
// severity = exp(-(k1 * E * x) / (D^2 * 100*exp(k2*(S - 100))))
//
// k1, k2 are arbitrary parameters (2 and .03, resp. seem to work well)
// E is energy of faser as percentage of full power (0 <= E <= 100)
// x is random integer in range 0 to 5000
// D is distance to source of faser
// S is energy of shields as percentage of full power (0 <= S <= 100)

double Ship::severity(const Shot& sh)
{
  double x, s;			// temps
  double sqdist;		// square of distance to shot source
  double pershld;		// percentage level of shields (this)
  double sev;			// severity
  Point cen;			// center of this ship

  cen = center();
  x = (double)(rand() % 5000);
  sqdist = (double)((cen.x - sh.start.x)*(cen.x - sh.start.x) +
		    (cen.y - sh.start.y)*(cen.y - sh.start.y));
  pershld = ((double)100 * (double)energy.shields) / (double)getmaxerg();
  s = (double)100*exp(SHIELDPARAM*(pershld - (double)100));
  sev = exp(-(INTENPARAM*sh.energy*x)/(sqdist*s));

  return (sev);
}


// initialize explosion. argument is the limiting radius of the explosion.

void Ship::explode(int r)
{
  Block block(row + SECTROW, col + SECTCOL);

  if (gamestate.visual != SECTOR)
  {
    cerr << "xspacewarp: class Ship: wrong visual state for explosion." << endl;
    exit(1);
  }

  explosion.center = block.center();
  if (r < 1)
  {
    cerr << "xspacewarp: class Ship: illegal explosion radius." << endl;
    exit(1);
  }
  explosion.limit = r;
  explosion.radius = 0;

  srand(time(0));
  expand();
}


// the explosion_to() timeout calls expand() every EXPANDINT
// millisecs.  expand() creates the image of an explosion by
// sprinkling a disk (centered at the source of the explosion)
// randomly with a fixed number of fragments (ie, FRAGW x FRAGH
// rectangles).  expand() then expands the disk out to a larger
// radius.  as the radius of the disk increases, the fragments
// become more rarified.

void Ship::expand()
{
  Point pnt;
  Point nw, se;		// northwest & southeast corners of cleanup rectangle;
  int i;

  explosion.radius += EXPANDRATE;	// expand out to next disk
  if (explosion.radius > explosion.limit) // explosion finished
  {
    // how to behave when explosion is finished depends on whether
    // this explosion was the coup de grace of the game

    switch (gamestate.visual)
    {
    case LOSE:			// explosion marked end of game
      draw_losemesg();
      break;
    case WIN:			// explosion marked end of game
      draw_winmesg();
      break;
    default:			// ordinary explosion: game not over
      // erase the explosion
      nw.x = max(explosion.center.x - EXPLODERAD - 1, SECTMINX);
      nw.y = max(explosion.center.y - EXPLODERAD - 1, SECTMINY);
      se.x = min(explosion.center.x + EXPLODERAD + FRAGW + 1, SECTMAXX);
      se.y = min(explosion.center.y + EXPLODERAD + FRAGH + 1, 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);
      break;
    }
    return;
  }

  // draw exploded fragments for next disk 
  
  for (i = 0; i < FRAGDENSITY; i++)
  {
    pnt = randfrag();

    // draw half the points in reverse video.
    // this makes a more chaotic looking explosion 

    if (rand()%2)
    {
      XFillRectangle(DISPLAY, XtWindow(widget), explodeGC,
		     pnt.x, pnt.y, FRAGW, FRAGH);
    }
    else
    {
      XFillRectangle(DISPLAY, XtWindow(widget), defrv_GC,
		     pnt.x, pnt.y, FRAGW, FRAGH);
    }
  }

  explosion.id = XtAppAddTimeOut(app_context, EXPANDINT,
				 (XtTimerCallbackProc) explosion_to,
				 (XtPointer) this);
}


// Return a random location for an explosion fragment inside a
// disk with center explosion.center and radius
// explosion.radius.  The fragment is drawn as a rectangle with
// dimensions FRAGW and FRAGH. The point returned by randfrag()
// is used as the northwest corner of the fragment.

Point Ship::randfrag()
{
  Point pnt;
  double r, theta;

  while (1)
  {
    // random point in polar coordinates
    
    r = (double)(rand() % explosion.radius);
    theta = (double)(rand() % 6283) / (double)1000;
    
    // convert to rectangular coordinates
    
    pnt.x = explosion.center.x + (int)(r*cos(theta));
    pnt.y = explosion.center.y + (int)(r*sin(theta));

    // reject pnt if the fragment having pnt at its NW corner
    // would extend beyond the sector boundary.

    if ((pnt.x >= SECTMINX) && (pnt.x <= SECTMAXX - FRAGW + 1) &&
	(pnt.y >= SECTMINY) && (pnt.y <= SECTMAXY - FRAGH + 1))
    {
      break;
    }
  }

  return (pnt);
}


// return true if ship icon is visible

bool Ship::visible() const
{
  return (universe[urow-1][ucol-1].hasendever() && gamestate.visual == SECTOR);
}


// return true if the ship icon is on the outer edge of the
// sector and false otherwise.

bool Ship::on_edge() const
{
  if (row == 0 || row == SECTROWS-1 || col == 1 || col == SECTCOLS-2)
     return (true);
  else
     return (false);
}

// end
