From 655ec946e63fcf2234f67a837cc7602099b6c301 Mon Sep 17 00:00:00 2001 From: "Emil A. Overbeck" Date: Fri, 17 Jul 2026 23:48:17 +0200 Subject: Better Steam path detection and build guide --- README.md | 40 ++++++++++++++++- meson.build | 112 +++++++++++++++++++++++++++++------------------- src/engine/renderer.cpp | 34 +++++++++++++-- 3 files changed, 137 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index a164756..9d43332 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,14 @@ content driven by Lua. Work in progress. See CLAUDE.md for details. ## Stack -C++20, Meson/Ninja (Podman), SDL3, Dear ImGui, a custom mass-spring physics -solver, Lua 5.4 with sol2, and stb_image. +C++20, Meson/Ninja, SDL3, Dear ImGui, a custom mass-spring physics solver, +Lua 5.4 with sol2, and stb_image. Box2D is linked but idle; bgfx, SoLoud, ENet +and EnTT are planned but not yet wired up. ## Build and run +### Container (reproducible, any distro) + ```sh podman build -t forts-clone-dev . podman run --rm -v "$PWD":/src:Z --userns=keep-id localhost/forts-clone-dev:latest \ @@ -17,6 +20,39 @@ podman run --rm -v "$PWD":/src:Z --userns=keep-id localhost/forts-clone-dev:late ./builddir/forts-clone # run from repo root ``` +The container builds every dependency into `/usr/local` and links Lua +statically, so the resulting binary runs on any host. You still run it on the +host (it needs the host's SDL3 and a display). + +### Local (Arch Linux) + +`meson.build` falls back to system packages, so a native build only needs the +libraries the code actually uses. bgfx, SoLoud, ENet and EnTT are part of the +planned stack but not yet wired up, so they are not required to build. + +```sh +# repo packages +sudo pacman -S --needed meson ninja gcc pkgconf sdl3 lua54 glm box2d + +# sol2 (header-only Lua bindings) from the AUR, e.g. with an AUR helper +paru -S sol2 # or: yay -S sol2 + +meson setup builddir +meson compile -C builddir +./builddir/forts-clone # run from repo root +``` + +Native builds link Lua dynamically against the system `lua54`, which is fine +when you build and run on the same machine. + +### Removing the container image + +```sh +podman rmi localhost/forts-clone-dev:latest # drop the dev image +podman image prune -f # drop dangling build layers +rm -rf builddir # meson dir, reconfigure after +``` + ## Assets The repo ships CC0 placeholders only. If you own Forts, real textures load at diff --git a/meson.build b/meson.build index 0f528a2..e063136 100644 --- a/meson.build +++ b/meson.build @@ -16,45 +16,75 @@ cpp = meson.get_compiler('cpp') # Dependencies (pre-built in /usr/local by Containerfile) # ============================================================================= -# SDL3 -- windowing + input (system package) +fs = import('fs') + +# A source-built /usr/local (what the container ships) provides the heavier +# libraries. On a stock system these paths are absent, so everything below falls +# back to system packages. include_directories() errors on a missing directory, +# hence the guard. +# +# NOTE: add /usr/local/include as a whole, never /usr/local/include/bx etc. +# individually. sys/types.h includes , which collides with bx's +# endian.h if -I.../include/bx is on the path. +local_incs = [] +if fs.is_dir('/usr/local/include') + local_incs += include_directories('/usr/local/include') +endif +usr_local_libdirs = ['/usr/local/lib64', '/usr/local/lib', '/usr/local/lib/static'] + +# --- Libraries the code actually compiles against --------------------------- + +# SDL3 -- windowing + input. System pkg-config on every target. sdl3_dep = dependency('sdl3', required: true) -# bgfx -- GPU rendering (built from source in Containerfile) -# CMake on Fedora installs to lib64 -bgfx_dep = cpp.find_library('bgfx', dirs: ['/usr/local/lib64', '/usr/local/lib']) -bimg_dep = cpp.find_library('bimg', dirs: ['/usr/local/lib64', '/usr/local/lib']) -bx_dep = cpp.find_library('bx', dirs: ['/usr/local/lib64', '/usr/local/lib']) - -# Box2D v3 -- physics engine (built from source in Containerfile) -box2d_dep = cpp.find_library('box2d', dirs: ['/usr/local/lib64', '/usr/local/lib']) - -# SoLoud -- audio engine (built from source in Containerfile) -soloud_dep = cpp.find_library('soloud', dirs: ['/usr/local/lib64', '/usr/local/lib']) - -# ENet -- networking (built from source in Containerfile) -enet_dep = cpp.find_library('enet', dirs: ['/usr/local/lib/static', '/usr/local/lib64', '/usr/local/lib']) - -# IMPORTANT: only add /usr/local/include, NOT /usr/local/include/bx etc. -# as individual paths. sys/types.h does #include and would -# collide with bx/endian.h if -I/usr/local/include/bx is in the path. -local_inc = include_directories('/usr/local/include') - -# Lua -- scripting. Linked STATICALLY from /usr/local/lib/liblua.a (built in the -# Containerfile) so the binary needs no liblua .so at runtime and runs on the -# host regardless of its distro's lua soname. Headers come from /usr/local/include. -lua_static = cpp.find_library('lua', dirs: ['/usr/local/lib'], static: true, required: true) -m_dep = cpp.find_library('m', required: false) -dl_dep = cpp.find_library('dl', required: false) -lua_dep = declare_dependency(dependencies: [lua_static, m_dep, dl_dep]) - -# glm -- math (system package) -glm_dep = dependency('glm', required: true) - -# sol2 -- Lua C++ bindings (header-only, installed to /usr/local/include) -sol2_dep = declare_dependency(include_directories: '/usr/local/include') - -# EnTT -- ECS (header-only, installed to /usr/local/include) -entt_dep = declare_dependency(include_directories: '/usr/local/include') +# glm -- header-only math. pkg-config where available, else rely on the include +# path (headers in /usr/include or the guarded /usr/local/include). +glm_dep = dependency('glm', required: false) +if not glm_dep.found() + glm_dep = declare_dependency() +endif + +# sol2 -- header-only Lua bindings. Headers resolve via the include path +# (/usr/include/sol system/AUR, or /usr/local/include/sol in the container). +sol2_dep = declare_dependency() + +# Lua 5.4. Container: a STATIC liblua.a in /usr/local/lib, so the binary carries +# Lua and runs on any host regardless of its liblua soname. Native build: the +# system Lua 5.4 (dynamic linking is fine when built and run on the same box). +lua_static = cpp.find_library('lua', dirs: ['/usr/local/lib'], static: true, required: false) +if lua_static.found() + m_dep = cpp.find_library('m', required: false) + dl_dep = cpp.find_library('dl', required: false) + lua_dep = declare_dependency(dependencies: [lua_static, m_dep, dl_dep]) +else + lua_dep = dependency('lua5.4', 'lua54', 'lua-5.4', required: false) + if not lua_dep.found() + lua_dep = dependency('lua', required: true) + endif +endif + +# Box2D v3 -- physics. Idle for now, but physics.cpp compiles against its header. +# Try /usr/local (container), then pkg-config/cmake, then a plain system lib. +box2d_dep = cpp.find_library('box2d', dirs: usr_local_libdirs, required: false) +if not box2d_dep.found() + box2d_dep = dependency('box2d', required: false) +endif +if not box2d_dep.found() + box2d_dep = cpp.find_library('box2d', required: true) +endif + +# --- Declared but not yet wired up ------------------------------------------ +# bgfx/SoLoud/ENet/EnTT belong to the planned stack, but no source file +# #includes them yet (the live renderer is SDL_Renderer; physics/audio/net are +# idle). Link them only if a source build has provided them; a stock system +# build simply skips them. Promote each to a real dependency when it is wired up. +optional_deps = [] +foreach libname : ['bgfx', 'bimg', 'bx', 'soloud', 'enet'] + found = cpp.find_library(libname, dirs: usr_local_libdirs, required: false) + if found.found() + optional_deps += found + endif +endforeach # ImGui -- skipped for M0, added when debug UI is needed @@ -96,18 +126,14 @@ engine_src = [ engine_deps = [ sdl3_dep, - bgfx_dep, bimg_dep, bx_dep, box2d_dep, - soloud_dep, - enet_dep, lua_dep, glm_dep, sol2_dep, - entt_dep, imgui_dep, -] +] + optional_deps -extra_inc = [local_inc, stb_inc] +extra_inc = local_incs + [stb_inc] # Required by bx/bgfx (normally set by cmake targets; we need them manually # since we link raw .a files). diff --git a/src/engine/renderer.cpp b/src/engine/renderer.cpp index 0be0e53..0ec6371 100644 --- a/src/engine/renderer.cpp +++ b/src/engine/renderer.cpp @@ -10,6 +10,35 @@ #include #include #include +#include +#include + +namespace { + +// Root of a bring-your-own Forts install, used to resolve "forts:REL" texture +// paths. Tries known developer install locations first, then the FORTS_DATA +// override, and returns the first that exists on disk. Resolved once, cached. +const std::string& forts_data_root() { + static const std::string root = [] { + std::vector candidates = { + "/opt/steam/steamapps/common/Forts/data", // Emil + "/mnt/games/SteamLibrary/steamapps/common/Forts/data", // Vaino + }; + if (const char* env = std::getenv("FORTS_DATA")) { + candidates.emplace_back(env); + } + for (const auto& c : candidates) { + std::error_code ec; + if (std::filesystem::is_directory(c, ec)) return c; + } + // None present; return the override if set, else the first guess, so the + // failed-load message at least points somewhere meaningful. + return candidates.back(); + }(); + return root; +} + +} // namespace bool Renderer::init(SDL_Window* window, int width, int height) { width_ = width; @@ -140,10 +169,7 @@ SDL_Texture* Renderer::load_texture(const std::string& path) { // files, like a Doom source port + WAD). Nothing is copied into the repo. std::string file = path; if (path.rfind("forts:", 0) == 0) { - const char* env = std::getenv("FORTS_DATA"); - std::string root = env ? env - : "/mnt/games/SteamLibrary/steamapps/common/Forts/data"; - file = root + "/" + path.substr(6); + file = forts_data_root() + "/" + path.substr(6); } int w = 0, h = 0, n = 0; -- cgit v1.3