diff options
Diffstat (limited to 'src/uqm/credits.c')
-rw-r--r-- | src/uqm/credits.c | 839 |
1 files changed, 839 insertions, 0 deletions
diff --git a/src/uqm/credits.c b/src/uqm/credits.c new file mode 100644 index 0000000..1889484 --- /dev/null +++ b/src/uqm/credits.c @@ -0,0 +1,839 @@ +//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 "credits.h" + +#include "controls.h" +#include "colors.h" +#include "options.h" +#include "oscill.h" +#include "comm.h" +#include "resinst.h" +#include "nameref.h" +#include "settings.h" +#include "sounds.h" +#include "setup.h" +#include "libs/graphics/drawable.h" +#include <math.h> + +// Rates in pixel lines per second +#define CREDITS_BASE_RATE 9 +#define CREDITS_MAX_RATE 130 +// Maximum frame rate +#define CREDITS_FRAME_RATE 36 + +#define CREDITS_TIMEOUT (ONE_SECOND * 5) + +#define TRANS_COLOR BRIGHT_BLUE_COLOR + +// Positive or negative scroll rate in pixel lines per second +static int CreditsRate; + +static BOOLEAN OutTakesRunning; +static BOOLEAN CreditsRunning; +static STRING CreditsTab; +static FRAME CreditsBack; + +// Context used for drawing to the screen +static CONTEXT DrawContext; +// Context used for pre-rendering a credits frame +static CONTEXT LocalContext; +// Pre-rendered frame, possibly with a cutout +static FRAME CreditsFrame; +// Size of the credits "window" (normally screen size) +static EXTENT CreditsExtent; + +typedef struct +{ + FRAME frame; + int strIndex; +} CreditTextFrame; + +#define MAX_CREDIT_FRAMES 32 +// Circular text frame buffer for scrolling +// Text frames are generated as needed, and when a text frame scrolls +// of the screen, it is destroyed +static CreditTextFrame textFrames[MAX_CREDIT_FRAMES]; +// Index of first active frame in the circular buffer (the first frame +// is the one on top) +static int firstFrame; +// Index of last active frame in the circular buffer + 1 +static int lastFrame; +// Total height of all active frames in the circular buffer +static int totalHeight; +// Current vertical offset into the first text frame +static int curFrameOfs; + +typedef struct +{ + int size; + RESOURCE res; + FONT font; +} FONT_SIZE_DEF; + +static FONT_SIZE_DEF CreditsFont[] = +{ + { 13, PT13AA_FONT, 0 }, + { 17, PT17AA_FONT, 0 }, + { 45, PT45AA_FONT, 0 }, + { 0, 0, 0 }, +}; + + +static FRAME +Credits_MakeTransFrame (int w, int h, Color TransColor) +{ + FRAME OldFrame; + FRAME f; + + f = CaptureDrawable (CreateDrawable (WANT_PIXMAP, w, h, 1)); + SetFrameTransparentColor (f, TransColor); + + OldFrame = SetContextFGFrame (f); + SetContextBackGroundColor (TransColor); + ClearDrawable (); + SetContextFGFrame (OldFrame); + + return f; +} + +static int +ParseTextLines (TEXT *Lines, int MaxLines, char *Buffer) +{ + int i; + const char* pEnd = Buffer + strlen (Buffer); + + for (i = 0; i < MaxLines && Buffer < pEnd; ++i, ++Lines) + { + char* pTerm = strchr (Buffer, '\n'); + if (!pTerm) + pTerm = Buffer + strlen (Buffer); + *pTerm = '\0'; /* terminate string */ + Lines->pStr = Buffer; + Lines->CharCount = ~0; + Buffer = pTerm + 1; + } + return i; +} + +#define MAX_TEXT_LINES 50 +#define MAX_TEXT_COLS 5 + +static FRAME +Credits_RenderTextFrame (CONTEXT TempContext, int *istr, int dir, + Color BackColor, Color ForeColor) +{ + FRAME f; + CONTEXT OldContext; + FRAME OldFrame; + TEXT TextLines[MAX_TEXT_LINES]; + char *pStr = NULL; + int size; + char salign[32]; + char *scol; + int scaned; + int i, rows, cnt; + char buf[2048]; + FONT_SIZE_DEF *fdef; + SIZE leading; + TEXT t; + RECT r; + typedef struct + { + TEXT_ALIGN align; + COORD basex; + } col_format_t; + col_format_t colfmt[MAX_TEXT_COLS]; + + if (*istr < 0 || *istr >= GetStringTableCount (CreditsTab)) + { // check if next one is within range + int next_s = *istr + dir; + + if (next_s < 0 || next_s >= GetStringTableCount (CreditsTab)) + return 0; + + *istr = next_s; + } + + // skip empty lines + while (*istr >= 0 && *istr < GetStringTableCount (CreditsTab)) + { + pStr = GetStringAddress ( + SetAbsStringTableIndex (CreditsTab, *istr)); + *istr += dir; + if (pStr && *pStr != '\0') + break; + } + + if (!pStr || *pStr == '\0') + return 0; + + if (2 != sscanf (pStr, "%d %31s %n", &size, salign, &scaned) + || size <= 0) + return 0; + pStr += scaned; + + utf8StringCopy (buf, sizeof (buf), pStr); + rows = ParseTextLines (TextLines, MAX_TEXT_LINES, buf); + if (rows == 0) + return 0; + // parse text columns + for (i = 0, cnt = rows; i < rows; ++i) + { + char *nextcol; + int icol; + + // we abuse the baseline here, but only a tiny bit + // every line starts at col 0 + TextLines[i].baseline.x = 0; + TextLines[i].baseline.y = i + 1; + + for (icol = 1, nextcol = strchr (TextLines[i].pStr, '\t'); + icol < MAX_TEXT_COLS && nextcol; + ++icol, nextcol = strchr (nextcol, '\t')) + { + *nextcol = '\0'; + ++nextcol; + + if (cnt < MAX_TEXT_LINES) + { + TextLines[cnt].pStr = nextcol; + TextLines[cnt].CharCount = ~0; + TextLines[cnt].baseline.x = icol; + TextLines[cnt].baseline.y = i + 1; + ++cnt; + } + } + } + + // init alignments + for (i = 0; i < MAX_TEXT_COLS; ++i) + { + colfmt[i].align = ALIGN_LEFT; + colfmt[i].basex = CreditsExtent.width / 64; + } + + // find the right font + for (fdef = CreditsFont; fdef->size && size > fdef->size; ++fdef) + ; + if (!fdef->size) + return 0; + + t.align = ALIGN_LEFT; + t.baseline.x = 100; // any value will do + t.baseline.y = 100; // any value will do + t.pStr = " "; + t.CharCount = 1; + + OldContext = SetContext (TempContext); + + // get font dimensions + SetContextFont (fdef->font); + GetContextFontLeading (&leading); + // get left/right margin + TextRect (&t, &r, NULL); + + // parse text column alignment + for (i = 0, scol = strtok (salign, ","); + scol && i < MAX_TEXT_COLS; + ++i, scol = strtok (NULL, ",")) + { + char c; + int x; + int n; + + // default + colfmt[i].align = ALIGN_LEFT; + colfmt[i].basex = r.extent.width; + + n = sscanf (scol, "%c/%d", &c, &x); + if (n < 1) + { // DOES NOT COMPUTE! :) + continue; + } + + switch (c) + { + case 'L': + colfmt[i].align = ALIGN_LEFT; + if (n >= 2) + colfmt[i].basex = x; + break; + case 'C': + colfmt[i].align = ALIGN_CENTER; + if (n >= 2) + colfmt[i].basex = x; + else + colfmt[i].basex = CreditsExtent.width / 2; + break; + case 'R': + colfmt[i].align = ALIGN_RIGHT; + if (n >= 2) + colfmt[i].basex = x; + else + colfmt[i].basex = CreditsExtent.width - r.extent.width; + break; + } + } + + for (i = 0; i < cnt; ++i) + { + // baseline contains coords in row/col quantities + col_format_t *fmt = colfmt + TextLines[i].baseline.x; + + TextLines[i].align = fmt->align; + TextLines[i].baseline.x = fmt->basex; + TextLines[i].baseline.y *= leading; + } + + f = Credits_MakeTransFrame (CreditsExtent.width, leading * rows + (leading >> 1), + BackColor); + OldFrame = SetContextFGFrame (f); + // draw text + SetContextForeGroundColor (ForeColor); + for (i = 0; i < cnt; ++i) + font_DrawText (TextLines + i); + + SetContextFGFrame (OldFrame); + SetContext (OldContext); + + return f; +} + +static inline int +frameIndex (int index) +{ + // Make sure index is positive before % + return (index + MAX_CREDIT_FRAMES) % MAX_CREDIT_FRAMES; +} + +static void +RenderCreditsScreen (CONTEXT targetContext) +{ + CONTEXT oldContext; + STAMP s; + int i; + + oldContext = SetContext (targetContext); + // draw background + s.origin.x = 0; + s.origin.y = 0; + s.frame = CreditsBack; + DrawStamp (&s); + + // draw text frames + s.origin.y = -curFrameOfs; + for (i = firstFrame; i != lastFrame; i = frameIndex (i + 1)) + { + RECT fr; + + s.frame = textFrames[i].frame; + DrawStamp (&s); + GetFrameRect (s.frame, &fr); + s.origin.y += fr.extent.height; + } + + if (OutTakesRunning) + { // Cut out the Outtakes rect + SetContextForeGroundColor (TRANS_COLOR); + DrawFilledRectangle (&CommWndRect); + } + + SetContext (oldContext); +} + +static void +InitCredits (void) +{ + RECT ctxRect; + CONTEXT oldContext; + FRAME targetFrame; + + memset (textFrames, 0, sizeof textFrames); + + LocalContext = CreateContext ("Credits.LocalContext"); + DrawContext = CreateContext ("Credits.DrawContext"); + + targetFrame = GetContextFGFrame (); + GetContextClipRect (&ctxRect); + CreditsExtent = ctxRect.extent; + + // prep our local context + oldContext = SetContext (LocalContext); + // Local screen copy. We draw everything to this frame, then cut + // the Outtakes rect out and draw this frame to the screen. + CreditsFrame = Credits_MakeTransFrame (CreditsExtent.width, + CreditsExtent.height, TRANS_COLOR); + SetContextFGFrame (CreditsFrame); + + // The first credits frame is fake, the height of the screen, + // so that the credits can roll in from the bottom + textFrames[0].frame = Credits_MakeTransFrame (1, CreditsExtent.height, + TRANS_COLOR); + textFrames[0].strIndex = -1; + firstFrame = 0; + lastFrame = firstFrame + 1; + + totalHeight = GetFrameHeight (textFrames[0].frame); + curFrameOfs = 0; + + // We use an own screen draw context to avoid collisions + SetContext (DrawContext); + SetContextFGFrame (targetFrame); + + SetContext (oldContext); + + // Prepare the first screen frame + RenderCreditsScreen (LocalContext); + + CreditsRate = CREDITS_BASE_RATE; + CreditsRunning = TRUE; +} + +static void +freeCreditTextFrame (CreditTextFrame *tf) +{ + DestroyDrawable (ReleaseDrawable (tf->frame)); + tf->frame = NULL; +} + +static void +UninitCredits (void) +{ + DestroyContext (DrawContext); + DrawContext = NULL; + DestroyContext (LocalContext); + LocalContext = NULL; + + // free remaining frames + DestroyDrawable (ReleaseDrawable (CreditsFrame)); + CreditsFrame = NULL; + for ( ; firstFrame != lastFrame; firstFrame = frameIndex (firstFrame + 1)) + freeCreditTextFrame (&textFrames[firstFrame]); +} + +static int +calcDeficitHeight (void) +{ + int i; + int maxPos; + + maxPos = -curFrameOfs; + for (i = firstFrame; i != lastFrame; i = frameIndex (i + 1)) + { + RECT fr; + + GetFrameRect (textFrames[i].frame, &fr); + maxPos += fr.extent.height; + } + + return CreditsExtent.height - maxPos; +} + +static void +processCreditsFrame (void) +{ + static TimeCount NextTime; + TimeCount Now = GetTimeCounter (); + + if (Now >= NextTime) + { + RECT fr; + CONTEXT OldContext; + int rate, direction, dirstep; + int deficitHeight; + STAMP s; + + rate = abs (CreditsRate); + if (rate != 0) + { + // scroll direction; forward or backward + direction = CreditsRate / rate; + // step in pixels + dirstep = (rate + CREDITS_FRAME_RATE - 1) / CREDITS_FRAME_RATE; + rate = ONE_SECOND * dirstep / rate; + // step is also directional + dirstep *= direction; + } + else + { // scroll stopped + direction = 0; + dirstep = 0; + // one second interframe + rate = ONE_SECOND; + } + + NextTime = GetTimeCounter () + rate; + + // draw the credits + // comm animations play with contexts so we need to make + // sure the context is not desynced + s.origin.x = 0; + s.origin.y = 0; + s.frame = CreditsFrame; + + OldContext = SetContext (DrawContext); + DrawStamp (&s); + SetContext (OldContext); + FlushGraphics (); + + // prepare next screen frame + deficitHeight = calcDeficitHeight (); + curFrameOfs += dirstep; + // cap scroll + if (curFrameOfs < -(CreditsExtent.height / 20)) + { // at the begining, deceleration + if (CreditsRate < 0) + CreditsRate -= CreditsRate / 10 - 1; + } + else if (deficitHeight > CreditsExtent.height / 25) + { // frame deficit -- credits almost over, deceleration + if (CreditsRate > 0) + CreditsRate -= CreditsRate / 10 + 1; + + CreditsRunning = (CreditsRate != 0); + } + else if (!CreditsRunning) + { // resumed + CreditsRunning = TRUE; + } + + if (firstFrame != lastFrame) + { // clean up frames that scrolled off the screen + if (direction > 0) + { // forward scroll + GetFrameRect (textFrames[firstFrame].frame, &fr); + if (curFrameOfs >= fr.extent.height) + { // past this frame already + totalHeight -= fr.extent.height; + freeCreditTextFrame (&textFrames[firstFrame]); + // next frame + firstFrame = frameIndex (firstFrame + 1); + curFrameOfs -= fr.extent.height; + } + } + else if (direction < 0) + { // backward scroll + int index = frameIndex (lastFrame - 1); + int framePos; + + GetFrameRect (textFrames[index].frame, &fr); + framePos = totalHeight - curFrameOfs - fr.extent.height; + if (framePos >= CreditsExtent.height) + { // past this frame already + lastFrame = index; + totalHeight -= fr.extent.height; + freeCreditTextFrame (&textFrames[lastFrame]); + } + } + } + + // render new text frames if needed + if (direction > 0) + { // forward scroll + int next_s = 0; + + // get next string + if (firstFrame != lastFrame) + next_s = textFrames[frameIndex (lastFrame - 1)].strIndex + 1; + + while (totalHeight - curFrameOfs < CreditsExtent.height + && next_s < GetStringTableCount (CreditsTab)) + { + CreditTextFrame *tf = &textFrames[lastFrame]; + + tf->frame = Credits_RenderTextFrame (LocalContext, &next_s, + direction, BLACK_COLOR, CREDITS_TEXT_COLOR); + tf->strIndex = next_s - 1; + if (tf->frame) + { + GetFrameRect (tf->frame, &fr); + totalHeight += fr.extent.height; + + lastFrame = frameIndex (lastFrame + 1); + } + } + } + else if (direction < 0) + { // backward scroll + int next_s = GetStringTableCount (CreditsTab) - 1; + + // get next string + if (firstFrame != lastFrame) + next_s = textFrames[firstFrame].strIndex - 1; + + while (curFrameOfs < 0 && next_s >= 0) + { + int index = frameIndex (firstFrame - 1); + CreditTextFrame *tf = &textFrames[index]; + + tf->frame = Credits_RenderTextFrame (LocalContext, &next_s, + direction, BLACK_COLOR, CREDITS_TEXT_COLOR); + tf->strIndex = next_s + 1; + if (tf->frame) + { + GetFrameRect (tf->frame, &fr); + totalHeight += fr.extent.height; + + firstFrame = index; + curFrameOfs += fr.extent.height; + } + } + } + + // draw next screen frame + RenderCreditsScreen (LocalContext); + } +} + +static BOOLEAN +LoadCredits (void) +{ + FONT_SIZE_DEF *fdef; + + CreditsTab = CaptureStringTable (LoadStringTable (CREDITS_STRTAB)); + if (!CreditsTab) + return FALSE; + CreditsBack = CaptureDrawable (LoadGraphic (CREDITS_BACK_ANIM)); + // load fonts + for (fdef = CreditsFont; fdef->size; ++fdef) + fdef->font = LoadFont (fdef->res); + + return TRUE; +} + +static void +FreeCredits (void) +{ + FONT_SIZE_DEF *fdef; + + DestroyStringTable (ReleaseStringTable (CreditsTab)); + CreditsTab = NULL; + + DestroyDrawable (ReleaseDrawable (CreditsBack)); + CreditsBack = NULL; + + // free fonts + for (fdef = CreditsFont; fdef->size; ++fdef) + { + DestroyFont (fdef->font); + fdef->font = NULL; + } +} + +static void +OutTakes (void) +{ +#define NUM_OUTTAKES 15 + static CONVERSATION outtake_list[NUM_OUTTAKES] = + { + ZOQFOTPIK_CONVERSATION, + TALKING_PET_CONVERSATION, + ORZ_CONVERSATION, + UTWIG_CONVERSATION, + THRADD_CONVERSATION, + SUPOX_CONVERSATION, + SYREEN_CONVERSATION, + SHOFIXTI_CONVERSATION, + PKUNK_CONVERSATION, + YEHAT_CONVERSATION, + DRUUGE_CONVERSATION, + URQUAN_CONVERSATION, + VUX_CONVERSATION, + BLACKURQ_CONVERSATION, + ARILOU_CONVERSATION + }; + + BOOLEAN oldsubtitles = optSubtitles; + int i = 0; + + // Outtakes have no voice tracks, so the subtitles are always on + optSubtitles = TRUE; + sliderDisabled = TRUE; + oscillDisabled = TRUE; + + for (i = 0; (i < NUM_OUTTAKES) && + !(GLOBAL (CurrentActivity) & CHECK_ABORT); i++) + { + SetCommIntroMode (CIM_CROSSFADE_WINDOW, 0); + InitCommunication (outtake_list[i]); + } + + optSubtitles = oldsubtitles; + sliderDisabled = FALSE; + oscillDisabled = FALSE; +} + +typedef struct +{ + // standard state required by DoInput + BOOLEAN (*InputFunc) (void *pInputState); + + BOOLEAN AllowCancel; + BOOLEAN AllowSpeedChange; + BOOLEAN CloseWhenDone; + DWORD CloseTimeOut; + +} CREDITS_INPUT_STATE; + +static BOOLEAN +DoCreditsInput (void *pIS) +{ + CREDITS_INPUT_STATE *pCIS = (CREDITS_INPUT_STATE *) pIS; + + if (CreditsRunning) + { // cancel timeout if resumed (or just running) + pCIS->CloseTimeOut = 0; + } + + if ((GLOBAL (CurrentActivity) & CHECK_ABORT) + || (pCIS->AllowCancel && + (PulsedInputState.menu[KEY_MENU_SELECT] || + PulsedInputState.menu[KEY_MENU_CANCEL])) + ) + { // aborted + return FALSE; + } + + if (pCIS->AllowSpeedChange + && (PulsedInputState.menu[KEY_MENU_UP] + || PulsedInputState.menu[KEY_MENU_DOWN])) + { // speed adjustment + int newrate = CreditsRate; + int step = abs (CreditsRate) / 5 + 1; + + if (PulsedInputState.menu[KEY_MENU_DOWN]) + newrate += step; + else if (PulsedInputState.menu[KEY_MENU_UP]) + newrate -= step; + if (newrate < -CREDITS_MAX_RATE) + newrate = -CREDITS_MAX_RATE; + else if (newrate > CREDITS_MAX_RATE) + newrate = CREDITS_MAX_RATE; + + CreditsRate = newrate; + } + + if (!CreditsRunning) + { // always allow cancelling once credits run through + pCIS->AllowCancel = TRUE; + } + + if (!CreditsRunning && pCIS->CloseWhenDone) + { // auto-close controlled by timeout + if (pCIS->CloseTimeOut == 0) + { // set timeout + pCIS->CloseTimeOut = GetTimeCounter () + CREDITS_TIMEOUT; + } + else if (GetTimeCounter () > pCIS->CloseTimeOut) + { // all done! + return FALSE; + } + } + + if (!CreditsRunning + && (PulsedInputState.menu[KEY_MENU_SELECT] + || PulsedInputState.menu[KEY_MENU_CANCEL])) + { // credits finished and exit requested + return FALSE; + } + + SleepThread (ONE_SECOND / CREDITS_FRAME_RATE); + + return TRUE; +} + +static void +on_input_frame (void) +{ + processCreditsFrame (); +} + +void +Credits (BOOLEAN WithOuttakes) +{ + MUSIC_REF hMusic; + CREDITS_INPUT_STATE cis; + RECT screenRect; + STAMP s; + + hMusic = LoadMusic (CREDITS_MUSIC); + + SetContext (ScreenContext); + SetContextClipRect (NULL); + GetContextClipRect (&screenRect); + SetContextBackGroundColor (BLACK_COLOR); + ClearDrawable (); + + if (!LoadCredits ()) + return; + + // Fade in the background + s.origin.x = 0; + s.origin.y = 0; + s.frame = CreditsBack; + DrawStamp (&s); + FadeScreen (FadeAllToColor, ONE_SECOND / 2); + + // set the position of outtakes comm + CommWndRect.corner.x = (screenRect.extent.width - CommWndRect.extent.width) + / 2; + CommWndRect.corner.y = 5; + + InitCredits (); + SetInputCallback (on_input_frame); + + if (WithOuttakes) + { + OutTakesRunning = TRUE; + OutTakes (); + OutTakesRunning = FALSE; + } + + if (!(GLOBAL (CurrentActivity) & CHECK_ABORT)) + { + if (hMusic) + PlayMusic (hMusic, TRUE, 1); + + // nothing to do now but wait until credits + // are done or canceled by user + cis.InputFunc = DoCreditsInput; + cis.AllowCancel = !WithOuttakes; + cis.CloseWhenDone = !WithOuttakes; + cis.AllowSpeedChange = TRUE; + SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE); + DoInput (&cis, TRUE); + } + + SetInputCallback (NULL); + FadeMusic (0, ONE_SECOND / 2); + UninitCredits (); + + SetContext (ScreenContext); + SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND / 2)); + FlushColorXForms (); + + if (hMusic) + { + StopMusic (); + DestroyMusic (hMusic); + } + FadeMusic (NORMAL_VOLUME, 0); + + FreeCredits (); +} |