diff options
Diffstat (limited to 'src/uqm/planets')
58 files changed, 20793 insertions, 0 deletions
diff --git a/src/uqm/planets/Makeinfo b/src/uqm/planets/Makeinfo new file mode 100644 index 0000000..da4026f --- /dev/null +++ b/src/uqm/planets/Makeinfo @@ -0,0 +1,7 @@ +uqm_SUBDIRS="generate" +uqm_CFILES="calc.c cargo.c devices.c gentopo.c lander.c orbits.c + oval.c pl_stuff.c planets.c plangen.c pstarmap.c report.c + roster.c scan.c solarsys.c surface.c" +uqm_HFILES="elemdata.h generate.h lander.h lifeform.h plandata.h planets.h + scan.h solarsys.h sundata.h" + diff --git a/src/uqm/planets/calc.c b/src/uqm/planets/calc.c new file mode 100644 index 0000000..09bd461 --- /dev/null +++ b/src/uqm/planets/calc.c @@ -0,0 +1,530 @@ +//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. + */ + +/* ----------------------------- INCLUDES ---------------------------- */ +#include "planets.h" +#include "uqm/starmap.h" +#include "libs/mathlib.h" +#include "libs/log.h" +/* -------------------------------- DATA -------------------------------- */ + +/* -------------------------------- CODE -------------------------------- */ + +//#define DEBUG_PLANET_CALC + +#define LOW_TEMP 0 +#define MED_TEMP 500 +#define HIGH_TEMP 1500 +#define LOW_TEMP_BONUS 10 +#define MED_TEMP_BONUS 25 +#define HIGH_TEMP_BONUS 50 +#define MAX_TECTONICS 255 + +enum +{ + RED_SUN_INTENSITY = 0, + ORANGE_SUN_INTENSITY, + YELLOW_SUN_INTENSITY, + GREEN_SUN_INTENSITY, + BLUE_SUN_INTENSITY, + WHITE_SUN_INTENSITY +}; + +static UWORD +CalcFromBase (UWORD base, UWORD variance) +{ + return base + LOWORD (RandomContext_Random (SysGenRNG)) % variance; +} + +static inline UWORD +CalcHalfBaseVariance (UWORD base) +{ + return CalcFromBase (base, (base >> 1) + 1); +} + +static void +CalcSysInfo (SYSTEM_INFO *SysInfoPtr) +{ + SysInfoPtr->StarSize = pSolarSysState->SunDesc[0].data_index; + switch (STAR_COLOR (CurStarDescPtr->Type)) + { + case BLUE_BODY: + SysInfoPtr->StarIntensity = BLUE_SUN_INTENSITY; + break; + case GREEN_BODY: + SysInfoPtr->StarIntensity = GREEN_SUN_INTENSITY; + break; + case ORANGE_BODY: + SysInfoPtr->StarIntensity = ORANGE_SUN_INTENSITY; + break; + case RED_BODY: + SysInfoPtr->StarIntensity = RED_SUN_INTENSITY; + break; + case WHITE_BODY: + SysInfoPtr->StarIntensity = WHITE_SUN_INTENSITY; + break; + case YELLOW_BODY: + SysInfoPtr->StarIntensity = YELLOW_SUN_INTENSITY; + break; + } + + switch (STAR_TYPE (CurStarDescPtr->Type)) + { + case DWARF_STAR: + SysInfoPtr->StarEnergy = + (SysInfoPtr->StarIntensity + 1) * DWARF_ENERGY; + break; + case GIANT_STAR: + SysInfoPtr->StarEnergy = + (SysInfoPtr->StarIntensity + 1) * GIANT_ENERGY; + break; + case SUPER_GIANT_STAR: + SysInfoPtr->StarEnergy = + (SysInfoPtr->StarIntensity + 1) * SUPERGIANT_ENERGY; + break; + } +} + +static UWORD +GeneratePlanetComposition (PLANET_INFO *PlanetInfoPtr, SIZE SurfaceTemp, + SIZE radius) +{ + if (PLANSIZE (PlanetInfoPtr->PlanDataPtr->Type) == GAS_GIANT) + { + PlanetInfoPtr->Weather = 7 << 5; + return (GAS_GIANT_ATMOSPHERE); + } + else + { + BYTE range; + UWORD atmo; + + PlanetInfoPtr->Weather = 0; + atmo = 0; + if ((range = HINIBBLE (PlanetInfoPtr->PlanDataPtr->AtmoAndDensity)) <= HEAVY) + { + if (SurfaceTemp < COLD_THRESHOLD) + --range; + else if (SurfaceTemp > HOT_THRESHOLD) + ++range; + + if (range <= HEAVY + 1) + { + switch (range) + { + case LIGHT: + atmo = THIN_ATMOSPHERE; + PlanetInfoPtr->Weather = 1 << 5; + break; + case MEDIUM: + atmo = NORMAL_ATMOSPHERE; + PlanetInfoPtr->Weather = 2 << 5; + break; + case HEAVY: + atmo = THICK_ATMOSPHERE; + PlanetInfoPtr->Weather = 4 << 5; + break; + default: + atmo = SUPER_THICK_ATMOSPHERE; + PlanetInfoPtr->Weather = 6 << 5; + break; + } + + radius /= EARTH_RADIUS; + if (radius < 2) + PlanetInfoPtr->Weather += 1 << 5; + else if (radius > 10) + PlanetInfoPtr->Weather -= 1 << 5; + atmo = CalcHalfBaseVariance (atmo); + } + } + + return (atmo); + } +} + +// This function is called both when the solar system is generated, +// and when planetary orbit is entered. +// In the former case, the if() block will not be executed, +// which means that the temperature calculated in that case will be +// slightly lower. The result is that the orbits may not always +// have the colour you'd expect based on the true temperature. +// (eg. Beta Corvi I). I don't know what the idea behind this is, +// but the if statement must be there for a reason. -- SvdB +// Update 2013-03-28: The contents of the if() block is probably there to +// model a greenhouse effect. It seems that it is taken into account when +// calculating the actual temperature (when landing or scanning), but not +// when determining the colors of the drawn orbits. (Thanks to James Scott +// for this insight.) +static SIZE +CalcTemp (SYSTEM_INFO *SysInfoPtr, SIZE radius) +{ +#define GENERIC_ALBEDO 33 /* In %, 0=black, 100 is reflective */ +#define ADJUST_FOR_KELVIN 273 +#define PLANET_TEMP_CONSTANT 277L + DWORD alb; + SIZE centigrade, bonus; + + alb = 100 - GENERIC_ALBEDO; + alb = square_root (square_root (alb * 100 * 10000)) + * PLANET_TEMP_CONSTANT * SysInfoPtr->StarEnergy + / ((YELLOW_SUN_INTENSITY + 1) * DWARF_ENERGY); + + centigrade = (SIZE)(alb / square_root (radius * 10000L / EARTH_RADIUS)) + - ADJUST_FOR_KELVIN; + + bonus = 0; + if (SysInfoPtr == &pSolarSysState->SysInfo + && HINIBBLE (SysInfoPtr->PlanetInfo.PlanDataPtr->AtmoAndDensity) <= HEAVY) + { +#define COLD_BONUS 20 +#define HOT_BONUS 200 + if (centigrade >= HOT_THRESHOLD) + bonus = HOT_BONUS; + else if (centigrade >= COLD_THRESHOLD) + bonus = COLD_BONUS; + + bonus <<= HINIBBLE (SysInfoPtr->PlanetInfo.PlanDataPtr->AtmoAndDensity); + bonus = CalcHalfBaseVariance (bonus); + } + + return (centigrade + bonus); +} + +static COUNT +CalcRotation (PLANET_INFO *PlanetInfoPtr) +{ + if (PLANSIZE (PlanetInfoPtr->PlanDataPtr->Type) == GAS_GIANT) + return CalcFromBase (80, 80); + else if (LOBYTE (RandomContext_Random (SysGenRNG)) % 10 == 0) + return CalcFromBase (50 * 240, 200 * 240); + else + return CalcFromBase (150, 150); +} + +static SIZE +CalcTilt (void) +{ /* Calculate Axial Tilt */ + SIZE tilt; + BYTE i; + +#define NUM_TOSSES 10 +#define TILT_RANGE 180 + tilt = -(TILT_RANGE / 2); + i = NUM_TOSSES; + do /* Using added Randomom values to give bell curve */ + { + tilt += LOWORD (RandomContext_Random (SysGenRNG)) + % ((TILT_RANGE / NUM_TOSSES) + 1); + } while (--i); + + return (tilt); +} + +UWORD +CalcGravity (const PLANET_INFO *PlanetInfoPtr) +{ + return (DWORD)PlanetInfoPtr->PlanetDensity * PlanetInfoPtr->PlanetRadius + / 100; +} + +static UWORD +CalcTectonics (UWORD base, UWORD temp) +{ + UWORD tect = CalcFromBase (base, 3 << 5); +#ifdef OLD + if (temp >= HIGH_TEMP) + tect += HIGH_TEMP_BONUS; + else if (temp >= MED_TEMP) + tect += MED_TEMP_BONUS; + else if (temp >= LOW_TEMP) + tect += LOW_TEMP_BONUS; +#else /* !OLD */ + (void) temp; /* silence compiler whining */ +#endif /* OLD */ + return tect; +} + +// This code moved from planets/surface.c:CalcLifeForms() +static int +CalcLifeChance (const PLANET_INFO *PlanetInfoPtr) +{ + SIZE life_var = 0; + + if (PLANSIZE (PlanetInfoPtr->PlanDataPtr->Type) == GAS_GIANT) + return -1; + + if (PlanetInfoPtr->SurfaceTemperature < -151) + life_var -= 300; + else if (PlanetInfoPtr->SurfaceTemperature < -51) + life_var -= 100; + else if (PlanetInfoPtr->SurfaceTemperature < 0) + life_var += 100; + else if (PlanetInfoPtr->SurfaceTemperature < 50) + life_var += 300; + else if (PlanetInfoPtr->SurfaceTemperature < 150) + life_var += 50; + else if (PlanetInfoPtr->SurfaceTemperature < 250) + life_var -= 100; + else if (PlanetInfoPtr->SurfaceTemperature < 500) + life_var -= 400; + else + life_var -= 800; + + if (PlanetInfoPtr->AtmoDensity == 0) + life_var -= 1000; + else if (PlanetInfoPtr->AtmoDensity < 15) + life_var += 100; + else if (PlanetInfoPtr->AtmoDensity < 30) + life_var += 200; + else if (PlanetInfoPtr->AtmoDensity < 100) + life_var += 300; + else if (PlanetInfoPtr->AtmoDensity < 1000) + life_var += 150; + else if (PlanetInfoPtr->AtmoDensity < 2500) + ; + else + life_var -= 100; + +#ifndef NOTYET + life_var += 200 + 80 + 80; +#else /* NOTYET */ + if (PlanetInfoPtr->SurfaceGravity < 10) + ; + else if (PlanetInfoPtr->SurfaceGravity < 35) + life_var += 50; + else if (PlanetInfoPtr->SurfaceGravity < 75) + life_var += 100; + else if (PlanetInfoPtr->SurfaceGravity < 150) + life_var += 200; + else if (PlanetInfoPtr->SurfaceGravity < 400) + life_var += 50; + else if (PlanetInfoPtr->SurfaceGravity < 800) + ; + else + life_var -= 100; + + if (PlanetInfoPtr->Tectonics < 1) + life_var += 80; + else if (PlanetInfoPtr->Tectonics < 2) + life_var += 70; + else if (PlanetInfoPtr->Tectonics < 3) + life_var += 60; + else if (PlanetInfoPtr->Tectonics < 4) + life_var += 50; + else if (PlanetInfoPtr->Tectonics < 5) + life_var += 25; + else if (PlanetInfoPtr->Tectonics < 6) + ; + else + life_var -= 100; + + if (PlanetInfoPtr->Weather < 1) + life_var += 80; + else if (PlanetInfoPtr->Weather < 2) + life_var += 70; + else if (PlanetInfoPtr->Weather < 3) + life_var += 60; + else if (PlanetInfoPtr->Weather < 4) + life_var += 50; + else if (PlanetInfoPtr->Weather < 5) + life_var += 25; + else if (PlanetInfoPtr->Weather < 6) + ; + else + life_var -= 100; +#endif /* NOTYET */ + + return life_var; +} + +// Sets the SysGenRNG to the required state first. +void +DoPlanetaryAnalysis (SYSTEM_INFO *SysInfoPtr, PLANET_DESC *pPlanetDesc) +{ + assert ((pPlanetDesc->data_index & ~WORLD_TYPE_SPECIAL) + < NUMBER_OF_PLANET_TYPES); + + RandomContext_SeedRandom (SysGenRNG, pPlanetDesc->rand_seed); + + CalcSysInfo (SysInfoPtr); + +#ifdef DEBUG_PLANET_CALC + { + BYTE ColorClass[6][8] = { + "Red", + "Orange", + "Yellow", + "Green", + "Blue", + "White", + }; + BYTE SizeName[3][12] = { + "Dwarf", + "Giant", + "Supergiant", + }; + + log_add (log_Debug, "%s %s", + ColorClass[SysInfoPtr->StarIntensity], + SizeName[SysInfoPtr->StarSize]); + log_add (log_Debug, "Stellar Energy: %d (sol = 3)", + SysInfoPtr->StarEnergy); + } +#endif /* DEBUG_PLANET_CALC */ + + { + SIZE radius; + + SysInfoPtr->PlanetInfo.PlanDataPtr = + &PlanData[pPlanetDesc->data_index & ~PLANET_SHIELDED]; + + if (pPlanetDesc->pPrevDesc == pSolarSysState->SunDesc) + radius = pPlanetDesc->radius; + else + radius = pPlanetDesc->pPrevDesc->radius; + SysInfoPtr->PlanetInfo.PlanetToSunDist = radius; + + SysInfoPtr->PlanetInfo.SurfaceTemperature = + CalcTemp (SysInfoPtr, radius); + switch (LONIBBLE (SysInfoPtr->PlanetInfo.PlanDataPtr->AtmoAndDensity)) + { + case GAS_DENSITY: + SysInfoPtr->PlanetInfo.PlanetDensity = 20; + break; + case LIGHT_DENSITY: + SysInfoPtr->PlanetInfo.PlanetDensity = 33; + break; + case LOW_DENSITY: + SysInfoPtr->PlanetInfo.PlanetDensity = 60; + break; + case NORMAL_DENSITY: + SysInfoPtr->PlanetInfo.PlanetDensity = 100; + break; + case HIGH_DENSITY: + SysInfoPtr->PlanetInfo.PlanetDensity = 150; + break; + case SUPER_DENSITY: + SysInfoPtr->PlanetInfo.PlanetDensity = 200; + break; + } + SysInfoPtr->PlanetInfo.PlanetDensity += + (SysInfoPtr->PlanetInfo.PlanetDensity / 20) + - (LOWORD (RandomContext_Random (SysGenRNG)) + % (SysInfoPtr->PlanetInfo.PlanetDensity / 10)); + + switch (PLANSIZE (SysInfoPtr->PlanetInfo.PlanDataPtr->Type)) + { + case SMALL_ROCKY_WORLD: +#define SMALL_RADIUS 25 + SysInfoPtr->PlanetInfo.PlanetRadius = CalcHalfBaseVariance (SMALL_RADIUS); + break; + case LARGE_ROCKY_WORLD: +#define LARGE_RADIUS 75 + SysInfoPtr->PlanetInfo.PlanetRadius = CalcHalfBaseVariance (LARGE_RADIUS); + break; + case GAS_GIANT: +#define MIN_GAS_RADIUS 300 +#define MAX_GAS_RADIUS 1500 + SysInfoPtr->PlanetInfo.PlanetRadius = + CalcFromBase (MIN_GAS_RADIUS, MAX_GAS_RADIUS - MIN_GAS_RADIUS); + break; + } + + SysInfoPtr->PlanetInfo.RotationPeriod = CalcRotation (&SysInfoPtr->PlanetInfo); + SysInfoPtr->PlanetInfo.SurfaceGravity = CalcGravity (&SysInfoPtr->PlanetInfo); + SysInfoPtr->PlanetInfo.AxialTilt = CalcTilt (); + if ((SysInfoPtr->PlanetInfo.Tectonics = + CalcTectonics (SysInfoPtr->PlanetInfo.PlanDataPtr->BaseTectonics, + SysInfoPtr->PlanetInfo.SurfaceTemperature)) > MAX_TECTONICS) + SysInfoPtr->PlanetInfo.Tectonics = MAX_TECTONICS; + + SysInfoPtr->PlanetInfo.AtmoDensity = + GeneratePlanetComposition (&SysInfoPtr->PlanetInfo, + SysInfoPtr->PlanetInfo.SurfaceTemperature, radius); + + SysInfoPtr->PlanetInfo.Tectonics >>= 5; + SysInfoPtr->PlanetInfo.Weather >>= 5; + + SysInfoPtr->PlanetInfo.LifeChance = CalcLifeChance (&SysInfoPtr->PlanetInfo); + +#ifdef DEBUG_PLANET_CALC + radius = (SIZE)((DWORD)UNSCALE_RADIUS (radius) * 100 / UNSCALE_RADIUS (EARTH_RADIUS)); + log_add (log_Debug, "\tOrbital Distance : %d.%02d AU", radius / 100, radius % 100); + //log_add (log_Debug, "\tPlanetary Mass : %d.%02d Earth masses", + // SysInfoPtr->PlanetInfo.PlanetMass / 100, + // SysInfoPtr->PlanetInfo.PlanetMass % 100); + log_add (log_Debug, "\tPlanetary Radius : %d.%02d Earth radii", + SysInfoPtr->PlanetInfo.PlanetRadius / 100, + SysInfoPtr->PlanetInfo.PlanetRadius % 100); + log_add (log_Debug, "\tSurface Gravity: %d.%02d gravities", + SysInfoPtr->PlanetInfo.SurfaceGravity / 100, + SysInfoPtr->PlanetInfo.SurfaceGravity % 100); + log_add (log_Debug, "\tSurface Temperature: %d degrees C", + SysInfoPtr->PlanetInfo.SurfaceTemperature ); + log_add (log_Debug, "\tAxial Tilt : %d degrees", + abs (SysInfoPtr->PlanetInfo.AxialTilt)); + log_add (log_Debug, "\tTectonics : Class %u", + SysInfoPtr->PlanetInfo.Tectonics + 1); + log_add (log_Debug, "\tAtmospheric Density: %u.%02u", + SysInfoPtr->PlanetInfo.AtmoDensity / EARTH_ATMOSPHERE, + (SysInfoPtr->PlanetInfo.AtmoDensity * 100 / EARTH_ATMOSPHERE) % 100); + if (SysInfoPtr->PlanetInfo.AtmoDensity == 0) + { + log_add (log_Debug, "\tAtmosphere: (Vacuum)"); + } + else if (SysInfoPtr->PlanetInfo.AtmoDensity < THIN_ATMOSPHERE) + { + log_add (log_Debug, "\tAtmosphere: (Thin)"); + } + else if (SysInfoPtr->PlanetInfo.AtmoDensity < NORMAL_ATMOSPHERE) + { + log_add (log_Debug, "\tAtmosphere: (Normal)"); + } + else if (SysInfoPtr->PlanetInfo.AtmoDensity < THICK_ATMOSPHERE) + { + log_add (log_Debug, "\tAtmosphere: (Thick)"); + } + else if (SysInfoPtr->PlanetInfo.AtmoDensity < SUPER_THICK_ATMOSPHERE) + { + log_add (log_Debug, "\tAtmosphere: (Super thick)"); + } + else + { + log_add (log_Debug, "\tAtmosphere: (Gas Giant)"); + } + + log_add (log_Debug, "\tWeather : Class %u", + SysInfoPtr->PlanetInfo.Weather + 1); + + if (SysInfoPtr->PlanetInfo.RotationPeriod >= 480) + { + log_add (log_Debug, "\tLength of day : %d.%d Earth days", + SysInfoPtr->PlanetInfo.RotationPeriod / 240, + SysInfoPtr->PlanetInfo.RotationPeriod % 240); + } + else + { + log_add (log_Debug, "\tLength of day : %d.%d Earth hours", + SysInfoPtr->PlanetInfo.RotationPeriod / 10, + SysInfoPtr->PlanetInfo.RotationPeriod % 10); + } +#endif /* DEBUG_PLANET_CALC */ + } +} + diff --git a/src/uqm/planets/cargo.c b/src/uqm/planets/cargo.c new file mode 100644 index 0000000..147d95a --- /dev/null +++ b/src/uqm/planets/cargo.c @@ -0,0 +1,356 @@ +//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 "../colors.h" +#include "../controls.h" +#include "../gamestr.h" +#include "../shipcont.h" +#include "../setup.h" +#include "../sounds.h" +#include "../util.h" +#include "../sis.h" + // for ClearSISRect(), DrawStatusMessage() +#include "planets.h" +#include "libs/graphics/drawable.h" + // for GetFrameBounds() + + +#define ELEMENT_ORG_Y 35 +#define FREE_ORG_Y (ELEMENT_ORG_Y + (NUM_ELEMENT_CATEGORIES \ + * ELEMENT_SPACING_Y)) +#define BIO_ORG_Y 119 +#define ELEMENT_SPACING_Y 9 + +#define ELEMENT_COL_0 7 +#define ELEMENT_COL_1 32 +#define ELEMENT_COL_2 58 + +#define ELEMENT_SEL_ORG_X (ELEMENT_COL_0 + 7 + 5) +#define ELEMENT_SEL_WIDTH (ELEMENT_COL_2 - ELEMENT_SEL_ORG_X + 1) + +#define TEXT_BASELINE 6 + + +void +ShowRemainingCapacity (void) +{ + RECT r; + TEXT t; + CONTEXT OldContext; + UNICODE buf[40]; + + OldContext = SetContext (StatusContext); + SetContextFont (TinyFont); + + r.corner.x = 40; + r.corner.y = FREE_ORG_Y; + + snprintf (buf, sizeof buf, "%u", + GetStorageBayCapacity () - GLOBAL_SIS (TotalElementMass)); + t.baseline.x = ELEMENT_COL_2 + 1; + t.baseline.y = r.corner.y + TEXT_BASELINE; + t.align = ALIGN_RIGHT; + t.pStr = buf; + t.CharCount = (COUNT)~0; + + r.extent.width = t.baseline.x - r.corner.x + 1; + r.extent.height = ELEMENT_SPACING_Y - 2; + + BatchGraphics (); + // erase previous free amount + SetContextForeGroundColor (CARGO_BACK_COLOR); + DrawFilledRectangle (&r); + // print the new free amount + SetContextForeGroundColor (CARGO_WORTH_COLOR); + font_DrawText (&t); + UnbatchGraphics (); + + SetContext (OldContext); +} + +static void +DrawElementAmount (COUNT element, bool selected) +{ + RECT r; + TEXT t; + UNICODE buf[40]; + + r.corner.x = ELEMENT_SEL_ORG_X; + r.extent.width = ELEMENT_SEL_WIDTH; + r.extent.height = ELEMENT_SPACING_Y - 2; + + if (element == NUM_ELEMENT_CATEGORIES) + r.corner.y = BIO_ORG_Y; + else + r.corner.y = ELEMENT_ORG_Y + (element * ELEMENT_SPACING_Y); + + // draw line background + SetContextForeGroundColor (selected ? + CARGO_SELECTED_BACK_COLOR : CARGO_BACK_COLOR); + DrawFilledRectangle (&r); + + t.align = ALIGN_RIGHT; + t.pStr = buf; + t.baseline.y = r.corner.y + TEXT_BASELINE; + + if (element == NUM_ELEMENT_CATEGORIES) + { // Bio + snprintf (buf, sizeof buf, "%u", GLOBAL_SIS (TotalBioMass)); + } + else + { // Element + // print element's worth + SetContextForeGroundColor (selected ? + CARGO_SELECTED_WORTH_COLOR : CARGO_WORTH_COLOR); + t.baseline.x = ELEMENT_COL_1; + snprintf (buf, sizeof buf, "%u", GLOBAL (ElementWorth[element])); + t.CharCount = (COUNT)~0; + font_DrawText (&t); + + snprintf (buf, sizeof buf, "%u", GLOBAL_SIS (ElementAmounts[element])); + } + + // print the element/bio amount + SetContextForeGroundColor (selected ? + CARGO_SELECTED_AMOUNT_COLOR : CARGO_AMOUNT_COLOR); + t.baseline.x = ELEMENT_COL_2; + t.CharCount = (COUNT)~0; + font_DrawText (&t); +} + +static void +DrawCargoDisplay (void) +{ + STAMP s; + TEXT t; + RECT r; + COORD cy; + COUNT i; + + r.corner.x = 2; + r.extent.width = FIELD_WIDTH + 1; + r.corner.y = 20; + // XXX: Shouldn't the height be 1 less? This draws the bottom border + // 1 pixel too low. Or if not, why do we need another box anyway? + r.extent.height = 129 - r.corner.y; + DrawStarConBox (&r, 1, + SHADOWBOX_MEDIUM_COLOR, SHADOWBOX_DARK_COLOR, + TRUE, CARGO_BACK_COLOR); + + // draw the "CARGO" title + SetContextFont (StarConFont); + t.baseline.x = (STATUS_WIDTH >> 1) - 1; + t.baseline.y = 27; + t.align = ALIGN_CENTER; + t.pStr = GAME_STRING (CARGO_STRING_BASE); + t.CharCount = (COUNT)~0; + SetContextForeGroundColor (CARGO_SELECTED_AMOUNT_COLOR); + font_DrawText (&t); + + SetContextFont (TinyFont); + + s.frame = SetAbsFrameIndex (MiscDataFrame, + (NUM_SCANDOT_TRANSITIONS * 2) + 3); + r.corner.x = ELEMENT_COL_0; + r.extent = GetFrameBounds (s.frame); + s.origin.x = r.corner.x + (r.extent.width >> 1); + + cy = ELEMENT_ORG_Y; + + // print element column headings + t.align = ALIGN_RIGHT; + t.baseline.y = cy - 1; + t.CharCount = (COUNT)~0; + + SetContextForeGroundColor (CARGO_WORTH_COLOR); + t.baseline.x = ELEMENT_COL_1; + t.pStr = "$"; + font_DrawText (&t); + + t.baseline.x = ELEMENT_COL_2; + t.pStr = "#"; + font_DrawText (&t); + + // draw element icons and print amounts + for (i = 0; i < NUM_ELEMENT_CATEGORIES; ++i, cy += ELEMENT_SPACING_Y) + { + // erase background under an element icon + SetContextForeGroundColor (BLACK_COLOR); + r.corner.y = cy; + DrawFilledRectangle (&r); + + // draw an element icon + s.origin.y = r.corner.y + (r.extent.height >> 1); + DrawStamp (&s); + s.frame = SetRelFrameIndex (s.frame, 5); + + DrawElementAmount (i, false); + } + + // erase background under the Bio icon + SetContextForeGroundColor (BLACK_COLOR); + r.corner.y = BIO_ORG_Y; + DrawFilledRectangle (&r); + + // draw the Bio icon + s.origin.y = r.corner.y + (r.extent.height >> 1); + s.frame = SetAbsFrameIndex (s.frame, 68); + DrawStamp (&s); + + // print the Bio amount + DrawElementAmount (NUM_ELEMENT_CATEGORIES, false); + + // draw the line over the Bio amount + r.corner.x = 4; + r.corner.y = BIO_ORG_Y - 2; + r.extent.width = FIELD_WIDTH - 3; + r.extent.height = 1; + SetContextForeGroundColor (CARGO_SELECTED_BACK_COLOR); + DrawFilledRectangle (&r); + + // print "Free" + t.baseline.x = 5; + t.baseline.y = FREE_ORG_Y + TEXT_BASELINE; + t.align = ALIGN_LEFT; + t.pStr = GAME_STRING (CARGO_STRING_BASE + 1); + t.CharCount = (COUNT)~0; + font_DrawText (&t); + + ShowRemainingCapacity (); +} + +void +DrawCargoStrings (BYTE OldElement, BYTE NewElement) +{ + CONTEXT OldContext; + + OldContext = SetContext (StatusContext); + SetContextFont (TinyFont); + + BatchGraphics (); + + if (OldElement > NUM_ELEMENT_CATEGORIES) + { // Asked for the initial display + DrawCargoDisplay (); + + // do not draw unselected again this time + OldElement = NewElement; + } + + if (OldElement != NewElement) + { // unselect the previous element + DrawElementAmount (OldElement, false); + } + + if (NewElement != (BYTE)~0) + { // select the new element + DrawElementAmount (NewElement, true); + } + + UnbatchGraphics (); + SetContext (OldContext); +} + +static void +DrawElementDescription (COUNT element) +{ + DrawStatusMessage (GAME_STRING (element + (CARGO_STRING_BASE + 2))); +} + +static BOOLEAN +DoDiscardCargo (MENU_STATE *pMS) +{ + BYTE NewState; + BOOLEAN select, cancel, back, forward; + + select = PulsedInputState.menu[KEY_MENU_SELECT]; + cancel = PulsedInputState.menu[KEY_MENU_CANCEL]; + back = PulsedInputState.menu[KEY_MENU_UP] || PulsedInputState.menu[KEY_MENU_LEFT]; + forward = PulsedInputState.menu[KEY_MENU_DOWN] || PulsedInputState.menu[KEY_MENU_RIGHT]; + + if (GLOBAL (CurrentActivity) & CHECK_ABORT) + return FALSE; + + if (cancel) + { + return FALSE; + } + else if (select) + { + if (GLOBAL_SIS (ElementAmounts[pMS->CurState])) + { + --GLOBAL_SIS (ElementAmounts[pMS->CurState]); + DrawCargoStrings (pMS->CurState, pMS->CurState); + + --GLOBAL_SIS (TotalElementMass); + ShowRemainingCapacity (); + } + else + { // no element left in cargo hold + PlayMenuSound (MENU_SOUND_FAILURE); + } + } + else + { + NewState = pMS->CurState; + if (back) + { + if (NewState == 0) + NewState += NUM_ELEMENT_CATEGORIES; + --NewState; + } + else if (forward) + { + ++NewState; + if (NewState == NUM_ELEMENT_CATEGORIES) + NewState = 0; + } + + if (NewState != pMS->CurState) + { + DrawCargoStrings (pMS->CurState, NewState); + DrawElementDescription (NewState); + pMS->CurState = NewState; + } + } + + SleepThread (ONE_SECOND / 30); + + return (TRUE); +} + +void +CargoMenu (void) +{ + MENU_STATE MenuState; + + memset (&MenuState, 0, sizeof MenuState); + + // draw the initial cargo display + DrawCargoStrings ((BYTE)~0, MenuState.CurState); + DrawElementDescription (MenuState.CurState); + + SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); + + MenuState.InputFunc = DoDiscardCargo; + DoInput (&MenuState, TRUE); + + // erase the cargo display + ClearSISRect (DRAW_SIS_DISPLAY); +} + diff --git a/src/uqm/planets/devices.c b/src/uqm/planets/devices.c new file mode 100644 index 0000000..e781d5b --- /dev/null +++ b/src/uqm/planets/devices.c @@ -0,0 +1,690 @@ +//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 "../build.h" +#include "../colors.h" +#include "../gendef.h" +#include "../starmap.h" +#include "../encount.h" +#include "../gamestr.h" +#include "../controls.h" +#include "../save.h" +#include "../settings.h" +#include "../shipcont.h" +#include "../setup.h" +#include "../state.h" +#include "../sis.h" + // for ClearSISRect() +#include "../grpinfo.h" +#include "../sounds.h" +#include "../util.h" +#include "../hyper.h" + // for SaveSisHyperState() +#include "planets.h" + // for SaveSolarSysLocation() and tests +#include "libs/strlib.h" + + +// If DEBUG_DEVICES is defined, the device list shown in the game will +// include the pictures of all devices defined, regardless of which +// devices the player actually possesses. +//#define DEBUG_DEVICES + +#define DEVICE_ICON_WIDTH 16 +#define DEVICE_ICON_HEIGHT 16 + +#define DEVICE_ORG_Y 33 +#define DEVICE_SPACING_Y (DEVICE_ICON_HEIGHT + 2) + +#define DEVICE_COL_0 4 +#define DEVICE_COL_1 40 + +#define DEVICE_SEL_ORG_X (DEVICE_COL_0 + DEVICE_ICON_WIDTH) +#define DEVICE_SEL_WIDTH (FIELD_WIDTH + 1 - DEVICE_SEL_ORG_X + 1) + +#define ICON_OFS_Y 1 +#define NAME_OFS_Y 2 +#define TEXT_BASELINE 6 +#define TEXT_SPACING_Y 7 + +#define MAX_VIS_DEVICES ((129 - DEVICE_ORG_Y) / DEVICE_SPACING_Y) + + +typedef enum +{ + DEVICE_FAILURE = 0, + DEVICE_SUCCESS, + DEVICE_SUCCESS_NO_SOUND, +} DeviceStatus; + +typedef struct +{ + BYTE list[NUM_DEVICES]; + // List of all devices player has + COUNT count; + // Number of devices in the list + COUNT topIndex; + // Index of the top device displayed +} DEVICES_STATE; + + +#if 0 +static void +EraseDevicesBackground (void) +{ + RECT r; + + r.corner.x = 2 + 1; + r.extent.width = FIELD_WIDTH + 1 - 2; + r.corner.y = DEVICE_ORG_Y; + r.extent.height = MAX_VIS_DEVICES * DEVICE_SPACING_Y; + SetContextForeGroundColor (DEVICES_BACK_COLOR); + DrawFilledRectangle (&r); +} +#endif + +static void +DrawDevice (COUNT device, COUNT pos, bool selected) +{ + RECT r; + TEXT t; + + t.align = ALIGN_CENTER; + t.baseline.x = DEVICE_COL_1; + + r.extent.width = DEVICE_SEL_WIDTH; + r.extent.height = TEXT_SPACING_Y * 2; + r.corner.x = DEVICE_SEL_ORG_X; + + // draw line background + r.corner.y = DEVICE_ORG_Y + pos * DEVICE_SPACING_Y + NAME_OFS_Y; + SetContextForeGroundColor (selected ? + DEVICES_SELECTED_BACK_COLOR : DEVICES_BACK_COLOR); + DrawFilledRectangle (&r); + + SetContextFont (TinyFont); + + // print device name + SetContextForeGroundColor (selected ? + DEVICES_SELECTED_NAME_COLOR : DEVICES_NAME_COLOR); + t.baseline.y = r.corner.y + TEXT_BASELINE; + t.pStr = GAME_STRING (device + DEVICE_STRING_BASE + 1); + t.CharCount = utf8StringPos (t.pStr, ' '); + font_DrawText (&t); + t.baseline.y += TEXT_SPACING_Y; + t.pStr = skipUTF8Chars (t.pStr, t.CharCount + 1); + t.CharCount = (COUNT)~0; + font_DrawText (&t); +} + +static void +DrawDevicesDisplay (DEVICES_STATE *devState) +{ + TEXT t; + RECT r; + STAMP s; + COORD cy; + COUNT i; + + r.corner.x = 2; + r.corner.y = 20; + r.extent.width = FIELD_WIDTH + 1; + // XXX: Shouldn't the height be 1 less? This draws the bottom border + // 1 pixel too low. Or if not, why do we need another box anyway? + r.extent.height = 129 - r.corner.y; + DrawStarConBox (&r, 1, + SHADOWBOX_MEDIUM_COLOR, SHADOWBOX_DARK_COLOR, + TRUE, DEVICES_BACK_COLOR); + + // print the "DEVICES" title + SetContextFont (StarConFont); + t.baseline.x = (STATUS_WIDTH >> 1) - 1; + t.baseline.y = r.corner.y + 7; + t.align = ALIGN_CENTER; + t.pStr = GAME_STRING (DEVICE_STRING_BASE); + t.CharCount = (COUNT)~0; + SetContextForeGroundColor (DEVICES_SELECTED_NAME_COLOR); + font_DrawText (&t); + + s.origin.x = DEVICE_COL_0; + cy = DEVICE_ORG_Y; + + // draw device icons and print names + for (i = 0; i < MAX_VIS_DEVICES; ++i, cy += DEVICE_SPACING_Y) + { + COUNT devIndex = devState->topIndex + i; + + if (devIndex >= devState->count) + break; + + // draw device icon + s.origin.y = cy + ICON_OFS_Y; + s.frame = SetAbsFrameIndex (MiscDataFrame, + 77 + devState->list[devIndex]); + DrawStamp (&s); + + DrawDevice (devState->list[devIndex], i, false); + } +} + +static void +DrawDevices (DEVICES_STATE *devState, COUNT OldDevice, COUNT NewDevice) +{ + BatchGraphics (); + + SetContext (StatusContext); + + if (OldDevice > NUM_DEVICES) + { // Asked for the initial display or refresh + DrawDevicesDisplay (devState); + + // do not draw unselected again this time + OldDevice = NewDevice; + } + + if (OldDevice != NewDevice) + { // unselect the previous element + DrawDevice (devState->list[OldDevice], OldDevice - devState->topIndex, + false); + } + + if (NewDevice < NUM_DEVICES) + { // select the new element + DrawDevice (devState->list[NewDevice], NewDevice - devState->topIndex, + true); + } + + UnbatchGraphics (); +} + +// Returns TRUE if the broadcaster has been successfully activated, +// and FALSE otherwise. +static BOOLEAN +UseCaster (void) +{ + if (inHQSpace ()) + { + if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1) + { + SET_GAME_STATE (USED_BROADCASTER, 1); + return TRUE; + } + return FALSE; + } + + if (LOBYTE (GLOBAL (CurrentActivity)) != IN_INTERPLANETARY + || !playerInSolarSystem ()) + return FALSE; + + if (playerInPlanetOrbit () + && matchWorld (pSolarSysState, pSolarSysState->pOrbitalDesc, + 1, MATCH_PLANET) + && CurStarDescPtr->Index == CHMMR_DEFINED + && !GET_GAME_STATE (CHMMR_UNLEASHED)) + { + // In orbit around the Chenjesu/Mmrnmhrm home planet. + NextActivity |= CHECK_LOAD; /* fake a load game */ + GLOBAL (CurrentActivity) |= START_ENCOUNTER; + + EncounterGroup = 0; + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); + SaveSolarSysLocation (); + return TRUE; + } + + { + BOOLEAN FoundIlwrath; + HIPGROUP hGroup; + + FoundIlwrath = (CurStarDescPtr->Index == ILWRATH_DEFINED) + && StartSphereTracking (ILWRATH_SHIP); + // In the Ilwrath home system and they are alive? + + if (!FoundIlwrath && + (hGroup = GetHeadLink (&GLOBAL (ip_group_q)))) + { + // Is an Ilwrath ship in the system? + IP_GROUP *GroupPtr; + + GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup); + FoundIlwrath = (GroupPtr->race_id == ILWRATH_SHIP); + UnlockIpGroup (&GLOBAL (ip_group_q), hGroup); + } + + if (FoundIlwrath) + { + NextActivity |= CHECK_LOAD; /* fake a load game */ + GLOBAL (CurrentActivity) |= START_ENCOUNTER; + + EncounterGroup = 0; + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + if (CurStarDescPtr->Index == ILWRATH_DEFINED) + { + // Ilwrath home system. + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 4); + } + else + { + // Ilwrath ship. + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 5); + } + + if (playerInPlanetOrbit ()) + SaveSolarSysLocation (); + return TRUE; + } + } + + return FALSE; +} + +static DeviceStatus +InvokeDevice (BYTE which_device) +{ + BYTE val; + + switch (which_device) + { + case ROSY_SPHERE_DEVICE: + val = GET_GAME_STATE (ULTRON_CONDITION); + if (val) + { + SET_GAME_STATE (ULTRON_CONDITION, val + 1); + SET_GAME_STATE (ROSY_SPHERE_ON_SHIP, 0); + SET_GAME_STATE (DISCUSSED_ULTRON, 0); + SET_GAME_STATE (SUPOX_ULTRON_HELP, 0); + return DEVICE_SUCCESS; + } + break; + case ARTIFACT_2_DEVICE: + break; + case ARTIFACT_3_DEVICE: + break; + case SUN_EFFICIENCY_DEVICE: + if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY + && playerInPlanetOrbit ()) + { + PlayMenuSound (MENU_SOUND_INVOKED); + SleepThreadUntil (FadeScreen (FadeAllToWhite, ONE_SECOND * 1) + + (ONE_SECOND * 2)); + if (CurStarDescPtr->Index != CHMMR_DEFINED + || !matchWorld (pSolarSysState, + pSolarSysState->pOrbitalDesc, + 1, MATCH_PLANET)) + { + FadeScreen (FadeAllToColor, ONE_SECOND * 2); + } + else + { + SET_GAME_STATE (CHMMR_EMERGING, 1); + + EncounterGroup = 0; + GLOBAL (CurrentActivity) |= START_ENCOUNTER; + + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + CloneShipFragment (CHMMR_SHIP, + &GLOBAL (npc_built_ship_q), 0); + } + return DEVICE_SUCCESS_NO_SOUND; + } + break; + case UTWIG_BOMB_DEVICE: + SET_GAME_STATE (UTWIG_BOMB, 0); + GLOBAL (CurrentActivity) &= ~IN_BATTLE; + GLOBAL_SIS (CrewEnlisted) = (COUNT)~0; + return DEVICE_SUCCESS; + case ULTRON_0_DEVICE: + break; + case ULTRON_1_DEVICE: + break; + case ULTRON_2_DEVICE: + break; + case ULTRON_3_DEVICE: + break; + case MAIDENS_DEVICE: + break; + case TALKING_PET_DEVICE: + NextActivity |= CHECK_LOAD; /* fake a load game */ + GLOBAL (CurrentActivity) |= START_ENCOUNTER; + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 0); + if (inHQSpace ()) + { + if (GetHeadEncounter ()) + { + SET_GAME_STATE (SHIP_TO_COMPEL, 1); + } + GLOBAL (CurrentActivity) &= ~IN_BATTLE; + + SaveSisHyperState (); + } + else + { + EncounterGroup = 0; + if (GetHeadLink (&GLOBAL (ip_group_q))) + { + SET_GAME_STATE (SHIP_TO_COMPEL, 1); + + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + } + + if (CurStarDescPtr->Index == SAMATRA_DEFINED) + { + SET_GAME_STATE (READY_TO_CONFUSE_URQUAN, 1); + } + if (playerInPlanetOrbit ()) + SaveSolarSysLocation (); + } + return DEVICE_SUCCESS; + case AQUA_HELIX_DEVICE: + val = GET_GAME_STATE (ULTRON_CONDITION); + if (val) + { + SET_GAME_STATE (ULTRON_CONDITION, val + 1); + SET_GAME_STATE (AQUA_HELIX_ON_SHIP, 0); + SET_GAME_STATE (DISCUSSED_ULTRON, 0); + SET_GAME_STATE (SUPOX_ULTRON_HELP, 0); + return DEVICE_SUCCESS; + } + break; + case CLEAR_SPINDLE_DEVICE: + val = GET_GAME_STATE (ULTRON_CONDITION); + if (val) + { + SET_GAME_STATE (ULTRON_CONDITION, val + 1); + SET_GAME_STATE (CLEAR_SPINDLE_ON_SHIP, 0); + SET_GAME_STATE (DISCUSSED_ULTRON, 0); + SET_GAME_STATE (SUPOX_ULTRON_HELP, 0); + return DEVICE_SUCCESS; + } + break; + case UMGAH_HYPERWAVE_DEVICE: + case BURVIX_HYPERWAVE_DEVICE: + if (UseCaster ()) + return DEVICE_SUCCESS; + break; + case TAALO_PROTECTOR_DEVICE: + break; + case EGG_CASING0_DEVICE: + case EGG_CASING1_DEVICE: + case EGG_CASING2_DEVICE: + break; + case SYREEN_SHUTTLE_DEVICE: + break; + case VUX_BEAST_DEVICE: + break; + case DESTRUCT_CODE_DEVICE: + break; + case PORTAL_SPAWNER_DEVICE: +#define PORTAL_FUEL_COST (10 * FUEL_TANK_SCALE) + if (inHyperSpace () + && GLOBAL_SIS (FuelOnBoard) >= PORTAL_FUEL_COST) + { + /* No DeltaSISGauges because the flagship picture + * is currently obscured. + */ + GLOBAL_SIS (FuelOnBoard) -= PORTAL_FUEL_COST; + SET_GAME_STATE (PORTAL_COUNTER, 1); + return DEVICE_SUCCESS; + } + break; + case URQUAN_WARP_DEVICE: + break; + case LUNAR_BASE_DEVICE: + break; + } + + return DEVICE_FAILURE; +} + +static BOOLEAN +DoManipulateDevices (MENU_STATE *pMS) +{ + DEVICES_STATE *devState = pMS->privData; + BOOLEAN select, cancel, back, forward; + BOOLEAN pagefwd, pageback; + + select = PulsedInputState.menu[KEY_MENU_SELECT]; + cancel = PulsedInputState.menu[KEY_MENU_CANCEL]; + back = PulsedInputState.menu[KEY_MENU_UP] || + PulsedInputState.menu[KEY_MENU_LEFT]; + forward = PulsedInputState.menu[KEY_MENU_DOWN] + || PulsedInputState.menu[KEY_MENU_RIGHT]; + pagefwd = PulsedInputState.menu[KEY_MENU_PAGE_DOWN]; + pageback = PulsedInputState.menu[KEY_MENU_PAGE_UP]; + + if (GLOBAL (CurrentActivity) & CHECK_ABORT) + return FALSE; + + if (cancel) + { + return FALSE; + } + else if (select) + { + DeviceStatus status; + + status = InvokeDevice (devState->list[pMS->CurState]); + if (status == DEVICE_FAILURE) + PlayMenuSound (MENU_SOUND_FAILURE); + else if (status == DEVICE_SUCCESS) + PlayMenuSound (MENU_SOUND_INVOKED); + + return (status == DEVICE_FAILURE); + } + else + { + SIZE NewTop; + SIZE NewState; + + NewTop = devState->topIndex; + NewState = pMS->CurState; + + if (back) + --NewState; + else if (forward) + ++NewState; + else if (pagefwd) + NewState += MAX_VIS_DEVICES; + else if (pageback) + NewState -= MAX_VIS_DEVICES; + + if (NewState < 0) + NewState = 0; + else if (NewState >= devState->count) + NewState = devState->count - 1; + + if (NewState < NewTop || NewState >= NewTop + MAX_VIS_DEVICES) + NewTop = NewState - NewState % MAX_VIS_DEVICES; + + if (NewState != pMS->CurState) + { + if (NewTop != devState->topIndex) + { // redraw the display + devState->topIndex = NewTop; + DrawDevices (devState, (COUNT)~0, NewState); + } + else + { // move selection to new device + DrawDevices (devState, pMS->CurState, NewState); + } + pMS->CurState = NewState; + } + + SleepThread (ONE_SECOND / 30); + } + + return TRUE; +} + +SIZE +InventoryDevices (BYTE *pDeviceMap, COUNT Size) +{ + BYTE i; + SIZE DevicesOnBoard; + + DevicesOnBoard = 0; + for (i = 0; i < NUM_DEVICES && Size > 0; ++i) + { + BYTE DeviceState; + + DeviceState = 0; + switch (i) + { + case ROSY_SPHERE_DEVICE: + DeviceState = GET_GAME_STATE (ROSY_SPHERE_ON_SHIP); + break; + case ARTIFACT_2_DEVICE: + DeviceState = GET_GAME_STATE (ARTIFACT_2_ON_SHIP); + break; + case ARTIFACT_3_DEVICE: + DeviceState = GET_GAME_STATE (ARTIFACT_3_ON_SHIP); + break; + case SUN_EFFICIENCY_DEVICE: + DeviceState = GET_GAME_STATE (SUN_DEVICE_ON_SHIP); + break; + case UTWIG_BOMB_DEVICE: + DeviceState = GET_GAME_STATE (UTWIG_BOMB_ON_SHIP); + break; + case ULTRON_0_DEVICE: + DeviceState = (GET_GAME_STATE (ULTRON_CONDITION) == 1); + break; + case ULTRON_1_DEVICE: + DeviceState = (GET_GAME_STATE (ULTRON_CONDITION) == 2); + break; + case ULTRON_2_DEVICE: + DeviceState = (GET_GAME_STATE (ULTRON_CONDITION) == 3); + break; + case ULTRON_3_DEVICE: + DeviceState = (GET_GAME_STATE (ULTRON_CONDITION) == 4); + break; + case MAIDENS_DEVICE: + DeviceState = GET_GAME_STATE (MAIDENS_ON_SHIP); + break; + case TALKING_PET_DEVICE: + DeviceState = GET_GAME_STATE (TALKING_PET_ON_SHIP); + break; + case AQUA_HELIX_DEVICE: + DeviceState = GET_GAME_STATE (AQUA_HELIX_ON_SHIP); + break; + case CLEAR_SPINDLE_DEVICE: + DeviceState = GET_GAME_STATE (CLEAR_SPINDLE_ON_SHIP); + break; + case UMGAH_HYPERWAVE_DEVICE: + DeviceState = GET_GAME_STATE (UMGAH_BROADCASTERS_ON_SHIP); + break; + case TAALO_PROTECTOR_DEVICE: + DeviceState = GET_GAME_STATE (TAALO_PROTECTOR_ON_SHIP); + break; + case EGG_CASING0_DEVICE: + DeviceState = GET_GAME_STATE (EGG_CASE0_ON_SHIP); + break; + case EGG_CASING1_DEVICE: + DeviceState = GET_GAME_STATE (EGG_CASE1_ON_SHIP); + break; + case EGG_CASING2_DEVICE: + DeviceState = GET_GAME_STATE (EGG_CASE2_ON_SHIP); + break; + case SYREEN_SHUTTLE_DEVICE: + DeviceState = GET_GAME_STATE (SYREEN_SHUTTLE_ON_SHIP); + break; + case VUX_BEAST_DEVICE: + DeviceState = GET_GAME_STATE (VUX_BEAST_ON_SHIP); + break; + case DESTRUCT_CODE_DEVICE: +#ifdef NEVER + DeviceState = GET_GAME_STATE (DESTRUCT_CODE_ON_SHIP); +#endif /* NEVER */ + break; + case PORTAL_SPAWNER_DEVICE: + DeviceState = GET_GAME_STATE (PORTAL_SPAWNER_ON_SHIP); + break; + case URQUAN_WARP_DEVICE: + DeviceState = GET_GAME_STATE (PORTAL_KEY_ON_SHIP); + break; + case BURVIX_HYPERWAVE_DEVICE: + DeviceState = GET_GAME_STATE (BURV_BROADCASTERS_ON_SHIP); + break; + case LUNAR_BASE_DEVICE: + DeviceState = GET_GAME_STATE (MOONBASE_ON_SHIP); + break; + } + +#ifndef DEBUG_DEVICES + if (DeviceState) +#endif /* DEBUG_DEVICES */ + { + *pDeviceMap++ = i; + ++DevicesOnBoard; + --Size; + } + } + + return DevicesOnBoard; +} + +BOOLEAN +DevicesMenu (void) +{ + MENU_STATE MenuState; + DEVICES_STATE DevicesState; + + memset (&MenuState, 0, sizeof MenuState); + MenuState.privData = &DevicesState; + + memset (&DevicesState, 0, sizeof DevicesState); + + DevicesState.count = InventoryDevices (DevicesState.list, NUM_DEVICES); + if (!DevicesState.count) + return FALSE; + + DrawDevices (&DevicesState, (COUNT)~0, MenuState.CurState); + + SetMenuSounds (MENU_SOUND_ARROWS | MENU_SOUND_PAGEUP | MENU_SOUND_PAGEDOWN, + MENU_SOUND_SELECT); + + MenuState.InputFunc = DoManipulateDevices; + DoInput (&MenuState, TRUE); + + SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); + + if (GLOBAL_SIS (CrewEnlisted) != (COUNT)~0 + && !(GLOBAL (CurrentActivity) & CHECK_ABORT)) + { + ClearSISRect (DRAW_SIS_DISPLAY); + + if (!GET_GAME_STATE (PORTAL_COUNTER) + && !(GLOBAL (CurrentActivity) & START_ENCOUNTER) + && GLOBAL_SIS (CrewEnlisted) != (COUNT)~0) + return TRUE; + } + + return FALSE; +} + diff --git a/src/uqm/planets/elemdata.h b/src/uqm/planets/elemdata.h new file mode 100644 index 0000000..53d228c --- /dev/null +++ b/src/uqm/planets/elemdata.h @@ -0,0 +1,215 @@ +//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. + */ + +#ifndef UQM_PLANETS_ELEMDATA_H_ +#define UQM_PLANETS_ELEMDATA_H_ + +#include "libs/compiler.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/*------------------------------ Type Defines ----------------------------- */ +enum +{ + COMMON = 0, + CORROSIVE, + BASE_METAL, + NOBLE, + RARE_EARTH, + PRECIOUS, + RADIOACTIVE, + EXOTIC, + + NUM_ELEMENT_CATEGORIES +}; + +#define ElementCategory(et) (Elements[et] & 0x7) + +/*------------------------------ Global Data ------------------------------ */ + +enum +{ + HYDROGEN, + HELIUM, + LITHIUM, + BERYLLIUM, + BORON, + CARBON, + NITROGEN, + OXYGEN, + FLUORINE, + NEON, + SODIUM, + MAGNESIUM, + ALUMINUM, + SILICON, + PHOSPHORUS, + SULFUR, + CHLORINE, + ARGON, + POTASSIUM, + CALCIUM, + SCANDIUM, + TITANIUM, + VANADIUM, + CHROMIUM, + MANGANESE, + IRON, + COBALT, + NICKEL, + COPPER, + ZINC, + GALLIUM, + GERMANIUM, + ARSENIC, + SELENIUM, + BROMINE, + KRYPTON, + RUBIDIUM, + STRONTIUM, + YTTRIUM, + ZIRCONIUM, + NIOBIUM, + MOLYBDENUM, + TECHNETIUM, + RUTHENIUM, + RHODIUM, + PALLADIUM, + SILVER, + CADMIUM, + INDIUM, + TIN, + ANTIMONY, + TELLURIUM, + IODINE, + XENON, + CESIUM, + BARIUM, + LANTHANUM, + CERIUM, + PRASEODYMIUM, + NEODYMIUM, + PROMETHIUM, + SAMARIUM, + EUROPIUM, + GADOLINIUM, + TERBIUM, + DYPROSIUM, + HOLMIUM, + ERBIUM, + THULIUM, + YTTERBIUM, + LUTETIUM, + HAFNIUM, + TANTALUM, + TUNGSTEN, + RHENIUM, + OSMIUM, + IRIDIUM, + PLATINUM, + GOLD, + MERCURY, + THALLIUM, + LEAD, + BISMUTH, + POLONIUM, + ASTATINE, + RADON, + FRANCIUM, + RADIUM, + ACTINIUM, + THORIUM, + PROTACTINIUM, + URANIUM, + NEPTUNIUM, + PLUTONIUM, + NUMBER_OF_NORMAL, + + OZONE = NUMBER_OF_NORMAL, + FREE_RADICALS, + CARBON_DIOXIDE, + CARBON_MONOXIDE, + AMMONIA, + METHANE, + SULFURIC_ACID, + HYDROCHLORIC_ACID, + HYDROCYANIC_ACID, + FORMIC_ACID, + PHOSPHORIC_ACID, + FORMALDEHYDE, + CYANOACETYLENE, + METHANOL, + ETHANOL, + SILICON_MONOXIDE, + TITANIUM_OXIDE, + ZIRCONIUM_OXIDE, + WATER, + SILICON_COMPOUNDS, + METAL_OXIDES, + QUANTUM_BH, + NEUTRONIUM, + MAGNETIC_MONOPOLES, + DEGENERATE_MATTER, + RT_SUPER_FLUID, + AGUUTI_NODULES, + IRON_COMPOUNDS, + ALUMINUM_COMPOUNDS, + NITROUS_OXIDE, + RADIOACTIVE_COMPOUNDS, + HYDROCARBONS, + CARBON_COMPOUNDS, + ANTIMATTER, + CHARON_DUST, + REISBURG_HELICES, + TZO_CRYSTALS, + CALCIUM_COMPOUNDS, + NITRIC_ACID, + + NUMBER_OF_ELEMENTS +}; +#define NUMBER_OF_SPECIAL (NUMBER_OF_ELEMENTS - NUMBER_OF_NORMAL) + +#define CHANCE_MASK ((1 << 2) - 1) + +#define FEW 1 +#define MODERATE 4 +#define NUMEROUS 8 + +enum +{ + LIGHT = 0, + MEDIUM, + HEAVY +}; +#define NOTHING (BYTE)(~0) + +#define MINERAL_DEPOSIT(qn,ql) MAKE_BYTE (qn, ql) +#define DEPOSIT_QUANTITY(md) LONIBBLE (md) +#define DEPOSIT_QUALITY(md) HINIBBLE (md) + +#define MAX_ELEMENT_UNITS 0xF + +extern const BYTE *Elements; + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_PLANETS_ELEMDATA_H_ */ diff --git a/src/uqm/planets/generate.h b/src/uqm/planets/generate.h new file mode 100644 index 0000000..9942e82 --- /dev/null +++ b/src/uqm/planets/generate.h @@ -0,0 +1,110 @@ +/* + * 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. + */ + +#ifndef GENERATE_H +#define GENERATE_H + +typedef struct GenerateFunctions GenerateFunctions; + +#include "types.h" +#include "planets.h" +#include "libs/compiler.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/* + * To do (for further cleanups): + * - split off generateOrbital in a calculation and an activation + * (graphics and music) part. + * - make generateOrbital return a meaningful value, specifically, whether + * or not the player is going into orbit + * - for GenerateNameFunction, set the name in an argument, instead + * of in GLOBAL_SYS(PlanetName) + * - make generateName work for moons + * - add parameters to initNcs, reinitNpcs, and uninitNpcs, so that + * globals don't have to be used. + * - Add a reference from each world to the solar system, so that most + * of these functions can do with one less argument. + * - (maybe) don't directly call the generate functions via + * solarSys->genFuncs->..., but use a function for this, which first + * checks for solar system dependent handlers, and if this does not exist, + * or returns false, calls the default function. + */ + +// Any of these functions returning true means that the action has been +// handled, and that the default function should not be called. +typedef bool (*InitNpcsFunction)(SOLARSYS_STATE *solarSys); +typedef bool (*ReinitNpcsFunction)(SOLARSYS_STATE *solarSys); +typedef bool (*UninitNpcsFunction)(SOLARSYS_STATE *solarSys); +typedef bool (*GeneratePlanetsFunction)(SOLARSYS_STATE *solarSys); +typedef bool (*GenerateMoonsFunction)(SOLARSYS_STATE *solarSys, + PLANET_DESC *planet); +typedef bool (*GenerateOrbitalFunction)(SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +typedef bool (*GenerateNameFunction)(const SOLARSYS_STATE *, + const PLANET_DESC *world); +// The following functions return the number of objects being generated +// (or the index of the current object in some cases) +typedef COUNT (*GenerateMineralsFunction)(const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +typedef COUNT (*GenerateEnergyFunction)(const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +typedef COUNT (*GenerateLifeFunction)(const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +// The following functions return true if the node should be removed +// from the surface, i.e. picked up. +typedef bool (*PickupMineralsFunction)(SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); +typedef bool (*PickupEnergyFunction)(SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); +typedef bool (*PickupLifeFunction)(SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + + +struct GenerateFunctions { + InitNpcsFunction initNpcs; + // Ships in the solar system, the first time it is accessed. + ReinitNpcsFunction reinitNpcs; + // Ships in the solar system, every next time it is accessed. + UninitNpcsFunction uninitNpcs; + // When leaving the solar system. + GeneratePlanetsFunction generatePlanets; + // Layout of planets within a solar system. + GenerateMoonsFunction generateMoons; + // Layout of moons around a planet. + GenerateNameFunction generateName; + // Name of a planet. + GenerateOrbitalFunction generateOrbital; + // Characteristics of words (planets and moons). + GenerateMineralsFunction generateMinerals; + // Minerals on the planet surface. + GenerateEnergyFunction generateEnergy; + // Energy sources on the planet surface. + GenerateLifeFunction generateLife; + // Bio on the planet surface. + PickupMineralsFunction pickupMinerals; + PickupEnergyFunction pickupEnergy; + PickupLifeFunction pickupLife; +}; + +#if defined(__cplusplus) +} +#endif + +#endif /* GENERATE_H */ + diff --git a/src/uqm/planets/generate/Makeinfo b/src/uqm/planets/generate/Makeinfo new file mode 100644 index 0000000..520af9d --- /dev/null +++ b/src/uqm/planets/generate/Makeinfo @@ -0,0 +1,6 @@ +uqm_CFILES="gendefault.c genand.c genburv.c genchmmr.c gencol.c gendru.c + genilw.c genmel.c genmyc.c genorz.c genpet.c genpku.c genrain.c + gensam.c genshof.c gensly.c gensol.c genspa.c gensup.c gensyr.c + genthrad.c gentrap.c genutw.c genvault.c genvux.c genwreck.c + genyeh.c genzfpscout.c genzoq.c" +uqm_HFILES="genall.h gendefault.h" diff --git a/src/uqm/planets/generate/genall.h b/src/uqm/planets/generate/genall.h new file mode 100644 index 0000000..3776cff --- /dev/null +++ b/src/uqm/planets/generate/genall.h @@ -0,0 +1,27 @@ +/* + * 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 + */ + +#ifndef UQM_PLANETS_GENERATE_GENALL_H_ +#define UQM_PLANETS_GENERATE_GENALL_H_ + +#include "gendefault.h" +#include "types.h" +#include "../generate.h" +#include "libs/compiler.h" + + +#endif /* UQM_PLANETS_GENERATE_GENALL_H_ */ + diff --git a/src/uqm/planets/generate/genand.c b/src/uqm/planets/generate/genand.c new file mode 100644 index 0000000..457b5ff --- /dev/null +++ b/src/uqm/planets/generate/genand.c @@ -0,0 +1,164 @@ +//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 "genall.h" +#include "../lander.h" +#include "../planets.h" +#include "../scan.h" +#include "../../globdata.h" +#include "../../nameref.h" +#include "../../resinst.h" +#include "../../sounds.h" +#include "libs/mathlib.h" + + +static bool GenerateAndrosynth_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateAndrosynth_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GenerateAndrosynth_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GenerateAndrosynth_pickupEnergy (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + + +const GenerateFunctions generateAndrosynthFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateAndrosynth_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateAndrosynth_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateAndrosynth_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateAndrosynth_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateAndrosynth_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT angle; + + GenerateDefault_generatePlanets (solarSys); + + solarSys->PlanetDesc[1].data_index = TELLURIC_WORLD; + solarSys->PlanetDesc[1].radius = EARTH_RADIUS * 204L / 100; + angle = ARCTAN (solarSys->PlanetDesc[1].location.x, + solarSys->PlanetDesc[1].location.y); + solarSys->PlanetDesc[1].location.x = + COSINE (angle, solarSys->PlanetDesc[1].radius); + solarSys->PlanetDesc[1].location.y = + SINE (angle, solarSys->PlanetDesc[1].radius); + + return true; +} + +static bool +GenerateAndrosynth_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if (matchWorld (solarSys, world, 1, MATCH_PLANET)) + { + COUNT i; + COUNT visits = 0; + + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable ( + LoadStringTable (ANDROSYNTH_RUINS_STRTAB)); + // Androsynth ruins are a special case. The DiscoveryString contains + // several lander reports which form a story. Each report is given + // when the player collides with a new city ruin. Ruins previously + // visited are marked in the upper 16 bits of ScanRetrieveMask, and + // the lower bits are cleared to keep the ruin nodes on the map. + for (i = 16; i < 32; ++i) + { + if (isNodeRetrieved (&solarSys->SysInfo.PlanetInfo, ENERGY_SCAN, i)) + ++visits; + } + if (visits >= GetStringTableCount ( + solarSys->SysInfo.PlanetInfo.DiscoveryString)) + { // All the reports were already given + DestroyStringTable (ReleaseStringTable ( + solarSys->SysInfo.PlanetInfo.DiscoveryString)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = 0; + } + else + { // Advance the report sequence to the first unread + solarSys->SysInfo.PlanetInfo.DiscoveryString = + SetRelStringTableIndex ( + solarSys->SysInfo.PlanetInfo.DiscoveryString, visits); + } + } + + GenerateDefault_generateOrbital (solarSys, world); + + if (matchWorld (solarSys, world, 1, MATCH_PLANET)) + { + solarSys->SysInfo.PlanetInfo.AtmoDensity = + EARTH_ATMOSPHERE * 144 / 100; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 28; + solarSys->SysInfo.PlanetInfo.Weather = 1; + solarSys->SysInfo.PlanetInfo.Tectonics = 1; + } + + return true; +} + +static bool +GenerateAndrosynth_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (matchWorld (solarSys, world, 1, MATCH_PLANET)) + { + PLANET_INFO *planetInfo = &solarSys->SysInfo.PlanetInfo; + + // Ruins previously visited are marked in the upper 16 bits + if (isNodeRetrieved (planetInfo, ENERGY_SCAN, whichNode + 16)) + return false; // already visited this ruin, do not remove + + setNodeRetrieved (planetInfo, ENERGY_SCAN, whichNode + 16); + // We set the retrieved bit manually here and need to indicate + // the change to the solar system state functions + SET_GAME_STATE (PLANETARY_CHANGE, 1); + + // Androsynth ruins have several lander reports which form a story + GenerateDefault_landerReportCycle (solarSys); + + return false; // do not remove the node from the surface + } + + return false; +} + +static COUNT +GenerateAndrosynth_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (matchWorld (solarSys, world, 1, MATCH_PLANET)) + { + return GenerateDefault_generateRuins (solarSys, whichNode, info); + } + + return 0; +} diff --git a/src/uqm/planets/generate/genburv.c b/src/uqm/planets/generate/genburv.c new file mode 100644 index 0000000..aa5b6bd --- /dev/null +++ b/src/uqm/planets/generate/genburv.c @@ -0,0 +1,192 @@ +//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 "genall.h" +#include "../lander.h" +#include "../planets.h" +#include "../../globdata.h" +#include "../../nameref.h" +#include "../../resinst.h" +#include "libs/mathlib.h" + + +static bool GenerateBurvixese_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateBurvixese_generateMoons (SOLARSYS_STATE *solarSys, + PLANET_DESC *planet); +static bool GenerateBurvixese_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GenerateBurvixese_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GenerateBurvixese_pickupEnergy (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + + +const GenerateFunctions generateBurvixeseFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateBurvixese_generatePlanets, + /* .generateMoons = */ GenerateBurvixese_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateBurvixese_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateBurvixese_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateBurvixese_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateBurvixese_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT angle; + + GenerateDefault_generatePlanets (solarSys); + + solarSys->PlanetDesc[0].data_index = REDUX_WORLD; + solarSys->PlanetDesc[0].NumPlanets = 1; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 39L / 100; + angle = ARCTAN (solarSys->PlanetDesc[0].location.x, + solarSys->PlanetDesc[0].location.y); + solarSys->PlanetDesc[0].location.x = + COSINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].location.y = + SINE (angle, solarSys->PlanetDesc[0].radius); + return true; +} + +static bool +GenerateBurvixese_generateMoons (SOLARSYS_STATE *solarSys, PLANET_DESC *planet) +{ + GenerateDefault_generateMoons (solarSys, planet); + + if (matchWorld (solarSys, planet, 0, MATCH_PLANET)) + { + COUNT angle; + DWORD rand_val; + + solarSys->MoonDesc[0].data_index = SELENIC_WORLD; + solarSys->MoonDesc[0].radius = MIN_MOON_RADIUS + + (MAX_MOONS - 1) * MOON_DELTA; + rand_val = RandomContext_Random (SysGenRNG); + angle = NORMALIZE_ANGLE (LOWORD (rand_val)); + solarSys->MoonDesc[0].location.x = + COSINE (angle, solarSys->MoonDesc[0].radius); + solarSys->MoonDesc[0].location.y = + SINE (angle, solarSys->MoonDesc[0].radius); + } + return true; +} + +static bool +GenerateBurvixese_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + DWORD rand_val; + + DoPlanetaryAnalysis (&solarSys->SysInfo, world); + rand_val = RandomContext_GetSeed (SysGenRNG); + + solarSys->SysInfo.PlanetInfo.ScanSeed[BIOLOGICAL_SCAN] = rand_val; + GenerateLifeForms (&solarSys->SysInfo, GENERATE_ALL, NULL); + rand_val = RandomContext_GetSeed (SysGenRNG); + + solarSys->SysInfo.PlanetInfo.ScanSeed[MINERAL_SCAN] = rand_val; + GenerateMineralDeposits (&solarSys->SysInfo, GENERATE_ALL, NULL); + + solarSys->SysInfo.PlanetInfo.ScanSeed[ENERGY_SCAN] = rand_val; + + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable ( + LoadGraphic (RUINS_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable ( + LoadStringTable (BURV_RUINS_STRTAB)); + solarSys->SysInfo.PlanetInfo.Weather = 0; + solarSys->SysInfo.PlanetInfo.Tectonics = 0; + } + else if (matchWorld (solarSys, world, 0, 0) + && !GET_GAME_STATE (BURVIXESE_BROADCASTERS)) + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = CaptureDrawable ( + LoadGraphic (BURV_BCS_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable (LoadStringTable (BURV_BCS_STRTAB)); + } + + LoadPlanet (NULL); + + return true; +} + +static COUNT +GenerateBurvixese_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + return GenerateDefault_generateRuins (solarSys, whichNode, info); + } + + if (matchWorld (solarSys, world, 0, 0)) + { + // This check is redundant since the retrieval bit will keep the + // node from showing up again + if (GET_GAME_STATE (BURVIXESE_BROADCASTERS)) + { // already picked up + return 0; + } + + return GenerateDefault_generateArtifact (solarSys, whichNode, info); + } + + return 0; +} + +static bool +GenerateBurvixese_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + // Standard ruins report + GenerateDefault_landerReportCycle (solarSys); + return false; + } + + if (matchWorld (solarSys, world, 0, 0)) + { + assert (!GET_GAME_STATE (BURVIXESE_BROADCASTERS) && whichNode == 0); + + GenerateDefault_landerReport (solarSys); + SetLanderTakeoff (); + + SET_GAME_STATE (BURVIXESE_BROADCASTERS, 1); + SET_GAME_STATE (BURV_BROADCASTERS_ON_SHIP, 1); + + return true; // picked up + } + + (void) whichNode; + return false; +} diff --git a/src/uqm/planets/generate/genchmmr.c b/src/uqm/planets/generate/genchmmr.c new file mode 100644 index 0000000..672d977 --- /dev/null +++ b/src/uqm/planets/generate/genchmmr.c @@ -0,0 +1,154 @@ +//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 "genall.h" +#include "../lander.h" +#include "../planets.h" +#include "../../build.h" +#include "../../comm.h" +#include "../../globdata.h" +#include "../../nameref.h" +#include "../../setup.h" +#include "../../sounds.h" +#include "../../state.h" +#include "libs/mathlib.h" + +static bool GenerateChmmr_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateChmmr_generateMoons (SOLARSYS_STATE *solarSys, + PLANET_DESC *planet); +static bool GenerateChmmr_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); + + +const GenerateFunctions generateChmmrFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateChmmr_generatePlanets, + /* .generateMoons = */ GenerateChmmr_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateChmmr_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateDefault_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateDefault_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateChmmr_generatePlanets (SOLARSYS_STATE *solarSys) +{ + GenerateDefault_generatePlanets (solarSys); + + solarSys->PlanetDesc[1].data_index = SAPPHIRE_WORLD; + if (!GET_GAME_STATE (CHMMR_UNLEASHED)) + solarSys->PlanetDesc[1].data_index |= PLANET_SHIELDED; + solarSys->PlanetDesc[1].NumPlanets = 1; + + return true; +} + +static bool +GenerateChmmr_generateMoons (SOLARSYS_STATE *solarSys, PLANET_DESC *planet) +{ + GenerateDefault_generateMoons (solarSys, planet); + + if (matchWorld (solarSys, planet, 1, MATCH_PLANET)) + { + COUNT angle; + DWORD rand_val; + + solarSys->MoonDesc[0].data_index = HIERARCHY_STARBASE; + solarSys->MoonDesc[0].radius = MIN_MOON_RADIUS; + rand_val = RandomContext_Random (SysGenRNG); + angle = NORMALIZE_ANGLE (LOWORD (rand_val)); + solarSys->MoonDesc[0].location.x = + COSINE (angle, solarSys->MoonDesc[0].radius); + solarSys->MoonDesc[0].location.y = + SINE (angle, solarSys->MoonDesc[0].radius); + } + + return true; +} + +static bool +GenerateChmmr_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if (matchWorld (solarSys, world, 1, MATCH_PLANET)) + { + if (GET_GAME_STATE (CHMMR_UNLEASHED)) + { + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); + InitCommunication (CHMMR_CONVERSATION); + + if (GET_GAME_STATE (CHMMR_BOMB_STATE) == 2) + { + GLOBAL (CurrentActivity) |= END_INTERPLANETARY; + } + + return true; + } + else if (GET_GAME_STATE (SUN_DEVICE_ON_SHIP) + && !GET_GAME_STATE (ILWRATH_DECEIVED) + && StartSphereTracking (ILWRATH_SHIP)) + { + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + CloneShipFragment (ILWRATH_SHIP, + &GLOBAL (npc_built_ship_q), INFINITE_FLEET); + + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 6); + GLOBAL (CurrentActivity) |= START_INTERPLANETARY; + InitCommunication (ILWRATH_CONVERSATION); + + if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))) + { + GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY; + ReinitQueue (&GLOBAL (npc_built_ship_q)); + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + } + + return true; + } + } + else if (matchWorld (solarSys, world, 1, 0)) + { + /* Starbase */ + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable (LoadStringTable (CHMMR_BASE_STRTAB)); + + DoDiscoveryReport (MenuSounds); + + DestroyStringTable (ReleaseStringTable ( + solarSys->SysInfo.PlanetInfo.DiscoveryString)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = 0; + FreeLanderFont (&solarSys->SysInfo.PlanetInfo); + + return true; + } + + GenerateDefault_generateOrbital (solarSys, world); + + return true; +} + diff --git a/src/uqm/planets/generate/gencol.c b/src/uqm/planets/generate/gencol.c new file mode 100644 index 0000000..27e4d5b --- /dev/null +++ b/src/uqm/planets/generate/gencol.c @@ -0,0 +1,126 @@ +//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 "genall.h" +#include "../planets.h" +#include "../../build.h" +#include "../../globdata.h" +#include "../../grpinfo.h" +#include "../../state.h" +#include "libs/mathlib.h" + + +static bool GenerateColony_initNpcs (SOLARSYS_STATE *solarSys); +static bool GenerateColony_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateColony_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); + + +const GenerateFunctions generateColonyFunctions = { + /* .initNpcs = */ GenerateColony_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateColony_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateColony_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateDefault_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateDefault_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateColony_initNpcs (SOLARSYS_STATE *solarSys) +{ + HIPGROUP hGroup; + + GLOBAL (BattleGroupRef) = GET_GAME_STATE_32 (COLONY_GRPOFFS0); + if (GLOBAL (BattleGroupRef) == 0) + { + CloneShipFragment (URQUAN_SHIP, + &GLOBAL (npc_built_ship_q), 0); + GLOBAL (BattleGroupRef) = PutGroupInfo (GROUPS_ADD_NEW, 1); + ReinitQueue (&GLOBAL (npc_built_ship_q)); + SET_GAME_STATE_32 (COLONY_GRPOFFS0, GLOBAL (BattleGroupRef)); + } + + GenerateDefault_initNpcs (solarSys); + + if (GLOBAL (BattleGroupRef) + && (hGroup = GetHeadLink (&GLOBAL (ip_group_q)))) + { + IP_GROUP *GroupPtr; + + GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup); + GroupPtr->task = IN_ORBIT; + GroupPtr->sys_loc = 0 + 1; /* orbitting colony */ + GroupPtr->dest_loc = 0 + 1; /* orbitting colony */ + GroupPtr->loc.x = 0; + GroupPtr->loc.y = 0; + GroupPtr->group_counter = 0; + UnlockIpGroup (&GLOBAL (ip_group_q), hGroup); + } + + return true; +} + +static bool +GenerateColony_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT angle; + PLANET_DESC *pMinPlanet; + + pMinPlanet = &solarSys->PlanetDesc[0]; + FillOrbits (solarSys, (BYTE)~0, pMinPlanet, FALSE); + + pMinPlanet->radius = EARTH_RADIUS * 115L / 100; + angle = ARCTAN (pMinPlanet->location.x, pMinPlanet->location.y); + pMinPlanet->location.x = COSINE (angle, pMinPlanet->radius); + pMinPlanet->location.y = SINE (angle, pMinPlanet->radius); + pMinPlanet->data_index = WATER_WORLD | PLANET_SHIELDED; + + return true; +} + +static bool +GenerateColony_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + DoPlanetaryAnalysis (&solarSys->SysInfo, world); + + solarSys->SysInfo.PlanetInfo.AtmoDensity = + EARTH_ATMOSPHERE * 98 / 100; + solarSys->SysInfo.PlanetInfo.Weather = 0; + solarSys->SysInfo.PlanetInfo.Tectonics = 0; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 28; + + LoadPlanet (NULL); + + return true; + } + + GenerateDefault_generateOrbital (solarSys, world); + + return true; +} + diff --git a/src/uqm/planets/generate/gendefault.c b/src/uqm/planets/generate/gendefault.c new file mode 100644 index 0000000..a88b89c --- /dev/null +++ b/src/uqm/planets/generate/gendefault.c @@ -0,0 +1,373 @@ +/* + * 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 "genall.h" +#include "../planets.h" +#include "../lander.h" +#include "../../encount.h" +#include "../../gamestr.h" +#include "../../globdata.h" +#include "../../grpinfo.h" +#include "../../races.h" +#include "../../state.h" +#include "../../sounds.h" +#include "libs/mathlib.h" + + +static void GeneratePlanets (SOLARSYS_STATE *system); +static void check_yehat_rebellion (void); + + +const GenerateFunctions generateDefaultFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateDefault_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateDefault_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateDefault_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateDefault_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +bool +GenerateDefault_initNpcs (SOLARSYS_STATE *solarSys) +{ + if (!GetGroupInfo (GLOBAL (BattleGroupRef), GROUP_INIT_IP)) + { + GLOBAL (BattleGroupRef) = 0; + BuildGroups (); + } + + (void) solarSys; + return true; +} + +bool +GenerateDefault_reinitNpcs (SOLARSYS_STATE *solarSys) +{ + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + // This is not a great place to do the Yehat rebellion check, but + // since you can start the rebellion in any star system (not just + // the Homeworld), I could not find a better place for it. + // At least it is better than where it was originally. + check_yehat_rebellion (); + + (void) solarSys; + return true; +} + +bool +GenerateDefault_uninitNpcs (SOLARSYS_STATE *solarSys) +{ + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (npc_built_ship_q)); + ReinitQueue (&GLOBAL (ip_group_q)); + + (void) solarSys; + return true; +} + +bool +GenerateDefault_generatePlanets (SOLARSYS_STATE *solarSys) +{ + FillOrbits (solarSys, (BYTE)~0, solarSys->PlanetDesc, FALSE); + GeneratePlanets (solarSys); + return true; +} + +bool +GenerateDefault_generateMoons (SOLARSYS_STATE *solarSys, PLANET_DESC *planet) +{ + FillOrbits (solarSys, planet->NumPlanets, solarSys->MoonDesc, FALSE); + return true; +} + +bool +GenerateDefault_generateName (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world) +{ + COUNT i = planetIndex (solarSys, world); + utf8StringCopy (GLOBAL_SIS (PlanetName), sizeof (GLOBAL_SIS (PlanetName)), + GAME_STRING (PLANET_NUMBER_BASE + (9 + 7) + i)); + SET_GAME_STATE (BATTLE_PLANET, world->data_index); + + return true; +} + +bool +GenerateDefault_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + DWORD rand_val; + SYSTEM_INFO *sysInfo; + +#ifdef DEBUG_SOLARSYS + if (worldIsPlanet (solarSys, world)) + { + log_add (log_Debug, "Planet index = %d", + planetIndex (solarSys, world)); + } + else + { + log_add (log_Debug, "Planet index = %d, Moon index = %d", + planetIndex (solarSys, world), + moonIndex (solarSys, world)); + } +#endif /* DEBUG_SOLARSYS */ + + sysInfo = &solarSys->SysInfo; + + DoPlanetaryAnalysis (sysInfo, world); + rand_val = RandomContext_GetSeed (SysGenRNG); + + sysInfo->PlanetInfo.ScanSeed[BIOLOGICAL_SCAN] = rand_val; + GenerateLifeForms (sysInfo, GENERATE_ALL, NULL); + rand_val = RandomContext_GetSeed (SysGenRNG); + + sysInfo->PlanetInfo.ScanSeed[MINERAL_SCAN] = rand_val; + GenerateMineralDeposits (sysInfo, GENERATE_ALL, NULL); + + sysInfo->PlanetInfo.ScanSeed[ENERGY_SCAN] = rand_val; + LoadPlanet (NULL); + + return true; +} + +COUNT +GenerateDefault_generateMinerals (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + return GenerateMineralDeposits (&solarSys->SysInfo, whichNode, info); + (void) world; +} + +bool +GenerateDefault_pickupMinerals (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + // Minerals do not need any extra handling as of now + (void) solarSys; + (void) world; + (void) whichNode; + return true; +} + +COUNT +GenerateDefault_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + (void) whichNode; + (void) solarSys; + (void) world; + (void) info; + return 0; +} + +bool +GenerateDefault_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + // This should never be called since every energy node needs + // special handling and the function should be overridden + assert (false); + (void) solarSys; + (void) world; + (void) whichNode; + return false; +} + +COUNT +GenerateDefault_generateLife (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + return GenerateLifeForms (&solarSys->SysInfo, whichNode, info); + (void) world; +} + +bool +GenerateDefault_pickupLife (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + // Bio does not need any extra handling as of now + (void) solarSys; + (void) world; + (void) whichNode; + return true; +} + +COUNT +GenerateDefault_generateArtifact (const SOLARSYS_STATE *solarSys, + COUNT whichNode, NODE_INFO *info) +{ + // Generate an energy node at a random location + return GenerateRandomNodes (&solarSys->SysInfo, ENERGY_SCAN, 1, 0, + whichNode, info); +} + +COUNT +GenerateDefault_generateRuins (const SOLARSYS_STATE *solarSys, + COUNT whichNode, NODE_INFO *info) +{ + // Generate a standard spread of city ruins of a destroyed civilization + return GenerateRandomNodes (&solarSys->SysInfo, ENERGY_SCAN, NUM_RACE_RUINS, + 0, whichNode, info); +} + +static inline void +runLanderReport (void) +{ + UnbatchGraphics (); + DoDiscoveryReport (MenuSounds); + BatchGraphics (); +} + +bool +GenerateDefault_landerReport (SOLARSYS_STATE *solarSys) +{ + PLANET_INFO *planetInfo = &solarSys->SysInfo.PlanetInfo; + + if (!planetInfo->DiscoveryString) + return false; + + runLanderReport (); + + // XXX: A non-cycling report is given only once and has to be deleted + // in some circumstances (like the Syreen Vault). It does not + // hurt to simply delete it in all cases. Nothing should rely on + // the presence of DiscoveryString, but the Syreen Vault and the + // Mycon Egg Cases rely on its absence. + DestroyStringTable (ReleaseStringTable (planetInfo->DiscoveryString)); + planetInfo->DiscoveryString = 0; + + return true; +} + +bool +GenerateDefault_landerReportCycle (SOLARSYS_STATE *solarSys) +{ + PLANET_INFO *planetInfo = &solarSys->SysInfo.PlanetInfo; + + if (!planetInfo->DiscoveryString) + return false; + + runLanderReport (); + // Advance to the next report + planetInfo->DiscoveryString = SetRelStringTableIndex ( + planetInfo->DiscoveryString, 1); + + // If our discovery strings have cycled, we're done + if (GetStringTableIndex (planetInfo->DiscoveryString) == 0) + { + DestroyStringTable (ReleaseStringTable (planetInfo->DiscoveryString)); + planetInfo->DiscoveryString = 0; + } + + return true; +} + +// NB. This function modifies the RNG state. +static void +GeneratePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT i; + PLANET_DESC *planet; + + for (i = solarSys->SunDesc[0].NumPlanets, + planet = &solarSys->PlanetDesc[0]; i; --i, ++planet) + { + DWORD rand_val; + BYTE byte_val; + BYTE num_moons; + BYTE type; + + rand_val = RandomContext_Random (SysGenRNG); + byte_val = LOBYTE (rand_val); + + num_moons = 0; + type = PlanData[planet->data_index & ~PLANET_SHIELDED].Type; + switch (PLANSIZE (type)) + { + case LARGE_ROCKY_WORLD: + if (byte_val < 0x00FF * 25 / 100) + { + if (byte_val < 0x00FF * 5 / 100) + ++num_moons; + ++num_moons; + } + break; + case GAS_GIANT: + if (byte_val < 0x00FF * 90 / 100) + { + if (byte_val < 0x00FF * 75 / 100) + { + if (byte_val < 0x00FF * 50 / 100) + { + if (byte_val < 0x00FF * 25 / 100) + ++num_moons; + ++num_moons; + } + ++num_moons; + } + ++num_moons; + } + break; + } + planet->NumPlanets = num_moons; + } +} + +static void +check_yehat_rebellion (void) +{ + HIPGROUP hGroup, hNextGroup; + + // XXX: Is there a better way to do this? I could not find one. + // When you talk to a Yehat ship (YEHAT_SHIP) and start the rebellion, + // there is no battle following the comm. There is *never* a battle in + // an encounter with Rebels, but the group race_id (YEHAT_REBEL_SHIP) + // is different from Royalists (YEHAT_SHIP). There is *always* a battle + // in an encounter with Royalists. + // TRANSLATION: "If the civil war has not started yet, or the player + // battled a ship -- bail." + if (!GET_GAME_STATE (YEHAT_CIVIL_WAR) || EncounterRace >= 0) + return; // not this time + + // Send Yehat groups to flee the system, but only if the player + // has actually talked to a ship. + for (hGroup = GetHeadLink (&GLOBAL (ip_group_q)); hGroup; + hGroup = hNextGroup) + { + IP_GROUP *GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup); + hNextGroup = _GetSuccLink (GroupPtr); + // IGNORE_FLAGSHIP was set in ipdisp.c:ip_group_collision() + // during a collision with the flagship. + if (GroupPtr->race_id == YEHAT_SHIP + && (GroupPtr->task & IGNORE_FLAGSHIP)) + { + GroupPtr->task &= REFORM_GROUP; + GroupPtr->task |= FLEE | IGNORE_FLAGSHIP; + GroupPtr->dest_loc = 0; + } + UnlockIpGroup (&GLOBAL (ip_group_q), hGroup); + } +} + + diff --git a/src/uqm/planets/generate/gendefault.h b/src/uqm/planets/generate/gendefault.h new file mode 100644 index 0000000..a6d0e71 --- /dev/null +++ b/src/uqm/planets/generate/gendefault.h @@ -0,0 +1,66 @@ +/* + * 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. + */ + +#ifndef GENDEFAULT_H +#define GENDEFAULT_H + +#include "types.h" +#include "../planets.h" +#include "libs/compiler.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +bool GenerateDefault_initNpcs (SOLARSYS_STATE *solarSys); +bool GenerateDefault_reinitNpcs (SOLARSYS_STATE *solarSys); +bool GenerateDefault_uninitNpcs (SOLARSYS_STATE *solarSys); +bool GenerateDefault_generatePlanets (SOLARSYS_STATE *solarSys); +bool GenerateDefault_generateMoons (SOLARSYS_STATE *solarSys, + PLANET_DESC *planet); +bool GenerateDefault_generateName (const SOLARSYS_STATE *, + const PLANET_DESC *world); +bool GenerateDefault_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +COUNT GenerateDefault_generateMinerals (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +COUNT GenerateDefault_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +COUNT GenerateDefault_generateLife (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +bool GenerateDefault_pickupMinerals (SOLARSYS_STATE *, PLANET_DESC *world, + COUNT whichNode); +bool GenerateDefault_pickupEnergy (SOLARSYS_STATE *, PLANET_DESC *world, + COUNT whichNode); +bool GenerateDefault_pickupLife (SOLARSYS_STATE *, PLANET_DESC *world, + COUNT whichNode); + +COUNT GenerateDefault_generateArtifact (const SOLARSYS_STATE *, + COUNT whichNode, NODE_INFO *info); +COUNT GenerateDefault_generateRuins (const SOLARSYS_STATE *, + COUNT whichNode, NODE_INFO *info); +bool GenerateDefault_landerReport (SOLARSYS_STATE *); +bool GenerateDefault_landerReportCycle (SOLARSYS_STATE *); + + +extern const GenerateFunctions generateDefaultFunctions; + +#if defined(__cplusplus) +} +#endif + +#endif /* GENDEFAULT_H */ + diff --git a/src/uqm/planets/generate/gendru.c b/src/uqm/planets/generate/gendru.c new file mode 100644 index 0000000..7202010 --- /dev/null +++ b/src/uqm/planets/generate/gendru.c @@ -0,0 +1,169 @@ +//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 "genall.h" +#include "../lander.h" +#include "../planets.h" +#include "../../build.h" +#include "../../comm.h" +#include "../../globdata.h" +#include "../../ipdisp.h" +#include "../../nameref.h" +#include "../../state.h" +#include "libs/mathlib.h" + +#include <string.h> + + +static bool GenerateDruuge_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateDruuge_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GenerateDruuge_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GenerateDruuge_pickupEnergy (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + + +const GenerateFunctions generateDruugeFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateDruuge_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateDruuge_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateDruuge_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateDruuge_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateDruuge_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT angle; + + GenerateDefault_generatePlanets (solarSys); + + memmove (&solarSys->PlanetDesc[1], &solarSys->PlanetDesc[0], + sizeof (solarSys->PlanetDesc[0]) + * solarSys->SunDesc[0].NumPlanets); + ++solarSys->SunDesc[0].NumPlanets; + + solarSys->PlanetDesc[0].data_index = DUST_WORLD; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 50L / 100; + solarSys->PlanetDesc[0].NumPlanets = 0; + angle = HALF_CIRCLE - OCTANT; + solarSys->PlanetDesc[0].location.x = + COSINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].location.y = + SINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].rand_seed = MAKE_DWORD ( + solarSys->PlanetDesc[0].location.x, + solarSys->PlanetDesc[0].location.y); + + return true; +} + +static bool +GenerateDruuge_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + if (StartSphereTracking (DRUUGE_SHIP)) + { + NotifyOthers (DRUUGE_SHIP, IPNL_ALL_CLEAR); + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + CloneShipFragment (DRUUGE_SHIP, + &GLOBAL (npc_built_ship_q), INFINITE_FLEET); + + GLOBAL (CurrentActivity) |= START_INTERPLANETARY; + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); + InitCommunication (DRUUGE_CONVERSATION); + if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))) + { + GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY; + ReinitQueue (&GLOBAL (npc_built_ship_q)); + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + } + return true; + } + else + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable ( + LoadStringTable (DRUUGE_RUINS_STRTAB)); + if (GET_GAME_STATE (ROSY_SPHERE)) + { // Already picked up Rosy Sphere, skip the report + solarSys->SysInfo.PlanetInfo.DiscoveryString = + SetAbsStringTableIndex ( + solarSys->SysInfo.PlanetInfo.DiscoveryString, 1); + } + } + } + + GenerateDefault_generateOrbital (solarSys, world); + + return true; +} + +static bool +GenerateDruuge_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + GenerateDefault_landerReportCycle (solarSys); + + // The artifact can be picked up from any ruin + if (!GET_GAME_STATE (ROSY_SPHERE)) + { // Just picked up the Rosy Sphere from a ruin + SetLanderTakeoff (); + + SET_GAME_STATE (ROSY_SPHERE, 1); + SET_GAME_STATE (ROSY_SPHERE_ON_SHIP, 1); + } + + return false; // do not remove the node + } + + (void) whichNode; + return false; +} + +static COUNT +GenerateDruuge_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + return GenerateDefault_generateRuins (solarSys, whichNode, info); + } + + return 0; +} + diff --git a/src/uqm/planets/generate/genilw.c b/src/uqm/planets/generate/genilw.c new file mode 100644 index 0000000..31a8fc4 --- /dev/null +++ b/src/uqm/planets/generate/genilw.c @@ -0,0 +1,150 @@ +//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 "genall.h" +#include "../planets.h" +#include "../../build.h" +#include "../../comm.h" +#include "../../globdata.h" +#include "../../ipdisp.h" +#include "../../nameref.h" +#include "../../state.h" +#include "libs/mathlib.h" + + +static bool GenerateIlwrath_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateIlwrath_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GenerateIlwrath_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GenerateIlwrath_pickupEnergy (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + + +const GenerateFunctions generateIlwrathFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateIlwrath_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateIlwrath_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateIlwrath_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateIlwrath_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateIlwrath_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT angle; + + GenerateDefault_generatePlanets (solarSys); + + solarSys->PlanetDesc[0].data_index = PRIMORDIAL_WORLD; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 204L / 100; + angle = ARCTAN ( + solarSys->PlanetDesc[0].location.x, + solarSys->PlanetDesc[0].location.y); + solarSys->PlanetDesc[0].location.x = + COSINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].location.y = + SINE (angle, solarSys->PlanetDesc[0].radius); + + return true; +} + +static bool +GenerateIlwrath_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + if (StartSphereTracking (ILWRATH_SHIP)) + { + NotifyOthers (ILWRATH_SHIP, IPNL_ALL_CLEAR); + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + CloneShipFragment (ILWRATH_SHIP, + &GLOBAL (npc_built_ship_q), INFINITE_FLEET); + + GLOBAL (CurrentActivity) |= START_INTERPLANETARY; + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); + InitCommunication (ILWRATH_CONVERSATION); + + if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))) + { + GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY; + ReinitQueue (&GLOBAL (npc_built_ship_q)); + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + } + return true; + } + else + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable ( + LoadGraphic (RUINS_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable (LoadStringTable (RUINS_STRTAB)); + } + } + + GenerateDefault_generateOrbital (solarSys, world); + + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + solarSys->SysInfo.PlanetInfo.Weather = 2; + solarSys->SysInfo.PlanetInfo.Tectonics = 3; + } + + return true; +} + +static COUNT +GenerateIlwrath_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + return GenerateDefault_generateRuins (solarSys, whichNode, info); + } + + return 0; +} + +static bool +GenerateIlwrath_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + // Standard ruins report + GenerateDefault_landerReportCycle (solarSys); + return false; + } + + (void) whichNode; + return false; +} diff --git a/src/uqm/planets/generate/genmel.c b/src/uqm/planets/generate/genmel.c new file mode 100644 index 0000000..27f31b6 --- /dev/null +++ b/src/uqm/planets/generate/genmel.c @@ -0,0 +1,114 @@ +//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 "genall.h" +#include "../../build.h" +#include "../../gendef.h" +#include "../../starmap.h" +#include "../../globdata.h" +#include "../../state.h" +#include "libs/log.h" + + +static bool GenerateMelnorme_initNpcs (SOLARSYS_STATE *solarSys); + +static int SelectMelnormeRefVar (void); +static DWORD GetMelnormeRef (void); +static void SetMelnormeRef (DWORD Ref); + + +const GenerateFunctions generateMelnormeFunctions = { + /* .initNpcs = */ GenerateMelnorme_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateDefault_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateDefault_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateDefault_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateDefault_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateMelnorme_initNpcs (SOLARSYS_STATE *solarSys) +{ + GLOBAL (BattleGroupRef) = GetMelnormeRef (); + if (GLOBAL (BattleGroupRef) == 0) + { + CloneShipFragment (MELNORME_SHIP, &GLOBAL (npc_built_ship_q), 0); + GLOBAL (BattleGroupRef) = PutGroupInfo (GROUPS_ADD_NEW, 1); + ReinitQueue (&GLOBAL (npc_built_ship_q)); + SetMelnormeRef (GLOBAL (BattleGroupRef)); + } + + GenerateDefault_initNpcs (solarSys); + + return true; +} + + +static int +SelectMelnormeRefVar (void) +{ + switch (CurStarDescPtr->Index) + { + case MELNORME0_DEFINED: return MELNORME0_GRPOFFS0; + case MELNORME1_DEFINED: return MELNORME1_GRPOFFS0; + case MELNORME2_DEFINED: return MELNORME2_GRPOFFS0; + case MELNORME3_DEFINED: return MELNORME3_GRPOFFS0; + case MELNORME4_DEFINED: return MELNORME4_GRPOFFS0; + case MELNORME5_DEFINED: return MELNORME5_GRPOFFS0; + case MELNORME6_DEFINED: return MELNORME6_GRPOFFS0; + case MELNORME7_DEFINED: return MELNORME7_GRPOFFS0; + case MELNORME8_DEFINED: return MELNORME8_GRPOFFS0; + default: + return -1; + } +} + +static DWORD +GetMelnormeRef (void) +{ + int RefVar = SelectMelnormeRefVar (); + if (RefVar < 0) + { + log_add (log_Warning, "GetMelnormeRef(): reference unknown"); + return 0; + } + + return GET_GAME_STATE_32 (RefVar); +} + +static void +SetMelnormeRef (DWORD Ref) +{ + int RefVar = SelectMelnormeRefVar (); + if (RefVar < 0) + { + log_add (log_Warning, "SetMelnormeRef(): reference unknown"); + return; + } + + SET_GAME_STATE_32 (RefVar, Ref); +} + diff --git a/src/uqm/planets/generate/genmyc.c b/src/uqm/planets/generate/genmyc.c new file mode 100644 index 0000000..ead32c7 --- /dev/null +++ b/src/uqm/planets/generate/genmyc.c @@ -0,0 +1,286 @@ +//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 "genall.h" +#include "../lander.h" +#include "../planets.h" +#include "../scan.h" +#include "../../build.h" +#include "../../comm.h" +#include "../../gendef.h" +#include "../../starmap.h" +#include "../../globdata.h" +#include "../../ipdisp.h" +#include "../../nameref.h" +#include "../../setup.h" +#include "../../state.h" +#include "libs/mathlib.h" + + +static bool GenerateMycon_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateMycon_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GenerateMycon_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static COUNT GenerateMycon_generateLife (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GenerateMycon_pickupEnergy (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + + +const GenerateFunctions generateMyconFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateMycon_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateMycon_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateMycon_generateEnergy, + /* .generateLife = */ GenerateMycon_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateMycon_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateMycon_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT angle; + + GenerateDefault_generatePlanets (solarSys); + + solarSys->PlanetDesc[0].data_index = SHATTERED_WORLD; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 80L / 100; + if (solarSys->PlanetDesc[0].NumPlanets > 2) + solarSys->PlanetDesc[0].NumPlanets = 2; + angle = ARCTAN ( + solarSys->PlanetDesc[0].location.x, + solarSys->PlanetDesc[0].location.y); + solarSys->PlanetDesc[0].location.x = + COSINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].location.y = + SINE (angle, solarSys->PlanetDesc[0].radius); + + return true; +} + +static bool +GenerateMycon_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + if ((CurStarDescPtr->Index == MYCON_DEFINED + || CurStarDescPtr->Index == SUN_DEVICE_DEFINED) + && StartSphereTracking (MYCON_SHIP)) + { + if (CurStarDescPtr->Index == MYCON_DEFINED + || !GET_GAME_STATE (SUN_DEVICE_UNGUARDED)) + { + NotifyOthers (MYCON_SHIP, IPNL_ALL_CLEAR); + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + if (CurStarDescPtr->Index == MYCON_DEFINED + || !GET_GAME_STATE (MYCON_FELL_FOR_AMBUSH)) + { + CloneShipFragment (MYCON_SHIP, + &GLOBAL (npc_built_ship_q), INFINITE_FLEET); + } + else + { + COUNT i; + + for (i = 0; i < 5; ++i) + CloneShipFragment (MYCON_SHIP, + &GLOBAL (npc_built_ship_q), 0); + } + + GLOBAL (CurrentActivity) |= START_INTERPLANETARY; + if (CurStarDescPtr->Index == MYCON_DEFINED) + { + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); + } + else + { + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 6); + } + InitCommunication (MYCON_CONVERSATION); + + if (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)) + return true; + + { + BOOLEAN MyconSurvivors; + + MyconSurvivors = + GetHeadLink (&GLOBAL (npc_built_ship_q)) != 0; + + GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY; + ReinitQueue (&GLOBAL (npc_built_ship_q)); + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + + if (MyconSurvivors) + return true; + + SET_GAME_STATE (SUN_DEVICE_UNGUARDED, 1); + RepairSISBorder (); + } + } + } + + switch (CurStarDescPtr->Index) + { + case SUN_DEVICE_DEFINED: + if (!GET_GAME_STATE (SUN_DEVICE)) + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable ( + LoadGraphic (SUN_DEVICE_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable ( + LoadStringTable (SUN_DEVICE_STRTAB)); + } + break; + case EGG_CASE0_DEFINED: + case EGG_CASE1_DEFINED: + case EGG_CASE2_DEFINED: + if (GET_GAME_STATE (KNOW_ABOUT_SHATTERED) == 0) + SET_GAME_STATE (KNOW_ABOUT_SHATTERED, 1); + + if (!isNodeRetrieved (&solarSys->SysInfo.PlanetInfo, + ENERGY_SCAN, 0)) + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable ( + LoadGraphic (EGG_CASE_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable ( + LoadStringTable (EGG_CASE_STRTAB)); + } + break; + } + } + + GenerateDefault_generateOrbital (solarSys, world); + return true; +} + +static COUNT +GenerateMycon_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (CurStarDescPtr->Index == SUN_DEVICE_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + // This check is redundant since the retrieval bit will keep the + // node from showing up again + if (GET_GAME_STATE (SUN_DEVICE)) + { // already picked up + return 0; + } + + return GenerateDefault_generateArtifact (solarSys, whichNode, info); + } + + if ((CurStarDescPtr->Index == EGG_CASE0_DEFINED + || CurStarDescPtr->Index == EGG_CASE1_DEFINED + || CurStarDescPtr->Index == EGG_CASE2_DEFINED) + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + // This check is redundant since the retrieval bit will keep the + // node from showing up again + // XXX: DiscoveryString is set by generateOrbital() only when the + // node has not been picked up yet + if (!solarSys->SysInfo.PlanetInfo.DiscoveryString) + { // already picked up + return 0; + } + + return GenerateDefault_generateArtifact (solarSys, whichNode, info); + } + + return 0; +} + +static bool +GenerateMycon_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (CurStarDescPtr->Index == SUN_DEVICE_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + assert (!GET_GAME_STATE (SUN_DEVICE) && whichNode == 0); + + GenerateDefault_landerReport (solarSys); + SetLanderTakeoff (); + + SET_GAME_STATE (SUN_DEVICE, 1); + SET_GAME_STATE (SUN_DEVICE_ON_SHIP, 1); + SET_GAME_STATE (MYCON_VISITS, 0); + + return true; // picked up + } + + if ((CurStarDescPtr->Index == EGG_CASE0_DEFINED + || CurStarDescPtr->Index == EGG_CASE1_DEFINED + || CurStarDescPtr->Index == EGG_CASE2_DEFINED) + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + assert (whichNode == 0); + + GenerateDefault_landerReport (solarSys); + SetLanderTakeoff (); + + switch (CurStarDescPtr->Index) + { + case EGG_CASE0_DEFINED: + SET_GAME_STATE (EGG_CASE0_ON_SHIP, 1); + break; + case EGG_CASE1_DEFINED: + SET_GAME_STATE (EGG_CASE1_ON_SHIP, 1); + break; + case EGG_CASE2_DEFINED: + SET_GAME_STATE (EGG_CASE2_ON_SHIP, 1); + break; + } + + return true; // picked up + } + + (void) whichNode; + return false; +} + +static COUNT +GenerateMycon_generateLife (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + // Gee, I wonder why there isn't any life in Mycon systems... + (void) whichNode; + (void) solarSys; + (void) world; + (void) info; + return 0; +} + diff --git a/src/uqm/planets/generate/genorz.c b/src/uqm/planets/generate/genorz.c new file mode 100644 index 0000000..a50f318 --- /dev/null +++ b/src/uqm/planets/generate/genorz.c @@ -0,0 +1,222 @@ +//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 "genall.h" +#include "../lander.h" +#include "../planets.h" +#include "../../build.h" +#include "../../comm.h" +#include "../../gendef.h" +#include "../../starmap.h" +#include "../../globdata.h" +#include "../../ipdisp.h" +#include "../../nameref.h" +#include "../../setup.h" +#include "../../state.h" +#include "libs/mathlib.h" + + +static bool GenerateOrz_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateOrz_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GenerateOrz_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GenerateOrz_pickupEnergy (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + + +const GenerateFunctions generateOrzFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateOrz_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateOrz_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateOrz_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateOrz_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateOrz_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT angle; + + GenerateDefault_generatePlanets (solarSys); + + if (CurStarDescPtr->Index == ORZ_DEFINED) + { + solarSys->PlanetDesc[0].data_index = WATER_WORLD; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 156L / 100; + solarSys->PlanetDesc[0].NumPlanets = 0; + angle = ARCTAN (solarSys->PlanetDesc[0].location.x, + solarSys->PlanetDesc[0].location.y); + solarSys->PlanetDesc[0].location.x = + COSINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].location.y = + SINE (angle, solarSys->PlanetDesc[0].radius); + } + + return true; +} + +static bool +GenerateOrz_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if ((CurStarDescPtr->Index == ORZ_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + || (CurStarDescPtr->Index == TAALO_PROTECTOR_DEFINED + && matchWorld (solarSys, world, 1, 2) + && !GET_GAME_STATE (TAALO_PROTECTOR))) + { + COUNT i; + + if ((CurStarDescPtr->Index == ORZ_DEFINED + || !GET_GAME_STATE (TAALO_UNPROTECTED)) + && StartSphereTracking (ORZ_SHIP)) + { + NotifyOthers (ORZ_SHIP, IPNL_ALL_CLEAR); + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + if (CurStarDescPtr->Index == ORZ_DEFINED) + { + CloneShipFragment (ORZ_SHIP, + &GLOBAL (npc_built_ship_q), INFINITE_FLEET); + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); + } + else + { + for (i = 0; i < 14; ++i) + { + CloneShipFragment (ORZ_SHIP, + &GLOBAL (npc_built_ship_q), 0); + } + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 6); + } + GLOBAL (CurrentActivity) |= START_INTERPLANETARY; + InitCommunication (ORZ_CONVERSATION); + + if (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)) + return true; + + { + BOOLEAN OrzSurvivors; + + OrzSurvivors = GetHeadLink (&GLOBAL (npc_built_ship_q)) + && (CurStarDescPtr->Index == ORZ_DEFINED + || !GET_GAME_STATE (TAALO_UNPROTECTED)); + + GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY; + ReinitQueue (&GLOBAL (npc_built_ship_q)); + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + + if (OrzSurvivors) + return true; + + RepairSISBorder (); + } + } + + SET_GAME_STATE (TAALO_UNPROTECTED, 1); + if (CurStarDescPtr->Index == TAALO_PROTECTOR_DEFINED) + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable ( + LoadGraphic (TAALO_DEVICE_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable ( + LoadStringTable (TAALO_DEVICE_STRTAB)); + } + else + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable (LoadStringTable (RUINS_STRTAB)); + } + } + + GenerateDefault_generateOrbital (solarSys, world); + + return true; +} + +static COUNT +GenerateOrz_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (CurStarDescPtr->Index == TAALO_PROTECTOR_DEFINED + && matchWorld (solarSys, world, 1, 2)) + { + // This check is redundant since the retrieval bit will keep the + // node from showing up again + if (GET_GAME_STATE (TAALO_PROTECTOR)) + { // already picked up + return 0; + } + + return GenerateDefault_generateArtifact (solarSys, whichNode, info); + } + + if (CurStarDescPtr->Index == ORZ_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + return GenerateDefault_generateRuins (solarSys, whichNode, info); + } + + return 0; +} + +static bool +GenerateOrz_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (CurStarDescPtr->Index == TAALO_PROTECTOR_DEFINED + && matchWorld (solarSys, world, 1, 2)) + { + assert (!GET_GAME_STATE (TAALO_PROTECTOR) && whichNode == 0); + + GenerateDefault_landerReport (solarSys); + SetLanderTakeoff (); + + SET_GAME_STATE (TAALO_PROTECTOR, 1); + SET_GAME_STATE (TAALO_PROTECTOR_ON_SHIP, 1); + + return true; // picked up + } + + if (CurStarDescPtr->Index == ORZ_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + // Standard ruins report + GenerateDefault_landerReportCycle (solarSys); + return false; + } + + (void) whichNode; + return false; +} diff --git a/src/uqm/planets/generate/genpet.c b/src/uqm/planets/generate/genpet.c new file mode 100644 index 0000000..4c5515c --- /dev/null +++ b/src/uqm/planets/generate/genpet.c @@ -0,0 +1,257 @@ +//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 "genall.h" +#include "../planets.h" +#include "../../build.h" +#include "../../comm.h" +#include "../../encount.h" +#include "../../starmap.h" +#include "../../globdata.h" +#include "../../ipdisp.h" +#include "../../nameref.h" +#include "../../setup.h" +#include "../../state.h" +#include "libs/mathlib.h" + + +static bool GenerateTalkingPet_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateTalkingPet_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GenerateTalkingPet_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GenerateTalkingPet_pickupEnergy (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + +static void ZapToUrquanEncounter (void); + + +const GenerateFunctions generateTalkingPetFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateTalkingPet_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateTalkingPet_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateTalkingPet_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateTalkingPet_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateTalkingPet_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT angle; + + GenerateDefault_generatePlanets (solarSys); + + solarSys->PlanetDesc[0].data_index = TELLURIC_WORLD; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 204L / 100; + angle = ARCTAN (solarSys->PlanetDesc[0].location.x, + solarSys->PlanetDesc[0].location.y); + solarSys->PlanetDesc[0].location.x = + COSINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].location.y = + SINE (angle, solarSys->PlanetDesc[0].radius); + + return true; +} + +static bool +GenerateTalkingPet_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET) + && (GET_GAME_STATE (UMGAH_ZOMBIE_BLOBBIES) + || !GET_GAME_STATE (TALKING_PET) + || StartSphereTracking (UMGAH_SHIP))) + { + NotifyOthers (UMGAH_SHIP, IPNL_ALL_CLEAR); + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + if (StartSphereTracking (UMGAH_SHIP)) + { + GLOBAL (CurrentActivity) |= START_INTERPLANETARY; + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); + if (!GET_GAME_STATE (UMGAH_ZOMBIE_BLOBBIES)) + { + CloneShipFragment (UMGAH_SHIP, + &GLOBAL (npc_built_ship_q), INFINITE_FLEET); + InitCommunication (UMGAH_CONVERSATION); + } + else + { + COUNT i; + + for (i = 0; i < 10; ++i) + { + CloneShipFragment (UMGAH_SHIP, + &GLOBAL (npc_built_ship_q), 0); + } + InitCommunication (TALKING_PET_CONVERSATION); + } + } + + if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))) + { + BOOLEAN UmgahSurvivors; + + UmgahSurvivors = GetHeadLink ( + &GLOBAL (npc_built_ship_q)) != 0; + GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY; + + if (GET_GAME_STATE (PLAYER_HYPNOTIZED)) + ZapToUrquanEncounter (); + else if (GET_GAME_STATE (UMGAH_ZOMBIE_BLOBBIES) + && !UmgahSurvivors) + { + // Defeated the zombie fleet. + InitCommunication (TALKING_PET_CONVERSATION); + } + else if (!(StartSphereTracking (UMGAH_SHIP))) + { + // The Kohr-Ah have destroyed the Umgah, but the + // talking pet survived. + InitCommunication (TALKING_PET_CONVERSATION); + } + + ReinitQueue (&GLOBAL (npc_built_ship_q)); + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + } + + return true; + } + + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable (LoadStringTable (RUINS_STRTAB)); + } + + GenerateDefault_generateOrbital (solarSys, world); + + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + solarSys->SysInfo.PlanetInfo.Weather = 0; + + return true; +} + +static COUNT +GenerateTalkingPet_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + return GenerateDefault_generateRuins (solarSys, whichNode, info); + } + + return 0; +} + +static bool +GenerateTalkingPet_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + // Standard ruins report + GenerateDefault_landerReportCycle (solarSys); + return false; + } + + (void) whichNode; + return false; +} + +static void +ZapToUrquanEncounter (void) +{ + HENCOUNTER hEncounter; + + if ((hEncounter = AllocEncounter ()) || (hEncounter = GetHeadEncounter ())) + { + SIZE dx, dy; + ENCOUNTER *EncounterPtr; + HFLEETINFO hStarShip; + FLEET_INFO *TemplatePtr; + BRIEF_SHIP_INFO *BSIPtr; + + LockEncounter (hEncounter, &EncounterPtr); + + if (hEncounter == GetHeadEncounter ()) + RemoveEncounter (hEncounter); + memset (EncounterPtr, 0, sizeof (*EncounterPtr)); + + InsertEncounter (hEncounter, GetHeadEncounter ()); + + hStarShip = GetStarShipFromIndex (&GLOBAL (avail_race_q), URQUAN_SHIP); + TemplatePtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip); + EncounterPtr->origin = TemplatePtr->loc; + EncounterPtr->radius = TemplatePtr->actual_strength; + EncounterPtr->race_id = URQUAN_SHIP; + EncounterPtr->num_ships = 1; + EncounterPtr->flags = ONE_SHOT_ENCOUNTER; + BSIPtr = &EncounterPtr->ShipList[0]; + BSIPtr->race_id = URQUAN_SHIP; + BSIPtr->crew_level = TemplatePtr->crew_level; + BSIPtr->max_crew = TemplatePtr->max_crew; + BSIPtr->max_energy = TemplatePtr->max_energy; + EncounterPtr->loc_pt.x = 5288; + EncounterPtr->loc_pt.y = 4892; + EncounterPtr->log_x = UNIVERSE_TO_LOGX (EncounterPtr->loc_pt.x); + EncounterPtr->log_y = UNIVERSE_TO_LOGY (EncounterPtr->loc_pt.y); + GLOBAL_SIS (log_x) = EncounterPtr->log_x; + GLOBAL_SIS (log_y) = EncounterPtr->log_y; + UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip); + + { +#define LOST_DAYS 15 + SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND * 2)); + MoveGameClockDays (LOST_DAYS); + } + + GLOBAL (CurrentActivity) = MAKE_WORD (IN_HYPERSPACE, 0) | START_ENCOUNTER; + + dx = CurStarDescPtr->star_pt.x - EncounterPtr->loc_pt.x; + dy = CurStarDescPtr->star_pt.y - EncounterPtr->loc_pt.y; + dx = (SIZE)square_root ((long)dx * dx + (long)dy * dy) + + (FUEL_TANK_SCALE >> 1); + + DeltaSISGauges (0, -dx, 0); + if (GLOBAL_SIS (FuelOnBoard) < 5 * FUEL_TANK_SCALE) + { + dx = ((5 + ((COUNT)TFB_Random () % 5)) * FUEL_TANK_SCALE) + - (SIZE)GLOBAL_SIS (FuelOnBoard); + DeltaSISGauges (0, dx, 0); + } + DrawSISMessage (NULL); + DrawHyperCoords (EncounterPtr->loc_pt); + + UnlockEncounter (hEncounter); + } +} + diff --git a/src/uqm/planets/generate/genpku.c b/src/uqm/planets/generate/genpku.c new file mode 100644 index 0000000..64b9965 --- /dev/null +++ b/src/uqm/planets/generate/genpku.c @@ -0,0 +1,159 @@ +//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 "genall.h" +#include "../lander.h" +#include "../planets.h" +#include "../../build.h" +#include "../../comm.h" +#include "../../globdata.h" +#include "../../ipdisp.h" +#include "../../nameref.h" +#include "../../state.h" +#include "libs/mathlib.h" + + +static bool GeneratePkunk_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GeneratePkunk_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GeneratePkunk_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GeneratePkunk_pickupEnergy (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + + +const GenerateFunctions generatePkunkFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GeneratePkunk_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GeneratePkunk_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GeneratePkunk_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GeneratePkunk_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GeneratePkunk_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT angle; + + GenerateDefault_generatePlanets (solarSys); + + solarSys->PlanetDesc[0].data_index = WATER_WORLD; + solarSys->PlanetDesc[0].NumPlanets = 1; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 104L / 100; + angle = ARCTAN (solarSys->PlanetDesc[0].location.x, + solarSys->PlanetDesc[0].location.y); + solarSys->PlanetDesc[0].location.x = + COSINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].location.y = + SINE (angle, solarSys->PlanetDesc[0].radius); + + return true; +} + +static bool +GeneratePkunk_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + if (StartSphereTracking (PKUNK_SHIP)) + { + NotifyOthers (PKUNK_SHIP, IPNL_ALL_CLEAR); + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + CloneShipFragment (PKUNK_SHIP, + &GLOBAL (npc_built_ship_q), INFINITE_FLEET); + + GLOBAL (CurrentActivity) |= START_INTERPLANETARY; + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); + InitCommunication (PKUNK_CONVERSATION); + + if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))) + { + GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY; + ReinitQueue (&GLOBAL (npc_built_ship_q)); + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + } + return true; + } + else + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable (LoadStringTable (PKUNK_RUINS_STRTAB)); + if (GET_GAME_STATE (CLEAR_SPINDLE)) + { // Already picked up the Clear Spindle, skip the report + solarSys->SysInfo.PlanetInfo.DiscoveryString = + SetAbsStringTableIndex ( + solarSys->SysInfo.PlanetInfo.DiscoveryString, 1); + } + } + } + + GenerateDefault_generateOrbital (solarSys, world); + return true; +} + +static bool +GeneratePkunk_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + GenerateDefault_landerReportCycle (solarSys); + + // The artifact can be picked up from any ruin + if (!GET_GAME_STATE (CLEAR_SPINDLE)) + { // Just picked up the Clear Spindle from a ruin + SetLanderTakeoff (); + + SET_GAME_STATE (CLEAR_SPINDLE, 1); + SET_GAME_STATE (CLEAR_SPINDLE_ON_SHIP, 1); + } + + return false; // do not remove the node + } + + (void) whichNode; + return false; +} + +static COUNT +GeneratePkunk_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + return GenerateDefault_generateRuins (solarSys, whichNode, info); + } + + return 0; +} + diff --git a/src/uqm/planets/generate/genrain.c b/src/uqm/planets/generate/genrain.c new file mode 100644 index 0000000..c149b29 --- /dev/null +++ b/src/uqm/planets/generate/genrain.c @@ -0,0 +1,102 @@ +//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 "genall.h" +#include "../planets.h" +#include "../../gendef.h" +#include "../../starmap.h" +#include "../../globdata.h" +#include "libs/mathlib.h" + + +static bool GenerateRainbowWorld_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateRainbowWorld_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); + + +const GenerateFunctions generateRainbowWorldFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateRainbowWorld_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateRainbowWorld_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateDefault_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateDefault_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateRainbowWorld_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT angle; + + GenerateDefault_generatePlanets (solarSys); + + solarSys->PlanetDesc[0].data_index = RAINBOW_WORLD; + solarSys->PlanetDesc[0].NumPlanets = 0; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 50L / 100; + angle = ARCTAN (solarSys->PlanetDesc[0].location.x, + solarSys->PlanetDesc[0].location.y); + if (angle <= QUADRANT) + angle += QUADRANT; + else if (angle >= FULL_CIRCLE - QUADRANT) + angle -= QUADRANT; + solarSys->PlanetDesc[0].location.x = + COSINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].location.y = + SINE (angle, solarSys->PlanetDesc[0].radius); + + return true; +} + +static bool +GenerateRainbowWorld_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + BYTE which_rainbow; + UWORD rainbow_mask; + STAR_DESC *SDPtr; + + rainbow_mask = MAKE_WORD ( + GET_GAME_STATE (RAINBOW_WORLD0), + GET_GAME_STATE (RAINBOW_WORLD1)); + + which_rainbow = 0; + SDPtr = &star_array[0]; + while (SDPtr != CurStarDescPtr) + { + if (SDPtr->Index == RAINBOW_DEFINED) + ++which_rainbow; + ++SDPtr; + } + rainbow_mask |= 1 << which_rainbow; + SET_GAME_STATE (RAINBOW_WORLD0, LOBYTE (rainbow_mask)); + SET_GAME_STATE (RAINBOW_WORLD1, HIBYTE (rainbow_mask)); + } + + GenerateDefault_generateOrbital (solarSys, world); + return true; +} + diff --git a/src/uqm/planets/generate/gensam.c b/src/uqm/planets/generate/gensam.c new file mode 100644 index 0000000..b2398b3 --- /dev/null +++ b/src/uqm/planets/generate/gensam.c @@ -0,0 +1,324 @@ +//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 "genall.h" +#include "../planets.h" +#include "../../build.h" +#include "../../comm.h" +#include "../../encount.h" +#include "../../globdata.h" +#include "../../grpinfo.h" +#include "../../state.h" +#include "libs/mathlib.h" + + +static bool GenerateSaMatra_initNpcs (SOLARSYS_STATE *solarSys); +static bool GenerateSaMatra_reinitNpcs (SOLARSYS_STATE *solarSys); +static bool GenerateSaMatra_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateSaMatra_generateMoons (SOLARSYS_STATE *solarSys, + PLANET_DESC *planet); +static bool GenerateSaMatra_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); + +static void BuildUrquanGuard (SOLARSYS_STATE *solarSys); + + +const GenerateFunctions generateSaMatraFunctions = { + /* .initNpcs = */ GenerateSaMatra_initNpcs, + /* .reinitNpcs = */ GenerateSaMatra_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateSaMatra_generatePlanets, + /* .generateMoons = */ GenerateSaMatra_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateSaMatra_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateDefault_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateDefault_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateSaMatra_initNpcs (SOLARSYS_STATE *solarSys) +{ + if (!GET_GAME_STATE (URQUAN_MESSED_UP)) + { + BuildUrquanGuard (solarSys); + } + else + { // Exorcise Ur-Quan ghosts upon system reentry + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + // wipe out the group + } + + (void) solarSys; + return true; +} + +static bool +GenerateSaMatra_reinitNpcs (SOLARSYS_STATE *solarSys) +{ + BOOLEAN GuardEngaged; + HIPGROUP hGroup; + HIPGROUP hNextGroup; + + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + EncounterGroup = 0; + EncounterRace = -1; + // Do not want guards to chase the player + + GuardEngaged = FALSE; + for (hGroup = GetHeadLink (&GLOBAL (ip_group_q)); + hGroup; hGroup = hNextGroup) + { + IP_GROUP *GroupPtr; + + GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup); + hNextGroup = _GetSuccLink (GroupPtr); + + if (GET_GAME_STATE (URQUAN_MESSED_UP)) + { + GroupPtr->task &= REFORM_GROUP; + GroupPtr->task |= FLEE | IGNORE_FLAGSHIP; + GroupPtr->dest_loc = 0; + } + else if (GroupPtr->task & REFORM_GROUP) + { + // REFORM_GROUP was set in ipdisp.c:ip_group_collision + // during a collision with the flagship. + GroupPtr->task &= ~REFORM_GROUP; + GroupPtr->group_counter = 0; + + GuardEngaged = TRUE; + } + + UnlockIpGroup (&GLOBAL (ip_group_q), hGroup); + } + + if (GuardEngaged) + { + COUNT angle; + POINT org; + + org = planetOuterLocation (4); + angle = ARCTAN (GLOBAL (ip_location.x) - org.x, + GLOBAL (ip_location.y) - org.y); + GLOBAL (ip_location.x) = org.x + COSINE (angle, 3000); + GLOBAL (ip_location.y) = org.y + SINE (angle, 3000); + XFormIPLoc (&GLOBAL (ip_location), + &GLOBAL (ShipStamp.origin), TRUE); + } + + (void) solarSys; + return true; +} + +static bool +GenerateSaMatra_generatePlanets (SOLARSYS_STATE *solarSys) +{ + GenerateDefault_generatePlanets (solarSys); + solarSys->PlanetDesc[4].NumPlanets = 1; + return true; +} + +static bool +GenerateSaMatra_generateMoons (SOLARSYS_STATE *solarSys, PLANET_DESC *planet) +{ + GenerateDefault_generateMoons (solarSys, planet); + + if (matchWorld (solarSys, planet, 4, MATCH_PLANET)) + { + COUNT angle; + DWORD rand_val; + + solarSys->MoonDesc[0].data_index = SA_MATRA; + solarSys->MoonDesc[0].radius = MIN_MOON_RADIUS + (2 * MOON_DELTA); + rand_val = RandomContext_Random (SysGenRNG); + angle = NORMALIZE_ANGLE (LOWORD (rand_val)); + solarSys->MoonDesc[0].location.x = + COSINE (angle, solarSys->MoonDesc[0].radius); + solarSys->MoonDesc[0].location.y = + SINE (angle, solarSys->MoonDesc[0].radius); + } + + return true; +} + +static bool +GenerateSaMatra_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + /* Samatra */ + if (matchWorld (solarSys, world, 4, 0)) + { + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + if (!GET_GAME_STATE (URQUAN_MESSED_UP)) + { + CloneShipFragment (!GET_GAME_STATE (KOHR_AH_FRENZY) ? + URQUAN_SHIP : BLACK_URQUAN_SHIP, + &GLOBAL (npc_built_ship_q), INFINITE_FLEET); + } + else + { +#define URQUAN_REMNANTS 3 + BYTE i; + + for (i = 0; i < URQUAN_REMNANTS; ++i) + { + CloneShipFragment (URQUAN_SHIP, + &GLOBAL (npc_built_ship_q), 0); + CloneShipFragment (BLACK_URQUAN_SHIP, + &GLOBAL (npc_built_ship_q), 0); + } + } + + GLOBAL (CurrentActivity) |= START_INTERPLANETARY; + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); + SET_GAME_STATE (URQUAN_PROTECTING_SAMATRA, 1); + InitCommunication (URQUAN_CONVERSATION); + + if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))) + { + BOOLEAN UrquanSurvivors; + + UrquanSurvivors = GetHeadLink (&GLOBAL (npc_built_ship_q)) != 0; + + GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY; + ReinitQueue (&GLOBAL (npc_built_ship_q)); + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + if (UrquanSurvivors) + { + SET_GAME_STATE (URQUAN_PROTECTING_SAMATRA, 0); + } + else + { + EncounterGroup = 0; + EncounterRace = -1; + GLOBAL (CurrentActivity) = IN_LAST_BATTLE | START_ENCOUNTER; + if (GET_GAME_STATE (YEHAT_CIVIL_WAR) + && StartSphereTracking (YEHAT_SHIP) + && EscortFeasibilityStudy (YEHAT_REBEL_SHIP)) + InitCommunication (YEHAT_REBEL_CONVERSATION); + } + } + return true; + } + + GenerateDefault_generateOrbital (solarSys, world); + return true; +} + +static void +BuildUrquanGuard (SOLARSYS_STATE *solarSys) +{ + BYTE ship1, ship2; + BYTE b0, b1; + POINT org; + HIPGROUP hGroup, hNextGroup; + + GLOBAL (BattleGroupRef) = GET_GAME_STATE_32 (SAMATRA_GRPOFFS0); + + if (!GET_GAME_STATE (KOHR_AH_FRENZY)) + { + ship1 = URQUAN_SHIP; + ship2 = BLACK_URQUAN_SHIP; + } + else + { + ship1 = BLACK_URQUAN_SHIP; + ship2 = URQUAN_SHIP; + } + + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + for (b0 = 0; b0 < MAX_SHIPS_PER_SIDE; ++b0) + CloneShipFragment (ship1, &GLOBAL (npc_built_ship_q), 0); + + if (GLOBAL (BattleGroupRef) == 0) + { + GLOBAL (BattleGroupRef) = PutGroupInfo (GROUPS_ADD_NEW, 1); + SET_GAME_STATE_32 (SAMATRA_GRPOFFS0, GLOBAL (BattleGroupRef)); + } + +#define NUM_URQUAN_GUARDS0 12 + for (b0 = 1; b0 <= NUM_URQUAN_GUARDS0; ++b0) + PutGroupInfo (GLOBAL (BattleGroupRef), b0); + + ReinitQueue (&GLOBAL (npc_built_ship_q)); + for (b0 = 0; b0 < MAX_SHIPS_PER_SIDE; ++b0) + CloneShipFragment (ship2, &GLOBAL (npc_built_ship_q), 0); + +#define NUM_URQUAN_GUARDS1 4 + for (b0 = 1; b0 <= NUM_URQUAN_GUARDS1; ++b0) + PutGroupInfo (GLOBAL (BattleGroupRef), + (BYTE)(NUM_URQUAN_GUARDS0 + b0)); + + ReinitQueue (&GLOBAL (npc_built_ship_q)); + + GetGroupInfo (GLOBAL (BattleGroupRef), GROUP_INIT_IP); + + org = planetOuterLocation (4); + hGroup = GetHeadLink (&GLOBAL (ip_group_q)); + for (b0 = 0, b1 = 0; + b0 < NUM_URQUAN_GUARDS0; + ++b0, b1 += FULL_CIRCLE / (NUM_URQUAN_GUARDS0 + NUM_URQUAN_GUARDS1)) + { + IP_GROUP *GroupPtr; + + if (b1 % (FULL_CIRCLE / NUM_URQUAN_GUARDS1) == 0) + b1 += FULL_CIRCLE / (NUM_URQUAN_GUARDS0 + NUM_URQUAN_GUARDS1); + + GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup); + hNextGroup = _GetSuccLink (GroupPtr); + GroupPtr->task = ON_STATION | IGNORE_FLAGSHIP; + GroupPtr->sys_loc = 0; + GroupPtr->dest_loc = 4 + 1; + GroupPtr->orbit_pos = NORMALIZE_FACING (ANGLE_TO_FACING (b1)); + GroupPtr->group_counter = 0; + GroupPtr->loc.x = org.x + COSINE (b1, STATION_RADIUS); + GroupPtr->loc.y = org.y + SINE (b1, STATION_RADIUS); + UnlockIpGroup (&GLOBAL (ip_group_q), hGroup); + hGroup = hNextGroup; + } + + for (b0 = 0, b1 = 0; + b0 < NUM_URQUAN_GUARDS1; + ++b0, b1 += FULL_CIRCLE / NUM_URQUAN_GUARDS1) + { + IP_GROUP *GroupPtr; + + GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup); + hNextGroup = _GetSuccLink (GroupPtr); + GroupPtr->task = ON_STATION | IGNORE_FLAGSHIP; + GroupPtr->sys_loc = 0; + GroupPtr->dest_loc = 4 + 1; + GroupPtr->orbit_pos = NORMALIZE_FACING (ANGLE_TO_FACING (b1)); + GroupPtr->group_counter = 0; + GroupPtr->loc.x = org.x + COSINE (b1, STATION_RADIUS); + GroupPtr->loc.y = org.y + SINE (b1, STATION_RADIUS); + UnlockIpGroup (&GLOBAL (ip_group_q), hGroup); + hGroup = hNextGroup; + } + + (void) solarSys; +} + diff --git a/src/uqm/planets/generate/genshof.c b/src/uqm/planets/generate/genshof.c new file mode 100644 index 0000000..7025f6a --- /dev/null +++ b/src/uqm/planets/generate/genshof.c @@ -0,0 +1,178 @@ +//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 "genall.h" +#include "../../build.h" +#include "../../globdata.h" +#include "../../grpinfo.h" +#include "../../state.h" +#include "../planets.h" + + +static bool GenerateShofixti_initNpcs (SOLARSYS_STATE *solarSys); +static bool GenerateShofixti_reinitNpcs (SOLARSYS_STATE *solarSys); +static bool GenerateShofixti_uninitNpcs (SOLARSYS_STATE *solarSys); +static bool GenerateShofixti_generatePlanets (SOLARSYS_STATE *solarSys); + +static void check_old_shofixti (void); + + +const GenerateFunctions generateShofixtiFunctions = { + /* .initNpcs = */ GenerateShofixti_initNpcs, + /* .reinitNpcs = */ GenerateShofixti_reinitNpcs, + /* .uninitNpcs = */ GenerateShofixti_uninitNpcs, + /* .generatePlanets = */ GenerateShofixti_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateDefault_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateDefault_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateDefault_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateShofixti_initNpcs (SOLARSYS_STATE *solarSys) +{ + if (!GET_GAME_STATE (SHOFIXTI_RECRUITED) + && (!GET_GAME_STATE (SHOFIXTI_KIA) + || (!GET_GAME_STATE (SHOFIXTI_BRO_KIA) + && GET_GAME_STATE (MAIDENS_ON_SHIP)))) + { + GLOBAL (BattleGroupRef) = GET_GAME_STATE_32 (SHOFIXTI_GRPOFFS0); + if (GLOBAL (BattleGroupRef) == 0 + || !GetGroupInfo (GLOBAL (BattleGroupRef), GROUP_INIT_IP)) + { + HSHIPFRAG hStarShip; + + if (GLOBAL (BattleGroupRef) == 0) + GLOBAL (BattleGroupRef) = ~0L; + + hStarShip = CloneShipFragment (SHOFIXTI_SHIP, + &GLOBAL (npc_built_ship_q), 1); + if (hStarShip) + { /* Set old Shofixti name; his brother if Tanaka died */ + SHIP_FRAGMENT *FragPtr = LockShipFrag ( + &GLOBAL (npc_built_ship_q), hStarShip); + /* Name Tanaka or Katana (+1) */ + FragPtr->captains_name_index = + NAME_OFFSET + NUM_CAPTAINS_NAMES + + (GET_GAME_STATE (SHOFIXTI_KIA) & 1); + UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip); + } + + GLOBAL (BattleGroupRef) = PutGroupInfo ( + GLOBAL (BattleGroupRef), 1); + ReinitQueue (&GLOBAL (npc_built_ship_q)); + SET_GAME_STATE_32 (SHOFIXTI_GRPOFFS0, GLOBAL (BattleGroupRef)); + } + } + + // This was originally a fallthrough to REINIT_NPCS. + // XXX: is the call to check_old_shofixti() needed? + GenerateDefault_initNpcs (solarSys); + check_old_shofixti (); + + return true; +} + +static bool +GenerateShofixti_reinitNpcs (SOLARSYS_STATE *solarSys) +{ + GenerateDefault_reinitNpcs (solarSys); + check_old_shofixti (); + + (void) solarSys; + return true; +} + +static bool +GenerateShofixti_uninitNpcs (SOLARSYS_STATE *solarSys) +{ + if (GLOBAL (BattleGroupRef) + && !GET_GAME_STATE (SHOFIXTI_RECRUITED) + && GetHeadLink (&GLOBAL (ip_group_q)) == 0) + { + if (!GET_GAME_STATE (SHOFIXTI_KIA)) + { + SET_GAME_STATE (SHOFIXTI_KIA, 1); + SET_GAME_STATE (SHOFIXTI_VISITS, 0); + } + else if (GET_GAME_STATE (MAIDENS_ON_SHIP)) + { + SET_GAME_STATE (SHOFIXTI_BRO_KIA, 1); + } + } + + GenerateDefault_uninitNpcs (solarSys); + return true; +} + +static bool +GenerateShofixti_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT i; + +#define NUM_PLANETS 6 + solarSys->SunDesc[0].NumPlanets = NUM_PLANETS; + for (i = 0; i < NUM_PLANETS; ++i) + { + PLANET_DESC *pCurDesc = &solarSys->PlanetDesc[i]; + + pCurDesc->NumPlanets = 0; + if (i < (NUM_PLANETS >> 1)) + pCurDesc->data_index = SELENIC_WORLD; + else + pCurDesc->data_index = METAL_WORLD; + } + + FillOrbits (solarSys, NUM_PLANETS, solarSys->PlanetDesc, TRUE); + + return true; +} + + +static void +check_old_shofixti (void) +{ + HIPGROUP hGroup; + IP_GROUP *GroupPtr; + + if (!GLOBAL (BattleGroupRef)) + return; // nothing to check + + hGroup = GetHeadLink (&GLOBAL (ip_group_q)); + if (!hGroup) + return; // still nothing to check + + GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup); + // REFORM_GROUP was set in ipdisp.c:ip_group_collision() + // during a collision with the flagship. + if (GroupPtr->race_id == SHOFIXTI_SHIP + && (GroupPtr->task & REFORM_GROUP) + && GET_GAME_STATE (SHOFIXTI_RECRUITED)) + { + GroupPtr->task = FLEE | IGNORE_FLAGSHIP | REFORM_GROUP; + GroupPtr->dest_loc = 0; + } + UnlockIpGroup (&GLOBAL (ip_group_q), hGroup); +} + diff --git a/src/uqm/planets/generate/gensly.c b/src/uqm/planets/generate/gensly.c new file mode 100644 index 0000000..48ed100 --- /dev/null +++ b/src/uqm/planets/generate/gensly.c @@ -0,0 +1,70 @@ +//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 "genall.h" +#include "../planets.h" +#include "../../comm.h" + + +static bool GenerateSlylandro_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateSlylandro_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); + + +const GenerateFunctions generateSlylandroFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateSlylandro_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateSlylandro_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateDefault_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateDefault_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateSlylandro_generatePlanets (SOLARSYS_STATE *solarSys) +{ + GenerateDefault_generatePlanets (solarSys); + + solarSys->PlanetDesc[3].data_index = RED_GAS_GIANT; + solarSys->PlanetDesc[3].NumPlanets = 1; + + return true; +} + +static bool +GenerateSlylandro_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world) +{ + if (matchWorld (solarSys, world, 3, MATCH_PLANET)) + { + InitCommunication (SLYLANDRO_HOME_CONVERSATION); + return true; + } + + GenerateDefault_generateOrbital (solarSys, world); + return true; +} + diff --git a/src/uqm/planets/generate/gensol.c b/src/uqm/planets/generate/gensol.c new file mode 100644 index 0000000..d6041f3 --- /dev/null +++ b/src/uqm/planets/generate/gensol.c @@ -0,0 +1,671 @@ +//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 "genall.h" +#include "../lander.h" +#include "../lifeform.h" +#include "../planets.h" +#include "../../build.h" +#include "../../encount.h" +#include "../../globdata.h" +#include "../../gamestr.h" +#include "../../grpinfo.h" +#include "../../nameref.h" +#include "../../state.h" +#include "libs/mathlib.h" + + +static bool GenerateSol_initNpcs (SOLARSYS_STATE *solarSys); +static bool GenerateSol_reinitNpcs (SOLARSYS_STATE *solarSys); +static bool GenerateSol_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateSol_generateMoons (SOLARSYS_STATE *solarSys, + PLANET_DESC *planet); +static bool GenerateSol_generateName (const SOLARSYS_STATE *, + const PLANET_DESC *world); +static bool GenerateSol_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GenerateSol_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static COUNT GenerateSol_generateLife (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GenerateSol_pickupEnergy (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + +static int init_probe (void); +static void check_probe (void); + + +const GenerateFunctions generateSolFunctions = { + /* .initNpcs = */ GenerateSol_initNpcs, + /* .reinitNpcs = */ GenerateSol_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateSol_generatePlanets, + /* .generateMoons = */ GenerateSol_generateMoons, + /* .generateName = */ GenerateSol_generateName, + /* .generateOrbital = */ GenerateSol_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateSol_generateEnergy, + /* .generateLife = */ GenerateSol_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateSol_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateSol_initNpcs (SOLARSYS_STATE *solarSys) +{ + GLOBAL (BattleGroupRef) = GET_GAME_STATE_32 (URQUAN_PROBE_GRPOFFS0); + if (GLOBAL (BattleGroupRef) == 0) + { + CloneShipFragment (URQUAN_DRONE_SHIP, &GLOBAL (npc_built_ship_q), 0); + GLOBAL (BattleGroupRef) = PutGroupInfo (GROUPS_ADD_NEW, 1); + ReinitQueue (&GLOBAL (npc_built_ship_q)); + SET_GAME_STATE_32 (URQUAN_PROBE_GRPOFFS0, GLOBAL (BattleGroupRef)); + } + + if (!init_probe ()) + GenerateDefault_initNpcs (solarSys); + + return true; +} + +static bool +GenerateSol_reinitNpcs (SOLARSYS_STATE *solarSys) +{ + if (GET_GAME_STATE (CHMMR_BOMB_STATE) != 3) + { + GenerateDefault_reinitNpcs (solarSys); + check_probe (); + } + else + { + GLOBAL (BattleGroupRef) = 0; + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + } + return true; +} + +static bool +GenerateSol_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT planetI; + +#define SOL_SEED 334241042L + RandomContext_SeedRandom (SysGenRNG, SOL_SEED); + + solarSys->SunDesc[0].NumPlanets = 9; + for (planetI = 0; planetI < 9; ++planetI) + { + COUNT angle; + DWORD rand_val; + UWORD word_val; + PLANET_DESC *pCurDesc = &solarSys->PlanetDesc[planetI]; + + pCurDesc->rand_seed = RandomContext_Random (SysGenRNG); + rand_val = pCurDesc->rand_seed; + word_val = LOWORD (rand_val); + angle = NORMALIZE_ANGLE ((COUNT)HIBYTE (word_val)); + + switch (planetI) + { + case 0: /* MERCURY */ + pCurDesc->data_index = METAL_WORLD; + pCurDesc->radius = EARTH_RADIUS * 39L / 100; + pCurDesc->NumPlanets = 0; + break; + case 1: /* VENUS */ + pCurDesc->data_index = PRIMORDIAL_WORLD; + pCurDesc->radius = EARTH_RADIUS * 72L / 100; + pCurDesc->NumPlanets = 0; + angle = NORMALIZE_ANGLE (FULL_CIRCLE - angle); + break; + case 2: /* EARTH */ + pCurDesc->data_index = WATER_WORLD | PLANET_SHIELDED; + pCurDesc->radius = EARTH_RADIUS; + pCurDesc->NumPlanets = 2; + break; + case 3: /* MARS */ + pCurDesc->data_index = DUST_WORLD; + pCurDesc->radius = EARTH_RADIUS * 152L / 100; + pCurDesc->NumPlanets = 0; + break; + case 4: /* JUPITER */ + pCurDesc->data_index = RED_GAS_GIANT; + pCurDesc->radius = EARTH_RADIUS * 500L /* 520L */ / 100; + pCurDesc->NumPlanets = 4; + break; + case 5: /* SATURN */ + pCurDesc->data_index = ORA_GAS_GIANT; + pCurDesc->radius = EARTH_RADIUS * 750L /* 952L */ / 100; + pCurDesc->NumPlanets = 1; + break; + case 6: /* URANUS */ + pCurDesc->data_index = GRN_GAS_GIANT; + pCurDesc->radius = EARTH_RADIUS * 1000L /* 1916L */ / 100; + pCurDesc->NumPlanets = 0; + break; + case 7: /* NEPTUNE */ + pCurDesc->data_index = BLU_GAS_GIANT; + pCurDesc->radius = EARTH_RADIUS * 1250L /* 2999L */ / 100; + pCurDesc->NumPlanets = 1; + break; + case 8: /* PLUTO */ + pCurDesc->data_index = PELLUCID_WORLD; + pCurDesc->radius = EARTH_RADIUS * 1550L /* 3937L */ / 100; + pCurDesc->NumPlanets = 0; + angle = FULL_CIRCLE - OCTANT; + break; + } + + pCurDesc->location.x = COSINE (angle, pCurDesc->radius); + pCurDesc->location.y = SINE (angle, pCurDesc->radius); + } + + return true; +} + +static bool +GenerateSol_generateMoons (SOLARSYS_STATE *solarSys, PLANET_DESC *planet) +{ + COUNT planetNr; + DWORD rand_val; + + GenerateDefault_generateMoons (solarSys, planet); + + planetNr = planetIndex (solarSys, planet); + switch (planetNr) + { + case 2: /* moons of EARTH */ + { + COUNT angle; + + /* Starbase: */ + solarSys->MoonDesc[0].data_index = HIERARCHY_STARBASE; + solarSys->MoonDesc[0].radius = MIN_MOON_RADIUS; + angle = HALF_CIRCLE + QUADRANT; + solarSys->MoonDesc[0].location.x = + COSINE (angle, solarSys->MoonDesc[0].radius); + solarSys->MoonDesc[0].location.y = + SINE (angle, solarSys->MoonDesc[0].radius); + + /* Luna: */ + solarSys->MoonDesc[1].data_index = SELENIC_WORLD; + solarSys->MoonDesc[1].radius = MIN_MOON_RADIUS + + (MAX_MOONS - 1) * MOON_DELTA; + rand_val = RandomContext_Random (SysGenRNG); + angle = NORMALIZE_ANGLE (LOWORD (rand_val)); + solarSys->MoonDesc[1].location.x = + COSINE (angle, solarSys->MoonDesc[1].radius); + solarSys->MoonDesc[1].location.y = + SINE (angle, solarSys->MoonDesc[1].radius); + break; + } + case 4: /* moons of JUPITER */ + solarSys->MoonDesc[0].data_index = RADIOACTIVE_WORLD; + /* Io */ + solarSys->MoonDesc[1].data_index = HALIDE_WORLD; + /* Europa */ + solarSys->MoonDesc[2].data_index = CYANIC_WORLD; + /* Ganymede */ + solarSys->MoonDesc[3].data_index = PELLUCID_WORLD; + /* Callisto */ + break; + case 5: /* moons of SATURN */ + solarSys->MoonDesc[0].data_index = ALKALI_WORLD; + /* Titan */ + break; + case 7: /* moons of NEPTUNE */ + solarSys->MoonDesc[0].data_index = VINYLOGOUS_WORLD; + /* Triton */ + break; + } + + return true; +} + +static bool +GenerateSol_generateName (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world) +{ + COUNT planetNr = planetIndex (solarSys, world); + utf8StringCopy (GLOBAL_SIS (PlanetName), sizeof (GLOBAL_SIS (PlanetName)), + GAME_STRING (PLANET_NUMBER_BASE + planetNr)); + SET_GAME_STATE (BATTLE_PLANET, solarSys->PlanetDesc[planetNr].data_index); + + return true; +} + +static bool +GenerateSol_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + DWORD rand_val; + COUNT planetNr; + + if (matchWorld (solarSys, world, 2, 0)) + { + /* Starbase */ + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + EncounterGroup = 0; + GLOBAL (CurrentActivity) |= START_ENCOUNTER; + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, (BYTE)~0); + return true; + } + + DoPlanetaryAnalysis (&solarSys->SysInfo, world); + rand_val = RandomContext_GetSeed (SysGenRNG); + + solarSys->SysInfo.PlanetInfo.ScanSeed[MINERAL_SCAN] = rand_val; + GenerateMineralDeposits (&solarSys->SysInfo, GENERATE_ALL, NULL); + rand_val = RandomContext_GetSeed (SysGenRNG); + + planetNr = planetIndex (solarSys, world); + if (worldIsPlanet (solarSys, world)) + { + switch (planetNr) + { + case 0: /* MERCURY */ + solarSys->SysInfo.PlanetInfo.AtmoDensity = 0; + solarSys->SysInfo.PlanetInfo.PlanetDensity = 98; + solarSys->SysInfo.PlanetInfo.PlanetRadius = 38; + solarSys->SysInfo.PlanetInfo.AxialTilt = 3; + solarSys->SysInfo.PlanetInfo.Weather = 0; + solarSys->SysInfo.PlanetInfo.Tectonics = 2; + solarSys->SysInfo.PlanetInfo.RotationPeriod = 59 * 240; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 165; + break; + case 1: /* VENUS */ + solarSys->SysInfo.PlanetInfo.AtmoDensity = 90 * + EARTH_ATMOSPHERE; + solarSys->SysInfo.PlanetInfo.PlanetDensity = 95; + solarSys->SysInfo.PlanetInfo.PlanetRadius = 95; + solarSys->SysInfo.PlanetInfo.AxialTilt = 177; + solarSys->SysInfo.PlanetInfo.Weather = 7; + solarSys->SysInfo.PlanetInfo.Tectonics = 1; + solarSys->SysInfo.PlanetInfo.RotationPeriod = 243 * 240; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 457; + break; + case 2: /* EARTH */ + solarSys->SysInfo.PlanetInfo.AtmoDensity = + EARTH_ATMOSPHERE; + solarSys->SysInfo.PlanetInfo.PlanetDensity = 100; + solarSys->SysInfo.PlanetInfo.PlanetRadius = 100; + solarSys->SysInfo.PlanetInfo.AxialTilt = 23; + solarSys->SysInfo.PlanetInfo.Weather = 1; + solarSys->SysInfo.PlanetInfo.Tectonics = 1; + solarSys->SysInfo.PlanetInfo.RotationPeriod = 240; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 22; + break; + case 3: /* MARS */ + // XXX: Mars atmo should actually be 1/2 in current units + solarSys->SysInfo.PlanetInfo.AtmoDensity = 1; + solarSys->SysInfo.PlanetInfo.PlanetDensity = 72; + solarSys->SysInfo.PlanetInfo.PlanetRadius = 53; + solarSys->SysInfo.PlanetInfo.AxialTilt = 24; + solarSys->SysInfo.PlanetInfo.Weather = 1; + solarSys->SysInfo.PlanetInfo.Tectonics = 1; + solarSys->SysInfo.PlanetInfo.RotationPeriod = 246; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -53; + break; + case 4: /* JUPITER */ + solarSys->SysInfo.PlanetInfo.AtmoDensity = + GAS_GIANT_ATMOSPHERE; + solarSys->SysInfo.PlanetInfo.PlanetDensity = 24; + solarSys->SysInfo.PlanetInfo.PlanetRadius = 1120; + solarSys->SysInfo.PlanetInfo.AxialTilt = 3; + solarSys->SysInfo.PlanetInfo.Weather = 7; + solarSys->SysInfo.PlanetInfo.Tectonics = 0; + solarSys->SysInfo.PlanetInfo.RotationPeriod = 98; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -143; + solarSys->SysInfo.PlanetInfo.PlanetToSunDist = + EARTH_RADIUS * 520L / 100; + break; + case 5: /* SATURN */ + solarSys->SysInfo.PlanetInfo.AtmoDensity = + GAS_GIANT_ATMOSPHERE; + solarSys->SysInfo.PlanetInfo.PlanetDensity = 13; + solarSys->SysInfo.PlanetInfo.PlanetRadius = 945; + solarSys->SysInfo.PlanetInfo.AxialTilt = 27; + solarSys->SysInfo.PlanetInfo.Weather = 7; + solarSys->SysInfo.PlanetInfo.Tectonics = 0; + solarSys->SysInfo.PlanetInfo.RotationPeriod = 102; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -197; + solarSys->SysInfo.PlanetInfo.PlanetToSunDist = + EARTH_RADIUS * 952L / 100; + break; + case 6: /* URANUS */ + solarSys->SysInfo.PlanetInfo.AtmoDensity = + GAS_GIANT_ATMOSPHERE; + solarSys->SysInfo.PlanetInfo.PlanetDensity = 21; + solarSys->SysInfo.PlanetInfo.PlanetRadius = 411; + solarSys->SysInfo.PlanetInfo.AxialTilt = 98; + solarSys->SysInfo.PlanetInfo.Weather = 7; + solarSys->SysInfo.PlanetInfo.Tectonics = 0; + solarSys->SysInfo.PlanetInfo.RotationPeriod = 172; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -217; + solarSys->SysInfo.PlanetInfo.PlanetToSunDist = + EARTH_RADIUS * 1916L / 100; + break; + case 7: /* NEPTUNE */ + solarSys->SysInfo.PlanetInfo.AtmoDensity = + GAS_GIANT_ATMOSPHERE; + solarSys->SysInfo.PlanetInfo.PlanetDensity = 28; + solarSys->SysInfo.PlanetInfo.PlanetRadius = 396; + solarSys->SysInfo.PlanetInfo.AxialTilt = 30; + solarSys->SysInfo.PlanetInfo.Weather = 7; + solarSys->SysInfo.PlanetInfo.Tectonics = 0; + solarSys->SysInfo.PlanetInfo.RotationPeriod = 182; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -229; + solarSys->SysInfo.PlanetInfo.PlanetToSunDist = + EARTH_RADIUS * 2999L / 100; + break; + case 8: /* PLUTO */ + if (!GET_GAME_STATE (FOUND_PLUTO_SPATHI)) + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable ( + LoadGraphic (SPAPLUTO_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable ( + LoadStringTable (SPAPLUTO_STRTAB)); + } + + solarSys->SysInfo.PlanetInfo.AtmoDensity = 0; + solarSys->SysInfo.PlanetInfo.PlanetDensity = 33; + solarSys->SysInfo.PlanetInfo.PlanetRadius = 18; + solarSys->SysInfo.PlanetInfo.AxialTilt = 119; + solarSys->SysInfo.PlanetInfo.Weather = 0; + solarSys->SysInfo.PlanetInfo.Tectonics = 0; + solarSys->SysInfo.PlanetInfo.RotationPeriod = 1533; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -235; + solarSys->SysInfo.PlanetInfo.PlanetToSunDist = + EARTH_RADIUS * 3937L / 100; + break; + } + + solarSys->SysInfo.PlanetInfo.SurfaceGravity = + CalcGravity (&solarSys->SysInfo.PlanetInfo); + LoadPlanet (planetNr == 2 ? + CaptureDrawable (LoadGraphic (EARTH_MASK_ANIM)) : NULL); + } + else + { + // World is a moon. + COUNT moonNr = moonIndex (solarSys, world); + + solarSys->SysInfo.PlanetInfo.AxialTilt = 0; + solarSys->SysInfo.PlanetInfo.AtmoDensity = 0; + solarSys->SysInfo.PlanetInfo.Weather = 0; + switch (planetNr) + { + case 2: /* moons of EARTH */ + // NOTE: Even though we save the seed here, it is irrelevant. + // The seed will be used to randomly place the tractors, but + // since they are mobile, they will be moved to different + // locations not governed by this seed. + solarSys->SysInfo.PlanetInfo.ScanSeed[BIOLOGICAL_SCAN] = + rand_val; + + if (!GET_GAME_STATE (MOONBASE_DESTROYED)) + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable ( + LoadGraphic (MOONBASE_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable ( + LoadStringTable (MOONBASE_STRTAB)); + } + + solarSys->SysInfo.PlanetInfo.PlanetDensity = 60; + solarSys->SysInfo.PlanetInfo.PlanetRadius = 25; + solarSys->SysInfo.PlanetInfo.AxialTilt = 0; + solarSys->SysInfo.PlanetInfo.Tectonics = 0; + solarSys->SysInfo.PlanetInfo.RotationPeriod = 240 * 29; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -18; + break; + + case 4: /* moons of JUPITER */ + solarSys->SysInfo.PlanetInfo.PlanetToSunDist = + EARTH_RADIUS * 520L / 100; + switch (moonNr) + { + case 0: /* Io */ + solarSys->SysInfo.PlanetInfo.PlanetDensity = 69; + solarSys->SysInfo.PlanetInfo.PlanetRadius = 25; + solarSys->SysInfo.PlanetInfo.Tectonics = 3; + solarSys->SysInfo.PlanetInfo.RotationPeriod = 390; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -163; + break; + case 1: /* Europa */ + solarSys->SysInfo.PlanetInfo.PlanetDensity = 54; + solarSys->SysInfo.PlanetInfo.PlanetRadius = 25; + solarSys->SysInfo.PlanetInfo.Tectonics = 1; + solarSys->SysInfo.PlanetInfo.RotationPeriod = 840; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -161; + break; + case 2: /* Ganymede */ + solarSys->SysInfo.PlanetInfo.PlanetDensity = 35; + solarSys->SysInfo.PlanetInfo.PlanetRadius = 41; + solarSys->SysInfo.PlanetInfo.Tectonics = 0; + solarSys->SysInfo.PlanetInfo.RotationPeriod = 1728; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -164; + break; + case 3: /* Callisto */ + solarSys->SysInfo.PlanetInfo.PlanetDensity = 35; + solarSys->SysInfo.PlanetInfo.PlanetRadius = 38; + solarSys->SysInfo.PlanetInfo.Tectonics = 1; + solarSys->SysInfo.PlanetInfo.RotationPeriod = 4008; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -167; + break; + } + break; + + case 5: /* moon of SATURN: Titan */ + solarSys->SysInfo.PlanetInfo.PlanetToSunDist = + EARTH_RADIUS * 952L / 100; + solarSys->SysInfo.PlanetInfo.AtmoDensity = 160; + solarSys->SysInfo.PlanetInfo.Weather = 2; + solarSys->SysInfo.PlanetInfo.PlanetDensity = 34; + solarSys->SysInfo.PlanetInfo.PlanetRadius = 40; + solarSys->SysInfo.PlanetInfo.Tectonics = 1; + solarSys->SysInfo.PlanetInfo.RotationPeriod = 3816; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -178; + break; + + case 7: /* moon of NEPTUNE: Triton */ + solarSys->SysInfo.PlanetInfo.PlanetToSunDist = + EARTH_RADIUS * 2999L / 100; + solarSys->SysInfo.PlanetInfo.AtmoDensity = 10; + solarSys->SysInfo.PlanetInfo.Weather = 1; + solarSys->SysInfo.PlanetInfo.PlanetDensity = 95; + solarSys->SysInfo.PlanetInfo.PlanetRadius = 27; + solarSys->SysInfo.PlanetInfo.Tectonics = 0; + solarSys->SysInfo.PlanetInfo.RotationPeriod = 4300; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -216; + break; + } + + solarSys->SysInfo.PlanetInfo.SurfaceGravity = + CalcGravity (&solarSys->SysInfo.PlanetInfo); + LoadPlanet (NULL); + } + + return true; +} + +static COUNT +GenerateSol_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (matchWorld (solarSys, world, 8, MATCH_PLANET)) + { + /* Pluto */ + // This check is needed because the retrieval bit is not set for + // this node to keep it on the surface while the lander is taking off + if (GET_GAME_STATE (FOUND_PLUTO_SPATHI)) + { // already picked up + return 0; + } + + if (info) + { + info->loc_pt.x = 20; + info->loc_pt.y = MAP_HEIGHT - 8; + } + + return 1; // only matters when count is requested + } + + if (matchWorld (solarSys, world, 2, 1)) + { + /* Earth Moon */ + // This check is redundant since the retrieval bit will keep the + // node from showing up again + if (GET_GAME_STATE (MOONBASE_DESTROYED)) + { // already picked up + return 0; + } + + if (info) + { + info->loc_pt.x = MAP_WIDTH * 3 / 4; + info->loc_pt.y = MAP_HEIGHT * 1 / 4; + } + + return 1; // only matters when count is requested + } + + (void) whichNode; + return 0; +} + +static bool +GenerateSol_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (matchWorld (solarSys, world, 8, MATCH_PLANET)) + { // Pluto + assert (!GET_GAME_STATE (FOUND_PLUTO_SPATHI) && whichNode == 0); + + // Ran into Fwiffo on Pluto + #define FWIFFO_FRAGS 8 + if (!KillLanderCrewSeq (FWIFFO_FRAGS, ONE_SECOND / 20)) + return false; // lander probably died + + SET_GAME_STATE (FOUND_PLUTO_SPATHI, 1); + + GenerateDefault_landerReport (solarSys); + SetLanderTakeoff (); + + // Do not remove the node from the surface while the lander is + // taking off. FOUND_PLUTO_SPATHI bit will keep the node from + // showing up on subsequent visits. + return false; + } + + if (matchWorld (solarSys, world, 2, 1)) + { // Earth Moon + assert (!GET_GAME_STATE (MOONBASE_DESTROYED) && whichNode == 0); + + GenerateDefault_landerReport (solarSys); + SetLanderTakeoff (); + + SET_GAME_STATE (MOONBASE_DESTROYED, 1); + SET_GAME_STATE (MOONBASE_ON_SHIP, 1); + + return true; // picked up + } + + (void) whichNode; + return false; +} + +static COUNT +GenerateSol_generateLife (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (matchWorld (solarSys, world, 2, 1)) + { + /* Earth Moon */ + return GenerateRandomNodes (&solarSys->SysInfo, BIOLOGICAL_SCAN, 10, + NUM_CREATURE_TYPES + 1, whichNode, info); + } + + return 0; +} + + +static int +init_probe (void) +{ + HIPGROUP hGroup; + + if (!GET_GAME_STATE (PROBE_MESSAGE_DELIVERED) + && GetGroupInfo (GLOBAL (BattleGroupRef), GROUP_INIT_IP) + && (hGroup = GetHeadLink (&GLOBAL (ip_group_q)))) + { + IP_GROUP *GroupPtr; + + GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup); + GroupPtr->task = IN_ORBIT; + GroupPtr->sys_loc = 2 + 1; /* orbitting earth */ + GroupPtr->dest_loc = 2 + 1; /* orbitting earth */ + GroupPtr->loc.x = 0; + GroupPtr->loc.y = 0; + GroupPtr->group_counter = 0; + UnlockIpGroup (&GLOBAL (ip_group_q), hGroup); + + return 1; + } + else + return 0; +} + +static void +check_probe (void) +{ + HIPGROUP hGroup; + IP_GROUP *GroupPtr; + + if (!GLOBAL (BattleGroupRef)) + return; // nothing to check + + hGroup = GetHeadLink (&GLOBAL (ip_group_q)); + if (!hGroup) + return; // still nothing to check + + GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup); + // REFORM_GROUP was set in ipdisp.c:ip_group_collision() + // during a collision with the flagship. + if (GroupPtr->race_id == URQUAN_DRONE_SHIP + && (GroupPtr->task & REFORM_GROUP)) + { + // We just want the probe to take off as fast as possible, + // so clear out REFORM_GROUP + GroupPtr->task = FLEE | IGNORE_FLAGSHIP; + GroupPtr->dest_loc = 0; + } + UnlockIpGroup (&GLOBAL (ip_group_q), hGroup); +} + diff --git a/src/uqm/planets/generate/genspa.c b/src/uqm/planets/generate/genspa.c new file mode 100644 index 0000000..26bc412 --- /dev/null +++ b/src/uqm/planets/generate/genspa.c @@ -0,0 +1,283 @@ +//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 "genall.h" +#include "../lifeform.h" +#include "../lander.h" +#include "../planets.h" +#include "../scan.h" +#include "../../build.h" +#include "../../comm.h" +#include "../../globdata.h" +#include "../../ipdisp.h" +#include "../../nameref.h" +#include "../../state.h" +#include "libs/mathlib.h" + + +static bool GenerateSpathi_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateSpathi_generateMoons (SOLARSYS_STATE *solarSys, + PLANET_DESC *planet); +static bool GenerateSpathi_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GenerateSpathi_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static COUNT GenerateSpathi_generateLife (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GenerateSpathi_pickupEnergy (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); +static bool GenerateSpathi_pickupLife (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + + +const GenerateFunctions generateSpathiFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateSpathi_generatePlanets, + /* .generateMoons = */ GenerateSpathi_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateSpathi_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateSpathi_generateEnergy, + /* .generateLife = */ GenerateSpathi_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateSpathi_pickupEnergy, + /* .pickupLife = */ GenerateSpathi_pickupLife, +}; + + +static bool +GenerateSpathi_generatePlanets (SOLARSYS_STATE *solarSys) +{ + PLANET_DESC *pMinPlanet; + COUNT angle; + + pMinPlanet = &solarSys->PlanetDesc[0]; + solarSys->SunDesc[0].NumPlanets = 1; + FillOrbits (solarSys, + solarSys->SunDesc[0].NumPlanets, pMinPlanet, FALSE); + + pMinPlanet->radius = EARTH_RADIUS * 1150L / 100; + angle = ARCTAN (pMinPlanet->location.x, pMinPlanet->location.y); + pMinPlanet->location.x = COSINE (angle, pMinPlanet->radius); + pMinPlanet->location.y = SINE (angle, pMinPlanet->radius); + pMinPlanet->data_index = WATER_WORLD; + if (GET_GAME_STATE (SPATHI_SHIELDED_SELVES)) + pMinPlanet->data_index |= PLANET_SHIELDED; + pMinPlanet->NumPlanets = 1; + + return true; +} + +static bool +GenerateSpathi_generateMoons (SOLARSYS_STATE *solarSys, PLANET_DESC *planet) +{ + COUNT angle; + + GenerateDefault_generateMoons (solarSys, planet); + + if (matchWorld (solarSys, planet, 0, MATCH_PLANET)) + { +#ifdef NOTYET + utf8StringCopy (GLOBAL_SIS (PlanetName), + sizeof (GLOBAL_SIS (PlanetName)), + "Spathiwa"); +#endif /* NOTYET */ + + solarSys->MoonDesc[0].data_index = PELLUCID_WORLD; + solarSys->MoonDesc[0].radius = MIN_MOON_RADIUS + MOON_DELTA; + angle = NORMALIZE_ANGLE (LOWORD (RandomContext_Random (SysGenRNG))); + solarSys->MoonDesc[0].location.x = + COSINE (angle, solarSys->MoonDesc[0].radius); + solarSys->MoonDesc[0].location.y = + SINE (angle, solarSys->MoonDesc[0].radius); + } + + return true; +} + +static bool +GenerateSpathi_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + DWORD rand_val; + + if (matchWorld (solarSys, world, 0, 0)) + { + /* Spathiwa's moon */ + if (!GET_GAME_STATE (SPATHI_SHIELDED_SELVES) + && StartSphereTracking (SPATHI_SHIP)) + { + NotifyOthers (SPATHI_SHIP, IPNL_ALL_CLEAR); + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + CloneShipFragment (SPATHI_SHIP, &GLOBAL (npc_built_ship_q), + INFINITE_FLEET); + + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); + GLOBAL (CurrentActivity) |= START_INTERPLANETARY; + InitCommunication (SPATHI_CONVERSATION); + + if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))) + { + GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY; + ReinitQueue (&GLOBAL (npc_built_ship_q)); + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + } + return true; + } + + DoPlanetaryAnalysis (&solarSys->SysInfo, world); + rand_val = RandomContext_GetSeed (SysGenRNG); + + solarSys->SysInfo.PlanetInfo.ScanSeed[BIOLOGICAL_SCAN] = rand_val; + GenerateLifeForms (&solarSys->SysInfo, GENERATE_ALL, NULL); + rand_val = RandomContext_GetSeed (SysGenRNG); + + solarSys->SysInfo.PlanetInfo.ScanSeed[MINERAL_SCAN] = rand_val; + GenerateMineralDeposits (&solarSys->SysInfo, GENERATE_ALL, NULL); + + solarSys->SysInfo.PlanetInfo.ScanSeed[ENERGY_SCAN] = rand_val; + + solarSys->SysInfo.PlanetInfo.Weather = 0; + solarSys->SysInfo.PlanetInfo.Tectonics = 0; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 28; + if (!GET_GAME_STATE (UMGAH_BROADCASTERS)) + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable (LoadGraphic (UMGAH_BCS_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable (LoadStringTable (UMGAH_BCS_STRTAB)); + if (!GET_GAME_STATE (SPATHI_SHIELDED_SELVES)) + { // The first report talks extensively about Spathi + // slave-shielding selves. If they never did so, the report + // makes no sense, so use an alternate. + solarSys->SysInfo.PlanetInfo.DiscoveryString = + SetAbsStringTableIndex ( + solarSys->SysInfo.PlanetInfo.DiscoveryString, 1); + } + } + LoadPlanet (NULL); + return true; + } + else if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + /* visiting Spathiwa */ + DoPlanetaryAnalysis (&solarSys->SysInfo, world); + rand_val = RandomContext_GetSeed (SysGenRNG); + + solarSys->SysInfo.PlanetInfo.ScanSeed[MINERAL_SCAN] = rand_val; + GenerateMineralDeposits (&solarSys->SysInfo, GENERATE_ALL, NULL); + rand_val = RandomContext_GetSeed (SysGenRNG); + + solarSys->SysInfo.PlanetInfo.ScanSeed[BIOLOGICAL_SCAN] = rand_val; + + solarSys->SysInfo.PlanetInfo.PlanetRadius = 120; + solarSys->SysInfo.PlanetInfo.SurfaceGravity = + CalcGravity (&solarSys->SysInfo.PlanetInfo); + solarSys->SysInfo.PlanetInfo.Weather = 0; + solarSys->SysInfo.PlanetInfo.Tectonics = 0; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 31; + + LoadPlanet (NULL); + return true; + } + + GenerateDefault_generateOrbital (solarSys, world); + + return true; +} + +static COUNT +GenerateSpathi_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (matchWorld (solarSys, world, 0, 0)) + { + // This check is redundant since the retrieval bit will keep the + // node from showing up again + if (GET_GAME_STATE (UMGAH_BROADCASTERS)) + { // already picked up + return 0; + } + + return GenerateDefault_generateArtifact (solarSys, whichNode, info); + } + + return 0; +} + +static bool +GenerateSpathi_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (matchWorld (solarSys, world, 0, 0)) + { + assert (!GET_GAME_STATE (UMGAH_BROADCASTERS) && whichNode == 0); + + GenerateDefault_landerReport (solarSys); + SetLanderTakeoff (); + + SET_GAME_STATE (UMGAH_BROADCASTERS, 1); + SET_GAME_STATE (UMGAH_BROADCASTERS_ON_SHIP, 1); + + return true; // picked up + } + + (void) whichNode; + return false; +} + +static COUNT +GenerateSpathi_generateLife (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + #define NUM_EVIL_ONES 32 + return GenerateRandomNodes (&solarSys->SysInfo, BIOLOGICAL_SCAN, NUM_EVIL_ONES, + NUM_CREATURE_TYPES, whichNode, info); + } + + return 0; +} + +static bool +GenerateSpathi_pickupLife (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + assert (!GET_GAME_STATE (SPATHI_CREATURES_ELIMINATED) && + !GET_GAME_STATE (SPATHI_SHIELDED_SELVES)); + + SET_GAME_STATE (SPATHI_CREATURES_EXAMINED, 1); + if (countNodesRetrieved (&solarSys->SysInfo.PlanetInfo, BIOLOGICAL_SCAN) + + 1 == NUM_EVIL_ONES) + { // last creature picked up + SET_GAME_STATE (SPATHI_CREATURES_ELIMINATED, 1); + } + + return true; // picked up + } + + return GenerateDefault_pickupLife (solarSys, world, whichNode); +} diff --git a/src/uqm/planets/generate/gensup.c b/src/uqm/planets/generate/gensup.c new file mode 100644 index 0000000..a618c89 --- /dev/null +++ b/src/uqm/planets/generate/gensup.c @@ -0,0 +1,159 @@ +//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 "genall.h" +#include "../lander.h" +#include "../planets.h" +#include "../../build.h" +#include "../../comm.h" +#include "../../globdata.h" +#include "../../ipdisp.h" +#include "../../nameref.h" +#include "../../state.h" +#include "libs/mathlib.h" + + +static bool GenerateSupox_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateSupox_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GenerateSupox_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GenerateSupox_pickupEnergy (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + + +const GenerateFunctions generateSupoxFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateSupox_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateSupox_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateSupox_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateSupox_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateSupox_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT angle; + + GenerateDefault_generatePlanets (solarSys); + + solarSys->PlanetDesc[0].data_index = WATER_WORLD; + solarSys->PlanetDesc[0].NumPlanets = 2; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 152L / 100; + angle = ARCTAN (solarSys->PlanetDesc[0].location.x, + solarSys->PlanetDesc[0].location.y); + solarSys->PlanetDesc[0].location.x = + COSINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].location.y = + SINE (angle, solarSys->PlanetDesc[0].radius); + + return true; +} + +static bool +GenerateSupox_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + if (StartSphereTracking (SUPOX_SHIP)) + { + NotifyOthers (SUPOX_SHIP, IPNL_ALL_CLEAR); + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + CloneShipFragment (SUPOX_SHIP, &GLOBAL (npc_built_ship_q), + INFINITE_FLEET); + + GLOBAL (CurrentActivity) |= START_INTERPLANETARY; + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); + InitCommunication (SUPOX_CONVERSATION); + + if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))) + { + GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY; + ReinitQueue (&GLOBAL (npc_built_ship_q)); + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + } + return true; + } + else + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable (LoadStringTable (SUPOX_RUINS_STRTAB)); + if (GET_GAME_STATE (ULTRON_CONDITION)) + { // Already picked up the Ultron, skip the report + solarSys->SysInfo.PlanetInfo.DiscoveryString = + SetAbsStringTableIndex ( + solarSys->SysInfo.PlanetInfo.DiscoveryString, 1); + } + } + } + + GenerateDefault_generateOrbital (solarSys, world); + + return true; +} + +static bool +GenerateSupox_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + GenerateDefault_landerReportCycle (solarSys); + + // The artifact can be picked up from any ruin + if (!GET_GAME_STATE (ULTRON_CONDITION)) + { // Just picked up the Ultron from a ruin + SetLanderTakeoff (); + + SET_GAME_STATE (ULTRON_CONDITION, 1); + } + + return false; // do not remove the node + } + + (void) whichNode; + return false; +} + +static COUNT +GenerateSupox_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + return GenerateDefault_generateRuins (solarSys, whichNode, info); + } + + return 0; +} + diff --git a/src/uqm/planets/generate/gensyr.c b/src/uqm/planets/generate/gensyr.c new file mode 100644 index 0000000..ebf3be4 --- /dev/null +++ b/src/uqm/planets/generate/gensyr.c @@ -0,0 +1,102 @@ +//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 "genall.h" +#include "../planets.h" +#include "../../comm.h" + +static bool GenerateSyreen_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateSyreen_generateMoons (SOLARSYS_STATE *solarSys, + PLANET_DESC *planet); +static bool GenerateSyreen_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); + + +const GenerateFunctions generateSyreenFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateSyreen_generatePlanets, + /* .generateMoons = */ GenerateSyreen_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateSyreen_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateDefault_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateDefault_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateSyreen_generatePlanets (SOLARSYS_STATE *solarSys) +{ + GenerateDefault_generatePlanets (solarSys); + + solarSys->PlanetDesc[0].data_index = WATER_WORLD | PLANET_SHIELDED; + solarSys->PlanetDesc[0].NumPlanets = 1; + + return true; +} + +static bool +GenerateSyreen_generateMoons (SOLARSYS_STATE *solarSys, PLANET_DESC *planet) +{ + GenerateDefault_generateMoons (solarSys, planet); + + if (matchWorld (solarSys, planet, 0, MATCH_PLANET)) + { + solarSys->MoonDesc[0].data_index = HIERARCHY_STARBASE; + solarSys->MoonDesc[0].radius = MIN_MOON_RADIUS; + solarSys->MoonDesc[0].location.x = + COSINE (QUADRANT, solarSys->MoonDesc[0].radius); + solarSys->MoonDesc[0].location.y = + SINE (QUADRANT, solarSys->MoonDesc[0].radius); + } + + return true; +} + +static bool +GenerateSyreen_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + /* Syreen home planet */ + GenerateDefault_generateOrbital (solarSys, world); + + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 19; + solarSys->SysInfo.PlanetInfo.Tectonics = 0; + solarSys->SysInfo.PlanetInfo.Weather = 0; + solarSys->SysInfo.PlanetInfo.AtmoDensity = EARTH_ATMOSPHERE * 9 / 10; + return true; + } + + if (matchWorld (solarSys, world, 0, 0)) + { + /* Starbase */ + InitCommunication (SYREEN_CONVERSATION); + return true; + } + + GenerateDefault_generateOrbital (solarSys, world); + + return true; +} + diff --git a/src/uqm/planets/generate/genthrad.c b/src/uqm/planets/generate/genthrad.c new file mode 100644 index 0000000..875e582 --- /dev/null +++ b/src/uqm/planets/generate/genthrad.c @@ -0,0 +1,217 @@ +//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 "genall.h" +#include "../lander.h" +#include "../planets.h" +#include "../../build.h" +#include "../../comm.h" +#include "../../gendef.h" +#include "../../starmap.h" +#include "../../globdata.h" +#include "../../ipdisp.h" +#include "../../nameref.h" +#include "../../setup.h" +#include "../../state.h" +#include "libs/mathlib.h" + + +static bool GenerateThraddash_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateThraddash_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GenerateThraddash_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GenerateThraddash_pickupEnergy (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + + +const GenerateFunctions generateThraddashFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateThraddash_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateThraddash_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateThraddash_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateThraddash_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateThraddash_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT angle; + + GenerateDefault_generatePlanets (solarSys); + + if (CurStarDescPtr->Index == AQUA_HELIX_DEFINED) + { + solarSys->PlanetDesc[0].data_index = PRIMORDIAL_WORLD; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 65L / 100; + angle = ARCTAN (solarSys->PlanetDesc[0].location.x, + solarSys->PlanetDesc[0].location.y); + solarSys->PlanetDesc[0].location.x = + COSINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].location.y = + SINE (angle, solarSys->PlanetDesc[0].radius); + } + else /* CurStarDescPtr->Index == THRADD_DEFINED */ + { + solarSys->PlanetDesc[0].data_index = WATER_WORLD; + solarSys->PlanetDesc[0].NumPlanets = 0; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 98L / 100; + angle = ARCTAN (solarSys->PlanetDesc[0].location.x, + solarSys->PlanetDesc[0].location.y); + solarSys->PlanetDesc[0].location.x = + COSINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].location.y = + SINE (angle, solarSys->PlanetDesc[0].radius); + } + return true; +} + +static bool +GenerateThraddash_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + if (StartSphereTracking (THRADDASH_SHIP) + && (CurStarDescPtr->Index == THRADD_DEFINED + || (!GET_GAME_STATE (HELIX_UNPROTECTED) + && (BYTE)(GET_GAME_STATE (THRADD_MISSION) - 1) >= 3))) + { + NotifyOthers (THRADDASH_SHIP, IPNL_ALL_CLEAR); + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + CloneShipFragment (THRADDASH_SHIP, &GLOBAL (npc_built_ship_q), + INFINITE_FLEET); + + GLOBAL (CurrentActivity) |= START_INTERPLANETARY; + if (CurStarDescPtr->Index == THRADD_DEFINED) + { + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); + } + else + { + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 6); + } + InitCommunication (THRADD_CONVERSATION); + + if (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)) + return true; + + GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY; + ReinitQueue (&GLOBAL (npc_built_ship_q)); + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + + if (CurStarDescPtr->Index == THRADD_DEFINED + || (!GET_GAME_STATE (HELIX_UNPROTECTED) + && (BYTE)(GET_GAME_STATE (THRADD_MISSION) - 1) >= 3)) + return true; + + RepairSISBorder (); + } + + if (CurStarDescPtr->Index == AQUA_HELIX_DEFINED + && !GET_GAME_STATE (AQUA_HELIX)) + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable (LoadGraphic (AQUA_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable (LoadStringTable (AQUA_STRTAB)); + } + else if (CurStarDescPtr->Index == THRADD_DEFINED) + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable (LoadStringTable (RUINS_STRTAB)); + } + } + + GenerateDefault_generateOrbital (solarSys, world); + return true; +} + +static COUNT +GenerateThraddash_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (CurStarDescPtr->Index == THRADD_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + return GenerateDefault_generateRuins (solarSys, whichNode, info); + } + + if (CurStarDescPtr->Index == AQUA_HELIX_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + // This check is redundant since the retrieval bit will keep the + // node from showing up again + if (GET_GAME_STATE (AQUA_HELIX)) + { // already picked up + return 0; + } + + return GenerateDefault_generateArtifact (solarSys, whichNode, info); + } + + return 0; +} + +static bool +GenerateThraddash_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (CurStarDescPtr->Index == THRADD_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + // Standard ruins report + GenerateDefault_landerReportCycle (solarSys); + return false; + } + + if (CurStarDescPtr->Index == AQUA_HELIX_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + assert (!GET_GAME_STATE (AQUA_HELIX) && whichNode == 0); + + GenerateDefault_landerReport (solarSys); + SetLanderTakeoff (); + + SET_GAME_STATE (HELIX_VISITS, 0); + SET_GAME_STATE (AQUA_HELIX, 1); + SET_GAME_STATE (AQUA_HELIX_ON_SHIP, 1); + SET_GAME_STATE (HELIX_UNPROTECTED, 1); + + return true; // picked up + } + + (void) whichNode; + return false; +} diff --git a/src/uqm/planets/generate/gentrap.c b/src/uqm/planets/generate/gentrap.c new file mode 100644 index 0000000..e8451cd --- /dev/null +++ b/src/uqm/planets/generate/gentrap.c @@ -0,0 +1,80 @@ +//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 "genall.h" +#include "../planets.h" + + +static bool GenerateTrap_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateTrap_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); + + +const GenerateFunctions generateTrapFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateTrap_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateTrap_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateDefault_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateDefault_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateTrap_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT angle; + + GenerateDefault_generatePlanets (solarSys); + + solarSys->PlanetDesc[0].data_index = TELLURIC_WORLD; + solarSys->PlanetDesc[0].NumPlanets = 1; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 203L / 100; + angle = ARCTAN (solarSys->PlanetDesc[0].location.x, + solarSys->PlanetDesc[0].location.y); + solarSys->PlanetDesc[0].location.x = + COSINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].location.y = + SINE (angle, solarSys->PlanetDesc[0].radius); + + return true; +} + +static bool +GenerateTrap_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + GenerateDefault_generateOrbital (solarSys, world); + + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + solarSys->SysInfo.PlanetInfo.AtmoDensity = EARTH_ATMOSPHERE * 2; + solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 35; + solarSys->SysInfo.PlanetInfo.Weather = 3; + solarSys->SysInfo.PlanetInfo.Tectonics = 1; + } + + return true; +} + diff --git a/src/uqm/planets/generate/genutw.c b/src/uqm/planets/generate/genutw.c new file mode 100644 index 0000000..71ac2aa --- /dev/null +++ b/src/uqm/planets/generate/genutw.c @@ -0,0 +1,269 @@ +//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 "genall.h" +#include "../lander.h" +#include "../planets.h" +#include "../../build.h" +#include "../../comm.h" +#include "../../gendef.h" +#include "../../starmap.h" +#include "../../globdata.h" +#include "../../ipdisp.h" +#include "../../nameref.h" +#include "../../setup.h" +#include "../../state.h" +#include "libs/mathlib.h" + + +static bool GenerateUtwig_initNpcs (SOLARSYS_STATE *solarSys); +static bool GenerateUtwig_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateUtwig_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GenerateUtwig_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GenerateUtwig_pickupEnergy (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + + +const GenerateFunctions generateUtwigFunctions = { + /* .initNpcs = */ GenerateUtwig_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateUtwig_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateUtwig_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateUtwig_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateUtwig_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateUtwig_initNpcs (SOLARSYS_STATE *solarSys) +{ + if (CurStarDescPtr->Index == BOMB_DEFINED + && !GET_GAME_STATE (UTWIG_BOMB)) + { + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + } + else + { + GenerateDefault_initNpcs (solarSys); + } + + return true; +} + +static bool +GenerateUtwig_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT angle; + + GenerateDefault_generatePlanets (solarSys); + + if (CurStarDescPtr->Index == UTWIG_DEFINED) + { + solarSys->PlanetDesc[0].data_index = WATER_WORLD; + solarSys->PlanetDesc[0].NumPlanets = 1; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 174L / 100; + angle = ARCTAN (solarSys->PlanetDesc[0].location.x, + solarSys->PlanetDesc[0].location.y); + solarSys->PlanetDesc[0].location.x = + COSINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].location.y = + SINE (angle, solarSys->PlanetDesc[0].radius); + } + + return true; +} + +static bool +GenerateUtwig_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if ((CurStarDescPtr->Index == UTWIG_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + || (CurStarDescPtr->Index == BOMB_DEFINED + && matchWorld (solarSys, world, 5, 1) + && !GET_GAME_STATE (UTWIG_BOMB))) + { + if ((CurStarDescPtr->Index == UTWIG_DEFINED + || !GET_GAME_STATE (UTWIG_HAVE_ULTRON)) + && StartSphereTracking (UTWIG_SHIP)) + { + NotifyOthers (UTWIG_SHIP, IPNL_ALL_CLEAR); + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + CloneShipFragment (UTWIG_SHIP, + &GLOBAL (npc_built_ship_q), INFINITE_FLEET); + + GLOBAL (CurrentActivity) |= START_INTERPLANETARY; + if (CurStarDescPtr->Index == UTWIG_DEFINED) + { + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); + } + else + { + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 6); + } + InitCommunication (UTWIG_CONVERSATION); + + if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))) + { + GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY; + ReinitQueue (&GLOBAL (npc_built_ship_q)); + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + } + return true; + } + + if (CurStarDescPtr->Index == BOMB_DEFINED + && !GET_GAME_STATE (BOMB_UNPROTECTED) + && StartSphereTracking (DRUUGE_SHIP)) + { + COUNT i; + + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + for (i = 0; i < 5; ++i) + { + CloneShipFragment (DRUUGE_SHIP, + &GLOBAL (npc_built_ship_q), 0); + } + GLOBAL (CurrentActivity) |= START_INTERPLANETARY; + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 6); + InitCommunication (DRUUGE_CONVERSATION); + + if (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)) + return true; + + { + BOOLEAN DruugeSurvivors; + + DruugeSurvivors = + GetHeadLink (&GLOBAL (npc_built_ship_q)) != 0; + + GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY; + ReinitQueue (&GLOBAL (npc_built_ship_q)); + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + + if (DruugeSurvivors) + return true; + + RepairSISBorder (); + SET_GAME_STATE (BOMB_UNPROTECTED, 1); + } + } + + if (CurStarDescPtr->Index == BOMB_DEFINED) + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable (LoadGraphic (BOMB_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable (LoadStringTable (BOMB_STRTAB)); + } + else + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable (LoadStringTable (RUINS_STRTAB)); + } + } + + GenerateDefault_generateOrbital (solarSys, world); + + if (CurStarDescPtr->Index == UTWIG_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + solarSys->SysInfo.PlanetInfo.Weather = 1; + solarSys->SysInfo.PlanetInfo.Tectonics = 1; + } + + return true; +} + +static COUNT +GenerateUtwig_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (CurStarDescPtr->Index == UTWIG_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + return GenerateDefault_generateRuins (solarSys, whichNode, info); + } + + if (CurStarDescPtr->Index == BOMB_DEFINED + && matchWorld (solarSys, world, 5, 1)) + { + // This check is redundant since the retrieval bit will keep the + // node from showing up again + if (GET_GAME_STATE (UTWIG_BOMB)) + { // already picked up + return 0; + } + + return GenerateDefault_generateArtifact (solarSys, whichNode, info); + } + + return 0; +} + +static bool +GenerateUtwig_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (CurStarDescPtr->Index == UTWIG_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + // Standard ruins report + GenerateDefault_landerReportCycle (solarSys); + return false; + } + + if (CurStarDescPtr->Index == BOMB_DEFINED + && matchWorld (solarSys, world, 5, 1)) + { + assert (!GET_GAME_STATE (UTWIG_BOMB) && whichNode == 0); + + GenerateDefault_landerReport (solarSys); + SetLanderTakeoff (); + + SET_GAME_STATE (UTWIG_BOMB, 1); + SET_GAME_STATE (UTWIG_BOMB_ON_SHIP, 1); + SET_GAME_STATE (DRUUGE_MANNER, 1); + SET_GAME_STATE (DRUUGE_VISITS, 0); + SET_GAME_STATE (DRUUGE_HOME_VISITS, 0); + + return true; // picked up + } + + (void) whichNode; + return false; +} diff --git a/src/uqm/planets/generate/genvault.c b/src/uqm/planets/generate/genvault.c new file mode 100644 index 0000000..e189897 --- /dev/null +++ b/src/uqm/planets/generate/genvault.c @@ -0,0 +1,130 @@ +//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 "genall.h" +#include "../lander.h" +#include "../planets.h" +#include "../../globdata.h" +#include "../../nameref.h" +#include "../../resinst.h" +#include "libs/mathlib.h" + + +static bool GenerateVault_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GenerateVault_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GenerateVault_pickupEnergy (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + + +const GenerateFunctions generateVaultFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateDefault_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateVault_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateVault_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateVault_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateVault_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if (matchWorld (solarSys, world, 0, 0)) + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable (LoadGraphic (VAULT_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable (LoadStringTable (VAULT_STRTAB)); + if (GET_GAME_STATE (SHIP_VAULT_UNLOCKED)) + { + solarSys->SysInfo.PlanetInfo.DiscoveryString = + SetAbsStringTableIndex ( + solarSys->SysInfo.PlanetInfo.DiscoveryString, 2); + } + else if (GET_GAME_STATE (SYREEN_SHUTTLE_ON_SHIP)) + { + solarSys->SysInfo.PlanetInfo.DiscoveryString = + SetAbsStringTableIndex ( + solarSys->SysInfo.PlanetInfo.DiscoveryString, 1); + } + } + + GenerateDefault_generateOrbital (solarSys, world); + return true; +} + +static COUNT +GenerateVault_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (matchWorld (solarSys, world, 0, 0)) + { + return GenerateDefault_generateArtifact (solarSys, whichNode, info); + } + + return 0; +} + +static bool +GenerateVault_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (matchWorld (solarSys, world, 0, 0)) + { + assert (whichNode == 0); + + if (GET_GAME_STATE (SHIP_VAULT_UNLOCKED)) + { // Give the final report, "omg empty" and whatnot + GenerateDefault_landerReportCycle (solarSys); + } + else if (GET_GAME_STATE (SYREEN_SHUTTLE_ON_SHIP)) + { + GenerateDefault_landerReportCycle (solarSys); + SetLanderTakeoff (); + + SET_GAME_STATE (SHIP_VAULT_UNLOCKED, 1); + SET_GAME_STATE (SYREEN_SHUTTLE_ON_SHIP, 0); + SET_GAME_STATE (SYREEN_HOME_VISITS, 0); + } + else + { + GenerateDefault_landerReport (solarSys); + + if (!GET_GAME_STATE (KNOW_SYREEN_VAULT)) + { + SET_GAME_STATE (KNOW_SYREEN_VAULT, 1); + } + } + + // The Vault cannot be "picked up". It is always on the surface. + return false; + } + + (void) whichNode; + return false; +} diff --git a/src/uqm/planets/generate/genvux.c b/src/uqm/planets/generate/genvux.c new file mode 100644 index 0000000..d4a0642 --- /dev/null +++ b/src/uqm/planets/generate/genvux.c @@ -0,0 +1,329 @@ +//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 "genall.h" +#include "../lander.h" +#include "../lifeform.h" +#include "../planets.h" +#include "../../build.h" +#include "../../comm.h" +#include "../../gendef.h" +#include "../../starmap.h" +#include "../../globdata.h" +#include "../../ipdisp.h" +#include "../../nameref.h" +#include "../../setup.h" +#include "../../sounds.h" +#include "../../state.h" +#include "libs/mathlib.h" + + +static bool GenerateVux_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateVux_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GenerateVux_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static COUNT GenerateVux_generateLife (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GenerateVux_pickupEnergy (SOLARSYS_STATE *, PLANET_DESC *world, + COUNT whichNode); +static bool GenerateVux_pickupLife (SOLARSYS_STATE *, PLANET_DESC *world, + COUNT whichNode); + + +const GenerateFunctions generateVuxFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateVux_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateVux_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateVux_generateEnergy, + /* .generateLife = */ GenerateVux_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateVux_pickupEnergy, + /* .pickupLife = */ GenerateVux_pickupLife, +}; + + +static bool +GenerateVux_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT angle; + + GenerateDefault_generatePlanets (solarSys); + + if (CurStarDescPtr->Index == MAIDENS_DEFINED) + { + GenerateDefault_generatePlanets (solarSys); + // XXX: this is the second time that this function is + // called. Is it safe to remove one, or does this change + // the RNG so that the outcome is different? + solarSys->PlanetDesc[0].data_index = REDUX_WORLD; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 212L / 100; + angle = ARCTAN (solarSys->PlanetDesc[0].location.x, + solarSys->PlanetDesc[0].location.y); + solarSys->PlanetDesc[0].location.x = + COSINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].location.y = + SINE (angle, solarSys->PlanetDesc[0].radius); + } + else + { + if (CurStarDescPtr->Index == VUX_DEFINED) + { + solarSys->PlanetDesc[0].data_index = REDUX_WORLD; + solarSys->PlanetDesc[0].NumPlanets = 1; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 42L / 100; + angle = HALF_CIRCLE + OCTANT; + } + else /* if (CurStarDescPtr->Index == VUX_BEAST_DEFINED) */ + { + memmove (&solarSys->PlanetDesc[1], &solarSys->PlanetDesc[0], + sizeof (solarSys->PlanetDesc[0]) + * solarSys->SunDesc[0].NumPlanets); + ++solarSys->SunDesc[0].NumPlanets; + + angle = HALF_CIRCLE - OCTANT; + solarSys->PlanetDesc[0].data_index = WATER_WORLD; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 110L / 100; + solarSys->PlanetDesc[0].NumPlanets = 0; + } + + solarSys->PlanetDesc[0].location.x = + COSINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].location.y = + SINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].rand_seed = MAKE_DWORD ( + solarSys->PlanetDesc[0].location.x, + solarSys->PlanetDesc[0].location.y); + } + return true; +} + +static bool +GenerateVux_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if ((matchWorld (solarSys, world, 0, MATCH_PLANET) + && (CurStarDescPtr->Index == VUX_DEFINED + || (CurStarDescPtr->Index == MAIDENS_DEFINED + && !GET_GAME_STATE (ZEX_IS_DEAD)))) + && StartSphereTracking (VUX_SHIP)) + { + NotifyOthers (VUX_SHIP, IPNL_ALL_CLEAR); + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + CloneShipFragment (VUX_SHIP, + &GLOBAL (npc_built_ship_q), INFINITE_FLEET); + if (CurStarDescPtr->Index == VUX_DEFINED) + { + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); + } + else + { + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 6); + } + + GLOBAL (CurrentActivity) |= START_INTERPLANETARY; + InitCommunication (VUX_CONVERSATION); + + if (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)) + return true; + + { + GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY; + ReinitQueue (&GLOBAL (npc_built_ship_q)); + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + + if (CurStarDescPtr->Index == VUX_DEFINED + || !GET_GAME_STATE (ZEX_IS_DEAD)) + return true; + + RepairSISBorder (); + } + } + + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + if (CurStarDescPtr->Index == MAIDENS_DEFINED) + { + if (!GET_GAME_STATE (SHOFIXTI_MAIDENS)) + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = CaptureDrawable ( + LoadGraphic (MAIDENS_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable ( + LoadStringTable (MAIDENS_STRTAB)); + } + } + else if (CurStarDescPtr->Index == VUX_BEAST_DEFINED) + { + if (!GET_GAME_STATE (VUX_BEAST)) + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = 0; + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable ( + LoadStringTable (BEAST_STRTAB)); + } + } + else + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable (LoadStringTable (RUINS_STRTAB)); + } + } + + GenerateDefault_generateOrbital (solarSys, world); + + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + solarSys->SysInfo.PlanetInfo.Weather = 2; + solarSys->SysInfo.PlanetInfo.Tectonics = 0; + } + + return true; +} + +static COUNT +GenerateVux_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (CurStarDescPtr->Index == MAIDENS_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + // This check is redundant since the retrieval bit will keep the + // node from showing up again + if (GET_GAME_STATE (SHOFIXTI_MAIDENS)) + { // already picked up + return 0; + } + + if (info) + { + info->loc_pt.x = MAP_WIDTH / 3; + info->loc_pt.y = MAP_HEIGHT * 5 / 8; + } + + return 1; // only matters when count is requested + } + + if (CurStarDescPtr->Index == VUX_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + return GenerateDefault_generateRuins (solarSys, whichNode, info); + } + + return 0; +} + +static bool +GenerateVux_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (CurStarDescPtr->Index == MAIDENS_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + assert (!GET_GAME_STATE (SHOFIXTI_MAIDENS) && whichNode == 0); + + GenerateDefault_landerReport (solarSys); + SetLanderTakeoff (); + + SET_GAME_STATE (SHOFIXTI_MAIDENS, 1); + SET_GAME_STATE (MAIDENS_ON_SHIP, 1); + + return true; // picked up + } + + if (CurStarDescPtr->Index == VUX_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + // Standard ruins report + GenerateDefault_landerReportCycle (solarSys); + return false; + } + + (void) whichNode; + return false; +} + +static COUNT +GenerateVux_generateLife (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (CurStarDescPtr->Index == MAIDENS_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + static const SBYTE life[] = + { + 9, 9, 9, 9, /* Carousel Beast */ + 14, 14, 14, 14, /* Amorphous Trandicula */ + 18, 18, 18, 18, /* Penguin Cyclops */ + -1 /* term */ + }; + return GeneratePresetLife (&solarSys->SysInfo, life, whichNode, info); + } + + if (CurStarDescPtr->Index == VUX_BEAST_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + static const SBYTE life[] = + { + NUM_CREATURE_TYPES + 2, /* VUX Beast */ + // Must be the first node, see pickupLife() below + 3, 3, 3, 3, 3, /* Whackin' Bush */ + 8, 8, 8, 8, 8, /* Glowing Medusa */ + -1 /* term */ + }; + return GeneratePresetLife (&solarSys->SysInfo, life, whichNode, info); + } + + return GenerateDefault_generateLife (solarSys, world, whichNode, info); +} + +static bool +GenerateVux_pickupLife (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (CurStarDescPtr->Index == VUX_BEAST_DEFINED + && matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + if (whichNode == 0) + { // Picked up Zex' Beauty + assert (!GET_GAME_STATE (VUX_BEAST)); + + GenerateDefault_landerReport (solarSys); + SetLanderTakeoff (); + + SET_GAME_STATE (VUX_BEAST, 1); + SET_GAME_STATE (VUX_BEAST_ON_SHIP, 1); + } + + return true; // picked up + } + + return GenerateDefault_pickupLife (solarSys, world, whichNode); +} diff --git a/src/uqm/planets/generate/genwreck.c b/src/uqm/planets/generate/genwreck.c new file mode 100644 index 0000000..05e956e --- /dev/null +++ b/src/uqm/planets/generate/genwreck.c @@ -0,0 +1,111 @@ +//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 "genall.h" +#include "../lander.h" +#include "../planets.h" +#include "../../globdata.h" +#include "../../nameref.h" +#include "../../resinst.h" +#include "libs/mathlib.h" + + +static bool GenerateWreck_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GenerateWreck_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GenerateWreck_pickupEnergy (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + + +const GenerateFunctions generateWreckFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateDefault_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateWreck_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateWreck_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateWreck_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateWreck_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if (matchWorld (solarSys, world, 6, MATCH_PLANET)) + { + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable (LoadGraphic (WRECK_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable (LoadStringTable (WRECK_STRTAB)); + if (GET_GAME_STATE (PORTAL_KEY)) + { // Already picked it up, skip the first report + solarSys->SysInfo.PlanetInfo.DiscoveryString = + SetAbsStringTableIndex ( + solarSys->SysInfo.PlanetInfo.DiscoveryString, 1); + } + } + + GenerateDefault_generateOrbital (solarSys, world); + return true; +} + +static COUNT +GenerateWreck_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (matchWorld (solarSys, world, 6, MATCH_PLANET)) + { + return GenerateDefault_generateArtifact (solarSys, whichNode, info); + } + + return 0; +} + +static bool +GenerateWreck_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (matchWorld (solarSys, world, 6, MATCH_PLANET)) + { + assert (whichNode == 0); + + GenerateDefault_landerReportCycle (solarSys); + + if (!GET_GAME_STATE (PORTAL_KEY)) + { + SetLanderTakeoff (); + + SET_GAME_STATE (PORTAL_KEY, 1); + SET_GAME_STATE (PORTAL_KEY_ON_SHIP, 1); + } + + // The Wreck cannot be "picked up". It is always on the surface. + return false; + } + + (void) whichNode; + return false; +} diff --git a/src/uqm/planets/generate/genyeh.c b/src/uqm/planets/generate/genyeh.c new file mode 100644 index 0000000..caae543 --- /dev/null +++ b/src/uqm/planets/generate/genyeh.c @@ -0,0 +1,140 @@ +//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 "genall.h" +#include "../planets.h" +#include "../../build.h" +#include "../../comm.h" +#include "../../globdata.h" +#include "../../ipdisp.h" +#include "../../nameref.h" +#include "../../state.h" +#include "libs/mathlib.h" + + +static bool GenerateYehat_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateYehat_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GenerateYehat_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GenerateYehat_pickupEnergy (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + + +const GenerateFunctions generateYehatFunctions = { + /* .initNpcs = */ GenerateDefault_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateYehat_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateYehat_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateYehat_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateYehat_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateYehat_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT angle; + + GenerateDefault_generatePlanets (solarSys); + solarSys->PlanetDesc[0].data_index = WATER_WORLD; + solarSys->PlanetDesc[0].NumPlanets = 1; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 106L / 100; + angle = ARCTAN (solarSys->PlanetDesc[0].location.x, + solarSys->PlanetDesc[0].location.y); + solarSys->PlanetDesc[0].location.x = + COSINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].location.y = + SINE (angle, solarSys->PlanetDesc[0].radius); + + return true; +} + +static bool +GenerateYehat_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + if (StartSphereTracking (YEHAT_SHIP)) + { + NotifyOthers (YEHAT_SHIP, IPNL_ALL_CLEAR); + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + CloneShipFragment (YEHAT_SHIP, &GLOBAL (npc_built_ship_q), + INFINITE_FLEET); + + GLOBAL (CurrentActivity) |= START_INTERPLANETARY; + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); + InitCommunication (YEHAT_CONVERSATION); + + if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))) + { + GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY; + ReinitQueue (&GLOBAL (npc_built_ship_q)); + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + } + + return true; + } + + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable (LoadStringTable (RUINS_STRTAB)); + } + + GenerateDefault_generateOrbital (solarSys, world); + return true; +} + +static COUNT +GenerateYehat_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + return GenerateDefault_generateRuins (solarSys, whichNode, info); + } + + return 0; +} + +static bool +GenerateYehat_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + // Standard ruins report + GenerateDefault_landerReportCycle (solarSys); + return false; + } + + (void) whichNode; + return false; +} diff --git a/src/uqm/planets/generate/genzfpscout.c b/src/uqm/planets/generate/genzfpscout.c new file mode 100644 index 0000000..93a6d5d --- /dev/null +++ b/src/uqm/planets/generate/genzfpscout.c @@ -0,0 +1,96 @@ +//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 "genall.h" +#include "../../build.h" +#include "../../globdata.h" +#include "../../grpinfo.h" +#include "../../state.h" + + +static bool GenerateZoqFotPikScout_initNpcs (SOLARSYS_STATE *solarSys); +static bool GenerateZoqFotPikScout_reinitNpcs (SOLARSYS_STATE *solarSys); + + +const GenerateFunctions generateZoqFotPikScoutFunctions = { + /* .initNpcs = */ GenerateZoqFotPikScout_initNpcs, + /* .reinitNpcs = */ GenerateZoqFotPikScout_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateDefault_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateDefault_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateDefault_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateDefault_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateZoqFotPikScout_initNpcs (SOLARSYS_STATE *solarSys) +{ + if (!GET_GAME_STATE (MET_ZOQFOT)) + { + GLOBAL (BattleGroupRef) = GET_GAME_STATE_32 (ZOQFOT_GRPOFFS0); + if (GLOBAL (BattleGroupRef) == 0) + { + CloneShipFragment (ZOQFOTPIK_SHIP, + &GLOBAL (npc_built_ship_q), 0); + GLOBAL (BattleGroupRef) = PutGroupInfo (GROUPS_ADD_NEW, 1); + ReinitQueue (&GLOBAL (npc_built_ship_q)); + SET_GAME_STATE_32 (ZOQFOT_GRPOFFS0, GLOBAL (BattleGroupRef)); + } + } + + GenerateDefault_initNpcs (solarSys); + + return true; +} + +static bool +GenerateZoqFotPikScout_reinitNpcs (SOLARSYS_STATE *solarSys) +{ + HIPGROUP hGroup; + IP_GROUP *GroupPtr; + + GenerateDefault_reinitNpcs (solarSys); + + if (!GLOBAL (BattleGroupRef)) + return true; // nothing to check + + hGroup = GetHeadLink (&GLOBAL (ip_group_q)); + if (!hGroup) + return true; // still nothing to check + + GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup); + // REFORM_GROUP was set in ipdisp.c:ip_group_collision() + // during a collision with the flagship. + if (GroupPtr->race_id == ZOQFOTPIK_SHIP + && (GroupPtr->task & REFORM_GROUP)) + { + GroupPtr->task = FLEE | IGNORE_FLAGSHIP | REFORM_GROUP; + GroupPtr->dest_loc = 0; + } + UnlockIpGroup (&GLOBAL (ip_group_q), hGroup); + + return true; +} + diff --git a/src/uqm/planets/generate/genzoq.c b/src/uqm/planets/generate/genzoq.c new file mode 100644 index 0000000..9b30f89 --- /dev/null +++ b/src/uqm/planets/generate/genzoq.c @@ -0,0 +1,170 @@ +//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 "genall.h" +#include "../planets.h" +#include "../../build.h" +#include "../../comm.h" +#include "../../globdata.h" +#include "../../nameref.h" +#include "../../state.h" +#include "libs/mathlib.h" + + +static bool GenerateZoqFotPik_initNpcs (SOLARSYS_STATE *solarSys); +static bool GenerateZoqFotPik_generatePlanets (SOLARSYS_STATE *solarSys); +static bool GenerateZoqFotPik_generateOrbital (SOLARSYS_STATE *solarSys, + PLANET_DESC *world); +static COUNT GenerateZoqFotPik_generateEnergy (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *); +static bool GenerateZoqFotPik_pickupEnergy (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT whichNode); + + +const GenerateFunctions generateZoqFotPikFunctions = { + /* .initNpcs = */ GenerateZoqFotPik_initNpcs, + /* .reinitNpcs = */ GenerateDefault_reinitNpcs, + /* .uninitNpcs = */ GenerateDefault_uninitNpcs, + /* .generatePlanets = */ GenerateZoqFotPik_generatePlanets, + /* .generateMoons = */ GenerateDefault_generateMoons, + /* .generateName = */ GenerateDefault_generateName, + /* .generateOrbital = */ GenerateZoqFotPik_generateOrbital, + /* .generateMinerals = */ GenerateDefault_generateMinerals, + /* .generateEnergy = */ GenerateZoqFotPik_generateEnergy, + /* .generateLife = */ GenerateDefault_generateLife, + /* .pickupMinerals = */ GenerateDefault_pickupMinerals, + /* .pickupEnergy = */ GenerateZoqFotPik_pickupEnergy, + /* .pickupLife = */ GenerateDefault_pickupLife, +}; + + +static bool +GenerateZoqFotPik_initNpcs (SOLARSYS_STATE *solarSys) +{ + if (GET_GAME_STATE (ZOQFOT_DISTRESS) != 1) + GenerateDefault_initNpcs (solarSys); + + return true; +} + +static bool +GenerateZoqFotPik_generatePlanets (SOLARSYS_STATE *solarSys) +{ + COUNT angle; + + GenerateDefault_generatePlanets (solarSys); + + solarSys->PlanetDesc[0].data_index = REDUX_WORLD; + solarSys->PlanetDesc[0].NumPlanets = 1; + solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 138L / 100; + angle = ARCTAN (solarSys->PlanetDesc[0].location.x, + solarSys->PlanetDesc[0].location.y); + solarSys->PlanetDesc[0].location.x = + COSINE (angle, solarSys->PlanetDesc[0].radius); + solarSys->PlanetDesc[0].location.y = + SINE (angle, solarSys->PlanetDesc[0].radius); + + return true; +} + +static bool +GenerateZoqFotPik_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + if (StartSphereTracking (ZOQFOTPIK_SHIP)) + { + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + ReinitQueue (&GLOBAL (ip_group_q)); + assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); + + if (GET_GAME_STATE (ZOQFOT_DISTRESS)) + { + CloneShipFragment (BLACK_URQUAN_SHIP, + &GLOBAL (npc_built_ship_q), 0); + + GLOBAL (CurrentActivity) |= START_INTERPLANETARY; + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); + InitCommunication (BLACKURQ_CONVERSATION); + + if (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)) + return true; + + if (GetHeadLink (&GLOBAL (npc_built_ship_q))) + { + GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY; + ReinitQueue (&GLOBAL (npc_built_ship_q)); + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + return true; + } + } + + CloneShipFragment (ZOQFOTPIK_SHIP, &GLOBAL (npc_built_ship_q), + INFINITE_FLEET); + + GLOBAL (CurrentActivity) |= START_INTERPLANETARY; + SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); + InitCommunication (ZOQFOTPIK_CONVERSATION); + + if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))) + { + GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY; + ReinitQueue (&GLOBAL (npc_built_ship_q)); + GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP); + } + + return true; + } + + LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo); + solarSys->PlanetSideFrame[1] = + CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM)); + solarSys->SysInfo.PlanetInfo.DiscoveryString = + CaptureStringTable (LoadStringTable (RUINS_STRTAB)); + } + + GenerateDefault_generateOrbital (solarSys, world); + return true; +} + +static COUNT +GenerateZoqFotPik_generateEnergy (const SOLARSYS_STATE *solarSys, + const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + return GenerateDefault_generateRuins (solarSys, whichNode, info); + } + + return 0; +} + +static bool +GenerateZoqFotPik_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world, + COUNT whichNode) +{ + if (matchWorld (solarSys, world, 0, MATCH_PLANET)) + { + // Standard ruins report + GenerateDefault_landerReportCycle (solarSys); + return false; + } + + (void) whichNode; + return false; +} diff --git a/src/uqm/planets/gentopo.c b/src/uqm/planets/gentopo.c new file mode 100644 index 0000000..5212143 --- /dev/null +++ b/src/uqm/planets/gentopo.c @@ -0,0 +1,206 @@ +//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. + */ + +// See doc/devel/planettopo for details. + +#include "libs/gfxlib.h" +#include "libs/mathlib.h" +#include "planets.h" + +void +DeltaTopography (COUNT num_iterations, SBYTE *DepthArray, RECT *pRect, + SIZE depth_delta) +{ + SIZE width, height, delta_y; + struct + { + COORD x_top, x_bot; + SIZE x_incr, delta_x, error_term; + } LineDDA0, LineDDA1; + + width = pRect->extent.width; + height = pRect->extent.height; + delta_y = (height - 1) << 1; + do + { + SIZE d; + COUNT h, w1, w2; + DWORD rand_val; + SBYTE *lpDst; + + if ((RandomContext_Random (SysGenRNG) & 1) == 0) + depth_delta = -depth_delta; + + rand_val = RandomContext_Random (SysGenRNG); + w1 = LOWORD (rand_val); + w2 = HIWORD (rand_val); + + LineDDA0.x_top = LOBYTE (w1) % width; + LineDDA0.x_bot = HIBYTE (w1) % width; + LineDDA0.delta_x = (LineDDA0.x_bot - LineDDA0.x_top) << 1; + if (LineDDA0.delta_x >= 0) + LineDDA0.x_incr = 1; + else + { + LineDDA0.x_incr = -1; + LineDDA0.delta_x = -LineDDA0.delta_x; + } + if (LineDDA0.delta_x > delta_y) + LineDDA0.error_term = -(LineDDA0.delta_x >> 1); + else + LineDDA0.error_term = -(delta_y >> 1); + + LineDDA1.x_top = (LOBYTE (w2) % (width - 1)) + LineDDA0.x_top + 1; + LineDDA1.x_bot = (HIBYTE (w2) % (width - 1)) + LineDDA0.x_bot + 1; + LineDDA1.delta_x = (LineDDA1.x_bot - LineDDA1.x_top) << 1; + if (LineDDA1.delta_x >= 0) + LineDDA1.x_incr = 1; + else + { + LineDDA1.x_incr = -1; + LineDDA1.delta_x = -LineDDA1.delta_x; + } + if (LineDDA1.delta_x > delta_y) + LineDDA1.error_term = -(LineDDA1.delta_x >> 1); + else + LineDDA1.error_term = -(delta_y >> 1); + + lpDst = &DepthArray[LineDDA0.x_top]; + h = height; + do + { + COUNT w; + + w1 = LineDDA1.x_top - LineDDA0.x_top; + w2 = width - w1; + + if ((int)(LineDDA0.x_top + w1) > (int)width) + w = width - LineDDA0.x_top; + else + { + w = w1; + LineDDA0.x_top += w1; + } + w1 -= w; + while (w--) + { + d = *lpDst + depth_delta; + if (d >= -128 && d <= 127) + *lpDst = (SBYTE)d; + ++lpDst; + } + if (w1 == 0) + { + if (LineDDA0.x_top == width) + { + LineDDA0.x_top = 0; + lpDst -= width; + } + } + else + { + LineDDA0.x_top = w1; + lpDst -= width; + do + { + d = *lpDst + depth_delta; + if (d >= -128 && d <= 127) + *lpDst = (SBYTE)d; + ++lpDst; + } while (--w1); + } + + if ((int)(LineDDA0.x_top + w2) > (int)width) + w = width - LineDDA0.x_top; + else + { + w = w2; + LineDDA0.x_top += w2; + } + w2 -= w; + while (w--) + { + d = *lpDst - depth_delta; + if (d >= -128 && d <= 127) + *lpDst = (SBYTE)d; + ++lpDst; + } + if (w2 == 0) + { + if (LineDDA0.x_top == width) + { + LineDDA0.x_top = 0; + lpDst -= width; + } + } + else + { + LineDDA0.x_top = w2; + lpDst -= width; + do + { + d = *lpDst - depth_delta; + if (d >= -128 && d <= 127) + *lpDst = (SBYTE)d; + ++lpDst; + } while (--w2); + } + + lpDst += pRect->extent.width; + + if (delta_y >= LineDDA0.delta_x) + { + if ((LineDDA0.error_term += LineDDA0.delta_x) >= 0) + { + lpDst += LineDDA0.x_incr; + LineDDA0.x_top += LineDDA0.x_incr; + LineDDA0.error_term -= delta_y; + } + } + else + { + do + { + lpDst += LineDDA0.x_incr; + LineDDA0.x_top += LineDDA0.x_incr; + } while ((LineDDA0.error_term += delta_y) < 0); + LineDDA0.error_term -= LineDDA0.delta_x; + } + + if (delta_y >= LineDDA1.delta_x) + { + if ((LineDDA1.error_term += LineDDA1.delta_x) >= 0) + { + LineDDA1.x_top += LineDDA1.x_incr; + LineDDA1.error_term -= delta_y; + } + } + else + { + do + { + LineDDA1.x_top += LineDDA1.x_incr; + } while ((LineDDA1.error_term += delta_y) < 0); + LineDDA1.error_term -= LineDDA1.delta_x; + } + } while (--h); + } while (--num_iterations); +} + + + 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 (); +} diff --git a/src/uqm/planets/lander.h b/src/uqm/planets/lander.h new file mode 100644 index 0000000..d41129b --- /dev/null +++ b/src/uqm/planets/lander.h @@ -0,0 +1,88 @@ +//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. + */ + +#ifndef UQM_PLANETS_LANDER_H_ +#define UQM_PLANETS_LANDER_H_ + +#include "elemdata.h" +#include "libs/compiler.h" +#include "libs/gfxlib.h" +#include "libs/sndlib.h" +#include "libs/timelib.h" +#include "../element.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +// Surface magnification shift (x4) +#define MAG_SHIFT 2 + +#define NUM_TEXT_FRAMES 32 + +// XXX: This is a private type now. Move it to lander.c? +// We may also want to merge it with LanderInputState. +typedef struct +{ + BOOLEAN InTransit; + // Landing on or taking of from a planet. + // Setting it while landed will initiate takeoff. + + COUNT ElementLevel; + COUNT MaxElementLevel; + COUNT BiologicalLevel; + COUNT ElementAmounts[NUM_ELEMENT_CATEGORIES]; + + COUNT NumFrames; + UNICODE AmountBuf[40]; + TEXT MineralText[3]; + + Color ColorCycle[NUM_TEXT_FRAMES >> 1]; + + BYTE TectonicsChance; + BYTE WeatherChance; + BYTE FireChance; +} PLANETSIDE_DESC; + +extern MUSIC_REF LanderMusic; + +extern void PlanetSide (POINT planetLoc); +extern void DoDiscoveryReport (SOUND ReadOutSounds); +extern void SetPlanetMusic (BYTE planet_type); +extern void LoadLanderData (void); +extern void FreeLanderData (void); + +extern void object_animation (ELEMENT *ElementPtr); + +extern void SetLanderTakeoff (void); +extern bool KillLanderCrewSeq (COUNT numKilled, DWORD period); + +extern unsigned GetThermalHazardRating (int temp); + +// ELEMENT.playerNr constants +enum +{ + PS_HUMAN_PLAYER, + PS_NON_PLAYER, +}; + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_PLANETS_LANDER_H_ */ diff --git a/src/uqm/planets/lifeform.h b/src/uqm/planets/lifeform.h new file mode 100644 index 0000000..fbe2d9d --- /dev/null +++ b/src/uqm/planets/lifeform.h @@ -0,0 +1,75 @@ +//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. + */ + +#ifndef UQM_PLANETS_LIFEFORM_H_ +#define UQM_PLANETS_LIFEFORM_H_ + +#include "libs/compiler.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +#define BEHAVIOR_HUNT (0 << 0) +#define BEHAVIOR_FLEE (1 << 0) +#define BEHAVIOR_UNPREDICTABLE (2 << 0) + +#define BEHAVIOR_MASK 0x03 +#define BEHAVIOR_SHIFT 0 + +#define AWARENESS_LOW (0 << 2) +#define AWARENESS_MEDIUM (1 << 2) +#define AWARENESS_HIGH (2 << 2) + +#define AWARENESS_MASK 0x0C +#define AWARENESS_SHIFT (BEHAVIOR_SHIFT + 2) + +#define SPEED_MOTIONLESS (0 << 4) +#define SPEED_SLOW (1 << 4) +#define SPEED_MEDIUM (2 << 4) +#define SPEED_FAST (3 << 4) + +#define SPEED_MASK 0x30 +#define SPEED_SHIFT (AWARENESS_SHIFT + 2) + +#define DANGER_HARMLESS (0 << 6) +#define DANGER_WEAK (1 << 6) +#define DANGER_NORMAL (2 << 6) +#define DANGER_MONSTROUS (3 << 6) + +#define DANGER_MASK 0xC0 +#define DANGER_SHIFT (SPEED_SHIFT + 2) + +#define NUM_CREATURE_TYPES 23 +#define NUM_SPECIAL_CREATURE_TYPES 3 +#define MAX_LIFE_VARIATION 3 + +#define CREATURE_AWARE (BYTE)(1 << 7) + +typedef struct +{ + BYTE Attributes, ValueAndHitPoints; +} LIFEFORM_DESC; + +extern const LIFEFORM_DESC CreatureData[]; + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_PLANETS_LIFEFORM_H */ diff --git a/src/uqm/planets/orbits.c b/src/uqm/planets/orbits.c new file mode 100644 index 0000000..f1ad0f4 --- /dev/null +++ b/src/uqm/planets/orbits.c @@ -0,0 +1,629 @@ +//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 "planets.h" +#include "../starmap.h" +#include "libs/compiler.h" +#include "libs/mathlib.h" +#include "libs/log.h" + + +//#define DEBUG_ORBITS + +enum +{ + PLANET_NEVER = 0, + PLANET_RARE = 15, + PLANET_FEW = 63, + PLANET_COMMON = 127, + PLANET_ALWAYS = 255 +}; + +static BYTE +BlueDistribution (BYTE which_world) +{ + const BYTE PlanetDistribution[NUMBER_OF_PLANET_TYPES] = + { + PLANET_ALWAYS, /* OOLITE_WORLD */ + PLANET_ALWAYS, /* YTTRIC_WORLD */ + PLANET_ALWAYS, /* QUASI_DEGENERATE_WORLD */ + PLANET_ALWAYS, /* LANTHANIDE_WORLD */ + PLANET_ALWAYS, /* TREASURE_WORLD */ + PLANET_ALWAYS, /* UREA_WORLD */ + PLANET_ALWAYS, /* METAL_WORLD */ + PLANET_ALWAYS, /* RADIOACTIVE_WORLD */ + PLANET_ALWAYS, /* OPALESCENT_WORLD */ + PLANET_ALWAYS, /* CYANIC_WORLD */ + PLANET_ALWAYS, /* ACID_WORLD */ + PLANET_ALWAYS, /* ALKALI_WORLD */ + PLANET_ALWAYS, /* HALIDE_WORLD */ + PLANET_ALWAYS, /* GREEN_WORLD */ + PLANET_ALWAYS, /* COPPER_WORLD */ + PLANET_ALWAYS, /* CARBIDE_WORLD */ + PLANET_ALWAYS, /* ULTRAMARINE_WORLD */ + PLANET_ALWAYS, /* NOBLE_WORLD */ + PLANET_ALWAYS, /* AZURE_WORLD */ + PLANET_NEVER, /* CHONDRITE_WORLD */ + PLANET_NEVER, /* PURPLE_WORLD */ + PLANET_NEVER, /* SUPER_DENSE_WORLD */ + PLANET_NEVER, /* PELLUCID_WORLD */ + PLANET_NEVER, /* DUST_WORLD */ + PLANET_NEVER, /* CRIMSON_WORLD */ + PLANET_NEVER, /* CIMMERIAN_WORLD */ + PLANET_NEVER, /* INFRARED_WORLD */ + PLANET_ALWAYS, /* SELENIC_WORLD */ + PLANET_ALWAYS, /* AURIC_WORLD */ + + PLANET_ALWAYS, /* FLUORESCENT_WORLD */ + PLANET_ALWAYS, /* ULTRAVIOLET_WORLD */ + PLANET_ALWAYS, /* PLUTONIC_WORLD */ + PLANET_NEVER, /* RAINBOW_WORLD */ + PLANET_NEVER, /* SHATTERED_WORLD */ + PLANET_ALWAYS, /* SAPPHIRE_WORLD */ + PLANET_ALWAYS, /* ORGANIC_WORLD */ + PLANET_ALWAYS, /* XENOLITHIC_WORLD */ + PLANET_ALWAYS, /* REDUX_WORLD */ + PLANET_ALWAYS, /* PRIMORDIAL_WORLD */ + PLANET_NEVER, /* EMERALD_WORLD */ + PLANET_ALWAYS, /* CHLORINE_WORLD */ + PLANET_ALWAYS, /* MAGNETIC_WORLD */ + PLANET_ALWAYS, /* WATER_WORLD */ + PLANET_ALWAYS, /* TELLURIC_WORLD */ + PLANET_NEVER, /* HYDROCARBON_WORLD */ + PLANET_NEVER, /* IODINE_WORLD */ + PLANET_NEVER, /* VINYLOGOUS_WORLD */ + PLANET_NEVER, /* RUBY_WORLD */ + PLANET_NEVER, /* MAGMA_WORLD */ + PLANET_NEVER, /* MAROON_WORLD */ + + PLANET_ALWAYS, /* BLU_GAS_GIANT */ + PLANET_ALWAYS, /* CYA_GAS_GIANT */ + PLANET_ALWAYS, /* GRN_GAS_GIANT */ + PLANET_ALWAYS, /* GRY_GAS_GIANT */ + PLANET_ALWAYS, /* ORA_GAS_GIANT */ + PLANET_ALWAYS, /* PUR_GAS_GIANT */ + PLANET_ALWAYS, /* RED_GAS_GIANT */ + PLANET_ALWAYS, /* VIO_GAS_GIANT */ + PLANET_ALWAYS, /* YEL_GAS_GIANT */ + }; + + return (PlanetDistribution[which_world]); +} + +static BYTE +GreenDistribution (BYTE which_world) +{ + const BYTE PlanetDistribution[NUMBER_OF_PLANET_TYPES] = + { + PLANET_NEVER, /* OOLITE_WORLD */ + PLANET_NEVER, /* YTTRIC_WORLD */ + PLANET_ALWAYS, /* QUASI_DEGENERATE_WORLD */ + PLANET_ALWAYS, /* LANTHANIDE_WORLD */ + PLANET_ALWAYS, /* TREASURE_WORLD */ + PLANET_ALWAYS, /* UREA_WORLD */ + PLANET_ALWAYS, /* METAL_WORLD */ + PLANET_ALWAYS, /* RADIOACTIVE_WORLD */ + PLANET_ALWAYS, /* OPALESCENT_WORLD */ + PLANET_ALWAYS, /* CYANIC_WORLD */ + PLANET_ALWAYS, /* ACID_WORLD */ + PLANET_ALWAYS, /* ALKALI_WORLD */ + PLANET_ALWAYS, /* HALIDE_WORLD */ + PLANET_ALWAYS, /* GREEN_WORLD */ + PLANET_ALWAYS, /* COPPER_WORLD */ + PLANET_ALWAYS, /* CARBIDE_WORLD */ + PLANET_ALWAYS, /* ULTRAMARINE_WORLD */ + PLANET_ALWAYS, /* NOBLE_WORLD */ + PLANET_ALWAYS, /* AZURE_WORLD */ + PLANET_ALWAYS, /* CHONDRITE_WORLD */ + PLANET_ALWAYS, /* PURPLE_WORLD */ + PLANET_ALWAYS, /* SUPER_DENSE_WORLD */ + PLANET_ALWAYS, /* PELLUCID_WORLD */ + PLANET_NEVER, /* DUST_WORLD */ + PLANET_NEVER, /* CRIMSON_WORLD */ + PLANET_NEVER, /* CIMMERIAN_WORLD */ + PLANET_NEVER, /* INFRARED_WORLD */ + PLANET_ALWAYS, /* SELENIC_WORLD */ + PLANET_ALWAYS, /* AURIC_WORLD */ + + PLANET_ALWAYS, /* FLUORESCENT_WORLD */ + PLANET_ALWAYS, /* ULTRAVIOLET_WORLD */ + PLANET_ALWAYS, /* PLUTONIC_WORLD */ + PLANET_NEVER, /* RAINBOW_WORLD */ + PLANET_NEVER, /* SHATTERED_WORLD */ + PLANET_NEVER, /* SAPPHIRE_WORLD */ + PLANET_ALWAYS, /* ORGANIC_WORLD */ + PLANET_ALWAYS, /* XENOLITHIC_WORLD */ + PLANET_ALWAYS, /* REDUX_WORLD */ + PLANET_ALWAYS, /* PRIMORDIAL_WORLD */ + PLANET_ALWAYS, /* EMERALD_WORLD */ + PLANET_ALWAYS, /* CHLORINE_WORLD */ + PLANET_ALWAYS, /* MAGNETIC_WORLD */ + PLANET_ALWAYS, /* WATER_WORLD */ + PLANET_ALWAYS, /* TELLURIC_WORLD */ + PLANET_ALWAYS, /* HYDROCARBON_WORLD */ + PLANET_ALWAYS, /* IODINE_WORLD */ + PLANET_NEVER, /* VINYLOGOUS_WORLD */ + PLANET_NEVER, /* RUBY_WORLD */ + PLANET_NEVER, /* MAGMA_WORLD */ + PLANET_NEVER, /* MAROON_WORLD */ + + PLANET_ALWAYS, /* BLU_GAS_GIANT */ + PLANET_ALWAYS, /* CYA_GAS_GIANT */ + PLANET_ALWAYS, /* GRN_GAS_GIANT */ + PLANET_ALWAYS, /* GRY_GAS_GIANT */ + PLANET_ALWAYS, /* ORA_GAS_GIANT */ + PLANET_ALWAYS, /* PUR_GAS_GIANT */ + PLANET_ALWAYS, /* RED_GAS_GIANT */ + PLANET_ALWAYS, /* VIO_GAS_GIANT */ + PLANET_ALWAYS, /* YEL_GAS_GIANT */ + }; + + return (PlanetDistribution[which_world]); +} + +static BYTE +OrangeDistribution (BYTE which_world) +{ + const BYTE PlanetDistribution[NUMBER_OF_PLANET_TYPES] = + { + PLANET_NEVER, /* OOLITE_WORLD */ + PLANET_NEVER, /* YTTRIC_WORLD */ + PLANET_NEVER, /* QUASI_DEGENERATE_WORLD */ + PLANET_NEVER, /* LANTHANIDE_WORLD */ + PLANET_NEVER, /* TREASURE_WORLD */ + PLANET_ALWAYS, /* UREA_WORLD */ + PLANET_NEVER, /* METAL_WORLD */ + PLANET_ALWAYS, /* RADIOACTIVE_WORLD */ + PLANET_NEVER, /* OPALESCENT_WORLD */ + PLANET_NEVER, /* CYANIC_WORLD */ + PLANET_ALWAYS, /* ACID_WORLD */ + PLANET_ALWAYS, /* ALKALI_WORLD */ + PLANET_ALWAYS, /* HALIDE_WORLD */ + PLANET_ALWAYS, /* GREEN_WORLD */ + PLANET_ALWAYS, /* COPPER_WORLD */ + PLANET_ALWAYS, /* CARBIDE_WORLD */ + PLANET_ALWAYS, /* ULTRAMARINE_WORLD */ + PLANET_ALWAYS, /* NOBLE_WORLD */ + PLANET_ALWAYS, /* AZURE_WORLD */ + PLANET_ALWAYS, /* CHONDRITE_WORLD */ + PLANET_ALWAYS, /* PURPLE_WORLD */ + PLANET_ALWAYS, /* SUPER_DENSE_WORLD */ + PLANET_ALWAYS, /* PELLUCID_WORLD */ + PLANET_ALWAYS, /* DUST_WORLD */ + PLANET_ALWAYS, /* CRIMSON_WORLD */ + PLANET_ALWAYS, /* CIMMERIAN_WORLD */ + PLANET_ALWAYS, /* INFRARED_WORLD */ + PLANET_ALWAYS, /* SELENIC_WORLD */ + PLANET_NEVER, /* AURIC_WORLD */ + + PLANET_NEVER, /* FLUORESCENT_WORLD */ + PLANET_NEVER, /* ULTRAVIOLET_WORLD */ + PLANET_NEVER, /* PLUTONIC_WORLD */ + PLANET_NEVER, /* RAINBOW_WORLD */ + PLANET_NEVER, /* SHATTERED_WORLD */ + PLANET_NEVER, /* SAPPHIRE_WORLD */ + PLANET_NEVER, /* ORGANIC_WORLD */ + PLANET_NEVER, /* XENOLITHIC_WORLD */ + PLANET_NEVER, /* REDUX_WORLD */ + PLANET_NEVER, /* PRIMORDIAL_WORLD */ + PLANET_NEVER, /* EMERALD_WORLD */ + PLANET_ALWAYS, /* CHLORINE_WORLD */ + PLANET_ALWAYS, /* MAGNETIC_WORLD */ + PLANET_ALWAYS, /* WATER_WORLD */ + PLANET_ALWAYS, /* TELLURIC_WORLD */ + PLANET_ALWAYS, /* HYDROCARBON_WORLD */ + PLANET_ALWAYS, /* IODINE_WORLD */ + PLANET_ALWAYS, /* VINYLOGOUS_WORLD */ + PLANET_NEVER, /* RUBY_WORLD */ + PLANET_ALWAYS, /* MAGMA_WORLD */ + PLANET_ALWAYS, /* MAROON_WORLD */ + + PLANET_ALWAYS, /* BLU_GAS_GIANT */ + PLANET_ALWAYS, /* CYA_GAS_GIANT */ + PLANET_ALWAYS, /* GRN_GAS_GIANT */ + PLANET_ALWAYS, /* GRY_GAS_GIANT */ + PLANET_ALWAYS, /* ORA_GAS_GIANT */ + PLANET_ALWAYS, /* PUR_GAS_GIANT */ + PLANET_ALWAYS, /* RED_GAS_GIANT */ + PLANET_ALWAYS, /* VIO_GAS_GIANT */ + PLANET_ALWAYS, /* YEL_GAS_GIANT */ + }; + + return (PlanetDistribution[which_world]); +} + +static BYTE +RedDistribution (BYTE which_world) +{ + const BYTE PlanetDistribution[NUMBER_OF_PLANET_TYPES] = + { + PLANET_NEVER, /* OOLITE_WORLD */ + PLANET_NEVER, /* YTTRIC_WORLD */ + PLANET_NEVER, /* QUASI_DEGENERATE_WORLD */ + PLANET_NEVER, /* LANTHANIDE_WORLD */ + PLANET_NEVER, /* TREASURE_WORLD */ + PLANET_ALWAYS, /* UREA_WORLD */ + PLANET_ALWAYS, /* METAL_WORLD */ + PLANET_NEVER, /* RADIOACTIVE_WORLD */ + PLANET_NEVER, /* OPALESCENT_WORLD */ + PLANET_NEVER, /* CYANIC_WORLD */ + PLANET_NEVER, /* ACID_WORLD */ + PLANET_NEVER, /* ALKALI_WORLD */ + PLANET_NEVER, /* HALIDE_WORLD */ + PLANET_NEVER, /* GREEN_WORLD */ + PLANET_NEVER, /* COPPER_WORLD */ + PLANET_NEVER, /* CARBIDE_WORLD */ + PLANET_ALWAYS, /* ULTRAMARINE_WORLD */ + PLANET_ALWAYS, /* NOBLE_WORLD */ + PLANET_ALWAYS, /* AZURE_WORLD */ + PLANET_ALWAYS, /* CHONDRITE_WORLD */ + PLANET_ALWAYS, /* PURPLE_WORLD */ + PLANET_ALWAYS, /* SUPER_DENSE_WORLD */ + PLANET_ALWAYS, /* PELLUCID_WORLD */ + PLANET_ALWAYS, /* DUST_WORLD */ + PLANET_ALWAYS, /* CRIMSON_WORLD */ + PLANET_ALWAYS, /* CIMMERIAN_WORLD */ + PLANET_ALWAYS, /* INFRARED_WORLD */ + PLANET_ALWAYS, /* SELENIC_WORLD */ + PLANET_NEVER, /* AURIC_WORLD */ + + PLANET_NEVER, /* FLUORESCENT_WORLD */ + PLANET_NEVER, /* ULTRAVIOLET_WORLD */ + PLANET_NEVER, /* PLUTONIC_WORLD */ + PLANET_NEVER, /* RAINBOW_WORLD */ + PLANET_NEVER, /* SHATTERED_WORLD */ + PLANET_NEVER, /* SAPPHIRE_WORLD */ + PLANET_NEVER, /* ORGANIC_WORLD */ + PLANET_NEVER, /* XENOLITHIC_WORLD */ + PLANET_NEVER, /* REDUX_WORLD */ + PLANET_NEVER, /* PRIMORDIAL_WORLD */ + PLANET_NEVER, /* EMERALD_WORLD */ + PLANET_NEVER, /* CHLORINE_WORLD */ + PLANET_ALWAYS, /* MAGNETIC_WORLD */ + PLANET_ALWAYS, /* WATER_WORLD */ + PLANET_ALWAYS, /* TELLURIC_WORLD */ + PLANET_ALWAYS, /* HYDROCARBON_WORLD */ + PLANET_ALWAYS, /* IODINE_WORLD */ + PLANET_ALWAYS, /* VINYLOGOUS_WORLD */ + PLANET_ALWAYS, /* RUBY_WORLD */ + PLANET_ALWAYS, /* MAGMA_WORLD */ + PLANET_ALWAYS, /* MAROON_WORLD */ + + PLANET_ALWAYS, /* BLU_GAS_GIANT */ + PLANET_ALWAYS, /* CYA_GAS_GIANT */ + PLANET_ALWAYS, /* GRN_GAS_GIANT */ + PLANET_ALWAYS, /* GRY_GAS_GIANT */ + PLANET_ALWAYS, /* ORA_GAS_GIANT */ + PLANET_ALWAYS, /* PUR_GAS_GIANT */ + PLANET_ALWAYS, /* RED_GAS_GIANT */ + PLANET_ALWAYS, /* VIO_GAS_GIANT */ + PLANET_ALWAYS, /* YEL_GAS_GIANT */ + }; + + return (PlanetDistribution[which_world]); +} + +static BYTE +WhiteDistribution (BYTE which_world) +{ + const BYTE PlanetDistribution[NUMBER_OF_PLANET_TYPES] = + { + PLANET_ALWAYS, /* OOLITE_WORLD */ + PLANET_ALWAYS, /* YTTRIC_WORLD */ + PLANET_ALWAYS, /* QUASI_DEGENERATE_WORLD */ + PLANET_ALWAYS, /* LANTHANIDE_WORLD */ + PLANET_ALWAYS, /* TREASURE_WORLD */ + PLANET_ALWAYS, /* UREA_WORLD */ + PLANET_ALWAYS, /* METAL_WORLD */ + PLANET_ALWAYS, /* RADIOACTIVE_WORLD */ + PLANET_ALWAYS, /* OPALESCENT_WORLD */ + PLANET_ALWAYS, /* CYANIC_WORLD */ + PLANET_ALWAYS, /* ACID_WORLD */ + PLANET_ALWAYS, /* ALKALI_WORLD */ + PLANET_ALWAYS, /* HALIDE_WORLD */ + PLANET_NEVER, /* GREEN_WORLD */ + PLANET_NEVER, /* COPPER_WORLD */ + PLANET_NEVER, /* CARBIDE_WORLD */ + PLANET_NEVER, /* ULTRAMARINE_WORLD */ + PLANET_NEVER, /* NOBLE_WORLD */ + PLANET_NEVER, /* AZURE_WORLD */ + PLANET_NEVER, /* CHONDRITE_WORLD */ + PLANET_NEVER, /* PURPLE_WORLD */ + PLANET_NEVER, /* SUPER_DENSE_WORLD */ + PLANET_NEVER, /* PELLUCID_WORLD */ + PLANET_NEVER, /* DUST_WORLD */ + PLANET_NEVER, /* CRIMSON_WORLD */ + PLANET_NEVER, /* CIMMERIAN_WORLD */ + PLANET_NEVER, /* INFRARED_WORLD */ + PLANET_ALWAYS, /* SELENIC_WORLD */ + PLANET_ALWAYS, /* AURIC_WORLD */ + + PLANET_ALWAYS, /* FLUORESCENT_WORLD */ + PLANET_ALWAYS, /* ULTRAVIOLET_WORLD */ + PLANET_ALWAYS, /* PLUTONIC_WORLD */ + PLANET_NEVER, /* RAINBOW_WORLD */ + PLANET_NEVER, /* SHATTERED_WORLD */ + PLANET_ALWAYS, /* SAPPHIRE_WORLD */ + PLANET_ALWAYS, /* ORGANIC_WORLD */ + PLANET_ALWAYS, /* XENOLITHIC_WORLD */ + PLANET_ALWAYS, /* REDUX_WORLD */ + PLANET_ALWAYS, /* PRIMORDIAL_WORLD */ + PLANET_ALWAYS, /* EMERALD_WORLD */ + PLANET_NEVER, /* CHLORINE_WORLD */ + PLANET_NEVER, /* MAGNETIC_WORLD */ + PLANET_NEVER, /* WATER_WORLD */ + PLANET_NEVER, /* TELLURIC_WORLD */ + PLANET_NEVER, /* HYDROCARBON_WORLD */ + PLANET_NEVER, /* IODINE_WORLD */ + PLANET_ALWAYS, /* VINYLOGOUS_WORLD */ + PLANET_ALWAYS, /* RUBY_WORLD */ + PLANET_NEVER, /* MAGMA_WORLD */ + PLANET_NEVER, /* MAROON_WORLD */ + + PLANET_ALWAYS, /* BLU_GAS_GIANT */ + PLANET_ALWAYS, /* CYA_GAS_GIANT */ + PLANET_ALWAYS, /* GRN_GAS_GIANT */ + PLANET_ALWAYS, /* GRY_GAS_GIANT */ + PLANET_ALWAYS, /* ORA_GAS_GIANT */ + PLANET_ALWAYS, /* PUR_GAS_GIANT */ + PLANET_ALWAYS, /* RED_GAS_GIANT */ + PLANET_ALWAYS, /* VIO_GAS_GIANT */ + PLANET_ALWAYS, /* YEL_GAS_GIANT */ + }; + + return (PlanetDistribution[which_world]); +} + +static BYTE +YellowDistribution (BYTE which_world) +{ + const BYTE PlanetDistribution[NUMBER_OF_PLANET_TYPES] = + { + PLANET_NEVER, /* OOLITE_WORLD */ + PLANET_NEVER, /* YTTRIC_WORLD */ + PLANET_NEVER, /* QUASI_DEGENERATE_WORLD */ + PLANET_NEVER, /* LANTHANIDE_WORLD */ + PLANET_ALWAYS, /* TREASURE_WORLD */ + PLANET_ALWAYS, /* UREA_WORLD */ + PLANET_ALWAYS, /* METAL_WORLD */ + PLANET_ALWAYS, /* RADIOACTIVE_WORLD */ + PLANET_ALWAYS, /* OPALESCENT_WORLD */ + PLANET_ALWAYS, /* CYANIC_WORLD */ + PLANET_ALWAYS, /* ACID_WORLD */ + PLANET_ALWAYS, /* ALKALI_WORLD */ + PLANET_ALWAYS, /* HALIDE_WORLD */ + PLANET_ALWAYS, /* GREEN_WORLD */ + PLANET_ALWAYS, /* COPPER_WORLD */ + PLANET_ALWAYS, /* CARBIDE_WORLD */ + PLANET_ALWAYS, /* ULTRAMARINE_WORLD */ + PLANET_ALWAYS, /* NOBLE_WORLD */ + PLANET_ALWAYS, /* AZURE_WORLD */ + PLANET_ALWAYS, /* CHONDRITE_WORLD */ + PLANET_ALWAYS, /* PURPLE_WORLD */ + PLANET_ALWAYS, /* SUPER_DENSE_WORLD */ + PLANET_ALWAYS, /* PELLUCID_WORLD */ + PLANET_ALWAYS, /* DUST_WORLD */ + PLANET_ALWAYS, /* CRIMSON_WORLD */ + PLANET_ALWAYS, /* CIMMERIAN_WORLD */ + PLANET_ALWAYS, /* INFRARED_WORLD */ + PLANET_ALWAYS, /* SELENIC_WORLD */ + PLANET_ALWAYS, /* AURIC_WORLD */ + + PLANET_NEVER, /* FLUORESCENT_WORLD */ + PLANET_NEVER, /* ULTRAVIOLET_WORLD */ + PLANET_NEVER, /* PLUTONIC_WORLD */ + PLANET_NEVER, /* RAINBOW_WORLD */ + PLANET_NEVER, /* SHATTERED_WORLD */ + PLANET_NEVER, /* SAPPHIRE_WORLD */ + PLANET_ALWAYS, /* ORGANIC_WORLD */ + PLANET_ALWAYS, /* XENOLITHIC_WORLD */ + PLANET_ALWAYS, /* REDUX_WORLD */ + PLANET_ALWAYS, /* PRIMORDIAL_WORLD */ + PLANET_NEVER, /* EMERALD_WORLD */ + PLANET_ALWAYS, /* CHLORINE_WORLD */ + PLANET_ALWAYS, /* MAGNETIC_WORLD */ + PLANET_ALWAYS, /* WATER_WORLD */ + PLANET_ALWAYS, /* TELLURIC_WORLD */ + PLANET_ALWAYS, /* HYDROCARBON_WORLD */ + PLANET_ALWAYS, /* IODINE_WORLD */ + PLANET_ALWAYS, /* VINYLOGOUS_WORLD */ + PLANET_NEVER, /* RUBY_WORLD */ + PLANET_ALWAYS, /* MAGMA_WORLD */ + PLANET_ALWAYS, /* MAROON_WORLD */ + + PLANET_ALWAYS, /* BLU_GAS_GIANT */ + PLANET_ALWAYS, /* CYA_GAS_GIANT */ + PLANET_ALWAYS, /* GRN_GAS_GIANT */ + PLANET_ALWAYS, /* GRY_GAS_GIANT */ + PLANET_ALWAYS, /* ORA_GAS_GIANT */ + PLANET_ALWAYS, /* PUR_GAS_GIANT */ + PLANET_ALWAYS, /* RED_GAS_GIANT */ + PLANET_ALWAYS, /* VIO_GAS_GIANT */ + PLANET_ALWAYS, /* YEL_GAS_GIANT */ + }; + + return (PlanetDistribution[which_world]); +} + +#define DWARF_ROCK_DIST MIN_PLANET_RADIUS +#define DWARF_GASG_DIST SCALE_RADIUS (12) + +#define GIANT_ROCK_DIST SCALE_RADIUS (8) +#define GIANT_GASG_DIST SCALE_RADIUS (13) + +#define SUPERGIANT_ROCK_DIST SCALE_RADIUS (16) +#define SUPERGIANT_GASG_DIST SCALE_RADIUS (33) + +void +FillOrbits (SOLARSYS_STATE *system, BYTE NumPlanets, + PLANET_DESC *pBaseDesc, BOOLEAN TypesDefined) +{ /* Generate Planets in orbit around star */ + BYTE StarColor, PlanetCount, MaxPlanet; + BOOLEAN GeneratingMoons; + COUNT StarSize; + PLANET_DESC *pPD; + struct + { + COUNT MinRockyDist, MinGasGDist; + } Suns[] = + { + {DWARF_ROCK_DIST, DWARF_GASG_DIST}, + {GIANT_ROCK_DIST, GIANT_GASG_DIST}, + {SUPERGIANT_ROCK_DIST, SUPERGIANT_GASG_DIST}, + }; +#ifdef DEBUG_ORBITS +UNICODE buf[256]; +char stype[] = {'D', 'G', 'S'}; +char scolor[] = {'B', 'G', 'O', 'R', 'W', 'Y'}; +#endif /* DEBUG_ORBITS */ + + pPD = pBaseDesc; + StarSize = system->SunDesc[0].data_index; + StarColor = STAR_COLOR (CurStarDescPtr->Type); + + if (NumPlanets == (BYTE)~0) + { +#define MAX_GENERATED_PLANETS 9 + // XXX: This is pretty funny. Instead of calling RNG once, like so: + // 1 + Random % MAX_GENERATED_PLANETS + // we spin in a loop until the result > 0. + // Note that this behavior must be kept to preserve the universe. + do + NumPlanets = LOWORD (RandomContext_Random (SysGenRNG)) + % (MAX_GENERATED_PLANETS + 1); + while (NumPlanets == 0); + system->SunDesc[0].NumPlanets = NumPlanets; + } + +#ifdef DEBUG_ORBITS + GetClusterName (CurStarDescPtr, buf); + log_add (log_Debug, "cluster name = %s color = %c type = %c", buf, + scolor[STAR_COLOR (CurStarDescPtr->Type)], + stype[STAR_TYPE (CurStarDescPtr->Type)]); +#endif /* DEBUG_ORBITS */ + GeneratingMoons = (BOOLEAN) (pBaseDesc == system->MoonDesc); + if (GeneratingMoons) + MaxPlanet = FIRST_LARGE_ROCKY_WORLD; + else + MaxPlanet = NUMBER_OF_PLANET_TYPES; + PlanetCount = NumPlanets; + while (NumPlanets--) + { + BYTE chance; + DWORD rand_val; + COUNT min_radius, angle; + SIZE delta_r; + PLANET_DESC *pLocPD; + + do + { + rand_val = RandomContext_Random (SysGenRNG); + if (TypesDefined) + rand_val = 0; + else + pPD->data_index = + (BYTE)(HIBYTE (LOWORD (rand_val)) % MaxPlanet); + + chance = PLANET_NEVER; + switch (StarColor) + { + case BLUE_BODY: + chance = BlueDistribution (pPD->data_index); + break; + case GREEN_BODY: + chance = GreenDistribution (pPD->data_index); + break; + case ORANGE_BODY: + chance = OrangeDistribution (pPD->data_index); + break; + case RED_BODY: + chance = RedDistribution (pPD->data_index); + break; + case WHITE_BODY: + chance = WhiteDistribution (pPD->data_index); + break; + case YELLOW_BODY: + chance = YellowDistribution (pPD->data_index); + break; + } + } while (LOBYTE (LOWORD (rand_val)) >= chance); + + if (pPD->data_index < FIRST_GAS_GIANT) + min_radius = Suns[StarSize].MinRockyDist; + else + min_radius = Suns[StarSize].MinGasGDist; +RelocatePlanet: + rand_val = RandomContext_Random (SysGenRNG); + if (GeneratingMoons) + { + pPD->radius = MIN_MOON_RADIUS + + ((LOWORD (rand_val) % MAX_MOONS) * MOON_DELTA); + for (pLocPD = pPD - 1; pLocPD >= pBaseDesc; --pLocPD) + { + if (pPD->radius == pLocPD->radius) + goto RelocatePlanet; + } + pPD->NumPlanets = 0; + } + else + { + pPD->radius = + (LOWORD (rand_val) % (MAX_PLANET_RADIUS - min_radius)) + + min_radius; + for (pLocPD = pPD - 1; pLocPD >= pBaseDesc; --pLocPD) + { + delta_r = UNSCALE_RADIUS (pLocPD->radius) / 5 + - UNSCALE_RADIUS (pPD->radius) / 5; + if (delta_r < 0) + delta_r = -delta_r; + if (delta_r <= 1) + goto RelocatePlanet; + } + } + + rand_val = RandomContext_Random (SysGenRNG); + angle = NORMALIZE_ANGLE (LOWORD (rand_val)); + pPD->location.x = COSINE (angle, pPD->radius); + pPD->location.y = SINE (angle, pPD->radius); + pPD->rand_seed = MAKE_DWORD (pPD->location.x, pPD->location.y); + + ++pPD; + } + + { + BYTE i; + + for (i = 0; i < PlanetCount; ++i) + { + BYTE j; + + for (j = (BYTE)(PlanetCount - 1); j > i; --j) + { + if (pBaseDesc[i].radius > pBaseDesc[j].radius) + { + PLANET_DESC temp; + + temp = pBaseDesc[i]; + pBaseDesc[i] = pBaseDesc[j]; + pBaseDesc[j] = temp; + } + } + } + } +} + diff --git a/src/uqm/planets/oval.c b/src/uqm/planets/oval.c new file mode 100644 index 0000000..4112997 --- /dev/null +++ b/src/uqm/planets/oval.c @@ -0,0 +1,329 @@ +//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 "../units.h" +#include "libs/gfxlib.h" +#include "libs/graphics/context.h" +#include "libs/graphics/drawable.h" + +#include "planets.h" + +#define NUM_QUADS 4 + +void +DrawOval (RECT *pRect, BYTE num_off_pixels) +{ +#define FIRST_QUAD (1 << 0) +#define SECOND_QUAD (1 << 1) +#define THIRD_QUAD (1 << 2) +#define FOURTH_QUAD (1 << 3) + COUNT off; + COORD x, y; + SIZE A, B; + long Asquared, TwoAsquared, + Bsquared, TwoBsquared; + long d, dx, dy; + BYTE quad_visible; + LINE corners; + POINT mp; + PRIMITIVE prim[NUM_QUADS]; + COUNT StartPrim; + RECT ClipRect; + BRESENHAM_LINE ClipLine; + + ClipRect.corner.x = ClipRect.corner.y = 0; + + corners.first.x = pRect->corner.x - ClipRect.corner.x; + corners.first.y = pRect->corner.y - ClipRect.corner.y; + corners.second.x = corners.first.x + pRect->extent.width - 1; + corners.second.y = corners.first.y + pRect->extent.height - 1; + if (corners.second.x <= corners.first.x + || corners.second.y <= corners.first.y) + { + if (corners.second.x < corners.first.x) + corners.second.x = corners.first.x; + if (corners.second.y < corners.first.y) + corners.second.y = corners.first.y; + + DrawLine (&corners); + return; + } + + ClipRect.extent.width = SIS_SCREEN_WIDTH; + ClipRect.extent.height = SIS_SCREEN_HEIGHT; + + quad_visible = 0; + mp.x = (corners.first.x + corners.second.x) >> 1; + mp.y = (corners.first.y + corners.second.y) >> 1; + ClipRect.corner.x = ClipRect.corner.y = 0; + + if (corners.first.y >= 0 && corners.first.y < ClipRect.extent.height + && corners.second.x >= 0 && corners.second.x < ClipRect.extent.width) + quad_visible |= FIRST_QUAD; + else + { + ClipLine.first.x = mp.x; + ClipLine.first.y = corners.first.y; + ClipLine.second.x = corners.second.x; + ClipLine.second.y = mp.y; + if (_clip_line (&ClipRect, &ClipLine)) + quad_visible |= FIRST_QUAD; + } + + if (corners.first.y >= 0 && corners.first.y < ClipRect.extent.height + && corners.first.x >= 0 && corners.first.x < ClipRect.extent.width) + quad_visible |= SECOND_QUAD; + else + { + ClipLine.first.x = mp.x; + ClipLine.first.y = corners.first.y; + ClipLine.second.x = corners.first.x; + ClipLine.second.y = mp.y; + if (_clip_line (&ClipRect, &ClipLine)) + quad_visible |= SECOND_QUAD; + } + + if (corners.second.y >= 0 && corners.second.y < ClipRect.extent.height + && corners.first.x >= 0 && corners.first.x < ClipRect.extent.width) + quad_visible |= THIRD_QUAD; + else + { + ClipLine.first.x = mp.x; + ClipLine.first.y = corners.second.y; + ClipLine.second.x = corners.first.x; + ClipLine.second.y = mp.y; + if (_clip_line (&ClipRect, &ClipLine)) + quad_visible |= THIRD_QUAD; + } + + if (corners.second.y >= 0 && corners.second.y < ClipRect.extent.height + && corners.second.x >= 0 && corners.second.x < ClipRect.extent.width) + quad_visible |= FOURTH_QUAD; + else + { + ClipLine.first.x = mp.x; + ClipLine.first.y = corners.second.y; + ClipLine.second.x = corners.second.x; + ClipLine.second.y = mp.y; + if (_clip_line (&ClipRect, &ClipLine)) + quad_visible |= FOURTH_QUAD; + } + + if (!quad_visible) + return; + + StartPrim = END_OF_LIST; + for (x = 0; x < NUM_QUADS; ++x) + { + if (quad_visible & (1 << x)) + { + SetPrimNextLink (&prim[x], StartPrim); + SetPrimType (&prim[x], POINT_PRIM); + SetPrimColor (&prim[x], _get_context_fg_color ()); + + StartPrim = x; + } + } + + A = pRect->extent.width >> 1; + B = pRect->extent.height >> 1; + + x = 0; + y = B; + + Asquared = ((long)A * A) << 1; + Bsquared = ((long)B * B) << 1; + do + { + Asquared >>= 1; + Bsquared >>= 1; + TwoAsquared = Asquared << 1; + dy = TwoAsquared * B; + } while (dy / B != TwoAsquared); + TwoBsquared = Bsquared << 1; + + dx = 0; + d = Bsquared - (dy >> 1) + (Asquared >> 2); + + off = 0; + A += pRect->corner.x; + B += pRect->corner.y; + while (dx < dy) + { + if (off-- == 0) + { + prim[0].Object.Point.x = prim[3].Object.Point.x = A + x; + prim[0].Object.Point.y = prim[1].Object.Point.y = B - y; + prim[1].Object.Point.x = prim[2].Object.Point.x = A - x; + prim[2].Object.Point.y = prim[3].Object.Point.y = B + y; + + DrawBatch (prim, StartPrim, 0); + off = num_off_pixels; + } + + if (d > 0) + { + --y; + dy -= TwoAsquared; + d -= dy; + } + + ++x; + dx += TwoBsquared; + d += Bsquared + dx; + } + + d += ((((Asquared - Bsquared) * 3) >> 1) - (dx + dy)) >> 1; + + while (y >= 0) + { + if (off-- == 0) + { + prim[0].Object.Point.x = prim[3].Object.Point.x = A + x; + prim[0].Object.Point.y = prim[1].Object.Point.y = B - y; + prim[1].Object.Point.x = prim[2].Object.Point.x = A - x; + prim[2].Object.Point.y = prim[3].Object.Point.y = B + y; + + DrawBatch (prim, StartPrim, 0); + off = num_off_pixels; + } + + if (d < 0) + { + ++x; + dx += TwoBsquared; + d += dx; + } + + --y; + dy -= TwoAsquared; + d += Asquared - dy; + } +} + +void +DrawFilledOval (RECT *pRect) +{ + COORD x, y; + SIZE A, B; + long Asquared, TwoAsquared, + Bsquared, TwoBsquared; + long d, dx, dy; + LINE corners; + PRIMITIVE prim[NUM_QUADS >> 1]; + COUNT StartPrim; + + corners.first.x = pRect->corner.x; + corners.first.y = pRect->corner.y; + corners.second.x = corners.first.x + pRect->extent.width - 1; + corners.second.y = corners.first.y + pRect->extent.height - 1; + if (corners.second.x <= corners.first.x + || corners.second.y <= corners.first.y) + { + if (corners.second.x < corners.first.x) + corners.second.x = corners.first.x; + if (corners.second.y < corners.first.y) + corners.second.y = corners.first.y; + + DrawLine (&corners); + return; + } + + StartPrim = END_OF_LIST; + for (x = 0; x < (NUM_QUADS >> 1); ++x) + { + SetPrimNextLink (&prim[x], StartPrim); + SetPrimType (&prim[x], RECTFILL_PRIM); + SetPrimColor (&prim[x], _get_context_fg_color ()); + prim[x].Object.Rect.extent.height = 1; + + StartPrim = x; + } + + A = pRect->extent.width >> 1; + B = pRect->extent.height >> 1; + + x = 0; + y = B; + + Asquared = ((long)A * A) << 1; + Bsquared = ((long)B * B) << 1; + do + { + Asquared >>= 1; + Bsquared >>= 1; + TwoAsquared = Asquared << 1; + dy = TwoAsquared * B; + } while (dy / B != TwoAsquared); + TwoBsquared = Bsquared << 1; + + dx = 0; + d = Bsquared - (dy >> 1) + (Asquared >> 2); + + A += pRect->corner.x; + B += pRect->corner.y; + while (dx < dy) + { + if (d > 0) + { + prim[0].Object.Rect.corner.x = + prim[1].Object.Rect.corner.x = A - x; + prim[0].Object.Rect.extent.width = + prim[1].Object.Rect.extent.width = (x << 1) + 1; + prim[0].Object.Rect.corner.y = B - y; + prim[1].Object.Rect.corner.y = B + y; + + DrawBatch (prim, StartPrim, 0); + + --y; + dy -= TwoAsquared; + d -= dy; + } + + ++x; + dx += TwoBsquared; + d += Bsquared + dx; + } + + d += ((((Asquared - Bsquared) * 3) >> 1) - (dx + dy)) >> 1; + + while (y >= 0) + { + prim[0].Object.Rect.corner.x = + prim[1].Object.Rect.corner.x = A - x; + prim[0].Object.Rect.extent.width = + prim[1].Object.Rect.extent.width = (x << 1) + 1; + prim[0].Object.Rect.corner.y = B - y; + prim[1].Object.Rect.corner.y = B + y; + + DrawBatch (prim, StartPrim, 0); + + if (d < 0) + { + ++x; + dx += TwoBsquared; + d += dx; + } + + --y; + dy -= TwoAsquared; + d += Asquared - dy; + } +} + + diff --git a/src/uqm/planets/pl_stuff.c b/src/uqm/planets/pl_stuff.c new file mode 100644 index 0000000..073e215 --- /dev/null +++ b/src/uqm/planets/pl_stuff.c @@ -0,0 +1,318 @@ +//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 "planets.h" +#include "../colors.h" +#include "../setup.h" +#include "libs/graphics/gfx_common.h" +#include "libs/graphics/drawable.h" +#include "libs/mathlib.h" +#include "scan.h" +#include "options.h" + +#include <math.h> + + +// define USE_ADDITIVE_SCAN_BLIT to use additive blittting +// instead of transparency for the planet scans. +// It still doesn't look right though (it is too bright) +#define USE_ADDITIVE_SCAN_BLIT + +static int rotFrameIndex; +static int rotDirection; +static bool throbShield; +static int rotPointIndex; + +// Draw the planet sphere and any extra graphic (like a shield) if present +void +DrawPlanetSphere (int x, int y) +{ + STAMP s; + PLANET_ORBIT *Orbit = &pSolarSysState->Orbit; + + s.origin.x = x; + s.origin.y = y; + + BatchGraphics (); + s.frame = Orbit->SphereFrame; + DrawStamp (&s); + if (Orbit->ObjectFrame) + { + s.frame = Orbit->ObjectFrame; + DrawStamp (&s); + } + UnbatchGraphics (); +} + +void +DrawDefaultPlanetSphere (void) +{ + CONTEXT oldContext; + + oldContext = SetContext (PlanetContext); + DrawPlanetSphere (SIS_SCREEN_WIDTH / 2, PLANET_ORG_Y); + SetContext (oldContext); +} + +void +InitSphereRotation (int direction, BOOLEAN shielded) +{ + PLANET_ORBIT *Orbit = &pSolarSysState->Orbit; + + rotDirection = direction; + rotPointIndex = 0; + throbShield = shielded && optWhichShield == OPT_3DO; + + if (throbShield) + { + // ObjectFrame must contain the shield graphic + Orbit->WorkFrame = Orbit->ObjectFrame; + // We need a scratch frame so that we can apply throbbing + // to the shield, so create one + Orbit->ObjectFrame = CaptureDrawable (CreateDrawable ( + WANT_PIXMAP | WANT_ALPHA, + GetFrameWidth (Orbit->ObjectFrame), + GetFrameHeight (Orbit->ObjectFrame), 2)); + } + + // Render the first sphere/shield frame + // Prepare will set the next one + rotFrameIndex = 1; + PrepareNextRotationFrame (); +} + +void +UninitSphereRotation (void) +{ + PLANET_ORBIT *Orbit = &pSolarSysState->Orbit; + + if (Orbit->WorkFrame) + { + DestroyDrawable (ReleaseDrawable (Orbit->ObjectFrame)); + Orbit->ObjectFrame = Orbit->WorkFrame; + Orbit->WorkFrame = NULL; + } +} + +void +PrepareNextRotationFrame (void) +{ + PLANET_ORBIT *Orbit = &pSolarSysState->Orbit; + + // Generate the next rotation frame + // We alternate between the frames because we do not call FlushGraphics() + // The frame we just drew may not have made it to the screen yet + rotFrameIndex ^= 1; + + // Go to next point, taking care of wraparounds + rotPointIndex += rotDirection; + if (rotPointIndex < 0) + rotPointIndex = MAP_WIDTH - 1; + else if (rotPointIndex >= MAP_WIDTH) + rotPointIndex = 0; + + // prepare the next sphere frame + Orbit->SphereFrame = SetAbsFrameIndex (Orbit->SphereFrame, rotFrameIndex); + RenderPlanetSphere (Orbit->SphereFrame, rotPointIndex, throbShield); + + if (throbShield) + { // prepare the next shield throb frame + Orbit->ObjectFrame = SetAbsFrameIndex (Orbit->ObjectFrame, + rotFrameIndex); + SetShieldThrobEffect (Orbit->WorkFrame, rotPointIndex, + Orbit->ObjectFrame); + } +} + +#define ZOOM_RATE 24 +#define ZOOM_TIME (ONE_SECOND * 6 / 5) + +// This takes care of zooming the planet sphere into place +// when entering orbit +void +ZoomInPlanetSphere (void) +{ + PLANET_ORBIT *Orbit = &pSolarSysState->Orbit; + const int base = GSCALE_IDENTITY; + int dx, dy; + int oldScale; + int oldMode; + int i; + int frameCount; + int zoomCorner; + RECT frameRect; + RECT repairRect; + TimeCount NextTime; + + frameCount = ZOOM_TIME / (ONE_SECOND / ZOOM_RATE); + + // Planet zoom in from a randomly chosen corner + zoomCorner = TFB_Random (); + dx = 1 - (zoomCorner & 1) * 2; + dy = 1 - (zoomCorner & 2); + + if (Orbit->ObjectFrame) + GetFrameRect (Orbit->ObjectFrame, &frameRect); + else + GetFrameRect (Orbit->SphereFrame, &frameRect); + repairRect = frameRect; + + for (i = 0; i <= frameCount; ++i) + { + double scale; + POINT pt; + + NextTime = GetTimeCounter () + (ONE_SECOND / ZOOM_RATE); + + // Use 1 + e^-2 - e^(-2x / frameCount)) function to get a decelerating + // zoom like the one 3DO does (supposedly) + if (i < frameCount) + scale = 1.134 - exp (-2.0 * i / frameCount); + else + scale = 1.0; // final frame + + // start from beyond the screen + pt.x = SIS_SCREEN_WIDTH / 2 + (int) (dx * (1.0 - scale) + * (SIS_SCREEN_WIDTH * 6 / 10) + 0.5); + pt.y = PLANET_ORG_Y + (int) (dy * (1.0 - scale) + * (SCAN_SCREEN_HEIGHT * 6 / 10) + 0.5); + + SetContext (PlanetContext); + + BatchGraphics (); + if (i > 0) + RepairBackRect (&repairRect); + + oldMode = SetGraphicScaleMode (TFB_SCALE_BILINEAR); + oldScale = SetGraphicScale ((int)(base * scale + 0.5)); + DrawPlanetSphere (pt.x, pt.y); + SetGraphicScale (oldScale); + SetGraphicScaleMode (oldMode); + + UnbatchGraphics (); + + repairRect.corner.x = pt.x + frameRect.corner.x; + repairRect.corner.y = pt.y + frameRect.corner.y; + + PrepareNextRotationFrame (); + + SleepThreadUntil (NextTime); + } +} + +void +RotatePlanetSphere (BOOLEAN keepRate) +{ + static TimeCount NextTime; + TimeCount Now = GetTimeCounter (); + + if (keepRate && Now < NextTime) + return; // not time yet + + NextTime = Now + PLANET_ROTATION_RATE; + DrawDefaultPlanetSphere (); + + PrepareNextRotationFrame (); +} + +static void +renderTintFrame (Color tintColor) +{ + PLANET_ORBIT *Orbit = &pSolarSysState->Orbit; + CONTEXT oldContext; + DrawMode mode, oldMode; + STAMP s; + RECT r; + + oldContext = SetContext (OffScreenContext); + SetContextFGFrame (Orbit->TintFrame); + SetContextClipRect (NULL); + // get the rect of the whole context (or our frame really) + GetContextClipRect (&r); + + // copy the topo frame to the tint frame + s.origin.x = 0; + s.origin.y = 0; + s.frame = pSolarSysState->TopoFrame; + DrawStamp (&s); + + // apply the tint +#ifdef USE_ADDITIVE_SCAN_BLIT + mode = MAKE_DRAW_MODE (DRAW_ADDITIVE, DRAW_FACTOR_1 / 2); +#else + mode = MAKE_DRAW_MODE (DRAW_ALPHA, DRAW_FACTOR_1 / 2); +#endif + oldMode = SetContextDrawMode (mode); + SetContextForeGroundColor (tintColor); + DrawFilledRectangle (&r); + SetContextDrawMode (oldMode); + + SetContext (oldContext); +} + +// tintColor.a is ignored +void +DrawPlanet (int tintY, Color tintColor) +{ + STAMP s; + PLANET_ORBIT *Orbit = &pSolarSysState->Orbit; + + s.origin.x = 0; + s.origin.y = 0; + + BatchGraphics (); + if (sameColor (tintColor, BLACK_COLOR)) + { // no tint -- just draw the surface + s.frame = pSolarSysState->TopoFrame; + DrawStamp (&s); + } + else + { // apply different scan type tints + FRAME tintFrame = Orbit->TintFrame; + int height = GetFrameHeight (tintFrame); + + if (!sameColor (tintColor, Orbit->TintColor)) + { + renderTintFrame (tintColor); + Orbit->TintColor = tintColor; + } + + if (tintY < height - 1) + { // untinted piece showing, draw regular topo + s.frame = pSolarSysState->TopoFrame; + DrawStamp (&s); + } + + if (tintY >= 0) + { // tinted piece showing, draw tinted piece + RECT oldClipRect; + RECT clipRect; + + // adjust cliprect to confine the tint + GetContextClipRect (&oldClipRect); + clipRect = oldClipRect; + clipRect.extent.height = tintY + 1; + SetContextClipRect (&clipRect); + s.frame = tintFrame; + DrawStamp (&s); + SetContextClipRect (&oldClipRect); + } + } + UnbatchGraphics (); +} + diff --git a/src/uqm/planets/plandata.h b/src/uqm/planets/plandata.h new file mode 100644 index 0000000..cd2a1c7 --- /dev/null +++ b/src/uqm/planets/plandata.h @@ -0,0 +1,318 @@ +//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. + */ + +#ifndef UQM_PLANETS_PLANDATA_H_ +#define UQM_PLANETS_PLANDATA_H_ + +#include "libs/reslib.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/*------------------------------ Type Defines ----------------------------- */ +#define NUMBER_OF_ORBITS 16 +#define VACANT 0xFF + +enum +{ + SMALL_ROCKY_WORLD = 0, + LARGE_ROCKY_WORLD, + GAS_GIANT, + + NUM_PLANET_TYPES +}; + +enum +{ + DWARF_STAR = 0, + GIANT_STAR, + SUPER_GIANT_STAR, + + NUM_STAR_TYPES +}; + +enum +{ + BLUE_BODY = 0, + GREEN_BODY, + ORANGE_BODY, + RED_BODY, + WHITE_BODY, + GRAY_BODY = WHITE_BODY, + YELLOW_BODY, + + NUM_STAR_COLORS, + + CYAN_BODY = NUM_STAR_COLORS, + PURPLE_BODY, + VIOLET_BODY +}; + +enum +{ + OWNER_NOBODY = 0, + OWNER_NEUTRAL, + OWNER_HIERARCHY, + + OWNER_PLAYER = (1 << 2) +}; + +#define STAR_OWNER_SHIFT 0 +#define STAR_TYPE_SHIFT 3 /* STAR_OWNER_SHIFT + 3 */ +#define STAR_COLOR_SHIFT 5 /* STAR_TYPE_SHIFT + 2 */ +#define STAR_COLOR_MASK (BYTE)(0xFF << STAR_COLOR_SHIFT) +#define STAR_TYPE_MASK (BYTE)((0xFF << STAR_TYPE_SHIFT) \ + & ~STAR_COLOR_MASK) +#define STAR_OWNER_MASK (BYTE)((0xFF << STAR_OWNER_SHIFT) \ + & ~(STAR_COLOR_MASK \ + | STAR_TYPE_MASK)) +#define STAR_UNKNOWN_MASK (STAR_OWNER_MASK & ~OWNER_PLAYER) + +#define MAKE_STAR(t,c,o) \ + (BYTE)((((BYTE)(t) << STAR_TYPE_SHIFT) & STAR_TYPE_MASK) \ + | (((BYTE)(c) << STAR_COLOR_SHIFT) & STAR_COLOR_MASK) \ + | (((BYTE)(o) << STAR_OWNER_SHIFT) & STAR_OWNER_MASK)) +#define STAR_TYPE(f) (BYTE)(((f) & STAR_TYPE_MASK) >> STAR_TYPE_SHIFT) +#define STAR_COLOR(f) (BYTE)(((f) & STAR_COLOR_MASK) >> STAR_COLOR_SHIFT) +#define STAR_OWNER(f) (BYTE)(((f) & STAR_OWNER_MASK) >> STAR_OWNER_SHIFT) +#define STAR_UNKNOWN(f) (BOOLEAN)((STAR_OWNER(f) \ + & STAR_UNKNOWN_MASK) == STAR_UNKNOWN_MASK) + +#define PLAN_SIZE_MASK 0x03 + +#define TOPO_ALGO (0 << 2) +#define CRATERED_ALGO (1 << 2) +#define GAS_GIANT_ALGO (2 << 2) +#define PLAN_ALGO_MASK 0x0C + +#define PLANSIZE(type) ((BYTE)((type) & PLAN_SIZE_MASK)) +#define PLANALGO(type) ((BYTE)((type) & PLAN_ALGO_MASK)) +#define PLANCOLOR(type) HINIBBLE (type) + +#define THIN_ATMOSPHERE 10 +#define NORMAL_ATMOSPHERE 75 +#define THICK_ATMOSPHERE 200 +#define SUPER_THICK_ATMOSPHERE 2500 +#define GAS_GIANT_ATMOSPHERE 0xFFFF + +enum +{ + FIRST_ROCKY_WORLD = 0, + FIRST_SMALL_ROCKY_WORLD = FIRST_ROCKY_WORLD, + + OOLITE_WORLD = FIRST_SMALL_ROCKY_WORLD, + YTTRIC_WORLD, + QUASI_DEGENERATE_WORLD, + LANTHANIDE_WORLD, + TREASURE_WORLD, + UREA_WORLD, + METAL_WORLD, + RADIOACTIVE_WORLD, + OPALESCENT_WORLD, + CYANIC_WORLD, + ACID_WORLD, + ALKALI_WORLD, + HALIDE_WORLD, + GREEN_WORLD, + COPPER_WORLD, + CARBIDE_WORLD, + ULTRAMARINE_WORLD, + NOBLE_WORLD, + AZURE_WORLD, + CHONDRITE_WORLD, + PURPLE_WORLD, + SUPER_DENSE_WORLD, + PELLUCID_WORLD, + DUST_WORLD, + CRIMSON_WORLD, + CIMMERIAN_WORLD, + INFRARED_WORLD, + SELENIC_WORLD, + AURIC_WORLD, + LAST_SMALL_ROCKY_WORLD = AURIC_WORLD, + + FIRST_LARGE_ROCKY_WORLD, + FLUORESCENT_WORLD = FIRST_LARGE_ROCKY_WORLD, + ULTRAVIOLET_WORLD, + PLUTONIC_WORLD, + RAINBOW_WORLD, + SHATTERED_WORLD, + SAPPHIRE_WORLD, + ORGANIC_WORLD, + XENOLITHIC_WORLD, + REDUX_WORLD, + PRIMORDIAL_WORLD, + EMERALD_WORLD, + CHLORINE_WORLD, + MAGNETIC_WORLD, + WATER_WORLD, + TELLURIC_WORLD, + HYDROCARBON_WORLD, + IODINE_WORLD, + VINYLOGOUS_WORLD, + RUBY_WORLD, + MAGMA_WORLD, + MAROON_WORLD, + LAST_LARGE_ROCKY_WORLD = MAROON_WORLD, + + FIRST_GAS_GIANT, + BLU_GAS_GIANT = FIRST_GAS_GIANT, /* Gas Giants */ + CYA_GAS_GIANT, + GRN_GAS_GIANT, + GRY_GAS_GIANT, + ORA_GAS_GIANT, + PUR_GAS_GIANT, + RED_GAS_GIANT, + VIO_GAS_GIANT, + YEL_GAS_GIANT, + LAST_GAS_GIANT = YEL_GAS_GIANT, + + NUMBER_OF_PLANET_TYPES, + + WORLD_TYPE_SPECIAL = 0x80, + PLANET_SHIELDED = WORLD_TYPE_SPECIAL, + + HIERARCHY_STARBASE = 127 | WORLD_TYPE_SPECIAL, + SA_MATRA = 126 | WORLD_TYPE_SPECIAL, +}; + +#define NUMBER_OF_SMALL_ROCKY_WORLDS (LAST_SMALL_ROCKY_WORLD - FIRST_SMALL_ROCKY_WORLD + 1) +#define NUMBER_OF_LARGE_ROCKY_WORLDS (LAST_LARGE_ROCKY_WORLD - FIRST_LARGE_ROCKY_WORLD + 1) +#define NUMBER_OF_ROCKY_WORLDS (NUMBER_OF_SMALL_ROCKY_WORLDS + NUMBER_OF_LARGE_ROCKY_WORLDS) +#define NUMBER_OF_GAS_GIANTS (LAST_GAS_GIANT - FIRST_GAS_GIANT + 1) + +// TODO: This struct is highly alignment and padding dependent and +// should not be used! The data is loaded as binary from files and +// cast to this struct. +typedef struct +{ + const SIZE level_tab[3]; + const BYTE xlat_tab[256]; +} XLAT_DESC; + +typedef struct +{ + BYTE ElementType; + /* Index of this element in element_array */ + BYTE Density; + /* bits 0-3: quantity of the deposits (maximum number of + * deposits), one of FEW, MODERATE, or NUMEROUS + * bits 4-7: quality of the deposit, one of LOW, MEDIUM, or HEAVY + */ +} ELEMENT_ENTRY; + +// PlanetFrame describes a type of planet. It is not used to describe +// individual planets. +typedef struct +{ + BYTE Type; + /* bits 0-1: size, one of SMALL_ROCKY_WORLD, LARGE_ROCKY_WORLD, or + * GAS_GIANT + * bits 2-3: map creation algoritm, one of TOPO_ALGO, + * CRATERED_ALGO, or GAS_GIANT_ALGO + * bits 4-7: interplanetary color, one of BLUE_BODY, GREEN_BODY, + * ORANGE_BODY, RED_BODY, WHITE_BODY (same as + * GRAY_BODY), YELLOW_BODY, CYAN_BODY, PURPLE_BODY, + * VIOLET_BODY) + */ + BYTE BaseTectonics; + /* Base constant for calculation of tectonic activity, + * relative to Earth at 100. + * One of: NO_TECTONICS, LOW_TECTONICS, MED_TECTONICS, + * HIGH_TECTONICS, or SUPER_TECTONICS + */ + BYTE AtmoAndDensity; + /* bits 0-3: planet density, one of GAS_DENSITY, LIGHT_DENSITY, + * LOW_DENSITY, NORMAL_DENSITY, HIGH_DENSITY, + * SUPER_DENSITY + * bits 4-7: atmosphere, one of LIGHT, MEDIUM, HEAVY, or + * (no define for this) super thick. + */ +#define NUM_USEFUL_ELEMENTS 8 + ELEMENT_ENTRY UsefulElements[NUM_USEFUL_ELEMENTS]; + /* Minerals on the planet */ + + RESOURCE CMapInstance; + /* Color map */ + RESOURCE XlatTabInstance; + /* Color translation map */ + + // Parameters for map-generation algoritms: + COUNT num_faults; + SIZE fault_depth; + COUNT num_blemishes; + SIZE base_elevation; +} PlanetFrame; + +typedef struct +{ + SIZE AxialTilt; + UWORD Tectonics; + UWORD Weather; + UWORD PlanetDensity; + UWORD PlanetRadius; + UWORD SurfaceGravity; + SIZE SurfaceTemperature; + UWORD RotationPeriod; + UWORD AtmoDensity; + SIZE LifeChance; + UWORD PlanetToSunDist; + + const PlanetFrame *PlanDataPtr; + + DWORD ScanSeed[NUM_SCAN_TYPES]; + DWORD ScanRetrieveMask[NUM_SCAN_TYPES]; + + STRING DiscoveryString; + FONT LanderFont; + FRAME LanderFontEff; +} PLANET_INFO; + +enum +{ + GAS_DENSITY, + LIGHT_DENSITY, + LOW_DENSITY, + NORMAL_DENSITY, + HIGH_DENSITY, + SUPER_DENSITY +}; + +extern UWORD CalcGravity (const PLANET_INFO*); + +#define EARTH_ATMOSPHERE 50 + +#define COLD_THRESHOLD -40 +#define HOT_THRESHOLD 100 + +/*------------------------------ Global Data ------------------------------ */ + +#define NO_TECTONICS 0 +#define LOW_TECTONICS 40 +#define MED_TECTONICS 80 +#define HIGH_TECTONICS 140 +#define SUPER_TECTONICS 200 + +extern const PlanetFrame *PlanData; + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_PLANETS_PLANDATA_H_ */ diff --git a/src/uqm/planets/planets.c b/src/uqm/planets/planets.c new file mode 100644 index 0000000..c76c2bb --- /dev/null +++ b/src/uqm/planets/planets.c @@ -0,0 +1,483 @@ +//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 "planets.h" + +#include "scan.h" +#include "lander.h" +#include "../colors.h" +#include "../element.h" +#include "../settings.h" +#include "../controls.h" +#include "../sounds.h" +#include "../gameopt.h" +#include "../shipcont.h" +#include "../setup.h" +#include "../uqmdebug.h" +#include "../resinst.h" +#include "../nameref.h" +#include "options.h" +#include "libs/graphics/gfx_common.h" + + +// PlanetOrbitMenu() items +enum PlanetMenuItems +{ + // XXX: Must match the enum in menustat.h + SCAN = 0, + STARMAP, + EQUIP_DEVICE, + CARGO, + ROSTER, + GAME_MENU, + NAVIGATION, +}; + +CONTEXT PlanetContext; + // Context for rotating planet view and lander surface view + +static void +CreatePlanetContext (void) +{ + CONTEXT oldContext; + RECT r; + + assert (PlanetContext == NULL); + + // PlanetContext rect is relative to SpaceContext + oldContext = SetContext (SpaceContext); + GetContextClipRect (&r); + + PlanetContext = CreateContext ("PlanetContext"); + SetContext (PlanetContext); + SetContextFGFrame (Screen); + r.extent.height -= MAP_HEIGHT + MAP_BORDER_HEIGHT; + SetContextClipRect (&r); + + SetContext (oldContext); +} + +static void +DestroyPlanetContext (void) +{ + if (PlanetContext) + { + DestroyContext (PlanetContext); + PlanetContext = NULL; + } +} + +void +DrawScannedObjects (BOOLEAN Reversed) +{ + HELEMENT hElement, hNextElement; + + for (hElement = Reversed ? GetTailElement () : GetHeadElement (); + hElement; hElement = hNextElement) + { + ELEMENT *ElementPtr; + + LockElement (hElement, &ElementPtr); + hNextElement = Reversed ? + GetPredElement (ElementPtr) : + GetSuccElement (ElementPtr); + + if (ElementPtr->state_flags & APPEARING) + { + STAMP s; + + s.origin = ElementPtr->current.location; + s.frame = ElementPtr->next.image.frame; + DrawStamp (&s); + } + + UnlockElement (hElement); + } +} + +void +DrawPlanetSurfaceBorder (void) +{ + CONTEXT oldContext; + RECT oldClipRect; + RECT clipRect; + RECT r; + + oldContext = SetContext (SpaceContext); + GetContextClipRect (&oldClipRect); + + // Expand the context clip-rect so that we can tweak the existing border + clipRect = oldClipRect; + clipRect.corner.x -= 1; + clipRect.extent.width += 2; + clipRect.extent.height += 1; + SetContextClipRect (&clipRect); + + BatchGraphics (); + + // Border bulk + SetContextForeGroundColor ( + BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08)); + r.corner.x = 0; + r.corner.y = clipRect.extent.height - MAP_HEIGHT - MAP_BORDER_HEIGHT; + r.extent.width = clipRect.extent.width; + r.extent.height = MAP_BORDER_HEIGHT - 2; + DrawFilledRectangle (&r); + + SetContextForeGroundColor (SIS_BOTTOM_RIGHT_BORDER_COLOR); + + // Border top shadow line + r.extent.width -= 1; + r.extent.height = 1; + r.corner.x = 1; + r.corner.y -= 1; + DrawFilledRectangle (&r); + + // XXX: We will need bulk left and right rects here if MAP_WIDTH changes + + // Right shadow line + r.extent.width = 1; + r.extent.height = MAP_HEIGHT + 2; + r.corner.y += MAP_BORDER_HEIGHT - 1; + r.corner.x = clipRect.extent.width - 1; + DrawFilledRectangle (&r); + + SetContextForeGroundColor (SIS_LEFT_BORDER_COLOR); + + // Left shadow line + r.corner.x -= MAP_WIDTH + 1; + DrawFilledRectangle (&r); + + // Border bottom shadow line + r.extent.width = MAP_WIDTH + 2; + r.extent.height = 1; + DrawFilledRectangle (&r); + + UnbatchGraphics (); + + SetContextClipRect (&oldClipRect); + SetContext (oldContext); +} + +typedef enum +{ + DRAW_ORBITAL_FULL, + DRAW_ORBITAL_WAIT, + DRAW_ORBITAL_UPDATE, + +} DRAW_ORBITAL_MODE; + +static void +DrawOrbitalDisplay (DRAW_ORBITAL_MODE Mode) +{ + RECT r; + + SetContext (SpaceContext); + GetContextClipRect (&r); + + BatchGraphics (); + + if (Mode != DRAW_ORBITAL_UPDATE) + { + SetTransitionSource (NULL); + + DrawSISFrame (); + DrawSISMessage (NULL); + DrawSISTitle (GLOBAL_SIS (PlanetName)); + DrawStarBackGround (); + DrawPlanetSurfaceBorder (); + } + + if (Mode == DRAW_ORBITAL_WAIT) + { + STAMP s; + + SetContext (GetScanContext (NULL)); + s.frame = CaptureDrawable (LoadGraphic (ORBENTER_PMAP_ANIM)); + s.origin.x = -SAFE_X; + s.origin.y = 0; + DrawStamp (&s); + DestroyDrawable (ReleaseDrawable (s.frame)); + } + else if (Mode == DRAW_ORBITAL_FULL) + { + DrawDefaultPlanetSphere (); + } + + if (Mode != DRAW_ORBITAL_WAIT) + { + SetContext (GetScanContext (NULL)); + DrawPlanet (0, BLACK_COLOR); + } + + if (Mode != DRAW_ORBITAL_UPDATE) + { + ScreenTransition (3, &r); + } + + UnbatchGraphics (); + + // for later RepairBackRect() + LoadIntoExtraScreen (&r); +} + +// Initialise the surface graphics, and start the planet music. +// Called from the GenerateFunctions.generateOribital() function +// (when orbit is entered; either from IP, or from loading a saved game) +// and when "starmap" is selected from orbit and then cancelled; +// also after in-orbit comm and after defeating planet guards in combat. +// SurfDefFrame contains surface definition images when a planet comes +// with its own bitmap (currently only for Earth) +void +LoadPlanet (FRAME SurfDefFrame) +{ + bool WaitMode = !(LastActivity & CHECK_LOAD); + PLANET_DESC *pPlanetDesc; + +#ifdef DEBUG + if (disableInteractivity) + return; +#endif + + assert (pSolarSysState->InOrbit && !pSolarSysState->TopoFrame); + + CreatePlanetContext (); + + if (WaitMode) + { + DrawOrbitalDisplay (DRAW_ORBITAL_WAIT); + } + + StopMusic (); + + pPlanetDesc = pSolarSysState->pOrbitalDesc; + GeneratePlanetSurface (pPlanetDesc, SurfDefFrame); + SetPlanetMusic (pPlanetDesc->data_index & ~PLANET_SHIELDED); + GeneratePlanetSide (); + + if (!PLRPlaying ((MUSIC_REF)~0)) + PlayMusic (LanderMusic, TRUE, 1); + + if (WaitMode) + { + ZoomInPlanetSphere (); + DrawOrbitalDisplay (DRAW_ORBITAL_UPDATE); + } + else + { + DrawOrbitalDisplay (DRAW_ORBITAL_FULL); + } +} + +void +FreePlanet (void) +{ + COUNT i; + PLANET_ORBIT *Orbit = &pSolarSysState->Orbit; + + UninitSphereRotation (); + + StopMusic (); + + for (i = 0; i < sizeof (pSolarSysState->PlanetSideFrame) + / sizeof (pSolarSysState->PlanetSideFrame[0]); ++i) + { + DestroyDrawable (ReleaseDrawable (pSolarSysState->PlanetSideFrame[i])); + pSolarSysState->PlanetSideFrame[i] = 0; + } + +// FreeLanderData (); + + DestroyStringTable (ReleaseStringTable (pSolarSysState->XlatRef)); + pSolarSysState->XlatRef = 0; + DestroyDrawable (ReleaseDrawable (pSolarSysState->TopoFrame)); + pSolarSysState->TopoFrame = 0; + DestroyColorMap (ReleaseColorMap (pSolarSysState->OrbitalCMap)); + pSolarSysState->OrbitalCMap = 0; + + HFree (Orbit->lpTopoData); + Orbit->lpTopoData = 0; + DestroyDrawable (ReleaseDrawable (Orbit->TopoZoomFrame)); + Orbit->TopoZoomFrame = 0; + DestroyDrawable (ReleaseDrawable (Orbit->SphereFrame)); + Orbit->SphereFrame = NULL; + + DestroyDrawable (ReleaseDrawable (Orbit->TintFrame)); + Orbit->TintFrame = 0; + Orbit->TintColor = BLACK_COLOR; + + DestroyDrawable (ReleaseDrawable (Orbit->ObjectFrame)); + Orbit->ObjectFrame = 0; + DestroyDrawable (ReleaseDrawable (Orbit->WorkFrame)); + Orbit->WorkFrame = 0; + + HFree (Orbit->TopoColors); + Orbit->TopoColors = NULL; + HFree (Orbit->ScratchArray); + Orbit->ScratchArray = NULL; + + DestroyStringTable (ReleaseStringTable ( + pSolarSysState->SysInfo.PlanetInfo.DiscoveryString + )); + pSolarSysState->SysInfo.PlanetInfo.DiscoveryString = 0; + FreeLanderFont (&pSolarSysState->SysInfo.PlanetInfo); + + // Need to make sure our own CONTEXTs are not active because + // we will destroy them now + SetContext (SpaceContext); + DestroyPlanetContext (); + DestroyScanContext (); + +} + +void +LoadStdLanderFont (PLANET_INFO *info) +{ + info->LanderFont = LoadFont (LANDER_FONT); + info->LanderFontEff = CaptureDrawable ( + LoadGraphic (LANDER_FONTEFF_PMAP_ANIM)); +} + +void +FreeLanderFont (PLANET_INFO *info) +{ + DestroyFont (info->LanderFont); + info->LanderFont = NULL; + DestroyDrawable (ReleaseDrawable (info->LanderFontEff)); + info->LanderFontEff = NULL; +} + +static BOOLEAN +DoPlanetOrbit (MENU_STATE *pMS) +{ + BOOLEAN select = PulsedInputState.menu[KEY_MENU_SELECT]; + BOOLEAN handled; + + if ((GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)) + || GLOBAL_SIS (CrewEnlisted) == (COUNT)~0) + return FALSE; + + // XXX: pMS actually refers to pSolarSysState->MenuState + handled = DoMenuChooser (pMS, PM_SCAN); + if (handled) + return TRUE; + + if (!select) + return TRUE; + + SetFlashRect (NULL); + + switch (pMS->CurState) + { + case SCAN: + ScanSystem (); + if (GLOBAL (CurrentActivity) & START_ENCOUNTER) + { // Found Fwiffo on Pluto + return FALSE; + } + break; + case EQUIP_DEVICE: + select = DevicesMenu (); + if (GLOBAL (CurrentActivity) & START_ENCOUNTER) + { // Invoked Talking Pet, a Caster or Sun Device over Chmmr, + // or a Caster for Ilwrath + // Going into conversation + return FALSE; + } + break; + case CARGO: + CargoMenu (); + break; + case ROSTER: + select = RosterMenu (); + break; + case GAME_MENU: + if (!GameOptions ()) + return FALSE; // abort or load + break; + case STARMAP: + { + BOOLEAN AutoPilotSet; + InputFrameCallback *oldCallback; + + // Deactivate planet rotation + oldCallback = SetInputCallback (NULL); + + RepairSISBorder (); + + AutoPilotSet = StarMap (); + if (GLOBAL (CurrentActivity) & CHECK_ABORT) + return FALSE; + + // Reactivate planet rotation + SetInputCallback (oldCallback); + + if (!AutoPilotSet) + { // Redraw the orbital display + DrawOrbitalDisplay (DRAW_ORBITAL_FULL); + break; + } + // Fall through !!! + } + case NAVIGATION: + return FALSE; + } + + if (!(GLOBAL (CurrentActivity) & CHECK_ABORT)) + { + if (select) + { // 3DO menu jumps to NAVIGATE after a successful submenu run + if (optWhichMenu != OPT_PC) + pMS->CurState = NAVIGATION; + DrawMenuStateStrings (PM_SCAN, pMS->CurState); + } + SetFlashRect (SFR_MENU_3DO); + } + + return TRUE; +} + +static void +on_input_frame (void) +{ + RotatePlanetSphere (TRUE); +} + +void +PlanetOrbitMenu (void) +{ + MENU_STATE MenuState; + InputFrameCallback *oldCallback; + + memset (&MenuState, 0, sizeof MenuState); + + DrawMenuStateStrings (PM_SCAN, SCAN); + SetFlashRect (SFR_MENU_3DO); + + MenuState.CurState = SCAN; + SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); + oldCallback = SetInputCallback (on_input_frame); + + MenuState.InputFunc = DoPlanetOrbit; + DoInput (&MenuState, TRUE); + + SetInputCallback (oldCallback); + + SetFlashRect (NULL); + DrawMenuStateStrings (PM_STARMAP, -NAVIGATION); +} diff --git a/src/uqm/planets/planets.h b/src/uqm/planets/planets.h new file mode 100644 index 0000000..c6f440d --- /dev/null +++ b/src/uqm/planets/planets.h @@ -0,0 +1,322 @@ +//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. + */ + +#ifndef UQM_PLANETS_PLANETS_H_ +#define UQM_PLANETS_PLANETS_H_ + +#include "libs/mathlib.h" + +#define END_INTERPLANETARY START_INTERPLANETARY + +enum PlanetScanTypes +{ + MINERAL_SCAN = 0, + ENERGY_SCAN, + BIOLOGICAL_SCAN, + + NUM_SCAN_TYPES, +}; + +#define MAP_WIDTH SIS_SCREEN_WIDTH +#define MAP_HEIGHT (75 - SAFE_Y) + +enum +{ + BIOLOGICAL_DISASTER = 0, + EARTHQUAKE_DISASTER, + LIGHTNING_DISASTER, + LAVASPOT_DISASTER, + + /* additional lander sounds */ + LANDER_INJURED, + LANDER_SHOOTS, + LANDER_HITS, + LIFEFORM_CANNED, + LANDER_PICKUP, + LANDER_FULL, + LANDER_DEPARTS, + LANDER_RETURNS, + LANDER_DESTROYED +}; + +#define MAX_SCROUNGED 50 /* max lander can hold */ + +#define SCALE_RADIUS(r) ((r) << 6) +#define UNSCALE_RADIUS(r) ((r) >> 6) +#define MAX_ZOOM_RADIUS SCALE_RADIUS(128) +#define MIN_ZOOM_RADIUS (MAX_ZOOM_RADIUS>>3) +#define EARTH_RADIUS SCALE_RADIUS(8) + +#define MIN_PLANET_RADIUS SCALE_RADIUS (4) +#define MAX_PLANET_RADIUS SCALE_RADIUS (124) + +#define DISPLAY_FACTOR ((SIS_SCREEN_WIDTH >> 1) - 8) + +#define NUM_SCANDOT_TRANSITIONS 4 + +#define MIN_MOON_RADIUS 35 +#define MOON_DELTA 20 + +#define MAX_SUNS 1 +#define MAX_PLANETS 16 +#define MAX_MOONS 4 + +#define MAP_BORDER_HEIGHT 5 +#define SCAN_SCREEN_HEIGHT (SIS_SCREEN_HEIGHT - MAP_HEIGHT - MAP_BORDER_HEIGHT) + +#define PLANET_ROTATION_TIME (ONE_SECOND * 12) +#define PLANET_ROTATION_RATE (PLANET_ROTATION_TIME / MAP_WIDTH) +// XXX: -9 to match the original, but why? I have no idea +#define PLANET_ORG_Y ((SCAN_SCREEN_HEIGHT - 9) / 2) + +#define NUM_RACE_RUINS 16 + +typedef struct planet_desc PLANET_DESC; +typedef struct star_desc STAR_DESC; +typedef struct node_info NODE_INFO; +typedef struct planet_orbit PLANET_ORBIT; +typedef struct solarsys_state SOLARSYS_STATE; + + +#include "generate.h" +#include "../menustat.h" +#include "../units.h" + +#include "elemdata.h" +#include "lifeform.h" +#include "plandata.h" +#include "sundata.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +struct planet_desc +{ + DWORD rand_seed; + + BYTE data_index; + BYTE NumPlanets; + SIZE radius; + POINT location; + + Color temp_color; + COUNT NextIndex; + STAMP image; + + PLANET_DESC *pPrevDesc; + // The Sun or planet that this world is orbiting around. +}; + +struct star_desc +{ + POINT star_pt; + BYTE Type; + BYTE Index; + BYTE Prefix; + BYTE Postfix; +}; + +struct node_info +{ + // This structire is filled in when a generateMinerals, generateEnergy, + // or generateLife call is made. + POINT loc_pt; + // Position of the mineral/bio/energy node on the planet. + COUNT density; + // For bio and energy: undefined + // For minerals the low byte is the gross size of the + // deposit (this determines the image), and the high + // byte is the fine size (the actual quantity). + COUNT type; + // For minerals: the type of element + // For bio: the type of the creature. + // 0 through NUM_CREATURE_TYPES - 1 are normal creatures, + // NUM_CREATURE_TYPES is an Evil One + // NUM_CREATURE_TYPES + 1 is a Brainbox Bulldozer + // NUM_CREATURE_TYPES + 2 is Zex' Beauty + // For energy: undefined +}; + +struct planet_orbit +{ + FRAME TopoZoomFrame; + // 4x scaled topo image for planet-side + SBYTE *lpTopoData; + // normal topo data; expressed in elevation levels + // data is signed for planets other than gas giants + // transformed to light variance map for 3d planet + FRAME SphereFrame; + // rotating 3d planet frames (current and next) + FRAME ObjectFrame; + // any extra planetary object (shield, atmo, rings) + // automatically drawn if present + FRAME TintFrame; + // tinted topo images for current scan type (dynamic) + Color TintColor; + // the color of the last used tint + Color *TopoColors; + // RGBA version of topo image; for 3d planet + Color *ScratchArray; + // temp RGBA data for whatever transforms (nuked often) + FRAME WorkFrame; + // any extra frame workspace (for dynamic objects) +}; + +// See doc/devel/generate for information on how this structure is +// filled. +struct solarsys_state +{ + // Standard field required by DoInput() + BOOLEAN (*InputFunc) (struct solarsys_state *); + + BOOLEAN InIpFlight; + // Set to TRUE when player is flying around in interplanetary + // Reset to FALSE when going into orbit or encounter + + COUNT WaitIntersect; + // Planet/moon number with which the flagship should not collide + // For example, if the player just left the planet or inner system + // If set to (COUNT)~0, all planet collisions are disabled until + // the flagship stops intersecting with all planets. + PLANET_DESC SunDesc[MAX_SUNS]; + PLANET_DESC PlanetDesc[MAX_PLANETS]; + // Description of the planets in the system. + // Only defined after a call to (*genFuncs)->generatePlanets() + // and overwritten by subsequent calls. + PLANET_DESC MoonDesc[MAX_MOONS]; + // Description of the moons orbiting the planet pointed to + // by pBaseDesc. + // Only defined after a call to (*genFuncs)->generateMoons() + // as its argument, and overwritten by subsequent calls. + PLANET_DESC *pBaseDesc; + // In outer system: points to PlanetDesc[] + // In inner system: points to MoonDesc[] + PLANET_DESC *pOrbitalDesc; + // In orbit: points into PlanetDesc or MoonDesc to the planet + // currently orbiting. + // In inner system: points into PlanetDesc to the planet whose + // inner system the ship is inside + SIZE FirstPlanetIndex, LastPlanetIndex; + // The planets get sorted on their image.origin.y value. + // PlanetDesc[FirstPlanetIndex] is the planet with the lowest + // image.origin.y, and PlanetDesc[LastPlanetIndex] has the + // highest image.origin.y. + // PlanetDesc[PlanetDesc[i].NextIndex] is the next planet + // after PlanetDesc[i] in the ordering. + + BYTE turn_counter; + BYTE turn_wait; + BYTE thrust_counter; + BYTE max_ship_speed; + + STRING XlatRef; + const void *XlatPtr; + COLORMAP OrbitalCMap; + + SYSTEM_INFO SysInfo; + + const GenerateFunctions *genFuncs; + // Functions to call to fill in various parts of this structure. + // See generate.h, doc/devel/generate + + FRAME PlanetSideFrame[3 + MAX_LIFE_VARIATION]; + /* Frames for planet-side elements. + * [0] = bio cannister + * [1] = energy node (world-specific) + * [2] = unused (formerly static slave shield, presumed) + * [3] = bio 1 (world-specific) + * [4] = bio 2 (world-specific) + * [5] = bio 3 (world-specific) + */ + FRAME TopoFrame; + PLANET_ORBIT Orbit; + BOOLEAN InOrbit; + // Set to TRUE when player hits a world in an inner system + // Homeworld encounters count as 'in orbit' +}; + +extern SOLARSYS_STATE *pSolarSysState; +extern MUSIC_REF SpaceMusic; +extern CONTEXT PlanetContext; + +// Random context used for all solar system, planets and surfaces generation +extern RandomContext *SysGenRNG; + +bool playerInSolarSystem (void); +bool playerInPlanetOrbit (void); +bool playerInInnerSystem (void); +bool worldIsPlanet (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world); +bool worldIsMoon (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world); +COUNT planetIndex (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world); +COUNT moonIndex (const SOLARSYS_STATE *solarSys, const PLANET_DESC *moon); +#define MATCH_PLANET ((BYTE) -1) +bool matchWorld (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world, + BYTE planetI, BYTE moonI); + +DWORD GetRandomSeedForStar (const STAR_DESC *star); + +POINT locationToDisplay (POINT pt, SIZE scaleRadius); +POINT displayToLocation (POINT pt, SIZE scaleRadius); +POINT planetOuterLocation (COUNT planetI); + +extern void LoadPlanet (FRAME SurfDefFrame); +extern void DrawPlanet (int dy, Color tintColor); +extern void FreePlanet (void); +extern void LoadStdLanderFont (PLANET_INFO *info); +extern void FreeLanderFont (PLANET_INFO *info); + +extern void ExploreSolarSys (void); +extern void DrawStarBackGround (void); +extern void XFormIPLoc (POINT *pIn, POINT *pOut, BOOLEAN ToDisplay); +extern void DrawOval (RECT *pRect, BYTE num_off_pixels); +extern void DrawFilledOval (RECT *pRect); +extern void FillOrbits (SOLARSYS_STATE *system, BYTE NumPlanets, + PLANET_DESC *pBaseDesc, BOOLEAN TypesDefined); +extern void InitLander (BYTE LanderFlags); + +extern void InitSphereRotation (int direction, BOOLEAN shielded); +extern void UninitSphereRotation (void); +extern void PrepareNextRotationFrame (void); +extern void DrawPlanetSphere (int x, int y); +extern void DrawDefaultPlanetSphere (void); +extern void RenderPlanetSphere (FRAME Frame, int offset, BOOLEAN doThrob); +extern void SetShieldThrobEffect (FRAME FromFrame, int offset, FRAME ToFrame); + +extern void ZoomInPlanetSphere (void); +extern void RotatePlanetSphere (BOOLEAN keepRate); + +extern void DrawScannedObjects (BOOLEAN Reversed); +extern void GeneratePlanetSurface (PLANET_DESC *pPlanetDesc, + FRAME SurfDefFrame); +extern void DeltaTopography (COUNT num_iterations, SBYTE *DepthArray, + RECT *pRect, SIZE depth_delta); + +extern void DrawPlanetSurfaceBorder (void); + +extern UNICODE* GetNamedPlanetaryBody (void); +extern void GetPlanetOrMoonName (UNICODE *buf, COUNT bufsize); + +extern void PlanetOrbitMenu (void); +extern void SaveSolarSysLocation (void); + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_PLANETS_PLANETS_H_ */ diff --git a/src/uqm/planets/plangen.c b/src/uqm/planets/plangen.c new file mode 100644 index 0000000..005a968 --- /dev/null +++ b/src/uqm/planets/plangen.c @@ -0,0 +1,1954 @@ +//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 "planets.h" +#include "scan.h" +#include "../nameref.h" +#include "../resinst.h" +#include "../setup.h" +#include "options.h" +#include "libs/graphics/gfx_common.h" +#include "libs/graphics/drawable.h" +#include "libs/mathlib.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include <math.h> +#include <time.h> + + +#undef PROFILE_ROTATION + +// define USE_ALPHA_SHIELD to use an aloha overlay instead of +// an additive overlay for the shield effect +#undef USE_ALPHA_SHIELD + +#define SHIELD_GLOW_COMP 120 +#define SHIELD_REFLECT_COMP 100 + +#define NUM_BATCH_POINTS 64 +#define RADIUS 37 +//2*RADIUS +#define TWORADIUS (RADIUS << 1) +//RADIUS^2 +#define RADIUS_2 (RADIUS * RADIUS) +// distance beyond which all pixels are transparent (for aa) +#define RADIUS_THRES ((RADIUS + 1) * (RADIUS + 1)) +#define DIAMETER (TWORADIUS + 1) +#if 0 +# define SPHERE_SPAN_X (MAP_WIDTH >> 1) +#else +# define SPHERE_SPAN_X (MAP_HEIGHT) +#endif + // XXX: technically, the sphere's span over X should be MAP_WIDTH/2 + // but this causes visible surface compression over X, because + // the surface dims ratio is H x H*PI, instead of H x 2*H + // see bug #885 + +#define DIFFUSE_BITS 16 +#define AA_WEIGHT_BITS 16 + +#ifndef M_TWOPI + #ifndef M_PI + #define M_PI 3.14159265358979323846 + #endif + #define M_TWOPI (M_PI * 2.0) +#endif +#ifndef M_DEG2RAD +#define M_DEG2RAD (M_TWOPI / 360.0) +#endif + +DWORD light_diff[DIAMETER][DIAMETER]; + +typedef struct +{ + POINT p[4]; + DWORD m[4]; +} MAP3D_POINT; + +MAP3D_POINT map_rotate[DIAMETER][DIAMETER]; + +typedef struct +{ + double x, y, z; +} POINT3; + +static void +RenderTopography (FRAME DstFrame, SBYTE *pTopoData, int w, int h) +{ + FRAME OldFrame; + + OldFrame = SetContextFGFrame (DstFrame); + + if (pSolarSysState->XlatRef == 0) + { + // There is currently nothing we can do w/o an xlat table + // This is still called for Earth for 4x scaled topo, but we + // do not need it because we cannot land on Earth. + } + else + { + COUNT i; + BYTE AlgoType; + SIZE base, d; + const XLAT_DESC *xlatDesc; + POINT pt; + const PlanetFrame *PlanDataPtr; + PRIMITIVE BatchArray[NUM_BATCH_POINTS]; + PRIMITIVE *pBatch; + SBYTE *pSrc; + const BYTE *xlat_tab; + BYTE *cbase; + POINT oldOrigin; + RECT ClipRect; + + oldOrigin = SetContextOrigin (MAKE_POINT (0, 0)); + GetContextClipRect (&ClipRect); + SetContextClipRect (NULL); + + pBatch = &BatchArray[0]; + for (i = 0; i < NUM_BATCH_POINTS; ++i, ++pBatch) + { + SetPrimNextLink (pBatch, i + 1); + SetPrimType (pBatch, POINT_PRIM); + } + SetPrimNextLink (&pBatch[-1], END_OF_LIST); + + PlanDataPtr = &PlanData[ + pSolarSysState->pOrbitalDesc->data_index & ~PLANET_SHIELDED + ]; + AlgoType = PLANALGO (PlanDataPtr->Type); + base = PlanDataPtr->base_elevation; + xlatDesc = (const XLAT_DESC *) pSolarSysState->XlatPtr; + xlat_tab = (const BYTE *) xlatDesc->xlat_tab; + cbase = GetColorMapAddress (pSolarSysState->OrbitalCMap); + + i = NUM_BATCH_POINTS; + pBatch = &BatchArray[i]; + pSrc = pTopoData; + for (pt.y = 0; pt.y < h; ++pt.y) + { + for (pt.x = 0; pt.x < w; ++pt.x, ++pSrc) + { + BYTE *ctab; + + d = *pSrc; + if (AlgoType == GAS_GIANT_ALGO) + { // make elevation value non-negative + d &= 255; + } + else + { + d += base; + if (d < 0) + d = 0; + else if (d > 255) + d = 255; + } + + --pBatch; + pBatch->Object.Point.x = pt.x; + pBatch->Object.Point.y = pt.y; + + d = xlat_tab[d] - cbase[0]; + ctab = (cbase + 2) + d * 3; + + // fixed planet surfaces being too dark + // ctab shifts were previously >> 3 .. -Mika + SetPrimColor (pBatch, BUILD_COLOR (MAKE_RGB15 (ctab[0] >> 1, + ctab[1] >> 1, ctab[2] >> 1), d)); + + if (--i == 0) + { // flush the batch and start the next one + DrawBatch (BatchArray, 0, 0); + i = NUM_BATCH_POINTS; + pBatch = &BatchArray[i]; + } + } + } + + if (i < NUM_BATCH_POINTS) + { + DrawBatch (BatchArray, i, 0); + } + + SetContextClipRect (&ClipRect); + SetContextOrigin (oldOrigin); + } + + SetContextFGFrame (OldFrame); +} + +static inline void +P3mult (POINT3 *res, POINT3 *vec, double cnst) +{ + res->x = vec->x * cnst; + res->y = vec->y * cnst; + res->z = vec->z * cnst; +} + +static inline void +P3sub (POINT3 *res, POINT3 *v1, POINT3 *v2) +{ + res->x = v1->x - v2->x; + res->y = v1->y - v2->y; + res->z = v1->z - v2->z; +} + +static inline double +P3dot (POINT3 *v1, POINT3 *v2) +{ + return (v1->x * v2->x + v1->y * v2->y + v1->z * v2->z); +} + +static inline void +P3norm (POINT3 *res, POINT3 *vec) +{ + double mag = sqrt (P3dot (vec, vec)); + P3mult (res, vec, 1/mag); +} + +// GenerateSphereMask builds a shadow map for the rotating planet +// loc indicates the planet's position relative to the sun +static void +GenerateSphereMask (POINT loc) +{ + POINT pt; + POINT3 light; + double lrad; + const DWORD step = 1 << DIFFUSE_BITS; + int y, x; + +#define AMBIENT_LIGHT 0.1 +#define LIGHT_Z 1.2 + // lrad is the distance from the sun to the planet + lrad = sqrt (loc.x * loc.x + loc.y * loc.y); + // light is the sun's position. the z-coordinate is whatever + // looks good + light.x = -((double)loc.x); + light.y = -((double)loc.y); + light.z = LIGHT_Z * lrad; + P3norm (&light, &light); + + for (pt.y = 0, y = -RADIUS; pt.y <= TWORADIUS; ++pt.y, y++) + { + DWORD y_2 = y * y; + + for (pt.x = 0, x = -RADIUS; pt.x <= TWORADIUS; ++pt.x, x++) + { + DWORD x_2 = x * x; + DWORD rad_2 = x_2 + y_2; + DWORD diff_int = 0; + POINT3 norm; + double diff; + + if (rad_2 < RADIUS_THRES) + { + // norm is the sphere's surface normal. + norm.x = (double)x; + norm.y = (double)y; + norm.z = (sqrt (RADIUS_2 - x_2) * sqrt (RADIUS_2 - y_2)) / + RADIUS; + P3norm (&norm, &norm); + // diffuse component is norm dot light + diff = P3dot (&norm, &light); + // negative diffuse is bad + if (diff < 0) + diff = 0.0; +#if 0 + // Specular is not used in practice and is left here + // if someone decides to use it later for some reason. + // Specular highlight is only good for perfectly smooth + // surfaces, like balls (of which planets are not) + // This is the Phong equation +#define LIGHT_INTENS 0.3 +#define MSHI 2 + double fb, spec; + POINT3 rvec; + POINT3 view; + + // always view along the z-axis + // ideally use a view point, and have the view change + // per pixel, but that is too much effort for now. + // the view MUST be normalized! + view.x = 0; + view.y = 0; + view.z = 1.0; + + // specular highlight is the phong equation: + // (rvec dot view)^MSHI + // where rvec = (2*diff)*norm - light (reflection of light + // around norm) + P3mult (&rvec, &norm, 2 * diff); + P3sub (&rvec, &rvec, &light); + fb = P3dot (&rvec, &view); + if (fb > 0.0) + spec = LIGHT_INTENS * pow (fb, MSHI); + else + spec = 0; +#endif + // adjust for the ambient light + if (diff < AMBIENT_LIGHT) + diff = AMBIENT_LIGHT; + // Now we antialias the edge of the spere to look nice + if (rad_2 > RADIUS_2) + { + diff *= 1 - (sqrt(rad_2) - RADIUS); + if (diff < 0) + diff = 0; + } + // diff_int allows us multiply by a ratio without using + // floating-point. + diff_int = (DWORD)(diff * step); + } + + light_diff[pt.y][pt.x] = diff_int; + } + } +} + +//create_aa_points creates weighted averages for +// 4 points around the 'ideal' point at x,y +// the concept is to compute the weight based on the +// distance from the integer location points to the ideal point +static void +create_aa_points (MAP3D_POINT *ppt, double x, double y) +{ + double deltax, deltay, inv_deltax, inv_deltay; + COORD nextx, nexty; + COUNT i; + double d1, d2, d3, d4, m[4]; + + if (x < 0) + x = 0; + else if (x >= SPHERE_SPAN_X) + x = SPHERE_SPAN_X - 1; + if (y < 0) + y = 0; + else if (y >= MAP_HEIGHT) + y = MAP_HEIGHT - 1; + + // get the integer value of this point + ppt->p[0].x = (COORD)x; + ppt->p[0].y = (COORD)y; + deltax = x - ppt->p[0].x; + deltay = y - ppt->p[0].y; + + // if this point doesn't need modificaton, set m[0]=0 + if (deltax == 0 && deltay == 0) + { + ppt->m[0] = 0; + return; + } + + // get the neighboring points surrounding the 'ideal' point + if (deltax != 0) + nextx = ppt->p[0].x + 1; + else + nextx = ppt->p[0].x; + if (deltay != 0) + nexty = ppt->p[0].y + 1; + else + nexty = ppt->p[0].y; + //(x1,y) + ppt->p[1].x = nextx; + ppt->p[1].y = ppt->p[0].y; + //(x,y1) + ppt->p[2].x = ppt->p[0].x; + ppt->p[2].y = nexty; + //(x1y1) + ppt->p[3].x = nextx; + ppt->p[3].y = nexty; + //the square 1x1, so opposite poinnts are at 1-delta + inv_deltax = 1.0 - fabs (deltax); + inv_deltax *= inv_deltax; + inv_deltay = 1.0 - fabs (deltay); + inv_deltay *= inv_deltay; + deltax *= deltax; + deltay *= deltay; + //d1-d4 contain the distances from the poinnts to the ideal point + d1 = sqrt (deltax + deltay); + d2 = sqrt (inv_deltax + deltay); + d3 = sqrt (deltax + inv_deltay); + d4 = sqrt (inv_deltax + inv_deltay); + //compute the weights. the sum(ppt->m[])=65536 + m[0] = 1 / (1 + d1 * (1 / d2 + 1 / d3 + 1 / d4)); + m[1] = m[0] * d1 / d2; + m[2] = m[0] * d1 / d3; + m[3] = m[0] * d1 / d4; + + for (i = 0; i < 4; i++) + ppt->m[i] = (DWORD)(m[i] * (1 << AA_WEIGHT_BITS) + 0.5); +} + +static inline BYTE +get_color_channel (Color c, int channel) +{ + switch (channel) + { + case 0: + return c.r; + case 1: + return c.g; + case 2: + return c.b; + default: + return 0; + } +} + +// Creates either a red, green, or blue value by +// computing the weighted averages of the 4 points in p +static BYTE +get_avg_channel (Color p[4], DWORD mult[4], int channel) +{ + COUNT j; + DWORD ci = 0; + + //sum(mult[])==65536 + //c is the red/green/blue value of this pixel + for (j = 0; j < 4; j++) + { + BYTE c = get_color_channel (p[j], channel); + ci += c * mult[j]; + } + ci >>= AA_WEIGHT_BITS; + //check for overflow + if (ci > 255) + ci = 255; + + return ((UBYTE)ci); +} + +// CreateSphereTiltMap creates 'map_rotate' to map the topo data +// for a tilted planet. It also does the sphere->plane mapping +static void +CreateSphereTiltMap (int angle) +{ + int x, y; + const double multx = ((double)SPHERE_SPAN_X / M_PI); + const double multy = ((double)MAP_HEIGHT / M_PI); + const double xadj = ((double)SPHERE_SPAN_X / 2.0); + + for (y = -RADIUS; y <= RADIUS; y++) + { + int y_2 = y * y; + + for (x = -RADIUS; x <= RADIUS; x++) + { + double dx, dy, newx, newy; + double da, rad, rad_2; + double xa, ya; + MAP3D_POINT *ppt = &map_rotate[y + RADIUS][x + RADIUS]; + + rad_2 = x * x + y_2; + + if (rad_2 >= RADIUS_THRES) + { // pixel won't be present + ppt->p[0].x = x + RADIUS; + ppt->p[0].y = y + RADIUS; + ppt->m[0] = 0; + + continue; + } + + rad = sqrt (rad_2); + // antialiasing goes beyond the actual radius + if (rad >= RADIUS) + rad = (double)RADIUS - 0.1; + + da = atan2 ((double)y, (double)x); + // compute the planet-tilt + da += M_DEG2RAD * angle; + dx = rad * cos (da); + dy = rad * sin (da); + + // Map the sphere onto a plane + xa = acos (-dx / RADIUS); + ya = acos (-dy / RADIUS); + newx = multx * xa; + newy = multy * ya; + // Adjust for vertical curvature + if (ya <= 0.05 || ya >= 3.1 /* almost PI */) + newx = xadj; // exact centerline + else + newx = xadj + ((newx - xadj) / sin (ya)); + + create_aa_points (ppt, newx, newy); + } + } +} + +//CreateShieldMask +// The shield is created in two parts. This routine creates the Halo. +// The red tint of the planet is currently applied in RenderPlanetSphere +// This was done because the shield glows and needs to modify how the planet +// gets lit. Currently, the planet area is transparent in the mask made by +// this routine, but a filter can be applied if desired too. + +// HALO rim size +#define SHIELD_HALO 7 +#define SHIELD_RADIUS (RADIUS + SHIELD_HALO) +#define SHIELD_DIAM ((SHIELD_RADIUS << 1) + 1) +#define SHIELD_RADIUS_2 (SHIELD_RADIUS * SHIELD_RADIUS) +#define SHIELD_RADIUS_THRES ((SHIELD_RADIUS + 1) * (SHIELD_RADIUS + 1)) +#define SHIELD_HALO_GLOW (SHIELD_GLOW_COMP + SHIELD_REFLECT_COMP) +#define SHIELD_HALO_GLOW_MIN (SHIELD_HALO_GLOW >> 2) + +static FRAME +CreateShieldMask (void) +{ + Color clear; + Color *pix; + int x, y; + FRAME ShieldFrame; + PLANET_ORBIT *Orbit = &pSolarSysState->Orbit; + + ShieldFrame = CaptureDrawable ( + CreateDrawable (WANT_PIXMAP | WANT_ALPHA, + SHIELD_DIAM, SHIELD_DIAM, 1)); + + pix = Orbit->ScratchArray; + // This is 100% transparent. + clear = BUILD_COLOR_RGBA (0, 0, 0, 0); + + for (y = -SHIELD_RADIUS; y <= SHIELD_RADIUS; y++) + { + for (x = -SHIELD_RADIUS; x <= SHIELD_RADIUS; ++x, ++pix) + { + int rad_2 = x * x + y * y; + // This is a non-transparent red for the halo + int red = SHIELD_HALO_GLOW; + int alpha = 255; + double rad; + + if (rad_2 >= SHIELD_RADIUS_THRES) + { // outside all bounds + *pix = clear; + continue; + } + // Inside the halo + if (rad_2 <= RADIUS_2) + { // planet's pixels, ours transparent + *pix = clear; + continue; + } + + // The halo itself + rad = sqrt (rad_2); + + if (rad <= RADIUS + 0.8) + { // pixels common between the shield and planet + // do antialiasing using alpha + alpha = (int) (red * (rad - RADIUS)); + red = 255; + } + else + { // shield pixels + red -= (int) ((red - SHIELD_HALO_GLOW_MIN) * (rad - RADIUS) + / SHIELD_HALO); + if (red < 0) + red = 0; + } + + *pix = BUILD_COLOR_RGBA (red, 0, 0, alpha); + } + } + + WriteFramePixelColors (ShieldFrame, Orbit->ScratchArray, + SHIELD_DIAM, SHIELD_DIAM); + SetFrameHot (ShieldFrame, MAKE_HOT_SPOT (SHIELD_RADIUS + 1, + SHIELD_RADIUS + 1)); + + return ShieldFrame; +} + +// SetShieldThrobEffect adjusts the red levels in the shield glow graphic +// the throbbing cycle is tied to the planet rotation cycle +#define SHIELD_THROBS 12 + // throb cycles per revolution +#define THROB_CYCLE ((MAP_WIDTH << 8) / SHIELD_THROBS) +#define THROB_HALF_CYCLE (THROB_CYCLE >> 1) + +#define THROB_MAX_LEVEL 256 +#define THROB_MIN_LEVEL 100 +#define THROB_D_LEVEL (THROB_MAX_LEVEL - THROB_MIN_LEVEL) + +static inline int +shield_level (int offset) +{ + int level; + + offset = (offset << 8) % THROB_CYCLE; + level = abs (offset - THROB_HALF_CYCLE); + level = THROB_MIN_LEVEL + level * THROB_D_LEVEL / THROB_HALF_CYCLE; + + return level; +} + +// See description above +// offset is effectively the angle of rotation around the planet's axis +void +SetShieldThrobEffect (FRAME ShieldFrame, int offset, FRAME ThrobFrame) +{ + int i; + int width, height; + PLANET_ORBIT *Orbit = &pSolarSysState->Orbit; + Color *pix; + int level; + + level = shield_level (offset); + + width = GetFrameWidth (ShieldFrame); + height = GetFrameHeight (ShieldFrame); + ReadFramePixelColors (ShieldFrame, Orbit->ScratchArray, width, height); + + for (i = 0, pix = Orbit->ScratchArray; i < width * height; ++i, ++pix) + { + Color p = *pix; + + if (p.a == 255) + { // adjust color data for full-alpha pixels + p.r = p.r * level / THROB_MAX_LEVEL; + p.g = p.g * level / THROB_MAX_LEVEL; + p.b = p.b * level / THROB_MAX_LEVEL; + } + else if (p.a > 0) + { // adjust alpha for translucent pixels + p.a = p.a * level / THROB_MAX_LEVEL; + } + + *pix = p; + } + + WriteFramePixelColors (ThrobFrame, Orbit->ScratchArray, width, height); + SetFrameHot (ThrobFrame, GetFrameHot (ShieldFrame)); +} + +// Apply the shield to the topo image +static void +ApplyShieldTint (void) +{ + DrawMode mode, oldMode; + FRAME oldFrame; + Color tint; + RECT r; + + // TopoFrame will be permanently changed + oldFrame = SetContextFGFrame (pSolarSysState->TopoFrame); + SetContextClipRect (NULL); + GetContextClipRect (&r); + + tint = BUILD_COLOR_RGBA (0xff, 0x00, 0x00, 0xff); +#ifdef USE_ALPHA_SHIELD + mode = MAKE_DRAW_MODE (DRAW_ALPHA, 150); +#else + mode = MAKE_DRAW_MODE (DRAW_ADDITIVE, DRAW_FACTOR_1); +#endif + oldMode = SetContextDrawMode (mode); + SetContextForeGroundColor (tint); + DrawFilledRectangle (&r); + SetContextDrawMode (oldMode); + SetContextFGFrame (oldFrame); +} + +static inline UBYTE +calc_map_light (UBYTE val, DWORD dif, int lvf) +{ + int i; + + // apply diffusion + i = (dif * val) >> DIFFUSE_BITS; + // apply light variance for 3d lighting effect + i += (lvf * val) >> 7; + + if (i < 0) + i = 0; + else if (i > 255) + i = 255; + + return ((UBYTE)i); +} + +static inline Color +get_map_pixel (Color *pixels, int x, int y) +{ + return pixels[y * (MAP_WIDTH + SPHERE_SPAN_X) + x]; +} + +static inline int +get_map_elev (SBYTE *elevs, int x, int y, int offset) +{ + return elevs[y * MAP_WIDTH + (offset + x) % MAP_WIDTH]; +} + +// RenderPlanetSphere builds a frame for the rotating planet view +// offset is effectively the angle of rotation around the planet's axis +// We use the SDL routines to directly write to the SDL_Surface to improve performance +void +RenderPlanetSphere (FRAME MaskFrame, int offset, BOOLEAN doThrob) +{ + PLANET_ORBIT *Orbit = &pSolarSysState->Orbit; + POINT pt; + Color *pix; + Color clear; + int x, y; + Color *pixels; + SBYTE *elevs; + int shLevel; + +#if PROFILE_ROTATION + static clock_t t = 0; + static int frames_done = 1; + clock_t t1; + t1 = clock (); +#endif + + + shLevel = shield_level (offset); + + pix = Orbit->ScratchArray; + clear = BUILD_COLOR_RGBA (0, 0, 0, 0); + pixels = Orbit->TopoColors + offset; + elevs = Orbit->lpTopoData; + + for (pt.y = 0, y = -RADIUS; pt.y <= TWORADIUS; ++pt.y, ++y) + { + for (pt.x = 0, x = -RADIUS; pt.x <= TWORADIUS; ++pt.x, ++x, ++pix) + { + Color c; + DWORD diffus = light_diff[pt.y][pt.x]; + int i; + MAP3D_POINT *ppt = &map_rotate[pt.y][pt.x]; + int lvf; // light variance factor + + if (diffus == 0) + { // full diffusion + *pix = clear; + continue; + } + + // get pixel from topo map and factor from light variance map + if (ppt->m[0] == 0) + { // exact pixel from the topo map + c = get_map_pixel (pixels, ppt->p[0].x, ppt->p[0].y); + lvf = get_map_elev (elevs, ppt->p[0].x, ppt->p[0].y, offset); + } + else + { // fractional pixel -- blend from 4 + Color p[4]; + int lvsum; + + // compute 'ideal' pixel + for (i = 0; i < 4; i++) + p[i] = get_map_pixel (pixels, ppt->p[i].x, ppt->p[i].y); + + c.r = get_avg_channel (p, ppt->m, 0); + c.g = get_avg_channel (p, ppt->m, 1); + c.b = get_avg_channel (p, ppt->m, 2); + + // compute 'ideal' light variance + for (i = 0, lvsum = 0; i < 4; i++) + lvsum += get_map_elev (elevs, ppt->p[0].x, ppt->p[0].y, + offset) * ppt->m[i]; + lvf = lvsum >> AA_WEIGHT_BITS; + } + + // Apply the lighting model. This also bounds the sphere + // to make it circular. + if (pSolarSysState->pOrbitalDesc->data_index & PLANET_SHIELDED) + { + int r; + + // add lite red filter (3/4) component + c.g = (c.g >> 1) + (c.g >> 2); + c.b = (c.b >> 1) + (c.b >> 2); + + c.r = calc_map_light (c.r, diffus, lvf); + c.g = calc_map_light (c.g, diffus, lvf); + c.b = calc_map_light (c.b, diffus, lvf); + + // The shield is glow + reflect (+ filter for others) + r = calc_map_light (SHIELD_REFLECT_COMP, diffus, 0); + r += SHIELD_GLOW_COMP; + + if (doThrob) + { // adjust red level for throbbing shield + r = r * shLevel / THROB_MAX_LEVEL; + } + + r += c.r; + if (r > 255) + r = 255; + c.r = r; + } + else + { + c.r = calc_map_light (c.r, diffus, lvf); + c.g = calc_map_light (c.g, diffus, lvf); + c.b = calc_map_light (c.b, diffus, lvf); + } + + c.a = 0xff; + *pix = c; + } + } + + WriteFramePixelColors (MaskFrame, Orbit->ScratchArray, DIAMETER, DIAMETER); + SetFrameHot (MaskFrame, MAKE_HOT_SPOT (RADIUS + 1, RADIUS + 1)); + +#if PROFILE_ROTATION + t += clock() - t1; + if (frames_done == MAP_WIDTH) + { + log_add (log_Debug, "Rotation frames/sec: %d/%ld(msec)=%f", + frames_done, + (long int) (((double)t / CLOCKS_PER_SEC) * 1000.0 + 0.5), + frames_done / ((double)t / CLOCKS_PER_SEC + 0.5)); + frames_done = 1; + t = clock () - t1; + } + else + frames_done++; +#endif +} + + +#define RANGE_SHIFT 6 + +static void +DitherMap (SBYTE *DepthArray) +{ +#define DITHER_VARIANCE (1 << (RANGE_SHIFT - 3)) + COUNT i; + SBYTE *elev; + DWORD rand_val = 0; + + for (i = 0, elev = DepthArray; i < MAP_WIDTH * MAP_HEIGHT; ++i, ++elev) + { + // Use up the random value byte by byte + if ((i & 3) == 0) + rand_val = RandomContext_Random (SysGenRNG); + else + rand_val >>= 8; + + // Bring the elevation point up or down + *elev += DITHER_VARIANCE / 2 - (rand_val & (DITHER_VARIANCE - 1)); + } +} + +static void +MakeCrater (RECT *pRect, SBYTE *DepthArray, SIZE rim_delta, SIZE + crater_delta, BOOLEAN SetDepth) +{ + COORD x, y, lf_x, rt_x; + SIZE A, B; + long Asquared, TwoAsquared, + Bsquared, TwoBsquared; + long d, dx, dy; + COUNT TopIndex, BotIndex, rim_pixels; + + A = pRect->extent.width >> 1; + B = pRect->extent.height >> 1; + + x = 0; + y = B; + + Asquared = (DWORD)A * A; + TwoAsquared = Asquared << 1; + Bsquared = (DWORD)B * B; + TwoBsquared = Bsquared << 1; + + dx = 0; + dy = TwoAsquared * B; + d = Bsquared - (dy >> 1) + (Asquared >> 2); + + A += pRect->corner.x; + B += pRect->corner.y; + TopIndex = (B - y) * MAP_WIDTH; + BotIndex = (B + y) * MAP_WIDTH; + rim_pixels = 1; + while (dx < dy) + { + if (d > 0) + { + lf_x = A - x; + rt_x = A + x; + if (SetDepth) + { + memset (&DepthArray[TopIndex + lf_x], 0, rt_x - lf_x + 1); + memset (&DepthArray[BotIndex + lf_x], 0, rt_x - lf_x + 1); + } + if (lf_x == rt_x) + { + DepthArray[TopIndex + lf_x] += rim_delta; + DepthArray[BotIndex + lf_x] += rim_delta; + rim_pixels = 0; + } + else + { + do + { + DepthArray[TopIndex + lf_x] += rim_delta; + DepthArray[BotIndex + lf_x] += rim_delta; + if (lf_x != rt_x) + { + DepthArray[TopIndex + rt_x] += rim_delta; + DepthArray[BotIndex + rt_x] += rim_delta; + } + ++lf_x; + --rt_x; + } while (--rim_pixels); + + while (lf_x < rt_x) + { + DepthArray[TopIndex + lf_x] += crater_delta; + DepthArray[BotIndex + lf_x] += crater_delta; + DepthArray[TopIndex + rt_x] += crater_delta; + DepthArray[BotIndex + rt_x] += crater_delta; + ++lf_x; + --rt_x; + } + + if (lf_x == rt_x) + { + DepthArray[TopIndex + lf_x] += crater_delta; + DepthArray[BotIndex + lf_x] += crater_delta; + } + } + + --y; + TopIndex += MAP_WIDTH; + BotIndex -= MAP_WIDTH; + dy -= TwoAsquared; + d -= dy; + } + + ++rim_pixels; + ++x; + dx += TwoBsquared; + d += Bsquared + dx; + } + + d += ((((Asquared - Bsquared) * 3) >> 1) - (dx + dy)) >> 1; + + while (y > 0) + { + lf_x = A - x; + rt_x = A + x; + if (SetDepth) + { + memset (&DepthArray[TopIndex + lf_x], 0, rt_x - lf_x + 1); + memset (&DepthArray[BotIndex + lf_x], 0, rt_x - lf_x + 1); + } + if (lf_x == rt_x) + { + DepthArray[TopIndex + lf_x] += rim_delta; + DepthArray[BotIndex + lf_x] += rim_delta; + } + else + { + do + { + DepthArray[TopIndex + lf_x] += rim_delta; + DepthArray[BotIndex + lf_x] += rim_delta; + if (lf_x != rt_x) + { + DepthArray[TopIndex + rt_x] += rim_delta; + DepthArray[BotIndex + rt_x] += rim_delta; + } + ++lf_x; + --rt_x; + } while (--rim_pixels); + + while (lf_x < rt_x) + { + DepthArray[TopIndex + lf_x] += crater_delta; + DepthArray[BotIndex + lf_x] += crater_delta; + DepthArray[TopIndex + rt_x] += crater_delta; + DepthArray[BotIndex + rt_x] += crater_delta; + ++lf_x; + --rt_x; + } + + if (lf_x == rt_x) + { + DepthArray[TopIndex + lf_x] += crater_delta; + DepthArray[BotIndex + lf_x] += crater_delta; + } + } + + if (d < 0) + { + ++x; + dx += TwoBsquared; + d += dx; + } + + rim_pixels = 1; + --y; + TopIndex += MAP_WIDTH; + BotIndex -= MAP_WIDTH; + dy -= TwoAsquared; + d += Asquared - dy; + } + + lf_x = A - x; + rt_x = A + x; + if (SetDepth) + memset (&DepthArray[TopIndex + lf_x], 0, rt_x - lf_x + 1); + if (lf_x == rt_x) + { + DepthArray[TopIndex + lf_x] += rim_delta; + } + else + { + do + { + DepthArray[TopIndex + lf_x] += rim_delta; + if (lf_x != rt_x) + DepthArray[TopIndex + rt_x] += rim_delta; + ++lf_x; + --rt_x; + } while (--rim_pixels); + + while (lf_x < rt_x) + { + DepthArray[TopIndex + lf_x] += crater_delta; + DepthArray[TopIndex + rt_x] += crater_delta; + ++lf_x; + --rt_x; + } + + if (lf_x == rt_x) + { + DepthArray[TopIndex + lf_x] += crater_delta; + } + } +} + +#define NUM_BAND_COLORS 4 + +static void +MakeStorms (COUNT storm_count, SBYTE *DepthArray) +{ +#define MAX_STORMS 8 + COUNT i; + RECT storm_r[MAX_STORMS]; + RECT *pstorm_r; + + pstorm_r = &storm_r[i = storm_count]; + while (i--) + { + BOOLEAN intersect; + DWORD rand_val; + UWORD loword, hiword; + SIZE band_delta; + + --pstorm_r; + do + { + COUNT j; + + intersect = FALSE; + + rand_val = RandomContext_Random (SysGenRNG); + loword = LOWORD (rand_val); + hiword = HIWORD (rand_val); + switch (HIBYTE (hiword) & 31) + { + case 0: + pstorm_r->extent.height = + (LOBYTE (hiword) % (MAP_HEIGHT >> 2)) + + (MAP_HEIGHT >> 2); + break; + case 1: + case 2: + case 3: + case 4: + pstorm_r->extent.height = + (LOBYTE (hiword) % (MAP_HEIGHT >> 3)) + + (MAP_HEIGHT >> 3); + break; + default: + pstorm_r->extent.height = + (LOBYTE (hiword) % (MAP_HEIGHT >> 4)) + + 4; + break; + } + + if (pstorm_r->extent.height <= 4) + pstorm_r->extent.height += 4; + + rand_val = RandomContext_Random (SysGenRNG); + loword = LOWORD (rand_val); + hiword = HIWORD (rand_val); + + pstorm_r->extent.width = pstorm_r->extent.height + + (LOBYTE (loword) % pstorm_r->extent.height); + + pstorm_r->corner.x = HIBYTE (loword) + % (MAP_WIDTH - pstorm_r->extent.width); + pstorm_r->corner.y = LOBYTE (loword) + % (MAP_HEIGHT - pstorm_r->extent.height); + + for (j = i + 1; j < storm_count; ++j) + { + COORD x, y; + SIZE w, h; + + x = storm_r[j].corner.x - pstorm_r->corner.x; + y = storm_r[j].corner.y - pstorm_r->corner.y; + w = x + storm_r[j].extent.width + 4; + h = y + storm_r[j].extent.height + 4; + intersect = (BOOLEAN) (w > 0 && h > 0 + && x < pstorm_r->extent.width + 4 + && y < pstorm_r->extent.height + 4); + if (intersect) + break; + } + + } while (intersect); + + MakeCrater (pstorm_r, DepthArray, 6, 6, FALSE); + ++pstorm_r->corner.x; + ++pstorm_r->corner.y; + pstorm_r->extent.width -= 2; + pstorm_r->extent.height -= 2; + + band_delta = HIBYTE (loword) & ((3 << RANGE_SHIFT) + 20); + + MakeCrater (pstorm_r, DepthArray, + band_delta, band_delta, TRUE); + ++pstorm_r->corner.x; + ++pstorm_r->corner.y; + pstorm_r->extent.width -= 2; + pstorm_r->extent.height -= 2; + + band_delta += 2; + if (pstorm_r->extent.width > 2 && pstorm_r->extent.height > 2) + { + MakeCrater (pstorm_r, DepthArray, + band_delta, band_delta, TRUE); + ++pstorm_r->corner.x; + ++pstorm_r->corner.y; + pstorm_r->extent.width -= 2; + pstorm_r->extent.height -= 2; + } + + band_delta += 2; + if (pstorm_r->extent.width > 2 && pstorm_r->extent.height > 2) + { + MakeCrater (pstorm_r, DepthArray, + band_delta, band_delta, TRUE); + ++pstorm_r->corner.x; + ++pstorm_r->corner.y; + pstorm_r->extent.width -= 2; + pstorm_r->extent.height -= 2; + } + + band_delta += 4; + MakeCrater (pstorm_r, DepthArray, + band_delta, band_delta, TRUE); + } +} + +static void +MakeGasGiant (COUNT num_bands, SBYTE *DepthArray, RECT *pRect, SIZE + depth_delta) +{ + COORD last_y, next_y; + SIZE band_error, band_bump, band_delta; + COUNT i, j, band_height; + SBYTE *lpDst; + UWORD loword, hiword; + DWORD rand_val; + + band_height = pRect->extent.height / num_bands; + band_bump = pRect->extent.height % num_bands; + band_error = num_bands >> 1; + lpDst = DepthArray; + + band_delta = ((LOWORD (RandomContext_Random (SysGenRNG)) + & (NUM_BAND_COLORS - 1)) << RANGE_SHIFT) + + (1 << (RANGE_SHIFT - 1)); + last_y = next_y = 0; + for (i = num_bands; i > 0; --i) + { + COORD cur_y; + + rand_val = RandomContext_Random (SysGenRNG); + loword = LOWORD (rand_val); + hiword = HIWORD (rand_val); + + next_y += band_height; + if ((band_error -= band_bump) < 0) + { + ++next_y; + band_error += num_bands; + } + if (i == 1) + cur_y = pRect->extent.height; + else + { + RECT r; + + cur_y = next_y + + ((band_height - 2) >> 1) + - ((LOBYTE (hiword) % (band_height - 2)) + 1); + r.corner.x = r.corner.y = 0; + r.extent.width = pRect->extent.width; + r.extent.height = 5; + DeltaTopography (50, + &DepthArray[(cur_y - 2) * r.extent.width], + &r, depth_delta); + } + + for (j = cur_y - last_y; j > 0; --j) + { + COUNT k; + + for (k = pRect->extent.width; k > 0; --k) + *lpDst++ += band_delta; + } + + last_y = cur_y; + band_delta = (band_delta + + ((((LOBYTE (loword) & 1) << 1) - 1) << RANGE_SHIFT)) + & (((1 << RANGE_SHIFT) * NUM_BAND_COLORS) - 1); + } + + MakeStorms (4 + (RandomContext_Random (SysGenRNG) & 3) + 1, DepthArray); + + DitherMap (DepthArray); +} + +static void +ValidateMap (SBYTE *DepthArray) +{ + BYTE state; + BYTE pixel_count[2], lb[2]; + SBYTE last_byte; + COUNT i; + SBYTE *lpDst; + + i = MAP_WIDTH - 1; + lpDst = DepthArray; + last_byte = *lpDst++; + state = pixel_count[0] = pixel_count[1] = 0; + do + { + if (pixel_count[state]++ == 0) + lb[state] = last_byte; + + if (last_byte > *lpDst) + { + if (last_byte - *lpDst > 128) + state ^= 1; + } + else + { + if (*lpDst - last_byte > 128) + state ^= 1; + } + last_byte = *lpDst++; + } while (--i); + + i = MAP_WIDTH * MAP_HEIGHT; + lpDst = DepthArray; + if (pixel_count[0] > pixel_count[1]) + last_byte = lb[0]; + else + last_byte = lb[1]; + do + { + if (last_byte > *lpDst) + { + if (last_byte - *lpDst > 128) + *lpDst = last_byte; + } + else + { + if (*lpDst - last_byte > 128) + *lpDst = last_byte; + } + last_byte = *lpDst++; + } while (--i); +} + +static void +planet_orbit_init (void) +{ + PLANET_ORBIT *Orbit = &pSolarSysState->Orbit; + + Orbit->SphereFrame = CaptureDrawable (CreateDrawable ( + WANT_PIXMAP | WANT_ALPHA, DIAMETER, DIAMETER, 2)); + Orbit->TintFrame = CaptureDrawable (CreateDrawable ( + WANT_PIXMAP, MAP_WIDTH, MAP_HEIGHT, 1)); + Orbit->ObjectFrame = 0; + Orbit->WorkFrame = 0; + Orbit->lpTopoData = HCalloc (MAP_WIDTH * MAP_HEIGHT); + Orbit->TopoZoomFrame = CaptureDrawable (CreateDrawable ( + WANT_PIXMAP, MAP_WIDTH << 2, MAP_HEIGHT << 2, 1)); + Orbit->TopoColors = HMalloc (sizeof (Orbit->TopoColors[0]) + * (MAP_HEIGHT * (MAP_WIDTH + SPHERE_SPAN_X))); + // always allocate the scratch array to largest needed size + Orbit->ScratchArray = HMalloc (sizeof (Orbit->ScratchArray[0]) + * (SHIELD_DIAM) * (SHIELD_DIAM)); +} + +static unsigned +frandom (void) +{ + static unsigned seed = 0x12345678; + + if (seed == 0) + seed = 15807; + seed = (seed >> 4) * 227; + + return seed; +} + +static inline int +TopoVarianceFactor (int step, int allowed, int min) +{ +#define SCALE_SHIFT 8 + return ((abs(step) * allowed) >> SCALE_SHIFT) + min; +} + +static inline int +TopoVarianceCalc (int factor) +{ + if (factor == 0) + return 0; + else + return (frandom () % factor) - (factor >> 1); +} + +static void +TopoScale4x (SBYTE *pDstTopo, SBYTE *pSrcTopo, int num_faults, int fault_var) +{ + // Interpolate the topographical data by connecting the elevations + // to their nearest neighboors using straight lines (in random + // direction) with random variance factors defined by + // num_faults and fault_var args +#define AVG_VARIANCE 250 + int x, y; + const int w = MAP_WIDTH, h = MAP_HEIGHT; + const int spitch = MAP_WIDTH, dpitch = MAP_WIDTH * 4; + SBYTE *pSrc; + SBYTE *pDst; + int* prevrow; + int* prow; + int elev[5][5]; + int var_allow, var_min; + static const struct line_def_t + { + int x0, y0, x1, y1; + int dx, dy; + } + fill_lines[4][6] = + { + { // diag set 0 + { 0, 2, 2, 0, 1, -1}, + { 0, 3, 3, 0, 1, -1}, + { 0, 4, 4, 0, 1, -1}, + { 1, 4, 4, 1, 1, -1}, + { 2, 4, 4, 2, 1, -1}, + {-1, -1, -1, -1, 0, 0}, // term + }, + { // diag set 1 + { 0, 2, 2, 4, 1, 1}, + { 0, 1, 3, 4, 1, 1}, + { 0, 0, 4, 4, 1, 1}, + { 1, 0, 4, 3, 1, 1}, + { 2, 0, 4, 2, 1, 1}, + {-1, -1, -1, -1, 0, 0}, // term + }, + { // horizontal + { 0, 1, 4, 1, 1, 0}, + { 0, 2, 4, 2, 1, 0}, + { 0, 3, 4, 3, 1, 0}, + {-1, -1, -1, -1, 0, 0}, // term + }, + { // vertical + { 1, 0, 1, 4, 0, 1}, + { 2, 0, 2, 4, 0, 1}, + { 3, 0, 3, 4, 0, 1}, + {-1, -1, -1, -1, 0, 0}, // term + }, + }; + + prevrow = (int *) HMalloc ((MAP_WIDTH * 4 + 1) * sizeof(prevrow[0])); + + var_allow = (num_faults << SCALE_SHIFT) / AVG_VARIANCE; + var_min = fault_var << SCALE_SHIFT; + + //memset (pDstTopo, 0, MAP_WIDTH * MAP_HEIGHT * 16); + + // init the first row in advance + pSrc = pSrcTopo; + prow = prevrow; +#define STEP_RANGE (4 - 1) + prow[0] = ((int)pSrc[0]) << SCALE_SHIFT;; + for (x = 0; x < w; ++x, ++pSrc, prow += 4) + { + int x2; + int val, step, rndfact; + + // next point in row + if (x < w - 1) + // one right + prow[4] = ((int)pSrc[1]) << SCALE_SHIFT; + else + // wrap around + prow[4] = ((int)pSrc[1 - spitch]) << SCALE_SHIFT; + + // compute elevations between 2 points + val = prow[0]; + step = (prow[4] - val) / STEP_RANGE; + rndfact = TopoVarianceFactor (step, var_allow, var_min); + for (x2 = 1, val += step; x2 < 4; ++x2, val += step) + prow[x2] = val + TopoVarianceCalc (rndfact); + } + + pSrc = pSrcTopo; + pDst = pDstTopo; + for (y = 0; y < h; ++y, pDst += dpitch * 3) + { + int x2, y2; + SBYTE *p; + int val, step, rndfact; + const struct line_def_t* pld; + + prow = prevrow; + // prime the first interpolated column + elev[4][0] = prow[0]; + if (y < h - 1) + elev[4][4] = ((int)pSrc[spitch]) << SCALE_SHIFT; + else + elev[4][4] = elev[4][0]; + // compute elevations for interpolated column + val = elev[4][0]; + step = (elev[4][4] - val) / STEP_RANGE; + rndfact = TopoVarianceFactor (step, var_allow, var_min); + for (y2 = 1, val += step; y2 < 4; ++y2, val += step) + elev[4][y2] = val + TopoVarianceCalc (rndfact); + + for (x = 0; x < w; ++x, ++pSrc, pDst += 4, prow += 4) + { + // recall the first interpolated row from prevrow + for (x2 = 0; x2 <= 4; ++x2) + elev[x2][0] = prow[x2]; + // recall the first interpolated column + for (y2 = 1; y2 <= 4; ++y2) + elev[0][y2] = elev[4][y2]; + + if (y < h - 1) + { + if (x < w - 1) + // one right, one down + elev[4][4] = ((int)pSrc[1 + spitch]) << SCALE_SHIFT; + else + // wrap around, one down + elev[4][4] = ((int)pSrc[1]) << SCALE_SHIFT; + } + else + { + elev[4][4] = elev[4][0]; + } + + // compute elevations for the rest of square borders first + val = elev[0][4]; + step = (elev[4][4] - val) / STEP_RANGE; + rndfact = TopoVarianceFactor (step, var_allow, var_min); + for (x2 = 1, val += step; x2 < 4; ++x2, val += step) + elev[x2][4] = val + TopoVarianceCalc (rndfact); + + val = elev[4][0]; + step = (elev[4][4] - val) / STEP_RANGE; + rndfact = TopoVarianceFactor (step, var_allow, var_min); + for (y2 = 1, val += step; y2 < 4; ++y2, val += step) + elev[4][y2] = val + TopoVarianceCalc (rndfact); + + // fill in the rest by connecting opposing elevations + // some randomness to determine which elevations to connect + for (pld = fill_lines[frandom () & 3]; pld->x0 >= 0; ++pld) + { + int num_steps; + + x2 = pld->x0; + y2 = pld->y0; + val = elev[x2][y2]; + num_steps = pld->x1 - pld->x0; + if (num_steps == 0) + num_steps = pld->y1 - pld->y0; + step = (elev[pld->x1][pld->y1] - val) / num_steps; + rndfact = TopoVarianceFactor (step, var_allow, var_min); + + for (x2 += pld->dx, y2 += pld->dy, val += step; + x2 != pld->x1 || y2 != pld->y1; + x2 += pld->dx, y2 += pld->dy, val += step) + { + elev[x2][y2] = val + TopoVarianceCalc (rndfact); + } + } + + // output the interpolated topography + for (y2 = 0; y2 < 4; ++y2) + { + p = pDst + y2 * dpitch; + for (x2 = 0; x2 < 4; ++x2, ++p) + { + int e = elev[x2][y2] >> SCALE_SHIFT; + if (e > 127) + e = 127; + else if (e < -128) + e = -128; + *p = (SBYTE)e; + } + } + + // save last interpolated row to prevrow for later + for (x2 = 0; x2 < 4; ++x2) + prow[x2] = elev[x2][4]; + } + // save last row point + prow[0] = elev[4][4]; + } + + HFree (prevrow); +} + + +// GenerateLightMap produces a surface light variance map for the +// rotating planet by, first, transforming absolute elevation data +// into normalized relative and then applying a weighted +// average-median of surrounding points +// Lots of pure Voodoo here ;) +// the goal is a 3D illusion, not mathematically correct lighting + +#define LMAP_AVG_BLOCK ((MAP_HEIGHT + 4) / 5) +#define LMAP_MAX_DIST ((LMAP_AVG_BLOCK + 1) >> 1) +#define LMAP_WEIGHT_THRES (LMAP_MAX_DIST * 2 / 3) + +typedef struct +{ + int min; + int max; + int avg; + +} elev_block_t; + +static inline void +get_vblock_avg (elev_block_t *pblk, SBYTE *pTopo, int x, int y) +{ + SBYTE *elev = pTopo; + int y0, y1, i; + int min = 127, max = -127; + int avg = 0, total_weight = 0; + + // surface wraps around along x + x = (x + MAP_WIDTH) % MAP_WIDTH; + + y0 = y - LMAP_MAX_DIST; + y1 = y + LMAP_MAX_DIST; + if (y0 < 0) + y0 = 0; + if (y1 > MAP_HEIGHT) + y1 = MAP_HEIGHT; + + elev = pTopo + y0 * MAP_WIDTH + x; + for (i = y0; i < y1; ++i, elev += MAP_WIDTH) + { + int delta = abs (i - y); + int weight = 255; // full weight + int v = *elev; + + if (delta >= LMAP_WEIGHT_THRES) + { // too far -- progressively reduced weight + weight = weight * (LMAP_MAX_DIST - delta + 1) + / (LMAP_MAX_DIST - LMAP_WEIGHT_THRES + 2); + } + + if (v > max) + max = v; + if (v < min) + min = v; + avg += v * weight; + total_weight += weight; + } + avg /= total_weight; + + pblk->min = min; + pblk->max = max; + pblk->avg = avg / (y1 - y0); +} + +// See description above +static void +GenerateLightMap (SBYTE *pTopo, int w, int h) +{ +#define LMAP_BLOCKS (2 * LMAP_MAX_DIST + 1) + int x, y; + elev_block_t vblocks[LMAP_BLOCKS]; + // we use a running block average to reduce the amount of work + // where a block is a vertical line of map points + SBYTE *elev; + int min, max, med; + int sfact, spread; + + // normalize the topo data + min = 127; + max = -128; + for (x = 0, elev = pTopo; x < w * h; ++x, ++elev) + { + int v = *elev; + if (v > max) + max = v; + if (v < min) + min = v; + } + med = (min + max) / 2; + spread = max - med; + + if (spread == 0) + { // perfectly smooth surface -- nothing to do but + // level it out completely + if (max != 0) + memset (pTopo, 0, w * h); + return; + } + + // these are whatever looks right + if (spread < 10) + sfact = 30; // minimal spread + else if (spread < 30) + sfact = 60; + else + sfact = 100; // full spread + + // apply spread + for (x = 0, elev = pTopo; x < w * h; ++x, ++elev) + { + int v = *elev; + v = (v - med) * sfact / spread; + *elev = v; + } + + // compute and apply weighted averages of surrounding points + for (y = 0, elev = pTopo; y < h; ++y) + { + elev_block_t *pblk; + int i; + + // prime the running block average + // get the minimum, maximum and avg elevation for each block + for (i = -LMAP_MAX_DIST; i < LMAP_MAX_DIST; ++i) + { + // blocks wrap around on both sides + pblk = vblocks + ((i + LMAP_BLOCKS) % LMAP_BLOCKS); + + get_vblock_avg (pblk, pTopo, i, y); + } + + for (x = 0; x < w; ++x, ++elev) + { + int avg = 0, total_weight = 0; + + min = 127; + max = -127; + + // prepare next block as we move along x + pblk = vblocks + ((x + LMAP_MAX_DIST) % LMAP_BLOCKS); + get_vblock_avg (pblk, pTopo, x + LMAP_MAX_DIST, y); + + // compute the min, max and weighted avg of blocks + for (i = x - LMAP_MAX_DIST; i <= x + LMAP_MAX_DIST; ++i) + { + int delta = abs (i - x); + int weight = 255; // full weight + + pblk = vblocks + ((i + LMAP_BLOCKS) % LMAP_BLOCKS); + + if (delta >= LMAP_WEIGHT_THRES) + { // too far -- progressively reduced weight + weight = weight * (LMAP_MAX_DIST - delta + 1) + / (LMAP_MAX_DIST - LMAP_WEIGHT_THRES + 2); + } + + if (pblk->max > max) + max = pblk->max; + if (pblk->min < min) + min = pblk->min; + + avg += pblk->avg * weight; + total_weight += weight; + } + avg /= total_weight; + + // This is mostly Voodoo + // figure out what kind of relative lighting factor + // to assign to this point +#if 0 + // relative to median + med = (min + max) / 2; // median + *elev = (int)*elev - med; +#else + // relative to median of (average, median) + med = (min + max) / 2; // median + med = (med + avg) / 2; + *elev = (int)*elev - med; +#endif + } + } +} + +// Sets the SysGenRNG to the required state first. +void +GeneratePlanetSurface (PLANET_DESC *pPlanetDesc, FRAME SurfDefFrame) +{ + RECT r; + const PlanetFrame *PlanDataPtr; + PLANET_INFO *PlanetInfo = &pSolarSysState->SysInfo.PlanetInfo; + COUNT i, y; + POINT loc; + CONTEXT OldContext; + CONTEXT TopoContext; + PLANET_ORBIT *Orbit = &pSolarSysState->Orbit; + BOOLEAN shielded = (pPlanetDesc->data_index & PLANET_SHIELDED) != 0; + + RandomContext_SeedRandom (SysGenRNG, pPlanetDesc->rand_seed); + + TopoContext = CreateContext ("Plangen.TopoContext"); + OldContext = SetContext (TopoContext); + planet_orbit_init (); + + PlanDataPtr = &PlanData[pPlanetDesc->data_index & ~PLANET_SHIELDED]; + + if (SurfDefFrame) + { // This is a defined planet; pixmap for the topography and + // elevation data is supplied in Surface Definition frame + BOOLEAN DeleteDef = FALSE; + FRAME ElevFrame; + + // surface pixmap + SurfDefFrame = SetAbsFrameIndex (SurfDefFrame, 0); + if (GetFrameWidth (SurfDefFrame) != MAP_WIDTH + || GetFrameHeight (SurfDefFrame) != MAP_HEIGHT) + { + pSolarSysState->TopoFrame = CaptureDrawable (RescaleFrame ( + SurfDefFrame, MAP_WIDTH, MAP_HEIGHT)); + // will not need the passed FRAME anymore + DeleteDef = TRUE; + } + else + pSolarSysState->TopoFrame = SurfDefFrame; + + if (GetFrameCount (SurfDefFrame) > 1) + { // 2nd frame is elevation data + int i; + SBYTE* elev; + + ElevFrame = SetAbsFrameIndex (SurfDefFrame, 1); + if (GetFrameWidth (ElevFrame) != MAP_WIDTH + || GetFrameHeight (ElevFrame) != MAP_HEIGHT) + { + ElevFrame = CaptureDrawable (RescaleFrame (ElevFrame, + MAP_WIDTH, MAP_HEIGHT)); + } + + // grab the elevation data in 1 byte per pixel format + ReadFramePixelIndexes (ElevFrame, (BYTE *)Orbit->lpTopoData, + MAP_WIDTH, MAP_HEIGHT); + // the supplied data is in unsigned format, must convert + for (i = 0, elev = Orbit->lpTopoData; + i < MAP_WIDTH * MAP_HEIGHT; + ++i, ++elev) + { + *elev = *(BYTE *)elev - 128; + } + } + else + { // no elevation data -- planet flat as a pancake + memset (Orbit->lpTopoData, 0, MAP_WIDTH * MAP_HEIGHT); + } + + if (DeleteDef) + DestroyDrawable (ReleaseDrawable (SurfDefFrame)); + } + else + { // Generate planet surface elevation data and look + + r.corner.x = r.corner.y = 0; + r.extent.width = MAP_WIDTH; + r.extent.height = MAP_HEIGHT; + { + memset (Orbit->lpTopoData, 0, MAP_WIDTH * MAP_HEIGHT); + switch (PLANALGO (PlanDataPtr->Type)) + { + case GAS_GIANT_ALGO: + MakeGasGiant (PlanDataPtr->num_faults, + Orbit->lpTopoData, &r, PlanDataPtr->fault_depth); + break; + case TOPO_ALGO: + case CRATERED_ALGO: + if (PlanDataPtr->num_faults) + DeltaTopography (PlanDataPtr->num_faults, + Orbit->lpTopoData, &r, + PlanDataPtr->fault_depth); + + for (i = 0; i < PlanDataPtr->num_blemishes; ++i) + { + RECT crater_r; + UWORD loword; + + loword = LOWORD (RandomContext_Random (SysGenRNG)); + switch (HIBYTE (loword) & 31) + { + case 0: + crater_r.extent.width = + (LOBYTE (loword) % (MAP_HEIGHT >> 2)) + + (MAP_HEIGHT >> 2); + break; + case 1: + case 2: + case 3: + case 4: + crater_r.extent.width = + (LOBYTE (loword) % (MAP_HEIGHT >> 3)) + + (MAP_HEIGHT >> 3); + break; + default: + crater_r.extent.width = + (LOBYTE (loword) % (MAP_HEIGHT >> 4)) + + 4; + break; + } + + loword = LOWORD (RandomContext_Random (SysGenRNG)); + crater_r.extent.height = crater_r.extent.width; + crater_r.corner.x = HIBYTE (loword) + % (MAP_WIDTH - crater_r.extent.width); + crater_r.corner.y = LOBYTE (loword) + % (MAP_HEIGHT - crater_r.extent.height); + MakeCrater (&crater_r, Orbit->lpTopoData, + PlanDataPtr->fault_depth << 2, + -(PlanDataPtr->fault_depth << 2), + FALSE); + } + + if (PLANALGO (PlanDataPtr->Type) == CRATERED_ALGO) + DitherMap (Orbit->lpTopoData); + ValidateMap (Orbit->lpTopoData); + break; + } + } + pSolarSysState->TopoFrame = CaptureDrawable ( + CreateDrawable (WANT_PIXMAP, (SIZE)MAP_WIDTH, + (SIZE)MAP_HEIGHT, 1)); + pSolarSysState->OrbitalCMap = CaptureColorMap ( + LoadColorMap (PlanDataPtr->CMapInstance)); + pSolarSysState->XlatRef = CaptureStringTable ( + LoadStringTable (PlanDataPtr->XlatTabInstance)); + + if (PlanetInfo->SurfaceTemperature > HOT_THRESHOLD) + { + pSolarSysState->OrbitalCMap = SetAbsColorMapIndex ( + pSolarSysState->OrbitalCMap, 2); + pSolarSysState->XlatRef = SetAbsStringTableIndex ( + pSolarSysState->XlatRef, 2); + } + else if (PlanetInfo->SurfaceTemperature > COLD_THRESHOLD) + { + pSolarSysState->OrbitalCMap = SetAbsColorMapIndex ( + pSolarSysState->OrbitalCMap, 1); + pSolarSysState->XlatRef = SetAbsStringTableIndex ( + pSolarSysState->XlatRef, 1); + } + pSolarSysState->XlatPtr = GetStringAddress (pSolarSysState->XlatRef); + RenderTopography (pSolarSysState->TopoFrame, + Orbit->lpTopoData, MAP_WIDTH, MAP_HEIGHT); + + } + + if (!shielded && PlanetInfo->AtmoDensity != GAS_GIANT_ATMOSPHERE) + { // produce 4x scaled topo image for Planetside + // for the planets that we can land on + SBYTE *pScaledTopo = HMalloc (MAP_WIDTH * 4 * MAP_HEIGHT * 4); + if (pScaledTopo) + { + TopoScale4x (pScaledTopo, Orbit->lpTopoData, + PlanDataPtr->num_faults, PlanDataPtr->fault_depth + * (PLANALGO (PlanDataPtr->Type) == CRATERED_ALGO ? 2 : 1 )); + RenderTopography (Orbit->TopoZoomFrame, pScaledTopo, + MAP_WIDTH * 4, MAP_HEIGHT * 4); + + HFree (pScaledTopo); + } + } + + // Generate a pixel array from the Topography map. + // We use this instead of lpTopoData because it needs to be + // WAP_WIDTH+SPHERE_SPAN_X wide and we need this method for Earth anyway. + // It may be more efficient to build it from lpTopoData instead of the + // FRAMPTR though. + ReadFramePixelColors (pSolarSysState->TopoFrame, Orbit->TopoColors, + MAP_WIDTH + SPHERE_SPAN_X, MAP_HEIGHT); + // Extend the width from MAP_WIDTH to MAP_WIDTH+SPHERE_SPAN_X + for (y = 0; y < MAP_HEIGHT * (MAP_WIDTH + SPHERE_SPAN_X); + y += MAP_WIDTH + SPHERE_SPAN_X) + memcpy (Orbit->TopoColors + y + MAP_WIDTH, Orbit->TopoColors + y, + SPHERE_SPAN_X * sizeof (Orbit->TopoColors[0])); + + if (PLANALGO (PlanDataPtr->Type) != GAS_GIANT_ALGO) + { // convert topo data to a light map, based on relative + // map point elevations + GenerateLightMap (Orbit->lpTopoData, MAP_WIDTH, MAP_HEIGHT); + } + else + { // gas giants are pretty much flat + memset (Orbit->lpTopoData, 0, MAP_WIDTH * MAP_HEIGHT); + } + + if (pSolarSysState->pOrbitalDesc->pPrevDesc == + &pSolarSysState->SunDesc[0]) + { // this is a planet -- get its location + loc = pSolarSysState->pOrbitalDesc->location; + } + else + { // this is a moon -- get its planet's location + loc = pSolarSysState->pOrbitalDesc->pPrevDesc->location; + } + + // Rotating planet sphere initialization + GenerateSphereMask (loc); + CreateSphereTiltMap (PlanetInfo->AxialTilt); + if (shielded) + Orbit->ObjectFrame = CreateShieldMask (); + InitSphereRotation (1 - 2 * (PlanetInfo->AxialTilt & 1), shielded); + + if (shielded) + { // This overwrites pSolarSysState->TopoFrame, so everything that + // needs it has to come before + ApplyShieldTint (); + } + + SetContext (OldContext); + DestroyContext (TopoContext); +} + diff --git a/src/uqm/planets/pstarmap.c b/src/uqm/planets/pstarmap.c new file mode 100644 index 0000000..cd33858 --- /dev/null +++ b/src/uqm/planets/pstarmap.c @@ -0,0 +1,1631 @@ +//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 "scan.h" +#include "../colors.h" +#include "../controls.h" +#include "../menustat.h" +#include "../starmap.h" +#include "../races.h" +#include "../gameopt.h" +#include "../gamestr.h" +#include "../globdata.h" +#include "../shipcont.h" +#include "../units.h" +#include "../hyper.h" +#include "../sis.h" + // for DrawHyperCoords(), DrawStatusMessage() +#include "../settings.h" +#include "../setup.h" +#include "../sounds.h" +#include "../state.h" +#include "../uqmdebug.h" +#include "options.h" +#include "libs/inplib.h" +#include "libs/strlib.h" +#include "libs/graphics/gfx_common.h" +#include "libs/mathlib.h" +#include "libs/memlib.h" + +#include <stdlib.h> + + +static POINT cursorLoc; +static POINT mapOrigin; +static int zoomLevel; +static FRAME StarMapFrame; + + +static inline long +signedDivWithError (long val, long divisor) +{ + int invert = 0; + if (val < 0) + { + invert = 1; + val = -val; + } + val = (val + ROUNDING_ERROR (divisor)) / divisor; + return invert ? -val : val; +} + +#define MAP_FIT_X ((MAX_X_UNIVERSE + 1) / SIS_SCREEN_WIDTH + 1) + +static inline COORD +universeToDispx (long ux) +{ + return signedDivWithError (((ux - mapOrigin.x) << zoomLevel) + * SIS_SCREEN_WIDTH, MAX_X_UNIVERSE + MAP_FIT_X) + + ((SIS_SCREEN_WIDTH - 1) >> 1); +} +#define UNIVERSE_TO_DISPX(ux) universeToDispx(ux) + +static inline COORD +universeToDispy (long uy) +{ + return signedDivWithError (((mapOrigin.y - uy) << zoomLevel) + * SIS_SCREEN_HEIGHT, MAX_Y_UNIVERSE + 2) + + ((SIS_SCREEN_HEIGHT - 1) >> 1); +} +#define UNIVERSE_TO_DISPY(uy) universeToDispy(uy) + +static inline COORD +dispxToUniverse (COORD dx) +{ + return (((long)(dx - ((SIS_SCREEN_WIDTH - 1) >> 1)) + * (MAX_X_UNIVERSE + MAP_FIT_X)) >> zoomLevel) + / SIS_SCREEN_WIDTH + mapOrigin.x; +} +#define DISP_TO_UNIVERSEX(dx) dispxToUniverse(dx) + +static inline COORD +dispyToUniverse (COORD dy) +{ + return (((long)(((SIS_SCREEN_HEIGHT - 1) >> 1) - dy) + * (MAX_Y_UNIVERSE + 2)) >> zoomLevel) + / SIS_SCREEN_HEIGHT + mapOrigin.y; +} +#define DISP_TO_UNIVERSEY(dy) dispyToUniverse(dy) + +static BOOLEAN transition_pending; + +static void +flashCurrentLocation (POINT *where) +{ + static BYTE c = 0; + static int val = -2; + static POINT universe; + static TimeCount NextTime = 0; + + if (where) + universe = *where; + + if (GetTimeCounter () >= NextTime) + { + Color OldColor; + CONTEXT OldContext; + STAMP s; + + NextTime = GetTimeCounter () + (ONE_SECOND / 16); + + OldContext = SetContext (SpaceContext); + + if (c == 0x00 || c == 0x1A) + val = -val; + c += val; + OldColor = SetContextForeGroundColor ( + BUILD_COLOR (MAKE_RGB15 (c, c, c), c)); + s.origin.x = UNIVERSE_TO_DISPX (universe.x); + s.origin.y = UNIVERSE_TO_DISPY (universe.y); + s.frame = IncFrameIndex (StarMapFrame); + DrawFilledStamp (&s); + SetContextForeGroundColor (OldColor); + + SetContext (OldContext); + } +} + +static void +DrawCursor (COORD curs_x, COORD curs_y) +{ + STAMP s; + + s.origin.x = curs_x; + s.origin.y = curs_y; + s.frame = StarMapFrame; + + DrawStamp (&s); +} + +static void +DrawAutoPilot (POINT *pDstPt) +{ + SIZE dx, dy, + xincr, yincr, + xerror, yerror, + cycle, delta; + POINT pt; + + if (!inHQSpace ()) + pt = CurStarDescPtr->star_pt; + else + { + pt.x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x)); + pt.y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y)); + } + pt.x = UNIVERSE_TO_DISPX (pt.x); + pt.y = UNIVERSE_TO_DISPY (pt.y); + + dx = UNIVERSE_TO_DISPX (pDstPt->x) - pt.x; + if (dx >= 0) + xincr = 1; + else + { + xincr = -1; + dx = -dx; + } + dx <<= 1; + + dy = UNIVERSE_TO_DISPY (pDstPt->y) - pt.y; + if (dy >= 0) + yincr = 1; + else + { + yincr = -1; + dy = -dy; + } + dy <<= 1; + + if (dx >= dy) + cycle = dx; + else + cycle = dy; + delta = xerror = yerror = cycle >> 1; + + SetContextForeGroundColor ( + BUILD_COLOR (MAKE_RGB15 (0x04, 0x04, 0x1F), 0x01)); + + delta &= ~1; + while (delta--) + { + if (!(delta & 1)) + DrawPoint (&pt); + + if ((xerror -= dx) <= 0) + { + pt.x += xincr; + xerror += cycle; + } + if ((yerror -= dy) <= 0) + { + pt.y += yincr; + yerror += cycle; + } + } +} + +static void +GetSphereRect (FLEET_INFO *FleetPtr, RECT *pRect, RECT *pRepairRect) +{ + long diameter; + + diameter = (long)(FleetPtr->known_strength * SPHERE_RADIUS_INCREMENT); + pRect->extent.width = UNIVERSE_TO_DISPX (diameter) + - UNIVERSE_TO_DISPX (0); + if (pRect->extent.width < 0) + pRect->extent.width = -pRect->extent.width; + else if (pRect->extent.width == 0) + pRect->extent.width = 1; + pRect->extent.height = UNIVERSE_TO_DISPY (diameter) + - UNIVERSE_TO_DISPY (0); + if (pRect->extent.height < 0) + pRect->extent.height = -pRect->extent.height; + else if (pRect->extent.height == 0) + pRect->extent.height = 1; + + pRect->corner.x = UNIVERSE_TO_DISPX (FleetPtr->known_loc.x); + pRect->corner.y = UNIVERSE_TO_DISPY (FleetPtr->known_loc.y); + pRect->corner.x -= pRect->extent.width >> 1; + pRect->corner.y -= pRect->extent.height >> 1; + + { + TEXT t; + STRING locString; + + SetContextFont (TinyFont); + + t.baseline.x = pRect->corner.x + (pRect->extent.width >> 1); + t.baseline.y = pRect->corner.y + (pRect->extent.height >> 1) - 1; + t.align = ALIGN_CENTER; + locString = SetAbsStringTableIndex (FleetPtr->race_strings, 1); + t.CharCount = GetStringLength (locString); + t.pStr = (UNICODE *)GetStringAddress (locString); + TextRect (&t, pRepairRect, NULL); + + if (pRepairRect->corner.x <= 0) + pRepairRect->corner.x = 1; + else if (pRepairRect->corner.x + pRepairRect->extent.width >= + SIS_SCREEN_WIDTH) + pRepairRect->corner.x = + SIS_SCREEN_WIDTH - pRepairRect->extent.width - 1; + if (pRepairRect->corner.y <= 0) + pRepairRect->corner.y = 1; + else if (pRepairRect->corner.y + pRepairRect->extent.height >= + SIS_SCREEN_HEIGHT) + pRepairRect->corner.y = + SIS_SCREEN_HEIGHT - pRepairRect->extent.height - 1; + + BoxUnion (pRepairRect, pRect, pRepairRect); + pRepairRect->extent.width++; + pRepairRect->extent.height++; + } +} + +static void +DrawStarMap (COUNT race_update, RECT *pClipRect) +{ +#define GRID_DELTA 500 + SIZE i; + COUNT which_space; + long diameter; + RECT r, old_r; + POINT oldOrigin = {0, 0}; + STAMP s; + FRAME star_frame; + STAR_DESC *SDPtr; + BOOLEAN draw_cursor; + + if (pClipRect == (RECT*)-1) + { + pClipRect = 0; + draw_cursor = FALSE; + } + else + { + draw_cursor = TRUE; + } + + SetContext (SpaceContext); + if (pClipRect) + { + GetContextClipRect (&old_r); + pClipRect->corner.x += old_r.corner.x; + pClipRect->corner.y += old_r.corner.y; + SetContextClipRect (pClipRect); + pClipRect->corner.x -= old_r.corner.x; + pClipRect->corner.y -= old_r.corner.y; + // Offset the origin so that we draw the correct gfx in the cliprect + oldOrigin = SetContextOrigin (MAKE_POINT (-pClipRect->corner.x, + -pClipRect->corner.y)); + } + + if (transition_pending) + { + SetTransitionSource (NULL); + } + BatchGraphics (); + + which_space = GET_GAME_STATE (ARILOU_SPACE_SIDE); + + if (which_space <= 1) + { + SDPtr = &star_array[0]; + SetContextForeGroundColor ( + BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x07), 0x57)); + SetContextBackGroundColor (BLACK_COLOR); + } + else + { + SDPtr = &star_array[NUM_SOLAR_SYSTEMS + 1]; + SetContextForeGroundColor ( + BUILD_COLOR (MAKE_RGB15 (0x00, 0x0B, 0x00), 0x6D)); + SetContextBackGroundColor ( + BUILD_COLOR (MAKE_RGB15 (0x00, 0x08, 0x00), 0x6E)); + } + ClearDrawable (); + + // Draw the fuel range circle + if (race_update == 0 + && which_space < 2 + && (diameter = (long)GLOBAL_SIS (FuelOnBoard) << 1)) + { + Color OldColor; + + if (!inHQSpace ()) + r.corner = CurStarDescPtr->star_pt; + else + { + r.corner.x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x)); + r.corner.y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y)); + } + + // Cap the diameter to a sane range + if (diameter > MAX_X_UNIVERSE * 4) + diameter = MAX_X_UNIVERSE * 4; + + r.extent.width = UNIVERSE_TO_DISPX (diameter) + - UNIVERSE_TO_DISPX (0); + if (r.extent.width < 0) + r.extent.width = -r.extent.width; + r.extent.height = UNIVERSE_TO_DISPY (diameter) + - UNIVERSE_TO_DISPY (0); + if (r.extent.height < 0) + r.extent.height = -r.extent.height; + + r.corner.x = UNIVERSE_TO_DISPX (r.corner.x) + - (r.extent.width >> 1); + r.corner.y = UNIVERSE_TO_DISPY (r.corner.y) + - (r.extent.height >> 1); + + OldColor = SetContextForeGroundColor ( + BUILD_COLOR (MAKE_RGB15 (0x03, 0x03, 0x03), 0x22)); + DrawFilledOval (&r); + SetContextForeGroundColor (OldColor); + } + + for (i = MAX_Y_UNIVERSE + 1; i >= 0; i -= GRID_DELTA) + { + SIZE j; + + r.corner.x = UNIVERSE_TO_DISPX (0); + r.corner.y = UNIVERSE_TO_DISPY (i); + r.extent.width = SIS_SCREEN_WIDTH << zoomLevel; + r.extent.height = 1; + DrawFilledRectangle (&r); + + r.corner.y = UNIVERSE_TO_DISPY (MAX_Y_UNIVERSE + 1); + r.extent.width = 1; + r.extent.height = SIS_SCREEN_HEIGHT << zoomLevel; + for (j = MAX_X_UNIVERSE + 1; j >= 0; j -= GRID_DELTA) + { + r.corner.x = UNIVERSE_TO_DISPX (j); + DrawFilledRectangle (&r); + } + } + + star_frame = SetRelFrameIndex (StarMapFrame, 2); + if (which_space <= 1) + { + COUNT index; + HFLEETINFO hStarShip, hNextShip; + static const Color race_colors[] = + { + RACE_COLORS + }; + + for (index = 0, + hStarShip = GetHeadLink (&GLOBAL (avail_race_q)); + hStarShip != 0; ++index, hStarShip = hNextShip) + { + FLEET_INFO *FleetPtr; + + FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip); + hNextShip = _GetSuccLink (FleetPtr); + + if (FleetPtr->known_strength) + { + RECT repair_r; + + GetSphereRect (FleetPtr, &r, &repair_r); + if (r.corner.x < SIS_SCREEN_WIDTH + && r.corner.y < SIS_SCREEN_HEIGHT + && r.corner.x + r.extent.width > 0 + && r.corner.y + r.extent.height > 0 + && (pClipRect == 0 + || (repair_r.corner.x < pClipRect->corner.x + pClipRect->extent.width + && repair_r.corner.y < pClipRect->corner.y + pClipRect->extent.height + && repair_r.corner.x + repair_r.extent.width > pClipRect->corner.x + && repair_r.corner.y + repair_r.extent.height > pClipRect->corner.y))) + { + Color c; + TEXT t; + STRING locString; + + c = race_colors[index]; + if (index + 1 == race_update) + SetContextForeGroundColor (WHITE_COLOR); + else + SetContextForeGroundColor (c); + DrawOval (&r, 0); + + SetContextFont (TinyFont); + + t.baseline.x = r.corner.x + (r.extent.width >> 1); + t.baseline.y = r.corner.y + (r.extent.height >> 1) - 1; + t.align = ALIGN_CENTER; + locString = SetAbsStringTableIndex ( + FleetPtr->race_strings, 1); + t.CharCount = GetStringLength (locString); + t.pStr = (UNICODE *)GetStringAddress (locString); + TextRect (&t, &r, NULL); + + if (r.corner.x <= 0) + t.baseline.x -= r.corner.x - 1; + else if (r.corner.x + r.extent.width >= SIS_SCREEN_WIDTH) + t.baseline.x -= (r.corner.x + r.extent.width) + - SIS_SCREEN_WIDTH + 1; + if (r.corner.y <= 0) + t.baseline.y -= r.corner.y - 1; + else if (r.corner.y + r.extent.height >= SIS_SCREEN_HEIGHT) + t.baseline.y -= (r.corner.y + r.extent.height) + - SIS_SCREEN_HEIGHT + 1; + + // The text color is slightly lighter than the color of + // the SoI. + c.r = (c.r >= 0xff - CC5TO8 (0x03)) ? + 0xff : c.r + CC5TO8 (0x03); + c.g = (c.g >= 0xff - CC5TO8 (0x03)) ? + 0xff : c.g + CC5TO8 (0x03); + c.b = (c.b >= 0xff - CC5TO8 (0x03)) ? + 0xff : c.b + CC5TO8 (0x03); + + SetContextForeGroundColor (c); + font_DrawText (&t); + } + } + + UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip); + } + } + + do + { + BYTE star_type; + + star_type = SDPtr->Type; + + s.origin.x = UNIVERSE_TO_DISPX (SDPtr->star_pt.x); + s.origin.y = UNIVERSE_TO_DISPY (SDPtr->star_pt.y); + if (which_space <= 1) + s.frame = SetRelFrameIndex (star_frame, + STAR_TYPE (star_type) + * NUM_STAR_COLORS + + STAR_COLOR (star_type)); + else if (SDPtr->star_pt.x == ARILOU_HOME_X + && SDPtr->star_pt.y == ARILOU_HOME_Y) + s.frame = SetRelFrameIndex (star_frame, + SUPER_GIANT_STAR * NUM_STAR_COLORS + GREEN_BODY); + else + s.frame = SetRelFrameIndex (star_frame, + GIANT_STAR * NUM_STAR_COLORS + GREEN_BODY); + DrawStamp (&s); + + ++SDPtr; + } while (SDPtr->star_pt.x <= MAX_X_UNIVERSE + && SDPtr->star_pt.y <= MAX_Y_UNIVERSE); + + if (GET_GAME_STATE (ARILOU_SPACE)) + { + if (which_space <= 1) + { + s.origin.x = UNIVERSE_TO_DISPX (ARILOU_SPACE_X); + s.origin.y = UNIVERSE_TO_DISPY (ARILOU_SPACE_Y); + } + else + { + s.origin.x = UNIVERSE_TO_DISPX (QUASI_SPACE_X); + s.origin.y = UNIVERSE_TO_DISPY (QUASI_SPACE_Y); + } + s.frame = SetRelFrameIndex (star_frame, + GIANT_STAR * NUM_STAR_COLORS + GREEN_BODY); + DrawStamp (&s); + } + + if (race_update == 0 + && GLOBAL (autopilot.x) != ~0 + && GLOBAL (autopilot.y) != ~0) + DrawAutoPilot (&GLOBAL (autopilot)); + + if (transition_pending) + { + GetContextClipRect (&r); + ScreenTransition (3, &r); + transition_pending = FALSE; + } + + UnbatchGraphics (); + + if (pClipRect) + { + SetContextClipRect (&old_r); + SetContextOrigin (oldOrigin); + } + + if (race_update == 0) + { + if (draw_cursor) + { + GetContextClipRect (&r); + LoadIntoExtraScreen (&r); + DrawCursor (UNIVERSE_TO_DISPX (cursorLoc.x), + UNIVERSE_TO_DISPY (cursorLoc.y)); + } + } +} + +static void +EraseCursor (COORD curs_x, COORD curs_y) +{ + RECT r; + + GetFrameRect (StarMapFrame, &r); + + if ((r.corner.x += curs_x) < 0) + { + r.extent.width += r.corner.x; + r.corner.x = 0; + } + else if (r.corner.x + r.extent.width >= SIS_SCREEN_WIDTH) + r.extent.width = SIS_SCREEN_WIDTH - r.corner.x; + if ((r.corner.y += curs_y) < 0) + { + r.extent.height += r.corner.y; + r.corner.y = 0; + } + else if (r.corner.y + r.extent.height >= SIS_SCREEN_HEIGHT) + r.extent.height = SIS_SCREEN_HEIGHT - r.corner.y; + +#ifndef OLD + RepairBackRect (&r); +#else /* NEW */ + r.extent.height += r.corner.y & 1; + r.corner.y &= ~1; + DrawStarMap (0, &r); +#endif /* OLD */ +} + +static void +ZoomStarMap (SIZE dir) +{ +#define MAX_ZOOM_SHIFT 4 + if (dir > 0) + { + if (zoomLevel < MAX_ZOOM_SHIFT) + { + ++zoomLevel; + mapOrigin = cursorLoc; + + DrawStarMap (0, NULL); + SleepThread (ONE_SECOND / 8); + } + } + else if (dir < 0) + { + if (zoomLevel > 0) + { + if (zoomLevel > 1) + mapOrigin = cursorLoc; + else + { + mapOrigin.x = MAX_X_UNIVERSE >> 1; + mapOrigin.y = MAX_Y_UNIVERSE >> 1; + } + --zoomLevel; + + DrawStarMap (0, NULL); + SleepThread (ONE_SECOND / 8); + } + } +} + +static void +UpdateCursorLocation (int sx, int sy, const POINT *newpt) +{ + STAMP s; + POINT pt; + + pt.x = UNIVERSE_TO_DISPX (cursorLoc.x); + pt.y = UNIVERSE_TO_DISPY (cursorLoc.y); + + if (newpt) + { // absolute move + sx = sy = 0; + s.origin.x = UNIVERSE_TO_DISPX (newpt->x); + s.origin.y = UNIVERSE_TO_DISPY (newpt->y); + cursorLoc = *newpt; + } + else + { // incremental move + s.origin.x = pt.x + sx; + s.origin.y = pt.y + sy; + } + + if (sx) + { + cursorLoc.x = DISP_TO_UNIVERSEX (s.origin.x) - sx; + while (UNIVERSE_TO_DISPX (cursorLoc.x) == pt.x) + cursorLoc.x += sx; + + if (cursorLoc.x < 0) + cursorLoc.x = 0; + else if (cursorLoc.x > MAX_X_UNIVERSE) + cursorLoc.x = MAX_X_UNIVERSE; + + s.origin.x = UNIVERSE_TO_DISPX (cursorLoc.x); + } + + if (sy) + { + cursorLoc.y = DISP_TO_UNIVERSEY (s.origin.y) + sy; + while (UNIVERSE_TO_DISPY (cursorLoc.y) == pt.y) + cursorLoc.y -= sy; + + if (cursorLoc.y < 0) + cursorLoc.y = 0; + else if (cursorLoc.y > MAX_Y_UNIVERSE) + cursorLoc.y = MAX_Y_UNIVERSE; + + s.origin.y = UNIVERSE_TO_DISPY (cursorLoc.y); + } + + if (s.origin.x < 0 || s.origin.y < 0 + || s.origin.x >= SIS_SCREEN_WIDTH + || s.origin.y >= SIS_SCREEN_HEIGHT) + { + mapOrigin = cursorLoc; + DrawStarMap (0, NULL); + + s.origin.x = UNIVERSE_TO_DISPX (cursorLoc.x); + s.origin.y = UNIVERSE_TO_DISPY (cursorLoc.y); + } + else + { + EraseCursor (pt.x, pt.y); + // ClearDrawable (); + DrawCursor (s.origin.x, s.origin.y); + } +} + +#define CURSOR_INFO_BUFSIZE 256 + +static void +UpdateCursorInfo (UNICODE *prevbuf) +{ + UNICODE buf[CURSOR_INFO_BUFSIZE] = ""; + POINT pt; + STAR_DESC *SDPtr; + STAR_DESC *BestSDPtr; + + pt.x = UNIVERSE_TO_DISPX (cursorLoc.x); + pt.y = UNIVERSE_TO_DISPY (cursorLoc.y); + + SDPtr = BestSDPtr = 0; + while ((SDPtr = FindStar (SDPtr, &cursorLoc, 75, 75))) + { + if (UNIVERSE_TO_DISPX (SDPtr->star_pt.x) == pt.x + && UNIVERSE_TO_DISPY (SDPtr->star_pt.y) == pt.y + && (BestSDPtr == 0 + || STAR_TYPE (SDPtr->Type) >= STAR_TYPE (BestSDPtr->Type))) + BestSDPtr = SDPtr; + } + + if (BestSDPtr) + { + cursorLoc = BestSDPtr->star_pt; + GetClusterName (BestSDPtr, buf); + } + else + { // No star found. Reset the coordinates to the cursor's location + cursorLoc.x = DISP_TO_UNIVERSEX (pt.x); + if (cursorLoc.x < 0) + cursorLoc.x = 0; + else if (cursorLoc.x > MAX_X_UNIVERSE) + cursorLoc.x = MAX_X_UNIVERSE; + cursorLoc.y = DISP_TO_UNIVERSEY (pt.y); + if (cursorLoc.y < 0) + cursorLoc.y = 0; + else if (cursorLoc.y > MAX_Y_UNIVERSE) + cursorLoc.y = MAX_Y_UNIVERSE; + } + + if (GET_GAME_STATE (ARILOU_SPACE)) + { + POINT ari_pt; + + if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1) + { + ari_pt.x = ARILOU_SPACE_X; + ari_pt.y = ARILOU_SPACE_Y; + } + else + { + ari_pt.x = QUASI_SPACE_X; + ari_pt.y = QUASI_SPACE_Y; + } + + if (UNIVERSE_TO_DISPX (ari_pt.x) == pt.x + && UNIVERSE_TO_DISPY (ari_pt.y) == pt.y) + { + cursorLoc = ari_pt; + utf8StringCopy (buf, sizeof (buf), + GAME_STRING (STAR_STRING_BASE + 132)); + } + } + + DrawHyperCoords (cursorLoc); + if (strcmp (buf, prevbuf) != 0) + { + strcpy (prevbuf, buf); + DrawSISMessage (buf); + } +} + +static void +UpdateFuelRequirement (void) +{ + UNICODE buf[80]; + COUNT fuel_required; + DWORD f; + POINT pt; + + if (!inHQSpace ()) + pt = CurStarDescPtr->star_pt; + else + { + pt.x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x)); + pt.y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y)); + } + pt.x -= cursorLoc.x; + pt.y -= cursorLoc.y; + + f = (DWORD)((long)pt.x * pt.x + (long)pt.y * pt.y); + if (f == 0 || GET_GAME_STATE (ARILOU_SPACE_SIDE) > 1) + fuel_required = 0; + else + fuel_required = square_root (f) + (FUEL_TANK_SCALE / 20); + + sprintf (buf, "%s %u.%u", + GAME_STRING (NAVIGATION_STRING_BASE + 4), + fuel_required / FUEL_TANK_SCALE, + (fuel_required % FUEL_TANK_SCALE) / 10); + + DrawStatusMessage (buf); +} + +#define STAR_SEARCH_BUFSIZE 256 + +typedef struct starsearch_state +{ + // TODO: pMS field is probably not needed anymore + MENU_STATE *pMS; + UNICODE Text[STAR_SEARCH_BUFSIZE]; + UNICODE LastText[STAR_SEARCH_BUFSIZE]; + DWORD LastChangeTime; + int FirstIndex; + int CurIndex; + int LastIndex; + BOOLEAN SingleClust; + BOOLEAN SingleMatch; + UNICODE Buffer[STAR_SEARCH_BUFSIZE]; + const UNICODE *Prefix; + const UNICODE *Cluster; + int PrefixLen; + int ClusterLen; + int ClusterPos; + int SortedStars[NUM_SOLAR_SYSTEMS]; +} STAR_SEARCH_STATE; + +static int +compStarName (const void *ptr1, const void *ptr2) +{ + int index1; + int index2; + + index1 = *(const int *) ptr1; + index2 = *(const int *) ptr2; + if (star_array[index1].Postfix != star_array[index2].Postfix) + { + return utf8StringCompare (GAME_STRING (star_array[index1].Postfix), + GAME_STRING (star_array[index2].Postfix)); + } + + if (star_array[index1].Prefix < star_array[index2].Prefix) + return -1; + + if (star_array[index1].Prefix > star_array[index2].Prefix) + return 1; + + return 0; +} + +static void +SortStarsOnName (STAR_SEARCH_STATE *pSS) +{ + int i; + int *sorted = pSS->SortedStars; + + for (i = 0; i < NUM_SOLAR_SYSTEMS; i++) + sorted[i] = i; + + qsort (sorted, NUM_SOLAR_SYSTEMS, sizeof (int), compStarName); +} + +static void +SplitStarName (STAR_SEARCH_STATE *pSS) +{ + UNICODE *buf = pSS->Buffer; + UNICODE *next; + UNICODE *sep = NULL; + + pSS->Prefix = 0; + pSS->PrefixLen = 0; + pSS->Cluster = 0; + pSS->ClusterLen = 0; + pSS->ClusterPos = 0; + + // skip leading space + for (next = buf; *next != '\0' && + getCharFromString ((const UNICODE **)&next) == ' '; + buf = next) + ; + if (*buf == '\0') + { // no text + return; + } + + pSS->Prefix = buf; + + // See if player gave a prefix + for (buf = next; *next != '\0' && + getCharFromString ((const UNICODE **)&next) != ' '; + buf = next) + ; + if (*buf != '\0') + { // found possibly separating ' ' + sep = buf; + // skip separating space + for (buf = next; *next != '\0' && + getCharFromString ((const UNICODE **)&next) == ' '; + buf = next) + ; + } + + if (*buf == '\0') + { // reached the end -- cluster only + pSS->Cluster = pSS->Prefix; + pSS->ClusterLen = utf8StringCount (pSS->Cluster); + pSS->ClusterPos = utf8StringCountN (pSS->Buffer, pSS->Cluster); + pSS->Prefix = 0; + return; + } + + // consider the rest cluster name (whatever there is) + pSS->Cluster = buf; + pSS->ClusterLen = utf8StringCount (pSS->Cluster); + pSS->ClusterPos = utf8StringCountN (pSS->Buffer, pSS->Cluster); + *sep = '\0'; // split + pSS->PrefixLen = utf8StringCount (pSS->Prefix); +} + +static inline int +SkipStarCluster (int *sortedStars, int istar) +{ + int Postfix = star_array[sortedStars[istar]].Postfix; + + for (++istar; istar < NUM_SOLAR_SYSTEMS && + star_array[sortedStars[istar]].Postfix == Postfix; + ++istar) + ; + return istar; +} + +static int +FindNextStarIndex (STAR_SEARCH_STATE *pSS, int from, BOOLEAN WithinClust) +{ + int i; + + if (!pSS->Cluster) + return -1; // nothing to search for + + for (i = from; i < NUM_SOLAR_SYSTEMS; ++i) + { + STAR_DESC *SDPtr = &star_array[pSS->SortedStars[i]]; + UNICODE FullName[STAR_SEARCH_BUFSIZE]; + UNICODE *ClusterName = GAME_STRING (SDPtr->Postfix); + const UNICODE *sptr; + const UNICODE *dptr; + int dlen; + int c; + + dlen = utf8StringCount (ClusterName); + if (pSS->ClusterLen > dlen) + { // no match, skip the rest of cluster + i = SkipStarCluster (pSS->SortedStars, i) - 1; + continue; + } + + for (c = 0, sptr = pSS->Cluster, dptr = ClusterName; + c < pSS->ClusterLen; ++c) + { + UniChar sc = getCharFromString (&sptr); + UniChar dc = getCharFromString (&dptr); + + if (UniChar_toUpper (sc) != UniChar_toUpper (dc)) + break; + } + + if (c < pSS->ClusterLen) + { // no match here, skip the rest of cluster + i = SkipStarCluster (pSS->SortedStars, i) - 1; + continue; + } + + if (pSS->Prefix && !SDPtr->Prefix) + // we were given a prefix but found a singular star; + // that is a no match + continue; + + if (WithinClust) + // searching within clusters; any prefix is a match + break; + + if (!pSS->Prefix) + { // searching for cluster name only + // return only the first stars in a cluster + if (i == 0 || SDPtr->Postfix != + star_array[pSS->SortedStars[i - 1]].Postfix) + { // found one + break; + } + else + { // another star in the same cluster, skip cluster + i = SkipStarCluster (pSS->SortedStars, i) - 1; + continue; + } + } + + // check prefix + GetClusterName (SDPtr, FullName); + dlen = utf8StringCount (FullName); + if (pSS->PrefixLen > dlen) + continue; + + for (c = 0, sptr = pSS->Prefix, dptr = FullName; + c < pSS->PrefixLen; ++c) + { + UniChar sc = getCharFromString (&sptr); + UniChar dc = getCharFromString (&dptr); + + if (UniChar_toUpper (sc) != UniChar_toUpper (dc)) + break; + } + + if (c >= pSS->PrefixLen) + break; // found one + } + + return (i < NUM_SOLAR_SYSTEMS) ? i : -1; +} + +static void +DrawMatchedStarName (TEXTENTRY_STATE *pTES) +{ + STAR_SEARCH_STATE *pSS = (STAR_SEARCH_STATE *) pTES->CbParam; + UNICODE buf[STAR_SEARCH_BUFSIZE] = ""; + SIZE ExPos = 0; + SIZE CurPos = -1; + STAR_DESC *SDPtr = &star_array[pSS->SortedStars[pSS->CurIndex]]; + COUNT flags; + + if (pSS->SingleClust || pSS->SingleMatch) + { // draw full star name + GetClusterName (SDPtr, buf); + ExPos = -1; + flags = DSME_SETFR; + } + else + { // draw substring match + UNICODE *pstr = buf; + + strcpy (pstr, pSS->Text); + ExPos = pSS->ClusterPos; + pstr = skipUTF8Chars (pstr, pSS->ClusterPos); + + strcpy (pstr, GAME_STRING (SDPtr->Postfix)); + ExPos += pSS->ClusterLen; + CurPos = pTES->CursorPos; + + flags = DSME_CLEARFR; + if (pTES->JoystickMode) + flags |= DSME_BLOCKCUR; + } + + DrawSISMessageEx (buf, CurPos, ExPos, flags); + DrawHyperCoords (cursorLoc); +} + +static void +MatchNextStar (STAR_SEARCH_STATE *pSS, BOOLEAN Reset) +{ + if (Reset) + pSS->FirstIndex = -1; // reset cache + + if (pSS->FirstIndex < 0) + { // first time after changes + pSS->CurIndex = -1; + pSS->LastIndex = -1; + pSS->SingleClust = FALSE; + pSS->SingleMatch = FALSE; + strcpy (pSS->Buffer, pSS->Text); + SplitStarName (pSS); + } + + pSS->CurIndex = FindNextStarIndex (pSS, pSS->CurIndex + 1, + pSS->SingleClust); + if (pSS->FirstIndex < 0) // first search + pSS->FirstIndex = pSS->CurIndex; + + if (pSS->CurIndex >= 0) + { // remember as last (searching forward-only) + pSS->LastIndex = pSS->CurIndex; + } + else + { // wrap around + pSS->CurIndex = pSS->FirstIndex; + + if (pSS->FirstIndex == pSS->LastIndex && pSS->FirstIndex != -1) + { + if (!pSS->Prefix) + { // only one cluster matching + pSS->SingleClust = TRUE; + } + else + { // exact match + pSS->SingleMatch = TRUE; + } + } + } +} + +static BOOLEAN +OnStarNameChange (TEXTENTRY_STATE *pTES) +{ + STAR_SEARCH_STATE *pSS = (STAR_SEARCH_STATE *) pTES->CbParam; + COUNT flags; + BOOLEAN ret = TRUE; + + if (strcmp (pSS->Text, pSS->LastText) != 0) + { // string changed + pSS->LastChangeTime = GetTimeCounter (); + strcpy (pSS->LastText, pSS->Text); + + // reset the search + MatchNextStar (pSS, TRUE); + } + + if (pSS->CurIndex < 0) + { // nothing found + if (pSS->Text[0] == '\0') + flags = DSME_SETFR; + else + flags = DSME_CLEARFR; + if (pTES->JoystickMode) + flags |= DSME_BLOCKCUR; + + ret = DrawSISMessageEx (pSS->Text, pTES->CursorPos, -1, flags); + } + else + { + STAR_DESC *SDPtr; + + // move the cursor to the found star + SDPtr = &star_array[pSS->SortedStars[pSS->CurIndex]]; + UpdateCursorLocation (0, 0, &SDPtr->star_pt); + + DrawMatchedStarName (pTES); + UpdateFuelRequirement (); + } + + return ret; +} + +static BOOLEAN +OnStarNameFrame (TEXTENTRY_STATE *pTES) +{ + STAR_SEARCH_STATE *pSS = (STAR_SEARCH_STATE *) pTES->CbParam; + + if (PulsedInputState.menu[KEY_MENU_NEXT]) + { // search for next match + STAR_DESC *SDPtr; + + MatchNextStar (pSS, FALSE); + + if (pSS->CurIndex < 0) + { // nothing found + if (PulsedInputState.menu[KEY_MENU_NEXT]) + PlayMenuSound (MENU_SOUND_FAILURE); + return TRUE; + } + + // move the cursor to the found star + SDPtr = &star_array[pSS->SortedStars[pSS->CurIndex]]; + UpdateCursorLocation (0, 0, &SDPtr->star_pt); + + DrawMatchedStarName (pTES); + UpdateFuelRequirement (); + } + + flashCurrentLocation (NULL); + + SleepThread (ONE_SECOND / 30); + + return TRUE; +} + +static BOOLEAN +DoStarSearch (MENU_STATE *pMS) +{ + TEXTENTRY_STATE tes; + STAR_SEARCH_STATE *pss; + BOOLEAN success; + + pss = HMalloc (sizeof (*pss)); + if (!pss) + return FALSE; + + DrawSISMessageEx ("", 0, 0, DSME_SETFR); + + pss->pMS = pMS; + pss->LastChangeTime = 0; + pss->Text[0] = '\0'; + pss->LastText[0] = '\0'; + pss->FirstIndex = -1; + SortStarsOnName (pss); + + // text entry setup + tes.Initialized = FALSE; + tes.BaseStr = pss->Text; + tes.MaxSize = sizeof (pss->Text); + tes.CursorPos = 0; + tes.CbParam = pss; + tes.ChangeCallback = OnStarNameChange; + tes.FrameCallback = OnStarNameFrame; + + SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); + SetDefaultMenuRepeatDelay (); + success = DoTextEntry (&tes); + + DrawSISMessageEx (pss->Text, -1, -1, DSME_CLEARFR); + + HFree (pss); + + return success; +} + +static BOOLEAN +DoMoveCursor (MENU_STATE *pMS) +{ +#define MIN_ACCEL_DELAY (ONE_SECOND / 60) +#define MAX_ACCEL_DELAY (ONE_SECOND / 8) +#define STEP_ACCEL_DELAY (ONE_SECOND / 120) + static UNICODE last_buf[CURSOR_INFO_BUFSIZE]; + DWORD TimeIn = GetTimeCounter (); + + if (!pMS->Initialized) + { + POINT universe; + + pMS->Initialized = TRUE; + pMS->InputFunc = DoMoveCursor; + + if (!inHQSpace ()) + universe = CurStarDescPtr->star_pt; + else + { + universe.x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x)); + universe.y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y)); + } + flashCurrentLocation (&universe); + + last_buf[0] = '\0'; + UpdateCursorInfo (last_buf); + UpdateFuelRequirement (); + + return TRUE; + } + else if (PulsedInputState.menu[KEY_MENU_CANCEL]) + { + return FALSE; + } + else if (PulsedInputState.menu[KEY_MENU_SELECT]) + { + GLOBAL (autopilot) = cursorLoc; +#ifdef DEBUG + if (instantMove) + { + PlayMenuSound (MENU_SOUND_INVOKED); + + if (inHQSpace ()) + { + // Move to the new location immediately. + doInstantMove (); + } + else if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY) + { + // We're in a solar system; exit it. + GLOBAL (CurrentActivity) |= END_INTERPLANETARY; + + // Set a hook to move to the new location: + debugHook = doInstantMove; + } + + return FALSE; + } +#endif + DrawStarMap (0, NULL); + } + else if (PulsedInputState.menu[KEY_MENU_SEARCH]) + { + if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1) + { // HyperSpace search + POINT oldpt = cursorLoc; + + if (!DoStarSearch (pMS)) + { // search failed or canceled - return cursor + UpdateCursorLocation (0, 0, &oldpt); + } + // make sure cmp fails + strcpy (last_buf, " <random garbage> "); + UpdateCursorInfo (last_buf); + UpdateFuelRequirement (); + + SetMenuRepeatDelay (MIN_ACCEL_DELAY, MAX_ACCEL_DELAY, + STEP_ACCEL_DELAY, TRUE); + SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE); + } + else + { // no search in QuasiSpace + PlayMenuSound (MENU_SOUND_FAILURE); + } + } + else + { + SBYTE sx, sy; + SIZE ZoomIn, ZoomOut; + + ZoomIn = ZoomOut = 0; + if (PulsedInputState.menu[KEY_MENU_ZOOM_IN]) + ZoomIn = 1; + else if (PulsedInputState.menu[KEY_MENU_ZOOM_OUT]) + ZoomOut = 1; + + ZoomStarMap (ZoomIn - ZoomOut); + + sx = sy = 0; + if (PulsedInputState.menu[KEY_MENU_LEFT]) sx = -1; + if (PulsedInputState.menu[KEY_MENU_RIGHT]) sx = 1; + if (PulsedInputState.menu[KEY_MENU_UP]) sy = -1; + if (PulsedInputState.menu[KEY_MENU_DOWN]) sy = 1; + + if (sx != 0 || sy != 0) + { + UpdateCursorLocation (sx, sy, NULL); + UpdateCursorInfo (last_buf); + UpdateFuelRequirement (); + } + + SleepThreadUntil (TimeIn + MIN_ACCEL_DELAY); + } + + flashCurrentLocation (NULL); + + return !(GLOBAL (CurrentActivity) & CHECK_ABORT); +} + +static void +RepairMap (COUNT update_race, RECT *pLastRect, RECT *pNextRect) +{ + RECT r; + + /* make a rect big enough for text */ + r.extent.width = 50; + r.corner.x = (pNextRect->corner.x + (pNextRect->extent.width >> 1)) + - (r.extent.width >> 1); + if (r.corner.x < 0) + r.corner.x = 0; + else if (r.corner.x + r.extent.width >= SIS_SCREEN_WIDTH) + r.corner.x = SIS_SCREEN_WIDTH - r.extent.width; + r.extent.height = 9; + r.corner.y = (pNextRect->corner.y + (pNextRect->extent.height >> 1)) + - r.extent.height; + if (r.corner.y < 0) + r.corner.y = 0; + else if (r.corner.y + r.extent.height >= SIS_SCREEN_HEIGHT) + r.corner.y = SIS_SCREEN_HEIGHT - r.extent.height; + BoxUnion (pLastRect, &r, &r); + BoxUnion (pNextRect, &r, &r); + *pLastRect = *pNextRect; + + if (r.corner.x < 0) + { + r.extent.width += r.corner.x; + r.corner.x = 0; + } + if (r.corner.x + r.extent.width > SIS_SCREEN_WIDTH) + r.extent.width = SIS_SCREEN_WIDTH - r.corner.x; + if (r.corner.y < 0) + { + r.extent.height += r.corner.y; + r.corner.y = 0; + } + if (r.corner.y + r.extent.height > SIS_SCREEN_HEIGHT) + r.extent.height = SIS_SCREEN_HEIGHT - r.corner.y; + + r.extent.height += r.corner.y & 1; + r.corner.y &= ~1; + + DrawStarMap (update_race, &r); +} + +static void +UpdateMap (void) +{ + BYTE ButtonState, VisibleChange; + BOOLEAN MapDrawn, Interrupted; + COUNT index; + HFLEETINFO hStarShip, hNextShip; + + FlushInput (); + ButtonState = 1; /* assume a button down */ + + MapDrawn = Interrupted = FALSE; + for (index = 1, + hStarShip = GetHeadLink (&GLOBAL (avail_race_q)); + hStarShip; ++index, hStarShip = hNextShip) + { + FLEET_INFO *FleetPtr; + + FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip); + hNextShip = _GetSuccLink (FleetPtr); + + if (ButtonState) + { + if (!AnyButtonPress (TRUE)) + ButtonState = 0; + } + else if ((Interrupted = (BOOLEAN)( + Interrupted || AnyButtonPress (TRUE) + ))) + MapDrawn = TRUE; + + if (FleetPtr->known_strength) + { + SIZE dx, dy, delta; + RECT r, last_r, temp_r0, temp_r1; + + dx = FleetPtr->loc.x - FleetPtr->known_loc.x; + dy = FleetPtr->loc.y - FleetPtr->known_loc.y; + if (dx || dy) + { + SIZE xincr, yincr, + xerror, yerror, + cycle; + + if (dx >= 0) + xincr = 1; + else + { + xincr = -1; + dx = -dx; + } + dx <<= 1; + + if (dy >= 0) + yincr = 1; + else + { + yincr = -1; + dy = -dy; + } + dy <<= 1; + + if (dx >= dy) + cycle = dx; + else + cycle = dy; + delta = xerror = yerror = cycle >> 1; + + if (!MapDrawn) + { + DrawStarMap ((COUNT)~0, NULL); + MapDrawn = TRUE; + } + + GetSphereRect (FleetPtr, &temp_r0, &last_r); + ++last_r.extent.width; + ++last_r.extent.height; + VisibleChange = FALSE; + do + { + do + { + if ((xerror -= dx) <= 0) + { + FleetPtr->known_loc.x += xincr; + xerror += cycle; + } + if ((yerror -= dy) <= 0) + { + FleetPtr->known_loc.y += yincr; + yerror += cycle; + } + GetSphereRect (FleetPtr, &temp_r1, &r); + } while (delta-- + && ((delta & 0x1F) + || (temp_r0.corner.x == temp_r1.corner.x + && temp_r0.corner.y == temp_r1.corner.y))); + + if (ButtonState) + { + if (!AnyButtonPress (TRUE)) + ButtonState = 0; + } + else if ((Interrupted = (BOOLEAN)( + Interrupted || AnyButtonPress (TRUE) + ))) + { + MapDrawn = TRUE; + goto DoneSphereMove; + } + + ++r.extent.width; + ++r.extent.height; + if (temp_r0.corner.x != temp_r1.corner.x + || temp_r0.corner.y != temp_r1.corner.y) + { + VisibleChange = TRUE; + RepairMap (index, &last_r, &r); + } + } while (delta >= 0); + if (VisibleChange) + RepairMap ((COUNT)~0, &last_r, &r); + +DoneSphereMove: + FleetPtr->known_loc = FleetPtr->loc; + } + + delta = FleetPtr->actual_strength - FleetPtr->known_strength; + if (delta) + { + if (!MapDrawn) + { + DrawStarMap ((COUNT)~0, NULL); + MapDrawn = TRUE; + } + + if (delta > 0) + dx = 1; + else + { + delta = -delta; + dx = -1; + } + --delta; + + GetSphereRect (FleetPtr, &temp_r0, &last_r); + ++last_r.extent.width; + ++last_r.extent.height; + VisibleChange = FALSE; + do + { + do + { + FleetPtr->known_strength += dx; + GetSphereRect (FleetPtr, &temp_r1, &r); + } while (delta-- + && ((delta & 0xF) + || temp_r0.extent.height == temp_r1.extent.height)); + + if (ButtonState) + { + if (!AnyButtonPress (TRUE)) + ButtonState = 0; + } + else if ((Interrupted = (BOOLEAN)( + Interrupted || AnyButtonPress (TRUE) + ))) + { + MapDrawn = TRUE; + goto DoneSphereGrowth; + } + ++r.extent.width; + ++r.extent.height; + if (temp_r0.extent.height != temp_r1.extent.height) + { + VisibleChange = TRUE; + RepairMap (index, &last_r, &r); + } + } while (delta >= 0); + if (VisibleChange || temp_r0.extent.width != temp_r1.extent.width) + RepairMap ((COUNT)~0, &last_r, &r); + +DoneSphereGrowth: + FleetPtr->known_strength = FleetPtr->actual_strength; + } + } + + UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip); + } +} + +BOOLEAN +StarMap (void) +{ + MENU_STATE MenuState; + POINT universe; + //FRAME OldFrame; + RECT clip_r; + CONTEXT OldContext; + + memset (&MenuState, 0, sizeof (MenuState)); + + zoomLevel = 0; + mapOrigin.x = MAX_X_UNIVERSE >> 1; + mapOrigin.y = MAX_Y_UNIVERSE >> 1; + StarMapFrame = SetAbsFrameIndex (MiscDataFrame, 48); + + if (!inHQSpace ()) + universe = CurStarDescPtr->star_pt; + else + { + universe.x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x)); + universe.y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y)); + } + + cursorLoc = GLOBAL (autopilot); + if (cursorLoc.x == ~0 && cursorLoc.y == ~0) + cursorLoc = universe; + + MenuState.InputFunc = DoMoveCursor; + MenuState.Initialized = FALSE; + + transition_pending = TRUE; + if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1) + UpdateMap (); + + DrawStarMap (0, (RECT*)-1); + transition_pending = FALSE; + + BatchGraphics (); + OldContext = SetContext (SpaceContext); + GetContextClipRect (&clip_r); + SetContext (OldContext); + LoadIntoExtraScreen (&clip_r); + DrawCursor (UNIVERSE_TO_DISPX (cursorLoc.x), + UNIVERSE_TO_DISPY (cursorLoc.y)); + UnbatchGraphics (); + + SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE); + SetMenuRepeatDelay (MIN_ACCEL_DELAY, MAX_ACCEL_DELAY, STEP_ACCEL_DELAY, + TRUE); + DoInput (&MenuState, FALSE); + SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); + SetDefaultMenuRepeatDelay (); + + DrawHyperCoords (universe); + DrawSISMessage (NULL); + DrawStatusMessage (NULL); + + if (GLOBAL (autopilot.x) == universe.x + && GLOBAL (autopilot.y) == universe.y) + GLOBAL (autopilot.x) = GLOBAL (autopilot.y) = ~0; + + return (GLOBAL (autopilot.x) != ~0 + && GLOBAL (autopilot.y) != ~0); +} + diff --git a/src/uqm/planets/report.c b/src/uqm/planets/report.c new file mode 100644 index 0000000..5defbe7 --- /dev/null +++ b/src/uqm/planets/report.c @@ -0,0 +1,271 @@ +//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 "scan.h" +#include "planets.h" +#include "../colors.h" +#include "../controls.h" +#include "../gamestr.h" +#include "../setup.h" +#include "../util.h" +#include "../sounds.h" +#include "../uqmdebug.h" +#include "options.h" +#include "libs/inplib.h" + +#include <ctype.h> +#include <string.h> + + +#define NUM_CELL_COLS MAP_WIDTH / 6 +#define NUM_CELL_ROWS MAP_HEIGHT / 6 +#define MAX_CELL_COLS 40 + +extern FRAME SpaceJunkFrame; + +static void +ClearReportArea (void) +{ + COUNT x, y; + RECT r; + STAMP s; + COORD startx; + + if (optWhichFonts == OPT_PC) + s.frame = SetAbsFrameIndex (SpaceJunkFrame, 21); + else + s.frame = SetAbsFrameIndex (SpaceJunkFrame, 18); + GetFrameRect (s.frame, &r); + + BatchGraphics (); + + SetContextBackGroundColor (BLACK_COLOR); + ClearDrawable (); + SetContextForeGroundColor ( + BUILD_COLOR (MAKE_RGB15 (0x00, 0x07, 0x00), 0x57)); + + startx = 1 + (r.extent.width >> 1) - 1; + s.origin.y = 1; + for (y = 0; y < NUM_CELL_ROWS; ++y) + { + s.origin.x = startx; + for (x = 0; x < NUM_CELL_COLS; ++x) + { + if (optWhichFonts == OPT_PC) + DrawStamp (&s); + else + DrawFilledStamp (&s); + + s.origin.x += r.extent.width + 1; + } + s.origin.y += r.extent.height + 1; + } + + UnbatchGraphics (); +} + +static void +MakeReport (SOUND ReadOutSounds, UNICODE *pStr, COUNT StrLen) +{ + BYTE ButtonState; + int end_page_len; + UNICODE end_page_buf[200]; + UniChar last_c = 0; + COUNT row_cells; + BOOLEAN Sleepy; + RECT r; + TEXT t; + + sprintf (end_page_buf, "%s\n", GAME_STRING (SCAN_STRING_BASE + NUM_SCAN_TYPES)); + end_page_len = utf8StringCount (end_page_buf); + + GetFrameRect (SetAbsFrameIndex (SpaceJunkFrame, 18), &r); + + t.align = ALIGN_LEFT; + t.CharCount = 1; + t.pStr = pStr; + + Sleepy = TRUE; + + FlushInput (); + // XXX: this is a pretty ugly goto + goto InitPageCell; + + while (StrLen) + { + COUNT col_cells; + const UNICODE *pLastStr; + const UNICODE *pNextStr; + COUNT lf_pos; + + pLastStr = t.pStr; + + // scan for LFs in the remaining string + // trailing LF will be ignored + for (lf_pos = StrLen, pNextStr = t.pStr; + lf_pos && getCharFromString (&pNextStr) != '\n'; + --lf_pos) + ; + + col_cells = 0; + // check if the remaining text fits on current screen + if (row_cells == NUM_CELL_ROWS - 1 + && (StrLen > NUM_CELL_COLS || lf_pos > 1)) + { + col_cells = (NUM_CELL_COLS >> 1) - (end_page_len >> 1); + t.pStr = end_page_buf; + StrLen += end_page_len; + } + t.baseline.x = 1 + (r.extent.width >> 1) + + (col_cells * (r.extent.width + 1)) - 1; + do + { + COUNT word_chars; + const UNICODE *pStr; + UniChar c; + + pStr = t.pStr; + pNextStr = t.pStr; + while (UniChar_isGraph (getCharFromString (&pNextStr))) + pStr = pNextStr; + + word_chars = utf8StringCountN (t.pStr, pStr); + if ((col_cells += word_chars) <= NUM_CELL_COLS) + { + TimeCount TimeOut; + + if (StrLen -= word_chars) + --StrLen; + TimeOut = GetTimeCounter (); + while (word_chars--) + { + pNextStr = t.pStr; + c = getCharFromString (&pNextStr); + + if (!Sleepy || (GLOBAL (CurrentActivity) & CHECK_ABORT)) + font_DrawText (&t); + else + { + font_DrawText (&t); + + PlaySound (ReadOutSounds, NotPositional (), NULL, + GAME_SOUND_PRIORITY); + + if (c == ',') + TimeOut += ONE_SECOND / 4; + if (c == '.' || c == '!' || c == '?') + TimeOut += ONE_SECOND / 2; + else + TimeOut += ONE_SECOND / 20; + if (word_chars == 0) + TimeOut += ONE_SECOND / 20; + + if (WaitForAnyButtonUntil (TRUE, TimeOut, FALSE)) + { + Sleepy = FALSE; + // We draw the whole thing at once after this + BatchGraphics (); + } + } + t.pStr = pNextStr; + t.baseline.x += r.extent.width + 1; + } + + ++col_cells; + last_c = getCharFromString (&t.pStr); + t.baseline.x += r.extent.width + 1; + } + } while (col_cells <= NUM_CELL_COLS && last_c != '\n' && StrLen); + + t.baseline.y += r.extent.height + 1; + if (++row_cells == NUM_CELL_ROWS || StrLen == 0) + { + t.pStr = pLastStr; + if (!Sleepy) + { + UnbatchGraphics (); + } + + if (!WaitForAnyButton (TRUE, WAIT_INFINITE, FALSE)) + break; + +InitPageCell: + ButtonState = 1; + t.baseline.y = r.extent.height + 1; + row_cells = 0; + if (StrLen) + { + if (!Sleepy) + BatchGraphics (); + ClearReportArea(); + SetContextForeGroundColor ( + BUILD_COLOR (MAKE_RGB15 (0x00, 0x1F, 0x00), 0xFF)); + } + } + } +} + +void +DoDiscoveryReport (SOUND ReadOutSounds) +{ + CONTEXT OldContext; + CONTEXT context; + BOOLEAN ownContext; + STAMP saveStamp; + +#ifdef DEBUG + if (disableInteractivity) + return; +#endif + + context = GetScanContext (&ownContext); + OldContext = SetContext (context); + saveStamp = SaveContextFrame (NULL); + { + FONT OldFont; + FRAME OldFontEffect; + + OldFont = SetContextFont ( + pSolarSysState->SysInfo.PlanetInfo.LanderFont); + if (optWhichFonts == OPT_PC) + OldFontEffect = SetContextFontEffect ( + pSolarSysState->SysInfo.PlanetInfo.LanderFontEff); + else + OldFontEffect = SetContextFontEffect (NULL); + + MakeReport (ReadOutSounds, + (UNICODE *)GetStringAddress (pSolarSysState->SysInfo.PlanetInfo.DiscoveryString), + GetStringLength (pSolarSysState->SysInfo.PlanetInfo.DiscoveryString)); + + SetContextFontEffect (OldFontEffect); + SetContextFont (OldFont); + } + // Restore previous screen + DrawStamp (&saveStamp); + SetContext (OldContext); + // TODO: Make CONTEXT ref-counted + if (ownContext) + DestroyScanContext (); + + DestroyDrawable (ReleaseDrawable (saveStamp.frame)); + + WaitForNoInput (WAIT_INFINITE, TRUE); +} + + diff --git a/src/uqm/planets/roster.c b/src/uqm/planets/roster.c new file mode 100644 index 0000000..663ac28 --- /dev/null +++ b/src/uqm/planets/roster.c @@ -0,0 +1,428 @@ +//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 "../build.h" +#include "../colors.h" +#include "../controls.h" +#include "../races.h" +#include "../units.h" +#include "../sis.h" +#include "../shipcont.h" +#include "../setup.h" +#include "../sounds.h" +#include "port.h" +#include "libs/gfxlib.h" +#include "libs/tasklib.h" + +#include <stdlib.h> + +// Ship icon positions in status display around the flagship +static const POINT ship_pos[MAX_BUILT_SHIPS] = +{ + SUPPORT_SHIP_PTS +}; + +typedef struct +{ + // Ship icon positions split into (lower half) left and right (upper) + // and sorted in the Y coord. These are used for navigation around the + // escort positions. + POINT shipPos[MAX_BUILT_SHIPS]; + COUNT count; + // Number of ships + + POINT curShipPt; + // Location of the currently selected escort + FRAME curShipFrame; + // Icon of the currently selected escort + bool modifyingCrew; + // true when in crew modification "sub-menu". This is simple + // enough that it does not require a real sub-menu. +} ROSTER_STATE; + +static SHIP_FRAGMENT* LockSupportShip (ROSTER_STATE *, HSHIPFRAG *phFrag); + +static void +drawSupportShip (ROSTER_STATE *rosterState, bool filled) +{ + STAMP s; + + if (!rosterState->curShipFrame) + return; + + s.origin = rosterState->curShipPt; + s.frame = rosterState->curShipFrame; + + if (filled) + DrawFilledStamp (&s); + else + DrawStamp (&s); +} + +static void +getSupportShipIcon (ROSTER_STATE *rosterState) +{ + HSHIPFRAG hShipFrag; + SHIP_FRAGMENT *ShipFragPtr; + + rosterState->curShipFrame = NULL; + ShipFragPtr = LockSupportShip (rosterState, &hShipFrag); + if (!ShipFragPtr) + return; + + rosterState->curShipFrame = ShipFragPtr->icons; + UnlockShipFrag (&GLOBAL (built_ship_q), hShipFrag); +} + +static void +flashSupportShip (ROSTER_STATE *rosterState) +{ + static Color c = BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x00, 0x00), 0x24); + static TimeCount NextTime = 0; + + if (GetTimeCounter () >= NextTime) + { + NextTime = GetTimeCounter () + (ONE_SECOND / 15); + + /* The commented code out code is the old code before the switch + * to 24-bits colors. The current code produces very slightly + * different colors due to rounding errors, but the old code wasn't + * original anyhow, and you can't tell the difference visually. + * - SvdB + if (c >= BUILD_COLOR (MAKE_RGB15 (0x1F, 0x19, 0x19), 0x24)) + c = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x00, 0x00), 0x24); + else + c += BUILD_COLOR (MAKE_RGB15 (0x00, 0x02, 0x02), 0x00); + */ + + if (c.g >= CC5TO8 (0x19)) + { + c = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x00, 0x00), 0x24); + } + else + { + c.g += CC5TO8 (0x02); + c.b += CC5TO8 (0x02); + } + SetContextForeGroundColor (c); + + drawSupportShip (rosterState, TRUE); + } +} + +static SHIP_FRAGMENT * +LockSupportShip (ROSTER_STATE *rosterState, HSHIPFRAG *phFrag) +{ + const POINT *pship_pos; + HSHIPFRAG hStarShip, hNextShip; + + // Lookup the current escort's location in the unsorted points list + // to find the original escort index + for (hStarShip = GetHeadLink (&GLOBAL (built_ship_q)), + pship_pos = ship_pos; + hStarShip; hStarShip = hNextShip, ++pship_pos) + { + SHIP_FRAGMENT *StarShipPtr; + + StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip); + + if (pointsEqual (*pship_pos, rosterState->curShipPt)) + { + *phFrag = hStarShip; + return StarShipPtr; + } + + hNextShip = _GetSuccLink (StarShipPtr); + UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip); + } + + return NULL; +} + +static void +flashSupportShipCrew (void) +{ + RECT r; + + SetContext (StatusContext); + GetStatusMessageRect (&r); + SetFlashRect (&r); +} + +static BOOLEAN +DeltaSupportCrew (ROSTER_STATE *rosterState, SIZE crew_delta) +{ + BOOLEAN ret = FALSE; + UNICODE buf[40]; + HFLEETINFO hTemplate; + HSHIPFRAG hShipFrag; + SHIP_FRAGMENT *StarShipPtr; + FLEET_INFO *TemplatePtr; + + StarShipPtr = LockSupportShip (rosterState, &hShipFrag); + if (!StarShipPtr) + return FALSE; + + hTemplate = GetStarShipFromIndex (&GLOBAL (avail_race_q), + StarShipPtr->race_id); + TemplatePtr = LockFleetInfo (&GLOBAL (avail_race_q), hTemplate); + + StarShipPtr->crew_level += crew_delta; + + if (StarShipPtr->crew_level == 0) + StarShipPtr->crew_level = 1; + else if (StarShipPtr->crew_level > TemplatePtr->crew_level && + crew_delta > 0) + StarShipPtr->crew_level -= crew_delta; + else + { + if (StarShipPtr->crew_level >= TemplatePtr->crew_level) + sprintf (buf, "%u", StarShipPtr->crew_level); + else + sprintf (buf, "%u/%u", + StarShipPtr->crew_level, + TemplatePtr->crew_level); + + PreUpdateFlashRect (); + DrawStatusMessage (buf); + PostUpdateFlashRect (); + DeltaSISGauges (-crew_delta, 0, 0); + if (crew_delta) + { + flashSupportShipCrew (); + } + ret = TRUE; + } + + UnlockFleetInfo (&GLOBAL (avail_race_q), hTemplate); + UnlockShipFrag (&GLOBAL (built_ship_q), hShipFrag); + + return ret; +} + +static void +drawModifiedSupportShip (ROSTER_STATE *rosterState) +{ + SetContext (StatusContext); + SetContextForeGroundColor (ROSTER_MODIFY_SHIP_COLOR); + drawSupportShip (rosterState, TRUE); +} + +static void +selectSupportShip (ROSTER_STATE *rosterState, COUNT shipIndex) +{ + rosterState->curShipPt = rosterState->shipPos[shipIndex]; + getSupportShipIcon (rosterState); + DeltaSupportCrew (rosterState, 0); +} + +static BOOLEAN +DoModifyRoster (MENU_STATE *pMS) +{ + ROSTER_STATE *rosterState = pMS->privData; + BOOLEAN select, cancel, up, down, horiz; + + if (GLOBAL (CurrentActivity) & CHECK_ABORT) + return FALSE; + + select = PulsedInputState.menu[KEY_MENU_SELECT]; + cancel = PulsedInputState.menu[KEY_MENU_CANCEL]; + up = PulsedInputState.menu[KEY_MENU_UP]; + down = PulsedInputState.menu[KEY_MENU_DOWN]; + // Left or right produces the same effect because there are 2 columns + horiz = PulsedInputState.menu[KEY_MENU_LEFT] || + PulsedInputState.menu[KEY_MENU_RIGHT]; + + if (cancel && !rosterState->modifyingCrew) + { + return FALSE; + } + else if (select || cancel) + { + rosterState->modifyingCrew ^= true; + if (!rosterState->modifyingCrew) + { + SetFlashRect (NULL); + SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); + } + else + { + drawModifiedSupportShip (rosterState); + flashSupportShipCrew (); + SetMenuSounds (MENU_SOUND_UP | MENU_SOUND_DOWN, + MENU_SOUND_SELECT | MENU_SOUND_CANCEL); + } + } + else if (rosterState->modifyingCrew) + { + SIZE delta = 0; + BOOLEAN failed = FALSE; + + if (up) + { + if (GLOBAL_SIS (CrewEnlisted)) + delta = 1; + else + failed = TRUE; + } + else if (down) + { + if (GLOBAL_SIS (CrewEnlisted) < GetCrewPodCapacity ()) + delta = -1; + else + failed = TRUE; + } + + if (delta != 0) + { + failed = !DeltaSupportCrew (rosterState, delta); + } + + if (failed) + { // not enough room or crew + PlayMenuSound (MENU_SOUND_FAILURE); + } + } + else + { + COUNT NewState; + POINT *pship_pos = rosterState->shipPos; + COUNT top_right = (rosterState->count + 1) >> 1; + + NewState = pMS->CurState; + + if (rosterState->count < 2) + { + // no navigation allowed + } + else if (horiz) + { + if (NewState == top_right - 1) + NewState = rosterState->count - 1; + else if (NewState >= top_right) + { + NewState -= top_right; + if (pship_pos[NewState].y < pship_pos[pMS->CurState].y) + ++NewState; + } + else + { + NewState += top_right; + if (NewState != top_right + && pship_pos[NewState].y > pship_pos[pMS->CurState].y) + --NewState; + } + } + else if (down) + { + ++NewState; + if (NewState == rosterState->count) + NewState = top_right; + else if (NewState == top_right) + NewState = 0; + } + else if (up) + { + if (NewState == 0) + NewState = top_right - 1; + else if (NewState == top_right) + NewState = rosterState->count - 1; + else + --NewState; + } + + BatchGraphics (); + SetContext (StatusContext); + + if (NewState != pMS->CurState) + { + // Draw the previous escort in unselected state + drawSupportShip (rosterState, FALSE); + // Select the new one + selectSupportShip (rosterState, NewState); + pMS->CurState = NewState; + } + + flashSupportShip (rosterState); + + UnbatchGraphics (); + } + + SleepThread (ONE_SECOND / 30); + + return TRUE; +} + +static int +compShipPos (const void *ptr1, const void *ptr2) +{ + const POINT *pt1 = (const POINT *) ptr1; + const POINT *pt2 = (const POINT *) ptr2; + + // Ships on the left in the lower half + if (pt1->x < pt2->x) + return -1; + else if (pt1->x > pt2->x) + return 1; + + // and ordered on Y + if (pt1->y < pt2->y) + return -1; + else if (pt1->y > pt2->y) + return 1; + else + return 0; +} + +BOOLEAN +RosterMenu (void) +{ + MENU_STATE MenuState; + ROSTER_STATE RosterState; + + memset (&MenuState, 0, sizeof MenuState); + MenuState.privData = &RosterState; + + memset (&RosterState, 0, sizeof RosterState); + + RosterState.count = CountLinks (&GLOBAL (built_ship_q)); + if (!RosterState.count) + return FALSE; + + // Get the escort positions we will use and sort on X then Y + assert (sizeof (RosterState.shipPos) == sizeof (ship_pos)); + memcpy (RosterState.shipPos, ship_pos, sizeof (ship_pos)); + qsort (RosterState.shipPos, RosterState.count, + sizeof (RosterState.shipPos[0]), compShipPos); + + SetContext (StatusContext); + selectSupportShip (&RosterState, MenuState.CurState); + + SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); + + MenuState.InputFunc = DoModifyRoster; + DoInput (&MenuState, TRUE); + + SetContext (StatusContext); + // unselect the last ship + drawSupportShip (&RosterState, FALSE); + DrawStatusMessage (NULL); + + return TRUE; +} + 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); +} + diff --git a/src/uqm/planets/scan.h b/src/uqm/planets/scan.h new file mode 100644 index 0000000..f66fb0e --- /dev/null +++ b/src/uqm/planets/scan.h @@ -0,0 +1,72 @@ +//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. + */ + +#ifndef UQM_PLANETS_SCAN_H_ +#define UQM_PLANETS_SCAN_H_ + +typedef struct scan_desc SCAN_DESC; +typedef struct scan_block SCAN_BLOCK; + +#include "libs/compiler.h" +#include "libs/gfxlib.h" +#include "planets.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +struct scan_desc +{ + POINT start; + COUNT start_dot; + COUNT num_dots; + COUNT dots_per_semi; +}; + +struct scan_block +{ + POINT *line_base; + COUNT num_scans; + COUNT num_same_scans; + SCAN_DESC *scan_base; +}; + +extern void ScanSystem (void); + +extern void RepairBackRect (RECT *pRect); +extern void GeneratePlanetSide (void); +extern COUNT callGenerateForScanType (const SOLARSYS_STATE *, + const PLANET_DESC *world, COUNT node, BYTE scanType, NODE_INFO *); +// Returns true if the node should be removed from the surface +extern bool callPickupForScanType (SOLARSYS_STATE *solarSys, + PLANET_DESC *world, COUNT node, BYTE scanType); + +extern void RedrawSurfaceScan (const POINT *newLoc); +extern CONTEXT GetScanContext (BOOLEAN *owner); +extern void DestroyScanContext (void); + +bool isNodeRetrieved (PLANET_INFO *planetInfo, BYTE scanType, BYTE nodeNr); +COUNT countNodesRetrieved (PLANET_INFO *planetInfo, BYTE scanType); +void setNodeRetrieved (PLANET_INFO *planetInfo, BYTE scanType, BYTE nodeNr); +void setNodeNotRetrieved (PLANET_INFO *planetInfo, BYTE scanType, BYTE nodeNr); + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_PLANETS_SCAN_H_ */ diff --git a/src/uqm/planets/solarsys.c b/src/uqm/planets/solarsys.c new file mode 100644 index 0000000..11bd4c0 --- /dev/null +++ b/src/uqm/planets/solarsys.c @@ -0,0 +1,2021 @@ +//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 "solarsys.h" +#include "lander.h" +#include "../colors.h" +#include "../controls.h" +#include "../menustat.h" + // for DrawMenuStateStrings() +#include "../starmap.h" +#include "../races.h" +#include "../gamestr.h" +#include "../gendef.h" +#include "../globdata.h" +#include "../sis.h" +#include "../init.h" +#include "../shipcont.h" +#include "../gameopt.h" +#include "../nameref.h" +#include "../resinst.h" +#include "../settings.h" +#include "../ipdisp.h" +#include "../grpinfo.h" +#include "../process.h" +#include "../setup.h" +#include "../sounds.h" +#include "../state.h" +#include "../uqmdebug.h" +#include "../save.h" +#include "options.h" +#include "libs/graphics/gfx_common.h" +#include "libs/mathlib.h" +#include "libs/log.h" +#include "libs/misc.h" + + +//#define DEBUG_SOLARSYS +//#define SMOOTH_SYSTEM_ZOOM 1 + +#define IP_FRAME_RATE (ONE_SECOND / 30) + +static BOOLEAN DoIpFlight (SOLARSYS_STATE *pSS); +static void DrawSystem (SIZE radius, BOOLEAN IsInnerSystem); +static FRAME CreateStarBackGround (void); +static void DrawInnerSystem (void); +static void DrawOuterSystem (void); +static void ValidateOrbits (void); + +// SolarSysMenu() items +enum SolarSysMenuMenuItems +{ + // XXX: Must match the enum in menustat.h + STARMAP = 1, + EQUIP_DEVICE, + CARGO, + ROSTER, + GAME_MENU, + NAVIGATION, +}; + + +SOLARSYS_STATE *pSolarSysState; +FRAME SISIPFrame; +FRAME SunFrame; +FRAME OrbitalFrame; +FRAME SpaceJunkFrame; +COLORMAP OrbitalCMap; +COLORMAP SunCMap; +MUSIC_REF SpaceMusic; + +SIZE EncounterRace; +BYTE EncounterGroup; + // last encountered group info + +static FRAME StarsFrame; + // prepared star-field graphic +static FRAME SolarSysFrame; + // saved solar system view graphic + +static RECT scaleRect; + // system zooms in when the flagship enters this rect + +RandomContext *SysGenRNG; + +#define DISPLAY_TO_LOC (DISPLAY_FACTOR >> 1) + +POINT +locationToDisplay (POINT pt, SIZE scaleRadius) +{ + POINT out; + + out.x = (SIS_SCREEN_WIDTH >> 1) + + (long)pt.x * DISPLAY_TO_LOC / scaleRadius; + out.y = (SIS_SCREEN_HEIGHT >> 1) + + (long)pt.y * DISPLAY_TO_LOC / scaleRadius; + + return out; +} + +POINT +displayToLocation (POINT pt, SIZE scaleRadius) +{ + POINT out; + + out.x = ((long)pt.x - (SIS_SCREEN_WIDTH >> 1)) + * scaleRadius / DISPLAY_TO_LOC; + out.y = ((long)pt.y - (SIS_SCREEN_HEIGHT >> 1)) + * scaleRadius / DISPLAY_TO_LOC; + + return out; +} + +POINT +planetOuterLocation (COUNT planetI) +{ + SIZE scaleRadius = pSolarSysState->SunDesc[0].radius; + return displayToLocation (pSolarSysState->PlanetDesc[planetI].image.origin, + scaleRadius); +} + +bool +worldIsPlanet (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world) +{ + return world->pPrevDesc == solarSys->SunDesc; +} + +bool +worldIsMoon (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world) +{ + return world->pPrevDesc != solarSys->SunDesc; +} + +// Returns the planet index of the world. If the world is a moon, then +// this is the index of the planet it is orbiting. +COUNT +planetIndex (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world) +{ + const PLANET_DESC *planet = worldIsPlanet (solarSys, world) ? + world : world->pPrevDesc; + return planet - solarSys->PlanetDesc; +} + +COUNT +moonIndex (const SOLARSYS_STATE *solarSys, const PLANET_DESC *moon) +{ + assert (!worldIsPlanet (solarSys, moon)); + return moon - solarSys->MoonDesc; +} + +// Test whether 'world' is the planetI-th planet, and if moonI is not +// set to MATCH_PLANET, also whether 'world' is the moonI-th moon. +bool +matchWorld (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world, + BYTE planetI, BYTE moonI) +{ + // Check whether we have the right planet. + if (planetIndex (solarSys, world) != planetI) + return false; + + if (moonI == MATCH_PLANET) + { + // Only test whether we are at the planet. + if (!worldIsPlanet (solarSys, world)) + return false; + } + else + { + // Test whether the moon matches too + if (!worldIsMoon (solarSys, world)) + return false; + + if (moonIndex (solarSys, world) != moonI) + return false; + } + + return true; +} + +bool +playerInSolarSystem (void) +{ + return pSolarSysState != NULL; +} + +bool +playerInPlanetOrbit (void) +{ + return playerInSolarSystem () && pSolarSysState->InOrbit; +} + +bool +playerInInnerSystem (void) +{ + assert (playerInSolarSystem ()); + assert (pSolarSysState->pBaseDesc == pSolarSysState->PlanetDesc + || pSolarSysState->pBaseDesc == pSolarSysState->MoonDesc); + return pSolarSysState->pBaseDesc != pSolarSysState->PlanetDesc; +} + +// Sets the SysGenRNG to the required state first. +static void +GenerateMoons (SOLARSYS_STATE *system, PLANET_DESC *planet) +{ + COUNT i; + COUNT facing; + PLANET_DESC *pMoonDesc; + + RandomContext_SeedRandom (SysGenRNG, planet->rand_seed); + + (*system->genFuncs->generateName) (system, planet); + (*system->genFuncs->generateMoons) (system, planet); + + facing = NORMALIZE_FACING (ANGLE_TO_FACING ( + ARCTAN (planet->location.x, planet->location.y))); + for (i = 0, pMoonDesc = &system->MoonDesc[0]; + i < MAX_MOONS; ++i, ++pMoonDesc) + { + pMoonDesc->pPrevDesc = planet; + if (i >= planet->NumPlanets) + continue; + + pMoonDesc->temp_color = planet->temp_color; + } +} + +void +FreeIPData (void) +{ + DestroyDrawable (ReleaseDrawable (SISIPFrame)); + SISIPFrame = 0; + DestroyDrawable (ReleaseDrawable (SunFrame)); + SunFrame = 0; + DestroyColorMap (ReleaseColorMap (SunCMap)); + SunCMap = 0; + DestroyColorMap (ReleaseColorMap (OrbitalCMap)); + OrbitalCMap = 0; + DestroyDrawable (ReleaseDrawable (OrbitalFrame)); + OrbitalFrame = 0; + DestroyDrawable (ReleaseDrawable (SpaceJunkFrame)); + SpaceJunkFrame = 0; + DestroyMusic (SpaceMusic); + SpaceMusic = 0; + + RandomContext_Delete (SysGenRNG); + SysGenRNG = NULL; +} + +void +LoadIPData (void) +{ + if (SpaceJunkFrame == 0) + { + SpaceJunkFrame = CaptureDrawable ( + LoadGraphic (IPBKGND_MASK_PMAP_ANIM)); + SISIPFrame = CaptureDrawable (LoadGraphic (SISIP_MASK_PMAP_ANIM)); + + OrbitalCMap = CaptureColorMap (LoadColorMap (ORBPLAN_COLOR_MAP)); + OrbitalFrame = CaptureDrawable ( + LoadGraphic (ORBPLAN_MASK_PMAP_ANIM)); + SunCMap = CaptureColorMap (LoadColorMap (IPSUN_COLOR_MAP)); + SunFrame = CaptureDrawable (LoadGraphic (SUN_MASK_PMAP_ANIM)); + + SpaceMusic = LoadMusic (IP_MUSIC); + } + + if (!SysGenRNG) + { + SysGenRNG = RandomContext_New (); + } +} + + +static void +sortPlanetPositions (void) +{ + COUNT i; + SIZE sort_array[MAX_PLANETS + 1]; + + // When this part is done, sort_array will contain the indices to + // all planets, sorted on their y position. + // The sun itself, which has its data located at + // pSolarSysState->PlanetDesc[-1], is included in this array. + // Very ugly stuff, but it's correct. + + // Initialise sort_array. + for (i = 0; i <= pSolarSysState->SunDesc[0].NumPlanets; ++i) + sort_array[i] = i - 1; + + // Sort sort_array, based on the positions of the planets/sun. + for (i = 0; i <= pSolarSysState->SunDesc[0].NumPlanets; ++i) + { + COUNT j; + + for (j = pSolarSysState->SunDesc[0].NumPlanets; j > i; --j) + { + SIZE real_i, real_j; + + real_i = sort_array[i]; + real_j = sort_array[j]; + if (pSolarSysState->PlanetDesc[real_i].image.origin.y > + pSolarSysState->PlanetDesc[real_j].image.origin.y) + { + SIZE temp; + + temp = sort_array[i]; + sort_array[i] = sort_array[j]; + sort_array[j] = temp; + } + } + } + + // Put the results of the sorting in the solar system structure. + pSolarSysState->FirstPlanetIndex = sort_array[0]; + pSolarSysState->LastPlanetIndex = + sort_array[pSolarSysState->SunDesc[0].NumPlanets]; + for (i = 0; i <= pSolarSysState->SunDesc[0].NumPlanets; ++i) { + PLANET_DESC *planet = &pSolarSysState->PlanetDesc[sort_array[i]]; + planet->NextIndex = sort_array[i + 1]; + } +} + +static void +initSolarSysSISCharacteristics (void) +{ + BYTE i; + BYTE num_thrusters; + + num_thrusters = 0; + for (i = 0; i < NUM_DRIVE_SLOTS; ++i) + { + if (GLOBAL_SIS (DriveSlots[i]) == FUSION_THRUSTER) + ++num_thrusters; + } + pSolarSysState->max_ship_speed = (BYTE)( + (num_thrusters + 5) * IP_SHIP_THRUST_INCREMENT); + + pSolarSysState->turn_wait = IP_SHIP_TURN_WAIT; + for (i = 0; i < NUM_JET_SLOTS; ++i) + { + if (GLOBAL_SIS (JetSlots[i]) == TURNING_JETS) + pSolarSysState->turn_wait -= IP_SHIP_TURN_DECREMENT; + } +} + +DWORD +GetRandomSeedForStar (const STAR_DESC *star) +{ + return MAKE_DWORD (star->star_pt.x, star->star_pt.y); +} + +// Returns an orbital PLANET_DESC when player is in orbit +static PLANET_DESC * +LoadSolarSys (void) +{ + COUNT i; + PLANET_DESC *orbital = NULL; + PLANET_DESC *pCurDesc; +#define NUM_TEMP_RANGES 5 + static const Color temp_color_array[NUM_TEMP_RANGES] = + { + BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x00, 0x0E), 0x54), + BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x06, 0x08), 0x62), + BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x0B, 0x00), 0x6D), + BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x00), 0x2D), + BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x08, 0x00), 0x75), + }; + + RandomContext_SeedRandom (SysGenRNG, GetRandomSeedForStar (CurStarDescPtr)); + + SunFrame = SetAbsFrameIndex (SunFrame, STAR_TYPE (CurStarDescPtr->Type)); + + pCurDesc = &pSolarSysState->SunDesc[0]; + pCurDesc->pPrevDesc = 0; + pCurDesc->rand_seed = RandomContext_Random (SysGenRNG); + + pCurDesc->data_index = STAR_TYPE (CurStarDescPtr->Type); + pCurDesc->location.x = 0; + pCurDesc->location.y = 0; + pCurDesc->image.origin = pCurDesc->location; + pCurDesc->image.frame = SunFrame; + + (*pSolarSysState->genFuncs->generatePlanets) (pSolarSysState); + if (GET_GAME_STATE (PLANETARY_CHANGE)) + { + PutPlanetInfo (); + SET_GAME_STATE (PLANETARY_CHANGE, 0); + } + + for (i = 0, pCurDesc = pSolarSysState->PlanetDesc; + i < MAX_PLANETS; ++i, ++pCurDesc) + { + pCurDesc->pPrevDesc = &pSolarSysState->SunDesc[0]; + pCurDesc->image.origin = pCurDesc->location; + if (i >= pSolarSysState->SunDesc[0].NumPlanets) + { + pCurDesc->image.frame = 0; + } + else + { + COUNT index; + SYSTEM_INFO SysInfo; + + DoPlanetaryAnalysis (&SysInfo, pCurDesc); + index = (SysInfo.PlanetInfo.SurfaceTemperature + 250) / 100; + if (index >= NUM_TEMP_RANGES) + index = NUM_TEMP_RANGES - 1; + pCurDesc->temp_color = temp_color_array[index]; + } + } + + sortPlanetPositions (); + + if (!GLOBAL (ip_planet)) + { // Outer system + pSolarSysState->pBaseDesc = pSolarSysState->PlanetDesc; + pSolarSysState->pOrbitalDesc = NULL; + } + else + { // Inner system + pSolarSysState->SunDesc[0].location = GLOBAL (ip_location); + GLOBAL (ip_location) = displayToLocation ( + GLOBAL (ShipStamp.origin), MAX_ZOOM_RADIUS); + + i = GLOBAL (ip_planet) - 1; + pSolarSysState->pOrbitalDesc = &pSolarSysState->PlanetDesc[i]; + GenerateMoons (pSolarSysState, pSolarSysState->pOrbitalDesc); + pSolarSysState->pBaseDesc = pSolarSysState->MoonDesc; + + SET_GAME_STATE (PLANETARY_LANDING, 0); + } + + initSolarSysSISCharacteristics (); + + if (GLOBAL (in_orbit)) + { // Only when loading a game into orbital + i = GLOBAL (in_orbit) - 1; + if (i == 0) + { // Orbiting the planet itself + orbital = pSolarSysState->pBaseDesc->pPrevDesc; + } + else + { // Orbiting a moon + // -1 because planet itself is 1, and moons have to be 1-based + i -= 1; + orbital = &pSolarSysState->MoonDesc[i]; + } + GLOBAL (ip_location) = pSolarSysState->SunDesc[0].location; + GLOBAL (in_orbit) = 0; + } + else + { + i = GLOBAL (ShipFacing); + // XXX: Solar system reentry test depends on ShipFacing != 0 + if (i == 0) + ++i; + + GLOBAL (ShipStamp.frame) = SetAbsFrameIndex (SISIPFrame, i - 1); + } + + return orbital; +} + +static void +saveNonOrbitalLocation (void) +{ + // XXX: Solar system reentry test depends on ShipFacing != 0 + GLOBAL (ShipFacing) = GetFrameIndex (GLOBAL (ShipStamp.frame)) + 1; + GLOBAL (in_orbit) = 0; + if (!playerInInnerSystem ()) + { + GLOBAL (ip_planet) = 0; + } + else + { + // ip_planet is 1-based because code tests for ip_planet!=0 + GLOBAL (ip_planet) = 1 + planetIndex (pSolarSysState, + pSolarSysState->pOrbitalDesc); + GLOBAL (ip_location) = pSolarSysState->SunDesc[0].location; + } +} + +static void +FreeSolarSys (void) +{ + if (pSolarSysState->InIpFlight) + { + pSolarSysState->InIpFlight = FALSE; + + if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))) + saveNonOrbitalLocation (); + } + + DestroyDrawable (ReleaseDrawable (SolarSysFrame)); + SolarSysFrame = NULL; + + StopMusic (); + +// FreeIPData (); +} + +static FRAME +getCollisionFrame (PLANET_DESC *planet, COUNT WaitPlanet) +{ + if (pSolarSysState->WaitIntersect != (COUNT)~0 + && pSolarSysState->WaitIntersect != WaitPlanet) + { // New collisions are with a single point (center of planet) + return DecFrameIndex (stars_in_space); + } + else + { // Existing collisions are cleared only once the ship does not + // intersect anymore with a full planet image + return planet->image.frame; + } +} + +// Returns the planet with which the flagship is colliding +static PLANET_DESC * +CheckIntersect (void) +{ + COUNT i; + PLANET_DESC *pCurDesc; + INTERSECT_CONTROL ShipIntersect, PlanetIntersect; + COUNT NewWaitPlanet; + BYTE PlanetOffset, MoonOffset; + + // Check collisions with the system center object + // This may be the planet in inner view, or the sun + pCurDesc = pSolarSysState->pBaseDesc->pPrevDesc; + PlanetOffset = pCurDesc - pSolarSysState->PlanetDesc + 1; + MoonOffset = 1; // the planet itself + + ShipIntersect.IntersectStamp.origin = GLOBAL (ShipStamp.origin); + ShipIntersect.EndPoint = ShipIntersect.IntersectStamp.origin; + ShipIntersect.IntersectStamp.frame = GLOBAL (ShipStamp.frame); + + PlanetIntersect.IntersectStamp.origin.x = SIS_SCREEN_WIDTH >> 1; + PlanetIntersect.IntersectStamp.origin.y = SIS_SCREEN_HEIGHT >> 1; + PlanetIntersect.EndPoint = PlanetIntersect.IntersectStamp.origin; + + PlanetIntersect.IntersectStamp.frame = getCollisionFrame (pCurDesc, + MAKE_WORD (PlanetOffset, MoonOffset)); + + // Start with no collisions + NewWaitPlanet = 0; + + if (pCurDesc != pSolarSysState->SunDesc /* can't intersect with sun */ + && DrawablesIntersect (&ShipIntersect, + &PlanetIntersect, MAX_TIME_VALUE)) + { +#ifdef DEBUG_SOLARSYS + log_add (log_Debug, "0: Planet %d, Moon %d", PlanetOffset, + MoonOffset); +#endif /* DEBUG_SOLARSYS */ + NewWaitPlanet = MAKE_WORD (PlanetOffset, MoonOffset); + if (pSolarSysState->WaitIntersect != (COUNT)~0 + && pSolarSysState->WaitIntersect != NewWaitPlanet) + { + pSolarSysState->WaitIntersect = NewWaitPlanet; +#ifdef DEBUG_SOLARSYS + log_add (log_Debug, "Star index = %d, Planet index = %d, <%d, %d>", + CurStarDescPtr - star_array, + pCurDesc - pSolarSysState->PlanetDesc, + pSolarSysState->SunDesc[0].location.x, + pSolarSysState->SunDesc[0].location.y); +#endif /* DEBUG_SOLARSYS */ + return pCurDesc; + } + } + + for (i = pCurDesc->NumPlanets, + pCurDesc = pSolarSysState->pBaseDesc; i; --i, ++pCurDesc) + { + PlanetIntersect.IntersectStamp.origin = pCurDesc->image.origin; + PlanetIntersect.EndPoint = PlanetIntersect.IntersectStamp.origin; + if (playerInInnerSystem ()) + { + PlanetOffset = pCurDesc->pPrevDesc - + pSolarSysState->PlanetDesc; + MoonOffset = pCurDesc - pSolarSysState->MoonDesc + 2; + } + else + { + PlanetOffset = pCurDesc - pSolarSysState->PlanetDesc; + MoonOffset = 0; + } + ++PlanetOffset; + PlanetIntersect.IntersectStamp.frame = getCollisionFrame (pCurDesc, + MAKE_WORD (PlanetOffset, MoonOffset)); + + if (DrawablesIntersect (&ShipIntersect, + &PlanetIntersect, MAX_TIME_VALUE)) + { +#ifdef DEBUG_SOLARSYS + log_add (log_Debug, "1: Planet %d, Moon %d", PlanetOffset, + MoonOffset); +#endif /* DEBUG_SOLARSYS */ + NewWaitPlanet = MAKE_WORD (PlanetOffset, MoonOffset); + + if (pSolarSysState->WaitIntersect == (COUNT)~0) + { // All collisions disallowed, but the ship is still colliding + // with something. Collisions will remain disabled. + break; + } + else if (pSolarSysState->WaitIntersect == NewWaitPlanet) + { // Existing and continued collision -- ignore + continue; + } + + // Collision with a new planet/moon. This may cause a transition + // to an inner system or start an orbital view. + pSolarSysState->WaitIntersect = NewWaitPlanet; + return pCurDesc; + } + } + + // This records the planet/moon with which the ship just collided + // It may be a previously existing collision also (the value won't change) + // If all collisions were disabled, this will reenable then once the ship + // stops colliding with any planets + if (pSolarSysState->WaitIntersect != (COUNT)~0 || NewWaitPlanet == 0) + pSolarSysState->WaitIntersect = NewWaitPlanet; + + return NULL; +} + +static void +GetOrbitRect (RECT *pRect, COORD dx, COORD dy, SIZE radius, + int xnumer, int ynumer, int denom) +{ + pRect->corner.x = (SIS_SCREEN_WIDTH >> 1) + (long)-dx * xnumer / denom; + pRect->corner.y = (SIS_SCREEN_HEIGHT >> 1) + (long)-dy * ynumer / denom; + pRect->extent.width = (long)radius * (xnumer << 1) / denom; + pRect->extent.height = pRect->extent.width >> 1; +} + +static void +GetPlanetOrbitRect (RECT *r, PLANET_DESC *planet, int sizeNumer, + int dyNumer, int denom) +{ + COORD dx, dy; + + dx = planet->radius; + dy = planet->radius; + if (sizeNumer > DISPLAY_FACTOR) + { + dx = dx + planet->location.x; + dy = (dy + planet->location.y) << 1; + } + GetOrbitRect (r, dx, dy, planet->radius, sizeNumer, dyNumer, denom); +} + +static void +ValidateOrbit (PLANET_DESC *planet, int sizeNumer, int dyNumer, int denom) +{ + COUNT index; + + if (sizeNumer <= DISPLAY_FACTOR) + { // All planets in outer view, and moons in inner + RECT r; + + GetPlanetOrbitRect (&r, planet, sizeNumer, dyNumer, denom); + + // Calculate the location of the planet's image + r.corner.x += (r.extent.width >> 1); + r.corner.y += (r.extent.height >> 1); + r.corner.x += (long)planet->location.x * sizeNumer / denom; + // Ellipse function always has coefficients a^2 = 2 * b^2 + r.corner.y += (long)planet->location.y * (sizeNumer / 2) / denom; + + planet->image.origin = r.corner; + } + + // Calculate the size and lighting angle of planet's image and + // set the image that will be drawn + index = planet->data_index & ~WORLD_TYPE_SPECIAL; + if (index < NUMBER_OF_PLANET_TYPES) + { // The world is a normal planetary body (planet or moon) + BYTE Type; + COUNT Size; + COUNT angle; + + Type = PlanData[index].Type; + Size = PLANSIZE (Type); + if (sizeNumer > DISPLAY_FACTOR) + { + Size += 3; + } + else if (worldIsMoon (pSolarSysState, planet)) + { + Size += 2; + } + else if (denom <= (MAX_ZOOM_RADIUS >> 2)) + { + ++Size; + if (denom == MIN_ZOOM_RADIUS) + ++Size; + } + + if (worldIsPlanet (pSolarSysState, planet)) + { // Planet + angle = ARCTAN (planet->location.x, planet->location.y); + } + else + { // Moon + angle = ARCTAN (planet->pPrevDesc->location.x, + planet->pPrevDesc->location.y); + } + planet->image.frame = SetAbsFrameIndex (OrbitalFrame, + (Size << FACING_SHIFT) + NORMALIZE_FACING ( + ANGLE_TO_FACING (angle))); + } + else if (planet->data_index == HIERARCHY_STARBASE) + { + planet->image.frame = SetAbsFrameIndex (SpaceJunkFrame, 16); + } + else if (planet->data_index == SA_MATRA) + { + planet->image.frame = SetAbsFrameIndex (SpaceJunkFrame, 19); + } +} + +static void +DrawOrbit (PLANET_DESC *planet, int sizeNumer, int dyNumer, int denom) +{ + RECT r; + + GetPlanetOrbitRect (&r, planet, sizeNumer, dyNumer, denom); + + SetContextForeGroundColor (planet->temp_color); + DrawOval (&r, 1); +} + +static SIZE +FindRadius (POINT shipLoc, SIZE fromRadius) +{ + SIZE nextRadius; + POINT displayLoc; + + do + { + fromRadius >>= 1; + if (fromRadius > MIN_ZOOM_RADIUS) + nextRadius = fromRadius >> 1; + else + nextRadius = 0; // scaleRect will be nul + + GetOrbitRect (&scaleRect, nextRadius, nextRadius, nextRadius, + DISPLAY_FACTOR, DISPLAY_FACTOR >> 2, fromRadius); + displayLoc = locationToDisplay (shipLoc, fromRadius); + + } while (pointWithinRect (scaleRect, displayLoc)); + + return fromRadius; +} + +static UWORD +flagship_inertial_thrust (COUNT CurrentAngle) +{ + BYTE max_speed; + SIZE cur_delta_x, cur_delta_y; + COUNT TravelAngle; + VELOCITY_DESC *VelocityPtr; + + max_speed = pSolarSysState->max_ship_speed; + VelocityPtr = &GLOBAL (velocity); + GetCurrentVelocityComponents (VelocityPtr, &cur_delta_x, &cur_delta_y); + TravelAngle = GetVelocityTravelAngle (VelocityPtr); + if (TravelAngle == CurrentAngle + && cur_delta_x == COSINE (CurrentAngle, max_speed) + && cur_delta_y == SINE (CurrentAngle, max_speed)) + return (SHIP_AT_MAX_SPEED); + else + { + SIZE delta_x, delta_y; + DWORD desired_speed; + + delta_x = cur_delta_x + + COSINE (CurrentAngle, IP_SHIP_THRUST_INCREMENT); + delta_y = cur_delta_y + + SINE (CurrentAngle, IP_SHIP_THRUST_INCREMENT); + desired_speed = (DWORD) ((long) delta_x * delta_x) + + (DWORD) ((long) delta_y * delta_y); + if (desired_speed <= (DWORD) ((UWORD) max_speed * max_speed)) + SetVelocityComponents (VelocityPtr, delta_x, delta_y); + else if (TravelAngle == CurrentAngle) + { + SetVelocityComponents (VelocityPtr, + COSINE (CurrentAngle, max_speed), + SINE (CurrentAngle, max_speed)); + return (SHIP_AT_MAX_SPEED); + } + else + { + VELOCITY_DESC v; + + v = *VelocityPtr; + + DeltaVelocityComponents (&v, + COSINE (CurrentAngle, IP_SHIP_THRUST_INCREMENT >> 1) + - COSINE (TravelAngle, IP_SHIP_THRUST_INCREMENT), + SINE (CurrentAngle, IP_SHIP_THRUST_INCREMENT >> 1) + - SINE (TravelAngle, IP_SHIP_THRUST_INCREMENT)); + GetCurrentVelocityComponents (&v, &cur_delta_x, &cur_delta_y); + desired_speed = + (DWORD) ((long) cur_delta_x * cur_delta_x) + + (DWORD) ((long) cur_delta_y * cur_delta_y); + if (desired_speed > (DWORD) ((UWORD) max_speed * max_speed)) + { + SetVelocityComponents (VelocityPtr, + COSINE (CurrentAngle, max_speed), + SINE (CurrentAngle, max_speed)); + return (SHIP_AT_MAX_SPEED); + } + + *VelocityPtr = v; + } + + return 0; + } +} + +static void +ProcessShipControls (void) +{ + COUNT index; + SIZE delta_x, delta_y; + + if (CurrentInputState.key[PlayerControls[0]][KEY_UP]) + delta_y = -1; + else + delta_y = 0; + + delta_x = 0; + if (CurrentInputState.key[PlayerControls[0]][KEY_LEFT]) + delta_x -= 1; + if (CurrentInputState.key[PlayerControls[0]][KEY_RIGHT]) + delta_x += 1; + + if (delta_x || delta_y < 0) + { + GLOBAL (autopilot.x) = ~0; + GLOBAL (autopilot.y) = ~0; + } + else if (GLOBAL (autopilot.x) != ~0 && GLOBAL (autopilot.y) != ~0) + delta_y = -1; + else + delta_y = 0; + + index = GetFrameIndex (GLOBAL (ShipStamp.frame)); + if (pSolarSysState->turn_counter) + --pSolarSysState->turn_counter; + else if (delta_x) + { + if (delta_x < 0) + index = NORMALIZE_FACING (index - 1); + else + index = NORMALIZE_FACING (index + 1); + + GLOBAL (ShipStamp.frame) = + SetAbsFrameIndex (GLOBAL (ShipStamp.frame), index); + + pSolarSysState->turn_counter = pSolarSysState->turn_wait; + } + if (pSolarSysState->thrust_counter) + --pSolarSysState->thrust_counter; + else if (delta_y < 0) + { +#define THRUST_WAIT 1 + flagship_inertial_thrust (FACING_TO_ANGLE (index)); + + pSolarSysState->thrust_counter = THRUST_WAIT; + } +} + +static void +enterInnerSystem (PLANET_DESC *planet) +{ +#define INNER_ENTRY_DISTANCE (MIN_MOON_RADIUS + ((MAX_MOONS - 1) \ + * MOON_DELTA) + (MOON_DELTA / 4)) + COUNT angle; + + // Calculate the inner system entry location and facing + angle = FACING_TO_ANGLE (GetFrameIndex (GLOBAL (ShipStamp.frame))) + + HALF_CIRCLE; + GLOBAL (ShipStamp.origin.x) = (SIS_SCREEN_WIDTH >> 1) + + COSINE (angle, INNER_ENTRY_DISTANCE); + GLOBAL (ShipStamp.origin.y) = (SIS_SCREEN_HEIGHT >> 1) + + SINE (angle, INNER_ENTRY_DISTANCE); + if (GLOBAL (ShipStamp.origin.y) < 0) + GLOBAL (ShipStamp.origin.y) = 1; + else if (GLOBAL (ShipStamp.origin.y) >= SIS_SCREEN_HEIGHT) + GLOBAL (ShipStamp.origin.y) = + (SIS_SCREEN_HEIGHT - 1) - 1; + + GLOBAL (ip_location) = displayToLocation ( + GLOBAL (ShipStamp.origin), MAX_ZOOM_RADIUS); + + pSolarSysState->SunDesc[0].location = + planetOuterLocation (planetIndex (pSolarSysState, planet)); + ZeroVelocityComponents (&GLOBAL (velocity)); + + GenerateMoons (pSolarSysState, planet); + pSolarSysState->pBaseDesc = pSolarSysState->MoonDesc; + pSolarSysState->pOrbitalDesc = planet; +} + +static void +leaveInnerSystem (PLANET_DESC *planet) +{ + COUNT outerPlanetWait; + + pSolarSysState->pBaseDesc = pSolarSysState->PlanetDesc; + pSolarSysState->pOrbitalDesc = NULL; + + outerPlanetWait = MAKE_WORD (planet - pSolarSysState->PlanetDesc + 1, 0); + GLOBAL (ip_location) = pSolarSysState->SunDesc[0].location; + XFormIPLoc (&GLOBAL (ip_location), &GLOBAL (ShipStamp.origin), TRUE); + ZeroVelocityComponents (&GLOBAL (velocity)); + + // Now the ship is in outer system (as per game logic) + + pSolarSysState->WaitIntersect = outerPlanetWait; + // See if we also intersect with another planet, and if we do, + // disable collisions comletely until we stop intersecting + // with any planet at all. + CheckIntersect (); + if (pSolarSysState->WaitIntersect != outerPlanetWait) + pSolarSysState->WaitIntersect = (COUNT)~0; +} + +static void +enterOrbital (PLANET_DESC *planet) +{ + ZeroVelocityComponents (&GLOBAL (velocity)); + pSolarSysState->pOrbitalDesc = planet; + pSolarSysState->InOrbit = TRUE; +} + +static BOOLEAN +CheckShipLocation (SIZE *newRadius) +{ + SIZE radius; + + radius = pSolarSysState->SunDesc[0].radius; + *newRadius = pSolarSysState->SunDesc[0].radius; + + if (GLOBAL (ShipStamp.origin.x) < 0 + || GLOBAL (ShipStamp.origin.x) >= SIS_SCREEN_WIDTH + || GLOBAL (ShipStamp.origin.y) < 0 + || GLOBAL (ShipStamp.origin.y) >= SIS_SCREEN_HEIGHT) + { + // The ship leaves the screen. + if (!playerInInnerSystem ()) + { // Outer zoom-out transition + if (radius == MAX_ZOOM_RADIUS) + { + // The ship leaves IP. + GLOBAL (CurrentActivity) |= END_INTERPLANETARY; + return FALSE; // no location change + } + + *newRadius = FindRadius (GLOBAL (ip_location), + MAX_ZOOM_RADIUS << 1); + } + else + { + leaveInnerSystem (pSolarSysState->pOrbitalDesc); + } + + return TRUE; + } + + if (!playerInInnerSystem () + && pointWithinRect (scaleRect, GLOBAL (ShipStamp.origin))) + { // Outer zoom-in transition + *newRadius = FindRadius (GLOBAL (ip_location), radius); + return TRUE; + } + + if (GLOBAL (autopilot.x) == ~0 && GLOBAL (autopilot.y) == ~0) + { // Not on autopilot -- may collide with a planet + PLANET_DESC *planet = CheckIntersect (); + if (planet) + { // Collision with a planet + if (playerInInnerSystem ()) + { // Entering planet orbit (scans, etc.) + enterOrbital (planet); + return FALSE; // no location change + } + else + { // Transition to inner system + enterInnerSystem (planet); + return TRUE; + } + } + } + + return FALSE; // no location change +} + +static void +DrawSystemTransition (BOOLEAN inner) +{ + SetTransitionSource (NULL); + BatchGraphics (); + if (inner) + DrawInnerSystem (); + else + DrawOuterSystem (); + RedrawQueue (FALSE); + ScreenTransition (3, NULL); + UnbatchGraphics (); +} + +static void +TransitionSystemIn (void) +{ + SetContext (SpaceContext); + DrawSystemTransition (playerInInnerSystem ()); +} + +static void +ScaleSystem (SIZE new_radius) +{ +#ifdef SMOOTH_SYSTEM_ZOOM + // XXX: This appears to have been an attempt to zoom the system view + // in a different way. This code zooms gradually instead of + // doing a crossfade from one zoom level to the other. + // TODO: Do not loop here, and instead increment the zoom level + // in IP_frame() with a function drawing the new zoom. The ship + // controls are not handled in the loop, and the flagship + // can collide with a group while zooming, and that is not handled + // 100% correctly. +#define NUM_STEPS 10 + COUNT i; + SIZE old_radius; + SIZE d, step; + + old_radius = pSolarSysState->SunDesc[0].radius; + + assert (old_radius != 0); + assert (old_radius != new_radius); + + d = new_radius - old_radius; + step = d / NUM_STEPS; + + for (i = 0; i < NUM_STEPS - 1; ++i) + { + pSolarSysState->SunDesc[0].radius += step; + XFormIPLoc (&GLOBAL (ip_location), &GLOBAL (ShipStamp.origin), TRUE); + + BatchGraphics (); + DrawOuterSystem (); + RedrawQueue (FALSE); + UnbatchGraphics (); + + SleepThread (ONE_SECOND / 30); + } + + // Final zoom step + pSolarSysState->SunDesc[0].radius = new_radius; + XFormIPLoc (&GLOBAL (ip_location), &GLOBAL (ShipStamp.origin), TRUE); + + BatchGraphics (); + DrawOuterSystem (); + RedrawQueue (FALSE); + UnbatchGraphics (); + +#else // !SMOOTH_SYSTEM_ZOOM + RECT r; + + pSolarSysState->SunDesc[0].radius = new_radius; + XFormIPLoc (&GLOBAL (ip_location), &GLOBAL (ShipStamp.origin), TRUE); + + GetContextClipRect (&r); + SetTransitionSource (&r); + BatchGraphics (); + DrawOuterSystem (); + RedrawQueue (FALSE); + ScreenTransition (3, &r); + UnbatchGraphics (); +#endif // SMOOTH_SYSTEM_ZOOM +} + +static void +RestoreSystemView (void) +{ + STAMP s; + + s.origin.x = 0; + s.origin.y = 0; + s.frame = SolarSysFrame; + DrawStamp (&s); +} + +// Normally called by DoIpFlight() to process a frame +static void +IP_frame (void) +{ + BOOLEAN locChange; + SIZE newRadius; + + SetContext (SpaceContext); + + GameClockTick (); + ProcessShipControls (); + + locChange = CheckShipLocation (&newRadius); + if (locChange) + { + if (playerInInnerSystem ()) + { // Entering inner system + DrawSystemTransition (TRUE); + } + else if (pSolarSysState->SunDesc[0].radius == newRadius) + { // Leaving inner system to outer + DrawSystemTransition (FALSE); + } + else + { // Zooming outer system + ScaleSystem (newRadius); + } + } + else + { // Just flying around, minding own business.. + BatchGraphics (); + RestoreSystemView (); + RedrawQueue (FALSE); + DrawAutoPilotMessage (FALSE); + UnbatchGraphics (); + } + +} + +static BOOLEAN +CheckZoomLevel (void) +{ + BOOLEAN InnerSystem; + POINT shipLoc; + + InnerSystem = playerInInnerSystem (); + if (InnerSystem) + shipLoc = pSolarSysState->SunDesc[0].location; + else + shipLoc = GLOBAL (ip_location); + + pSolarSysState->SunDesc[0].radius = FindRadius (shipLoc, + MAX_ZOOM_RADIUS << 1); + if (!InnerSystem) + { // Update ship stamp since the radius probably changed + XFormIPLoc (&shipLoc, &GLOBAL (ShipStamp.origin), TRUE); + } + + return InnerSystem; +} + +static void +ValidateOrbits (void) +{ + COUNT i; + PLANET_DESC *planet; + + for (i = pSolarSysState->SunDesc[0].NumPlanets, + planet = &pSolarSysState->PlanetDesc[0]; i; --i, ++planet) + { + ValidateOrbit (planet, DISPLAY_FACTOR, DISPLAY_FACTOR / 4, + pSolarSysState->SunDesc[0].radius); + } +} + +static void +ValidateInnerOrbits (void) +{ + COUNT i; + PLANET_DESC *planet; + + assert (playerInInnerSystem ()); + + planet = pSolarSysState->pOrbitalDesc; + ValidateOrbit (planet, DISPLAY_FACTOR * 4, DISPLAY_FACTOR, + planet->radius); + + for (i = 0; i < planet->NumPlanets; ++i) + { + PLANET_DESC *moon = &pSolarSysState->MoonDesc[i]; + ValidateOrbit (moon, 2, 1, 2); + } +} + +static void +DrawInnerSystem (void) +{ + ValidateInnerOrbits (); + DrawSystem (pSolarSysState->pOrbitalDesc->radius, TRUE); + DrawSISTitle (GLOBAL_SIS (PlanetName)); +} + +static void +DrawOuterSystem (void) +{ + ValidateOrbits (); + DrawSystem (pSolarSysState->SunDesc[0].radius, FALSE); + DrawHyperCoords (CurStarDescPtr->star_pt); +} + +static void +ResetSolarSys (void) +{ + // Originally there was a flash_task test here, however, I found no cases + // where flash_task could be set at the time of call. The test was + // probably needed on 3DO when IP_frame() was a task. + assert (!pSolarSysState->InIpFlight); + + DrawMenuStateStrings (PM_STARMAP, -(PM_NAVIGATE - PM_SCAN)); + + InitDisplayList (); + // This also spawns the flagship element + DoMissions (); + + // Figure out and note which planet/moon we just left, if any + // This records any existing collision and prevents the ship + // from entering planets until a new collision occurs. + // TODO: this may need logic similar to one in leaveInnerSystem() + // for when the ship collides with more than one planet at + // the same time. While quite rare, it's still possible. + CheckIntersect (); + + pSolarSysState->InIpFlight = TRUE; + + // Do not start playing the music if we entered the solarsys only + // to load a game (load invoked from Main menu) + // XXX: This is quite hacky + if (!PLRPlaying ((MUSIC_REF)~0) && + (LastActivity != CHECK_LOAD || NextActivity)) + { + PlayMusic (SpaceMusic, TRUE, 1); + } +} + +static void +EnterPlanetOrbit (void) +{ + if (pSolarSysState->InIpFlight) + { // This means we hit a planet in IP flight; not a Load into orbit + FreeSolarSys (); + + if (worldIsMoon (pSolarSysState, pSolarSysState->pOrbitalDesc)) + { // Moon -- use its origin + // XXX: The conversion functions do not error-correct, so the + // point we set here will change once flag_ship_preprocess() + // in ipdisp.c starts over again. + GLOBAL (ShipStamp.origin) = + pSolarSysState->pOrbitalDesc->image.origin; + } + else + { // Planet -- its origin is for the outer view, so use mid-screen + GLOBAL (ShipStamp.origin.x) = SIS_SCREEN_WIDTH >> 1; + GLOBAL (ShipStamp.origin.y) = SIS_SCREEN_HEIGHT >> 1; + } + } + + GetPlanetInfo (); + (*pSolarSysState->genFuncs->generateOrbital) (pSolarSysState, + pSolarSysState->pOrbitalDesc); + LastActivity &= ~(CHECK_LOAD | CHECK_RESTART); + if ((GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD | + START_ENCOUNTER)) || GLOBAL_SIS (CrewEnlisted) == (COUNT)~0 + || GET_GAME_STATE (CHMMR_BOMB_STATE) == 2) + return; + + // Implement a to-do in generate.h for a better test + if (pSolarSysState->TopoFrame) + { // We've entered orbit; LoadPlanet() called planet surface-gen code + PlanetOrbitMenu (); + FreePlanet (); + } + // Otherwise, generateOrbital function started a homeworld conversation, + // and we did not get to the planet no matter what. + + // START_ENCOUNTER could be set by Devices menu a number of ways: + // Talking Pet, Sun Device or a Caster over Chmmr, or + // a Caster for Ilwrath + // Could also have blown self up with Utwig Bomb + if (!(GLOBAL (CurrentActivity) & (START_ENCOUNTER | + CHECK_ABORT | CHECK_LOAD)) + && GLOBAL_SIS (CrewEnlisted) != (COUNT)~0) + { // Reload the system and return to the inner view + PLANET_DESC *orbital = LoadSolarSys (); + assert (!orbital); + CheckZoomLevel (); + ValidateOrbits (); + ValidateInnerOrbits (); + ResetSolarSys (); + + RepairSISBorder (); + TransitionSystemIn (); + } +} + +static void +InitSolarSys (void) +{ + BOOLEAN InnerSystem; + BOOLEAN Reentry; + PLANET_DESC *orbital; + + + LoadIPData (); + LoadLanderData (); + + Reentry = (GLOBAL (ShipFacing) != 0); + if (!Reentry) + { + GLOBAL (autopilot.x) = ~0; + GLOBAL (autopilot.y) = ~0; + + GLOBAL (ShipStamp.origin.x) = SIS_SCREEN_WIDTH >> 1; + GLOBAL (ShipStamp.origin.y) = SIS_SCREEN_HEIGHT - 2; + + GLOBAL (ip_location) = displayToLocation (GLOBAL (ShipStamp.origin), + MAX_ZOOM_RADIUS); + } + + + StarsFrame = CreateStarBackGround (); + + SetContext (SpaceContext); + SetContextFGFrame (Screen); + SetContextBackGroundColor (BLACK_COLOR); + + + orbital = LoadSolarSys (); + InnerSystem = CheckZoomLevel (); + ValidateOrbits (); + if (InnerSystem) + ValidateInnerOrbits (); + + if (Reentry) + { + (*pSolarSysState->genFuncs->reinitNpcs) (pSolarSysState); + } + else + { + EncounterRace = -1; + EncounterGroup = 0; + GLOBAL (BattleGroupRef) = 0; + ReinitQueue (&GLOBAL (ip_group_q)); + ReinitQueue (&GLOBAL (npc_built_ship_q)); + (*pSolarSysState->genFuncs->initNpcs) (pSolarSysState); + } + + if (orbital) + { + enterOrbital (orbital); + } + else + { // Draw the borders, the system (inner or outer) and fade/transition + SetContext (SpaceContext); + + SetTransitionSource (NULL); + BatchGraphics (); + + DrawSISFrame (); + DrawSISMessage (NULL); + + ResetSolarSys (); + + if (LastActivity == (CHECK_LOAD | CHECK_RESTART)) + { // Starting a new game, NOT from load! + // We have to fade the screen in from intro or menu + DrawOuterSystem (); + RedrawQueue (FALSE); + UnbatchGraphics (); + FadeScreen (FadeAllToColor, ONE_SECOND / 2); + + LastActivity = 0; + } + else if (LastActivity == CHECK_LOAD && !NextActivity) + { // Called just to load a game; invoked from Main menu + // No point in drawing anything + UnbatchGraphics (); + } + else + { // Entered a new system, or loaded into inner or outer + if (InnerSystem) + DrawInnerSystem (); + else + DrawOuterSystem (); + RedrawQueue (FALSE); + ScreenTransition (3, NULL); + UnbatchGraphics (); + + LastActivity &= ~CHECK_LOAD; + } + } +} + +static void +endInterPlanetary (void) +{ + GLOBAL (CurrentActivity) &= ~END_INTERPLANETARY; + + if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))) + { + // These are game state changing ops and so cannot be + // called once another game has been loaded! + (*pSolarSysState->genFuncs->uninitNpcs) (pSolarSysState); + SET_GAME_STATE (USED_BROADCASTER, 0); + } +} + +// Find the closest planet to a point, in interplanetary. +static PLANET_DESC * +closestPlanetInterPlanetary (const POINT *point) +{ + BYTE i; + BYTE numPlanets; + DWORD bestDistSquared; + PLANET_DESC *bestPlanet = NULL; + + assert(pSolarSysState != NULL); + + numPlanets = pSolarSysState->SunDesc[0].NumPlanets; + + bestDistSquared = (DWORD) -1; // Maximum value of DWORD. + for (i = 0; i < numPlanets; i++) + { + PLANET_DESC *planet = &pSolarSysState->PlanetDesc[i]; + + SIZE dx = point->x - planet->image.origin.x; + SIZE dy = point->y - planet->image.origin.y; + + DWORD distSquared = (DWORD) ((long) dx * dx + (long) dy * dy); + if (distSquared < bestDistSquared) + { + bestDistSquared = distSquared; + bestPlanet = planet; + } + } + + return bestPlanet; +} + +static void +UninitSolarSys (void) +{ + FreeSolarSys (); + +//FreeLanderData (); +//FreeIPData (); + + DestroyDrawable (ReleaseDrawable (StarsFrame)); + StarsFrame = NULL; + + if (GLOBAL (CurrentActivity) & END_INTERPLANETARY) + { + endInterPlanetary (); + return; + } + + if ((GLOBAL (CurrentActivity) & START_ENCOUNTER) && EncounterGroup) + { + GetGroupInfo (GLOBAL (BattleGroupRef), EncounterGroup); + // Generate the encounter location name based on the closest planet + + if (GLOBAL (ip_planet) == 0) + { + PLANET_DESC *planet = + closestPlanetInterPlanetary (&GLOBAL (ShipStamp.origin)); + + (*pSolarSysState->genFuncs->generateName) ( + pSolarSysState, planet); + } + } +} + +static void +CalcSunSize (PLANET_DESC *pSunDesc, SIZE radius) +{ + SIZE index = 0; + + if (radius <= (MAX_ZOOM_RADIUS >> 1)) + { + ++index; + if (radius <= (MAX_ZOOM_RADIUS >> 2)) + ++index; + } + + pSunDesc->image.origin.x = SIS_SCREEN_WIDTH >> 1; + pSunDesc->image.origin.y = SIS_SCREEN_HEIGHT >> 1; + pSunDesc->image.frame = SetRelFrameIndex (SunFrame, index); +} + +static void +SetPlanetColorMap (PLANET_DESC *planet) +{ + COUNT index = planet->data_index & ~WORLD_TYPE_SPECIAL; + assert (index < NUMBER_OF_PLANET_TYPES); + SetColorMap (GetColorMapAddress (SetAbsColorMapIndex (OrbitalCMap, + PLANCOLOR (PlanData[index].Type)))); +} + +static void +DrawInnerPlanets (PLANET_DESC *planet) +{ + STAMP s; + COUNT i; + PLANET_DESC *moon; + + // Draw the planet image + SetPlanetColorMap (planet); + s.origin.x = SIS_SCREEN_WIDTH >> 1; + s.origin.y = SIS_SCREEN_HEIGHT >> 1; + s.frame = planet->image.frame; + + i = planet->data_index & ~WORLD_TYPE_SPECIAL; + if (i < NUMBER_OF_PLANET_TYPES + && (planet->data_index & PLANET_SHIELDED)) + { // Shielded world looks "shielded" in inner view + s.frame = SetAbsFrameIndex (SpaceJunkFrame, 17); + } + DrawStamp (&s); + + // Draw the moon images + for (i = planet->NumPlanets, moon = pSolarSysState->MoonDesc; + i; --i, ++moon) + { + if (!(moon->data_index & WORLD_TYPE_SPECIAL)) + SetPlanetColorMap (moon); + DrawStamp (&moon->image); + } +} + +static void +DrawSystem (SIZE radius, BOOLEAN IsInnerSystem) +{ + BYTE i; + PLANET_DESC *pCurDesc; + PLANET_DESC *pBaseDesc; + CONTEXT oldContext; + STAMP s; + + if (!SolarSysFrame) + { // Create the saved view graphic + RECT clipRect; + + GetContextClipRect (&clipRect); + SolarSysFrame = CaptureDrawable (CreateDrawable (WANT_PIXMAP, + clipRect.extent.width, clipRect.extent.height, 1)); + } + + oldContext = SetContext (OffScreenContext); + SetContextFGFrame (SolarSysFrame); + SetContextClipRect (NULL); + + DrawStarBackGround (); + + pBaseDesc = pSolarSysState->pBaseDesc; + if (IsInnerSystem) + { // Draw the inner system view *planet's* orbit segment + pCurDesc = pSolarSysState->pOrbitalDesc; + DrawOrbit (pCurDesc, DISPLAY_FACTOR * 4, DISPLAY_FACTOR, radius); + } + + // Draw the planet orbits or moon orbits + for (i = pBaseDesc->pPrevDesc->NumPlanets, pCurDesc = pBaseDesc; + i; --i, ++pCurDesc) + { + if (IsInnerSystem) + DrawOrbit (pCurDesc, 2, 1, 2); + else + DrawOrbit (pCurDesc, DISPLAY_FACTOR, DISPLAY_FACTOR / 4, + radius); + } + + if (IsInnerSystem) + { // Draw the inner system view + DrawInnerPlanets (pSolarSysState->pOrbitalDesc); + } + else + { // Draw the outer system view + SIZE index; + + CalcSunSize (&pSolarSysState->SunDesc[0], radius); + + index = pSolarSysState->FirstPlanetIndex; + for (;;) + { + pCurDesc = &pSolarSysState->PlanetDesc[index]; + if (pCurDesc == &pSolarSysState->SunDesc[0]) + { // It's a sun + SetColorMap (GetColorMapAddress (SetAbsColorMapIndex ( + SunCMap, STAR_COLOR (CurStarDescPtr->Type)))); + } + else + { // It's a planet + SetPlanetColorMap (pCurDesc); + } + DrawStamp (&pCurDesc->image); + + if (index == pSolarSysState->LastPlanetIndex) + break; + index = pCurDesc->NextIndex; + } + } + + SetContext (oldContext); + + // Draw the now-saved view graphic + s.origin.x = 0; + s.origin.y = 0; + s.frame = SolarSysFrame; + DrawStamp (&s); +} + +void +DrawStarBackGround (void) +{ + STAMP s; + + s.origin.x = 0; + s.origin.y = 0; + s.frame = StarsFrame; + DrawStamp (&s); +} + +static FRAME +CreateStarBackGround (void) +{ + COUNT i, j; + DWORD rand_val; + STAMP s; + CONTEXT oldContext; + RECT clipRect; + FRAME frame; + + // Use SpaceContext to find out the dimensions of the background + oldContext = SetContext (SpaceContext); + GetContextClipRect (&clipRect); + + // Prepare a pre-drawn stars frame for this system + frame = CaptureDrawable (CreateDrawable (WANT_PIXMAP, + clipRect.extent.width, clipRect.extent.height, 1)); + SetContext (OffScreenContext); + SetContextFGFrame (frame); + SetContextClipRect (NULL); + SetContextBackGroundColor (BLACK_COLOR); + + ClearDrawable (); + + RandomContext_SeedRandom (SysGenRNG, GetRandomSeedForStar (CurStarDescPtr)); + +#define NUM_DIM_PIECES 8 + s.frame = SpaceJunkFrame; + for (i = 0; i < NUM_DIM_PIECES; ++i) + { +#define NUM_DIM_DRAWN 5 + for (j = 0; j < NUM_DIM_DRAWN; ++j) + { + rand_val = RandomContext_Random (SysGenRNG); + s.origin.x = LOWORD (rand_val) % SIS_SCREEN_WIDTH; + s.origin.y = HIWORD (rand_val) % SIS_SCREEN_HEIGHT; + + DrawStamp (&s); + } + s.frame = IncFrameIndex (s.frame); + } +#define NUM_BRT_PIECES 8 + for (i = 0; i < NUM_BRT_PIECES; ++i) + { +#define NUM_BRT_DRAWN 30 + for (j = 0; j < NUM_BRT_DRAWN; ++j) + { + rand_val = RandomContext_Random (SysGenRNG); + s.origin.x = LOWORD (rand_val) % SIS_SCREEN_WIDTH; + s.origin.y = HIWORD (rand_val) % SIS_SCREEN_HEIGHT; + + DrawStamp (&s); + } + s.frame = IncFrameIndex (s.frame); + } + + SetContext (oldContext); + + return frame; +} + +void +XFormIPLoc (POINT *pIn, POINT *pOut, BOOLEAN ToDisplay) +{ + if (ToDisplay) + *pOut = locationToDisplay (*pIn, pSolarSysState->SunDesc[0].radius); + else + *pOut = displayToLocation (*pIn, pSolarSysState->SunDesc[0].radius); +} + +void +ExploreSolarSys (void) +{ + SOLARSYS_STATE SolarSysState; + + if (CurStarDescPtr == 0) + { + POINT universe; + + universe.x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x)); + universe.y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y)); + CurStarDescPtr = FindStar (0, &universe, 1, 1); + if (!CurStarDescPtr) + { + log_add (log_Fatal, "ExploreSolarSys(): do not know where you are!"); + explode (); + } + } + GLOBAL_SIS (log_x) = UNIVERSE_TO_LOGX (CurStarDescPtr->star_pt.x); + GLOBAL_SIS (log_y) = UNIVERSE_TO_LOGY (CurStarDescPtr->star_pt.y); + + pSolarSysState = &SolarSysState; + + memset (pSolarSysState, 0, sizeof (*pSolarSysState)); + + SolarSysState.genFuncs = getGenerateFunctions (CurStarDescPtr->Index); + + InitSolarSys (); + SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE); + SolarSysState.InputFunc = DoIpFlight; + DoInput (&SolarSysState, FALSE); + UninitSolarSys (); + pSolarSysState = 0; +} + +UNICODE * +GetNamedPlanetaryBody (void) +{ + if (!CurStarDescPtr || !playerInSolarSystem () || !playerInInnerSystem ()) + return NULL; // Not inside an inner system, so no name + + assert (pSolarSysState->pOrbitalDesc != NULL); + + if (CurStarDescPtr->Index == SOL_DEFINED) + { // Planets and moons in Sol + int planet; + int moon; + + planet = planetIndex (pSolarSysState, pSolarSysState->pOrbitalDesc); + + if (worldIsPlanet (pSolarSysState, pSolarSysState->pOrbitalDesc)) + { // A planet + return GAME_STRING (PLANET_NUMBER_BASE + planet); + } + + // Moons + moon = moonIndex (pSolarSysState, pSolarSysState->pOrbitalDesc); + switch (planet) + { + case 2: // Earth + switch (moon) + { + case 0: // Starbase + return GAME_STRING (STARBASE_STRING_BASE + 0); + case 1: // Luna + return GAME_STRING (PLANET_NUMBER_BASE + 9); + } + break; + case 4: // Jupiter + switch (moon) + { + case 0: // Io + return GAME_STRING (PLANET_NUMBER_BASE + 10); + case 1: // Europa + return GAME_STRING (PLANET_NUMBER_BASE + 11); + case 2: // Ganymede + return GAME_STRING (PLANET_NUMBER_BASE + 12); + case 3: // Callisto + return GAME_STRING (PLANET_NUMBER_BASE + 13); + } + break; + case 5: // Saturn + if (moon == 0) // Titan + return GAME_STRING (PLANET_NUMBER_BASE + 14); + break; + case 7: // Neptune + if (moon == 0) // Triton + return GAME_STRING (PLANET_NUMBER_BASE + 15); + break; + } + } + else if (CurStarDescPtr->Index == SPATHI_DEFINED) + { + if (matchWorld (pSolarSysState, pSolarSysState->pOrbitalDesc, + 0, MATCH_PLANET)) + { +#ifdef NOTYET + return "Spathiwa"; +#endif // NOTYET + } + } + else if (CurStarDescPtr->Index == SAMATRA_DEFINED) + { + if (matchWorld (pSolarSysState, pSolarSysState->pOrbitalDesc, 4, 0)) + { // Sa-Matra + return GAME_STRING (PLANET_NUMBER_BASE + 32); + } + } + + return NULL; +} + +void +GetPlanetOrMoonName (UNICODE *buf, COUNT bufsize) +{ + UNICODE *named; + int moon; + int i; + + named = GetNamedPlanetaryBody (); + if (named) + { + utf8StringCopy (buf, bufsize, named); + return; + } + + // Either not named or we already have a name + utf8StringCopy (buf, bufsize, GLOBAL_SIS (PlanetName)); + + if (!playerInSolarSystem () || !playerInInnerSystem () || + worldIsPlanet (pSolarSysState, pSolarSysState->pOrbitalDesc)) + { // Outer or inner system or orbiting a planet + return; + } + + // Orbiting an unnamed moon + i = strlen (buf); + buf += i; + bufsize -= i; + moon = moonIndex (pSolarSysState, pSolarSysState->pOrbitalDesc); + if (bufsize >= 3) + { + snprintf (buf, bufsize, "-%c", 'A' + moon); + buf[bufsize - 1] = '\0'; + } +} + +void +SaveSolarSysLocation (void) +{ + assert (playerInSolarSystem ()); + + // This is a two-stage saving procedure + // Stage 1: called when saving from inner/outer view + // Stage 2: called when saving from orbital + + if (!playerInPlanetOrbit ()) + { + saveNonOrbitalLocation (); + } + else + { // In orbit around a planet. + BYTE moon; + + // Update the starinfo.dat file if necessary. + if (GET_GAME_STATE (PLANETARY_CHANGE)) + { + PutPlanetInfo (); + SET_GAME_STATE (PLANETARY_CHANGE, 0); + } + + // GLOBAL (ip_planet) is already set + assert (GLOBAL (ip_planet) != 0); + + // has to be at least 1 because code tests for in_orbit!=0 + moon = 1; /* the planet itself */ + if (worldIsMoon (pSolarSysState, pSolarSysState->pOrbitalDesc)) + { + moon += moonIndex (pSolarSysState, pSolarSysState->pOrbitalDesc); + // +1 because moons have to be 1-based + moon += 1; + } + GLOBAL (in_orbit) = moon; + } +} + +static BOOLEAN +DoSolarSysMenu (MENU_STATE *pMS) +{ + BOOLEAN select = PulsedInputState.menu[KEY_MENU_SELECT]; + BOOLEAN handled; + + if ((GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)) + || GLOBAL_SIS (CrewEnlisted) == (COUNT)~0) + return FALSE; + + handled = DoMenuChooser (pMS, PM_STARMAP); + if (handled) + return TRUE; + + if (LastActivity == CHECK_LOAD) + select = TRUE; // Selected LOAD from main menu + + if (!select) + return TRUE; + + SetFlashRect (NULL); + + switch (pMS->CurState) + { + case EQUIP_DEVICE: + select = DevicesMenu (); + if (GLOBAL (CurrentActivity) & START_ENCOUNTER) + { // Invoked Talking Pet or a Caster for Ilwrath + // Going into conversation + return FALSE; + } + break; + case CARGO: + CargoMenu (); + break; + case ROSTER: + select = RosterMenu (); + break; + case GAME_MENU: + if (!GameOptions ()) + return FALSE; // abort or load + break; + case STARMAP: + StarMap (); + if (GLOBAL (CurrentActivity) & CHECK_ABORT) + return FALSE; + + TransitionSystemIn (); + // Fall through !!! + case NAVIGATION: + return FALSE; + } + + if (!(GLOBAL (CurrentActivity) & CHECK_ABORT)) + { + if (select) + { // 3DO menu jumps to NAVIGATE after a successful submenu run + if (optWhichMenu != OPT_PC) + pMS->CurState = NAVIGATION; + DrawMenuStateStrings (PM_STARMAP, pMS->CurState); + } + SetFlashRect (SFR_MENU_3DO); + } + + return TRUE; +} + +static void +SolarSysMenu (void) +{ + MENU_STATE MenuState; + + memset (&MenuState, 0, sizeof MenuState); + + if (LastActivity == CHECK_LOAD) + { // Selected LOAD from main menu + MenuState.CurState = GAME_MENU; + } + else + { + DrawMenuStateStrings (PM_STARMAP, STARMAP); + MenuState.CurState = STARMAP; + } + + DrawStatusMessage (NULL); + SetFlashRect (SFR_MENU_3DO); + + SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); + MenuState.InputFunc = DoSolarSysMenu; + DoInput (&MenuState, TRUE); + + DrawMenuStateStrings (PM_STARMAP, -NAVIGATION); +} + +static BOOLEAN +DoIpFlight (SOLARSYS_STATE *pSS) +{ + static TimeCount NextTime; + BOOLEAN cancel = PulsedInputState.menu[KEY_MENU_CANCEL]; + + if (pSS->InOrbit) + { // CheckShipLocation() or InitSolarSys() sent us to orbital + EnterPlanetOrbit (); + SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE); + pSS->InOrbit = FALSE; + } + else if (cancel || LastActivity == CHECK_LOAD) + { + SolarSysMenu (); + SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE); + } + else + { + assert (pSS->InIpFlight); + IP_frame (); + SleepThreadUntil (NextTime); + NextTime = GetTimeCounter () + IP_FRAME_RATE; + } + + return (!(GLOBAL (CurrentActivity) + & (START_ENCOUNTER | END_INTERPLANETARY + | CHECK_ABORT | CHECK_LOAD)) + && GLOBAL_SIS (CrewEnlisted) != (COUNT)~0); +} diff --git a/src/uqm/planets/solarsys.h b/src/uqm/planets/solarsys.h new file mode 100644 index 0000000..0f86fdd --- /dev/null +++ b/src/uqm/planets/solarsys.h @@ -0,0 +1,34 @@ +//Copyright (C) 2011, Scott A. Colcord + +/* + * 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. + */ + +#ifndef SOLARSYS_H +#define SOLARSYS_H + +#if defined(__cplusplus) +extern "C" { +#endif + +extern void LoadIPData (void); +extern void FreeIPData (void); + +#if defined(__cplusplus) +} +#endif + +#endif /* SOLARSYS_H */ + diff --git a/src/uqm/planets/sundata.h b/src/uqm/planets/sundata.h new file mode 100644 index 0000000..6d7888e --- /dev/null +++ b/src/uqm/planets/sundata.h @@ -0,0 +1,73 @@ +//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. + */ + +#ifndef UQM_PLANETS_SUNDATA_H_ +#define UQM_PLANETS_SUNDATA_H_ + +#include "plandata.h" +#include "libs/compiler.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/*------------------------------ Global Data ------------------------------ */ + +#define NUMBER_OF_SUN_SIZES (SUPER_GIANT_STAR - DWARF_STAR + 1) + +#define DWARF_ENERGY 1 +#define GIANT_ENERGY 5 +#define SUPERGIANT_ENERGY 20 + +typedef struct +{ + BYTE StarSize; + BYTE StarIntensity; + UWORD StarEnergy; + + PLANET_INFO PlanetInfo; +} SYSTEM_INFO; + +#define GENERATE_ALL ((COUNT)~0) + +extern COUNT GenerateMineralDeposits (const SYSTEM_INFO *, COUNT whichDeposit, + NODE_INFO *info); +extern COUNT GenerateLifeForms (const SYSTEM_INFO *, COUNT whichLife, + NODE_INFO *info); +extern void GenerateRandomLocation (POINT *loc); +extern COUNT GenerateRandomNodes (const SYSTEM_INFO *, COUNT scan, COUNT numNodes, + COUNT type, COUNT whichNode, NODE_INFO *info); +// Generate lifeforms from a preset lifeTypes[] array +extern COUNT GeneratePresetLife (const SYSTEM_INFO *, + const SBYTE *lifeTypes, COUNT whichLife, NODE_INFO *info); + +#define DWARF_ELEMENT_DENSITY 1 +#define GIANT_ELEMENT_DENSITY 3 +#define SUPERGIANT_ELEMENT_DENSITY 8 + +#define MAX_ELEMENT_DENSITY ((MAX_ELEMENT_UNITS * SUPERGIANT_ELEMENT_DENSITY) << 1) + +extern void DoPlanetaryAnalysis (SYSTEM_INFO *SysInfoPtr, + PLANET_DESC *pPlanetDesc); + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_PLANETS_SUNDATA_H_ */ + diff --git a/src/uqm/planets/surface.c b/src/uqm/planets/surface.c new file mode 100644 index 0000000..79f9e75 --- /dev/null +++ b/src/uqm/planets/surface.c @@ -0,0 +1,251 @@ +//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 "lifeform.h" +#include "planets.h" +#include "libs/mathlib.h" +#include "libs/log.h" + + +//#define DEBUG_SURFACE + +const BYTE *Elements; +const PlanetFrame *PlanData; + +static COUNT +CalcMineralDeposits (const SYSTEM_INFO *SysInfoPtr, COUNT which_deposit, + NODE_INFO *info) +{ + BYTE j; + COUNT num_deposits; + const ELEMENT_ENTRY *eptr; + + eptr = &SysInfoPtr->PlanetInfo.PlanDataPtr->UsefulElements[0]; + num_deposits = 0; + j = NUM_USEFUL_ELEMENTS; + do + { + BYTE num_possible; + + num_possible = LOBYTE (RandomContext_Random (SysGenRNG)) + % (DEPOSIT_QUANTITY (eptr->Density) + 1); + while (num_possible--) + { +#define MEDIUM_DEPOSIT_THRESHOLD 150 +#define LARGE_DEPOSIT_THRESHOLD 225 + COUNT deposit_quality_fine; + COUNT deposit_quality_gross; + + deposit_quality_fine = (LOWORD (RandomContext_Random (SysGenRNG)) % 100) + + ( + DEPOSIT_QUALITY (eptr->Density) + + SysInfoPtr->StarSize + ) * 50; + if (deposit_quality_fine < MEDIUM_DEPOSIT_THRESHOLD) + deposit_quality_gross = 0; + else if (deposit_quality_fine < LARGE_DEPOSIT_THRESHOLD) + deposit_quality_gross = 1; + else + deposit_quality_gross = 2; + + GenerateRandomLocation (&info->loc_pt); + + info->density = MAKE_WORD ( + deposit_quality_gross, deposit_quality_fine / 10 + 1); + info->type = eptr->ElementType; +#ifdef DEBUG_SURFACE + log_add (log_Debug, "\t\t%d units of %Fs", + info->density, + Elements[eptr->ElementType].name); +#endif /* DEBUG_SURFACE */ + if (num_deposits >= which_deposit + || ++num_deposits == sizeof (DWORD) * 8) + { // reached the maximum or the requested node + return num_deposits; + } + } + ++eptr; + } while (--j); + + return num_deposits; +} + +// Returns: +// for whichLife==~0 : the number of nodes generated +// for whichLife<32 : the index of the last node (no known usage exists) +// Sets the SysGenRNG to the required state first. +COUNT +GenerateMineralDeposits (const SYSTEM_INFO *SysInfoPtr, COUNT whichDeposit, + NODE_INFO *info) +{ + NODE_INFO temp_info; + if (!info) // user not interested in info but we need space for it + info = &temp_info; + RandomContext_SeedRandom (SysGenRNG, + SysInfoPtr->PlanetInfo.ScanSeed[MINERAL_SCAN]); + return CalcMineralDeposits (SysInfoPtr, whichDeposit, info); +} + +static COUNT +CalcLifeForms (const SYSTEM_INFO *SysInfoPtr, COUNT which_life, + NODE_INFO *info) +{ + COUNT num_life_forms; + + num_life_forms = 0; + if (PLANSIZE (SysInfoPtr->PlanetInfo.PlanDataPtr->Type) != GAS_GIANT) + { +#define MIN_LIFE_CHANCE 10 + SIZE life_var; + + life_var = RandomContext_Random (SysGenRNG) & 1023; + if (life_var < SysInfoPtr->PlanetInfo.LifeChance + || (SysInfoPtr->PlanetInfo.LifeChance < MIN_LIFE_CHANCE + && life_var < MIN_LIFE_CHANCE)) + { + BYTE num_types; + + num_types = 1 + LOBYTE (RandomContext_Random (SysGenRNG)) + % MAX_LIFE_VARIATION; + do + { + BYTE index, num_creatures; + UWORD rand_val; + + rand_val = RandomContext_Random (SysGenRNG); + index = LOBYTE (rand_val) % NUM_CREATURE_TYPES; + num_creatures = 1 + HIBYTE (rand_val) % 10; + do + { + GenerateRandomLocation (&info->loc_pt); + info->type = index; + info->density = 0; + + if (num_life_forms >= which_life + || ++num_life_forms == sizeof (DWORD) * 8) + { // reached the maximum or the requested node + return num_life_forms; + } + } while (--num_creatures); + } while (--num_types); + } +#ifdef DEBUG_SURFACE + else + { + log_add (log_Debug, "It's dead, Jim! (%d >= %d)", life_var, + SysInfoPtr->PlanetInfo.LifeChance); + } +#endif /* DEBUG_SURFACE */ + } + + return num_life_forms; +} + +// Returns: +// for whichLife==~0 : the number of lifeforms generated +// for whichLife<32 : the index of the last lifeform (no known usage exists) +// Sets the SysGenRNG to the required state first. +COUNT +GenerateLifeForms (const SYSTEM_INFO *SysInfoPtr, COUNT whichLife, + NODE_INFO *info) +{ + NODE_INFO temp_info; + if (!info) // user not interested in info but we need space for it + info = &temp_info; + RandomContext_SeedRandom (SysGenRNG, + SysInfoPtr->PlanetInfo.ScanSeed[BIOLOGICAL_SCAN]); + return CalcLifeForms (SysInfoPtr, whichLife, info); +} + +// Returns: +// for whichLife==~0 : the number of lifeforms generated +// for whichLife<32 : the index of the last lifeform (no known usage exists) +// Sets the SysGenRNG to the required state first. +// lifeTypes[] is terminated with -1 +COUNT +GeneratePresetLife (const SYSTEM_INFO *SysInfoPtr, const SBYTE *lifeTypes, + COUNT whichLife, NODE_INFO *info) +{ + COUNT i; + NODE_INFO temp_info; + + if (!info) // user not interested in info but we need space for it + info = &temp_info; + + // This function may look unnecessarily complicated, but it must be + // kept this way to preserve the universe. That is done by preserving + // the order and number of Random() calls. + + RandomContext_SeedRandom (SysGenRNG, + SysInfoPtr->PlanetInfo.ScanSeed[BIOLOGICAL_SCAN]); + + for (i = 0; lifeTypes[i] >= 0; ++i) + { + GenerateRandomLocation (&info->loc_pt); + info->type = lifeTypes[i]; + // density is irrelevant for bio nodes + info->density = 0; + + if (i >= whichLife) + break; + } + + return i; +} + +void +GenerateRandomLocation (POINT *loc) +{ + UWORD rand_val; + + rand_val = RandomContext_Random (SysGenRNG); + loc->x = 8 + LOBYTE (rand_val) % (MAP_WIDTH - (8 << 1)); + loc->y = 8 + HIBYTE (rand_val) % (MAP_HEIGHT - (8 << 1)); +} + +// Returns: +// for whichNode==~0 : the number of nodes generated +// for whichNode<32 : the index of the last node (no known usage exists) +// Sets the SysGenRNG to the required state first. +COUNT +GenerateRandomNodes (const SYSTEM_INFO *SysInfoPtr, COUNT scan, COUNT numNodes, + COUNT type, COUNT whichNode, NODE_INFO *info) +{ + COUNT i; + NODE_INFO temp_info; + + if (!info) // user not interested in info but we need space for it + info = &temp_info; + + RandomContext_SeedRandom (SysGenRNG, + SysInfoPtr->PlanetInfo.ScanSeed[scan]); + + for (i = 0; i < numNodes; ++i) + { + GenerateRandomLocation (&info->loc_pt); + // type is irrelevant for energy nodes + info->type = type; + // density is irrelevant for energy and bio nodes + info->density = 0; + + if (i >= whichNode) + break; + } + + return i; +} |