Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60d4d5bb47 | |||
| 8f3a74ddb4 | |||
| 93775cc36a |
@@ -25,7 +25,6 @@ files:
|
|||||||
- node_modules/mimic-fn/**/*
|
- node_modules/mimic-fn/**/*
|
||||||
- node_modules/semver/**/*
|
- node_modules/semver/**/*
|
||||||
- node_modules/onetime/**/*
|
- node_modules/onetime/**/*
|
||||||
- node_modules/uuid/**/*
|
|
||||||
- "!node_modules/**/*.ts"
|
- "!node_modules/**/*.ts"
|
||||||
- "!node_modules/**/*.map"
|
- "!node_modules/**/*.map"
|
||||||
asar: true
|
asar: true
|
||||||
@@ -43,6 +42,7 @@ nsis:
|
|||||||
allowToChangeInstallationDirectory: true
|
allowToChangeInstallationDirectory: true
|
||||||
createDesktopShortcut: true
|
createDesktopShortcut: true
|
||||||
createStartMenuShortcut: true
|
createStartMenuShortcut: true
|
||||||
|
shortcutName: Krunker Civilian Client
|
||||||
artifactName: "${productName}-${version}-Setup.${ext}"
|
artifactName: "${productName}-${version}-Setup.${ext}"
|
||||||
|
|
||||||
portable:
|
portable:
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "krunker-civilian-client",
|
"name": "krunker-civilian-client",
|
||||||
"version": "0.5.3",
|
"version": "0.5.6",
|
||||||
"description": "Cross-platform Krunker game client",
|
"description": "Cross-platform Krunker game client",
|
||||||
"main": "dist/main/index.js",
|
"main": "dist/main/index.js",
|
||||||
"homepage": "https://gitea.crjlab.net/bigjakk/krunker-civilian-client",
|
"homepage": "https://gitea.crjlab.net/bigjakk/krunker-civilian-client",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export interface AppConfig {
|
|||||||
maxPlayers: number;
|
maxPlayers: number;
|
||||||
minRemainingTime: number;
|
minRemainingTime: number;
|
||||||
openServerBrowser: boolean;
|
openServerBrowser: boolean;
|
||||||
|
autoJoin: boolean;
|
||||||
};
|
};
|
||||||
keybinds: {
|
keybinds: {
|
||||||
reload: Keybind;
|
reload: Keybind;
|
||||||
@@ -140,6 +141,7 @@ export const config = new Store<AppConfig>({
|
|||||||
maxPlayers: 6,
|
maxPlayers: 6,
|
||||||
minRemainingTime: 120,
|
minRemainingTime: 120,
|
||||||
openServerBrowser: true,
|
openServerBrowser: true,
|
||||||
|
autoJoin: false,
|
||||||
},
|
},
|
||||||
keybinds: DEFAULT_KEYBINDS,
|
keybinds: DEFAULT_KEYBINDS,
|
||||||
userscripts: {
|
userscripts: {
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ const advancedConfig = { ...advancedDefaults, ...config.get('advanced') };
|
|||||||
const perfConfig = { fpsUnlocked: true, ...config.get('performance') };
|
const perfConfig = { fpsUnlocked: true, ...config.get('performance') };
|
||||||
applyPlatformFlags(platformInfo, advancedConfig, perfConfig);
|
applyPlatformFlags(platformInfo, advancedConfig, perfConfig);
|
||||||
|
|
||||||
|
// ── App identity (must match electron-builder appId for taskbar pin persistence) ──
|
||||||
|
app.setAppUserModelId('com.krunkercivilian.client');
|
||||||
|
|
||||||
// ── Resource swapper protocol (must register before app.ready) ──
|
// ── Resource swapper protocol (must register before app.ready) ──
|
||||||
initSwapperProtocol();
|
initSwapperProtocol();
|
||||||
|
|
||||||
|
|||||||
+6
-2
@@ -35,7 +35,7 @@ export function initSwapperProtocol(): void {
|
|||||||
* Must be called AFTER app.ready.
|
* Must be called AFTER app.ready.
|
||||||
*/
|
*/
|
||||||
export function registerSwapperFileProtocol(ses: Session): void {
|
export function registerSwapperFileProtocol(ses: Session): void {
|
||||||
ses.protocol.handle(PROTOCOL_NAME, (request) => {
|
ses.protocol.handle(PROTOCOL_NAME, async (request) => {
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
// Reconstruct the file path from the URL
|
// Reconstruct the file path from the URL
|
||||||
// Windows: kpc-swap://C/foo/bar → C:/foo/bar
|
// Windows: kpc-swap://C/foo/bar → C:/foo/bar
|
||||||
@@ -47,7 +47,11 @@ export function registerSwapperFileProtocol(ses: Session): void {
|
|||||||
} else {
|
} else {
|
||||||
filePath = url.pathname;
|
filePath = url.pathname;
|
||||||
}
|
}
|
||||||
return net.fetch(`file://${filePath}`);
|
try {
|
||||||
|
return await net.fetch(`file://${filePath}`);
|
||||||
|
} catch {
|
||||||
|
return new Response('Not found', { status: 404 });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -590,7 +590,7 @@ function buildSwapperSection(body: HTMLElement, swapperConf: any): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildMatchmakerSection(body: HTMLElement, mmConf: any, bag: SettingsBag): void {
|
function buildMatchmakerSection(body: HTMLElement, mmConf: any, bag: SettingsBag): void {
|
||||||
const mm = mmConf || { enabled: true, regions: [], gamemodes: [], minPlayers: 1, maxPlayers: 6, minRemainingTime: 120, openServerBrowser: true };
|
const mm = mmConf || { enabled: true, regions: [], gamemodes: [], minPlayers: 1, maxPlayers: 6, minRemainingTime: 120, openServerBrowser: true, autoJoin: false };
|
||||||
|
|
||||||
function saveMM(): void {
|
function saveMM(): void {
|
||||||
ipcRenderer.invoke('set-config', 'matchmaker', mm);
|
ipcRenderer.invoke('set-config', 'matchmaker', mm);
|
||||||
@@ -610,6 +610,13 @@ function buildMatchmakerSection(body: HTMLElement, mmConf: any, bag: SettingsBag
|
|||||||
onChange: (v) => { mm.openServerBrowser = v; saveMM(); },
|
onChange: (v) => { mm.openServerBrowser = v; saveMM(); },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
body.appendChild(createToggleRow({
|
||||||
|
label: 'Auto-Join',
|
||||||
|
desc: 'Automatically join the best match without showing the popup',
|
||||||
|
checked: mm.autoJoin ?? false, instant: true,
|
||||||
|
onChange: (v) => { mm.autoJoin = v; saveMM(); },
|
||||||
|
}));
|
||||||
|
|
||||||
body.appendChild(createKeybindRow('Matchmaker Hotkey', 'Key to trigger the custom matchmaker', bag.binds.matchmaker, (b) => {
|
body.appendChild(createKeybindRow('Matchmaker Hotkey', 'Key to trigger the custom matchmaker', bag.binds.matchmaker, (b) => {
|
||||||
bag.binds.matchmaker = b;
|
bag.binds.matchmaker = b;
|
||||||
bag.saveBinds();
|
bag.saveBinds();
|
||||||
|
|||||||
+55
-22
@@ -1,7 +1,8 @@
|
|||||||
// ── Custom Matchmaker (ported from Crankshaft) ──
|
// ── Custom Matchmaker (ported from Crankshaft) ──
|
||||||
// Fetches live lobby list from matchmaker.krunker.io, filters by user criteria,
|
// Fetches live lobby list from matchmaker.krunker.io, filters by user criteria,
|
||||||
// presents a popup to join a random matching game.
|
// sorts by lowest ping then highest player count, and joins the best match.
|
||||||
|
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
import type { Keybind } from '../main/config';
|
import type { Keybind } from '../main/config';
|
||||||
import type { SavedConsole } from './utils';
|
import type { SavedConsole } from './utils';
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ export interface MatchmakerConfig {
|
|||||||
maxPlayers: number;
|
maxPlayers: number;
|
||||||
minRemainingTime: number;
|
minRemainingTime: number;
|
||||||
openServerBrowser: boolean;
|
openServerBrowser: boolean;
|
||||||
|
autoJoin: boolean;
|
||||||
acceptKey: Keybind;
|
acceptKey: Keybind;
|
||||||
cancelKey: Keybind;
|
cancelKey: Keybind;
|
||||||
}
|
}
|
||||||
@@ -92,7 +94,7 @@ function getPopup(): PopupDOM {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── State ──
|
// ── State ──
|
||||||
let currentMatch = '';
|
let popupGameID = '';
|
||||||
let openServerBrowser = true;
|
let openServerBrowser = true;
|
||||||
let confirmKey: Keybind = { key: 'Enter', ctrl: false, shift: false, alt: false };
|
let confirmKey: Keybind = { key: 'Enter', ctrl: false, shift: false, alt: false };
|
||||||
let cancelKey: Keybind = { key: 'Escape', ctrl: false, shift: false, alt: false };
|
let cancelKey: Keybind = { key: 'Escape', ctrl: false, shift: false, alt: false };
|
||||||
@@ -101,12 +103,12 @@ function decideMatchmakerDecision(accept: boolean): void {
|
|||||||
const w = window as any;
|
const w = window as any;
|
||||||
if (typeof w.playSelect === 'function') w.playSelect();
|
if (typeof w.playSelect === 'function') w.playSelect();
|
||||||
|
|
||||||
if (accept && currentMatch !== 'none') {
|
if (accept && popupGameID !== 'none') {
|
||||||
window.location.href = `https://krunker.io/?game=${currentMatch}`;
|
window.location.href = `https://krunker.io/?game=${popupGameID}`;
|
||||||
} else {
|
} else {
|
||||||
const popup = getPopup();
|
const popup = getPopup();
|
||||||
if (popup.element.parentNode) popup.element.remove();
|
if (popup.element.parentNode) popup.element.remove();
|
||||||
if (currentMatch === 'none' && openServerBrowser && typeof w.openServerWindow === 'function') {
|
if (popupGameID === 'none' && openServerBrowser && typeof w.openServerWindow === 'function') {
|
||||||
w.openServerWindow(0);
|
w.openServerWindow(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,7 +137,7 @@ function createFetchedGamePopup(game: MatchmakerGame): void {
|
|||||||
const mapIdx = MAP_ICON_INDICES.indexOf(game.map);
|
const mapIdx = MAP_ICON_INDICES.indexOf(game.map);
|
||||||
popup.element.style.backgroundImage = `url(https://assets.krunker.io/img/maps/map_${mapIdx >= 0 ? mapIdx : 0}.png)`;
|
popup.element.style.backgroundImage = `url(https://assets.krunker.io/img/maps/map_${mapIdx >= 0 ? mapIdx : 0}.png)`;
|
||||||
|
|
||||||
currentMatch = game.gameID;
|
popupGameID = game.gameID;
|
||||||
if (game.gameID === 'none') {
|
if (game.gameID === 'none') {
|
||||||
popup.title.textContent = 'No Games Found...';
|
popup.title.textContent = 'No Games Found...';
|
||||||
popup.description.textContent = 'Check the server browser to see other lobbies.';
|
popup.description.textContent = 'Check the server browser to see other lobbies.';
|
||||||
@@ -159,16 +161,7 @@ function createFetchedGamePopup(game: MatchmakerGame): void {
|
|||||||
if (uiBase) uiBase.appendChild(popup.element);
|
if (uiBase) uiBase.appendChild(popup.element);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchGame(mmConfig: MatchmakerConfig, _con?: SavedConsole): Promise<void> {
|
async function fetchAndFilterGames(mmConfig: MatchmakerConfig): Promise<MatchmakerGame[]> {
|
||||||
openServerBrowser = mmConfig.openServerBrowser;
|
|
||||||
confirmKey = mmConfig.acceptKey;
|
|
||||||
cancelKey = mmConfig.cancelKey;
|
|
||||||
|
|
||||||
// Dismiss existing popup if active
|
|
||||||
if (document.getElementById(POPUP_ID)) decideMatchmakerDecision(false);
|
|
||||||
|
|
||||||
_con?.log('[KCC-MM] Fetching game list...');
|
|
||||||
|
|
||||||
const response = await fetch(`https://matchmaker.krunker.io/game-list?hostname=${window.location.hostname}`);
|
const response = await fetch(`https://matchmaker.krunker.io/game-list?hostname=${window.location.hostname}`);
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
const games: MatchmakerGame[] = [];
|
const games: MatchmakerGame[] = [];
|
||||||
@@ -182,7 +175,6 @@ export async function fetchGame(mmConfig: MatchmakerConfig, _con?: SavedConsole)
|
|||||||
const gamemode = MATCHMAKER_GAMEMODES[game[4].g] ?? 'Unknown Gamemode';
|
const gamemode = MATCHMAKER_GAMEMODES[game[4].g] ?? 'Unknown Gamemode';
|
||||||
const remainingTime: number = game[5];
|
const remainingTime: number = game[5];
|
||||||
|
|
||||||
// Apply filters — empty arrays mean "all selected" (no filter)
|
|
||||||
if (mmConfig.regions.length > 0 && !mmConfig.regions.includes(region)) continue;
|
if (mmConfig.regions.length > 0 && !mmConfig.regions.includes(region)) continue;
|
||||||
if (mmConfig.gamemodes.length > 0 && !mmConfig.gamemodes.includes(gamemode)) continue;
|
if (mmConfig.gamemodes.length > 0 && !mmConfig.gamemodes.includes(gamemode)) continue;
|
||||||
if (playerCount < mmConfig.minPlayers) continue;
|
if (playerCount < mmConfig.minPlayers) continue;
|
||||||
@@ -190,19 +182,60 @@ export async function fetchGame(mmConfig: MatchmakerConfig, _con?: SavedConsole)
|
|||||||
if (remainingTime < mmConfig.minRemainingTime) continue;
|
if (remainingTime < mmConfig.minRemainingTime) continue;
|
||||||
if (playerCount === playerLimit) continue;
|
if (playerCount === playerLimit) continue;
|
||||||
if (window.location.href.includes(gameID)) continue;
|
if (window.location.href.includes(gameID)) continue;
|
||||||
if (currentMatch === gameID) continue;
|
|
||||||
|
|
||||||
games.push({ gameID, region, playerCount, playerLimit, map, gamemode, remainingTime });
|
games.push({ gameID, region, playerCount, playerLimit, map, gamemode, remainingTime });
|
||||||
}
|
}
|
||||||
|
|
||||||
_con?.log('[KCC-MM] Received', result.games?.length ?? 0, 'games,', games.length, 'passed filters');
|
return games;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortByPingThenPlayers(games: MatchmakerGame[], pings: Record<string, number>): MatchmakerGame[] {
|
||||||
|
return games.sort((a, b) => {
|
||||||
|
const pingA = pings[a.region] ?? 999;
|
||||||
|
const pingB = pings[b.region] ?? 999;
|
||||||
|
if (pingA !== pingB) return pingA - pingB;
|
||||||
|
return b.playerCount - a.playerCount;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchGame(mmConfig: MatchmakerConfig, _con?: SavedConsole): Promise<void> {
|
||||||
|
openServerBrowser = mmConfig.openServerBrowser;
|
||||||
|
confirmKey = mmConfig.acceptKey;
|
||||||
|
cancelKey = mmConfig.cancelKey;
|
||||||
|
|
||||||
|
// Dismiss existing popup if active
|
||||||
|
if (document.getElementById(POPUP_ID)) decideMatchmakerDecision(false);
|
||||||
|
|
||||||
|
_con?.log('[KCC-MM] Fetching game list + pings...');
|
||||||
|
|
||||||
|
const [games, pings] = await Promise.all([
|
||||||
|
fetchAndFilterGames(mmConfig),
|
||||||
|
ipcRenderer.invoke('ping-regions').catch(() => ({} as Record<string, number>)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
_con?.log('[KCC-MM]', games.length, 'games passed filters, pings:', pings);
|
||||||
|
|
||||||
if (games.length > 0) {
|
if (games.length > 0) {
|
||||||
const selected = games[Math.floor(Math.random() * games.length)];
|
sortByPingThenPlayers(games, pings);
|
||||||
_con?.log('[KCC-MM] Selected:', selected.gameID, selected.region, selected.map);
|
const best = games[0];
|
||||||
createFetchedGamePopup(selected);
|
_con?.log('[KCC-MM] Best match:', best.gameID, best.region, best.map, `(${pings[best.region] ?? '?'}ms)`);
|
||||||
|
|
||||||
|
if (mmConfig.autoJoin) {
|
||||||
|
window.location.href = `https://krunker.io/?game=${best.gameID}`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
createFetchedGamePopup(best);
|
||||||
} else {
|
} else {
|
||||||
_con?.log('[KCC-MM] No matching games found');
|
_con?.log('[KCC-MM] No matching games found');
|
||||||
|
|
||||||
|
if (mmConfig.autoJoin) {
|
||||||
|
if (openServerBrowser && typeof (window as any).openServerWindow === 'function') {
|
||||||
|
(window as any).openServerWindow(0);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
createFetchedGamePopup({
|
createFetchedGamePopup({
|
||||||
gameID: 'none',
|
gameID: 'none',
|
||||||
region: 'none',
|
region: 'none',
|
||||||
|
|||||||
Reference in New Issue
Block a user