// (C) Copyright 2025 Arslaan Pathan - xminecrafterfun@gmail.com // Showdown of the Sticks #include "lua/lua.hpp" #include #include #include #include #include #include // Prevents SDL from shitting the Windows cross-compiler and causing a "WinMain" error // At least that's what I think - I'm not sure what this does but if I remove it the code won't work // If it ain't broke don't fix it #define SDL_MAIN_HANDLED #include #include #include #include #include #ifdef __APPLE__ #include // macOS only #endif void fix_working_directory() { #ifdef __APPLE__ char path[1024]; uint32_t size = sizeof(path); if (_NSGetExecutablePath(path, &size) == 0) { std::filesystem::path exePath = std::filesystem::canonical(path); std::filesystem::path resPath = exePath.parent_path().parent_path() / "Resources"; std::filesystem::current_path(resPath); } #endif } #define WIDTH 1280 #define HEIGHT 720 #define KEYSYM event.key.keysym.sym SDL_Window *window = nullptr; SDL_Renderer *renderer = nullptr; SDL_Event event; TTF_Font *globalFont = nullptr; bool running = true; // Lua lua_State *L; // Colors SDL_Color veryDarkGrey = {30, 30, 47, 255}; const char *globalFontFile = "assets/fonts/OpenSans-Regular.ttf"; // Forward declarations int l_keycode_from_string(lua_State *L); int l_set_background_image(lua_State *L); int l_queue_texture_for_render(lua_State *L); int l_unqueue_all_textures(lua_State *L); int l_queue_button_for_render(lua_State *L); int l_queue_text_for_render(lua_State *L); int l_get_text_width(lua_State *L); int l_quit_game(lua_State* L); struct QueuedTexture { SDL_Texture *texture; int x; int y; }; struct QueuedButton { SDL_Rect rect; std::string text; std::string callback; }; struct QueuedText { std::string text; std::string font_file; int x; int y; int font_size; SDL_Color color = {255, 255, 255, 255}; }; struct QueuedRect { SDL_Rect rect; SDL_Color color; }; std::vector textureList = {}; std::map textureCache; std::vector buttonList = {}; std::vector textList = {}; std::map, TTF_Font *> textCache; std::vector rectList = {}; SDL_Texture *background_texture = nullptr; int l_keycode_from_string(lua_State *L) { const char *keystr = luaL_checkstring(L, 1); SDL_Scancode sc = SDL_GetScancodeFromName(keystr); if (sc == SDL_SCANCODE_UNKNOWN) { lua_pushinteger(L, -1); } else { lua_pushinteger(L, sc + 1); } return 1; } int l_queue_button_for_render(lua_State *L) { const char *text = luaL_checkstring(L, 1); int x = luaL_checkinteger(L, 2); int y = luaL_checkinteger(L, 3); int w = luaL_checkinteger(L, 4); int h = luaL_checkinteger(L, 5); const char *callback = luaL_checkstring(L, 6); SDL_Rect rect = {x, y, w, h}; buttonList.push_back({rect, text, callback}); return 0; } int l_queue_rect_for_render(lua_State* L) { int x = luaL_checkinteger(L, 1); int y = luaL_checkinteger(L, 2); int w = luaL_checkinteger(L, 3); int h = luaL_checkinteger(L, 4); Uint8 r = luaL_checkinteger(L, 5); Uint8 g = luaL_checkinteger(L, 6); Uint8 b = luaL_checkinteger(L, 7); Uint8 a = luaL_checkinteger(L, 8); SDL_Rect rect = {x, y, w, h}; SDL_Color color = {r, g, b, a}; rectList.push_back({rect, color}); return 0; } int l_queue_text_for_render(lua_State *L) { std::string text = (std::string)luaL_checkstring(L, 1); std::string font_file = (std::string)luaL_checkstring(L, 2); int x = luaL_checkinteger(L, 3); int y = luaL_checkinteger(L, 4); int font_size = luaL_checkinteger(L, 5); Uint8 r = luaL_checkinteger(L, 6); Uint8 g = luaL_checkinteger(L, 7); Uint8 b = luaL_checkinteger(L, 8); Uint8 a = luaL_checkinteger(L, 9); SDL_Color color = {r, g, b, a}; textList.push_back({text, font_file, x, y, font_size, color}); return 0; } bool lua_checkboolean(lua_State *L, int index) { if (!lua_isboolean(L, index)) { luaL_error(L, "Expected boolean at argument %d", index); return false; } return lua_toboolean(L, index); } int l_queue_texture_for_render(lua_State *L) { fix_working_directory(); const char *path = luaL_checkstring(L, 1); int x = luaL_checkinteger(L, 2); int y = luaL_checkinteger(L, 3); SDL_Texture *texture = nullptr; // Check cache first auto it = textureCache.find(path); if (it != textureCache.end()) { texture = it->second; } else { SDL_Surface *surface = IMG_Load(path); if (!surface) { std::cerr << "Failed to load image in queueTextureForRender: " << IMG_GetError() << std::endl; return 0; } texture = SDL_CreateTextureFromSurface(renderer, surface); SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); SDL_FreeSurface(surface); if (!texture) { std::cerr << "Failed to create texture in queueTextureForRender: " << SDL_GetError() << std::endl; return 0; } textureCache[path] = texture; // Cache it } textureList.push_back({texture, x, y}); return 0; } int l_unqueue_all_textures(lua_State *L) { textureList.clear(); return 0; } int l_set_background_image(lua_State *L) { fix_working_directory(); const char *bgImagePath = luaL_checkstring(L, 1); SDL_Surface *temp_surface = IMG_Load(bgImagePath); if (!temp_surface) { std::cerr << "Failed to load image: " << IMG_GetError() << std::endl; return 0; } if (background_texture) { SDL_DestroyTexture(background_texture); } background_texture = SDL_CreateTextureFromSurface(renderer, temp_surface); SDL_SetTextureBlendMode(background_texture, SDL_BLENDMODE_BLEND); SDL_FreeSurface(temp_surface); if (!background_texture) { std::cerr << "Failed to create texture: " << SDL_GetError() << std::endl; } return 0; } int l_get_text_width(lua_State *L) { const char *font_file = luaL_checkstring(L, 1); int font_size = luaL_checkinteger(L, 2); const char *text = luaL_checkstring(L, 3); std::pair key = {font_file, font_size}; TTF_Font *font; auto it = textCache.find(key); if (it != textCache.end()) { font = it->second; } else { font = TTF_OpenFont(font_file, font_size); if (!font) { std::cerr << "Failed to load font: " << TTF_GetError() << std::endl; lua_pushinteger(L, -1); return 1; } textCache[key] = font; } if (!font) { lua_pushinteger(L, -1); return 1; } int w = 0; TTF_SizeText(font, text, &w, nullptr); lua_pushinteger(L, w); return 1; } int l_quit_game(lua_State* L) { running = false; return 0; } void push_keys_to_lua(lua_State *L, bool keys[SDL_NUM_SCANCODES]) { lua_newtable(L); // create a new table on the stack for (int i = 0; i < SDL_NUM_SCANCODES; i++) { lua_pushinteger(L, i + 1); lua_pushboolean(L, keys[i]); lua_settable(L, -3); } } void call_lua_function(lua_State *L, const char *func_name) { lua_getglobal(L, func_name); if (lua_isfunction(L, -1)) { if (lua_pcall(L, 0, 0, 0) != LUA_OK) { std::cerr << "Lua Error in " << func_name << ": " << lua_tostring(L, -1) << std::endl; lua_pop(L, 1); } } else { lua_pop(L, 1); } } void expose_c_functions() { lua_register(L, "getKeycodeByName", l_keycode_from_string); lua_register(L, "setBgImage", l_set_background_image); lua_register(L, "queueTextureForRender", l_queue_texture_for_render); lua_register(L, "unqueueAllTextures", l_unqueue_all_textures); lua_register(L, "queueButtonForRender", l_queue_button_for_render); lua_register(L, "queueTextForRender", l_queue_text_for_render); lua_register(L, "getTextWidth", l_get_text_width); lua_register(L, "quitGame", l_quit_game); lua_register(L, "queueRectForRender", l_queue_rect_for_render); } int main() { fix_working_directory(); L = luaL_newstate(); expose_c_functions(); luaL_openlibs(L); char cwd[1024]; luaL_dofile(L, "assets/scripts/mainMenu.lua"); bool keys[SDL_NUM_SCANCODES] = {false}; push_keys_to_lua(L, keys); lua_setglobal(L, "keys"); lua_pushinteger(L, WIDTH); lua_setglobal(L, "WIDTH"); lua_pushinteger(L, HEIGHT); lua_setglobal(L, "HEIGHT"); SDL_Init(SDL_INIT_EVERYTHING); SDL_CreateWindowAndRenderer(WIDTH, HEIGHT, 0, &window, &renderer); SDL_SetWindowTitle(window, "Showdown of the Sticks"); SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); SDL_RenderSetLogicalSize(renderer, WIDTH, HEIGHT); SDL_RenderSetIntegerScale(renderer, SDL_FALSE); SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best"); SDL_SetWindowResizable(window, SDL_TRUE); SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); if (TTF_Init() == -1) { std::cerr << "TTF_Init failed: " << TTF_GetError() << std::endl; return 1; } globalFont = TTF_OpenFont(globalFontFile, 24); if (!globalFont) { std::cerr << "Failed to load font: " << TTF_GetError() << std::endl; return 1; } if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) { std::cerr << "Failed to initialize SDL_image: " << IMG_GetError() << std::endl; return 1; } call_lua_function(L, "Setup"); while (running) { push_keys_to_lua(L, keys); lua_setglobal(L, "keys"); call_lua_function(L, "Update"); while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { running = false; } if (event.type == SDL_KEYDOWN) { keys[event.key.keysym.scancode] = true; } if (event.type == SDL_KEYUP) { keys[event.key.keysym.scancode] = false; } if (event.type == SDL_MOUSEBUTTONDOWN) { int x = event.button.x; int y = event.button.y; std::cout << "Mouse clicked at: " << x << ", " << y << std::endl; for (const auto &button : buttonList) { if (x > button.rect.x && x < button.rect.x + button.rect.w && y > button.rect.y && y < button.rect.y + button.rect.h) { std::cout << "Clicked button: " << button.text << " (Callback: " << button.callback << ")" << std::endl; lua_getglobal(L, button.callback.c_str()); if (lua_isfunction(L, -1)) { if (lua_pcall(L, 0, 0, 0) != LUA_OK) { std::cerr << "Lua Error in button callback: " << lua_tostring(L, -1) << std::endl; lua_pop(L, 1); } } else { lua_pop(L, 1); } } } } } // Clear screen SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); // Background rendering SDL_RenderCopy(renderer, background_texture, nullptr, nullptr); // UI rendering for (const auto &tex : textureList) { SDL_Rect dst; dst.x = tex.x; dst.y = tex.y; SDL_QueryTexture(tex.texture, nullptr, nullptr, &dst.w, &dst.h); SDL_RenderCopy(renderer, tex.texture, nullptr, &dst); } textureList.clear(); for (const auto &button : buttonList) { SDL_SetRenderDrawColor(renderer, 252, 210, 77, 255); SDL_RenderFillRect(renderer, &button.rect); if (globalFont) { SDL_Surface *textSurface = TTF_RenderText_Blended(globalFont, button.text.c_str(), veryDarkGrey); if (textSurface) { SDL_Texture *textTexture = SDL_CreateTextureFromSurface(renderer, textSurface); SDL_Rect textRect; textRect.w = textSurface->w; textRect.h = textSurface->h; textRect.x = button.rect.x + (button.rect.w - textRect.w) / 2; textRect.y = button.rect.y + (button.rect.h - textRect.h) / 2; SDL_FreeSurface(textSurface); if (textTexture) { SDL_RenderCopy(renderer, textTexture, nullptr, &textRect); SDL_DestroyTexture(textTexture); } } } } buttonList.clear(); for (const auto& rect: rectList) { SDL_SetRenderDrawColor(renderer, rect.color.r, rect.color.g, rect.color.b, rect.color.a); SDL_RenderFillRect(renderer, &rect.rect); } rectList.clear(); for (const auto &text : textList) { std::pair key = {text.font_file, text.font_size}; TTF_Font *font; auto it = textCache.find(key); if (it != textCache.end()) { font = it->second; } else { font = TTF_OpenFont(text.font_file.c_str(), text.font_size); if (!font) { std::cerr << "Failed to load font: " << TTF_GetError() << std::endl; break; } textCache[key] = font; } SDL_Surface *textSurface = TTF_RenderText_Blended(font, text.text.c_str(), text.color); if (textSurface) { SDL_Texture *textTexture = SDL_CreateTextureFromSurface(renderer, textSurface); SDL_Rect textRect; textRect.w = textSurface->w; textRect.h = textSurface->h; textRect.x = text.x; textRect.y = text.y; SDL_FreeSurface(textSurface); if (textTexture) { SDL_RenderCopy(renderer, textTexture, nullptr, &textRect); SDL_DestroyTexture(textTexture); } } } textList.clear(); SDL_RenderPresent(renderer); SDL_Delay(10); } lua_close(L); if (background_texture) { SDL_DestroyTexture(background_texture); } for (auto &[_, tex] : textureCache) { SDL_DestroyTexture(tex); } for (auto &[_, fnt] : textCache) { TTF_CloseFont(fnt); } textureCache.clear(); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); IMG_Quit(); TTF_Quit(); Mix_Quit(); SDL_Quit(); return 0; }