/* $Filename:       WormWars/Source/engine.c(pp) $
 * $VER:            WormWars 4.3 (5.9.99) $
 * $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 "stdafx.h"
#include "diff.h"
#include "same.h"
#include "engine.h"

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

struct
{	SBYTE		x, y, oldx, oldy;
	ABOOL		alive, moved, teleported, visible, reflected;
} bullet[POWERLIMIT + 1];
struct
{	SBYTE		x, y, type;
	ABOOL		alive, moved;
} drip[DRIPS + 1];
struct
{	SBYTE		x, y, deltax, deltay, last;
    ABOOL        alive, moved;
} frag[ORBS + 2][8];
struct
{	SBYTE		x, y, last;
	ABOOL		alive;
} killer[KILLERS + 1];
struct
{   SBYTE       x, y, deltax, deltay;
	ABOOL		alive, moved;
} missile[4];

struct
{	WORD		frequency;
	LONG		score;
} object[LASTOBJECT + 1] =
{	{1280,  60},	//	AFFIXER
    {  80,  20},    //  AMMO
	{ 110,  20},	//	ARMOUR
	{  90,  50},	//	BIAS
	{ 200,  30},	//	BOMB
	{  80,  10},	//	BONUS
	{1020,	60},	//	CLOCK
	{ 360,  50},	//	GROWER
	{1900,	90},	//	HEALER
	{1360,  60},	//	ICE
	{ 150,  50},	//	LIFE
	{ 240,  40},	//	MISSILE
	{ 640,  50},	//	MULTIPLIER
	{ 400,  10},	//	NITRO
	{ 240,  30},	//	POWER
	{ 480,  50},	//	PROTECTOR
	{ 600,  40},	//	SLAYER
	{ 730,  70},	//	SWITCHER
	{ 320,  20},	//	TONGUE
	{2200, 100},	//	TREASURE
	{3600, 100}		//	UMBRELLA
};

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

struct
{   SBYTE       x, y, deltax, deltay, relx, rely;
    BOOL        alive, last, visible;
} protector[4][PROTECTORS + 1];
struct
{	SBYTE		x, y, time;
	BOOL		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
}	}	};

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

MODULE	ABOOL					letters[4][LETTERS + 1], trainer;
MODULE	SBYTE					dripspeed, fragspeed, killerspeed, orbspeed,
								ice, noletter, treasurer;
MODULE	SWORD					dripfreq, killerfreq, orbfreq, slimefreq;

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

AGLOBAL	ABOOL					clearthem	= FALSE,
								modified	= FALSE;
AGLOBAL	SBYTE					a = GAMEOVER,
								board[MAXLEVELS + 1][FIELDX + 1][FIELDY + 1],
								brush = STONE,
								field[FIELDX + 1][FIELDY + 1], hi = 3,
								level = 1, levels, lo = 0, reallevel,
								startx[MAXLEVELS + 1],
								starty[MAXLEVELS + 1];
AGLOBAL	SWORD					secondsleft, secondsperlevel;
AGLOBAL	STRPTR					pathname = (STRPTR) DEFAULTSET;
AGLOBAL	ULONG					delay, r;
AGLOBAL	struct HiScoreStruct	hiscore[HISCORES + 1];
AGLOBAL	struct OrbStruct		orb[ORBS + 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 > KILLER) && 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;
	LONG	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 bouncekiller(SBYTE which, SBYTE x, SBYTE y)
{	SBYTE i;

	if (field[x][y] == KILLER)
		for (i = 0; i <= KILLERS; i++)
			if (killer[i].alive && x == killer[i].x && y == killer[i].y)
			{	killer[i].alive = FALSE;
				draw(x, y, BONUS);
				field[x][y] = BONUS;
				orbscore(which, KILLKILLER);
}			}

ABOOL bounceorb(SBYTE which, SBYTE x, SBYTE y)
{	if (field[x][y] == METAL)
		return TRUE;
	elif (orb[which].mode == NONE)
	{	if (field[x][y] >= FIRSTNONE && field[x][y] <= LASTNONE)
			return TRUE;
		else return FALSE;
	} elif (orb[which].mode == TONGUE)
	{	if (field[x][y] >= FIRSTTONGUE && field[x][y] <= LASTTONGUE)
			return TRUE;
		else return FALSE;
	} else // assumes orb[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].name[0] = 0;
		hiscore[i].fresh = FALSE;
}   }

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	counter, i, pain, player, survivor, which;
	ABOOL	flag, slow;

	for (player = lo; player <= hi; 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 KILLER:
					pain = KILLERPAIN;
					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;
				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, worm[player].nitro);
					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)
				{	flag = FALSE;
					for (i = lo; i <= hi; i++)
						if (worm[i].control == HUMAN)
							flag = TRUE;
					if (!flag || worm[player].control == HUMAN)
						effect(FXPAIN);
					// Amiga-worms only make pain sounds in demo mode
					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)
						{	draw(protector[player][which].x, protector[player][which].y, EMPTY);
							field[protector[player][which].x][protector[player][which].y] = EMPTY;
					}	}
					counter = 0;
					for (which = lo; which <= hi; which++)
						if (worm[which].lives)
						{	survivor = which;
							counter++;
						}
					if (counter == 1)
						wormscore(survivor, SURVIVOR);
	}	}	}	}
	if (!worm[0].lives && !worm[1].lives && !worm[2].lives && !worm[3].lives)
	{	// End of game
		newhiscores();
		effect(FXDEFEAT);
		a = GAMEOVER;
		if (lo == hi)
			say((STRPTR) "Game over!", worm[lo].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;
	
	// handle level completion

	for (player = lo; player <= hi; 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);
}

/* NAME     fragloop -- controls fragments
SYNOPSIS    fragloop(void);
FUNCTION    Controls all fragments.
MODULE      engine.c */

void fragloop(void)
{	SBYTE	i, j, k, thissy, which;

for (which = 0; which <= ORBS + 1; which++)
	for (i = 0; i <= 7; i++)
        if (frag[which][i].alive)
        {   if (frag[which][i].moved)
            {   draw(frag[which][i].x, frag[which][i].y, frag[which][i].last);
				field[frag[which][i].x][frag[which][i].y] = frag[which][i].last;
			} else frag[which][i].moved = TRUE;
			frag[which][i].x += frag[which][i].deltax;
			frag[which][i].y += frag[which][i].deltay;
			if (!(valid(frag[which][i].x, frag[which][i].y)))
				frag[which][i].alive = FALSE;
			else
			{	thissy = field[frag[which][i].x][frag[which][i].y];
                if (thissy >= FIRSTFRAGDEATH && thissy <= LASTFRAGDEATH)
                {   effect(FXTHUD);
					frag[which][i].alive = FALSE;
                    if (thissy == KILLER)
                    {   effect(FXKILLERDEATH);
						j = whichkiller(frag[which][i].x, frag[which][i].y);
                        killer[j].alive = FALSE;
                        field[killer[j].x][killer[j].y] = BONUS;
                        draw(killer[j].x, killer[j].y, BONUS);
                }   }
				elif (thissy >= FIRSTPROTECTOR && thissy <= LASTPROTECTOR)
				{	effect(FXUSEPROTECTOR);
					reflect(which, i);
				} elif (thissy >= FIRSTMISSILE && thissy <= LASTMISSILE)
				{	frag[which][i].alive = FALSE;
					missile[thissy - FIRSTMISSILE].alive = FALSE;
                    field[missile[thissy - FIRSTMISSILE].x][missile[thissy - FIRSTMISSILE].y] = EMPTY;
                    draw(missile[thissy - FIRSTMISSILE].x, missile[thissy - FIRSTMISSILE].y, EMPTY);
				} elif (thissy >= FIRSTHEAD && thissy <= LASTHEAD)
				{	if (worm[thissy - FIRSTHEAD].mode != ARMOUR)
					{	worm[thissy - FIRSTHEAD].cause = FRAGMENT;
						worm[thissy - FIRSTHEAD].victor = -1;
						worm[thissy - FIRSTHEAD].alive = FALSE;
					} else
					{	effect(FXUSEARMOUR);
						reflect(which, i);
				}	}
				elif (thissy >= FIRSTDRIP && thissy <= LASTDRIP)
				{	j = whichdrip(frag[which][i].x, frag[which][i].y);
					drip[j].alive = FALSE;
					frag[which][i].alive = FALSE;
					field[frag[which][i].x][frag[which][i].y] = BONUS;
					draw(frag[which][i].x, frag[which][i].y, BONUS);
				} else switch(thissy)
				{
				case METAL:
					reflect(which, i);
					break;
                case ORB:
                    j = whichorb(frag[which][i].x, frag[which][i].y);
                    if (orb[j].mode != ARMOUR)
                        orb[j].explode = TRUE;
                    else effect(FXUSEARMOUR);
                    frag[which][i].alive = FALSE;
                    break;
                case TELEPORT:
                    j = whichteleport(frag[which][i].x, frag[which][i].y);
                    if (blocked(j, frag[which][i].deltax, frag[which][i].deltay))
                        frag[which][i].alive = FALSE;
                    else
                    {   effect(FXUSETELEPORT);
                        frag[which][i].x = xwrap(teleport[level][partner(j)].x + frag[which][i].deltax);
                        frag[which][i].y = ywrap(teleport[level][partner(j)].y + frag[which][i].deltay);
                        frag[which][i].last = SILVER;
                    }
                    break;
                case BOMB:
                    j = whichtimebomb(frag[which][i].x, frag[which][i].y);
                    if (j != -1)
                    {   frag[which][i].alive = FALSE;
                        timebomb[j].alive = FALSE;
                        field[timebomb[j].x][timebomb[j].y] = EMPTY;
                        draw(timebomb[j].x, timebomb[j].y, EMPTY);
                        bombblast(BOMB, 0, timebomb[j].x, timebomb[j].y);
                    }
                    break;
				case FRAGMENT:
					effect(FXTHUD);
					draw(frag[which][i].x, frag[which][i].y, EMPTY);
					frag[which][i].alive = FALSE;
					whichfrag(frag[which][i].x, frag[which][i].y, &j, &k);
					frag[j][k].alive = FALSE;
					break;
                default:
                    break;
                }
                if (frag[which][i].alive)
                {   field[frag[which][i].x][frag[which][i].y] = FRAGMENT;
                    draw(frag[which][i].x, frag[which][i].y, FRAGMENT);
		}	}	}
}

void gameloop(void)
{	SBYTE player, which;

	fastloop();
	gameinput();
	for (player = lo; player <= hi; player++)
		if (worm[player].lives && !(r % worm[player].speed) && (ice == -1 || ice == player))
			wormloop(player);
	if (ice == -1)
	{	for (which = 0; which <= ORBS; which++)
			if (orb[which].alive && !(r % orb[which].speed))
				if (orb[which].explode)
					orbexplosion(which);
				else orbloop(which);
        if (!(r % killerspeed))
			killerloop();
		if (!(r % dripspeed))
			driploop();
        if (!(r % fragspeed))
			fragloop();
		if (!(r % MISSILESPEED))
			missileloop();
	}
	if (a == PLAYGAME)
	{	death();
		if (!(r % VERYSLOW))
			slowloop();
	}
	timing();
}

void killall(void)
{	SBYTE iwhich, which;

	for (which = 0; which <= ORBS; which++)
		orb[which].alive = FALSE;
	for (which = 0; which <= KILLERS; which++)
		killer[which].alive = FALSE;
	for (which = 0; which <= ORBS + 1; which++)
		for (iwhich = 0; iwhich <= 7; iwhich++)
			frag[which][iwhich].alive = FALSE;
	for (which = 0; which <= 3; which++)
        if (missile[which].alive)
            missile[which].alive = FALSE;
	for (which = 0; which <= TIMEBOMBS; which++)
		timebomb[which].alive = FALSE;
	teleport[level][2].alive = FALSE;
	teleport[level][3].alive = FALSE;
}

void killerloop(void)
{	ABOOL	happy;
	UBYTE	count;
	SBYTE	which, x, xx, y, yy;

	for (which = 0; which <= KILLERS; which++)
        if (killer[which].alive)
		{	happy = FALSE;
			for (x = killer[which].x - 1; x <= killer[which].x + 1; x++)
				for (y = killer[which].y - 1; y <= killer[which].y + 1; y++)
					if (field[x][y] >= FIRSTEMPTY && field[x][y] <= LASTEMPTY)
						happy = TRUE;
            if ((!happy) || (!(rand() % RESTFULNESS)))
			{	x = (rand() % 3) - 1;
				y = (rand() % 3) - 1;
				if (valid(killer[which].x + x, killer[which].y + y) && (x || y)
				&& field[killer[which].x + x][killer[which].y + y] >= FIRSTKILLER && field[killer[which].x + x][killer[which].y + y] <= LASTKILLER && field[killer[which].x + x][killer[which].y + y] != KILLER)
				{	field[killer[which].x][killer[which].y] = killer[which].last;
					draw(killer[which].x, killer[which].y, killer[which].last);
					killer[which].x += x;
					killer[which].y += y;
					killer[which].last = field[killer[which].x][killer[which].y];
					field[killer[which].x][killer[which].y] = KILLER;
					draw(killer[which].x, killer[which].y, KILLER);
			}	}
            if (!frag[ORBS + 1][which].alive && !(rand() % KILLERFIREFREQ))
			{	count = 0;
				do
				{	x = (rand() % 3) - 1;
					y = (rand() % 3) - 1;
					xx = killer[which].x + x;
					yy = killer[which].y + y;
                } while (((!x && !y) || xx < 0 || xx > FIELDX || yy < 0 || yy > FIELDY || field[xx][yy] > LASTKILLERFIRE) && ++count < PATIENCE);
				if (count < PATIENCE)
				{	effect(FXKILLERFIRE);
					frag[ORBS + 1][which].alive = TRUE;
                    frag[ORBS + 1][which].x = killer[which].x;
                    frag[ORBS + 1][which].y = killer[which].y;
					frag[ORBS + 1][which].deltax = x;
					frag[ORBS + 1][which].deltay = y;
                    frag[ORBS + 1][which].moved = FALSE;
					frag[ORBS + 1][which].last = EMPTY;
}		}	}	}

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);
		renderboard();
}	}

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

void levelinsert(void)
{	SBYTE i;

	// push boards

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

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

	// open file

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

	// read header

	if (!ZRead(IOBuffer, 10))
    {	ZClose();
        return FALSE;
    }
	if (!strcmp(IOBuffer, "FSET 4.3"))
		ver = 43;
	elif (!strcmp(IOBuffer, "FSET 4.2"))
		ver = 42;
	elif (!stricmp(IOBuffer, "FSET 4.1"))
		ver = 41;
	else
	{	ZClose();
		return FALSE;
	}
    levels							= IOBuffer[9];

	// read high score table

	for (i = 0; i <= HISCORES; i++)
	{	if (!ZRead(IOBuffer, 6))
		{	ZClose();
			return FALSE;
		}
		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 FALSE;
		}
		for (j = 0; j <= NAMELENGTH; j++)
			hiscore[i].name[j]		= IOBuffer[j];
    }

	// read level data

    for (i = 0; i <= levels; i++)
    {   if (!ZRead(IOBuffer, 8))
		{	ZClose();
			return FALSE;
		}
		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 FALSE;
		} else
		{	if (ver == 41)
			{	// convert from FSET 4.1 to FSET 4.2 format

				for (x = 0; x <= FIELDX; x++)
					for (y = 0; y <= FIELDY; y++)
						if (board[i][x][y] == 18) // old HEALER
							board[i][x][y] = 7; // new HEALER
						elif (board[i][x][y] >= 7)
							board[i][x][y]++;
			}
			if (ver == 41 || ver == 42)
			{	// convert from FSET 4.2 to FSET 4.3 format

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

	// no need to read version string

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

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);
}		}

/* NAME     missileloop -- controls missiles
SYNOPSIS    missileloop(void);
FUNCTION    Controls all killers.
MODULE      engine.c */

void missileloop(void)
{	SBYTE    distx, disty, distance, iwhich, player, thissy, which;
    UBYTE   bestdistance;
	
	for (player = lo; player <= hi; player++)
    {   if (missile[player].alive && (ice == -1 || ice == player))
        {   bestdistance = (UBYTE) -1;
            for (which = lo; which <= hi; which++)
                if (which != player && worm[which].lives > 0 && !worm[which].bias)
                {   distx = abs(worm[which].x - missile[player].x);
                    disty = abs(worm[which].y - missile[player].y);
					if (distx < disty)
						distance = distx;
					else distance = disty;
                    if (distance <= bestdistance)
                    {   bestdistance = distance;
                        missile[player].deltax = bsign(worm[which].x - missile[player].x);
                        missile[player].deltay = bsign(worm[which].y - missile[player].y);
				}	}
            for (which = lo; which <= hi; which++)
                if (which != player && missile[which].alive)
                {   distx = abs(missile[which].x - missile[player].x);
                    disty = abs(missile[which].y - missile[player].y);
					if (distx < disty)
						distance = distx;
					else distance = disty;
                    if (distance <= bestdistance)
                    {   bestdistance = distance;
                        missile[player].deltax = bsign(missile[which].x - missile[player].x);
                        missile[player].deltay = bsign(missile[which].y - missile[player].y);
				}	}
            for (which = 0; which <= ORBS; which++)
                if (orb[which].alive)
                {   distx = abs(orb[which].x - missile[player].x) * 2;
                    disty = abs(orb[which].y - missile[player].y) * 2;
					if (distx < disty)
						distance = distx;
					else distance = disty;
                    if (distance <= bestdistance)
                    {   bestdistance = distance;
                        missile[player].deltax = bsign(orb[which].x - missile[player].x);
                        missile[player].deltay = bsign(orb[which].y - missile[player].y);
				}	}
			for (which = 0; which <= KILLERS; which++)
				if (killer[which].alive)
				{	distx = abs(killer[which].x - missile[player].x) * 2;
					disty = abs(killer[which].y - missile[player].y) * 2;
					if (distx < disty)
						distance = distx;
					else distance = disty;
                    if (distance <= bestdistance)
                    {   bestdistance = distance;
                        missile[player].deltax = bsign(killer[which].x - missile[player].x);
                        missile[player].deltay = bsign(killer[which].y - missile[player].y);
				}	}
            if (missile[player].moved)
            {   draw(missile[player].x, missile[player].y, EMPTY);
                field[missile[player].x][missile[player].y] = EMPTY;
            } else missile[player].moved = TRUE;
            if (bestdistance == (UBYTE) -1)
                missile[player].alive = FALSE;
            else
            {   missile[player].x += missile[player].deltax;
				missile[player].y += missile[player].deltay;
				thissy = field[missile[player].x][missile[player].y];
				if (thissy >= FIRSTLETTER && thissy <= WOOD)
                {   missile[player].alive = FALSE;
                    if (thissy == KILLER)
                    {   effect(FXKILLERDEATH);
						which = whichkiller(missile[player].x, missile[player].y);
                        killer[which].alive = FALSE;
                        field[killer[which].x][killer[which].y] = BONUS;
                        draw(killer[which].x, killer[which].y, BONUS);
                        wormscore(player, KILLKILLER);
                        if (worm[player].bias)
                        {   worm[player].lives += KILLERBLOOD;
                            stat(player, LIFE);
                }   }   }
				elif (thissy == FRAGMENT)
                {   missile[player].alive = FALSE;
                    whichfrag(missile[player].x, missile[player].y, &which, &iwhich);
                    frag[which][iwhich].alive = FALSE;
					draw(missile[player].x, missile[player].y, EMPTY);
                } elif (thissy == ORB)
                {	missile[player].alive = FALSE;
                    which = whichorb(missile[player].x, missile[player].y);
                    if (orb[which].mode != ARMOUR)
                    {   effect(FXORBDEATH);
                        orb[which].alive = FALSE;
                        draw(orb[which].x, orb[which].y, BONUS);
                        field[orb[which].x][orb[which].y] = BONUS;
                        wormscore(player, orb[which].score);
                        if (worm[player].bias)
                        {   worm[player].lives += ORBBLOOD;
                            stat(player, LIFE);
                        }
                    } else effect(FXUSEARMOUR);
                } elif (thissy >= FIRSTPROTECTOR && thissy <= LASTPROTECTOR)
                {   if (player != thissy - FIRSTPROTECTOR)
					{	effect(FXUSEPROTECTOR);
                        missile[player].alive = FALSE;
                }   }
				elif (thissy >= FIRSTDRIP && thissy <= LASTDRIP)
					missile[player].alive = FALSE;
                elif (thissy >= FIRSTHEAD && thissy <= LASTHEAD)
                {   missile[player].alive = FALSE;
					if (worm[thissy - FIRSTHEAD].mode != ARMOUR)
					{	worm[thissy - FIRSTHEAD].cause = FIRSTMISSILE + player;
						worm[thissy - FIRSTHEAD].victor = player;
						worm[thissy - FIRSTHEAD].alive = FALSE;
					} else effect(FXUSEARMOUR);
                } elif (thissy >= FIRSTMISSILE && thissy <= LASTMISSILE)
				{	missile[player].alive = FALSE;
                    missile[thissy - FIRSTMISSILE].alive = FALSE;
                    field[missile[which].x][missile[which].y] = BONUS;
                    draw(missile[which].x, missile[which].y, BONUS);
                } elif (thissy == TELEPORT)
                {   which = whichteleport(missile[player].x, missile[player].y);
                    if (blocked(which, missile[player].deltax, missile[player].deltay))
                        missile[player].alive = FALSE;
                    else
                    {   effect(FXUSETELEPORT);
                        missile[player].x = xwrap(teleport[level][partner(which)].x + missile[player].deltax);
                        missile[player].y = ywrap(teleport[level][partner(which)].y + missile[player].deltay);
                }   }
                elif (thissy == BOMB)
                {   which = whichtimebomb(missile[player].x, missile[player].y);
                    if (which != -1)
                    {	missile[player].alive = FALSE;
                        timebomb[which].alive = FALSE;
                        field[timebomb[which].x][timebomb[which].y] = EMPTY;
                        draw(timebomb[which].x, timebomb[which].y, EMPTY);
                        bombblast(BOMB, 0, timebomb[which].x, timebomb[which].y);
                }   }
				elif (thissy == SKULL || thissy == METAL)
					missile[player].alive = FALSE;
                if (missile[player].alive)
				{	draw(missile[player].x, missile[player].y, FIRSTMISSILE + player);
					field[missile[player].x][missile[player].y] = FIRSTMISSILE + player;
}   }   }   }   }

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)
		{	renderboard();
			saylevel(WHITE);
		} else
			hiscores();
}	}

void newgame(void)
{	SBYTE player;

	if (worm[0].control != NONE)
		lo = 0;
	elif (worm[1].control != NONE)
		lo = 1;
	elif (worm[2].control != NONE)
		lo = 2;
	else lo = 3;
	if (worm[3].control != NONE)
		hi = 3;
	elif (worm[2].control != NONE)
		hi = 2;
	elif (worm[1].control != NONE)
		hi = 1;
	else hi = 0;

	for (player = 0; player <= 3; player++)
		worm[player].lives = 0;

	r			= -1;
	trainer		= FALSE;
	ice			= -1;
	reallevel	= 0;
	level		= 1;
	a			= PLAYGAME;
	clearstats();
	newlevel(rand() % 4);
	timing();
}

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

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

			if (which < HISCORES)
				for (iwhich = HISCORES; iwhich >= which + 1; iwhich--)
				{	hiscore[iwhich].player	= hiscore[iwhich - 1].player;
					hiscore[iwhich].score	= hiscore[iwhich - 1].score;
					hiscore[iwhich].level	= hiscore[iwhich - 1].level;
					hiscore[iwhich].fresh	= hiscore[iwhich - 1].fresh;
					strcpy(hiscore[iwhich].name, hiscore[iwhich - 1].name);
				}

			modified = TRUE;
			hiscore[which].player = player;
			hiscore[which].score = worm[player].score;
			hiscore[which].level = worm[player].levelreached;
			if (worm[player].control == AMIGA)
			{	strcpy(hiscore[which].name, amiganame[player]);
				hiscore[which].fresh = FALSE;
			} else
			{	strcpy(hiscore[which].name, "(New)");
				hiscore[which].fresh = TRUE;
			}
			break;
}		}

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

	if (level >= 2 && worm[player].lives)
		rundown(player);
	if (a == PLAYGAME)
	{	if (level > levels)
		{	for (which = lo; which <= hi; which++)
				if (worm[which].lives)
					worm[which].levelreached = -1;
			celebrate();
			newhiscores();
			titlescreen();
		} else
		{	saylevel(WHITE);
			for (which = lo; which <= hi; which++)
				worm[which].multi = (SBYTE) atleast(worm[which].multi / 2, 1);
			killall();
			clearletters();
			orientworms();
			changefield();
			renderboard();
		      dripspeed = (SBYTE) atleast(  DRIPSPEED_MAX - (level /   DRIPSPEED_DIV), VERYFAST);
		      fragspeed = (SBYTE) atleast(  FRAGSPEED_MAX - (level /   FRAGSPEED_DIV), VERYFAST);
		    killerspeed = (SBYTE) atleast(KILLERSPEED_MAX - (level / KILLERSPEED_DIV), VERYFAST);
		       orbspeed = (SBYTE) atleast(   ORBSPEED_MAX - (level /    ORBSPEED_DIV), VERYFAST);
			   dripfreq =         atleast(   DRIPFREQ_MAX - (level *    DRIPFREQ_DEC),   DRIPFREQ_MIN);
			 killerfreq =         atleast( KILLERFREQ_MAX - (level *  KILLERFREQ_DEC), KILLERFREQ_MIN);
			    orbfreq =         atleast(    ORBFREQ_MAX - (level *     ORBFREQ_DEC),    ORBFREQ_MIN);
			  slimefreq =         atleast(  SLIMEFREQ_MAX - (level *   SLIMEFREQ_DEC),  SLIMEFREQ_MIN);
			      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].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 orbexplosion(SBYTE which)
{	SBYTE iwhich;

	effect(FXEXPLODE);
	orb[which].alive = FALSE;
	for (iwhich = 0; iwhich <= 7; iwhich++)
	{	frag[which][iwhich].alive = TRUE;
		frag[which][iwhich].x = orb[which].x;
		frag[which][iwhich].y = orb[which].y;
		frag[which][iwhich].moved = TRUE;
		frag[which][iwhich].last = EMPTY;
		switch (iwhich)
		{
		case 0:
			frag[which][iwhich].deltax = 0;
			frag[which][iwhich].deltay = -1;
			break;
		case 1:
			frag[which][iwhich].deltax = 1;
			frag[which][iwhich].deltay = -1;
			break;
		case 2:
			frag[which][iwhich].deltax = 1;
			frag[which][iwhich].deltay = 0;
			break;
		case 3:
			frag[which][iwhich].deltax = 1;
			frag[which][iwhich].deltay = 1;
			break;
		case 4:
			frag[which][iwhich].deltax = 0;
			frag[which][iwhich].deltay = 1;
			break;
		case 5:
			frag[which][iwhich].deltax = -1;
			frag[which][iwhich].deltay = 1;
			break;
		case 6:
			frag[which][iwhich].deltax = -1;
			frag[which][iwhich].deltay = 0;
			break;
		case 7:
			frag[which][iwhich].deltax = -1;
			frag[which][iwhich].deltay = -1;
			break;
		default:
			break;
}	}	}

/* NAME     orbloop -- controls orbs
SYNOPSIS    orbloop(SBYTE);
FUNCTION    Controls an orb.
MODULE      engine.c */

void orbloop(SBYTE which) {
SBYTE	frontx, fronty, iiwhich, iwhich, player, newdeltax, newdeltay,
		rearx, reary, thissy, x, xx, y, yy;
ULONG	score = 0L;

// erase previous image

if (orb[which].moved)
	if (field[orb[which].x][orb[which].y] != ORB)
		orb[which].moved = FALSE;
	else
	{	draw(orb[which].x, orb[which].y, orb[which].last);
		field[orb[which].x][orb[which].y] = orb[which].last;
	}
else orb[which].moved = TRUE;

// bounce/move

frontx	= xwrap(orb[which].x + orb[which].deltax);	// look in front
fronty	= ywrap(orb[which].y + orb[which].deltay);
rearx	= xwrap(orb[which].x - orb[which].deltax);	// look behind
reary	= ywrap(orb[which].y - orb[which].deltay);
if (bounceorb(which, frontx, fronty))
{	bouncekiller(which, frontx, fronty);
	newdeltax = -orb[which].deltax;	// default bounce angle is 180°
	newdeltay = -orb[which].deltay;
	if (!bounceorb(which, frontx, reary))
	{	if (bounceorb(which, rearx, fronty))
		{	bouncekiller(which, rearx, fronty);
			newdeltax = orb[which].deltax;
	}	}
	elif (!bounceorb(which, rearx, fronty))
	{	bouncekiller(which, rearx, fronty);
		newdeltay = orb[which].deltay;
	}
	orb[which].deltax = newdeltax;
	orb[which].deltay = newdeltay;
}
orb[which].x = xwrap(orb[which].x + orb[which].deltax);
orb[which].y = ywrap(orb[which].y + orb[which].deltay);
orb[which].last = EMPTY;

// collision detection

if (orb[which].alive)
{	x = orb[which].x;
	y = orb[which].y;
	thissy = field[x][y];
	if (thissy <= LASTOBJECT)
	{	score = object[thissy].score;
		if (thissy != SLAYER && thissy != BOMB)
			effect(FXGETOBJECT);
		if (thissy == AMMO || thissy == SLAYER)
		{	if (orb[which].mode != ARMOUR)
				orb[which].explode = TRUE;
			else effect(FXUSEARMOUR);
		} elif (thissy == NITRO || thissy == POWER)
		{	effect(FXGETNITRO);
			orb[which].speed = speedup(orb[which].speed, TRUE);
		} elif (thissy == HEALER || thissy == LIFE || thissy == ICE || thissy == TREASURE || thissy == UMBRELLA)
			orbsplit(which);
		else switch(thissy) {
		case ARMOUR:
			orb[which].armour += MODEADD + (rand() % MODERAND);
			orb[which].mode = ARMOUR;
			break;
		case TONGUE:
			orb[which].tongue += MODEADD + (rand() % MODERAND);
			orb[which].mode = TONGUE;
			break;
		case BOMB:
            iwhich = whichtimebomb(x, y);
            timebomb[iwhich].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);
			break;
		case PROTECTOR:
			for (player = lo; player <= hi; player++)
                if (worm[player].lives)
					for (iwhich = 0; iwhich <= PROTECTORS; iwhich++)
						if (protector[player][iwhich].alive)
						{	protector[player][iwhich].alive = FALSE;    
                            if (protector[player][iwhich].visible)
							{	draw(protector[player][iwhich].x, protector[player][iwhich].y, EMPTY);
								field[protector[player][iwhich].x][protector[player][iwhich].y] = EMPTY;
						}	}
			break;
		case MISSILE:
			for (player = lo; player <= hi; player++)
				if (missile[player].alive)
				{	missile[player].alive = FALSE;
					draw(missile[player].x, missile[player].y, EMPTY);
					field[missile[player].x][missile[player].y] = EMPTY;
				}
			break;
		case MULTIPLIER:
			orb[which].multi *= 2;
			if (orb[which].multi > MULTILIMIT)
				orb[which].multi = MULTILIMIT;
			break;
		case BIAS:
			for (player = lo; player <= hi; player++)
                   if (worm[player].lives && worm[player].bias)
				{	worm[player].bias = 0;
					stat(player, BIAS);
				}
			break;
		case AFFIXER:
			for (player = lo; player <= hi; player++)
                if (worm[player].lives)
					worm[player].affixer = FALSE;
			break;
        case SWITCHER:
            for (x = 0; x <= FIELDX; x++)
                for (y = 0; y <= FIELDY; y++)
                    if (field[x][y] >= FIRSTTAIL && field[x][y] <= LASTTAIL)
                    {   field[x][y] = WOOD;
                        draw(x, y, WOOD);
                    }
            break;
		case GROWER:
			effect(FXGETGROWER);
			for (x = 0; x <= FIELDX; x++)
				for (y = 0; y <= FIELDY; y++)
					if (field[x][y] == WOOD)
						for (xx = x - 1; xx <= x + 1; xx++)
							for (yy = y - 1; yy <= y + 1; yy++)
								if (valid(xx, yy) && field[xx][yy] == EMPTY)
									field[xx][yy] = TEMPWOOD;
			for (x = 0; x <= FIELDX; x++)
				for (y = 0; y <= FIELDY; y++)
					if (field[x][y] == TEMPWOOD)
					{	field[x][y] = WOOD;
						draw(x, y, WOOD);
					}
			break;
		case CLOCK:
			secondsperlevel -= rand() % CLOCKRAND;
			break;
		default:
			break;
    }   }
    elif (thissy >= FIRSTPROTECTOR && thissy <= LASTPROTECTOR)
    {   effect(FXUSEPROTECTOR);
		effect(FXORBDEATH);
        orb[which].alive = FALSE;
        wormscore(thissy - FIRSTPROTECTOR, orb[which].score);
        if (worm[thissy - FIRSTPROTECTOR].bias)
        {   worm[thissy - FIRSTPROTECTOR].lives += ORBBLOOD;
            stat(thissy - FIRSTPROTECTOR, LIFE);
    }   }
    elif (thissy >= FIRSTTAIL && thissy <= LASTTAIL)
    {   if (orb[which].mode == TONGUE)
           effect(FXUSETONGUE);
        else
        {   if (worm[thissy - FIRSTTAIL].alive)
            {   effect(FXORBDEATH);
                wormscore(thissy - FIRSTTAIL, orb[which].score);
                if (worm[thissy - FIRSTTAIL].bias)
                {   worm[thissy - FIRSTTAIL].lives += ORBBLOOD;
                    stat(thissy - FIRSTTAIL, LIFE);
            }   }
            orb[which].alive = FALSE;
            field[x][y] = BONUS;
            draw(x, y, BONUS);
    }   }
	elif (thissy >= FIRSTHEAD && thissy <= LASTHEAD)
	{	if (worm[thissy - FIRSTHEAD].mode != ARMOUR)
    	{	worm[thissy - FIRSTHEAD].cause = ORB;
			worm[thissy - FIRSTHEAD].victor = -1;
			worm[thissy - FIRSTHEAD].alive = FALSE;
			if (orb[which].mode == ARMOUR)
			{	effect(FXUSEARMOUR);
				score = KILLWORM;
			} else
            {   effect(FXORBDEATH);
                orb[which].alive = FALSE;
                wormscore(thissy - FIRSTHEAD, orb[which].score);
                if (worm[thissy - FIRSTHEAD].bias)
                {   worm[thissy - FIRSTHEAD].lives += ORBBLOOD;
                    stat(thissy - FIRSTHEAD, LIFE);
        }   }   }
		else
		{	effect(FXUSEARMOUR);
            effect(FXORBDEATH);
            orb[which].alive = FALSE;
            wormscore(thissy - FIRSTHEAD, orb[which].score);
            if (worm[thissy - FIRSTHEAD].bias)
            {   worm[thissy - FIRSTHEAD].lives += ORBBLOOD;
                stat(thissy - FIRSTHEAD, LIFE);
		}   }
		orb[which].last = thissy - FIRSTHEAD + FIRSTTAIL; // note sign issues
	} elif (thissy >= FIRSTDRIP && thissy <= LASTDRIP)
	{	iwhich = whichdrip(x, y);
		drip[iwhich].alive = FALSE;
		if (orb[which].mode != ARMOUR)
		{	orb[which].explode = TRUE;
			draw(x, y, EMPTY);
	}	}
	elif (thissy >= FIRSTLETTER && thissy <= LASTLETTER)
	{	for (player = lo; player <= hi; player++)
		{	letters[player][thissy - FIRSTLETTER] = FALSE;
			drawletter(player, thissy, BLACK);
		}
		putletter(-1);
	} elif (thissy >= FIRSTMISSILE && thissy <= LASTMISSILE)
	{	missile[thissy - FIRSTMISSILE].alive = FALSE;
		if (orb[which].mode != ARMOUR)
        {   effect(FXORBDEATH);
            wormscore(thissy - FIRSTMISSILE, orb[which].score);
            if (worm[thissy - FIRSTMISSILE].bias)
            {   worm[thissy - FIRSTMISSILE].lives += ORBBLOOD;
                stat(thissy - FIRSTMISSILE, LIFE);
            }
            orb[which].alive = FALSE;
			draw(x, y, BONUS);
			field[x][y] = BONUS;
		} else effect(FXUSEARMOUR);
    } else switch (thissy) {
	case EMPTY:
		score = EMPTYPOINT;
		break;
	case SILVER:
		score = SILVERPOINT;
		break;
	case GOLD:
		score = GOLDPOINT;
		break;
	case TELEPORT:
        iwhich = whichteleport(x, y);
        if (blocked(iwhich, orb[which].deltax, orb[which].deltay))
            orb[which].alive = FALSE;
        else
        {   effect(FXUSETELEPORT);
            score = TELPOINT;
            orb[which].x = xwrap(teleport[level][partner(iwhich)].x + orb[which].deltax);
            orb[which].y = ywrap(teleport[level][partner(iwhich)].y + orb[which].deltay);
        }
		break;
	case ORB:
		for (iwhich = 0; iwhich <= ORBS; iwhich++)
			if (iwhich != which && orb[iwhich].alive && x == orb[iwhich].x && y == orb[iwhich].y)
            {   effect(FXORBDEATH);
                if (orb[iwhich].mode != ARMOUR)
				{	orb[iwhich].alive = FALSE;
					if (orb[which].mode != ARMOUR)
						orb[which].alive = FALSE;
					else effect(FXUSEARMOUR);
                } else
                {   orb[which].alive = FALSE;
					if (orb[which].mode == ARMOUR)
						orb[iwhich].alive = FALSE;
					else effect(FXUSEARMOUR);
				}
				if (orb[which].alive && !orb[iwhich].alive)
					score = orb[iwhich].score;
                elif (!orb[which].alive && orb[iwhich].alive)
					orbscore(iwhich, orb[which].score);
                elif (!orb[which].alive && !orb[iwhich].alive)
				{	draw(x, y, BONUS);
					field[x][y] = BONUS;
			}	}
		break;
	case FRAGMENT:
        whichfrag(x, y, &iwhich, &iiwhich);
        frag[iwhich][iiwhich].alive = FALSE;
        if (orb[which].mode != ARMOUR)
            orb[which].explode = TRUE;
        else effect(FXUSEARMOUR);
		break;
	case SKULL:
		effect(FXGETSKULL);
		score = SKULLPOINT;
		break;
	case SLIME:
		if (orb[which].mode != TONGUE)
			orb[which].alive = FALSE;
		break;
	default:
		break;
	}
	orbscore(which, score);
}

// update field

if (orb[which].alive)
{	if (orb[which].mode == TONGUE)
		draw(orb[which].x, orb[which].y, ORBTONGUE);
	elif (orb[which].mode == ARMOUR)
		draw(orb[which].x, orb[which].y, ORBARMOUR);
	else
		draw(orb[which].x, orb[which].y, ORB);
	field[orb[which].x][orb[which].y] = ORB;
}
}

void orbsplit(SBYTE which)
{	ABOOL	available;
	SBYTE	copy = 0, iwhich, iiwhich;

	effect(FXORBSPLIT);
	for (iwhich = 0; iwhich <= ORBS; iwhich++)
	{	available = TRUE;
		for (iiwhich = 0; iiwhich <= 7; iiwhich++)
			if (frag[iwhich][iiwhich].alive)
				available = FALSE;
		if (!orb[iwhich].alive && copy <= 3 && available)
		{	orb[iwhich].x		= orb[which].x;
			orb[iwhich].y		= orb[which].y;
			orb[iwhich].score	= orb[which].score;
			orb[iwhich].armour	= orb[which].armour;
			orb[iwhich].tongue	= orb[which].tongue;
			orb[iwhich].mode	= orb[which].mode;
			orb[iwhich].speed	= orb[which].speed;
			orb[iwhich].explode	= FALSE;
			orb[iwhich].multi	= orb[which].multi;
			orb[iwhich].moved	= FALSE;
			switch (copy)
			{
			case 0:
				if (orb[which].deltax != -1 || orb[which].deltay != -1)
				{	orb[iwhich].deltax = -1;
					orb[iwhich].deltay = -1;
					orb[iwhich].alive = TRUE;
				}
				break;
			case 1:
				if (orb[which].deltax != 1 || orb[which].deltay != 1)
				{	orb[iwhich].deltax = 1;
					orb[iwhich].deltay = 1;
					orb[iwhich].alive = TRUE;
				}
				break;
			case 2:
				if (orb[which].deltax != 1 || orb[which].deltay != -1)
				{	orb[iwhich].deltax = 1;
					orb[iwhich].deltay = -1;
					orb[iwhich].alive = TRUE;
				}
				break;
			case 3:
				if (orb[which].deltax != -1 || orb[which].deltay != 1)
				{	orb[iwhich].deltax = -1;
					orb[iwhich].deltay = 1;
					orb[iwhich].alive = TRUE;
				}
				break;
			default:
				break;
			}
			copy++;
}	}	}

void orientworms(void)
{	SBYTE	player;

	for (player = lo; player <= hi; 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);
		field[x][y] = letter + FIRSTLETTER;
		draw(x, y, 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(SBYTE which, SBYTE iwhich)
{	frag[which][iwhich].deltax	 = -frag[which][iwhich].deltax;
	frag[which][iwhich].deltay	 = -frag[which][iwhich].deltay;
	frag[which][iwhich].x		+=  frag[which][iwhich].deltax * 2;
	frag[which][iwhich].y		+=  frag[which][iwhich].deltay * 2;
}

void renderboard(void)
{	SBYTE x, y;

	switch(rand() % 3) {
	case 0:
		// chequered
		for (y = 0; y <= FIELDY; y += 2)
			for (x = 0; x <= FIELDX; x += 2)
				draw(x, y, board[level][x][y]);
		for (x = 1; x <= FIELDX; x += 2)
			for (y = 1; y <= FIELDY; y += 2)
				draw(x, y, board[level][x][y]);
		for (y = 0; y <= FIELDY; y += 2)
			for (x = 1; x <= FIELDX; x += 2)
				draw(x, y, board[level][x][y]);
		for (x = 0; x <= FIELDX; x += 2)
			for (y = 1; y <= FIELDY; y += 2)
				draw(x, y, board[level][x][y]);
		break;
	case 1:
		// `closing sliding doors'
		for (x = 0; x <= FIELDX / 2; x++)
			for (y = 0; y <= FIELDY; y++)
			{	draw(x, y, board[level][x][y]);
				draw(FIELDX - x, y, board[level][FIELDX - x][y]);
			}
		break;
	case 2:
		// `opening sliding doors'
		for (x = FIELDX / 2; x >= 0; x--)
			for (y = 0; y <= FIELDY; y++)
			{	draw(x, y, board[level][x][y]);
				draw(FIELDX - x, y, board[level][FIELDX - x][y]);
			}
		break;
	default:
		break;
	}

	if (a == FIELDEDIT)
	{	draw(startx[level], starty[level], START);
		if (teleport[level][0].alive)
		{	draw(teleport[level][0].x, teleport[level][0].y, ONE);
			draw(teleport[level][1].x, teleport[level][1].y, TWO);
}	}	}

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

	matchteleports();

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

	// write header

	strcpy(IOBuffer,					"FSET 4.3");
    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;
	}	}

	// 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 say((STRPTR) "Treasury", colour);
}

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

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

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

void slowloop(void)
{		ABOOL	available, done;
		SBYTE	i, iwhich, player, which, x, xx, y, yy;
PERSIST	SBYTE	type = FIRSTDRIP;

// decrement strength

for (player = lo; player <= hi; 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 = lo; which <= hi; 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);
	}	}

if (ice == -1)
{	for (which = 0; which <= ORBS; which++)
		if (orb[which].alive)
			if (orb[which].mode == ARMOUR)
			{	if (--orb[which].armour == 0)
					if (orb[which].tongue > 0)
						orb[which].mode = TONGUE;
					else orb[which].mode = NULL;
			} elif (orb[which].mode == TONGUE)
			{	if (--orb[which].tongue == 0)
					if (orb[which].armour > 0)
						orb[which].mode = ARMOUR;
					else orb[which].mode = NULL;
			}

	// create killers

	for (which = 0; which <= KILLERS; which++)
		if (!killer[which].alive && !(rand() % killerfreq) && findempty(&x, &y, FIRSTKILLER, LASTKILLER) && field[x][y] != KILLER)
		{	effect(FXKILLERBORN);
			killer[which].x = x;
			killer[which].y = y;
			killer[which].alive = TRUE;
			killer[which].last = field[killer[which].x][killer[which].y];
			field[killer[which].x][killer[which].y] = KILLER;
			draw(killer[which].x, killer[which].y, KILLER);
		}

	// create orbs

	for (which = 0; which <= ORBS; which++)
		if (!orb[which].alive && !(rand() % orbfreq))
		{	orb[which].x = rand() % (FIELDX + 1);
			orb[which].y = rand() % (FIELDY + 1);
			available = TRUE;
			for (iwhich = 0; iwhich <= 7; iwhich++)
				if (frag[which][iwhich].alive)
					available = FALSE;
			if (available && field[orb[which].x][orb[which].y] >= FIRSTEMPTY && field[orb[which].x][orb[which].y] <= LASTEMPTY)
			{	orb[which].deltax = (rand() % 2) * 2 - 1;
				orb[which].deltay = (rand() % 2) * 2 - 1;
				if (field[orb[which].x + orb[which].deltay][orb[which].y + orb[which].deltay] >= FIRSTEMPTY && field[orb[which].x + orb[which].deltax][orb[which].y + orb[which].deltay] <= LASTEMPTY)
				{	effect(FXORBBORN);
                    orb[which].score = 0;
					orb[which].alive = TRUE;
					orb[which].armour = 0;
					orb[which].tongue = 0;
					orb[which].mode = NULL;
					orb[which].speed = orbspeed;
					orb[which].explode = FALSE;
					orb[which].multi = 1;
					orb[which].moved = FALSE;
		}	}	}

	// create drips

	for (which = 0; which <= DRIPS; which++)
		if (!drip[which].alive && !(rand() % dripfreq))
		{	drip[which].x = rand() % (FIELDX + 1);
			drip[which].y = rand() % 3;
			if (field[drip[which].x][drip[which].y] >= FIRSTEMPTY && field[drip[which].x][drip[which].y] <= LASTEMPTY
			&& field[drip[which].x][drip[which].y + 1] >= FIRSTEMPTY && field[drip[which].x][drip[which].y + 1] <= LASTEMPTY)
			{	effect(FXDRIPBORN);
				drip[which].alive = TRUE;
				drip[which].moved = FALSE;
				drip[which].type = type;
				if (++type > LASTDRIP)
					type = FIRSTDRIP;
		}	}

	// create objects

	for (which = 0; which <= LASTOBJECT; which++)
		if (!(rand() % object[which].frequency) && findempty(&x, &y, FIRSTEMPTY, LASTEMPTY))
		{	field[x][y] = which;
			draw(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;
		field[teleport[level][2].x][teleport[level][2].y] = TELEPORT;
		draw(teleport[level][2].x, teleport[level][2].y, TELEPORT);
		teleport[level][3].alive = TRUE;
		field[teleport[level][3].x][teleport[level][3].y] = TELEPORT;
		draw(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;
						field[timebomb[which].x][timebomb[which].y] = EMPTY;
						draw(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);
			}	}

	// create slime
	
	if (!(rand() % slimefreq))
	{	if (findempty(&x, &y, FIRSTEMPTY, LASTEMPTY))
		{	field[x][y] = SLIME;
			draw(x, y, SLIME);
	}	}

	// grow slime

	if (!(rand() % slimefreq))
	{	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)
				{	field[x][y] = SLIME;
					draw(x, y, SLIME);
	}			}
			
	// retry putting letters if necessary

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

// blank out old causes

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

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

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

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

	if (thissy <= LASTOBJECT)
	{	field[x][y] = filler;
		draw(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)
	{	draw(x, y, filler);
		field[x][y] = filler;
	} elif (thissy == ORB)
	{	which = whichorb(x, y);
		if (orb[which].mode != ARMOUR)
        {   effect(FXORBDEATH);
            orb[which].alive = FALSE;
			score = orb[which].score;
            if (type == HEAD && worm[player].bias)
                 worm[player].lives += ORBBLOOD;
            draw(x, y, BONUS);
			field[x][y] = BONUS;
		} else effect(FXUSEARMOUR);
	} elif (thissy >= FIRSTDRIP && thissy <= LASTDRIP)
	{	which = whichdrip(x, y);
		drip[which].alive = FALSE;
		draw(x, y, filler);
		field[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)
        {   missile[thissy - FIRSTMISSILE].alive = FALSE;
			draw(x, y, filler);
			field[x][y] = filler;
	}	}
	elif (thissy == FRAGMENT)
	{	whichfrag(x, y, &which, &iwhich);
		frag[which][iwhich].alive = FALSE;
		draw(x, y, filler);
		field[x][y] = filler;
	} elif (thissy == KILLER)
	{	which = whichkiller(x, y);
		killer[which].alive = FALSE;
		draw(x, y, BONUS);
		field[x][y] = BONUS;
	}
	return(score);
}

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

	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;
			  dripspeed	= speedup(dripspeed,	  TRUE);
			  fragspeed	= speedup(fragspeed,	  TRUE);
			   orbspeed	= speedup(orbspeed,		  TRUE);
			killerspeed	= speedup(killerspeed,	  TRUE);
			  dripfreq  = atleast(  dripfreq / 2,   DRIPFREQ_MIN);
	        killerfreq  = atleast(killerfreq / 2, KILLERFREQ_MIN);
			   orbfreq  = atleast(   orbfreq / 2,    ORBFREQ_MIN);
			 slimefreq  = atleast( slimefreq / 2,  SLIMEFREQ_MIN);
	}	}
	else
	{	outoftime = FALSE;
		say(timedisplay, WHITE);
}	}

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

	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)
					{	draw(x, y, METAL);
						field[x][y] = METAL;
					} elif (field[x][y] != STONE && field[x][y] != KILLER && field[x][y] != ORB && field[x][y] != SKULL && (field[x][y] < FIRSTHEAD || field[x][y] > LASTHEAD) && (field[x][y] < FIRSTLETTER || field[x][y] > LASTLETTER))
					{	draw(x, y, GOLD);
						field[x][y] = GOLD;
		}			}
		break;
	case NUMERICPLUS:
		// Full lives, tongue, bias, ammo, power, nitro and affixer.
		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;
				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, worm[player].nitro);
		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);
}

SBYTE whichdrip(SBYTE x, SBYTE y)
{   SBYTE which;

    for (which = 0; which <= DRIPS; which++)
        if (drip[which].alive && drip[which].x == x && drip[which].y == y)
            return(which);
    return(-1); // error code
}
void whichfrag(SBYTE x, SBYTE y, SBYTE* whichptr, SBYTE* iwhichptr)
{	SBYTE which, iwhich;

	for (which = 0; which <= ORBS + 1; which++)
        for (iwhich = 0; iwhich <= 7; iwhich++)
            if (frag[which][iwhich].alive && frag[which][iwhich].x == x && frag[which][iwhich].y == y)
			{	*whichptr = which;
				*iwhichptr = iwhich;
				return;
}			}
SBYTE whichkiller(SBYTE x, SBYTE y)
{   SBYTE which;

    for (which = 0; which <= KILLERS; which++)
        if (killer[which].alive && killer[which].x == x && killer[which].y == y)
            return(which);
    return(-1); // error code
}
SBYTE whichorb(SBYTE x, SBYTE y)
{   SBYTE which;

    for (which = 0; which <= ORBS; which++)
        if (orb[which].alive && orb[which].x == x && orb[which].y == y)
            return(which);
    return(-1); // 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,
			ok			= FALSE,
			lettered	= FALSE;
    LONG	score;
    SBYTE	distance,
			thissy,
			i, j, k,
			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 > KILLER) && thissy != METAL))
			{	// Amiga-worms only make jumping sounds in demo mode
				flag = FALSE;
				for (i = lo; i <= hi; 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 (!missile[player].alive && worm[player].bias)
        {   missile[player].alive	= TRUE;
			missile[player].x		= worm[player].x;
			missile[player].y		= worm[player].y;
			missile[player].moved	= FALSE;
		}
		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 + worm[player].deltay + 1;
            } elif (worm[player].deltay == 0)
			{	bullet[i].x = worm[player].x + worm[player].deltax + 1;
				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)))
               	bullet[i].alive = FALSE;
		}
		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)
						if (bullet[i].teleported)
                        {   if (worm[player].bias)
							{	draw(bullet[i].x, bullet[i].y, GOLD);
								field[bullet[i].x][bullet[i].y] = GOLD;
							} else
							{	draw(bullet[i].x, bullet[i].y, SILVER);
								field[bullet[i].x][bullet[i].y] = SILVER;
						}	}
						else
						{	draw(bullet[i].x, bullet[i].y, EMPTY);
							field[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
					{	thissy = field[bullet[i].x][bullet[i].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)
                                missile[thissy - FIRSTMISSILE].alive = FALSE;
                            else bullet[i].visible = FALSE;
						} elif (thissy >= FIRSTDRIP && thissy <= LASTDRIP)
						{	j = whichdrip(bullet[i].x, bullet[i].y);
							drip[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 = whichorb(bullet[i].x, bullet[i].y);
								if (orb[j].mode != ARMOUR)
                                {   orb[j].explode = TRUE;
                                    score += orb[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 KILLER:
	                            for (j = 0; j <= KILLERS; j++)
	                                if (killer[j].alive && killer[j].x == bullet[i].x && killer[j].y == bullet[i].y)
	                                {   killer[j].alive = FALSE;
	                                    draw(bullet[i].x, bullet[i].y, BONUS);
	                                    field[bullet[i].x][bullet[i].y] = BONUS;
	                                    score += KILLKILLER + HITSHOT;
	                                    if (worm[player].bias)
											worm[player].lives += KILLERBLOOD;
       								}
       	                        bullet[i].alive = FALSE;
       	                        break;
       						case SKULL:
       							bullet[i].alive = FALSE;
       							break;
       						case FRAGMENT:
       						    bullet[i].alive = FALSE;
       							whichfrag(bullet[i].x, bullet[i].y, &j, &k);
       							frag[j][k].alive = FALSE;
       							field[bullet[i].x][bullet[i].y] = EMPTY;
       							draw(bullet[i].x, bullet[i].y, EMPTY);
       							break;
       						case BOMB:
       							j = whichtimebomb(bullet[i].x, bullet[i].y);
       							if (j != -1)
       							{   bullet[i].alive = FALSE;
       							    timebomb[j].alive = FALSE;
       								field[timebomb[j].x][timebomb[j].y] = EMPTY;
       								draw(timebomb[j].x, timebomb[j].y, EMPTY);
       								bombblast(HEAD, player, timebomb[j].x, timebomb[j].y);
       							}
       							break;
       						default:
       							break;
							}
           	        		if (bullet[i].alive && bullet[i].visible)
								draw(bullet[i].x, bullet[i].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	complete, done, flag;
SBYTE	bestgood, bestx, besty, creature, dirx, diry, good, i, ithis,
		iwhich, thissy, thisletter, thisprot = -1, which, x, xx, y, yy;
LONG    score = 0L;

	/* 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)
		finer gradients of decision
			(eg. certain objects better than others)
		longer lookahead */

	if (worm[player].control == AMIGA)
	{	if (!(rand() % 200))
			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 = 80;
				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 KILLER:
					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

field[worm[player].x][worm[player].y] = worm[player].last;
draw(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)
        {   draw(protector[player][which].x, protector[player][which].y, protector[player][which].last);
            field[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)
			{	if (worm[player].position == -1)
					worm[player].posidir = 1;
				elif (worm[player].position == 1)
					worm[player].posidir = -1;
				worm[player].position += worm[player].posidir;
				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

creature = HEAD;
while (creature != NULL)
{	if (creature == HEAD)
    {   x = worm[player].x;
		y = worm[player].y;
    } else
	{	x = protector[player][thisprot].x;
		y = protector[player][thisprot].y;
	}
	thissy = field[x][y];
    if (creature == HEAD)
    {   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 (lo != hi)
					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)
        	{   missile[thissy - FIRSTMISSILE].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)
		{	which = whichdrip(x, y);
			drip[which].alive = FALSE;
			if (player == thissy - FIRSTDRIP || worm[player].bias)
			{	effect(FXFX);
				wormscore(player, DRIPBONUS);
			} else
			{	worm[player].alive = FALSE;
				worm[player].cause = thissy;
				worm[player].victor = -1;
			}
        } elif (thissy == STONE || thissy == KILLER || thissy == METAL)
        {   worm[player].cause = thissy;
            worm[player].victor = -1;
            worm[player].alive = FALSE;
            ramming(player);
        } elif (thissy == WOOD)
        {   if (worm[player].mode != TONGUE)
            {   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 == 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);
    }   }   }
	else // creature == PROTECTOR
    {   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;
            }
            draw(x, y, EMPTY);
            field[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);
                missile[thissy - FIRSTMISSILE].alive = FALSE;
            } else protector[player][thisprot].visible = FALSE;
        } elif (thissy == TELEPORT)
        {   protector[player][thisprot].visible = FALSE;
            score += TELPOINT;
		} elif (thissy >= FIRSTDRIP && thissy <= LASTDRIP)
		{	which = whichdrip(x, y);
			drip[which].alive = FALSE;
			if (player == thissy - FIRSTDRIP || worm[player].bias)
			{	score += DRIPBONUS;
				effect(FXFX);
		}	}
		elif (thissy == KILLER)
        {   effect(FXUSEPROTECTOR);
			effect(FXKILLERDEATH);
			which = whichkiller(x, y);
            protector[player][thisprot].last = BONUS;
            killer[which].alive = FALSE;
            score += KILLKILLER;
            if (worm[player].bias)
            {   worm[player].lives += KILLERBLOOD;
                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:
            if (!worm[player].bias)
				thisletter = rand() % (LETTERS + 1);
			else
			{	complete = TRUE;
				for (which = 0; which <= LETTERS; which++)
                    if (!letters[player][which])
					{	complete = FALSE;
						do thisletter = rand() % (LETTERS + 1);
                        while (letters[player][thisletter]);
						break;
					}
                if (complete)
					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 (creature == 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 <= ORBS; which++)
				if (orb[which].alive)
                {   score += orb[which].score;
					orb[which].explode = TRUE;
					if (worm[player].bias)
						worm[player].lives += ORBBLOOD;
				}
			for (which = 0; which <= KILLERS; which++)
				if (killer[which].alive)
				{	effect(FXKILLERDEATH);
					killer[which].alive = FALSE;
                    score += KILLKILLER;
					if (worm[player].bias)
						worm[player].lives += KILLERBLOOD;
					field[killer[which].x][killer[which].y] = BONUS;
					draw(killer[which].x, killer[which].y, BONUS);
				}
			for (which = 0; which <= DRIPS; which++)
			{	drip[which].alive = FALSE;
				field[drip[which].x][drip[which].y] = EMPTY;
				draw(drip[which].x, drip[which].y, EMPTY);
			}
			for (which = lo; which <= hi; 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)
					{	draw(x, y, EMPTY);
						field[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:
            if (!missile[player].alive)
			{	missile[player].alive = TRUE;
				missile[player].x = worm[player].x;
				missile[player].y = worm[player].y;
				missile[player].moved = FALSE;
			}
			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:
						field[x][y] = GOLD;
						draw(x, y, GOLD);
						break;
					case TEMPSILVER:
						field[x][y] = SILVER;
						draw(x, y, SILVER);
						break;
					case TEMPTAIL:
						field[x][y] = FIRSTTAIL + player;
						draw(x, y, FIRSTTAIL + player);
						break;
					default:
						break;
					}
			break;
		case TREASURE:
			treasurer = player;
			if (level)
			{	say((STRPTR) "Treasure!", worm[treasurer].colour);
				secondsperlevel = 0;
			}
			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 (lo != hi)
				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)
						{	field[x][y] = FIRSTTAIL + player;
							draw(x, y, FIRSTTAIL + 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() % 3);
			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;
        default:
            break;
	}	}
    else
    {   switch (thissy)
		{
		case EMPTY:
            score += EMPTYPOINT;
			break;
		case SILVER:
            score += SILVERPOINT;
			break;
		case GOLD:
            score += GOLDPOINT;
			break;
		case ORB:
            iwhich = whichorb(x, y);
			if (worm[player].mode == ARMOUR || creature == PROTECTOR)
            {   if (creature == HEAD)
					effect(FXUSEARMOUR);
                else effect(FXUSEPROTECTOR);
				effect(FXORBDEATH);
                score += orb[iwhich].score;
                orb[iwhich].alive = FALSE;
                if (worm[player].bias)
                {   worm[player].lives += ORBBLOOD;
                    stat(player, LIFE);
            }   }
			else
            {   if (orb[iwhich].mode == ARMOUR)
                {   effect(FXUSEARMOUR);
                    orbscore(iwhich, KILLWORM);
                } else orb[iwhich].alive = FALSE;
				worm[player].cause = ORB;
				worm[player].victor = -1;
				worm[player].alive = FALSE;
			}
			break;
		case FRAGMENT:
            whichfrag(x, y, &which, &iwhich);
            if (creature == HEAD)
            {	if (worm[player].mode != ARMOUR)
            	{   worm[player].cause = FRAGMENT;
                    worm[player].victor = -1;
                    worm[player].alive = FALSE;
					frag[which][iwhich].alive = FALSE;
                } else
				{	effect(FXUSEARMOUR);
					reflect(which, iwhich);
			}	}
			else
			{	effect(FXUSEPROTECTOR);
				reflect(which, iwhich);
			}
			break;
		case SKULL:
			effect(FXGETSKULL);
            score += SKULLPOINT;
			for (which = lo; which <= hi; 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 (creature == 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 creature == PROTECTOR
		if (protector[player][thisprot].alive && protector[player][thisprot].visible)
    	{   field[x][y] = FIRSTPROTECTOR + player;
    	    draw(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)
		creature = NULL;
	else creature = PROTECTOR;
}
wormscore(player, score);
}

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

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

SWORD xpixeltosquare(SWORD x)
{	x = (x - STARTXPIXEL) / SQUAREX;
	if (x < 0)
		x--;
	return (x);
}
SWORD ypixeltosquare(SWORD y)
{	y = (y - STARTYPIXEL) / SQUAREY;
	if (y < 0)
		y--;
	return (y);
}

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;
}	}

void driploop(void)
{	SBYTE i, j, k, thissy;

	for (i = 0; i <= DRIPS; i++)
		if (drip[i].alive)
		{	if (drip[i].moved)
			{	field[drip[i].x][drip[i].y] = EMPTY;
				draw(drip[i].x, drip[i].y, EMPTY);
			} else drip[i].moved = TRUE;
			drip[i].y++;
			if (!valid(drip[i].x, drip[i].y))
				drip[i].alive = FALSE;
			else
			{	thissy = field[drip[i].x][drip[i].y];
				if (thissy >= FIRSTHEAD && thissy <= LASTHEAD)
				{	drip[i].alive = FALSE;
					if (drip[i].type - FIRSTDRIP == thissy - FIRSTHEAD || worm[thissy - FIRSTHEAD].bias)
					{	effect(FXFX);
						wormscore(thissy - FIRSTHEAD, DRIPBONUS);
					} else
					{	worm[thissy - FIRSTHEAD].alive = FALSE;
						worm[thissy - FIRSTHEAD].cause = drip[i].type;
						worm[thissy - FIRSTHEAD].victor = -1;
				}	}
				elif (thissy >= FIRSTPROTECTOR && thissy <= LASTPROTECTOR)
				{	drip[i].alive = FALSE;
					if (drip[i].type == thissy - FIRSTPROTECTOR + FIRSTDRIP || worm[thissy - FIRSTPROTECTOR].bias)
					{	wormscore(thissy - FIRSTPROTECTOR, DRIPBONUS);
						effect(FXFX);
				}	}
				elif (thissy >= FIRSTLETTER && thissy <= LASTLETTER)
					drip[i].alive = FALSE;
				elif (thissy >= FIRSTMISSILE && thissy <= LASTMISSILE)
					missile[thissy - FIRSTMISSILE].alive = FALSE;
				else switch(thissy)
				{
				case FRAGMENT:
					drip[i].alive = FALSE;
					whichfrag(drip[i].x, drip[i].y, &j, &k);
					frag[j][k].alive = BONUS;
					draw(drip[i].x, drip[i].y, BONUS);
				case ORB:
					drip[i].alive = FALSE;
					j = whichorb(drip[i].x, drip[i].y);
					if (orb[j].mode != ARMOUR)
					{	orb[j].explode = TRUE;
						draw(drip[i].x, drip[i].y, EMPTY);
					}
					break;
				case KILLER:
					drip[i].alive = FALSE;
					j = whichkiller(drip[i].x, drip[i].y);
					killer[j].alive = FALSE;
					field[drip[i].x][drip[i].y] = BONUS;
					draw(drip[i].x, drip[i].y, BONUS);
					break;
				case SLIME:
					drip[i].alive = FALSE;
					field[drip[i].x][drip[i].y] = EMPTY;
					draw(drip[i].x, drip[i].y, EMPTY);
					break;
				case STONE:
					drip[i].alive = FALSE;
					break;
				case METAL:
					drip[i].alive = FALSE;
					break;
				case TELEPORT:
					j = whichteleport(drip[i].x, drip[i].y);
					if (blocked(j, 0, 1))
						drip[i].alive = FALSE;
					else
					{	drip[i].x = teleport[level][partner(j)].x;
						drip[i].y = teleport[level][partner(j)].y;
						drip[i].moved = FALSE;
					}
					break;
				default:
					break;
				}
				if (drip[i].alive)
				{	draw(drip[i].x, drip[i].y, drip[i].type);
					field[drip[i].x][drip[i].y] = drip[i].type;
}		}	}	}
	
SWORD atleast(SWORD value, SWORD minimum)
{	if (value < minimum)
		return(minimum);
	else return(value);
}

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

// Must have blank line at EOF.
