Permalink
Fetching contributors…
Cannot retrieve contributors at this time
531 lines (442 sloc) 15 KB
/*
* See Licensing and Copyright notice in naev.h
*/
/**
* @file intro.c
*
* @brief Handles the introduction sequence.
*
* @todo Allow handling of images and other fancy things once we get them.
*/
#include "intro.h"
#include "naev.h"
#include "nstring.h"
#include "SDL.h"
#include "log.h"
#include "ndata.h"
#include "font.h"
#include "music.h"
#include "nstd.h"
#include "toolkit.h"
#include "conf.h"
#define INTRO_SPEED 30. /**< Speed of text in characters / second. */
/**
* @brief Scroll Buffer: For a linked list of render text.
*/
typedef struct scroll_buf_t_ {
const char *text; /* Text to render. */
struct scroll_buf_t_ *next; /* Next line in the linked list. */
} scroll_buf_t;
/**
* @brief Intro Image: to be displayed to the side of the scrolling.
*/
typedef struct intro_img_t_ {
glTexture *tex; /* Image. */
double x, y; /* Position. */
glColour c; /* Alpha channel for fading. */
double fade_rate; /* Positive for fade-in, negative for fade-out. */
} intro_img_t;
/*
* Introduction text lines.
*/
static char **intro_lines = NULL; /**< Introduction text lines. */
static int intro_nlines = 0; /**< Number of introduction text lines. */
static int intro_length = 0; /**< Length of the text. */
static glFont intro_font; /**< Introduction font. */
static int has_side_gfx = 0; /* Determines how wide to make the text. */
/*
* Prototypes.
*/
static int intro_load( const char *text );
static void intro_cleanup (void);
static scroll_buf_t *arrange_scroll_buf( scroll_buf_t *arr, int n );
static void intro_event_handler( int *stop, double *offset, double *vel );
static void initialize_image( intro_img_t *img );
static void intro_fade_image_in( intro_img_t *side, intro_img_t *transition,
const char *img_file );
static int intro_draw_text( scroll_buf_t *sb_list, double offset,
double line_height );
/**
* @brief Loads the intro stuff.
*/
static int intro_load( const char *text )
{
uint32_t intro_size;
char *intro_buf;
char img_src[128]; /* path to image to be displayed alongside text. */
int length;
int i, p, n;
int mem;
has_side_gfx = 0;
/* Load text. */
intro_buf = ndata_read( text, &intro_size );
intro_length = intro_size; /* Length approximation. */
/* Create intro font. */
gl_fontInit( &intro_font, "dat/mono.ttf", conf.font_size_intro );
/* Load lines. */
p = 0;
n = 0;
mem = 0;
while ((uint32_t)p < intro_size) {
/* Copy the line. */
if (n+1 > mem) {
mem += 128;
intro_lines = realloc( intro_lines, sizeof(char*) * mem );
}
if ( intro_buf[p] == '[' /* Don't do sscanf for every line! */
&& sscanf( &intro_buf[p], "[fadein %s", img_src ) == 1 ) {
/* an image to appear next to text. */
/* Get the length. */
for (i = 0; intro_buf[p + i] != '\n' && intro_buf[p + i] != '\0'; ++i);
length = strlen( img_src );
intro_lines[n] = malloc( length + 2 );
intro_lines[n][0] = 'i';
strncpy( &intro_lines[n][1], img_src, length );
intro_lines[n][length] = '\0';
/* Mark that there are graphics. */
has_side_gfx = 1;
} else if ( intro_buf[p] == '[' /* Don't do strncmp for every line! */
&& strncmp( &intro_buf[p], "[fadeout]", 9 ) == 0 ) {
/* fade out the image next to the text. */
for (i = 0; intro_buf[p + i] != '\n' && intro_buf[p + i] != '\0'; ++i);
intro_lines[n] = malloc( 2 );
intro_lines[n][0] = 'o';
intro_lines[n][1] = '\0'; /* not strictly necessary, but safe. */
} else { /* plain old text. */
/* Get the length. */
i = gl_printWidthForText( &intro_font, &intro_buf[p], SCREEN_W - 500. );
intro_lines[n] = malloc( i + 2 );
intro_lines[n][0] = 't';
strncpy( &intro_lines[n][1], &intro_buf[p], i );
intro_lines[n][i+1] = '\0';
}
p += i + 1; /* Move pointer. */
n++; /* New line. */
}
/* Clean up. */
free(intro_buf);
intro_nlines = n;
return 0;
}
/**
* @brief Cleans up the intro stuff.
*/
static void intro_cleanup (void)
{
int i;
/* Free memory. */
for (i=0; i<intro_nlines; i++)
free(intro_lines[i]);
free(intro_lines);
gl_freeFont(&intro_font);
/* Set defaults. */
intro_lines = NULL;
intro_nlines = 0;
}
/**
* @brief Convert an array of scroll_buf_t into a circularly linked list.
*
* @brief arr Input array.
* @brief n Number of elements.
* @return A pointer into the circular list.
*/
static scroll_buf_t *arrange_scroll_buf( scroll_buf_t *arr, int n )
{
scroll_buf_t *sb_list = &arr[n - 1];
int i;
for (i = 0; i < n; ++i) {
arr[i].text = NULL;
arr[i].next = sb_list;
sb_list = &arr[i];
}
return sb_list;
}
/**
* @brief Initialize an intro_img_t to default values.
*
* @brief img Image to initialize.
*/
static void initialize_image( intro_img_t *img )
{
img->tex = NULL;
img->x = 100.;
img->c.r = 1.0;
img->c.g = 1.0;
img->c.b = 1.0;
img->c.a = 1.0;
img->fade_rate = 0.0;
}
/**
* @brief Fade an image in.
*
* @brief side Present image being displayed.
* @brief transition Image in transition or on deck.
* @brief img_file Path to the PNG on disk.
*/
static void intro_fade_image_in( intro_img_t *side, intro_img_t *transition,
const char *img_file )
{
if (NULL == side->tex) {
/* Simple fade-in. */
side->tex = gl_newImage( img_file, 0 );
side->y = (double)SCREEN_H / 2.0 - (side->tex->h / 2.0);
side->c.a = 0.0;
side->fade_rate = 0.1;
}
else {
/*
* Transition or on-deck. The difference is whether one image is
* replacing the other (transition), or whether the first must fade out
* completely before the second comes in (on-deck).
*
* We can determine which is the case by seeing whether [fadeout] has been
* called. I.e., is side->fade_rate < 0?
*/
if (NULL != transition->tex) {
/* Scrolling is happening faster than fading... */
WARN( "Intro scrolling too fast!" );
gl_freeTexture( transition->tex );
}
transition->tex = gl_newImage( img_file, 0 );
transition->y =
(double)SCREEN_H / 2.0 - (transition->tex->h / 2.0);
transition->c.a = 0.0;
if (side->fade_rate < 0.0)
transition->fade_rate = 0.0; /* put an image on deck. */
else {
/* transition. */
transition->fade_rate = 0.1;
side->fade_rate = -0.1; /* begin fading out. */
side->c.a = 0.99;
}
}
}
/**
* @brief Handle user events (mouse clicks, key presses, etc.).
*
* @brief stop Whether to stop the intro.
* @brief vel How fast the text should scroll.
*/
static void intro_event_handler( int *stop, double *offset, double *vel )
{
SDL_Event event; /* user key-press, mouse-push, etc. */
while (SDL_PollEvent(&event)) {
#if SDL_VERSION_ATLEAST(2,0,0)
if (event.type == SDL_WINDOWEVENT &&
event.window.event == SDL_WINDOWEVENT_RESIZED) {
naev_resize( event.window.data1, event.window.data2 );
continue;
}
#endif /* SDL_VERSION_ATLEAST(2,0,0) */
if (event.type != SDL_KEYDOWN)
continue;
/* Escape skips directly. */
if (event.key.keysym.sym == SDLK_ESCAPE) {
*stop = 1;
break;
}
/* Slow down the text. */
else if (event.key.keysym.sym == SDLK_UP) {
*vel -= 8.0;
if (*vel < 0.0)
*vel = 0.0;
}
/* Speed up the text. */
else if (event.key.keysym.sym == SDLK_DOWN) {
*vel += 8.0;
if (*vel > 100.0)
*vel = 100.0;
}
/* Jump down. */
else if ((event.key.keysym.sym == SDLK_SPACE) ||
(event.key.keysym.sym == SDLK_RETURN))
*offset += 100;
/* Jump up. */
else if (event.key.keysym.sym == SDLK_BACKSPACE)
*offset -= 100;
/* User is clearly flailing on keyboard. */
else
*vel = 16.;
}
}
/**
* @brief Draw intro text onto the screen.
*
* @brief sb_list List of text lines.
* @brief offset For smooth scrolling.
* @brief line_height V-space of the font (plus leading).
* @return Whether to stop. 1 if no text was rendered, 0 otherwise.
*/
static int intro_draw_text( scroll_buf_t *sb_list, double offset,
double line_height)
{
double x, y; /* render position. */
scroll_buf_t *list_iter; /* iterator through sb_list. */
register int stop = 1;
if (has_side_gfx)
x = 400.0; /* leave some space for graphics if they exist. */
else
x = 100.0;
list_iter = sb_list;
y = SCREEN_H + offset - line_height;
do {
if (NULL != list_iter->text) {
stop = 0;
gl_print( &intro_font, x, y, &cConsole, list_iter->text );
}
y -= line_height;
list_iter = list_iter->next;
} while (list_iter != sb_list);
return stop;
}
/**
* @brief Displays the introduction sequence.
*
* @brief text Path of text file to use.
* @brief mus Type of music to use (run through music.lua).
* @return 0 on success.
*/
int intro_display( const char *text, const char *mus )
{
double offset; /* distance from bottom of the top line. */
double line_height; /* # pixels per line. */
int lines_per_screen; /* max appearing lines on the screen. */
scroll_buf_t *sb_arr; /* array of lines to render. */
scroll_buf_t *sb_list; /* list " " " " */
double vel = 16.; /* velocity: speed of text. */
int stop = 0; /* stop the intro. */
unsigned int tcur, tlast; /* timers. */
double delta; /* time diff from last render to this one. */
int line_index = 0; /* index into the big list of intro lines. */
intro_img_t side_image; /* image to go along with the text. */
intro_img_t transition; /* image for transitioning. */
/* Load the introduction. */
if (intro_load(text) < 0)
return -1;
/* Change music to intro music. */
if (mus != NULL)
music_choose(mus);
/* We need to clear key repeat to avoid infinite loops. */
toolkit_clearKey();
/* Enable keyrepeat just for the intro. */
#if !SDL_VERSION_ATLEAST(2,0,0)
SDL_EnableKeyRepeat( conf.repeat_delay, conf.repeat_freq );
#endif /* !SDL_VERSION_ATLEAST(2,0,0) */
/* Do a few calculations to figure out how many lines can be present on the
screen at any given time. */
line_height = (double)intro_font.h * 1.3;
lines_per_screen = (int)(SCREEN_H / line_height + 1.5); /* round up + 1 */
sb_arr = (scroll_buf_t*)malloc( sizeof(scroll_buf_t) * lines_per_screen );
/* Force the first line to be loaded immediately. */
offset = line_height;
/* Create a cycle of lines. */
sb_list = arrange_scroll_buf( sb_arr, lines_per_screen );
/* Unset the side image. */
initialize_image( &side_image );
initialize_image( &transition );
tlast = SDL_GetTicks();
while (!stop) {
tcur = SDL_GetTicks();
delta = (double)(tcur - tlast) / 1000.;
tlast = tcur;
/* Increment position. */
offset += vel * delta;
while (! (offset < line_height)) {
/* One line has scrolled off, and another one on. */
if (line_index < intro_nlines) {
switch (intro_lines[line_index][0]) {
case 't': /* plain ol' text. */
sb_list->text = &intro_lines[line_index][1];
offset -= line_height;
sb_list = sb_list->next;
break;
case 'i': /* fade in image. */
intro_fade_image_in( &side_image, &transition,
&intro_lines[line_index][1] );
break;
case 'o': /* fade out image. */
if (NULL == side_image.tex) {
WARN("Tried to fade out without an image." );
break;
}
side_image.fade_rate = -0.1;
side_image.c.a = 0.99;
break;
default: /* unknown. */
break;
}
++line_index;
} else {
sb_list->text = NULL;
offset -= line_height;
sb_list = sb_list->next;
}
} /* while (offset > line_height) */
/* Fade the side image. */
if (side_image.tex != NULL && side_image.c.a < 1.0) {
side_image.c.a += delta * vel * side_image.fade_rate;
if (transition.tex != NULL && transition.fade_rate > 0.0)
transition.c.a += delta * vel * transition.fade_rate;
if (side_image.c.a > 1.0) {
/* Faded in... */
side_image.c.a = 1.0;
side_image.fade_rate = 0.0;
}
else if (side_image.c.a < 0.0) {
/* Faded out... */
gl_freeTexture( side_image.tex );
if (transition.tex != NULL) {
side_image.tex = transition.tex;
side_image.c.a = transition.c.a;
side_image.y = transition.y;
side_image.fade_rate = 0.1;
transition.tex = NULL;
transition.c.a = 1.0;
}
else {
side_image.c.a = 1.0;
side_image.tex = NULL;
side_image.fade_rate = 0.0;
}
}
}
/* Clear stuff. */
glClear(GL_COLOR_BUFFER_BIT);
/* Only thing we actually care about updating is music. */
music_update( 0. );
/* Draw text. */
stop = intro_draw_text( sb_list, offset, line_height );
if (NULL != side_image.tex)
/* Draw the image next to the text. */
gl_blitScale( side_image.tex, side_image.x, side_image.y,
side_image.tex->w, side_image.tex->h, &side_image.c );
if (NULL != transition.tex && transition.c.a > 0.0)
/* Draw the image in transition. */
gl_blitScale( transition.tex, transition.x, transition.y,
transition.tex->w, transition.tex->h, &transition.c );
/* Display stuff. */
#if SDL_VERSION_ATLEAST(2,0,0)
SDL_GL_SwapWindow( gl_screen.window );
#else /* SDL_VERSION_ATLEAST(2,0,0) */
SDL_GL_SwapBuffers();
#endif /* SDL_VERSION_ATLEAST(2,0,0) */
SDL_Delay(10); /* No need to burn CPU. */
/* Handle user events. */
intro_event_handler( &stop, &offset, &vel );
} /* while (!stop) */
/* free malloc'd memory. */
free( sb_arr );
if (NULL != side_image.tex)
gl_freeTexture( side_image.tex );
if (NULL != transition.tex)
gl_freeTexture( transition.tex );
/* Disable intro's key repeat. */
#if !SDL_VERSION_ATLEAST(2,0,0)
SDL_EnableKeyRepeat( 0, 0 );
#endif /* !SDL_VERSION_ATLEAST(2,0,0) */
/* Stop music, normal music will start shortly after. */
music_stop();
/* Clean up after the introduction. */
intro_cleanup();
return 0;
}