//Copyright Paul Reiche, Fred Ford. 1992-2002 /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "../build.h" #include "../colors.h" #include "../gendef.h" #include "../starmap.h" #include "../encount.h" #include "../gamestr.h" #include "../controls.h" #include "../save.h" #include "../settings.h" #include "../shipcont.h" #include "../setup.h" #include "../state.h" #include "../sis.h" // for ClearSISRect() #include "../grpinfo.h" #include "../sounds.h" #include "../util.h" #include "../hyper.h" // for SaveSisHyperState() #include "planets.h" // for SaveSolarSysLocation() and tests #include "libs/strlib.h" // If DEBUG_DEVICES is defined, the device list shown in the game will // include the pictures of all devices defined, regardless of which // devices the player actually possesses. //#define DEBUG_DEVICES #define DEVICE_ICON_WIDTH 16 #define DEVICE_ICON_HEIGHT 16 #define DEVICE_ORG_Y 33 #define DEVICE_SPACING_Y (DEVICE_ICON_HEIGHT + 2) #define DEVICE_COL_0 4 #define DEVICE_COL_1 40 #define DEVICE_SEL_ORG_X (DEVICE_COL_0 + DEVICE_ICON_WIDTH) #define DEVICE_SEL_WIDTH (FIELD_WIDTH + 1 - DEVICE_SEL_ORG_X + 1) #define ICON_OFS_Y 1 #define NAME_OFS_Y 2 #define TEXT_BASELINE 6 #define TEXT_SPACING_Y 7 #define MAX_VIS_DEVICES ((129 - DEVICE_ORG_Y) / DEVICE_SPACING_Y) typedef enum { DEVICE_FAILURE = 0, DEVICE_SUCCESS, DEVICE_SUCCESS_NO_SOUND, } DeviceStatus; typedef struct { BYTE list[NUM_DEVICES]; // List of all devices player has COUNT count; // Number of devices in the list COUNT topIndex; // Index of the top device displayed } DEVICES_STATE; #if 0 static void EraseDevicesBackground (void) { RECT r; r.corner.x = 2 + 1; r.extent.width = FIELD_WIDTH + 1 - 2; r.corner.y = DEVICE_ORG_Y; r.extent.height = MAX_VIS_DEVICES * DEVICE_SPACING_Y; SetContextForeGroundColor (DEVICES_BACK_COLOR); DrawFilledRectangle (&r); } #endif static void DrawDevice (COUNT device, COUNT pos, bool selected) { RECT r; TEXT t; t.align = ALIGN_CENTER; t.baseline.x = DEVICE_COL_1; r.extent.width = DEVICE_SEL_WIDTH; r.extent.height = TEXT_SPACING_Y * 2; r.corner.x = DEVICE_SEL_ORG_X; // draw line background r.corner.y = DEVICE_ORG_Y + pos * DEVICE_SPACING_Y + NAME_OFS_Y; SetContextForeGroundColor (selected ? DEVICES_SELECTED_BACK_COLOR : DEVICES_BACK_COLOR); DrawFilledRectangle (&r); SetContextFont (TinyFont); // print device name SetContextForeGroundColor (selected ? DEVICES_SELECTED_NAME_COLOR : DEVICES_NAME_COLOR); t.baseline.y = r.corner.y + TEXT_BASELINE; t.pStr = GAME_STRING (device + DEVICE_STRING_BASE + 1); t.CharCount = utf8StringPos (t.pStr, ' '); font_DrawText (&t); t.baseline.y += TEXT_SPACING_Y; t.pStr = skipUTF8Chars (t.pStr, t.CharCount + 1); t.CharCount = (COUNT)~0; font_DrawText (&t); } static void DrawDevicesDisplay (DEVICES_STATE *devState) { TEXT t; RECT r; STAMP s; COORD cy; COUNT i; r.corner.x = 2; r.corner.y = 20; r.extent.width = FIELD_WIDTH + 1; // XXX: Shouldn't the height be 1 less? This draws the bottom border // 1 pixel too low. Or if not, why do we need another box anyway? r.extent.height = 129 - r.corner.y; DrawStarConBox (&r, 1, SHADOWBOX_MEDIUM_COLOR, SHADOWBOX_DARK_COLOR, TRUE, DEVICES_BACK_COLOR); // print the "DEVICES" title SetContextFont (StarConFont); t.baseline.x = (STATUS_WIDTH >> 1) - 1; t.baseline.y = r.corner.y + 7; t.align = ALIGN_CENTER; t.pStr = GAME_STRING (DEVICE_STRING_BASE); t.CharCount = (COUNT)~0; SetContextForeGroundColor (DEVICES_SELECTED_NAME_COLOR); font_DrawText (&t); s.origin.x = DEVICE_COL_0; cy = DEVICE_ORG_Y; // draw device icons and print names for (i = 0; i < MAX_VIS_DEVICES; ++i, cy += DEVICE_SPACING_Y) { COUNT devIndex = devState->topIndex + i; if (devIndex >= devState->count) break; // draw device icon s.origin.y = cy + ICON_OFS_Y; s.frame = SetAbsFrameIndex (MiscDataFrame, 77 + devState->list[devIndex]); DrawStamp (&s); DrawDevice (devState->list[devIndex], i, false); } } static void DrawDevices (DEVICES_STATE *devState, COUNT OldDevice, COUNT NewDevice) { BatchGraphics (); SetContext (StatusContext); if (OldDevice > NUM_DEVICES) { // Asked for the initial display or refresh DrawDevicesDisplay (devState); // do not draw unselected again this time OldDevice = NewDevice; } if (OldDevice != NewDevice) { // unselect the previous element DrawDevice (devState->list[OldDevice], OldDevice - devState->topIndex, false); } if (NewDevice < NUM_DEVICES) { // select the new element DrawDevice (devState->list[NewDevice], NewDevice - devState->topIndex, true); } UnbatchGraphics (); } // Returns TRUE if the broadcaster has been successfully activated, // and FALSE otherwise. static BOOLEAN UseCaster (void) { if (inHQSpace ()) { if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1) { SET_GAME_STATE (USED_BROADCASTER, 1); return TRUE; } return FALSE; } if (LOBYTE (GLOBAL (CurrentActivity)) != IN_INTERPLANETARY || !playerInSolarSystem ()) return FALSE; if (playerInPlanetOrbit () && matchWorld (pSolarSysState, pSolarSysState->pOrbitalDesc, 1, MATCH_PLANET) && CurStarDescPtr->Index == CHMMR_DEFINED && !GET_GAME_STATE (CHMMR_UNLEASHED)) { // In orbit around the Chenjesu/Mmrnmhrm home planet. NextActivity |= CHECK_LOAD; /* fake a load game */ GLOBAL (CurrentActivity) |= START_ENCOUNTER; EncounterGroup = 0; PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); ReinitQueue (&GLOBAL (ip_group_q)); assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7); SaveSolarSysLocation (); return TRUE; } { BOOLEAN FoundIlwrath; HIPGROUP hGroup; FoundIlwrath = (CurStarDescPtr->Index == ILWRATH_DEFINED) && StartSphereTracking (ILWRATH_SHIP); // In the Ilwrath home system and they are alive? if (!FoundIlwrath && (hGroup = GetHeadLink (&GLOBAL (ip_group_q)))) { // Is an Ilwrath ship in the system? IP_GROUP *GroupPtr; GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup); FoundIlwrath = (GroupPtr->race_id == ILWRATH_SHIP); UnlockIpGroup (&GLOBAL (ip_group_q), hGroup); } if (FoundIlwrath) { NextActivity |= CHECK_LOAD; /* fake a load game */ GLOBAL (CurrentActivity) |= START_ENCOUNTER; EncounterGroup = 0; PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); ReinitQueue (&GLOBAL (ip_group_q)); assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); if (CurStarDescPtr->Index == ILWRATH_DEFINED) { // Ilwrath home system. SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 4); } else { // Ilwrath ship. SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 5); } if (playerInPlanetOrbit ()) SaveSolarSysLocation (); return TRUE; } } return FALSE; } static DeviceStatus InvokeDevice (BYTE which_device) { BYTE val; switch (which_device) { case ROSY_SPHERE_DEVICE: val = GET_GAME_STATE (ULTRON_CONDITION); if (val) { SET_GAME_STATE (ULTRON_CONDITION, val + 1); SET_GAME_STATE (ROSY_SPHERE_ON_SHIP, 0); SET_GAME_STATE (DISCUSSED_ULTRON, 0); SET_GAME_STATE (SUPOX_ULTRON_HELP, 0); return DEVICE_SUCCESS; } break; case ARTIFACT_2_DEVICE: break; case ARTIFACT_3_DEVICE: break; case SUN_EFFICIENCY_DEVICE: if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY && playerInPlanetOrbit ()) { PlayMenuSound (MENU_SOUND_INVOKED); SleepThreadUntil (FadeScreen (FadeAllToWhite, ONE_SECOND * 1) + (ONE_SECOND * 2)); if (CurStarDescPtr->Index != CHMMR_DEFINED || !matchWorld (pSolarSysState, pSolarSysState->pOrbitalDesc, 1, MATCH_PLANET)) { FadeScreen (FadeAllToColor, ONE_SECOND * 2); } else { SET_GAME_STATE (CHMMR_EMERGING, 1); EncounterGroup = 0; GLOBAL (CurrentActivity) |= START_ENCOUNTER; PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); ReinitQueue (&GLOBAL (ip_group_q)); assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); CloneShipFragment (CHMMR_SHIP, &GLOBAL (npc_built_ship_q), 0); } return DEVICE_SUCCESS_NO_SOUND; } break; case UTWIG_BOMB_DEVICE: SET_GAME_STATE (UTWIG_BOMB, 0); GLOBAL (CurrentActivity) &= ~IN_BATTLE; GLOBAL_SIS (CrewEnlisted) = (COUNT)~0; return DEVICE_SUCCESS; case ULTRON_0_DEVICE: break; case ULTRON_1_DEVICE: break; case ULTRON_2_DEVICE: break; case ULTRON_3_DEVICE: break; case MAIDENS_DEVICE: break; case TALKING_PET_DEVICE: NextActivity |= CHECK_LOAD; /* fake a load game */ GLOBAL (CurrentActivity) |= START_ENCOUNTER; SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 0); if (inHQSpace ()) { if (GetHeadEncounter ()) { SET_GAME_STATE (SHIP_TO_COMPEL, 1); } GLOBAL (CurrentActivity) &= ~IN_BATTLE; SaveSisHyperState (); } else { EncounterGroup = 0; if (GetHeadLink (&GLOBAL (ip_group_q))) { SET_GAME_STATE (SHIP_TO_COMPEL, 1); PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); ReinitQueue (&GLOBAL (ip_group_q)); assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0); } if (CurStarDescPtr->Index == SAMATRA_DEFINED) { SET_GAME_STATE (READY_TO_CONFUSE_URQUAN, 1); } if (playerInPlanetOrbit ()) SaveSolarSysLocation (); } return DEVICE_SUCCESS; case AQUA_HELIX_DEVICE: val = GET_GAME_STATE (ULTRON_CONDITION); if (val) { SET_GAME_STATE (ULTRON_CONDITION, val + 1); SET_GAME_STATE (AQUA_HELIX_ON_SHIP, 0); SET_GAME_STATE (DISCUSSED_ULTRON, 0); SET_GAME_STATE (SUPOX_ULTRON_HELP, 0); return DEVICE_SUCCESS; } break; case CLEAR_SPINDLE_DEVICE: val = GET_GAME_STATE (ULTRON_CONDITION); if (val) { SET_GAME_STATE (ULTRON_CONDITION, val + 1); SET_GAME_STATE (CLEAR_SPINDLE_ON_SHIP, 0); SET_GAME_STATE (DISCUSSED_ULTRON, 0); SET_GAME_STATE (SUPOX_ULTRON_HELP, 0); return DEVICE_SUCCESS; } break; case UMGAH_HYPERWAVE_DEVICE: case BURVIX_HYPERWAVE_DEVICE: if (UseCaster ()) return DEVICE_SUCCESS; break; case TAALO_PROTECTOR_DEVICE: break; case EGG_CASING0_DEVICE: case EGG_CASING1_DEVICE: case EGG_CASING2_DEVICE: break; case SYREEN_SHUTTLE_DEVICE: break; case VUX_BEAST_DEVICE: break; case DESTRUCT_CODE_DEVICE: break; case PORTAL_SPAWNER_DEVICE: #define PORTAL_FUEL_COST (10 * FUEL_TANK_SCALE) if (inHyperSpace () && GLOBAL_SIS (FuelOnBoard) >= PORTAL_FUEL_COST) { /* No DeltaSISGauges because the flagship picture * is currently obscured. */ GLOBAL_SIS (FuelOnBoard) -= PORTAL_FUEL_COST; SET_GAME_STATE (PORTAL_COUNTER, 1); return DEVICE_SUCCESS; } break; case URQUAN_WARP_DEVICE: break; case LUNAR_BASE_DEVICE: break; } return DEVICE_FAILURE; } static BOOLEAN DoManipulateDevices (MENU_STATE *pMS) { DEVICES_STATE *devState = pMS->privData; BOOLEAN select, cancel, back, forward; BOOLEAN pagefwd, pageback; select = PulsedInputState.menu[KEY_MENU_SELECT]; cancel = PulsedInputState.menu[KEY_MENU_CANCEL]; back = PulsedInputState.menu[KEY_MENU_UP] || PulsedInputState.menu[KEY_MENU_LEFT]; forward = PulsedInputState.menu[KEY_MENU_DOWN] || PulsedInputState.menu[KEY_MENU_RIGHT]; pagefwd = PulsedInputState.menu[KEY_MENU_PAGE_DOWN]; pageback = PulsedInputState.menu[KEY_MENU_PAGE_UP]; if (GLOBAL (CurrentActivity) & CHECK_ABORT) return FALSE; if (cancel) { return FALSE; } else if (select) { DeviceStatus status; status = InvokeDevice (devState->list[pMS->CurState]); if (status == DEVICE_FAILURE) PlayMenuSound (MENU_SOUND_FAILURE); else if (status == DEVICE_SUCCESS) PlayMenuSound (MENU_SOUND_INVOKED); return (status == DEVICE_FAILURE); } else { SIZE NewTop; SIZE NewState; NewTop = devState->topIndex; NewState = pMS->CurState; if (back) --NewState; else if (forward) ++NewState; else if (pagefwd) NewState += MAX_VIS_DEVICES; else if (pageback) NewState -= MAX_VIS_DEVICES; if (NewState < 0) NewState = 0; else if (NewState >= devState->count) NewState = devState->count - 1; if (NewState < NewTop || NewState >= NewTop + MAX_VIS_DEVICES) NewTop = NewState - NewState % MAX_VIS_DEVICES; if (NewState != pMS->CurState) { if (NewTop != devState->topIndex) { // redraw the display devState->topIndex = NewTop; DrawDevices (devState, (COUNT)~0, NewState); } else { // move selection to new device DrawDevices (devState, pMS->CurState, NewState); } pMS->CurState = NewState; } SleepThread (ONE_SECOND / 30); } return TRUE; } SIZE InventoryDevices (BYTE *pDeviceMap, COUNT Size) { BYTE i; SIZE DevicesOnBoard; DevicesOnBoard = 0; for (i = 0; i < NUM_DEVICES && Size > 0; ++i) { BYTE DeviceState; DeviceState = 0; switch (i) { case ROSY_SPHERE_DEVICE: DeviceState = GET_GAME_STATE (ROSY_SPHERE_ON_SHIP); break; case ARTIFACT_2_DEVICE: DeviceState = GET_GAME_STATE (ARTIFACT_2_ON_SHIP); break; case ARTIFACT_3_DEVICE: DeviceState = GET_GAME_STATE (ARTIFACT_3_ON_SHIP); break; case SUN_EFFICIENCY_DEVICE: DeviceState = GET_GAME_STATE (SUN_DEVICE_ON_SHIP); break; case UTWIG_BOMB_DEVICE: DeviceState = GET_GAME_STATE (UTWIG_BOMB_ON_SHIP); break; case ULTRON_0_DEVICE: DeviceState = (GET_GAME_STATE (ULTRON_CONDITION) == 1); break; case ULTRON_1_DEVICE: DeviceState = (GET_GAME_STATE (ULTRON_CONDITION) == 2); break; case ULTRON_2_DEVICE: DeviceState = (GET_GAME_STATE (ULTRON_CONDITION) == 3); break; case ULTRON_3_DEVICE: DeviceState = (GET_GAME_STATE (ULTRON_CONDITION) == 4); break; case MAIDENS_DEVICE: DeviceState = GET_GAME_STATE (MAIDENS_ON_SHIP); break; case TALKING_PET_DEVICE: DeviceState = GET_GAME_STATE (TALKING_PET_ON_SHIP); break; case AQUA_HELIX_DEVICE: DeviceState = GET_GAME_STATE (AQUA_HELIX_ON_SHIP); break; case CLEAR_SPINDLE_DEVICE: DeviceState = GET_GAME_STATE (CLEAR_SPINDLE_ON_SHIP); break; case UMGAH_HYPERWAVE_DEVICE: DeviceState = GET_GAME_STATE (UMGAH_BROADCASTERS_ON_SHIP); break; case TAALO_PROTECTOR_DEVICE: DeviceState = GET_GAME_STATE (TAALO_PROTECTOR_ON_SHIP); break; case EGG_CASING0_DEVICE: DeviceState = GET_GAME_STATE (EGG_CASE0_ON_SHIP); break; case EGG_CASING1_DEVICE: DeviceState = GET_GAME_STATE (EGG_CASE1_ON_SHIP); break; case EGG_CASING2_DEVICE: DeviceState = GET_GAME_STATE (EGG_CASE2_ON_SHIP); break; case SYREEN_SHUTTLE_DEVICE: DeviceState = GET_GAME_STATE (SYREEN_SHUTTLE_ON_SHIP); break; case VUX_BEAST_DEVICE: DeviceState = GET_GAME_STATE (VUX_BEAST_ON_SHIP); break; case DESTRUCT_CODE_DEVICE: #ifdef NEVER DeviceState = GET_GAME_STATE (DESTRUCT_CODE_ON_SHIP); #endif /* NEVER */ break; case PORTAL_SPAWNER_DEVICE: DeviceState = GET_GAME_STATE (PORTAL_SPAWNER_ON_SHIP); break; case URQUAN_WARP_DEVICE: DeviceState = GET_GAME_STATE (PORTAL_KEY_ON_SHIP); break; case BURVIX_HYPERWAVE_DEVICE: DeviceState = GET_GAME_STATE (BURV_BROADCASTERS_ON_SHIP); break; case LUNAR_BASE_DEVICE: DeviceState = GET_GAME_STATE (MOONBASE_ON_SHIP); break; } #ifndef DEBUG_DEVICES if (DeviceState) #endif /* DEBUG_DEVICES */ { *pDeviceMap++ = i; ++DevicesOnBoard; --Size; } } return DevicesOnBoard; } BOOLEAN DevicesMenu (void) { MENU_STATE MenuState; DEVICES_STATE DevicesState; memset (&MenuState, 0, sizeof MenuState); MenuState.privData = &DevicesState; memset (&DevicesState, 0, sizeof DevicesState); DevicesState.count = InventoryDevices (DevicesState.list, NUM_DEVICES); if (!DevicesState.count) return FALSE; DrawDevices (&DevicesState, (COUNT)~0, MenuState.CurState); SetMenuSounds (MENU_SOUND_ARROWS | MENU_SOUND_PAGEUP | MENU_SOUND_PAGEDOWN, MENU_SOUND_SELECT); MenuState.InputFunc = DoManipulateDevices; DoInput (&MenuState, TRUE); SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); if (GLOBAL_SIS (CrewEnlisted) != (COUNT)~0 && !(GLOBAL (CurrentActivity) & CHECK_ABORT)) { ClearSISRect (DRAW_SIS_DISPLAY); if (!GET_GAME_STATE (PORTAL_COUNTER) && !(GLOBAL (CurrentActivity) & START_ENCOUNTER) && GLOBAL_SIS (CrewEnlisted) != (COUNT)~0) return TRUE; } return FALSE; }