/* $Filename:       WormWars/Source/engine.c $
 * $VER:            WormWars 5.2 $
 * $Description:    Game engine $
 *
 * © Copyright 1999 James R. Jacobs. Freely distributable.
 */

#include <string.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>			// EXIT_SUCCESS, EXIT_FAILURE

#include "diff.h"
#include "same.h"
#include "engine.h"

// PRIVATE STRUCTURES -----------------------------------------------------

struct
{	SBYTE		x, y, oldx, oldy;
	ABOOL		alive, moved, teleported, visible, reflected;
} bullet[7];
struct
{	UWORD		frequency;
	SLONG		score;
} object[LASTOBJECT + 1] =
{	{1280,  60},	//	AFFIXER
	{  80,  20},	//	AMMO
	{ 110,  20},	//	ARMOUR
	{  70,  50},	//	BIAS
	{ 190,  30},	//	BOMB
	{  80,  10},	//	BONUS
	{1020,	60},	//	CLOCK
	{ 330,  50},	//	GROWER
	{1900,	90},	//	HEALER
	{1360,  60},	//	ICE
	{ 140,  60},	//	LIFE
	{ 130,	80},	//	LIGHTNING
	{ 240,  40},	//	MISSILE
	{ 640,  50},	//	MULTIPLIER
	{ 400,  10},	//	NITRO
	{ 240,  30},	//	POWER
	{ 480,  50},	//	PROTECTOR
	{ 210,	40},	//	PULSE
	{ 400,	40},	//	REMNANTS
	{ 600,  40},	//	SLAYER
	{ 980,  40},	//	SLOWER
	{ 730,  70},	//	SWITCHER
	{ 320,  20},	//	TONGUE
	{1200, 120},	//	TREASURE
	{3900, 140}	//	UMBRELLA
};

/*	-200		common
	220-400		uncommon
	420-980		rare
	1000+		very rare	*/

struct
{   SBYTE       x, y, deltax, deltay, relx, rely;
    ABOOL       alive, last, visible;
} protector[4][PROTECTORS + 1];
struct
{	SBYTE		x, y, time;
	ABOOL		alive;
} timebomb[TIMEBOMBS + 1];
struct
{	SBYTE		deltax;
	SBYTE		deltay;
} thequeue[4][QUEUELIMIT + 1];

SBYTE eachworm[4][2][9] =
{	{	{	GREENHEADUP,		GREENHEADUP,		GREENHEADUP,
			GREENHEADLEFT,		ANYTHING,			GREENHEADRIGHT,
			GREENHEADDOWN,		GREENHEADDOWN,		GREENHEADDOWN
		},
		{	GREENMODEUP,		GREENMODEUP,		GREENMODEUP,
			GREENMODELEFT,		ANYTHING,			GREENMODERIGHT,
			GREENMODEDOWN,		GREENMODEDOWN,		GREENMODEDOWN
	}	},
	{	{	REDHEADUP,		REDHEADUP,			REDHEADUP,
			REDHEADLEFT,		ANYTHING,			REDHEADRIGHT,
			REDHEADDOWN,		REDHEADDOWN,		REDHEADDOWN
		},
		{	REDMODEUP,		REDMODEUP,			REDMODEUP,
			REDMODELEFT,		ANYTHING,			REDMODERIGHT,
			REDMODEDOWN,		REDMODEDOWN,		REDMODEDOWN
	}	},
	{	{	BLUEHEADUP,		BLUEHEADUP,			BLUEHEADUP,
			BLUEHEADLEFT,		ANYTHING,			BLUEHEADRIGHT,
			BLUEHEADDOWN,		BLUEHEADDOWN,		BLUEHEADDOWN
		},
		{	BLUEMODEUP,		BLUEMODEUP,			BLUEMODEUP,
			BLUEMODELEFT,		ANYTHING,			BLUEMODERIGHT,
			BLUEMODEDOWN,		BLUEMODEDOWN,		BLUEMODEDOWN
	}	},
	{	{	YELLOWHEADUP,		YELLOWHEADUP,		YELLOWHEADUP,
			YELLOWHEADLEFT,		ANYTHING,			YELLOWHEADRIGHT,
			YELLOWHEADDOWN,		YELLOWHEADDOWN,		YELLOWHEADDOWN
		},
		{	YELLOWMODEUP,		YELLOWMODEUP,		YELLOWMODEUP,
			YELLOWMODELEFT,		ANYTHING,			YELLOWMODERIGHT,
			YELLOWMODEDOWN,		YELLOWMODEDOWN,		YELLOWMODEDOWN
}	}	};

struct
{   SBYTE speed;
    SWORD freq;
    UBYTE species;
    UBYTE type;         // owning worm, 0-3 (used for drips and missiles)
    SBYTE x, y;
	SBYTE deltax, deltay;
	UBYTE last, oldlast;
	ABOOL alive, explode, visible;
        UBYTE mode;
	SWORD armour, tongue;
        ULONG score;
        UBYTE multi;
} creature[CREATURES + 1];

// MODULE VARIABLES (used only within engine.c) ---------------------------

MODULE	ABOOL	letters[4][LETTERS + 1], trainer;
MODULE  SBYTE	ice, lettertype, letterx, lettery, leveltype,
				noletter, reallevel, treasurer;

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

AGLOBAL	ABOOL			clearthem	= FALSE,
						modified	= FALSE;
AGLOBAL	SBYTE			a = GAMEOVER,
						board[MAXLEVELS + 1][FIELDX + 1][FIELDY + 1],
						brush = STONE, players,
						field[FIELDX + 1][FIELDY + 1],
						level = 1, levels,
						startx[MAXLEVELS + 1], starty[MAXLEVELS + 1];
AGLOBAL	SWORD			secondsleft, secondsperlevel;
AGLOBAL	STRPTR			pathname = (STRPTR) DEFAULTSET;
AGLOBAL	ULONG			delay, r;
AGLOBAL	TEXT			date[DATELENGTH + 1], times[TIMELENGTH + 1];
AGLOBAL	struct HiScoreStruct	hiscore[HISCORES + 1];
AGLOBAL	struct TeleportStruct	teleport[MAXLEVELS + 1][4];
AGLOBAL	struct WormStruct		worm[4];

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

NAME        align -- right-justify a string within another string
SYNOPSIS    align(STRPTR, SBYTE, TEXT);
FUNCTION    Moves all text in a string to the right, padding with
            spaces. Does not itself add a null terminator.
INPUTS      string - pointer to the string of text
              size - size in characters of the containing string
            filler - what to pad the left of the string with
NOTE        Null terminators are written over by thissy function, but that
            does not matter, because calling functions use Text() with an
            explicit length. This function only works with monospaced
            fonts.
MODULE      engine.c */

void align(STRPTR string, SBYTE size, TEXT filler)
{   SBYTE i, shift, length;

	length = strlen((const char*) string);
	shift = size - length;
	for (i = 1; i <= length; i++)
		*(string + size - i) = *(string + size - i - shift);
	for (i = 0; i <= shift - 1; i++)
		*(string + i) = filler;
}

ABOOL blocked(SBYTE which, SBYTE deltax, SBYTE deltay)
{   SBYTE thissy;

    thissy = field[xwrap(teleport[level][partner(which)].x + deltax)][ywrap(teleport[level][partner(which)].y + deltay)];
    if ((thissy < STONE || thissy > GOAT) && thissy != METAL)
        return FALSE;
    else return TRUE;
}

void bombblast(SBYTE triggerer, SBYTE player, SBYTE centrex, SBYTE centrey)
{	SBYTE	counter, downy, downymax, leftx, leftxmax, rightx, rightxmax, strength, uppy, uppymax, x, y;
	SLONG	score = 0L;

	effect(FXBOMBBLAST);

	strength = BOMBADD + (rand() % BOMBRAND);

	leftxmax = centrex - strength;
	if (leftxmax < 0)
		leftxmax = 0;
	rightxmax = centrex + strength;
	if (rightxmax > FIELDX)
		rightxmax = FIELDX;
	uppymax = centrey - strength;
	if (uppymax < 0)
		uppymax = 0;
	downymax = centrey + strength;
	if (downymax > FIELDY)
		downymax = FIELDY;

	leftx = centrex;
	rightx = centrex;
	uppy = centrey;
	downy = centrey;
	for (counter = 1; counter <= strength; counter++)
	{	if (leftx > leftxmax)
		{	leftx--;
			for (y = uppy; y <= downy; y++)
				score += squareblast(triggerer, player, field[leftx][y], leftx, y);
		}
		if (rightx < rightxmax)
		{	rightx++;
			for (y = uppy; y <= downy; y++)
				score += squareblast(triggerer, player, field[rightx][y], rightx, y);
		}
		if (uppy > uppymax)
		{	uppy--;
			for (x = leftx; x <= rightx; x++)
				score += squareblast(triggerer, player, field[x][uppy], x, uppy);
		}
		if (downy < downymax)
		{	downy++;
			for (x = leftx; x <= rightx; x++)
				score += squareblast(triggerer, player, field[x][downy], x, downy);
	}	}

	if (triggerer == HEAD)
	{	wormscore(player, score);
        if (worm[player].bias)
            stat(player, LIFE);
	} else
		orbscore(player, score);
}

void bouncegoat(SBYTE which, SBYTE x, SBYTE y)
{   if (field[x][y] == GOAT)
    {   creature[whichcreature(x, y, GOAT, 255)].alive = FALSE;
        orbscore(which, KILLGOAT);
        change(x, y, BONUS);
}   }

ABOOL bounceorb(SBYTE which, SBYTE x, SBYTE y)
{	if (field[x][y] == METAL)
		return TRUE;
    elif (creature[which].mode == NONE)
	{	if (field[x][y] >= FIRSTNONE && field[x][y] <= LASTNONE)
			return TRUE;
		else return FALSE;
    } elif (creature[which].mode == TONGUE)
	{	if (field[x][y] >= FIRSTTONGUE && field[x][y] <= LASTTONGUE)
			return TRUE;
		else return FALSE;
    } else // assumes creature[which].mode == ARMOUR
	{	if (field[x][y] >= FIRSTARMOUR && field[x][y] <= LASTARMOUR)
			return TRUE;
		else return FALSE;
}	}

SBYTE bsign(SBYTE value)
{	if (value < 0)
		return (-1);
	elif (value > 0)
		return (1);
	else
		return (0);
}

void changefield(void)
{   SBYTE x, y;

	for (x = 0; x <= FIELDX; x++)
		for (y = 0; y <= FIELDY; y++)
			field[x][y] = board[level][x][y];
}

void clearhiscores(void)
{	SBYTE i;
	
	clearthem = FALSE;
	for (i = 0; i <= HISCORES; i++)
	{	hiscore[i].player = -1;
		hiscore[i].level = 0;
		hiscore[i].score = 0L;
		hiscore[i].fresh = FALSE;
		hiscore[i].name[0] = 0;
		hiscore[i].time[0] = 0;
		hiscore[i].date[0] = 0;
}   }

void clearletters(void)
{	SBYTE player, which;

	for (player = 0; player <= 3; player++)
		for (which = 0; which <= LETTERS; which++)
		{	letters[player][which] = FALSE;
			drawletter(player, FIRSTLETTER + which, BLACK);
}		}

void copyfield(SBYTE source, SBYTE destination)
{	SBYTE which, x, y;

	for (x = 0; x <= FIELDX; x++)
		for (y = 0; y <= FIELDY; y++)
			board[destination][x][y] = board[source][x][y];
	startx[destination] = startx[source];
	starty[destination] = starty[source];
	for (which = 0; which <= 1; which++)
	{	teleport[destination][which].alive = teleport[source][which].alive;
		teleport[destination][which].x     = teleport[source][which].x;
		teleport[destination][which].y     = teleport[source][which].y;
}	}

void death(void)
{	SBYTE	pain, player, which;
	ABOOL	slow;

    for (player = 0; player <= 3; player++)
	{	if (worm[player].lives)
	    {   if (!worm[player].alive)
			{	slow = FALSE;
				pain = 0;
				if (worm[player].cause >= FIRSTTAIL && worm[player].cause <= LASTTAIL)
				{	if (player == worm[player].cause - FIRSTTAIL)
						pain = TAILPAIN;
					else pain = OTHERTAILPAIN;
					slow = TRUE;
				} elif (worm[player].cause >= FIRSTFIRE && worm[player].cause <= LASTFIRE)
					pain = WORMFIREPAIN;
				elif (worm[player].cause >= FIRSTHEAD && worm[player].cause <= LASTHEAD)
					pain = HEADPAIN;
				elif (worm[player].cause >= FIRSTPROTECTOR && worm[player].cause <= LASTPROTECTOR)
					pain = PROTECTORPAIN;
				elif (worm[player].cause >= FIRSTMISSILE && worm[player].cause <= LASTMISSILE)
					pain = MISSILEPAIN;
				elif (worm[player].cause >= FIRSTDRIP && worm[player].cause <= LASTDRIP)
					pain = DRIPPAIN;
				else switch (worm[player].cause)
				{
				case BOMB:
					pain = BOMBPAIN;
				break;
				case WOOD:
					pain = WOODPAIN;
					slow = TRUE;
				break;
				case FRAGMENT:
					pain = FRAGMENTPAIN;
				break;
				case GOAT:
					pain = GOATPAIN;
					slow = TRUE;
				break;
				case SLAYER:
					pain = SLAYERPAIN;
				break;
				case STONE:
					pain = STONEPAIN;
					slow = TRUE;
				break;
				case TELEPORT:
					pain = TELEPORTPAIN;
					slow = TRUE;
				break;
				case SLIME:
					pain = SLIMEPAIN;
					slow = TRUE;
				break;
				case METAL:
					pain = METALPAIN;
					slow = TRUE;
				break;
				case REMNANTS:
					pain = REMNANTPAIN;
					slow = TRUE;
				break;
				case LIGHTNING:
					pain = LIGHTNINGPAIN;
				break;
				case PENGUIN:
					pain = PENGUINPAIN;
				break;
				default:
					break;
				}
				if (worm[player].victor >= 0 && worm[player].victor != player)
				{	wormscore(worm[player].victor, KILLWORM);
					if (worm[worm[player].victor].bias)
					{	worm[worm[player].victor].lives += pain;
						stat(worm[player].victor, LIFE);
				}	}
				if (slow)
				{	worm[player].speed = slowdown(worm[player].speed);
					stat(player, NITRO); 
				}
				if (pain > worm[player].lives)
					worm[player].lives = 0;
				else worm[player].lives -= pain;
				draw(worm[player].x, worm[player].y, SKULL);
				drawcause(player, NORMAL);
				stat(player, LIFE);
				if (level)
					worm[player].levelreached = level;
				else worm[player].levelreached = reallevel;
				if (worm[player].lives)
				{	effect(FXPAIN + player);
					worm[player].alive = TRUE;
					worm[player].causewait = r + CAUSEWAIT;
				} else
				{	// kill worm

					effect(FXWORMDEATH);
					if (ice == player)
						ice = -1;
					field[worm[player].x][worm[player].y] = SKULL;
					for (which = 0; which <= PROTECTORS; which++)
                        if (protector[player][which].alive && protector[player][which].visible)
                            change(protector[player][which].x, protector[player][which].y, EMPTY);
                    if (worm[player].score >= worm[player].hiscore)
                        worm[player].hiscore = worm[player].score;
	}	}	}	}
	if (!worm[0].lives && !worm[1].lives && !worm[2].lives && !worm[3].lives)
	{	// End of game
		newhiscores();
		effect(FXDEFEAT);
		a = GAMEOVER;
        if (players == 1)
            say((STRPTR) "Game over!", worm[onlyworm(FALSE)].colour);
		elif (worm[0].control && ((!worm[1].control) || worm[1].score < worm[0].score) && ((!worm[2].control) || worm[2].score < worm[0].score) && ((!worm[3].control) || worm[3].score < worm[0].score))
	    	say((STRPTR) "Green wins!", GREEN);
		elif (worm[1].control && ((!worm[0].control) || worm[0].score < worm[1].score) && ((!worm[2].control) || worm[2].score < worm[1].score) && ((!worm[3].control) || worm[3].score < worm[1].score))
	    	say((STRPTR) "Red wins!", RED);
		elif (worm[2].control && ((!worm[0].control) || worm[0].score < worm[2].score) && ((!worm[1].control) || worm[1].score < worm[2].score) && ((!worm[3].control) || worm[3].score < worm[2].score))
			say((STRPTR) "Blue wins!", BLUE);
		elif (worm[3].control && ((!worm[0].control) || worm[0].score < worm[3].score) && ((!worm[1].control) || worm[1].score < worm[3].score) && ((!worm[2].control) || worm[2].score < worm[3].score))
			say((STRPTR) "Yellow wins!", YELLOW);
		else say((STRPTR) "A draw!", WHITE);
		waitasec();
		anykey(FALSE);
}   }

void drawcause(SBYTE player, SBYTE state)
{	if (state == BLACK)
		draw(-2 + ((FIELDX + 4) * worm[player].statx), (FIELDY / 2) - CAUSEYDISTANCE + (worm[player].staty * CAUSEYDISTANCE * 2), BLACKENED);
	else draw(-2 + ((FIELDX + 4) * worm[player].statx), (FIELDY / 2) - CAUSEYDISTANCE + (worm[player].staty * CAUSEYDISTANCE * 2), worm[player].cause);
}

void drawletter(SBYTE player, SBYTE letter, SBYTE state)
{	SBYTE thissy;
	
	if (state == BLACK)
		thissy = BLACKENED;
	else thissy = letter;
	if (!worm[player].statx)
		if (!worm[player].staty)
			draw(-7 + ((letter - FIRSTLETTER) % 4),
				(FIELDY / 2) - 2 + ((letter - FIRSTLETTER) / 4),
				thissy);
		else
			draw(-7 + ((letter - FIRSTLETTER) % 4),
				(FIELDY / 2) + 1 + ((letter - FIRSTLETTER) / 4),
				thissy);
	else
		if (!worm[player].staty)
			draw(FIELDX + 4 + ((letter - FIRSTLETTER) % 4),
				(FIELDY / 2) - 2 + ((letter - FIRSTLETTER) / 4),
				thissy);
			else
				draw(FIELDX + 4 + ((letter - FIRSTLETTER) % 4),
				(FIELDY / 2) + 1 + ((letter - FIRSTLETTER) / 4),
				thissy);
}

/* NAME     enginesetup -- once-only initialization of engine variables
SYNOPSIS    enginesetup(void);
FUNCTION    Sets up the unchanging worm variables.
MODULE      engine.c */

void enginesetup(void)
{   worm[0].statx = worm[0].staty = worm[1].staty = worm[2].statx = 0;
	worm[1].statx = worm[2].staty = worm[3].statx = worm[3].staty = 1;
	worm[0].colour	= GREEN;
	worm[1].colour	= RED;
	worm[2].colour	= BLUE;
	worm[3].colour	= YELLOW;
	worm[0].name[0] = worm[1].name[0] = worm[2].name[0] = worm[3].name[0] = 0;

	systemsetup();
}

/* NAME     fastloop -- things done often
SYNOPSIS    fastloop(void);
FUNCTION    Checks for and handles level completion.
MODULE      engine.c */

void fastloop(void)
{	ABOOL	complete;
	SBYTE	advancer = -1, player, which;

	// flash letter

	if (level)
		if (r % 16 == 1)
			draw(letterx, lettery, WHITENED);
		elif (r % 16 == 2)
			draw(letterx, lettery, lettertype);

	// handle level completion

    for (player = 0; player <= 3; player++)
	{	complete = TRUE;
		for (which = 0; which <= LETTERS; which++)
			if (!letters[player][which])
				complete = FALSE;
		if (complete)
            advancer = player;
	}
    if (advancer != -1)
	{	if (level++ == 0)
		{	level = reallevel + 1;
			reallevel = 0;
		}
		stopfx(0L);
		if (level > levels)
			effect(FXVICTORY);
		newlevel(advancer);
}	}

void fillfield(SBYTE which)
{	SBYTE x, y;

	for (x = 0; x <= FIELDX; x++)
		for (y = 0; y <= FIELDY; y++)
		{	board[level][x][y] = which;
			draw(x, y, which);
		}
	board[level][startx[level]][starty[level]] = EMPTY;
	draw(startx[level], starty[level], START);
    if (teleport[level][0].alive)
	{	board[level][teleport[level][0].x][teleport[level][0].y] = TELEPORT;
		draw(teleport[level][0].x, teleport[level][0].y, ONE);
	}
    if (teleport[level][1].alive)
	{	board[level][teleport[level][1].x][teleport[level][1].y] = TELEPORT;
		draw(teleport[level][1].x, teleport[level][1].y, TWO);
}	}

ABOOL findempty(SBYTE* x, SBYTE* y, SBYTE first, SBYTE last)
{	SBYTE count = 0, xx, yy;

	do
	{	xx = rand() % (FIELDX + 1);
		yy = rand() % (FIELDY + 1);
	} while ((field[xx][yy] < first || field[xx][yy] > last) && ++count < PATIENCE);
	if (count < PATIENCE)
	{	*x = xx;
		*y = yy;
		return(TRUE);
	} else return(FALSE);
}

void gameloop(void)
{	SBYTE player, which;

    if (a == PLAYGAME)
    {   fastloop();
        gameinput();
    }
    if (a == PLAYGAME)
        for (player = 0; player <= 3; player++)
            if (worm[player].lives && (!(r % worm[player].speed)) && (ice == -1 || ice == player))
                wormloop(player);
	if (a == PLAYGAME)
        for (which = 0; which <= CREATURES; which++)
            if (creature[which].alive && (!(r % creature[which].speed)) && (ice == -1 || (creature[which].species == MISSILE && ice == creature[which].type)))
				creatureloop(which);
    if (a == PLAYGAME)
        death();
    if (a == PLAYGAME)
        if (ice == -1 && (!(r % VERYSLOW)))
			slowloop();
	timing();
}

void killall(void)
{	UBYTE i;

	for (i = 0; i <= CREATURES; i++)
		creature[i].alive = FALSE;
	for (i = 0; i <= TIMEBOMBS; i++)
		timebomb[i].alive = FALSE;
	teleport[level][2].alive = FALSE;
	teleport[level][3].alive = FALSE;
}

void levelappend(void)
{	UBYTE oldlevel;

	if (levels < MAXLEVELS)
	{	oldlevel = level;
		level = ++levels;
		newfield();
		level = oldlevel;
		saylevel(WHITE);
}	}

void leveldelete(void)
{	SBYTE i;

	// pull boards

	if (levels > 1)
	{	if (level < levels)
			for (i = level; i < levels; i++)
				copyfield(i + 1, i);
		else
			level--;
		levels--;
		saylevel(WHITE);
		turborender();
}	}

void levelerase(void)
{	newfield();
	turborender();
}

void levelinsert(void)
{	SBYTE i;

	// push boards

	if (levels < MAXLEVELS)
	{	for (i = levels; i >= level; i--)
			copyfield(i, i + 1);
		levels++;
		saylevel(WHITE);
		newfield();
		turborender();
}	}

SBYTE loadfields(STRPTR fieldname)
{	SBYTE				i, j, x, y;
	TEXT				IOBuffer[NAMELENGTH + 1];
	UBYTE				ver;

	/* This routine is not entirely robust, especially regarding
	failures part way through reading. Also, field data values must be
	those supported by the field editor (ie. objects, and the squares
	represented by F1-F8), or undefined behaviour may result. None of
	this is currently checked for. Provided that the fieldset was
	created with the field editor (4.4+), and the file is not corrupt,
	these failures should never happen anyway.

	open file */

	if (!ZOpen(fieldname, FALSE))
		return 1; // no harm done

	// read header

	if (!ZRead(IOBuffer, 10))
    {	ZClose();
        return 2; // no harm done
    }
	
	if (!strcmp(IOBuffer, "FSET 5.1"))
		ver = 51;
	elif (!strcmp(IOBuffer, "FSET 5.0"))
		ver = 50;
	elif (!strcmp(IOBuffer, "FSET 4.4"))
		ver = 44;
	else
	{	ZClose();
		return 3; // no harm done
	}
    levels							= IOBuffer[9];

	// read high score table

	for (i = 0; i <= HISCORES; i++)
	{	if (!ZRead(IOBuffer, 6))
		{	ZClose();
			return 4; // incorrect levels
		}
		hiscore[i].fresh			=  FALSE;
		hiscore[i].player			=  IOBuffer[0];
		hiscore[i].level			=  IOBuffer[1];
		hiscore[i].score			= (IOBuffer[3] * 65536)
									+ (IOBuffer[4] * 256)
									+  IOBuffer[5];

		if (!ZRead(IOBuffer, NAMELENGTH + 1))
		{	ZClose();
			return 5; // incorrect levels, corrupted high scores
		}
		for (j = 0; j <= NAMELENGTH; j++)
			hiscore[i].name[j]		= IOBuffer[j];

		if (ver >= 50)
		{	if (!ZRead(IOBuffer, TIMELENGTH + 1))
			{	ZClose();
				return 6; // incorrect levels, corrupted high scores
			}
			for (j = 0; j <= TIMELENGTH; j++)
				hiscore[i].time[j]	= IOBuffer[j];

			if (!ZRead(IOBuffer, DATELENGTH + 1))
			{	ZClose();
				return 7; // incorrect levels, corrupted high scores
			}
			for (j = 0; j <= DATELENGTH; j++)
				hiscore[i].date[j]	= IOBuffer[j];
		} else
		{	// skip extra name character

			if (!ZRead(IOBuffer, 1))
			{	ZClose();
				return 8; // incorrect levels, corrupted high scores
			} else
			{	if (hiscore[i].name[0])
				{	strcpy(hiscore[i].time, "??:??");
					strcpy(hiscore[i].date, "??/??/??");
				} else
				{	hiscore[i].time[0] = 0;
					hiscore[i].date[0] = 0;
	}	}	}	}

	// read level data

    for (i = 0; i <= levels; i++)
    {   if (!ZRead(IOBuffer, 8))
		{	ZClose();
			return 9;
			/* incorrect levels, corrupted high scores,
			incorrect startx, teleports, field data */
		}
		startx[i]					=  IOBuffer[0];
        starty[i]					=  IOBuffer[1];
	    teleport[i][0].alive		=  IOBuffer[2];
		teleport[i][0].x			=  IOBuffer[3];
		teleport[i][0].y			=  IOBuffer[4];
	    teleport[i][1].alive		=  IOBuffer[5];
		teleport[i][1].x			=  IOBuffer[6];
		teleport[i][1].y			=  IOBuffer[7];

		if (!ZRead((char *) &board[i][0][0], (FIELDX + 1) * (FIELDY + 1)))
		{	ZClose();
			return 10;
			/* incorrect levels, corrupted high scores,
			incorrect startx, teleports, field data */
		} else
		{	if (ver <= 44)
			{	// convert from FSET 4.4 to FSET 5.0 format
				
				for (x = 0; x <= FIELDX; x++)
					for (y = 0; y <= FIELDY; y++)
						if (board[i][x][y] >= 16)
							board[i][x][y] += 2;
			}
			if (ver <= 50)
			{	// convert from FSET 5.0 to FSET 5.1 format

				for (x = 0; x <= FIELDX; x++)
					for (y = 0; y <= FIELDY; y++)
						if (board[i][x][y] >= LIGHTNING)
							board[i][x][y]++;
	}	}	}

	// no need to read version string

	ZClose();
    modified = FALSE;
    return 0;
}

void matchteleports(void)
{	SBYTE which;

	for (which = 0; which <= levels; which++)
		if (teleport[which][0].alive == TRUE && teleport[which][1].alive == FALSE)
		{	board[which][teleport[which][0].x][teleport[which][0].y] = EMPTY;
			teleport[which][0].alive = FALSE;
			if (level == which && a == FIELDEDIT)
				draw(teleport[which][0].x, teleport[which][0].y, EMPTY);
		} elif (teleport[which][0].alive == FALSE && teleport[which][1].alive == TRUE)
		{	board[which][teleport[which][1].x][teleport[which][1].y] = EMPTY;
			teleport[which][1].alive = FALSE;
			if (level == which && a == FIELDEDIT)
				draw(teleport[which][1].x, teleport[which][1].y, EMPTY);
}		}

void newfield(void)
{	int x, y;

	teleport[level][0].alive = FALSE;
	teleport[level][1].alive = FALSE;
	startx[level] = FIELDX / 2;
	starty[level] = FIELDY / 2;

	if (level)
		for (x = 0; x <= FIELDX; x++)
			for (y = 0; y <= FIELDY; y++)
				board[level][x][y] = EMPTY;
	else for (x = 0; x <= FIELDX; x++)
		for (y = 0; y <= FIELDY; y++)
			board[0][x][y] = SILVER;
}

void newfields(void)
{	if (verify())
    {   pathname = (STRPTR) DEFAULTSET;
		levels = DEFAULTLEVELS;
		modified = FALSE;
		for (level = 0; level <= levels; level++)
			newfield();
		clearhiscores();
		level = 1;
		if (a == FIELDEDIT)
		{	turborender();
			saylevel(WHITE);
		} else
			hiscores();
}	}

void newgame(void)
{	SBYTE player;

	players = 0;
    for (player = 0; player <= 3; player++)
		if (worm[player].control != NONE)
			players++;

	for (player = 0; player <= 3; player++)
	{	worm[player].lives = 0;
		worm[player].speed = NORMAL;
        worm[player].hiscore = 0;
	}

	r			= -1;
	trainer		= FALSE;
	ice			= -1;
	reallevel	= 0;

	level		= 1;
	a			= PLAYGAME;
	clearscreen();
	newlevel(rand() % 4);
	timing();
}

void newhiscores(void)
{	PERSIST	TEXT	amiganame[4][NAMELENGTH + 1] = {"Jay Miner", "Carl Sassenrath", "R. J. Mical", "Dave Morse"};
			SBYTE	i, j, player;

	datestamp();
    for (player = 0; player <= 3; player++)
		for (i = 0; i <= HISCORES; i++)
			if (worm[player].control != NONE && worm[player].score >= hiscore[i].score)
			{	// push all worse hiscores down

				if (i < HISCORES)
					for (j = HISCORES; j >= i + 1; j--)
					{	hiscore[j].player	  = hiscore[j - 1].player;
						hiscore[j].score	  = hiscore[j - 1].score;
						hiscore[j].level	  = hiscore[j - 1].level;
						hiscore[j].fresh	  = hiscore[j - 1].fresh;
						strcpy(hiscore[j].name, hiscore[j - 1].name);
						strcpy(hiscore[j].date, hiscore[j - 1].date);
						strcpy(hiscore[j].time, hiscore[j - 1].time);
					}
				modified = TRUE;
				hiscore[i].player = player;
				hiscore[i].score = worm[player].hiscore;
				hiscore[i].level = worm[player].levelreached;
				if (worm[player].control == AMIGA)
				{	strcpy(hiscore[i].name, amiganame[player]);
					hiscore[i].fresh = FALSE;
				} else
				{	strcpy(hiscore[i].name, "(New)");
					hiscore[i].fresh = TRUE;
				}
				strcpy(hiscore[i].time, times);
				strcpy(hiscore[i].date, date);
				break; // vital
}			}

void newlevel(SBYTE player)
{	SBYTE	iwhich, which;

	if (level >= 2 && worm[player].lives)
		rundown(player);
	if (a == PLAYGAME)
	{	if (level > levels)
        {   for (which = 0; which <= 3; which++)
				if (worm[which].lives)
					worm[which].levelreached = -1;
			celebrate();
			newhiscores();
			titlescreen();
		} else
		{	saylevel(WHITE);
            for (which = 0; which <= 3; which++)
				worm[which].multi = (SBYTE) atleast(worm[which].multi / 2, 1);
			killall();
			clearletters();
			orientworms();
			changefield();
			turborender();
            delay = atleast(DELAY_MAX - (level * DELAY_DEC), DELAY_MIN);

			if (level)
			{	secondsperlevel = SECONDSPERLEVEL;
				putletter(-1);
				for (which = 0; which <= 3; which++)
    			{   if (!worm[which].lives && worm[which].control != NONE)
    				{   // create (or resurrect) a worm

						worm[which].lives      = STARTLIVES;
					    worm[which].score      = 0;
						worm[which].alive      = TRUE;
					    worm[which].oldscore   = 0;
					    worm[which].armour     = 0;
					    worm[which].tongue     = 0;
					    worm[which].nitro      = FALSE;
					    worm[which].mode       = NULL;
					    worm[which].power      = 0;
					    worm[which].bias       = 0;
					    worm[which].multi      = 1;
					    worm[which].ice        = 0;
					    worm[which].victor     = -1;
						worm[which].ammo       = 0;
						worm[which].remnants   = 0;
						worm[which].affixer    = FALSE;
						worm[which].causewait  = (ULONG) -1;
						worm[which].last       = FIRSTTAIL + which;
						worm[which].pos        = -1;
						for (iwhich = 0; iwhich <= PROTECTORS; iwhich++)
							protector[which][iwhich].alive = FALSE;
					    for (iwhich = 0; iwhich <= LASTOBJECT; iwhich++)
					    	    stat(which, iwhich);
	}	}	}	}	}
	clearjoystick();
	clearkybd();
	resettime();
}

void explosion(SBYTE x, SBYTE y, SBYTE exceptionx, SBYTE exceptiony)
{   UBYTE i, generated = 0;

/* You wouldn't think this would work properly for pulse-explosions,
because the worm's head is obliterated. However, it is refreshed (as
tail) the next time wormloop() is called for that worm. */

    effect(FXEXPLODE);
    for (i = 0; i <= CREATURES; i++)
        if ((!(creature[i].alive)) && generated <= 7)
        {   creature[i].last      = EMPTY;
            creature[i].x         = x;
            creature[i].y         = y;
            if (level)
                creature[i].speed = (SBYTE) atleast(FRAGSPEED / level, 1);
            else creature[i].speed = BONUSFRAGSPEED;
	    creature[i].species   = FRAGMENT;
            creature[i].visible   = TRUE;
	    	switch (generated)
			{
			case 0:
                creature[i].deltax = 0;
                creature[i].deltay = -1;
            break;
			case 1:
                creature[i].deltax = 1;
                creature[i].deltay = -1;
            break;
			case 2:
                creature[i].deltax = 1;
                creature[i].deltay = 0;
            break;
			case 3:
                creature[i].deltax = 1;
                creature[i].deltay = 1;
            break;
			case 4:
                creature[i].deltax = 0;
                creature[i].deltay = 1;
            break;
			case 5:
                creature[i].deltax = -1;
                creature[i].deltay = 1;
            break;
			case 6:
                creature[i].deltax = -1;
                creature[i].deltay = 0;
            break;
			case 7:
                creature[i].deltax = -1;
                creature[i].deltay = -1;
			break;
		default:
			break;
        }
	generated++;
        if (creature[i].deltax != exceptionx || creature[i].deltay != exceptiony)
        {   creature[i].alive = TRUE;
            if (generated == 1)
	    	change(x, y, FRAGMENT);
}   }   }

/* Many creatures have been reorganized, to take advantage of shared
characteristics. 6 creature types (orbs, goats, drips, fragments, missiles
and penguins) use the creature structure. Not encapsulated are worms,
protectors, worm bullets, timebombs and teleports. */

void creatureloop(SBYTE which)
{   ABOOL   happy = FALSE;
    UBYTE   bestdistance = 255, distance, player, thissy, i;
    SBYTE   x, y, xx, yy, xxx, yyy, frontx, fronty, rearx, reary;

x = creature[which].x;
y = creature[which].y;

if (creature[which].species == ORB && creature[which].explode)
{   creature[which].alive = FALSE;
    explosion(x, y, -2, -2);
    return;
}

// decide whether and where to move

switch(creature[which].species)
{
case PENGUIN:
	do
	{	xx = (rand() % 3) - 1;
		yy = (rand() % 3) - 1;
	} while (!valid(x + xx, y + yy));
	thissy = field[x + xx][y + yy];
	if (thissy >= FIRSTEMPTY && thissy <= LASTEMPTY)
	{	creature[which].deltax = xx;
		creature[which].deltay = yy;
	} else
	{	creature[which].deltax = 0;
		creature[which].deltay = 0;
	}
break;
case MISSILE:
    for (player = 0; player <= 3; player++)
        if (creature[which].type != player && worm[player].lives && !worm[player].bias)
        {   xx = abs(worm[player].x - x);
            yy = abs(worm[player].y - y);
            if (xx < yy)
                distance = xx;
            else distance = yy;
            if (distance <= bestdistance)
            {   bestdistance = distance;
                creature[which].deltax = bsign(worm[player].x - x);
                creature[which].deltay = bsign(worm[player].y - y);
        }   }
	for (i = 0; i <= CREATURES; i++)
        if (creature[i].alive && which != i)
        {   if
            (   creature[i].species == ORB
            ||  (creature[i].species == MISSILE && creature[i].type != creature[which].type)
            ||  creature[i].species == GOAT
            )
            {   xx = abs(creature[i].x - x);
                yy = abs(creature[i].y - y);
                if (xx < yy)
                    distance = xx;
                else distance = yy;
                if (distance <= bestdistance)
                {   bestdistance = distance;
                    creature[which].deltax = bsign(creature[i].x - x);
                    creature[which].deltay = bsign(creature[i].y - y);
        }   }   }
    if (bestdistance == 255)
        creature[which].alive = FALSE;
break;
case ORB:
    frontx  = xwrap(x + creature[which].deltax);  // look in front
    fronty  = ywrap(y + creature[which].deltay);
    rearx   = xwrap(x - creature[which].deltax);  // look behind
    reary   = ywrap(y - creature[which].deltay);
    if (bounceorb(which, frontx, fronty))
    {   bouncegoat(which, frontx, fronty);
        xx = -creature[which].deltax; // default bounce angle is 180°
        yy = -creature[which].deltay;
    	if (!bounceorb(which, frontx, reary))
    	{	if (bounceorb(which, rearx, fronty))
            {   bouncegoat(which, rearx, fronty);
                xx = creature[which].deltax;
    	}	}
    	elif (!bounceorb(which, rearx, fronty))
        {   bouncegoat(which, rearx, fronty);
            yy = creature[which].deltay;
    	}
        creature[which].deltax = xx;
        creature[which].deltay = yy;
    }
break;
case GOAT:
    // decide whether to move
	if (!(rand() % RESTFULNESS))
    {   for (xx = x - 1; xx <= x + 1; xx++)
            for (yy = y - 1; yy <= y + 1; yy++)
                if (valid(xx, yy) && field[xx][yy] >= FIRSTEMPTY && field[xx][yy] <= LASTEMPTY)
				{   happy = TRUE;
					break;
	}			}
    creature[which].deltax = 0;
	creature[which].deltay = 0;
    if (!happy)
	{	xx = (rand() % 3) - 1;
		yy = (rand() % 3) - 1;
		if (valid(x + xx, y + yy) && (xx || yy))
		{	thissy = field[x + xx][y + yy];
            if (thissy >= FIRSTGOAT && thissy <= LASTGOAT && thissy != GOAT)
        	{	creature[which].last = creature[which].oldlast;
				creature[which].oldlast = thissy;
				creature[which].deltax = xx;
				creature[which].deltay = yy;
	}	}	}
break;
default:
break;
}

// now move

if (creature[which].deltax || creature[which].deltay)
{	if (creature[which].visible)
	{   // erase previous image
	    change(x, y, creature[which].last);
	    if (creature[which].species != FRAGMENT)
	    	creature[which].last = EMPTY;
	}
	if (creature[which].alive)
	{   creature[which].x += creature[which].deltax;
	    creature[which].y += creature[which].deltay;
	    if (creature[which].species == ORB)
	    {   creature[which].x = xwrap(creature[which].x);
	        creature[which].y = ywrap(creature[which].y);
	    } elif (creature[which].species == FRAGMENT || creature[which].species == DRIP)
	    {	if (!valid(creature[which].x, creature[which].y))
		   creature[which].alive = FALSE;
}	}   }

creature[which].visible = TRUE;
x = creature[which].x;
y = creature[which].y;

// collision detection

if
(	creature[which].alive
&&	creature[which].species != GOAT
&&	creature[which].species != PENGUIN
&&	(creature[which].deltax || creature[which].deltay)
)
{	thissy = field[x][y];

	if (thissy <= LASTOBJECT)
	{	if (creature[which].species == ORB)
        {   orbscore(which, object[thissy].score);
    		if (thissy != SLAYER && thissy != BOMB)
	    		effect(FXGETOBJECT);
        }
        if (thissy == BOMB)
        {   i = whichtimebomb(x, y);

            if (creature[which].species == FRAGMENT || creature[which].species == MISSILE)
            {   if (i != -1)
                {   creature[which].alive = FALSE;
                    timebomb[i].alive = FALSE;
                    if (creature[which].species == MISSILE)
		    	draw(x, y, FIRSTMISSILE + creature[which].type);
		    else draw(x, y, FRAGMENT);
                    bombblast(BOMB, 0, x, y);
                    change(x, y, EMPTY);
            }   }
            elif (creature[which].species == ORB)
            {   if (i != -1)
                {   timebomb[i].alive = FALSE;
                    draw(x, y, ORB);
                    /* so the user understands what is happening
                    BEFORE the s-l-o-w bombblast() occurs */
                }
                bombblast(ORB, which, x, y);
    }   }   }
    elif (thissy >= FIRSTDRIP && thissy <= LASTDRIP)
    {   if (creature[which].species == MISSILE)
            creature[which].alive = FALSE;
        elif (creature[which].species == FRAGMENT)
        {   creature[whichcreature(x, y, DRIP, 255)].alive = FALSE;
			creature[which].alive = FALSE;
            change(x, y, BONUS);
        } elif (creature[which].species == ORB)
    	{	creature[whichcreature(x, y, DRIP, 255)].alive = FALSE;
            if (creature[which].mode != ARMOUR)
		    {	creature[which].explode = TRUE;
			    draw(x, y, EMPTY);
	}	}   }
    elif (thissy >= FIRSTMISSILE && thissy <= LASTMISSILE)
    {   // Drips, orbs, fragments, missiles
		creature[whichcreature(x, y, MISSILE, which)].alive = FALSE;

        if (creature[which].species == FRAGMENT)
		{	creature[which].alive = FALSE;
			change(x, y, EMPTY);
		} elif (creature[which].species == MISSILE)
        {   creature[which].alive = FALSE;
            change(x, y, BONUS);
        } elif (creature[which].species == ORB)
    	{	if (creature[which].mode != ARMOUR)
            {   effect(FXORBDEATH);
                wormscore(thissy - FIRSTMISSILE, creature[which].score);
                if (worm[thissy - FIRSTMISSILE].bias)
                {   worm[thissy - FIRSTMISSILE].lives += ORBBLOOD;
                    stat(thissy - FIRSTMISSILE, LIFE);
                }
                creature[which].alive = FALSE;
                change(x, y, BONUS);
		    } else effect(FXUSEARMOUR);
    }   }
    elif (thissy >= FIRSTHEAD && thissy <= LASTHEAD)
    {   if (creature[which].species == MISSILE)
		{	if (creature[which].type == thissy - FIRSTHEAD)
				creature[which].visible = FALSE;
			else
	        {   creature[which].alive = FALSE;
	            if (worm[thissy - FIRSTHEAD].mode != ARMOUR)
			    {   worm[thissy - FIRSTHEAD].cause = FIRSTMISSILE + creature[which].type;
            	    worm[thissy - FIRSTHEAD].victor = creature[which].type;
            	    worm[thissy - FIRSTHEAD].alive = FALSE;
            	} else effect(FXUSEARMOUR);
		}	}
        elif (creature[which].species == FRAGMENT)
        {   if (worm[thissy - FIRSTHEAD].mode != ARMOUR)
			{	worm[thissy - FIRSTHEAD].cause = FRAGMENT;
				worm[thissy - FIRSTHEAD].victor = -1;
				worm[thissy - FIRSTHEAD].alive = FALSE;
				creature[which].alive = FALSE;
			} else
			{	effect(FXUSEARMOUR);
				reflect(which);
		}	}
        elif (creature[which].species == DRIP)
        {   creature[which].alive = FALSE;
			if (creature[which].type == thissy - FIRSTHEAD || worm[thissy - FIRSTHEAD].bias)
			{	effect(FXDRIP);
				wormscore(thissy - FIRSTHEAD, DRIPBONUS);
			} else
			{	worm[thissy - FIRSTHEAD].alive = FALSE;
				worm[thissy - FIRSTHEAD].cause = FIRSTDRIP + creature[which].type;
				worm[thissy - FIRSTHEAD].victor = -1;
		}	}
    	elif (creature[which].species == ORB)
    	{	if (worm[thissy - FIRSTHEAD].mode != ARMOUR)
    	    {	worm[thissy - FIRSTHEAD].cause = ORB;
			    worm[thissy - FIRSTHEAD].victor = -1;
			    worm[thissy - FIRSTHEAD].alive = FALSE;
			    if (creature[which].mode == ARMOUR)
			    {	effect(FXUSEARMOUR);
				    orbscore(which, KILLWORM);
    			} else
                {   effect(FXORBDEATH);
                    creature[which].alive = FALSE;
                    wormscore(thissy - FIRSTHEAD, creature[which].score);
                    if (worm[thissy - FIRSTHEAD].bias)
                    {   worm[thissy - FIRSTHEAD].lives += ORBBLOOD;
                        stat(thissy - FIRSTHEAD, LIFE);
            }   }   }
		    else
		    {	effect(FXUSEARMOUR);
                effect(FXORBDEATH);
                creature[which].alive = FALSE;
                wormscore(thissy - FIRSTHEAD, creature[which].score);
                if (worm[thissy - FIRSTHEAD].bias)
                {   worm[thissy - FIRSTHEAD].lives += ORBBLOOD;
                    stat(thissy - FIRSTHEAD, LIFE);
		    }   }
            creature[which].last = thissy - FIRSTHEAD + FIRSTTAIL; // note sign issues
	}   }
    elif (thissy >= FIRSTTAIL && thissy <= LASTTAIL)
    {   if (creature[which].species == ORB)
        {   if (creature[which].mode == TONGUE)
                effect(FXUSETONGUE);
            else
            {   if (worm[thissy - FIRSTTAIL].alive)
                {   effect(FXORBDEATH);
                    wormscore(thissy - FIRSTTAIL, creature[which].score);
                    if (worm[thissy - FIRSTTAIL].bias)
                    {   worm[thissy - FIRSTTAIL].lives += ORBBLOOD;
                        stat(thissy - FIRSTTAIL, LIFE);
                }   }
                creature[which].alive = FALSE;
                change(x, y, BONUS);
    }   }   }
    elif (thissy >= FIRSTPROTECTOR && thissy <= LASTPROTECTOR)
    {   if (creature[which].species == MISSILE)
        {   if (creature[which].type != thissy - FIRSTPROTECTOR)
            {   effect(FXUSEPROTECTOR);
                creature[which].alive = FALSE;
            } else creature[which].visible = FALSE;
        } elif (creature[which].species == FRAGMENT)
		{	effect(FXUSEPROTECTOR);
			reflect(which);
        } elif (creature[which].species == DRIP)
        {	creature[which].alive = FALSE;
			if (creature[which].type == thissy - FIRSTPROTECTOR || worm[thissy - FIRSTPROTECTOR].bias)
			{	wormscore(thissy - FIRSTPROTECTOR, DRIPBONUS);
				effect(FXDRIP);
        }   }
        elif (creature[which].species == ORB)
        {   effect(FXUSEPROTECTOR);
	    	effect(FXORBDEATH);
            creature[which].alive = FALSE;
            wormscore(thissy - FIRSTPROTECTOR, creature[which].score);
            if (worm[thissy - FIRSTPROTECTOR].bias)
            {   worm[thissy - FIRSTPROTECTOR].lives += ORBBLOOD;
                stat(thissy - FIRSTPROTECTOR, LIFE);
    }   }   }
	elif (thissy >= FIRSTLETTER && thissy <= LASTLETTER)
    {   if (creature[which].species == DRIP || creature[which].species == FRAGMENT || creature[which].species == MISSILE)
        {   effect(FXTHUD);
        	creature[which].alive = FALSE;
        } elif (creature[which].species == ORB)
        {   for (player = 0; player <= 3; player++)
		    {	letters[player][thissy - FIRSTLETTER] = FALSE;
			    drawletter(player, thissy, BLACK);
    		}
		    putletter(-1);
	}   }
	elif (thissy == PENGUIN)
	{	creature[whichcreature(x, y, PENGUIN, 255)].alive = FALSE;
		if (creature[which].species == ORB && creature[which].mode == ARMOUR)
			orbscore(which, KILLPENGUIN);
		else
		{	creature[which].alive = FALSE;
			change(x, y, EMPTY);
	}	}
    elif (thissy == FRAGMENT)
    {   creature[whichcreature(x, y, FRAGMENT, which)].alive = FALSE;

        if (creature[which].species == DRIP)
        {   creature[which].alive = FALSE;
            change(x, y, BONUS);
		} elif (creature[which].species == PENGUIN)
		{	creature[which].alive = FALSE;
			change(x, y, EMPTY);
        } elif (creature[which].species == MISSILE)
		{	creature[which].alive = FALSE;
			change(x, y, EMPTY);
		} elif (creature[which].species == ORB)
        {   if (creature[which].mode != ARMOUR)
                creature[which].explode = TRUE;
            else effect(FXUSEARMOUR);
        } elif (creature[which].species == FRAGMENT)
		{	effect(FXTHUD);
			draw(x, y, EMPTY);
			creature[which].alive = FALSE;
    }   }
    elif (thissy == METAL)
    {   if (creature[which].species == DRIP || creature[which].species == MISSILE)
            creature[which].alive = FALSE;
        elif (creature[which].species == FRAGMENT)
            reflect(which);
    } elif (thissy == STONE)
    {   if (creature[which].species == DRIP || creature[which].species == FRAGMENT || creature[which].species == MISSILE)
        {   effect(FXTHUD);
            creature[which].alive = FALSE;
    }   }
    elif (thissy == WOOD)
    {   if (creature[which].species == FRAGMENT || creature[which].species == MISSILE)
        {   effect(FXTHUD);
		    creature[which].alive = FALSE;
    }   }
    elif (thissy == GOAT)
    {   // Drips, fragments and missiles
        creature[whichcreature(x, y, GOAT, 255)].alive = FALSE;
        effect(FXGOATDEATH);
        change(x, y, BONUS);
        creature[which].alive = FALSE;

        if (creature[which].species == MISSILE)
        {   wormscore(creature[which].type, KILLGOAT);
            if (worm[creature[which].type].bias)
            {   worm[creature[which].type].lives += GOATBLOOD;
                stat(creature[which].type, LIFE);
    }   }   }
    elif (thissy == SLIME)
    {   if (creature[which].species == DRIP || creature[which].species == FRAGMENT || creature[which].species == MISSILE)
		{	creature[which].alive = FALSE;
            change(x, y, EMPTY);
        } elif (creature[which].species == ORB)
		{   if (creature[which].mode != TONGUE)
			    creature[which].alive = FALSE;
    }   }
    elif (thissy == SKULL)
	{	if (creature[which].species == ORB)
        {   effect(FXGETSKULL);
		    orbscore(which, SKULLPOINT);
        } elif (creature[which].species == FRAGMENT || creature[which].species == DRIP || creature[which].species == MISSILE)
        {   effect(FXTHUD);
            creature[which].alive = FALSE;
    }   }
    elif (thissy == TELEPORT)
    {   // Drips, fragments, missiles, orbs
        i = whichteleport(x, y);
        if (blocked(i, creature[which].deltax, creature[which].deltay))
            creature[which].alive = FALSE;
        else
        {   effect(FXUSETELEPORT);
            creature[which].x = teleport[level][partner(i)].x + creature[which].deltax;
            creature[which].y = teleport[level][partner(i)].y + creature[which].deltay;

            if (creature[which].species == DRIP)
            {   if (!(valid(creature[which].x, creature[which].y)))
                    creature[which].alive = FALSE;
            } elif (creature[which].species == ORB)
            {   orbscore(which, TELPOINT);
                creature[which].x = xwrap(creature[which].x);
                creature[which].y = ywrap(creature[which].y);
            } elif (creature[which].species == FRAGMENT)
                creature[which].last = SILVER;
    }   }
	elif (thissy == ORB)
    {   i = whichcreature(x, y, ORB, which);
        creature[which].alive = FALSE;

        if (creature[which].species == MISSILE)
        {   if (creature[i].mode != ARMOUR)
            {   effect(FXORBDEATH);
                creature[i].alive = FALSE;
                change(x, y, BONUS);
                wormscore(creature[which].type, creature[i].score);
                if (worm[creature[which].type].bias)
                {   worm[creature[which].type].lives += ORBBLOOD;
                    stat(creature[which].type, LIFE);
            }   }
            else effect(FXUSEARMOUR);
        } elif (creature[which].species == FRAGMENT || creature[which].species == DRIP)
        {   if (creature[i].mode != ARMOUR)
            {   creature[i].explode = TRUE;
                draw(x, y, EMPTY);
            } else effect(FXUSEARMOUR);
        } elif (creature[which].species == ORB)
		{   effect(FXORBDEATH);
            creature[i].alive = FALSE;
            change(x, y, BONUS);
	}	}
    elif (creature[which].species == ORB)
    {   if (thissy == EMPTY)
    		orbscore(which, EMPTYPOINT);
        elif (thissy == SILVER)
        	orbscore(which, SILVERPOINT);
        elif (thissy == GOLD)
    		orbscore(which, GOLDPOINT);
        elif (thissy == AMMO || thissy == PULSE || thissy == SLAYER || thissy == LIGHTNING)
        {   if (creature[which].mode != ARMOUR)
                creature[which].explode = TRUE;
			else effect(FXUSEARMOUR);
        } elif (thissy == NITRO || thissy == POWER || thissy == SLOWER || thissy == REMNANTS)
        {   effect(FXGETNITRO);
            creature[which].speed = speedup(creature[which].speed, TRUE);
        } elif (thissy == HEALER || thissy == LIFE || thissy == ICE || thissy == TREASURE || thissy == UMBRELLA)
            orbsplit(which);
        elif (thissy == ARMOUR)
        {   creature[which].armour += MODEADD + (rand() % MODERAND);
            creature[which].mode = ARMOUR;
        } elif (thissy == TONGUE)
        {   creature[which].tongue += MODEADD + (rand() % MODERAND);
            creature[which].mode = TONGUE;
        } elif (thissy == PROTECTOR)
        {   for (player = 0; player <= 3; player++)
                if (worm[player].lives)
					for (i = 0; i <= PROTECTORS; i++)
						if (protector[player][i].alive)
						{	protector[player][i].alive = FALSE;    
                            if (protector[player][i].visible)
                                change(protector[player][i].x, protector[player][i].y, EMPTY);
        }               }
        elif (thissy == MISSILE)
        {   for (i = 0; i <= CREATURES; i++)
            {   if (creature[i].alive && creature[i].species == MISSILE)
                {   creature[i].alive = FALSE;
                    change(x, y, EMPTY);
        }   }   }
        elif (thissy == MULTIPLIER)
        {   creature[which].multi *= 2;
            if (creature[which].multi > MULTILIMIT)
                creature[which].multi = MULTILIMIT;
        } elif (thissy == BIAS)
        {   for (player = 0; player <= 3; player++)
                if (worm[player].lives && worm[player].bias)
				{	worm[player].bias = 0;
					stat(player, BIAS);
		}		}
		elif (thissy == AFFIXER)
        {   for (player = 0; player <= 3; player++)
                if (worm[player].lives)
					worm[player].affixer = FALSE;
		} elif (thissy == SWITCHER)
        {   for (xx = 0; xx <= FIELDX; xx++)
                for (yy = 0; yy <= FIELDY; yy++)
                    if (field[xx][yy] >= FIRSTTAIL && field[xx][yy] <= LASTTAIL)
                        change(xx, yy, WOOD);
        } elif (thissy == GROWER)
		{	effect(FXGETGROWER);
			for (xx = 0; xx <= FIELDX; xx++)
				for (yy = 0; yy <= FIELDY; yy++)
					if (field[xx][yy] == WOOD)
						for (xxx = xx - 1; xxx <= xx + 1; xxx++)
							for (yyy = yy - 1; yyy <= yy + 1; yyy++)
								if (valid(xxx, yyy) && field[xxx][yyy] == EMPTY)
									field[xxx][yyy] = TEMPWOOD;
			for (xx = 0; xx <= FIELDX; xx++)
				for (yy = 0; yy <= FIELDY; yy++)
					if (field[xx][yy] == TEMPWOOD)
                        change(xx, yy, WOOD);
		} elif (thissy == CLOCK)
			secondsperlevel -= rand() % CLOCKRAND;
}   }

if (creature[which].alive && creature[which].visible && (creature[which].deltax || creature[which].deltay))
{   if (creature[which].species == MISSILE)
        change(x, y, FIRSTMISSILE + creature[which].type);
    elif (creature[which].species == DRIP)
        change(x, y, FIRSTDRIP + creature[which].type);
    elif (creature[which].species == FRAGMENT)
        change(x, y, FRAGMENT);
    elif (creature[which].species == GOAT)
        change(x, y, GOAT);
    elif (creature[which].species == PENGUIN)
    	change(x, y, PENGUIN);
    elif (creature[which].species == ORB)
    {   field[x][y] = ORB;
        if (creature[which].mode == TONGUE)
            draw(x, y, ORBTONGUE);
        elif (creature[which].mode == ARMOUR)
            draw(x, y, ORBARMOUR);
        else draw(x, y, ORB);
}   }

if (creature[which].alive && creature[which].species == GOAT)
{   // decide whether to fire
    if (!(rand() % 10))
    {   for (i = 0; i <= CREATURES; i++)
        {   if (!creature[i].alive)
            {   creature[i].deltax = (rand() % 3) - 1;
                creature[i].deltay = (rand() % 3) - 1;
                if
                (   valid(x + creature[i].deltax, y + creature[i].deltay)
                && (creature[i].deltax || creature[i].deltay)
		)
                {   thissy = field[x + creature[i].deltax][y + creature[i].deltay];
                    if
                    (   (thissy >= FIRSTEMPTY && thissy <= LASTEMPTY)
                    ||  (thissy >= FIRSTTAIL && thissy <= LASTTAIL)
		    )
                    {   effect(FXGOATFIRE);
                        creature[i].alive = TRUE;
                        creature[i].species = FRAGMENT;
                        creature[i].x = x + creature[i].deltax;
                        creature[i].y = y + creature[i].deltay;
                        creature[i].visible = TRUE;
                        creature[i].last = EMPTY;
                        creature[i].speed = (SBYTE) atleast(FRAGSPEED / level, 1);
		}   }
		break;
}   }   }   }
}

void orbsplit(SBYTE which)
{   SBYTE   copy = 0, i;

	effect(FXORBSPLIT);
    for (i = 0; i <= CREATURES; i++)
    {   if (!creature[i].alive)
        {   creature[i].x       = creature[which].x;
            creature[i].y       = creature[which].y;
            creature[i].score   = creature[which].score;
            creature[i].armour  = creature[which].armour;
            creature[i].tongue  = creature[which].tongue;
            creature[i].mode    = creature[which].mode;
            creature[i].speed   = creature[which].speed;
            creature[i].explode = FALSE;
            creature[i].multi   = creature[which].multi;
            creature[i].last    = EMPTY;
			switch (copy)
			{
			case 0:
                if (creature[which].deltax != -1 || creature[which].deltay != -1)
                {   creature[i].deltax = -1;
                    creature[i].deltay = -1;
                    creature[i].alive = TRUE;
				}
            break;
			case 1:
                if (creature[which].deltax != 1 || creature[which].deltay != 1)
                {   creature[i].deltax = 1;
                    creature[i].deltay = 1;
                    creature[i].alive = TRUE;
				}
            break;
			case 2:
                if (creature[which].deltax != 1 || creature[which].deltay != -1)
                {   creature[i].deltax = 1;
                    creature[i].deltay = -1;
                    creature[i].alive = TRUE;
				}
            break;
			case 3:
                if (creature[which].deltax != -1 || creature[which].deltay != 1)
                {   creature[i].deltax = -1;
                    creature[i].deltay = 1;
                    creature[i].alive = TRUE;
				}
            break;
			default:
            break;
			}
            if (++copy >= 4)
                return;
}	}	}

void orientworms(void)
{	SBYTE	player;

    for (player = 0; player <= 3; player++)
	{	worm[player].speed = NORMAL;
		if (worm[player].lives)
			stat(player, NITRO);
		worm[player].moved = FALSE;
		worm[player].x = startx[level];
		worm[player].y = starty[level];
		switch (player)
		{
		case 0:
			worm[0].deltax = -1;
			worm[0].deltay = 0;
			break;
		case 1:
			worm[1].deltax = 1;
			worm[1].deltay = 0;
			break;
		case 2:
			worm[2].deltax = 0;
			worm[2].deltay = -1;
			break;
		case 3:
			worm[3].deltax = 0;
			worm[3].deltay = 1;
			break;
		default:
			break;
}   }   }

SBYTE partner(SBYTE which)
{	if (which % 2 == 0)
        return((SBYTE) (which + 1));
	else return((SBYTE) (which - 1));
}

/* NAME		putletter -- Put a letter onto the field
SYNOPSIS	void putletter(SBYTE player);
INPUTS		SBYTE player -
			0-3: player on whose behalf the letter is put on for
			-1: any letter
RESULT		none
NOTES		noletter -
			-2: if we found somewhere to put the letter
			player: if we didn't */

void putletter(SBYTE player)
{	SBYTE	count = 0, letter;
	SBYTE	i, x, y;

	do
	{	x = rand() % (FIELDX + 1);
		y = rand() % (FIELDY + 1);
	} while ((field[x][y] < FIRSTEMPTY || field[x][y] > LASTEMPTY) && (++count < PATIENCE));
	if (count < PATIENCE)
	{	noletter = -2;
		if (player != -1)
		{	for (i = 0; i <= LETTERS; i++)
				if (!(letters[player][i]))
				{	break;
				}
			if (i > LETTERS)
				letter = rand() % (LETTERS + 1);
			else
			{	do
					letter = rand() % (LETTERS + 1);
				while (letters[player][letter]);
			}
		} else
			letter = rand() % (LETTERS + 1);
        change(x, y, letter + FIRSTLETTER);
		letterx = x;
		lettery = y;
		lettertype = letter + FIRSTLETTER;
	} else noletter = player;
}

/* NAME     queue -- adds a keystroke to the key queue
SYNOPSIS    name(SBYTE, SBYTE, SBYTE);
FUNCTION    Adds a keystroke to the in-game key queue.
INPUTS      player - player that pressed the key
            deltax - the deltax of the key
            deltay - the deltay of the key
IMPLEMENTATION
            thequeue[] is an array, with QUEUELIMIT as its last index.
            It is implemented as a FIFO stack rather than LIFO so that
            the keystrokes are processed in the correct order (that is,
            the order in which they were pressed). The oldest keystroke
            is always at index [0], the next oldest at [1], and so on
            upwards to the newest keystroke, at [worm[player].pos].
            Keystrokes are removed from the bottom of the array ([0]),
            and the rest of the array is shuffled down to fill the gap,
            so that the contents of [1] go to [0], the contents of [2]
            go to [1], etc. worm[player].pos is adjusted to always point
            to the newest entry, which is the 'end' of the queue.
MODULE      engine.c */

void queue(SBYTE player, SBYTE deltax, SBYTE deltay)
{   if (worm[player].pos < QUEUELIMIT)
	{	worm[player].pos++;
		thequeue[player][worm[player].pos].deltax = deltax;
		thequeue[player][worm[player].pos].deltay = deltay;
}	}

void reflect(UBYTE which)
{	creature[which].deltax	 = -creature[which].deltax;
	creature[which].deltay	 = -creature[which].deltay;
    creature[which].x       +=  creature[which].deltax * 2;
	creature[which].y		+=  creature[which].deltay * 2;
}

ABOOL savefields(STRPTR fieldname)
{	SBYTE	i, j;
	TEXT	IOBuffer[NAMELENGTH + 1];

	matchteleports();

    if (!ZOpen(fieldname, TRUE))
		return FALSE;

	// write header

	strcpy(IOBuffer,					"FSET 5.1");
    IOBuffer[9]							= levels;
    if (!ZWrite(IOBuffer, 10))
    {   ZClose();
        return FALSE;
    }

	// write high score table

	for (i = 0; i <= HISCORES; i++)
	{	IOBuffer[0]						=  hiscore[i].player;
		IOBuffer[1]						=  hiscore[i].level;
		IOBuffer[2]						=  0;
		IOBuffer[3]						=  hiscore[i].score / 65536;
		IOBuffer[4]						= (hiscore[i].score % 65536) / 256;
		IOBuffer[5]						= (hiscore[i].score % 65536) % 256;
	    if (!ZWrite(IOBuffer, 6))
	    {   ZClose();
	        return FALSE;
	    }

		for (j = 0; j <= NAMELENGTH; j++)
			IOBuffer[j]						= hiscore[i].name[j];
	    if (!ZWrite(IOBuffer, NAMELENGTH + 1))
	    {   ZClose();
			return FALSE;
		}
		for (j = 0; j <= TIMELENGTH; j++)
			IOBuffer[j]						= hiscore[i].time[j];
	    if (!ZWrite(IOBuffer, TIMELENGTH + 1))
	    {   ZClose();
			return FALSE;
		}
		for (j = 0; j <= DATELENGTH; j++)
			IOBuffer[j]						= hiscore[i].date[j];
	    if (!ZWrite(IOBuffer, DATELENGTH + 1))
	    {   ZClose();
			return FALSE;
	}	}

	// write level data

    for (i = 0; i <= levels; i++)
    {   IOBuffer[0]						= startx[i];
        IOBuffer[1]						= starty[i];
		IOBuffer[2]						= teleport[i][0].alive;
		IOBuffer[3]						= teleport[i][0].x;
		IOBuffer[4]						= teleport[i][0].y;
		IOBuffer[5]						= teleport[i][1].alive;
		IOBuffer[6]						= teleport[i][1].x;
		IOBuffer[7]						= teleport[i][1].y;
	    if (!ZWrite(IOBuffer, 8))
	    {   ZClose();
			return FALSE;
	    }

	    if (!ZWrite((char *) &board[i][0][0], (FIELDX + 1) * (FIELDY + 1)))
	    {   ZClose();
			return FALSE;
	}	}

	// write version string

    if (!ZWrite(VERSION, strlen(VERSION)))
    {   ZClose();
		return FALSE;
    }

	ZClose();

	if (clearthem)
		clearhiscores();
    modified = FALSE;
	return TRUE;
}

void saylevel(COLOUR colour)
{	TEXT mainstring[15] = "Level ", tempstring[4];

	if (level > 0)
	{	stci_d(&mainstring[6], level);
		strcat((char*) mainstring, " of ");
		stci_d(tempstring, levels);
		strcat((char*) mainstring, (char*) tempstring);
		say(mainstring, colour);
	} else
	{	if (a == FIELDEDIT)
			say("Bonus Level", colour);
		else
		{	if (leveltype == TREASURE)
				say("Bonus Level: Treasury!", colour);
			else say("Bonus Level: Drips!", colour);
}	}	}

void setbrush(SBYTE newbrush)
{	brush = newbrush;
	setpointer(brush);
	underline(brush);
}

SBYTE slowdown(SBYTE speed)
{	speed *= 2;
	if (speed > SLOW)
		speed = SLOW;
	return(speed);
}

/* NAME     slowloop -- things done rarely
SYNOPSIS    slowloop(void);
FUNCTION    Decrements strength; creates goats, orbs, objects,
            teleports, slime; controls timebombs; puts letters if
            neccessary; blanks out old causes.
MODULE      engine.c */

void slowloop(void)
{		ABOOL	done;
		SBYTE	i, player, which, x, xx, y, yy;
        UBYTE   thissy;

// decrement worm strength

for (player = 0; player <= 3; player++)
	if (worm[player].lives > 0 && ice == -1 || ice == player)
	{	if (worm[player].bias > 0)
		{	worm[player].bias--;
			stat(player, BIAS);
		}
		if (worm[player].ice > 0 && --worm[player].ice == 0)
        {   for (which = 0; which <= 3; which++)
				if (player != which)
					worm[player].pos = -1;
			ice = -1;
		}
		if (worm[player].mode == ARMOUR)
		{	if (--worm[player].armour == 0)
			{	if (worm[player].tongue > 0)
					worm[player].mode = TONGUE;
				else worm[player].mode = NULL;
			}
			stat(player, ARMOUR);
		} elif (worm[player].mode == TONGUE)
		{	if (--worm[player].tongue == 0)
			{	if (worm[player].armour > 0)
					worm[player].mode = ARMOUR;
				else worm[player].mode = NULL;
			}
			stat(player, TONGUE);
	}	}

// blank out old causes

for (player = 0; player <= 3; player++)
{	if (worm[player].lives > 0 && r > worm[player].causewait)
	{	drawcause(player, BLACK);
		worm[player].causewait = (ULONG) -1; // most future time possible
}	}

if (ice == -1)
{   if (level)
    {	// decrement orb strength
        for (which = 0; which <= CREATURES; which++)
            if (creature[which].alive && creature[which].species == ORB)
                if (creature[which].mode == ARMOUR)
                {   if (--creature[which].armour == 0)
                        if (creature[which].tongue > 0)
                            creature[which].mode = TONGUE;
                        else creature[which].mode = NULL;
                } elif (creature[which].mode == TONGUE)
                {   if (--creature[which].tongue == 0)
                        if (creature[which].armour > 0)
                            creature[which].mode = ARMOUR;
                        else creature[which].mode = NULL;
                }

        // create goats
        if
        (   (!(rand() % (atleast(GOATFREQ / level, 10))))
        &&  findempty(&x, &y, FIRSTGOAT, LASTGOAT)
        &&  field[x][y] != GOAT
        )
        {   for (i = 0; i <= CREATURES; i++)
            {   if (!creature[i].alive)
                {   effect(FXGOATBORN);
                    creature[i].x = x;
                    creature[i].y = y;
                    creature[i].alive = TRUE;
                    creature[i].species = GOAT;
                    creature[i].last = creature[i].oldlast = field[x][y];
                    creature[i].speed = (SBYTE) atleast(GOATSPEED / level, 2);
                    creature[i].visible = TRUE;
                    change(x, y, GOAT);
                    break;
        }   }   }

		// create orbs
		if
		(	(!(rand() % (atleast(ORBFREQ / level, 10))))
        &&  findempty(&x, &y, FIRSTEMPTY, LASTEMPTY)
        )
        {   for (i = 0; i <= CREATURES; i++)
			{	if (!creature[i].alive)
				{	effect(FXORBBORN);
            		creature[i].deltax = (rand() % 2) * 2 - 1;
            		creature[i].deltay = (rand() % 2) * 2 - 1;
            		creature[i].score = 0;
            		creature[i].alive = TRUE;
            		creature[i].armour = 0;
            		creature[i].tongue = 0;
            		creature[i].mode = NULL;
            		creature[i].speed = (SBYTE) atleast(ORBSPEED / level, 2);
					creature[i].species = ORB;
					creature[i].explode = FALSE;
					creature[i].multi = 1;
					creature[i].x = x;
					creature[i].y = y;
					creature[i].last = EMPTY;
					creature[i].visible = TRUE;
					change(x, y, ORB);
					break;
        }	}	}

		// create penguins
		if
		(	(!(rand() % (atleast(PENGUINFREQ / level, 10))))
		&&	findempty(&x, &y, FIRSTEMPTY, LASTEMPTY))
		{	for (i = 0; i <= CREATURES; i++)
				if (!(creature[i].alive))
				{	effect(FXPENGUINBORN);
					creature[i].x = x;
					creature[i].y = y;
					creature[i].last = EMPTY;
					creature[i].speed = (SBYTE) atleast(PENGUINSPEED / level, 2);
					creature[i].visible = TRUE;
					creature[i].deltax = 0;
					creature[i].deltay = 0;
					creature[i].species = PENGUIN;
					creature[i].alive = TRUE;
					change(x, y, PENGUIN);
					break;
		}		}

		// create slime
		if (!(rand() % (atleast(SLIMEFREQ / level, 10))))
            if (findempty(&x, &y, FIRSTEMPTY, LASTEMPTY))
                change(x, y, SLIME);

		// grow slime
		if (!(rand() % (atleast(SLIMEFREQ / level, 10))))
		{	for (x = 0; x <= FIELDX; x++)
				for (y = 0; y <= FIELDY; y++)
					if (field[x][y] == SLIME)
						for (xx = x - 1; xx <= x + 1; xx++)
							for (yy = y - 1; yy <= y + 1; yy++)
								if (valid(xx, yy) && field[xx][yy] == EMPTY && !(rand() % 2))
									field[xx][yy] = TEMPSLIME;
			for (x = 0; x <= FIELDX; x++)
				for (y = 0; y <= FIELDY; y++)
					if (field[x][y] == TEMPSLIME)
                        change(x, y, SLIME);
        }
			
		// retry putting letters if necessary

		if (noletter != -2)
			putletter(noletter);
	}

	// create drips
	if
	(	(level && (!(rand() % (atleast(DRIPFREQ / level, 10)))))
	||  ((!level) && leveltype == DRIP && (!(rand() % BONUSDRIPFREQ)))
	)
    {   x = (rand() % (FIELDX + 1));
        y = (rand() % 3);
		thissy = field[x][y];
		if (thissy >= FIRSTEMPTY && thissy <= LASTEMPTY)
		{	for (i = 0; i <= CREATURES; i++)
			{	if (!creature[i].alive)
				{	effect(FXDRIPBORN);
                	creature[i].alive	= TRUE;
                   	creature[i].last	= EMPTY;
                   	creature[i].species	= DRIP;
                   	creature[i].type	= rand() % 4;
                   	creature[i].x		= x;
					creature[i].y		= y;
					creature[i].deltax	= 0;
                   	creature[i].deltay	= 1;
                    creature[i].visible	= TRUE;
					if (level)
						creature[i].speed = (SBYTE) atleast(DRIPSPEED / level, 2);
					else creature[i].speed = BONUSDRIPSPEED;
					change(x, y, FIRSTDRIP + creature[which].type);
					break;
	}	}	}	}

	// create objects

	for (which = 0; which <= LASTOBJECT; which++)
		if (level || leveltype != TREASURE || which == TREASURE)
		{	if (!(rand() % object[which].frequency) && findempty(&x, &y, FIRSTEMPTY, LASTEMPTY))
                change(x, y, which);
        } elif (!(rand() % (object[which].frequency / 10)) && findempty(&x, &y, FIRSTEMPTY, LASTEMPTY))
            change(x, y, which);

	// create teleports

	if (!(rand() % TELFREQ)
	&& !teleport[level][2].alive
	&& findempty(&(teleport[level][2].x), &(teleport[level][2].y), FIRSTEMPTY, LASTEMPTY)
	&& findempty(&(teleport[level][3].x), &(teleport[level][3].y), FIRSTEMPTY, LASTEMPTY)
	&& (teleport[level][2].x != teleport[level][3].x || teleport[level][2].y != teleport[level][3].y))
	{	teleport[level][2].alive = TRUE;
		teleport[level][3].alive = TRUE;
        change(teleport[level][2].x, teleport[level][2].y, TELEPORT);
        change(teleport[level][3].x, teleport[level][3].y, TELEPORT);
	}

	// create timebombs

	for (i = 0; i <= level; i++)
	{	x = rand() % (FIELDX + 1);
		y = rand() % (FIELDY + 1);
		if (field[x][y] == BOMB)
		{	done = FALSE;
			for (which = 0; which <= TIMEBOMBS; which++)
				if (timebomb[which].alive == FALSE && !done)
				{	timebomb[which].alive = TRUE;
					timebomb[which].x = x;
					timebomb[which].y = y;
					timebomb[which].time = 10;
					done = TRUE;
	}	}		}

	// decrement and explode timebombs

	if (!(r % (VERYSLOW * TIMEBOMBSPEED)))
		for (which = 0; which <= TIMEBOMBS; which++)
			if (timebomb[which].alive)
			{	if (field[timebomb[which].x][timebomb[which].y] != BOMB)
					timebomb[which].alive = FALSE;
				else
				{	effect(FXTIMEBOMBTICK);
					timebomb[which].time--;
					if (timebomb[which].time < 0)
					{	timebomb[which].alive = FALSE;
                        change(timebomb[which].x, timebomb[which].y, EMPTY);
						bombblast(BOMB, 0, timebomb[which].x, timebomb[which].y);
					} else draw(timebomb[which].x, timebomb[which].y, ZERO + timebomb[which].time);
}			}	}
}

SBYTE speedup(SBYTE speed, ABOOL nitro)
{	speed /= 2;
	if (speed < FAST)
		speed = FAST;
	return(speed);
}

ULONG squareblast(SBYTE type, SBYTE player, SBYTE thissy, SBYTE x, SBYTE y)
{	SBYTE	which;
	ULONG	score = 0L;
	SBYTE	filler;

	if (type == HEAD && worm[player].bias)
		filler = SILVER;
	else filler = EMPTY;

	if (thissy <= LASTOBJECT)
    {   change(x, y, filler);
		if (thissy == BOMB)
			for (which = 0; which <= TIMEBOMBS; which++)
				if (timebomb[which].alive && timebomb[which].x == x && timebomb[which].y == y)
					timebomb[which].alive = FALSE;
	} elif (thissy >= FIRSTTAIL && thissy <= LASTTAIL)
        change(x, y, filler);
    elif (thissy == ORB)
    {   which = whichcreature(x, y, ORB, 255);
        if (creature[which].mode != ARMOUR)
        {   effect(FXORBDEATH);
            creature[which].alive = FALSE;
            score = creature[which].score;
            if (type == HEAD && worm[player].bias)
                 worm[player].lives += ORBBLOOD;
            change(x, y, BONUS);
		} else effect(FXUSEARMOUR);
	} elif (thissy >= FIRSTDRIP && thissy <= LASTDRIP)
    {   creature[whichcreature(x, y, DRIP, 255)].alive = FALSE;
        change(x, y, filler);
	} elif (thissy >= FIRSTHEAD && thissy <= LASTHEAD)
	{	if (type != HEAD || player != thissy - FIRSTHEAD)
			if (worm[thissy - FIRSTHEAD].mode != ARMOUR)
			{	worm[thissy - FIRSTHEAD].cause = BOMB;
				worm[thissy - FIRSTHEAD].alive = FALSE;
				stat(thissy - FIRSTHEAD, LIFE);
				if (type == HEAD)
					worm[thissy - FIRSTHEAD].victor = player;
				else
				{	worm[thissy - FIRSTHEAD].victor = -1;
					score = KILLWORM; // worms will get thissy bonus from death(), so it is not given for them here.
				}
			} else effect(FXUSEARMOUR);
	} elif (thissy >= FIRSTMISSILE && thissy <= LASTMISSILE)
	{	if (type != HEAD || player != thissy - FIRSTMISSILE)
        {   creature[whichcreature(x, y, MISSILE, 255)].alive = FALSE;
            change(x, y, filler);
	}	}
	elif (thissy == PENGUIN)
	{	creature[whichcreature(x, y, PENGUIN, 255)].alive = FALSE;
		change(x, y, filler);
	} elif (thissy == FRAGMENT)
    {	creature[whichcreature(x, y, FRAGMENT, 255)].alive = FALSE;
        change(x, y, filler);
    } elif (thissy == GOAT)
    {   creature[whichcreature(x, y, GOAT, 255)].alive = FALSE;
        change(x, y, BONUS);
	}
	return(score);
}

void timeloop(void)
{           TEXT  timedisplay[5] = {"#:##"};
    PERSIST ABOOL outoftime      = FALSE;
            UBYTE i;

	secondsleft = atleast(secondsleft, 0);
	timedisplay[0] = 48 +  (secondsleft / 60);
	timedisplay[2] = 48 + ((secondsleft % 60) / 10);
	timedisplay[3] = 48 + ((secondsleft % 60) % 10);

	if (!level)
	{	say(timedisplay, worm[treasurer].colour);
		if (!secondsleft)
		{	level = reallevel + 1;
	        secondsleft = SECONDSPERLEVEL;
			newlevel(treasurer);
	}	}
	elif (!secondsleft)
	{	say("Out of time!", WHITE);
		if (!outoftime)
		{   effect(FXTIMEALERT);
	            outoftime = TRUE;
                    for (i = 0; i <= CREATURES; i++)
                        if (creature[i].alive)
                            creature[i].speed = speedup(creature[i].speed, TRUE);
	}       }
        else
	{	outoftime = FALSE;
		say(timedisplay, WHITE);
}	}

void train(SCANCODE scancode)
{	SBYTE i, x, y;
    UBYTE thissy;

	switch(scancode) {
	case HELP:
		trainer = !trainer;
    break;
	case NUMERICSLASH:
		// Complete the level.
		if (trainer)
		{	trainer = FALSE;
			if (worm[1].lives > 0)
				for (i = 0; i <= LETTERS; i++)
                {   letters[1][i] = TRUE;
					drawletter(1, FIRSTLETTER + i, NORMAL);
        }       }
    break;
	case NUMERICASTERISK:
		// Change most squares to gold.
		if (trainer)
		{	trainer = FALSE;
			for (x = 0; x <= FIELDX; x++)
				for (y = 0; y <= FIELDY; y++)
					if (field[x][y] == FIRSTTAIL + 1)
                        change(x, y, METAL);
                    else
                    {   thissy = field[x][y];
                        if
                        (   thissy != STONE
                        &&  thissy != GOAT
                        &&  thissy != ORB
                        &&  thissy != SKULL
                        && (thissy < FIRSTHEAD || thissy > LASTHEAD)
                        && (thissy < FIRSTLETTER || thissy > LASTLETTER)
                        )
                            change(x, y, GOLD);
        }           }
    break;
	case NUMERICPLUS:
        /* Full lives, tongue, bias, ammo, power, nitro, affixer
        and remnants. */
		if (trainer)
		{	trainer = FALSE;
			if (worm[1].lives > 0)
			{	worm[1].lives = LIVESLIMIT;
				stat(1, LIFE);
				worm[1].tongue = MODELIMIT;
				worm[1].mode = TONGUE;
				stat(1, TONGUE);
				worm[1].bias = BIASLIMIT;
				stat(1, BIAS);
				worm[1].ammo = AMMOLIMIT;
				stat(1, AMMO);
				worm[1].power = POWERLIMIT;
				stat(1, POWER);
				worm[1].nitro = TRUE;
				stat(1, NITRO);
				worm[1].affixer = TRUE;
				worm[1].remnants = 100;
				trainer = FALSE;
		}	}
    break;
	default:
    break;
}	}

void turnworm(SBYTE player, SBYTE deltax, SBYTE deltay)
{
if (worm[player].nitro || !deltax || !deltay)
{	if (worm[player].deltax == deltax && worm[player].deltay == deltay)
	{	worm[player].speed = speedup(worm[player].speed, worm[player].nitro);
		stat(player, NITRO);
	} elif (worm[player].deltax == -deltax && worm[player].deltay == -deltay)
	{	worm[player].speed = slowdown(worm[player].speed);
		stat(player, NITRO);
	} else
	{	worm[player].deltax = deltax;
		worm[player].deltay = deltay;
}	}
}

void updatesquare(SBYTE x, SBYTE y)
{	SBYTE	which;

	if (startx[level] == x && starty[level] == y)
		draw(x, y, START);
	elif (board[level][x][y] == TELEPORT)
	{	for (which = 0; which <= 1; which++)
			if (teleport[level][which].alive && teleport[level][which].x == x && teleport[level][which].y == y)
				draw(x, y, ONE + which);
	} else draw(x, y, board[level][x][y]);
}

SBYTE valid(SBYTE x, SBYTE y)
{	if (x >= 0 && x <= FIELDX && y >= 0 && y <= FIELDY)
		return(TRUE);
	else
		return(FALSE);
}

UBYTE whichcreature(SBYTE x, SBYTE y, UBYTE species, UBYTE exception)
{   UBYTE i;	

    for (i = 0; i <= CREATURES; i++)
        if
		(   creature[i].alive
         && creature[i].x == x
         && creature[i].y == y
         && creature[i].species == species
		 && i != exception
		)
            return i;
    return 255; // error code
}
SBYTE whichteleport(SBYTE x, SBYTE y)
{   SBYTE which;

    for (which = 0; which <= TIMEBOMBS; which++)
        if (teleport[level][which].alive && teleport[level][which].x == x && teleport[level][which].y == y)
            return((SBYTE) which);
    return((SBYTE) -1); // error code
}
SBYTE whichtimebomb(SBYTE x, SBYTE y)
{   SBYTE which;

    for (which = 0; which <= TIMEBOMBS; which++)
        if (timebomb[which].alive && timebomb[which].x == x && timebomb[which].y == y)
            return(which);
    return(-1); // error code
}

void wormbullet(SBYTE player)
{	ABOOL	finished,
			flag		= FALSE,
			remnants	= FALSE,
			ok			= FALSE,
			lettered	= FALSE;
    LONG	score;
    SBYTE	distance,
			thissy,
			i, j,
			x, y;

	if (!worm[player].ammo)
	{	stat(player, BONUS);
		if (worm[player].speed == FAST)
			distance = FASTDISTANCE;
		elif (worm[player].speed == NORMAL)
			distance = NORMALDISTANCE;
		else
		{	// assert(worm[player].speed == SLOW);
			distance = SLOWDISTANCE;
		}

		// check for metal

		for (i = 1; i < distance; i++)
		{	x = xwrap(worm[player].x + (i * worm[player].deltax));
	        y = ywrap(worm[player].y + (i * worm[player].deltay));
			if (field[x][y] == METAL)
				flag = TRUE;
		}

		if (!flag)
		{	// assert(abs(worm[player].deltax) == 1 && abs(worm[player].deltay) == 1);
			x = xwrap(worm[player].x + worm[player].deltax * distance);
			y = ywrap(worm[player].y + worm[player].deltay * distance);
			thissy = field[x][y];
			if (thissy == TELEPORT)
			{   i = whichteleport(x, y);
				if (!blocked(i, worm[player].deltax, worm[player].deltay))
			        ok = TRUE;
			}
            if (ok || ((thissy < STONE || thissy > GOAT) && thissy != METAL))
			{	// Amiga-worms only make jumping sounds in demo mode
				flag = FALSE;
                for (i = 0; i <= 3; i++)
					if (worm[i].control == HUMAN)
						flag = TRUE;
				if (!flag || worm[player].control == HUMAN)
					effect(FXJUMP);
				worm[player].deltax *= distance;
				worm[player].deltay *= distance;
	}	}	}
	else
	{	effect(FXSHOOT);
		worm[player].ammo--;
		stat(player, AMMO);
		if (worm[player].remnants)
		{	worm[player].remnants--;
			remnants = TRUE;
		}
        if (worm[player].bias)
            createmissile(player);

		for (i = 0; i <= worm[player].power; i++)
		{	bullet[i].alive			= TRUE;
			bullet[i].teleported	= 0;
			bullet[i].visible		= TRUE;
			bullet[i].reflected		= FALSE;
			if (i % 2 == 0)
				distance = i / 2;
			else
				distance = -((i + 1) / 2);
			if (worm[player].deltax == 0)
			{	bullet[i].x = worm[player].x + distance;
				bullet[i].y = worm[player].y;
            } elif (worm[player].deltay == 0)
			{	bullet[i].x = worm[player].x;
				bullet[i].y = worm[player].y + distance;
            } else
			{	switch (i) {
	            case 0:
                   bullet[i].x = worm[player].x + worm[player].deltax;
                   bullet[i].y = worm[player].y + worm[player].deltay;
                   break;
				case 1:
                   bullet[i].x = worm[player].x + worm[player].deltax;
                   bullet[i].y = worm[player].y;
                   break;
				case 2:
                   bullet[i].x = worm[player].x;
                   bullet[i].y = worm[player].y + worm[player].deltay;
                   break;
                case 3:
                   bullet[i].x = worm[player].x + worm[player].deltax * 2;
                   bullet[i].y = worm[player].y;
                   break;
                case 4:
                   bullet[i].x = worm[player].x;
                   bullet[i].y = worm[player].y + worm[player].deltay * 2;
                   break;
                case 5:
                   bullet[i].x = worm[player].x + worm[player].deltax * 2;
                   bullet[i].y = worm[player].y - worm[player].deltay;
                   break;
                case 6:
                   bullet[i].x = worm[player].x - worm[player].deltax;
                   bullet[i].y = worm[player].y + worm[player].deltay * 2;
                   break;
                default:
                   break;
			}	}
           	if
			(	(!(valid(bullet[i].x, bullet[i].y)))
			||	field[bullet[i].x][bullet[i].y] == METAL
			||	field[bullet[i].x][bullet[i].y] == STONE
			||	(field[bullet[i].x][bullet[i].y] > FIRSTLETTER && field[bullet[i].x][bullet[i].y] < LASTLETTER)
            )
				bullet[i].alive = FALSE;
				/* That is somewhat kludgy. It is designed to prevent
				players from being able to destroy metal, etc. by
				firing at point-blank range. What really should be
				done, however, is to organize wormbullet() so that
				things are done in the same order as most of the
				other loops are. Note that theoretically the bullet
				would bounce back from metal and strike the firer.
				This is not handled by this kludge code. */
		}
		score = 0L;
		finished = FALSE;
        while (!finished)
		{	finished = TRUE;
			for (i = 0; i <= worm[player].power; i++)
            {   if (bullet[i].alive)
				{	finished = FALSE;
                    if (bullet[i].visible && (!remnants))
					{	if (bullet[i].teleported)
                            change(bullet[i].x, bullet[i].y, GOLD);
                        else change(bullet[i].x, bullet[i].y, EMPTY);
                    }
                    else bullet[i].visible = TRUE;
					if (bullet[i].reflected)
					{	bullet[i].x -= worm[player].deltax;
						bullet[i].y -= worm[player].deltay;
					} else
					{	bullet[i].x += worm[player].deltax;
						bullet[i].y += worm[player].deltay;
					}
					if (!(valid(bullet[i].x, bullet[i].y)))
						bullet[i].alive = FALSE;
					else if (bullet[i].x == worm[player].x && bullet[i].y == worm[player].y)
					{	// hit by own bullet
						bullet[i].alive = FALSE;
						if (worm[player].mode != ARMOUR)
						{	worm[player].cause = FIRSTFIRE + player;
							worm[player].victor = -1;
							worm[player].alive = FALSE;
					}	}
					else
                    {   x = bullet[i].x;
                        y = bullet[i].y;
                        thissy = field[x][y];
						if (thissy >= FIRSTHEAD && thissy <= LASTHEAD)
						{	if (worm[thissy - FIRSTHEAD].mode != ARMOUR)
							{	worm[thissy - FIRSTHEAD].cause = FIRSTFIRE + player;
								worm[thissy - FIRSTHEAD].victor = player;
								worm[thissy - FIRSTHEAD].alive = FALSE;
								if (player != thissy - FIRSTHEAD)
									score += KILLWORM + HITSHOT;
							} else effect(FXUSEARMOUR);
							bullet[i].alive = FALSE;
						} elif (thissy >= FIRSTPROTECTOR && thissy <= LASTPROTECTOR)
						{	if (player != thissy - FIRSTPROTECTOR)
							{	effect(FXUSEPROTECTOR);
								bullet[i].alive = FALSE;
							} else
								bullet[i].visible = FALSE;
                        } elif (thissy >= FIRSTMISSILE && thissy <= LASTMISSILE)
						{	if (player != thissy - FIRSTMISSILE)
                                creature[whichcreature(x, y, MISSILE, 255)].alive = FALSE;
                            else bullet[i].visible = FALSE;
						} elif (thissy >= FIRSTDRIP && thissy <= LASTDRIP)
                        {   j = whichcreature(x, y, DRIP, 255);
                            creature[j].alive = FALSE;
                        } elif (thissy >= FIRSTLETTER && thissy <= LASTLETTER)
						{	wormletter(player, thissy);
							lettered = TRUE;
                        } else
						{	switch(thissy) {
							case METAL:
								if (bullet[i].reflected)
									bullet[i].alive = FALSE;
								else
								{	bullet[i].reflected = TRUE;
									bullet[i].x -= worm[player].deltax;
									bullet[i].y -= worm[player].deltay;
								}
								break;
							case STONE:
								bullet[i].alive = FALSE;
								break;
							case ORB:
								bullet[i].alive = FALSE;
                                j = whichcreature(x, y, ORB, 255);
                                if (creature[j].mode != ARMOUR)
                                {   creature[j].explode = TRUE;
                                    score += creature[j].score + HITSHOT;
                                    if (worm[player].bias)
                                        worm[player].lives += ORBBLOOD;
                                } else effect(FXUSEARMOUR);
								break;
							case TELEPORT:
								j = whichteleport(bullet[i].x, bullet[i].y);
								if (bullet[i].teleported == 2)
									bullet[i].alive = FALSE;
								else
								{   effect(FXUSETELEPORT);
									bullet[i].visible = FALSE;
									bullet[i].teleported++;
									bullet[i].x = teleport[level][partner(j)].x;
									bullet[i].y = teleport[level][partner(j)].y;
									score += TELPOINT;
								}
								break;
	                        case WOOD:
	                            if (!worm[player].bias)
	                                bullet[i].alive = FALSE;
	                            break;
                            case GOAT:
                                creature[whichcreature(x, y, GOAT, 255)].alive = FALSE;
                                change(x, y, BONUS);
                                score += KILLGOAT + HITSHOT;
                                if (worm[player].bias)
                                    worm[player].lives += GOATBLOOD;
       	                        bullet[i].alive = FALSE;
       	                        break;
       						case SKULL:
       							bullet[i].alive = FALSE;
       						break;
       						case FRAGMENT:
       						    bullet[i].alive = FALSE;
                                                    creature[whichcreature(x, y, FRAGMENT, 255)].alive = FALSE;
                                                    change(x, y, EMPTY);
       						break;
       						case BOMB:
                                j = whichtimebomb(x, y);
       							if (j != -1)
       							{   bullet[i].alive = FALSE;
       							    timebomb[j].alive = FALSE;
                                    change(timebomb[j].x, timebomb[j].y, EMPTY);
       								bombblast(HEAD, player, timebomb[j].x, timebomb[j].y);
       							}
       						break;
       						case PENGUIN:
								bullet[i].alive = FALSE;
								creature[whichcreature(x, y, PENGUIN, 255)].alive = FALSE;
								change(x, y, EMPTY);
								score += KILLPENGUIN + HITSHOT;
								break;
							default:
       							break;
							}
           	        		if (bullet[i].alive && bullet[i].visible)
                            {   draw(x, y, FIRSTFIRE + player);
								if (remnants)
                                    field[x][y] = FIRSTFIRE + player;
		}	}	}	}	}	}
		if (lettered)
			putletter(player);
		wormscore(player, score);
		if (worm[player].bias)
			stat(player, LIFE);
		clearkybd();
}	}

void wormletter(SBYTE player, SBYTE thissy)
{	if (thissy == GREEN_C)
		effect(FX_C);
	elif (thissy == RED_O)
		effect(FX_O);
	elif (thissy == BLUE_M)
		effect(FX_M);
	elif (thissy == YELLOW_P)
		effect(FX_P);
	elif (thissy == GREEN_L)
		effect(FX_L);
	elif (thissy == BLUE_T)
		effect(FX_T);
	else // assumes RED_E or YELLOW_E
		effect(FX_E);
	letters[player][thissy - FIRSTLETTER] = TRUE;
       drawletter(player, thissy, NORMAL);
    if (player == (thissy - FIRSTLETTER) % 4)
        wormscore(player, MYLETTER);
	else wormscore(player, YOURLETTER);
}

/* NAME     wormloop -- controls worms and protectors
SYNOPSIS    wormloop(SBYTE);
FUNCTION    Amiga-worm thinking, processing one keystroke from the
            worm's queue, all the worm's protectors' control, the
            worm's control.
MODULE      engine.c */

void wormloop(SBYTE player)
{		ABOOL	done, flag;
		SBYTE	bestgood, bestx, besty, animal, dirx, diry,
				good, i, ithis, iwhich, thissy, thisletter,
				thisprot = -1, which, x, xx, y, yy;
		LONG    score = 0L;
PERSIST	SBYTE	otherfield[FIELDX + 1][FIELDY + 1];

	/* AI: Amiga worm control

	Worm checks ahead, left and right one square. Assigns opinions to those
	three choices and then takes the appropriate one.
	
	Possible enhancements:
		recognition of possession of objects
			(esp. ammo, nitro, tongue, armour)
		longer lookahead */

	if (worm[player].control == AMIGA)
	{	if (!(rand() % 50))
			queue(player, (rand() % 3) - 1, (rand() % 3) - 1);
		else
		{	bestgood = -128;

			for (i = 0; i <= 2; i++)
			{	switch(i) {
				case 0:
					dirx = worm[player].deltax;
					diry = worm[player].deltay;
					break;
				case 1:
					if (worm[player].deltax == 0)	// if going north or south
					{	dirx = -1;					// then look west
						diry = 0;
					} else							// if going east or west
					{	dirx = 0;					// then look north
						diry = -1;
					}
					break;
				case 2:
					if (worm[player].deltax == 0)	// if going north or south
					{	dirx = 1;					// then look east
						diry = 0;
					} else							// if going east or west
					{	dirx = 0;					// then look south
						diry = 1;
					}
					break;
				default:
					break;
				}
				thissy = field[xwrap(worm[player].x + dirx)][ywrap(worm[player].y + diry)];
				if (thissy >= FIRSTLETTER && thissy <= LASTLETTER)
					good = 100;
				elif (thissy <= LASTOBJECT)
					good = object[thissy].score;
				elif (thissy == FIRSTPROTECTOR + player)
					good = 6;
				elif (thissy == FIRSTFIRE + player)
					good = 5;
				elif (thissy >= FIRSTTAIL && thissy <= LASTTAIL)
				{	if (player == thissy - FIRSTTAIL)
						good = -30;
					else good = -60;
				} else switch(thissy) {
				case SKULL:
					good = 70;
					break;
				case GOLD:
					good = 50;
					break;
				case SILVER:
					good = 40;
					break;
				case EMPTY:
					good = 10;
					break;
				case WOOD:
					good = -80;
					break;
				case STONE:
					good = -90;
					break;
				case METAL:
					good = -90;
					break;
				case GOAT:
					good = -100;
					break;
				default:
					good = -50; // slime, heads, orbs, etc.
					break;
				}
				if (good > bestgood)
				{	bestx = dirx;
					besty = diry;
					bestgood = good;
			}	}

			if (bestgood <= -60)
				queue(player, 0, 0);
			elif (bestgood < 0 && (!(rand() % 2)))
				queue(player, 0, 0);
			elif (bestx != worm[player].deltax || besty != worm[player].deltay)
				queue(player, bestx, besty);
	}	}

// remove a keystroke from the queue

if (worm[player].pos != -1)
{   if (thequeue[player][0].deltax == 0 && thequeue[player][0].deltay == 0)
		wormbullet(player);
    else turnworm(player, thequeue[player][0].deltax, thequeue[player][0].deltay);
    if (--worm[player].pos != -1)
        for (i = 0; i <= worm[player].pos; i++)
        {   thequeue[player][i].deltax = thequeue[player][i + 1].deltax;
            thequeue[player][i].deltay = thequeue[player][i + 1].deltay;
}       }

// move worm

change(worm[player].x, worm[player].y, worm[player].last);
worm[player].x = xwrap(worm[player].x + worm[player].deltax);
worm[player].y = ywrap(worm[player].y + worm[player].deltay);
worm[player].deltax = bsign(worm[player].deltax);
worm[player].deltay = bsign(worm[player].deltay);
worm[player].last = FIRSTTAIL + player;

// move protectors

for (which = 0; which <= PROTECTORS; which++)
{   if (protector[player][which].alive)
    {   if (protector[player][which].visible)
            change(protector[player][which].x, protector[player][which].y, protector[player][which].last);
        else protector[player][which].visible = TRUE;
		protector[player][which].last = EMPTY;

		if (which == NOSE)
		{	protector[player][which].relx = worm[player].deltax * NOSEDISTANCE;
			protector[player][which].rely = worm[player].deltay * NOSEDISTANCE;
			if (!worm[player].affixer)
			{	worm[player].position = 0;
				if (worm[player].deltax == 0)
					protector[player][which].relx = worm[player].position;
				elif (worm[player].deltay == 0)
					protector[player][which].rely = worm[player].position;
				elif (worm[player].position == -1)
					protector[player][which].relx = worm[player].deltax * (NOSEDISTANCE - 1);
				elif (worm[player].position == 1)
					protector[player][which].rely = worm[player].deltay * (NOSEDISTANCE - 1);
		}	}
		elif (!worm[player].affixer)
		{	if (protector[player][which].relx == 1 && protector[player][which].rely == -1)
			{	protector[player][which].deltax = 0;
				protector[player][which].deltay = 1;
			} elif (protector[player][which].relx == 1 && protector[player][which].rely == 1)
			{	protector[player][which].deltax = -1;
				protector[player][which].deltay = 0;
			} elif (protector[player][which].relx == -1 && protector[player][which].rely == 1)
			{	protector[player][which].deltax = 0;
				protector[player][which].deltay = -1;
			} elif (protector[player][which].relx == -1 && protector[player][which].rely == -1)
			{	protector[player][which].deltax = 1;
				protector[player][which].deltay = 0;
			}
			protector[player][which].relx += protector[player][which].deltax;
			protector[player][which].rely += protector[player][which].deltay;
		}
		protector[player][which].x = worm[player].x + protector[player][which].relx;
		protector[player][which].y = worm[player].y + protector[player][which].rely;
		if (!valid(protector[player][which].x, protector[player][which].y))
			protector[player][which].visible = FALSE;
}	}

// collision detection

animal = HEAD;
while (animal != NULL)
{   if (animal == HEAD)
    {   x = worm[player].x;
		y = worm[player].y;
		thissy = field[x][y];
        if (thissy >= FIRSTHEAD && thissy <= LASTHEAD)
        {   if (worm[player].mode != TONGUE)
				if (worm[thissy - FIRSTHEAD].mode != TONGUE)
				{	// both worms die
					worm[player].cause				= thissy;
					worm[player].alive				= FALSE;
					worm[player].victor				= -1;
					worm[thissy - FIRSTHEAD].cause	= FIRSTHEAD + player;
					worm[thissy - FIRSTHEAD].alive	= FALSE;
					worm[thissy - FIRSTHEAD].victor	= -1;
				} else
				{	// this worm dies
					worm[player].cause				= thissy;
					worm[player].alive				= FALSE;
					worm[player].victor				= thissy - FIRSTHEAD;
				}
            elif (worm[thissy - FIRSTHEAD].mode != TONGUE)
			{	// other worm dies
				worm[thissy - FIRSTHEAD].cause		= FIRSTHEAD + player;
				worm[thissy - FIRSTHEAD].alive		= FALSE;
				worm[thissy - FIRSTHEAD].victor		= player;
		}	}
        elif (thissy >= FIRSTTAIL && thissy <= LASTTAIL)
        {   if (worm[player].mode == TONGUE)
            {	effect(FXUSETONGUE);
				if (players == 1)
					if (player == thissy - FIRSTTAIL)
                	{   score += TURNTOSILVER;
                	    worm[player].last = SILVER;
                	} else
                	{   score += TURNTOGOLD;
                	    worm[player].last = GOLD;
            }   	}
            else
            {   worm[player].cause = thissy;
                worm[player].alive = FALSE;
                worm[player].victor = thissy - FIRSTTAIL;
        }   }
        elif (thissy >= FIRSTPROTECTOR && thissy <= LASTPROTECTOR)
        	if (worm[player].mode != ARMOUR)
        	{   worm[player].cause = thissy;
        	    worm[player].victor = thissy - FIRSTPROTECTOR;
        	    worm[player].alive = FALSE;
        	} else effect(FXUSEARMOUR);
        elif (thissy >= FIRSTMISSILE && thissy <= LASTMISSILE)
        {   if (player != thissy - FIRSTMISSILE)
            {   creature[whichcreature(x, y, MISSILE, 255)].alive = FALSE;
                if (worm[player].mode != ARMOUR)
                {   worm[player].alive = FALSE;
                    worm[player].cause = thissy;
                    worm[player].victor = thissy - FIRSTMISSILE;
                } else effect(FXUSEARMOUR);
        }   }
		elif (thissy >= FIRSTDRIP && thissy <= LASTDRIP)
        {   creature[whichcreature(x, y, DRIP, 255)].alive = FALSE;
			if (player == thissy - FIRSTDRIP || worm[player].bias)
			{	effect(FXDRIP);
				wormscore(player, DRIPBONUS);
			} else
			{	worm[player].alive = FALSE;
				worm[player].cause = thissy;
				worm[player].victor = -1;
			}
        } elif (thissy == STONE || thissy == GOAT || thissy == METAL)
        {   worm[player].cause = thissy;
            worm[player].victor = -1;
            worm[player].alive = FALSE;
            ramming(player);
        } elif (thissy == WOOD)
        {   if (worm[player].mode != ARMOUR)
            {   worm[player].cause = WOOD;
                worm[player].alive = FALSE;
                worm[player].victor = -1;
        }   }
		elif (thissy == SLIME)
		{	if (worm[player].mode != ARMOUR)
			{	worm[player].cause = SLIME;
				worm[player].alive = FALSE;
				worm[player].victor = -1;
		}	}
        elif (thissy == PENGUIN)
		{	effect(FXPENGUINDEATH);
			creature[whichcreature(x, y, PENGUIN, 255)].alive = FALSE;
			if (worm[player].armour)
			{	score += KILLPENGUIN;
				if (worm[player].bias)
				{	worm[player].lives += PENGUINBLOOD;
					stat(player, LIFE);
			}	}
			else
			{	worm[player].alive = FALSE;
				worm[player].cause = PENGUIN;
				worm[player].victor = 1;
		}	}
		elif (thissy == TELEPORT)
        {   which = whichteleport(x, y);
            if (blocked(which, worm[player].deltax, worm[player].deltay))
            {   worm[player].cause = TELEPORT;
                worm[player].victor = -1;
                worm[player].alive = FALSE;
				ramming(player);
			} else
            {   effect(FXUSETELEPORT);
                score += TELPOINT;
                worm[player].x = xwrap(teleport[level][partner(which)].x + worm[player].deltax);
                worm[player].y = ywrap(teleport[level][partner(which)].y + worm[player].deltay);
		}	}
		elif (thissy >= FIRSTFIRE && thissy <= LASTFIRE)
		{	if (player != thissy - FIRSTFIRE && worm[player].mode != ARMOUR)
			{	worm[player].cause = REMNANTS;
				worm[player].victor = thissy - FIRSTFIRE;
				worm[player].alive = FALSE;
    }   }   }
	else // animal == PROTECTOR
    {   x = protector[player][thisprot].x;
		y = protector[player][thisprot].y;
		thissy = field[x][y];
		if (thissy >= FIRSTHEAD && thissy <= LASTHEAD)
        {   if (player != thissy - FIRSTHEAD)
			{	if (worm[thissy - FIRSTHEAD].mode != ARMOUR)
            	{   effect(FXUSEPROTECTOR);
					worm[thissy - FIRSTHEAD].cause = FIRSTPROTECTOR + player;
            	    worm[thissy - FIRSTHEAD].alive = FALSE;
            	    worm[thissy - FIRSTHEAD].victor = player;
            	} else
            	{   effect(FXUSEARMOUR);
            	    protector[player][thisprot].visible = FALSE;
            }	}
			else // protector is over worm's own head; caused by ramming
				protector[player][thisprot].visible = FALSE;
        } elif (thissy >= FIRSTTAIL && thissy <= LASTTAIL)
        {   if (player == thissy - FIRSTTAIL || worm[player].mode == TONGUE)
                protector[player][thisprot].visible = FALSE;
        } elif (thissy >= FIRSTPROTECTOR && thissy <= LASTPROTECTOR)
        {   protector[player][thisprot].alive = FALSE;
        	for (which = 0; which <= PROTECTORS; which++)
            if (protector[thissy - FIRSTPROTECTOR][which].alive && protector[thissy - FIRSTPROTECTOR][which].x == x && protector[thissy - FIRSTPROTECTOR][which].y == y)
            {   protector[thissy - FIRSTPROTECTOR][which].alive = FALSE;
                break;
            }
            change(x, y, EMPTY);
        } elif (thissy == STONE || thissy == WOOD || thissy == METAL)
            protector[player][thisprot].visible = FALSE;
        elif (thissy >= FIRSTMISSILE && thissy <= LASTMISSILE)
        {   if (player != thissy - FIRSTMISSILE)
            {   effect(FXUSEPROTECTOR);
                creature[whichcreature(x, y, MISSILE, 255)].alive = FALSE;
            } else protector[player][thisprot].visible = FALSE;
        } elif (thissy == TELEPORT)
        	protector[player][thisprot].visible = FALSE;
		elif (thissy >= FIRSTDRIP && thissy <= LASTDRIP)
        {   creature[whichcreature(x, y, DRIP, 255)].alive = FALSE;
			if (player == thissy - FIRSTDRIP || worm[player].bias)
            {   effect(FXDRIP);
                score += DRIPBONUS;
		}	}
        elif (thissy == PENGUIN)
		{	effect(FXUSEPROTECTOR);
			effect(FXPENGUINDEATH);
			creature[whichcreature(x, y, PENGUIN, 255)].alive = FALSE;
			score += KILLPENGUIN;
			if (worm[player].bias)
			{	worm[player].lives += PENGUINBLOOD;
				stat(player, LIFE);
		}	}
		elif (thissy == GOAT)
        {   effect(FXUSEPROTECTOR);
            effect(FXGOATDEATH);
            creature[whichcreature(x, y, GOAT, 255)].alive = FALSE;
            protector[player][thisprot].last = BONUS;
            score += KILLGOAT;
            if (worm[player].bias)
            {   worm[player].lives += GOATBLOOD;
                stat(player, LIFE);
    }   }   }
    if (thissy >= FIRSTLETTER && thissy <= LASTLETTER)
    {	wormletter(player, thissy);
		putletter(player);
    } elif (thissy <= LASTOBJECT)
    {   score += object[thissy].score;
        if (thissy != SLAYER
		 && thissy != BOMB
		 && thissy != MISSILE
		 && thissy != NITRO
		 && thissy != POWER
		 && thissy != AMMO)
            effect(FXGETOBJECT);
        switch(thissy)
		{
        case BONUS:
            thisletter = rand() % (LETTERS + 1);
			letters[player][thisletter] = TRUE;
			drawletter(player, FIRSTLETTER + thisletter, NORMAL);
        break;
		case AMMO:
			effect(FXGETAMMO);
			worm[player].ammo += (rand() % 5) + 2; // 2-6 bullets
			stat(player, AMMO);
			break;
		case ARMOUR:
			/* FXUSEDRILL sample will automatically stop, because
			a square with ARMOUR is not a square with TAIL. */

			worm[player].armour += MODEADD + (rand() % MODERAND);
			worm[player].mode = ARMOUR;
			stat(player, ARMOUR);
			break;
		case TONGUE:
			worm[player].tongue += MODEADD + (rand() % MODERAND);
			worm[player].mode = TONGUE;
			stat(player, TONGUE);
            worm[player].last = FIRSTTAIL + player;
			break;
		case NITRO:
			effect(FXGETNITRO);
			worm[player].nitro = TRUE;
			stat(player, NITRO);
			break;
		case BOMB:
			flag = FALSE;
            which = whichtimebomb(x, y);
            if (which != -1)
            {   flag = TRUE;
                if (animal == HEAD)
                {   // push timebomb

                    if (valid(x + worm[player].deltax, y + worm[player].deltay))
                    {   ithis = field[x + worm[player].deltax][y + worm[player].deltay];
                        if (ithis == TELEPORT)
                            score += BOMBOVEREDGE;
                        elif (ithis == SKULL)
                        {	effect(FXGETSKULL);
							score += SKULLPOINT;
                        } elif (ithis <= LASTEMPTY)
                        {   if (ithis <= LASTOBJECT)
                            {   score += object[thissy].score;
                                if (ithis == BOMB)
                                {   iwhich = whichtimebomb(x + worm[player].deltax, y + worm[player].deltay);
                                    if (iwhich != -1)
                                        timebomb[iwhich].alive = FALSE;
                            }   }                                        
                            timebomb[which].x += worm[player].deltax;
                            timebomb[which].y += worm[player].deltay;
                            field[timebomb[which].x][timebomb[which].y] = BOMB;
                            draw(timebomb[which].x, timebomb[which].y, ZERO + timebomb[which].time);
                        } else flag = FALSE;
                    } else score += BOMBOVEREDGE;
                } else protector[player][thisprot].visible = FALSE;
            }
            if (!flag)
            {   if (worm[player].mode == NULL)
					draw(worm[player].x, worm[player].y, eachworm[player][0][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]);
				else draw(worm[player].x, worm[player].y, eachworm[player][1][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]);
				bombblast(HEAD, player, worm[player].x, worm[player].y);
            }
            break;
		case POWER:
			effect(FXGETPOWERUP);
			if (worm[player].power < POWERLIMIT)
			{	worm[player].power += 2;
				stat(player, POWER);
			}
			break;
		case SLAYER:
            for (which = 0; which <= CREATURES; which++)
                if (creature[which].alive)
                {   if (creature[which].species == ORB)
                    {   effect(FXORBDEATH);
			score += creature[which].score;
                        creature[which].explode = TRUE;
                        if (worm[player].bias)
                            worm[player].lives += ORBBLOOD;
                    } elif (creature[which].species == GOAT)
                    {   effect(FXGOATDEATH);
                        creature[which].alive = FALSE;
                        score += KILLGOAT;
                        if (worm[player].bias)
                            worm[player].lives += GOATBLOOD;
                        change(creature[which].x, creature[which].y, BONUS);
                    } elif (creature[which].species == DRIP)
                    {   creature[which].alive = FALSE;
                        change(creature[which].x, creature[which].y, EMPTY);
		    } elif (creature[which].species == MISSILE && creature[which].type != player)
		    {	creature[which].alive = FALSE;
			change(creature[which].x, creature[which].y, EMPTY);
                }   }
            for (which = 0; which <= 3; which++)
				if (player != which && worm[which].mode != ARMOUR)
				{	worm[which].alive = FALSE;
					worm[which].cause = SLAYER;
					worm[which].victor = player;
				}
			for (x = 0; x <= FIELDX; x++)
				for (y = 0; y <= FIELDY; y++)
					if (field[x][y] == SLIME)
                        change(x, y, EMPTY);
			if (worm[player].bias)
				stat(player, LIFE);
			break;
		case PROTECTOR:
			done = FALSE;
			for (which = 0; which <= PROTECTORS; which++)
                if (!protector[player][which].alive && !done)
				{	do
					{	protector[player][which].relx = ((rand() % 2) * 2) - 1;
						protector[player][which].rely = ((rand() % 2) * 2) - 1;
						for (iwhich = 0; iwhich <= PROTECTORS; iwhich++)
							if (which == NOSE || !protector[player][iwhich].alive || protector[player][iwhich].x != xwrap(worm[player].x + protector[player][which].relx) || protector[player][iwhich].y != ywrap(worm[player].y + protector[player][which].rely))
							{	effect(FXPROTECTORBORN);
								done = TRUE;
								protector[player][which].alive = TRUE;
								protector[player][which].visible = FALSE;
								protector[player][which].last = EMPTY;
								if (which == NOSE)
									worm[player].position = -1;
							}
                    } while (!done);
				}
			break;
		case MISSILE:
            createmissile(player);
        break;
		case LIFE:
			worm[player].lives += (rand() % 5) + 2; // 2-6 lives
			stat(player, LIFE);
			break;
		case MULTIPLIER:
			if (worm[player].multi < MULTILIMIT)
				worm[player].multi *= 2;
			break;
		case BIAS:
			worm[player].bias += MODEADD + (rand() % MODERAND);
			stat(player, BIAS);
			break;
		case ICE:
			worm[player].ice += ICEADD + (rand() % ICERAND);
			ice = player;
			break;
		case GROWER:
			effect(FXGETGROWER);

			// grow tail

			if (worm[player].bias)
			{	for (x = 0; x <= FIELDX; x++)
					for (y = 0; y <= FIELDY; y++)
						if (field[x][y] == FIRSTTAIL + player)
							for (xx = x - 1; xx <= x + 1; xx++)
								for (yy = y - 1; yy <= y + 1; yy++)
									if (valid(xx, yy))
							if (field[xx][yy] == EMPTY)
								field[xx][yy] = TEMPTAIL;
			}

			// grow silver

			for (x = 0; x <= FIELDX; x++)
				for (y = 0; y <= FIELDY; y++)
					if (field[x][y] == SILVER)
						for (xx = x - 1; xx <= x + 1; xx++)
							for (yy = y - 1; yy <= y + 1; yy++)
								if (valid(xx, yy))
						if (field[xx][yy] == EMPTY || field[xx][yy] == TEMPTAIL)
							field[xx][yy] = TEMPSILVER;

			// grow gold

			for (x = 0; x <= FIELDX; x++)
				for (y = 0; y <= FIELDY; y++)
					if (field[x][y] == GOLD)
						for (xx = x - 1; xx <= x + 1; xx++)
							for (yy = y - 1; yy <= y + 1; yy++)
								if (valid(xx, yy))
						if (field[xx][yy] == EMPTY || field[xx][yy] == TEMPTAIL || field[xx][yy] == TEMPSILVER)
							field[xx][yy] = TEMPGOLD;

			// update

			for (x = 0; x <= FIELDX; x++)
				for (y = 0; y <= FIELDY; y++)
					switch (field[x][y])
					{
					case TEMPGOLD:
                        change(x, y, GOLD);
						break;
					case TEMPSILVER:
                        change(x, y, SILVER);
						break;
					case TEMPTAIL:
                        change(x, y, FIRSTTAIL + player);
						break;
					default:
						break;
					}
			break;
		case TREASURE:
			treasurer = player;
			if (level)
			{	secondsperlevel = 0;
				leveltype = rand() % 2;
				if (leveltype == 0 || worm[player].bias)
				{	say("Bonus Level: Treasury!", worm[treasurer].colour);
					leveltype = TREASURE;
				} else
				{	say("Bonus Level: Drips!", worm[treasurer].colour);
					leveltype = DRIP;
			}	}
			secondsperlevel += TREASUREADD + (rand() % TREASURERAND);
			if (level)
			{	stat(player, BONUS);
				reallevel = level;
				level = 0;
                newlevel(player);
			}
			break;
		case AFFIXER:
			worm[player].affixer = TRUE;
			break;
		case SWITCHER:
			if (players >= 2)
				for (x = 0; x <= FIELDX; x++)
					for (y = 0; y <= FIELDY; y++)
						if (field[x][y] >= FIRSTTAIL && field[x][y] <= LASTTAIL && field[x][y] != FIRSTTAIL + player)
                            change(x, y, FIRSTTAIL + player);
                        elif (worm[player].bias && field[x][y] >= FIRSTFIRE && field[x][y] <= LASTFIRE && field[x][y] != FIRSTFIRE + player)
                            change(x, y, FIRSTFIRE + player);
			break;
		case HEALER:
			if (worm[player].lives < 100)
				worm[player].lives = 100;
			else worm[player].lives = LIVESLIMIT;
			stat(player, LIFE);
			break;
		case UMBRELLA:
			level += (rand() % 2) + 1;
			if (level >= levels)
				level = level - 1;
			for (i = 0; i <= LETTERS; i++)
				letters[player][i] = TRUE;
			break;
		case CLOCK:
			if (secondsleft)
				secondsperlevel += (rand() % CLOCKRAND) + CLOCKADD;
			break;
		case SLOWER:
            for (which = 0; which <= CREATURES; which++)
                if (creature[which].alive && creature[which].species != MISSILE)
                    creature[which].speed = (SBYTE) atleast(creature[which].speed * 2, VERYFAST);
			break;
		case PULSE:
			explosion(		xwrap(worm[player].x - worm[player].deltax),
						ywrap(worm[player].y - worm[player].deltay),
						worm[player].deltax,
						worm[player].deltay
			         );
			break;
		case REMNANTS:
			worm[player].remnants += (rand() % 5) + 2; // 2-6 remnants
			break;
        case LIGHTNING:
			for (x = 0; x <= FIELDX; x++)
				for (y = 0; y <= FIELDY; y++)
					otherfield[x][y] = EMPTY;
			for (x = 0; x <= FIELDX; x++)
				for (y = 0; y <= FIELDY; y++)
					if (field[x][y] == FIRSTTAIL + player)
						for (xx = x - 1; xx <= x + 1; xx++)
							for (yy = y - 1; yy <= y + 1; yy++)
								if
								(	valid(xx, yy)
								 && (field[xx][yy] < FIRSTLETTER || field[xx][yy] > LASTLETTER)
								 && field[xx][yy] != METAL
								 && field[xx][yy] != STONE
								 && field[xx][yy] != WOOD
								 && field[xx][yy] != SKULL
								 && (field[xx][yy] != FIRSTTAIL + player)
								)
								{	otherfield[xx][yy] = TEMPTAIL; // lightning actually
									draw(xx, yy, LIGHTNING);
								}
			for (x = 0; x <= FIELDX; x++)
			{	for (y = 0; y <= FIELDY; y++)
				{	if (otherfield[x][y] == TEMPTAIL)
					{	switch(field[x][y])
						{
						case ORB:
                            which = whichcreature(x, y, ORB, 255);
                            if (creature[which].mode == ARMOUR)
								draw(x, y, ORBARMOUR);
							else
                            {   creature[which].alive = FALSE;
                                wormscore(player, creature[which].score);
                                change(x, y, BONUS);
							}
                        break;
                        case GOAT:
                            creature[whichcreature(x, y, GOAT, 255)].alive = FALSE;
                            wormscore(player, KILLGOAT);
                            change(x, y, BONUS);
                        break;
			case MISSILE:
			    i = whichcreature(x, y, MISSILE, 255);
			    if (player != creature[i].type)
			    {	creature[i].alive = FALSE;
				change(x, y, EMPTY);
			    } else draw(x, y, FIRSTMISSILE + player);
			break;
			case PENGUIN:
			    wormscore(player, KILLPENGUIN);
			    // note no break here
			case FRAGMENT:
                            creature[whichcreature(x, y, field[x][y], 255)].alive = FALSE;
                            change(x, y, EMPTY);
                        break;
			default:
			    if (field[x][y] >= FIRSTDRIP && field[x][y] <= LASTDRIP)
                            {   creature[whichcreature(x, y, DRIP, 255)].alive = FALSE;
								wormscore(player, DRIPBONUS);
							}
							if (field[x][y] >= FIRSTHEAD && field[x][y] <= LASTHEAD)
							{	if (player != field[x][y] - FIRSTHEAD && worm[field[x][y] - FIRSTHEAD].mode != ARMOUR)
								{	worm[field[x][y] - FIRSTHEAD].alive = FALSE;
									worm[field[x][y] - FIRSTHEAD].cause = LIGHTNING;
									worm[field[x][y] - FIRSTHEAD].victor = player;
                                    change(x, y, EMPTY);
							}	}
							else // eg. tail
                                change(x, y, EMPTY);
                        break;
			}	}	}	}
			break;
		default:
            break;
	}	}
    else
    {   switch (thissy)
		{
		case EMPTY:
            score += EMPTYPOINT;
			break;
		case SILVER:
            score += SILVERPOINT;
			break;
		case GOLD:
            score += GOLDPOINT;
			break;
		case ORB:
            iwhich = whichcreature(x, y, ORB, 255);
			if (worm[player].mode == ARMOUR || animal == PROTECTOR)
            {   if (animal == HEAD)
					effect(FXUSEARMOUR);
                else effect(FXUSEPROTECTOR);
				effect(FXORBDEATH);
                wormscore(player, creature[iwhich].score);
                creature[iwhich].alive = FALSE;
                if (worm[player].bias)
                {   worm[player].lives += ORBBLOOD;
                    stat(player, LIFE);
            }   }
			else
            {   if (creature[iwhich].mode == ARMOUR)
                {   effect(FXUSEARMOUR);
                    orbscore(iwhich, KILLWORM);
                } else creature[iwhich].alive = FALSE;
				worm[player].cause = ORB;
				worm[player].victor = -1;
				worm[player].alive = FALSE;
			}
			break;
		case FRAGMENT:
            which = whichcreature(x, y, FRAGMENT, 255);
            if (animal == HEAD)
            {	if (worm[player].mode != ARMOUR)
            	{   worm[player].cause = FRAGMENT;
                    worm[player].victor = -1;
                    worm[player].alive = FALSE;
                    creature[which].alive = FALSE;
                } else
				{	effect(FXUSEARMOUR);
                    reflect(which);
			}	}
			else
			{	effect(FXUSEPROTECTOR);
                reflect(which);
			}
			break;
		case SKULL:
			effect(FXGETSKULL);
            score += SKULLPOINT;
            for (which = 0; which <= 3; which++)
			{	if (worm[which].alive == FALSE && worm[which].x == worm[player].x && worm[which].y == worm[player].y)
					iwhich = which;
			}
			worm[player].bias	+= worm[iwhich].bias;
			if (worm[player].bias > 0)
			{	stat(player, BIAS);
				worm[iwhich].bias = 0;
				stat(iwhich, BIAS);
			}
			worm[player].multi	*= worm[iwhich].multi;
			if (worm[player].multi > 1)
			{	if (worm[player].multi > MULTILIMIT)
					worm[player].multi = MULTILIMIT;
			}
			worm[player].power	+= worm[iwhich].power;
			if (worm[player].power > 1)
			{	if (worm[player].power > POWERLIMIT)
					worm[player].power = POWERLIMIT;
				stat(player, POWER);
				worm[iwhich].power = 0;
				stat(iwhich, POWER);
			}
			worm[player].ammo	+= worm[iwhich].ammo;
			if (worm[player].ammo > 0)
			{	stat(player, AMMO);
				worm[iwhich].ammo = 0;
				stat(iwhich, AMMO);
			}
			worm[player].armour	+= worm[iwhich].armour;
			if (worm[player].armour > 0)
			{	stat(player, ARMOUR);
				worm[iwhich].armour = 0;
				stat(iwhich, ARMOUR);
			}
			worm[player].tongue	+= worm[iwhich].tongue;
			if (worm[player].tongue > 0)
			{	stat(player, TONGUE);
				worm[iwhich].tongue = 0;
				stat(iwhich, TONGUE);
			}
			if (worm[player].armour > 0 || worm[player].tongue > 0)
			{	if (worm[player].armour >= worm[player].tongue)
					worm[player].mode = ARMOUR;
				else worm[player].mode = TONGUE;
			}
			if (worm[iwhich].nitro)
			{	worm[player].nitro = TRUE;
				stat(player, NITRO);
				worm[iwhich].nitro = FALSE;
				worm[iwhich].speed = NORMAL;
				stat(iwhich, NITRO);
			}
			for (which = 0; which <= LETTERS; which++)
				if (letters[iwhich][which])
				{	drawletter(iwhich, FIRSTLETTER + which, BLACK);
					if (!letters[player][which])
					{	letters[player][which] = TRUE;
						drawletter(player, FIRSTLETTER + which, NORMAL);
				}	}
			break;
		default:
			break;
	}	}	
    if (animal == HEAD)
	// it is important that HEAD is done before PROTECTORs
    {   field[worm[player].x][worm[player].y] = FIRSTHEAD + player;
        if (worm[player].alive)
		{	switch (worm[player].mode)
			{
			case NULL:
				draw(worm[player].x, worm[player].y, eachworm[player][0][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]);
				break;
			case TONGUE:
				if (worm[player].tongue < 10 && (r / SLOW) % 2 == 0)
					draw(worm[player].x, worm[player].y, WHITENED);
				else draw(worm[player].x, worm[player].y, eachworm[player][1][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]);
				break;
			case ARMOUR:
				if (worm[player].armour < 10 && (r / SLOW) % 2 == 0)
					draw(worm[player].x, worm[player].y, WHITENED);
				else draw(worm[player].x, worm[player].y, eachworm[player][1][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]);
				break;
			default:
				break;
		}	}
		else draw(worm[player].x, worm[player].y, SKULL);
    } else // assumes animal == PROTECTOR
		if (protector[player][thisprot].alive && protector[player][thisprot].visible)
            change(x, y, FIRSTPROTECTOR + player);
	while (++thisprot <= PROTECTORS)
		if (protector[player][thisprot].alive && valid(protector[player][thisprot].x, protector[player][thisprot].y))
			break;
	if (thisprot > PROTECTORS)
		animal = NULL;
	else animal = PROTECTOR;
}
wormscore(player, score);
}

void wormscore(SBYTE player, LONG score)
{   worm[player].score += score * worm[player].multi * players;
    stat(player, BONUS);
}

SWORD wsign(SWORD value)
{	if (value < 0)
		return (-1);
	elif (value > 0)
		return (1);
	else
		return (0);
}

SBYTE xwrap(SBYTE x)
{	if (x < 0)
		x += FIELDX + 1;
	elif (x > FIELDX)
		x -= FIELDX + 1;
	return(x);
}
SBYTE ywrap(SBYTE y)
{	if (y < 0)
		y += FIELDY + 1;
	elif (y > FIELDY)
		y -= FIELDY + 1;
	return(y);
}
void ramming(SBYTE player)
{	SBYTE i;

	worm[player].x = xwrap(worm[player].x - worm[player].deltax);
    worm[player].y = ywrap(worm[player].y - worm[player].deltay);
	for (i = 0; i <= PROTECTORS; i++)
	{	// no point checking whether the protectors are alive or dead
		protector[player][i].x -= worm[player].deltax;
		protector[player][i].y -= worm[player].deltay;
}	}

	
SWORD atleast(SWORD value, SWORD minimum)
{	if (value < minimum)
		return(minimum);
	else return(value);
}

void orbscore(SBYTE which, ULONG score)
{   creature[which].score += score * creature[which].multi;
}

void __inline change(SBYTE x, SBYTE y, UBYTE image)
{   field[x][y] = image;
    draw(x, y, image);
}

void createmissile(UBYTE player)
{   UBYTE i, j = 0;

    for (i = 0; i <= CREATURES; i++)
        if (creature[i].alive && creature[i].species == MISSILE && creature[i].type == player)
            if (++j >= 2)
                return;

    for (i = 0; i <= CREATURES; i++)
        if (!creature[i].alive)
        {   effect(FXMISSILEACTIVE);
            creature[i].alive      = TRUE;
            creature[i].x          = worm[player].x;
            creature[i].y          = worm[player].y;
            creature[i].species    = MISSILE;
            creature[i].type       = player;
            creature[i].last       = EMPTY;
            creature[i].visible    = FALSE;
            if (level)
                creature[i].speed  = (SBYTE) atleast(MISSILESPEED / level, 1);
            else creature[i].speed = BONUSMISSILESPEED;
            break;
}       }

/* WormWars FSET 5.1 format for fieldset contents and high
score table (Amiga and IBM-PC), as follows:

header
	TEXT[]				"FSET 5.1" (NULL-terminated)
	SBYTE				levels;
high score table
	for (slot = 0; slot <= HISCORES; slot++)
	{	SBYTE			hiscore[slot].player,
					hiscore[slot].level;
		SLONG			hiscore[slot].score;
		TEXT[]			hiscore[slot].name (NULL-terminated)
		TEXT[]			hiscore[slot].time (NULL-terminated)
		TEXT[]			hiscore[slot].date (NULL-terminated)
	}
level data
	for (level = 0; level <= levels; level++)
	{	SBYTE			startx[level],
						starty[level];
		ABOOL			teleport[level][0].alive;
		SBYTE			teleport[level][0].x,
						teleport[level][0].y;
		ABOOL			teleport[level][1].alive;
		SBYTE			teleport[level][1].x,
						teleport[level][1].y;
		for (x = 0; x <= FIELDX; x++)
			for (y = 0; y <= FIELDY; y++)
				SBYTE	board[level][x][y];
	}
version string
	TEXT[]				"$VER: Worm Wars 5.1 (dd.mm.yy) $" (NULL-terminated) */

SBYTE onlyworm(ABOOL alive)
{   UBYTE i, theworm, worms = 0;

    for (i = 0; i <= 3; i++)
        if (worm[i].control != NONE && ((!alive) || worm[i].lives))
        {   theworm = i;
            worms++;
        }
    if (worms == 1)
        return (SBYTE) theworm;
    else return -1;
}

// Must have blank line at EOF.
