summaryrefslogtreecommitdiff
path: root/src/uqm/uqmdebug.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/uqm/uqmdebug.c')
-rw-r--r--src/uqm/uqmdebug.c1926
1 files changed, 1926 insertions, 0 deletions
diff --git a/src/uqm/uqmdebug.c b/src/uqm/uqmdebug.c
new file mode 100644
index 0000000..4113a5d
--- /dev/null
+++ b/src/uqm/uqmdebug.c
@@ -0,0 +1,1926 @@
+/*
+ * 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.
+ */
+
+#if defined(DEBUG) || defined(USE_DEBUG_KEY)
+
+#include "uqmdebug.h"
+
+#include "build.h"
+#include "colors.h"
+#include "controls.h"
+#include "clock.h"
+#include "starmap.h"
+#include "element.h"
+#include "sis.h"
+#include "status.h"
+#include "gamestr.h"
+#include "gameev.h"
+#include "gendef.h"
+#include "globdata.h"
+#include "planets/lifeform.h"
+#include "planets/scan.h"
+#include "races.h"
+#include "setup.h"
+#include "state.h"
+#include "libs/mathlib.h"
+
+#include <stdio.h>
+#include <errno.h>
+
+
+static void dumpEventCallback (const EVENT *eventPtr, void *arg);
+
+static void starRecurse (STAR_DESC *star, void *arg);
+static void planetRecurse (STAR_DESC *star, SOLARSYS_STATE *system,
+ PLANET_DESC *planet, void *arg);
+static void moonRecurse (STAR_DESC *star, SOLARSYS_STATE *system,
+ PLANET_DESC *planet, PLANET_DESC *moon, void *arg);
+
+static void dumpSystemCallback (const STAR_DESC *star,
+ const SOLARSYS_STATE *system, void *arg);
+static void dumpPlanetCallback (const PLANET_DESC *planet, void *arg);
+static void dumpMoonCallback (const PLANET_DESC *moon, void *arg);
+static void dumpWorld (FILE *out, const PLANET_DESC *world);
+
+typedef struct TallyResourcesArg TallyResourcesArg;
+static void tallySystemPreCallback (const STAR_DESC *star, const
+ SOLARSYS_STATE *system, void *arg);
+static void tallySystemPostCallback (const STAR_DESC *star, const
+ SOLARSYS_STATE *system, void *arg);
+static void tallyPlanetCallback (const PLANET_DESC *planet, void *arg);
+static void tallyMoonCallback (const PLANET_DESC *moon, void *arg);
+static void tallyResourcesWorld (TallyResourcesArg *arg,
+ const PLANET_DESC *world);
+
+static void dumpPlanetTypeCallback (int index, const PlanetFrame *planet,
+ void *arg);
+
+
+BOOLEAN instantMove = FALSE;
+BOOLEAN disableInteractivity = FALSE;
+void (* volatile debugHook) (void) = NULL;
+
+
+// Must be called on the Starcon2Main thread.
+// This function is called synchronously wrt the game logic thread.
+void
+debugKeyPressedSynchronous (void)
+{
+ // State modifying:
+ equipShip ();
+ giveDevices ();
+
+ // Give the player the ships you can't ally with under normal
+ // conditions.
+ clearEscorts ();
+ AddEscortShips (ARILOU_SHIP, 1);
+ AddEscortShips (PKUNK_SHIP, 1);
+ AddEscortShips (VUX_SHIP, 1);
+ AddEscortShips (YEHAT_SHIP, 1);
+ AddEscortShips (MELNORME_SHIP, 1);
+ AddEscortShips (DRUUGE_SHIP, 1);
+ AddEscortShips (ILWRATH_SHIP, 1);
+ AddEscortShips (MYCON_SHIP, 1);
+ AddEscortShips (SLYLANDRO_SHIP, 1);
+ AddEscortShips (UMGAH_SHIP, 1);
+ AddEscortShips (URQUAN_SHIP, 1);
+ AddEscortShips (BLACK_URQUAN_SHIP, 1);
+
+ resetCrewBattle ();
+ resetEnergyBattle ();
+ instantMove = !instantMove;
+ showSpheres ();
+ activateAllShips ();
+// forwardToNextEvent (TRUE);
+// SET_GAME_STATE (MELNORME_CREDIT1, 100);
+// GLOBAL_SIS (ResUnits) = 100000;
+
+ // Informational:
+// dumpEvents (stderr);
+
+ // Graphical and textual:
+// debugContexts();
+}
+
+// Can be called on any thread, but usually on main()
+// This function is called asynchronously wrt the game logic thread,
+// which means locking applies. Use carefully.
+// TODO: Once game logic thread is purged of graphics and clock locks,
+// this function may not call graphics and game clock functions at all.
+void
+debugKeyPressed (void)
+{
+ // Tests
+// Scale_PerfTest ();
+
+ // Informational:
+// dumpStrings (stdout);
+// dumpPlanetTypes(stderr);
+// debugHook = dumpUniverseToFile;
+ // This will cause dumpUniverseToFile to be called from the
+ // Starcon2Main loop. Calling it from here would give threading
+ // problems.
+// debugHook = tallyResourcesToFile;
+ // This will cause tallyResourcesToFile to be called from the
+ // Starcon2Main loop. Calling it from here would give threading
+ // problems.
+
+ // Interactive:
+// uio_debugInteractive(stdin, stdout, stderr);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+// Fast forwards to the next event.
+// If skipHEE is set, HYPERSPACE_ENCOUNTER_EVENTs are skipped.
+// Must be called from the Starcon2Main thread.
+// TODO: LockGameClock may be removed since it is only
+// supposed to be called synchronously wrt the game logic thread.
+void
+forwardToNextEvent (BOOLEAN skipHEE)
+{
+ HEVENT hEvent;
+ EVENT *EventPtr;
+ COUNT year, month, day;
+ // time of next event
+ BOOLEAN done;
+
+ if (!GameClockRunning ())
+ return;
+
+ LockGameClock ();
+
+ done = !skipHEE;
+ do {
+ hEvent = GetHeadEvent ();
+ if (hEvent == 0)
+ return;
+ LockEvent (hEvent, &EventPtr);
+ if (EventPtr->func_index != HYPERSPACE_ENCOUNTER_EVENT)
+ done = TRUE;
+ year = EventPtr->year_index;
+ month = EventPtr->month_index;
+ day = EventPtr->day_index;
+ UnlockEvent (hEvent);
+
+ for (;;) {
+ if (GLOBAL (GameClock.year_index) > year ||
+ (GLOBAL (GameClock.year_index) == year &&
+ (GLOBAL (GameClock.month_index) > month ||
+ (GLOBAL (GameClock.month_index) == month &&
+ GLOBAL (GameClock.day_index) >= day))))
+ break;
+
+ MoveGameClockDays (1);
+ }
+ } while (!done);
+
+ UnlockGameClock ();
+}
+
+const char *
+eventName (BYTE func_index)
+{
+ switch (func_index) {
+ case ARILOU_ENTRANCE_EVENT:
+ return "ARILOU_ENTRANCE_EVENT";
+ case ARILOU_EXIT_EVENT:
+ return "ARILOU_EXIT_EVENT";
+ case HYPERSPACE_ENCOUNTER_EVENT:
+ return "HYPERSPACE_ENCOUNTER_EVENT";
+ case KOHR_AH_VICTORIOUS_EVENT:
+ return "KOHR_AH_VICTORIOUS_EVENT";
+ case ADVANCE_PKUNK_MISSION:
+ return "ADVANCE_PKUNK_MISSION";
+ case ADVANCE_THRADD_MISSION:
+ return "ADVANCE_THRADD_MISSION";
+ case ZOQFOT_DISTRESS_EVENT:
+ return "ZOQFOT_DISTRESS";
+ case ZOQFOT_DEATH_EVENT:
+ return "ZOQFOT_DEATH_EVENT";
+ case SHOFIXTI_RETURN_EVENT:
+ return "SHOFIXTI_RETURN_EVENT";
+ case ADVANCE_UTWIG_SUPOX_MISSION:
+ return "ADVANCE_UTWIG_SUPOX_MISSION";
+ case KOHR_AH_GENOCIDE_EVENT:
+ return "KOHR_AH_GENOCIDE_EVENT";
+ case SPATHI_SHIELD_EVENT:
+ return "SPATHI_SHIELD_EVENT";
+ case ADVANCE_ILWRATH_MISSION:
+ return "ADVANCE_ILWRATH_MISSION";
+ case ADVANCE_MYCON_MISSION:
+ return "ADVANCE_MYCON_MISSION";
+ case ARILOU_UMGAH_CHECK:
+ return "ARILOU_UMGAH_CHECK";
+ case YEHAT_REBEL_EVENT:
+ return "YEHAT_REBEL_EVENT";
+ case SLYLANDRO_RAMP_UP:
+ return "SLYLANDRO_RAMP_UP";
+ case SLYLANDRO_RAMP_DOWN:
+ return "SLYLANDRO_RAMP_DOWN";
+ default:
+ // Should not happen
+ return "???";
+ }
+}
+
+static void
+dumpEventCallback (const EVENT *eventPtr, void *arg)
+{
+ FILE *out = (FILE *) arg;
+ dumpEvent (out, eventPtr);
+}
+
+void
+dumpEvent (FILE *out, const EVENT *eventPtr)
+{
+ fprintf (out, "%4u/%02u/%02u: %s\n",
+ eventPtr->year_index,
+ eventPtr->month_index,
+ eventPtr->day_index,
+ eventName (eventPtr->func_index));
+}
+
+void
+dumpEvents (FILE *out)
+{
+ LockGameClock ();
+ ForAllEvents (dumpEventCallback, out);
+ UnlockGameClock ();
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+// NB: Ship maximum speed and turning rate aren't updated in
+// HyperSpace/QuasiSpace or in melee.
+void
+equipShip (void)
+{
+ int i;
+
+ // Don't do anything unless in the full game.
+ if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE)
+ return;
+
+ // Thrusters:
+ for (i = 0; i < NUM_DRIVE_SLOTS; i++)
+ GLOBAL_SIS (DriveSlots[i]) = FUSION_THRUSTER;
+
+ // Turning jets:
+ for (i = 0; i < NUM_JET_SLOTS; i++)
+ GLOBAL_SIS (JetSlots[i]) = TURNING_JETS;
+
+ // Shields:
+ SET_GAME_STATE (LANDER_SHIELDS,
+ (1 << EARTHQUAKE_DISASTER) |
+ (1 << BIOLOGICAL_DISASTER) |
+ (1 << LIGHTNING_DISASTER) |
+ (1 << LAVASPOT_DISASTER));
+ // Lander upgrades:
+ SET_GAME_STATE (IMPROVED_LANDER_SPEED, 1);
+ SET_GAME_STATE (IMPROVED_LANDER_CARGO, 1);
+ SET_GAME_STATE (IMPROVED_LANDER_SHOT, 1);
+
+ // Modules:
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) < 2)
+ {
+ // The Precursor bomb has not been installed.
+ // This is the original TFB testing layout.
+ i = 0;
+ GLOBAL_SIS (ModuleSlots[i++]) = HIGHEFF_FUELSYS;
+ GLOBAL_SIS (ModuleSlots[i++]) = HIGHEFF_FUELSYS;
+ GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
+ GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
+ GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
+ GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
+ GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
+ GLOBAL_SIS (ModuleSlots[i++]) = STORAGE_BAY;
+ GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
+ GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
+ GLOBAL_SIS (ModuleSlots[i++]) = DYNAMO_UNIT;
+ GLOBAL_SIS (ModuleSlots[i++]) = TRACKING_SYSTEM;
+ GLOBAL_SIS (ModuleSlots[i++]) = TRACKING_SYSTEM;
+ GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
+ GLOBAL_SIS (ModuleSlots[i++]) = CANNON_WEAPON;
+ GLOBAL_SIS (ModuleSlots[i++]) = CANNON_WEAPON;
+
+ // Landers:
+ GLOBAL_SIS (NumLanders) = MAX_LANDERS;
+ }
+ else
+ {
+ // The Precursor bomb has been installed.
+ i = NUM_BOMB_MODULES;
+ GLOBAL_SIS (ModuleSlots[i++]) = HIGHEFF_FUELSYS;
+ GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
+ GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
+ GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
+ GLOBAL_SIS (ModuleSlots[i++]) = CANNON_WEAPON;
+ GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
+ }
+
+ assert (i <= NUM_MODULE_SLOTS);
+
+ // Fill the fuel and crew compartments to the maximum.
+ GLOBAL_SIS (FuelOnBoard) = FUEL_RESERVE;
+ GLOBAL_SIS (CrewEnlisted) = 0;
+ for (i = 0; i < NUM_MODULE_SLOTS; i++)
+ {
+ switch (GLOBAL_SIS (ModuleSlots[i])) {
+ case CREW_POD:
+ GLOBAL_SIS (CrewEnlisted) += CREW_POD_CAPACITY;
+ break;
+ case FUEL_TANK:
+ GLOBAL_SIS (FuelOnBoard) += FUEL_TANK_CAPACITY;
+ break;
+ case HIGHEFF_FUELSYS:
+ GLOBAL_SIS (FuelOnBoard) += HEFUEL_TANK_CAPACITY;
+ break;
+ }
+ }
+
+ // Update the maximum speed and turning rate when in interplanetary.
+ if (pSolarSysState != NULL)
+ {
+ // Thrusters:
+ pSolarSysState->max_ship_speed = 5 * IP_SHIP_THRUST_INCREMENT;
+ for (i = 0; i < NUM_DRIVE_SLOTS; i++)
+ if (GLOBAL_SIS (DriveSlots[i] == FUSION_THRUSTER))
+ pSolarSysState->max_ship_speed += IP_SHIP_THRUST_INCREMENT;
+
+ // Turning jets:
+ 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;
+ }
+
+ // Make sure everything is redrawn:
+ if (inHQSpace () ||
+ LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY)
+ {
+ DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA, UNDEFINED_DELTA);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+giveDevices (void) {
+ SET_GAME_STATE (ROSY_SPHERE_ON_SHIP, 1);
+ SET_GAME_STATE (ARTIFACT_2_ON_SHIP, 1);
+ SET_GAME_STATE (ARTIFACT_3_ON_SHIP, 1);
+ SET_GAME_STATE (SUN_DEVICE_ON_SHIP, 1);
+ SET_GAME_STATE (UTWIG_BOMB_ON_SHIP, 1);
+ SET_GAME_STATE (ULTRON_CONDITION, 1);
+ //SET_GAME_STATE (ULTRON_CONDITION, 2);
+ //SET_GAME_STATE (ULTRON_CONDITION, 3);
+ //SET_GAME_STATE (ULTRON_CONDITION, 4);
+ SET_GAME_STATE (MAIDENS_ON_SHIP, 1);
+ SET_GAME_STATE (TALKING_PET_ON_SHIP, 1);
+ SET_GAME_STATE (AQUA_HELIX_ON_SHIP, 1);
+ SET_GAME_STATE (CLEAR_SPINDLE_ON_SHIP, 1);
+ SET_GAME_STATE (UMGAH_BROADCASTERS_ON_SHIP, 1);
+ SET_GAME_STATE (TAALO_PROTECTOR_ON_SHIP, 1);
+ SET_GAME_STATE (EGG_CASE0_ON_SHIP, 1);
+ SET_GAME_STATE (EGG_CASE1_ON_SHIP, 1);
+ SET_GAME_STATE (EGG_CASE2_ON_SHIP, 1);
+ SET_GAME_STATE (SYREEN_SHUTTLE_ON_SHIP, 1);
+ SET_GAME_STATE (VUX_BEAST_ON_SHIP, 1);
+ SET_GAME_STATE (PORTAL_SPAWNER_ON_SHIP, 1);
+ SET_GAME_STATE (PORTAL_KEY_ON_SHIP, 1);
+ SET_GAME_STATE (BURV_BROADCASTERS_ON_SHIP, 1);
+ SET_GAME_STATE (MOONBASE_ON_SHIP, 1);
+
+ // Not strictly a device (although it originally was one).
+ SET_GAME_STATE (DESTRUCT_CODE_ON_SHIP, 1);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+clearEscorts (void)
+{
+ HSHIPFRAG hStarShip, hNextShip;
+
+ for (hStarShip = GetHeadLink (&GLOBAL (built_ship_q));
+ hStarShip; hStarShip = hNextShip)
+ {
+ SHIP_FRAGMENT *StarShipPtr;
+
+ StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ hNextShip = _GetSuccLink (StarShipPtr);
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+
+ RemoveQueue (&GLOBAL (built_ship_q), hStarShip);
+ FreeShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ }
+
+ DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA, UNDEFINED_DELTA);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+#if 0
+// Not needed anymore, but could be useful in the future.
+
+// Find the HELEMENT belonging to the Flagship.
+static HELEMENT
+findFlagshipElement (void)
+{
+ HELEMENT hElement, hNextElement;
+ ELEMENT *ElementPtr;
+
+ // Find the ship element.
+ for (hElement = GetTailElement (); hElement != 0;
+ hElement = hNextElement)
+ {
+ LockElement (hElement, &ElementPtr);
+
+ if ((ElementPtr->state_flags & PLAYER_SHIP) != 0)
+ {
+ UnlockElement (hElement);
+ return hElement;
+ }
+
+ hNextElement = GetPredElement (ElementPtr);
+ UnlockElement (hElement);
+ }
+ return 0;
+}
+#endif
+
+// Move the Flagship to the destination of the autopilot.
+// Should only be called from HyperSpace/QuasiSpace.
+// It can be called from debugHook directly after entering HS/QS though.
+void
+doInstantMove (void)
+{
+ // Move to the new location:
+ if ((GLOBAL (autopilot)).x == ~0 || (GLOBAL (autopilot)).y == ~0)
+ {
+ // If no destination has been selected, use the current location
+ // as the destination.
+ (GLOBAL (autopilot)).x = LOGX_TO_UNIVERSE(GLOBAL_SIS (log_x));
+ (GLOBAL (autopilot)).y = LOGY_TO_UNIVERSE(GLOBAL_SIS (log_y));
+ }
+ else
+ {
+ // A new destination has been selected.
+ GLOBAL_SIS (log_x) = UNIVERSE_TO_LOGX((GLOBAL (autopilot)).x);
+ GLOBAL_SIS (log_y) = UNIVERSE_TO_LOGY((GLOBAL (autopilot)).y);
+ }
+
+ // Check for a solar systems at the destination.
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1)
+ {
+ // If there's a solar system at the destination, enter it.
+ CurStarDescPtr = FindStar (0, &(GLOBAL (autopilot)), 0, 0);
+ if (CurStarDescPtr)
+ {
+ // Leave HyperSpace/QuasiSpace if we're there:
+ SET_GAME_STATE (USED_BROADCASTER, 0);
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+
+ // Enter IP:
+ GLOBAL (ShipFacing) = 0;
+ GLOBAL (ip_planet) = 0;
+ GLOBAL (in_orbit) = 0;
+ // This causes the ship position in IP to be reset.
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ }
+ }
+
+ // Turn off the autopilot:
+ (GLOBAL (autopilot)).x = ~0;
+ (GLOBAL (autopilot)).y = ~0;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+showSpheres (void)
+{
+ HFLEETINFO hStarShip, hNextShip;
+
+ for (hStarShip = GetHeadLink (&GLOBAL (avail_race_q));
+ hStarShip != NULL; hStarShip = hNextShip)
+ {
+ FLEET_INFO *FleetPtr;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ hNextShip = _GetSuccLink (FleetPtr);
+
+ if ((FleetPtr->actual_strength != INFINITE_RADIUS) &&
+ (FleetPtr->known_strength != FleetPtr->actual_strength))
+ {
+ FleetPtr->known_strength = FleetPtr->actual_strength;
+ FleetPtr->known_loc = FleetPtr->loc;
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+activateAllShips (void)
+{
+ HFLEETINFO hStarShip, hNextShip;
+
+ for (hStarShip = GetHeadLink (&GLOBAL (avail_race_q));
+ hStarShip != NULL; hStarShip = hNextShip)
+ {
+ FLEET_INFO *FleetPtr;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ hNextShip = _GetSuccLink (FleetPtr);
+
+ if (FleetPtr->icons != NULL)
+ // Skip the Ur-Quan probe.
+ {
+ FleetPtr->allied_state = GOOD_GUY;
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+forAllStars (void (*callback) (STAR_DESC *, void *), void *arg)
+{
+ int i;
+ extern STAR_DESC starmap_array[];
+
+ for (i = 0; i < NUM_SOLAR_SYSTEMS; i++)
+ callback (&starmap_array[i], arg);
+}
+
+void
+forAllPlanets (STAR_DESC *star, SOLARSYS_STATE *system, void (*callback) (
+ STAR_DESC *, SOLARSYS_STATE *, PLANET_DESC *, void *), void *arg)
+{
+ COUNT i;
+
+ assert(CurStarDescPtr == star);
+ assert(pSolarSysState == system);
+
+ for (i = 0; i < system->SunDesc[0].NumPlanets; i++)
+ callback (star, system, &system->PlanetDesc[i], arg);
+}
+
+void
+forAllMoons (STAR_DESC *star, SOLARSYS_STATE *system, PLANET_DESC *planet,
+ void (*callback) (STAR_DESC *, SOLARSYS_STATE *, PLANET_DESC *,
+ PLANET_DESC *, void *), void *arg)
+{
+ COUNT i;
+
+ assert(pSolarSysState == system);
+
+ for (i = 0; i < planet->NumPlanets; i++)
+ callback (star, system, planet, &system->MoonDesc[i], arg);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+// Must be called from the Starcon2Main thread.
+// TODO: LockGameClock may be removed
+void
+UniverseRecurse (UniverseRecurseArg *universeRecurseArg)
+{
+ ACTIVITY savedActivity;
+
+ if (universeRecurseArg->systemFuncPre == NULL
+ && universeRecurseArg->systemFuncPost == NULL
+ && universeRecurseArg->planetFuncPre == NULL
+ && universeRecurseArg->planetFuncPost == NULL
+ && universeRecurseArg->moonFunc == NULL)
+ return;
+
+ LockGameClock ();
+ //TFB_DEBUG_HALT = 1;
+ savedActivity = GLOBAL (CurrentActivity);
+ disableInteractivity = TRUE;
+
+ forAllStars (starRecurse, (void *) universeRecurseArg);
+
+ disableInteractivity = FALSE;
+ GLOBAL (CurrentActivity) = savedActivity;
+ UnlockGameClock ();
+}
+
+static void
+starRecurse (STAR_DESC *star, void *arg)
+{
+ UniverseRecurseArg *universeRecurseArg = (UniverseRecurseArg *) arg;
+
+ SOLARSYS_STATE SolarSysState;
+ SOLARSYS_STATE *oldPSolarSysState = pSolarSysState;
+ STAR_DESC *oldStarDescPtr = CurStarDescPtr;
+ CurStarDescPtr = star;
+
+ RandomContext_SeedRandom (SysGenRNG, GetRandomSeedForStar (star));
+
+ memset (&SolarSysState, 0, sizeof (SolarSysState));
+ SolarSysState.SunDesc[0].pPrevDesc = 0;
+ SolarSysState.SunDesc[0].rand_seed = RandomContext_Random (SysGenRNG);
+ SolarSysState.SunDesc[0].data_index = STAR_TYPE (star->Type);
+ SolarSysState.SunDesc[0].location.x = 0;
+ SolarSysState.SunDesc[0].location.y = 0;
+ //SolarSysState.SunDesc[0].radius = MIN_ZOOM_RADIUS;
+ SolarSysState.genFuncs = getGenerateFunctions (star->Index);
+
+ pSolarSysState = &SolarSysState;
+ (*SolarSysState.genFuncs->generatePlanets) (&SolarSysState);
+
+ if (universeRecurseArg->systemFuncPre != NULL)
+ {
+ (*universeRecurseArg->systemFuncPre) (
+ star, &SolarSysState, universeRecurseArg->arg);
+ }
+
+ if (universeRecurseArg->planetFuncPre != NULL
+ || universeRecurseArg->planetFuncPost != NULL
+ || universeRecurseArg->moonFunc != NULL)
+ {
+ forAllPlanets (star, &SolarSysState, planetRecurse,
+ (void *) universeRecurseArg);
+ }
+
+ if (universeRecurseArg->systemFuncPost != NULL)
+ {
+ (*universeRecurseArg->systemFuncPost) (
+ star, &SolarSysState, universeRecurseArg->arg);
+ }
+
+ pSolarSysState = oldPSolarSysState;
+ CurStarDescPtr = oldStarDescPtr;
+}
+
+static void
+planetRecurse (STAR_DESC *star, SOLARSYS_STATE *system, PLANET_DESC *planet,
+ void *arg)
+{
+ UniverseRecurseArg *universeRecurseArg = (UniverseRecurseArg *) arg;
+
+ assert(CurStarDescPtr == star);
+ assert(pSolarSysState == system);
+
+ planet->pPrevDesc = &system->SunDesc[0];
+
+ if (universeRecurseArg->planetFuncPre != NULL)
+ {
+ system->pOrbitalDesc = planet;
+ DoPlanetaryAnalysis (&system->SysInfo, planet);
+ // When GenerateDefaultFunctions is used as genFuncs,
+ // generateOrbital will also call DoPlanetaryAnalysis,
+ // but with other GenerateFunctions this is not guaranteed.
+ (*system->genFuncs->generateOrbital) (system, planet);
+ (*universeRecurseArg->planetFuncPre) (
+ planet, universeRecurseArg->arg);
+ }
+
+ if (universeRecurseArg->moonFunc != NULL)
+ {
+ RandomContext_SeedRandom (SysGenRNG, planet->rand_seed);
+
+ (*system->genFuncs->generateMoons) (system, planet);
+
+ forAllMoons (star, system, planet, moonRecurse,
+ (void *) universeRecurseArg);
+ }
+
+ if (universeRecurseArg->planetFuncPost != NULL)
+ {
+ system->pOrbitalDesc = planet;
+ DoPlanetaryAnalysis (&system->SysInfo, planet);
+ // When GenerateDefaultFunctions is used as genFuncs,
+ // generateOrbital will also call DoPlanetaryAnalysis,
+ // but with other GenerateFunctions this is not guaranteed.
+ (*system->genFuncs->generateOrbital) (system, planet);
+ (*universeRecurseArg->planetFuncPost) (
+ planet, universeRecurseArg->arg);
+ }
+}
+
+static void
+moonRecurse (STAR_DESC *star, SOLARSYS_STATE *system, PLANET_DESC *planet,
+ PLANET_DESC *moon, void *arg)
+{
+ UniverseRecurseArg *universeRecurseArg = (UniverseRecurseArg *) arg;
+
+ assert(CurStarDescPtr == star);
+ assert(pSolarSysState == system);
+
+ moon->pPrevDesc = planet;
+
+ if (universeRecurseArg->moonFunc != NULL)
+ {
+ system->pOrbitalDesc = moon;
+ if (moon->data_index != HIERARCHY_STARBASE && moon->data_index != SA_MATRA)
+ {
+ DoPlanetaryAnalysis (&system->SysInfo, moon);
+ // When GenerateDefaultFunctions is used as genFuncs,
+ // generateOrbital will also call DoPlanetaryAnalysis,
+ // but with other GenerateFunctions this is not guaranteed.
+ }
+ (*system->genFuncs->generateOrbital) (system, moon);
+ (*universeRecurseArg->moonFunc) (
+ moon, universeRecurseArg->arg);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+typedef struct
+{
+ FILE *out;
+} DumpUniverseArg;
+
+// Must be called from the Starcon2Main thread.
+void
+dumpUniverse (FILE *out)
+{
+ DumpUniverseArg dumpUniverseArg;
+ UniverseRecurseArg universeRecurseArg;
+
+ dumpUniverseArg.out = out;
+
+ universeRecurseArg.systemFuncPre = dumpSystemCallback;
+ universeRecurseArg.systemFuncPost = NULL;
+ universeRecurseArg.planetFuncPre = dumpPlanetCallback;
+ universeRecurseArg.planetFuncPost = NULL;
+ universeRecurseArg.moonFunc = dumpMoonCallback;
+ universeRecurseArg.arg = (void *) &dumpUniverseArg;
+
+ UniverseRecurse (&universeRecurseArg);
+}
+
+// Must be called from the Starcon2Main thread.
+void
+dumpUniverseToFile (void)
+{
+ FILE *out;
+
+# define UNIVERSE_DUMP_FILE "PlanetInfo"
+ out = fopen(UNIVERSE_DUMP_FILE, "w");
+ if (out == NULL)
+ {
+ fprintf(stderr, "Error: Could not open file '%s' for "
+ "writing: %s\n", UNIVERSE_DUMP_FILE, strerror(errno));
+ return;
+ }
+
+ dumpUniverse (out);
+
+ fclose(out);
+
+ fprintf(stdout, "*** Star dump complete. The game may be in an "
+ "undefined state.\n");
+ // Data generation may have changed the game state,
+ // in particular for special planet generation.
+}
+
+static void
+dumpSystemCallback (const STAR_DESC *star, const SOLARSYS_STATE *system,
+ void *arg)
+{
+ FILE *out = ((DumpUniverseArg *) arg)->out;
+ dumpSystem (out, star, system);
+}
+
+void
+dumpSystem (FILE *out, const STAR_DESC *star, const SOLARSYS_STATE *system)
+{
+ UNICODE name[256];
+ UNICODE buf[40];
+
+ GetClusterName (star, name);
+ snprintf (buf, sizeof buf, "%s %s",
+ bodyColorString (STAR_COLOR(star->Type)),
+ starTypeString (STAR_TYPE(star->Type)));
+ fprintf (out, "%-22s (%3d.%1d, %3d.%1d) %-19s %s\n",
+ name,
+ star->star_pt.x / 10, star->star_pt.x % 10,
+ star->star_pt.y / 10, star->star_pt.y % 10,
+ buf,
+ starPresenceString (star->Index));
+
+ (void) system; /* satisfy compiler */
+}
+
+const char *
+bodyColorString (BYTE col)
+{
+ switch (col) {
+ case BLUE_BODY:
+ return "blue";
+ case GREEN_BODY:
+ return "green";
+ case ORANGE_BODY:
+ return "orange";
+ case RED_BODY:
+ return "red";
+ case WHITE_BODY:
+ return "white";
+ case YELLOW_BODY:
+ return "yellow";
+ case CYAN_BODY:
+ return "cyan";
+ case PURPLE_BODY:
+ return "purple";
+ case VIOLET_BODY:
+ return "violet";
+ default:
+ // Should not happen
+ return "???";
+ }
+}
+
+const char *
+starTypeString (BYTE type)
+{
+ switch (type) {
+ case DWARF_STAR:
+ return "dwarf";
+ case GIANT_STAR:
+ return "giant";
+ case SUPER_GIANT_STAR:
+ return "super giant";
+ default:
+ // Should not happen
+ return "???";
+ }
+}
+
+const char *
+starPresenceString (BYTE index)
+{
+ switch (index) {
+ case 0:
+ // nothing
+ return "";
+ case SOL_DEFINED:
+ return "Sol";
+ case SHOFIXTI_DEFINED:
+ return "Shofixti male";
+ case MAIDENS_DEFINED:
+ return "Shofixti maidens";
+ case START_COLONY_DEFINED:
+ return "Starting colony";
+ case SPATHI_DEFINED:
+ return "Spathi home";
+ case ZOQFOT_DEFINED:
+ return "Zoq-Fot-Pik home";
+ case MELNORME0_DEFINED:
+ return "Melnorme trader #0";
+ case MELNORME1_DEFINED:
+ return "Melnorme trader #1";
+ case MELNORME2_DEFINED:
+ return "Melnorme trader #2";
+ case MELNORME3_DEFINED:
+ return "Melnorme trader #3";
+ case MELNORME4_DEFINED:
+ return "Melnorme trader #4";
+ case MELNORME5_DEFINED:
+ return "Melnorme trader #5";
+ case MELNORME6_DEFINED:
+ return "Melnorme trader #6";
+ case MELNORME7_DEFINED:
+ return "Melnorme trader #7";
+ case MELNORME8_DEFINED:
+ return "Melnorme trader #8";
+ case TALKING_PET_DEFINED:
+ return "Talking Pet";
+ case CHMMR_DEFINED:
+ return "Chmmr home";
+ case SYREEN_DEFINED:
+ return "Syreen home";
+ case BURVIXESE_DEFINED:
+ return "Burvixese ruins";
+ case SLYLANDRO_DEFINED:
+ return "Slylandro home";
+ case DRUUGE_DEFINED:
+ return "Druuge home";
+ case BOMB_DEFINED:
+ return "Precursor bomb";
+ case AQUA_HELIX_DEFINED:
+ return "Aqua Helix";
+ case SUN_DEVICE_DEFINED:
+ return "Sun Device";
+ case TAALO_PROTECTOR_DEFINED:
+ return "Taalo Shield";
+ case SHIP_VAULT_DEFINED:
+ return "Syreen ship vault";
+ case URQUAN_WRECK_DEFINED:
+ return "Ur-Quan ship wreck";
+ case VUX_BEAST_DEFINED:
+ return "Zex' beauty";
+ case SAMATRA_DEFINED:
+ return "Sa-Matra";
+ case ZOQ_SCOUT_DEFINED:
+ return "Zoq-Fot-Pik scout";
+ case MYCON_DEFINED:
+ return "Mycon home";
+ case EGG_CASE0_DEFINED:
+ return "Mycon egg shell #0";
+ case EGG_CASE1_DEFINED:
+ return "Mycon egg shell #1";
+ case EGG_CASE2_DEFINED:
+ return "Mycon egg shell #2";
+ case PKUNK_DEFINED:
+ return "Pkunk home";
+ case UTWIG_DEFINED:
+ return "Utwig home";
+ case SUPOX_DEFINED:
+ return "Supox home";
+ case YEHAT_DEFINED:
+ return "Yehat home";
+ case VUX_DEFINED:
+ return "Vux home";
+ case ORZ_DEFINED:
+ return "Orz home";
+ case THRADD_DEFINED:
+ return "Thraddash home";
+ case RAINBOW_DEFINED:
+ return "Rainbow world";
+ case ILWRATH_DEFINED:
+ return "Ilwrath home";
+ case ANDROSYNTH_DEFINED:
+ return "Androsynth ruins";
+ case MYCON_TRAP_DEFINED:
+ return "Mycon trap";
+ default:
+ // Should not happen
+ return "???";
+ }
+}
+
+static void
+dumpPlanetCallback (const PLANET_DESC *planet, void *arg)
+{
+ FILE *out = ((DumpUniverseArg *) arg)->out;
+ dumpPlanet (out, planet);
+}
+
+void
+dumpPlanet (FILE *out, const PLANET_DESC *planet)
+{
+ (*pSolarSysState->genFuncs->generateName) (pSolarSysState, planet);
+ fprintf (out, "- %-37s %s\n", GLOBAL_SIS (PlanetName),
+ planetTypeString (planet->data_index & ~PLANET_SHIELDED));
+ dumpWorld (out, planet);
+}
+
+static void
+dumpMoonCallback (const PLANET_DESC *moon, void *arg)
+{
+ FILE *out = ((DumpUniverseArg *) arg)->out;
+ dumpMoon (out, moon);
+}
+
+void
+dumpMoon (FILE *out, const PLANET_DESC *moon)
+{
+ const char *typeStr;
+
+ if (moon->data_index == HIERARCHY_STARBASE)
+ {
+ typeStr = "StarBase";
+ }
+ else if (moon->data_index == SA_MATRA)
+ {
+ typeStr = "Sa-Matra";
+ }
+ else
+ {
+ typeStr = planetTypeString (moon->data_index & ~PLANET_SHIELDED);
+ }
+ fprintf (out, " - Moon %-30c %s\n",
+ 'a' + (moon - &pSolarSysState->MoonDesc[0]), typeStr);
+
+ dumpWorld (out, moon);
+}
+
+static void
+dumpWorld (FILE *out, const PLANET_DESC *world)
+{
+ PLANET_INFO *info;
+
+ if (world->data_index == HIERARCHY_STARBASE) {
+ return;
+ }
+
+ if (world->data_index == SA_MATRA) {
+ return;
+ }
+
+ info = &pSolarSysState->SysInfo.PlanetInfo;
+ fprintf(out, " AxialTilt: %d\n", info->AxialTilt);
+ fprintf(out, " Tectonics: %d\n", info->Tectonics);
+ fprintf(out, " Weather: %d\n", info->Weather);
+ fprintf(out, " Density: %d\n", info->PlanetDensity);
+ fprintf(out, " Radius: %d\n", info->PlanetRadius);
+ fprintf(out, " Gravity: %d\n", info->SurfaceGravity);
+ fprintf(out, " Temp: %d\n", info->SurfaceTemperature);
+ fprintf(out, " Day: %d\n", info->RotationPeriod);
+ fprintf(out, " Atmosphere: %d\n", info->AtmoDensity);
+ fprintf(out, " LifeChance: %d\n", info->LifeChance);
+ fprintf(out, " DistToSun: %d\n", info->PlanetToSunDist);
+
+ if (world->data_index & PLANET_SHIELDED) {
+ // Slave-shielded planet
+ return;
+ }
+
+ fprintf (out, " Bio: %4d Min: %4d\n",
+ calculateBioValue (pSolarSysState, world),
+ calculateMineralValue (pSolarSysState, world));
+}
+
+COUNT
+calculateBioValue (const SOLARSYS_STATE *system, const PLANET_DESC *world)
+{
+ COUNT result;
+ COUNT numBio;
+ COUNT i;
+
+ assert (system->pOrbitalDesc == world);
+
+ numBio = callGenerateForScanType (system, world, GENERATE_ALL,
+ BIOLOGICAL_SCAN, NULL);
+
+ result = 0;
+ for (i = 0; i < numBio; i++)
+ {
+ NODE_INFO info;
+ callGenerateForScanType (system, world, i, BIOLOGICAL_SCAN, &info);
+ result += BIO_CREDIT_VALUE *
+ LONIBBLE (CreatureData[info.type].ValueAndHitPoints);
+ }
+ return result;
+}
+
+void
+generateBioIndex(const SOLARSYS_STATE *system, const PLANET_DESC *world,
+ COUNT bio[])
+{
+ COUNT numBio;
+ COUNT i;
+
+ assert (system->pOrbitalDesc == world);
+
+ numBio = callGenerateForScanType (system, world, GENERATE_ALL,
+ BIOLOGICAL_SCAN, NULL);
+
+ for (i = 0; i < NUM_CREATURE_TYPES + NUM_SPECIAL_CREATURE_TYPES; i++)
+ bio[i] = 0;
+
+ for (i = 0; i < numBio; i++)
+ {
+ NODE_INFO info;
+ callGenerateForScanType (system, world, i, BIOLOGICAL_SCAN, &info);
+ bio[info.type]++;
+ }
+}
+
+COUNT
+calculateMineralValue (const SOLARSYS_STATE *system, const PLANET_DESC *world)
+{
+ COUNT result;
+ COUNT numDeposits;
+ COUNT i;
+
+ assert (system->pOrbitalDesc == world);
+
+ numDeposits = callGenerateForScanType (system, world, GENERATE_ALL,
+ MINERAL_SCAN, NULL);
+
+ result = 0;
+ for (i = 0; i < numDeposits; i++)
+ {
+ NODE_INFO info;
+ callGenerateForScanType (system, world, i, MINERAL_SCAN, &info);
+ result += HIBYTE (info.density) *
+ GLOBAL (ElementWorth[ElementCategory (info.type)]);
+ }
+ return result;
+}
+
+void
+generateMineralIndex(const SOLARSYS_STATE *system, const PLANET_DESC *world,
+ COUNT minerals[])
+{
+ COUNT numDeposits;
+ COUNT i;
+
+ assert (system->pOrbitalDesc == world);
+
+ numDeposits = callGenerateForScanType (system, world, GENERATE_ALL,
+ MINERAL_SCAN, NULL);
+
+ for (i = 0; i < NUM_ELEMENT_CATEGORIES; i++)
+ minerals[i] = 0;
+
+ for (i = 0; i < numDeposits; i++)
+ {
+ NODE_INFO info;
+ callGenerateForScanType (system, world, i, MINERAL_SCAN, &info);
+ minerals[ElementCategory (info.type)] += HIBYTE (info.density);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+struct TallyResourcesArg
+{
+ FILE *out;
+ COUNT mineralCount;
+ COUNT bioCount;
+};
+
+// Must be called from the Starcon2Main thread.
+void
+tallyResources (FILE *out)
+{
+ TallyResourcesArg tallyResourcesArg;
+ UniverseRecurseArg universeRecurseArg;
+
+ tallyResourcesArg.out = out;
+
+ universeRecurseArg.systemFuncPre = tallySystemPreCallback;
+ universeRecurseArg.systemFuncPost = tallySystemPostCallback;
+ universeRecurseArg.planetFuncPre = tallyPlanetCallback;
+ universeRecurseArg.planetFuncPost = NULL;
+ universeRecurseArg.moonFunc = tallyMoonCallback;
+ universeRecurseArg.arg = (void *) &tallyResourcesArg;
+
+ UniverseRecurse (&universeRecurseArg);
+}
+
+// Must be called from the Starcon2Main thread.
+void
+tallyResourcesToFile (void)
+{
+ FILE *out;
+
+# define RESOURCE_TALLY_FILE "ResourceTally"
+ out = fopen(RESOURCE_TALLY_FILE, "w");
+ if (out == NULL)
+ {
+ fprintf(stderr, "Error: Could not open file '%s' for "
+ "writing: %s\n", RESOURCE_TALLY_FILE, strerror(errno));
+ return;
+ }
+
+ tallyResources (out);
+
+ fclose(out);
+
+ fprintf(stdout, "*** Resource tally complete. The game may be in an "
+ "undefined state.\n");
+ // Data generation may have changed the game state,
+ // in particular for special planet generation.
+}
+
+static void
+tallySystemPreCallback (const STAR_DESC *star, const SOLARSYS_STATE *system,
+ void *arg)
+{
+ TallyResourcesArg *tallyResourcesArg = (TallyResourcesArg *) arg;
+ tallyResourcesArg->mineralCount = 0;
+ tallyResourcesArg->bioCount = 0;
+
+ (void) star; /* satisfy compiler */
+ (void) system; /* satisfy compiler */
+}
+
+static void
+tallySystemPostCallback (const STAR_DESC *star, const SOLARSYS_STATE *system,
+ void *arg)
+{
+ UNICODE name[256];
+ TallyResourcesArg *tallyResourcesArg = (TallyResourcesArg *) arg;
+ FILE *out = tallyResourcesArg->out;
+
+ GetClusterName (star, name);
+ fprintf (out, "%s\t%d\t%d\n", name, tallyResourcesArg->mineralCount,
+ tallyResourcesArg->bioCount);
+
+ (void) star; /* satisfy compiler */
+ (void) system; /* satisfy compiler */
+}
+
+static void
+tallyPlanetCallback (const PLANET_DESC *planet, void *arg)
+{
+ tallyResourcesWorld ((TallyResourcesArg *) arg, planet);
+}
+
+static void
+tallyMoonCallback (const PLANET_DESC *moon, void *arg)
+{
+ tallyResourcesWorld ((TallyResourcesArg *) arg, moon);
+}
+
+static void
+tallyResourcesWorld (TallyResourcesArg *arg, const PLANET_DESC *world)
+{
+ if (world->data_index == HIERARCHY_STARBASE) {
+ return;
+ }
+
+ if (world->data_index == SA_MATRA) {
+ return;
+ }
+
+ arg->bioCount += calculateBioValue (pSolarSysState, world),
+ arg->mineralCount += calculateMineralValue (pSolarSysState, world);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+forAllPlanetTypes (void (*callback) (int, const PlanetFrame *, void *),
+ void *arg)
+{
+ int i;
+ extern const PlanetFrame planet_array[];
+
+ for (i = 0; i < NUMBER_OF_PLANET_TYPES; i++)
+ callback (i, &planet_array[i], arg);
+}
+
+typedef struct
+{
+ FILE *out;
+} DumpPlanetTypesArg;
+
+void
+dumpPlanetTypes (FILE *out)
+{
+ DumpPlanetTypesArg dumpPlanetTypesArg;
+ dumpPlanetTypesArg.out = out;
+
+ forAllPlanetTypes (dumpPlanetTypeCallback, (void *) &dumpPlanetTypesArg);
+}
+
+static void
+dumpPlanetTypeCallback (int index, const PlanetFrame *planetType, void *arg)
+{
+ DumpPlanetTypesArg *dumpPlanetTypesArg = (DumpPlanetTypesArg *) arg;
+
+ dumpPlanetType(dumpPlanetTypesArg->out, index, planetType);
+}
+
+void
+dumpPlanetType (FILE *out, int index, const PlanetFrame *planetType)
+{
+ int i;
+ fprintf (out,
+ "%s\n"
+ "\tType: %s\n"
+ "\tColor: %s\n"
+ "\tSurface generation algoritm: %s\n"
+ "\tTectonics: %s\n"
+ "\tAtmosphere: %s\n"
+ "\tDensity: %s\n"
+ "\tElements:\n",
+ planetTypeString (index),
+ worldSizeString (PLANSIZE (planetType->Type)),
+ bodyColorString (PLANCOLOR (planetType->Type)),
+ worldGenAlgoString (PLANALGO (planetType->Type)),
+ tectonicsString (planetType->BaseTectonics),
+ atmosphereString (HINIBBLE (planetType->AtmoAndDensity)),
+ densityString (LONIBBLE (planetType->AtmoAndDensity))
+ );
+ for (i = 0; i < NUM_USEFUL_ELEMENTS; i++)
+ {
+ const ELEMENT_ENTRY *entry;
+ entry = &planetType->UsefulElements[i];
+ if (entry->Density == 0)
+ continue;
+ fprintf(out, "\t\t0 to %d %s-quality (+%d) deposits of %s (%s)\n",
+ DEPOSIT_QUANTITY (entry->Density),
+ depositQualityString (DEPOSIT_QUALITY (entry->Density)),
+ DEPOSIT_QUALITY (entry->Density) * 5,
+ GAME_STRING (ELEMENTS_STRING_BASE + entry->ElementType),
+ GAME_STRING (CARGO_STRING_BASE + 2 + ElementCategory (
+ entry->ElementType))
+ );
+ }
+ fprintf (out, "\n");
+}
+
+const char *
+planetTypeString (int typeIndex)
+{
+ static UNICODE typeStr[40];
+
+ if (typeIndex >= FIRST_GAS_GIANT)
+ {
+ // "Gas Giant"
+ snprintf(typeStr, sizeof typeStr, "%s",
+ GAME_STRING (SCAN_STRING_BASE + 4 + 51));
+ }
+ else
+ {
+ // "<type> World" (eg. "Water World")
+ snprintf(typeStr, sizeof typeStr, "%s %s",
+ GAME_STRING (SCAN_STRING_BASE + 4 + typeIndex),
+ GAME_STRING (SCAN_STRING_BASE + 4 + 50));
+ }
+ return typeStr;
+}
+
+// size is what you get from PLANSIZE (planetFrame.Type)
+const char *
+worldSizeString (BYTE size)
+{
+ switch (size)
+ {
+ case SMALL_ROCKY_WORLD:
+ return "small rocky world";
+ case LARGE_ROCKY_WORLD:
+ return "large rocky world";
+ case GAS_GIANT:
+ return "gas giant";
+ default:
+ // Should not happen
+ return "???";
+ }
+}
+
+// algo is what you get from PLANALGO (planetFrame.Type)
+const char *
+worldGenAlgoString (BYTE algo)
+{
+ switch (algo)
+ {
+ case TOPO_ALGO:
+ return "TOPO_ALGO";
+ case CRATERED_ALGO:
+ return "CRATERED_ALGO";
+ case GAS_GIANT_ALGO:
+ return "GAS_GIANT_ALGO";
+ default:
+ // Should not happen
+ return "???";
+ }
+}
+
+// tectonics is what you get from planetFrame.BaseTechtonics
+// not reentrant
+const char *
+tectonicsString (BYTE tectonics)
+{
+ static char buf[sizeof "-127"];
+ switch (tectonics)
+ {
+ case NO_TECTONICS:
+ return "none";
+ case LOW_TECTONICS:
+ return "low";
+ case MED_TECTONICS:
+ return "medium";
+ case HIGH_TECTONICS:
+ return "high";
+ case SUPER_TECTONICS:
+ return "super";
+ default:
+ snprintf (buf, sizeof buf, "%d", tectonics);
+ return buf;
+ }
+}
+
+// atmosphere is what you get from HINIBBLE (planetFrame.AtmoAndDensity)
+const char *
+atmosphereString (BYTE atmosphere)
+{
+ switch (atmosphere)
+ {
+ case LIGHT:
+ return "thin";
+ case MEDIUM:
+ return "normal";
+ case HEAVY:
+ return "thick";
+ default:
+ return "super thick";
+ }
+}
+
+// density is what you get from LONIBBLE (planetFrame.AtmoAndDensity)
+const char *
+densityString (BYTE density)
+{
+ switch (density)
+ {
+ case GAS_DENSITY:
+ return "gaseous";
+ case LIGHT_DENSITY:
+ return "light";
+ case LOW_DENSITY:
+ return "low";
+ case NORMAL_DENSITY:
+ return "normal";
+ case HIGH_DENSITY:
+ return "high";
+ case SUPER_DENSITY:
+ return "super high";
+ default:
+ // Should not happen
+ return "???";
+ }
+}
+
+// quality is what you get from DEPOSIT_QUALITY (elementEntry.Density)
+const char *
+depositQualityString (BYTE quality)
+{
+ switch (quality)
+ {
+ case LIGHT:
+ return "low";
+ case MEDIUM:
+ return "medium";
+ case HEAVY:
+ return "high";
+ default:
+ // Should not happen
+ return "???";
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+// playerNr should be 0 or 1
+STARSHIP*
+findPlayerShip (SIZE playerNr)
+{
+ HELEMENT hElement, hNextElement;
+
+ for (hElement = GetHeadElement (); hElement; hElement = hNextElement)
+ {
+ ELEMENT *ElementPtr;
+
+ LockElement (hElement, &ElementPtr);
+ hNextElement = GetSuccElement (ElementPtr);
+
+ if ((ElementPtr->state_flags & PLAYER_SHIP) &&
+ ElementPtr->playerNr == playerNr)
+ {
+ STARSHIP *StarShipPtr;
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ UnlockElement (hElement);
+ return StarShipPtr;
+ }
+
+ UnlockElement (hElement);
+ }
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+resetCrewBattle (void)
+{
+ STARSHIP *StarShipPtr;
+ COUNT delta;
+ CONTEXT OldContext;
+
+ if (!(GLOBAL (CurrentActivity) & IN_BATTLE) ||
+ (inHQSpace ()))
+ return;
+
+ StarShipPtr = findPlayerShip (RPG_PLAYER_NUM);
+ if (StarShipPtr == NULL || StarShipPtr->RaceDescPtr == NULL)
+ return;
+
+ delta = StarShipPtr->RaceDescPtr->ship_info.max_crew -
+ StarShipPtr->RaceDescPtr->ship_info.crew_level;
+
+ OldContext = SetContext (StatusContext);
+ DeltaCrew (StarShipPtr->hShip, delta);
+ SetContext (OldContext);
+}
+
+void
+resetEnergyBattle (void)
+{
+ STARSHIP *StarShipPtr;
+ COUNT delta;
+ CONTEXT OldContext;
+
+ if (!(GLOBAL (CurrentActivity) & IN_BATTLE) ||
+ (inHQSpace ()))
+ return;
+
+ StarShipPtr = findPlayerShip (RPG_PLAYER_NUM);
+ if (StarShipPtr == NULL || StarShipPtr->RaceDescPtr == NULL)
+ return;
+
+ delta = StarShipPtr->RaceDescPtr->ship_info.max_energy -
+ StarShipPtr->RaceDescPtr->ship_info.energy_level;
+
+ OldContext = SetContext (StatusContext);
+ DeltaEnergy (StarShipPtr->hShip, delta);
+ SetContext (OldContext);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+// This function should help in making sure that gamestr.h matches
+// gamestrings.txt.
+void
+dumpStrings (FILE *out)
+{
+#define STRINGIZE(a) #a
+#define MAKE_STRING_CATEGORY(prefix) \
+ { \
+ /* .name = */ STRINGIZE(prefix ## _BASE), \
+ /* .base = */ prefix ## _BASE, \
+ /* .count = */ prefix ## _COUNT \
+ }
+ struct {
+ const char *name;
+ size_t base;
+ size_t count;
+ } categories[] = {
+ { "0", 0, 0 },
+ MAKE_STRING_CATEGORY(STAR_STRING),
+ MAKE_STRING_CATEGORY(DEVICE_STRING),
+ MAKE_STRING_CATEGORY(CARGO_STRING),
+ MAKE_STRING_CATEGORY(ELEMENTS_STRING),
+ MAKE_STRING_CATEGORY(SCAN_STRING),
+ MAKE_STRING_CATEGORY(STAR_NUMBER),
+ MAKE_STRING_CATEGORY(PLANET_NUMBER),
+ MAKE_STRING_CATEGORY(MONTHS_STRING),
+ MAKE_STRING_CATEGORY(FEEDBACK_STRING),
+ MAKE_STRING_CATEGORY(STARBASE_STRING),
+ MAKE_STRING_CATEGORY(ENCOUNTER_STRING),
+ MAKE_STRING_CATEGORY(NAVIGATION_STRING),
+ MAKE_STRING_CATEGORY(NAMING_STRING),
+ MAKE_STRING_CATEGORY(MELEE_STRING),
+ MAKE_STRING_CATEGORY(SAVEGAME_STRING),
+ MAKE_STRING_CATEGORY(OPTION_STRING),
+ MAKE_STRING_CATEGORY(QUITMENU_STRING),
+ MAKE_STRING_CATEGORY(STATUS_STRING),
+ MAKE_STRING_CATEGORY(FLAGSHIP_STRING),
+ MAKE_STRING_CATEGORY(ORBITSCAN_STRING),
+ MAKE_STRING_CATEGORY(MAINMENU_STRING),
+ MAKE_STRING_CATEGORY(NETMELEE_STRING),
+ { "GAMESTR_COUNT", GAMESTR_COUNT, (size_t) -1 }
+ };
+ size_t numCategories = sizeof categories / sizeof categories[0];
+ size_t numStrings = GetStringTableCount (GameStrings);
+ size_t stringI;
+ size_t categoryI = 0;
+
+ // Start with a sanity check to see if gamestr.h has been changed but
+ // not this file.
+ for (categoryI = 0; categoryI < numCategories - 1; categoryI++) {
+ if (categories[categoryI].base + categories[categoryI].count !=
+ categories[categoryI + 1].base) {
+ fprintf(stderr, "Error: String category list in dumpStrings() is "
+ "not up to date.\n");
+ return;
+ }
+ }
+
+ if (GAMESTR_COUNT != numStrings) {
+ fprintf(stderr, "Warning: GAMESTR_COUNT is %d, but GameStrings "
+ "contains %d strings.\n", GAMESTR_COUNT, numStrings);
+ }
+
+ categoryI = 0;
+ for (stringI = 0; stringI < numStrings; stringI++) {
+ while (categoryI < numCategories &&
+ stringI >= categories[categoryI + 1].base)
+ categoryI++;
+ fprintf(out, "[ %s + %d ] %s\n", categories[categoryI].name,
+ stringI - categories[categoryI].base, GAME_STRING(stringI));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+
+static Color
+hsvaToRgba (double hue, double sat, double val, BYTE alpha)
+{
+ unsigned int hi = (int) (hue / 60.0);
+ double f = (hue / 60.0) - ((int) (hue / 60.0));
+ double p = val * (1.0 - sat);
+ double q = val * (1.0 - f * sat);
+ double t = val * (1.0 - (1.0 - f * sat));
+
+ // Convert p, q, t, and v from [0..1] to [0..255]
+ BYTE pb = (BYTE) (p * 255.0 + 0.5);
+ BYTE qb = (BYTE) (q * 255.0 + 0.5);
+ BYTE tb = (BYTE) (t * 255.0 + 0.5);
+ BYTE vb = (BYTE) (val * 255.0 + 0.5);
+
+ assert (hue >= 0.0 && hue < 360.0);
+ assert (sat >= 0 && sat <= 1.0);
+ assert (val >= 0 && val <= 1.0);
+ /*fprintf(stderr, "hsva = (%.1f, %.2f, %.2f, %.2d)\n",
+ hue, sat, val, alpha);*/
+
+ assert (hi < 6);
+ switch (hi) {
+ case 0: return BUILD_COLOR_RGBA (vb, tb, pb, alpha);
+ case 1: return BUILD_COLOR_RGBA (qb, vb, pb, alpha);
+ case 2: return BUILD_COLOR_RGBA (pb, vb, tb, alpha);
+ case 3: return BUILD_COLOR_RGBA (pb, qb, vb, alpha);
+ case 4: return BUILD_COLOR_RGBA (tb, pb, vb, alpha);
+ case 5: return BUILD_COLOR_RGBA (vb, pb, qb, alpha);
+ }
+
+ // Should not happen.
+ return BUILD_COLOR_RGBA (0, 0, 0, alpha);
+}
+
+// Returns true iff this context has a visible FRAME.
+static bool
+isContextVisible (CONTEXT context)
+{
+ FRAME contextFrame;
+
+ // Save the original context.
+ CONTEXT oldContext = SetContext (context);
+
+ // Get the frame of the specified context.
+ contextFrame = GetContextFGFrame ();
+
+ // Restore the original context.
+ SetContext (oldContext);
+
+ return contextFrame == Screen;
+}
+
+static size_t
+countVisibleContexts (void)
+{
+ size_t contextCount;
+ CONTEXT context;
+
+ contextCount = 0;
+ for (context = GetFirstContext (); context != NULL;
+ context = GetNextContext (context))
+ {
+ if (!isContextVisible (context))
+ continue;
+
+ contextCount++;
+ }
+
+ return contextCount;
+}
+
+static void
+drawContext (CONTEXT context, double hue /* no pun intended */)
+{
+ FRAME drawFrame;
+ CONTEXT oldContext;
+ FONT oldFont;
+ DrawMode oldMode;
+ Color oldFgCol;
+ Color rectCol;
+ Color lineCol;
+ Color textCol;
+ bool haveClippingRect;
+ RECT rect;
+ LINE line;
+ TEXT text;
+ POINT p1, p2, p3, p4;
+
+ drawFrame = GetContextFGFrame ();
+ rectCol = hsvaToRgba (hue, 1.0, 0.5, 100);
+ lineCol = hsvaToRgba (hue, 1.0, 1.0, 90);
+ textCol = lineCol;
+
+ // Save the original context.
+ oldContext = SetContext (context);
+
+ // Get the clipping rectangle of the specified context.
+ haveClippingRect = GetContextClipRect (&rect);
+
+ // Switch back the old context; we're going to draw in it.
+ (void) SetContext (oldContext);
+
+ p1 = rect.corner;
+ p2.x = rect.corner.x + rect.extent.width - 1;
+ p2.y = rect.corner.y;
+ p3.x = rect.corner.x;
+ p3.y = rect.corner.y + rect.extent.height - 1;
+ p4.x = rect.corner.x + rect.extent.width - 1;
+ p4.y = rect.corner.y + rect.extent.height - 1;
+
+ oldFgCol = SetContextForeGroundColor (rectCol);
+ DrawFilledRectangle (&rect);
+
+ SetContextForeGroundColor (lineCol);
+ line.first = p1; line.second = p2; DrawLine (&line);
+ line.first = p2; line.second = p4; DrawLine (&line);
+ line.first = p1; line.second = p3; DrawLine (&line);
+ line.first = p3; line.second = p4; DrawLine (&line);
+ line.first = p1; line.second = p4; DrawLine (&line);
+ line.first = p2; line.second = p3; DrawLine (&line);
+ // Gimme C'99! So I can do:
+ // DrawLine ((LINE) { .first = p1, .second = p2 })
+
+ oldFont = SetContextFont (TinyFont);
+ SetContextForeGroundColor (textCol);
+ // Text prim does not yet support alpha via Color.a
+ oldMode = SetContextDrawMode (MAKE_DRAW_MODE (DRAW_ALPHA, textCol.a));
+ text.baseline.x = (p1.x + (p2.x + 1)) / 2;
+ text.baseline.y = p1.y + 8;
+ text.pStr = GetContextName (context);
+ text.align = ALIGN_CENTER;
+ text.CharCount = (COUNT) ~0;
+ font_DrawText (&text);
+ (void) SetContextDrawMode (oldMode);
+
+ (void) SetContextForeGroundColor (oldFgCol);
+ (void) SetContextFont (oldFont);
+}
+
+static void
+describeContext (FILE *out, const CONTEXT context) {
+ RECT rect;
+ CONTEXT oldContext = SetContext (context);
+
+ GetContextClipRect (&rect);
+ fprintf(out, "Context '%s':\n"
+ "\tClipRect = (%d, %d)-(%d, %d) (%d x %d)\n",
+ GetContextName (context),
+ rect.corner.x, rect.corner.y,
+ rect.corner.x + rect.extent.width,
+ rect.corner.y + rect.extent.height,
+ rect.extent.width, rect.extent.height);
+
+ SetContext (oldContext);
+}
+
+
+typedef struct wait_state
+{
+ // standard state required by DoInput
+ BOOLEAN (*InputFunc) (struct wait_state *self);
+} WAIT_STATE;
+
+
+// Maybe move to elsewhere, where it can be reused?
+static BOOLEAN
+waitForKey (struct wait_state *self) {
+ if (PulsedInputState.menu[KEY_MENU_SELECT] ||
+ PulsedInputState.menu[KEY_MENU_CANCEL])
+ return FALSE;
+
+ SleepThread (ONE_SECOND / 20);
+
+ (void) self;
+ return TRUE;
+}
+
+// Maybe move to elsewhere, where it can be reused?
+static FRAME
+getScreen (void)
+{
+ CONTEXT oldContext = SetContext (ScreenContext);
+ FRAME savedFrame;
+ RECT screenRect;
+
+ screenRect.corner.x = 0;
+ screenRect.corner.y = 0;
+ screenRect.extent.width = ScreenWidth;
+ screenRect.extent.height = ScreenHeight;
+ savedFrame = CaptureDrawable (LoadDisplayPixmap (&screenRect, (FRAME) 0));
+
+ (void) SetContext (oldContext);
+ return savedFrame;
+}
+
+static void
+putScreen (FRAME savedFrame) {
+ STAMP stamp;
+
+ CONTEXT oldContext = SetContext (ScreenContext);
+
+ stamp.origin.x = 0;
+ stamp.origin.y = 0;
+ stamp.frame = savedFrame;
+ DrawStamp (&stamp);
+
+ (void) SetContext (oldContext);
+}
+
+// Show the contexts on the screen.
+// Must be called from the main thread.
+void
+debugContexts (void)
+{
+ static volatile bool inDebugContexts = false;
+ // Prevent this function from being called from within itself.
+
+ CONTEXT orgContext;
+ CONTEXT debugDrawContext;
+ // We're going to use this context to draw in.
+ FRAME debugDrawFrame;
+ double hueIncrement;
+ size_t visibleContextI;
+ CONTEXT context;
+ size_t contextCount;
+ FRAME savedScreen;
+
+ // Prevent this function from being called from within itself.
+ if (inDebugContexts)
+ return;
+ inDebugContexts = true;
+
+ contextCount = countVisibleContexts ();
+ if (contextCount == 0)
+ {
+ goto out;
+ }
+
+ savedScreen = getScreen ();
+ FlushGraphics ();
+ // Make sure that the screen has actually been captured,
+ // before we use the frame.
+
+ // Create a new frame to draw on.
+ debugDrawContext = CreateContext ("debugDrawContext");
+ // New work frame is a copy of the original.
+ debugDrawFrame = CaptureDrawable (CloneFrame (savedScreen));
+ orgContext = SetContext (debugDrawContext);
+ SetContextFGFrame (debugDrawFrame);
+
+ hueIncrement = 360.0 / contextCount;
+
+ visibleContextI = 0;
+ for (context = GetFirstContext (); context != NULL;
+ context = GetNextContext (context))
+ {
+ if (context == debugDrawContext) {
+ // Skip our own context.
+ continue;
+ }
+
+ if (isContextVisible (context))
+ {
+ // Only draw the visible contexts.
+ drawContext (context, visibleContextI * hueIncrement);
+ visibleContextI++;
+ }
+
+ describeContext (stderr, context);
+ }
+
+ // Blit the final debugging frame to the screen.
+ putScreen (debugDrawFrame);
+
+ // Wait for a key:
+ {
+ WAIT_STATE state;
+ state.InputFunc = waitForKey;
+ DoInput(&state, TRUE);
+ }
+
+ SetContext (orgContext);
+
+ // Destroy the debugging frame and context.
+ DestroyContext (debugDrawContext);
+ // This does nothing with the drawable set with
+ // SetContextFGFrame().
+ DestroyDrawable (ReleaseDrawable (debugDrawFrame));
+
+ putScreen (savedScreen);
+
+ DestroyDrawable (ReleaseDrawable (savedScreen));
+
+out:
+ inDebugContexts = false;
+}
+
+#endif /* DEBUG */
+