Windows support
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -34,3 +34,4 @@ __pycache__/
|
|||||||
*.swp
|
*.swp
|
||||||
*~
|
*~
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
dist/
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ target_include_directories(enet PUBLIC ${enet_SOURCE_DIR}/include)
|
|||||||
|
|
||||||
# --- Shared game core (no graphics) ---------------------------------------------
|
# --- Shared game core (no graphics) ---------------------------------------------
|
||||||
add_library(bs_shared STATIC
|
add_library(bs_shared STATIC
|
||||||
shared/Appearance.cpp shared/Net.cpp shared/Protocol.cpp shared/Roster.cpp shared/Zone.cpp)
|
shared/Appearance.cpp shared/Net.cpp shared/Paths.cpp shared/Protocol.cpp shared/Roster.cpp shared/Zone.cpp)
|
||||||
target_include_directories(bs_shared PUBLIC shared)
|
target_include_directories(bs_shared PUBLIC shared)
|
||||||
target_link_libraries(bs_shared PUBLIC glm::glm nlohmann_json::nlohmann_json enet)
|
target_link_libraries(bs_shared PUBLIC glm::glm nlohmann_json::nlohmann_json enet)
|
||||||
target_compile_definitions(bs_shared PUBLIC GLM_FORCE_RADIANS GLM_ENABLE_EXPERIMENTAL
|
target_compile_definitions(bs_shared PUBLIC GLM_FORCE_RADIANS GLM_ENABLE_EXPERIMENTAL
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -56,8 +56,21 @@ ninja -C build # safari_client, safari_server, safari_bot
|
|||||||
|
|
||||||
Server only (no GPU needed): `cmake -S . -B build_srv -G Ninja -DBS_BUILD_CLIENT=OFF`.
|
Server only (no GPU needed): `cmake -S . -B build_srv -G Ninja -DBS_BUILD_CLIENT=OFF`.
|
||||||
|
|
||||||
Built files load assets from the source checkout (`BS_ASSET_DIR` and the engine's asset path are
|
Development builds load assets from the source checkout (`BS_ASSET_DIR` and the engine's asset
|
||||||
baked in at configure time), so run them where they were built.
|
path are baked in at configure time). A release package instead keeps them next to the
|
||||||
|
executables (`assets/` and `engine/`), which the programs check first.
|
||||||
|
|
||||||
|
### Windows release zip (cross-compiled from Linux)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo pacman -S mingw-w64-gcc zip # once
|
||||||
|
tools/package_windows.sh v0.1.0 # -> dist/BrandlingSafari-v0.1.0-win64.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Downloads the Windows builds of GLFW, libepoxy and SQLite from the MSYS2 repository (cached in
|
||||||
|
`~/.cache/brandling-safari`), builds in `build_win/` with `cmake/mingw-w64-x86_64.cmake`, and
|
||||||
|
zips the executables, their DLLs, the game and engine assets and a player `README.txt`.
|
||||||
|
Unzip anywhere and run `safari_client.exe`.
|
||||||
|
|
||||||
### Windows (MSYS2, recommended)
|
### Windows (MSYS2, recommended)
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "Appearance.h"
|
#include "Appearance.h"
|
||||||
#include "Movement.h"
|
#include "Movement.h"
|
||||||
#include "Net.h"
|
#include "Net.h"
|
||||||
|
#include "Paths.h"
|
||||||
#include "Protocol.h"
|
#include "Protocol.h"
|
||||||
#include "Rng.h"
|
#include "Rng.h"
|
||||||
#include "Zone.h"
|
#include "Zone.h"
|
||||||
@@ -621,7 +622,7 @@ int Bot::run() {
|
|||||||
}
|
}
|
||||||
rng_.reseed(hash32(token.c_str()));
|
rng_.reseed(hash32(token.c_str()));
|
||||||
std::string err;
|
std::string err;
|
||||||
if (!manifest_.load(std::string(BS_ASSET_DIR) + "/characters/manifest.json", &err)) {
|
if (!manifest_.load(gameAssetDir() + "/characters/manifest.json", &err)) {
|
||||||
std::fprintf(stderr, "manifest: %s\n", err.c_str());
|
std::fprintf(stderr, "manifest: %s\n", err.c_str());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ bool App::init() {
|
|||||||
if (capture_) ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouse;
|
if (capture_) ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouse;
|
||||||
|
|
||||||
if (!world_.init()) {
|
if (!world_.init()) {
|
||||||
HD2D_ERR("failed to load game assets from %s", BS_ASSET_DIR);
|
HD2D_ERR("failed to load game assets from %s", gameAssetDir().c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
world_.buildZone("meadow", 1); // title backdrop; rebuilt with the server's seed on join
|
world_.buildZone("meadow", 1); // title backdrop; rebuilt with the server's seed on join
|
||||||
|
|||||||
@@ -2,10 +2,21 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <filesystem>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace bs {
|
namespace bs {
|
||||||
|
|
||||||
|
const std::string& engineAssetDir() {
|
||||||
|
static const std::string dir = [] {
|
||||||
|
std::filesystem::path packaged = executableDir() / "engine";
|
||||||
|
std::error_code ec;
|
||||||
|
if (std::filesystem::exists(packaged / "shaders" / "common.glsl", ec)) return packaged.string();
|
||||||
|
return std::string(HD2D_ENGINE_ASSET_DIR);
|
||||||
|
}();
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
void UiTexture::upload(const hd2d::Image& img) {
|
void UiTexture::upload(const hd2d::Image& img) {
|
||||||
if (id_ && size_ != glm::ivec2(img.width, img.height)) reset();
|
if (id_ && size_ != glm::ivec2(img.width, img.height)) reset();
|
||||||
if (!id_) {
|
if (!id_) {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
// Small client-wide helpers: asset paths, UI textures, color conversion.
|
// Small client-wide helpers: asset paths, UI textures, color conversion.
|
||||||
|
|
||||||
#include "hd2d/GL.h"
|
#include "hd2d/GL.h"
|
||||||
|
#include "Paths.h"
|
||||||
|
|
||||||
#include "hd2d/Texture.h"
|
#include "hd2d/Texture.h"
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
@@ -11,17 +13,18 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#ifndef BS_ASSET_DIR
|
|
||||||
#define BS_ASSET_DIR "assets"
|
|
||||||
#endif
|
|
||||||
#ifndef HD2D_ENGINE_ASSET_DIR
|
#ifndef HD2D_ENGINE_ASSET_DIR
|
||||||
#define HD2D_ENGINE_ASSET_DIR "../Vibe-HD-2D/assets"
|
#define HD2D_ENGINE_ASSET_DIR "../Vibe-HD-2D/assets"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace bs {
|
namespace bs {
|
||||||
|
|
||||||
inline std::string gameAsset(const std::string& rel) { return std::string(BS_ASSET_DIR) + "/" + rel; }
|
// Game assets: `<exe dir>/assets` in a release package, else the source checkout (see Paths.h).
|
||||||
inline std::string engineAsset(const std::string& rel) { return std::string(HD2D_ENGINE_ASSET_DIR) + "/" + rel; }
|
inline std::string gameAsset(const std::string& rel) { return gameAssetDir() + "/" + rel; }
|
||||||
|
// Engine shaders, fonts, textures, models: `<exe dir>/engine` in a release package, else the
|
||||||
|
// engine checkout baked in by CMake.
|
||||||
|
const std::string& engineAssetDir();
|
||||||
|
inline std::string engineAsset(const std::string& rel) { return engineAssetDir() + "/" + rel; }
|
||||||
|
|
||||||
constexpr float kPixelsPerUnit = 32.0f; // world texel density shared by all pixel art
|
constexpr float kPixelsPerUnit = 32.0f; // world texel density shared by all pixel art
|
||||||
constexpr float kCreaturePixelsPerUnit = 40.0f; // creatures render a bit smaller than trainers
|
constexpr float kCreaturePixelsPerUnit = 40.0f; // creatures render a bit smaller than trainers
|
||||||
|
|||||||
@@ -28,34 +28,6 @@ namespace bs {
|
|||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
fs::path userDataDir() {
|
|
||||||
fs::path base;
|
|
||||||
#if defined(_WIN32)
|
|
||||||
if (const char* a = std::getenv("APPDATA"); a && *a) base = a;
|
|
||||||
#else
|
|
||||||
if (const char* x = std::getenv("XDG_DATA_HOME"); x && *x) base = x;
|
|
||||||
else if (const char* h = std::getenv("HOME"); h && *h) base = fs::path(h) / ".local/share";
|
|
||||||
#endif
|
|
||||||
if (base.empty()) base = ".";
|
|
||||||
fs::path d = base / "brandling-safari";
|
|
||||||
std::error_code ec;
|
|
||||||
fs::create_directories(d, ec);
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
fs::path executableDir() {
|
|
||||||
#if defined(_WIN32)
|
|
||||||
std::wstring buf(32768, L'\0');
|
|
||||||
DWORD n = GetModuleFileNameW(nullptr, buf.data(), DWORD(buf.size()));
|
|
||||||
buf.resize(n);
|
|
||||||
return fs::path(buf).parent_path();
|
|
||||||
#else
|
|
||||||
std::error_code ec;
|
|
||||||
fs::path exe = fs::read_symlink("/proc/self/exe", ec);
|
|
||||||
return ec ? fs::current_path() : exe.parent_path();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|||||||
@@ -4,18 +4,13 @@
|
|||||||
// server saves and exits cleanly on every platform (Windows has no SIGTERM),
|
// server saves and exits cleanly on every platform (Windows has no SIGTERM),
|
||||||
// and only kills it if it does not exit in time. Its output goes to a log file.
|
// and only kills it if it does not exit in time. Its output goes to a log file.
|
||||||
|
|
||||||
|
#include "Paths.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <filesystem>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace bs {
|
namespace bs {
|
||||||
|
|
||||||
// Per-user data folder: %APPDATA%\brandling-safari on Windows,
|
|
||||||
// $XDG_DATA_HOME/brandling-safari or ~/.local/share/brandling-safari elsewhere
|
|
||||||
// (the server uses the same folder for world.db). Created if missing.
|
|
||||||
std::filesystem::path userDataDir();
|
|
||||||
std::filesystem::path executableDir();
|
|
||||||
|
|
||||||
class LocalServer {
|
class LocalServer {
|
||||||
public:
|
public:
|
||||||
LocalServer() = default;
|
LocalServer() = default;
|
||||||
|
|||||||
18
cmake/mingw-w64-x86_64.cmake
Normal file
18
cmake/mingw-w64-x86_64.cmake
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Cross-compile for 64-bit Windows from Linux with mingw-w64 (UCRT).
|
||||||
|
# cmake -S . -B build_win -G Ninja -DCMAKE_TOOLCHAIN_FILE=cmake/mingw-w64-x86_64.cmake \
|
||||||
|
# -DMINGW_SYSROOT=<dir with include/ lib/ bin/ of MSYS2 ucrt64 packages>
|
||||||
|
# tools/package_windows.sh downloads that sysroot and runs this for you.
|
||||||
|
set(CMAKE_SYSTEM_NAME Windows)
|
||||||
|
set(CMAKE_SYSTEM_PROCESSOR x86_64)
|
||||||
|
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
|
||||||
|
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
|
||||||
|
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
|
||||||
|
|
||||||
|
set(MINGW_SYSROOT "" CACHE PATH "MSYS2 ucrt64 package tree (include/, lib/, bin/)")
|
||||||
|
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32 ${MINGW_SYSROOT})
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||||
|
# The host's pkg-config would report Linux libraries.
|
||||||
|
set(CMAKE_DISABLE_FIND_PACKAGE_PkgConfig ON)
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "Game.h"
|
#include "Game.h"
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include "Net.h"
|
#include "Net.h"
|
||||||
|
#include "Paths.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@@ -35,16 +36,7 @@ std::atomic<bool> gStop{false};
|
|||||||
|
|
||||||
void onSignal(int) { gStop = true; }
|
void onSignal(int) { gStop = true; }
|
||||||
|
|
||||||
std::string defaultDbPath() {
|
std::string defaultDbPath() { return (userDataDir() / "world.db").string(); }
|
||||||
std::string base;
|
|
||||||
if (const char* x = std::getenv("XDG_DATA_HOME"); x && *x) base = x;
|
|
||||||
#if defined(_WIN32)
|
|
||||||
else if (const char* a = std::getenv("APPDATA"); a && *a) base = a;
|
|
||||||
#endif
|
|
||||||
else if (const char* h = std::getenv("HOME"); h && *h) base = std::string(h) + "/.local/share";
|
|
||||||
else base = ".";
|
|
||||||
return base + "/brandling-safari/world.db";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Non-blocking stdin lines for the console. POSIX polls fd 0 from the tick loop (a reader thread
|
// Non-blocking stdin lines for the console. POSIX polls fd 0 from the tick loop (a reader thread
|
||||||
// blocked inside stdio would deadlock glibc's exit-time stream flush); Windows uses a detached
|
// blocked inside stdio would deadlock glibc's exit-time stream flush); Windows uses a detached
|
||||||
@@ -112,8 +104,8 @@ int main(int argc, char** argv) {
|
|||||||
uint16_t port = kDefaultPort;
|
uint16_t port = kDefaultPort;
|
||||||
GameConfig cfg;
|
GameConfig cfg;
|
||||||
std::string dbPath = defaultDbPath();
|
std::string dbPath = defaultDbPath();
|
||||||
std::string rosterPath = std::string(BS_ASSET_DIR) + "/creatures/roster.json";
|
std::string rosterPath = gameAssetDir() + "/creatures/roster.json";
|
||||||
std::string manifestPath = std::string(BS_ASSET_DIR) + "/characters/manifest.json";
|
std::string manifestPath = gameAssetDir() + "/characters/manifest.json";
|
||||||
bool seedGiven = false;
|
bool seedGiven = false;
|
||||||
|
|
||||||
for (int i = 1; i < argc; ++i) {
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
|||||||
61
shared/Paths.cpp
Normal file
61
shared/Paths.cpp
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#include "Paths.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef BS_ASSET_DIR
|
||||||
|
#define BS_ASSET_DIR "assets"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace bs {
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
fs::path executableDir() {
|
||||||
|
#if defined(_WIN32)
|
||||||
|
std::wstring buf(32768, L'\0');
|
||||||
|
DWORD n = GetModuleFileNameW(nullptr, buf.data(), DWORD(buf.size()));
|
||||||
|
buf.resize(n);
|
||||||
|
return fs::path(buf).parent_path();
|
||||||
|
#else
|
||||||
|
std::error_code ec;
|
||||||
|
fs::path exe = fs::read_symlink("/proc/self/exe", ec);
|
||||||
|
return ec ? fs::current_path() : exe.parent_path();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path userDataDir() {
|
||||||
|
fs::path base;
|
||||||
|
#if defined(_WIN32)
|
||||||
|
if (const char* a = std::getenv("APPDATA"); a && *a) base = a;
|
||||||
|
#else
|
||||||
|
if (const char* x = std::getenv("XDG_DATA_HOME"); x && *x) base = x;
|
||||||
|
else if (const char* h = std::getenv("HOME"); h && *h) base = fs::path(h) / ".local/share";
|
||||||
|
#endif
|
||||||
|
if (base.empty()) base = ".";
|
||||||
|
fs::path d = base / "brandling-safari";
|
||||||
|
std::error_code ec;
|
||||||
|
fs::create_directories(d, ec);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& gameAssetDir() {
|
||||||
|
static const std::string dir = [] {
|
||||||
|
fs::path packaged = executableDir() / "assets";
|
||||||
|
std::error_code ec;
|
||||||
|
if (fs::exists(packaged / "characters" / "manifest.json", ec)) return packaged.string();
|
||||||
|
return std::string(BS_ASSET_DIR);
|
||||||
|
}();
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace bs
|
||||||
21
shared/Paths.h
Normal file
21
shared/Paths.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
// Where things live on disk.
|
||||||
|
//
|
||||||
|
// Assets: a release package keeps them next to the executables
|
||||||
|
// (`<exe dir>/assets` for the game, `<exe dir>/engine` for the engine's shaders,
|
||||||
|
// fonts, textures and models); a development build falls back to the source
|
||||||
|
// checkout paths baked in by CMake (BS_ASSET_DIR / HD2D_ENGINE_ASSET_DIR).
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace bs {
|
||||||
|
|
||||||
|
std::filesystem::path executableDir();
|
||||||
|
// %APPDATA%\brandling-safari on Windows, $XDG_DATA_HOME/brandling-safari or
|
||||||
|
// ~/.local/share/brandling-safari elsewhere. Created if missing.
|
||||||
|
std::filesystem::path userDataDir();
|
||||||
|
// Game asset root (contains characters/manifest.json).
|
||||||
|
const std::string& gameAssetDir();
|
||||||
|
|
||||||
|
} // namespace bs
|
||||||
97
tools/package_windows.sh
Executable file
97
tools/package_windows.sh
Executable file
@@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Builds a self-contained Windows release zip from Linux:
|
||||||
|
# tools/package_windows.sh [version] -> dist/BrandlingSafari-<version>-win64.zip
|
||||||
|
#
|
||||||
|
# Needs: x86_64-w64-mingw32-g++ (Arch: mingw-w64-gcc), cmake, ninja, curl, zstd-capable tar, zip.
|
||||||
|
# The Windows libraries (GLFW, libepoxy, SQLite, ...) come from the MSYS2 ucrt64 repository and
|
||||||
|
# are cached in ~/.cache/brandling-safari/msys2-ucrt64. The engine checkout is ../Vibe-HD-2D
|
||||||
|
# (or $HD2D_ENGINE_DIR).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
ENGINE="${HD2D_ENGINE_DIR:-$ROOT/../Vibe-HD-2D}"
|
||||||
|
VERSION="${1:-$(git -C "$ROOT" describe --tags --always --dirty 2>/dev/null || echo dev)}"
|
||||||
|
NAME="BrandlingSafari-$VERSION-win64"
|
||||||
|
CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/brandling-safari/msys2-ucrt64"
|
||||||
|
SYSROOT="$CACHE/ucrt64"
|
||||||
|
BUILD="$ROOT/build_win"
|
||||||
|
STAGE="$ROOT/dist/$NAME"
|
||||||
|
REPO="https://repo.msys2.org/mingw/ucrt64"
|
||||||
|
PACKAGES=(glfw libepoxy sqlite3 nlohmann-json glm libwinpthread)
|
||||||
|
|
||||||
|
# --- 1. Windows libraries ------------------------------------------------------
|
||||||
|
mkdir -p "$CACHE"
|
||||||
|
if [[ ! -f "$CACHE/.done" ]]; then
|
||||||
|
echo "== downloading MSYS2 ucrt64 packages"
|
||||||
|
curl -fsSL "$REPO/" -o "$CACHE/index.html"
|
||||||
|
for p in "${PACKAGES[@]}"; do
|
||||||
|
f=$(grep -oE "mingw-w64-ucrt-x86_64-$p-[0-9][^\"]*-any\.pkg\.tar\.zst\"" "$CACHE/index.html" | tr -d '"' | sort -V | tail -1)
|
||||||
|
[[ -n "$f" ]] || { echo "package $p not found in $REPO" >&2; exit 1; }
|
||||||
|
echo " $f"
|
||||||
|
curl -fsSL "$REPO/$f" -o "$CACHE/$f"
|
||||||
|
tar --zstd -xf "$CACHE/$f" -C "$CACHE" 2>/dev/null
|
||||||
|
done
|
||||||
|
touch "$CACHE/.done"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 2. Build ------------------------------------------------------------------
|
||||||
|
echo "== building"
|
||||||
|
cmake -S "$ROOT" -B "$BUILD" -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE="$ROOT/cmake/mingw-w64-x86_64.cmake" -DMINGW_SYSROOT="$SYSROOT" \
|
||||||
|
-DHD2D_ENGINE_DIR="$ENGINE" > /dev/null
|
||||||
|
ninja -C "$BUILD" safari_client safari_server
|
||||||
|
|
||||||
|
# --- 3. Stage --------------------------------------------------------------------
|
||||||
|
echo "== staging $STAGE"
|
||||||
|
rm -rf "$STAGE"
|
||||||
|
mkdir -p "$STAGE/assets" "$STAGE/engine/textures" "$STAGE/engine/models"
|
||||||
|
cp "$BUILD/safari_client.exe" "$BUILD/safari_server.exe" "$STAGE/"
|
||||||
|
x86_64-w64-mingw32-strip "$STAGE"/*.exe
|
||||||
|
|
||||||
|
# Every non-system DLL the executables import (one level deep is enough: the MSYS2 DLLs
|
||||||
|
# used here only import Windows system DLLs).
|
||||||
|
for dll in $(x86_64-w64-mingw32-objdump -p "$STAGE"/*.exe | awk '/DLL Name/ {print $3}' | sort -u); do
|
||||||
|
for dir in "$SYSROOT/bin" /usr/x86_64-w64-mingw32/bin; do
|
||||||
|
if [[ -f "$dir/$dll" ]]; then cp "$dir/$dll" "$STAGE/"; break; fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# Game assets (no review sheets, no Blender texture sources: the .glb embeds them).
|
||||||
|
cp -r "$ROOT/assets/characters" "$ROOT/assets/data" "$STAGE/assets/"
|
||||||
|
mkdir -p "$STAGE/assets/creatures" "$STAGE/assets/models"
|
||||||
|
cp -r "$ROOT/assets/creatures/sprites" "$ROOT/assets/creatures/roster.json" "$STAGE/assets/creatures/"
|
||||||
|
cp "$ROOT/assets/models/safari.glb" "$STAGE/assets/models/"
|
||||||
|
# Engine assets the game uses.
|
||||||
|
cp -r "$ENGINE/assets/shaders" "$ENGINE/assets/fonts" "$STAGE/engine/"
|
||||||
|
cp "$ENGINE"/assets/textures/*.png "$STAGE/engine/textures/"
|
||||||
|
cp "$ENGINE/assets/models/scenery.glb" "$STAGE/engine/models/"
|
||||||
|
|
||||||
|
cat > "$STAGE/README.txt" <<EOF
|
||||||
|
Brandling Safari $VERSION (Windows, 64-bit)
|
||||||
|
|
||||||
|
PLAY
|
||||||
|
Double-click safari_client.exe.
|
||||||
|
- Host & Play: starts a game on this PC that friends on the same network can join.
|
||||||
|
Windows Firewall will ask about safari_server.exe the first time: allow it on
|
||||||
|
private networks (UDP ports 27420 and 27421).
|
||||||
|
- Join a LAN Game: lists games found on your network, or type the host's IP address.
|
||||||
|
Windows may show "Windows protected your PC" because the game is not code-signed:
|
||||||
|
click "More info" then "Run anyway".
|
||||||
|
|
||||||
|
CONTROLS
|
||||||
|
WASD / arrows move, Shift run, Space throw a Safari Capsule, F cast / reel a fishing line,
|
||||||
|
E talk to Professor Henry, Tab your Brandlings, Enter chat, Q/E or right-drag camera,
|
||||||
|
mouse wheel zoom, Esc menu, F12 screenshot.
|
||||||
|
|
||||||
|
DEDICATED SERVER
|
||||||
|
safari_server.exe --port 27420 --name "My Safari" (type "help" in its window)
|
||||||
|
|
||||||
|
Saves, your identity and server.log are in %APPDATA%\\brandling-safari.
|
||||||
|
Needs a GPU with OpenGL 4.5 (any GPU from the last ~10 years with current drivers).
|
||||||
|
EOF
|
||||||
|
sed -i 's/$/\r/' "$STAGE/README.txt"
|
||||||
|
|
||||||
|
# --- 4. Zip -------------------------------------------------------------------------
|
||||||
|
rm -f "$ROOT/dist/$NAME.zip"
|
||||||
|
(cd "$ROOT/dist" && zip -qr9 "$NAME.zip" "$NAME")
|
||||||
|
echo "== $(du -h "$ROOT/dist/$NAME.zip" | cut -f1) $ROOT/dist/$NAME.zip"
|
||||||
Reference in New Issue
Block a user