/*   _____________________________________________________________
    /                                                             \
    | File:     ZONE OF THUNDER - main module
    \_____________________________________________________________/

    This is Public Domain.

    contents:

        mymain():

            instanciates the playfield,
            shows & handles the main menu,
            calls PlayField::Main for action.

    Developers: ID: Name:
    =========== --- -----
                JTH Juergen Thumm
  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\
 yymmdd ------- --- ---------------------------------------------------------
 940129 0.00001 JTH first prototype
 940202             first sound implementation in 43 mins.!
 940204 1.00000 JTH V 1.00 graphics, sound & scoring system complete.
 940206 1.10    JTH first great version
 940209 1.12        XWeapon: Extra Explosion Charge. RobDriver fixed.
 940210 1.13        Bazo+ tighter explosions, new ItemGen scheduling
 940213 1.14        Highlighted SHD if low on shields, highlighted Amo
 940215 1.15        Flame Thrower
 940216 1.16        RobDriver::ploop simple looping detection
 940218 1.17        FastPix high-speed graphics
 940219 1.18        sound classes fixed - hope they don't crash an more
 940220 1.19        set/match bike announcements, speed optimization
 940221 1.20        field.tunmapxy() and usage by Explosions and Flames
 940301 1.21        PlayField::open()/close(),.num,.opt
 940302 1.22        'hyperfield' option: giant playfield with soft scrolling
 940303 1.23        split source, improved xweapon spreading across playfield
 940310 1.24        intf, Flame class: int scaling now with bitshift
 940311 1.25        big(!) performance enhancements in Explosion, Flame
                    classes by optimized vector table access (using ptr)
 940313 [Rollback to 1.25]
 940314 1.26        forgotten memory fixes; virtualized ~XWeapon, ~PlayObj
 940316 1.27        ItemGen::calcipos endless loop bug fixed
 940318 1.28        explosion classes redesigned
 940319 1.29        Machine Gun. Own color. Amo incremented by 'AddAmo'
                    removed 'unknown parent' bug in Sound class, but
                    Sound classes still crash.
 940320 1.30        completely revised soundclasses.
 940322 1.31        field.blastoff mode
 940324 1.32        stuffed the code with checkpoints.
                    there were still crashes which seem to come from
                    redundant destructor calls of the MAXON compiler;
                    stuffed some classes with DCL_DESTR & CHK_DESTR, so
                    they should be immune now to this.
 940328 1.33        MGun performance fix
 940404 1.34        minor changes -> playfield.cpp, global.cpp
 940405 1.341       AmDisplay::AdjustPositions cleanup, AmDSection cleanup
 940407 1.35        Replay Mode -> global.h,.cpp, playfield.h,.cpp
 940409 1.351       FastGfx extended by FastWriteText,
                    -> playfield.cpp, global.cpp, FastPix.asm
 940412 1.352       180 degree turn non-avoid bug fixed -> Bike::lsdir
                    Bazooka movement fix, PlayField::joystick.pipe0/1
                    for better memcontrol
 940416 1.36        horizontal scrolling support
 940418 1.361       free user-selectable playfield width & height,
                    Joy11Driver: double click does no longer speed up bikes
 940803 1.37        strengthen weak players option,
                    -> 'justice' in playfield.cpp, global.cpp, global.h
 940811 1.38        compilation under Maxon 1.11.6; to do so, global.cpp
                    and playfield.cpp had to be split up into new sources:
                    xweap.cpp, robot.cpp, playfunc.cpp

 940921 1.40        ported to Windows 3.1 (basic port took 5 days).
                    fixed PF::stepobj() "objects removed" bug by a workaround
                    using sequential numbers in PlayObjects.
 940924 1.41        PORT COMPLETED. And this took just two weeks!
                    Tested 1:20 hours (5 robots) with mem surveillance.
 940926 1.42        added SHAPE heads for human-driven Bikes
 940929 1.43        shape deactivated; display now prinicipally supports
                    logical palette under WIN16, but it's usually inactive.
 941011 1.45        ANALOGUE MOVEMENTS (optional, with analogue joystick).
 941012 1.46        replay now supports analogue movements.
                    Sound ChanMan WaveSlot 'initial' logic problem fixed.
                    Sound ChanMan now supports 44 kHz, but it sounds nasty.
 941014 1.47        XWeapons now have a weight. The more weight a bike has
                    the more inertia it has when steering.
 941016 1.48        title music, directly played using sndPlaySnd
 941017 1.482       end tune after a match. sndchan.cpp: _srcxsegcpy added
 941020 1.50        dream-like, phantastic ultra mega sounds with 32 kHz !!!
                    AddShields to collect shields. XCharge has weight.
                    Bike parking support. Rapid slow down if joystick is
                    pulled against driving direction with trigger pressed.
 941023 1.51        eight new sounds (BIKETHRUWALL, BIKESTART/STOP etc.).
                    had to change Sound snd[] array in PlayField into
                    Sound *p_snd[] array 'cause "automatic data segment"
                    got larger than 64k.
 941024 1.52        RobDriver now emulates analogue driving direction
                    and can handle a CGUN.
 941025 1.53        trigger double click firing deactivated under WIN16.
                    Robots now have temporar tracks.
                    If both main bikes park too long, something happens.
 941026 1.531       MemArray limited to 32k to fix 64k problem with WIN16.
                    field.col.tracktail is now an obstacle.
 941027 1.532       SND_BIKE_CRASH stopped before playing SND_SETBIKE.
                    CANNON sound as a random mix of different subsounds.
 941029 1.533       Flame: now gets a Bike* instead of pos/dir/speed
                           and is now (also) analogue.
                    Pump Gun (PGun) class created; it's rather poor
                    so it's currently inactive.
                    Special thing on both bikes parking now more often.
                    PlayField::roundtime().
                    Dead robots are now deleted one cycle later.
 941030 1.54        random fire (FirePix class) that burns the field free.
                    PixDisplay: plot/readpix performance improved.
 941101 1.541       ItemGen::calcipos min distance to both bikes
                    CGun+,++,+++ etc. explosions optionally temporar
                    PGun reactivated with a bazooka explosion and
                    targeting range like bazo.
 941103 1.542       space in front of vtunnels,
                    music samples deactivateable,
                    joystick buttons could be swapped.
 941108 1.55        Reporter class created -> playfield.cpp.
                    Special thing on both bikes parking no longer needed.
                    Steering delay by weight disactivated.
                    Bikes driving onto field border now just loose
                    one shield. Plain MG has 'empty' sound.
                    Standartized range selection -> Bike::TargetRange()
 941109 1.551       Also sprach Zarathustra (Anfang).
 941110 1.56        Pump Gun pressure explosions
 941111 1.562       PExpl now just pressure, with own color, chargeable.
                    Free 'channel' plot to left/right of bikes at start.
                    PlayField::walktab removed, now using walksucptr.
 941113 1.564       PlayField::getcache for ::getobj boosting
                    -> PF::constr, ::getobj, operator -= of PF.
                    Replaced FirePix class by FPix / FireDriver system.
                    PExpl now blasts off xweapons lying w/i reach & sight.
                    Lying XW's now refresh their letterbox occasionally.
                    XWeaponAnchor->pos[] now contains middle of LetterBox.
 941114 1.565       BlastXWeapon() no longer removes PlayObj's from list
                    on call, but creates a BlastCom object which does
                    the job when called through ::step.
 941114 1.57        4MB compile project created. This compiles for
                    - approx. 4MB of RAM
                    - 11kHz 8 bit stereo sound
                    - 386 w/o math processor (will only be used if found)
                    !!! explib is from now on compiled w/o math inline !!!
                    field.opt.swappixcol added.
 941115 1.571       naughty 'perr' calls when sound loading failed removed.
 941123 1.572       Bike::joyElong identification, TargetRange() using it.
                    CGun impacts much less vectors.
                    SmallExpl much more vectors, but now temporar.
                    ::Flame init speeds increased, bike now does now
                    longer drive into own flame even at 120 kmh.
                    explib revised (-> .h, .cpp).
 941124 1.58        Shape::matchpos, Bike::matchpos, sethit -> advance_step.
                    TargetLaser as a base class, CGun direct bike hit.
                    Bike::crashdelay.
 941125 1.582       explib logpc fix, PixDisp minor fixes,
                    WIN16 reduction to 64 colors, shade colors deactivated.
                    Five new sounds.
 941126 1.583       Liquid class. Remainings of old MGun class removed.
                    Oil class. Bike::allow.xxx.
                    Bike steering disabled when driving through oil.
 941127 1.59        rework of display.cpp after first successfull
                    run of 1.583 on 386 / 256 VGA (but w/o loadcol()).
 941128 1.595       MONO SOUND SUPPORT by sndchan.dll.
                    SHAPE SUPPORT with robot drivers. Well, they
                    behave extremely poor now, but at least one
                    gets an impression of what the game is about
                    when they fight each other.
                    OilSquirt class. Some new sounds.
 941128 1.60        NEW STEERING. Acceleration is now done by pressing
                    the analogue joystick far enough into driving
                    direction.
 941130             renamed 'Chain Gun' to 'Machine Gun'.
 941201 1.61        optimized frequency of xweapon creation.
                    justice for weak player now includes crashdelay++.
                    base crashdelay set to 250 msec.
                    Bike::OilOnTyres added.
                    ~TargetLaser with auto-unplot.
                    Mainloop: keys now checked just every 8th cycle.
                    Replay made some problems, I hope it's fixed now;
                    the case it ain't, removed SysErr/perr messages
                    concerning it, so if replay fails there won't
                    be nasty log messages.
 941204 1.62        VGA Palette handling tested successfully on 386.
                    Light Sword. And the sounds for it.
                    Further sounds, justice/crashdelay improved,
                    crashdelay with no shields no 1 second.
                    Bike::delay.steer/accel and setting by LSword.
                    AddAmmo support for LSword.
 941205 1.621       array.cpp fixes, perfreqpipe now a pointer.
                    LSword xcharging support.
                    Replay stop with ESCAPE support.
 941206 1.622       LSword color no longer obstacle color.
                    Reporter class reworked.
 941209 1.63        Sound 32/11 kHz selection in menu.
                    All sound files from now on just 32 kHz,
                    if 11 kHz selected they're converted on loading.
                    sndTrySound, sndStopSound.
                    PF::stat.games[].
                    WIN16 FineTimer rework: could now also be compiled
                    to use timeGetTime(), but this seems to cost
                    performance. Proper delays added for timer
                    hardware access.
 941210 1.64        removed superfluous sounds.
                    All sounds reworked.
 941211 1.70        PF::showstatus.games fix. PixDisp now uses palette
                    by default.
 941211             now using extension window and opening main window
                    at negative pos'n to avoid title border.
                    Global FastTrig instance 'ftrig'.
                    AnalogueCtl class. Adapted SetMove, LSword,
                    RedrawVCursor, PlotTSpot.
                    Load NO sounds option.
                    TLaser no longer plots over Bike shape color.
 941213             tried explicite _export with WinMain & MainWndProc
                    and this crashed, so removed it. revised .def file.
 941214             Joystick swap bug fixed. Round start now by any
                    reaction from both joysticks.
 941217             int -> cpix/pcol adaption in all sources.
                    replaced 'const int' colors by 'enum Color'.
                    PD's FrameBuffer now uses line adress array.
                    inlined PlayObj::CPUentry, PF::obstacle/convdir.
                    optimized PF::nextobj.
 941220             Bike::tdist, TargetRange() now dynamic.
 941221             No. of colors now 64, backgrnd pattern with WIN16.
                    (-> global.h, playfunc, playfield).
                    PD::move/draw/line now use native WIN16 functions.
 941222             OpenChanMan: sound now set by first sound.
                    raw sound file default speed now 32000 with WIN16.
                    raw file name extension now PCM.
 941213             Bike::joyVec[], direct but delayed adaption of
                    joyVec to target distance.
                    TargetLaser now supports a mindist.
                    Diverse bikevcur<->tlaserspot<->shape plot fixes.
                    Reporter now also looks at amo. Four new sounds.
                    PGun now 3 bullets per AddAmmo.
                    Tunnel switch created, but it's no great fun,
                    so deactivated it.
 941225             Fixed fixwalls. Grass. Nitro. NitroExpl.
                    -> adapted LSword::strike, Shape::prescan.
                    Machine gun's ID now MGUN throughout the source.
                    PF::Main rseed's now ushort.
                    field.opt.playmode, PLAYMODE_NITROBUSHES.
 941228             mini fixes: col_nitro now evaporates with oil,
                    bike thru wall hoovering snds, col_lswordx
                    doesn't hurt bikex, nitroexpl w/o impactexpl.
                    Shape::prescan takes lsword col as backgrnd.
 941229             col_flame now first obstcol, tlaser ignores it.
 950102             RobDriver acceleration fix.
                    Global constructor cleanup.
                    PlayField::scanline with playfield border check.
                    NitroExpl class replaced by CrtNitroExpl().
                    TLaser usable again at field border.
                    PGun TLaser on obstacle fixed.
 950103             PExpl impact through flames.
                    AnalogueCtl::CalcVDir, MovingObj::SetDir/Speed.
                    Missile and Rocket Launcher.
 950104             Cruise Missiles. more .ini options.
                    final magic options (bazo wall, oil puddles).
                    LightSword border plot bug fixed.
 950122 1.711       minor fixes -> xweap.cpp, global.cpp
                    magic codes reduced to three digits.
 950128 1.72        DIVISION BUG FIX. -> playfunc, convdir(cpix,cpix).
 950307 1.74        sndchan.dll fixed, hopefully.
 950309 1.75        using Windows timeGetTime instead of HW timer.
 950310 1.76        get positions only of USED joysticks. the unused ones
                    held the system for a while.
                    color palette: backgrnd color now no. 0, which works
                    better in Win-OS/2 sessions.
 950311 1.77        Sound::Resample with blow and shrink.
                    22 kHz sound support.
                    Warning on out-of-date SndChan DLL.
                    The larger the field the more weapons are created.
                    We can NOT just set a higher maxspeed for the bikes,
                    because anything is optimized to max speed 120.
                    Weak support: now inital values selectable.
                    Pressure explosion blasts flying missiles.
                    Added missile sounds.
 950312 1.78        FineTimer::open(SW/HW),close(). Additional speed
                    or hardware access selectable.
                    Bazooka now also active when missiles selected,
                    but then it blasts off at obstacles.
                    Playstick: hwfetch() call bug fixed.
                    Cruise missiles now semi-official option.
 950319 1.80 pre    complete rework of SND_xxx. Now there are two
                    groups of sounds, base and extension.
                    support for lots of extension sounds, esp. new
                    collection sound support.
                    timing option now hidden.
                    weak support now +3, +6, +9.
                    missiles now official weapon, but 20% less often.
                    WaveExpl class created. this is now used with Bazooka.
                    col_waveexpl added.
                    MGun no longer uses ImpactExpl but Explosion, with
                    color-cycling.
 950320             deactivated use of waveexpl for bazo, looked too
                    anti-dynamic.
 950321             PlayField::selectRandomSound() added.
                    up to 10 out-of-shield sounds support.
                    up to 10 setbike musics support.
                    timing speed now official option.
                    hidden option for brighter backgrnd.
 950325             ColorManager.
                    Bazo Explosion with color cycling.
                    Shape now has backward pointer to it's Bike.
                    Shape sets new setXWCycleRequest() of Bike.
                    [TargetLaser some more points, with color cycling.]
                    Sound immediate stop on pause and if space pressed
                    while loading sounds.
                    Snake robots now ignore col_mgimpact/flame as obstacles.
 950326             TrueColor support w/o color mapping.
                    Dashboard full display of two frontmost weapons.
 950329             big headline. field size auto-adapt to desktop size.
                    .ini only needed for optional open-y-delta parameter.
                    field::clear w/o flickering.
                    xweapon creation now switchable.
 950402 1.81        ItemCnt: no setrnddel in ctr, but initial in setstate
                    if first call to setstate in the session. therefore
                    direct DisplayWidth access no longer needed.
                    .ini now supports lots of parms.
                    PlayField ctr full option default setup.
                    No longer default option setup in main().
                    Removed DisplayWidth (now field.opt.openwidth)
                    and DisplayHeight (now field.opt.openheight).
                    PixelDisplay ctr now zero-inits hwfrm/hwsize.
                    main(): after field.open(), field.opt.swappixcol
                    set in .ini is promoted into PixelDisplay.
                    Stacksize reduced to 20000, shouldn't make problems.
 950408 1.82        Complete rework of zoth.cpp, complete title restyling.
                    PD::plotby,unplot. TargetLaser spot using plotby.
                    BikeVCursor using plotby.
                    .ini now also takes hex values.
 950409             selectable target laser shapes. field.opt.showmouse.
 950415             added col_fire. Now, Flame and FirePix have separate
                    color codes.
                    Flame no longer uses fat pixels, it cost too much
                    performance.
                    totalSeconds() now using GetCurrentTime() to avoid
                    midnight overflow.
                    Missiles no longer scan every step.
                    No longer HW timer support.
                    Timing speed now main option again.
 950416             BaseExpl now wall-destructing if col_waveexpl.
                    Explosion class plotting larger blank center
                    if col_waveexpl.
                    PixelDisplay::lineby,unline,
                    PlayField::unplot,unline.
                    TLaser now using PF::unplot().
                    Rockets using lineby() and unline().
 950417 1.83        FThrower now inactive by default (with fat pixels again).
                    col_waveexpl now also creates some fire.
                    Added some checkpoints to function entries.
                    Backgrnd color write access surveillance added.
 950418 1.84        PixelDisplay::goPalette bugfix: now initializing all
                    Windows system colors with PC_NOCOLLAPSE.
 950421 1.85        PF::opt.debugmode; .ini 'logfile' and 'showsounds' keys.
                    PerfMon lots of infos.
                    Upto 20 bknosh sounds. Upto 15 setbike sounds.
                    Upto 5 duce sounds.
                    Katjuscha firing sound prio over hit sound.
                    Fixwalls burnable again.
                    Oil off by default.
 950422             all sound selection now done using rand(), except
                    for selectRandomSound which may optionally use altrand().
                    Shape::unplot now checks for SHAPE_PLOTTED.
                    PF::getcache optimization: cache entry for an object group
                    is now adapted in PF::op +=/-=. Therefore the entry MUST
                    be set if ANY objects of that type exist. -> playfunc.cpp.
                    PExpl now also blasts off Rockets.
                    Moved Rocket class declaration to global.h.
                    PlayObj ctr now enforces an 'enum Objcode' idcode.
                    Added Rocket::spos[], rocket now moves it's pos[].
                    JoyPipe::setmode now also does extjinf[].reset().
 950423 1.9         div 0 crash provocation via magic number combination
                    to test behaviour of WinSpector.
                    perr provocation magic number to test logfile creation.
                    PF::playsound now ignores stereo flag if mono mode active.
 950430             Oil and FThrower now activateable in menu with keys.
                    Default Target Laser shape is now small narrow arrows.
                    repow1 now standard sound. threshold is BKNOSH0.
 950501             ScalarPipe::flush now public. added JoyPipe::flush.
                    PF::TerminalCrash() now flushes pipes if in record mode.
                    PF::Main: checkup() etc. now called only in MEMTRACK mode.
                    PF::playsound now tries for some sounds to map them to
                    others if they're not loaded.
                    PF::msg joystick query fixed.
                    Sound loading could now be stopped with ESCAPE.
                    Renamed 'RLauncher' to MLauncher, and same with sounds.
                    Renamed 'plusxep/sp' to 'plusx', as with SND_ symbols.
                    Removed 'overlay sounds'.
                    MGun 4+: added SND_MGUN4F1,2,3,E. added playing of these
                    and mapping in PF::playsound.
 950503 1.95        inactivated whole replay code by ifdef's.
                    array.cpp removed from project.
                    put unused code into 'ifndef ZOTH' sections.
 950508             Rocket premature detonation fix, now checking for
                    a pixel min distance instead no. of flown segments.
                    Upto 30 setbike sounds. Upto 10 duce sounds.
                    Upto 40 bknosh sounds.
                    PF::loadsound more compact filename displaying.
                    Checking sndchan.dll version again.
                    PF::loadsound now shows more columns if showsounds
                    selected.
                    PlayStick cyclesize for hwfetching now
                    proportionally increased with taddspeed.
 950605             playable Demo project created.
                    chdir("..") at end of program.
                    isdown() i/o pressed() check for KEY_ESCAPE/SPACE
                    while loading sounds.
 950610             PF::LoadSound: if demo, keeping space for info text.
                    Demo: showing info text while loading sound.
                    Demo: no ammunition but xcharge.
                    Demo: no magic numbers, no weapon switching in menu.
                    Demo: no weak support but 'Deutsche Infos' switch.
                    Now upto 40 setbike and upto 50 noshd sounds.
                    If no HTunnel pass sound loaded, no stopsound
                    now done before trying to play pass sound.
                    Demo: default 22 kHz.
                    Demo: written by Juergen Thumm in swaptext.
                    Demo: press space to continue after sound loading.
 950611             Default backgrnd colors now brighter.
                    Default TLaser shape fat wide arrows.
 950712             added TermDriver class, a Terminator BikeDriver.
                    -> global.h, term.cpp.
                    Non-Main-Bikes are now always terminators.
 950714             term.cpp improvement; created term.hpp;
                    added LightSword::isopen().
                    The following preproc symbols are now checked:
                        DEMO  - for ANY playable demo.
                        RDEMO - for REDUCED demo.
                        NDEMO - NERVY demo.
                    Added Bike: friend CreateTerminator().
                    Added magic key to create terminator.
                    Automatic terminator creation after session/roundtime
                    combination in PF::Main.
                    Reworked the info text's during sound loading.
                    Created order.txt and bestell.txt.
                    NDEMO: no missiles and rockets creation,
                           these are only for terminator.
                    Added sound SND_TERMCRT.
 950715             Missiles now also in NDEMO.
                    Changed himsg.time to himsg.cnt; fixed displaying
                    time of global messages.
                    Now issuing message in CreateTerminator().
 950716             moved TermCrt by key to ItemGen::step.
 950722             Demo: adapted info text's.
                    Rocket impact expl halve vecs if not hitting a bike.
                    SmallExpl 1/3 less vecs.
                    MAGIC_CRTTERM i/o terminator creation by key.
 950723             Replaced WaveExpl by NukeExpl, added MAGIC_NUKEBAZO.
                    Full version: if showsounds, wait for space key
                    after loading sounds.
 950801             now upto 70 bknoshd sounds. Now, if >= 40 noshd sounds
                    available, they're played always if out of shields.
 950805             german info text: "order.txt" -> "bestell.txt".
 950823             in single player mode unlimited playing time w/o
                    terminator creation. in two player mode now first
                    terminator after 20 minutes.
 950827             score file support:
                        global.h    added field.stat.loaded
                                    added PF::savescore()/loadscore()
                        zoth.cpp    'S'/'L' check in mainmenu
                        PF::ctr     clear 'stat'
                        PF::checkpause  'S' check while playing
 950916             sound class now supports ResampleTo(). Changed
                    PF::loadSound accordingly, and the calls to it.
                    LightSword: SND_LSMOVEx support, added oldang.
                    PF::playSound: added LSMOVE2,3 mapping. SND_CANF2,3,4
                    now check if SND_CANF1 loaded and then map accordingly.
                    Added "Press 'X' in MainMenu" in the demo text's.

 951119 1.96 JTH    removed any old pixelshape stuff. added shape capability
                    to PlayObj. Bike now uses 'real' shape.
                    PF supplies ShapeClasses. Some MovingObj methods
                    automatically also adapt shape pos/dir, if available.
 951202             PD: changed gethdc() to getcurhdc(), added get's for
                    front- and backhdc, transRect, setLevel.
 951209             TermDriver now does also direct-mount self-created weapons.
 951227 1.98 JTH    added mgamoc, pgamoc etc. sounds.
 951228             added SND_LOSTSHxx and handling (Bike::shdloss stuff).
*/

#   define  VerStr "1.98"

#   define  REQ_CHANMAN_VERSION 0x0115  // requires ChanMan x.xx

#   include "expdef.h"

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

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

const char *vstr="$VER: ZOTH " VerStr ;

const char* VER_STR = VerStr;   // for other modules

//  ======================================================================

#ifdef  AMIGA
void main(int argc, char *argv[])
{
 int mtrack=0;

    if(argc && argv[1] && !strcmp(argv[1], "-m"))   mtrack = 2;

    ROOTNAME.Prolog(argc, argv, LOG_FILE_NAME, 1 | mtrack);

    if(ROOTNAME.HelpRequest())
    {
        printf(

        "Zone of Thunder " VerStr " by Jürgen Thumm\n\n"
        "Usage: zoth [-options]\n"
            "   -h : number of Human players (0..2)\n"
            "   -r : number of robot players (0..about 20)\n"
            "   -v : sound Volume (0..100)\n"
            "   -b : Beginner mode\n"
            "   -j : swap Joysticks\n"
            "   -s : no Scoring\n"
            "   -S : strengthen weak players\n"
            "   -x : X-large playfield (size 320 x 600)\n"
            "   -pwnnn : playfield width  nnn (320 ... 1024)\n"
            "   -phnnn : playfield height nnn (256 ... 1024)\n"

        "\nwhile playing, press SPACE for pause,\n"
        "and cursor left/right for volume.\n"

        "\nafter every round, SPACE for replay.\n"
        "in replay, AMIGA/ALT right for replay speed change.\n"

            );

        return;
    }

    ushort us;

    char* opt;
    while((opt = ROOTNAME.NextOption()) != NULL)
    {
        switch(*opt++)
        {
            case 'h' : field.num.humans     = atoi(opt);    break;
            case 'r' : field.num.robots     = atoi(opt);    break;
            case 'v' : field.volume         = atoi(opt);    break;
            case 'b' : field.opt.beginner   = 1;            break;
            case 'j' : field.opt.swapsticks = 1;            break;
            case 's' : field.opt.noscoring  = 1;            break;
            case 'S' : justice.active       = 1;            break;

            case 'x' :  field.opt.hyperfield    = 1;
                        field.opt.openwidth     = 320;
                        field.opt.openheight    = 600;
                        break;

            case 'p' :  if(*opt == 'w')
                        {
                            us = atoi(opt+1);
                            if(us < 320)    us = 320;
                            field.opt.openwidth = us;
                            if(us > 320)    field.opt.hyperfield = 1;
                        }
                        else if(*opt == 'h')
                        {
                            us = atoi(opt+1);
                            if(us < 256)    us = 256;
                            if(us > 1024)   us = 1024;
                            field.opt.openheight = us;
                            if(us > 256)    field.opt.hyperfield = 1;
                        }
                        break;

            // developer private stuff:
            case 'd' : field.opt.debug      = 1;            break;
            case 'm' :
                if(mtrack != 2)
                {   printf("-m must be FIRST option!\n");   return; }
                break;  // handled above

            default : printf("illegal option %s\n",--opt); return;
        }
    }

    field.num.players   = field.num.humans + field.num.robots;

    if(field.open() == OK)
    {
        field.Main();
        field.close();
    }
}

#else   // WIN16 compile

static ulong ulSessionStart = 0;    // in seconds

ulong totalSeconds(void)
{
    return (ulong)GetCurrentTime() / 1000UL;

   // static time_t tt; time(&tt); tm *t = localtime(&tt);
   // return t->tm_hour * 3600 + t->tm_min * 60 + t->tm_sec;
   // t->tm_mday, t->tm_mon+1, t->tm_year, t->tm_hour, t->tm_min, t->tm_sec
}

ulong sessionMinutes(void)
{
    return (totalSeconds() - ulSessionStart) / 60UL;
}

//  ==================================================
//  ==================== MAIN MENU ===================
//  ==================================================

void d3frame(PixelDisplay &pd, cpix x,cpix y,cpix w,cpix h,bool inverse)
{
    pd.color(col_misc);

    ushort rgb0 = 0x888;
    ushort rgb1, rgb2;

    if(inverse)
    {
        rgb1    = rgb0 - 0x222;
        rgb2    = rgb0 + 0x222;
    }
    else
    {
        rgb1    = rgb0 + 0x222;
        rgb2    = rgb0 - 0x222;
    }

    pd.loadcol(col_misc, rgb1);
    pd.line(x,  y,  x+w,    y   );
    pd.line(x,  y,  x,      y+h );

    pd.loadcol(col_misc, rgb2);
    pd.line(x+w,y+h,x+w,    y   );
    pd.line(x+w,y+h,x,      y+h );
}

void textout(PixelDisplay &pd, cpix x,cpix y,char *txt,pcol c)
{
    SetTextColor(pd.getcurhdc(), pd.wincolref(c));
    TextOut(pd.getcurhdc(), x,y, txt, strlen(txt));
}

void drawtext(PixelDisplay &pd, cpix x,cpix y,cpix w,cpix h,char *txt,pcol c)
{
    SetTextColor(pd.getcurhdc(), pd.wincolref(c));

    RECT rhl;
    rhl.left    = x;
    rhl.right   = x+w-2;
    rhl.top     = y;
    rhl.bottom  = y+h-2;

    DrawText(pd.getcurhdc(), txt, -1, &rhl, DT_CENTER|DT_VCENTER);
}

struct CAnim
{
    short   val,dir;
    short   minval,maxval;

    CAnim(short minv=4, short maxv=15)
        {   val=10; dir=1; minval=minv; maxval=maxv;    }

    short   step(void);
};

short CAnim::step(void)
{
    val += dir;

    if(val == maxval)
    {
        dir = 0 - dir;
        return 2;
    }

    if(val == minval)
    {
        dir = 0 - dir;
        return 1;
    }

    return 0;
}

struct Setting
{
    cpix    x,y;
    char    *key;
    char    *text;
    long    ivalue;
    short   changed;    // changed recently
    short   cdelay;     // color cycling delay
    CAnim   canim;

    Setting()
    {
        x = y   = 100;
        key     = "A";
        text    = "n/a";
        ivalue  = 0;
        changed = 0;
        cdelay  = 0;
    }

    void    set(char *xkey, char *xtext)
            {   key = xkey; text = xtext;   }

    long    value(void)
            {   return ivalue;  }

    void    setval(long newval)
            {   ivalue = newval; changed = 2;   }
};

struct CycleText {

    cpix    x,y,w,h;

    char    **ttab;
    CAnim   canim;
    ushort  itext;

    PixelDisplay &pd;

    CycleText(char *textTab[],PixelDisplay &pd,cpix x,cpix y,cpix w,cpix h);

    void    step(void);

    };

CycleText :: CycleText( char *textTab[],PixelDisplay &rpd,
                        cpix x1,cpix y1,cpix w1,cpix h1 )
:   pd      (rpd),
    ttab    (textTab),
    canim   (0,50)
{
    x       = x1;
    y       = y1;
    w       = w1;
    h       = h1;
    itext   = 0;
}

void CycleText::step(void)
{
    short  rc  = canim.step();
    ushort rgb = 0;
    bool   clearback = 0;

    switch(canim.val)
    {
     case  0: rgb = 0x888; clearback = 1; break;
     case  1: rgb = 0x887; break;
     case  2: rgb = 0x877; break;
     case  3: rgb = 0x976; break;
     case  4: rgb = 0x966; break;
     case  5: rgb = 0x965; break;
     case  6: rgb = 0xa55; break;
     case  7: rgb = 0xa54; break;
     case  8: rgb = 0xb44; break;
     case  9: rgb = 0xb43; break;
     case 10: rgb = 0xc33; break;
     case 11: rgb = 0xc32; break;
     case 12: rgb = 0xd22; break;
     case 13: rgb = 0xd21; break;
     case 14: rgb = 0xe11; break;
     case 15: rgb = 0xe10; break;

     default: rgb = 0xf00; break;
    }

    if(clearback)
        pd.transRect(x,y,x+w,y+h);
    else
    {
        pd.loadcol(col_misc, rgb);
        SetBkMode(pd.getcurhdc(), TRANSPARENT);
        drawtext(pd, x,y,w,h, ttab[itext],col_misc);
    }

    if(rc == 1)
    {
        itext++;
        if(!*ttab[itext])   // if eot reached
            itext   = 0;    // wrap around
    }
}

//  =============== SETTINGS AND THEIR HANDLING ==================

#define NSETS       10

Setting setg[NSETS];

#define SHUMANS     setg[0]
#define SROBOTS     setg[1]
#define STPOWER     setg[2]
#define SJOYS       setg[3]
#define SWEAK       setg[4]
#define SMUSIC      setg[5]
#define SSTEREO     setg[6]
#define SLOAD       setg[7]
#define SSWAPPIX    setg[8]
#define STIMER      setg[9]

void showsettings(PixelDisplay &pd, short cs0, short cs1)
{
#   define S        setg[i]

    static char  buf[80];
    ushort i,k;
    cpix   tx,ty;
    static short cdelay = 0;

    setg[0].set("H","HUMAN PLAYERS");
    setg[1].set(" ","TERMINATORS  ");
    setg[2].set("P","TERM. POWER  ");
    setg[3].set("J","JOYSTICK SWAP");
    setg[4].set("W","WEAK SUPPORT ");
    setg[5].set("M","MUSIC FX     ");
    setg[6].set("S","STEREO SOUND ");
    setg[7].set("L","LOAD SOUND   ");
    setg[8].set("X","SWAP PIXCOLOR");
    setg[9].set("T","TIMING SPEED ");

    short   yrange = pd.csize(1)*6L/10L;
    long    ystep  = (long)yrange / (NSETS + 1);

    cpix    wbutton = cs0 + 20;
    cpix    hbutton = (yrange / (NSETS+1)) - 4;

    for(i=0; i<NSETS; i++)
    {
        S.x = 100;
        S.y = pd.cfrm(1) + pd.csize(1)*3L/10L + ystep * i;

        if(*S.key != ' ')
            d3frame(pd,S.x-2,S.y-2,wbutton,hbutton,S.changed ? 1 : 0);

        SetBkMode(pd.getcurhdc(), TRANSPARENT);

        pd.loadcol(col_misc, S.canim.val);
        drawtext(pd,S.x,S.y,wbutton,hbutton,S.key,col_misc);

        textout(pd,S.x+wbutton*2,S.y,S.text,col_misc);

        static char *ttLoadSnd[]    =
             {  "32 KHZ","22 KHZ","11 KHZ","NONE  " };

        switch(i)
        {
         case 3:
         case 5:
         case 6:
         case 8:    sprintf(buf, "%s ", S.value() ? "YES" : "NO ");
                    break;

         case 7:    sprintf(buf, "%s ", ttLoadSnd[S.value() & 3]);
                    break;

         case 4:    if(justice.active)
                      sprintf(buf, "%01d:%01d  ",
                          justice.support[0], justice.support[1]);
                    else
                      sprintf(buf, "OFF ");
                    break;

         case 2:    sprintf(buf, "%ld%c", S.value() * 10, '%%');
                    break;

         default:   sprintf(buf, "%ld ", S.value());
        }

        tx = pd.cfrm(0)+pd.csize(0)*3L/4L;
        ty = S.y;

        if(S.changed == 2)
            pd.transRect(tx,ty,pd.cfrm(2)-1,ty+ystep); // unplot from backmap

        textout(pd,tx,ty,buf,col_misc);

        // cycle colors

        S.canim.step();

        if(S.changed)
            S.changed--;
    }

#   undef   S
}

#define DRAW_TEST(xc,rgb,y,str) \
    \
    field.color(xc);                        \
    field.loadcol(xc, rgb);                 \
    field.rectfill(10,y,20,y+csy*2, xc);    \
    for(rx=0; rx<10; rx++)                  \
        for(ry=0; ry<csy*2; ry++)           \
                field.plot(30+rx,y+ry,xc);  \
    field.rectfill(50,y,50+csx*strlen(str),y+csy);  \
    field.drawmode(NORMAL);                 \
    field.color(col_backgrnd);              \
    field.text(50,y, str);                  \
    field.drawmode(NORMAL2);                \
    field.color(xc);                        \
    field.text(50,y+csy, str);

bool switchxw = 0;

void changesettings(PixelDisplay &pd)
{
#   define  HANDLE(setting,nval)    \
            \
            if(kb.pressed(*setting.key))    \
            {   ps  = &setting;             \
                setting.setval((setting.value()+1)%nval);

    bool     drawtest = 0;
    KeyBoard kb;

    Setting *ps = 0;

    HANDLE(SHUMANS,3)
        field.num.humans = ps->value();
        field.num.robots = 2 - field.num.humans;
        SROBOTS.setval(field.num.robots);
    }

    /*
    HANDLE(SROBOTS,3)
        field.num.robots = ps->value();
        if(field.num.humans + field.num.robots < 2)
        {
            field.num.humans    = 2 - field.num.robots;
            SHUMANS.setval(field.num.humans);
        }
        if(field.num.humans + field.num.robots > 2)
        {
            field.num.robots    = 2 - field.num.humans;
            SROBOTS.setval(field.num.robots);
        }
    }
    */

    HANDLE(SJOYS,   2)
        field.opt.swapsticks = ps->value();
    }

    HANDLE(SWEAK,   8)
        short js0,js1,a;
        switch(ps->value())
        {
         case 0: a=0; js0=0; js1=0; break;
         case 1: a=1; js0=0; js1=0; break;
         case 2: a=1; js0=3; js1=0; break;
         case 3: a=1; js0=6; js1=0; break;
         case 4: a=1; js0=9; js1=0; break;
         case 5: a=1; js0=0; js1=3; break;
         case 6: a=1; js0=0; js1=6; break;
         case 7: a=1; js0=0; js1=9; break;
         default: SysErr(704951926);
        }
        justice.active     =   a;
        justice.support[0] = js0;
        justice.support[1] = js1;
    }

    HANDLE(SMUSIC,  2)
        field.opt.nomusic  = ps->value() ^ 1;
    }

    HANDLE(SSTEREO, 2)
        field.opt.stereo   = ps->value();
    }

    HANDLE(SLOAD,   4)
        switch(ps->value())
        {
         case 0: field.opt.khz = 32; break;
         case 1: field.opt.khz = 22; break;
         case 2: field.opt.khz = 11; break;
         case 3: field.opt.khz =  0; break;
         default: SysErr(704951929);
        }
    }

    HANDLE(STPOWER, 11)
        field.termpower.initial   = ps->value() * 10;
        field.termpower.increment = ps->value() /  2;
    }

    HANDLE(SSWAPPIX,2)
        field.opt.swappixcol    = ps->value();

        // promote swappixcol selection to display:

        PixelDisplayDA pda(&pd);

        if(field.opt.swappixcol)
            pda.SetWinMode(WINM_SETPIXSWAPRB);
        else
            pda.ClrWinMode(WINM_SETPIXSWAPRB);

        drawtest = 1;
    }

    HANDLE(STIMER,10)
        field.taddspeed         = ps->value();
    }

    //  ===== hidden official stuff =====

    //  save/load score:
    //  only for the full version, which is NOT expected
    //  to be started from CD-ROM, therefore the local filename "score.dat".

    if(kb.pressed(KEY_F7))
        field.savescore(0); // 0 == called from main menu

    if(kb.pressed(KEY_F5))
        field.loadscore();  // called only from main menu

    if(!switchxw && kb.pressed('1'))
    {
        if(field.opt.usewinpal ^= 1)
            field.goPalette(PF_NCOLORS, field.startcols);
        else
            field.goTrueColor(PF_NCOLORS, field.startcols);

        pd.color(col_shadow); pd.drawmode(NORMAL2);
        pd.text(10,10, field.opt.usewinpal ? "palette on " : "palette off");

        drawtest = 1;
    }

    if(drawtest)
    {
        // determine 3 colors which will
        // be re-initialized later automatically,
        // and can thus be used now for tests:

        pcol col1 = col_smallexpl;
        pcol col2 = col_alarm;
        pcol col3 = col_bigexpl;

        ushort rx,ry;       // used in DRAW_TEST macro
        ushort csx = pd.charsize[0];    // "
        ushort csy = pd.charsize[1];    // "

        // draw test picture

        short  yb  = SSWAPPIX.y - csy * 5;

        DRAW_TEST(col1, 0xF00,yb      , "RED  ");
        DRAW_TEST(col2, 0x0F0,yb+csy*3, "GREEN");
        DRAW_TEST(col3, 0x00F,yb+csy*6, "BLUE ");
    }

    if(!switchxw && kb.pressed('2'))
    {
        static uword cinc = 0;

        cinc = ++cinc & 3;

        field.startcols[col_backgrnd ] = 0x006 + cinc;
        field.startcols[col_backgrnd2] = 0x007 + cinc;
        field.startcols[col_backgrnd3] = 0x005 + cinc;

        pd.color(col_shadow); pd.drawmode(NORMAL2);
        pd.text(10,10, "brighter background (%u)", cinc);
    }

    //  ===== hidden inofficial stuff =====

    if(kb.pressed(KEY_F12) || switchxw)
    {
        // switch XWeapons off/on:

        if(!switchxw)
            switchxw    = 1;
        else
        {
            short v = -1, itxt = -1;

            if(kb.pressed('0')) v = 0;
            if(kb.pressed('1')) v = 1;
            if(kb.pressed('2')) v = 2;
            if(kb.pressed('3')) v = 3;
            if(kb.pressed('4')) v = 4;
            if(kb.pressed('5')) v = 5;
            if(kb.pressed('6')) v = 6;
            if(kb.pressed('7')) v = 7;
            if(kb.pressed('8')) v = 8;
            if(kb.pressed('O')) {v = 24; itxt =  9;}
            if(kb.pressed('F')) {v = 25; itxt = 10;}

            if(itxt == -1)
               itxt = v;

            if(v != -1)
            {
                static char *wnames[11] =
                {
                    "bazooka",
                    "extra charge (+)",
                    "ammunition",
                    "light sword",
                    "machine gun",
                    "pump gun",
                    "additional shields",
                    "cruise missiles",
                    "rocket launcher",
                    "oil",
                    "flame thrower"
                };

                pd.color(col_shadow); pd.drawmode(NORMAL2);

                if(field.opt.magic & MAGIC_LOTSOF)
                {
                    field.opt.lotsof ^= (1UL << v);
                    pd.text(10,10, "%s of %s ",
                            (field.opt.lotsof & (1UL << v)) ? "lots":"less",
                            wnames[itxt % 11]);
                }
                else
                {
                    field.opt.cxw ^= (1UL << v);
                    pd.text(10,10, "%s %s ",
                            wnames[itxt % 11],
                            (field.opt.cxw & (1UL << v)) ? "on":"off");
                }

                switchxw = 0;
            }
        }
    }

    if(kb.pressed('C'))
    {
        field.opt.showmouse ^= 1;
        pd.color(col_shadow); pd.drawmode(NORMAL2);
        pd.text(10,10, "mouse cursor %s while playing",
                field.opt.showmouse ? "on " : "off");
    }

    if(kb.pressed('N'))
    {
        field.opt.alwaysmusic ^= 1;
        pd.color(col_shadow); pd.drawmode(NORMAL2);
        pd.text(10,10, "play music %s",
                field.opt.alwaysmusic ? "always     " : "on setbikes");
    }

    if(kb.pressed('I'))
    {
        field.termpower.increment >>= 1;
        pd.color(col_shadow); pd.drawmode(NORMAL2);
        pd.text(10,10, "terminator: power increment %d percent",
                        (short)field.termpower.increment);
    }

    //  magic number detection
    enum { NKEYS = 7, NCODES = 8 };

    uchar   dig;
    bool    disp = 0;
    static  ulong   num = 0;

    static uchar kdigit[NKEYS][2] =
    {
        '0',0, '4',4, '5',5, '6',6, '7',7, '8',8, '9',9,

        /*
        KEY_NUMPAD0, 0, KEY_NUMPAD1, 1, KEY_NUMPAD2, 2,
        KEY_NUMPAD3, 3, KEY_NUMPAD4, 4, KEY_NUMPAD5, 5,
        KEY_NUMPAD6, 6, KEY_NUMPAD7, 7, KEY_NUMPAD8, 8,
        KEY_NUMPAD9, 9, KEY_ADD    ,10
        */
    };

    static ulong mcodes[NCODES][2] =
    {
        754L, MAGIC_LOTSOF,
        869L, MAGIC_OILPUDDLES,
        468L, MAGIC_BAZOWALL,
        797L, MAGIC_CRTTERM,
        449L, MAGIC_NUKEBAZO,

        59678UL, 1UL<<29,
        96785UL, 1UL<<30,
        58769UL, 1UL<<31
    };

    for(ushort i=0; i<NKEYS; i++)
    {
        static ulong div0pk = 0;    // div 0 provocation key

        if(kb.pressed(kdigit[i][0]))
        {
            //  process digit

            if((dig = kdigit[i][1]) == 10)
                num = 0;    // reset number
            else
                num = num * 10L + dig;

            for(ushort k=0; k<NCODES; k++)

                if(mcodes[k][0] == num)
                {
                    if(mcodes[k][1] >= (1UL<<29))
                        div0pk  ^= mcodes[k][1];
                    else
                        field.opt.magic ^= mcodes[k][1];
                    num  = 0;
                    disp = 1;
                }

            if(((div0pk >> 30)&3)==3)
            {
                //  provoce div0 crash to test error handling
                ushort a=0, b=0, c=0;
                c = a / b;
            }

            if(div0pk & (1UL<<29))
            {
                div0pk ^= (1UL<<29);
                perr("test error");
            }
        }

        if(disp)
        {
            pd.color(col_shadow); pd.drawmode(NORMAL2);
            pd.text(10,10, "%lx ", field.opt.magic);
            if(div0pk >= (1UL<<30))
                pd.text(field.csize(0)>>1,10, "div0: confirm");
        }
    }
}

rcode MainMenu(PixelDisplay &pd, PixelDisplayDA &pda)
{
    bool crsrhidden = 0;

    if(!field.opt.showmouse)
    {
        ShowCursor(0);
        crsrhidden  = 1;
    }

    rcode rc = OK;

    switchxw = 0;

    //  initialize SETTINGS array:
    SHUMANS.ivalue  = field.num.humans;
    SROBOTS.ivalue  = field.num.robots;
    SJOYS.ivalue    = field.opt.swapsticks;
    SMUSIC.ivalue   = (field.opt.nomusic & 1) ^ 1;
    SSTEREO.ivalue  = field.opt.stereo;
    SSWAPPIX.ivalue = field.opt.swappixcol;
    STIMER.ivalue   = field.taddspeed;
    STPOWER.ivalue  = field.termpower.initial / 10;

    switch(field.opt.khz)
    {
        case 32: SLOAD.ivalue = 0; break;
        case 22: SLOAD.ivalue = 1; break;
        case 11: SLOAD.ivalue = 2; break;
        case  0: SLOAD.ivalue = 3; break;
    }

    //  TimingSpeedOpt  = field.taddspeed % MAPTADDTSOTRUNC;

    //  display preparations:
    pd.loadcol(col_shadow,   0x666);
    pd.backcol(col_backgrnd);
    pd.clear();
    if(field.hasshapeclass(SCL_TITLEBACK))
    {
        Shape sback(field.shapeclass(SCL_TITLEBACK));
        if(!sback.isdead())
        {
            short xsteps = pd.csize(0) / sback.width();
            short ysteps = pd.csize(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.moveto(xs*sback.width()+w2,ys*sback.height()+h2);
                sback.plot();
            }
        }
    }
    pd.copyToBackMap(); // need this for un-drawing

    RECT rhl;

    short cwidth  = pd.csize(1) / 20;
    short cheight = pd.csize(0) /  8;

    HFONT hfont = CreateFont(
                     cheight,cwidth,    // height, width
                     0,0,       // angle, angle
                     700,       // weight
                     0,0,0,     // italic,underline,strikeout
                     ANSI_CHARSET,
                     OUT_DEFAULT_PRECIS,
                     CLIP_DEFAULT_PRECIS,
                     DEFAULT_QUALITY,
                     DEFAULT_PITCH|FF_DONTCARE,
                     "times new roman"  // fontname
                     );

    if(hfont)
    {
       HANDLE hold = SelectObject(pd.getcurhdc(), hfont);

       if(hold)
       {
          SetBkMode(pd.getcurhdc(), TRANSPARENT);

          SetTextColor(pd.getcurhdc(), pd.wincolref(col_shadow));

          short d = 20;

          rhl.left      = pd.cfrm(0) + 10 + d;
          rhl.right     = pd.cfrm(2) - 10;
          rhl.top       = pd.cfrm(1) + 10 + d;
          rhl.bottom    = pd.cfrm(1) + pd.csize(1) /  5;

          DrawText( pd.getcurhdc(), "ZONE OF THUNDER",
                    -1, &rhl, DT_CENTER|DT_VCENTER );

          pd.loadcol(col_misc, 0xf00);
          SetTextColor(pd.getcurhdc(), pd.wincolref(col_misc));

          rhl.left      = pd.cfrm(0) + 10;
          rhl.right     = pd.cfrm(2) - 10 - d;
          rhl.top       = pd.cfrm(1) + 10;
          rhl.bottom    = pd.cfrm(1) + (pd.csize(1) /  5) - d;

          DrawText( pd.getcurhdc(), "ZONE OF THUNDER",
                    -1, &rhl, DT_CENTER|DT_VCENTER );

          SelectObject(pd.getcurhdc(), hold);
       }

       DeleteObject(hfont);
    }

    short   yrange = pd.csize(1)*6L/10L;
    short   cs0,cs1;    // char sizes

    cs0     = pd.csize(0) / 40;
    cs1     = (yrange / (NSETS+1)) - 4;

    HFONT hTextFont = CreateFont(
                     cs1,cs0,   // height, width
                     0,0,       // angle, angle
                     1000,      // weight
                     0,0,0,     // italic,underline,strikeout
                     ANSI_CHARSET,
                     OUT_DEFAULT_PRECIS,
                     CLIP_DEFAULT_PRECIS,
                     DEFAULT_QUALITY,
                     DEFAULT_PITCH|FF_DONTCARE,
                     "arial"    // font name
                     );

    HANDLE hOldTextFont = 0;

    if(hTextFont)
       hOldTextFont = SelectObject(pd.getcurhdc(), hTextFont);

    short d = 10;

    char *str = "THE COMBAT EXPERIENCE";

    SetTextColor(pd.getcurhdc(), pd.wincolref(col_shadow));
    rhl.left    = pd.cfrm(0) + 20 + d;
    rhl.right   = pd.cfrm(2) - 20;
    rhl.top     = pd.cfrm(1) + pd.csize(1) / 5 + d;
    rhl.bottom  = rhl.top    + pd.csize(1) / 10;
    DrawText(pd.getcurhdc(), str, -1, &rhl, DT_CENTER|DT_VCENTER);

    pd.loadcol(col_misc, 0xf00);
    SetTextColor(pd.getcurhdc(), pd.wincolref(col_misc));
    rhl.left    = pd.cfrm(0) + 20;
    rhl.right   = pd.cfrm(2) - 20 - d;
    rhl.top     = pd.cfrm(1) + pd.csize(1) / 5;
    rhl.bottom  = rhl.top    + pd.csize(1) / 10 - d;
    DrawText(pd.getcurhdc(), str, -1, &rhl, DT_CENTER|DT_VCENTER);

    pd.color(col_misc);
    if(ChanManVersion() < REQ_CHANMAN_VERSION)
        pd.text(10,10, "!!! SNDCHAN.DLL HAS OUT-OF-DATE VERSION !!!");

    TEXTMETRIC tm;

    if(GetTextMetrics(pd.getcurhdc(), (TEXTMETRIC FAR *)&tm))
    {
        cs0 = tm.tmAveCharWidth;
        cs1 = tm.tmHeight;
    }
    else
        cs0 = cs1 = 16;

    char *textToCycle[] =
    {
        "VERSION " VerStr,
        "Release Date " __DATE__,

        "PRESS SPACE TO START",
        "WRITTEN BY JUERGEN THUMM",
        "THIS IS PUBLIC DOMAIN",

        "SPECIAL THANKS TO",
        "MARCEL",
        "FOR THE TESTING",

        ""
        };

    CycleText ctext(textToCycle, pd,
                    field.cfrm(0)+20,
                    field.cfrm(1)+field.csize(1)*35L/40L,
                    field.cfrm(0)+field.csize(0)-40,
                    field.csize(1)*1L/10L);

    short canimInit[NSETS] =
    {
         5,6,7,8,9,10,11,12,13,14
    };

    for(ushort i=0; i<NSETS; i++)
    {
        setg[i].canim.val = canimInit[i];
        setg[i].canim.dir = -1;
    }

    short dispDelay = 0;

    KeyBoard kb;

    while(1)
    {
        if(kb.pressed(KEY_SPACE) || kb.pressed(KEY_RETURN))
            break;

        if(kb.pressed(KEY_ESCAPE))
        {
            rc = SPECIAL;
            break;
        }

        changesettings(pd);

        if(!(++dispDelay % 8))
            showsettings(pd, cs0, cs1);

        if(!(dispDelay % 4))
            ctext.step();

        field.timer.delay(10);
    }

    // cleanup:

    if(hOldTextFont)
       SelectObject(pd.getcurhdc(), hOldTextFont);

    if(hTextFont)
       DeleteObject(hTextFont);

    if(crsrhidden)
        ShowCursor(1);

    return rc;
}

//  ==================================================
//  ================== STARTUP ENTRY =================
//  ==================================================

bool sndPlaying = 0;    // a sound is currently played using sndPlaySound

BOOL sndTrySound(LPCSTR name, UINT wFlags)
{
 FILE *f;

    if(!name)   return 0;

    ifn(wFlags & SND_MEMORY)    // if not already loaded

        if(f=fopen(name, "rb")) // check if file exists

            fclose(f);          // yea, fall through
        else
            return  0;          // no, this failed

    sndPlaySound(name, wFlags);

    sndPlaying  = 1;

    return  1;
}

void sndStopSound(void)
{
    if(sndPlaying)

        sndPlaySound((LPCSTR)0, 0); // stop playing

    sndPlaying  = 0;
}

// if this is set, it's played instead of
// the normal end tune:
uchar HUGE * TheEndSound = 0;
ulong        TheEndSize  = 0;   // size of end sound

void globalMessage(char *str, ...);

void mymain(char* cmdline)
{
 KeyBoard kb;
 bool   SoundsLoaded = 0;
 bool   ShapesLoaded = 0;
 ushort oldkhz = field.opt.khz;
 time_t tm;
#ifdef ZOTHREL
 int    RunMode = RootBase::NOLOG; // forbid logfile creation
#else
 int    RunMode = RootBase::DEBUG; // log dmsg'es
#endif
 rcode  result;

    if(cmdline && !strcmp(cmdline, "memwatch"))
        RunMode  = RootBase::MEMTRACK;  // also allows logfile

    if(field.opt.debugmode & ZDEB_ALLOWLOGFILE)
        RunMode &= (~0 ^ RootBase::NOLOG);

    ROOTNAME.Prolog (   0, 0,      // argc, argv    (see also
                        "log.tmp", // logfile name   Prolog call in
                        RunMode    // debug mode     zothwin.cpp)
                    );

    ulSessionStart  = totalSeconds();

    srand((ushort)time(&tm));       // init main randomizer

    if(field.open())    return;

    PixelDisplayDA pda(&field); // get direct access
    if(field.opt.swappixcol)    // if set in .ini,
        pda.SetWinMode(WINM_SETPIXSWAPRB);  // promote now

    sndTrySound((LPCSTR)"snd\\title.wav",
                 SND_LOOP|SND_ASYNC|SND_NODEFAULT);

    // usePalette(PF_NCOLORS, menucols);

    field.LoadShapes(1); // 1 == load just titleBackShape

    while(MainMenu(field, pda) == OK)
    {
        ushort  i;

        //  11 kHz is though to be used only with low-memory systems,
        //  therefore free luxurious end tune in that case.

        if(TheEndSound && field.opt.khz < 22)
        {
            farfree(TheEndSound);
            TheEndSound = 0;
        }

        //  clear screen by copying the background to front:
        field.backcol(col_backgrnd);
        field.transRect(field.cfrm(0),field.cfrm(1),field.cfrm(2),field.cfrm(3));

        if(!ShapesLoaded || !SoundsLoaded || oldkhz != field.opt.khz)
        {
            if(field.texter())
            {
                field.texter()->solve("storyall");

                // display a background story:
                showFormattedText(field.texter()->result(),
                    field,
                    field.cfrm(0)  + 20,
                    field.cfrm(1)  + 20,
                    field.csize(0) - 40,
                    field.csize(1) * 2L / 3 - 40);
            }
        }

        // now, if any one calls globalMessage() while, for example,
        // loading a bitmap, it will be displayed in the status field:
        field.openStatusField();

        if(!ShapesLoaded)
        {
            if(field.opt.debugmode & ZDEB_SHOWBITMAPS)
                globalMessage("\rLOADING BITMAPS, PLEASE WAIT...\n");
            else
                globalMessage("\rBOOTING GAME ENGINE, PLEASE WAIT...\n");

            field.showGLPSmsg(1);   // switch info contents to be displayed
            field.LoadShapes();     // will display infos while doing so
            field.showGLPSmsg(0);
            ShapesLoaded = 1;

            // now that we loaded the shapes,
            // set the hitherto unused logial Windows colors to YELLOW.
            // the sense of this is that the light saviour, which is drawn
            // as an xor-line and therefore uses high color indexes,
            // will then be displayed in yellow color (as long as those high
            // indices are not overwritten due to a shortage of colors).
            field.setUnusedSystemColorsTo(0xff0);
            // if we do this before loading shapes, some shape colors
            // will collapse.
        }

        if(!SoundsLoaded || oldkhz != field.opt.khz)
        {
          if(SoundsLoaded)
          {
            // we have to reload the sounds,
            // so first free current sample datas.
            for(i=0; i<(ushort)SND_MAX; i++)
                field.FreeSound((enum Sounds)i);

            SoundsLoaded = 0;
          }

          if(field.opt.khz > 0) // if any sounds to load
          {
            globalMessage("\rLOADING SOUNDS, PLEASE WAIT ...\n");

            oldkhz  = field.opt.khz;

            ushort  targspeed = 0;

            switch(field.opt.khz)
            {
                case  0: targspeed =     0; break;
                case 32: targspeed = 32000; break;
                case 22: targspeed = 22050; break;
                case 11: targspeed = 11025; break;
                default: SysErr(1103951403);
            }

            // load base sounds, parallel to title music

            bool bOutOfMem = 0;

            enum Sounds sndExtStart = SND_BKNOSH0;  // threshold snd

            for(i=0; i<(ushort)sndExtStart; i++)
            {
              if(field.LoadSound((enum Sounds)i, targspeed) == MEMORY)
                bOutOfMem = 1;

              if(kb.isdown(KEY_ESCAPE))
                break;

              if(kb.isdown(KEY_SPACE))
                sndStopSound();
            }

            sndStopSound(); // stop playing of title music (if any)

            // exension sounds available?

            if(!bOutOfMem)
            {
              result = field.LoadSound(sndExtStart, targspeed);

              if(result == MEMORY)
                bOutOfMem = 1;

              if(result == OK)
              {
                // yes - load extension sounds

                for(i=(ushort)sndExtStart+1; i<(ushort)SND_MAX; i++)
                {
                  if(field.LoadSound((enum Sounds)i, targspeed) == MEMORY)
                    bOutOfMem = 1;

                  if(kb.isdown(KEY_ESCAPE))
                    break;

                  if(kb.isdown(KEY_SPACE))
                    sndStopSound();
                }
              }
            }

            // luxury match end sound:
            if(field.opt.khz >= 22 && !TheEndSound)
                TheEndSound = loadfile("ext\\theend.wav", &TheEndSize);
                // this sound might be very large! If loading fails,
                // the 'normal' tune is played at end of game

            if(bOutOfMem)
            {
                globalMessage("OUT OF MEMORY - GAME MIGHT CRASH!\n");
                field.timer.delay(5000);
            }

            if(!(field.opt.debugmode & ZDEB_SHOWSOUNDS))
                field.timer.delay(1000);
            else
            {
                globalMessage("*** PRESS SPACE TO CONTINUE ***\n");
                while(!kb.pressed(KEY_SPACE));
            }

            SoundsLoaded = 1;

          } // endif load any sounds at all

        }   // endif

        field.closeStatusField();   // if open at all

        sndStopSound(); // stop playing of title music (if any)

        field.num.players   = field.num.humans + field.num.robots;

        if(!field.opt.showmouse)
            ShowCursor(0);  // mouse off

         field.Main();  // play!

        if(!field.opt.showmouse)
            ShowCursor(1);  // mouse on

        while(kb.pressed(KEY_ESCAPE));  // wait 'til released

        sndTrySound((LPCSTR)"snd\\title.wav",
                     SND_LOOP|SND_ASYNC|SND_NODEFAULT);

        // field.PixelDisplay::clear();
        // usePalette(PF_NCOLORS, menucols);
    }

    sndStopSound(); // stop playing of title music (if any)

    if(TheEndSound) farfree(TheEndSound);

    field.close();
}

#endif
