diff --git a/README.md b/README.md index a97868c..be1eb5f 100644 --- a/README.md +++ b/README.md @@ -412,14 +412,28 @@ The remote deployment process transfers: - ❌ Build artifacts (source code, compilation files stay local) ### Module Presets -- Drop comma-separated module lists into `profiles/*.conf` (for example `profiles/playerbot-modules.conf`). +- Define JSON presets in `profiles/*.json`. Each file contains: + - `modules` (array, required) – list of `MODULE_*` identifiers to enable. + - `label` (string, optional) – text shown in the setup menu (emoji welcome). + - `description` (string, optional) – short help text for maintainers. + - `order` (number, optional) – determines the menu position (lower appears first). + Example: + + ```json + { + "modules": ["MODULE_ELUNA", "MODULE_SOLO_LFG", "MODULE_SOLOCRAFT"], + "label": "⭐ Suggested Modules", + "description": "Baseline solo-friendly quality of life mix", + "order": 1 + } + ``` - `setup.sh` automatically adds these presets to the module menu and enables the listed modules when selected or when `--module-config ` is provided. - Built-in presets: - - `profiles/suggested-modules.conf` – default solo-friendly QoL stack. - - `profiles/playerbots-suggested-modules.conf` – suggested stack plus playerbots. - - `profiles/playerbot-only.conf` – playerbot-focused profile (adjust `--playerbot-max-bots`). + - `profiles/suggested-modules.json` – default solo-friendly QoL stack. + - `profiles/playerbots-suggested-modules.json` – suggested stack plus playerbots. + - `profiles/playerbots-only.json` – playerbot-focused profile (adjust `--playerbot-max-bots`). - Custom example: - - `profiles/sam.conf` – Sam's playerbot-focused profile (set `--playerbot-max-bots 3000` when using this preset). + - `profiles/sam.json` – Sam's playerbot-focused profile (set `--playerbot-max-bots 3000` when using this preset). --- diff --git a/profiles/all-modules.conf b/profiles/all-modules.conf deleted file mode 100644 index 102a3c2..0000000 --- a/profiles/all-modules.conf +++ /dev/null @@ -1 +0,0 @@ -MODULE_1V1_ARENA,MODULE_ACCOUNTWIDE_SYSTEMS,MODULE_ACCOUNT_ACHIEVEMENTS,MODULE_ACCOUNT_MOUNTS,MODULE_ACORE_SUBSCRIPTIONS,MODULE_ACTIVE_CHAT,MODULE_AHBOT,MODULE_AIO,MODULE_AIO_BLACKJACK,MODULE_ANTIFARMING,MODULE_AOE_LOOT,MODULE_ARAC,MODULE_ARENA_REPLAY,MODULE_ASSISTANT,MODULE_AUTOBALANCE,MODULE_AUTO_REVIVE,MODULE_AZEROTHSHARD,MODULE_BG_SLAVERYVALLEY,MODULE_BLACK_MARKET_AUCTION_HOUSE,MODULE_BOSS_ANNOUNCER,MODULE_BREAKING_NEWS,MODULE_CARBON_COPY,MODULE_CHALLENGE_MODES,MODULE_DISCORD_NOTIFIER,MODULE_DUEL_RESET,MODULE_DUNGEON_RESPAWN,MODULE_DYNAMIC_TRADER,MODULE_DYNAMIC_XP,MODULE_ELUNA,MODULE_ELUNA_SCRIPTS,MODULE_ELUNA_TS,MODULE_EVENT_SCRIPTS,MODULE_EXCHANGE_NPC,MODULE_FIREWORKS,MODULE_GAIN_HONOR_GUARD,MODULE_GLOBAL_CHAT,MODULE_GLOBAL_MAIL_BANKING_AUCTIONS,MODULE_GUILDHOUSE,MODULE_HARDCORE_MODE,MODULE_HORADRIC_CUBE,MODULE_INDIVIDUAL_PROGRESSION,MODULE_INSTANCE_RESET,MODULE_ITEM_LEVEL_UP,MODULE_KEEP_OUT,MODULE_LEARN_SPELLS,MODULE_LEVEL_GRANT,MODULE_LEVEL_UP_REWARD,MODULE_LOTTERY_LUA,MODULE_LUA_AH_BOT,MODULE_MORPHSUMMON,MODULE_MULTIVENDOR,MODULE_NPCBOT_EXTENDED_COMMANDS,MODULE_NPC_BEASTMASTER,MODULE_NPC_BUFFER,MODULE_NPC_ENCHANTER,MODULE_NPC_FREE_PROFESSIONS,MODULE_NPC_TALENT_TEMPLATE,MODULE_OLLAMA_CHAT,MODULE_PHASED_DUELS,MODULE_PLAYERBOTS,MODULE_PLAYER_BOT_LEVEL_BRACKETS,MODULE_POCKET_PORTAL,MODULE_PREMIUM,MODULE_PRESTIGE_DRAFT_MODE,MODULE_PROGRESSION_SYSTEM,MODULE_PROMOTION_AZEROTHCORE,MODULE_PVP_TITLES,MODULE_RANDOM_ENCHANTS,MODULE_REAGENT_BANK,MODULE_RECRUIT_A_FRIEND,MODULE_RESURRECTION_SCROLL,MODULE_REWARD_PLAYED_TIME,MODULE_SEND_AND_BIND,MODULE_SERVER_AUTO_SHUTDOWN,MODULE_SOLOCRAFT,MODULE_SOLO_LFG,MODULE_STATBOOSTER,MODULE_SYSTEM_VIP,MODULE_TEMP_ANNOUNCEMENTS,MODULE_TIC_TAC_TOE,MODULE_TIME_IS_TIME,MODULE_TRANSMOG,MODULE_TRANSMOG_AIO,MODULE_TREASURE_CHEST_SYSTEM,MODULE_ULTIMATE_FULL_LOOT_PVP,MODULE_WAR_EFFORT,MODULE_WEEKEND_XP,MODULE_WHO_LOGGED,MODULE_WORGOBLIN,MODULE_ZONE_CHECK,MODULE_ZONE_DIFFICULTY diff --git a/profiles/all-modules.json b/profiles/all-modules.json new file mode 100644 index 0000000..19a9ece --- /dev/null +++ b/profiles/all-modules.json @@ -0,0 +1,98 @@ +{ + "modules": [ + "MODULE_1V1_ARENA", + "MODULE_ACCOUNTWIDE_SYSTEMS", + "MODULE_ACCOUNT_ACHIEVEMENTS", + "MODULE_ACCOUNT_MOUNTS", + "MODULE_ACORE_SUBSCRIPTIONS", + "MODULE_ACTIVE_CHAT", + "MODULE_AHBOT", + "MODULE_AIO", + "MODULE_AIO_BLACKJACK", + "MODULE_ANTIFARMING", + "MODULE_AOE_LOOT", + "MODULE_ARAC", + "MODULE_ARENA_REPLAY", + "MODULE_ASSISTANT", + "MODULE_AUTOBALANCE", + "MODULE_AUTO_REVIVE", + "MODULE_AZEROTHSHARD", + "MODULE_BG_SLAVERYVALLEY", + "MODULE_BLACK_MARKET_AUCTION_HOUSE", + "MODULE_BOSS_ANNOUNCER", + "MODULE_BREAKING_NEWS", + "MODULE_CARBON_COPY", + "MODULE_CHALLENGE_MODES", + "MODULE_DISCORD_NOTIFIER", + "MODULE_DUEL_RESET", + "MODULE_DUNGEON_RESPAWN", + "MODULE_DYNAMIC_TRADER", + "MODULE_DYNAMIC_XP", + "MODULE_ELUNA", + "MODULE_ELUNA_SCRIPTS", + "MODULE_ELUNA_TS", + "MODULE_EVENT_SCRIPTS", + "MODULE_EXCHANGE_NPC", + "MODULE_FIREWORKS", + "MODULE_GAIN_HONOR_GUARD", + "MODULE_GLOBAL_CHAT", + "MODULE_GLOBAL_MAIL_BANKING_AUCTIONS", + "MODULE_GUILDHOUSE", + "MODULE_HARDCORE_MODE", + "MODULE_HORADRIC_CUBE", + "MODULE_INDIVIDUAL_PROGRESSION", + "MODULE_INSTANCE_RESET", + "MODULE_ITEM_LEVEL_UP", + "MODULE_KEEP_OUT", + "MODULE_LEARN_SPELLS", + "MODULE_LEVEL_GRANT", + "MODULE_LEVEL_UP_REWARD", + "MODULE_LOTTERY_LUA", + "MODULE_LUA_AH_BOT", + "MODULE_MORPHSUMMON", + "MODULE_MULTIVENDOR", + "MODULE_NPCBOT_EXTENDED_COMMANDS", + "MODULE_NPC_BEASTMASTER", + "MODULE_NPC_BUFFER", + "MODULE_NPC_ENCHANTER", + "MODULE_NPC_FREE_PROFESSIONS", + "MODULE_NPC_TALENT_TEMPLATE", + "MODULE_OLLAMA_CHAT", + "MODULE_PHASED_DUELS", + "MODULE_PLAYERBOTS", + "MODULE_PLAYER_BOT_LEVEL_BRACKETS", + "MODULE_POCKET_PORTAL", + "MODULE_PREMIUM", + "MODULE_PRESTIGE_DRAFT_MODE", + "MODULE_PROGRESSION_SYSTEM", + "MODULE_PROMOTION_AZEROTHCORE", + "MODULE_PVP_TITLES", + "MODULE_RANDOM_ENCHANTS", + "MODULE_REAGENT_BANK", + "MODULE_RECRUIT_A_FRIEND", + "MODULE_RESURRECTION_SCROLL", + "MODULE_REWARD_PLAYED_TIME", + "MODULE_SEND_AND_BIND", + "MODULE_SERVER_AUTO_SHUTDOWN", + "MODULE_SOLOCRAFT", + "MODULE_SOLO_LFG", + "MODULE_STATBOOSTER", + "MODULE_SYSTEM_VIP", + "MODULE_TEMP_ANNOUNCEMENTS", + "MODULE_TIC_TAC_TOE", + "MODULE_TIME_IS_TIME", + "MODULE_TRANSMOG", + "MODULE_TRANSMOG_AIO", + "MODULE_TREASURE_CHEST_SYSTEM", + "MODULE_ULTIMATE_FULL_LOOT_PVP", + "MODULE_WAR_EFFORT", + "MODULE_WEEKEND_XP", + "MODULE_WHO_LOGGED", + "MODULE_WORGOBLIN", + "MODULE_ZONE_CHECK", + "MODULE_ZONE_DIFFICULTY" + ], + "label": "\ud83e\udde9 All Modules", + "description": "Enable every optional module in the repository", + "order": 5 +} \ No newline at end of file diff --git a/profiles/playerbots-only.conf b/profiles/playerbots-only.conf deleted file mode 100644 index b06343a..0000000 --- a/profiles/playerbots-only.conf +++ /dev/null @@ -1 +0,0 @@ -MODULE_PLAYERBOTS,MODULE_ELUNA,MODULE_ELUNA_TS \ No newline at end of file diff --git a/profiles/playerbots-only.json b/profiles/playerbots-only.json new file mode 100644 index 0000000..4b53366 --- /dev/null +++ b/profiles/playerbots-only.json @@ -0,0 +1,10 @@ +{ + "modules": [ + "MODULE_PLAYERBOTS", + "MODULE_ELUNA", + "MODULE_ELUNA_TS" + ], + "label": "\ud83e\udde9 Playerbots Only", + "description": "Minimal preset that only enables playerbot prerequisites", + "order": 6 +} \ No newline at end of file diff --git a/profiles/playerbots-suggested-modules.conf b/profiles/playerbots-suggested-modules.conf deleted file mode 100644 index 100ee71..0000000 --- a/profiles/playerbots-suggested-modules.conf +++ /dev/null @@ -1 +0,0 @@ -MODULE_PLAYERBOTS,MODULE_SOLO_LFG,MODULE_SOLOCRAFT,MODULE_AUTOBALANCE,MODULE_TRANSMOG,MODULE_NPC_BUFFER,MODULE_LEARN_SPELLS,MODULE_FIREWORKS diff --git a/profiles/playerbots-suggested-modules.json b/profiles/playerbots-suggested-modules.json new file mode 100644 index 0000000..c9e2b44 --- /dev/null +++ b/profiles/playerbots-suggested-modules.json @@ -0,0 +1,15 @@ +{ + "modules": [ + "MODULE_PLAYERBOTS", + "MODULE_SOLO_LFG", + "MODULE_SOLOCRAFT", + "MODULE_AUTOBALANCE", + "MODULE_TRANSMOG", + "MODULE_NPC_BUFFER", + "MODULE_LEARN_SPELLS", + "MODULE_FIREWORKS" + ], + "label": "\ud83e\udd16 Playerbots + Suggested modules", + "description": "Suggested stack plus playerbots enabled", + "order": 2 +} \ No newline at end of file diff --git a/profiles/sam.conf b/profiles/sam.conf deleted file mode 100644 index e0210a5..0000000 --- a/profiles/sam.conf +++ /dev/null @@ -1 +0,0 @@ -MODULE_ELUNA,MODULE_PLAYERBOTS,MODULE_PLAYER_BOT_LEVEL_BRACKETS,MODULE_SOLO_LFG,MODULE_TRANSMOG,MODULE_NPC_BUFFER,MODULE_LEARN_SPELLS,MODULE_FIREWORKS,MODULE_REAGENT_BANK,MODULE_BLACK_MARKET_AUCTION_HOUSE,MODULE_1V1_ARENA,MODULE_ACCOUNT_ACHIEVEMENTS,MODULE_BREAKING_NEWS,MODULE_BOSS_ANNOUNCER,MODULE_AUTO_REVIVE,MODULE_ELUNA_TS,MODULE_NPC_BEASTMASTER,MODULE_NPC_ENCHANTER,MODULE_RANDOM_ENCHANTS,MODULE_INSTANCE_RESET,MODULE_TIME_IS_TIME,MODULE_GAIN_HONOR_GUARD,MODULE_ARAC \ No newline at end of file diff --git a/profiles/sam.json b/profiles/sam.json new file mode 100644 index 0000000..59fb957 --- /dev/null +++ b/profiles/sam.json @@ -0,0 +1,30 @@ +{ + "modules": [ + "MODULE_ELUNA", + "MODULE_PLAYERBOTS", + "MODULE_PLAYER_BOT_LEVEL_BRACKETS", + "MODULE_SOLO_LFG", + "MODULE_TRANSMOG", + "MODULE_NPC_BUFFER", + "MODULE_LEARN_SPELLS", + "MODULE_FIREWORKS", + "MODULE_REAGENT_BANK", + "MODULE_BLACK_MARKET_AUCTION_HOUSE", + "MODULE_1V1_ARENA", + "MODULE_ACCOUNT_ACHIEVEMENTS", + "MODULE_BREAKING_NEWS", + "MODULE_BOSS_ANNOUNCER", + "MODULE_AUTO_REVIVE", + "MODULE_ELUNA_TS", + "MODULE_NPC_BEASTMASTER", + "MODULE_NPC_ENCHANTER", + "MODULE_RANDOM_ENCHANTS", + "MODULE_INSTANCE_RESET", + "MODULE_TIME_IS_TIME", + "MODULE_GAIN_HONOR_GUARD", + "MODULE_ARAC" + ], + "label": "\ud83e\udde9 Sam", + "description": "Sam's playerbot-centric preset (use high bot counts)", + "order": 7 +} \ No newline at end of file diff --git a/profiles/suggested-modules.conf b/profiles/suggested-modules.conf deleted file mode 100644 index 4f9b7f7..0000000 --- a/profiles/suggested-modules.conf +++ /dev/null @@ -1 +0,0 @@ -MODULE_ELUNA,MODULE_SOLO_LFG,MODULE_SOLOCRAFT,MODULE_AUTOBALANCE,MODULE_TRANSMOG,MODULE_NPC_BUFFER,MODULE_LEARN_SPELLS,MODULE_FIREWORKS \ No newline at end of file diff --git a/profiles/suggested-modules.json b/profiles/suggested-modules.json new file mode 100644 index 0000000..ea8c231 --- /dev/null +++ b/profiles/suggested-modules.json @@ -0,0 +1,15 @@ +{ + "modules": [ + "MODULE_ELUNA", + "MODULE_SOLO_LFG", + "MODULE_SOLOCRAFT", + "MODULE_AUTOBALANCE", + "MODULE_TRANSMOG", + "MODULE_NPC_BUFFER", + "MODULE_LEARN_SPELLS", + "MODULE_FIREWORKS" + ], + "label": "\u2b50 Suggested Modules", + "description": "Baseline solo-friendly quality of life mix", + "order": 1 +} \ No newline at end of file diff --git a/scripts/setup_profiles.py b/scripts/setup_profiles.py new file mode 100755 index 0000000..02aae4e --- /dev/null +++ b/scripts/setup_profiles.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +""" +Expose profile metadata for setup.sh. + +Profiles are JSON documents with at least: +{ + "modules": ["MODULE_FOO", "MODULE_BAR"], + "label": "...", # optional + "description": "..." # optional +} +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path +from typing import Iterable, List, Tuple + + +def normalize_modules(raw_modules: Iterable[str], profile: Path) -> List[str]: + """Return a cleaned list of module identifiers.""" + modules: List[str] = [] + for item in raw_modules: + if not isinstance(item, str): + raise ValueError(f"Profile {profile.name}: module entries must be strings") + value = item.strip() + if not value: + continue + modules.append(value) + if not modules: + raise ValueError(f"Profile {profile.name}: modules list cannot be empty") + return modules + + +def sanitize(text: str | None) -> str: + if not text: + return "" + return str(text).replace("\t", " ").replace("\n", " ").strip() + + +def load_profile(path: Path) -> Tuple[str, List[str], str, str, int]: + try: + data = json.loads(path.read_text()) + except json.JSONDecodeError as exc: + raise ValueError(f"Profile {path.name}: invalid JSON - {exc}") from exc + + raw_modules = data.get("modules") + if not isinstance(raw_modules, list): + raise ValueError(f"Profile {path.name}: 'modules' must be a list") + + modules = normalize_modules(raw_modules, path) + name = data.get("name") or path.stem + label = sanitize(data.get("label")) or " ".join(part.capitalize() for part in name.replace("-", " ").split()) + description = sanitize(data.get("description")) + + order_raw = data.get("order") + try: + order = int(order_raw) if order_raw is not None else 10000 + except (TypeError, ValueError): + raise ValueError(f"Profile {path.name}: 'order' must be an integer") from None + + return name, modules, label, description, order + + +def cmd_list(directory: Path) -> int: + if not directory.is_dir(): + print(f"ERROR: Profile directory not found: {directory}", file=sys.stderr) + return 1 + + profiles: List[Tuple[str, List[str], str, str, int]] = [] + for candidate in sorted(directory.glob("*.json")): + try: + profiles.append(load_profile(candidate)) + except ValueError as exc: + print(f"ERROR: {exc}", file=sys.stderr) + return 1 + + profiles.sort(key=lambda item: item[4]) + + for name, modules, label, description, order in profiles: + modules_csv = ",".join(modules) + print("\t".join([name, modules_csv, label, description, str(order)])) + return 0 + + +COMMANDS = { + "list": cmd_list, +} + + +def main(argv: List[str]) -> int: + if len(argv) != 3: + print(f"Usage: {argv[0]} ", file=sys.stderr) + return 1 + + command = argv[1] + handler = COMMANDS.get(command) + if handler is None: + valid = ", ".join(sorted(COMMANDS)) + print(f"Unknown command '{command}'. Valid commands: {valid}", file=sys.stderr) + return 1 + + directory = Path(argv[2]) + return handler(directory) + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/setup.sh b/setup.sh index 5555f30..2f373fa 100755 --- a/setup.sh +++ b/setup.sh @@ -350,6 +350,7 @@ EOF MODULE_MANIFEST_PATH="$SCRIPT_DIR/config/modules.json" MODULE_MANIFEST_HELPER="$SCRIPT_DIR/scripts/setup_manifest.py" +MODULE_PROFILES_HELPER="$SCRIPT_DIR/scripts/setup_profiles.py" ENV_TEMPLATE_FILE="$SCRIPT_DIR/.env.template" declare -a MODULE_KEYS=() @@ -566,7 +567,7 @@ Options: --backup-retention-hours N Hourly backup retention (default 6) --backup-daily-time HH Daily backup hour 00-23 (default 09) --module-mode MODE suggested, playerbots, manual, or none - --module-config NAME Use preset NAME from profiles/.conf + --module-config NAME Use preset NAME from profiles/.json --enable-modules LIST Comma-separated module list (MODULE_* or shorthand) --playerbot-enabled 0|1 Override PLAYERBOT_ENABLED flag --playerbot-min-bots N Override PLAYERBOT_MIN_BOTS value @@ -926,24 +927,28 @@ fi local MODE_SELECTION="" local MODE_PRESET_NAME="" declare -A MODULE_PRESET_CONFIGS=() - declare -a MODULE_PRESET_ORDER=() + declare -A MODULE_PRESET_LABELS=() + declare -A MODULE_PRESET_DESCRIPTIONS=() + declare -A MODULE_PRESET_ORDER=() local CONFIG_DIR="$SCRIPT_DIR/profiles" + if [ ! -x "$MODULE_PROFILES_HELPER" ]; then + say ERROR "Profile helper not found or not executable at $MODULE_PROFILES_HELPER" + exit 1 + fi if [ -d "$CONFIG_DIR" ]; then - while IFS= read -r preset_path; do - [ -n "$preset_path" ] || continue - local preset_name - preset_name="$(basename "$preset_path" .conf)" - local preset_value - preset_value="$(tr -d '\r' < "$preset_path" | tr '\n' ',' | sed -E 's/,+/,/g; s/^,//; s/,$//')" - MODULE_PRESET_CONFIGS["$preset_name"]="$preset_value" - MODULE_PRESET_ORDER+=("$preset_name") - done < <(find "$CONFIG_DIR" -maxdepth 1 -type f -name '*.conf' -print | sort) + while IFS=$'\t' read -r preset_name preset_modules preset_label preset_desc preset_order; do + [ -n "$preset_name" ] || continue + MODULE_PRESET_CONFIGS["$preset_name"]="$preset_modules" + MODULE_PRESET_LABELS["$preset_name"]="$preset_label" + MODULE_PRESET_DESCRIPTIONS["$preset_name"]="$preset_desc" + MODULE_PRESET_ORDER["$preset_name"]="${preset_order:-10000}" + done < <(python3 "$MODULE_PROFILES_HELPER" list "$CONFIG_DIR") fi local missing_presets=0 for required_preset in "$DEFAULT_PRESET_SUGGESTED" "$DEFAULT_PRESET_PLAYERBOTS"; do if [ -z "${MODULE_PRESET_CONFIGS[$required_preset]:-}" ]; then - say ERROR "Missing module preset profiles/${required_preset}.conf" + say ERROR "Missing module preset profiles/${required_preset}.json" missing_presets=1 fi done @@ -998,25 +1003,38 @@ fi # Module config say HEADER "MODULE PRESET" - echo "1) ⭐ Suggested Modules" - echo "2) 🤖 Playerbots + Suggested modules" + echo "1) ${MODULE_PRESET_LABELS[$DEFAULT_PRESET_SUGGESTED]:-⭐ Suggested Modules}" + echo "2) ${MODULE_PRESET_LABELS[$DEFAULT_PRESET_PLAYERBOTS]:-🤖 Playerbots + Suggested modules}" echo "3) ⚙️ Manual selection" echo "4) 🚫 No modules" local menu_index=5 declare -A MENU_PRESET_INDEX=() - if [ ${#MODULE_PRESET_ORDER[@]} -gt 0 ]; then - for preset_name in "${MODULE_PRESET_ORDER[@]}"; do - if [ "$preset_name" = "$DEFAULT_PRESET_SUGGESTED" ] || [ "$preset_name" = "$DEFAULT_PRESET_PLAYERBOTS" ]; then - continue - fi - local pretty_name - pretty_name=$(echo "$preset_name" | tr '_-' ' ' | awk '{for(i=1;i<=NF;i++){$i=toupper(substr($i,1,1)) substr($i,2)}}1') - echo "${menu_index}) 🧩 ${pretty_name} (profiles/${preset_name}.conf)" - MENU_PRESET_INDEX[$menu_index]="$preset_name" - menu_index=$((menu_index + 1)) - done + local -a ORDERED_PRESETS=() + for preset_name in "${!MODULE_PRESET_CONFIGS[@]}"; do + if [ "$preset_name" = "$DEFAULT_PRESET_SUGGESTED" ] || [ "$preset_name" = "$DEFAULT_PRESET_PLAYERBOTS" ]; then + continue + fi + local order="${MODULE_PRESET_ORDER[$preset_name]:-10000}" + ORDERED_PRESETS+=("$(printf '%05d::%s' "$order" "$preset_name")") + done + if [ ${#ORDERED_PRESETS[@]} -gt 0 ]; then + IFS=$'\n' ORDERED_PRESETS=($(printf '%s\n' "${ORDERED_PRESETS[@]}" | sort)) fi + + for entry in "${ORDERED_PRESETS[@]}"; do + local preset_name="${entry#*::}" + [ -n "${MODULE_PRESET_CONFIGS[$preset_name]:-}" ] || continue + local pretty_name + if [ -n "${MODULE_PRESET_LABELS[$preset_name]:-}" ]; then + pretty_name="${MODULE_PRESET_LABELS[$preset_name]}" + else + pretty_name=$(echo "$preset_name" | tr '_-' ' ' | awk '{for(i=1;i<=NF;i++){$i=toupper(substr($i,1,1)) substr($i,2)}}1') + fi + echo "${menu_index}) ${pretty_name} (profiles/${preset_name}.json)" + MENU_PRESET_INDEX[$menu_index]="$preset_name" + menu_index=$((menu_index + 1)) + done local max_option=$((menu_index - 1)) if [ "$NON_INTERACTIVE" = "1" ] && [ -z "$MODE_SELECTION" ]; then @@ -1085,11 +1103,13 @@ fi if [ "$MODE_SELECTION" = "1" ]; then MODE_PRESET_NAME="$DEFAULT_PRESET_SUGGESTED" apply_module_preset "${MODULE_PRESET_CONFIGS[$DEFAULT_PRESET_SUGGESTED]}" - module_mode_label="preset 1 (Suggested)" + local preset_label="${MODULE_PRESET_LABELS[$DEFAULT_PRESET_SUGGESTED]:-Suggested Modules}" + module_mode_label="preset 1 (${preset_label})" elif [ "$MODE_SELECTION" = "2" ]; then MODE_PRESET_NAME="$DEFAULT_PRESET_PLAYERBOTS" apply_module_preset "${MODULE_PRESET_CONFIGS[$DEFAULT_PRESET_PLAYERBOTS]}" - module_mode_label="preset 2 (Playerbots + Suggested)" + local preset_label="${MODULE_PRESET_LABELS[$DEFAULT_PRESET_PLAYERBOTS]:-Playerbots + Suggested}" + module_mode_label="preset 2 (${preset_label})" elif [ "$MODE_SELECTION" = "3" ]; then MODE_PRESET_NAME="" say INFO "Answer y/n for each module (organized by category)" @@ -1195,7 +1215,8 @@ fi else say WARNING "Preset '${MODE_PRESET_NAME}' did not contain any module selections." fi - module_mode_label="preset (${MODE_PRESET_NAME})" + local preset_label="${MODULE_PRESET_LABELS[$MODE_PRESET_NAME]:-$MODE_PRESET_NAME}" + module_mode_label="preset (${preset_label})" fi auto_enable_module_dependencies