first test

This commit is contained in:
2026-01-08 23:52:08 +01:00
parent 45e0df8b9e
commit 4fb4b669bc
41 changed files with 1535 additions and 139 deletions

View File

@@ -1 +1,11 @@
test
# Icons Enhanced
Icons Enhanced mejora la lectura de encantamientos y la UI sin cambiar el gameplay.
## Caracteristicas
- Iconos y colores por encantamiento en los tooltips.
- Descripciones de encantamientos.
- Bordes en slots de inventario y contenedores.
## Licencia
Apache-2.0

View File

@@ -5,16 +5,21 @@ org.gradle.parallel=true
# IntelliJ IDEA is not yet fully compatible with configuration cache, see: https://github.com/FabricMC/fabric-loom/issues/1349
org.gradle.configuration-cache=false
# Fabric Properties
# check these on https://fabricmc.net/develop
minecraft_version=1.21.11
loader_version=0.18.4
# Fabric tooling
loom_version=1.14-SNAPSHOT
# Mod Properties
mod_version=1.0.0
maven_group=com.straice
archives_base_name=template-mod
# Mod metadata
mod.id=iconsenhanced
mod.name=Icons Enhanced
mod.version=0.1.0
mod.group=dev.dekin
mod.description=Client-only suite of enchantment and UI improvements.
mod.license=Apache-2.0
mod.author=DekinDev
# Dependencies
fabric_api_version=0.141.1+1.21.11
# Dependencies (overridden per version in versions/*/gradle.properties)
deps.fabric_loader=0.18.4
deps.fabric_api=0.141.1+1.21.11
# Defaults (overridden per version as needed)
java_version=21

View File

@@ -1,10 +1,33 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven {
name = 'KikuGie Releases'
url = 'https://maven.kikugie.dev/releases'
}
maven {
name = 'KikuGie Snapshots'
url = 'https://maven.kikugie.dev/snapshots'
}
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
mavenCentral()
gradlePluginPortal()
}
}
}
plugins {
id 'dev.kikugie.stonecutter' version '0.8.2'
}
stonecutter {
// Stonecutter uses Kotlin controller by default; keep Groovy for consistency.
kotlinController = false
centralScript = 'build.gradle'
create(rootProject) {
versions '1.20.1', '1.20.4', '1.20.6', '1.21.1', '1.21.4', '1.21.11'
vcsVersion = '1.21.11'
}
}

View File

@@ -1,10 +0,0 @@
package com.straice;
import net.fabricmc.api.ClientModInitializer;
public class TemplateModClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
// This entrypoint is suitable for setting up client-specific logic, such as rendering.
}
}

View File

@@ -1,15 +0,0 @@
package com.straice.mixin.client;
import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Minecraft.class)
public class ExampleClientMixin {
@Inject(at = @At("HEAD"), method = "run")
private void init(CallbackInfo info) {
// This code is injected into the start of Minecraft.run()V
}
}

View File

@@ -0,0 +1,59 @@
package dev.dekin.iconsenhanced.client;
import dev.dekin.iconsenhanced.common.BorderColorLogic;
import dev.dekin.iconsenhanced.common.ConfigManager;
import dev.dekin.iconsenhanced.common.IconsEnhancedConfig;
import dev.dekin.iconsenhanced.mixin.AbstractContainerScreenAccessor;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
public final class BorderRenderer {
private static final BorderColorLogic COLOR_LOGIC = new BorderColorLogic();
private BorderRenderer() {
}
public static void applyConfig() {
COLOR_LOGIC.applyConfig(ConfigManager.get());
}
public static void render(Screen screen, GuiGraphics graphics, int mouseX, int mouseY) {
IconsEnhancedConfig config = ConfigManager.get();
if (!config.borders.enabled) {
return;
}
if (!(screen instanceof AbstractContainerScreen<?> handled)) {
return;
}
int thickness = clamp(config.borders.thickness, 1, 3);
AbstractContainerScreenAccessor accessor = (AbstractContainerScreenAccessor) handled;
int left = accessor.iconsenhanced$getLeftPos();
int top = accessor.iconsenhanced$getTopPos();
for (Slot slot : handled.getMenu().slots) {
ItemStack stack = slot.getItem();
if (stack.isEmpty()) {
continue;
}
int color = COLOR_LOGIC.resolve(stack);
drawBorder(graphics, left + slot.x, top + slot.y, thickness, color);
}
}
private static void drawBorder(GuiGraphics graphics, int x, int y, int thickness, int color) {
int x2 = x + 16;
int y2 = y + 16;
graphics.fill(x, y, x2, y + thickness, color);
graphics.fill(x, y2 - thickness, x2, y2, color);
graphics.fill(x, y, x + thickness, y2, color);
graphics.fill(x2 - thickness, y, x2, y2, color);
}
private static int clamp(int value, int min, int max) {
return Math.min(max, Math.max(min, value));
}
}

View File

@@ -0,0 +1,21 @@
package dev.dekin.iconsenhanced.client;
import dev.dekin.iconsenhanced.common.ConfigManager;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
public final class ClientInit implements ClientModInitializer {
@Override
public void onInitializeClient() {
ConfigManager.load();
BorderRenderer.applyConfig();
TooltipAugmenter.register();
ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> {
ScreenEvents.afterRender(screen).register((current, graphics, mouseX, mouseY, delta) -> {
BorderRenderer.render(current, graphics, mouseX, mouseY);
TooltipAugmenter.renderOverlay(current, graphics, mouseX, mouseY);
});
});
}
}

View File

@@ -0,0 +1,201 @@
package dev.dekin.iconsenhanced.client;
import dev.dekin.iconsenhanced.common.IconAtlas;
import dev.dekin.iconsenhanced.common.TooltipLayoutModel;
import dev.dekin.iconsenhanced.common.TooltipLayoutModel.Row;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
import net.minecraft.network.chat.Component;
//? if >=1.21 {
import net.minecraft.client.renderer.RenderPipelines;
//?} else {
/*import net.minecraft.client.renderer.MultiBufferSource;
import org.joml.Matrix4f;
*///?}
public final class EnchantTooltipComponent implements ClientTooltipComponent {
private static final int ROW_GAP = 2;
private static final int TEXT_PADDING = 4;
private static final int BADGE_BG = 0xAA000000;
private static final int BADGE_TEXT = 0xFFFFFFFF;
private static final int FULL_BRIGHT = 0xF000F0;
private final TooltipLayoutModel layout;
private final int iconSize;
private final boolean showLevelBadge;
public EnchantTooltipComponent(EnchantTooltipData data) {
this.layout = data.layout();
this.iconSize = Math.max(4, data.iconSize());
this.showLevelBadge = data.showLevelBadge();
}
@Override
public int getWidth(Font font) {
int textStart = iconSize + TEXT_PADDING;
int width = 0;
for (Row row : layout.rows()) {
width = Math.max(width, textStart + font.width(row.text));
for (Component desc : row.descriptions) {
width = Math.max(width, textStart + font.width(desc));
}
}
return width;
}
//? if >=1.21 {
@Override
public int getHeight(Font font) {
return computeHeight(font);
}
//?} else {
/*@Override
public int getHeight() {
return computeHeight(null);
}
*///?}
private int computeHeight(Font font) {
int height = 0;
int lineHeight = font == null ? 9 : font.lineHeight;
int rowHeight = Math.max(iconSize, lineHeight);
for (int i = 0; i < layout.rows().size(); i++) {
Row row = layout.rows().get(i);
height += rowHeight;
height += row.descriptions.size() * lineHeight;
if (i < layout.rows().size() - 1) {
height += ROW_GAP;
}
}
return height;
}
//? if >=1.21 {
@Override
public boolean showTooltipWithItemInHand() {
return true;
}
//?}
//? if >=1.21 {
@Override
public void renderText(GuiGraphics graphics, Font font, int x, int y) {
int yOffset = 0;
int rowHeight = Math.max(iconSize, font.lineHeight);
int textX = x + iconSize + TEXT_PADDING;
for (Row row : layout.rows()) {
int textY = y + yOffset + (rowHeight - font.lineHeight) / 2;
graphics.drawString(font, row.text, textX, textY, 0xFFFFFFFF, false);
yOffset += rowHeight;
for (Component desc : row.descriptions) {
graphics.drawString(font, desc, textX, y + yOffset, 0xFFFFFFFF, false);
yOffset += font.lineHeight;
}
yOffset += ROW_GAP;
}
}
//?} else {
/*@Override
public void renderText(Font font, int x, int y, Matrix4f matrix, MultiBufferSource.BufferSource buffer) {
int yOffset = 0;
int rowHeight = Math.max(iconSize, font.lineHeight);
int textX = x + iconSize + TEXT_PADDING;
for (Row row : layout.rows()) {
int textY = y + yOffset + (rowHeight - font.lineHeight) / 2;
font.drawInBatch(row.text, textX, textY, 0xFFFFFFFF, false, matrix, buffer, Font.DisplayMode.NORMAL, 0, FULL_BRIGHT);
yOffset += rowHeight;
for (Component desc : row.descriptions) {
font.drawInBatch(desc, textX, y + yOffset, 0xFFFFFFFF, false, matrix, buffer, Font.DisplayMode.NORMAL, 0, FULL_BRIGHT);
yOffset += font.lineHeight;
}
yOffset += ROW_GAP;
}
}
*///?}
//? if >=1.21 {
@Override
public void renderImage(Font font, int x, int y, int width, int height, GuiGraphics graphics) {
renderIcons(font, x, y, graphics);
}
//?} else {
/*@Override
public void renderImage(Font font, int x, int y, GuiGraphics graphics) {
renderIcons(font, x, y, graphics);
}
*///?}
private void renderIcons(Font font, int x, int y, GuiGraphics graphics) {
int yOffset = 0;
int rowHeight = Math.max(iconSize, font.lineHeight);
for (Row row : layout.rows()) {
int iconY = y + yOffset + (rowHeight - iconSize) / 2;
drawIcon(graphics, x, iconY, row.iconKey);
if (showLevelBadge) {
drawBadge(graphics, font, x, iconY, row.level);
}
yOffset += rowHeight;
yOffset += row.descriptions.size() * font.lineHeight;
yOffset += ROW_GAP;
}
}
private void drawIcon(GuiGraphics graphics, int x, int y, String key) {
IconAtlas.IconSprite sprite = IconAtlas.get(key);
float scale = iconSize / (float) IconAtlas.ICON_SOURCE_SIZE;
//? if >=1.21 {
graphics.pose().pushMatrix();
graphics.pose().translate(x, y);
graphics.pose().scale(scale, scale);
//?} else {
/*graphics.pose().pushPose();
graphics.pose().translate(x, y, 0);
graphics.pose().scale(scale, scale, 1.0f);
*///?}
blit(graphics, 0, 0, sprite.u, sprite.v, sprite.size);
//? if >=1.21 {
graphics.pose().popMatrix();
//?} else {
/*graphics.pose().popPose();
*///?}
}
private void drawBadge(GuiGraphics graphics, Font font, int x, int y, int level) {
String text = levelToRoman(level);
if (text.isEmpty()) {
return;
}
int textWidth = font.width(text);
int badgeWidth = textWidth + 2;
int badgeHeight = font.lineHeight;
int badgeX = x + iconSize - badgeWidth;
int badgeY = y - 1;
graphics.fill(badgeX, badgeY, badgeX + badgeWidth, badgeY + badgeHeight, BADGE_BG);
graphics.drawString(font, text, badgeX + 1, badgeY + 1, BADGE_TEXT, false);
}
private static String levelToRoman(int level) {
return switch (level) {
case 1 -> "I";
case 2 -> "II";
case 3 -> "III";
case 4 -> "IV";
case 5 -> "V";
case 6 -> "VI";
case 7 -> "VII";
case 8 -> "VIII";
case 9 -> "IX";
case 10 -> "X";
default -> Integer.toString(level);
};
}
private static void blit(GuiGraphics graphics, int x, int y, int u, int v, int size) {
//? if >=1.21 {
graphics.blit(RenderPipelines.GUI_TEXTURED, IconAtlas.ATLAS, x, y, u, v, size, size, IconAtlas.ATLAS_SIZE, IconAtlas.ATLAS_SIZE);
//?} else {
/*graphics.blit(IconAtlas.ATLAS, x, y, 0, (float) u, (float) v, size, size, IconAtlas.ATLAS_SIZE, IconAtlas.ATLAS_SIZE);
*///?}
}
}

View File

@@ -0,0 +1,28 @@
package dev.dekin.iconsenhanced.client;
import dev.dekin.iconsenhanced.common.TooltipLayoutModel;
import net.minecraft.world.inventory.tooltip.TooltipComponent;
public final class EnchantTooltipData implements TooltipComponent {
private final TooltipLayoutModel layout;
private final int iconSize;
private final boolean showLevelBadge;
public EnchantTooltipData(TooltipLayoutModel layout, int iconSize, boolean showLevelBadge) {
this.layout = layout;
this.iconSize = iconSize;
this.showLevelBadge = showLevelBadge;
}
public TooltipLayoutModel layout() {
return layout;
}
public int iconSize() {
return iconSize;
}
public boolean showLevelBadge() {
return showLevelBadge;
}
}

View File

@@ -0,0 +1,218 @@
package dev.dekin.iconsenhanced.client;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import dev.dekin.iconsenhanced.IconsEnhanced;
import dev.dekin.iconsenhanced.adapters.DefaultEnchantmentAdapter;
import dev.dekin.iconsenhanced.adapters.EnchantmentAdapter;
import dev.dekin.iconsenhanced.common.ConfigManager;
import dev.dekin.iconsenhanced.common.EnchEntry;
import dev.dekin.iconsenhanced.common.EnchantSortLogic;
import dev.dekin.iconsenhanced.common.IconsEnhancedConfig;
import dev.dekin.iconsenhanced.common.TooltipLayoutModel;
import dev.dekin.iconsenhanced.mixin.AbstractContainerScreenAccessor;
import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback;
import net.fabricmc.fabric.api.client.rendering.v1.TooltipComponentCallback;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.network.chat.Component;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.inventory.tooltip.TooltipComponent;
public final class TooltipAugmenter {
private static final EnchantmentAdapter ADAPTER = new DefaultEnchantmentAdapter();
private static final EnchantSortLogic SORT_LOGIC = new EnchantSortLogic();
private static final ThreadLocal<Boolean> CAPTURE = ThreadLocal.withInitial(() -> Boolean.FALSE);
private static final ThreadLocal<EnchantTooltipData> CAPTURED_DATA = new ThreadLocal<>();
private TooltipAugmenter() {
}
public static void register() {
//? if >=1.21 {
ItemTooltipCallback.EVENT.register((stack, context, flag, lines) -> onTooltip(stack, lines));
//?} else {
/*ItemTooltipCallback.EVENT.register((stack, context, lines) -> onTooltip(stack, lines));
*///?}
TooltipComponentCallback.EVENT.register(data -> {
if (data instanceof EnchantTooltipData enchData) {
return new EnchantTooltipComponent(enchData);
}
return null;
});
}
public static void renderOverlay(Screen screen, GuiGraphics graphics, int mouseX, int mouseY) {
IconsEnhancedConfig config = ConfigManager.get();
if (!config.tooltips.enabled || !config.tooltips.showIcons) {
return;
}
if (!(screen instanceof AbstractContainerScreen<?> handled)) {
return;
}
Slot hovered = findHoveredSlot(handled, mouseX, mouseY);
if (hovered == null) {
return;
}
ItemStack stack = hovered.getItem();
if (stack.isEmpty()) {
return;
}
Minecraft client = Minecraft.getInstance();
List<Component> lines;
EnchantTooltipData data;
try {
CAPTURE.set(Boolean.TRUE);
CAPTURED_DATA.remove();
lines = Screen.getTooltipFromItem(client, stack);
data = CAPTURED_DATA.get();
} finally {
CAPTURE.remove();
CAPTURED_DATA.remove();
}
if (data == null) {
return;
}
Optional<TooltipComponent> tooltipData = Optional.of(data);
//? if >=1.21 {
graphics.setTooltipForNextFrame(client.font, lines, tooltipData, mouseX, mouseY);
//?} else {
/*graphics.renderTooltip(client.font, lines, tooltipData, mouseX, mouseY);
*///?}
}
private static void onTooltip(ItemStack stack, List<Component> lines) {
try {
IconsEnhancedConfig config = ConfigManager.get();
if (!config.tooltips.enabled) {
return;
}
List<EnchEntry> entries = ADAPTER.getEnchantments(stack);
if (entries.isEmpty()) {
return;
}
TooltipBlock block = TooltipBlock.find(lines, entries);
if (!block.contiguous) {
return;
}
List<EnchEntry> sorted = SORT_LOGIC.sort(entries, config.tooltips);
if (Boolean.TRUE.equals(CAPTURE.get()) && config.tooltips.showIcons) {
EnchantTooltipData data = buildTooltipData(sorted, config.tooltips);
CAPTURED_DATA.set(data);
block.remove(lines);
return;
}
if (!config.tooltips.reorderEnchantments && !config.tooltips.showDescriptions) {
return;
}
List<EnchEntry> ordered = config.tooltips.reorderEnchantments ? sorted : entries;
List<Component> replacement = buildTextLines(ordered, config.tooltips.showDescriptions);
block.replace(lines, replacement);
} catch (Exception e) {
IconsEnhanced.LOGGER.warn("Tooltip augmentation failed.", e);
}
}
private static EnchantTooltipData buildTooltipData(List<EnchEntry> entries, IconsEnhancedConfig.Tooltips tooltips) {
List<TooltipLayoutModel.Row> rows = new ArrayList<>();
for (EnchEntry entry : entries) {
List<Component> desc = tooltips.showDescriptions
? TooltipTextUtil.getDescriptionLines(entry.descKey)
: List.of();
rows.add(new TooltipLayoutModel.Row(entry.displayName, desc, entry.iconKey, entry.level, entry.isCurse));
}
return new EnchantTooltipData(new TooltipLayoutModel(rows), tooltips.iconSize, tooltips.showLevelBadge);
}
private static List<Component> buildTextLines(List<EnchEntry> entries, boolean showDescriptions) {
List<Component> lines = new ArrayList<>();
for (EnchEntry entry : entries) {
lines.add(entry.displayName);
if (showDescriptions) {
lines.addAll(TooltipTextUtil.getDescriptionLines(entry.descKey));
}
}
return lines;
}
private static Slot findHoveredSlot(AbstractContainerScreen<?> screen, int mouseX, int mouseY) {
AbstractContainerScreenAccessor accessor = (AbstractContainerScreenAccessor) screen;
int left = accessor.iconsenhanced$getLeftPos();
int top = accessor.iconsenhanced$getTopPos();
for (Slot slot : screen.getMenu().slots) {
int sx = left + slot.x;
int sy = top + slot.y;
if (mouseX >= sx && mouseX < sx + 16 && mouseY >= sy && mouseY < sy + 16) {
return slot;
}
}
return null;
}
private static final class TooltipBlock {
private final int start;
private final int end;
private final boolean contiguous;
private TooltipBlock(int start, int end, boolean contiguous) {
this.start = start;
this.end = end;
this.contiguous = contiguous;
}
private void remove(List<Component> lines) {
lines.subList(start, end + 1).clear();
}
private void replace(List<Component> lines, List<Component> replacement) {
lines.subList(start, end + 1).clear();
lines.addAll(start, replacement);
}
private static TooltipBlock find(List<Component> lines, List<EnchEntry> entries) {
Set<String> names = new HashSet<>();
for (EnchEntry entry : entries) {
names.add(entry.displayNameString);
}
int first = -1;
int last = -1;
int count = 0;
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i).getString();
if (names.contains(line)) {
if (first == -1) {
first = i;
}
last = i;
count++;
}
}
if (first == -1) {
return new TooltipBlock(0, 0, false);
}
boolean contiguous = (last - first + 1) == count;
return new TooltipBlock(first, last, contiguous);
}
}
}

View File

@@ -0,0 +1,44 @@
package dev.dekin.iconsenhanced.client;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.minecraft.client.resources.language.I18n;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
public final class TooltipTextUtil {
private static final Map<String, List<Component>> CACHE = new HashMap<>();
private static final Style DESCRIPTION_STYLE = Style.EMPTY;
private TooltipTextUtil() {
}
public static List<Component> getDescriptionLines(String key) {
if (key == null || key.isBlank()) {
return Collections.emptyList();
}
if (!I18n.exists(key)) {
return Collections.emptyList();
}
return CACHE.computeIfAbsent(key, TooltipTextUtil::splitLines);
}
private static List<Component> splitLines(String key) {
String text = I18n.get(key);
if (text == null || text.isEmpty()) {
return Collections.emptyList();
}
String[] parts = text.split("\\\\n");
List<Component> lines = new ArrayList<>(parts.length);
for (String part : parts) {
if (!part.isEmpty()) {
lines.add(Component.literal(part).setStyle(DESCRIPTION_STYLE));
}
}
return Collections.unmodifiableList(lines);
}
}

View File

@@ -0,0 +1,14 @@
package dev.dekin.iconsenhanced.mixin;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(AbstractContainerScreen.class)
public interface AbstractContainerScreenAccessor {
@Accessor("leftPos")
int iconsenhanced$getLeftPos();
@Accessor("topPos")
int iconsenhanced$getTopPos();
}

View File

@@ -1,11 +0,0 @@
{
"required": true,
"package": "com.straice.mixin.client",
"compatibilityLevel": "JAVA_21",
"client": [
"ExampleClientMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View File

@@ -1,24 +0,0 @@
package com.straice;
import net.fabricmc.api.ModInitializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TemplateMod implements ModInitializer {
public static final String MOD_ID = "template-mod";
// This logger is used to write text to the console and the log file.
// It is considered best practice to use your mod id as the logger's name.
// That way, it's clear which mod wrote info, warnings, and errors.
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
@Override
public void onInitialize() {
// This code runs as soon as Minecraft is in a mod-load-ready state.
// However, some things (like resources) may still be uninitialized.
// Proceed with mild caution.
LOGGER.info("Hello Fabric world!");
}
}

View File

@@ -1,15 +0,0 @@
package com.straice.mixin;
import net.minecraft.server.MinecraftServer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(MinecraftServer.class)
public class ExampleMixin {
@Inject(at = @At("HEAD"), method = "loadLevel")
private void init(CallbackInfo info) {
// This code is injected into the start of MinecraftServer.loadLevel()V
}
}

View File

@@ -0,0 +1,12 @@
package dev.dekin.iconsenhanced;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class IconsEnhanced {
public static final String MOD_ID = "iconsenhanced";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
private IconsEnhanced() {
}
}

View File

@@ -0,0 +1,116 @@
package dev.dekin.iconsenhanced.adapters;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
//? if <1.21 {
/*import java.util.Map;
*///?}
import dev.dekin.iconsenhanced.IconsEnhanced;
import dev.dekin.iconsenhanced.common.EnchEntry;
import dev.dekin.iconsenhanced.common.IconKeyResolver;
import dev.dekin.iconsenhanced.common.IconKeyResolver.Visual;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.Enchantment;
//? if >=1.21 {
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponents;
import net.minecraft.tags.EnchantmentTags;
import net.minecraft.world.item.enchantment.ItemEnchantments;
//?} else {
/*import net.minecraft.nbt.ListTag;
import net.minecraft.world.item.EnchantedBookItem;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
*///?}
public final class DefaultEnchantmentAdapter implements EnchantmentAdapter {
@Override
public List<EnchEntry> getEnchantments(ItemStack stack) {
if (stack == null || stack.isEmpty()) {
return Collections.emptyList();
}
List<EnchEntry> entries = new ArrayList<>();
//? if >=1.21 {
appendEntries(entries, stack, stack.getEnchantments());
ItemEnchantments stored = stack.get(DataComponents.STORED_ENCHANTMENTS);
if (stored != null && !stored.isEmpty()) {
appendEntries(entries, stack, stored);
}
//?} else {
/*Map<Enchantment, Integer> enchantments = EnchantmentHelper.getEnchantments(stack);
if (!enchantments.isEmpty()) {
appendEntries(entries, stack, enchantments);
}
if (stack.getItem() instanceof EnchantedBookItem) {
ListTag list = EnchantedBookItem.getEnchantments(stack);
Map<Enchantment, Integer> stored = EnchantmentHelper.deserializeEnchantments(list);
appendEntries(entries, stack, stored);
}
*///?}
return entries;
}
//? if >=1.21 {
private static void appendEntries(List<EnchEntry> entries, ItemStack stack, ItemEnchantments enchantments) {
if (enchantments == null || enchantments.isEmpty()) {
return;
}
for (Object2IntMap.Entry<Holder<Enchantment>> entry : enchantments.entrySet()) {
Holder<Enchantment> holder = entry.getKey();
Enchantment enchantment = holder.value();
int level = entry.getIntValue();
String id = holder.unwrapKey()
.map(key -> key.identifier().toString())
.orElse("");
boolean isCurse = holder.is(EnchantmentTags.CURSE);
Visual visual = IconKeyResolver.resolve(id, isCurse);
Component name = colorize(enchantment.getFullname(holder, level), visual.color);
String descKey = buildDescKey(id);
entries.add(new EnchEntry(id, level, name, isCurse, visual.iconKey, descKey));
}
}
//?} else {
/*private static void appendEntries(List<EnchEntry> entries, ItemStack stack, Map<Enchantment, Integer> enchantments) {
if (enchantments == null || enchantments.isEmpty()) {
return;
}
for (Map.Entry<Enchantment, Integer> entry : enchantments.entrySet()) {
Enchantment enchantment = entry.getKey();
int level = entry.getValue();
String id = BuiltInRegistries.ENCHANTMENT.getKey(enchantment).toString();
boolean isCurse = enchantment.isCurse();
Visual visual = IconKeyResolver.resolve(id, isCurse);
Component name = colorize(enchantment.getFullname(level), visual.color);
String descKey = buildDescKey(id);
entries.add(new EnchEntry(id, level, name, isCurse, visual.iconKey, descKey));
}
}
*///?}
private static String buildDescKey(String id) {
if (id == null || id.isBlank()) {
return "";
}
int split = id.indexOf(':');
if (split <= 0 || split >= id.length() - 1) {
return "";
}
String namespace = id.substring(0, split);
String path = id.substring(split + 1);
return IconsEnhanced.MOD_ID + ".desc." + namespace + "." + path;
}
private static Component colorize(Component name, int color) {
if (color == 0) {
return name;
}
return name.copy().withStyle(style -> style.withColor(color));
}
}

View File

@@ -0,0 +1,10 @@
package dev.dekin.iconsenhanced.adapters;
import java.util.List;
import dev.dekin.iconsenhanced.common.EnchEntry;
import net.minecraft.world.item.ItemStack;
public interface EnchantmentAdapter {
List<EnchEntry> getEnchantments(ItemStack stack);
}

View File

@@ -0,0 +1,115 @@
package dev.dekin.iconsenhanced.common;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import dev.dekin.iconsenhanced.IconsEnhanced;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Rarity;
public final class BorderColorLogic {
private final Map<String, Integer> rarityColors = new HashMap<>();
private final Map<String, Integer> customRuleColors = new HashMap<>();
private boolean enchantedOverride;
private int enchantedColor;
private boolean useCustomRules;
private String mode;
private int alpha;
public void applyConfig(IconsEnhancedConfig config) {
IconsEnhancedConfig.Borders borders = config.borders;
rarityColors.clear();
for (Map.Entry<String, String> entry : borders.rarityColors.entrySet()) {
rarityColors.put(entry.getKey(), parseColor(entry.getValue(), 0xFFAAAAAA));
}
customRuleColors.clear();
for (IconsEnhancedConfig.CustomBorderRule rule : borders.customRules) {
String id = normalizeId(rule.itemId);
if (id == null) {
IconsEnhanced.LOGGER.warn("Invalid border rule item id: {}", rule.itemId);
continue;
}
customRuleColors.put(id, parseColor(rule.color, 0xFFFFFFFF));
}
enchantedOverride = borders.enchantedOverride.enabled;
enchantedColor = parseColor(borders.enchantedOverride.color, 0xFFFFD700);
useCustomRules = borders.useCustomRules;
mode = borders.mode == null ? "RARITY" : borders.mode.trim().toUpperCase();
alpha = clamp(borders.alpha, 0, 255);
}
public int resolve(ItemStack stack) {
String itemId = BuiltInRegistries.ITEM.getKey(stack.getItem()).toString();
if (useCustomRules) {
Integer ruleColor = customRuleColors.get(itemId);
if (ruleColor != null) {
return withAlpha(ruleColor, alpha);
}
}
if (enchantedOverride && stack.isEnchanted()) {
return withAlpha(enchantedColor, alpha);
}
if ("CUSTOM".equals(mode)) {
Integer ruleColor = customRuleColors.get(itemId);
if (ruleColor != null) {
return withAlpha(ruleColor, alpha);
}
}
int rarityColor = rarityColors.getOrDefault(rarityKey(stack.getRarity()), 0xFFAAAAAA);
return withAlpha(rarityColor, alpha);
}
private static String normalizeId(String raw) {
if (raw == null) {
return null;
}
String cleaned = raw.trim().toLowerCase(Locale.ROOT);
if (cleaned.isEmpty() || !cleaned.contains(":")) {
return null;
}
return cleaned;
}
private static String rarityKey(Rarity rarity) {
if (rarity == Rarity.UNCOMMON) {
return "uncommon";
}
if (rarity == Rarity.RARE) {
return "rare";
}
if (rarity == Rarity.EPIC) {
return "epic";
}
return "common";
}
private static int parseColor(String value, int fallback) {
if (value == null) {
return fallback;
}
try {
String cleaned = value.trim();
if (cleaned.isEmpty()) {
return fallback;
}
return (int) Long.decode(cleaned).longValue();
} catch (NumberFormatException ex) {
return fallback;
}
}
private static int withAlpha(int color, int alpha) {
return (color & 0x00FFFFFF) | ((alpha & 0xFF) << 24);
}
private static int clamp(int value, int min, int max) {
return Math.min(max, Math.max(min, value));
}
}

View File

@@ -0,0 +1,72 @@
package dev.dekin.iconsenhanced.common;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import dev.dekin.iconsenhanced.IconsEnhanced;
import net.fabricmc.loader.api.FabricLoader;
public final class ConfigManager {
private static final Gson GSON = new GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
.create();
private static final Path CONFIG_PATH = FabricLoader.getInstance()
.getConfigDir()
.resolve(IconsEnhanced.MOD_ID + ".json");
private static IconsEnhancedConfig config = new IconsEnhancedConfig();
private ConfigManager() {
}
public static IconsEnhancedConfig get() {
return config;
}
public static void load() {
IconsEnhancedConfig loaded = null;
boolean needsSave = false;
if (Files.exists(CONFIG_PATH)) {
try (Reader reader = Files.newBufferedReader(CONFIG_PATH)) {
loaded = GSON.fromJson(reader, IconsEnhancedConfig.class);
} catch (JsonSyntaxException | IOException e) {
IconsEnhanced.LOGGER.warn("Config parse failed, recreating defaults.", e);
needsSave = true;
}
} else {
needsSave = true;
}
if (loaded == null) {
loaded = new IconsEnhancedConfig();
needsSave = true;
} else {
needsSave |= loaded.applyDefaults();
}
config = loaded;
if (needsSave) {
save();
}
}
public static void save() {
try {
Files.createDirectories(CONFIG_PATH.getParent());
try (Writer writer = Files.newBufferedWriter(CONFIG_PATH)) {
GSON.toJson(config, writer);
}
} catch (IOException e) {
IconsEnhanced.LOGGER.warn("Failed to write config to disk.", e);
}
}
}

View File

@@ -0,0 +1,41 @@
package dev.dekin.iconsenhanced.common;
import net.minecraft.network.chat.Component;
public final class EnchEntry {
public final String id;
public final String namespace;
public final String path;
public final int level;
public final Component displayName;
public final String displayNameString;
public final boolean isCurse;
public final String iconKey;
public final String descKey;
public EnchEntry(String id, int level, Component displayName, boolean isCurse, String iconKey, String descKey) {
this.id = id == null ? "" : id;
IdParts parts = IdParts.parse(this.id);
this.namespace = parts.namespace;
this.path = parts.path;
this.level = level;
this.displayName = displayName;
this.displayNameString = displayName.getString();
this.isCurse = isCurse;
this.iconKey = iconKey;
this.descKey = descKey;
}
private record IdParts(String namespace, String path) {
private static IdParts parse(String id) {
if (id == null || id.isEmpty()) {
return new IdParts("", "");
}
int split = id.indexOf(':');
if (split <= 0 || split >= id.length() - 1) {
return new IdParts("minecraft", id);
}
return new IdParts(id.substring(0, split), id.substring(split + 1));
}
}
}

View File

@@ -0,0 +1,63 @@
package dev.dekin.iconsenhanced.common;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public final class EnchantSortLogic {
public List<EnchEntry> sort(List<EnchEntry> entries, IconsEnhancedConfig.Tooltips tooltips) {
List<EnchEntry> sorted = new ArrayList<>(entries);
Comparator<EnchEntry> comparator = buildComparator(tooltips);
sorted.sort(comparator);
return sorted;
}
private static Comparator<EnchEntry> buildComparator(IconsEnhancedConfig.Tooltips tooltips) {
Comparator<EnchEntry> base = switch (SortMode.fromString(tooltips.sortMode)) {
case BY_NAME -> Comparator.comparing(entry -> entry.displayNameString.toLowerCase(Locale.ROOT));
case BY_LEVEL_DESC -> Comparator.<EnchEntry>comparingInt(entry -> entry.level).reversed()
.thenComparing(entry -> entry.displayNameString.toLowerCase(Locale.ROOT));
case CUSTOM_PRIORITY_LIST -> customPriorityComparator(tooltips.customPriorityList);
case BY_VANILLA_LIKE -> Comparator
.comparing((EnchEntry entry) -> entry.namespace)
.thenComparing(entry -> entry.path);
};
if (tooltips.cursesLast) {
return Comparator.comparingInt((EnchEntry entry) -> entry.isCurse ? 1 : 0).thenComparing(base);
}
return base;
}
private static Comparator<EnchEntry> customPriorityComparator(List<String> priorityList) {
Map<String, Integer> priority = new HashMap<>();
for (int i = 0; i < priorityList.size(); i++) {
priority.put(priorityList.get(i), i);
}
return Comparator
.comparingInt((EnchEntry entry) -> priority.getOrDefault(entry.id, Integer.MAX_VALUE))
.thenComparing(entry -> entry.displayNameString.toLowerCase(Locale.ROOT));
}
public enum SortMode {
BY_VANILLA_LIKE,
BY_NAME,
BY_LEVEL_DESC,
CUSTOM_PRIORITY_LIST;
public static SortMode fromString(String raw) {
if (raw == null) {
return BY_LEVEL_DESC;
}
for (SortMode value : values()) {
if (value.name().equalsIgnoreCase(raw)) {
return value;
}
}
return BY_LEVEL_DESC;
}
}
}

View File

@@ -0,0 +1,69 @@
package dev.dekin.iconsenhanced.common;
import java.util.HashMap;
import java.util.Map;
import dev.dekin.iconsenhanced.IconsEnhanced;
//? if >=1.21 {
import net.minecraft.resources.Identifier;
//?} else {
/*import net.minecraft.resources.ResourceLocation;
*///?}
public final class IconAtlas {
//? if >=1.21 {
public static final Identifier ATLAS = Identifier.fromNamespaceAndPath(IconsEnhanced.MOD_ID, "textures/gui/enchant_icons.png");
//?} else {
/*public static final ResourceLocation ATLAS = new ResourceLocation(IconsEnhanced.MOD_ID, "textures/gui/enchant_icons.png");
*///?}
public static final int ICON_SOURCE_SIZE = 16;
public static final int ATLAS_COLUMNS = 4;
public static final int ATLAS_SIZE = ICON_SOURCE_SIZE * ATLAS_COLUMNS;
private static final Map<String, IconSprite> ICONS = new HashMap<>();
static {
register("boots", 0);
register("bow", 1);
register("chestplate", 2);
register("crossbow", 3);
register("fishing", 4);
register("helmet", 5);
register("leggings", 6);
register("mace", 7);
register("pickaxe", 8);
register("curse", 9);
register("default", 10);
register("sword", 11);
register("trident", 12);
}
private IconAtlas() {
}
public static IconSprite get(String key) {
IconSprite sprite = ICONS.get(key);
if (sprite != null) {
return sprite;
}
return ICONS.get("default");
}
private static void register(String key, int index) {
int col = index % ATLAS_COLUMNS;
int row = index / ATLAS_COLUMNS;
ICONS.put(key, new IconSprite(col * ICON_SOURCE_SIZE, row * ICON_SOURCE_SIZE, ICON_SOURCE_SIZE));
}
public static final class IconSprite {
public final int u;
public final int v;
public final int size;
private IconSprite(int u, int v, int size) {
this.u = u;
this.v = v;
this.size = size;
}
}
}

View File

@@ -0,0 +1,89 @@
package dev.dekin.iconsenhanced.common;
import java.util.HashMap;
import java.util.Map;
public final class IconKeyResolver {
public static final class Visual {
public final String iconKey;
public final int color;
private Visual(String iconKey, int color) {
this.iconKey = iconKey;
this.color = color;
}
}
private static final Visual DEFAULT = new Visual("default", 0xFCFCFC);
private static final Visual CURSE_DEFAULT = new Visual("curse", 0xA80000);
private static final Map<String, Visual> VISUALS = new HashMap<>();
static {
register("minecraft:aqua_affinity", "helmet", 0x54FCFC);
register("minecraft:bane_of_arthropods", "sword", 0xA800A8);
register("minecraft:curse_of_binding", "curse", 0xA80000);
register("minecraft:blast_protection", "chestplate", 0x00A800);
register("minecraft:channeling", "trident", 0xFCA800);
register("minecraft:depth_strider", "boots", 0x54FCFC);
register("minecraft:efficiency", "pickaxe", 0x00A800);
register("minecraft:feather_falling", "boots", 0x5454FC);
register("minecraft:fire_aspect", "sword", 0xFC5454);
register("minecraft:fire_protection", "chestplate", 0xFC5454);
register("minecraft:flame", "bow", 0xFC5454);
register("minecraft:fortune", "pickaxe", 0xFC54FC);
register("minecraft:frost_walker", "boots", 0x54FCFC);
register("minecraft:impaling", "trident", 0x54FCFC);
register("minecraft:infinity", "bow", 0xFC54FC);
register("minecraft:knockback", "sword", 0x00A8A8);
register("minecraft:looting", "sword", 0xFC54FC);
register("minecraft:loyalty", "trident", 0x0000A8);
register("minecraft:luck_of_the_sea", "fishing", 0x54FCFC);
register("minecraft:lure", "fishing", 0x54FCFC);
register("minecraft:mending", "default", 0xFCFC54);
register("minecraft:multishot", "crossbow", 0xFC54FC);
register("minecraft:piercing", "crossbow", 0xFCA800);
register("minecraft:power", "bow", 0xFCA800);
register("minecraft:projectile_protection", "chestplate", 0x54FC54);
register("minecraft:protection", "chestplate", 0xFC54FC);
register("minecraft:punch", "bow", 0x54FC54);
register("minecraft:quick_charge", "crossbow", 0x54FC54);
register("minecraft:respiration", "helmet", 0x00A8A8);
register("minecraft:riptide", "trident", 0x00A8A8);
register("minecraft:sharpness", "sword", 0x54FC54);
register("minecraft:silk_touch", "pickaxe", 0x54FCFC);
register("minecraft:smite", "sword", 0xFCA800);
register("minecraft:soul_speed", "boots", 0x00A8A8);
register("minecraft:sweeping_edge", "sword", 0x54FCFC);
register("minecraft:swift_sneak", "leggings", 0x00A8A8);
register("minecraft:thorns", "chestplate", 0x00A800);
register("minecraft:unbreaking", "default", 0xFCFC54);
register("minecraft:curse_of_vanishing", "curse", 0xA80000);
register("minecraft:breach", "mace", 0xA800A8);
register("minecraft:density", "mace", 0xA80000);
register("minecraft:wind_burst", "mace", 0x54FCFC);
}
private IconKeyResolver() {
}
public static Visual resolve(String id, boolean isCurse) {
String key = normalizeId(id);
Visual visual = VISUALS.get(key);
if (visual != null) {
return visual;
}
if (isCurse) {
return CURSE_DEFAULT;
}
return DEFAULT;
}
private static void register(String id, String iconKey, int color) {
VISUALS.put(id, new Visual(iconKey, color));
}
private static String normalizeId(String id) {
return id == null ? "" : id;
}
}

View File

@@ -0,0 +1,164 @@
package dev.dekin.iconsenhanced.common;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public final class IconsEnhancedConfig {
public Borders borders = new Borders();
public Tooltips tooltips = new Tooltips();
public boolean applyDefaults() {
boolean changed = false;
if (borders == null) {
borders = new Borders();
changed = true;
} else {
changed |= borders.applyDefaults();
}
if (tooltips == null) {
tooltips = new Tooltips();
changed = true;
} else {
changed |= tooltips.applyDefaults();
}
return changed;
}
public static final class Borders {
public boolean enabled = true;
public int thickness = 1;
public int alpha = 200;
public String mode = "RARITY";
public Map<String, String> rarityColors = defaultRarityColors();
public EnchantedOverride enchantedOverride = new EnchantedOverride();
public boolean useCustomRules = false;
public List<CustomBorderRule> customRules = new ArrayList<>();
private boolean applyDefaults() {
boolean changed = false;
if (mode == null || mode.isBlank()) {
mode = "RARITY";
changed = true;
}
if (rarityColors == null) {
rarityColors = defaultRarityColors();
changed = true;
} else {
changed |= ensureRarityKey("common", "0xFFAAAAAA");
changed |= ensureRarityKey("uncommon", "0xFF55FF55");
changed |= ensureRarityKey("rare", "0xFF5555FF");
changed |= ensureRarityKey("epic", "0xFFFF55FF");
}
if (enchantedOverride == null) {
enchantedOverride = new EnchantedOverride();
changed = true;
}
if (customRules == null) {
customRules = new ArrayList<>();
changed = true;
}
return changed;
}
private boolean ensureRarityKey(String key, String value) {
if (!rarityColors.containsKey(key)) {
rarityColors.put(key, value);
return true;
}
return false;
}
private static Map<String, String> defaultRarityColors() {
Map<String, String> defaults = new LinkedHashMap<>();
defaults.put("common", "0xFFAAAAAA");
defaults.put("uncommon", "0xFF55FF55");
defaults.put("rare", "0xFF5555FF");
defaults.put("epic", "0xFFFF55FF");
return defaults;
}
}
public static final class EnchantedOverride {
public boolean enabled = true;
public String color = "0xFFFFD700";
}
public static final class CustomBorderRule {
public String itemId = "";
public String color = "0xFFFFFFFF";
}
public static final class Tooltips {
public boolean enabled = true;
public boolean reorderEnchantments = true;
public String sortMode = "CUSTOM_PRIORITY_LIST";
public boolean cursesLast = true;
public boolean showDescriptions = true;
public boolean showIcons = true;
public int iconSize = 10;
public boolean showLevelBadge = true;
public List<String> customPriorityList = defaultCustomPriorityList();
private boolean applyDefaults() {
boolean changed = false;
if (sortMode == null || sortMode.isBlank()) {
sortMode = "CUSTOM_PRIORITY_LIST";
changed = true;
}
if (customPriorityList == null || customPriorityList.isEmpty()) {
customPriorityList = defaultCustomPriorityList();
changed = true;
}
return changed;
}
private static List<String> defaultCustomPriorityList() {
List<String> defaults = new ArrayList<>();
defaults.add("minecraft:bane_of_arthropods");
defaults.add("minecraft:density");
defaults.add("minecraft:efficiency");
defaults.add("minecraft:impaling");
defaults.add("minecraft:power");
defaults.add("minecraft:sharpness");
defaults.add("minecraft:smite");
defaults.add("minecraft:blast_protection");
defaults.add("minecraft:breach");
defaults.add("minecraft:feather_falling");
defaults.add("minecraft:fire_protection");
defaults.add("minecraft:piercing");
defaults.add("minecraft:projectile_protection");
defaults.add("minecraft:protection");
defaults.add("minecraft:depth_strider");
defaults.add("minecraft:fortune");
defaults.add("minecraft:looting");
defaults.add("minecraft:loyalty");
defaults.add("minecraft:luck_of_the_sea");
defaults.add("minecraft:lure");
defaults.add("minecraft:quick_charge");
defaults.add("minecraft:respiration");
defaults.add("minecraft:riptide");
defaults.add("minecraft:soul_speed");
defaults.add("minecraft:sweeping_edge");
defaults.add("minecraft:swift_sneak");
defaults.add("minecraft:thorns");
defaults.add("minecraft:unbreaking");
defaults.add("minecraft:wind_burst");
defaults.add("minecraft:fire_aspect");
defaults.add("minecraft:frost_walker");
defaults.add("minecraft:knockback");
defaults.add("minecraft:punch");
defaults.add("minecraft:aqua_affinity");
defaults.add("minecraft:channeling");
defaults.add("minecraft:curse_of_binding");
defaults.add("minecraft:curse_of_vanishing");
defaults.add("minecraft:flame");
defaults.add("minecraft:infinity");
defaults.add("minecraft:mending");
defaults.add("minecraft:multishot");
defaults.add("minecraft:silk_touch");
return defaults;
}
}
}

View File

@@ -0,0 +1,33 @@
package dev.dekin.iconsenhanced.common;
import java.util.List;
import net.minecraft.network.chat.Component;
public final class TooltipLayoutModel {
private final List<Row> rows;
public TooltipLayoutModel(List<Row> rows) {
this.rows = rows;
}
public List<Row> rows() {
return rows;
}
public static final class Row {
public final Component text;
public final List<Component> descriptions;
public final String iconKey;
public final int level;
public final boolean isCurse;
public Row(Component text, List<Component> descriptions, String iconKey, int level, boolean isCurse) {
this.text = text;
this.descriptions = descriptions;
this.iconKey = iconKey;
this.level = level;
this.isCurse = isCurse;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

View File

@@ -0,0 +1,11 @@
{
"iconsenhanced.desc.minecraft.sharpness": "Increases melee damage.",
"iconsenhanced.desc.minecraft.protection": "Reduces incoming damage.",
"iconsenhanced.desc.minecraft.unbreaking": "Items lose durability slower.",
"iconsenhanced.desc.minecraft.mending": "Repairs items with experience.",
"iconsenhanced.desc.minecraft.efficiency": "Increases mining speed.",
"iconsenhanced.desc.minecraft.power": "Increases bow damage.",
"iconsenhanced.desc.minecraft.thorns": "Reflects damage to attackers.",
"iconsenhanced.desc.minecraft.binding_curse": "Cursed: stays equipped.",
"iconsenhanced.desc.minecraft.vanishing_curse": "Cursed: disappears on death."
}

View File

@@ -0,0 +1,11 @@
{
"iconsenhanced.desc.minecraft.sharpness": "Aumenta el dano cuerpo a cuerpo.",
"iconsenhanced.desc.minecraft.protection": "Reduce el dano recibido.",
"iconsenhanced.desc.minecraft.unbreaking": "Los objetos pierden durabilidad mas lento.",
"iconsenhanced.desc.minecraft.mending": "Repara objetos con experiencia.",
"iconsenhanced.desc.minecraft.efficiency": "Aumenta la velocidad de mineria.",
"iconsenhanced.desc.minecraft.power": "Aumenta el dano del arco.",
"iconsenhanced.desc.minecraft.thorns": "Refleja dano a atacantes.",
"iconsenhanced.desc.minecraft.binding_curse": "Maldito: queda equipado.",
"iconsenhanced.desc.minecraft.vanishing_curse": "Maldito: desaparece al morir."
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -1,38 +1,26 @@
{
"schemaVersion": 1,
"id": "template-mod",
"version": "${version}",
"name": "Template Mod",
"description": "This is an example description! Tell everyone what your mod is about!",
"authors": [
"Me!"
],
"contact": {
"homepage": "https://fabricmc.net/",
"sources": "https://github.com/FabricMC/fabric-example-mod"
},
"license": "CC0-1.0",
"icon": "assets/template-mod/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"com.straice.TemplateMod"
],
"client": [
"com.straice.TemplateModClient"
]
},
"mixins": [
"template-mod.mixins.json",
{
"config": "template-mod.client.mixins.json",
"environment": "client"
}
],
"depends": {
"fabricloader": ">=0.18.4",
"minecraft": "~1.21.11",
"java": ">=21",
"fabric-api": "*"
}
}
"schemaVersion": 1,
"id": "${mod_id}",
"version": "${version}",
"name": "${mod_name}",
"description": "${mod_description}",
"authors": [
"${mod_author}"
],
"license": "${mod_license}",
"environment": "client",
"entrypoints": {
"client": [
"dev.dekin.iconsenhanced.client.ClientInit"
]
},
"mixins": [
"iconsenhanced.mixins.json"
],
"depends": {
"fabricloader": ">=${fabric_loader_version}",
"fabric": "*",
"minecraft": "${minecraft_version}"
},
"icon": "assets/${mod_id}/icon.png"
}

View File

@@ -0,0 +1,11 @@
{
"required": true,
"package": "dev.dekin.iconsenhanced.mixin",
"compatibilityLevel": "JAVA_17",
"client": [
"AbstractContainerScreenAccessor"
],
"injectors": {
"defaultRequire": 1
}
}

View File

@@ -1,14 +0,0 @@
{
"required": true,
"package": "com.straice.mixin",
"compatibilityLevel": "JAVA_21",
"mixins": [
"ExampleMixin"
],
"injectors": {
"defaultRequire": 1
},
"overwrites": {
"requireAnnotations": true
}
}

15
stonecutter.gradle Normal file
View File

@@ -0,0 +1,15 @@
plugins {
id 'dev.kikugie.stonecutter'
}
stonecutter.active "1.21.11"
tasks.register("chiseledBuild") {
group = "build"
dependsOn(stonecutter.tree.nodes.collect { it.project.tasks.named("build") })
}
tasks.register("chiseledClean") {
group = "build"
dependsOn(stonecutter.tree.nodes.collect { it.project.tasks.named("clean") })
}

View File

@@ -0,0 +1,7 @@
# Fabric dependencies
deps.fabric_loader=0.18.4
deps.fabric_api=0.92.6+1.20.1
# Java
java_version=17

View File

@@ -0,0 +1,7 @@
# Fabric dependencies
deps.fabric_loader=0.18.4
deps.fabric_api=0.97.3+1.20.4
# Java
java_version=17

View File

@@ -0,0 +1,7 @@
# Fabric dependencies
deps.fabric_loader=0.18.4
deps.fabric_api=0.99.4+1.20.6
# Java
java_version=21

View File

@@ -0,0 +1,7 @@
# Fabric dependencies
deps.fabric_loader=0.18.4
deps.fabric_api=0.116.7+1.21.1
# Java
java_version=21

View File

@@ -0,0 +1,7 @@
# Fabric dependencies
deps.fabric_loader=0.18.4
deps.fabric_api=0.141.1+1.21.11
# Java
java_version=21

View File

@@ -0,0 +1,7 @@
# Fabric dependencies
deps.fabric_loader=0.18.4
deps.fabric_api=0.119.4+1.21.4
# Java
java_version=21