//Copyright Paul Reiche, Fred Ford. 1992-2002

/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "../ship.h"
#include "sis_ship.h"
#include "resinst.h"

#include "uqm/colors.h"
#include "uqm/controls.h"
#include "uqm/globdata.h"
#include "uqm/hyper.h"
#include "libs/mathlib.h"

/* Core characteristics.
 * All of these are changed at init time by some module, except for
 * MAX_ENERGY, THRUST_INCREMENT, and SHIP_MASS. */

#define MAX_CREW MAX_CREW_SIZE
		/* This value gets thrown out - actual max crew is determined by the
		 * number of crew pods. The minimum value is 1 (just the Captain). */

#define MAX_ENERGY MAX_ENERGY_SIZE
#define ENERGY_REGENERATION 1
		/* Shiva furnaces increase this by 1 each. */
#define SHIVA_ENERGY_REGEN_INC 1

#define ENERGY_WAIT 10
		/* Dynamos decrease this by 2 each, to a minimum of 4. */
#define MIN_ENERGY_WAIT 4
#define DYNAMO_UNIT_ENERGY_WAIT_DEC 2

#define MAX_THRUST 10
		/* Thrusters increase this and decrease THRUST_WAIT based on
		 * THRUST_INCREMENT, see InitDriveSlots near the bottom of this file
		 * for details. */
#define THRUST_INCREMENT 4
#define THRUST_WAIT 6
#define TURN_WAIT 17
		/* Turning jets decrease by 2 each */
#define SHIP_MASS MAX_SHIP_MASS


/* Primary weapon - energy cost and damage change at init time based on
 * the number and type of weapon modules installed. */

#define BLASTER_DAMAGE 2
		/* This is the damage value for the basic ion bolt guns. Fusion
		 * blasters and hellbore cannons end up doing (BLASTER_DAMAGE * 2)
		 * and (BLASTER_DAMAGE * 3) damage, respectively, but this depends
		 * on enum values. */

#define BLASTER_HITS 2 /* Hitpoints for ion bolt guns, see BLASTER_DAMAGE */

#define WEAPON_ENERGY_COST 1
		/* This value gets thrown out and reset in an ugly manner based on
		 * the enum that is used for module IDs. Bigger gun = higher value.
		 */
#define WEAPON_WAIT 6
#define BLASTER_SPEED DISPLAY_TO_WORLD (24)
#define BLASTER_LIFE 12
		/* This value is greatly increased, based in part on the enum used
		 * for module IDs (bigger gun == longer life). See the first half of
		 * InitWeaponSlots */
#define MAX_TRACKING 3
#define TRACKER_ENERGY_COST 3
#define BLASTER_OFFSET 8
#define SIS_VERT_OFFSET 28
		/* Used for foward, spread, and rear slots */
#define SIS_HORZ_OFFSET 20
		/* Used for side slot */


/* Secondary weapon */
#define SPECIAL_ENERGY_COST 0
		/* Increased by 1 for each point defense module */
#define ANTIMISSILE_ENERGY_INC 1
#define SPECIAL_WAIT 9
#define LASER_RANGE (UWORD)100
#define MAX_DEFENSE 8


static RACE_DESC sis_desc =
{
	{ /* SHIP_INFO */
		"flagship",
		0,
		16, /* Super Melee cost */
		MAX_CREW, MAX_CREW,
		MAX_ENERGY, MAX_ENERGY,
		NULL_RESOURCE,
		SIS_ICON_MASK_PMAP_ANIM,
		NULL_RESOURCE,
		NULL, NULL, NULL
	},
	{ /* FLEET_STUFF */
		0, /* Initial sphere of influence radius */
		{ /* Known location (center of SoI) */
			0, 0,
		},
	},
	{
		MAX_THRUST,
		THRUST_INCREMENT,
		ENERGY_REGENERATION,
		WEAPON_ENERGY_COST,
		SPECIAL_ENERGY_COST,
		ENERGY_WAIT,
		TURN_WAIT,
		THRUST_WAIT,
		WEAPON_WAIT,
		SPECIAL_WAIT,
		SHIP_MASS,
	},
	{
		{
			SIS_BIG_MASK_PMAP_ANIM,
			SIS_MED_MASK_PMAP_ANIM,
			SIS_SML_MASK_PMAP_ANIM,
		},
		{
			BLASTER_BIG_MASK_PMAP_ANIM,
			BLASTER_MED_MASK_PMAP_ANIM,
			BLASTER_SML_MASK_PMAP_ANIM,
		},
		{
			NULL_RESOURCE,
			NULL_RESOURCE,
			NULL_RESOURCE,
		},
		{
			SIS_CAPTAIN_MASK_PMAP_ANIM,
			NULL, NULL, NULL, NULL, NULL
		},
		SIS_VICTORY_SONG,
		SIS_SHIP_SOUNDS,
		{ NULL, NULL, NULL },
		{ NULL, NULL, NULL },
		{ NULL, NULL, NULL },
		NULL, NULL
	},
	{
		0,
		BLASTER_SPEED * BLASTER_LIFE,
		NULL,
	},
	(UNINIT_FUNC *) NULL,
	(PREPROCESS_FUNC *) NULL,
	(POSTPROCESS_FUNC *) NULL,
	(INIT_WEAPON_FUNC *) NULL,
	0,
	0, /* CodeRef */
};

// Private per-instance SIS data
typedef struct
{
	COUNT num_trackers;
	COUNT num_blasters;
	MISSILE_BLOCK MissileBlock[6];

} SIS_DATA;

static void InitWeaponSlots (RACE_DESC *RaceDescPtr,
		const BYTE *ModuleSlots);
static void InitModuleSlots (RACE_DESC *RaceDescPtr,
		const BYTE *ModuleSlots);
static void InitDriveSlots (RACE_DESC *RaceDescPtr,
		const BYTE *DriveSlots);
static void InitJetSlots (RACE_DESC *RaceDescPtr,
		const BYTE *JetSlots);
static void uninit_sis (RACE_DESC *pRaceDesc);


// Local typedef
typedef SIS_DATA CustomShipData_t;

// Retrieve race-specific ship data from a race desc
static CustomShipData_t *
GetCustomShipData (RACE_DESC *pRaceDesc)
{
	return pRaceDesc->data;
}

// Set the race-specific data in a race desc
// (Re)Allocates its own storage for the data.
static void
SetCustomShipData (RACE_DESC *pRaceDesc, const CustomShipData_t *data)
{
	if (pRaceDesc->data == data)
		return;  // no-op

	if (pRaceDesc->data) // Out with the old
	{
		HFree (pRaceDesc->data);
		pRaceDesc->data = NULL;
	}

	if (data) // In with the new
	{
		CustomShipData_t* newData = HMalloc (sizeof (*data));
		*newData = *data;
		pRaceDesc->data = newData;
	}
}

static void
sis_hyper_preprocess (ELEMENT *ElementPtr)
{
	SIZE udx = 0, udy = 0;
	SIZE dx = 0, dy = 0;
	SIZE AccelerateDirection;
	STARSHIP *StarShipPtr;

	if (ElementPtr->state_flags & APPEARING)
		ElementPtr->velocity = GLOBAL (velocity);

	AccelerateDirection = 0;

	GetElementStarShip (ElementPtr, &StarShipPtr);
	++StarShipPtr->weapon_counter; /* no shooting in hyperspace! */
	if ((GLOBAL (autopilot)).x == ~0
			|| (GLOBAL (autopilot)).y == ~0
			|| (StarShipPtr->cur_status_flags & (LEFT | RIGHT | THRUST)))
	{
LeaveAutoPilot:
		(GLOBAL (autopilot)).x =
				(GLOBAL (autopilot)).y = ~0;
		if (!(StarShipPtr->cur_status_flags & THRUST)
				|| (GLOBAL_SIS (FuelOnBoard) == 0
				&& GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1))
		{
			AccelerateDirection = -1;
			GetCurrentVelocityComponents (&ElementPtr->velocity,
					&dx, &dy);
			udx = dx << 4;
			udy = dy << 4;

			StarShipPtr->cur_status_flags &= ~THRUST;
		}
	}
	else
	{
		SIZE facing;
		POINT universe;

		universe.x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x));
		universe.y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y));
		udx = (GLOBAL (autopilot)).x - universe.x;
		udy = -((GLOBAL (autopilot)).y - universe.y);
		if ((dx = udx) < 0)
			dx = -dx;
		if ((dy = udy) < 0)
			dy = -dy;
		if (dx <= 1 && dy <= 1)
			goto LeaveAutoPilot;

		facing = NORMALIZE_FACING (ANGLE_TO_FACING (ARCTAN (udx, udy)));

		/* This prevents ship from flying backwards on auto-pilot.
		 * It could also theoretically abort autopilot in a bad savegame */
		if ((StarShipPtr->cur_status_flags & SHIP_AT_MAX_SPEED)
				/*|| (ElementPtr->state_flags & APPEARING)*/ )
		{
			if (NORMALIZE_FACING (StarShipPtr->ShipFacing
					+ ANGLE_TO_FACING (QUADRANT)
					- facing) > ANGLE_TO_FACING (HALF_CIRCLE))
				goto LeaveAutoPilot;

			facing = StarShipPtr->ShipFacing;
		}
		else if ((int)facing != (int)StarShipPtr->ShipFacing
				&& ElementPtr->turn_wait == 0)
		{
			if (NORMALIZE_FACING (
					StarShipPtr->ShipFacing - facing
					) >= ANGLE_TO_FACING (HALF_CIRCLE))
			{
				facing = NORMALIZE_FACING (facing - 1);
				StarShipPtr->cur_status_flags |= RIGHT;
			}
			else if ((int)StarShipPtr->ShipFacing != (int)facing)
			{
				facing = NORMALIZE_FACING (facing + 1);
				StarShipPtr->cur_status_flags |= LEFT;
			}

			if ((int)facing == (int)StarShipPtr->ShipFacing)
			{
				ZeroVelocityComponents (&ElementPtr->velocity);
			}
		}

		GetCurrentVelocityComponents (&ElementPtr->velocity, &dx, &dy);
		if ((GLOBAL_SIS (FuelOnBoard)
				|| GET_GAME_STATE (ARILOU_SPACE_SIDE) > 1)
				&& (int)facing == (int)StarShipPtr->ShipFacing)
		{
			StarShipPtr->cur_status_flags |= SHIP_AT_MAX_SPEED;
			AccelerateDirection = 1;
		}
		else
		{
			AccelerateDirection = -1;
			udx = dx << 4;
			udy = dy << 4;
		}
	}

	if (ElementPtr->thrust_wait == 0 && AccelerateDirection)
	{
		COUNT dist;
		SIZE speed, velocity_increment;

		velocity_increment = WORLD_TO_VELOCITY (
				StarShipPtr->RaceDescPtr->characteristics.thrust_increment);

		if ((dist = square_root ((long)udx * udx + (long)udy * udy)) == 0)
			dist = 1; /* prevent divide by zero */

		speed = square_root ((long)dx * dx + (long)dy * dy);
		if (AccelerateDirection < 0)
		{
			dy = (speed / velocity_increment - 1) * velocity_increment;
			if (dy < speed - velocity_increment)
				dy = speed - velocity_increment;
			if ((speed = dy) < 0)
				speed = 0;

			StarShipPtr->cur_status_flags &= ~SHIP_AT_MAX_SPEED;
		}
		else
		{
			SIZE max_velocity;

			AccelerateDirection = 0;

			max_velocity = WORLD_TO_VELOCITY (
					StarShipPtr->RaceDescPtr->characteristics.max_thrust);

			dy = (speed / velocity_increment + 1)
					* velocity_increment;
			if (dy < speed + velocity_increment)
				dy = speed + velocity_increment;
			if ((speed = dy) > max_velocity)
			{
				speed = max_velocity;
				StarShipPtr->cur_status_flags |= SHIP_AT_MAX_SPEED;
			}
		}

		dx = (SIZE)((long)udx * speed / (long)dist);
		dy = (SIZE)((long)udy * speed / (long)dist);
		SetVelocityComponents (&ElementPtr->velocity, dx, dy);

		ElementPtr->thrust_wait =
				StarShipPtr->RaceDescPtr->characteristics.thrust_wait;
	}
}

static void
sis_hyper_postprocess (ELEMENT *ElementPtr)
{
	STARSHIP *StarShipPtr;

	GLOBAL (velocity) = ElementPtr->velocity;

	GetElementStarShip (ElementPtr, &StarShipPtr);
	if (((StarShipPtr->cur_status_flags & WEAPON) ||
			PulsedInputState.menu[KEY_MENU_CANCEL])
			&& StarShipPtr->special_counter == 0)
	{
#define MENU_DELAY 10
		HyperspaceMenu ();
		StarShipPtr->cur_status_flags &= ~SHIP_AT_MAX_SPEED;
		StarShipPtr->special_counter = MENU_DELAY;
	}
}

static void
spawn_point_defense (ELEMENT *ElementPtr)
{
	STARSHIP *StarShipPtr;

	GetElementStarShip (ElementPtr, &StarShipPtr);
	if (ElementPtr->state_flags & PLAYER_SHIP)
	{
		HELEMENT hDefense;

		hDefense = AllocElement ();
		if (hDefense)
		{
			ELEMENT *DefensePtr;

			LockElement (hDefense, &DefensePtr);
			DefensePtr->playerNr = ElementPtr->playerNr;
			DefensePtr->state_flags = APPEARING | NONSOLID | FINITE_LIFE;
			DefensePtr->death_func = spawn_point_defense;
			
			GetElementStarShip (ElementPtr, &StarShipPtr);
			SetElementStarShip (DefensePtr, StarShipPtr);
			UnlockElement (hDefense);

			PutElement (hDefense);
		}
	}
	else
	{
		BOOLEAN PaidFor;
		HELEMENT hObject, hNextObject;
		ELEMENT *ShipPtr;
		Color LaserColor;
		static const Color ColorRange[] =
		{
			BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x03, 0x00), 0x7F),
			BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x07, 0x00), 0x7E),
			BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0A, 0x00), 0x7D),
			BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0E, 0x00), 0x7C),
			BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x11, 0x00), 0x7B),
			BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x15, 0x00), 0x7A),
			BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x18, 0x00), 0x79),
			BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x1C, 0x00), 0x78),
		};

		PaidFor = FALSE;

		LaserColor = ColorRange[
				StarShipPtr->RaceDescPtr->characteristics.special_energy_cost
				];
		LockElement (StarShipPtr->hShip, &ShipPtr);
		for (hObject = GetTailElement (); hObject; hObject = hNextObject)
		{
			ELEMENT *ObjectPtr;

			LockElement (hObject, &ObjectPtr);
			hNextObject = GetPredElement (ObjectPtr);
			if (ObjectPtr != ShipPtr && CollidingElement (ObjectPtr) &&
					!OBJECT_CLOAKED (ObjectPtr))
			{
				SIZE delta_x, delta_y;

				delta_x = ObjectPtr->next.location.x -
						ShipPtr->next.location.x;
				delta_y = ObjectPtr->next.location.y -
						ShipPtr->next.location.y;
				if (delta_x < 0)
					delta_x = -delta_x;
				if (delta_y < 0)
					delta_y = -delta_y;
				delta_x = WORLD_TO_DISPLAY (delta_x);
				delta_y = WORLD_TO_DISPLAY (delta_y);
				if ((UWORD)delta_x <= LASER_RANGE &&
						(UWORD)delta_y <= LASER_RANGE &&
						(UWORD)delta_x * (UWORD)delta_x +
						(UWORD)delta_y * (UWORD)delta_y <=
						LASER_RANGE * LASER_RANGE)
				{
					HELEMENT hPointDefense;
					LASER_BLOCK LaserBlock;

					if (!PaidFor)
					{
						if (!DeltaEnergy (ShipPtr,
								-(StarShipPtr->RaceDescPtr->characteristics.special_energy_cost
								<< 2)))
							break;

						ProcessSound (SetAbsSoundIndex (
										/* POINT_DEFENSE_LASER */
								StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
						StarShipPtr->special_counter =
								StarShipPtr->RaceDescPtr->characteristics.special_wait;
						PaidFor = TRUE;
					}

					LaserBlock.cx = ShipPtr->next.location.x;
					LaserBlock.cy = ShipPtr->next.location.y;
					LaserBlock.face = 0;
					LaserBlock.ex = ObjectPtr->next.location.x
							- ShipPtr->next.location.x;
					LaserBlock.ey = ObjectPtr->next.location.y
							- ShipPtr->next.location.y;
					LaserBlock.sender = ShipPtr->playerNr;
					LaserBlock.flags = IGNORE_SIMILAR;
					LaserBlock.pixoffs = 0;
					LaserBlock.color = LaserColor;
					hPointDefense = initialize_laser (&LaserBlock);
					if (hPointDefense)
					{
						ELEMENT *PDPtr;

						LockElement (hPointDefense, &PDPtr);
						PDPtr->mass_points =
								StarShipPtr->RaceDescPtr->characteristics.special_energy_cost;
						SetElementStarShip (PDPtr, StarShipPtr);
						PDPtr->hTarget = 0;
						UnlockElement (hPointDefense);

						PutElement (hPointDefense);
					}
				}
			}
			UnlockElement (hObject);
		}
		UnlockElement (StarShipPtr->hShip);
	}
}

static void
sis_battle_preprocess (ELEMENT *ElementPtr)
{
	STARSHIP *StarShipPtr;

	GetElementStarShip (ElementPtr, &StarShipPtr);
	if (StarShipPtr->RaceDescPtr->characteristics.special_energy_cost == 0)
	{
		StarShipPtr->cur_status_flags &= ~SPECIAL;
		StarShipPtr->special_counter = 2;
	}
	if (!(StarShipPtr->RaceDescPtr->ship_info.ship_flags
			& (FIRES_FORE | FIRES_RIGHT | FIRES_AFT | FIRES_LEFT)))
	{
		StarShipPtr->cur_status_flags &= ~WEAPON;
		StarShipPtr->weapon_counter = 2;
	}
}

static void
sis_battle_postprocess (ELEMENT *ElementPtr)
{
	STARSHIP *StarShipPtr;

	GetElementStarShip (ElementPtr, &StarShipPtr);
	if ((StarShipPtr->cur_status_flags & SPECIAL)
			&& StarShipPtr->special_counter == 0
			&& StarShipPtr->RaceDescPtr->characteristics.special_energy_cost)
	{
		spawn_point_defense (ElementPtr);
	}
}

static void
blaster_collision (ELEMENT *ElementPtr0, POINT *pPt0,
		ELEMENT *ElementPtr1, POINT *pPt1)
{
	HELEMENT hBlastElement;

	hBlastElement = weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
	if (hBlastElement)
	{
		ELEMENT *BlastElementPtr;

		LockElement (hBlastElement, &BlastElementPtr);
		switch (ElementPtr0->mass_points)
		{
			case BLASTER_DAMAGE * 1:
				BlastElementPtr->life_span = 2;
				BlastElementPtr->current.image.frame =
						SetAbsFrameIndex (ElementPtr0->current.image.frame, 0);
				BlastElementPtr->preprocess_func = NULL;
				break;
			case BLASTER_DAMAGE * 2:
				BlastElementPtr->life_span = 6;
				BlastElementPtr->current.image.frame =
						IncFrameIndex (ElementPtr0->current.image.frame);
				break;
			case BLASTER_DAMAGE * 3:
				BlastElementPtr->life_span = 7;
				BlastElementPtr->current.image.frame =
						SetAbsFrameIndex (ElementPtr0->current.image.frame, 20);
				break;
		}
		UnlockElement (hBlastElement);
	}
}

static void
blaster_preprocess (ELEMENT *ElementPtr)
{
	BYTE wait;

	switch (ElementPtr->mass_points)
	{
		case BLASTER_DAMAGE * 1:
			if (GetFrameIndex (ElementPtr->current.image.frame) < 8)
			{
				ElementPtr->next.image.frame =
						IncFrameIndex (ElementPtr->current.image.frame);
				ElementPtr->state_flags |= CHANGING;
			}
			break;
		case BLASTER_DAMAGE * 3:
			if (GetFrameIndex (ElementPtr->current.image.frame) < 19)
				ElementPtr->next.image.frame =
						IncFrameIndex (ElementPtr->current.image.frame);
			else
				ElementPtr->next.image.frame =
						SetAbsFrameIndex (ElementPtr->current.image.frame, 16);
			ElementPtr->state_flags |= CHANGING;
			break;
	}

	if (LONIBBLE (ElementPtr->turn_wait))
		--ElementPtr->turn_wait;
	else if ((wait = HINIBBLE (ElementPtr->turn_wait)))
	{
		COUNT facing;

		facing = NORMALIZE_FACING (ANGLE_TO_FACING (
				GetVelocityTravelAngle (&ElementPtr->velocity)));
		if (TrackShip (ElementPtr, &facing) > 0)
			SetVelocityVector (&ElementPtr->velocity, BLASTER_SPEED, facing);

		ElementPtr->turn_wait = MAKE_BYTE (wait, wait);
	}
}

static COUNT
initialize_blasters (ELEMENT *ShipPtr, HELEMENT BlasterArray[])
{
	BYTE nt;
	COUNT i;
	STARSHIP *StarShipPtr;
	SIS_DATA *SisData;

	GetElementStarShip (ShipPtr, &StarShipPtr);
	SisData = GetCustomShipData (StarShipPtr->RaceDescPtr);

	nt = (BYTE)((4 - SisData->num_trackers) & 3);

	for (i = 0; i < SisData->num_blasters; ++i)
	{
		MISSILE_BLOCK MissileBlock = SisData->MissileBlock[i];

		MissileBlock.cx = ShipPtr->next.location.x;
		MissileBlock.cy = ShipPtr->next.location.y;
		MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
		MissileBlock.sender = ShipPtr->playerNr;
		MissileBlock.face = NORMALIZE_FACING (StarShipPtr->ShipFacing
				+ MissileBlock.face);

		BlasterArray[i] = initialize_missile (&MissileBlock);
		if (BlasterArray[i])
		{
			ELEMENT *BlasterPtr;

			LockElement (BlasterArray[i], &BlasterPtr);
			BlasterPtr->collision_func = blaster_collision;
			BlasterPtr->turn_wait = MAKE_BYTE (nt, nt);
			UnlockElement (BlasterArray[i]);
		}
	
	}

	return SisData->num_blasters;
}

static void
sis_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
		COUNT ConcernCounter)
{
	EVALUATE_DESC *lpEvalDesc;
	STARSHIP *StarShipPtr;
	SIS_DATA *SisData;

	GetElementStarShip (ShipPtr, &StarShipPtr);
	SisData = GetCustomShipData (StarShipPtr->RaceDescPtr);

	lpEvalDesc = &ObjectsOfConcern[ENEMY_WEAPON_INDEX];
	if (lpEvalDesc->ObjectPtr)
	{
		if (StarShipPtr->RaceDescPtr->characteristics.special_energy_cost)
		{
			if (StarShipPtr->special_counter == 0
					&& ((lpEvalDesc->ObjectPtr
					&& lpEvalDesc->which_turn <= 2)
					|| (ObjectsOfConcern[ENEMY_SHIP_INDEX].ObjectPtr != NULL
					&& ObjectsOfConcern[ENEMY_SHIP_INDEX].which_turn <= 4)))
				StarShipPtr->ship_input_state |= SPECIAL;
			else
				StarShipPtr->ship_input_state &= ~SPECIAL;
			lpEvalDesc->ObjectPtr = NULL;
		}
		else if (MANEUVERABILITY (&StarShipPtr->RaceDescPtr->cyborg_control)
				< MEDIUM_SHIP
				&& lpEvalDesc->MoveState == ENTICE
				&& (!(lpEvalDesc->ObjectPtr->state_flags & CREW_OBJECT)
				|| lpEvalDesc->which_turn <= 8)
				&& (!(lpEvalDesc->ObjectPtr->state_flags & FINITE_LIFE)
				|| (lpEvalDesc->ObjectPtr->mass_points >= 4
				&& lpEvalDesc->which_turn == 2
				&& ObjectsOfConcern[ENEMY_SHIP_INDEX].which_turn > 16)))
			lpEvalDesc->MoveState = PURSUE;
	}

	ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);

	lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
	if (SisData->num_trackers
			&& StarShipPtr->weapon_counter == 0
			&& !(StarShipPtr->ship_input_state & WEAPON)
			&& lpEvalDesc->ObjectPtr
			&& lpEvalDesc->which_turn <= 16)
	{
		COUNT direction_facing;
		SIZE delta_x, delta_y;
		UWORD fire_flags, ship_flags;
		COUNT facing;

		delta_x = lpEvalDesc->ObjectPtr->current.location.x
				- ShipPtr->current.location.x;
		delta_y = lpEvalDesc->ObjectPtr->current.location.y
				- ShipPtr->current.location.y;
		direction_facing = NORMALIZE_FACING (
				ANGLE_TO_FACING (ARCTAN (delta_x, delta_y)));

		ship_flags = StarShipPtr->RaceDescPtr->ship_info.ship_flags;
		for (fire_flags = FIRES_FORE, facing = StarShipPtr->ShipFacing;
				fire_flags <= FIRES_LEFT;
				fire_flags <<= 1, facing += QUADRANT)
		{
			if ((ship_flags & fire_flags) && NORMALIZE_FACING (
					direction_facing - facing + ANGLE_TO_FACING (OCTANT)
					) <= ANGLE_TO_FACING (QUADRANT))
			{
				StarShipPtr->ship_input_state |= WEAPON;
				break;
			}
		}
	}
}

static void
InitWeaponSlots (RACE_DESC *RaceDescPtr, const BYTE *ModuleSlots)
{
	COUNT i;
	SIS_DATA *SisData = GetCustomShipData (RaceDescPtr);
	MISSILE_BLOCK *lpMB = SisData->MissileBlock;

	SisData->num_blasters = 0;
	for (i = 0; i < NUM_MODULE_SLOTS; ++i)
	{
		COUNT which_gun;

		if (i == 3)
			i = NUM_MODULE_SLOTS - 1;
		
		which_gun = ModuleSlots[(NUM_MODULE_SLOTS - 1) - i];
		
		if (which_gun < GUN_WEAPON || which_gun > CANNON_WEAPON)
			continue; /* not a gun */

		which_gun -= GUN_WEAPON - 1;
		RaceDescPtr->characteristics.weapon_energy_cost +=
				which_gun * 2;
		
		lpMB->flags = IGNORE_SIMILAR;
		lpMB->blast_offs = BLASTER_OFFSET;
		lpMB->speed = BLASTER_SPEED;
		lpMB->preprocess_func = blaster_preprocess;
		lpMB->hit_points = BLASTER_HITS * which_gun;
		lpMB->damage = BLASTER_DAMAGE * which_gun;
		lpMB->life = BLASTER_LIFE + ((BLASTER_LIFE >> 2) * (which_gun - 1));

		if (which_gun == 1)
			lpMB->index = 0;
		else if (which_gun == 2)
			lpMB->index = 9;
		else
			lpMB->index = 16;

		switch (i)
		{
			case 0: /* NOSE WEAPON */
				RaceDescPtr->ship_info.ship_flags |= FIRES_FORE;
				lpMB->pixoffs = SIS_VERT_OFFSET;
				lpMB->face = 0;
				break;
			case 1: /* SPREAD WEAPON */
				RaceDescPtr->ship_info.ship_flags |= FIRES_FORE;
				lpMB->pixoffs = SIS_VERT_OFFSET;
				lpMB->face = +1;
				/* copy it because there are two */
				lpMB[1] = lpMB[0];
				++lpMB;
				++SisData->num_blasters;
				lpMB->face = NORMALIZE_FACING (-1);
				break;
			case 2: /* SIDE WEAPON */
				RaceDescPtr->ship_info.ship_flags |=
						FIRES_LEFT | FIRES_RIGHT;
				lpMB->pixoffs = SIS_HORZ_OFFSET;
				lpMB->face = ANGLE_TO_FACING (QUADRANT);
				/* copy it because there are two */
				lpMB[1] = lpMB[0];
				++lpMB;
				++SisData->num_blasters;
				lpMB->face = NORMALIZE_FACING (-ANGLE_TO_FACING (QUADRANT));
				break;
			case NUM_MODULE_SLOTS - 1: /* TAIL WEAPON */
				RaceDescPtr->ship_info.ship_flags |= FIRES_AFT;
				lpMB->pixoffs = SIS_VERT_OFFSET;
				lpMB->face = ANGLE_TO_FACING (HALF_CIRCLE);
				break;
		}

		++lpMB;
		++SisData->num_blasters;
	}
}

static void
InitModuleSlots (RACE_DESC *RaceDescPtr, const BYTE *ModuleSlots)
{
	COUNT i;
	COUNT num_trackers;
	SIS_DATA *SisData = GetCustomShipData (RaceDescPtr);

	RaceDescPtr->ship_info.max_crew = 0;
	num_trackers = 0;
	for (i = 0; i < NUM_MODULE_SLOTS; ++i)
	{
		BYTE which_mod;

		which_mod = ModuleSlots[(NUM_MODULE_SLOTS - 1) - i];
		switch (which_mod)
		{
			case CREW_POD:
				RaceDescPtr->ship_info.max_crew += CREW_POD_CAPACITY;
				break;
			case TRACKING_SYSTEM:
				++num_trackers;
				break;
			case ANTIMISSILE_DEFENSE:
				RaceDescPtr->characteristics.special_energy_cost +=
						ANTIMISSILE_ENERGY_INC;
				break;
			case SHIVA_FURNACE:
				RaceDescPtr->characteristics.energy_regeneration +=
						SHIVA_ENERGY_REGEN_INC;
				break;
			case DYNAMO_UNIT:
				RaceDescPtr->characteristics.energy_wait -=
						DYNAMO_UNIT_ENERGY_WAIT_DEC;
				if (RaceDescPtr->characteristics.energy_wait < MIN_ENERGY_WAIT)
					RaceDescPtr->characteristics.energy_wait = MIN_ENERGY_WAIT;
				break;
		}
	}

	if (num_trackers > MAX_TRACKING)
		num_trackers = MAX_TRACKING;
	RaceDescPtr->characteristics.weapon_energy_cost +=
			num_trackers * TRACKER_ENERGY_COST;
	SisData->num_trackers = num_trackers;
	if (RaceDescPtr->characteristics.special_energy_cost)
	{
		RaceDescPtr->ship_info.ship_flags |= POINT_DEFENSE;
		if (RaceDescPtr->characteristics.special_energy_cost > MAX_DEFENSE)
			RaceDescPtr->characteristics.special_energy_cost = MAX_DEFENSE;
	}
}

static void
InitDriveSlots (RACE_DESC *RaceDescPtr, const BYTE *DriveSlots)
{
	COUNT i;

	// NB. RaceDescPtr->characteristics.max_thrust is already initialised.
	RaceDescPtr->characteristics.thrust_wait = 0;
	for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
	{
		switch (DriveSlots[i])
		{
			case FUSION_THRUSTER:
				RaceDescPtr->characteristics.max_thrust += 2;
				++RaceDescPtr->characteristics.thrust_wait;
				break;
		}
	}
	RaceDescPtr->characteristics.thrust_wait = (BYTE)(
			THRUST_WAIT - (RaceDescPtr->characteristics.thrust_wait >> 1));
	RaceDescPtr->characteristics.max_thrust =
			((RaceDescPtr->characteristics.max_thrust /
			RaceDescPtr->characteristics.thrust_increment) + 1)
			* RaceDescPtr->characteristics.thrust_increment;
}

static void
InitJetSlots (RACE_DESC *RaceDescPtr, const BYTE *JetSlots)
{
	COUNT i;

	for (i = 0; i < NUM_JET_SLOTS; ++i)
	{
		switch (JetSlots[i])
		{
			case TURNING_JETS:
				RaceDescPtr->characteristics.turn_wait -= 2;
				break;
		}
	}
}

RACE_DESC*
init_sis (void)
{
	RACE_DESC *RaceDescPtr;
	COUNT i;
	// The caller of this func will copy the struct
	static RACE_DESC new_sis_desc;
	SIS_DATA empty_data;
	memset (&empty_data, 0, sizeof (empty_data));

	/* copy initial ship settings to new_sis_desc */
	new_sis_desc = sis_desc;
	
	new_sis_desc.uninit_func = uninit_sis;

	if (inHQSpace ())
	{
		for (i = 0; i < NUM_VIEWS; ++i)
		{
			new_sis_desc.ship_data.ship_rsc[i] = NULL_RESOURCE;
			new_sis_desc.ship_data.weapon_rsc[i] = NULL_RESOURCE;
			new_sis_desc.ship_data.special_rsc[i] = NULL_RESOURCE;
		}
		new_sis_desc.ship_info.icons_rsc = NULL_RESOURCE;
		new_sis_desc.ship_data.captain_control.captain_rsc = NULL_RESOURCE;
		new_sis_desc.ship_data.victory_ditty_rsc = NULL_RESOURCE;
		new_sis_desc.ship_data.ship_sounds_rsc = NULL_RESOURCE;

		new_sis_desc.ship_data.ship_rsc[0] = SIS_HYPER_MASK_PMAP_ANIM;

		new_sis_desc.preprocess_func = sis_hyper_preprocess;
		new_sis_desc.postprocess_func = sis_hyper_postprocess;

		new_sis_desc.characteristics.max_thrust -= 4;
	}
	else
	{
		new_sis_desc.preprocess_func = sis_battle_preprocess;
		new_sis_desc.postprocess_func = sis_battle_postprocess;
		new_sis_desc.init_weapon_func = initialize_blasters;
		new_sis_desc.cyborg_control.intelligence_func = sis_intelligence;

		if (GET_GAME_STATE (CHMMR_BOMB_STATE) == 3)
			SET_GAME_STATE (BOMB_CARRIER, 1);
	}

	SetCustomShipData (&new_sis_desc, &empty_data);
	InitModuleSlots (&new_sis_desc, GLOBAL_SIS (ModuleSlots));
	InitWeaponSlots (&new_sis_desc, GLOBAL_SIS (ModuleSlots));
	InitDriveSlots (&new_sis_desc, GLOBAL_SIS (DriveSlots));
	InitJetSlots (&new_sis_desc, GLOBAL_SIS (JetSlots));
	
	if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE)
	{
		new_sis_desc.ship_info.crew_level = new_sis_desc.ship_info.max_crew;
	}
	else
	{
		// Count the captain too.
		new_sis_desc.ship_info.max_crew++;
		new_sis_desc.ship_info.crew_level = GLOBAL_SIS (CrewEnlisted) + 1;
		new_sis_desc.ship_info.ship_flags |= PLAYER_CAPTAIN;
	}
	
	new_sis_desc.ship_info.energy_level = new_sis_desc.ship_info.max_energy;

	RaceDescPtr = &new_sis_desc;

	return (RaceDescPtr);
}

static void
uninit_sis (RACE_DESC *pRaceDesc)
{
	if (!inHQSpace ())
	{
		GLOBAL_SIS (CrewEnlisted) = pRaceDesc->ship_info.crew_level;
		if (pRaceDesc->ship_info.ship_flags & PLAYER_CAPTAIN)
			GLOBAL_SIS (CrewEnlisted)--;
	}

	SetCustomShipData (pRaceDesc, NULL);
}


