├── common ├── gradle.properties ├── src │ └── main │ │ ├── resources │ │ ├── assets │ │ │ └── betterstats │ │ │ │ ├── icon.png │ │ │ │ ├── textures │ │ │ │ └── gui │ │ │ │ │ └── widgets.png │ │ │ │ └── lang │ │ │ │ ├── bs_ba.json │ │ │ │ ├── nl_nl.json │ │ │ │ ├── pl_pl.json │ │ │ │ ├── nl_be.json │ │ │ │ ├── tr_tr.json │ │ │ │ ├── ko_kr.json │ │ │ │ ├── ja_jp.json │ │ │ │ ├── ar_sa.json │ │ │ │ ├── de_de.json │ │ │ │ ├── tt_ru.json │ │ │ │ ├── pt_br.json │ │ │ │ ├── it_it.json │ │ │ │ ├── es_es.json │ │ │ │ └── fr_fr.json │ │ ├── pack.mcmeta │ │ ├── META-INF │ │ │ └── jarjar-excluded │ │ │ │ └── tcdcommons-4.0.1+common-1.21.10.jar │ │ ├── betterstats.mixin.hooks.json │ │ ├── betterstats.mixin.events.json │ │ ├── betterstats.client.mixin.events.json │ │ └── betterstats.properties.json │ │ └── java │ │ └── io │ │ └── github │ │ └── thecsdev │ │ └── betterstats │ │ ├── server │ │ └── BetterStatsServer.java │ │ ├── mixin │ │ ├── hooks │ │ │ └── AccessorStat.java │ │ └── events │ │ │ └── MixinServerStatHandler.java │ │ ├── api │ │ ├── client │ │ │ ├── util │ │ │ │ ├── StatFilterSettings.java │ │ │ │ └── io │ │ │ │ │ └── LocalPlayerStatsProvider.java │ │ │ ├── registry │ │ │ │ ├── BSClientRegistries.java │ │ │ │ └── BSStatsTabs.java │ │ │ ├── gui │ │ │ │ ├── panel │ │ │ │ │ └── BSComponentPanel.java │ │ │ │ ├── stats │ │ │ │ │ ├── widget │ │ │ │ │ │ ├── MobStatTextWidget.java │ │ │ │ │ │ ├── CustomStatElement.java │ │ │ │ │ │ ├── PlayerBadgeStatWidget.java │ │ │ │ │ │ ├── GeneralStatWidget.java │ │ │ │ │ │ └── AbstractStatWidget.java │ │ │ │ │ └── panel │ │ │ │ │ │ └── PBSummaryPanel.java │ │ │ │ ├── widget │ │ │ │ │ ├── ScrollBarWidget.java │ │ │ │ │ └── SelectStatsTabWidget.java │ │ │ │ └── screen │ │ │ │ │ └── BetterStatsScreenWrapper.java │ │ │ └── badge │ │ │ │ └── BSClientPlayerBadge.java │ │ ├── events │ │ │ └── client │ │ │ │ └── gui │ │ │ │ └── BetterStatsGUIEvent.java │ │ └── util │ │ │ ├── io │ │ │ ├── EmptyStatsProvider.java │ │ │ ├── IllegalHeaderException.java │ │ │ ├── IStatsProvider.java │ │ │ ├── ServerPlayerStatsProvider.java │ │ │ └── RAMStatsProvider.java │ │ │ ├── enumerations │ │ │ ├── MobStatType.java │ │ │ ├── ItemStatType.java │ │ │ ├── FilterSortMobsBy.java │ │ │ └── FilterSortItemsBy.java │ │ │ ├── BSUtils.java │ │ │ ├── stats │ │ │ ├── SUStat.java │ │ │ └── SUGeneralStat.java │ │ │ └── interfaces │ │ │ └── IThirdPartyStatsListener.java │ │ ├── client │ │ ├── gui │ │ │ ├── screen │ │ │ │ └── hud │ │ │ │ │ ├── BetterStatsHudScreenWrapper.java │ │ │ │ │ └── entry │ │ │ │ │ └── StatsHudGeneralEntry.java │ │ │ ├── stats │ │ │ │ ├── panel │ │ │ │ │ ├── impl │ │ │ │ │ │ ├── MenuBarPanelProxyImpl.java │ │ │ │ │ │ ├── StatsTabPanelProxyImpl.java │ │ │ │ │ │ └── StatFiltersPanelProxyImpl.java │ │ │ │ │ └── StatFiltersPanel.java │ │ │ │ └── tabs │ │ │ │ │ ├── MonstersHuntedStatsTab.java │ │ │ │ │ ├── FoodStuffsStatsTab.java │ │ │ │ │ └── AdvancementsTab.java │ │ │ ├── panel │ │ │ │ └── PageChooserPanel.java │ │ │ └── widget │ │ │ │ └── AdvancementStatWidget.java │ │ ├── mixin │ │ │ └── events │ │ │ │ └── MixinMinecraftClient.java │ │ └── network │ │ │ └── OtherClientPlayerStatsProvider.java │ │ ├── BetterStatsConfig.java │ │ ├── BetterStatsProperties.java │ │ ├── util │ │ ├── stats │ │ │ └── SASConfig.java │ │ └── io │ │ │ └── BetterStatsWebApiUtils.java │ │ ├── BetterStats.java │ │ └── network │ │ └── BetterStatsNetwork.java └── build.gradle ├── fabric ├── gradle.properties ├── src │ └── main │ │ ├── resources │ │ ├── META-INF │ │ │ └── jarjar-excluded │ │ │ │ └── tcdcommons-4.0.1+fabric-1.21.10.jar │ │ ├── betterstats.mixin.security.json │ │ └── fabric.mod.json │ │ └── java │ │ └── io │ │ └── github │ │ └── thecsdev │ │ └── betterstats │ │ └── fabric │ │ ├── BetterStatsFabric.java │ │ ├── modmenu │ │ └── ModMenuApiImpl.java │ │ └── mixin │ │ └── security │ │ └── MixinModLoader.java └── build.gradle ├── neoforge ├── gradle.properties ├── src │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ ├── jarjar-excluded │ │ │ └── tcdcommons-4.0.1+neoforge-1.21.10.jar │ │ │ └── neoforge.mods.toml │ │ └── java │ │ └── io │ │ └── github │ │ └── thecsdev │ │ └── betterstats │ │ └── neo │ │ └── BetterStatsNeo.java └── build.gradle ├── .gitattributes ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── .github └── ISSUE_TEMPLATE │ ├── mod_issue_question.yml │ ├── mod_issue_suggestion.yml │ └── mod_issue_bug.yml ├── gradle.properties ├── .gitignore └── gradlew.bat /common/gradle.properties: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fabric/gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform = fabric 2 | -------------------------------------------------------------------------------- /neoforge/gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform = neoforge 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCSMods/mc-betterstats/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /common/src/main/resources/assets/betterstats/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCSMods/mc-betterstats/HEAD/common/src/main/resources/assets/betterstats/icon.png -------------------------------------------------------------------------------- /common/src/main/resources/assets/betterstats/textures/gui/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCSMods/mc-betterstats/HEAD/common/src/main/resources/assets/betterstats/textures/gui/widgets.png -------------------------------------------------------------------------------- /common/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": 3 | { 4 | "description": { "text": "${properties["mod.name"]}'s resource pack." }, 5 | "pack_format": ${properties["pack.format"]} 6 | } 7 | } -------------------------------------------------------------------------------- /common/src/main/resources/META-INF/jarjar-excluded/tcdcommons-4.0.1+common-1.21.10.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCSMods/mc-betterstats/HEAD/common/src/main/resources/META-INF/jarjar-excluded/tcdcommons-4.0.1+common-1.21.10.jar -------------------------------------------------------------------------------- /fabric/src/main/resources/META-INF/jarjar-excluded/tcdcommons-4.0.1+fabric-1.21.10.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCSMods/mc-betterstats/HEAD/fabric/src/main/resources/META-INF/jarjar-excluded/tcdcommons-4.0.1+fabric-1.21.10.jar -------------------------------------------------------------------------------- /neoforge/src/main/resources/META-INF/jarjar-excluded/tcdcommons-4.0.1+neoforge-1.21.10.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCSMods/mc-betterstats/HEAD/neoforge/src/main/resources/META-INF/jarjar-excluded/tcdcommons-4.0.1+neoforge-1.21.10.jar -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/server/BetterStatsServer.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.server; 2 | 3 | import io.github.thecsdev.betterstats.BetterStats; 4 | 5 | public final class BetterStatsServer extends BetterStats 6 | { 7 | public BetterStatsServer() {} 8 | } -------------------------------------------------------------------------------- /fabric/src/main/resources/betterstats.mixin.security.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "io.github.thecsdev.betterstats.fabric.mixin.security", 5 | "compatibilityLevel": "JAVA_21", 6 | "mixins": ["MixinModLoader"], 7 | "injectors": { "defaultRequire": 1 } 8 | } -------------------------------------------------------------------------------- /common/src/main/resources/betterstats.mixin.hooks.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "io.github.thecsdev.betterstats.mixin.hooks", 5 | "compatibilityLevel": "JAVA_21", 6 | "mixins": 7 | [ 8 | "AccessorStat" 9 | ], 10 | "injectors": { "defaultRequire": 1 } 11 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /common/src/main/resources/betterstats.mixin.events.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "io.github.thecsdev.betterstats.mixin.events", 5 | "compatibilityLevel": "JAVA_21", 6 | "mixins": 7 | [ 8 | "MixinServerStatHandler" 9 | ], 10 | "injectors": { "defaultRequire": 1 } 11 | } -------------------------------------------------------------------------------- /common/src/main/resources/betterstats.client.mixin.events.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "io.github.thecsdev.betterstats.client.mixin.events", 5 | "compatibilityLevel": "JAVA_21", 6 | "client": [ 7 | "MixinMinecraftClient" 8 | ], 9 | "injectors": { "defaultRequire": 1 } 10 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement 2 | { 3 | repositories 4 | { 5 | maven { url "https://maven.fabricmc.net/" } 6 | maven { url "https://maven.architectury.dev/" } 7 | maven { url "https://files.minecraftforge.net/maven/" } 8 | gradlePluginPortal() 9 | } 10 | } 11 | 12 | rootProject.name = "${settings["mod.id"]}" 13 | include("common", "fabric", "neoforge") 14 | -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/mixin/hooks/AccessorStat.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.mixin.hooks; 2 | 3 | import net.minecraft.stats.Stat; 4 | import net.minecraft.stats.StatFormatter; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(value = Stat.class) 9 | public interface AccessorStat 10 | { 11 | @Accessor("formatter") StatFormatter getFormatter(); 12 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/betterstats/lang/bs_ba.json: -------------------------------------------------------------------------------- 1 | { 2 | "betterstats": "Better Statistics Screen", 3 | 4 | 5 | "betterstats.stattype_phrase.minecraft.killed": "Ubijeno", 6 | "betterstats.stattype_phrase.minecraft.killed_by": "Poginuo/la od", 7 | "betterstats.stattype_phrase.morestats.damaged": "Nanijeta šteta (x10)", 8 | "betterstats.stattype_phrase.morestats.damaged_by": "Primljena šteta (x10)", 9 | "betterstats.stattype_phrase.morestats.totem_popped_by": "Totemi aktivirani od" 10 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/betterstats/lang/nl_nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "betterstats": "Better Statistics Screen", 3 | 4 | 5 | "betterstats.stattype_phrase.minecraft.killed": "Gedood", 6 | "betterstats.stattype_phrase.minecraft.killed_by": "Overleden door", 7 | "betterstats.stattype_phrase.morestats.damaged": "Schade toegebracht (x10)", 8 | "betterstats.stattype_phrase.morestats.damaged_by": "Schade geleden (x10)", 9 | "betterstats.stattype_phrase.morestats.totem_popped_by": "Totems geactiveerd door" 10 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/betterstats/lang/pl_pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "betterstats": "Better Statistics Screen", 3 | 4 | 5 | "betterstats.stattype_phrase.minecraft.killed": "Zabitych", 6 | "betterstats.stattype_phrase.minecraft.killed_by": "Śmierć przez", 7 | "betterstats.stattype_phrase.morestats.damaged": "Zadane obrażenia (x10)", 8 | "betterstats.stattype_phrase.morestats.damaged_by": "Otrzymane obrażenia (x10)", 9 | "betterstats.stattype_phrase.morestats.totem_popped_by": "Aktywowane totemy przez" 10 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/betterstats/lang/nl_be.json: -------------------------------------------------------------------------------- 1 | { 2 | "betterstats": "Better Statistics Screen", 3 | 4 | 5 | "betterstats.stattype_phrase.minecraft.killed": "Gedempt", 6 | "betterstats.stattype_phrase.minecraft.killed_by": "Gestorven door", 7 | "betterstats.stattype_phrase.morestats.damaged": "Toegebrachte schade (x10)", 8 | "betterstats.stattype_phrase.morestats.damaged_by": "Opgelopen schade (x10)", 9 | "betterstats.stattype_phrase.morestats.totem_popped_by": "Totems geactiveerd door" 10 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/betterstats/lang/tr_tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "betterstats": "Better Statistics Screen", 3 | 4 | 5 | "betterstats.stattype_phrase.minecraft.killed": "Öldürülen", 6 | "betterstats.stattype_phrase.minecraft.killed_by": "Şunun yüzünden öldü", 7 | "betterstats.stattype_phrase.morestats.damaged": "Verilen hasar (x10)", 8 | "betterstats.stattype_phrase.morestats.damaged_by": "Alınan hasar (x10)", 9 | "betterstats.stattype_phrase.morestats.totem_popped_by": "Şunun tarafından patlatılan totemler" 10 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/mod_issue_question.yml: -------------------------------------------------------------------------------- 1 | name: Questions & Other 2 | description: The other options aren't what you're looking for? Create your own custom issue. 3 | title: "Issue name" 4 | labels: ["Question"] 5 | assignees: [] 6 | body: 7 | - type: textarea 8 | id: question 9 | attributes: 10 | label: Question 11 | description: What is it you would like to ask? 12 | placeholder: Write the question here! 13 | value: "I would like to ask a question regarding..." 14 | validations: 15 | required: true 16 | -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/client/util/StatFilterSettings.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.client.util; 2 | 3 | import io.github.thecsdev.betterstats.client.gui.stats.panel.impl.BetterStatsPanel; 4 | import io.github.thecsdev.tcdcommons.api.util.collections.GenericProperties; 5 | import net.minecraft.resources.ResourceLocation; 6 | 7 | /** 8 | * An instance of {@link GenericProperties} whose sole purpose is to store 9 | * information about "statistics filters" applied to a {@link BetterStatsPanel}. 10 | */ 11 | public final class StatFilterSettings extends GenericProperties 12 | { 13 | private static final long serialVersionUID = 4330232763610691948L; 14 | } -------------------------------------------------------------------------------- /fabric/src/main/java/io/github/thecsdev/betterstats/fabric/BetterStatsFabric.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.fabric; 2 | 3 | import net.fabricmc.api.ClientModInitializer; 4 | import net.fabricmc.api.DedicatedServerModInitializer; 5 | 6 | /** 7 | * Fabric Mod Loader entry-points for this mod. 8 | */ 9 | public final class BetterStatsFabric implements ClientModInitializer, DedicatedServerModInitializer 10 | { 11 | // ================================================== 12 | public @Override void onInitializeClient() { new io.github.thecsdev.betterstats.client.BetterStatsClient(); } 13 | public @Override void onInitializeServer() { new io.github.thecsdev.betterstats.server.BetterStatsServer(); } 14 | // ================================================== 15 | } -------------------------------------------------------------------------------- /fabric/src/main/java/io/github/thecsdev/betterstats/fabric/modmenu/ModMenuApiImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.fabric.modmenu; 2 | 3 | import com.terraformersmc.modmenu.api.ConfigScreenFactory; 4 | import com.terraformersmc.modmenu.api.ModMenuApi; 5 | 6 | import io.github.thecsdev.betterstats.api.client.gui.screen.BetterStatsConfigScreen; 7 | 8 | /** 9 | * An optional {@link ModMenuApi} integration for the config GUI.
10 | * @apiNote Interacting with this class could crash the game, as it depends on 'modmenu'. 11 | */ 12 | public final class ModMenuApiImpl implements ModMenuApi 13 | { 14 | public final @Override ConfigScreenFactory getModConfigScreenFactory() 15 | { 16 | return parent -> new BetterStatsConfigScreen(parent).getAsScreen(); 17 | } 18 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/client/gui/screen/hud/BetterStatsHudScreenWrapper.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.client.gui.screen.hud; 2 | 3 | import org.jetbrains.annotations.ApiStatus.Internal; 4 | 5 | import io.github.thecsdev.tcdcommons.api.client.gui.screen.TScreenWrapper; 6 | import io.github.thecsdev.tcdcommons.api.client.util.interfaces.IStatsListener; 7 | 8 | @Internal class BetterStatsHudScreenWrapper extends TScreenWrapper implements IStatsListener 9 | { 10 | // ================================================== 11 | public BetterStatsHudScreenWrapper(BetterStatsHudScreen target) { super(target); } 12 | // -------------------------------------------------- 13 | public final @Override void onStatsReady() { this.target.refresh(); } 14 | // ================================================== 15 | } -------------------------------------------------------------------------------- /neoforge/src/main/java/io/github/thecsdev/betterstats/neo/BetterStatsNeo.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.neo; 2 | 3 | import io.github.thecsdev.betterstats.BetterStats; 4 | import io.github.thecsdev.betterstats.client.BetterStatsClient; 5 | import io.github.thecsdev.betterstats.server.BetterStatsServer; 6 | import net.neoforged.fml.common.Mod; 7 | import net.neoforged.fml.loading.FMLEnvironment; 8 | 9 | @Mod(BetterStats.ModID) 10 | public class BetterStatsNeo 11 | { 12 | // ================================================== 13 | public BetterStatsNeo() 14 | { 15 | //create an instance of the mod's main class, depending on the dist 16 | switch(FMLEnvironment.getDist()) 17 | { 18 | case CLIENT -> new BetterStatsClient(); 19 | case DEDICATED_SERVER -> new BetterStatsServer(); 20 | } 21 | } 22 | // ================================================== 23 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/client/mixin/events/MixinMinecraftClient.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.client.mixin.events; 2 | 3 | import io.github.thecsdev.betterstats.client.BetterStatsClient; 4 | import net.minecraft.client.Minecraft; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 9 | 10 | @Mixin(Minecraft.class) 11 | public class MixinMinecraftClient 12 | { 13 | // ================================================== 14 | @Inject(method = "", at = @At("RETURN")) 15 | public void onInit(CallbackInfo callback) 16 | { 17 | //assign the minecraft client instance once it's ready 18 | BetterStatsClient.MC_CLIENT = (Minecraft)(Object)this; 19 | } 20 | // ================================================== 21 | } 22 | -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/client/registry/BSClientRegistries.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.client.registry; 2 | 3 | import io.github.thecsdev.betterstats.BetterStats; 4 | import io.github.thecsdev.betterstats.api.client.registry.StatsTab.FiltersInitContext; 5 | import io.github.thecsdev.betterstats.api.client.registry.StatsTab.StatsInitContext; 6 | import io.github.thecsdev.tcdcommons.api.registry.TMutableRegistry; 7 | import io.github.thecsdev.tcdcommons.api.registry.TRegistry; 8 | 9 | /** 10 | * {@link BetterStats} registries that are present on the client side only. 11 | */ 12 | public final class BSClientRegistries 13 | { 14 | // ================================================== 15 | private BSClientRegistries() {} 16 | // ================================================== 17 | /** 18 | * A {@link TRegistry} containing {@link StatsTab}s that will be 19 | * shown on the list of "statistics tabs" to select from. 20 | * @see StatsTab 21 | * @see StatsTab#initStats(StatsInitContext) 22 | * @see StatsTab#initFilters(FiltersInitContext) 23 | */ 24 | public static final TMutableRegistry STATS_TAB = new TMutableRegistry<>(); 25 | // ================================================== 26 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/events/client/gui/BetterStatsGUIEvent.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.events.client.gui; 2 | 3 | import io.github.thecsdev.betterstats.BetterStats; 4 | import io.github.thecsdev.betterstats.api.client.gui.screen.BetterStatsScreen; 5 | import io.github.thecsdev.betterstats.client.gui.stats.panel.MenuBarPanel; 6 | import io.github.thecsdev.tcdcommons.api.client.gui.panel.menu.TMenuBarPanel; 7 | import io.github.thecsdev.tcdcommons.api.event.TEvent; 8 | import io.github.thecsdev.tcdcommons.api.event.TEventFactory; 9 | 10 | /** 11 | * {@link TEvent}s related to the {@link BetterStatsScreen} and 12 | * other {@link BetterStats} GUI-related events. 13 | */ 14 | public interface BetterStatsGUIEvent 15 | { 16 | /** 17 | * @see MenuBarInitialized#invoke(TMenuBarPanel) 18 | */ 19 | TEvent MENU_BAR_INITIALIZED = TEventFactory.createLoop(); 20 | 21 | public static interface MenuBarInitialized 22 | { 23 | /** 24 | * A {@link TEvent} that is invoked after the {@link MenuBarPanel} 25 | * component initializes its menu items in the {@link TMenuBarPanel}. 26 | *

27 | * Use this to add your own menu items to the {@link TMenuBarPanel}. 28 | */ 29 | public void invoke(TMenuBarPanel menuBar); 30 | } 31 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/util/io/EmptyStatsProvider.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.util.io; 2 | 3 | import java.util.UUID; 4 | import net.minecraft.network.chat.Component; 5 | import net.minecraft.resources.ResourceLocation; 6 | import net.minecraft.stats.Stat; 7 | import org.jetbrains.annotations.ApiStatus.Internal; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import com.mojang.authlib.GameProfile; 11 | 12 | import io.github.thecsdev.tcdcommons.api.util.TextUtils; 13 | 14 | final @Internal class EmptyStatsProvider implements IStatsProvider 15 | { 16 | // ================================================== 17 | private static final Component NULL_NAME = TextUtils.literal("null"); 18 | private static final GameProfile NULL_GP = new GameProfile(new UUID(0, 0), "null"); 19 | // ================================================== 20 | public EmptyStatsProvider() {} 21 | // ================================================== 22 | public final @Override int getStatValue(Stat stat) { return 0; } 23 | public final @Override int getPlayerBadgeValue(ResourceLocation badgeId) { return 0; } 24 | public @Nullable GameProfile getGameProfile() { return NULL_GP; } 25 | public @Nullable Component getDisplayName() { return NULL_NAME; } 26 | // ================================================== 27 | } -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | architectury { common parent["enabled_platforms"].split(',') } 2 | 3 | repositories 4 | { 5 | maven { url = "https://maven.shedaniel.me/" } 6 | maven { url = "https://maven.terraformersmc.com/releases/" } 7 | } 8 | 9 | dependencies 10 | { 11 | // We depend on Fabric Loader here to use the Fabric @Environment annotations, 12 | // which get remapped to the correct annotations on each platform. 13 | // Do NOT use other classes from Fabric Loader. 14 | modImplementation "net.fabricmc:fabric-loader:${parent["fabric.loader_version"]}" 15 | modImplementation "dev.architectury:architectury:${parent["architectury.version"]}" 16 | 17 | // Roughly Enough Items 18 | modCompileOnly "me.shedaniel:RoughlyEnoughItems-fabric:${parent["rei.version"]}" 19 | 20 | //compile-time jar files in jarjar-excluded 21 | modCompileOnlyApi(fileTree(dir: 'src/main/resources/META-INF/jarjar-excluded', include: '*.jar')) 22 | } 23 | 24 | processResources 25 | { 26 | //include the gradle properties file but renamed as 'mod-id.properties' 27 | from("../gradle.properties") { rename { "${parent["mod.id"]}.properties" } } 28 | 29 | //include the license file as well. necessary for legal reasons 30 | from("../LICENSE.txt") 31 | 32 | //expand root project properties to potential mixin files 33 | filesMatching(["*.json", "*.mcmeta"]) { expand(properties: parent.properties) } 34 | } 35 | -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/client/gui/panel/BSComponentPanel.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.client.gui.panel; 2 | 3 | import static io.github.thecsdev.betterstats.BetterStats.getModID; 4 | 5 | import io.github.thecsdev.betterstats.BetterStats; 6 | import io.github.thecsdev.betterstats.api.client.gui.screen.BetterStatsScreen; 7 | import io.github.thecsdev.tcdcommons.api.client.gui.panel.TRefreshablePanelElement; 8 | import net.minecraft.resources.ResourceLocation; 9 | 10 | /** 11 | * A {@link TRefreshablePanelElement} representing a {@link BetterStatsScreen} GUI component. 12 | * @apiNote Not intended for outside use. Please use {@link TRefreshablePanelElement} instead. 13 | */ 14 | public abstract class BSComponentPanel extends TRefreshablePanelElement 15 | { 16 | // ================================================== 17 | /** 18 | * {@link BetterStats}'s "{@code textures/gui/widgets.png}" texture {@link ResourceLocation}. 19 | */ 20 | public static final ResourceLocation BS_WIDGETS_TEXTURE = ResourceLocation.fromNamespaceAndPath(getModID(), "textures/gui/widgets.png"); 21 | // ================================================== 22 | public BSComponentPanel(int x, int y, int width, int height) 23 | { 24 | super(x, y, width, height); 25 | this.scrollFlags = 0; 26 | this.scrollPadding = 0; 27 | this.outlineColor = -16777216; //black 28 | } 29 | // ================================================== 30 | } -------------------------------------------------------------------------------- /fabric/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "${properties["mod.id"]}", 4 | "version": "${properties["mod.version"]}+fabric-${properties["minecraft.version"]}", 5 | "name": "${properties["mod.name"]}", 6 | "description": "${properties["mod.description"]}", 7 | "authors": ["${properties["mod.author"]}"], 8 | "license": "${properties["mod.license"]}", 9 | "icon": "assets/${properties["mod.id"]}/icon.png", 10 | "contact": 11 | { 12 | "homepage": "${properties["mod.link.homepage"]}", 13 | "sources": "${properties["mod.link.sources"]}", 14 | "issues": "${properties["mod.link.issues"]}" 15 | }, 16 | "environment": "*", 17 | "entrypoints": 18 | { 19 | "client": ["${properties["fabric.json.entrypoints.client"]}"], 20 | "server": ["${properties["fabric.json.entrypoints.client"]}"], 21 | "modmenu": ["${properties["fabric.json.entrypoints.modmenu"]}"] 22 | }, 23 | "mixins": ${properties["mixins"]}, 24 | "depends": 25 | { 26 | "java": "${properties["fabric.json.depends.java"]}", 27 | "minecraft": "${properties["fabric.json.depends.minecraft"]}", 28 | "fabricloader": "${properties["fabric.json.depends.fabricloader"]}", 29 | "fabric-api": "${properties["fabric.json.depends.fabric-api"]}", 30 | "architectury": "${properties["fabric.json.depends.architectury"]}", 31 | "tcdcommons": "${properties["fabric.json.depends.tcdcommons"]}" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/client/gui/stats/panel/impl/MenuBarPanelProxyImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.client.gui.stats.panel.impl; 2 | 3 | import java.util.Objects; 4 | 5 | import org.jetbrains.annotations.ApiStatus.Internal; 6 | 7 | import io.github.thecsdev.betterstats.api.client.registry.StatsTab; 8 | import io.github.thecsdev.betterstats.api.util.io.IStatsProvider; 9 | import io.github.thecsdev.betterstats.client.gui.stats.panel.MenuBarPanel.MenuBarPanelProxy; 10 | import io.github.thecsdev.betterstats.client.gui.stats.panel.impl.BetterStatsPanel.BetterStatsPanelProxy; 11 | 12 | final @Internal class MenuBarPanelProxyImpl implements MenuBarPanelProxy 13 | { 14 | // ================================================== 15 | protected final BetterStatsPanel bsPanel; 16 | protected final BetterStatsPanelProxy proxy; 17 | // ================================================== 18 | public MenuBarPanelProxyImpl(BetterStatsPanel bsPanel) throws NullPointerException 19 | { 20 | this.bsPanel = Objects.requireNonNull(bsPanel); 21 | this.proxy = bsPanel.proxy; 22 | } 23 | // ================================================== 24 | public final @Override IStatsProvider getStatsProvider() { return this.proxy.getStatsProvider(); } 25 | public final @Override void setSelectedStatsTab(StatsTab statsTab) 26 | { 27 | this.proxy.setSelectedStatsTab(statsTab); 28 | this.bsPanel.refresh(); 29 | } 30 | // ================================================== 31 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/client/network/OtherClientPlayerStatsProvider.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.client.network; 2 | 3 | import java.util.Objects; 4 | 5 | import org.jetbrains.annotations.ApiStatus.Internal; 6 | 7 | import com.mojang.authlib.GameProfile; 8 | 9 | import io.github.thecsdev.betterstats.api.util.io.IStatsProvider; 10 | import io.github.thecsdev.betterstats.api.util.io.RAMStatsProvider; 11 | import io.github.thecsdev.tcdcommons.api.util.TUtils; 12 | import io.github.thecsdev.tcdcommons.api.util.TextUtils; 13 | 14 | /** 15 | * A {@link RAMStatsProvider}, except in form of a {@link Class} that is specifically 16 | * used for storing {@link IStatsProvider} data about another player that isn't the client. 17 | */ 18 | public final @Internal class OtherClientPlayerStatsProvider extends RAMStatsProvider 19 | { 20 | // ================================================== 21 | private final String playerName; 22 | // ================================================== 23 | OtherClientPlayerStatsProvider(String playerName) throws NullPointerException 24 | { 25 | this.playerName = Objects.requireNonNull(playerName); 26 | setDisplayName(TextUtils.literal(playerName)); 27 | setGameProfile(new GameProfile(TUtils.getOfflinePlayerUuid(playerName), playerName)); 28 | } 29 | // -------------------------------------------------- 30 | public final String getPlayerName() { return this.playerName; } 31 | // ================================================== 32 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/client/gui/stats/panel/impl/StatsTabPanelProxyImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.client.gui.stats.panel.impl; 2 | 3 | import java.util.Objects; 4 | 5 | import org.jetbrains.annotations.ApiStatus.Internal; 6 | 7 | import io.github.thecsdev.betterstats.api.client.registry.StatsTab; 8 | import io.github.thecsdev.betterstats.api.client.util.StatFilterSettings; 9 | import io.github.thecsdev.betterstats.api.util.io.IStatsProvider; 10 | import io.github.thecsdev.betterstats.client.gui.stats.panel.StatsTabPanel.StatsTabPanelProxy; 11 | import io.github.thecsdev.betterstats.client.gui.stats.panel.impl.BetterStatsPanel.BetterStatsPanelProxy; 12 | 13 | final @Internal class StatsTabPanelProxyImpl implements StatsTabPanelProxy 14 | { 15 | // ================================================== 16 | protected final BetterStatsPanel bsPanel; 17 | protected final BetterStatsPanelProxy proxy; 18 | // ================================================== 19 | public StatsTabPanelProxyImpl(BetterStatsPanel bsPanel) throws NullPointerException 20 | { 21 | this.bsPanel = Objects.requireNonNull(bsPanel); 22 | this.proxy = bsPanel.proxy; 23 | } 24 | // ================================================== 25 | public final @Override IStatsProvider getStatsProvider() { return this.proxy.getStatsProvider(); } 26 | public final @Override StatsTab getSelectedStatsTab() { return this.proxy.getSelectedStatsTab(); } 27 | public final @Override StatFilterSettings getFilterSettings() { return this.proxy.getFilterSettings(); } 28 | // ================================================== 29 | } -------------------------------------------------------------------------------- /neoforge/src/main/resources/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "${properties["net.toml.loaderVersion"]}" 3 | license = "${properties["mod.license"]}" 4 | issueTrackerURL = "${properties["mod.link.issues"]}" 5 | 6 | [[mods]] 7 | modId = "${properties["mod.id"]}" 8 | version = "${properties["mod.version"]}+neoforge-${properties["minecraft.version"]}" 9 | displayName = "${properties["mod.name"]}" 10 | description = "${properties["mod.description"]}." 11 | authors = "${properties["mod.author"]}" 12 | logoFile = "assets/${properties["mod.id"]}/icon.png" 13 | 14 | [[dependencies.${properties["mod.id"]}]] 15 | modId = "neoforge" 16 | type = "required" 17 | versionRange = "${properties["neo.toml.dependencies.neoforge"]}" 18 | ordering = "NONE" 19 | side = "BOTH" 20 | 21 | [[dependencies.${properties["mod.id"]}]] 22 | modId = "minecraft" 23 | type = "required" 24 | versionRange = "${properties["neo.toml.dependencies.minecraft"]}" 25 | ordering = "NONE" 26 | side = "BOTH" 27 | 28 | [[dependencies.${properties["mod.id"]}]] 29 | modId = "architectury" 30 | type = "required" 31 | versionRange = "${properties["neo.toml.dependencies.architectury"]}" 32 | ordering = "AFTER" 33 | side = "BOTH" 34 | 35 | [[dependencies.${properties["mod.id"]}]] 36 | modId = "tcdcommons" 37 | type = "required" 38 | versionRange = "${properties["neo.toml.dependencies.tcdcommons"]}" 39 | ordering = "AFTER" 40 | side = "BOTH" 41 | 42 | # Mixin declarations are dynamically inserted below: 43 | ${properties["mixins"]} 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/mod_issue_suggestion.yml: -------------------------------------------------------------------------------- 1 | name: Suggestion 2 | description: Create a suggestion for this mod. 3 | title: "[Suggestion] Suggestion name" 4 | labels: ["Suggestion"] 5 | assignees: [] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Thanks for taking the time to fill out this suggestion! 11 | 12 | # **Note:** Suggestion titles start with the square brackets that contain the **latest** full mod version. 13 | # **Format:** [mod_loader]-[mod_version]+[minecraft_version] 14 | # **Example: fabric-v1.0+1.19.2** 15 | - type: dropdown 16 | id: loader 17 | attributes: 18 | label: What is this suggestion for? 19 | description: "This suggestion is for the:" 20 | multiple: false 21 | options: 22 | - Mod Itself 23 | - Repository 24 | - Wiki & Documentation 25 | - GitHub Pages 26 | - Other 27 | validations: 28 | required: true 29 | - type: textarea 30 | id: suggestion 31 | attributes: 32 | label: Suggestion 33 | description: What is it you would like to suggest? A new feature? A change? Write it down here. 34 | placeholder: Write the suggestion here! 35 | value: "I would like to have a feature where..." 36 | validations: 37 | required: true 38 | - type: checkboxes 39 | id: terms 40 | attributes: 41 | label: Code of Conduct 42 | description: "By submitting this suggestion, you agree to the following:" 43 | options: 44 | - label: I agree that I will be available later for any follow-up questions to help with implementing the suggested thing. 45 | required: true 46 | -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/client/gui/stats/widget/MobStatTextWidget.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.client.gui.stats.widget; 2 | 3 | import io.github.thecsdev.betterstats.api.util.stats.SUMobStat; 4 | import io.github.thecsdev.tcdcommons.api.client.gui.util.TDrawContext; 5 | import io.github.thecsdev.tcdcommons.api.util.annotations.Virtual; 6 | import io.github.thecsdev.tcdcommons.api.util.enumerations.HorizontalAlignment; 7 | import net.minecraft.client.gui.components.Tooltip; 8 | import net.minecraft.network.chat.Component; 9 | 10 | /** 11 | * An alternative to {@link MobStatWidget}, displaying an {@link SUMobStat} 12 | * in a {@link GeneralStatWidget}-like fashion. 13 | */ 14 | public @Virtual class MobStatTextWidget extends AbstractStatWidget 15 | { 16 | // ================================================== 17 | protected final Component txt_label; 18 | protected final Component txt_value; 19 | // ================================================== 20 | @SuppressWarnings("deprecation") 21 | public MobStatTextWidget(int x, int y, int width, SUMobStat stat) throws NullPointerException 22 | { 23 | super(x, y, width, GeneralStatWidget.HEIGHT, stat); 24 | this.txt_label = stat.getStatLabel(); 25 | this.txt_value = Component.literal("⚔" + stat.kills + " / 💀" + stat.deaths); 26 | 27 | setTooltip(Tooltip.create(MobStatWidget.createTooltipText(stat))); 28 | } 29 | // ================================================== 30 | public @Virtual @Override void render(TDrawContext pencil) 31 | { 32 | super.render(pencil); 33 | pencil.drawTElementTextTH(this.txt_label, HorizontalAlignment.LEFT); 34 | pencil.drawTElementTextTH(this.txt_value, HorizontalAlignment.RIGHT); 35 | } 36 | // ================================================== 37 | } -------------------------------------------------------------------------------- /fabric/src/main/java/io/github/thecsdev/betterstats/fabric/mixin/security/MixinModLoader.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.fabric.mixin.security; 2 | 3 | import io.github.thecsdev.betterstats.fabric.BetterStatsFabric; 4 | import io.github.thecsdev.tcdcommons.api.util.integrity.SelfDefense; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 9 | 10 | @Mixin(value = BetterStatsFabric.class, priority = 9001) 11 | public abstract class MixinModLoader 12 | { 13 | @Inject(method = "", at = @At("HEAD"), cancellable = true, require = 0) 14 | private static void onClassInit(CallbackInfo callback) 15 | { 16 | SelfDefense.reportClassInitializer(BetterStatsFabric.class); 17 | callback.cancel(); 18 | 19 | /*//construct the message 20 | final var fullName = BetterStatsFabric.class.getName(); 21 | final var message = String.format( 22 | "AN INTEGRITY VIOLATION WAS FOUND:\n" 23 | + "The class '%s' has a static constructor, which isn't allowed!\n" 24 | + "The game will now close. Please run a virus scan in the meantime.", fullName); 25 | 26 | //terminate the program 27 | throw new ExceptionInInitializerError(message);*/ 28 | 29 | /* ^ IMPORTANT NOTE: if you have a static constructor defined, or a static field defined, 30 | * the above code WILL end up always executing, even when it isn't supposed to, which is bad. 31 | * 32 | * if you absolutely have to have a static field, then use the approach below instead 33 | * (just make sure to not initialize it with the `=` sign or in a static constructor): 34 | */ 35 | 36 | //not allowed to execute code in static constructor/initializer 37 | //callback.cancel(); -- only use if the above approach is infeasible 38 | } 39 | } -------------------------------------------------------------------------------- /neoforge/build.gradle: -------------------------------------------------------------------------------- 1 | import static TCDModUtils.getMixinFileNames; 2 | 3 | plugins { id 'com.github.johnrengelman.shadow' } 4 | 5 | architectury 6 | { 7 | platformSetupLoomIde() 8 | neoForge() 9 | } 10 | 11 | configurations 12 | { 13 | common { canBeResolved = true; canBeConsumed = false } 14 | 15 | compileClasspath.extendsFrom common 16 | runtimeClasspath.extendsFrom common 17 | developmentNeoForge.extendsFrom common 18 | 19 | // Files in this configuration will be bundled into your mod using the Shadow plugin. 20 | // Don't use the `shadow` configuration from the plugin itself as it's meant for excluding files. 21 | shadowBundle { canBeResolved = true; canBeConsumed = false } 22 | } 23 | 24 | repositories 25 | { 26 | maven { name = 'NeoForged'; url = 'https://maven.neoforged.net/releases' } 27 | } 28 | 29 | dependencies 30 | { 31 | neoForge "net.neoforged:neoforge:${parent["neoforge.version"]}" 32 | modImplementation "dev.architectury:architectury-neoforge:${parent["architectury.version"]}" 33 | 34 | common(project(path: ':common', configuration: 'namedElements')) { transitive false } 35 | shadowBundle project(path: ':common', configuration: 'transformProductionNeoForge') 36 | 37 | //jar files in jarjar-excluded 38 | modImplementation(fileTree(dir: 'src/main/resources/META-INF/jarjar-excluded', include: '*.jar')) 39 | } 40 | 41 | processResources 42 | { 43 | //obtain array of applicable mixin files, and turn it into string 44 | String mixins = (getMixinFileNames(project) + getMixinFileNames(project(":common"))) 45 | .collect { '[[mixins]]\nconfig = "' + it + '"\n' } 46 | .join('\n') 47 | 48 | //expand root project properties to the 'neoforge.mods.toml' file 49 | filesMatching(["META-INF/neoforge.mods.toml", "*.json"]) { 50 | expand(properties: [mixins: mixins] + parent.properties) 51 | } 52 | } 53 | 54 | shadowJar 55 | { 56 | configurations = [project.configurations.shadowBundle] 57 | archiveClassifier = 'dev-shadow' 58 | 59 | exclude('META-INF/jarjar-excluded/*.jar') 60 | } 61 | 62 | remapJar { inputFile.set shadowJar.archiveFile } 63 | -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/client/gui/stats/tabs/MonstersHuntedStatsTab.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.client.gui.stats.tabs; 2 | 3 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.translatable; 4 | 5 | import java.util.function.Predicate; 6 | import net.minecraft.network.chat.Component; 7 | import net.minecraft.world.entity.MobCategory; 8 | import org.jetbrains.annotations.ApiStatus.Internal; 9 | 10 | import io.github.thecsdev.betterstats.api.client.gui.stats.widget.MobStatWidget; 11 | import io.github.thecsdev.betterstats.api.client.gui.util.StatsTabUtils; 12 | import io.github.thecsdev.betterstats.api.client.registry.BSStatsTabs; 13 | import io.github.thecsdev.betterstats.api.client.util.StatFilterSettings; 14 | import io.github.thecsdev.betterstats.api.util.stats.SUMobStat; 15 | import io.github.thecsdev.tcdcommons.api.client.gui.panel.TPanelElement; 16 | import io.github.thecsdev.tcdcommons.api.util.annotations.Virtual; 17 | 18 | public final @Internal class MonstersHuntedStatsTab extends MobStatsTab 19 | { 20 | // ================================================== 21 | public final @Override Component getName() { return translatable("soundCategory.hostile"); } 22 | // -------------------------------------------------- 23 | protected final @Override void processWidget(MobStatWidget widget) 24 | { 25 | super.processWidget(widget); 26 | if(widget.getStat().kills > 0) widget.setOutlineColor(BSStatsTabs.COLOR_SPECIAL); 27 | else if(!widget.getStat().isEmpty()) widget.setOutlineColor(TPanelElement.COLOR_OUTLINE); 28 | } 29 | // -------------------------------------------------- 30 | protected @Virtual Predicate getPredicate(StatFilterSettings filterSettings) 31 | { 32 | final String sq = filterSettings.getPropertyOrDefault(StatsTabUtils.FILTER_ID_SEARCH, ""); 33 | final Predicate sqPred = stat -> stat.matchesSearchQuery(sq); 34 | return sqPred.and(stat -> stat.getEntityType().getCategory() == MobCategory.MONSTER); 35 | } 36 | // ================================================== 37 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/client/gui/stats/panel/impl/StatFiltersPanelProxyImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.client.gui.stats.panel.impl; 2 | 3 | import static io.github.thecsdev.betterstats.client.gui.stats.panel.StatsTabPanel.FILTER_ID_SCROLL_CACHE; 4 | 5 | import java.util.Objects; 6 | 7 | import org.jetbrains.annotations.ApiStatus.Internal; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import io.github.thecsdev.betterstats.api.client.registry.StatsTab; 11 | import io.github.thecsdev.betterstats.api.client.util.StatFilterSettings; 12 | import io.github.thecsdev.betterstats.client.gui.stats.panel.StatFiltersPanel.StatFiltersPanelProxy; 13 | import io.github.thecsdev.betterstats.client.gui.stats.panel.impl.BetterStatsPanel.BetterStatsPanelProxy; 14 | 15 | final @Internal class StatFiltersPanelProxyImpl implements StatFiltersPanelProxy 16 | { 17 | // ================================================== 18 | protected final BetterStatsPanel bsPanel; 19 | protected final BetterStatsPanelProxy proxy; 20 | // ================================================== 21 | public StatFiltersPanelProxyImpl(BetterStatsPanel bsPanel) throws NullPointerException 22 | { 23 | this.bsPanel = Objects.requireNonNull(bsPanel); 24 | this.proxy = bsPanel.proxy; 25 | } 26 | // ================================================== 27 | public final @Override StatFilterSettings getFilterSettings() { return this.proxy.getFilterSettings(); } 28 | public final @Override @Nullable StatsTab getSelectedStatsTab() { return this.proxy.getSelectedStatsTab(); } 29 | public final @Override void setSelectedStatsTab(StatsTab statsTab) 30 | { 31 | getFilterSettings().setProperty(FILTER_ID_SCROLL_CACHE, 0D); //clear scroll cache when switching tabs 32 | this.proxy.setSelectedStatsTab(statsTab); 33 | } 34 | public final @Override void refreshStatsTab() 35 | { 36 | getFilterSettings().setProperty(FILTER_ID_SCROLL_CACHE, 0D); //clear scroll cache when changing filter settings 37 | this.bsPanel.refreshStatsTab(); 38 | } 39 | // ================================================== 40 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/util/enumerations/MobStatType.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.util.enumerations; 2 | 3 | import java.util.Objects; 4 | import java.util.function.Function; 5 | import net.minecraft.network.chat.Component; 6 | import net.minecraft.stats.Stat; 7 | import net.minecraft.stats.StatType; 8 | import net.minecraft.stats.Stats; 9 | import io.github.thecsdev.betterstats.api.util.stats.SUMobStat; 10 | import io.github.thecsdev.betterstats.util.BST; 11 | import io.github.thecsdev.tcdcommons.api.util.interfaces.ITextProvider; 12 | 13 | public enum MobStatType implements ITextProvider 14 | { 15 | // ================================================== 16 | KILLED(Stats.ENTITY_KILLED, BST.stp_mc_killed(), s -> s.kills), 17 | KILLED_BY(Stats.ENTITY_KILLED_BY, BST.stp_mc_killedBy(), s -> s.deaths); 18 | // ================================================== 19 | private final StatType statType; 20 | private final Component text; 21 | private final Function statValueSupplier; 22 | // ================================================== 23 | private MobStatType(StatType statType, Component text, Function statValueSupplier) 24 | { 25 | this.statType = Objects.requireNonNull(statType); 26 | this.text = Objects.requireNonNull(text); 27 | this.statValueSupplier = Objects.requireNonNull(statValueSupplier); 28 | } 29 | // ================================================== 30 | public final StatType getStatType() { return this.statType; } 31 | public final @Override Component getText() { return this.text; } 32 | public final int getStatValue(SUMobStat stat) throws NullPointerException 33 | { 34 | return this.statValueSupplier.apply(Objects.requireNonNull(stat)); 35 | } 36 | // -------------------------------------------------- 37 | public static final boolean isMobStat(Stat stat) 38 | { 39 | final var statType = stat.getType(); 40 | for(final var val : MobStatType.values()) 41 | if(Objects.equals(statType, val.statType)) 42 | return true; 43 | return false; 44 | } 45 | // ================================================== 46 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/util/io/IllegalHeaderException.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.util.io; 2 | 3 | import java.io.IOException; 4 | import java.util.Objects; 5 | import net.minecraft.network.FriendlyByteBuf; 6 | 7 | /** 8 | * An {@link IOException} that takes place when reading a {@link RAMStatsProvider}'s 9 | * data form a {@link FriendlyByteBuf} that contains invalid header data. 10 | * @see StatsProviderIO#read(FriendlyByteBuf, IEditableStatsProvider) 11 | */ 12 | public final class IllegalHeaderException extends IOException 13 | { 14 | // ================================================== 15 | private static final long serialVersionUID = -1266798578155159251L; 16 | // -------------------------------------------------- 17 | private final String expected, got; 18 | // ================================================== 19 | public IllegalHeaderException(String expected, String got) 20 | { 21 | super(String.format("Failed to read a buffer; Illegal header. Expected \"%s\", but \"%s\" was present instead.", 22 | Objects.requireNonNull(expected), Objects.requireNonNull(got))); 23 | this.expected = expected; 24 | this.got = got; 25 | } 26 | // ================================================== 27 | /** 28 | * Returns the header {@link String} that was expected to be in a {@link FriendlyByteBuf} at a given point. 29 | */ 30 | public final String getExpected() { return this.expected; } 31 | 32 | /** 33 | * Returns the {@link String} that was obtained instead of the {@link #getExpected()} one. 34 | */ 35 | public final String getGot() { return this.got; } 36 | // ================================================== 37 | public final @Override int hashCode() { return Objects.hash(expected, got); } 38 | public final @Override boolean equals(Object obj) 39 | { 40 | if (this == obj) return true; 41 | if (obj == null || getClass() != obj.getClass()) return false; 42 | IllegalHeaderException other = (IllegalHeaderException) obj; 43 | return Objects.equals(this.expected, other.expected) && Objects.equals(this.got, other.got); 44 | } 45 | // ================================================== 46 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/BetterStatsConfig.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats; 2 | 3 | import io.github.thecsdev.betterstats.util.stats.SASConfig; 4 | import io.github.thecsdev.tcdcommons.api.config.AutoConfig; 5 | import io.github.thecsdev.tcdcommons.api.config.annotation.NonSerialized; 6 | import io.github.thecsdev.tcdcommons.api.config.annotation.SerializedAs; 7 | 8 | public class BetterStatsConfig extends AutoConfig 9 | { 10 | // ================================================== 11 | public static @NonSerialized boolean DEV_ENV = false; 12 | // Temporary configurations bound to the current game session 13 | public static @NonSerialized boolean DEBUG_MODE = false; 14 | public static @NonSerialized boolean SHOW_HUD_SCREEN = true; //client-sided 15 | // -------------------------------------------------- 16 | public @SerializedAs("client-guiSmoothScroll") boolean guiSmoothScroll = true; 17 | public @SerializedAs("client-guiMobsFollowCursor") boolean guiMobsFollowCursor = true; 18 | public @SerializedAs("client-trustAllServersBssNet") boolean trustAllServersBssNet = true; 19 | public @SerializedAs("client-allowStatsSharing") boolean netPref_allowStatsSharing = false; //v3.11+ 20 | public @SerializedAs("client-wideStatsPanel") boolean wideStatsPanel = false; //v3.12+ 21 | public @SerializedAs("client-centeredStatsPanel") boolean centeredStatsPanel = false; //v3.12+ 22 | public @SerializedAs("client-updateItemGroupsOnJoin") boolean updateItemGroupsOnJoin = true; //3.13.6 23 | public @SerializedAs("client-hidePlayerInfo") boolean hidePlayerInfo = false; //3.13.7 24 | public @SerializedAs("server-registerCommands") boolean registerCommands = true; 25 | public @SerializedAs("server-enableSAS") boolean enableServerSAS = false; 26 | public @SerializedAs("server-sasConfig") SASConfig sasConfig = new SASConfig(); 27 | // ================================================== 28 | public BetterStatsConfig(String name) { super(name); } 29 | // ================================================== 30 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/client/gui/widget/ScrollBarWidget.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.client.gui.widget; 2 | 3 | import java.awt.Color; 4 | 5 | import io.github.thecsdev.tcdcommons.api.client.gui.panel.TPanelElement; 6 | import io.github.thecsdev.tcdcommons.api.client.gui.util.TDrawContext; 7 | import io.github.thecsdev.tcdcommons.api.client.gui.widget.TScrollBarWidget; 8 | import io.github.thecsdev.tcdcommons.api.util.annotations.Virtual; 9 | 10 | /** 11 | * A {@link TScrollBarWidget} implementation whose visuals use 12 | * flat/static colors, instead of the default textures. 13 | */ 14 | public @Virtual class ScrollBarWidget extends TScrollBarWidget 15 | { 16 | // ================================================== 17 | private static final int COLOR_BLACK = Color.BLACK.getRGB(); 18 | private static final int COLOR_NORMAL = new Color(255,255,255,50).getRGB(); 19 | private static final int COLOR_HOVERED = new Color(255,255,255,110).getRGB(); 20 | // ================================================== 21 | public ScrollBarWidget(int x, int y, int width, int height, TPanelElement target) { super(x, y, width, height, target); } 22 | public ScrollBarWidget(int x, int y, int width, int height, TPanelElement target, boolean autoSetScrollFlags) { super(x, y, width, height, target, autoSetScrollFlags); } 23 | // ================================================== 24 | public @Virtual @Override void render(TDrawContext pencil) 25 | { 26 | pencil.drawTFill(1342177280); 27 | pencil.drawTBorder(COLOR_BLACK); 28 | renderSliderKnob(pencil); 29 | } 30 | // -------------------------------------------------- 31 | public @Virtual @Override void renderSliderProgressBar(TDrawContext pencil) {/*doesn't have a visual one*/} 32 | // -------------------------------------------------- 33 | public @Virtual @Override void renderSliderKnob 34 | (TDrawContext pencil, int knobX, int knobY, int knobWidth, int knobHeight) 35 | { 36 | pencil.fill( 37 | knobX, knobY, 38 | knobX + knobWidth, knobY + knobHeight, 39 | isFocusedOrHovered() ? COLOR_HOVERED : COLOR_NORMAL); 40 | } 41 | // ================================================== 42 | } -------------------------------------------------------------------------------- /fabric/build.gradle: -------------------------------------------------------------------------------- 1 | import static TCDModUtils.getMixinFileNames; 2 | 3 | plugins { id 'com.github.johnrengelman.shadow' } 4 | 5 | architectury { platformSetupLoomIde(); fabric() } 6 | 7 | configurations 8 | { 9 | common { canBeResolved = true; canBeConsumed = false } 10 | 11 | compileClasspath.extendsFrom common 12 | runtimeClasspath.extendsFrom common 13 | developmentFabric.extendsFrom common 14 | 15 | // Files in this configuration will be bundled into your mod using the Shadow plugin. 16 | // Don't use the `shadow` configuration from the plugin itself as it's meant for excluding files. 17 | shadowBundle { canBeResolved = true; canBeConsumed = false } 18 | } 19 | 20 | repositories 21 | { 22 | maven { url = "https://maven.terraformersmc.com/releases/" } 23 | } 24 | 25 | dependencies 26 | { 27 | modImplementation "net.fabricmc:fabric-loader:${parent["fabric.loader_version"]}" 28 | modImplementation "net.fabricmc.fabric-api:fabric-api:${parent["fabric.api_version"]}" 29 | modImplementation "dev.architectury:architectury-fabric:${parent["architectury.version"]}" 30 | 31 | common(project(path: ':common', configuration: 'namedElements')) { transitive false } 32 | shadowBundle project(path: ':common', configuration: 'transformProductionFabric') 33 | 34 | // Mod Menu 35 | modCompileOnly "com.terraformersmc:modmenu:${parent["modmenu.version"]}" 36 | 37 | //jar files in jarjar-excluded 38 | modImplementation(fileTree(dir: 'src/main/resources/META-INF/jarjar-excluded', include: '*.jar')) 39 | } 40 | 41 | processResources 42 | { 43 | //obtain array of applicable mixin files, and turn it into string 44 | String mixins = (getMixinFileNames(project) + getMixinFileNames(project(":common"))) 45 | .collect { "\"$it\"" } 46 | .join(',').with { "[$it]" } 47 | 48 | //expand root project properties to the 'fabric.mod.json' file 49 | filesMatching(["fabric.mod.json", "*.json"]) { 50 | expand(properties: [mixins: mixins] + parent.properties) 51 | } 52 | } 53 | 54 | shadowJar 55 | { 56 | configurations = [project.configurations.shadowBundle] 57 | archiveClassifier = 'dev-shadow' 58 | 59 | exclude('META-INF/jarjar-excluded/*.jar') 60 | } 61 | 62 | remapJar { inputFile.set(shadowJar.archiveFile) } 63 | -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/BetterStatsProperties.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats; 2 | 3 | import org.jetbrains.annotations.ApiStatus.Internal; 4 | 5 | import com.google.gson.JsonObject; 6 | 7 | import io.github.thecsdev.tcdcommons.TCDCommons; 8 | 9 | /** 10 | * Properties from the betterstats.properties.json file. 11 | */ 12 | @Internal 13 | public final class BetterStatsProperties 14 | { 15 | // ================================================== 16 | private static final JsonObject MOD_PROPERTIES; 17 | // -------------------------------------------------- 18 | public static final String URL_SOURCES, URL_ISSUES, URL_CURSEFORGE, URL_MODRINTH; 19 | public static final String URL_WEBSITE, URL_YOUTUBE; 20 | public static final String URL_QS_LEGAL, URL_REMOTE_APIS; 21 | // ================================================== 22 | public static final void init() {/*calls static*/} 23 | static 24 | { 25 | //read the mod properties resource file 26 | try 27 | { 28 | final var propertiesStream = BetterStats.class.getResourceAsStream("/betterstats.properties.json"); 29 | final var propertiesJsonStr = new String(propertiesStream.readAllBytes()); 30 | propertiesStream.close(); 31 | MOD_PROPERTIES = TCDCommons.GSON.fromJson(propertiesJsonStr, JsonObject.class); 32 | } 33 | catch(Exception e) { throw new ExceptionInInitializerError(e); } 34 | 35 | //read links 36 | final var links = MOD_PROPERTIES.get("links").getAsJsonObject(); 37 | URL_SOURCES = links.get("sources") .getAsString(); 38 | URL_ISSUES = links.get("issues") .getAsString(); 39 | 40 | URL_CURSEFORGE = links.get("curseforge") .getAsString(); 41 | URL_MODRINTH = links.get("modrinth") .getAsString(); 42 | URL_WEBSITE = links.get("website") .getAsString(); 43 | URL_YOUTUBE = links.get("youtube") .getAsString(); 44 | 45 | URL_QS_LEGAL = links.get("quickshare_legal").getAsString(); 46 | URL_REMOTE_APIS = links.get("remote_api_links").getAsString(); 47 | } 48 | // ================================================== 49 | public static final @Internal JsonObject getModProperties() { return MOD_PROPERTIES; } 50 | // ================================================== 51 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/client/gui/stats/tabs/FoodStuffsStatsTab.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.client.gui.stats.tabs; 2 | 3 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.translatable; 4 | 5 | import java.util.function.Predicate; 6 | import net.minecraft.core.component.DataComponents; 7 | import net.minecraft.network.chat.Component; 8 | import org.jetbrains.annotations.ApiStatus.Internal; 9 | 10 | import io.github.thecsdev.betterstats.api.client.gui.stats.widget.ItemStatWidget; 11 | import io.github.thecsdev.betterstats.api.client.gui.util.StatsTabUtils; 12 | import io.github.thecsdev.betterstats.api.client.registry.BSStatsTabs; 13 | import io.github.thecsdev.betterstats.api.client.util.StatFilterSettings; 14 | import io.github.thecsdev.betterstats.api.util.enumerations.FilterGroupBy; 15 | import io.github.thecsdev.betterstats.api.util.stats.SUItemStat; 16 | import io.github.thecsdev.tcdcommons.api.client.gui.panel.TPanelElement; 17 | import io.github.thecsdev.tcdcommons.api.util.annotations.Virtual; 18 | 19 | public final @Internal class FoodStuffsStatsTab extends ItemStatsTab 20 | { 21 | // ================================================== 22 | public final @Override Component getName() { return translatable("itemGroup.foodAndDrink"); } 23 | // -------------------------------------------------- 24 | protected final @Override FilterGroupBy getDefaultGroupFilter() { return FilterGroupBy.MOD; } 25 | // -------------------------------------------------- 26 | protected final @Override void processWidget(ItemStatWidget widget) 27 | { 28 | super.processWidget(widget); 29 | if(widget.getStat().used > 0) widget.setOutlineColor(BSStatsTabs.COLOR_SPECIAL); 30 | else if(!widget.getStat().isEmpty()) widget.setOutlineColor(TPanelElement.COLOR_OUTLINE); 31 | } 32 | // -------------------------------------------------- 33 | protected @Virtual @Override Predicate getPredicate(StatFilterSettings filterSettings) 34 | { 35 | final String sq = filterSettings.getPropertyOrDefault(StatsTabUtils.FILTER_ID_SEARCH, ""); 36 | final Predicate sqPred = stat -> stat.matchesSearchQuery(sq); 37 | return sqPred.and(stat -> stat.getItem().components().has(DataComponents.FOOD)); 38 | } 39 | // ================================================== 40 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/mixin/events/MixinServerStatHandler.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.mixin.events; 2 | 3 | import static io.github.thecsdev.betterstats.api.util.enumerations.ItemStatType.isItemStat; 4 | import static io.github.thecsdev.betterstats.api.util.enumerations.MobStatType.isMobStat; 5 | 6 | import io.github.thecsdev.tcdcommons.mixin.hooks.AccessorServerPlayer; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | 12 | import io.github.thecsdev.betterstats.network.BetterStatsServerPlayNetworkHandler; 13 | import io.github.thecsdev.betterstats.util.stats.StatAnnouncementSystem; 14 | import net.minecraft.server.level.ServerPlayer; 15 | import net.minecraft.stats.ServerStatsCounter; 16 | import net.minecraft.stats.Stat; 17 | import net.minecraft.stats.StatsCounter; 18 | import net.minecraft.world.entity.player.Player; 19 | 20 | //super low priority is used to avoid conflicts with other mods, and to really make sure 21 | //the code in these Mixin-s executes ONLY if the events are NOT cancelled by another mod 22 | @Mixin(value = ServerStatsCounter.class, priority = -9000) 23 | public abstract class MixinServerStatHandler extends StatsCounter 24 | { 25 | @Inject(method = "setValue", at = @At("HEAD")) 26 | public void onPreSetStat(Player player, Stat stat, int value, CallbackInfo ci) 27 | { 28 | //only handle server players 29 | if(player instanceof ServerPlayer sPlayer) 30 | { 31 | //handle SAS 32 | StatAnnouncementSystem.__handleStatChange(sPlayer, stat, stats.getInt(stat), value); 33 | } 34 | } 35 | 36 | @Inject(method = "setValue", at = @At("RETURN")) 37 | public void onSetStat(Player player, Stat stat, int value, CallbackInfo ci) 38 | { 39 | //only handle server players 40 | if(player instanceof ServerPlayer serverPlayer) 41 | { 42 | //if a stat is an item stat or a mob stat.. 43 | if(isItemStat(stat) || isMobStat(stat)) 44 | { 45 | //..handle live stats updates for those stat types 46 | if(((AccessorServerPlayer)(Object)serverPlayer).getServer().isRunning()) 47 | BetterStatsServerPlayNetworkHandler.of(serverPlayer).sendLiveStatsAttepmt(); 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/client/gui/stats/widget/CustomStatElement.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.client.gui.stats.widget; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | import io.github.thecsdev.betterstats.api.util.stats.SUGeneralStat; 6 | import io.github.thecsdev.tcdcommons.api.client.gui.other.TBlankElement; 7 | import io.github.thecsdev.tcdcommons.api.client.gui.util.TDrawContext; 8 | import io.github.thecsdev.tcdcommons.api.util.annotations.Virtual; 9 | import io.github.thecsdev.tcdcommons.api.util.enumerations.HorizontalAlignment; 10 | import net.minecraft.network.chat.Component; 11 | 12 | /** 13 | * A {@link TBlankElement} that only renders {@link Component}s on the 14 | * left and right side of the {@link CustomStatElement}.
15 | * Does not handle any user input or have a background color. 16 | */ 17 | public @Virtual class CustomStatElement extends TBlankElement 18 | { 19 | // ================================================== 20 | public static final int HEIGHT = Math.max(GeneralStatWidget.HEIGHT, ItemStatWidget.SIZE); 21 | // -------------------------------------------------- 22 | protected @Nullable Component txtLeft, txtRight; 23 | // ================================================== 24 | public CustomStatElement(int x, int y, int width, SUGeneralStat generalStat) 25 | { 26 | this(x, y, width, generalStat.getStatLabel(), generalStat.valueText); 27 | } 28 | 29 | public CustomStatElement(int x, int y, int width, @Nullable Component left, @Nullable Component right) 30 | { 31 | super(x, y, width, HEIGHT); 32 | this.txtLeft = left; 33 | this.txtRight = right; 34 | } 35 | // ================================================== 36 | public final @Nullable Component getLeftText() { return this.txtLeft; } 37 | public final @Nullable Component getRightText() { return this.txtLeft; } 38 | public @Virtual void setLeftText(@Nullable Component left) { this.txtLeft = left; } 39 | public @Virtual void setRightText(@Nullable Component right) { this.txtRight = right; } 40 | // ================================================== 41 | public @Virtual @Override void render(TDrawContext pencil) 42 | { 43 | pencil.drawTElementTextTH(this.txtLeft, HorizontalAlignment.LEFT); 44 | pencil.drawTElementTextTH(this.txtRight, HorizontalAlignment.RIGHT); 45 | } 46 | // ================================================== 47 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/util/BSUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.util; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import net.minecraft.world.item.CreativeModeTab; 6 | import net.minecraft.world.item.CreativeModeTabs; 7 | import net.minecraft.world.item.Item; 8 | import net.minecraft.world.item.Items; 9 | import org.jetbrains.annotations.Nullable; 10 | import org.jetbrains.annotations.ApiStatus.Internal; 11 | 12 | public final class BSUtils 13 | { 14 | // ================================================== 15 | /** 16 | * An {@link Item} to {@link CreativeModeTab} map. 17 | */ 18 | private static final HashMap ITG = new HashMap(); 19 | // ================================================== 20 | private BSUtils() {} 21 | // ================================================== 22 | /** 23 | * Updates the {@link #ITG} {@link Map} that is used by {@link #getItemGroup(Item)}. 24 | */ 25 | public static final @Internal void updateITG() 26 | { 27 | //clear the ITG, and then update it 28 | ITG.clear(); 29 | final var searchGroup = CreativeModeTabs.searchTab(); 30 | final var air = Items.AIR; 31 | for(final CreativeModeTab group : CreativeModeTabs.allTabs()) 32 | { 33 | //ignore the search group, as it is used for the 34 | //creative menu item search tab 35 | if(group == searchGroup) continue; 36 | 37 | //add group's items to ITG 38 | group.getDisplayItems().forEach(stack -> 39 | { 40 | //obtain the stack's item, and ensure an item is present 41 | //(in Minecraft's "language", AIR usually refers to "null") 42 | final var item = stack.getItem(); 43 | if(item == null || item == air) return; 44 | 45 | //put the item and its group to the ITG map 46 | ITG.put(item, group); 47 | }); 48 | } 49 | } 50 | // -------------------------------------------------- 51 | /** 52 | * Uses {@link #ITG} to find the {@link CreativeModeTab} for the given {@link Item}. 53 | * @param item The {@link Item} in question. 54 | * @apiNote An {@link Item} can be part of multiple {@link CreativeModeTab}s. 55 | * This method will return the first or last found {@link CreativeModeTab}. Keep that in mind. 56 | */ 57 | public static @Nullable CreativeModeTab getItemGroup(Item item) { return ITG.getOrDefault(item, null); } 58 | // ================================================== 59 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to Gradle. 2 | org.gradle.jvmargs=-Xmx2G 3 | org.gradle.parallel=true 4 | 5 | # Other properties 6 | enabled_platforms = fabric,neoforge 7 | 8 | # Maven properties 9 | maven.group = io.github.thecsdev 10 | 11 | # Mod properties 12 | mod.id = betterstats 13 | mod.version = 4.0.0 14 | mod.name = Better Statistics Screen 15 | mod.description = Improves the statistics screen and makes it more useful. 16 | mod.author = TheCSDev 17 | mod.license = LGPL-3.0 18 | 19 | mod.link.homepage = https://github.com/TheCSDev 20 | mod.link.sources = https://github.com/TheCSDev/mc-better-stats 21 | mod.link.issues = https://github.com/TheCSDev/mc-better-stats/issues 22 | 23 | modmenu_link_curseforge = https://www.curseforge.com/projects/667464 24 | modmenu_link_modrinth = https://modrinth.com/mod/n6PXGAoM 25 | modmenu_link_website = https://thecsdev.com/ 26 | modmenu_link_youtube = https://www.youtube.com/@TheCSDev 27 | 28 | # 'pack.mcmeta' properties 29 | pack.format = 88 30 | 31 | # Minecraft properties 32 | minecraft.version = 1.21.10 33 | 34 | # Fabric properties 35 | fabric.loader_version = 0.17.3 36 | fabric.api_version = 0.135.0+1.21.10 37 | 38 | # fabric.mod.json properties 39 | fabric.json.entrypoints.client = io.github.thecsdev.betterstats.fabric.BetterStatsFabric 40 | fabric.json.entrypoints.server = io.github.thecsdev.betterstats.fabric.BetterStatsFabric 41 | fabric.json.entrypoints.modmenu = io.github.thecsdev.betterstats.fabric.modmenu.ModMenuApiImpl 42 | 43 | fabric.json.depends.java = >=21 44 | fabric.json.depends.minecraft = >=1.21.10 45 | fabric.json.depends.fabricloader = >=0.17.3 46 | fabric.json.depends.fabric-api = >=0.135.0 47 | fabric.json.depends.architectury = >=18.0.5 48 | fabric.json.depends.tcdcommons = >=4.0.0 49 | 50 | # Mod-Menu properties 51 | modmenu.version = 16.0.0-rc.1 52 | 53 | # Neo-Forge properties 54 | neoforge.version = 21.10.16-beta 55 | 56 | # neoforge.mods.toml properties 57 | net.toml.loaderVersion = [2,) 58 | neo.toml.dependencies.neoforge = [21.10.16-beta,) 59 | neo.toml.dependencies.minecraft = [1.21.10,) 60 | neo.toml.dependencies.architectury = [18.0.5,) 61 | neo.toml.dependencies.tcdcommons = [4.0.0,) 62 | 63 | # Architectury properties 64 | architectury.version = 18.0.5 65 | 66 | # Roughly Enough Items properties 67 | rei.version = 17.0.789 -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/util/enumerations/ItemStatType.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.util.enumerations; 2 | 3 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.translatable; 4 | 5 | import java.util.Objects; 6 | import java.util.function.Function; 7 | import net.minecraft.network.chat.Component; 8 | import net.minecraft.stats.Stat; 9 | import net.minecraft.stats.StatType; 10 | import net.minecraft.stats.Stats; 11 | import io.github.thecsdev.betterstats.api.util.stats.SUItemStat; 12 | import io.github.thecsdev.tcdcommons.api.util.interfaces.ITextProvider; 13 | 14 | public enum ItemStatType implements ITextProvider 15 | { 16 | // ================================================== 17 | MINED(Stats.BLOCK_MINED, translatable("stat_type.minecraft.mined"), s -> s.mined), 18 | CRAFTED(Stats.ITEM_CRAFTED, translatable("stat_type.minecraft.crafted"), s -> s.crafted), 19 | PICKED_UP(Stats.ITEM_PICKED_UP, translatable("stat_type.minecraft.picked_up"), s -> s.pickedUp), 20 | DROPPED(Stats.ITEM_DROPPED, translatable("stat_type.minecraft.dropped"), s -> s.dropped), 21 | USED(Stats.ITEM_USED, translatable("stat_type.minecraft.used"), s -> s.used), 22 | BROKEN(Stats.ITEM_BROKEN, translatable("stat_type.minecraft.broken"), s -> s.broken); 23 | // ================================================== 24 | private final StatType statType; 25 | private final Component text; 26 | private final Function statValueSupplier; 27 | // ================================================== 28 | private ItemStatType(StatType statType, Component text, Function statValueSupplier) 29 | { 30 | this.statType = Objects.requireNonNull(statType); 31 | this.text = Objects.requireNonNull(text); 32 | this.statValueSupplier = Objects.requireNonNull(statValueSupplier); 33 | } 34 | // ================================================== 35 | public final StatType getStatType() { return this.statType; } 36 | public final @Override Component getText() { return this.text; } 37 | public final int getStatValue(SUItemStat stat) throws NullPointerException 38 | { 39 | return this.statValueSupplier.apply(Objects.requireNonNull(stat)); 40 | } 41 | // -------------------------------------------------- 42 | public static final boolean isItemStat(Stat stat) 43 | { 44 | final var statType = stat.getType(); 45 | for(final var val : ItemStatType.values()) 46 | if(Objects.equals(statType, val.statType)) 47 | return true; 48 | return false; 49 | } 50 | // ================================================== 51 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # -------------------- Eclipse IDE ------------------- 2 | .metadata 3 | bin/ 4 | tmp/ 5 | *.tmp 6 | *.bak 7 | *.swp 8 | *~.nib 9 | local.properties 10 | .settings/ 11 | .loadpath 12 | .recommenders 13 | 14 | # External tool builders 15 | .externalToolBuilders/ 16 | 17 | # Locally stored "Eclipse launch configurations" 18 | *.launch 19 | 20 | # PyDev specific (Python IDE for Eclipse) 21 | *.pydevproject 22 | 23 | # CDT-specific (C/C++ Development Tooling) 24 | .cproject 25 | 26 | # CDT- autotools 27 | .autotools 28 | 29 | # Java annotation processor (APT) 30 | .factorypath 31 | 32 | # PDT-specific (PHP Development Tools) 33 | .buildpath 34 | 35 | # sbteclipse plugin 36 | .target 37 | 38 | # Tern plugin 39 | .tern-project 40 | 41 | # TeXlipse plugin 42 | .texlipse 43 | 44 | # STS (Spring Tool Suite) 45 | .springBeans 46 | 47 | # Code Recommenders 48 | .recommenders/ 49 | 50 | # Annotation Processing 51 | .apt_generated/ 52 | .apt_generated_test/ 53 | 54 | # Scala IDE specific (Scala & Java development for Eclipse) 55 | .cache-main 56 | .scala_dependencies 57 | .worksheet 58 | 59 | # Uncomment this line if you wish to ignore the project description file. 60 | # Typically, this file would be tracked if it contains build/dependency configurations: 61 | #.project 62 | 63 | # ----------------------- Java ----------------------- 64 | # Compiled class file 65 | *.class 66 | 67 | # Log file 68 | *.log 69 | 70 | # BlueJ files 71 | *.ctxt 72 | 73 | # Mobile Tools for Java (J2ME) 74 | .mtj.tmp/ 75 | 76 | # Package Files # 77 | *.jar 78 | !**/src/main/resources/**/*.jar 79 | *.war 80 | *.nar 81 | *.ear 82 | *.zip 83 | *.tar.gz 84 | *.rar 85 | 86 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 87 | hs_err_pid* 88 | /.gradle/ 89 | /build/ 90 | 91 | # -------------------- Idea IDE ------------------- 92 | **/.idea/ 93 | 94 | # ----------------------- Gradle ----------------------- 95 | .gradle 96 | **/build/ 97 | !src/**/build/ 98 | 99 | # Ignore Gradle GUI config 100 | gradle-app.setting 101 | 102 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 103 | !gradle-wrapper.jar 104 | 105 | # Avoid ignore Gradle wrappper properties 106 | !gradle-wrapper.properties 107 | 108 | # Cache of project 109 | .gradletasknamecache 110 | 111 | # Eclipse Gradle plugin generated files 112 | # Eclipse Core 113 | .project 114 | # JDT-specific (Eclipse Java Development Tools) 115 | .classpath 116 | 117 | # ----------------------- Fabric ----------------------- 118 | run/ 119 | -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/client/gui/screen/BetterStatsScreenWrapper.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.client.gui.screen; 2 | 3 | import static io.github.thecsdev.betterstats.client.BetterStatsClient.MC_CLIENT; 4 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.translatable; 5 | 6 | import java.util.Objects; 7 | 8 | import org.jetbrains.annotations.ApiStatus.Internal; 9 | 10 | import io.github.thecsdev.betterstats.api.client.util.io.LocalPlayerStatsProvider; 11 | import io.github.thecsdev.betterstats.api.util.interfaces.IThirdPartyStatsListener; 12 | import io.github.thecsdev.betterstats.client.network.OtherClientPlayerStatsProvider; 13 | import io.github.thecsdev.betterstats.util.BST; 14 | import io.github.thecsdev.tcdcommons.api.client.gui.screen.TDialogBoxScreen; 15 | import io.github.thecsdev.tcdcommons.api.client.gui.screen.TScreenWrapper; 16 | import io.github.thecsdev.tcdcommons.api.client.network.PlayerBadgeNetworkListener; 17 | import io.github.thecsdev.tcdcommons.api.client.util.interfaces.IStatsListener; 18 | 19 | final @Internal class BetterStatsScreenWrapper 20 | extends TScreenWrapper 21 | implements IStatsListener, PlayerBadgeNetworkListener, IThirdPartyStatsListener 22 | { 23 | // ================================================== 24 | public BetterStatsScreenWrapper(BetterStatsScreen target) { super(target); } 25 | // ================================================== 26 | public final @Override void onStatsReady() 27 | { 28 | //if the user is viewing their own statistics, and they receive a statistics packet... 29 | if(this.target.getStatsProvider() == LocalPlayerStatsProvider.getInstance()) 30 | //...refresh the statistics screen 31 | this.target.refresh(); 32 | } 33 | 34 | public final @Override void onStatsReady(TpslContext context) 35 | { 36 | //only handle this if the screen is listening for these stats 37 | if(this.target.getStatsProvider() instanceof OtherClientPlayerStatsProvider tps && 38 | Objects.equals(context.getPlayerName(), tps.getPlayerName())) 39 | { 40 | //handle based on response type 41 | switch(context.getType()) 42 | { 43 | case SAME_SERVER_PLAYER: this.target.refresh(); break; 44 | case SAME_SERVER_PLAYER_NOT_FOUND: 45 | final var dialog = new TDialogBoxScreen(this, 46 | translatable("mco.configure.world.players.error"), 47 | BST.gui_tpsbs_ssps_playerNotFound()); 48 | MC_CLIENT.setScreen(dialog.getAsScreen()); 49 | break; 50 | default: break; 51 | } 52 | } 53 | } 54 | // -------------------------------------------------- 55 | public @Override void onPlayerBadgesReady() { onStatsReady(); } 56 | // ================================================== 57 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/mod_issue_bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a bug report for this mod. 3 | title: "[vX.X+loader-1.X.X] Bug report name" 4 | labels: ["Bug"] 5 | assignees: [] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: Thanks for taking the time to fill out this bug report! 10 | - type: markdown 11 | attributes: 12 | value: | 13 | **Note:** Issue titles start with the square brackets that contain the full mod version. 14 | **Format:** v[mod_version]+[mod_loader]-[minecraft_version] 15 | **Example: v1.0+fabric-1.20** 16 | - type: textarea 17 | id: what-happened 18 | attributes: 19 | label: What happened? 20 | description: Please describe what happened, aka the bug or issue you encountered. 21 | placeholder: Describe the issue here! 22 | value: "When doing something expecting X to happen, I got Y instad." 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: reproduction-steps 27 | attributes: 28 | label: Steps to reproduce 29 | description: If applicable, please list the steps to reproduce the issue. 30 | placeholder: List steps to reproduce here! 31 | value: | 32 | 1. Go to... 33 | 2. Then do... 34 | 3. See the bug or issue or a crash 35 | validations: 36 | required: false 37 | - type: textarea 38 | id: logs 39 | attributes: 40 | label: Relevant log output or crash report 41 | description: | 42 | Please copy and paste any relevant log outputs or crash reports here. 43 | Alternatively, you can publish the crash report to [Crashy](https://crashy.net/) and link it here. 44 | render: shell 45 | - type: textarea 46 | id: other-mods 47 | attributes: 48 | label: Other installed mods 49 | description: If applicable, please list all other mods you have installed alongside this one. 50 | placeholder: List other installed mods here, or "N/A" if you don't have any other mods installed. 51 | value: | 52 | 1. First mod's name 53 | 2. Second mod's name 54 | 3. And so on... If you use a mod pack instead, state that and name the mod pack. 55 | validations: 56 | required: true 57 | - type: checkboxes 58 | id: terms 59 | attributes: 60 | label: Code of Conduct 61 | description: "By submitting this issue, you agree to the following:" 62 | options: 63 | - label: I agree that if the issue is regarding a crash, I gave the logs and the crash report. 64 | required: true 65 | - label: I agree that I will be available later for any follow-up questions to help diagnose and resolve the issue. 66 | required: true 67 | -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/util/enumerations/FilterSortMobsBy.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.util.enumerations; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Objects; 7 | import net.minecraft.network.chat.Component; 8 | import io.github.thecsdev.betterstats.api.util.stats.SUMobStat; 9 | import io.github.thecsdev.betterstats.util.BST; 10 | import io.github.thecsdev.tcdcommons.api.util.interfaces.ITextProvider; 11 | 12 | /** 13 | * A statistics filter {@link Enum} that dictates how 14 | * {@link SUMobStat} statistics entries are sorted. 15 | */ 16 | public enum FilterSortMobsBy implements ITextProvider 17 | { 18 | // ================================================== 19 | DEFAULT(BST.filter_groupBy_default()), 20 | KILLS(MobStatType.KILLED.getText()), 21 | DEATHS(MobStatType.KILLED_BY.getText()); 22 | // ================================================== 23 | private final Component text; 24 | // -------------------------------------------------- 25 | private FilterSortMobsBy(Component text) { this.text = Objects.requireNonNull(text); } 26 | public final @Override Component getText() { return this.text; } 27 | // ================================================== 28 | /** 29 | * Sorts a {@link Map} of {@link SUMobStat}s based on {@code this} {@link FilterSortMobsBy}. 30 | * @param stats The {@link SUMobStat}s to sort. 31 | */ 32 | public final void sortMobStats(Map> stats) { sortMobStats(stats, this); } 33 | 34 | /** 35 | * Sorts a {@link List}<{@link SUMobStat}> based on {@code this} {@link FilterSortMobsBy}. 36 | * @param stats The {@link SUMobStat}s to sort. 37 | */ 38 | public final void sortMobStats(List stats) { sortMobStats(stats, this); } 39 | // -------------------------------------------------- 40 | /** 41 | * Sorts a {@link Map} of {@link SUMobStat}s based on {@link FilterSortMobsBy}. 42 | * @param stats The {@link SUMobStat}s to sort. 43 | * @param sortBy The {@link FilterSortMobsBy}. 44 | */ 45 | public static final void sortMobStats(Map> stats, FilterSortMobsBy sortBy) 46 | { 47 | for(final var entry : stats.entrySet()) 48 | sortMobStats(entry.getValue(), sortBy); 49 | } 50 | 51 | /** 52 | * Sorts a {@link List}<{@link SUMobStat}> based on {@link FilterSortMobsBy}. 53 | * @param stats The {@link SUMobStat}s to sort. 54 | * @param sortBy The {@link FilterSortMobsBy}. 55 | */ 56 | public static final void sortMobStats(List stats, FilterSortMobsBy sortBy) 57 | { 58 | switch(sortBy) 59 | { 60 | case KILLS: Collections.sort(stats, (s1, s2) -> Integer.compare(s2.kills, s1.kills)); break; 61 | case DEATHS: Collections.sort(stats, (s1, s2) -> Integer.compare(s2.deaths, s1.deaths)); break; 62 | default: break; 63 | } 64 | } 65 | // ================================================== 66 | } -------------------------------------------------------------------------------- /common/src/main/resources/betterstats.properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": 3 | { 4 | "homepage": "${properties["mod.link.homepage"]}", 5 | "sources": "${properties["mod.link.sources"]}", 6 | "issues": "${properties["mod.link.issues"]}", 7 | 8 | "curseforge": "${properties.modmenu_link_curseforge}", 9 | "modrinth": "${properties.modmenu_link_modrinth}", 10 | "website": "${properties.modmenu_link_website}", 11 | "youtube": "${properties.modmenu_link_youtube}", 12 | 13 | "quickshare_legal": "https://thecsdev.github.io/ref/bss_qs_legal", 14 | "remote_api_links": "https://thecsdev.github.io/api/links/betterstats.json" 15 | }, 16 | 17 | "contributors": 18 | [ 19 | { 20 | "name": "MrLoLf", 21 | "contact": { "homepage": "https://github.com/MrLoLf" } 22 | }, 23 | { 24 | "name": "Black-Hole", 25 | "contact": { "homepage": "https://github.com/Black-Hole" } 26 | }, 27 | { 28 | "name": "Sw3et-Dre4mer", 29 | "contact": { "homepage": "https://github.com/Sw3et-Dre4mer" } 30 | }, 31 | { 32 | "name": "Felix14-v2", 33 | "contact": { "homepage": "https://github.com/Felix14-v2" } 34 | }, 35 | { 36 | "name": "Amirhan-Taipovjan-Greatest-I", 37 | "contact": { "homepage": "https://github.com/Amirhan-Taipovjan-Greatest-I" } 38 | }, 39 | { 40 | "name": "rvbsm", 41 | "contact": { "homepage": "https://github.com/rvbsm" } 42 | }, 43 | { 44 | "name": "IwasConfused", 45 | "contact": { "homepage": "https://github.com/IwasConfused" } 46 | }, 47 | { 48 | "name": "notlin4", 49 | "contact": { "homepage": "https://github.com/notlin4" } 50 | }, 51 | { 52 | "name": "Atakku", 53 | "contact": { "homepage": "https://github.com/Atakku" } 54 | }, 55 | { 56 | "name": "Korben", 57 | "contact": { "homepage": "https://github.com/mpustovoi" } 58 | }, 59 | { 60 | "name": "MC洛洛", 61 | "contact": { "homepage": "https://github.com/MC-luoluo" } 62 | }, 63 | { 64 | "name": "dirtTW", 65 | "contact": { "homepage": "https://github.com/yichifauzi" } 66 | }, 67 | { 68 | "name": "mcxiangdon", 69 | "contact": { "homepage": "https://github.com/mcxiangdon" } 70 | }, 71 | { 72 | "name": "Jerome", 73 | "contact": { "homepage": "https://github.com/jeromecools" } 74 | }, 75 | { 76 | "name": "Missing_Love", 77 | "contact": { "homepage": "https://github.com/Q2297045667" } 78 | } 79 | ], 80 | 81 | "special_thanks": 82 | [ 83 | { 84 | "name": "You", 85 | "contact": { "homepage": "https://thecsdev.github.io/ref/thank_you" } 86 | }, 87 | { 88 | "name": "FabricMC", 89 | "contact": { "homepage": "https://fabricmc.net/" } 90 | }, 91 | { 92 | "name": "Sinytra", 93 | "contact": { "homepage": "https://sinytra.org/" } 94 | }, 95 | { 96 | "name": "Sternschnaube", 97 | "contact": { "homepage": "https://github.com/sternschnaube" } 98 | } 99 | ] 100 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/client/badge/BSClientPlayerBadge.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.client.badge; 2 | 3 | import java.util.Objects; 4 | import java.util.function.Function; 5 | import net.minecraft.network.chat.Component; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import io.github.thecsdev.betterstats.BetterStats; 9 | import io.github.thecsdev.betterstats.api.util.io.IStatsProvider; 10 | import io.github.thecsdev.betterstats.api.util.stats.SUPlayerBadgeStat; 11 | import io.github.thecsdev.tcdcommons.api.client.badge.ClientPlayerBadge; 12 | import io.github.thecsdev.tcdcommons.api.util.annotations.Virtual; 13 | 14 | /** 15 | * {@link BetterStats}'s implementation of {@link ClientPlayerBadge}. 16 | */ 17 | @Deprecated(since = "3.13.9") 18 | public @Virtual class BSClientPlayerBadge extends ClientPlayerBadge 19 | { 20 | // ================================================== 21 | private static final Function EMPTY_CRITERIA = __ -> 0; 22 | // -------------------------------------------------- 23 | protected final Component name, description; 24 | // -------------------------------------------------- 25 | protected @Nullable Function statCriteria = EMPTY_CRITERIA; 26 | // ================================================== 27 | public BSClientPlayerBadge(Component title, Component description) throws NullPointerException 28 | { 29 | this.name = Objects.requireNonNull(title); 30 | this.description = Objects.requireNonNull(description); 31 | } 32 | // ================================================== 33 | public @Virtual @Override Component getName() { return this.name; } 34 | public @Virtual @Override Component getDescription() { return this.description; } 35 | // ================================================== 36 | /** 37 | * About the returned function: 38 | *

39 | * The {@link Function} returns an {@link Integer} if a given {@link IStatsProvider} 40 | * contains statistics that meet the criteria for this {@link BSClientPlayerBadge} 41 | * to be "awarded" to said {@link IStatsProvider}. 42 | *

43 | * The returned {@link Integer} indicates the "value" aka "quantity" 44 | * of the {@link BSClientPlayerBadge} that should be "awarded". 45 | * 46 | * @see SUPlayerBadgeStat#value 47 | */ 48 | public final @Nullable Function getStatCriteria() { return this.statCriteria; } 49 | 50 | /** 51 | * Sets the "stat criteria" {@link Function}. 52 | * @see #getStatCriteria() 53 | * @throws IllegalStateException If {@link #statCriteria} is already defined. 54 | */ 55 | public @Virtual void setStatCriteria(@Nullable Function statCriteria) throws IllegalStateException 56 | { 57 | if(this.statCriteria != null && this.statCriteria != EMPTY_CRITERIA) 58 | throw new IllegalStateException("Already defined."); 59 | this.statCriteria = statCriteria; 60 | } 61 | // ================================================== 62 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/util/io/IStatsProvider.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.util.io; 2 | 3 | import java.util.Objects; 4 | import net.minecraft.network.chat.Component; 5 | import net.minecraft.resources.ResourceLocation; 6 | import net.minecraft.stats.Stat; 7 | import net.minecraft.stats.StatType; 8 | import net.minecraft.stats.StatsCounter; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import com.mojang.authlib.GameProfile; 12 | 13 | import io.github.thecsdev.tcdcommons.api.badge.PlayerBadge; 14 | 15 | /** 16 | * A component that contains loaded statistics about a given player. 17 | */ 18 | public interface IStatsProvider 19 | { 20 | // ================================================== 21 | /** 22 | * {@link EmptyStatsProvider} that always returns 0 for every single stat. 23 | */ 24 | public static final IStatsProvider EMPTY = new EmptyStatsProvider(); 25 | // ================================================== 26 | /** 27 | * Returns a "visual"/"user friendly" display {@link Component} that will 28 | * be shown on the GUI screen as an indicator as to who the stats belong to. 29 | * @apiNote Does not have to follow any Minecraft account naming rules or even correspond to one. 30 | */ 31 | public @Nullable Component getDisplayName(); 32 | 33 | /** 34 | * Returns the {@link GameProfile} of the player these stats belong to, 35 | * or {@code null} if these stats are not associated with a player. 36 | */ 37 | public @Nullable GameProfile getGameProfile(); 38 | // ================================================== 39 | /** 40 | * Returns the {@link Integer} value of a given {@link Stat}. 41 | * @param stat The {@link Stat} whose value is to be obtained. 42 | * @see StatsCounter 43 | */ 44 | public int getStatValue(Stat stat); 45 | 46 | /** 47 | * Returns the {@link Integer} value of a given {@link StatType} and its corresponding {@link Stat}. 48 | * @param type The {@link StatType}. 49 | * @param stat The {@link Stat} whose value is to be obtained. 50 | * @see StatsCounter 51 | * @apiNote You should not override this, as it calls {@link #getStatValue(Stat)} by default. 52 | */ 53 | default int getStatValue(StatType type, T stat) { return type.contains(stat) ? getStatValue(type.get(stat)) : 0; } 54 | // -------------------------------------------------- 55 | /** 56 | * Returns the {@link Integer} value of a given {@link PlayerBadge} stat. 57 | * @param badgeId The unique {@link ResourceLocation} of the {@link PlayerBadge}. 58 | */ 59 | public int getPlayerBadgeValue(ResourceLocation badgeId); 60 | 61 | /** 62 | * Returns the {@link Integer} value of a given {@link PlayerBadge} stat. 63 | * @param playerBadge The given {@link PlayerBadge}. Must be registered. 64 | * @throws NullPointerException If the argument is {@code null}, or the {@link PlayerBadge} is not registered. 65 | */ 66 | default int getPlayerBadgeValue(PlayerBadge playerBadge) throws NullPointerException 67 | { 68 | return getPlayerBadgeValue(Objects.requireNonNull(playerBadge.getId().orElse(null))); 69 | } 70 | // ================================================== 71 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/util/io/ServerPlayerStatsProvider.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.util.io; 2 | 3 | import java.util.Objects; 4 | import net.minecraft.client.player.LocalPlayer; 5 | import net.minecraft.network.chat.Component; 6 | import net.minecraft.resources.ResourceLocation; 7 | import net.minecraft.server.level.ServerPlayer; 8 | import net.minecraft.stats.ServerStatsCounter; 9 | import net.minecraft.stats.Stat; 10 | import net.minecraft.stats.StatType; 11 | import com.mojang.authlib.GameProfile; 12 | 13 | import io.github.thecsdev.tcdcommons.api.badge.ServerPlayerBadgeHandler; 14 | 15 | /** 16 | * An {@link IStatsProvider} that provides statistics 17 | * about a given {@link ServerPlayer}. 18 | */ 19 | public final class ServerPlayerStatsProvider implements IStatsProvider 20 | { 21 | // ================================================== 22 | private final ServerPlayer player; 23 | // -------------------------------------------------- 24 | private final Component displayName; 25 | private final GameProfile gameProfile; 26 | private final ServerStatsCounter statHandler; 27 | private final ServerPlayerBadgeHandler badgeHandler; 28 | // ================================================== 29 | private ServerPlayerStatsProvider(ServerPlayer player) throws NullPointerException 30 | { 31 | this.player = Objects.requireNonNull(player); 32 | this.displayName = player.getDisplayName(); 33 | this.gameProfile = player.getGameProfile(); 34 | this.statHandler = player.getStats(); 35 | this.badgeHandler = ServerPlayerBadgeHandler.getServerBadgeHandler(player); 36 | } 37 | // -------------------------------------------------- 38 | public final ServerPlayer getPlayer() { return this.player; } 39 | // ================================================== 40 | public final @Override Component getDisplayName() { return this.displayName; } 41 | public final @Override GameProfile getGameProfile() { return this.gameProfile; } 42 | // -------------------------------------------------- 43 | public final @Override int getStatValue(Stat stat) { return this.statHandler.getValue(stat); } 44 | public final @Override int getStatValue(StatType type, T stat) { return this.statHandler.getValue(type, stat); } 45 | public final @Override int getPlayerBadgeValue(ResourceLocation badgeId) { return this.badgeHandler.getValue(badgeId); } 46 | // ================================================== 47 | public final @Override int hashCode() { return this.player.hashCode(); } 48 | public final @Override boolean equals(Object obj) 49 | { 50 | if (this == obj) return true; 51 | if (obj == null || getClass() != obj.getClass()) return false; 52 | final var spsp = (ServerPlayerStatsProvider)obj; 53 | return (this.player == spsp.player); 54 | } 55 | // ================================================== 56 | /** 57 | * Creates a {@link ServerPlayerStatsProvider} instance based on a {@link ServerPlayer}. 58 | * @param player The {@link LocalPlayer}. 59 | */ 60 | public static final ServerPlayerStatsProvider of(ServerPlayer player) throws NullPointerException 61 | { 62 | return new ServerPlayerStatsProvider(player); 63 | } 64 | // ================================================== 65 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/client/gui/stats/widget/PlayerBadgeStatWidget.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.client.gui.stats.widget; 2 | 3 | import static io.github.thecsdev.tcdcommons.api.client.registry.TClientRegistries.PLAYER_BADGE_RENDERER; 4 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.fLiteral; 5 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.literal; 6 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.translatable; 7 | 8 | import java.util.Objects; 9 | import net.minecraft.client.gui.components.Tooltip; 10 | import net.minecraft.network.chat.Component; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import io.github.thecsdev.betterstats.api.util.stats.SUPlayerBadgeStat; 14 | import io.github.thecsdev.betterstats.util.BST; 15 | import io.github.thecsdev.tcdcommons.api.badge.PlayerBadge; 16 | import io.github.thecsdev.tcdcommons.api.client.badge.ClientPlayerBadge; 17 | import io.github.thecsdev.tcdcommons.api.client.gui.util.TDrawContext; 18 | import io.github.thecsdev.tcdcommons.api.client.render.badge.PlayerBadgeRenderer; 19 | import io.github.thecsdev.tcdcommons.api.util.annotations.Virtual; 20 | 21 | @Deprecated(since = "3.13.9") 22 | public @Virtual class PlayerBadgeStatWidget extends AbstractStatWidget 23 | { 24 | // ================================================== 25 | public static final int SIZE = 25; 26 | // 27 | public static final Component TXT_STAT_OBTAINED = BST.sWidget_pbadge_obtained(); 28 | // -------------------------------------------------- 29 | protected final PlayerBadge playerBadge; 30 | protected final @Nullable PlayerBadgeRenderer playerBadgeRenderer; 31 | // 32 | protected final Tooltip defaultTooltip; 33 | // ================================================== 34 | public PlayerBadgeStatWidget(int x, int y, SUPlayerBadgeStat stat) throws NullPointerException 35 | { 36 | super(x, y, SIZE, SIZE, stat); 37 | this.playerBadge = Objects.requireNonNull(stat.getPlayerBadge()); 38 | this.playerBadgeRenderer = PLAYER_BADGE_RENDERER.getValue(stat.getStatID()).orElse(null); 39 | 40 | final Component ttt = literal("") //MUST create new text instance 41 | .append(stat.getStatLabel()) 42 | .append(fLiteral("\n§7" + stat.getStatID())) 43 | .append("\n\n§r") 44 | .append(this.playerBadge.getDescription()) 45 | .append("\n\n") 46 | .append(fLiteral("§e" + TXT_STAT_OBTAINED.getString() + ": §r" + stat.value)) 47 | .append((this.playerBadge instanceof ClientPlayerBadge) ? 48 | fLiteral("\n\n§9" + translatable("tcdcommons.client_side").getString()) : 49 | literal("")); 50 | setTooltip(this.defaultTooltip = Tooltip.create(ttt)); 51 | } 52 | // ================================================== 53 | public @Virtual @Override void render(TDrawContext pencil) 54 | { 55 | super.render(pencil); 56 | if(this.playerBadgeRenderer != null) 57 | this.playerBadgeRenderer.render( 58 | pencil, 59 | this.getX() + 2, this.getY() + 2, 60 | this.getWidth() - 4, this.getHeight() - 4, 61 | pencil.mouseX, pencil.mouseY, 62 | pencil.deltaTime 63 | ); 64 | else pencil.drawTFill(TDrawContext.DEFAULT_ERROR_COLOR); 65 | } 66 | // ================================================== 67 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/client/gui/stats/panel/PBSummaryPanel.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.client.gui.stats.panel; 2 | 3 | import java.util.Comparator; 4 | import java.util.Objects; 5 | import java.util.stream.Collectors; 6 | 7 | import io.github.thecsdev.betterstats.api.client.gui.panel.BSComponentPanel; 8 | import io.github.thecsdev.betterstats.api.util.io.IStatsProvider; 9 | import io.github.thecsdev.betterstats.api.util.stats.SUPlayerBadgeStat; 10 | import io.github.thecsdev.betterstats.client.gui.stats.panel.StatsTabPanel; 11 | import io.github.thecsdev.betterstats.client.gui.stats.tabs.PlayerBadgeStatsTab; 12 | import io.github.thecsdev.tcdcommons.api.badge.PlayerBadge; 13 | import io.github.thecsdev.tcdcommons.api.client.gui.layout.UILayout; 14 | import io.github.thecsdev.tcdcommons.api.client.gui.other.TLabelElement; 15 | import io.github.thecsdev.tcdcommons.api.client.gui.panel.TPanelElement; 16 | import io.github.thecsdev.tcdcommons.api.util.enumerations.HorizontalAlignment; 17 | 18 | /** 19 | * Player badge summary panel.
20 | * Shows a few highlight {@link PlayerBadge}s for a given {@link IStatsProvider}. 21 | */ 22 | public final class PBSummaryPanel extends BSComponentPanel 23 | { 24 | // ================================================== 25 | private final IStatsProvider statsProvider; 26 | // -------------------------------------------------- 27 | protected int maxEntries = 10; 28 | // ================================================== 29 | public PBSummaryPanel(int x, int y, int width, int height, IStatsProvider statsProvider) 30 | { 31 | super(x, y, width, height); 32 | this.statsProvider = Objects.requireNonNull(statsProvider); 33 | 34 | this.setScrollPadding(5); 35 | this.setScrollFlags(TPanelElement.SCROLL_VERTICAL); 36 | } 37 | // ================================================== 38 | /** 39 | * Returns the {@link IStatsProvider} associated with this {@link PBSummaryPanel}. 40 | */ 41 | public final IStatsProvider getStatsProvider() { return this.statsProvider; } 42 | // -------------------------------------------------- 43 | public final int getMaxEntries() { return this.maxEntries; } 44 | public final void setMaxEntries(int maxEntries) { this.maxEntries = Math.abs(maxEntries); } 45 | // ================================================== 46 | protected final @Override void init() 47 | { 48 | //obtain stats, sorting highest to lowest 49 | final var stats = SUPlayerBadgeStat.getPlayerBadgeStats(this.statsProvider, s -> !s.isEmpty()) 50 | .stream() 51 | .sorted(Comparator.comparingInt(entry -> entry.value).reversed()) 52 | .limit(this.maxEntries) 53 | .collect(Collectors.toList()); 54 | 55 | //place stats 56 | PlayerBadgeStatsTab.initStats(this, stats, w -> w.setSize(20, 20)); 57 | 58 | //no stats label 59 | if(getChildren().size() == 0) 60 | { 61 | final var n1 = UILayout.nextChildVerticalRect(this); 62 | final var lbl = new TLabelElement( 63 | n1.x, getY() + (getHeight() / 2) - (n1.height / 2), 64 | n1.width, n1.height, 65 | StatsTabPanel.TXT_NO_STATS_YET); 66 | lbl.setTextHorizontalAlignment(HorizontalAlignment.CENTER); 67 | addChild(lbl, false); 68 | } 69 | } 70 | // ================================================== 71 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/util/stats/SASConfig.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.util.stats; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | 6 | import io.github.thecsdev.tcdcommons.api.config.ACJsonHandler; 7 | 8 | public class SASConfig implements ACJsonHandler 9 | { 10 | // ================================================== 11 | public String[] firstMinedBlocks = new String[] { "diamond_ore", "deepslate_diamond_ore", "ancient_debris", "deepslate_coal_ore", "dragon_egg", "sculk_sensor", "reinforced_deepslate" }; 12 | public String[] firstCraftedItems = new String[] { "wooden_pickaxe", "diamond_pickaxe", "beacon", "netherite_block", "ender_eye" }; 13 | public String[] firstKilledEntities = new String[] { "zombie", "blaze", "enderman", "ender_dragon", "warden", "wither", "player" }; 14 | public String[] firstKilledByEntities = new String[] { "ender_dragon", "warden", "wither", "player" }; 15 | public String[] firstCustomStats = new String[] { "deaths" }; 16 | // ================================================== 17 | public final @Override JsonObject saveToJson() 18 | { 19 | final var json = new JsonObject(); 20 | json.add("firstMinedBlocks", stringArrayToJsonArray(this.firstMinedBlocks)); 21 | json.add("firstCraftedItems", stringArrayToJsonArray(this.firstCraftedItems)); 22 | json.add("firstKilledEntities", stringArrayToJsonArray(this.firstKilledEntities)); 23 | json.add("firstKilledByEntities", stringArrayToJsonArray(this.firstKilledByEntities)); 24 | json.add("firstCustomStats", stringArrayToJsonArray(this.firstCustomStats)); 25 | return json; 26 | } 27 | // -------------------------------------------------- 28 | public final @Override boolean loadFromJson(JsonObject json) 29 | { 30 | //make an attempt to load the config; ignore failures 31 | try 32 | { 33 | if(json.has("firstMinedBlocks")) this.firstMinedBlocks = jsonArrayToStringArray(json.getAsJsonArray("firstMinedBlocks")); 34 | if(json.has("firstCraftedItems")) this.firstCraftedItems = jsonArrayToStringArray(json.getAsJsonArray("firstCraftedItems")); 35 | if(json.has("firstKilledEntities")) this.firstKilledEntities = jsonArrayToStringArray(json.getAsJsonArray("firstKilledEntities")); 36 | if(json.has("firstKilledByEntities")) this.firstKilledByEntities = jsonArrayToStringArray(json.getAsJsonArray("firstKilledByEntities")); 37 | if(json.has("firstCustomStats")) this.firstCustomStats = jsonArrayToStringArray(json.getAsJsonArray("firstCustomStats")); 38 | return true; 39 | } 40 | catch(Exception e) { return false; } 41 | } 42 | // ================================================== 43 | // Helper method to convert String array to JsonArray 44 | private final JsonArray stringArrayToJsonArray(String[] array) 45 | { 46 | JsonArray jsonArray = new JsonArray(); 47 | for(final String item : array) jsonArray.add(item); 48 | return jsonArray; 49 | } 50 | 51 | // Helper method to convert JsonArray to String array 52 | private final String[] jsonArrayToStringArray(JsonArray jsonArray) 53 | { 54 | String[] array = new String[jsonArray.size()]; 55 | for(int i = 0; i < jsonArray.size(); i++) 56 | array[i] = jsonArray.get(i).getAsString(); 57 | return array; 58 | } 59 | // ================================================== 60 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/client/gui/panel/PageChooserPanel.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.client.gui.panel; 2 | 3 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.literal; 4 | 5 | import java.util.Objects; 6 | 7 | import io.github.thecsdev.betterstats.api.client.gui.panel.BSComponentPanel; 8 | import io.github.thecsdev.tcdcommons.api.client.gui.other.TLabelElement; 9 | import io.github.thecsdev.tcdcommons.api.client.gui.panel.TRefreshablePanelElement; 10 | import io.github.thecsdev.tcdcommons.api.client.gui.widget.TButtonWidget; 11 | import io.github.thecsdev.tcdcommons.api.util.enumerations.HorizontalAlignment; 12 | 13 | /** 14 | * A {@link TRefreshablePanelElement} featuring a label that shows the 15 | * "current page", as well as buttons that allow the user to change "pages". 16 | */ 17 | public final class PageChooserPanel extends BSComponentPanel 18 | { 19 | // ================================================== 20 | private final PageChooserPanelProxy proxy; 21 | // ================================================== 22 | public PageChooserPanel(int x, int y, int width, PageChooserPanelProxy proxy) 23 | { 24 | super(x, y, width, 22); 25 | this.proxy = Objects.requireNonNull(proxy); 26 | } 27 | // -------------------------------------------------- 28 | /** 29 | * Returns the {@link PageChooserPanelProxy} associated with this object. 30 | */ 31 | public PageChooserPanelProxy getProxy() { return this.proxy; } 32 | // ================================================== 33 | protected final @Override void init() 34 | { 35 | //get page info 36 | final int maxPages = Math.max(this.proxy.getPageCount(), 1); 37 | final int page = Math.min(Math.max(this.proxy.getPage(), 1), maxPages); 38 | 39 | //init label 40 | final var lbl = new TLabelElement(0, 0, this.width, this.height); 41 | lbl.setText(literal(page + " / " + maxPages)); 42 | lbl.setTextHorizontalAlignment(HorizontalAlignment.CENTER); 43 | addChild(lbl, true); 44 | 45 | //init left button 46 | final var btn_left = new TButtonWidget(1, 1, 20, 20, literal("<")); 47 | btn_left.setOnClick(__ -> nav(-1)); 48 | btn_left.setEnabled(page > 1); 49 | addChild(btn_left, true); 50 | 51 | //init right button 52 | final var btn_right = new TButtonWidget(this.width - 21, 1, 20, 20, literal(">")); 53 | btn_right.setOnClick(__ -> nav(1)); 54 | btn_right.setEnabled(page < maxPages); 55 | addChild(btn_right, true); 56 | } 57 | // -------------------------------------------------- 58 | private final void nav(int num) 59 | { 60 | //get page info + number 61 | final int maxPages = Math.max(this.proxy.getPageCount(), 1); 62 | final int page = Math.min(Math.max(this.proxy.getPage() + num, 1), maxPages); 63 | 64 | //navigate, and refresh if needed 65 | this.proxy.setPage(page); 66 | if(getParent() != null) refresh(); 67 | } 68 | // ================================================== 69 | /** 70 | * A component used by {@link PageChooserPanel} that relays the 71 | * {@link PageChooserPanel}'s current state to the {@link PageChooserPanel}. 72 | */ 73 | public static interface PageChooserPanelProxy 74 | { 75 | public int getPage(); 76 | public int getPageCount(); 77 | public void setPage(int page); 78 | } 79 | // ================================================== 80 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/client/gui/stats/tabs/AdvancementsTab.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.client.gui.stats.tabs; 2 | 3 | import static io.github.thecsdev.betterstats.client.BetterStatsClient.MC_CLIENT; 4 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.translatable; 5 | 6 | import java.util.stream.Collectors; 7 | import net.minecraft.network.chat.Component; 8 | import org.jetbrains.annotations.ApiStatus.Experimental; 9 | import org.jetbrains.annotations.ApiStatus.Internal; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import io.github.thecsdev.betterstats.api.client.gui.util.StatsTabUtils; 13 | import io.github.thecsdev.betterstats.api.client.registry.StatsTab; 14 | import io.github.thecsdev.betterstats.client.gui.widget.AdvancementStatWidget; 15 | import io.github.thecsdev.tcdcommons.api.client.gui.layout.UIAutomaticSizeLayout; 16 | import io.github.thecsdev.tcdcommons.api.client.gui.layout.UIHorizontalGridLayout; 17 | import io.github.thecsdev.tcdcommons.api.client.gui.layout.UILayout; 18 | import io.github.thecsdev.tcdcommons.api.client.gui.other.TBlankElement; 19 | import io.github.thecsdev.tcdcommons.api.util.enumerations.AutomaticSize; 20 | 21 | /** 22 | * A {@link StatsTab} that displays information about advancements. 23 | */ 24 | @Experimental 25 | public final @Internal class AdvancementsTab extends StatsTab 26 | { 27 | // ================================================== 28 | public final @Override Component getName() { return translatable("gui.advancements"); } 29 | // ================================================== 30 | public final @Override boolean isAvailable() { return false; } 31 | // -------------------------------------------------- 32 | public final @Override void initStats(StatsInitContext initContext) 33 | { 34 | //prepare 35 | final var panel = initContext.getStatsPanel(); 36 | final @Nullable var srv = MC_CLIENT.getConnection(); 37 | if(srv == null) return; 38 | 39 | //collect the advancement categories, aka "roots" 40 | final var aRoots = srv.getAdvancements().getTree().nodes().stream() 41 | .collect(Collectors.groupingBy(a -> a.root())); 42 | aRoots.remove(null); //just in case 43 | 44 | //iterate all groups, and list their stats 45 | for(final var aRoot : aRoots.keySet()) 46 | { 47 | //initialize the group label 48 | final var arDisplay = aRoot.advancement().display().orElse(null); 49 | if(arDisplay == null) continue; 50 | StatsTabUtils.initGroupLabel(panel, arDisplay.getTitle()); 51 | 52 | //prepare to initialize the group stats 53 | final var n1 = UILayout.nextChildVerticalRect(panel); 54 | final var div = new TBlankElement(n1.x, n1.y, n1.width, 0); 55 | panel.addChild(div, false); 56 | 57 | //initialize group advancement stats 58 | for(final var advancement : aRoots.get(aRoot)) 59 | { 60 | //skip advancements that list themselves as roots 61 | if(advancement == aRoot) continue; 62 | 63 | //add advancement stat widget 64 | final var widget = new AdvancementStatWidget(0, 0, advancement); 65 | div.addChild(widget, false); 66 | } 67 | 68 | //apply UI layouts 69 | new UIHorizontalGridLayout().apply(div); 70 | new UIAutomaticSizeLayout(AutomaticSize.Y).apply(div); 71 | } 72 | } 73 | // ================================================== 74 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/client/gui/stats/widget/GeneralStatWidget.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.client.gui.stats.widget; 2 | 3 | import static io.github.thecsdev.betterstats.api.util.stats.SUGeneralStat.TEXT_VALUE; 4 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.fLiteral; 5 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.literal; 6 | import static io.github.thecsdev.tcdcommons.client.TCDCommonsClient.MC_CLIENT; 7 | 8 | import org.jetbrains.annotations.ApiStatus.Experimental; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import io.github.thecsdev.betterstats.api.util.formatters.StatValueFormatter; 12 | import io.github.thecsdev.betterstats.api.util.stats.SUGeneralStat; 13 | import io.github.thecsdev.tcdcommons.api.client.gui.util.TDrawContext; 14 | import io.github.thecsdev.tcdcommons.api.util.annotations.Virtual; 15 | import io.github.thecsdev.tcdcommons.api.util.enumerations.HorizontalAlignment; 16 | import net.minecraft.client.gui.components.Tooltip; 17 | import net.minecraft.network.chat.Component; 18 | 19 | public @Virtual class GeneralStatWidget extends AbstractStatWidget 20 | { 21 | // ================================================== 22 | public static final int HEIGHT = MC_CLIENT.font.lineHeight + 8; 23 | // -------------------------------------------------- 24 | protected final Component txt_label; 25 | protected /*final*/ Component txt_value; //no longer "final" as of v3.13 26 | protected final Tooltip defaultTooltip; 27 | protected @Experimental StatValueFormatter formatter; //experimental 28 | // ================================================== 29 | public GeneralStatWidget(int x, int y, int width, SUGeneralStat stat) throws NullPointerException 30 | { 31 | super(x, y, width, HEIGHT, stat); 32 | this.txt_label = stat.getStatLabel(); 33 | //this.txt_value = stat.valueText; -- will assign on the following line 34 | setFormatter(null); //a formatter value must be assigned; null will be turned into default value 35 | 36 | final Component ttt = literal("") //MUST create new text instance 37 | .append(stat.getStatLabel()) 38 | .append(fLiteral("\n§7K: " + stat.getStatID())) 39 | .append(fLiteral("\n§7V: " + stat.getGeneralStat().getValue())) 40 | .append("\n\n§r") 41 | .append(fLiteral("§e" + TEXT_VALUE.getString() + ": §r" + stat.value)); 42 | setTooltip(this.defaultTooltip = Tooltip.create(ttt)); 43 | } 44 | // -------------------------------------------------- 45 | public final @Experimental StatValueFormatter getFormatter() { return this.formatter; } 46 | public final @Experimental void setFormatter(@Nullable StatValueFormatter formatter) 47 | { 48 | this.formatter = (formatter != null) ? formatter : new StatValueFormatter() 49 | { 50 | public final @Override Component getDisplayName() { return literal("-"); } 51 | public final @Override Component format(int number) { return GeneralStatWidget.this.stat.valueText; } 52 | }; 53 | this.txt_value = this.formatter.format(this.stat.value); 54 | } 55 | // ================================================== 56 | public @Virtual @Override void render(TDrawContext pencil) 57 | { 58 | super.render(pencil); 59 | pencil.drawTElementTextTH(this.txt_label, HorizontalAlignment.LEFT); 60 | pencil.drawTElementTextTH(this.txt_value, HorizontalAlignment.RIGHT); 61 | } 62 | // ================================================== 63 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/BetterStats.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import io.github.thecsdev.betterstats.command.StatisticsCommand; 7 | import io.github.thecsdev.betterstats.network.BetterStatsNetwork; 8 | import io.github.thecsdev.tcdcommons.TCDCommons; 9 | import io.github.thecsdev.tcdcommons.api.events.server.command.CommandManagerEvent; 10 | import io.github.thecsdev.tcdcommons.command.PlayerBadgeCommand; 11 | 12 | public class BetterStats extends Object 13 | { 14 | // ================================================== 15 | public static final Logger LOGGER = LoggerFactory.getLogger(getModID()); 16 | // -------------------------------------------------- 17 | private static final String ModName = "Better Statistics Screen"; 18 | public static final String ModID = "betterstats"; 19 | private static BetterStats Instance; 20 | // -------------------------------------------------- 21 | protected final BetterStatsConfig config; 22 | // ================================================== 23 | public BetterStats() 24 | { 25 | //validate instance first 26 | if(isModInitialized()) 27 | throw new IllegalStateException(getModID() + " has already been initialized."); 28 | else if(!isInstanceValid(this)) 29 | throw new UnsupportedOperationException("Invalid " + getModID() + " type: " + this.getClass().getName()); 30 | 31 | //assign instance 32 | Instance = this; 33 | //modInfo = FabricLoader.getInstance().getModContainer(getModID()).get(); 34 | 35 | //log stuff 36 | LOGGER.info("Initializing '" + getModName() + "' as '" + getClass().getSimpleName() + "'."); 37 | 38 | //load config 39 | this.config = new BetterStatsConfig(getModID()); 40 | this.config.loadFromFileOrCrash(true); 41 | 42 | //init stuff 43 | BetterStatsProperties.init(); 44 | BetterStatsNetwork.init(); 45 | 46 | // ---------- register commands 47 | CommandManagerEvent.COMMAND_REGISTRATION_CALLBACK.register((dispatcher, commandRegAccess, regEnv) -> 48 | { 49 | //check the config property 50 | if(!this.config.registerCommands) return; 51 | 52 | //register commands 53 | StatisticsCommand.register(dispatcher, commandRegAccess); 54 | if(TCDCommons.getInstance().getConfig().enablePlayerBadges) 55 | PlayerBadgeCommand.register(dispatcher); 56 | }); 57 | } 58 | // ================================================== 59 | public static BetterStats getInstance() { return Instance; } 60 | public BetterStatsConfig getConfig() { return this.config; } 61 | // -------------------------------------------------- 62 | public static String getModName() { return ModName; } 63 | public static String getModID() { return ModID; } 64 | // -------------------------------------------------- 65 | public static boolean isModInitialized() { return isInstanceValid(Instance); } 66 | private static boolean isInstanceValid(BetterStats instance) { return isServer(instance) || isClient(instance); } 67 | // -------------------------------------------------- 68 | public static boolean isServer() { return isServer(Instance); } 69 | public static boolean isClient() { return isClient(Instance); } 70 | 71 | private static boolean isServer(BetterStats arg0) { return arg0 instanceof io.github.thecsdev.betterstats.server.BetterStatsServer; } 72 | private static boolean isClient(BetterStats arg0) { return arg0 instanceof io.github.thecsdev.betterstats.client.BetterStatsClient; } 73 | // ================================================== 74 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/util/enumerations/FilterSortItemsBy.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.util.enumerations; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Objects; 7 | import net.minecraft.network.chat.Component; 8 | import io.github.thecsdev.betterstats.api.util.stats.SUItemStat; 9 | import io.github.thecsdev.betterstats.util.BST; 10 | import io.github.thecsdev.tcdcommons.api.util.interfaces.ITextProvider; 11 | 12 | /** 13 | * A statistics filter {@link Enum} that dictates how 14 | * {@link SUItemStat} statistics entries are sorted. 15 | */ 16 | public enum FilterSortItemsBy implements ITextProvider 17 | { 18 | // ================================================== 19 | DEFAULT(BST.filter_groupBy_default()), 20 | MINED(ItemStatType.MINED.getText()), 21 | CRAFTED(ItemStatType.CRAFTED.getText()), 22 | USED(ItemStatType.USED.getText()), 23 | BROKEN(ItemStatType.BROKEN.getText()), 24 | PICKED_UP(ItemStatType.PICKED_UP.getText()), 25 | DROPPED(ItemStatType.DROPPED.getText()); 26 | // ================================================== 27 | private final Component text; 28 | // -------------------------------------------------- 29 | private FilterSortItemsBy(Component text) { this.text = Objects.requireNonNull(text); } 30 | public final @Override Component getText() { return this.text; } 31 | // ================================================== 32 | /** 33 | * Sorts a {@link Map} of {@link SUItemStat}s based on {@code this} {@link FilterSortItemsBy}. 34 | * @param stats The {@link SUItemStat}s to sort. 35 | */ 36 | public final void sortItemStats(Map> stats) { sortItemStats(stats, this); } 37 | 38 | /** 39 | * Sorts a {@link List}<{@link SUItemStat}> based on {@code this} {@link FilterSortItemsBy}. 40 | * @param stats The {@link SUItemStat}s to sort. 41 | */ 42 | public final void sortItemStats(List stats) { sortItemStats(stats, this); } 43 | // -------------------------------------------------- 44 | /** 45 | * Sorts a {@link Map} of {@link SUItemStat}s based on {@link FilterSortItemsBy}. 46 | * @param stats The {@link SUItemStat}s to sort. 47 | * @param sortBy The {@link FilterSortItemsBy}. 48 | */ 49 | public static final void sortItemStats(Map> stats, FilterSortItemsBy sortBy) 50 | { 51 | for(final var entry : stats.entrySet()) 52 | sortItemStats(entry.getValue(), sortBy); 53 | } 54 | 55 | /** 56 | * Sorts a {@link List}<{@link SUItemStat}> based on {@link FilterSortItemsBy}. 57 | * @param stats The {@link SUItemStat}s to sort. 58 | * @param sortBy The {@link FilterSortItemsBy}. 59 | */ 60 | public static final void sortItemStats(List stats, FilterSortItemsBy sortBy) 61 | { 62 | switch(sortBy) 63 | { 64 | case MINED: Collections.sort(stats, (s1, s2) -> Integer.compare(s2.mined, s1.mined)); break; 65 | case CRAFTED: Collections.sort(stats, (s1, s2) -> Integer.compare(s2.crafted, s1.crafted)); break; 66 | case USED: Collections.sort(stats, (s1, s2) -> Integer.compare(s2.used, s1.used)); break; 67 | case BROKEN: Collections.sort(stats, (s1, s2) -> Integer.compare(s2.broken, s1.broken)); break; 68 | case PICKED_UP: Collections.sort(stats, (s1, s2) -> Integer.compare(s2.pickedUp, s1.pickedUp)); break; 69 | case DROPPED: Collections.sort(stats, (s1, s2) -> Integer.compare(s2.dropped, s1.dropped)); break; 70 | default: break; 71 | } 72 | } 73 | // ================================================== 74 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/util/stats/SUStat.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.util.stats; 2 | 3 | import java.util.Objects; 4 | import net.minecraft.network.chat.Component; 5 | import net.minecraft.resources.ResourceLocation; 6 | import net.minecraft.stats.Stat; 7 | import net.minecraft.stats.Stats; 8 | import net.minecraft.world.entity.EntityType; 9 | import net.minecraft.world.item.Item; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.jetbrains.annotations.ApiStatus.Internal; 12 | 13 | import io.github.thecsdev.betterstats.api.util.io.IStatsProvider; 14 | import io.github.thecsdev.tcdcommons.api.util.annotations.Virtual; 15 | 16 | /** 17 | * A "Stat utils stat".

18 | * Represents a statistic about something, whether it be about {@link Item}s, 19 | * or {@link EntityType}s, or anything else. This is a utility class for 20 | * easier reading of statistics about given things. 21 | * @apiNote Not intended to be {@link Override}n outside of this API. 22 | */ 23 | public abstract @Internal class SUStat extends Object 24 | { 25 | // ================================================== 26 | public static final ResourceLocation ID_NULL = ResourceLocation.parse("null"); 27 | // -------------------------------------------------- 28 | protected final IStatsProvider statsProvider; 29 | protected final ResourceLocation statId; 30 | protected final Component statLabel; 31 | protected final String statLabelSQ, statIdSQ; //"search query" helpers 32 | // ================================================== 33 | protected SUStat(IStatsProvider statsProvider, ResourceLocation statId, Component statLabel) throws NullPointerException 34 | { 35 | this.statsProvider = Objects.requireNonNull(statsProvider); 36 | this.statId = Objects.requireNonNull(statId); 37 | this.statLabel = Objects.requireNonNull(statLabel); 38 | 39 | this.statLabelSQ = this.statLabel.getString().toLowerCase().replaceAll("\\s+",""); 40 | this.statIdSQ = Objects.toString(statId); 41 | } 42 | // ================================================== 43 | /** 44 | * Returns the {@link Component}ual label that represents the {@link Stat}. 45 | */ 46 | public final Component getStatLabel() { return this.statLabel; } 47 | 48 | /** 49 | * Returns the unique {@link ResourceLocation} associated with this {@link SUStat}. 50 | *

51 | * For {@link SUGeneralStat}, refers to {@link Stat#getValue()},
52 | * for {@link SUItemStat}, refers to {@link Item}'s {@link ResourceLocation},
53 | * for {@link SUMobStat}, refers to {@link EntityType}'s {@link ResourceLocation}. 54 | */ 55 | public final ResourceLocation getStatID() { return this.statId; } 56 | 57 | /** 58 | * Returns the {@link IStatsProvider} the {@link Stats} data was obtained from. 59 | */ 60 | public final IStatsProvider getStatsProvider() { return this.statsProvider; } 61 | // -------------------------------------------------- 62 | /** 63 | * Checks if this {@link SUStat}'s {@link #statLabel} matches a given "search query". 64 | * @param search The search query being performed. 65 | * @see #getStatLabel() 66 | */ 67 | public @Virtual boolean matchesSearchQuery(String search) 68 | { 69 | search = StringUtils.defaultString(search).toLowerCase().replaceAll("\\s+",""); 70 | return this.statLabelSQ.contains(search) || this.statIdSQ.contains(search); 71 | } 72 | // ================================================== 73 | /** 74 | * Returns {@code true} if the sum of all the {@link Stat} 75 | * values associated with this {@link SUStat} is {@code 0}. 76 | */ 77 | public abstract boolean isEmpty(); 78 | // ================================================== 79 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/network/BetterStatsNetwork.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.network; 2 | 3 | import static io.github.thecsdev.betterstats.BetterStats.getModID; 4 | 5 | import java.util.function.Function; 6 | import net.minecraft.client.player.LocalPlayer; 7 | import net.minecraft.network.chat.Component; 8 | import net.minecraft.network.protocol.PacketFlow; 9 | import net.minecraft.resources.ResourceLocation; 10 | import net.minecraft.server.level.ServerPlayer; 11 | import net.minecraft.world.entity.player.Player; 12 | import org.jetbrains.annotations.ApiStatus.Internal; 13 | 14 | import io.github.thecsdev.betterstats.BetterStats; 15 | import io.github.thecsdev.betterstats.client.network.BetterStatsClientPlayNetworkHandler; 16 | import io.github.thecsdev.betterstats.util.BST; 17 | import io.github.thecsdev.tcdcommons.api.events.server.PlayerManagerEvent; 18 | import io.github.thecsdev.tcdcommons.api.network.CustomPayloadNetwork; 19 | 20 | /** 21 | * Represents the server-side network handler for {@link BetterStats}. 22 | */ 23 | public final @Internal class BetterStatsNetwork 24 | { 25 | // ================================================== 26 | private BetterStatsNetwork() {} 27 | // -------------------------------------------------- 28 | public static final Component TXT_TOGGLE_TOOLTIP = BST.net_toggleTooltip(); 29 | public static final Component TXT_CONSENT_WARNING = BST.net_consentWarning(); 30 | // 31 | public static final int NETWORK_VERSION = 3; 32 | // 33 | public static final ResourceLocation S2C_I_HAVE_BSS = ResourceLocation.fromNamespaceAndPath(getModID(), "s2c_bss"); 34 | public static final ResourceLocation C2S_I_HAVE_BSS = ResourceLocation.fromNamespaceAndPath(getModID(), "c2s_bss"); 35 | public static final ResourceLocation C2S_PREFERENCES = ResourceLocation.fromNamespaceAndPath(getModID(), "c2s_prf"); //v3.11+ | NV 3+ 36 | public static final ResourceLocation C2S_MCBS_REQUEST = ResourceLocation.fromNamespaceAndPath(getModID(), "c2s_mcbs_req"); //v3.11+ | NV 3+ 37 | public static final ResourceLocation S2C_MCBS = ResourceLocation.fromNamespaceAndPath(getModID(), "s2c_mcbs"); //v3.11+ | NV 3+ 38 | // ================================================== 39 | public static void init() {} 40 | static 41 | { 42 | // ---------- SHORTCUT FUNCTIONS 43 | final Function c = player -> 44 | BetterStatsClientPlayNetworkHandler.of((LocalPlayer)player); 45 | final Function s = player -> 46 | BetterStatsServerPlayNetworkHandler.of((ServerPlayer)player); 47 | 48 | // ---------- SINGLEPLAYER/DEDICATED SERVER HANDLERS 49 | //init event handlers 50 | PlayerManagerEvent.PLAYER_CONNECTED.register(player -> 51 | s.apply(player).onPlayerConnected()); 52 | 53 | //init network handlers 54 | CustomPayloadNetwork.registerReciever(PacketFlow.SERVERBOUND, C2S_I_HAVE_BSS, ctx -> 55 | s.apply(ctx.getPlayer()).onIHaveBss(ctx)); 56 | 57 | CustomPayloadNetwork.registerReciever(PacketFlow.SERVERBOUND, C2S_PREFERENCES, ctx -> 58 | s.apply(ctx.getPlayer()).onPreferences(ctx)); 59 | 60 | CustomPayloadNetwork.registerReciever(PacketFlow.SERVERBOUND, C2S_MCBS_REQUEST, ctx -> 61 | s.apply(ctx.getPlayer()).onMcbsRequest(ctx)); 62 | 63 | // ---------- PURE CLIENT-SIDE HANDLERS 64 | if(BetterStats.isClient()) 65 | { 66 | //init network handlers 67 | CustomPayloadNetwork.registerReciever(PacketFlow.CLIENTBOUND, S2C_I_HAVE_BSS, ctx -> 68 | c.apply(ctx.getPlayer()).onIHaveBss(ctx)); 69 | 70 | CustomPayloadNetwork.registerReciever(PacketFlow.CLIENTBOUND, S2C_MCBS, ctx -> 71 | c.apply(ctx.getPlayer()).onMcbs(ctx)); 72 | } 73 | } 74 | // ================================================== 75 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/client/gui/stats/panel/StatFiltersPanel.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.client.gui.stats.panel; 2 | 3 | import java.util.Objects; 4 | import net.minecraft.network.chat.Component; 5 | import io.github.thecsdev.betterstats.api.client.gui.panel.BSComponentPanel; 6 | import io.github.thecsdev.betterstats.api.client.gui.widget.ScrollBarWidget; 7 | import io.github.thecsdev.betterstats.api.client.registry.StatsTab; 8 | import io.github.thecsdev.betterstats.api.client.registry.StatsTab.FiltersInitContext; 9 | import io.github.thecsdev.betterstats.api.client.util.StatFilterSettings; 10 | import io.github.thecsdev.betterstats.client.gui.stats.panel.impl.BetterStatsPanel.BetterStatsPanelProxy; 11 | import io.github.thecsdev.betterstats.util.BST; 12 | import io.github.thecsdev.tcdcommons.api.client.gui.panel.TPanelElement; 13 | 14 | public final class StatFiltersPanel extends BSComponentPanel 15 | { 16 | // ================================================== 17 | public static final Component TXT_FILTERS = BST.filters(); 18 | // -------------------------------------------------- 19 | private final StatFiltersPanelProxy proxy; 20 | // ================================================== 21 | public StatFiltersPanel(int x, int y, int width, int height, StatFiltersPanelProxy proxy) throws NullPointerException 22 | { 23 | super(x, y, width, height); 24 | this.proxy = Objects.requireNonNull(proxy); 25 | } 26 | // ================================================== 27 | /** 28 | * Returns the {@link StatFiltersPanelProxy} associated 29 | * with this {@link StatFiltersPanel}. 30 | */ 31 | public final StatFiltersPanelProxy getProxy() { return this.proxy; } 32 | // -------------------------------------------------- 33 | protected final @Override void init() 34 | { 35 | //create a panel for the filters 36 | final var panel = new TPanelElement(0, 0, getWidth() - 8, getHeight()); 37 | panel.setScrollFlags(TPanelElement.SCROLL_VERTICAL); 38 | panel.setScrollPadding(10); 39 | panel.setSmoothScroll(true); 40 | panel.setBackgroundColor(0); 41 | panel.setOutlineColor(0); 42 | addChild(panel, true); 43 | 44 | //create a scroll bar for the panel 45 | final var scroll_panel = new ScrollBarWidget(panel.getWidth(), 0, 8, panel.getHeight(), panel); 46 | addChild(scroll_panel, true); 47 | 48 | //init filter gui 49 | final var statsTab = this.proxy.getSelectedStatsTab(); 50 | if(statsTab == null) return; //safety check; should not be null tho 51 | final var filterSettings = this.proxy.getFilterSettings(); 52 | 53 | statsTab.initFilters(new FiltersInitContext() 54 | { 55 | public TPanelElement getFiltersPanel() { return panel; } 56 | public StatFilterSettings getFilterSettings() { return filterSettings; } 57 | public void refreshStatsTab() { StatFiltersPanel.this.proxy.refreshStatsTab(); } 58 | public StatsTab getSelectedStatsTab() { return statsTab; } 59 | public void setSelectedStatsTab(StatsTab statsTab) { StatFiltersPanel.this.proxy.setSelectedStatsTab(statsTab); } 60 | }); 61 | } 62 | // ================================================== 63 | /** 64 | * A component that provides the {@link StatFiltersPanel} with 65 | * the necessary information to operate properly. 66 | */ 67 | public static interface StatFiltersPanelProxy 68 | { 69 | /** 70 | * Stores the "filter settings" aka user's stat filter 71 | * preferences that apply for the current session. 72 | * @apiNote Must not be {@code null}. 73 | */ 74 | public StatFilterSettings getFilterSettings(); 75 | 76 | /** 77 | * @see BetterStatsPanelProxy#getSelectedStatsTab() 78 | */ 79 | public StatsTab getSelectedStatsTab(); 80 | 81 | /** 82 | * @see BetterStatsPanelProxy#setSelectedStatsTab(StatsTab) 83 | */ 84 | public void setSelectedStatsTab(StatsTab statsTab); 85 | 86 | /** 87 | * Refreshes the {@link StatsTabPanel}. 88 | */ 89 | public void refreshStatsTab(); 90 | } 91 | // ================================================== 92 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/client/gui/widget/SelectStatsTabWidget.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.client.gui.widget; 2 | 3 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.literal; 4 | 5 | import java.util.NoSuchElementException; 6 | import java.util.Objects; 7 | import net.minecraft.client.gui.components.Tooltip; 8 | import net.minecraft.network.chat.Component; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import io.github.thecsdev.betterstats.BetterStatsConfig; 12 | import io.github.thecsdev.betterstats.api.client.registry.BSClientRegistries; 13 | import io.github.thecsdev.betterstats.api.client.registry.StatsTab; 14 | import io.github.thecsdev.tcdcommons.api.client.gui.widget.TSelectWidget; 15 | import io.github.thecsdev.tcdcommons.api.util.annotations.Virtual; 16 | 17 | /** 18 | * A {@link TSelectWidget} for {@link StatsTab}s.
19 | * Allows the user to Select a {@link StatsTab} via a GUI interface. 20 | */ 21 | public @Virtual class SelectStatsTabWidget extends TSelectWidget 22 | { 23 | // ================================================== 24 | public SelectStatsTabWidget(int x, int y, int width, int height) { this(x, y, width, height, DEFAULT_LABEL); } 25 | public SelectStatsTabWidget(int x, int y, int width, int height, Component text) 26 | { 27 | //super 28 | super(x, y, width, height, text); 29 | 30 | //add enum entries using the enum type 31 | final boolean debug = BetterStatsConfig.DEBUG_MODE; 32 | for(final var statsTabRegEntry : BSClientRegistries.STATS_TAB) 33 | { 34 | //null check and availability check 35 | final StatsTab statsTab = statsTabRegEntry.getValue(); 36 | if(statsTab == null || !statsTab.isAvailable()) 37 | continue; 38 | 39 | //create and add entry 40 | final StatsTabEntry entry = new StatsTabEntry(statsTab); 41 | entry.tooltip = (debug ? Tooltip.create(literal(Objects.toString(statsTabRegEntry.getKey()))) : null); 42 | addEntry(entry); 43 | } 44 | } 45 | // ================================================== 46 | /** 47 | * Returns a {@link StatsTabEntry} that is associated with a given {@link StatsTab} value. 48 | * Will return {@code null} if no such {@link StatsTabEntry} exists or was removed. 49 | */ 50 | public final StatsTabEntry entryOf(StatsTab statsTab) { return this.entries.find(e -> e.getStatsTab() == statsTab); } 51 | // -------------------------------------------------- 52 | /** 53 | * Sets the selected {@link StatsTabEntry} using its {@link StatsTab} value. 54 | * @throws NoSuchElementException If this {@link SelectStatsTabWidget} does not have 55 | * a {@link StatsTabEntry} that corresponds with the given {@link StatsTab} value. 56 | * @see #entryOf(StatsTab) 57 | * @see StatsTabEntry#getStatsTab() 58 | */ 59 | public final void setSelected(StatsTab statsTab) throws NoSuchElementException 60 | { 61 | final var e = entryOf(statsTab); 62 | if(e == null && statsTab != null) 63 | throw new NoSuchElementException(); 64 | setSelected(e); 65 | } 66 | // ================================================== 67 | public static final class StatsTabEntry implements TSelectWidget.Entry 68 | { 69 | // ---------------------------------------------- 70 | protected final StatsTab statsTab; 71 | protected @Nullable Tooltip tooltip; 72 | // ---------------------------------------------- 73 | public StatsTabEntry(StatsTab statsTab) throws NullPointerException 74 | { 75 | this.statsTab = Objects.requireNonNull(statsTab); 76 | } 77 | // ---------------------------------------------- 78 | public final StatsTab getStatsTab() { return this.statsTab; } 79 | // ---------------------------------------------- 80 | public final @Override Component getText() { return this.statsTab.getName(); } 81 | public final @Override @Nullable Runnable getOnSelect() { return null; } 82 | public final @Override @Nullable Tooltip getTooltip() { return this.tooltip; } 83 | // ---------------------------------------------- 84 | } 85 | // ================================================== 86 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/util/interfaces/IThirdPartyStatsListener.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.util.interfaces; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | import io.github.thecsdev.betterstats.api.util.io.IEditableStatsProvider; 6 | import io.github.thecsdev.betterstats.api.util.io.IStatsProvider; 7 | import io.github.thecsdev.tcdcommons.api.client.util.interfaces.IStatsListener; 8 | import net.minecraft.client.gui.screens.Screen; 9 | 10 | /** 11 | * This interface is similar to {@link IStatsListener}.
12 | * The difference is that this interface is used to listen for "third-party" 13 | * statistics, aka things like statistics about other players that are 14 | * present on the server the client is currently connected to. 15 | * @see IStatsListener 16 | * @apiNote Intended to be used on the client-side. 17 | * @apiNote This interface is intended to be applied to client-side 18 | * {@link Screen}s. The reason this interface is present in common-sided 19 | * APIs is so {@link TpslContext} is fully visible in all contexts. 20 | */ 21 | public interface IThirdPartyStatsListener 22 | { 23 | // ================================================== 24 | /** 25 | * Called when third-party statistics are received. 26 | * @param context The {@link TpslContext}. 27 | */ 28 | public void onStatsReady(TpslContext context); 29 | // ================================================== 30 | /** 31 | * An interface that provides the context about received 32 | * third-party statistics in {@link IThirdPartyStatsListener#onStatsReady(TpslContext)}. 33 | */ 34 | public static interface TpslContext 35 | { 36 | /** 37 | * An enum that indicates what kind of third-party statistics were received. 38 | * @apiNote Each {@link Type} has its own unique {@link Integer} value. This 39 | * value must NOT be changed at all, as the BSS network depends on them being 40 | * what they are currently set to. 41 | */ 42 | public static enum Type 43 | { 44 | /** 45 | * Represents a "null"-like or "unknown" value. 46 | * Used in places where actual {@code null} values are not allowed. 47 | * @apiNote {@link TpslContext#getStatsProvider()} should return 48 | * {@code null} when this {@link Type} applies. 49 | */ 50 | NULL(0), 51 | 52 | /** 53 | * Indicates that the third-party statistics are about a player 54 | * that is present on the same server the client is connected to. 55 | */ 56 | SAME_SERVER_PLAYER(100), 57 | 58 | /** 59 | * Indicates that a request was made to retrieve statistics about 60 | * another player present on the current server, but that other 61 | * player is either offline or does not exist or does not consent 62 | * to having their statistics shared. 63 | * @apiNote {@link TpslContext#getStatsProvider()} should return {@code null}. 64 | */ 65 | SAME_SERVER_PLAYER_NOT_FOUND(101); 66 | 67 | private final int value; 68 | private Type(int value) { this.value = value; } 69 | public final int getIntValue() { return this.value; } 70 | 71 | public static final Type of(int intValue) 72 | { 73 | for(final var v : values()) 74 | if(v.getIntValue() == intValue) 75 | return v; 76 | return NULL; 77 | } 78 | } 79 | 80 | /** 81 | * Returns information about the {@link Type} of 82 | * third-party statistics that were received. 83 | */ 84 | public Type getType(); 85 | 86 | /** 87 | * Returns the name of the player the {@link IStatsProvider} represents, 88 | * if applicable. May not always be present. 89 | */ 90 | public @Nullable String getPlayerName(); 91 | 92 | /** 93 | * Returns the {@link IStatsListener} containing the third-party stats. 94 | * @apiNote Depending on the {@link Type}, this may return {@code null}. 95 | * @apiNote Might not be {@link IEditableStatsProvider}. Do not treat it as such. 96 | */ 97 | public @Nullable IStatsProvider getStatsProvider(); 98 | } 99 | // ================================================== 100 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/client/gui/stats/widget/AbstractStatWidget.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.client.gui.stats.widget; 2 | 3 | import static io.github.thecsdev.betterstats.client.BetterStatsClient.MC_CLIENT; 4 | 5 | import java.util.Objects; 6 | import net.minecraft.client.resources.sounds.SimpleSoundInstance; 7 | import net.minecraft.sounds.SoundEvents; 8 | import io.github.thecsdev.betterstats.api.util.stats.SUStat; 9 | import io.github.thecsdev.tcdcommons.api.client.gui.panel.TPanelElement; 10 | import io.github.thecsdev.tcdcommons.api.client.gui.util.TDrawContext; 11 | import io.github.thecsdev.tcdcommons.api.client.gui.util.TInputContext; 12 | import io.github.thecsdev.tcdcommons.api.client.gui.widget.TClickableWidget; 13 | import io.github.thecsdev.tcdcommons.api.util.annotations.Virtual; 14 | 15 | /** 16 | * A GUI widget that displays stats information from a given {@link SUStat}. 17 | */ 18 | public abstract class AbstractStatWidget> extends TClickableWidget 19 | { 20 | // ================================================== 21 | /** 22 | * The {@link SUStat}. 23 | */ 24 | protected final S stat; 25 | // -------------------------------------------------- 26 | /** 27 | * The background fill color of the {@link AbstractStatWidget}. 28 | */ 29 | protected int backgroundColor = TPanelElement.COLOR_BACKGROUND; 30 | 31 | /** 32 | * The outline color used when this {@link AbstractStatWidget} is not focused or hovered. 33 | */ 34 | protected int outlineColor = 0; 35 | 36 | /** 37 | * The outline color used when this {@link AbstractStatWidget} is focused or hovered. 38 | */ 39 | protected int focusOutlineColor = TPanelElement.COLOR_OUTLINE_FOCUSED; 40 | // ================================================== 41 | public AbstractStatWidget(int x, int y, int width, int height, S stat) throws NullPointerException 42 | { 43 | super(x, y, width, height); 44 | this.stat = Objects.requireNonNull(stat); 45 | } 46 | // ================================================== 47 | /** 48 | * Returns the {@link SUStat} associated with this {@link AbstractStatWidget}. 49 | */ 50 | public final S getStat() { return this.stat; } 51 | // -------------------------------------------------- 52 | public final int getBackgroundColor() { return this.backgroundColor; } 53 | public final int getOutlineColor() { return this.outlineColor; } 54 | public final int getFocusOutlineColor() { return this.focusOutlineColor; } 55 | public @Virtual void setBackgroundColor(int backgroundColor) { this.backgroundColor = backgroundColor; } 56 | public @Virtual void setOutlineColor(int outlineColor) { this.outlineColor = outlineColor; } 57 | public @Virtual void setFocusOutlineColor(int focusOutlineColor) { this.focusOutlineColor = focusOutlineColor; } 58 | // ================================================== 59 | protected @Virtual @Override void onClick() {} 60 | // -------------------------------------------------- 61 | public @Virtual @Override boolean input(TInputContext inputContext) 62 | { 63 | //handle input based on type 64 | switch(inputContext.getInputType()) 65 | { 66 | case MOUSE_PRESS: //clearing focus when clicking and focused 67 | if(isFocused()) { getParentTScreen().setFocusedElement(null); return true; } 68 | break; 69 | default: break; 70 | } 71 | //return super by default 72 | return super.input(inputContext); 73 | } 74 | // -------------------------------------------------- 75 | public @Virtual @Override void render(TDrawContext pencil) { pencil.drawTFill(this.backgroundColor); } 76 | public @Virtual @Override void postRender(TDrawContext pencil) 77 | { 78 | //render the borders 79 | if(isFocusedOrHovered()) pencil.drawTBorder(this.focusOutlineColor); 80 | else pencil.drawTBorder(this.outlineColor); 81 | 82 | //a neat little on-hover sound 83 | if(isHovered() && !isFocused() && !this.__wasHovered) 84 | MC_CLIENT.getSoundManager().play(SimpleSoundInstance.forUI( 85 | SoundEvents.NOTE_BLOCK_HAT.value(), 1.8f, 0.05f)); 86 | this.__wasHovered = isHovered(); 87 | } 88 | private boolean __wasHovered = false; 89 | // ================================================== 90 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/betterstats/lang/ko_kr.json: -------------------------------------------------------------------------------- 1 | { 2 | "betterstats": "더 나은 통계 스크린", 3 | 4 | "betterstats.translators.title": "번역가", 5 | 6 | 7 | "commands.statistics.edit.output": "%s 통계를 %s 플레이어에게 수정했습니다.", 8 | "commands.statistics.clear.kick": "당신의 통계가 지워졌습니다, 다시 접속하고 가입해야 합니다.", 9 | "commands.statistics.clear.output": "%s 플레이어의 통계를 지웠습니다.", 10 | 11 | 12 | "betterstats.stattype_phrase.minecraft.killed": "처치한 수", 13 | "betterstats.stattype_phrase.minecraft.killed_by": "(~(으)로 인해) 사망", 14 | "betterstats.stattype_phrase.morestats.damaged": "입힌 피해 (x10)", 15 | "betterstats.stattype_phrase.morestats.damaged_by": "받은 피해 (x10)", 16 | "betterstats.stattype_phrase.morestats.totem_popped_by": "(~에 의해) 발동된 토템", 17 | 18 | 19 | "betterstats.gui.menu_bar.menu_file": "파일", 20 | "betterstats.gui.menu_bar.menu_file.new": "새로 만들기", 21 | "betterstats.gui.menu_bar.menu_file.open": "열기", 22 | "betterstats.gui.menu_bar.menu_file.save": "저장", 23 | "betterstats.gui.menu_bar.menu_file.save_as": "다른 이름으로 저장", 24 | "betterstats.gui.menu_bar.menu_view": "보기", 25 | "betterstats.gui.menu_bar.menu_view.vanilla_stats": "바닐라 통계 보기", 26 | "betterstats.gui.menu_bar.menu_about": "정보", 27 | "betterstats.gui.menu_bar.menu_about.source": "소스 코드", 28 | "betterstats.gui.menu_bar.menu_about.curseforge": "CurseForge", 29 | "betterstats.gui.menu_bar.menu_about.modrinth": "Modrinth", 30 | "betterstats.gui.menu_bar.menu_about.youtube": "YouTube", 31 | "betterstats.gui.menu_bar.menu_about.kofi": "Ko-Fi", 32 | "betterstats.gui.menu_bar.menu_about.discord": "Discord", 33 | 34 | "betterstats.api.client.gui.stats.panel.statfilterspanel.filters": "필터", 35 | "betterstats.api.client.gui.stats.panel.statfilterspanel.show_empty_stats": "빈 통계 보기", 36 | "betterstats.api.util.enumerations.filtergroupby.default": "기본", 37 | "betterstats.api.util.enumerations.filtergroupby.mod": "모드", 38 | 39 | "betterstats.api.client.gui.stats.widget.mobstatwidget.kills": "살인", 40 | "betterstats.api.client.gui.stats.widget.mobstatwidget.deaths": "사망", 41 | "betterstats.api.client.gui.stats.widget.playerbadgestatwidget.obtained": "얻음", 42 | "betterstats.api.client.gui.stats.widget.generalstatwidget.value": "값", 43 | 44 | "betterstats.client.gui.stats.panel.statstabpanel.no_stats_yet": "아직 보여줄 통계가 없습니다...", 45 | 46 | "betterstats.config.debug_mode": "디버그 모드", 47 | "betterstats.config.gui_mob_follow_cursor": "GUI 몬스터가 커서를 따라갑니다", 48 | 49 | "betterstats.player_badge.dedication.title": "헌신", 50 | "betterstats.player_badge.dedication.description": "진정한 헌신을 보였습니다! 이 세상에서 적어도 12일 (288시간) 동안 플레이했습니다.", 51 | "betterstats.player_badge.loyalty.title": "충성", 52 | "betterstats.player_badge.loyalty.description": "귀하의 충성스러움은 칭찬 받을 만합니다! 이 세상에서 적어도 24일 (576시간) 동안 플레이했습니다.", 53 | "betterstats.player_badge.the_next_generation.title": "다음 세대", 54 | "betterstats.player_badge.the_next_generation.description": "당신은 최종 승리의 상징인 드래곤 알을 소유했습니다.", 55 | "betterstats.player_badge.adventurous_traveler.title": "모험가 여행자", 56 | "betterstats.player_badge.adventurous_traveler.description": "높은 산과 낮은 계곡을 통해, 당신은 먼 곳으로 여행했습니다, 많은 것을 보여주었습니다. 미지의 풍경을 찾아, 금 블록으로 이루어진 여행의 정도를 찾아. 그리고 당신은 그것을 보았습니다...\n\n당신은 적어도 727,000 블록을 여행했습니다.", 57 | "betterstats.player_badge.perennial_survivor.title": "영원한 생존자", 58 | "betterstats.player_badge.perennial_survivor.description": "당신은 지금까지 죽음을 피해 왔습니다. 당신은 불가피한 것을 계속 피할 수 있을까요?\n\n당신은 적어도 12시간 동안 생존했습니다.", 59 | 60 | 61 | "betterstats.gui.hud_screen.betterstatshudscreen": "통계 HUD", 62 | "betterstats.gui.hud_screen.betterstatshudscreen.pin_stat": "HUD에 고정", 63 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_1": "통계를 우클릭하면([Shift]+RMB) 이곳에 추가할 수 있습니다.", 64 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_2": "HUD 항목을 우클릭하여 수정하거나 제거합니다.", 65 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_3": "이 스크린을 닫으려면 Escape (ESC)를 누르십시오.", 66 | 67 | "betterstats.network.betterstatsnetworkhandler.toggle_tooltip": "'betterstats' 서버 연결 토글", 68 | "betterstats.network.betterstatsnetworkhandler.consent_warning": "중요:\n이 기능을 활성화하면, 서버는 당신이 'betterstats'가 설치된 수정된 클라이언트를 사용하고 있다는 사실을 알게 될 것입니다. 당신의 개인 정보를 보호하기 위해, 수정된 클라이언트나 이 모드를 허용하지 않는 서버에서는 이 기능을 사용하지 마십시오.\n\n이 기능을 활성화하면, 모드가 서버에도 설치되어 있을 때 사용할 수 있는 몇 가지 추가 기능이 추가됩니다." 69 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/client/registry/BSStatsTabs.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.client.registry; 2 | 3 | import static io.github.thecsdev.betterstats.BetterStats.getModID; 4 | 5 | import org.jetbrains.annotations.ApiStatus.Internal; 6 | 7 | import io.github.thecsdev.betterstats.BetterStats; 8 | import io.github.thecsdev.betterstats.api.client.gui.util.StatsTabUtils; 9 | import io.github.thecsdev.betterstats.client.gui.stats.tabs.AdvancementsTab; 10 | import io.github.thecsdev.betterstats.client.gui.stats.tabs.BSConfigTab; 11 | import io.github.thecsdev.betterstats.client.gui.stats.tabs.BSCreditsTab; 12 | import io.github.thecsdev.betterstats.client.gui.stats.tabs.BSStatsSharingTab; 13 | import io.github.thecsdev.betterstats.client.gui.stats.tabs.FoodStuffsStatsTab; 14 | import io.github.thecsdev.betterstats.client.gui.stats.tabs.GeneralStatsTab; 15 | import io.github.thecsdev.betterstats.client.gui.stats.tabs.ItemStatsTab; 16 | import io.github.thecsdev.betterstats.client.gui.stats.tabs.MobStatsTab; 17 | import io.github.thecsdev.betterstats.client.gui.stats.tabs.MonstersHuntedStatsTab; 18 | import io.github.thecsdev.betterstats.client.gui.stats.tabs.PlayerBadgeStatsTab; 19 | import io.github.thecsdev.tcdcommons.api.client.gui.panel.TPanelElement; 20 | import net.minecraft.network.chat.Component; 21 | import net.minecraft.resources.ResourceLocation; 22 | 23 | /** 24 | * {@link BetterStats}'s {@link StatsTab}s. 25 | */ 26 | public final class BSStatsTabs 27 | { 28 | // ================================================== 29 | private BSStatsTabs() {} 30 | // ================================================== 31 | /** 32 | * An {@link Integer} corresponding to the yellow color.

33 | * Used for group labels featured in {@link StatsTabUtils#initGroupLabel(TPanelElement, Component)}. 34 | */ 35 | public static final @Internal int COLOR_SPECIAL = 0xFFFFFF00; 36 | // -------------------------------------------------- 37 | public static final StatsTab BSS_CREDITS = new BSCreditsTab(); 38 | public static final StatsTab BSS_CONFIG = new BSConfigTab(); 39 | public static final StatsTab GENERAL = new GeneralStatsTab(); 40 | public static final StatsTab ITEMS = new ItemStatsTab(); 41 | public static final StatsTab ENTITIES = new MobStatsTab(); 42 | public static final StatsTab FOOD_STUFFS = new FoodStuffsStatsTab(); 43 | public static final StatsTab MONSTERS_HUNTED = new MonstersHuntedStatsTab(); 44 | public static final StatsTab PLAYER_BADGES = new PlayerBadgeStatsTab(); 45 | public static final StatsTab BSS_STATS_SHARING = new BSStatsSharingTab(); 46 | public static final StatsTab ADVANCEMENTS = new AdvancementsTab(); 47 | // ================================================== 48 | /** 49 | * Registers the {@link BSStatsTabs} to the {@link BSClientRegistries#STATS_TAB} registry. 50 | * @apiNote May only be called once. 51 | */ 52 | public static void register() {} 53 | static 54 | { 55 | final String modId = getModID(); 56 | BSClientRegistries.STATS_TAB.register(ResourceLocation.fromNamespaceAndPath(modId, "bss_credits"), BSS_CREDITS); 57 | BSClientRegistries.STATS_TAB.register(ResourceLocation.fromNamespaceAndPath(modId, "bss_config"), BSS_CONFIG); 58 | BSClientRegistries.STATS_TAB.register(ResourceLocation.fromNamespaceAndPath(modId, "general"), GENERAL); 59 | BSClientRegistries.STATS_TAB.register(ResourceLocation.fromNamespaceAndPath(modId, "items"), ITEMS); 60 | BSClientRegistries.STATS_TAB.register(ResourceLocation.fromNamespaceAndPath(modId, "entities"), ENTITIES); 61 | BSClientRegistries.STATS_TAB.register(ResourceLocation.fromNamespaceAndPath(modId, "food_stuffs"), FOOD_STUFFS); 62 | BSClientRegistries.STATS_TAB.register(ResourceLocation.fromNamespaceAndPath(modId, "monsters_hunted"), MONSTERS_HUNTED); 63 | BSClientRegistries.STATS_TAB.register(ResourceLocation.fromNamespaceAndPath(modId, "player_badges"), PLAYER_BADGES); 64 | BSClientRegistries.STATS_TAB.register(ResourceLocation.fromNamespaceAndPath(modId, "bss_stats_sharing"), BSS_STATS_SHARING); 65 | BSClientRegistries.STATS_TAB.register(ResourceLocation.fromNamespaceAndPath(modId, "advancements"), ADVANCEMENTS); 66 | } 67 | // ================================================== 68 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/util/io/RAMStatsProvider.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.util.io; 2 | 3 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.literal; 4 | 5 | import java.util.Objects; 6 | import net.minecraft.network.FriendlyByteBuf; 7 | import net.minecraft.network.chat.Component; 8 | import net.minecraft.resources.ResourceLocation; 9 | import net.minecraft.stats.Stat; 10 | import org.jetbrains.annotations.ApiStatus.Internal; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import com.mojang.authlib.GameProfile; 14 | 15 | import io.github.thecsdev.tcdcommons.api.util.exceptions.UnsupportedFileVersionException; 16 | import it.unimi.dsi.fastutil.objects.Object2IntMap; 17 | import it.unimi.dsi.fastutil.objects.Object2IntMaps; 18 | import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; 19 | 20 | /** 21 | * An {@link IEditableStatsProvider} whose statistics are loaded into and 22 | * held in the memory (aka RAM), hence the name {@link RAMStatsProvider}. 23 | * @apiNote Extending this class is not recommended. This class is not final 24 | * only because an {@link Internal} component needs to extend it. 25 | */ 26 | public /*non-final*/ class RAMStatsProvider implements IEditableStatsProvider 27 | { 28 | // ================================================== 29 | protected @Nullable Component displayName; 30 | protected @Nullable GameProfile gameProfile; 31 | // -------------------------------------------------- 32 | protected final Object2IntMap> statMap = Object2IntMaps.synchronize(new Object2IntOpenHashMap<>()); 33 | protected final Object2IntMap playerBadgeStatMap = Object2IntMaps.synchronize(new Object2IntOpenHashMap<>()); 34 | // ================================================== 35 | public RAMStatsProvider() 36 | { 37 | this.statMap.defaultReturnValue(0); 38 | this.playerBadgeStatMap.defaultReturnValue(0); 39 | } 40 | 41 | /** 42 | * Creates a {@link RAMStatsProvider} instance, after which 43 | * {@link StatsProviderIO#read(FriendlyByteBuf, IEditableStatsProvider)} is called. 44 | * @param buffer The {@link FriendlyByteBuf} to read from. 45 | * @param releaseBuffer After reading, call {@link FriendlyByteBuf#release()}? 46 | * @apiNote {@link FriendlyByteBuf#release()} will get called when requested, even when an {@link Exception} is raised. 47 | */ 48 | public RAMStatsProvider(FriendlyByteBuf buffer, boolean releaseBuffer) 49 | throws NullPointerException, IllegalHeaderException, UnsupportedFileVersionException 50 | { 51 | this(); 52 | try { StatsProviderIO.read(Objects.requireNonNull(buffer), this); } 53 | finally { if(releaseBuffer && buffer.refCnt() > 0) buffer.release(); } 54 | } 55 | // ================================================== 56 | public final @Override Component getDisplayName() { return this.displayName; } 57 | public final @Override void setDisplayName(Component displayName) 58 | { 59 | if(displayName == null) displayName = literal("-"); 60 | this.displayName = displayName; 61 | } 62 | // 63 | public final @Override GameProfile getGameProfile() { return this.gameProfile; } 64 | public final @Override void setGameProfile(@Nullable GameProfile playerProfile) { this.gameProfile = playerProfile; } 65 | // -------------------------------------------------- 66 | public final @Override int getStatValue(Stat stat) { return this.statMap.getInt(stat); } 67 | public final @Override void setStatValue(Stat stat, int value) throws NullPointerException 68 | { 69 | if(value < 1) this.statMap.removeInt(stat); 70 | else this.statMap.put(Objects.requireNonNull(stat), value); 71 | } 72 | // -------------------------------------------------- 73 | public final @Override int getPlayerBadgeValue(ResourceLocation badgeId) { return this.playerBadgeStatMap.getInt(badgeId); } 74 | public final @Override void setPlayerBadgeValue(ResourceLocation badgeId, int value) throws NullPointerException 75 | { 76 | if(value < 1) this.playerBadgeStatMap.removeInt(badgeId); 77 | else this.playerBadgeStatMap.put(Objects.requireNonNull(badgeId), value); 78 | } 79 | // ================================================== 80 | public final Object2IntMap> getStatMap() { return this.statMap; } 81 | public final Object2IntMap getPlayerBadgeStatMap() { return this.playerBadgeStatMap; } 82 | // ================================================== 83 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/client/gui/widget/AdvancementStatWidget.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.client.gui.widget; 2 | 3 | import static io.github.thecsdev.betterstats.api.client.gui.stats.widget.ItemStatWidget.SIZE; 4 | import static io.github.thecsdev.tcdcommons.api.client.gui.panel.TPanelElement.COLOR_OUTLINE_FOCUSED; 5 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.literal; 6 | 7 | import java.util.Objects; 8 | import java.util.function.Consumer; 9 | import net.minecraft.ChatFormatting; 10 | import net.minecraft.advancements.Advancement; 11 | import net.minecraft.advancements.AdvancementNode; 12 | import net.minecraft.client.gui.components.Tooltip; 13 | import net.minecraft.world.item.ItemStack; 14 | import net.minecraft.world.item.Items; 15 | import org.jetbrains.annotations.ApiStatus.Experimental; 16 | import org.jetbrains.annotations.ApiStatus.Internal; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | import io.github.thecsdev.tcdcommons.api.client.gui.panel.TPanelElement; 20 | import io.github.thecsdev.tcdcommons.api.client.gui.util.TDrawContext; 21 | import io.github.thecsdev.tcdcommons.api.client.gui.widget.TClickableWidget; 22 | 23 | /** 24 | * A stat widget representing statistics about an {@link Advancement}. 25 | */ 26 | @Experimental 27 | public final @Internal class AdvancementStatWidget extends TClickableWidget 28 | { 29 | // ================================================== 30 | private final AdvancementNode advancement; 31 | private final ItemStack displayItem; 32 | // -------------------------------------------------- 33 | private int backgroundColor = 0; 34 | private @Nullable Consumer onClick; 35 | // ================================================== 36 | public AdvancementStatWidget(int x, int y, AdvancementNode advancement) throws NullPointerException 37 | { 38 | super(x, y, SIZE, SIZE); 39 | this.advancement = Objects.requireNonNull(advancement); 40 | 41 | final var tooltip = literal(""); 42 | final @Nullable var display = advancement.advancement().display().orElse(null); 43 | if(display != null) 44 | { 45 | tooltip.append(literal("").append(display.getTitle()).withStyle(ChatFormatting.YELLOW)); 46 | tooltip.append("\n"); 47 | tooltip.append(literal("").append(display.getDescription()).withStyle(ChatFormatting.GRAY)); 48 | this.displayItem = display.getIcon(); 49 | 50 | this.backgroundColor = switch(display.getType()) 51 | { 52 | case TASK -> TPanelElement.COLOR_BACKGROUND; 53 | case GOAL -> 0x50223333; 54 | case CHALLENGE -> 0x44330066; 55 | default -> TPanelElement.COLOR_BACKGROUND; 56 | }; 57 | } 58 | else 59 | { 60 | tooltip.append(literal(advancement.holder().id().toString()).withStyle(ChatFormatting.YELLOW)); 61 | this.displayItem = Items.AIR.getDefaultInstance(); 62 | this.backgroundColor = TPanelElement.COLOR_BACKGROUND; 63 | } 64 | setTooltip(Tooltip.create(tooltip)); 65 | } 66 | // ================================================== 67 | /** 68 | * Retrieves the "on-click" action of this {@link AdvancementStatWidget}. 69 | */ 70 | public final @Nullable Consumer getOnClick() { return this.onClick; } 71 | 72 | /** 73 | * Sets the action that will take place when this {@link AdvancementStatWidget} is clicked. 74 | */ 75 | public final void setOnClick(@Nullable Consumer onClick) { this.onClick = onClick; } 76 | // -------------------------------------------------- 77 | /** 78 | * Returns the associated {@link Advancement}. 79 | */ 80 | public final AdvancementNode getAdvancement() { return this.advancement; } 81 | // ================================================== 82 | protected final @Override void onClick() { if(this.onClick != null) this.onClick.accept(this); } 83 | // -------------------------------------------------- 84 | public final @Override void render(TDrawContext pencil) 85 | { 86 | //draw the solid background color, and then the display item 87 | pencil.drawTFill(this.backgroundColor); 88 | pencil.renderItem(this.displayItem, getX() + 3, getY() + 3); 89 | } 90 | 91 | public final @Override void postRender(TDrawContext pencil) 92 | { 93 | //draw an outline when the widget is hovered or focused 94 | if(isFocusedOrHovered()) 95 | pencil.drawTBorder(COLOR_OUTLINE_FOCUSED); 96 | } 97 | // ================================================== 98 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/betterstats/lang/ja_jp.json: -------------------------------------------------------------------------------- 1 | { 2 | "betterstats": "Better Statistics Screen", 3 | 4 | "betterstats.translators.title": "翻訳者", 5 | 6 | 7 | "commands.statistics.edit.output": "%2$s人のプレイヤーの統計%1$sを編集しました", 8 | "commands.statistics.clear.kick": "統計がクリアされたためサーバーから切断されました。再参加が必要です。", 9 | "commands.statistics.clear.output": "%s人のプレイヤーの統計をクリアしました", 10 | "commands.statistics.query.output": "%sの統計%sの値は %s です", 11 | 12 | 13 | "betterstats.stattype_phrase.minecraft.killed": "倒した数", 14 | "betterstats.stattype_phrase.minecraft.killed_by": "(~に)やられた", 15 | "betterstats.stattype_phrase.morestats.damaged": "与えたダメージ (x10)", 16 | "betterstats.stattype_phrase.morestats.damaged_by": "受けたダメージ (x10)", 17 | "betterstats.stattype_phrase.morestats.totem_popped_by": "(~に)起動されたトーテム", 18 | 19 | 20 | "betterstats.gui.menu_bar.menu_file": "ファイル", 21 | "betterstats.gui.menu_bar.menu_file.new": "新規", 22 | "betterstats.gui.menu_bar.menu_file.open": "開く", 23 | "betterstats.gui.menu_bar.menu_file.save": "保存", 24 | "betterstats.gui.menu_bar.menu_file.save_as": "名前を付けて保存", 25 | "betterstats.gui.menu_bar.menu_view": "表示", 26 | "betterstats.gui.menu_bar.menu_view.vanilla_stats": "バニラの統計画面を表示する", 27 | "betterstats.gui.menu_bar.menu_about": "このModについて", 28 | "betterstats.gui.menu_bar.menu_about.source": "ソースコード", 29 | "betterstats.gui.menu_bar.menu_about.curseforge": "CurseForge", 30 | "betterstats.gui.menu_bar.menu_about.modrinth": "Modrinth", 31 | "betterstats.gui.menu_bar.menu_about.youtube": "YouTube", 32 | "betterstats.gui.menu_bar.menu_about.kofi": "Ko-Fi", 33 | "betterstats.gui.menu_bar.menu_about.discord": "Discord", 34 | 35 | "betterstats.api.client.gui.stats.panel.gameprofilepanel.uuid": "UUID", 36 | 37 | "betterstats.api.client.gui.stats.panel.statfilterspanel.filters": "フィルター", 38 | "betterstats.api.client.gui.stats.panel.statfilterspanel.show_empty_stats": "空の統計情報を表示する", 39 | "betterstats.api.client.gui.stats.panel.statfilterspanel.no_filters_question": "フィルターなし?", 40 | 41 | "betterstats.api.util.enumerations.filtergroupby.default": "デフォルト", 42 | "betterstats.api.util.enumerations.filtergroupby.mod": "Mod", 43 | 44 | "betterstats.api.client.gui.stats.widget.mobstatwidget.kills": "倒した回数", 45 | "betterstats.api.client.gui.stats.widget.mobstatwidget.deaths": "死亡した回数", 46 | "betterstats.api.client.gui.stats.widget.playerbadgestatwidget.obtained": "取得した数", 47 | "betterstats.api.client.gui.stats.widget.generalstatwidget.value": "値", 48 | 49 | "betterstats.client.gui.stats.panel.statstabpanel.no_stats_yet": "表示できる統計情報がまだありません…", 50 | "betterstats.client.gui.stats.panel.statstabpanel.seed_sha256": "シード(SHA-256)", 51 | 52 | "betterstats.config.debug_mode": "デバッグモード", 53 | "betterstats.config.gui_mob_follow_cursor": "統計画面のモブをカーソルに合わせて動かす", 54 | 55 | "betterstats.player_badge.dedication.title": "献身", 56 | "betterstats.player_badge.dedication.description": "君って献身的なんだね!このワールドで12日(288時間)以上プレイする。", 57 | "betterstats.player_badge.loyalty.title": "忠誠心", 58 | "betterstats.player_badge.loyalty.description": "見上げた忠誠心じゃないか!このワールドで24日(576時間)以上プレイする。", 59 | "betterstats.player_badge.the_next_generation.title": "ザ・ネクストジェネレーション", 60 | "betterstats.player_badge.the_next_generation.description": "究極の勝利の象徴である「ドラゴンの卵」を手にする。", 61 | "betterstats.player_badge.adventurous_traveler.title": "冒険好きな旅人", 62 | "betterstats.player_badge.adventurous_traveler.description": "高い山を越え、低い谷を越え、あなたは遠くまで旅をしてきた。景色を求め、語られざる物語を求め、金ブロックで旅を測ってきた。そしてあなたは見た...\n\n727,000ブロック以上を移動する。", 63 | "betterstats.player_badge.perennial_survivor.title": "永遠のサバイバー", 64 | "betterstats.player_badge.perennial_survivor.description": "今はなんとか生き延びているが、死はいずれ訪れる。それでもその運命に抗うつもりか?\n\n12時間以上生き残る。", 65 | 66 | 67 | "betterstats.gui.hud_screen.betterstatshudscreen": "統計情報をHUDに表示する", 68 | "betterstats.gui.hud_screen.betterstatshudscreen.pin_stat": "HUDにピンを付ける", 69 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_1": "統計情報を右クリック([Shift]+RMB)すると、ここに追加できます。", 70 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_2": "HUDエントリーを右クリックして、それを変更または削除します。", 71 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_3": "この画面を閉じるには、Escape(ESC)を押してください。", 72 | 73 | "betterstats.network.betterstatsnetworkhandler.toggle_tooltip": "「betterstats」によるサーバー接続を切り替える", 74 | "betterstats.network.betterstatsnetworkhandler.consent_warning": "重要:\nこの機能を有効にすると「betterstats」を使用していることがサーバーに検出されます。Mod導入済みクライアントまたはこのModが許可されていないサーバーでは使用しないでください。\n\n有効にすると、このModがサーバーにも導入されている場合に使用できる機能が追加されます。" 75 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/client/gui/screen/hud/entry/StatsHudGeneralEntry.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.client.gui.screen.hud.entry; 2 | 3 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.literal; 4 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.translatable; 5 | 6 | import java.util.Objects; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraft.stats.StatType; 9 | import net.minecraft.stats.Stats; 10 | import org.jetbrains.annotations.ApiStatus.Experimental; 11 | 12 | import io.github.thecsdev.betterstats.api.client.gui.stats.widget.CustomStatElement; 13 | import io.github.thecsdev.betterstats.api.client.util.io.LocalPlayerStatsProvider; 14 | import io.github.thecsdev.betterstats.api.util.formatters.StatValueFormatter; 15 | import io.github.thecsdev.betterstats.api.util.io.IEditableStatsProvider; 16 | import io.github.thecsdev.betterstats.api.util.io.IStatsProvider; 17 | import io.github.thecsdev.betterstats.api.util.stats.SUGeneralStat; 18 | import io.github.thecsdev.betterstats.client.network.OtherClientPlayerStatsProvider; 19 | import io.github.thecsdev.tcdcommons.api.client.gui.TElement; 20 | import io.github.thecsdev.tcdcommons.api.client.gui.panel.TPanelElement; 21 | import io.github.thecsdev.tcdcommons.api.client.gui.screen.TWidgetHudScreen; 22 | import io.github.thecsdev.tcdcommons.api.client.gui.util.TDrawContext; 23 | 24 | public class StatsHudGeneralEntry extends TWidgetHudScreen.WidgetEntry 25 | { 26 | // ================================================== 27 | static final int WIDTH = 100; 28 | // -------------------------------------------------- 29 | protected IStatsProvider statsProvider; 30 | protected final ResourceLocation generalStat; 31 | protected StatType mode = Stats.CUSTOM; 32 | // 33 | protected final @Experimental StatValueFormatter formatter; //since v3.13 34 | // ================================================== 35 | public StatsHudGeneralEntry(SUGeneralStat stat, StatValueFormatter formatter) throws NullPointerException { 36 | this(stat.getStatsProvider(), stat.getGeneralStat().getValue(), formatter); 37 | } 38 | public StatsHudGeneralEntry( 39 | IStatsProvider statsProvider, 40 | ResourceLocation generalStat, 41 | StatValueFormatter formatter) throws NullPointerException 42 | { 43 | super(0.5, 0.25); 44 | this.statsProvider = Objects.requireNonNull(statsProvider); 45 | this.generalStat = Objects.requireNonNull(generalStat); 46 | this.formatter = Objects.requireNonNull(formatter); 47 | } 48 | // ================================================== 49 | public final @Override TElement createWidget() 50 | { 51 | //ensure local stat providers are up-to-date 52 | if(this.statsProvider instanceof LocalPlayerStatsProvider) 53 | this.statsProvider = Objects.requireNonNull(LocalPlayerStatsProvider.getInstance()); 54 | 55 | //create the element, and add the context menu to it 56 | if(this.mode == null) this.mode = Stats.CUSTOM; 57 | final var el = new Element(); 58 | el.eContextMenu.register((__, cMenu) -> 59 | { 60 | cMenu.addButton(translatable("selectWorld.delete"), ___ -> removeEntry()); 61 | }); 62 | 63 | //return the new element 64 | return el; 65 | } 66 | // ================================================== 67 | private final class Element extends TElement 68 | { 69 | private int overlayColor = (statsProvider instanceof IEditableStatsProvider) ? 70 | ((statsProvider instanceof OtherClientPlayerStatsProvider) ? 0x7700aaff : 0x55ff0000) : 71 | 0; 72 | public Element() 73 | { 74 | //init super 75 | super(0, 0, WIDTH, CustomStatElement.HEIGHT); 76 | 77 | //obtain values and texts 78 | int rightVar = statsProvider.getStatValue(mode, generalStat); 79 | var left = (mode == Stats.CUSTOM) ? 80 | SUGeneralStat.getGeneralStatText(mode.get(generalStat)) : 81 | literal(""); 82 | var right = StatsHudGeneralEntry.this.formatter.format(rightVar); 83 | 84 | //update width 85 | this.setSize(this.width + getTextRenderer().width(left), this.height); 86 | 87 | //add custom stat element child 88 | addChild(new CustomStatElement(0, 0, this.width, left, right)); 89 | } 90 | public @Override void render(TDrawContext pencil) 91 | { 92 | pencil.drawTFill(TPanelElement.COLOR_BACKGROUND); 93 | pencil.drawTFill(this.overlayColor); 94 | } 95 | } 96 | // ================================================== 97 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/client/util/io/LocalPlayerStatsProvider.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.client.util.io; 2 | 3 | import java.util.Objects; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.client.player.LocalPlayer; 6 | import net.minecraft.network.chat.Component; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraft.stats.Stat; 9 | import net.minecraft.stats.StatType; 10 | import net.minecraft.stats.StatsCounter; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import com.mojang.authlib.GameProfile; 14 | 15 | import io.github.thecsdev.betterstats.api.util.io.IStatsProvider; 16 | import io.github.thecsdev.betterstats.client.BetterStatsClient; 17 | import io.github.thecsdev.tcdcommons.api.badge.PlayerBadgeHandler; 18 | import io.github.thecsdev.tcdcommons.api.client.badge.ClientPlayerBadge; 19 | 20 | /** 21 | * An {@link IStatsProvider} for {@link LocalPlayer}s. 22 | * @see Minecraft#player 23 | */ 24 | public final class LocalPlayerStatsProvider implements IStatsProvider 25 | { 26 | // ================================================== 27 | private static LocalPlayerStatsProvider INSTANCE = null; 28 | // ================================================== 29 | private final LocalPlayer player; 30 | // -------------------------------------------------- 31 | private final Component displayName; 32 | private final GameProfile gameProfile; 33 | private final StatsCounter statsHandler; 34 | private final PlayerBadgeHandler badgeHandler; 35 | // ================================================== 36 | private LocalPlayerStatsProvider(LocalPlayer player) throws NullPointerException 37 | { 38 | this.player = Objects.requireNonNull(player); 39 | this.displayName = player.getDisplayName(); 40 | this.gameProfile = player.getGameProfile(); 41 | this.statsHandler = player.getStats(); 42 | this.badgeHandler = ClientPlayerBadge.getClientPlayerBadgeHandler(player); 43 | } 44 | // -------------------------------------------------- 45 | public final LocalPlayer getPlayer() { return this.player; } 46 | // ================================================== 47 | public final @Override Component getDisplayName() { return this.displayName; } 48 | public final @Override GameProfile getGameProfile() { return this.gameProfile; } 49 | // -------------------------------------------------- 50 | public final @Override int getStatValue(Stat stat) { return this.statsHandler.getValue(stat); } 51 | public final @Override int getStatValue(StatType type, T stat) { return this.statsHandler.getValue(type, stat); } 52 | public final @Override int getPlayerBadgeValue(ResourceLocation badgeId) { return this.badgeHandler.getValue(badgeId); } 53 | // ================================================== 54 | public final @Override int hashCode() { return this.player.hashCode(); } 55 | public final @Override boolean equals(Object obj) 56 | { 57 | if (this == obj) return true; 58 | if (obj == null || getClass() != obj.getClass()) return false; 59 | final var lpsp = (LocalPlayerStatsProvider)obj; 60 | return (this.player == lpsp.player); 61 | } 62 | // ================================================== 63 | /** 64 | * Returns the current {@link LocalPlayerStatsProvider} instance, 65 | * or {@code null} if the {@link Minecraft} is not "in-game". 66 | */ 67 | public static final @Nullable LocalPlayerStatsProvider getInstance() 68 | { 69 | //obtain the Minecraft client instance 70 | final var player = BetterStatsClient.MC_CLIENT.player; 71 | //if the local player is null, return null... 72 | if(player == null) return (INSTANCE = null); 73 | //...else get or create instance 74 | else 75 | { 76 | //if the instance is null, or its stat handler no longer matches player stat handler, 77 | //create a new instance 78 | if(INSTANCE == null || INSTANCE.player != player) 79 | INSTANCE = new LocalPlayerStatsProvider(player); 80 | //finally return the instance 81 | return INSTANCE; 82 | } 83 | } 84 | 85 | /** 86 | * Creates a {@link LocalPlayerStatsProvider} instance based on a {@link LocalPlayer}. 87 | * @param player The {@link LocalPlayer}. 88 | */ 89 | public static final LocalPlayerStatsProvider of(LocalPlayer player) throws NullPointerException 90 | { 91 | //null-check 92 | Objects.requireNonNull(player); 93 | //return INSTANCE if the player is the same. create and return new stats provider otherwise 94 | return (INSTANCE != null && INSTANCE.player == player) ? 95 | INSTANCE : new LocalPlayerStatsProvider(player); 96 | } 97 | // ================================================== 98 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/betterstats/lang/ar_sa.json: -------------------------------------------------------------------------------- 1 | { 2 | "betterstats": "شاشة الإحصائيات الأفضل", 3 | 4 | "betterstats.translators.title": "المترجمون", 5 | 6 | 7 | "commands.statistics.edit.output": "تم تعديل الإحصائيات %s لـ %s لاعبين.", 8 | "commands.statistics.clear.kick": "تم مسح إحصائياتك، مما يتطلب منك الخروج وإعادة الانضمام.", 9 | "commands.statistics.clear.output": "تم مسح الإحصائيات لـ %s من اللاعبين.", 10 | 11 | 12 | "betterstats.stattype_phrase.minecraft.killed": "عمليات القتل", 13 | "betterstats.stattype_phrase.minecraft.killed_by": "موت بـ", 14 | "betterstats.stattype_phrase.morestats.damaged": "الضرر المتسبب (x10)", 15 | "betterstats.stattype_phrase.morestats.damaged_by": "الضرر المُتلقى (x10)", 16 | "betterstats.stattype_phrase.morestats.totem_popped_by": "الطواطم المُفعلة بواسطة", 17 | 18 | 19 | "betterstats.gui.menu_bar.menu_file": "ملف", 20 | "betterstats.gui.menu_bar.menu_file.new": "جديد", 21 | "betterstats.gui.menu_bar.menu_file.open": "فتح", 22 | "betterstats.gui.menu_bar.menu_file.save": "حفظ", 23 | "betterstats.gui.menu_bar.menu_file.save_as": "حفظ كـ", 24 | "betterstats.gui.menu_bar.menu_view": "عرض", 25 | "betterstats.gui.menu_bar.menu_view.vanilla_stats": "إظهار الإحصائيات الفانيليا", 26 | "betterstats.gui.menu_bar.menu_about": "حول", 27 | "betterstats.gui.menu_bar.menu_about.source": "الكود المصدري", 28 | "betterstats.gui.menu_bar.menu_about.curseforge": "كيرسفورج", 29 | "betterstats.gui.menu_bar.menu_about.modrinth": "مودرينث", 30 | "betterstats.gui.menu_bar.menu_about.youtube": "يوتيوب", 31 | "betterstats.gui.menu_bar.menu_about.kofi": "Ko-Fi", 32 | "betterstats.gui.menu_bar.menu_about.discord": "ديسكورد", 33 | 34 | "betterstats.api.client.gui.stats.panel.statfilterspanel.filters": "الفلاتر", 35 | "betterstats.api.client.gui.stats.panel.statfilterspanel.show_empty_stats": "عرض الإحصائيات الفارغة", 36 | "betterstats.api.util.enumerations.filtergroupby.default": "الافتراضي", 37 | "betterstats.api.util.enumerations.filtergroupby.mod": "التعديل", 38 | 39 | "betterstats.api.client.gui.stats.widget.mobstatwidget.kills": "القتلى", 40 | "betterstats.api.client.gui.stats.widget.mobstatwidget.deaths": "الوفيات", 41 | "betterstats.api.client.gui.stats.widget.playerbadgestatwidget.obtained": "مكتسب", 42 | "betterstats.api.client.gui.stats.widget.generalstatwidget.value": "القيمة", 43 | 44 | "betterstats.client.gui.stats.panel.statstabpanel.no_stats_yet": "لا توجد إحصائيات للعرض حتى الآن...", 45 | 46 | "betterstats.config.debug_mode": "وضع التصحيح", 47 | "betterstats.config.gui_mob_follow_cursor": "الغوغاء في واجهة المستخدم تتبع المؤشر", 48 | 49 | "betterstats.player_badge.dedication.title": "التفاني", 50 | "betterstats.player_badge.dedication.description": "لقد أظهرت تفانيا حقيقيا! لقد لعبت في هذا العالم لمدة 12 يومًا على الأقل (288 ساعة).", 51 | "betterstats.player_badge.loyalty.title": "الولاء", 52 | "betterstats.player_badge.loyalty.description": "ولاءك مثير للإعجاب! لقد لعبت في هذا العالم لمدة 24 يومًا على الأقل (576 ساعة).", 53 | "betterstats.player_badge.the_next_generation.title": "الجيل القادم", 54 | "betterstats.player_badge.the_next_generation.description": "لقد حملت رمز النصر النهائي - بيضة الدراجون.", 55 | "betterstats.player_badge.adventurous_traveler.title": "المسافر المغامر", 56 | "betterstats.player_badge.adventurous_traveler.description": "من خلال الجبال العالية والوديان الواطئة، لقد سافرت بعيدًا، مع الكثير لتظهره. تبحث عن منظر، حكاية لم تروى، مقياس الرحلة في كتل الذهب. وثم رأيته...\n\nلقد سافرت لمدة 727,000 كتلة على الأقل.", 57 | "betterstats.player_badge.perennial_survivor.title": "الناجي الدائم", 58 | "betterstats.player_badge.perennial_survivor.description": "تمكنت من خداع الموت، للآن. هل ستستمر في تجنب المحتوم؟\n\nلقد نجوت لمدة 12 ساعة على الأقل.", 59 | 60 | 61 | "betterstats.gui.hud_screen.betterstatshudscreen": "شاشة الإحصائيات", 62 | "betterstats.gui.hud_screen.betterstatshudscreen.pin_stat": "تعليق إحصائية على الشاشة", 63 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_1": "انقر بزر الماوس الأيمن على الإحصاء ([Shift]+RMB) لإضافته هنا.", 64 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_2": "انقر بزر الماوس الأيمن على إحصاء الشاشة لتعديله أو إزالته.", 65 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_3": "اضغط على Escape (ESC) لإغلاق هذه الشاشة.", 66 | 67 | "betterstats.network.betterstatsnetworkhandler.toggle_tooltip": "تبديل الاتصال بالخادم 'betterstats'", 68 | "betterstats.network.betterstatsnetworkhandler.consent_warning": "مهم:\nبتمكين هذه الميزة، سيرى الخادم أنك تستخدم عميلًا معدلًا بتثبيت 'betterstats'. لحماية خصوصيتك، لا تستخدم هذه الميزة على الخوادم التي قد لا تسمح بعملاء معدلين أو هذا التعديل.\n\nتمكين هذه الميزة يضيف بعض الميزات الإضافية التي متوفرة عند تثبيت التعديل على الخادم أيضًا." 69 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/betterstats/lang/de_de.json: -------------------------------------------------------------------------------- 1 | { 2 | "betterstats": "Bessere Statistiken", 3 | 4 | "betterstats.translators.title": "Übersetzer", 5 | 6 | 7 | "betterstats.stattype_phrase.minecraft.killed": "Getötet", 8 | "betterstats.stattype_phrase.minecraft.killed_by": "Gestorben durch", 9 | "betterstats.stattype_phrase.morestats.damaged": "Verursachter Schaden (x10)", 10 | "betterstats.stattype_phrase.morestats.damaged_by": "Erhaltener Schaden (x10)", 11 | "betterstats.stattype_phrase.morestats.totem_popped_by": "Ausgelöste Totems durch", 12 | 13 | 14 | "betterstats.gui.menu_bar.menu_file": "Datei", 15 | "betterstats.gui.menu_bar.menu_file.new": "Neu", 16 | "betterstats.gui.menu_bar.menu_file.open": "Öffnen", 17 | "betterstats.gui.menu_bar.menu_file.save": "Speichern", 18 | "betterstats.gui.menu_bar.menu_file.save_as": "Speichern als", 19 | "betterstats.gui.menu_bar.menu_view": "Ansicht", 20 | "betterstats.gui.menu_bar.menu_view.vanilla_stats": "Zeige Vanillastatistiken", 21 | "betterstats.gui.menu_bar.menu_about": "Über", 22 | "betterstats.gui.menu_bar.menu_about.source": "Quellcode", 23 | "betterstats.gui.menu_bar.menu_about.curseforge": "CurseForge", 24 | "betterstats.gui.menu_bar.menu_about.modrinth": "Modrinth", 25 | "betterstats.gui.menu_bar.menu_about.youtube": "YouTube", 26 | "betterstats.gui.menu_bar.menu_about.kofi": "Ko-Fi", 27 | "betterstats.gui.menu_bar.menu_about.discord": "Discord", 28 | 29 | "betterstats.api.client.gui.stats.panel.statfilterspanel.filters": "Filter", 30 | "betterstats.api.client.gui.stats.panel.statfilterspanel.show_empty_stats": "Zeige leere Statistiken", 31 | "betterstats.api.util.enumerations.filtergroupby.default": "Standard", 32 | "betterstats.api.util.enumerations.filtergroupby.mod": "Mod", 33 | 34 | "betterstats.api.client.gui.stats.widget.mobstatwidget.kills": "Tötungen", 35 | "betterstats.api.client.gui.stats.widget.mobstatwidget.deaths": "Tode", 36 | "betterstats.api.client.gui.stats.widget.playerbadgestatwidget.obtained": "Erhalten", 37 | "betterstats.api.client.gui.stats.widget.generalstatwidget.value": "Wert", 38 | 39 | "betterstats.client.gui.stats.panel.statstabpanel.no_stats_yet": "Derzeit gibt es noch keine Statistiken zum anzeigen...", 40 | 41 | "betterstats.config.debug_mode": "Debugmodus", 42 | "betterstats.config.gui_mob_follow_cursor": "GUI Mobs folgen Mauszeiger", 43 | 44 | "betterstats.player_badge.dedication.title": "Hingabe", 45 | "betterstats.player_badge.dedication.description": "Du hast wahre Hingabe gezeigt! Du hast auf dieser Welt mindestens 12 Tage (288 Stunden) gespielt.", 46 | "betterstats.player_badge.loyalty.title": "Loyalität", 47 | "betterstats.player_badge.loyalty.description": "Deine Loyalität ist lobenswert! Du hast mindestens 24 Tage (576 Stunden) auf dieser Welt gespielt.", 48 | "betterstats.player_badge.the_next_generation.title": "Die nächste Generation", 49 | "betterstats.player_badge.the_next_generation.description": "Du hast das Symbol des ultimativen Triumphs in der Hand gehalten - das Drachenei.", 50 | "betterstats.player_badge.adventurous_traveler.title": "Abenteuerlustiger Reisender", 51 | "betterstats.player_badge.adventurous_traveler.description": "Durch hohe Berge und tiefe Täler bist du weit gereist, und du hast viel zu zeigen. Auf der Suche nach einer Sehenswürdigkeit, einer unerzählten Geschichte, dem Maß einer Reise in Goldblöcken. Und dann hast du es gesehen...\n\nDu bist mindestens 727.000 Blöcke weit gereist.", 52 | "betterstats.player_badge.perennial_survivor.title": "Dauerhafter Überlebenskünstler", 53 | "betterstats.player_badge.perennial_survivor.description": "Du hast es geschafft, dem Tod zu entkommen - vorerst. Wirst du auch weiterhin dem Unvermeidlichen entgehen?\n\nDu hast mindestens 12 Stunden lang überlebt.", 54 | 55 | 56 | "betterstats.gui.hud_screen.betterstatshudscreen": "Statistik HUD", 57 | "betterstats.gui.hud_screen.betterstatshudscreen.pin_stat": "Anheften an HUD", 58 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_1": "Halte [Umsch] gedrückt und klicke mit der rechten Maustaste auf eine Statistik, um sie hier hinzuzufügen.", 59 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_2": "Klicke mit der rechten Maustaste auf einen HUD-Eintrag, um ihn zu ändern oder zu entfernen.", 60 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_3": "Drücke [Esc], um diesen Bildschirm zu schließen.", 61 | 62 | "betterstats.network.betterstatsnetworkhandler.toggle_tooltip": "Umschalten der 'betterstats'-Serververbindung", 63 | "betterstats.network.betterstatsnetworkhandler.consent_warning": "WICHTIG!\nWenn du diese Funktion aktivieren, erfährt der Server, dass du einen modifizierten Client mit installiertem 'betterstats' verwendest. Um deine Privatsphäre zu schützen, verwende diese Funktion nicht auf Servern, die modifizierte Clients oder diesen Mod nicht zulassen.\n\nDie Aktivierung dieser Funktion fügt einige zusätzliche Funktionen hinzu, die verfügbar sind, wenn die Mod auch auf dem Server installiert ist." 64 | } 65 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/betterstats/lang/tt_ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "betterstats": "Better Statistics экраны", 3 | "modmenu.summaryTranslation.betterstats": "Яхшыртылган һәм файдалырак статистика экраны.", 4 | "modmenu.descriptionTranslation.betterstats": "Статистика экранын яхшырта һәм аны файдалы итә.", 5 | 6 | "betterstats.translators.title": "Тәрҗемәчеләр", 7 | 8 | 9 | "commands.statistics.edit.output": "%2$s уенчы өчен %1$s статистикасы үзгәртелде.", 10 | "commands.statistics.clear.kick": "Сезнең статистика чистартылды, шуңа күрә Сезгә сүндерергә һәм яңадан кушылырга кирәк.", 11 | "commands.statistics.clear.output": "%s уенчы статистикасы чистарылды.", 12 | 13 | 14 | "betterstats.gui.menu_bar.menu_file": "Файл", 15 | "betterstats.gui.menu_bar.menu_file.new": "Яңа", 16 | "betterstats.gui.menu_bar.menu_file.open": "Ачу", 17 | "betterstats.gui.menu_bar.menu_file.save": "Саклау", 18 | "betterstats.gui.menu_bar.menu_file.save_as": "Саклау рәвеше", 19 | "betterstats.gui.menu_bar.menu_view": "Карау", 20 | "betterstats.gui.menu_bar.menu_view.vanilla_stats": "Ванила статистикасын күрсәтү", 21 | "betterstats.gui.menu_bar.menu_about": "Мод турында", 22 | "betterstats.gui.menu_bar.menu_about.source": "Чыганак", 23 | "betterstats.gui.menu_bar.menu_about.curseforge": "CurseForge", 24 | "betterstats.gui.menu_bar.menu_about.modrinth": "Modrinth", 25 | "betterstats.gui.menu_bar.menu_about.youtube": "YouTube", 26 | "betterstats.gui.menu_bar.menu_about.kofi": "Ko-Fi", 27 | "betterstats.gui.menu_bar.menu_about.discord": "Discord", 28 | 29 | "betterstats.api.client.gui.stats.panel.gameprofilepanel.uuid": "UUID", 30 | 31 | "betterstats.api.client.gui.stats.panel.statfilterspanel.filters": "Фильтрлар", 32 | "betterstats.api.client.gui.stats.panel.statfilterspanel.show_empty_stats": "Буш статистиканы күрсәтү", 33 | "betterstats.api.client.gui.stats.panel.statfilterspanel.no_filters_question": "Фильтрларсызмы?", 34 | 35 | 36 | "betterstats.api.util.enumerations.filtergroupby.default": "Беренчел", 37 | "betterstats.api.util.enumerations.filtergroupby.mod": "Мод буенча", 38 | 39 | "betterstats.api.client.gui.stats.widget.mobstatwidget.kills": "Үтерүләр саны", 40 | "betterstats.api.client.gui.stats.widget.mobstatwidget.deaths": "Үлемнәр саны", 41 | "betterstats.api.client.gui.stats.widget.playerbadgestatwidget.obtained": "Табылган", 42 | 43 | "betterstats.client.gui.stats.panel.statstabpanel.no_stats_yet": "Күрсәтү өчен статистика юк әле...", 44 | "betterstats.client.gui.stats.panel.statstabpanel.seed_sha256": "Генератор ачкычы (SHA-256)", 45 | 46 | "betterstats.config.debug_mode": "Төзәтү шарты", 47 | "betterstats.config.gui_mob_follow_cursor": "Интерфейстагы асыллар күрсәткечне күзәтә", 48 | 49 | "betterstats.player_badge.dedication.title": "Бирелгәнлек", 50 | "betterstats.player_badge.dedication.description": "Сез дөрес бирелгәнлекне күрсәттегез! Сез бу дөньяда кимендә 12 көн (288 сәгать) буена уйнадыгыз.", 51 | "betterstats.player_badge.loyalty.title": "Тугрылык", 52 | "betterstats.player_badge.loyalty.description": "Сезнең тугрылык мактаулы! Сез бу дөньяда кимендә 24 көн (576 сәгать) буена уйнадыгыз.", 53 | "betterstats.player_badge.the_next_generation.title": "Яңа буын", 54 | "betterstats.player_badge.the_next_generation.description": "Сез соңгы тантана символын тоттыгыз — аҗдаһа йомыркасы.", 55 | "betterstats.player_badge.adventurous_traveler.title": "Маҗаралы сәяхәтче", 56 | "betterstats.player_badge.adventurous_traveler.description": "Сез бик таулар һәм түбән үзәнлекләр аша озын сәяхәт итте, күрсәтү өчен күп әйберләрне белән. Күренешне, әйтелмәгән сөйләкне, алтын блокларда сәяхәтнең чарасын эзләү. Һәм Сез ул вакытта аны күрдегез...\n\nСез кимендә 727000 блок бардыгыз.", 57 | "betterstats.player_badge.perennial_survivor.title": "Күпьеллык яшәүче", 58 | "betterstats.player_badge.perennial_survivor.description": "Сез Әҗәлне алдадыгыз, бу юлы. Сез котылгысыздан качырга дәвам итәсезме?\n\nСез кимендә 12 сәгать буена яшәдегез.", 59 | 60 | 61 | "betterstats.gui.hud_screen.betterstatshudscreen": "Статистиканың HUD", 62 | "betterstats.gui.hud_screen.betterstatshudscreen.pin_stat": "HUD-та беркетү", 63 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_1": "Статистика монда өстәү өчен аңа уң төймәгә ([Shift] + уң төймә) басыгыз.", 64 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_2": "HUD элементын үзгәртү яки бетерү өчен уң төймәгә басыгыз.", 65 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_3": "Бу экранны ябу өчен Escape (ESC) басыгыз.", 66 | 67 | "betterstats.network.betterstatsnetworkhandler.toggle_tooltip": "«betterstats» сервер тоташуын күчерү", 68 | "betterstats.network.betterstatsnetworkhandler.consent_warning": "МӨҺИМ:\nБу көйләүне кабызып, сервер Сезнең «betterstats» утыртылган белән үзгәртелгән клиентны куллануыгызны ачыклаячак. Сезнең хосусыйлыгыгызны саклау өчен, үзгәртелгән клиентларны яки шушы модны рөхсәт итмәс серверларда бу көйләүне кулланмагыз.\n\nБу көйләүне кушу модны серверга шулай ук утыртылган кулланышлы кайбер артык функцияләрне өсти." 69 | } 70 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/betterstats/lang/pt_br.json: -------------------------------------------------------------------------------- 1 | { 2 | "betterstats": "Tela de Estatísticas Avançadas", 3 | 4 | "betterstats.translators.title": "Tradutores", 5 | 6 | 7 | "commands.statistics.edit.output": "Editada a estatística %s para %s jogador(es).", 8 | "commands.statistics.clear.kick": "Suas estatísticas foram limpas, o que exige que você se desconecte e reconecte.", 9 | "commands.statistics.clear.output": "Limpas as estatísticas de %s jogador(es).", 10 | 11 | 12 | "betterstats.stattype_phrase.minecraft.killed": "Eliminados", 13 | "betterstats.stattype_phrase.minecraft.killed_by": "Morto por", 14 | "betterstats.stattype_phrase.morestats.damaged": "Dano causado (x10)", 15 | "betterstats.stattype_phrase.morestats.damaged_by": "Dano recebido (x10)", 16 | "betterstats.stattype_phrase.morestats.totem_popped_by": "Totens estourados por", 17 | 18 | 19 | "betterstats.gui.menu_bar.menu_file": "Arquivo", 20 | "betterstats.gui.menu_bar.menu_file.new": "Novo", 21 | "betterstats.gui.menu_bar.menu_file.open": "Abrir", 22 | "betterstats.gui.menu_bar.menu_file.save": "Salvar", 23 | "betterstats.gui.menu_bar.menu_file.save_as": "Salvar como", 24 | "betterstats.gui.menu_bar.menu_view": "Visualizar", 25 | "betterstats.gui.menu_bar.menu_view.vanilla_stats": "Mostrar estatísticas padrão", 26 | "betterstats.gui.menu_bar.menu_about": "Sobre", 27 | "betterstats.gui.menu_bar.menu_about.source": "Código fonte", 28 | "betterstats.gui.menu_bar.menu_about.curseforge": "CurseForge", 29 | "betterstats.gui.menu_bar.menu_about.modrinth": "Modrinth", 30 | "betterstats.gui.menu_bar.menu_about.youtube": "YouTube", 31 | "betterstats.gui.menu_bar.menu_about.kofi": "Ko-Fi", 32 | "betterstats.gui.menu_bar.menu_about.discord": "Discord", 33 | 34 | "betterstats.api.client.gui.stats.panel.statfilterspanel.filters": "Filtros", 35 | "betterstats.api.client.gui.stats.panel.statfilterspanel.show_empty_stats": "Mostrar estatísticas vazias", 36 | "betterstats.api.util.enumerations.filtergroupby.default": "Padrão", 37 | "betterstats.api.util.enumerations.filtergroupby.mod": "Mod", 38 | 39 | "betterstats.api.client.gui.stats.widget.mobstatwidget.kills": "Matou", 40 | "betterstats.api.client.gui.stats.widget.mobstatwidget.deaths": "Mortes", 41 | "betterstats.api.client.gui.stats.widget.playerbadgestatwidget.obtained": "Conseguido", 42 | "betterstats.api.client.gui.stats.widget.generalstatwidget.value": "Valor", 43 | 44 | "betterstats.client.gui.stats.panel.statstabpanel.no_stats_yet": "Ainda não há estatísticas para mostrar...", 45 | 46 | "betterstats.config.debug_mode": "Modo de depuração", 47 | "betterstats.config.gui_mob_follow_cursor": "Mobs da GUI seguem o cursor", 48 | 49 | "betterstats.player_badge.dedication.title": "Dedicação", 50 | "betterstats.player_badge.dedication.description": "Você demonstrou verdadeira dedicação! Você jogou neste mundo por pelo menos 12 dias (288 horas).", 51 | "betterstats.player_badge.loyalty.title": "Lealdade", 52 | "betterstats.player_badge.loyalty.description": "Sua lealdade é louvável! Você jogou neste mundo por pelo menos 24 dias (576 horas).", 53 | "betterstats.player_badge.the_next_generation.title": "A Próxima Geração", 54 | "betterstats.player_badge.the_next_generation.description": "Você segurou o símbolo do triunfo final - o Ovo do Dragão.", 55 | "betterstats.player_badge.adventurous_traveler.title": "Viajante Aventureiro", 56 | "betterstats.player_badge.adventurous_traveler.description": "Através de montanhas altas e vales baixos, você viajou longe, com muito para mostrar. Buscando um ponto de vista, uma história não contada, a medida de uma jornada em blocos de ouro. E então você viu...\n\nVocê viajou pelo menos 727.000 blocos.", 57 | "betterstats.player_badge.perennial_survivor.title": "Sobrevivente Perene", 58 | "betterstats.player_badge.perennial_survivor.description": "Você conseguiu enganar a morte, por enquanto. Você continuará a evitar o inevitável?\n\nVocê sobreviveu por pelo menos 12 horas.", 59 | 60 | 61 | "betterstats.gui.hud_screen.betterstatshudscreen": "HUD de estatísticas", 62 | "betterstats.gui.hud_screen.betterstatshudscreen.pin_stat": "Fixar no HUD", 63 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_1": "Clique com o botão direito em uma estatística ([Shift]+RMB) para adicioná-la aqui.", 64 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_2": "Clique com o botão direito em uma entrada do HUD para modificar ou removê-la.", 65 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_3": "Pressione Escape (ESC) para fechar esta tela.", 66 | 67 | "betterstats.network.betterstatsnetworkhandler.toggle_tooltip": "Alternar conexão do servidor 'betterstats'", 68 | "betterstats.network.betterstatsnetworkhandler.consent_warning": "IMPORTANTE:\nAo habilitar este recurso, o servidor descobrirá que você está usando um cliente modificado com 'betterstats' instalado. Para proteger sua privacidade, não use este recurso em servidores que possam não permitir clientes modificados ou este mod.\n\nHabilitar este recurso adiciona alguns recursos extras que estão disponíveis quando o mod também é instalado no servidor." 69 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/betterstats/lang/it_it.json: -------------------------------------------------------------------------------- 1 | { 2 | "betterstats": "Schermata Migliore Delle Statistiche", 3 | 4 | "betterstats.translators.title": "Traduttori", 5 | 6 | "commands.statistics.edit.output": "Modificata la statistica %s per %s giocatore(i).", 7 | "commands.statistics.clear.kick": "Le tue statistiche sono state cancellate, il che richiede che tu ti disconnetta e rientri.", 8 | "commands.statistics.clear.output": "Cancellate le statistiche di %s giocatore(i).", 9 | 10 | 11 | "betterstats.stattype_phrase.minecraft.killed": "Uccisi", 12 | "betterstats.stattype_phrase.minecraft.killed_by": "Morto per", 13 | "betterstats.stattype_phrase.morestats.damaged": "Danno inflitto (x10)", 14 | "betterstats.stattype_phrase.morestats.damaged_by": "Danno subito (x10)", 15 | "betterstats.stattype_phrase.morestats.totem_popped_by": "Totem attivati da", 16 | 17 | 18 | "betterstats.gui.menu_bar.menu_file": "File", 19 | "betterstats.gui.menu_bar.menu_file.new": "Nuovo", 20 | "betterstats.gui.menu_bar.menu_file.open": "Apri", 21 | "betterstats.gui.menu_bar.menu_file.save": "Salva", 22 | "betterstats.gui.menu_bar.menu_file.save_as": "Salva come", 23 | "betterstats.gui.menu_bar.menu_view": "Visualizza", 24 | "betterstats.gui.menu_bar.menu_view.vanilla_stats": "Visualizza statistiche vanilla", 25 | "betterstats.gui.menu_bar.menu_about": "Informazioni", 26 | "betterstats.gui.menu_bar.menu_about.source": "Codice sorgente", 27 | "betterstats.gui.menu_bar.menu_about.curseforge": "CurseForge", 28 | "betterstats.gui.menu_bar.menu_about.modrinth": "Modrinth", 29 | "betterstats.gui.menu_bar.menu_about.youtube": "YouTube", 30 | "betterstats.gui.menu_bar.menu_about.kofi": "Ko-Fi", 31 | "betterstats.gui.menu_bar.menu_about.discord": "Discord", 32 | 33 | "betterstats.api.client.gui.stats.panel.statfilterspanel.filters": "Filtri", 34 | "betterstats.api.client.gui.stats.panel.statfilterspanel.show_empty_stats": "Mostra statistiche vuote", 35 | "betterstats.api.util.enumerations.filtergroupby.default": "Predefinito", 36 | "betterstats.api.util.enumerations.filtergroupby.mod": "Mod", 37 | 38 | "betterstats.api.client.gui.stats.widget.mobstatwidget.kills": "Uccisioni", 39 | "betterstats.api.client.gui.stats.widget.mobstatwidget.deaths": "Morti", 40 | "betterstats.api.client.gui.stats.widget.playerbadgestatwidget.obtained": "Ottenuto", 41 | "betterstats.api.client.gui.stats.widget.generalstatwidget.value": "Valore", 42 | 43 | "betterstats.client.gui.stats.panel.statstabpanel.no_stats_yet": "Non ci sono ancora statistiche da mostrare...", 44 | 45 | "betterstats.config.debug_mode": "Modalità debug", 46 | "betterstats.config.gui_mob_follow_cursor": "I mob della GUI seguono il cursore", 47 | 48 | "betterstats.player_badge.dedication.title": "Dedizione", 49 | "betterstats.player_badge.dedication.description": "Hai dimostrato vera dedizione! Hai giocato in questo mondo per almeno 12 giorni (288 ore).", 50 | "betterstats.player_badge.loyalty.title": "Lealtà", 51 | "betterstats.player_badge.loyalty.description": "La tua lealtà è lodevole! Hai giocato in questo mondo per almeno 24 giorni (576 ore).", 52 | "betterstats.player_badge.the_next_generation.title": "La Prossima Generazione", 53 | "betterstats.player_badge.the_next_generation.description": "Hai tenuto il simbolo del trionfo finale - l'uovo del drago.", 54 | "betterstats.player_badge.adventurous_traveler.title": "Viaggiatore Avventuroso", 55 | "betterstats.player_badge.adventurous_traveler.description": "Tra montagne alte e valli basse, hai viaggiato molto, con molto da mostrare. In cerca di una vista, una storia non raccontata, la misura di un viaggio in blocchi d'oro. E poi l'hai vista...\n\nHai viaggiato per almeno 727.000 blocchi.", 56 | "betterstats.player_badge.perennial_survivor.title": "Sopravvissuto Perenne", 57 | "betterstats.player_badge.perennial_survivor.description": "Sei riuscito a ingannare la morte, per ora. Continuerai a evitare l'inevitabile?\n\nSei sopravvissuto per almeno 12 ore.", 58 | 59 | 60 | "betterstats.gui.hud_screen.betterstatshudscreen": "Hud delle statistiche", 61 | "betterstats.gui.hud_screen.betterstatshudscreen.pin_stat": "Fissa a HUD", 62 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_1": "Fai clic con il tasto destro su una statistica ([Shift]+RMB) per aggiungerla qui.", 63 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_2": "Fai clic con il tasto destro su una voce HUD per modificarla o rimuoverla.", 64 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_3": "Premi Escape (ESC) per chiudere questa schermata.", 65 | 66 | "betterstats.network.betterstatsnetworkhandler.toggle_tooltip": "Alterna connessione server 'betterstats'", 67 | "betterstats.network.betterstatsnetworkhandler.consent_warning": "IMPORTANTE:\nAbilitando questa funzione, il server scoprirà che stai utilizzando un client modificato con 'betterstats' installato. Per proteggere la tua privacy, non utilizzare questa funzione sui server che potrebbero non consentire client modificati o questo mod.\n\nAbilitare questa funzione aggiunge alcune funzioni extra che sono disponibili quando il mod è installato anche sul server." 68 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/betterstats/lang/es_es.json: -------------------------------------------------------------------------------- 1 | { 2 | "betterstats": "Mejora de la pantalla de estadísticas", 3 | 4 | "betterstats.translators.title": "Traductores", 5 | "betterstats.contributors.title": "Colaboradores", 6 | 7 | 8 | "commands.statistics.edit.output": "Se editó la estadística %s para jugador(es) %s.", 9 | "commands.statistics.clear.kick": "Se borraron tus estadísticas, lo que requiere que te desconectes y vuelvas a unirte.", 10 | "commands.statistics.clear.output": "Se borraron las estadísticas de %s jugador(es).", 11 | 12 | 13 | "betterstats.stattype_phrase.minecraft.killed": "Asesinados", 14 | "betterstats.stattype_phrase.minecraft.killed_by": "Muerto por", 15 | "betterstats.stattype_phrase.morestats.damaged": "Daño infligido (x10)", 16 | "betterstats.stattype_phrase.morestats.damaged_by": "Daño recibido (x10)", 17 | "betterstats.stattype_phrase.morestats.totem_popped_by": "Tótems activados por", 18 | 19 | 20 | "betterstats.gui.menu_bar.menu_file": "Archivo", 21 | "betterstats.gui.menu_bar.menu_file.new": "Nuevo", 22 | "betterstats.gui.menu_bar.menu_file.open": "Abrir", 23 | "betterstats.gui.menu_bar.menu_file.save": "Guardar", 24 | "betterstats.gui.menu_bar.menu_file.save_as": "Guardar como", 25 | "betterstats.gui.menu_bar.menu_view": "Ver", 26 | "betterstats.gui.menu_bar.menu_view.vanilla_stats": "Mostrar estadísticas vanilla", 27 | "betterstats.gui.menu_bar.menu_about": "Acerca de", 28 | "betterstats.gui.menu_bar.menu_about.source": "Código fuente", 29 | "betterstats.gui.menu_bar.menu_about.curseforge": "CurseForge", 30 | "betterstats.gui.menu_bar.menu_about.modrinth": "Modrinth", 31 | "betterstats.gui.menu_bar.menu_about.youtube": "YouTube", 32 | "betterstats.gui.menu_bar.menu_about.kofi": "Ko-Fi", 33 | "betterstats.gui.menu_bar.menu_about.discord": "Discord", 34 | 35 | "betterstats.api.client.gui.stats.panel.statfilterspanel.filters": "Filtros", 36 | "betterstats.api.client.gui.stats.panel.statfilterspanel.show_empty_stats": "Mostrar estadísticas vacías", 37 | "betterstats.api.util.enumerations.filtergroupby.default": "Predeterminado", 38 | "betterstats.api.util.enumerations.filtergroupby.mod": "Mod", 39 | 40 | "betterstats.api.client.gui.stats.widget.mobstatwidget.kills": "Muertes", 41 | "betterstats.api.client.gui.stats.widget.mobstatwidget.deaths": "Muertes", 42 | "betterstats.api.client.gui.stats.widget.playerbadgestatwidget.obtained": "Obtenido", 43 | "betterstats.api.client.gui.stats.widget.generalstatwidget.value": "Valor", 44 | 45 | "betterstats.client.gui.stats.panel.statstabpanel.no_stats_yet": "Aún no hay estadísticas para mostrar...", 46 | 47 | "betterstats.config.debug_mode": "Modo de depuración", 48 | "betterstats.config.gui_mob_follow_cursor": "Mobs del GUI siguen al cursor", 49 | 50 | "betterstats.player_badge.dedication.title": "Dedicación", 51 | "betterstats.player_badge.dedication.description": "¡Has mostrado verdadera dedicación! Has jugado en este mundo durante al menos 12 días (288 horas).", 52 | "betterstats.player_badge.loyalty.title": "Lealtad", 53 | "betterstats.player_badge.loyalty.description": "Tu lealtad es encomiable. Has jugado en este mundo durante al menos 24 días (576 horas).", 54 | "betterstats.player_badge.the_next_generation.title": "La próxima generación", 55 | "betterstats.player_badge.the_next_generation.description": "Has sostenido el símbolo del triunfo supremo: el Huevo de Dragón.", 56 | "betterstats.player_badge.adventurous_traveler.title": "Viajero aventurero", 57 | "betterstats.player_badge.adventurous_traveler.description": "A través de montañas altas y valles bajos, has viajado lejos, con mucho que mostrar. Buscando una vista, una historia sin contar, la medida de un viaje en bloques de oro. Y entonces lo viste...\n\nHas viajado al menos 727.000 bloques.", 58 | "betterstats.player_badge.perennial_survivor.title": "Superviviente perenne", 59 | "betterstats.player_badge.perennial_survivor.description": "Has logrado engañar a la muerte, por ahora. ¿Seguirás evitando lo inevitable?\n\nHas sobrevivido al menos 12 horas.", 60 | 61 | "betterstats.gui.hud_screen.betterstatshudscreen": "Estadísticas en la pantalla", 62 | "betterstats.gui.hud_screen.betterstatshudscreen.pin_stat": "Anclar a la pantalla", 63 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_1": "Haz clic derecho en una estadística ([Shift]+RMB) para agregarla aquí.", 64 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_2": "Haz clic derecho en una entrada de la pantalla para modificarla o eliminarla.", 65 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_3": "Presiona Esc (ESC) para cerrar esta pantalla.", 66 | 67 | "betterstats.network.betterstatsnetworkhandler.toggle_tooltip": "Alternar conexión al servidor 'betterstats'", 68 | "betterstats.network.betterstatsnetworkhandler.consent_warning": "IMPORTANTE:\nAl habilitar esta función, el servidor averiguará que estás utilizando un cliente modificado con 'betterstats' instalado. Para proteger tu privacidad, no utilices esta función en servidores que no permitan clientes modificados o este mod.\n\nHabilitar esta característica agrega algunas funciones adicionales que están disponibles cuando el mod está instalado en el servidor también." 69 | } -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/api/util/stats/SUGeneralStat.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.api.util.stats; 2 | 3 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.fLiteral; 4 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.fTranslatable; 5 | import static io.github.thecsdev.tcdcommons.api.util.TextUtils.translatable; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Comparator; 9 | import java.util.List; 10 | import java.util.Objects; 11 | import java.util.Optional; 12 | import java.util.function.Predicate; 13 | import net.minecraft.core.registries.BuiltInRegistries; 14 | import net.minecraft.network.chat.Component; 15 | import net.minecraft.resources.ResourceLocation; 16 | import net.minecraft.stats.Stat; 17 | import net.minecraft.stats.Stats; 18 | import org.jetbrains.annotations.ApiStatus.Internal; 19 | import org.jetbrains.annotations.Nullable; 20 | 21 | import io.github.thecsdev.betterstats.api.util.io.IStatsProvider; 22 | import io.github.thecsdev.betterstats.util.BST; 23 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 24 | 25 | public final class SUGeneralStat extends SUStat 26 | { 27 | // ================================================== 28 | public static final Component TEXT_VALUE = BST.sWidget_general_value(); 29 | // -------------------------------------------------- 30 | private final Stat stat; 31 | private final boolean isEmpty; //cached value to avoid re-calculations 32 | 33 | /** 34 | * The raw {@link Integer} value of this {@link Stat}. 35 | */ 36 | public final int value; 37 | 38 | /** 39 | * The formatted {@link Component}ual user-friendly version of this {@link Stat}'s {@link #value}. 40 | * @see Stat#format(int) 41 | */ 42 | public final Component valueText; 43 | // ================================================== 44 | public SUGeneralStat(IStatsProvider statsProvider, Stat stat) 45 | { 46 | super(statsProvider, id(stat), getGeneralStatText(stat)); 47 | this.stat = Objects.requireNonNull(stat); 48 | this.value = statsProvider.getStatValue(stat); 49 | this.valueText = fLiteral(stat.format(this.value)); 50 | this.isEmpty = this.value == 0; 51 | } 52 | // -------------------------------------------------- 53 | private static final @Internal ResourceLocation id(Stat stat) 54 | { 55 | //not the intended way of dealing with this, but has to be done to deal with incompatibilities, 56 | //and by incompatibilities i mean mod devs. not registering their stats because... idk either 57 | return Optional.ofNullable(BuiltInRegistries.CUSTOM_STAT.getKey(stat.getValue())).orElse(ID_NULL); 58 | } 59 | // ================================================== 60 | /** 61 | * Returns the "general" {@link Stat} that corresponds with this {@link SUGeneralStat}. 62 | */ 63 | public final Stat getGeneralStat() { return this.stat; } 64 | // -------------------------------------------------- 65 | public final @Override boolean isEmpty() { return this.isEmpty; } 66 | // ================================================== 67 | /** 68 | * Returns the translation key for a given {@link Stat}. 69 | * @param stat The statistic in question. 70 | */ 71 | public static String getGeneralStatTranslationKey(Stat stat) 72 | { 73 | //note: throw NullPointerException if stat is null 74 | return "stat." + stat.getValue().toString().replace(':', '.'); 75 | } 76 | // -------------------------------------------------- 77 | /** 78 | * Returns the {@link Component} that should correspond to a 79 | * given general {@link SUGeneralStat}, using {@link #getGeneralStatTranslationKey(Stat)}. 80 | */ 81 | public static Component getGeneralStatText(Stat stat) { return fTranslatable(getGeneralStatTranslationKey(stat)); } 82 | // ================================================== 83 | /** 84 | * Obtains a list of all "general" {@link Stat}s in form of {@link SUGeneralStat}. 85 | * @param statsProvider The {@link IStatsProvider}. 86 | * @param filter Optional. A {@link Predicate} used to filter out any unwanted {@link SUGeneralStat}s. 87 | */ 88 | public static List getGeneralStats 89 | (IStatsProvider statsProvider, @Nullable Predicate filter) 90 | { 91 | //null checks 92 | Objects.requireNonNull(statsProvider); 93 | 94 | //create an array list 95 | final var result = new ArrayList(); 96 | 97 | //obtain stats 98 | final var statsList = new ObjectArrayList>(Stats.CUSTOM.iterator()); 99 | statsList.sort(Comparator.comparing(stat -> translatable(getGeneralStatTranslationKey(stat)).getString())); 100 | 101 | for(final var stat : statsList) result.add(new SUGeneralStat(statsProvider, stat)); 102 | //filter out stats with NULL IDs and stats the filter filters out 103 | result.removeIf(stat -> Objects.equals(ID_NULL, stat.getStatID()) || (filter != null && !filter.test(stat))); 104 | 105 | //return the result list 106 | return result; 107 | } 108 | // ================================================== 109 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/betterstats/lang/fr_fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "betterstats": "Meilleur Écran de Statistiques", 3 | 4 | "betterstats.translators.title": "Traducteurs", 5 | 6 | 7 | "commands.statistics.edit.output": "La stat %s a été modifiée pour %s joueur(s).", 8 | "commands.statistics.clear.kick": "Vos statistiques ont été effacées, ce qui nécessite que vous vous déconnectiez et que vous vous reconnectiez.", 9 | "commands.statistics.clear.output": "Statistiques de %s joueur(s) effacées.", 10 | 11 | "betterstats.stattype_phrase.minecraft.killed": "Tués", 12 | "betterstats.stattype_phrase.minecraft.killed_by": "Mort à cause de", 13 | "betterstats.stattype_phrase.morestats.damaged": "Dégâts infligés (x10)", 14 | "betterstats.stattype_phrase.morestats.damaged_by": "Dégâts subis (x10)", 15 | "betterstats.stattype_phrase.morestats.totem_popped_by": "Totems déclenchés par", 16 | 17 | 18 | "betterstats.gui.menu_bar.menu_file": "Fichier", 19 | "betterstats.gui.menu_bar.menu_file.new": "Nouveau", 20 | "betterstats.gui.menu_bar.menu_file.open": "Ouvrir", 21 | "betterstats.gui.menu_bar.menu_file.save": "Enregistrer", 22 | "betterstats.gui.menu_bar.menu_file.save_as": "Enregistrer sous", 23 | "betterstats.gui.menu_bar.menu_view": "Affichage", 24 | "betterstats.gui.menu_bar.menu_view.vanilla_stats": "Afficher les statistiques vanille", 25 | "betterstats.gui.menu_bar.menu_about": "À propos", 26 | "betterstats.gui.menu_bar.menu_about.source": "Code source", 27 | "betterstats.gui.menu_bar.menu_about.curseforge": "CurseForge", 28 | "betterstats.gui.menu_bar.menu_about.modrinth": "Modrinth", 29 | "betterstats.gui.menu_bar.menu_about.youtube": "YouTube", 30 | "betterstats.gui.menu_bar.menu_about.kofi": "Ko-Fi", 31 | "betterstats.gui.menu_bar.menu_about.discord": "Discord", 32 | 33 | "betterstats.api.client.gui.stats.panel.statfilterspanel.filters": "Filtres", 34 | "betterstats.api.client.gui.stats.panel.statfilterspanel.show_empty_stats": "Afficher les statistiques vides", 35 | "betterstats.api.util.enumerations.filtergroupby.default": "Par défaut", 36 | "betterstats.api.util.enumerations.filtergroupby.mod": "Mod", 37 | 38 | "betterstats.api.client.gui.stats.widget.mobstatwidget.kills": "Tués", 39 | "betterstats.api.client.gui.stats.widget.mobstatwidget.deaths": "Morts", 40 | "betterstats.api.client.gui.stats.widget.playerbadgestatwidget.obtained": "Obtenu", 41 | "betterstats.api.client.gui.stats.widget.generalstatwidget.value": "Valeur", 42 | 43 | "betterstats.client.gui.stats.panel.statstabpanel.no_stats_yet": "Il n'y a pas encore de statistiques à afficher...", 44 | 45 | "betterstats.config.debug_mode": "Mode débogage", 46 | "betterstats.config.gui_mob_follow_cursor": "Les GUI des mobs suivent le curseur", 47 | 48 | "betterstats.player_badge.dedication.title": "Dévouement", 49 | "betterstats.player_badge.dedication.description": "Vous avez fait preuve de vrai dévouement ! Vous avez joué dans ce monde pendant au moins 12 jours (288 heures).", 50 | "betterstats.player_badge.loyalty.title": "Loyauté", 51 | "betterstats.player_badge.loyalty.description": "Votre loyauté est louable ! Vous avez joué dans ce monde pendant au moins 24 jours (576 heures).", 52 | "betterstats.player_badge.the_next_generation.title": "La Prochaine Génération", 53 | "betterstats.player_badge.the_next_generation.description": "Vous avez tenu le symbole du triomphe ultime - l'Oeuf de Dragon.", 54 | "betterstats.player_badge.adventurous_traveler.title": "Voyageur Aventureux", 55 | "betterstats.player_badge.adventurous_traveler.description": "À travers les montagnes hautes et les vallées basses, vous avez voyagé loin, avec beaucoup à montrer. Cherchant une vue, une histoire inédite, la mesure d'un voyage en blocs d'or. Et puis vous l'avez vu...\n\nVous avez voyagé pour au moins 727,000 blocs.", 56 | "betterstats.player_badge.perennial_survivor.title": "Survivant pérenne", 57 | "betterstats.player_badge.perennial_survivor.description": "Vous avez réussi à tromper la mort, pour l'instant. Allez-vous continuer à échapper à l'inévitable ?\n\nVous avez survécu pendant au moins 12 heures.", 58 | 59 | "betterstats.gui.hud_screen.betterstatshudscreen": "HUD de statistiques", 60 | "betterstats.gui.hud_screen.betterstatshudscreen.pin_stat": "Épingler à l'HUD", 61 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_1": "Cliquer droit sur une statistique ([Shift]+RMB) pour l'ajouter ici.", 62 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_2": "Cliquer droit sur une entrée de l'HUD pour la modifier ou la supprimer.", 63 | "betterstats.gui.hud_screen.betterstatshudscreen.tutorial_3": "Appuyez sur Échap (ESC) pour fermer cet écran.", 64 | 65 | "betterstats.network.betterstatsnetworkhandler.toggle_tooltip": "Basculer la connexion du serveur 'betterstats'", 66 | "betterstats.network.betterstatsnetworkhandler.consent_warning": "IMPORTANT:\nEn activant cette fonction, le serveur saura que vous utilisez un client modifié avec 'betterstats' installé. Pour protéger votre vie privée, n'utilisez pas cette fonction sur des serveurs qui peuvent ne pas autoriser les clients modifiés ou ce mod.\n\nL'activation de cette fonction ajoute quelques fonctionnalités supplémentaires qui sont disponibles quand le mod est installé sur le serveur aussi." 67 | } 68 | -------------------------------------------------------------------------------- /common/src/main/java/io/github/thecsdev/betterstats/util/io/BetterStatsWebApiUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.thecsdev.betterstats.util.io; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonObject; 5 | import io.github.thecsdev.betterstats.BetterStats; 6 | import io.github.thecsdev.betterstats.BetterStatsConfig; 7 | import io.github.thecsdev.tcdcommons.api.util.io.cache.CachedResource; 8 | import io.github.thecsdev.tcdcommons.api.util.io.cache.CachedResourceManager; 9 | import io.github.thecsdev.tcdcommons.api.util.io.cache.IResourceFetchTask; 10 | import net.minecraft.resources.ResourceLocation; 11 | import net.minecraft.util.thread.BlockableEventLoop; 12 | import org.apache.commons.io.IOUtils; 13 | import org.apache.http.HttpException; 14 | import org.apache.http.client.HttpResponseException; 15 | import org.apache.http.util.EntityUtils; 16 | import org.jetbrains.annotations.ApiStatus.Internal; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | import java.time.Duration; 20 | import java.time.Instant; 21 | import java.util.function.Consumer; 22 | 23 | import static io.github.thecsdev.betterstats.BetterStats.getModID; 24 | import static io.github.thecsdev.betterstats.BetterStatsProperties.URL_REMOTE_APIS; 25 | import static io.github.thecsdev.betterstats.client.BetterStatsClient.MC_CLIENT; 26 | import static io.github.thecsdev.tcdcommons.api.util.io.HttpUtils.fetchSync; 27 | 28 | /** 29 | * {@link Internal} utilities for {@link BetterStats}'s HTTP APIs. 30 | */ 31 | @Internal 32 | public final class BetterStatsWebApiUtils 33 | { 34 | // ================================================== 35 | public static final @Internal Gson GSON = new Gson(); 36 | private static final JsonObject DEBUG_API_LINK_OVERRIDES = new JsonObject(); 37 | // ================================================== 38 | private BetterStatsWebApiUtils() {} 39 | static 40 | { 41 | //Note: Feel free to remove this code on non-Fabric platforms. 42 | // It's just for testing purposes anyways. 43 | if(BetterStatsConfig.DEV_ENV) 44 | { 45 | DEBUG_API_LINK_OVERRIDES.addProperty("quickshare_gdu", "http://localhost:8080/api-s/betterstats/quick-share/generate-download-url/"); 46 | DEBUG_API_LINK_OVERRIDES.addProperty("quickshare_guu", "http://localhost:8080/api-s/betterstats/quick-share/generate-upload-url/"); 47 | } 48 | } 49 | // ================================================== 50 | /** 51 | * Asynchronously fetches {@link BetterStats}'s API URLs. 52 | */ 53 | public static final void fetchBssApiLinksAsync( 54 | BlockableEventLoop minecraftClientOrServer, 55 | Consumer onReady, 56 | Consumer onError) throws NullPointerException 57 | { 58 | fetchBssApiLinks(true, minecraftClientOrServer, onReady, onError); 59 | } 60 | 61 | /** 62 | * Fetches {@link BetterStats}'s API URLs. 63 | */ 64 | public static final void fetchBssApiLinks( 65 | boolean isAsync, 66 | BlockableEventLoop minecraftClientOrServer, 67 | Consumer onReady, 68 | Consumer onError) throws NullPointerException 69 | { 70 | //prepare to fetch 71 | final var crId = ResourceLocation.fromNamespaceAndPath(getModID(), "links.json"); 72 | final var irft = new IResourceFetchTask() 73 | { 74 | public final @Override BlockableEventLoop getMinecraftClientOrServer() { return MC_CLIENT; } 75 | public final @Override Class getResourceType() { return JsonObject.class; } 76 | public final @Override CachedResource fetchResourceSync() throws Exception 77 | { 78 | //perform the http request 79 | final var httpResult = fetchSync(URL_REMOTE_APIS); 80 | @Nullable String httpResultStr = null; 81 | try 82 | { 83 | //throw an exception if the server does not respond with status 200 84 | final int statusCode = httpResult.getStatusLine().getStatusCode(); 85 | if(statusCode != 200) 86 | throw new HttpResponseException(statusCode, httpResult.getStatusLine().getReasonPhrase()); 87 | 88 | //read the response body as string 89 | final @Nullable var httpResultEntity = httpResult.getEntity(); 90 | if(httpResultEntity == null) throw new HttpException("Missing HTTP response body."); 91 | httpResultStr = EntityUtils.toString(httpResultEntity); 92 | } 93 | finally { IOUtils.closeQuietly(httpResult); } 94 | 95 | //return the result 96 | return new CachedResource( 97 | GSON.fromJson(httpResultStr, JsonObject.class), 98 | httpResultStr.length(), 99 | Instant.now().plus(Duration.ofMinutes(42))); 100 | } 101 | public final @Override void onReady(JsonObject result) 102 | { 103 | if(BetterStatsConfig.DEBUG_MODE) 104 | result.asMap().putAll(DEBUG_API_LINK_OVERRIDES.asMap()); 105 | onReady.accept(result); 106 | } 107 | public final @Override void onError(Exception error) { onError.accept(error); } 108 | }; 109 | 110 | //fetch 111 | if(isAsync) CachedResourceManager.getResourceAsync(crId, irft); 112 | else CachedResourceManager.getResourceSync(crId, irft); 113 | } 114 | // ================================================== 115 | } --------------------------------------------------------------------------------