diff --git a/frontend/src/components/BotList.svelte b/frontend/src/components/BotList.svelte index a427d50..3173415 100644 --- a/frontend/src/components/BotList.svelte +++ b/frontend/src/components/BotList.svelte @@ -210,7 +210,6 @@ function filterBots( } function scriptDuplicateAgentIdWarning(d: ToggleableScript): string | null { - const agentId = d.info.config.settings.agentId; if (!agentId) { diff --git a/frontend/src/components/Teams/TeamBotList.svelte b/frontend/src/components/Teams/TeamBotList.svelte index f4b9e53..1f048ce 100644 --- a/frontend/src/components/Teams/TeamBotList.svelte +++ b/frontend/src/components/Teams/TeamBotList.svelte @@ -12,7 +12,7 @@ import cannotAutoRunIcon from "../../assets/cannot_play.svg"; import defaultIcon from "../../assets/rlbot_mono.png"; import PlayerOverridesModal from "./PlayerOverridesModal.svelte"; -import {Browser} from "@wailsio/runtime"; +import { Browser } from "@wailsio/runtime"; import warningIcon from "../../assets/exclamation-triangle-fill.svg"; let { diff --git a/frontend/src/pages/Home.svelte b/frontend/src/pages/Home.svelte index 429c2a2..3c3b5c9 100644 --- a/frontend/src/pages/Home.svelte +++ b/frontend/src/pages/Home.svelte @@ -186,7 +186,7 @@ function collectDuplicateAgentIds( bots: DraggablePlayer[], scripts: ToggleableScript[], ): Set { - const agentIdTomlMap: { [id: string]: string } = {} + const agentIdTomlMap: { [id: string]: string } = {}; const duplicateAgentIds = new Set(); for (const bot of bots) { diff --git a/players.go b/players.go index f893368..f408b79 100644 --- a/players.go +++ b/players.go @@ -8,6 +8,7 @@ import ( "path/filepath" "runtime" "sort" + "strings" "github.com/BurntSushi/toml" "github.com/RLBot/go-interface/flat" @@ -188,31 +189,83 @@ func findTorchLibDir(fromPath string) string { return "" } -// setTorchEnv populates the Environment field with PATH/LD_LIBRARY_PATH -// pointing to the torch-archive lib directory, if it exists. +// pathSeparator returns the platform-specific path list separator as a string. +func pathSeparator() string { + if runtime.GOOS == "windows" { + return ";" + } + return ":" +} + +// resolveEnvironmentVariables resolves path-like prepend/postpend markers in +// environment variable values. If a value starts with the platform path +// separator, it is prepended to the current value (falling back to os.Getenv). +// If it ends with the separator, it is postpended. Otherwise the value is used +// as-is. +func resolveEnvironmentVariables(env []*flat.EnvironmentVariableT) []*flat.EnvironmentVariableT { + sep := pathSeparator() + resolvedMap := make(map[string]string) + result := make([]*flat.EnvironmentVariableT, 0, len(env)) + + for _, ev := range env { + existing := resolvedMap[ev.Name] + if existing == "" { + existing = os.Getenv(ev.Name) + } + + value := ev.Value + if strings.HasPrefix(value, sep) { + cleanValue := strings.TrimPrefix(value, sep) + if existing == "" { + value = cleanValue + } else { + value = cleanValue + sep + existing + } + } else if strings.HasSuffix(value, sep) { + cleanValue := strings.TrimSuffix(value, sep) + if existing == "" { + value = cleanValue + } else { + value = existing + sep + cleanValue + } + } + + resolvedMap[ev.Name] = value + result = append(result, &flat.EnvironmentVariableT{ + Name: ev.Name, + Value: value, + }) + } + + return result +} + +// setTorchEnv appends a PATH/LD_LIBRARY_PATH entry pointing to the +// torch-archive lib directory, if it exists. The value begins with the +// platform path separator so resolveEnvironmentVariables prepends it to the +// existing path. func setTorchEnv(tomlPath string, env *[]*flat.EnvironmentVariableT) { torchLibDir := findTorchLibDir(tomlPath) if torchLibDir == "" { return } + varName := "LD_LIBRARY_PATH" if runtime.GOOS == "windows" { - *env = append(*env, &flat.EnvironmentVariableT{ - Name: "PATH", - Value: torchLibDir + ";" + os.Getenv("PATH"), - }) - } else { - ldLibPath := os.Getenv("LD_LIBRARY_PATH") - if ldLibPath != "" { - ldLibPath = torchLibDir + ":" + ldLibPath - } else { - ldLibPath = torchLibDir + varName = "PATH" + } + + for _, ev := range *env { + if ev.Name == varName { + return } - *env = append(*env, &flat.EnvironmentVariableT{ - Name: "LD_LIBRARY_PATH", - Value: ldLibPath, - }) } + + sep := pathSeparator() + *env = append(*env, &flat.EnvironmentVariableT{ + Name: varName, + Value: sep + torchLibDir, + }) } func (botInfo BotInfo) ToPlayerConfig(team uint32) *flat.PlayerConfigurationT { @@ -244,8 +297,19 @@ func (botInfo BotInfo) ToPlayerConfig(team uint32) *flat.PlayerConfigurationT { Hivemind: botInfo.Config.Settings.Hivemind, } + // Add environment variables from the bot's config toml first + for k, v := range botInfo.Config.Settings.Environment { + customBot.Environment = append(customBot.Environment, &flat.EnvironmentVariableT{ + Name: k, + Value: v, + }) + } + + // Then append torch paths (if found) so they combine properly with any toml-specified paths setTorchEnv(botInfo.TomlPath, &customBot.Environment) + customBot.Environment = resolveEnvironmentVariables(customBot.Environment) + return &flat.PlayerConfigurationT{ Variety: &flat.PlayerClassT{ Type: flat.PlayerClassCustomBot, @@ -279,6 +343,8 @@ type BotSettings struct { RunCommandLinux string `toml:"run_command_linux" json:"runCommandLinux"` // If bot can handle multiple agents with one client Hivemind bool `toml:"hivemind" json:"hivemind"` + // Additional environment variables to set for the bot process + Environment map[string]string `toml:"environment" json:"environment"` } type BotDetails struct { diff --git a/scripts.go b/scripts.go index 930be60..858f4a5 100644 --- a/scripts.go +++ b/scripts.go @@ -28,8 +28,19 @@ func (botInfo BotInfo) ToScriptConfig() *flat.ScriptConfigurationT { ScriptId: 0, // let core do this } + // Add environment variables from the script's config toml first + for k, v := range botInfo.Config.Settings.Environment { + scriptConfig.Environment = append(scriptConfig.Environment, &flat.EnvironmentVariableT{ + Name: k, + Value: v, + }) + } + + // Then append torch paths (if found) so they combine properly with any toml-specified paths setTorchEnv(botInfo.TomlPath, &scriptConfig.Environment) + scriptConfig.Environment = resolveEnvironmentVariables(scriptConfig.Environment) + return scriptConfig }