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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf
*.bat text eol=crlf
2 changes: 2 additions & 0 deletions .github/release-extras/opengl3-compatibility.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@echo off
"%~dp0Rhythia.exe" --rendering-driver opengl3 %*
4 changes: 4 additions & 0 deletions .github/release-extras/opengl3-compatibility.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
printf '\033c\033]0;%s\a' Rhythia
base_path="$(dirname "$(realpath "$0")")"
exec "$base_path/Rhythia.x86_64" --rendering-driver opengl3 "$@"
57 changes: 57 additions & 0 deletions .github/scripts/install-godot.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# Installs the Godot .NET editor and export templates matching .github/godot_version.txt
# Exports GODOT (path to the editor binary) to $GITHUB_ENV when running in Actions
set -euo pipefail

VERSION=$(tr -d '[:space:]' < .github/godot_version.txt)
TAG="${VERSION}-stable"
BASE_URL="https://github.com/godotengine/godot/releases/download/${TAG}"
CACHE_DIR="${GODOT_CACHE:-$HOME/.cache/godot-downloads}"

case "$(uname -s)" in
Linux)
EDITOR_ZIP="Godot_v${TAG}_mono_linux_x86_64.zip"
TEMPLATE_DIR="$HOME/.local/share/godot/export_templates/${VERSION}.stable.mono"
GODOT="$CACHE_DIR/Godot_v${TAG}_mono_linux_x86_64/Godot_v${TAG}_mono_linux.x86_64"
;;
Darwin)
EDITOR_ZIP="Godot_v${TAG}_mono_macos.universal.zip"
TEMPLATE_DIR="$HOME/Library/Application Support/Godot/export_templates/${VERSION}.stable.mono"
GODOT="$CACHE_DIR/Godot_mono.app/Contents/MacOS/Godot"
;;
*)
echo "Unsupported OS: $(uname -s)" >&2
exit 1
;;
esac

TEMPLATES_TPZ="Godot_v${TAG}_mono_export_templates.tpz"

mkdir -p "$CACHE_DIR"

for file in "$EDITOR_ZIP" "$TEMPLATES_TPZ"; do
if [ ! -f "$CACHE_DIR/$file" ]; then
echo "Downloading $file"
curl -fsSL --retry 3 -o "$CACHE_DIR/$file" "$BASE_URL/$file"
fi
done

if [ ! -x "$GODOT" ]; then
unzip -q -o "$CACHE_DIR/$EDITOR_ZIP" -d "$CACHE_DIR"
fi

if [ ! -f "$TEMPLATE_DIR/version.txt" ]; then
rm -rf "$CACHE_DIR/templates"
unzip -q -o "$CACHE_DIR/$TEMPLATES_TPZ" -d "$CACHE_DIR"
mkdir -p "$TEMPLATE_DIR"
cp -R "$CACHE_DIR/templates/." "$TEMPLATE_DIR/"
rm -rf "$CACHE_DIR/templates"
fi

"$GODOT" --headless --version

if [ -n "${GITHUB_ENV:-}" ]; then
echo "GODOT=$GODOT" >> "$GITHUB_ENV"
else
echo "GODOT=$GODOT"
fi
100 changes: 100 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: Release

on:
release:
types: [published]
workflow_dispatch:

permissions:
contents: write

jobs:
export:
name: Export ${{ matrix.preset }}
strategy:
fail-fast: false
matrix:
include:
- preset: Windows Desktop
asset: Windows.zip
runner: ubuntu-latest
extras: opengl3-compatibility.bat
binary: Rhythia.exe
export: Rhythia.zip
- preset: Linux
asset: Linux.zip
runner: ubuntu-latest
extras: opengl3-compatibility.sh
binary: Rhythia.x86_64
export: Rhythia.x86_64.zip
# macOS must export on a Mac: the preset signs with Xcode codesign,
# Godot's built-in signer produces apps that are killed at launch
- preset: macOS
asset: macOS.zip
runner: macos-latest
extras: ""
binary: Rhythia.app/Contents/MacOS/Rhythia
export: Rhythia.zip
runs-on: ${{ matrix.runner }}
env:
GODOT_CACHE: ${{ github.workspace }}/../godot-cache
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # Checkout v4

- name: Setup
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # Dotnet 4.0.2
with:
dotnet-version: 10.0.x

- name: Cache Godot
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # Cache v4
with:
path: ${{ env.GODOT_CACHE }}
key: godot-${{ runner.os }}-${{ hashFiles('.github/godot_version.txt') }}

- name: Install Godot
run: .github/scripts/install-godot.sh

- name: Restore dependencies
run: dotnet restore --locked-mode

- name: Build
run: dotnet build --no-restore

- name: Import project
run: '"$GODOT" --headless --path . --import'

# Godot names the executable after the export file, so export under the binary's name and rename
- name: Export
run: |
mkdir -p build/export
"$GODOT" --headless --path . --export-release "${{ matrix.preset }}" "build/export/${{ matrix.export }}"
mv "build/export/${{ matrix.export }}" "build/${{ matrix.asset }}"

- name: Add launch scripts
if: matrix.extras != ''
working-directory: .github/release-extras
run: zip -X "../../build/${{ matrix.asset }}" ${{ matrix.extras }}

- name: Verify archive layout
run: unzip -Z1 "build/${{ matrix.asset }}" | grep -x "${{ matrix.binary }}" > /dev/null

- name: Verify macOS app launches
if: matrix.preset == 'macOS'
run: |
mkdir -p build/verify
ditto -x -k build/macOS.zip build/verify
codesign --verify --deep --strict build/verify/Rhythia.app
build/verify/Rhythia.app/Contents/MacOS/Rhythia --headless --version

- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # Upload Artifact v4
with:
name: ${{ matrix.asset }}
path: build/${{ matrix.asset }}
if-no-files-found: error

- name: Upload to release
if: github.event_name == 'release'
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload "${{ github.event.release.tag_name }}" "build/${{ matrix.asset }}" --clobber
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ Properties/
# Godot 4+ specific ignores
.godot/
.import/
export_presets.cfg

# Imported translations (automatically generated from CSV files)
*.translation
Expand All @@ -30,3 +29,6 @@ mono_crash.*.json
# Other
nuget.config
*.lscache

# Local export output
build/
52 changes: 51 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
- [Windows](#windows)
- [Linux](#linux)
- [Linux Troubleshooting](#linux-troubleshooting)
- [macOS](#macos)
- [macOS Troubleshooting](#macos-troubleshooting)
- [User Folder](#user-folder)
- [Getting Maps](#getting-maps)
- [Development](#development)
Expand Down Expand Up @@ -64,6 +66,7 @@ You can view all releases on the [Rewrite releases page](https://github.com/Rhyt
|---|---|
| **Windows** | [Download](https://github.com/Rhythia/Client/releases/latest/download/windows.zip) |
| **Linux** | [Download](https://github.com/Rhythia/Client/releases/latest/download/linux.zip) |
| **macOS** | [Download](https://github.com/Rhythia/Client/releases/latest/download/macOS.zip) |

You can view the Rewrite source code [here](https://github.com/Rhythia/Client).

Expand Down Expand Up @@ -138,6 +141,41 @@ sudo chmod +x Rhythia.x86_64

Still having issues? Visit the **#support** channel in our [Discord](https://discord.gg/rhythia).

### macOS

> [!NOTE]
> Requires macOS 11 (Big Sur) or later on Intel, or macOS 13 (Ventura) or later on Apple Silicon.

1. Download `macOS.zip` for your chosen client above.
2. Double-click the downloaded file to extract `Rhythia.app`.
3. Move `Rhythia.app` to your **Applications** folder.
4. Open `Rhythia.app`. The first launch will be blocked, see below.

#### Allowing Rhythia to Open

Rhythia is not signed with an Apple Developer ID, so macOS blocks it the first time you open it. You only need to do this once; in-game updates keep working afterwards.

**System Settings:**

1. Try to open `Rhythia.app` and close the warning that appears.
2. Open **System Settings** → **Privacy & Security**.
3. Scroll down to the message about Rhythia being blocked and click **Open Anyway**.
4. Confirm with your password, then open Rhythia again.

**Terminal:**

```bash
xattr -dr com.apple.quarantine /Applications/Rhythia.app
```

### macOS Troubleshooting

If macOS says the app **is damaged and can't be opened**, run the Terminal command above; it removes the download flag macOS adds to files from the internet.

If the game won't open at all, check that you are using a release newer than 0.2.0; the 0.2.0 `macOS.zip` and earlier builds were signed incorrectly and are killed by macOS on launch.

Still having issues? Visit the **#support** channel in our [Discord](https://discord.gg/rhythia).

---

## User Folder
Expand All @@ -148,6 +186,7 @@ Your maps, replays, skins, and settings are stored separately from the game inst
|---|---|
| **Windows** | `%appdata%\Rhythia` |
| **Linux** | `~/.local/share/Rhythia` |
| **macOS** | `~/Library/Application Support/Rhythia` |

You can also access the user folder from within the game via the **User Folder** button on the top bar.

Expand Down Expand Up @@ -210,7 +249,7 @@ We ❤️ developers! Contributions are welcome — whether it's bug fixes, feat
| Software | Notes |
|---|---|
| [Godot 4.7 Stable (.NET)](https://godotengine.org/download) | The C# / .NET build is required |
| [Git](https://git-scm.com/downloads) | Windows: Git for Windows · Linux: `git` package |
| [Git](https://git-scm.com/downloads) | Windows: Git for Windows · Linux: `git` package · macOS: `xcode-select --install` or `brew install git` |
| [GitHub Account](https://github.com/signup) | Needed to fork and open pull requests |

**Optional:** [Visual Studio Code](https://code.visualstudio.com/) with the [godot-tools](https://marketplace.visualstudio.com/items?itemName=geequlim.godot-tools) extension.
Expand Down Expand Up @@ -250,6 +289,17 @@ dotnet tool restore
3. Browse to your cloned repository folder and select `project.godot`.
4. Click **Open**.

#### 4. Exporting a Build (Optional)

Release builds are exported automatically by the [Release workflow](.github/workflows/release.yml) when a release is published. To export locally, install the matching [export templates](https://godotengine.org/download) and use **Project** → **Export**, or:

```bash
godot --headless --export-release "macOS" build/macOS/Rhythia.zip
```

> [!NOTE]
> The macOS preset signs with Apple's `codesign`, so macOS builds must be exported on a Mac.

### Submitting Your Changes

1. **Format** the project:
Expand Down
10 changes: 5 additions & 5 deletions export_presets.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ custom_features=""
export_filter="all_resources"
include_filter="user/*"
exclude_filter=""
export_path=""
export_path="build/macOS/Rhythia.zip"
patches=PackedStringArray()
patch_delta_encoding=false
patch_delta_compression_level_zstd=19
Expand All @@ -160,11 +160,11 @@ debug/export_console_wrapper=1
application/liquid_glass_icon=""
application/icon=""
application/icon_interpolation=4
application/bundle_identifier=""
application/bundle_identifier="wiki.rhythia.net"
application/signature=""
application/app_category="Games"
application/short_version=""
application/version=""
application/short_version="0.2.0"
application/version="0.2.0"
application/copyright=""
application/copyright_localized={}
application/min_macos_version_x86_64="11.00"
Expand All @@ -179,7 +179,7 @@ xcode/sdk_build="22C55"
xcode/sdk_name="macosx13.1"
xcode/xcode_version="1420"
xcode/xcode_build="14C18"
codesign/codesign=1
codesign/codesign=3
codesign/installer_identity=""
codesign/apple_team_id=""
codesign/identity=""
Expand Down
2 changes: 0 additions & 2 deletions scripts/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ public partial class Constants : Node
{
public static readonly ulong STARTED = Time.GetTicksUsec();

public static readonly string ROOT_FOLDER = Directory.GetCurrentDirectory();

public static readonly string DEFAULT_USER_FOLDER = OS.GetUserDataDir();

public static readonly string USER_FOLDER_POINTER = $"{DEFAULT_USER_FOLDER}/user_folder_path.txt";
Expand Down
5 changes: 2 additions & 3 deletions scripts/SettingsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,11 +269,10 @@ private static void restartGame()
OS.CreateProcess("powershell.exe", ["-Command", $"Wait-Process -Id {pid}; Start-Process '{executablePath}'"]);
break;
case "macOS":
string executablePathMac = Path.Combine(executablePath, "../..");
OS.CreateProcess("/bin/sh", [$"while kill -0 {pid} 2>/dev/null; do sleep 0.1; done; open {executablePathMac}"]);
OS.CreateProcess("/bin/sh", ["-c", $"{Platform.WaitForExitScript}; open {Platform.ShellQuote(Platform.MacAppBundlePath)}"]);
break;
case "Linux":
OS.CreateProcess("/bin/sh", [$"while kill -0 {pid} 2>/dev/null; do sleep 0.1; done; exec {executablePath}"]);
OS.CreateProcess("/bin/sh", ["-c", $"{Platform.WaitForExitScript}; exec {Platform.ShellQuote(executablePath)}"]);
break;
}

Expand Down
2 changes: 1 addition & 1 deletion scripts/ui/SettingsMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ public override void _Input(InputEvent @event)
switch (eventKey.Keycode)
{
case Key.O:
if (eventKey.CtrlPressed)
if (eventKey.IsCommandOrControlPressed())
{
ShowMenu(!Shown);
GetViewport().SetInputAsHandled();
Expand Down
2 changes: 1 addition & 1 deletion scripts/ui/menu/play/SearchPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public override void _Ready()

public override void _Input(InputEvent @event)
{
if (@event is InputEventKey eventKey && eventKey.Pressed && !eventKey.CtrlPressed && !eventKey.AltPressed)
if (@event is InputEventKey eventKey && eventKey.Pressed && !eventKey.CtrlPressed && !eventKey.MetaPressed && !eventKey.AltPressed)
{
Control focusOwner = GetViewport().GuiGetFocusOwner();

Expand Down
20 changes: 20 additions & 0 deletions scripts/util/Platform.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.IO;
using Godot;

public static class Platform
{
/// <summary>
/// Path to the running .app bundle on macOS, the executable lives at Rhythia.app/Contents/MacOS/Rhythia
/// </summary>
public static string MacAppBundlePath => Path.GetFullPath(Path.Combine(OS.GetExecutablePath(), "../../.."));

/// <summary>
/// Wraps a value in single quotes so it is passed to /bin/sh as one literal argument
/// </summary>
public static string ShellQuote(string value) => $"'{value.Replace("'", "'\\''")}'";

/// <summary>
/// Shell snippet that blocks until the current process has exited
/// </summary>
public static string WaitForExitScript => $"while kill -0 {OS.GetProcessId()} 2>/dev/null; do sleep 0.1; done";
}
1 change: 1 addition & 0 deletions scripts/util/Platform.cs.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://d0b88m1ycctvg
Loading
Loading