Allow direct joining servers on old instances (#4094)

* Implement direct server joining for 1.6.2 through 1.19.4

* Implement direct server joining for versions before 1.6.2

* Ignore methods with a $ in them

* Run intl:extract

* Improve code of MinecraftTransformer

* Support showing last played time for profiles before 1.7

* Reorganize QuickPlayVersion a bit to prepare for singleplayer

* Only inject quick play checking in versions where it's needed

* Optimize agent some and fix error on NeoForge

* Remove some code for quickplay singleplayer support before 1.20, as we can't reasonably support that with an agent

* Invert the default hasServerQuickPlaySupport return value

* Remove Play Anyway button

* Fix "Server couldn't be contacted" on singleplayer worlds

* Fix "Jump back in" section not working
This commit is contained in:
Josiah Glosson
2025-08-04 12:29:20 -07:00
committed by GitHub
parent 13dbb4c57e
commit 0657e4466f
24 changed files with 760 additions and 174 deletions

View File

@@ -9,7 +9,7 @@
"tsc:check": "vue-tsc --noEmit",
"lint": "eslint . && prettier --check .",
"fix": "eslint . --fix && prettier --write .",
"intl:extract": "formatjs extract \"{,src/components,src/composables,src/helpers,src/pages,src/store}/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" --ignore '**/*.d.ts' --ignore 'node_modules' --out-file src/locales/en-US/index.json --format crowdin --preserve-whitespace",
"intl:extract": "formatjs extract \"src/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" --ignore \"**/*.d.ts\" --ignore node_modules --out-file src/locales/en-US/index.json --format crowdin --preserve-whitespace",
"test": "vue-tsc --noEmit"
},
"dependencies": {

View File

@@ -6,9 +6,8 @@ import type {
ServerWorld,
SingleplayerWorld,
World,
set_world_display_status,
getWorldIdentifier,
} from '@/helpers/worlds.ts'
import { set_world_display_status, getWorldIdentifier } from '@/helpers/worlds.ts'
import { formatNumber, getPingLevel } from '@modrinth/utils'
import {
useRelativeTime,
@@ -61,7 +60,8 @@ const props = withDefaults(
playingInstance?: boolean
playingWorld?: boolean
startingInstance?: boolean
supportsQuickPlay?: boolean
supportsServerQuickPlay?: boolean
supportsWorldQuickPlay?: boolean
currentProtocol?: ProtocolVersion | null
highlighted?: boolean
@@ -85,7 +85,8 @@ const props = withDefaults(
playingInstance: false,
playingWorld: false,
startingInstance: false,
supportsQuickPlay: false,
supportsServerQuickPlay: true,
supportsWorldQuickPlay: false,
currentProtocol: null,
refreshing: false,
@@ -128,9 +129,13 @@ const messages = defineMessages({
id: 'instance.worlds.a_minecraft_server',
defaultMessage: 'A Minecraft Server',
},
noQuickPlay: {
id: 'instance.worlds.no_quick_play',
defaultMessage: 'You can only jump straight into worlds on Minecraft 1.20+',
noServerQuickPlay: {
id: 'instance.worlds.no_server_quick_play',
defaultMessage: 'You can only jump straight into servers on Minecraft Alpha 1.0.5+',
},
noSingleplayerQuickPlay: {
id: 'instance.worlds.no_singleplayer_quick_play',
defaultMessage: 'You can only jump straight into singleplayer worlds on Minecraft 1.20+',
},
gameAlreadyOpen: {
id: 'instance.worlds.game_already_open',
@@ -152,10 +157,6 @@ const messages = defineMessages({
id: 'instance.worlds.view_instance',
defaultMessage: 'View instance',
},
playAnyway: {
id: 'instance.worlds.play_anyway',
defaultMessage: 'Play anyway',
},
playInstance: {
id: 'instance.worlds.play_instance',
defaultMessage: 'Play instance',
@@ -330,17 +331,24 @@ const messages = defineMessages({
<ButtonStyled v-else>
<button
v-tooltip="
!serverStatus
? formatMessage(messages.noContact)
: serverIncompatible
? formatMessage(messages.incompatibleServer)
: !supportsQuickPlay
? formatMessage(messages.noQuickPlay)
: playingOtherWorld || locked
? formatMessage(messages.gameAlreadyOpen)
: null
world.type == 'server' && !supportsServerQuickPlay
? formatMessage(messages.noServerQuickPlay)
: world.type == 'singleplayer' && !supportsWorldQuickPlay
? formatMessage(messages.noSingleplayerQuickPlay)
: playingOtherWorld || locked
? formatMessage(messages.gameAlreadyOpen)
: !serverStatus
? formatMessage(messages.noContact)
: serverIncompatible
? formatMessage(messages.incompatibleServer)
: null
"
:disabled="
playingOtherWorld ||
startingInstance ||
(world.type == 'server' && !supportsServerQuickPlay) ||
(world.type == 'singleplayer' && !supportsWorldQuickPlay)
"
:disabled="!supportsQuickPlay || playingOtherWorld || startingInstance"
@click="emit('play')"
>
<SpinnerIcon v-if="startingInstance && playingWorld" class="animate-spin" />
@@ -357,11 +365,6 @@ const messages = defineMessages({
disabled: playingInstance,
action: () => emit('play-instance'),
},
{
id: 'play-anyway',
shown: serverIncompatible && !playingInstance && supportsQuickPlay,
action: () => emit('play'),
},
{
id: 'open-instance',
shown: !!instancePath,
@@ -427,10 +430,6 @@ const messages = defineMessages({
<PlayIcon aria-hidden="true" />
{{ formatMessage(messages.playInstance) }}
</template>
<template #play-anyway>
<PlayIcon aria-hidden="true" />
{{ formatMessage(messages.playAnyway) }}
</template>
<template #open-instance>
<EyeIcon aria-hidden="true" />
{{ formatMessage(messages.viewInstance) }}

View File

@@ -311,15 +311,24 @@ export async function refreshWorlds(instancePath: string): Promise<World[]> {
return worlds ?? []
}
const FIRST_QUICK_PLAY_VERSION = '23w14a'
export function hasServerQuickPlaySupport(gameVersions: GameVersion[], currentVersion: string) {
if (!gameVersions.length) {
return true
}
export function hasQuickPlaySupport(gameVersions: GameVersion[], currentVersion: string) {
const versionIndex = gameVersions.findIndex((v) => v.version === currentVersion)
const targetIndex = gameVersions.findIndex((v) => v.version === 'a1.0.5_01')
return versionIndex === -1 || targetIndex === -1 || versionIndex <= targetIndex
}
export function hasWorldQuickPlaySupport(gameVersions: GameVersion[], currentVersion: string) {
if (!gameVersions.length) {
return false
}
const versionIndex = gameVersions.findIndex((v) => v.version === currentVersion)
const targetIndex = gameVersions.findIndex((v) => v.version === FIRST_QUICK_PLAY_VERSION)
const targetIndex = gameVersions.findIndex((v) => v.version === '23w14a')
return versionIndex !== -1 && targetIndex !== -1 && versionIndex <= targetIndex
}

View File

@@ -383,11 +383,11 @@
"instance.worlds.no_contact": {
"message": "Server couldn't be contacted"
},
"instance.worlds.no_quick_play": {
"message": "You can only jump straight into worlds on Minecraft 1.20+"
"instance.worlds.no_server_quick_play": {
"message": "You can only jump straight into servers on Minecraft Alpha 1.0.5+"
},
"instance.worlds.play_anyway": {
"message": "Play anyway"
"instance.worlds.no_singleplayer_quick_play": {
"message": "You can only jump straight into singleplayer worlds on Minecraft 1.20+"
},
"instance.worlds.play_instance": {
"message": "Play instance"

View File

@@ -67,7 +67,8 @@
:key="`world-${world.type}-${world.type == 'singleplayer' ? world.path : `${world.address}-${world.index}`}`"
:world="world"
:highlighted="highlightedWorld === getWorldIdentifier(world)"
:supports-quick-play="supportsQuickPlay"
:supports-server-quick-play="supportsServerQuickPlay"
:supports-world-quick-play="supportsWorldQuickPlay"
:current-protocol="protocolVersion"
:playing-instance="playing"
:playing-world="worldsMatch(world, worldPlaying)"
@@ -150,10 +151,11 @@ import {
refreshWorld,
sortWorlds,
refreshServers,
hasQuickPlaySupport,
hasWorldQuickPlaySupport,
refreshWorlds,
handleDefaultProfileUpdateEvent,
showWorldInFolder,
hasServerQuickPlaySupport,
} from '@/helpers/worlds.ts'
import AddServerModal from '@/components/ui/world/modal/AddServerModal.vue'
import EditServerModal from '@/components/ui/world/modal/EditServerModal.vue'
@@ -355,8 +357,11 @@ function worldsMatch(world: World, other: World | undefined) {
}
const gameVersions = ref<GameVersion[]>(await get_game_versions().catch(() => []))
const supportsQuickPlay = computed(() =>
hasQuickPlaySupport(gameVersions.value, instance.value.game_version),
const supportsServerQuickPlay = computed(() =>
hasServerQuickPlaySupport(gameVersions.value, instance.value.game_version),
)
const supportsWorldQuickPlay = computed(() =>
hasWorldQuickPlaySupport(gameVersions.value, instance.value.game_version),
)
const filterOptions = computed(() => {

View File

@@ -250,7 +250,7 @@ pub async fn profile_get_pack_export_candidates(
// invoke('plugin:profile|profile_run', path)
#[tauri::command]
pub async fn profile_run(path: &str) -> Result<ProcessMetadata> {
let process = profile::run(path, &QuickPlayType::None).await?;
let process = profile::run(path, QuickPlayType::None).await?;
Ok(process)
}

View File

@@ -4,6 +4,7 @@ use enumset::EnumSet;
use tauri::{AppHandle, Manager, Runtime};
use theseus::prelude::ProcessMetadata;
use theseus::profile::{QuickPlayType, get_full_path};
use theseus::server_address::ServerAddress;
use theseus::worlds::{
DisplayStatus, ProtocolVersion, ServerPackStatus, ServerStatus, World,
WorldType, WorldWithProfile,
@@ -203,7 +204,7 @@ pub async fn start_join_singleplayer_world(
world: String,
) -> Result<ProcessMetadata> {
let process =
profile::run(path, &QuickPlayType::Singleplayer(world)).await?;
profile::run(path, QuickPlayType::Singleplayer(world)).await?;
Ok(process)
}
@@ -213,8 +214,11 @@ pub async fn start_join_server(
path: &str,
address: &str,
) -> Result<ProcessMetadata> {
let process =
profile::run(path, &QuickPlayType::Server(address.to_owned())).await?;
let process = profile::run(
path,
QuickPlayType::Server(ServerAddress::Unresolved(address.to_owned())),
)
.await?;
Ok(process)
}