/*   _____________________________________________________________
    /                                                             \
    | File: playfield.cpp   - playfield code
    \_____________________________________________________________/

    contents:

        code for

            PlayField
            Reporter
            PlayObj
            BlastCom

    Developers: ID: Name:
    =========== --- -----
                JTH Juergen Thumm
  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\
 yymmdd ------- --- ---------------------------------------------------------
 940303 1.22    JTH created
 940316             performance optimized spreading of PC checkpoints ('_')
 940404 1.34        perfmon: no further ignore of showstatus()
 940407 1.35        replay mode
 940409 1.351       OpenFastGfx, CloseFastGfx, fasttext()
 940412 1.352       replay now supported also after button click past crash
 940417 1.36        dashboards in hyperfield now under each playfield sectn
 940803 1.37        'justice' support in PlayField::TerminalCrash()
 950916             PF::loadSound: adapted to targspeed usage.
 951119 1.96        added ShapeClassData table, PF::open/close p_scl stuff
*/

#   define  PLAYFIELD_CPP
#   include "global.h"
#   include "shape.h"
#   include "term.h"
#   include "texter.h"

#include <stdarg.h>

#define XWASS XWA_SHAPESIZE

struct ShapeClassData {

    char   *fnamebase;  // fullname = fnamebase + "x.bmp", x = 0..99
    ushort  nfaces;
    cpix    maxsize;
    ushort  nrot;
    ushort  nzoom;
    ulong   flags;

    } scl_data[SCL_MAX] = {

    // filename      nfaces size nrot nzoom flags
    { "gfx\\tback",  1,     64,   1,  1,    ShapeClass::JUSTVIDEO },
    { "gfx\\bike",   2,     16,  16,  1,    0     },
    { "gfx\\xweap", 10,  XWASS,   1,  1,    0     },
    { "gfx\\wall",   5,    128,   3,  1,    0     },
    { "gfx\\tree",   1,     64,   1,  1,    0     },
    { "gfx\\back",   2,    128,   1,  1,    ShapeClass::JUSTVIDEO },
    { "gfx\\bazo",   1,     16,  32,  1,    0     },
    { "gfx\\mgcrat", 2,     32,   1,  1,    ShapeClass::JUSTVIDEO },
    { "gfx\\pgcrat", 1,     32,   1,  1,    ShapeClass::JUSTVIDEO },
    { "gfx\\rcrat",  1,     32,   1,  1,    ShapeClass::JUSTVIDEO },
    { "gfx\\mscrat", 1,     32,   1,  1,    ShapeClass::JUSTVIDEO },
    { "gfx\\xwswit", 1,     16,   1,  1,    0     }

    };

void pokeShape( ushort  shapenumber,
                ushort  xfaces,
                cpix    xmaxsize,
                ushort  xnrot,
                ushort  xnzoom,
                ulong   xflags  )
{
    if(shapenumber < SCL_MAX)
    {
        scl_data[shapenumber].nfaces    = xfaces;
        scl_data[shapenumber].maxsize   = xmaxsize;
        scl_data[shapenumber].nrot      = xnrot;
        scl_data[shapenumber].nzoom     = xnzoom;
        scl_data[shapenumber].flags     = xflags;
    }
}

//|| SOUNDNAMES
char *PlayField::sndfname[SND_MAX] = {

    // === BASE PACKAGE SOUNDS ===

    "plaingf",      // plain gun fire       (fatgun2)
    "plaingh",      // plain gun hit        (gun)

    "vtpass",       // vtunnel passing      (lsidle)
    "bkexpl",       // bike explodes        (jabexpl)

    "bazof",        // bazooke fired        (planefly)
    "bazoh",        // bazooka hit          (swush3)

    "mislf",        // missile fire         (misllnch)
    "mislh",        // missile hit          (mislimp)

    "katshaf1",     // katjusha fire
    "katshaf2",     // variation
    "katshah",      // katjusha hit

    "xweapc",       // extra weapon collected   (reload)

    "matchend",     // end of match         (music)
    "applause",     // applause             (applause)

    "bknoshds",     // bike has no shields  (buzzer)
    "flamef",       // flame firing         (flame)

    "advtplr1",     // advantage player1
    "advtplr2",     // advantage player2
    "ducetext",     // duce textual, i.e. spoken
    "matchpnt",     // match point announce

    "plainmgf",     // machine gun firing
    "plainmge",     // plain mg empty
    "fatmgf",       // fat mg firing
    "fatmge",       // fat mg empty

    "bkhwall",      // bike hits wall       (glascrsh)
    "bkhobst",      // bike hits obstacle   (glascsh2)
    "bkstart",      // bike starts
    "bkstop",       // bike stops
    "bktyres",      // bike's tyres         (break)

    "bkdrive1",     // bike driving speedlevel 1
    "bkdrive2",     // bike driving speedlevel 2

    "canf1",        // cannon firing
    "canf2",        // variation
    "canf3",        // variation
    "canf4",        // variation
    "cane",         // cannon empty

    "pgunfr",       // pumpgun fire & reload
    "pgunfe",       // pumpgun fire & empty

    "oilf",         // oil 'firing'         (squirt)

    "lsopen",       // lightsword open
    "lsclose",      // ls close
    "lse",          // ls empty
    "lsh1",         // ls hit 1
    "lsh2",         // ls hit 2
    "lsh3",         // ls hit 3
    "lshbk",        // hitting a bike
    "lsmove1",      // lightsword circular movement
    "lsmove2",      // variation | in a
    "lsmove3",      // variation/  sequence!

    "bkhbord",      // bike hits border

    "htpassl",      // htunnel pass lchan   (shpassl)
    "htpassr",      // htunnel pass rchan   (shpassr)

    "plus2",        // weapon ++
    "plus3",        // weapon +++
    "plus4",        // weapon ++++
    "plus5",        // weapon +++++

    "pgunhxs",      // pump gun hit extra strong    (scream)
    "lsplus2",      // light sword ++       (jablaugh)

    "nitroxpl",     // nitro explosion      (tntexpl)
    // "htopen",    // htunnel open         (gateopen)
    // "htclose",   // htunnel close        (gateclose)
    "bkwcycle",     // bike weapon cycling
    "termcrt",      // terminator creation
    "termfoc1",     // terminator focuses on victim

    "setbk0",       // setbike sound
    "setbk1",       // variation |
    "setbk2",       // variation | in
    "ext/setbk3",   // variation | a
    "ext/setbk4",   // variation | sequence!
    "ext/setbk5",   // variation | first
    "ext/setbk6",   // variation | unavailable sound
    "ext/setbk7",   // variation | ends
    "ext/setbk8",   // variation | block!
    "ext/setbk9",   // variation |
    "ext/setbk10",  // variation |
    "ext/setbk11",  // variation |
    "ext/setbk12",  // variation |
    "ext/setbk13",  // variation |
    "ext/setbk14",  // variation |
    "ext/setbk15",  // variation |
    "ext/setbk16",  // variation |
    "ext/setbk17",  // variation |
    "ext/setbk18",  // variation |
    "ext/setbk19",  // variation |
    "ext/setbk20",  // variation |
    "ext/setbk21",  // variation |
    "ext/setbk22",  // variation |
    "ext/setbk23",  // variation |
    "ext/setbk24",  // variation |
    "ext/setbk25",  // variation |
    "ext/setbk26",  // variation |
    "ext/setbk27",  // variation |
    "ext/setbk28",  // variation |
    "ext/setbk29",  // variation |
    "ext/setbk30",  // variation |
    "ext/setbk31",  // variation |
    "ext/setbk32",  // variation |
    "ext/setbk33",  // variation |
    "ext/setbk34",  // variation |
    "ext/setbk35",  // variation |
    "ext/setbk36",  // variation |
    "ext/setbk37",  // variation |
    "ext/setbk38",  // variation |
    "ext/setbk39",  // variation |
    "ext/duce0",    // duce, set bike was not won
    "ext/duce1",    // variation |
    "ext/duce2",    // variation |
    "ext/duce3",    // variation |
    "ext/duce4",    // variation |
    "ext/duce5",    // variation |
    "ext/duce6",    // variation |
    "ext/duce7",    // variation |
    "ext/duce8",    // variation |
    "ext/duce9",    // variation/

    "bknosh0",      // bike out of shields
    "bknosh1",      // variation |
    "bknosh2",      // variation | in
    "bknosh3",      // variation | a
    "bknosh4",      // variation | sequence!
    "bknosh5",      // variation | first
    "bknosh6",      // variation | unavailable sound
    "bknosh7",      // variation | ends
    "bknosh8",      // variation | block!
    "bknosh9",      // variation |
    "ext/bknosh10", // variation | (extension)
    "ext/bknosh11", // variation |
    "ext/bknosh12", // variation |
    "ext/bknosh13", // variation |
    "ext/bknosh14", // variation |
    "ext/bknosh15", // variation |
    "ext/bknosh16", // variation |
    "ext/bknosh17", // variation |
    "ext/bknosh18", // variation |
    "ext/bknosh19", // variation |
    "ext/bknosh20", // variation |
    "ext/bknosh21", // variation |
    "ext/bknosh22", // variation |
    "ext/bknosh23", // variation |
    "ext/bknosh24", // variation |
    "ext/bknosh25", // variation |
    "ext/bknosh26", // variation |
    "ext/bknosh27", // variation |
    "ext/bknosh28", // variation |
    "ext/bknosh29", // variation |
    "ext/bknosh30", // variation |
    "ext/bknosh31", // variation |
    "ext/bknosh32", // variation |
    "ext/bknosh33", // variation |
    "ext/bknosh34", // variation |
    "ext/bknosh35", // variation |
    "ext/bknosh36", // variation |
    "ext/bknosh37", // variation |
    "ext/bknosh38", // variation |
    "ext/bknosh39", // variation |
    "ext/bknosh40", // variation |
    "ext/bknosh41", // variation |
    "ext/bknosh42", // variation |
    "ext/bknosh43", // variation |
    "ext/bknosh44", // variation |
    "ext/bknosh45", // variation |
    "ext/bknosh46", // variation |
    "ext/bknosh47", // variation |
    "ext/bknosh48", // variation |
    "ext/bknosh49", // variation/

    "ext/intro0"  , // start of a new round
    "ext/intro1"  , // variation |
    "ext/intro2"  , // variation |
    "ext/intro3"  , // variation |
    "ext/intro4"  , // variation |
    "ext/intro5"  , // variation |
    "ext/intro6"  , // variation |
    "ext/intro7"  , // variation |
    "ext/intro8"  , // variation |
    "ext/intro9"  , // variation/

    "ext/lostsh20", // lost two shields
    "ext/lostsh21", // variation |
    "ext/lostsh22", // variation |
    "ext/lostsh23", // variation |
    "ext/lostsh24", // variation |
    "ext/lostsh25", // variation |
    "ext/lostsh26", // variation |
    "ext/lostsh27", // variation |
    "ext/lostsh28", // variation |
    "ext/lostsh29", // variation |
    "ext/lostsh2a", // variation |
    "ext/lostsh2b", // variation |
    "ext/lostsh2c", // variation |
    "ext/lostsh2d", // variation |
    "ext/lostsh2e", // variation |
    "ext/lostsh2f", // variation |
    "ext/lostsh2g", // variation |
    "ext/lostsh2h", // variation |
    "ext/lostsh2i", // variation |
    "ext/lostsh2j", // variation/
    "ext/lostsh30", // lost three shields
    "ext/lostsh31", // variation |
    "ext/lostsh32", // variation |
    "ext/lostsh33", // variation |
    "ext/lostsh34", // variation |
    "ext/lostsh35", // variation |
    "ext/lostsh36", // variation |
    "ext/lostsh37", // variation |
    "ext/lostsh38", // variation |
    "ext/lostsh39", // variation |
    "ext/lostsh3a", // variation |
    "ext/lostsh3b", // variation |
    "ext/lostsh3c", // variation |
    "ext/lostsh3d", // variation |
    "ext/lostsh3e", // variation |
    "ext/lostsh3f", // variation |
    "ext/lostsh3g", // variation |
    "ext/lostsh3h", // variation |
    "ext/lostsh3i", // variation |
    "ext/lostsh3j", // variation/

    "repow1",       // (hateflow)
    "ext/repow2",   // (powerful)
    "ext/repow3",   // (imperal)

    "ext/bkhwoil",  // bike hits wall with oil  (brainhrt)
    "ext/bkhmg",    // bike hit by mg           (confess)
    "ext/bkhflame", // bike hit by flame        (hotnhere)

    "ext/mgun4f1",  // mgun 4+ firing
    "ext/mgun4f2",  // variation | in a
    "ext/mgun4f3",  // variation/  sequence
    "ext/mgun4e",   // mgun 4+ empty

    "ext/bazoc",    // bazooka collect
    "ext/amoc",     // ammunition collect (default sound)
    "ext/lswordc",  // lsword collect
    "ext/mgunc",    // mgun collect
    "ext/pgunc",    // pump gun collect
    "ext/shieldc",  // shield collect
    "ext/mlaunc",   // mlauncher collect
    "ext/katshac",  // katjuscha collect
    "ext/oilc",     // oil collect
    "ext/fthrowc",  // fthrower collect

    "ext/mgamoc",   // mgun      ammo collect
    "ext/pgamoc",   // pgun      ammo collect
    "ext/ftamoc",   // fthrower  ammo collect
    "ext/lsamoc",   // lsword    ammo collect
    "ext/mlamoc",   // mlauncher ammo collect
    "ext/kamoc",    // rlauncher ammo collect

    "ext/mlaune",   // rocket launcher empty
    "ext/katshae",  // katjusha empty
    "ext/fthrowe",  // flame thrower empty

    };

int PlayField::sndparm[SND_MAX][2] = {

// speed    volume(percentage of system volume)

    10000,  -1, // harfe
    10000,  -1, // eug
    10000,  -1, // smarte
     5000,  -1, // expl
     7000,  -1, // laserss
     8000,  -1, // gun
     5000,  -1, // arrow
     3000,  -1, // expl2
     5593,  -1, // dog
     9000,  -1, // duce
     9000,  -1, // advtg plr1
     9000,  -1, // advtg plr2
    10000,  60, // livesitb as music after someone's won
    13558,  -1, // thunder e.g. when collecting something
    10000,  -1, // applause
    11000,  -1, // buzzer
    10000,  -1, // flame
    10000,  -1, // set bike announcement
    12000,  -1, // panflute
    10000,  -1, // chgweapon
    19886,  -1, // mister manic
    10054,  -1, // laughter
     5013,  -1, // machine gun
     4500,  -1, // quadro plus weapon

    };

//|| COLORS_2
uword PlayField::startcols[PF_NUMPRESCOLS] = {

    0x008,  // BACKGROUND COLOR
    0x009,  // brighter backgrnd color
    0x007,  // darker   backgrnd color

    0x000,  // message areas (and fasttext) backgrnd color

    0xff0,  // bike 0's track
    0xf00,  // bike 1's track
    0xff8,  // robot bike's track head
    0xf4f,  // last four 5th's of a temporar track

    // Obstacles:

    0xfb6,  // flame thrower's flames
    0xfa0,  // fire
    0xf84,  // wave explosion
    0xaaa,  // fixwalls
    0x888,  // dust
    0xfa0,  // missile impact
    0x55f,  // passive xweapon
    0x0f0,  // shape0
    0xf00,  // shape1
    0xfa0,  // mgun impact explosion    (cycled)

    // Non-obstacles:

    0x8f8,  // light sword0
    0xf88,  // light sword1
    0xafa,  // flying bazooka
    0xff0,  // horizontal tunnel
    0xf44,  // bullets
    0x999,  // border around race area
    0x00a,  // global messages (top line) (will be cycled)
    0xbbb,  // msg area line 1
    0x0f0,  // msg area line 2
    0x00a,  // msg area line 3 (primary weapon)
    0xc00,  // msg area line 4 (messages)
    0xf00,  // alarm message color (blinking red while playing)
    0x080,  // smart explosion (set by object)
    0xa0a,  // big explosion   (" " ")
    0x0f0,  // highlighted message area entries
    0x0dd,  // shield text in dashboards
    0x0ff,  // shield text in dashboards, lighter
    0x0bb,  // shield text in dashboards, darker

    0xf0f,  // bike's vector direction cursor
    0xfaa,  // bike 0 target laser
    0xfaa,  // bike 1 target laser
    0xfaa,  // any other bike's target laser
    0xff0,  // pump gun explosion (set by object)
    0x743,  // oil

    0xddd,  // fixwall brighter margin
    0x888,  // fixwall darker   margin

    0x090,  // green grass
    0xa80,  // nitro glycerine
    0x909,  // xweapon switch
    0xff4,  // missile
    0xff8,  // rocket

    0x888,  // msg area border or background
    0x111,  // miscellaneous color, load on use
    0x666,  // shadow color

    0xfff,  // white

    0x001,  // shades of blue
    0x002,
    0x003,
    0x004,
    0x005,
    0x006,
    0x00a,
    0x00b,
    0x00c,
    0x00d,
    0x00e,

    };

PlayField::PlayField()

    :   PASSED_HTUNNEL  (1),
        PASSED_VTUNNEL  (2),

        CYCLE_MS    (10)

{_
    dead=1;

    memset(&opt, 0, sizeof(opt));

    num.humans      = 2;
    num.robots      = 0;
    num.players     = num.humans + num.robots;

    opt.swapsticks  = 0;
    opt.nomusic     = 0;
    opt.stereo      = 1;
#ifndef DEMO
    opt.khz         = 32;
#else
    opt.khz         = 22;
#endif
    taddspeed       = 1;        // additional timing speed
    opt.cxw         = 0xFFFFUL; // crt all xw's except fthrower
    opt.lotsof      = 0;
    opt.playmode    = 0;
    opt.magic       = 0;
    opt.showxinfos  = 0;
    opt.debugmode   = ZDEB_SHOWBITMAPS;

    // unselectable stuff:

    opt.emulstick   = 0;
    opt.notracks    = 1;    // uses shapes if set
    opt.fattracks   = 0;    // only if tracks active

    opt.blastoff    = 1;
    replay          = 0;    // not currently in replay mode
    volume          = 100;

    memset(&stat, 0, sizeof(stat)); // reset no. of games, sets, points

    istat.npo       = 0;    // no entries in PlayObj list
    istat.memory    = 0;    // nothing yet allocated

    opt.openwidth   = 800;  // default playfield width
    opt.openheight  = 600;  // default playfield height
    opt.hyperfield  = 0;    // by default, no extra large playfield

#ifdef  WIN16
    opt.swappixcol  = 0;    // swap red/blue with SetPixel()
    opt.usewinpal   = 1;    // use palette by default
#endif

    opt.tlaser      = 3;     // fat wide arrows by default

    opt.playsoundenabled= 1; // playsound() does something by default

    termpower.initial   =  30;
    termpower.increment = termpower.initial / 20;
    termpower.maxval    = 350;
    termpower.current   =   0;

    walksucptr  = 0;    // not set by default, only used in stepobjs()
    memset(getcache, 0, sizeof(getcache));  // getobj() cache init

    memset(&sf, 0, sizeof(sf)); // reset status field stuff

    ptexter = 0;

    if(xwtfield.isdead())
        return;

    dead        = 0;
}

PlayField::~PlayField()
{_p (dead, -1, -1, -1);

    dead = 1;
}

ShapeClass &PlayField::shapeclass(enum EShapeClass escl)
{
    if(escl >= SCL_MAX) {SysErr(1911951734); return *p_scl[0];}
    return *p_scl[escl];
}

bool PlayField::hasshapeclass(enum EShapeClass escl)
{
    if(escl >= SCL_MAX) {SysErr(612951943); return 0;}
    return p_scl[escl] ? 1 : 0;
}

rcode PlayField::open()
{_
    memset(p_snd, 0, sizeof(p_snd)); // set all ptrs to NULL
    memset(p_scl, 0, sizeof(p_scl)); // "

    for(ushort isnd=0; isnd<SND_MAX; isnd++)

        ifn(p_snd[isnd] = new Sound)
        {
            perr("cannot alloc sound instances");
            return MEMORY;
        }

#ifdef  AMIGA
 extern rcode OpenFastGfx(Screen* scr);

    // we always have overscan, i.e. 320 + 16 pixels.

    if(opt.openwidth < 336) opt.openwidth = 336;

    // make openwidth a multiple of 16:

    opt.openwidth   &= (0xFFFF ^ 15);

    if(AmigaDisplay::open(opt.openwidth, opt.openheight, PF_DEPTH))
    {
        printf("OUT OF MEMORY - can't open display\n");
        return MEMORY;
    }

    if(OpenFastGfx(scr))    // init fast gfx routines for opened screen
        return  FAILED;

    loadcolors(PF_NUMPRESCOLS, startcols);  // set predefined colors
#else

    if(PixelDisplay::open( opt.openwidth, opt.openheight, PF_DEPTH,
                           PIXD_NIL,
                           PF_NUMPRESCOLS, startcols // set predefined colors
                         ))
        return MEMORY;

#endif

    hmarea[0]   = 0;
    hmarea[1]   = 0;
    hmarea[2]   = hwsize[0];
    hmarea[3]   = charsize[1] + 2;

    if(hwsize[0] >= 600)
    {
        hmarea[2]   = charsize[0] * 40;
        hmarea[0]   = (hwsize[0] - hmarea[2]) / 2;
    }

    bord[0] = hwfrm[0] + 1 + opt.hyperfield ? 2 : 0;
    // the additional 2 in hyperfield case is a workaround for
    // AmigaDisplay which can't currently display leftmost column
    // if in horizontal scroll mode.
    bord[1] = hmarea[1] + hmarea[3] + 1;
    bord[2] = hwsize[0] - 1;
    bord[3] = hwsize[1] - (charsize[1]+1)*4;    // leave room for 4 lines of text

#ifdef  WIN16
    bord[3] -= charsize[1]; // to fix lowest-textline display problem
#endif

    if(opt.hyperfield)  bord[3] -= 8;   // blankline for clear overscan

    size[0] = bord[2] - 1 - bord[0];    // x size of playfield
    size[1] = bord[3] - 1 - bord[1];    // y size of playfield
    sizemax = max(size[0],size[1]);     // needed for relative distances

    pavpos[0]   = bord[1]-1;
    pavpos[1]   = 255 - 4;  // video display line, NOT memory line!
    pavpos[2]   = pavpos[0] + (pavpos[1] - pavpos[0]) / 2;

    msgarea[0]  = 0;
    msgarea[1]  = bord[3] + 2;
    msgarea[2]  = hwsize[0];
    msgarea[3]  = hwsize[1] - msgarea[1];   // bord[3];

    // calc vtunnel's x positions:

    vtun[0][0]  = hwsize[0]/4;              // vtunnel 0 x0
    vtun[0][1]  = vtun[0][0] + hwsize[0]/20;    // vtunnel 0 x1
    vtun[1][0]  = hwsize[0]*3/4;                // vtunnel 1 x0
    vtun[1][1]  = vtun[1][0] + hwsize[0]/20;    // vtunnel 1 x1

    htun[0] = htun[1] = 0;  // set to default. see HTunnel constructor

#ifdef  AMIGA
    // create display sections for soft scroll support:

    AmDSection* sec;

    // top section: player 1 playfield area

    ifn(sec = new AmDSection(0, pavpos[0]))     return MEMORY;
    sec->SetMemPos(0, bord[1]-1);   // by default, same mempos as displaypos
    AddDSection(sec);

    // separator section: player 1 dash-board

    ifn(sec = new AmDSection(0, pavpos[2]-9))   return MEMORY;
    sec->SetMemPos(0, msgarea[1]);
    AddDSection(sec);

    // middle section: player 2 playfield area

    ifn(sec = new AmDSection(0, pavpos[2]+10))  return MEMORY;
    sec->SetMemPos(0, bord[1]-1);
    AddDSection(sec);

    // bottom section: player 2 dash-board

    ifn(sec = new AmDSection(0, pavpos[1]))     return MEMORY;
    sec->SetMemPos(0, msgarea[1] + 2 + 16);
    AddDSection(sec);
#endif

    backcol(col_backgrnd);  // set background color

    // --- create global joypipes ---

    joystick.pipe0  = new JoyPipe(0);
    joystick.pipe1  = new JoyPipe(1);

    if(!joystick.pipe0 || !joystick.pipe1)
        {MemErr(1204941735); return MEMORY;}

#ifdef REPLAY
    ifn(PerfReqPipe = new ScalarPipe)
        {MemErr(512941407); return MEMORY;}
#endif

    if(allocBackMap())
        {MemErr(2511951654); return MEMORY;}

    if(xwtfield.open(opt.openwidth,opt.openheight))
        return FAILED;

    ptexter = new Texter("zoth.dat");
    if(ptexter->isdead())
    {
        delete ptexter;
        ptexter = 0;    // will not be used then
    }

    return  OK;
}

rcode   PlayField::LoadShapes(bool loadTitleBackShape)
{
    ushort istart = SCL_BIKE;
    if (loadTitleBackShape) istart = SCL_TITLEBACK;

    for(ushort iscl=istart; iscl<SCL_MAX; iscl++)
    {
      ifn(p_scl[iscl] = new ShapeClass
            (*this, scl_data[iscl].fnamebase ,
                    scl_data[iscl].nfaces    ,
                    scl_data[iscl].maxsize   ,
                    scl_data[iscl].nrot      ,
                    scl_data[iscl].nzoom     ,
                    scl_data[iscl].flags))
      {
        perr("cannot create shapeclass %d",iscl);
        // return MEMORY;
      }

      if(iscl != SCL_TITLEBACK)
              if(p_scl[iscl]->setcfrm(bord[0],bord[1],bord[2]-1,bord[3]-1))
            {SysErr(1911951509); return INTERNAL;}

      if(loadTitleBackShape)
         break; // done
    }

    return OK;
}

rcode   PlayField::LoadSound(enum Sounds snd, ushort targspeed)
{_
 rcode  rc,rc2;
 ushort  i  = (ushort)snd;

 bool   bExtSnd = (!strncmp(sndfname[i], "ext/", 4)) ? 1 : 0;
 bool   bExtMention = 0;
 ushort oldspeed,newspeed;

 char   fullnamebuf[FILENAME_MAX+2];
 char   statusbuf[80];

    sprintf(fullnamebuf, "snd\\%s", sndfname[i]);
    statusbuf[0] = 0;

    if(rc = p_snd[i]->load(fullnamebuf))
    {
        //  loading failed
        if(rc == MEMORY)
            strcpy(statusbuf, "OUT OF MEMORY");
        else
        if(bExtSnd && bExtMention)
            strcpy(statusbuf, "n/a");
    }
    else
    {
        //  loaded successfully

        //  optional kHz conversion:
        oldspeed  = p_snd[i]->waveinfo()->speed;
        rc2       = p_snd[i]->ResampleTo(targspeed);
        newspeed  = p_snd[i]->waveinfo()->speed;

        if(rc2)
            strcpy(statusbuf, "CANNOT RESAMPLE");
        else
        if(newspeed != oldspeed)
            sprintf(statusbuf,"loaded (%u -> %u)", oldspeed, newspeed);
        else
            strcpy(statusbuf, "loaded");

        istat.memory += p_snd[i]->ssize;
    }

    void globalMessage(char *str, ...);
    if(statusbuf[0])
    {
        globalMessage("%s : %s\n", fullnamebuf, statusbuf);

        loadcol(col_misc, 0x999);
        backcol(col_misc);
        loadcol(col_misc, 0xccc);
        color(col_misc);
        drawmode(NORMAL2);
        text(csize(0)*1/10,csize(1)-40, "%lu k alloc'ed, %lu k free ",
             istat.memory >> 10, (ulong)GetFreeSpace(GMEM_NOT_BANKED) >> 10);
    }

    return  rc;
}

void    PlayField::FreeSound(enum Sounds snd)
{_
 ushort i = (ushort)snd;

    istat.memory    -= p_snd[i]->ssize;

    p_snd[i]->unload();
}

void PlayField::close()
{_
#ifdef  AMIGA
 extern void CloseFastGfx();

    // make mouse pointer visible again
    uword *hwp=(uword *)0xDFF096; *hwp = 0x8020;
#endif

    // --- delete global pipes ---

    if(joystick.pipe0)  delete joystick.pipe0; joystick.pipe0=0;
    if(joystick.pipe1)  delete joystick.pipe1; joystick.pipe1=0;

#ifdef REPLAY
    if(PerfReqPipe)     delete PerfReqPipe; PerfReqPipe = 0;
#endif

    // --- other stuff ---

    deleteall();        // delete all PlayObj's
    gml.deleteall();    // delete all global messages
#ifdef  AMIGA
    CloseFastGfx();     // close fastgfx's font etc.
    AmigaDisplay::close();  // close display
#else
    PixelDisplay::close();
#endif

    for(ushort isnd=0; isnd<SND_MAX; isnd++)
        if(p_snd[isnd])
            delete p_snd[isnd];

    for(ushort iscl=0; iscl<SCL_MAX; iscl++)
        if(p_scl[iscl])
            delete p_scl[iscl];

    if(ptexter) delete ptexter;
}

bool DrawSwitch;    // set by DrawBushes(), deleted in DrawBush()

void DrawBush(cpix x, cpix y, short dir, short len, ushort lev=0)
{_
 cpix   x2,y2,xd,yd,x2b,y2b;
 intf   fxd,fyd;
 long   brad;
 short  i,bdir,itry;
 short  dirvar = ftrig.degmax() * 60 / 100; // vary upto 60 %
 pcol   scanbnd[2] = {col_fixwall, col_fixwall};
 cpix   spos[2],epos[2],hitpos[2];
 bool   drawnswitch = 0;
 uchar  s;

    if(lev++)
    {
        // search free direction for the twig

        if(field.inbord(x,y))

        for(itry=0; itry<8; itry++)
        {
            spos[0] = x + convif(ftrig.sin(dir) * 6L);
            spos[1] = y - convif(ftrig.cos(dir) * 6L);

            epos[0] = x + convif(ftrig.sin(dir) * (long)(len + 30));
            epos[1] = y - convif(ftrig.cos(dir) * (long)(len + 30));

            if(field.scanline(spos, epos, scanbnd, hitpos) == -1)

                break;  // nothing in the way: use that dir

            dir += ftrig.degmax() >> 3; // else vary dir and try again
        }

        x2 = x + convif(ftrig.sin(dir) * (long)len);
        y2 = y - convif(ftrig.cos(dir) * (long)len);

        // draw a 'bush-line'

        if(field.inbord(x,y) && field.inbord(x2,y2))
        {
            fxd = ftrig.sin(dir + (ftrig.degmax() >> 2));
            fyd = ftrig.cos(dir + (ftrig.degmax() >> 2));

            for(i=0; i<12; i++)
            {
                static pcol  lincol[3] =
                    {
                        col_fixwall, col_fixwall2, col_fixwall3
                    };

                static uchar relcol[2][12] =
                    {
                        1,0,0,0,0,0,0,0,0,0,0,2,
                        2,0,0,0,0,0,0,0,0,0,0,1
                    };

                xd  = convif(fxd * (long)(i-6) / 3);    // step third
                yd  = convif(fyd * (long)(i-6) / 3);    // of a pixel

                s   = (     dir >= (long)ftrig.degmax()*3/8
                        &&  dir <  (long)ftrig.degmax()*7/8 ) ? 1 : 0;

                x2b = x2 + xd;
                y2b = y2 - yd;

                field.line(x+xd,y-yd, x2b,y2b, lincol[relcol[s][i]]);

                if(lev >= 4)    // if max. depth

                if(DrawSwitch)
                {
                    if(!drawnswitch && i == 11)
                    {
                        drawnswitch = 1;

                        field.rectfill(x2-9,y2-9, x2+9,y2+9, col_backgrnd);

                        field.rectfill(x2-3,y2-3, x2+3,y2+3, col_xwswitch);
                        field.rectfill(x2-4,y2-4, x2-2,y2-2, col_xwswitch);
                        field.rectfill(x2+2,y2-4, x2+4,y2-2, col_xwswitch);
                        field.rectfill(x2-4,y2+2, x2-2,y2+4, col_xwswitch);
                        field.rectfill(x2+2,y2+2, x2+4,y2+4, col_xwswitch);
                    }
                }
                else
                {
                    // draw a single bread of grass or nitro:

                    brad    = rand() % 3 + 4;
                    bdir    = dir + (i - 6) * 66;

                    field.line(x2b,y2b, x2b + convif(ftrig.sin(bdir) * brad),
                           y2b - convif(ftrig.cos(bdir) * brad),
                           (field.opt.playmode & PLAYMODE_NITROBUSHES) ?
                           col_nitro : col_grass);
                }
            }

            if(drawnswitch) DrawSwitch = 0;

        }   // endif inbord

        x = x2; y = y2;

    }   // endif lev++

    if(lev < 4)
    {
        len = len / 2;

        // vary further in 3 sub-sections:

        DrawBush(x,y, dir + (rand() % dirvar), len, lev);
        DrawBush(x,y, dir + (rand() % dirvar)+(ftrig.degmax()>>1), len, lev);
    }
}

rcode PlayField::DrawWalls(void)
{_
    short nbase  = 10L * field.csize(0) / 800;
    short nwalls = (rand() % max(1, nbase)) + nbase;
    short tbase  =  3L * field.csize(0) / 800;
    short ntrees = (rand() % max(1, tbase)) + tbase;

    cpix   x,y,tx,ty;
    ushort ang,face,tface;

    Shape wshape(shapeclass(SCL_WALL), col_fixwall);
    if(wshape.isdead()) return FAILED;

    Shape tshape(shapeclass(SCL_TREE), col_fixwall);
    if(tshape.isdead()) return FAILED;

    XWCycler *xwc = (XWCycler *)field.getobj(XWCYCLER);
    if(!xwc) return FAILED;

    short wsw  = wshape.width();
    short wsh  = wshape.height();
    short wsw2 = wsw >> 1;
    short wsh2 = wsh >> 1;

    short tsw  = tshape.width();
    short tsh  = tshape.height();
    short tsw2 = tsw >> 1;
    short tsh2 = tsh >> 1;

    cpix  swx,swy;
    short swang,swdist;

    // create free-positioned trees
    for(short i=0; i<ntrees; i++)
    {
        tx    = rand() % (size[0] - tsw) + bord[0] + tsw2;
        ty    = rand() % (size[1] - tsh) + bord[1] + tsh2;
        tface = rand() % tshape.faces();

        tshape.setface(tface);
        tshape.moveto(tx,ty);
        tshape.plot();
    }

    // create wall objects, some with embedded trees
    for(i=0; i<nwalls; i++)
    {
        x    = rand() % (size[0] - wsw) + bord[0] + wsw2;
        y    = rand() % (size[1] - wsh) + bord[1] + wsh2;
        ang  = rand() % ftrig.degmax();
        face = rand() % wshape.faces() - 1; // one of the faces is a negative mask

        if(face >= 3)   // skip negative mask for face 2
            face++;

        wshape.setface(face);
        wshape.moveto(x,y);
        wshape.turnto(ang);
        wshape.plot();

        switch(face)
        {
            case 0:
                // shapeface 0 was plotted. we expect it to have a bay
                // in the left lower corner
                // in which we may plot a xweapon cycle switch.
                swang   = wshape.truncangle() + ftrig.degmax() * 5L / 8;
                swdist  = wsw * 36L / 100;
                swx     = x + convif(ftrig.sin(swang) * swdist);
                swy     = y - convif(ftrig.cos(swang) * swdist);
                if(inbord(swx,swy))
                    xwc->createswitch(swx,swy);
                    // XWCycler will ignore this call if max. number is created,
                    // or if the position is not free.
                break;

            case 1:
                if(inbord(x,y))     // a bay in the middle
                    xwc->createswitch(x,y);
                break;

            case 2:
                // the 'fried egg' has empty space inside which, due to simplifications
                // in the shape class, would be massive fix wall by default.
                // to clear these inside spaces, for every plotted fried egg (face 2),
                // the inverse of the inside (face 3) must be un-plotted on the same spot.
                wshape.setface(face+1);
                wshape.setcolcode(col_backgrnd);
                wshape.plot();  // i.e. unplot inside area in the framebuffer
                wshape.setcolcode(col_fixwall);
                break;
        }

        // add a tree?

        tx   = (rand() % wsw) - wsw2;
        ty   = (rand() % wsh) - wsh2;
        tface=  rand() % tshape.faces();

        if(inbord(x+tx,y+ty) && ((rand() % 100) < 40))
        {
            tshape.setface(tface);
            tshape.moveto(x+tx,y+ty);
            tshape.plot();
        }
    }

    // just in case randon shape selection didn't create enough switches,
    // create the missing ones now:
    xwc->createmissingswitches();

    return OK;
}

void DrawBushes()
{_
    short basesize = min(field.size[0], field.size[1]) / 4;

    short nbush = (rand() % 3) + 5;

    cpix    x0,y0;
    short   pdir,sz;
    short   szbase = min(field.size[0],field.size[1]) >> 1;

    // draw tunnel switches?

    short   SwitchRemain = 5;

    if(field.opt.magic & MAGIC_OILPUDDLES)
    {
        //  create random oil puddles

        ushort  npud = (rand() % 5) + 5;
        cpix    x,y,x2,y2,r;

        for(ushort i=0; i<npud; i++)
        {
            x   = field.bord[0] + rand() % field.size[0];
            y   = field.bord[1] + rand() % field.size[1];

            r   = rand() % field.sizemax / 13 + 10;

            for(ushort l=0; l<ftrig.degmax(); l += 12)
            {
                r  += rand() % 5 - 2;

                x2  = x + convif(ftrig.sin(l) * r);
                y2  = y - convif(ftrig.cos(l) * r);

                if(field.inbord(x2,y2))

                    field.line(x,y, x2,y2, col_oil);
            }
        }
    }

    for(short i=0; i<nbush; i++)
    {
        x0   = field.bord[0] + rand() % field.size[0];
        y0   = field.bord[1] + rand() % field.size[1];

        pdir =  rand() % ftrig.degmax();
        sz   = (rand() % szbase) + szbase / 2;

        if(SwitchRemain-- > 0)

            DrawSwitch = 1; // draw one switch at outmost level

        DrawBush(x0,y0, pdir, sz);
    }
}

void PlayField::fill3drect(cpix x0,cpix y0,cpix x1,cpix y1,
                           cpix t,pcol colcode,ushort rgbbase)
{_
    color(colcode);

    // loadcol(colcode, rgbbase);
    // rectfill(x0,y0,x1,y1);

    cpix xm;

    if(hwsize[0] >= 700)
    {
        x0 += t; y0 += t; x1 -= t; y1 -= t;
        xm = x0 + ((x1-x0) >> 1);
    }
    else
        xm = x1;

    loadcol(colcode, rgbbase-0x222);
    line(x0+t,y0+t,xm-t,y0+t);
    line(x0+t,y0+t,x0+t,y1-t);

    if(hwsize[0] >= 700)
    {
        line(xm+t,y0+t,x1-t,y0+t);
        line(xm+t,y0+t,xm+t,y1-t);
    }

    loadcol(colcode, rgbbase+0x222);
    line(x0+t,y1-t,xm-t,y1-t);
    line(xm-t,y0+t,xm-t,y1-t);

    if(hwsize[0] >= 700)
    {
        line(xm+t,y1-t,x1-t,y1-t);
        line(x1-t,y0+t,x1-t,y1-t);
    }

    loadcol(colcode, rgbbase);
}

void PlayField::clear()
{_
#ifdef  AMIGA
    // make mouse pointer invisible
    uword *hwp=(uword *)0xDFF096;
    WaitTOF();
    *hwp = 0x0020;

    // switch off low-pass sound filter
    uchar *hwp2=(uchar *)0xbfe001;
    *hwp2 |= 1<<1;
#endif

    // clear playing area (WIN16: first w/o backpattern)

#ifdef  WIN16
    ClearPlayGround();
#else
    rectfill(bord[0],bord[1], bord[2],bord[3], col_backgrnd);
#endif

    // clear message areas
    rectfill(   hwfrm[0], hmarea[1],
                hwfrm[2], hmarea[1] + hmarea[3]-1, col_msgback  );
    rectfill(   hmarea[0],hmarea[1],
                hmarea[0]+hmarea[2]-1, hmarea[1]+hmarea[3]-1,
                col_msgback );

    // draw bike dashboard backgrounds
    ushort  rgbbase = 0x888;
    loadcol(col_lmarea, rgbbase);
    rectfill(   hwfrm[0],msgarea[1],
                hwfrm[2],msgarea[1] + msgarea[3]-1,
                col_lmarea  );
    fill3drect( msgarea[0],msgarea[1],
                msgarea[0]+(msgarea[2]>>1)-1,msgarea[1]+msgarea[3]-1-3,
                2, col_lmarea, rgbbase );
    fill3drect( msgarea[0]+(msgarea[2]>>1),msgarea[1],
                msgarea[0]+msgarea[2]-1,msgarea[1]+msgarea[3]-1-3,
                2, col_lmarea, rgbbase );

    himsg.cnt = 0;

    // draw "just a background" background
    // the SC_BACK shapeclass is JUSTVIDEO, i.e. PD::framebuffer
    // stays unchanged.
    Shape sback(shapeclass(SCL_BACK));
    if(!sback.isdead())
    {
        short xsteps = size[0] / sback.width();
        short ysteps = size[1] / sback.height();
        short w2 = sback.width() >> 1;
        short h2 = sback.height() >> 1;

        for(short xs=0; xs<=xsteps; xs++)
        for(short ys=0; ys<=ysteps; ys++)
        {
            sback.setface(rand() % sback.faces());
            sback.moveto(xs*sback.width()+w2,ys*sback.height()+h2);
            sback.plot();
        }
    }

    // copy complete empty background to backmap:
    copyToBackMap();

    // draw walls:
    DrawWalls();

    // draw borders, which are OUTSIDE the playing area
    // so they can't be damaged!
    color(col_fixwall);

    // left and right borders (in which htunnel will run)
    line(bord[0]-1, bord[1]-1, bord[0]-1, bord[3]);
    line(bord[2]  , bord[1]-1, bord[2]  , bord[3]);
    // top and bottom borders with vertical tunnels
    line(bord[0],bord[1]-1, bord[2]-1,bord[1]-1);
    line(bord[0],bord[3]  , bord[2]-1,bord[3]  );

    // make quadratic spaces in front of each vertical tunnel:
        line(vtun[0][0],bord[1]-1,
             vtun[0][1],bord[1]-1, col_backgrnd);
    rectfill(vtun[0][0],bord[1]-1,
             vtun[0][1],bord[1]+(vtun[0][1]-vtun[0][0]), col_backgrnd);

        line(vtun[1][0],bord[1]-1,
             vtun[1][1],bord[1]-1, col_backgrnd);
    rectfill(vtun[1][0],bord[1]-1,
             vtun[1][1],bord[1]+(vtun[1][1]-vtun[1][0]), col_backgrnd);

        line(vtun[0][0],bord[3]  ,
             vtun[0][1],bord[3]  , col_backgrnd);
    rectfill(vtun[0][0],bord[3]  ,
             vtun[0][1],bord[3]-(vtun[0][1]-vtun[0][0]), col_backgrnd);

        line(vtun[1][0],bord[3]  ,
             vtun[1][1],bord[3]  , col_backgrnd);
    rectfill(vtun[1][0],bord[3]  ,
             vtun[1][1],bord[3]-(vtun[1][1]-vtun[1][0]), col_backgrnd);

    // draw a line over bord[1]-1:
    line(bord[0],bord[1]-2, bord[2]-1,bord[1]-2, col_fixwall);

    // unplot some areas for easier driving:
    Bike* bike;
    for(short nbk=0; nbk<2; nbk++)
    {
        if(bike = (Bike*)getobj(nbk ? BIKE1 : BIKE0))
        {
            // there might be something already on bike position,
            // so remove this first:
            rectfill (bike->pos[0] - 20, bike->pos[1] - 20,
                      bike->pos[0] + 20, bike->pos[1] + 20, col_backgrnd);
            transRect(bike->pos[0] - 20, bike->pos[1] - 20,
                      bike->pos[0] + 20, bike->pos[1] + 20, 1); // 1=to backmap

            // plot a 'free channel' to left or right of bike
            if(!nbk)
            {
                rectfill (field.bord[0], bike->pos[1] - 10,
                     bike->pos[0] - 20, bike->pos[1] + 10, col_backgrnd);
                transRect(field.bord[0], bike->pos[1] - 10,
                     bike->pos[0] - 20, bike->pos[1] + 10, 1);
            }
            else
            {
                rectfill (bike->pos[0] + 20, bike->pos[1] - 10,
                     field.bord[2] - 1, bike->pos[1] + 10, col_backgrnd);
                transRect(bike->pos[0] + 20, bike->pos[1] - 10,
                     field.bord[2] - 1, bike->pos[1] + 10, 1);
            }
        }
    }

    // copy complete background with free areas to backmap:
    copyToBackMap();

    // draw main bike's start positions
    for(nbk=0; nbk<2; nbk++)
        if(bike = (Bike*)getobj(nbk ? BIKE1 : BIKE0))
            bike->shape().plot();

    // draw all additional bike's start positions:
    bike = (Bike*)getobj(HI_BIKE);
    while(bike)
    {
        bike->track.plot(bike->pos, col_track_hi);
        bike = (Bike*)nextobj(bike, HI_BIKE);
    }
}

//  --- the Reporter analyzes the game situation from
//  --- time to time and issues comments (sounds or messages).

//|| Reporter

class Reporter : public PlayObj {

    struct  Info    {

        //  current infos

        Bike    *bk;
        XWeapon *xw;

        short   charge; // is 0 if no xw
        short   amo;    // if no xw contains bullets
        short   shd;

        ushort  power;  // 0==weak 1==average 2==strong

        bool    isterm; // is a terminator

        //  round-scope infos

        short   minshd;     // minimum of shields in this round
        long    shdless;    // how long shieldless

        ushort  done;       // done these comments already

        Info()
            :   bk(0), xw(0), charge(0), amo(0), shd(0), power(0),
                minshd(SHRT_MAX), shdless(0), done(0), isterm(0)
            {}

        }   info[2];    // infos about two main bikes

    //  possible comments:

    const   ushort  REPOWER1;
    const   ushort  REPOWER2;
    const   ushort  VERYSTRONG;

 public:

    Reporter()

        :   PlayObj(REPORTER, 100),

            REPOWER1(1<<0),
            REPOWER2(1<<1),
            VERYSTRONG(1<<2)

            {}

    rcode step(PlayMsg* pm);

    };

#   define  INFO    info[i]
#   define  OTHR    info[(i^1)&1]

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

    // gather information

    for(ushort i=0; i<2; i++)
    {
        ifn(INFO.bk = (Bike*)field.getobj(i ? BIKE1 : BIKE0))
            return  OK; // no bike no info

        ifn(INFO.bk->driver)
            return  OK;

        if(INFO.bk->driver->type() == TERM_DRIVER)
           INFO.isterm = 1;

        if(INFO.xw  = INFO.bk->weapons.first())
        {
            INFO.charge = 1 + INFO.xw->xcharge();
            INFO.amo    = INFO.xw->amo;

            // if holding a pumpgun, every bullet counts 10 times:

            if(INFO.xw->idcode() == PGUN)

                INFO.amo *= 10;
        }
        else
        {
            INFO.charge = 0;
            INFO.amo    = INFO.bk->bul.amo;
        }

        INFO.shd    = INFO.bk->shields;
        INFO.minshd = min(INFO.minshd, INFO.shd);

        // now, is this bike currently strong or not?

        if(     INFO.shd    >= 8    // has lots of shields
            &&  INFO.charge >= 4    // and a very strong weapon
            &&  INFO.amo    >= 80   // and lots of amo
          )
            INFO.power  = 3;    // very strong
        else
        if(     INFO.shd    >= 4    // has enough shields
            &&  INFO.charge >= 3    // and a strong weapon
            &&  INFO.amo    >= 50   // and enough amo
          )
            INFO.power  = 2;    // strong
        else
        if(     INFO.shd    >= 2
            &&  INFO.charge >= 2
            &&  INFO.amo    >= 30   // and some amo
          )
            INFO.power  = 1;    // average
        else
            INFO.power  = 0;    // weak
    }

    // has a bike lost all it's shields but not crashed
    // for at least halve a second? if so, sometimes
    // issue a comment sound.

    bool played = 0;

    enum Sounds snd = SND_MAX;

    for(i=0; i<2; i++)
    {
        //  bike w/o shields for a minimum time?

        if(!INFO.shd && !INFO.bk->dead)
        {
            if((INFO.shdless += cyclesize) == 2000)
            {
                // bike i is low on shields since exactly 2 seconds.

                ushort perc = 20;

                if(field.soundexists( SND_BKNOSH3)) perc =  25;
                if(field.soundexists( SND_BKNOSH5)) perc =  30;
                if(field.soundexists( SND_BKNOSH7)) perc =  40;
                if(field.soundexists( SND_BKNOSH9)) perc =  50;
                if(field.soundexists(SND_BKNOSH20)) perc =  70;
                if(field.soundexists(SND_BKNOSH25)) perc = 100;

                if((SNDRAND() % 100) < perc)

                    snd = field.selectRandomSound(SND_BKNOSH0,SND_BKNOSH_MAX);
                    // returns SND_MAX if nothing available in this block
            }
        }
        else    INFO.shdless = 0;   // has shields or is dead: reset counter

        if(!INFO.minshd)            // if bike was once shieldless...

        if( !(INFO.done & REPOWER1)
            &&  INFO.power == 1 )   // ...but is now average again
        {
            snd = SND_REPOW1;       // checked for existance below

            INFO.done |= REPOWER1;  // just once per bike and round
        }
        else
        if( !(INFO.done & REPOWER2)
            &&  INFO.power >= 2     // ...but is now strong again
            &&  OTHR.power <= 1     // and other is weaker
          )
        {
            snd = SND_REPOW2;       // checked for existance below

            INFO.done |= REPOWER2;
        }

        if( snd == SND_MAX
            &&  !(INFO.done & VERYSTRONG)
            &&  INFO.power >= 3     // if bike is very strong
            &&  OTHR.power <= 2     // and other not so much
            &&  !INFO.isterm        // but it's not a terminator
          )
        {
            snd = SND_REPOW3;       // checked for existance below

            INFO.done |= VERYSTRONG;
        }

        if(     snd < SND_MAX       // if something to play
            &&  field.soundexists(snd)  )   // which is available
        {
            field.stopsound();      // stop ALL sounds

            field.playsound(snd, 1);    // play stereo
        }
    }

    return  OK;
}

static ulong ulRoundStart = 0;  // start of round in seconds

ulong roundSeconds(void)
{
    ulong totalSeconds(void);
    return totalSeconds() - ulRoundStart;
}

//|| PF::Main
rcode PlayField::Main()
{_
 Bike   *bike0,*bike1;
 short  sdelay  = 0;    // status delay counter
 short  fdelay  = 0;    // video frame delay counter
 short  repdelay = 0;   // repeat mode delay
 ushort rseed;          // random number generator's seed for this round
 ushort oldrseed = 0;   // copy of that
 ushort veryoseed;      // very old rseed
 bool   initial = 1;
 JoyStick joy0(0),joy1(1);  // just for button checks
 Joy11Driver *joydv0,*joydv1;
 rcode  rc = MEMORY;
 uchar  keydelay = 0;
 ushort ntermcrt = 0;       // no. of terminators created
 ulong  ntotalrounds = 0;   // since entering Main()

#ifdef  WIN16
    // Try to open sound channel manager.

    if(p_snd[SND_PLAINGF]->valid())
    {
        // at least that sample is loaded.
        // it will now decide at which speed (11 or 32 kHz)
        // Channel Manager will run.

        if(OpenChanMan(p_snd[SND_PLAINGF]->waveinfo(), opt.stereo))

            // open failed. maybe we should try mono?

            if(opt.stereo)
            {
                opt.stereo  = 0;

                OpenChanMan(p_snd[SND_PLAINGF]->waveinfo(), opt.stereo);

                // if this failed too, we have no sound.
            }
    }
#endif

    if(stat.loaded)
        stat.loaded = 0;    // take loaded score once
    else
        memset(&stat, 0, sizeof(stat)); // reset no. of games, sets, points

#ifdef  AMIGA
    if(opt.hyperfield)  AdjustDisplay(1);   // initial coplist creation

    ifn(opt.debug)
        Forbid();   // Forbid multitasking to stop screen-blanker tools
#endif

    if(!num.humans)
        termpower.current   = 100;  // pure demo: always maximum action
    else
        termpower.current   = field.termpower.initial;

    backcol(col_backgrnd);

    // clear whole display area
    rectfill(hwfrm[0],hwfrm[1],hwfrm[2],hwfrm[3],col_msgback);

    while(1)
    {
        if(!replay)
            ntotalrounds++;

        // another round begins, or a replay of the old round.
        // throw away all PlayObj's
        deleteall();

        // clear XWTileField map
        xwtfield.reset();

#ifdef REPLAY
        if(initial)
#endif
            replay = 0; // nothing to replay

        //  --- switch randomizer to new round or replay ---

        rseed   = (rand() % 10000) + 10;

        if(replay)
            rseed       = oldrseed;
        else
        {
            veryoseed   = oldrseed; // for possible late replay
            oldrseed    = rseed;
        }

        srand(rseed);       // set randomizer to initial state
        saltrand(rseed);    // set also sound randomizer

        //  --- NOW randomizer can be used ---

        if(replay)
            repdelay = 1;   // default speed for replay mode

        // create basic objects:

        if(!initial)
            if((termpower.current += termpower.increment) > termpower.maxval)
                termpower.current  = termpower.maxval;

        // PlayStick for cyclic joystick reading
        ifn(new PlayStick)
            {MemErr(2309941625); goto X01;}

        // MAIN BIKE 0 : human or robot driver
        ifn(bike0 = new Bike(0))
            {MemErr(2901941444); goto X01;}

        // MAIN BIKE 1 : human or robot driver
        ifn(bike1 = new Bike(1))
            {MemErr(3001941719); goto X01;}

        joydv0 = joydv1 = 0;

        if(num.humans >= 1)
        {
            ifn(joydv0 = new Joy11Driver(bike0))
                {MemErr(3101941809); goto X01;}

            bike0->driver = joydv0;
        }
        else
            ifn(bike0->driver = new TermDriver(bike0, termpower.current))
                {MemErr(3101941809); goto X01;}

        // NOTE that in NDEMO, bike1->driver->type()
        // will be checked below to determine if to
        // create terminator.
        if(num.humans >= 2)
        {
            ifn(joydv1 = new Joy11Driver(bike1))
                {MemErr(3101941810); goto X01;}

            bike1->driver = joydv1;
        }
        else
            ifn(bike1->driver = new TermDriver(bike1, termpower.current))
                {MemErr(3101941809); goto X01;}

        /*
        if(num.players >= 3)
        {
            for(int plr=2; plr<num.players; plr++)
            {
                ifn(bike0 = new Bike(plr))
                    {MemErr(302941033); goto X01;}

                ifn(bike0->driver = new RobDriver(bike0))
                    {MemErr(3101941809); goto X01;}
            }
        }
        */

        gml.deleteall();    // empty global message list

        new XWCycler();     // XWeapon cycle switches refresher

        clear();            // the playfield. requires XWCycler.

#ifdef  AMIGA
        if(opt.hyperfield)  AdjustDisplay();    // show resetted posn's
#endif

        stat.wonlast = 0;

        // initially show status

        showstatus();

        // reset CPU performance measurement
        memset(&istat.cpu, 0, sizeof(istat.cpu));

        ifn(replay)
        {
            // if set bike or match bike, announce it now

            bool ancd = ShowAnnouncement();

            rcode mrc = msg(ancd ? "" : "GET READY ... PRESS BOTH STICKS",
                            1, 500, ancd);

            if(mrc == FAILED)   goto FullStop;

            if(ancd)
                ClearAnnouncement(); // if shown a score msg, remove it now

#ifdef REPLAY
            if(mrc == SPECIAL)
            {
                // LATE replay selected: restore parameters of old round

                replay      = 1;

                oldrseed    = veryoseed;    // was already overwritten

                continue;   // re-start old round
            }
#endif
        }

        //  ShowAnnouncement() would change altrand setting
        saltrand(rseed);    // set again sound randomizer

#ifdef REPLAY
        if(replay)
            msg("» » »   R E P L A Y   « « «", 2, 500);
        else
#endif
            initial = 0;    // first or following round starts

        // create or setup the objects that behave different
        // in play or replay mode:

        if(joydv0)
            joydv0->joy->setmode(replay ?
                    joydv0->joy->JP_READ
                :   joydv0->joy->JP_WRITE);

        if(joydv1)
            joydv1->joy->setmode(replay ?
                    joydv1->joy->JP_READ
                :   joydv1->joy->JP_WRITE);

#ifdef REPLAY
        PerfReqPipe->index(0);  // reset perfreq()'s data pipe idx
#endif

        new ItemGen();      // special items generator
        new HTunnel();      // create horizontal tunnel
        new Blinker();      // color blink driver

#ifndef RDEMO
        new FireDriver();   // drives fire pixels
        new OilDriver();    // drives oil puddles
#endif
        new Reporter(); // comment issuer

        // ---------- driving loop -----------

        ulong totalSeconds(void);
        ulRoundStart = totalSeconds(); // remember start time

        ntermcrt = 0;   // no terminators created yet

        while(1)
        {
            timer.beginperiod(CYCLE_MS - taddspeed);

            stepobjs();

#ifdef  AMIGA
            if((fdelay -= CYCLE_MS) <= 0)
            {
                fdelay  = 10;   // 40 msec per video frame

                if(opt.hyperfield)  AdjustDisplay();
            }
#endif

            if(do_gml())    // do jobs in global message list

                break;      // returned non-zero: END OF ROUND

            if((sdelay -= CYCLE_MS) <= 0)
            {
                sdelay = (replay && !repdelay) ? 410 : 205;

                showstatus();
            }

            istat.cpu.tmrem = timer.remain();   // get remainig CPU time

            calcperf(); // calc system performance usage

            if(explib_root.mode & RootBase::MEMTRACK)
            {
                checkup();  // scan PlayField list for integrity
                explib_root.MemTracker.checkup();   // scan memlist for hits
            }

            ifn(replay)
                timer.waitforperiodend();   // wait 'til end of period
            else
            {
             static StringLib sl;
             short  odel = repdelay;

                //  repeat speed management

                if(keyb.pressed('N') && repdelay)       repdelay--;

                if(keyb.pressed('M') && repdelay < 50)  repdelay++;

                if(repdelay != odel)
                {
                    tell(sl.printf("REPLAY DELAY %d", repdelay), 200);
                    sdelay  = 0;    // enforce status showing
                }

                if(repdelay)
                {
                    timer.waitforperiodend();
                    timer.endperiod();

                    for(short rdc = repdelay - 1; rdc; rdc--)

                        timer.delay(CYCLE_MS);
                }
            }

            istat.cpu.time  += CYCLE_MS;    // count overall CPU time

            ifn(keydelay++ & 7)             // every 8th cycle
            {
                if(sysbreak())              // check ESCAPE key
                {
                    while(sysbreak());      // wait 'til released

                    if(replay)
                    {
                        replay  = 0;

                        break;              // just stop replay
                    }

                    goto FullStop;          // else stop match
                }

                checkpause();               // and other keys.
            }

            if(opt.showxinfos & 1)
                perfmon();      // performance monitor

            // user-selected terminator creation:

            short tc0b = 30 - 18;

            if(    (opt.magic & MAGIC_CRTTERM)
                &&  roundSeconds() >= tc0b + 18
                &&  ntermcrt < 1
              )
            {
                CreateTerminator();
                ntermcrt++;
            }

            timer.endperiod();  // release timer for next period
        }

        timer.endperiod();  // release timer for next period

        if(sysbreak())  break;
    }

FullStop:

    timer.endperiod();  // release timer to make system work again

#ifdef  AMIGA
    ifn(opt.debug)
        Permit();   // allow multitasking again
#endif

    rc  = OK;

X01:

#ifdef  WIN16
    CloseChanMan();
#endif

    return rc;
}

void PlayField::calcperf()
{
    istat.cpu.cur    = 100 - convif(istat.cpu.tmrem * 10);
    istat.cpu.sum   += istat.cpu.cur;

    // every half second, an average is calculated:
    istat.cpu.avg    = istat.cpu.sum / (++istat.cpu.div);

    istat.cpu.maxavg[0] = max(istat.cpu.maxavg[0], istat.cpu.avg);

    // calc average per 1.0 sec:

    if(istat.cpu.div == 100)
    {
        istat.cpu.div = 0;
        istat.cpu.sum = 0;

        istat.cpu.maxavg[1] = istat.cpu.maxavg[0];
        istat.cpu.maxavg[0] = 0;
    }
}

intf PlayField::perfreq()
{_
 uchar val = 0;

    // now, how good or bad is the state of the system performance?
    // if CPU usage is < 90%, anything's O.K.
    // if it's over that, return a linear value from 0.0 to 1.0

#ifdef REPLAY
    ifn(replay)
    {
#endif
        // really create value from current system performance

        if(istat.cpu.cur < 90)
            val = 0;
        else
            val = (istat.cpu.cur - 90);

#ifdef REPLAY
        // store it in pipe
        if(PerfReqPipe->putb(val))
        {
            static bool doneerr=0;
            if(!doneerr++) perr("perfreqpipe write error");
        }
    }
    else
    {
        // replay value 'as it was' while playing
        if(PerfReqPipe->getb(val))
        {
            val = 10;   // some error: return this as dummy
        }
    }
#endif

    // return value as intf from 0.0 to 1.0

    return  convfi((ulong)val) / 10;
}

void PlayField::perfmon()
{_
  char tbuf[60];
  static StringLib sl;
  static long uct = CYCLE_MS - convif(istat.cpu.tmrem); // used CPU time
  static long pomc[2]={0,0};    // pixels on max cpu
  static long stepcnt=0;
  static long tatlrp=0;     // time at last RealizePalette()

    stepcnt++;

    // textual performance display:

    ifn(stepcnt & 0xf)
    {
        extern bool globalBoolPDRealizedPalette;

        if(globalBoolPDRealizedPalette)
        {
            globalBoolPDRealizedPalette = 0;
            tatlrp  = roundtime();
        }
        else
        if(roundtime() < tatlrp)    // new round?
            tatlrp=0;

        sprintf(tbuf, "%03.3u %03.3ld %03d %03d %03d ",
                (short)cman.colorremain(),
                (long)(roundtime() - tatlrp) / 1000L,
                (short)istat.npo,
                (short)istat.cpu.maxavg[0],
                (short)istat.cpu.maxavg[1]
                );

        if(istat.cpu.cur==100)
        {   pomc[0]=istat.pix.plot; pomc[1]=istat.pix.read; }

        istat.pix.plot = istat.pix.read = 0;

        loadcol(col_globmsg, 0x00c);
        color(col_globmsg);
        backcol(col_msgback);
        text(20, hmarea[1], tbuf);
    }

    // graphical CPU performance display:

    long wtext  = charsize[0] * 26;
    long wwidth = field.size[0] - wtext - 40;   // window width
    long ypos   = hmarea[1] + 7 - istat.cpu.cur * 7 / 100;
    long xpos;
    pcol col;

    if(istat.cpu.cur <= 70) col=col_msg2;
    else
    if(istat.cpu.cur <= 98) col=col_highlight;
    else                    col=col_fire;

#ifdef  WIN16
    xpos    = 20 + wtext + stepcnt % wwidth;
    line(xpos, hmarea[1], xpos, hmarea[1] + 7, col_msgback);
    plot(xpos, ypos, col);
#endif

#ifdef  AMIGA
    xpos    = hmarea[0] + cs10 + wwidth - 1;
    plot(xpos, ypos, col);

    ScrollRaster(rport, 1,0,
                 hmarea[0]+cs10, hmarea[1],
                 hmarea[0]+cs10+wwidth-1, hmarea[1]+7);
#endif
}

inline rcode PlayObj::CPUentry(PlayMsg* pm)
{
    // In one time cycle, an object may step several times;
    // The first step with pm==0,
    // all following with PlayMsg FURTHERSTEP.
    // Whenever the object wants one more step in the current cycle,
    // it returns rcode SPECIAL;
    // As soon as it is done with this cycle, it returns something else.

    if(pm)  return step(pm);    // if msg given, fall through 1:1

    if((delaycnt -= field.CYCLE_MS) <= 0)
    {
        // another PlayObj time cycle is to be started.
        // if PlayField is in performance troubles, perfreq()
        // will return something over zero (max. 1.00);
        // if psac_size is also over zero, object will
        // automatically slow down to improve speed
        // of more important objects.

        delaycnt += cyclesize + convif(field.perfreq() * psac_size);

        return step(pm);
    }

    return OK;  // nothing to do now
}

rcode PlayField::stepobjs()
{_
 PlayObj *po;
 bool cont_loop;

    cont_loop = 0;

    //  MIND that an object may remove itself from the list
    //  while it gets CPU time.
    //  the convention is that no object is allowed
    //  to directly remove other PlayObj's.

    for(po = first(); po; po = walksucptr)
    {
        walksucptr  = po->next();

        if(po->CPUentry(0) == SPECIAL)
            cont_loop=1;

        // now, walksucptr might be updated if po's successor
        // was deleted during po's activity.
    }

    //  Some objects may require several calls per time cycle.
    //  As long as at least one object returns rcode SPECIAL,
    //  we loop:

    PlayMsg pm;
    pm.type = PMT_FURTHERSTEP;

    while(cont_loop)
    {_
        cont_loop=0;

        for(po = first(); po; po = walksucptr)
        {
            walksucptr  = po->next();

            if(po->CPUentry(&pm) == SPECIAL)

                cont_loop=1;
        }
    }

    return OK;
}

bool PlayField::sysbreak()
{_
    if(keyb.stateof(KEY_ESCAPE) & KEYSTATE_DOWN)

        return 1;

    return 0;
}

//|| BlastXWeapon

rcode PlayField::BlastXWeapon(cpix pos[2], enum Dirs xdir, int delay)
{_
    //  do NOT remove PlayObj's now from system list,
    //  'cause this might crash the caller if he calls
    //  BlastXWeapon several times while walking with
    //  nextobj().

    new BlastCom(pos, xdir, delay);

    return  OK;
}

BlastCom::BlastCom(cpix xpos[2], enum Dirs xdir, int delay)

    :   PlayObj(BLASTCOM, delay, 0)
{_
    SetPos(xpos);
    dir = xdir;

    //  make LetterBox(es) unseen to avoid redundant calls
    //  of BlastCom on same spot before real blast-off
    //  is performed:

 XWeaponAnchor* xwa;
 XWeapon* xw;

    xwa = (XWeaponAnchor*)field.getobj(XWEAPON_ANCHOR);

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

    while(xwa)
    {
        ifn(xw = xwa->xweapon)  {SysErr(1411941214); return;}

        if(xw->matchpos(pos))

            xw->RemoveLetterBox();  // make box unseen

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

rcode BlastCom::step(PlayMsg *pm)
{_
 rcode      rc = OK;
 Bike       *tbike;
 ItemGen    *igen = (ItemGen*)field.getobj(ITEMGEN);
 XWeapon    *xw,*old=0;

    if(pm)  return INDATA;

    // create a temporar bike, with dummy number 255;
    // it'll add itself to PlayField only as long as this function lasts

    ifn(tbike = new Bike(255))
    {
        rc  = MEMORY;   goto X01;
    }

    tbike->SetPos(pos);
    tbike->SetMov(dir, convfi(0));  // direction of caller, speed 0

    if(igen)
    {
        // mount ALL XWeapon's with matching positions on temporar bike:
        igen->pleazmount(tbike, XW_BLAST_OFF);

        // launch ALL XWeapon's now, letting them detonate immediately:
        while(xw = tbike->weapons.first())
        {
            if(xw == old)   {SysErr(2203941740); break;}
            // XWeapon MUST leave 'weapons' box in XW_BLAST_OFF mode!
            old = xw;

            xw->launch(XW_BLAST_OFF);
        }
    }

    // 'tbike' becomes destructed now!

    delete  tbike;

X01:
    delete  this;   // this BlastCom performed, delete instance
    return  rc;
}

#define TELL(str) if(duringGame) tell(str); else text(10,10,str)

void PlayField::savescore(bool duringGame)
{
    if(!duringGame)
    {
        color(col_shadow);
        drawmode(NORMAL2);
    }

    FILE *f = fopen("..\\score.dat","wb");  // we're in the 'snd' dir!
    if (f)
    {
        size_t nwrite = fwrite(&stat, 1, sizeof(stat), f);
        fclose(f);

        if(nwrite == sizeof(stat))
            TELL("saved current score ");
        else
            TELL("score saving failed ");
    }
    else
        TELL("can't write score file ");
}

void PlayField::loadscore(void) // called ONLY from main menu
{
    color(col_shadow);
    drawmode(NORMAL2);

    FILE *f = fopen("..\\score.dat","rb");  // we're in the 'snd' dir!
    if (f)
    {
        size_t nread = fread(&stat, 1, sizeof(stat), f);
        stat.loaded = ((nread == sizeof(stat)) ? 1 : 0);
        fclose(f);

        if (stat.loaded)
            text(10,10,"loaded score: %d %d %d / %d %d %d",
                (short)stat.points[0],
                (short)stat.sets  [0],
                (short)stat.games [0],
                (short)stat.points[1],
                (short)stat.sets  [1],
                (short)stat.games [1]);
        else
            text(10,10,"invalid score file ");
    }
    else
        text(10,10,"can't read score file ");
}

rcode PlayField::openStatusField(void)
{
    sf.wchar = charsize[0];
    sf.hchar = charsize[1] + 4;

    sf.x     = csize(0) * 1 / 10;
    sf.y     = csize(1) * 2 /  3;

    sf.w     = csize(0) * 8 / 10;
    sf.w    /= sf.wchar;
    sf.w    *= sf.wchar;

    sf.h     = csize(1) * 3 / 12;
    sf.h    /= sf.hchar;
    sf.h    *= sf.hchar;

    sf.cy    = 0;
    sf.cymax = (sf.h / sf.hchar)-1;

    // transRect(sf.x,sf.y,sf.x+sf.w,sf.y+sf.h); // clear backgrnd
    short fat = 5;
    loadcol(col_misc, 0x999);
    rectfill(sf.x,sf.y,sf.x+sf.w,sf.y+sf.h+1,col_misc);
    void d3frame(PixelDisplay &pd, cpix x,cpix y,cpix w,cpix h,bool inverse);
    // from zoth.cpp
    for(short i=1; i<=fat; i++)
        d3frame(*this, sf.x-i,sf.y-i,sf.w+i*2,sf.h+i*2, 1);

    // 2nd rect around place where memory infos will be displayed:
    cpix y = csize(1) - 40;
    loadcol(col_misc, 0x999);
    rectfill(sf.x,y,sf.x+sf.w,y+charsize[1]+1,col_misc);
    for(i=1; i<=fat; i++)
        d3frame(*this, sf.x-i,y-i,sf.w+i*2,charsize[1]+i*2, 1);

    sf.open  = 1;
    return OK;
}

void PlayField::closeStatusField(void)
{
    sf.open = 0;
}

void PlayField::statusMessage(char *txt)
{
    if(!sf.open)
        return;

    bool center = 1;
    bool lf     = 0;
    bool right  = 0;

    if (txt[0] == '\r')
    {
        center = 0;
        txt++;
    }
    else
    if (txt[0] == '\t')
    {
        right  = 1;
        txt++;
    }

    char *cp = strchr(txt, '\n');
    if(cp)
    {
        lf  = 1;
        *cp = 0;
    }

    if(!right && (sf.glpsmode && !(opt.debugmode & ZDEB_SHOWBITMAPS)))
    {
        // graphics loader with propaganda surface is active!
        // generate a really impressive message...
        if(texter())
        {
            texter()->solve("glps");
            txt = texter()->result();
        }
    }

    cpix x  = sf.x;
    cpix y  = sf.y + sf.cy * sf.hchar;
    cpix x2 = x + sf.w;
    cpix y2 = y + sf.hchar;

    if(right)
        x   = x2 - sf.wchar * strlen(txt);

    loadcol(col_misc, 0x999);
    if(lf)
    {
        static bool initial = 1;    // what a hack...
        if(sf.cy == sf.cymax)
        {
            if(initial)
                initial = 0;
            else
            {
                backcol(col_misc);
                RECT r = {sf.x,sf.y,sf.x+sf.w,sf.y+sf.h};
                ScrollDC(getcurhdc(), 0,0 - sf.hchar, &r,&r, 0,0);
                rectfill(x,y,x2,y2,col_misc);
            }
        }
        else initial = 1;
    }
    rectfill(x,y,x2,y2,col_misc);

    SetBkMode(getcurhdc(), TRANSPARENT);

    SetTextColor(getcurhdc(), wincolref(col_shadow));
    RECT rhl2 = {x+2,y+2,x2,y2};
    DrawText(getcurhdc(), txt, -1, &rhl2, DT_VCENTER | (center ? DT_CENTER : 0));

    SetTextColor(getcurhdc(), RGB(0xC0,0xC0,0xC0));
    RECT rhl = {x,y,x2-2,y2-2};
    DrawText(getcurhdc(), txt, -1, &rhl, DT_VCENTER | (center ? DT_CENTER : 0));

    if(lf && sf.cy < sf.cymax)
       sf.cy++;
}

void globalMessage(char *str, ...)
{
    char buf[512];
    buf[sizeof(buf)-1] = 0;
    va_list arg_ptr;
    va_start(arg_ptr, str);
    vsprintf(buf, str, arg_ptr);
    va_end(arg_ptr);
    if(buf[sizeof(buf)-1])
    {  SysErr(1612951147); return;  }

    field.statusMessage(buf);
}
