From c9f07bfa99c8b0f4c15e5ed8cd91c8b62d14bd75 Mon Sep 17 00:00:00 2001 From: S1M0N38 Date: Mon, 11 Aug 2025 19:32:09 +0200 Subject: [PATCH 1/6] refactor: split types across multiple files --- src/lua/types.lua | 359 ------------------------------- src/lua/types/api.lua | 83 +++++++ src/lua/types/gamestate.lua | 418 ++++++++++++++++++++++++++++++++++++ src/lua/types/log.lua | 31 +++ src/lua/types/settings.lua | 15 ++ src/lua/types/utils.lua | 12 ++ 6 files changed, 559 insertions(+), 359 deletions(-) delete mode 100644 src/lua/types.lua create mode 100644 src/lua/types/api.lua create mode 100644 src/lua/types/gamestate.lua create mode 100644 src/lua/types/log.lua create mode 100644 src/lua/types/settings.lua create mode 100644 src/lua/types/utils.lua diff --git a/src/lua/types.lua b/src/lua/types.lua deleted file mode 100644 index c1faf35..0000000 --- a/src/lua/types.lua +++ /dev/null @@ -1,359 +0,0 @@ ----@meta balatrobot-types ----Type definitions for the BalatroBot Lua mod - --- ============================================================================= --- TCP Socket Types --- ============================================================================= - ----@class TCPSocket ----@field settimeout fun(self: TCPSocket, timeout: number) ----@field setsockname fun(self: TCPSocket, address: string, port: number): boolean, string? ----@field receivefrom fun(self: TCPSocket, size: number): string?, string?, number? ----@field sendto fun(self: TCPSocket, data: string, address: string, port: number): number?, string? - --- ============================================================================= --- API Request Types (used in api.lua) --- ============================================================================= - ----@class PendingRequest ----@field condition fun(): boolean Function that returns true when the request condition is met ----@field action fun() Function to execute when condition is met ----@field args? table Optional arguments passed to the request - ----@class APIRequest ----@field name string The name of the API function to call ----@field arguments table The arguments to pass to the function - ----@class ErrorResponse ----@field error string The error message ----@field error_code string Standardized error code (e.g., "E001") ----@field state any The current game state ----@field context? table Optional additional context about the error - ----@class StartRunArgs ----@field deck string The deck name to use ----@field stake? number The stake level (optional) ----@field seed? string The seed for the run (optional) ----@field challenge? string The challenge name (optional) ----@field log_path? string The full file path for the run log (optional, must include .jsonl extension) - --- ============================================================================= --- Game Action Argument Types (used in api.lua) --- ============================================================================= - ----@class BlindActionArgs ----@field action "select" | "skip" The action to perform on the blind - ----@class HandActionArgs ----@field action "play_hand" | "discard" The action to perform ----@field cards number[] Array of card indices (0-based) - ----@class RearrangeHandArgs ----@field action "rearrange" The action to perform ----@field cards number[] Array of card indices for every card in hand (0-based) - ----@class RearrangeJokersArgs ----@field jokers number[] Array of joker indices for every joker (0-based) - ----@class RearrangeConsumablesArgs ----@field consumables number[] Array of consumable indices for every consumable (0-based) - ----@class ShopActionArgs ----@field action "next_round" | "buy_card" | "reroll" | "redeem_voucher" The action to perform ----@field index? number The index of the card to act on (buy, buy_and_use, redeem, open) (0-based) - --- TODO: add the other actions | "buy_and_use" | "open_pack" - ----@class SellJokerArgs ----@field index number The index of the joker to sell (0-based) - ----@class SellConsumableArgs ----@field index number The index of the consumable to sell (0-based) - --- ============================================================================= --- Main API Module (defined in api.lua) --- ============================================================================= - ----Main API module for handling TCP communication with bots ----@class API ----@field socket? TCPSocket TCP socket instance ----@field functions table Map of API function names to their implementations ----@field pending_requests table Map of pending async requests ----@field last_client_ip? string IP address of the last client that sent a message ----@field last_client_port? number Port of the last client that sent a message - --- ============================================================================= --- Game Entity Types --- ============================================================================= - --- Root game state response (G object) ----@class G ----@field state any Current game state enum value ----@field game? GGame Game information (null if not in game) ----@field hand? GHand Hand information (null if not available) ----@field jokers GJokers Jokers area object (with sub-field `cards`) ----@field consumables GConsumables Consumables area object ----@field shop_jokers? GShopJokers Shop jokers area ----@field shop_vouchers? GShopVouchers Shop vouchers area ----@field shop_booster? GShopBooster Shop booster packs area - --- Game state (G.GAME) ----@class GGame ----@field bankrupt_at number Money threshold for bankruptcy ----@field base_reroll_cost number Base cost for rerolling shop ----@field blind_on_deck string Current blind type ("Small", "Big", "Boss") ----@field bosses_used GGameBossesUsed Bosses used in run (bl_ = 1|0) ----@field chips number Current chip count ----@field current_round GGameCurrentRound Current round information ----@field discount_percent number Shop discount percentage ----@field dollars number Current money amount ----@field hands_played number Total hands played in the run ----@field inflation number Current inflation rate ----@field interest_amount number Interest amount per dollar ----@field interest_cap number Maximum interest that can be earned ----@field last_blind GGameLastBlind Last blind information ----@field max_jokers number Maximum number of jokers in card area ----@field planet_rate number Probability for planet cards in shop ----@field playing_card_rate number Probability for playing cards in shop ----@field previous_round GGamePreviousRound Previous round information ----@field probabilities GGameProbabilities Various game probabilities ----@field pseudorandom GGamePseudorandom Pseudorandom seed data ----@field round number Current round number ----@field round_bonus GGameRoundBonus Round bonus information ----@field round_scores GGameRoundScores Round scoring data ----@field seeded boolean Whether the run uses a seed ----@field selected_back GGameSelectedBack Selected deck information ----@field shop GGameShop Shop configuration ----@field skips number Number of skips used ----@field smods_version string SMODS version ----@field stake number Current stake level ----@field starting_params GGameStartingParams Starting parameters ----@field tags GGameTags[] Array of tags ----@field tarot_rate number Probability for tarot cards in shop ----@field uncommon_mod number Modifier for uncommon joker probability ----@field unused_discards number Unused discards from previous round ----@field used_vouchers table Vouchers used in run ----@field voucher_text string Voucher text display ----@field win_ante number Ante required to win ----@field won boolean Whether the run is won - --- Game tags (G.GAME.tags[]) ----@class GGameTags ----@field key string Tag ID (e.g., "tag_foil") ----@field name string Tag display name (e.g., "Foil Tag") - --- Last blind info (G.GAME.last_blind) ----@class GGameLastBlind ----@field boss boolean Whether the last blind was a boss ----@field name string Name of the last blind - --- Current round info (G.GAME.current_round) ----@class GGameCurrentRound ----@field discards_left number Number of discards remaining ----@field discards_used number Number of discards used ----@field hands_left number Number of hands remaining ----@field hands_played number Number of hands played ----@field reroll_cost number Current dollar cost to reroll the shop offer ----@field free_rerolls number Free rerolls remaining this round ----@field voucher GGameCurrentRoundVoucher Vouchers for this round - --- Selected deck info (G.GAME.selected_back) ----@class GGameSelectedBack ----@field name string Name of the selected deck - --- Shop configuration (G.GAME.shop) ----@class GGameShop - --- Starting parameters (G.GAME.starting_params) ----@class GGameStartingParams - --- Previous round info (G.GAME.previous_round) ----@class GGamePreviousRound - --- Game probabilities (G.GAME.probabilities) ----@class GGameProbabilities - --- Pseudorandom data (G.GAME.pseudorandom) ----@class GGamePseudorandom - --- Round bonus (G.GAME.round_bonus) ----@class GGameRoundBonus - --- Round scores (G.GAME.round_scores) ----@class GGameRoundScores - --- Hand structure (G.hand) ----@class GHand ----@field cards GHandCards[] Array of cards in hand ----@field config GHandConfig Hand configuration - --- Hand configuration (G.hand.config) ----@class GHandConfig ----@field card_count number Number of cards in hand ----@field card_limit number Maximum cards allowed in hand ----@field highlighted_limit number Maximum cards that can be highlighted - --- Hand card (G.hand.cards[]) ----@class GHandCards ----@field label string Display label of the card ----@field sort_id number Unique identifier for this card instance ----@field base GHandCardsBase Base card properties ----@field config GHandCardsConfig Card configuration ----@field debuff boolean Whether card is debuffed ----@field facing string Card facing direction ("front", etc.) ----@field highlighted boolean Whether card is highlighted - --- Hand card base properties (G.hand.cards[].base) ----@class GHandCardsBase ----@field id any Card ID ----@field name string Base card name ----@field nominal string Nominal value ----@field original_value string Original card value ----@field suit string Card suit ----@field times_played number Times this card has been played ----@field value string Current card value - --- Hand card configuration (G.hand.cards[].config) ----@class GHandCardsConfig ----@field card_key string Unique card identifier ----@field card GHandCardsConfigCard Card-specific data - --- Hand card config card data (G.hand.cards[].config.card) ----@class GHandCardsConfigCard ----@field name string Card name ----@field suit string Card suit ----@field value string Card value - ----@class GCardAreaConfig ----@field card_count number Number of cards currently present in the area ----@field card_limit number Maximum cards allowed in the area - ----@class GJokers ----@field config GCardAreaConfig Config for jokers card area ----@field cards GJokersCard[] Array of joker card objects - ----@class GConsumables ----@field config GCardAreaConfig Configuration for the consumables slot ----@field cards? GConsumablesCard[] Array of consumable card objects - --- Joker card (G.jokers.cards[]) ----@class GJokersCard ----@field label string Display label of the joker ----@field cost number Purchase cost of the joker ----@field sort_id number Unique identifier for this card instance ----@field config GJokersCardConfig Joker card configuration ----@field debuff boolean Whether joker is debuffed ----@field facing string Card facing direction ("front", "back") ----@field highlighted boolean Whether joker is highlighted - --- Joker card configuration (G.jokers.cards[].config) ----@class GJokersCardConfig ----@field center_key string Key identifier for the joker center - --- Consumable card (G.consumables.cards[]) ----@class GConsumablesCard ----@field label string Display label of the consumable ----@field cost number Purchase cost of the consumable ----@field config GConsumablesCardConfig Consumable configuration - --- Consumable card configuration (G.consumables.cards[].config) ----@class GConsumablesCardConfig ----@field center_key string Key identifier for the consumable center - --- ============================================================================= --- Utility Module --- ============================================================================= - ----Utility functions for game state extraction and data processing ----@class utils ----@field get_game_state fun(): G Extracts the current game state ----@field table_to_json fun(obj: any, depth?: number): string Converts a Lua table to JSON string - --- ============================================================================= --- Log Types (used in log.lua) --- ============================================================================= - ----@class Log ----@field mod_path string? Path to the mod directory for log file storage ----@field current_run_file string? Path to the current run's log file ----@field pending_logs table Map of pending log entries awaiting conditions ----@field game_state_before G Game state before function call ----@field init fun() Initializes the logger by setting up hooks ----@field write fun(log_entry: LogEntry) Writes a log entry to the JSONL file ----@field update fun() Processes pending logs by checking completion conditions ----@field schedule_write fun(function_call: FunctionCall) Schedules a log entry to be written when condition is met - ----@class PendingLog ----@field log_entry table The log entry data to be written when condition is met ----@field condition function Function that returns true when the log should be written - ----@class FunctionCall ----@field name string The name of the function being called ----@field arguments table The parameters passed to the function - ----@class LogEntry ----@field timestamp_ms_before number Timestamp in milliseconds since epoch before function call ----@field timestamp_ms_after number? Timestamp in milliseconds since epoch after function call ----@field function FunctionCall Function call information ----@field game_state_before G Game state before function call ----@field game_state_after G? Game state after function call - --- ============================================================================= --- Configuration Types (used in balatrobot.lua) --- ============================================================================= - ----@class BalatrobotConfig ----@field port string Port for the bot to listen on ----@field dt number Tells the game that every update is dt seconds long ----@field max_fps integer? Maximum frames per second ----@field vsync_enabled boolean Whether vertical sync is enabled - --- ============================================================================= --- Shop Area Types --- ============================================================================= - --- Shop jokers area ----@class GShopJokers ----@field config GCardAreaConfig Configuration for the shop jokers area ----@field cards? GShopCard[] Array of shop card objects - --- Shop vouchers area ----@class GShopVouchers ----@field config GCardAreaConfig Configuration for the shop vouchers area ----@field cards? GShopCard[] Array of shop voucher objects - --- Shop booster area ----@class GShopBooster ----@field config GCardAreaConfig Configuration for the shop booster area ----@field cards? GShopCard[] Array of shop booster objects - --- Shop card ----@class GShopCard ----@field label string Display label of the shop card ----@field cost number Purchase cost of the card ----@field sell_cost number Sell cost of the card ----@field debuff boolean Whether card is debuffed ----@field facing string Card facing direction ("front", "back") ----@field highlighted boolean Whether card is highlighted ----@field ability GShopCardAbility Card ability information ----@field config GShopCardConfig Shop card configuration - --- Shop card ability (G.shop_*.cards[].ability) ----@class GShopCardAbility ----@field set string The set of the card: "Joker", "Planet", "Tarot", "Spectral", "Voucher", "Booster", or "Consumable" - --- Shop card configuration (G.shop_*.cards[].config) ----@class GShopCardConfig ----@field center_key string Key identifier for the card center - --- ============================================================================= --- Additional Game State Types --- ============================================================================= - --- Round voucher (G.GAME.current_round.voucher) ----@class GGameCurrentRoundVoucher --- This is intentionally empty as the voucher table structure varies - --- Bosses used (G.GAME.bosses_used) ----@class GGameBossesUsed --- Dynamic table with boss name keys mapping to 1|0 diff --git a/src/lua/types/api.lua b/src/lua/types/api.lua new file mode 100644 index 0000000..31bf364 --- /dev/null +++ b/src/lua/types/api.lua @@ -0,0 +1,83 @@ +---@meta balatrobot-api-types +---Type definitions for API communication and TCP socket handling + +-- ============================================================================= +-- TCP Socket Types +-- ============================================================================= + +---@class TCPSocket +---@field settimeout fun(self: TCPSocket, timeout: number) +---@field setsockname fun(self: TCPSocket, address: string, port: number): boolean, string? +---@field receivefrom fun(self: TCPSocket, size: number): string?, string?, number? +---@field sendto fun(self: TCPSocket, data: string, address: string, port: number): number?, string? + +-- ============================================================================= +-- API Request Types (used in api.lua) +-- ============================================================================= + +---@class PendingRequest +---@field condition fun(): boolean Function that returns true when the request condition is met +---@field action fun() Function to execute when condition is met +---@field args? table Optional arguments passed to the request + +---@class APIRequest +---@field name string The name of the API function to call +---@field arguments table The arguments to pass to the function + +---@class ErrorResponse +---@field error string The error message +---@field error_code string Standardized error code (e.g., "E001") +---@field state any The current game state +---@field context? table Optional additional context about the error + +---@class StartRunArgs +---@field deck string The deck name to use +---@field stake? number The stake level (optional) +---@field seed? string The seed for the run (optional) +---@field challenge? string The challenge name (optional) +---@field log_path? string The full file path for the run log (optional, must include .jsonl extension) + +-- ============================================================================= +-- Game Action Argument Types (used in api.lua) +-- ============================================================================= + +---@class BlindActionArgs +---@field action "select" | "skip" The action to perform on the blind + +---@class HandActionArgs +---@field action "play_hand" | "discard" The action to perform +---@field cards number[] Array of card indices (0-based) + +---@class RearrangeHandArgs +---@field action "rearrange" The action to perform +---@field cards number[] Array of card indices for every card in hand (0-based) + +---@class RearrangeJokersArgs +---@field jokers number[] Array of joker indices for every joker (0-based) + +---@class RearrangeConsumablesArgs +---@field consumables number[] Array of consumable indices for every consumable (0-based) + +---@class ShopActionArgs +---@field action "next_round" | "buy_card" | "reroll" | "redeem_voucher" The action to perform +---@field index? number The index of the card to act on (buy, buy_and_use, redeem, open) (0-based) + +-- TODO: add the other actions | "buy_and_use" | "open_pack" + +---@class SellJokerArgs +---@field index number The index of the joker to sell (0-based) + +---@class SellConsumableArgs +---@field index number The index of the consumable to sell (0-based) + +-- ============================================================================= +-- Main API Module (defined in api.lua) +-- ============================================================================= + +---Main API module for handling TCP communication with bots +---@class API +---@field socket? TCPSocket TCP socket instance +---@field functions table Map of API function names to their implementations +---@field pending_requests table Map of pending async requests +---@field last_client_ip? string IP address of the last client that sent a message +---@field last_client_port? number Port of the last client that sent a message diff --git a/src/lua/types/gamestate.lua b/src/lua/types/gamestate.lua new file mode 100644 index 0000000..1a8ddbe --- /dev/null +++ b/src/lua/types/gamestate.lua @@ -0,0 +1,418 @@ +---@meta balatrobot-gamestate-types +---Type definitions for game state including the main game object and all card types + +-- ============================================================================= +-- Root Game State Response (G object) +-- ============================================================================= + +---@class G +---@field state any Current game state enum value +---@field game? G.Game Game information (null if not in game) +---@field hand? G.Hand Hand information (null if not available) +---@field jokers G.Jokers Jokers area object (with sub-field `cards`) +---@field consumables G.Consumables Consumables area object +---@field shop_jokers? G.ShopJokers Shop jokers area +---@field shop_vouchers? G.ShopVouchers Shop vouchers area +---@field shop_booster? G.ShopBooster Shop booster packs area +---@field deck? G.Deck Deck area (when available) +---@field discard? G.Discard Discard pile (when available) +---@field blind? G.Blind Current blind information (when in blind state) +---@field SPEEDFACTOR? number Current game speed factor + +-- ============================================================================= +-- Game State (G.GAME) +-- ============================================================================= + +---@class G.Game +---@field ante number Current ante level (boss blind level) +---@field bankrupt_at number Money threshold for bankruptcy +---@field base_reroll_cost number Base cost for rerolling shop +---@field blind_on_deck string Current blind type ("Small", "Big", "Boss") +---@field bosses_used G.Game.BossesUsed Bosses used in run (bl_ = 1|0) +---@field chips number Current chip count +---@field current_round G.Game.CurrentRound Current round information +---@field discount_percent number Shop discount percentage +---@field dollars number Current money amount +---@field hands number Base hands per round +---@field discards number Base discards per round +---@field hands_played number Total hands played in the run +---@field inflation number Current inflation rate +---@field interest_amount number Interest amount per dollar +---@field interest_cap number Maximum interest that can be earned +---@field last_blind G.Game.LastBlind Last blind information +---@field max_jokers number Maximum number of jokers in card area +---@field mult number Current multiplier count +---@field planet_rate number Probability for planet cards in shop +---@field playing_card_rate number Probability for playing cards in shop +---@field previous_round G.Game.PreviousRound Previous round information +---@field probabilities G.Game.Probabilities Various game probabilities +---@field pseudorandom G.Game.Pseudorandom Pseudorandom seed data +---@field round number Current round number +---@field round_resets number Number of times the round has been reset +---@field round_bonus G.Game.RoundBonus Round bonus information +---@field round_scores G.Game.RoundScores Round scoring data +---@field seeded boolean Whether the run uses a seed +---@field selected_back G.Game.SelectedBack Selected deck information +---@field shop G.Game.Shop Shop configuration +---@field skips number Number of skips used +---@field smods_version string SMODS version +---@field stake number Current stake level +---@field starting_params G.Game.StartingParams Starting parameters +---@field tags G.Game.Tags[] Array of tags +---@field tarot_rate number Probability for tarot cards in shop +---@field uncommon_mod number Modifier for uncommon joker probability +---@field unused_discards number Unused discards from previous round +---@field used_vouchers table Vouchers used in run +---@field voucher_text string Voucher text display +---@field win_ante number Ante required to win +---@field won boolean Whether the run is won + +-- ============================================================================= +-- Game Sub-types +-- ============================================================================= + +-- Game tags (G.GAME.tags[]) +---@class G.Game.Tags +---@field key string Tag ID (e.g., "tag_foil") +---@field name string Tag display name (e.g., "Foil Tag") + +-- Last blind info (G.GAME.last_blind) +---@class G.Game.LastBlind +---@field boss boolean Whether the last blind was a boss +---@field name string Name of the last blind + +-- Current round info (G.GAME.current_round) +---@class G.Game.CurrentRound +---@field discards_left number Number of discards remaining +---@field discards_used number Number of discards used +---@field hands_left number Number of hands remaining +---@field hands_played number Number of hands played +---@field reroll_cost number Current dollar cost to reroll the shop offer +---@field free_rerolls number Free rerolls remaining this round +---@field voucher G.Game.CurrentRound.Voucher Vouchers for this round + +-- Round voucher (G.GAME.current_round.voucher) +---@class G.Game.CurrentRound.Voucher +-- This is intentionally empty as the voucher table structure varies + +-- Selected deck info (G.GAME.selected_back) +---@class G.Game.SelectedBack +---@field name string Name of the selected deck + +-- Shop configuration (G.GAME.shop) +---@class G.Game.Shop +---@field joker_max number Maximum jokers that can appear in shop +---@field consumeable_max number Maximum consumables that can appear in shop +---@field booster_max number Maximum booster packs that can appear in shop +---@field voucher_max number Maximum vouchers that can appear in shop + +-- Starting parameters (G.GAME.starting_params) +---@class G.Game.StartingParams +---@field ante_scaling number Ante scaling multiplier +---@field boosters_in_shop number Number of booster packs in shop +---@field consumable_slots number Number of consumable slots +---@field deck_cards number Starting deck size +---@field discards number Starting discards per round +---@field dollars number Starting money amount +---@field hand_size number Starting hand size +---@field hands number Starting hands per round +---@field interest_amount number Interest earned per dollar +---@field interest_cap number Maximum interest that can be earned +---@field joker_slots number Number of joker slots +---@field reroll_cost number Base cost to reroll shop +---@field vouchers_in_shop number Number of vouchers in shop + +-- Previous round info (G.GAME.previous_round) +---@class G.Game.PreviousRound +---@field dollars number Dollars earned from previous round +---@field hands_played number Hands played in previous round +---@field discards_used number Discards used in previous round + +-- Game probabilities (G.GAME.probabilities) +---@class G.Game.Probabilities +---@field normal number Base probability modifier +---@field legendary number Legendary rarity modifier +---@field rare number Rare rarity modifier +---@field uncommon number Uncommon rarity modifier + +-- Pseudorandom data (G.GAME.pseudorandom) +---@class G.Game.Pseudorandom +---@field seed string Current pseudorandom seed +---@field index number Current seed index + +-- Round bonus (G.GAME.round_bonus) +---@class G.Game.RoundBonus +---@field next_hands number Additional hands for next round +---@field discards number Additional discards for current/next round +---@field mult number Bonus multiplier for round + +-- Round scores (G.GAME.round_scores) +---@class G.Game.RoundScores +---@field cards_played table Statistics of cards played +---@field cards_discarded table Statistics of cards discarded +---@field furthest_ante table Furthest ante reached stats +---@field furthest_round table Furthest round reached stats +---@field times_played table Times specific elements were played + +-- Bosses used (G.GAME.bosses_used) +---@class G.Game.BossesUsed +-- Dynamic table with boss name keys mapping to 1|0 + +-- ============================================================================= +-- Blind Information Types +-- ============================================================================= + +-- Current blind (G.blind) +---@class G.Blind +---@field name string Blind name (e.g., "Small Blind", "Big Blind", "The Hook") +---@field chips number Chip requirement to beat the blind +---@field dollars number Dollar reward for beating the blind +---@field mult number Score multiplier for the blind +---@field boss? boolean Whether this is a boss blind +---@field pos table Position information for blind display +---@field config? table Blind-specific configuration +---@field ability? table Blind ability information (for boss blinds) +---@field discovered? boolean Whether blind is discovered in collection +---@field debuff table Cards/suits debuffed by this blind +---@field played_cards number Number of cards played this blind +---@field discarded_cards number Number of cards discarded this blind + +-- ============================================================================= +-- Card Area Configuration +-- ============================================================================= + +---@class G.CardArea.Config +---@field card_count number Number of cards currently present in the area +---@field card_limit number Maximum cards allowed in the area +---@field highlighted_limit number Maximum cards that can be highlighted in area +---@field type string Area type ("hand", "joker", "consumable", "deck", "discard", "shop") +---@field offset number Position offset for card arrangement + +-- ============================================================================= +-- Hand Structure and Hand Cards +-- ============================================================================= + +-- Hand structure (G.hand) +---@class G.Hand +---@field cards G.Hand.Cards[] Array of cards in hand +---@field config G.Hand.Config Hand configuration + +-- Hand configuration (G.hand.config) +---@class G.Hand.Config +---@field card_count number Number of cards in hand +---@field card_limit number Maximum cards allowed in hand +---@field highlighted_limit number Maximum cards that can be highlighted + +-- Hand card (G.hand.cards[]) +---@class G.Hand.Cards +---@field label string Display label of the card +---@field sort_id number Unique identifier for this card instance +---@field base G.Hand.Cards.Base Base card properties +---@field config G.Hand.Cards.Config Card configuration +---@field debuff boolean Whether card is debuffed +---@field facing string Card facing direction ("front", "back") +---@field highlighted boolean Whether card is highlighted +---@field ability G.PlayingCard.Ability Card ability information +---@field edition? G.Card.Edition Card edition (Foil, Holographic, Polychrome, Negative) +---@field seal? G.Card.Seal Card seal (Gold, Red, Blue, Purple) +---@field enhancement? G.Card.Enhancement Card enhancement (Bonus, Mult, Wild, Glass, Steel, Stone, Lucky, etc.) +---@field area? table Reference to the card area containing this card +---@field unique_val number Unique value for this card instance +---@field cost number Current cost of the card +---@field sell_cost number Current sell value of the card +---@field rank? number Card rank for sorting and organization + +-- Hand card base properties (G.hand.cards[].base) +---@class G.Hand.Cards.Base +---@field id number Card ID (2-14, where J=11, Q=12, K=13, A=14) +---@field name string Base card name (e.g., "2 of Spades", "King of Hearts") +---@field suit string Card suit ("Spades", "Hearts", "Diamonds", "Clubs") +---@field value string Current card value ("2", "3", ..., "10", "Jack", "Queen", "King", "Ace") +---@field nominal number Numeric value for scoring (2-11, face cards = 10, Ace = 11) +---@field original_value string Original card value before modifications +---@field times_played number Times this specific card has been played +---@field colour table Color information for the suit +---@field suit_nominal number Small numeric value for suit identification +---@field suit_nominal_original number Original suit nominal value +---@field face_nominal number Additional value for face cards (0.1-0.4) + +-- Hand card configuration (G.hand.cards[].config) +---@class G.Hand.Cards.Config +---@field card_key string Unique card identifier +---@field card G.Hand.Cards.Config.Card Card-specific data + +-- Hand card config card data (G.hand.cards[].config.card) +---@class G.Hand.Cards.Config.Card +---@field name string Card name +---@field suit string Card suit +---@field value string Card value + +-- ============================================================================= +-- Jokers Area and Joker Cards +-- ============================================================================= + +---@class G.Jokers +---@field config G.CardArea.Config Config for jokers card area +---@field cards G.Jokers.Card[] Array of joker card objects + +-- Joker card (G.jokers.cards[]) +---@class G.Jokers.Card +---@field label string Display label of the joker +---@field cost number Purchase cost of the joker +---@field sell_cost number Sell value of the joker +---@field sort_id number Unique identifier for this card instance +---@field config G.Jokers.Card.Config Joker card configuration +---@field debuff boolean Whether joker is debuffed +---@field facing string Card facing direction ("front", "back") +---@field highlighted boolean Whether joker is highlighted +---@field ability G.Joker.Ability Joker-specific ability data +---@field edition? G.Card.Edition Card edition (Foil, Holographic, Polychrome, Negative) +---@field area? table Reference to the card area containing this joker +---@field unique_val number Unique value for this joker instance +---@field rank? number Joker rank for sorting + +-- Joker card configuration (G.jokers.cards[].config) +---@class G.Jokers.Card.Config +---@field center_key string Key identifier for the joker center + +-- ============================================================================= +-- Consumables Area and Consumable Cards +-- ============================================================================= + +---@class G.Consumables +---@field config G.CardArea.Config Configuration for the consumables slot +---@field cards? G.Consumables.Card[] Array of consumable card objects + +-- Consumable card (G.consumables.cards[]) +---@class G.Consumables.Card +---@field label string Display label of the consumable +---@field cost number Purchase cost of the consumable +---@field sell_cost number Sell value of the consumable +---@field sort_id number Unique identifier for this consumable instance +---@field config G.Consumables.Card.Config Consumable configuration +---@field debuff boolean Whether consumable is debuffed +---@field facing string Card facing direction ("front", "back") +---@field highlighted boolean Whether consumable is highlighted +---@field ability G.Consumable.Ability Consumable-specific ability data +---@field edition? G.Card.Edition Card edition (Foil, Holographic, Polychrome, Negative) +---@field area? table Reference to the card area containing this consumable +---@field unique_val number Unique value for this consumable instance + +-- Consumable card configuration (G.consumables.cards[].config) +---@class G.Consumables.Card.Config +---@field center_key string Key identifier for the consumable center + +-- ============================================================================= +-- Shop Areas and Shop Cards +-- ============================================================================= + +-- Shop jokers area +---@class G.ShopJokers +---@field config G.CardArea.Config Configuration for the shop jokers area +---@field cards? G.Shop.Card[] Array of shop card objects + +-- Shop vouchers area +---@class G.ShopVouchers +---@field config G.CardArea.Config Configuration for the shop vouchers area +---@field cards? G.Shop.Card[] Array of shop voucher objects + +-- Shop booster area +---@class G.ShopBooster +---@field config G.CardArea.Config Configuration for the shop booster area +---@field cards? G.Shop.Card[] Array of shop booster objects + +-- Shop card +---@class G.Shop.Card +---@field label string Display label of the shop card +---@field cost number Purchase cost of the card +---@field sell_cost number Sell cost of the card +---@field sort_id number Unique identifier for this shop card instance +---@field debuff boolean Whether card is debuffed +---@field facing string Card facing direction ("front", "back") +---@field highlighted boolean Whether card is highlighted +---@field ability G.Shop.Card.Ability Card ability information +---@field config G.Shop.Card.Config Shop card configuration +---@field edition? G.Card.Edition Card edition (Foil, Holographic, Polychrome, Negative) +---@field enhancement? G.Card.Enhancement Card enhancement (for playing cards in shop) +---@field seal? G.Card.Seal Card seal (for playing cards in shop) +---@field area? table Reference to the shop area containing this card +---@field unique_val number Unique value for this card instance + +-- Shop card ability (G.shop_*.cards[].ability) +---@class G.Shop.Card.Ability +---@field set string The set of the card: "Joker", "Planet", "Tarot", "Spectral", "Voucher", "Booster", or "Consumable" + +-- Shop card configuration (G.shop_*.cards[].config) +---@class G.Shop.Card.Config +---@field center_key string Key identifier for the card center + +-- ============================================================================= +-- Additional Card Areas +-- ============================================================================= + +-- Deck area (G.deck) +---@class G.Deck +---@field config G.CardArea.Config Configuration for the deck +---@field cards G.Hand.Cards[] Array of cards in deck (when accessible) + +-- Discard pile (G.discard) +---@class G.Discard +---@field config G.CardArea.Config Configuration for the discard pile +---@field cards G.Hand.Cards[] Array of cards in discard pile (when accessible) + +-- ============================================================================= +-- Card Ability and Enhancement Types +-- ============================================================================= + +-- Base card ability interface (shared by all card types) +---@class G.Card.Ability +---@field set string The set/category of the card +---@field name? string Name of the ability +---@field consumeable? boolean Whether this is a consumable card +---@field extra? table Additional ability-specific data + +-- Playing card ability (for G.hand.cards[].ability) +---@class G.PlayingCard.Ability : G.Card.Ability +---@field name string Playing card name (e.g., "2 of Spades") +---@field suit string Card suit +---@field value string Card value +---@field effect string Current effect applied to card ("Base", "Mult", "Bonus", "Wild", etc.) + +-- Joker ability (for G.jokers.cards[].ability) +---@class G.Joker.Ability : G.Card.Ability +---@field name string Joker name (e.g., "Joker", "Greedy Joker", "Lusty Joker") +---@field set string Always "Joker" +---@field order number Display order in collection +---@field extra table Joker-specific data (varies by joker type) +---@field extra_value number Current extra value (for stacking jokers) +---@field mult number Multiplier contribution +---@field h_mult number Hand multiplier contribution +---@field t_mult number Type multiplier contribution +---@field h_size number Hand size modifier +---@field consumeable boolean Whether joker consumes on use + +-- Consumable ability (for G.consumables.cards[].ability) +---@class G.Consumable.Ability : G.Card.Ability +---@field name string Consumable name (e.g., "The Fool", "The Magician") +---@field set string The set ("Tarot", "Planet", "Spectral") +---@field order number Display order in collection +---@field consumeable boolean Always true for consumables +---@field extra? table Consumable-specific data + +-- Card edition types +---@class G.Card.Edition +---@field type string Edition type ("foil", "holo", "polychrome", "negative") +---@field chip_mod? number Chip modifier for this edition +---@field mult_mod? number Multiplier modifier for this edition + +-- Card seal types +---@class G.Card.Seal +---@field type string Seal type ("Red", "Blue", "Gold", "Purple") +---@field color table Color information for rendering + +-- Card enhancement types +---@class G.Card.Enhancement +---@field type string Enhancement type ("m_bonus", "m_mult", "m_wild", "m_glass", "m_steel", "m_stone", "m_gold", "m_lucky") +---@field chip_mod? number Chip modifier +---@field mult_mod? number Multiplier modifier +---@field effect? string Special effect description diff --git a/src/lua/types/log.lua b/src/lua/types/log.lua new file mode 100644 index 0000000..5c911e3 --- /dev/null +++ b/src/lua/types/log.lua @@ -0,0 +1,31 @@ +---@meta balatrobot-log-types +---Type definitions for logging system and log entry structures + +-- ============================================================================= +-- Log System Types (used in log.lua) +-- ============================================================================= + +---@class Log +---@field mod_path string? Path to the mod directory for log file storage +---@field current_run_file string? Path to the current run's log file +---@field pending_logs table Map of pending log entries awaiting conditions +---@field game_state_before G Game state before function call +---@field init fun() Initializes the logger by setting up hooks +---@field write fun(log_entry: LogEntry) Writes a log entry to the JSONL file +---@field update fun() Processes pending logs by checking completion conditions +---@field schedule_write fun(function_call: FunctionCall) Schedules a log entry to be written when condition is met + +---@class PendingLog +---@field log_entry table The log entry data to be written when condition is met +---@field condition function Function that returns true when the log should be written + +---@class FunctionCall +---@field name string The name of the function being called +---@field arguments table The parameters passed to the function + +---@class LogEntry +---@field timestamp_ms_before number Timestamp in milliseconds since epoch before function call +---@field timestamp_ms_after number? Timestamp in milliseconds since epoch after function call +---@field function FunctionCall Function call information +---@field game_state_before G Game state before function call +---@field game_state_after G? Game state after function call diff --git a/src/lua/types/settings.lua b/src/lua/types/settings.lua new file mode 100644 index 0000000..5828c76 --- /dev/null +++ b/src/lua/types/settings.lua @@ -0,0 +1,15 @@ +---@meta balatrobot-settings-types +---Type definitions for configuration and settings structures + +-- ============================================================================= +-- Configuration Types (used in settings.lua and balatrobot.lua) +-- ============================================================================= + +---@class BalatrobotConfig +---@field port string Port for the bot to listen on +---@field dt number Tells the game that every update is dt seconds long +---@field headless boolean Whether running in headless mode +---@field fast boolean Whether running in fast mode + +---@class SETTINGS +---@field setup fun() Main setup function that configures Love2D, Balatro speed, and headless mode diff --git a/src/lua/types/utils.lua b/src/lua/types/utils.lua new file mode 100644 index 0000000..123218d --- /dev/null +++ b/src/lua/types/utils.lua @@ -0,0 +1,12 @@ +---@meta balatrobot-utils-types +---Type definitions for utility functions and helper modules + +-- ============================================================================= +-- Utility Module (used in utils.lua) +-- ============================================================================= + +---Utility functions for game state extraction and data processing +---@class utils +---@field sets_equal fun(list1: any[], list2: any[]): boolean Checks if two lists contain the same elements +---@field table_to_json fun(obj: any, depth?: number): string Converts a Lua table to JSON string with depth limiting +---@field COMPLETION_CONDITIONS table Completion conditions for different game actions From f86afab89fd079f1a15721ca33a5176e6d73d7ca Mon Sep 17 00:00:00 2001 From: S1M0N38 Date: Mon, 11 Aug 2025 19:32:36 +0200 Subject: [PATCH 2/6] refactor: move gamestate to separate file --- src/lua/gamestate.lua | 550 ++++++++++++++++++++++++++++++++++++++++++ src/lua/utils.lua | 548 +---------------------------------------- 2 files changed, 554 insertions(+), 544 deletions(-) create mode 100644 src/lua/gamestate.lua diff --git a/src/lua/gamestate.lua b/src/lua/gamestate.lua new file mode 100644 index 0000000..90138cc --- /dev/null +++ b/src/lua/gamestate.lua @@ -0,0 +1,550 @@ +---Game state extraction and manipulation functions +gamestate = {} +local json = require("json") +local socket = require("socket") + +-- ========================================================================== +-- Game State Extraction +-- +-- In the following code there are a lot of comments which document the +-- process of understanding the game state. There are many fields that +-- we are not interested for BalatroBot. I leave the comments here for +-- future reference. +-- +-- The proper documnetation of the game state is available in the types.lua +-- file (src/lua/types.lua). +-- ========================================================================== + +---Extracts the current game state including game info, hand, and jokers +---@return G game_state The complete game state +function gamestate.get() + local game = nil + if G.GAME then + local tags = {} + if G.GAME.tags then + for i, tag in pairs(G.GAME.tags) do + tags[i] = { + -- There are a couples of fieds regarding UI. we are not intersted in that. + -- HUD_tag = table/list, -- ?? + -- ID = int -- id used in the UI or tag id? + -- ability = table/list, -- ?? + -- config = table/list, -- ?? + key = tag.key, -- id string of the tag (e.g. "tag_foil") + name = tag.name, -- text string of the tag (e.g. "Foil Tag") + -- pos = table/list, coords of the tags in the UI + -- tag_sprite = table/list, sprite of the tag for the UI + -- tally = int (default 0), -- ?? + -- triggered = bool (default false), -- false when the tag will be trigger in later stages. + -- For exaple double money trigger instantly and it's not even add to the tags talbe, + -- while other tags trigger in the next shop phase. + } + end + end + + local last_blind = { + boss = false, + name = "", + } + if G.GAME.last_blind then + last_blind = { + boss = G.GAME.last_blind.boss, -- bool. True if the last blind was a boss + name = G.GAME.last_blind.name, -- str (default "" before entering round 1) + -- When entering round 1, the last blind is set to "Small Blind". + -- So I think that the last blind refers to the blind selected in the most recent BLIND_SELECT state. + } + end + game = { + -- STOP_USE = int (default 0), -- ?? + bankrupt_at = G.GAME.bankrupt_at, + -- banned_keys = table/list, -- ?? + base_reroll_cost = G.GAME.base_reroll_cost, + + -- blind = {}, This is active during the playing phase and contains + -- information about the UI of the blind object. It can be dragged around + -- We are not interested in it. + + blind_on_deck = G.GAME.blind_on_deck, -- Small | ?? | ?? + bosses_used = { + -- bl_ = int, 1 | 0 (default 0) + -- ... x 28 + -- In a normal ante there should be only one boss used, so only one value is one + }, + -- cards_played: table, change during game phase + + chips = G.GAME.chips, + -- chip_text = str, the text of the current chips in the UI + -- common_mod = int (default 1), -- prob that a common joker appear in the shop + + -- "consumeable_buffer": int, (default 0) -- number of cards in the consumeable buffer? + -- consumeable_usage = { }, -- table/list to track the consumable usage through the run. + -- "current_boss_streak": int, (default 0) -- in the simple round should be == to the ante? + -- + + current_round = { + -- "ancient_card": { -- maybe some random card used by some joker/effect? idk + -- "suit": "Spades" | "Hearts" | "Diamonds" | "Clubs", + -- }, + -- any_hand_drawn = true, -- bool (default true) ?? + -- "cards_flipped": int, (Defualt 0) + -- "castle_card": { -- ?? + -- "suit": "Spades" | "Hearts" | "Diamonds" | "Clubs", + -- }, + + -- This should contains interesting info during playing phase + -- "current_hand": { + -- "chip_text": str, Default "-" + -- "chip_total": int, Default 0 + -- "chip_total_text: str , Default "" + -- "chips": int, Default 0 + -- "hand_level": str Default "" + -- "handname": str Default "" + -- "handname_text": str Default "" + -- "mult": int, Default 0 + -- "mult_text": str, Default "0" + -- }, + + discards_left = G.GAME.current_round.discards_left, -- Number of discards left for this round + discards_used = G.GAME.current_round.discards_used, -- int (default 0) Number of discard used in this round + + --"dollars": int, (default 0) -- maybe dollars earned in this round? + -- "dollars_to_be_earned": str, (default "") -- ?? + -- "free_rerolls": int, (default 0) -- Number of free rerolls in the shop? + hands_left = G.GAME.current_round.hands_left, -- Number of hands left for this round + hands_played = G.GAME.current_round.hands_played, -- Number of hands played in this round + + -- Reroll information (used in shop state) + reroll_cost = G.GAME.current_round.reroll_cost, -- Current cost for a shop reroll + free_rerolls = G.GAME.current_round.free_rerolls, -- Free rerolls remaining this round + -- "idol_card": { -- what's a idol card?? maybe some random used by some joker/effect? idk + -- "rank": "Ace" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "10" | "Jack" | "Queen" | "King", + -- "suit": "Spades" | "Hearts" | "Diamonds" | "Clubs", + -- }, + -- "jokers_purchased": int, (default 0) -- Number of jokers purchased in this round ? + -- "mail_card": { -- what's a mail card?? maybe some random used by some joker/effect? idk + -- "id": int -- id of the mail card + -- "rank": str, -- + -- } + -- "most_played_poker_hand": str, (Default "High Card") + -- "reroll_cost": int, (default 5) + -- "reroll_cost_increase": int, (default 0) + -- "round_dollars": int, (default 0) ?? + -- "round_text": str, (default "Round ") + -- "used_packs": table/list, + voucher = { -- this is a list cuz some effect can give multiple vouchers per ante + -- "1": "v_hone", + -- "spawn": { + -- "v_hone": "..." + -- } + }, + }, + + -- "disabled_ranks" = table/list, -- there are some boss that disable certain ranks + -- "disabled_suits" = table/list, -- there are some boss that disable certain suits + + discount_percent = G.GAME.discount_percent, -- int (default 0) this lower the price in the shop. A voucher must be redeemed + dollars = G.GAME.dollars, -- int , current dollars in the run + + -- "ecto_minus": int, + -- "edition_rate": int, (default 1) -- change the prob. to find a card which is not a base? + -- "hand_usage": table/list, (default {}) -- maybe track the hand played so far in the run? + + -- table/list. Maybe this track the various hand levels? + -- "hands": { + -- "Five of a Kind": {...}, + -- "Flush": { + -- "_saved_d_u": true, This is a private field so we are not interested in it + -- "chips": 35, current chips reward + -- "example": { + -- "1": "...", + -- "2": "...", + -- "3": "...", + -- "4": "...", + -- "5": "..." + -- }, + -- "l_chips": 15, boundary for chips? + -- "l_mult": 2, boundary for mult? + -- "level": 1, level of the hand + -- "mult": 4, curent mult reward + -- "order": 7, order for how good the hand is Five of a Kind is 1, High Card is 12 + -- "played": 0, how many time the hand has been played in this run + -- "played_this_round": 0, how many time the hand has been played in this round + -- "s_chips": 35, boundary for chips? + -- "s_mult": 4, boundary for mult? + -- "visible": true, is this hand visible in the Run Info interface + -- }, + -- "Flush Five": {...}, + -- "Flush House": {...}, + -- "Four of a Kind": {...}, + -- "Full House": {...}, + -- "High Card": {...}, + -- "Pair": {...}, + -- "Straight": {...}, + -- "Straight Flush": {...}, + -- "Three of a Kind": {...}, + -- "Two Pair": {...}, + -- }, + hands_played = G.GAME.hands_played, -- (default 0) hand played in this run + inflation = G.GAME.inflation, -- (default 0) maybe there are some stakes that increase the prices in the shop ? + interest_amount = G.GAME.interest_amount, -- (default 1) how much each $ is worth at the eval round stage + interest_cap = G.GAME.interest_cap, -- (default 25) cap for interest, e.g. 25 dollar means that every each 5 dollar you get one $ + + -- joker_buffer = int, -- (default 0) ?? + -- joker_rate = int, -- (default 20) prob that a joker appear in the shop + -- joker_usage = G.GAME.joker_usage, -- list/table maybe a list of jokers used in the run? + -- + last_blind = last_blind, + -- legendary_mod = G.GAME.legendary_mod, -- (default 1) maybe the probality/modifier to find a legendary joker in the shop? + + max_jokers = G.GAME.max_jokers, --(default 0) the number of held jokers? + + -- modifiers = list/table, -- ?? + -- orbital_choices = { -- what's an orbital choice?? This is a list (table with int keys). related to pseudorandom + -- -- 1: { + -- -- "Big": "Two Pair", + -- -- "Boss": "Three of a Kind", + -- -- "Small": "Full House" + -- -- } + -- }, + -- pack_size = G.GAME.pack_size (int default 2), -- number of pack slots ? + -- perishable_rounds = int (default 5), -- ?? + -- perscribed_bosses = list/table, -- ?? + + planet_rate = G.GAME.planet_rate, -- (int default 4) -- prob that a planet card appers in the shop + playing_card_rate = G.GAME.playing_card_rate, -- (int default 0) -- prob that a playing card appers in the shop. at the start of the run playable cards are not purchasable so it's 0, then by reedming a voucher, you can buy them in the shop. + -- pool_flags = list/table, -- ?? + + previous_round = { + -- I think that this table will contain the previous round info + -- "dollars": int, (default 4, this is the dollars amount when starting red deck white stake) + }, + probabilities = { + -- Maybe this table track various probabilities for various events (e.g. prob that planet cards appers in the + -- shop) + -- "normal": int, (default 1) + }, + + -- This table contains the seed used to start a run. The seed is used in the generation of pseudorandom number + -- which themselves are used to add randomness to a run. (e.g. which is the first tag? well the float that is + -- probably used to extract the tag for the first round is in Tag1.) + pseudorandom = { + -- float e.g. 0.1987752917732 (all the floats are in the range [0, 1) with 13 digit after the dot. + -- Tag1 = float, + -- Voucher1 = float, + -- Voucher1_resample2 = float, + -- Voucher1_resample3 = float, + -- anc1 = float, + -- boss = float, + -- cas1 = float, + -- hashed_seed = float, + -- idol1 = float, + -- mail1 = float, + -- orbital = float, + -- seed = string, This is the seed used to start a run + -- shuffle = float, + }, + -- rare_mod = G.GAME.rare_mod, (int default 1) -- maybe the probality/modifier to find a rare joker in the shop? + -- rental_rate = int (default 3), -- maybe the probality/modifier to find a rental card in the shop? + round = G.GAME.round, -- number of the current round. 0 before starting the first rounthe first round + round_bonus = { -- What's a "round_bonus"? Some bonus given at the end of the round? maybe use in the eval round phase + -- "discards": int, (default 0) ?? + -- "next_hands": int, (default 0) ?? + }, + + -- round_resets = table/list, -- const used to reset the round? but should be not relevant for our use case + round_scores = { + -- contains values used in the round eval phase? + -- "cards_discarded": { + -- "amt": int, (default 0) amount of cards discarded + -- "label": "Cards Discarded" label for the amount of cards discarded. maybe used in the interface + -- }, + -- "cards_played": {...}, amount of cards played in this round + -- "cards_purchased": {...}, amount of cards purchased in this round + -- "furthest_ante": {...}, furthest ante in this run + -- "furthest_round": {...}, furthest round in this round or run? + -- "hand": {...}, best hand in this round + -- "new_collection": {...}, new cards discovered in this round + -- "poker_hand": {...}, most played poker hand in this round + -- "times_rerolled": {...}, number of times rerolled in this round + }, + seeded = G.GAME.seeded, -- bool if the run use a seed or not + selected_back = { + -- The back should be the deck: Red Deck, Black Deck, etc. + -- This table contains functions and info about deck selection + -- effect = {} -- contains function e.g. "set" + -- loc_name = str, -- ?? (default "Red Deck") + name = G.GAME.selected_back.name, -- name of the deck + -- pos = {x = int (default 0), y = int (default 0)}, -- ?? + }, + -- seleted_back_key = table -- ?? + shop = { + -- contains info about the shop + -- joker_max = int (default 2), -- max number that can appear in the shop or the number of shop slots? + }, + skips = G.GAME.skips, -- number of skips in this run + smods_version = G.GAME.smods_version, -- version of smods loaded + -- sort = str, (default "desc") card sort order. descending (desc) or suit, I guess? + -- spectral_rate = int (default 0), -- prob that a spectral card appear in the shop + stake = G.GAME.stake, --int (default 1), -- the stake for the run (1 for White Stake, 2 for Red Stake ...) + -- starting_deck_size = int (default 52), -- the starting deck size for the run. + starting_params = { + -- The starting parmeters are maybe not relevant, we are intersted in + -- the actual values of the parameters + -- + -- ante_scaling = G.GAME.starting_params.ante_scaling, -- (default 1) increase the ante by one after boss defeated + -- boosters_in_shop = G.GAME.starting_params.boosters_in_shop, -- (default 2) Number of booster slots + -- consumable_slots = G.GAME.starting_params.consumable_slots, -- (default 2) Number of consumable slots + -- discard_limit = G.GAME.starting_params.discard_limit, -- (default 5) Number of cards to discard + -- ... + }, + + -- tag_tally = -- int (default 0), -- what's a tally? + tags = tags, + tarot_rate = G.GAME.tarot_rate, -- int (default 4), -- prob that a tarot card appear in the shop + uncommon_mod = G.GAME.uncommon_mod, -- int (default 1), -- prob that an uncommon joker appear in the shop + unused_discards = G.GAME.unused_discards, -- int (default 0), -- number of discards left at the of a round. This is used some time to in the eval round phase + -- used_jokers = { -- table/list to track the joker usage through the run ? + -- c_base = bool + -- } + used_vouchers = G.GAME.used_vouchers, -- table/list to track the voucher usage through the run. Should be the ones that can be see in "Run Info" + voucher_text = G.GAME.voucher_text, -- str (default ""), -- the text of the voucher for the current run + win_ante = G.GAME.win_ante, -- int (default 8), -- the ante for the win condition + won = G.GAME.won, -- bool (default false), -- true if the run is won (e.g. current ante > win_ante) + } + end + + local consumables = nil + if G.consumeables then + local cards = {} + if G.consumeables.cards then + for i, card in pairs(G.consumeables.cards) do + cards[i] = { + ability = { + set = card.ability.set, + }, + label = card.label, + cost = card.cost, + sort_id = card.sort_id, -- Unique identifier for this card instance (used for rearranging) + config = { + center_key = card.config.center_key, + }, + debuff = card.debuff, + facing = card.facing, + highlighted = card.highlighted, + } + end + end + consumables = { + cards = cards, + config = { + card_count = G.consumeables.config.card_count, + card_limit = G.consumeables.config.card_limit, + }, + } + end + + local hand = nil + if G.hand then + local cards = {} + for i, card in pairs(G.hand.cards) do + cards[i] = { + ability = { + set = card.ability.set, -- str. The set of the card: Joker, Planet, Voucher, Booster, or Consumable + }, + -- ability = table of card abilities effect, mult, extra_value + label = card.label, -- str (default "Base Card") | ... | ... | ? + -- playing_card = card.config.card.playing_card, -- int. The card index in the deck for the current round ? + -- sell_cost = card.sell_cost, -- int (default 1). The dollars you get if you sell this card ? + sort_id = card.sort_id, -- int. Unique identifier for this card instance + base = { + -- These should be the valude for the original base card + -- without any modifications + id = card.base.id, -- ?? + name = card.base.name, + nominal = card.base.nominal, + original_value = card.base.original_value, + suit = card.base.suit, + times_played = card.base.times_played, + value = card.base.value, + }, + config = { + card_key = card.config.card_key, + card = { + name = card.config.card.name, + suit = card.config.card.suit, + value = card.config.card.value, + }, + }, + debuff = card.debuff, + -- debuffed_by_blind = bool (default false). True if the card is debuffed by the blind + facing = card.facing, -- str (default "front") | ... | ... | ? + highlighted = card.highlighted, -- bool (default false). True if the card is highlighted + } + end + + hand = { + cards = cards, + config = { + card_count = G.hand.config.card_count, -- (int) number of cards in the hand + card_limit = G.hand.config.card_limit, -- (int) max number of cards in the hand + highlighted_limit = G.hand.config.highlighted_limit, -- (int) max number of highlighted cards in the hand + -- lr_padding ?? flaot + -- sort = G.hand.config.sort, -- (str) sort order of the hand. "desc" | ... | ? not really... idk + -- temp_limit ?? (int) + -- type ?? (Default "hand", str) + }, + -- container = table for UI elements. we are not interested in it + -- created_on_pause = bool ?? + -- highlighted = list of highlighted cards. This is a list of card. + -- hover_offset = table/list, coords of the hand in the UI. we are not interested in it. + -- last_aligned = int, ?? + -- last_moved = int, ?? + -- + -- There a a lot of other fields that we are not interested in ... + } + end + + local jokers = nil + if G.jokers then + local cards = {} + if G.jokers.cards then + for i, card in pairs(G.jokers.cards) do + cards[i] = { + ability = { + set = card.ability.set, -- str. The set of the card: Joker, Planet, Voucher, Booster, or Consumable + }, + label = card.label, + cost = card.cost, + sort_id = card.sort_id, -- Unique identifier for this card instance (used for rearranging) + config = { + center_key = card.config.center_key, + }, + debuff = card.debuff, + facing = card.facing, + highlighted = card.highlighted, + } + end + end + jokers = { + cards = cards, + config = { + card_count = G.jokers.config.card_count, + card_limit = G.jokers.config.card_limit, + }, + } + end + + local shop_jokers = nil + if G.shop_jokers then + local config = {} + if G.shop_jokers.config then + config = { + card_count = G.shop_jokers.config.card_count, -- int. how many cards are in the the shop + card_limit = G.shop_jokers.config.card_limit, -- int. how many cards can be in the shop + } + end + local cards = {} + if G.shop_jokers.cards then + for i, card in pairs(G.shop_jokers.cards) do + cards[i] = { + ability = { + set = card.ability.set, -- str. The set of the card: Joker, Planet, Voucher, Booster, or Consumable + }, + config = { + center_key = card.config.center_key, -- id of the card + }, + debuff = card.debuff, -- bool. True if the card is a debuff + cost = card.cost, -- int. The cost of the card + label = card.label, -- str. The label of the card + facing = card.facing, -- str. The facing of the card: front | back + highlighted = card.highlighted, -- bool. True if the card is highlighted + sell_cost = card.sell_cost, -- int. The sell cost of the card + } + end + end + shop_jokers = { + config = config, + cards = cards, + } + end + + local shop_vouchers = nil + if G.shop_vouchers then + local config = {} + if G.shop_vouchers.config then + config = { + card_count = G.shop_vouchers.config.card_count, + card_limit = G.shop_vouchers.config.card_limit, + } + end + local cards = {} + if G.shop_vouchers.cards then + for i, card in pairs(G.shop_vouchers.cards) do + cards[i] = { + ability = { + set = card.ability.set, + }, + config = { + center_key = card.config.center_key, + }, + debuff = card.debuff, + cost = card.cost, + label = card.label, + facing = card.facing, + highlighted = card.highlighted, + sell_cost = card.sell_cost, + } + end + end + shop_vouchers = { + config = config, + cards = cards, + } + end + + local shop_booster = nil + if G.shop_booster then + -- NOTE: In the game these are called "packs" + -- but the variable name is "cards" in the API. + local config = {} + if G.shop_booster.config then + config = { + card_count = G.shop_booster.config.card_count, + card_limit = G.shop_booster.config.card_limit, + } + end + local cards = {} + if G.shop_booster.cards then + for i, card in pairs(G.shop_booster.cards) do + cards[i] = { + ability = { + set = card.ability.set, + }, + config = { + center_key = card.config.center_key, + }, + cost = card.cost, + label = card.label, + highlighted = card.highlighted, + sell_cost = card.sell_cost, + } + end + end + shop_booster = { + config = config, + cards = cards, + } + end + + return { + state = G.STATE, + game = game, + hand = hand, + jokers = jokers, + shop_jokers = shop_jokers, -- NOTE: This contains all cards in the shop, not only jokers. + shop_vouchers = shop_vouchers, + shop_booster = shop_booster, + consumables = consumables, + } +end + +return gamestate diff --git a/src/lua/utils.lua b/src/lua/utils.lua index ca298af..0e569f1 100644 --- a/src/lua/utils.lua +++ b/src/lua/utils.lua @@ -3,554 +3,14 @@ utils = {} local json = require("json") local socket = require("socket") --- ========================================================================== --- Game State Extraction --- --- In the following code there are a lot of comments which document the --- process of understanding the game state. There are many fields that --- we are not interested for BalatroBot. I leave the comments here for --- future reference. --- --- The proper documnetation of the game state is available in the types.lua --- file (src/lua/types.lua). --- ========================================================================== - ----Extracts the current game state including game info, hand, and jokers ----@return G game_state The complete game state -function utils.get_game_state() - local game = nil - if G.GAME then - local tags = {} - if G.GAME.tags then - for i, tag in pairs(G.GAME.tags) do - tags[i] = { - -- There are a couples of fieds regarding UI. we are not intersted in that. - -- HUD_tag = table/list, -- ?? - -- ID = int -- id used in the UI or tag id? - -- ability = table/list, -- ?? - -- config = table/list, -- ?? - key = tag.key, -- id string of the tag (e.g. "tag_foil") - name = tag.name, -- text string of the tag (e.g. "Foil Tag") - -- pos = table/list, coords of the tags in the UI - -- tag_sprite = table/list, sprite of the tag for the UI - -- tally = int (default 0), -- ?? - -- triggered = bool (default false), -- false when the tag will be trigger in later stages. - -- For exaple double money trigger instantly and it's not even add to the tags talbe, - -- while other tags trigger in the next shop phase. - } - end - end - - local last_blind = { - boss = false, - name = "", - } - if G.GAME.last_blind then - last_blind = { - boss = G.GAME.last_blind.boss, -- bool. True if the last blind was a boss - name = G.GAME.last_blind.name, -- str (default "" before entering round 1) - -- When entering round 1, the last blind is set to "Small Blind". - -- So I think that the last blind refers to the blind selected in the most recent BLIND_SELECT state. - } - end - game = { - -- STOP_USE = int (default 0), -- ?? - bankrupt_at = G.GAME.bankrupt_at, - -- banned_keys = table/list, -- ?? - base_reroll_cost = G.GAME.base_reroll_cost, - - -- blind = {}, This is active during the playing phase and contains - -- information about the UI of the blind object. It can be dragged around - -- We are not interested in it. - - blind_on_deck = G.GAME.blind_on_deck, -- Small | ?? | ?? - bosses_used = { - -- bl_ = int, 1 | 0 (default 0) - -- ... x 28 - -- In a normal ante there should be only one boss used, so only one value is one - }, - -- cards_played: table, change during game phase - - chips = G.GAME.chips, - -- chip_text = str, the text of the current chips in the UI - -- common_mod = int (default 1), -- prob that a common joker appear in the shop - - -- "consumeable_buffer": int, (default 0) -- number of cards in the consumeable buffer? - -- consumeable_usage = { }, -- table/list to track the consumable usage through the run. - -- "current_boss_streak": int, (default 0) -- in the simple round should be == to the ante? - -- - - current_round = { - -- "ancient_card": { -- maybe some random card used by some joker/effect? idk - -- "suit": "Spades" | "Hearts" | "Diamonds" | "Clubs", - -- }, - -- any_hand_drawn = true, -- bool (default true) ?? - -- "cards_flipped": int, (Defualt 0) - -- "castle_card": { -- ?? - -- "suit": "Spades" | "Hearts" | "Diamonds" | "Clubs", - -- }, - - -- This should contains interesting info during playing phase - -- "current_hand": { - -- "chip_text": str, Default "-" - -- "chip_total": int, Default 0 - -- "chip_total_text: str , Default "" - -- "chips": int, Default 0 - -- "hand_level": str Default "" - -- "handname": str Default "" - -- "handname_text": str Default "" - -- "mult": int, Default 0 - -- "mult_text": str, Default "0" - -- }, - - discards_left = G.GAME.current_round.discards_left, -- Number of discards left for this round - discards_used = G.GAME.current_round.discards_used, -- int (default 0) Number of discard used in this round - - --"dollars": int, (default 0) -- maybe dollars earned in this round? - -- "dollars_to_be_earned": str, (default "") -- ?? - -- "free_rerolls": int, (default 0) -- Number of free rerolls in the shop? - hands_left = G.GAME.current_round.hands_left, -- Number of hands left for this round - hands_played = G.GAME.current_round.hands_played, -- Number of hands played in this round - - -- Reroll information (used in shop state) - reroll_cost = G.GAME.current_round.reroll_cost, -- Current cost for a shop reroll - free_rerolls = G.GAME.current_round.free_rerolls, -- Free rerolls remaining this round - -- "idol_card": { -- what's a idol card?? maybe some random used by some joker/effect? idk - -- "rank": "Ace" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "10" | "Jack" | "Queen" | "King", - -- "suit": "Spades" | "Hearts" | "Diamonds" | "Clubs", - -- }, - -- "jokers_purchased": int, (default 0) -- Number of jokers purchased in this round ? - -- "mail_card": { -- what's a mail card?? maybe some random used by some joker/effect? idk - -- "id": int -- id of the mail card - -- "rank": str, -- - -- } - -- "most_played_poker_hand": str, (Default "High Card") - -- "reroll_cost": int, (default 5) - -- "reroll_cost_increase": int, (default 0) - -- "round_dollars": int, (default 0) ?? - -- "round_text": str, (default "Round ") - -- "used_packs": table/list, - voucher = { -- this is a list cuz some effect can give multiple vouchers per ante - -- "1": "v_hone", - -- "spawn": { - -- "v_hone": "..." - -- } - }, - }, - - -- "disabled_ranks" = table/list, -- there are some boss that disable certain ranks - -- "disabled_suits" = table/list, -- there are some boss that disable certain suits - - discount_percent = G.GAME.discount_percent, -- int (default 0) this lower the price in the shop. A voucher must be redeemed - dollars = G.GAME.dollars, -- int , current dollars in the run - - -- "ecto_minus": int, - -- "edition_rate": int, (default 1) -- change the prob. to find a card which is not a base? - -- "hand_usage": table/list, (default {}) -- maybe track the hand played so far in the run? - - -- table/list. Maybe this track the various hand levels? - -- "hands": { - -- "Five of a Kind": {...}, - -- "Flush": { - -- "_saved_d_u": true, This is a private field so we are not interested in it - -- "chips": 35, current chips reward - -- "example": { - -- "1": "...", - -- "2": "...", - -- "3": "...", - -- "4": "...", - -- "5": "..." - -- }, - -- "l_chips": 15, boundary for chips? - -- "l_mult": 2, boundary for mult? - -- "level": 1, level of the hand - -- "mult": 4, curent mult reward - -- "order": 7, order for how good the hand is Five of a Kind is 1, High Card is 12 - -- "played": 0, how many time the hand has been played in this run - -- "played_this_round": 0, how many time the hand has been played in this round - -- "s_chips": 35, boundary for chips? - -- "s_mult": 4, boundary for mult? - -- "visible": true, is this hand visible in the Run Info interface - -- }, - -- "Flush Five": {...}, - -- "Flush House": {...}, - -- "Four of a Kind": {...}, - -- "Full House": {...}, - -- "High Card": {...}, - -- "Pair": {...}, - -- "Straight": {...}, - -- "Straight Flush": {...}, - -- "Three of a Kind": {...}, - -- "Two Pair": {...}, - -- }, - hands_played = G.GAME.hands_played, -- (default 0) hand played in this run - inflation = G.GAME.inflation, -- (default 0) maybe there are some stakes that increase the prices in the shop ? - interest_amount = G.GAME.interest_amount, -- (default 1) how much each $ is worth at the eval round stage - interest_cap = G.GAME.interest_cap, -- (default 25) cap for interest, e.g. 25 dollar means that every each 5 dollar you get one $ - - -- joker_buffer = int, -- (default 0) ?? - -- joker_rate = int, -- (default 20) prob that a joker appear in the shop - -- joker_usage = G.GAME.joker_usage, -- list/table maybe a list of jokers used in the run? - -- - last_blind = last_blind, - -- legendary_mod = G.GAME.legendary_mod, -- (default 1) maybe the probality/modifier to find a legendary joker in the shop? - - max_jokers = G.GAME.max_jokers, --(default 0) the number of held jokers? - - -- modifiers = list/table, -- ?? - -- orbital_choices = { -- what's an orbital choice?? This is a list (table with int keys). related to pseudorandom - -- -- 1: { - -- -- "Big": "Two Pair", - -- -- "Boss": "Three of a Kind", - -- -- "Small": "Full House" - -- -- } - -- }, - -- pack_size = G.GAME.pack_size (int default 2), -- number of pack slots ? - -- perishable_rounds = int (default 5), -- ?? - -- perscribed_bosses = list/table, -- ?? - - planet_rate = G.GAME.planet_rate, -- (int default 4) -- prob that a planet card appers in the shop - playing_card_rate = G.GAME.playing_card_rate, -- (int default 0) -- prob that a playing card appers in the shop. at the start of the run playable cards are not purchasable so it's 0, then by reedming a voucher, you can buy them in the shop. - -- pool_flags = list/table, -- ?? - - previous_round = { - -- I think that this table will contain the previous round info - -- "dollars": int, (default 4, this is the dollars amount when starting red deck white stake) - }, - probabilities = { - -- Maybe this table track various probabilities for various events (e.g. prob that planet cards appers in the - -- shop) - -- "normal": int, (default 1) - }, - - -- This table contains the seed used to start a run. The seed is used in the generation of pseudorandom number - -- which themselves are used to add randomness to a run. (e.g. which is the first tag? well the float that is - -- probably used to extract the tag for the first round is in Tag1.) - pseudorandom = { - -- float e.g. 0.1987752917732 (all the floats are in the range [0, 1) with 13 digit after the dot. - -- Tag1 = float, - -- Voucher1 = float, - -- Voucher1_resample2 = float, - -- Voucher1_resample3 = float, - -- anc1 = float, - -- boss = float, - -- cas1 = float, - -- hashed_seed = float, - -- idol1 = float, - -- mail1 = float, - -- orbital = float, - -- seed = string, This is the seed used to start a run - -- shuffle = float, - }, - -- rare_mod = G.GAME.rare_mod, (int default 1) -- maybe the probality/modifier to find a rare joker in the shop? - -- rental_rate = int (default 3), -- maybe the probality/modifier to find a rental card in the shop? - round = G.GAME.round, -- number of the current round. 0 before starting the first rounthe first round - round_bonus = { -- What's a "round_bonus"? Some bonus given at the end of the round? maybe use in the eval round phase - -- "discards": int, (default 0) ?? - -- "next_hands": int, (default 0) ?? - }, - - -- round_resets = table/list, -- const used to reset the round? but should be not relevant for our use case - round_scores = { - -- contains values used in the round eval phase? - -- "cards_discarded": { - -- "amt": int, (default 0) amount of cards discarded - -- "label": "Cards Discarded" label for the amount of cards discarded. maybe used in the interface - -- }, - -- "cards_played": {...}, amount of cards played in this round - -- "cards_purchased": {...}, amount of cards purchased in this round - -- "furthest_ante": {...}, furthest ante in this run - -- "furthest_round": {...}, furthest round in this round or run? - -- "hand": {...}, best hand in this round - -- "new_collection": {...}, new cards discovered in this round - -- "poker_hand": {...}, most played poker hand in this round - -- "times_rerolled": {...}, number of times rerolled in this round - }, - seeded = G.GAME.seeded, -- bool if the run use a seed or not - selected_back = { - -- The back should be the deck: Red Deck, Black Deck, etc. - -- This table contains functions and info about deck selection - -- effect = {} -- contains function e.g. "set" - -- loc_name = str, -- ?? (default "Red Deck") - name = G.GAME.selected_back.name, -- name of the deck - -- pos = {x = int (default 0), y = int (default 0)}, -- ?? - }, - -- seleted_back_key = table -- ?? - shop = { - -- contains info about the shop - -- joker_max = int (default 2), -- max number that can appear in the shop or the number of shop slots? - }, - skips = G.GAME.skips, -- number of skips in this run - smods_version = G.GAME.smods_version, -- version of smods loaded - -- sort = str, (default "desc") card sort order. descending (desc) or suit, I guess? - -- spectral_rate = int (default 0), -- prob that a spectral card appear in the shop - stake = G.GAME.stake, --int (default 1), -- the stake for the run (1 for White Stake, 2 for Red Stake ...) - -- starting_deck_size = int (default 52), -- the starting deck size for the run. - starting_params = { - -- The starting parmeters are maybe not relevant, we are intersted in - -- the actual values of the parameters - -- - -- ante_scaling = G.GAME.starting_params.ante_scaling, -- (default 1) increase the ante by one after boss defeated - -- boosters_in_shop = G.GAME.starting_params.boosters_in_shop, -- (default 2) Number of booster slots - -- consumable_slots = G.GAME.starting_params.consumable_slots, -- (default 2) Number of consumable slots - -- discard_limit = G.GAME.starting_params.discard_limit, -- (default 5) Number of cards to discard - -- ... - }, - - -- tag_tally = -- int (default 0), -- what's a tally? - tags = tags, - tarot_rate = G.GAME.tarot_rate, -- int (default 4), -- prob that a tarot card appear in the shop - uncommon_mod = G.GAME.uncommon_mod, -- int (default 1), -- prob that an uncommon joker appear in the shop - unused_discards = G.GAME.unused_discards, -- int (default 0), -- number of discards left at the of a round. This is used some time to in the eval round phase - -- used_jokers = { -- table/list to track the joker usage through the run ? - -- c_base = bool - -- } - used_vouchers = G.GAME.used_vouchers, -- table/list to track the voucher usage through the run. Should be the ones that can be see in "Run Info" - voucher_text = G.GAME.voucher_text, -- str (default ""), -- the text of the voucher for the current run - win_ante = G.GAME.win_ante, -- int (default 8), -- the ante for the win condition - won = G.GAME.won, -- bool (default false), -- true if the run is won (e.g. current ante > win_ante) - } - end - - local consumables = nil - if G.consumeables then - local cards = {} - if G.consumeables.cards then - for i, card in pairs(G.consumeables.cards) do - cards[i] = { - ability = { - set = card.ability.set, - }, - label = card.label, - cost = card.cost, - sort_id = card.sort_id, -- Unique identifier for this card instance (used for rearranging) - config = { - center_key = card.config.center_key, - }, - debuff = card.debuff, - facing = card.facing, - highlighted = card.highlighted, - } - end - end - consumables = { - cards = cards, - config = { - card_count = G.consumeables.config.card_count, - card_limit = G.consumeables.config.card_limit, - }, - } - end - - local hand = nil - if G.hand then - local cards = {} - for i, card in pairs(G.hand.cards) do - cards[i] = { - ability = { - set = card.ability.set, -- str. The set of the card: Joker, Planet, Voucher, Booster, or Consumable - }, - -- ability = table of card abilities effect, mult, extra_value - label = card.label, -- str (default "Base Card") | ... | ... | ? - -- playing_card = card.config.card.playing_card, -- int. The card index in the deck for the current round ? - -- sell_cost = card.sell_cost, -- int (default 1). The dollars you get if you sell this card ? - sort_id = card.sort_id, -- int. Unique identifier for this card instance - base = { - -- These should be the valude for the original base card - -- without any modifications - id = card.base.id, -- ?? - name = card.base.name, - nominal = card.base.nominal, - original_value = card.base.original_value, - suit = card.base.suit, - times_played = card.base.times_played, - value = card.base.value, - }, - config = { - card_key = card.config.card_key, - card = { - name = card.config.card.name, - suit = card.config.card.suit, - value = card.config.card.value, - }, - }, - debuff = card.debuff, - -- debuffed_by_blind = bool (default false). True if the card is debuffed by the blind - facing = card.facing, -- str (default "front") | ... | ... | ? - highlighted = card.highlighted, -- bool (default false). True if the card is highlighted - } - end - - hand = { - cards = cards, - config = { - card_count = G.hand.config.card_count, -- (int) number of cards in the hand - card_limit = G.hand.config.card_limit, -- (int) max number of cards in the hand - highlighted_limit = G.hand.config.highlighted_limit, -- (int) max number of highlighted cards in the hand - -- lr_padding ?? flaot - -- sort = G.hand.config.sort, -- (str) sort order of the hand. "desc" | ... | ? not really... idk - -- temp_limit ?? (int) - -- type ?? (Default "hand", str) - }, - -- container = table for UI elements. we are not interested in it - -- created_on_pause = bool ?? - -- highlighted = list of highlighted cards. This is a list of card. - -- hover_offset = table/list, coords of the hand in the UI. we are not interested in it. - -- last_aligned = int, ?? - -- last_moved = int, ?? - -- - -- There a a lot of other fields that we are not interested in ... - } - end - - local jokers = nil - if G.jokers then - local cards = {} - if G.jokers.cards then - for i, card in pairs(G.jokers.cards) do - cards[i] = { - ability = { - set = card.ability.set, -- str. The set of the card: Joker, Planet, Voucher, Booster, or Consumable - }, - label = card.label, - cost = card.cost, - sort_id = card.sort_id, -- Unique identifier for this card instance (used for rearranging) - config = { - center_key = card.config.center_key, - }, - debuff = card.debuff, - facing = card.facing, - highlighted = card.highlighted, - } - end - end - jokers = { - cards = cards, - config = { - card_count = G.jokers.config.card_count, - card_limit = G.jokers.config.card_limit, - }, - } - end - - local shop_jokers = nil - if G.shop_jokers then - local config = {} - if G.shop_jokers.config then - config = { - card_count = G.shop_jokers.config.card_count, -- int. how many cards are in the the shop - card_limit = G.shop_jokers.config.card_limit, -- int. how many cards can be in the shop - } - end - local cards = {} - if G.shop_jokers.cards then - for i, card in pairs(G.shop_jokers.cards) do - cards[i] = { - ability = { - set = card.ability.set, -- str. The set of the card: Joker, Planet, Voucher, Booster, or Consumable - }, - config = { - center_key = card.config.center_key, -- id of the card - }, - debuff = card.debuff, -- bool. True if the card is a debuff - cost = card.cost, -- int. The cost of the card - label = card.label, -- str. The label of the card - facing = card.facing, -- str. The facing of the card: front | back - highlighted = card.highlighted, -- bool. True if the card is highlighted - sell_cost = card.sell_cost, -- int. The sell cost of the card - } - end - end - shop_jokers = { - config = config, - cards = cards, - } - end - - local shop_vouchers = nil - if G.shop_vouchers then - local config = {} - if G.shop_vouchers.config then - config = { - card_count = G.shop_vouchers.config.card_count, - card_limit = G.shop_vouchers.config.card_limit, - } - end - local cards = {} - if G.shop_vouchers.cards then - for i, card in pairs(G.shop_vouchers.cards) do - cards[i] = { - ability = { - set = card.ability.set, - }, - config = { - center_key = card.config.center_key, - }, - debuff = card.debuff, - cost = card.cost, - label = card.label, - facing = card.facing, - highlighted = card.highlighted, - sell_cost = card.sell_cost, - } - end - end - shop_vouchers = { - config = config, - cards = cards, - } - end - - local shop_booster = nil - if G.shop_booster then - -- NOTE: In the game these are called "packs" - -- but the variable name is "cards" in the API. - local config = {} - if G.shop_booster.config then - config = { - card_count = G.shop_booster.config.card_count, - card_limit = G.shop_booster.config.card_limit, - } - end - local cards = {} - if G.shop_booster.cards then - for i, card in pairs(G.shop_booster.cards) do - cards[i] = { - ability = { - set = card.ability.set, - }, - config = { - center_key = card.config.center_key, - }, - cost = card.cost, - label = card.label, - highlighted = card.highlighted, - sell_cost = card.sell_cost, - } - end - end - shop_booster = { - config = config, - cards = cards, - } - end - - return { - state = G.STATE, - game = game, - hand = hand, - jokers = jokers, - shop_jokers = shop_jokers, -- NOTE: This contains all cards in the shop, not only jokers. - shop_vouchers = shop_vouchers, - shop_booster = shop_booster, - consumables = consumables, - } -end - -- ========================================================================== -- Utility Functions -- ========================================================================== +---Checks if two lists contain the same elements (regardless of order) +---@param list1 any[] First list to compare +---@param list2 any[] Second list to compare +---@return boolean equal True if both lists contain the same elements function utils.sets_equal(list1, list2) if #list1 ~= #list2 then return false From 24ba482c7d82efb75f440ca72271a8da921f7cc0 Mon Sep 17 00:00:00 2001 From: S1M0N38 Date: Mon, 11 Aug 2025 19:32:48 +0200 Subject: [PATCH 3/6] refactor: update src/lua with new gamestate function --- src/lua/api.lua | 37 +++++++++++++++++++------------------ src/lua/log.lua | 5 +++-- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/lua/api.lua b/src/lua/api.lua index 46f5be0..3559e20 100644 --- a/src/lua/api.lua +++ b/src/lua/api.lua @@ -1,5 +1,6 @@ local socket = require("socket") local json = require("json") +local gamestate = require("gamestate") -- Constants local SOCKET_TIMEOUT = 0 @@ -198,7 +199,7 @@ API.functions["get_game_state"] = function(_) API.pending_requests["get_game_state"] = { condition = utils.COMPLETION_CONDITIONS["get_game_state"][""], action = function() - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) end, } @@ -210,7 +211,7 @@ end API.functions["go_to_menu"] = function(_) if G.STATE == G.STATES.MENU and G.MAIN_MENU_UI then sendDebugMessage("go_to_menu called but already in menu", "API") - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) return end @@ -219,7 +220,7 @@ API.functions["go_to_menu"] = function(_) API.pending_requests["go_to_menu"] = { condition = utils.COMPLETION_CONDITIONS["go_to_menu"][""], action = function() - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) end, } @@ -279,7 +280,7 @@ API.functions["start_run"] = function(args) API.pending_requests["start_run"] = { condition = utils.COMPLETION_CONDITIONS["start_run"][""], action = function() - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) end, } @@ -336,7 +337,7 @@ API.functions["skip_or_select_blind"] = function(args) API.pending_requests["skip_or_select_blind"] = { condition = utils.COMPLETION_CONDITIONS["skip_or_select_blind"]["select"], action = function() - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) end, args = args, @@ -349,7 +350,7 @@ API.functions["skip_or_select_blind"] = function(args) API.pending_requests["skip_or_select_blind"] = { condition = utils.COMPLETION_CONDITIONS["skip_or_select_blind"]["skip"], action = function() - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) end, } @@ -450,7 +451,7 @@ API.functions["play_hand_or_discard"] = function(args) API.pending_requests["play_hand_or_discard"] = { condition = utils.COMPLETION_CONDITIONS["play_hand_or_discard"][args.action], action = function() - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) end, } @@ -524,7 +525,7 @@ API.functions["rearrange_hand"] = function(args) API.pending_requests["rearrange_hand"] = { condition = utils.COMPLETION_CONDITIONS["rearrange_hand"][""], action = function() - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) end, } @@ -600,7 +601,7 @@ API.functions["rearrange_jokers"] = function(args) API.pending_requests["rearrange_jokers"] = { condition = utils.COMPLETION_CONDITIONS["rearrange_jokers"][""], action = function() - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) end, } @@ -676,7 +677,7 @@ API.functions["rearrange_consumables"] = function(args) API.pending_requests["rearrange_consumables"] = { condition = utils.COMPLETION_CONDITIONS["rearrange_consumables"][""], action = function() - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) end, } @@ -701,7 +702,7 @@ API.functions["cash_out"] = function(_) API.pending_requests["cash_out"] = { condition = utils.COMPLETION_CONDITIONS["cash_out"][""], action = function() - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) end, } @@ -737,7 +738,7 @@ API.functions["shop"] = function(args) API.pending_requests["shop"] = { condition = utils.COMPLETION_CONDITIONS["shop"]["next_round"], action = function() - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) end, } @@ -829,7 +830,7 @@ API.functions["shop"] = function(args) return utils.COMPLETION_CONDITIONS["shop"]["buy_card"]() end, action = function() - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) end, } @@ -856,7 +857,7 @@ API.functions["shop"] = function(args) return utils.COMPLETION_CONDITIONS["shop"]["reroll"]() end, action = function() - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) end, } @@ -908,7 +909,7 @@ API.functions["shop"] = function(args) return utils.COMPLETION_CONDITIONS["shop"]["redeem_voucher"]() end, action = function() - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) end, } @@ -999,7 +1000,7 @@ API.functions["sell_joker"] = function(args) return utils.COMPLETION_CONDITIONS["sell_joker"][""]() end, action = function() - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) end, } @@ -1094,7 +1095,7 @@ API.functions["use_consumable"] = function(args) return utils.COMPLETION_CONDITIONS["use_consumable"][""]() end, action = function() - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) end, } @@ -1179,7 +1180,7 @@ API.functions["sell_consumable"] = function(args) return utils.COMPLETION_CONDITIONS["sell_consumable"][""]() end, action = function() - local game_state = utils.get_game_state() + local game_state = gamestate.get() API.send_response(game_state) end, } diff --git a/src/lua/log.lua b/src/lua/log.lua index e2758e7..828c08e 100644 --- a/src/lua/log.lua +++ b/src/lua/log.lua @@ -1,5 +1,6 @@ local json = require("json") local socket = require("socket") +local gamestate = require("gamestate") LOG = { mod_path = nil, @@ -33,7 +34,7 @@ function LOG.update() if pending_log.condition() then -- Update the log entry with after function call info pending_log.log_entry["timestamp_ms_after"] = math.floor(socket.gettime() * 1000) - pending_log.log_entry["game_state_after"] = utils.get_game_state() + pending_log.log_entry["game_state_after"] = gamestate.get() LOG.write(pending_log.log_entry) -- Prepare for the next log entry LOG.game_state_before = pending_log.log_entry.game_state_after @@ -408,7 +409,7 @@ function hook_hand_rearrange() timestamp_ms_before = timestamp_ms, game_state_before = LOG.game_state_before, timestamp_ms_after = timestamp_ms, - game_state_after = utils.get_game_state(), + game_state_after = gamestate.get(), } sendInfoMessage(function_call.name .. "(" .. json.encode(function_call.arguments) .. ")", "LOG") From 0464dc61192267bcd682f4533f4d46adef74451f Mon Sep 17 00:00:00 2001 From: Stephen Kirby Date: Mon, 11 Aug 2025 22:43:10 -0500 Subject: [PATCH 4/6] feat: clarified some missing gamestate comments --- src/lua/gamestate.lua | 76 +++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/lua/gamestate.lua b/src/lua/gamestate.lua index 90138cc..29f6ceb 100644 --- a/src/lua/gamestate.lua +++ b/src/lua/gamestate.lua @@ -81,12 +81,12 @@ function gamestate.get() -- current_round = { - -- "ancient_card": { -- maybe some random card used by some joker/effect? idk + -- "ancient_card": { -- joker specific: Ancient Joker provides 1.5x mult for this suit when played -- "suit": "Spades" | "Hearts" | "Diamonds" | "Clubs", -- }, -- any_hand_drawn = true, -- bool (default true) ?? -- "cards_flipped": int, (Defualt 0) - -- "castle_card": { -- ?? + -- "castle_card": { -- joker specific: Castle gains chips when this suit is discarded -- "suit": "Spades" | "Hearts" | "Diamonds" | "Clubs", -- }, @@ -108,29 +108,29 @@ function gamestate.get() --"dollars": int, (default 0) -- maybe dollars earned in this round? -- "dollars_to_be_earned": str, (default "") -- ?? - -- "free_rerolls": int, (default 0) -- Number of free rerolls in the shop? + -- "free_rerolls": int, (default 0) -- Number of free rerolls in the shop, provided by the joker "Chaos the Clown" hands_left = G.GAME.current_round.hands_left, -- Number of hands left for this round hands_played = G.GAME.current_round.hands_played, -- Number of hands played in this round -- Reroll information (used in shop state) reroll_cost = G.GAME.current_round.reroll_cost, -- Current cost for a shop reroll free_rerolls = G.GAME.current_round.free_rerolls, -- Free rerolls remaining this round - -- "idol_card": { -- what's a idol card?? maybe some random used by some joker/effect? idk + -- "idol_card": { -- joker specific: Idol provides 1.5x mult for exact card when played -- "rank": "Ace" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "10" | "Jack" | "Queen" | "King", -- "suit": "Spades" | "Hearts" | "Diamonds" | "Clubs", -- }, -- "jokers_purchased": int, (default 0) -- Number of jokers purchased in this round ? - -- "mail_card": { -- what's a mail card?? maybe some random used by some joker/effect? idk + -- "mail_card": { -- joker specific: Mail in rebate provides four dollars when this rank is discarded -- "id": int -- id of the mail card -- "rank": str, -- -- } - -- "most_played_poker_hand": str, (Default "High Card") + -- "most_played_poker_hand": str, (Default "High Card") -- Most played hand this GAME. -- "reroll_cost": int, (default 5) -- "reroll_cost_increase": int, (default 0) -- "round_dollars": int, (default 0) ?? -- "round_text": str, (default "Round ") -- "used_packs": table/list, - voucher = { -- this is a list cuz some effect can give multiple vouchers per ante + voucher = { -- this is a list since skip tags can cause multiple vouchers to appear in the shop -- "1": "v_hone", -- "spawn": { -- "v_hone": "..." @@ -138,39 +138,39 @@ function gamestate.get() }, }, - -- "disabled_ranks" = table/list, -- there are some boss that disable certain ranks - -- "disabled_suits" = table/list, -- there are some boss that disable certain suits + -- "disabled_ranks" = table/list, -- Some boss blinds disable certain ranks + -- "disabled_suits" = table/list, -- Some boss blinds disable certain suits discount_percent = G.GAME.discount_percent, -- int (default 0) this lower the price in the shop. A voucher must be redeemed dollars = G.GAME.dollars, -- int , current dollars in the run - -- "ecto_minus": int, - -- "edition_rate": int, (default 1) -- change the prob. to find a card which is not a base? - -- "hand_usage": table/list, (default {}) -- maybe track the hand played so far in the run? + -- "ecto_minus": int, -- The decrement to hand size that the next ectoplasm spectral card will apply + -- "edition_rate": int, (default 1) -- change the prob. to find a card which has an edition in the shop. Increased by vouchers + -- "hand_usage": table/list, (default {}) -- tracks the number of times each hand has been played this run for some jokers (supernova, etc.) - -- table/list. Maybe this track the various hand levels? + -- table/list. Tracks hand levels for gameplay and statistics for Run Info tab. (key to gameplay) -- "hands": { -- "Five of a Kind": {...}, -- "Flush": { - -- "_saved_d_u": true, This is a private field so we are not interested in it - -- "chips": 35, current chips reward - -- "example": { + -- "_saved_d_u": true, -- This is a private field so we are not interested in it + -- "chips": 35, -- current chips reward + -- "example": { -- Example hand for Run Info tab, not required for gameplay or set gamestate -- "1": "...", -- "2": "...", -- "3": "...", -- "4": "...", -- "5": "..." -- }, - -- "l_chips": 15, boundary for chips? - -- "l_mult": 2, boundary for mult? - -- "level": 1, level of the hand - -- "mult": 4, curent mult reward - -- "order": 7, order for how good the hand is Five of a Kind is 1, High Card is 12 - -- "played": 0, how many time the hand has been played in this run - -- "played_this_round": 0, how many time the hand has been played in this round - -- "s_chips": 35, boundary for chips? - -- "s_mult": 4, boundary for mult? - -- "visible": true, is this hand visible in the Run Info interface + -- "l_chips": 15, -- Increment to chips on level up + -- "l_mult": 2, -- increment to mult on level up + -- "level": 1, -- level of the hand + -- "mult": 4, -- current mult reward + -- "order": 7, -- order for how good the hand is Five of a Kind is 1, High Card is 12 + -- "played": 0, -- how many time the hand has been played in this run + -- "played_this_round": 0, --how many time the hand has been played in this round + -- "s_chips": 35, -- boundary for chips? + -- "s_mult": 4, -- boundary for mult? + -- "visible": true, -- is this hand visible in the Run Info interface. Special hands are not visible until played (e.g. flush five) -- }, -- "Flush Five": {...}, -- "Flush House": {...}, @@ -184,7 +184,7 @@ function gamestate.get() -- "Two Pair": {...}, -- }, hands_played = G.GAME.hands_played, -- (default 0) hand played in this run - inflation = G.GAME.inflation, -- (default 0) maybe there are some stakes that increase the prices in the shop ? + inflation = G.GAME.inflation, -- (default 0) Used by one challenge "inflation" to increase shop prices interest_amount = G.GAME.interest_amount, -- (default 1) how much each $ is worth at the eval round stage interest_cap = G.GAME.interest_cap, -- (default 25) cap for interest, e.g. 25 dollar means that every each 5 dollar you get one $ @@ -193,7 +193,7 @@ function gamestate.get() -- joker_usage = G.GAME.joker_usage, -- list/table maybe a list of jokers used in the run? -- last_blind = last_blind, - -- legendary_mod = G.GAME.legendary_mod, -- (default 1) maybe the probality/modifier to find a legendary joker in the shop? + -- legendary_mod = G.GAME.legendary_mod, -- (default 1) The probality/modifier to find a legendary joker in the shop? max_jokers = G.GAME.max_jokers, --(default 0) the number of held jokers? @@ -206,7 +206,7 @@ function gamestate.get() -- -- } -- }, -- pack_size = G.GAME.pack_size (int default 2), -- number of pack slots ? - -- perishable_rounds = int (default 5), -- ?? + -- perishable_rounds = int (default 5), -- Number of rounds before a perishable joker will become innate. -- perscribed_bosses = list/table, -- ?? planet_rate = G.GAME.planet_rate, -- (int default 4) -- prob that a planet card appers in the shop @@ -214,7 +214,7 @@ function gamestate.get() -- pool_flags = list/table, -- ?? previous_round = { - -- I think that this table will contain the previous round info + -- This table contains the previous round info -- "dollars": int, (default 4, this is the dollars amount when starting red deck white stake) }, probabilities = { @@ -243,9 +243,9 @@ function gamestate.get() -- shuffle = float, }, -- rare_mod = G.GAME.rare_mod, (int default 1) -- maybe the probality/modifier to find a rare joker in the shop? - -- rental_rate = int (default 3), -- maybe the probality/modifier to find a rental card in the shop? + -- rental_rate = int (default 3), -- The probality/modifier to find a rental joker in the shop (gold stake only) round = G.GAME.round, -- number of the current round. 0 before starting the first rounthe first round - round_bonus = { -- What's a "round_bonus"? Some bonus given at the end of the round? maybe use in the eval round phase + round_bonus = { -- Likely used on the green deck's special ability. pays out gold for unused discards and hands -- "discards": int, (default 0) ?? -- "next_hands": int, (default 0) ?? }, @@ -275,15 +275,15 @@ function gamestate.get() name = G.GAME.selected_back.name, -- name of the deck -- pos = {x = int (default 0), y = int (default 0)}, -- ?? }, - -- seleted_back_key = table -- ?? + -- seleted_back_key = table -- Likely the card key of the selected deck shop = { -- contains info about the shop -- joker_max = int (default 2), -- max number that can appear in the shop or the number of shop slots? }, - skips = G.GAME.skips, -- number of skips in this run + skips = G.GAME.skips, -- number of skips used in this run, used to compute some skip tags and the "Throwback" Joker smods_version = G.GAME.smods_version, -- version of smods loaded - -- sort = str, (default "desc") card sort order. descending (desc) or suit, I guess? - -- spectral_rate = int (default 0), -- prob that a spectral card appear in the shop + -- sort = str, (default "desc") -- The in-hand card sort order. rank descending ('desc') or 'suit' - also effects tarot/spectral card selection area + -- spectral_rate = int (default 0), -- prob that a spectral card appear in the shop. Only > 0 if the Ghost deck is in use stake = G.GAME.stake, --int (default 1), -- the stake for the run (1 for White Stake, 2 for Red Stake ...) -- starting_deck_size = int (default 52), -- the starting deck size for the run. starting_params = { @@ -299,9 +299,9 @@ function gamestate.get() -- tag_tally = -- int (default 0), -- what's a tally? tags = tags, - tarot_rate = G.GAME.tarot_rate, -- int (default 4), -- prob that a tarot card appear in the shop + tarot_rate = G.GAME.tarot_rate, -- int (default 4), -- prob that a tarot card appear in the shop, effected by vouchers uncommon_mod = G.GAME.uncommon_mod, -- int (default 1), -- prob that an uncommon joker appear in the shop - unused_discards = G.GAME.unused_discards, -- int (default 0), -- number of discards left at the of a round. This is used some time to in the eval round phase + unused_discards = G.GAME.unused_discards, -- int (default 0), -- number of discards left at the of a round. This is used for some joker calculations in the eval round phase -- used_jokers = { -- table/list to track the joker usage through the run ? -- c_base = bool -- } From 84df276e8bfc0edfb6cb58ec319f1814ea5a8aad Mon Sep 17 00:00:00 2001 From: Stephen Kirby Date: Mon, 11 Aug 2025 23:21:15 -0500 Subject: [PATCH 5/6] feat: add more fields and context to get gamestate --- src/lua/gamestate.lua | 51 ++++++++++++++++++++----------------- src/lua/types/gamestate.lua | 4 ++- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/lua/gamestate.lua b/src/lua/gamestate.lua index 29f6ceb..6503bfe 100644 --- a/src/lua/gamestate.lua +++ b/src/lua/gamestate.lua @@ -268,10 +268,10 @@ function gamestate.get() }, seeded = G.GAME.seeded, -- bool if the run use a seed or not selected_back = { - -- The back should be the deck: Red Deck, Black Deck, etc. - -- This table contains functions and info about deck selection + -- The back is the deck: Red Deck, Black Deck, etc. + -- This table contains functions and info about deck selection and effects -- effect = {} -- contains function e.g. "set" - -- loc_name = str, -- ?? (default "Red Deck") + -- loc_name = str, -- localized name of the deck (default "Red Deck") name = G.GAME.selected_back.name, -- name of the deck -- pos = {x = int (default 0), y = int (default 0)}, -- ?? }, @@ -328,8 +328,10 @@ function gamestate.get() center_key = card.config.center_key, }, debuff = card.debuff, - facing = card.facing, - highlighted = card.highlighted, + facing = card.facing, -- Always "front" for consumables. + highlighted = card.highlighted, -- bool. True if the card is selected. + sell_cost = card.sell_cost, -- int. The $ value of the card when sold. + edition = card.edition, -- string or nil. Can only be negative for consumables } end end @@ -347,37 +349,38 @@ function gamestate.get() local cards = {} for i, card in pairs(G.hand.cards) do cards[i] = { - ability = { + ability = { -- table of card abilities effect: enhancements, editions, seals, extra chips on discard, etc. set = card.ability.set, -- str. The set of the card: Joker, Planet, Voucher, Booster, or Consumable }, - -- ability = table of card abilities effect, mult, extra_value label = card.label, -- str (default "Base Card") | ... | ... | ? -- playing_card = card.config.card.playing_card, -- int. The card index in the deck for the current round ? - -- sell_cost = card.sell_cost, -- int (default 1). The dollars you get if you sell this card ? sort_id = card.sort_id, -- int. Unique identifier for this card instance base = { -- These should be the valude for the original base card -- without any modifications - id = card.base.id, -- ?? + id = card.base.id, -- Used to identify the rank of the card. name = card.base.name, - nominal = card.base.nominal, - original_value = card.base.original_value, + nominal = card.base.nominal, -- Used to measure the value (chips) of the card. Follows blackjack rules. May be increased by some jokers. + original_value = card.base.original_value, -- Used to measure the original value (chips) of the card. Follows blackjack rules. suit = card.base.suit, - times_played = card.base.times_played, - value = card.base.value, + times_played = card.base.times_played, -- Used to track how many times the card has been played in this run. + value = card.base.value, -- str name of the Card. 1-10, Jack, Queen, King, Ace. }, config = { - card_key = card.config.card_key, + card_key = card.config.card_key, -- Shorthand for card. I.E. S_K for Spades King. card = { name = card.config.card.name, suit = card.config.card.suit, value = card.config.card.value, }, }, - debuff = card.debuff, - -- debuffed_by_blind = bool (default false). True if the card is debuffed by the blind - facing = card.facing, -- str (default "front") | ... | ... | ? + debuff = card.debuff, -- Debuffed cards may be played but add no chips and trigger no effects. + facing = card.facing, -- str (default "front") | back highlighted = card.highlighted, -- bool (default false). True if the card is highlighted + + -- The following values exist but are not needed for gameplay + -- sell_cost = card.sell_cost, -- int (default 1). All cards have a sell cost, but playing cards can not be sold. + -- debuffed_by_blind = bool (default false). True if the card is debuffed by the blind. Joker specific: Only used for matador joker. } end @@ -385,12 +388,12 @@ function gamestate.get() cards = cards, config = { card_count = G.hand.config.card_count, -- (int) number of cards in the hand - card_limit = G.hand.config.card_limit, -- (int) max number of cards in the hand - highlighted_limit = G.hand.config.highlighted_limit, -- (int) max number of highlighted cards in the hand - -- lr_padding ?? flaot - -- sort = G.hand.config.sort, -- (str) sort order of the hand. "desc" | ... | ? not really... idk - -- temp_limit ?? (int) - -- type ?? (Default "hand", str) + card_limit = G.hand.config.card_limit, -- (int) max number of cards in the hand, note that this is a soft limit. May be exceeded by Cryptid, DNA + highlighted_limit = G.hand.config.highlighted_limit, -- (int) max number of highlighted cards in the hand (always 5 without mods) + sort = G.hand.config.sort, -- (str) sort order of the hand. "desc" (rank) | "suit" (likely important for the player) + temp_limit = G.hand.config.highlighted_limit, -- (int) temp_limit (hand size limit) may be exceed the normal hand limit if the juggler skip tag is used. Otherwise equal to card_limit + -- type -- (Default "hand", str). I believe this will always be "hand" in this context + -- lr_padding -- float, used for drawing cards in the UI }, -- container = table for UI elements. we are not interested in it -- created_on_pause = bool ?? @@ -452,7 +455,7 @@ function gamestate.get() config = { center_key = card.config.center_key, -- id of the card }, - debuff = card.debuff, -- bool. True if the card is a debuff + debuff = card.debuff, -- bool. True if the card is debuffed cost = card.cost, -- int. The cost of the card label = card.label, -- str. The label of the card facing = card.facing, -- str. The facing of the card: front | back diff --git a/src/lua/types/gamestate.lua b/src/lua/types/gamestate.lua index 1a8ddbe..ee79601 100644 --- a/src/lua/types/gamestate.lua +++ b/src/lua/types/gamestate.lua @@ -202,6 +202,8 @@ ---@field card_count number Number of cards in hand ---@field card_limit number Maximum cards allowed in hand ---@field highlighted_limit number Maximum cards that can be highlighted +---@field sort string Sort order of the hand. "desc" (rank) | "suit" +---@field temp_limit number Temporary hand limit, used by the skip tag Juggler and some jokers. -- Hand card (G.hand.cards[]) ---@class G.Hand.Cards @@ -294,7 +296,7 @@ ---@field facing string Card facing direction ("front", "back") ---@field highlighted boolean Whether consumable is highlighted ---@field ability G.Consumable.Ability Consumable-specific ability data ----@field edition? G.Card.Edition Card edition (Foil, Holographic, Polychrome, Negative) +---@field edition? G.Card.Edition Card edition (Only Negative for consumables) ---@field area? table Reference to the card area containing this consumable ---@field unique_val number Unique value for this consumable instance From a3ef63a0129a9331fff38876a9662aaed841ea30 Mon Sep 17 00:00:00 2001 From: Stephen Kirby Date: Mon, 11 Aug 2025 23:34:40 -0500 Subject: [PATCH 6/6] feat: add more comment clarity --- src/lua/gamestate.lua | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/lua/gamestate.lua b/src/lua/gamestate.lua index 6503bfe..9861b62 100644 --- a/src/lua/gamestate.lua +++ b/src/lua/gamestate.lua @@ -56,7 +56,7 @@ function gamestate.get() game = { -- STOP_USE = int (default 0), -- ?? bankrupt_at = G.GAME.bankrupt_at, - -- banned_keys = table/list, -- ?? + -- banned_keys = table/list, -- Likely for challenges that ban certain jokers or cards. base_reroll_cost = G.GAME.base_reroll_cost, -- blind = {}, This is active during the playing phase and contains @@ -78,18 +78,21 @@ function gamestate.get() -- "consumeable_buffer": int, (default 0) -- number of cards in the consumeable buffer? -- consumeable_usage = { }, -- table/list to track the consumable usage through the run. -- "current_boss_streak": int, (default 0) -- in the simple round should be == to the ante? - -- current_round = { + + -- TODO: joker specific computation, likely can be removed. -- "ancient_card": { -- joker specific: Ancient Joker provides 1.5x mult for this suit when played -- "suit": "Spades" | "Hearts" | "Diamonds" | "Clubs", -- }, - -- any_hand_drawn = true, -- bool (default true) ?? - -- "cards_flipped": int, (Defualt 0) -- "castle_card": { -- joker specific: Castle gains chips when this suit is discarded -- "suit": "Spades" | "Hearts" | "Diamonds" | "Clubs", -- }, + -- any_hand_drawn = true, -- bool (default true) ?? + -- "cards_flipped": int, (Defualt 0) + + -- This should contains interesting info during playing phase -- "current_hand": { -- "chip_text": str, Default "-" @@ -144,7 +147,6 @@ function gamestate.get() discount_percent = G.GAME.discount_percent, -- int (default 0) this lower the price in the shop. A voucher must be redeemed dollars = G.GAME.dollars, -- int , current dollars in the run - -- "ecto_minus": int, -- The decrement to hand size that the next ectoplasm spectral card will apply -- "edition_rate": int, (default 1) -- change the prob. to find a card which has an edition in the shop. Increased by vouchers -- "hand_usage": table/list, (default {}) -- tracks the number of times each hand has been played this run for some jokers (supernova, etc.) @@ -188,24 +190,22 @@ function gamestate.get() interest_amount = G.GAME.interest_amount, -- (default 1) how much each $ is worth at the eval round stage interest_cap = G.GAME.interest_cap, -- (default 25) cap for interest, e.g. 25 dollar means that every each 5 dollar you get one $ - -- joker_buffer = int, -- (default 0) ?? -- joker_rate = int, -- (default 20) prob that a joker appear in the shop - -- joker_usage = G.GAME.joker_usage, -- list/table maybe a list of jokers used in the run? - -- + last_blind = last_blind, -- legendary_mod = G.GAME.legendary_mod, -- (default 1) The probality/modifier to find a legendary joker in the shop? max_jokers = G.GAME.max_jokers, --(default 0) the number of held jokers? - -- modifiers = list/table, -- ?? - -- orbital_choices = { -- what's an orbital choice?? This is a list (table with int keys). related to pseudorandom + -- modifiers = list/table, -- Likely card modifiers available to the player. For example, negatives don't appear in the first ante. + -- orbital_choices = { -- The orbital skip tag uses this to upgrade hands when present. This is a list (table with int keys). related to pseudorandom -- -- 1: { -- -- "Big": "Two Pair", -- -- "Boss": "Three of a Kind", -- -- "Small": "Full House" -- -- } -- }, - -- pack_size = G.GAME.pack_size (int default 2), -- number of pack slots ? + -- pack_size = G.GAME.pack_size (int default 2), -- number of cards available in the pack states. Set by the opened pack. -- perishable_rounds = int (default 5), -- Number of rounds before a perishable joker will become innate. -- perscribed_bosses = list/table, -- ?? @@ -302,13 +302,21 @@ function gamestate.get() tarot_rate = G.GAME.tarot_rate, -- int (default 4), -- prob that a tarot card appear in the shop, effected by vouchers uncommon_mod = G.GAME.uncommon_mod, -- int (default 1), -- prob that an uncommon joker appear in the shop unused_discards = G.GAME.unused_discards, -- int (default 0), -- number of discards left at the of a round. This is used for some joker calculations in the eval round phase - -- used_jokers = { -- table/list to track the joker usage through the run ? - -- c_base = bool - -- } + used_vouchers = G.GAME.used_vouchers, -- table/list to track the voucher usage through the run. Should be the ones that can be see in "Run Info" voucher_text = G.GAME.voucher_text, -- str (default ""), -- the text of the voucher for the current run win_ante = G.GAME.win_ante, -- int (default 8), -- the ante for the win condition won = G.GAME.won, -- bool (default false), -- true if the run is won (e.g. current ante > win_ante) + + -- TODO: niche, will be a long time before considering these + -- "ecto_minus": int, -- The decrement to hand size that the next ectoplasm spectral card will apply + + -- TODO: Safe to remove these + -- joker_buffer = int, -- (default 0) -- Joker specifc: used to compute the riff-raff joker which creates jokers. Some jokers free up slots so the buffer is only needed internally to balatro. + -- joker_usage = G.GAME.joker_usage, -- list/table maybe a list of jokers used in all runs for long-term statistics + -- used_jokers = { -- table/list to track the joker usage through the run ? + -- c_base = bool + -- } } end @@ -492,9 +500,9 @@ function gamestate.get() debuff = card.debuff, cost = card.cost, label = card.label, - facing = card.facing, + facing = card.facing, -- Will always be "front" for vouchers. highlighted = card.highlighted, - sell_cost = card.sell_cost, + sell_cost = card.sell_cost, -- Never used for vouchers. } end end @@ -528,7 +536,7 @@ function gamestate.get() cost = card.cost, label = card.label, highlighted = card.highlighted, - sell_cost = card.sell_cost, + sell_cost = card.sell_cost, -- Never used for boosters/packs. } end end