Trivial Solutions
                        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.
God is watching; God is just; God is never out-done in generosity.  He talks to me!

LoseThos code compiles with the 64-bit LoseThos compiler/assembler I wrote.
Boot the LoseThos CD and you can compile it.  Not in Kansas anymore!

Click here for preliminary compiler information you need.

/* 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
AcctRegSetDftEntry("LT/XCaliber","I64 best_score=0;\r\n");
AcctRegExecuteBranch("LT/XCaliber");

class MyMass
{
  MassStruct m;
  F64 temperature;
  F64 radius;
  I64 color;
};

class MySpring
{
  SpringStruct s;
  F64 strength;
  I64 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;
  I64 type,masses,springs;
  MyMass   p[MAX_MASSES];
  MySpring s[MAX_SPRINGS];
  F64 fire_rate;
  F64 reload_timeout,spacewalk_timeout;
  F64 die_time,die_timeout;
  I64 spacewalk_side;
  F64 laser_temperature;
  BoolI8 lasering,exploding,laser_overheat;
} *human;

F64 human_t_left,human_t_right,human_antispin;

class Bullet
{
  Bullet *next,*last;
  F64 radius;
  F64 fuse_time,die_timeout;
  MyMass p;
  BoolI8 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
BoolI8 game_over,show_level_msg;

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

Ship ship_root;
Bullet bullet_root;
Ode *ode=NULL;
I64 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
BoolI64 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(I64 x,I64 y,I64 type)
{
  I64 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)
{
  I64 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(I64 type)
{
  Ship *ee;
  if (CheckOverlap)
    return;
  while (TRUE) {
    ee=ShipNew(RandU16%(Fs->win_pixel_width-20)+10,RandU16%(Fs->win_pixel_height-20)+10,type);
    if (CheckOverlap)
      ShipDel(ee);
    else
      break;
  }
}

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

I64 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);
}

BoolI64 LaserPlot(GrBitMap *base,I64 x,I64 y,I64 z)
{
  I64 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)
{
  I64 i,j;
  GrBitMap *base=GrAlias(gr_refreshed_base,task);
  F64 arg;
  Ship *temps;
  Bullet *tempb;
  D3 p,p1,p2;
  F64 t_left,t_right,spin,d,x,y;
  MySpring *tempss;
  U8 *img;
  BoolI8 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);
            GrElemsPlotRotZ3b(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);
            GrElemsPlotRotZ3b(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))
                GrElemsPlotRotZ3b(base,temps->p[3].m.x,temps->p[3].m.y,0,img,arg);
              if (!(temps->s[5].s.flags&SSF_INACTIVE))
                GrElemsPlotRotZ3b(base,temps->p[4].m.x,temps->p[4].m.y,0,img,arg);
            case 2:
              if (!(temps->s[1].s.flags&SSF_INACTIVE))
                GrElemsPlotRotZ3b(base,temps->p[1].m.x,temps->p[1].m.y,0,img,arg);
              if (!(temps->s[2].s.flags&SSF_INACTIVE))
                GrElemsPlotRotZ3b(base,temps->p[2].m.x,temps->p[2].m.y,0,img,arg);
            case 1:
              GrElemsPlotRotZ3b(base,temps->p[0].m.x,temps->p[0].m.y,0,img,arg);
              break;
            default:
              GrElemsPlotRotZ3b(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);
              }
              GrElemsPlotRotZ3b(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);
          GrElemsPlotRotZ3b(base,temps->p[0].m.x,temps->p[0].m.y,0,img,arg);
          arg=Arg(p1.x,p1.y);
          GrElemsPlotRotZ3b(base,temps->p[0].m.x+p1.x,temps->p[0].m.y+p1.y,0,<EnemySide>,arg);
          arg=Arg(p2.x,p2.y);
          GrElemsPlotRotZ3b(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,F64 r)
{
  MyMass *tempm;
  D3 p1;
  F64 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,F64 t,Order2D3 *state,Order2D3 *DstateDt)
{
  nounusedwarn t,state,DstateDt;
  I64 i;
  F64 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()
{
  I64 i,j,k;
  Ship *temps,*temps1;
  MyMass *tempm,*best_mass;
  D3 p,p1,p2;
  F64 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_F64;
        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,I64 g,F64 r,F64 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()
{

  F64 r=3.0*ctrl_panel.bullet_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 && 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()
{
  I64 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()
{
  I64 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]=RandU16%GR_WIDTH;
    stars_y[i]=RandU16%GR_HEIGHT;
  }

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

  InitLevel;
}


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

U0 XCaliber()
{
  U64 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);
  Fs->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.u8[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.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 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","I64 best_score=%d;\r\n",best_score);
}