1#include <stdio.h>
2#include <stdarg.h>
3#include <getopt.h>
4#include <stdlib.h>
5#include <time.h>
6#include <string.h>
7
8#include "raylib.h"
9#include "lua.h"
10#include "lualib.h"
11#include "lauxlib.h"
12
13#include "stdlib/json.h"
14#include "stdlib/color.h"
15#include "stdlib/button.h"
16#include "stdlib/helpers.h"
17
18#include "fonts/dejavusans_mono_bold.h"
19
20#define VERSION "x.x"
21#define DEBUG_LEVEL LOG_DEBUG
22#define FONT_IMPORT_SIZE 30
23#define UID_LENGTH 64
24#define GAMEPAD_INDEX 0
25#define XBOX_ALIAS_1 "xbox"
26#define XBOX_ALIAS_2 "x-box"
27#define PS_ALIAS "playstation"
28
29typedef struct ExternalImage {
30 char uid[UID_LENGTH + 1];
31 Texture2D texture;
32 struct ExternalImage *next;
33} ExternalImage;
34
35typedef struct {
36 ExternalImage *head;
37 ExternalImage *tail;
38 int count;
39} ImageList;
40
41typedef struct ExternalSound {
42 char uid[UID_LENGTH + 1];
43 Sound sound;
44 struct ExternalSound *next;
45} ExternalSound;
46
47typedef struct {
48 ExternalSound *head;
49 ExternalSound *tail;
50 int count;
51} SoundList;
52
53typedef struct {
54 Font font;
55 int font_size;
56 Camera2D camera;
57 ImageList images;
58 SoundList sounds;
59} Context;
60
61// Setting up global context.
62Context ctx = {0};
63
64static void generate_uid(char *str, size_t length) {
65 static const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
66 size_t charset_size = sizeof(charset) - 1; // exclude null terminator
67
68 for (size_t i = 0; i < length; i++) {
69 int key = rand() % charset_size;
70 str[i] = charset[key];
71 }
72 str[length] = '\0';
73}
74
75void init_image_list(ImageList *list) {
76 list->head = NULL;
77 list->tail = NULL;
78 list->count = 0;
79}
80
81void init_sound_list(SoundList *list) {
82 list->head = NULL;
83 list->tail = NULL;
84 list->count = 0;
85}
86
87void free_image_list(ImageList *list) {
88 ExternalImage *curr = list->head;
89 while (curr) {
90 ExternalImage *next = curr->next;
91 UnloadTexture(curr->texture);
92 free(curr);
93 curr = next;
94 }
95 list->head = list->tail = NULL;
96 list->count = 0;
97}
98
99void free_sound_list(SoundList *list) {
100 ExternalSound *curr = list->head;
101 while (curr) {
102 ExternalSound *next = curr->next;
103 UnloadSound(curr->sound);
104 free(curr);
105 curr = next;
106 }
107 list->head = list->tail = NULL;
108 list->count = 0;
109}
110
111static int lua_getfield_int(lua_State *L, int index, const char *key) {
112 lua_getfield(L, index, key);
113 int val = (int)luaL_checknumber(L, -1);
114 lua_pop(L, 1);
115 return val;
116}
117
118static int lua_getfield_int_opt(lua_State *L, int index, const char *key, int def) {
119 lua_getfield(L, index, key);
120 int val = lua_isnil(L, -1) ? def : (int)luaL_checknumber(L, -1);
121 lua_pop(L, 1);
122 return val;
123}
124
125static int load_embedded_module(lua_State *L, const char *module_code, size_t code_len, const char *module_name) {
126 if (luaL_loadbuffer(L, module_code, code_len, module_name) || lua_pcall(L, 0, 1, 0)) {
127 TraceLog(LOG_FATAL, "Error loading %s: %s\n", module_name, lua_tostring(L, -1));
128 return 0;
129 }
130 lua_setglobal(L, module_name);
131 return 1;
132}
133
134static int l_open_window(lua_State *L) {
135 int width = luaL_checknumber(L, 1);
136 int height = luaL_checknumber(L, 2);
137 const char *title = luaL_checkstring(L, 3);
138 SetConfigFlags(FLAG_VSYNC_HINT | FLAG_WINDOW_HIGHDPI);
139 InitWindow(width, height, title);
140
141 ctx.font_size = FONT_IMPORT_SIZE;
142 ctx.font = LoadFontFromMemory(".ttf", dejavusans_mono_bold, dejavusans_mono_bold_len, ctx.font_size, NULL, 0);
143 SetTextureFilter(ctx.font.texture, TEXTURE_FILTER_TRILINEAR);
144
145 if (!IsFontValid(ctx.font)) {
146 TraceLog(LOG_DEBUG, "Font not valid.");
147 }
148
149 ctx.camera.target = (Vector2){ 0.0f, 0.0f };
150 ctx.camera.offset = (Vector2){ GetScreenHeight()/2.0f, GetScreenHeight()/2.0f };
151 ctx.camera.rotation = 0.0f;
152 ctx.camera.zoom = 1.0f;
153
154 InitAudioDevice();
155
156 init_image_list(&ctx.images);
157 init_sound_list(&ctx.sounds);
158
159 return 0;
160}
161
162static int l_close_window(lua_State *L) {
163 free_image_list(&ctx.images);
164 free_sound_list(&ctx.sounds);
165 UnloadFont(ctx.font);
166 CloseAudioDevice();
167 CloseWindow();
168 return 0;
169}
170
171// XXX: This function name is still a bit sus. Revisit the name later.
172static int l_window_running(lua_State *L) {
173 lua_pushboolean(L, !WindowShouldClose());
174 return 1;
175}
176
177static int l_set_fps(lua_State *L) {
178 int fps = luaL_checknumber(L, 1);
179 SetTargetFPS(fps);
180 return 0;
181}
182
183static int l_get_dt(lua_State *L) {
184 lua_pushnumber(L, GetFrameTime());
185 return 1;
186}
187
188static int l_get_fps(lua_State *L) {
189 lua_pushnumber(L, GetFPS());
190 return 1;
191}
192
193static int l_get_time(lua_State *L) {
194 lua_pushnumber(L, GetTime());
195 return 1;
196}
197
198static int l_get_width(lua_State *L) {
199 lua_pushnumber(L, GetScreenWidth());
200 return 1;
201}
202
203static int l_get_height(lua_State *L) {
204 lua_pushnumber(L, GetScreenHeight());
205 return 1;
206}
207
208static int l_start_drawing(lua_State *L) {
209 BeginDrawing();
210 return 0;
211}
212
213static int l_stop_drawing(lua_State *L) {
214 EndDrawing();
215 return 0;
216}
217
218static int l_start_camera(lua_State *L) {
219 BeginMode2D(ctx.camera);
220 return 0;
221}
222
223static int l_stop_camera(lua_State *L) {
224 EndMode2D();
225 return 0;
226}
227
228static int l_move_camera(lua_State *L) {
229 int x = luaL_checknumber(L, 1);
230 int y = luaL_checknumber(L, 2);
231
232 ctx.camera.target.x = x;
233 ctx.camera.target.y = y;
234 return 0;
235}
236
237static int l_clear_window(lua_State *L) {
238 luaL_checktype(L, 1, LUA_TTABLE);
239 Color color = {
240 .r = (unsigned char)lua_getfield_int(L, 1, "r"),
241 .g = (unsigned char)lua_getfield_int(L, 1, "g"),
242 .b = (unsigned char)lua_getfield_int(L, 1, "b"),
243 .a = (unsigned char)lua_getfield_int_opt(L, 1, "a", 255)
244 };
245 ClearBackground(color);
246 return 0;
247}
248
249static int l_draw_info(lua_State *L) {
250 float delta = GetFrameTime();
251 int fps = GetFPS();
252 double runtime = GetTime();
253 int height = GetScreenHeight();
254
255 DrawRectangle(10, height - 75, 150, 65, DARKBLUE);
256 DrawTextEx(ctx.font, TextFormat("fps: %d", fps), (Vector2){ 15, height - 70 }, 16, 0, RAYWHITE);
257 DrawTextEx(ctx.font, TextFormat("run: %.4f", runtime), (Vector2){ 15, height - 50 }, 16, 0, RAYWHITE);
258 DrawTextEx(ctx.font, TextFormat("dt: %.4f", delta), (Vector2){ 15, height - 30 }, 16, 0, RAYWHITE);
259
260 return 0;
261}
262
263static int l_draw_rect(lua_State *L) {
264 int x = luaL_checknumber(L, 1);
265 int y = luaL_checknumber(L, 2);
266 int width = luaL_checknumber(L, 3);
267 int height = luaL_checknumber(L, 4);
268 luaL_checktype(L, 5, LUA_TTABLE);
269 Color color = {
270 .r = (unsigned char)lua_getfield_int(L, 5, "r"),
271 .g = (unsigned char)lua_getfield_int(L, 5, "g"),
272 .b = (unsigned char)lua_getfield_int(L, 5, "b"),
273 .a = (unsigned char)lua_getfield_int_opt(L, 5, "a", 255)
274 };
275 DrawRectangle(x, y, width, height, color);
276 return 0;
277}
278
279static int l_draw_text(lua_State *L) {
280 const char *text = luaL_checkstring(L, 1);
281 int x = luaL_checknumber(L, 2);
282 int y = luaL_checknumber(L, 3);
283 int size = luaL_checknumber(L, 4);
284 luaL_checktype(L, 5, LUA_TTABLE);
285 Color color = {
286 .r = (unsigned char)lua_getfield_int(L, 5, "r"),
287 .g = (unsigned char)lua_getfield_int(L, 5, "g"),
288 .b = (unsigned char)lua_getfield_int(L, 5, "b"),
289 .a = (unsigned char)lua_getfield_int_opt(L, 5, "a", 255)
290 };
291 DrawTextEx(ctx.font, text, (Vector2){ x, y }, size, 0, color);
292 return 0;
293}
294
295static int l_draw_pixel(lua_State *L) {
296 int x = luaL_checknumber(L, 1);
297 int y = luaL_checknumber(L, 2);
298 luaL_checktype(L, 3, LUA_TTABLE);
299 Color color = {
300 .r = (unsigned char)lua_getfield_int(L, 3, "r"),
301 .g = (unsigned char)lua_getfield_int(L, 3, "g"),
302 .b = (unsigned char)lua_getfield_int(L, 3, "b"),
303 .a = (unsigned char)lua_getfield_int_opt(L, 3, "a", 255)
304 };
305 DrawPixel(x, y, color);
306}
307
308static int l_draw_line(lua_State *L) {
309 int x1 = luaL_checknumber(L, 1);
310 int y1 = luaL_checknumber(L, 2);
311 int x2 = luaL_checknumber(L, 3);
312 int y2 = luaL_checknumber(L, 4);
313 luaL_checktype(L, 5, LUA_TTABLE);
314 Color color = {
315 .r = (unsigned char)lua_getfield_int(L, 5, "r"),
316 .g = (unsigned char)lua_getfield_int(L, 5, "g"),
317 .b = (unsigned char)lua_getfield_int(L, 5, "b"),
318 .a = (unsigned char)lua_getfield_int_opt(L, 5, "a", 255)
319 };
320 DrawLine(x1, y1, x2, y2, color);
321 return 0;
322}
323
324static int l_draw_circle(lua_State *L) {
325 int center_x = luaL_checknumber(L, 1);
326 int center_y = luaL_checknumber(L, 2);
327 int radius = luaL_checknumber(L, 3);
328 luaL_checktype(L, 4, LUA_TTABLE);
329 Color color = {
330 .r = (unsigned char)lua_getfield_int(L, 4, "r"),
331 .g = (unsigned char)lua_getfield_int(L, 4, "g"),
332 .b = (unsigned char)lua_getfield_int(L, 4, "b"),
333 .a = (unsigned char)lua_getfield_int_opt(L, 4, "a", 255)
334 };
335 DrawCircle(center_x, center_y, radius, color);
336 return 0;
337}
338
339static int l_draw_ellipse(lua_State *L) {
340 int center_x = luaL_checknumber(L, 1);
341 int center_y = luaL_checknumber(L, 2);
342 int radius_h = luaL_checknumber(L, 3);
343 int radius_v = luaL_checknumber(L, 4);
344 luaL_checktype(L, 5, LUA_TTABLE);
345 Color color = {
346 .r = (unsigned char)lua_getfield_int(L, 5, "r"),
347 .g = (unsigned char)lua_getfield_int(L, 5, "g"),
348 .b = (unsigned char)lua_getfield_int(L, 5, "b"),
349 .a = (unsigned char)lua_getfield_int_opt(L, 5, "a", 255)
350 };
351 DrawEllipse(center_x, center_y, radius_h, radius_v, color);
352 return 0;
353}
354
355static int l_draw_triangle(lua_State *L) {
356 int x1 = luaL_checknumber(L, 1);
357 int y1 = luaL_checknumber(L, 2);
358 int x2 = luaL_checknumber(L, 3);
359 int y2 = luaL_checknumber(L, 4);
360 int x3 = luaL_checknumber(L, 5);
361 int y3 = luaL_checknumber(L, 6);
362 luaL_checktype(L, 7, LUA_TTABLE);
363 Color color = {
364 .r = (unsigned char)lua_getfield_int(L, 7, "r"),
365 .g = (unsigned char)lua_getfield_int(L, 7, "g"),
366 .b = (unsigned char)lua_getfield_int(L, 7, "b"),
367 .a = (unsigned char)lua_getfield_int_opt(L, 7, "a", 255)
368 };
369
370 // NOTE: Raylib orders vertices in counter-clockwise order. We order them
371 // in clockwise order instead to make this a bit easier to use.
372 Vector2 p1 = { x1, y1 };
373 Vector2 p2 = { x3, y3 };
374 Vector2 p3 = { x2, y2 };
375
376 DrawTriangle(p1, p2, p3, color);
377 return 0;
378}
379
380static int l_button_down(lua_State *L) {
381 luaL_checktype(L, 1, LUA_TTABLE);
382 int keyboard = (int)lua_getfield_int(L, 1, "keyboard");
383 int xbox = (int)lua_getfield_int(L, 1, "xbox");
384 int playstation = (int)lua_getfield_int(L, 1, "playstation");
385 bool is_active = false;
386
387 // Check keyboard first.
388 if (IsKeyDown(keyboard)) {
389 is_active = true;
390 } else {
391 // FIXME: This does not work currently due to a bug in GLFW that Raylib
392 // is using. SDL could be used to compile Raylib but that doesn't
393 // statically link against SDL so it's not ideal.
394
395 // Check for controllers otherwise.
396 if (IsGamepadAvailable(GAMEPAD_INDEX)) {
397 // Xbox controller.
398 if (TextFindIndex(TextToLower(GetGamepadName(GAMEPAD_INDEX)), XBOX_ALIAS_1) > -1 || TextFindIndex(TextToLower(GetGamepadName(GAMEPAD_INDEX)), XBOX_ALIAS_2) > -1) {
399 if (IsGamepadButtonDown(GAMEPAD_INDEX, xbox)) {
400 is_active = true;
401 }
402 }
403
404 // Playstation controller.
405 if (TextFindIndex(TextToLower(GetGamepadName(GAMEPAD_INDEX)), PS_ALIAS) > -1) {
406 if (IsGamepadButtonDown(GAMEPAD_INDEX, playstation)) {
407 is_active = true;
408 }
409 }
410 }
411 }
412
413 lua_pushboolean(L, is_active);
414 return 1;
415}
416
417static int l_button_pressed(lua_State *L) {
418 luaL_checktype(L, 1, LUA_TTABLE);
419 int keyboard = (int)lua_getfield_int(L, 1, "keyboard");
420 int xbox = (int)lua_getfield_int(L, 1, "xbox");
421 int playstation = (int)lua_getfield_int(L, 1, "playstation");
422 bool is_active = false;
423
424 // Check keyboard first.
425 if (IsKeyPressed(keyboard)) {
426 is_active = true;
427 } else {
428 // FIXME: This does not work currently due to a bug in GLFW that Raylib
429 // is using. SDL could be used to compile Raylib but that doesn't
430 // statically link against SDL so it's not ideal.
431
432 // Check for controllers otherwise.
433 if (IsGamepadAvailable(GAMEPAD_INDEX)) {
434 // Xbox controller.
435 if (TextFindIndex(TextToLower(GetGamepadName(GAMEPAD_INDEX)), XBOX_ALIAS_1) > -1 || TextFindIndex(TextToLower(GetGamepadName(GAMEPAD_INDEX)), XBOX_ALIAS_2) > -1) {
436 if (IsGamepadButtonPressed(GAMEPAD_INDEX, xbox)) {
437 is_active = true;
438 }
439 }
440
441 // Playstation controller.
442 if (TextFindIndex(TextToLower(GetGamepadName(GAMEPAD_INDEX)), PS_ALIAS) > -1) {
443 if (IsGamepadButtonPressed(GAMEPAD_INDEX, playstation)) {
444 is_active = true;
445 }
446 }
447 }
448 }
449
450 lua_pushboolean(L, is_active);
451 return 1;
452}
453
454static int l_load_image(lua_State *L) {
455 const char *filepath = luaL_checkstring(L, 1);
456
457 ExternalImage *img = malloc(sizeof(ExternalImage));
458 if (!img) {
459 TraceLog(LOG_FATAL, "Out of memory!");
460 return 0;
461 }
462
463 Image image = LoadImage(filepath);
464 if (!image.data) {
465 TraceLog(LOG_FATAL, "Failed to load image: %s\n", filepath);
466 free(img);
467 return 0;
468 }
469
470 img->texture = LoadTextureFromImage(image);
471 UnloadImage(image);
472
473 generate_uid(img->uid, UID_LENGTH);
474 img->next = NULL;
475
476 if (ctx.images.tail) {
477 ctx.images.tail->next = img;
478 ctx.images.tail = img;
479 } else {
480 ctx.images.head = img;
481 ctx.images.tail = img;
482 }
483 ctx.images.count++;
484
485 TraceLog(LOG_DEBUG, "[add_img] %s (%d)", img->uid, strlen(img->uid));
486
487 lua_pushstring(L, img->uid);
488 return 1;
489}
490
491static int l_draw_image(lua_State *L) {
492 const char *uid = luaL_checkstring(L, 1);
493 int x = luaL_checknumber(L, 2);
494 int y = luaL_checknumber(L, 3);
495
496 ExternalImage *current = ctx.images.head;
497 while (current) {
498 if (strncmp(current->uid, uid, UID_LENGTH) == 0) {
499 DrawTexture(current->texture, x, y, WHITE);
500 break;
501 }
502 current = current->next;
503 }
504
505 return 0;
506}
507
508static int l_load_sound(lua_State *L) {
509 const char *filepath = luaL_checkstring(L, 1);
510
511 ExternalSound *snd = malloc(sizeof(ExternalSound));
512 if (!snd) {
513 TraceLog(LOG_FATAL, "Out of memory!");
514 return 0;
515 }
516
517 snd->sound = LoadSound(filepath);
518 generate_uid(snd->uid, UID_LENGTH);
519 snd->next = NULL;
520
521 if (ctx.sounds.tail) {
522 ctx.sounds.tail->next = snd;
523 ctx.sounds.tail = snd;
524 } else {
525 ctx.sounds.head = snd;
526 ctx.sounds.tail = snd;
527 }
528 ctx.sounds.count++;
529
530 TraceLog(LOG_DEBUG, "[add_snd] %s (%d)", snd->uid, strlen(snd->uid));
531
532 lua_pushstring(L, snd->uid);
533 return 1;
534}
535
536static int l_play_sound(lua_State *L) {
537 const char *uid = luaL_checkstring(L, 1);
538
539 ExternalSound *current = ctx.sounds.head;
540 while (current) {
541 if (strncmp(current->uid, uid, UID_LENGTH) == 0) {
542 PlaySound(current->sound);
543 break;
544 }
545 current = current->next;
546 }
547
548 return 0;
549}
550
551static int l_stop_sound(lua_State *L) {
552 const char *uid = luaL_checkstring(L, 1);
553
554 ExternalSound *current = ctx.sounds.head;
555 while (current) {
556 if (strncmp(current->uid, uid, UID_LENGTH) == 0) {
557 StopSound(current->sound);
558 break;
559 }
560 current = current->next;
561 }
562
563 return 0;
564}
565
566static void help(const char *argv0) {
567 printf("Usage: %s [options]\n"
568 "\nAvailable options:\n"
569 " -f,--file=file.lua run input file\n"
570 " -b,--bundle bundles this folder\n"
571 " -d,--debug prints debug information\n"
572 " -h,--help this help\n"
573 " -v,--version show version\n",
574 argv0);
575}
576
577static void version(const char *argv0) {
578 printf("%s version %s\n", argv0, VERSION);
579}
580
581int main(int argc, char *argv[]) {
582 srand(time(NULL));
583
584 TraceLogLevel debug_level = LOG_WARNING;
585 const char *run_file = NULL;
586
587 const char short_options[] = "f:dbhv";
588 const struct option long_options[] = {
589 { "file", 1, NULL, 'f' },
590 { "debug", 0, NULL, 'd' },
591 { "bundle", 0, NULL, 'b' },
592 { "help", 0, NULL, 'h' },
593 { "version", 0, NULL, 'v' },
594 { 0 },
595 };
596
597 int opt;
598 while ((opt = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
599 switch (opt) {
600 case 'f':
601 run_file = optarg;
602 break;
603 case 'b':
604 printf("TODO: Bundler\n");
605 return 0;
606 case 'd':
607 debug_level = LOG_DEBUG;
608 break;
609 case 'h':
610 help(argv[0]);
611 return 0;
612 case 'v':
613 version(argv[0]);
614 return 0;
615 default:
616 fprintf(stdout, "Missing options. Check help.\n");
617 return 0;
618 }
619 }
620
621 if (run_file) {
622 SetTraceLogLevel(debug_level);
623
624 lua_State *L = luaL_newstate();
625 luaL_openlibs(L);
626
627 // Loading embeded modules into Lua state.
628 if (!load_embedded_module(L, json, json_len, "json")) return 1;
629 if (!load_embedded_module(L, color, color_len, "color")) return 1;
630 if (!load_embedded_module(L, button, button_len, "button")) return 1;
631 if (!load_embedded_module(L, helpers, helpers_len, "helpers")) return 1;
632
633 // Registring Raylib mappings.
634 lua_register(L, "open_window", l_open_window);
635 lua_register(L, "close_window", l_close_window);
636 lua_register(L, "window_running", l_window_running);
637
638 lua_register(L, "start_drawing", l_start_drawing);
639 lua_register(L, "stop_drawing", l_stop_drawing);
640 lua_register(L, "clear_window", l_clear_window);
641
642 lua_register(L, "start_camera", l_start_camera);
643 lua_register(L, "stop_camera", l_stop_camera);
644 lua_register(L, "move_camera", l_move_camera);
645
646 lua_register(L, "set_fps", l_set_fps);
647 lua_register(L, "get_fps", l_get_fps);
648 lua_register(L, "get_dt", l_get_dt);
649 lua_register(L, "get_time", l_get_time);
650 lua_register(L, "get_width", l_get_width);
651 lua_register(L, "get_height", l_get_height);
652
653 lua_register(L, "button_down", l_button_down);
654 lua_register(L, "button_pressed", l_button_pressed);
655
656 lua_register(L, "draw_info", l_draw_info);
657 lua_register(L, "draw_rect", l_draw_rect);
658 lua_register(L, "draw_text", l_draw_text);
659 lua_register(L, "draw_pixel", l_draw_pixel);
660 lua_register(L, "draw_line", l_draw_line);
661 lua_register(L, "draw_circle", l_draw_circle);
662 lua_register(L, "draw_ellipse", l_draw_ellipse);
663 lua_register(L, "draw_triangle", l_draw_triangle);
664
665 lua_register(L, "load_image", l_load_image);
666 lua_register(L, "draw_image", l_draw_image);
667
668 lua_register(L, "load_sound", l_load_sound);
669 lua_register(L, "play_sound", l_play_sound);
670 lua_register(L, "stop_sound", l_play_sound);
671
672 // Interpreting and running input file Lua script.
673 if (luaL_loadfile(L, run_file) || lua_pcall(L, 0, 0, 0)) {
674 TraceLog(LOG_FATAL, "Error: %s\n", lua_tostring(L, -1));
675 return 1;
676 }
677
678 lua_close(L);
679 }
680
681 return 0;
682}