/*   _____________________________________________________________
    /                                                             \
    | File: xweap.cpp - extra weapons code
    \_____________________________________________________________/

    contents:

        code for

            NukeExpl
            LightSword
            XWeaponAnchor
            XWeapon
            XWeaponBox
            AddAmmo
            AddShield
            Bazooka
            XCharge
            FlameThrower
            Bullet
            MGun
            TargetLaser
            PGun
            OilDriver
            OilSquirt
            Missile
            MLauncher
            Rocket
            Katjuscha
            XWCycler

    Developers: ID: Name:
    =========== --- -----
                JTH Juergen Thumm
  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\
 yymmdd ------- --- ---------------------------------------------------------
 940811 1.38    JTH created from global.cpp due to memory overflow on comp.
 950122             Missile: dir handling fixed, expl color now mgimpact.
 950916             LSword: oldang, SND_LSMOVEx support.
*/

#   include "global.h"
#   include "shape.h"

bool isObstacleColor(pcol col, void *pUserData)
{   return field.obstcol(col);  }

//|| NukeExpl
class NukeExpl : public PlayObj {

 public:
    NukeExpl(cpix   pos[2]);
    rcode   step(class PlayMsg* msg);

 private:
    long    radius;     // current radius step

    };

NukeExpl::NukeExpl(cpix xpos[2])
    :   PlayObj (NUKE_EXPLOSION, 30, 60)
{_
    pos[0]  = xpos[0];
    pos[1]  = xpos[1];
    radius  = field.sizemax / 5;

    if(pos[0] <  field.bord[0] + 3) pos[0] = field.bord[0] + 3;
    if(pos[0] >= field.bord[2] - 3) pos[0] = field.bord[2] - 3;
    if(pos[1] <  field.bord[1] + 3) pos[1] = field.bord[1] + 3;
    if(pos[1] >= field.bord[3] - 3) pos[1] = field.bord[3] - 3;

    field.rectfill(pos[0]-2,pos[1]-2, pos[0]+2,pos[1]+2, col_backgrnd);
}

rcode NukeExpl::step(PlayMsg* pm)
{_
    if(pm)  return INDATA;

    if(--radius < 30)
    {
        delete this;
        return OK;
    }

    if(radius % 10)
        return OK;

    // create sub-explosions

    ushort  nsubexpl    = radius / 5;

    long    rvary   = max(radius / 5, 1);
    long    rsub;
    ushort  asub;
    cpix    spos[2];

    for(ushort i=0; i<nsubexpl; i++)
    {
        // sub-explosions radius is baseradius
        // varied by some percent

        rsub    = radius + (rand() % rvary) - (rvary >> 1);

        if(rsub < 10)
            continue;

        asub    = rand() % ftrig.degmax();

        spos[0] = pos[0] + convif(ftrig.sin(asub) * rsub);
        spos[1] = pos[1] - convif(ftrig.cos(asub) * rsub);

        if(field.inbord(spos[0],spos[1]))

            new ImpactExpl( spos, col_waveexpl,
                            30, 7, 50,
                            BE_MODE_TEMP
                          );
    }

    return  OK;
}

#define LSVDRAW

//|| LSword
LightSword::LightSword(cpix pos[2])

    :   XWeapon(pos, "Light Sword", 'L', LIGHTSWORD),
        PlayObj(LIGHTSWORD, 20,0),  // performance requires slow updating

        ibmask((sizeof(backcol) / sizeof(pcol)) - 1),

        time(500,500)   // opening time, closing time
{_
    amo     = 100;
    subamo  =   0;

    colcode     = col_lsword0;  // default
    visible     = 0;
    linestep    = 1;
    lengthmax   = 1;    // dummy value, is set in step()
    length      = 0;
    sndplayopt  = 0;
    oldang      = -1;   // not yet set

    spos[0] = spos[1] = 0;
    epos[0] = epos[1] = 0;
    memset(&old, 0, sizeof(old));
    memset(ppos, 0, sizeof(ppos));
    memset(backcol, 0, sizeof(backcol));

    if(     sizeof(backcol) / sizeof(pcol)
        !=  sizeof(ppos) / (sizeof(cpix) << 1)  )

        SysErr(1204941419); // no. of index entries MUST be equal!
}

LightSword::~LightSword()
{_
    unplot();
}

rcode LightSword::mount(XWeaponBox* box, int xmode)
{_
    XWeapon::mount(box, xmode); // this MUST be

    Bike *bk = box->bike();

    ifn(bk) {SysErr(512941459); return INTERNAL;}

    colcode = col_lsword0 + (bk->number & 1);

    if(bk->number < 2)

        sndplayopt = bk->number ? SND_PLAYRIGHT : SND_PLAYLEFT;

    return  OK;
}

void LightSword::unplot(void)
{_
    if(!visible)    return;
    Bike*   bk = box->bike();
    ifn(bk) {SysErr(1203942048); return;}

#ifdef LSVDRAW
    // VIDEO UN-DRAW old lines, if any:
    if(old.vdpos[0])
    {
        HDC hdc    = field.getfronthdc();
        int ropold = SetROP2(hdc, R2_NOT);
        SetTextColor(hdc, RGB(255,255,255));

        MoveTo(hdc, old.vdpos[0],  old.vdpos[1]);
        LineTo(hdc, old.vdpos[2],  old.vdpos[3]);
        MoveTo(hdc, old.vdpos[4],  old.vdpos[5]);
        LineTo(hdc, old.vdpos[6],  old.vdpos[7]);
        MoveTo(hdc, old.vdpos[8],  old.vdpos[9]);
        LineTo(hdc, old.vdpos[10], old.vdpos[11]);

        SetROP2(hdc, ropold);
        memset(&old, 0, sizeof(old));
        visible=0;
    }
#else
    cpix sx  = spos[0], sy  = spos[1];
    cpix ex  = epos[0], ey  = epos[1];
    int dx   = ex - sx, dy  = ey - sy;
    int adx  = abs(dx), ady = abs(dy);
    int amax = max(adx, ady) & ibmask;

    cpix cx,cy;
    pcol col;

    cpix *ip = &ppos[0][0];

    if(amax)
    {
        for(ushort i=0; i<amax; i += linestep, ip += linestep << 1)
        {
            cx  = * ip;
            cy  = *(ip+1);

            if(cx == -1)

                break;  // reached field border

            col = backcol[i];

            if(     col != -1
                &&  field.readpix(cx,cy) == colcode )
                field.plot(cx,cy,col);
        }
    }
#endif

    visible = 0;
}

ushort LightSword::angle(void)
{
    Bike    *bk = box->bike();

    ifn(bk) {SysErr(1212941444); return 0;}

    short ang   = bk->ana.angle;

    //  are there TWO active light swords at the same bike?

    XWeapon     *xw;
    LightSword  *ls;

    if(     (box->first() == this && (xw = (XWeapon*)XWeapon::next()))
        ||  (box->first() != this && (xw = box->first()))   )

        if(     xw->idcode() == LIGHTSWORD
            &&  (ls = (LightSword*)xw)
            &&  ls->length  )
        {
            //  vary angle so both swords could be used parallel

            if(box->first() == this)    ang -= ftrig.degmax() >> 4;
            else                        ang += ftrig.degmax() >> 4;

            if(ang < 0) ang += (short)ftrig.degmax();

            ang &= ftrig.degmask();
        }

    return  (ushort)ang;
}

rcode LightSword::strike(void)
{_
    Bike* bk = box->bike();
    ifn(bk) {SysErr(1203942048); return INTERNAL;}
    ifn(bk->ana.log)    return FAILED;

    // calc lsword's new position of other end:
    ushort  ang = angle();

    // new start and end positions of line
    cpix *lpos  = bk->launchpos(2); // two pix distance

    spos[0] = * lpos    ;
    spos[1] = *(lpos+1) ;

    epos[0] = spos[0] + convif(ftrig.sin(ang) * length);
    epos[1] = spos[1] - convif(ftrig.cos(ang) * length);

    // now, line goes from spos to epos

#ifdef LSVDRAW
    // VIDEO DRAW new lines:
    ushort rang, lang;
    short  ldx,ldy,rdx,rdy;
    cpix   vdpos[12];
    lang   = ang + ((ftrig.degmax() * 3 / 4) & ftrig.degmask());
    rang   = ang + ((ftrig.degmax()     / 4) & ftrig.degmask());
    ldx    =     convif(ftrig.sin(lang) * 2);
    ldy    = 0 - convif(ftrig.cos(lang) * 2);
    rdx    =     convif(ftrig.sin(rang) * 2);
    rdy    = 0 - convif(ftrig.cos(rang) * 2);
    vdpos[0] = spos[0] + ldx / 2;
    vdpos[1] = spos[1] + ldy / 2;
    vdpos[2] = epos[0] + ldx;
    vdpos[3] = epos[1] + ldy;
    vdpos[4] = spos[0] + rdx / 2;
    vdpos[5] = spos[1] + rdy / 2;
    vdpos[6] = epos[0] + rdx;
    vdpos[7] = epos[1] + rdy;
    vdpos[8] = spos[0];
    vdpos[9] = spos[1];
    vdpos[10] = epos[0];
    vdpos[11] = epos[1];

    if(     !field.clipline(&vdpos[0],&vdpos[2])
        &&  !field.clipline(&vdpos[4],&vdpos[6])
        &&  !field.clipline(&vdpos[8],&vdpos[10])
      )
    {
        HDC hdc    = field.getfronthdc();
        int ropold = SetROP2(hdc, R2_NOT);
        SetTextColor(hdc, RGB(255,0,0));

        MoveTo(hdc, vdpos[0], vdpos[1]);
        LineTo(hdc, vdpos[2], vdpos[3]);
        MoveTo(hdc, vdpos[4], vdpos[5]);
        LineTo(hdc, vdpos[6], vdpos[7]);
        MoveTo(hdc, vdpos[8], vdpos[9]);
        LineTo(hdc, vdpos[10], vdpos[11]);

        SetROP2(hdc, ropold);
        visible=1;
        memcpy(old.vdpos, vdpos, sizeof(old.vdpos));
    }
#endif

    cpix sx  = spos[0], sy  = spos[1];
    cpix ex  = epos[0], ey  = epos[1];
    int dx   = ex - sx, dy  = ey - sy;
    int adx  = abs(dx), ady = abs(dy);
    int amax = max(adx, ady) & ibmask;

    cpix    x,y,cpos[2];
    pcol    col;
    short   dir_x;
    enum    Dirs dir_e;

    linestep    = 4;

    //  pre-scan:
    //  calc every pixel's coords, store them,
    //  and check if hitting any obstacles.

    ushort  curlen = 0; // current sword length

    cpix *ip = &ppos[0][0];

    if(amax)
    for(curlen=0; curlen<amax; curlen++)
    {
        x = (cpix)(sx + (int)curlen * dx / amax);
        y = (cpix)(sy + (int)curlen * dy / amax);

        if(field.inbord(x,y))
        {
            *ip++   = x;
            *ip++   = y;

            if(field.obstacle(x,y))
                linestep    = 1;
        }
        else
        {
            *ip++   = -1;   // mark every(!) pix outside
            *ip++   = -1;   // field with -1
        }
    }

    // if no obstacles, just plot every 4th pix
    // for performance reasons.

    uchar   crash, lcrash = 0, hitbike = 0;

    ip  = &ppos[0][0];

    if(amax)
    if(curlen < 10) // if too near at field border
        *ip = -1;   // don't plot sword at all
    else
    for(ushort i=0; i<amax; i += linestep, ip += linestep << 1)
    {
        cpos[0] = x = * ip;
        cpos[1] = y = *(ip+1);

        crash   = 0;

        if(field.inbord(x,y))   // also if x != -1
        {
          //    handle a pixel of the sword

          col   = field.readpix(x,y);

          if(field.obstcol(col))
          {
            Bike *tbike;

            switch(col)
            {
              case col_shape0:

                if(bk->number == 0 || (hitbike & 1))
                {
                    backcol[i]  = -1;
                    continue;   // ignore (further) pix of (own) bike
                }
                else
                if(     (tbike = (Bike*)field.getobj(BIKE0))
                    &&  tbike->matchpos(cpos)
                    &&  tbike->sethit(cpos, col_bullet) )
                {
                    hitbike |= 1;

                    //  slow our bike down so victim has
                    //  a chance to escape
                    if((bk->speed -= convfi(20)) < bk->speedmin)
                        bk->speed  = bk->speedmin;

                    crash    = 2;
                }

                break;

              case col_shape1:

                if(bk->number == 1 || (hitbike & 2))
                {
                    backcol[i]  = -1;
                    continue;
                }
                else
                if(     (tbike = (Bike*)field.getobj(BIKE1))
                    &&  tbike->matchpos(cpos)
                    &&  tbike->sethit(cpos, col_bullet) )
                {
                    hitbike |= 2;

                    //  slow our bike down so victim has
                    //  a chance to escape
                    if((bk->speed -= convfi(20)) < bk->speedmin)
                        bk->speed  = bk->speedmin;

                    crash    = 2;
                }

                break;

              case col_xweapon:
                // lying XWeapon: blast it off!
#ifdef LSVDRAW
                unplot();
#endif
                dir_x   = field.convdir((short)(x - bk->pos[0]),
                                        (short)(y - bk->pos[1]));
                dir_e   = field.convdir(dir_x); // conv to enum
                field.BlastXWeapon(cpos, dir_e);
                break;

              case col_mgimpact:
              case col_cmimpact:
                backcol[i]  = col_backgrnd;
                break;

              default:
                crash   = 1;
#ifdef LSVDRAW
                unplot();
#endif
                // create impact explosion, which is
                // basically harmless and just removes pixels
                new ImpactExpl(cpos, col_smallexpl,
                               10, 2, 5, BE_MODE_TEMP);
                backcol[i]  = col_backgrnd;
#ifdef LSVDRAW
                lcrash |= 1;
                i = amax;   // break scan loop
#endif
                break;
            }   // endswitch col
          }
          else
          if(col == col_nitro)
          {
#ifndef RDEMO
            CrtNitroExpl(cpos);
#endif
            backcol[i]  = col_backgrnd;
          }
#ifndef LSVDRAW
          else
          {
            // store current pixel's color and plot over
            backcol[i]  = col;
            // plot only from pix 4 to end, 'cause updating
            // the sword line is done every 4th step of a bike,
            // se we must avoid to plot too close to own bike.
            if(i >= 4)
                field.plot(x,y,colcode);
          } // endif obstcol
#endif

        }   // endif inbord
        else
            backcol[i]  = -1;

        lcrash  |= crash;   // line overall crash flag

    }   // endfor pix of lsword

    if(lcrash & 2)  // if hitted a bike at all
        field.playsound(SND_LSHBK); // NOT bound to one side!
    else
    if(lcrash & 1)  // if hitted anything at all
        field.playsound((enum Sounds)(SND_LSH1 + (SNDRAND() % 3)),
                        0, 1, sndplayopt);
    else
    {
        // any circular movements?
        if(oldang == -1)
            oldang = ang;
        else
        if(abs((short)ang - (short)oldang) > (ftrig.degmax() >> 2))
        {
            oldang = ang;
            field.playsound((enum Sounds)(SND_LSMOVE1 + (SNDRAND() % 3)),
                        0, 1, sndplayopt);
            // if sounds not loaded, nothing's played.
        }
    }

    visible = 1;

    return  OK;
}

rcode LightSword::launch(int xmode)
{_
    mode    |= xmode;

    Bike*   bk = box->bike();

    ifn(bk) {SysErr(1203942041); return INTERNAL;}

    if(mode & XW_BLAST_OFF)
    {
        delete  this;
        return  OK;
    }

    if(!length)     StartOpen();    // if not open, open it
    else
    if(!time.open)  StartClose();

    return  OK;
}

void  LightSword::StartOpen(void)
{
    //  start opening process
    time.open   = time.openmax; // takes so many msec
    time.close  = 0;
    field.stopsound(SND_LSPLUS2);   // the case it's playing
    field.playsound(SND_LSOPEN, 0, 1, sndplayopt);
    oldang      = -1;   // devalidate remembered angle
}

void LightSword::StartClose(void)
{
    //  start closing process
    time.close  = time.closemax;
    field.stopsound(SND_LSOPEN);    // the case it's still playing
    field.playsound(SND_LSCLOSE, 0, 1, sndplayopt);
}

rcode LightSword::step(PlayMsg* pm)
{_
    if(pm)  return INDATA;

    if(status != XWS_MOUNTED)   return  OK;

    Bike *bk;

    ifn(box)                {SysErr(412942202); return INTERNAL;}
    ifn(bk = box->bike())   {SysErr(412942203); return INTERNAL;}

    unplot();

    //  the case we got some + loaded on the weapon,
    //  re-calc lengthmax here:

    int newlmax = field.sizemax / 20 * (1 + xcharged);

    if(newlmax != lengthmax)
    {
        lengthmax   = newlmax;      // lengthmax changes
        if(length)  StartOpen();    // if open, re-open it
    }

    if(     length                          // open
        &&  !time.close                     // and not currently closing
        &&  (bk->isdead() || !nearfront())  // and (bike dead or inactive)
      )
        StartClose();   // shut lsword

    if(time.open)
    {
        //  opening process: sword gets longer
        length  = (time.openmax - time.open) * lengthmax / time.openmax;
        if((time.open -= cyclesize) <= 0)
            time.open   = 0;
    }
    else
    if(time.close)
    {
        //  closing process: sword gets shorter
        length  = time.close * lengthmax / time.closemax;
        if((time.close -= cyclesize) <= 0)
        {
            time.close = 0;
            length  = 0;
            bk->SetSteerDelay(0);
            bk->SetAccelDelay(0);
        }
    }

    if(length)
    {
        if(--subamo < 0)
        {
            // one amo == n subamo.
            // energy vanishes faster with longer lswords.
            subamo  = 8 / (1 + xcharged);
            amo--;
        }

        if(amo)
            strike();   // sword is open and still has amo: display it

        // lsword is a very dangerous weapon. the bike
        // that uses it must have a delayed steering
        // so other bike has a chance to escape:
        // (128 means 100% additional delay)

        bk->SetSteerDelay(length * 128L * 2 * (1 + xcharged) / lengthmax);
        bk->SetAccelDelay(length * 128L * 2 * (1 + xcharged) / lengthmax);
    }

    if(!amo)
    {
        bk->SetSteerDelay(0);
        bk->SetAccelDelay(0);

        field.stopsound(SND_LSH1);
        field.stopsound(SND_LSH2);
        field.stopsound(SND_LSH3);
        field.stopsound(SND_LSHBK);

        field.playsound(SND_LSE, 0, 1, sndplayopt);

        delete  this;
        return  OK;
    }

    return  OK;
}

//|| XWAnchor
XWeaponAnchor::XWeaponAnchor(XWeapon* myxweap, cpix mpos[2],enum ObjCodes idc)
    :   PlayObj(XWEAPON_ANCHOR,10000)
{_
    idead=1;

    SetPos(mpos);   // middle of LetterBox
    xweapon = myxweap;
    if(createshape(SCL_XWEAP, col_xweapon)) // deleted in PlayObj dtr
    {
        MemErr(2511951723);
        return;
    }
    else
    {
        if(idc >= MGUN && idc <= MLAUNCHER)
            shape().setface(idc - MGUN);
        else
            shape().setface(shape().faces()-1);

        shape().moveto(mpos[0],mpos[1]);
        paintya();
    }

    // set flag in XWeaponTileField: this tile is used now
    field.xwtfield.setasused(mpos[0],mpos[1]);

    idead=0;
}

XWeaponAnchor::~XWeaponAnchor()
{_
    if(xweapon)
    {
        // auto-delete xweapon: usually used on global cleanup

        if(xweapon->status != XWS_LYING)
            SysErr(1403941647); // this should NOT happen!
        else
            delete  xweapon;
    }

    if(hasshape())
        shape().unplot();

    // set flag in XWeaponTileField: this tile is free again
    field.xwtfield.setasused(pos[0],pos[1],0);

    xweapon = 0;
}

rcode XWeaponAnchor::step(PlayMsg* pm)
{_
    if(pm)  return INDATA;
    //  refresh letter box every 15 seconds
    paintya();
    return OK;
}

void XWeaponAnchor::paintya(void)
{
    if(!xweapon)    {SysErr(2511951729);return;}

    if(hasshape())
    {
        shape().plot();
        return;
    }

    char sbuf[2];
    sbuf[0] = xweapon->id_char;
    sbuf[1] = '\0';
    field.color(col_backgrnd);
    field.drawmode(NORMAL); // overlap without overwrite
    field.text(pos[0],pos[1], sbuf);
}

//|| XWeapCode

ObjCodes XWeapon::idcode()  {return UNKNOWN;}   // default function

XWeapon::~XWeapon()
{_
    if(box) detach();
}

XWeapon::XWeapon(cpix pos[2], char* xname, char idchr,enum ObjCodes idc)
{_
    idead=1;

    box = 0;    // FIX /110994: forgot this

    paspos[0]   = pos[0] - field.charsize[0];
    paspos[1]   = pos[1] - field.charsize[1];
    if(paspos[0] < field.bord[0])   paspos[0] = field.bord[0];
    if(paspos[1] < field.bord[1])   paspos[1] = field.bord[1];

    paspos[2]   = paspos[0] + field.charsize[0] + 3;
    paspos[3]   = paspos[1] + field.charsize[1] + 2;
    if(paspos[2] >= field.bord[2])  paspos[2] = field.bord[2]-1;
    if(paspos[3] >= field.bord[3])  paspos[3] = field.bord[3]-1;

    name        = xname;
    status      = XWS_LYING;
    xcharged    = 0;
    id_char     = idchr;
    amo         = 1;        // by default single-shot unit
    mode        = 0;        // no special modes by default
    weight      = 10000UL;  // default weight 10 kg

    // create a temporar PlayObj which's only purpose is
    // to make us accessible via PlayField's PlayObj list:
    ifn(anchor = new XWeaponAnchor(this, pos, idc))
        {MemErr(302941822); return;}
    if(anchor->isdead())    return;

    idead=0;
}

ushort XWeapon::nearfront(void)
{_
    //  is this xw one of the two front weapons?

    ifn(box)    return  0;

    XWeapon *xw;

    ifn(xw = box->first())  return  0;

    if(xw == this)          return  1;

    if(xw->next() == this)  return  2;

    return  0;
}

void XWeapon::RemoveLetterBox(void)
{_
    field.rectfill(paspos[0],paspos[1], paspos[2],paspos[3],
        col_backgrnd);
}

bool XWeapon::matchpos(cpix xpos[2])
{_
    if(anchor && anchor->hasshape())
        return anchor->shape().ispixset(
                    xpos[0] - anchor->pos[0],
                    xpos[1] - anchor->pos[1]
                    );

    if(     xpos[0] >= paspos[0]
        &&  xpos[0] <= paspos[2]
        &&  xpos[1] >= paspos[1]
        &&  xpos[1] <= paspos[3] )
        return 1;

    return 0;
}

rcode XWeapon::mount(XWeaponBox* box, int xmode)
{_
    mode    |= xmode;

    this->box   = box;      // remember in which box we are
    box->AddFrontXW(this);  // put ourselves into box
    status  = XWS_MOUNTED;

    anchor->xweapon = 0;    // to avoid deletion of xweapon by
    delete  anchor;         // anchor's destructor
    anchor  = 0;

    RemoveLetterBox();

    ifn(mode & XW_BLAST_OFF)    // if no dummy mount, play sound
    {
        enum Sounds snd = SND_MAX;

        switch(idcode())
        {
            case MGUN:          snd = SND_MGUNC;    break;
            case LIGHTSWORD:    snd = SND_LSWORDC;  break;
            case FTHROWER:      snd = SND_FTHROWC;  break;
            case PGUN:          snd = SND_PGUNC;    break;
            case OIL_SQUIRT:    snd = SND_OILC;     break;
            case BAZOOKA:       snd = SND_BAZOC;    break;
            case MLAUNCHER:     snd = SND_MLAUNC;   break;
            case XSHIELD:       snd = SND_SHIELDC;  break;
            case KATJUSCHA:     snd = SND_KATSHAC;  break;

            // extra ammunition: see AddAmmo::mount for sound
            case XAMO:          snd = SND_MAX;      break; // do NOT play here

            default:            snd = SND_XWEAPC;   break;
        }

        if(snd < SND_MAX)
            field.playsound(snd);
    }

    return OK;
}

void XWeapon::detach()
{_
    ifn(box)            {SysErr(302941611);  return;}
    ifn(box->bike())    {SysErr(2803941631); return;}

    if(!this)   {SysErr(1109942004); return;}

    // check if changing from FlameThrower to MGun or Bullets;
    // if so, lock shooting of the boxe's bike for a while.

    if(idcode() == FTHROWER)
    {
        XWeapon* xw = (XWeapon*)next();

        if(!xw || xw->idcode() == MGUN)

            box->bike()->shootdelay = 100;  // lock shooting a while
    }

    box->RemoveXW(this);
    box = 0;
}

// DEFAULT launch() function for XWeapons:

rcode XWeapon::launch(int mode)
{_
    if(mode & XW_BLAST_OFF)
    {
        // this xweapon has no special blast-off code.
        // so perform a smart explosion instead.

        field.playsound(SND_PLAINGH);

        status  = XWS_LAUNCHED; // temporarily set status

        delete  this;       // and that's it (del will also detach())

        return  OK;
    }

    return  OK;
}

rcode XWeapon::incweight(ulong w)
{_
    ifn(box)    {SysErr(2010941545);  return INTERNAL;}

    weight += w;

    box->incweight(w);

    return OK;
}

//|| XWeapBox

XWeaponBox::~XWeaponBox()
{_
 XWeapon* xw;

    while(xw = first())
    {
        ifn(xw->box)    {SysErr(1403941854); return;}

        delete xw;  // they auto-remove themselves
    }
}

void XWeaponBox::AddFrontXW(XWeapon* xw)
{_
    if(!xw) {SysErr(1410941430); return;}

    addfront(xw);   // add at list front

    iweight +=  xw->weight; // add XW's weight to box weight
}

void XWeaponBox::RemoveXW(XWeapon* xw)
{_
    if(!xw) {SysErr(1410941431); return;}

    iweight -=  xw->weight; // sub XW's weight from box weight

    remove(xw);     // remove from list
}

#ifndef RDEMO

//|| AddAmo

AddAmmo::AddAmmo(cpix pos[2]) : XWeapon(pos, "Ammunition", 'A', XAMO)
{_}

rcode AddAmmo::mount(XWeaponBox* box, int xmode)
{_
 Bike*      bk;
 XWeapon*   xw;
 bool       d=0;

    XWeapon::mount(box, xmode); // this MUST be to avoid great fuzz!

    ifn(bk = box->bike())   {SysErr(412942146); return INTERNAL;}

    enum Sounds snd = SND_AMOC; // default sound

    if(xw = (XWeapon*)next())
    {
        // there is another xw after us.
        // what is it?

        switch(xw->idcode())
        {
            case    MGUN:   xw->amo += BULPACK_SIZE;
                            snd      = SND_MGAMOC;
                            bk->tell("collected Clips");    d=1; break;

            case    PGUN:   xw->amo += 3;
                            snd      = SND_PGAMOC;
                            bk->tell("Pump Gun loaded");    d=1; break;

            case FTHROWER:  xw->amo += 3;
                            snd      = SND_FTAMOC;
                            bk->tell("more fire");          d=1; break;

            case LIGHTSWORD: xw->amo += 50;
                            snd      = SND_LSAMOC;
                            bk->tell("more energy");        d=1; break;

            case MLAUNCHER: xw->amo += 1;
                            snd      = SND_MLAMOC;
                            bk->tell("one missile");        d=1; break;

            case KATJUSCHA: xw->amo += 3;
                            snd      = SND_KAMOC;
                            bk->tell("three rockets");      d=1; break;
        }
    }

    if(!d)
    {
        bk->bul.amo += BULPACK_SIZE;
        bk->tell("simple bullets");
    }

    ifn(mode & XW_BLAST_OFF) // if XW_BLAST_OFF, del will follow in launch()
    {
        field.playsound(snd);

        delete  this;
        return  OK;
    }

    return OK;
}

//|| AddShield

AddShield::AddShield(cpix pos[2]) : XWeapon(pos, "Shield", 'S', XSHIELD)
{_}

rcode AddShield::mount(XWeaponBox* box, int xmode)
{_
 Bike* bk;

    XWeapon::mount(box, xmode); // this MUST be to avoid great fuzz!

    bk  = box->bike();

    bk->shields++;

    bk->tell("collected Shield");

    ifn(mode & XW_BLAST_OFF) // if XW_BLAST_OFF, del will follow in launch()
    {
        delete  this;
        return  OK;
    }

    return OK;
}

//|| BazoCode

Bazooka::Bazooka(cpix pos[2])
    : XWeapon(pos, "Bazooka", 'B', BAZOOKA),
      MovingObj(BAZOOKA)
{_
}

rcode Bazooka::launch(int xmode)
{_
 Bike   *bike = box->bike();

    if(!createshape(SCL_BAZO, col_bazooka))
        shape().setObstacleFunction(isObstacleColor, 0);

    mode    |= xmode;

    short dist = 1;
    if(hasshape())  dist = shape().backsize() + 1;

    SetPos(bike->launchpos(dist));
    SetMov(bike->dir, convfi(400), &bike->ana);
    flymax      = bike->TargetRange(field.sizemax >> 3);
    flystep     = flymax;
    blink[0]    = 0;    // init r/b color
    blink[1]    = 1;    // init blink direction
    status      = XWS_LAUNCHED; // step() will care now

    detach();   // from XWeaponBox (MUST be done if XW_BLAST_OFF)

    ifn(mode & XW_BLAST_OFF)        // if not exploding NOW,
        field.playsound(SND_BAZOF); // play firing sound

    return OK;
}

rcode Bazooka::step(PlayMsg* msg)
{_
 cpix   oldpos[2];  // to avoid leaving a track
 bool   crash=0;
 cpix   obstx,obsty;

    if(msg && msg->type != PMT_FURTHERSTEP)
        return INDATA;

    if(status != XWS_LAUNCHED)  return OK;  // nothing to do

    oldpos[0] = pos[0];
    oldpos[1] = pos[1];

    int res = advance(msg ? 0 : 1);     // advance a step

    ifn(mode & XW_BLAST_OFF)
    {
        if(res == NOTHING)  return OK;  // nothing happened
    }
    else    crash = 1;

    // moved a pixel:
    if(res & ABORDER)   crash = 1;  // reached a border

    if(hasshape() && !(flystep & 1))
    {
        shape().unplot();

        shape().moveto(pos[0],pos[1]);
        shape().turnto(ana.angle);

        if(shape().prescan(obstx,obsty))
            crash = 1;
        else
            shape().plot();
    }

    // The Bazooka flies mercyless as far as the 'flystep'
    // down-counter determines.
    if(!crash)
        if(field.obstacle(pos[0], pos[1]))
            crash   = 1;
        // new ImpactExpl(pos, col_smallexpl, 10, 4, 5, BE_MODE_TEMP);

    if(flystep)
        flystep--;
    else
        crash = 1;

    if(crash)
    {
        // bazooka flew far enough OR became to slow: BOOM!
        int vecs,steps,slices;

        switch(xcharged)
        {
            // Normal Bazooka Explosion: 210 * 14 = 2940 units
            case 0 : vecs= 300; steps=10; slices= 3; break;
            // Single XCharged Explosn:  9240 units -> 3 times normal
            case 1 : vecs= 400; steps=20; slices= 4; break;
            // Very(!) seldom achieved Double XC: 30000 -> 10 times normal
            case 2 : vecs= 800; steps=30; slices= 8; break;
            // Anything over that should be avoided by XCharge class.
            // Only for the case it isn't:
            default:    vecs    = 70 * 7 * (1 + xcharged);
                        steps   =  2 * 7 * (1 + xcharged);
                        slices  = vecs / 60;
        }

        static uword BazoPalette[12] =
        {
            0x999,
            0x999,
            0xaaa,
            0xaaa,
            0xbbb,
            0xbbb,
            0xaaa,
            0xaaa,
            0x999,
            0x999,
            0x888,
            0x888
        };

        if(field.opt.magic & MAGIC_NUKEBAZO)
            new NukeExpl(pos);
        else
            new Explosion(
                    pos,
                    vecs,
                    steps,
                    col_dust,
                    sizeof(BazoPalette)/sizeof(uword),
                    BazoPalette,    // palette
                    slices,         // no. of slices
                    0               // modes: permanent impact
                    );

        field.playsound(SND_BAZOH, 1);  // in stereo

        delete  this;   // Bazooka leaves playfield
        return  OK;
    }

    if(field.opt.magic & MAGIC_BAZOWALL)
    {
        // let bazo create a wall
        cpix    cpos[2];
        short   acur;

        if(flystep < flymax * 3 / 4)

        for(ushort i=0; i<2; i++)
        {
            acur  = (ana.angle + ftrig.degmax() * (3+i*2) / 8);
            acur &= ftrig.degmask();

            cpos[0] = pos[0] + convif(ftrig.sin(acur) * 5);
            cpos[1] = pos[1] - convif(ftrig.cos(acur) * 5);

            field.line(pos[0],pos[1], cpos[0],cpos[1], col_fixwall);
            field.plot(cpos[0],cpos[1], col_fixwall2);
        }
    }

    if(!hasshape())
    {
        // don't leave a track: first delete old position's pixel
        if(field.readpix(oldpos[0], oldpos[1]) == col_bazooka)
              field.plot(oldpos[0], oldpos[1], col_backgrnd);

        // now, IF new position is free, draw a nice pixel:
        if(field.readpix(pos[0], pos[1]) == col_backgrnd)
              field.plot(pos[0], pos[1], col_bazooka);
    }

    return (reladv >= RADV_MAX) ? SPECIAL : OK;
}

#endif  // !defined(RDEMO)

//|| XCharge

XCharge::XCharge(cpix pos[2]) : XWeapon(pos, "Extra Charge", '+', XCHARGE)
{_}

rcode XCharge::mount(XWeaponBox* box, int xmode)
{_
 Bike* bike = box->bike();
 XWeapon* xw;
 static StringLib sl;

    XWeapon::mount(box, xmode); // this MUST be to avoid great fuzz!
    // NOTE that XCharge is now the frontmost XWeapon!

    // what should be charged?

    ifn(bike)   {SysErr(902941311); return INTERNAL;}

    bool charged = 0, played = 0;

    xw = (XWeapon*)this->next();    // get xweapon after us, IF ANY.

    if(xw)  // IF there's ANY xweapon AT ALL: (FIX /2003941909)
    {
        // put some extra charge on Bike's active XWeapon:

        switch(xw->idcode())
        {
            case BAZOOKA:
                if(xw->xcharged >= 1)   break;

                bike->tell(sl.printf("Bigger %s", xw->name));

                xw->xcharged++;

                charged = 1;
                break;

            case FTHROWER:
                if(xw->xcharged >= 1)   break;

                bike->tell("Longer Flames");

                xw->xcharged++;

                charged = 1;
                break;

            case MGUN:

                if(xw->xcharged < 5)
                {
                    switch(xw->xcharged)
                    {
                     case 0 : bike->tell("Bigger Impacts"    ); break;
                     case 1 : bike->tell("That's a big one"  ); break;
                     case 2 : bike->tell("The field is yours"); break;
                     case 3 : bike->tell("They wanna wonga"  ); break;
                     case 4 : bike->tell("Be careful with it"); break;
                    }

                    xw->xcharged++;
                    charged = 1;
                }

                break;

            case PGUN:

                if(xw->xcharged < 5)
                {
                    xw->xcharged++;

                    bike->tell(sl.printf("%d percent",
                        (80 + (1 + xw->xcharged) * 20)
                        ));

                    charged = 1;
                }

                break;

            case LIGHTSWORD:

                if(xw->xcharged < 2)
                {
                    xw->xcharged++;

                    bike->tell(sl.printf("%dx longer", xw->xcharged));

                    charged = 1;
                }

                break;

            case MLAUNCHER:

                if(xw->xcharged < 2)
                {
                    xw->xcharged++;

                    bike->tell("bigger impacts");

                    charged = 1;
                }

                break;

            case KATJUSCHA:

                if(xw->xcharged < 5)
                {
                    xw->xcharged++;

                    bike->tell("deeper impacts");

                    charged = 1;
                }

                break;

            // on default charge bullets (below)
        }

        if(charged)
        {
            box->incweight(3000);   // each + has a weight of 3 kg.

            Bike    *otbk;

            if(     bike->number < 2
                && (otbk = (Bike*)field.getobj(bike->number ? BIKE0 : BIKE1))
              )
            {
                // how strong is other one's weapon?

                XWeapon *oxw = otbk->weapons.first();

                short   myxc =  xw->xcharged;
                short   otxc = oxw ? oxw->xcharged : 0;
                enum Sounds s = SND_MAX;

                if(myxc == 2 && otxc <= 1
                   && xw->idcode() == LIGHTSWORD)   s = SND_LSPLUS2;

                if(s < SND_MAX && field.soundexists(s))
                {
                    field.stopsound();
                    field.playsound(s, 1);

                    played  = 1;
                }
            }

            if(!played)

             switch(xw->xcharged)
             {
                case 2 : field.playsound(SND_PLUS2);    break;
                case 3 : field.playsound(SND_PLUS3);    break;
                case 4 : field.playsound(SND_PLUS4);    break;
                case 5 : field.playsound(SND_PLUS5);    break;
             }

        }   // endif charged

    }   // endif xw

    ifn(charged)
    {
        // supply Bike with extra charge for n Bullets:

        bike->bul.xchrgcnt += BULPACK_SIZE;

        bike->tell("Bigger Bullets");
    }

    ifn(mode & XW_BLAST_OFF)    // if XW_BLAST_OFF, del will follow in launch()
    {
        delete  this;       // all done, leave PlayField
        return  OK;
    }

    return OK;
}

#ifndef RDEMO

//|| FThrower

FlameThrower::FlameThrower(cpix pos[2]) : XWeapon(pos, "Flame Thrower", 'F', FTHROWER)
{_
    amo      = 3;   // so many flames per thrower
    xcharged = 0;   // not yet extra charged
}

rcode FlameThrower::launch(int xmode)
{_
 Bike* bike = box->bike();
 int vecs,steps,slices;
 int xtm = BE_MODE_CPUPRIO;

    // CPUPRIO: flames must be anminated without delay, otherwise
    //          the launching bike would drive into it.

    mode    |= xmode;

    // how large should the flame be?

    switch(xcharged)
    {
        case 0 : vecs= 20; steps= 20; slices= 2; break;
        // ^ slices=1 would also work, but it's TOO fast!

        case 1 : vecs= 20; steps= 40; slices= 2; break;
        case 2 : vecs= 20; steps= 60; slices= 2; break;
        case 3 : vecs= 20; steps= 80; slices= 2; break;

        default :
            vecs    = 20;
            steps   = 20 + xcharged * 20;
            slices  = 2;
    }

    if(mode & XW_BLAST_OFF) xtm = 0;    // NO CPUPRIO for BLAST_OFF's!

    new Flame(bike->launchpos(), bike,
              vecs, steps, col_flame, 1, 0, slices, xtm);

    field.playsound(SND_FLAMEF);

    if(     !(mode & XW_BLAST_OFF)
        &&  amo == 1
        &&  field.soundexists(SND_FTHROWE)
      )
        field.playsound(SND_FTHROWE);

    if(!--amo || (mode & XW_BLAST_OFF)) // if XW_BLAST_OFF, MUST detach()
    {
        delete  this;   // does also detach()
        return  OK;
    }

    return OK;
}

#endif  // !defined(RDEMO)

//|| BulltCode

Bullet::Bullet(Bike* bike, bool temp, pcol xcol) : MovingObj(BULLET)
{_
    SetPos(bike->launchpos());
    SetMov(bike->dir, bike->speed + convfi(200), &bike->ana);

    charge      = 1;
    tempexpl    = temp;

    if(xcol != -1)
        colcode = xcol;
    else
        colcode = col_dust;

    // supply bullet with double charge?
    if(bike->bul.xchrgcnt)
        {   bike->bul.xchrgcnt--; charge = 2;   }

    oldcol  = col_backgrnd;
}

//  Bullet constructor to launch without a Bike:

Bullet::Bullet(cpix pos[2], enum Dirs xdir, intf xspeed,
               bool temp, pcol xcol) : MovingObj(BULLET)
{_
    SetPos(pos);
    SetMov(xdir, xspeed + convfi(100));

    charge      = 1;
    tempexpl    = temp;

    if(xcol != -1)
        colcode = xcol;
    else
        colcode = col_mgimpact; // col_dust;

    oldcol  = col_backgrnd;
}

rcode Bullet::step(PlayMsg* pm)
{_
 uchar  crash=0;    // 0=nothing, 1=hit something, 2=hit a bike
 pcol   col;

    if(pm)
    {
        if(pm->type != PMT_FURTHERSTEP) return INDATA;  // not supported

        // else: do another step in the same time cycle
    }

    oldpos[0] = pos[0];
    oldpos[1] = pos[1];

    // if PlayMsg is given, we advance without incrementing reladv;
    // this is the case in all additional time slices of a cycle

    int res = advance(pm ? 0 : 1);

    if(res==NOTHING)    return OK;  // nothing happened

    // check for crashes:

    if(res & ABORDER)

        crash = 1;

    if(!crash)

        if(field.obstacle(pos[0], pos[1]))
        {
            // crash only if obstacle isn't xweapon or mgun explosion color

            col = field.readpix(pos[0], pos[1]);

            Bike *tbike;

            if(     col == col_shape0
                &&  (tbike = (Bike*)field.getobj(BIKE0))
                &&  tbike->matchpos(pos)
                &&  tbike->sethit(pos, col_bullet)  )

                crash   = 2;

            if(     col == col_shape1
                &&  (tbike = (Bike*)field.getobj(BIKE1))
                &&  tbike->matchpos(pos)
                &&  tbike->sethit(pos, col_bullet)  )

                crash   = 2;

            if(!crash)
            switch(col)
            {
                case col_xweapon:
                     field.BlastXWeapon(pos, dir);  // let XW detonate!
                     break;
                case col_mgimpact:
                case col_cmimpact:
                     break;
                default:
                     crash = 1;
            }
        }

    if(crash)
    {
        if(crash == 1)  // if not hit a bike

            // perform bullet explosion
            new ImpactExpl(
                    pos, colcode,
                    charge * 30, charge * 2, 5,
                    BE_MODE_TEMP
                    );

        field.playsound(SND_PLAINGH);   // play the bullet impact

        delete  this;   // bullet leaves playfield
        return  OK;
    }
    else
    {
        // don't leave a track: first delete old position's pixel
        if(field.readpix(oldpos[0], oldpos[1]) == col_bullet)
        {
              field.plot(oldpos[0], oldpos[1], oldcol);
        }

        // now, IF new position is free, draw a nice pixel;
        // fly also over xweapons, and remember background color (oldcol)
        oldcol = field.readpix(pos[0], pos[1]);

        // leave empty track when passing through temporar pixels:

        if(tempexpl)
        switch(oldcol)
        {
            case col_mgimpact:
            case col_cmimpact:
                 oldcol = col_backgrnd;
                 break;
        }

        if(oldcol == col_backgrnd || oldcol == col_xweapon)
        {
              field.plot(pos[0], pos[1], col_bullet);
        }
    }

    //  if there's a big undriven 'reladv' distance remaining,
    //  request another CPU time slice in this time cycle:

    return (reladv >= RADV_MAX) ? SPECIAL : OK;
}

//|| MGun

MGun::MGun(cpix pos[2])

    :   XWeapon(pos, "Machine Gun", 'M', MGUN),
        PlayObj(MGUN,20,20),
        TargetLaser(this, 10)   // 10 == default mindist when parking
{_
    amo     = BULPACK_SIZE;     // no. of bullets
    createshape(SCL_MGCRATER, col_backgrnd);
    // if creation fails, there will simply be no craters plotted.
}

rcode MGun::launch(int xmode)
{_
    mode    |= xmode;

    Bike*   bk = box->bike();

    ifn(bk) {SysErr(2803941809); return INTERNAL;}

    // perform a laser re-scan to update target position:

    UnplotTSpot();  // unplot old target laser spot, if any
    PlotTSpot();    // plot new target laser spot

    if(   !(mode & XW_BLAST_OFF)
        &&  bk->number < 2
        &&  targcolor == col_shape0 + (bk->number & 1)  )
    {
        // oh dear, the target laser lies on our OWN bike!
        // this should NOT happen usually ... but sometimes it does,
        // therefore this extra check:
        return OK;
    }

    // fire a single bullet.
    // this flies so fast it cannot be seen.
    // it just impacts immediately at the position scanned
    // by the target laser (if any).

    if(targcolor != -1 || (mode & XW_BLAST_OFF))
    {
        if(mode & XW_BLAST_OFF)
        {
            // explode at pos'n of dummy bike

            UnplotTSpot();

            new ImpactExpl(
                bk->pos, col_mgimpact,
                (1 + xcharged) * 10, (1 + xcharged) * 2, 5,
                BE_MODE_TEMP
                );
        }
        else
        {
            // hitting directly a bike?
            // (checked only for main bikes)

            Bike *tbike = 0;

            if(bk->number < 2)
            {
                // get OTHER bike and check for position match

                if(tbike = (Bike*)field.getobj(bk->number ? BIKE0 : BIKE1))

                    ifn(tbike->matchpos(targpos))

                        tbike = 0;
            }

            if(tbike && tbike->sethit(targpos, col_mgimpact))
            {
                // hit a bike (which 'accepted' the hit)

                UnplotTSpot();

                // sometimes play a funny sound:
                // mind that 'direct hits' happen very often,
                // therefore play only in 3% of cases

                if(     (SNDRAND() % 1000) < 30
                    &&  field.soundexists(SND_BKHMG)    )
                {
                    field.stopsound();
                    field.playsound(SND_BKHMG, 1);
                }
            }
            else
            {
                // no... create normal impact explosion
                // at pos'n one pixel before target pos'n
                // (to avoid explosion centers in walls).

                UnplotTSpot();

                // first, plot a mini crater at impact position
                // into the background.
                if(hasshape())
                {
                    if(xcharged < 2)
                        shape().setface(0);
                    else
                        shape().setface(1);
                    shape().moveto(prepos[0],prepos[1]);
                    shape().plot();     // plot into front
                    field.setLevel(1);  // switch to backmap
                    shape().plot();     // plot into backmap
                    field.setLevel(0);  // switch back to front
                    shape().markAsUnplotted();  // so ~PlayObj won't unplot it
                }

                static uword MGPalette[4] =
                    {
                        0xfa0,
                        0xfc0,
                        0xf80,
                        0xff0
                    };

                new Explosion(
                        prepos,
                        (1 + xcharged) * 10,    // vecs
                        (1 + xcharged) * 2,     // steps
                        col_mgimpact,           // colcode
                        sizeof(MGPalette)/sizeof(uword),    // ncolors
                        MGPalette,              // palette
                        5,                      // no. of slices
                        BE_MODE_TEMP            // temporar
                        );
            }
        }

        switch(xcharged)
        {
         case 0:
         case 1:
            field.playsound(SND_PLAINMGF);
            break;

         case 2:
         case 3:    // mgun 3+, max. 4 sounds
            field.playsound((enum Sounds)(SND_CANF1 + (SNDRAND() & 3)));
            break;

         case 4:    // mgun 4+, max. 3 sounds
            if(field.soundexists(SND_MGUN4F1))
                field.playsound((enum Sounds)(SND_MGUN4F1 + (SNDRAND() % 3)));
            else
                field.playsound(SND_FATMGF);
            break;

         case 5:    // mgun 5+
            field.playsound(SND_FATMGF);
            break;
        }

        amo--;

        if(!amo)
        {
            // out of amo, play 'empty' sound

            switch(xcharged)
            {
                case 0:
                case 1:
                    field.stopsound(SND_PLAINMGF);
                    field.playsound(SND_PLAINMGE,1);
                    break;

                case 2:
                case 3:
                    field.stopsound(SND_CANF1);
                    field.stopsound(SND_CANF2);
                    field.stopsound(SND_CANF3);
                    field.stopsound(SND_CANF4);
                    field.playsound(SND_CANE, 1);
                    break;

                case 4:
                    if(field.soundexists(SND_MGUN4F1))
                    {
                        field.stopsound(SND_MGUN4F1);
                        field.stopsound(SND_MGUN4F2);
                        field.stopsound(SND_MGUN4F3);
                        field.playsound(SND_MGUN4E, 1);
                    }
                    else
                    {
                        field.stopsound(SND_FATMGF);
                        field.playsound(SND_FATMGE, 1);
                    }
                    break;

                case 5:
                    field.stopsound(SND_FATMGF);
                    field.playsound(SND_FATMGE, 1);
                    break;
            }
        }
    }

    // if launch() was called in BLAST_OFF mode, MGun MUST leave PlayField:

    if(!amo || (mode & XW_BLAST_OFF))   {delete this; return OK;}

    return  OK;
}

rcode MGun::step(PlayMsg* pm)
{_
    if(pm)  return INDATA;

    if(status != XWS_MOUNTED)   return OK;

    if(!box || !box->bike())
    {   SysErr(1110942111);     return INTERNAL;    }

    Bike *bk = box->bike();

    ushort nf;

    if(nf = nearfront())    // if one of the max. two active weapons
    {
        UnplotTSpot();      // unplot old target laser spot, if any

        XWeapon *xw = 0;

        switch(nf)
        {
            case 1 : xw = (XWeapon*)XWeapon::next();    break;
            case 2 : xw = box->first();     break;
        }

        // adjust mindist:
        // max. mindist with max. speed OR five plus

        long rmdr = field.sizemax / 6 - 50;
        cpix rmd1 = convif(bk->speed) * rmdr / convif(bk->speedmax);
        cpix rmd2 = xcharged * rmdr / 5;

        mindist = 50 + max(rmd1, rmd2);

        switch(xcharged)
        {
            case 0: mindist = min(mindist, 60); break;
            case 1: mindist = min(mindist, 80); break;
        }

        // If there's an open lightsword, avoid overlay
        // by tlaser 'cause this would leave pixel garbage.
        if(xw && xw->idcode() == LIGHTSWORD)
        {
            short l1 = ((LightSword*)xw)->getlength() + 20;
            mindist  = max(l1, mindist);
        }

        // PumpGun laser has other mindist than ours, so if one
        // is active don't plot our to avoid confusion.
        ifn(xw && xw->idcode() == PGUN) // only if no PGun active
            PlotTSpot();            // plot new target laser spot
    }
    else
    if(targcolor != -1) // laser spot still visible
        UnplotTSpot();  // make it invisible, we're inactive

    return  OK;
}

//|| TLaser

TargetLaser::TargetLaser(XWeapon *my, cpix xmindist)
{_
    xweapon = my;
    mindist = xmindist;

    memset(pos, 0xFF, sizeof(pos));
    memset(backcol, 0, sizeof(backcol));

    targcolor  = -1;
    targpos[0] = targpos[1] = 0;
    prepos[0]  = prepos[1]  = 0;

    idxpal     = 0;
    paldelay   = 0;
}

TargetLaser::~TargetLaser()
{_
    if(targcolor != -1) UnplotTSpot();
}

void TargetLaser::UnplotTSpot()
{_
    if(targcolor == -1) return;

    // unplot old target laser spot

    cpix cx,cy;

    for(ushort i=0; i<TLASER_MAXSPOTS; i++)
    {
        cx  = pos[i][0];
        cy  = pos[i][1];

        if(cx == SHRT_MAX)
            break;      // end of data reached

        if(cx != -1)    // if plotted at all
            field.unplot(cx,cy);
    }

    targcolor = -1; // spot now invalid
}

rcode TargetLaser::PlotTSpot()
{_
    if(!xweapon)        return  FAILED;
    if(!xweapon->box)   return  FAILED;

    Bike* bk    = xweapon->box->bike();

    if(!bk)             return FAILED;
    if(!bk->ana.log)    return FAILED;

    // target laser scans in bike's viewing direction
    // for all obstacles EXCEPT col_mgimpact
    // which MUST BE LAST OBSTACLE COLOR so it can
    // be excluded easily in the scanline() call.

    cpix tpos[2];
    pcol colbnd[2]={col_OBST_MIN+1,col_OBST_MAX-1}; // w/o flames + mgimpact!

    // max. shooting distance is usually up to halve of field width

    int maxdist   = bk->TargetRange(mindist);
    short targang = bk->TargetDir();

    tpos[0] = bk->pos[0] + convif(ftrig.sin(targang) * maxdist);
    tpos[1] = bk->pos[1] - convif(ftrig.cos(targang) * maxdist);

    if(tpos[0] <  field.bord[0])    tpos[0] = field.bord[0];
    if(tpos[0] >= field.bord[2])    tpos[0] = field.bord[2] - 1;
    if(tpos[1] <  field.bord[1])    tpos[1] = field.bord[1];
    if(tpos[1] >= field.bord[3])    tpos[1] = field.bord[3] - 1;

    targcolor = field.scanline(bk->launchpos(4), tpos,
                               colbnd, targpos, prepos);

    if(targcolor == -1 && field.inbord(tpos))
    {
        targpos[0] = prepos[0] = tpos[0];
        targpos[1] = prepos[1] = tpos[1];

        targcolor  = col_backgrnd;
    }

    static short relpos[TLASER_SHAPES][TLASER_MAXSPOTS * 2] =
        {
            // narrow arrows, small

             0,-2, -1,-3,  1,-3,
            -2, 2, -3, 2, -2, 3,
             2, 2,  3, 2,  2, 3,
             SHRT_MAX, SHRT_MAX,
             0,0, 0,0, 0,0, 0,0,
             0,0, 0,0, 0,0, 0,0,

            // narrow arrows, large

             0,-2, -1,-3,  1,-3,
            -2, 2, -3, 2, -2, 3,
             2, 2,  3, 2,  2, 3,
             0,-3, -1,-4,  1,-4,
            -4, 2, -3, 3, -2, 4,
             4, 2,  3, 3,  2, 4,

            // wide arrows, small
             0,-3, -1,-4,  1,-4,
            -3, 3, -4, 3, -3, 4,
             3, 3,  4, 3,  3, 4,
             SHRT_MAX, SHRT_MAX,
             0,0, 0,0, 0,0, 0,0,
             0,0, 0,0, 0,0, 0,0,

            // wide arrows, large
             0,-3, -1,-4,  1,-4,
            -3, 3, -4, 3, -3, 4,
             3, 3,  4, 3,  3, 4,
             0,-4, -1,-5,  1,-5,
            -5, 3, -4, 4, -3, 5,
             5, 3,  4, 4,  3, 5,

            // standard cross, small

            -1,-1, -2,-2,
             1,-1,  2,-2,
            -1, 1, -2, 2,
             1, 1,  2, 2,
             SHRT_MAX, SHRT_MAX,    // end of data
             0,0, 0,0, 0,0, 0,0,
             0,0, 0,0, 0,0, 0,0,
             0,0,                   // fill-up dummys

            // standard cross, large

            -1,-1, -2,-2, -3,-3,
             1,-1,  2,-2,  3,-3,
            -1, 1, -2, 2, -3, 3,
             1, 1,  2, 2,  3, 3,
             SHRT_MAX, SHRT_MAX,    // end of data
             0,0, 0,0, 0,0,
             0,0, 0,0,              // fill-up dummys

            // vertical cross, small
            -2, 0, -3, 0,
             0,-2,  0,-3,
             2, 0,  3, 0,
             0, 2,  0, 3,
             SHRT_MAX, SHRT_MAX,
             0,0, 0,0, 0,0, 0,0,
             0,0, 0,0, 0,0, 0,0,
             0,0,

            // vertical cross, large
            -2, 0, -3, 0, -4, 0, -5, 0,
             0,-2,  0,-3,  0,-4,  0,-5,
             2, 0,  3, 0,  4, 0,  5, 0,
             0, 2,  0, 3,  0, 4,  0, 5,
             SHRT_MAX, SHRT_MAX,
             0,0
        };

    short *rpptr = &relpos[field.opt.tlaser & (TLASER_SHAPES-1)][0];

    cpix nx,ny;
    pcol col;

    if(targcolor != -1)
    {
        // if posn's lies on walls outside field boundary, adjust them

        if(prepos[0] <  field.bord[0])  prepos[0] = field.bord[0];
        if(prepos[0] >= field.bord[2])  prepos[0] = field.bord[2] - 1;
        if(prepos[1] <  field.bord[1])  prepos[1] = field.bord[1];
        if(prepos[1] >= field.bord[3])  prepos[1] = field.bord[3] - 1;

        if(targpos[0] <  field.bord[0]) targpos[0] = field.bord[0];
        if(targpos[0] >= field.bord[2]) targpos[0] = field.bord[2] - 1;
        if(targpos[1] <  field.bord[1]) targpos[1] = field.bord[1];
        if(targpos[1] >= field.bord[3]) targpos[1] = field.bord[3] - 1;

        for(ushort i=0; i<TLASER_MAXSPOTS; i++)
        {
            if(*rpptr == SHRT_MAX)
            {
                pos[i][0]   = SHRT_MAX; // promote end of data
                break;                  // end of data reached
            }

            nx  = prepos[0] + *rpptr++;
            ny  = prepos[1] + *rpptr++;

            if(field.inbord(nx,ny)) // if new coords valid
            {
                //  plot pixel bypassing framebuffer

                pos[i][0]   = nx;
                pos[i][1]   = ny;

                if(bk->number == 0)
                    field.plotby(nx,ny, col_tlaser0);
                else
                if(bk->number == 1)
                    field.plotby(nx,ny, col_tlaser1);
                else
                    field.plotby(nx,ny, col_tlaser2);   // any other bike
            }
            else
                pos[i][0]   = -1;   // no spot plotted
        }
    }

    return OK;
}

#ifndef RDEMO

//|| PGun

PGun::PGun(cpix pos[2])

    :   XWeapon(pos, "Pump Gun", 'P', PGUN),
        PlayObj(PGUN,20,20),
        TargetLaser(this, 10)   // mindist 10 pixels
{_
    amo     = 3;    // no. of bullets
    createshape(SCL_PGCRATER, col_backgrnd);
    // if creation fails, there will simply be no craters plotted.
}

rcode PGun::launch(int xmode)
{_
    mode    |= xmode;

    Bike*   bk = box->bike();

    ifn(bk) {SysErr(2910941119); return INTERNAL;}

    // perform a laser re-scan to update target position:

    UnplotTSpot();  // unplot old target laser spot, if any
    PlotTSpot();    // plot new target laser spot

    // fire a single bullet.
    // this flies so fast it cannot be seen.
    // it just impacts immediately at the position scanned
    // by the target laser (if any).

    if(targcolor != -1 || (mode & XW_BLAST_OFF))
    {
        cpix tpos[2];

        if(mode & XW_BLAST_OFF)
        {
            tpos[0] = bk->pos[0];   // explode at pos'n
            tpos[1] = bk->pos[1];   // of dummy bike
        }
        else
        {
            tpos[0] = prepos[0];
            tpos[1] = prepos[1];
        }

        // core position must be valid, check it again:
        if(!field.inbord(tpos))
        {   SysErr(1110942136); return INTERNAL;    }

        // remove laser spot (if any).
        // targpos is invalid after that.

        UnplotTSpot();

        // now then, blast off cpos[] !

        // first, plot a mini crater at impact position
        // into the background.
        if(hasshape())
        {
             shape().setface(0);
             shape().moveto(tpos[0],tpos[1]);
             shape().plot();     // plot into front
             field.setLevel(1);  // switch to backmap
             shape().plot();     // plot into backmap
             field.setLevel(0);  // switch back to front
             shape().markAsUnplotted();  // so ~PlayObj won't unplot it
        }

        new PExpl(tpos, 1 + xcharged);

        if(!--amo || (mode & XW_BLAST_OFF))
            field.playsound(SND_PGUNFE);    // fire & empty
        else
        {
            field.playsound(SND_PGUNFR);    // fire & reload

            bk->shootdelay  = 700;  // wait 0.7 sec for next shot
        }
    }

    // if launch() was called in BLAST_OFF mode, PGun MUST leave PlayField:

    if(!amo || (mode & XW_BLAST_OFF))   {delete this; return OK;}

    return  OK;
}

rcode PGun::step(PlayMsg* pm)
{_
    if(pm)  return INDATA;

    if(status != XWS_MOUNTED)   return OK;

    if(!box || !box->bike())
    {   SysErr(1110942111);     return INTERNAL;    }

    ushort nf;

    if(nf = nearfront())    // if one of the max. two active weapons
    {
        UnplotTSpot();      // unplot old target laser spot, if any

        XWeapon *xw = 0;

        switch(nf)
        {
            case 1 : xw = (XWeapon*)XWeapon::next();    break;
            case 2 : xw = box->first();     break;
        }

        mindist = 10;

        // If there's an open lightsword, avoid overlay
        // by tlaser 'cause this would leave pixel garbage.
        if(xw && xw->idcode() == LIGHTSWORD)
        {
            short l1 = ((LightSword*)xw)->getlength() + 20;
            mindist  = max(l1, mindist);
        }

        PlotTSpot();    // plot new target laser spot
    }
    else
    if(targcolor != -1) // laser spot still visible
        UnplotTSpot();  // make it invisible, we're inactive

    return  OK;
}

//|| OilDriver

short OilDriver::dpix[DROP_PIX][2] =

    {
        -DROP_RADIUS,-DROP_RADIUS,
         DROP_RADIUS,-DROP_RADIUS,
         0, 0,
        -DROP_RADIUS, DROP_RADIUS,
         DROP_RADIUS, DROP_RADIUS

    };

OilDriver::OilDriver() : PlayObj(OIL_DRIVER, 200, 100)
{_
    ifree       = 0;
    ihighest    = 0;
    indrops     = 0;
    slice       = 0;

    //  init all drop instances
    memset(drop, 0, sizeof(drop));
}

rcode OilDriver::CreateDrop(cpix x, cpix y)
{_
 Drop   *d;
 cpix   px,py;
 pcol   colcode = col_oil;

    ifn(field.inbord(x,y))

        return  FAILED;

//  if(field.opt.playmode & PLAYMODE_NITROOIL)

    if(rand() % 100 < 5)

        colcode = col_nitro;

    if(ifree < DROPS_MAX)
    {
        ihighest = max(ihighest, ifree);

        d   = &drop[ifree];

        d->x        = (ushort)x;
        d->y        = (ushort)y;

        d->delay    = 55000L + (long)random(10000);

        for(ushort ip=0; ip<DROP_PIX; ip++)
        {
            px  = d->x + dpix[ip][0];
            py  = d->y + dpix[ip][1];

            if(field.inbord(px, py))

                switch(field.readpix(px, py))
                {
                    case col_nitro:
                    case col_xweapon:

                         break;

                    default:

                        field.plot(px, py, colcode);
                }
        }

        indrops++;  // count this drop

        //  find next ifree, if any:

        while(++ifree < DROPS_MAX && drop[ifree].delay);

        //  now, ifree might be DROPS_MAX, which means
        //  no more drops could be created at the moment.
    }

    return  OK;
}

rcode OilDriver::step(PlayMsg *pm)
{_
    if(pm)  return INDATA;

    Drop    *d;
    cpix    px,py;

    ushort  i,nbit,bail;
    bool    deleted = 0;    // deleted 1 or more pix

    short   slices  = 32;   // MUST be a power of 2 !

    long    cycms   = cyclesize * slices;

    i = (slice++) & (slices - 1);

    for(d = &drop[i]; i <= ihighest; i += slices, d += slices)
    {
        if(!d->delay)   continue;

        if((d->delay -= cycms) <= 0)
        {
            d->delay  = 0;

            indrops--;  // adapt number of drops

            for(ushort ip=0; ip<DROP_PIX; ip++)
            {
                px  = d->x + dpix[ip][0];
                py  = d->y + dpix[ip][1];

                if(field.inbord(px, py))

                    switch(field.readpix(px, py))
                    {
                        case col_nitro:
                        case col_oil:

                            field.plot(px, py, col_backgrnd);

                            break;
                    }
            }

            ifree   = min(ifree, i);    // adapt index to 1st free

            deleted = 1;
        }

    }   // endfor i

    if(deleted)
    {
        // adapt ihighest to last active pix

        while(ihighest && !drop[ihighest].delay)

            ihighest--;
    }

    return  OK;
}

//|| OilSquirt

OilSquirt::OilSquirt(cpix pos[2])

    :   XWeapon(pos, "Oil", 'O'),
        PlayObj(OIL_SQUIRT,30000)
{_
    //  it is expected that an OilSquirt is created
    //  only if OilDriver has at least that amount
    //  of drops free:

    amo     = 2;
}

rcode OilSquirt::launch(int xmode)
{_
    mode    |= xmode;

    Bike*   bk = box->bike();

    ifn(bk) {SysErr(2803941809); return INTERNAL;}

    new OilFlush(bk->launchpos(), bk,
                  40, 80, col_oil, 1, 0, 2, 0);

    //  OilFlush creates one Drop every 4th step,
    //  i.e. overall it's 40 * 80 / 4 drops.

    field.playsound(SND_OILF);

    amo--;

    if(!amo || (mode & XW_BLAST_OFF))
    {
        delete  this;
        return  OK;
    }
    else
        bk->shootdelay  = 500;  // delay next launch 0.5 sec

    return  OK;
}

rcode OilSquirt::step(PlayMsg *pm)
{
    if(pm)  return  INDATA;

    return  OK;
}

//|| Missile
Missile::Missile(Bike *bike, short chrg, enum ObjCodes xtarget)
    : MovingObj(MISSILE)
{_
    SetPos(bike->launchpos());
    SetMov(bike->dir, convfi(200), &bike->ana);

    memset(tpix, 0xFF, sizeof(tpix));
    ihead   = 0;

    flytime = 30000;    // max. flytime so many msec
    elapsed = 0;        // nothing yet flown
    target  = xtarget;  // target bike (if any)
    charge  = chrg;     // 1..3
    blastRequest = 0;

    sdelaymax   = 8;    // scan delay factor
    sdelaycnt   = 0;    // scan delay counter init

    createshape(SCL_MSCRATER, col_backgrnd);
    // if creation fails, there will simply be no craters plotted.
}

short Missile::tplot(cpix pos[2], pcol color)
{_
    cpix x=pos[0],y=pos[1];

    // if track has maxlength, unplot tail pix
    // unplot only if pixel wasn't overridden by another color

    ushort  itl = itail();

    cpix px = tpix[itl][0];
    cpix py = tpix[itl][1];

    // unplot tail pix

    if(px != -1)
    {
        if(field.readpix(px,py) == col_missile)
        {
            field.plot(px,py, tpix[itl][2]);
            tpix[itl][0]    = -1;   // mark as unset
        }
    }

    // add & plot new frontpix

    ihead   = (ihead + 1) & TIMSK;

    tpix[ihead][0]  = x;
    tpix[ihead][1]  = y;

    pcol col = field.readpix(x,y);

    switch(col)
    {
        case col_shape0:
        case col_shape1:
        case col_lsword0:
        case col_lsword1:

        case col_flame:     // these are
        case col_fire:      // scanned
        case col_fixwall:   // for
        case col_dust:      // on
        case col_waveexpl:  // flight

        case col_mgimpact:  // defense against
        case col_pexpl:     // missiles

            return  1;  // crash!

        case col_bikevcur:
        case col_tlaser0:
        case col_tlaser1:
        case col_cmimpact:

            col = col_backgrnd; // don't redraw

        default:

            tpix[ihead][2]  = col;
    }

    field.plot(x,y, col_missile);

    return  0;
}

rcode Missile::step(PlayMsg *pm)
{_
 int    res;
 uchar  crash = 0;
 pcol   col;
 Bike   *bk = 0, *tbk = 0;

    if(pm && pm->type != PMT_FURTHERSTEP)

        return  INDATA; // not supported

    if((res = advance(pm ? 0 : 1)) == NOTHING)

        return  OK;

    // stepped a pixel

    if(res & ABORDER)   // reached border?

        crash = 1;

    // obstacle checks are done in tplot call below

    if(!crash)
    {
        elapsed += cyclesize;

        if((flytime -= cyclesize) < 0)
        {
            flytime = 0;

            crash   = 1;
        }
    }

    if(!crash)
    {
        // visually step the missile

        if(tplot(pos, col_missile))

            crash   = 1;    // stepped onto some obstacle

        else

        if(++sdelaycnt >= sdelaymax)    // scan delay
        {
            sdelaycnt   = 0;

            long mindist = LONG_MAX, d[3];

            for(short i=0; i<2; i++)
            {
                if(target != UNKNOWN)
                    bk  = (Bike*)field.getobj(target);
                else
                    bk  = (Bike*)field.getobj(i ? BIKE1 : BIKE0);

                if(bk)
                {
                    // is distance to 'bk' new minimum?

                    d[0] = bk->pos[0] - pos[0];
                    d[1] = bk->pos[1] - pos[1];
                    d[2] = sqrt((ulong)(d[0]*d[0]) + (ulong)(d[1]*d[1]));

                    if(d[2] < mindist)
                    {
                        mindist = d[2];
                        tbk     = bk;   // yo: got new target bike
                    }
                }

                if(target != UNKNOWN)   break;
            }

            if(tbk)
            {
                // is target in sight?

                cpix tpos[2];
                pcol colbnd[2]={col_flame,col_dust};

                bool adaptdir = 0;

                if(field.scanline(pos, tbk->pos, colbnd, tpos) == -1)
                    adaptdir  = 1;
                else
                {
                    // not in sight: try to avoid collisions with obstacles

                    short atarg = ana.CalcAngle(d[0], d[1]);
                    short ahead = ana.angle;
                    short acur;
                    short pfree[4], fang = ahead;
                    cpix  cpos[2],spos[2];
                    long  l[3],fmax=0;
                    short c2th = ftrig.degmax() >> 1;
                    short c4th = ftrig.degmax() >> 2;

                    // how much free in heading direction?

                    cpos[0] = pos[0] + convif(ftrig.sin(ahead) * 80);
                    cpos[1] = pos[1] - convif(ftrig.cos(ahead) * 80);

                    field.scanline(pos, cpos, colbnd, spos);

                    l[0] = pos[0] - spos[0];
                    l[1] = pos[1] - spos[1];
                    fmax = sqrt((ulong)(l[0]*l[0]) + (ulong)(l[1]*l[1]));

                    for(short i=0; i<=4; i++)
                    {
                        acur    = atarg + i * c2th / 4 - c4th;
                        cpos[0] = pos[0] + convif(ftrig.sin(acur) * 80);
                        cpos[1] = pos[1] - convif(ftrig.cos(acur) * 80);

                        field.scanline(pos, cpos, colbnd, spos);

                        l[0] = pos[0] - spos[0];
                        l[1] = pos[1] - spos[1];
                        l[2] = sqrt((ulong)(l[0]*l[0]) + (ulong)(l[1]*l[1]));

                        if(l[2] > fmax)
                        {
                            fmax    = l[2];
                            fang    = acur;
                        }
                    }

                    if(fang != ahead)
                    {
                        short* v = ana.CalcVDir(fang, 30000);

                        d[0] = (long)(*v);
                        d[1] = (long)(*(v+1));

                        adaptdir = 1;
                    }
                }

                if(adaptdir && (target != UNKNOWN))
                {
                // adapt flying direction to target bike
                // atarg is direction as should be
                // ahead is direction as is

                short atarg = ana.CalcAngle((short)d[0], (short)d[1]);
                short ahead = ana.angle;
                short dmax  = ftrig.degmax();
                short step  = ((long)dmax * (long)sdelaymax) >> 8;
                // step 1/n of a circle.
                // the more scan delay, the larger the steps.

                if(atarg >= ahead)
                    if(atarg - ahead <= (dmax >> 1))    ahead += step;
                    else                                ahead -= step;
                else
                    if(ahead - atarg <= (dmax >> 1))    ahead -= step;
                    else                                ahead += step;

                if(ahead < 0)   ahead += dmax;

                ahead      &= ftrig.degmask();

                ana.angle   = ahead;

                ana.CalcVDir(); // re-calc direction vector

                // distance to target below threshold?

                if(d[2] < 10)       // if within so many pixels

                    crash   = 1;    // enforce explosion
                }

                if(target == UNKNOWN)
                   target  = (tbk->number ? BIKE1 : BIKE0);

            } // endif(tbk)
        }
    }

    if(crash || blastRequest)
    {
        // unplot missile

        cpix px,py;

        for(ushort i=0; i<TPIX; i++)
        {
            px = tpix[i][0];
            py = tpix[i][1];

            if(px != -1)

                if(field.readpix(px,py) == col_missile)

                    field.plot(px,py, tpix[i][2]);
        }

        // plot impact crater?
        if(hasshape())
        {
            shape().setface(0);
            shape().moveto(pos[0],pos[1]);
            shape().plot();     // plot into front
            field.setLevel(1);  // switch to backmap
            shape().plot();     // plot into backmap
            field.setLevel(0);  // switch back to front
            shape().markAsUnplotted();  // so ~PlayObj won't unplot it
        }

        // perform explosion

        new ImpactExpl(pos, col_cmimpact, 80, 8 * charge, 3, BE_MODE_TEMP);

        field.stopsound(SND_MISLF);
        field.playsound(SND_MISLH);

        delete  this;   // missile leaves playfield
        return  OK;
    }

    //  if there's a big undriven 'reladv' distance remaining,
    //  request another CPU time slice in this time cycle:

    return (reladv >= RADV_MAX) ? SPECIAL : OK;
}

//|| MLauncher
MLauncher::MLauncher(cpix pos[2]) : XWeapon(pos, "Cruise Missiles", 'C', MLAUNCHER)
{_
    amo      = 3;   // so many missiles per default
    xcharged = 0;   // not yet extra charged
}

rcode MLauncher::launch(int xmode)
{_
 Bike* bike = box->bike();

    ifn(bike)   {SysErr(103952000); return INTERNAL;}

    mode |= xmode;

    if(mode & XW_BLAST_OFF)
    {
        new ImpactExpl(bike->pos, col_cmimpact, 80, 1, 3, BE_MODE_TEMP);
        field.playsound(SND_MISLH);

        delete  this;
        return  OK;
    }

    enum ObjCodes targ = UNKNOWN;

    if(bike->number == 0)   targ = BIKE1;
    else
    if(bike->number == 1)   targ = BIKE0;

    new Missile(bike, 1 + xcharged, targ);

    bike->shootdelay = max(bike->shootdelay, 500);

    ifn(mode & XW_BLAST_OFF)        // if not exploding NOW,
    {
        field.playsound(SND_MISLF); // play firing sound
        if(amo == 1 && field.soundexists(SND_MLAUNE))
            field.playsound(SND_MLAUNE);
    }

    if(!--amo || (mode & XW_BLAST_OFF)) // if XW_BLAST_OFF, MUST detach()
    {
        delete  this;   // does also detach()
        return  OK;
    }

    return OK;
}

//|| Rocket

Rocket::Rocket(cpix xpos[2], AnalogueCtl &anadir, short chrg)
    : PlayObj(ROCKET, 20, 0)
{_
    SetPos(xpos);
    spos[0] = xpos[0];
    spos[1] = xpos[1];

    // 'chrg' ranges from 1 to 6
    if(chrg < 1)    chrg = 1;
    if(chrg > 6)    chrg = 6;

    ana     = anadir;
    charge  = chrg * 2;

    curseg  = -1;
    maxdist = field.sizemax * 2L / 3L;
    maxseg  = 50 + (6 - chrg) * 6;
    segsize = maxdist / maxseg;

    rxmax   = spos[0] + convif(ftrig.sin(ana.angle) * maxdist);
    rymax   = spos[1] - convif(ftrig.cos(ana.angle) * maxdist);

    blastRequest = 0;

    memset(tseg, 0, sizeof(tseg));

    createshape(SCL_RCRATER, col_backgrnd);
    // if creation fails, there will simply be no craters plotted.
}

rcode Rocket::step(PlayMsg* pm)
{_
    if(pm)  return INDATA;  // using no FURTHERSTEPS

    //  unplot tail segment, if valid.
    if(tseg[nsegs-1].flags & fvalid)
        field.unline(   tseg[nsegs-2].pos[0],tseg[nsegs-2].pos[1],
                        tseg[nsegs-1].pos[0],tseg[nsegs-1].pos[1]   );

    //  rotate entries
    for(ushort i = nsegs - 1; i > 0; i--)
        tseg[i]  = tseg[i-1];

    //  calc new frontmost segment pos'n
    long dist = curseg++ * segsize;
    tseg[0].pos[0]  = spos[0] + convif(ftrig.sin(ana.angle) * dist);
    tseg[0].pos[1]  = spos[1] - convif(ftrig.cos(ana.angle) * dist);

    //  if valid,
    short crash = 0;
    if(field.inbord(tseg[0].pos))
    {
        tseg[0].flags   |= fvalid;

        //  and if older point is valid,
        if(tseg[1].flags & fvalid)
        {
            //  scan it for obstacles
            cpix    poshit[2],posprehit[2];
            pcol    colbnd[2]={col_OBST_MIN+3,col_OBST_MAX};

            //  colbnd w/o col_flame, col_fire and col_waveexpl
            if(field.scanline(  tseg[0].pos, tseg[1].pos,
                                colbnd, poshit, posprehit   ) != -1)
                crash   = 2;    // crash by obstacle
            else
            {
                //  and plot it, bypassing PixelDisplay's framebuffer:
                field.lineby(   tseg[0].pos[0],tseg[0].pos[1],
                                tseg[1].pos[0],tseg[1].pos[1],
                                col_rocket
                            );
            }
        }
    }
    else
        crash = 1;  // reached invalid position

    if(curseg >= maxseg)
        crash = 1;  // maximum steps done

    //  take most-distant point as potential impact position
    cpix ipos[2];

    ipos[0] = spos[0];  // in worst case,
    ipos[1] = spos[1];  // take launch position

    for(i = 0; i < nsegs; i++)
        if(tseg[i].flags & fvalid)
        {
            ipos[0] = tseg[i].pos[0];   // take as potential
            ipos[1] = tseg[i].pos[1];   // impact position
            break;
        }

    pos[0]  = ipos[0];  // set also PlayObj's
    pos[1]  = ipos[1];  // position to that

    //  is there a bike nearby?

    ushort mseg = nsegs >> 1;           // middle segment index

    if(     // curseg >= (maxseg >> 3)
            dist >= 60L                 // if flown a minimum distance
        &&  (tseg[mseg].flags & fvalid) // which then should be the case
      )
    {
        long d[3];
        Bike *bk;

        for(i=0; i<2; i++)
            if(bk = (Bike*)field.getobj(i ? BIKE1 : BIKE0))
            {
                d[0] = bk->pos[0] - tseg[mseg].pos[0];
                d[1] = bk->pos[1] - tseg[mseg].pos[1];
                d[2] = sqrt((ulong)(d[0]*d[0]) + (ulong)(d[1]*d[1]));

                // if nearer than so many pixels
                if(d[2] <= bk->shape().frontsize() + 6L)
                {
                    crash = 3;  // detonate, extra fast
                    break;
                }
            }
    }

    //  impact?

    if(crash || blastRequest)
    {
        if(crash == 2 && (--charge <= 0))
           crash = 1;   // broke thru enough walls

        if(crash != 2)
        {
        //  unplot anything visible.
        for(i = 0; i < nsegs - 1; i++)

            if(     (tseg[i].flags & fvalid)
                &&  (tseg[i+1].flags & fvalid)  )

                field.unline(   tseg[i].pos[0],tseg[i].pos[1],
                                tseg[i+1].pos[0],tseg[i+1].pos[1]   );
        }

        static ushort explpal[] =
        {
            0xfa0,
            0xfb0,
            0xfc0,
            0xfd0,
            0xfe0
        };

        // plot impact crater?
        if(hasshape())
        {
            shape().setface(0);
            shape().moveto(ipos[0],ipos[1]);
            shape().plot();     // plot into front
            field.setLevel(1);  // switch to backmap
            shape().plot();     // plot into backmap
            field.setLevel(0);  // switch back to front
            shape().markAsUnplotted();  // so ~PlayObj won't unplot it
        }

        new Explosion(  ipos,
                        (crash == 3) ? 80 : 40,
                        5,
                        col_waveexpl,
                        sizeof(explpal)/sizeof(ushort), explpal,
                        (crash == 3) ? 4 : 10,
                        BE_MODE_TEMP
                     );

        field.stopsound(SND_KATSHAF1);
        field.stopsound(SND_KATSHAF2);
        field.playsound(SND_KATSHAH);   // NOT in stereo

        if(crash != 2)      // if not crashed due to obstacle
        {
            delete  this;   // missile leaves playfield
            return  OK;
        }
    }

    return  OK;
}

//|| Katjuscha
Katjuscha::Katjuscha(cpix pos[2]) : XWeapon(pos, "Rocket Launcher", 'K', KATJUSCHA)
{_
    amo      = 3;   // so many rockets per default
    xcharged = 0;   // not yet extra charged
}

rcode Katjuscha::launch(int xmode)
{_
 Bike* bike = box->bike();

    ifn(bike)   {SysErr(103952000); return INTERNAL;}

    mode |= xmode;

    if(mode & XW_BLAST_OFF)
    {
        new ImpactExpl(bike->pos, col_cmimpact, 80, 1, 3, BE_MODE_TEMP);
        field.playsound(SND_KATSHAH);

        delete  this;
        return  OK;
    }

    //  calc launch positions for twin rockets

    ushort rang, lang;
    cpix   rpos[2], lpos[2];

    rang = bike->ana.angle + ((ftrig.degmax()     / 8) & ftrig.degmask());
    lang = bike->ana.angle + ((ftrig.degmax() * 7 / 8) & ftrig.degmask());

    long   dist = bike->shape().frontsize() + 8;  // distance to the left and right

    rpos[0] = bike->pos[0] + convif(ftrig.sin(rang) * dist);
    rpos[1] = bike->pos[1] - convif(ftrig.cos(rang) * dist);

    lpos[0] = bike->pos[0] + convif(ftrig.sin(lang) * dist);
    lpos[1] = bike->pos[1] - convif(ftrig.cos(lang) * dist);

    //  launch two rockets to the bike's sides

    if(field.inbord(rpos))
        new Rocket(rpos, bike->ana, 1 + xcharged);

    if(field.inbord(lpos))
        new Rocket(lpos, bike->ana, 1 + xcharged);

    bike->shootdelay = max(bike->shootdelay, 400);

    ifn(mode & XW_BLAST_OFF)    // if not exploding NOW,
    {
        field.stopsound(SND_KATSHAH);   // the case it still plays

        if(xcharged <= 2)
            field.playsound(SND_KATSHAF1, 2);   // play firing sound
        else
            field.playsound(SND_KATSHAF2, 2);   // play other firing sound

        if(amo == 1 && field.soundexists(SND_KATSHAE))
            field.playsound(SND_KATSHAE);
    }

    if(!--amo || (mode & XW_BLAST_OFF)) // if XW_BLAST_OFF, MUST detach()
    {
        delete  this;   // does also detach()
        return  OK;
    }

    return OK;
}

#endif  // !defined(RDEMO)

#define FORSWITCHES for(i=0; i<NSWITCHES; i++)
#define SWITCHX  sw[i].pos[0]
#define SWITCHY  sw[i].pos[1]
#define RDELAY   sw[i].rdelay
#define HOTRDCNT sw[i].hotrdcnt

XWCycler::XWCycler() : PlayObj(XWCYCLER, 100)
{_
    idead = 1;

    if(createshape(SCL_XWSWITCH, col_xwswitch)) // deleted in PlayObj dtr
    {
        MemErr(1112951408);
        return;
    }

    memset(sw, 0, sizeof(sw));
    // every switch with SWITCHX == 0 is not created

    idead = 0;
}

XWCycler::~XWCycler()
{_
}

rcode XWCycler::step(PlayMsg* pm)
{_
    if(pm)  return INDATA;

    ushort i;
    FORSWITCHES
    {
        if(!SWITCHX)
            break;  // reached 1st uncreated switch, if any

        if((RDELAY -= cyclesize) < 0)
        {
            if(HOTRDCNT)
            {
                HOTRDCNT--;
                RDELAY  = 100;
            }
            else
                RDELAY  = 1000;

            // refresh cycleswitch:
            shape().moveto(SWITCHX,SWITCHY);
            shape().plot();
        }
    }

    return OK;
}

rcode XWCycler::createswitch(cpix x,cpix y)
{
    cpix ox,oy;

    ushort i;
    FORSWITCHES
    {
        if(!SWITCHX)
        {
            shape().moveto(x,y);
            if(shape().prescan(ox,oy))  // if position is not free,
                return FAILED;          // don't create there
            shape().plot();
            SWITCHX = x;
            SWITCHY = y;
            RDELAY  = 1000;
            return OK;
        }
    }

    return FAILED;
}

rcode XWCycler::createmissingswitches(void)
{
    ushort i;
    FORSWITCHES
    {
        if(i >= 3)  // if 3 switches exist,
            break;  // that's enough.

        if(!SWITCHX)
        {
            SWITCHX = (rand() % (field.size[0] * 8L / 10)) + field.bord[0] + field.size[0] / 10;
            SWITCHY = (rand() % (field.size[1] * 8L / 10)) + field.bord[1] + field.size[1] / 10;
            shape().moveto(SWITCHX,SWITCHY);
            shape().plot();
            RDELAY  = 1000;
        }
    }

    return OK;
}

rcode XWCycler::touchswitch(cpix x, cpix y)
{
    ushort i;
    FORSWITCHES
    {
        if(!SWITCHX)
            break;  // reached 1st uncreated switch, if any

        if(shape().ispixset(x - SWITCHX, y - SWITCHY))
        {
            // probably a bike drover over a switch, and by doing so
            // deleted the switch image in the frame buffer.
            // therefore intensify refresh for a period:
            HOTRDCNT =  10; // ten times
            RDELAY   = 100; // every 100 msec
            // after that, go back to normal refresh rate.
            return OK;
        }
    }

    field.tell("touchswitch failed");
    return FAILED;
}
