package net.minecraft.client.gui.screens.achievement; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.Set; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.AbstractSelectionList; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.ContainerObjectSelectionList; import net.minecraft.client.gui.components.ObjectSelectionList; import net.minecraft.client.gui.components.Tooltip; import net.minecraft.client.gui.components.tabs.GridLayoutTab; import net.minecraft.client.gui.components.tabs.LoadingTab; import net.minecraft.client.gui.components.tabs.MenuTabBar; import net.minecraft.client.gui.components.tabs.TabManager; import net.minecraft.client.gui.layouts.HeaderAndFooterLayout; import net.minecraft.client.gui.navigation.ScreenRectangle; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.achievement.StatsScreen.ItemStatisticsList.HeaderEntry; import net.minecraft.client.gui.screens.achievement.StatsScreen.ItemStatisticsList.ItemRow; import net.minecraft.client.gui.screens.achievement.StatsScreen.ItemStatisticsList.ItemRowComparator; import net.minecraft.client.gui.screens.achievement.StatsScreen.MobsStatisticsList.MobRow; import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen; import net.minecraft.client.input.KeyEvent; import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.client.resources.language.I18n; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ServerboundClientCommandPacket; import net.minecraft.network.protocol.game.ServerboundClientCommandPacket.Action; import net.minecraft.resources.Identifier; import net.minecraft.stats.Stat; import net.minecraft.stats.StatType; import net.minecraft.stats.Stats; import net.minecraft.stats.StatsCounter; import net.minecraft.world.entity.EntityType; import net.minecraft.world.item.Item; import net.minecraft.world.item.Items; import net.minecraft.world.level.block.Block; import org.jspecify.annotations.Nullable; public class StatsScreen extends Screen { private static final Component TITLE = Component.translatable("gui.stats"); private static final Identifier SLOT_SPRITE = Identifier.withDefaultNamespace("container/slot"); private static final Identifier HEADER_SPRITE = Identifier.withDefaultNamespace("statistics/header"); private static final Identifier SORT_UP_SPRITE = Identifier.withDefaultNamespace("statistics/sort_up"); private static final Identifier SORT_DOWN_SPRITE = Identifier.withDefaultNamespace("statistics/sort_down"); private static final Component PENDING_TEXT = Component.translatable("multiplayer.downloadingStats"); private static final Component NO_VALUE_DISPLAY = Component.translatable("stats.none"); private static final Component GENERAL_BUTTON = Component.translatable("stat.generalButton"); private static final Component ITEMS_BUTTON = Component.translatable("stat.itemsButton"); private static final Component MOBS_BUTTON = Component.translatable("stat.mobsButton"); protected final Screen lastScreen; private static final int LIST_WIDTH = 280; private final HeaderAndFooterLayout layout = new HeaderAndFooterLayout(this); private final TabManager tabManager = new TabManager(x$0 -> this.addRenderableWidget(x$0), x$0 -> this.removeWidget(x$0)); @Nullable private MenuTabBar tabNavigationBar; private final StatsCounter stats; private boolean isLoading = true; public StatsScreen(final Screen lastScreen, final StatsCounter stats) { super(TITLE); this.lastScreen = lastScreen; this.stats = stats; } @Override protected void init() { Component loadingTitle = PENDING_TEXT; this.tabNavigationBar = MenuTabBar.builder(this.tabManager, this.width) .addTabs( new LoadingTab(this.getFont(), GENERAL_BUTTON, loadingTitle), new LoadingTab(this.getFont(), ITEMS_BUTTON, loadingTitle), new LoadingTab(this.getFont(), MOBS_BUTTON, loadingTitle) ) .build(); this.addRenderableWidget(this.tabNavigationBar); this.layout.addToFooter(Button.builder(CommonComponents.GUI_DONE, button -> this.onClose()).width(200).build()); this.tabNavigationBar.setTabActiveState(0, true); this.tabNavigationBar.setTabActiveState(1, false); this.tabNavigationBar.setTabActiveState(2, false); this.layout.visitWidgets(button -> { button.setTabOrderGroup(1); this.addRenderableWidget(button); }); this.tabNavigationBar.selectTab(0, false); this.repositionElements(); this.minecraft.getConnection().send(new ServerboundClientCommandPacket(Action.REQUEST_STATS)); } public void onStatsUpdated() { if (this.isLoading) { if (this.tabNavigationBar != null) { this.removeWidget(this.tabNavigationBar); } this.tabNavigationBar = MenuTabBar.builder(this.tabManager, this.width) .addTabs( new StatsScreen.StatisticsTab(GENERAL_BUTTON, new StatsScreen.GeneralStatisticsList(this.minecraft)), new StatsScreen.StatisticsTab(ITEMS_BUTTON, new StatsScreen.ItemStatisticsList(this.minecraft)), new StatsScreen.StatisticsTab(MOBS_BUTTON, new StatsScreen.MobsStatisticsList(this.minecraft)) ) .build(); this.setFocused(this.tabNavigationBar); this.addRenderableWidget(this.tabNavigationBar); this.setTabActiveStateAndTooltip(1); this.setTabActiveStateAndTooltip(2); this.tabNavigationBar.selectTab(0, false); this.repositionElements(); this.isLoading = false; } } private void setTabActiveStateAndTooltip(final int index) { if (this.tabNavigationBar != null) { boolean active = this.tabNavigationBar.getTabs().get(index) instanceof StatsScreen.StatisticsTab statsTab && !statsTab.list.children().isEmpty(); this.tabNavigationBar.setTabActiveState(index, active); if (active) { this.tabNavigationBar.setTabTooltip(index, null); } else { this.tabNavigationBar.setTabTooltip(index, Tooltip.create(Component.translatable("gui.stats.none_found"))); } } } @Override protected void repositionElements() { if (this.tabNavigationBar != null) { this.tabNavigationBar.arrangeElements(this.width); int tabAreaTop = this.tabNavigationBar.getRectangle().bottom(); ScreenRectangle tabArea = new ScreenRectangle(0, tabAreaTop, this.width, this.height - this.layout.getFooterHeight() - tabAreaTop); this.tabNavigationBar.getTabs().forEach(tab -> tab.visitChildren(child -> child.setHeight(tabArea.height()))); this.tabManager.setTabArea(tabArea); this.layout.setHeaderHeight(tabAreaTop); this.layout.arrangeElements(); } } @Override public boolean keyPressed(final KeyEvent event) { return this.tabNavigationBar != null && this.tabNavigationBar.keyPressed(event) ? true : super.keyPressed(event); } @Override public void extractRenderState(final GuiGraphicsExtractor graphics, final int xm, final int ym, final float a) { super.extractRenderState(graphics, xm, ym, a); graphics.blit(RenderPipelines.GUI_TEXTURED, Screen.FOOTER_SEPARATOR, 0, this.height - this.layout.getFooterHeight(), 0.0F, 0.0F, this.width, 2, 32, 2); } @Override protected void extractMenuBackground(final GuiGraphicsExtractor graphics) { graphics.blit(RenderPipelines.GUI_TEXTURED, CreateWorldScreen.TAB_HEADER_BACKGROUND, 0, 0, 0.0F, 0.0F, this.width, this.layout.getHeaderHeight(), 16, 16); this.extractMenuBackground(graphics, 0, this.layout.getHeaderHeight(), this.width, this.height); } @Override public void onClose() { this.minecraft.gui.setScreen(this.lastScreen); } private static String getTranslationKey(final Stat stat) { return "stat." + ((Identifier)stat.getValue()).toString().replace(':', '.'); } private class GeneralStatisticsList extends ObjectSelectionList { public GeneralStatisticsList(final Minecraft minecraft) { Objects.requireNonNull(StatsScreen.this); super(minecraft, StatsScreen.this.width, StatsScreen.this.layout.getContentHeight(), 33, 14); ObjectArrayList> stats = new ObjectArrayList<>(Stats.CUSTOM.iterator()); stats.sort(Comparator.comparing(k -> I18n.get(StatsScreen.getTranslationKey(k)))); for (Stat stat : stats) { this.addEntry(new net.minecraft.client.gui.screens.achievement.StatsScreen.GeneralStatisticsList.Entry(this, stat)); } } @Override public int getRowWidth() { return 280; } @Override protected void extractListBackground(final GuiGraphicsExtractor graphics) { } @Override protected void extractListSeparators(final GuiGraphicsExtractor graphics) { } } private class ItemStatisticsList extends ContainerObjectSelectionList { private static final int SLOT_BG_SIZE = 18; private static final int SLOT_STAT_HEIGHT = 22; private static final int SLOT_BG_Y = 1; private static final int SORT_NONE = 0; private static final int SORT_DOWN = -1; private static final int SORT_UP = 1; protected final List> blockColumns; protected final List> itemColumns; protected final Comparator itemStatSorter; @Nullable protected StatType sortColumn; protected int sortOrder; public ItemStatisticsList(final Minecraft minecraft) { Objects.requireNonNull(StatsScreen.this); super(minecraft, StatsScreen.this.width, StatsScreen.this.layout.getContentHeight(), 33, 22); this.itemStatSorter = new ItemRowComparator(this); this.blockColumns = Lists.>newArrayList(); this.blockColumns.add(Stats.BLOCK_MINED); this.itemColumns = Lists.>newArrayList(Stats.ITEM_BROKEN, Stats.ITEM_CRAFTED, Stats.ITEM_USED, Stats.ITEM_PICKED_UP, Stats.ITEM_DROPPED); Set items = Sets.newIdentityHashSet(); for (Item item : BuiltInRegistries.ITEM) { boolean addToList = false; for (StatType type : this.itemColumns) { if (type.contains(item) && StatsScreen.this.stats.getValue(type.get(item)) > 0) { addToList = true; } } if (addToList) { items.add(item); } } for (Block block : BuiltInRegistries.BLOCK) { boolean addToList = false; for (StatType typex : this.blockColumns) { if (typex.contains(block) && StatsScreen.this.stats.getValue(typex.get(block)) > 0) { addToList = true; } } if (addToList) { items.add(block.asItem()); } } items.remove(Items.AIR); if (!items.isEmpty()) { this.addEntry(new HeaderEntry(this)); for (Item item : items) { this.addEntry(new ItemRow(this, item)); } } } @Override protected void extractListBackground(final GuiGraphicsExtractor graphics) { } private int getColumnX(final int col) { return 75 + 40 * col; } @Override public int getRowWidth() { return 280; } private StatType getColumn(final int i) { return i < this.blockColumns.size() ? (StatType)this.blockColumns.get(i) : (StatType)this.itemColumns.get(i - this.blockColumns.size()); } private int getColumnIndex(final StatType column) { int i = this.blockColumns.indexOf(column); if (i >= 0) { return i; } else { int j = this.itemColumns.indexOf(column); return j >= 0 ? j + this.blockColumns.size() : -1; } } protected void sortByColumn(final StatType column) { if (column != this.sortColumn) { this.sortColumn = column; this.sortOrder = -1; } else if (this.sortOrder == -1) { this.sortOrder = 1; } else { this.sortColumn = null; this.sortOrder = 0; } this.sortItems(this.itemStatSorter); } protected void sortItems(final Comparator comparator) { List itemRows = this.getItemRows(); itemRows.sort(comparator); this.clearEntriesExcept((net.minecraft.client.gui.screens.achievement.StatsScreen.ItemStatisticsList.Entry)this.children().getFirst()); for (ItemRow newChild : itemRows) { this.addEntry(newChild); } } private List getItemRows() { List itemRows = new ArrayList(); this.children().forEach(entry -> { if (entry instanceof ItemRow itemRow) { itemRows.add(itemRow); } }); return itemRows; } @Override protected void extractListSeparators(final GuiGraphicsExtractor graphics) { } } private class MobsStatisticsList extends ObjectSelectionList { public MobsStatisticsList(final Minecraft minecraft) { Objects.requireNonNull(StatsScreen.this); super(minecraft, StatsScreen.this.width, StatsScreen.this.layout.getContentHeight(), 33, 9 * 4); for (EntityType type : BuiltInRegistries.ENTITY_TYPE) { if (StatsScreen.this.stats.getValue(Stats.ENTITY_KILLED.get(type)) > 0 || StatsScreen.this.stats.getValue(Stats.ENTITY_KILLED_BY.get(type)) > 0) { this.addEntry(new MobRow(this, type)); } } } @Override public int getRowWidth() { return 280; } @Override protected void extractListBackground(final GuiGraphicsExtractor graphics) { } @Override protected void extractListSeparators(final GuiGraphicsExtractor graphics) { } } private class StatisticsTab extends GridLayoutTab { protected final AbstractSelectionList list; public StatisticsTab(final Component title, final AbstractSelectionList list) { Objects.requireNonNull(StatsScreen.this); super(title); this.layout.addChild(list, 1, 1); this.list = list; } @Override public void doLayout(final ScreenRectangle screenRectangle) { this.list.updateSizeAndPosition(StatsScreen.this.width, StatsScreen.this.layout.getContentHeight(), StatsScreen.this.layout.getHeaderHeight()); super.doLayout(screenRectangle); } } }