summaryrefslogtreecommitdiff
path: root/src/uqm/comm/melnorm/melnorm.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/uqm/comm/melnorm/melnorm.c')
-rw-r--r--src/uqm/comm/melnorm/melnorm.c1855
1 files changed, 1855 insertions, 0 deletions
diff --git a/src/uqm/comm/melnorm/melnorm.c b/src/uqm/comm/melnorm/melnorm.c
new file mode 100644
index 0000000..00b6a07
--- /dev/null
+++ b/src/uqm/comm/melnorm/melnorm.c
@@ -0,0 +1,1855 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/gameev.h"
+#include "uqm/shipcont.h"
+#include "libs/inplib.h"
+#include "libs/mathlib.h"
+
+#include "uqm/hyper.h"
+ // for SOL_X/SOL_Y
+#include "uqm/planets/planets.h"
+ // for xxx_DISASTER
+#include "uqm/sis.h"
+
+
+static const NUMBER_SPEECH_DESC melnorme_numbers_english;
+
+static LOCDATA melnorme_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ MELNORME_PMAP_ANIM, /* AlienFrame */
+ MELNORME_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ MELNORME_COLOR_MAP, /* AlienColorMap */
+ MELNORME_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ MELNORME_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 4, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 6, /* StartIndex */
+ 5, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 12, 0, /* FrameRate */
+ ONE_SECOND * 4, ONE_SECOND * 4,/* RestartRate */
+ (1 << 1), /* BlockMask */
+ },
+ {
+ 11, /* StartIndex */
+ 9, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND * 4, ONE_SECOND * 4,/* RestartRate */
+ (1 << 0), /* BlockMask */
+ },
+ {
+ 20, /* StartIndex */
+ 2, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 22, /* StartIndex */
+ 2, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 5, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ &melnorme_numbers_english, /* AlienNumberSpeech - default */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static COUNT melnorme_digit_names[] =
+{
+ ENUMERATE_ZERO,
+ ENUMERATE_ONE,
+ ENUMERATE_TWO,
+ ENUMERATE_THREE,
+ ENUMERATE_FOUR,
+ ENUMERATE_FIVE,
+ ENUMERATE_SIX,
+ ENUMERATE_SEVEN,
+ ENUMERATE_EIGHT,
+ ENUMERATE_NINE
+};
+
+static COUNT melnorme_teen_names[] =
+{
+ ENUMERATE_TEN,
+ ENUMERATE_ELEVEN,
+ ENUMERATE_TWELVE,
+ ENUMERATE_THIRTEEN,
+ ENUMERATE_FOURTEEN,
+ ENUMERATE_FIFTEEN,
+ ENUMERATE_SIXTEEN,
+ ENUMERATE_SEVENTEEN,
+ ENUMERATE_EIGHTEEN,
+ ENUMERATE_NINETEEN
+};
+
+static COUNT melnorme_tens_names[] =
+{
+ 0, /* invalid */
+ 0, /* skip digit */
+ ENUMERATE_TWENTY,
+ ENUMERATE_THIRTY,
+ ENUMERATE_FOURTY,
+ ENUMERATE_FIFTY,
+ ENUMERATE_SIXTY,
+ ENUMERATE_SEVENTY,
+ ENUMERATE_EIGHTY,
+ ENUMERATE_NINETY
+};
+
+static const NUMBER_SPEECH_DESC melnorme_numbers_english =
+{
+ 5, /* NumDigits */
+ {
+ { /* 1000-999999 */
+ 1000, /* Divider */
+ 0, /* Subtrahend */
+ NULL, /* StrDigits - recurse */
+ NULL, /* Names - not used */
+ ENUMERATE_THOUSAND /* CommonIndex */
+ },
+ { /* 100-999 */
+ 100, /* Divider */
+ 0, /* Subtrahend */
+ melnorme_digit_names, /* StrDigits */
+ NULL, /* Names - not used */
+ ENUMERATE_HUNDRED /* CommonIndex */
+ },
+ { /* 20-99 */
+ 10, /* Divider */
+ 0, /* Subtrahend */
+ melnorme_tens_names, /* StrDigits */
+ NULL, /* Names - not used */
+ 0 /* CommonIndex - not used */
+ },
+ { /* 10-19 */
+ 1, /* Divider */
+ 10, /* Subtrahend */
+ melnorme_teen_names, /* StrDigits */
+ NULL, /* Names - not used */
+ 0 /* CommonIndex - not used */
+ },
+ { /* 0-9 */
+ 1, /* Divider */
+ 0, /* Subtrahend */
+ melnorme_digit_names, /* StrDigits */
+ NULL, /* Names - not used */
+ 0 /* CommonIndex - not used */
+ }
+ }
+};
+
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof (*array))
+
+
+//////////////Technology System///////////////////////
+// This section deals with enabling and checking for
+// various technologies. It should probably be
+// migrated to its own file.
+
+// Identifiers for the various technologies
+typedef enum
+{
+ TECH_MODULE_BLASTER,
+ TECH_LANDER_SPEED,
+ TECH_MODULE_ANTIMISSILE,
+ TECH_LANDER_SHIELD_BIO,
+ TECH_LANDER_CARGO,
+ TECH_MODULE_BIGFUELTANK,
+ TECH_LANDER_RAPIDFIRE,
+ TECH_LANDER_SHIELD_QUAKE,
+ TECH_MODULE_TRACKING,
+ TECH_LANDER_SHIELD_LIGHTNING,
+ TECH_LANDER_SHIELD_HEAT,
+ TECH_MODULE_CANNON,
+ TECH_MODULE_FURNACE,
+} TechId_t;
+
+// Group the technologies into three subtypes
+typedef enum
+{
+ TECH_TYPE_MODULE, // Flagship modules
+ // subtype = moduleId, info = cost
+ // Cost will be scaled by MODULE_COST_SCALE.
+ TECH_TYPE_LANDER_SHIELD, // Lander shield enhancements
+ // subtype = disaster type, info = unused
+ TECH_TYPE_STATE // Other game state changes
+ // subtype = stateId, info = state value
+} TechType_t;
+
+
+// Define the information specifying a particular technology
+typedef struct
+{
+ TechId_t id; // ID of the technology
+ TechType_t type; // Type of the technology
+ int subtype; // Subtype of the technology
+ int info; // Supplemental information
+} TechData;
+
+
+// A table of the available technologies.
+// This should really be an associative map of TechIds to tech data records,
+// but implementing that would be excessive.
+static const TechData tech_data_table[] =
+{
+ // Tech ID Tech Type, Supplemental info
+ { TECH_MODULE_BLASTER, TECH_TYPE_MODULE, BLASTER_WEAPON, 4000 },
+ { TECH_LANDER_SPEED, TECH_TYPE_STATE, IMPROVED_LANDER_SPEED, 1 },
+ { TECH_MODULE_ANTIMISSILE, TECH_TYPE_MODULE, ANTIMISSILE_DEFENSE, 4000 },
+ { TECH_LANDER_SHIELD_BIO, TECH_TYPE_LANDER_SHIELD, BIOLOGICAL_DISASTER, -1 },
+ { TECH_LANDER_CARGO, TECH_TYPE_STATE, IMPROVED_LANDER_CARGO, 1 },
+ { TECH_MODULE_BIGFUELTANK, TECH_TYPE_MODULE, HIGHEFF_FUELSYS, 1000 },
+ { TECH_LANDER_RAPIDFIRE, TECH_TYPE_STATE, IMPROVED_LANDER_SHOT, 1 },
+ { TECH_LANDER_SHIELD_QUAKE, TECH_TYPE_LANDER_SHIELD, EARTHQUAKE_DISASTER, -1 },
+ { TECH_MODULE_TRACKING, TECH_TYPE_MODULE, TRACKING_SYSTEM, 5000 },
+ { TECH_LANDER_SHIELD_LIGHTNING, TECH_TYPE_LANDER_SHIELD, LIGHTNING_DISASTER, -1 },
+ { TECH_LANDER_SHIELD_HEAT, TECH_TYPE_LANDER_SHIELD, LAVASPOT_DISASTER, -1 },
+ { TECH_MODULE_CANNON, TECH_TYPE_MODULE, CANNON_WEAPON, 6000 },
+ { TECH_MODULE_FURNACE, TECH_TYPE_MODULE, SHIVA_FURNACE, 4000 },
+};
+const size_t NUM_TECHNOLOGIES = ARRAY_SIZE (tech_data_table);
+
+// Lookup function to get the data for a particular tech
+static const TechData*
+GetTechData (TechId_t techId)
+{
+ size_t i = 0;
+ for (i = 0; i < NUM_TECHNOLOGIES; ++i)
+ {
+ if (tech_data_table[i].id == techId)
+ return &tech_data_table[i];
+ }
+ return NULL;
+}
+
+
+// We have to explicitly switch on the state ID because the xxx_GAME_STATE
+// macros use preprocessor stringizing.
+static bool
+HasStateTech (int stateId)
+{
+ switch (stateId)
+ {
+ case IMPROVED_LANDER_SPEED:
+ return GET_GAME_STATE (IMPROVED_LANDER_SPEED);
+ case IMPROVED_LANDER_CARGO:
+ return GET_GAME_STATE (IMPROVED_LANDER_CARGO);
+ case IMPROVED_LANDER_SHOT:
+ return GET_GAME_STATE (IMPROVED_LANDER_SHOT);
+ }
+ return false;
+}
+
+static void
+GrantStateTech (int stateId, BYTE value)
+{
+ switch (stateId)
+ {
+ case IMPROVED_LANDER_SPEED:
+ SET_GAME_STATE (IMPROVED_LANDER_SPEED, value);
+ return;
+ case IMPROVED_LANDER_CARGO:
+ SET_GAME_STATE (IMPROVED_LANDER_CARGO, value);
+ return;
+ case IMPROVED_LANDER_SHOT:
+ SET_GAME_STATE (IMPROVED_LANDER_SHOT, value);
+ return;
+ }
+}
+
+static bool
+HasTech (TechId_t techId)
+{
+ const TechData* techData = GetTechData (techId);
+ if (!techData)
+ return false;
+
+ switch (techData->type)
+ {
+ case TECH_TYPE_MODULE:
+ return GLOBAL (ModuleCost[techData->subtype]) != 0;
+ case TECH_TYPE_LANDER_SHIELD:
+ return (GET_GAME_STATE (LANDER_SHIELDS) & (1 << techData->subtype)) != 0;
+ case TECH_TYPE_STATE:
+ return HasStateTech (techData->subtype);
+ }
+ return false;
+}
+
+static void
+GrantTech (TechId_t techId)
+{
+ const TechData* techData = GetTechData (techId);
+ if (!techData)
+ return;
+
+ switch (techData->type)
+ {
+ case TECH_TYPE_MODULE:
+ GLOBAL (ModuleCost[techData->subtype]) = techData->info / MODULE_COST_SCALE;
+ return;
+ case TECH_TYPE_LANDER_SHIELD:
+ {
+ COUNT state = GET_GAME_STATE (LANDER_SHIELDS) | (1 << techData->subtype);
+ SET_GAME_STATE (LANDER_SHIELDS, state);
+ return;
+ }
+ case TECH_TYPE_STATE:
+ GrantStateTech (techData->subtype, techData->info);
+ return;
+ }
+}
+
+
+////////////Melnorme Sales System///////////
+// This section contains code related to Melnorme sales
+
+// Many of the conversation lines in strings.h fall into groups
+// of sequential responses. These structures allow those
+// responses to be interated through.
+static const int ok_buy_event_lines[] =
+{
+ OK_BUY_EVENT_1, OK_BUY_EVENT_2, OK_BUY_EVENT_3, OK_BUY_EVENT_4,
+ OK_BUY_EVENT_5, OK_BUY_EVENT_6, OK_BUY_EVENT_7, OK_BUY_EVENT_8
+};
+const size_t NUM_EVENT_ITEMS = ARRAY_SIZE (ok_buy_event_lines);
+
+static const int ok_buy_alien_race_lines[] =
+{
+ OK_BUY_ALIEN_RACE_1, OK_BUY_ALIEN_RACE_2, OK_BUY_ALIEN_RACE_3,
+ OK_BUY_ALIEN_RACE_4, OK_BUY_ALIEN_RACE_5, OK_BUY_ALIEN_RACE_6,
+ OK_BUY_ALIEN_RACE_7, OK_BUY_ALIEN_RACE_8, OK_BUY_ALIEN_RACE_9,
+ OK_BUY_ALIEN_RACE_10, OK_BUY_ALIEN_RACE_11, OK_BUY_ALIEN_RACE_12,
+ OK_BUY_ALIEN_RACE_13, OK_BUY_ALIEN_RACE_14, OK_BUY_ALIEN_RACE_15,
+ OK_BUY_ALIEN_RACE_16
+};
+const size_t NUM_ALIEN_RACE_ITEMS = ARRAY_SIZE (ok_buy_alien_race_lines);
+
+static const int ok_buy_history_lines[] =
+{
+ OK_BUY_HISTORY_1, OK_BUY_HISTORY_2, OK_BUY_HISTORY_3,
+ OK_BUY_HISTORY_4, OK_BUY_HISTORY_5, OK_BUY_HISTORY_6,
+ OK_BUY_HISTORY_7, OK_BUY_HISTORY_8, OK_BUY_HISTORY_9
+};
+const size_t NUM_HISTORY_ITEMS = ARRAY_SIZE (ok_buy_history_lines);
+
+static const int hello_and_down_to_business_lines[] =
+{
+ HELLO_AND_DOWN_TO_BUSINESS_1, HELLO_AND_DOWN_TO_BUSINESS_2,
+ HELLO_AND_DOWN_TO_BUSINESS_3, HELLO_AND_DOWN_TO_BUSINESS_4,
+ HELLO_AND_DOWN_TO_BUSINESS_5, HELLO_AND_DOWN_TO_BUSINESS_6,
+ HELLO_AND_DOWN_TO_BUSINESS_7, HELLO_AND_DOWN_TO_BUSINESS_8,
+ HELLO_AND_DOWN_TO_BUSINESS_9, HELLO_AND_DOWN_TO_BUSINESS_10
+};
+const size_t NUM_HELLO_LINES = ARRAY_SIZE (hello_and_down_to_business_lines);
+
+static const int rescue_lines[] =
+{
+ RESCUE_EXPLANATION, RESCUE_AGAIN_1, RESCUE_AGAIN_2,
+ RESCUE_AGAIN_3, RESCUE_AGAIN_4, RESCUE_AGAIN_5
+};
+const size_t NUM_RESCUE_LINES = ARRAY_SIZE (rescue_lines);
+
+// How many lines are available in the given array?
+static size_t
+GetNumLines (const int array[])
+{
+ if (array == ok_buy_event_lines)
+ return NUM_EVENT_ITEMS;
+ else if (array == ok_buy_alien_race_lines)
+ return NUM_ALIEN_RACE_ITEMS;
+ else if (array == ok_buy_history_lines)
+ return NUM_HISTORY_ITEMS;
+ else if (array == hello_and_down_to_business_lines)
+ return NUM_HELLO_LINES;
+ else if (array == rescue_lines)
+ return NUM_RESCUE_LINES;
+ return 0;
+}
+
+// Get the line, with range checking.
+// Returns the last line if the desired one is out of range.
+static int
+GetLineSafe (const int array[], size_t linenum)
+{
+ const size_t array_size = GetNumLines (array);
+ assert (array_size > 0);
+ if (linenum >= array_size)
+ linenum = array_size - 1;
+ return array[linenum];
+}
+
+// Data structure to hold the Melnorme's info on a technology
+typedef struct
+{
+ TechId_t techId; // ID of technology
+ int price; // Melnorme's price to sell
+ int sale_line; // Sales pitch line ID
+ int sold_line; // Post-sale line ID
+} TechSaleData;
+
+// Right now, all techs have the same price.
+#define TECHPRICE (75 * BIO_CREDIT_VALUE)
+
+static const TechSaleData tech_sale_catalog[] =
+{
+ { TECH_MODULE_BLASTER, TECHPRICE, NEW_TECH_1, OK_BUY_NEW_TECH_1 },
+ { TECH_LANDER_SPEED, TECHPRICE, NEW_TECH_2, OK_BUY_NEW_TECH_2 },
+ { TECH_MODULE_ANTIMISSILE, TECHPRICE, NEW_TECH_3, OK_BUY_NEW_TECH_3 },
+ { TECH_LANDER_SHIELD_BIO, TECHPRICE, NEW_TECH_4, OK_BUY_NEW_TECH_4 },
+ { TECH_LANDER_CARGO, TECHPRICE, NEW_TECH_5, OK_BUY_NEW_TECH_5 },
+ { TECH_MODULE_BIGFUELTANK, TECHPRICE, NEW_TECH_6, OK_BUY_NEW_TECH_6 },
+ { TECH_LANDER_RAPIDFIRE, TECHPRICE, NEW_TECH_7, OK_BUY_NEW_TECH_7 },
+ { TECH_LANDER_SHIELD_QUAKE, TECHPRICE, NEW_TECH_8, OK_BUY_NEW_TECH_8 },
+ { TECH_MODULE_TRACKING, TECHPRICE, NEW_TECH_9, OK_BUY_NEW_TECH_9 },
+ { TECH_LANDER_SHIELD_LIGHTNING, TECHPRICE, NEW_TECH_10, OK_BUY_NEW_TECH_10 },
+ { TECH_LANDER_SHIELD_HEAT, TECHPRICE, NEW_TECH_11, OK_BUY_NEW_TECH_11 },
+ { TECH_MODULE_CANNON, TECHPRICE, NEW_TECH_12, OK_BUY_NEW_TECH_12 },
+ { TECH_MODULE_FURNACE, TECHPRICE, NEW_TECH_13, OK_BUY_NEW_TECH_13 },
+};
+const size_t NUM_TECH_ITEMS = ARRAY_SIZE (tech_sale_catalog);
+
+// Return the next tech for sale that the player doesn't already have.
+// Returns NULL if the player has all the techs.
+static const TechSaleData*
+GetNextTechForSale (void)
+{
+ size_t i = 0;
+ for (i = 0; i < NUM_TECH_ITEMS; ++i)
+ {
+ if (!HasTech (tech_sale_catalog[i].techId))
+ return &tech_sale_catalog[i];
+ }
+
+ return NULL;
+}
+
+///////////End Melnorme Sales Section//////////////////
+
+static StatMsgMode prevMsgMode;
+
+static void DoFirstMeeting (RESPONSE_REF R);
+
+static COUNT
+ShipWorth (void)
+{
+ BYTE i;
+ SBYTE crew_pods;
+ COUNT worth;
+
+ worth = GLOBAL_SIS (NumLanders)
+ * GLOBAL (ModuleCost[PLANET_LANDER]);
+ for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
+ {
+ if (GLOBAL_SIS (DriveSlots[i]) < EMPTY_SLOT)
+ worth += GLOBAL (ModuleCost[FUSION_THRUSTER]);
+ }
+ for (i = 0; i < NUM_JET_SLOTS; ++i)
+ {
+ if (GLOBAL_SIS (JetSlots[i]) < EMPTY_SLOT)
+ worth += GLOBAL (ModuleCost[TURNING_JETS]);
+ }
+
+ crew_pods = -(SBYTE)(
+ (GLOBAL_SIS (CrewEnlisted) + CREW_POD_CAPACITY - 1)
+ / CREW_POD_CAPACITY
+ );
+ for (i = 0; i < NUM_MODULE_SLOTS; ++i)
+ {
+ BYTE which_module;
+
+ which_module = GLOBAL_SIS (ModuleSlots[i]);
+ if (which_module < BOMB_MODULE_0
+ && (which_module != CREW_POD || ++crew_pods > 0))
+ {
+ worth += GLOBAL (ModuleCost[which_module]);
+ }
+ }
+
+ return (worth);
+}
+
+static COUNT rescue_fuel;
+static SIS_STATE SIS_copy;
+
+// Extract method to return the response string index
+// for stripping a given module.
+static int
+GetStripModuleRef (int moduleID)
+{
+ switch (moduleID)
+ {
+ case PLANET_LANDER: return LANDERS;
+ case FUSION_THRUSTER: return THRUSTERS;
+ case TURNING_JETS: return JETS;
+ case CREW_POD: return PODS;
+ case STORAGE_BAY: return BAYS;
+ case DYNAMO_UNIT: return DYNAMOS;
+ case SHIVA_FURNACE: return FURNACES;
+ case GUN_WEAPON: return GUNS;
+ case BLASTER_WEAPON: return BLASTERS;
+ case CANNON_WEAPON: return CANNONS;
+ case TRACKING_SYSTEM: return TRACKERS;
+ case ANTIMISSILE_DEFENSE: return DEFENSES;
+ // If a modder has added new modules, should it really
+ // be a fatal error if the Melnorme don't know about
+ // them?
+ default:
+ assert (0 && "Unknown module");
+ }
+ return 0;
+}
+
+static DWORD
+getStripRandomSeed (void)
+{
+ DWORD x, y;
+ // We truncate the location because encounters move the ship slightly in
+ // HSpace, and throw some other relatively immutable values in the mix to
+ // vary the deal when stuck at the same general location again.
+ // It is still possible but unlikely for encounters to move the ship into
+ // another truncation sector so the player could choose from 2 deals.
+ x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x)) / 100;
+ y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y)) / 100;
+ // prime numbers help randomness
+ return y * 1013 + x + GLOBAL_SIS (NumLanders)
+ + GLOBAL_SIS (ModuleSlots[1]) + GLOBAL_SIS (ModuleSlots[4])
+ + GLOBAL_SIS (ModuleSlots[7]) + GLOBAL_SIS (ModuleSlots[10]);
+}
+
+static BOOLEAN
+StripShip (COUNT fuel_required)
+{
+ BYTE i, which_module;
+ SBYTE crew_pods;
+
+ SET_GAME_STATE (MELNORME_RESCUE_REFUSED, 0);
+
+ crew_pods = -(SBYTE)(
+ (GLOBAL_SIS (CrewEnlisted) + CREW_POD_CAPACITY - 1)
+ / CREW_POD_CAPACITY
+ );
+ if (fuel_required == 0)
+ {
+ GlobData.SIS_state = SIS_copy;
+ DeltaSISGauges (UNDEFINED_DELTA, rescue_fuel, UNDEFINED_DELTA);
+ }
+ else if (fuel_required == (COUNT)~0)
+ {
+ GLOBAL_SIS (NumLanders) = 0;
+ for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
+ GLOBAL_SIS (DriveSlots[i]) = EMPTY_SLOT + 0;
+ for (i = 0; i < NUM_JET_SLOTS; ++i)
+ GLOBAL_SIS (JetSlots[i]) = EMPTY_SLOT + 1;
+ if (GLOBAL_SIS (FuelOnBoard) > FUEL_RESERVE)
+ GLOBAL_SIS (FuelOnBoard) = FUEL_RESERVE;
+ GLOBAL_SIS (TotalBioMass) = 0;
+ GLOBAL_SIS (TotalElementMass) = 0;
+ for (i = 0; i < NUM_ELEMENT_CATEGORIES; ++i)
+ GLOBAL_SIS (ElementAmounts[i]) = 0;
+ for (i = 0; i < NUM_MODULE_SLOTS; ++i)
+ {
+ which_module = GLOBAL_SIS (ModuleSlots[i]);
+ if (which_module < BOMB_MODULE_0
+ && (which_module != CREW_POD
+ || ++crew_pods > 0))
+ GLOBAL_SIS (ModuleSlots[i]) = EMPTY_SLOT + 2;
+ }
+
+ DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA, UNDEFINED_DELTA);
+ }
+ else if (fuel_required)
+ {
+ SBYTE bays;
+ BYTE num_searches, beg_mod, end_mod;
+ COUNT worth, total;
+ BYTE module_count[BOMB_MODULE_0];
+ BYTE slot;
+ DWORD capacity;
+ RandomContext *rc;
+
+ // Bug #567
+ // In order to offer the same deal each time if it is refused, we seed
+ // the random number generator with our location, thus making the deal
+ // a repeatable pseudo-random function of where we got stuck and what,
+ // exactly, is on our ship.
+ rc = RandomContext_New();
+ RandomContext_SeedRandom (rc, getStripRandomSeed ());
+
+ SIS_copy = GlobData.SIS_state;
+ for (i = PLANET_LANDER; i < BOMB_MODULE_0; ++i)
+ module_count[i] = 0;
+
+ capacity = FUEL_RESERVE;
+ slot = NUM_MODULE_SLOTS - 1;
+ do
+ {
+ if (SIS_copy.ModuleSlots[slot] == FUEL_TANK
+ || SIS_copy.ModuleSlots[slot] == HIGHEFF_FUELSYS)
+ {
+ COUNT volume;
+
+ volume = SIS_copy.ModuleSlots[slot] == FUEL_TANK
+ ? FUEL_TANK_CAPACITY : HEFUEL_TANK_CAPACITY;
+ capacity += volume;
+ }
+ } while (slot--);
+ if (fuel_required > capacity)
+ fuel_required = capacity;
+
+ bays = -(SBYTE)(
+ (SIS_copy.TotalElementMass + STORAGE_BAY_CAPACITY - 1)
+ / STORAGE_BAY_CAPACITY
+ );
+ for (i = 0; i < NUM_MODULE_SLOTS; ++i)
+ {
+ which_module = SIS_copy.ModuleSlots[i];
+ if (which_module == CREW_POD)
+ ++crew_pods;
+ else if (which_module == STORAGE_BAY)
+ ++bays;
+ }
+
+ worth = fuel_required / FUEL_TANK_SCALE;
+ total = 0;
+ num_searches = 0;
+ beg_mod = end_mod = (BYTE)~0;
+ while (total < worth && ShipWorth () && ++num_searches)
+ {
+ DWORD rand_val;
+
+ rand_val = RandomContext_Random (rc);
+ switch (which_module = LOBYTE (LOWORD (rand_val)) % (CREW_POD + 1))
+ {
+ case PLANET_LANDER:
+ if (SIS_copy.NumLanders == 0)
+ continue;
+ --SIS_copy.NumLanders;
+ break;
+ case FUSION_THRUSTER:
+ for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
+ {
+ if (SIS_copy.DriveSlots[i] < EMPTY_SLOT)
+ break;
+ }
+ if (i == NUM_DRIVE_SLOTS)
+ continue;
+ SIS_copy.DriveSlots[i] = EMPTY_SLOT + 0;
+ break;
+ case TURNING_JETS:
+ for (i = 0; i < NUM_JET_SLOTS; ++i)
+ {
+ if (SIS_copy.JetSlots[i] < EMPTY_SLOT)
+ break;
+ }
+ if (i == NUM_JET_SLOTS)
+ continue;
+ SIS_copy.JetSlots[i] = EMPTY_SLOT + 1;
+ break;
+ case CREW_POD:
+ i = HIBYTE (LOWORD (rand_val)) % NUM_MODULE_SLOTS;
+ which_module = SIS_copy.ModuleSlots[i];
+ if (which_module >= BOMB_MODULE_0
+ || which_module == FUEL_TANK
+ || which_module == HIGHEFF_FUELSYS
+ || (which_module == STORAGE_BAY
+ && module_count[STORAGE_BAY] >= bays)
+ || (which_module == CREW_POD
+ && module_count[CREW_POD] >= crew_pods))
+ continue;
+ SIS_copy.ModuleSlots[i] = EMPTY_SLOT + 2;
+ break;
+ }
+
+ if (beg_mod == (BYTE)~0)
+ beg_mod = end_mod = which_module;
+ else if (which_module > end_mod)
+ end_mod = which_module;
+ ++module_count[which_module];
+ total += GLOBAL (ModuleCost[which_module]);
+ }
+ RandomContext_Delete (rc);
+
+ if (total == 0)
+ {
+ NPCPhrase (CHARITY);
+ DeltaSISGauges (0, fuel_required, 0);
+ return (FALSE);
+ }
+ else
+ {
+ NPCPhrase (RESCUE_OFFER);
+ rescue_fuel = fuel_required;
+ if (rescue_fuel == capacity)
+ NPCPhrase (RESCUE_TANKS);
+ else
+ NPCPhrase (RESCUE_HOME);
+ for (i = PLANET_LANDER; i < BOMB_MODULE_0; ++i)
+ {
+ if (module_count[i])
+ {
+ if (i == end_mod && i != beg_mod)
+ NPCPhrase (END_LIST_WITH_AND);
+ NPCPhrase (ENUMERATE_ONE + (module_count[i] - 1));
+ NPCPhrase (GetStripModuleRef (i));
+ }
+ }
+ }
+ }
+
+ return (TRUE);
+}
+
+static void
+ExitConversation (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, no_trade_now))
+ NPCPhrase (OK_NO_TRADE_NOW_BYE);
+ else if (PLAYER_SAID (R, youre_on))
+ {
+ NPCPhrase (YOU_GIVE_US_NO_CHOICE);
+
+ SET_GAME_STATE (MELNORME_ANGER, 1);
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, so_we_can_attack))
+ {
+ NPCPhrase (DECEITFUL_HUMAN);
+
+ SET_GAME_STATE (MELNORME_ANGER, 2);
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, bye_melnorme_slightly_angry))
+ NPCPhrase (MELNORME_SLIGHTLY_ANGRY_GOODBYE);
+ else if (PLAYER_SAID (R, ok_strip_me))
+ {
+ if (ShipWorth () < 4000 / MODULE_COST_SCALE)
+ /* is ship worth stripping */
+ NPCPhrase (NOT_WORTH_STRIPPING);
+ else
+ {
+ SET_GAME_STATE (MELNORME_ANGER, 0);
+
+ StripShip ((COUNT)~0);
+ NPCPhrase (FAIR_JUSTICE);
+ }
+ }
+ else if (PLAYER_SAID (R, fight_some_more))
+ {
+ NPCPhrase (OK_FIGHT_SOME_MORE);
+
+ SET_GAME_STATE (MELNORME_ANGER, 3);
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, bye_melnorme_pissed_off))
+ NPCPhrase (MELNORME_PISSED_OFF_GOODBYE);
+ else if (PLAYER_SAID (R, well_if_thats_the_way_you_feel))
+ {
+ NPCPhrase (WE_FIGHT_AGAIN);
+
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, you_hate_us_so_we_go_away))
+ NPCPhrase (HATE_YOU_GOODBYE);
+ else if (PLAYER_SAID (R, take_it))
+ {
+ StripShip (0);
+ NPCPhrase (HAPPY_TO_HAVE_RESCUED);
+ }
+ else if (PLAYER_SAID (R, leave_it))
+ {
+ SET_GAME_STATE (MELNORME_RESCUE_REFUSED, 1);
+ NPCPhrase (MAYBE_SEE_YOU_LATER);
+ }
+ else if (PLAYER_SAID (R, no_help))
+ {
+ SET_GAME_STATE (MELNORME_RESCUE_REFUSED, 1);
+ NPCPhrase (GOODBYE_AND_GOODLUCK);
+ }
+ else if (PLAYER_SAID (R, no_changed_mind))
+ {
+ NPCPhrase (GOODBYE_AND_GOODLUCK_AGAIN);
+ }
+ else if (PLAYER_SAID (R, be_leaving_now)
+ || PLAYER_SAID (R, goodbye))
+ {
+ NPCPhrase (FRIENDLY_GOODBYE);
+ }
+}
+
+static void
+DoRescue (RESPONSE_REF R)
+{
+ SIZE dx, dy;
+ COUNT fuel_required;
+
+ (void) R; // ignored
+ dx = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x))
+ - SOL_X;
+ dy = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y))
+ - SOL_Y;
+ fuel_required = square_root (
+ (DWORD)((long)dx * dx + (long)dy * dy)
+ ) + (2 * FUEL_TANK_SCALE);
+
+ if (StripShip (fuel_required))
+ {
+ Response (take_it, ExitConversation);
+ Response (leave_it, ExitConversation);
+ }
+}
+
+// Extract method for getting the player's current credits.
+static COUNT
+GetAvailableCredits (void)
+{
+ return MAKE_WORD (GET_GAME_STATE (MELNORME_CREDIT0),
+ GET_GAME_STATE (MELNORME_CREDIT1));
+}
+
+// Extract method for setting the player's current credits.
+static void
+SetAvailableCredits (COUNT credits)
+{
+ SET_GAME_STATE (MELNORME_CREDIT0, LOBYTE (credits));
+ SET_GAME_STATE (MELNORME_CREDIT1, HIBYTE (credits));
+}
+
+// Now returns whether the purchase succeeded instead of the remaining
+// credit balance. Use GetAvailableCredits() to get the latter.
+static bool
+DeltaCredit (SIZE delta_credit)
+{
+ COUNT Credit = GetAvailableCredits ();
+
+ // Can they afford it?
+ if ((int)delta_credit >= 0 || ((int)(-delta_credit) <= (int)(Credit)))
+ {
+ Credit += delta_credit;
+ SetAvailableCredits (Credit);
+ DrawStatusMessage (NULL);
+ return true;
+ }
+
+ // Fail
+ NPCPhrase (NEED_MORE_CREDIT0);
+ NPCNumber (-delta_credit - Credit, NULL);
+ NPCPhrase (NEED_MORE_CREDIT1);
+
+ return false;
+}
+
+
+// Extract methods to process the giving of various bits of information to the
+// player. Ideally, we'd want to merge these three into a single parameterized
+// function, but the nature of the XXX_GAME_STATE() code makes that tricky.
+static void
+CurrentEvents (void)
+{
+ BYTE stack = GET_GAME_STATE (MELNORME_EVENTS_INFO_STACK);
+ const int phraseId = GetLineSafe (ok_buy_event_lines, stack);
+ NPCPhrase (phraseId);
+ SET_GAME_STATE (MELNORME_EVENTS_INFO_STACK, stack + 1);
+}
+
+static void
+AlienRaces (void)
+{
+ BYTE stack = GET_GAME_STATE (MELNORME_ALIEN_INFO_STACK);
+ const int phraseId = GetLineSafe (ok_buy_alien_race_lines, stack);
+ // Two pieces of alien knowledge trigger state changes.
+ switch (phraseId)
+ {
+ case OK_BUY_ALIEN_RACE_14:
+ if (!GET_GAME_STATE (FOUND_PLUTO_SPATHI))
+ {
+ SET_GAME_STATE (KNOW_SPATHI_PASSWORD, 1);
+ SET_GAME_STATE (SPATHI_HOME_VISITS, 7);
+ }
+ break;
+ case OK_BUY_ALIEN_RACE_15:
+ if (GET_GAME_STATE (KNOW_ABOUT_SHATTERED) < 2)
+ {
+ SET_GAME_STATE (KNOW_ABOUT_SHATTERED, 2);
+ }
+ SET_GAME_STATE (KNOW_SYREEN_WORLD_SHATTERED, 1);
+ break;
+ }
+ NPCPhrase (phraseId);
+ SET_GAME_STATE (MELNORME_ALIEN_INFO_STACK, stack + 1);
+}
+
+static void
+History (void)
+{
+ BYTE stack = GET_GAME_STATE (MELNORME_HISTORY_INFO_STACK);
+ const int phraseId = GetLineSafe (ok_buy_history_lines, stack);
+ NPCPhrase (phraseId);
+ SET_GAME_STATE (MELNORME_HISTORY_INFO_STACK, stack + 1);
+}
+
+// extract method to tell if we have any information left to sell to the player.
+static bool AnyInfoLeftToSell (void)
+{
+ return GET_GAME_STATE (MELNORME_EVENTS_INFO_STACK) < NUM_EVENT_ITEMS
+ || GET_GAME_STATE (MELNORME_ALIEN_INFO_STACK) < NUM_ALIEN_RACE_ITEMS
+ || GET_GAME_STATE (MELNORME_HISTORY_INFO_STACK) < NUM_HISTORY_ITEMS;
+}
+
+static void NatureOfConversation (RESPONSE_REF R);
+
+static BYTE AskedToBuy;
+
+
+static void
+DoBuy (RESPONSE_REF R)
+{
+ COUNT credit;
+ SIZE needed_credit;
+ BYTE slot;
+ DWORD capacity;
+
+ credit = GetAvailableCredits ();
+
+ capacity = FUEL_RESERVE;
+ slot = NUM_MODULE_SLOTS - 1;
+ do
+ {
+ if (GLOBAL_SIS (ModuleSlots[slot]) == FUEL_TANK
+ || GLOBAL_SIS (ModuleSlots[slot]) == HIGHEFF_FUELSYS)
+ {
+ COUNT volume;
+
+ volume = GLOBAL_SIS (ModuleSlots[slot]) == FUEL_TANK
+ ? FUEL_TANK_CAPACITY : HEFUEL_TANK_CAPACITY;
+ capacity += volume;
+ }
+ } while (slot--);
+
+ // If they're out of credits, educate them on how commerce works.
+ if (credit == 0)
+ {
+ AskedToBuy = TRUE;
+ NPCPhrase (NEED_CREDIT);
+
+ NatureOfConversation (R);
+ }
+ else if (PLAYER_SAID (R, buy_fuel)
+ || PLAYER_SAID (R, buy_1_fuel)
+ || PLAYER_SAID (R, buy_5_fuel)
+ || PLAYER_SAID (R, buy_10_fuel)
+ || PLAYER_SAID (R, buy_25_fuel)
+ || PLAYER_SAID (R, fill_me_up))
+ {
+ needed_credit = 0;
+ if (PLAYER_SAID (R, buy_1_fuel))
+ needed_credit = 1;
+ else if (PLAYER_SAID (R, buy_5_fuel))
+ needed_credit = 5;
+ else if (PLAYER_SAID (R, buy_10_fuel))
+ needed_credit = 10;
+ else if (PLAYER_SAID (R, buy_25_fuel))
+ needed_credit = 25;
+ else if (PLAYER_SAID (R, fill_me_up))
+ needed_credit = (capacity - GLOBAL_SIS (FuelOnBoard)
+ + FUEL_TANK_SCALE - 1)
+ / FUEL_TANK_SCALE;
+
+ if (needed_credit == 0)
+ {
+ if (!GET_GAME_STATE (MELNORME_FUEL_PROCEDURE))
+ {
+ NPCPhrase (BUY_FUEL_INTRO);
+ SET_GAME_STATE (MELNORME_FUEL_PROCEDURE, 1);
+ }
+ }
+ else
+ {
+ if (GLOBAL_SIS (FuelOnBoard) / FUEL_TANK_SCALE
+ + needed_credit > capacity / FUEL_TANK_SCALE)
+ {
+ NPCPhrase (NO_ROOM_FOR_FUEL);
+ goto TryFuelAgain;
+ }
+
+ if ((int)(needed_credit * (BIO_CREDIT_VALUE / 2)) <= (int)credit)
+ {
+ DWORD f;
+
+ NPCPhrase (GOT_FUEL);
+
+ f = (DWORD)needed_credit * FUEL_TANK_SCALE;
+ while (f > 0x3FFFL)
+ {
+ DeltaSISGauges (0, 0x3FFF, 0);
+ f -= 0x3FFF;
+ }
+ DeltaSISGauges (0, (SIZE)f, 0);
+ }
+ needed_credit *= (BIO_CREDIT_VALUE / 2);
+ }
+ if (needed_credit)
+ {
+ DeltaCredit (-needed_credit);
+ if (GLOBAL_SIS (FuelOnBoard) >= capacity)
+ goto BuyBuyBuy;
+ }
+TryFuelAgain:
+ NPCPhrase (HOW_MUCH_FUEL);
+
+ Response (buy_1_fuel, DoBuy);
+ Response (buy_5_fuel, DoBuy);
+ Response (buy_10_fuel, DoBuy);
+ Response (buy_25_fuel, DoBuy);
+ Response (fill_me_up, DoBuy);
+ Response (done_buying_fuel, DoBuy);
+ }
+ else if (PLAYER_SAID (R, buy_technology)
+ || PLAYER_SAID (R, buy_new_tech))
+ {
+ // Note that this code no longer uses the MELNORME_TECH_STACK state
+ // buts, as they're not needed; we can tell what technologies the
+ // player has by using the technology API above. This opens the
+ // possibility of the player acquiring tech from someplace other than
+ // the Melnorme.
+ const TechSaleData* nextTech;
+
+ // If it's our first time, give an introduction.
+ if (!GET_GAME_STATE (MELNORME_TECH_PROCEDURE))
+ {
+ NPCPhrase (BUY_NEW_TECH_INTRO);
+ SET_GAME_STATE (MELNORME_TECH_PROCEDURE, 1);
+ }
+
+ // Did the player just attempt to buy a tech?
+ if (PLAYER_SAID (R, buy_new_tech))
+ {
+ nextTech = GetNextTechForSale ();
+ if (!nextTech)
+ goto BuyBuyBuy; // No tech left to buy
+
+ if (!DeltaCredit (-nextTech->price))
+ goto BuyBuyBuy; // Can't afford it
+
+ // Make the sale
+ GrantTech (nextTech->techId);
+ NPCPhrase (nextTech->sold_line);
+ }
+
+ nextTech = GetNextTechForSale ();
+ if (!nextTech)
+ {
+ NPCPhrase (NEW_TECH_ALL_GONE);
+ goto BuyBuyBuy; // No tech left to buy
+ }
+
+ NPCPhrase (nextTech->sale_line);
+
+ Response (buy_new_tech, DoBuy);
+ Response (no_buy_new_tech, DoBuy);
+ }
+ else if (PLAYER_SAID (R, buy_info)
+ || PLAYER_SAID (R, buy_current_events)
+ || PLAYER_SAID (R, buy_alien_races)
+ || PLAYER_SAID (R, buy_history))
+ {
+ if (!GET_GAME_STATE (MELNORME_INFO_PROCEDURE))
+ {
+ NPCPhrase (BUY_INFO_INTRO);
+ SET_GAME_STATE (MELNORME_INFO_PROCEDURE, 1);
+ }
+ else if (PLAYER_SAID (R, buy_info))
+ {
+ NPCPhrase (OK_BUY_INFO);
+ }
+ else
+ {
+#define INFO_COST 75
+ if (!DeltaCredit (-INFO_COST))
+ goto BuyBuyBuy;
+
+ if (PLAYER_SAID (R, buy_current_events))
+ CurrentEvents ();
+ else if (PLAYER_SAID (R, buy_alien_races))
+ AlienRaces ();
+ else if (PLAYER_SAID (R, buy_history))
+ History ();
+ }
+
+ if (!AnyInfoLeftToSell ())
+ {
+ NPCPhrase (INFO_ALL_GONE);
+ goto BuyBuyBuy;
+ }
+
+ if (GET_GAME_STATE (MELNORME_EVENTS_INFO_STACK) < NUM_EVENT_ITEMS)
+ Response (buy_current_events, DoBuy);
+ if (GET_GAME_STATE (MELNORME_ALIEN_INFO_STACK) < NUM_ALIEN_RACE_ITEMS)
+ Response (buy_alien_races, DoBuy);
+ if (GET_GAME_STATE (MELNORME_HISTORY_INFO_STACK) < NUM_HISTORY_ITEMS)
+ Response (buy_history, DoBuy);
+ Response (done_buying_info, DoBuy);
+ }
+ else
+ {
+ if (PLAYER_SAID (R, done_buying_fuel))
+ NPCPhrase (OK_DONE_BUYING_FUEL);
+ else if (PLAYER_SAID (R, no_buy_new_tech))
+ NPCPhrase (OK_NO_BUY_NEW_TECH);
+ else if (PLAYER_SAID (R, done_buying_info))
+ NPCPhrase (OK_DONE_BUYING_INFO);
+ else
+ NPCPhrase (WHAT_TO_BUY);
+
+BuyBuyBuy:
+ if (GLOBAL_SIS (FuelOnBoard) < capacity)
+ Response (buy_fuel, DoBuy);
+ if (GetNextTechForSale ())
+ Response (buy_technology, DoBuy);
+ if (AnyInfoLeftToSell ())
+ Response (buy_info, DoBuy);
+
+ Response (done_buying, NatureOfConversation);
+ Response (be_leaving_now, ExitConversation);
+ }
+}
+
+static void
+DoSell (RESPONSE_REF R)
+{
+ BYTE num_new_rainbows;
+ UWORD rainbow_mask;
+ SIZE added_credit;
+ int what_to_sell_queued = 0;
+
+ rainbow_mask = MAKE_WORD (
+ GET_GAME_STATE (RAINBOW_WORLD0),
+ GET_GAME_STATE (RAINBOW_WORLD1)
+ );
+ num_new_rainbows = (BYTE)(-GET_GAME_STATE (MELNORME_RAINBOW_COUNT));
+ while (rainbow_mask)
+ {
+ if (rainbow_mask & 1)
+ ++num_new_rainbows;
+
+ rainbow_mask >>= 1;
+ }
+
+ if (!PLAYER_SAID (R, sell))
+ {
+ if (PLAYER_SAID (R, sell_life_data))
+ {
+ DWORD TimeIn;
+
+ added_credit = GLOBAL_SIS (TotalBioMass) * BIO_CREDIT_VALUE;
+
+ NPCPhrase (SOLD_LIFE_DATA1);
+ NPCNumber (GLOBAL_SIS (TotalBioMass), NULL);
+ NPCPhrase (SOLD_LIFE_DATA2);
+ NPCNumber (added_credit, NULL);
+ NPCPhrase (SOLD_LIFE_DATA3);
+ // queue WHAT_TO_SELL before talk-segue
+ if (num_new_rainbows)
+ {
+ NPCPhrase (WHAT_TO_SELL);
+ what_to_sell_queued = 1;
+ }
+ AlienTalkSegue (1);
+
+ DrawCargoStrings ((BYTE)~0, (BYTE)~0);
+ SleepThread (ONE_SECOND / 2);
+ TimeIn = GetTimeCounter ();
+ DrawCargoStrings (
+ (BYTE)NUM_ELEMENT_CATEGORIES,
+ (BYTE)NUM_ELEMENT_CATEGORIES
+ );
+ do
+ {
+ TimeIn = GetTimeCounter ();
+ if (AnyButtonPress (TRUE))
+ {
+ DeltaCredit (GLOBAL_SIS (TotalBioMass) * BIO_CREDIT_VALUE);
+ GLOBAL_SIS (TotalBioMass) = 0;
+ }
+ else
+ {
+ --GLOBAL_SIS (TotalBioMass);
+ DeltaCredit (BIO_CREDIT_VALUE);
+ }
+ DrawCargoStrings (
+ (BYTE)NUM_ELEMENT_CATEGORIES,
+ (BYTE)NUM_ELEMENT_CATEGORIES
+ );
+ } while (GLOBAL_SIS (TotalBioMass));
+ SleepThread (ONE_SECOND / 2);
+
+ ClearSISRect (DRAW_SIS_DISPLAY);
+ }
+ else /* if (R == sell_rainbow_locations) */
+ {
+ added_credit = num_new_rainbows * (250 * BIO_CREDIT_VALUE);
+
+ NPCPhrase (SOLD_RAINBOW_LOCATIONS1);
+ NPCNumber (num_new_rainbows, NULL);
+ NPCPhrase (SOLD_RAINBOW_LOCATIONS2);
+ NPCNumber (added_credit, NULL);
+ NPCPhrase (SOLD_RAINBOW_LOCATIONS3);
+
+ num_new_rainbows += GET_GAME_STATE (MELNORME_RAINBOW_COUNT);
+ SET_GAME_STATE (MELNORME_RAINBOW_COUNT, num_new_rainbows);
+ num_new_rainbows = 0;
+
+ DeltaCredit (added_credit);
+ }
+
+ AskedToBuy = FALSE;
+ }
+
+ if (GLOBAL_SIS (TotalBioMass) || num_new_rainbows)
+ {
+ if (!what_to_sell_queued)
+ NPCPhrase (WHAT_TO_SELL);
+
+ if (GLOBAL_SIS (TotalBioMass))
+ Response (sell_life_data, DoSell);
+ if (num_new_rainbows)
+ Response (sell_rainbow_locations, DoSell);
+ Response (done_selling, NatureOfConversation);
+ }
+ else
+ {
+ if (PLAYER_SAID (R, sell))
+ NPCPhrase (NOTHING_TO_SELL);
+ DISABLE_PHRASE (sell);
+
+ NatureOfConversation (R);
+ }
+}
+
+
+static void
+NatureOfConversation (RESPONSE_REF R)
+{
+ BYTE num_new_rainbows;
+ UWORD rainbow_mask;
+ COUNT Credit;
+
+ if (PLAYER_SAID (R, get_on_with_business))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK2, 5);
+ R = 0;
+ }
+
+ // Draw credits display
+ DeltaCredit (0);
+ Credit = GetAvailableCredits ();
+ if (R == 0)
+ {
+ BYTE stack = GET_GAME_STATE (MELNORME_YACK_STACK2) - 5;
+ NPCPhrase (GetLineSafe (hello_and_down_to_business_lines, stack));
+ if (stack < (NUM_HELLO_LINES - 1))
+ ++stack;
+ SET_GAME_STATE (MELNORME_YACK_STACK2, stack + 5);
+ }
+
+ rainbow_mask = MAKE_WORD (
+ GET_GAME_STATE (RAINBOW_WORLD0),
+ GET_GAME_STATE (RAINBOW_WORLD1)
+ );
+ num_new_rainbows = (BYTE)(-GET_GAME_STATE (MELNORME_RAINBOW_COUNT));
+ while (rainbow_mask)
+ {
+ if (rainbow_mask & 1)
+ ++num_new_rainbows;
+
+ rainbow_mask >>= 1;
+ }
+
+ if (GLOBAL_SIS (FuelOnBoard) > 0
+ || GLOBAL_SIS (TotalBioMass)
+ || Credit
+ || num_new_rainbows)
+ {
+ if (!GET_GAME_STATE (TRADED_WITH_MELNORME))
+ {
+ SET_GAME_STATE (TRADED_WITH_MELNORME, 1);
+
+ NPCPhrase (TRADING_INFO);
+ }
+
+ if (R == 0)
+ {
+ /* Melnorme reports any news and turns purple */
+ NPCPhrase (BUY_OR_SELL);
+ AlienTalkSegue (1);
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 1)
+ ), ONE_SECOND / 2);
+ AlienTalkSegue ((COUNT)~0);
+ }
+ else if (PLAYER_SAID (R, why_turned_purple))
+ {
+ SET_GAME_STATE (WHY_MELNORME_PURPLE, 1);
+
+ NPCPhrase (TURNED_PURPLE_BECAUSE);
+ }
+ else if (PLAYER_SAID (R, done_selling))
+ {
+ NPCPhrase (OK_DONE_SELLING);
+ }
+ else if (PLAYER_SAID (R, done_buying))
+ {
+ NPCPhrase (OK_DONE_BUYING);
+ }
+
+ if (!GET_GAME_STATE (WHY_MELNORME_PURPLE))
+ {
+ Response (why_turned_purple, NatureOfConversation);
+ }
+ if (!AskedToBuy)
+ Response (buy, DoBuy);
+ if (PHRASE_ENABLED (sell))
+ Response (sell, DoSell);
+ Response (goodbye, ExitConversation);
+ }
+ else /* needs to be rescued */
+ {
+ if (GET_GAME_STATE (MELNORME_RESCUE_REFUSED))
+ {
+ NPCPhrase (CHANGED_MIND);
+
+ Response (yes_changed_mind, DoRescue);
+ Response (no_changed_mind, ExitConversation);
+ }
+ else
+ {
+ BYTE num_rescues = GET_GAME_STATE (MELNORME_RESCUE_COUNT);
+ NPCPhrase (GetLineSafe (rescue_lines, num_rescues));
+
+ if (num_rescues < NUM_RESCUE_LINES - 1)
+ {
+ ++num_rescues;
+ SET_GAME_STATE (MELNORME_RESCUE_COUNT, num_rescues);
+ }
+
+ NPCPhrase (SHOULD_WE_HELP_YOU);
+
+ Response (yes_help, DoRescue);
+ Response (no_help, ExitConversation);
+ }
+ }
+}
+
+static BYTE local_stack0, local_stack1;
+
+static void
+DoBluster (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, trade_is_for_the_weak))
+ {
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 2)
+ ), ONE_SECOND / 2);
+
+ SET_GAME_STATE (MELNORME_YACK_STACK2, 4);
+ NPCPhrase (WERE_NOT_AFRAID);
+ }
+ else if (PLAYER_SAID (R, why_blue_light))
+ {
+ SET_GAME_STATE (WHY_MELNORME_BLUE, 1);
+
+ NPCPhrase (BLUE_IS_MAD);
+ }
+ else if (PLAYER_SAID (R, we_strong_1))
+ {
+ local_stack0 = 1;
+ NPCPhrase (YOU_NOT_STRONG_1);
+ }
+ else if (PLAYER_SAID (R, we_strong_2))
+ {
+ local_stack0 = 2;
+ NPCPhrase (YOU_NOT_STRONG_2);
+ }
+ else if (PLAYER_SAID (R, we_strong_3))
+ {
+ local_stack0 = 3;
+ NPCPhrase (YOU_NOT_STRONG_3);
+ }
+ else if (PLAYER_SAID (R, just_testing))
+ {
+ local_stack1 = 1;
+ NPCPhrase (REALLY_TESTING);
+ }
+
+ if (!GET_GAME_STATE (WHY_MELNORME_BLUE))
+ Response (why_blue_light, DoBluster);
+ switch (local_stack0)
+ {
+ case 0:
+ Response (we_strong_1, DoBluster);
+ break;
+ case 1:
+ Response (we_strong_2, DoBluster);
+ break;
+ case 2:
+ Response (we_strong_3, DoBluster);
+ break;
+ }
+ switch (local_stack1)
+ {
+ case 0:
+ Response (just_testing, DoBluster);
+ break;
+ case 1:
+ {
+ Response (yes_really_testing, DoFirstMeeting);
+ break;
+ }
+ }
+ Response (youre_on, ExitConversation);
+}
+
+static void
+yack0_respond (void)
+{
+
+ switch (GET_GAME_STATE (MELNORME_YACK_STACK0))
+ {
+ case 0:
+ {
+ UNICODE buf[ALLIANCE_NAME_BUFSIZE];
+
+ GetAllianceName (buf, name_1);
+ construct_response (
+ shared_phrase_buf,
+ we_are_from_alliance0,
+ buf,
+ (RESPONSE_REF)-1);
+ DoResponsePhrase (we_are_from_alliance0, DoFirstMeeting, shared_phrase_buf);
+ break;
+ }
+ case 1:
+ Response (how_know, DoFirstMeeting);
+ break;
+ }
+}
+
+static void
+yack1_respond (void)
+{
+ switch (GET_GAME_STATE (MELNORME_YACK_STACK1))
+ {
+ case 0:
+ Response (what_about_yourselves, DoFirstMeeting);
+ break;
+ case 1:
+ Response (what_factors, DoFirstMeeting);
+ case 2:
+ Response (get_on_with_business, NatureOfConversation);
+ break;
+ }
+}
+
+static void
+yack2_respond (void)
+{
+ switch (GET_GAME_STATE (MELNORME_YACK_STACK2))
+ {
+ case 0:
+ Response (what_about_universe, DoFirstMeeting);
+ break;
+ case 1:
+ Response (giving_is_good_1, DoFirstMeeting);
+ break;
+ case 2:
+ Response (giving_is_good_2, DoFirstMeeting);
+ break;
+ case 3:
+ Response (trade_is_for_the_weak, DoBluster);
+ break;
+ }
+}
+
+static void
+DoFirstMeeting (RESPONSE_REF R)
+{
+ BYTE last_stack = 0;
+ PVOIDFUNC temp_func, stack_func[] =
+ {
+ yack0_respond,
+ yack1_respond,
+ yack2_respond,
+ };
+
+ if (R == 0)
+ {
+ BYTE business_count;
+
+ business_count = GET_GAME_STATE (MELNORME_BUSINESS_COUNT);
+ switch (business_count++)
+ {
+ case 0:
+ NPCPhrase (HELLO_NOW_DOWN_TO_BUSINESS_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_NOW_DOWN_TO_BUSINESS_2);
+ break;
+ case 2:
+ NPCPhrase (HELLO_NOW_DOWN_TO_BUSINESS_3);
+ --business_count;
+ break;
+ }
+ SET_GAME_STATE (MELNORME_BUSINESS_COUNT, business_count);
+ }
+ else if (PLAYER_SAID (R, we_are_from_alliance0))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK0, 1);
+ NPCPhrase (KNOW_OF_YOU);
+ }
+ else if (PLAYER_SAID (R, how_know))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK0, 2);
+ NPCPhrase (KNOW_BECAUSE);
+ }
+ else if (PLAYER_SAID (R, what_about_yourselves))
+ {
+ last_stack = 1;
+ SET_GAME_STATE (MELNORME_YACK_STACK1, 1);
+ NPCPhrase (NO_TALK_ABOUT_OURSELVES);
+ }
+ else if (PLAYER_SAID (R, what_factors))
+ {
+ last_stack = 1;
+ SET_GAME_STATE (MELNORME_YACK_STACK1, 2);
+ NPCPhrase (FACTORS_ARE);
+ }
+ else if (PLAYER_SAID (R, what_about_universe))
+ {
+ last_stack = 2;
+ SET_GAME_STATE (MELNORME_YACK_STACK2, 1);
+ NPCPhrase (NO_FREE_LUNCH);
+ }
+ else if (PLAYER_SAID (R, giving_is_good_1))
+ {
+ last_stack = 2;
+ SET_GAME_STATE (MELNORME_YACK_STACK2, 2);
+ NPCPhrase (GIVING_IS_BAD_1);
+ }
+ else if (PLAYER_SAID (R, giving_is_good_2))
+ {
+ last_stack = 2;
+ SET_GAME_STATE (MELNORME_YACK_STACK2, 3);
+ NPCPhrase (GIVING_IS_BAD_2);
+ }
+ else if (PLAYER_SAID (R, yes_really_testing))
+ {
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 0)
+ ), ONE_SECOND / 2);
+
+ NPCPhrase (TEST_RESULTS);
+ }
+ else if (PLAYER_SAID (R, we_apologize))
+ {
+ SET_GAME_STATE (MELNORME_ANGER, 0);
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 0)
+ ), ONE_SECOND / 2);
+
+ NPCPhrase (APOLOGY_ACCEPTED);
+ }
+
+ temp_func = stack_func[0];
+ stack_func[0] = stack_func[last_stack];
+ stack_func[last_stack] = temp_func;
+ (*stack_func[0]) ();
+ (*stack_func[1]) ();
+ (*stack_func[2]) ();
+ Response (no_trade_now, ExitConversation);
+}
+
+static void
+DoMelnormeMiffed (RESPONSE_REF R)
+{
+ if (R == 0)
+ {
+ BYTE miffed_count;
+
+ miffed_count = GET_GAME_STATE (MELNORME_MIFFED_COUNT);
+ switch (miffed_count++)
+ {
+ case 0:
+ NPCPhrase (HELLO_SLIGHTLY_ANGRY_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_SLIGHTLY_ANGRY_2);
+ break;
+ default:
+ --miffed_count;
+ NPCPhrase (HELLO_SLIGHTLY_ANGRY_3);
+ break;
+ }
+ SET_GAME_STATE (MELNORME_MIFFED_COUNT, miffed_count);
+
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 2)
+ ), ONE_SECOND / 2);
+ }
+ else if (PLAYER_SAID (R, explore_relationship))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK3, 1);
+
+ NPCPhrase (EXAMPLE_OF_RELATIONSHIP);
+ }
+ else if (PLAYER_SAID (R, excuse_1))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK3, 2);
+
+ NPCPhrase (NO_EXCUSE_1);
+ }
+ else if (PLAYER_SAID (R, excuse_2))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK3, 3);
+
+ NPCPhrase (NO_EXCUSE_2);
+ }
+ else if (PLAYER_SAID (R, excuse_3))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK3, 4);
+
+ NPCPhrase (NO_EXCUSE_3);
+ }
+
+ switch (GET_GAME_STATE (MELNORME_YACK_STACK3))
+ {
+ case 0:
+ Response (explore_relationship, DoMelnormeMiffed);
+ break;
+ case 1:
+ Response (excuse_1, DoMelnormeMiffed);
+ break;
+ case 2:
+ Response (excuse_2, DoMelnormeMiffed);
+ break;
+ case 3:
+ Response (excuse_3, DoMelnormeMiffed);
+ break;
+ }
+ Response (we_apologize, DoFirstMeeting);
+ Response (so_we_can_attack, ExitConversation);
+ Response (bye_melnorme_slightly_angry, ExitConversation);
+}
+
+static void
+DoMelnormePissed (RESPONSE_REF R)
+{
+ if (R == 0)
+ {
+ BYTE pissed_count;
+
+ pissed_count = GET_GAME_STATE (MELNORME_PISSED_COUNT);
+ switch (pissed_count++)
+ {
+ case 0:
+ NPCPhrase (HELLO_PISSED_OFF_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_PISSED_OFF_2);
+ break;
+ default:
+ --pissed_count;
+ NPCPhrase (HELLO_PISSED_OFF_3);
+ break;
+ }
+ SET_GAME_STATE (MELNORME_PISSED_COUNT, pissed_count);
+
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 2)
+ ), ONE_SECOND / 2);
+ }
+ else if (PLAYER_SAID (R, beg_forgiveness))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK4, 1);
+
+ NPCPhrase (LOTS_TO_MAKE_UP_FOR);
+ }
+ else if (PLAYER_SAID (R, you_are_so_right))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK4, 2);
+
+ NPCPhrase (ONE_LAST_CHANCE);
+ }
+
+ switch (GET_GAME_STATE (MELNORME_YACK_STACK4))
+ {
+ case 0:
+ Response (beg_forgiveness, DoMelnormePissed);
+ break;
+ case 1:
+ Response (you_are_so_right, DoMelnormePissed);
+ break;
+ case 2:
+ Response (ok_strip_me, ExitConversation);
+ break;
+ }
+ Response (fight_some_more, ExitConversation);
+ Response (bye_melnorme_pissed_off, ExitConversation);
+}
+
+static void
+DoMelnormeHate (RESPONSE_REF R)
+{
+ BYTE hate_count;
+
+ (void) R; // ignored
+ hate_count = GET_GAME_STATE (MELNORME_HATE_COUNT);
+ switch (hate_count++)
+ {
+ case 0:
+ NPCPhrase (HELLO_HATE_YOU_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_HATE_YOU_2);
+ break;
+ default:
+ --hate_count;
+ NPCPhrase (HELLO_HATE_YOU_3);
+ break;
+ }
+ SET_GAME_STATE (MELNORME_HATE_COUNT, hate_count);
+
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 2)
+ ), ONE_SECOND / 2);
+
+ Response (well_if_thats_the_way_you_feel, ExitConversation);
+ Response (you_hate_us_so_we_go_away, ExitConversation);
+}
+
+static void
+Intro (void)
+{
+ prevMsgMode = SetStatusMessageMode (SMM_CREDITS);
+
+ if (GET_GAME_STATE (MET_MELNORME) == 0)
+ {
+ SET_GAME_STATE (MET_MELNORME, 1);
+ DoFirstMeeting (0);
+ }
+ else
+ {
+ switch (GET_GAME_STATE (MELNORME_ANGER))
+ {
+ case 0:
+ if (GET_GAME_STATE (MELNORME_YACK_STACK2) <= 5)
+ DoFirstMeeting (0);
+ else
+ NatureOfConversation (0);
+ break;
+ case 1:
+ DoMelnormeMiffed (0);
+ break;
+ case 2:
+ DoMelnormePissed (0);
+ break;
+ default:
+ DoMelnormeHate (0);
+ break;
+ }
+ }
+}
+
+static COUNT
+uninit_melnorme (void)
+{
+ return 0;
+}
+
+static void
+post_melnorme_enc (void)
+{
+ if (prevMsgMode != SMM_UNDEFINED)
+ SetStatusMessageMode (prevMsgMode);
+ DrawStatusMessage (NULL);
+}
+
+LOCDATA*
+init_melnorme_comm (void)
+{
+ LOCDATA *retval;
+
+ melnorme_desc.init_encounter_func = Intro;
+ melnorme_desc.post_encounter_func = post_melnorme_enc;
+ melnorme_desc.uninit_encounter_func = uninit_melnorme;
+
+ melnorme_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ melnorme_desc.AlienTextBaseline.y = 0;
+ melnorme_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ local_stack0 = 0;
+ local_stack1 = 0;
+
+ prevMsgMode = SMM_UNDEFINED;
+
+ setSegue (Segue_peace);
+ AskedToBuy = FALSE;
+ retval = &melnorme_desc;
+
+ return (retval);
+}