diff options
Diffstat (limited to 'src/uqm/planets/solarsys.c')
-rw-r--r-- | src/uqm/planets/solarsys.c | 2021 |
1 files changed, 2021 insertions, 0 deletions
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); +} |