#include "twn_game_api.h" #include "state.h" #include #include #include #include /* require will go through physicsfs exclusively so that scripts can be in the data dir */ static int physfs_loader(lua_State *L) { const char *name = luaL_checkstring(L, 1); char *final_path = NULL; SDL_asprintf(&final_path, "%s.lua", name); if (!file_exists(final_path)) { char *error_message = NULL; SDL_asprintf(&error_message, "could not find module %s in filesystem", name); lua_pushstring(L, error_message); free(error_message); free(final_path); return 1; } unsigned char *buf = NULL; int64_t buf_size = file_to_bytes(final_path, &buf); free(final_path); luaL_loadbuffer(L, (char *)buf, buf_size, name); free(buf); return 1; } static Rect table_to_rect(lua_State *L, int idx, const char *name) { /* types are checked here to help prevent unexpected results */ Rect rect; int is_num; lua_getfield(L, idx, "x"); rect.x = (float)lua_tonumberx(L, -1, &is_num); if (!is_num) luaL_error(L, "bad field 'x' in '%s' (number expected, got %s)", name, luaL_typename(L, -1)); lua_pop(L, 1); lua_getfield(L, idx, "y"); rect.y = (float)lua_tonumberx(L, -1, &is_num); if (!is_num) luaL_error(L, "bad field 'y' in '%s' (number expected, got %s)", name, luaL_typename(L, -1)); lua_pop(L, 1); lua_getfield(L, idx, "w"); rect.w = (float)lua_tonumberx(L, -1, &is_num); if (!is_num) luaL_error(L, "bad field 'w' in '%s' (number expected, got %s)", name, luaL_typename(L, -1)); lua_pop(L, 1); lua_getfield(L, idx, "h"); rect.h = (float)lua_tonumberx(L, -1, &is_num); if (!is_num) luaL_error(L, "bad field 'h' in '%s' (number expected, got %s)", name, luaL_typename(L, -1)); lua_pop(L, 1); return rect; } static Color table_to_color(lua_State *L, int idx) { Color color; lua_getfield(L, idx, "r"); color.r = (uint8_t)lua_tointeger(L, -1); lua_pop(L, 1); lua_getfield(L, idx, "g"); color.g = (uint8_t)lua_tointeger(L, -1); lua_pop(L, 1); lua_getfield(L, idx, "b"); color.b = (uint8_t)lua_tointeger(L, -1); lua_pop(L, 1); lua_getfield(L, idx, "a"); color.a = (uint8_t)lua_tointeger(L, -1); lua_pop(L, 1); return color; } /* sprite(data [table]) */ /* data should contain the following fields: */ /* * path [string] * rect [table] * texture_region [table], optional * color [table], optional * rotation [number], optional * flip_x [boolean], optional * flip_y [boolean], optional * stretch [boolean], optional */ static int b_sprite(lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); PushSpriteArgs args = { 0 }; lua_getfield(L, 1, "path"); const char *field_path = lua_tostring(L, -1); if (field_path == NULL) luaL_error(L, "bad field 'path' in 'data' (string expected, got %s)", luaL_typename(L, -1)); args.path = field_path; lua_getfield(L, 1, "rect"); if (!lua_istable(L, -1)) luaL_error(L, "bad field 'rect' in 'data' (table expected, got %s)", luaL_typename(L, -1)); args.rect = table_to_rect(L, -1, "data.rect"); lua_getfield(L, 1, "texture_region"); if (lua_istable(L, -1)) { args.texture_region_opt = table_to_rect(L, -1, "data.texture_region"); args.texture_region_opt_set = true; } lua_getfield(L, 1, "color"); if (lua_istable(L, -1)) { args.color_opt = table_to_color(L, -1); args.color_opt_set = true; } lua_getfield(L, 1, "rotation"); if (lua_isnumber(L, -1)) { args.rotation_opt = (float)lua_tonumber(L, -1); args.rotation_opt_set = true; } lua_getfield(L, 1, "flip_x"); if (lua_isboolean(L, -1)) { args.flip_x_opt = lua_toboolean(L, -1); args.flip_x_opt_set = true; } lua_getfield(L, 1, "flip_y"); if (lua_isboolean(L, -1)) { args.flip_y_opt = lua_toboolean(L, -1); args.flip_y_opt_set = true; } lua_getfield(L, 1, "stretch"); if (lua_isboolean(L, -1)) { args.stretch_opt = lua_toboolean(L, -1); args.stretch_opt_set = true; } push_sprite(args); return 0; } /* rectangle(data [table]) */ /* data should contain the following fields: */ /* * rect [table] * color [table], optional, defaults to white */ static int b_rectangle(lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); lua_getfield(L, 1, "rect"); if (!lua_istable(L, -1)) luaL_error(L, "bad field 'rect' in 'data' (table expected, got %s)", luaL_typename(L, -1)); Rect rect = table_to_rect(L, -1, "data.rect"); Color color = { 255, 255, 255, 255 }; lua_getfield(L, 1, "color"); if (lua_istable(L, -1)) color = table_to_color(L, -1); push_rectangle(rect, color); return 0; } /* text(data [table]) */ /* data should contain the following fields: */ /* * string [string] * position [table] * height_px [number], optional, defaults to 22 * color [table], optional, defaults to black * font_path [string] */ static int b_text(lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); lua_getfield(L, 1, "string"); const char *string = lua_tostring(L, -1); if (string == NULL) luaL_error(L, "bad field 'string' in 'data' (string expected, got %s)", luaL_typename(L, -1)); lua_getfield(L, 1, "position"); if (!lua_istable(L, -1)) luaL_error(L, "bad field 'position' in 'data' (table expected, got %s)", luaL_typename(L, -1)); lua_getfield(L, -1, "x"); float x = (float)lua_tonumber(L, -1); lua_getfield(L, -2, "y"); float y = (float)lua_tonumber(L, -1); lua_getfield(L, 1, "height_px"); int is_num; int height_px = (int)lua_tointegerx(L, -1, &is_num); if (!is_num) height_px = 22; lua_getfield(L, 1, "color"); Color color = { 0, 0, 0, 255 }; if (lua_istable(L, -1)) color = table_to_color(L, -1); lua_getfield(L, 1, "font_path"); const char *font_path = lua_tostring(L, -1); if (font_path == NULL) luaL_error(L, "bad field 'font_path' in 'data' (string expected, got %s)", luaL_typename(L, -1)); push_text(string, (Vec2) { x, y }, height_px, color, font_path); return 0; } void game_tick(void) { if (ctx.initialization_needed) { if (!ctx.udata) ctx.udata = ccalloc(1, sizeof (State)); State *state = ctx.udata; /* let's init lua */ /* state existed already */ if (state->L != NULL) { lua_close(state->L); } state->L = luaL_newstate(); /* fakey version of luaL_openlibs() that excludes file i/o */ { static const luaL_Reg loaded_libs[] = { { LUA_GNAME, luaopen_base }, { LUA_LOADLIBNAME, luaopen_package }, { LUA_COLIBNAME, luaopen_coroutine }, { LUA_TABLIBNAME, luaopen_table }, { LUA_OSLIBNAME, luaopen_os }, { LUA_STRLIBNAME, luaopen_string }, { LUA_MATHLIBNAME, luaopen_math }, { LUA_UTF8LIBNAME, luaopen_utf8 }, { LUA_DBLIBNAME, luaopen_debug }, { NULL, NULL }, }; for (const luaL_Reg *lib = loaded_libs; lib->func; ++lib) { luaL_requiref(state->L, lib->name, lib->func, true); lua_pop(state->L, 1); /* already stored, don't need the copy */ } } /* package.searchers = { physfs_loader } */ lua_getglobal(state->L, "package"); lua_newtable(state->L); lua_setfield(state->L, -2, "searchers"); lua_getfield(state->L, -1, "searchers"); lua_pushcfunction(state->L, physfs_loader); lua_seti(state->L, -2, 1); /* pop package, package.searchers */ lua_pop(state->L, 2); /* now finally get to running the code */ unsigned char *game_buf = NULL; int64_t game_buf_size = file_to_bytes("/scripts/game.lua", &game_buf); if (luaL_loadbuffer(state->L, (char *)game_buf, game_buf_size, "game.lua") == LUA_OK) { if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) { log_critical("%s", lua_tostring(state->L, -1)); lua_pop(state->L, 1); } } /* from this point we have access to everything defined in lua */ /* binding */ lua_register(state->L, "sprite", b_sprite); lua_register(state->L, "rectangle", b_rectangle); lua_register(state->L, "text", b_text); } State *state = ctx.udata; lua_getglobal(state->L, "game_tick"); if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) { log_critical("%s", lua_tostring(state->L, -1)); lua_pop(state->L, 1); } } void game_end(void) { State *state = ctx.udata; lua_close(state->L); free(state); }