diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 5c9f819..336707d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -20,8 +20,8 @@ body: **Version format:** v[mod_version]+[mod_loader]-[minecraft_version] - **Version example:** v5.0+fabric-1.26 - **Title example:** [v5.3.1+neoforge-1.26.2] Red "Do not click" button + **Version example:** v5.0+fabric-26 + **Title example:** [v5.3.1+neoforge-26.1.2] Red "Do not click" button crashes the game when clicked - type: textarea id: description @@ -69,9 +69,8 @@ body: mod-pack. This helps project maintainers reproduce the issue, as other mods often can and do cause issues. placeholder: >- - Name a list of mods or link a mod-pack here. Please do not expect - project maintainers to manually download and install like 300 separate - mods. + Name a list of mods or link a mod-pack here. To rule out mod conflicts, it is + advised to reproduce the issue without other mods present (except for dependencies). validations: required: true - type: checkboxes diff --git a/gradle.properties b/gradle.properties index 4893f6e..622268b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ maven.group = com.thecsdev # Mod properties mod.id = betterstats -mod.version = 5.4.2 +mod.version = 5.5.0 mod.name = Better Statistics Screen mod.description = A Minecraft mod that improves the statistics screen and makes it more useful. mod.author = TheCSDev @@ -48,7 +48,7 @@ fabric.api_version = 0.152.1+26.2 fabric.json.depends.minecraft = ~26.2 fabric.json.depends.fabricloader = >=0.19.3 fabric.json.depends.fabric-api = >=0.152.1 - fabric.json.depends.tcdcommons = >=5.4.1 + fabric.json.depends.tcdcommons = >=5.5.0 # Neo-Forge properties neoforge.version = 26.1.0.7-beta @@ -57,4 +57,4 @@ neoforge.version = 26.1.0.7-beta net.toml.loaderVersion = [4,) neo.toml.dependencies.neoforge = [26.2.0.1-beta,) neo.toml.dependencies.minecraft = [26.2, 26.3) - neo.toml.dependencies.tcdcommons = [5.4.1,) + neo.toml.dependencies.tcdcommons = [5.5.0,) diff --git a/src/main/java/com/thecsdev/betterstats/api/mcbs/controller/tab/McbsEditorFileTab.java b/src/main/java/com/thecsdev/betterstats/api/mcbs/controller/tab/McbsEditorFileTab.java index e9c04bc..3e19f6d 100644 --- a/src/main/java/com/thecsdev/betterstats/api/mcbs/controller/tab/McbsEditorFileTab.java +++ b/src/main/java/com/thecsdev/betterstats/api/mcbs/controller/tab/McbsEditorFileTab.java @@ -155,7 +155,6 @@ public final void setCurrentView(@NotNull StatsView view) throws NullPointerExce /** * Puts an {@link McbsGoal} into {@link McbsFile#getGoals()}. * @param goal The {@link McbsGoal} to add. - * @return {@code true} if the {@link McbsFile} did not already contain the goal. * @throws NullPointerException If the argument is {@code null}. */ public final void putGoal(@NotNull McbsGoal goal) throws NullPointerException diff --git a/src/main/java/com/thecsdev/betterstats/api/mcbs/model/McbsStats.java b/src/main/java/com/thecsdev/betterstats/api/mcbs/model/McbsStats.java index 3fc7670..f1d319b 100644 --- a/src/main/java/com/thecsdev/betterstats/api/mcbs/model/McbsStats.java +++ b/src/main/java/com/thecsdev/betterstats/api/mcbs/model/McbsStats.java @@ -37,14 +37,15 @@ private McbsStats( // ================================================== /** * Clears redundant statistic data like zero-value entries and empty maps. + * @apiNote Costly operation. Avoid frequent calls! */ private final @ApiStatus.Internal void cleanUp() { //remove zero-value statistic entries - for(final var statTypeEntry : this.intStats.entrySet()) - statTypeEntry.getValue().entrySet().removeIf(e -> e.getValue() == 0); + for(final var statTypeEntry : this.intStats.values()) + statTypeEntry.values().removeIf(e -> e == 0); //remove empty stat-type maps - this.intStats.entrySet().removeIf(e -> e.getValue().isEmpty()); + this.intStats.values().removeIf(ConcurrentHashMap::isEmpty); } // ================================================== /** @@ -52,7 +53,6 @@ private McbsStats( * integer-based statistics values. */ public final @NotNull ConcurrentHashMap> getIntValues() { - cleanUp(); return this.intStats; } @@ -66,8 +66,7 @@ private McbsStats( @NotNull Identifier type) throws NullPointerException { Objects.requireNonNull(type); - cleanUp(); - return this.intStats.computeIfAbsent(type, __ -> new ConcurrentHashMap<>()); + return this.intStats.computeIfAbsent(type, _ -> new ConcurrentHashMap<>()); } // ================================================== /** @@ -226,7 +225,6 @@ public final void clearAndAddAll(@NotNull IStatsProvider statsProvider) throws N public final void forEach(@NotNull McbsStats.IntValueConsumer consumer) throws NullPointerException { Objects.requireNonNull(consumer); - cleanUp(); for(final var statTypeEntry : this.intStats.entrySet()) { final var statTypeId = statTypeEntry.getKey(); for(final var statSubjectEntry : statTypeEntry.getValue().entrySet()) { @@ -274,6 +272,8 @@ public final void forEach(@NotNull McbsStats.IntValueConsumer consumer) throws N * {@link Codec} implementation for {@link McbsStats}. */ @ApiStatus.Internal - static final Codec CODEC = CODEC_INT_STATS.xmap(McbsStats::new, McbsStats::getIntValues); + static final Codec CODEC = CODEC_INT_STATS.xmap( + McbsStats::new, + stats -> { stats.cleanUp(); return stats.getIntValues(); }); // ================================================== ================================================== } diff --git a/src/main/java/com/thecsdev/betterstats/client/BetterStatsClient.java b/src/main/java/com/thecsdev/betterstats/client/BetterStatsClient.java index 92e7ba0..bb28b86 100644 --- a/src/main/java/com/thecsdev/betterstats/client/BetterStatsClient.java +++ b/src/main/java/com/thecsdev/betterstats/client/BetterStatsClient.java @@ -41,6 +41,36 @@ public BetterStatsClient() //keep track of last login time ClientEvent.PLAYER_JOIN.addListener(_ -> LAST_LOGIN_TIME = System.currentTimeMillis()); ClientEvent.PLAYER_QUIT.addListener(_ -> LAST_LOGIN_TIME = System.currentTimeMillis()); + + //FIXME - REMOVE TEST CODE THAT POPULATES STATS WITH ARBITRARY NUMBERS: + /* + ClientEvent.PLAYER_JOIN.addListener(_ -> + { + //obtain singleplayer server + final var server = Minecraft.getInstance().getSingleplayerServer(); + if(server == null) return; + + //continue execution on server-side + server.execute(() -> + { + //obtain the first player (who is usually the main player) + final var player = server.getPlayerList().getPlayers().stream().findFirst().orElse(null); + if(player == null) return; + final var stats = player.getStats(); + final var rng = new Random(); + + //noinspection unchecked | award every single stat in the game to the player + for(final var statType : (Registry>)(Object) BuiltInRegistries.STAT_TYPE) + { + //skip "custom/general" statistics + if(((Object) statType) == Stats.CUSTOM) continue; + //populate all other stat types + for(final var statSubject : statType.getRegistry()) + stats.setValue(player, statType.get(statSubject), rng.nextInt(0, 1024)); + } + }); + }); + */ } // ================================================== /** diff --git a/src/main/java/com/thecsdev/betterstats/client/gui/panel/PersonalHomePanel.java b/src/main/java/com/thecsdev/betterstats/client/gui/panel/PersonalHomePanel.java index d8091bd..effc568 100644 --- a/src/main/java/com/thecsdev/betterstats/client/gui/panel/PersonalHomePanel.java +++ b/src/main/java/com/thecsdev/betterstats/client/gui/panel/PersonalHomePanel.java @@ -23,10 +23,7 @@ import com.thecsdev.commonmc.api.client.gui.widget.TButtonWidget; import com.thecsdev.commonmc.api.client.gui.widget.TClickableWidget; import com.thecsdev.commonmc.api.client.gui.widget.stats.TTextualStatWidget; -import com.thecsdev.commonmc.api.stats.util.BlockStats; -import com.thecsdev.commonmc.api.stats.util.CustomStat; -import com.thecsdev.commonmc.api.stats.util.EntityStats; -import com.thecsdev.commonmc.api.stats.util.ItemStats; +import com.thecsdev.commonmc.api.stats.util.*; import com.thecsdev.commonmc.api.util.modinfo.ModInfoProvider; import com.thecsdev.commonmc.resource.TComponent; import com.thecsdev.commonmc.resource.TLanguage; @@ -38,15 +35,11 @@ import net.minecraft.resources.Identifier; import net.minecraft.stats.StatFormatter; import net.minecraft.stats.Stats; -import net.minecraft.world.entity.MobCategory; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import java.util.concurrent.Future; import java.util.function.Consumer; @@ -175,38 +168,13 @@ else if(stat.isTime()) ibmPanel.setBounds(computeNextYBounds(255, GAP)); //featured item statistics - final var items = ItemStats.getItemStats(lpStats, null, null).stream() - .sorted(Comparator.comparingDouble( - (ItemStats obj) -> obj.getValues().values().stream() - .mapToInt(Integer::intValue) - .average() - .orElse(0.0)).reversed()) - .limit(7) - .toList(); - StatsViewUtils.initItemStats(ibmPanel, null, items); + StatsViewUtils.initItemStats(ibmPanel, null, getTopStats(ItemStats.getItemStats(lpStats, null, null), 7)); //featured block statistics - final var blocks = BlockStats.getBlockStats(lpStats, null, null).stream() - .sorted(Comparator.comparingDouble( - (BlockStats obj) -> obj.getValues().values().stream() - .mapToInt(Integer::intValue) - .average() - .orElse(0.0)).reversed()) - .limit(7) - .toList(); - StatsViewUtils.initBlockStats(ibmPanel, null, blocks); + StatsViewUtils.initBlockStats(ibmPanel, null, getTopStats(BlockStats.getBlockStats(lpStats, null, null), 7)); //featured mob statistics - final var entities = EntityStats.getEntityStats(lpStats, null, null).stream() - .filter(es -> es.getSubject().getCategory() != MobCategory.MISC) - .sorted(Comparator.comparingDouble( - (EntityStats obj) -> obj.getValues().values().stream() - .mapToInt(Integer::intValue) - .average() - .orElse(0.0)).reversed()) - .limit(5) - .toList(); - StatsViewUtils.initMobStats(ibmPanel, null, entities); + StatsViewUtils.initMobStats(ibmPanel, null, getTopStats(EntityStats.getEntityStats(lpStats, null, null), 5)); //and finally, center that panel and add it to this panel ibmPanel.setBounds(ibmPanel.getContentBounds()); @@ -238,6 +206,56 @@ private final void initNewsAsync() lbl_group.textScaleProperty().set(1.1d, PersonalHomePanel.class); news.forEach(section -> CreditsPanel.initSection(this, section)); } + // ================================================== + /** + * Returns a new {@link Collection} containing the "top/largest" stats from another + * collection of stats. + * @param origin The origin {@link Collection}. + * @param size The size of the returned {@link Collection}. + * @throws NullPointerException If the argument is {@code null}. + * @throws IllegalArgumentException If {@code size < 1}. + * @implNote Uses an optimized sorting algorithm that avoids wasteful sort operations. + */ + private static final > @NotNull Collection getTopStats( + @NotNull Collection origin, int size) + throws NullPointerException, IllegalArgumentException + { + //check arguments + Objects.requireNonNull(origin); + if(origin.isEmpty() || size < 1) return new ArrayList<>(); + + //define the priority queue that'll be used for stat ranking + record RankedStat(S stat, int weight) {} + final var queue = new PriorityQueue>(size, Comparator.comparingInt(RankedStat::weight)); + + //iterate stats from origin with a single pass + for(final var stat : origin) + { + //calculate total "weight" the current stat is worth + int weight = 0; + for(final int statVal : stat.getValues().values()) weight += statVal; + + //if the queue is full, check if the current item can fit in + if(queue.size() == size) { + //a '0' definitely will not fit in the queue + if(weight == 0) continue; + //compare the score against the lowest rank in the queue + final var lowestRank = queue.peek(); + if(lowestRank != null && weight <= lowestRank.weight) + continue; //skip current stat if it ranks below lowest rank + } + + //add to queue + queue.add(new RankedStat<>(stat, weight)); + if(queue.size() > size) queue.poll(); + } + + //return a sorted list containing the ranked elements + final var stats = new ArrayList(size); + while(stats.size() < size && !queue.isEmpty()) + stats.add(queue.poll().stat); + return stats.reversed(); + } // ================================================== ================================================== // ModSummaryPanel IMPLEMENTATION // ================================================== ================================================== diff --git a/src/main/resources/META-INF/jarjar/tcdcommons-5.4.1+fn-26.2.jar b/src/main/resources/META-INF/jarjar/tcdcommons-5.5.0+fn-26.2.jar similarity index 91% rename from src/main/resources/META-INF/jarjar/tcdcommons-5.4.1+fn-26.2.jar rename to src/main/resources/META-INF/jarjar/tcdcommons-5.5.0+fn-26.2.jar index bde03a7..a748961 100644 Binary files a/src/main/resources/META-INF/jarjar/tcdcommons-5.4.1+fn-26.2.jar and b/src/main/resources/META-INF/jarjar/tcdcommons-5.5.0+fn-26.2.jar differ