Trivial Solutions Corp.
                        Happy Programmers Make Us Happy

The LoseThos 64-bit PC Operating System
File:/LT/Apps/X-Caliber/X-Caliber.CPZ
This, and all LoseThos files, are public domain.  Do whatever you like.


/* I did not limit myself to real physics.  It's a game.
I suppose it shoots plazma blobs that have a delayed
explosion due to a chemical reaction timer.

The caliber of the gun is adjustible.

I did a simple anti-spin controller when you press cursor-down.
It's intentionally not very good.  It needs to take into
account flexing of the struts.  You'll notice when you
try to shoot the laser.

If you are interested in control theory, you might download
simstructure, a program I wrote.

http://www.losethos.com/files/SimStrSetup.exe
Product Key: L00-W10-M70

There's not sound in space and explosions
don't make shock waves, or something like that.
In X-Caliber, ships are destroyed by
causing springs to break due to acceleration
differences on different masses in a ship.

A real game would be dull with guided
missiles that always hit and lasers
that never miss.

Don't be anal.  If you aspire to making
games, do like Hollywood.  They know the
public isn't interested in realism.

*/

// ********************************** Glbls
AcctRegSetDefaultEntry("LT/XCaliber","I8 best_score=0;\r\n");
AcctRegExecuteBranch("LT/XCaliber");

class MyMass
{
  MassStruct m;
  F8 temperature;
  F8 radius;
  I8 color;
};

class MySpring
{
  SpringStruct s;
  F8 strength;
  I8 color;
};


#define SPIN_GAIN               0.25
#define MAX_MASSES              8
#define MAX_SPRINGS             16
#define ST_HUMAN1               0
#define ST_ENEMY1               1
#define ST_ENEMY2               2
class Ship
{
  Ship *next,*last;
  I8 type,masses,springs;
  MyMass   p[MAX_MASSES];
  MySpring s[MAX_SPRINGS];
  F8 fire_rate;
  F8 reload_timeout,spacewalk_timeout;
  F8 die_time,die_timeout;
  I8 spacewalk_side;
  F8 laser_temperature;
  BoolI1 lasering,exploding,laser_overheat;
} *human;

F8 human_t_left,human_t_right,human_antispin;

class Bullet
{
  Bullet *next,*last;
  F8 radius;
  F8 fuse_time,die_timeout;
  MyMass p;
  BoolI1 exploding;
};

#define MAX_THRUST      200.0
#define MAX_ANTISPIN    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
BoolI1 game_over,show_level_msg;

#define NUM_STARS       100
I8 stars_x[NUM_STARS],stars_y[NUM_STARS];

Ship ship_root;
Bullet bullet_root;
Ode *ode=NULL;
I8 level,score,remaining;

// ********************************** Pictures



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

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

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


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


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


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


// ********************************** Ship
BoolI8 CheckOverlap()
{
  D3 p;
  MyMass *tempm,*tempm1;
  tempm=ode->next_mass;
  while (tempm!=&ode->next_mass) {
    tempm1=ode->next_mass;
    while (tempm1!=&ode->next_mass) {
      if (tempm!=tempm1) {
        D3Sub(&tempm->m.x,&tempm1->m.x,&p);
        if (D3SqrNorm(&p)<=Sqr(tempm->radius+tempm1->radius))
          return TRUE;
      }
      tempm1=tempm1->m.next;
    }
    tempm=tempm->m.next;
  }
  return FALSE;
}

Ship *ShipNew(I8 x,I8 y,I8 type)
{
  I8 i;
  Ship *temps=CAlloc(sizeof(Ship));

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

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

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

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

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

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

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

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

      for (i=0;i<temps->masses;i++) {
        temps->p[i].m.mass=1;
        temps->p[i].radius=6;
        temps->p[i].m.drag_profile_factor=5;
        temps->p[i].color=PURPLE;
        InsQue(&temps->p[i].m,ode->last_mass);
      }

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

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

U0 ShipDel(Ship *temps)
{
  I8 i;
  if (!temps) return;
  for (i=0;i<temps->masses;i++)
    RemQue(&temps->p[i].m);
  for (i=0;i<temps->springs;i++)
    RemQue(&temps->s[i]);
  RemQue(temps);
  Free(temps);
  remaining--;
}

U0 PlaceShip(I8 type)
{
  Ship *ee;
  if (CheckOverlap)
    return;
  while (TRUE) {
    ee=ShipNew(RandU2%(Fs->win_pixel_width-20)+10,RandU2%(Fs->win_pixel_height-20)+10,type);
    if (CheckOverlap)
      ShipDel(ee);
    else
      break;
  }
}

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

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

U0 AllDel(Ode *ode)
{
  Ship *temps,*temps1;
  Bullet *tempb=bullet_root.next,*tempb1;
  RemQue(ode);
  temps=ship_root.next;
  while (temps!=&ship_root) {
    temps1=temps->next;
    ShipDel(temps);
    temps=temps1;
  }
  human=NULL;
  while (tempb!=&bullet_root) {
    tempb1=tempb->next;
    RemQue(tempb);
    Free(tempb);
    tempb=tempb1;
  }
  OdeDel(ode);
}

BoolI8 LaserPlot(GrBitMap *base,I8 x,I8 y,I8 z)
{
  I8 c;
  nounusedwarn z;
  c=GrPeek(base,x,y);
  if (c!=BLACK && c!=WHITE)
    return FALSE;
  else {
    GrPlot(base,x,y);
    return TRUE;
  }
}

// **********************************
U0 UpdateWin(TaskStruct *task)
{
  I8 i,j;
  GrBitMap *base=GrAlias(gr_refreshed_base,task);
  F8 arg;
  Ship *temps;
  Bullet *tempb;
  D3 p,p1,p2;
  F8 t_left,t_right,spin,d,x,y;
  MySpring *tempss;
  U1 *img;
  BoolI1 draw_laser_line=FALSE;

  if (ode!=task->last_ode) goto done_draw;

  base->color=WHITE;
  GrPrintF(base,0,0,"Level:%d Score:%d High Score:%d",level,score,best_score);
  if (game_over) {
    if (Blink(,tP(task)))
      GrPutS(base,(task->win_pixel_width-9*FONT_WIDTH)/2,
          (task->win_pixel_height-FONT_HEIGHT)/2,"Game Over");
  } else if (show_level_msg) {
    if (Blink(,tP(task)))
      GrPrintF(base,(task->win_pixel_width-8*FONT_WIDTH)/2,
          (task->win_pixel_height-FONT_HEIGHT)/2+50,"Level %d",level);
  }

  for (i=0;i<NUM_STARS;i++)
    GrPlot(base,stars_x[i],stars_y[i]);

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

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

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


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

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

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

          if (tP(task)>temps->reload_timeout)
            img=<gun_ready>;
          else
            img=<gun_busy>;
          arg=Arg(p.x,p.y);
          switch (level) {
            case 3:
              if (!(temps->s[3].s.flags&SSF_INACTIVE))
                GrElemsPlotRotZ(base,temps->p[3].m.x,temps->p[3].m.y,0,img,arg);
              if (!(temps->s[5].s.flags&SSF_INACTIVE))
                GrElemsPlotRotZ(base,temps->p[4].m.x,temps->p[4].m.y,0,img,arg);
            case 2:
              if (!(temps->s[1].s.flags&SSF_INACTIVE))
                GrElemsPlotRotZ(base,temps->p[1].m.x,temps->p[1].m.y,0,img,arg);
              if (!(temps->s[2].s.flags&SSF_INACTIVE))
                GrElemsPlotRotZ(base,temps->p[2].m.x,temps->p[2].m.y,0,img,arg);
            case 1:
              GrElemsPlotRotZ(base,temps->p[0].m.x,temps->p[0].m.y,0,img,arg);
              break;
            default:
              GrElemsPlotRotZ(base,temps->p[0].m.x,temps->p[0].m.y,0,<Laser>,arg);
              if (temps->lasering && !temps->laser_overheat) {
                draw_laser_line=TRUE;
                Snd(1000);
              }
              break;
          }

          ctrl_panel.laser_temperature=temps->laser_temperature;

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

  tempb=bullet_root.next;
  while (tempb!=&bullet_root) {
    if (tP(task)>tempb->fuse_time) {
      d=(tP(task)-tempb->fuse_time)/(tempb->die_timeout-tempb->fuse_time);
      d=7*Sin(pi*d)*tempb->radius+1;
      for (i=1;i<d;i++) {
        if (i&1)
          base->color=YELLOW;
        else
          base->color=LTRED;
        GrCircle(base,tempb->p.m.x,tempb->p.m.y,i);
      }
      } else {
      if (tempb->radius<1.0) {
        base->color=LTGREEN;
        GrPlot(base,tempb->p.m.x,tempb->p.m.y);
      } else {
        base->color=YELLOW;
        GrCircle(base,tempb->p.m.x,tempb->p.m.y,tempb->radius);
        if (tempb->radius>=2.0)
          GrFloodFill(base,tempb->p.m.x,tempb->p.m.y,TRUE);
        base->color=LTGREEN;
        GrCircle(base,tempb->p.m.x,tempb->p.m.y,tempb->radius);
      }
    }
    tempb=tempb->next;
  }

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

done_draw:
  GrDel(base);
}

U0 Explosion(MyMass *tempm1,F8 r)
{
  MyMass *tempm;
  D3 p1;
  F8 d;

  tempm=ode->next_mass;
  while (tempm!=&ode->next_mass) {
    if (tempm!=tempm1) {
      D3Sub(&tempm->m.state->x,&tempm1->m.state->x,&p1);
      d=D3SqrNorm(&p1)-tempm->radius*tempm->radius;
      if (d<100.0*100.0) {
        if (d<1)
          d=1;
        else
          d=Sqrt(d);
        d=2e8*r`7/d`6;
        if (d>1e5) d=1e5;
        D3MulEqu(&p1,d);
        D3AddEqu(&tempm->m.DstateDt->DxDt,&p1);
      }
    }
    tempm=tempm->m.next;
  }
}

U0 MyDerivative(Ode *ode,F8 t,Order2D3 *state,Order2D3 *DstateDt)
{
  nounusedwarn t,state,DstateDt;
  I8 i;
  F8 d,dd,dd2,spin,t_left,t_right;
  TaskStruct *task=ode->win_task;
  D3 p,p1,p2;
  Bullet *tempb;
  Ship *temps;
  MyMass *tempm,*tempm1;

  tempm=ode->next_mass;
  while (tempm!=&ode->next_mass) {
    d=tempm->m.state->x;
    if (d-tempm->radius<0)
      tempm->m.DstateDt->DxDt+=Sqr(Sqr(Sqr(d-tempm->radius)));
    if (d+tempm->radius>task->win_pixel_width)
      tempm->m.DstateDt->DxDt-=Sqr(Sqr(Sqr((d+tempm->radius)-task->win_pixel_width)));

    d=tempm->m.state->y;
    if (d-tempm->radius<0)
      tempm->m.DstateDt->DyDt+=Sqr(Sqr(Sqr(d-tempm->radius)));
    if (d+tempm->radius>task->win_pixel_height)
      tempm->m.DstateDt->DyDt-=Sqr(Sqr(Sqr((d+tempm->radius)-task->win_pixel_height)));
    tempm1=ode->next_mass;
    while (tempm1!=&ode->next_mass) {
      if (tempm!=tempm1) {
        D3Sub(&tempm->m.state->x,&tempm1->m.state->x,&p);
        dd=D3SqrNorm(&p);
        dd2=Sqr(tempm->radius+tempm1->radius);
        if (dd<=dd2) {
          d=Sqrt(dd)+0.0001;
          D3MulEqu(&p,Sqr(Sqr(dd2-dd))/d);
          D3AddEqu(&tempm ->m.DstateDt->DxDt,&p);
          D3SubEqu(&tempm1->m.DstateDt->DxDt,&p);
        }
      }
      tempm1=tempm1->m.next;
    }
    tempm=tempm->m.next;
  }


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

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

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

  tempb=bullet_root.next;
  while (tempb!=&bullet_root) {
    if (tempb->fuse_time<=tP(ode->win_task)<=tempb->die_timeout)
      Explosion(&tempb->p.m,tempb->radius);
    tempb=tempb->next;
  }
}


U0 CheckDamage()
{
  I8 i,j,k;
  Ship *temps,*temps1;
  MyMass *tempm,*best_mass;
  D3 p,p1,p2;
  F8 d,best_distance;

  tempm=ode->next_mass;
  while (tempm!=&ode->next_mass) {
    tempm->temperature*=0.9;
    tempm=tempm->m.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_MAX_TEMP) {
        human->laser_overheat=TRUE;
        Snd(0);
      } else {
        human->laser_temperature+=1.0;
        D3Sub(&human->p[0].m.x,&human->p[1].m.x,&p1);
        D3Sub(&human->p[0].m.x,&human->p[2].m.x,&p2);
        D3Unit(D3Add(&p1,&p2,&p));
        p2.x=p.y;
        p2.y=-p.x;
        p2.z=0;
        best_mass=NULL;
        best_distance=MAX_F8;
        tempm=ode->next_mass;
        while (tempm!=&ode->next_mass) {
          D3Sub(&human->p[0].m.x,&tempm->m.x,&p1);
          if (Abs(D3Dot(&p1,&p2))<tempm->radius &&
              D3Dot(&p1,&p)<0.0) {
            d=D3SqrNorm(&p1);
            if (d<best_distance) {
              best_distance=d;
              best_mass=tempm;
            }
          }
          tempm=tempm->m.next;
        }
        if (best_mass)
          best_mass->temperature+=1.0;
      }
    }
  }

  temps=ship_root.next;
  while (temps!=&ship_root) {
    temps1=temps->next;
    switch (temps->type) {
      case ST_HUMAN1:
        if (temps->exploding) {
          if (tP>temps->die_timeout)  {
            ShipDel(temps);
            human=NULL;
          }
        } else
          for (i=0;i<temps->springs;i++) {
            if (Abs(temps->s[i].s.f)>temps->s[i].strength)
              temps->s[i].s.flags|=SSF_INACTIVE;
            if (temps->s[i].s.flags&SSF_INACTIVE) {
              if (i<3) {
                if (!temps->exploding) {
                  temps->exploding=TRUE;
                  temps->die_time=tP;
                  temps->die_timeout=tP+0.75;
                  game_over=TRUE;
                  Noise(750,1000,2000);
                  break;
                }
              }
            }
          }
          break;
      default:
        if (temps->exploding) {
          if (tP>temps->die_timeout) {
            ShipDel(temps);
            score+=level;
            if (score>best_score)
              best_score=score;
          }
        } else {
          j=0;
          for (i=0;i<temps->springs;i++) {
            if (temps->s[i].s.flags&SSF_INACTIVE)
              j++;
            else if (Abs(temps->s[i].s.f)>temps->s[i].strength) {
              temps->s[i].s.flags|=SSF_INACTIVE;
              j++;
            }
          }
          k=0;
          for (i=0;i<temps->masses;i++) {
            if (temps->p[i].temperature>MASS_MAX_TEMP)
              k++;
          }
          if (j>1 || k) {
            temps->exploding=TRUE;
            temps->die_time=tP;
            temps->die_timeout=tP+0.75;
            Noise(750,1000,3000);
          }
        }
        break;
    }
    temps=temps1;
  }
}

// ********************************** Bullets

U0 FireOneGun(Ship *temps,I8 g,F8 r,F8 fuse_time)
{
  D3 p,p1,p2;
  Bullet *tempb;

  tempb=CAlloc(sizeof(Bullet));
  D3Copy(&tempb->p.m.x,&temps->p[g].m.x);
  tempb->radius=r;
  tempb->fuse_time=tP+1.5*fuse_time;
  tempb->die_timeout=tempb->fuse_time+0.125;
  tempb->p.m.mass=0.3*r*r*r;
  D3Sub(&temps->p[0].m.x,&temps->p[1].m.x,&p1);
  D3Sub(&temps->p[0].m.x,&temps->p[2].m.x,&p2);
  D3Add(&p1,&p2,&p);
  D3AddEqu(&tempb->p.m.x,D3MulEqu(D3Copy(&p1,D3Unit(&p)),r+temps->p[0].radius+5));
  D3AddEqu(D3MulEqu(&p,1000/(r+1)),&temps->p[g].m.DxDt);
  D3Copy(&tempb->p.m.DxDt,&p);
  D3MulEqu(&p,tempb->p.m.mass/temps->p[g].m.mass/100.0);
  D3SubEqu(&temps->p[g].m.DxDt,&p);
  InsQue(&tempb->p.m,ode->last_mass);
  InsQue(tempb,bullet_root.last);
  temps->reload_timeout=tP+r/temps->fire_rate;
  Noise(100,r*200,r*400);
}

U0 HumanFireBegin()
{

  F8 r=3.0*ctrl_panel.bullet_radius/CTRL_PANEL_RANGE+0.5,
     fuse_time=ToF8(ctrl_panel.fuse_time+1)/CTRL_PANEL_RANGE;
  if (human) {
    if (!human->exploding && !human->spacewalk_side && tP>human->reload_timeout)
      switch (level) {
        case 3:
          if (!(human->s[3].s.flags&SSF_INACTIVE))
            FireOneGun(human,3,r,fuse_time);
          if (!(human->s[5].s.flags&SSF_INACTIVE))
            FireOneGun(human,4,r,fuse_time);
        case 2:
          if (!(human->s[1].s.flags&SSF_INACTIVE))
            FireOneGun(human,1,r,fuse_time);
          if (!(human->s[2].s.flags&SSF_INACTIVE))
            FireOneGun(human,2,r,fuse_time);
        case 1:
          FireOneGun(human,0,r,fuse_time);
          break;
        default:
          human->lasering=TRUE;
          break;
      }
  }
}

U0 HumanFireEnd()
{
  if (human && !human->exploding)
    switch (level) {
      case 3:
      case 2:
      case 1:
        break;
      default:
        human->lasering=FALSE;
        Snd(0);
        break;
    }
}

U0 ExpireShots()
{
  Bullet *tempb=bullet_root.next,*tempb1;
  while (tempb!=&bullet_root) {
    tempb1=tempb->next;
    if (tP>tempb->fuse_time && !tempb->exploding) {
      tempb->exploding=TRUE;
      Noise(50,3000,6000);
    }
    if (tP>tempb->die_timeout) {
      RemQue(tempb);
      RemQue(&tempb->p.m);
      Free(tempb);
    }
    tempb=tempb1;
  }
}

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

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

// ********************************** Init


U0 InitLevel()
{
  I8 i;
  human=ShipNew(Fs->win_pixel_width/2,Fs->win_pixel_height/2,ST_HUMAN1);
  for (i=0;i<level+2;i++)
    PlaceShip(ST_ENEMY1);
  PlaceShip(ST_ENEMY2);
  show_level_msg=TRUE;
  ode->flags|=ODEF_PAUSED;
}

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

  ship_root.next=ship_root.last=&ship_root;
  bullet_root.next=bullet_root.last=&bullet_root;

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

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

  InitLevel;
}


// ********************************** Main

U0 XCaliber()
{
  U8 ch,msg_code,p1,p2,sc;
  Ctrl *cp=CtrlPanelNew;

  SettingsPush; //See SettingsPush
  Preempt(OFF);
  Fs->text_attr=BLACK<<4+WHITE;
  MenuPush(
  "File {"
  "  Abort(,CH_CTRLQ);"
  "  Exit(,CH_ESC);"
  "}"
  "Play {"
  "  Restart(,CH_CR);"
  "  Fire(,CH_SPACE);"
  "  Thrust(,,SC_CURSOR_UP);"
  "  StopSpin(,,SC_CURSOR_DOWN);"
  "  Left(,,SC_CURSOR_LEFT);"
  "  Right(,,SC_CURSOR_RIGHT);"
  "  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);"
      "}"
      );
  WinMax;
  WordStat(OFF);
  WinBorder(ON);
  win_inhibit=WIF_ALL-WIF_BORDER-WIF_MENU-WIF_CTRLS;
  Fs->update_win=&UpdateWin;
  do {
    ode=OdeNew(0,0.01,ODEF_HAS_MASSES);
    ode->derivative=&MyDerivative;
    ode->min_tolerance=1e-9;
    ode->drag_v3=0.00001;
    Init;
    InsQue(ode,Fs->last_ode);
    ch=0;
    do {
      while (!game_over && !show_level_msg &&
          (msg_code=ScanMsg(&p1,&p2,1<<MSG_KEY_DOWN|1<<MSG_KEY_UP))) {
        switch (msg_code) {
          case MSG_KEY_DOWN:
            ch=p1; sc=p2;
            switch (ch) {
              case 0:
                switch (sc.u1[0]) {
                  case SC_CURSOR_RIGHT:
                    if (sc&SCF_SHIFT)
                      ctrl_panel.fuse_time+=2;
                    else
                      human_t_right=MAX_THRUST;
                    break;
                  case SC_CURSOR_LEFT:
                    if (sc&SCF_SHIFT)
                      ctrl_panel.fuse_time-=2;
                    else
                      human_t_left =MAX_THRUST;
                    break;
                  case SC_CURSOR_UP:
                    if (sc&SCF_SHIFT)
                      ctrl_panel.bullet_radius+=2;
                    else {
                      human_t_right=MAX_THRUST;
                      human_t_left =MAX_THRUST;
                    }
                    break;
                  case SC_CURSOR_DOWN:
                    if (sc&SCF_SHIFT)
                      ctrl_panel.bullet_radius-=2;
                    else
                      human_antispin=MAX_ANTISPIN;
                    break;
                }
                break;
              case CH_SPACE:
                HumanFireBegin;
                break;
              case 'w':
                ctrl_panel.spacewalk=TRUE;
                break;
            }
            break;
          case MSG_KEY_UP:
            ch=p1; sc=p2;
            switch (ch) {
              case 0:
                switch (sc.u1[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 CH_CR:
                ch=0;
                break;
              case CH_SPACE:
                HumanFireEnd;
                break;
            }
            break;
        }
      }
      AI;
      ExpireShots;
      CheckDamage;
      WinSync; //msgs are only queued by winmngr
      if (show_level_msg) {
        ch=GetKey(&sc);
        if (ch==CH_CR)
          ch=0;
        ode->flags&=~ODEF_PAUSED;
        show_level_msg=FALSE;
      } else if (game_over) {
        ch=ScanChar;
      } else {
        if (!remaining) {
          level++;
          ShipDel(human);
          human=NULL;
          InitLevel;
        }
      }
    } while (ch!=CH_ESC && ch!=CH_CR && ch!=CH_CTRLQ);
    AllDel(ode);
  } while (ch!=CH_ESC && ch!=CH_CTRLQ);
  SettingsPop;
  CtrlPanelDel(cp);
  MenuPop;
  AcctRegWriteBranch("LT/XCaliber","I8 best_score=%d;\r\n",best_score);
}