//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 "lander.h"

#include "lifeform.h"
#include "scan.h"
#include "../cons_res.h"
#include "../controls.h"
#include "../colors.h"
#include "../process.h"
#include "../units.h"
#include "../gamestr.h"
#include "../nameref.h"
#include "../resinst.h"
#include "../setup.h"
#include "../sounds.h"
#include "../element.h"
#include "libs/graphics/gfx_common.h"
#include "libs/mathlib.h"
#include "libs/log.h"


//define SPIN_ON_LAUNCH to let the planet spin while
// the lander animation is playing
#define SPIN_ON_LAUNCH

// PLANET_SIDE_RATE governs how fast the lander,
// bio and planet effects will be
// We're using the 3DO speed, which is 35 FPS
// The PC speed was 30 FPS.
// Remember that all values need to evenly divide
// ONE_SECOND.
#define PLANET_SIDE_RATE (ONE_SECOND / 35)


// This is a derived type from INPUT_STATE_DESC.
typedef struct LanderInputState LanderInputState;
struct LanderInputState {
	// Fields required by DoInput()
	BOOLEAN (*InputFunc) (LanderInputState *pMS);

	BOOLEAN Initialized;
	TimeCount NextTime;
			// Frame rate control
};

FRAME LanderFrame[8];
static SOUND LanderSounds;
MUSIC_REF LanderMusic;
#define NUM_ORBIT_THEMES 5
static MUSIC_REF OrbitMusic[NUM_ORBIT_THEMES];

const LIFEFORM_DESC CreatureData[] =
{
	{SPEED_MOTIONLESS | DANGER_HARMLESS, MAKE_BYTE (1, 1)},
			// Roto-Dendron
	{SPEED_MOTIONLESS | DANGER_HARMLESS, MAKE_BYTE (6, 1)},
			// Macrocillia
	{SPEED_MOTIONLESS | DANGER_WEAK, MAKE_BYTE (3, 1)},
			// Splort Wort
	{SPEED_MOTIONLESS | DANGER_NORMAL, MAKE_BYTE (5, 3)},
			// Whackin' Bush
	{SPEED_MOTIONLESS | DANGER_HARMLESS, MAKE_BYTE (2, 10)},
			// Slot Machine Tree
	{BEHAVIOR_UNPREDICTABLE | SPEED_SLOW | DANGER_HARMLESS, MAKE_BYTE (1, 2)},
			// Neon Worm
	{BEHAVIOR_FLEE | AWARENESS_MEDIUM | SPEED_SLOW | DANGER_HARMLESS, MAKE_BYTE (8, 5)},
			// Stiletto Urchin
	{BEHAVIOR_HUNT | AWARENESS_LOW | SPEED_SLOW | DANGER_WEAK, MAKE_BYTE (2, 2)},
			// Deluxe Blob
	{BEHAVIOR_UNPREDICTABLE | SPEED_SLOW | DANGER_NORMAL, MAKE_BYTE (3, 8)},
			// Glowing Medusa
	{BEHAVIOR_HUNT | AWARENESS_MEDIUM | SPEED_SLOW | DANGER_MONSTROUS, MAKE_BYTE (10, 15)},
			// Carousel Beast
	{BEHAVIOR_HUNT | AWARENESS_MEDIUM | SPEED_MEDIUM | DANGER_WEAK, MAKE_BYTE (3, 3)},
			// Mysterious Bees
	{BEHAVIOR_FLEE | AWARENESS_MEDIUM | SPEED_MEDIUM | DANGER_HARMLESS, MAKE_BYTE (2, 1)},
			// Hopping Blobby
	{BEHAVIOR_UNPREDICTABLE | SPEED_MEDIUM | DANGER_WEAK, MAKE_BYTE (2, 2)},
			// Blood Monkey
	{BEHAVIOR_HUNT | AWARENESS_HIGH | SPEED_MEDIUM | DANGER_NORMAL, MAKE_BYTE (4, 6)},
			// Yompin Yiminy
	{BEHAVIOR_UNPREDICTABLE | SPEED_MEDIUM | DANGER_MONSTROUS, MAKE_BYTE (9, 12)},
			// Amorphous Trandicula
	{BEHAVIOR_HUNT | AWARENESS_HIGH | SPEED_FAST | DANGER_WEAK, MAKE_BYTE (3, 1)},
			// Crazy Weasel
	{BEHAVIOR_FLEE | AWARENESS_HIGH | SPEED_FAST | DANGER_HARMLESS, MAKE_BYTE (1, 1)},
			// Merry Whumpet
	{BEHAVIOR_HUNT | AWARENESS_LOW | SPEED_FAST | DANGER_NORMAL, MAKE_BYTE (7, 8)},
			// Fungal Squid
	{BEHAVIOR_FLEE | AWARENESS_HIGH | SPEED_FAST | DANGER_WEAK, MAKE_BYTE (15, 2)},
			// Penguin Cyclops
	{BEHAVIOR_FLEE | AWARENESS_LOW | SPEED_FAST | DANGER_WEAK, MAKE_BYTE (1, 1)},
			// Chicken
	{BEHAVIOR_UNPREDICTABLE | SPEED_SLOW | DANGER_WEAK, MAKE_BYTE (6, 2)},
			// Bubble Vine
	{BEHAVIOR_FLEE | AWARENESS_HIGH | SPEED_SLOW | DANGER_WEAK, MAKE_BYTE (4, 2)},
			// Bug-Eyed Bait
	{SPEED_MOTIONLESS | DANGER_WEAK, MAKE_BYTE (8, 5)},
			// Goo Burger

	{SPEED_MOTIONLESS | DANGER_MONSTROUS, MAKE_BYTE (1, 1)},
			// Evil One
	{BEHAVIOR_UNPREDICTABLE | SPEED_SLOW | DANGER_HARMLESS, MAKE_BYTE (0, 1)},
			// Brainbox Bulldozers
	{BEHAVIOR_HUNT | AWARENESS_HIGH | SPEED_FAST | DANGER_MONSTROUS, MAKE_BYTE (15, 15)},
			// Zex's Beauty
};


extern PRIM_LINKS DisplayLinks;

#define DAMAGE_CYCLE 6
// XXX: There are actually only 9 explosion images.
//   The last frame is drawn twice.
#define EXPLOSION_LIFE 10
// How long to wait after the lander explodes, so that the full
// gravity of the player's situation sinks in
#define EXPLOSION_WAIT         (ONE_SECOND * 2)
#define EXPLOSION_WAIT_FRAMES  (EXPLOSION_WAIT / PLANET_SIDE_RATE)
// The actual number of frame that the explosion and wait takes is:
//   EXPLOSION_LIFE * 3 + EXPLOSION_WAIT_FRAMES

#define DEATH_EXPLOSION 0

// TODO: redefine these in terms of CONTEXT width/height
#define SURFACE_WIDTH SIS_SCREEN_WIDTH
#define SURFACE_HEIGHT (SIS_SCREEN_HEIGHT - MAP_HEIGHT - MAP_BORDER_HEIGHT)

#define REPAIR_LANDER (1 << 7)
#define REPAIR_TRANSITION (1 << 6)
#define KILL_CREW (1 << 5)
#define ADD_AT_END (1 << 4)
#define REPAIR_COUNT (0xf)

#define LANDER_SPEED_DENOM 10

static BYTE lander_flags;
static POINT curLanderLoc;
static int crew_left;
static int shieldHit;
		// which shield was hit, assuming it helped
static int damage_index;
		// number of lander damage frames left
static int explosion_index;
		// lander explosion progression. Semantics are similar to an
		// inverse of ELEMENT.life_span
static int turn_wait;
		// thus named for similar semantics to ELEMENT.turn_wait
static int weapon_wait;
		// semantics similar to STARSHIP.weapon_counter

// TODO: We may want to make the PLANETSIDE_DESC fields into static vars
static PLANETSIDE_DESC *planetSideDesc;

#define ON_THE_GROUND   0


static Color
DamageColorCycle (Color c, COUNT i)
{
	static const Color damage_tab[DAMAGE_CYCLE + 1] =
	{
		WHITE_COLOR_INIT,
		BUILD_COLOR (MAKE_RGB15_INIT (0x1B, 0x00, 0x00), 0x2A),
		BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x07, 0x00), 0x7E),
		BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0E, 0x00), 0x7C),
		BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x15, 0x00), 0x7A),
		BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x1C, 0x00), 0x78),
		BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x1F, 0x0A), 0x0E),
	};

	if (i)
		c = damage_tab[i];
	else if (sameColor(c, WHITE_COLOR))
		c = damage_tab[6];
	else if (sameColor(c, BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E)))
		c = damage_tab[5];
	else if (sameColor(c, BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1C, 0x00), 0x78)))
		c = damage_tab[4];
	else if (sameColor(c, BUILD_COLOR (MAKE_RGB15 (0x1F, 0x15, 0x00), 0x7A)))
		c = damage_tab[3];
	else if (sameColor(c, BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0E, 0x00), 0x7C)))
		c = damage_tab[2];
	else if (sameColor(c, BUILD_COLOR (MAKE_RGB15 (0x1F, 0x07, 0x00), 0x7E)))
		c = damage_tab[1];
	else
		c = damage_tab[0];

	return c;
}

static HELEMENT AddGroundDisaster (COUNT which_disaster);

void
object_animation (ELEMENT *ElementPtr)
{
	COUNT frame_index, angle;
	PRIMITIVE *pPrim;

	pPrim = &DisplayArray[ElementPtr->PrimIndex];
	if (GetPrimType (pPrim) == STAMPFILL_PRIM
			&& !((ElementPtr->state_flags & FINITE_LIFE)
			&& ElementPtr->mass_points == EARTHQUAKE_DISASTER))
	{
		Color c;

		c = DamageColorCycle (GetPrimColor (pPrim), 0);
		if (sameColor(c, WHITE_COLOR))
		{
			SetPrimType (pPrim, STAMP_PRIM);
			if (ElementPtr->hit_points == 0)
			{
				ZeroVelocityComponents (&ElementPtr->velocity);
				pPrim->Object.Stamp.frame =
						SetAbsFrameIndex (pPrim->Object.Stamp.frame, 0);

				PlaySound (SetAbsSoundIndex (LanderSounds, LIFEFORM_CANNED),
						NotPositional (), NULL, GAME_SOUND_PRIORITY);
			}
		}

		SetPrimColor (pPrim, c);
	}

	frame_index = GetFrameIndex (pPrim->Object.Stamp.frame) + 1;
	if (LONIBBLE (ElementPtr->turn_wait))
		--ElementPtr->turn_wait;
	else
	{
		ElementPtr->turn_wait += HINIBBLE (ElementPtr->turn_wait);

		pPrim->Object.Stamp.frame = IncFrameIndex (pPrim->Object.Stamp.frame);
		
		if (ElementPtr->state_flags & FINITE_LIFE)
		{
			/* A natural disaster */
			if (ElementPtr->mass_points == DEATH_EXPLOSION)
			{	// Lander explosion
				++explosion_index;
				if (explosion_index >= EXPLOSION_LIFE)
				{	// XXX: The last frame is drawn twice
					pPrim->Object.Stamp.frame =
							DecFrameIndex (pPrim->Object.Stamp.frame);
				}
			}
			else if (ElementPtr->mass_points == EARTHQUAKE_DISASTER)
			{
				SIZE s;

				if (frame_index >= 13)
					s = 0;
				else
					s = (14 - frame_index) >> 1;
				// XXX: Was 0x8000 the background flag on 3DO?
				//SetPrimColor (pPrim, BUILD_COLOR (0x8000 | MAKE_RGB15 (0x1F, 0x1F, 0x1F), s));
				SetPrimColor (pPrim, BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), s));
				if (frame_index == 13)
					PlaySound (SetAbsSoundIndex (LanderSounds, EARTHQUAKE_DISASTER),
							NotPositional (), NULL, GAME_SOUND_PRIORITY);
			}
			
			if (ElementPtr->mass_points == LAVASPOT_DISASTER
					&& frame_index == 5
					&& TFB_Random () % 100 < 90)
			{
				HELEMENT hLavaElement;

				/* Change lava-spot direction of travel */
				hLavaElement = AddGroundDisaster (LAVASPOT_DISASTER);
				if (hLavaElement)
				{
					ELEMENT *LavaElementPtr;

					angle = FACING_TO_ANGLE (ElementPtr->facing);
					LockElement (hLavaElement, &LavaElementPtr);
					LavaElementPtr->next.location = ElementPtr->next.location;
					LavaElementPtr->next.location.x += COSINE (angle, 4);
					LavaElementPtr->next.location.y += SINE (angle, 4);
					if (LavaElementPtr->next.location.y < 0)
						LavaElementPtr->next.location.y = 0;
					else if (LavaElementPtr->next.location.y >= (MAP_HEIGHT << MAG_SHIFT))
						LavaElementPtr->next.location.y = (MAP_HEIGHT << MAG_SHIFT) - 1;
					if (LavaElementPtr->next.location.x < 0)
						LavaElementPtr->next.location.x += MAP_WIDTH << MAG_SHIFT;
					else
						LavaElementPtr->next.location.x %= MAP_WIDTH << MAG_SHIFT;
					LavaElementPtr->facing = NORMALIZE_FACING (
							ElementPtr->facing + (TFB_Random () % 3 - 1));
					UnlockElement (hLavaElement);
				}
			}
		}
		else if (!(frame_index & 3) && ElementPtr->hit_points)
		{
			BYTE index;
			COUNT speed;

			index = ElementPtr->mass_points & ~CREATURE_AWARE;
			speed = CreatureData[index].Attributes & SPEED_MASK;
			if (speed)
			{
				SIZE dx, dy;
				COUNT old_angle;

				dx = curLanderLoc.x - ElementPtr->next.location.x;
				if (dx < 0 && dx < -(MAP_WIDTH << (MAG_SHIFT - 1)))
					dx += MAP_WIDTH << MAG_SHIFT;
				else if (dx > (MAP_WIDTH << (MAG_SHIFT - 1)))
					dx -= MAP_WIDTH << MAG_SHIFT;
				dy = curLanderLoc.y - ElementPtr->next.location.y;
				angle = ARCTAN (dx, dy);
				if (dx < 0)
					dx = -dx;
				if (dy < 0)
					dy = -dy;

				if (dx >= SURFACE_WIDTH || dy >= SURFACE_WIDTH
						|| dx * dx + dy * dy >= SURFACE_WIDTH * SURFACE_WIDTH)
					ElementPtr->mass_points &= ~CREATURE_AWARE;
				else if (!(ElementPtr->mass_points & CREATURE_AWARE))
				{
					BYTE DetectPercent;

					DetectPercent = (((BYTE)(CreatureData[index].Attributes
							& AWARENESS_MASK) >> AWARENESS_SHIFT) + 1)
							* (30 / 6);
							// XXX: Shouldn't this be dependent on
							// PLANET_SIDE_RATE somehow? And why is it
							// written as '30 / 6' instead of 5? Does the 30
							// specify the (PC) framerate? That doesn't make
							// sense; I would expect it to be in the
							// denominator. And even then, it wouldn't give
							// the same results with different frame rates,
							// as repeating 'random(x / 30)' 30 times doesn't
							// generally have the same result as repeating
							// 'random(x / 35)' 25 times. - SvdB
					if (TFB_Random () % 100 < DetectPercent)
					{
						ElementPtr->thrust_wait = 0;
						ElementPtr->mass_points |= CREATURE_AWARE;
					}
				}

				if (ElementPtr->next.location.y == 0
						|| ElementPtr->next.location.y ==
						(MAP_HEIGHT << MAG_SHIFT) - 1)
					ElementPtr->thrust_wait = 0;

				old_angle = GetVelocityTravelAngle (&ElementPtr->velocity);
				if (ElementPtr->thrust_wait)
				{
					--ElementPtr->thrust_wait;
					angle = old_angle;
				}
				else if (!(ElementPtr->mass_points & CREATURE_AWARE)
						|| (CreatureData[index].Attributes
						& BEHAVIOR_MASK) == BEHAVIOR_UNPREDICTABLE)
				{
					COUNT rand_val;

					rand_val = TFB_Random ();
					angle = NORMALIZE_ANGLE (LOBYTE (rand_val));
					ElementPtr->thrust_wait =
							(HIBYTE (rand_val) >> 2) + 10;
				}
				else if ((CreatureData[index].Attributes
						& BEHAVIOR_MASK) == BEHAVIOR_FLEE)
				{
					if (ElementPtr->next.location.y == 0
							|| ElementPtr->next.location.y ==
							(MAP_HEIGHT << MAG_SHIFT) - 1)
					{
						if (angle & (HALF_CIRCLE - 1))
							angle = HALF_CIRCLE - angle;
						else if (old_angle == QUADRANT
								|| old_angle == (FULL_CIRCLE - QUADRANT))
							angle = old_angle;
						else
							angle = ((TFB_Random () & 1)
										* HALF_CIRCLE) - QUADRANT;
						ElementPtr->thrust_wait = 5;
					}
					angle = NORMALIZE_ANGLE (angle + HALF_CIRCLE);
				}

				switch (speed)
				{
					case SPEED_SLOW:
						speed = WORLD_TO_VELOCITY (2 * 1) >> 2;
						break;
					case SPEED_MEDIUM:
						speed = WORLD_TO_VELOCITY (2 * 1) >> 1;
						break;
					case SPEED_FAST:
						speed = WORLD_TO_VELOCITY (2 * 1) * 9 / 10;
						break;
				}

				SetVelocityComponents (&ElementPtr->velocity,
						COSINE (angle, speed), SINE (angle, speed));
			}
		}
	}

	if ((ElementPtr->state_flags & FINITE_LIFE)
			&& ElementPtr->mass_points == DEATH_EXPLOSION
			&& GetSuccLink (DisplayLinks) != ElementPtr->PrimIndex)
		lander_flags |= ADD_AT_END;
}

#define NUM_CREW_COLS 6
#define NUM_CREW_ROWS 2

static void
DeltaLanderCrew (SIZE crew_delta, COUNT which_disaster)
{
	STAMP s;
	CONTEXT OldContext;

	if (crew_delta > 0)
	{
		// Filling up the crew bar when landing.
		crew_delta = crew_left;
		crew_left += 1;

		s.frame = SetAbsFrameIndex (LanderFrame[0], 55);
	}
	else /* if (crew_delta < 0) */
	{
		if (crew_left < 1)
			return; // irrelevant -- all dead
		
		shieldHit = GET_GAME_STATE (LANDER_SHIELDS);
		shieldHit &= 1 << which_disaster;
		if (!shieldHit || TFB_Random () % 100 >= 95)
		{	// No shield, or it did not help
			shieldHit = 0;
			--crew_left;
		}

		damage_index = DAMAGE_CYCLE;
		if (shieldHit)
			return;

		crew_delta = crew_left;
		s.frame = SetAbsFrameIndex (LanderFrame[0], 56);

		PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_INJURED),
				NotPositional (), NULL, GAME_SOUND_PRIORITY);
	}

	s.origin.x = 11 + (6 * (crew_delta % NUM_CREW_COLS));
	s.origin.y = 35 - (6 * (crew_delta / NUM_CREW_COLS));

	OldContext = SetContext (RadarContext);
	DrawStamp (&s);
	SetContext (OldContext);
}

static void
FillLanderHold (PLANETSIDE_DESC *pPSD, COUNT scan, COUNT NumRetrieved)
{
	COUNT start_count;
	STAMP s;
	CONTEXT OldContext;

	PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_PICKUP),
			NotPositional (), NULL, GAME_SOUND_PRIORITY);

	if (scan == BIOLOGICAL_SCAN)
	{
		start_count = pPSD->BiologicalLevel;

		s.frame = SetAbsFrameIndex (LanderFrame[0], 41);

		pPSD->BiologicalLevel += NumRetrieved;
	}
	else
	{
		start_count = pPSD->ElementLevel;
		pPSD->ElementLevel += NumRetrieved;
		if (GET_GAME_STATE (IMPROVED_LANDER_CARGO))
		{
			start_count >>= 1;
			NumRetrieved = (pPSD->ElementLevel >> 1) - start_count;
		}

		s.frame = SetAbsFrameIndex (LanderFrame[0], 43);
	}

	s.origin.x = 0;
	s.origin.y = -(int)start_count;
	if (!(start_count & 1))
		s.frame = IncFrameIndex (s.frame);

	OldContext = SetContext (RadarContext);
	while (NumRetrieved--)
	{
		if (start_count++ & 1)
			s.frame = IncFrameIndex (s.frame);
		else
			s.frame = DecFrameIndex (s.frame);
		DrawStamp (&s);
		--s.origin.y;
	}
	SetContext (OldContext);
}

// returns true iff the node was picked up.
static bool
pickupMineralNode (PLANETSIDE_DESC *pPSD, COUNT NumRetrieved,
		ELEMENT *ElementPtr, const INTERSECT_CONTROL *LanderControl,
		const INTERSECT_CONTROL *ElementControl)
{
	BYTE EType;
	UNICODE ch;
	UNICODE *pStr;

	if (pPSD->ElementLevel >= pPSD->MaxElementLevel)
	{
		// Lander full
		PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_FULL),
				NotPositional (), NULL, GAME_SOUND_PRIORITY);
		return false;
	}

	if (pPSD->ElementLevel + NumRetrieved > pPSD->MaxElementLevel)
	{
		// Deposit could only be picked up partially.
		NumRetrieved = (COUNT)(pPSD->MaxElementLevel - pPSD->ElementLevel);
	}

	FillLanderHold (pPSD, MINERAL_SCAN, NumRetrieved);

	EType = ElementPtr->turn_wait;
	pPSD->ElementAmounts[ElementCategory (EType)] += NumRetrieved;

	pPSD->NumFrames = NUM_TEXT_FRAMES;
	sprintf (pPSD->AmountBuf, "%u", NumRetrieved);
	pStr = GAME_STRING (EType + ELEMENTS_STRING_BASE);

	pPSD->MineralText[0].baseline.x = (SURFACE_WIDTH >> 1)
			+ (ElementControl->EndPoint.x - LanderControl->EndPoint.x);
	pPSD->MineralText[0].baseline.y = (SURFACE_HEIGHT >> 1)
			+ (ElementControl->EndPoint.y - LanderControl->EndPoint.y);
	pPSD->MineralText[0].CharCount = (COUNT)~0;
	pPSD->MineralText[1].pStr = pStr;

	while ((ch = *pStr++) && ch != ' ')
		;
	if (ch == '\0')
	{
		pPSD->MineralText[1].CharCount = (COUNT)~0;
		pPSD->MineralText[2].CharCount = 0;
	}
	else  /* ch == ' ' */
	{
		// Name contains a space. Print over
		// two lines.
		pPSD->MineralText[1].CharCount = utf8StringCountN(
				pPSD->MineralText[1].pStr, pStr - 1);
		pPSD->MineralText[2].pStr = pStr;
		pPSD->MineralText[2].CharCount = (COUNT)~0;
	}

	return true;
}

static bool
pickupBioNode (PLANETSIDE_DESC *pPSD, COUNT NumRetrieved)
{
	if (pPSD->BiologicalLevel >= MAX_SCROUNGED)
	{
		// Lander is full.
		PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_FULL),
				NotPositional (), NULL, GAME_SOUND_PRIORITY);
		return false;
	}

	if (pPSD->BiologicalLevel + NumRetrieved > MAX_SCROUNGED)
	{
		// Node could only be picked up partially.
		NumRetrieved = (COUNT)(MAX_SCROUNGED - pPSD->BiologicalLevel);
	}

	FillLanderHold (pPSD, BIOLOGICAL_SCAN, NumRetrieved);

	return true;
}

static void
shotCreature (ELEMENT *ElementPtr, BYTE value,
		INTERSECT_CONTROL *LanderControl, PRIMITIVE *pPrim)
{
	if (ElementPtr->hit_points == 0)
	{
		// Creature is already canned.
		return;
	}

	--ElementPtr->hit_points;
	if (ElementPtr->hit_points == 0)
	{
		// Can creature.
		ElementPtr->mass_points = value;
		DisplayArray[ElementPtr->PrimIndex].Object.Stamp.frame =
				pSolarSysState->PlanetSideFrame[0];
	}
	else if (CreatureData[ElementPtr->mass_points & ~CREATURE_AWARE]
			.Attributes & SPEED_MASK)
	{
		COUNT angle;

		angle = FACING_TO_ANGLE (GetFrameIndex (
				LanderControl->IntersectStamp.frame) -
				ANGLE_TO_FACING (FULL_CIRCLE));
		DeltaVelocityComponents (&ElementPtr->velocity,
				COSINE (angle, WORLD_TO_VELOCITY (1)),
				SINE (angle, WORLD_TO_VELOCITY (1)));
		ElementPtr->thrust_wait = 0;
		ElementPtr->mass_points |= CREATURE_AWARE;
	}

	SetPrimType (pPrim, STAMPFILL_PRIM);
	SetPrimColor (pPrim, WHITE_COLOR);

	PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_HITS),
			NotPositional (), NULL, GAME_SOUND_PRIORITY);
}

static void
CheckObjectCollision (COUNT index)
{
	INTERSECT_CONTROL LanderControl;
	DRAWABLE LanderHandle;
	PRIMITIVE *pPrim;
	PRIMITIVE *pLanderPrim;
	PLANETSIDE_DESC *pPSD = planetSideDesc;

	if (index != END_OF_LIST)
	{
		pLanderPrim = &DisplayArray[index];
		LanderControl.IntersectStamp = pLanderPrim->Object.Stamp;
		index = GetPredLink (GetPrimLinks (pLanderPrim));
	}
	else
	{
		pLanderPrim = 0;
		LanderControl.IntersectStamp.origin.x = SURFACE_WIDTH >> 1;
		LanderControl.IntersectStamp.origin.y = SURFACE_HEIGHT >> 1;
		LanderControl.IntersectStamp.frame = LanderFrame[0];
		index = GetSuccLink (DisplayLinks);
	}

	LanderControl.EndPoint = LanderControl.IntersectStamp.origin;
	LanderHandle = GetFrameParentDrawable (LanderControl.IntersectStamp.frame);

	for (; index != END_OF_LIST; index = GetPredLink (GetPrimLinks (pPrim)))
	{
		INTERSECT_CONTROL ElementControl;
		HELEMENT hElement, hNextElement;

		pPrim = &DisplayArray[index];
		ElementControl.IntersectStamp = pPrim->Object.Stamp;
		ElementControl.EndPoint = ElementControl.IntersectStamp.origin;

		if (GetFrameParentDrawable (ElementControl.IntersectStamp.frame)
				== LanderHandle)
		{
			CheckObjectCollision (index);
			continue;
		}

		if (!DrawablesIntersect (&LanderControl,
				&ElementControl, MAX_TIME_VALUE))
			continue;

		for (hElement = GetHeadElement (); hElement; hElement = hNextElement)
		{
			ELEMENT *ElementPtr;

			LockElement (hElement, &ElementPtr);
			hNextElement = GetSuccElement (ElementPtr);

			if (&DisplayArray[ElementPtr->PrimIndex] == pLanderPrim)
			{
				ElementPtr->state_flags |= DISAPPEARING;
				UnlockElement (hElement);
				continue;
			}
			
			if (&DisplayArray[ElementPtr->PrimIndex] != pPrim
					|| ElementPtr->playerNr != PS_NON_PLAYER)
			{
				UnlockElement (hElement);
				continue;
			}

			{
				COUNT scan, NumRetrieved;
				SIZE which_node;

				scan = LOBYTE (ElementPtr->scan_node);
				if (pLanderPrim == 0)
				{
					/* Collision of lander with another object */
					if (crew_left == 0 || pPSD->InTransit)
						break;

					if (ElementPtr->state_flags & FINITE_LIFE)
					{
						/* A natural disaster */
						scan = ElementPtr->mass_points;
						switch (scan)
						{
							case EARTHQUAKE_DISASTER:
							case LAVASPOT_DISASTER:
								if (TFB_Random () % 100 < 25)
									DeltaLanderCrew (-1, scan);
								break;
						}

						UnlockElement (hElement);
						continue;
					}
					else if (scan == ENERGY_SCAN)
					{
						// noop; handled by generation funcs, see below
					}
					else if (scan == BIOLOGICAL_SCAN && ElementPtr->hit_points)
					{
						BYTE danger_vals[] =
						{
							0, 6, 13, 26
						};
						int creatureIndex = ElementPtr->mass_points
								& ~CREATURE_AWARE;
						int dangerLevel =
								(CreatureData[creatureIndex].Attributes &
								DANGER_MASK) >> DANGER_SHIFT;

						if (TFB_Random () % 128 < danger_vals[dangerLevel])
						{
							PlaySound (SetAbsSoundIndex (
									LanderSounds, BIOLOGICAL_DISASTER),
									NotPositional (), NULL,
									GAME_SOUND_PRIORITY);
							DeltaLanderCrew (-1, BIOLOGICAL_DISASTER);
						}
						UnlockElement (hElement);
						continue;
					}

					NumRetrieved = ElementPtr->mass_points;
				}
				else if (ElementPtr->state_flags & FINITE_LIFE)
				{
					/* Collision of a stun bolt with a natural disaster */
					UnlockElement (hElement);
					continue;
				}
				else
				{
					BYTE value;

					if (scan == ENERGY_SCAN)
					{
						/* Collision of a stun bolt with an energy node */
						UnlockElement (hElement);
						break;
					}

					if (scan == BIOLOGICAL_SCAN
							&& (value = LONIBBLE (CreatureData[
							ElementPtr->mass_points
							& ~CREATURE_AWARE
							].ValueAndHitPoints)))
					{
						/* Collision of a stun bolt with a viable creature */
						shotCreature (ElementPtr, value, &LanderControl,
								pPrim);
						UnlockElement (hElement);
						break;
					}

					NumRetrieved = 0;
				}

				if (NumRetrieved)
				{
					switch (scan)
					{
						case ENERGY_SCAN:
							break;
						case MINERAL_SCAN:
							if (!pickupMineralNode (pPSD, NumRetrieved,
									ElementPtr, &LanderControl,
									&ElementControl))
								continue;
							break;
						case BIOLOGICAL_SCAN:
							if (!pickupBioNode (pPSD, NumRetrieved))
								continue;
							break;
					}
				}

				which_node = HIBYTE (ElementPtr->scan_node) - 1;
				if (callPickupForScanType (pSolarSysState,
						pSolarSysState->pOrbitalDesc, which_node, scan))
				{	// Node retrieved, remove from the surface
					setNodeRetrieved (&pSolarSysState->SysInfo.PlanetInfo,
							scan, which_node);
					SET_GAME_STATE (PLANETARY_CHANGE, 1);
					ElementPtr->state_flags |= DISAPPEARING;
				}
				UnlockElement (hElement);
			}
		}
	}
}

static void
lightning_process (ELEMENT *ElementPtr)
{
	PRIMITIVE *pPrim;

	pPrim = &DisplayArray[ElementPtr->PrimIndex];
	if (LONIBBLE (ElementPtr->turn_wait))
		--ElementPtr->turn_wait;
	else
	{
		COUNT num_frames;

		num_frames = GetFrameCount (pPrim->Object.Stamp.frame) - 7;
		if (GetFrameIndex (pPrim->Object.Stamp.frame) >= num_frames)
		{
			/* Advance to the next surface strike effect frame */
			// XXX: This is unused, we never get here
			pPrim->Object.Stamp.frame =
					IncFrameIndex (pPrim->Object.Stamp.frame);
		}
		else
		{
			SIZE s;
			
			// XXX: Color cycling is largely unused, because the color
			//   never actually changes RGB values (see MAKE_RGB15 below).
			//   This did, however, work in DOS SC2 version (fade effect).
			s = 7 - ((SIZE)ElementPtr->cycle - (SIZE)ElementPtr->life_span);
			if (s < 0)
				s = 0;
			// XXX: Was 0x8000 the background flag on 3DO?
			//SetPrimColor (pPrim, BUILD_COLOR (0x8000 | MAKE_RGB15 (0x1F, 0x1F, 0x1F), s));
			SetPrimColor (pPrim, BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), s));

			if (ElementPtr->mass_points == LIGHTNING_DISASTER)
			{
				/* This one always strikes the lander and can hurt */
				if (crew_left && TFB_Random () % 100 < 10
						&& !planetSideDesc->InTransit)
					lander_flags |= KILL_CREW;

				ElementPtr->next.location = curLanderLoc;
			}

			pPrim->Object.Stamp.frame =
					 SetAbsFrameIndex (pPrim->Object.Stamp.frame,
					 TFB_Random () % num_frames);
		}

		ElementPtr->turn_wait += HINIBBLE (ElementPtr->turn_wait);
	}

	if (GetSuccLink (DisplayLinks) != ElementPtr->PrimIndex)
		lander_flags |= ADD_AT_END;
}

static void
AddLightning (void)
{
	HELEMENT hLightningElement;

	hLightningElement = AllocElement ();
	if (hLightningElement)
	{
		DWORD rand_val;
		ELEMENT *LightningElementPtr;

		LockElement (hLightningElement, &LightningElementPtr);

		LightningElementPtr->playerNr = PS_NON_PLAYER;
		LightningElementPtr->state_flags = FINITE_LIFE;
		LightningElementPtr->preprocess_func = lightning_process;
		if (TFB_Random () % 100 >= 25)
			LightningElementPtr->mass_points = 0; /* harmless */
		else
			LightningElementPtr->mass_points = LIGHTNING_DISASTER;

		rand_val = TFB_Random ();
		LightningElementPtr->life_span = 10 + (HIWORD (rand_val) % 10) + 1;
		LightningElementPtr->next.location.x = (curLanderLoc.x
				+ ((MAP_WIDTH << MAG_SHIFT) - ((SURFACE_WIDTH >> 1) - 6))
				+ (LOBYTE (rand_val) % (SURFACE_WIDTH - 12))
				) % (MAP_WIDTH << MAG_SHIFT);
		LightningElementPtr->next.location.y = (curLanderLoc.y
				+ ((MAP_HEIGHT << MAG_SHIFT) - ((SURFACE_HEIGHT >> 1) - 6))
				+ (HIBYTE (rand_val) % (SURFACE_HEIGHT - 12))
				) % (MAP_HEIGHT << MAG_SHIFT);

		LightningElementPtr->cycle = LightningElementPtr->life_span;
		
		SetPrimType (&DisplayArray[LightningElementPtr->PrimIndex], STAMPFILL_PRIM);
		SetPrimColor (&DisplayArray[LightningElementPtr->PrimIndex], WHITE_COLOR);
		DisplayArray[LightningElementPtr->PrimIndex].Object.Stamp.frame =
				LanderFrame[2];

		UnlockElement (hLightningElement);

		PutElement (hLightningElement);

		PlaySound (SetAbsSoundIndex (LanderSounds, LIGHTNING_DISASTER),
				NotPositional (), NULL, GAME_SOUND_PRIORITY);
	}
}

static HELEMENT
AddGroundDisaster (COUNT which_disaster)
{
	HELEMENT hGroundDisasterElement;

	hGroundDisasterElement = AllocElement ();
	if (hGroundDisasterElement)
	{
		DWORD rand_val;
		ELEMENT *GroundDisasterElementPtr;
		PRIMITIVE *pPrim;

		LockElement (hGroundDisasterElement, &GroundDisasterElementPtr);

		pPrim = &DisplayArray[GroundDisasterElementPtr->PrimIndex];
		GroundDisasterElementPtr->mass_points = which_disaster;
		GroundDisasterElementPtr->playerNr = PS_NON_PLAYER;
		GroundDisasterElementPtr->state_flags = FINITE_LIFE;
		GroundDisasterElementPtr->preprocess_func = object_animation;

		rand_val = TFB_Random ();
		GroundDisasterElementPtr->next.location.x = (curLanderLoc.x
				+ ((MAP_WIDTH << MAG_SHIFT) - (SURFACE_WIDTH * 3 / 8))
				+ (LOWORD (rand_val) % (SURFACE_WIDTH * 3 / 4))
				) % (MAP_WIDTH << MAG_SHIFT);
		GroundDisasterElementPtr->next.location.y = (curLanderLoc.y
				+ ((MAP_HEIGHT << MAG_SHIFT) - (SURFACE_HEIGHT * 3 / 8))
				+ (HIWORD (rand_val) % (SURFACE_HEIGHT * 3 / 4))
				) % (MAP_HEIGHT << MAG_SHIFT);


		if (which_disaster == EARTHQUAKE_DISASTER)
		{
			SetPrimType (pPrim, STAMPFILL_PRIM);
			pPrim->Object.Stamp.frame = LanderFrame[1];
			GroundDisasterElementPtr->turn_wait = MAKE_BYTE (2, 2);
		}
		else /* if (which_disaster == LAVASPOT_DISASTER) */
		{
			SetPrimType (pPrim, STAMP_PRIM);
			GroundDisasterElementPtr->facing =
					NORMALIZE_FACING (TFB_Random ());
			pPrim->Object.Stamp.frame = LanderFrame[3];
			GroundDisasterElementPtr->turn_wait = MAKE_BYTE (0, 0);
		}
		GroundDisasterElementPtr->life_span =
				GetFrameCount (pPrim->Object.Stamp.frame)
				* (LONIBBLE (GroundDisasterElementPtr->turn_wait) + 1) - 1;

		UnlockElement (hGroundDisasterElement);

		PutElement (hGroundDisasterElement);
	}

	return (hGroundDisasterElement);
}

// This function replaces the ELEMENT manipulations typically done by
// PreProcess() and PostProcess() in process.c. Lander code does not
// call RedrawQueue() & Co and thus does not reap the benefits (or curses,
// depending how you look at it) of automatic flags processing.
static void
BuildObjectList (void)
{
	DWORD rand_val;
	POINT org;
	HELEMENT hElement, hNextElement;
	PLANETSIDE_DESC *pPSD = planetSideDesc;

	DisplayLinks = MakeLinks (END_OF_LIST, END_OF_LIST);
	
	lander_flags &= ~KILL_CREW;

	rand_val = TFB_Random ();
	if (LOBYTE (HIWORD (rand_val)) < pPSD->FireChance)
	{
		AddGroundDisaster (LAVASPOT_DISASTER);
		PlaySound (SetAbsSoundIndex (LanderSounds, LAVASPOT_DISASTER),
				NotPositional (), NULL, GAME_SOUND_PRIORITY);
	}

	if (HIBYTE (LOWORD (rand_val)) < pPSD->TectonicsChance)
		AddGroundDisaster (EARTHQUAKE_DISASTER);

	if (LOBYTE (LOWORD (rand_val)) < pPSD->WeatherChance)
		AddLightning ();

	org = curLanderLoc;
	for (hElement = GetHeadElement ();
			hElement; hElement = hNextElement)
	{
		SIZE dx, dy;
		ELEMENT *ElementPtr;

		LockElement (hElement, &ElementPtr);

		if (ElementPtr->life_span == 0
				|| (ElementPtr->state_flags & DISAPPEARING))
		{
			hNextElement = GetSuccElement (ElementPtr);
			UnlockElement (hElement);
			RemoveElement (hElement);
			FreeElement (hElement);
			continue;
		}
		else if (ElementPtr->state_flags & FINITE_LIFE)
			--ElementPtr->life_span;

		lander_flags &= ~ADD_AT_END;

		if (ElementPtr->preprocess_func)
			(*ElementPtr->preprocess_func) (ElementPtr);

		GetNextVelocityComponents (&ElementPtr->velocity, &dx, &dy, 1);
		if (dx || dy)
		{
			ElementPtr->next.location.x += dx;
			ElementPtr->next.location.y += dy;
				/* if not lander's shot */
			if (ElementPtr->playerNr != PS_HUMAN_PLAYER)
			{
				if (ElementPtr->next.location.y < 0)
					ElementPtr->next.location.y = 0;
				else if (ElementPtr->next.location.y >= (MAP_HEIGHT << MAG_SHIFT))
					ElementPtr->next.location.y = (MAP_HEIGHT << MAG_SHIFT) - 1;
			}
			if (ElementPtr->next.location.x < 0)
				ElementPtr->next.location.x += MAP_WIDTH << MAG_SHIFT;
			else
				ElementPtr->next.location.x %= MAP_WIDTH << MAG_SHIFT;
			
			// XXX: APPEARING flag is set by scan.c for scanned blips
			if (ElementPtr->state_flags & APPEARING)
			{	// Update the location of a moving object on the scan map
				ElementPtr->current.location.x =
						ElementPtr->next.location.x >> MAG_SHIFT;
				ElementPtr->current.location.y =
						ElementPtr->next.location.y >> MAG_SHIFT;
			}
		}

		{
			PRIMITIVE *pPrim;

			pPrim = &DisplayArray[ElementPtr->PrimIndex];
			pPrim->Object.Stamp.origin.x =
					ElementPtr->next.location.x
					- org.x + (SURFACE_WIDTH >> 1);
			if (pPrim->Object.Stamp.origin.x >=
					(MAP_WIDTH << MAG_SHIFT) - (SURFACE_WIDTH * 3 / 2))
				pPrim->Object.Stamp.origin.x -= MAP_WIDTH << MAG_SHIFT;
			else if (pPrim->Object.Stamp.origin.x <=
					-((MAP_WIDTH << MAG_SHIFT) - (SURFACE_WIDTH * 3 / 2)))
				pPrim->Object.Stamp.origin.x += MAP_WIDTH << MAG_SHIFT;

			pPrim->Object.Stamp.origin.y =
					ElementPtr->next.location.y
					- org.y + (SURFACE_HEIGHT >> 1);

			if (lander_flags & ADD_AT_END)
				InsertPrim (&DisplayLinks, ElementPtr->PrimIndex, END_OF_LIST);
			else
				InsertPrim (&DisplayLinks, ElementPtr->PrimIndex, GetPredLink (DisplayLinks));
		}

		hNextElement = GetSuccElement (ElementPtr);
		UnlockElement (hElement);
	}
}

static void
ScrollPlanetSide (SIZE dx, SIZE dy, int landingOffset)
{
	POINT new_pt;
	STAMP lander_s, shadow_s, shield_s;
	CONTEXT OldContext;

	new_pt.y = curLanderLoc.y + dy;
	if (new_pt.y < 0)
	{
		new_pt.y = 0;
		dy = new_pt.y - curLanderLoc.y;
		dx = 0;
		ZeroVelocityComponents (&GLOBAL (velocity));
	}
	else if (new_pt.y > (MAP_HEIGHT << MAG_SHIFT) - 1)
	{
		new_pt.y = (MAP_HEIGHT << MAG_SHIFT) - 1;
		dy = new_pt.y - curLanderLoc.y;
		dx = 0;
		ZeroVelocityComponents (&GLOBAL (velocity));
	}

	new_pt.x = curLanderLoc.x + dx;
	if (new_pt.x < 0)
		new_pt.x += MAP_WIDTH << MAG_SHIFT;
	else if (new_pt.x >= MAP_WIDTH << MAG_SHIFT)
		new_pt.x -= MAP_WIDTH << MAG_SHIFT;
	
	curLanderLoc = new_pt;

	OldContext = SetContext (PlanetContext);

	BatchGraphics ();

	// Display planet area, accounting for horizontal wrapping if
	// near the edges.
	{
		STAMP s;

		ClearDrawable ();
		s.origin.x = -new_pt.x + (SURFACE_WIDTH >> 1);
		s.origin.y = -new_pt.y + (SURFACE_HEIGHT >> 1);
		s.frame = pSolarSysState->Orbit.TopoZoomFrame;
		DrawStamp (&s);
		s.origin.x += MAP_WIDTH << MAG_SHIFT;
		DrawStamp (&s);
		s.origin.x -= MAP_WIDTH << (MAG_SHIFT + 1);
		DrawStamp (&s);
	}

	BuildObjectList ();
	
	DrawBatch (DisplayArray, DisplayLinks, 0);

	// Draw the lander while is still alive and keep drawing for a few
	// frames while it is exploding
	if (crew_left || damage_index || explosion_index < 3)
	{
		lander_s.origin.x = SURFACE_WIDTH >> 1;
		lander_s.origin.y = (SURFACE_HEIGHT >> 1) + landingOffset;
		lander_s.frame = LanderFrame[0];

		if (landingOffset != ON_THE_GROUND)
		{	// Landing, draw a shadow
			shadow_s.origin.x = lander_s.origin.y + (SURFACE_WIDTH >> 1) - (SURFACE_HEIGHT >> 1);//2;
			shadow_s.origin.y = lander_s.origin.y;
			shadow_s.frame = lander_s.frame;
			SetContextForeGroundColor (BLACK_COLOR);
			DrawFilledStamp (&shadow_s);
		}

		if (damage_index == 0)
		{	// No damage -- normal lander
			DrawStamp (&lander_s);
		}
		else if (shieldHit)
		{	// Was protected by a shield
			--damage_index;
			if (damage_index > 0)
			{
				shield_s.origin = lander_s.origin;
				shield_s.frame = SetEquFrameIndex (
						LanderFrame[4], lander_s.frame);

				// XXX: Shouldn't this color-cycle with damage_index?
				//   damage_index is used, but only as a VGA index!
				/*SetContextForeGroundColor (BUILD_COLOR (
						MAKE_RGB15 (0x1F, 0x1F, 0x1F) | 0x8000,
						damage_index));*/
				SetContextForeGroundColor (
						BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), damage_index));
				DrawFilledStamp (&shield_s);
			}
			DrawStamp (&lander_s);
		}
		else
		{	// Direct hit, no shield
			--damage_index;
			SetContextForeGroundColor (
					DamageColorCycle (BLACK_COLOR, damage_index));
			DrawFilledStamp (&lander_s);
		}
	}
	
	if (landingOffset == ON_THE_GROUND && crew_left
			&& GetPredLink (DisplayLinks) != END_OF_LIST)
		CheckObjectCollision (END_OF_LIST);

	{
		PLANETSIDE_DESC *pPSD = planetSideDesc;
		if (pPSD->NumFrames)
		{
			--pPSD->NumFrames;
			SetContextForeGroundColor (pPSD->ColorCycle[pPSD->NumFrames >> 1]);

			pPSD->MineralText[0].baseline.x -= dx;
			pPSD->MineralText[0].baseline.y -= dy;
			font_DrawText (&pPSD->MineralText[0]);
			pPSD->MineralText[1].baseline.x =
					pPSD->MineralText[0].baseline.x;
			pPSD->MineralText[1].baseline.y =
					pPSD->MineralText[0].baseline.y + 7;
			font_DrawText (&pPSD->MineralText[1]);
			pPSD->MineralText[2].baseline.x =
					pPSD->MineralText[1].baseline.x;
			pPSD->MineralText[2].baseline.y =
					pPSD->MineralText[1].baseline.y + 7;
			font_DrawText (&pPSD->MineralText[2]);
		}
	}

	RedrawSurfaceScan (&new_pt);

	if (lander_flags & KILL_CREW)
		DeltaLanderCrew (-1, LIGHTNING_DISASTER);

	UnbatchGraphics ();

	SetContext (OldContext);
}

static void
animationInterframe (TimeCount *TimeIn, COUNT periods)
{
#define ANIM_FRAME_RATE  (ONE_SECOND / 30)

	for ( ; periods; --periods)
	{
		RotatePlanetSphere (TRUE);

		SleepThreadUntil (*TimeIn + ANIM_FRAME_RATE);
		*TimeIn = GetTimeCounter ();
	}
}

static void
AnimateLaunch (FRAME farray)
{
	RECT r;
	STAMP s;
	COUNT num_frames;
	TimeCount NextTime;

	SetContext (PlanetContext);

	r.corner.x = 0;
	r.corner.y = 0;
	r.extent.width = 0;
	r.extent.height = 0;
	s.origin.x = 0;
	s.origin.y = 0;
	s.frame = farray;

	for (num_frames = GetFrameCount (s.frame); num_frames; --num_frames)
	{
		NextTime = GetTimeCounter () + (ONE_SECOND / 22);

		BatchGraphics ();
		RepairBackRect (&r);
#ifdef SPIN_ON_LAUNCH
		RotatePlanetSphere (FALSE);
#else
		DrawDefaultPlanetSphere ();
#endif
		DrawStamp (&s);
		UnbatchGraphics ();

		GetFrameRect (s.frame, &r);
		s.frame = IncFrameIndex (s.frame);

		SleepThreadUntil (NextTime);
	}

	RepairBackRect (&r);
}

static void
AnimateLanderWarmup (void)
{
	SIZE num_crew;
	STAMP s;
	CONTEXT OldContext;
	TimeCount TimeIn = GetTimeCounter ();

	OldContext = SetContext (RadarContext);

	s.origin.x = 0;
	s.origin.y = 0;
	s.frame = SetAbsFrameIndex (LanderFrame[0],
			(ANGLE_TO_FACING (FULL_CIRCLE) << 1) + 1);

	DrawStamp (&s);

	animationInterframe (&TimeIn, 2);

	for (num_crew = 0; num_crew < (NUM_CREW_COLS * NUM_CREW_ROWS)
			&& GLOBAL_SIS (CrewEnlisted); ++num_crew)
	{
		animationInterframe (&TimeIn, 1);
		
		DeltaSISGauges (-1, 0, 0);
		DeltaLanderCrew (1, 0);
	}

	animationInterframe (&TimeIn, 2);

	if (GET_GAME_STATE (IMPROVED_LANDER_SHOT))
		s.frame = SetAbsFrameIndex (s.frame, 58);
	else
		s.frame = SetAbsFrameIndex (s.frame,
				(ANGLE_TO_FACING (FULL_CIRCLE) << 1) + 2);
	DrawStamp (&s);

	animationInterframe (&TimeIn, 2);

	if (GET_GAME_STATE (IMPROVED_LANDER_SPEED))
		s.frame = SetAbsFrameIndex (s.frame, 57);
	else
	{
		s.frame = SetAbsFrameIndex (s.frame,
				(ANGLE_TO_FACING (FULL_CIRCLE) << 1) + 3);
		DrawStamp (&s);

		animationInterframe (&TimeIn, 2);

		s.frame = IncFrameIndex (s.frame);
	}
	DrawStamp (&s);

	if (GET_GAME_STATE (IMPROVED_LANDER_CARGO))
	{
		animationInterframe (&TimeIn, 2);

		s.frame = SetAbsFrameIndex (s.frame, 59);
		DrawStamp (&s);
	}

	animationInterframe (&TimeIn, 2);

	PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_DEPARTS),
			NotPositional (), NULL, GAME_SOUND_PRIORITY + 1);
}

static void
InitPlanetSide (POINT pt)
{
	// Adjust landing location by a random jitter.
#define RANDOM_MISS 64
	// Jitter the X landing point.
	pt.x -= RANDOM_MISS - TFB_Random () % (RANDOM_MISS << 1);
	if (pt.x < 0)
		pt.x += (MAP_WIDTH << MAG_SHIFT);
	else if (pt.x >= (MAP_WIDTH << MAG_SHIFT))
		pt.x -= (MAP_WIDTH << MAG_SHIFT);

	// Jitter the Y landing point.
	pt.y -= RANDOM_MISS - TFB_Random () % (RANDOM_MISS << 1);
	if (pt.y < 0)
		pt.y = 0;
	else if (pt.y >= (MAP_HEIGHT << MAG_SHIFT))
		pt.y = (MAP_HEIGHT << MAG_SHIFT) - 1;

	curLanderLoc = pt;

	SetContext (PlanetContext);
	SetContextFont (TinyFont);

	{
		RECT r;

		GetContextClipRect (&r);

		SetTransitionSource (&r);
		BatchGraphics ();
		
		{
			STAMP s;

			// Note - This code is the same as in ScrollPlanetSize,
			// Display planet area, accounting for horizontal wrapping if
			// near the edges.
			ClearDrawable ();
			s.origin.x = -pt.x + (SURFACE_WIDTH >> 1);
			s.origin.y = -pt.y + (SURFACE_HEIGHT >> 1);
			s.frame = pSolarSysState->Orbit.TopoZoomFrame;
			DrawStamp (&s);
			s.origin.x += MAP_WIDTH << MAG_SHIFT;
			DrawStamp (&s);
			s.origin.x -= MAP_WIDTH << (MAG_SHIFT + 1);
			DrawStamp (&s);
		}

		ScreenTransition (3, &r);
		UnbatchGraphics ();
	}


	SET_GAME_STATE (PLANETARY_LANDING, 1);
}

static void
LanderFire (SIZE facing)
{
#define SHUTTLE_FIRE_WAIT 15
	HELEMENT hWeaponElement;
	SIZE wdx, wdy;
	ELEMENT *WeaponElementPtr;
	COUNT angle;

	hWeaponElement = AllocElement ();
	if (hWeaponElement == NULL)
		return;

	LockElement (hWeaponElement, &WeaponElementPtr);

	WeaponElementPtr->playerNr = PS_HUMAN_PLAYER;
	WeaponElementPtr->mass_points = 1;
	WeaponElementPtr->life_span = 12;
	WeaponElementPtr->state_flags = FINITE_LIFE;
	WeaponElementPtr->next.location = curLanderLoc;

	SetPrimType (&DisplayArray[WeaponElementPtr->PrimIndex], STAMP_PRIM);
	DisplayArray[WeaponElementPtr->PrimIndex].Object.Stamp.frame =
			SetAbsFrameIndex (LanderFrame[0],
			/* shot images immediately follow the lander images */
			facing + ANGLE_TO_FACING (FULL_CIRCLE));

	if (!CurrentInputState.key[PlayerControls[0]][KEY_UP])
	{
		wdx = 0;
		wdy = 0;
	}
	else
	{
		GetCurrentVelocityComponents (&GLOBAL (velocity), &wdx, &wdy);
	}

	angle = FACING_TO_ANGLE (facing);
	SetVelocityComponents (
			&WeaponElementPtr->velocity,
			COSINE (angle, WORLD_TO_VELOCITY (2 * 3)) + wdx,
			SINE (angle, WORLD_TO_VELOCITY (2 * 3)) + wdy);

	UnlockElement (hWeaponElement);

	InsertElement (hWeaponElement, GetHeadElement ());

	PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_SHOOTS),
			NotPositional (), NULL, GAME_SOUND_PRIORITY);
}

static BOOLEAN
LanderExplosion (void)
{
	HELEMENT hExplosionElement;
	ELEMENT *ExplosionElementPtr;

	hExplosionElement = AllocElement ();
	if (!hExplosionElement)
		return FALSE;

	LockElement (hExplosionElement, &ExplosionElementPtr);

	ExplosionElementPtr->playerNr = PS_HUMAN_PLAYER;
	ExplosionElementPtr->mass_points = DEATH_EXPLOSION;
	ExplosionElementPtr->state_flags = FINITE_LIFE;
	ExplosionElementPtr->next.location = curLanderLoc;
	ExplosionElementPtr->preprocess_func = object_animation;
	// Animation advances every 3rd frame
	ExplosionElementPtr->turn_wait = MAKE_BYTE (2, 2);
	ExplosionElementPtr->life_span = EXPLOSION_LIFE
			* (LONIBBLE (ExplosionElementPtr->turn_wait) + 1);

	SetPrimType (&DisplayArray[ExplosionElementPtr->PrimIndex],
			STAMP_PRIM);
	DisplayArray[ExplosionElementPtr->PrimIndex].Object.Stamp.frame =
			SetAbsFrameIndex (LanderFrame[0], 46);

	UnlockElement (hExplosionElement);

	InsertElement (hExplosionElement, GetHeadElement ());

	PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_DESTROYED),
			NotPositional (), NULL, GAME_SOUND_PRIORITY + 1);

	return TRUE;
}

static BOOLEAN
DoPlanetSide (LanderInputState *pMS)
{
	SIZE dx = 0;
	SIZE dy = 0;

#define SHUTTLE_TURN_WAIT 2
	if (GLOBAL (CurrentActivity) & CHECK_ABORT)
		return (FALSE);

	if (!pMS->Initialized)
	{
		COUNT landerSpeedNumer;
		COUNT angle;

		pMS->Initialized = TRUE;
		
		turn_wait = 0;
		weapon_wait = 0;

		angle = FACING_TO_ANGLE (GetFrameIndex (LanderFrame[0]));
		landerSpeedNumer = GET_GAME_STATE (IMPROVED_LANDER_SPEED) ?
				WORLD_TO_VELOCITY (2 * 14) :
				WORLD_TO_VELOCITY (2 * 8);

#ifdef FAST_FAST
landerSpeedNumer = WORLD_TO_VELOCITY (48);
#endif

		SetVelocityComponents (&GLOBAL (velocity),
				COSINE (angle, landerSpeedNumer) / LANDER_SPEED_DENOM,
				SINE (angle, landerSpeedNumer) / LANDER_SPEED_DENOM);

		return TRUE;
	}
	else if (crew_left /* alive and taking off */
			&& ((CurrentInputState.key[PlayerControls[0]][KEY_ESCAPE] ||
			CurrentInputState.key[PlayerControls[0]][KEY_SPECIAL])
			|| planetSideDesc->InTransit))
	{
		return FALSE;
	}
	else if (!crew_left && !damage_index)
	{	// Dead, damage dealt, and exploding
		if (explosion_index > EXPLOSION_LIFE + EXPLOSION_WAIT_FRAMES)
			return FALSE;
		
		if (explosion_index > EXPLOSION_LIFE)
		{	// Keep going until the wait expires
			++explosion_index;
		}
		else if (explosion_index == 0)
		{	// Start the explosion animation
			if (LanderExplosion ())
			{
				// Advance the state only once we've got the element
				++explosion_index;
			}
			else
			{	// We could not allocate because the queue was full, but
				// we will get another chance on the next iteration
				log_add (log_Warning, "DoPlanetSide(): could not"
						" allocate explosion element!");
			}
		}
	}
	else
	{
		if (crew_left)
		{
			SIZE index = GetFrameIndex (LanderFrame[0]);
			if (turn_wait)
				--turn_wait;
			else if (CurrentInputState.key[PlayerControls[0]][KEY_LEFT] ||
					CurrentInputState.key[PlayerControls[0]][KEY_RIGHT])
			{
				COUNT landerSpeedNumer;
				COUNT angle;

				if (CurrentInputState.key[PlayerControls[0]][KEY_LEFT])
					--index;
				else
					++index;

				index = NORMALIZE_FACING (index);
				LanderFrame[0] = SetAbsFrameIndex (LanderFrame[0], index);

				angle = FACING_TO_ANGLE (index);
				landerSpeedNumer = GET_GAME_STATE (IMPROVED_LANDER_SPEED) ?
						WORLD_TO_VELOCITY (2 * 14) :
						WORLD_TO_VELOCITY (2 * 8);

#ifdef FAST_FAST
landerSpeedNumer = WORLD_TO_VELOCITY (48);
#endif

				SetVelocityComponents (&GLOBAL (velocity),
						COSINE (angle, landerSpeedNumer) / LANDER_SPEED_DENOM,
						SINE (angle, landerSpeedNumer) / LANDER_SPEED_DENOM);

				turn_wait = SHUTTLE_TURN_WAIT;
			}

			if (!CurrentInputState.key[PlayerControls[0]][KEY_UP])
			{
				dx = 0;
				dy = 0;
			}
			else
				GetNextVelocityComponents (&GLOBAL (velocity), &dx, &dy, 1);

			if (weapon_wait)
				--weapon_wait;
			else if (CurrentInputState.key[PlayerControls[0]][KEY_WEAPON])
			{
				LanderFire (index);

				weapon_wait = SHUTTLE_FIRE_WAIT;
				if (GET_GAME_STATE (IMPROVED_LANDER_SHOT))
					weapon_wait >>= 1;
			}
		}
	}

	ScrollPlanetSide (dx, dy, ON_THE_GROUND);

	SleepThreadUntil (pMS->NextTime);
	// NOTE: The rate is not stabilized
	pMS->NextTime = GetTimeCounter () + PLANET_SIDE_RATE;

	return TRUE;
}

void
FreeLanderData (void)
{
	COUNT i;
	COUNT landerFrameCount;

	if (LanderFrame[0] == NULL)
		return;

	for (i = 0; i < NUM_ORBIT_THEMES; ++i)
	{
		DestroyMusic (OrbitMusic[i]);
		OrbitMusic[i] = 0;
	}

	DestroySound (ReleaseSound (LanderSounds));
	LanderSounds = 0;

	landerFrameCount = sizeof (LanderFrame) / sizeof (LanderFrame[0]);
	for (i = 0; i < landerFrameCount; ++i)
	{
		DestroyDrawable (ReleaseDrawable (LanderFrame[i]));
		LanderFrame[i] = 0;
	}
}

void
LoadLanderData (void)
{
	if (LanderFrame[0] != 0)
		return;

	LanderFrame[0] =
			CaptureDrawable (LoadGraphic (LANDER_MASK_PMAP_ANIM));
	LanderFrame[1] =
			CaptureDrawable (LoadGraphic (QUAKE_MASK_PMAP_ANIM));
	LanderFrame[2] =
			CaptureDrawable (LoadGraphic (LIGHTNING_MASK_ANIM));
	LanderFrame[3] =
			CaptureDrawable (LoadGraphic (LAVA_MASK_PMAP_ANIM));
	LanderFrame[4] =
			CaptureDrawable (LoadGraphic (LANDER_SHIELD_MASK_ANIM));
	LanderFrame[5] =
			CaptureDrawable (LoadGraphic (LANDER_LAUNCH_MASK_PMAP_ANIM));
	LanderFrame[6] =
			CaptureDrawable (LoadGraphic (LANDER_RETURN_MASK_PMAP_ANIM));
	LanderFrame[7] =
			CaptureDrawable (LoadGraphic (ORBIT_VIEW_ANIM));
	
	LanderSounds = CaptureSound (LoadSound (LANDER_SOUNDS));

	{
		COUNT i;

		for (i = 0; i < NUM_ORBIT_THEMES; ++i)
			OrbitMusic[i] = load_orbit_theme (i);
	}
}

void
SetPlanetMusic (BYTE planet_type)
{
	LanderMusic = OrbitMusic[planet_type % NUM_ORBIT_THEMES];
}

static void
ReturnToOrbit (void)
{
	CONTEXT OldContext;
	RECT r;

	OldContext = SetContext (PlanetContext);
	GetContextClipRect (&r);

	SetTransitionSource (&r);
	BatchGraphics ();
	DrawStarBackGround ();
	DrawPlanetSurfaceBorder ();
	RedrawSurfaceScan (NULL);
	ScreenTransition (3, &r);
	UnbatchGraphics ();

	SetContext (OldContext);
}

static void
IdlePlanetSide (LanderInputState *inputState, TimeCount howLong)
{
#define IDLE_OFFSET     
	TimeCount TimeOut = GetTimeCounter () + howLong;

	while (GetTimeCounter () < TimeOut)
	{
		// 10 to clear the lander off of the screen
		ScrollPlanetSide (0, 0, -(SURFACE_HEIGHT / 2 + 10));
		SleepThreadUntil (inputState->NextTime);
		inputState->NextTime += PLANET_SIDE_RATE;
	}
}

static void
LandingTakeoffSequence (LanderInputState *inputState, BOOLEAN landing)
{
// We cannot solve a quadratic equation in a macro, so use a sensible max
#define MAX_OFFSETS  20
// 10 to clear the lander off of the screen
#define DISTANCE_COVERED  (SURFACE_HEIGHT / 2 + 10)
	int landingOfs[MAX_OFFSETS];
	int start;
	int end;
	int delta;
	int index;

	// Produce smooth acceleration deltas from a simple 1..x progression
	delta = 0;
	for (index = 0; index < MAX_OFFSETS && delta < DISTANCE_COVERED; ++index)
	{
		delta += index + 1;
		landingOfs[index] = -delta;
	}
	assert (delta >= DISTANCE_COVERED && "Increase MAX_OFFSETS!");

	if (landing)
	{
		start = index - 1;
		end = -1;
		delta = -1;
	}
	else
	{	// takeoff
		start = 0;
		end = index;
		delta = +1;
	}

	if (landing)
		IdlePlanetSide (inputState, ONE_SECOND);

	// Draw the landing/takeoff lander positions
	for (index = start; index != end; index += delta)
	{
		ScrollPlanetSide (0, 0, landingOfs[index]);
		SleepThreadUntil (inputState->NextTime);
		inputState->NextTime += PLANET_SIDE_RATE;
	}

	if (!landing)
		IdlePlanetSide (inputState, ONE_SECOND / 2);
}

void
SetLanderTakeoff (void)
{
	assert (planetSideDesc != NULL);
	if (planetSideDesc)
		planetSideDesc->InTransit = TRUE;
}

// Returns whether the lander is still alive at the end of the sequence
bool
KillLanderCrewSeq (COUNT numKilled, DWORD period)
{
	TimeCount TimeOut;
	COUNT i;

	TimeOut = GetTimeCounter ();
	for (i = 0; i < numKilled && crew_left; ++i)
	{
		TimeOut += period;
		DeltaLanderCrew (-1, LANDER_INJURED);
		SleepThreadUntil (TimeOut);
	}
	
	return crew_left > 0;
}

// Maps a temperature to a (0-7) hazard rating.
// Thermal hazards aren't exposed to the user as a hazard number,
// but the code still works with them that way.
#define ARRAY_SIZE(array) (sizeof(array) / sizeof (*array))
unsigned
GetThermalHazardRating (int temp)
{
	static const int tempBreakpoints[] = { 50, 100, 150, 250, 350, 550, 800 };
	const size_t numBreakpoints = ARRAY_SIZE (tempBreakpoints);
	unsigned i;

	for (i = 0; i < numBreakpoints; ++i)
	{
		if (temp < tempBreakpoints[i])
			return i;
	}

	return numBreakpoints;
}

// Given a hazard type and rating, return the chance (out of 256) of the hazard
// being generated.
static BYTE
GetHazardChance (int hazardType, unsigned HazardRating)
{
	static const BYTE TectonicsChanceTab[] = {0*3, 0*3, 1*3, 2*3, 4*3,  8*3, 16*3, 32*3};
	static const BYTE WeatherChanceTab  [] = {0*3, 0*3, 1*3, 2*3, 3*3,  6*3, 12*3, 24*3};
	static const BYTE FireChanceTab     [] = {0*3, 0*3, 1*3, 2*3, 4*3, 12*3, 24*3, 48*3};

	switch (hazardType)
	{
		case EARTHQUAKE_DISASTER:
			return TectonicsChanceTab[HazardRating];
		case LIGHTNING_DISASTER:
			return WeatherChanceTab[HazardRating];
		case LAVASPOT_DISASTER:
			return FireChanceTab[HazardRating];
	}

	return 0;
}

void
PlanetSide (POINT planetLoc)
{
	SIZE index;
	LanderInputState landerInputState;
	PLANETSIDE_DESC PSD;

	memset (&PSD, 0, sizeof (PSD));
	PSD.InTransit = TRUE;

	// Set our chances of hazards occurring.
	PSD.TectonicsChance = GetHazardChance (EARTHQUAKE_DISASTER,
			pSolarSysState->SysInfo.PlanetInfo.Tectonics);
	PSD.WeatherChance = GetHazardChance (LIGHTNING_DISASTER,
			pSolarSysState->SysInfo.PlanetInfo.Weather);
	PSD.FireChance = GetHazardChance (LAVASPOT_DISASTER, GetThermalHazardRating (
			pSolarSysState->SysInfo.PlanetInfo.SurfaceTemperature));

	PSD.ElementLevel = GetStorageBayCapacity () - GLOBAL_SIS (TotalElementMass);
	PSD.MaxElementLevel = MAX_SCROUNGED;
	if (GET_GAME_STATE (IMPROVED_LANDER_CARGO))
		PSD.MaxElementLevel <<= 1;
	if (PSD.ElementLevel < PSD.MaxElementLevel)
		PSD.MaxElementLevel = PSD.ElementLevel;
	PSD.ElementLevel = 0;

	PSD.MineralText[0].align = ALIGN_CENTER;
	PSD.MineralText[0].pStr = PSD.AmountBuf;
	PSD.MineralText[1] = PSD.MineralText[0];
	PSD.MineralText[2] = PSD.MineralText[1];

	PSD.ColorCycle[0] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x03, 0x00), 0x7F);
	PSD.ColorCycle[1] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x00), 0x7D);
	PSD.ColorCycle[2] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x11, 0x00), 0x7B);
	PSD.ColorCycle[3] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x00), 0x71);
	for (index = 4; index < (NUM_TEXT_FRAMES >> 1) - 4; ++index)
	{
		PSD.ColorCycle[index] =
				BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F);
	}
	PSD.ColorCycle[(NUM_TEXT_FRAMES >> 1) - 4] =
			BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x00), 0x71);
	PSD.ColorCycle[(NUM_TEXT_FRAMES >> 1) - 3] =
			BUILD_COLOR (MAKE_RGB15 (0x1F, 0x11, 0x00), 0x7B);
	PSD.ColorCycle[(NUM_TEXT_FRAMES >> 1) - 2] =
			BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x00), 0x7D);
	PSD.ColorCycle[(NUM_TEXT_FRAMES >> 1) - 1] =
			BUILD_COLOR (MAKE_RGB15 (0x1F, 0x03, 0x00), 0x7F);
	planetSideDesc = &PSD;
	
	index = NORMALIZE_FACING (TFB_Random ());
	LanderFrame[0] = SetAbsFrameIndex (LanderFrame[0], index);
	crew_left = 0;
	damage_index = 0;
	explosion_index = 0;

	AnimateLanderWarmup ();
	AnimateLaunch (LanderFrame[5]);
	InitPlanetSide (planetLoc);

	landerInputState.NextTime = GetTimeCounter () + PLANET_SIDE_RATE;
	LandingTakeoffSequence (&landerInputState, TRUE);
	PSD.InTransit = FALSE;

	landerInputState.Initialized = FALSE;
	landerInputState.InputFunc = DoPlanetSide;
	SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
	DoInput (&landerInputState, FALSE);

	if (!(GLOBAL (CurrentActivity) & CHECK_ABORT))
	{
		if (crew_left == 0)
		{
			--GLOBAL_SIS (NumLanders);
			DrawLanders ();

			ReturnToOrbit ();
		}
		else
		{
			PSD.InTransit = TRUE;
			PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_RETURNS),
					NotPositional (), NULL, GAME_SOUND_PRIORITY + 1);

			LandingTakeoffSequence (&landerInputState, FALSE);
			ReturnToOrbit ();
			AnimateLaunch (LanderFrame[6]);
			
			DeltaSISGauges (crew_left, 0, 0);

			if (PSD.ElementLevel)
			{
				for (index = 0; index < NUM_ELEMENT_CATEGORIES; ++index)
				{
					GLOBAL_SIS (ElementAmounts[index]) +=
							PSD.ElementAmounts[index];
					GLOBAL_SIS (TotalElementMass) +=
							PSD.ElementAmounts[index];
				}
				DrawStorageBays (FALSE);
			}

			GLOBAL_SIS (TotalBioMass) += PSD.BiologicalLevel;
		}
	}

	planetSideDesc = NULL;

	{
		HELEMENT hElement, hNextElement;

		for (hElement = GetHeadElement ();
				hElement; hElement = hNextElement)
		{
			ELEMENT *ElementPtr;

			LockElement (hElement, &ElementPtr);
			hNextElement = _GetSuccLink (ElementPtr);
			if (ElementPtr->state_flags & FINITE_LIFE)
			{
				UnlockElement (hElement);

				RemoveElement (hElement);
				FreeElement (hElement);

				continue;
			}
			UnlockElement (hElement);
		}
	}

	ZeroVelocityComponents (&GLOBAL (velocity));
	SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
}

void
InitLander (BYTE LanderFlags)
{
	RECT r;

	SetContext (RadarContext);
	BatchGraphics ();
	
	r.corner.x = 0;
	r.corner.y = 0;
	r.extent.width = RADAR_WIDTH;
	r.extent.height = RADAR_HEIGHT;
	SetContextForeGroundColor (BLACK_COLOR);
	DrawFilledRectangle (&r);
	
	if (GLOBAL_SIS (NumLanders) || LanderFlags)
	{
		BYTE ShieldFlags, capacity_shift;
		COUNT free_space;
		STAMP s;

		s.origin.x = 0; /* set up powered-down lander */
		s.origin.y = 0;
		s.frame = SetAbsFrameIndex (LanderFrame[0],
				ANGLE_TO_FACING (FULL_CIRCLE) << 1);
		DrawStamp (&s);
		if (LanderFlags == 0)
		{
			ShieldFlags = GET_GAME_STATE (LANDER_SHIELDS);
			capacity_shift = GET_GAME_STATE (IMPROVED_LANDER_CARGO);
		}
		else
		{
			ShieldFlags = (unsigned char)(LanderFlags &
					((1 << EARTHQUAKE_DISASTER)
					| (1 << BIOLOGICAL_DISASTER)
					| (1 << LIGHTNING_DISASTER)
					| (1 << LAVASPOT_DISASTER)));
			s.frame = IncFrameIndex (s.frame);
			DrawStamp (&s);
			if (LanderFlags & (1 << (4 + 0)))
				s.frame = SetAbsFrameIndex (s.frame, 57);
			else
			{
				s.frame = SetAbsFrameIndex (s.frame,
						(ANGLE_TO_FACING (FULL_CIRCLE) << 1) + 3);
				DrawStamp (&s);
				s.frame = IncFrameIndex (s.frame);
			}
			DrawStamp (&s);
			if (!(LanderFlags & (1 << (4 + 1))))
				capacity_shift = 0;
			else
			{
				capacity_shift = 1;
				s.frame = SetAbsFrameIndex (s.frame, 59);
				DrawStamp (&s);
			}
			if (LanderFlags & (1 << (4 + 2)))
				s.frame = SetAbsFrameIndex (s.frame, 58);
			else
				s.frame = SetAbsFrameIndex (s.frame,
						(ANGLE_TO_FACING (FULL_CIRCLE) << 1) + 2);
			DrawStamp (&s);
		}

		free_space = GetStorageBayCapacity () - GLOBAL_SIS (TotalElementMass);
		if ((int)free_space < (int)(MAX_SCROUNGED << capacity_shift))
		{
			r.corner.x = 1;
			r.extent.width = 4;
			r.extent.height = MAX_SCROUNGED
					- (free_space >> capacity_shift) + 1;
			SetContextForeGroundColor (BLACK_COLOR);
			DrawFilledRectangle (&r);
		}

		s.frame = SetAbsFrameIndex (s.frame, 37);
		if (ShieldFlags & (1 << EARTHQUAKE_DISASTER))
			DrawStamp (&s);
		s.frame = IncFrameIndex (s.frame);
		if (ShieldFlags & (1 << BIOLOGICAL_DISASTER))
			DrawStamp (&s);
		s.frame = IncFrameIndex (s.frame);
		if (ShieldFlags & (1 << LIGHTNING_DISASTER))
			DrawStamp (&s);
		s.frame = IncFrameIndex (s.frame);
		if (ShieldFlags & (1 << LAVASPOT_DISASTER))
			DrawStamp (&s);
	}

	UnbatchGraphics ();
}
