#define  XMSGF_ANTISPIN         0
#define  XMSGF_SOLAR_STORM      1
RegDft("TempleOS/XCaliber",
        "I64 best_score=0;\n"
        "I64 msg_flags=0;\n"
        );
RegExe("TempleOS/XCaliber");

#define MT_HUMAN_SHIP           0
#define MT_ENEMY_SHIP           1
#define MT_SOLAR_FLARE          2
#define MT_ION                  3
#define MT_ANTIMATTER_BALL      4
#define MT_ANTIMATTER_SPLAT     5
#define MT_MISSILE              6

class MyMass:CMass
{
  F64 temperature,radius,die_timeout;
  I64 type;
  Bool no_overlap;
};

class MySpring:CSpring
{
  F64 strength;
  I64 color;
};

#define SPIN_GAIN               0.25
#define MASSES_NUM              8
#define SPRINGS_NUM             16
#define MISSILES_NUM            2
#define ST_HUMAN1               0
#define ST_ENEMY1               1
#define ST_ENEMY2               2
extern class Ship;

#define MISSILE_LEN             5
class Missile
{
  Missile *next,*last;
  F64 tons,fuse_time,die_timeout;
  MyMass p_front,p_back;
  MySpring s[5];
  U8 *img;
  Ship *owner,*target;
  Bool active,launched,exploding;
  U8 label[5];
} missile_head;

class Ship
{
  Ship *next,*last;
  I64 type,masses,springs;
  MyMass   p[MASSES_NUM];
  MySpring s[SPRINGS_NUM];
  F64 fire_rate;
  F64 reload_timeout,spacewalk_timeout;
  F64 die_time,die_timeout;
  I64 spacewalk_side;
  F64 laser_temperature;
  Missile missiles[MISSILES_NUM];
  Bool lasering,exploding,laser_overheat;
} ship_head,*human;

F64 human_t_left,human_t_right,human_antispin;

class Shot
{
  Shot *next,*last;
  F64 radius,fuse_time;
  I64 splats;
  MyMass p;
} shot_head;

F64 t_solar_storm;
Bool alarm;

#define THRUST_MAX      200.0
#define ANTISPIN_MAX    25.0
#define SPACEWALK_TIME  7.5

#define CMD_NULL        0
#define CMD_SPIN_LEFT   1
#define CMD_SPIN_RIGHT  2
#define CMD_THRUST      3
#define CMD_FIRE        4
#define CMD_EXIT        5
Bool game_over,show_level_msg;

#define STARS_NUM       100
I64 stars_x[STARS_NUM],stars_y[STARS_NUM];

CMathODE *ode=NULL;
I64 level,score,remaining;


<1>/* Graphics Not Rendered in HTML */

<2>/* Graphics Not Rendered in HTML */

<3>/* Graphics Not Rendered in HTML */

<4>/* Graphics Not Rendered in HTML */


<5>/* Graphics Not Rendered in HTML */

<6>/* Graphics Not Rendered in HTML */

<7>/* Graphics Not Rendered in HTML */

<8>/* Graphics Not Rendered in HTML */


//********************************** Ship
Bool CheckOverlap()
{
  CD3 p;
  MyMass *tmpm,*tmpm1;
  tmpm=ode->next_mass;
  while (tmpm!=&ode->next_mass) {
    tmpm1=ode->next_mass;
    while (tmpm1!=&ode->next_mass) {
      if (tmpm!=tmpm1 && !tmpm->no_overlap && !tmpm1->no_overlap) {
        D3Sub(&p,&tmpm->x,&tmpm1->x);
        if (D3NormSqr(&p)<=Sqr(tmpm->radius+tmpm1->radius))
          return TRUE;
      }
      tmpm1=tmpm1->next;
    }
    tmpm=tmpm->next;
  }
  return FALSE;
}

U0 MissileNew(Ship *tmpsp,I64 n)
{
  I64 i;
  CD3 p,p1,p2;
  Missile *tmpmi=&tmpsp->missiles[n];
  MemSet(tmpmi,0,sizeof(Missile));

  D3Equ(&tmpmi->p_front.x,
        (tmpsp->p[n+1].x+tmpsp->p[n+3].x)/2,
        (tmpsp->p[n+1].y+tmpsp->p[n+3].y)/2,0);
  D3Copy(&tmpmi->p_back.x,&tmpmi->p_front.x);

  if (n&1)
    StrCpy(tmpmi->label,"L");
  else
    StrCpy(tmpmi->label,"R");
  tmpmi->owner=tmpsp;
  tmpmi->tons=0.5;
  tmpmi->p_front.mass=0.1;
  tmpmi->p_front.type=MT_MISSILE;
  tmpmi->p_back.mass =0.1;
  tmpmi->p_back.type =MT_MISSILE;
  tmpmi->p_front.radius=2;
  tmpmi->p_back.radius =2;
  tmpmi->p_front.no_overlap=TRUE;
  tmpmi->p_back.no_overlap =TRUE;
  D3Sub(&p1,&tmpsp->p[0].x,&tmpsp->p[1].x);
  D3Sub(&p2,&tmpsp->p[0].x,&tmpsp->p[2].x);
  D3Unit(D3Add(&p,&p1,&p2));
  D3AddEqu(&tmpmi->p_front.x,D3MulEqu(D3Copy(&p1,&p),MISSILE_LEN/2+1));
  D3SubEqu(&tmpmi->p_back.x ,D3MulEqu(D3Copy(&p1,&p),MISSILE_LEN/2-1));
  D3Copy(&tmpmi->p_front.DxDt,&tmpsp->p[n].DxDt);
  D3Copy(&tmpmi->p_back.DxDt,&tmpsp->p[n].DxDt);
  QueIns(&tmpmi->p_front,ode->last_mass);
  QueIns(&tmpmi->p_back, ode->last_mass);

  tmpmi->s[0].end1=&tmpmi->p_front;
  tmpmi->s[0].end2=&tmpmi->p_back;
  tmpmi->s[1].end1=&tmpmi->p_front;
  tmpmi->s[1].end2=&tmpsp->p[n+1];
  tmpmi->s[2].end1=&tmpmi->p_back;
  tmpmi->s[2].end2=&tmpsp->p[n+1];
  tmpmi->s[3].end1=&tmpmi->p_front;
  tmpmi->s[3].end2=&tmpsp->p[n+3];
  tmpmi->s[4].end1=&tmpmi->p_back;
  tmpmi->s[4].end2=&tmpsp->p[n+3];
 
  for (i=0;i<5;i++) {
    tmpmi->s[i].const=10000;
    tmpmi->s[i].strength  =20000;
    tmpmi->s[i].color=BLACK;
    tmpmi->s[i].rest_len=D3Dist(&tmpmi->s[i].end1->x,&tmpmi->s[i].end2->x);
    QueIns(&tmpmi->s[i],ode->last_spring);
  }
  tmpmi->img=<7>;
  tmpmi->active=TRUE;
  QueIns(tmpmi,missile_head.last);
}

Ship *ShipNew(I64 x,I64 y,I64 type)
{
  I64 i;
  Ship *tmpsp=CAlloc(sizeof(Ship));

  switch (tmpsp->type=type) {
    case ST_HUMAN1:
      tmpsp->fire_rate=25;
      tmpsp->masses=5;
      tmpsp->p[0].x=x;
      tmpsp->p[0].y=y;
      tmpsp->p[1].x=x+3;
      tmpsp->p[1].y=y+10;
      tmpsp->p[2].x=x-3;
      tmpsp->p[2].y=y+10;
      tmpsp->p[3].x=x+20;
      tmpsp->p[3].y=y+20;
      tmpsp->p[4].x=x-20;
      tmpsp->p[4].y=y+20;

      for (i=0;i<tmpsp->masses;i++) {
        tmpsp->p[i].mass=1;
        tmpsp->p[i].type=MT_HUMAN_SHIP;
        if (i<3)
          tmpsp->p[i].radius=2.5;
        else
          tmpsp->p[i].radius=4;
        tmpsp->p[i].drag_profile_factor=3;
        QueIns(&tmpsp->p[i],ode->last_mass);
      }
      tmpsp->p[3].mass/=10.0;
      tmpsp->p[4].mass/=10.0;

      tmpsp->springs=7;
      tmpsp->s[0].end1=&tmpsp->p[0];
      tmpsp->s[0].end2=&tmpsp->p[1];
      tmpsp->s[1].end1=&tmpsp->p[2];
      tmpsp->s[1].end2=&tmpsp->p[0];
      tmpsp->s[2].end1=&tmpsp->p[1];
      tmpsp->s[2].end2=&tmpsp->p[2];
      tmpsp->s[3].end1=&tmpsp->p[1];
      tmpsp->s[3].end2=&tmpsp->p[3];
      tmpsp->s[4].end1=&tmpsp->p[0];
      tmpsp->s[4].end2=&tmpsp->p[3];
      tmpsp->s[5].end1=&tmpsp->p[2];
      tmpsp->s[5].end2=&tmpsp->p[4];
      tmpsp->s[6].end1=&tmpsp->p[0];
      tmpsp->s[6].end2=&tmpsp->p[4];

      for (i=0;i<tmpsp->springs;i++) {
        tmpsp->s[i].rest_len=
              D3Dist(&tmpsp->s[i].end1->x,&tmpsp->s[i].end2->x);
        tmpsp->s[i].const=10000;
        tmpsp->s[i].strength  =30000;
        if (i<=2)
          tmpsp->s[i].color=LTCYAN;
        else
          tmpsp->s[i].color=LTGRAY;
        QueIns(&tmpsp->s[i],ode->last_spring);
      }
      MissileNew(tmpsp,0);
      MissileNew(tmpsp,1);
      remaining=0;

      break;
    case ST_ENEMY1:
      tmpsp->fire_rate=2.5;
      tmpsp->masses=3;
      tmpsp->p[0].x=x;
      tmpsp->p[0].y=y;
      tmpsp->p[1].x=x+15;
      tmpsp->p[1].y=y;
      tmpsp->p[2].x=x;
      tmpsp->p[2].y=y+15;

      for (i=0;i<tmpsp->masses;i++) {
        tmpsp->p[i].mass=1;
        tmpsp->p[i].type=MT_ENEMY_SHIP;
        tmpsp->p[i].radius=7;
        tmpsp->p[i].drag_profile_factor=3;
        QueIns(&tmpsp->p[i],ode->last_mass);
      }

      tmpsp->springs=3;
      tmpsp->s[0].end1=&tmpsp->p[0];
      tmpsp->s[0].end2=&tmpsp->p[1];
      tmpsp->s[1].end1=&tmpsp->p[1];
      tmpsp->s[1].end2=&tmpsp->p[2];
      tmpsp->s[2].end1=&tmpsp->p[2];
      tmpsp->s[2].end2=&tmpsp->p[0];

      for (i=0;i<tmpsp->springs;i++) {
        tmpsp->s[i].rest_len=
              D3Dist(&tmpsp->s[i].end1->x,&tmpsp->s[i].end2->x);
        tmpsp->s[i].const=10000;
        tmpsp->s[i].strength  =20000;
        tmpsp->s[i].color=BLACK;
        QueIns(&tmpsp->s[i],ode->last_spring);
      }
      remaining++;
      break;
    case ST_ENEMY2:
      tmpsp->fire_rate=5.0;
      tmpsp->masses=5;
      tmpsp->p[0].x=x;
      tmpsp->p[0].y=y;
      tmpsp->p[1].x=x-7;
      tmpsp->p[1].y=y+10;
      tmpsp->p[2].x=x+7;
      tmpsp->p[2].y=y+10;
      tmpsp->p[3].x=x-14;
      tmpsp->p[3].y=y+20;
      tmpsp->p[4].x=x+14;
      tmpsp->p[4].y=y+20;

      for (i=0;i<tmpsp->masses;i++) {
        tmpsp->p[i].mass=1;
        tmpsp->p[i].type=MT_ENEMY_SHIP;
        tmpsp->p[i].radius=6;
        tmpsp->p[i].drag_profile_factor=5;
        QueIns(&tmpsp->p[i],ode->last_mass);
      }

      tmpsp->springs=7;
      tmpsp->s[0].end1=&tmpsp->p[0];
      tmpsp->s[0].end2=&tmpsp->p[1];
      tmpsp->s[1].end1=&tmpsp->p[0];
      tmpsp->s[1].end2=&tmpsp->p[2];
      tmpsp->s[2].end1=&tmpsp->p[1];
      tmpsp->s[2].end2=&tmpsp->p[2];
      tmpsp->s[3].end1=&tmpsp->p[1];
      tmpsp->s[3].end2=&tmpsp->p[3];
      tmpsp->s[4].end1=&tmpsp->p[2];
      tmpsp->s[4].end2=&tmpsp->p[4];
      tmpsp->s[5].end1=&tmpsp->p[2];
      tmpsp->s[5].end2=&tmpsp->p[3];
      tmpsp->s[6].end1=&tmpsp->p[1];
      tmpsp->s[6].end2=&tmpsp->p[4];

      for (i=0;i<tmpsp->springs;i++) {
        tmpsp->s[i].rest_len=
              D3Dist(&tmpsp->s[i].end1->x,&tmpsp->s[i].end2->x);
        tmpsp->s[i].const= 40000;
        tmpsp->s[i].strength  =75000;
        if (i>=3)
          tmpsp->s[i].color=LTPURPLE;
        else
          tmpsp->s[i].color=BLACK;
        QueIns(&tmpsp->s[i],ode->last_spring);
      }
      remaining++;
      break;
  }
  QueIns(tmpsp,ship_head.last);
  return tmpsp;
}

U0 MissileDel(Missile *tmpmi)
{
  I64 i;
  if (tmpmi->active) {
    QueRem(tmpmi);
    for(i=0;i<5;i++)
      QueRem(&tmpmi->s[i]);
    QueRem(&tmpmi->p_front);
    QueRem(&tmpmi->p_back);
    tmpmi->active=FALSE;
  }
}

U0 ShipDel(Ship *tmpsp)
{
  I64 i;
  if (!tmpsp) return;
  for (i=0;i<tmpsp->masses;i++)
    QueRem(&tmpsp->p[i]);
  for (i=0;i<tmpsp->springs;i++)
    QueRem(&tmpsp->s[i]);
  for (i=0;i<2;i++)
    MissileDel(&tmpsp->missiles[i]);
  QueRem(tmpsp);
  Free(tmpsp);
  remaining--;
}

U0 PlaceShip(I64 type)
{
  Ship *tmpsp;
  if (CheckOverlap)
    return;
  while (TRUE) {
    tmpsp=ShipNew(RandU16%(Fs->pix_width-20)+10,
          RandU16%(Fs->pix_height-20)+10,type);
    if (CheckOverlap)
      ShipDel(tmpsp);
    else
      break;
  }
}

//********************************** Human Ship

I64 Tweaked()
{
  CD3 p,p1,p2;
  if (human) {
    D3Sub(&p1,&human->p[0].x,&human->p[1].x);
    D3Sub(&p2,&human->p[0].x,&human->p[2].x);
    D3Unit(D3Add(&p,&p1,&p2));
    D3Sub(&p1,&human->p[0].x,&human->p[3].x);
    D3Sub(&p2,&human->p[0].x,&human->p[4].x);
    D3Unit(&p1);
    D3Unit(&p2);
    if (!(human->s[3].flags&SSF_INACTIVE) && D3Dot(&p,&p1)>Cos(20*pi/180))
      return 3;
    if (!(human->s[5].flags&SSF_INACTIVE) && D3Dot(&p,&p2)>Cos(20*pi/180))
      return 4;
    return 0;
  }
}

U0 AllDel(CMathODE *ode)
{
  Ship *tmpsp,*tmpsp1;
  QueRem(ode);
  tmpsp=ship_head.next;
  while (tmpsp!=&ship_head) {
    tmpsp1=tmpsp->next;
    ShipDel(tmpsp);
    tmpsp=tmpsp1;
  }
  human=NULL;
  QueDel(&shot_head,TRUE);
  ODEDel(ode);
}

Bool LaserPlot(CDC *dc,I64 x,I64 y,I64)
{
  I64 c;
  c=GrPeek(dc,x,y);
  if (c!=BLACK && c!=WHITE)
    return FALSE;
  else {
    GrPlot(dc,x,y);
    return TRUE;
  }
}

//**********************************
U0 DrawIt(CTask *task,CDC *dc)
{
  I64 i,j;
  F64 arg;
  Ship *tmpsp;
  Shot *tmps;
  Missile *tmpmi;
  CD3 p,p1,p2;
  F64 t_left,t_right,spin,d,x,y;
  MySpring *tmpsps;
  MyMass *tmpm;
  U8 *img;
  Bool draw_laser_line=FALSE;

  if (ode!=task->last_ode) return;

  dc->color=WHITE;
  GrPrint(dc,0,0,"Level:%d Score:%d High Score:%d",level,score,best_score);
  if (game_over) {
    if (Blink)
      GrPrint(dc,(task->pix_width-9*FONT_WIDTH)/2,
            (task->pix_height-FONT_HEIGHT)/2,"Game Over");
  } else if (show_level_msg) {
    if (Blink)
      GrPrint(dc,(task->pix_width-8*FONT_WIDTH)/2,
            (task->pix_height-FONT_HEIGHT)/2+50,"Level %d",level);
  }

  for (i=0;i<STARS_NUM;i++)
    GrPlot(dc,stars_x[i],stars_y[i]);

  tmpm=ode->next_mass;
  while (tmpm!=&ode->next_mass) {
    if (tmpm->type==MT_ANTIMATTER_SPLAT) {
      dc->color=LTGREEN;
      GrPlot(dc,tmpm->x,tmpm->y);
    } else if (tmpm->type==MT_ION) {
      dc->color=YELLOW;
      GrPlot(dc,tmpm->x,tmpm->y);
    }
    tmpm=tmpm->next;
  }

  tmpsps=ode->next_spring;
  while (tmpsps!=&ode->next_spring) {
    if (!(tmpsps->flags&SSF_INACTIVE) && tmpsps->color) {
      dc->color=tmpsps->color;
      GrLine(dc,tmpsps->end1->x,tmpsps->end1->y,
            tmpsps->end2->x,tmpsps->end2->y);
    }
    tmpsps=tmpsps->next;
  }

  tmpmi=missile_head.next;
  while (tmpmi!=&missile_head) {
    if (tmpmi->active) {
      if (tmpmi->launched && tmpmi->exploding) {
        d=(tS-tmpmi->fuse_time)/(tmpmi->die_timeout-tmpmi->fuse_time);
        d=70*Sin(pi*d)*tmpmi->tons+1;
        for (i=1;i<d;i++) {
          if (i&1)
            dc->color=YELLOW;
          else
            dc->color=LTRED;
          GrCircle(dc,tmpmi->p_front.x,tmpmi->p_front.y,i);
        }
      } else
        Sprite3ZB(dc,(tmpmi->p_front.x+tmpmi->p_back.x)/2,
              (tmpmi->p_front.y+tmpmi->p_back.y)/2,0,tmpmi->img,
              Arg(tmpmi->p_front.x-tmpmi->p_back.x,
              tmpmi->p_front.y-tmpmi->p_back.y));
    }
    tmpmi=tmpmi->next;
  }

  tmpsp=ship_head.next;
  while (tmpsp!=&ship_head) {
    if (!tmpsp->exploding) {
      switch (tmpsp->type) {
        case ST_HUMAN1:
          if (tmpsp->spacewalk_side) {
            t_left=0;
            t_right=0;
          } else {
            if (d=D3Norm(D3Sub(&p1,&tmpsp->p[0].x,&tmpsp->p[1].x))) {
              D3Sub(&p2,&tmpsp->p[0].DxDt,&tmpsp->p[1].DxDt);
              D3Cross(&p,&p1,&p2);
              spin=p.z/d;
            } else
              spin=0;
            t_left =Clamp(human_t_left+SPIN_GAIN*spin*human_antispin,
                  0,THRUST_MAX);

            if (d=D3Norm(D3Sub(&p1,&tmpsp->p[0].x,&tmpsp->p[2].x))) {
              D3Sub(&p2,&tmpsp->p[0].DxDt,&tmpsp->p[2].DxDt);
              D3Cross(&p,&p1,&p2);
              spin=p.z/d;
            } else
              spin=0;
            t_right=Clamp(human_t_right-SPIN_GAIN*spin*human_antispin,
                  0,THRUST_MAX);
          }

          D3Sub(&p1,&tmpsp->p[1].x,&tmpsp->p[0].x);
          D3Sub(&p2,&tmpsp->p[2].x,&tmpsp->p[0].x);
          D3Unit(D3Add(&p,&p1,&p2));

          if (!(tmpsp->s[3].flags&SSF_INACTIVE)) {
            dc->color=YELLOW;
            D3AddEqu(D3Mul(&p1,t_left/25,&p),&tmpsp->p[3].x);
            GrLine(dc,tmpsp->p[1].x,tmpsp->p[1].y,p1.x,p1.y);
            arg=Arg(p.x,p.y);
            Sprite3ZB(dc,tmpsp->p[3].x,tmpsp->p[3].y,0,<thruster>,arg);
          }

          if (!(tmpsp->s[5].flags&SSF_INACTIVE)) {
            dc->color=YELLOW;
            D3AddEqu(D3Mul(&p2,t_right/25,&p),&tmpsp->p[4].x);
            GrLine(dc,tmpsp->p[2].x,tmpsp->p[2].y,p2.x,p2.y);
            arg=Arg(p.x,p.y);
            Sprite3ZB(dc,tmpsp->p[4].x,tmpsp->p[4].y,0,<thruster>,arg);
          }

          if (tS>tmpsp->reload_timeout)
            img=<gun_ready>;
          else
            img=<gun_busy>;
          arg=Arg(p.x,p.y);
          switch (level) {
            case 3:
              if (!(tmpsp->s[3].flags&SSF_INACTIVE))
                Sprite3ZB(dc,tmpsp->p[3].x,tmpsp->p[3].y,0,img,arg);
              if (!(tmpsp->s[5].flags&SSF_INACTIVE))
                Sprite3ZB(dc,tmpsp->p[4].x,tmpsp->p[4].y,0,img,arg);
            case 2:
              if (!(tmpsp->s[1].flags&SSF_INACTIVE))
                Sprite3ZB(dc,tmpsp->p[1].x,tmpsp->p[1].y,0,img,arg);
              if (!(tmpsp->s[2].flags&SSF_INACTIVE))
                Sprite3ZB(dc,tmpsp->p[2].x,tmpsp->p[2].y,0,img,arg);
            case 1:
              Sprite3ZB(dc,tmpsp->p[0].x,tmpsp->p[0].y,0,img,arg);
              break;
            default:
              Sprite3ZB(dc,tmpsp->p[0].x,tmpsp->p[0].y,0,<Laser>,arg);
              if (tmpsp->lasering && !tmpsp->laser_overheat) {
                draw_laser_line=TRUE;
                Snd(74);
              }
          }

          ctrl_panel.laser_temperature=tmpsp->laser_temperature;

          if (tmpsp->spacewalk_side) {
            d=1.0-(tmpsp->spacewalk_timeout-tS)/SPACEWALK_TIME;
            if (d>1.0) {
              tmpsp->spacewalk_side=0;
              ctrl_panel.spacewalk=FALSE;
            } else {
              if (d<0.5) {
                d=d*2;
                x=tmpsp->p[0].x*(1.0-d)+
                      tmpsp->p[tmpsp->spacewalk_side].x*(d);
                y=tmpsp->p[0].y*(1.0-d)+
                      tmpsp->p[tmpsp->spacewalk_side].y*(d);
              } else {
                d=(d-0.5)*2;
                x=tmpsp->p[tmpsp->spacewalk_side].x*(1.0-d)+
                      tmpsp->p[0].x*(d);
                y=tmpsp->p[tmpsp->spacewalk_side].y*(1.0-d)+
                      tmpsp->p[0].y*(d);
              }
              Sprite3ZB(dc,x,y,0,<spacewalk>,arg+0.75*Sin(tS*2));
            }
          } else {
            if (ctrl_panel.spacewalk) {
              if (tmpsp->spacewalk_side=Tweaked)
                tmpsp->spacewalk_timeout=tS+SPACEWALK_TIME;
              else
                ctrl_panel.spacewalk=FALSE;
            }
          }
          break;
        case ST_ENEMY2:
          for (i=3;i<tmpsp->masses;i++) {
            dc->color=PURPLE;
            GrCircle(dc,tmpsp->p[i].x,tmpsp->p[i].y,tmpsp->p[i].radius);
            GrFloodFill(dc,tmpsp->p[i].x,tmpsp->p[i].y+2,TRUE);
            dc->color=WHITE;
            GrCircle(dc,tmpsp->p[i].x,tmpsp->p[i].y,tmpsp->p[i].radius);
          }
        case ST_ENEMY1:
          D3DivEqu(D3Sub(&p1,&tmpsp->p[1].x,&tmpsp->p[0].x),2.0);
          D3DivEqu(D3Sub(&p2,&tmpsp->p[2].x,&tmpsp->p[0].x),2.0);
          D3Unit(D3Add(&p,&p1,&p2));
          if (tS>tmpsp->reload_timeout)
            img=<gun_ready>;
          else
            img=<gun_busy>;
          arg=Arg(p.x,p.y);
          Sprite3ZB(dc,tmpsp->p[0].x,tmpsp->p[0].y,0,img,arg);
          arg=Arg(p1.x,p1.y);
          Sprite3ZB(dc,tmpsp->p[0].x+p1.x,tmpsp->p[0].y+p1.y,0,
                <EnemySide>,arg);
          arg=Arg(p2.x,p2.y);
          Sprite3ZB(dc,tmpsp->p[0].x+p2.x,tmpsp->p[0].y+p2.y,0,
                <EnemySide>,arg);
          break;
      }
      for (i=0;i<tmpsp->masses;i++) {
        dc->color=YELLOW;
        if (tmpsp->p[i].temperature>=1.0)
          GrCircle(dc,tmpsp->p[i].x,tmpsp->p[i].y,
                tmpsp->p[i].temperature);
      }
    }
    else if (tmpsp->die_time<=tS<=tmpsp->die_timeout)
      for (j=0;j<tmpsp->masses;j++) {
        d=(tS-tmpsp->die_time)/(tmpsp->die_timeout-tmpsp->die_time);
        d=7*Sin(pi*d)*(6+j)+1;
        for (i=1;i<d;i++) {
          if (i&1)
            dc->color=YELLOW;
          else
            dc->color=LTRED;
          GrCircle(dc,tmpsp->p[j].x,tmpsp->p[j].y,i);
        }
      }
    tmpsp=tmpsp->next;
  }

  tmps=shot_head.next;
  while (tmps!=&shot_head) {
    if (tmps->radius<1.0) {
      dc->color=LTGREEN;
      GrPlot(dc,tmps->p.x,tmps->p.y);
    } else {
      dc->color=YELLOW;
      GrCircle(dc,tmps->p.x,tmps->p.y,tmps->radius);
      if (tmps->radius>=2.0)
        GrFloodFill(dc,tmps->p.x,tmps->p.y,TRUE);
      dc->color=LTGREEN;
      GrCircle(dc,tmps->p.x,tmps->p.y,tmps->radius);
    }
    tmps=tmps->next;
  }

  if (human && draw_laser_line) {
    D3Sub(&p1,&human->p[1].x,&human->p[0].x);
    D3Sub(&p2,&human->p[2].x,&human->p[0].x);
    D3Unit(D3Add(&p,&p1,&p2));
    dc->color=LTBLUE;
    Line(dc,human->p[0].x-10*p.x,human->p[0].y-10*p.y,0,
          human->p[0].x-800*p.x,human->p[0].y-800*p.y,0,&LaserPlot);
  }

  tmpmi=missile_head.next;
  while (tmpmi!=&missile_head) {
    if (tmpsp=tmpmi->target) {
      dc->color=LTRED;
      GrCircle(dc,tmpsp->p[0].x,tmpsp->p[0].y,10);
      GrPrint(dc,tmpsp->p[0].x+12,tmpsp->p[0].y-4,tmpmi->label);
    }
    tmpmi=tmpmi->next;
  }
}

U0 Explosion(MyMass *tmpm1,MyMass *tmpm2,F64 tons)
{
  MyMass *tmpm;
  CD3 p1;
  F64 d;

  tmpm=ode->next_mass;
  while (tmpm!=&ode->next_mass) {
    if (tmpm!=tmpm1 && tmpm!=tmpm2) {
      D3Sub(&p1,&tmpm->state->x,&tmpm1->state->x);
      d=D3NormSqr(&p1)-tmpm->radius*tmpm->radius;
      if (d<100.0*100.0) {
        if (d<1)
          d=1;
        else
          d=Sqrt(d);
        d=250000*tons/d`2;
        D3MulEqu(&p1,d);
        D3AddEqu(&tmpm->DstateDt->DxDt,&p1);
      }
    }
    tmpm=tmpm->next;
  }
}

Ship TargetGet(Missile *tmpmi)
{
  Ship *tmpsp,*res=NULL;
  F64 dd,best_dd=F64_MAX;
  I64 i;
  CD3 p,p1,p2;
  D3Unit(D3Sub(&p,&tmpmi->p_front.state->x,&tmpmi->p_back.state->x));
  tmpsp=ship_head.next;
  while (tmpsp!=&ship_head) {
    if (!tmpsp->exploding && tmpsp!=tmpmi->owner)
      for (i=0;i<tmpsp->masses;i++) {
        D3Sub(&p1,&tmpsp->p[i].state->x,&tmpmi->p_front.state->x);
        D3Unit(D3Copy(&p2,&p1));
        D3Cross(&p1,&p,&p2);
        if (D3Dot(&p,&p2)>0 && D3Norm(&p1)<=pi/16) {
          dd=D3NormSqr(&p1);
          if (dd<best_dd) {
            best_dd=dd;
            res=tmpsp;
          }
        }
      }
    tmpsp=tmpsp->next;
  }
  return res;
}

U0 MyDerivative(CMathODE *ode,F64,COrder2D3 *,COrder2D3 *)
{
  I64 i;
  F64 d,dd,dd2,spin,t_left,t_right,theta_err,theta_thrust,DthetaDt;
  CTask *task=ode->win_task;
  CD3 p,p1,p2;
  Ship *tmpsp;
  Missile *tmpmi;
  MyMass *tmpm,*tmpm1;

  tmpm=ode->next_mass;
  while (tmpm!=&ode->next_mass) {
    if (tmpm->type!=MT_SOLAR_FLARE && tmpm->type!=MT_ION) {
      d=tmpm->state->x;
      if (d-tmpm->radius<0)
        tmpm->DstateDt->DxDt+=Sqr(Sqr(Sqr(d-tmpm->radius)));
      if (d+tmpm->radius>task->pix_width)
        tmpm->DstateDt->DxDt-=Sqr(Sqr(Sqr((d+tmpm->radius)-task->pix_width)));
      d=tmpm->state->y;
      if (d-tmpm->radius<0)
        tmpm->DstateDt->DyDt+=Sqr(Sqr(Sqr(d-tmpm->radius)));
      if (d+tmpm->radius>task->pix_height)
        tmpm->DstateDt->DyDt-=Sqr(Sqr(Sqr((d+tmpm->radius)-task->pix_height)));
    }
    if (tmpm->type!=MT_ION && tmpm->type!=MT_ANTIMATTER_SPLAT) {
      tmpm1=ode->next_mass;
      while (tmpm1!=&ode->next_mass) {
        if (tmpm!=tmpm1) {
          if (tmpm1->type==MT_ANTIMATTER_SPLAT) {
            if (tmpm->type==MT_HUMAN_SHIP || tmpm->type==MT_ENEMY_SHIP) {
              D3Sub(&p,&tmpm->state->x,&tmpm1->state->x);
              dd=D3NormSqr(&p)+1;
              if (dd<100000) {
                D3MulEqu(&p,100000/dd);
                D3AddEqu(&tmpm1->DstateDt->DxDt,&p);
              }
            }
          } else if (tmpm1->type!=MT_ION) {
            D3Sub(&p,&tmpm->state->x,&tmpm1->state->x);
            dd=D3NormSqr(&p);
            dd2=Sqr(tmpm->radius+tmpm1->radius);
            if (dd<=dd2) {
              d=Sqrt(dd)+0.0001;
              D3MulEqu(&p,Sqr(Sqr(dd2-dd))/d);
              D3AddEqu(&tmpm ->DstateDt->DxDt,&p);
              D3SubEqu(&tmpm1->DstateDt->DxDt,&p);
            }
          }
        }
        tmpm1=tmpm1->next;
      }
    }
    tmpm=tmpm->next;
  }

  tmpsp=ship_head.next;
  while (tmpsp!=&ship_head) {
    if (tmpsp->exploding && tmpsp->die_time<=tS<=tmpsp->die_timeout)
      for (i=0;i<tmpsp->masses;i++)
        Explosion(&tmpsp->p[i],NULL,tmpsp->p[i].radius/250.0);
    switch (tmpsp->type) {
      case ST_HUMAN1:
        if (!tmpsp->exploding) {
          if (tmpsp->spacewalk_side) {
            t_left=0;
            t_right=0;
            d=1.0-(tmpsp->spacewalk_timeout-tS)/SPACEWALK_TIME;
            if (0.485<d<0.515) {
              D3Unit(D3Sub(&p,&tmpsp->p[2].state->x,&tmpsp->p[1].state->x));
              if (tmpsp->spacewalk_side==3) {
                tmpsp->p[3].DstateDt->DxDt-=10*THRUST_MAX*p.x;
                tmpsp->p[3].DstateDt->DyDt-=10*THRUST_MAX*p.y;
                tmpsp->p[1].DstateDt->DxDt+=10*THRUST_MAX*p.x;
                tmpsp->p[1].DstateDt->DyDt+=10*THRUST_MAX*p.y;
              } else {
                tmpsp->p[4].DstateDt->DxDt+=10*THRUST_MAX*p.x;
                tmpsp->p[4].DstateDt->DyDt+=10*THRUST_MAX*p.y;
                tmpsp->p[2].DstateDt->DxDt-=10*THRUST_MAX*p.x;
                tmpsp->p[2].DstateDt->DyDt-=10*THRUST_MAX*p.y;
              }
            }
          } else {
            if (d=D3Norm(D3Sub(&p1,&tmpsp->p[0].state->x,
                  &tmpsp->p[1].state->x))) {
              D3Sub(&p2,&tmpsp->p[0].state->DxDt,&tmpsp->p[1].state->DxDt);
              D3Cross(&p,&p1,&p2);
              spin=p.z/d;
            } else
              spin=0;
            t_left =Clamp(human_t_left+SPIN_GAIN*spin*human_antispin,
                  0,THRUST_MAX);

            if (d=D3Norm(D3Sub(&p1,&tmpsp->p[0].state->x,
                  &tmpsp->p[2].state->x))) {
              D3Sub(&p2,&tmpsp->p[0].state->DxDt,&tmpsp->p[2].state->DxDt);
              D3Cross(&p,&p1,&p2);
              spin=p.z/d;
            } else
              spin=0;
            t_right=Clamp(human_t_right-SPIN_GAIN*spin*human_antispin,
                  0,THRUST_MAX);

            D3Sub(&p1,&tmpsp->p[0].state->x,&tmpsp->p[1].state->x);
            D3Sub(&p2,&tmpsp->p[0].state->x,&tmpsp->p[2].state->x);
            D3Unit(D3Add(&p,&p1,&p2));
            if (!(tmpsp->s[3].flags&SSF_INACTIVE)) {
              D3Mul(&p1,t_left,&p);
              D3AddEqu(&tmpsp->p[3].DstateDt->DxDt,&p1);
            }
            if (!(tmpsp->s[5].flags&SSF_INACTIVE)) {
              D3Mul(&p2,t_right,&p);
              D3AddEqu(&tmpsp->p[4].DstateDt->DxDt,&p2);
            }
          }
        }
        break;
    }
    tmpsp=tmpsp->next;
  }

  tmpmi=missile_head.next;
  while (tmpmi!=&missile_head) {
    if (tmpmi->active) {
      if (tmpmi->launched) {
        if (tmpmi->exploding)
          Explosion(&tmpmi->p_front,&tmpmi->p_back,tmpmi->tons);
        else {
//Guide missile
          if (tmpsp=tmpmi->target) {
            D3Unit(D3Sub(&p,&tmpmi->p_front.state->x,
                  &tmpmi->p_back.state->x));
            D3Sub(&p1,&tmpsp->p[0].state->x,&tmpmi->p_front.state->x);
            d=D3Norm(&p1);
            D3Unit(&p1);
            theta_err=D3Dot(&p,&p1);
            D3Sub(&p1,&tmpmi->p_front.state->DxDt,&tmpmi->p_back.state->DxDt);
            D3Cross(&p2,&p,&p1);
            DthetaDt=D3Norm(&p2);
            if (p2.z<0)
              DthetaDt=-DthetaDt;
            theta_thrust=Clamp(200*(theta_err+2*DthetaDt)/(d+200),-pi/8,pi/8);
            p2.x=p.x*Cos(theta_thrust)-p.y*Sin(theta_thrust);
            p2.y=p.y*Cos(theta_thrust)+p.x*Sin(theta_thrust);
            p2.z=0;
            D3AddEqu(&tmpmi->p_back.DstateDt->DxDt,D3MulEqu(&p2,THRUST_MAX));
          }
        }
      } else
        tmpmi->target=TargetGet(tmpmi);
    } else
      tmpmi->target=NULL;
    tmpmi=tmpmi->next;
  }
}

U0 CheckDamage()
{
  I64 i,j,death_score;
  Ship *tmpsp,*tmpsp1;
  MyMass *tmpm,*tmpm1,*best_mass;
  CD3 p,p1,p2;
  F64 d,best_distance;
  Bool facing_sun=FALSE;

  if (human) {
    D3Sub(&p1,&human->p[1].x,&human->p[0].x);
    D3Sub(&p2,&human->p[2].x,&human->p[0].x);
    D3Add(&p,&p1,&p2);
    if (p.x>0)
      facing_sun=TRUE;
  }

  tmpm=ode->next_mass;
  while (tmpm!=&ode->next_mass) {
    if (tmpm->type==MT_ION) {
      if (facing_sun) {
        tmpm1=ode->next_mass;
        while (tmpm1!=&ode->next_mass) {
          if (tmpm1->type==MT_HUMAN_SHIP) {
            D3Sub(&p,&tmpm1->x,&tmpm->x);
            if (D3NormSqr(&p)<Sqr(tmpm1->radius))
              tmpm1->temperature+=3.0;
          }
          tmpm1=tmpm1->next;
        }
      }
    } else if (tmpm->type==MT_ANTIMATTER_SPLAT) {
      tmpm1=ode->next_mass;
      while (tmpm1!=&ode->next_mass) {
        if (tmpm1->type!=MT_ION && tmpm1->type!=MT_ANTIMATTER_SPLAT) {
          D3Sub(&p,&tmpm1->x,&tmpm->x);
          if (D3NormSqr(&p)<Sqr(tmpm1->radius))
            tmpm1->temperature+=0.4;
        }
        tmpm1=tmpm1->next;
      }
    } else
      tmpm->temperature*=0.9;
    tmpm=tmpm->next;
  }

  if (human) {
    human->laser_temperature*=0.975;

    if (human->laser_overheat) {
      if (human->laser_temperature<LASER_THRESHOLD_TEMP)
        human->laser_overheat=FALSE;
    }
    if (!human->laser_overheat && human->lasering) {
      if (human->laser_temperature>=LASER_TEMP_MAX) {
        human->laser_overheat=TRUE;
        Snd;
      } else {
        human->laser_temperature+=1.0;
        D3Sub(&p1,&human->p[0].x,&human->p[1].x);
        D3Sub(&p2,&human->p[0].x,&human->p[2].x);
        D3Unit(D3Add(&p,&p1,&p2));
        p2.x=p.y;
        p2.y=-p.x;
        p2.z=0;
        best_mass=NULL;
        best_distance=F64_MAX;
        tmpm=ode->next_mass;
        while (tmpm!=&ode->next_mass) {
          D3Sub(&p1,&human->p[0].x,&tmpm->x);
          if (Abs(D3Dot(&p1,&p2))<tmpm->radius &&
                D3Dot(&p1,&p)<0.0) {
            d=D3NormSqr(&p1);
            if (d<best_distance) {
              best_distance=d;
              best_mass=tmpm;
            }
          }
          tmpm=tmpm->next;
        }
        if (best_mass)
          best_mass->temperature+=1.0;
      }
    }
  }

  tmpsp=ship_head.next;
  while (tmpsp!=&ship_head) {
    tmpsp1=tmpsp->next;
    death_score=0;
    switch (tmpsp->type) {
      case ST_HUMAN1:
        if (tmpsp->exploding) {
          if (tS>tmpsp->die_timeout)  {
            ShipDel(tmpsp);
            human=NULL;
          }
        } else
          for (i=0;i<tmpsp->springs;i++) {
            if (Abs(tmpsp->s[i].f)>tmpsp->s[i].strength) {
              tmpsp->s[i].flags|=SSF_INACTIVE;
              if (i==4)
                MissileDel(&tmpsp->missiles[0]);
              else if (i==5)
                MissileDel(&tmpsp->missiles[1]);
            }
            if (tmpsp->s[i].flags&SSF_INACTIVE && i<3)
              death_score++;
          }
        break;
      default:
        if (tmpsp->exploding) {
          if (tS>tmpsp->die_timeout) {
            ShipDel(tmpsp);
            score+=level;
            if (score>best_score)
              best_score=score;
          }
        } else {
          j=0;
          for (i=0;i<tmpsp->springs;i++) {
            if (tmpsp->s[i].flags&SSF_INACTIVE)
              j++;
            else if (Abs(tmpsp->s[i].f)>tmpsp->s[i].strength) {
              tmpsp->s[i].flags|=SSF_INACTIVE;
              j++;
            }
          }
          if (j>1)
            death_score++;
        }
    }
    if (!tmpsp->exploding) {
      for (i=0;i<tmpsp->masses;i++)
        if (tmpsp->p[i].temperature>MASS_TEMP_MAX)
          death_score++;
      if (death_score) {
        tmpsp->exploding=TRUE;
        tmpsp->die_time=tS;
        tmpsp->die_timeout=tS+0.75;
        Noise(750,74,93);
        if (tmpsp->type==ST_HUMAN1)
          game_over=TRUE;
      }
    }
    tmpsp=tmpsp1;
  }
}

//********************************** Shots

Shot *ShotNew(I64 type,CD3 *_p,CD3 *_v,F64 r,F64 fuse_time,
        CD3 *_p_gun_offset=NULL)
{
  Shot *tmps=CAlloc(sizeof(Shot));
  D3Copy(&tmps->p.x,_p);
  tmps->radius=r;
  tmps->splats=20*r;
  tmps->fuse_time=tS+fuse_time;
  tmps->p.mass=0.3*r*r*r;
  tmps->p.type=type;
  if (_p_gun_offset)
    D3AddEqu(&tmps->p.x,_p_gun_offset);
  D3Copy(&tmps->p.DxDt,_v);
  QueIns(&tmps->p,ode->last_mass);
  QueIns(tmps,shot_head.last);
}

U0 SolarFlares()
{
  CD3 p,v,p1,p2;
  CTask *task=ode->win_task;
  if (!alarm && t_solar_storm-2.0<tS<t_solar_storm+1.0) {
    Sweep(2000,74,93);
    alarm=TRUE;
  }
  if (t_solar_storm<tS) {  //If solar storm has arrived
    if (tS<t_solar_storm+5.0) { //If solar storm not over
      if (Rand<.1) {
        D3Equ(&p,-300,Rand*task->pix_height,0);
        D3Equ(&v,200.0,0,0);
        ShotNew(MT_SOLAR_FLARE,&p,&v,25,0.1);
      }
    } else {
      t_solar_storm=tS+25*Rand;  //Schedule next solar storm
      alarm=FALSE;
    }
  }
}

U0 FireOneGun(Ship *tmpsp,I64 n,F64 r,F64 fuse_time)
{
  I64 ona;
  CD3 p,v,p1,p2;
  Shot *tmps;
  D3Sub(&p1,&tmpsp->p[0].x,&tmpsp->p[1].x);
  D3Sub(&p2,&tmpsp->p[0].x,&tmpsp->p[2].x);
  D3Unit(D3Add(&p,&p1,&p2));
  D3MulEqu(D3Copy(&p1,&p),r+tmpsp->p[0].radius+5);
  D3AddEqu(D3MulEqu(D3Copy(&v,&p),1000/(r+1)),&tmpsp->p[n].DxDt);
  tmps=ShotNew(MT_ANTIMATTER_BALL,&tmpsp->p[n].x,&v,r,fuse_time,&p1);
  D3MulEqu(&p,tmps->p.mass/tmpsp->p[n].mass/100.0);
  D3SubEqu(&tmpsp->p[n].DxDt,&p);
  tmpsp->reload_timeout=tS+r/tmpsp->fire_rate;
  ona=Freq2Ona(500/r);
  Noise(100,ona,ona+12);
}

U0 FireOneMissile(Ship *tmpsp,I64 n)
{
  I64 i;
  Missile *tmpmi=&tmpsp->missiles[n];
  if (!tmpmi->launched && tmpmi->target) {
    tmpmi->fuse_time=tS+1.0;
    tmpmi->die_timeout=tmpmi->fuse_time+0.125;
    tmpmi->img=<8>;
    for (i=1;i<5;i++)
      tmpmi->s[i].flags|=SSF_INACTIVE;
    tmpmi->launched=TRUE;
    Sweep(250,53,56);
  }
}

U0 HumanFireGunBegin()
{
  F64 r=3.0*ctrl_panel.shot_radius/CTRL_PANEL_RANGE+0.5,
        fuse_time=ToF64(ctrl_panel.fuse_time+1)/CTRL_PANEL_RANGE;
  if (human) {
    if (!human->exploding && !human->spacewalk_side && tS>human->reload_timeout)
      switch (level) {
        case 3:
          if (!(human->s[3].flags&SSF_INACTIVE))
            FireOneGun(human,3,r,fuse_time);
          if (!(human->s[5].flags&SSF_INACTIVE))
            FireOneGun(human,4,r,fuse_time);
        case 2:
          if (!(human->s[1].flags&SSF_INACTIVE))
            FireOneGun(human,1,r,fuse_time);
          if (!(human->s[2].flags&SSF_INACTIVE))
            FireOneGun(human,2,r,fuse_time);
        case 1:
          FireOneGun(human,0,r,fuse_time);
          break;
      }
  }
}

U0 HumanFireMissileBegin(I64 n)
{
  if (human && !human->exploding &&
        !human->spacewalk_side && tS>human->reload_timeout)
    FireOneMissile(human,n);
}

U0 HumanFireLaserBegin()
{
  if (human && !human->exploding &&
        !human->spacewalk_side && tS>human->reload_timeout)
    human->lasering=TRUE;
}
U0 HumanFireLaserEnd()
{
  if (human && !human->exploding) {
    human->lasering=FALSE;
    Snd;
  }
}

U0 SplatNew(Shot *tmps,F64 die_time,F64 start,F64 end)
{
  MyMass *tmpm;
  F64 theta=Arg(tmps->p.DxDt,tmps->p.DyDt);
  I64 i;
  for (i=0;i<tmps->splats;i++) {
    tmpm=CAlloc(sizeof(MyMass));
    D3Copy(&tmpm->x,&tmps->p.x);
    tmpm->radius=1;
    tmpm->mass=1;
    tmpm->die_timeout=tS+die_time;
    if (tmps->p.type==MT_SOLAR_FLARE)
      tmpm->type=MT_ION;
    else
      tmpm->type=MT_ANTIMATTER_SPLAT;
    D3Copy(&tmpm->DxDt,&tmps->p.DxDt);
    tmpm->DxDt+=50*Sqr(tmps->radius)*Rand*
          Sin(start+theta+(end-start)*i/tmps->splats);
    tmpm->DyDt+=50*Sqr(tmps->radius)*Rand*
          Cos(start+theta+(end-start)*i/tmps->splats);
    QueIns(tmpm,ode->last_mass);
  }
}

U0 ExpireShots()
{
  Shot *tmps=shot_head.next,*tmps1;
  while (tmps!=&shot_head) {
    tmps1=tmps->next;
    if (tS>tmps->fuse_time) {
      if (tmps->p.type==MT_SOLAR_FLARE)
        SplatNew(tmps,1.0,3*pi/8,5*pi/8);
      else
        SplatNew(tmps,.2,0,2*pi);
      QueRem(tmps);
      QueRem(&tmps->p);
      Free(tmps);
    }
    tmps=tmps1;
  }
}

U0 ExpireSplats()
{
  MyMass *tmpm,*tmpm1;
  tmpm=ode->next_mass;
  while (tmpm!=&ode->next_mass) {
    tmpm1=tmpm->next;
    if ((tmpm->type==MT_ION || tmpm->type==MT_ANTIMATTER_SPLAT) &&
          tS>tmpm->die_timeout) {
      QueRem(tmpm);
      Free(tmpm);
    }
    tmpm=tmpm1;
  }
}

U0 ExpireMissiles()
{
  I64 i;
  F64 dd,best_dd;
  Missile *tmpmi=missile_head.next,*tmpm1;
  while (tmpmi!=&missile_head) {
    tmpm1=tmpmi->next;
    if (tmpmi->launched) {
      best_dd=F64_MAX;
      if (tmpmi->target)
        for (i=0;i<tmpmi->target->masses;i++) {
          dd=D3DistSqr(&tmpmi->p_front.x,&tmpmi->target->p[i].x);
          if (dd<best_dd)
            best_dd=dd;
        }
      if (!tmpmi->exploding && (best_dd<30*30 || tS>tmpmi->fuse_time)) {
        tmpmi->p_front.mass=10.0; //They go flying, if too light.
        tmpmi->p_back.mass =10.0;
        tmpmi->exploding=TRUE;
        Noise(50,93,105);
      } else if (tS>tmpmi->die_timeout)
        MissileDel(tmpmi);
    }
    tmpmi=tmpm1;
  }
}

//********************************** AI

U0 AI()
{
  CD3 p,p1,p2;
  Ship *tmpsp=ship_head.next;
  if (human && !human->exploding) {
    while (tmpsp!=&ship_head) {
      D3Sub(&p1,&tmpsp->p[0].x,&tmpsp->p[1].x);
      D3Sub(&p2,&tmpsp->p[0].x,&tmpsp->p[2].x);
      D3Add(&p,&p1,&p2);
      D3Sub(&p1,&human->p[0].x,&tmpsp->p[0].x);
      if (D3Dot(D3Unit(&p),D3Unit(&p1))>0.995 &&
            tS>tmpsp->reload_timeout) {
        FireOneGun(tmpsp,0,1.5+.5,.4);
      }
      tmpsp=tmpsp->next;
    }
  }
}

//********************************** Init
U0 InitLevel()
{
  I64 i;
  MyMass *tmpm,*tmpm1;

  t_solar_storm=0;

  tmpm=ode->next_mass;
  while (tmpm!=&ode->next_mass) {
    tmpm1=tmpm->next;
    if (tmpm->type==MT_ION || tmpm->type==MT_ANTIMATTER_SPLAT) {
      QueRem(tmpm);
      Free(tmpm);
    }
    tmpm=tmpm1;
  }
  if (level==1)
    OneTimePopUp(&msg_flags,XMSGF_SOLAR_STORM,
          "Face away from Sun in solar storm.\n");
  if (level==4)
    OneTimePopUp(&msg_flags,XMSGF_ANTISPIN,
          "Press $GREEN$<CURSOR-DOWN>$FG$ for anti-spin stabilizer.\n");
  human=ShipNew(Fs->pix_width/2,Fs->pix_height/2,ST_HUMAN1);
  for (i=0;i<level+2;i++)
    PlaceShip(ST_ENEMY1);
  PlaceShip(ST_ENEMY2);
  show_level_msg=TRUE;
  ODEPause(ode);
}

U0 Init()
{
  I64 i;
  game_over=FALSE;
  score=0;
  level=1;

  QueInit(&ship_head);
  QueInit(&shot_head);
  QueInit(&missile_head);

  for (i=0;i<STARS_NUM;i++) {
    stars_x[i]=RandU16%GR_WIDTH;
    stars_y[i]=RandU16%GR_HEIGHT;
  }

  human_t_left=0;
  human_t_right=0;
  human_antispin=0;

  InitLevel;
}

//********************************** Main
U0 XCaliber()
{
  I64 ch,msg_code,arg1,arg2,sc;
  CCtrl *cp=CtrlPanelNew;

  SettingsPush; //See SettingsPush
  Fs->text_attr=BLACK<<4+WHITE;
  MenuPush(
        "File {"
        "  Abort(,CH_SHIFT_ESC);"
        "  Exit(,CH_ESC);"
        "}"
        "Game {"
        "  Restart(,'\n');"
        "  LevelUp(,'+');"
        "  LevelDown(,'-');"
        "}"
        "Play {"
        "  Fire(,CH_SPACE);"
        "  Thrust(,,SC_CURSOR_UP);"
        "  StopSpin(,,SC_CURSOR_DOWN);"
        "  Left(,,SC_CURSOR_LEFT);"
        "  Right(,,SC_CURSOR_RIGHT);"
        "  LeftMissile(,,SC_CURSOR_LEFT|SCF_CTRL);"
        "  RightMissile(,,SC_CURSOR_RIGHT|SCF_CTRL);"
        "  Spackwalk(,'w');"
        "  LongerFuse(,,SC_CURSOR_RIGHT|SCF_SHIFT);"
        "  ShorterFuse(,,SC_CURSOR_LEFT|SCF_SHIFT);"
        "  LargerShot(,,SC_CURSOR_UP|SCF_SHIFT);"
        "  SmallerShot(,,SC_CURSOR_DOWN|SCF_SHIFT);"
        "}"
        );
  AutoComplete;
  WinBorder;
  WinMax;
  DocCursor;
  DocClear;
  Fs->win_inhibit=WIG_TASK_DFT-WIF_SELF_FOCUS
        -WIF_SELF_BORDER-WIF_FOCUS_TASK_MENU-WIF_SELF_CTRLS;
  Fs->draw_it=&DrawIt;
  do {
    ode=ODENew(0,0.01,ODEF_HAS_MASSES);
    ode->derive=&MyDerivative;
    ode->min_tolerance=1e-9;
    ode->drag_v3=0.00001;
    Init;
    QueIns(ode,Fs->last_ode);
    ch=0;
    do {
      while (!game_over && !show_level_msg &&
            (msg_code=ScanMsg(&arg1,&arg2,1<<MSG_KEY_DOWN|1<<MSG_KEY_UP))) {
        switch (msg_code) {
          case MSG_KEY_DOWN:
            ch=arg1; sc=arg2;
            switch (ch) {
              case 0:
                switch (sc.u8[0]) {
                  case SC_CURSOR_RIGHT:
                    if (sc&SCF_CTRL)
                      HumanFireMissileBegin(0);
                    else if (sc&SCF_SHIFT)
                      ctrl_panel.fuse_time+=2;
                    else
                      human_t_right=THRUST_MAX;
                    break;
                  case SC_CURSOR_LEFT:
                    if (sc&SCF_CTRL)
                      HumanFireMissileBegin(1);
                    else if (sc&SCF_SHIFT)
                      ctrl_panel.fuse_time-=2;
                    else
                      human_t_left =THRUST_MAX;
                    break;
                  case SC_CURSOR_UP:
                    if (sc&SCF_SHIFT)
                      ctrl_panel.shot_radius+=2;
                    else {
                      human_t_right=THRUST_MAX;
                      human_t_left =THRUST_MAX;
                    }
                    break;
                  case SC_CURSOR_DOWN:
                    if (sc&SCF_SHIFT)
                      ctrl_panel.shot_radius-=2;
                    else
                      human_antispin=ANTISPIN_MAX;
                    break;
                }
                break;
              case CH_SPACE:
                if (level<4)
                  HumanFireGunBegin;
                else
                  HumanFireLaserBegin;
                break;
              case 'w':
                ctrl_panel.spacewalk=TRUE;
                break;
              case '+':
                level++;
                break;
              case '-':
                level--;
                break;
            }
            break;
          case MSG_KEY_UP:
            ch=arg1; sc=arg2;
            switch (ch) {
              case 0:
                switch (sc.u8[0]) {
                  case SC_CURSOR_RIGHT:
                    human_t_right=0;
                    break;
                  case SC_CURSOR_LEFT:
                    human_t_left =0;
                    break;
                  case SC_CURSOR_UP:
                    human_t_right=0;
                    human_t_left =0;
                    break;
                  case SC_CURSOR_DOWN:
                    human_antispin=0;
                    break;
                }
                break;
              case '\n':
                ch=0;
                break;
              case CH_SPACE:
                if (level>=4)
                  HumanFireLaserEnd;
                break;
            }
            break;
        }
      }
      AI;
      SolarFlares;
      ExpireShots;
      ExpireSplats;
      ExpireMissiles;
      CheckDamage;
      Refresh; //msgs are only qued by winmgr
      if (show_level_msg) {
        ch=GetKey(&sc);
        if (ch=='\n')
          ch=0;
        ODEPause(ode,OFF);
        show_level_msg=FALSE;
      } else if (game_over) {
        ch=ScanChar;
      } else {
        if (!remaining) {
          level++;
          ShipDel(human);
          human=NULL;
          InitLevel;
        }
      }
    } while (ch!=CH_ESC && ch!='\n' && ch!=CH_SHIFT_ESC);
    AllDel(ode);
  } while (ch!=CH_ESC && ch!=CH_SHIFT_ESC);
  SettingsPop;
  CtrlPanelDel(cp);
  MenuPop;
  RegWrite("TempleOS/XCaliber",
        "I64 best_score=%d;\n"
        "I64 msg_flags=%d;\n",best_score,msg_flags);
}