├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
└── src
└── main
├── java
└── seedsearch
│ ├── IdChecker.java
│ ├── Reward.java
│ ├── SearchSettings.java
│ ├── SeedResult.java
│ ├── SeedRunner.java
│ ├── SeedSearch.java
│ └── patches
│ ├── AbstractCardPatch.java
│ ├── AbstractCreaturePatch.java
│ ├── AbstractRoomPatch.java
│ ├── AnimatedNpcPatch.java
│ ├── AnimationStatePatch.java
│ ├── BaseModPatch.java
│ ├── BeyondPatch.java
│ ├── BottledFlamePatch.java
│ ├── BottledLightningPatch.java
│ ├── BottledTornadoPatch.java
│ ├── CardCrawlGameRenderPatch.java
│ ├── CardRewardScreenPatch.java
│ ├── CityPatch.java
│ ├── CombatRewardScreenPatch.java
│ ├── EndingPatch.java
│ ├── EventHelperPatch.java
│ ├── ExordiumPatch.java
│ ├── LoadImagePatch.java
│ ├── LogMetricPatch.java
│ ├── MainPatch.java
│ ├── MonsterHelperPatch.java
│ ├── SaveHelperPatch.java
│ ├── ShowCardAndObtainEffectPatch.java
│ ├── StartSearch.java
│ ├── TextureAtlasPatch.java
│ └── WatcherPatch.java
└── resources
└── ModTheSpire.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 |
25 | # IntelliJ
26 | .idea/
27 | out/
28 | SeedSearch.iml
29 |
30 | # maven
31 | target/
32 | dependency-reduced-pom.xml
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 ForgottenArbiter
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SeedSearch
2 | A mod that searches through Slay the Spire seeds.
3 |
4 | ## Requirements
5 |
6 | * Slay the Spire
7 | * ModTheSpire (https://github.com/kiooeht/ModTheSpire)
8 |
9 | Note: Because it runs a headless version of Slay the Spire and has no access to graphics, SeedSearch is not designed for compatibility with all other mods, and has no mod requirements. Also, any mod can change the outcome of seeds, so you should not use any mods, including BaseMod, with SeedSearch when searching for seeds to use in unmodded gameplay. Still, some character mods, such as Thorton and Jorbs Mod, have been found to work (or at least not crash/hang) with SeedSearch.
10 |
11 | ## Setup and Usage
12 |
13 | To install the mod, download seedsearch.jar from the [Releases](https://github.com/ForgottenArbiter/SeedSearch/releases) page, or compile it yourself. Create a folder named "mods" in your Slay the Spire installation directory if it does not yet exist, and put seedsearch.jar in that folder. When you run the mod launcher, SeedSearch should now show up.
14 |
15 | Run the mod once to search the first 100 seeds with some default settings. On the current patch of the game (v2.2), you should find one seed (54). A file called "searchConfig.json" should have been created in your current working directory. Edit that file to control the behavior of future searches. Note that the game will not launch. All output will be printed to the program's stdout.
16 |
17 | ## Settings
18 |
19 | These are the descriptions of the settings in searchConfig.json. Edit them as you like to control the outcome of the seed search.
20 |
21 | Some settings take lists of relics, cards, or events. For these settings, either use the ID (found in the game's code and output of Seed Search) or the name in the game's currently selected language. Seed Search will warn you if an invalid name or ID is provided. For example, the following two settings for requiredEvents are both valid:
22 |
23 | ` "requiredEvents": ["FaceTrader", "Beggar"],`
24 | ` "requiredEvents": ["Face Trader", "Old Beggar"],`
25 |
26 | ### Core search parameters
27 |
28 | * **ascensionLevel**: The ascension level used for the search (0 to 20)
29 | * **playerClass**: The class used to search (IRONCLAD, THE_SILENT, DEFECT, or WATCHER)
30 | * **startSeed**: The first seed to search
31 | * **endSeed**: The last seed to search
32 | * **verbose**: Whether to print out detailed information about each seed found
33 | * **exitAfterSearch** Set to true to cause the program to immediately exit after search every seed
34 | * **highestFloor** How many floors into the seed you want to search
35 | * **ironcladUnlocks** How many unlocks are available for the Ironclad (0 to 5)
36 | * **silentUnlocks** How many unlocks are available for the Silent (0 to 5)
37 | * **defectUnlocks** How many unlocks are available for the Defect (0 to 5)
38 | * **watcherUnlocks** How many unlocks are available for the Watcher (0 to 5)
39 | * **firstBoss** How many act 1 bosses have been seen (0 to 3)
40 | * **secondBoss** How many act 2 bosses have been seen (0 to 3)
41 | * **thirdBoss** How many act 3 bosses have been seen (0 to 3)
42 |
43 | ### Navigation
44 |
45 | These room weights are used to determine which path is taken through each map. The path with the lowest weight, obtained by adding the weights for each individual node on the path, is selected.
46 |
47 | * **eliteRoomWeight**
48 | * **monsterRoomWeight**
49 | * **restRoomWeight**
50 | * **shopRoomWeight**
51 | * **eventRoomWeight**
52 | * **wingBootsThreshold**: One or more charges of Wing Boots are used if they reduce the weight of the path by at least this much.
53 |
54 | ### General decisions
55 |
56 | * **relicsToBuy**: These are the only relics which will be bought, in order of priority.
57 | * **potionsToBuy**: These are the only potions which will be bought, in order of priority.
58 | * **cardsToBuy**: These are the only cards which will be bought, in order of priority.
59 | * **bossRelicsToTake**: These are the only boss relics which will be taken, in order of priority. All others will be skipped.
60 | * **neowChoice**: Which Neow option to choose (0 to 3). 0 is the first option and 3 is the last (boss relic trade).
61 | * **forceNeowLament**: Limits the Neow options to max HP and Neow's Lament.
62 | * **useShovel**: Whether to dig at rest sites whenever available.
63 | * **speedrunPace**: If set to true, then the secret portal event will not spawn.
64 | * **act4**: If set to true, then the runs will include Act 4. Note that there is no check to ensure that Act 4 can be unlocked with the selected path.
65 | * **alwaysSpawnBottledTornado**: If set to true, then the player is assumed to always have a power in their deck to make Bottled Tornado spawn.
66 | * **alwaysSpawnBottledLightning**: Same as above, but for non-basic skills
67 | * **alwaysSpawnBottledFlame**: Same as above, but for non-basic attacks
68 |
69 | ### Event decisions
70 |
71 | All of these control which actions are taken at various events in the game.
72 |
73 | * **takeSerpentGold**: Whether to take the gold for the curse in the Sssserpent event.
74 | * **takeWarpedTongs**: Whether to take the Warped Tongs in the Accursed Blacksmith event.
75 | * **takeBigFishRelic**: Whether to take the relic for the curse in the Big Fish event.
76 | * **takeDeadAdventurerFight**: If set to true, always start a combat in the Dead Adventurer event if possible. Otherwise, always skip the event.
77 | * **takeMausoleumRelic**: Whether to take the relic for the (poosible) curse in the Mausoleum event.
78 | * **takeScrapOozeRelic**: Whether to dig for the relic in the Scrp Ooze event.
79 | * **takeAddictRelic**: Whether to buy the relic in the Addict event.
80 | * **takeMysteriousSphereFight**: Whether to take the fight in the Mysterious Sphere event.
81 | * **takeRedMaskAct3**: Whether to trade all of your gold for the Red Mask in Act 3 if available.
82 | * **takeMushroomFight**: Whether to fight the mushrooms in the Mushroom event.
83 | * **takeMaskedBanditFight**: Whether to fight the Masked Bandits (and keep all your gold) in Act 2.
84 | * **takeGoldenIdolWithoutCurse**: Whether to take the Golden Idol in the Golden Idol event (without gaining a curse)
85 | * **takeGoldenIdolWithCurse**: Whether to take the Golden Idol in the Golden Idol event, gaining a curse
86 | * **tradeGoldenIdolForBloody**: Whether to trade the Golden Idol for Bloody Idol in the Forgotten Altar event.
87 | * **takeCursedTome**: Whether to take a book in the Cursed Tome event.
88 | * **tradeFaces**: Whether to trade faces in the Face Trader event.
89 | * **takeMindBloomGold**: Whether to take the gold in the Mind Bloom event, if available (these 3 options are in order of priority)
90 | * **takeMindBloomFight**: Whether to take the fight in the Mind Bloom event, if the gold was not taken
91 | * **takeMindBloomUpgrade**: Whether to take the upgrades in the Mind Bloom event, if the other options were not yet taken
92 | * **tradeGoldenIdolForMoney**: Whether to trade the Golden Idol for gold in the Moai Head event, if available
93 | * **takePortal**: Whether to take the portal straight to the boss in the Secret Portal event (not implemented yet)
94 | * **numSensoryStoneCards**: How many cards to take in the Sensory Stone event (1 to 3)
95 | * **takeWindingHallsCurse**: Whether to take the curse in Winding Halls
96 | * **takeWindingHallsMadness**: Whether to take the Madness cards in Winding Halls
97 | * **takeColosseumFight**: Whether to take the Slaver + Nob fight in the Colosseum
98 | * **takeDrugDealerRelic**: Whether to take the Mutagenic Strength relic from the Drug Dealer event
99 | * **takeDrugDealerTransform**: Whether to transform 2 cards in the Drug Dealer event
100 | * **takeLibraryCard**: Whether to take a card from the Library event
101 | * **takeWeMeetAgainRelic**: Whether to trade something in for a relic in the We Meet Again event.
102 |
103 | ### Result filters
104 |
105 | These options control the criteria for deciding which seeds are selected as valid results.
106 |
107 | * **requiredAct1Cards**: The cards which must be present somewhere in Act 1
108 | * **bannedAct1Cards**: The cards which must not be present somewhere in Act 1
109 | * **requiredAct1Relics**: The relics which must be acquired somewhere in Act 1
110 | * **requiredAct1Potions**: The potions which must be acquired somewhere in Act 1
111 | * **requiredRelics**: The relics which must be acquired anywhere in the run
112 | * **requiredPotions**: The potions which must be acquired anywhere in the run
113 | * **requiredEvents**: The events which must be encountered somewhere in the run
114 | * **requiredCombats**: The combats which must be encountered somewhere in the run (e.g. "The Champ")
115 | * **minimumElites**: The minimum number of elites which must be encountered
116 | * **maximumElites**: The maximum number of elites which may be encountered
117 | * **minimumCombats**: The minimum number of combats (event combats, normal combats, elites, and bosses)
118 | * **maximumCombats**: The maximum number of combats
119 | * **minimumRestSites**: The minimum number of rest sites which must be encountered
120 |
121 | ### Output filters
122 |
123 | These options control the information which is shown to the user when the program is executed.
124 |
125 | * **showNeowOptions**: Shows the neow options that are available
126 | * **showCombats**: Shows the monsters and elite monsters that are fought in order
127 | * **showBosses**: Shows the bosses that are fought
128 | * **showRelics**: Shows the relics obtained
129 | * **showShopRelics**: Shows the relics available in the shops
130 | * **showShopCards**: Shows the cards available in the shops
131 | * **showShopPotions**: Shows the potions available in the shops
132 | * **showBossRelics**: Shows the potential boss relics
133 | * **showEvents**: Shows the names of the events
134 | * **showCardChoices**: Shows the cards that the player can choose from rewards
135 | * **showPotions**: Shows the potions obtained from combats and events
136 | * **showOtherCards**: Shows cards that the player obtains from events and relics
137 | * **showRawRelicPools**: Shows the complete list of all relics in the seed in the order they're obtained
138 |
139 | ## Caveats
140 |
141 | When searching through seeds, many assumptions must be made about your choices and game state. When you run a seed yourself, you may notice diverging behavior, especially later on in the run. This is expected. It can be caused by a large number of factors, including spending extra gold and being low on HP. Currently, many of those assumptions can be controlled through the config file.
142 |
143 | ## Current Major Restrictions
144 |
145 | - Default seed filtering is very limited. To do something complicated, you must program it yourself.
146 | - There is no checking to make sure that you can actually make it to Act 4.
147 |
148 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 4.0.0
5 |
6 | seedsearch
7 | SeedSearch
8 | 1.4.1
9 | jar
10 | SeedSearch
11 | Finds seeds with desired properties
12 |
13 |
14 | 1.8
15 | 1.8
16 | 10-04-2022
17 | 3.30.0
18 |
19 |
20 |
21 |
22 | com.megacrit.cardcrawl
23 | slaythespire
24 | ${SlayTheSpire.version}
25 | system
26 | ${basedir}/../lib/desktop-1.0.jar
27 |
28 |
29 | com.evacipated.cardcrawl
30 | ModTheSpire
31 | ${ModTheSpire.version}
32 | system
33 | ${basedir}/../lib/ModTheSpire.jar
34 |
35 |
36 | org.mockito
37 | mockito-core
38 | 2.28.2
39 |
40 |
41 |
42 |
43 | SeedSearch
44 |
45 |
46 | org.apache.maven.plugins
47 | maven-shade-plugin
48 | 2.4.2
49 |
50 |
51 | SeedSearch
52 | package
53 |
54 | shade
55 |
56 |
57 |
58 |
59 | seedsearch:SeedSearch
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | org.apache.maven.plugins
69 | maven-antrun-plugin
70 | 1.8
71 |
72 |
73 | package
74 |
75 |
76 |
77 |
78 |
79 |
80 | run
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | src/main/resources
90 |
91 |
92 | src/main/resources
93 | false
94 |
95 | ModTheSpire.json
96 |
97 |
98 |
99 | src/main/resources
100 | true
101 |
102 | ModTheSpire.json
103 |
104 |
105 |
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/IdChecker.java:
--------------------------------------------------------------------------------
1 | package seedsearch;
2 |
3 | import com.megacrit.cardcrawl.characters.AbstractPlayer;
4 | import com.megacrit.cardcrawl.events.beyond.*;
5 | import com.megacrit.cardcrawl.events.city.*;
6 | import com.megacrit.cardcrawl.events.exordium.*;
7 | import com.megacrit.cardcrawl.events.shrines.*;
8 | import com.megacrit.cardcrawl.helpers.*;
9 | import com.megacrit.cardcrawl.localization.PotionStrings;
10 | import com.megacrit.cardcrawl.relics.AbstractRelic;
11 | import seedsearch.patches.MonsterHelperPatch;
12 |
13 | import java.util.ArrayList;
14 | import java.util.HashMap;
15 | import java.util.Set;
16 |
17 | public class IdChecker {
18 |
19 | private static String[] eventIds = {
20 | AccursedBlacksmith.ID,
21 | Bonfire.ID,
22 | FountainOfCurseRemoval.ID,
23 | Designer.ID,
24 | Duplicator.ID,
25 | Lab.ID,
26 | GremlinMatchGame.ID,
27 | GoldShrine.ID,
28 | PurificationShrine.ID,
29 | Transmogrifier.ID,
30 | GremlinWheelGame.ID,
31 | UpgradeShrine.ID,
32 | FaceTrader.ID,
33 | NoteForYourself.ID,
34 | WeMeetAgain.ID,
35 | WomanInBlue.ID,
36 | BigFish.ID,
37 | Cleric.ID,
38 | DeadAdventurer.ID,
39 | GoldenWing.ID,
40 | GoldenIdolEvent.ID,
41 | GoopPuddle.ID,
42 | ForgottenAltar.ID,
43 | ScrapOoze.ID,
44 | Sssserpent.ID,
45 | LivingWall.ID,
46 | Mushrooms.ID,
47 | Nloth.ID,
48 | ShiningLight.ID,
49 | Vampires.ID,
50 | Ghosts.ID,
51 | Addict.ID,
52 | BackToBasics.ID,
53 | Beggar.ID,
54 | CursedTome.ID,
55 | DrugDealer.ID,
56 | KnowingSkull.ID,
57 | MaskedBandits.ID,
58 | Nest.ID,
59 | TheLibrary.ID,
60 | TheMausoleum.ID,
61 | TheJoust.ID,
62 | Colosseum.ID,
63 | MysteriousSphere.ID,
64 | SecretPortal.ID,
65 | TombRedMask.ID,
66 | Falling.ID,
67 | WindingHalls.ID,
68 | MoaiHead.ID,
69 | SensoryStone.ID,
70 | MindBloom.ID
71 | };
72 |
73 | private static HashMap eventMap;
74 | private static HashMap cardMap;
75 | private static HashMap potionMap;
76 | private static HashMap relicMap;
77 | private static HashMap encounterMap;
78 |
79 | static {
80 | eventMap = new HashMap<>();
81 | for (String id : eventIds) {
82 | eventMap.put(EventHelper.getEventName(id), id);
83 | }
84 |
85 | Set cardIdSet = CardLibrary.cards.keySet();
86 | cardMap = new HashMap<>();
87 | for (String id : cardIdSet) {
88 | cardMap.put(CardLibrary.cards.get(id).name, id);
89 | }
90 |
91 | potionMap = new HashMap<>();
92 | for (String id : PotionHelper.getPotions(AbstractPlayer.PlayerClass.IRONCLAD, true))
93 | {
94 | potionMap.put(PotionHelper.getPotion(id).name, id);
95 | }
96 |
97 | relicMap = new HashMap<>();
98 | ArrayList> relicLists = new ArrayList<>();
99 | relicLists.add(RelicLibrary.starterList);
100 | relicLists.add(RelicLibrary.commonList);
101 | relicLists.add(RelicLibrary.uncommonList);
102 | relicLists.add(RelicLibrary.rareList);
103 | relicLists.add(RelicLibrary.bossList);
104 | relicLists.add(RelicLibrary.specialList);
105 | relicLists.add(RelicLibrary.shopList);
106 | for (ArrayList relicList : relicLists) {
107 | for (AbstractRelic relic : relicList) {
108 | relicMap.put(relic.name, relic.relicId);
109 | }
110 | }
111 |
112 | encounterMap = new HashMap<>();
113 | MonsterHelper.uploadEnemyData();
114 | for (String encounter : MonsterHelperPatch.ids) {
115 | String encounterName = MonsterHelper.getEncounterName(encounter);
116 | if (!encounterName.equals("")) {
117 | encounterMap.put(encounterName, encounter);
118 | } else {
119 | encounterMap.put(encounter, encounter);
120 | }
121 | }
122 | }
123 |
124 | private static ArrayList findBadIds(HashMap map, ArrayList ids) {
125 | ArrayList mistakes = new ArrayList<>();
126 | for (int i = 0; i < ids.size(); i++) {
127 | String id = ids.get(i);
128 | if (!map.containsValue(id)) {
129 | if (!map.containsKey(id)) {
130 | mistakes.add(id);
131 | } else {
132 | ids.set(i, map.get(id));
133 | }
134 | }
135 | }
136 | return mistakes;
137 | }
138 |
139 | public static ArrayList findBadEventIds(ArrayList ids) {
140 | return findBadIds(eventMap, ids);
141 | }
142 |
143 | public static ArrayList findBadCardIds(ArrayList ids) {
144 | return findBadIds(cardMap, ids);
145 | }
146 |
147 | public static ArrayList findBadPotionIds(ArrayList ids) {
148 | return findBadIds(potionMap, ids);
149 | }
150 |
151 | public static ArrayList findBadRelicIds(ArrayList ids) {
152 | return findBadIds(relicMap, ids);
153 | }
154 |
155 | public static ArrayList findBadEncounterIds(ArrayList ids) {
156 | return findBadIds(encounterMap, ids);
157 | }
158 |
159 | }
160 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/Reward.java:
--------------------------------------------------------------------------------
1 | package seedsearch;
2 |
3 | import com.megacrit.cardcrawl.cards.AbstractCard;
4 | import com.megacrit.cardcrawl.potions.AbstractPotion;
5 |
6 | import java.util.ArrayList;
7 |
8 | public class Reward {
9 |
10 | public int floor;
11 | public ArrayList cards;
12 | public ArrayList relics;
13 | public ArrayList potions;
14 |
15 | public Reward(int floor, ArrayList cards, ArrayList relics, ArrayList potions) {
16 | this.floor = floor;
17 | this.cards = cards;
18 | this.relics = relics;
19 | this.potions = potions;
20 | }
21 |
22 | public Reward(int floor) {
23 | this(floor, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
24 | }
25 |
26 | public static Reward makeCardReward(int floor, ArrayList cards) {
27 | return new Reward(floor, cards, new ArrayList<>(), new ArrayList<>());
28 | }
29 |
30 | public static Reward makeRelicReward(int floor, ArrayList relics) {
31 | return new Reward(floor, new ArrayList<>(), relics, new ArrayList<>());
32 | }
33 |
34 | public static Reward makePotionReward(int floor, ArrayList potions) {
35 | return new Reward(floor, new ArrayList<>(), new ArrayList<>(), potions);
36 | }
37 |
38 | public void addCard(AbstractCard card) {
39 | this.cards.add(card);
40 | }
41 |
42 | public void addCards(ArrayList cards) {
43 | this.cards.addAll(cards);
44 | }
45 |
46 | public void addRelic(String relic) {
47 | this.relics.add(relic);
48 | }
49 |
50 | public void addRelics(ArrayList relics) {
51 | this.relics.addAll(relics);
52 | }
53 |
54 | public void addPotion(AbstractPotion potion) {
55 | this.potions.add(potion);
56 | }
57 |
58 | public void addPotions(ArrayList potions) {
59 | this.potions.addAll(potions);
60 | }
61 |
62 | public boolean isEmpty() {
63 | return cards.isEmpty() && relics.isEmpty() && potions.isEmpty();
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/SearchSettings.java:
--------------------------------------------------------------------------------
1 | package seedsearch;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.GsonBuilder;
5 | import com.megacrit.cardcrawl.cards.red.BodySlam;
6 | import com.megacrit.cardcrawl.characters.AbstractPlayer;
7 | import com.megacrit.cardcrawl.relics.Calipers;
8 | import com.megacrit.cardcrawl.relics.JuzuBracelet;
9 |
10 | import java.io.File;
11 | import java.io.FileReader;
12 | import java.io.FileWriter;
13 | import java.io.IOException;
14 | import java.util.ArrayList;
15 |
16 | public class SearchSettings {
17 |
18 | private static final String configName = "searchConfig.json";
19 |
20 | // Core search parameters
21 |
22 | public int ascensionLevel = 0;
23 | public AbstractPlayer.PlayerClass playerClass = AbstractPlayer.PlayerClass.IRONCLAD;
24 | public long startSeed = 0L;
25 | public long endSeed = 100L;
26 | public boolean verbose = true;
27 | public boolean exitAfterSearch = false;
28 | public int highestFloor = 55;
29 | public int ironcladUnlocks = 5;
30 | public int silentUnlocks = 5;
31 | public int defectUnlocks = 5;
32 | public int watcherUnlocks = 5;
33 | public int firstBoss = 3;
34 | public int secondBoss = 3;
35 | public int thirdBoss = 3;
36 |
37 |
38 | // Navigation
39 |
40 | public float eliteRoomWeight = 1.2f;
41 | public float monsterRoomWeight = 1f;
42 | public float restRoomWeight = 0f;
43 | public float shopRoomWeight = 0.9f;
44 | public float eventRoomWeight = 0.9f;
45 | public float wingBootsThreshold = 1f; // Wing boots charges are used if weight is changed by this amount
46 |
47 | // General decisions
48 |
49 | public ArrayList relicsToBuy = new ArrayList<>(); // Use the ID for cards and relics
50 | public ArrayList potionsToBuy = new ArrayList<>();
51 | public ArrayList cardsToBuy = new ArrayList<>();
52 | public ArrayList bossRelicsToTake = new ArrayList<>(); // Give them in priority order to always take a relic
53 | public boolean forceNeowLament = true; // Overrides Neow option below
54 | public int neowChoice = 3; // 3 is the boss relic trade
55 | public boolean useShovel = false;
56 | public boolean speedrunPace = true; // Do you reach Act 3 fast enough to skip Secret Portal?
57 | public boolean act4 = false;
58 | public boolean alwaysSpawnBottledTornado = true; // Assume you always have a power for Bottled Tornado to spawn
59 | public boolean alwaysSpawnBottledLightning = true; // Assume you always have a non-basic skill for Bottled Lightning to spawn
60 | public boolean alwaysSpawnBottledFlame = true; // Assume you always have a non-basic attack for Bottled Flame to spawn
61 | public boolean ignorePandoraCards = false; // Don't add the cards from Pandora's Box (as if you glitch it)
62 |
63 | // Event decisions
64 |
65 | public boolean takeSerpentGold = false;
66 | public boolean takeWarpedTongs = false;
67 | public boolean takeBigFishRelic = false;
68 | public boolean takeDeadAdventurerFight = false;
69 | public boolean takeMausoleumRelic = false;
70 | public boolean takeScrapOozeRelic = true;
71 | public boolean takeAddictRelic = true; // Always assume you pay, no taking Shame
72 | public boolean takeMysteriousSphereFight = false;
73 | public boolean takeRedMaskAct3 = true;
74 | public boolean takeMushroomFight = true;
75 | public boolean takeMaskedBanditFight = true;
76 | public boolean takeGoldenIdolWithoutCurse = true;
77 | public boolean takeGoldenIdolWithCurse = false;
78 | public boolean tradeGoldenIdolForBloody = true;
79 | public boolean takeCursedTome = true;
80 | public boolean tradeFaces = false;
81 | public boolean takeMindBloomGold = false; // Mind Bloom choices in order of priority
82 | public boolean takeMindBloomFight = true;
83 | public boolean takeMindBloomUpgrade = false;
84 | public boolean tradeGoldenIdolForMoney = true; // Moai Head event
85 | public boolean takePortal = false;
86 | public int numSensoryStoneCards = 1; // Keep it between 1 and 3, please!
87 | public boolean takeWindingHallsCurse = false;
88 | public boolean takeWindingHallsMadness = false;
89 | public boolean takeColosseumFight = false;
90 | public boolean takeDrugDealerRelic = false;
91 | public boolean takeDrugDealerTransform = true;
92 | public boolean takeLibraryCard = false;
93 | public boolean takeWeMeetAgainRelic = true;
94 |
95 | // Result filters
96 |
97 | public ArrayList requiredAct1Cards = new ArrayList<>();
98 | public ArrayList bannedAct1Cards = new ArrayList<>();
99 | public ArrayList requiredAct1Relics = new ArrayList<>();
100 | public ArrayList requiredAct1Potions = new ArrayList<>();
101 | public ArrayList requiredRelics = new ArrayList<>();
102 | public ArrayList requiredPotions = new ArrayList<>();
103 | public ArrayList requiredEvents = new ArrayList<>();
104 | public ArrayList requiredCombats = new ArrayList<>();
105 | public int minimumElites = 0;
106 | public int maximumElites = 1;
107 | public int minimumCombats = 0;
108 | public int maximumCombats = 33;
109 | public int minimumRestSites = 0;
110 |
111 | // Output filters
112 |
113 | public boolean showNeowOptions = true;
114 | public boolean showCombats = true;
115 | public boolean showBosses = true;
116 | public boolean showBossRelics = true;
117 | public boolean showRelics = true;
118 | public boolean showShopRelics = true;
119 | public boolean showShopCards = true;
120 | public boolean showShopPotions = true;
121 | public boolean showEvents = true;
122 | public boolean showCardChoices = true;
123 | public boolean showPotions = true;
124 | public boolean showOtherCards = true;
125 | public boolean showRawRelicPools = false;
126 |
127 | public SearchSettings() {
128 | }
129 |
130 | private void setDefaults() {
131 | relicsToBuy.add(JuzuBracelet.ID);
132 | requiredRelics.add(Calipers.ID);
133 | requiredAct1Cards.add(BodySlam.ID);
134 | }
135 |
136 | public static SearchSettings loadSettings() {
137 | try {
138 | File file = new File(configName);
139 | if (file.exists()) {
140 | Gson gson = new Gson();
141 | SearchSettings settings = gson.fromJson(new FileReader(file), SearchSettings.class);
142 | return settings;
143 | } else {
144 | SearchSettings settings = new SearchSettings();
145 | settings.setDefaults();
146 | settings.saveSettings();
147 | return settings;
148 | }
149 | } catch (IOException e) {
150 | e.printStackTrace();
151 | System.out.println(String.format("Could not load search settings: %s", e.getMessage()));
152 | SearchSettings settings = new SearchSettings();
153 | settings.setDefaults();
154 | return settings;
155 | }
156 | }
157 |
158 | private void saveSettings() {
159 | try {
160 | Gson gson = new GsonBuilder().setPrettyPrinting().create();
161 | FileWriter writer = new FileWriter(configName);
162 | gson.toJson(this, writer);
163 | writer.flush();
164 | writer.close();
165 | } catch (IOException e) {
166 | e.printStackTrace();
167 | throw new RuntimeException("Could not serialize the search settings.");
168 | }
169 | }
170 |
171 | public boolean checkIds() {
172 | ArrayList> relicLists = new ArrayList<>();
173 | relicLists.add(relicsToBuy);
174 | relicLists.add(bossRelicsToTake);
175 | relicLists.add(requiredAct1Relics);
176 | relicLists.add(requiredRelics);
177 |
178 | ArrayList> cardLists = new ArrayList<>();
179 | cardLists.add(cardsToBuy);
180 | cardLists.add(requiredAct1Cards);
181 | cardLists.add(bannedAct1Cards);
182 |
183 | ArrayList> potionLists = new ArrayList<>();
184 | potionLists.add(potionsToBuy);
185 | potionLists.add(requiredAct1Potions);
186 | potionLists.add(requiredPotions);
187 |
188 | ArrayList> eventLists = new ArrayList<>();
189 | eventLists.add(requiredEvents);
190 |
191 | ArrayList> encounterLists = new ArrayList<>();
192 | encounterLists.add(requiredCombats);
193 |
194 | boolean mistakesMade = false;
195 |
196 | for (ArrayList relicList : relicLists) {
197 | ArrayList mistakes = IdChecker.findBadRelicIds(relicList);
198 | if (mistakes.size() > 0) {
199 | System.out.println(String.format("WARNING: Bad relic ids/names found: %s", mistakes));
200 | mistakesMade = true;
201 | }
202 | }
203 |
204 | for (ArrayList cardList : cardLists) {
205 | ArrayList mistakes = IdChecker.findBadCardIds(cardList);
206 | if (mistakes.size() > 0) {
207 | System.out.println(String.format("WARNING: Bad card ids/names found: %s", mistakes));
208 | mistakesMade = true;
209 | }
210 | }
211 |
212 | for (ArrayList potionList : potionLists) {
213 | ArrayList mistakes = IdChecker.findBadPotionIds(potionList);
214 | if (mistakes.size() > 0) {
215 | System.out.println(String.format("WARNING: Bad potion ids/names found: %s", mistakes));
216 | mistakesMade = true;
217 | }
218 | }
219 |
220 | for (ArrayList eventList : eventLists) {
221 | ArrayList mistakes = IdChecker.findBadEventIds(eventList);
222 | if (mistakes.size() > 0) {
223 | System.out.println(String.format("WARNING: Bad event ids/names found: %s", mistakes));
224 | mistakesMade = true;
225 | }
226 | }
227 |
228 | for (ArrayList encounterList : encounterLists) {
229 | ArrayList mistakes = IdChecker.findBadEncounterIds(encounterList);
230 | if (mistakes.size() > 0) {
231 | System.out.println(String.format("WARNING: Bad encounter ids/names found: %s", mistakes));
232 | mistakesMade = true;
233 | }
234 | }
235 |
236 | return mistakesMade;
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/SeedResult.java:
--------------------------------------------------------------------------------
1 | package seedsearch;
2 |
3 | import com.megacrit.cardcrawl.cards.AbstractCard;
4 | import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
5 | import com.megacrit.cardcrawl.helpers.SeedHelper;
6 | import com.megacrit.cardcrawl.neow.NeowReward;
7 | import com.megacrit.cardcrawl.potions.AbstractPotion;
8 | import com.megacrit.cardcrawl.relics.AbstractRelic;
9 |
10 | import java.text.MessageFormat;
11 | import java.util.ArrayList;
12 | import java.util.Collections;
13 |
14 | public class SeedResult {
15 |
16 | private ArrayList miscRewards;
17 | private ArrayList shopRewards;
18 | private ArrayList cardRewards;
19 | private ArrayList neowRewards;
20 | private ArrayList events;
21 | private ArrayList bosses;
22 | private ArrayList monsters;
23 | private ArrayList mapPath;
24 | private ArrayList trueMapPath;
25 | private ArrayList bossRelics;
26 | private ArrayList relics;
27 | private ArrayList rawCommonRelics;
28 | private ArrayList rawUncommonRelics;
29 | private ArrayList rawRareRelics;
30 | private ArrayList rawBossRelics;
31 | private ArrayList rawShopRelics;
32 | private int numElites;
33 | private int numCombats;
34 | private int numRestSites;
35 | private long seed;
36 |
37 | public SeedResult(long seed) {
38 | this.seed = seed;
39 | this.miscRewards = new ArrayList<>();
40 | this.shopRewards = new ArrayList<>();
41 | this.cardRewards = new ArrayList<>();
42 | this.neowRewards = new ArrayList<>();
43 | this.events = new ArrayList<>();
44 | this.bosses = new ArrayList<>();
45 | this.monsters = new ArrayList<>();
46 | this.mapPath = new ArrayList<>();
47 | this.trueMapPath = new ArrayList<>();
48 | this.bossRelics = new ArrayList<>();
49 | this.relics = new ArrayList<>();
50 | }
51 |
52 | public void addCardReward(int floor, ArrayList cards) {
53 | Reward reward = Reward.makeCardReward(floor, cards);
54 | cardRewards.add(reward);
55 | }
56 |
57 | public void addCardReward(Reward reward) {
58 | cardRewards.add(reward);
59 | }
60 |
61 | public void addAllCardRewards(ArrayList rewards) {
62 | cardRewards.addAll(rewards);
63 | }
64 |
65 | public void addMiscReward(Reward reward) {
66 | miscRewards.add(reward);
67 | }
68 |
69 | public void addShopReward(Reward reward) {
70 | shopRewards.add(reward);
71 | }
72 |
73 | public void addNeowRewards(ArrayList neowRewards) {
74 | this.neowRewards = neowRewards;
75 | }
76 |
77 | public void registerCombat(String monsterName) {
78 | numCombats += 1;
79 | monsters.add(monsterName);
80 | }
81 |
82 | public void registerEliteCombat(String monsterName) {
83 | numElites += 1;
84 | registerCombat(monsterName);
85 | }
86 |
87 | public void registerBossCombat(String monsterName) {
88 | bosses.add(monsterName);
89 | registerCombat(monsterName);
90 | }
91 |
92 | public void registerEvent(String eventName) {
93 | events.add(eventName);
94 | }
95 |
96 | public void countRestSite() {
97 | numRestSites++;
98 | }
99 |
100 | public void addBossReward(ArrayList bossRelics) {
101 | this.bossRelics.addAll(bossRelics);
102 | }
103 |
104 | public void addToMapPath(String mapSymbol) {
105 | mapPath.add(mapSymbol);
106 | }
107 |
108 | public void addToTrueMapPath(String mapSymbol) {
109 | trueMapPath.add(mapSymbol);
110 | }
111 |
112 | public void updateRelics() {
113 | relics = new ArrayList<>();
114 | for (AbstractRelic relic : AbstractDungeon.player.relics) {
115 | relics.add(relic.relicId);
116 | }
117 | }
118 |
119 | public void SetCommonRelicPool(ArrayList relics){
120 | rawCommonRelics = new ArrayList<>(relics);
121 | }
122 |
123 | public void SetUncommonRelicPool(ArrayList relics){
124 | rawUncommonRelics = new ArrayList<>(relics);
125 | }
126 |
127 | public void SetRareRelicPool(ArrayList relics){
128 | rawRareRelics = new ArrayList<>(relics);
129 | }
130 |
131 | public void SetBossRelicPool(ArrayList relics){
132 | rawBossRelics = new ArrayList<>(relics);
133 | }
134 |
135 | public void SetShopRelicPool(ArrayList relics){
136 | rawShopRelics = new ArrayList<>(relics);
137 | }
138 |
139 | public boolean testFinalFilters(SearchSettings settings) {
140 | if (numCombats > settings.maximumCombats) {
141 | return false;
142 | }
143 | if (numCombats < settings.minimumCombats) {
144 | return false;
145 | }
146 | if (numElites > settings.maximumElites) {
147 | return false;
148 | }
149 | if (numElites < settings.minimumElites) {
150 | return false;
151 | }
152 | if (numRestSites < settings.minimumRestSites) {
153 | return false;
154 | }
155 | if (!events.containsAll(settings.requiredEvents)) {
156 | return false;
157 | }
158 | if (!relics.containsAll(settings.requiredRelics)) {
159 | return false;
160 | }
161 | if (!monsters.containsAll(settings.requiredCombats)) {
162 | return false;
163 | }
164 | ArrayList allPotions = getAllPotionIds();
165 | for (String potion : settings.requiredPotions) {
166 | if (allPotions.contains(potion)){
167 | allPotions.remove(potion);
168 | } else{
169 | return false;
170 | }
171 | }
172 | return true;
173 | }
174 |
175 | public boolean testAct1Filters(SearchSettings settings) {
176 | if (!relics.containsAll(settings.requiredAct1Relics)) {
177 | return false;
178 | }
179 | ArrayList allCards = getAllCardIds();
180 | for (String card : settings.bannedAct1Cards) {
181 | if (allCards.contains(card)) {
182 | return false;
183 | }
184 | }
185 | for (String card : settings.requiredAct1Cards) {
186 | if (allCards.contains(card)) {
187 | allCards.remove(card);
188 | } else {
189 | return false;
190 | }
191 | }
192 | ArrayList allPotions = getAllPotionIds();
193 | for (String potion : settings.requiredAct1Potions) {
194 | if (allPotions.contains(potion)){
195 | allPotions.remove(potion);
196 | } else{
197 | return false;
198 | }
199 | }
200 | return true;
201 | }
202 |
203 | private ArrayList getAllCardIds() {
204 | ArrayList allCards = new ArrayList<>();
205 | for (Reward reward : cardRewards) {
206 | for (AbstractCard card : reward.cards) {
207 | allCards.add(card.cardID);
208 | }
209 | }
210 | for (Reward reward : miscRewards) {
211 | for (AbstractCard card : reward.cards) {
212 | allCards.add(card.cardID);
213 | }
214 | }
215 | return allCards;
216 | }
217 |
218 | private ArrayList getAllPotionIds() {
219 | ArrayList allPotions = new ArrayList<>();
220 | for (Reward reward : miscRewards) {
221 | for (AbstractPotion potion : reward.potions){
222 | allPotions.add(potion.ID);
223 | }
224 | }
225 | return allPotions;
226 | }
227 |
228 | private static String removeTextFormatting(String text) {
229 | text = text.replaceAll("~|@(\\S+)~|@", "$1");
230 | return text.replaceAll("#.|NL", "");
231 | }
232 |
233 | public void printSeedStats(SearchSettings settings) {
234 | ArrayList shopRelics = new ArrayList<>();
235 | ArrayList shopCards = new ArrayList<>();
236 | ArrayList shopPotions = new ArrayList<>();
237 | for (Reward shopReward : shopRewards) {
238 | shopRelics.addAll(shopReward.relics);
239 | for (AbstractCard card : shopReward.cards) {
240 | shopCards.add(card.name);
241 | }
242 | for (AbstractPotion potion : shopReward.potions)
243 | {
244 | shopPotions.add(potion.name);
245 | }
246 | }
247 |
248 | System.out.println(MessageFormat.format("Seed: {0} ({1})", SeedHelper.getString(seed), seed));
249 | if (settings.showNeowOptions) {
250 | System.out.println("Neow Options:");
251 | for (NeowReward reward : neowRewards) {
252 | System.out.println(removeTextFormatting(reward.optionLabel));
253 | }
254 | }
255 | if (settings.showCombats) {
256 | System.out.println(MessageFormat.format("{0} combats ({1} elite(s)):", numCombats, numElites));
257 | System.out.println(monsters);
258 | }
259 | if (settings.showBosses) {
260 | System.out.println("Bosses:");
261 | System.out.println(bosses);
262 | }
263 | if (settings.showBossRelics) {
264 | System.out.println("Boss relics:");
265 | System.out.println(bossRelics);
266 | }
267 | if (settings.showRelics) {
268 | System.out.println(MessageFormat.format("{0} relics:", relics.size()));
269 | System.out.println(relics);
270 | }
271 | if (settings.showShopRelics) {
272 | System.out.println("Shop relics:");
273 | System.out.println(shopRelics);
274 | }
275 | if (settings.showShopCards) {
276 | System.out.println("Shop cards:");
277 | System.out.println(shopCards);
278 | }
279 | if (settings.showShopPotions) {
280 | System.out.println("Shop potions:");
281 | System.out.println(shopPotions);
282 | }
283 | if (settings.showEvents) {
284 | System.out.println("Events:");
285 | System.out.println(events);
286 | }
287 | System.out.println("Map path:");
288 | System.out.println(mapPath);
289 | System.out.println("True map path:");
290 | ArrayList combinedMapPath = new ArrayList<>();
291 | for (int i = 0; i < mapPath.size(); i++) {
292 | String mapPathItem = mapPath.get(i);
293 | String trueItem = trueMapPath.get(i);
294 | if (mapPathItem.equals(trueItem)) {
295 | combinedMapPath.add(trueItem);
296 | } else {
297 | combinedMapPath.add(String.format("%s/%s", mapPathItem, trueItem));
298 | }
299 | }
300 | System.out.println(combinedMapPath);
301 | if (settings.showCardChoices) {
302 | System.out.println("Card choices:");
303 | for (Reward reward : cardRewards) {
304 | if (reward.cards.size() > 0) {
305 | System.out.println(String.format("Floor %d: %s", reward.floor, reward.cards));
306 | }
307 | }
308 | }
309 | if (settings.showPotions) {
310 | System.out.println("Potions:");
311 | for (Reward reward : miscRewards) {
312 | if (reward.potions.size() > 0) {
313 | ArrayList potionNames = new ArrayList<>();
314 | for (AbstractPotion potion : reward.potions) {
315 | potionNames.add(potion.name);
316 | }
317 | System.out.println(String.format("Floor %d: %s", reward.floor, potionNames));
318 | }
319 | }
320 | }
321 | if (settings.showOtherCards) {
322 | System.out.println("Other cards:");
323 | for (Reward reward : miscRewards) {
324 | if (reward.cards.size() > 0) {
325 | System.out.println(String.format("Floor %d: %s", reward.floor, reward.cards));
326 | }
327 | }
328 | }
329 | if (settings.showRawRelicPools) {
330 | System.out.println("Raw common relic list:");
331 | System.out.println(rawCommonRelics);
332 | System.out.println("Raw uncommon relic list:");
333 | System.out.println(rawUncommonRelics);
334 | System.out.println("Raw rare relic list:");
335 | System.out.println(rawRareRelics);
336 | System.out.println("Raw boss relic list:");
337 | System.out.println(rawBossRelics);
338 | System.out.println("Raw shop relic list:");
339 | System.out.println(rawShopRelics);
340 | }
341 | System.out.println("#####################################");
342 | }
343 | }
344 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/SeedRunner.java:
--------------------------------------------------------------------------------
1 | package seedsearch;
2 |
3 | import com.megacrit.cardcrawl.cards.AbstractCard;
4 | import com.megacrit.cardcrawl.cards.CardGroup;
5 | import com.megacrit.cardcrawl.cards.colorless.JAX;
6 | import com.megacrit.cardcrawl.cards.colorless.Madness;
7 | import com.megacrit.cardcrawl.cards.curses.*;
8 | import com.megacrit.cardcrawl.cards.red.Strike_Red;
9 | import com.megacrit.cardcrawl.characters.AbstractPlayer;
10 | import com.megacrit.cardcrawl.characters.CharacterManager;
11 | import com.megacrit.cardcrawl.core.CardCrawlGame;
12 | import com.megacrit.cardcrawl.core.Settings;
13 | import com.megacrit.cardcrawl.dungeons.*;
14 | import com.megacrit.cardcrawl.events.AbstractEvent;
15 | import com.megacrit.cardcrawl.events.beyond.*;
16 | import com.megacrit.cardcrawl.events.city.*;
17 | import com.megacrit.cardcrawl.events.exordium.*;
18 | import com.megacrit.cardcrawl.events.shrines.*;
19 | import com.megacrit.cardcrawl.helpers.EventHelper;
20 | import com.megacrit.cardcrawl.helpers.PotionHelper;
21 | import com.megacrit.cardcrawl.helpers.RelicLibrary;
22 | import com.megacrit.cardcrawl.map.MapEdge;
23 | import com.megacrit.cardcrawl.map.MapRoomNode;
24 | import com.megacrit.cardcrawl.neow.NeowEvent;
25 | import com.megacrit.cardcrawl.neow.NeowReward;
26 | import com.megacrit.cardcrawl.potions.AbstractPotion;
27 | import com.megacrit.cardcrawl.random.Random;
28 | import com.megacrit.cardcrawl.relics.*;
29 | import com.megacrit.cardcrawl.rewards.chests.AbstractChest;
30 | import com.megacrit.cardcrawl.rewards.chests.BossChest;
31 | import com.megacrit.cardcrawl.rooms.*;
32 | import com.megacrit.cardcrawl.screens.CharSelectInfo;
33 | import com.megacrit.cardcrawl.shop.Merchant;
34 | import com.megacrit.cardcrawl.shop.ShopScreen;
35 | import com.megacrit.cardcrawl.shop.StorePotion;
36 | import com.megacrit.cardcrawl.shop.StoreRelic;
37 | import seedsearch.patches.AbstractRoomPatch;
38 | import seedsearch.patches.CardRewardScreenPatch;
39 | import seedsearch.patches.EventHelperPatch;
40 | import seedsearch.patches.ShowCardAndObtainEffectPatch;
41 |
42 | import java.lang.reflect.Field;
43 | import java.lang.reflect.InvocationTargetException;
44 | import java.lang.reflect.Method;
45 | import java.util.ArrayList;
46 | import java.util.Collections;
47 |
48 | import static com.megacrit.cardcrawl.helpers.MonsterHelper.*;
49 |
50 |
51 | public class SeedRunner {
52 |
53 | public static ArrayList combatRelics = new ArrayList<>();
54 | public static ArrayList combatCardRewards = new ArrayList<>();
55 | public static ArrayList combatPotions = new ArrayList<>();
56 | public static int combatGold = 0;
57 |
58 | private AbstractPlayer player;
59 | private int currentAct;
60 | private int actFloor;
61 | private int bootsCharges = 0;
62 |
63 | private SeedResult seedResult;
64 |
65 | private SearchSettings settings;
66 | private long currentSeed;
67 |
68 | public SeedRunner(SearchSettings settings) {
69 | this.settings = settings;
70 | AbstractDungeon.fadeColor = Settings.SHADOW_COLOR;
71 | CharacterManager characterManager = new CharacterManager();
72 | CardCrawlGame.characterManager = characterManager;
73 | characterManager.setChosenCharacter(settings.playerClass);
74 | currentSeed = settings.startSeed;
75 | AbstractDungeon.ascensionLevel = settings.ascensionLevel;
76 | Settings.seedSet = true;
77 | this.settings.checkIds();
78 | }
79 |
80 | private void setSeed(long seed) {
81 | Settings.seed = seed;
82 | currentSeed = seed;
83 | AbstractDungeon.generateSeeds();
84 | player = AbstractDungeon.player;
85 | AbstractDungeon.reset();
86 | resetCharacter();
87 | clearCombatRewards();
88 |
89 | currentAct = 0;
90 | actFloor = 0;
91 | this.bootsCharges = 0;
92 | seedResult = new SeedResult(currentSeed);
93 | }
94 |
95 | private void resetCharacter() {
96 | player.relics = new ArrayList<>();
97 | try {
98 | Method starterRelicsMethod = AbstractPlayer.class.getDeclaredMethod("initializeStarterRelics", AbstractPlayer.PlayerClass.class);
99 | starterRelicsMethod.setAccessible(true);
100 | starterRelicsMethod.invoke(player, settings.playerClass);
101 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
102 | e.printStackTrace();
103 | throw new RuntimeException("Reflection error when initializing player relics");
104 | }
105 | player.potions = new ArrayList<>();
106 | player.masterDeck = new CardGroup(CardGroup.CardGroupType.MASTER_DECK);
107 | CharSelectInfo info = player.getLoadout();
108 | player.maxHealth = info.maxHp;
109 | player.gold = info.gold;
110 |
111 | // Remove the MockMusic tracks that would otherwise pile up
112 | CardCrawlGame.music.dispose();
113 | CardCrawlGame.music.update();
114 | }
115 |
116 | private boolean runSeed() {
117 |
118 | AbstractDungeon exordium = new Exordium(player, new ArrayList<>());
119 | ArrayList exordiumPath = findMapPath(AbstractDungeon.map);
120 |
121 | if (!settings.speedrunPace) {
122 | CardCrawlGame.playtime = 900F;
123 | } else {
124 | CardCrawlGame.playtime = 0F;
125 | }
126 |
127 | if (settings.showRawRelicPools)
128 | {
129 | seedResult.SetCommonRelicPool(AbstractDungeon.commonRelicPool);
130 | seedResult.SetUncommonRelicPool(AbstractDungeon.uncommonRelicPool);
131 | seedResult.SetRareRelicPool(AbstractDungeon.rareRelicPool);
132 | seedResult.SetBossRelicPool(AbstractDungeon.bossRelicPool);
133 | seedResult.SetShopRelicPool(AbstractDungeon.shopRelicPool);
134 | }
135 |
136 | ArrayList neowRewards = getNeowRewards();
137 | seedResult.addNeowRewards(neowRewards);
138 | if (settings.neowChoice < 0 || settings.neowChoice > 3) {
139 | throw new RuntimeException("The 'neowChoice' setting must be between 0 and 3.");
140 | }
141 | NeowReward neowReward;
142 | if (settings.forceNeowLament) {
143 | neowReward = new NeowReward(true);
144 | } else {
145 | neowReward = neowRewards.get(settings.neowChoice);
146 | }
147 | claimNeowReward(neowReward);
148 |
149 | runPath(exordiumPath);
150 | getBossRewards();
151 |
152 | seedResult.updateRelics();
153 | if (!seedResult.testAct1Filters(settings)) {
154 | return false;
155 | }
156 |
157 | currentAct += 1;
158 | AbstractDungeon city = new TheCity(player, AbstractDungeon.specialOneTimeEventList);
159 | ArrayList cityPath = findMapPath(AbstractDungeon.map);
160 | runPath(cityPath);
161 | getBossRewards();
162 |
163 | currentAct += 1;
164 | AbstractDungeon beyond = new TheBeyond(player, AbstractDungeon.specialOneTimeEventList);
165 | ArrayList beyondPath = findMapPath(AbstractDungeon.map);
166 | runPath(beyondPath);
167 | getBossRewards();
168 |
169 | if (settings.act4) {
170 | currentAct += 1;
171 | AbstractDungeon end = new TheEnding(player, AbstractDungeon.specialOneTimeEventList);
172 | AbstractDungeon.floorNum += 1;
173 | ArrayList endPath = new ArrayList<>();
174 | endPath.add(AbstractDungeon.map.get(0).get(3));
175 | endPath.add(AbstractDungeon.map.get(1).get(3));
176 | endPath.add(AbstractDungeon.map.get(2).get(3));
177 | runPath(endPath);
178 | getBossRewards();
179 | }
180 |
181 | seedResult.updateRelics();
182 | return seedResult.testFinalFilters(settings);
183 | }
184 |
185 | public boolean runSeed(long seed) {
186 | setSeed(seed);
187 | return runSeed();
188 | }
189 |
190 | public static void clearCombatRewards() {
191 | combatGold = 0;
192 | combatRelics.clear();
193 | combatCardRewards.clear();
194 | combatPotions.clear();
195 | }
196 |
197 | private ArrayList getNeowRewards() {
198 | NeowEvent.rng = new Random(Settings.seed);
199 | ArrayList rewards = new ArrayList<>();
200 | rewards.add(new NeowReward(0));
201 | rewards.add(new NeowReward(1));
202 | rewards.add(new NeowReward(2));
203 | rewards.add(new NeowReward(3));
204 | return rewards;
205 | }
206 |
207 | private void claimNeowReward(NeowReward neowOption) {
208 | Reward reward = new Reward(0);
209 | AbstractDungeon.getCurrMapNode().room = new EmptyRoom();
210 | AbstractRoomPatch.obtainedRelic = null;
211 | CardRewardScreenPatch.rewardCards = null;
212 | ShowCardAndObtainEffectPatch.resetCards();
213 | neowOption.activate();
214 | if (AbstractRoomPatch.obtainedRelic != null) {
215 | awardRelic(AbstractRoomPatch.obtainedRelic, reward);
216 | }
217 | if (CardRewardScreenPatch.rewardCards != null) {
218 | seedResult.addCardReward(0, CardRewardScreenPatch.rewardCards);
219 | }
220 | if (ShowCardAndObtainEffectPatch.obtainedCards.size() > 0) {
221 | for (AbstractCard card : ShowCardAndObtainEffectPatch.obtainedCards) {
222 | addInvoluntaryCardReward(card, reward);
223 | }
224 | }
225 | if (combatPotions.size() > 0) {
226 | reward.addPotions(combatPotions);
227 | }
228 | if (neowOption.type == NeowReward.NeowRewardType.TRANSFORM_CARD) {
229 | // We're assuming we remove the second card in the deck here to avoid Ascender's Bane
230 | AbstractCard removedCard = player.masterDeck.group.get(1);
231 | AbstractDungeon.transformCard(removedCard, false, NeowEvent.rng);
232 | player.masterDeck.removeCard(removedCard);
233 | addInvoluntaryCardReward(AbstractDungeon.getTransformedCard(), reward);
234 | }
235 | if (neowOption.type == NeowReward.NeowRewardType.TRANSFORM_TWO_CARDS) {
236 | AbstractCard removedCard = player.masterDeck.group.get(1);
237 | AbstractDungeon.transformCard(removedCard, false, NeowEvent.rng);
238 | player.masterDeck.removeCard(removedCard);
239 | addInvoluntaryCardReward(AbstractDungeon.getTransformedCard(), reward);
240 | removedCard = player.masterDeck.group.get(1);
241 | AbstractDungeon.transformCard(removedCard, false, NeowEvent.rng);
242 | player.masterDeck.removeCard(removedCard);
243 | addInvoluntaryCardReward(AbstractDungeon.getTransformedCard(), reward);
244 | }
245 | if (neowOption.drawback == NeowReward.NeowRewardDrawback.CURSE) {
246 | addInvoluntaryCardReward(AbstractDungeon.getCardWithoutRng(AbstractCard.CardRarity.CURSE), reward);
247 | }
248 | seedResult.addMiscReward(reward);
249 | }
250 |
251 | private void awardRelic(String relic, Reward reward) {
252 | reward.addRelic(relic);
253 | AbstractRelic realRelic = RelicLibrary.getRelic(relic);
254 | doRelicPickupLogic(realRelic, reward);
255 | }
256 |
257 | private void awardRelic(AbstractRelic relic, Reward reward) {
258 | String relicKey = relic.relicId;
259 | reward.addRelic(relicKey);
260 | doRelicPickupLogic(relic, reward);
261 | }
262 |
263 | private void doRelicPickupLogic(AbstractRelic relic, Reward reward) {
264 | this.player.relics.add(relic);
265 | String relicKey = relic.relicId;
266 | switch (relicKey) {
267 | case TinyHouse.ID:
268 | seedResult.addCardReward(reward.floor, AbstractDungeon.getRewardCards());
269 | Reward tinyHouseReward = new Reward(AbstractDungeon.floorNum);
270 | AbstractDungeon.miscRng.random(); // Random call for gold generation
271 | AbstractPotion tinyHousePotion = PotionHelper.getRandomPotion(AbstractDungeon.miscRng);
272 | tinyHouseReward.addPotion(tinyHousePotion);
273 | seedResult.addMiscReward(tinyHouseReward);
274 | addGoldReward(50);
275 | break;
276 | case WingBoots.ID:
277 | this.bootsCharges = 3;
278 | break;
279 | case CallingBell.ID:
280 | addInvoluntaryCardReward(new CurseOfTheBell(), reward);
281 | String relic1 = AbstractDungeon.returnRandomRelicKey(AbstractRelic.RelicTier.COMMON);
282 | String relic2 = AbstractDungeon.returnRandomRelicKey(AbstractRelic.RelicTier.UNCOMMON);
283 | String relic3 = AbstractDungeon.returnRandomRelicKey(AbstractRelic.RelicTier.RARE);
284 | awardRelic(relic1, reward);
285 | awardRelic(relic2, reward);
286 | awardRelic(relic3, reward);
287 | break;
288 | case PandorasBox.ID:
289 | int count = 0;
290 | ArrayList strikesAndDefends = new ArrayList<>();
291 | for (AbstractCard card : player.masterDeck.group) {
292 | if ((card.cardID.equals("Strike_R")) || (card.cardID.equals("Strike_G")) ||
293 | (card.cardID.equals("Strike_B")) || (card.cardID.equals("Strike_P")) ||
294 | (card.cardID.equals("Defend_R")) || (card.cardID.equals("Defend_G")) ||
295 | (card.cardID.equals("Defend_B")) || (card.cardID.equals("Defend_P"))) {
296 | count += 1;
297 | strikesAndDefends.add(card);
298 | }
299 | }
300 | for (AbstractCard card : strikesAndDefends) {
301 | player.masterDeck.group.remove(card);
302 | }
303 | for (int i = 0; i < count; i++) {
304 | AbstractCard newCard = AbstractDungeon.returnTrulyRandomCard().makeCopy();
305 | if (!settings.ignorePandoraCards) {
306 | addInvoluntaryCardReward(newCard, reward);
307 | }
308 | }
309 | break;
310 | case Astrolabe.ID:
311 | AbstractCard removedCard = player.masterDeck.group.get(1);
312 | AbstractDungeon.transformCard(removedCard, true, AbstractDungeon.miscRng);
313 | player.masterDeck.removeCard(removedCard);
314 | addInvoluntaryCardReward(AbstractDungeon.getTransformedCard(), reward);
315 | removedCard = player.masterDeck.group.get(1);
316 | AbstractDungeon.transformCard(removedCard, true, AbstractDungeon.miscRng);
317 | player.masterDeck.removeCard(removedCard);
318 | addInvoluntaryCardReward(AbstractDungeon.getTransformedCard(), reward);
319 | removedCard = player.masterDeck.group.get(1);
320 | AbstractDungeon.transformCard(removedCard, true, AbstractDungeon.miscRng);
321 | player.masterDeck.removeCard(removedCard);
322 | addInvoluntaryCardReward(AbstractDungeon.getTransformedCard(), reward);
323 | break;
324 | case Necronomicon.ID:
325 | AbstractCard curse = new Necronomicurse();
326 | addInvoluntaryCardReward(curse, reward);
327 | break;
328 | }
329 | }
330 |
331 | private ArrayList findBootsPath(ArrayList> map) {
332 | // I apologize for this monstrosity
333 |
334 | float[][] weights = new float[15][7];
335 | float[][][] pathWeights = new float[15][7][4];
336 | ArrayList>> parents = new ArrayList<>();
337 | for (int i = 0; i < 15; i++) {
338 | for (int j = 0; j < 7; j++) {
339 | weights[i][j] = getRoomScore(map.get(i).get(j).room);
340 | for (int k = 0; k < 4; k++) {
341 | if (i == 0) {
342 | pathWeights[i][j][k] = weights[i][j];
343 | } else {
344 | pathWeights[i][j][k] = 100000f;
345 | }
346 | }
347 | }
348 | }
349 | for (int floor = 0; floor < 14; floor++) {
350 | ArrayList> floorParents = new ArrayList<>(4);
351 | for (int i = 0; i < 4; i++) {
352 | ArrayList floorSubList = new ArrayList<>(7);
353 | for (int j = 0; j < 7; j++) {
354 | floorSubList.add(null);
355 | }
356 | floorParents.add(floorSubList);
357 | }
358 | for (int x = 0; x < 7; x++) {
359 | MapRoomNode node = map.get(floor).get(x);
360 | if (node.room == null) {
361 | continue;
362 | }
363 | ArrayList edges = node.getEdges();
364 | ArrayList nextXs = new ArrayList<>(0);
365 | for (MapEdge edge : edges) {
366 | int targetX = edge.dstX;
367 | nextXs.add(targetX);
368 | }
369 | for (int nx = 0; nx < 7; nx++) {
370 | for (int wing_uses = 0; wing_uses <= bootsCharges; wing_uses++) {
371 | if (nextXs.contains(nx)) {
372 | float testWeight = weights[floor + 1][nx] + pathWeights[floor][x][wing_uses];
373 | if (testWeight < pathWeights[floor + 1][nx][wing_uses]) {
374 | pathWeights[floor + 1][nx][wing_uses] = testWeight;
375 | floorParents.get(wing_uses).set(nx, node);
376 | }
377 | } else if (wing_uses > 0) {
378 | float testWeight = weights[floor + 1][nx] + pathWeights[floor][x][wing_uses - 1];
379 | if (testWeight < pathWeights[floor + 1][nx][wing_uses]) {
380 | pathWeights[floor + 1][nx][wing_uses] = testWeight;
381 | floorParents.get(wing_uses).set(nx, node);
382 | }
383 | }
384 | }
385 | }
386 | }
387 | parents.add(floorParents);
388 | }
389 | int[] best_top = {0, 0, 0, 0};
390 | float[] best_score = {100000f, 100000f, 100000f, 100000f};
391 | for (int uses = 0; uses < 4; uses++) {
392 | for (int x = 0; x < 7; x++) {
393 | if (pathWeights[14][x][uses] < best_score[uses]) {
394 | best_score[uses] = pathWeights[14][x][uses];
395 | best_top[uses] = x;
396 | }
397 | }
398 | }
399 | int best_uses = 0;
400 | if (best_score[0] - best_score[1] >= settings.wingBootsThreshold) {
401 | best_uses = 1;
402 | }
403 | if (best_score[1] - best_score[2] >= settings.wingBootsThreshold) {
404 | best_uses = 2;
405 | }
406 | if (best_score[2] - best_score[3] >= settings.wingBootsThreshold) {
407 | best_uses = 3;
408 | }
409 | int cur_uses = best_uses;
410 | ArrayList path = new ArrayList<>(15);
411 | int next_x = best_top[cur_uses];
412 | path.add(map.get(14).get(best_top[cur_uses]));
413 | for (int y = 14; y > 0; y--) {
414 | MapRoomNode parent = parents.get(y - 1).get(cur_uses).get(next_x);
415 | boolean isConnected = false;
416 | for (MapEdge edge : parent.getEdges()) {
417 | if (edge.dstX == next_x) {
418 | isConnected = true;
419 | break;
420 | }
421 | }
422 | if (!isConnected) {
423 | cur_uses -= 1;
424 | }
425 | path.add(0, parent);
426 | next_x = parent.x;
427 | }
428 | if (best_uses != 0) {
429 | this.bootsCharges -= best_uses;
430 | }
431 | return path;
432 | }
433 |
434 | private ArrayList findMapPath(ArrayList> map) {
435 | if (bootsCharges > 0) {
436 | return findBootsPath(map);
437 | }
438 | float[][] weights = new float[15][7];
439 | float[][] pathWeights = new float[15][7];
440 | ArrayList> parents = new ArrayList<>();
441 | for (int i = 0; i < 15; i++) {
442 | for (int j = 0; j < 7; j++) {
443 | weights[i][j] = getRoomScore(map.get(i).get(j).room);
444 | if (i == 0) {
445 | pathWeights[i][j] = weights[i][j];
446 | } else {
447 | pathWeights[i][j] = 100000f;
448 | }
449 | }
450 | }
451 | for (int floor = 0; floor < 14; floor++) {
452 | ArrayList floorParents = new ArrayList<>(7);
453 | for (int i = 0; i < 7; i++) {
454 | floorParents.add(null);
455 | }
456 | for (int x = 0; x < 7; x++) {
457 | MapRoomNode node = map.get(floor).get(x);
458 | if (node.room == null) {
459 | continue;
460 | }
461 | ArrayList edges = node.getEdges();
462 | for (MapEdge edge : edges) {
463 | int targetX = edge.dstX;
464 | float testWeight = weights[floor + 1][targetX] + pathWeights[floor][x];
465 | if (testWeight < pathWeights[floor + 1][targetX]) {
466 | pathWeights[floor + 1][targetX] = testWeight;
467 | floorParents.set(targetX, node);
468 | }
469 | }
470 | }
471 | parents.add(floorParents);
472 | }
473 | int best_top = 0;
474 | float best_score = 100000f;
475 | for (int x = 0; x < 7; x++) {
476 | if (pathWeights[14][x] < best_score) {
477 | best_score = pathWeights[14][x];
478 | best_top = x;
479 | }
480 | }
481 | ArrayList path = new ArrayList(15);
482 | int next_x = best_top;
483 | path.add(map.get(14).get(best_top));
484 | for (int y = 14; y > 0; y--) {
485 | MapRoomNode parent = parents.get(y - 1).get(next_x);
486 | path.add(0, parent);
487 | next_x = parent.x;
488 | }
489 | return path;
490 | }
491 |
492 | private float getRoomScore(AbstractRoom room) {
493 | if (room instanceof TreasureRoom) {
494 | return 0f;
495 | } else if (room instanceof MonsterRoomElite) {
496 | return settings.eliteRoomWeight;
497 | } else if (room instanceof MonsterRoom) {
498 | return settings.monsterRoomWeight;
499 | } else if (room instanceof RestRoom) {
500 | return settings.restRoomWeight;
501 | } else if (room instanceof ShopRoom) {
502 | return settings.shopRoomWeight;
503 | } else if (room instanceof EventRoom) {
504 | return settings.eventRoomWeight;
505 | } else {
506 | return 0f;
507 | }
508 | }
509 |
510 | public enum RoomType {
511 | EVENT, ELITE, MONSTER, SHOP, TREASURE, REST
512 | }
513 |
514 | private void runPath(ArrayList path) {
515 | for (actFloor = 0; actFloor < path.size(); actFloor++) {
516 | AbstractDungeon.floorNum += 1;
517 | if (AbstractDungeon.floorNum > settings.highestFloor) {
518 | return;
519 | }
520 | AbstractDungeon.miscRng = new Random(currentSeed + (long) AbstractDungeon.floorNum);
521 | MapRoomNode node = path.get(actFloor);
522 | RoomType result;
523 | if (node.room instanceof EventRoom) {
524 | result = RoomType.EVENT;
525 | seedResult.addToMapPath("?");
526 | } else if (node.room instanceof MonsterRoomElite) {
527 | result = RoomType.ELITE;
528 | seedResult.addToMapPath("E");
529 | } else if (node.room instanceof MonsterRoom) {
530 | result = RoomType.MONSTER;
531 | seedResult.addToMapPath("M");
532 | } else if (node.room instanceof ShopRoom) {
533 | result = RoomType.SHOP;
534 | seedResult.addToMapPath("S");
535 | } else if (node.room instanceof TreasureRoom) {
536 | result = RoomType.TREASURE;
537 | seedResult.addToMapPath("T");
538 | } else {
539 | result = RoomType.REST;
540 | seedResult.addToMapPath("R");
541 | }
542 | if (result == RoomType.EVENT) {
543 | EventHelper.RoomResult eventRoll = EventHelper.roll();
544 | switch (eventRoll) {
545 | case ELITE:
546 | result = RoomType.ELITE;
547 | node.room = new MonsterRoomElite();
548 | break;
549 | case MONSTER:
550 | result = RoomType.MONSTER;
551 | node.room = new MonsterRoom();
552 | break;
553 | case SHOP:
554 | result = RoomType.SHOP;
555 | node.room = new ShopRoom();
556 | break;
557 | case TREASURE:
558 | result = RoomType.TREASURE;
559 | node.room = new TreasureRoom();
560 | break;
561 | }
562 | }
563 | AbstractDungeon.currMapNode = node;
564 | if (AbstractDungeon.getCurrRoom().phase == AbstractRoom.RoomPhase.COMBAT) {
565 | AbstractDungeon.getCurrRoom().phase = AbstractRoom.RoomPhase.COMPLETE;
566 | }
567 | clearCombatRewards();
568 | switch (result) {
569 | case EVENT:
570 | seedResult.addToTrueMapPath("?");
571 | Random eventRngDuplicate = new Random(Settings.seed, AbstractDungeon.eventRng.counter);
572 | AbstractEvent event = AbstractDungeon.generateEvent(eventRngDuplicate);
573 | String eventKey = EventHelperPatch.eventName;
574 | Reward eventReward = getEventReward(event, eventKey, AbstractDungeon.floorNum);
575 | seedResult.registerEvent(eventKey);
576 | if (!eventReward.isEmpty()) {
577 | seedResult.addMiscReward(eventReward);
578 | }
579 | break;
580 | case MONSTER:
581 | seedResult.addToTrueMapPath("M");
582 | String monster = AbstractDungeon.monsterList.remove(0);
583 | seedResult.registerCombat(monster);
584 | seedResult.addCardReward(AbstractDungeon.floorNum, AbstractDungeon.getRewardCards());
585 | if (player.hasRelic(PrayerWheel.ID)) {
586 | seedResult.addCardReward(AbstractDungeon.floorNum, AbstractDungeon.getRewardCards());
587 | }
588 | int gold = AbstractDungeon.treasureRng.random(10, 20);
589 | addGoldReward(gold);
590 | AbstractPotion monsterPotion = getPotionReward();
591 | if (monsterPotion != null) {
592 | Reward monsterPotionReward = new Reward(AbstractDungeon.floorNum);
593 | monsterPotionReward.addPotion(monsterPotion);
594 | seedResult.addMiscReward(monsterPotionReward);
595 | }
596 | break;
597 | case ELITE:
598 | seedResult.addToTrueMapPath("E");
599 | String elite = AbstractDungeon.eliteMonsterList.remove(0);
600 | seedResult.registerEliteCombat(elite);
601 | seedResult.addCardReward(AbstractDungeon.floorNum, AbstractDungeon.getRewardCards());
602 | AbstractRelic.RelicTier tier = AbstractDungeon.returnRandomRelicTier();
603 | String relic = AbstractDungeon.returnRandomRelicKey(tier);
604 | Reward relicReward = new Reward(AbstractDungeon.floorNum);
605 | awardRelic(relic, relicReward);
606 | if (player.hasRelic(BlackStar.ID)) {
607 | AbstractRelic starRelic = AbstractDungeon.returnRandomNonCampfireRelic(tier);
608 | awardRelic(starRelic, relicReward);
609 | }
610 | seedResult.addMiscReward(relicReward);
611 | gold = AbstractDungeon.treasureRng.random(25, 35);
612 | addGoldReward(gold);
613 | AbstractPotion elitePotion = getPotionReward();
614 | if (elitePotion != null) {
615 | Reward monsterPotionReward = new Reward(AbstractDungeon.floorNum);
616 | monsterPotionReward.addPotion(elitePotion);
617 | seedResult.addMiscReward(monsterPotionReward);
618 | }
619 | break;
620 | case SHOP:
621 | seedResult.addToTrueMapPath("S");
622 | Merchant merchant = new Merchant();
623 | Reward shopReward = getShopReward(AbstractDungeon.floorNum);
624 | seedResult.addShopReward(shopReward);
625 | break;
626 | case TREASURE:
627 | seedResult.addToTrueMapPath("T");
628 | AbstractChest chest = AbstractDungeon.getRandomChest();
629 | ShowCardAndObtainEffectPatch.resetCards();
630 | chest.open(false);
631 | addGoldReward(combatGold);
632 | Reward treasureRelicReward = new Reward(AbstractDungeon.floorNum);
633 | for (AbstractRelic treasureRelic : combatRelics) {
634 | awardRelic(treasureRelic, treasureRelicReward);
635 | }
636 | for (AbstractCard card : ShowCardAndObtainEffectPatch.obtainedCards) {
637 | addInvoluntaryCardReward(card, treasureRelicReward);
638 | }
639 | seedResult.addAllCardRewards(combatCardRewards);
640 | seedResult.addMiscReward(treasureRelicReward);
641 | break;
642 | case REST:
643 | seedResult.addToTrueMapPath("R");
644 | seedResult.countRestSite();
645 | if (settings.useShovel && player.hasRelic(Shovel.ID)) {
646 | Reward digReward = new Reward(AbstractDungeon.floorNum);
647 | AbstractRelic.RelicTier digTier = AbstractDungeon.returnRandomRelicTier();
648 | awardRelic(AbstractDungeon.returnRandomRelic(digTier), digReward);
649 | }
650 | break;
651 | }
652 | }
653 | seedResult.addToMapPath("BOSS");
654 | seedResult.addToTrueMapPath("BOSS");
655 | }
656 |
657 |
658 | @SuppressWarnings("unchecked")
659 | private Reward getShopReward(int floor) {
660 | Reward shopReward = new Reward(floor);
661 | ShopScreen screen = AbstractDungeon.shopScreen;
662 | try {
663 | Field coloredCardsField = ShopScreen.class.getDeclaredField("coloredCards");
664 | Field colorlessCardsField = ShopScreen.class.getDeclaredField("colorlessCards");
665 | Field relicsField = ShopScreen.class.getDeclaredField("relics");
666 | Field potionsField = ShopScreen.class.getDeclaredField("potions");
667 | coloredCardsField.setAccessible(true);
668 | colorlessCardsField.setAccessible(true);
669 | relicsField.setAccessible(true);
670 | potionsField.setAccessible(true);
671 | ArrayList coloredCards = (ArrayList) coloredCardsField.get(screen);
672 | ArrayList colorlessCards = (ArrayList) colorlessCardsField.get(screen);
673 | ArrayList relics = (ArrayList) relicsField.get(screen);
674 | ArrayList potions = (ArrayList) potionsField.get(screen);
675 | for (AbstractCard card : coloredCards) {
676 | shopReward.addCard(card);
677 | }
678 | for (AbstractCard card : colorlessCards) {
679 | shopReward.addCard(card);
680 | }
681 | for (StoreRelic relic : relics) {
682 | if (settings.relicsToBuy.contains(relic.relic.relicId) && relic.price <= player.gold) {
683 | awardRelic(relic.relic, shopReward);
684 | addGoldReward(-relic.price);
685 | } else {
686 | shopReward.addRelic(relic.relic.relicId);
687 | }
688 | }
689 | for (AbstractCard card : shopReward.cards) {
690 | if (settings.cardsToBuy.contains(card.cardID) && card.price <= player.gold) {
691 | player.masterDeck.addToBottom(card);
692 | Reward shopCardReward = new Reward(AbstractDungeon.floorNum);
693 | shopCardReward.addCard(card);
694 | seedResult.addMiscReward(shopCardReward);
695 | addGoldReward(-card.price);
696 | }
697 | }
698 | for (StorePotion potion : potions) {
699 | if (settings.potionsToBuy.contains(potion.potion.ID) && potion.price <= player.gold){
700 | Reward shopPotionReward = new Reward(AbstractDungeon.floorNum);
701 | shopPotionReward.addPotion(potion.potion);
702 | seedResult.addMiscReward(shopPotionReward);
703 | addGoldReward(-potion.price);
704 | }
705 | shopReward.addPotion(potion.potion);
706 | }
707 | } catch (NoSuchFieldException | IllegalAccessException e) {
708 | e.printStackTrace();
709 | }
710 | return shopReward;
711 | }
712 |
713 | private Reward getEventReward(AbstractEvent event, String eventKey, int floor) {
714 | clearCombatRewards();
715 | ShowCardAndObtainEffectPatch.resetCards();
716 | Random miscRng = AbstractDungeon.miscRng;
717 | Reward reward = new Reward(floor);
718 | switch (eventKey) {
719 | case GoopPuddle.ID:
720 | addGoldReward(75);
721 | break;
722 | case Sssserpent.ID:
723 | if (settings.takeSerpentGold) {
724 | int goldgain = AbstractDungeon.ascensionLevel >= 15 ? 150 : 175;
725 | addGoldReward(goldgain);
726 | addInvoluntaryCardReward(new Doubt(), reward);
727 | }
728 | break;
729 | case AccursedBlacksmith.ID:
730 | if (settings.takeWarpedTongs) {
731 | reward.addRelic(WarpedTongs.ID);
732 | addInvoluntaryCardReward(new Pain(), reward);
733 | }
734 | break;
735 | case BigFish.ID:
736 | if (settings.takeBigFishRelic) {
737 | AbstractRelic.RelicTier fishTier = AbstractDungeon.returnRandomRelicTier();
738 | awardRelic(AbstractDungeon.returnRandomScreenlessRelic(fishTier), reward);
739 | addInvoluntaryCardReward(new Regret(), reward);
740 | }
741 | break;
742 | case DeadAdventurer.ID:
743 | if (settings.takeDeadAdventurerFight) {
744 | DeadAdventurer deadAdventurer = (DeadAdventurer) event;
745 |
746 | try {
747 | Method getMonster = DeadAdventurer.class.getDeclaredMethod("getMonster");
748 | getMonster.setAccessible(true);
749 | String monster = (String) getMonster.invoke(deadAdventurer);
750 |
751 | int encounterChance = AbstractDungeon.ascensionLevel >= 15 ? 35 : 25;
752 | for (int i = 1; i <= 3; i++) {
753 | if (miscRng.random(0, 99) < encounterChance) {
754 | addGoldReward(miscRng.random(25, 35));
755 | seedResult.addCardReward(AbstractDungeon.floorNum, AbstractDungeon.getRewardCards());
756 | seedResult.registerCombat(monster);
757 | break;
758 | } else {
759 | encounterChance += 25;
760 | }
761 | }
762 | addGoldReward(30);
763 | AbstractRelic.RelicTier adventurerTier = AbstractDungeon.returnRandomRelicTier();
764 | awardRelic(AbstractDungeon.returnRandomScreenlessRelic(adventurerTier), reward);
765 |
766 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
767 | e.printStackTrace();
768 | }
769 | }
770 | break;
771 | case TheMausoleum.ID:
772 | if (settings.takeMausoleumRelic) {
773 | AbstractRelic.RelicTier mausoleumTier = AbstractDungeon.returnRandomRelicTier();
774 | awardRelic(AbstractDungeon.returnRandomScreenlessRelic(mausoleumTier), reward);
775 | if (miscRng.randomBoolean() || AbstractDungeon.ascensionLevel >= 15) {
776 | addInvoluntaryCardReward(new Writhe(), reward);
777 | }
778 | }
779 | break;
780 | case ScrapOoze.ID:
781 | if (settings.takeScrapOozeRelic) {
782 | AbstractRelic.RelicTier scrapOozeTier = AbstractDungeon.returnRandomRelicTier();
783 | awardRelic(AbstractDungeon.returnRandomScreenlessRelic(scrapOozeTier), reward);
784 | }
785 | break;
786 | case Addict.ID:
787 | if (settings.takeAddictRelic) {
788 | AbstractRelic.RelicTier addictTier = AbstractDungeon.returnRandomRelicTier();
789 | awardRelic(AbstractDungeon.returnRandomScreenlessRelic(addictTier), reward);
790 | }
791 | break;
792 | case MysteriousSphere.ID:
793 | if (settings.takeMysteriousSphereFight) {
794 | addGoldReward(miscRng.random(45, 55));
795 | seedResult.registerCombat("2 Orb Walkers");
796 | AbstractRelic.RelicTier mysteriousSphereTier = AbstractRelic.RelicTier.RARE;
797 | awardRelic(AbstractDungeon.returnRandomScreenlessRelic(mysteriousSphereTier), reward);
798 | seedResult.addCardReward(AbstractDungeon.floorNum, AbstractDungeon.getRewardCards());
799 | }
800 | break;
801 | case TombRedMask.ID:
802 | if (!player.hasRelic(RedMask.ID) && settings.takeRedMaskAct3) {
803 | awardRelic(RedMask.ID, reward);
804 | addGoldReward(-player.gold);
805 | } else {
806 | addGoldReward(222);
807 | }
808 | break;
809 | case Mushrooms.ID:
810 | if (settings.takeMushroomFight) {
811 | seedResult.registerCombat(MUSHROOMS_EVENT_ENC);
812 | addGoldReward(miscRng.random(25, 35));
813 | awardRelic(OddMushroom.ID, reward);
814 | seedResult.addCardReward(AbstractDungeon.floorNum, AbstractDungeon.getRewardCards());
815 | } else {
816 | addInvoluntaryCardReward(new Parasite(), reward);
817 | }
818 | break;
819 | case MaskedBandits.ID:
820 | if (settings.takeMaskedBanditFight) {
821 | seedResult.registerCombat(MASKED_BANDITS_ENC);
822 | addGoldReward(miscRng.random(25, 35));
823 | awardRelic(RedMask.ID, reward);
824 | seedResult.addCardReward(AbstractDungeon.floorNum, AbstractDungeon.getRewardCards());
825 | } else {
826 | addGoldReward(-player.gold);
827 | }
828 | break;
829 | case GoldenIdol.ID:
830 | if (settings.takeGoldenIdolWithCurse) {
831 | awardRelic(GoldenIdol.ID, reward);
832 | addInvoluntaryCardReward(new Injury(), reward);
833 | } else if (settings.takeGoldenIdolWithoutCurse) {
834 | awardRelic(GoldenIdol.ID, reward);
835 | }
836 | break;
837 | case ForgottenAltar.ID:
838 | if (player.hasRelic(GoldenIdol.ID) && settings.tradeGoldenIdolForBloody) {
839 | awardRelic(BloodyIdol.ID, reward);
840 | loseRelic(GoldenIdol.ID);
841 | }
842 | break;
843 | case Bonfire.ID:
844 | if (player.isCursed()) {
845 | awardRelic(SpiritPoop.ID, reward);
846 | }
847 | break;
848 | case CursedTome.ID:
849 | if (settings.takeCursedTome) {
850 | int roll = miscRng.random(2);
851 | switch (roll) {
852 | case 0:
853 | awardRelic(Necronomicon.ID, reward);
854 | break;
855 | case 1:
856 | awardRelic(Enchiridion.ID, reward);
857 | break;
858 | case 2:
859 | awardRelic(NilrysCodex.ID, reward);
860 | break;
861 | }
862 | }
863 | break;
864 | case FaceTrader.ID:
865 | if (settings.tradeFaces) {
866 | try {
867 | Method hotkeyCheck = FaceTrader.class.getDeclaredMethod("getRandomFace");
868 | hotkeyCheck.setAccessible(true);
869 | AbstractRelic mask = (AbstractRelic) hotkeyCheck.invoke(event);
870 | awardRelic(mask, reward);
871 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
872 | e.printStackTrace();
873 | }
874 | }
875 | break;
876 | case MindBloom.ID:
877 | if (settings.takeMindBloomGold && AbstractDungeon.floorNum <= 40) {
878 | addGoldReward(999);
879 | addInvoluntaryCardReward(new Normality(), reward);
880 | addInvoluntaryCardReward(new Normality(), reward);
881 | } else if (settings.takeMindBloomFight) {
882 | addGoldReward(100);
883 | ArrayList encounters = new ArrayList<>();
884 | encounters.add(GUARDIAN_ENC);
885 | encounters.add(HEXAGHOST_ENC);
886 | encounters.add(SLIME_BOSS_ENC);
887 | Collections.shuffle(encounters, new java.util.Random(miscRng.randomLong()));
888 | seedResult.registerCombat(encounters.get(0));
889 | AbstractRelic bloomRelic = AbstractDungeon.returnRandomRelic(AbstractRelic.RelicTier.RARE);
890 | awardRelic(bloomRelic, reward);
891 | seedResult.addCardReward(AbstractDungeon.floorNum, AbstractDungeon.getRewardCards());
892 | } else if (settings.takeMindBloomUpgrade) {
893 | awardRelic(MarkOfTheBloom.ID, reward);
894 | } // Not really worrying about the other case
895 | break;
896 | case SecretPortal.ID:
897 | if (settings.takePortal) {
898 | // TODO: Portal is not currently supported
899 | }
900 | break;
901 | case MoaiHead.ID:
902 | if (settings.tradeGoldenIdolForMoney && player.hasRelic(GoldenIdol.ID)) {
903 | loseRelic(GoldenIdol.ID);
904 | addGoldReward(333);
905 | }
906 | break;
907 | case Colosseum.ID:
908 | seedResult.registerCombat(COLOSSEUM_SLAVER_ENC);
909 | AbstractDungeon.treasureRng.random(10, 20); //Unused rng roll
910 | if (settings.takeColosseumFight) {
911 | seedResult.registerCombat(COLOSSEUM_NOB_ENC);
912 | AbstractRelic rareRelic = AbstractDungeon.returnRandomRelic(AbstractRelic.RelicTier.RARE);
913 | AbstractRelic uncommonRelic = AbstractDungeon.returnRandomRelic(AbstractRelic.RelicTier.UNCOMMON);
914 | awardRelic(rareRelic, reward);
915 | awardRelic(uncommonRelic, reward);
916 | addGoldReward(100);
917 | seedResult.addCardReward(AbstractDungeon.floorNum, AbstractDungeon.getRewardCards());
918 | }
919 | break;
920 | case TheLibrary.ID:
921 | if (settings.takeLibraryCard) {
922 | CardGroup group = new CardGroup(CardGroup.CardGroupType.UNSPECIFIED);
923 | for (int i = 0; i < 20; i++) {
924 | AbstractCard card = AbstractDungeon.getCard(AbstractDungeon.rollRarity()).makeCopy();
925 | if (!group.contains(card)) {
926 | group.addToBottom(card);
927 | } else {
928 | i--;
929 | }
930 | }
931 | reward.addCards(group.group);
932 | }
933 | break;
934 | case DrugDealer.ID:
935 | if (settings.takeDrugDealerRelic) {
936 | awardRelic(MutagenicStrength.ID, reward);
937 | } else if (settings.takeDrugDealerTransform) {
938 | // Assume two strikes? Note that the cards you choose do matter here.
939 | for (int i = 0; i < 2; i++) {
940 | AbstractDungeon.transformCard(new Strike_Red(), false, miscRng);
941 | reward.addCard(AbstractDungeon.getTransformedCard());
942 | }
943 | } else {
944 | reward.addCard(new JAX());
945 | }
946 | break;
947 | case SensoryStone.ID:
948 | for (int i = 0; i < settings.numSensoryStoneCards; i++) {
949 | seedResult.addCardReward(AbstractDungeon.floorNum, AbstractDungeon.getColorlessRewardCards());
950 | }
951 | break;
952 | case WeMeetAgain.ID:
953 | if (settings.takeWeMeetAgainRelic) {
954 | AbstractRelic.RelicTier weMeetAgainTier = AbstractDungeon.returnRandomRelicTier();
955 | awardRelic(AbstractDungeon.returnRandomScreenlessRelic(weMeetAgainTier), reward);
956 | }
957 | break;
958 | case WindingHalls.ID:
959 | if (settings.takeWindingHallsCurse) {
960 | addInvoluntaryCardReward(new Writhe(), reward);
961 | } else if (settings.takeWindingHallsMadness) {
962 | for (int i = 0; i < 2; i++) {
963 | addInvoluntaryCardReward(new Madness(), reward);
964 | }
965 | }
966 | break;
967 | case GremlinMatchGame.ID:
968 | try {
969 | Field eventCards = GremlinMatchGame.class.getDeclaredField("cards");
970 | eventCards.setAccessible(true);
971 | CardGroup gremlinCards = (CardGroup) eventCards.get(event);
972 | ArrayList matchCards = new ArrayList<>();
973 | ArrayList pairs = new ArrayList<>();
974 | for (AbstractCard card : gremlinCards.group) {
975 | if (pairs.contains(card.cardID)) {
976 | pairs.remove(card.cardID);
977 | } else {
978 | pairs.add(card.cardID);
979 | matchCards.add(card);
980 | }
981 | }
982 | reward.addCards(matchCards);
983 | } catch (NoSuchFieldException | IllegalAccessException e) {
984 | e.printStackTrace();
985 | }
986 | break;
987 | case GremlinWheelGame.ID:
988 | try {
989 | Method buttonMethod = GremlinWheelGame.class.getDeclaredMethod("buttonEffect", int.class);
990 | buttonMethod.setAccessible(true);
991 | buttonMethod.invoke(event, 0);
992 |
993 | Method preResultMethod = GremlinWheelGame.class.getDeclaredMethod("preApplyResult");
994 | preResultMethod.setAccessible(true);
995 | preResultMethod.invoke(event);
996 |
997 | Method resultMethod = GremlinWheelGame.class.getDeclaredMethod("applyResult");
998 | resultMethod.setAccessible(true);
999 | resultMethod.invoke(event);
1000 |
1001 | for (AbstractRelic relic : combatRelics) {
1002 | awardRelic(relic, reward);
1003 | }
1004 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
1005 | e.printStackTrace();
1006 | }
1007 | break;
1008 | case Lab.ID:
1009 | ArrayList potions = new ArrayList<>();
1010 | potions.add(PotionHelper.getRandomPotion());
1011 | potions.add(PotionHelper.getRandomPotion());
1012 | if (AbstractDungeon.ascensionLevel < 15) {
1013 | potions.add(PotionHelper.getRandomPotion());
1014 | }
1015 | reward.addPotions(potions);
1016 | break;
1017 | case WomanInBlue.ID:
1018 | ArrayList womanPotions = new ArrayList<>();
1019 | womanPotions.add(PotionHelper.getRandomPotion());
1020 | womanPotions.add(PotionHelper.getRandomPotion());
1021 | womanPotions.add(PotionHelper.getRandomPotion());
1022 | reward.addPotions(womanPotions);
1023 | break;
1024 | }
1025 | if (ShowCardAndObtainEffectPatch.obtainedCards.size() > 0) {
1026 | for (AbstractCard card : ShowCardAndObtainEffectPatch.obtainedCards) {
1027 | addInvoluntaryCardReward(card, reward);
1028 | }
1029 | }
1030 | return reward;
1031 | }
1032 |
1033 | private void getBossRewards() {
1034 | AbstractDungeon.floorNum += 1;
1035 | if (AbstractDungeon.floorNum > settings.highestFloor) {
1036 | return;
1037 | }
1038 | seedResult.registerBossCombat(AbstractDungeon.bossKey);
1039 | AbstractDungeon.currMapNode = new MapRoomNode(-1, 15);
1040 | AbstractDungeon.currMapNode.room = new MonsterRoomBoss();
1041 | AbstractDungeon.currMapNode.room.phase = AbstractRoom.RoomPhase.COMPLETE;
1042 | if (AbstractDungeon.ascensionLevel == 20 && currentAct == 2) {
1043 | seedResult.registerBossCombat(AbstractDungeon.bossList.get(1));
1044 | AbstractDungeon.floorNum += 1;
1045 | if (AbstractDungeon.floorNum > settings.highestFloor) {
1046 | return;
1047 | }
1048 | }
1049 | if (currentAct < 2) {
1050 | AbstractDungeon.miscRng = new Random(currentSeed + (long) AbstractDungeon.floorNum);
1051 |
1052 | Reward cardReward = new Reward(AbstractDungeon.floorNum);
1053 | cardReward.addCards(AbstractDungeon.getRewardCards());
1054 | int gold = 100 + AbstractDungeon.miscRng.random(-5, 5);
1055 | if (AbstractDungeon.ascensionLevel >= 13) {
1056 | gold = (int) (gold * 0.75);
1057 | }
1058 | addGoldReward(gold);
1059 | AbstractPotion potion = getPotionReward();
1060 | if (potion != null) {
1061 | Reward potionReward = new Reward(AbstractDungeon.floorNum);
1062 | potionReward.addPotion(potion);
1063 | seedResult.addMiscReward(potionReward);
1064 | }
1065 |
1066 | AbstractDungeon.currMapNode.room = new TreasureRoomBoss();
1067 | AbstractDungeon.floorNum += 1;
1068 | if (AbstractDungeon.floorNum > settings.highestFloor) {
1069 | return;
1070 | }
1071 | AbstractDungeon.miscRng = new Random(currentSeed + (long) AbstractDungeon.floorNum);
1072 |
1073 | BossChest bossChest = new BossChest();
1074 | ArrayList bossRelicStrings = new ArrayList<>();
1075 | for (AbstractRelic relic : bossChest.relics) {
1076 | bossRelicStrings.add(relic.relicId);
1077 | }
1078 | seedResult.addBossReward(bossRelicStrings);
1079 | Reward bossRelicReward = new Reward(AbstractDungeon.floorNum);
1080 | for (String relic : settings.bossRelicsToTake) {
1081 | if (bossRelicStrings.contains(relic)) {
1082 | doRelicPickupLogic(RelicLibrary.getRelic(relic), bossRelicReward);
1083 | break;
1084 | }
1085 | }
1086 |
1087 | seedResult.addCardReward(cardReward);
1088 | seedResult.addMiscReward(bossRelicReward);
1089 | }
1090 | }
1091 |
1092 | private AbstractPotion getPotionReward() {
1093 | int chance = 40;
1094 | chance = chance + AbstractRoom.blizzardPotionMod;
1095 |
1096 | if (AbstractDungeon.player.hasRelic(WhiteBeast.ID)) {
1097 | chance = 100;
1098 | }
1099 |
1100 | if (AbstractDungeon.potionRng.random(0, 99) >= chance && !Settings.isDebug) {
1101 | AbstractRoom.blizzardPotionMod += 10;
1102 | return null;
1103 | } else {
1104 | AbstractRoom.blizzardPotionMod -= 10;
1105 | return AbstractDungeon.returnRandomPotion();
1106 | }
1107 | }
1108 |
1109 | private void addGoldReward(int amount) {
1110 | if (amount > 0) {
1111 | player.gainGold(amount);
1112 | } else {
1113 | player.loseGold(-amount);
1114 | }
1115 | }
1116 |
1117 | public SeedResult getSeedResult() {
1118 | return seedResult;
1119 | }
1120 |
1121 | private void addInvoluntaryCardReward(AbstractCard card, Reward reward) {
1122 | reward.cards.add(card);
1123 | AbstractDungeon.player.masterDeck.addToTop(card);
1124 | }
1125 |
1126 | private void loseRelic(String relicID) {
1127 | player.loseRelic(relicID);
1128 | }
1129 |
1130 | }
1131 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/SeedSearch.java:
--------------------------------------------------------------------------------
1 | package seedsearch;
2 |
3 | import com.megacrit.cardcrawl.characters.AbstractPlayer;
4 | import com.megacrit.cardcrawl.unlock.UnlockTracker;
5 |
6 | import java.util.ArrayList;
7 |
8 | import static java.lang.System.exit;
9 |
10 | public class SeedSearch {
11 |
12 | public static boolean loadingEnabled = true;
13 | public static SearchSettings settings;
14 |
15 | private static void unlockBosses(String[] bosslist, int unlockLevel) {
16 | for (int i = 0; i < unlockLevel; i++) {
17 | if (i >= 3) {
18 | break;
19 | }
20 | UnlockTracker.unlockPref.putInteger(bosslist[i], 2);
21 | UnlockTracker.bossSeenPref.putInteger(bosslist[i], 1);
22 | }
23 | }
24 |
25 | private static boolean isPlayerClassValid(SearchSettings settings) {
26 | if (settings.playerClass == null) {
27 | System.out.println("Invalid playerClass specified in search settings.");
28 | System.out.println("Possible values: ");
29 | for (AbstractPlayer.PlayerClass c: AbstractPlayer.PlayerClass.values()) {
30 | System.out.println(c.name());
31 | }
32 | return false;
33 | }
34 | return true;
35 | }
36 |
37 | public static void search() {
38 | loadingEnabled = false;
39 | settings = SearchSettings.loadSettings();
40 | if (!isPlayerClassValid(settings)) {
41 | exit(1);
42 | }
43 | String[] expectedBaseUnlocks = {"The Silent", "Defect", "Watcher"};
44 | String[] firstBossUnlocks = {"GUARDIAN", "GHOST", "SLIME"};
45 | String[] secondBossUnlocks = {"CHAMP", "AUTOMATON", "COLLECTOR"};
46 | String[] thirdBossUnlocks = {"CROW", "DONUT", "WIZARD"};
47 | UnlockTracker.unlockPref.data.clear();
48 | UnlockTracker.bossSeenPref.data.clear();
49 | for (String key : expectedBaseUnlocks) {
50 | UnlockTracker.unlockPref.putInteger(key, 2);
51 | }
52 | unlockBosses(firstBossUnlocks, settings.firstBoss);
53 | unlockBosses(secondBossUnlocks, settings.secondBoss);
54 | unlockBosses(thirdBossUnlocks, settings.thirdBoss);
55 | UnlockTracker.resetUnlockProgress(AbstractPlayer.PlayerClass.IRONCLAD);
56 | UnlockTracker.unlockProgress.putInteger("IRONCLADUnlockLevel", settings.ironcladUnlocks);
57 | UnlockTracker.resetUnlockProgress(AbstractPlayer.PlayerClass.THE_SILENT);
58 | UnlockTracker.unlockProgress.putInteger("THE_SILENTUnlockLevel", settings.silentUnlocks);
59 | UnlockTracker.resetUnlockProgress(AbstractPlayer.PlayerClass.DEFECT);
60 | UnlockTracker.unlockProgress.putInteger("DEFECTUnlockLevel", settings.defectUnlocks);
61 | UnlockTracker.resetUnlockProgress(AbstractPlayer.PlayerClass.WATCHER);
62 | UnlockTracker.unlockProgress.putInteger("WATCHERUnlockLevel", settings.watcherUnlocks);
63 | UnlockTracker.retroactiveUnlock();
64 | UnlockTracker.refresh();
65 | SeedRunner runner = new SeedRunner(settings);
66 | ArrayList foundSeeds = new ArrayList<>();
67 | for (long seed = settings.startSeed; seed < settings.endSeed; seed++) {
68 | if (runner.runSeed(seed)) {
69 | foundSeeds.add(seed);
70 | if (settings.verbose) {
71 | runner.getSeedResult().printSeedStats(settings);
72 | }
73 | }
74 | }
75 | System.out.println(String.format("%d seeds found: ", foundSeeds.size()));
76 | System.out.println(foundSeeds);
77 |
78 | if (settings.exitAfterSearch) {
79 | exit(0);
80 | } else {
81 | System.out.println("Search complete. Manually close this program when finished.");
82 | }
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/AbstractCardPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 | import com.evacipated.cardcrawl.modthespire.lib.SpireReturn;
5 | import com.megacrit.cardcrawl.cards.AbstractCard;
6 |
7 | public class AbstractCardPatch {
8 |
9 | @SpirePatch(
10 | clz= AbstractCard.class,
11 | method="initializeDescription"
12 | )
13 | public static class InitializeDescriptionPatch {
14 | public static SpireReturn Prefix(AbstractCard _instance) {
15 | return SpireReturn.Return(null);
16 | }
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/AbstractCreaturePatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.esotericsoftware.spine.AnimationState;
4 | import com.esotericsoftware.spine.AnimationStateData;
5 | import com.esotericsoftware.spine.Skeleton;
6 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
7 | import com.megacrit.cardcrawl.core.AbstractCreature;
8 |
9 | import java.lang.reflect.Field;
10 |
11 | import static org.mockito.Mockito.mock;
12 |
13 | @SpirePatch(
14 | clz= AbstractCreature.class,
15 | method="loadAnimation"
16 | )
17 | public class AbstractCreaturePatch {
18 |
19 | public static void Replace(AbstractCreature _instance, String atlasUrl, String skeletonUrl, float scale) {
20 |
21 | _instance.state = new AnimationState();
22 |
23 | try {
24 | Field data = AbstractCreature.class.getDeclaredField("stateData");
25 | data.setAccessible(true);
26 | data.set(_instance, mock(AnimationStateData.class));
27 | Field skeleton = AbstractCreature.class.getDeclaredField("skeleton");
28 | skeleton.setAccessible(true);
29 | skeleton.set(_instance, mock(Skeleton.class));
30 | } catch (IllegalAccessException | NoSuchFieldException e) {
31 | e.printStackTrace();
32 | }
33 | }
34 |
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/AbstractRoomPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 | import com.megacrit.cardcrawl.relics.AbstractRelic;
5 | import com.megacrit.cardcrawl.rooms.AbstractRoom;
6 |
7 | public class AbstractRoomPatch {
8 |
9 | public static AbstractRelic obtainedRelic;
10 |
11 | @SpirePatch(
12 | clz=AbstractRoom.class,
13 | method="spawnRelicAndObtain"
14 | )
15 | public static class Patch {
16 | public static void Replace(AbstractRoom _instance, float x, float y, AbstractRelic relic) {
17 | AbstractRoomPatch.obtainedRelic = relic;
18 | }
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/AnimatedNpcPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 | import com.evacipated.cardcrawl.modthespire.lib.SpireReturn;
5 | import com.megacrit.cardcrawl.characters.AnimatedNpc;
6 |
7 | @SpirePatch(
8 | clz=AnimatedNpc.class,
9 | method=SpirePatch.CONSTRUCTOR
10 | )
11 | public class AnimatedNpcPatch {
12 | public static SpireReturn Prefix(AnimatedNpc _instance) {
13 | return SpireReturn.Return(null);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/AnimationStatePatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.esotericsoftware.spine.AnimationState;
4 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
5 |
6 | @SpirePatch(
7 | clz= AnimationState.class,
8 | method="setAnimation",
9 | paramtypez = {int.class, String.class, boolean.class}
10 | )
11 | public class AnimationStatePatch {
12 |
13 | public static AnimationState.TrackEntry Replace(AnimationState _instance, int arg1, String arg2, boolean arg3) {
14 | return new AnimationState.TrackEntry();
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/BaseModPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 |
5 | @SpirePatch(
6 | cls="basemod.BaseMod",
7 | method="setupAnimationGfx",
8 | optional=true
9 | )
10 | public class BaseModPatch {
11 |
12 | public static void Replace() {
13 | // Do nothing
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/BeyondPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 | import com.megacrit.cardcrawl.characters.AbstractPlayer;
5 | import com.megacrit.cardcrawl.dungeons.TheBeyond;
6 | import javassist.CannotCompileException;
7 | import javassist.expr.ExprEditor;
8 | import javassist.expr.NewExpr;
9 |
10 | import java.util.ArrayList;
11 |
12 | @SpirePatch(
13 | clz= TheBeyond.class,
14 | method=SpirePatch.CONSTRUCTOR,
15 | paramtypez = {AbstractPlayer.class, ArrayList.class}
16 | )
17 | public class BeyondPatch {
18 |
19 | public static ExprEditor Instrument() {
20 | return new ExprEditor() {
21 | public void edit(NewExpr m) throws CannotCompileException {
22 | if (m.getClassName().equals("com.megacrit.cardcrawl.scenes.TheBeyondScene")) {
23 | m.replace("{$_ = $0;}");
24 | }
25 | }
26 | };
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/BottledFlamePatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 | import com.evacipated.cardcrawl.modthespire.lib.SpireReturn;
5 | import com.megacrit.cardcrawl.relics.BottledFlame;
6 | import seedsearch.SeedSearch;
7 |
8 | @SpirePatch(
9 | clz= BottledFlame.class,
10 | method="canSpawn"
11 | )
12 | public class BottledFlamePatch {
13 | public static SpireReturn Prefix() {
14 | if (SeedSearch.settings.alwaysSpawnBottledFlame) {
15 | return SpireReturn.Return(true);
16 | } else {
17 | return SpireReturn.Continue();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/BottledLightningPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 | import com.evacipated.cardcrawl.modthespire.lib.SpireReturn;
5 | import com.megacrit.cardcrawl.relics.BottledLightning;
6 | import seedsearch.SeedSearch;
7 |
8 | @SpirePatch(
9 | clz= BottledLightning.class,
10 | method="canSpawn"
11 | )
12 | public class BottledLightningPatch {
13 | public static SpireReturn Prefix() {
14 | if (SeedSearch.settings.alwaysSpawnBottledLightning) {
15 | return SpireReturn.Return(true);
16 | } else {
17 | return SpireReturn.Continue();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/BottledTornadoPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 | import com.evacipated.cardcrawl.modthespire.lib.SpireReturn;
5 | import com.megacrit.cardcrawl.relics.BottledTornado;
6 | import seedsearch.SeedSearch;
7 |
8 | @SpirePatch(
9 | clz= BottledTornado.class,
10 | method="canSpawn"
11 | )
12 | public class BottledTornadoPatch {
13 | public static SpireReturn Prefix() {
14 | if (SeedSearch.settings.alwaysSpawnBottledTornado) {
15 | return SpireReturn.Return(true);
16 | } else {
17 | return SpireReturn.Continue();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/CardCrawlGameRenderPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 | import com.megacrit.cardcrawl.core.CardCrawlGame;
5 |
6 | @SpirePatch(
7 | clz= CardCrawlGame.class,
8 | method="render"
9 | )
10 | public class CardCrawlGameRenderPatch {
11 | public static void Replace(CardCrawlGame _instance) {
12 | // Do nothing
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/CardRewardScreenPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 | import com.megacrit.cardcrawl.cards.AbstractCard;
5 | import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
6 | import com.megacrit.cardcrawl.rewards.RewardItem;
7 | import com.megacrit.cardcrawl.screens.CardRewardScreen;
8 | import com.megacrit.cardcrawl.screens.CombatRewardScreen;
9 | import seedsearch.Reward;
10 | import seedsearch.SeedRunner;
11 |
12 | import java.util.ArrayList;
13 |
14 | public class CardRewardScreenPatch {
15 |
16 | public static ArrayList rewardCards;
17 |
18 | @SpirePatch(
19 | clz= CardRewardScreen.class,
20 | method="open"
21 | )
22 | public static class Patch {
23 | public static void Replace(CardRewardScreen _instance, ArrayList cards, RewardItem rItem, String header) {
24 | rewardCards = cards;
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/CityPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 | import com.megacrit.cardcrawl.characters.AbstractPlayer;
5 | import com.megacrit.cardcrawl.dungeons.TheCity;
6 | import javassist.CannotCompileException;
7 | import javassist.expr.ExprEditor;
8 | import javassist.expr.NewExpr;
9 |
10 | import java.util.ArrayList;
11 |
12 | @SpirePatch(
13 | clz= TheCity.class,
14 | method=SpirePatch.CONSTRUCTOR,
15 | paramtypez = {AbstractPlayer.class, ArrayList.class}
16 | )
17 | public class CityPatch {
18 |
19 | public static ExprEditor Instrument() {
20 | return new ExprEditor() {
21 | public void edit(NewExpr m) throws CannotCompileException {
22 | if (m.getClassName().equals("com.megacrit.cardcrawl.scenes.TheCityScene")) {
23 | m.replace("{$_ = $0;}");
24 | }
25 | }
26 | };
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/CombatRewardScreenPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 | import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
5 | import com.megacrit.cardcrawl.rewards.RewardItem;
6 | import com.megacrit.cardcrawl.screens.CombatRewardScreen;
7 | import seedsearch.Reward;
8 | import seedsearch.SeedRunner;
9 |
10 | import java.util.ArrayList;
11 |
12 | public class CombatRewardScreenPatch {
13 |
14 | @SpirePatch(
15 | clz= CombatRewardScreen.class,
16 | method="open",
17 | paramtypez = {}
18 | )
19 | public static class Patch1 {
20 | public static void Replace(CombatRewardScreen _instance) {
21 | extractRewards(_instance);
22 | }
23 | }
24 |
25 | @SpirePatch(
26 | clz= CombatRewardScreen.class,
27 | method="open",
28 | paramtypez = {String.class}
29 | )
30 | public static class Patch2 {
31 | public static void Replace(CombatRewardScreen _instance, String _arg) {
32 | extractRewards(_instance);
33 | }
34 | }
35 |
36 | @SpirePatch(
37 | clz= CombatRewardScreen.class,
38 | method="openCombat",
39 | paramtypez = {String.class, boolean.class}
40 | )
41 | public static class Patch3 {
42 | public static void Replace(CombatRewardScreen _instance, String _arg1, boolean _arg2) {
43 | extractRewards(_instance);
44 | }
45 | }
46 |
47 | @SpirePatch(
48 | clz= CombatRewardScreen.class,
49 | method="openCombat",
50 | paramtypez = {String.class}
51 | )
52 | public static class Patch4 {
53 | public static void Replace(CombatRewardScreen _instance, String _arg) {
54 | extractRewards(_instance);
55 | }
56 | }
57 |
58 | public static void extractRewards(CombatRewardScreen _instance) {
59 | SeedRunner.clearCombatRewards();
60 | _instance.setupItemReward();
61 | for (RewardItem item : _instance.rewards) {
62 | if (item.type == RewardItem.RewardType.RELIC) {
63 | SeedRunner.combatRelics.add(item.relic);
64 | } else if (item.type == RewardItem.RewardType.CARD) {
65 | SeedRunner.combatCardRewards.add(Reward.makeCardReward(AbstractDungeon.floorNum, item.cards));
66 | } else if (item.type == RewardItem.RewardType.GOLD) {
67 | SeedRunner.combatGold = item.goldAmt;
68 | } else if (item.type == RewardItem.RewardType.POTION) {
69 | SeedRunner.combatPotions.add(item.potion);
70 | }
71 | }
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/EndingPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 | import com.megacrit.cardcrawl.characters.AbstractPlayer;
5 | import com.megacrit.cardcrawl.dungeons.TheEnding;
6 | import javassist.CannotCompileException;
7 | import javassist.expr.ExprEditor;
8 | import javassist.expr.NewExpr;
9 |
10 | import java.util.ArrayList;
11 |
12 | @SpirePatch(
13 | clz= TheEnding.class,
14 | method=SpirePatch.CONSTRUCTOR,
15 | paramtypez = {AbstractPlayer.class, ArrayList.class}
16 | )
17 | public class EndingPatch {
18 |
19 | public static ExprEditor Instrument() {
20 | return new ExprEditor() {
21 | public void edit(NewExpr m) throws CannotCompileException {
22 | if (m.getClassName().equals("com.megacrit.cardcrawl.scenes.TheEndingScene")) {
23 | m.replace("{$_ = $0;}");
24 | }
25 | }
26 | };
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/EventHelperPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 | import com.megacrit.cardcrawl.helpers.EventHelper;
5 |
6 | @SpirePatch(
7 | clz= EventHelper.class,
8 | method="getEvent"
9 | )
10 | public class EventHelperPatch {
11 |
12 | public static String eventName = "";
13 |
14 | public static void Prefix(String key) {
15 | eventName = key;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/ExordiumPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 | import com.megacrit.cardcrawl.characters.AbstractPlayer;
5 | import com.megacrit.cardcrawl.dungeons.Exordium;
6 | import javassist.CannotCompileException;
7 | import javassist.expr.ExprEditor;
8 | import javassist.expr.MethodCall;
9 | import javassist.expr.NewExpr;
10 |
11 | import java.util.ArrayList;
12 |
13 | @SpirePatch(
14 | clz= Exordium.class,
15 | method=SpirePatch.CONSTRUCTOR,
16 | paramtypez = {AbstractPlayer.class, ArrayList.class}
17 | )
18 | public class ExordiumPatch {
19 | public static ExprEditor Instrument() {
20 | return new ExprEditor() {
21 | public void edit(NewExpr m) throws CannotCompileException {
22 | if (m.getClassName().equals("com.megacrit.cardcrawl.scenes.TheBottomScene")) {
23 | m.replace("{$_ = $0;}");
24 | } else if (m.getClassName().equals("com.megacrit.cardcrawl.neow.NeowRoom")) {
25 | m.replace("{$_ = $0;}");
26 | }
27 | }
28 |
29 | public void edit(MethodCall m) throws CannotCompileException {
30 | if (m.getMethodName().equals("randomizeScene")) {
31 | m.replace("{}");
32 | }
33 | }
34 | };
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/LoadImagePatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.badlogic.gdx.graphics.Texture;
4 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
5 | import com.evacipated.cardcrawl.modthespire.lib.SpireReturn;
6 | import com.megacrit.cardcrawl.helpers.ImageMaster;
7 |
8 | import static seedsearch.SeedSearch.loadingEnabled;
9 |
10 | public class LoadImagePatch {
11 |
12 | public static Texture defaultTexture;
13 |
14 | @SpirePatch(
15 | clz= ImageMaster.class,
16 | method="loadImage",
17 | paramtypez = {String.class}
18 | )
19 | public static class Patch1 {
20 | public static SpireReturn Prefix() {
21 | if(loadingEnabled) {
22 | return SpireReturn.Continue();
23 | } else {
24 | return SpireReturn.Return(defaultTexture);
25 | }
26 | }
27 | }
28 |
29 | @SpirePatch(
30 | clz= ImageMaster.class,
31 | method="loadImage",
32 | paramtypez = {String.class, boolean.class}
33 | )
34 | public static class Patch2 {
35 | public static SpireReturn Prefix() {
36 | if(loadingEnabled) {
37 | return SpireReturn.Continue();
38 | } else {
39 | return SpireReturn.Return(defaultTexture);
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/LogMetricPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 | import com.megacrit.cardcrawl.events.AbstractEvent;
5 |
6 | import java.util.List;
7 |
8 | @SpirePatch(
9 | clz= AbstractEvent.class,
10 | method="logMetric",
11 | paramtypez = {String.class, String.class, List.class, List.class, List.class, List.class, List.class,
12 | List.class, List.class, int.class, int.class, int.class, int.class, int.class, int.class}
13 | )
14 | public class LogMetricPatch {
15 | public static void Replace(String eventName, String playerChoice, List cardsObtained, List cardsRemoved,
16 | List cardsTransformed, List cardsUpgraded, List relicsObtained,
17 | List potionsObtained, List relicsLost, int damageTaken, int damageHealed,
18 | int hpLoss, int hpGain, int goldGain, int goldLoss) {
19 |
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/MainPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.badlogic.gdx.Gdx;
4 | import com.badlogic.gdx.backends.headless.HeadlessApplication;
5 | import com.badlogic.gdx.backends.headless.HeadlessApplicationConfiguration;
6 | import com.badlogic.gdx.backends.headless.HeadlessNativesLoader;
7 | import com.badlogic.gdx.backends.headless.mock.graphics.MockGraphics;
8 | import com.badlogic.gdx.graphics.GL20;
9 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
10 | import com.megacrit.cardcrawl.core.CardCrawlGame;
11 | import com.megacrit.cardcrawl.desktop.DesktopLauncher;
12 | import org.apache.logging.log4j.Level;
13 | import org.apache.logging.log4j.core.config.Configurator;
14 |
15 | import static org.mockito.Mockito.mock;
16 |
17 | @SpirePatch(
18 | clz = DesktopLauncher.class,
19 | method = "main"
20 | )
21 | public class MainPatch {
22 |
23 | public static void Replace(String[] args) {
24 | try {
25 | Configurator.setRootLevel(Level.OFF);
26 | HeadlessNativesLoader.load();
27 | Gdx.graphics = new MockGraphics();
28 | Gdx.gl = Gdx.gl20 = mock(GL20.class);
29 | Gdx.audio = null;
30 | HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration();
31 | new HeadlessApplication(new CardCrawlGame(config.preferencesDirectory), config);
32 | } catch (Exception e) {
33 | e.printStackTrace();
34 | Gdx.app.exit();
35 | }
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/MonsterHelperPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.*;
4 | import com.evacipated.cardcrawl.modthespire.patcher.PatchingException;
5 | import com.megacrit.cardcrawl.helpers.EnemyData;
6 | import com.megacrit.cardcrawl.helpers.MonsterHelper;
7 | import com.megacrit.cardcrawl.metrics.BotDataUploader;
8 | import javassist.CannotCompileException;
9 | import javassist.CtBehavior;
10 |
11 | import java.util.ArrayList;
12 |
13 | @SpirePatch(
14 | clz=MonsterHelper.class,
15 | method="uploadEnemyData"
16 | )
17 | public class MonsterHelperPatch {
18 |
19 | public static ArrayList ids;
20 |
21 | @SpireInsertPatch(
22 | locator=Locator.class,
23 | localvars={"data"}
24 | )
25 | public static SpireReturn Insert(ArrayList data) {
26 | ids = new ArrayList<>();
27 | for (EnemyData enemy : data) {
28 | ids.add(enemy.name);
29 | }
30 | return SpireReturn.Return(null);
31 | }
32 |
33 | private static class Locator extends SpireInsertLocator {
34 | public int[] Locate(CtBehavior ctMethodToPatch) throws CannotCompileException, PatchingException {
35 | Matcher matcher = new Matcher.MethodCallMatcher(BotDataUploader.class, "uploadDataAsync");
36 | return LineFinder.findInOrder(ctMethodToPatch, new ArrayList(), matcher);
37 | }
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/SaveHelperPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 | import com.megacrit.cardcrawl.helpers.SaveHelper;
5 |
6 | @SpirePatch(
7 | clz = SaveHelper.class,
8 | method = "shouldSave"
9 | )
10 | public class SaveHelperPatch {
11 |
12 | public static boolean Replace() {
13 | return false;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/ShowCardAndObtainEffectPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.*;
4 | import com.evacipated.cardcrawl.modthespire.patcher.PatchingException;
5 | import com.megacrit.cardcrawl.cards.AbstractCard;
6 | import com.megacrit.cardcrawl.unlock.UnlockTracker;
7 | import com.megacrit.cardcrawl.vfx.cardManip.ShowCardAndObtainEffect;
8 | import javassist.CannotCompileException;
9 | import javassist.CtBehavior;
10 |
11 | import java.util.ArrayList;
12 |
13 | @SpirePatch(
14 | clz= ShowCardAndObtainEffect.class,
15 | method=SpirePatch.CONSTRUCTOR,
16 | paramtypez = {AbstractCard.class, float.class, float.class, boolean.class}
17 | )
18 | public class ShowCardAndObtainEffectPatch {
19 |
20 | public static ArrayList obtainedCards = new ArrayList<>();
21 |
22 | public static void resetCards() {
23 | obtainedCards = new ArrayList<>();
24 | }
25 |
26 | @SpireInsertPatch(
27 | locator=Locator.class
28 | )
29 | public static void Insert(ShowCardAndObtainEffect _instance, AbstractCard card, float x, float y, boolean convergeCards) {
30 | if (!_instance.isDone) {
31 | obtainedCards.add(card);
32 | }
33 | }
34 |
35 | private static class Locator extends SpireInsertLocator {
36 | public int[] Locate(CtBehavior ctMethodToPatch) throws CannotCompileException, PatchingException {
37 | Matcher matcher = new Matcher.MethodCallMatcher(UnlockTracker.class, "markCardAsSeen");
38 | return LineFinder.findInOrder(ctMethodToPatch, new ArrayList(), matcher);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/StartSearch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.*;
4 | import com.evacipated.cardcrawl.modthespire.patcher.PatchingException;
5 | import com.megacrit.cardcrawl.core.CardCrawlGame;
6 | import com.megacrit.cardcrawl.core.Settings;
7 | import com.megacrit.cardcrawl.helpers.GameTips;
8 | import com.megacrit.cardcrawl.helpers.ImageMaster;
9 | import com.megacrit.cardcrawl.screens.DisplayOption;
10 | import javassist.CannotCompileException;
11 | import javassist.CtBehavior;
12 | import javassist.expr.ExprEditor;
13 | import javassist.expr.NewExpr;
14 | import seedsearch.SeedSearch;
15 |
16 | import java.util.ArrayList;
17 |
18 | @SpirePatch(
19 | clz= CardCrawlGame.class,
20 | method="create"
21 | )
22 | public class StartSearch {
23 |
24 | public static void Prefix(CardCrawlGame _instance) {
25 | Settings.displayOptions = new ArrayList<>();
26 | Settings.displayOptions.add(new DisplayOption(0,0));
27 | }
28 |
29 | @SpireInsertPatch(
30 | locator = Locator.class
31 | )
32 | public static SpireReturn Insert(CardCrawlGame _instance) {
33 | try {
34 | LoadImagePatch.defaultTexture = ImageMaster.loadImage("images/npcs/rug/eng.png");
35 | SeedSearch.search();
36 | return SpireReturn.Return(null);
37 | } catch (Exception e) {
38 | e.printStackTrace();
39 | }
40 | return SpireReturn.Return(null);
41 | }
42 |
43 | public static ExprEditor Instrument() {
44 | return new ExprEditor() {
45 | public void edit(NewExpr m) throws CannotCompileException {
46 | if (m.getClassName().equals("com.badlogic.gdx.graphics.g2d.SpriteBatch")
47 | || m.getClassName().equals("com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch")
48 | || m.getClassName().equals(GameTips.class.getName())) {
49 | m.replace("{$_ = $0;}");
50 | }
51 | }
52 | };
53 | }
54 |
55 | private static class Locator extends SpireInsertLocator {
56 | public int[] Locate(CtBehavior ctMethodToPatch) throws CannotCompileException, PatchingException {
57 | Matcher matcher = new Matcher.FieldAccessMatcher(CardCrawlGame.class, "splashScreen");
58 | return LineFinder.findInOrder(ctMethodToPatch, new ArrayList(), matcher);
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/TextureAtlasPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.badlogic.gdx.graphics.g2d.TextureAtlas;
4 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
5 | import com.evacipated.cardcrawl.modthespire.lib.SpireReturn;
6 |
7 | import static seedsearch.SeedSearch.loadingEnabled;
8 |
9 | public class TextureAtlasPatch {
10 |
11 | @SpirePatch(
12 | clz=TextureAtlas.class,
13 | method="findRegion",
14 | paramtypez = {String.class}
15 | )
16 | public static class FindRegionPatch1{
17 | public static SpireReturn Prefix() {
18 | if(loadingEnabled) {
19 | return SpireReturn.Continue();
20 | } else {
21 | return SpireReturn.Return(null);
22 | }
23 | }
24 | }
25 |
26 | @SpirePatch(
27 | clz=TextureAtlas.class,
28 | method="findRegion",
29 | paramtypez = {String.class, int.class}
30 | )
31 | public static class FindRegionPatch2{
32 | public static SpireReturn Prefix() {
33 | if(loadingEnabled) {
34 | return SpireReturn.Continue();
35 | } else {
36 | return SpireReturn.Return(null);
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/seedsearch/patches/WatcherPatch.java:
--------------------------------------------------------------------------------
1 | package seedsearch.patches;
2 |
3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
4 | import com.megacrit.cardcrawl.characters.Watcher;
5 |
6 | public class WatcherPatch {
7 |
8 | @SpirePatch(
9 | clz= Watcher.class,
10 | method="loadEyeAnimation"
11 | )
12 | public static class InitializeDescriptionPatch {
13 | public static void Replace(Watcher _instance) {
14 | //Do nothing
15 | }
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/resources/ModTheSpire.json:
--------------------------------------------------------------------------------
1 | {
2 | "modid": "${project.artifactId}",
3 | "name": "${project.name}",
4 | "author_list": ["Forgotten Arbiter"],
5 | "description": "${project.description}",
6 | "version": "${project.version}",
7 | "sts_version": "${SlayTheSpire.version}",
8 | "mts_version": "${ModTheSpire.version}"
9 | }
--------------------------------------------------------------------------------