Переименовка предмета, как в наковальне.

Версия Minecraft
1.12.2
API
Forge
128
4
6
Я создал блок, с возможностью в нем создавать разнообразные рецепты, данный блок похож на наковальню и блок из новых версий майна кузнечный стол.
Одна проблема которую я ни как не могу решить хотя ответ где-то рядом, не знаю в каком направлении думать. Нужно добавить возможность переименовки предметов, однако столкнулся с непонятным решением в ваниле.
Tile:
public class SmithingAlchemyTableTile extends TileInventoryBasic implements ITickable {
    public static final int INV_SIZE = 5;
    public static final String nameInv = "container.brazier_soul";

    private int lvlFire = -1;
    private boolean clientSideUpdate = false;
    private String customName = "";

    public SmithingAlchemyTableTile() { super(nameInv, INV_SIZE, 64); }

    @Override public void update() {
        if (world.isRemote) {
            clientSideUpdate = true;
            markDirty();
            return;
        }

        setFireToBrazierSoul();

        boolean foundRecipe = false;
        ItemStack catalystSlot = getStackInSlot(SmithingAlchemyTableContainer.CATALYST_SLOT);

        for (SmithingAlchemyTableRecipe recipe : SmithingAlchemyTableCraft.recipes) {
            SmithingAlchemyTableRecipe findRecipe = recipe.findMatchingRecipe(this);
            if (findRecipe != null) {
                setInventorySlotContents(SmithingAlchemyTableContainer.OUTPUT_SLOT, findRecipe.getOutput().copy());
                foundRecipe = true;
                break;
            } else {
                if (recipe.matchImprovements(this, recipe.armorImprovement)) {
                    if (catalystSlot.getItem() instanceof ItemArmor) {
                        ItemStack armor = catalystSlot.copy();
                        if (recipe.matchCatalystForImprovements(this, armor)) {
                            addUpgradeItemArmor(armor, 1);
                            setArmorModifiers(armor, ((ItemArmor) armor.getItem()).armorType);

                            setInventorySlotContents(SmithingAlchemyTableContainer.OUTPUT_SLOT, armor);
                            foundRecipe = true;
                            break;
                        }
                    }
                }
            }
        }
        if (!foundRecipe) setInventorySlotContents(SmithingAlchemyTableContainer.OUTPUT_SLOT, ItemStack.EMPTY);

        markDirtyAndNotify();
    }

    public EntityPlayer getClosestPlayer() { return world.getClosestPlayer(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, 3.0, false); }

    public void setFireToBrazierSoul() {
        TileEntity tile = world.getTileEntity(pos.down());
        if (tile != null) {
            if (tile instanceof BrazierSoulTile) {
                BrazierSoulTile bst = (BrazierSoulTile) tile;
                setLvlFire(bst.getLvlFire());
            }
        } else setLvlFire(-1);
        markDirtyAndNotify();
    }

    @Override public void readPacketNBT(NBTTagCompound nbt) {
        super.readPacketNBT(nbt);
        lvlFire = nbt.getInteger("LevelFire");
        clientSideUpdate = nbt.getBoolean("clientSideUpdate");
        customName = nbt.getString("CustomName");
    }
    @Override public void writePacketNBT(NBTTagCompound nbt) {
        super.writePacketNBT(nbt);
        nbt.setInteger("LevelFire", lvlFire);
        nbt.setBoolean("clientSideUpdate", clientSideUpdate);
        nbt.setString("CustomName", customName);
    }

    public int getLvlFire() { return this.lvlFire; }
    public void setLvlFire(int lvl) { this.lvlFire = lvl; }

    public String getCustomName() { return customName; }
    public void setCustomName(String name) { this.customName = name; markDirty(); }

    /*public void updateItemName(String newName) {
        setCustomName(newName);
        ItemStack catalyst = getStackInSlot(SmithingAlchemyTableContainer.CATALYST_SLOT);

        if (!catalyst.isEmpty()) {
            ItemStack itemstack = getStackInSlot(SmithingAlchemyTableContainer.OUTPUT_SLOT);

            if (StringUtils.isBlank(newName)) itemstack.clearCustomName();
            else itemstack.setStackDisplayName(getCustomName());
        }
    }*/

    public void updateClientSide() {
        if (!clientSideUpdate) return;
        clientSideUpdate = false;
        for (int i = 0; i < SmithingAlchemyTableContainer.INPUT_SLOT.length; i++) {
            int slotIndex = SmithingAlchemyTableContainer.INPUT_SLOT[i];
            ItemStack stack = getStackInSlot(slotIndex);

            if (!stack.isEmpty()) { markDirty(); break; }
        }
    }

    private int getUpgradeItemArmor(ItemStack stack) { return ItemNBTHelper.getInt(stack, "up_armor_count", 0); }
    private void addUpgradeItemArmor(ItemStack stack, int count) { ItemNBTHelper.setInt(stack, "up_armor_count", getUpgradeItemArmor(stack) + count); }
}
Gui:
public class SmithingAlchemyTableGui extends GuiContainerHandler {
    private static final ResourceLocation GUI_TEXTURE = new ResourceLocation(Main.MOD_ID, "textures/other/gui/inventory/smithing_alchemy_table.png");
    private final int TEXTURE_WIDTH = 256;
    private final int TEXTURE_HEIGHT = 256;

    private static final int GUI_WIDTH = 176;
    private static final int GUI_HEIGHT = 170;

    private GuiTextField nameField;
    private final SmithingAlchemyTableTile tile;
    private final SmithingAlchemyTableContainer container;
    private int guiLeft, guiTop;

    public SmithingAlchemyTableGui(IInventory pInv, SmithingAlchemyTableTile tile) {
        super(new SmithingAlchemyTableContainer(pInv, tile));
        this.tile = tile;
        this.container = (SmithingAlchemyTableContainer) this.inventorySlots;
    }

    @Override public void initGui() {
        super.initGui();
        this.guiLeft = (width - GUI_WIDTH) / 2;
        this.guiTop = (height - GUI_HEIGHT + 2) / 2;

        this.nameField = new GuiTextField(0, this.fontRenderer, guiLeft + 54, guiTop + 25, 103, 12);
        this.nameField.setTextColor(-1);
        this.nameField.setDisabledTextColour(-1);
        this.nameField.setEnableBackgroundDrawing(false);
        this.nameField.setMaxStringLength(60);
    }

    @Override protected void keyTyped(char typedChar, int keyCode) throws IOException {
        /*if (this.nameField.textboxKeyTyped(typedChar, keyCode)) renameItem(); else */super.keyTyped(typedChar, keyCode);
    }

    /*private void renameItem() {
        String s = this.nameField.getText();
        Slot slot = container.getSlot(SmithingAlchemyTableContainer.CATALYST_SLOT);

        if (slot != null && slot.getHasStack() && !slot.getStack().hasDisplayName() && s.equals(slot.getStack().getDisplayName())) {
            s = "";
        }

        this.tile.updateItemName(s);
        this.mc.player.connection.sendPacket(new CPacketCustomPayload("MC|ItemName", (new PacketBuffer(Unpooled.buffer())).writeString(s)));
    }*/

    @Override protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException {
        super.mouseClicked(mouseX, mouseY, mouseButton);
        this.nameField.mouseClicked(mouseX, mouseY, mouseButton);
    }

    @Override public void updateScreen() { super.updateScreen(); tile.updateClientSide(); }

    @Override public void drawScreen(int mouseX, int mouseY, float partialTicks) {
        this.drawDefaultBackground();
        super.drawScreen(mouseX, mouseY, partialTicks);
        this.renderHoveredToolTip(mouseX, mouseY);

        ItemStack catalyst = tile.getStackInSlot(SmithingAlchemyTableContainer.CATALYST_SLOT);
        if (!catalyst.isEmpty()) {
            nameField.setText(catalyst.getDisplayName());
            GlStateManager.disableLighting();
            GlStateManager.disableBlend();
            this.nameField.drawTextBox();
        }
    }

    @Override protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY) {
        GlStateManager.pushMatrix();
        GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f);
        mc.getTextureManager().bindTexture(GUI_TEXTURE);

        int textBox = container.getSlot(0).getHasStack() ? 170 : 186;

        drawScaledCustomSize(guiLeft, guiTop, 0, 0, GUI_WIDTH, GUI_HEIGHT, GUI_WIDTH, GUI_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT);
        drawScaledCustomSize(guiLeft + 51, guiTop + 21, 0, textBox, 110, 16, 110, 16, TEXTURE_WIDTH, TEXTURE_HEIGHT);
        drawFireLevelIndicator(tile.getLvlFire());
        drawExperienceTextIfApplicable();

        GlStateManager.popMatrix();
    }

    private void drawFireLevelIndicator(int fireLevel) {
        int textureY = 37 + (fireLevel * 19);
        drawScaledCustomSize(guiLeft + 33, guiTop + 64, 178, textureY, 18, 18, 18, 18, TEXTURE_WIDTH, TEXTURE_HEIGHT);
    }

    private void drawExperienceTextIfApplicable() {
        ItemStack catalyst = tile.getStackInSlot(SmithingAlchemyTableContainer.CATALYST_SLOT);
        for (SmithingAlchemyTableRecipe recipe : SmithingAlchemyTableCraft.recipes) {
            if (recipe.findMatchingRecipe(tile) != null) {
                drawExpText(mc, width, height, recipe.findMatchingRecipe(tile));
                break;
            }
            if (recipe.matchImprovements(tile, recipe.armorImprovement) && recipe.findMatchingRecipe(tile) == null) {
                if (catalyst.getItem() instanceof ItemArmor && !catalyst.isEmpty()) {
                    drawExpLvlText(mc, width, height, recipe.lvlArmor);
                    break;
                }
            }
        }
    }

    public void drawExpLvlText(Minecraft mc, int recipeWidth, int recipeHeight, int lvl) {
        String text = JeiHandler.locKey(JeiHandler.SAT_RequiresExperience).concat(" " + lvl + GuiContainerHandler.EXP_LEVELS);
        drawTextBox(mc, recipeWidth, recipeHeight, text, lvl);
    }

    public void drawExpText(Minecraft mc, int recipeWidth, int recipeHeight, SmithingAlchemyTableRecipe recipe) {
        String labelText = JeiHandler.locKey(JeiHandler.SAT_RequiresExperience);
        String labelTextNull = JeiHandler.locKey(JeiHandler.SAT_RequiresExperienceNull);
        String expText = recipe.isExpLevel() ? recipe.getExpCost() + GuiContainerHandler.EXP_LEVELS : recipe.getExpCost() + GuiContainerHandler.EXP_POINTS;
        String text = recipe.getExpCost() != 0 ? labelText.concat(" " + expText) : labelTextNull;
        drawTextBox(mc, recipeWidth, recipeHeight, text, recipe);
    }

    private void drawTextBox(Minecraft mc, int recipeWidth, int recipeHeight, String text, int lvl) {
        int BOX_PADDING = 1;
        float textScale = mc.getLanguageManager().getCurrentLanguage().getLanguageCode().startsWith("ru") ? 1.0f : 0.8f;
        int yOffset = mc.getLanguageManager().getCurrentLanguage().getLanguageCode().startsWith("ru") ? 6 : 7;

        int labelWidth = mc.fontRenderer.getStringWidth(text) + (BOX_PADDING * 2);
        int boxHeight = mc.fontRenderer.FONT_HEIGHT + (BOX_PADDING * 2);
        int BASE_X = ((recipeWidth / 2) + 81) - labelWidth;
        int BASE_Y = ((recipeHeight / 2) - 22) + yOffset;

        GlStateManager.enableAlpha();
        GlStateManager.enableBlend();
        GlStateManager.pushMatrix();

        GlStateManager.translate(BASE_X, BASE_Y, 0);
        GlStateManager.scale(textScale, textScale, 1.0F);
        drawBox(0, 0, labelWidth, boxHeight, 0x80000000);

        int textColor = mc.player.experienceLevel >= lvl ? 0xFF80FF20 : 0xF51000;
        mc.fontRenderer.drawStringWithShadow(text, BOX_PADDING, BOX_PADDING, textColor);

        GlStateManager.popMatrix();
        GlStateManager.disableBlend();
        GlStateManager.disableAlpha();
    }
    private void drawTextBox(Minecraft mc, int recipeWidth, int recipeHeight, String text, SmithingAlchemyTableRecipe recipe) {
        int BOX_PADDING = 1;
        float textScale = mc.getLanguageManager().getCurrentLanguage().getLanguageCode().startsWith("ru") ? 1.0f : 0.8f;
        int yOffset = mc.getLanguageManager().getCurrentLanguage().getLanguageCode().startsWith("ru") ? 6 : 7;

        int labelWidth = mc.fontRenderer.getStringWidth(text) + (BOX_PADDING * 2);
        int boxHeight = mc.fontRenderer.FONT_HEIGHT + (BOX_PADDING * 2);
        int BASE_X = ((recipeWidth / 2) + 81) - labelWidth;
        int BASE_Y = ((recipeHeight / 2) - 22) + yOffset;

        GlStateManager.enableAlpha();
        GlStateManager.enableBlend();
        GlStateManager.pushMatrix();

        GlStateManager.translate(BASE_X, BASE_Y, 0);
        GlStateManager.scale(textScale, textScale, 1.0F);
        drawBox(0, 0, labelWidth, boxHeight, 0x80000000);

        int textColor = recipe.hasEnoughExperience(mc.player) ? 0xFF80FF20 : 0xF51000;
        mc.fontRenderer.drawStringWithShadow(text, BOX_PADDING, BOX_PADDING, textColor);

        GlStateManager.popMatrix();
        GlStateManager.disableBlend();
        GlStateManager.disableAlpha();
    }
}
Container:
public class SmithingAlchemyTableContainer extends Container {
    private SmithingAlchemyTableTile tile;

    public static int CATALYST_SLOT = 0;
    public static int[] INPUT_SLOT = {1, 2, 3};
    public static int OUTPUT_SLOT = 4;

    public SmithingAlchemyTableContainer(IInventory pInv, SmithingAlchemyTableTile tile) {
        this.tile = tile;

        addSlotToContainer(new SInput(tile, 0, 90, 44));
        addSlotToContainer(new SInput(tile, 1, 16, 44));
        addSlotToContainer(new SInput(tile, 2, 34, 44));
        addSlotToContainer(new SInput(tile, 3, 52, 44));
        addSlotToContainer(new SOutput(tile, 4, 144, 44));

        for (int i = 0; i < 3; ++i) for (int j = 0; j < 9; ++j) this.addSlotToContainer(new Slot(pInv, j + i * 9 + 9, 8 + j * 18, 86 + i * 18));
        for (int k = 0; k < 9; ++k) this.addSlotToContainer(new Slot(pInv, k, 8 + k * 18, 144));
    }

    @Override public ItemStack transferStackInSlot(EntityPlayer player, int index) {
        ItemStack stack = ItemStack.EMPTY;
        Slot slot = this.inventorySlots.get(index);
        int sizeSlots = SmithingAlchemyTableTile.INV_SIZE;

        if (slot != null && slot.getHasStack()) {
            ItemStack stackToMove = slot.getStack();
            stack = stackToMove.copy();

            if (index >= sizeSlots && index != OUTPUT_SLOT) {
                if (!this.mergeItemStack(stackToMove, 0, sizeSlots-1, false)) return ItemStack.EMPTY;
            } else if (index < sizeSlots) {
                if (!this.mergeItemStack(stackToMove, sizeSlots, this.inventorySlots.size(), false)) return ItemStack.EMPTY;
            }

            if (stackToMove.isEmpty()) slot.putStack(ItemStack.EMPTY); else slot.onSlotChanged();
            if (stackToMove.getCount() == stack.getCount()) return ItemStack.EMPTY;

            slot.onTake(player, stackToMove);
            return stack;
        }
        return ItemStack.EMPTY;
    }

    @Override public boolean canInteractWith(@Nonnull EntityPlayer player) {
        if (this.tile.getWorld().getBlockState(this.tile.getPos()).getBlock() != BlockInit.SMITHING_ALCHEMY_TABLE) return false;
        else return tile.isUsableByPlayer(player);
    }

    private static class SInput extends Slot {
        public SInput(IInventory inv, int index, int xPosition, int yPosition) { super(inv, index, xPosition, yPosition); }
        @Override public boolean isItemValid(@Nonnull ItemStack is) { return true; }
    }
    private static class SOutput extends Slot {
        public SOutput(IInventory inv, int index, int xPosition, int yPosition) { super(inv, index, xPosition, yPosition); }
        @Override public boolean isItemValid(@Nonnull ItemStack is) { return false; }

        @Override public ItemStack onTake(EntityPlayer player, ItemStack stack) { return processOutput(player, stack); }
        @Override public boolean canTakeStack(EntityPlayer player) { return canPlayerTakeOutput(player); }

        private boolean canPlayerTakeOutput(EntityPlayer player) {
            SmithingAlchemyTableTile tile = (SmithingAlchemyTableTile) inventory;
            player = tile.getClosestPlayer();

            for (SmithingAlchemyTableRecipe recipe : SmithingAlchemyTableCraft.recipes) {
                SmithingAlchemyTableRecipe findRecipe = recipe.findMatchingRecipe(tile);
                if (findRecipe != null) {
                    if (player != null) return findRecipe.hasEnoughExperience(player);
                    break;
                } else {
                    if (recipe.matchImprovements(tile, recipe.armorImprovement)) {
                        if (player != null) return player.experienceLevel >= recipe.lvlArmor;
                        break;
                    }
                }
            }
            return false;
        }

        private ItemStack processOutput(EntityPlayer player, ItemStack stack) {
            SmithingAlchemyTableTile tile = (SmithingAlchemyTableTile) inventory;
            for (SmithingAlchemyTableRecipe recipe : SmithingAlchemyTableCraft.recipes) if (tryProcessCraft(recipe, tile, player)) return super.onTake(player, stack);
            return ItemStack.EMPTY;
        }

        private boolean tryProcessCraft(SmithingAlchemyTableRecipe recipe, SmithingAlchemyTableTile tile, EntityPlayer player) {
            SmithingAlchemyTableRecipe findRecipe = recipe.findMatchingRecipe(tile);
            if (findRecipe != null) {
                return processBasicCraft(findRecipe, tile, player);
            } else {
                if (recipe.matchImprovements(tile, recipe.armorImprovement)) {
                    return processImprovementCraft(recipe, tile, player, recipe.armorImprovement, recipe.lvlArmor);
                }
            }
            return false;
        }

        private boolean processImprovementCraft(SmithingAlchemyTableRecipe recipe, SmithingAlchemyTableTile tile, EntityPlayer player, ItemStack stack, int lvl) {
            ItemStack catalystSlot = tile.getStackInSlot(SmithingAlchemyTableContainer.CATALYST_SLOT);
            if (player.experienceLevel >= lvl) {
                player.addExperienceLevel(-lvl);
                consumeInputStack(stack, recipe, tile);
                catalystSlot.shrink(catalystSlot.getCount());
                return true;
            }
            return false;
        }
        private boolean processBasicCraft(SmithingAlchemyTableRecipe recipe, SmithingAlchemyTableTile tile, EntityPlayer player) {
            ItemStack catalystSlot = tile.getStackInSlot(SmithingAlchemyTableContainer.CATALYST_SLOT);
            if (recipe.hasEnoughExperience(player)) {
                recipe.consumeExperience(player);
                consumeInputs(recipe, tile);
                catalystSlot.shrink(recipe.getCatalystInput().getCount());
                return true;
            }
            return false;
        }

        private void consumeInputStack(ItemStack stack, SmithingAlchemyTableRecipe recipe, SmithingAlchemyTableTile tile) {
            List<ItemStack> remainIng = new ArrayList<>();
            remainIng.add(stack.copy());
            consumeStacks(remainIng, recipe, tile);
        }
        private void consumeInputs(SmithingAlchemyTableRecipe recipe, SmithingAlchemyTableTile tile) {
            List<ItemStack> remainIng = new ArrayList<>();
            for (Ingredient input : recipe.getInputs()) for (ItemStack ing : input.getMatchingStacks()) remainIng.add(ing.copy());
            consumeStacks(remainIng, recipe, tile);
        }
        private void consumeStacks(List<ItemStack> remainIng, SmithingAlchemyTableRecipe recipe, SmithingAlchemyTableTile tile) {
            for (int index : SmithingAlchemyTableContainer.INPUT_SLOT) {
                ItemStack invStack = tile.getStackInSlot(index);
                for (int i = 0; i < remainIng.size(); i++) {
                    ItemStack requiredStack = remainIng.get(i);
                    if (recipe.compareStacks(invStack, requiredStack)) {
                        invStack.shrink(requiredStack.getCount());
                        remainIng.remove(i);
                        break;
                    }
                }
            }
        }
    }
}
Уже много вариантов перебрал но, переименовка не работает. Подскажите как правильно это все реализовать.
 
Тебе нужно вызвать stack.setStackDisplayName("you_name"); на сервере и клиенте.

CPacketCustomPayload:
this.mc.player.connection.sendPacket(new CPacketCustomPayload("MC|ItemName", (new PacketBuffer(Unpooled.buffer())).writeString(s)));
Отправляет пакет на сервер и вызывает у ContainerRepair метод updateItemName, вот кусок кода:
NetHandlerPlayServer:
else if ("MC|ItemName".equals(s))
{
    if (this.player.openContainer instanceof ContainerRepair)
    {
        ContainerRepair containerrepair = (ContainerRepair)this.player.openContainer;

        if (packetIn.getBufferData() != null && packetIn.getBufferData().readableBytes() >= 1)
        {
            String s5 = ChatAllowedCharacters.filterAllowedCharacters(packetIn.getBufferData().readString(32767));

            if (s5.length() <= 35)
            {
                containerrepair.updateItemName(s5);
            }
        }
        else
        {
            containerrepair.updateItemName("");
        }
    }
}
Так что он тебе не поможет, нада делать свой пакет (или наследоваться от ContainerRepair, но это такой себе вариант).
 
Последнее редактирование:
Тебе нужно вызвать stack.setStackDisplayName("you_name"); на сервере и клиенте.
Это интересная идея но мне нужно создать свой метод для переименовки, поскольку мне нужно чтобы если переименовывался предмет крафт не происходил, переименовка еще включает в себя не просто изменение названия предмета, а еще редактирование типа изменение цвета. Проблем в том я не знаю как правильно все это привязать, и нужно ли сохранять имя предмета в тайл или проще использовать инвентарь.
 
Это интересная идея
Не идея а база, без этого нечего работать не будет.
если переименовывался предмет крафт не происходил
Вот проверка stack.hasDisplayName() - возвращает true если предмет переименован.
нужно ли сохранять имя предмета в тайл или проще использовать инвентарь
Ну если хочешь чтобы записанное в поле имя сохранялось после перезахода в gui то да.
В наковальне это сделано для синхронизации, по хорошему надо иметь на сервере то что написано в поле ввода на клиенте.
но мне нужно создать свой метод для переименовки
Я не понял... Что?
Ну вот я создал свой метод, не благодари
renameItem:
public void renameItem(ItemStack stack, String name) {
    stack.setStackDisplayName(name);
}
переименовка еще включает в себя не просто изменение названия предмета, а еще редактирование типа изменение цвета
Я опять нечего не понял, тогда отвечу на все то что понял

1. а еще редактирование типа изменение цвета
Какой тип изменения цвета? Никогда такого не слышал🤔​

2. а еще редактирование типа изменение цвета (тут типа это короче типа ну типа вот такое вот)
Ну как это на что-то влияет? Если хочешь менять цвет то TextFormatting в помощь​

3. а еще редактирование типа, - (запятая) изменение цвета
Ну я не знаю что за тип, можешь в свой метод переименования добавить что-то вроде stack.changeType(type);​
 
Последнее редактирование:
мне нужно создать свой метод для переименовки, поскольку мне нужно чтобы если переименовывался предмет крафт не происходил
Чтобы предмет вставлялся в слот и при наборе текста в поле, предмет просто переименовывался?
 
просто переименовывался?
да, но я хочу сделать редактор, если игрок пишет символ & и дополнительно букву или цифру 7a106c4556147202910fef8ab5761f57.gif то менялся цвет и так далее и если несколько таких символов в одном слове то тоже все нормально работало. Однако если стереть название и начать писать с символа ! то добавлялось описание вместо изменения названия и его также можно было бы изменять вот этим символом &
 
редактор, если игрок пишет символ & и дополнительно букву или цифру
7a106c4556147202910fef8ab5761f57.gif
то менялся цвет и так далее и если несколько таких символов в одном слове то тоже все нормально работало.
Сделай .replace('&', '§') и посмотри как делается форматирование названия серверов (motd).
Однако если стереть название и начать писать с символа ! то добавлялось описание вместо изменения названия и его также можно было бы изменять вот этим символом &
Если строка .startsWith("!"), то вставляешь в ItemStack nbt тег с описанием, а в ивенте ItemTooltipEvent показываешь это описание.
 
Назад
Сверху