Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Experimental flags — copy to .env and edit as needed
show_sandbox_collection=false
alt_stakes=false

# Server overrides (take precedence over Multiplayer.jkr / config.lua)
# server_url=balatro.virtualized.dev
# server_port=8788
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
.DS_Store
.env
.claude/
dist/
CLAUDE.local.md
replays/*
!replays/.gitkeep
2 changes: 1 addition & 1 deletion Multiplayer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"priority": 10000000,
"badge_colour": "AC3232",
"badge_text_colour": "FFFFFF",
"version": "0.4.0~pre1-DEV",
"version": "0.4.0~pre3-DEV",
"dependencies": [
"Steamodded (>=1.0.0~BETA-1221a)",
"Lovely (>=0.8)",
Expand Down
20 changes: 15 additions & 5 deletions core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ MP.EXPERIMENTAL = {
mem_debug = true,
}

-- Override experimental flags from .env file if present
-- Override experimental flags and server config from .env file if present
MP.ENV = {}
local env_path = MP.path .. "/.env"
local env_info = NFS.getInfo(env_path)
if env_info then
Expand All @@ -78,17 +79,20 @@ if env_info then
line = line:match("^%s*(.-)%s*$") -- trim
if line ~= "" and not line:match("^#") then
local key, val = line:match("^([%w_]+)%s*=%s*(.+)$")
if key and MP.EXPERIMENTAL[key] ~= nil then
if key then
if val == "true" then
val = true
elseif val == "false" then
val = false
end
MP.EXPERIMENTAL[key] = val
MP.ENV[key] = val
if MP.EXPERIMENTAL[key] ~= nil then
MP.EXPERIMENTAL[key] = val
end
end
end
end
sendDebugMessage("Loaded .env overrides for MP.EXPERIMENTAL", "MULTIPLAYER")
sendDebugMessage("Loaded .env overrides", "MULTIPLAYER")
end
end

Expand Down Expand Up @@ -317,5 +321,11 @@ MP.load_mp_dir("objects/challenges")

local SOCKET = MP.load_mp_file("networking/socket.lua")
MP.NETWORKING_THREAD = love.thread.newThread(SOCKET)
MP.NETWORKING_THREAD:start(SMODS.Mods["Multiplayer"].config.server_url, SMODS.Mods["Multiplayer"].config.server_port)
local server_url = MP.ENV.server_url or SMODS.Mods["Multiplayer"].config.server_url
local server_port = tonumber(MP.ENV.server_port) or SMODS.Mods["Multiplayer"].config.server_port
sendInfoMessage(
string.format("Connecting to %s:%s", tostring(server_url), tostring(server_port)),
"MULTIPLAYER"
)
MP.NETWORKING_THREAD:start(server_url, server_port)
MP.ACTIONS.connect()
155 changes: 155 additions & 0 deletions scripts/release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#!/usr/bin/env bash
#
# release.sh — build the Multiplayer mod, in one of two modes:
#
# --dev Playtest/dev build. Source is the WORKING TREE (uncommitted edits
# included). Your real .env is baked in so
# the build talks to whatever server/port your .env points at and
# keeps the "-DEV" version (which triggers the in-game dev warning).
#
# --release Shippable build. Source is still the WORKING TREE for now
# (TODO: switch to a clean git tag/ref + GitHub runner), but:
# - .env is NOT shipped (build falls back to config.lua)
# - version is auto-stripped to a clean release string
# (drops ~preN and -DEV)
# - config.lua server port is forced to production
# - core.lua debug defaults (mem_debug) are turned off
#
# Output: dist/Multiplayer-v<version>/ (clean unzipped folder, both modes)
# dist/Multiplayer-v<version>.zip (--dev: versioned playtest zip)
# dist/BalatroMultiplayer.zip (--release: literal name BMM and
# balatromp.com require)
#
set -euo pipefail

# --- production server (used by --release to sanitize config.lua) ------------
PROD_SERVER_URL="balatro.virtualized.dev"
PROD_SERVER_PORT=8788

# --- mode (required) ---
MODE="${1:-}"
case "$MODE" in
--dev) MODE=dev ;;
--release) MODE=release ;;
*)
echo "usage: $(basename "$0") --dev | --release" >&2
exit 2
;;
esac

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"

# Portable in-place edit (BSD/macOS + GNU sed differ; perl is consistent).
inplace() { perl -0pi -e "$1" "$2"; }

# --- version (from manifest) ---------------------
VERSION="$(grep -m1 '"version"' Multiplayer.json \
| sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/')"

if [ "$MODE" = release ]; then
# Strip the pre-release/dev tail: "0.4.0~pre2-DEV" -> "0.4.0".
VERSION="${VERSION%%~*}" # drop everything from the first '~'
VERSION="${VERSION%-[Dd][Ee][Vv]}" # drop a trailing '-DEV' if no '~' was present
if printf '%s' "$VERSION" | grep -qi 'dev'; then
echo "!! refusing to make a release: version still looks like a dev build ('$VERSION')" >&2
exit 1
fi
fi

SAFE_VERSION="$(printf '%s' "$VERSION" | tr -c 'A-Za-z0-9._-' '_')"
NAME="Multiplayer-v${SAFE_VERSION}"

# Release artifacts MUST be named exactly "BalatroMultiplayer.zip" — BMM and
# balatromp.com look up that literal filename for every release (see
# .github/RELEASE_CHECKLIST.md). Dev builds stay versioned so playtest zips
# don't clobber each other.
if [ "$MODE" = release ]; then
ZIP_NAME="BalatroMultiplayer"
else
ZIP_NAME="$NAME"
fi

DIST="${ROOT}/dist"
STAGE="${DIST}/${NAME}"
ZIP="${DIST}/${ZIP_NAME}.zip"

echo "==> building ${NAME} (mode: ${MODE}, version: ${VERSION})"

rm -rf "$STAGE" "$ZIP"
mkdir -p "$STAGE"

# --- enumerate the files that should ship -----------------------------------
# The file list comes from git, not from a raw filesystem walk, so the build
# only ever contains things that actually belong to the repo

# local dev folders, editor/tooling junk, scratch files,
# and anything matched by .gitignore are excluded
#
# --dev: tracked files + new (untracked, non-ignored) files
# --release: tracked files only — a clean, repo-faithful set
#
# -a archive. In --dev we also pass -L to follow symlinks (some maintainers
# symlink their .env to its real contents); --release omits .env entirely.
RSYNC_FLAGS=(-a)
GIT_LS=(git ls-files -z)
if [ "$MODE" = dev ]; then
RSYNC_FLAGS+=(-L)
GIT_LS+=(--cached --others --exclude-standard)
else
GIT_LS+=(--cached)
fi

# Build the NUL-delimited file list, then pipe it straight into rsync
# .env is gitignored on purpose (git never lists it), so
# append it by hand for dev builds
{
"${GIT_LS[@]}" -- \
':(exclude).github' \
':(exclude).gitignore' \
':(exclude)stylua.toml' \
':(exclude)agents.md' \
':(exclude)CONTRIBUTING.md' \
':(exclude)tests' \
':(exclude)scripts' \
':(exclude).claude'
if [ "$MODE" = dev ] && [ -e .env ]; then
printf '%s\0' .env
fi
} | rsync "${RSYNC_FLAGS[@]}" --from0 --files-from=- ./ "$STAGE/"

# we love macOS here
find "$STAGE" -name '.DS_Store' -delete

# --- sanitize the release copy ---------------------------------
if [ "$MODE" = release ]; then
# Clean version into the shipped manifest (no ~preN / -DEV -> no dev warning).
inplace "s/(\"version\"\\s*:\\s*\")[^\"]+\"/\${1}${VERSION}\"/" "$STAGE/Multiplayer.json"
# Point config.lua at the production server (working tree may hold a dev port).
inplace "s/(\\[\"server_url\"\\]\\s*=\\s*\")[^\"]*\"/\${1}${PROD_SERVER_URL}\"/" "$STAGE/config.lua"
inplace "s/(\\[\"server_port\"\\]\\s*=\\s*)\\d+/\${1}${PROD_SERVER_PORT}/" "$STAGE/config.lua"
fi

# --- sanity: make sure the bits that MUST (and must NOT) ship are present ----
for required in Multiplayer.json core.lua; do
if [ ! -e "${STAGE}/${required}" ]; then
echo "!! WARNING: expected '${required}' missing from build" >&2
fi
done

if [ "$MODE" = dev ]; then
[ -e "${STAGE}/.env" ] || echo "!! WARNING: dev build but '.env' missing" >&2
else
[ -e "${STAGE}/.env" ] && echo "!! WARNING: release build is shipping a '.env' — it should not" >&2
[ -e "${STAGE}/.env.example" ] || echo "!! WARNING: release build missing '.env.example'" >&2
fi

# --- zip it -----------------------------------------------------------------
# from INSIDE the stage so the mod files land at the archive root (no outer
# "Multiplayer-vX/" wrapper). BMM and balatromp.com expect Multiplayer.json at
# the zip root — see .github/RELEASE_CHECKLIST.md
( cd "$STAGE" && zip -rqX "../${ZIP_NAME}.zip" . )

echo "==> folder: ${STAGE}"
echo "==> zip: ${ZIP}"
echo "==> done."