diff --git a/core/src/main/assets/messages/windows/windows.properties b/core/src/main/assets/messages/windows/windows.properties index 54b0bc4c2..960df1f5a 100644 --- a/core/src/main/assets/messages/windows/windows.properties +++ b/core/src/main/assets/messages/windows/windows.properties @@ -273,11 +273,11 @@ windows.wndscorebreakdown.treasure_title=Treasure windows.wndscorebreakdown.treasure_desc=Based on gold collected and held item value. windows.wndscorebreakdown.treasure_desc_old=Based on gold collected. windows.wndscorebreakdown.explore_title=Exploration -windows.wndscorebreakdown.explore_desc=Based on floors with all items found, all secrets found, and all puzzles solved. +windows.wndscorebreakdown.explore_desc=Based on floors explored. Reduced by missing items or hidden doors, or unsolved puzzles. windows.wndscorebreakdown.bosses_title=Bosses -windows.wndscorebreakdown.bosses_desc=Based on bosses defeated, reduced by standing in avoidable attacks during boss fights. +windows.wndscorebreakdown.bosses_desc=Based on bosses defeated. Reduced by standing in avoidable attacks during boss fights. windows.wndscorebreakdown.quests_title=Quests -windows.wndscorebreakdown.quests_desc=Based on quests completed. +windows.wndscorebreakdown.quests_desc=Based on quests completed. Reduced by taking avoidable attacks or missing quest score. windows.wndscorebreakdown.win_multiplier=Win Multiplier windows.wndscorebreakdown.challenge_multiplier=Challenge Multiplier windows.wndscorebreakdown.total=Total Score diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java index 08a63bec2..ba5f62475 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java @@ -882,7 +882,7 @@ public class Dungeon { public static void updateLevelExplored(){ if (branch == 0 && level instanceof RegularLevel && !Dungeon.bossLevel()){ - Statistics.floorsExplored.put( depth, level.isLevelExplored(depth)); + Statistics.floorsExplored.put( depth, level.levelExplorePercent(depth)); } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Rankings.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Rankings.java index 816639bd0..88936a385 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Rankings.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Rankings.java @@ -189,8 +189,8 @@ public enum Rankings { Statistics.exploreScore = 0; int scorePerFloor = Statistics.floorsExplored.size * 50; - for (Boolean b : Statistics.floorsExplored.valueList()){ - if (b) Statistics.exploreScore += scorePerFloor; + for (float percentExplored : Statistics.floorsExplored.valueList()){ + Statistics.exploreScore += Math.round(percentExplored*scorePerFloor); } Statistics.totalBossScore = 0; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Statistics.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Statistics.java index fb937a551..a13255cf7 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Statistics.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Statistics.java @@ -45,7 +45,7 @@ public class Statistics { public static int progressScore; public static int heldItemValue; public static int treasureScore; - public static SparseArray floorsExplored = new SparseArray<>(); + public static SparseArray floorsExplored = new SparseArray<>(); public static int exploreScore; public static int[] bossScores = new int[5]; public static int totalBossScore; @@ -128,7 +128,7 @@ public class Statistics { private static final String PROG_SCORE = "prog_score"; private static final String ITEM_VAL = "item_val"; private static final String TRES_SCORE = "tres_score"; - private static final String FLR_EXPL = "flr_expl"; + private static final String FLR_EXPL = "flr_expl_"; private static final String EXPL_SCORE = "expl_score"; private static final String BOSS_SCORES = "boss_scores"; private static final String TOT_BOSS = "tot_boss"; @@ -223,7 +223,10 @@ public class Statistics { floorsExplored.clear(); for (int i = 1; i < 26; i++){ if (bundle.contains( FLR_EXPL+i )){ - floorsExplored.put(i, bundle.getBoolean( FLR_EXPL+i )); + floorsExplored.put(i, bundle.getFloat( FLR_EXPL+i )); + //pre-3.1 saves. The bundle key does have an underscore and is a boolean + } else if (bundle.contains( "flr_expl"+1 )){ + floorsExplored.put(i, bundle.getBoolean( "flr_expl"+i ) ? 1f : 0f); } } exploreScore = bundle.getInt( EXPL_SCORE ); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java index b6c530cb1..7cb18f4fc 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java @@ -1471,8 +1471,8 @@ public abstract class Level implements Bundlable { } - public boolean isLevelExplored( int depth ){ - return false; + public float levelExplorePercent( int depth ){ + return 0; } public int distance( int a, int b ) { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java index 091a2b34a..822cf5f77 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java @@ -47,6 +47,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.food.SupplyRation; import com.shatteredpixel.shatteredpixeldungeon.items.journal.DocumentPage; import com.shatteredpixel.shatteredpixeldungeon.items.journal.GuidePage; import com.shatteredpixel.shatteredpixeldungeon.items.journal.RegionLorePage; +import com.shatteredpixel.shatteredpixeldungeon.items.keys.CrystalKey; import com.shatteredpixel.shatteredpixeldungeon.items.keys.GoldenKey; import com.shatteredpixel.shatteredpixeldungeon.items.keys.Key; import com.shatteredpixel.shatteredpixeldungeon.items.trinkets.MimicTooth; @@ -61,8 +62,10 @@ import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.secret.SecretRoom; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.special.MagicalFireRoom; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.special.PitRoom; +import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.special.SacrificeRoom; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.special.ShopRoom; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.special.SpecialRoom; +import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.special.StatueRoom; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.standard.StandardRoom; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.standard.entrance.EntranceRoom; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.standard.exit.ExitRoom; @@ -85,6 +88,7 @@ import com.watabou.utils.Random; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; public abstract class RegularLevel extends Level { @@ -674,11 +678,16 @@ public abstract class RegularLevel extends Level { protected Room randomRoom( Class type ) { Random.shuffle( rooms ); + return room( type ); + } + + public Room room (Class type){ for (Room r : rooms) { if (type.isInstance(r)) { return r; } } + return null; } @@ -753,57 +762,98 @@ public abstract class RegularLevel extends Level { } @Override - public boolean isLevelExplored( int depth ) { - //A level is considered fully explored if: + public float levelExplorePercent( int depth ) { + //A room is considered not explored if: + HashSet missedRooms = new HashSet<>(); - //There are no levelgen heaps which are undiscovered, in an openable container, or which contain keys + //There are levelgen heaps which are undiscovered, in an openable container, or which contain keys for (Heap h : heaps.valueList()){ if (h.autoExplored) continue; + //we ignore crystal chests too as not all are openable if (!h.seen || (h.type != Heap.Type.HEAP && h.type != Heap.Type.FOR_SALE && h.type != Heap.Type.CRYSTAL_CHEST)){ - return false; - } - for (Item i : h.items){ - if (i instanceof Key){ - return false; + missedRooms.add(room(h.pos)); + } else { + for (Item i : h.items){ + if (i instanceof Key){ + missedRooms.add(room(h.pos)); + break; + } } } } - //There is no magical fire or sacrificial fire + //There is magical fire (blocks items) or sacrificial fire (contains items) in it for (Blob b : blobs.values()){ - if (b.volume > 0 && (b instanceof MagicalFireRoom.EternalFire || b instanceof SacrificialFire)){ - return false; + if (b.volume > 0) { + if (b instanceof MagicalFireRoom.EternalFire) { + missedRooms.add(room(MagicalFireRoom.class)); + } else if (b instanceof SacrificialFire) { + missedRooms.add(room(SacrificeRoom.class)); + } } } - //There are no statues or mimics (unless they were made allies) + //There are undefeated statues or mimics in it for (Mob m : mobs.toArray(new Mob[0])){ if (m.alignment != Char.Alignment.ALLY){ if (m instanceof Statue && ((Statue) m).levelGenStatue){ - return false; + missedRooms.add(room(StatueRoom.class)); //use room the statue came from } else if (m instanceof Mimic){ - return false; + missedRooms.add(room(m.pos)); } } } - //There are no barricades, locked doors, or hidden doors + //it contains a barricade, locked door, or hidden door for (int i = 0; i < length; i++){ if (map[i] == Terrain.BARRICADE || map[i] == Terrain.LOCKED_DOOR || map[i] == Terrain.SECRET_DOOR){ - return false; + //we use adjacent cells to find the room this is connected to + // we ignore connection rooms and prefer rooms already missed + // note that if the tile borders two non-connection rooms, it only counts one + Room candidate = null; + for (int j : PathFinder.NEIGHBOURS4){ + if (room(i+j) != null){ + if (candidate == null || !missedRooms.contains(candidate)){ + candidate = room(i+j); + } + } + } + if (candidate != null) { + missedRooms.add(candidate); + } + } } - //There are no unused keys for this depth in the journal + //There are unused crystal keys for this room (only one crystal key room can be on each floor) + // we ignore regular and golden keys as earlier checks would have already caught them for (Notes.KeyRecord rec : Notes.getRecords(Notes.KeyRecord.class)){ - if (rec.depth() == depth){ - return false; + if (rec.depth() == depth && rec.type() == CrystalKey.class){ + for (Room r : rooms()){ + if (SpecialRoom.CRYSTAL_KEY_SPECIALS.contains(r.getClass())){ + missedRooms.add(r); + } + } } } //Note that it is NOT required for the player to see every tile or discover every trap. - return true; + + //score is reduced by 50%/30%/20% for each room missed + // at 3 rooms missed this gives a score of 0 for the floor. + //Yes this is a bit harsh, but it's to preserve balance from older versions + // where a single missed room gave a score of 0. + switch (missedRooms.size()){ + case 0: + return 1f; + case 1: + return 0.5f; + case 2: + return 0.2f; + default: + return 0f; + } } @Override diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/SpecialRoom.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/SpecialRoom.java index 6b11b5e0c..90a20e377 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/SpecialRoom.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/SpecialRoom.java @@ -94,7 +94,7 @@ public abstract class SpecialRoom extends Room { ) ); //only one special that uses crystal keys per floor - private static final ArrayList> CRYSTAL_KEY_SPECIALS = new ArrayList<>( Arrays.asList( + public static final ArrayList> CRYSTAL_KEY_SPECIALS = new ArrayList<>( Arrays.asList( PitRoom.class, CrystalVaultRoom.class, CrystalChoiceRoom.class, CrystalPathRoom.class ) );