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

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


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


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

#define RADIUS  7

class MyMass:CMass
{
  U8 *img;
};

class MySpring:CSpring
{
  I64 type,action_key;
};

//Main Modses
#define MMD_EDIT        0
#define MMD_PLAY        1
#define MMD_MODES_NUM   2
CColorROPU32 main_mode_colors[MMD_MODES_NUM]={LTRED,LTGREEN};
DefineLstLoad("ST_MAIN_MODES","Edit\0Play\0");
CCtrlBttnState main_mode_bttn;

//Edit Modes
#define EMD_MASS        0
#define EMD_SPRING      1
#define EMD_CONNECTOR   2
#define EMD_THRUSTER    3
#define EMD_MOVE        4
#define EMD_MODES_NUM   5
CColorROPU32 edit_mode_colors[EMD_MODES_NUM]={LTGRAY,LTCYAN,CYAN,YELLOW,LTBLUE};
DefineLstLoad("ST_EDIT_MODES","Mass\0Spring\0Connector\0Thruster\0Move\0");
CCtrlBttnState edit_mode_bttn;

CTask *task;
F64 zoom;
I64 next_action_key,action_scan_codes[10];
CMathODE *ode=NULL;

U0 S2W(F64 sx,F64 sy,F64 *_wx,F64 *_wy)
{
  sx-=task->pix_left+task->scroll_x;
  sy-=task->pix_top +task->scroll_y;
  *_wx=sx/zoom;
  *_wy=sy/zoom;
}

U0 W2S(F64 wx,F64 wy,F64 *_sx,F64 *_sy)
{
  *_sx=wx*zoom; *_sy=wy*zoom;
}

#define ZOOM_STEPS      20

U0 Zoom(F64 d)
{
  F64 sx,sy,wx,wy;
  I64 i,x=ms.pos.x,y=ms.pos.y;
  d=Exp(Ln(d)/ZOOM_STEPS);
  for (i=0;i<ZOOM_STEPS;i++) {
    S2W(x,y,&wx,&wy);
    zoom=Clamp(zoom*d,0.02,50);
    W2S(wx,wy,&sx,&sy);
    task->scroll_x=ms.pos.x-sx-task->pix_left;
    task->scroll_y=ms.pos.y-sy-task->pix_top;
    Sleep(10);
  }
}

U0 DrawIt(CTask *,CDC *dc)
{
  I64 i;
  F64 theta,d,x1,y1,x2,y2;
  MyMass   *tmpm;
  MySpring *tmps;

  tmpm=ode->next_mass;
  if (tmpm!=&ode->next_mass) {
    task->scroll_x=-tmpm->x*zoom+task->pix_width >>1;
    task->scroll_y=-tmpm->y*zoom+task->pix_height>>1;
  }

  dc->flags|=DCF_TRANSFORMATION;
  Mat4x4Scale(dc->r,zoom);

  switch (main_mode_bttn.state) {
    case MMD_EDIT:
      task->text_attr=DKGRAY<<4+WHITE;
      dc->color=BLACK;
      break;
    case MMD_PLAY:
      task->text_attr=BLACK<<4+WHITE;
      dc->color=WHITE;
      for (i=0;i<STARS_NUM;i++)
        GrPlot3(dc,stars_x[i],stars_y[i],0);
      break;
  }

  if (main_mode_bttn.state==MMD_EDIT) {
    if (edit_mode_bttn.state==EMD_CONNECTOR) {
      dc->color=CYAN;
      S2W(FONT_WIDTH*11,FONT_HEIGHT*7,&x1,&y1);
      GrPutChar3(dc,x1,y1,0,next_action_key);
    } else if (edit_mode_bttn.state==EMD_THRUSTER) {
      dc->color=YELLOW;
      S2W(FONT_WIDTH*11,FONT_HEIGHT*7,&x1,&y1);
      GrPutChar3(dc,x1,y1,0,next_action_key);
    }
  }

  tmps=ode->next_spring;
  while (tmps!=&ode->next_spring) {
    if (tmps->type==EMD_SPRING) {
      dc->color=LTCYAN;
      GrLine3(dc,tmps->end1->x,tmps->end1->y,0,
            tmps->end2->x,tmps->end2->y,0);
    } else if (tmps->type==EMD_CONNECTOR) {
      dc->color=CYAN;
      GrLine3(dc,tmps->end1->x,tmps->end1->y,0,
            tmps->end2->x,tmps->end2->y,0);
    }
    tmps=tmps->next;
  }

  dc->color=LTGRAY;
  tmpm=ode->next_mass;
  while (tmpm!=&ode->next_mass) {
    Sprite3(dc,tmpm->x,tmpm->y,0,tmpm->img);
    tmpm=tmpm->next;
  }

  tmps=ode->next_spring;
  while (tmps!=&ode->next_spring) {
    x1=tmps->end1->x; y1=tmps->end1->y;
    x2=tmps->end2->x; y2=tmps->end2->y;
    if (tmps->type==EMD_THRUSTER) {
      theta=Arg(x2-x1,y2-y1);
      if (Bt(kbd.down_bitmap,action_scan_codes[tmps->action_key-'0'])) {
        dc->flags|=DCF_SYMMETRY;
        DCSymmetry3Set(dc,x1,y1,256,x1,y1,0,x1-256*Cos(theta),y1-256*Sin(theta),0);
        for (i=0;i<8;i++) {
          d=20*Rand+6;
          if (d<10)
            dc->color=BLUE;
          else if (d<16)
            dc->color=LTBLUE;
          else
            dc->color=YELLOW;
          GrLine3(dc,x1-3*Cos(theta),y1-3*Sin(theta),0,
                x1-d*Cos(theta)+0.5*d*Sin(theta),
                y1-d*Sin(theta)-0.5*d*Cos(theta),0);
          GrLine3(dc,x1-2*d*Cos(theta),y1-2*d*Sin(theta),0,
                x1-d*Cos(theta)+0.5*d*Sin(theta),
                y1-d*Sin(theta)-0.5*d*Cos(theta),0);
        }
        dc->flags&=~DCF_SYMMETRY;
      }
      Sprite3ZB(dc,x1,y1,0,<3>,theta);
      if (zoom>0.5) {
        dc->color=YELLOW;
        GrPutChar3(dc,(x1*7+x2)/8,(y1*7+y2)/8,0,tmps->action_key);
      }
    } else if (tmps->type==EMD_CONNECTOR) {
      if (zoom>0.5) {
        dc->color=CYAN;
        GrPutChar3(dc,(x1*7+x2)/8,(y1*7+y2)/8,0,tmps->action_key);
      }
    }
    tmps=tmps->next;
  }
}

U0 MyDerivative(CMathODE *ode,F64,COrder2D3 *,COrder2D3 *)
{
//The forces due to springs and drag are
  //automatically handled by the
  //ode code.  We can add new forces
  //here.
  F64 d,dd;
  CD3 p;
  MyMass *tmpm1,*tmpm2;
  MySpring *tmps;

  tmpm1=ode->next_mass;
  while (tmpm1!=&ode->next_mass) {
    tmpm2=tmpm1->next;
    while (tmpm2!=&ode->next_mass) {
      D3Sub(&p,&tmpm2->state->x,&tmpm1->state->x);
      dd=D3NormSqr(&p);
      if (dd<=Sqr(2*RADIUS)) {
        d=Sqrt(dd)+0.0001;
        dd=10.0*Sqr(Sqr(Sqr(2*RADIUS)-dd));
        D3MulEqu(&p,dd/d);
        D3AddEqu(&tmpm2->DstateDt->DxDt,&p);
        D3SubEqu(&tmpm1->DstateDt->DxDt,&p);
      }
      tmpm2=tmpm2->next;
    }
    tmpm1=tmpm1->next;
  }

  tmps=ode->next_spring;
  while (tmps!=&ode->next_spring) {
    if (main_mode_bttn.state==MMD_PLAY && tmps->type==EMD_THRUSTER &&
          Bt(kbd.down_bitmap,action_scan_codes[tmps->action_key-'0'])) {
      D3Sub(&p,&tmps->end2->state->x,&tmps->end1->state->x);
      D3Unit(&p);
      D3MulEqu(&p,2000);
      D3AddEqu(&tmps->end1->DstateDt->DxDt,&p);
    }
    tmps=tmps->next;
  }
}

MyMass *PlaceMass(I64 x, I64 y)
{
  MyMass *tmpm=CAlloc(sizeof(MyMass));
  tmpm->mass=1.0;
  tmpm->drag_profile_factor=100.0;
  tmpm->x=x;
  tmpm->y=y;
  QueIns(tmpm,ode->last_mass);
  return tmpm;
}

MySpring *PlaceSpring(MyMass *tmpm1,MyMass *tmpm2,I64 type)
{
  MySpring *tmps=CAlloc(sizeof(MySpring));
  F64 d=D3Dist(&tmpm1->x,&tmpm2->x);
  tmps->end1=tmpm1;
  tmps->end2=tmpm2;
  tmps->rest_len=d;
  tmps->type=type;
  if (type==EMD_THRUSTER)
    tmps->const=0;
  else
    tmps->const=2500000/Sqr(d);
  tmps->action_key=next_action_key;
  QueIns(tmps,ode->last_spring);
  return tmps;
}

U0 CenterMasses()
{
  CD3 p;
  MyMass *tmpm1,*tmpm2;
  tmpm1=ode->next_mass;
  if (tmpm1!=&ode->next_mass) {
    D3Copy(&p,&tmpm1->x);
    tmpm2=ode->next_mass;
    while (tmpm2!=&ode->next_mass) {
      D3SubEqu(&tmpm2->x,&p);
      tmpm2=tmpm2->next;
    }
  }
}

U0 NullSprings()
{
  MySpring *tmps=ode->next_spring;
  while (tmps!=&ode->next_spring) {
    tmps->rest_len=D3Dist(&tmps->end1->x,&tmps->end2->x);
    tmps=tmps->next;
  }
}

U0 BreakConnectors()
{
  MySpring *tmps=ode->next_spring,*tmps1;
  while (tmps!=&ode->next_spring) {
    tmps1=tmps->next;
    if (tmps->type==EMD_CONNECTOR &&
          Bt(kbd.down_bitmap,action_scan_codes[tmps->action_key-'0'])) {
      QueRem(tmps);
      Free(tmps);
    }
    tmps=tmps1;
  }
}

U0 Init()
{
  I64 i;
  task=Fs;
  for (i=0;i<=9;i++)
    action_scan_codes[i]=Char2ScanCode('0'+i);
  for (i=0;i<STARS_NUM;i++) {
    stars_x[i]=RandU32%8192-4096;
    stars_y[i]=RandU32%8192-4096;
  }
  next_action_key='1';
  zoom=1.0;
  ode=ODENew(0,1e-4,ODEF_HAS_MASSES);
  ode->derive=&MyDerivative;
  ode->acceleration_limit=5e3;
  ode->drag_v2=0.000002;
  ode->drag_v3=0.0000001;
  QueIns(ode,Fs->last_ode);
}

U0 CleanUp()
{
  if (ode) {
    QueRem(ode);
    QueDel(&ode->next_mass,TRUE);
    QueDel(&ode->next_spring,TRUE);
    ODEDel(ode);
    ode=NULL;
  }
}

U0 PlayShip()
{
  I64 arg1,arg2;
  F64 last_noise=0;
  Bool okay;
  ODEPause(ode,OFF);
  MenuPush(
        "File {"
        "  Abort(,CH_SHIFT_ESC);"
        "  Exit(,CH_ESC);"
        "}"
        "Edit {"
        "  EditShip(,CH_SPACE);"
        "}"
        "Play {"
        "  Center(,'c');"
        "  Damp(,'d');"
        "  Action0(,'0');"
        "  Action1(,'1');"
        "  Action2(,'2');"
        "  Action3(,'3');"
        "  Action4(,'4');"
        "  Action5(,'5');"
        "  Action6(,'6');"
        "  Action7(,'7');"
        "  Action8(,'8');"
        "  Action9(,'9');"
        "}"
        "View {"
        "  ZoomIn(,'z');"
        "  ZoomOut(,'Z');"
        "}"
        );
  DocClear;
  try {
    while (main_mode_bttn.state==MMD_PLAY) {
      BreakConnectors;
      switch (ScanMsg(&arg1,&arg2,1<<MSG_KEY_DOWN|1<<MSG_KEY_UP)) {
        case MSG_KEY_DOWN:
          switch (arg1) {
            case '0'...'9':
              if (tS>last_noise+0.25) {
                Noise(250,18,46);
                last_noise=tS;
              }
              break;
            case 'z':
              Spawn(&Zoom,2.0(I64));
              break;
            case 'Z':
              Spawn(&Zoom,0.5(I64));
              break;
            case 'c':
              CenterMasses;
              break;
            case 'd':
              ode->drag_v2=0.002;
              ode->drag_v3=0.0001;
              break;
            case CH_SPACE:
              if (++main_mode_bttn.state==MMD_MODES_NUM)
                main_mode_bttn.state=0;
              GetMsg(,,1<<MSG_KEY_UP);
              goto ps_done;
            case CH_SHIFT_ESC:
            case CH_ESC:
              throw;
          }
          break;
        case MSG_KEY_UP:
          switch (arg1) {
            case 'd':
              ode->drag_v2=0.000002;
              ode->drag_v3=0.0000001;
              break;
          }
          break;
      }
      Refresh;
    }
ps_done: //Don't goto out of try
    okay=TRUE;
  } catch {
    Fs->catch_except=TRUE;
    okay=FALSE;
  }
  MenuPop;
  if (!okay)
    throw;
}

U0 EditShip()
{
  I64 arg1,arg2;
  F64 wx,wy;
  Bool okay;
  MyMass *tmpm1=NULL,*tmpm2=NULL;
  CCtrl *bt_edit_mode=CtrlBttnNew(0,5*FONT_HEIGHT+4,80,,EMD_MODES_NUM,
        Define("ST_EDIT_MODES"),edit_mode_colors,&edit_mode_bttn);
  ODEPause(ode);
  MenuPush(
        "File {"
        "  Abort(,CH_SHIFT_ESC);"
        "  Exit(,CH_ESC);"
        "}"
        "Play {"
        "  PlayShip(,CH_SPACE);"
        "  Center(,'c');"
        "  Damp(,'d');"
        "}"
        "Edit {"
        "  Move(,'v');"
        "  Mass(,'m');"
        "  Spring(,'s');"
        "  Connector(,'n');"
        "  Thruster(,'t');"
        "  Restart(,'\n');"
        "  Action0(,'0');"
        "  Action1(,'1');"
        "  Action2(,'2');"
        "  Action3(,'3');"
        "  Action4(,'4');"
        "  Action5(,'5');"
        "  Action6(,'6');"
        "  Action7(,'7');"
        "  Action8(,'8');"
        "  Action9(,'9');"
        "}"
        "View {"
        "  ZoomIn(,'z');"
        "  ZoomOut(,'Z');"
        "}"
        );
  DocClear;
  try {
    while (main_mode_bttn.state==MMD_EDIT) {
      switch (ScanMsg(&arg1,&arg2,
            1<<MSG_MS_L_DOWN|1<<MSG_MS_L_UP|1<<MSG_MS_R_DOWN|
            1<<MSG_MS_MOVE|1<<MSG_KEY_DOWN|1<<MSG_KEY_UP)) {
        case MSG_MS_L_DOWN:
          switch (edit_mode_bttn.state) {
            case EMD_MASS:
              S2W(ms.pos.x,ms.pos.y,&wx,&wy);
              tmpm1=PlaceMass(wx,wy);
              if (ode->next_mass==tmpm1)
                tmpm1->img=<1>;
              else
                tmpm1->img=<2>;
              tmpm1=NULL;
              break;
            case EMD_SPRING:
            case EMD_CONNECTOR:
            case EMD_THRUSTER:
            case EMD_MOVE:
              S2W(ms.pos.x,ms.pos.y,&wx,&wy);
              tmpm1=MassFind(ode,wx,wy);
              tmpm2=NULL;
              break;
          }
          break;
        case MSG_MS_L_UP:
          switch (edit_mode_bttn.state) {
            case EMD_MASS:
              break;
            case EMD_SPRING:
            case EMD_CONNECTOR:
            case EMD_THRUSTER:
              S2W(ms.pos.x,ms.pos.y,&wx,&wy);
              if (tmpm1 && (tmpm2=MassFind(ode,wx,wy)) && tmpm1!=tmpm2)
                PlaceSpring(tmpm1,tmpm2,edit_mode_bttn.state);
              tmpm1=tmpm2=NULL;
              break;
            case EMD_MOVE:
              S2W(ms.pos.x,ms.pos.y,&wx,&wy);
              if (tmpm1) {
                tmpm1->x=wx;
                tmpm1->y=wy;
                tmpm1->z=0;
                NullSprings;
              }
              tmpm1=tmpm2=NULL;
              break;
          }
          break;
        case MSG_MS_MOVE:
          switch (edit_mode_bttn.state) {
            case EMD_MOVE:
              S2W(ms.pos.x,ms.pos.y,&wx,&wy);
              if (tmpm1) {
                tmpm1->x=wx;
                tmpm1->y=wy;
                tmpm1->z=0;
                NullSprings;
              }
              break;
          }
          break;
        case MSG_MS_R_DOWN:
          if (++edit_mode_bttn.state==EMD_MODES_NUM)
            edit_mode_bttn.state=0;
          break;
        case MSG_MS_R_UP:
          break;
        case MSG_KEY_DOWN:
          switch (arg1) {
            case '\n':
              CleanUp;
              Init;
              break;
            case '0'...'9':
              next_action_key=arg1;
              break;
            case 'c':
              CenterMasses;
              break;
            case 'd':
              ODEPause(ode,OFF);
              ode->drag_v2=0.002;
              ode->drag_v3=0.0001;
              break;
            case 'v':
              edit_mode_bttn.state=EMD_MOVE;
              break;
            case 'm':
              edit_mode_bttn.state=EMD_MASS;
              break;
            case 'n':
              edit_mode_bttn.state=EMD_CONNECTOR;
              break;
            case 's':
              edit_mode_bttn.state=EMD_SPRING;
              break;
            case 't':
              edit_mode_bttn.state=EMD_THRUSTER;
              break;
            case 'z':
              Spawn(&Zoom,2.0(I64));
              break;
            case 'Z':
              Spawn(&Zoom,0.5(I64));
              break;
            case CH_SPACE:
              if (++main_mode_bttn.state==MMD_MODES_NUM)
                main_mode_bttn.state=0;
              GetMsg(,,1<<MSG_KEY_UP);
              goto es_done;
            case CH_SHIFT_ESC:
            case CH_ESC:
              throw;
          }
          break;
        case MSG_KEY_UP:
          switch (arg1) {
            case 'd':
              ODEPause(ode);
              ode->drag_v2=0.000002;
              ode->drag_v3=0.0000001;
              break;
          }
          break;
      }
      Refresh;
    }
es_done: //Don't goto out of try
    okay=TRUE;
  } catch {
    Fs->catch_except=TRUE;
    okay=FALSE;
  }
  MenuPop;
  CtrlBttnDel(bt_edit_mode);
  if (!okay)
    throw;
}

U0 Strut()
{
  CCtrl *bt_main_mode;
  SettingsPush; //See SettingsPush
  Fs->win_inhibit|=WIF_SELF_MS_L|WIF_SELF_MS_R|WIG_DBL_CLICK;
  AutoComplete;
  WinBorder;
  WinMax;
  DocCursor;
  DocClear;
  "\n$WW,1$$PURPLE$$TX+CX,\"Build a ship.\"$$FG$\n\n"
        "Sel mass mode.\tLeft-click to place masses.\n"
        "Sel spring mode.\tLeft-drag to make members.\n"
        "Sel thruster mode.\tPress a digit, 0-9.  Drag to make thruster.\n"
        "Sel connector mode.\tPress a digit, 0-9.  "
        "Drag to make breakable connector.\n\n"
        "Press $GREEN$<SPACE>$FG$ to run the game.  Press digits to operate "
        "thrusters and break connectors.\n\n"
        "Press $GREEN$<z>$FG$ or $GREEN$<SHIFT-Z>$FG$ to zoom in/out.\n\n";

  PressAKey;
  bt_main_mode=CtrlBttnNew(
        (GR_WIDTH-4*FONT_WIDTH-16)>>1,1*FONT_HEIGHT+4,80,,MMD_MODES_NUM,
        Define("ST_MAIN_MODES"),main_mode_colors,&main_mode_bttn);
  DocClear;
  Init;
  Fs->draw_it=&DrawIt;
  try {
    while (TRUE) {
      EditShip;
      PlayShip;
    }
  } catch {
    CleanUp;
    Fs->catch_except=TRUE;
  }
  SettingsPop;
  CtrlBttnDel(bt_main_mode);
}