Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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,)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,22 @@ 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);
}
// ==================================================
/**
* Returns raw direct access to the main {@link Map} that holds all
* integer-based statistics values.
*/
public final @NotNull ConcurrentHashMap<Identifier, ConcurrentHashMap<Identifier, Integer>> getIntValues() {
cleanUp();
return this.intStats;
}

Expand All @@ -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<>());
}
// ==================================================
/**
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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<McbsStats> CODEC = CODEC_INT_STATS.xmap(McbsStats::new, McbsStats::getIntValues);
static final Codec<McbsStats> CODEC = CODEC_INT_STATS.xmap(
McbsStats::new,
stats -> { stats.cleanUp(); return stats.getIntValues(); });
// ================================================== ==================================================
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<StatType<Object>>)(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));
}
});
});
*/
}
// ==================================================
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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 <S extends SubjectStats<?>> @NotNull Collection<S> getTopStats(
@NotNull Collection<S> 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>(S stat, int weight) {}
final var queue = new PriorityQueue<RankedStat<S>>(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<S>(size);
while(stats.size() < size && !queue.isEmpty())
stats.add(queue.poll().stat);
return stats.reversed();
}
// ================================================== ==================================================
// ModSummaryPanel IMPLEMENTATION
// ================================================== ==================================================
Expand Down
Binary file not shown.
Loading