
LBOLMP
Adds co-op multiplayer to LBoL, similar to Slay the Spire 2. F2 to open multiplayer menu.Agent control
A development-only HTTP interface that lets an external program read the board and play the game, so a second instance can be driven by a script or an AI agent instead of by hand. Built for testing multiplayer without needing two humans.
This is a remote control channel into a running game. It is off unless you turn it on, and it
only ever listens on 127.0.0.1. Leave it off in normal play.
Turning it on
Set AgentPort in the Debug section of the config for the instance you want driven:
[Debug]
AgentPort = 7788
Give each instance its own port if you want more than one driven. 0 disables it.
The instance with the port open is the one being driven: while the server is running, it also answers card-picking prompts over HTTP instead of showing them on screen.
Using it
./tools/agent.sh # read the board
./tools/agent.sh play 0 e1 # play hand card 0 at enemy 1
./tools/agent.sh end # end the turn
Or straight to curl:
curl -s http://127.0.0.1:7788/state
curl -s --data-binary 'play 0 e1' http://127.0.0.1:7788/cmd
Every response carries the board, but a move is queued, not run inline: the reply shows the
board as it was when the command was accepted, with battle.yourMove false. Poll /state
until it goes true again to see the result.
Commands
| Command | Meaning |
|---|---|
state |
Read the board and change nothing. |
play <i> |
Play hand card i, for a card that needs no target. |
play <i> e<n> |
... aimed at enemy n. |
play <i> p<id> |
... aimed at the player with id id, for the Partner-targeted cards. |
play <i> mana=2WU |
... paying an exact cost, when the automatic choice picks badly. |
end |
End the turn. |
answer <i> <j> ... |
Answer a waiting prompt with those cards, by index into interaction.cards. |
answer none |
Answer it with nothing, where the prompt allows that. |
us / us e0 |
Use the spell card. |
Outside a fight:
| Command | Meaning |
|---|---|
take <i> |
Take station reward i (money, exhibit, tool). |
take <i> <c> |
Take card c from card reward i. |
skip <i> |
Abandon reward i. |
vote |
Vote for whichever reachable node the party likes best. |
vote <x> <y> |
Vote for a specific node. |
buy card <i> / buy exhibit <i> |
Buy from the shop. |
upgrade <d> / remove <d> |
Shop card services, by index into deck. |
gap <i> |
Pick a gap option (tea, upgrade). |
choose <i> |
Answer an event or a boss selection. |
next / skiptext |
Advance dialogue a line, or fast-forward it. |
boss <i> |
Take a shining exhibit after a boss. |
pick <i> |
Answer the deck card picker (gap upgrade, card transform). pick -1 cancels. |
advance |
Move on to the next act once the boss station is finished. |
Indices are always into the arrays the last state returned. Nothing is remembered between calls,
so read the board again after anything that changes it.
Reading the board
state returns phase, session (who else is in the lobby and who can be targeted), and, in a
fight, a battle object holding your unit, the enemies with their intentions, your hand, and pile
counts. Two fields matter most:
battle.yourMove— whether a command will be accepted at all right now.battle.interaction— non-null when a card has stopped to ask something. Nothing else is accepted until it is answered.
Each hand card reports canUse and affordable separately, so an agent can tell "I cannot pay for
this" from "this card refuses to be played".
Reading the board outside a fight
state carries station, rewards, map (reachable nodes with vote counts), shop, gap,
bossRewards, dialog and deck. Whatever does not apply is null.
vote with no arguments follows the party rather than choosing for itself, which is what makes an
agent tag along with your map decisions instead of arguing with them.
What it does not do
Only five card prompts are answered here: SelectCardInteraction, SelectHandInteraction,
MiniSelectCardInteraction, UpgradeCardInteraction and RemoveCardInteraction. Anything else
falls through to the real UI rather than hanging, so a prompt this does not understand needs a click
on that instance.
Out of a fight there is no equivalent of BattleController.Request* to work through: the run's flow
waits on panels being dismissed, not on model state. So most out-of-battle commands drive the same
entry point a click would. That makes them more fragile than the battle commands, and more likely to
need revisiting if the game updates. The one exception is taking a card from a card reward, where
the panel's own picker is unreachable from outside and the model call is made directly.
Mana is chosen for you by the game's own solver, which never uses the pool that the mana panel lets a
human stage cards in. mana= overrides it when that matters.