Skip to content

Buy Max Manager

Relevant source files

The Buy Max Manager is a utility component that adds automated batch purchasing functionality to the game’s upgrade system. It injects a split button into the upgrades tab UI, providing players with four different purchase strategies to efficiently spend accumulated currency on upgrades. This manager is part of the optional gameplay enhancements and is always enabled once the mod is loaded.

For information about other utility managers (FocusHandler, WireColorOverrides, etc.), see Other Utility Managers. For the broader context of gameplay features including cheats and node limit controls, see Gameplay Features. For configuration management details, see Configuration System.

Sources: extensions/scripts/utilities/buy_max_manager.gd L1-L10


The BuyMaxManager extends Node and follows the standard mod manager initialization pattern. It is instantiated and configured by mod_main.gd during the deferred setup phase after the HUD is available.

sequenceDiagram
  participant mod_main.gd
  participant BuyMaxManager
  participant ConfigManager
  participant Main/HUD
  participant UpgradesTab UI

  mod_main.gd->>BuyMaxManager: "new()"
  mod_main.gd->>BuyMaxManager: "setup(tree, config)"
  BuyMaxManager->>ConfigManager: "get_value('buy_max_strategy')"
  ConfigManager-->>BuyMaxManager: "Strategy.ROUND_ROBIN"
  BuyMaxManager->>Main/HUD: "_find_upgrades_tab()"
  Main/HUD-->>BuyMaxManager: "VBoxContainer reference"
  BuyMaxManager->>BuyMaxManager: "_inject_buy_max_button()"
  BuyMaxManager->>UpgradesTab UI: "add BuyMaxContainer"
  BuyMaxManager->>UpgradesTab UI: "add BuyMaxButton"
  BuyMaxManager->>UpgradesTab UI: "add StrategyButton"
  BuyMaxManager->>BuyMaxManager: "set _initialized = true"
  note over BuyMaxManager,UpgradesTab UI: "Ready for user interaction"

Key Initialization Steps:

  1. Load saved strategy from ConfigManager
  2. Locate the upgrades tab in the HUD scene tree
  3. Inject UI components into the buttons panel
  4. Configure event handlers and styling

Sources: extensions/scripts/utilities/buy_max_manager.gd L45-L76


The manager implements four distinct purchase strategies defined in the Strategy enum:

Strategy IDNameBehaviorUse Case
ROUND_ROBIN (0)Round RobinBuys 1 level of each upgrade in rotation until funds exhaustedBalanced progression across all upgrades
CHEAPEST_FIRST (1)Cheapest FirstAlways purchases the cheapest available upgrade firstMaximize number of purchases, incremental gains
EXPENSIVE_FIRST (2)Most ExpensiveBuys the most expensive affordable upgradeMaximize impact per purchase
TOP_TO_BOTTOM (3)Top to BottomMaxes out upgrades sequentially from top to bottom of UIComplete individual upgrades before moving to next

Sources: extensions/scripts/utilities/buy_max_manager.gd L12-L31

flowchart TD

User["👤 User"]
StratBtn["StrategyButton (MenuButton)"]
Popup["PopupMenu"]
Handler["_on_strategy_selected(id)"]
SetStrat["set_strategy(strategy)"]
Config["ConfigManager"]
Update["_update_strategy_menu()"]
Tooltip["_update_main_button_tooltip()"]
Signal["Signals.notify.emit()"]

User --> StratBtn
StratBtn --> Popup
Popup --> Handler
Handler --> SetStrat
SetStrat --> Config
SetStrat --> Update
Update --> Tooltip
SetStrat --> Signal
Update --> Popup

Strategy Persistence: The selected strategy is immediately persisted via ConfigManager.set_value("buy_max_strategy", strategy) and restored on next game session.

Sources: extensions/scripts/utilities/buy_max_manager.gd L79-L87

extensions/scripts/utilities/buy_max_manager.gd L238-L242


The Buy Max button uses a split-button pattern consisting of two components inside an HBoxContainer:

flowchart TD

ExistingBtns["Existing Tab Buttons"]
MainBtn["BuyMaxButton<br>text: 'Buy Max'<br>theme: TabButton<br>SIZE_FILL"]
DropBtn["StrategyButton<br>text: '▼'<br>theme: TabButton<br>min_size: 45x0"]
ExecuteBuyMax["_execute_buy_max()"]
PopupMenu["PopupMenu (4 items)"]

MainBtn --> ExecuteBuyMax
DropBtn --> PopupMenu

subgraph ButtonsPanel/ButtonsContainer ["ButtonsPanel/ButtonsContainer"]
    ExistingBtns

subgraph BuyMaxCont ["BuyMaxCont"]
    MainBtn
    DropBtn
end
end

UI Tree Location: The manager searches for a VBoxContainer that contains both ButtonsPanel/ButtonsContainer and TabContainer children, which uniquely identifies the upgrades tab structure.

Theme Matching: Both buttons use theme_type_variation = "TabButton" to match the game’s existing tab buttons. The popup menu is styled with StyleBoxFlat to match the dark theme defined in main.tres.

Sources: extensions/scripts/utilities/buy_max_manager.gd L112-L169

extensions/scripts/utilities/buy_max_manager.gd L202-L235

flowchart TD

HUD["Main/HUD"]
Search["_find_upgrades_tab(node)"]
Check["Is VBoxContainer?"]
CheckBtns["Has ButtonsPanel/ButtonsContainer?"]
CheckTabs["Has TabContainer?"]
Found["Return VBoxContainer"]
Recurse["Recurse children"]

HUD --> Search
Search --> Check
Check --> CheckBtns
Check --> Recurse
CheckBtns --> CheckTabs
CheckBtns --> Recurse
CheckTabs --> Found
CheckTabs --> Recurse
Recurse --> Search

Sources: extensions/scripts/utilities/buy_max_manager.gd L94-L108


flowchart TD

Trigger["BuyMaxButton.pressed"]
Execute["_execute_buy_max()"]
Sound["Sound.play('click_toggle2')"]
DoBuyMax["_do_buy_max() -> int"]
GetPanels["_get_active_upgrade_panels()"]
Filter["Filter out token upgrades"]
Dispatch["Strategy?"]
RR["_buy_round_robin()"]
CF["_buy_cheapest_first()"]
EF["_buy_expensive_first()"]
TB["_buy_top_to_bottom()"]
Result["Return total purchased"]
Notify["Signals.notify.emit()"]

Trigger --> Execute
Execute --> Sound
Execute --> DoBuyMax
DoBuyMax --> GetPanels
GetPanels --> Filter
Filter --> Dispatch
Dispatch --> RR
Dispatch --> CF
Dispatch --> EF
Dispatch --> TB
RR --> Result
CF --> Result
EF --> Result
TB --> Result
Result --> Notify

Panel Discovery: The algorithm queries the currently active tab in the TabContainer and recursively searches for all visible Panel nodes that have both can_purchase() and update_all() methods.

Token Exclusion: Upgrades with currency_key == "currency:token" are filtered out before processing to prevent accidental token spending.

Sources: extensions/scripts/utilities/buy_max_manager.gd L244-L283

extensions/scripts/utilities/buy_max_manager.gd L405-L432

Groups upgrades by currency type and rotates through each group, buying one level at a time until no more purchases are possible in that currency.

Algorithm:

1. Group panels by _get_currency_key(panel)
2. For each currency group:
a. While any purchase made this pass:
- For each panel in group:
* panel.update_all()
* If can_purchase(): panel._on_purchase_pressed()
b. Final pass: attempt one more purchase per panel
3. Return total purchased

Sources: extensions/scripts/utilities/buy_max_manager.gd L290-L323

Finds and purchases the globally cheapest affordable upgrade, repeating until no more purchases are possible.

Algorithm:

1. While any purchase made:
a. Set cheapest_cost = INF, cheapest_panel = null
b. For each panel:
- panel.update_all()
- If can_purchase() and cost < cheapest_cost:
* Update cheapest_cost and cheapest_panel
c. If cheapest_panel found:
- cheapest_panel._on_purchase_pressed()
- Increment total
2. Return total purchased

Sources: extensions/scripts/utilities/buy_max_manager.gd L326-L351

Purchases the most expensive affordable upgrade repeatedly, maximizing impact per transaction.

Algorithm:

1. While any purchase made:
a. Set highest_cost = -1, expensive_panel = null
b. For each panel:
- panel.update_all()
- If can_purchase() and cost > highest_cost:
* Update highest_cost and expensive_panel
c. If expensive_panel found:
- expensive_panel._on_purchase_pressed()
- Increment total
2. Return total purchased

Sources: extensions/scripts/utilities/buy_max_manager.gd L354-L379

Processes panels in UI order (top to bottom), exhausting each upgrade before moving to the next.

Algorithm:

1. For each panel in order:
a. While panel.can_purchase():
- panel.update_all()
- panel._on_purchase_pressed()
- Increment total
2. Return total purchased

Sources: extensions/scripts/utilities/buy_max_manager.gd L382-L398


The _get_currency_key() helper function groups upgrades by their cost type for fair distribution in Round Robin strategy:

flowchart TD

Panel["upgrade_panel"]
GetKey["_get_currency_key(panel)"]
CheckData["Panel in Data.upgrades?"]
GetCostType["cost_type = upgrade_data.cost_type"]
CheckType["Cost Type?"]
Incremental["INCREMENTAL/FIXED"]
Attribute["ATTRIBUTE"]
Unknown["'unknown'"]
CurrencyKey["'currency:' + currency"]
AttributeKey["'attribute:' + attribute_cost"]

Panel --> GetKey
GetKey --> CheckData
CheckData --> Unknown
CheckData --> GetCostType
GetCostType --> CheckType
CheckType --> Incremental
CheckType --> Attribute
CheckType --> Unknown
Incremental --> CurrencyKey
Attribute --> AttributeKey

Currency Key Examples:

  • "currency:dollar" - Standard dollar upgrades
  • "currency:research" - Research point upgrades
  • "attribute:compute" - Upgrades costing compute attribute
  • "currency:token" - Token upgrades (filtered out)

Sources: extensions/scripts/utilities/buy_max_manager.gd L435-L450


Configuration KeyTypeDefaultDescription
buy_max_strategyintStrategy.ROUND_ROBIN (0)Currently selected purchase strategy

The strategy preference is:

  1. Loaded in setup() via config.get_value("buy_max_strategy", Strategy.ROUND_ROBIN)
  2. Saved immediately on change via config.set_value("buy_max_strategy", strategy)
  3. Persisted to user://tajs_mod_config.json

Configuration Flow:

flowchart TD

Load["setup() loads config"]
Mem["current_strategy variable"]
UI["StrategyButton dropdown"]
User["User selection"]
Save["set_strategy() saves"]
Config["tajs_mod_config.json"]

Load --> Mem
Mem --> UI
User --> UI
UI --> Save
Save --> Config
Config --> Load

Sources: extensions/scripts/utilities/buy_max_manager.gd L52-L54

extensions/scripts/utilities/buy_max_manager.gd L80-L86


The Buy Max Manager interacts with the game’s upgrade panels through their public interface:

MethodPurposeCalled By
update_all()Recalculates cost and affordabilityBefore each can_purchase() check
can_purchase()Returns bool if upgrade is affordableDecision logic in all strategies
_on_purchase_pressed()Executes the purchase transactionAfter affordability confirmed

Panel Properties:

  • panel.cost - Current cost of next upgrade level
  • panel.visible - Whether panel is shown (used for filtering)
  • panel.name - Upgrade identifier, maps to Data.upgrades[name]

Sources: extensions/scripts/utilities/buy_max_manager.gd L309-L321

extensions/scripts/utilities/buy_max_manager.gd L426-L432

flowchart TD

BMM["BuyMaxManager"]
Sound["Sound.play()"]
Signals["Signals.notify.emit()"]
NotifLog["Notification Log"]

BMM --> Sound
BMM --> Signals
BMM --> Signals
Signals --> NotifLog

Notification Messages:

  • Success: "Bought %d upgrades (%s)" with strategy name
  • Failure: "Nothing affordable on this page"
  • Strategy change: "Strategy: %s" with strategy name

Sources: extensions/scripts/utilities/buy_max_manager.gd L246-L252

extensions/scripts/utilities/buy_max_manager.gd L85


The manager includes defensive checks to prevent initialization failures:

flowchart TD

Setup["setup(tree, config)"]
Check1["_initialized?"]
Check2["Main exists?"]
Check3["HUD exists?"]
Check4["_upgrades_tab found?"]
Success["Inject button & set _initialized"]
EarlyReturn["Log warning & return"]

Setup --> Check1
Check1 --> EarlyReturn
Check1 --> Check2
Check2 --> EarlyReturn
Check2 --> Check3
Check3 --> EarlyReturn
Check3 --> Check4
Check4 --> EarlyReturn
Check4 --> Success

Before creating UI components, the manager checks if BuyMaxContainer already exists in the button panel using buttons_container.has_node("BuyMaxContainer"). If found, it reuses the existing reference rather than creating duplicates.

Sources: extensions/scripts/utilities/buy_max_manager.gd L46-L76

extensions/scripts/utilities/buy_max_manager.gd L118-L121

flowchart TD

DoBuyMax["_do_buy_max()"]
GetPanels["_get_active_upgrade_panels()"]
CheckEmpty1["panels.is_empty()?"]
Filter["Filter out tokens"]
CheckEmpty2["filtered.is_empty()?"]
Execute["Execute strategy"]
Return0["return 0"]

DoBuyMax --> GetPanels
GetPanels --> CheckEmpty1
CheckEmpty1 --> Return0
CheckEmpty1 --> Filter
Filter --> CheckEmpty2
CheckEmpty2 --> Return0
CheckEmpty2 --> Execute

Sources: extensions/scripts/utilities/buy_max_manager.gd L257-L271


This manager is referenced by other mod components:

  • mod_main.gd: Instantiates and calls setup() during _setup_for_main()
  • default_commands.gd: Potentially includes commands to trigger buy max or change strategy
  • Settings Panel: May include UI controls to disable or configure the feature

The manager operates independently once initialized and requires no ongoing coordination with mod_main.gd beyond the initial setup phase.

Sources: High-level architecture context from provided diagrams