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