Skip to content

feat(multiplayer)#1579

Draft
icgnos wants to merge 14 commits into
mainfrom
feat/multiplayer
Draft

feat(multiplayer)#1579
icgnos wants to merge 14 commits into
mainfrom
feat/multiplayer

Conversation

@icgnos

@icgnos icgnos commented May 1, 2026

Copy link
Copy Markdown
Collaborator

Checklist

  • Changes have been tested locally and work as expected.
  • All tests in workflows pass successfully.
  • Documentation has been updated if necessary.
  • Code formatting and commit messages align with the project's conventions.
  • Comments have been added for any complex logic or functionality if possible.

This PR is a ..

  • 🆕 New feature
  • 🐞 Bug fix
  • 🛠 Refactoring
  • ⚡️ Performance improvement
  • 🌐 Internationalization
  • 📄 Documentation improvement
  • 🎨 Code style optimization
  • ❓ Other (Please specify below)

Related Issues

Description

  • 新增了基于 Terracotta 的联机功能,支持以房主身份创建房间和以房客身份通过邀请码加入房间。

Additional Context

  • 已经在 Windows 11 测试了所有功能(下载联机核心、创建房间、加入房间、连接断开重连),macOS/Linux 未测试
  • 前端完全由 AI 生成,代码质量无法保证。
  • UI 很差,需要重新设计

Summary by Sourcery

Add Terracotta-based multiplayer support with a new launcher UI entry point and Tauri backend integration for downloading, launching, and managing the external multiplayer core.

New Features:

  • Introduce a multiplayer modal flow for creating and joining Terracotta rooms, including invite code handling, guest/host states, and error display.
  • Add a dedicated multiplayer button to the launch page UI styled separately from the main launch button.
  • Provide a frontend MultiplayerService for platform checks and invoking Tauri multiplayer commands.
  • Implement Tauri-side multiplayer helpers and commands to download Terracotta from prioritized mirrors, decompress and install it, launch the executable, and detect its communication port.
  • Extend resource handling to support a new Terracotta resource type and associated download endpoints.

Enhancements:

  • Filter additional noisy debug logs from reqwest connector output in the logging setup.
  • Expose CSS modules in the TypeScript global declarations to satisfy new imports.
  • Extend the shared modals infrastructure to register and host the new multiplayer modal.

Build:

  • Add flate2 and tar dependencies required for handling Terracotta archive downloads in the Tauri backend.

icgnos and others added 12 commits March 29, 2026 16:25
	modified:   src-tauri/src/multiplayer/helpers/terracotta.rs
	new file:   src-tauri/target-codex-checkkzyTw1/CACHEDIR.TAG
	new file:   src/components/modals/multiplayer-modal.tsx
	modified:   src/components/special/shared-modals-provider.tsx
	modified:   src/global.d.ts
	modified:   src/locales/en.json
	modified:   src/locales/zh-Hans.json
	modified:   src/pages/launch.tsx
	new file:   src/services/multiplayer.ts
	modified:   src/styles/launch.module.css
	new file:   target-codex-checkC3WPzf/CACHEDIR.TAG
	modified:   src-tauri/src/multiplayer/helpers/terracotta.rs
	modified:   src/components/modals/multiplayer-modal.tsx
	modified:   src/locales/en.json
	modified:   src/locales/zh-Hans.json
	modified:   src/services/multiplayer.ts
@github-actions github-actions Bot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label May 1, 2026
@sourcery-ai

sourcery-ai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Introduce a Terracotta-based multiplayer feature, including frontend UI to host/join rooms, a new multiplayer modal and service, and backend Tauri commands and resource plumbing to download, unpack, and launch the Terracotta binary with task monitoring.

Sequence diagram for hosting a Terracotta multiplayer room

sequenceDiagram
  actor User
  participant LaunchPage
  participant MultiplayerModal
  participant MultiplayerService
  participant TauriBackend
  participant TaskMonitor
  participant TerracottaBinary

  User->>LaunchPage: Click multiplayer button
  LaunchPage->>MultiplayerService: checkPlatformSupport()
  MultiplayerService-->>LaunchPage: InvokeResponse<boolean>
  LaunchPage->>MultiplayerModal: openSharedModal(multiplayer)

  Note over MultiplayerModal,TauriBackend: Core not installed path
  MultiplayerModal->>MultiplayerService: downloadTerracotta()
  MultiplayerService->>TauriBackend: invoke download_terracotta
  TauriBackend->>TauriBackend: build_download_param()
  TauriBackend->>TaskMonitor: schedule_progressive_task_group("terracotta")
  TaskMonitor-->>TauriBackend: task_group_id
  TauriBackend->>TaskMonitor: wait_for_task_group(task_group_id)
  TaskMonitor-->>TauriBackend: Completed or error
  TauriBackend->>TauriBackend: decompress() terracotta archive
  TauriBackend-->>MultiplayerService: success
  MultiplayerService-->>MultiplayerModal: success

  Note over MultiplayerModal,TerracottaBinary: Host creates room
  User->>MultiplayerModal: Click create room
  MultiplayerModal->>MultiplayerService: launchTerracotta()
  MultiplayerService->>TauriBackend: invoke launch_terracotta
  TauriBackend->>TerracottaBinary: spawn --hmcl <temp_path>
  TauriBackend-->>MultiplayerService: success
  MultiplayerService-->>MultiplayerModal: success

  TerracottaBinary->>TauriBackend: write port file sjmcl-terracotta
  MultiplayerModal->>MultiplayerService: fetchPort()
  MultiplayerService->>TauriBackend: invoke fetch_port
  TauriBackend-->>MultiplayerService: port
  MultiplayerService-->>MultiplayerModal: port

  loop Poll Terracotta state
    MultiplayerModal->>TerracottaBinary: HTTP GET /state
    TerracottaBinary-->>MultiplayerModal: JSON { room, profiles, state }
  end

  MultiplayerModal-->>User: Show invite code and joined profiles
Loading

Class diagram for multiplayer-related frontend and backend types

classDiagram
  class MultiplayerService {
    <<TypeScript_class>>
    +static checkPlatformSupport() InvokeResponse~boolean~
    +static checkTerracotta() InvokeResponse~boolean~
    +static launchTerracotta() InvokeResponse~void~
    +static downloadTerracotta() InvokeResponse~void~
    +static fetchPort() InvokeResponse~number~
  }

  class MultiplayerModal {
    <<React_Component>>
    -Phase phase
    -number port
    -string generatedInviteCode
    -boolean isDownloading
    -number errorType
    -string difficulty
    -Profile[] profiles
    -boolean isJoinDialogOpen
    -string joinCode
    -boolean isJoining
    +handleCreateRoom()
    +handleDownloadTerracotta()
    +handleJoinRoomConfirm()
    +handleReconnect()
    +handleReturnToLobby()
    +handleCopyInviteCode()
  }

  class MultiplayerActionButton {
    <<React_Component>>
    +icon IconType
    +imageSrc string
    +title string
  }

  class Phase {
    <<TypeScript_union>>
    checking
    notDownloaded
    ready
    scanning
    roomStarted
    guestStarting
    guestOk
    error
    disconnected
  }

  class MultiplayerError {
    <<Rust_enum>>
    +ExecutableNotFound
    +PortFileNotFound
    +CompressedFileNotFound
  }

  class ResourceType {
    <<Rust_enum>>
    +Terracotta
    ..existing_variants..
  }

  class TerracottaHelpers {
    <<Rust_module>>
    +build_download_param(app AppHandle) SJMCLResult~Vec~PTaskParam~~
    +decompress(app AppHandle) SJMCLResult~()~
  }

  class MultiplayerCommands {
    <<Rust_module>>
    +check_terracotta(app AppHandle) SJMCLResult~bool~
    +launch_terracotta(app AppHandle) SJMCLResult~()~
    +download_terracotta(app AppHandle) SJMCLResult~()~
    +fetch_port(app AppHandle) SJMCLResult~u16~
  }

  class TaskMonitor {
    <<Rust_struct>>
    +wait_for_task_group(task_group &str) SJMCLResult~()~
    ..existing_methods..
  }

  class DownloadParam {
    <<Rust_struct>>
    +src Url
    +dest PathBuf
    +filename Option~String~
    +sha1 Option~String~
  }

  class PTaskParam {
    <<Rust_enum>>
    +Download(DownloadParam)
    ..other_variants..
  }

  MultiplayerModal --> MultiplayerService : uses
  MultiplayerModal --> Phase : state
  MultiplayerModal --> MultiplayerActionButton : composes

  MultiplayerService --> MultiplayerCommands : invokes_via_Tauri

  MultiplayerCommands --> TerracottaHelpers : calls
  TerracottaHelpers --> ResourceType : uses

  MultiplayerCommands --> TaskMonitor : schedules_and_waits
  PTaskParam --> DownloadParam : contains
  TerracottaHelpers --> PTaskParam : returns

  MultiplayerCommands --> MultiplayerError : returns_error
  TerracottaHelpers --> MultiplayerError : returns_error
Loading

File-Level Changes

Change Details Files
Add multiplayer entry point to the launch page and styling for the new button
  • Insert a left-bottom "multiplayer" button on the launch page that checks platform support via MultiplayerService and opens a shared multiplayer modal
  • Refactor existing launch page layout to wrap the player card and launch button in a fragment while preserving previous behavior
  • Define CSS styles for the multiplayer button including gradients, shadows, and hover/active transitions
src/pages/launch.tsx
src/styles/launch.module.css
Implement a multiplayer modal UI for hosting and joining Terracotta rooms
  • Create a MultiplayerModal component that drives a phase-based state machine for checking core availability, downloading, hosting, joining, error handling, and disconnection
  • Integrate with Tauri Terracotta HTTP API endpoints (/state, /state/scanning, /state/guesting, /state/ide) to poll status and control sessions
  • Add UI flows for downloading the core, creating rooms, joining by invite code, copying invite codes, listing joined profiles, and handling various error types
  • Register MultiplayerModal in the shared modals provider so it can be opened via openSharedModal
src/components/modals/multiplayer-modal.tsx
src/components/special/shared-modals-provider.tsx
Add a frontend MultiplayerService wrapper for Tauri multiplayer commands and platform checks
  • Introduce MultiplayerService with Tauri invocations for check_terracotta, launch_terracotta, download_terracotta, and fetch_port
  • Implement a Windows-only platform support check based on OS type and build version using @tauri-apps/plugin-os
  • Standardize responses via the existing responseHandler utility
src/services/multiplayer.ts
Add Tauri-side multiplayer module to download, unpack, and launch Terracotta
  • Register a new multiplayer module and its Tauri commands (check_terracotta, launch_terracotta, download_terracotta, fetch_port) in the app entrypoint
  • Implement Terracotta download parameter construction based on OS/arch and configured source priority, targeting GitHub or Gitee releases
  • Use the existing progressive task system to download the Terracotta archive and add a TaskMonitor.wait_for_task_group helper to block until completion
  • Decompress the downloaded tar.gz into the app data terracotta directory, setting executable permissions on Unix and cleaning up archives
  • Implement launch_terracotta to locate the platform-specific Terracotta executable and spawn it with an --hmcl argument pointing at a temp file
  • Implement fetch_port to read a JSON file written by Terracotta and extract the listening port, with retry and timeout behavior
src-tauri/src/lib.rs
src-tauri/src/multiplayer/mod.rs
src-tauri/src/multiplayer/commands.rs
src-tauri/src/multiplayer/helpers/terracotta.rs
src-tauri/src/multiplayer/models.rs
src-tauri/src/tasks/monitor.rs
Extend resource and logging infrastructure to support Terracotta downloads and reduce noise
  • Add a Terracotta variant to ResourceType and map it to GitHub/BMCL (Gitee) release URLs in get_download_api
  • Include flate2 and tar dependencies for archive handling in Cargo.toml and lockfile
  • Filter out noisy reqwest::connect debug logs in the logging setup
src-tauri/src/resource/models.rs
src-tauri/src/resource/helpers/misc.rs
src-tauri/Cargo.toml
src-tauri/src/utils/logging.rs
src-tauri/Cargo.lock
Miscellaneous frontend plumbing and type adjustments
  • Allow importing CSS modules in TypeScript by declaring *.css in global.d.ts
  • Wire translations for the new multiplayer UI into English and Simplified Chinese locale files
  • Update Tauri capabilities configuration to expose the new multiplayer commands
src/global.d.ts
src/locales/en.json
src/locales/zh-Hans.json
src-tauri/capabilities/default.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@UNIkeEN UNIkeEN left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

首先,请删除 codex 的缓存文件夹喵 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants