From cdb1c1f814b3bd21a4133682fab6059bd616c20a Mon Sep 17 00:00:00 2001 From: Davis Cook Date: Mon, 16 Jan 2023 14:25:58 -0500 Subject: [PATCH 1/7] allow negative seeds in regex --- src/main/java/communicationmod/CommandExecutor.java | 2 +- src/main/java/communicationmod/GameStateConverter.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/communicationmod/CommandExecutor.java b/src/main/java/communicationmod/CommandExecutor.java index 122555f..f5b3154 100644 --- a/src/main/java/communicationmod/CommandExecutor.java +++ b/src/main/java/communicationmod/CommandExecutor.java @@ -352,7 +352,7 @@ private static void executeStartCommand(String[] tokens) throws InvalidCommandEx } if(tokens.length >= 4) { String seedString = tokens[3].toUpperCase(); - if(!seedString.matches("^[A-Z0-9]+$")) { + if(!seedString.matches("^-?[A-Z0-9]+$")) { throw new InvalidCommandException(tokens, InvalidCommandException.InvalidCommandFormat.INVALID_ARGUMENT, seedString); } seedSet = true; diff --git a/src/main/java/communicationmod/GameStateConverter.java b/src/main/java/communicationmod/GameStateConverter.java index 6842f0a..5ca3e35 100644 --- a/src/main/java/communicationmod/GameStateConverter.java +++ b/src/main/java/communicationmod/GameStateConverter.java @@ -698,6 +698,7 @@ private static HashMap convertMonsterToJson(AbstractMonster mons jsonMonster.put("second_last_move_id", monster.moveHistory.get(monster.moveHistory.size() - 3)); } jsonMonster.put("half_dead", monster.halfDead); + jsonMonster.put("is_escaping", monster.isEscaping); jsonMonster.put("is_gone", monster.isDeadOrEscaped()); jsonMonster.put("block", monster.currentBlock); jsonMonster.put("powers", convertCreaturePowersToJson(monster)); From 31a903a5d059853e1de57218b0e6a8791010d965 Mon Sep 17 00:00:00 2001 From: Davis Cook Date: Mon, 16 Jan 2023 14:35:37 -0500 Subject: [PATCH 2/7] Allow numerical seeds --- src/main/java/communicationmod/CommandExecutor.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/communicationmod/CommandExecutor.java b/src/main/java/communicationmod/CommandExecutor.java index f5b3154..31a27b4 100644 --- a/src/main/java/communicationmod/CommandExecutor.java +++ b/src/main/java/communicationmod/CommandExecutor.java @@ -352,11 +352,19 @@ private static void executeStartCommand(String[] tokens) throws InvalidCommandEx } if(tokens.length >= 4) { String seedString = tokens[3].toUpperCase(); + boolean numerical = false; if(!seedString.matches("^-?[A-Z0-9]+$")) { throw new InvalidCommandException(tokens, InvalidCommandException.InvalidCommandFormat.INVALID_ARGUMENT, seedString); } + if (seedString.matches("^-?[0-9]+$]")) { + numerical = true; + } seedSet = true; - seed = SeedHelper.getLong(seedString); + if (numerical) { + seed = Long.parseLong(seedString); + } else { + seed = SeedHelper.getLong(seedString); + } boolean isTrialSeed = TrialHelper.isTrialSeed(seedString); if (isTrialSeed) { Settings.specialSeed = seed; From eff40845b327f8356f0cf5e35f48263f5cec7e70 Mon Sep 17 00:00:00 2001 From: Davis Cook Date: Mon, 16 Jan 2023 14:48:48 -0500 Subject: [PATCH 3/7] fix regex --- src/main/java/communicationmod/CommandExecutor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/communicationmod/CommandExecutor.java b/src/main/java/communicationmod/CommandExecutor.java index 31a27b4..3c34db7 100644 --- a/src/main/java/communicationmod/CommandExecutor.java +++ b/src/main/java/communicationmod/CommandExecutor.java @@ -356,7 +356,7 @@ private static void executeStartCommand(String[] tokens) throws InvalidCommandEx if(!seedString.matches("^-?[A-Z0-9]+$")) { throw new InvalidCommandException(tokens, InvalidCommandException.InvalidCommandFormat.INVALID_ARGUMENT, seedString); } - if (seedString.matches("^-?[0-9]+$]")) { + if (seedString.matches("^-?[0-9]+$")) { numerical = true; } seedSet = true; From ffd4890b69cd48794cd0e7dcaa4c4aa247131cf0 Mon Sep 17 00:00:00 2001 From: Davis Cook Date: Mon, 16 Jan 2023 22:00:40 -0500 Subject: [PATCH 4/7] introduce console commands to pause/play/restart the comms process while running --- .../java/communicationmod/CommsCommand.java | 59 +++++++++++++++++++ .../communicationmod/CommunicationMod.java | 6 +- 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/main/java/communicationmod/CommsCommand.java diff --git a/src/main/java/communicationmod/CommsCommand.java b/src/main/java/communicationmod/CommsCommand.java new file mode 100644 index 0000000..dfb574e --- /dev/null +++ b/src/main/java/communicationmod/CommsCommand.java @@ -0,0 +1,59 @@ +package communicationmod; + +import basemod.DevConsole; +import basemod.devcommands.ConsoleCommand; + +import java.util.ArrayList; + +public class CommsCommand extends ConsoleCommand { + + public CommsCommand() { + maxExtraTokens = 1; + minExtraTokens = 1; + requiresPlayer = false; + } + + private static void cmdHelp() { + DevConsole.couldNotParse(); + DevConsole.log("options are:"); + DevConsole.log("* pause"); + DevConsole.log("* play"); + DevConsole.log("* restart"); + } + + public void execute(String[] tokens, int depth) { + switch (tokens[1].toLowerCase()) { + case "pause": + CommunicationMod.paused = true; + break; + case "play": + CommunicationMod.paused = false; + break; + case "restart": + CommunicationMod.paused = false; + CommunicationMod.mustSendGameState = true; + } + } + + @Override + public ArrayList extraOptions(String[] tokens, int depth) { + ArrayList result = new ArrayList<>(); + result.add("pause"); + result.add("play"); + result.add("restart"); + + if (tokens.length == depth + 1) { + return result; + } else if (result.contains(tokens[depth])) { + complete = true; + result = new ArrayList<>(); + } + return result; + } + + @Override + public void errorMsg() { + cmdHelp(); + } + +} diff --git a/src/main/java/communicationmod/CommunicationMod.java b/src/main/java/communicationmod/CommunicationMod.java index 375521e..1d4464b 100644 --- a/src/main/java/communicationmod/CommunicationMod.java +++ b/src/main/java/communicationmod/CommunicationMod.java @@ -26,6 +26,8 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import static basemod.devcommands.ConsoleCommand.addCommand; + @SpireInitializer public class CommunicationMod implements PostInitializeSubscriber, PostUpdateSubscriber, PostDungeonUpdateSubscriber, PreUpdateSubscriber, OnStateChangeSubscriber { @@ -37,6 +39,7 @@ public class CommunicationMod implements PostInitializeSubscriber, PostUpdateSub private static BlockingQueue writeQueue; private static Thread readThread; private static BlockingQueue readQueue; + public static boolean paused; private static final String MODNAME = "Communication Mod"; private static final String AUTHOR = "Forgotten Arbiter"; private static final String DESCRIPTION = "This mod communicates with an external program to play Slay the Spire."; @@ -81,6 +84,7 @@ public CommunicationMod(){ public static void initialize() { CommunicationMod mod = new CommunicationMod(); + addCommand("comms", CommsCommand.class); } public void receivePreUpdate() { @@ -89,7 +93,7 @@ public void receivePreUpdate() { writeThread.interrupt(); readThread.interrupt(); } - if(messageAvailable()) { + if(messageAvailable() && !paused) { try { boolean stateChanged = CommandExecutor.executeCommand(readMessage()); if(stateChanged) { From c7a1008083ceda7a55f4dc0c7e6b7700743d3305 Mon Sep 17 00:00:00 2001 From: Davis Cook Date: Mon, 16 Jan 2023 22:00:50 -0500 Subject: [PATCH 5/7] transfer runic dome intents --- .../communicationmod/GameStateConverter.java | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/main/java/communicationmod/GameStateConverter.java b/src/main/java/communicationmod/GameStateConverter.java index 5ca3e35..e669ba2 100644 --- a/src/main/java/communicationmod/GameStateConverter.java +++ b/src/main/java/communicationmod/GameStateConverter.java @@ -669,27 +669,25 @@ private static HashMap convertMonsterToJson(AbstractMonster mons jsonMonster.put("name", monster.name); jsonMonster.put("current_hp", monster.currentHealth); jsonMonster.put("max_hp", monster.maxHealth); - if (AbstractDungeon.player.hasRelic(RunicDome.ID)) { - jsonMonster.put("intent", AbstractMonster.Intent.NONE); - } else { - jsonMonster.put("intent", monster.intent.name()); - EnemyMoveInfo moveInfo = (EnemyMoveInfo)ReflectionHacks.getPrivate(monster, AbstractMonster.class, "move"); - if (moveInfo != null) { - jsonMonster.put("move_id", moveInfo.nextMove); - jsonMonster.put("move_base_damage", moveInfo.baseDamage); - int intentDmg = (int)ReflectionHacks.getPrivate(monster, AbstractMonster.class, "intentDmg"); - if (moveInfo.baseDamage > 0) { - jsonMonster.put("move_adjusted_damage", intentDmg); - } else { - jsonMonster.put("move_adjusted_damage", moveInfo.baseDamage); - } - int move_hits = moveInfo.multiplier; - // If isMultiDamage is not set, the multiplier is probably 0, but there is really 1 attack. - if (!moveInfo.isMultiDamage) { - move_hits = 1; - } - jsonMonster.put("move_hits", move_hits); + // send over full intent information + // the user must exclude this if runic dome is present if they wish to avoid "cheating" + jsonMonster.put("intent", monster.intent.name()); + EnemyMoveInfo moveInfo = (EnemyMoveInfo)ReflectionHacks.getPrivate(monster, AbstractMonster.class, "move"); + if (moveInfo != null) { + jsonMonster.put("move_id", moveInfo.nextMove); + jsonMonster.put("move_base_damage", moveInfo.baseDamage); + int intentDmg = (int)ReflectionHacks.getPrivate(monster, AbstractMonster.class, "intentDmg"); + if (moveInfo.baseDamage > 0) { + jsonMonster.put("move_adjusted_damage", intentDmg); + } else { + jsonMonster.put("move_adjusted_damage", moveInfo.baseDamage); + } + int move_hits = moveInfo.multiplier; + // If isMultiDamage is not set, the multiplier is probably 0, but there is really 1 attack. + if (!moveInfo.isMultiDamage) { + move_hits = 1; } + jsonMonster.put("move_hits", move_hits); } if(monster.moveHistory.size() >= 2) { jsonMonster.put("last_move_id", monster.moveHistory.get(monster.moveHistory.size() - 2)); From 2def5a6d1a85f2d4914bc3ff0ecec8d372870add Mon Sep 17 00:00:00 2001 From: Davis Cook Date: Mon, 16 Jan 2023 23:13:47 -0500 Subject: [PATCH 6/7] introduce abandon command --- .../java/communicationmod/CommandExecutor.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/communicationmod/CommandExecutor.java b/src/main/java/communicationmod/CommandExecutor.java index 3c34db7..2a6b08a 100644 --- a/src/main/java/communicationmod/CommandExecutor.java +++ b/src/main/java/communicationmod/CommandExecutor.java @@ -20,6 +20,7 @@ import com.megacrit.cardcrawl.random.Random; import com.megacrit.cardcrawl.relics.AbstractRelic; import com.megacrit.cardcrawl.rooms.*; +import com.megacrit.cardcrawl.screens.DeathScreen; import communicationmod.patches.InputActionPatch; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -66,6 +67,9 @@ public static boolean executeCommand(String command) throws InvalidCommandExcept case "start": executeStartCommand(tokens); return true; + case "abandon": + executeAbandonCommand(); + return true; case "state": executeStateCommand(); return false; @@ -108,6 +112,9 @@ public static ArrayList getAvailableCommands() { if (isStartCommandAvailable()) { availableCommands.add("start"); } + if (isAbandonCommandAvailable()) { + availableCommands.add("abandon"); + } if (isInDungeon()) { availableCommands.add("key"); availableCommands.add("click"); @@ -189,6 +196,10 @@ public static boolean isStartCommandAvailable() { return !isInDungeon() && CardCrawlGame.mainMenuScreen != null; } + public static boolean isAbandonCommandAvailable() { + return !isStartCommandAvailable(); + } + private static void executeStateCommand() { CommunicationMod.mustSendGameState = true; } @@ -389,6 +400,12 @@ private static void executeStartCommand(String[] tokens) throws InvalidCommandEx GameStateListener.resetStateVariables(); } + private static void executeAbandonCommand() { + AbstractDungeon.closeCurrentScreen(); + AbstractDungeon.player.isDead = true; + AbstractDungeon.deathScreen = new DeathScreen(AbstractDungeon.getMonsters()); + } + private static void executeKeyCommand(String[] tokens) throws InvalidCommandException { if (tokens.length < 2) { throw new InvalidCommandException(tokens, InvalidCommandException.InvalidCommandFormat.MISSING_ARGUMENT); From 72a003c08216df1f665ff5c301176a9a978d01bd Mon Sep 17 00:00:00 2001 From: Davis Cook Date: Tue, 17 Jan 2023 07:17:58 -0500 Subject: [PATCH 7/7] include some more detailed information about the states of monsters --- .../communicationmod/GameStateConverter.java | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/src/main/java/communicationmod/GameStateConverter.java b/src/main/java/communicationmod/GameStateConverter.java index e669ba2..d5a4a6e 100644 --- a/src/main/java/communicationmod/GameStateConverter.java +++ b/src/main/java/communicationmod/GameStateConverter.java @@ -13,6 +13,10 @@ import com.megacrit.cardcrawl.map.MapRoomNode; import com.megacrit.cardcrawl.monsters.AbstractMonster; import com.megacrit.cardcrawl.monsters.EnemyMoveInfo; +import com.megacrit.cardcrawl.monsters.beyond.*; +import com.megacrit.cardcrawl.monsters.city.BookOfStabbing; +import com.megacrit.cardcrawl.monsters.city.Champ; +import com.megacrit.cardcrawl.monsters.exordium.Hexaghost; import com.megacrit.cardcrawl.neow.NeowEvent; import com.megacrit.cardcrawl.orbs.AbstractOrb; import com.megacrit.cardcrawl.potions.AbstractPotion; @@ -689,12 +693,50 @@ private static HashMap convertMonsterToJson(AbstractMonster mons } jsonMonster.put("move_hits", move_hits); } - if(monster.moveHistory.size() >= 2) { + if (monster.moveHistory.size() >= 2) { jsonMonster.put("last_move_id", monster.moveHistory.get(monster.moveHistory.size() - 2)); } - if(monster.moveHistory.size() >= 3) { + if (monster.moveHistory.size() >= 3) { jsonMonster.put("second_last_move_id", monster.moveHistory.get(monster.moveHistory.size() - 3)); } + + // some monster-specific information that isn't a part of powers + String[] miscIntFieldNames = { + "nipDmg", "thornsCount", "stabCount", "biteDmg", "currentCharge" + }; + // nipDmg is the darkling nip attack damage amount (only available to the player after the first time the darkling rolls nip) + // thornsCount is the number of times a spiker has buffed its thorns amount + // stabCount is the number of stabs the book of stabbing does in its multi-attack + // biteDmg is the louse bite attack damage amount (only available to the player after the first time the louse rolls bite) + // currentCharge is the amount of turns the gremlin wizard has charged up + Object misc; + for (String fieldName : miscIntFieldNames) { + misc = getFieldIfExists(monster, fieldName); + if (misc != null) { + jsonMonster.put("miscInt", (int)misc); + break; + } + } + String[] miscBoolFieldNames = { + "thresholdReached", "usedHaste", "form1", "usedMegaDebuff", "usedStasis" + }; + // thresholdReached is if champ has gone below half HP - this is probably redundant + // usedHaste is if time eater has used haste (the heal move) + // form1 is if awakened one is in its first form still + // usedMegaDebuff is if writhing mass has used implant (the curse move) + // usedStasis is if the bronze orb has used stasis yet + for (String fieldName : miscBoolFieldNames) { + misc = getFieldIfExists(monster, fieldName); + if (misc != null) { + jsonMonster.put("miscBool", (boolean)misc); + break; + } + } + if (monster instanceof Hexaghost) { + jsonMonster.put("active_orbs", getFieldIfExists(monster, "orbActiveCount")); + jsonMonster.put("miscInt", monster.damage.get(2).base); + } + jsonMonster.put("half_dead", monster.halfDead); jsonMonster.put("is_escaping", monster.isEscaping); jsonMonster.put("is_gone", monster.isDeadOrEscaped()); @@ -782,13 +824,14 @@ private static ArrayList convertCreaturePowersToJson(AbstractCreature cr json_power.put("card", convertCardToJson((AbstractCard)card)); } String[] miscFieldNames = { - "basePower", "maxAmt", "storedAmount", "hpLoss", "cardsDoubledThisTurn" + "basePower", "maxAmt", "storedAmount", "hpLoss", "cardsDoubledThisTurn", "energyGainAmount" }; // basePower gives the base power for Malleable // maxAmt gives the max amount of damage per turn for Invincible // storedAmount gives the number of stacks per turn for Flight // hpLoss gives the amount of HP lost per turn with Combust // cardsDoubledThisTurn gives the number of cards already doubled with Echo Form + // energyGainedAmount gives the amount of energy per turn given by Deva Form Object misc = null; for (String fieldName : miscFieldNames) { misc = getFieldIfExists(power, fieldName);