//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 "options.h" #include "colors.h" #include "controls.h" #include "menustat.h" #include "gameopt.h" #include "gamestr.h" #include "resinst.h" #include "nameref.h" #include "settings.h" #include "starbase.h" #include "setup.h" #include "sis.h" #include "units.h" #include "sounds.h" #include "planets/planets.h" // for xxx_DISASTER #include "libs/graphics/gfx_common.h" enum { OUTFIT_FUEL, OUTFIT_MODULES, OUTFIT_SAVELOAD, OUTFIT_EXIT, OUTFIT_DOFUEL }; static void DrawModuleStrings (MENU_STATE *pMS, BYTE NewModule) { RECT r; STAMP s; CONTEXT OldContext; OldContext = SetContext (StatusContext); GetContextClipRect (&r); s.origin.x = RADAR_X - r.corner.x; s.origin.y = RADAR_Y - r.corner.y; r.corner.x = s.origin.x - 1; r.corner.y = s.origin.y - 11; r.extent.width = RADAR_WIDTH + 2; r.extent.height = 11; BatchGraphics (); SetContextForeGroundColor ( BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08)); DrawFilledRectangle (&r); if (NewModule >= EMPTY_SLOT) { r.corner = s.origin; r.extent.width = RADAR_WIDTH; r.extent.height = RADAR_HEIGHT; SetContextForeGroundColor ( BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x00), 0x00)); DrawFilledRectangle (&r); } else if (pMS->CurFrame) { TEXT t; UNICODE buf[40]; s.frame = SetAbsFrameIndex (pMS->CurFrame, NewModule); DrawStamp (&s); t.baseline.x = s.origin.x + RADAR_WIDTH - 2; t.baseline.y = s.origin.y + RADAR_HEIGHT - 2; t.align = ALIGN_RIGHT; t.CharCount = (COUNT)~0; t.pStr = buf; sprintf (buf, "%u", GLOBAL (ModuleCost[NewModule]) * MODULE_COST_SCALE); SetContextFont (TinyFont); SetContextForeGroundColor ( BUILD_COLOR (MAKE_RGB15 (0x00, 0x1F, 0x00), 0x02)); font_DrawText (&t); } UnbatchGraphics (); SetContext (OldContext); } static void RedistributeFuel (void) { const DWORD FuelVolume = GLOBAL_SIS (FuelOnBoard); const CONTEXT OldContext = SetContext (SpaceContext); RECT r; r.extent.height = 1; // Loop through all the rows to draw BatchGraphics (); for (GLOBAL_SIS (FuelOnBoard) = FUEL_RESERVE; GLOBAL_SIS (FuelOnBoard) < GetFTankCapacity (&r.corner); GLOBAL_SIS (FuelOnBoard) += FUEL_VOLUME_PER_ROW) { // If we're less than the fuel level, draw fuel. if (GLOBAL_SIS (FuelOnBoard) < FuelVolume) { r.extent.width = 3; DrawPoint (&r.corner); r.corner.x += r.extent.width + 1; DrawPoint (&r.corner); r.corner.x -= r.extent.width; SetContextForeGroundColor ( SetContextBackGroundColor (BLACK_COLOR)); } else // Otherwise, draw an empty bar. { r.extent.width = 5; SetContextForeGroundColor ( BUILD_COLOR (MAKE_RGB15 (0x0B, 0x00, 0x00), 0x2E)); } DrawFilledRectangle (&r); } UnbatchGraphics (); SetContext (OldContext); GLOBAL_SIS (FuelOnBoard) = FuelVolume; } #define LANDER_X 24 #define LANDER_Y 67 #define LANDER_WIDTH 15 static void DisplayLanders (MENU_STATE *pMS) { STAMP s; s.frame = pMS->ModuleFrame; if (GET_GAME_STATE (CHMMR_BOMB_STATE) == 3) { s.origin.x = s.origin.y = 0; s.frame = DecFrameIndex (s.frame); DrawStamp (&s); } else { COUNT i; s.origin.x = LANDER_X; s.origin.y = LANDER_Y; for (i = 0; i < GLOBAL_SIS (NumLanders); ++i) { DrawStamp (&s); s.origin.x += LANDER_WIDTH; } SetContextForeGroundColor (BLACK_COLOR); for (; i < MAX_LANDERS; ++i) { DrawFilledStamp (&s); s.origin.x += LANDER_WIDTH; } } } static BOOLEAN DoInstallModule (MENU_STATE *pMS) { BYTE NewState, new_slot_piece, old_slot_piece; SIZE FirstItem, LastItem; BOOLEAN select, cancel, motion; if (GLOBAL (CurrentActivity) & CHECK_ABORT) { pMS->InputFunc = DoOutfit; return (TRUE); } select = PulsedInputState.menu[KEY_MENU_SELECT]; cancel = PulsedInputState.menu[KEY_MENU_CANCEL]; motion = PulsedInputState.menu[KEY_MENU_LEFT] || PulsedInputState.menu[KEY_MENU_RIGHT] || PulsedInputState.menu[KEY_MENU_UP] || PulsedInputState.menu[KEY_MENU_DOWN]; SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); FirstItem = 0; NewState = pMS->CurState; switch (NewState) { case PLANET_LANDER: case EMPTY_SLOT + 3: old_slot_piece = pMS->delta_item < GLOBAL_SIS (NumLanders) ? PLANET_LANDER : (EMPTY_SLOT + 3); LastItem = MAX_LANDERS - 1; break; case FUSION_THRUSTER: case EMPTY_SLOT + 0: old_slot_piece = GLOBAL_SIS (DriveSlots[pMS->delta_item]); LastItem = NUM_DRIVE_SLOTS - 1; break; case TURNING_JETS: case EMPTY_SLOT + 1: old_slot_piece = GLOBAL_SIS (JetSlots[pMS->delta_item]); LastItem = NUM_JET_SLOTS - 1; break; default: old_slot_piece = GLOBAL_SIS (ModuleSlots[pMS->delta_item]); if (GET_GAME_STATE (CHMMR_BOMB_STATE) == 3) FirstItem = NUM_BOMB_MODULES; LastItem = NUM_MODULE_SLOTS - 1; break; } if (NewState < CREW_POD) FirstItem = LastItem = NewState; else if (NewState < EMPTY_SLOT) FirstItem = CREW_POD, LastItem = NUM_PURCHASE_MODULES - 1; if (!pMS->Initialized) { new_slot_piece = old_slot_piece; pMS->Initialized = TRUE; pMS->InputFunc = DoInstallModule; SetContext (SpaceContext); ClearSISRect (CLEAR_SIS_RADAR); SetFlashRect (NULL); goto InitFlash; } else if (select || cancel) { new_slot_piece = pMS->CurState; if (select) { if (new_slot_piece < EMPTY_SLOT) { if (GLOBAL_SIS (ResUnits) < (DWORD)(GLOBAL (ModuleCost[new_slot_piece]) * MODULE_COST_SCALE)) { // not enough RUs to build PlayMenuSound (MENU_SOUND_FAILURE); return (TRUE); } } else if (new_slot_piece == EMPTY_SLOT + 2) { if (old_slot_piece == CREW_POD) { if (GLOBAL_SIS (CrewEnlisted) > CREW_POD_CAPACITY * (CountSISPieces (CREW_POD) - 1)) { // crew pod still needed for crew recruited PlayMenuSound (MENU_SOUND_FAILURE); return (TRUE); } } else if (old_slot_piece == FUEL_TANK || old_slot_piece == HIGHEFF_FUELSYS) { DWORD volume; volume = (DWORD)CountSISPieces (FUEL_TANK) * FUEL_TANK_CAPACITY + (DWORD)CountSISPieces (HIGHEFF_FUELSYS) * HEFUEL_TANK_CAPACITY; volume -= (old_slot_piece == FUEL_TANK ? FUEL_TANK_CAPACITY : HEFUEL_TANK_CAPACITY); if (GLOBAL_SIS (FuelOnBoard) > volume + FUEL_RESERVE) { // fuel tank still needed for the fuel on board PlayMenuSound (MENU_SOUND_FAILURE); return (TRUE); } } else if (old_slot_piece == STORAGE_BAY) { if (GLOBAL_SIS (TotalElementMass) > STORAGE_BAY_CAPACITY * (CountSISPieces (STORAGE_BAY) - 1)) { // storage bay still needed for the cargo PlayMenuSound (MENU_SOUND_FAILURE); return (TRUE); } } } } SetContext (SpaceContext); SetFlashRect (NULL); if (select) { if (new_slot_piece >= EMPTY_SLOT && old_slot_piece >= EMPTY_SLOT) { new_slot_piece -= EMPTY_SLOT - 1; if (new_slot_piece > CREW_POD) new_slot_piece = PLANET_LANDER; } else { switch (pMS->CurState) { case PLANET_LANDER: ++GLOBAL_SIS (NumLanders); break; case EMPTY_SLOT + 3: --GLOBAL_SIS (NumLanders); break; case FUSION_THRUSTER: case EMPTY_SLOT + 0: GLOBAL_SIS (DriveSlots[pMS->delta_item]) = new_slot_piece; break; case TURNING_JETS: case EMPTY_SLOT + 1: GLOBAL_SIS (JetSlots[pMS->delta_item]) = new_slot_piece; break; default: GLOBAL_SIS (ModuleSlots[pMS->delta_item]) = new_slot_piece; break; } if (new_slot_piece < EMPTY_SLOT) DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA, -(GLOBAL (ModuleCost[new_slot_piece]) * MODULE_COST_SCALE)); else /* if (old_slot_piece < EMPTY_SLOT) */ DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA, GLOBAL (ModuleCost[old_slot_piece]) * MODULE_COST_SCALE); if (pMS->CurState == PLANET_LANDER || pMS->CurState == EMPTY_SLOT + 3) DisplayLanders (pMS); else { DrawShipPiece (pMS->ModuleFrame, new_slot_piece, pMS->delta_item, FALSE); if (new_slot_piece > TURNING_JETS && old_slot_piece > TURNING_JETS) RedistributeFuel (); if (optWhichFonts == OPT_PC) DrawFlagshipStats (); } } cancel = FALSE; } if (pMS->CurState < EMPTY_SLOT) { pMS->CurState += EMPTY_SLOT - 1; if (pMS->CurState < EMPTY_SLOT) pMS->CurState = EMPTY_SLOT + 3; else if (pMS->CurState > EMPTY_SLOT + 2) pMS->CurState = EMPTY_SLOT + 2; if (cancel) new_slot_piece = pMS->CurState; goto InitFlash; } else if (!cancel) { pMS->CurState = new_slot_piece; goto InitFlash; } else { SetContext (StatusContext); DrawMenuStateStrings (PM_FUEL, pMS->CurState = OUTFIT_MODULES); SetFlashRect (SFR_MENU_3DO); pMS->InputFunc = DoOutfit; ClearSISRect (DRAW_SIS_DISPLAY); } } else if (motion) { SIZE NewItem; NewItem = NewState < EMPTY_SLOT ? pMS->CurState : pMS->delta_item; do { if (NewState >= EMPTY_SLOT && (PulsedInputState.menu[KEY_MENU_UP] || PulsedInputState.menu[KEY_MENU_DOWN])) { if (PulsedInputState.menu[KEY_MENU_UP]) { if (NewState-- == EMPTY_SLOT) NewState = EMPTY_SLOT + 3; } else { if (NewState++ == EMPTY_SLOT + 3) NewState = EMPTY_SLOT; } NewItem = 0; if (GET_GAME_STATE (CHMMR_BOMB_STATE) == 3) { if (NewState == EMPTY_SLOT + 3) NewState = PulsedInputState.menu[KEY_MENU_UP] ? EMPTY_SLOT + 2 : EMPTY_SLOT; if (NewState == EMPTY_SLOT + 2) NewItem = NUM_BOMB_MODULES; } pMS->delta_item = NewItem; } else if (PulsedInputState.menu[KEY_MENU_LEFT] || PulsedInputState.menu[KEY_MENU_UP]) { if (NewItem-- == FirstItem) NewItem = LastItem; } else if (PulsedInputState.menu[KEY_MENU_RIGHT] || PulsedInputState.menu[KEY_MENU_DOWN]) { if (NewItem++ == LastItem) NewItem = FirstItem; } } while (NewState < EMPTY_SLOT && (GLOBAL (ModuleCost[NewItem]) == 0 || (NewItem >= GUN_WEAPON && NewItem <= CANNON_WEAPON && pMS->delta_item > 0 && pMS->delta_item < 13))); if (NewState < EMPTY_SLOT) { if (NewItem != pMS->CurState) { pMS->CurState = NewItem; PreUpdateFlashRect (); DrawModuleStrings (pMS, NewItem); PostUpdateFlashRect (); } } else if (NewItem != pMS->delta_item || NewState != pMS->CurState) { SIZE w; switch (NewState) { case PLANET_LANDER: case EMPTY_SLOT + 3: new_slot_piece = NewItem < GLOBAL_SIS (NumLanders) ? PLANET_LANDER : (EMPTY_SLOT + 3); break; case FUSION_THRUSTER: case EMPTY_SLOT + 0: new_slot_piece = GLOBAL_SIS (DriveSlots[NewItem]); break; case TURNING_JETS: case EMPTY_SLOT + 1: new_slot_piece = GLOBAL_SIS (JetSlots[NewItem]); break; default: new_slot_piece = GLOBAL_SIS (ModuleSlots[NewItem]); break; } SetContext (SpaceContext); if (NewState == pMS->CurState) { if (NewState == PLANET_LANDER || NewState == EMPTY_SLOT + 3) w = LANDER_WIDTH; else w = SHIP_PIECE_OFFSET; w *= (NewItem - pMS->delta_item); pMS->flash_rect0.corner.x += w; pMS->delta_item = NewItem; } else { pMS->CurState = NewState; InitFlash: w = SHIP_PIECE_OFFSET; switch (pMS->CurState) { case PLANET_LANDER: case EMPTY_SLOT + 3: pMS->flash_rect0.corner.x = LANDER_X - 1; pMS->flash_rect0.corner.y = LANDER_Y - 1; pMS->flash_rect0.extent.width = 11 + 2; pMS->flash_rect0.extent.height = 13 + 2; w = LANDER_WIDTH; break; case FUSION_THRUSTER: case EMPTY_SLOT + 0: pMS->flash_rect0.corner.x = DRIVE_TOP_X - 1; pMS->flash_rect0.corner.y = DRIVE_TOP_Y - 1; pMS->flash_rect0.extent.width = 8; pMS->flash_rect0.extent.height = 6; break; case TURNING_JETS: case EMPTY_SLOT + 1: pMS->flash_rect0.corner.x = JET_TOP_X - 1; pMS->flash_rect0.corner.y = JET_TOP_Y - 1; pMS->flash_rect0.extent.width = 9; pMS->flash_rect0.extent.height = 10; break; default: pMS->flash_rect0.corner.x = MODULE_TOP_X - 1; pMS->flash_rect0.corner.y = MODULE_TOP_Y - 1; pMS->flash_rect0.extent.width = SHIP_PIECE_OFFSET + 2; pMS->flash_rect0.extent.height = 34; break; } w *= pMS->delta_item; pMS->flash_rect0.corner.x += w; } DrawModuleStrings (pMS, new_slot_piece); if (pMS->CurState < EMPTY_SLOT) // flash with PC menus too SetFlashRect (SFR_MENU_ANY); else SetFlashRect (&pMS->flash_rect0); } } return (TRUE); } static void ChangeFuelQuantity (void) { int incr = 0; // Fuel increment in fuel points (not units). if (PulsedInputState.menu[KEY_MENU_UP]) incr = FUEL_TANK_SCALE; // +1 Unit else if (PulsedInputState.menu[KEY_MENU_DOWN]) incr = -FUEL_TANK_SCALE; // -1 Unit else if (PulsedInputState.menu[KEY_MENU_PAGE_UP]) incr = FUEL_VOLUME_PER_ROW; // +1 Bar else if (PulsedInputState.menu[KEY_MENU_PAGE_DOWN]) incr = -FUEL_VOLUME_PER_ROW; // -1 Bar else return; // Clamp incr to what we can afford/hold/have. { const int maxFit = GetFuelTankCapacity () - GLOBAL_SIS (FuelOnBoard); const int maxAfford = GLOBAL_SIS (ResUnits) / GLOBAL (FuelCost); const int minFit = - (int) GLOBAL_SIS (FuelOnBoard); if (incr > maxFit) incr = maxFit; // All we can hold. if (incr > maxAfford * FUEL_TANK_SCALE) incr = maxAfford * FUEL_TANK_SCALE; // All we can afford. if (incr < minFit) incr = minFit; // All we have. } if (!incr) { // No more room, not enough RUs, or no fuel left to drain. PlayMenuSound (MENU_SOUND_FAILURE); } else { const int cost = (incr / FUEL_TANK_SCALE) * GLOBAL (FuelCost); PreUpdateFlashRect (); DeltaSISGauges (0, incr, -cost); PostUpdateFlashRect (); RedistributeFuel (); } { // Make fuel gauge flash. RECT r; CONTEXT oldContext = SetContext (StatusContext); GetGaugeRect (&r, FALSE); SetFlashRect (&r); SetContext (oldContext); } } static void onNamingDone (void) { // In case player just named a ship, redraw it DrawFlagshipName (FALSE); } BOOLEAN DoOutfit (MENU_STATE *pMS) { if (GLOBAL (CurrentActivity) & CHECK_ABORT) goto ExitOutfit; if (!pMS->Initialized) { pMS->InputFunc = DoOutfit; pMS->Initialized = TRUE; SetNamingCallback (onNamingDone); { COUNT num_frames; STAMP s; pMS->CurFrame = CaptureDrawable ( LoadGraphic (MODULES_PMAP_ANIM)); pMS->hMusic = LoadMusic (OUTFIT_MUSIC); pMS->CurState = OUTFIT_FUEL; pMS->ModuleFrame = CaptureDrawable ( LoadGraphic (SISMODS_MASK_PMAP_ANIM)); s.origin.x = s.origin.y = 0; s.frame = CaptureDrawable ( LoadGraphic (OUTFIT_PMAP_ANIM)); SetTransitionSource (NULL); BatchGraphics (); DrawSISFrame (); DrawSISMessage (GAME_STRING (STARBASE_STRING_BASE + 2)); DrawSISTitle (GAME_STRING (STARBASE_STRING_BASE)); SetContext (SpaceContext); DrawStamp (&s); DestroyDrawable (ReleaseDrawable (s.frame)); for (num_frames = 0; num_frames < NUM_DRIVE_SLOTS; ++num_frames) { BYTE which_piece; which_piece = GLOBAL_SIS (DriveSlots[num_frames]); if (which_piece < EMPTY_SLOT) DrawShipPiece (pMS->ModuleFrame, which_piece, num_frames, FALSE); } for (num_frames = 0; num_frames < NUM_JET_SLOTS; ++num_frames) { BYTE which_piece; which_piece = GLOBAL_SIS (JetSlots[num_frames]); if (which_piece < EMPTY_SLOT) DrawShipPiece (pMS->ModuleFrame, which_piece, num_frames, FALSE); } for (num_frames = 0; num_frames < NUM_MODULE_SLOTS; ++num_frames) { BYTE which_piece; which_piece = GLOBAL_SIS (ModuleSlots[num_frames]); if (which_piece < EMPTY_SLOT) DrawShipPiece (pMS->ModuleFrame, which_piece, num_frames, FALSE); } RedistributeFuel (); DisplayLanders (pMS); if (GET_GAME_STATE (CHMMR_BOMB_STATE) < 3) { BYTE ShieldFlags; ShieldFlags = GET_GAME_STATE (LANDER_SHIELDS); s.frame = SetAbsFrameIndex (pMS->ModuleFrame, GetFrameCount (pMS->ModuleFrame) - 5); if (ShieldFlags & (1 << EARTHQUAKE_DISASTER)) DrawStamp (&s); s.frame = IncFrameIndex (s.frame); if (ShieldFlags & (1 << BIOLOGICAL_DISASTER)) DrawStamp (&s); s.frame = IncFrameIndex (s.frame); if (ShieldFlags & (1 << LIGHTNING_DISASTER)) DrawStamp (&s); s.frame = IncFrameIndex (s.frame); if (ShieldFlags & (1 << LAVASPOT_DISASTER)) DrawStamp (&s); } DrawMenuStateStrings (PM_FUEL, pMS->CurState); DrawFlagshipName (FALSE); if (optWhichFonts == OPT_PC) DrawFlagshipStats (); ScreenTransition (3, NULL); PlayMusic (pMS->hMusic, TRUE, 1); UnbatchGraphics (); SetFlashRect (SFR_MENU_3DO); GLOBAL_SIS (FuelOnBoard) = (GLOBAL_SIS (FuelOnBoard) + (FUEL_TANK_SCALE >> 1)) / FUEL_TANK_SCALE; GLOBAL_SIS (FuelOnBoard) *= FUEL_TANK_SCALE; } SetContext (StatusContext); } else if (PulsedInputState.menu[KEY_MENU_CANCEL] || (PulsedInputState.menu[KEY_MENU_SELECT] && pMS->CurState == OUTFIT_EXIT)) { if (pMS->CurState == OUTFIT_DOFUEL) { pMS->CurState = OUTFIT_FUEL; SetFlashRect (SFR_MENU_3DO); } else { ExitOutfit: DestroyDrawable (ReleaseDrawable (pMS->CurFrame)); pMS->CurFrame = 0; DestroyDrawable (ReleaseDrawable (pMS->ModuleFrame)); pMS->ModuleFrame = 0; SetNamingCallback (NULL); return (FALSE); } } else if (PulsedInputState.menu[KEY_MENU_SELECT]) { switch (pMS->CurState) { case OUTFIT_FUEL: { RECT r; pMS->CurState = OUTFIT_DOFUEL; SetContext (StatusContext); GetGaugeRect (&r, FALSE); SetFlashRect (&r); break; } case OUTFIT_DOFUEL: pMS->CurState = OUTFIT_FUEL; SetFlashRect (SFR_MENU_3DO); break; case OUTFIT_MODULES: pMS->CurState = EMPTY_SLOT + 2; if (GET_GAME_STATE (CHMMR_BOMB_STATE) != 3) pMS->delta_item = 0; else pMS->delta_item = NUM_BOMB_MODULES; pMS->first_item.y = 0; pMS->Initialized = 0; DoInstallModule (pMS); break; case OUTFIT_SAVELOAD: // Clearing FlashRect is not necessary if (!GameOptions ()) goto ExitOutfit; DrawMenuStateStrings (PM_FUEL, pMS->CurState); SetFlashRect (SFR_MENU_3DO); break; } } else { switch (pMS->CurState) { case OUTFIT_DOFUEL: SetMenuSounds (MENU_SOUND_UP | MENU_SOUND_DOWN | MENU_SOUND_PAGEUP | MENU_SOUND_PAGEDOWN, MENU_SOUND_SELECT | MENU_SOUND_CANCEL); break; default: SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); break; } if (pMS->CurState == OUTFIT_DOFUEL) { ChangeFuelQuantity (); SleepThread (ONE_SECOND / 30); } else DoMenuChooser (pMS, PM_FUEL); } return (TRUE); }