Implement night for world map + automatic night mode
[tennix.git] / graphics.c
blob69a0c3da074ca1990d2128c569619787cd0954af
2 /**
4 * Tennix! SDL Port
5 * Copyright (C) 2003, 2007, 2008, 2009 Thomas Perl <thp@thpinfo.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 * MA 02110-1301, USA.
22 **/
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <math.h>
27 #include <unistd.h>
28 #include <assert.h>
29 #include <ctype.h>
30 #include <string.h>
32 #include "tennix.h"
33 #include "graphics.h"
34 #include "archive.h"
35 #include "sound.h"
37 static Image* images;
39 static SDL_Surface *buffer;
40 static SDL_Surface *background;
42 static SDL_Rect *rect_update_cache;
43 static SDL_Rect *rect_update_old;
44 static int rect_update_cache_current;
45 static int rect_update_old_count;
47 Uint32 fading_start = 0;
49 static const char* graphics[] = {
50 "court.png",
51 "shadow.png",
52 "player-racket.png",
53 "ball.png",
54 "smallish-font.png",
55 "dkc2.png",
56 "referee.png",
57 "ctt_hard.png",
58 "ctt_clay.png",
59 "ctt_grass.png",
60 "sidebar.png",
61 "tennixlogo.png",
62 "btnplay.png",
63 "btnresume.png",
64 "btnquit.png",
65 "stadium.png",
66 "fog.png",
67 "fog2.png",
68 "night.png",
69 "talk.png",
70 "cursor.png",
71 "worldmap.png",
72 "loc_margaret_court_arena.png",
73 "loc_stade_roland_garros.png",
74 "loc_court_no_1.png",
75 "loc_arthur_ashe_stadium.png",
76 #ifdef NONFREE_LOCATIONS
77 "loc_training_camp.png"
78 #endif
81 void init_graphics() {
82 int i;
83 char *d;
84 SDL_Surface* data;
85 SDL_Surface* tmp;
86 TennixArchive *tnxar;
88 rect_update_cache = (SDL_Rect*)calloc(RECT_UPDATE_CACHE, sizeof(SDL_Rect));
89 rect_update_old = (SDL_Rect*)calloc(RECT_UPDATE_CACHE, sizeof(SDL_Rect));
90 rect_update_cache_current = 0;
91 rect_update_old_count = 0;
93 images = (Image*)calloc( GR_COUNT, sizeof( Image));
95 buffer = SDL_CreateRGBSurface(SDL_SWSURFACE, WIDTH, HEIGHT,
96 screen->format->BitsPerPixel,
97 screen->format->Rmask,
98 screen->format->Gmask,
99 screen->format->Bmask,
100 screen->format->Amask);
102 background = SDL_CreateRGBSurface(SDL_SWSURFACE, WIDTH, HEIGHT,
103 screen->format->BitsPerPixel,
104 screen->format->Rmask,
105 screen->format->Gmask,
106 screen->format->Bmask,
107 screen->format->Amask);
109 if( buffer == NULL) {
110 fprintf( stderr, "Cannot create buffer surface: %s\n", SDL_GetError());
113 tnxar = tnxar_open(ARCHIVE_FILE);
114 if (tnxar == NULL) {
115 /* not found in cwd - try installed... */
116 tnxar = tnxar_open(ARCHIVE_FILE_INSTALLED);
117 assert(tnxar != NULL);
120 draw_button(40, (HEIGHT-40)/2, WIDTH-80, 40, 100, 100, 100, 1);
121 store_screen();
122 updatescr();
124 for( i=0; i<GR_COUNT; i++) {
125 if (tnxar_set_current_filename(tnxar, graphics[i]) != 0) {
126 d = tnxar_read_current(tnxar);
127 tmp = IMG_Load_RW(SDL_RWFromMem(d, tnxar_size_current(tnxar)), 0);
128 free(d);
129 } else {
130 fprintf(stderr, "Cannot find file: %s\n", graphics[i]);
131 exit(EXIT_FAILURE);
133 if( !tmp) {
134 fprintf( stderr, "Error: %s\n", SDL_GetError());
135 continue;
138 if( GRAPHICS_IS_FONT(i)) {
139 /* Convert to RGB w/ colorkey=black for opacity support */
140 SDL_SetColorKey( tmp, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB( tmp->format, 0, 0, 0));
141 data = SDL_ConvertSurface( tmp, screen->format, SDL_SRCCOLORKEY | SDL_RLEACCEL);
142 } else {
143 /* Convert to RGBA for alpha channel from PNG */
144 data = SDL_DisplayFormatAlpha( tmp);
146 SDL_FreeSurface( tmp);
148 if( !data) {
149 fprintf( stderr, "Error: %s\n", SDL_GetError());
150 continue;
152 images[i].data = data;
154 draw_button(40, (HEIGHT-40)/2, (WIDTH-80)*i/(GR_COUNT+SOUND_MAX), 40, 100, 250, 100, 0);
155 rectangle(40+BUTTON_BORDER*2, (HEIGHT-40)/2+20+BUTTON_BORDER, (WIDTH-80)*i/(GR_COUNT+SOUND_MAX)-BUTTON_BORDER*2, 10, 170, 250, 170);
156 updatescr();
158 tnxar_close(tnxar);
161 void uninit_graphics() {
162 int i;
164 for( i=0; i<GR_COUNT; i++) {
165 SDL_FreeSurface( images[i].data);
168 if( buffer != NULL) {
169 SDL_FreeSurface( buffer);
172 if (background != NULL) {
173 SDL_FreeSurface(background);
176 free(rect_update_cache);
177 free(rect_update_old);
178 free(images);
181 int get_image_width( image_id id) {
182 return images[id].data->w;
185 int get_image_height( image_id id) {
186 return images[id].data->h;
189 int get_sprite_width( image_id id, int items) {
190 return images[id].data->w / items;
193 void show_sprite( image_id id, int pos, int items, int x_offset, int y_offset, int opacity) {
194 SDL_Surface *bitmap;
195 SDL_Rect src, dst;
197 bitmap = images[id].data;
199 if( !bitmap) return;
201 SDL_SetAlpha( bitmap, SDL_SRCALPHA | SDL_RLEACCEL, opacity);
203 dst.w = src.w = bitmap->w/items;
204 dst.h = src.h = bitmap->h;
205 src.x = src.w*pos;
206 src.y = 0;
207 dst.x = x_offset;
208 dst.y = y_offset;
210 SDL_BlitSurface( bitmap, &src, screen, &dst);
211 update_rect2(dst);
214 void fill_image_offset( image_id id, int x, int y, int w, int h, int offset_x, int offset_y) {
215 SDL_Surface *bitmap;
216 SDL_Rect src, dst;
217 int dx = 0, dy = 0, cx, cy;
219 if( id >= GR_COUNT) return;
221 bitmap = images[id].data;
223 /* Make negative offsets positive */
224 if (offset_x < 0) {
225 offset_x = (offset_x%bitmap->w)+bitmap->w;
227 if (offset_y < 0) {
228 offset_y = (offset_y%bitmap->h)+bitmap->h;
231 src.y = offset_y % bitmap->h;
232 while( dy < h) {
233 src.h = dst.h = cy = (h-dy > bitmap->h-src.y)?(bitmap->h-src.y):(h-dy);
234 dst.y = y+dy;
236 dx = 0;
237 src.x = offset_x % bitmap->w;
238 while( dx < w) {
239 src.w = dst.w = cx = (w-dx > bitmap->w-src.x)?(bitmap->w-src.x):(w-dx);
240 dst.x = x+dx;
242 SDL_BlitSurface( bitmap, &src, screen, &dst);
243 update_rect2(dst);
245 dx += cx;
246 src.x = 0;
249 dy += cy;
250 src.y = 0;
254 void line_horiz( int y, Uint8 r, Uint8 g, Uint8 b) {
255 rectangle(0, y, screen->w, 1, r, g, b);
258 void line_vert( int x, Uint8 r, Uint8 g, Uint8 b) {
259 rectangle(x, 0, 1, screen->h, r, g, b);
262 void rectangle( int x, int y, int w, int h, Uint8 r, Uint8 g, Uint8 b) {
263 Uint32 color = SDL_MapRGB( screen->format, r, g, b);
264 SDL_Rect rect;
266 rect.x = x;
267 rect.y = y;
268 rect.w = w;
269 rect.h = h;
271 SDL_FillRect( screen, &rect, color);
272 update_rect(x, y, w, h);
275 void rectangle_alpha(int x, int y, int w, int h, Uint8 r, Uint8 g, Uint8 b, Uint8 opacity)
277 Uint32 color = SDL_MapRGB(screen->format, r, g, b);
278 SDL_Surface *buf;
279 SDL_Rect rect, rect2;
281 buf = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h,
282 screen->format->BitsPerPixel,
283 screen->format->Rmask,
284 screen->format->Gmask,
285 screen->format->Bmask,
286 screen->format->Amask);
288 rect.x = rect.y = 0;
289 rect2.x = x;
290 rect2.y = y;
291 rect.w = rect2.w = w;
292 rect.h = rect2.h = h;
294 SDL_FillRect(buf, &rect, color);
295 SDL_SetAlpha(buf, SDL_RLEACCEL | SDL_SRCALPHA, opacity);
296 SDL_BlitSurface(buf, &rect, screen, &rect2);
297 update_rect(x, y, w, h);
299 SDL_FreeSurface(buf);
302 void draw_button( int x, int y, int w, int h, Uint8 r, Uint8 g, Uint8 b, char pressed) {
303 float diff = (pressed?1.0-BUTTON_HIGHLIGHT:1.0+BUTTON_HIGHLIGHT);
304 rectangle(x, y, w, h, MIN(r*diff, 255), MIN(g*diff, 255), MIN(b*diff, 255));
305 rectangle(x+BUTTON_BORDER, y+BUTTON_BORDER, w-BUTTON_BORDER, h-BUTTON_BORDER, MIN(r/diff, 255), MIN(g/diff, 255), MIN(b/diff, 255));
306 rectangle(x+BUTTON_BORDER, y+BUTTON_BORDER, w-2*BUTTON_BORDER, h-2*BUTTON_BORDER, r, g, b);
309 void draw_button_text(const char* s, int x, int y, int w, int h, Uint8 r, Uint8 g, Uint8 b, char pressed) {
310 int font_x, font_y;
311 pressed = pressed?1:0;
312 draw_button( x, y, w, h, r, g, b, pressed);
313 font_x = x + w/2 - font_get_string_width( GR_SMALLISH_FONT, s)/2 + pressed*BUTTON_BORDER;
314 font_y = y + h/2 - get_image_height( GR_SMALLISH_FONT)/2 + pressed*BUTTON_BORDER;
315 font_draw_string( GR_SMALLISH_FONT, s, font_x, font_y, 0, 0);
318 void clear_screen()
320 SDL_Rect rect;
322 rect.x = rect.y = 0;
323 rect.w = WIDTH;
324 rect.h = HEIGHT;
326 SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, 0, 0, 0));
329 void store_screen()
331 SDL_BlitSurface(screen, NULL, background, NULL);
332 rect_update_old[0].x = rect_update_old[0].y = 0;
333 rect_update_old[0].w = WIDTH;
334 rect_update_old[0].h = HEIGHT;
335 rect_update_old_count = 1;
336 rect_update_cache_current = 0;
339 void reset_screen()
341 int i;
342 SDL_Rect* tmp;
343 SDL_BlitSurface(background, NULL, screen, NULL);
345 for (i=0; i<rect_update_cache_current; i++) {
346 SDL_BlitSurface(background, &rect_update_cache[i], screen, &rect_update_cache[i]);
349 /* Save rects I've just updated for redraw later */
350 tmp = rect_update_cache;
351 rect_update_cache = rect_update_old;
352 rect_update_old = tmp;
353 rect_update_old_count = rect_update_cache_current;
354 rect_update_cache_current = 0;
357 void update_rect(Sint32 x, Sint32 y, Sint32 w, Sint32 h)
359 SDL_Rect* u = NULL;
360 static int inside = 0;
361 if (inside) return;
362 inside = 1;
364 #ifdef DRAW_UPDATE_RECTANGLE
365 rectangle(x, y, w, h, 50+rand()%200, 50+rand()%200 ,50+rand()%200);
366 #endif
368 if ((x >= WIDTH) | (y >= HEIGHT) | (x+w <= 0) | (y+h <= 0) | (!w) | (!h)) {
369 inside = 0;
370 return;
373 if (rect_update_cache_current == RECT_UPDATE_CACHE) {
374 fprintf(stderr, "Overflow\n");
375 rect_update_cache_current = 0;
378 u = &(rect_update_cache[rect_update_cache_current]);
380 if (x < 0/* && x+w > 0*/) {
381 w += x;
382 x = 0;
384 if (y < 0/* && y+h > 0*/) {
385 h += y;
386 y = 0;
389 if (x+w >= WIDTH) {
390 w -= (x+w-WIDTH+1);
393 if (y+h >= HEIGHT) {
394 h -= (y+h-HEIGHT+1);
397 if (w==0 || h==0) {
398 inside = 0;
399 return;
402 u->x = x;
403 u->y = y;
404 u->w = w;
405 u->h = h;
407 rect_update_cache_current++;
409 inside = 0;
412 void updatescr()
414 Uint32 ticks = SDL_GetTicks();
415 SDL_Rect *r_current = NULL, *end_current = NULL;
416 SDL_Rect *r_old = NULL;
418 static int fading_last_time = 0;
419 unsigned char fading_now = ticks < fading_start+FADE_DURATION;
421 if (fading_now) {
422 SDL_SetAlpha(buffer, SDL_SRCALPHA | SDL_RLEACCEL, 255-255*(ticks-fading_start)/FADE_DURATION);
423 SDL_BlitSurface(buffer, NULL, screen, NULL);
424 SDL_UpdateRect(screen, 0, 0, 0, 0);
425 } else if (fading_last_time && !fading_now) {
426 SDL_UpdateRect(screen, 0, 0, 0, 0);
427 } else {
428 if (rect_update_old_count == rect_update_cache_current) {
429 /* Merge rects into one single rect list */
430 r_old = rect_update_old;
431 r_current = rect_update_cache;
432 end_current = rect_update_cache + rect_update_cache_current;
433 while (r_current != end_current) {
434 r_old->w = MAX(r_current->x+r_current->w, r_old->x+r_old->w);
435 r_old->h = MAX(r_current->y+r_current->h, r_old->y+r_old->h);
436 r_old->x = MIN(r_current->x, r_old->x);
437 r_old->y = MIN(r_current->y, r_old->y);
438 r_old->w -= r_old->x;
439 r_old->h -= r_old->y;
441 r_current++;
442 r_old++;
444 SDL_UpdateRects(screen, rect_update_old_count, rect_update_old);
445 } else {
446 SDL_UpdateRects(screen, rect_update_old_count, rect_update_old);
447 SDL_UpdateRects(screen, rect_update_cache_current, rect_update_cache);
451 reset_screen();
452 fading_last_time = fading_now;
455 void start_fade() {
456 SDL_BlitSurface( screen, NULL, buffer, NULL);
457 fading_start = SDL_GetTicks();
460 int font_get_metrics(image_id id, unsigned char ch, int* xp, int* wp) {
461 /* Caching of x and width values for faster calculation */
462 static int xcache[0xFF][GRAPHICS_FONT_COUNT],
463 wcache[0xFF][GRAPHICS_FONT_COUNT];
464 static unsigned char cacheflag[0xFF][GRAPHICS_FONT_COUNT];
466 SDL_Surface *bitmap;
467 int pos, x = -1, w = 0;
468 int search_pos = 0, search_x = 0;
469 unsigned char font_index = id-GRAPHICS_FONT_FIRST;
470 Uint8 red, green, blue;
472 if(!GRAPHICS_IS_FONT(id)) return 0;
474 if (cacheflag[ch][font_index]) {
475 /* saved! */
476 if (xp != NULL) *xp = xcache[ch][font_index];
477 if (wp != NULL) *wp = wcache[ch][font_index];
478 return wcache[ch][font_index];
481 pos = toupper( ch) - ' '; /* ' ' = first character in font bitmap */
483 bitmap = images[id].data;
485 SDL_LockSurface( bitmap);
486 while( search_x < bitmap->w) {
487 GET_PIXEL_RGB( bitmap, search_x, 0, &red, &green, &blue);
489 /* Increase pos counter if we have a "marker" pixel (255,0,255) */
490 if( red > 250 && green < 10 && blue > 250) {
491 search_pos++;
492 if( search_pos == pos) {
493 x = search_x;
494 } else if( search_pos == pos + 1) {
495 w = search_x - x;
496 break;
500 search_x++;
502 SDL_UnlockSurface( bitmap);
504 if( wp != NULL) (*wp) = w;
505 if( xp != NULL) (*xp) = x;
507 cacheflag[ch][font_index]++;
508 xcache[ch][font_index] = x;
509 wcache[ch][font_index] = w;
511 return w;
514 int font_draw_char( image_id id, char ch, int x_offset, int y_offset) {
515 SDL_Surface *bitmap;
516 SDL_Rect src, dst;
517 int x = -1, w = 0;
519 font_get_metrics( id, ch, &x, &w);
520 if( x == -1) return w;
522 bitmap = images[id].data;
524 dst.w = src.w = w;
525 dst.h = src.h = bitmap->h - 1;
526 src.x = x;
527 src.y = 1;
528 dst.x = x_offset;
529 dst.y = y_offset;
531 SDL_BlitSurface( bitmap, &src, screen, &dst);
533 return src.w;
536 void font_draw_string_alpha( image_id id, const char* s, int x_offset, int y_offset, int start, int animation, int opacity) {
537 int y = y_offset;
538 int x = x_offset;
539 int additional_x = 0, additional_y = 0;
540 float xw = 0.0, xw_diff = 0.0;
541 unsigned int i;
542 SDL_Surface *bitmap;
544 if( id > GR_COUNT) return;
546 bitmap = images[id].data;
547 SDL_SetAlpha( bitmap, SDL_SRCALPHA | SDL_RLEACCEL, opacity);
549 if( animation & ANIMATION_BUNGEE) {
550 xw = (25.0*sinf( start/10.0));
551 x -= xw / 2;
554 if( animation & ANIMATION_PENDULUM) {
555 x -= (int)(20.0*sinf( start/20.0));
556 additional_x += 21;
559 for( i=0; i<strlen(s); i++) {
560 if( animation & ANIMATION_WAVE) {
561 y = y_offset + (int)(3.0*sinf( start/10.0 + x/30.0));
563 x += font_draw_char( id, s[i], x, y);
564 if( animation & ANIMATION_BUNGEE) {
565 xw_diff += xw/strlen(s);
566 if( xw_diff > 1.0 || xw_diff < -1.0) {
567 x += (int)(xw_diff);
568 if( xw_diff > 1.0) {
569 xw_diff -= 1.0;
570 } else {
571 xw_diff += 1.0;
576 if (animation & ANIMATION_WAVE) {
577 additional_y += 4;
579 if (animation & ANIMATION_BUNGEE) {
580 additional_x += 26;
582 update_rect(x_offset-additional_x, y_offset-additional_y, x-x_offset+additional_x*2, get_image_height(id)+additional_y*2);
585 int font_get_string_width( image_id id, const char* s) {
586 int w = 0;
587 unsigned int i;
589 for( i=0; i<strlen(s); i++) {
590 w += font_get_metrics( id, s[i], NULL, NULL);
593 return w;
596 void draw_line_faded( int x1, int y1, int x2, int y2, int r, int g, int b, int r2, int g2, int b2) {
597 float step, dx, dy, x = x1, y = y1;
598 int i;
599 char fade = (r!=r2 || g!=g2 || b!=b2);
601 step = (float)(abs(x2-x1)>abs(y2-y1)?abs(x2-x1):abs(y2-y1));
602 dx = (float)(x2-x1) / step;
603 dy = (float)(y2-y1) / step;
605 SDL_LockSurface( screen);
606 for( i=0; i<step; i++) {
607 x += dx;
608 y += dy;
609 if( x < 0.0 || x >= WIDTH || y < 0.0 || y >= HEIGHT) {
610 continue;
612 if( fade) {
613 SET_PIXEL_RGB( screen, (int)x, (int)y, (r*(step-i)+r2*i)/step, (g*(step-i)+g2*i)/step, (b*(step-i)+b2*i)/step);
614 } else {
615 SET_PIXEL_RGB( screen, (int)x, (int)y, r, g, b);
618 SDL_UnlockSurface( screen);