#define  XMESSAGEF_ANTISPIN     0
#define  XMESSAGEF_SOLAR_STORM  1

RegDefault("ZealOS/XCaliber",
                        "I64 best_score=0;\n"
                        "I64 message_flags=0;\n");
RegExe("ZealOS/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_message;

#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)
                StrCopy(tmpmi->label, "L");
        else
                StrCopy(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);
        QueueInsert(&tmpmi->p_front, ode->last_mass);
        QueueInsert(&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);
                QueueInsert(&tmpmi->s[i], ode->last_spring);
        }
        tmpmi->img = <7>;
        tmpmi->active = TRUE;
        QueueInsert(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;
                                QueueInsert(&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;
                                QueueInsert(&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;
                                QueueInsert(&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;
                                QueueInsert(&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;
                                QueueInsert(&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;
                                QueueInsert(&tmpsp->s[i], ode->last_spring);
                        }
                        remaining++;
                        break;
        }
        QueueInsert(tmpsp, ship_head.last);

        return tmpsp;
}

U0 MissileDel(Missile *tmpmi)
{
        I64 i;

        if (tmpmi->active)
        {
                QueueRemove(tmpmi);
                for(i = 0; i < 5; i++)
                        QueueRemove(&tmpmi->s[i]);
                QueueRemove(&tmpmi->p_front);
                QueueRemove(&tmpmi->p_back);
                tmpmi->active = FALSE;
        }
}

U0 ShipDel(Ship *tmpsp)
{
        I64 i;

        if (!tmpsp)
                return;
        for (i = 0; i < tmpsp->masses; i++)
                QueueRemove(&tmpsp->p[i]);
        for (i = 0; i < tmpsp->springs; i++)
                QueueRemove(&tmpsp->s[i]);
        for (i = 0; i < 2; i++)
                MissileDel(&tmpsp->missiles[i]);
        QueueRemove(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;

        QueueRemove(ode);
        tmpsp = ship_head.next;
        while (tmpsp != &ship_head)
        {
                tmpsp1 = tmpsp->next;
                ShipDel(tmpsp);
                tmpsp = tmpsp1;
        }
        human = NULL;
        QueueDel(&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_message)
        {
                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;
                                                                Sound(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 MyNoise(I64 mS, F64 min_ona, F64 max_ona)
{//Make white noise for given number of mS.
// See Noise. On bare-metal, Spawn() hogs CPU.
        CSoundEffectFrame *ns;

        if (mS > 0)
        {
                ns = MAlloc(sizeof(CSoundEffectFrame));
                ns->type = SE_NOISE;
                ns->duration = mS / 1000.0;
                ns->ona1 = min_ona;
                ns->ona2 = max_ona;
                music.mute++;
                SoundEffectTask(ns);
                Sound;
        }

        return;
}

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;
                                Sound;
                        }
                        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;
                                MyNoise(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);
        QueueInsert(&tmps->p, ode->last_mass);
        QueueInsert(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);
        MyNoise(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;
                Sound;
        }
}

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);
                QueueInsert(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);
                        QueueRemove(tmps);
                        QueueRemove(&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)
                {
                        QueueRemove(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;
                                MyNoise(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)
                {
                        QueueRemove(tmpm);
                        Free(tmpm);
                }
                tmpm = tmpm1;
        }
        if (level == 1)
                OneTimePopUp(&message_flags, XMESSAGEF_SOLAR_STORM, 
                                        "Face away from Sun in solar storm.\n");
        if (level == 4)
                OneTimePopUp(&message_flags, XMESSAGEF_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_message = TRUE;
        ODEPause(ode);
}

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

        QueueInit(&ship_head);
        QueueInit(&shot_head);
        QueueInit(&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, message_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;
        PaletteSetLight(FALSE);
        Fs->win_inhibit = WIG_TASK_DEFAULT - 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;
                QueueInsert(ode, Fs->last_ode);
                ch = 0;
                do
                {
                        while (!game_over && !show_level_message &&
                                        (message_code = MessageScan(&arg1, &arg2, 1 << MESSAGE_KEY_DOWN | 1 << MESSAGE_KEY_UP)))
                        {
                                switch (message_code)
                                {
                                        case MESSAGE_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 MESSAGE_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; //messages are only qued by winmgr
                        if (show_level_message)
                        {
                                ch = KeyGet(&sc);
                                if (ch == '\n')
                                        ch = 0;
                                ODEPause(ode, OFF);
                                show_level_message = FALSE;
                        }
                        else if (game_over)
                        {
                                ch = CharScan;
                        }
                        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("ZealOS/XCaliber", 
                         "I64 best_score=%d;\n"
                         "I64 message_flags=%d;\n", best_score, message_flags);
}