1 /*
2     3omns - old-school arcade-style tile-based bomb-dropping deathmatch jam
3             <https://chazomaticus.github.io/3omns/>
4     b3 - base library for 3omns
5     Copyright 2014-2015 Charles Lindsay <chaz@chazomatic.us>
6 
7     3omns is free software: you can redistribute it and/or modify it under the
8     terms of the GNU General Public License as published by the Free Software
9     Foundation, either version 3 of the License, or (at your option) any later
10     version.
11 
12     3omns is distributed in the hope that it will be useful, but WITHOUT ANY
13     WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14     FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
15     details.
16 
17     You should have received a copy of the GNU General Public License along
18     with 3omns.  If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 #include "b3.h"
22 
23 #include <SDL.h>
24 #include <SDL_image.h>
25 #include <SDL_ttf.h>
26 #include <stdarg.h>
27 #include <stddef.h>
28 
29 
30 struct b3_image {
31     int ref_count;
32     SDL_Texture *texture;
33     b3_rect rect;
34     b3_image *parent;
35 };
36 
37 struct b3_font {
38     int ref_count;
39     TTF_Font *font;
40 };
41 
42 struct b3_text {
43     int ref_count;
44     char *string;
45     SDL_Texture *texture;
46     b3_size size;
47     b3_color color;
48 };
49 
50 
51 b3_size b3_window_size = {0, 0};
52 b3_ticks b3_tick_frequency = 0;
53 
54 static SDL_Window *window = NULL;
55 static SDL_Renderer *renderer = NULL;
56 
57 static _Bool quit = 0;
58 
59 static b3_input_callback handle_input = NULL;
60 
61 
b3_init(void)62 void b3_init(void) {
63     quit = 0;
64 
65     if(SDL_Init(SDL_INIT_NOPARACHUTE) != 0)
66         b3_fatal("Error initializing SDL: %s", SDL_GetError());
67 
68     if((IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG) != IMG_INIT_PNG)
69         b3_fatal("Error initializing SDL_image: %s", IMG_GetError());
70 
71     if(TTF_Init() != 0)
72         b3_fatal("Error initializing SDL_ttf: %s", TTF_GetError());
73 
74     b3_tick_frequency = (b3_ticks)SDL_GetPerformanceFrequency();
75 }
76 
b3_quit(void)77 void b3_quit(void) {
78     TTF_Quit();
79     IMG_Quit();
80     SDL_Quit();
81 }
82 
b3_enter_window(const char * restrict window_title,const b3_size * restrict window_size,b3_input_callback input_callback)83 void b3_enter_window(
84     const char *restrict window_title,
85     const b3_size *restrict window_size,
86     b3_input_callback input_callback
87 ) {
88     if(SDL_WasInit(SDL_INIT_VIDEO) == 0) {
89         if(SDL_InitSubSystem(SDL_INIT_VIDEO) != 0)
90             b3_fatal("Error initializing SDL video: %s", SDL_GetError());
91     }
92 
93     window = SDL_CreateWindow(
94         window_title,
95         SDL_WINDOWPOS_UNDEFINED,
96         SDL_WINDOWPOS_UNDEFINED,
97         window_size->width,
98         window_size->height,
99         SDL_WINDOW_OPENGL
100     );
101     if(!window)
102         b3_fatal("Error creating the main window: %s", SDL_GetError());
103     b3_window_size = *window_size;
104 
105     SDL_DisableScreenSaver();
106 
107     // From the docs (<http://wiki.libsdl.org/SDL_CreateRenderer>) it sounds
108     // like this prefers hardware-accelerated renderers.  If we need more
109     // control, we may want to try creating different types of renderers in a
110     // loop until one succeeds or something.
111     renderer = SDL_CreateRenderer(window, -1, 0);
112     if(!renderer)
113         b3_fatal("Error creating rendering context: %s", SDL_GetError());
114 
115     handle_input = input_callback;
116 }
117 
b3_exit_window(void)118 void b3_exit_window(void) {
119     b3_window_size = (b3_size){0, 0};
120     handle_input = NULL;
121     if(renderer) {
122         SDL_DestroyRenderer(renderer);
123         renderer = NULL;
124     }
125     if(window) {
126         SDL_DestroyWindow(window);
127         window = NULL;
128     }
129 }
130 
keysym_to_input(const SDL_Keysym * restrict keysym)131 static int keysym_to_input(const SDL_Keysym *restrict keysym) {
132     // TODO: configuration or any kind of custom mapping.
133 
134     switch(keysym->sym) {
135     case SDLK_ESCAPE: return B3_INPUT_BACK;
136     case SDLK_PAUSE: return B3_INPUT_PAUSE;
137     default: break;
138     }
139 
140     switch(keysym->scancode) {
141     case SDL_SCANCODE_GRAVE: return B3_INPUT_DEBUG;
142     case SDL_SCANCODE_UP: return B3_INPUT_UP_0;
143     case SDL_SCANCODE_DOWN: return B3_INPUT_DOWN_0;
144     case SDL_SCANCODE_LEFT: return B3_INPUT_LEFT_0;
145     case SDL_SCANCODE_RIGHT: return B3_INPUT_RIGHT_0;
146     case SDL_SCANCODE_RCTRL: return B3_INPUT_FIRE_0;
147     case SDL_SCANCODE_W: return B3_INPUT_UP_1;
148     case SDL_SCANCODE_S: return B3_INPUT_DOWN_1;
149     case SDL_SCANCODE_A: return B3_INPUT_LEFT_1;
150     case SDL_SCANCODE_D: return B3_INPUT_RIGHT_1;
151     case SDL_SCANCODE_LCTRL: return B3_INPUT_FIRE_1;
152     default: break;
153     }
154 
155     // These may not be desirable, depending on the layout.  A quick glance at
156     // the 'poedia list of keyboard layouts seems to indicate it's safe.
157     switch(keysym->sym) {
158     case SDLK_RETURN: return B3_INPUT_FIRE_0; // For menus.
159     case SDLK_p: return B3_INPUT_PAUSE;
160     default: break;
161     }
162 
163     return -1;
164 }
165 
handle_key_event(const SDL_KeyboardEvent * restrict event,void * input_callback_data)166 static _Bool handle_key_event(
167     const SDL_KeyboardEvent *restrict event,
168     void *input_callback_data
169 ) {
170     // TODO: gamepads or joysticks.
171 
172     if(!handle_input || event->repeat)
173         return 0;
174 
175     int input = keysym_to_input(&event->keysym);
176     if(input < 0)
177         return 0;
178 
179     return handle_input(
180         (b3_input)input,
181         event->state == SDL_PRESSED,
182         input_callback_data
183     );
184 }
185 
b3_process_events(void * input_callback_data)186 _Bool b3_process_events(void *input_callback_data) {
187     SDL_Event event;
188     while(SDL_PollEvent(&event) && !quit) {
189         if(event.type == SDL_QUIT)
190             quit = 1;
191         else if(event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) {
192             if(handle_key_event(&event.key, input_callback_data))
193                 quit = 1;
194         }
195     }
196 
197     return quit;
198 }
199 
b3_get_tick_count(void)200 b3_ticks b3_get_tick_count(void) {
201     return (b3_ticks)SDL_GetPerformanceCounter();
202 }
203 
b3_sleep(int ms)204 void b3_sleep(int ms) {
205     SDL_Delay((Uint32)ms);
206 }
207 
b3_begin_scene(void)208 void b3_begin_scene(void) {
209     SDL_RenderClear(renderer);
210 }
211 
b3_end_scene(void)212 void b3_end_scene(void) {
213     SDL_RenderPresent(renderer);
214 }
215 
b3_load_image(const char * restrict filename)216 b3_image *b3_load_image(const char *restrict filename) {
217     SDL_Surface *surface = IMG_Load(filename);
218     if(!surface)
219         b3_fatal("Error loading image %s: %s", filename, IMG_GetError());
220 
221     b3_size size = {surface->w, surface->h};
222 
223     SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
224     SDL_FreeSurface(surface);
225     if(!texture)
226         b3_fatal("Error creating texture from %s: %s", filename, SDL_GetError());
227 
228     b3_image *image = b3_malloc(sizeof(*image), 1);
229     image->texture = texture;
230     image->rect = (b3_rect){.size = size};
231     return b3_ref_image(image);
232 }
233 
b3_new_sub_image(b3_image * restrict image,const b3_rect * restrict rect)234 b3_image *b3_new_sub_image(
235     b3_image *restrict image,
236     const b3_rect *restrict rect
237 ) {
238     b3_image *sub_image = b3_malloc(sizeof(*sub_image), 1);
239     sub_image->texture = image->texture;
240     sub_image->rect = *rect;
241     sub_image->parent = b3_ref_image(image);
242     return b3_ref_image(sub_image);
243 }
244 
b3_ref_image(b3_image * restrict image)245 b3_image *b3_ref_image(b3_image *restrict image) {
246     image->ref_count++;
247     return image;
248 }
249 
b3_free_image(b3_image * restrict image)250 void b3_free_image(b3_image *restrict image) {
251     if(image && !--image->ref_count) {
252         if(!image->parent)
253             SDL_DestroyTexture(image->texture);
254         b3_free_image(image->parent);
255         b3_free(image, sizeof(*image));
256     }
257 }
258 
b3_get_image_size(b3_image * restrict image)259 b3_size b3_get_image_size(b3_image *restrict image) {
260     return image->rect.size;
261 }
262 
b3_draw_image(b3_image * restrict image,const b3_rect * restrict rect)263 void b3_draw_image(b3_image *restrict image, const b3_rect *restrict rect) {
264     const b3_rect *restrict r = &image->rect;
265     SDL_RenderCopy(
266         renderer,
267         image->texture,
268         &(SDL_Rect){r->x, r->y, r->width, r->height},
269         &(SDL_Rect){rect->x, rect->y, rect->width, rect->height}
270     );
271 }
272 
b3_load_font(int size,const char * restrict filename,int index)273 b3_font *b3_load_font(int size, const char *restrict filename, int index) {
274     TTF_Font *ttf = TTF_OpenFontIndex(filename, size, (long)index);
275     if(!ttf)
276         b3_fatal("Error loading font %s: %s", filename, TTF_GetError());
277 
278     b3_font *font = b3_malloc(sizeof(*font), 1);
279     font->font = ttf;
280     return b3_ref_font(font);
281 }
282 
b3_ref_font(b3_font * restrict font)283 b3_font *b3_ref_font(b3_font *restrict font) {
284     font->ref_count++;
285     return font;
286 }
287 
b3_free_font(b3_font * restrict font)288 void b3_free_font(b3_font *restrict font) {
289     if(font && !--font->ref_count) {
290         TTF_CloseFont(font->font);
291         b3_free(font, sizeof(*font));
292     }
293 }
294 
b3_new_text(b3_font * restrict font,const char * restrict format,...)295 b3_text *b3_new_text(
296     b3_font *restrict font,
297     const char *restrict format,
298     ...
299 ) {
300     va_list args;
301     va_start(args, format);
302 
303     char *string = b3_copy_vformat(format, args);
304 
305     va_end(args);
306 
307     SDL_Surface *surface = TTF_RenderUTF8_Blended(
308         font->font,
309         string,
310         (SDL_Color){255, 255, 255, 255}
311     );
312     if(!surface)
313         b3_fatal("Error rendering text '%s': %s", string, TTF_GetError());
314 
315     b3_size size = {surface->w, surface->h};
316 
317     SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
318     SDL_FreeSurface(surface);
319     if(!texture)
320         b3_fatal("Error creating texture from string: %s", SDL_GetError());
321 
322     b3_text *text = b3_malloc(sizeof(*text), 1);
323     text->string = string;
324     text->texture = texture;
325     text->size = size;
326     text->color = 0xffffffff;
327     return b3_ref_text(text);
328 }
329 
b3_ref_text(b3_text * restrict text)330 b3_text *b3_ref_text(b3_text *restrict text) {
331     text->ref_count++;
332     return text;
333 }
334 
b3_free_text(b3_text * restrict text)335 void b3_free_text(b3_text *restrict text) {
336     if(text && !--text->ref_count) {
337         b3_free(text->string, 0);
338         SDL_DestroyTexture(text->texture);
339         b3_free(text, sizeof(*text));
340     }
341 }
342 
b3_get_text_string(b3_text * restrict text)343 const char *b3_get_text_string(b3_text *restrict text) {
344     return text->string;
345 }
346 
b3_get_text_size(b3_text * restrict text)347 b3_size b3_get_text_size(b3_text *restrict text) {
348     return text->size;
349 }
350 
b3_get_text_color(b3_text * restrict text)351 b3_color b3_get_text_color(b3_text *restrict text) {
352     return text->color;
353 }
354 
b3_set_text_color(b3_text * restrict text,b3_color color)355 b3_text *b3_set_text_color(b3_text *restrict text, b3_color color) {
356     text->color = color;
357 
358     SDL_SetTextureColorMod(
359         text->texture,
360         (Uint8)B3_RED(color),
361         (Uint8)B3_GREEN(color),
362         (Uint8)B3_BLUE(color)
363     );
364     SDL_SetTextureAlphaMod(text->texture, (Uint8)B3_ALPHA(color));
365 
366     return text;
367 }
368 
b3_draw_text(b3_text * restrict text,const b3_rect * restrict rect)369 void b3_draw_text(b3_text *restrict text, const b3_rect *restrict rect) {
370     SDL_RenderCopy(
371         renderer,
372         text->texture,
373         NULL,
374         &(SDL_Rect){rect->x, rect->y, rect->width, rect->height}
375     );
376 }
377