312 lines
8.8 KiB
C
312 lines
8.8 KiB
C
|
#include "twn_game_api.h"
|
||
|
#include "state.h"
|
||
|
|
||
|
#include <lua.h>
|
||
|
#include <lualib.h>
|
||
|
#include <lauxlib.h>
|
||
|
|
||
|
#include <malloc.h>
|
||
|
|
||
|
|
||
|
/* 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);
|
||
|
}
|