1 //Copyright Paul Reiche, Fred Ford. 1992-2002
2 
3 /*
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18 
19 #include "solarsys.h"
20 #include "lander.h"
21 #include "../colors.h"
22 #include "../controls.h"
23 #include "../menustat.h"
24 		// for DrawMenuStateStrings()
25 #include "../starmap.h"
26 #include "../races.h"
27 #include "../gamestr.h"
28 #include "../gendef.h"
29 #include "../globdata.h"
30 #include "../sis.h"
31 #include "../init.h"
32 #include "../shipcont.h"
33 #include "../gameopt.h"
34 #include "../nameref.h"
35 #include "../resinst.h"
36 #include "../settings.h"
37 #include "../ipdisp.h"
38 #include "../grpinfo.h"
39 #include "../process.h"
40 #include "../setup.h"
41 #include "../sounds.h"
42 #include "../state.h"
43 #include "../uqmdebug.h"
44 #include "../save.h"
45 #include "options.h"
46 #include "libs/graphics/gfx_common.h"
47 #include "libs/mathlib.h"
48 #include "libs/log.h"
49 #include "libs/misc.h"
50 
51 
52 //#define DEBUG_SOLARSYS
53 //#define SMOOTH_SYSTEM_ZOOM  1
54 
55 #define IP_FRAME_RATE  (ONE_SECOND / 30)
56 
57 static BOOLEAN DoIpFlight (SOLARSYS_STATE *pSS);
58 static void DrawSystem (SIZE radius, BOOLEAN IsInnerSystem);
59 static FRAME CreateStarBackGround (void);
60 static void DrawInnerSystem (void);
61 static void DrawOuterSystem (void);
62 static void ValidateOrbits (void);
63 
64 // SolarSysMenu() items
65 enum SolarSysMenuMenuItems
66 {
67 	// XXX: Must match the enum in menustat.h
68 	STARMAP = 1,
69 	EQUIP_DEVICE,
70 	CARGO,
71 	ROSTER,
72 	GAME_MENU,
73 	NAVIGATION,
74 };
75 
76 
77 SOLARSYS_STATE *pSolarSysState;
78 FRAME SISIPFrame;
79 FRAME SunFrame;
80 FRAME OrbitalFrame;
81 FRAME SpaceJunkFrame;
82 COLORMAP OrbitalCMap;
83 COLORMAP SunCMap;
84 MUSIC_REF SpaceMusic;
85 
86 SIZE EncounterRace;
87 BYTE EncounterGroup;
88 		// last encountered group info
89 
90 static FRAME StarsFrame;
91 		// prepared star-field graphic
92 static FRAME SolarSysFrame;
93 		// saved solar system view graphic
94 
95 static RECT scaleRect;
96 		// system zooms in when the flagship enters this rect
97 
98 RandomContext *SysGenRNG;
99 
100 #define DISPLAY_TO_LOC  (DISPLAY_FACTOR >> 1)
101 
102 POINT
locationToDisplay(POINT pt,SIZE scaleRadius)103 locationToDisplay (POINT pt, SIZE scaleRadius)
104 {
105 	POINT out;
106 
107 	out.x = (SIS_SCREEN_WIDTH >> 1)
108 			+ (long)pt.x * DISPLAY_TO_LOC / scaleRadius;
109 	out.y = (SIS_SCREEN_HEIGHT >> 1)
110 			+ (long)pt.y * DISPLAY_TO_LOC / scaleRadius;
111 
112 	return out;
113 }
114 
115 POINT
displayToLocation(POINT pt,SIZE scaleRadius)116 displayToLocation (POINT pt, SIZE scaleRadius)
117 {
118 	POINT out;
119 
120 	out.x = ((long)pt.x - (SIS_SCREEN_WIDTH >> 1))
121 			* scaleRadius / DISPLAY_TO_LOC;
122 	out.y = ((long)pt.y - (SIS_SCREEN_HEIGHT >> 1))
123 			* scaleRadius / DISPLAY_TO_LOC;
124 
125 	return out;
126 }
127 
128 POINT
planetOuterLocation(COUNT planetI)129 planetOuterLocation (COUNT planetI)
130 {
131 	SIZE scaleRadius = pSolarSysState->SunDesc[0].radius;
132 	return displayToLocation (pSolarSysState->PlanetDesc[planetI].image.origin,
133 			scaleRadius);
134 }
135 
136 bool
worldIsPlanet(const SOLARSYS_STATE * solarSys,const PLANET_DESC * world)137 worldIsPlanet (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world)
138 {
139 	return world->pPrevDesc == solarSys->SunDesc;
140 }
141 
142 bool
worldIsMoon(const SOLARSYS_STATE * solarSys,const PLANET_DESC * world)143 worldIsMoon (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world)
144 {
145 	return world->pPrevDesc != solarSys->SunDesc;
146 }
147 
148 // Returns the planet index of the world. If the world is a moon, then
149 // this is the index of the planet it is orbiting.
150 COUNT
planetIndex(const SOLARSYS_STATE * solarSys,const PLANET_DESC * world)151 planetIndex (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world)
152 {
153 	const PLANET_DESC *planet = worldIsPlanet (solarSys, world) ?
154 			world : world->pPrevDesc;
155 	return planet - solarSys->PlanetDesc;
156 }
157 
158 COUNT
moonIndex(const SOLARSYS_STATE * solarSys,const PLANET_DESC * moon)159 moonIndex (const SOLARSYS_STATE *solarSys, const PLANET_DESC *moon)
160 {
161 	assert (!worldIsPlanet (solarSys, moon));
162 	return moon - solarSys->MoonDesc;
163 }
164 
165 // Test whether 'world' is the planetI-th planet, and if moonI is not
166 // set to MATCH_PLANET, also whether 'world' is the moonI-th moon.
167 bool
matchWorld(const SOLARSYS_STATE * solarSys,const PLANET_DESC * world,BYTE planetI,BYTE moonI)168 matchWorld (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world,
169 		BYTE planetI, BYTE moonI)
170 {
171 	// Check whether we have the right planet.
172 	if (planetIndex (solarSys, world) != planetI)
173 		return false;
174 
175 	if (moonI == MATCH_PLANET)
176 	{
177 		// Only test whether we are at the planet.
178 		if (!worldIsPlanet (solarSys, world))
179 			return false;
180 	}
181 	else
182 	{
183 		// Test whether the moon matches too
184 		if (!worldIsMoon (solarSys, world))
185 			return false;
186 
187 		if (moonIndex (solarSys, world) != moonI)
188 			return false;
189 	}
190 
191 	return true;
192 }
193 
194 bool
playerInSolarSystem(void)195 playerInSolarSystem (void)
196 {
197 	return pSolarSysState != NULL;
198 }
199 
200 bool
playerInPlanetOrbit(void)201 playerInPlanetOrbit (void)
202 {
203 	return playerInSolarSystem () && pSolarSysState->InOrbit;
204 }
205 
206 bool
playerInInnerSystem(void)207 playerInInnerSystem (void)
208 {
209 	assert (playerInSolarSystem ());
210 	assert (pSolarSysState->pBaseDesc == pSolarSysState->PlanetDesc
211 			|| pSolarSysState->pBaseDesc == pSolarSysState->MoonDesc);
212 	return pSolarSysState->pBaseDesc != pSolarSysState->PlanetDesc;
213 }
214 
215 // Sets the SysGenRNG to the required state first.
216 static void
GenerateMoons(SOLARSYS_STATE * system,PLANET_DESC * planet)217 GenerateMoons (SOLARSYS_STATE *system, PLANET_DESC *planet)
218 {
219 	COUNT i;
220 	COUNT facing;
221 	PLANET_DESC *pMoonDesc;
222 
223 	RandomContext_SeedRandom (SysGenRNG, planet->rand_seed);
224 
225 	(*system->genFuncs->generateName) (system, planet);
226 	(*system->genFuncs->generateMoons) (system, planet);
227 
228 	facing = NORMALIZE_FACING (ANGLE_TO_FACING (
229 			ARCTAN (planet->location.x, planet->location.y)));
230 	for (i = 0, pMoonDesc = &system->MoonDesc[0];
231 			i < MAX_MOONS; ++i, ++pMoonDesc)
232 	{
233 		pMoonDesc->pPrevDesc = planet;
234 		if (i >= planet->NumPlanets)
235 			continue;
236 
237 		pMoonDesc->temp_color = planet->temp_color;
238 	}
239 }
240 
241 void
FreeIPData(void)242 FreeIPData (void)
243 {
244 	DestroyDrawable (ReleaseDrawable (SISIPFrame));
245 	SISIPFrame = 0;
246 	DestroyDrawable (ReleaseDrawable (SunFrame));
247 	SunFrame = 0;
248 	DestroyColorMap (ReleaseColorMap (SunCMap));
249 	SunCMap = 0;
250 	DestroyColorMap (ReleaseColorMap (OrbitalCMap));
251 	OrbitalCMap = 0;
252 	DestroyDrawable (ReleaseDrawable (OrbitalFrame));
253 	OrbitalFrame = 0;
254 	DestroyDrawable (ReleaseDrawable (SpaceJunkFrame));
255 	SpaceJunkFrame = 0;
256 	DestroyMusic (SpaceMusic);
257 	SpaceMusic = 0;
258 
259 	RandomContext_Delete (SysGenRNG);
260 	SysGenRNG = NULL;
261 }
262 
263 void
LoadIPData(void)264 LoadIPData (void)
265 {
266 	if (SpaceJunkFrame == 0)
267 	{
268 		SpaceJunkFrame = CaptureDrawable (
269 				LoadGraphic (IPBKGND_MASK_PMAP_ANIM));
270 		SISIPFrame = CaptureDrawable (LoadGraphic (SISIP_MASK_PMAP_ANIM));
271 
272 		OrbitalCMap = CaptureColorMap (LoadColorMap (ORBPLAN_COLOR_MAP));
273 		OrbitalFrame = CaptureDrawable (
274 				LoadGraphic (ORBPLAN_MASK_PMAP_ANIM));
275 		SunCMap = CaptureColorMap (LoadColorMap (IPSUN_COLOR_MAP));
276 		SunFrame = CaptureDrawable (LoadGraphic (SUN_MASK_PMAP_ANIM));
277 
278 		SpaceMusic = LoadMusic (IP_MUSIC);
279 	}
280 
281 	if (!SysGenRNG)
282 	{
283 		SysGenRNG = RandomContext_New ();
284 	}
285 }
286 
287 
288 static void
sortPlanetPositions(void)289 sortPlanetPositions (void)
290 {
291 	COUNT i;
292 	SIZE sort_array[MAX_PLANETS + 1];
293 
294 	// When this part is done, sort_array will contain the indices to
295 	// all planets, sorted on their y position.
296 	// The sun itself, which has its data located at
297 	// pSolarSysState->PlanetDesc[-1], is included in this array.
298 	// Very ugly stuff, but it's correct.
299 
300 	// Initialise sort_array.
301 	for (i = 0; i <= pSolarSysState->SunDesc[0].NumPlanets; ++i)
302 		sort_array[i] = i - 1;
303 
304 	// Sort sort_array, based on the positions of the planets/sun.
305 	for (i = 0; i <= pSolarSysState->SunDesc[0].NumPlanets; ++i)
306 	{
307 		COUNT j;
308 
309 		for (j = pSolarSysState->SunDesc[0].NumPlanets; j > i; --j)
310 		{
311 			SIZE real_i, real_j;
312 
313 			real_i = sort_array[i];
314 			real_j = sort_array[j];
315 			if (pSolarSysState->PlanetDesc[real_i].image.origin.y >
316 					pSolarSysState->PlanetDesc[real_j].image.origin.y)
317 			{
318 				SIZE temp;
319 
320 				temp = sort_array[i];
321 				sort_array[i] = sort_array[j];
322 				sort_array[j] = temp;
323 			}
324 		}
325 	}
326 
327 	// Put the results of the sorting in the solar system structure.
328 	pSolarSysState->FirstPlanetIndex = sort_array[0];
329 	pSolarSysState->LastPlanetIndex =
330 			sort_array[pSolarSysState->SunDesc[0].NumPlanets];
331 	for (i = 0; i <= pSolarSysState->SunDesc[0].NumPlanets; ++i) {
332 		PLANET_DESC *planet = &pSolarSysState->PlanetDesc[sort_array[i]];
333 		planet->NextIndex = sort_array[i + 1];
334 	}
335 }
336 
337 static void
initSolarSysSISCharacteristics(void)338 initSolarSysSISCharacteristics (void)
339 {
340 	BYTE i;
341 	BYTE num_thrusters;
342 
343 	num_thrusters = 0;
344 	for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
345 	{
346 		if (GLOBAL_SIS (DriveSlots[i]) == FUSION_THRUSTER)
347 			++num_thrusters;
348 	}
349 	pSolarSysState->max_ship_speed = (BYTE)(
350 			(num_thrusters + 5) * IP_SHIP_THRUST_INCREMENT);
351 
352 	pSolarSysState->turn_wait = IP_SHIP_TURN_WAIT;
353 	for (i = 0; i < NUM_JET_SLOTS; ++i)
354 	{
355 		if (GLOBAL_SIS (JetSlots[i]) == TURNING_JETS)
356 			pSolarSysState->turn_wait -= IP_SHIP_TURN_DECREMENT;
357 	}
358 }
359 
360 DWORD
GetRandomSeedForStar(const STAR_DESC * star)361 GetRandomSeedForStar (const STAR_DESC *star)
362 {
363 	return MAKE_DWORD (star->star_pt.x, star->star_pt.y);
364 }
365 
366 // Returns an orbital PLANET_DESC when player is in orbit
367 static PLANET_DESC *
LoadSolarSys(void)368 LoadSolarSys (void)
369 {
370 	COUNT i;
371 	PLANET_DESC *orbital = NULL;
372 	PLANET_DESC *pCurDesc;
373 #define NUM_TEMP_RANGES 5
374 	static const Color temp_color_array[NUM_TEMP_RANGES] =
375 	{
376 		BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x00, 0x0E), 0x54),
377 		BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x06, 0x08), 0x62),
378 		BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x0B, 0x00), 0x6D),
379 		BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x00), 0x2D),
380 		BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x08, 0x00), 0x75),
381 	};
382 
383 	RandomContext_SeedRandom (SysGenRNG, GetRandomSeedForStar (CurStarDescPtr));
384 
385 	SunFrame = SetAbsFrameIndex (SunFrame, STAR_TYPE (CurStarDescPtr->Type));
386 
387 	pCurDesc = &pSolarSysState->SunDesc[0];
388 	pCurDesc->pPrevDesc = 0;
389 	pCurDesc->rand_seed = RandomContext_Random (SysGenRNG);
390 
391 	pCurDesc->data_index = STAR_TYPE (CurStarDescPtr->Type);
392 	pCurDesc->location.x = 0;
393 	pCurDesc->location.y = 0;
394 	pCurDesc->image.origin = pCurDesc->location;
395 	pCurDesc->image.frame = SunFrame;
396 
397 	(*pSolarSysState->genFuncs->generatePlanets) (pSolarSysState);
398 	if (GET_GAME_STATE (PLANETARY_CHANGE))
399 	{
400 		PutPlanetInfo ();
401 		SET_GAME_STATE (PLANETARY_CHANGE, 0);
402 	}
403 
404 	for (i = 0, pCurDesc = pSolarSysState->PlanetDesc;
405 			i < MAX_PLANETS; ++i, ++pCurDesc)
406 	{
407 		pCurDesc->pPrevDesc = &pSolarSysState->SunDesc[0];
408 		pCurDesc->image.origin = pCurDesc->location;
409 		if (i >= pSolarSysState->SunDesc[0].NumPlanets)
410 		{
411 			pCurDesc->image.frame = 0;
412 		}
413 		else
414 		{
415 			COUNT index;
416 			SYSTEM_INFO SysInfo;
417 
418 			DoPlanetaryAnalysis (&SysInfo, pCurDesc);
419 			index = (SysInfo.PlanetInfo.SurfaceTemperature + 250) / 100;
420 			if (index >= NUM_TEMP_RANGES)
421 				index = NUM_TEMP_RANGES - 1;
422 			pCurDesc->temp_color = temp_color_array[index];
423 		}
424 	}
425 
426 	sortPlanetPositions ();
427 
428 	if (!GLOBAL (ip_planet))
429 	{	// Outer system
430 		pSolarSysState->pBaseDesc = pSolarSysState->PlanetDesc;
431 		pSolarSysState->pOrbitalDesc = NULL;
432 	}
433 	else
434 	{	// Inner system
435 		pSolarSysState->SunDesc[0].location = GLOBAL (ip_location);
436 		GLOBAL (ip_location) = displayToLocation (
437 				GLOBAL (ShipStamp.origin), MAX_ZOOM_RADIUS);
438 
439 		i = GLOBAL (ip_planet) - 1;
440 		pSolarSysState->pOrbitalDesc = &pSolarSysState->PlanetDesc[i];
441 		GenerateMoons (pSolarSysState, pSolarSysState->pOrbitalDesc);
442 		pSolarSysState->pBaseDesc = pSolarSysState->MoonDesc;
443 
444 		SET_GAME_STATE (PLANETARY_LANDING, 0);
445 	}
446 
447 	initSolarSysSISCharacteristics ();
448 
449 	if (GLOBAL (in_orbit))
450 	{	// Only when loading a game into orbital
451 		i = GLOBAL (in_orbit) - 1;
452 		if (i == 0)
453 		{	// Orbiting the planet itself
454 			orbital = pSolarSysState->pBaseDesc->pPrevDesc;
455 		}
456 		else
457 		{	// Orbiting a moon
458 			// -1 because planet itself is 1, and moons have to be 1-based
459 			i -= 1;
460 			orbital = &pSolarSysState->MoonDesc[i];
461 		}
462 		GLOBAL (ip_location) = pSolarSysState->SunDesc[0].location;
463 		GLOBAL (in_orbit) = 0;
464 	}
465 	else
466 	{
467 		i = GLOBAL (ShipFacing);
468 		// XXX: Solar system reentry test depends on ShipFacing != 0
469 		if (i == 0)
470 			++i;
471 
472 		GLOBAL (ShipStamp.frame) = SetAbsFrameIndex (SISIPFrame, i - 1);
473 	}
474 
475 	return orbital;
476 }
477 
478 static void
saveNonOrbitalLocation(void)479 saveNonOrbitalLocation (void)
480 {
481 	// XXX: Solar system reentry test depends on ShipFacing != 0
482 	GLOBAL (ShipFacing) = GetFrameIndex (GLOBAL (ShipStamp.frame)) + 1;
483 	GLOBAL (in_orbit) = 0;
484 	if (!playerInInnerSystem ())
485 	{
486 		GLOBAL (ip_planet) = 0;
487 	}
488 	else
489 	{
490 		// ip_planet is 1-based because code tests for ip_planet!=0
491 		GLOBAL (ip_planet) = 1 + planetIndex (pSolarSysState,
492 				pSolarSysState->pOrbitalDesc);
493 		GLOBAL (ip_location) = pSolarSysState->SunDesc[0].location;
494 	}
495 }
496 
497 static void
FreeSolarSys(void)498 FreeSolarSys (void)
499 {
500 	if (pSolarSysState->InIpFlight)
501 	{
502 		pSolarSysState->InIpFlight = FALSE;
503 
504 		if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
505 			saveNonOrbitalLocation ();
506 	}
507 
508 	DestroyDrawable (ReleaseDrawable (SolarSysFrame));
509 	SolarSysFrame = NULL;
510 
511 	StopMusic ();
512 
513 //    FreeIPData ();
514 }
515 
516 static FRAME
getCollisionFrame(PLANET_DESC * planet,COUNT WaitPlanet)517 getCollisionFrame (PLANET_DESC *planet, COUNT WaitPlanet)
518 {
519 	if (pSolarSysState->WaitIntersect != (COUNT)~0
520 			&& pSolarSysState->WaitIntersect != WaitPlanet)
521 	{	// New collisions are with a single point (center of planet)
522 		return DecFrameIndex (stars_in_space);
523 	}
524 	else
525 	{	// Existing collisions are cleared only once the ship does not
526 		// intersect anymore with a full planet image
527 		return planet->image.frame;
528 	}
529 }
530 
531 // Returns the planet with which the flagship is colliding
532 static PLANET_DESC *
CheckIntersect(void)533 CheckIntersect (void)
534 {
535 	COUNT i;
536 	PLANET_DESC *pCurDesc;
537 	INTERSECT_CONTROL ShipIntersect, PlanetIntersect;
538 	COUNT NewWaitPlanet;
539 	BYTE PlanetOffset, MoonOffset;
540 
541 	// Check collisions with the system center object
542 	// This may be the planet in inner view, or the sun
543 	pCurDesc = pSolarSysState->pBaseDesc->pPrevDesc;
544 	PlanetOffset = pCurDesc - pSolarSysState->PlanetDesc + 1;
545 	MoonOffset = 1; // the planet itself
546 
547 	ShipIntersect.IntersectStamp.origin = GLOBAL (ShipStamp.origin);
548 	ShipIntersect.EndPoint = ShipIntersect.IntersectStamp.origin;
549 	ShipIntersect.IntersectStamp.frame = GLOBAL (ShipStamp.frame);
550 
551 	PlanetIntersect.IntersectStamp.origin.x = SIS_SCREEN_WIDTH >> 1;
552 	PlanetIntersect.IntersectStamp.origin.y = SIS_SCREEN_HEIGHT >> 1;
553 	PlanetIntersect.EndPoint = PlanetIntersect.IntersectStamp.origin;
554 
555 	PlanetIntersect.IntersectStamp.frame = getCollisionFrame (pCurDesc,
556 			MAKE_WORD (PlanetOffset, MoonOffset));
557 
558 	// Start with no collisions
559 	NewWaitPlanet = 0;
560 
561 	if (pCurDesc != pSolarSysState->SunDesc /* can't intersect with sun */
562 			&& DrawablesIntersect (&ShipIntersect,
563 			&PlanetIntersect, MAX_TIME_VALUE))
564 	{
565 #ifdef DEBUG_SOLARSYS
566 		log_add (log_Debug, "0: Planet %d, Moon %d", PlanetOffset,
567 				MoonOffset);
568 #endif /* DEBUG_SOLARSYS */
569 		NewWaitPlanet = MAKE_WORD (PlanetOffset, MoonOffset);
570 		if (pSolarSysState->WaitIntersect != (COUNT)~0
571 				&& pSolarSysState->WaitIntersect != NewWaitPlanet)
572 		{
573 			pSolarSysState->WaitIntersect = NewWaitPlanet;
574 #ifdef DEBUG_SOLARSYS
575 			log_add (log_Debug, "Star index = %d, Planet index = %d, <%d, %d>",
576 					CurStarDescPtr - star_array,
577 					pCurDesc - pSolarSysState->PlanetDesc,
578 					pSolarSysState->SunDesc[0].location.x,
579 					pSolarSysState->SunDesc[0].location.y);
580 #endif /* DEBUG_SOLARSYS */
581 			return pCurDesc;
582 		}
583 	}
584 
585 	for (i = pCurDesc->NumPlanets,
586 			pCurDesc = pSolarSysState->pBaseDesc; i; --i, ++pCurDesc)
587 	{
588 		PlanetIntersect.IntersectStamp.origin = pCurDesc->image.origin;
589 		PlanetIntersect.EndPoint = PlanetIntersect.IntersectStamp.origin;
590 		if (playerInInnerSystem ())
591 		{
592 			PlanetOffset = pCurDesc->pPrevDesc -
593 					pSolarSysState->PlanetDesc;
594 			MoonOffset = pCurDesc - pSolarSysState->MoonDesc + 2;
595 		}
596 		else
597 		{
598 			PlanetOffset = pCurDesc - pSolarSysState->PlanetDesc;
599 			MoonOffset = 0;
600 		}
601 		++PlanetOffset;
602 		PlanetIntersect.IntersectStamp.frame = getCollisionFrame (pCurDesc,
603 				MAKE_WORD (PlanetOffset, MoonOffset));
604 
605 		if (DrawablesIntersect (&ShipIntersect,
606 				&PlanetIntersect, MAX_TIME_VALUE))
607 		{
608 #ifdef DEBUG_SOLARSYS
609 			log_add (log_Debug, "1: Planet %d, Moon %d", PlanetOffset,
610 					MoonOffset);
611 #endif /* DEBUG_SOLARSYS */
612 			NewWaitPlanet = MAKE_WORD (PlanetOffset, MoonOffset);
613 
614 			if (pSolarSysState->WaitIntersect == (COUNT)~0)
615 			{	// All collisions disallowed, but the ship is still colliding
616 				// with something. Collisions will remain disabled.
617 				break;
618 			}
619 			else if (pSolarSysState->WaitIntersect == NewWaitPlanet)
620 			{	// Existing and continued collision -- ignore
621 				continue;
622 			}
623 
624 			// Collision with a new planet/moon. This may cause a transition
625 			// to an inner system or start an orbital view.
626 			pSolarSysState->WaitIntersect = NewWaitPlanet;
627 			return pCurDesc;
628 		}
629 	}
630 
631 	// This records the planet/moon with which the ship just collided
632 	// It may be a previously existing collision also (the value won't change)
633 	// If all collisions were disabled, this will reenable then once the ship
634 	// stops colliding with any planets
635 	if (pSolarSysState->WaitIntersect != (COUNT)~0 || NewWaitPlanet == 0)
636 		pSolarSysState->WaitIntersect = NewWaitPlanet;
637 
638 	return NULL;
639 }
640 
641 static void
GetOrbitRect(RECT * pRect,COORD dx,COORD dy,SIZE radius,int xnumer,int ynumer,int denom)642 GetOrbitRect (RECT *pRect, COORD dx, COORD dy, SIZE radius,
643 		int xnumer, int ynumer, int denom)
644 {
645 	pRect->corner.x = (SIS_SCREEN_WIDTH >> 1) + (long)-dx * xnumer / denom;
646 	pRect->corner.y = (SIS_SCREEN_HEIGHT >> 1) + (long)-dy * ynumer / denom;
647 	pRect->extent.width = (long)radius * (xnumer << 1) / denom;
648 	pRect->extent.height = pRect->extent.width >> 1;
649 }
650 
651 static void
GetPlanetOrbitRect(RECT * r,PLANET_DESC * planet,int sizeNumer,int dyNumer,int denom)652 GetPlanetOrbitRect (RECT *r, PLANET_DESC *planet, int sizeNumer,
653 		int dyNumer, int denom)
654 {
655 	COORD dx, dy;
656 
657 	dx = planet->radius;
658 	dy = planet->radius;
659 	if (sizeNumer > DISPLAY_FACTOR)
660 	{
661 		dx = dx + planet->location.x;
662 		dy = (dy + planet->location.y) << 1;
663 	}
664 	GetOrbitRect (r, dx, dy, planet->radius, sizeNumer, dyNumer, denom);
665 }
666 
667 static void
ValidateOrbit(PLANET_DESC * planet,int sizeNumer,int dyNumer,int denom)668 ValidateOrbit (PLANET_DESC *planet, int sizeNumer, int dyNumer, int denom)
669 {
670 	COUNT index;
671 
672 	if (sizeNumer <= DISPLAY_FACTOR)
673 	{	// All planets in outer view, and moons in inner
674 		RECT r;
675 
676 		GetPlanetOrbitRect (&r, planet, sizeNumer, dyNumer, denom);
677 
678 		// Calculate the location of the planet's image
679 		r.corner.x += (r.extent.width >> 1);
680 		r.corner.y += (r.extent.height >> 1);
681 		r.corner.x += (long)planet->location.x * sizeNumer / denom;
682 		// Ellipse function always has coefficients a^2 = 2 * b^2
683 		r.corner.y += (long)planet->location.y * (sizeNumer / 2) / denom;
684 
685 		planet->image.origin = r.corner;
686 	}
687 
688 	// Calculate the size and lighting angle of planet's image and
689 	// set the image that will be drawn
690 	index = planet->data_index & ~WORLD_TYPE_SPECIAL;
691 	if (index < NUMBER_OF_PLANET_TYPES)
692 	{	// The world is a normal planetary body (planet or moon)
693 		BYTE Type;
694 		COUNT Size;
695 		COUNT angle;
696 
697 		Type = PlanData[index].Type;
698 		Size = PLANSIZE (Type);
699 		if (sizeNumer > DISPLAY_FACTOR)
700 		{
701 			Size += 3;
702 		}
703 		else if (worldIsMoon (pSolarSysState, planet))
704 		{
705 			Size += 2;
706 		}
707 		else if (denom <= (MAX_ZOOM_RADIUS >> 2))
708 		{
709 			++Size;
710 			if (denom == MIN_ZOOM_RADIUS)
711 				++Size;
712 		}
713 
714 		if (worldIsPlanet (pSolarSysState, planet))
715 		{	// Planet
716 			angle = ARCTAN (planet->location.x, planet->location.y);
717 		}
718 		else
719 		{	// Moon
720 			angle = ARCTAN (planet->pPrevDesc->location.x,
721 					planet->pPrevDesc->location.y);
722 		}
723 		planet->image.frame =	SetAbsFrameIndex (OrbitalFrame,
724 				(Size << FACING_SHIFT) + NORMALIZE_FACING (
725 				ANGLE_TO_FACING (angle)));
726 	}
727 	else if (planet->data_index == HIERARCHY_STARBASE)
728 	{
729 		planet->image.frame = SetAbsFrameIndex (SpaceJunkFrame, 16);
730 	}
731 	else if (planet->data_index == SA_MATRA)
732 	{
733 		planet->image.frame = SetAbsFrameIndex (SpaceJunkFrame, 19);
734 	}
735 }
736 
737 static void
DrawOrbit(PLANET_DESC * planet,int sizeNumer,int dyNumer,int denom)738 DrawOrbit (PLANET_DESC *planet, int sizeNumer, int dyNumer, int denom)
739 {
740 	RECT r;
741 
742 	GetPlanetOrbitRect (&r, planet, sizeNumer, dyNumer, denom);
743 
744 	SetContextForeGroundColor (planet->temp_color);
745 	DrawOval (&r, 1);
746 }
747 
748 static SIZE
FindRadius(POINT shipLoc,SIZE fromRadius)749 FindRadius (POINT shipLoc, SIZE fromRadius)
750 {
751 	SIZE nextRadius;
752 	POINT displayLoc;
753 
754 	do
755 	{
756 		fromRadius >>= 1;
757 		if (fromRadius > MIN_ZOOM_RADIUS)
758 			nextRadius = fromRadius >> 1;
759 		else
760 			nextRadius = 0; // scaleRect will be nul
761 
762 		GetOrbitRect (&scaleRect, nextRadius, nextRadius, nextRadius,
763 				DISPLAY_FACTOR, DISPLAY_FACTOR >> 2, fromRadius);
764 		displayLoc = locationToDisplay (shipLoc, fromRadius);
765 
766 	} while (pointWithinRect (scaleRect, displayLoc));
767 
768 	return fromRadius;
769 }
770 
771 static UWORD
flagship_inertial_thrust(COUNT CurrentAngle)772 flagship_inertial_thrust (COUNT CurrentAngle)
773 {
774 	BYTE max_speed;
775 	SIZE cur_delta_x, cur_delta_y;
776 	COUNT TravelAngle;
777 	VELOCITY_DESC *VelocityPtr;
778 
779 	max_speed = pSolarSysState->max_ship_speed;
780 	VelocityPtr = &GLOBAL (velocity);
781 	GetCurrentVelocityComponents (VelocityPtr, &cur_delta_x, &cur_delta_y);
782 	TravelAngle = GetVelocityTravelAngle (VelocityPtr);
783 	if (TravelAngle == CurrentAngle
784 			&& cur_delta_x == COSINE (CurrentAngle, max_speed)
785 			&& cur_delta_y == SINE (CurrentAngle, max_speed))
786 		return (SHIP_AT_MAX_SPEED);
787 	else
788 	{
789 		SIZE delta_x, delta_y;
790 		DWORD desired_speed;
791 
792 		delta_x = cur_delta_x
793 				+ COSINE (CurrentAngle, IP_SHIP_THRUST_INCREMENT);
794 		delta_y = cur_delta_y
795 				+ SINE (CurrentAngle, IP_SHIP_THRUST_INCREMENT);
796 		desired_speed = (DWORD) ((long) delta_x * delta_x)
797 				+ (DWORD) ((long) delta_y * delta_y);
798 		if (desired_speed <= (DWORD) ((UWORD) max_speed * max_speed))
799 			SetVelocityComponents (VelocityPtr, delta_x, delta_y);
800 		else if (TravelAngle == CurrentAngle)
801 		{
802 			SetVelocityComponents (VelocityPtr,
803 					COSINE (CurrentAngle, max_speed),
804 					SINE (CurrentAngle, max_speed));
805 			return (SHIP_AT_MAX_SPEED);
806 		}
807 		else
808 		{
809 			VELOCITY_DESC v;
810 
811 			v = *VelocityPtr;
812 
813 			DeltaVelocityComponents (&v,
814 					COSINE (CurrentAngle, IP_SHIP_THRUST_INCREMENT >> 1)
815 					- COSINE (TravelAngle, IP_SHIP_THRUST_INCREMENT),
816 					SINE (CurrentAngle, IP_SHIP_THRUST_INCREMENT >> 1)
817 					- SINE (TravelAngle, IP_SHIP_THRUST_INCREMENT));
818 			GetCurrentVelocityComponents (&v, &cur_delta_x, &cur_delta_y);
819 			desired_speed =
820 					(DWORD) ((long) cur_delta_x * cur_delta_x)
821 					+ (DWORD) ((long) cur_delta_y * cur_delta_y);
822 			if (desired_speed > (DWORD) ((UWORD) max_speed * max_speed))
823 			{
824 				SetVelocityComponents (VelocityPtr,
825 						COSINE (CurrentAngle, max_speed),
826 						SINE (CurrentAngle, max_speed));
827 				return (SHIP_AT_MAX_SPEED);
828 			}
829 
830 			*VelocityPtr = v;
831 		}
832 
833 		return 0;
834 	}
835 }
836 
837 static void
ProcessShipControls(void)838 ProcessShipControls (void)
839 {
840 	COUNT index;
841 	SIZE delta_x, delta_y;
842 
843 	if (CurrentInputState.key[PlayerControls[0]][KEY_UP])
844 		delta_y = -1;
845 	else
846 		delta_y = 0;
847 
848 	delta_x = 0;
849 	if (CurrentInputState.key[PlayerControls[0]][KEY_LEFT])
850 		delta_x -= 1;
851 	if (CurrentInputState.key[PlayerControls[0]][KEY_RIGHT])
852 		delta_x += 1;
853 
854 	if (delta_x || delta_y < 0)
855 	{
856 		GLOBAL (autopilot.x) = ~0;
857 		GLOBAL (autopilot.y) = ~0;
858 	}
859 	else if (GLOBAL (autopilot.x) != ~0 && GLOBAL (autopilot.y) != ~0)
860 		delta_y = -1;
861 	else
862 		delta_y = 0;
863 
864 	index = GetFrameIndex (GLOBAL (ShipStamp.frame));
865 	if (pSolarSysState->turn_counter)
866 		--pSolarSysState->turn_counter;
867 	else if (delta_x)
868 	{
869 		if (delta_x < 0)
870 			index = NORMALIZE_FACING (index - 1);
871 		else
872 			index = NORMALIZE_FACING (index + 1);
873 
874 		GLOBAL (ShipStamp.frame) =
875 				SetAbsFrameIndex (GLOBAL (ShipStamp.frame), index);
876 
877 		pSolarSysState->turn_counter = pSolarSysState->turn_wait;
878 	}
879 	if (pSolarSysState->thrust_counter)
880 		--pSolarSysState->thrust_counter;
881 	else if (delta_y < 0)
882 	{
883 #define THRUST_WAIT 1
884 		flagship_inertial_thrust (FACING_TO_ANGLE (index));
885 
886 		pSolarSysState->thrust_counter = THRUST_WAIT;
887 	}
888 }
889 
890 static void
enterInnerSystem(PLANET_DESC * planet)891 enterInnerSystem (PLANET_DESC *planet)
892 {
893 #define INNER_ENTRY_DISTANCE  (MIN_MOON_RADIUS + ((MAX_MOONS - 1) \
894 		* MOON_DELTA) + (MOON_DELTA / 4))
895 	COUNT angle;
896 
897 	// Calculate the inner system entry location and facing
898 	angle = FACING_TO_ANGLE (GetFrameIndex (GLOBAL (ShipStamp.frame)))
899 			+ HALF_CIRCLE;
900 	GLOBAL (ShipStamp.origin.x) = (SIS_SCREEN_WIDTH >> 1)
901 			+ COSINE (angle, INNER_ENTRY_DISTANCE);
902 	GLOBAL (ShipStamp.origin.y) = (SIS_SCREEN_HEIGHT >> 1)
903 			+ SINE (angle, INNER_ENTRY_DISTANCE);
904 	if (GLOBAL (ShipStamp.origin.y) < 0)
905 		GLOBAL (ShipStamp.origin.y) = 1;
906 	else if (GLOBAL (ShipStamp.origin.y) >= SIS_SCREEN_HEIGHT)
907 		GLOBAL (ShipStamp.origin.y) =
908 				(SIS_SCREEN_HEIGHT - 1) - 1;
909 
910 	GLOBAL (ip_location) = displayToLocation (
911 			GLOBAL (ShipStamp.origin), MAX_ZOOM_RADIUS);
912 
913 	pSolarSysState->SunDesc[0].location =
914 			planetOuterLocation (planetIndex (pSolarSysState, planet));
915 	ZeroVelocityComponents (&GLOBAL (velocity));
916 
917 	GenerateMoons (pSolarSysState, planet);
918 	pSolarSysState->pBaseDesc = pSolarSysState->MoonDesc;
919 	pSolarSysState->pOrbitalDesc = planet;
920 }
921 
922 static void
leaveInnerSystem(PLANET_DESC * planet)923 leaveInnerSystem (PLANET_DESC *planet)
924 {
925 	COUNT outerPlanetWait;
926 
927 	pSolarSysState->pBaseDesc = pSolarSysState->PlanetDesc;
928 	pSolarSysState->pOrbitalDesc = NULL;
929 
930 	outerPlanetWait = MAKE_WORD (planet - pSolarSysState->PlanetDesc + 1, 0);
931 	GLOBAL (ip_location) = pSolarSysState->SunDesc[0].location;
932 	XFormIPLoc (&GLOBAL (ip_location), &GLOBAL (ShipStamp.origin), TRUE);
933 	ZeroVelocityComponents (&GLOBAL (velocity));
934 
935 	// Now the ship is in outer system (as per game logic)
936 
937 	pSolarSysState->WaitIntersect = outerPlanetWait;
938 	// See if we also intersect with another planet, and if we do,
939 	// disable collisions comletely until we stop intersecting
940 	// with any planet at all.
941 	CheckIntersect ();
942 	if (pSolarSysState->WaitIntersect != outerPlanetWait)
943 		pSolarSysState->WaitIntersect = (COUNT)~0;
944 }
945 
946 static void
enterOrbital(PLANET_DESC * planet)947 enterOrbital (PLANET_DESC *planet)
948 {
949 	ZeroVelocityComponents (&GLOBAL (velocity));
950 	pSolarSysState->pOrbitalDesc = planet;
951 	pSolarSysState->InOrbit = TRUE;
952 }
953 
954 static BOOLEAN
CheckShipLocation(SIZE * newRadius)955 CheckShipLocation (SIZE *newRadius)
956 {
957 	SIZE radius;
958 
959 	radius = pSolarSysState->SunDesc[0].radius;
960 	*newRadius = pSolarSysState->SunDesc[0].radius;
961 
962 	if (GLOBAL (ShipStamp.origin.x) < 0
963 			|| GLOBAL (ShipStamp.origin.x) >= SIS_SCREEN_WIDTH
964 			|| GLOBAL (ShipStamp.origin.y) < 0
965 			|| GLOBAL (ShipStamp.origin.y) >= SIS_SCREEN_HEIGHT)
966 	{
967 		// The ship leaves the screen.
968 		if (!playerInInnerSystem ())
969 		{	// Outer zoom-out transition
970 			if (radius == MAX_ZOOM_RADIUS)
971 			{
972 				// The ship leaves IP.
973 				GLOBAL (CurrentActivity) |= END_INTERPLANETARY;
974 				return FALSE; // no location change
975 			}
976 
977 			*newRadius = FindRadius (GLOBAL (ip_location),
978 					MAX_ZOOM_RADIUS << 1);
979 		}
980 		else
981 		{
982 			leaveInnerSystem (pSolarSysState->pOrbitalDesc);
983 		}
984 
985 		return TRUE;
986 	}
987 
988 	if (!playerInInnerSystem ()
989 			&& pointWithinRect (scaleRect, GLOBAL (ShipStamp.origin)))
990 	{	// Outer zoom-in transition
991 		*newRadius = FindRadius (GLOBAL (ip_location), radius);
992 		return TRUE;
993 	}
994 
995 	if (GLOBAL (autopilot.x) == ~0 && GLOBAL (autopilot.y) == ~0)
996 	{	// Not on autopilot -- may collide with a planet
997 		PLANET_DESC *planet = CheckIntersect ();
998 		if (planet)
999 		{	// Collision with a planet
1000 			if (playerInInnerSystem ())
1001 			{	// Entering planet orbit (scans, etc.)
1002 				enterOrbital (planet);
1003 				return FALSE; // no location change
1004 			}
1005 			else
1006 			{	// Transition to inner system
1007 				enterInnerSystem (planet);
1008 				return TRUE;
1009 			}
1010 		}
1011 	}
1012 
1013 	return FALSE; // no location change
1014 }
1015 
1016 static void
DrawSystemTransition(BOOLEAN inner)1017 DrawSystemTransition (BOOLEAN inner)
1018 {
1019 	SetTransitionSource (NULL);
1020 	BatchGraphics ();
1021 	if (inner)
1022 		DrawInnerSystem ();
1023 	else
1024 		DrawOuterSystem ();
1025 	RedrawQueue (FALSE);
1026 	ScreenTransition (3, NULL);
1027 	UnbatchGraphics ();
1028 }
1029 
1030 static void
TransitionSystemIn(void)1031 TransitionSystemIn (void)
1032 {
1033 	SetContext (SpaceContext);
1034 	DrawSystemTransition (playerInInnerSystem ());
1035 }
1036 
1037 static void
ScaleSystem(SIZE new_radius)1038 ScaleSystem (SIZE new_radius)
1039 {
1040 #ifdef SMOOTH_SYSTEM_ZOOM
1041 	// XXX: This appears to have been an attempt to zoom the system view
1042 	//   in a different way. This code zooms gradually instead of
1043 	//   doing a crossfade from one zoom level to the other.
1044 	// TODO: Do not loop here, and instead increment the zoom level
1045 	//   in IP_frame() with a function drawing the new zoom. The ship
1046 	//   controls are not handled in the loop, and the flagship
1047 	//   can collide with a group while zooming, and that is not handled
1048 	//   100% correctly.
1049 #define NUM_STEPS 10
1050 	COUNT i;
1051 	SIZE old_radius;
1052 	SIZE d, step;
1053 
1054 	old_radius = pSolarSysState->SunDesc[0].radius;
1055 
1056 	assert (old_radius != 0);
1057 	assert (old_radius != new_radius);
1058 
1059 	d = new_radius - old_radius;
1060 	step = d / NUM_STEPS;
1061 
1062 	for (i = 0; i < NUM_STEPS - 1; ++i)
1063 	{
1064 		pSolarSysState->SunDesc[0].radius += step;
1065 		XFormIPLoc (&GLOBAL (ip_location), &GLOBAL (ShipStamp.origin), TRUE);
1066 
1067 		BatchGraphics ();
1068 		DrawOuterSystem ();
1069 		RedrawQueue (FALSE);
1070 		UnbatchGraphics ();
1071 
1072 		SleepThread (ONE_SECOND / 30);
1073 	}
1074 
1075 	// Final zoom step
1076 	pSolarSysState->SunDesc[0].radius = new_radius;
1077 	XFormIPLoc (&GLOBAL (ip_location), &GLOBAL (ShipStamp.origin), TRUE);
1078 
1079 	BatchGraphics ();
1080 	DrawOuterSystem ();
1081 	RedrawQueue (FALSE);
1082 	UnbatchGraphics ();
1083 
1084 #else // !SMOOTH_SYSTEM_ZOOM
1085 	RECT r;
1086 
1087 	pSolarSysState->SunDesc[0].radius = new_radius;
1088 	XFormIPLoc (&GLOBAL (ip_location), &GLOBAL (ShipStamp.origin), TRUE);
1089 
1090 	GetContextClipRect (&r);
1091 	SetTransitionSource (&r);
1092 	BatchGraphics ();
1093 	DrawOuterSystem ();
1094 	RedrawQueue (FALSE);
1095 	ScreenTransition (3, &r);
1096 	UnbatchGraphics ();
1097 #endif // SMOOTH_SYSTEM_ZOOM
1098 }
1099 
1100 static void
RestoreSystemView(void)1101 RestoreSystemView (void)
1102 {
1103 	STAMP s;
1104 
1105 	s.origin.x = 0;
1106 	s.origin.y = 0;
1107 	s.frame = SolarSysFrame;
1108 	DrawStamp (&s);
1109 }
1110 
1111 // Normally called by DoIpFlight() to process a frame
1112 static void
IP_frame(void)1113 IP_frame (void)
1114 {
1115 	BOOLEAN locChange;
1116 	SIZE newRadius;
1117 
1118 	SetContext (SpaceContext);
1119 
1120 	GameClockTick ();
1121 	ProcessShipControls ();
1122 
1123 	locChange = CheckShipLocation (&newRadius);
1124 	if (locChange)
1125 	{
1126 		if (playerInInnerSystem ())
1127 		{	// Entering inner system
1128 			DrawSystemTransition (TRUE);
1129 		}
1130 		else if (pSolarSysState->SunDesc[0].radius == newRadius)
1131 		{	// Leaving inner system to outer
1132 			DrawSystemTransition (FALSE);
1133 		}
1134 		else
1135 		{	// Zooming outer system
1136 			ScaleSystem (newRadius);
1137 		}
1138 	}
1139 	else
1140 	{	// Just flying around, minding own business..
1141 		BatchGraphics ();
1142 		RestoreSystemView ();
1143 		RedrawQueue (FALSE);
1144 		DrawAutoPilotMessage (FALSE);
1145 		UnbatchGraphics ();
1146 	}
1147 
1148 }
1149 
1150 static BOOLEAN
CheckZoomLevel(void)1151 CheckZoomLevel (void)
1152 {
1153 	BOOLEAN InnerSystem;
1154 	POINT shipLoc;
1155 
1156 	InnerSystem = playerInInnerSystem ();
1157 	if (InnerSystem)
1158 		shipLoc = pSolarSysState->SunDesc[0].location;
1159 	else
1160 		shipLoc = GLOBAL (ip_location);
1161 
1162 	pSolarSysState->SunDesc[0].radius = FindRadius (shipLoc,
1163 			MAX_ZOOM_RADIUS << 1);
1164 	if (!InnerSystem)
1165 	{	// Update ship stamp since the radius probably changed
1166 		XFormIPLoc (&shipLoc, &GLOBAL (ShipStamp.origin), TRUE);
1167 	}
1168 
1169 	return InnerSystem;
1170 }
1171 
1172 static void
ValidateOrbits(void)1173 ValidateOrbits (void)
1174 {
1175 	COUNT i;
1176 	PLANET_DESC *planet;
1177 
1178 	for (i = pSolarSysState->SunDesc[0].NumPlanets,
1179 			planet = &pSolarSysState->PlanetDesc[0]; i; --i, ++planet)
1180 	{
1181 		ValidateOrbit (planet, DISPLAY_FACTOR, DISPLAY_FACTOR / 4,
1182 				pSolarSysState->SunDesc[0].radius);
1183 	}
1184 }
1185 
1186 static void
ValidateInnerOrbits(void)1187 ValidateInnerOrbits (void)
1188 {
1189 	COUNT i;
1190 	PLANET_DESC *planet;
1191 
1192 	assert (playerInInnerSystem ());
1193 
1194 	planet = pSolarSysState->pOrbitalDesc;
1195 	ValidateOrbit (planet, DISPLAY_FACTOR * 4, DISPLAY_FACTOR,
1196 			planet->radius);
1197 
1198 	for (i = 0; i < planet->NumPlanets; ++i)
1199 	{
1200 		PLANET_DESC *moon = &pSolarSysState->MoonDesc[i];
1201 		ValidateOrbit (moon, 2, 1, 2);
1202 	}
1203 }
1204 
1205 static void
DrawInnerSystem(void)1206 DrawInnerSystem (void)
1207 {
1208 	ValidateInnerOrbits ();
1209 	DrawSystem (pSolarSysState->pOrbitalDesc->radius, TRUE);
1210 	DrawSISTitle (GLOBAL_SIS (PlanetName));
1211 }
1212 
1213 static void
DrawOuterSystem(void)1214 DrawOuterSystem (void)
1215 {
1216 	ValidateOrbits ();
1217 	DrawSystem (pSolarSysState->SunDesc[0].radius, FALSE);
1218 	DrawHyperCoords (CurStarDescPtr->star_pt);
1219 }
1220 
1221 static void
ResetSolarSys(void)1222 ResetSolarSys (void)
1223 {
1224 	// Originally there was a flash_task test here, however, I found no cases
1225 	// where flash_task could be set at the time of call. The test was
1226 	// probably needed on 3DO when IP_frame() was a task.
1227 	assert (!pSolarSysState->InIpFlight);
1228 
1229 	DrawMenuStateStrings (PM_STARMAP, -(PM_NAVIGATE - PM_SCAN));
1230 
1231 	InitDisplayList ();
1232 	// This also spawns the flagship element
1233 	DoMissions ();
1234 
1235 	// Figure out and note which planet/moon we just left, if any
1236 	// This records any existing collision and prevents the ship
1237 	// from entering planets until a new collision occurs.
1238 	// TODO: this may need logic similar to one in leaveInnerSystem()
1239 	//   for when the ship collides with more than one planet at
1240 	//   the same time. While quite rare, it's still possible.
1241 	CheckIntersect ();
1242 
1243 	pSolarSysState->InIpFlight = TRUE;
1244 
1245 	// Do not start playing the music if we entered the solarsys only
1246 	// to load a game (load invoked from Main menu)
1247 	// XXX: This is quite hacky
1248 	if (!PLRPlaying ((MUSIC_REF)~0) &&
1249 			(LastActivity != CHECK_LOAD || NextActivity))
1250 	{
1251 		PlayMusic (SpaceMusic, TRUE, 1);
1252 	}
1253 }
1254 
1255 static void
EnterPlanetOrbit(void)1256 EnterPlanetOrbit (void)
1257 {
1258 	if (pSolarSysState->InIpFlight)
1259 	{	// This means we hit a planet in IP flight; not a Load into orbit
1260 		FreeSolarSys ();
1261 
1262 		if (worldIsMoon (pSolarSysState, pSolarSysState->pOrbitalDesc))
1263 		{	// Moon -- use its origin
1264 			// XXX: The conversion functions do not error-correct, so the
1265 			//   point we set here will change once flag_ship_preprocess()
1266 			//   in ipdisp.c starts over again.
1267 			GLOBAL (ShipStamp.origin) =
1268 					pSolarSysState->pOrbitalDesc->image.origin;
1269 		}
1270 		else
1271 		{	// Planet -- its origin is for the outer view, so use mid-screen
1272 			GLOBAL (ShipStamp.origin.x) = SIS_SCREEN_WIDTH >> 1;
1273 			GLOBAL (ShipStamp.origin.y) = SIS_SCREEN_HEIGHT >> 1;
1274 		}
1275 	}
1276 
1277 	GetPlanetInfo ();
1278 	(*pSolarSysState->genFuncs->generateOrbital) (pSolarSysState,
1279 			pSolarSysState->pOrbitalDesc);
1280 	LastActivity &= ~(CHECK_LOAD | CHECK_RESTART);
1281 	if ((GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD |
1282 			START_ENCOUNTER)) || GLOBAL_SIS (CrewEnlisted) == (COUNT)~0
1283 			|| GET_GAME_STATE (CHMMR_BOMB_STATE) == 2)
1284 		return;
1285 
1286 	// Implement a to-do in generate.h for a better test
1287 	if (pSolarSysState->TopoFrame)
1288 	{	// We've entered orbit; LoadPlanet() called planet surface-gen code
1289 		PlanetOrbitMenu ();
1290 		FreePlanet ();
1291 	}
1292 	// Otherwise, generateOrbital function started a homeworld conversation,
1293 	// and we did not get to the planet no matter what.
1294 
1295 	// START_ENCOUNTER could be set by Devices menu a number of ways:
1296 	// Talking Pet, Sun Device or a Caster over Chmmr, or
1297 	// a Caster for Ilwrath
1298 	// Could also have blown self up with Utwig Bomb
1299 	if (!(GLOBAL (CurrentActivity) & (START_ENCOUNTER |
1300 			CHECK_ABORT | CHECK_LOAD))
1301 			&& GLOBAL_SIS (CrewEnlisted) != (COUNT)~0)
1302 	{	// Reload the system and return to the inner view
1303 		PLANET_DESC *orbital = LoadSolarSys ();
1304 		assert (!orbital);
1305 		CheckZoomLevel ();
1306 		ValidateOrbits ();
1307 		ValidateInnerOrbits ();
1308 		ResetSolarSys ();
1309 
1310 		RepairSISBorder ();
1311 		TransitionSystemIn ();
1312 	}
1313 }
1314 
1315 static void
InitSolarSys(void)1316 InitSolarSys (void)
1317 {
1318 	BOOLEAN InnerSystem;
1319 	BOOLEAN Reentry;
1320 	PLANET_DESC *orbital;
1321 
1322 
1323 	LoadIPData ();
1324 	LoadLanderData ();
1325 
1326 	Reentry = (GLOBAL (ShipFacing) != 0);
1327 	if (!Reentry)
1328 	{
1329 		GLOBAL (autopilot.x) = ~0;
1330 		GLOBAL (autopilot.y) = ~0;
1331 
1332 		GLOBAL (ShipStamp.origin.x) = SIS_SCREEN_WIDTH >> 1;
1333 		GLOBAL (ShipStamp.origin.y) = SIS_SCREEN_HEIGHT - 2;
1334 
1335 		GLOBAL (ip_location) = displayToLocation (GLOBAL (ShipStamp.origin),
1336 				MAX_ZOOM_RADIUS);
1337 	}
1338 
1339 
1340 	StarsFrame = CreateStarBackGround ();
1341 
1342 	SetContext (SpaceContext);
1343 	SetContextFGFrame (Screen);
1344 	SetContextBackGroundColor (BLACK_COLOR);
1345 
1346 
1347 	orbital = LoadSolarSys ();
1348 	InnerSystem = CheckZoomLevel ();
1349 	ValidateOrbits ();
1350 	if (InnerSystem)
1351 		ValidateInnerOrbits ();
1352 
1353 	if (Reentry)
1354 	{
1355 		(*pSolarSysState->genFuncs->reinitNpcs) (pSolarSysState);
1356 	}
1357 	else
1358 	{
1359 		EncounterRace = -1;
1360 		EncounterGroup = 0;
1361 		GLOBAL (BattleGroupRef) = 0;
1362 		ReinitQueue (&GLOBAL (ip_group_q));
1363 		ReinitQueue (&GLOBAL (npc_built_ship_q));
1364 		(*pSolarSysState->genFuncs->initNpcs) (pSolarSysState);
1365 	}
1366 
1367 	if (orbital)
1368 	{
1369 		enterOrbital (orbital);
1370 	}
1371 	else
1372 	{	// Draw the borders, the system (inner or outer) and fade/transition
1373 		SetContext (SpaceContext);
1374 
1375 		SetTransitionSource (NULL);
1376 		BatchGraphics ();
1377 
1378 		DrawSISFrame ();
1379 		DrawSISMessage (NULL);
1380 
1381 		ResetSolarSys ();
1382 
1383 		if (LastActivity == (CHECK_LOAD | CHECK_RESTART))
1384 		{	// Starting a new game, NOT from load!
1385 			// We have to fade the screen in from intro or menu
1386 			DrawOuterSystem ();
1387 			RedrawQueue (FALSE);
1388 			UnbatchGraphics ();
1389 			FadeScreen (FadeAllToColor, ONE_SECOND / 2);
1390 
1391 			LastActivity = 0;
1392 		}
1393 		else if (LastActivity == CHECK_LOAD && !NextActivity)
1394 		{	// Called just to load a game; invoked from Main menu
1395 			// No point in drawing anything
1396 			UnbatchGraphics ();
1397 		}
1398 		else
1399 		{	// Entered a new system, or loaded into inner or outer
1400 			if (InnerSystem)
1401 				DrawInnerSystem ();
1402 			else
1403 				DrawOuterSystem ();
1404 			RedrawQueue (FALSE);
1405 			ScreenTransition (3, NULL);
1406 			UnbatchGraphics ();
1407 
1408 			LastActivity &= ~CHECK_LOAD;
1409 		}
1410 	}
1411 }
1412 
1413 static void
endInterPlanetary(void)1414 endInterPlanetary (void)
1415 {
1416 	GLOBAL (CurrentActivity) &= ~END_INTERPLANETARY;
1417 
1418 	if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
1419 	{
1420 		// These are game state changing ops and so cannot be
1421 		// called once another game has been loaded!
1422 		(*pSolarSysState->genFuncs->uninitNpcs) (pSolarSysState);
1423 		SET_GAME_STATE (USED_BROADCASTER, 0);
1424 	}
1425 }
1426 
1427 // Find the closest planet to a point, in interplanetary.
1428 static PLANET_DESC *
closestPlanetInterPlanetary(const POINT * point)1429 closestPlanetInterPlanetary (const POINT *point)
1430 {
1431 	BYTE i;
1432 	BYTE numPlanets;
1433 	DWORD bestDistSquared;
1434 	PLANET_DESC *bestPlanet = NULL;
1435 
1436 	assert(pSolarSysState != NULL);
1437 
1438 	numPlanets = pSolarSysState->SunDesc[0].NumPlanets;
1439 
1440 	bestDistSquared = (DWORD) -1;  // Maximum value of DWORD.
1441 	for (i = 0; i < numPlanets; i++)
1442 	{
1443 		PLANET_DESC *planet = &pSolarSysState->PlanetDesc[i];
1444 
1445 		SIZE dx = point->x - planet->image.origin.x;
1446 		SIZE dy = point->y - planet->image.origin.y;
1447 
1448 		DWORD distSquared = (DWORD) ((long) dx * dx + (long) dy * dy);
1449 		if (distSquared < bestDistSquared)
1450 		{
1451 			bestDistSquared = distSquared;
1452 			bestPlanet = planet;
1453 		}
1454 	}
1455 
1456 	return bestPlanet;
1457 }
1458 
1459 static void
UninitSolarSys(void)1460 UninitSolarSys (void)
1461 {
1462 	FreeSolarSys ();
1463 
1464 //FreeLanderData ();
1465 //FreeIPData ();
1466 
1467 	DestroyDrawable (ReleaseDrawable (StarsFrame));
1468 	StarsFrame = NULL;
1469 
1470 	if (GLOBAL (CurrentActivity) & END_INTERPLANETARY)
1471 	{
1472 		endInterPlanetary ();
1473 		return;
1474 	}
1475 
1476 	if ((GLOBAL (CurrentActivity) & START_ENCOUNTER) && EncounterGroup)
1477 	{
1478 		GetGroupInfo (GLOBAL (BattleGroupRef), EncounterGroup);
1479 		// Generate the encounter location name based on the closest planet
1480 
1481 		if (GLOBAL (ip_planet) == 0)
1482 		{
1483 			PLANET_DESC *planet =
1484 					closestPlanetInterPlanetary (&GLOBAL (ShipStamp.origin));
1485 
1486 			(*pSolarSysState->genFuncs->generateName) (
1487 					pSolarSysState, planet);
1488 		}
1489 	}
1490 }
1491 
1492 static void
CalcSunSize(PLANET_DESC * pSunDesc,SIZE radius)1493 CalcSunSize (PLANET_DESC *pSunDesc, SIZE radius)
1494 {
1495 	SIZE index = 0;
1496 
1497 	if (radius <= (MAX_ZOOM_RADIUS >> 1))
1498 	{
1499 		++index;
1500 		if (radius <= (MAX_ZOOM_RADIUS >> 2))
1501 			++index;
1502 	}
1503 
1504 	pSunDesc->image.origin.x = SIS_SCREEN_WIDTH >> 1;
1505 	pSunDesc->image.origin.y = SIS_SCREEN_HEIGHT >> 1;
1506 	pSunDesc->image.frame = SetRelFrameIndex (SunFrame, index);
1507 }
1508 
1509 static void
SetPlanetColorMap(PLANET_DESC * planet)1510 SetPlanetColorMap (PLANET_DESC *planet)
1511 {
1512 	COUNT index = planet->data_index & ~WORLD_TYPE_SPECIAL;
1513 	assert (index < NUMBER_OF_PLANET_TYPES);
1514 	SetColorMap (GetColorMapAddress (SetAbsColorMapIndex (OrbitalCMap,
1515 			PLANCOLOR (PlanData[index].Type))));
1516 }
1517 
1518 static void
DrawInnerPlanets(PLANET_DESC * planet)1519 DrawInnerPlanets (PLANET_DESC *planet)
1520 {
1521 	STAMP s;
1522 	COUNT i;
1523 	PLANET_DESC *moon;
1524 
1525 	// Draw the planet image
1526 	SetPlanetColorMap (planet);
1527 	s.origin.x = SIS_SCREEN_WIDTH >> 1;
1528 	s.origin.y = SIS_SCREEN_HEIGHT >> 1;
1529 	s.frame = planet->image.frame;
1530 
1531 	i = planet->data_index & ~WORLD_TYPE_SPECIAL;
1532 	if (i < NUMBER_OF_PLANET_TYPES
1533 			&& (planet->data_index & PLANET_SHIELDED))
1534 	{	// Shielded world looks "shielded" in inner view
1535 		s.frame = SetAbsFrameIndex (SpaceJunkFrame, 17);
1536 	}
1537 	DrawStamp (&s);
1538 
1539 	// Draw the moon images
1540 	for (i = planet->NumPlanets, moon = pSolarSysState->MoonDesc;
1541 			i; --i, ++moon)
1542 	{
1543 		if (!(moon->data_index & WORLD_TYPE_SPECIAL))
1544 			SetPlanetColorMap (moon);
1545 		DrawStamp (&moon->image);
1546 	}
1547 }
1548 
1549 static void
DrawSystem(SIZE radius,BOOLEAN IsInnerSystem)1550 DrawSystem (SIZE radius, BOOLEAN IsInnerSystem)
1551 {
1552 	BYTE i;
1553 	PLANET_DESC *pCurDesc;
1554 	PLANET_DESC *pBaseDesc;
1555 	CONTEXT oldContext;
1556 	STAMP s;
1557 
1558 	if (!SolarSysFrame)
1559 	{	// Create the saved view graphic
1560 		RECT clipRect;
1561 
1562 		GetContextClipRect (&clipRect);
1563 		SolarSysFrame = CaptureDrawable (CreateDrawable (WANT_PIXMAP,
1564 				clipRect.extent.width, clipRect.extent.height, 1));
1565 	}
1566 
1567 	oldContext = SetContext (OffScreenContext);
1568 	SetContextFGFrame (SolarSysFrame);
1569 	SetContextClipRect (NULL);
1570 
1571 	DrawStarBackGround ();
1572 
1573 	pBaseDesc = pSolarSysState->pBaseDesc;
1574 	if (IsInnerSystem)
1575 	{	// Draw the inner system view *planet's* orbit segment
1576 		pCurDesc = pSolarSysState->pOrbitalDesc;
1577 		DrawOrbit (pCurDesc, DISPLAY_FACTOR * 4, DISPLAY_FACTOR, radius);
1578 	}
1579 
1580 	// Draw the planet orbits or moon orbits
1581 	for (i = pBaseDesc->pPrevDesc->NumPlanets, pCurDesc = pBaseDesc;
1582 			i; --i, ++pCurDesc)
1583 	{
1584 		if (IsInnerSystem)
1585 			DrawOrbit (pCurDesc, 2, 1, 2);
1586 		else
1587 			DrawOrbit (pCurDesc, DISPLAY_FACTOR, DISPLAY_FACTOR / 4,
1588 					radius);
1589 	}
1590 
1591 	if (IsInnerSystem)
1592 	{	// Draw the inner system view
1593 		DrawInnerPlanets (pSolarSysState->pOrbitalDesc);
1594 	}
1595 	else
1596 	{	// Draw the outer system view
1597 		SIZE index;
1598 
1599 		CalcSunSize (&pSolarSysState->SunDesc[0], radius);
1600 
1601 		index = pSolarSysState->FirstPlanetIndex;
1602 		for (;;)
1603 		{
1604 			pCurDesc = &pSolarSysState->PlanetDesc[index];
1605 			if (pCurDesc == &pSolarSysState->SunDesc[0])
1606 			{	// It's a sun
1607 				SetColorMap (GetColorMapAddress (SetAbsColorMapIndex (
1608 						SunCMap, STAR_COLOR (CurStarDescPtr->Type))));
1609 			}
1610 			else
1611 			{	// It's a planet
1612 				SetPlanetColorMap (pCurDesc);
1613 			}
1614 			DrawStamp (&pCurDesc->image);
1615 
1616 			if (index == pSolarSysState->LastPlanetIndex)
1617 				break;
1618 			index = pCurDesc->NextIndex;
1619 		}
1620 	}
1621 
1622 	SetContext (oldContext);
1623 
1624 	// Draw the now-saved view graphic
1625 	s.origin.x = 0;
1626 	s.origin.y = 0;
1627 	s.frame = SolarSysFrame;
1628 	DrawStamp (&s);
1629 }
1630 
1631 void
DrawStarBackGround(void)1632 DrawStarBackGround (void)
1633 {
1634 	STAMP s;
1635 
1636 	s.origin.x = 0;
1637 	s.origin.y = 0;
1638 	s.frame = StarsFrame;
1639 	DrawStamp (&s);
1640 }
1641 
1642 static FRAME
CreateStarBackGround(void)1643 CreateStarBackGround (void)
1644 {
1645 	COUNT i, j;
1646 	DWORD rand_val;
1647 	STAMP s;
1648 	CONTEXT oldContext;
1649 	RECT clipRect;
1650 	FRAME frame;
1651 
1652 	// Use SpaceContext to find out the dimensions of the background
1653 	oldContext = SetContext (SpaceContext);
1654 	GetContextClipRect (&clipRect);
1655 
1656 	// Prepare a pre-drawn stars frame for this system
1657 	frame = CaptureDrawable (CreateDrawable (WANT_PIXMAP,
1658 			clipRect.extent.width, clipRect.extent.height, 1));
1659 	SetContext (OffScreenContext);
1660 	SetContextFGFrame (frame);
1661 	SetContextClipRect (NULL);
1662 	SetContextBackGroundColor (BLACK_COLOR);
1663 
1664 	ClearDrawable ();
1665 
1666 	RandomContext_SeedRandom (SysGenRNG, GetRandomSeedForStar (CurStarDescPtr));
1667 
1668 #define NUM_DIM_PIECES 8
1669 	s.frame = SpaceJunkFrame;
1670 	for (i = 0; i < NUM_DIM_PIECES; ++i)
1671 	{
1672 #define NUM_DIM_DRAWN 5
1673 		for (j = 0; j < NUM_DIM_DRAWN; ++j)
1674 		{
1675 			rand_val = RandomContext_Random (SysGenRNG);
1676 			s.origin.x = LOWORD (rand_val) % SIS_SCREEN_WIDTH;
1677 			s.origin.y = HIWORD (rand_val) % SIS_SCREEN_HEIGHT;
1678 
1679 			DrawStamp (&s);
1680 		}
1681 		s.frame = IncFrameIndex (s.frame);
1682 	}
1683 #define NUM_BRT_PIECES 8
1684 	for (i = 0; i < NUM_BRT_PIECES; ++i)
1685 	{
1686 #define NUM_BRT_DRAWN 30
1687 		for (j = 0; j < NUM_BRT_DRAWN; ++j)
1688 		{
1689 			rand_val = RandomContext_Random (SysGenRNG);
1690 			s.origin.x = LOWORD (rand_val) % SIS_SCREEN_WIDTH;
1691 			s.origin.y = HIWORD (rand_val) % SIS_SCREEN_HEIGHT;
1692 
1693 			DrawStamp (&s);
1694 		}
1695 		s.frame = IncFrameIndex (s.frame);
1696 	}
1697 
1698 	SetContext (oldContext);
1699 
1700 	return frame;
1701 }
1702 
1703 void
XFormIPLoc(POINT * pIn,POINT * pOut,BOOLEAN ToDisplay)1704 XFormIPLoc (POINT *pIn, POINT *pOut, BOOLEAN ToDisplay)
1705 {
1706 	if (ToDisplay)
1707 		*pOut = locationToDisplay (*pIn, pSolarSysState->SunDesc[0].radius);
1708 	else
1709 		*pOut = displayToLocation (*pIn, pSolarSysState->SunDesc[0].radius);
1710 }
1711 
1712 void
ExploreSolarSys(void)1713 ExploreSolarSys (void)
1714 {
1715 	SOLARSYS_STATE SolarSysState;
1716 
1717 	if (CurStarDescPtr == 0)
1718 	{
1719 		POINT universe;
1720 
1721 		universe.x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x));
1722 		universe.y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y));
1723 		CurStarDescPtr = FindStar (0, &universe, 1, 1);
1724 		if (!CurStarDescPtr)
1725 		{
1726 			log_add (log_Fatal, "ExploreSolarSys(): do not know where you are!");
1727 			explode ();
1728 		}
1729 	}
1730 	GLOBAL_SIS (log_x) = UNIVERSE_TO_LOGX (CurStarDescPtr->star_pt.x);
1731 	GLOBAL_SIS (log_y) = UNIVERSE_TO_LOGY (CurStarDescPtr->star_pt.y);
1732 
1733 	pSolarSysState = &SolarSysState;
1734 
1735 	memset (pSolarSysState, 0, sizeof (*pSolarSysState));
1736 
1737 	SolarSysState.genFuncs = getGenerateFunctions (CurStarDescPtr->Index);
1738 
1739 	InitSolarSys ();
1740 	SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
1741 	SolarSysState.InputFunc = DoIpFlight;
1742 	DoInput (&SolarSysState, FALSE);
1743 	UninitSolarSys ();
1744 	pSolarSysState = 0;
1745 }
1746 
1747 UNICODE *
GetNamedPlanetaryBody(void)1748 GetNamedPlanetaryBody (void)
1749 {
1750 	if (!CurStarDescPtr || !playerInSolarSystem () || !playerInInnerSystem ())
1751 		return NULL; // Not inside an inner system, so no name
1752 
1753 	assert (pSolarSysState->pOrbitalDesc != NULL);
1754 
1755 	if (CurStarDescPtr->Index == SOL_DEFINED)
1756 	{	// Planets and moons in Sol
1757 		int planet;
1758 		int moon;
1759 
1760 		planet = planetIndex (pSolarSysState, pSolarSysState->pOrbitalDesc);
1761 
1762 		if (worldIsPlanet (pSolarSysState, pSolarSysState->pOrbitalDesc))
1763 		{	// A planet
1764 			return GAME_STRING (PLANET_NUMBER_BASE + planet);
1765 		}
1766 
1767 		// Moons
1768 		moon = moonIndex (pSolarSysState, pSolarSysState->pOrbitalDesc);
1769 		switch (planet)
1770 		{
1771 			case 2: // Earth
1772 				switch (moon)
1773 				{
1774 					case 0: // Starbase
1775 						return GAME_STRING (STARBASE_STRING_BASE + 0);
1776 					case 1: // Luna
1777 						return GAME_STRING (PLANET_NUMBER_BASE + 9);
1778 				}
1779 				break;
1780 			case 4: // Jupiter
1781 				switch (moon)
1782 				{
1783 					case 0: // Io
1784 						return GAME_STRING (PLANET_NUMBER_BASE + 10);
1785 					case 1: // Europa
1786 						return GAME_STRING (PLANET_NUMBER_BASE + 11);
1787 					case 2: // Ganymede
1788 						return GAME_STRING (PLANET_NUMBER_BASE + 12);
1789 					case 3: // Callisto
1790 						return GAME_STRING (PLANET_NUMBER_BASE + 13);
1791 				}
1792 				break;
1793 			case 5: // Saturn
1794 				if (moon == 0) // Titan
1795 					return GAME_STRING (PLANET_NUMBER_BASE + 14);
1796 				break;
1797 			case 7: // Neptune
1798 				if (moon == 0) // Triton
1799 					return GAME_STRING (PLANET_NUMBER_BASE + 15);
1800 				break;
1801 		}
1802 	}
1803 	else if (CurStarDescPtr->Index == SPATHI_DEFINED)
1804 	{
1805 		if (matchWorld (pSolarSysState, pSolarSysState->pOrbitalDesc,
1806 				0, MATCH_PLANET))
1807 		{
1808 #ifdef NOTYET
1809 			return "Spathiwa";
1810 #endif // NOTYET
1811 		}
1812 	}
1813 	else if (CurStarDescPtr->Index == SAMATRA_DEFINED)
1814 	{
1815 		if (matchWorld (pSolarSysState, pSolarSysState->pOrbitalDesc, 4, 0))
1816 		{	// Sa-Matra
1817 			return GAME_STRING (PLANET_NUMBER_BASE + 32);
1818 		}
1819 	}
1820 
1821 	return NULL;
1822 }
1823 
1824 void
GetPlanetOrMoonName(UNICODE * buf,COUNT bufsize)1825 GetPlanetOrMoonName (UNICODE *buf, COUNT bufsize)
1826 {
1827 	UNICODE *named;
1828 	int moon;
1829 	int i;
1830 
1831 	named = GetNamedPlanetaryBody ();
1832 	if (named)
1833 	{
1834 		utf8StringCopy (buf, bufsize, named);
1835 		return;
1836 	}
1837 
1838 	// Either not named or we already have a name
1839 	utf8StringCopy (buf, bufsize, GLOBAL_SIS (PlanetName));
1840 
1841 	if (!playerInSolarSystem () || !playerInInnerSystem () ||
1842 			worldIsPlanet (pSolarSysState, pSolarSysState->pOrbitalDesc))
1843 	{	// Outer or inner system or orbiting a planet
1844 		return;
1845 	}
1846 
1847 	// Orbiting an unnamed moon
1848 	i = strlen (buf);
1849 	buf += i;
1850 	bufsize -= i;
1851 	moon = moonIndex (pSolarSysState, pSolarSysState->pOrbitalDesc);
1852 	if (bufsize >= 3)
1853 	{
1854 		snprintf (buf, bufsize, "-%c", 'A' + moon);
1855 		buf[bufsize - 1] = '\0';
1856 	}
1857 }
1858 
1859 void
SaveSolarSysLocation(void)1860 SaveSolarSysLocation (void)
1861 {
1862 	assert (playerInSolarSystem ());
1863 
1864 	// This is a two-stage saving procedure
1865 	// Stage 1: called when saving from inner/outer view
1866 	// Stage 2: called when saving from orbital
1867 
1868 	if (!playerInPlanetOrbit ())
1869 	{
1870 		saveNonOrbitalLocation ();
1871 	}
1872 	else
1873 	{	// In orbit around a planet.
1874 		BYTE moon;
1875 
1876 		// Update the starinfo.dat file if necessary.
1877 		if (GET_GAME_STATE (PLANETARY_CHANGE))
1878 		{
1879 			PutPlanetInfo ();
1880 			SET_GAME_STATE (PLANETARY_CHANGE, 0);
1881 		}
1882 
1883 		// GLOBAL (ip_planet) is already set
1884 		assert (GLOBAL (ip_planet) != 0);
1885 
1886 		// has to be at least 1 because code tests for in_orbit!=0
1887 		moon = 1; /* the planet itself */
1888 		if (worldIsMoon (pSolarSysState, pSolarSysState->pOrbitalDesc))
1889 		{
1890 			moon += moonIndex (pSolarSysState, pSolarSysState->pOrbitalDesc);
1891 			// +1 because moons have to be 1-based
1892 			moon += 1;
1893 		}
1894 		GLOBAL (in_orbit) = moon;
1895 	}
1896 }
1897 
1898 static BOOLEAN
DoSolarSysMenu(MENU_STATE * pMS)1899 DoSolarSysMenu (MENU_STATE *pMS)
1900 {
1901 	BOOLEAN select = PulsedInputState.menu[KEY_MENU_SELECT];
1902 	BOOLEAN handled;
1903 
1904 	if ((GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))
1905 			|| GLOBAL_SIS (CrewEnlisted) == (COUNT)~0)
1906 		return FALSE;
1907 
1908 	handled = DoMenuChooser (pMS, PM_STARMAP);
1909 	if (handled)
1910 		return TRUE;
1911 
1912 	if (LastActivity == CHECK_LOAD)
1913 		select = TRUE; // Selected LOAD from main menu
1914 
1915 	if (!select)
1916 		return TRUE;
1917 
1918 	SetFlashRect (NULL);
1919 
1920 	switch (pMS->CurState)
1921 	{
1922 		case EQUIP_DEVICE:
1923 			select = DevicesMenu ();
1924 			if (GLOBAL (CurrentActivity) & START_ENCOUNTER)
1925 			{	// Invoked Talking Pet or a Caster for Ilwrath
1926 				// Going into conversation
1927 				return FALSE;
1928 			}
1929 			break;
1930 		case CARGO:
1931 			CargoMenu ();
1932 			break;
1933 		case ROSTER:
1934 			select = RosterMenu ();
1935 			break;
1936 		case GAME_MENU:
1937 			if (!GameOptions ())
1938 				return FALSE; // abort or load
1939 			break;
1940 		case STARMAP:
1941 			StarMap ();
1942 			if (GLOBAL (CurrentActivity) & CHECK_ABORT)
1943 				return FALSE;
1944 
1945 			TransitionSystemIn ();
1946 			// Fall through !!!
1947 		case NAVIGATION:
1948 			return FALSE;
1949 	}
1950 
1951 	if (!(GLOBAL (CurrentActivity) & CHECK_ABORT))
1952 	{
1953 		if (select)
1954 		{	// 3DO menu jumps to NAVIGATE after a successful submenu run
1955 			if (optWhichMenu != OPT_PC)
1956 				pMS->CurState = NAVIGATION;
1957 			DrawMenuStateStrings (PM_STARMAP, pMS->CurState);
1958 		}
1959 		SetFlashRect (SFR_MENU_3DO);
1960 	}
1961 
1962 	return TRUE;
1963 }
1964 
1965 static void
SolarSysMenu(void)1966 SolarSysMenu (void)
1967 {
1968 	MENU_STATE MenuState;
1969 
1970 	memset (&MenuState, 0, sizeof MenuState);
1971 
1972 	if (LastActivity == CHECK_LOAD)
1973 	{	// Selected LOAD from main menu
1974 		MenuState.CurState = GAME_MENU;
1975 	}
1976 	else
1977 	{
1978 		DrawMenuStateStrings (PM_STARMAP, STARMAP);
1979 		MenuState.CurState = STARMAP;
1980 	}
1981 
1982 	DrawStatusMessage (NULL);
1983 	SetFlashRect (SFR_MENU_3DO);
1984 
1985 	SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
1986 	MenuState.InputFunc = DoSolarSysMenu;
1987 	DoInput (&MenuState, TRUE);
1988 
1989 	DrawMenuStateStrings (PM_STARMAP, -NAVIGATION);
1990 }
1991 
1992 static BOOLEAN
DoIpFlight(SOLARSYS_STATE * pSS)1993 DoIpFlight (SOLARSYS_STATE *pSS)
1994 {
1995 	static TimeCount NextTime;
1996 	BOOLEAN cancel = PulsedInputState.menu[KEY_MENU_CANCEL];
1997 
1998 	if (pSS->InOrbit)
1999 	{	// CheckShipLocation() or InitSolarSys() sent us to orbital
2000 		EnterPlanetOrbit ();
2001 		SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
2002 		pSS->InOrbit = FALSE;
2003 	}
2004 	else if (cancel || LastActivity == CHECK_LOAD)
2005 	{
2006 		SolarSysMenu ();
2007 		SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
2008 	}
2009 	else
2010 	{
2011 		assert (pSS->InIpFlight);
2012 		IP_frame ();
2013 		SleepThreadUntil (NextTime);
2014 		NextTime = GetTimeCounter () + IP_FRAME_RATE;
2015 	}
2016 
2017 	return (!(GLOBAL (CurrentActivity)
2018 			& (START_ENCOUNTER | END_INTERPLANETARY
2019 			| CHECK_ABORT | CHECK_LOAD))
2020 			&& GLOBAL_SIS (CrewEnlisted) != (COUNT)~0);
2021 }
2022