From 9eaaceb7a254a7dee27d188c8bdced6d5a433ef2 Mon Sep 17 00:00:00 2001 From: Evan Debenham Date: Sat, 16 Apr 2022 13:35:21 -0400 Subject: [PATCH] v1.3.0: added support for custom seeded runs --- core/src/main/assets/interfaces/icons.png | Bin 14504 -> 14630 bytes .../assets/messages/scenes/scenes.properties | 4 ++ .../messages/windows/windows.properties | 4 ++ .../shatteredpixeldungeon/Badges.java | 29 ++++---- .../shatteredpixeldungeon/Bones.java | 8 +-- .../shatteredpixeldungeon/Dungeon.java | 17 ++++- .../GamesInProgress.java | 8 ++- .../shatteredpixeldungeon/Rankings.java | 3 + .../shatteredpixeldungeon/SPDSettings.java | 10 +++ .../scenes/HeroSelectScene.java | 66 ++++++++++++++++-- .../scenes/StartScene.java | 4 ++ .../shatteredpixeldungeon/ui/Icons.java | 29 ++++---- .../ui/WndTextInput.java | 38 ++++++---- .../utils/DungeonSeed.java | 46 +++++++++--- .../windows/WndGameInProgress.java | 25 ++----- .../windows/WndHero.java | 12 ++-- .../windows/WndRanking.java | 2 +- 17 files changed, 220 insertions(+), 85 deletions(-) diff --git a/core/src/main/assets/interfaces/icons.png b/core/src/main/assets/interfaces/icons.png index b1d2ffabb6b532ef51565b7d959db7367e2b4b85..6835e9314e1f33b00885be747eb6d7b03fb7c27c 100644 GIT binary patch literal 14630 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 literal 14504 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 KLX8|O?0*2{Xch_p diff --git a/core/src/main/assets/messages/scenes/scenes.properties b/core/src/main/assets/messages/scenes/scenes.properties index 736c7fa0f..51b32f04b 100644 --- a/core/src/main/assets/messages/scenes/scenes.properties +++ b/core/src/main/assets/messages/scenes/scenes.properties @@ -47,6 +47,10 @@ scenes.gamescene.trample=Trample scenes.gamescene.examine=Examine scenes.heroselectscene.title=Choose Your Hero +scenes.heroselectscene.custom_seed_title=Enter a Custom Seed +scenes.heroselectscene.custom_seed_desc=The same seed and game version always generate the same dungeon! Seeds are nine uppercase letters (e.g. ABC-DEF-GHI), but you can enter anything you want and the game will convert it. _Games with a custom seed cannot earn badges or appear in rankings._ +scenes.heroselectscene.custom_seed_set=Set +scenes.heroselectscene.custom_seed_clear=Clear scenes.interlevelscene$mode.descend=Descending... scenes.interlevelscene$mode.ascend=Ascending... diff --git a/core/src/main/assets/messages/windows/windows.properties b/core/src/main/assets/messages/windows/windows.properties index c4dcc0458..3bde042c9 100644 --- a/core/src/main/assets/messages/windows/windows.properties +++ b/core/src/main/assets/messages/windows/windows.properties @@ -46,6 +46,8 @@ windows.wndgameinprogress.str=Strength windows.wndgameinprogress.health=Health windows.wndgameinprogress.gold=Gold Collected windows.wndgameinprogress.depth=Maximum Depth +windows.wndgameinprogress.dungeon_seed=Dungeon Seed +windows.wndgameinprogress.custom_seed=_Custom Seed_ windows.wndgameinprogress.continue=Continue windows.wndgameinprogress.erase=Erase windows.wndgameinprogress.erase_warn_title=Are you sure you want to delete this save? @@ -62,6 +64,8 @@ windows.wndhero$statstab.str=Strength windows.wndhero$statstab.health=Health windows.wndhero$statstab.gold=Gold Collected windows.wndhero$statstab.depth=Maximum Depth +windows.wndhero$statstab.dungeon_seed=Dungeon Seed +windows.wndhero$statstab.custom_seed=_Custom Seed_ windows.wndheroinfo.talents=talents windows.wndheroinfo.talents_msg=The hero gains one talent point each time they level up. Higher tier talents appear after defeating the second boss. diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Badges.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Badges.java index e1d2a30b8..b7d7a91be 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Badges.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Badges.java @@ -283,7 +283,7 @@ public class Badges { } } - public static int unlocked(boolean global){ + public static int totalUnlocked(boolean global){ if (global) return Badges.global.size(); else return Badges.local.size(); } @@ -533,7 +533,7 @@ public class Badges { for (Catalog cat : Catalog.values()){ if (cat.allSeen()){ Badge b = Catalog.catalogBadges.get(cat); - if (!global.contains(b)){ + if (!isUnlocked(b)){ displayBadge(b); } } @@ -654,7 +654,7 @@ public class Badges { break; } local.add( badge ); - addGlobal(badge); + unlock(badge); if (isUnlocked( Badge.BOSS_SLAIN_1_WARRIOR ) && isUnlocked( Badge.BOSS_SLAIN_1_MAGE ) && @@ -697,7 +697,7 @@ public class Badges { return; } local.add( badge ); - addGlobal(badge); + unlock(badge); if (isUnlocked( Badge.BOSS_SLAIN_3_GLADIATOR ) && isUnlocked( Badge.BOSS_SLAIN_3_BERSERKER ) && @@ -735,11 +735,11 @@ public class Badges { break; } - addGlobal(badge); + unlock(badge); } public static void validateRatmogrify(){ - addGlobal(Badge.FOUND_RATMOGRIFY); + unlock(Badge.FOUND_RATMOGRIFY); } public static void validateMageUnlock(){ @@ -788,7 +788,7 @@ public class Badges { break; } local.add( badge ); - addGlobal(badge); + unlock(badge); if (isUnlocked( Badge.VICTORY_WARRIOR ) && isUnlocked( Badge.VICTORY_MAGE ) && @@ -855,11 +855,11 @@ public class Badges { badge = Badge.CHAMPION_1; } if (challenges >= 3){ - addGlobal(badge); + unlock(badge); badge = Badge.CHAMPION_2; } if (challenges >= 6){ - addGlobal(badge); + unlock(badge); badge = Badge.CHAMPION_3; } local.add(badge); @@ -868,11 +868,11 @@ public class Badges { private static void displayBadge( Badge badge ) { - if (badge == null) { + if (badge == null || Dungeon.usingCustomSeed) { return; } - if (global.contains( badge )) { + if (isUnlocked( badge )) { if (!badge.meta) { GLog.h( Messages.get(Badges.class, "endorsed", badge.title()) ); @@ -881,8 +881,7 @@ public class Badges { } else { - global.add( badge ); - saveNeeded = true; + unlock(badge); GLog.h( Messages.get(Badges.class, "new", badge.title() + " (" + badge.desc() + ")") ); GLog.newLine(); @@ -905,8 +904,8 @@ public class Badges { saveNeeded = true; } - public static void addGlobal( Badge badge ){ - if (!global.contains(badge)){ + public static void unlock( Badge badge ){ + if (!isUnlocked(badge) && !Dungeon.usingCustomSeed){ global.add( badge ); saveNeeded = true; } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Bones.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Bones.java index ece3972ed..1ce2f68c6 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Bones.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Bones.java @@ -50,8 +50,8 @@ public class Bones { depth = Dungeon.depth; - //heroes which have won the game, who die far above their farthest depth, or who are challenged drop no bones. - if (Statistics.amuletObtained || (Statistics.deepestFloor - 5) >= depth || Dungeon.challenges > 0) { + //heroes drop no bones if they have the amulet, die far above their farthest depth, are challenged, or are playing with a custom seed. + if (Statistics.amuletObtained || (Statistics.deepestFloor - 5) >= depth || Dungeon.challenges > 0 || Dungeon.usingCustomSeed) { depth = -1; return; } @@ -141,8 +141,8 @@ public class Bones { } } else { - //heroes who are challenged cannot find bones - if (depth == Dungeon.depth && Dungeon.challenges == 0) { + //heroes who are challenged or on a seeded run cannot find bones + if (depth == Dungeon.depth && Dungeon.challenges == 0 && !Dungeon.usingCustomSeed) { Bundle emptyBones = new Bundle(); emptyBones.put(LEVEL, 0); try { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java index 97c8476d9..5f8ba0e03 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java @@ -172,6 +172,7 @@ public class Dungeon { public static int version; public static long seed; + public static boolean usingCustomSeed; public static void init() { @@ -179,7 +180,13 @@ public class Dungeon { challenges = SPDSettings.challenges(); mobsToChampion = -1; - seed = DungeonSeed.randomSeed(); + if (SPDSettings.customSeed() != -1){ + seed = SPDSettings.customSeed(); + usingCustomSeed = true; + } else { + seed = DungeonSeed.randomSeed(); + usingCustomSeed = false; + } Actor.clear(); Actor.resetNextID(); @@ -441,6 +448,7 @@ public class Dungeon { private static final String VERSION = "version"; private static final String SEED = "seed"; + private static final String CUSTOM_SEED = "custom_seed"; private static final String CHALLENGES = "challenges"; private static final String MOBS_TO_CHAMPION = "mobs_to_champion"; private static final String HERO = "hero"; @@ -462,6 +470,7 @@ public class Dungeon { version = Game.versionCode; bundle.put( VERSION, version ); bundle.put( SEED, seed ); + bundle.put( CUSTOM_SEED, usingCustomSeed ); bundle.put( CHALLENGES, challenges ); bundle.put( MOBS_TO_CHAMPION, mobsToChampion ); bundle.put( HERO, hero ); @@ -537,7 +546,7 @@ public class Dungeon { saveGame( GamesInProgress.curSlot ); saveLevel( GamesInProgress.curSlot ); - GamesInProgress.set( GamesInProgress.curSlot, depth, challenges, hero ); + GamesInProgress.set( GamesInProgress.curSlot, depth, challenges, seed, usingCustomSeed, hero ); } } @@ -553,6 +562,7 @@ public class Dungeon { version = bundle.getInt( VERSION ); seed = bundle.contains( SEED ) ? bundle.getLong( SEED ) : DungeonSeed.randomSeed(); + usingCustomSeed = bundle.getBoolean( CUSTOM_SEED ); Actor.clear(); Actor.restoreNextID( bundle ); @@ -683,6 +693,9 @@ public class Dungeon { info.depth = bundle.getInt( DEPTH ); info.version = bundle.getInt( VERSION ); info.challenges = bundle.getInt( CHALLENGES ); + info.seed = bundle.getLong( SEED ); + info.customSeed = bundle.getBoolean( CUSTOM_SEED ); + Hero.preview( info, bundle.getBundle( HERO ) ); Statistics.preview( info, bundle ); } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/GamesInProgress.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/GamesInProgress.java index 8cc3b6718..99302fb26 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/GamesInProgress.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/GamesInProgress.java @@ -121,13 +121,16 @@ public class GamesInProgress { } } - public static void set(int slot, int depth, int challenges, + public static void set(int slot, int depth, int challenges, long seed, boolean customSeed, Hero hero) { Info info = new Info(); info.slot = slot; info.depth = depth; info.challenges = challenges; + + info.seed = seed; + info.customSeed = customSeed; info.level = hero.lvl; info.str = hero.STR; @@ -160,6 +163,9 @@ public class GamesInProgress { public int depth; public int version; public int challenges; + + public long seed; + public boolean customSeed; public int level; public int str; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Rankings.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Rankings.java index 5d18e3732..cf6050ca7 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Rankings.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Rankings.java @@ -61,6 +61,9 @@ public enum Rankings { public void submit( boolean win, Class cause ) { + //games with custom seeds do not appear in rankings + if (Dungeon.usingCustomSeed) return; + load(); Record rec = new Record(); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/SPDSettings.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/SPDSettings.java index b44693255..bba62b03e 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/SPDSettings.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/SPDSettings.java @@ -24,6 +24,7 @@ package com.shatteredpixel.shatteredpixeldungeon; import com.shatteredpixel.shatteredpixeldungeon.messages.Languages; import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene; +import com.shatteredpixel.shatteredpixeldungeon.utils.DungeonSeed; import com.watabou.noosa.Game; import com.watabou.noosa.audio.Music; import com.watabou.noosa.audio.Sample; @@ -179,6 +180,7 @@ public class SPDSettings extends GameSettings { public static final String KEY_LAST_CLASS = "last_class"; public static final String KEY_CHALLENGES = "challenges"; + public static final String KEY_CUSTOM_SEED = "custom_seed"; public static final String KEY_INTRO = "intro"; public static final String KEY_SUPPORT_NAGGED= "support_nagged"; @@ -207,6 +209,14 @@ public class SPDSettings extends GameSettings { return getInt( KEY_CHALLENGES, 0, 0, Challenges.MAX_VALUE ); } + public static void customSeed( long value ){ + put( KEY_CUSTOM_SEED, value ); + } + + public static long customSeed() { + return getLong( KEY_CUSTOM_SEED, -1, -1, DungeonSeed.TOTAL_SEEDS-1); + } + public static void supportNagged( boolean value ) { put( KEY_SUPPORT_NAGGED, value ); } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/HeroSelectScene.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/HeroSelectScene.java index 36c54ea57..5b60b9228 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/HeroSelectScene.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/HeroSelectScene.java @@ -21,8 +21,6 @@ package com.shatteredpixel.shatteredpixeldungeon.scenes; -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.graphics.GL20; import com.shatteredpixel.shatteredpixeldungeon.Badges; import com.shatteredpixel.shatteredpixeldungeon.Chrome; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; @@ -40,6 +38,8 @@ import com.shatteredpixel.shatteredpixeldungeon.ui.Icons; import com.shatteredpixel.shatteredpixeldungeon.ui.RenderedTextBlock; import com.shatteredpixel.shatteredpixeldungeon.ui.StyledButton; import com.shatteredpixel.shatteredpixeldungeon.ui.Window; +import com.shatteredpixel.shatteredpixeldungeon.ui.WndTextInput; +import com.shatteredpixel.shatteredpixeldungeon.utils.DungeonSeed; import com.shatteredpixel.shatteredpixeldungeon.windows.WndChallenges; import com.shatteredpixel.shatteredpixeldungeon.windows.WndHeroInfo; import com.shatteredpixel.shatteredpixeldungeon.windows.WndKeyBindings; @@ -64,6 +64,7 @@ public class HeroSelectScene extends PixelScene { private ArrayList heroBtns = new ArrayList<>(); private StyledButton startBtn; private IconButton infoButton; + private IconButton seedButton; private IconButton challengeButton; private IconButton btnExit; @@ -146,7 +147,7 @@ public class HeroSelectScene extends PixelScene { } }; infoButton.visible = false; - infoButton.setSize(21, 21); + infoButton.setSize(20, 21); add(infoButton); HeroClass[] classes = HeroClass.values(); @@ -192,14 +193,67 @@ public class HeroSelectScene extends PixelScene { return Messages.titleCase(Messages.get(WndChallenges.class, "title")); } }; - challengeButton.setRect(heroBtnleft + 16, Camera.main.height-HeroBtn.HEIGHT-16, 21, 21); + challengeButton.setRect(heroBtnleft + 16, Camera.main.height-HeroBtn.HEIGHT-16, 20, 21); challengeButton.visible = false; + seedButton = new IconButton( Icons.get(Icons.SEED)){ + @Override + protected void onClick() { + String existingSeedtext = SPDSettings.customSeed() == -1 ? "" : DungeonSeed.convertToCode(SPDSettings.customSeed()); + ShatteredPixelDungeon.scene().addToFront( new WndTextInput(Messages.get(HeroSelectScene.class, "custom_seed_title"), + Messages.get(HeroSelectScene.class, "custom_seed_desc"), + existingSeedtext, + 30, + false, + Messages.get(HeroSelectScene.class, "custom_seed_set"), + Messages.get(HeroSelectScene.class, "custom_seed_clear")){ + @Override + public void onSelect(boolean positive, String text) { + long seed = DungeonSeed.convertFromText(text); + if (positive && seed != -1){ + SPDSettings.customSeed(seed); + icon.hardlight(1f, 1.5f, 0.67f); + } else { + SPDSettings.customSeed(-1); + icon.resetColor(); + } + } + }); + } + + @Override + protected void onPointerUp() { + if (SPDSettings.customSeed() != -1){ + icon.hardlight(1f, 1.5f, 0.67f); + } else { + icon.resetColor(); + } + } + + @Override + public void update() { + if( !visible && GamesInProgress.selectedClass != null){ + visible = true; + } + super.update(); + } + + @Override + protected String hoverText() { + return Messages.get(HeroSelectScene.class, "custom_seed_title"); + } + }; + if (SPDSettings.customSeed() != -1) seedButton.icon().hardlight(1f, 1.5f, 0.67f); + seedButton.setRect(challengeButton.left()-16, challengeButton.top(), 16, 21); + seedButton.visible = false; + if (DeviceCompat.isDebug() || Badges.isUnlocked(Badges.Badge.VICTORY)){ + add(seedButton); add(challengeButton); } else { Dungeon.challenges = 0; SPDSettings.challenges(0); + SPDSettings.customSeed(-1); } btnExit = new ExitButton(); @@ -251,6 +305,9 @@ public class HeroSelectScene extends PixelScene { challengeButton.visible = true; challengeButton.setPos(startBtn.left()-challengeButton.width(), startBtn.top()); + + seedButton.visible = true; + seedButton.setPos(challengeButton.left()-seedButton.width(), challengeButton.top()); } private float uiAlpha; @@ -274,6 +331,7 @@ public class HeroSelectScene extends PixelScene { startBtn.alpha(alpha); btnExit.icon().alpha(alpha); challengeButton.icon().alpha(alpha); + seedButton.icon().alpha(alpha); infoButton.icon().alpha(alpha); } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/StartScene.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/StartScene.java index 32e48b577..385df840b 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/StartScene.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/StartScene.java @@ -202,6 +202,10 @@ public class StartScene extends PixelScene { depth.resetColor(); level.resetColor(); } + + if (info.customSeed){ + steps.hardlight(1f, 1.5f, 0.67f); + } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/Icons.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/Icons.java index b07f0ea5f..84ca5e424 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/Icons.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/Icons.java @@ -22,6 +22,7 @@ package com.shatteredpixel.shatteredpixeldungeon.ui; import com.shatteredpixel.shatteredpixeldungeon.Assets; +import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroClass; import com.shatteredpixel.shatteredpixeldungeon.levels.Level; import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene; @@ -60,6 +61,7 @@ public enum Icons { CHALLENGE_ON, RENAME_OFF, RENAME_ON, + SEED, LEFTARROW, RIGHTARROW, @@ -92,7 +94,7 @@ public enum Icons { SLEEP, ALERT, LOST, - DEPTH, + DEPTH, //depth icons have two variants, for regular and seeded runs DEPTH_CHASM, DEPTH_WATER, DEPTH_GRASS, @@ -203,11 +205,14 @@ public enum Icons { case RENAME_ON: icon.frame( icon.texture.uvRectBySize( 176, 32, 15, 14 ) ); break; + case SEED: + icon.frame( icon.texture.uvRectBySize( 192, 32, 10, 10 ) ); + break; case LEFTARROW: - icon.frame( icon.texture.uvRectBySize( 192, 32, 14, 8 ) ); + icon.frame( icon.texture.uvRectBySize( 208, 32, 14, 8 ) ); break; case RIGHTARROW: - icon.frame( icon.texture.uvRectBySize( 208, 32, 14, 8 ) ); + icon.frame( icon.texture.uvRectBySize( 224, 32, 14, 8 ) ); break; case UNCHECKED: @@ -290,31 +295,31 @@ public enum Icons { icon.frame( icon.texture.uvRectBySize( 40, 72, 8, 8 ) ); break; case DEPTH: - icon.frame( icon.texture.uvRectBySize( 48, 64, 6, 7 ) ); + icon.frame( icon.texture.uvRectBySize( 48, 64 + (Dungeon.usingCustomSeed ? 8 : 0), 6, 7 ) ); break; case DEPTH_CHASM: - icon.frame( icon.texture.uvRectBySize( 56, 64, 7, 7 ) ); + icon.frame( icon.texture.uvRectBySize( 56, 64 + (Dungeon.usingCustomSeed ? 8 : 0), 7, 7 ) ); break; case DEPTH_WATER: - icon.frame( icon.texture.uvRectBySize( 64, 64, 7, 7 ) ); + icon.frame( icon.texture.uvRectBySize( 64, 64 + (Dungeon.usingCustomSeed ? 8 : 0), 7, 7 ) ); break; case DEPTH_GRASS: - icon.frame( icon.texture.uvRectBySize( 72, 64, 7, 7 ) ); + icon.frame( icon.texture.uvRectBySize( 72, 64 + (Dungeon.usingCustomSeed ? 8 : 0), 7, 7 ) ); break; case DEPTH_DARK: - icon.frame( icon.texture.uvRectBySize( 80, 64, 7, 7 ) ); + icon.frame( icon.texture.uvRectBySize( 80, 64 + (Dungeon.usingCustomSeed ? 8 : 0), 7, 7 ) ); break; case DEPTH_LARGE: - icon.frame( icon.texture.uvRectBySize( 88, 64, 7, 7 ) ); + icon.frame( icon.texture.uvRectBySize( 88, 64 + (Dungeon.usingCustomSeed ? 8 : 0), 7, 7 ) ); break; case DEPTH_TRAPS: - icon.frame( icon.texture.uvRectBySize( 96, 64, 7, 7 ) ); + icon.frame( icon.texture.uvRectBySize( 96, 64 + (Dungeon.usingCustomSeed ? 8 : 0), 7, 7 ) ); break; case DEPTH_SECRETS: - icon.frame( icon.texture.uvRectBySize( 104, 64, 7, 7 ) ); + icon.frame( icon.texture.uvRectBySize( 104, 64 + (Dungeon.usingCustomSeed ? 8 : 0), 7, 7 ) ); break; case CHAL_COUNT: - icon.frame( icon.texture.uvRectBySize( 48, 72, 7, 7 ) ); + icon.frame( icon.texture.uvRectBySize( 112, 64, 7, 7 ) ); break; case LIBGDX: diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/WndTextInput.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/WndTextInput.java index 26ab4b717..525ef1817 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/WndTextInput.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/WndTextInput.java @@ -21,7 +21,6 @@ package com.shatteredpixel.shatteredpixeldungeon.ui; -import com.badlogic.gdx.scenes.scene2d.ui.TextButton; import com.shatteredpixel.shatteredpixeldungeon.Chrome; import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene; import com.watabou.noosa.TextInput; @@ -29,14 +28,14 @@ import com.watabou.utils.DeviceCompat; public class WndTextInput extends Window { - private static final int WIDTH = 120; - private static final int W_LAND_MULTI = 200; //in the specific case of multiline in landscape + private static final int WIDTH = 130; + private static final int W_LAND_EXTRA = 200; //extra width is sometimes used in landscape private static final int MARGIN = 2; private static final int BUTTON_HEIGHT = 16; private TextInput textBox; - public WndTextInput(final String title, final String initialValue, final int maxLength, + public WndTextInput(final String title, final String body, final String initialValue, final int maxLength, final boolean multiLine, final String posTxt, final String negTxt) { super(); @@ -50,17 +49,32 @@ public class WndTextInput extends Window { } final int width; - if (PixelScene.landscape() && multiLine) { - width = W_LAND_MULTI; //more editing space for landscape users + if (PixelScene.landscape() && (multiLine || body != null)) { + width = W_LAND_EXTRA; //more space for landscape users } else { width = WIDTH; } - final RenderedTextBlock txtTitle = PixelScene.renderTextBlock(title, 9); - txtTitle.maxWidth(width); - txtTitle.hardlight(Window.TITLE_COLOR); - txtTitle.setPos((width - txtTitle.width()) / 2, 2); - add(txtTitle); + float pos = 2; + + if (title != null) { + final RenderedTextBlock txtTitle = PixelScene.renderTextBlock(title, 9); + txtTitle.maxWidth(width); + txtTitle.hardlight(Window.TITLE_COLOR); + txtTitle.setPos((width - txtTitle.width()) / 2, 2); + add(txtTitle); + + pos = txtTitle.bottom() + 2 * MARGIN; + } + + if (body != null) { + final RenderedTextBlock txtBody = PixelScene.renderTextBlock(body, 6); + txtBody.maxWidth(width); + txtBody.setPos(0, pos); + add(txtBody); + + pos = txtBody.bottom() + 2 * MARGIN; + } int textSize = (int)PixelScene.uiCamera.zoom * (multiLine ? 6 : 9); textBox = new TextInput(Chrome.get(Chrome.Type.TOAST_WHITE), multiLine, textSize){ @@ -74,8 +88,6 @@ public class WndTextInput extends Window { if (initialValue != null) textBox.setText(initialValue); textBox.setMaxLength(maxLength); - float pos = txtTitle.bottom() + 2 * MARGIN; - //sets different height depending on whether this is a single or multi line input. final float inputHeight; if (multiLine) { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/utils/DungeonSeed.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/utils/DungeonSeed.java index 97f872813..9e49df047 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/utils/DungeonSeed.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/utils/DungeonSeed.java @@ -26,7 +26,7 @@ import com.watabou.utils.Random; //This class defines the parameters for seeds in ShatteredPD and contains a few convenience methods public class DungeonSeed { - private static long TOTAL_SEEDS = 5429503678976L; //26^9 possible seeds + public static long TOTAL_SEEDS = 5429503678976L; //26^9 possible seeds public static long randomSeed(){ return Random.Long( TOTAL_SEEDS ); @@ -41,8 +41,12 @@ public class DungeonSeed { //Takes a seed code (@@@@@@@@@) and converts it to the equivalent long value public static long convertFromCode( String code ){ - if (code.length() != 9) + //ignore whitespace characters and dashes + code = code.replaceAll("[-\\s]", ""); + + if (code.length() != 9) { throw new IllegalArgumentException("codes must be 9 A-Z characters."); + } long result = 0; for (int i = 8; i >= 0; i--) { @@ -57,12 +61,13 @@ public class DungeonSeed { //Takes a long value and converts it to the equivalent seed code public static String convertToCode( long seed ){ - if (seed < 0 || seed >= TOTAL_SEEDS) + if (seed < 0 || seed >= TOTAL_SEEDS) { throw new IllegalArgumentException("seeds must be within the range [0, TOTAL_SEEDS)"); + } //this almost gives us the right answer, but its 0-p instead of A-Z String interrim = Long.toString(seed, 26); - String result = ""; + StringBuilder result = new StringBuilder(); //so we convert for (int i = 0; i < 9; i++) { @@ -72,21 +77,42 @@ public class DungeonSeed { if (c <= '9') c += 17; //convert 0-9 to A-J else c -= 22; //convert a-p to K-Z - result += c; + result.append(c); } else { - result = 'A' + result; //pad with A (zeroes) until we reach length of 9 + result.insert(0, 'A'); //pad with A (zeroes) until we reach length of 9 } } - return result; + //insert dashes for readability + result.insert(3, '-'); + result.insert(7, '-'); + + return result.toString(); } - //Using this we can let users input 'fun' plaintext seeds and convert them to a long equivalent. - // This is basically the same as string.hashcode except with long, and accounting for overflow - // to ensure the produced seed is always in the range [0, TOTAL_SEEDS) + //Creates a seed from arbitrary user text input public static long convertFromText( String inputText ){ + if (inputText.isEmpty()) return -1; + + //First see if input is a seed code, use that format if it is + try { + return convertFromCode(inputText); + } catch (IllegalArgumentException e){ + + } + + //Then see if input is a number (ignoring spaces), if so parse as a long seed (with overflow) + try { + return Long.parseLong(inputText.replaceAll("\\s", "")) % TOTAL_SEEDS; + } catch (NumberFormatException e){ + + } + + //Finally, if the user has entered unformatted text, convert it to a long seed equivalent + // This is basically the same as string.hashcode except with long, and overflow + // this lets the user input 'fun' seeds, like names or places long total = 0; for (char c : inputText.toCharArray()){ total = 31 * total + c; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndGameInProgress.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndGameInProgress.java index 6e2a85d04..996c00ee2 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndGameInProgress.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndGameInProgress.java @@ -36,6 +36,7 @@ import com.shatteredpixel.shatteredpixeldungeon.ui.Icons; import com.shatteredpixel.shatteredpixeldungeon.ui.RedButton; import com.shatteredpixel.shatteredpixeldungeon.ui.RenderedTextBlock; import com.shatteredpixel.shatteredpixeldungeon.ui.Window; +import com.shatteredpixel.shatteredpixeldungeon.utils.DungeonSeed; import com.watabou.noosa.Game; import com.shatteredpixel.shatteredpixeldungeon.ui.Button; import com.watabou.utils.Bundle; @@ -70,23 +71,6 @@ public class WndGameInProgress extends Window { title.setRect( 0, 0, WIDTH, 0 ); add(title); - //manually produces debug information about a run, mainly useful for levelgen errors - Button debug = new Button(){ - @Override - protected boolean onLongClick() { - try { - Bundle bundle = FileUtils.bundleFromFile(GamesInProgress.gameFile(slot)); - ShatteredPixelDungeon.scene().addToFront(new WndMessage("_Debug Info:_\n\n" + - "Version: " + Game.version + " (" + Game.versionCode + ")\n" + - "Seed: " + bundle.getLong("seed") + "\n" + - "Challenge Mask: " + info.challenges)); - } catch (IOException ignored) { } - return true; - } - }; - debug.setRect(0, 0, title.imIcon.width(), title.imIcon.height); - add(debug); - if (info.challenges > 0) GAP -= 2; pos = title.bottom() + GAP; @@ -119,6 +103,11 @@ public class WndGameInProgress extends Window { pos += GAP; statSlot( Messages.get(this, "gold"), info.goldCollected ); statSlot( Messages.get(this, "depth"), info.maxDepth ); + if (info.customSeed){ + statSlot( Messages.get(this, "custom_seed"), "_" + DungeonSeed.convertToCode(info.seed) + "_" ); + } else { + statSlot( Messages.get(this, "dungeon_seed"), DungeonSeed.convertToCode(info.seed) ); + } pos += GAP; @@ -175,7 +164,7 @@ public class WndGameInProgress extends Window { add( txt ); txt = PixelScene.renderTextBlock( value, 8 ); - txt.setPos(WIDTH * 0.6f, pos); + txt.setPos(WIDTH * 0.55f, pos); PixelScene.align(txt); add( txt ); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndHero.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndHero.java index 25f72e18c..7d01ae22a 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndHero.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndHero.java @@ -21,7 +21,6 @@ package com.shatteredpixel.shatteredpixeldungeon.windows; -import com.shatteredpixel.shatteredpixeldungeon.Assets; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon; import com.shatteredpixel.shatteredpixeldungeon.Statistics; @@ -41,12 +40,10 @@ import com.shatteredpixel.shatteredpixeldungeon.ui.StatusPane; import com.shatteredpixel.shatteredpixeldungeon.ui.TalentButton; import com.shatteredpixel.shatteredpixeldungeon.ui.TalentsPane; import com.shatteredpixel.shatteredpixeldungeon.ui.Window; -import com.watabou.gltextures.SmartTexture; -import com.watabou.gltextures.TextureCache; +import com.shatteredpixel.shatteredpixeldungeon.utils.DungeonSeed; import com.watabou.noosa.Gizmo; import com.watabou.noosa.Group; import com.watabou.noosa.Image; -import com.watabou.noosa.TextureFilm; import com.watabou.noosa.ui.Component; import java.util.ArrayList; @@ -188,6 +185,11 @@ public class WndHero extends WndTabbed { statSlot( Messages.get(this, "gold"), Statistics.goldCollected ); statSlot( Messages.get(this, "depth"), Statistics.deepestFloor ); + if (Dungeon.usingCustomSeed){ + statSlot( Messages.get(this, "custom_seed"), "_" + DungeonSeed.convertToCode(Dungeon.seed) + "_" ); + } else { + statSlot( Messages.get(this, "dungeon_seed"), DungeonSeed.convertToCode(Dungeon.seed) ); + } pos += GAP; } @@ -199,7 +201,7 @@ public class WndHero extends WndTabbed { add( txt ); txt = PixelScene.renderTextBlock( value, 8 ); - txt.setPos(WIDTH * 0.6f, pos); + txt.setPos(WIDTH * 0.55f, pos); PixelScene.align(txt); add( txt ); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndRanking.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndRanking.java index 304dc2a1d..1f6730f2a 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndRanking.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndRanking.java @@ -330,7 +330,7 @@ public class WndRanking extends WndTabbed { camera = WndRanking.this.camera; Component badges; - if (Badges.unlocked(false) <= 7){ + if (Badges.totalUnlocked(false) <= 7){ badges = new BadgesList(false); } else { badges = new BadgesGrid(false);