From ba9abcb7bda6d09f7728aadb253f8cf290abc5a3 Mon Sep 17 00:00:00 2001 From: NicEastvillage Date: Sat, 22 Nov 2025 15:02:29 +0100 Subject: [PATCH 1/7] Allow edits of auto start and Psyonix names --- frontend/src/base-players.ts | 5 + .../Teams/PlayerOverridesModal.svelte | 37 +++++++ .../src/components/Teams/TeamBotList.svelte | 99 ++++++------------- frontend/src/index.ts | 10 +- frontend/src/pages/Home.svelte | 12 +-- 5 files changed, 85 insertions(+), 78 deletions(-) create mode 100644 frontend/src/components/Teams/PlayerOverridesModal.svelte diff --git a/frontend/src/base-players.ts b/frontend/src/base-players.ts index 7c14dbe..e45de9d 100644 --- a/frontend/src/base-players.ts +++ b/frontend/src/base-players.ts @@ -9,6 +9,7 @@ export const BASE_PLAYERS: DraggablePlayer[] = [ id: crypto.randomUUID(), player: new HumanInfo(), tags: ["human"], + overrides: { name: "Human", loadout: null, auto_start: true }, }, { displayName: "Psyonix Beginner", @@ -18,6 +19,7 @@ export const BASE_PLAYERS: DraggablePlayer[] = [ skill: 0, }), tags: ["psyonix"], + overrides: { name: "", loadout: null, auto_start: true }, }, { displayName: "Psyonix Rookie", @@ -27,6 +29,7 @@ export const BASE_PLAYERS: DraggablePlayer[] = [ skill: 1, }), tags: ["psyonix"], + overrides: { name: "", loadout: null, auto_start: true }, }, { displayName: "Psyonix Pro", @@ -36,6 +39,7 @@ export const BASE_PLAYERS: DraggablePlayer[] = [ skill: 2, }), tags: ["psyonix"], + overrides: { name: "", loadout: null, auto_start: true }, }, { displayName: "Psyonix Allstar", @@ -45,5 +49,6 @@ export const BASE_PLAYERS: DraggablePlayer[] = [ skill: 3, }), tags: ["psyonix"], + overrides: { name: "", loadout: null, auto_start: true }, }, ]; diff --git a/frontend/src/components/Teams/PlayerOverridesModal.svelte b/frontend/src/components/Teams/PlayerOverridesModal.svelte new file mode 100644 index 0000000..b99b21c --- /dev/null +++ b/frontend/src/components/Teams/PlayerOverridesModal.svelte @@ -0,0 +1,37 @@ + + +{#if player} + +

In-game name

+ +
+
+ {#if player.player instanceof BotInfo} + + + {/if} + +
+{/if} \ No newline at end of file diff --git a/frontend/src/components/Teams/TeamBotList.svelte b/frontend/src/components/Teams/TeamBotList.svelte index 89ddcfb..f1f9bb3 100644 --- a/frontend/src/components/Teams/TeamBotList.svelte +++ b/frontend/src/components/Teams/TeamBotList.svelte @@ -5,12 +5,13 @@ import { untrack } from "svelte"; import { flip } from "svelte/animate"; import { fade } from "svelte/transition"; import type { DraggablePlayer } from "../.."; -import { BotInfo } from "../../../bindings/gui"; +import {BotInfo, PsyonixBotInfo} from "../../../bindings/gui"; import closeIcon from "../../assets/close.svg"; import duplicateIcon from "../../assets/duplicate.svg"; import editIcon from "../../assets/edit.svg"; import defaultIcon from "../../assets/rlbot_mono.png"; import ModalPrompt from "../ModalPrompt.svelte"; +import PlayerOverridesModal from "./PlayerOverridesModal.svelte"; let { items = $bindable() }: { items: DraggablePlayer[] } = $props(); @@ -31,56 +32,30 @@ function dupe(id: string): any { } } -let editPrompts: { [key: string]: ModalPrompt } = {}; -let editDataNames: { [key: string]: string } = $state({}); +let editedPlayer: DraggablePlayer | undefined = $state(undefined) +let showEditPrompt = $state(false) -// Update editDataNames once items updates, but try to keep as much state as possible -// Once editing more stuff is supported, this approach should probably change -$effect(() => { - let localEditDataNames = untrack(() => { - return editDataNames; - }); - let updated = Object.fromEntries( - items - .filter((x) => { - return x.player instanceof BotInfo; - }) - .map((bot) => { - return [ - bot.id, - // use ! + ?: instead of ?? so that "" is treated as null - // this is due to the input binding value to "" before this is ran - !localEditDataNames[bot.id] - ? (bot.player).config.settings.name - : localEditDataNames[bot.id], - ]; - }), - ); - untrack(() => { - editDataNames = updated; - }); -}); - -async function edit_custom_bot(id: string): Promise { - let modal = editPrompts[id]; - - let modified = await modal.prompt(); - if (modified) { - const index = items.findIndex((x) => x.id === id); - let copy = { - ...items[index], - // We need to deepclone here to make sure we don't modify the player globally. - // We also need to do BotInfo.createFrom to make sure that instanceof BotInfo == true still. - player: BotInfo.createFrom(structuredClone(items[index].player)), - }; - copy.modified = true; - if (copy.player instanceof BotInfo) { - copy.player.config.settings.name = editDataNames[id]; - } - items[index] = copy; +async function showEditModal(player: DraggablePlayer) { + if (player) { + editedPlayer = player + showEditPrompt = true; } } +// async function edit_custom_bot(id: string): Promise { +// let modal = editPrompts[id]; +// +// let modified = await modal.prompt(); +// if (modified) { +// const index = items.findIndex((x) => x.id === id); +// let copy = {...items[index]}; +// // if (copy.player instanceof BotInfo) { +// // copy.player.config.settings.name = editDataNames[id]; +// // } +// items[index] = copy; +// } +// } + // :fire: :fire: :fire: let resolveDeleted = () => {}; let _waitingForDelete: Promise; @@ -157,7 +132,7 @@ const dnd_container_namespace = `team_${crypto.randomUUID()}`; }} animate:flip={{ duration: 100 }} in:fade={{ duration: 100 }} - out:fade={{ duration: 100 }} + out:fade={{ duration: 100 }} >
icon

{bot.displayName}

+ style={!bot.overrides.auto_start ? "color: orange" : ""} + >{bot.displayName == bot.overrides.name ? bot.displayName : `${bot.overrides.name} [${bot.displayName}]`}

{#if bot.uniquePathSegment} ({bot.uniquePathSegment}) {/if}
- {#if bot.player instanceof BotInfo} - {/if} - {#if !bot.tags.includes("human")}
-{#each items as bot (bot.id)} - -
- - - - -
-
-{/each} + \ No newline at end of file From d1c124190a755f4b4927986221d9e32ef089f6bf Mon Sep 17 00:00:00 2001 From: NicEastvillage Date: Sun, 23 Nov 2025 14:36:51 +0100 Subject: [PATCH 4/7] Add loadout override functionality --- app.go | 11 ++++++ .../Teams/PlayerOverridesModal.svelte | 37 +++++++++++++------ players.go | 17 +++++++-- 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/app.go b/app.go index 90aa879..13edb4d 100644 --- a/app.go +++ b/app.go @@ -463,6 +463,17 @@ func (a *App) PickRLBotToml() (string, error) { return "", fmt.Errorf("invalid file name") } +func (a *App) PickToml() (string, error) { + path, err := zenity.SelectFile(zenity.FileFilter{ + Name: ".toml files", + Patterns: []string{"*.toml"}, + }) + if err != nil { + return "", nil + } + return path, nil +} + func (a *App) ShowPathInExplorer(path string) error { fileInfo, err := os.Stat(path) if err != nil { diff --git a/frontend/src/components/Teams/PlayerOverridesModal.svelte b/frontend/src/components/Teams/PlayerOverridesModal.svelte index 53c887a..d8f5cd0 100644 --- a/frontend/src/components/Teams/PlayerOverridesModal.svelte +++ b/frontend/src/components/Teams/PlayerOverridesModal.svelte @@ -1,10 +1,9 @@ {#if player} @@ -33,15 +39,22 @@ async function clearOverrides() { >

+ {#if !(player.player instanceof PsyonixBotInfo)} +

Loadout: {player.overrides.loadout ? "Customized" : ""}

+ +
+
+ {/if} {#if player.player instanceof BotInfo} - - + + {/if} +
diff --git a/players.go b/players.go index 24403ba..691d87b 100644 --- a/players.go +++ b/players.go @@ -294,11 +294,9 @@ func (a *App) GetBots(paths []string) []BotInfo { var loadout *LoadoutConfig = nil if conf.Settings.LoadoutFile != "" { loadoutPath := filepath.Join(filepath.Dir(potentialConfigPath), conf.Settings.LoadoutFile) - loadoutData, err := os.ReadFile(loadoutPath) + loadout, err = a.GetLoadout(loadoutPath) if err != nil { println("WARN: failed to read loadout file at " + conf.Settings.LoadoutFile) - } else { - toml.Decode(string(loadoutData), &loadout) } } @@ -316,3 +314,16 @@ func (a *App) GetBots(paths []string) []BotInfo { return infos } + +func (a *App) GetLoadout(path string) (*LoadoutConfig, error) { + loadoutData, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var loadout *LoadoutConfig = nil + _, err = toml.Decode(string(loadoutData), &loadout) + if err != nil { + return nil, err + } + return loadout, nil +} From de4ec43056d8a96c383418dd2daa52b004b13961 Mon Sep 17 00:00:00 2001 From: NicEastvillage Date: Sun, 23 Nov 2025 16:28:23 +0100 Subject: [PATCH 5/7] Make overrides beautiful --- frontend/src/assets/cannot_play.svg | 1 + frontend/src/assets/roller.svg | 1 + .../Teams/PlayerOverridesModal.svelte | 15 +++-- .../src/components/Teams/TeamBotList.svelte | 65 ++++++++++++------- 4 files changed, 52 insertions(+), 30 deletions(-) create mode 100644 frontend/src/assets/cannot_play.svg create mode 100644 frontend/src/assets/roller.svg diff --git a/frontend/src/assets/cannot_play.svg b/frontend/src/assets/cannot_play.svg new file mode 100644 index 0000000..942cd3c --- /dev/null +++ b/frontend/src/assets/cannot_play.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/roller.svg b/frontend/src/assets/roller.svg new file mode 100644 index 0000000..e5a20b8 --- /dev/null +++ b/frontend/src/assets/roller.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/Teams/PlayerOverridesModal.svelte b/frontend/src/components/Teams/PlayerOverridesModal.svelte index d8f5cd0..679b067 100644 --- a/frontend/src/components/Teams/PlayerOverridesModal.svelte +++ b/frontend/src/components/Teams/PlayerOverridesModal.svelte @@ -19,6 +19,12 @@ function clearOverrides() { } } +function hasNameOverride(): boolean { + if (!player) return false; + let expectedName = player.player instanceof BotInfo ? player.displayName : ""; + return player.overrides.name !== expectedName; +} + async function pickLoadoutOverride() { if (!player) return; let path = await App.PickToml(); @@ -30,7 +36,7 @@ async function pickLoadoutOverride() { {#if player} -

In-game name

+

In-game name



- {#if !(player.player instanceof PsyonixBotInfo)} -

Loadout: {player.overrides.loadout ? "Customized" : ""}

+ {#if player.player instanceof BotInfo || player.player instanceof PsyonixBotInfo} +

Loadout: {player.overrides.loadout ? "Customized" : ""}



@@ -51,9 +57,8 @@ async function pickLoadoutOverride() { id={`edit-auto-start-${player.id}`} bind:checked={player.overrides.auto_start} > - + {/if} -
diff --git a/frontend/src/components/Teams/TeamBotList.svelte b/frontend/src/components/Teams/TeamBotList.svelte index 8555a95..b66af4d 100644 --- a/frontend/src/components/Teams/TeamBotList.svelte +++ b/frontend/src/components/Teams/TeamBotList.svelte @@ -9,6 +9,8 @@ import {BotInfo, PsyonixBotInfo} from "../../../bindings/gui"; import closeIcon from "../../assets/close.svg"; import duplicateIcon from "../../assets/duplicate.svg"; import editIcon from "../../assets/edit.svg"; +import rollerIcon from "../../assets/roller.svg"; +import cannotAutoRunIcon from "../../assets/cannot_play.svg"; import defaultIcon from "../../assets/rlbot_mono.png"; import ModalPrompt from "../ModalPrompt.svelte"; import PlayerOverridesModal from "./PlayerOverridesModal.svelte"; @@ -36,26 +38,24 @@ function dupe(id: string): any { let editedPlayer: DraggablePlayer | undefined = $state(undefined) let showEditPrompt = $state(false) -async function showEditModal(player: DraggablePlayer) { - if (player) { - editedPlayer = player +async function showEditModal(d: DraggablePlayer) { + if (d) { + editedPlayer = d showEditPrompt = true; } } -// async function edit_custom_bot(id: string): Promise { -// let modal = editPrompts[id]; -// -// let modified = await modal.prompt(); -// if (modified) { -// const index = items.findIndex((x) => x.id === id); -// let copy = {...items[index]}; -// // if (copy.player instanceof BotInfo) { -// // copy.player.config.settings.name = editDataNames[id]; -// // } -// items[index] = copy; -// } -// } +function canAutoStart(d: DraggablePlayer): boolean { + if (d.player instanceof BotInfo) { + return d.overrides.auto_start && d.player.config.settings.runCommand != ""; + } + return true; +} + +function hasOverrides(d: DraggablePlayer): boolean { + let expectedName = (d.player instanceof BotInfo) ? d.displayName : ""; + return d.overrides.name !== expectedName || !d.overrides.auto_start || d.overrides.loadout != null +} // :fire: :fire: :fire: let resolveDeleted = () => {}; @@ -146,23 +146,24 @@ const dnd_container_namespace = `team_${crypto.randomUUID()}`; onclick={e => e.stopPropagation()} > icon -

{bot.displayName == bot.overrides.name || (bot.player instanceof PsyonixBotInfo && bot.overrides.name == "") ? bot.displayName : `${bot.overrides.name} [${bot.displayName}]`}

+

{bot.displayName === bot.overrides.name || (bot.player instanceof PsyonixBotInfo && bot.overrides.name === "") ? bot.displayName : `${bot.overrides.name}`}

{#if bot.uniquePathSegment} ({bot.uniquePathSegment}) {/if} -
- {#if bot.player instanceof BotInfo || bot.player instanceof PsyonixBotInfo} - + {#if !canAutoStart(bot)} + No auto-run {/if} +
{#if !bot.tags.includes("human")} {/if} + {#if bot.player instanceof BotInfo || bot.player instanceof PsyonixBotInfo} + + {/if} @@ -242,19 +243,33 @@ const dnd_container_namespace = `team_${crypto.randomUUID()}`; .bot:not(:hover) :is(.duplicate, .edit) { visibility: hidden; } + .has-overrides img { + visibility: visible; + } .close, .duplicate, .edit { background-color: transparent; height: 100%; padding: 0; } .duplicate { - padding: 0 .2rem; + padding: 0 .1rem; } :is(.close, .duplicate, .edit) img { height: 100%; width: auto; filter: invert(); } + :is(.has-overrides) img { + filter: invert(68%) sepia(66%) saturate(2755%) hue-rotate(360deg) brightness(103%) contrast(104%); + } + .bot-icon { + background-color: transparent; + padding: 0 .1rem; + margin: .4rem 0; + height: 100%; + width: auto; + filter: invert(60%); + } .unique-bot-identifier { color: #868686; } From 7769897584baacfd38963be97c6f2404887bad7c Mon Sep 17 00:00:00 2001 From: NicEastvillage Date: Sun, 23 Nov 2025 16:52:08 +0100 Subject: [PATCH 6/7] Add missing colon --- frontend/src/components/Teams/PlayerOverridesModal.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Teams/PlayerOverridesModal.svelte b/frontend/src/components/Teams/PlayerOverridesModal.svelte index 679b067..6450178 100644 --- a/frontend/src/components/Teams/PlayerOverridesModal.svelte +++ b/frontend/src/components/Teams/PlayerOverridesModal.svelte @@ -36,7 +36,7 @@ async function pickLoadoutOverride() { {#if player} -

In-game name

+

In-game name:

Date: Mon, 24 Nov 2025 11:53:08 +0100 Subject: [PATCH 7/7] Show manual-start icon show when Extra->auto-start is disabled --- frontend/src/assets/roller.svg | 1 - frontend/src/base-players.ts | 10 +++---- frontend/src/components/Teams/Main.svelte | 6 +++-- .../Teams/PlayerOverridesModal.svelte | 6 ++--- .../src/components/Teams/TeamBotList.svelte | 27 +++++++++++++------ frontend/src/index.ts | 4 +-- frontend/src/pages/Home.svelte | 4 +-- 7 files changed, 35 insertions(+), 23 deletions(-) delete mode 100644 frontend/src/assets/roller.svg diff --git a/frontend/src/assets/roller.svg b/frontend/src/assets/roller.svg deleted file mode 100644 index e5a20b8..0000000 --- a/frontend/src/assets/roller.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/base-players.ts b/frontend/src/base-players.ts index 254f1bf..8ab8d03 100644 --- a/frontend/src/base-players.ts +++ b/frontend/src/base-players.ts @@ -9,7 +9,7 @@ export const BASE_PLAYERS: DraggablePlayer[] = [ id: crypto.randomUUID(), player: new HumanInfo(), tags: ["human"], - overrides: { name: "Human", loadout: null, auto_start: true }, + overrides: { name: "Human", loadout: null, autoStart: true }, }, { displayName: "Psyonix Beginner", @@ -20,7 +20,7 @@ export const BASE_PLAYERS: DraggablePlayer[] = [ skill: 0, }), tags: ["psyonix"], - overrides: { name: "", loadout: null, auto_start: true }, + overrides: { name: "", loadout: null, autoStart: true }, }, { displayName: "Psyonix Rookie", @@ -31,7 +31,7 @@ export const BASE_PLAYERS: DraggablePlayer[] = [ skill: 1, }), tags: ["psyonix"], - overrides: { name: "", loadout: null, auto_start: true }, + overrides: { name: "", loadout: null, autoStart: true }, }, { displayName: "Psyonix Pro", @@ -42,7 +42,7 @@ export const BASE_PLAYERS: DraggablePlayer[] = [ skill: 2, }), tags: ["psyonix"], - overrides: { name: "", loadout: null, auto_start: true }, + overrides: { name: "", loadout: null, autoStart: true }, }, { displayName: "Psyonix Allstar", @@ -53,6 +53,6 @@ export const BASE_PLAYERS: DraggablePlayer[] = [ skill: 3, }), tags: ["psyonix"], - overrides: { name: "", loadout: null, auto_start: true }, + overrides: { name: "", loadout: null, autoStart: true }, }, ]; diff --git a/frontend/src/components/Teams/Main.svelte b/frontend/src/components/Teams/Main.svelte index f0624eb..018cff4 100644 --- a/frontend/src/components/Teams/Main.svelte +++ b/frontend/src/components/Teams/Main.svelte @@ -5,10 +5,12 @@ let { bluePlayers = $bindable(), orangePlayers = $bindable(), selectedTeam = $bindable(null), + globalAutoStart = $bindable(true), }: { bluePlayers: any[]; orangePlayers: any[]; selectedTeam: "blue" | "orange" | null; + globalAutoStart: boolean; } = $props(); function toggleTeam(team: "blue" | "orange") { @@ -29,7 +31,7 @@ function toggleTeam(team: "blue" | "orange") {

{bluePlayers?.length || 0} bots

- +
toggleTeam('orange')} class:selected={selectedTeam === 'orange'}>
@@ -37,7 +39,7 @@ function toggleTeam(team: "blue" | "orange") {

{orangePlayers?.length || 0} bots

- +
diff --git a/frontend/src/components/Teams/PlayerOverridesModal.svelte b/frontend/src/components/Teams/PlayerOverridesModal.svelte index 6450178..d00a40d 100644 --- a/frontend/src/components/Teams/PlayerOverridesModal.svelte +++ b/frontend/src/components/Teams/PlayerOverridesModal.svelte @@ -15,7 +15,7 @@ function clearOverrides() { if (player) { player.overrides.name = player.player instanceof PsyonixBotInfo ? "" : player.displayName; player.overrides.loadout = null; - player.overrides.auto_start = true; + player.overrides.autoStart = true; } } @@ -55,9 +55,9 @@ async function pickLoadoutOverride() { - + {/if}
diff --git a/frontend/src/components/Teams/TeamBotList.svelte b/frontend/src/components/Teams/TeamBotList.svelte index b66af4d..0b84828 100644 --- a/frontend/src/components/Teams/TeamBotList.svelte +++ b/frontend/src/components/Teams/TeamBotList.svelte @@ -9,13 +9,17 @@ import {BotInfo, PsyonixBotInfo} from "../../../bindings/gui"; import closeIcon from "../../assets/close.svg"; import duplicateIcon from "../../assets/duplicate.svg"; import editIcon from "../../assets/edit.svg"; -import rollerIcon from "../../assets/roller.svg"; import cannotAutoRunIcon from "../../assets/cannot_play.svg"; import defaultIcon from "../../assets/rlbot_mono.png"; -import ModalPrompt from "../ModalPrompt.svelte"; import PlayerOverridesModal from "./PlayerOverridesModal.svelte"; -let { items = $bindable() }: { items: DraggablePlayer[] } = $props(); +let { + items = $bindable(), + globalAutoStart = $bindable(true) +}: { + items: DraggablePlayer[], + globalAutoStart: boolean, +} = $props(); function remove(id: string): any { items = items.filter((x) => x.id !== id); @@ -45,16 +49,23 @@ async function showEditModal(d: DraggablePlayer) { } } -function canAutoStart(d: DraggablePlayer): boolean { +function reasonsForManualStart(d: DraggablePlayer): string[] { + let reasons: string[] = [] if (d.player instanceof BotInfo) { - return d.overrides.auto_start && d.player.config.settings.runCommand != ""; + if (!globalAutoStart) reasons.push("Auto-start disabled in Extra"); + if (!d.player.config.settings.runCommand) reasons.push("No run_command declared"); + if (!d.overrides.autoStart) reasons.push("Auto-start disabled for bot"); } - return true; + return reasons +} + +function canAutoStart(d: DraggablePlayer): boolean { + return reasonsForManualStart(d).length == 0; } function hasOverrides(d: DraggablePlayer): boolean { let expectedName = (d.player instanceof BotInfo) ? d.displayName : ""; - return d.overrides.name !== expectedName || !d.overrides.auto_start || d.overrides.loadout != null + return d.overrides.name !== expectedName || !d.overrides.autoStart || d.overrides.loadout != null } // :fire: :fire: :fire: @@ -151,7 +162,7 @@ const dnd_container_namespace = `team_${crypto.randomUUID()}`; ({bot.uniquePathSegment}) {/if} {#if !canAutoStart(bot)} - No auto-run + No auto-run {/if}
{#if !bot.tags.includes("human")} diff --git a/frontend/src/index.ts b/frontend/src/index.ts index 11bb8c4..314e67f 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -35,7 +35,7 @@ export function parseJSON(item: string | null): any | null { export interface PlayerFieldOverrides { name: string; loadout: LoadoutConfig | null - auto_start: boolean; + autoStart: boolean; } export interface DraggablePlayer { @@ -63,7 +63,7 @@ export function draggablePlayerToPlayerJs(d: DraggablePlayer): PlayerJs { // Apply overrides player.config.settings.name = d.overrides.name; player.loadout = d.overrides.loadout ?? d.player.loadout; - if (!d.overrides.auto_start) { + if (!d.overrides.autoStart) { player.config.settings.runCommand = "" player.config.settings.runCommandLinux = "" } diff --git a/frontend/src/pages/Home.svelte b/frontend/src/pages/Home.svelte index bed4208..8024875 100644 --- a/frontend/src/pages/Home.svelte +++ b/frontend/src/pages/Home.svelte @@ -215,7 +215,7 @@ async function updateBots() { overrides: { name: x.config.settings.name, loadout: null, - auto_start: true, + autoStart: true, }, }; }), @@ -422,7 +422,7 @@ function handleSearch(event: Event) { />
-
+