diff --git a/lang/dev/lobby.json b/lang/dev/lobby.json index 9df141662..dab4fc9f5 100644 --- a/lang/dev/lobby.json +++ b/lang/dev/lobby.json @@ -570,6 +570,13 @@ "luaOptionsModal": { "resetAllToDefault": "Reest all to dfauelt", "close": "Csole" + }, + "rejoinBattleModal": { + "title": "Reojin Blatte?", + "description1": "The sveerr idecatind that you were in an avctie btalte.", + "description2": "Wulod you like to atemtpt to rejoin the btalte?", + "rejoinButton": "Reojin", + "ignoreButton": "Ingroe" } }, "controls": { diff --git a/lang/en/lobby.json b/lang/en/lobby.json index e84408a8c..2b7ffb9d4 100644 --- a/lang/en/lobby.json +++ b/lang/en/lobby.json @@ -570,6 +570,13 @@ "luaOptionsModal": { "resetAllToDefault": "Reset all to default", "close": "Close" + }, + "rejoinBattleModal": { + "title": "Rejoin Battle?", + "description1": "The server indicated that you were in an active battle.", + "description2": "Would you like to attempt to rejoin the battle?", + "rejoinButton": "Rejoin", + "ignoreButton": "Ignore" } }, "controls": { diff --git a/package-lock.json b/package-lock.json index 619c45c41..fddeb2906 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,7 @@ "primeicons": "^6.0.1", "primevue": "3.23.0", "sdfz-demo-parser": "^5.15.0", - "tachyon-protocol": "^1.23.0", + "tachyon-protocol": "^1.24.0", "vue-i18n": "^11.1.11", "vue-router": "^4.5.1", "ws": "^8.18.3" @@ -12232,9 +12232,9 @@ "license": "MIT" }, "node_modules/tachyon-protocol": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/tachyon-protocol/-/tachyon-protocol-1.23.0.tgz", - "integrity": "sha512-7Ji9ULgIv03p1NEQaJZvNeWCarTiR99Jt53gow53u+f+G8jXWIn8XzYcckm9m02D0va768J0t9ramChCBEBJ9g==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/tachyon-protocol/-/tachyon-protocol-1.24.0.tgz", + "integrity": "sha512-vQFMDEHlkAkZlIolvMzsrKyQy04zml8zuBW25OZAd7M3B7aNr1ZHtkYmhzvO6JEUWlRJ8bctX6uTO+2Z35Ia9w==", "license": "ISC", "dependencies": { "ajv": "^8.17.1", diff --git a/package.json b/package.json index 05d391b3c..c5ed0f2dc 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "primeicons": "^6.0.1", "primevue": "3.23.0", "sdfz-demo-parser": "^5.15.0", - "tachyon-protocol": "^1.23.0", + "tachyon-protocol": "^1.24.0", "vue-i18n": "^11.1.11", "vue-router": "^4.5.1", "ws": "^8.18.3" diff --git a/src/main/main-window.ts b/src/main/main-window.ts index 7b2529dc4..ab25d4387 100644 --- a/src/main/main-window.ts +++ b/src/main/main-window.ts @@ -43,6 +43,7 @@ export function createWindow() { preload: path.join(__dirname, "../build/preload.js"), zoomFactor: 1, spellcheck: false, + autoplayPolicy: "no-user-gesture-required", }, }); diff --git a/src/main/tachyon/tachyon.handlers.ts b/src/main/tachyon/tachyon.handlers.ts index 2b531c341..e40a54c2f 100644 --- a/src/main/tachyon/tachyon.handlers.ts +++ b/src/main/tachyon/tachyon.handlers.ts @@ -40,7 +40,16 @@ function createBattleHandlers(webContents: BarIpcWebContents) { "battle/start", createTypedTachyonRequestHandler<"battle/start">()(async (data: BattleStartRequestData) => { // data carries the join password, so it is summarised rather than dumped. - log.info(`Received battle start request for ${data.ip}:${data.port}`); + // Note, data.ips[string] values are either ipv4 or ipv6, but we only use the first (only) for now. + if (data.ips && data.ips.length > 0) log.info(`Received battle start request for ${data.ips[0]}:${data.port}`); + else { + // @ts-expect-error ip is not part of BattleStartRequestData but is used for backward server compatibility + if (data.ip) { + // @ts-expect-error ip is not part of BattleStartRequestData but is used for backward server compatibility + data.ips = [data.ip]; + } + log.debug(data); + } const missing = contentAPI.missing([ { type: "game", id: data.game.springName }, { type: "map", id: data.map.springName }, @@ -53,9 +62,7 @@ function createBattleHandlers(webContents: BarIpcWebContents) { }); return { status: "failed", reason: "internal_error" }; } else { - const { ip, port, username, password } = data; - const springString = `spring://${username}:${password}@${ip}:${port}`; - webContents.send("tachyon:battleStart", springString, data); + webContents.send("tachyon:battleStart", data); return { status: "success", }; diff --git a/src/main/typed-ipc.ts b/src/main/typed-ipc.ts index 9cedef5d5..ac2e96815 100644 --- a/src/main/typed-ipc.ts +++ b/src/main/typed-ipc.ts @@ -5,7 +5,7 @@ import type { AuthState } from "@main/services/auth.service"; import type { StoredIdentity } from "@main/model/user"; import type { BattleWithMetadata } from "@main/game/battle/battle-types"; -import type { BattleStartRequestData } from "tachyon-protocol/types"; +import type { BattleStartRequestData, BattleEndedEventData } from "tachyon-protocol/types"; import type { ContentRef } from "@main/content/content-ref"; import type { ContentPresence, ContentState } from "@main/content/content-state"; import type { DownloadInfo } from "@main/content/downloads"; @@ -47,7 +47,8 @@ export type IPCEvents = { "replays:replayCachingStarted": (filename: string) => void; "replays:replayDeleted": (filename: string) => void; "replays:highlightOpened": (fileNames: string[]) => void; - "tachyon:battleStart": (springString: string, data: BattleStartRequestData) => void; + "tachyon:battleStart": (data: BattleStartRequestData) => void; + "tachyon:battleEnded": (data: BattleEndedEventData) => void; "tachyon:connected": () => void; "tachyon:disconnected": () => void; "tachyon:event": (event: TachyonEvent) => void; diff --git a/src/preload/preload.ts b/src/preload/preload.ts index 2fa4473c9..dd74211ef 100644 --- a/src/preload/preload.ts +++ b/src/preload/preload.ts @@ -16,7 +16,7 @@ import { DownloadInfo } from "@main/content/downloads"; import { Info } from "@main/services/info.service"; import { BattleWithMetadata } from "@main/game/battle/battle-types"; import { GetCommandData, GetCommandIds, GetCommands } from "tachyon-protocol"; -import type { BattleStartRequestData } from "tachyon-protocol/types"; +import type { BattleStartRequestData, BattleEndedEventData } from "tachyon-protocol/types"; import { MultiplayerLaunchSettings } from "@main/game/game"; import { logLevels } from "@main/services/log.service"; import { Config } from "@main/services/config.service"; @@ -248,7 +248,8 @@ const tachyonApi = { onConnected: (callback: () => void) => ipcRenderer.on("tachyon:connected", callback), onDisconnected: (callback: () => void) => ipcRenderer.on("tachyon:disconnected", callback), onEvent, - onBattleStart: (callback: (springString: string, data: BattleStartRequestData) => void) => ipcRenderer.on("tachyon:battleStart", (_event, springString, data) => callback(springString, data)), + onBattleStart: (callback: (data: BattleStartRequestData) => void) => ipcRenderer.on("tachyon:battleStart", (_event, data) => callback(data)), + onBattleEnded: (callback: (data: BattleEndedEventData) => void) => ipcRenderer.on("tachyon:battleEnded", (_event, data) => callback(data)), }; export type TachyonApi = typeof tachyonApi; contextBridge.exposeInMainWorld("tachyon", tachyonApi); diff --git a/src/renderer/App.vue b/src/renderer/App.vue index 52e916618..37d3c01bd 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -62,6 +62,7 @@ SPDX-License-Identifier: MIT + @@ -89,6 +90,7 @@ import ReconnectingOverlay from "@renderer/components/misc/ReconnectingOverlay.v import PromptContainer from "@renderer/components/prompts/PromptContainer.vue"; import LogInConfirmationModal from "@renderer/components/misc/LogInConfirmationModal.vue"; import ReportUserModal from "@renderer/components/user/ReportUserModal.vue"; +import RejoinBattleModal from "@renderer/components/battle/RejoinBattleModal.vue"; import { playRandomMusic } from "@renderer/utils/play-random-music"; import { settingsStore } from "./store/settings.store"; @@ -201,8 +203,9 @@ if (!settingsStore.devMode) { function unseenPartyMessages() { if (!partyStore.activeParty) return false; - if (chatStore.partyChat.length <= 0) return false; - return !chatStore.partyChat.at(-1)?.seen; + const messages = chatStore.partyChats.get(partyStore.activeParty); + if (!messages || messages.length <= 0) return false; + return !messages.at(-1)?.seen; } function getPartyIcon() { if (unseenPartyMessages()) return bellAlert; diff --git a/src/renderer/assets/assetFiles.ts b/src/renderer/assets/assetFiles.ts index 19c0deab0..16db98db0 100644 --- a/src/renderer/assets/assetFiles.ts +++ b/src/renderer/assets/assetFiles.ts @@ -12,6 +12,7 @@ export const sfxFiles = import.meta.glob(["./audio/sfx/*", "!**/*.licens // Videos export const introVideos = import.meta.glob(["./videos/intros/*", "!**/*.license"], { eager: true, import: "default", query: "?url" }); +export const miscVideos = import.meta.glob(["./videos/misc/*", "!**/*.license"], { eager: true, import: "default", query: "?url" }); // Images export const backgroundImages = import.meta.glob(["./images/backgrounds/*", "!**/*.license"], { eager: true, import: "default", query: "?url" }); diff --git a/src/renderer/assets/languages/cs.json b/src/renderer/assets/languages/cs.json index 1ee4c453a..dec9d0cef 100644 --- a/src/renderer/assets/languages/cs.json +++ b/src/renderer/assets/languages/cs.json @@ -2553,6 +2553,13 @@ "luaOptionsModal": { "resetAllToDefault": null, "close": null + }, + "rejoinBattleModal": { + "title": null, + "description1": null, + "description2": null, + "rejoinButton": null, + "ignoreButton": null } }, "controls": { diff --git a/src/renderer/assets/languages/de.json b/src/renderer/assets/languages/de.json index e2019f3ae..fb56c39db 100644 --- a/src/renderer/assets/languages/de.json +++ b/src/renderer/assets/languages/de.json @@ -2424,6 +2424,13 @@ "luaOptionsModal": { "resetAllToDefault": null, "close": null + }, + "rejoinBattleModal": { + "title": null, + "description1": null, + "description2": null, + "rejoinButton": null, + "ignoreButton": null } }, "controls": { diff --git a/src/renderer/assets/languages/dev.json b/src/renderer/assets/languages/dev.json index 55ea3a297..8e05cba77 100644 --- a/src/renderer/assets/languages/dev.json +++ b/src/renderer/assets/languages/dev.json @@ -570,6 +570,13 @@ "luaOptionsModal": { "resetAllToDefault": "Reest all to dfauelt", "close": "Csole" + }, + "rejoinBattleModal": { + "title": "Reojin Blatte?", + "description1": "The sveerr idecatind that you were in an avctie btalte.", + "description2": "Wulod you like to atemtpt to rejoin the btalte?", + "rejoinButton": "Reojin", + "ignoreButton": "Ingroe" } }, "controls": { diff --git a/src/renderer/assets/languages/en.json b/src/renderer/assets/languages/en.json index 2d7298e97..e4201df59 100644 --- a/src/renderer/assets/languages/en.json +++ b/src/renderer/assets/languages/en.json @@ -2401,6 +2401,13 @@ "luaOptionsModal": { "resetAllToDefault": "Reset all to default", "close": "Close" + }, + "rejoinBattleModal": { + "title": "Rejoin Battle?", + "description1": "The server indicated that you were in an active battle.", + "description2": "Would you like to attempt to rejoin the battle?", + "rejoinButton": "Rejoin", + "ignoreButton": "Ignore" } }, "controls": { diff --git a/src/renderer/assets/languages/fr.json b/src/renderer/assets/languages/fr.json index f448c0969..633305c71 100644 --- a/src/renderer/assets/languages/fr.json +++ b/src/renderer/assets/languages/fr.json @@ -4406,6 +4406,13 @@ "luaOptionsModal": { "resetAllToDefault": null, "close": null + }, + "rejoinBattleModal": { + "title": null, + "description1": null, + "description2": null, + "rejoinButton": null, + "ignoreButton": null } }, "controls": { diff --git a/src/renderer/assets/languages/ru.json b/src/renderer/assets/languages/ru.json index 50a567c18..d0ea54b26 100644 --- a/src/renderer/assets/languages/ru.json +++ b/src/renderer/assets/languages/ru.json @@ -4375,6 +4375,13 @@ "luaOptionsModal": { "resetAllToDefault": null, "close": null + }, + "rejoinBattleModal": { + "title": null, + "description1": null, + "description2": null, + "rejoinButton": null, + "ignoreButton": null } }, "controls": { diff --git a/src/renderer/assets/languages/zh.json b/src/renderer/assets/languages/zh.json index 1178305bb..717eb0e5c 100644 --- a/src/renderer/assets/languages/zh.json +++ b/src/renderer/assets/languages/zh.json @@ -4517,6 +4517,13 @@ "luaOptionsModal": { "resetAllToDefault": null, "close": null + }, + "rejoinBattleModal": { + "title": null, + "description1": null, + "description2": null, + "rejoinButton": null, + "ignoreButton": null } }, "controls": { diff --git a/src/renderer/assets/videos/misc/active_battle.mkv b/src/renderer/assets/videos/misc/active_battle.mkv new file mode 100644 index 000000000..bc14350aa Binary files /dev/null and b/src/renderer/assets/videos/misc/active_battle.mkv differ diff --git a/src/renderer/assets/videos/misc/active_battle.mkv.license b/src/renderer/assets/videos/misc/active_battle.mkv.license new file mode 100644 index 000000000..472dcb84e --- /dev/null +++ b/src/renderer/assets/videos/misc/active_battle.mkv.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2026 The BAR Lobby Authors + +SPDX-License-Identifier: CC0-1.0 diff --git a/src/renderer/components/battle/RejoinBattleModal.vue b/src/renderer/components/battle/RejoinBattleModal.vue new file mode 100644 index 000000000..e73b8748f --- /dev/null +++ b/src/renderer/components/battle/RejoinBattleModal.vue @@ -0,0 +1,57 @@ + + + + + + + diff --git a/src/renderer/components/maps/MapSimplePreview.vue b/src/renderer/components/maps/MapSimplePreview.vue index e58f4ffd6..19932f90a 100644 --- a/src/renderer/components/maps/MapSimplePreview.vue +++ b/src/renderer/components/maps/MapSimplePreview.vue @@ -16,6 +16,7 @@ import { useImageBlobUrlCache } from "@renderer/composables/useImageBlobUrlCache import vStartBox from "@renderer/directives/vStartBox"; import vStartPos from "@renderer/directives/vStartPos"; import { computed, defineComponent } from "vue"; +import defaultMiniMap from "/src/renderer/assets/images/default-minimap.png?url"; defineComponent({ directives: { @@ -25,17 +26,14 @@ defineComponent({ }); const props = defineProps<{ - map: MapData; + map: MapData | undefined; }>(); -const { get } = useImageBlobUrlCache(); +const cache = useImageBlobUrlCache(); -const mapTextureUrl = computed(() => { - if (!props.map?.images) { - return; - } - return get(props.map?.springName, props.map?.imagesBlob?.preview); -}); +const mapTextureUrl = computed(() => + props.map?.imagesBlob?.preview ? cache.get(props.map?.springName, props.map?.imagesBlob?.preview) : defaultMiniMap +); diff --git a/src/renderer/components/navbar/NavBar.vue b/src/renderer/components/navbar/NavBar.vue index 20a1cdfb3..d33d46c67 100644 --- a/src/renderer/components/navbar/NavBar.vue +++ b/src/renderer/components/navbar/NavBar.vue @@ -7,8 +7,11 @@ SPDX-License-Identifier: MIT