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}