diff options
Diffstat (limited to 'src/uqm/planets/scan.c')
-rw-r--r-- | src/uqm/planets/scan.c | 1385 |
1 files changed, 1385 insertions, 0 deletions
diff --git a/src/uqm/planets/scan.c b/src/uqm/planets/scan.c new file mode 100644 index 0000000..3d5d9fd --- /dev/null +++ b/src/uqm/planets/scan.c @@ -0,0 +1,1385 @@ +//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 "../build.h" +#include "../colors.h" +#include "../cons_res.h" +#include "../controls.h" +#include "../menustat.h" +#include "../encount.h" + // for EncounterGroup +#include "../gamestr.h" +#include "../nameref.h" +#include "../resinst.h" +#include "../settings.h" +#include "../util.h" +#include "../process.h" +#include "../setup.h" +#include "../sounds.h" +#include "../state.h" +#include "../sis.h" +#include "../save.h" +#include "options.h" +#include "libs/graphics/gfx_common.h" +#include "libs/graphics/drawable.h" +#include "libs/inplib.h" +#include "libs/mathlib.h" + +extern FRAME SpaceJunkFrame; + +// define SPIN_ON_SCAN to allow the planet to spin +// while scaning is going on +#undef SPIN_ON_SCAN + +#define FLASH_INDEX 105 + +static CONTEXT ScanContext; + +static POINT planetLoc; +static RECT cursorRect; +static FRAME eraseFrame; + +// ScanSystem() menu items +// The first three are from enum PlanetScanTypes in planets.h +enum ScanMenuItems +{ + EXIT_SCAN = NUM_SCAN_TYPES, + AUTO_SCAN, + DISPATCH_SHUTTLE, +}; + + +void +RepairBackRect (RECT *pRect) +{ + RECT new_r, old_r; + + GetContextClipRect (&old_r); + new_r.corner.x = pRect->corner.x + old_r.corner.x; + new_r.corner.y = pRect->corner.y + old_r.corner.y; + new_r.extent = pRect->extent; + + new_r.extent.height += new_r.corner.y & 1; + new_r.corner.y &= ~1; + DrawFromExtraScreen (&new_r); +} + +static void +EraseCoarseScan (void) +{ + SetContext (PlanetContext); + + BatchGraphics (); + DrawStarBackGround (); + DrawDefaultPlanetSphere (); + UnbatchGraphics (); +} + +static void +PrintScanTitlePC (TEXT *t, RECT *r, const char *txt, int xpos) +{ + t->baseline.x = xpos; + SetContextForeGroundColor (SCAN_PC_TITLE_COLOR); + t->pStr = txt; + t->CharCount = (COUNT)~0; + font_DrawText (t); + TextRect (t, r, NULL); + t->baseline.x += r->extent.width; + SetContextForeGroundColor (SCAN_INFO_COLOR); +} + +static void +MakeScanValue (UNICODE *buf, long val, const UNICODE *extra) +{ + if (val >= 10 * 100) + { // 1 decimal place + sprintf (buf, "%ld.%ld%s", val / 100, (val / 10) % 10, extra); + } + else + { // 2 decimal places + sprintf (buf, "%ld.%02ld%s", val / 100, val % 100, extra); + } +} + +static void +GetPlanetTitle (UNICODE *buf, COUNT bufsize) +{ + int val; + UNICODE *named = GetNamedPlanetaryBody (); + if (named) + { + utf8StringCopy (buf, bufsize, named); + return; + } + + // Unnamed body, use world type + val = pSolarSysState->pOrbitalDesc->data_index & ~PLANET_SHIELDED; + if (val >= FIRST_GAS_GIANT) + { + sprintf (buf, "%s", GAME_STRING (SCAN_STRING_BASE + 4 + 51)); + // Gas Giant + } + else + { + sprintf (buf, "%s %s", + GAME_STRING (SCAN_STRING_BASE + 4 + val), + GAME_STRING (SCAN_STRING_BASE + 4 + 50)); + // World + } +} + +static void +PrintCoarseScanPC (void) +{ +#define SCAN_LEADING_PC 14 + SDWORD val; + TEXT t; + RECT r; + UNICODE buf[200]; + + GetPlanetTitle (buf, sizeof (buf)); + + SetContext (PlanetContext); + + t.align = ALIGN_CENTER; + t.baseline.x = SIS_SCREEN_WIDTH >> 1; + t.baseline.y = 13; + t.pStr = buf; + t.CharCount = (COUNT)~0; + + SetContextForeGroundColor (SCAN_PC_TITLE_COLOR); + SetContextFont (MicroFont); + font_DrawText (&t); + + SetContextFont (TinyFont); + +#define LEFT_SIDE_BASELINE_X_PC 5 +#define RIGHT_SIDE_BASELINE_X_PC (SIS_SCREEN_WIDTH - 75) +#define SCAN_BASELINE_Y_PC 40 + + t.baseline.y = SCAN_BASELINE_Y_PC; + t.align = ALIGN_LEFT; + + PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE), + LEFT_SIDE_BASELINE_X_PC); // "Orbit: " + val = ((pSolarSysState->SysInfo.PlanetInfo.PlanetToSunDist * 100L + + (EARTH_RADIUS >> 1)) / EARTH_RADIUS); + MakeScanValue (buf, val, + GAME_STRING (ORBITSCAN_STRING_BASE + 1)); // " a.u." + t.pStr = buf; + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += SCAN_LEADING_PC; + + PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 2), + LEFT_SIDE_BASELINE_X_PC); // "Atmo: " + if (pSolarSysState->SysInfo.PlanetInfo.AtmoDensity == GAS_GIANT_ATMOSPHERE) + utf8StringCopy (buf, sizeof (buf), + GAME_STRING (ORBITSCAN_STRING_BASE + 3)); // "Super Thick" + else if (pSolarSysState->SysInfo.PlanetInfo.AtmoDensity == 0) + utf8StringCopy (buf, sizeof (buf), + GAME_STRING (ORBITSCAN_STRING_BASE + 4)); // "Vacuum" + else + { + val = (pSolarSysState->SysInfo.PlanetInfo.AtmoDensity * 100 + + (EARTH_ATMOSPHERE >> 1)) / EARTH_ATMOSPHERE; + MakeScanValue (buf, val, + GAME_STRING (ORBITSCAN_STRING_BASE + 5)); // " atm" + } + t.pStr = buf; + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += SCAN_LEADING_PC; + + PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 6), + LEFT_SIDE_BASELINE_X_PC); // "Temp: " + sprintf (buf, "%d" STR_DEGREE_SIGN " c", + pSolarSysState->SysInfo.PlanetInfo.SurfaceTemperature); + t.pStr = buf; + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += SCAN_LEADING_PC; + + PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 7), + LEFT_SIDE_BASELINE_X_PC); // "Weather: " + if (pSolarSysState->SysInfo.PlanetInfo.AtmoDensity == 0) + t.pStr = GAME_STRING (ORBITSCAN_STRING_BASE + 8); // "None" + else + { + sprintf (buf, "%s %u", + GAME_STRING (ORBITSCAN_STRING_BASE + 9), // "Class" + pSolarSysState->SysInfo.PlanetInfo.Weather + 1); + t.pStr = buf; + } + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += SCAN_LEADING_PC; + + PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 10), + LEFT_SIDE_BASELINE_X_PC); // "Tectonics: " + if (PLANSIZE (pSolarSysState->SysInfo.PlanetInfo.PlanDataPtr->Type) == + GAS_GIANT) + t.pStr = GAME_STRING (ORBITSCAN_STRING_BASE + 8); // "None" + else + { + sprintf (buf, "%s %u", + GAME_STRING (ORBITSCAN_STRING_BASE + 9), // "Class" + pSolarSysState->SysInfo.PlanetInfo.Tectonics + 1); + t.pStr = buf; + } + t.CharCount = (COUNT)~0; + font_DrawText (&t); + + t.baseline.y = SCAN_BASELINE_Y_PC; + + PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 11), + RIGHT_SIDE_BASELINE_X_PC); // "Mass: " + val = pSolarSysState->SysInfo.PlanetInfo.PlanetRadius; + val = ((DWORD) val * (DWORD) val * (DWORD) val / 100L + * pSolarSysState->SysInfo.PlanetInfo.PlanetDensity + + ((100L * 100L) >> 1)) / (100L * 100L); + if (val == 0) + val = 1; + MakeScanValue (buf, val, + GAME_STRING (ORBITSCAN_STRING_BASE + 12)); // " e.s." + t.pStr = buf; + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += SCAN_LEADING_PC; + + PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 13), + RIGHT_SIDE_BASELINE_X_PC); // "Radius: " + val = pSolarSysState->SysInfo.PlanetInfo.PlanetRadius; + MakeScanValue (buf, val, + GAME_STRING (ORBITSCAN_STRING_BASE + 12)); // " e.s." + t.pStr = buf; + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += SCAN_LEADING_PC; + + PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 14), + RIGHT_SIDE_BASELINE_X_PC); // "Gravity: " + val = pSolarSysState->SysInfo.PlanetInfo.SurfaceGravity; + if (val == 0) + val = 1; + MakeScanValue (buf, val, + GAME_STRING (ORBITSCAN_STRING_BASE + 15)); // " g." + t.pStr = buf; + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += SCAN_LEADING_PC; + + PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 16), + RIGHT_SIDE_BASELINE_X_PC); // "Day: " + val = (SDWORD)pSolarSysState->SysInfo.PlanetInfo.RotationPeriod + * 10 / 24; + MakeScanValue (buf, val, + GAME_STRING (ORBITSCAN_STRING_BASE + 17)); // " days" + t.pStr = buf; + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += SCAN_LEADING_PC; + + PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 18), + RIGHT_SIDE_BASELINE_X_PC); // "Tilt: " + val = pSolarSysState->SysInfo.PlanetInfo.AxialTilt; + if (val < 0) + val = -val; + t.pStr = buf; + sprintf (buf, "%d" STR_DEGREE_SIGN, val); + t.CharCount = (COUNT)~0; + font_DrawText (&t); +} + +static void +PrintCoarseScan3DO (void) +{ +#define SCAN_LEADING 19 + SDWORD val; + TEXT t; + STAMP s; + UNICODE buf[200]; + + GetPlanetTitle (buf, sizeof (buf)); + + SetContext (PlanetContext); + + t.align = ALIGN_CENTER; + t.baseline.x = SIS_SCREEN_WIDTH >> 1; + t.baseline.y = 13; + t.pStr = buf; + t.CharCount = (COUNT)~0; + + SetContextForeGroundColor (SCAN_INFO_COLOR); + SetContextFont (MicroFont); + font_DrawText (&t); + + s.origin.x = s.origin.y = 0; + s.origin.x = 16 - SAFE_X; + s.frame = SetAbsFrameIndex (SpaceJunkFrame, 20); + DrawStamp (&s); + +#define LEFT_SIDE_BASELINE_X (27 + (16 - SAFE_X)) +#define RIGHT_SIDE_BASELINE_X (SIS_SCREEN_WIDTH - LEFT_SIDE_BASELINE_X) +#define SCAN_BASELINE_Y 25 + + t.baseline.x = LEFT_SIDE_BASELINE_X; + t.baseline.y = SCAN_BASELINE_Y; + t.align = ALIGN_LEFT; + + t.pStr = buf; + val = ((pSolarSysState->SysInfo.PlanetInfo.PlanetToSunDist * 100L + + (EARTH_RADIUS >> 1)) / EARTH_RADIUS); + MakeScanValue (buf, val, STR_EARTH_SIGN); + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += SCAN_LEADING; + + t.pStr = buf; + if (pSolarSysState->SysInfo.PlanetInfo.AtmoDensity == GAS_GIANT_ATMOSPHERE) + strcpy (buf, STR_INFINITY_SIGN); + else + { + val = (pSolarSysState->SysInfo.PlanetInfo.AtmoDensity * 100 + + (EARTH_ATMOSPHERE >> 1)) / EARTH_ATMOSPHERE; + MakeScanValue (buf, val, STR_EARTH_SIGN); + } + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += SCAN_LEADING; + + t.pStr = buf; + sprintf (buf, "%d" STR_DEGREE_SIGN, + pSolarSysState->SysInfo.PlanetInfo.SurfaceTemperature); + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += SCAN_LEADING; + + t.pStr = buf; + sprintf (buf, "<%u>", pSolarSysState->SysInfo.PlanetInfo.AtmoDensity == 0 + ? 0 : (pSolarSysState->SysInfo.PlanetInfo.Weather + 1)); + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += SCAN_LEADING; + + t.pStr = buf; + sprintf (buf, "<%u>", + PLANSIZE ( + pSolarSysState->SysInfo.PlanetInfo.PlanDataPtr->Type + ) == GAS_GIANT + ? 0 : (pSolarSysState->SysInfo.PlanetInfo.Tectonics + 1)); + t.CharCount = (COUNT)~0; + font_DrawText (&t); + + t.baseline.x = RIGHT_SIDE_BASELINE_X; + t.baseline.y = SCAN_BASELINE_Y; + t.align = ALIGN_RIGHT; + + t.pStr = buf; + val = pSolarSysState->SysInfo.PlanetInfo.PlanetRadius; + val = ((DWORD) val * (DWORD) val * (DWORD) val / 100L + * pSolarSysState->SysInfo.PlanetInfo.PlanetDensity + + ((100L * 100L) >> 1)) / (100L * 100L); + if (val == 0) + val = 1; + MakeScanValue (buf, val, STR_EARTH_SIGN); + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += SCAN_LEADING; + + t.pStr = buf; + val = pSolarSysState->SysInfo.PlanetInfo.PlanetRadius; + MakeScanValue (buf, val, STR_EARTH_SIGN); + + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += SCAN_LEADING; + + t.pStr = buf; + val = pSolarSysState->SysInfo.PlanetInfo.SurfaceGravity; + if (val == 0) + val = 1; + MakeScanValue (buf, val, STR_EARTH_SIGN); + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += SCAN_LEADING; + + t.pStr = buf; + val = pSolarSysState->SysInfo.PlanetInfo.AxialTilt; + if (val < 0) + val = -val; + sprintf (buf, "%d" STR_DEGREE_SIGN, val); + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += SCAN_LEADING; + + t.pStr = buf; + val = (SDWORD)pSolarSysState->SysInfo.PlanetInfo.RotationPeriod + * 10 / 24; + MakeScanValue (buf, val, STR_EARTH_SIGN); + t.CharCount = (COUNT)~0; + font_DrawText (&t); +} + +static void +initPlanetLocationImage (void) +{ + FRAME cursorFrame; + + // Get the cursor image + cursorFrame = SetAbsFrameIndex (MiscDataFrame, FLASH_INDEX); + cursorRect.extent = GetFrameBounds (cursorFrame); +} + +static void +savePlanetLocationImage (void) +{ + RECT r; + FRAME cursorFrame = SetAbsFrameIndex (MiscDataFrame, FLASH_INDEX); + HOT_SPOT hs = GetFrameHot (cursorFrame); + + DestroyDrawable (ReleaseDrawable (eraseFrame)); + + r = cursorRect; + r.corner.x -= hs.x; + r.corner.y -= hs.y; + eraseFrame = CaptureDrawable (CopyContextRect (&r)); + SetFrameHot (eraseFrame, hs); +} + +static void +restorePlanetLocationImage (void) +{ + STAMP s; + + s.origin = cursorRect.corner; + s.frame = eraseFrame; // saved image + DrawStamp (&s); +} + +static void +drawPlanetCursor (BOOLEAN filled) +{ + STAMP s; + + s.origin = cursorRect.corner; + s.frame = SetAbsFrameIndex (MiscDataFrame, FLASH_INDEX); + if (filled) + DrawFilledStamp (&s); + else + DrawStamp (&s); +} + +static void +setPlanetCursorLoc (POINT new_pt) +{ + new_pt.x >>= MAG_SHIFT; + new_pt.y >>= MAG_SHIFT; + cursorRect.corner = new_pt; +} + +static void +setPlanetLoc (POINT new_pt, BOOLEAN restoreOld) +{ + planetLoc = new_pt; + + SetContext (ScanContext); + if (restoreOld) + restorePlanetLocationImage (); + setPlanetCursorLoc (new_pt); + savePlanetLocationImage (); +} + +static void +flashPlanetLocation (void) +{ +#define FLASH_FRAME_DELAY (ONE_SECOND / 16) + static BYTE c = 0x00; + static int val = -2; + static POINT prevPt; + static TimeCount NextTime = 0; + BOOLEAN locChanged; + TimeCount Now = GetTimeCounter (); + + locChanged = prevPt.x != cursorRect.corner.x + || prevPt.y != cursorRect.corner.y; + + if (!locChanged && Now < NextTime) + return; // nothing to do + + if (locChanged) + { // Reset the flashing cycle + c = 0x00; + val = -2; + prevPt = cursorRect.corner; + + NextTime = Now + FLASH_FRAME_DELAY; + } + else + { // Continue the flashing cycle + if (c == 0x00 || c == 0x1A) + val = -val; + c += val; + + if (Now - NextTime > FLASH_FRAME_DELAY) + NextTime = Now + FLASH_FRAME_DELAY; // missed timing by too much + else + NextTime += FLASH_FRAME_DELAY; // stable frame rate + } + + SetContext (ScanContext); + SetContextForeGroundColor (BUILD_COLOR (MAKE_RGB15 (c, c, c), c)); + drawPlanetCursor (TRUE); +} + +void +RedrawSurfaceScan (const POINT *newLoc) +{ + CONTEXT OldContext; + + OldContext = SetContext (ScanContext); + + BatchGraphics (); + DrawPlanet (0, BLACK_COLOR); + DrawScannedObjects (TRUE); + if (newLoc) + { + setPlanetLoc (*newLoc, FALSE); + drawPlanetCursor (FALSE); + } + UnbatchGraphics (); + + SetContext (OldContext); +} + +static COUNT +getLandingFuelNeeded (void) +{ + COUNT fuel; + + fuel = pSolarSysState->SysInfo.PlanetInfo.SurfaceGravity << 1; + if (fuel > 3 * FUEL_TANK_SCALE) + fuel = 3 * FUEL_TANK_SCALE; + + return fuel; +} + +static void +spawnFwiffo (void) +{ + HSHIPFRAG hStarShip; + + EncounterGroup = 0; + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + hStarShip = CloneShipFragment (SPATHI_SHIP, + &GLOBAL (npc_built_ship_q), 1); + if (hStarShip) + { + SHIP_FRAGMENT *StarShipPtr; + + StarShipPtr = LockShipFrag (&GLOBAL (npc_built_ship_q), + hStarShip); + // Name Fwiffo + StarShipPtr->captains_name_index = NAME_OFFSET + + NUM_CAPTAINS_NAMES; + UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip); + } +} + +// Returns TRUE if the parent menu should remain +static BOOLEAN +DispatchLander (void) +{ + InputFrameCallback *oldCallback; + SIZE landingFuel = getLandingFuelNeeded (); + + EraseCoarseScan (); + + // Deactivate planet rotation callback + oldCallback = SetInputCallback (NULL); + + DeltaSISGauges (0, -landingFuel, 0); + SetContext (ScanContext); + drawPlanetCursor (FALSE); + + PlanetSide (planetLoc); + if (GLOBAL (CurrentActivity) & CHECK_ABORT) + return FALSE; + + if (GET_GAME_STATE (FOUND_PLUTO_SPATHI) == 1) + { + /* Create Fwiffo group and go into comm with it */ + spawnFwiffo (); + + NextActivity |= CHECK_LOAD; /* fake a load game */ + GLOBAL (CurrentActivity) |= START_ENCOUNTER; + SaveSolarSysLocation (); + + return FALSE; + } + + if (optWhichCoarseScan == OPT_PC) + PrintCoarseScanPC (); + else + PrintCoarseScan3DO (); + + // Reactivate planet rotation callback + SetInputCallback (oldCallback); + + return TRUE; +} + +typedef struct +{ + bool success; + // true when player selected a location +} PICK_PLANET_STATE; + +static BOOLEAN +DoPickPlanetSide (MENU_STATE *pMS) +{ + PICK_PLANET_STATE *pickState = pMS->privData; + DWORD TimeIn = GetTimeCounter (); + BOOLEAN select, cancel; + + select = PulsedInputState.menu[KEY_MENU_SELECT]; + cancel = PulsedInputState.menu[KEY_MENU_CANCEL]; + + if (GLOBAL (CurrentActivity) & CHECK_ABORT) + { + pickState->success = false; + return FALSE; + } + + if (cancel) + { + pickState->success = false; + return FALSE; + } + else if (select) + { + pickState->success = true; + return FALSE; + } + else + { + SIZE dx = 0; + SIZE dy = 0; + POINT new_pt; + + new_pt = planetLoc; + + if (CurrentInputState.menu[KEY_MENU_LEFT]) + dx = -1; + if (CurrentInputState.menu[KEY_MENU_RIGHT]) + dx = 1; + if (CurrentInputState.menu[KEY_MENU_UP]) + dy = -1; + if (CurrentInputState.menu[KEY_MENU_DOWN]) + dy = 1; + + BatchGraphics (); + + dx = dx << MAG_SHIFT; + if (dx) + { + new_pt.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); + } + dy = dy << MAG_SHIFT; + if (dy) + { + new_pt.y += dy; + if (new_pt.y < 0 || new_pt.y >= (MAP_HEIGHT << MAG_SHIFT)) + new_pt.y = planetLoc.y; + } + + if (!pointsEqual (new_pt, planetLoc)) + { + setPlanetLoc (new_pt, TRUE); + } + + flashPlanetLocation (); + + UnbatchGraphics (); + + SleepThreadUntil (TimeIn + ONE_SECOND / 40); + } + + return TRUE; +} + +static void +drawLandingFuelUsage (COUNT fuel) +{ + UNICODE buf[100]; + + sprintf (buf, "%s%1.1f", + GAME_STRING (NAVIGATION_STRING_BASE + 5), + (float) fuel / FUEL_TANK_SCALE); + DrawStatusMessage (buf); +} + +static void +eraseLandingFuelUsage (void) +{ + DrawStatusMessage (NULL); +} + +static BOOLEAN +PickPlanetSide (void) +{ + MENU_STATE MenuState; + PICK_PLANET_STATE PickState; + COUNT fuel = getLandingFuelNeeded (); + BOOLEAN retval = TRUE; + + memset (&MenuState, 0, sizeof MenuState); + MenuState.privData = &PickState; + + ClearSISRect (CLEAR_SIS_RADAR); + SetContext (ScanContext); + BatchGraphics (); + DrawPlanet (0, BLACK_COLOR); + DrawScannedObjects (FALSE); + UnbatchGraphics (); + + drawLandingFuelUsage (fuel); + // Set the current flash location + setPlanetCursorLoc (planetLoc); + savePlanetLocationImage (); + + InitLander (0); + + SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_SELECT); + + PickState.success = false; + MenuState.InputFunc = DoPickPlanetSide; + DoInput (&MenuState, TRUE); + + eraseLandingFuelUsage (); + if (PickState.success) + { // player chose a location + retval = DispatchLander (); + } + else + { // player bailed out + restorePlanetLocationImage (); + } + + SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); + + return retval; +} + +#define NUM_FLASH_COLORS 8 + +static void +DrawScannedStuff (COUNT y, COUNT scan) +{ + HELEMENT hElement, hNextElement; + Color OldColor; + + OldColor = SetContextForeGroundColor (BLACK_COLOR); + + for (hElement = GetHeadElement (); hElement; hElement = hNextElement) + { + ELEMENT *ElementPtr; + SIZE dy; + STAMP s; + + LockElement (hElement, &ElementPtr); + hNextElement = GetSuccElement (ElementPtr); + + dy = y - ElementPtr->current.location.y; + if (LOBYTE (ElementPtr->scan_node) != scan || dy < 0) + { // node of wrong type, or not time for it yet + UnlockElement (hElement); + continue; + } + + // XXX: flag this as 'found' scanned object + ElementPtr->state_flags |= APPEARING; + + s.origin = ElementPtr->current.location; + + if (dy >= NUM_FLASH_COLORS) + { // flashing done for this node, draw normal + s.frame = ElementPtr->next.image.frame; + DrawStamp (&s); + } + else + { + BYTE grad; + Color c = WHITE_COLOR; + COUNT nodeSize; + + // mineral -- white --> turquoise?? (contrasts with red) + // energy -- white --> red (contrasts with white) + // bio -- white --> violet (contrasts with green) + grad = 0xff - 0xff * dy / (NUM_FLASH_COLORS - 1); + switch (scan) + { + case MINERAL_SCAN: + c.r = grad; + break; + case ENERGY_SCAN: + c.g = grad; + c.b = grad; + break; + case BIOLOGICAL_SCAN: + c.g = grad; + break; + } + + SetContextForeGroundColor (c); + + // flash the node from the smallest size to node size + // Get the node size for mineral, or number of transitions + // for other scan types (was set by GeneratePlanetSide()) + nodeSize = GetFrameIndex (ElementPtr->next.image.frame) + - GetFrameIndex (ElementPtr->current.image.frame); + if (dy > nodeSize) + dy = nodeSize; + + s.frame = SetRelFrameIndex (ElementPtr->current.image.frame, dy); + DrawFilledStamp (&s); + } + + UnlockElement (hElement); + } + + SetContextForeGroundColor (OldColor); +} + +COUNT +callGenerateForScanType (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT node, BYTE scanType, NODE_INFO *info) +{ + switch (scanType) + { + case MINERAL_SCAN: + return (*solarSys->genFuncs->generateMinerals) ( + solarSys, world, node, info); + case ENERGY_SCAN: + return (*solarSys->genFuncs->generateEnergy) ( + solarSys, world, node, info); + case BIOLOGICAL_SCAN: + return (*solarSys->genFuncs->generateLife) ( + solarSys, world, node, info); + } + + assert (false); + return 0; +} + +bool +callPickupForScanType (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT node, BYTE scanType) +{ + switch (scanType) + { + case MINERAL_SCAN: + return (*solarSys->genFuncs->pickupMinerals) ( + solarSys, world, node); + case ENERGY_SCAN: + return (*solarSys->genFuncs->pickupEnergy) ( + solarSys, world, node); + case BIOLOGICAL_SCAN: + return (*solarSys->genFuncs->pickupLife) ( + solarSys, world, node); + } + + assert (false); + return false; +} + +static void +ScanPlanet (COUNT scanType) +{ +#define SCAN_DURATION (ONE_SECOND * 7 / 4) +// NUM_FLASH_COLORS for flashing blips; 1 for the final frame +#define SCAN_LINES (MAP_HEIGHT + NUM_FLASH_COLORS + 1) +#define SCAN_LINE_WAIT (SCAN_DURATION / SCAN_LINES) + + COUNT startScan, endScan; + COUNT scan; + RECT r; + static const Color textColors[] = + { + SCAN_MINERAL_TEXT_COLOR, + SCAN_ENERGY_TEXT_COLOR, + SCAN_BIOLOGICAL_TEXT_COLOR, + }; + static const Color tintColors[] = + { + SCAN_MINERAL_TINT_COLOR, + SCAN_ENERGY_TINT_COLOR, + SCAN_BIOLOGICAL_TINT_COLOR, + }; + + if (scanType == AUTO_SCAN) + { + startScan = MINERAL_SCAN; + endScan = BIOLOGICAL_SCAN; + } + else + { + startScan = scanType; + endScan = scanType; + } + + for (scan = startScan; scan <= endScan; ++scan) + { + TEXT t; + SWORD i; + Color tintColor; + // Alpha value will be ignored. + TimeCount TimeOut; + + t.baseline.x = SIS_SCREEN_WIDTH >> 1; + t.baseline.y = SIS_SCREEN_HEIGHT - MAP_HEIGHT - 7; + t.align = ALIGN_CENTER; + t.CharCount = (COUNT)~0; + + t.pStr = GAME_STRING (SCAN_STRING_BASE + scan); + + SetContext (PlanetContext); + r.corner.x = 0; + r.corner.y = t.baseline.y - 10; + r.extent.width = SIS_SCREEN_WIDTH; + r.extent.height = t.baseline.y - r.corner.y + 1; + // XXX: I do not know why we are repairing it here, as there + // should not be anything drawn over the stars at the moment + RepairBackRect (&r); + + SetContextFont (MicroFont); + SetContextForeGroundColor (textColors[scan]); + font_DrawText (&t); + + SetContext (ScanContext); + + // Draw a virgin surface + BatchGraphics (); + DrawPlanet (0, BLACK_COLOR); + UnbatchGraphics (); + + tintColor = tintColors[scan]; + + // Draw the scan slowly line by line + TimeOut = GetTimeCounter (); + for (i = 0; i < SCAN_LINES; i++) + { + TimeOut += SCAN_LINE_WAIT; + if (WaitForAnyButtonUntil (TRUE, TimeOut, FALSE)) + break; + + BatchGraphics (); + DrawPlanet (i, tintColor); + DrawScannedStuff (i, scan); + UnbatchGraphics (); +#ifdef SPIN_ON_SCAN + RotatePlanetSphere (TRUE); +#endif + } + + if (i < SCAN_LINES) + { // Aborted by a keypress; draw in finished state + BatchGraphics (); + DrawPlanet (SCAN_LINES - 1, tintColor); + DrawScannedStuff (SCAN_LINES - 1, scan); + UnbatchGraphics (); + } + } + + SetContext (PlanetContext); + RepairBackRect (&r); + + SetContext (ScanContext); + if (scanType == AUTO_SCAN) + { // clear the last scan + DrawPlanet (0, BLACK_COLOR); + DrawScannedObjects (FALSE); + } + + FlushInput (); +} + +static BOOLEAN +DoScan (MENU_STATE *pMS) +{ + BOOLEAN select, cancel; + + select = PulsedInputState.menu[KEY_MENU_SELECT]; + cancel = PulsedInputState.menu[KEY_MENU_CANCEL]; + + if (GLOBAL (CurrentActivity) & CHECK_ABORT) + return FALSE; + + if (cancel || (select && pMS->CurState == EXIT_SCAN)) + { + return FALSE; + } + else if (select) + { + if (pMS->CurState == DISPATCH_SHUTTLE) + { + COUNT fuel_required; + + if ((pSolarSysState->pOrbitalDesc->data_index & PLANET_SHIELDED) + || (pSolarSysState->SysInfo.PlanetInfo.AtmoDensity == + GAS_GIANT_ATMOSPHERE)) + { // cannot dispatch to shielded planets or gas giants + PlayMenuSound (MENU_SOUND_FAILURE); + return TRUE; + } + + fuel_required = getLandingFuelNeeded (); + if (GLOBAL_SIS (FuelOnBoard) < fuel_required + || GLOBAL_SIS (NumLanders) == 0 + || GLOBAL_SIS (CrewEnlisted) == 0) + { + PlayMenuSound (MENU_SOUND_FAILURE); + return TRUE; + } + + SetFlashRect (NULL); + + if (!PickPlanetSide ()) + return FALSE; + + DrawMenuStateStrings (PM_MIN_SCAN, pMS->CurState); + SetFlashRect (SFR_MENU_3DO); + + return TRUE; + } + + // Various scans + if (pSolarSysState->pOrbitalDesc->data_index & PLANET_SHIELDED) + { // cannot scan shielded planets + PlayMenuSound (MENU_SOUND_FAILURE); + return TRUE; + } + + ScanPlanet (pMS->CurState); + if (pMS->CurState == AUTO_SCAN) + { + pMS->CurState = DISPATCH_SHUTTLE; + DrawMenuStateStrings (PM_MIN_SCAN, pMS->CurState); + } + } + else if (optWhichMenu == OPT_PC || + (!(pSolarSysState->pOrbitalDesc->data_index & PLANET_SHIELDED) + && pSolarSysState->SysInfo.PlanetInfo.AtmoDensity != + GAS_GIANT_ATMOSPHERE)) + { + DoMenuChooser (pMS, PM_MIN_SCAN); + } + + return TRUE; +} + +static CONTEXT +CreateScanContext (void) +{ + CONTEXT oldContext; + CONTEXT context; + RECT r; + + // ScanContext rect is relative to SpaceContext + oldContext = SetContext (SpaceContext); + GetContextClipRect (&r); + + context = CreateContext ("ScanContext"); + SetContext (context); + SetContextFGFrame (Screen); + r.corner.x += r.extent.width - MAP_WIDTH; + r.corner.y += r.extent.height - MAP_HEIGHT; + r.extent.width = MAP_WIDTH; + r.extent.height = MAP_HEIGHT; + SetContextClipRect (&r); + + SetContext (oldContext); + + return context; +} + +CONTEXT +GetScanContext (BOOLEAN *owner) +{ + // TODO: Make CONTEXT ref-counted + if (ScanContext) + { + if (owner) + *owner = FALSE; + } + else + { + if (owner) + *owner = TRUE; + ScanContext = CreateScanContext (); + } + return ScanContext; +} + +void +DestroyScanContext (void) +{ + if (ScanContext) + { + DestroyContext (ScanContext); + ScanContext = NULL; + } +} + +void +ScanSystem (void) +{ + MENU_STATE MenuState; + + memset (&MenuState, 0, sizeof MenuState); + + GetScanContext (NULL); + + if (optWhichMenu == OPT_3DO && + ((pSolarSysState->pOrbitalDesc->data_index & PLANET_SHIELDED) + || pSolarSysState->SysInfo.PlanetInfo.AtmoDensity == + GAS_GIANT_ATMOSPHERE)) + { + MenuState.CurState = EXIT_SCAN; + } + else + { + MenuState.CurState = AUTO_SCAN; + planetLoc.x = (MAP_WIDTH >> 1) << MAG_SHIFT; + planetLoc.y = (MAP_HEIGHT >> 1) << MAG_SHIFT; + + initPlanetLocationImage (); + SetContext (ScanContext); + DrawScannedObjects (FALSE); + } + + DrawMenuStateStrings (PM_MIN_SCAN, MenuState.CurState); + SetFlashRect (SFR_MENU_3DO); + + if (optWhichCoarseScan == OPT_PC) + PrintCoarseScanPC (); + else + PrintCoarseScan3DO (); + + SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); + + MenuState.InputFunc = DoScan; + DoInput (&MenuState, FALSE); + + SetFlashRect (NULL); + + // cleanup scan graphics + BatchGraphics (); + SetContext (ScanContext); + DrawPlanet (0, BLACK_COLOR); + EraseCoarseScan (); + UnbatchGraphics (); + + DestroyDrawable (ReleaseDrawable (eraseFrame)); + eraseFrame = NULL; +} + +static void +generateBioNode (SOLARSYS_STATE *system, ELEMENT *NodeElementPtr, + BYTE *life_init_tab, COUNT creatureType) +{ + COUNT i; + + // NOTE: TFB_Random() calls here are NOT part of the deterministic planet + // generation PRNG flow. + if (CreatureData[creatureType].Attributes & SPEED_MASK) + { + // Place moving creatures at a random location. + i = TFB_Random (); + NodeElementPtr->current.location.x = + (LOBYTE (i) % (MAP_WIDTH - (8 << 1))) + 8; + NodeElementPtr->current.location.y = + (HIBYTE (i) % (MAP_HEIGHT - (8 << 1))) + 8; + } + + if (system->PlanetSideFrame[0] == 0) + system->PlanetSideFrame[0] = + CaptureDrawable (LoadGraphic (CANNISTER_MASK_PMAP_ANIM)); + + for (i = 0; i < MAX_LIFE_VARIATION + && life_init_tab[i] != (BYTE)(creatureType + 1); + ++i) + { + if (life_init_tab[i] != 0) + continue; + + life_init_tab[i] = (BYTE)creatureType + 1; + + system->PlanetSideFrame[i + 3] = load_life_form (creatureType); + break; + } + + NodeElementPtr->mass_points = (BYTE)creatureType; + NodeElementPtr->hit_points = HINIBBLE ( + CreatureData[creatureType].ValueAndHitPoints); + DisplayArray[NodeElementPtr->PrimIndex]. + Object.Stamp.frame = SetAbsFrameIndex ( + system->PlanetSideFrame[i + 3], (COUNT)TFB_Random ()); +} + +void +GeneratePlanetSide (void) +{ + SIZE scan; + BYTE life_init_tab[MAX_LIFE_VARIATION]; + // life_init_tab is filled with the creature types of already + // selected creatures. If an entry is 0, none has been selected + // yet, otherwise, it is 1 more than the creature type. + + InitDisplayList (); + if (pSolarSysState->pOrbitalDesc->data_index & PLANET_SHIELDED) + return; + + memset (life_init_tab, 0, sizeof life_init_tab); + + for (scan = BIOLOGICAL_SCAN; scan >= MINERAL_SCAN; --scan) + { + COUNT num_nodes; + FRAME f; + + f = SetAbsFrameIndex (MiscDataFrame, + NUM_SCANDOT_TRANSITIONS * (scan - ENERGY_SCAN)); + + num_nodes = callGenerateForScanType (pSolarSysState, + pSolarSysState->pOrbitalDesc, GENERATE_ALL, scan, NULL); + + while (num_nodes--) + { + HELEMENT hNodeElement; + ELEMENT *NodeElementPtr; + NODE_INFO info; + + if (isNodeRetrieved (&pSolarSysState->SysInfo.PlanetInfo, + scan, num_nodes)) + continue; + + hNodeElement = AllocElement (); + if (!hNodeElement) + continue; + + LockElement (hNodeElement, &NodeElementPtr); + + callGenerateForScanType (pSolarSysState, + pSolarSysState->pOrbitalDesc, num_nodes, + scan, &info); + + NodeElementPtr->scan_node = MAKE_WORD (scan, num_nodes + 1); + NodeElementPtr->playerNr = PS_NON_PLAYER; + NodeElementPtr->current.location.x = info.loc_pt.x; + NodeElementPtr->current.location.y = info.loc_pt.y; + + SetPrimType (&DisplayArray[NodeElementPtr->PrimIndex], STAMP_PRIM); + if (scan == MINERAL_SCAN) + { + NodeElementPtr->turn_wait = info.type; + NodeElementPtr->mass_points = HIBYTE (info.density); + NodeElementPtr->current.image.frame = SetAbsFrameIndex ( + MiscDataFrame, (NUM_SCANDOT_TRANSITIONS * 2) + + ElementCategory (info.type) * 5); + NodeElementPtr->next.image.frame = SetRelFrameIndex ( + NodeElementPtr->current.image.frame, + LOBYTE (info.density) + 1); + DisplayArray[NodeElementPtr->PrimIndex].Object.Stamp.frame = + IncFrameIndex (NodeElementPtr->next.image.frame); + } + else /* (scan == BIOLOGICAL_SCAN || scan == ENERGY_SCAN) */ + { + NodeElementPtr->current.image.frame = f; + NodeElementPtr->next.image.frame = SetRelFrameIndex ( + f, NUM_SCANDOT_TRANSITIONS - 1); + NodeElementPtr->turn_wait = MAKE_BYTE (4, 4); + NodeElementPtr->preprocess_func = object_animation; + if (scan == ENERGY_SCAN) + { + NodeElementPtr->mass_points = MAX_SCROUNGED; + DisplayArray[NodeElementPtr->PrimIndex].Object.Stamp.frame = + pSolarSysState->PlanetSideFrame[1]; + } + else /* (scan == BIOLOGICAL_SCAN) */ + { + generateBioNode (pSolarSysState, NodeElementPtr, + life_init_tab, info.type); + } + } + + NodeElementPtr->next.location.x = + NodeElementPtr->current.location.x << MAG_SHIFT; + NodeElementPtr->next.location.y = + NodeElementPtr->current.location.y << MAG_SHIFT; + UnlockElement (hNodeElement); + + PutElement (hNodeElement); + } + } +} + +bool +isNodeRetrieved (PLANET_INFO *planetInfo, BYTE scanType, BYTE nodeNr) +{ + return (planetInfo->ScanRetrieveMask[scanType] & ((DWORD) 1 << nodeNr)) + != 0; +} + +COUNT +countNodesRetrieved (PLANET_INFO *planetInfo, BYTE scanType) +{ + COUNT count; + DWORD mask = planetInfo->ScanRetrieveMask[scanType]; + + // count the number of bits set + // Caution: 'mask' must be unsigned + for (count = 0; mask != 0; mask >>= 1) + { + if (mask & 1) + ++count; + } + return count; +} + +void +setNodeRetrieved (PLANET_INFO *planetInfo, BYTE scanType, BYTE nodeNr) +{ + planetInfo->ScanRetrieveMask[scanType] |= ((DWORD) 1 << nodeNr); +} + +void +setNodeNotRetrieved (PLANET_INFO *planetInfo, BYTE scanType, BYTE nodeNr) +{ + planetInfo->ScanRetrieveMask[scanType] &= ~((DWORD) 1 << nodeNr); +} + |