/*   _____________________________________________________________
    /                                                             \
    | File: global.cpp - code for diverse classes, esp. Bike
    \_____________________________________________________________/

    contents:

        code for

            AnalogueCtl
            XWTileField
            PlayObj
            MovingObj
            JoyStick
            JoyPipe
            PlayStick
            Blinker
            HTunnel
            ItemCnt
            ItemGen
            BikeDriver
            Joy11Driver
            Bike
            FireDriver

    Developers: ID: Name:
    =========== --- -----
                JTH Juergen Thumm
  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\
 yymmdd ------- --- ---------------------------------------------------------
 940303 1.22    JTH created
 940314             ~XWeapon created, auto-removes remaining weapons
 940316             optimized spreading of PC checkpoints ('_')
 940317             fix: endless coordinate calc loop in ItemGen::calcipos
 940318             moved Explosion & Flame class code to 'explosion.cpp'.
                    Bike::steer: resetting reladv now after bullet launch,
                    hope crash-into-own-bullet bug is now fixed
 940322 1.31        all XWeapon's now support BLAST_OFF mode
 940328 1.33        MGun performance improvement fix with Clip class
 940404 1.34        SND_CHG_WEAPON now playing only on FTHROWER/MGUN detach
 940407 1.35        see .h
 940409 1.351       OpenFastGfx, CloseFastGfx
 940412 1.352       180 degree turn non-avoid bug fixed -> Bike::lsdir
                    Bazooka movement fix
 940416 1.36        OpenFastGfx updated to long array and 1100 lines support
 940803 1.37        'justice' support in Bike::Bike
 940811 1.38        migrated parts of this file into new 'xweap.cpp'
 950122             SND_HOTINHERE not so often.
*/

#   include "expdef.h"

#ifdef  WIN16
#   include <mmsystem.h>
#endif

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

ushort  AnalogueCtl::CalcAngle(short xdx, short xdy)
{
    static  ushort qtab[2][2] = {3,2,0,1};
    static  ushort sel[4]    = {1,0,1,0};

    long    dx = xdx, dy = xdy;

    long    ang = 0, quad;

    long    circ4th = ftrig.degmax() >> 2;
    long    rsum;

    quad    = qtab[dx < 0 ? 0 : 1][dy < 0 ? 0 : 1];

    if(rsum = ABS(dx) + ABS(dy))
    {
        ang = quad * (circ4th)

              + (   (sel[quad] & 1) ?

                      (ABS(dx) * (circ4th) / rsum)

                    : (ABS(dy) * (circ4th) / rsum)
                );
    }

    return  (ushort)ang;
}

ushort  AnalogueCtl::CalcAngle(void)
{
    return  angle = CalcAngle(vdir[0], vdir[1]);
}

short*  AnalogueCtl::CalcVDir(short angle, short scale)
{
 static short vec[2];

    vec[0]  =     (short)convif(ftrig.sin(angle) * (long)scale);
    vec[1]  = 0 - (short)convif(ftrig.cos(angle) * (long)scale);

    return  vec;
}

void    AnalogueCtl::CalcVDir(void)
{
    short* v = CalcVDir(angle, 30000);

    vdir[0] = *v;
    vdir[1] = *(v+1);
}

//|| XWTileField
XWTileField::XWTileField()
 : idead(0),
   wpix(0),
   hpix(0),
   wt(0),
   ht(0),
   tiles(0),
   size(0)
{ }

rcode XWTileField::open(cpix w,cpix h)
{
    idead = 1;

    wpix    = w;
    hpix    = h;
    wt      = w / XWA_SHAPESIZE;
    ht      = h / XWA_SHAPESIZE;
    size    = wt*ht;

    if(!(tiles = new uchar[size]))
    { MemErr(2611951820); return MEMORY; }

    reset();

    idead = 0;

    return OK;
}

XWTileField::~XWTileField()
{
    if(tiles) delete tiles;
}

void XWTileField::reset(void)
{
    if(tiles)
        memset(tiles, 0, size);
}

uchar &XWTileField::tile(cpix x,cpix y)
{
    static uchar dummy;
    if(!tiles)
    { SysErr(2611951827); return dummy; }

    ushort xt = x / XWA_SHAPESIZE;
    ushort yt = y / XWA_SHAPESIZE;

    if(xt>=wt || yt>=ht)
    { perr("XWTileField::tile: illegal xy %d %d",x,y); return dummy; }

    return tiles[yt * wt + xt];
}

void XWTileField::setasused(cpix x,cpix y,uchar val)
{ tile(x,y) = val; }

uchar XWTileField::isused(cpix x,cpix y)
{ return tile(x,y); }

//|| PlayOCode
PlayObj::PlayObj(enum ObjCodes xidcode, int timecycsize, int xpsac_size)
{_
    if((ushort)xidcode >= (ushort)ID_MAX)
    {   SysErr(2204951642); return; }

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

    cyclesize   = timecycsize;
    psac_size   = xpsac_size;
    delaycnt    = cyclesize;
    id.code     = xidcode;

    ishape      = 0;

    field += this;
}

PlayObj::~PlayObj()
{_
    field -= this;

    if(ishape)
    {
        ishape->unplot();   // if not done already
        destroyshape();
    }
}

Shape &PlayObj::shape(void)
{
    if(!ishape) SysErr(1911951723);
    return *ishape;
}

rcode PlayObj::createshape(enum EShapeClass escl, pcol col)
{
    if(ishape) { SysErr(1911951728); return INTERNAL; }
    if(!(&field.shapeclass(escl))) { SysErr(1911951729); return INTERNAL; }

    if(!(ishape = new Shape(field.shapeclass(escl), col)))
    {   MemErr(1911951727); return MEMORY;      }

    if(ishape->isdead())
    {   destroyshape(); return FAILED;  }

    return OK;
}

void PlayObj::destroyshape(void)
{
    if(ishape) delete ishape;
    ishape = 0;
}

// for CPUentry() search near PlayField::stepobjs()

//|| MovObjCode

MovingObj::MovingObj(enum ObjCodes xidcode, int timecsize, int psacsize)

    : RADV_MAX  (100),

      NOTHING   (0),    // didn't step a pixel
      PIXSTEP   (1),    // stepped a single pixel

      LBORDER   (2),    // reached left   border
      RBORDER   (4),    // reached right  border
      TBORDER   (8),    // reached top    border
      BBORDER   (16),   // reached bottom border

      ABORDER   (LBORDER|RBORDER|TBORDER|BBORDER),

      PASSED_HTUNNEL    (32),   // passed horizontal tunnel
      PASSED_VTUNNEL    (64),   // passed vertical tunnel

      PlayObj   (xidcode, timecsize, psacsize)
{_
    dir     = UP;
    reladv  = RADV_MAX;     // step immediately (if digital)
    speed   = 0;
}

void MovingObj::SetMov(enum Dirs xdir, intf xspeed, AnalogueCtl *actl)
{_
    dir     = xdir;
    speed   = xspeed;

    if(actl)    ana = *actl;    // copy analogue direction info
}

void MovingObj::SetDir(short adir)
{
    ana.angle = adir;
    ana.CalcVDir();

    if(hasshape())
        shape().turnto(adir);

    // convert vector dir to digital dir
    // first convert to 0..7 dir,
    // then that to enum.
    dir = field.convdir((short)(((long)adir << 3) / (long)ftrig.degmax()));
}

int MovingObj::advance(bool addup)
{_
 cpix oldpos[2];
 int res=NOTHING;
 enum Dirs sdir = dir;  // step direction(s)
 bool checkdigadd = 1;  // perform addup as digital

    if(ana.log)
    {
        // moving ANALOGUE:
        if(addup)
        {
            // the following is all SIGNED !

            // 1. calc relation between x and y direction value,
            //    if there is ANY x/y elongation

            long rsum,rx,ry;

            if(rsum = ABS((long)ana.vdir[0]) + ABS((long)ana.vdir[1]))
            {
                // we have a usable analogue direction.

                sdir        = NONE; // digi dir not needed
                checkdigadd = 0;    // don't addup as digital

                rx  = ana.vdir[0] * 1000L / rsum;   // 0.xxx value
                ry  = ana.vdir[1] * 1000L / rsum;   // 0.xxx value

                // 2. add percentage of speed to related x / y advance

                ana.vadv[0] += convif(speed) * rx / 1000L;
                ana.vadv[1] += convif(speed) * ry / 1000L;

                if(ana.vadv[0] < 0 - RADV_MAX)
                { sdir = (enum Dirs)(sdir | LEFT);  ana.vadv[0] += RADV_MAX; }

                if(ana.vadv[0] > 0 + RADV_MAX)
                { sdir = (enum Dirs)(sdir | RIGHT); ana.vadv[0] -= RADV_MAX; }

                if(ana.vadv[1] < 0 - RADV_MAX)
                { sdir = (enum Dirs)(sdir |   UP);  ana.vadv[1] += RADV_MAX; }

                if(ana.vadv[1] > 0 + RADV_MAX)
                { sdir = (enum Dirs)(sdir | DOWN ); ana.vadv[1] -= RADV_MAX; }

                if(sdir == NONE)
                    return res; // nothing at all happened
            }

            // ... else take digi direction as default
        }
    }

    if(checkdigadd)
    {
        // moving DIGITAL:

        // by default, add up relative advance counter:
        if(addup)   reladv += convif(speed);

        // now check for overflow

        if(reladv < RADV_MAX)
            return res; // nothing at all happened

        reladv  -= RADV_MAX;
    }

    // advance a single playfield pixel position:

    res |= PIXSTEP;

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

    if(sdir & LEFT)
        if(--pos[0]  < field.bord[0])
        {
            // horizontal tunnel passing?
            // check with > < instead of >=, <= the case
            // htunnel is shut and thus only one pix large.

            if(pos[1] > field.htun[0] && pos[1] < field.htun[1])
            {
                pos[0]  = field.bord[2]-1;
                res |= PASSED_HTUNNEL;
            }
            else
                res |= LBORDER;
        }

    if(sdir & RIGHT)
        if(++pos[0] >= field.bord[2])
        {
            if(pos[1] > field.htun[0] && pos[1] < field.htun[1])
            {
                pos[0]  = field.bord[0];
                res |= PASSED_HTUNNEL;
            }
            else
                res |= RBORDER;
        }

    if(sdir & UP)
        if(--pos[1]  < field.bord[1])
        {
            // passing vertical tunnel?

            if( (   pos[0] >= field.vtun[0][0]
                 && pos[0] <= field.vtun[0][1])
                ||
                (   pos[0] >= field.vtun[1][0]
                 && pos[0] <= field.vtun[1][1]) )
            {
                pos[1] = field.bord[3]-1;
                res |= PASSED_VTUNNEL;
            }
            else
                res |= TBORDER;
        }

    if(sdir & DOWN)
        if(++pos[1] >= field.bord[3])
        {
            // passing vertical tunnel?

            if( (   pos[0] >= field.vtun[0][0]
                 && pos[0] <= field.vtun[0][1])
                ||
                (   pos[0] >= field.vtun[1][0]
                 && pos[0] <= field.vtun[1][1]) )
            {
                pos[1] = field.bord[1];
                res |= PASSED_VTUNNEL;
            }
            else
                res |= BBORDER;
        }

    // if collided with border, step back:
    if(res & (LBORDER|RBORDER)) pos[0] = oldpos[0];
    if(res & (TBORDER|BBORDER)) pos[1] = oldpos[1];

    if(hasshape())
        shape().moveto(pos[0],pos[1]);

    return res;
}

//|| JoyStick

enum Dirs JoyStick::states[16] = {
        NONE,
        DOWN,
        (enum Dirs)(RIGHT|DOWN),
        RIGHT,
        UP,
        NONE,
        NONE,
        (enum Dirs)(RIGHT|UP),
        (enum Dirs)(LEFT|UP),
        NONE,
        NONE,
        NONE,
        LEFT,
        (enum Dirs)(LEFT|DOWN),
        NONE,
        NONE
        };

enum Dirs JoyStick::read(ExtJoyInfo* extinf)
{_
 static KeyBoard kb;

 if(field.opt.emulstick & (1 << num))
 {
    // use keyboard keys to emulate joystick

    ushort r = 0;

    if(num == 0)
    {
        if(kb.isdown('S'))  r |= (enum Dirs)LEFT;
        if(kb.isdown('D'))  r |= (enum Dirs)RIGHT;
        if(kb.isdown('E'))  r |= (enum Dirs)UP;
        if(kb.isdown('X'))  r |= (enum Dirs)DOWN;

        if(kb.isdown('W'))  r |= (enum Dirs)LEFT |(enum Dirs)UP;
        if(kb.isdown('R'))  r |= (enum Dirs)RIGHT|(enum Dirs)UP;
        if(kb.isdown('Y'))  r |= (enum Dirs)LEFT |(enum Dirs)DOWN;
        if(kb.isdown('C'))  r |= (enum Dirs)RIGHT|(enum Dirs)DOWN;

        if(kb.isdown('M'))  r |= (enum Dirs)BUTTON;
    }
    else
    {
        if(kb.isdown(KEY_NUMPAD4))  r |= (enum Dirs)LEFT;
        if(kb.isdown(KEY_NUMPAD6))  r |= (enum Dirs)RIGHT;
        if(kb.isdown(KEY_NUMPAD8))  r |= (enum Dirs)UP;
        if(kb.isdown(KEY_NUMPAD2))  r |= (enum Dirs)DOWN;

        if(kb.isdown(KEY_NUMPAD7))  r |= (enum Dirs)LEFT |(enum Dirs)UP;
        if(kb.isdown(KEY_NUMPAD9))  r |= (enum Dirs)RIGHT|(enum Dirs)UP;
        if(kb.isdown(KEY_NUMPAD1))  r |= (enum Dirs)LEFT |(enum Dirs)DOWN;
        if(kb.isdown(KEY_NUMPAD3))  r |= (enum Dirs)RIGHT|(enum Dirs)DOWN;

        if(kb.isdown(KEY_NUMPAD0))  r |= (enum Dirs)BUTTON;
    }

    return (enum Dirs)r;
 }

#ifdef  AMIGA

 uword x,idx,*xp;
 enum Dirs butm;

    xp  = (uword *)0xDFF00A;
    if(num==1)  xp = (uword *)0xDFF00C;

    x   = *xp;

    // create 4-bit-index for states[] tab:
    idx = ((x & (1<<9|1<<8)) >> 6) | (x & 3);

    // crt button-mask:
    xp  = (uword *)0xBFE000;
    x   = *xp;
    butm = NONE;
    if(num==0 && !(x & 1<<6))   butm = (enum Dirs)BUTTON;
    if(num==1 && !(x & 1<<7))   butm = (enum Dirs)BUTTON;

    return  (enum Dirs(states[idx] | butm));

#endif

#ifdef  WIN16   // DIGITAL JoyStick emulation under Windows 3.1

  JOYINFO ji;

  if(num==0)
    joyGetPos(JOYSTICKID1, (LPJOYINFO)&ji);
  else
    joyGetPos(JOYSTICKID2, (LPJOYINFO)&ji);

  ushort lth = 21845U; // lower threshold for analog/digital conversion
  ushort uth = 43691U; // upper threshold

  ushort r = 0;

  if(ji.wXpos < lth)    r |= (enum Dirs)LEFT;
  if(ji.wXpos > uth)    r |= (enum Dirs)RIGHT;

  if(ji.wYpos < lth)    r |= (enum Dirs)UP;
  if(ji.wYpos > uth)    r |= (enum Dirs)DOWN;

  //if(ji.wButtons & 2) r |= (enum Dirs)BUTTON;
  if(ji.wButtons & 1)   r |= (enum Dirs)BUTTON;

  // IF there's a target for extended joystick informations, fill it:

  if(extinf)
  {
    if(ji.wXpos <= (ushort)SHRT_MAX)
            extinf->analogX = SHRT_MIN + ji.wXpos;
    else    extinf->analogX = ji.wXpos - SHRT_MAX - 1;

    if(ji.wYpos <= (ushort)SHRT_MAX)
            extinf->analogY = SHRT_MIN + ji.wYpos;
    else    extinf->analogY = ji.wYpos - SHRT_MAX - 1;

    extinf->button2 = (ji.wButtons & 2) ? 1 : 0;
  }

  return (enum Dirs)r;

#endif  // WIN16
}

//|| JoyPipe

JoyPipe::JoyPipe(uchar num)

    :   JP_ILLEG    (0),
        JP_UNSET    (1),
        JP_WRITE    (2),
        JP_READ     (3),

        JoyStick    (num)
{_
    setmode(JP_ILLEG);

    curdir[0] = curdir[1] = (enum Dirs)NONE;
}

rcode   JoyPipe::setmode(int m)
{_
 rcode  rc=OK;

    if(m > JP_READ) {SysErr(704941656);rc=INTERNAL;}

#ifdef REPLAY
    if(     pipe[0].index(0)
        ||  pipe[1].index(0)
        ||  pipe[2].index(0)
        ||  pipe[3].index(0)
        ||  pipe[4].index(0)
      )
        {SysErr(704941657);rc=INTERNAL;}
#endif

    imode = m;

    curdir[0] = curdir[1] = (enum Dirs)NONE;

    extjinf[0].reset();
    extjinf[1].reset();

    return  rc;
}

void JoyPipe::hwfetch()
{_
    curdir[num & 1] = JoyStick::read(&extjinf[num & 1]);
}

enum Dirs JoyPipe::read(ExtJoyInfo* extinfo)
{_
 enum Dirs d;
 uchar  uc,buf[10];

    if((enum Dirs)ALL_MASK & 0x80)  // is bit 7 used by directions?
    {   SysErr(1210941514); return NONE;    }

    if((num & 1) != num)
    {   SysErr(1210941522); return NONE;    }

    if(imode == JP_WRITE)
    {
        //  read & store digital direction.
        //  'button2' of analogue extension is packed into this.
        d   = curdir[num];

#ifdef REPLAY
        if(extjinf[num].button2)
            d = (enum Dirs)(d | 0x80);  // add 'button2' flag

        if(pipe[0].putb((uchar)d))  {MemErr(704941702); imode=JP_UNSET;}

        d   = (enum Dirs)(d & 0x7F);    // remove 'button2' flag
#endif

        //  read & store analogue extension
        if(extinfo)
        {
            *extinfo = extjinf[num];

#ifdef REPLAY
            if(     pipe[1].putb((uchar)(extjinf[num].analogX >> 8))
                ||  pipe[2].putb((uchar) extjinf[num].analogX     )
                ||  pipe[3].putb((uchar)(extjinf[num].analogY >> 8))
                ||  pipe[4].putb((uchar) extjinf[num].analogY     )
              )
              {MemErr(1210941500); imode=JP_UNSET;}
#endif
        }
    }
#ifdef REPLAY
    else
    if(imode == JP_READ)
    {
        //  read remembered digital direction
        //  'button2' of analogue extension is packed into this

        if(pipe[0].getb(uc))    // if get fails,

            imode = JP_UNSET;   // don't issue a SysErr.

        d   = (enum Dirs)(uc & 0x7F);   // w/o button2 flag

        //  read remembered analogue extension
        if(extinfo)
        {
            if(     pipe[1].getb(buf[0])
                ||  pipe[2].getb(buf[1])
                ||  pipe[3].getb(buf[2])
                ||  pipe[4].getb(buf[3])
              )

              imode = JP_UNSET;

            extjinf[num].analogX = ((short)buf[0] << 8) | buf[1];
            extjinf[num].analogY = ((short)buf[2] << 8) | buf[3];
            extjinf[num].button2 = (uc & 0x80) ? 1 : 0;

            *extinfo = extjinf[num];
        }
    }
#endif
    else
    if(imode != JP_UNSET)   {SysErr(704941705); d=UP; imode=JP_UNSET;}

    return  d;
}

void JoyPipe::flush(void)
{
#ifdef REPLAY
    if(imode == JP_WRITE)
    {
        pipe[0].flush();
        pipe[1].flush();
        pipe[2].flush();
        pipe[3].flush();
        pipe[4].flush();
    }
#endif
}

//|| PlayStick

PlayStick::PlayStick()
    : PlayObj(PLAYSTICK, 50 + 10 * field.taddspeed, 0)
{}  // has CPU time priority

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

    // JoyPipes do NOT use hw settings in replay mode
    if(field.replay)    return OK;

    if(field.joystick.pipe0) // should always be the case
    {
        // read ONLY CONNECTED joysticks!!!!!
        // joysticks NOT connected will halt the game for a while!!!!!

        if(field.num.humans == 1)
        {
            if(field.opt.swapsticks)
                field.joystick.pipe1->hwfetch();
            else
                field.joystick.pipe0->hwfetch();
        }
        else
        if(field.num.humans == 2)
        {
            // two humans: read both joysticks

            field.joystick.pipe0->hwfetch();
            field.joystick.pipe1->hwfetch();
        }
    }

    return  OK;
}

//|| Blinker

char Blinker::init_tab[BLINK_COLMAX][3][4] = {

    // col_alarm:
    0x8,0xf, 0xf, -1,   // red color part
    0x0,0x0, 0x0,  0,   // grn color part
    0x0,0x0, 0x0,  0,   // blu color part

    };

Blinker::Blinker()

    :   iMIN    (0),
        iMAX    (1),
        iVAL    (2),
        iDIR    (3),

        PlayObj (BLINKER, 50)
{_
    // take initial color table:

    memcpy(col, init_tab, min(sizeof(col), sizeof(init_tab)));
}

rcode Blinker::step(PlayMsg* pm)
{_
 char ci,cp,v;

    if(pm)  return INDATA;

    for(ci=0; ci<BLINK_COLMAX; ci++)    // for color index
    {
        for(cp=0; cp<3; cp++)   // for color part
        {
            v = (col[ci][cp][iVAL] += col[ci][cp][iDIR]); // change value
            if(v == col[ci][cp][iMIN] || v == col[ci][cp][iMAX])
                col[ci][cp][iDIR] *= -1;    // if border reached, chg dir
        }
    }

    // load new color values to PlayField:

    field.loadcol(col_alarm, colval(0));

    return OK;
}

//|| HTunnel

HTunnel::HTunnel()

    :   RADV_MAX    (100),
        PlayObj     (HTUNNEL, 20),
        time(field.size[1]/8*4,field.size[1]*4)
        // see ::step for reasons
{_
    hpos[0] = field.bord[0]-1;
    hpos[1] = field.bord[2];

    vpos[0] = random(field.size[1]) * 9 / 10 + field.bord[1];
    vpos[1] = vpos[0];  // will open initially

    time.open   = time.openmax;

    shut    = 0;

    PosToField();

    dir     = DOWN;
}

void HTunnel::StartOpen(void)
{
    //  start opening process

    time.open   = time.openmax; // takes so many msec
    time.close  = 0;

    shut    = 0;

    //field.stopsound();    // attention please
    //field.playsound(SND_HTOPEN, 1);
}

void HTunnel::StartClose(void)
{
    //  start closing process

    time.close  = time.closemax;

    //field.stopsound();    // attention please
    //field.playsound(SND_HTCLOSE, 1);
}

void HTunnel::Switch(void)
{_
    if(time.open || time.close) return;

    if(shut)    StartOpen();
    else        StartClose();
}

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

    if(shut)    return  OK;

    //  opening 'time' in pixel is downcounted,
    //  closing time just for masking.

    if(time.open)   time.open--;
    if(time.close)  time.close--;

    const ushort msk = 3;   // open/close speed slow down by 4

    //  unplot rim pixels

    if(dir == DOWN)
    {
        field.plot(hpos[0],vpos[0],col_fixwall);
        field.plot(hpos[1],vpos[0],col_fixwall);

        if((time.open & msk)==msk)
            vpos[1]++;
        else
        if((time.close & msk)==msk)
            vpos[0]++;
        else
        {
            vpos[0]++;
            vpos[1]++;
        }

        if(vpos[0] != vpos[1])
        {
            field.plot(hpos[0],vpos[1],col_htunnel);
            field.plot(hpos[1],vpos[1],col_htunnel);
        }

        if(vpos[1] >= field.bord[3])    dir = UP;
    }
    else
    if(dir == UP)
    {
        field.plot(hpos[0],vpos[1],col_fixwall);
        field.plot(hpos[1],vpos[1],col_fixwall);

        if((time.open & msk)==msk)
            vpos[0]--;
        else
        if((time.close & msk)==msk)
            vpos[1]--;
        else
        {
            vpos[0]--;
            vpos[1]--;
        }

        if(vpos[0] != vpos[1])
        {
            field.plot(hpos[0],vpos[0],col_htunnel);
            field.plot(hpos[1],vpos[0],col_htunnel);
        }

        if(vpos[0] <= field.bord[1])    dir = DOWN;
    }

    if(vpos[0] == vpos[1])
    {
        shut        = 1;
        time.close  = 0;
    }

    PosToField();   // copy vpos to field.htun for global access

    return OK;
}

//|| ItemGen

ItemCnt::ItemCnt(int mind, int maxd)
{_
    // playfield.size[] is NOT available here due to static instances.

    delay[0] = mind * 10;
    delay[1] = maxd * 10;
    delay[2] = -1;  // force init in ItemGen ctr
    delay[3] = -1;  // force init in ItemGen ctr
    divdelay =  1;  // by default
}

void ItemCnt::setrnddel()
{
    if(delay[1] > delay[0])
    {
        if(!field.csize(0))
        {   SysErr(204951307); delay[2] = 0; return;    }

        //  the larger the field, the less must be
        //  the delay, so more weapons are created.

        delay[2] =  800L    // standard field size to relate to
                    * (long)(random(delay[1] - delay[0]) + delay[0])
                    / (long)field.csize(0);

        if(divdelay > 1)
            delay[2]    /=  divdelay;   // speed up creation by factor

        //delay[2] =    800L * 600L // standard field size to relate to
        //          * (long)(random(delay[1] - delay[0]) + delay[0])
        //          / ((long)field.csize(0) * (long)field.csize(1));
    }
    else
        delay[2] = delay[0];    // no random
}

void ItemCnt::SetState(bool restore, ulong lotsofval)
{
    if(field.opt.lotsof & lotsofval)
        divdelay = 3;   // speed up creation by this factor
    else
        divdelay = 1;   // default: no speedup

    if(restore)
    {
        if(delay[3] == -1)
        {
            setrnddel();            // init at 1st round start of session
            delay[3] = delay[2];    // remember this
        }
        else
            delay[2] = delay[3];    // restore state of round-start
    }
    else
    {
        if(delay[2] == -1)
            setrnddel();            // init at 1st round start of session

        delay[3] = delay[2];
    }
}

ItemCnt ItemGen::bazo    (10,22); // 16 avg for one plr 32 avg for both
ItemCnt ItemGen::fthrower( 8,18); // 13, 26
ItemCnt ItemGen::lsword  ( 8,18); // 13, 26
ItemCnt ItemGen::mgun    ( 8,18); // 13, 26
ItemCnt ItemGen::pgun    ( 8,18); // 13, 26
ItemCnt ItemGen::oil     (15,30); // 22, 44
ItemCnt ItemGen::addamo  ( 5,10); //  7, 14
ItemCnt ItemGen::xcharge ( 3, 7); //  5, 10, i.e. 50 for five plus
ItemCnt ItemGen::shield  ( 3, 7); //  5, 10
ItemCnt ItemGen::mlauncher(18,36); // 27, 54
ItemCnt ItemGen::katjuscha(18,36); // 27, 54

ItemGen::ItemGen() : PlayObj(ITEMGEN, 100)  // time cycle size = 0.1 sec
{_
    //  ItemGen is created at every start of a round
    //  or at start of a round replay.
    //  If started at round, store ItemCnt's states,
    //  else restore state for proper replay:

    bool r = field.replay;

         bazo.SetState(r, CXW_BAZOOKA   );
     fthrower.SetState(r, CXW_FTHROWER  );
       lsword.SetState(r, CXW_LSWORD    );
         mgun.SetState(r, CXW_MGUN      );
         pgun.SetState(r, CXW_PGUN      );
          oil.SetState(r, CXW_OIL       );
       addamo.SetState(r, CXW_ADDAMO    );
      xcharge.SetState(r, CXW_XCHARGE   );
       shield.SetState(r, CXW_SHIELD    );
    mlauncher.SetState(r, CXW_MLAUNCHER );
    katjuscha.SetState(r, CXW_KATJUSCHA );

    //  further inits

    parktime    = -5000;    // if both bikes park at start
    //  of a round, don't mind for this period of time
    partycnt    = 0;    // no special things yet started
}

rcode ItemGen::pleazmount(Bike* bike, int mode)
{_
 XWeaponAnchor* xwa;
 XWeapon* xw;
 bool mounted=0;    // at least one xw mounted

    ifn(xwa = (XWeaponAnchor*)field.getobj(XWEAPON_ANCHOR))
        return FAILED;

    // scan through all XWeaponAnchor's
    // and mount ALL XWeapons with a matching position:

    while(1)
    {
        xw  = xwa->xweapon;

        if(xw->matchpos(bike->pos))
        {
            // get next anchor before 'xwa' is removed from list:
            xwa = (XWeaponAnchor*)field.nextobj(xwa, XWEAPON_ANCHOR);

            xw->mount(&bike->weapons, mode);
            mounted = 1;

            if(xwa) continue;
            else    break;
        }

        ifn(xwa = (XWeaponAnchor*)field.nextobj(xwa, XWEAPON_ANCHOR))
            break;
    }

    return mounted ? OK : FAILED;
}

rcode ItemGen::calcipos(cpix ipos[2])
{_
 Bike   *b0 = (Bike*)field.getobj(BIKE0), *b1 = (Bike*)field.getobj(BIKE1);
 int    m[2],r[2],rdist,rdx;
 short  termcnt;
 const  int SCALE=100;

    ipos[0] = ipos[1] = 0;

    if(!b0 || !b1)  return FAILED;  // huh?

    int dist1[3], dist2[3], mdist[3], dist3[3], dist4[3];

    short wssize  = XWA_SHAPESIZE + 2;  // plus tolerance
    short wshsize = wssize >> 1;

    //  ----- pure random position algorithm -----

    if((rand() % 100) > 90)
    {
        // in 10 % of cases, calc a totally random position
        // that just has to have a minimum distance to both bikes:

        // minimum distance for a new xweapon to both bikes
        // is halve of field height

        int mindist = min(field.size[0], field.size[1]) / 2;

        for(short i=0; i<10; i++)   // upto 10 trys
        {
            // calc a totally random point

            m[0]    = (rand() % (field.size[0] - wssize)) + field.bord[0] + wshsize;
            m[1]    = (rand() % (field.size[1] - wssize)) + field.bord[1] + wshsize;

            // now, has this point a minumum distance to both bikes?

            dist1[0] = b0->pos[0] - m[0];
            dist1[1] = b0->pos[1] - m[1];
            dist1[2] = sqrt((ulong)(dist1[0]*dist1[0]) + (ulong)(dist1[1]*dist1[1]));

            dist2[0] = b1->pos[0] - m[0];
            dist2[1] = b1->pos[1] - m[1];
            dist2[2] = sqrt((ulong)(dist2[0]*dist2[0]) + (ulong)(dist2[1]*dist2[1]));

            if(dist1[2] >= mindist && dist2[2] >= mindist)
            {
                // yes, take this position:

                ipos[0] = m[0];
                ipos[1] = m[1];

                if(field.xwtfield.isused(ipos[0],ipos[1]))
                    return FAILED;

                return  OK;
            }
        }

        return  FAILED;
    }

    //  ----- random middle-between-bikes algorithm -----

    dist1[0] = b0->pos[0] - b1->pos[0];
    dist1[1] = b0->pos[1] - b1->pos[1];
    dist1[2] = sqrt((ulong)(dist1[0]*dist1[0]) + (ulong)(dist1[1]*dist1[1]));

    dist2[0] = field.size[0];
    dist2[1] = field.size[1];
    dist2[2] = sqrt((ulong)(dist2[0]*dist2[0]) + (ulong)(dist2[1]*dist2[1]));

    // middle between bikes:

    m[0] = dist1[0] / 2 + b1->pos[0];
    m[1] = dist1[1] / 2 + b1->pos[1];

    // if middle is at a good position, try calculating
    // a good random position

    if(     m[0] > field.bord[0] + 20
        &&  m[0] < field.bord[2] - 20
        &&  m[1] > field.bord[1] + 20
        &&  m[1] < field.bord[3] - 20   )
    {
      termcnt   = 20+1;

      do
      {
        // calc scaled random distance value

        do
        {
            rdist   = ((int)random(dist2[2]) * SCALE)

                        - (dist2[2] / 2) * SCALE;
        }
        while(ABS(rdist) < field.size[0] / 5 * SCALE);

        if(!dist1[0])
        {
            r[0]    = m[0] + rdist / SCALE;
            r[1]    = m[1];
        }
        else
        if(!dist1[1])
        {
            r[0]    = m[0];
            r[1]    = m[1] + rdist / SCALE;
        }
        else
        {
            rdx     = rdist * -1 * dist1[1] / dist1[0];

            r[0]    = m[0] + rdx   / SCALE;
            r[1]    = m[1] + rdist / SCALE;
        }

        // calc distance of insert position 'r' to 'm':

        mdist[0]    = r[0] - m[0];
        mdist[1]    = r[1] - m[1];
        mdist[2]    = sqrt(     (ulong)mdist[0] * mdist[0]
                             +  (ulong)mdist[1] * mdist[1] );
      }
      while(    (       r[0] < field.bord[0] + wshsize
                    ||  r[0] > field.bord[2] - wshsize
                    ||  r[1] < field.bord[1] + wshsize
                    ||  r[1] > field.bord[3] - wshsize

                    ||  mdist[2] < 10   // let bikes don't shoot it
                )

            &&  --termcnt   // cancel after 20 trys!
           );

#ifdef  AMIGA
      volatile uword* hwcol=(uword*)0xdff180;
      if(!termcnt && field.opt.debug)   *hwcol=0x800;   // show red blink
#endif

    }
    else
      termcnt = 0;

    if(!termcnt)    return FAILED;

    ipos[0] = r[0];
    ipos[1] = r[1];

    if(field.xwtfield.isused(ipos[0],ipos[1]))
        return FAILED;

    return OK;
}

rcode ItemGen::step(PlayMsg* pm)
{_
 cpix ipos[2];

    if(pm)  return INDATA;

    // count down item creation delay counts.
    // as soon as one of them underflows, create item.

#ifndef RDEMO

    // bazookas are pointless against terminator,
    // who can drive thru dust. therefore don't create them
    // if less than 2 human players.
    if(field.num.humans >= 2 && !(bazo.delay[2]--))
    {
        bazo.setrnddel();   // set new random delay
        if(field.opt.cxw & CXW_BAZOOKA)
            ifn(calcipos(ipos))
                new Bazooka(ipos);
    }

#endif  // !defined(RDEMO)

    ifn(xcharge.delay[2]--)
    {
        xcharge.setrnddel();
        if(field.opt.cxw & CXW_XCHARGE)
            ifn(calcipos(ipos))
                new XCharge(ipos);
    }

#ifndef RDEMO

    ifn(addamo.delay[2]--)
    {
        addamo.setrnddel();
        if(field.opt.cxw & CXW_ADDAMO)
            ifn(calcipos(ipos))
                new AddAmmo(ipos);
    }

    ifn(fthrower.delay[2]--)
    {
        fthrower.setrnddel();
        if(field.opt.cxw & CXW_FTHROWER)
            ifn(calcipos(ipos))
                new FlameThrower(ipos);
    }

#endif  // !defined(RDEMO)

    ifn(lsword.delay[2]--)
    {
        lsword.setrnddel();
        if(field.opt.cxw & CXW_LSWORD)
            ifn(calcipos(ipos))
                new LightSword(ipos);
    }

    ifn(mgun.delay[2]--)
    {
        mgun.setrnddel();
        if(field.opt.cxw & CXW_MGUN)
            ifn(calcipos(ipos))
                new MGun(ipos);
    }

#ifndef RDEMO

    ifn(pgun.delay[2]--)
    {
        pgun.setrnddel();
        if(field.opt.cxw & CXW_PGUN)
            ifn(calcipos(ipos))
                new PGun(ipos);
    }

    ifn(shield.delay[2]--)
    {
        shield.setrnddel();
        if(field.opt.cxw & CXW_SHIELD)
            ifn(calcipos(ipos))
                new AddShield(ipos);
    }

    ifn(oil.delay[2]--)
    {
        oil.setrnddel();

        OilDriver *odrv = (OilDriver*)field.getobj(OIL_DRIVER);

        //  count oil units that might be needed by existing squirts

        int noil = 0;

        for(    OilSquirt *sq = (OilSquirt *)field.getobj(OIL_SQUIRT);
                sq;
                sq = (OilSquirt *)field.nextobj(sq, OIL_SQUIRT)
           )

           noil += 1600;    // approx. per squirt

        if(odrv && odrv->AvailDrops() >= max(noil, 1600))
            if(field.opt.cxw & CXW_OIL)
                ifn(calcipos(ipos))
                    new OilSquirt(ipos);
    }

    ifn(mlauncher.delay[2]--)
    {
        mlauncher.setrnddel();
        if(field.opt.cxw & CXW_MLAUNCHER)
            ifn(calcipos(ipos))
                new MLauncher(ipos);
    }

#ifndef NDEMO
    ifn(katjuscha.delay[2]--)
    {
        katjuscha.setrnddel();
        if(field.opt.cxw & CXW_KATJUSCHA)
            ifn(calcipos(ipos))
                new Katjuscha(ipos);
    }
#endif

#endif  // !defined(RDEMO)

    return OK;
}

//|| BikDrvCode
BikeDriver::BikeDriver(Bike* newbike)
{_
    bike    = newbike;  // mount bike
    itype   = 0;        // type of driver unknown by default
}

//|| Joy11Code

Joy11Driver::Joy11Driver(class Bike* bike)
    : BikeDriver(bike), PlayObj(JOY11DRIVER, 10)
{_
    ifn(bike)   {SysErr(704941826); return;}

    //  set pointer to this JoyDriver's pipe:

    uchar   num = bike->number & 1;

    if(field.opt.swapsticks)    num ^= 1;

    if(!num)
        joy = field.joystick.pipe0;
    else
        joy = field.joystick.pipe1;

#ifdef  WIN16
    bike->ana.log   = 1;    // bike driven by analogue joystick
#endif

    //  if in replay mode, use stored joydata instead of physical stick

    joy->setmode(field.replay ? joy->JP_READ : joy->JP_WRITE);
}

Joy11Driver::~Joy11Driver() {_}

rcode Joy11Driver::step(PlayMsg* pm)
{_
    if(pm)  return INDATA;  // no msg support

#ifndef WIN16
    // Multi-Click detection (for specfunc 1):

    bool b = (joy->read(&extjinf) & BUTTON) ? 1 : 0;

    // the sideeffect of multi-clicks is that the bike speeds up;
    // to avoid this effect, remember bike speed at start
    // of a (potential) multiclick and reset speed later.
    // the start of a multiclick is determined by
    // - a pressed button
    // - no time yet counted for the pressed button
    // - no 'button down' yet counted, i.e. mc.clicks[1] == 0

    if(b && !mc.time[1] && !mc.clicks[1])   mc.bikespeed = bike->speed;

    // was button in other state for a valid period?

    if(mc.time[1-b] >= mc.CT_MIN)
    {
        if(mc.time[1-b] <= mc.CT_MAX)
            mc.clicks[1-b]++;   // valid time: cnt period
        else
            mc.reset(); // too long: reset all
    }

    mc.time[1-b] =  0;          // reset time of non-active state
    mc.time[b]  += cyclesize;   // and count time of active state

    if(mc.time[b] > mc.CT_MAX)  mc.reset(); // too long in this state

    if(mc.clicks[0] == mc.clicks[1])    // pres/rel same often?
        mc.clicks[2] = mc.clicks[0];    // yea: set overall cnt
    else
        mc.clicks[2] = 0;   // nya: no overall clicks yet

    // ... now, in mc.clicks[2] is the number of current clicks.
#endif

    return OK;
}

// determine if a 'special function' was selected using a simple
// digital joystick...

int Joy11Driver::specfunc()
{
    ifn(bike)   {SysErr(311941038); return 0;}

    if(field.opt.swapbuttons[bike->number & 1])
    {
        return (joy->read() & BUTTON) ? 1 : 0;
    }
    else
        if(extjinf.button2) return 1;   // second button pressed

#ifndef WIN16
    if(mc.clicks[2] == 2)   // if double-clicked
    {
        // the doubleclick sped up the bike, so restore old speed now
        if(mc.bikespeed)    bike->speed = mc.bikespeed;

        mc.reset();

        return 1;
    }
#endif

    return 0;
}

bool Joy11Driver::accelerate()
{
    ifn(bike)   {SysErr(311941039); return 0;}

    if(field.opt.swapbuttons[bike->number & 1])
    {
        if(extjinf.button2) return 1;   // second button pressed
    }
    else
        return (joy->read() & BUTTON) ? 1 : 0;

    return 0;
}

enum Dirs Joy11Driver::direction(short *vdir)
{
 enum Dirs ddirs;   // digital directions

    ddirs   = (enum Dirs)(joy->read(&extjinf) & DIR_MASK);

    if(vdir)
    {
        vdir[0] = extjinf.analogX;
        vdir[1] = extjinf.analogY;
    }

    return  ddirs;
}

//|| TempTrack
rcode   TempTrack::plot(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 = pix[itl][0], py = pix[itl][1];

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

    // if track is longer than maxlength / 5,
    // plot anything after first 5th in another color

    itl = (ihead - (TRACK_PIX / 5)) & TRACK_IMASK;

    px  = pix[itl][0], py = pix[itl][1];

    if(px != -1)
    {
        if(field.readpix(px,py) == color)

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

    // add & plot new frontpix

    ihead   = (ihead + 1) & TRACK_IMASK;

    pix[ihead][0]   = x;
    pix[ihead][1]   = y;
    pix[ihead][2]   = field.readpix(x,y);

    field.plot(x,y, color);

    return  OK;
}

bool isBikeObstacleColor(pcol col,void *pBike)
{
    Bike *bike = (Bike*)pBike;
    if(!bike) {SysErr(1911951412); return 0;}

    switch(col)
    {
        case col_flame:
        case col_fire:
        case col_fixwall:
        case col_dust:
        case col_cmimpact:
        case col_mgimpact:
        case col_waveexpl:
        case col_xweapon:
            return 1;

        case col_bikevcur:
        case col_tlaser0:
        case col_tlaser1:
        case col_lsword0:
        case col_lsword1:
        case col_missile:
            // col  = col_backgrnd;
            break;

        case col_oil:
        //  bike->status |= SHAPE_ON_OIL;
            break;

        case col_nitro:
            // apos[0] = ax; apos[1] = ay;
            // CrtNitroExpl(apos);
            // col  = col_backgrnd;
            break;

        case col_xwswitch:
            // if no XWCycleRequest is pending on this bike,
            if(!(bike->comstate & Bike::CS_XWCYCLEREQUEST))
                return 1; // let it set one
            break;

    }   // endswitch(col)

    return 0;
}

//|| BikeCode
Bike::Bike(uchar num)
    :   MovingObj(  (num<2) ? (num ? BIKE1 : BIKE0) : HI_BIKE,
                    10, 0   ),
        weapons(this)
{_
    dead = 1;

    speedmin = convfi( 20); // if shape, might be changed below
    speedmax = convfi(120); // don't change this, it's all optimized to it

    parkallow   = 1;    // parking allowed by default
    // RobDriver will set this flag to 0

    number  = num;

    if(number==0)
    {
        pos[0]  = field.size[0] * 1 / 3;
        pos[1]  = field.size[1] / 2;
    }
    else
    if(number==1)
    {
        pos[0]  = field.size[0] * 2 / 3;
        pos[1]  = field.size[1] / 2;
    }
    else
    {
        // additional robot players:

        int adrobs  = max(field.num.players - 2, 1);
        int spread  = (field.size[0] / 2) / adrobs;
        int xarea   = adrobs * spread;
        int rsx     = field.bord[0] + (field.size[0]/2) - xarea/2 + spread/2;

        pos[0]  = (cpix)(rsx + (number - 2) * spread);
        pos[1]  = field.bord[3] * 3 / 4;
    }

    SetMov(UP, speedmin);

    lsdir   = NONE;     // no 'last step direction' set

    shields = 5;
    bul.amo = BULPACK_SIZE;
    bul.xchrgcnt = 0;   // no bullets with extra charge

    // Justice adjustments for weaker player?

    if(justice.active && (num == 0 || num == 1))
    {
        shields += justice.support[num];    // more shields for weaker
    }

    statustxt[0] = 0;
    statustime   = 0;

    shootdelay  = 800;

    ana.log     = 0;    // this bike drives digital by default
    ana.vdir[0] = 0;        // the case it'll drive analogue,
    ana.vdir[1] = SHRT_MIN; // setup default vector
    joyVec[0]   = 0;        // same with
    joyVec[1]   = SHRT_MIN; // joyVec

    backsndcnt  = 0;    // init backgrnd sound refresh counter
    hoovering   = 0;    // not hoovering by default
    joyElong    = 0;    // joystick elongation MEDIUM by default
    redrawcnt   = 0;    // needed when parking with shape
    ihitby      = -1;   // not yet hit by something
    crashdelay  = 0;
    OilOnTyres  = 0;
    OilDispCnt  = 0;

    delay.steer = 0;    // no additional delays
    delay.accel = 0;    // by default

    memset(&allow, 0xFF, sizeof(allow));    // allow all

    // max (scaled) targeting distance is halve of field's maxsize
    tdist.vmax  = (long)field.sizemax << (10 - 1);
    tdist.vcur  = 0;
    tdist.locked= 0;

    tdir.angle  = 0;
    tdir.locked = 0;

    comstate    = 0;    // reset common status bits
    xwcdelay    = 0;
    shdloss     = 0;
    shdlossdly  = 0;
    smallexpdelay = 0;

    pcol scol;
    if(!number) scol = col_shape0;
    else if(number == 1) scol = col_shape1;
    else scol = col_track_hi;

    if(createshape(SCL_BIKE, scol))
        return;

    if(number == 1) shape().setface(1); // red bike face
    shape().moveto(pos[0],pos[1]);
    shape().setObstacleFunction(isBikeObstacleColor, this);

    sfaceval    = 0;
    sfvaldir    = 1;

    dead    = 0;
}

bool Bike::matchpos(cpix xpos[2])
{_
    // shape cares about off-area coord values
    return shape().ispixset(xpos[0]-pos[0],xpos[1]-pos[1]);
}

void Bike::setXWCycleRequest(void)
{
    if(!xwcdelay)   // if not still downcounting
    {
         comstate |= CS_XWCYCLEREQUEST;
         xwcdelay  = 500;   // so many msec's delay 'til next switching
    }
}

rcode Bike::cycleXWeapons(void)
{_
    XWeapon *pFront = weapons.first(), *pBack;

    if(pFront && pFront->next())
    {
        // can really cycle.
        weapons.remove(pFront);

        short   dcnt = 3;

        if(!weapons.first())    {SysErr(2503951559); return INTERNAL;}

        for(    pBack = weapons.first();
                pBack->next() && dcnt--;
                pBack = (XWeapon*)pBack->next()
           );

        weapons.insert(pBack, pFront);  // insert pFront as new 5th

        tell("weapons cycled");

        field.playsound(SND_BKWCYCLE);

        return OK;
    }
    else
    {
        // can't cycle.

        tell("can't do that");

        return FAILED;
    }
}

//  if several weapons set bike's delay parallel,
//  take always the maximum of those delays,
//  unless it's re-set by zero value.

void Bike::SetSteerDelay(int d)
{
    if(d)
        delay.steer = max(delay.steer, d);
    else
        delay.steer = 0;
}

void Bike::SetAccelDelay(int d)
{
    if(d)
        delay.accel = max(delay.accel, d);
    else
        delay.accel = 0;
}

bool Bike::sethit(cpix xpos[2], pcol col)
{_
    // does 'pos' really belong to us?

   if(matchpos(xpos))
   {
        ihitby  = col;  // processed in ::step

        return  1;
   }
   else
        return  0;
}

// calc safe launching position, e.g. for a bazooka;
// that is, if bike uses a shape, pos'n must be outside shape.
cpix *Bike::launchpos(short distfact)
{_
 static cpix lpos[2];

    shape().frontpos(lpos[0],lpos[1],1+distfact);

    ifn(field.inbord(lpos[0], lpos[1]))
    {
        // the launch pos'n would be illegal, so use bike pos'n
        lpos[0] = pos[0];
        lpos[1] = pos[1];
    }

    return lpos;
}

void Bike::tlog_add(short newdir)
{_
    // make space for new entry:
    for(int i = BIKE_TLSIZE-1; i >= 1; i--)
        tlog[i] = tlog[i-1];

    // enter entry:
    tlog[0].xdir    = newdir;
    tlog[0].pos[0]  = pos[0];
    tlog[0].pos[1]  = pos[1];
}

rcode Bike::advance_step(PlayMsg *pm)
{_
 bool crash = 0, hitborder = 0;
 pcol col;
 short idir = (short)field.convdir(dir);
 int res;
 enum Dirs olddir = lsdir;  // to determine if dir changed

#ifdef  WIN16
    if(driver && driver->type() == ROBOT_DRIVER)
    {
        // driven by robot: we need EXACT driving pixel by pixel,
        // so don't use emulated analogue direction but step
        // in digital mode.
        ana.log = 0;    // pretend we're digital
        res = advance(pm ? 0 : 1);  // if FURTHERSTEP, don't add up reladv
        ana.log = 1;    // switch back to analogue
    }
    else
#endif
        res = advance(pm ? 0 : 1);  // if FURTHERSTEP, don't add up reladv

    if(res==NOTHING)
        if(speed)
            return OK;  // driving, but not yet a full pixel

    // stepped a pixel OR parking: in both cases
    // at least bike direction has to be updated

    if((res & PIXSTEP) && !hoovering)
    {
        // animate walking shape:

#define SF_SCALE 6
#define SF_MAX   (shape().faces() * SF_SCALE)

        if(shape().faces() >= 10)
        {
            sfaceval += sfvaldir;

            if(sfaceval < 0)
            {  sfaceval = SF_SCALE; sfvaldir = 1;   }

            if(sfaceval >= SF_MAX)
            {  sfaceval = SF_MAX - 1 - SF_SCALE; sfvaldir = -1; }

            shape().setface(sfaceval / SF_SCALE);
        }
    }

    // advanced a single playfield pixel position.
    lsdir = dir;    // remember last step's dir

    // reached left or right border?

    if(res & (LBORDER|RBORDER))
    {
        if(     !(dir & (UP|DOWN))
            ||  (res & (TBORDER|BBORDER))   )

            hitborder = 1;
    }

    if(res & (TBORDER|BBORDER))
    {
        if(     !(dir & (LEFT|RIGHT))
            ||  (res & (LBORDER|RBORDER))   )

            hitborder = 1;
    }

    if(hitborder)
    {
        // crashed in 90 degrees onto field border
        speed   = convfi(0);
        field.playsound(SND_BKHBORD);
    }

    if(res & (PASSED_HTUNNEL | PASSED_VTUNNEL))
    {
        // play tunnel-pass sounds

        if(res & PASSED_HTUNNEL)
        {
            if(field.soundexists(SND_HTPASSL))
                field.stopsound();

            // PASSL is the 'arriving' sound,
            // so play it on the side where
            // bike arrived:

            ulong ropt = SND_PLAYRIGHT;
            ulong lopt = SND_PLAYLEFT;

            if(dir & LEFT)
            {
                // arriving at right side:

                ropt = SND_PLAYLEFT;
                lopt = SND_PLAYRIGHT;
            }

            field.playsound(SND_HTPASSR, 0, 1, ropt);
            field.playsound(SND_HTPASSL, 0, 1, lopt);
        }

        if(res & PASSED_VTUNNEL)
            field.playsound(SND_VTPASS);
    }

    // driving through an oil puddle?

    if(0) // OnOil()
    {
        // yes: disable steering and breaks
        allow.steering  = 0;
        allow.breaks    = 0;

        // increment oil load on tyres with every step
        if((res & PIXSTEP) && OilOnTyres < field.size[0])
                OilOnTyres++;
    }
    else
    {
#ifndef RDEMO
        // allow steering only if no oil is left on tyres

        if(OilOnTyres)
        {
          if(res & PIXSTEP)
          {
            OilOnTyres--;

            ifn(OilDispCnt++ & 3)   // just every 4th step
            {
                OilDriver *odrv = (OilDriver*)field.getobj(OIL_DRIVER);

                cpix *tpos;

                if(odrv && odrv->AvailDrops())
                {
                    // bike leaves an oil track

                    tpos = launchpos(-(DROP_RADIUS+1));

                    odrv->CreateDrop(*tpos, *(tpos+1));
                }
                else
                    OilOnTyres = 0; // no more oil technical available
            }
          }
        }
        else
#endif
        {
            allow.steering  = 1;
            allow.breaks    = 1;
        }
    }

    // pixel obstacle checks

    cpix    obstx,obsty;
    pcol    shapecol = col_shape0 + (number & 1);
    ushort  donesmallexpl = 0;  // created a small explosion for optical reasons
    rcode   isobst;
    bool    isterminator = (driver->type() == TERM_DRIVER);

    // if no logical obstacles (i.e. field border)

    if(!crash)
    {
        // obstacle check

        do
        {
            if(ihitby != -1)
                isobst  = SPECIAL;  // externally set hit
            else
                isobst = shape().prescan(obstx,obsty);

            if(isobst)
            {
                // prescan() found an obstacle (pixel). what is it?
                if(ihitby != -1)
                {
                    col     = ihitby;
                    ihitby  = -1;   // processed
                }
                else
                    col = field.readpix(obstx, obsty);

                if(col == col_xweapon)
                {
                    // let's collect an extra weapon!

                    ItemGen*    igen = (ItemGen*)field.getobj(ITEMGEN);
                    if(igen)    igen->pleazmount(this);
                }
                else
                if(col == col_xwswitch)
                {
                    // we're driving over a weapon cycle switch.
                    // set the cycling request for this bike
                    // and inform XWCycler that the switch was probably
                    // wiped out in the framebuffer by our shape,
                    // therefore it must be refreshed more intensely.
                    XWCycler *xwc = (XWCycler *)field.getobj(XWCYCLER);
                    if (!xwc) { SysErr(1712951432); return INTERNAL; }
                    xwc->touchswitch(obstx,obsty);
                    setXWCycleRequest();
                }
                else
                {

        //  -----

        if(isterminator)
        {
            bool dontmind = 1;

            switch(col)
            {
                case col_flame      :
                case col_cmimpact   :
                case col_mgimpact   :
                case col_waveexpl   :
                case col_bullet     :   // set by lsword hit!
                    dontmind = 0;
                    break;

                case col_fixwall    :
                case col_dust       :
                case col_fire       :
                    if(hoovering)
                        dontmind = 0;
                    break;
            }

            if(dontmind)        // a quick hack
                crashdelay++;   // to avoid potential shield loss
        }

        // collision: serious?

        if(shields || crashdelay)
        {
            if(!crashdelay)
            {
                if(driver->type() != TERM_DRIVER)
                    shields--;  // no, just loosing a shield
                else
                // terminator: only some colors hit him
                switch(col)
                {
                    case col_flame      :
                    case col_cmimpact   :
                    case col_mgimpact   :
                    case col_waveexpl   :
                    case col_bullet     :   // set by lsword hit!
                        shields--;          // immediate hit
                        break;

                    case col_fixwall    :
                    case col_dust       :
                    case col_fire       :
                        if(hoovering)
                            shields--;      // bombed thru stuff by pump gun
                        break;
                }
            }

            // unplot obstacle pixel
            field.plot(obstx, obsty, col_backgrnd);

            // should wall etc. be destroyed a bit?
            switch(col)
            {
                case col_fire:
                case col_fixwall:
                case col_dust:
                    if(     obstx > field.bord[0]+1
                        &&  obstx < field.bord[2]-2
                        &&  obsty > field.bord[1]+1
                        &&  obsty < field.bord[3]-2)
                    {
                        field.setLevel(1);
                        ((PixelDisplay*)&field)->rectfill(obstx-2,obsty-2,obstx+2,obsty+2,col_blue6);
                        field.setLevel(0);
                        field.rectfill(obstx-2,obsty-2,obstx+2,obsty+2,col_backgrnd);
                    }
                    break;
            }

            if(!donesmallexpl++ && !smallexpdelay)
            {
                new SmallExpl(pos);     // just for optical reasons
                smallexpdelay = 100;    // max. 1 SmallExpl per 100 msec
            }

            if(!crashdelay)
            {
                ushort r = SNDRAND() % 100;

                switch(col)
                {
                  case col_fixwall:
                    if(     hoovering
                        &&  field.num.humans
                        &&  r < 5
                        &&  field.soundexists(SND_BKHWOIL)  )
                    {
                        field.stopsound();
                        field.playsound(SND_BKHWOIL, 1);
                    }
                    else
                        field.playsound(SND_BKHWALL);
                    break;

                  case col_flame:
                    if(     r < 8
                        &&  field.soundexists(SND_BKHFLAME) )
                    {
                        field.stopsound();
                        field.playsound(SND_BKHFLAME, 1);
                    }
                    else
                        field.playsound(SND_BKHOBST);
                    break;

                  case col_cmimpact:
                    break;  // the missile explosion already made a sound

                  default:
                    field.playsound(SND_BKHOBST);
                }

                if(shields)
                {
                    // set a minimum delay 'til next allowance
                    // of a collision to avoid 'fatal hits'
                    // that wipe out all shields immediately:
                    crashdelay  =  250; // so many msec's secure

                    // do we count this lost shield?
                    // if crashed thru fixwall, count only if hoovering,
                    // i.e. crashing is due to pump gun hit.
                    if(col != col_fixwall || hoovering)
                    {
                        shdloss++;          // no. of shd losses within period
                        shdlossdly  = 1500; // start 1.5 sec period
                    }

                    if(!isterminator)
                      switch(shdloss)
                      {
                        //             123456789012345678
                        case 0:  break;
                        case 1:  if(statustime <= 0) // no other msg displayed
                                    tell("lost a shield");
                                 break;
                        case 2:  tell("lost two shields");      break;
                        case 3:  tell("LOST THREE SHIELDS");    break;
                        default: tell("!!! AAAARGHHHH !!!");    break;
                      }
                    else
                      if(statustime <= 0) // no other msg displayed
                        tell("who cares");
                }
                else
                {
                    if(isterminator)
                        tell("wrong...");
                    else
                        tell("OUT OF SHIELDS", 10000);

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

                    // with next hit, bike will crash,
                    // so give it a last extra support:
                    crashdelay  = 1000;
                }

                // weak player support:
                // e.g. if weak one has 10 extra shields,
                //      his crashdelay increases to 450.
                if(justice.active && (number < 2))
                    crashdelay  += justice.support[number & 1] * 20;
            }
        }
        else
        {
            // yea, all shields are gone.

            crash   = 1;

            break;  // handle below
        }

        //  -----

                }   // endelse xweapon pixel

            // just the case obstacle pixel wasn't correctly removed,
            // avoid potential endless loop by clearing it:

            field.plot(obstx, obsty, col_backgrnd);

            }   // endif isobst

        }
        while(isobst);  // endwhile shape obstacles

    }   // endif no logical obstacles

    if(crash)
    {
        // fatal crash.

        // if this is player1 or 2, game should stop;
        // otherwise, only an additional robot driver crashed

        if(number < 2)
        {
            PlayMsg* pm = new PlayMsg;
            if(!pm) {   MemErr(2901942052); return MEMORY;  }

            pm->type    = PMT_TERMCRASH;
            pm->src     = this;
            pm->delay   = 5000; // wait so long before terminating

            field   +=  pm;
        }

        // Explosion: if this is a main player (BIKE0 or BIKE1),
        //            perform heavy explosion, otherwise smaller

        if(number < 2)
        {
            new BigExpl(pos);

            // stop all (potentially playing) driving sounds

            field.stopsound(SND_BKDRIVE1);
            field.stopsound(SND_BKDRIVE2);

            field.playsound(SND_BKEXPL, 1); // in stereo
        }
        else
        {
            StringLib sl;

            // additional (robot) bike crashed:

            new SmallExpl(pos);

            field.tell(sl.printf("Roboter %d crashed", number-2+1));
        }

        dead    = 1;    // will avoid further steps of this object

        tell("CRASHED", 10000);

        return  OK;

    }   // endif crash

    if(    (res & PIXSTEP)    // if stepped a pixel
       || !(++redrawcnt & 15) // or 16 cycles passed
      )
    {
       shape().unplot();    // at old position
       shape().plot();      // at new position
    }

    // if(ana.log)
    //  plot a 'targeting cursor' to see in which
    //  exact direction we're looking:
    //  RedrawVCursor();

    // if there's still a big up-added 'reladv',
    // request another time slice in this cycle

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

rcode Bike::RedrawVCursor()
{_
    // unplot old cursor, if any, and if it
    // wasn't plotted over by something other:

    if(vcur.pos[0] != -1)
        //  can't unplot() directly, must user overlayed
        //  plot of PlayField. therefore:
        field.plot( vcur.pos[0], vcur.pos[1],
                    field.readpix(vcur.pos[0], vcur.pos[1]) );

        //  if(field.readpix(vcur.pos[0], vcur.pos[1]) == col_bikevcur)

    // plot new cursor

    int rad = TargetRange(field.sizemax >> 3) >> 1;

    cpix nx = (cpix)(pos[0] + convif(ftrig.sin(ana.angle) * rad));
    cpix ny = (cpix)(pos[1] - convif(ftrig.cos(ana.angle) * rad));

    if(field.inbord(nx,ny)) // if new coords valid
    {
        vcur.pos[0] = nx;
        vcur.pos[1] = ny;

        switch(vcur.backcol = field.readpix(nx,ny))
        {
            case col_shape0:
            case col_shape1:
            case col_bikevcur:
            case col_tlaser0:
            case col_tlaser1:
            case col_xweapon:
            case col_missile:

                vcur.pos[0] = -1;   // no cursor

                break;

            default:

                field.plotby(nx,ny, col_bikevcur);
        }
    }
    else
        vcur.pos[0] = -1;   // no cursor

    return OK;
}

//|| Bike.steer
rcode Bike::steer()
{_
 enum Dirs dmask = NONE, allowed = NONE;
 bool dacl=0,dshoot=0,dselect=0;
 intf accel;
 bool launched=0;
 long  vdist[2];    // vector distance, used in soft stepping
 ushort drvtype=0;  // driver type
 short oldvx,oldvy;

    if(driver)
    {
        drvtype = driver->type();

        dmask   = driver->direction(joyVec);

        if(drvtype == ROBOT_DRIVER)
        {
            dacl    = driver->accelerate();
            dshoot  = driver->specfunc();
        }
        else
        {
            dshoot  = driver->accelerate();
            dselect = driver->specfunc();
        }
    }

    // if any direction pressed, make it new direction:
    if(dmask)
    {
        if(ana.log)
        {
          oldvx = ana.vdir[0];
          oldvy = ana.vdir[1];

          // for the case bike runs analogue, softly step
          // into the newly pressed direction. This means
          // that steering is delayed. Furthermore, the more
          // weapons are loaded, the more weight and thus
          // inertia has the bike
          // NOTE: digital direction must wait 'til vector direction
          //       has stepped far enough before it changes.
          //       this is achieved by creating an 'allowed' mask.

          if(drvtype == ROBOT_DRIVER && allow.steering)
          {
            // take emulated analogue direction 1:1

            ana.vdir[0] = joyVec[0];
            ana.vdir[1] = joyVec[1];
          }
          else
          {
            long inertia;

            // joystick (human) driver: soft steering

            if(!allow.steering)
                inertia = 300UL;    // steering 'disabled'
            else
            if(speed)
                // driving: take weapon's weight for inertia
                inertia = 10 * (128 + delay.steer) >> 7;
            else
                // parking: high inertia to aim very smoothly
                inertia = 80;

            vdist[0]     = (long)joyVec[0] - (long)ana.vdir[0];
            ana.vdir[0] += (short)(vdist[0] / inertia); // step 1/i parts

            vdist[1]     = (long)joyVec[1] - (long)ana.vdir[1];
            ana.vdir[1] += (short)(vdist[1] / inertia); // step 1/i parts

            // determine elongation of analogue joystick in driving direction.
            // unfortunately, PC analogue joysticks are anti-precise.
            // We thus determine just three levels of elongation:
            // MIN (0), MEDIUM (1), MAX (2).

            // map direction & pressure -> elongation:

            static uchar map_dpe[4][4] =
            {       // x y xp yp
                0,  // 0 0  0 0
                0,  // 0 0  1 0
                0,  // 0 0  0 1
                0,  // 0 0  1 1

                0,  // 1 0  0 0
                2,  // 1 0  1 0
                0,  // 1 0  0 1
                2,  // 1 0  1 1

                0,  // 0 1  0 0
                0,  // 0 1  1 0
                2,  // 0 1  0 1
                2,  // 0 1  1 1

                0,  // 1 1  0 0
                2,  // 1 1  1 0
                2,  // 1 1  0 1
                2   // 1 1  1 1
            };

            ushort absx = abs(joyVec[0]>>2);
            ushort absy = abs(joyVec[1]>>2);

            uchar  d0 = 0, d1 = 0, p0 = 0, p1 = 0;

            if(absx > (SHRT_MAX>>2) >> 1)   d0 = 1;
            if(absy > (SHRT_MAX>>2) >> 1)   d1 = 1;

            if(absx > (SHRT_MAX>>2) * 3 / 4)    p0 = 1;
            if(absy > (SHRT_MAX>>2) * 3 / 4)    p1 = 1;

            joyElong = map_dpe[(d1<<1)+d0][(p1<<1)+p0];

            dacl    = (joyElong == 2);
          }

          // now, where is dmask allowed to point to?

          if(ana.vdir[0] < 0)   allowed = (enum Dirs)(allowed | LEFT);
          if(ana.vdir[0] > 0)   allowed = (enum Dirs)(allowed | RIGHT);

          if(ana.vdir[1] < 0)   allowed = (enum Dirs)(allowed | UP);
          if(ana.vdir[1] > 0)   allowed = (enum Dirs)(allowed | DOWN);

          dmask = (enum Dirs)(dmask & allowed);

          // now, if vdir changed, calculate new angle in
          // binary degrees (0..ftrig.degmsk()) from it:

          if(oldvx != ana.vdir[0] || oldvy != ana.vdir[1])
              ana.CalcAngle();  // calc ana.angle

          shape().turnto(ana.angle);

        }   // endif ana.log

        if(dmask)
        {
            // so, is this really a new direction?

            if(dir != dmask)
                // yes: enter change in tracklog
                tlog_add(field.convdir(dmask));

            dir = dmask;

        }   // endif dmask

    }   // endif dmask (2nd)

    // set bike speed

    ifn(accel = convfi( (int)(speedmax / max(speed,convfi(1))) ))
        accel = convfi(1);

    if(allow.breaks)
        accel   /=  8 * (128 + delay.accel) >> 7;   // normal driving
    else
        accel   /= 80;  // sliding through oil

    if(dacl)
    {
        if(!speed && parkallow)
        {
            // from 0 to speedmin in 0.0 seconds!
            speed    = speedmin;

            field.playsound(SND_BKSTART);
        }
        else
        if(speed < speedmax)
            speed   += accel;

        // play a driving sound. actually, it's
        // an endless sound that's overridden by
        // other sounds and refreshed from time to time.

        if(number < 2)
        {
            ulong playopt = number ? SND_PLAYRIGHT : SND_PLAYLEFT;

            if(!allow.steering)
            {
                if(!backsndcnt)
                {
                    backsndcnt  = 2000; // msec

                    field.playsound(SND_BKTYRES, 0, 1, playopt);
                }
            }
            else
            if(!backsndcnt)
            {
                backsndcnt  = 1000;

                if(speed < speedmax)
                    field.playsound(SND_BKDRIVE1, 0, 1000, playopt);
                else
                    field.playsound(SND_BKDRIVE2, 0, 1000, playopt);
            }
        }
    }
    else
    {
        // no acceleration: slow down

        if(speed == speedmin && parkallow)
        {
            // stop bike
            speed   = 0;

            field.playsound(SND_BKSTOP);
        }
        else
        if(speed > speedmin)
            if((speed -= accel) < speedmin)
                speed = speedmin;
    }

    if(!shootdelay && (dshoot | dselect))
    {
        XWeapon* xw, *old;
        enum ObjCodes ocode;
        short oxcharg;

        if((xw = weapons.first()) && dselect)

            xw = (XWeapon*)xw->next();

        if(xw)
        {
            old     = xw;
            ocode   = xw->idcode();  // remember 'case 'old' deletes itself
            oxcharg = xw->xcharge(); // remember charging

            xw->launch();   // will detach itself from box
            launched = 1;

            // immediately re-fetch xw at the same position (if any):

            if((xw = weapons.first()) && dselect)

                xw = (XWeapon*)xw->next();

            // xw MIGHT BE NULL now !

            // it's senseless to launch 5 weapons in 50 msec,
            // so wait at least this minimum period between launches:
            shootdelay  = max(shootdelay, 100);

            // did XWeapon detach itself?

            if(xw != old)

                shootdelay  = max(shootdelay, 500);  // 0.5 sec no firing
        }
        else
        {
            // fire normal ammunition:

            if(bul.amo)
            {
                new Bullet(this);   // launch bullet from bike
                bul.amo--;
                launched = 1;

                shootdelay  = 50;   // at least so many msec between shots

                field.playsound(SND_PLAINGF);
            }
            else
            {
                // out of ammo: issue comment?

                tell("out of bullets", 300);

                /*
                if(!(comstate & CS_OOA_COMMENTED))
                {
                    comstate |= CS_OOA_COMMENTED;

                    field.playsound(SND_CANTDOTHIS, 1);
                }
                */
            }
        }

        // if launched, 'step' will return immediate without advancing,
        // but if playfield performs another object loop
        // in the same time cycle, advance_step() will be
        // called, though. so reset reladv to avoid stepping
        // in that case.

        if(launched)    reladv  = 0;    // (might be superfluous)
    }

    // if bike launched something, tell ::step now by
    // rcode FAILED that it should NOT call advance_step now,
    // so we avoid stepping into our own launched weapon!

    return launched ? FAILED : OK;
}

void Bike::LockTargDist(cpix todist)
{
    if(todist == -1)
    {
        tdist.locked = 0;
        return;
    }
    tdist.vcur   = (long)todist << 10;
    tdist.locked = 1;
}

rcode Bike::StepTargDist(void)
{_
    //  this is expected to be called every 10 msec.

    //  get direct analogue elongation vector of joystick

    long e1 = ABS((long)joyVec[0]); // x elong
    long e2 = ABS((long)joyVec[1]); // y elong

    //  all elongations under 1/4 of SHRT_MAX are pure random,
    //  so use just the area 1/4 to SHRT_MAX.

    if(e1 < (SHRT_MAX>>2))  e1 = 0; else e1 -= SHRT_MAX>>2;
    if(e2 < (SHRT_MAX>>2))  e2 = 0; else e2 -= SHRT_MAX>>2;

    long v  = (e1 > e2) ? e1 : e2;  // max(x,y) is of interest

    //  PC joystick elongations are 'flickering' around a position.
    //  to avoid a flickering target cursor, map elongation to distance
    //  with some inertia:

    long vtarg  = (
                    (
                        v
                        * (tdist.vmax>>10)
                        / (SHRT_MAX - (SHRT_MAX>>2))

                    )   << 10
                  );

    tdist.vcur += (vtarg - tdist.vcur) >> 3;    // step in 1/n of delta

    return  OK;
}

void Bike::LockTargDir(short toangle)
{
    if(toangle == -1)
    {
        tdir.locked = 0;
        return;
    }
    tdir.angle  = toangle;
    tdir.locked = 1;
}

short Bike::TargetDir(void)
{
    if(tdir.locked)
        return tdir.angle;

    return ana.angle;   // return driving direction by default
}

long Bike::TargetRange(cpix mindist)
{
    long v = tdist.vcur >> 10;

    if(v < mindist) v = mindist;

    return v;
}

//|| Bike.step
rcode Bike::step(PlayMsg* msg)
{_
    if(dead)

        // it is not allowed to delete bikes before
        // the round is finished; dependent objects
        // like BikeDrivers might crash in that case.

        return OK;  // just do nothing

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

        return INDATA;

    if(!msg)
    {
        // this all runs only in primar cycle...

        if(!tdist.locked)
            // adjust current targeting distance
            StepTargDist();

        // count down shooting delay, if set:
        if(shootdelay)
            if((shootdelay -= cyclesize) < 0)
                shootdelay  = 0;

        // count down crash delay, if set:
        if(crashdelay)
            if((crashdelay -= cyclesize) < 0)
                crashdelay  = 0;

        // sound refresh delay
        if(backsndcnt)
            if((backsndcnt -= cyclesize) < 0)
                backsndcnt  = 0;

        // small explosion create delay
        if(smallexpdelay)
            if((smallexpdelay -= cyclesize) < 0)
                smallexpdelay  = 0;

        // xweapon cycling delay
        if(xwcdelay)
        {
            // xweapon cycling not yet done?

            if(comstate & CS_XWCYCLEREQUEST)
            {
                comstate &= (~0 ^ CS_XWCYCLEREQUEST);
                cycleXWeapons();
            }

            if((xwcdelay -= cyclesize) < 0)
                xwcdelay    = 0;
        }

        // shield loss delay
        if(shdlossdly)
        {
            if((shdlossdly -= cyclesize) <= 0)
            {
                shdlossdly  = 0;

                if(    field.num.humans     // NOT in demo mode
                    && number < 2
                    && shdloss >= 2
                    && shields              // avoid double comments
                  )
                {
                    if(shdloss > 3) shdloss = 3;
                    shdloss -= 2;   // make zero-based

                    enum Sounds snd1 = (enum Sounds)(SND_LOSTSH20 + shdloss * SND_LOSTSH_BLOCKSIZE);
                    enum Sounds snd2 = (enum Sounds)(snd1 + SND_LOSTSH_BLOCKSIZE - 1);
                    enum Sounds snd  = field.selectRandomSound(snd1, snd2);

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

                shdloss     = 0;
            }
        }

        if(hoovering)
        {
            // slow down 2d speed to pretend falling down in 3d

            if((speed -= speed / 50) <= (speedmin << 1))
            {
                speed   = (speedmin << 1);

                hoovering   = 0;    // landed on ground
            }
        }
        else
        {
            // on ground: steering enabled

            // change direction & throttle:
            if(steer() == FAILED)
                return OK;
            // if a bullet etc. was launched in steer() it returns FAILED
            // so we don't call advance_step and thus avoid stepping
            // into our own bullet (at high speeds)
        }
    }

    return
        advance_step(msg);  // drive, and maybe collide
}

#ifdef  AMIGA

//  AMIGA HyperSpeed Graphics Support
//  =================================

const   int MAXLINES    = 1100; // y coords upto this-1 supported
const   int MAXPLANES   = 8;    // upto 8 bitplanes supported

ulong   ytable[MAXLINES];   // NEW /1604941807: ARRAY OF LONGS
ulong   bp[MAXPLANES];
short   nplanes;
struct  TextFont*   textfont;
struct  TextAttr    textattr = {"topaz.font",8,0,1};    // ,1 : RomFont

rcode   OpenFastGfx(Screen* scr)
{_
 ushort y,wbytes;

    BitMap* bm  = scr->RastPort.BitMap;
    wbytes      = bm->BytesPerRow;

    for(y=0; y < MAXLINES; y++) ytable[y] = (ulong)y * wbytes;

    nplanes = min(bm->Depth, MAXPLANES);

    for(y=0; y < nplanes; y++)  bp[y] = (ULONG)bm->Planes[y];

    ifn(textfont = OpenFont(&textattr))
    {
        perr("CANNOT OPEN %s", textattr.ta_Name);
        return  NOTAVAIL;
    }

    return  OK;
}

void    CloseFastGfx()
{
    if(textfont)    CloseFont(textfont);    textfont=0;
}

#endif

#ifndef RDEMO

#   define  FIRE_SCANDIST   5

//|| FireDriver
FireDriver::FireDriver() : PlayObj(FIRE_DRIVER, 50, 50)
{_
    ifree       = 0;
    ihighest    = 0;
    slice       = 0;

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

void FireDriver::CreatePix(cpix x, cpix y)
{_
 FPix *fp;

    ifn(field.inbord(x,y))  // valid position?

        return; // if not, don't create

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

        //  set core pix:
        field.plot(x, y, col_fire);

        fp  = &fpix[ifree];

        fp->x       = (short)x;
        fp->y       = (short)y;
        fp->npat    = 0;
        fp->ncnt    = 0;

        fp->active  = 1;

        //  find next ifree, if any:

        while(++ifree < FPIX_MAX && fpix[ifree].active);

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

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

    FPix    *fp;

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

    ushort  slices  = 16;   // MUST be a power of 2 !

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

    for(fp = &fpix[i]; i <= ihighest; i += slices, fp += slices)
    {

    if(!fp->active) continue;

    // --- handle pixel 'fp' ---

    // select next neighbour to check by random.
    // scan through pattern of checked neighbours to do so.

    nbit = rand() & 7;
    bail = 10;

    // search free bit from pos'n nbit on

    while((fp->npat & (1 << nbit)) && --bail)

        nbit = (nbit + 1) & 7;

    if(!bail)   // if search failed, all n.'s are checked
    {
        // delete pixel:

        field.setLevel(1);
        ((PixelDisplay*)&field)->plot(fp->x, fp->y, col_blue6);
        field.setLevel(0);
        field.plot(fp->x, fp->y, col_backgrnd);

        fp->active  = 0;    // inactive

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

        deleted = 1;

        continue;           // with next fpix
    }

    fp->npat |= 1 << nbit;  // mark neighbour as checked

    // nbit is the neighbour.

    static short ds_delta[8][2] = {
            0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1, -1,0, -1,-1
            };

    // check some pixels in that direction.

    short dist;
    cpix x,y;
    pcol col;

    for(dist=0; dist<FIRE_SCANDIST; dist++)
    {
        x   = (cpix)fp->x + ds_delta[nbit][0] * dist;
        y   = (cpix)fp->y + ds_delta[nbit][1] * dist;

        if(field.inbord(x,y))
        {
            col = field.readpix(x,y);

            switch(col)
            {
                case col_fixwall:
                case col_fixwall2:
                case col_fixwall3:
                case col_dust:
                case col_waveexpl:
                case col_oil:
                case col_shape0:
                case col_shape1:
                case col_grass:
                    //  try to create new fpix:
                    CreatePix(x,y);
                    break;
            }
        }
    }

    // -------------------------

    }   // endfor i

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

        while(ihighest && !fpix[ihighest].active)

            ihighest--;
    }

    return  OK;
}

#endif  // !defined(RDEMO)
