/*   _____________________________________________________________
    /                                                             \
    | File: global.h -  global header file
    \_____________________________________________________________/

    Developers: ID: Name:
    =========== --- -----
                JTH Juergen Thumm
  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\
 yymmdd ------- --- ---------------------------------------------------------
 940303 1.22    JTH created
 940314             virtual destructors for PlayObj, XWeapon.
                    subversive problem:
                        Bazooka x was derived from MovingObj, XWeapon.
                        delete x led to a delete(a,s) call whereby a was
                        not(!) the same adress as at the 'a = new Bazooka'
                        call.
                    solution:
                        changed baseclass sequence of Bazooka:
                        class Bazooka : public XWeapon, public MovingObj
                        -> now the new/delete inconsistency is gone.
 940318             removed intf's from flames. scaling now by hand.
                    'Explosion' & 'Flame' init now also with *vp,
                    removed access macros 'vecw','vecr'.
 940322             XWeapon::launch(): mode BLAST_IMMEDIATE created
                    Bikeless second constructor for 'Bullet'
 940407 1.35        PlayField.replay, JoyPipe, Joy11Driver, array.h
 940409 1.351       PlayField.fasttext()
 940412 1.352       Bike::lsdir, field.jostick.pipe0/1 instead of joyp0/1
 940416 1.36        blue background color
 940803 1.37        added 'Justice' control
 941215             PF::inbord() now implicitely inline
 950311             LoadSound: UpSampFact, DownSampFact.
                    field.opt.khz11 -> ushort field.opt.khz with 0,11,22,32.
                    Missile::blast().
                    col_cmimpact added.
 950916             PF::LoadSound: changed to targspeed parm.
                    LightSword: added oldang.
 951119 1.96        PF::p_scl, ShapeClass enumeration.
*/

extern const char* VER_STR;

#define SNDRAND altrand

#define ROOTNAME explib_root

#ifdef  AMIGA
#   define LOG_FILE_NAME "t:log"
#else
#   define LOG_FILE_NAME "log"
#endif

#   include <stdio.h>
#   include <math.h>

#   include "expdef.h"
#   include "explib.h"
#   include "list.h"
#   include "misc.h"
#   include "display.h"
#   include "sound.h"

#ifdef REPLAY
#   include "array.h"
#endif

#ifdef  AMIGA

#   include <libraries/dosextens.h>
#   include <libraries/dos.h>
#   include "FastPix.h"

extern "C" void FWriteFatPix(short x, short y, short color);

#endif

#   include "fixes.h"

#   define  USE_EXT // use extensions not yet tested

#   define  ABS(x) ((x < 0) ? 0 - x : x)

#   define  BULPACK_SIZE 30 // a package contains 30 bullets

class Texter;

//  Global color codes to be used by playfield objects:

enum Colors {

    col_backgrnd =  0,  // overall  background
    col_backgrnd2=  1,  // brighter background
    col_backgrnd3=  2,  // darker   background

    col_msgback  =  3,  // message areas & fasttext backgrnd

    col_track0   =  4,  // bike 0's track            \ in
    col_track1   =  5,  // bike 1's track            | a
    col_track_hi =  6,  // bike 2 (and more)'s track / sequence
    col_tracktail=  7,  // last four 5th's of a temporar track

    //  obstacle colors

    col_OBST_MIN =  8,  // [lowest obstacle color code]
    col_flame    =  8,  // expected to be FIRST obstacle color!
    col_fire     =  9,  // fire, SECOND obstacle color!
    col_waveexpl = 10,  // wave explosion, THIRD obstacle color!
    col_fixwall  = 11,  // indestructable walls
    col_dust     = 12,  // destructable explosion dust
    col_cmimpact = 13,  // missile impact
    col_xweapon  = 14,  // passive xweapon lying i.t. field
    col_shape0   = 15,  // bike 0's shape \ sequentially \ MUST be
    col_shape1   = 16,  // bike 1's shape / together     / before mgimpact!
    col_mgimpact = 17,  // expected to be LAST obstacle color!
    col_OBST_MAX = 17,  // [highest obstacle color code]

    //  other colors

    col_lsword0     ,   // bike 0's light sword
    col_lsword1     ,   // bike 1's light sword
    col_bazooka     ,   // bazooka (flying)
    col_htunnel     ,   // horizontal tunnel
    col_bullet      ,   // bullets fired by bikes
    col_border      ,   // anything around the race area
    col_globmsg     ,   // global (important) messages
    col_msg1        ,   // message area 1st line
    col_msg2        ,   // 2nd line
    col_msg3        ,   // 3rd line
    col_msg4        ,   // 4th line
    col_alarm       ,   // for very important messages (blinking)
    col_smallexpl   ,   // smart explosion
    col_bigexpl     ,   // big explosion
    col_highlight   ,   // message area highlighted entries
    col_dbshd       ,   // shield text color in dashboards
    col_dbshd2      ,   // lighter frame color
    col_dbshd3      ,   // darker  frame color

    col_bikevcur    ,   // vector direction cursor for bikes, plotted bypassed
    col_tlaser0     ,   // bike 0 target laser, plotted bypassed
    col_tlaser1     ,   // bike 1 target laser, plotted bypassed
    col_tlaser2     ,   // other bike's tlaser, plotted bypassed
    col_pexpl       ,   // pump gun explosion
    col_oil         ,   // oil puddle

    col_fixwall2    ,   // brighter fixwall margin, no obstacle
    col_fixwall3    ,   // darker   fixwall margin, no obstacle

    col_grass       ,   // green grass
    col_nitro       ,   // nitro glycerine
    col_xwswitch    ,   // xweapon switch
    col_missile     ,   // missile
    col_rocket      ,   // rocket

    col_lmarea      ,   // low message areas color
    col_misc        ,   // for miscellaneous use
    col_shadow      ,   // to draw shadows

    col_white       ,   // (nearly) pure white

    col_blue1       ,
    col_blue2       ,
    col_blue3       ,
    col_blue4       ,
    col_blue5       ,
    col_blue6       ,
    col_bluea       ,
    col_blueb       ,
    col_bluec       ,
    col_blued       ,
    col_bluee       ,

    col_ext0        ,
    col_ext1        ,
    col_ext2        ,
    col_ext3        ,
    col_ext4        ,

    PF_NUMPRESCOLS      // number of preset colors
    // don't pre-set too many colors; it will slow down
    // ColorManager's performance, especially when drawing
    // explosions.
    };

// Shape Classes

enum EShapeClass {

    SCL_TITLEBACK = 0,  // must be 1st shape in list, loaded very early

    SCL_BIKE    , // must be 2nd shape, loaded before entering playfield
    SCL_XWEAP   ,
    SCL_WALL    ,
    SCL_TREE    ,
    SCL_BACK    ,
    SCL_BAZO    ,
    SCL_MGCRATER,
    SCL_PGCRATER,
    SCL_RCRATER ,
    SCL_MSCRATER,
    SCL_XWSWITCH,

    SCL_MAX
    };

class ShapeClass;
class Shape;

// Sounds in the system

enum Sounds {

    // === BASE PACKAGE SOUNDS ===

    SND_PLAINGF = 0,    // plain gun firing
    SND_PLAINGH,    // plain gun hit

    SND_VTPASS,     // bike passing vtunnel
    SND_BKEXPL,     // bike crashing

    SND_BAZOF,      // bazooka fired
    SND_BAZOH,      // bazooka hit

    SND_MISLF,      // missile fire
    SND_MISLH,      // missile hit

    SND_KATSHAF1,   // katjusha fire
    SND_KATSHAF2,   // variation
    SND_KATSHAH,    // katjusha hit

    SND_XWEAPC,     // collected extra stuff

    SND_MATCHEND,   // end of match
    SND_APPLAUSE,   // applause at end of set

    SND_BKNOSHDS,   // alarm: bike has no shields
    SND_FLAMEF,     // FlameThrower's flame firing

    SND_ADVTPLR1,   // advantage player1
    SND_ADVTPLR2,   // advantage player2
    SND_DUCETEXT,   // duce textual (spoken)
    SND_MATCHPNT,   // match point announcement

    SND_PLAINMGF,   // plain mgun fire
    SND_PLAINMGE,   // plain mgun empty
    SND_FATMGF,     // fat mgun fire
    SND_FATMGE,     // fat mgun empty

    SND_BKHWALL,    // bike hits wall
    SND_BKHOBST,    // bike hits obstacle
    SND_BKSTART,    // bike starts
    SND_BKSTOP,     // bike stops
    SND_BKTYRES,    // bike's tyres sound

    SND_BKDRIVE1,   // first driving sound
    SND_BKDRIVE2,   // second driving sound

    SND_CANF1,      // cannon (MGun+++ or ++++)
    SND_CANF2,      // variation    | in
    SND_CANF3,      // variation    | a
    SND_CANF4,      // variation    / sequence
    SND_CANE,       // cannon empty

    SND_PGUNFR,     // pump gun fire & reload
    SND_PGUNFE,     // pump gun fire & empty

    SND_OILF,       // oil 'firing'

    SND_LSOPEN,     // light sword opening
    SND_LSCLOSE,    // closing
    SND_LSE,        // empty
    SND_LSH1,       // strike hits something
    SND_LSH2,       // variation | in a
    SND_LSH3,       // variation / sequence
    SND_LSHBK,      // when lsword hits a bike
    SND_LSMOVE1,    // lightsword circular movement
    SND_LSMOVE2,    // variation | in a
    SND_LSMOVE3,    // variation/  sequence!

    SND_BKHBORD,    // bike hits field border

    SND_HTPASSL,    // htunnel pass left channel
    SND_HTPASSR,    // htunnel pass right channel

    SND_PLUS2,      // weapon ++
    SND_PLUS3,      // weapon +++
    SND_PLUS4,      // weapon ++++
    SND_PLUS5,      // weapon +++++

    SND_PGUNHXS,    // pump gun hit extra strong
    SND_LSPLUS2,    // light sword ++

    SND_NITROXPL,   // nitro glycerine explosion
    //SND_HTOPEN,   // tunnel opens
    //SND_HTCLOSE,  // tunnel closes
    SND_BKWCYCLE,   // bike weapon cycling
    SND_TERMCRT,    // terminator creation
    SND_TERMFOC1,   // terminator focuses on victim

    SND_SETBIKE0,   // one player may get the set
    SND_SETBIKE1,   // variation |
    SND_SETBIKE2,   // variation | in a
    SND_SETBIKE3,   // variation | sequence!!!
    SND_SETBIKE4,   // variation |
    SND_SETBIKE5,   // variation | used sounds must start
    SND_SETBIKE6,   // variation | at SETBIKE0 and be
    SND_SETBIKE7,   // variation | in one block.
    SND_SETBIKE8,   // variation | first unused sound
    SND_SETBIKE9,   // variation | ends the block.
    SND_SETBIKE10,  // variation |
    SND_SETBIKE11,  // variation |
    SND_SETBIKE12,  // variation |
    SND_SETBIKE13,  // variation |
    SND_SETBIKE14,  // variation |
    SND_SETBIKE15,  // variation |
    SND_SETBIKE16,  // variation |
    SND_SETBIKE17,  // variation |
    SND_SETBIKE18,  // variation |
    SND_SETBIKE19,  // variation |
    SND_SETBIKE20,  // variation |
    SND_SETBIKE21,  // variation |
    SND_SETBIKE22,  // variation |
    SND_SETBIKE23,  // variation |
    SND_SETBIKE24,  // variation |
    SND_SETBIKE25,  // variation |
    SND_SETBIKE26,  // variation |
    SND_SETBIKE27,  // variation |
    SND_SETBIKE28,  // variation |
    SND_SETBIKE29,  // variation |
    SND_SETBIKE30,  // variation |
    SND_SETBIKE31,  // variation |
    SND_SETBIKE32,  // variation |
    SND_SETBIKE33,  // variation |
    SND_SETBIKE34,  // variation |
    SND_SETBIKE35,  // variation |
    SND_SETBIKE36,  // variation |
    SND_SETBIKE37,  // variation |
    SND_SETBIKE38,  // variation |
    SND_SETBIKE39,  // variation/  # SET ALSO SND_SETBIKE_MAX below! #
    SND_DUCE0,      // duce, set bike was not won
    SND_DUCE1,      // variation |
    SND_DUCE2,      // variation |
    SND_DUCE3,      // variation |
    SND_DUCE4,      // variation |
    SND_DUCE5,      // variation |
    SND_DUCE6,      // variation |
    SND_DUCE7,      // variation |
    SND_DUCE8,      // variation |
    SND_DUCE9,      // variation/  # SET ALSO SND_DUCE_MAX below! #

    SND_BKNOSH0,    // bike has no shields, random sound
    SND_BKNOSH1,    // variation |
    SND_BKNOSH2,    // variation | in
    SND_BKNOSH3,    // variation | a
    SND_BKNOSH4,    // variation | sequence!!!
    SND_BKNOSH5,    // variation | used sounds must start
    SND_BKNOSH6,    // variation | at BKNOSH0 and be
    SND_BKNOSH7,    // variation | in one block.
    SND_BKNOSH8,    // variation | first unused sound
    SND_BKNOSH9,    // variation | ends the block.
    SND_BKNOSH10,   // variation |
    SND_BKNOSH11,   // variation |
    SND_BKNOSH12,   // variation |
    SND_BKNOSH13,   // variation |
    SND_BKNOSH14,   // variation |
    SND_BKNOSH15,   // variation |
    SND_BKNOSH16,   // variation |
    SND_BKNOSH17,   // variation |
    SND_BKNOSH18,   // variation |
    SND_BKNOSH19,   // variation |
    SND_BKNOSH20,   // variation |
    SND_BKNOSH21,   // variation |
    SND_BKNOSH22,   // variation |
    SND_BKNOSH23,   // variation |
    SND_BKNOSH24,   // variation |
    SND_BKNOSH25,   // variation |
    SND_BKNOSH26,   // variation |
    SND_BKNOSH27,   // variation |
    SND_BKNOSH28,   // variation |
    SND_BKNOSH29,   // variation |
    SND_BKNOSH30,   // variation |
    SND_BKNOSH31,   // variation |
    SND_BKNOSH32,   // variation |
    SND_BKNOSH33,   // variation |
    SND_BKNOSH34,   // variation |
    SND_BKNOSH35,   // variation |
    SND_BKNOSH36,   // variation |
    SND_BKNOSH37,   // variation |
    SND_BKNOSH38,   // variation |
    SND_BKNOSH39,   // variation |
    SND_BKNOSH40,   // variation |
    SND_BKNOSH41,   // variation |
    SND_BKNOSH42,   // variation |
    SND_BKNOSH43,   // variation |
    SND_BKNOSH44,   // variation |
    SND_BKNOSH45,   // variation |
    SND_BKNOSH46,   // variation |
    SND_BKNOSH47,   // variation |
    SND_BKNOSH48,   // variation |
    SND_BKNOSH49,   // variation/  # SET ALSO SND_BKNOSH_MAX below! #

    SND_INTRO0  ,   // start of a new round
    SND_INTRO1  ,   // variation |
    SND_INTRO2  ,   // variation |
    SND_INTRO3  ,   // variation |
    SND_INTRO4  ,   // variation |
    SND_INTRO5  ,   // variation |
    SND_INTRO6  ,   // variation |
    SND_INTRO7  ,   // variation |
    SND_INTRO8  ,   // variation |
    SND_INTRO9  ,   // variation/  # SET ALSO SND_INTRO_MAX below! #

#   define SND_LOSTSH_BLOCKSIZE 20

    SND_LOSTSH20,   // lost two shields
    SND_LOSTSH21,   // variation |
    SND_LOSTSH22,   // variation |
    SND_LOSTSH23,   // variation |
    SND_LOSTSH24,   // variation |
    SND_LOSTSH25,   // variation |
    SND_LOSTSH26,   // variation |
    SND_LOSTSH27,   // variation |
    SND_LOSTSH28,   // variation |
    SND_LOSTSH29,   // variation |
    SND_LOSTSH2A,   // variation |
    SND_LOSTSH2B,   // variation |
    SND_LOSTSH2C,   // variation |
    SND_LOSTSH2D,   // variation |
    SND_LOSTSH2E,   // variation |
    SND_LOSTSH2F,   // variation |
    SND_LOSTSH2G,   // variation |
    SND_LOSTSH2H,   // variation |
    SND_LOSTSH2I,   // variation |
    SND_LOSTSH2J,   // variation/
    SND_LOSTSH30,   // lost three shields
    SND_LOSTSH31,   // variation |
    SND_LOSTSH32,   // variation |
    SND_LOSTSH33,   // variation |
    SND_LOSTSH34,   // variation |
    SND_LOSTSH35,   // variation |
    SND_LOSTSH36,   // variation |
    SND_LOSTSH37,   // variation |
    SND_LOSTSH38,   // variation |
    SND_LOSTSH39,   // variation |
    SND_LOSTSH3A,   // variation |
    SND_LOSTSH3B,   // variation |
    SND_LOSTSH3C,   // variation |
    SND_LOSTSH3D,   // variation |
    SND_LOSTSH3E,   // variation |
    SND_LOSTSH3F,   // variation |
    SND_LOSTSH3G,   // variation |
    SND_LOSTSH3H,   // variation |
    SND_LOSTSH3I,   // variation |
    SND_LOSTSH3J,   // variation/

    SND_REPOW1,     // repowered, min
    SND_REPOW2,     // repowered, med
    SND_REPOW3,     // repowered, max

    SND_BKHWOIL,    // bike hits wall with oil on tyres
    SND_BKHMG,      // bike hit by mg
    SND_BKHFLAME,   // bike hit by flame

    SND_MGUN4F1,    // mgun 4+ firing (extension)
    SND_MGUN4F2,    // variation | in a
    SND_MGUN4F3,    // variation/  sequence
    SND_MGUN4E,     // mgun 4+ empty

    SND_BAZOC,      // bazo collect
    SND_AMOC,       // ammunition collect (default sound)
    SND_LSWORDC,    // lsword collect
    SND_MGUNC,      // mg collect
    SND_PGUNC,      // pgun collect
    SND_SHIELDC,    // shield collect
    SND_MLAUNC,     // mlauncher collect
    SND_KATSHAC,    // katjusha collect
    SND_OILC,       // oil collect
    SND_FTHROWC,    // fthrower collect

    SND_MGAMOC,     // mgun ammo collect
    SND_PGAMOC,     // pgun
    SND_FTAMOC,     // fthrower
    SND_LSAMOC,     // lsword
    SND_MLAMOC,     // mlauncher
    SND_KAMOC,      // rlauncher

    SND_MLAUNE,     // rocket launcher empty
    SND_KATSHAE,    // katjusha empty
    SND_FTHROWE,    // flame thrower empty

    SND_MAX // total number of sounds to load
    };

// sound group maxima:

#   define  SND_SETBIKE_MAX SND_SETBIKE39
#   define  SND_DUCE_MAX    SND_DUCE9
#   define  SND_BKNOSH_MAX  SND_BKNOSH49
#   define  SND_INTRO_MAX   SND_INTRO9

// Possible directions to steer and drive

enum Dirs {
    NONE    =0,
    RIGHT   =1,
    LEFT    =2,
    DOWN    =4,
    UP      =8,
    BUTTON  =16,    // not really a direction, but needed here;
    DIR_MASK=15,    // so if want pure direction, '&' with this
    ALL_MASK=31     // represents all bits used
    };

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

// PlayField object's VIRTUAL base class.

enum ObjCodes {

    UNKNOWN = 0,

    ITEMGEN,
    XWEAPON_ANCHOR,

    BIKE0,
    BIKE1,
    HI_BIKE,

    MGUN,       // \ Must be FIRST shaped xweapon.
    PGUN,       // | In
    LIGHTSWORD, // | a
    KATJUSCHA,  // | sequence!
    BAZOOKA,    // | directly used
    XAMO,       // | for
    XSHIELD,    // | shapeface index.
    XCHARGE,    // |
    MLAUNCHER,  // /

    FTHROWER,
    OIL_SQUIRT,
    MISSILE,
    ROCKET,
    BULLET,
    TUNSWITCH,

    BASE_EXPLOSION,
    NUKE_EXPLOSION,
    SHAPE_EXPLOSION,
    FIRE_DRIVER,
    OIL_DRIVER,
    JOY11DRIVER,
    ROBDRIVER,
    HTUNNEL,
    PLAYSTICK,
    BLINKER,
    REPORTER,
    BLASTCOM,
    TERMDRIVER,
    XWCYCLER,

    ID_MAX      // all id.codes MUST be < ID_MAX !

    };

// The following class is used to split the playfield area,
// e.g. 800x600 pixels, into tiles of XWeaponAnchor size.
// Each tile contains a flag which says if there's an XWA currently lying.
// This allows ItemGen proper random spreading of XW's w/o putting
// several xweapons onto the same position.

//|| XWTileField
class XWTileField {

public:
    XWTileField ();
   ~XWTileField ();
    bool isdead() { return idead; }

    rcode  open (cpix width,cpix height);
    // no close, dtr does cleanup.

    void   reset();
    // must be called before a round starts

    void   setasused(cpix xpix, cpix ypix,uchar val=1);
    uchar  isused(cpix xpix, cpix ypix);

private:
    uchar &tile(cpix x,cpix y);
    bool   idead;
    ulong  size;        // in tile units
    cpix   wpix,hpix;   // w,h in pixels
    ushort wt,ht;       // w,h in tiles
    uchar *tiles;
};

//|| PlayObj
class PlayObj : public ListNode {

public:
    PlayObj(enum ObjCodes idcode,
            int time_cycle_size = 10,
            int psac_size = 10);
    //  psac_size: by default, any PlayObj might be slowed down
    //  upto additional 10 msec (i.e. factor 2) if system performance
    //  is at the edge due to too many objects.

    cpix pos[2];    // position in playfield

    virtual ~PlayObj(); // NEW /1403941614: virtual destructor

    PlayObj* next() {return (PlayObj*)ListNode::next();}

    void SetPos(cpix xpos[2])   {pos[0]=xpos[0]; pos[1]=xpos[1];}

    // the PlayField class calls ever time cycle all objects in its list.
    // this is the entry point for every object:
    rcode CPUentry(class PlayMsg* msg);

    // PlayObjects receive messages and perform actions according
    // to the messages. Executing a single message should
    // take AS SHORT AS POSSIBLE, otherwise the whole
    // system will behave clumsy.
    virtual rcode step(class PlayMsg* msg) = 0;
    // NOTE: if your object doesn't process msg'es, let it
    //       immediately 'return' if (msg) is non-zero!

    bool   hasshape(void)    { return ishape ? 1 : 0; }
    Shape &shape   (void);   // access forbidden if !hasshape()

protected:
    rcode  createshape  (enum EShapeClass escl, pcol color);
    void   destroyshape (void);

    struct  {
        enum ObjCodes code; // identification code
        }   id; // object identification tag
    int cyclesize;  // this object wants CPU time every 'cyclesize' msec
    int psac_size;  // performance support additional cycle size
    // if system is in performance trouble (too many objects doing something
    // parallel), cyclesize may be increased by (a fraction of) this value
    int delaycnt;   // countdown for every time cycle

    Shape *ishape;

friend class PlayField;
};

// A list of PlayField objects

struct ObjList : public ListHead {

    PlayObj* first()    {return (PlayObj*)ListHead::first();}

    };

//  Virtual base class for all objects that move in usual ways

struct AnalogueCtl {

    bool  log;      // this object moves in ANALOG mode

    short vdir[2];  // vector direction for analog moving
    int   vadv[2];  // vector advance, like reladv
    ushort  angle;  // direction angle (0..ftrig.degmask())

    AnalogueCtl()
    {
        log     = 0;    // inactive by default

        vdir[0] = 0;    vdir[1] = SHRT_MIN; // UP by default
        vadv[0] = 0;    vadv[1] = 0;
        angle   = 0;
    }

    // calc angle from a given x/y vector
    ushort  CalcAngle(short xvec, short yvec);

    // calc 'angle' from vdir
    ushort  CalcAngle(void);

    // calc x/y vector from any angle
    short   *CalcVDir(short angle, short scale);
    // returns ptr to short[2] for immediate use

    // calc 'vdir' from 'angle'
    void    CalcVDir(void);

    };

//|| MovgObj
class MovingObj : public PlayObj {

 public:

    const int RADV_MAX; // it takes so many relunits for a pixel

    enum Dirs dir;  // moving direction (without BUTTON)
    int  reladv;    // counter; if reaching RADV_MAX, stepping a pixel
    intf speed;     // speed

    AnalogueCtl ana;    // if (ana.log) this moves analogue!

    MovingObj(  enum ObjCodes xidcode,
                int time_cycle_size = 10, int psac_size = 0 );

    void SetMov(enum Dirs xdir, intf xspeed, AnalogueCtl *actl = 0);

    // set a direction specified in degrees (0..ftrig.degmsk())
    void SetDir(short adir);

    void SetSpeed(intf spd) {   speed = spd;    }

    int advance(bool addup=1);
    // addup: by default, reladv is incremented by speed;
    //        avoid this by setting addup=0 (used in FURTHERSTEPS)
    // result values (combinations possible):
    const int NOTHING   ;       // didn't step a pixel
    const int PIXSTEP   ;       // stepped a single pixel

    const int LBORDER   ;       // reached left   border
    const int RBORDER   ;       // reached right  border
    const int TBORDER   ;       // reached top    border
    const int BBORDER   ;   // reached bottom border

    const int ABORDER   ;   // any border

    const int PASSED_HTUNNEL; // passed horizontal tunnel
    const int PASSED_VTUNNEL; // passed vertical tunnel
    // if reached a border, pos[] stays unchanged, i.e. in the
    // old & valid position

    };

//  virtual base class for eXtra Weapons

enum XWeapStates {

        XWS_LYING=1,    // around, who will collect me? (don't change '1')
        XWS_MOUNTED,    // on the bike
        XWS_LAUNCHED    // activated, thrown, shot, etc.

        };

//|| XWeapon

class XWeapon : public ListNode {

 friend class ItemGen;
 friend class XWeaponBox;
 friend class XCharge;
 friend class PlayField;
 friend class TargetLaser;

    struct XWeaponAnchor* anchor;
    // as long as LYING, this is the connection to PlayField's list
    // of PlayObjects, so we're checked from 'ItemGen' via 'anchor'
    // if we should be 'mount'ed on a bike.
    // NOTE!!! As long as anchor->xweapon pointer is set, ANCHOR
    //         WILL DELETE XWEAPON on destruction!

 protected:

    class XWeaponBox* box;  // when mounted, we're in this box

    bool  idead;

 public:

    char* name;     // to inform users what's available

    char  id_char;  // single identification character (e.g. in playfield)

    enum  XWeapStates status;

    short   amo;    // if this is a firing xweapon, that's remaining ammunition

 protected:

    ulong   weight; // this weapon's current weight in gramm
    // a default weight is set in ::XWeapon

    void detach();  // detach ya from box
    // after detach() call, all links to the firing bike
    // are gone, i.e. the bike may be destructed.

    cpix paspos[4]; // passive position x1[0] y1[1] x2[2] y2[3]
    // as long as it's LYING, this is it's position in the playfield.

    short   xcharged;   // extra charged this often (0==normal charge)

    int     mode;   // special modes (0 by default)

    // supported special modes:
#   define  XW_BLAST_OFF 1  // let xw blast off at bike's position
    // \ if launch(XW_BLAST_OFF) is called, xw MUST detach() itself
    //   immediately, 'cause bike may be deleted soon after call,
    //   and bike expects that xw leaves the XWeaponBox immediately.

 public:

    XWeapon(cpix pos[2], char* name, char id_letter,
            enum ObjCodes idcode = UNKNOWN);
    // must supply playfield position where it waits to be collected
    // and name, and a single letter for user to identify
    // the item in the playfield
    // The idcode parameter is redundant, but it's necessary
    // 'cause at ctr time the idcode() function does NOT
    // necessarily deliver anything of use for objects
    // which are derived from several base classes.
    virtual ~XWeapon(); // NEW /1403941740: virtual
    bool    isdead() { return idead; }

    bool    matchpos(cpix pos[2]);
    // if LYING: check if lying on this position

    virtual rcode mount(class XWeaponBox* box, int mode=0);
    // putting into a box
    // NOTE: usually use this method (implicitely);
    //       if a derived class has it's own mount(),
    //       it MUST also call XWeapon::mount() in it
    //       to perform very important inits!

    virtual rcode launch(int mode=0);   // launching from boxe's bike
    // to be implemented by derived classes
    // NOTE: after launch(XW_BLAST_OFF), no further launch on the same
    //       xweapon is allowed!

    virtual ObjCodes idcode();  // return your idcode (if you have one)
    // this is necessary 'cause XWeapon can't be derived from PlayObj

    short   xcharge()   {return xcharged;}

    rcode   incweight(ulong w); // increment weight
    // this will also increment boxes weight

    void    DrawLetterBox(void);    // (re)draw letter box
    void    RemoveLetterBox(void);  // undraw the box

    ushort  nearfront(void);
    // returns 1 if first weapon of weaponbox' list
    // returns 2 if second weapon
    // returns 0 in all other cases

    };

//  Now comes something complex:
//  We can't directly derive XWeapon from PlayObj and enter
//  it in the PlayField's list, 'cause weapons derived from
//  XWeapon are also derived from PlayObj and so lot's of fuzz
//  will happen.
//  So the following struct is used to enter XWeapon's in the
//  PlayField list as long as they're LYING.
//  When mounting the XWeapon, the XWeaponAnchor is deleted.

//|| XWeapAnchor

#define XWA_SHAPESIZE 32    // size of xweapon lying in the field

struct XWeaponAnchor : public PlayObj {

    // PlayObj's pos[] describes middle of LetterBox.
    // Absolute LetterBox coords are stored in xweapon->paspos[].
    friend class XWeapon;
    XWeapon* xweapon;

    // NOTE!!! As long as anchor->xweapon pointer is set, ANCHOR
    //         WILL DELETE XWEAPON on destruction!
    XWeaponAnchor(XWeapon* myxweap, cpix midposn[2], enum ObjCodes idcode);
    ~XWeaponAnchor();   // will also delete XWeapon if 'xweapon' set!
    bool idead;
    bool isdead() { return idead; }

    rcode step(PlayMsg* pm);
    void  paintya(void);
    };

//  One bike may have SEVERAL XWeapons at one time.
//  Only one can be used at a time (the one last mounted)
//  So, where to put all those xweapons?

//|| XWeapBox
class XWeaponBox : private ListHead {

 friend class XWeapon;
 friend class Bike;

    class Bike* mybike; // the bike this box is strapped on

    ulong   iweight;    // weight of all weapons in gramm

 public:

    XWeaponBox(Bike* bike)  {mybike = bike; iweight = 0;}
    ~XWeaponBox();  // deletes all remaining XWeapons

    XWeapon* first()
        {   return (XWeapon*)ListHead::first(); }

    void AddFrontXW(XWeapon* xw);   // add XW at front of list
    void RemoveXW(XWeapon* xw);     // remove XW from list

    class Bike* bike()  {return mybike;}

    ulong   weight()    {return iweight;}

    rcode   incweight(ulong w) {iweight += w;   return OK;}
    // this should only be called from XWeapon class!

    };

enum PlayMsgTypes {

    PMT_RESET,      // PlayObj bring ya to start position

    PMT_TERMCRASH,  // terminal crash occured in playfield

    PMT_FURTHERSTEP,
    // from field to object: here you get another CPU time slice;
    // use it or not; if you need more, return SPECIAL.

    };

//|| PlayMsg
class PlayMsg : public ListNode {

 public:

    PlayMsg* next() {return (PlayMsg*)ListNode::next();}

    PlayMsgTypes type;  // see PMT_xxx

    PlayObj* src;   // who sends this message to the playfield

    int delay;  // if >0: wait so many milliseconds before really
    // taking action

    };

class PlayMsgList : public ListHead {

 public:

    PlayMsg* first()    {return (PlayMsg*)ListHead::first();}

    };

//|| PlayField

class PlayField :

#ifdef  AMIGA
    public  AmigaDisplay,
#else
    public  PixelDisplay,
#endif
        public ObjList {

 friend class PlayObj;  // the base as friend, not more

    bool dead;  // set if constructor failed or if deleted

 public:

#define PF_DEPTH    7   // no. of bitplanes for playfield colors
#define PF_NCOLORS  (1<<PF_DEPTH)   // total no. of colors

 private:
    const int CYCLE_MS; // time is measured in time cycles.
    // one cycle is so many milliseconds long.

 private:

    struct {

        int npo;    // current no. of PlayObjs added to PlayField

        struct CPU {

            intf  tmrem; // timer remain of current cycle

            ulong time; // elapsed CPU time in msec

            // CPU performance usage...
            int cur;    // in current cycle
            int sum;    // sum
            int div;    // divisor to calc
            int avg;    // average
            int maxavg[2];  // average maximums now and over period

            CPU()   {time=0; sum=0; div=0; avg=0;maxavg[0]=maxavg[1]=0;}

            } cpu;  // CPU performance measurement

        struct PIX {

            int plot;   // no. of plot() calls in 'cpu.time'
            int read;   // no. of readpix() calls

            PIX()   {plot=read=0;}

            } pix;  // pixel performance measurement

        ulong   memory; // overall allocated bytes

        } istat;        // internal statistics

#ifdef REPLAY
    ScalarPipe  *PerfReqPipe;   // pipe needed by perfreq()
#endif

    struct {

        int points[2];  // sub-points
        int sets[2];    // sets of player 1,2 (bike 0,1)
        int games[2];   // who has won how many games

        uchar wonlast;  // who won last (0=none, 1, 2)
        uchar loaded;   // freshly loaded from score file

        } stat; // statistics

    bool sysbreak();    // check for user break (ESC)

    rcode stepobjs();   // STEP all objects once
    // to be called ONCE per time cycle

    rcode do_gml();     // scan global message list for entries
    // and if there are do what's to be done
    // to be called ONCE per time cycle

    void showstatus(char* extra_txt0=0, char* extra_txt1=0);
    // display current bike etc. status text's
    // can also be called with extra texts for bike0 or bike1

    void calcperf();    // calc actual performance usage
    // to be called every CYCLE_MS

    intf perfreq();     // this returns a value from 0.0 to 1.0;
    // in case of 0, current system performance is so good
    // that no object must be slowed down. If it's 1.0,
    // every psac_size must be used at 100%, i.e. objects that
    // support slowing down must be slowed down at maximum rate.

    void perfmon();
    // performance monitor

    PlayMsgList gml;    // Global Message List

    rcode TerminalCrash();  // handle terminal crashes

    rcode resetobjs();  // reset all playfield objects in the list
    // (not used)

    void clear();   // reset display, supply clear racing area

    void fill3drect(cpix x0,cpix y0,cpix x1,cpix y1,
                    cpix t,pcol colcode,ushort rgbbase);

    rcode DrawWalls(void);  // draw fractal walls

 public:

    static uword startcols[PF_NUMPRESCOLS]; // default playfield colors

    struct {

        short   humans; // no. of human players (0..2)
        short   robots; // no. of robot players (0..approx. 20)
        short   players;    // sum of the two above

        } num;  // number settings

    struct {

        // two developer switches:
        bool showxinfos;    // display extended informations
        bool swapsticks;    // if 0, bike 0 has joystick 0, else joy 1
        bool noscoring;     // disable scoring
        bool hyperfield;    // hyper large playfield with scrolling
        bool blastoff;      // let lying xweapons explode if hit
        bool fattracks;     // draw fat track lines
        bool notracks;      // draw no track lines (use bike shapes)
        bool swapbuttons[2];    // swap buttons 1/2 of stick 0/1
#ifdef  WIN16
        bool swappixcol;    // swap red/blue with SetPixel()
        bool usewinpal;     // use a palette or direct RGB values
#endif
        bool   nomusic;     // don't play music samples (for setbike)
        bool   alwaysmusic; // play setbike music always
        bool   stereo;      // stereo or mono sound
        ushort khz;         // 0 (no sound), 11, 22, 32 kHz

        ushort  openwidth;  // user-selected playfield width in pixel
        ushort  openheight; // user-selected playfield height in pixel

        ulong   playmode;   // special playing mode(s)

#       define  PLAYMODE_NITROBUSHES    (1UL<<0)

        ulong   magic;      // undocumented options

#       define  MAGIC_LOTSOF        (1UL<<0)
#       define  MAGIC_OILPUDDLES    (1UL<<1)
#       define  MAGIC_BAZOWALL      (1UL<<2)
#       define  MAGIC_CRTTERM       (1UL<<3)
#       define  MAGIC_NUKEBAZO      (1UL<<4)

        ulong   cxw;        // create these xweapons:
        ulong   lotsof;     // generate lots of these weapons:

#       define  CXW_BAZOOKA   (1UL<<0)
#       define  CXW_XCHARGE   (1UL<<1)
#       define  CXW_ADDAMO    (1UL<<2)
#       define  CXW_LSWORD    (1UL<<3)
#       define  CXW_MGUN      (1UL<<4)
#       define  CXW_PGUN      (1UL<<5)
#       define  CXW_SHIELD    (1UL<<6)
#       define  CXW_MLAUNCHER (1UL<<7)
#       define  CXW_KATJUSCHA (1UL<<8)
#       define  CXW_OIL       (1UL<<24)
#       define  CXW_FTHROWER  (1UL<<25)

        uchar   emulstick;  // if bits 0 and/or 1 set, emulate
        //  joystick 0 and/or 1 by keyboard keys

        uchar   showmouse;  // mouse visible while playing 1/0

        ushort  tlaser;     // target laser shape index
        //  0 ... TLASER_SHAPES - 1

        ushort  debugmode;  // as defined below:

#       define  ZDEB_ALLOWLOGFILE  (1U<<0) // allow log output
#       define  ZDEB_SHOWSOUNDS    (1U<<1) // list unavailable ext. sounds
#       define  ZDEB_VIDEODEBUG    (1U<<2) // display video debug infos
#       define  ZDEB_SHOWBITMAPS   (1U<<3) // show what bitmaps are loaded

        bool    playsoundenabled;   // if 0, playsound() ignores calls

        } opt;  // all options

    struct  {
        ushort  initial;
        ushort  increment;
        ushort  maxval;
        ushort  current;
        }   termpower;  // terminator power, all values in percent

    bool replay;    // field runs currently in replay mode

    cpix bord[4];   // absolute playfield borders
    // [0]=x0 [1]=y0 [2]=x1 [3]=y1

    bool inbord(cpix pos[2])    // test of 'pos' is valid, i.e. in bord[]
        {
            if(     pos[0] >= bord[0]
                &&  pos[0]  < bord[2]
                &&  pos[1] >= bord[1]
                &&  pos[1]  < bord[3]   )
                return 1;
            else
                return 0;
        }

    bool inbord(cpix x, cpix y) // for use with explicite x/y coords
        {
            if(     x   >= bord[0]
                &&  x    < bord[2]
                &&  y   >= bord[1]
                &&  y    < bord[3]  )
                return 1;
            else
                return 0;
        }

    cpix size[2];   // x(0) and y(1) size of playfield,
    // directly derived from bord[]

    cpix sizemax;   // max(size[0],size[1])

    cpix msgarea[4];    // message area, [0/1]=left/top, [2/3]=width/height

    cpix hmarea[4]; // HiMessage area, one-line messages at top of screen

    cpix pavpos[3]; // playing area vertical posn's
    // 0 == top y       (== bord[1])
    // 1 == bottom y    (== bord[3]-1)
    // 2 == middle y    (used only with splitted soft scroll display)

    cpix vtun[2][2];    // vtunnel's x positions
    cpix htun[2];   // htunnel's y positions
    // \ this is updated by HTunnel instance as it moves

    short tunmapxy(cpix& x, cpix& y, cpix *htpos=0);
    // checks if (x,y) lies exactly ONE pixel OUTside bord[].
    // if so, checks if there's a tunnel.
    // if so, adjusts (x,y) accordingly (vector goes through tunnel)
    //        and returns non-zero value.
    // in all other cases returns 0.
    // htpos: if set to an cpix[2], use this as current htunnel pos'n
    //        instead of field.htun[]
    // result:
    const   int PASSED_HTUNNEL;
    const   int PASSED_VTUNNEL;

    FineTimer   timer;  // for proper delays
    ushort  taddspeed;  // additional speed (0..9)

    PlayField();
    ~PlayField();

    rcode open();   // open display, load all stuff, return OK if OK
    void close();   // free all, close display

    rcode Main();   // let system run

    rcode operator += (PlayObj* pobj);  // add playobj to playfield
    rcode operator -= (PlayObj* pobj);  // remove playobj
    void deleteall();   // delete all remaining PlayObjects
    // \ NOTE that PlayObj's remove themselves from list on deletion

    PlayObj* getobj(enum ObjCodes oc);
    // get (first) object from list with matching id.code

    // get next object with matching id.code,
    // starting the search with cur's successor.
    PlayObj* nextobj(PlayObj* cur, enum ObjCodes oc);
    // WARNING!!! Do NOT perform any stuff that REMOVES,
    // directly or indirectly, any PlayObj's from field's
    // list while walking with nextobj() through the list!
    // For example, BlastXWeapon() does not blast off
    // weapon(s) immediately but creates a 'blast command'
    // object which does the job when called through ::step().

    // if something of global interest happens, any object
    // may give a message to the playfield via this function:
    rcode operator += (PlayMsg* m)  {gml.add(m); return OK;}

    rcode msg(char* txt, uchar waitfor=0, int minwait=1500,
              bool callShowAnnouncement=0);
    // display global one-line message
    // waitfor: 0 == wait for any button pressed
    //          1 == wait for BOTH buttons pressed
    //          2 == don't wait for anything except minwait elapse
    // minwait: msg() will not return before so many msec's elapsed
    // result: if non-zero, sysbreak() was activated

    // is c an obstacle color?
    bool obstcol(pcol c)
         {  return (c >= col_OBST_MIN && c <= col_OBST_MAX);    }

    // is there an obstacle (color) at the position?
    bool obstacle(cpix x, cpix y)
        {   return  obstcol(readpix(x,y));  }

    rcode   BlastXWeapon(cpix pos[2], enum Dirs xdir, int delay = 10);
    // this blasts off lying xweapons at pixel position pos[],
    // in the direction of xdir. wait 'delay' msec's between
    // un-plotting LetterBox and really blasting off the weapon.
    // No PlayObj's are removed from system list during this call,
    // so stepping via nextobj() is allowed while performing
    // several BlastXWeapon's.

    // ---- Temporar messages for top line: ----

    // if something of global interest happens during the game,
    // a string is copied to 'himsgtxt', and this is displayed
    // max. as long as determined by 'himsgtime'.
    // If a round terminates (bike crash), the then displayed
    // message has priority.

    struct HiMsg {

        const   int MAXLEN; // max. string length
        char    txt[40+2];  // MAXLEN + 2
        int     cnt;        // no. of blue wrap-around cycles
        int     ccycle[2];  // color cycling support

        HiMsg() : MAXLEN(40)
            {
                txt[0]  = '\0';
                cnt     =   0 ;
                ccycle[0] = ccycle[1] = 0;
            }

        }   himsg;

    void    tell(char *txt, int msec=1000);

    // ---- SOUND ----

    static char *sndfname[SND_MAX];     // sound file names
    static int   sndparm[SND_MAX][2];   // sound play parameters

    Sound *p_snd[SND_MAX];  // table of all available sound objects
    // all instances alloc'ed on open(), deleted on close()

    rcode LoadShapes(bool loadJustTitleBackShape = 0);
    // load and init shapes. displays story while doing so.

    rcode LoadSound(enum Sounds snd, ushort targspeed);
    // load one sound. convert speed if necessary.

    void  FreeSound(enum Sounds snd);
    // this frees the sample data of a sound (not the instance itself).

    bool  soundexists(enum Sounds snd);
    // returns true of sound is loaded

    rcode playsound(enum Sounds sndno, bool stereo=0, short howoften=1,
                    ulong options = 0);
    // starts playing a single sound once
    // stereo: this will allocate TWO channels!
    // howoften: play this often, 0 == permanent
    // options: see sndchan.h, SND_xxx
    // plays only if playsound is enabled (default).

    void  enableplaysound(bool yesno) { opt.playsoundenabled = yesno; }

    rcode stopsound(enum Sounds sndno);
    // if playing a sound permanently, stop it with this

    void  stopsound(void);  // stop ALL sounds immediately

    void silence();
    // this returns as soon as any sounds have stopped playing

    enum Sounds selectRandomSound(  enum Sounds esBlockStart,
                                    enum Sounds esMaxBlockEnd,
                                    bool useAltRand = 0 );
    // selects a random sound from a block of sounds.
    // all available sounds (if any) must be in one sequence,
    // starting with esBlockStart! first non-available sound
    // ends the block!
    // RETURNS SND_MAX IF NOTHING AVAILABLE !
    // useAltRand: uses altrand() instead of rand(), which is
    // needed in code that behaves different in replay mode.

    int volume; // 0...100 - global system volume

    // --------- direction system conversion ---------

    // convert 'enum Dirs' dir'n into short index for 'ds' tables
    short   convdir(enum Dirs dir)
        {
            if(dir == (UP))         return 0;
            if(dir == (UP|RIGHT))   return 1;
            if(dir == (RIGHT))      return 2;
            if(dir == (DOWN|RIGHT)) return 3;
            if(dir == (DOWN))       return 4;
            if(dir == (DOWN|LEFT))  return 5;
            if(dir == (LEFT))       return 6;
            if(dir == (UP|LEFT))    return 7;

            return 0;
        }

    static enum Dirs PlayField::MapIEDir[8];

    // convert index dir into 'enum Dirs'
    enum    Dirs convdir(short dir)
        {
            return  MapIEDir[dir & 7];
        }

    // convert analog direction to digital 8-states direction
    short   convdir(cpix dx, cpix dy);

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

    void checkpause();  // pauses game if 'p' pressed

    bool ShowAnnouncement(bool textOnly = 0);
    // if set bike or match bike, announce it
    // textOnly: don't play sounds
    // result: if announced anything, returns 1
    void ClearAnnouncement();   // clear announcement

#ifdef  AMIGA
    //   _____________________________________________________________
    //  /                                                             \
    //  | AMIGA HyperSpeed graphics routines:                         |
    //  \_____________________________________________________________/

    //  To make the two following methods work, InitFastGfx() is
    //  called as soon as screen was opened

    //  Redefine plot() and readpix() of PixelDisplay class:

    rcode   plot(cpix x, cpix y, pcol col=-1);  // set single pixel
    pcol    readpix(cpix x, cpix y);    // return color of a single pixel

    rcode   fasttext(const char *str);
    rcode   fasttext(cpix x, cpix y, const char *str);

    //   _____________________________________________________________
    //  /                                                             \
    //  | AMIGA Soft-Scroll playfield support:                        |
    //  \_____________________________________________________________/

    rcode AdjustDisplay(bool initial=0);
    //  this will display the two playfield halves in accordance
    //  to the current main bike positions, IF the positions have changed
    //  since the last call.
    //  initial: create necessary copperlist (will take a while),
    //           only to be set on first call, or on big changes.
    //      if only the memory positions of the display sections
    //      changed, set initial to 0.

#endif

    //  ----- Shape support -----

    ShapeClass *p_scl[SCL_MAX];
    ShapeClass &shapeclass(enum EShapeClass escl);
    bool    hasshapeclass(enum EShapeClass escl);

    //  === miscellaneous ===

    struct  {

        class JoyPipe* pipe0;   // global joystick data pipe,
        class JoyPipe* pipe1;   // used for replay -> Joy11Driver

        } joystick;

    XWTileField xwtfield;       // used by ItemGen, XWeaponAnchor

 private:

    PlayObj *walksucptr;    // operator -= must check if this ptr
    //  points to object to be removed. if so, object's successor
    //  (if any) must be entered here.

    PlayObj *getcache[ID_MAX+2];    // adress cache for getobj()
    //  this is accessed only by getobj (entering adress)
    //  and operator -= (removing adress), and constructor.

 public:

    //  A keyboard instance

    KeyBoard    keyb;

    //  PixelDisplay::scanline replacement that checks for
    //  playfield (not display) borders:
    pcol scanline(cpix start[2], cpix end[2], pcol forcolors[2],
                  cpix firsthit[2], cpix *prehitpos = 0);

    rcode clipline(cpix pos1[2], cpix pos2[2]);
    //  returns OK if line was clipped or didn't need to be clipped.
    //  pos1 then contains the in-border start position,
    //  and pos2 contains the on-border clipping position.
    //  returns FAILED if both positions were off bound (not supported).

#ifdef  WIN16
    //  this replacement for PixelDisplay::plot plots a backgrnd
    //  pattern if col == col_backgrnd.
    rcode   plot(cpix x, cpix y, pcol col=-1);

    //  after using plotby(), this unplot must be used:
    rcode   unplot(cpix x, cpix y);

    //  lineby() avoiding native Window's lining:
    //  this is necessary to unplot the exactly same points
    //  in the unline() function.
    rcode   lineby(cpix x1, cpix y1, cpix x2, cpix y2, pcol xcol);

    //  after using lineby(), this unline must be used:
    rcode   unline(cpix x1, cpix y1, cpix x2, cpix y2);

    //  rectfill replacement with pattern support:
    rcode   rectfill(cpix x1, cpix y1, cpix x2, cpix y2, pcol col=-1);

    void    ClearPlayGround(void);  // and draw backpattern

    //  plot a pixel in basecolor 'col' and shading colors
    //  around it to get a pseudo-3D effect. which of the upto 8
    //  surrounding pixels are to be plotted is defined by the 8
    //  bits of 'rim'.
    rcode   plotshad(cpix x, cpix y, pcol col, uchar rim);

    //  rcode   loadcol(ushort icolor, ushort rgb);
    //  security function. this checks if the loadcol() call
    //  is allowed and then calls PixelDisplay::loadcol.
#endif

    //  how many msec's are elapsed in current round?
    ulong   roundtime() {   return istat.cpu.time;  }

    //  save/load current score:
    void    savescore(bool duringGame);
    void    loadscore(void);

    //  field for status infos while initializing GLPS:
private:
    struct  {
        cpix  x,y,w,h;
        cpix  wchar;
        cpix  hchar;
        short cy,cymax;
        bool  open;
        bool  glpsmode;
        }   sf;
public:
    rcode   openStatusField(void);
    void    closeStatusField(void);
    void    statusMessage(char *txt);
    void    showGLPSmsg(bool yea)   { sf.glpsmode = yea; }

private:
    Texter  *ptexter;
public:
    Texter  *texter(void)   { return ptexter; }
    // returns NULL if failed to create

    };

//|| Global ----- GLOBAL ACCESS INSTANCES -----

#ifdef  PLAYFIELD_CPP
PlayField   field;
FastTrig    ftrig(1024);    // fast sin/cos with 1024 degrees per circle
#else
extern  PlayField field;    // external single instance with global access
extern  FastTrig  ftrig;
#endif

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

//|| Justice

class Justice {

 public:

    bool  active;       // if this is set,

    short beats;        // this counts crashes that make points.
                        // when it reaches some value,
    short beaten[2];    // this is checked to see if one of the
        // main players is hopelessly weaker than the other.
        // if so,

    short support[2];
        // the weaker one gets 'support' points which are used
        // - in Bike constructor for more shields
        // - to increment crashdelay by support * 10

    Justice()
        {
            active  =0;
            beats   =0;
            beaten[0]   = beaten[1] =0;
            support[0]  = support[1]=0;
        }

    };

#ifdef  PLAYFIELD_CPP
Justice justice;
#else
extern  Justice justice;    // external single instance with global access
#endif

//|| JoyStick

//  structure for extended joystick informations as available
//  with analog joystick:

struct ExtJoyInfo {

    short   analogX;    // analog X position, SHRT_MIN to SHRT_MAX
    short   analogY;    // analog Y position
    bool    button2;    // second fire button state (trigger is 1st)

    void    reset(void)
            {   analogX = 0; analogY = 0; button2 = 0;  }

    ExtJoyInfo()
            {   reset();    }
    };

class JoyStick {

 protected:

    uchar num;  // this stick's number (0/1)

    static enum Dirs states[16];

 public:

    JoyStick(uchar joynum)  {num=joynum;}   // 0 or 1

    enum Dirs read(ExtJoyInfo* extinf=0);   // get current state
    // result see JoyFlags,
    // e.g. if(result & DOWN) ...
    // to get additional information, e.g. analog joystick data,
    // supply a pointer to an ExtJoyInfo.

    };

//  A JOYPIPE stores all joystick movements, so they can be
//  replayed in replay mode.

//|| JoyPipe

class JoyPipe : public JoyStick {

#ifdef REPLAY
    ScalarPipe  pipe[5];    // five parallel byte pipes
    //  pipe 0   == digital direction with 'button2' extension packed into
    //  pipe 1/2 == analogue extension, X, high byte first
    //  pipe 3/4 == analogue extension, Y
#endif

    int imode;  // current mode

    enum Dirs curdir[2];    // current direction's buffer

    ExtJoyInfo extjinf[2];  // extended joystick informations

 public:

    const   int JP_ILLEG ;  // illegal, error
    const   int JP_UNSET ;  // no mode set
    const   int JP_WRITE ;  // reads from physical stick, writes to pipe
    const   int JP_READ  ;  // reads from pipe

    rcode   setmode(int m); // WRITE or READ

    JoyPipe(uchar joynum);

    enum Dirs read(ExtJoyInfo *extinf=0); // get virtual 'current' state
    //  extinf is optional and only for analog joystick

    //  reading the joystick too often directly drives the system
    //  into performance problems; e.g. under WIN16 reading one
    //  JoyStick takes 2.5 msec.
    //  therefore, last pos'ns are stored internally and read()
    //  will always return the internal-stored pos'ns,
    //  until hwfetch() is called, which does a system call
    //  to read the next 'real' position.

    void hwfetch();     // fetch next hardware position
    //  \ only to be called by PlayStick object

    void flush(void);   // clear write buffer
    //  this MUST be called at end of round to make sure
    //  that ALL movements are stored.

    };

    //  PlayStick performs a joystick->pipe0/1->hwfetch()
    //  every 20 or so msec. One hwfetch() call lasts 2.5 msec.

class PlayStick : public PlayObj {

 public:

    PlayStick();

    rcode step(PlayMsg* pm);

    };

//  --------- color blink driver (active while playing) ---------

//|| Blinker
class Blinker : public PlayObj {

#define BLINK_COLMAX 1  // Blinker handles this many colors

    const char iMIN, iMAX, iVAL, iDIR;  // const indexes

    char col[BLINK_COLMAX][3][4];
    //       ^--------------color code
    //               ^------red[0] green[1] blue[2]
    //                  ^---min[0] max[1] value[2] direction[3]

    static char init_tab[BLINK_COLMAX][3][4];   // color init table

    uword colval(uchar ci)  // get combined rgb value of a color
        {   return   ((uword)col[ci][0][iVAL] << 8)
                    |((uword)col[ci][1][iVAL] << 4)
                    | (uword)col[ci][2][iVAL];
        }

 public:

    Blinker();

    rcode step(PlayMsg* pm);

    };

//  --------------------- Explosions -------------------------

//|| BaseExplosion
class BaseExpl : public PlayObj {

 protected:

    bool    dead;       // if constructor fails, deletes itself on step()

    // --- vector management: ---

    int     nvecs;      // how many vectors are used for drawing

    int     *vecs;  // dynamically alloc'ed vector table
    // access: vecs[vecnum][posn/speed][x/y]
    //                n      0     1    0 1
    //
    // do NOT change ^this sequence, 'cause in BaseExpl::step()
    // a pointer is incrementally stepping over the array.

    int     *vec2;  // second vector table
    // at init, 'vecs' is copied into 'vec2'. as soon as the 'vecs'
    // are flown a bit, 'vec2' vecs with background color will follow
    // so in the end the BaseExpl will create free space.

    const   int SCALEB; // ALL vecs/vec2 values scaled by so many bits

    long    *htp;   // horizontal tunnel's positions
    // BaseExpl pixels may pass tunnels. the problem: htunnel moves
    // while BaseExpl is moving, so while 'pixel delete pass' is running
    // the htunnel pos'n isn't the same as when 'pixel display pass' ran.
    // so, we have to remember htunnel pos'n for each pixel individually.
    //
    // access:  x = htp[vecnum][ty0/ty1]
    //          HIGH word of x is result of 'tunmap' call
    //           LOW word of x is htunnel position (if any)
    //
    // IF a pixel has never passed htunnel, htp[pix][0] == -1

    // --- color management: ---

    pcol    colcode;    // use this playfield color code
    short   ncolors;    // through how many palette entries will we cycle

    uword   *palette;   // IF ncolors > 1, this points to palette table
    short   colidx;     // IF ncolors > 1, this is used to index pal. table

    // --- stepping: ---

    // the following two members allow 'softer' BaseExpls which will
    // spread the consumed CPU time over several step()'s

    short   slices;     // in every step(), do 1/slices BaseExplsteps
    short   slice;      // current slice in this step()

    short   steps[3];   // countdown for drawing[0] and deleting[1] vectors
    // steps[2] determines how long backgrnd vecs wait until they start

    // --- misc: ---

    bool    fatpix;     // draw tiny(0) or fat(1) pixels
    int     mode;       // mode flags, see below

 public:

    BaseExpl(

        cpix    pos[2],     // position in playfield
        int     nvecs,      // number of vectors to be used
        int     steps,      // number of steps to be performed

        pcol    colcode,    // playfield color code
        short   ncolors,    // number of colors in color cycle palette
        uword   *palette=0, // if not 0, points to color cycle palette

        short   slices=5,   // divide speed by this (uses less CPU time)

        int     mode=0      // see below; by default non-temporar

        );

    ~BaseExpl();

#   define  BE_MODE_TEMP    1   // this is a temporar explosion,
    // i.e. pixels are first drawn and then deleted

#   define  BE_MODE_CPUPRIO 2   // NEVER slow this down
    // little flames should not be slowed down because bike
    // would then crash into it's own flame immediately

    rcode   step(class PlayMsg* msg);   // perform stepwise BaseExpl

    };

//|| Explosion
class Explosion : public BaseExpl {

 public:

    Explosion(

        cpix    pos[2],     // position in playfield
        int     nvecs,      // number of vectors to be used
        int     steps,      // number of steps to be performed

        pcol    colcode,    // playfield color code
        short   ncolors,    // number of colors in color cycle palette
        uword   *palette=0, // if not 0, points to color cycle palette

        short   slices=5,   // divide speed by this (uses less CPU time)

        int     mode=0      // see BaseExpl

        );
    };

//|| SmallExpl
class SmallExpl : public Explosion {

#define SMALLEXPL_NCOLORS   10

    static uword palette[SMALLEXPL_NCOLORS];

 public:

    SmallExpl(cpix pos[2]);

    };

//|| BigExpl
class BigExpl : public Explosion {

#define BIGEXPL_NCOLORS 40  // number of displayed colors

    static uword palette[BIGEXPL_NCOLORS];

 public:

    BigExpl(cpix pos[2]);   // create at playfield position

    };

//|| ImpactExpl
class ImpactExpl : public Explosion {

 public:

    ImpactExpl(

        cpix    pos[2],
        pcol    col,
        int     vecs    =30,
        int     steps   =2,
        short   slices  =3,
        int     modes   =0      // see BaseExpl::MODE_xxx

        );

    };

#ifndef RDEMO

//|| FPix
struct FPix {

    short   x;
    short   y;

    ushort  npat;   // neighbour pattern
    uchar   ncnt;   // neighbour count

    char    active;

    //  all instances cleared in ::FireDriver

    };

//|| FireDriver
class FireDriver : public PlayObj {

#   define  FPIX_MAX    1000

    FPix    fpix[FPIX_MAX];

    ushort  ifree;      // index of 1st free fpix
    ushort  ihighest;   // index of last used fpix

    ushort  slice;      // pixel slice counter

 public:

    FireDriver();

    rcode   step(PlayMsg *pm);

    void    CreatePix(cpix x, cpix y);

    };

//|| Flame
class Flame : public BaseExpl {

    const   cpix FLAMEDIST;     // initial distance of flame to firing bike

    static  char dvct[8][2][2]; // direction->vector conversion table

 public:

    Flame(

        cpix    pos[2],     // create position

        Bike    *bike,      // bike from which flame's to be launched
        // this is NOT checked for NULL pointers - always supply valid adress!

        int     nvecs,      // no. of vectors to be used (how many pixel fly)
        int     steps,      // no. of steps (how long do they fly)

        pcol    colcode,    // which playfield color should be used for it
        short   ncolors,    // if > 1, use
        uword   *palette,   // entries to cycle color 'colcode'

        short   slices,     // divide speed by this (uses less CPU time)

        int     xtramodes   // extra BE_ modes

        );

    };

//|| OilFlush
class OilFlush : public BaseExpl {

    const   cpix OFLUSHDIST;    // initial distance of flush to firing bike

    static  char dvct[8][2][2]; // direction->vector conversion table

 public:

    OilFlush(

        cpix    pos[2],     // create position

        Bike    *bike,      // bike from which flush's to be launched
        // this is NOT checked for NULL pointers - always supply valid adress!

        int     nvecs,      // no. of vectors to be used (how many pixel fly)
        int     steps,      // no. of steps (how long do they fly)

        pcol    colcode,    // which playfield color should be used for it
        short   ncolors,    // if > 1, use
        uword   *palette,   // entries to cycle color 'colcode'

        short   slices,     // divide speed by this (uses less CPU time)

        int     xtramodes   // extra BE_ modes

        );

    };

//|| PExpl  - pressure Explosion

class PExpl : public Explosion {

#define PEXPL_NCOLORS   18  // also used as no. of steps

    static uword palette[PEXPL_NCOLORS];

 public:

    PExpl(

        cpix    pos[2],
        int     charge  =1

        );

    };

//|| NitroExpl - nitro glycerine explosion

void CrtNitroExpl(cpix pos[2]); // create nitro explosion

#endif  // !defined(RDEMO)

// the virtual bike driver base class

//|| BikeDrivr
class BikeDriver {

 protected:

    class Bike* bike;   // this driver drives this bike

    ushort  itype;  // type of driver
    // this is 0 or one of these:

#   define  ROBOT_DRIVER    (1<<1)
#   define  TERM_DRIVER     (1<<2)

 public:

    BikeDriver(class Bike* biketomount);    // driver is put on a bike

    virtual bool accelerate() = 0;  // returns 0 to slow down, 1 to accelerate

    virtual enum Dirs direction(short *vdir) = 0;
    // returns direction to turn to or NONE
    // vdir is an optional pointer to a vector direction which
    // can be filled additionally, e.g. by analog joystick

    virtual int  specfunc() = 0;    // special function selection
    // returns 0 if nothing, or 1,2,4,... etc. (or combinations)
    // if a special function was selected.
    // For example, if a joystick would have two buttons,
    // specfunc() may return 1 if second button is pressed

    ushort type()   {return itype;} // return type of driver

    };

//|| Joy11Drvr
class Joy11Driver : public BikeDriver, public PlayObj {

 friend class PlayField;

    // int  number; // of stick (0/1)

    JoyPipe *joy;   // pointer to this JoyDrivers's pipe

#   ifndef  WIN16
    struct MCA {

        // a VALID single click lasts...
        const int CT_MIN ;  // at least that many msec's
        const int CT_MAX ;  // a maximum of that many msec's

        int time[2];    // how many msec's button released[0] / pressed[1]
        int clicks[3];  // how often rel[0] / pres[1] / overall[2]

        intf bikespeed; // what was bike's speed at start of multiclick
        // if 0, this is invalid

        MCA() : CT_MIN(1), CT_MAX(200) {}

        void reset()
            {
                time[0] = time[1] = 0;
                memset(clicks,0,sizeof(clicks));
                bikespeed = 0;
            }

        } mc;   // multi-click acknowledgement
#   endif

    ExtJoyInfo  extjinf;    // used with analog joystick

 public:

    Joy11Driver(Bike* bike);
    ~Joy11Driver();

    bool accelerate();  // returns 0 to slow down, 1 to accelerate
    enum Dirs direction(short *vdir);
    // returns direction to turn to or NONE
    // returns analog direction too if vdir set

    rcode step(PlayMsg* pm); // so we get CPU time to determine specfunc
    // step() is expected to be called once per field time cycle

    int  specfunc();    // returns 1 if button2 pressed

    };

//|| TempTrack
class TempTrack {

#   define  TRACK_PIXBIT    5
#   define  TRACK_PIX   (1<<TRACK_PIXBIT)
#   define  TRACK_IMASK (TRACK_PIX - 1)

    cpix pix[TRACK_PIX][3]; // [0]=x [1]=y [2]=color
    // x == -1 means no pixel yet set

    ushort  ihead;  // frontmost pixel index

    ushort  itail() {   return (ihead + 1) & TRACK_IMASK;   }

 public:

    TempTrack() {   memset(pix, 0xFF, sizeof(pix)); ihead = 0;  }

    rcode   plot(cpix pos[2], pcol color);  // plots new track pix,
    // and unplots oldest track pix if track length
    // has reached maximum

    };

//|| Bike

class Bike : public MovingObj {

 friend class PlayField;
 friend class RobDriver;
 friend class TermDriver;
 friend class Bullet;
 friend class XWeapon;
 friend class AddAmmo;
 friend class XCharge;
 friend class AddShield;
 friend class ItemGen;
 friend class Reporter;     // see playfield.cpp
 friend class PExpl;
 friend void  CreateTerminator(void);
 friend bool  isBikeObstacleColor(pcol col,void *pBike);

 public:
    intf    speedmin, speedmax;
    bool    parkallow;  // if set, parking is allowed
#define BIKE_STXT_LEN   20  // status text strings upto so much chars
    uchar   number;     // bike 0, bike 1, ....

 private:
    int     shields;    // no. of remaining collision shields
    bool    dead;       // if non-zero, it's a corps

    struct {
        int amo;        // bullets ammunition
        int xchrgcnt;   // how many bullets launch with double charge?
        } bul;  // bullets control

    rcode   steer();    // determine direction & throttle
    rcode   advance_step(PlayMsg* pm);  // advance bike one pixel step
    rcode   crashchk(); // determine colissions

    char    statustxt[BIKE_STXT_LEN+2]; // if something interesting happens,
    // enter a string here, playfield will show it ...
    int     statustime; // ... for so many milliseconds as
    // specified in this field.

    enum    Dirs lsdir; // last (pixel) step's direction -> ::steer
    short   backsndcnt; // background (driving) sound refresh counter
    bool    hoovering;  // bike hoovers (steering disabled)
    short   joyVec[2];  // direct 1:1 vector from (joystick) driver
    uchar   joyElong;   // analogue joystick elongation
    // 0 = MIN, 1 = MEDIUM, 2 = MAX
    uchar   redrawcnt;  // shape redraw counter
    pcol    ihitby;     // if not -1, bike was hit by something
    // with this color, and crash actions must be performed
    // in ::step (or subfunctions).
    short   crashdelay; // if set, wait that many msec before
    // the next shield could be lost in action. this is used
    // to prevent 'fatal hits' that wipe out all shields
    // in one step.

    struct  {
        bool    steering;   // if 0, steering disabled
        bool    breaks;     // if 0, can't slow down
        }   allow;

    ushort  OilOnTyres;     // as long as non-zero, bike floats
    uchar   OilDispCnt;     // oil disperse cnt

    struct  {
        int steer;      // steering inertia increased by so many bperc
        int accel;      // acceleration poorer by so many bperc
        //  bperc means 'binary percent', i.e. 128 == 100%
        }   delay;      // reactions to joystick movements

    enum ComStates {
        CS_OOA_COMMENTED    = (1<<0),   // out of ammo commented
        CS_XWCYCLEREQUEST   = (1<<1)    // xweapon cycling requested
        };

    ushort  comstate;   // common status bits as defined above
    short   xwcdelay;   // xweapon cycling delay cnt
    short   shdloss;    // shield loss accumulation counter
    short   shdlossdly; // shield loss announcement delay
    short   smallexpdelay; // small explosion create delay
    short   sfaceval;   // shape face value (0..numberOfFaces-1)
    short   sfvaldir;   // shape face value direction (1 or -1)

 public:
    int     shootdelay; // disable shooting for a while
    // while this is >0, shooting is disabled for this time in msec.

 public:
    void    tell(char *txt, int msec=100)
            {strncpy(statustxt, txt, BIKE_STXT_LEN); statustime=msec;}

    class XWeaponBox weapons;   // contains all extra weapons

    // The Track-Log keeps track of the most resent directions
    // this bike drove, with the coordinates of direction changes.
    // This is important for the RobDriver to recognize driving loops.

#define BIKE_TLSIZE 10  // no. of entries in track-log

    struct TLEntry {

        short xdir;     // new direction (0..7)
        short pos[2];   // position of direction change

        TLEntry()   {xdir=-1;}  // -1 == entry is invalid

        } tlog[BIKE_TLSIZE];    // [0] == most recent

    void tlog_add(short newdir);    // add entry to log

 private:
    class BikeDriver* driver;   // sits on the bike

    struct VecCursor {

        cpix pos[2];    // latest cursor position, -1 == invalid pos'n

        pcol backcol;   // color UNDER that cursor

        VecCursor() {pos[0]=pos[1]=-1; backcol=col_backgrnd;}

        } vcur; // vector direction cursor, used in analog mode

    TempTrack   track;  // if bike plots a track, it uses this

    struct TargDistCtl {
        long    vmax;   // max,
        long    vcur;   // current value, all scaled by 1024
        bool    locked; // dist was set by LockTargDist(); this avoids
                        // calls to StepTargDist()
        } tdist;

    struct TargDirCtl  {
        short   angle;  // if locked, that's targeting angle
        bool    locked; // if 0, driving direction is used
        } tdir;

    rcode   StepTargDist(void);     // see also LockTargDist
    rcode   cycleXWeapons(void);    // plays also sound

 public:
    Bike(uchar num);

    bool  isdead(void)  {   return dead;    }

    rcode step(class PlayMsg* msg);

    cpix *launchpos(short distfact = 1);
    // calc safe launch position for xweapons
    // returns ptr to cpix[2] which must be read immediately.
    // distfact is multiplied with distance to bike,
    // e.g. -1 would return a position at bike's BACKside.

    rcode RedrawVCursor();  // redraw vector cursor
    // only used in analog mode with shapes

    long TargetRange(cpix mindist);
    // targeting range in pixel

    short TargetDir(void);
    // targeting direction in ftrig degrees

    bool matchpos(cpix pos[2]); // returns non-zero if
    // pixel at 'pos' belongs to bike

    bool sethit(cpix pos[2], pcol hitcolor);    // bike is hit
    // by something. if 'pos' does not belong to a pixel
    // of the bike, this will return 0.

    void SetSteerDelay(int d);
    void SetAccelDelay(int d);

    bool hoovers(void)  {   return hoovering;   }

    void setXWCycleRequest(void);
    // if called, bike will cyclic switch it's xweapons.

    void LockTargDist(cpix todist); // used by TermDriver
    // 'todist' of -1 unlocks it

    void LockTargDir(short toangle); // used by TermDriver
    // 'toangle' of -1 unlocks it

    };

//|| Bullet
class Bullet : public MovingObj {

    cpix    oldpos[2];  // to avoid leaving a track
    pcol    oldcol;     // background color of old position

    int     charge;     // charge for explosion (default=1)

    bool    tempexpl;   // temporar explosions (0 == no by default)

    pcol    colcode;    // color code (col_dust by default)

 public:

    Bullet(

        class   Bike* bike, // bike from which Bullet is launched

        bool    tempexpl=0, // temporar bullet impacts (no by default)
        pcol    colcode=-1  // color; if -1, use default: col_dust

        );

    // Bikeless constructor:

    Bullet(

        cpix    pos[2],     // start position in playfield
        enum    Dirs dir,   // firing direction
        intf    speed,      // speed of object that fires

        bool    tempexpl=0,
        pcol    colcode=-1

        );

    rcode step(PlayMsg* pm);

    };

//  horizontal tunnel

//|| HTunnel
class HTunnel : public PlayObj {

 friend class Bike;

    const int RADV_MAX; // it takes so many relunits for a pixel

    enum Dirs dir;  // wandering direction (only UP or DOWN)

    cpix hpos[2];   // constant horizontal positions of left/right tunnel

    cpix vpos[2];   // vertical pos'n of upper/lowermost pixels

    void PosToField()   // copy vpos to field.htun[] array
    {
        field.htun[0]=vpos[0]; field.htun[1]=vpos[1];
    }

    bool shut;  // tunnel is shut

    struct  TimeCtl {

        const   int openmax;
        const   int closemax;

        int open;
        int close;

        TimeCtl(int opmax, int clmax)

            :   openmax(opmax), closemax(clmax),

                open(0), close(0)   {}

        }   time;

    bool hasswitch; // if set, TunSwitch instances exist

 public:

    HTunnel();

    rcode step(PlayMsg* pm);

    void StartOpen(void);   // start opening process
    void StartClose(void);  // start closing process
    void Switch(void);      // switch open<->shut

    bool HasSwitch(short set = -1)
         {
            if(set != -1)   hasswitch = (bool)set;
            return  hasswitch;
         }
    };

#ifndef RDEMO

//|| AddAmmo
class AddAmmo : public XWeapon {

 public:

    AddAmmo(cpix pos[2]);

    rcode mount(XWeaponBox* box, int mode=0);
    // this will only temporarily mount (XWeapon Baseclass auto-mounts),
    // increment bike's bullets cnt,
    // and then delete itself (== dismount)

    ObjCodes idcode()   {return XAMO;}  // default function

    };

//|| AddShield
class AddShield : public XWeapon {

 public:

    AddShield(cpix pos[2]);

    rcode mount(XWeaponBox* box, int mode=0);
    // this will only temporarily mount (XWeapon Baseclass auto-mounts),
    // increment bike's shields cnt,
    // and then delete itself (== dismount)

    ObjCodes idcode()   {return XSHIELD;}   // default function

    };

//|| Bazooka
class Bazooka : public XWeapon, public MovingObj {

    int blink[2];   // color blinking support

    int flymax;     // flying distance overall
    int flystep;    // flying distance countdown

 public:

    Bazooka(cpix pos[2]);   // passive position to lie around

    rcode launch(int mode=0);   // implementation of virtual function

    rcode step(PlayMsg* pm);

    ObjCodes idcode()   {return id.code;}

    };

#endif  // !defined(RDEMO)

//|| TLaser
class TargetLaser {

#   define  TLASER_SHAPES   8   // so many different shapes supported
                                // \ MUST be a power of 2 !
#   define  TLASER_MAXSPOTS 18  // max. number of spots to plot

 protected:

    pcol targcolor;         // color of pix laser points to
    // if -1, this and targ/prepos are unset
    cpix targpos[2];        // exact position where laser points to
    cpix prepos[2];         // position one pixel before hitpos

 private:

    cpix pos[TLASER_MAXSPOTS][2];   // absolute spot pixel positions
    //  -1 == invalid pos'n, to be skipped
    //  SHRT_MAX == end of table (max. end is maxspots)

    pcol backcol[TLASER_MAXSPOTS];  // color under 'spot' spots

    XWeapon *xweapon;   // xw this laser belongs to

    ushort  idxpal;     // index into color cycling palette
    short   paldelay;   // color cycling delay

 protected:

    cpix    mindist;    // minimum distance to bike

 public:

    TargetLaser(XWeapon *my, cpix MinDist);
    ~TargetLaser();

    rcode PlotTSpot();      // draw new target laser spot
    void  UnplotTSpot();    // erase old target laser spot

    };

//|| MGun
class MGun : public XWeapon, public PlayObj, private TargetLaser {

 public:

    MGun(cpix pos[2]);  // passive position to lie around

    rcode launch(int mode=0);       // implementation of virtual function

    rcode step(PlayMsg* pm);

    ObjCodes idcode()   {return id.code;}

    };

#ifndef RDEMO

//|| PGun
class PGun : public XWeapon, public PlayObj, private TargetLaser {

 public:

    PGun(cpix pos[2]);  // passive position to lie around

    rcode launch(int mode=0);       // implementation of virtual function

    rcode step(PlayMsg* pm);

    ObjCodes idcode()   {return id.code;}

    };

#endif  // !defined(RDEMO)

//|| XCharge
class XCharge : public XWeapon {

 public:

    XCharge(cpix pos[2]);

    rcode mount(XWeaponBox* box, int mode=0);
    // this will only temporarily mount (XWeapon Baseclass auto-mounts),
    // increment explosion charge of a weapon
    // and then delete itself (== dismount)

    ObjCodes idcode()   {return XCHARGE;}   // default function

    };

#ifndef RDEMO

//|| FlameThrower
class FlameThrower : public XWeapon {

 public:

    FlameThrower(cpix pos[2]);

    rcode launch(int mode=0);

    ObjCodes idcode()   {return FTHROWER;}

    };

#endif  // !defined(RDEMO)

//|| ItemGen

struct ItemCnt {

        short delay[4];     // min[0], max[1], current[2],
        //  stored[3] cnt in secs * 10

        short divdelay;     // divide delay by this

        void setrnddel();   // set a random delay

        ItemCnt(int mind, int maxd);

        //  the following is needed for replay support:

        void SetState(bool restore, ulong lotsofval = 0UL);
    };

class ItemGen : public PlayObj {

    static ItemCnt  bazo;       // 'static' because ItemGen is created new at
    static ItemCnt  xcharge;    // every playing round, and we don't want to
    static ItemCnt  addamo;     // have same XWeapon sequences every time.
    static ItemCnt  fthrower;
    static ItemCnt  lsword;
    static ItemCnt  mgun;
    static ItemCnt  pgun;
    static ItemCnt  shield;
    static ItemCnt  oil;
    static ItemCnt  mlauncher;
    static ItemCnt  katjuscha;

    long    parktime;   // both main bikes parked for so many msec's
    ushort  partycnt;   // used with parktime

 public:

    ItemGen();

    rcode step(PlayMsg* pm);

    // method to be called by a Bike:
    // Bike: "I am standing on a xweapon but I don't know how to mount.
    //        Please mount it on me."
    //
    // ItemGen: "Righto."
    rcode pleazmount(Bike* bk, int mode=0);
    // returns FAILED if xweapon wasn't mountable

    rcode calcipos(cpix ipos[2]);   // calc item (creation) position

    };

//|| RobDriver
class RobDriver : public BikeDriver, public PlayObj {

    // the following const's determine how often driver
    // scannes its environment randomly:
    const int RAND_CNTMAX ;
    const int RAND_CNTMIN ;

    bool obstacle(cpix x,cpix y);   // is obstacle on pos'n x/y?
    // uses field.obstacle() but furthermore checks if coords
    // are valid at all. If not, this is also an obstacle.

    int stepcnt;    // direction support counter
    int lastrdir;   // last random direction (-1 = none)

    int acnt;       // acceleration support counter
    bool throttle;  // current throttle

    int scandir(enum Dirs d, int maxstep, pcol &target_point_color);
    // return number of free pixels in this direction

    int sdircnt;    // delay counter for scandir()

    static short ds_delta[8][2];
    // direction->x/y step conversion table
    // e.g. in direction 0 (straight up) is deltax ([0][0]) == 0
    //      and deltay ([0][1]) == -1

    struct DirScan {

        enum    { SCANMAX=15 }; // scan max. so many pixels per direction

        bool    scanned[8]; // where set, the following entry is valid:
        uchar   pfree[8];   // so many pixels free in this dir
        uchar   pfull[8];   // so many pixels full in this dir
        short   xweapon[8]; // wherever NOT -1, so many pixels to a XWeapon

        short   spos[8][2]; // current scanning positions

        } ds;   // direction scanning

    void scandirs(bool full);
    // scan around how many pixels are...
    //  - free [full==0]
    //  - full [full==1]
    // in all directions around

    void sortDIT(uchar dit[8][2], bool lowfront=0);
    // sort direction info table.
    // each [0..7] entry contains direction index [0] and no. of pixels [1]
    // lowfront: if set, lowest value to table start else highest.

    bool ploop(short xdir); // potential loop direction
    // this determines via the bike's tracklog if we MAY drive into
    // a loop IF we will choose this direction.

 public:

    RobDriver(Bike* bike);

    bool accelerate();  // returns 0 to slow down, 1 to accelerate
    enum Dirs direction(short *vdir);
    // returns direction to turn to or NONE
    // vdir is ignored

    rcode step(PlayMsg* pm); // so we get CPU time to 'think'
    // step() is expected to be called once per field time cycle
    int  specfunc();    // returns 1 on multiple-click of button

    };

    // every BlastXWeapon() call creates an instance
    // of the following class. this instance then performs
    // the blast-off when called. this mechanism is necessary
    // 'cause a caller of BlastXWeapon might walk over the
    // list of PlayObjs using nextobj(). If, during the walk,
    // objects would be removed (by blasting them), system
    // would crash.

//|| BlastCom
class BlastCom : public PlayObj {

    enum Dirs dir;

 public:

    BlastCom(cpix pos[2], enum Dirs xdir, int delay);

    rcode   step(PlayMsg *pm);

    };

#ifndef RDEMO

//|| OilDriver
class OilDriver : public PlayObj {

#   define  DROPS_MAX   4000

    struct  Drop    {

        ushort  x,y;

        long    delay;  // exist so many msec
        //  if 0, drop is not existing

        //  all instances cleared in ::OilDriver

        }   drop[DROPS_MAX];

#   define  DROP_PIX    5   // 1 drop creates so many display pixels
#   define  DROP_RADIUS 3   // 1 drop creates pix in this radius

    static short dpix[DROP_PIX][2]; // delta posn's of these pixels

 protected:

    ushort  ifree;      // index of 1st free drop
    ushort  ihighest;   // index of last used drop
    ushort  indrops;    // total number of existing drops

    ushort  slice;      // drop processing slice counter

 public:

    OilDriver();

    rcode   step(PlayMsg *pm);

    rcode   CreateDrop(cpix x, cpix y);

    int     AvailDrops()    {   return  DROPS_MAX - indrops;    }
    //  how many drops could be created
    //  ItemGen only creates new squirts if this returns
    //  a value >= squirt's initial amo.

    };

//|| OilSquirt
class OilSquirt : private XWeapon, public PlayObj

    {

 public:

    OilSquirt(cpix pos[2]);

    rcode launch(int mode=0);

    rcode step(PlayMsg *pm);

    };

#endif  // !defined(RDEMO)

//|| LSword

class LightSword

    :   private XWeapon,
        public PlayObj
{
    pcol colcode;   // this sword's color code

    bool visible;   // line is visible

    cpix spos[2];   // start position of sword line
    cpix epos[2];   // end position of sword line

    struct {
        cpix vdpos[12]; // video-draw positions
        } old;

    cpix ppos[256][2];  // pixel positions
    //  MUST have SAME front index size as backcol[] !

    ushort  linestep;   // every nth pixel of line is visible

    pcol backcol[256];  // colors of pixels under sword line
    //  MUST be a power of 2 !

    cpix lengthmax; // of the sword, MUST be <= ibmask
    cpix length;    // of the sword, MUST be <= ibmask

    short subamo;   // counts fractions of amo

    const   ushort ibmask;  // mask for access to backcol[]

    struct  LSTime  {

        const   int openmax;
        const   int closemax;

        int open;
        int close;

        LSTime(int opmax, int clmax)

            :   openmax(opmax), closemax(clmax),

                open(0), close(0)   {}

        }   time;

    ulong   sndplayopt; // how to play the LSword sounds

    void    StartOpen(void);    // start opening process
    void    StartClose(void);   // start closing process

    ushort  angle(void);

    void    unplot(void);

    rcode   strike(void);

    short   oldang;     // old angle, to detect
    // circular movements

 public:

     LightSword(cpix pos[2]);
    ~LightSword();

    ObjCodes idcode()   {return LIGHTSWORD;}

    rcode mount(XWeaponBox* box, int mode=0);
    rcode launch(int mode=0);
    rcode step(PlayMsg* pm);
    bool  isopen(void)  {return length ? 1 : 0;}
    cpix  getlength(void)   {return length;}
};

#ifdef  WIN16
     // check if the (very large) sound file 'name' exists.
     // if so, play it using sndPlaySound.
BOOL sndTrySound(LPCSTR name, UINT wFlags);

     // if something plays, stop it:
void sndStopSound(void);
#endif

#ifndef RDEMO

//|| Missile
class Missile : public MovingObj {

    long    flytime;    // flying time countdown in msec

    long    elapsed;    // elapsed flying time

    enum    {   TPIX = 8, TIMSK = 7 };

    cpix    tpix[TPIX][3];  // [0]=x [1]=y [2]=color
    // x == -1 means no pixel yet set

    ushort  ihead;  // frontmost pixel index

    ushort  itail() {   return (ihead + 1) & TIMSK; }

    short   tplot(cpix pos[2], pcol color);

    enum    ObjCodes target;    // UNKNOWN, BIKE0, BIKE1

    short   charge;             // 1..3, where 3 is "++"

    bool    blastRequest;       // if set, blasts in step()

    short   sdelaymax;  // scan delay factor
    short   sdelaycnt;  // scan delay counter

 public:

    Missile(Bike *bike, short charge, enum ObjCodes target = UNKNOWN);

    rcode   step(PlayMsg* pm);

    ObjCodes idcode()   {return id.code;}

    void    blast(void)             // missile, blast off yourself!
        {   blastRequest = 1;   }   // blast is done in step()

    };

class MLauncher : public XWeapon
{
 public:

    MLauncher(cpix pos[2]);

    rcode launch(int mode=0);

    ObjCodes idcode()   {return MLAUNCHER;}

    };

//|| Rocket

class Rocket : public PlayObj
{
    AnalogueCtl ana;    // direction

    cpix    spos[2];    // start position

    long    curseg;     // current line segment in direction
    long    maxdist;    // max. distance in pixels
    long    maxseg;     // segments for the distance
    long    segsize;    // segment size in pixels

    long    rxmax;      // max. relative x, calc'ed on launch
    long    rymax;      // max. relative y, calc'ed on launch

    short   charge;

    bool    blastRequest;

    enum    Enum    {
        nsegs   = 4,
        fvalid  = 1
        };

    struct  Segment {
        cpix    pos[2];
        ushort  flags;
        }
        tseg[nsegs];

 public:

    Rocket(cpix pos[2], AnalogueCtl &anadir, short chrg);

    rcode step(class PlayMsg* msg);

    void    blast(void)             // rocket, blast off yourself!
        {   blastRequest = 1;   }   // blast is done in step()
};

class Katjuscha : public XWeapon
{
 public:

    Katjuscha(cpix pos[2]);

    rcode launch(int mode=0);

    ObjCodes idcode()   {return KATJUSCHA;}
};

#endif  // !defined(RDEMO)

class XWCycler : public PlayObj
{
 public:
    XWCycler();
   ~XWCycler();
    bool isdead() { return idead; }

    enum EConst {
        NSWITCHES = 5   // no. of xweapon cycle switches
        };

    rcode createswitch(cpix x,cpix y);
    // create a xwcycle switch on this position.
    // returns FAILED if max. number of switches was already created.

    rcode createmissingswitches(void);
    // if not all switches were created 'til now, create the missing ones
    // at random positions.

    rcode touchswitch(cpix x,cpix y);
    // enforce intense refresh for some time

    rcode step(class PlayMsg* msg);

 private:
    bool  idead;
    struct {
        cpix  pos[2];
        short rdelay;   // refresh delay
        short hotrdcnt; // hot refresh downcount
        }
        sw[NSWITCHES];
};
