/* $Filename: WormWars/Source/system.c $
 * $VER:      Worm Wars 6.6 for Amiga $
 *
 * © Copyright 2001 James R. Jacobs. Freely distributable.
 *        _
 *       //   -=AMIGA=-
 *      //
 * _   //
 * \\ //
 *  \X/

#INCLUDES ---------------------------------------------------------------- */

#include "system.h"
#include "amiga.h"
#include "qdraw020.h"

#include <assert.h>
// #define ASSERT

/* PROTOTYPES (Amiga-only) ----------------------------------------------- */

MODULE void freefx(void);
MODULE void loadthefx(void);
MODULE void loadthemusic(void);
MODULE void parsewb(void);
MODULE void pausetimer(void);
MODULE void unpausetimer(void);
MODULE void preserve(SWORD x, SBYTE row, SBYTE image, UWORD amount);
MODULE ABOOL beginfx(void);
MODULE UBYTE ReadJoystick(UWORD joynum);

AGLOBAL struct Window          *HelpWindowPtr = NULL,
                               *MainWindowPtr = NULL;
AGLOBAL struct Menu*            MenuPtr       = NULL;
AGLOBAL struct VisualInfo*      VisualInfoPtr = NULL;
AGLOBAL struct MsgPort*         JoyPortPtr    = NULL;
AGLOBAL struct timerequest*     TimerRqPtr    = NULL;
AGLOBAL struct InputEvent       GameEvent;
AGLOBAL struct Screen*          ScreenPtr     = NULL;
AGLOBAL ABOOL                   icons         = TRUE,
                                joy           = FALSE;

/* EXTERNAL VARIABLES (owned by engine.c, imported by system.c) ----------- */

IMPORT  struct ExecBase*        SysBase;

IMPORT  ABOOL                   modified, randomflag;
IMPORT  SBYTE                   a, eachworm[4][2][9],
                                level, levels, players, sourcelevel,
                                startx[MAXLEVELS + 1], starty[MAXLEVELS + 1];
IMPORT  SWORD                   secondsleft, secondsperlevel;
IMPORT  ULONG                   delay, r;
IMPORT  UBYTE                   board[MAXLEVELS + 1][FIELDX + 1][FIELDY + 1],
                                field[FIELDX + 1][FIELDY + 1];
IMPORT	struct HiScoreStruct	hiscore[HISCORES + 1];
IMPORT	struct WormStruct       worm[4];
IMPORT  TEXT                    pathname[81],
                                date[DATELENGTH + 1],
                                times[TIMELENGTH + 1];

        struct Library*         TimerBase;
        struct ASLBase*         ASLBase        = NULL;
//      struct Library*         AmigaGuideBase = NULL;
        struct DiskFontBase*    DiskFontBase   = NULL;
        struct GadToolsBase*    GadToolsBase   = NULL;
        struct GfxBase*         GfxBase        = NULL;
        struct Library*         IconBase       = NULL;
        struct IntuitionBase*   IntuitionBase  = NULL;
        struct MEDPlayerBase*   MEDPlayerBase  = NULL;
        struct UtilityBase*     UtilityBase    = NULL;

/* MODULE VARIABLES (used only within system.c) --------------------------- */

MODULE  ABOOL  eversent[4],
               ignore       = FALSE,
               iso          = TRUE,
               quiet        = FALSE;
MODULE  SBYTE  AudioClosed  = TRUE,
               Controller   = GPCT_NOCONTROLLER,
               JoyClosed    = TRUE,
               OldPri       = 0,
               TimerClosed  = TRUE;
MODULE  UBYTE  fxable       = UNTRIED,
               mode         = NULL,
               musicable    = UNTRIED;
MODULE  ULONG  fsize,
               receipter[4] = {(ULONG) -1, (ULONG) -1, (ULONG) -1, (ULONG) -1};
MODULE  UBYTE* fbase        = NULL;
MODULE  BPTR   FilePtr      = NULL;
MODULE  APTR   OldWindowPtr = NULL;
MODULE  SWORD  pixy;

MODULE  struct QDPlaneInfo    QDPI;
MODULE  ABOOL                 useqdraw            = FALSE;

MODULE	struct TextAttr       WormWars8 =
{	(STRPTR) "WormWars.font", 8, FS_NORMAL, FPF_DISKFONT | FPF_DESIGNED
},                            Topaz8 =
{	(STRPTR) "topaz.font", 8, FS_NORMAL, FPF_ROMFONT | FPF_DESIGNED
};

MODULE	struct Gadget         *CheckboxGadgetPtr  = NULL,
                              *CycleGadgetPtr[4]  = {NULL, NULL, NULL, NULL},
                              *GListPtr           = NULL,
                              *PrevGadgetPtr      = NULL,
                              *RandomGadgetPtr    = NULL,
                              *StringGadgetPtr[5] = {NULL, NULL, NULL, NULL, NULL};
MODULE	struct MsgPort        *AudioPortPtr[4]    = {NULL, NULL, NULL, NULL},
                              *TimerPortPtr       = NULL;
MODULE	struct timeval        *CurrentValPtr      = NULL,
                              *PausedValPtr       = NULL,
                              *StartValPtr        = NULL;

MODULE  struct CIA*           CIAPtr              = (struct CIA *) 0xBFE001;
MODULE	struct RDArgs*        ArgsPtr             = NULL;
MODULE	struct FileRequester* ASLRqPtr            = NULL;
MODULE	struct IOAudio*       AudioRqPtr[4]       = {NULL, NULL, NULL, NULL};
MODULE	struct TextFont*      FontPtr             = NULL;
MODULE	struct IOStdReq*      JoyRqPtr            = NULL;
MODULE	struct Process*       ProcessPtr          = NULL;
MODULE	struct MMD0*          SongPtr             = NULL;
MODULE	struct WBArg*         WBArg               = NULL;
MODULE	struct WBStartup*     WBMsg               = NULL;

/* FUNCTIONS -------------------------------------------------------------- */

int main(int argc, char** argv)
{
BPTR    OldDir;
SLONG   i;
ABOOL   success = FALSE;
SBYTE   error = 0,
        player, which;
TEXT    saystring[SAYLIMIT + 1];
SWORD   oldsecondsleft;
UWORD   Pens[10] =
{       BLACK,          /* DETAILPEN            text in title bar */
        WHITE,          /* BLOCKPEN             fill title bar */
        WHITE,          /* TEXTPEN              regular text on BACKGROUNDPEN */
        LIGHTGREY,      /* SHINEPEN             bright edge */
        DARKGREY,       /* SHADOWPEN            dark edge */
        PURPLE,         /* FILLPEN              filling active window borders
                                                and selected gadgets */
        BLACK,          /* FILLTEXTPEN          text rendered over FILLPEN */
        BLACK,          /* BACKGROUNDPEN        background colour */
        RED,            /* HIGHLIGHTTEXTPEN     highlighted text on BACKGROUNDPEN */
        (UWORD) ~0      /* and used against BLOCKPEN in ASL save requesters */
};
SLONG                   args[12] = {0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L};
struct ColorSpec        Colours[21] =
{   /* colour   red   green   blue   description */
    {   0,      0x0,    0x0,    0x0},   /*        BLACK */
    {   1,      0xF,    0xF,    0xF},   /*        WHITE */
    {   2,      0x5,    0x5,    0x5},   /*    DARKGREY */
    {   3,      0x9,    0x9,    0x9},   /*  MEDIUMGREY */
    {   4,      0xB,    0xB,    0xB},   /*   LIGHTGREY */
    {   5,      0x0,    0x0,    0x0},   /*        black */
    {   6,      0xC,    0x5,    0xC},   /*        PURPLE */
    {   7,      0x8,    0x4,    0x2},   /*        brown */
    {   8,      0x3,    0x9,    0x3},   /*    DARKGREEN */
    {   9,      0x4,    0xE,    0x4},   /*  light GREEN */
    {   10,     0xF,    0x1,    0x1},   /*    DARKRED */
    {   11,     0xF,    0x5,    0x5},   /*  light RED */
    {   12,     0x3,    0x3,    0xF},   /*    DARKBLUE */
    {   13,     0x6,    0x6,    0xF},   /*  light BLUE */
    {   14,     0xA,    0xA,    0x2},   /*    DARKYELLOW */
    {   15,     0xC,    0xC,    0x2},   /*  light YELLOW */
    {   16,     0x0,    0x0,    0x0},   /* pointer: transparent */
    {   17,     0xE,    0x4,    0x4},   /* pointer: fill */
    {   18,     0x3,    0x3,    0x3},   /* pointer: shadow */
    {   19,     0xC,    0xC,    0xC},   /* pointer: shine */
    {   -1,     NULL,   NULL,   NULL}
};

/* Start of program.

version embedding into executable */

if (0) /* that is, never */
	say(VERSION, ANYTHING);

if (!(IntuitionBase = (struct IntuitionBase *) OpenLibrary("intuition.library", 37L)))
{	Write(Output(), OLDKICKSTART, strlen(OLDKICKSTART));
	cleanexit(EXIT_FAILURE);
}

/* From this point onwards, we can be sure we have Kickstart 2.04+... */

for (i = 0; i <= SAMPLES; i++)
    samp[i].base = NULL;
enginesetup();
ProcessPtr = (struct Process *) FindTask(NULL);

if (SysBase->LibNode.lib_Version < 36L)
{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Need exec.library V36+!\0", 24);
	cleanexit(EXIT_FAILURE);
}
if (!(GfxBase = (struct GfxBase *) OpenLibrary("graphics.library", 0L)))
{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't open graphics.library!\0", 24);
	cleanexit(EXIT_FAILURE);
}
if (!(GadToolsBase = (struct GadToolsBase *) OpenLibrary("gadtools.library", 37L)))
{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't open GadTools.library V37+!\0", 24);
	cleanexit(EXIT_FAILURE);
}
if (!(ASLBase = (struct ASLBase *) OpenLibrary("asl.library", 0L)))
{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't open ASL.library!\0", 24);
	cleanexit(EXIT_FAILURE);
}
if (!(UtilityBase = (struct UtilityBase *) OpenLibrary("utility.library", 0L)))
{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't open utility.library!\0", 24);
	cleanexit(EXIT_FAILURE);
}
if (!(IconBase = (struct Library *) OpenLibrary("icon.library", 0L)))
{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't open icon.library!\0", 24);
        cleanexit(EXIT_FAILURE);
}

/* argument parsing */

if (argc) /* started from CLI */
{   if (!(ArgsPtr = ReadArgs
    (   "-F=NOFX/S,-M=NOMUSIC/S,-I=NOICONS/S,-P=PRI/K/N,-O=OVERHEAD/S,"
        "-S=SHUFFLE/S,-Q=QUIET/S,GREEN/K,RED/K,BLUE/K,YELLOW/K,FILE",
        (LONG *) args,
        NULL
    )))
    {   Printf
        (   "Usage: %s [-f=NOFX] [-m=NOMUSIC] [-i=NOICONS] "
            "[-p=PRI <priority>] [-o=OVERHEAD] [-s=SHUFFLE] [-q=QUIET] "
            "[GREEN=KYBD|JOY|AMIGA|NONE] "
            "[RED=KYBD|JOY|AMIGA|NONE] [BLUE=HUMAN|AMIGA|NONE] "
            "[YELLOW=HUMAN|AMIGA|NONE] [[FILE=]<fieldset>]\n",
            argv[0]
        );
        cleanexit(EXIT_FAILURE);
    }
    if (args[0])
        fxable = DEFER;
    if (args[1])
        musicable = DEFER;
    if (args[2])
        icons = FALSE;
    if (args[3])
    {   if (args[3] < -128 || args[3] > 5)
        {   Printf("%s: Priority range is -128 to +5\n", argv[0]);
            cleanexit(EXIT_FAILURE);
        }
        OldPri = SetTaskPri((struct Task *) ProcessPtr, args[3]);
    }
    if (args[4])
        iso = FALSE;
    if (args[5])
        randomflag = TRUE;
    if (args[6])
    {   quiet = TRUE;
    }
    if (args[7])
    {   if (!stricmp(args[7], "KYBD"))
        {   worm[0].control = KEYBOARD;
            worm[0].control = KEYBOARD;
        } elif (!stricmp(args[7], "JOY"))
        {   worm[0].control = JOYSTICK;
            worm[0].control = JOYSTICK;
        } elif (!stricmp(args[7], "AMIGA"))
        {   worm[0].control = AMIGA;
        } elif (!stricmp(args[7], "NONE"))
        {   worm[0].control = NONE;
        } else
        {   Printf("%s: Green worm control must be KYBD, JOY, AMIGA or NONE\n", argv[0]);
            cleanexit(EXIT_FAILURE);
    }   }
    if (args[8])
    {   if (!stricmp(args[8], "KYBD"))
        {   worm[1].control = KEYBOARD;
            worm[1].control = KEYBOARD;
        } elif (!stricmp(args[8], "JOY"))
        {   worm[1].control = JOYSTICK;
            worm[1].control = JOYSTICK;
        } elif (!stricmp(args[8], "AMIGA"))
        {   worm[1].control = AMIGA;
        } elif (!stricmp(args[8], "NONE"))
        {   worm[1].control = NONE;
        } else
        {   Printf("%s: Red worm control must be KYBD, JOY, AMIGA or NONE\n", argv[0]);
            cleanexit(EXIT_FAILURE);
    }   }
	if (args[9])
	{	if (!stricmp(args[9], "JOY"))
			worm[2].control = JOYSTICK;
		elif (!stricmp(args[9], "AMIGA"))
			worm[2].control = AMIGA;
		elif (!stricmp(args[9], "NONE"))
			worm[2].control = NONE;
		else
		{	Printf("%s: Blue worm control must be JOY, AMIGA or NONE\n", argv[0]);
			cleanexit(EXIT_FAILURE);
	}	}
	if (args[10])
	{	if (!stricmp(args[10], "JOY"))
			worm[3].control = JOYSTICK;
		elif (!stricmp(args[10], "AMIGA"))
			worm[3].control = AMIGA;
		elif (!stricmp(args[10], "NONE"))
			worm[3].control = NONE;
		else
		{	Printf("%s: Yellow worm control must be JOY, AMIGA or NONE\n", argv[0]);
			cleanexit(EXIT_FAILURE);
	}	}
	if (args[11])
        {   strcpy(pathname, args[11]);
    }   }
    else /* started from WB */
    {   WBMsg = (struct WBStartup *) argv;
	WBArg = WBMsg->sm_ArgList; /* head of the arg list */

	for (i = 0;
		 i < WBMsg->sm_NumArgs;
		 i++, WBArg++)
	{	if (WBArg->wa_Lock)
		{	/* something that does not support locks */
			parsewb();
		}
		else
		{	/* locks supported, change to the proper directory */
			OldDir = CurrentDir(WBArg->wa_Lock);
			parsewb();
			CurrentDir(OldDir);
                }
                if (i == 1)
                {    strcpy(pathname, WBArg->wa_Name);
}       }       }

if (!(DiskFontBase = (struct DiskFontBase *) OpenLibrary("diskfont.library", 0L)))
	error = 1;
	
if
(       (!(StartValPtr = (struct timeval *) AllocMem(sizeof(struct timeval), MEMF_PUBLIC | MEMF_CLEAR)))
||      (!(CurrentValPtr = (struct timeval *) AllocMem(sizeof(struct timeval), MEMF_PUBLIC | MEMF_CLEAR)))
||      (!(PausedValPtr = (struct timeval *) AllocMem(sizeof(struct timeval), MEMF_PUBLIC | MEMF_CLEAR)))
)
{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't allocate timer value structure!\0", 24);
	cleanexit(EXIT_FAILURE);
}
if (!(TimerPortPtr = (struct MsgPort *) CreateMsgPort()))
{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't allocate timer message port!\0", 24);
	cleanexit(EXIT_FAILURE);
}
if (!(TimerRqPtr = (struct timerequest *) CreateIORequest(TimerPortPtr, sizeof(struct timerequest))))
{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't create timer I/O request!\0", 24);
	cleanexit(EXIT_FAILURE);
}
if (TimerClosed = OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest *) TimerRqPtr, 0))
{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't open timer.device!\0", 24);
	cleanexit(EXIT_FAILURE);
}
TimerBase = (struct Library *) TimerRqPtr->tr_node.io_Device;

/* PREPARE DISPLAY -----------------------------------------------

font and screen */

if ((!error) && (FontPtr = (struct TextFont *) OpenDiskFont(&WormWars8)))
{   /* loaded WormWars.font */

    ScreenPtr = (struct Screen *) OpenScreenTags
    (   NULL,
        SA_Width,       640,
        SA_Height,      256,
        SA_Depth,       DEPTH,
        SA_DisplayID,   HIRES_KEY | PAL_MONITOR_ID,
        SA_Title,       TITLEBAR,
        SA_Colors,      Colours,
        SA_Font,        &WormWars8,
        SA_Pens,        Pens,
        SA_PubName,     "WormWars",
        TAG_DONE
    );
    if (!ScreenPtr)
    {   DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't open screen!\0", 24);
        cleanexit(EXIT_FAILURE);
}   }
else
{   if (!error)
        error = 2;

    ScreenPtr = (struct Screen *) OpenScreenTags
    (   NULL,
        SA_Width,       640,
        SA_Height,      256,
        SA_Depth,       DEPTH,
        SA_DisplayID,   HIRES_KEY | PAL_MONITOR_ID,
        SA_Title,       TITLEBAR,
        SA_Colors,      Colours,
        SA_Font,        &Topaz8,
        SA_Pens,        Pens,
        SA_PubName,     "WormWars",
        TAG_DONE
    );
    if (!ScreenPtr)
    {   DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't open screen nor font!\0", 24);
        cleanexit(EXIT_FAILURE);
}   }

PubScreenStatus(ScreenPtr, NULL); // take the screen public

/* For some reason, we lose 4 memory chunks around this point: 2 chunks
each of 292 bytes and 10 bytes. It is related to OpenScreenTagList(),
but is not related to the SA_Colors, SA_Font or SA_Pens tags, nor to
the timer.device.

Now that the screen is set up, we initialize the turbo graphics as
follows: */

for (i = 0; i < DEPTH; i++)
    QDPI.pi_Planes[i] = ScreenPtr->RastPort.BitMap->Planes[i];
QDPI.pi_BPR           = ScreenPtr->RastPort.BitMap->BytesPerRow;
QDPI.pi_PlaneSZ       = ScreenPtr->RastPort.BitMap->BytesPerRow *
                        ScreenPtr->RastPort.BitMap->Rows;

/* GetBitMapAttr() is more forwards-compatible than direct structure
access. */
useqdraw = (SysBase->AttnFlags & AFB_68020);

/* GadTools */

if (!(CycleGadget.ng_VisualInfo = StringGadget.ng_VisualInfo = CheckboxGadget.ng_VisualInfo = RandomGadget.ng_VisualInfo = VisualInfoPtr = (APTR) GetVisualInfo(ScreenPtr, TAG_DONE)))
{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't get GadTools visual info!\0", 24);
	cleanexit(EXIT_FAILURE);
}
if (!(MenuPtr = (struct Menu *) CreateMenus(NewMenu, TAG_DONE)))
{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't create menus!\0", 24);
	cleanexit(EXIT_FAILURE);
}
if (!(LayoutMenus(MenuPtr, VisualInfoPtr, TAG_DONE)))
{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't lay out menus!\0", 24);
	cleanexit(EXIT_FAILURE);
}
if (!(PrevGadgetPtr = (struct Gadget *) CreateContext(&GListPtr)))
{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't create GadTools context!\0", 24);
	cleanexit(EXIT_FAILURE);
}

    for (player = 0; player <= 3; player++)
    {   CycleGadget.ng_TopEdge = TSOFFSET + 84 + (player * (FONTX + 8));
	CycleGadget.ng_GadgetText = CycleText[player];
	CycleGadgetPtr[player] = PrevGadgetPtr = (struct Gadget *) CreateGadget
        (   CYCLE_KIND,
            PrevGadgetPtr,
            &CycleGadget,
            GTCY_Labels, CycleOptions[player],
            GTCY_Active, worm[player].control,
            GT_Underscore, '_',
            GA_Disabled, TRUE,
            TAG_DONE
	);
    }
    RandomGadgetPtr = PrevGadgetPtr = (struct Gadget *) CreateGadget
    (   CHECKBOX_KIND,
	PrevGadgetPtr,
	&RandomGadget,
	GTCB_Checked, randomflag,
	GT_Underscore, '_',
	GA_Disabled, TRUE,
	TAG_DONE
    );
    CheckboxGadgetPtr = PrevGadgetPtr = (struct Gadget *) CreateGadget
    (   CHECKBOX_KIND,
	PrevGadgetPtr,
	&CheckboxGadget,
	GTCB_Checked, iso,
	GT_Underscore, '_',
	GA_Disabled, TRUE,
	TAG_DONE
    );

/* main window */

if (!(MainWindowPtr = (struct Window *) OpenWindowTags(NULL,
        WA_Top,                 11,
        WA_Width,               SCREENXPIXEL,
        WA_Height,              SCREENYPIXEL,
        WA_IDCMP,               IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_CLOSEWINDOW | IDCMP_ACTIVEWINDOW | IDCMP_MENUPICK | IDCMP_MENUVERIFY | CYCLEIDCMP | STRINGIDCMP | CHECKBOXIDCMP | IDCMP_REFRESHWINDOW | IDCMP_INTUITICKS,
        WA_Gadgets,             GListPtr,
	WA_CustomScreen,	ScreenPtr,
	WA_Borderless,		TRUE,
	WA_Activate,		TRUE,
	WA_SmartRefresh,	TRUE,
	WA_RptQueue,		16,
	TAG_DONE)))
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't open main window!\0", 24);
		cleanexit(EXIT_FAILURE);
	}

/* redirection of AmigaDOS system requesters */
	
OldWindowPtr = ProcessPtr->pr_WindowPtr;
ProcessPtr->pr_WindowPtr = (APTR) MainWindowPtr;

if (!(ASLRqPtr = AllocAslRequestTags(ASL_FileRequest, ASL_Pattern, PATTERN, ASL_Window, MainWindowPtr, TAG_DONE)))
{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't create ASL request!\0", 24);
	cleanexit(EXIT_FAILURE);
}

/* String gadgets: first the window is opened with the cycle and
checkbox gadgets.
	It is necessary not to display the string gadgets yet, so the
gadgets are then created and added to the gadget list. You will note
there is no command given to render them as yet. When the attributes
are modified at highscore time, the applicable gadget is refreshed
then. */

for (which = 0; which <= HISCORES; which++)
{       StringGadget.ng_TopEdge = TSOFFSET + (which * HISCOREDISTANCE);
	StringGadgetPtr[which] = PrevGadgetPtr = (struct Gadget *) CreateGadget(STRING_KIND, PrevGadgetPtr, &StringGadget, GTST_MaxChars, NAMELENGTH, STRINGA_ReplaceMode, TRUE, GA_Disabled, TRUE, TAG_DONE);
}
if (!PrevGadgetPtr)
{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't create GadTools gadgets!\0", 24);
	cleanexit(EXIT_FAILURE);
}

    if (!icons)
        NewMenu[CREATEICONS].nm_Flags &= ~CHECKED;

    if (!quiet)
    {   if (error == 1)
        {   say("Can't open diskfont.library!", RED);
            anykey(TRUE);
        } elif (error == 2)
        {   say("Can't open WormWars.font!", RED);
            anykey(TRUE);
    }   }

    if (!(JoyPortPtr = (struct MsgPort *) CreateMsgPort()))
    {   if (!quiet)
        {   say("Can't create joystick message port!", BLUE);
            anykey(TRUE);
    }   }
    elif (!(JoyRqPtr = (struct IOStdReq *) CreateIORequest(JoyPortPtr, sizeof(struct IOStdReq))))
    {   if (!quiet)
        {   say("Can't create joystick I/O request!", BLUE);
        anykey(TRUE);
    }   }
    elif (JoyClosed = OpenDevice("gameport.device", 1, (struct IORequest *) JoyRqPtr, 0))
    {   if (!quiet)
        {   say("Can't open gameport.device!", BLUE);
            anykey(TRUE);
        } else
        {   Forbid();
            JoyRqPtr->io_Command = GPD_ASKCTYPE;
	    JoyRqPtr->io_Length  = 1;
            JoyRqPtr->io_Flags   = IOF_QUICK;
            JoyRqPtr->io_Data    = (APTR) &Controller;
            DoIO((struct IORequest *) JoyRqPtr);
            if (Controller == GPCT_NOCONTROLLER)
            {   Controller           = GPCT_ABSJOYSTICK;
                JoyRqPtr->io_Command = GPD_SETCTYPE;
                JoyRqPtr->io_Length  = 1;
                JoyRqPtr->io_Data    = (APTR) &Controller;
                DoIO((struct IORequest *) JoyRqPtr);
                success = TRUE;
            }
            Permit();
            /* Note that say(), anykey() calls must be outside the
            critical section (Forbid()/Permit() pair). */
            if (success)
            {    JoyRqPtr->io_Command	= GPD_SETTRIGGER;
                JoyRqPtr->io_Data		= (APTR) &Trigger;
                JoyRqPtr->io_Length		= sizeof(struct GamePortTrigger);
                DoIO((struct IORequest *) JoyRqPtr);
                sendreadrequest();
                joy = TRUE;
            } else
            {   if (!quiet)
                {   say("Gameport already in use!", BLUE);
                    anykey(TRUE);
    }   }   }   }

    if (musicable == UNTRIED)
        loadthemusic();
    if (fxable == UNTRIED)
        loadthefx();

    if (fxable == SUCCEEDED)
        toggle(F);
    else if (musicable == SUCCEEDED)
        toggle(M);

    strcpy(saystring, "Loading ");
    strcat(saystring, pathname);
    strcat(saystring, "...");
    say(saystring, WHITE);
    if (loadfields(pathname))
    {   if (!quiet)
        {   strcpy(saystring, "Can't open ");
            strcat(saystring, pathname);
            strcat(saystring, "!");
            say(saystring, RED);
        }
        newfields();
        if (!quiet)
        {   anykey(TRUE);
    }   }

    while (1)
    {   titlescreen();
    
        /* MAIN GAME LOOP ------------------------------------------------- */
    
        while (a == PLAYGAME)
        {   r++;
            oldsecondsleft = secondsleft;
            GetSysTime(CurrentValPtr);
            SubTime(CurrentValPtr, StartValPtr);
            secondsleft = secondsperlevel - CurrentValPtr->tv_secs;
            if (secondsleft != oldsecondsleft)
                timeloop();
            TimerRqPtr->tr_node.io_Command  = TR_ADDREQUEST;
            TimerRqPtr->tr_time.tv_secs     = 0;
            TimerRqPtr->tr_time.tv_micro    = delay;
            SendIO((struct IORequest *) TimerRqPtr);
            gameloop();
            // CheckIO() returns FALSE if complete, pointer if incomplete.
            if (CheckIO((struct IORequest *) TimerRqPtr)) // CheckIO returns pointer to struct IORequest
                draw(CLOCKICON, ICONY, CLOCK);
            else draw(CLOCKICON, ICONY, BLACKENED);
            WaitIO((struct IORequest *) TimerRqPtr);
        }
        say("Title Screen", WHITE);
}   }
    
/* SUPPORT FUNCTIONS ----------------------------------------------------- */
    
/* NAME     anykey -- wait for a user press
SYNOPSIS    anykey(ABOOL);
FUNCTION    Waits for a user response. Optional automatic timeout.
INPUTS      timeout - timeout?
RESULTS     FALSE if user presses Escape, TRUE otherwise.
FILE        system.c */

ABOOL anykey(ABOOL timeout)
{	ABOOL					done		= FALSE;
	SBYTE					count		= 0;
	UWORD					code, qual;
	ULONG					class;
	struct IntuiMessage*	MsgPtr;

	clearkybd();
	Forbid();
	MainWindowPtr->Flags |= WFLG_RMBTRAP;
	Permit();
	
	while (!done)
	{	if (joy && GetMsg(JoyPortPtr))
		{	if (GameEvent.ie_Code == IECODE_LBUTTON)
				done = TRUE;
			sendreadrequest();
		}
		if (joy)
		{	Wait((1L << MainWindowPtr->UserPort->mp_SigBit) | (1L << JoyPortPtr->mp_SigBit));
			while (GetMsg(JoyPortPtr))
			{	if (GameEvent.ie_Code == IECODE_LBUTTON)
					done = TRUE;
				sendreadrequest();
		}	}
		else Wait(1L << MainWindowPtr->UserPort->mp_SigBit);
		while (MsgPtr = (struct IntuiMessage *) GT_GetIMsg(MainWindowPtr->UserPort))
		{	class = MsgPtr->Class;
			code  = MsgPtr->Code;
			qual  = MsgPtr->Qualifier;
			GT_ReplyIMsg(MsgPtr);
			switch(class)
			{
			case IDCMP_RAWKEY:
                            if ((!(qual & IEQUALIFIER_REPEAT)) && code < KEYUP && (code < FIRSTQUALIFIER || code > LASTQUALIFIER))
                            {   done = TRUE;
                                if (code == M)
                                    toggle(M);
                                else if (code == F)
                                    toggle(F);
                                else if (code == ESCAPE)
                                {   if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
                                        if (verify())
                                            cleanexit(EXIT_SUCCESS);
                                    else
                                    {   Forbid();
                                        MainWindowPtr->Flags &= ~WFLG_RMBTRAP;
                                        Permit();
                                        return FALSE;
                            }   }   }
                        break;
			case IDCMP_CLOSEWINDOW:
				cleanexit(EXIT_SUCCESS);
				break;
			case IDCMP_ACTIVEWINDOW:
				ignore = TRUE;
				break;
			case IDCMP_MOUSEBUTTONS:
				if ((code == SELECTDOWN || code == MENUDOWN) && !(qual & IEQUALIFIER_REPEAT))
					if (ignore)
						ignore = FALSE;
					else done = TRUE;
				break;
			case IDCMP_INTUITICKS:
				if (timeout && ++count > PATIENCE)
					done = TRUE;
				break;
			default:
				break;
	}	}	}

	Forbid();
	MainWindowPtr->Flags &= ~WFLG_RMBTRAP;
	Permit();
    return TRUE;
}

void celebrate(void)
{	ABOOL					done = FALSE;
	ULONG					class;
	UWORD					code, qual;
	struct IntuiMessage*	MsgPtr;
    UBYTE                   player;

    for (player = 0; player <= 3; player++)
        if (worm[player].control != NONE && worm[player].score >= worm[player].hiscore)
             worm[player].hiscore = worm[player].score;
    waitasec();
	clearkybd();
    while (!done)
	{	while (MsgPtr = (struct IntuiMessage *) GT_GetIMsg(MainWindowPtr->UserPort))
		{	class = MsgPtr->Class;
			code  = MsgPtr->Code;
			qual  = MsgPtr->Qualifier;
			GT_ReplyIMsg(MsgPtr);
			switch (class)
			{
			case IDCMP_RAWKEY:
				if (code == ESCAPE)
				{	if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
					{	if (verify())
							cleanexit(EXIT_SUCCESS);
					} else
						done = TRUE;
				}
				else if (code == RETURN || code == ENTER || code == SPACEBAR)
					done = TRUE;
				else if (code == M)
					toggle(M);
				else if (code == F)
					toggle(F);
				break;
			case IDCMP_MOUSEBUTTONS:
				if (code == SELECTDOWN && !(qual & IEQUALIFIER_REPEAT))
					if (ignore)
						ignore = FALSE;
					else done = TRUE;
				break;
			case IDCMP_ACTIVEWINDOW:
				ignore = TRUE;
				break;
			case IDCMP_CLOSEWINDOW:
				cleanexit(EXIT_SUCCESS);
				break;
			default:
				break;
        }   }
        if (joy && GetMsg(JoyPortPtr))
        {   if (GameEvent.ie_Code == IECODE_LBUTTON)
                done = TRUE;
            sendreadrequest();
        }
        SetRGB4(&ScreenPtr->ViewPort, 17, rand() % 16, rand() % 16, rand() % 16);
        draw(rand() % (FIELDX + 1), rand() % (FIELDY + 1), rand() % LASTOBJECT);
    }
    SetRGB4(&ScreenPtr->ViewPort, 17, 14, 4, 4);
    a = GAMEOVER;
}

void cleanexit(SLONG rc)
{   SBYTE i;
    struct Message* MsgPtr;

// CheckIO() returns NULL if complete, pointer if incomplete?
if (TimerRqPtr && (!CheckIO((struct IORequest *) TimerRqPtr)))
                        {       AbortIO((struct IORequest *) TimerRqPtr);
                                WaitIO((struct IORequest *) TimerRqPtr);
                        }
                                freefx();
                                for (i = 0; i <= SAMPLES; i++)
                                    if (samp[i].base)
                                        FreeMem(samp[i].base, samp[i].size);
if (mode == MUSIC)		StopPlayer();
if (SongPtr)			UnLoadModule(SongPtr);
if (MEDPlayerBase)	{	FreePlayer();
                                CloseLibrary((struct Library *) MEDPlayerBase);
                        }
if (joy)                {       AbortIO((struct IORequest *) JoyRqPtr);
                                WaitIO((struct IORequest *) JoyRqPtr);
                        }
if (Controller != GPCT_NOCONTROLLER)
                        {       Forbid();
                                Controller           = GPCT_NOCONTROLLER;
                                JoyRqPtr->io_Command = GPD_SETCTYPE;
                                JoyRqPtr->io_Length  = 1;
                                JoyRqPtr->io_Data    = (APTR) &Controller;
                                DoIO((struct IORequest *) JoyRqPtr);
                                Permit();
                        }
if (!JoyClosed)                 CloseDevice((struct IORequest *) JoyRqPtr);
if (JoyRqPtr)           {       DeleteIORequest((struct IORequest *) JoyRqPtr);
                                while(MsgPtr = GetMsg(JoyPortPtr))
                                    ReplyMsg(MsgPtr); // important, or we lose memory
                        }
if (JoyPortPtr)			DeleteMsgPort(JoyPortPtr);

if (ASLRqPtr)			FreeAslRequest(ASLRqPtr);
if (OldWindowPtr)		ProcessPtr->pr_WindowPtr = OldWindowPtr;
if (MainWindowPtr)	{	clearkybd();
                                ClearMenuStrip(MainWindowPtr);
                                CloseWindow(MainWindowPtr);                                             }
if (GListPtr)			FreeGadgets(GListPtr);
if (MenuPtr)			FreeMenus(MenuPtr);
if (VisualInfoPtr)		FreeVisualInfo(VisualInfoPtr);
if (ScreenPtr)			CloseScreen(ScreenPtr);
if (FontPtr)			CloseFont(FontPtr);
if (!TimerClosed)               CloseDevice((struct IORequest *) TimerRqPtr);
if (TimerRqPtr)                 DeleteIORequest(TimerRqPtr);
if (TimerPortPtr)		DeleteMsgPort(TimerPortPtr);
if (PausedValPtr)		FreeMem(PausedValPtr, sizeof(struct timeval));
if (CurrentValPtr)		FreeMem(CurrentValPtr, sizeof(struct timeval));
if (StartValPtr)		FreeMem(StartValPtr, sizeof(struct timeval));
if (DiskFontBase)		CloseLibrary((struct Library *) DiskFontBase);
if (UtilityBase)		CloseLibrary((struct Library *) UtilityBase);
if (ASLBase)			CloseLibrary((struct Library *) ASLBase);
if (GadToolsBase)		CloseLibrary((struct Library *) GadToolsBase);
if (GfxBase)			CloseLibrary((struct Library *) GfxBase);
if (IconBase)			CloseLibrary((struct Library *) IconBase);
if (IntuitionBase)	{	OpenWorkBench();
                                CloseLibrary((struct Library *) IntuitionBase);                                    }
                                SetTaskPri((struct Task *) ProcessPtr, OldPri);
if (ArgsPtr)			FreeArgs(ArgsPtr);
                                exit(rc); /* End of program. */
}

void clearjoystick(void)
{   if (joy)
        while (GetMsg(JoyPortPtr))
            sendreadrequest();
}

void clearkybd(void)
{   struct IntuiMessage* MsgPtr;

    while (MsgPtr = (struct IntuiMessage *) GT_GetIMsg(MainWindowPtr->UserPort))
        GT_ReplyIMsg(MsgPtr);
}

void draw(SBYTE x, SBYTE y, UBYTE image)
{   SWORD pixx;

    /* Isometric methodology:
	
    read the colour of the pixel, then set the colour in the image
    to the same value.

    So if, for the top left pixel, we read colour 10 (= $A or %1010),
    we have to clear the relevant bits in plane 0 and 2, and set them
    in planes 1 and 3.

    plane 0 (least significant bit)
        1234567890123456 pixels for top row (y = 0)
        1234567890123456 pixels for next row (y = 1)
        : : :
    plane 1
        : : :

     01234567890
    0..#########
    1..#########
    2.#########.
    3.#########.
    4#########..
    5#########..

    Image is effectively 9*6, but requires an 11*6 bitmap. */

    if (a == PLAYGAME && iso)
    {   pixx = (x * ISOSQUAREX) + 20 + ((FIELDY - y) * 3);
        pixy = (y * ISOSQUAREY) + STARTYPIXEL;

        if (useqdraw)
        {   // Turbo field rendering routines, thanks to Jilles Tjoelker.
            qdraw020iso
            (   &QDPI,
                pixx,
                pixy + MainWindowPtr->TopEdge,
                (WORD *) IsoImageData[image]
            );
        }
        else
        {   IsoImage.ImageData = IsoImageData[image];
            preserve(pixx,      0, image, 32768);
            preserve(pixx,      1, image, 32768);
            preserve(pixx,      2, image, 32768);
            preserve(pixx,      3, image, 32768);
            preserve(pixx +  1, 0, image, 16384);
            preserve(pixx +  1, 1, image, 16384);
            preserve(pixx +  9, 4, image,    64);
            preserve(pixx +  9, 5, image,    64);
            preserve(pixx + 10, 2, image,    32);
            preserve(pixx + 10, 3, image,    32);
            preserve(pixx + 10, 4, image,    32);
            preserve(pixx + 10, 5, image,    32);
            DrawImage
            (   MainWindowPtr->RPort,
                &IsoImage,
                pixx,
                pixy
            );
    }   }
    else
    {   if (useqdraw)
        {   // Turbo field rendering routines, thanks to Jilles Tjoelker.
            qdraw020
            (   &QDPI,
                STARTXPIXEL + (SQUAREX * x),
                STARTYPIXEL + (SQUAREY * y) + MainWindowPtr->TopEdge,
                (WORD *) ImageData[image]
            );
        }
        else
        {   Image.ImageData = ImageData[image];
            DrawImage
            (   MainWindowPtr->RPort,
                &Image,
                STARTXPIXEL + (SQUAREX * x),
                STARTYPIXEL + (SQUAREY * y)
            );
}   }   }

MODULE void preserve(SWORD x, SBYTE row, SBYTE image, UWORD amount)
{	LONG colour;
	
	/* x = image top-left corner coordinate on destination screen.
	row = y-row of the image.
	image = image number
	amount = value of the bit to be set/cleared. */

	colour = ReadPixel
	(	MainWindowPtr->RPort,
		x,
		pixy + row
	);
	if (colour & 8)
		IsoImageData[image][row + 18] |= amount;
	else IsoImageData[image][row + 18] &= ~amount;
	if (colour & 4)
		IsoImageData[image][row + 12] |= amount;
	else IsoImageData[image][row + 12] &= ~amount;
	if (colour & 2)
		IsoImageData[image][row + 6] |= amount;
	else IsoImageData[image][row + 6] &= ~amount;
	if (colour & 1)
		IsoImageData[image][row] |= amount;
	else IsoImageData[image][row] &= ~amount;
}

void effect(SBYTE index)
{   AUTO    SBYTE i;
    AUTO    SBYTE ok            = -1;
    AUTO    ULONG oldestreceipt = (ULONG) -1L;
    PERSIST ULONG nextreceipt   = 1L;

    /* oldestreceipt = temporary variable for ascertaining oldest
                       sound still playing.
    nextreceipt = next unused receipt number (monotonically incrementing). */

	if (mode == FX)
	{	for (i = 0; i <= 3; i++)
		{	/* decide on a channel */

			if (ok == -1)
			{	if (!eversent[i])
					ok = i;
                                // CheckIO() returns NULL if complete, pointer if incomplete.
                                elif (CheckIO((struct IORequest *) AudioRqPtr[i]))
				{	WaitIO((struct IORequest *) AudioRqPtr[i]);
					ok = i;
		}	}	}
        if (ok == -1)
        {   for (i = 0; i <= 3; i++)
                if (receipter[i] < oldestreceipt)
                {   ok = i;
                    oldestreceipt = receipter[i];
                }
            AbortIO((struct IORequest *) AudioRqPtr[ok]);
            WaitIO((struct IORequest *) AudioRqPtr[ok]);
        }
        eversent[ok] = TRUE;
        AudioRqPtr[ok]->ioa_Cycles              = 1;
        AudioRqPtr[ok]->ioa_Request.io_Command  = CMD_WRITE;
        AudioRqPtr[ok]->ioa_Request.io_Flags    = ADIOF_PERVOL;
        AudioRqPtr[ok]->ioa_Request.io_Unit     = (struct Unit *) (1 << ok);
        AudioRqPtr[ok]->ioa_Volume              = samp[index].volume;
        AudioRqPtr[ok]->ioa_Period              = (UWORD) samp[index].speed;
        AudioRqPtr[ok]->ioa_Request.io_Message.mn_ReplyPort
                                                = AudioPortPtr[ok];
        AudioRqPtr[ok]->ioa_Data                = (UBYTE *) samp[index].base;
        AudioRqPtr[ok]->ioa_Length              = samp[index].length[samp[index].bank];
        BeginIO(AudioRqPtr[ok]);
        receipter[ok] = nextreceipt;
}   }

AGLOBAL void help(UBYTE type)
{   TEXT  title[10];
    SWORD x, y;
    UBYTE i;

    effect(FXHELP);
    if (type == ORB)
    {   strcpy(title, "Creatures");
        x = 112;
        y = 18 + ((CREATUREHELPS + 1) * 10); // 128 currently
    } else
    {   strcpy(title, "Objects");
        x = 640;
        y = 28 + ((LASTOBJECT / 2) * 10);
    }
    if (!(HelpWindowPtr = (struct Window *) OpenWindowTags(NULL,
    WA_Left,          (SCREENXPIXEL / 2) - (x / 2),
    WA_Top,           (SCREENYPIXEL / 2) - (y / 2),
    WA_Width,         x,
    WA_Height,        y,
    WA_IDCMP,         IDCMP_CLOSEWINDOW | IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS,
    WA_Title,         title,
    WA_Gadgets,       NULL,
    WA_CustomScreen,  ScreenPtr,
    WA_DragBar,       TRUE,
    WA_CloseGadget,   TRUE,
    WA_NoCareRefresh, TRUE,
    WA_Activate,      TRUE,
    TAG_DONE)))
    {   say("Can't open help window!", RED);
        anykey(TRUE);
    } else
    {   SetAPen(HelpWindowPtr->RPort, WHITE);
        if (type == ORB)
        {   for (i = 0; i <= CREATUREHELPS; i++)
            {   Image.ImageData = ImageData[creaturehelp[i].image];
                DrawImage
                (   HelpWindowPtr->RPort,
                    &Image,
                    10,
                    15 + (i * 10)
                );
                Move(HelpWindowPtr->RPort, 26, 20 + (i * 10));
                Text(HelpWindowPtr->RPort, creaturehelp[i].desc, strlen(creaturehelp[i].desc));
        }   }
        else /* assumes (type == AFFIXER) */
        {   for (i = 0; i <= LASTOBJECT; i++)
            {   Image.ImageData = ImageData[i];
                if (i <= LASTOBJECT / 2)
                {   Move(HelpWindowPtr->RPort, 26, 20 + (i * 10));
                    DrawImage
                    (   HelpWindowPtr->RPort,
                        &Image,
                        10,
                        15 + (i * 10)
                    );
                } else
                {   Move(HelpWindowPtr->RPort, 354, 10 + ((i - (LASTOBJECT / 2)) * 10));
                    DrawImage
                    (   HelpWindowPtr->RPort,
                        &Image,
                        338,
                        5 + ((i - (LASTOBJECT / 2)) * 10)
                    );
                }
                Text(HelpWindowPtr->RPort, objectdesc[i], strlen(objectdesc[i]));
        }   }
        helploop(type);
}   }

AGLOBAL void helpmanual(void)
{
/*  struct NewAmigaGuide NAG = {NULL};
    AMIGAGUIDECONTEXT Handle;

    if (!(AmigaGuideBase = (struct AmigaGuideBase *) OpenLibrary("amigaguide.library", 0L)))
    {   say("Can't open amigaguide.library!", RED);
        anykey(TRUE);
        return;
    }

    // Fill in the NewAmigaGuide structure
    NAG.nag_Name = "PROGDIR:WormWars.guide";
    NAG.nag_Node = NULL;
    /* If you wanted a specific node of the AmigaGuide, you could set
    nag_Node to, for example, "Edit". */
    NAG.nag_Screen = ScreenPtr; // or NULL for default public screen

    // Open the AmigaGuide client
    if (Handle = OpenAmigaGuide(&NAG, TAG_DONE))
    {   /* if you opened it on the default public screen, add this line:
        ScreenToFront(ScreenPtr); */
        // Close the AmigaGuide client
        CloseAmigaGuide(Handle);
    } else
    {   /* if you want to know the reason, add this line:
        LONG rc = IoErr(); */
        DisplayBeep(ScreenPtr);
    }
    CloseLibrary((struct Library *) AmigaGuideBase); */

    if (SystemTagList("SYS:Utilities/MultiView >NIL: WormWars.guide", TAG_DONE) == -1)
        DisplayBeep(ScreenPtr);
    else ScreenToFront(ScreenPtr);
}

AGLOBAL void helpabout(void)
{       SBYTE line;
        SLONG projectval;
        TEXT  projectstring[7], ks[5] = "    ", wb[5] = "    ";
        ULONG ksval = SysBase->LibNode.lib_Version,
              wbval = IconBase->lib_Version;

        effect(FXHELP);
	if (!(HelpWindowPtr = (struct Window *) OpenWindowTags(NULL,
	WA_Left,			(SCREENXPIXEL / 2) - (ABOUTXPIXEL / 2),
	WA_Top,				(SCREENYPIXEL / 2) - (ABOUTYPIXEL / 2),
	WA_Width,			ABOUTXPIXEL,
	WA_Height,			ABOUTYPIXEL,
	WA_IDCMP,			IDCMP_CLOSEWINDOW | IDCMP_RAWKEY,
	WA_Title,			"About Worm Wars",
	WA_Gadgets,			NULL,
	WA_CustomScreen,	ScreenPtr,
	WA_DragBar,			TRUE,
	WA_CloseGadget,		TRUE,
	WA_NoCareRefresh,	TRUE,
	WA_Activate,		TRUE,
	TAG_DONE)))
	{	say("Can't open About... window!", RED);
		anykey(TRUE);
	} else
	{	/* calculate project size */
		
		projectval =
                  10                                 // header
                + ((HISCORES + 1) * HISCORESIZE)     // high scores
                + ((levels + 1) * (8 + (LEVELSIZE))) // level data
                + strlen(VERSION);                   // version string

                switch(ksval)
                {
                case 37:
                        strcpy(ks, "2.04");
                break;
                case 38:
                        strcpy(ks, "2.1 ");
                break;
                case 39:
                        strcpy(ks, "3.0 ");
                break;
                case 40:
                        strcpy(ks, "3.1 ");
                break;
                default:
                        strcpy(ks, "4.0+");
                break;
                }

                switch(wbval)
                {
                case 37:
                        strcpy(wb, "2.04");
                break;
                case 38:
                        strcpy(wb, "2.1 ");
                break;
                case 39:
                        strcpy(wb, "3.0 ");
                break;
                case 40:
                case 41:
                case 42:
                case 43:
                    strcpy(wb, "3.1 ");
                break;
                case 44:
                    strcpy(wb, "3.5 ");
                break;
                case 45:
                    strcpy(wb, "3.9 ");
                break;
                default:
                    strcpy(wb, "4.0+");
                break;
                }
                
		SetAPen(HelpWindowPtr->RPort, worm[rand() % 4].colour);
		RectFill(HelpWindowPtr->RPort, 8, 13, ABOUTXPIXEL - 11, ABOUTYPIXEL - 6);
		SetAPen(HelpWindowPtr->RPort, ABOUTSHADOW);
		Move(HelpWindowPtr->RPort, 7, ABOUTYPIXEL - 5);
		Draw(HelpWindowPtr->RPort, 7, 12);
		Draw(HelpWindowPtr->RPort, ABOUTXPIXEL - 10, 12);
		SetAPen(HelpWindowPtr->RPort, ABOUTSHINE);
		Draw(HelpWindowPtr->RPort, ABOUTXPIXEL - 10, ABOUTYPIXEL - 5);
		Draw(HelpWindowPtr->RPort, 8, ABOUTYPIXEL - 5);
		SetAPen(HelpWindowPtr->RPort, BLACK);
		SetDrMd(HelpWindowPtr->RPort, JAM1);
		for (line = 0; line <= ABOUTLINES; line++)
                {   Move(HelpWindowPtr->RPort, about[line].x, about[line].y);
                    Text(HelpWindowPtr->RPort, about[line].text, (SBYTE) strlen(about[line].text));
		}
		stcl_d(projectstring, projectval);
                align(projectstring, 6, ' ');
		Move(HelpWindowPtr->RPort, PROJECTX, PROJECTY);
                Text(HelpWindowPtr->RPort, projectstring, 6);
                Move(HelpWindowPtr->RPort, KICKSTARTX, KICKSTARTY);
                Text(HelpWindowPtr->RPort, ks, 4);
                Move(HelpWindowPtr->RPort, WORKBENCHX, WORKBENCHY);
                Text(HelpWindowPtr->RPort, wb, 4);

		DrawBevelBox(HelpWindowPtr->RPort, 18, 21, 47, 22, GT_VisualInfo, VisualInfoPtr);
		DrawImage(HelpWindowPtr->RPort, &About, 19, 22);

                // boingball();
                helploop(NOSQUARE);
}        }

AGLOBAL void helploop(UBYTE type)
{   ABOOL                done = FALSE;
    UBYTE                objx, objy;
    UWORD                code, qual;
    SWORD                mousex, mousey;
    ULONG                class;
    struct IntuiMessage* MsgPtr;

    while(!done)
    {   /* if (type == NOSQUARE)
            DrawGels();
        else */ Wait(1L << HelpWindowPtr->UserPort->mp_SigBit);
        while (MsgPtr = (struct IntuiMessage *) GetMsg(HelpWindowPtr->UserPort))
        {   class  = MsgPtr->Class;
            code   = MsgPtr->Code;
            qual   = MsgPtr->Qualifier;
            mousex = MsgPtr->MouseX;
            mousey = MsgPtr->MouseY;
            ReplyMsg((struct Message *) MsgPtr);
            switch(class)
            {
            case IDCMP_CLOSEWINDOW:
                done = TRUE;
            break;
            case IDCMP_RAWKEY:
                if (code == SPACEBAR || code == RETURN || code == ENTER || code == HELP)
                    done = TRUE;
                elif (code == ESCAPE)
                {   if (qual & IEQUALIFIER_LSHIFT || qual & IEQUALIFIER_RSHIFT)
                    {   if (verify())
                        {   CloseWindow(HelpWindowPtr);
                            cleanexit(EXIT_SUCCESS);
                   }   }
                   else done = TRUE;
                }
            break;
            case IDCMP_MOUSEBUTTONS:
                if (a == FIELDEDIT && type == AFFIXER)
                {   objx = (mousex - 10) / 328; // which column
                    objy = (mousey - 13) / 10;  // which row
                    if ((objx == 0 || objx == 1) && (objy >= 0 && objy <= LASTOBJECT / 2))
                    {   if (objx)
                            setbrush(objy + 1 + (LASTOBJECT / 2));
                        else setbrush(objy);
                        done = TRUE;
                }   }
            break;
            default:
            break;
    }   }   }
    /* if (type == NOSQUARE)
         unboingball(); */
    CloseWindow(HelpWindowPtr);
    clearkybd();
}

AGLOBAL void filedelete(void)
{   TEXT newpathname[255], temp1[81];

    if (AslRequestTags(ASLRqPtr, ASL_Hail, "Delete Fieldset", ASL_FuncFlags, FILF_PATGAD | FILF_SAVE, TAG_DONE) && *(ASLRqPtr->rf_File) != 0)
    {   strcpy(newpathname, ASLRqPtr->rf_Dir);
        AddPart(newpathname, ASLRqPtr->rf_File, 254);
        if (DeleteFile(newpathname))
        {   strcpy(temp1, "Deleted ");
            strcat(temp1, newpathname);
            strcat(temp1, ".");
        } else
        {   strcpy(temp1, "Couldn't delete ");
            strcat(temp1, newpathname);
            strcat(temp1, "!");
        }
        say(temp1, WHITE);
}   }

AGLOBAL void fileopen(ABOOL revert)
{   TEXT temp1[81] = {"Opened "}, temp2[3], newpathname[255];

    strcpy(newpathname, pathname);
    if (revert || (AslRequestTags(ASLRqPtr, ASL_Hail, "Open Fieldset", ASL_FuncFlags, FILF_PATGAD, TAG_DONE) && *(ASLRqPtr->rf_File)))
    {   if (!revert)
        {   strcpy(newpathname, ASLRqPtr->rf_Dir);
            AddPart(newpathname, ASLRqPtr->rf_File, 254);
        }
        if (!loadfields(newpathname))
        {   strcpy(pathname, newpathname);
            strcat(temp1, pathname);
            strcat(temp1, " (");
            stci_d(temp2, levels);
            strcat(temp1, temp2);
            strcat(temp1, " levels).");
            say(temp1, WHITE);
            if (a == FIELDEDIT)
                turborender();
            else hiscores();
        } else
        {   strcpy(temp1, "Couldn't open ");
            strcat(temp1, newpathname);
            strcat(temp1, "!");
            say(temp1, WHITE);
    }   }
    if (a == GAMEOVER)
        anykey(TRUE);
}

AGLOBAL void filesaveas(ABOOL flag)
{	ABOOL	cont = TRUE;
	TEXT	newpathname[255], temp1[SAYLIMIT + 1], temp2[3];
        struct DiskObject* InfoHandle;

    /* flag is TRUE for 'save as...', FALSE for 'save'. */

	strcpy(newpathname, pathname);
    if (flag)
        if (AslRequestTags(ASLRqPtr, ASL_Hail, "Save Fieldset", ASL_FuncFlags, FILF_PATGAD | FILF_SAVE, TAG_DONE) && *(ASLRqPtr->rf_File) != 0)
        {   strcpy(newpathname, ASLRqPtr->rf_Dir);
            AddPart(newpathname, ASLRqPtr->rf_File, 254);
        } else cont = FALSE;
    if (cont)
    {   matchteleports();
        strcpy(temp1, "Saving ");
	strcat(temp1, newpathname);
	strcat(temp1, "...");
	say(temp1, WHITE);
	if (savefields(newpathname))
        {   strcpy(pathname, newpathname);
            if (icons)
            {   InfoHandle = GetDiskObjectNew(DEFAULTSET);
                InfoHandle->do_CurrentX = NO_ICON_POSITION;
                InfoHandle->do_CurrentY = NO_ICON_POSITION;
                if (!PutDiskObject(pathname, InfoHandle))
                {   say("Couldn't write .info file!", RED);
                    anykey(TRUE);
            }   }
            strcpy(temp1, "Saved ");
            strcat(temp1, pathname);
            strcat(temp1, " (");
            stci_d(temp2, levels);
            strcat(temp1, temp2);
            strcat(temp1, " levels).");
        } else
        {   strcpy(temp1, "Couldn't save ");
            strcat(temp1, newpathname);
            strcat(temp1, "!");
        }
        say(temp1, WHITE);
        if (a == GAMEOVER)
            anykey(TRUE);
}   }

MODULE void freefx(void)
{   SBYTE i;

    stopfx();
    if (!AudioClosed)
    {   CloseDevice((struct IORequest *) AudioRqPtr[0]);
        AudioClosed = TRUE;
    }
    for (i = 0; i <= 3; i++)
    {   if (AudioRqPtr[i])
        {   DeleteIORequest(AudioRqPtr[i]);
            AudioRqPtr[i] = NULL;
        }
        if (AudioPortPtr[i])
        {   // if we ReplyMsg() first, that causes crashes
            DeleteMsgPort(AudioPortPtr[i]);
            AudioPortPtr[i] = NULL;
    }   }
    if (fbase)
    {   FreeMem(fbase, fsize);
        fbase = NULL;
    }
    if (FilePtr)
    {   Close(FilePtr);
        FilePtr = NULL;
}   }

void gameinput(void)
{   ABOOL                done;
    UWORD                code, qual;
    ULONG                class;
    struct IntuiMessage* MsgPtr;
    SBYTE                keyplayer;
    UBYTE                which;

    for (which = 0; which <= NUMKEYS; which++)
        key[which].down = FALSE;

/* keyboard */

while (MsgPtr = (struct IntuiMessage *) GetMsg(MainWindowPtr->UserPort))
{   class = MsgPtr->Class;
    code  = MsgPtr->Code;
    qual  = MsgPtr->Qualifier;
    ReplyMsg((struct Message *) MsgPtr);
    if (class == IDCMP_RAWKEY && (!(qual & IEQUALIFIER_REPEAT)))
    {   if (code < KEYUP)
        {   switch(code)
            {
            case M:
                toggle(M);
            break;
                case F:
                toggle(F);
            break;
            case P:
                clearkybd();
                say("Paused...press P to unpause", WHITE);
                pausetimer();
                done = FALSE;
                while (!done)
                {   Wait(1L << MainWindowPtr->UserPort->mp_SigBit);
                    while (MsgPtr = (struct IntuiMessage *) GetMsg(MainWindowPtr->UserPort))
                    {   class = MsgPtr->Class;
                        code  = MsgPtr->Code;
                        qual  = MsgPtr->Qualifier;
                        ReplyMsg((struct Message *) MsgPtr);
                        if (class == IDCMP_RAWKEY && (!(qual & IEQUALIFIER_REPEAT)))
                        {   if (code == ESCAPE)
                            {   if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
                                {   if (verify())
                                        cleanexit(EXIT_SUCCESS);
                                } else
                                {   a = GAMEOVER;
                                    done = TRUE;
                                    worm[0].lives = worm[1].lives = worm[2].lives = worm[3].lives = 0;
                            }   }
                            elif (code == M)
                                toggle(M);
                            elif (code == F)
                                toggle(F);
                            elif (code == P)
                            {   say("Unpaused", WHITE);
                                done = TRUE;
                }   }   }   }
                unpausetimer();
            break;
            case ESCAPE:
                if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
                {   if (verify())
                        cleanexit(EXIT_SUCCESS);
                } else
                {   a = GAMEOVER;
                    worm[0].lives = worm[1].lives = worm[2].lives = worm[3].lives = 0;
                }
            break;
            default:
               for (which = 0; which <= NUMKEYS; which++)
                   if (code == key[which].scancode)
                       key[which].down = TRUE;
            break;
    }   }   }
    elif (class == IDCMP_CLOSEWINDOW)
        cleanexit(EXIT_SUCCESS);
    elif (class == IDCMP_REFRESHWINDOW)
    {   GT_BeginRefresh(MainWindowPtr);
        GT_EndRefresh(MainWindowPtr, TRUE);
}   }

/* Received but ignored: IDCMP_ACTIVEWINDOW, IDCMP_MOUSEBUTTONS and
IDCMP_INTUITICKS. */

for (which = 0; which <= NUMKEYS; which++)
{   if (key[which].down)
    {   if (key[which].special == ONEHUMAN)
        {   if
            (   (worm[0].control == KEYBOARD && worm[1].control != KEYBOARD)
            )
                wormqueue(0, key[which].deltax, key[which].deltay);
            elif
            (   (worm[1].control == KEYBOARD && worm[0].control != KEYBOARD)
            )
                wormqueue(1, key[which].deltax, key[which].deltay);
        } elif (key[which].special == MOVE || key[which].special == AMMO)
        {   if (worm[key[which].player].control == KEYBOARD)
                keyplayer = key[which].player;
            elif
            (   key[which].player == 1
             && (worm[0].control == KEYBOARD && worm[1].control != KEYBOARD)
            )
                keyplayer = 0;
            elif
            (   key[which].player == 0
             && (worm[1].control == KEYBOARD && worm[0].control != KEYBOARD)
            )
                keyplayer = 1;
            else keyplayer = -1;
            if (keyplayer != -1)
            {   wormqueue(keyplayer, key[which].deltax, key[which].deltay);
        }   }
        elif (worm[1].lives) /* assumes key[which].special == TRAINER */
            train(key[which].scancode);
}   }

/* joystick `2' */

if (worm[2].control == JOYSTICK)
    while (GetMsg(JoyPortPtr))
    {   if (GameEvent.ie_position.ie_xy.ie_x != 0 || GameEvent.ie_position.ie_xy.ie_y != 0)
            wormqueue(2, GameEvent.ie_position.ie_xy.ie_x, GameEvent.ie_position.ie_xy.ie_y);
        if (GameEvent.ie_Code == IECODE_LBUTTON)
            wormqueue(2, 0, 0);
        sendreadrequest();
}   }

void hiscores(void)
{   SBYTE which;
    TEXT  tempstring[NAMELENGTH + 1];

/* render hiscores

#################################################### # = shadow
#   #   #   #                      #     #         % % = shine
#   #   #   #                      #     #         %
#   #   #   #                      #     #         %
#   #   #   #                      #     #         %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% */

SetDrMd(MainWindowPtr->RPort, JAM1);
for (which = 0; which <= HISCORES; which++)
{	if (hiscore[which].player == -1)
		SetAPen(MainWindowPtr->RPort, LIGHTGREY);
	else SetAPen(MainWindowPtr->RPort, worm[hiscore[which].player].colour);
        RectFill(MainWindowPtr->RPort, 104, TSOFFSET + 1 + (which * HISCOREDISTANCE), 536, TSOFFSET + 10 + (which * HISCOREDISTANCE));
	if (hiscore[which].player == 0)
		SetAPen(MainWindowPtr->RPort, DARKGREEN);
	else if (hiscore[which].player == 1)
		SetAPen(MainWindowPtr->RPort, DARKRED);
	else if (hiscore[which].player == 2)
		SetAPen(MainWindowPtr->RPort, DARKBLUE);
	else if (hiscore[which].player == 3)
		SetAPen(MainWindowPtr->RPort, DARKYELLOW);
	else SetAPen(MainWindowPtr->RPort, DARKGREY);
        Move(MainWindowPtr->RPort, 103, TSOFFSET + 11 + (which * HISCOREDISTANCE));
        Draw(MainWindowPtr->RPort, 103, TSOFFSET +      (which * HISCOREDISTANCE));
        Draw(MainWindowPtr->RPort, 537, TSOFFSET +      (which * HISCOREDISTANCE));

	if (hiscore[which].player != -1)
	{	/* divider bars */
	
                Move(MainWindowPtr->RPort, 182 - 55, TSOFFSET +      (which * HISCOREDISTANCE));
                Draw(MainWindowPtr->RPort, 182 - 55, TSOFFSET + 10 + (which * HISCOREDISTANCE));
                Move(MainWindowPtr->RPort, 254 - 55, TSOFFSET +      (which * HISCOREDISTANCE));
                Draw(MainWindowPtr->RPort, 254 - 55, TSOFFSET + 10 + (which * HISCOREDISTANCE));
                Move(MainWindowPtr->RPort, 290 - 55, TSOFFSET +      (which * HISCOREDISTANCE));
                Draw(MainWindowPtr->RPort, 290 - 55, TSOFFSET + 10 + (which * HISCOREDISTANCE));
                Move(MainWindowPtr->RPort, 416,      TSOFFSET +      (which * HISCOREDISTANCE));
                Draw(MainWindowPtr->RPort, 416,      TSOFFSET + 10 + (which * HISCOREDISTANCE));
                Move(MainWindowPtr->RPort, 464,      TSOFFSET +      (which * HISCOREDISTANCE));
                Draw(MainWindowPtr->RPort, 464,      TSOFFSET + 10 + (which * HISCOREDISTANCE));
	}
	
	SetAPen(MainWindowPtr->RPort, WHITE);
        Move(MainWindowPtr->RPort, 159 - 55, TSOFFSET + 11 + (which * HISCOREDISTANCE));
        Draw(MainWindowPtr->RPort, 482 + 55, TSOFFSET + 11 + (which * HISCOREDISTANCE));
        Draw(MainWindowPtr->RPort, 482 + 55, TSOFFSET +  1 + (which * HISCOREDISTANCE));
	SetAPen(MainWindowPtr->RPort, BLACK);

	if (hiscore[which].player != -1)
	{	stci_d(tempstring, which + 1);
		tempstring[1] = '.';
                Move(MainWindowPtr->RPort, 161 - 55, TSOFFSET + 8 + (which * HISCOREDISTANCE));
		Text(MainWindowPtr->RPort, tempstring, 2);
		stci_d(tempstring, hiscore[which].score);
		align(tempstring, 7, ' ');
                Move(MainWindowPtr->RPort, 193 - 55, TSOFFSET + 8 + (which * HISCOREDISTANCE));
		Text(MainWindowPtr->RPort, tempstring, 7);
		if (hiscore[which].level == -1)
			strcpy(tempstring, "All");
		else
		{	stci_d(tempstring, hiscore[which].level);
			align(tempstring, 3, ' ');
		}
                Move(MainWindowPtr->RPort, 206, TSOFFSET + 8 + (which * HISCOREDISTANCE));
		Text(MainWindowPtr->RPort, tempstring, 3);
                Move(MainWindowPtr->RPort, 241, TSOFFSET + 8 + (which * HISCOREDISTANCE));
		Text(MainWindowPtr->RPort, hiscore[which].name, strlen(hiscore[which].name));
                Move(MainWindowPtr->RPort, 418, TSOFFSET + 8 + (which * HISCOREDISTANCE));
		Text(MainWindowPtr->RPort, hiscore[which].time, strlen(hiscore[which].time));
                Move(MainWindowPtr->RPort, 466, TSOFFSET + 8 + (which * HISCOREDISTANCE));
		Text(MainWindowPtr->RPort, hiscore[which].date, strlen(hiscore[which].date));
}	}
SetDrMd(MainWindowPtr->RPort, JAM2);
}

void hiscorenames(void)
{
ULONG					class;
ABOOL					done;
SBYTE					which;
struct IntuiMessage*	MsgPtr;

for (which = 0; which <= HISCORES; which++)
	if (hiscore[which].fresh)
	{	GT_SetGadgetAttrs(StringGadgetPtr[which], MainWindowPtr, NULL, GA_Disabled, FALSE, GTST_String, worm[hiscore[which].player].name, TAG_DONE);
		ActivateGadget(StringGadgetPtr[which], MainWindowPtr, NULL);
		done = FALSE;
		while (!done)
		{	while (MsgPtr = (struct IntuiMessage *) GT_GetIMsg(MainWindowPtr->UserPort))
			{	class = MsgPtr->Class;
				GT_ReplyIMsg(MsgPtr);
				if (class == IDCMP_GADGETUP)
					done = TRUE;
				else if (class == IDCMP_MOUSEBUTTONS)
					ActivateGadget(StringGadgetPtr[which], MainWindowPtr, NULL);
				else if (class == IDCMP_REFRESHWINDOW)
				{	GT_BeginRefresh(MainWindowPtr);
					GT_EndRefresh(MainWindowPtr, TRUE);
		}	}	}
		GT_SetGadgetAttrs(StringGadgetPtr[which], MainWindowPtr, NULL, GA_Disabled, TRUE, TAG_DONE);
                effect(FXAPPLAUSE);
		strcpy(hiscore[which].name, ((struct StringInfo *) (StringGadgetPtr[which]->SpecialInfo))->Buffer);
		if (hiscore[which].name[0] >= 97 && hiscore[which].name[0] <= 123)
			hiscore[which].name[0] -= 32;
		strcpy(worm[hiscore[which].player].name, hiscore[which].name);
		hiscore[which].fresh = FALSE;
		hiscores();
}	}

MODULE ABOOL beginfx(void)
{   		SBYTE	i;
	PERSIST	UBYTE	chan[]	= {15};

    for (i = 0; i <= 3; i++)
    {   eversent[i] = FALSE;
    	if (!(AudioPortPtr[i] = (struct MsgPort *) CreateMsgPort()))
        {   freefx();
            draw(MUSICICON, ICONY, BLACKENED);
            mode = FALSE;
            say("No port for effects!", RED);
            anykey(TRUE);
            return FALSE;
        } else if (!(AudioRqPtr[i] = (struct IOAudio *) CreateIORequest(AudioPortPtr[i], sizeof(struct IOAudio))))
        {   freefx();
            draw(MUSICICON, ICONY, BLACKENED);
            mode = FALSE;
            say("No I/O memory for effects!", RED);
            anykey(TRUE);
            return FALSE;
    }   }
    AudioRqPtr[0]->ioa_Request.io_Message.mn_ReplyPort      = AudioPortPtr[0];
    AudioRqPtr[0]->ioa_Request.io_Message.mn_Node.ln_Pri    = 127;
    AudioRqPtr[0]->ioa_AllocKey                             = 0;
    AudioRqPtr[0]->ioa_Data                                 = chan;
    AudioRqPtr[0]->ioa_Length                               = 1;
    if (AudioClosed = OpenDevice(AUDIONAME, 0L, (struct IORequest *) AudioRqPtr[0], 0L))
    {   freefx();
        draw(MUSICICON, ICONY, BLACKENED);
        mode = FALSE;
        say("Can't allocate all channels for effects!", RED);
        anykey(TRUE);
		return FALSE;
    } else
    {   for (i = 1; i <= 3; i++)
            CopyMem(AudioRqPtr[0], AudioRqPtr[i], sizeof(struct IOAudio));
        return TRUE;
}   }

void resettime(void)
{   GetSysTime(StartValPtr);
    srand((UWORD) StartValPtr->tv_micro);
}

void rundown(SBYTE player)
{       ABOOL                done = FALSE, flag = FALSE;
	TEXT                 tempstring[6];
	SBYTE                i, j, x, y;
        UBYTE                multiply = worm[player].multi * players;
	UWORD                counter = 0, code, qual;
	ULONG                levelbonus, timebonus, tailbonus, class;
	struct IntuiMessage* MsgPtr;

	clearscreen();
	SetAPen(MainWindowPtr->RPort, worm[player].colour);

	Image.ImageData = ImageData[TREASURE];
	DrawImage(MainWindowPtr->RPort, &Image, 201, 103);
	Move(MainWindowPtr->RPort, 218, 108);
	Text(MainWindowPtr->RPort, "Level Bonus:    ## x ##00 =", 27);
        stci_d(tempstring, multiply);
	align(tempstring, 2, ' ');
	Move(MainWindowPtr->RPort, 218 + (FONTX * 21), 108);
	Text(MainWindowPtr->RPort, tempstring, 2);
	stci_d(tempstring, level - 1);
	align(tempstring, 2, ' ');
	Move(MainWindowPtr->RPort, 218 + (FONTX * 16), 108);
	Text(MainWindowPtr->RPort, tempstring, 2);

	Image.ImageData = ImageData[CLOCK];
	DrawImage(MainWindowPtr->RPort, &Image, 201, 111);
	Move(MainWindowPtr->RPort, 218, 116);
	Text(MainWindowPtr->RPort, " Time Bonus: ##:## x  ##0 =", 27);
        stci_d(tempstring, multiply);
	align(tempstring, 2, ' ');
	Move(MainWindowPtr->RPort, 226 + (FONTX	 * 21), 116);
	Text(MainWindowPtr->RPort, tempstring, 2);
	stci_d(tempstring, secondsleft / 60);
	align(tempstring, 2, ' ');
	Move(MainWindowPtr->RPort, 226 + (FONTX * 12), 116);
	Text(MainWindowPtr->RPort, tempstring, 2);
	stci_d(tempstring, secondsleft % 60);
	align(tempstring, 2, '0');
	Move(MainWindowPtr->RPort, 226 + (FONTX * 15), 116);
	Text(MainWindowPtr->RPort, tempstring, 2);

	Image.ImageData = ImageData[FIRSTTAIL + player];
	DrawImage(MainWindowPtr->RPort, &Image, 201, 119);
	Move(MainWindowPtr->RPort, 218, 124);
	Text(MainWindowPtr->RPort, " Tail Bonus:  #### x   ## =", 27);
        stci_d(tempstring, multiply);
	align(tempstring, 2, ' ');
        Move(MainWindowPtr->RPort, 226 + (FONTX * 22), 124);
        Text(MainWindowPtr->RPort, tempstring, 2);
	for (x = 0; x <= FIELDX; x++)
		for (y = 0; y <= FIELDY; y++)
			if (field[x][y] == FIRSTTAIL + player)
				counter++;
    stci_d(tempstring, counter);
    align(tempstring, 4, ' ');
    Move(MainWindowPtr->RPort, 226 + (FONTX * 13), 124);
    Text(MainWindowPtr->RPort, tempstring, 4);

    levelbonus = (level - 1) * 100 * multiply;
    timebonus = secondsleft * 10 * multiply;
    tailbonus = counter * multiply;

    for (i = 0; i <= 3; i++)
        if (worm[i].control != NONE)
            for (j = 0; j <= LASTOBJECT; j++)
                stat(i, j);

    while (!done)
    {   while (MsgPtr = (struct IntuiMessage *) GetMsg(MainWindowPtr->UserPort))
        {   class  = MsgPtr->Class;
	    code   = MsgPtr->Code;
	    qual   = MsgPtr->Qualifier;
	    ReplyMsg((struct Message *) MsgPtr);
            if (class == IDCMP_RAWKEY && (!(qual & IEQUALIFIER_REPEAT)) && code < KEYUP)
                done = TRUE;
        }
        if (done)
	{   wormscore
            (   player,
                (levelbonus / multiply) +
                ( timebonus / multiply) +
                ( tailbonus / multiply)
            );
            levelbonus = timebonus = tailbonus = 0;
        }
        SetAPen(MainWindowPtr->RPort, worm[player].colour);
        stci_d(tempstring, levelbonus);
        align(tempstring, 5, ' ');
        Move(MainWindowPtr->RPort, 218 + (FONTX * 27), 108);
        Text(MainWindowPtr->RPort, tempstring, 5);

        stci_d(tempstring, timebonus);
        align(tempstring, 5, ' ');
        Move(MainWindowPtr->RPort, 226 + (FONTX * 26), 116);
        Text(MainWindowPtr->RPort, tempstring, 5);

        stci_d(tempstring, tailbonus);
        align(tempstring, 5, ' ');
        Move(MainWindowPtr->RPort, 226 + (FONTX * 26), 124);
        Text(MainWindowPtr->RPort, tempstring, 5);

        done = TRUE;
        if (levelbonus)
        {   levelbonus -= multiply;
            wormscore(player, 1);
            done = FALSE;
        }
        if (timebonus)
        {   timebonus -= multiply;
            wormscore(player, 1);
            done = FALSE;
        }
        if (tailbonus)
        {   tailbonus -= multiply;
            wormscore(player, 1);
            done = FALSE;
        }
        effect(FXCLICK);
    }
    clearkybd();
    effect(FXRIFF);
    for (i = 0; i <= 3; i++)
        if (worm[i].control == KEYBOARD || worm[i].control == JOYSTICK)
        {   flag = TRUE;
            break;
        }
    waitasec();
    if (flag)
        anykey(FALSE);
}

void say(STRPTR sentence, COLOUR colour)
{   SBYTE length = (SBYTE) strlen(sentence);

    /* truncate text */

    if (length > SAYLIMIT)
        *(sentence + SAYLIMIT - 1) = *(sentence + SAYLIMIT - 2) = *(sentence + SAYLIMIT - 3) = '.';

    /* clear areas to left, right and top of text, respectively */

    SetAPen(MainWindowPtr->RPort, BLACK);
    RectFill
    (   MainWindowPtr->RPort,
        SAYSTARTXPIXEL,
        2,
        (SCREENXPIXEL / 2) - (length * FONTX / 2),
        10
    );
    RectFill
    (   MainWindowPtr->RPort,
        (SCREENXPIXEL / 2) + (length * FONTX / 2) + 1,
        2,
        SAYENDXPIXEL,
        10
    );
    Move(MainWindowPtr->RPort, (SCREENXPIXEL / 2) - (length * FONTX / 2), 2);
    Draw(MainWindowPtr->RPort, (SCREENXPIXEL / 2) + (length * FONTX / 2), 2);

    /* render shadow text */
	
    SetAPen(MainWindowPtr->RPort, MEDIUMGREY);
    Move(MainWindowPtr->RPort, (SCREENXPIXEL / 2) - (length * FONTX / 2) + 1, FONTY + 1);
    Text(MainWindowPtr->RPort, sentence, length);
	
    /* render actual text */
	
    SetDrMd(MainWindowPtr->RPort, JAM1);
    SetAPen(MainWindowPtr->RPort, colour);
    Move(MainWindowPtr->RPort, (SCREENXPIXEL / 2) - (length * FONTX / 2), FONTY);
    Text(MainWindowPtr->RPort, sentence, length);
    SetDrMd(MainWindowPtr->RPort, JAM2);
}
	
void sendreadrequest(void)
{   JoyRqPtr->io_Command = GPD_READEVENT;
    JoyRqPtr->io_Flags   = NULL;
    JoyRqPtr->io_Length  = sizeof(struct InputEvent);
    JoyRqPtr->io_Data    = (APTR) &GameEvent;
    SendIO((struct IORequest *) JoyRqPtr);
}

void stat(SBYTE player, SBYTE line)
{   ABOOL print = TRUE;
    SBYTE i, len, theline;
    TEXT  output[9];

    strcpy(output, "        "); /* 8 spaces */
    switch (line)
    {
    case BONUS:
        do
        {   worm[player].oldscore += (LIFEMODULO - (worm[player].oldscore % LIFEMODULO));
            if (worm[player].score >= worm[player].oldscore)
            {   worm[player].lives++;
                stat(player, LIFE);
        }   }
        while (worm[player].score > worm[player].oldscore);
        worm[player].oldscore = worm[player].score;
        if (worm[player].multi > 1)
            SetAPen(MainWindowPtr->RPort, WHITE);
        else SetAPen(MainWindowPtr->RPort, worm[player].colour);
        stcl_d(output, worm[player].score);
        theline = 0;
        if (worm[player].score <= 999999)
        {   len = 6;
            if (player == 1 && iso && worm[1].score <= 99999)
                align(output, 6, ' ');
            else
                for (i = 0; i <= 8; i++)
                    if (!output[i])
                        output[i] = ' ';
        } else
        {   len = 8;
            for (i = 0; i <= 8; i++)
                if (!output[i])
                    output[i] = ' ';
            // assert(worm[player].score <= 99999999);
        }
    break;
    case LIFE:
        if (worm[player].lives > STARTLIVES)
        {   SetAPen(MainWindowPtr->RPort, WHITE);
            if (worm[player].lives > LIVESLIMIT)
                worm[player].lives = LIVESLIMIT;
        } else SetAPen(MainWindowPtr->RPort, worm[player].colour);
        stci_d(output, worm[player].lives);
        if (player == 1 && iso)
        {   len = 6;
            align(output, 6, ' ');
        } else
        {   len = 3;
            for (i = 0; i <= 2; i++)
                if (!output[i])
                    output[i] = ' ';
        }
        theline = 1;
    break;
    case BIAS:
        if (worm[player].bias > 0)
        {   if (worm[player].bias > BIASLIMIT)
                worm[player].bias = BIASLIMIT;
            SetAPen(MainWindowPtr->RPort, WHITE);
        } else SetAPen(MainWindowPtr->RPort, worm[player].colour);
        stci_d(output, worm[player].bias);
        for (i = 0; i <= 2; i++)
            if (!output[i])
                output[i] = ' ';
        theline = 2;
        len = 3;
    break;
    case NITRO:
            if (worm[player].nitro)
                SetAPen(MainWindowPtr->RPort, WHITE);
            else SetAPen(MainWindowPtr->RPort, worm[player].colour);
            if (worm[player].speed == FAST)
                strcpy(output, "Fast  ");
            elif (worm[player].speed == NORMAL)
                strcpy(output, "Normal");
            elif (worm[player].speed == SLOW)
                strcpy(output, "Slow  ");
            else
            {   // assert(worm[player].speed == VERYSLOW);
                strcpy(output, "V.Slow");
            }
            theline = 3;
            len = 6;
        break;
	case AMMO:
            if (worm[player].ammo)
            {   if (worm[player].ammo > AMMOLIMIT)
                    worm[player].ammo = AMMOLIMIT;
                SetAPen(MainWindowPtr->RPort, WHITE);
            } else SetAPen(MainWindowPtr->RPort, worm[player].colour);
            stci_d(output, worm[player].ammo);
            for (i = 0; i <= 2; i++)
                if (!output[i])
                    output[i] = ' ';
            if (iso)
                theline = 4;
            else theline = 5;
            len = 3;
        break;
	case POWER:
            switch(worm[player].power)
            {
            case 0:
                SetAPen(MainWindowPtr->RPort, worm[player].colour);
                strcpy(output, "Single");
            break;
            case 2:
                SetAPen(MainWindowPtr->RPort, WHITE);
                strcpy(output, "Triple");
            break;
            case 4:
                SetAPen(MainWindowPtr->RPort, WHITE);
                strcpy(output, "Quint.");
            break;
            case 6:
                SetAPen(MainWindowPtr->RPort, WHITE);
                strcpy(output, "Sept. ");
            break;
            default:
            break;
            }
            if (iso)
                theline = 5;
            else theline = 6;
            len = 6;
        break;
	case ARMOUR:
            if (worm[player].armour > MODELIMIT)
                worm[player].armour = MODELIMIT;
            if (worm[player].mode == ARMOUR)
                SetAPen(MainWindowPtr->RPort, WHITE);
            else SetAPen(MainWindowPtr->RPort, worm[player].colour);
            stci_d(output, worm[player].armour);
            for (i = 0; i <= 2; i++)
                if (!output[i])
                    output[i] = ' ';
            if (iso)
                theline = 6;
            else theline = 8;
            len = 3;
    break;
    case TONGUE:
        if (worm[player].tongue > MODELIMIT)
            worm[player].tongue = MODELIMIT;
        if (worm[player].mode == TONGUE)
            SetAPen(MainWindowPtr->RPort, WHITE);
        else SetAPen(MainWindowPtr->RPort, worm[player].colour);
        stci_d(output, worm[player].tongue);
        for (i = 0; i <= 2; i++)
            if (!output[i])
                output[i] = ' ';
        if (iso)
            theline = 7;
        else theline = 9;
        len = 3;
    break;
    default:
        print = FALSE;
        /* These next two lines are just to prevent spurious compiler
        warnings about possibly uninitialized variables */
        theline = 0;
        len = 0;
    break;
    }

    /* Sometimes stat() is called with a valid line, yet an invalid player.
    Defensive programming currently ensures that this is not a problem, but
    it would be better to fix the bug. */

    if (print)
    {   if (iso)
        {   switch(player)
            {
            case 0:
                Move(MainWindowPtr->RPort, 0  ,   8 + (theline * FONTY));
            break;
            case 1:
                Move(MainWindowPtr->RPort, 528, 189 + (theline * FONTY));
            break;
            case 2:
                Move(MainWindowPtr->RPort, 66 ,   8 + (theline * FONTY));
            break;
            case 3:
                Move(MainWindowPtr->RPort, 592, 189 + (theline * FONTY));
            break;
            default:
                // assert(0);
            break;
        }   }
        else
        {   Move
            (   MainWindowPtr->RPort,
                (FONTX * 3) + (worm[player].statx * ENDXPIXEL),
                STARTYPIXEL + 6 + (theline * FONTY) + (worm[player].staty * (ENDYPIXEL - 1 - (11 * FONTY)))
            );
        }
        Text(MainWindowPtr->RPort, output, len);
}   }

void stopfx(void)
{   SBYTE i;

    if (mode == FX)
        for (i = 0; i <= 3; i++)
            // CheckIO() returns NULL if complete, pointer if incomplete? (!)
            if (eversent[i] && (!(CheckIO((struct IORequest *) AudioRqPtr[i]))))
            {   AbortIO((struct IORequest *) AudioRqPtr[i]);
                WaitIO((struct IORequest *) AudioRqPtr[i]);
}           }

void titlescreen(void)
{   SBYTE                lisa = 0, object = LASTOBJECT, player;
    SWORD                descx = FIRSTDESCX;
    ULONG                class;
    UWORD                code, qual;
    struct IntuiMessage* MsgPtr;
    struct Gadget*       WhichGadgetPtr;
    struct MenuItem*     ItemPtr;

    effect(FXLAUNCH);
    Forbid();
    MainWindowPtr->Flags &= ~WFLG_RMBTRAP;
    Permit();
    clearscreen();

    SetMenuStrip(MainWindowPtr, MenuPtr);
    for (player = 0; player <= 3; player++)
        GT_SetGadgetAttrs(CycleGadgetPtr[player], MainWindowPtr, NULL, GA_Disabled, FALSE, TAG_DONE);
    GT_SetGadgetAttrs(RandomGadgetPtr, MainWindowPtr, NULL, GA_Disabled, FALSE, TAG_DONE);
    GT_SetGadgetAttrs(CheckboxGadgetPtr, MainWindowPtr, NULL, GA_Disabled, FALSE, TAG_DONE);

    clearkybd();
    clearjoystick();
    hiscores();
    hiscorenames();

        SetDrMd(MainWindowPtr->RPort, JAM1);
        SetAPen(MainWindowPtr->RPort, BLACK);
        Move(MainWindowPtr->RPort, STARTXPIXEL + 9, TSOFFSET +  86);
        Text(MainWindowPtr->RPort, "Spacebar: Field Editor ", 23);
        Move(MainWindowPtr->RPort, STARTXPIXEL + 9, TSOFFSET +  98);
        Text(MainWindowPtr->RPort, "       F: Sound Effects", 23);
        Move(MainWindowPtr->RPort, STARTXPIXEL + 9, TSOFFSET + 110);
        Text(MainWindowPtr->RPort, "       M: Music        ", 23);
        SetAPen(MainWindowPtr->RPort, WHITE);
        Move(MainWindowPtr->RPort, STARTXPIXEL + 8, TSOFFSET +  85);
        Text(MainWindowPtr->RPort, "Spacebar: Field Editor ", 23);
        Move(MainWindowPtr->RPort, STARTXPIXEL + 8, TSOFFSET +  97);
        Text(MainWindowPtr->RPort, "       F: Sound Effects", 23);
        Move(MainWindowPtr->RPort, STARTXPIXEL + 8, TSOFFSET + 109);
        Text(MainWindowPtr->RPort, "       M: Music        ", 23);
        SetDrMd(MainWindowPtr->RPort, JAM2);

	DrawImage(MainWindowPtr->RPort, &Logo, 203, 22);

	do
	{	TimerRqPtr->tr_node.io_Command	= TR_ADDREQUEST;
                TimerRqPtr->tr_time.tv_secs     = 0;
		TimerRqPtr->tr_time.tv_micro	= ANIMDELAY;
		SendIO((struct IORequest *) TimerRqPtr);

		if (descx == FIRSTDESCX)
		{	if (++object > LASTOBJECT)
				object = 0;
			say(objectdesc[object], WHITE);
			Image.ImageData = ImageData[object];
			DrawImage(MainWindowPtr->RPort, &Image, SECONDDESCX, DESCY);
		}
		SetAPen(MainWindowPtr->RPort, BLACK);
		Move(MainWindowPtr->RPort, descx - 1, DESCY - 1);
		Draw(MainWindowPtr->RPort, descx - 1, DESCY + 1 + SQUAREY);
		Move(MainWindowPtr->RPort, descx + 1 + SQUAREX, DESCY - 1);
		Draw(MainWindowPtr->RPort, descx + 1 + SQUAREX, DESCY + 1 + SQUAREY);
		Image.ImageData = ImageData[object];
		DrawImage(MainWindowPtr->RPort, &Image, descx, DESCY);
		if (++descx > SECONDDESCX)
			descx = FIRSTDESCX;

		while (MsgPtr = (struct IntuiMessage *) GT_GetIMsg(MainWindowPtr->UserPort))
                {   WhichGadgetPtr = (struct Gadget *) MsgPtr->IAddress;
                    class = MsgPtr->Class;
                    code  = MsgPtr->Code;
                    qual  = MsgPtr->Qualifier;
                    GT_ReplyIMsg(MsgPtr);
                    switch (class)
                    {
                    case IDCMP_RAWKEY:
                        if (!(qual & IEQUALIFIER_REPEAT))
                            switch (code)
                            {
                            case F:
                                toggle(F);
                            break;
                            case M:
						toggle(M);
						break;
					case SPACEBAR:
						effect(FXCLICK);
						a = FIELDEDIT;
						break;
					case F1:
					case ALPHAONE:
					case NUMERICONE:
						effect(FXCLICK);
						if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
						{	if (--worm[0].control < 0)
                                                                worm[0].control = 3;
						} else
                                                {       if (++worm[0].control > 3)
								worm[0].control = 0;
						}
						GT_SetGadgetAttrs(CycleGadgetPtr[0], MainWindowPtr, NULL, GTCY_Active, worm[0].control, TAG_DONE);
						break;
					case F2:
					case ALPHATWO:
					case NUMERICTWO:
						effect(FXCLICK);
						if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
						{	if (--worm[1].control < 0)
                                                                worm[1].control = 3;
						} else
                                                {       if (++worm[1].control > 3)
								worm[1].control = 0;
						}
						GT_SetGadgetAttrs(CycleGadgetPtr[1], MainWindowPtr, NULL, GTCY_Active, worm[1].control, TAG_DONE);
					break;
					case F3:
					case ALPHATHREE:
					case NUMERICTHREE:
						effect(FXCLICK);
						if (joy)
						{	if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
							{	if (--worm[2].control < 0)
									worm[2].control = 2;
							} else
							{	if (++worm[2].control > 2)
									worm[2].control = 0;
						}	}
						else if (worm[2].control == NONE)
							worm[2].control = AMIGA;
						else
							worm[2].control = NONE;
						GT_SetGadgetAttrs(CycleGadgetPtr[2], MainWindowPtr, NULL, GTCY_Active, worm[2].control, TAG_DONE);
					break;
					case F4:
					case ALPHAFOUR:
					case NUMERICFOUR:
						effect(FXCLICK);
						if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
						{	if (--worm[3].control < 0)
								worm[3].control = 2;
						} else
						{	if (++worm[3].control > 2)
								worm[3].control = 0;
						}
						GT_SetGadgetAttrs(CycleGadgetPtr[3], MainWindowPtr, NULL, GTCY_Active, worm[3].control, TAG_DONE);
					break;
					case RETURN:
					case ENTER:
						a = PLAYGAME;
						break;
					case I:
						effect(FXCLICK);
						iso = !iso;
						GT_SetGadgetAttrs(CheckboxGadgetPtr, MainWindowPtr, NULL, GTCB_Checked, iso, TAG_DONE);
						break;
                                        case S:
                                            effect(FXCLICK);
                                            randomflag = !randomflag;
                                            GT_SetGadgetAttrs(RandomGadgetPtr, MainWindowPtr, NULL, GTCB_Checked, randomflag, TAG_DONE);
					break;
					case ESCAPE:
						if (verify())
							cleanexit(EXIT_SUCCESS);
						break;
					case HELP:
						helpabout();
						break;
					case INTERNATIONALONE:
						if (lisa == 2)
						{	say(FIRSTLISA, RED);
							anykey(FALSE);
							lisa = 0;
						} else
							lisa = 1;
						break;
					case INTERNATIONALTWO:
						if (lisa == 1)
						{	say(SECONDLISA, BLUE);
							anykey(FALSE);
							lisa = 0;
						} else
							lisa = 2;
						break;
					default:
						break;
					}
				break;
			case IDCMP_MENUPICK:
				while (code != MENUNULL)
				{	ItemPtr = ItemAddress(MenuPtr, code);
					switch (MENUNUM(code))
					{
					case MN_PROJECT:
						switch (ITEMNUM(code))
						{
						case IN_NEW:
							effect(FXFILENEW);
							newfields();
							say("New done.", WHITE);
							anykey(TRUE);
							say(objectdesc[object], WHITE);
						break;
                            case IN_OPEN:
                                effect(FXFILEOPEN);
                                fileopen(FALSE);
                                say(objectdesc[object], WHITE);
                            break;
                            case IN_REVERT:
                                fileopen(TRUE);
                                say(objectdesc[object], WHITE);
                            break;
                            case IN_SAVE:
                                effect(FXFILESAVE);
                                filesaveas(FALSE);
                                say(objectdesc[object], WHITE);
                            break;
                            case IN_SAVEAS:
                                effect(FXFILESAVEAS);
                                filesaveas(TRUE);
                                say(objectdesc[object], WHITE);
                            break;
                            case IN_PROJECTDELETE:
                                effect(FXEDITDELETE);
                                filedelete();
                            break;
                            case IN_QUIT:
                                if (verify())
                                    cleanexit(EXIT_SUCCESS);
                            break;
                            default:
                            break;
                            }
                        break;
                        case MN_SETTINGS:
                        switch(ITEMNUM(code))
                        {
                        case IN_CREATEICONS:
                            if (ItemPtr->Flags & CHECKED)
                                icons = TRUE;
                            else icons = FALSE;
                        break;
                        default:
                        break;
                        }
                    break;
                    case MN_HELP:
                        switch(ITEMNUM(code))
                        {
                        case IN_MANUAL:
                            helpmanual();
                        break;
                        case IN_ABOUT:
			    helpabout();
			break;
                        case IN_CREATURES:
                            help(ORB);
                        break;
                        case IN_OBJECTS:
                            help(AFFIXER);
                        break;
                        }
		    break;
                    default:
			break;
		    }
		    code = ItemPtr->NextSelect;
		}
		break;
			case IDCMP_MOUSEBUTTONS:
				if (code == SELECTDOWN)
					if (ignore)
						ignore = FALSE;
					else a = PLAYGAME;
				break;
			case IDCMP_REFRESHWINDOW:
				GT_BeginRefresh(MainWindowPtr);
				GT_EndRefresh(MainWindowPtr, TRUE);
				break;
			case IDCMP_GADGETUP:
				if (WhichGadgetPtr == CheckboxGadgetPtr)
				    iso = !iso;
                                elif (WhichGadgetPtr == RandomGadgetPtr)
                                    randomflag = !randomflag;
				else
				{	for (player = 0; player <= 3; player++)
						if (WhichGadgetPtr == CycleGadgetPtr[player])
						{	if (code == JOYSTICK && player == 2 && (!joy))
							{	if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
									worm[2].control = NONE;
								else
									worm[2].control = AMIGA;
								GT_SetGadgetAttrs(CycleGadgetPtr[2], MainWindowPtr, NULL, GTCY_Active, worm[2].control, TAG_DONE);
							} else
								worm[player].control = code;
							break;
						}
				}
				break;
			case IDCMP_ACTIVEWINDOW:
				ignore = TRUE;
				break;
			case IDCMP_CLOSEWINDOW:
				cleanexit(EXIT_SUCCESS);
				break;
			default:
                /* IDCMP_MENUVERIFY, IDCMP_INTUITICKS */
                break;
		}	}
		if (joy && GetMsg(JoyPortPtr))
		{	if (GameEvent.ie_Code == IECODE_LBUTTON)
				a = PLAYGAME;
			sendreadrequest();
		}
		if (a == PLAYGAME)
		{	if (worm[0].control == NONE && worm[1].control == NONE && worm[2].control == NONE && worm[3].control == NONE)
			{	say("No worms active!", WHITE);
				anykey(TRUE);
				a = GAMEOVER;
		}	}
                // CheckIO() returns NULL if complete, pointer if incomplete.
                if (CheckIO((struct IORequest *) TimerRqPtr))
			draw(CLOCKICON, ICONY, CLOCK);
		else draw(CLOCKICON, ICONY, BLACKENED);
		WaitIO((struct IORequest *) TimerRqPtr);
	} while (a == GAMEOVER);

    for (player = 0; player <= 3; player++)
        GT_SetGadgetAttrs(CycleGadgetPtr[player], MainWindowPtr, NULL, GA_Disabled, TRUE, TAG_DONE);
    GT_SetGadgetAttrs(CheckboxGadgetPtr, MainWindowPtr, NULL, GA_Disabled, TRUE, TAG_DONE);
    GT_SetGadgetAttrs(RandomGadgetPtr, MainWindowPtr, NULL, GA_Disabled, TRUE, TAG_DONE);
    Image.ImageData = ImageData[BLACKENED];
    DrawImage(MainWindowPtr->RPort, &Image, FIRSTDESCX, DESCY);
    DrawImage(MainWindowPtr->RPort, &Image, SECONDDESCX, DESCY);

    if (a == FIELDEDIT)
        fieldedit();
    else
    {   newgame();
        clearkybd();
        Forbid();
        MainWindowPtr->Flags |= WFLG_RMBTRAP;
        Permit();
}   }

AGLOBAL void toggle(SBYTE key)
{   PERSIST ABOOL songstarted = FALSE;

    switch(key)
    {
    case F:
        if (mode == FX) /* F in FX mode: no sound */
        {   freefx();
            mode = FALSE;
            draw(MUSICICON, ICONY, BLACKENED);
        } else if (fxable != FAILED) /* F otherwise: change to FX mode */
        {   if (mode == MUSIC) /* stop any music that is playing */
            {   StopPlayer();
                FreePlayer();
            }
            if (fxable == DEFER) /* load samples if needed */
                loadthefx();
            if (fxable == SUCCEEDED) /* if we have samples in memory */
            {   if (beginfx())
                {   mode = FX;
                    effect(FXLAUNCH);
                    draw(MUSICICON, ICONY, FX);
        }   }   }
    break;
    case M:
        if (mode == MUSIC) /* M in MUSIC mode: no sound */
        {   StopPlayer();
            FreePlayer();
            mode = FALSE;
            draw(MUSICICON, ICONY, BLACKENED);
        } else if (musicable != FAILED) /* M otherwise: change to music mode */
        {   if (mode == FX) /* stop any samples that are playing */
                freefx();
            /* Of course, these statements are ordered in this
            way for a reason, so don't change it. :-) */
            if (musicable == DEFER)
                loadthemusic();
            if (musicable == SUCCEEDED)
            {   if (GetPlayer(0))
                {   say("No channels for music!", RED);
                    anykey(TRUE);
                    mode = FALSE;
                    draw(MUSICICON, ICONY, BLACKENED);
                } else
                {   if (songstarted)
                        ContModule(SongPtr);
                    else
                    {   PlayModule(SongPtr);
                        songstarted = TRUE;
                    }
                    mode = MUSIC;
                    draw(MUSICICON, ICONY, MUSIC);
        }   }   }
    break;
    default:
        flash(2);
        // assert(0);
    break;
}   }

ABOOL verify(void)
{	pausetimer();
	if (modified && (EasyRequest(MainWindowPtr, &EasyStruct, NULL) == 0))
		return FALSE;
	else return TRUE;
	unpausetimer();
}

void waitasec(void)
{   Delay(50);
}

void systemsetup(void)
{	worm[0].control	= NONE;
	worm[1].control	= KEYBOARD;
	worm[2].control	= NONE;
	worm[3].control = AMIGA;
}

ABOOL ZOpen(STRPTR fieldname, ABOOL write)
{	if (!write)
		if (FilePtr = Open(fieldname, MODE_OLDFILE))
			return TRUE;
		else return FALSE;
	else
		if (FilePtr = Open(fieldname, MODE_NEWFILE))
			return TRUE;
		else return FALSE;
}
ABOOL ZRead(STRPTR IOBuffer, ULONG length)
{	if (Read(FilePtr, IOBuffer, length) == length)
		return TRUE;
	else return FALSE;
}
ABOOL ZWrite(STRPTR IOBuffer, ULONG length)
{	if (Write(FilePtr, IOBuffer, length) == length)
		return TRUE;
	else return FALSE;
}
ABOOL ZClose(void)
{	if (Close(FilePtr))
	{	FilePtr = NULL;
		return TRUE;
	} else
	{	/* "If Close() returns DOSFALSE, the user has already cancelled an
		error requester and the function has already freed the FileHandle
		(and even marked it so any attempt to close it again will bring up
		the "Software Failure" requester). Therefore FilePtr should be set
		to zero in any case." - Jilles Tjoelker. */

		FilePtr = NULL;
		return FALSE;
}	}

void timing(void)
{	;
}

MODULE void loadthefx(void)
{   UBYTE*        p8data;
    TEXT          saystring[SAYLIMIT + 1];
    SBYTE         code = 0, i,
                  iobuffer[8]; /* buffer for 8SVX.VHDR  */
    SBYTE*        psample[2];  /* sample pointers */
    struct Chunk* p8Chunk;     /* pointers for 8SVX parsing */
    Voice8Header* pVoice8Header = NULL; // only set to NULL to avoid spurious warnings
    ULONG         rd8count;

    say("Loading sound effects...", WHITE);
    fxable = SUCCEEDED;

    for (i = 0; i <= SAMPLES; i++)
        samp[i].base = NULL;

    for (i = 0; i <= SAMPLES; i++)
    {   if (!(FilePtr = Open(samp[i].filename, MODE_OLDFILE)))
            code = 1;                               /* can't open file */
        else
        {   rd8count = Read(FilePtr, iobuffer, 8L);
            if (rd8count == -1)
                code = 2;                           /* can't read file */
            elif (rd8count < 8)
                code = 3;                           /* not an IFF 8SVX; too short */
            else
            {   p8Chunk = (struct Chunk *) iobuffer;
                if (p8Chunk->ckID != ID_FORM)
                    code = 4;                       /* not an IFF FORM */
                elif (!(fbase = (UBYTE *) AllocMem(fsize = p8Chunk->ckSize, MEMF_PUBLIC | MEMF_CLEAR)))
                    code = 5;                       /* no memory for read */
                else
                {   p8data = fbase;
                    rd8count = Read(FilePtr, p8data, p8Chunk->ckSize);
                    if (rd8count == -1)
                        code = 6;                   /* read error */
                    elif (rd8count < p8Chunk->ckSize)
                        code = 7;                   /* malformed IFF; too short */
                    elif (MAKE_ID(*p8data, *(p8data + 1), *(p8data + 2), *(p8data + 3)) != ID_8SVX)
                        code = 8;                   /* not an IFF 8SVX */
                    else
                    {   p8data = p8data + 4;
                        while (p8data < fbase + fsize)
                        {   p8Chunk = (struct Chunk *) p8data;
                            switch(p8Chunk->ckID)
                            {
                            case ID_VHDR:
                                pVoice8Header     = (Voice8Header *) (p8data + 8L);
                            break;
                            case ID_BODY:
                                psample[0]        = (SBYTE *) (p8data + 8L);
                                psample[1]        = psample[0] + pVoice8Header->oneShotHiSamples;
                                samp[i].length[0] = (ULONG) pVoice8Header->oneShotHiSamples;
                                samp[i].length[1] = (ULONG) pVoice8Header->repeatHiSamples;

                                /* To grab the volume level from the IFF
                                8SVX file itself, add this line here:

                                samp[i].volume    = (SBYTE) (pVoice8Header->volume / 156); */
                            break;
                            default:
                            break;
                            }
                            p8data += 8L + p8Chunk->ckSize;
                            if (p8Chunk->ckSize & 1L == 1)
                                p8data++;
                        }
                        if (samp[i].length[0] == 0)
                            samp[i].bank = 1;
                        else samp[i].bank = 0;
                        if (samp[i].length[samp[i].bank] <= 102400)
                            samp[i].size = samp[i].length[samp[i].bank];
                        else samp[i].size = 102400;
                        samp[i].base = (UBYTE *) AllocMem(samp[i].size, MEMF_CHIP | MEMF_CLEAR);
                        if (!samp[i].base)
                            code = 9; /* no chip memory */
                        else
                        {   CopyMem(psample[samp[i].bank], samp[i].base, samp[i].size);
                            psample[samp[i].bank] += samp[i].size;
                            samp[i].speed = PALCLOCK / pVoice8Header->samplesPerSec;
                            /* perhaps we should have a different
                            value for DblPAL screens? */
                            if (fbase)
                            {   FreeMem(fbase, fsize);
                                fbase = NULL;
                            }
                            if (FilePtr)
                            {   Close(FilePtr);
                                FilePtr = NULL;
        }   }   }   }   }   }
        if (code)
        {   freefx();
            fxable = FAILED;
            strcpy(saystring, samp[i].filename);
            strcat(saystring, ": ");
            strcat(saystring, sfxerror[code]);
            say(saystring, RED);
            anykey(TRUE);
            break;
}   }   }

MODULE void loadthemusic(void)
{	if (!(MEDPlayerBase = (struct MEDPlayerBase *) OpenLibrary("medplayer.library", 0L)))
	{	say("Can't open MEDPlayer.library!", RED);
		anykey(TRUE);
	} else
	{	say("Loading music...", WHITE);
		if (SongPtr = (struct MMD0 *) LoadModule("PROGDIR:WormWars.MED"))
                        musicable = SUCCEEDED;
		else
		{	say("Can't load music!", RED);
			anykey(TRUE);
}	}	}

void clearscreen(void)
{   SBYTE i;

    Background.PlaneOnOff = 8 + ((rand() % 4) * 2);
    // random background colour
    DrawImage
    (   MainWindowPtr->RPort,
        &Background,
        0,
        0
    );
    if (mode == MUSIC)
        draw(MUSICICON, ICONY, MUSIC);
    else if (mode == FX)
        draw(MUSICICON, ICONY, FX);
    else draw(MUSICICON, ICONY, BLACKENED);
    draw(CLOCKICON, ICONY, CLOCK);

    if (a != FIELDEDIT)
    {   if (!iso)
	{   for (i = 0; i <= 3; i++)
	    {   if (worm[i].control != NONE)
                {   Image.ImageData = ImageData[BONUS];
                    DrawImage
                    (   MainWindowPtr->RPort,
                        &Image,
                        (worm[i].statx * ENDXPIXEL) + FONTX + 4,
			STARTYPIXEL + 1               + (worm[i].staty * (ENDYPIXEL - 1 - (11 * FONTY)))
                    );
                    Image.ImageData = ImageData[LIFE];
                    DrawImage
                    (   MainWindowPtr->RPort,
                        &Image,
                        (worm[i].statx * ENDXPIXEL) + FONTX + 4,
                        STARTYPIXEL + 1 + (1 * FONTY) + (worm[i].staty * (ENDYPIXEL - 1 - (11 * FONTY)))
                    );
                    Image.ImageData = ImageData[BIAS];
                    DrawImage
                    (   MainWindowPtr->RPort,
                        &Image,
                        (worm[i].statx * ENDXPIXEL) + FONTX + 4,
                        STARTYPIXEL + 1 + (2 * FONTY) + (worm[i].staty * (ENDYPIXEL - 1 - (11 * FONTY)))
                    );
                    Image.ImageData = ImageData[NITRO];
                    DrawImage
                    (   MainWindowPtr->RPort,
                        &Image,
                        (worm[i].statx * ENDXPIXEL) + FONTX + 4,
                        STARTYPIXEL + 1 + (3 * FONTY) + (worm[i].staty * (ENDYPIXEL - 1 - (11 * FONTY)))
                    );
                    Image.ImageData = ImageData[AMMO];
                    DrawImage
                    (   MainWindowPtr->RPort,
                        &Image,
                        (worm[i].statx * ENDXPIXEL) + FONTX + 4,
                        STARTYPIXEL + 1 + (5 * FONTY) + (worm[i].staty * (ENDYPIXEL - 1 - (11 * FONTY)))
                    );
                    Image.ImageData = ImageData[POWER];
                    DrawImage
                    (   MainWindowPtr->RPort,
                        &Image,
                        (worm[i].statx * ENDXPIXEL) + FONTX + 4,
                        STARTYPIXEL + 1 + (6 * FONTY) + (worm[i].staty * (ENDYPIXEL - 1 - (11 * FONTY)))
                    );
                    Image.ImageData = ImageData[ARMOUR];
                    DrawImage
                    (   MainWindowPtr->RPort,
                        &Image,
                        (worm[i].statx * ENDXPIXEL) + FONTX + 4,
                        STARTYPIXEL + 1 + (8 * FONTY) + (worm[i].staty * (ENDYPIXEL - 1 - (11 * FONTY)))
                    );
                    Image.ImageData = ImageData[TONGUE];
                    DrawImage
                    (   MainWindowPtr->RPort,
                        &Image,
                        (worm[i].statx * ENDXPIXEL) + FONTX + 4,
                        STARTYPIXEL + 1 + (9 * FONTY) + (worm[i].staty * (ENDYPIXEL - 1 - (11 * FONTY)))
                    );
        }   }   }
        else
        {   // assert(iso);
            if (worm[0].control != NONE || worm[2].control != NONE)
            {   Image.ImageData = ImageData[BONUS];
                DrawImage(MainWindowPtr->RPort, &Image, 54, 4);
                Image.ImageData = ImageData[LIFE];
                DrawImage(MainWindowPtr->RPort, &Image, 54, 12);
                Image.ImageData = ImageData[BIAS];
                DrawImage(MainWindowPtr->RPort, &Image, 54, 20);
                Image.ImageData = ImageData[NITRO];
                DrawImage(MainWindowPtr->RPort, &Image, 54, 28);
                Image.ImageData = ImageData[AMMO];
                DrawImage(MainWindowPtr->RPort, &Image, 54, 36);
                Image.ImageData = ImageData[POWER];
                DrawImage(MainWindowPtr->RPort, &Image, 54, 44);
                Image.ImageData = ImageData[ARMOUR];
                DrawImage(MainWindowPtr->RPort, &Image, 54, 52);
                Image.ImageData = ImageData[TONGUE];
                DrawImage(MainWindowPtr->RPort, &Image, 54, 60);
            }
            if (worm[1].control != NONE || worm[3].control != NONE)
            {   Image.ImageData = ImageData[BONUS];
                DrawImage(MainWindowPtr->RPort, &Image, 579, 183);
                Image.ImageData = ImageData[LIFE];
                DrawImage(MainWindowPtr->RPort, &Image, 579, 191);
                Image.ImageData = ImageData[BIAS];
                DrawImage(MainWindowPtr->RPort, &Image, 579, 199);
                Image.ImageData = ImageData[NITRO];
                DrawImage(MainWindowPtr->RPort, &Image, 579, 207);
                Image.ImageData = ImageData[AMMO];
                DrawImage(MainWindowPtr->RPort, &Image, 579, 215);
                Image.ImageData = ImageData[POWER];
                DrawImage(MainWindowPtr->RPort, &Image, 579, 223);
                Image.ImageData = ImageData[ARMOUR];
                DrawImage(MainWindowPtr->RPort, &Image, 579, 231);
                Image.ImageData = ImageData[TONGUE];
                DrawImage(MainWindowPtr->RPort, &Image, 579, 239);
        }   }
        for (i = 0; i <= 3; i++)
        {   icon(i, ICE);
            icon(i, REMNANTS);
            icon(i, AFFIXER);
            icon(i, SIDESHOT);
            icon(i, PUSHER);
            icon(i, FREEDOM);
            icon(i, CUTTER);
}   }   }

void datestamp(void)
{   ULONG            seconds, micros;
    struct ClockData Date;
    TEXT             temp[5];

	CurrentTime(&seconds, &micros);
	Amiga2Date(seconds, &Date);
	stci_d(times, Date.hour);			/* hh */
	align(times, 2, ' ');
	times[2] = ':';                     /* hh: */
	times[3] = 0;
	stci_d(temp, Date.min);
	align(temp, 2, '0');
	temp[2] = 0;
	strcat(times, temp);				/* hh:mm */

	stci_d(date, Date.mday);			/* dd */
	align(date, 2, ' ');
	date[2] = '/';
	date[3] = 0;						/* dd/ */
	stci_d(temp, Date.month);
	align(temp, 2, ' ');
	temp[2] = 0;
	strcat(date, temp);					/* dd/mm */
	strcat(date, "/");					/* dd/mm/ */
	stci_d(temp, Date.year);
	temp[0] = temp[2];
	temp[1] = temp[3];
	temp[2] = 0;
	strcat(date, temp);					/* dd/mm/yy */
}

void turborender(void)
{   UBYTE random = rand() % 3;
    SBYTE x, y;

    if (a != PLAYGAME || (!level) || !(randomflag))
        sourcelevel = level;

    switch(random)
    {
    case 0:
        for (x = 0; x <= (FIELDX / 2) + 1; x++)
            for (y = 0; y <= FIELDY / 2; y++)
            {   draw(x, y, board[sourcelevel][x][y]);
                draw(FIELDX - x, y, board[sourcelevel][FIELDX - x][y]);
                draw(x, FIELDY - y, board[sourcelevel][x][FIELDY - y]);
                draw(FIELDX - x, FIELDY - y, board[sourcelevel][FIELDX - x][FIELDY - y]);
            }
    break;
    case 1:
        for (y = 0; y <= FIELDY / 2; y++)
            for (x = 0; x <= (FIELDX / 2) + 1; x++)
            {   draw(x, y, board[sourcelevel][x][y]);
                draw(FIELDX - x, y, board[sourcelevel][FIELDX - x][y]);
                draw(x, FIELDY - y, board[sourcelevel][x][FIELDY - y]);
                draw(FIELDX - x, FIELDY - y, board[sourcelevel][FIELDX - x][FIELDY - y]);
            }
    break;
    case 2:
        x = y = 0;
        do
        {   draw(x, y, board[sourcelevel][x][y]);
            x += APPEAR_CONSTANT;
            if (x >= FWIDTH)
            {   x -= FWIDTH;
                y++;
                if (y >= FHEIGHT)
                    y = 0;
        }   }
        while (x || y);
    break;
    default:
        assert(0);
    break;
    }

    if (a == FIELDEDIT)
    {   draw(startx[sourcelevel], starty[sourcelevel], START);
    } else
    {   for (y = 0; y <= FIELDY; y++)
            draw(ARROWX, y, BLACKENED);
}   }

MODULE void parsewb(void)
{   struct DiskObject* DiskObject;
    char**             ToolArray;
    char*              s;

    if ((*WBArg->wa_Name) && (DiskObject = GetDiskObject(WBArg->wa_Name)))
    {   ToolArray = (char **) DiskObject->do_ToolTypes;

        if (s = (char *) FindToolType(ToolArray, "NOFX"))
            fxable = DEFER;
        if (s = (char *) FindToolType(ToolArray, "NOMUSIC"))
            musicable = DEFER;
        if (s = (char *) FindToolType(ToolArray, "NOICONS"))
            icons = FALSE;
        if (s = (char *) FindToolType(ToolArray, "OVERHEAD"))
            iso = FALSE;
        if (s = (char *) FindToolType(ToolArray, "SHUFFLE"))
            randomflag = TRUE;
        if (s = (STRPTR) FindToolType(ToolArray, "QUIET"))
        {   quiet = TRUE;
        }
        if (s = (char *) FindToolType(ToolArray, "FILE"))
            strcpy(pathname, WBArg->wa_Name);
        if (s = (char *) FindToolType(ToolArray, "GREEN"))
        {   if (MatchToolValue(s, "KYBD"))
                worm[0].control = KEYBOARD;
            elif (MatchToolValue(s, "JOY"))
                worm[0].control = JOYSTICK;
            elif (MatchToolValue(s, "AMIGA"))
            {   worm[0].control = AMIGA;
            } elif (MatchToolValue(s, "NONE"))
            {   worm[0].control = NONE;
        }   }
        if (s = (char *) FindToolType(ToolArray, "RED"))
        {   if (MatchToolValue(s, "KYBD"))
            {   worm[1].control = KEYBOARD;
            } elif (MatchToolValue(s, "JOY"))
            {   worm[1].control = JOYSTICK;
            } elif (MatchToolValue(s, "AMIGA"))
            {   worm[1].control = AMIGA;
            } elif (MatchToolValue(s, "NONE"))
            {   worm[1].control = NONE;
        }   }
        if (s = (char *) FindToolType(ToolArray, "BLUE"))
        {   if (MatchToolValue(s, "JOY"))
                worm[2].control = JOYSTICK;
            elif (MatchToolValue(s, "AMIGA"))
                worm[2].control = AMIGA;
            elif (MatchToolValue(s, "NONE"))
                worm[2].control = NONE;
        }
        if (s = (char *) FindToolType(ToolArray, "YELLOW"))
        {   if (MatchToolValue(s, "JOY"))
                worm[2].control = JOYSTICK;
            elif (MatchToolValue(s, "AMIGA"))
                worm[3].control = AMIGA;
            elif (MatchToolValue(s, "NONE"))
                worm[3].control = NONE;
        }
        FreeDiskObject(DiskObject);
}   }

MODULE void pausetimer(void)
{	GetSysTime(CurrentValPtr);
}
MODULE void unpausetimer(void)
{	GetSysTime(PausedValPtr);
	SubTime(PausedValPtr, CurrentValPtr);
	AddTime(StartValPtr, PausedValPtr);
}

MODULE UBYTE ReadJoystick(UWORD joynum)
{   extern struct Custom far custom;
    UBYTE ret = 0;
    UWORD joy;

    if (joynum == 0)
        joy = custom.joy0dat;
    else joy = custom.joy1dat;

    ret += (joy >> 1 ^ joy) & 0x0100 ? JOYUP : 0;  
    ret += (joy >> 1 ^ joy) & 0x0001 ? JOYDOWN : 0;
    ret += joy & 0x0200 ? JOYLEFT : 0;
    ret += joy & 0x0002 ? JOYRIGHT : 0;

    if (joynum == 0)
    {   ret += !(CIAPtr->ciapra & 0x0040) ? JOYFIRE1 : 0; /* read firebuttons */
        ret += !(POTGOR & 0x0400) ? JOYFIRE2 : 0;         /* on joyport 0 */
    } else
    {   ret += !(CIAPtr->ciapra & 0x0080) ? JOYFIRE1 : 0; /* read firebuttons */
        ret += !(POTGOR & 0x4000) ? JOYFIRE2 : 0;         /* on joyport 1 */
    }

    return(ret);
}

AGLOBAL void ReadAdapterJoystick(UBYTE unit)
{   AUTO    UBYTE firebit, addroffset, snapshot;
    AUTO    SBYTE xx = 0, yy = 0;
    AUTO    ABOOL fire;
    PERSIST UBYTE oldsnapshot[2] = {0, 0};
    PERSIST ABOOL oldfire[2]     = {0, 0};
    PERSIST UBYTE *fireaddrPtr,
                  *moveaddrPtr;

    fireaddrPtr = 0xBFD000;
    moveaddrPtr = 0xBFE101;

/*  // Those pointers could easily be auto or constant or whatever; they
    // never change.

    bit 7 of $bfe101 is right of joystick '4'
    bit 6 of $bfe101 is left  of joystick '4'
    bit 5 of $bfe101 is down  of joystick '4'
    bit 4 of $bfe101 is up    of joystick '4'
    bit 3 of $bfe101 is right of joystick '3'
    bit 2 of $bfe101 is left  of joystick '3'
    bit 1 of $bfe101 is down  of joystick '3'
    bit 0 of $bfe101 is up    of joystick '3'

    bit 2 of $bfd000 is fire  of joystick '3'
    bit 0 of $bfd000 is fire  of joystick '4'

    unit: number of the unit (2 = '3', 3 = '4') */

    if (unit == 2)
    {   firebit = 2;
        addroffset = 0;
    } else
    {   // assert(unit == 3);
        firebit = 0;
        addroffset = 4;
    }

    if (worm[unit].control == JOYSTICK && worm[unit].lives)
    {   if ((*fireaddrPtr) & (1 << firebit))
            fire = TRUE;

        snapshot = *moveaddrPtr; // we cache this address in the snapshot
                                 // variable, so that joystick reading is an
                                 // atomic operation
        if (snapshot & (1 << (addroffset + 3)))
            xx = 1;
        elif (snapshot & (1 << (addroffset + 2)))
            xx = -1;
        if (snapshot & (1 << (addroffset + 1)))
            yy = 1;
        elif (snapshot & (1 <<  addroffset     ))
            yy = -1;

        if (oldsnapshot[unit - 2] != snapshot || oldfire[unit - 2] != fire)
        {   if (fire)
                wormqueue(unit, 0, 0);
            else
                wormqueue(unit, xx, yy);
        }
        oldsnapshot[unit - 2] = snapshot;
        oldfire[unit - 2]     = fire;
}   }

void joy0(void)
{   AUTO    UBYTE joyval;
    AUTO    ABOOL fire = FALSE;
    AUTO    SBYTE xx = 0, yy = 0;
    PERSIST ABOOL oldjoyval;

    if (worm[3].control == JOYSTICK && worm[3].lives)
    {   joyval = ReadJoystick(0);
        if (joyval != oldjoyval)
        {   if (joyval & JOYUP)
                yy = -1;
            elif (joyval & JOYDOWN)
                yy = 1;
            if (joyval & JOYLEFT)
                xx = -1;
            elif (joyval & JOYRIGHT)
                xx = 1;
            if ((joyval & JOYFIRE1) || (joyval & JOYFIRE2))
                fire = TRUE;
            if (joyval != 0)              // if joystick is off-centre or firebutton is down
            {   if (fire)                 // if firebutton is down
                    wormqueue(3, 0, 0);   // then shoot/jump
                else                      // if firebutton is up
                    wormqueue(3, xx, yy); // then move
        }   }
        oldjoyval = joyval;
}   }

void flash(ULONG where)
{   TEXT saystring[SAYLIMIT + 1];

    stci_d(saystring, where);
    say(saystring, PURPLE);
    while(1);
}

