11local socket = require (" socket" )
22local json = require (" json" )
33
4+ -- Constants
5+ local UDP_BUFFER_SIZE = 65536
6+ local SOCKET_TIMEOUT = 0
7+ local EVENT_QUEUE_THRESHOLD = 3
8+
49API = {}
510API .socket = nil
611API .functions = {}
@@ -16,7 +21,7 @@ function API.update(_)
1621 -- Create socket if it doesn't exist
1722 if not API .socket then
1823 API .socket = socket .udp ()
19- API .socket :settimeout (0 )
24+ API .socket :settimeout (SOCKET_TIMEOUT )
2025 local port = BALATRO_BOT_CONFIG .port
2126 API .socket :setsockname (" 127.0.0.1" , tonumber (port ))
2227 sendDebugMessage (" UDP socket created on port " .. port , " BALATROBOT" )
@@ -31,33 +36,28 @@ function API.update(_)
3136 end
3237
3338 -- Parse received data and run the appropriate function
34- local raw_data , client_ip , client_port = API .socket :receivefrom (65536 )
39+ local raw_data , client_ip , client_port = API .socket :receivefrom (UDP_BUFFER_SIZE )
3540 if raw_data and client_ip and client_port then
3641 -- Store the last client connection
3742 API .last_client_ip = client_ip
3843 API .last_client_port = client_port
3944
4045 local ok , data = pcall (json .decode , raw_data )
4146 if not ok then
42- sendErrorMessage (" Invalid JSON" , " BALATROBOT" )
43- API .send_response ({ error = " Invalid JSON" })
47+ API .send_error_response (" Invalid JSON" )
4448 return
4549 end
4650 if data .name == nil then
47- sendErrorMessage (" Message must contain a name" , " BALATROBOT" )
48- API .send_response ({ error = " Message must contain a name" })
51+ API .send_error_response (" Message must contain a name" )
4952 elseif data .arguments == nil then
50- sendErrorMessage (" Message must contain arguments" , " BALATROBOT" )
51- API .send_response ({ error = " Message must contain arguments" })
53+ API .send_error_response (" Message must contain arguments" )
5254 else
5355 local func = API .functions [data .name ]
5456 local args = data .arguments
5557 if func == nil then
56- sendErrorMessage (" Unknown function name: " .. data .name , " BALATROBOT" )
57- API .send_response ({ error = " Unknown function name: " .. data .name })
58+ API .send_error_response (" Unknown function name" , { function_name = data .name })
5859 elseif type (args ) ~= " table" then
59- sendErrorMessage (" Arguments must be a table" , " BALATROBOT" )
60- API .send_response ({ error = " Arguments must be a table: " .. type (args ) })
60+ API .send_error_response (" Arguments must be a table" , { received_type = type (args ) })
6161 else
6262 sendDebugMessage (data .name .. " (" .. json .encode (args ) .. " )" , " BALATROBOT" )
6363 func (args )
@@ -74,6 +74,15 @@ function API.send_response(response)
7474 end
7575end
7676
77+ function API .send_error_response (message , context )
78+ sendErrorMessage (message , " BALATROBOT" )
79+ local response = { error = message , state = G .STATE }
80+ if context then
81+ response .context = context
82+ end
83+ API .send_response (response )
84+ end
85+
7786function API .init ()
7887 -- Hook into the game's update loop
7988 local original_update = love .update
@@ -140,8 +149,7 @@ API.functions["start_run"] = function(args)
140149 end
141150 end
142151 if not deck_found then
143- sendErrorMessage (" Invalid deck arg for start_run: " .. tostring (args .deck ), " BALATROBOT" )
144- API .send_response ({ error = " Invalid deck arg for start_run: " .. tostring (args .deck ) })
152+ API .send_error_response (" Invalid deck arg for start_run" , { deck = args .deck })
145153 return
146154 end
147155
@@ -206,16 +214,17 @@ API.functions["skip_or_select_blind"] = function(args)
206214 end ,
207215 }
208216 else
209- sendErrorMessage (" Invalid action arg for skip_or_select_blind: " .. args .action , " BALATROBOT" )
210- API .send_response ({ error = " Invalid action arg for skip_or_select_blind: " .. args .action })
217+ API .send_error_response (" Invalid action arg for skip_or_select_blind" , { action = args .action })
211218 return
212219 end
213220end
214221
215222API .functions [" play_hand_or_discard" ] = function (args )
216223 if args .action == " discard" and G .GAME .current_round .discards_left == 0 then
217- sendErrorMessage (" No discards left to perform discard" , " BALATROBOT" )
218- API .send_response ({ error = " No discards left to perform discard" , state = G .STATE })
224+ API .send_error_response (
225+ " No discards left to perform discard" ,
226+ { discards_left = G .GAME .current_round .discards_left }
227+ )
219228 return
220229 end
221230
@@ -227,8 +236,7 @@ API.functions["play_hand_or_discard"] = function(args)
227236 -- Check that all cards are selectable
228237 for _ , card_index in ipairs (args .cards ) do
229238 if not G .hand .cards [card_index ] then
230- sendErrorMessage (" Invalid card index: " .. tostring (card_index ), " BALATROBOT" )
231- API .send_response ({ error = " Invalid card index: " .. tostring (card_index ) })
239+ API .send_error_response (" Invalid card index" , { card_index = card_index , hand_size = # G .hand .cards })
232240 return
233241 end
234242 end
@@ -247,16 +255,15 @@ API.functions["play_hand_or_discard"] = function(args)
247255 local discard_button = UIBox :get_UIE_by_ID (" discard_button" , G .buttons .UIRoot )
248256 G .FUNCS [" discard_cards_from_highlighted" ](discard_button )
249257 else
250- sendErrorMessage (" Invalid action arg for play_hand_or_discard: " .. args .action , " BALATROBOT" )
251- API .send_response ({ error = " Invalid action arg for play_hand_or_discard: " .. args .action })
258+ API .send_error_response (" Invalid action arg for play_hand_or_discard" , { action = args .action })
252259 return
253260 end
254261
255262 -- Defer sending response until the run has started
256263 API .pending_requests [" play_hand_or_discard" ] = {
257264 condition = function ()
258265 -- TODO: maybe remove brittle G.E_MANAGER check
259- if # G .E_MANAGER .queues .base < 3 and G .STATE_COMPLETE then
266+ if # G .E_MANAGER .queues .base < EVENT_QUEUE_THRESHOLD and G .STATE_COMPLETE then
260267 -- round still going
261268 if G .buttons and G .STATE == G .STATES .SELECTING_HAND then
262269 return true
@@ -280,15 +287,14 @@ end
280287API .functions [" cash_out" ] = function (_ )
281288 -- Validate current game state is appropriate for cash out
282289 if G .STATE ~= G .STATES .ROUND_EVAL then
283- sendErrorMessage (" Cannot cash out when not in shop. Current state: " .. tostring (G .STATE ), " BALATROBOT" )
284- API .send_response ({ error = " Cannot cash out when not in shop" , state = G .STATE })
290+ API .send_error_response (" Cannot cash out when not in shop" , { current_state = G .STATE })
285291 return
286292 end
287293
288294 G .FUNCS .cash_out ({ config = {} })
289295 API .pending_requests [" cash_out" ] = {
290296 condition = function ()
291- return G .STATE == G .STATES .SHOP and # G .E_MANAGER .queues .base < 3 and G .STATE_COMPLETE
297+ return G .STATE == G .STATES .SHOP and # G .E_MANAGER .queues .base < EVENT_QUEUE_THRESHOLD and G .STATE_COMPLETE
292298 end ,
293299 action = function ()
294300 local game_state = utils .get_game_state ()
0 commit comments