/*   _____________________________________________________________
    /                                                             \
    | File: playfunc.cpp - playfield functions
    \_____________________________________________________________/

    contents:

        code for

            PlayField

    Developers: ID: Name:
    =========== --- -----
                JTH Juergen Thumm
  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\
 yymmdd ------- --- ---------------------------------------------------------
 940811 1.38    JTH created from playfield.cpp due to memory overflow on comp.
 950128 1.72        division bug fix: convdir(cpix,cpix): the short division
                    crashed when -32768/-1 was calculated. fixed this by
                    using long division.
 950422 1.85        PF::operator += now sets the obj cache ptr if it's NULL.
                    PF::operator -= now adapts cach ptr entry to NEXT object
                    of same type, if any available.
                    The consequence is that the cache entry MUST be set
                    if there are ANY objects of that type.
                    So, getobj() now returns IMMEDIATELY if the cache entry
                    is not set.
 950916             PF::playSound: added LSMOVE2,3 and better SND_CANF2,3,4
                    mapping.
*/

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

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

//  ModInit(playfunc_o);

//|| PlayFCode

pcol PlayField::scanline(cpix start[2], cpix end[2],
    pcol forcolors[2], cpix firsthit[2], cpix *prehitpos)
{_
    // scan from start to end for first matching color or display border

    long sx = start[0];
    long sy = start[1];
    long ex = end[0];
    long ey = end[1];

    long dx = ex - sx;
    long dy = ey - sy;

    long adx    = abs(dx);
    long ady    = abs(dy);
    long amax   = max(adx, ady);

    long cx=-1,cy=-1,ox,oy;
    pcol col,lowcol=forcolors[0],hicol=forcolors[1];

    firsthit[0] = -1;
    firsthit[1] = -1;

    if(prehitpos)
    {
        prehitpos[0]    = -1;
        prehitpos[1]    = -1;
    }

    if(amax)
    {
        for(long i=0; i<amax; i++)
        {
            ox  = cx;   // store for use
            oy  = cy;   // as prehitpos

            cx  = sx + i * dx / amax;
            cy  = sy + i * dy / amax;

            if(inbord((cpix)cx,(cpix)cy))
            {
#ifdef  AMIGA
                col = readpix(cx,cy);
#else
                col = fbuf.get(cx,cy);  // fast access
#endif
                if(col >= lowcol && col <= hicol)
                {
                    // found a matching color:

                    firsthit[0] = cx;
                    firsthit[1] = cy;

                    if(prehitpos)
                    {
                        // return also pos'n before match:

                        if(ox != -1)
                        {
                            prehitpos[0]    = ox;
                            prehitpos[1]    = oy;
                        }
                        else
                        {
                            prehitpos[0]    = cx;
                            prehitpos[1]    = cy;
                        }
                    }

                    return  col;
                }
            }
            else
            {
                if(ox != -1)
                {
                    firsthit[0] = ox;   // return last
                    firsthit[1] = oy;   // valid pos'n
                }

                return -1;  // illegal coord reached
            }
        }

        // scanned all with no hit:

        firsthit[0] = cx;   // not really a hit,
        firsthit[1] = cy;   // but max. free position
    }

    // lines with just one pixel are NOT supported
    // and return always -1

    return  -1; // nothing found or illegal coords
}

#ifdef  WIN16

#   define  BACKPAT_SIZE (1<<4)
#   define  BACKPAT_MSK  (BACKPAT_SIZE-1)

static uchar BackPat[BACKPAT_SIZE][BACKPAT_SIZE] = {

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

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

    };

#   define  BackPatCol(x,y) BackPat[y & BACKPAT_MSK][x & BACKPAT_MSK]

rcode PlayField::plot(cpix x, cpix y, pcol xcol)
{
 pcol   dcol;   // display color code
 ulong  bgr;

    if(xcol != -1)      color(xcol);
    if(checkbnd(x,y))   return FAILED;

    switch(col)
    {
        case col_backgrnd:
            fbuf.put(x,y,col);
            return transPix(x,y);

        case col_flame:
        case col_fire:
        case col_waveexpl:
        case col_fixwall:
        case col_dust:
        case col_cmimpact:
        case col_xweapon:
        case col_lsword0:
        case col_lsword1:
        case col_mgimpact:
        case col_smallexpl:

            switch(fbuf.get(x,y))
            {
                case col_fixwall:
                case col_dust:
                    // a fixwall got hit, destroy a bit
                    setLevel(1);    // switch to backmap
                    PixelDisplay::plotby(x,y,col_backgrnd);
                    // plotby does NOT change current selected 'col'
                    setLevel(0);
            }
    }

    // plot color code into frame buffer
    fbuf.put(x,y,col);

    //if(col == col_backgrnd)
    //    dcol = col_backgrnd + BackPatCol(x,y);
    //else
    //    dcol = col;

    dcol = col;

    // plot display color code onto display

    if(getcurhdc() == getfronthdc() && (winmodes & WINM_SETPIXSWAPRB))
    {
        bgr = cman.rgb32of(dcol);

        SetPixel(getcurhdc(), x, y,
                 ((bgr & 0xFFUL)<<16)|(bgr & 0xFF00UL)|(bgr >> 16));
    }
    else
        SetPixel(getcurhdc(), x, y, WinCol(dcol));

    return OK;
}

rcode PlayField::unplot(cpix x, cpix y)
{
 pcol   dcol;
 ulong  bgr;

    if(checkbnd(x,y))   return FAILED;

    // get color code from frame buffer
    dcol = fbuf.get(x,y);

    if(   dcol == col_backgrnd
       || dcol == col_fixwall
       || dcol == col_xweapon
      )
        return transPix(x,y);

    // plot display color code onto display
    if(getcurhdc() == getfronthdc() && (winmodes & WINM_SETPIXSWAPRB))
    {
        bgr = cman.rgb32of(dcol);

        SetPixel(getcurhdc(), x, y,
                 ((bgr & 0xFFUL)<<16)|(bgr & 0xFF00UL)|(bgr >> 16));
    }
    else
        SetPixel(getcurhdc(), x, y, WinCol(dcol));

    return OK;
}

rcode PlayField::lineby(cpix x1, cpix y1, cpix x2, cpix y2, pcol xcol)
{_
    if(check()) {SysErr(1604951801);    return FAILED;}

    if(xcol != -1)  color(xcol);

#ifdef  WIN16

    //  can't perform native Window's LineTo() 'cause this plots
    //  into slightly other coordinates than PlayField's unline().
    //  therefore plot-by all points of the line by hand.

    long sx = x1;
    long sy = y1;
    long ex = x2;
    long ey = y2;

    long dx = ex - sx;
    long dy = ey - sy;

    long adx    = abs(dx);
    long ady    = abs(dy);
    long amax   = max(adx, ady);

    long cx,cy;

    if(amax)
    {
        for(long i=0; i<amax; i++)
        {
            cx  = sx + i * dx / amax;
            cy  = sy + i * dy / amax;

            if(checkbnd((cpix)cx,(cpix)cy)==OK)
                plotby((cpix)cx, (cpix)cy, col);
        }
    }

    plotby(x2,y2, col);  // unplot single or end point

#endif

    return OK;
}

rcode PlayField::unline(cpix x1, cpix y1, cpix x2, cpix y2)
{_
    if(check()) {SysErr(1604951806); return FAILED;}

#ifdef  WIN16

    //  un-plot all points of the line by hand

    long sx = x1;
    long sy = y1;
    long ex = x2;
    long ey = y2;

    long dx = ex - sx;
    long dy = ey - sy;

    long adx    = abs(dx);
    long ady    = abs(dy);
    long amax   = max(adx, ady);

    long cx,cy;

    if(amax)
    {
        for(long i=0; i<amax; i++)
        {
            cx  = sx + i * dx / amax;
            cy  = sy + i * dy / amax;

            if(checkbnd((cpix)cx,(cpix)cy)==OK)
                unplot((cpix)cx, (cpix)cy); // using PlayField::unplot
        }
    }

    unplot(x2,y2);  // unplot single or end point

#endif

    return OK;
}

rcode PlayField::rectfill(cpix x1, cpix y1, cpix x2, cpix y2, pcol xcol)
{_
    if(xcol != -1)  color(xcol);

    cpix h;

    if(x2 < x1) {h=x1; x1=x2; x2=h;}
    if(y2 < y1) {h=y1; y1=y2; y2=h;}

    if(checkbnd(x1,y1)) return FAILED;
    if(checkbnd(x2,y2)) return FAILED;

    fbuf.rectfill(x1,y1,x2,y2,col); // fill rect in virtual frame buffer

    if(col == col_backgrnd)
    {
        transRect(x1,y1,x2,y2); // from back to front
        return OK;
    }

    SetTextColor(getcurhdc(), WinCol(col));

    HBRUSH brush;

    if(brush = CreateSolidBrush(WinCol(col)))
    {
        RECT rect;

        rect.left   = x1;
        rect.top    = y1;
        rect.right  = x2 + 1;
        rect.bottom = y2;

        FillRect(getcurhdc(), &rect, brush);    // really paint a filled rectangle

        DeleteObject(brush);
    }
    else
        perr("rectfill: can't create brush");

    if(col == col_backgrnd)
    {
        //  create backgrnd pattern

        cpix    x,y;
        pcol    dcol;
        ulong   bgr;

        for(x = x1; x <= x2; x++)

            for(y = y1; y <= y2; y++)
            {
                dcol = col_backgrnd + BackPatCol(x,y);

                if(dcol != col_backgrnd)

                if(getcurhdc() == getfronthdc() && (winmodes & WINM_SETPIXSWAPRB))
                {
                    bgr = cman.rgb32of(dcol);

                    SetPixel(getcurhdc(), x, y,
                         ((bgr & 0xFFUL)<<16)|(bgr & 0xFF00UL)|(bgr >> 16));
                }
                else
                    SetPixel(getcurhdc(), x, y, WinCol(dcol));
            }
    }

    return OK;
}

void PlayField::ClearPlayGround(void)
{_
    PixelDisplay::rectfill(bord[0],bord[1], bord[2],bord[3], col_backgrnd);

#   define LINE(x1,y1,x2,y2,pen)    \
    \
    hold = SelectObject(getcurhdc(), pen);  \
    MoveTo(getcurhdc(),x1,y1);              \
    LineTo(getcurhdc(),x2,y2);              \
    SelectObject(getcurhdc(), hold)

    short       step = BACKPAT_SIZE;    // pattern size
    cpix        cx,cy;
    HPEN        pen2,pen3;
    HANDLE      hold;

    //  lines for backpattern are drawn with native WIN16 functions
    //  for maximum speed.

    pen2    = CreatePen(PS_SOLID, 1, WinCol(col_backgrnd2));
    pen3    = CreatePen(PS_SOLID, 1, WinCol(col_backgrnd3));

    if(!pen2 || !pen3)  return;

    for(cx = 0; cx < hwfrm[2]; cx += step)
    {
        if(cx >= bord[0] && cx < bord[2])
        {
            LINE(cx, bord[1], cx, bord[3]-1, pen2);
            LINE(cx+step-1, bord[1], cx+step-1,bord[3]-1, pen3);
        }
    }

    for(cy = 0; cy < hwfrm[3]; cy += step)
    {
        if(cy >= bord[1] && cy < bord[3])
        {
            LINE(bord[0],cy, bord[2]-1,cy, pen2);
            LINE(bord[0],cy+step-1, bord[2]-1,cy+step-1, pen3);
        }
    }

    DeleteObject(pen2);
    DeleteObject(pen3);
}

#endif

#ifdef  WIN16

static char rimxyc[8][3] =

    {
         0,-1,  0,  // x 0 y -1 relcolor 0
         1,-1,  0,
         1, 0,  1,
         1, 1,  1,
         0, 1,  1,
        -1, 1,  0,
        -1, 0,  0,
        -1,-1,  0
    };

rcode PlayField::plotshad(cpix x, cpix y, pcol xcol, uchar rimpix)
{
 rcode rc;

    // plot center pixel in base color

    if(rc = plot(x,y,xcol)) return rc;

#ifdef  WIN16
    /*

    cpix ax,ay;

    // plot shade pixels in shade colors

    for(uchar pix=0; pix<8; pix++)

        if(rimpix & (1<<pix))
        {
            ax = x + rimxyc[pix][0];
            ay = y + rimxyc[pix][1];

            if(inbord(ax,ay))

                plot(ax,ay, col_shadebase + xcol * 2 + rimxyc[pix][2]);
        }

    */
#endif

    return  OK;
}

#endif

#ifdef  AMIGA

rcode PlayField::plot(cpix x, cpix y, pcol xcol)
{
 short pcol;

    // coordinate validity check:
    // MUST be, 'cause FastPixel routines won't do it

    if(     x < hwfrm[0] || x > hwfrm[2]
        ||  y < hwfrm[0] || y > hwfrm[3]    )   return INDATA;

    if(xcol == -1)
        pcol = (short)PixelDisplay::col;    // default: take PixDisp's color
    else
    {
        pcol = (short)xcol;
        color(xcol);        // call PixelDisplay::color()
    }

    FWritePix((short)x, (short)y, pcol);

    istat.pix.plot++;   // count plot call

    return OK;
}

pcol PlayField::readpix(cpix x, cpix y)
{
    istat.pix.read++;   // count read call

    return (pcol)FReadPix((short)x, (short)y);
}

rcode PlayField::fasttext(const char *str)
{
 short x = rport->cp_x, y = rport->cp_y;

    if(check()) {SysErr(904941638); return FAILED;}

    if(strlen(str) > (hwfrm[2] >> 3) - (x >> 3))
    {   SysErr(904941757); return INDATA;   }

    FWriteText(x, y, (short)PixelDisplay::col, str);

    return OK;
}

rcode PlayField::fasttext(cpix x, cpix y, const char *str)
{
 rcode  rc;

    if(rc = move(x,y))  return rc;

    return  fasttext(str);
}

#endif

rcode PlayField::operator += (PlayObj* pobj)
{_
    add(pobj);      // add new PlayObj's at END of list,
    // so their seqnum entries will be sorted ascendingly

    //  NEW: if this is the first object of that type in the list,
    //       set now and here the cache ptr to it.

    ushort gcidx = (ushort)pobj->id.code;

    if(gcidx >= ID_MAX)
        SysErr(2204951528);
    else
    if(!getcache[gcidx])    // if there's yet no other obj of that type,
        getcache[gcidx] = pobj; // set cache ptr to the new first one.

    istat.npo++;    // count number of PlayObj's for internal statistics

    return OK;
}

rcode PlayField::operator -= (PlayObj* pobj)
{_
    if(walksucptr == pobj)
    {
        walksucptr  = pobj->next();

        dmsg("PF::op -= walksucptr adapted");
    }

    ushort gcidx = (ushort)pobj->id.code;

    if(gcidx >= ID_MAX)
        SysErr(1311941150);
    else
    if(getcache[gcidx] == pobj)
    {
        //  CHANGE 220495: do not just NULL the cache entry,
        //  but adapt cache ptr to next object of that type, if any

        for(    PlayObj* pnext = pobj->next();
                pnext;
                pnext = pnext->next()
           )
            if((ushort)pnext->id.code == gcidx)
                break;

        getcache[gcidx] = pnext;    // next or NULL
//      dmsg("getcache entry %u now %lxh",gcidx,pnext);
    }

    remove(pobj);
    istat.npo--;    // cnt no. of PlayObjs

    return OK;
}

void PlayField::deleteall()
{_
 PlayObj* po;

    // DON'T use ListHead::deleteall() ! The list contains only
    // PlayObj's, and PlayObj's remove THEMSELVES from list on deletion,
    // which contrasts with ListHead::deleteall() which removes and
    // deletes all listnodes.

    while(po = first()) delete po;

    istat.npo=0;
}

short PlayField::tunmapxy(cpix& x, cpix& y, cpix *htpos)
{
 bool   inht=0,invt=0;
 cpix   h2tun[2];
 const  cpix tol=3; // three pixels tolerance by pass-through check

    if(htpos)   // use field's htunnel pos'n or a supplied pos'n?
    {
        h2tun[0] = *htpos++;    // take supplied one
        h2tun[1] = *htpos;
    }
    else
    {
        h2tun[0] = htun[0];     // use current htunnel's pos'n
        h2tun[1] = htun[1];
    }

    if(     (x >= vtun[0][0] && x <= vtun[0][1])
        ||  (x >= vtun[1][0] && x <= vtun[1][1])    )

        invt = 1;

    if(y > h2tun[0] && y < h2tun[1])

        inht = 1;

    if((x  < bord[0] && x >= bord[0] - tol) && inht)
        {   x = bord[2]-1;  return PASSED_HTUNNEL;  }

    if((x >= bord[2] && x <= bord[2] + tol) && inht)
        {   x = bord[0];    return PASSED_HTUNNEL;  }

    if((y  < bord[1] && y >= bord[1] - tol) && invt)
        {   y = bord[3]-1;  return PASSED_VTUNNEL;  }

    if((y >= bord[3] && y <= bord[3] + tol) && invt)
        {   y = bord[1];    return PASSED_VTUNNEL;  }

    return  0;
}

enum Dirs PlayField::MapIEDir[8] = {

    (enum Dirs)(UP),
    (enum Dirs)(UP|RIGHT),
    (enum Dirs)(RIGHT),
    (enum Dirs)(DOWN|RIGHT),
    (enum Dirs)(DOWN),
    (enum Dirs)(DOWN|LEFT),
    (enum Dirs)(LEFT),
    (enum Dirs)(UP|LEFT)

    };

short PlayField::convdir(cpix dx, cpix dy)
{
 long d,ad;

    ifn(dy) return (dx > 0) ? 2 : 6;

    d   = ((long)dx << 10) / (long)dy;  // ascend scaled by 1024
    ad  = abs(d);

    if(d < 0)   // 0,1,2, 4,5,6
    {
        if(dx > 0)
        {
            if(ad <  512)   return 0;
            if(ad < 1024)   return 1;
            if(ad < 2048)   return 1;
                            return 2;
        }
        else
        {
            if(ad <  512)   return 4;
            if(ad < 1024)   return 5;
            if(ad < 2048)   return 5;
                            return 6;
        }
    }
    else    // d >= 0 : 2,3,4, 6,7,0
    {
        if(dx > 0)
        {
            if(ad <  512)   return 4;
            if(ad < 1024)   return 3;
            if(ad < 2048)   return 3;
                            return 2;
        }
        else
        {
            if(ad <  512)   return 0;
            if(ad < 1024)   return 7;
            if(ad < 2048)   return 7;
                            return 6;
        }
    }

    return  0;
}

PlayObj* PlayField::getobj(enum ObjCodes oc)
{
 PlayObj* po;
 ushort gcidx = (ushort)oc;

    if(oc == UNKNOWN || gcidx >= ID_MAX)
    {
        SysErr(1311941158); return 0;
    }

    //  if there are ANY objects of that type,
    //  the cache entry MUST be set to the first one.

    return getcache[gcidx]; // object or NULL
}

PlayObj* PlayField::nextobj(PlayObj* cur, enum ObjCodes oc)
{
    while((cur = cur->next()) && cur->id.code != oc);

    return  cur;
}

bool PlayField::soundexists(enum Sounds sndno)
{
    if(sndno >= SND_MAX)
    {   SysErr(2103951844); return 0;   }

    if(!p_snd[sndno])
    {   SysErr(1903951150); return 0;   }

    return p_snd[sndno]->valid();
}

rcode PlayField::playsound(enum Sounds sndno, bool stereo, short howoften,
                           ulong opts)
{_
    if(!opt.playsoundenabled)
        return OK;

 int vol,speed;

    if(sndno >= SND_MAX)
    {   SysErr(2103951843); return INTERNAL;    }

    if(sndparm[sndno][1]==-1)
        vol = volume;
    else
    {
        // calc volume value relative to system volume:
        // take table value as PERCENTAGE of system volume

        vol = volume * sndparm[sndno][1] / 100;
    }

    vol = vol * SND_VOL_MAX / 100;

    ifn(speed = p_snd[sndno]->IFFspeed())   // if IFF sound, take it's speed,

        speed = sndparm[sndno][0];          // otherwise speed from table

    if(!(p_snd[sndno]->valid()))
    {
      // sound isn't loaded. try to map to another:

      switch(sndno)
      {
        case SND_BAZOH:
        case SND_KATSHAH:
        case SND_BKEXPL:
            sndno = SND_MISLH;
            break;

        case SND_CANF1:
            sndno = SND_PLAINMGF;
            break;

        case SND_CANF2:
        case SND_CANF3:
        case SND_CANF4:
        case SND_FATMGF:
            if(p_snd[SND_CANF1]->valid())
                sndno = SND_CANF1;
            else
                sndno = SND_PLAINMGF;
            break;

        case SND_LSH2:
        case SND_LSH3:
        case SND_LSHBK:
            sndno = SND_LSH1;
            break;

        case SND_LSMOVE2:
        case SND_LSMOVE3:
            sndno = SND_LSMOVE1;
            break;

        case SND_MGUN4F2:   // reached only when f1 available
        case SND_MGUN4F3:   // "
            sndno = SND_MGUN4F1;
            break;

        case SND_MGUN4E:
        case SND_FATMGE:
            sndno = SND_PLAINMGE;
            break;

        case SND_KATSHAF2:  sndno = SND_KATSHAF1;   break;
        case SND_BKHWALL:   sndno = SND_BKHOBST;    break;
        case SND_BKDRIVE2:  sndno = SND_BKDRIVE1;   break;
        case SND_PGUNFE:    sndno = SND_PGUNFR;     break;
        case SND_MISLF:     sndno = SND_BAZOF;      break;
        case SND_AMOC:      sndno = SND_XWEAPC;     break;

        case SND_MGAMOC:
        case SND_PGAMOC:
        case SND_FTAMOC:
        case SND_LSAMOC:
        case SND_MLAMOC:
        case SND_KAMOC:
            if(p_snd[SND_AMOC]->valid())
                sndno = SND_AMOC;
            else
                sndno = SND_XWEAPC;
            break;

        case SND_MGUNC:
        case SND_LSWORDC:
        case SND_FTHROWC:
        case SND_PGUNC:
        case SND_OILC:
        case SND_BAZOC:
        case SND_MLAUNC:
        case SND_SHIELDC:
        case SND_KATSHAC:
            sndno = SND_XWEAPC;
            break;
      }
    }

    if(p_snd[sndno]->valid())
    {
        if(stereo && opt.stereo)    // play stereo only in stereo mode
            return p_snd[sndno]->playstereo(howoften, speed, vol);
        else
            return p_snd[sndno]->play(howoften, speed, vol, opts);
    }

    return NOTAVAIL;
}

void PlayField::silence()
{_
 short i;

#ifdef  WIN16
    // driving sounds are played in endless mode,
    // so stop them now:

    stopsound(SND_BKDRIVE1);
    stopsound(SND_BKDRIVE2);
#endif

    do
    {
        for(i=0; i<SND_MAX; i++)

            if(p_snd[i]->playing())
            {
#ifdef  AMIGA
                Delay(5);   // wait 0.1 sec
#endif
                break;
            }
    }
    while(i < SND_MAX); // as long as at least one sound is playing
}

rcode PlayField::stopsound(enum Sounds sndno)
{_
    if(sndno >= SND_MAX)
    {   SysErr(2103951845); return INTERNAL;    }

    if(p_snd[sndno]->valid())
    {
        p_snd[sndno]->stop();
        return OK;
    }

    return NOTAVAIL;
}

void PlayField::stopsound(void)
{_
    for(ushort i=0; i<SND_MAX; i++)

        p_snd[i]->stop();
}

#ifdef  AMIGA

//|| AdjustDisplay

rcode PlayField::AdjustDisplay(bool initial)
{_
 AmDSection *top,*mid,*bot;
 Bike       *bk;
 int        vyfull  = (pavpos[2] - 12 - pavpos[0]);
 int        vy4th   = vyfull/2;
 int        vxfull  = 320 + 16; // so many visible pixels
 int        vxhalve = vxfull/2; // halve of this
 bool       refresh = initial;  // display refresh necessary?
 cpix       bpos[2][2]; // bike positions

    // get the main bike positions.
    // on initial call, it might be that there are no bikes

    if(bk = (Bike*)getobj(BIKE0))
    {
        bpos[0][0]  = bk->pos[0];
        bpos[0][1]  = bk->pos[1];
    }
    else
    {
        bpos[0][0]  = bord[0]+10;   // default dummy position
        bpos[0][1]  = bord[1]+10;
    }

    if(bk = (Bike*)getobj(BIKE1))
    {
        bpos[1][0]  = bk->pos[0];
        bpos[1][1]  = bk->pos[1];
    }
    else
    {
        bpos[1][0]  = bord[0]+10;   // default dummy position
        bpos[1][1]  = bord[1]+10;
    }

    // get display section controls:
    ifn(top = FirstDSection())  return NOTAVAIL;
    ifn(mid = top->next())      return NOTAVAIL;    // read separator,
    ifn(mid = mid->next())      return NOTAVAIL;    // then real 'mid'
    ifn(bot = mid->next())      return NOTAVAIL;

    // adjust display section y positions according to bike positions:

    // adjust BIKE0 position:

    int tdiff,bdiff,line;
    int ldiff,rdiff,colmn;

    // y pos'n:

    tdiff   = bpos[0][1] - (bord[1] - 1);
    bdiff   = bord[3] - bpos[0][1];

    if(tdiff  < vy4th)
        line    = bord[1] - 1;
    else
    if(bdiff >= vy4th)
        line    = bpos[0][1] - vy4th;
    else
        line    = bord[3] - vyfull;

    // x pos'n:

    if(hwsize[0] > vxfull)
    {
        if(bpos[0][0] < vxhalve)
            colmn   = 0;
        else
        if(bpos[0][0] < hwsize[0] - 1 - vxhalve)
            colmn   = bpos[0][0] - vxhalve;
        else
            colmn   = hwsize[0] - 1 - vxfull;
    }
    else
        colmn   = 0;

    if(top->mem_y != line || top->mem_x != colmn)
    {
        top->SetMemPos(colmn, line);
        refresh = 1;
    }

    // adjust BIKE1 position:

    // y pos'n:

    tdiff   = bpos[1][1] - (bord[1] - 1);
    bdiff   = bord[3] - bpos[1][1];

    if(tdiff  < vy4th)
        line    = bord[1] - 1;
    else
    if(bdiff >= vy4th)
        line    = bpos[1][1] - vy4th;
    else
        line    = bord[3] - vyfull;

    // x pos'n:

    if(hwsize[0] > vxfull)
    {
        if(bpos[1][0] < vxhalve)
            colmn   = 0;
        else
        if(bpos[1][0] < hwsize[0] - 1 - vxhalve)
            colmn   = bpos[1][0] - vxhalve;
        else
            colmn   = hwsize[0] - 1 - vxfull;
    }
    else
        colmn   = 0;

    if(mid->mem_y != line || mid->mem_x != colmn)
    {
        mid->SetMemPos(colmn, line);
        refresh = 1;
    }

    // bottom mem-position stays the same

    // let OS remake display ONLY if something visible changed:

    if(initial)
    {
        //Permit();     // temporarily re-allow multitasking
        ReDisplay();    // create new user copper list (fondles multitasking)
        //Forbid();     // re-forbid multitasking
    }
    else
        if(refresh)
            AdjustPositions();  //÷ change pos'n instructions in user coplst

    return OK;
}

#endif

void PlayField::checkpause()
{_
    if(keyb.pressed(KEY_SPACE))
    {
        showstatus("GAME","PAUSED");

        stopsound();    // stop ALL sounds immediately

        while(!keyb.pressed(KEY_SPACE))
#ifdef  AMIGA
            Delay(2);
#else
            ;
#endif
        showstatus();
    }

    static ushort lpdelay = 0;  // low prio keys delay

    if(!(++lpdelay & 1))        // every 2nd call
    {
        if(keyb.pressed('P'))
            opt.showxinfos ^= 1;    // perfmon on/off

        if(keyb.pressed('C'))
        {
            if(opt.showxinfos & 4)  // extended time on?
            {
               opt.showxinfos ^= 6; // all time off
               tell("", 100);       // un-display time
            }
            else
            if(opt.showxinfos & 2)  // simple time on?
               opt.showxinfos |= 4; // switch to extended time
            else
               opt.showxinfos |= 2; // else simple time on
        }

        if(keyb.pressed('T'))
            field.opt.tlaser = (field.opt.tlaser + 1) & (TLASER_SHAPES-1);

        if(keyb.pressed(KEY_F7))
            field.savescore(1); // 1 == while playing

        if(keyb.pressed('V'))
            field.opt.debugmode ^= ZDEB_VIDEODEBUG;

        if(keyb.pressed('U'))
        {
            pcol c;
            cpix x,y;

            // PixelDisplay framebuffer visual check:
            for(y = bord[1]; y < bord[3]; y++)
            for(x = bord[0]; x < bord[2]; x++)
                if(c = fbuf.get(x,y))
                    PixelDisplay::plotby(x,y,c);
        }
    }
}

void PlayField::tell(char* txt, int msec)
{_
 int len = min(strlen(txt), himsg.MAXLEN);

    memset(himsg.txt, ' ', himsg.MAXLEN);
    if(len) strncpy(&himsg.txt[(himsg.MAXLEN-len)/2], txt, len);
    himsg.txt[himsg.MAXLEN] = '\0';

    // the game runs in cycles to (CYCLE_MS - taddspeed) msec.
    // every 20 cycles showstatus() is called which steps the globalmessage.
    // after 11 steps the globmsg has performed a cycle.
    // himsg.cnt contains number of cycles.
    himsg.cnt = ((long)msec / ((max(CYCLE_MS - taddspeed,1)) * 20L * 11L)) + 1L;

    himsg.ccycle[0] = startcols[col_globmsg];
    himsg.ccycle[1] = 1;    // cycling direction
}

bool xwDiffers(XWeapon* xw1, XWeapon* xw2, short oldAmo, short oldXCharge)
{
    if(!xw1 && !xw2)    // both NULL?
        return  0;

    if(!xw1 || !xw2)    // one of them NULL?
        return  1;

    if(xw1 != xw2)      // not same xweapon?
        return  1;

    // same xweapon as before, check members

    if(     xw1->amo != oldAmo
        ||  xw1->xcharge() != oldXCharge    )
        return  1;

    return  0;
}

char* xwAsString(XWeapon* xw)
{
 static char buf[40];
 StringLib   sl;

    //            01234567890123456789
    //            flame thrower d+ ddd
    sprintf(buf, "                    ");

    if(xw)
    {
        if(xw->name)
        {
            strncpy(buf, xw->name, strlen(xw->name));
            sl.toupper(buf);
        }

        if(xw->xcharge())
        {
            buf[14] = '0' + xw->xcharge();
            buf[15] = '+';
        }

        sprintf(&buf[17], "%03d", xw->amo);
    }

    return buf;
}

#ifdef  AMIGA
#   define  ftxt fasttext
#else
#   define  ftxt text
#endif

//|| SHOWSTATUS
void PlayField::showstatus(char* extra_txt0, char* extra_txt1)
{_
 char   buf[4][42],tbuf[80], tbuf2[20];
 Bike   *bike0 = (Bike*)getobj(BIKE0);
 Bike   *bike1 = (Bike*)getobj(BIKE1);
 Bike   *bike;
 XWeapon *xw;
 char   *txt;
 int    xccnt;
 short  offs,scol,paintdark=0,k,bnum;
 uchar  iKMH=0,iSHD=0,iAMO=0,iPTS=0,iSETS=0,iGMS=0; // buffer write indexes
 bool   compact = 0;
 short  ma4th   = msgarea[2] >> 2;
 short  cs0     = charsize[0];
 StringLib sl;

    if(!bike0 || !bike1)
    {   SysErr(2204951706); return; }

//  if(hwsize[0] < 640)
//      compact = 1;    // compact dashboard with primary weapon info

    if(compact)
    {
        //                 xxx xx xxx xx xxx
        //               0123456789012345678901234567890123456789
        sprintf(buf[0], "1:KMH SH AMO PT S G 2:KMH SH AMO PT S G ");
        sprintf(buf[1], "                                        ");
        sprintf(buf[2], "                                        ");
        sprintf(buf[3], "                                        ");
        iKMH = 2;
        iSHD = 6;
        iAMO = 9;
        iPTS =13;
    }
    else
    {
        //                  xxx  xxx xxx xxx
        //               0123456789012345678901234567890123456789
        sprintf(buf[0], "1:SPEED SHD PTS S M 2:SPEED SHD PTS S M ");
        sprintf(buf[1], "                                        ");
        sprintf(buf[2], "                                        ");
        sprintf(buf[3], "                                        ");
        iKMH = 3;
        iSHD = 8;
        iAMO =12;   // used instead of iPTS
        iPTS =12;
    }

    iSETS=16;
    iGMS =17;

    for(bnum=0; bnum<2; bnum++)
    {
        bike    = bnum ? bike1 : bike0;
        offs    = bnum ?    20 :     0;

        if(!bike)   continue;

        sprintf(tbuf, "%3.3d", convif(bike->speed)); // speed
        strncpy(&buf[1][offs+iKMH], tbuf, 3);

        if(compact)
            sprintf(tbuf, "%2.2d", bike->shields);   // shields
        else
        {
            if(bike->shields < 10)
                sprintf(tbuf, " %d", bike->shields);
            else
                sprintf(tbuf, "%d", bike->shields);
        }
        strncpy(&buf[1][offs+iSHD], tbuf, 2);

        if(compact)
        {
            sprintf(tbuf, "%3.3d", bike->bul.amo);   // ammunition
            strncpy(&buf[1][offs+iAMO], tbuf, 3);
        }

        sprintf(tbuf, "%2.2d", stat.points[bnum]);   // points
        if(tbuf[0] == '0')  tbuf[0] = ' ';
        strncpy(&buf[1][offs+iPTS], tbuf, 2);

        buf[1][offs+iSETS] = stat.sets[bnum] + '0';  // sets

        sprintf(tbuf, "%2.2d", stat.games[bnum]);    // games
        if(tbuf[0] == '0')  tbuf[0] = ' ';
        strncpy(&buf[1][offs+iGMS], tbuf, 2);

        // extra text, or special weapon(s) mounted?

        txt = bnum ? extra_txt1 : extra_txt0;

        if(txt)
            sl.center(&buf[2][offs+0], txt, 19);
        else
        if(compact && (xw = bike->weapons.first()))
        {
            if(txt = xw->name)
            {
                // replace 'AMO' by XWeapon ID letter:

                buf[0][offs+iAMO] = buf[0][offs+iAMO+2] = ' ';
                buf[0][offs+iAMO+1] = xw->id_char;

                // put XWeapon amo in amo's field:

                sprintf(tbuf, "%2.2d ", xw->amo);       // 2 digits + blank
                strncpy(&buf[1][offs+iAMO], tbuf, 3);   // are 3 chars

                // put xw's name to tbuf:

                strcpy(tbuf, txt);

                // is XWeapon extra charged?

                if(xccnt = xw->xcharged)
                {
                    xccnt = min(xccnt, 6);
                    while(xccnt--)  strcat(tbuf, "+");
                }

                sl.center(&buf[2][offs+0], tbuf, 19);
            }
        }

        // status text set?

        txt = bike->statustxt;
        if(*txt)
        {
            sl.center(&buf[3][offs+0], txt, 19);

            // how long should the statustxt be displayed?
            bike->statustime    -= CYCLE_MS;
            if(bike->statustime <= 0)
                *txt = '\0';    // time elapsed: remove msg
        }
        else
        {
            // no status text set.
            // if bike has more than 5 weapons, display 6 ff. in short form.
            // if it has less, display number of remaining bullets.

            k = 0;

            for(xw = bike->weapons.first();
                xw && (k < 19);
                xw = (XWeapon*)xw->next() )
            {
                if(k >= 5)
                    tbuf[k-5]   = xw->id_char;

                k++;
            }

            if(k <= 5)
            {
                // less than 5 weapons: display no. of bullets
                sprintf(tbuf, "%d bullets", (short)bike->bul.amo);
            }
            else
                tbuf[k-5] = '\0';

            sl.center(&buf[3][offs+0], tbuf, 19);
            paintdark |= 1 << bnum; // paint section(s) 7 or 3 dark
        }

    }   // endfor bike i

    drawmode(NORMAL2);  // overlap = overwrite

    backcol(col_msgback);   // black background for messages

    // if a global (temporar) message is set,
    // display it in hi message line:
    if(!(opt.showxinfos & 1))
    {
      if(himsg.cnt)
      {
        color(col_globmsg);
        ftxt(hmarea[0], hmarea[1], himsg.txt);

        // cycle blue part of color:

        himsg.ccycle[0] += himsg.ccycle[1];

        if(himsg.ccycle[0] == 0xf)
            himsg.ccycle[1] = -1;
        else
        if(himsg.ccycle[0] <= 0xa)  // if one color cycle completed,
        {
            himsg.ccycle[1] = 1;

            if(himsg.cnt-- == 1)
            {
                // stop displaying, clear msg line
                himsg.cnt = 0;
                memset(himsg.txt, ' ', himsg.MAXLEN);
                himsg.txt[himsg.MAXLEN] = '\0';
                ftxt(hmarea[0], hmarea[1], himsg.txt);
            }
        }

        loadcol(col_globmsg, himsg.ccycle[0]);
      }
      else
      if(opt.showxinfos & 2)
      {
        // display current time

        ulong sessionMinutes(void);
        ulong roundSeconds(void);

        static time_t tt; time(&tt); tm *t = localtime(&tt);

        ulong rsec = roundSeconds();

        if(opt.showxinfos & 4)
            sprintf(tbuf, "%02d.%02d.%02d %2d:%02d:%02d %ld:%02ld %ld",
                    t->tm_mday, t->tm_mon+1, t->tm_year,
                    t->tm_hour, t->tm_min, t->tm_sec,
                    rsec / 60L, rsec % 60L,
                    sessionMinutes());
        else
            sprintf(tbuf, "%2d:%02d %ld:%02ld %ld",
                    t->tm_hour, t->tm_min,
                    rsec / 60L, rsec % 60L,
                    sessionMinutes());

        color(col_misc);
        loadcol(col_misc, 0x555);
        ftxt(hmarea[0] + (((40 - strlen(tbuf)) * cs0) >> 1), hmarea[1], tbuf);
      }
      else
      {
        loadcol(col_globmsg, 0x007);
        color(col_globmsg);
#ifdef  AMIGA
        sprintf(tbuf, "[%lu] ", AvailMem(0));
        ftxt(hmarea[0], hmarea[1], tbuf);
#else
        if(explib_root.mode & 2)    // if memtracking active
        {
            sprintf(tbuf, "[%lu] ", explib_root.MemTracker.allocated());
            ftxt(hmarea[0], hmarea[1], tbuf);
        }
#endif
      }
    }

    backcol(col_lmarea);

    // display bottom status lines:

    // * bike value lines: *

    ushort sec; // current section
    ushort l,c; // source line, column
    Bike*  bk;  // source bike

    ushort x,y; // destination x,y
    ushort xoffs;
    ushort x2,y2;

    for(sec=0; sec<8; sec++)    // eight sections
    {
        // setup source offsets

        switch(sec)
        {
            case 0 : bk = bike0; l = 0; c =  0; break;
            case 1 : bk = bike0; l = 1; c =  0; break;
            case 2 : bk = bike0; l = 2; c =  0; break;
            case 3 : bk = bike0; l = 3; c =  0; break;
            case 4 : bk = bike1; l = 0; c = 20; break;
            case 5 : bk = bike1; l = 1; c = 20; break;
            case 6 : bk = bike1; l = 2; c = 20; break;
            case 7 : bk = bike1; l = 3; c = 20; break;
        }

        // setup destination pos'n

        ifn(opt.hyperfield)
        {
            // simple playfield: 1:1 positions

            if(sec <= 3)
              x = msgarea[0] + ma4th + ((ma4th - cs0 * 19) >> 1);
            else
              x = msgarea[0] + (msgarea[2] >> 1) + ((ma4th - cs0 * 19) >> 1);

            y = msgarea[1] + 2 + charsize[1] * (sec & 3) + 4;
        }
        else
        {
            // large playfield:  mapped positions

            x = msgarea[0]; if((sec & 3) >= 2) x += charsize[0] * 20;

            y = msgarea[1] + 2 + charsize[1] * (sec & 1);

            if(sec >= 4) y += charsize[1] * 2;
        }

        // setup base color of section

        switch(sec & 3)
        {
            case 0 : scol = col_msg1; break;
            case 1 : scol = col_msg2; break;
            case 2 : scol = col_msg3; break;
            case 3 : scol = col_msg4; break;
        }

        if(     sec == 3 && (paintdark & 1)
            ||  sec == 7 && (paintdark & 2)
          )
            scol = col_msg1;

        ifn(bk->shields)
            scol = col_alarm;   // let whole dashboard blink!

        // text the section into the display:

        switch(sec)
        {
            case 0 :
            case 1 :
            case 4 :
            case 5 :

                // speed section:

                color(scol);
                buf[l][c + iSHD - 1] = '\0';
                ftxt(x + 0, y, &buf[l][c]);

                if(compact)
                {
                    // ammunition section :

                    if(bk->weapons.first()) // || bk->bul.xchrgcnt)
                        color(col_highlight);

                    buf[l][c + iAMO + 3] = '\0';
                    ftxt(x + charsize[0] * iAMO, y, &buf[l][c + iAMO]);
                }

                // points, sets & games section:

                color(scol);
                buf[l][c + 19] = '\0';
                ftxt(x + charsize[0] * iPTS, y, &buf[l][c + iPTS]);

                // shield section: if shields low, use alarm color

                ifn(bk->shields) color(col_alarm); else color(col_dbshd2);
                buf[l][c + iAMO - 1] = '\0';

                //if(!compact && (sec&1) && bk->shields < 10)
                //  xoffs = charsize[0];
                //else
                xoffs = 0;
                ftxt(x + charsize[0] * iSHD + xoffs, y, &buf[l][c + iSHD]);

                break;

            case 3 :
            case 7 :

                // other status lines: ftxt in one piece

                color(scol);
                buf[l][c + 19] = '\0';
                ftxt(x, y, &buf[l][c]);

                break;

            case 2 :
            case 6 :

                // graphical shields display:

                y  += charsize[1] >> 2;
                x2  = x + min((bk->shields<<2), 19) * charsize[0];
                y2  = y + (charsize[1]>>1);

                // rectfill doesn't include bottom-most line.

                if(x2 > x)
                {
                    rectfill(x,y+1, x2-1,y2, col_dbshd);
                    line(x, y, x2-1, y, col_dbshd2);    // lighter line
                    line(x,y2, x2-1,y2, col_dbshd3);    // darker  line
                }

                rectfill(x2,y, x + charsize[0] * 19,y2+1, col_lmarea);

                break;

        }   // endswitch section

    }   // endfor sections

#ifdef  WIN16
    if(!compact)
    {
        // display extended sections:

        color(col_misc);

        // display only things that changed.

        static XWeapon *pold[10] = {0,0,0,0,0,0,0,0,0,0};
        XWeapon        *pnow[10];
        static short    oldAmo[10]      = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
        static short    oldCharge[10]   = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};

        memset(pnow, 0, sizeof(pnow));

        if(bike0)
        {
            if(pnow[0] = bike0->weapons.first())
            if(pnow[1] = (XWeapon*)pnow[0]->next())
            if(pnow[2] = (XWeapon*)pnow[1]->next())
            if(pnow[3] = (XWeapon*)pnow[2]->next())
               pnow[4] = (XWeapon*)pnow[3]->next();
        }

        if(bike1)
        {
            if(pnow[5] = bike1->weapons.first())
            if(pnow[6] = (XWeapon*)pnow[5]->next())
            if(pnow[7] = (XWeapon*)pnow[6]->next())
            if(pnow[8] = (XWeapon*)pnow[7]->next())
               pnow[9] = (XWeapon*)pnow[8]->next();
        }

        // NOTE: - xwAsString handles NULL pointers,
        //       - xwDiffers handles NULL's and address differences,
        //         so if one of the xw's is an invalid old adress
        //         it will return true.

        short dx,dy;
        short cs1 = charsize[1] - 3;

        for(ushort i=0; i<10; i++)
        {
            if(i < 5)
            {
                dx = ((ma4th - cs0 * 20) >> 1);
                dy = cs1 * i;
            }
            else
            {
                dx = msgarea[2] - ma4th + ((ma4th - cs0 * 20) >> 1);
                dy = cs1 * (i - 5);
            }

            if(xwDiffers(pnow[i],pold[i],oldAmo[i],oldCharge[i]))
            {
                if(i==0||i==1||i==5||i==6)
                    loadcol(col_misc, 0x0ff);   // weapon 1 and 2
                else
                    loadcol(col_misc, 0xbbf);   // following weapons

                ftxt(msgarea[0]+dx, msgarea[1]+dy+6, xwAsString(pnow[i]));

                if(pnow[i])
                    oldAmo[i]   = pnow[i]->amo;
                else
                    oldAmo[i]   = -1;

                if(pnow[i])
                    oldCharge[i] = pnow[i]->xcharge();
                else
                    oldCharge[i] = -1;
            }

            pold[i] = pnow[i];
        }
    }
#endif

    backcol(col_backgrnd);
}

bool PlayField::ShowAnnouncement(bool textonly)
{_
 StringLib sl;
 int    sp0 = stat.points[0];
 int    sp1 = stat.points[1];
 uchar  infront;    // who's in front?
 int    dist = max(sp0,sp1) - min(sp0,sp1);
 char   *mstr;
 Bike*  bk0 = (Bike*)getobj(BIKE0);
 Bike*  bk1 = (Bike*)getobj(BIKE1);
 Bike   *fbk,*bbk;

    if(!bk0 || !bk1)    {SysErr(1003941119); return 0;}

    if(sp0 >= 3 || sp1 >= 3)
    {
      if(dist >= 1)
      {
        // one of the players might win the set!

        infront = (sp0 > sp1) ? 0 : 1;

        if(infront == 0)
        {   fbk = bk0; bbk = bk1;   }
        else
        {   fbk = bk1; bbk = bk0;   }

        mstr = " SET ";

        // may this also end the game?

        if(stat.sets[infront] == 2)
        {
            mstr = "MATCH";

            if(!textonly)
                playsound(SND_MATCHPNT, 1); // stereo once
        }
        else
        {
            if(!textonly && !opt.nomusic)
            {
                enum Sounds snd;

                // selectRandomSound must use alternative randomizer,
                // because this is NOT called in replay mode:

                snd = field.selectRandomSound(SND_SETBIKE0,SND_SETBIKE_MAX,1);
                // returns SND_MAX if nothing available in this block

                if(snd < SND_MAX)   // if any music at all available
                {
                    stopsound(SND_BKEXPL);  // now comes music
                    playsound(snd, 1);      // stereo once
                }
            }
            // else play nothing - hope textual announcement is played.
        }

        // temporarily display message string in the playfield:

        if(!textonly)   // if not 2nd ff. call
            loadcol(col_alarm, 0xf00);

        color(col_alarm);
        drawmode(NORMAL2);      // overlap = overwrite
        backcol(col_backgrnd);  // backcolor of text

        // show 'SET/MATCH BIKE' over front bike:

        text(fbk->pos[0]-charsize[0]*3 + (charsize[0] >> 1),
             fbk->pos[1]-charsize[1]*3, sl.printf("%s",mstr));

        text(fbk->pos[0]-charsize[0]*2,
             fbk->pos[1]-charsize[1]*3/2, "BIKE");

        // show 'DON'T PANIC' over back bike:

        text(bbk->pos[0]-charsize[0]*3 + (charsize[0] >> 1),
             bbk->pos[1]-charsize[1]*3, "DON'T");

        text(bbk->pos[0]-charsize[0]*3 + (charsize[0] >> 1),
             bbk->pos[1]-charsize[1]*3/2, "PANIC");

        backcol(col_backgrnd);  // reset to default

        return 1;
      }
      else
      {
        // the difference is zero, at least one reached 3 points,
        // so both have at least 3 points.
        // this means duce.

        if(!textonly && !opt.nomusic)
        {
            enum Sounds snd;

            snd = field.selectRandomSound(SND_DUCE0, SND_DUCE_MAX, 1);
            // returns SND_MAX if nothing available in this block

            if(snd < SND_MAX)   // if any music at all available
            {
                stopsound(SND_BKEXPL);  // now comes music
                playsound(snd, 1);      // stereo once
            }
        }
      }
    }
    else    // no one has >= 3 points
    {
        // play round intro sound
        if(!textonly && !opt.nomusic)
        {
            enum Sounds snd = SND_MAX;

            if(field.soundexists(SND_INTRO0))
                snd = field.selectRandomSound(SND_INTRO0, SND_INTRO_MAX, 1);
            else
            if(field.opt.alwaysmusic)
                snd = field.selectRandomSound(SND_SETBIKE0,SND_SETBIKE_MAX,1);

            if(snd < SND_MAX)   // if any music at all available
            {
                stopsound(SND_BKEXPL);  // now comes music
                playsound(snd, 1);      // stereo once
            }
        }
    }

    return 0;
}

void PlayField::ClearAnnouncement()
{_
 Bike* bk0 = (Bike*)getobj(BIKE0);
 Bike* bk1 = (Bike*)getobj(BIKE1);

    if(!bk0 || !bk1)    {SysErr(1704941512); return;}

    // no need to stop SND_MATCHBIKE, just played once
    // no need to stop SND_SETBIKE, just played once

    drawmode(NORMAL2);  // overlap = overwrite

    cpix x1,y1;

    x1 = bk0->pos[0]-charsize[0]*3;
    y1 = bk0->pos[1]-charsize[1]*3;
    rectfill(x1,y1, x1+charsize[0]*6,y1+charsize[1], col_backgrnd);

    y1 = bk0->pos[1]-charsize[1]*3/2;
    rectfill(x1,y1, x1+charsize[0]*6,y1+charsize[1], col_backgrnd);

    x1 = bk1->pos[0]-charsize[0]*3;
    y1 = bk1->pos[1]-charsize[1]*3;
    rectfill(x1,y1, x1+charsize[0]*6,y1+charsize[1], col_backgrnd);

    y1 = bk1->pos[1]-charsize[1]*3/2;
    rectfill(x1,y1, x1+charsize[0]*6,y1+charsize[1], col_backgrnd);
}

rcode PlayField::msg(char* txt, uchar waitfor, int minwait,bool callShowAnn)
{_
 char   buf[40];
 int    l=strlen(txt);
 int    mincnt = minwait / 50;  // in loop below, each cycle lasts 50 msec
 rcode  rc=OK;

    if(l>39)    {SysErr(3001941650); return FAILED;}

    memset(buf, ' ', sizeof(buf));
    buf[39] = '\0';
    strncpy(&buf[(40-l)/2], txt, l);

    // make himsg color initially dark:
    loadcol(col_globmsg, (uword)0);

    color(col_globmsg);
    drawmode(NORMAL2);
    backcol(col_msgback);   // black background for himessages
    text(hmarea[0], hmarea[1], buf);

    // do some color cycling:

    short   red=0xa;
    uchar   bdir=0;
    JoyStick    joy0(0),joy1(1);
    bool    p0,p1;

    while(1)
    {
        loadcol(col_globmsg, (red << 8));
        loadcol(col_alarm,   (red << 8));

#ifdef  WIN16
        backcol(col_msgback);
        text(hmarea[0], hmarea[1], buf);
#endif
        if(callShowAnn)
            // let announcement's color cycle
            ShowAnnouncement(1); // 1 == text only

        if(bdir)
        {   red++;  if(red > 14) bdir=0;    }
        else
        {   red--;  if(red < 8) bdir=1; }

        timer.endperiod();  // just the case

        for(int d=0; d<5; d++)
            timer.delay(10);    // delay blinking

        if(sysbreak())  {rc=FAILED; break;}

        if(mincnt)
            mincnt--;
        else
        {
            if(waitfor == 2)    break;  // don't wait for any button

            if(!num.humans)     break;  // no one at joysticks: don't wait

// #ifdef REPLAY
//          if(keyb.pressed(KEY_SPACE)) { rc = SPECIAL; break; }    // REPLAY
// #endif

            if(keyb.pressed('Z'))
            {
                // display background w/o big explosion:
                copyFromBackMap();
                Bike *bk;
                if((bk = (Bike *)getobj(BIKE0)) && bk->hasshape()) bk->shape().plot();
                if((bk = (Bike *)getobj(BIKE1)) && bk->hasshape()) bk->shape().plot();
            }

            if(keyb.pressed('U'))
            {
                pcol c;
                cpix x,y;

                // PixelDisplay framebuffer visual check:
                for(y = bord[1]; y < bord[3]; y++)
                for(x = bord[0]; x < bord[2]; x++)
                    if(c = fbuf.get(x,y))
                        PixelDisplay::plotby(x,y,c);
            }

            p0  = 0;
            p1  = 0;

            if(num.humans == 1)
            {
                if(field.opt.swapsticks)
                    p0 = joy1.read();
                else
                    p0 = joy0.read();
            }
            else
            if(num.humans == 2)
            {
                // two humans: read both joysticks
                p0 = joy0.read();
                p1 = joy1.read();
            }

            if((waitfor == 0) && (p0 || p1))
                break;      // any button pressed

            if(waitfor == 1)
            {
                if((num.humans == 1) && p0)
                    break;  // single player pressed button

                if((num.humans == 2) && p0 && p1)
                    break;  // both players pressed button
            }

            /*
            if(waitfor == 1 && num.humans > 1)
            {   if(p0 && p1)    break;  }   // return if both buttons prezd
            else
            {
                // test active joystick's button
                if(     (p0 && !field.opt.swapsticks)
                    ||  (p1 &&  field.opt.swapsticks) ) break;
            }
            */
        }
    }

    // clear message line:
    memset(buf, ' ', sizeof(buf));
    buf[39] = '\0';
    backcol(col_msgback);
    text(hmarea[0], hmarea[1], buf);

    backcol(col_backgrnd);  // reset to default

    // reset himsg color:
    loadcol(col_globmsg, startcols[col_globmsg]);

    return  rc;
}

rcode PlayField::resetobjs()
{_
 PlayObj* po;
 PlayMsg  pm;

    pm.type = PMT_RESET;
    pm.src  = 0;

    for(po=first(); po; po=po->next())
        po->step(&pm);

_   return OK;
}

//  ================= global happening handling =================

rcode PlayField::do_gml()
{_
 PlayMsg* pm;

    for(pm=gml.first(); pm; pm=pm->next())
    {
        if(pm->delay > 0)
        {
            // count-down the message's lifetime before
            // taking action:

            if((pm->delay -= CYCLE_MS) < 0)

                pm->delay = 0;
        }
        else
        {
            if(pm->type == PMT_TERMCRASH)

                return  TerminalCrash();
        }
    }

    return OK;
}

rcode PlayField::TerminalCrash()
{_
 rcode  rc=OK,mrc=OK;
 uchar  crashed=0;
 char   *mstr,buf[80];
 bool   game_won = 0;
 ObjCodes oc;
 PlayMsg* pm;
 bool   doscore = !(opt.noscoring | replay);
 short  delta=0;

    // one or both bikes exploded, the round has ended.

#ifdef REPLAY
    ifn(replay)
    {
        // recording mode:
        // make sure pipe recordings are stored completely

        if(     joystick.pipe0
            &&  joystick.pipe1
            &&  PerfReqPipe )
        {
            joystick.pipe0->flush();    // write buffers
            joystick.pipe1->flush();    // write buffers
            PerfReqPipe->flush();       // write buffer
        }
        else
            SysErr(105951452);
    }
#endif

    // there may be several obj's at once involved in crash(es).
    // check if one or both players are involved,
    // by scanning the whole gml queue:

    for(pm=gml.first(); pm; pm=pm->next())
    {
        oc  = pm->src->id.code;

        if(oc == BIKE0 && pm->type==PMT_TERMCRASH)  crashed |= 1;
        if(oc == BIKE1 && pm->type==PMT_TERMCRASH)  crashed |= 2;
    }

    if(crashed == 1)
    {
        mstr="PLAYER 1 CRASHED";
        if(doscore) stat.points[1]++;

        if(!replay) {   justice.beats++;    justice.beaten[0]++;    }

        rc  = FAILED;
    }
    else
    if(crashed == 2)
    {
        mstr="PLAYER 2 CRASHED";
        if(doscore) stat.points[0]++;

        if(!replay) {   justice.beats++;    justice.beaten[1]++;    }

        rc  = FAILED;
    }
    else
    if(crashed == 3)
    {
        mstr="BOTH PLAYERS CRASHED";
        rc  = FAILED;
    }

    if(!replay && justice.active && justice.beats >= 5)
    {
        // if one of the players is hopelessly weaker than the other,
        // give him 'support', e.g. by increased shield number.

        if(justice.beaten[0]==5 || justice.beaten[1]==5)    delta = 2;
        if(justice.beaten[0]==4 || justice.beaten[1]==4)    delta = 1;

        if(delta)
        {
            if(justice.beaten[0] > justice.beaten[1])
            {
                if((justice.support[1] -= delta) < 0)
                {
                    justice.support[0] -= justice.support[1];
                    justice.support[1]  = 0;
                }
            }
            else
            {
                if((justice.support[0] -= delta) < 0)
                {
                    justice.support[1] -= justice.support[0];
                    justice.support[0]  = 0;
                }
            }
        }

        justice.beats       = 0;
        justice.beaten[0]   = 0;
        justice.beaten[1]   = 0;

        // 'support' will be evaluated in Bike constructor
    }

    if(crashed)
    {
        // display crash information

        // scoring: who first gets 4 points wins,
        //          but only if his distance to the other
        //          is at least 2 points;
        //          if not, battle takes on until a 2-subpt dist.
        //          is reached.

        int sp0 = stat.points[0];
        int sp1 = stat.points[1];
        int dist = max(sp0,sp1) - min(sp0,sp1);
        bool set=0;

        if(sp0 >= 4 || sp1 >= 4)
        {
            if(dist >= 2)
            {
                // === SET IS OVER: ===

                buf[0] = '\0';

                if(sp0 > sp1)
                {
                    stat.sets[0]++;

                    sprintf(buf, "SET FOR PLAYER ONE. SCORE NOW ");
                }
                else
                if(sp1 > sp0)
                {
                    stat.sets[1]++;

                    sprintf(buf, "SET FOR PLAYER TWO. SCORE NOW ");
                }
                else SysErr(502942240);

                if(stat.sets[0] == 3 || stat.sets[1] == 3)
                    game_won = 1;

                playsound(SND_APPLAUSE, 1); // in stereo

                // display overall sets:

                sprintf(&buf[strlen(buf)], "%ld : %ld",
                    stat.sets[0], stat.sets[1]);

                mstr = buf;

                // new set begins:

                stat.points[0] = stat.points[1] = 0;
            }
            else
            {
                silence();

                if(sp0 > sp1)
                {
                    mstr = "ADVANTAGE PLAYER ONE";
                    playsound(SND_ADVTPLR1, 1);
                }
                else
                if(sp1 > sp0)
                {
                    mstr = "ADVANTAGE PLAYER TWO";
                    playsound(SND_ADVTPLR2, 1);
                }
                else
                {
                    mstr = "DUCE";
                    playsound(SND_DUCETEXT, 1);
                }
            }
        }

        showstatus();

        if(game_won)
            msg(mstr, 2, 3000); // delay 3 sec but don't wait for buttons
        else
            mrc = msg(mstr);
    }

    // has one of the players won the game?

    if(game_won)
    {
        // from set termination, "applause" is now playing

        silence();

#ifdef  AMIGA
        Delay(25);
#endif

        if(stat.sets[0] == 3)
        {
            sprintf(buf, "PLAYER ONE WINS MATCH %ld : %ld",
                stat.sets[0], stat.sets[1]);

            stat.games[0]++;
        }
        else
        {
            sprintf(buf, "PLAYER TWO WINS MATCH %ld : %ld",
                stat.sets[1], stat.sets[0]);

            stat.games[1]++;
        }

        extern uchar HUGE * TheEndSound;

#ifdef  WIN16
        if(TheEndSound)
        {
            CloseChanMan(); // if it's not open, nothing happens

            sndTrySound((LPCSTR)TheEndSound,
                SND_LOOP|SND_ASYNC|SND_NODEFAULT|SND_MEMORY);
        }
        else
#endif
            playsound(SND_MATCHEND, 1, 0);  // stereo, permanent

        showstatus();

        mrc = msg(mstr, 0, 500);    // display at least 0.5 sec's

#ifdef  WIN16
        if(TheEndSound)
        {
            sndStopSound();

            OpenChanMan(p_snd[SND_PLAINGF]->waveinfo(), opt.stereo);
            // if it can't open, nothing happens
        }
        else
#endif
            stopsound(SND_MATCHEND);

        stat.sets[0] = stat.sets[1] = 0;
    }

#ifdef REPLAY
    // START REPLAY?
    if(mrc == SPECIAL)  // if magic key was pressed in 'msg'
        replay  = 1;    // then set replay mode
    else
#endif
        replay  = 0;    // else stop replaying, if any

    return rc;
}

enum Sounds PlayField::selectRandomSound(
    enum Sounds esBlockStart,
    enum Sounds esMaxBlockEnd,
    bool useAltRand
    )
{_
    if(     esBlockStart > esMaxBlockEnd
        ||  esBlockStart >= SND_MAX
        ||  esMaxBlockEnd >= SND_MAX    )
    {   SysErr(21039518049); return SND_MAX;    }

    // select from available BKNOSH sounds:

    int nsnd=0, isnd;

    for(isnd  = esBlockStart;
        isnd <= esMaxBlockEnd;
        isnd++, nsnd++)

        if(!field.soundexists((enum Sounds)isnd))
            break;

    if(nsnd)
    {
        enum Sounds snd;

        if(useAltRand)
            snd = (enum Sounds)((altrand() % nsnd) + esBlockStart);
        else
            snd = (enum Sounds)((SNDRAND() % nsnd) + esBlockStart);

        if(     snd < esBlockStart
            ||  snd > esMaxBlockEnd )
        {   SysErr(2103951811); return SND_MAX; }

        return snd;
    }

    return SND_MAX; // nothing available in block
}

#define xin(x) (x >= bord[0] && x < bord[2])
#define yin(y) (y >= bord[1] && y < bord[3])

rcode PlayField::clipline(cpix pos1[2], cpix pos2[2])
{
    cpix pin[2], poff[2];

    if(!inbord(pos1))
    {
        if(!inbord(pos2))
            return FAILED;  // one point must be inside
        poff[0] = pos1[0];
        poff[1] = pos1[1];
        pin[0]  = pos2[0];
        pin[1]  = pos2[1];
    }
    else
    if(!inbord(pos2))
    {
        if(!inbord(pos1))
            return FAILED;  // one point must be inside
        poff[0] = pos2[0];
        poff[1] = pos2[1];
        pin[0]  = pos1[0];
        pin[1]  = pos1[1];
    }
    else
        return OK;  // no clipping necessary

    long dxoff  = poff[0] - pin[0];
    long dyoff  = poff[1] - pin[1];
    if(!dxoff) dxoff = 1;
    if(!dyoff) dyoff = 1;

    long bordx  = (poff[0] < pin[0]) ? bord[0] : (bord[2]-1);
    long bordy  = (poff[1] < pin[1]) ? bord[1] : (bord[3]-1);
    long dxin   = bordx - pin[0];
    long dyin   = bordy - pin[1];

    long dy1    = pin[1] + dxin * dyoff / dxoff;
    long dx1    = pin[0] + dyin * dxoff / dyoff;
    long dy2    = pin[1] + dyin;
    long dx2    = pin[0] + dxin;

    pos1[0] = pin[0];
    pos1[1] = pin[1];

    if(yin(dy1) && xin(dx2))
    {
        pos2[0] = dx2;
        pos2[1] = dy1;
        return OK;
    }
    else
    if(xin(dx1) && yin(dy2))
    {
        pos2[0] = dx1;
        pos2[1] = dy2;
        return OK;
    }

    return FAILED;
}
