diff options
Diffstat (limited to 'src/uqm/planets/lander.c')
-rw-r--r-- | src/uqm/planets/lander.c | 2101 |
1 files changed, 2101 insertions, 0 deletions
diff --git a/src/uqm/planets/lander.c b/src/uqm/planets/lander.c new file mode 100644 index 0000000..17aad8f --- /dev/null +++ b/src/uqm/planets/lander.c @@ -0,0 +1,2101 @@ +//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 (); +} |