diff options
Diffstat (limited to 'src/uqm/shipyard.c')
-rw-r--r-- | src/uqm/shipyard.c | 1495 |
1 files changed, 1495 insertions, 0 deletions
diff --git a/src/uqm/shipyard.c b/src/uqm/shipyard.c new file mode 100644 index 0000000..f317ba3 --- /dev/null +++ b/src/uqm/shipyard.c @@ -0,0 +1,1495 @@ +//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 "controls.h" +#include "menustat.h" +#include "fmv.h" +#include "gameopt.h" +#include "gamestr.h" +#include "supermelee/melee.h" +#include "master.h" +#include "options.h" +#include "races.h" +#include "nameref.h" +#include "resinst.h" +#include "settings.h" +#include "starbase.h" +#include "setup.h" +#include "sis.h" +#include "units.h" +#include "sounds.h" +#include "libs/graphics/gfx_common.h" +#include "libs/inplib.h" + + +#ifdef USE_3DO_HANGAR +// 3DO 4x3 hangar layout +# define HANGAR_SHIPS_ROW 4 +# define HANGAR_Y 64 +# define HANGAR_DY 44 + +static const COORD hangar_x_coords[HANGAR_SHIPS_ROW] = +{ + 19, 60, 116, 157 +}; + +#else // use PC hangar +// modified PC 6x2 hangar layout +# define HANGAR_SHIPS_ROW 6 +# define HANGAR_Y 88 +# define HANGAR_DY 84 + +static const COORD hangar_x_coords[HANGAR_SHIPS_ROW] = +{ + 0, 38, 76, 131, 169, 207 +}; +#endif // USE_3DO_HANGAR + +#define HANGAR_SHIPS 12 +#define HANGAR_ROWS (HANGAR_SHIPS / HANGAR_SHIPS_ROW) +#define HANGAR_ANIM_RATE 15 // fps + +enum +{ + SHIPYARD_CREW, + SHIPYARD_SAVELOAD, + SHIPYARD_EXIT +}; + +// Editing mode for DoModifyShips() +typedef enum { + DMS_Mode_navigate, // Navigating the ship slots. + DMS_Mode_addEscort, // Selecting a ship to add to an empty slot. + DMS_Mode_editCrew, // Hiring or dismissing crew. + DMS_Mode_exit, // Leaving DoModifyShips() mode. +} DMS_Mode; + +static COUNT ShipCost[] = +{ + RACE_SHIP_COST +}; + + +static void +animatePowerLines (MENU_STATE *pMS) +{ + static STAMP s; + static COLORMAP ColorMap; + static TimeCount NextTime = 0; + TimeCount Now = GetTimeCounter (); + + if (pMS) + { // Init animation + s.origin.x = 0; + s.origin.y = 0; + s.frame = SetAbsFrameIndex (pMS->ModuleFrame, 24); + ColorMap = SetAbsColorMapIndex (pMS->CurString, 0); + } + + if (Now >= NextTime || pMS) + { + NextTime = Now + (ONE_SECOND / HANGAR_ANIM_RATE); + + SetColorMap (GetColorMapAddress (ColorMap)); + DrawStamp (&s); + // Advance colomap cycle + ColorMap = SetRelColorMapIndex (ColorMap, 1); + } +} + +static void +on_input_frame (void) +{ + CONTEXT oldContext; + + oldContext = SetContext (SpaceContext); + animatePowerLines (NULL); + SetContext (oldContext); +} + +#ifdef WANT_SHIP_SPINS +static void +SpinStarShip (MENU_STATE *pMS, HFLEETINFO hStarShip) +{ + int Index; + FLEET_INFO *FleetPtr; + + FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip); + Index = FindMasterShipIndex (FleetPtr->SpeciesID); + UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip); + + if (Index >= 0 && Index < NUM_MELEE_SHIPS) + { + DoShipSpin (Index, pMS->hMusic); + } +} +#endif + +// Count the ships which can be built by the player. +static COUNT +GetAvailableRaceCount (void) +{ + COUNT Index; + HFLEETINFO hStarShip, hNextShip; + + Index = 0; + for (hStarShip = GetHeadLink (&GLOBAL (avail_race_q)); + hStarShip; hStarShip = hNextShip) + { + FLEET_INFO *FleetPtr; + + FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip); + if (FleetPtr->allied_state == GOOD_GUY) + ++Index; + + hNextShip = _GetSuccLink (FleetPtr); + UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip); + } + + return Index; +} + +static HFLEETINFO +GetAvailableRaceFromIndex (BYTE Index) +{ + HFLEETINFO hStarShip, hNextShip; + + for (hStarShip = GetHeadLink (&GLOBAL (avail_race_q)); + hStarShip; hStarShip = hNextShip) + { + FLEET_INFO *FleetPtr; + + FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip); + if (FleetPtr->allied_state == GOOD_GUY && Index-- == 0) + { + UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip); + return hStarShip; + } + + hNextShip = _GetSuccLink (FleetPtr); + UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip); + } + + return 0; +} + +static void +DrawRaceStrings (MENU_STATE *pMS, BYTE NewRaceItem) +{ + 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 (); + ClearSISRect (CLEAR_SIS_RADAR); + SetContextForeGroundColor ( + BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08)); + DrawFilledRectangle (&r); + r.corner = s.origin; + r.extent.width = RADAR_WIDTH; + r.extent.height = RADAR_HEIGHT; + SetContextForeGroundColor (BLACK_COLOR); + DrawFilledRectangle (&r); + if (NewRaceItem != (BYTE)~0) + { + TEXT t; + HFLEETINFO hStarShip; + FLEET_INFO *FleetPtr; + UNICODE buf[30]; + + hStarShip = GetAvailableRaceFromIndex (NewRaceItem); + NewRaceItem = GetIndexFromStarShip (&GLOBAL (avail_race_q), + hStarShip); + + // Draw the ship name, above the ship image. + s.frame = SetAbsFrameIndex (pMS->ModuleFrame, 3 + NewRaceItem); + DrawStamp (&s); + + // Draw the ship image. + FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip); + s.frame = FleetPtr->melee_icon; + UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip); + t.baseline.x = s.origin.x + RADAR_WIDTH - 2; + t.baseline.y = s.origin.y + RADAR_HEIGHT - 2; + s.origin.x += (RADAR_WIDTH >> 1); + s.origin.y += (RADAR_HEIGHT >> 1); + DrawStamp (&s); + + // Print the ship cost. + t.align = ALIGN_RIGHT; + t.CharCount = (COUNT)~0; + t.pStr = buf; + sprintf (buf, "%u", ShipCost[NewRaceItem]); + SetContextFont (TinyFont); + SetContextForeGroundColor ( + BUILD_COLOR (MAKE_RGB15 (0x00, 0x1F, 0x00), 0x02)); + font_DrawText (&t); + } + UnbatchGraphics (); + SetContext (OldContext); + +} + +#define SHIP_WIN_WIDTH 34 +#define SHIP_WIN_HEIGHT (SHIP_WIN_WIDTH + 6) +#define SHIP_WIN_FRAMES ((SHIP_WIN_WIDTH >> 1) + 1) + +// Print the crew count of an escort ship on top of its (already drawn) +// image, either as '30' (full), '28/30' (partially full), or 'SCRAP' +// (empty). +// pRect is the rectangle of the ship image. +static void +ShowShipCrew (SHIP_FRAGMENT *StarShipPtr, const RECT *pRect) +{ + RECT r; + TEXT t; + UNICODE buf[80]; + HFLEETINFO hTemplate; + FLEET_INFO *TemplatePtr; + COUNT maxCrewLevel; + + hTemplate = GetStarShipFromIndex (&GLOBAL (avail_race_q), + StarShipPtr->race_id); + TemplatePtr = LockFleetInfo (&GLOBAL (avail_race_q), hTemplate); + maxCrewLevel = TemplatePtr->crew_level; + UnlockFleetInfo (&GLOBAL (avail_race_q), hTemplate); + + if (StarShipPtr->crew_level >= maxCrewLevel) + sprintf (buf, "%u", StarShipPtr->crew_level); + else if (StarShipPtr->crew_level == 0) + // XXX: "SCRAP" needs to be moved to starcon.txt + utf8StringCopy (buf, sizeof (buf), "SCRAP"); + else + sprintf (buf, "%u/%u", StarShipPtr->crew_level, maxCrewLevel); + + r = *pRect; + t.baseline.x = r.corner.x + (r.extent.width >> 1); + t.baseline.y = r.corner.y + r.extent.height - 1; + t.align = ALIGN_CENTER; + t.pStr = buf; + t.CharCount = (COUNT)~0; + if (r.corner.y) + { + r.corner.y = t.baseline.y - 6; + r.extent.width = SHIP_WIN_WIDTH; + r.extent.height = 6; + SetContextForeGroundColor (BLACK_COLOR); + DrawFilledRectangle (&r); + } + SetContextForeGroundColor ((StarShipPtr->crew_level != 0) ? + (BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x00), 0x02)): + (BUILD_COLOR (MAKE_RGB15 (0x12, 0x00, 0x00), 0x2B))); + font_DrawText (&t); +} + +static void +ShowCombatShip (MENU_STATE *pMS, COUNT which_window, + SHIP_FRAGMENT *YankedStarShipPtr) +{ + COUNT i; + COUNT num_ships; + HSHIPFRAG hStarShip, hNextShip; + SHIP_FRAGMENT *StarShipPtr; + struct + { + SHIP_FRAGMENT *StarShipPtr; + POINT finished_s; + STAMP ship_s; + STAMP lfdoor_s; + STAMP rtdoor_s; + } ship_win_info[MAX_BUILT_SHIPS], *pship_win_info; + + num_ships = 1; + pship_win_info = &ship_win_info[0]; + if (YankedStarShipPtr) + { + pship_win_info->StarShipPtr = YankedStarShipPtr; + + pship_win_info->lfdoor_s.origin.x = -(SHIP_WIN_WIDTH >> 1); + pship_win_info->rtdoor_s.origin.x = (SHIP_WIN_WIDTH >> 1); + pship_win_info->lfdoor_s.origin.y = 0; + pship_win_info->rtdoor_s.origin.y = 0; + pship_win_info->lfdoor_s.frame = IncFrameIndex (pMS->ModuleFrame); + pship_win_info->rtdoor_s.frame = + IncFrameIndex (pship_win_info->lfdoor_s.frame); + + pship_win_info->ship_s.origin.x = (SHIP_WIN_WIDTH >> 1) + 1; + pship_win_info->ship_s.origin.y = (SHIP_WIN_WIDTH >> 1); + pship_win_info->ship_s.frame = YankedStarShipPtr->melee_icon; + + pship_win_info->finished_s.x = hangar_x_coords[ + which_window % HANGAR_SHIPS_ROW]; + pship_win_info->finished_s.y = HANGAR_Y + (HANGAR_DY * + (which_window / HANGAR_SHIPS_ROW)); + } + else + { + if (which_window == (COUNT)~0) + { + hStarShip = GetHeadLink (&GLOBAL (built_ship_q)); + num_ships = CountLinks (&GLOBAL (built_ship_q)); + } + else + { + HSHIPFRAG hTailShip; + + hTailShip = GetTailLink (&GLOBAL (built_ship_q)); + RemoveQueue (&GLOBAL (built_ship_q), hTailShip); + + hStarShip = GetHeadLink (&GLOBAL (built_ship_q)); + while (hStarShip) + { + StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip); + if (StarShipPtr->index > which_window) + { + UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip); + break; + } + hNextShip = _GetSuccLink (StarShipPtr); + UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip); + + hStarShip = hNextShip; + } + InsertQueue (&GLOBAL (built_ship_q), hTailShip, hStarShip); + + hStarShip = hTailShip; + StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip); + StarShipPtr->index = which_window; + UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip); + } + + for (i = 0; i < num_ships; ++i) + { + StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip); + hNextShip = _GetSuccLink (StarShipPtr); + + pship_win_info->StarShipPtr = StarShipPtr; + // XXX BUG: this looks wrong according to the original + // semantics of LockShipFrag(): StarShipPtr is not valid + // anymore after UnlockShipFrag() is called, but it is + // used thereafter. + + pship_win_info->lfdoor_s.origin.x = -1; + pship_win_info->rtdoor_s.origin.x = 1; + pship_win_info->lfdoor_s.origin.y = 0; + pship_win_info->rtdoor_s.origin.y = 0; + pship_win_info->lfdoor_s.frame = IncFrameIndex (pMS->ModuleFrame); + pship_win_info->rtdoor_s.frame = + IncFrameIndex (pship_win_info->lfdoor_s.frame); + + pship_win_info->ship_s.origin.x = (SHIP_WIN_WIDTH >> 1) + 1; + pship_win_info->ship_s.origin.y = (SHIP_WIN_WIDTH >> 1); + pship_win_info->ship_s.frame = StarShipPtr->melee_icon; + + which_window = StarShipPtr->index; + pship_win_info->finished_s.x = hangar_x_coords[ + which_window % HANGAR_SHIPS_ROW]; + pship_win_info->finished_s.y = HANGAR_Y + (HANGAR_DY * + (which_window / HANGAR_SHIPS_ROW)); + ++pship_win_info; + + UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip); + hStarShip = hNextShip; + } + } + + if (num_ships) + { + BOOLEAN AllDoorsFinished; + DWORD TimeIn; + RECT r; + CONTEXT OldContext; + RECT OldClipRect; + int j; + + AllDoorsFinished = FALSE; + r.corner.x = r.corner.y = 0; + r.extent.width = SHIP_WIN_WIDTH; + r.extent.height = SHIP_WIN_HEIGHT; + FlushInput (); + TimeIn = GetTimeCounter (); + + for (j = 0; (j < SHIP_WIN_FRAMES) && !AllDoorsFinished; j++) + { + SleepThreadUntil (TimeIn + ONE_SECOND / 24); + TimeIn = GetTimeCounter (); + if (AnyButtonPress (FALSE)) + { + if (YankedStarShipPtr != 0) + { // Fully close the doors + ship_win_info[0].lfdoor_s.origin.x = 0; + ship_win_info[0].rtdoor_s.origin.x = 0; + } + AllDoorsFinished = TRUE; + } + + OldContext = SetContext (SpaceContext); + GetContextClipRect (&OldClipRect); + SetContextBackGroundColor (BLACK_COLOR); + + BatchGraphics (); + pship_win_info = &ship_win_info[0]; + for (i = 0; i < num_ships; ++i) + { + { + RECT ClipRect; + + ClipRect.corner.x = SIS_ORG_X + + pship_win_info->finished_s.x; + ClipRect.corner.y = SIS_ORG_Y + + pship_win_info->finished_s.y; + ClipRect.extent.width = SHIP_WIN_WIDTH; + ClipRect.extent.height = SHIP_WIN_HEIGHT; + SetContextClipRect (&ClipRect); + + ClearDrawable (); + DrawStamp (&pship_win_info->ship_s); + ShowShipCrew (pship_win_info->StarShipPtr, &r); + if (!AllDoorsFinished || YankedStarShipPtr) + { + DrawStamp (&pship_win_info->lfdoor_s); + DrawStamp (&pship_win_info->rtdoor_s); + if (YankedStarShipPtr) + { // Close the doors + ++pship_win_info->lfdoor_s.origin.x; + --pship_win_info->rtdoor_s.origin.x; + } + else + { // Open the doors + --pship_win_info->lfdoor_s.origin.x; + ++pship_win_info->rtdoor_s.origin.x; + } + } + } + ++pship_win_info; + } + + SetContextClipRect (&OldClipRect); +#ifndef USE_3DO_HANGAR + animatePowerLines (NULL); +#endif + UnbatchGraphics (); + SetContext (OldContext); + } + } +} + +static void +CrewTransaction (SIZE crew_delta) +{ + if (crew_delta) + { + SIZE crew_bought; + + crew_bought = (SIZE)MAKE_WORD ( + GET_GAME_STATE (CREW_PURCHASED0), + GET_GAME_STATE (CREW_PURCHASED1)) + crew_delta; + if (crew_bought < 0) + { + if (crew_delta < 0) + crew_bought = 0; + else + crew_bought = 0x7FFF; + } + else if (crew_delta > 0) + { + if (crew_bought >= CREW_EXPENSE_THRESHOLD + && crew_bought - crew_delta < CREW_EXPENSE_THRESHOLD) + { + GLOBAL (CrewCost) += 2; + DrawMenuStateStrings (PM_CREW, SHIPYARD_CREW); + } + } + else + { + if (crew_bought < CREW_EXPENSE_THRESHOLD + && crew_bought - crew_delta >= CREW_EXPENSE_THRESHOLD) + { + GLOBAL (CrewCost) -= 2; + DrawMenuStateStrings (PM_CREW, SHIPYARD_CREW); + } + } + if (CheckAlliance (SHOFIXTI_SHIP) != GOOD_GUY) + { + SET_GAME_STATE (CREW_PURCHASED0, LOBYTE (crew_bought)); + SET_GAME_STATE (CREW_PURCHASED1, HIBYTE (crew_bought)); + } + } +} + +static void +DMS_FlashFlagShip (void) +{ + RECT r; + r.corner.x = 0; + r.corner.y = 0; + r.extent.width = SIS_SCREEN_WIDTH; + r.extent.height = 61; + SetFlashRect (&r); +} + +static void +DMS_GetEscortShipRect (RECT *rOut, BYTE slotNr) +{ + BYTE row = slotNr / HANGAR_SHIPS_ROW; + BYTE col = slotNr % HANGAR_SHIPS_ROW; + + rOut->corner.x = hangar_x_coords[col]; + rOut->corner.y = HANGAR_Y + (HANGAR_DY * row); + rOut->extent.width = SHIP_WIN_WIDTH; + rOut->extent.height = SHIP_WIN_HEIGHT; +} + +static void +DMS_FlashEscortShip (BYTE slotNr) +{ + RECT r; + DMS_GetEscortShipRect (&r, slotNr); + SetFlashRect (&r); +} + +static void +DMS_FlashFlagShipCrewCount (void) +{ + RECT r; + SetContext (StatusContext); + GetGaugeRect (&r, TRUE); + SetFlashRect (&r); + SetContext (SpaceContext); +} + +static void +DMS_FlashEscortShipCrewCount (BYTE slotNr) +{ + RECT r; + BYTE row = slotNr / HANGAR_SHIPS_ROW; + BYTE col = slotNr % HANGAR_SHIPS_ROW; + + r.corner.x = hangar_x_coords[col]; + r.corner.y = (HANGAR_Y + (HANGAR_DY * row)) + (SHIP_WIN_HEIGHT - 6); + r.extent.width = SHIP_WIN_WIDTH; + r.extent.height = 5; + + SetContext (SpaceContext); + SetFlashRect (&r); +} + +// Helper function for DoModifyShips(). Called to change the flash +// rectangle to the currently selected ship (flagship or escort ship). +static void +DMS_FlashActiveShip (MENU_STATE *pMS) +{ + if (HINIBBLE (pMS->CurState)) + { + // Flash the flag ship. + DMS_FlashFlagShip (); + } + else + { + // Flash the current escort ship slot. + DMS_FlashEscortShip (pMS->CurState); + } +} + +// Helper function for DoModifyShips(). Called to switch between +// the various edit modes. +// XXX: right now, this only switches the sound and flash rectangle. +// Perhaps we should move more of the code to modify other aspects +// here too. +static void +DMS_SetMode (MENU_STATE *pMS, DMS_Mode mode) +{ + switch (mode) { + case DMS_Mode_navigate: + SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); + DMS_FlashActiveShip (pMS); + break; + case DMS_Mode_addEscort: + SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); + SetFlashRect (SFR_MENU_ANY); + break; + case DMS_Mode_editCrew: + SetMenuSounds (MENU_SOUND_UP | MENU_SOUND_DOWN, + MENU_SOUND_SELECT | MENU_SOUND_CANCEL); + if (HINIBBLE (pMS->CurState)) + { + // Enter crew editing mode for the flagship. + DMS_FlashFlagShipCrewCount (); + } + else + { + // Enter crew editing mode for an escort ship. + DMS_FlashEscortShipCrewCount (pMS->CurState); + } + break; + case DMS_Mode_exit: + SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); + SetFlashRect (SFR_MENU_3DO); + break; + } +} + +#define MODIFY_CREW_FLAG (1 << 8) +#ifdef WANT_SHIP_SPINS +// Helper function for DoModifyShips(), called when the player presses the +// special button. +// It works both when the cursor is over an escort ship, while not editing +// the crew, and when a new ship is added. +// hStarShip is the ship in the slot under the cursor (or 0 if no such ship). +static BOOLEAN +DMS_SpinShip (MENU_STATE *pMS, HSHIPFRAG hStarShip) +{ + HFLEETINFO hSpinShip = 0; + CONTEXT OldContext; + RECT OldClipRect; + + // No spinning the flagship. + if (HINIBBLE (pMS->CurState) != 0) + return FALSE; + + // We must either be hovering over a used ship slot, or adding a new + // ship to the fleet. + if ((hStarShip == 0) == !(pMS->delta_item & MODIFY_CREW_FLAG)) + return FALSE; + + if (!hStarShip) + { + // Selecting a ship to build. + hSpinShip = GetAvailableRaceFromIndex (LOBYTE (pMS->delta_item)); + if (!hSpinShip) + return FALSE; + } + else + { + // Hovering over an escort ship. + SHIP_FRAGMENT *FragPtr = LockShipFrag ( + &GLOBAL (built_ship_q), hStarShip); + hSpinShip = GetStarShipFromIndex ( + &GLOBAL (avail_race_q), FragPtr->race_id); + UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip); + } + + SetFlashRect (NULL); + + OldContext = SetContext (ScreenContext); + GetContextClipRect (&OldClipRect); + + SpinStarShip (pMS, hSpinShip); + + SetContextClipRect (&OldClipRect); + SetContext (OldContext); + + return TRUE; +} +#endif /* WANT_SHIP_SPINS */ + +// Helper function for DoModifyShips(), called when the player presses the +// up button when modifying the crew of the flagship. +// Buy crew for the flagship. +// Returns the change in crew (1 on success, 0 on failure). +static SIZE +DMS_HireFlagShipCrew (void) +{ + RECT r; + + if (GetCPodCapacity (&r.corner) <= GetCrewCount ()) + { + // At capacity. + return 0; + } + + if (GLOBAL_SIS (ResUnits) < (DWORD)GLOBAL (CrewCost)) + { + // Not enough RUs. + return 0; + } + + // Draw a crew member. + DrawPoint (&r.corner); + + // Update the crew counter and RU. Note that the crew counter is + // flashing. + PreUpdateFlashRect (); + DeltaSISGauges (1, 0, -GLOBAL (CrewCost)); + PostUpdateFlashRect (); + + return 1; +} + +// Helper function for DoModifyShips(), called when the player presses the +// down button when modifying the crew of the flagship. +// Dismiss crew from the flagship. +// Returns the change in crew (-1 on success, 0 on failure). +static SIZE +DMS_DismissFlagShipCrew (void) +{ + SIZE crew_bought; + RECT r; + + if (GetCrewCount () == 0) + { + // No crew to dismiss. + return 0; + } + + crew_bought = (SIZE)MAKE_WORD ( + GET_GAME_STATE (CREW_PURCHASED0), + GET_GAME_STATE (CREW_PURCHASED1)); + + // Update the crew counter and RU. Note that the crew counter is + // flashing. + PreUpdateFlashRect (); + DeltaSISGauges (-1, 0, GLOBAL (CrewCost) - + (crew_bought == CREW_EXPENSE_THRESHOLD ? 2 : 0)); + PostUpdateFlashRect (); + + // Remove the pixel representing the crew member. + GetCPodCapacity (&r.corner); + SetContextForeGroundColor (BLACK_COLOR); + DrawPoint (&r.corner); + + return -1; +} + +// Helper function for DoModifyShips(), called when the player presses the +// up button when modifying the crew of an escort ship. +// Buy crew for an escort ship +// Returns the change in crew (1 on success, 0 on failure). +static SIZE +DMS_HireEscortShipCrew (SHIP_FRAGMENT *StarShipPtr) +{ + COUNT templateMaxCrew; + RECT r; + + { + // XXX Split this off into a separate function? + HFLEETINFO hTemplate = GetStarShipFromIndex (&GLOBAL (avail_race_q), + StarShipPtr->race_id); + FLEET_INFO *TemplatePtr = + LockFleetInfo (&GLOBAL (avail_race_q), hTemplate); + templateMaxCrew = TemplatePtr->crew_level; + UnlockFleetInfo (&GLOBAL (avail_race_q), hTemplate); + } + + if (GLOBAL_SIS (ResUnits) < (DWORD)GLOBAL (CrewCost)) + { + // Not enough money to hire a crew member. + return 0; + } + + if (StarShipPtr->crew_level >= StarShipPtr->max_crew) + { + // This ship cannot handle more crew. + return 0; + } + + if (StarShipPtr->crew_level >= templateMaxCrew) + { + // A ship of this type cannot handle more crew. + return 0; + } + + if (StarShipPtr->crew_level > 0) + { + DeltaSISGauges (0, 0, -GLOBAL (CrewCost)); + } + else + { + // Buy a ship. + DeltaSISGauges (0, 0, -(COUNT)ShipCost[StarShipPtr->race_id]); + } + + ++StarShipPtr->crew_level; + + PreUpdateFlashRect (); + DMS_GetEscortShipRect (&r, StarShipPtr->index); + ShowShipCrew (StarShipPtr, &r); + PostUpdateFlashRect (); + + return 1; +} + +// Helper function for DoModifyShips(), called when the player presses the +// down button when modifying the crew of an escort ship. +// Dismiss crew from an escort ship +// Returns the change in crew (-1 on success, 0 on failure). +static SIZE +DMS_DismissEscortShipCrew (SHIP_FRAGMENT *StarShipPtr) +{ + SIZE crew_delta = 0; + RECT r; + + if (StarShipPtr->crew_level > 0) + { + if (StarShipPtr->crew_level > 1) + { + // The ship was not at 'scrap'. + // Give one crew member worth of RU. + SIZE crew_bought = (SIZE)MAKE_WORD ( + GET_GAME_STATE (CREW_PURCHASED0), + GET_GAME_STATE (CREW_PURCHASED1)); + + DeltaSISGauges (0, 0, GLOBAL (CrewCost) + - (crew_bought == CREW_EXPENSE_THRESHOLD ? 2 : 0)); + } + else + { + // With the last crew member, the ship will be scrapped. + // Give RU for the ship. + DeltaSISGauges (0, 0, (COUNT)ShipCost[StarShipPtr->race_id]); + } + crew_delta = -1; + --StarShipPtr->crew_level; + } + else + { // no crew to dismiss + PlayMenuSound (MENU_SOUND_FAILURE); + } + + PreUpdateFlashRect (); + DMS_GetEscortShipRect (&r, StarShipPtr->index); + ShowShipCrew (StarShipPtr, &r); + PostUpdateFlashRect (); + + return crew_delta; +} + +// Helper function for DoModifyShips(), called when the player presses the +// up or down button when modifying the crew of the flagship or of an escort +// ship. +// 'hStarShip' is the currently escort ship, or 0 if no ship is +// selected. +// 'dy' is -1 if the 'up' button was pressed, or '1' if the down button was +// pressed. +static void +DMS_ModifyCrew (MENU_STATE *pMS, HSHIPFRAG hStarShip, SBYTE dy) +{ + SIZE crew_delta = 0; + SHIP_FRAGMENT *StarShipPtr = NULL; + + if (hStarShip) + StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip); + + if (hStarShip == 0) + { + // Add/Dismiss crew for the flagship. + if (dy < 0) + { + // Add crew for the flagship. + crew_delta = DMS_HireFlagShipCrew (); + } + else + { + // Dismiss crew from the flagship. + crew_delta = DMS_DismissFlagShipCrew (); + } + + if (crew_delta != 0) + DMS_FlashFlagShipCrewCount (); + } + else + { + // Add/Dismiss crew for an escort ship. + if (dy < 0) + { + // Add crew for an escort ship. + crew_delta = DMS_HireEscortShipCrew (StarShipPtr); + } + else + { + // Dismiss crew from an escort ship. + crew_delta = DMS_DismissEscortShipCrew (StarShipPtr); + } + + if (crew_delta != 0) + DMS_FlashEscortShipCrewCount (StarShipPtr->index); + } + + if (crew_delta == 0) + PlayMenuSound (MENU_SOUND_FAILURE); + + if (hStarShip) + { + UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip); + + // Clear out the bought ship index so that flash rects work + // correctly. + pMS->delta_item &= MODIFY_CREW_FLAG; + } + + CrewTransaction (crew_delta); +} + +// Helper function for DoModifyShips(), called when the player presses the +// select button when the cursor is over an empty escort ship slot. +// Try to add the currently selected ship as an escort ship. +static void +DMS_TryAddEscortShip (MENU_STATE *pMS) +{ + HFLEETINFO shipInfo = GetAvailableRaceFromIndex ( + LOBYTE (pMS->delta_item)); + COUNT Index = GetIndexFromStarShip (&GLOBAL (avail_race_q), shipInfo); + + if (GLOBAL_SIS (ResUnits) >= (DWORD)ShipCost[Index] + && CloneShipFragment (Index, &GLOBAL (built_ship_q), 1)) + { + ShowCombatShip (pMS, pMS->CurState, NULL); + // Reset flash rectangle + DrawMenuStateStrings (PM_CREW, SHIPYARD_CREW); + + DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA, + -((int)ShipCost[Index])); + DMS_SetMode (pMS, DMS_Mode_editCrew); + } + else + { + // not enough RUs to build, or cloning the ship failed. + PlayMenuSound (MENU_SOUND_FAILURE); + } +} + +// Helper function for DoModifyShips(), called when the player is in the +// mode to add a new escort ship to the fleet (after pressing select on an +// empty slot). +// LOBYTE (pMS->delta_item) is used to store the currently highlighted ship. +// Returns FALSE if the flash rectangle needs to be updated. +static void +DMS_AddEscortShip (MENU_STATE *pMS, BOOLEAN special, BOOLEAN select, + BOOLEAN cancel, SBYTE dx, SBYTE dy) +{ + assert (pMS->delta_item & MODIFY_CREW_FLAG); + +#ifdef WANT_SHIP_SPINS + if (special) + { + HSHIPFRAG hStarShip = GetEscortByStarShipIndex (pMS->delta_item); + if (DMS_SpinShip (pMS, hStarShip)) + DMS_SetMode (pMS, DMS_Mode_addEscort); + return; + } +#else + (void) special; // Satisfying compiler. +#endif /* WANT_SHIP_SPINS */ + + if (cancel) + { + // Cancel selecting an escort ship. + pMS->delta_item &= ~MODIFY_CREW_FLAG; + SetFlashRect (NULL); + DrawMenuStateStrings (PM_CREW, SHIPYARD_CREW); + DMS_SetMode (pMS, DMS_Mode_navigate); + } + else if (select) + { + // Selected a ship to be inserted in an empty escort + // ship slot. + DMS_TryAddEscortShip (pMS); + } + else if (dx || dy) + { + // Motion key pressed while selecting a ship to be + // inserted in an empty escort ship slot. + COUNT availableCount = GetAvailableRaceCount (); + BYTE currentShip = LOBYTE (pMS->delta_item); + if (dx < 0 || dy < 0) + { + if (currentShip-- == 0) + currentShip = availableCount - 1; + } + else if (dx > 0 || dy > 0) + { + if (++currentShip == availableCount) + currentShip = 0; + } + + if (currentShip != LOBYTE (pMS->delta_item)) + { + PreUpdateFlashRect (); + DrawRaceStrings (pMS, currentShip); + PostUpdateFlashRect (); + pMS->delta_item = currentShip | MODIFY_CREW_FLAG; + } + } +} + +// Helper function for DoModifyShips(), called when the player presses +// 'select' or 'cancel' after selling all the crew. +static void +DMS_ScrapEscortShip (MENU_STATE *pMS, HSHIPFRAG hStarShip) +{ + SHIP_FRAGMENT *StarShipPtr = + LockShipFrag (&GLOBAL (built_ship_q), hStarShip); + BYTE slotNr; + + SetFlashRect (NULL); + ShowCombatShip (pMS, pMS->CurState, StarShipPtr); + + slotNr = StarShipPtr->index; + UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip); + + RemoveQueue (&GLOBAL (built_ship_q), hStarShip); + FreeShipFrag (&GLOBAL (built_ship_q), hStarShip); + // refresh SIS display + DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA, UNDEFINED_DELTA); + + SetContext (SpaceContext); + DMS_SetMode (pMS, DMS_Mode_navigate); +} + +// Helper function for DoModifyShips(), called when the player presses +// one of the motion keys when not in crew modification mode. +static BYTE +DMS_MoveCursor (BYTE curState, SBYTE dx, SBYTE dy) +{ + BYTE row = LONIBBLE(curState) / HANGAR_SHIPS_ROW; + BYTE col = LONIBBLE(curState) % HANGAR_SHIPS_ROW; + BOOLEAN isFlagShipSelected = (HINIBBLE(curState) != 0); + + if (dy) + { + // Vertical motion. + + // We consider the flagship an extra row (on the bottom), + // to ease operations. + if (isFlagShipSelected) + row = HANGAR_ROWS; + + // Move up/down, wrapping around: + row = (row + (HANGAR_ROWS + 1) + dy) % (HANGAR_ROWS + 1); + + // If we moved to the 'extra row', this means the flag ship. + isFlagShipSelected = (row == HANGAR_ROWS); + if (isFlagShipSelected) + row = 0; + } + else if (dx) + { + // Horizontal motion. + if (!isFlagShipSelected) + { + // Moving horizontally through the escort ship slots, + // wrapping around if necessary. + col = (col + HANGAR_SHIPS_ROW + dx) % HANGAR_SHIPS_ROW; + } + } + + return MAKE_BYTE(row * HANGAR_SHIPS_ROW + col, + isFlagShipSelected ? 0xf : 0); +} + +// Helper function for DoModifyShips(), called every time DoModifyShip() is +// called when we are in crew editing mode. +static void +DMS_EditCrewMode (MENU_STATE *pMS, HSHIPFRAG hStarShip, + BOOLEAN select, BOOLEAN cancel, SBYTE dy) +{ + if (select || cancel) + { + // Leave crew editing mode. + if (hStarShip != 0) + { + // Exiting crew editing mode for an escort ship. + SHIP_FRAGMENT *StarShipPtr = LockShipFrag ( + &GLOBAL (built_ship_q), hStarShip); + COUNT crew_level = StarShipPtr->crew_level; + UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip); + + if (crew_level == 0) + { + // Scrapping the escort ship before exiting crew edit + // mode. + DMS_ScrapEscortShip (pMS, hStarShip); + } + } + + pMS->delta_item &= ~MODIFY_CREW_FLAG; + DMS_SetMode (pMS, DMS_Mode_navigate); + } + else if (dy) + { + // Hire or dismiss crew for the flagship or an escort + // ship. + DMS_ModifyCrew (pMS, hStarShip, dy); + } +} + +// Helper function for DoModifyShips(), called every time DoModifyShip() is +// called when we are in the mode where you can select a ship or empty slot. +static void +DMS_NavigateShipSlots (MENU_STATE *pMS, BOOLEAN special, BOOLEAN select, + BOOLEAN cancel, SBYTE dx, SBYTE dy) +{ + HSHIPFRAG hStarShip = GetEscortByStarShipIndex (pMS->CurState); + + if (dx || dy) + { + // Moving through the ship slots. + BYTE NewState = DMS_MoveCursor (pMS->CurState, dx, dy); + if (NewState != pMS->CurState) + { + pMS->CurState = NewState; + DMS_FlashActiveShip(pMS); + } + } + +#ifndef WANT_SHIP_SPINS + (void) special; // Satisfying compiler. +#else + if (special) + { + if (DMS_SpinShip (pMS, hStarShip)) + DMS_SetMode (pMS, DMS_Mode_navigate); + } + else +#endif /* WANT_SHIP_SPINS */ + if (select) + { + if (hStarShip == 0 && HINIBBLE (pMS->CurState) == 0) + { + // Select button was pressed over an empty escort + // ship slot. Switch to 'add escort ship' mode. + pMS->delta_item = MODIFY_CREW_FLAG; + DrawRaceStrings (pMS, 0); + DMS_SetMode (pMS, DMS_Mode_addEscort); + } + else + { + // Select button was pressed over an escort ship or + // the flagship. Entering crew editing mode + pMS->delta_item |= MODIFY_CREW_FLAG; + DMS_SetMode (pMS, DMS_Mode_editCrew); + } + } + else if (cancel) + { + // Leave escort ship editor. + pMS->InputFunc = DoShipyard; + pMS->CurState = SHIPYARD_CREW; + DrawMenuStateStrings (PM_CREW, pMS->CurState); + DMS_SetMode (pMS, DMS_Mode_exit); + } +} + +/* In this routine, the least significant byte of pMS->CurState is used + * to store the current selected ship index + * a special case for the row is hi-nibble == -1 (0xf), which specifies + * SIS as the selected ship + * some bitwise math is still done to scroll through ships, for it to work + * ships per row number must divide 0xf0 without remainder + */ +static BOOLEAN +DoModifyShips (MENU_STATE *pMS) +{ + if (GLOBAL (CurrentActivity) & CHECK_ABORT) + { + pMS->InputFunc = DoShipyard; + return TRUE; + } + + if (!pMS->Initialized) + { + pMS->InputFunc = DoModifyShips; + pMS->Initialized = TRUE; + pMS->CurState = MAKE_BYTE (0, 0xF); + pMS->delta_item = 0; + + SetContext (SpaceContext); + DMS_SetMode (pMS, DMS_Mode_navigate); + } + else + { + BOOLEAN special = (PulsedInputState.menu[KEY_MENU_SPECIAL] != 0); + BOOLEAN select = (PulsedInputState.menu[KEY_MENU_SELECT] != 0); + BOOLEAN cancel = (PulsedInputState.menu[KEY_MENU_CANCEL] != 0); + SBYTE dx = 0; + SBYTE dy = 0; + + if (PulsedInputState.menu[KEY_MENU_RIGHT]) + dx = 1; + if (PulsedInputState.menu[KEY_MENU_LEFT]) + dx = -1; + if (PulsedInputState.menu[KEY_MENU_UP]) + dy = -1; + if (PulsedInputState.menu[KEY_MENU_DOWN]) + dy = 1; + + + if (!(pMS->delta_item & MODIFY_CREW_FLAG)) + { + // Navigating through the ship slots. + DMS_NavigateShipSlots (pMS, special, select, cancel, dx, dy); + } + else + { + // Add an escort ship or edit the crew of a ship. + HSHIPFRAG hStarShip = GetEscortByStarShipIndex (pMS->CurState); + + if (hStarShip == 0 && HINIBBLE (pMS->CurState) == 0) + { + // Cursor is over an empty escort ship slot, while we're + // in 'add escort ship' mode. + DMS_AddEscortShip (pMS, special, select, cancel, dx, dy); + } + else + { + // Crew editing mode. + DMS_EditCrewMode (pMS, hStarShip, select, cancel, dy); + } + } + + } + + SleepThread (ONE_SECOND / 30); + + return TRUE; +} + +static void +DrawBluePrint (MENU_STATE *pMS) +{ + COUNT num_frames; + STAMP s; + FRAME ModuleFrame; + + ModuleFrame = CaptureDrawable (LoadGraphic (SISBLU_MASK_ANIM)); + + s.origin.x = 0; + s.origin.y = 0; + s.frame = DecFrameIndex (ModuleFrame); + SetContextForeGroundColor ( + BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x16), 0x01)); + DrawFilledStamp (&s); + + for (num_frames = 0; num_frames < NUM_DRIVE_SLOTS; ++num_frames) + { + DrawShipPiece (ModuleFrame, GLOBAL_SIS (DriveSlots[num_frames]), + num_frames, TRUE); + } + for (num_frames = 0; num_frames < NUM_JET_SLOTS; ++num_frames) + { + DrawShipPiece (ModuleFrame, GLOBAL_SIS (JetSlots[num_frames]), + num_frames, TRUE); + } + for (num_frames = 0; num_frames < NUM_MODULE_SLOTS; ++num_frames) + { + BYTE which_piece; + + which_piece = GLOBAL_SIS (ModuleSlots[num_frames]); + + if (!(pMS->CurState == SHIPYARD && which_piece == CREW_POD)) + DrawShipPiece (ModuleFrame, which_piece, num_frames, TRUE); + } + + SetContextForeGroundColor ( + BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)); + for (num_frames = 0; num_frames < NUM_MODULE_SLOTS; ++num_frames) + { + BYTE which_piece; + + which_piece = GLOBAL_SIS (ModuleSlots[num_frames]); + if (pMS->CurState == SHIPYARD && which_piece == CREW_POD) + DrawShipPiece (ModuleFrame, which_piece, num_frames, TRUE); + } + + { + num_frames = GLOBAL_SIS (CrewEnlisted); + GLOBAL_SIS (CrewEnlisted) = 0; + + while (num_frames--) + { + POINT pt; + + GetCPodCapacity (&pt); + DrawPoint (&pt); + + ++GLOBAL_SIS (CrewEnlisted); + } + } + { + RECT r; + + num_frames = GLOBAL_SIS (TotalElementMass); + GLOBAL_SIS (TotalElementMass) = 0; + + r.extent.width = 9; + r.extent.height = 1; + while (num_frames) + { + COUNT m; + + m = num_frames < SBAY_MASS_PER_ROW ? + num_frames : SBAY_MASS_PER_ROW; + GLOBAL_SIS (TotalElementMass) += m; + GetSBayCapacity (&r.corner); + DrawFilledRectangle (&r); + num_frames -= m; + } + } + if (GLOBAL_SIS (FuelOnBoard) > FUEL_RESERVE) + { + DWORD FuelVolume; + RECT r; + + FuelVolume = GLOBAL_SIS (FuelOnBoard) - FUEL_RESERVE; + GLOBAL_SIS (FuelOnBoard) = FUEL_RESERVE; + + r.extent.width = 3; + r.extent.height = 1; + while (FuelVolume) + { + COUNT m; + + GetFTankCapacity (&r.corner); + DrawPoint (&r.corner); + r.corner.x += r.extent.width + 1; + DrawPoint (&r.corner); + r.corner.x -= r.extent.width; + SetContextForeGroundColor ( + SetContextBackGroundColor (BLACK_COLOR)); + DrawFilledRectangle (&r); + m = FuelVolume < FUEL_VOLUME_PER_ROW ? + (COUNT)FuelVolume : FUEL_VOLUME_PER_ROW; + GLOBAL_SIS (FuelOnBoard) += m; + FuelVolume -= m; + } + } + + DestroyDrawable (ReleaseDrawable (ModuleFrame)); +} + +BOOLEAN +DoShipyard (MENU_STATE *pMS) +{ + BOOLEAN select, cancel; + if (GLOBAL (CurrentActivity) & CHECK_ABORT) + goto ExitShipyard; + + select = PulsedInputState.menu[KEY_MENU_SELECT]; + cancel = PulsedInputState.menu[KEY_MENU_CANCEL]; + + SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); + if (!pMS->Initialized) + { + pMS->InputFunc = DoShipyard; + + { + STAMP s; + RECT r, old_r; + + pMS->ModuleFrame = CaptureDrawable ( + LoadGraphic (SHIPYARD_PMAP_ANIM)); + + pMS->CurString = CaptureColorMap ( + LoadColorMap (HANGAR_COLOR_TAB)); + + pMS->hMusic = LoadMusic (SHIPYARD_MUSIC); + + SetTransitionSource (NULL); + BatchGraphics (); + DrawSISFrame (); + DrawSISMessage (GAME_STRING (STARBASE_STRING_BASE + 3)); + DrawSISTitle (GAME_STRING (STARBASE_STRING_BASE)); + SetContext (SpaceContext); + DrawBluePrint (pMS); + + pMS->CurState = SHIPYARD_CREW; + DrawMenuStateStrings (PM_CREW, pMS->CurState); + + SetContext (SpaceContext); + s.origin.x = 0; + s.origin.y = 0; + s.frame = SetAbsFrameIndex (pMS->ModuleFrame, 0); +#ifdef USE_3DO_HANGAR + DrawStamp (&s); +#else // PC hangar + // the PC ship dock needs to overwrite the border + // expand the clipping rect by 1 pixel + GetContextClipRect (&old_r); + r = old_r; + r.corner.x--; + r.extent.width += 2; + r.extent.height += 1; + SetContextClipRect (&r); + DrawStamp (&s); + SetContextClipRect (&old_r); + animatePowerLines (pMS); +#endif // USE_3DO_HANGAR + + SetContextFont (TinyFont); + + ScreenTransition (3, NULL); + UnbatchGraphics (); + + PlayMusic (pMS->hMusic, TRUE, 1); + + ShowCombatShip (pMS, (COUNT)~0, NULL); + + SetInputCallback (on_input_frame); + + SetFlashRect (SFR_MENU_3DO); + } + + pMS->Initialized = TRUE; + } + else if (cancel || (select && pMS->CurState == SHIPYARD_EXIT)) + { +ExitShipyard: + SetInputCallback (NULL); + + DestroyDrawable (ReleaseDrawable (pMS->ModuleFrame)); + pMS->ModuleFrame = 0; + DestroyColorMap (ReleaseColorMap (pMS->CurString)); + pMS->CurString = 0; + + return FALSE; + } + else if (select) + { + if (pMS->CurState != SHIPYARD_SAVELOAD) + { + pMS->Initialized = FALSE; + DoModifyShips (pMS); + } + else + { + // Clearing FlashRect is not necessary + if (!GameOptions ()) + goto ExitShipyard; + DrawMenuStateStrings (PM_CREW, pMS->CurState); + SetFlashRect (SFR_MENU_3DO); + } + } + else + { + DoMenuChooser (pMS, PM_CREW); + } + + return TRUE; +} + |