minecraft-src/net/minecraft/client/gui/screens/inventory/MerchantScreen.java
2025-07-04 03:45:38 +03:00

327 lines
14 KiB
Java

package net.minecraft.client.gui.screens.inventory;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.Button.OnPress;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ServerboundSelectTradePacket;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.npc.VillagerData;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.MerchantMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.trading.MerchantOffer;
import net.minecraft.world.item.trading.MerchantOffers;
@Environment(EnvType.CLIENT)
public class MerchantScreen extends AbstractContainerScreen<MerchantMenu> {
private static final ResourceLocation OUT_OF_STOCK_SPRITE = ResourceLocation.withDefaultNamespace("container/villager/out_of_stock");
private static final ResourceLocation EXPERIENCE_BAR_BACKGROUND_SPRITE = ResourceLocation.withDefaultNamespace("container/villager/experience_bar_background");
private static final ResourceLocation EXPERIENCE_BAR_CURRENT_SPRITE = ResourceLocation.withDefaultNamespace("container/villager/experience_bar_current");
private static final ResourceLocation EXPERIENCE_BAR_RESULT_SPRITE = ResourceLocation.withDefaultNamespace("container/villager/experience_bar_result");
private static final ResourceLocation SCROLLER_SPRITE = ResourceLocation.withDefaultNamespace("container/villager/scroller");
private static final ResourceLocation SCROLLER_DISABLED_SPRITE = ResourceLocation.withDefaultNamespace("container/villager/scroller_disabled");
private static final ResourceLocation TRADE_ARROW_OUT_OF_STOCK_SPRITE = ResourceLocation.withDefaultNamespace("container/villager/trade_arrow_out_of_stock");
private static final ResourceLocation TRADE_ARROW_SPRITE = ResourceLocation.withDefaultNamespace("container/villager/trade_arrow");
private static final ResourceLocation DISCOUNT_STRIKETHRUOGH_SPRITE = ResourceLocation.withDefaultNamespace("container/villager/discount_strikethrough");
/**
* The GUI texture for the villager merchant GUI.
*/
private static final ResourceLocation VILLAGER_LOCATION = ResourceLocation.withDefaultNamespace("textures/gui/container/villager.png");
private static final int TEXTURE_WIDTH = 512;
private static final int TEXTURE_HEIGHT = 256;
private static final int MERCHANT_MENU_PART_X = 99;
private static final int PROGRESS_BAR_X = 136;
private static final int PROGRESS_BAR_Y = 16;
private static final int SELL_ITEM_1_X = 5;
private static final int SELL_ITEM_2_X = 35;
private static final int BUY_ITEM_X = 68;
private static final int LABEL_Y = 6;
private static final int NUMBER_OF_OFFER_BUTTONS = 7;
private static final int TRADE_BUTTON_X = 5;
private static final int TRADE_BUTTON_HEIGHT = 20;
private static final int TRADE_BUTTON_WIDTH = 88;
private static final int SCROLLER_HEIGHT = 27;
private static final int SCROLLER_WIDTH = 6;
private static final int SCROLL_BAR_HEIGHT = 139;
private static final int SCROLL_BAR_TOP_POS_Y = 18;
private static final int SCROLL_BAR_START_X = 94;
private static final Component TRADES_LABEL = Component.translatable("merchant.trades");
private static final Component DEPRECATED_TOOLTIP = Component.translatable("merchant.deprecated");
/**
* The integer value corresponding to the currently selected merchant recipe.
*/
private int shopItem;
private final MerchantScreen.TradeOfferButton[] tradeOfferButtons = new MerchantScreen.TradeOfferButton[7];
int scrollOff;
private boolean isDragging;
public MerchantScreen(MerchantMenu menu, Inventory playerInventory, Component title) {
super(menu, playerInventory, title);
this.imageWidth = 276;
this.inventoryLabelX = 107;
}
private void postButtonClick() {
this.menu.setSelectionHint(this.shopItem);
this.menu.tryMoveItems(this.shopItem);
this.minecraft.getConnection().send(new ServerboundSelectTradePacket(this.shopItem));
}
@Override
protected void init() {
super.init();
int i = (this.width - this.imageWidth) / 2;
int j = (this.height - this.imageHeight) / 2;
int k = j + 16 + 2;
for (int l = 0; l < 7; l++) {
this.tradeOfferButtons[l] = this.addRenderableWidget(new MerchantScreen.TradeOfferButton(i + 5, k, l, button -> {
if (button instanceof MerchantScreen.TradeOfferButton) {
this.shopItem = ((MerchantScreen.TradeOfferButton)button).getIndex() + this.scrollOff;
this.postButtonClick();
}
}));
k += 20;
}
}
@Override
protected void renderLabels(GuiGraphics guiGraphics, int mouseX, int mouseY) {
int i = this.menu.getTraderLevel();
if (i > 0 && i <= 5 && this.menu.showProgressBar()) {
Component component = Component.translatable("merchant.title", this.title, Component.translatable("merchant.level." + i));
int j = this.font.width(component);
int k = 49 + this.imageWidth / 2 - j / 2;
guiGraphics.drawString(this.font, component, k, 6, 4210752, false);
} else {
guiGraphics.drawString(this.font, this.title, 49 + this.imageWidth / 2 - this.font.width(this.title) / 2, 6, 4210752, false);
}
guiGraphics.drawString(this.font, this.playerInventoryTitle, this.inventoryLabelX, this.inventoryLabelY, 4210752, false);
int l = this.font.width(TRADES_LABEL);
guiGraphics.drawString(this.font, TRADES_LABEL, 5 - l / 2 + 48, 6, 4210752, false);
}
@Override
protected void renderBg(GuiGraphics guiGraphics, float partialTick, int mouseX, int mouseY) {
int i = (this.width - this.imageWidth) / 2;
int j = (this.height - this.imageHeight) / 2;
guiGraphics.blit(RenderType::guiTextured, VILLAGER_LOCATION, i, j, 0.0F, 0.0F, this.imageWidth, this.imageHeight, 512, 256);
MerchantOffers merchantOffers = this.menu.getOffers();
if (!merchantOffers.isEmpty()) {
int k = this.shopItem;
if (k < 0 || k >= merchantOffers.size()) {
return;
}
MerchantOffer merchantOffer = (MerchantOffer)merchantOffers.get(k);
if (merchantOffer.isOutOfStock()) {
guiGraphics.blitSprite(RenderType::guiTextured, OUT_OF_STOCK_SPRITE, this.leftPos + 83 + 99, this.topPos + 35, 28, 21);
}
}
}
private void renderProgressBar(GuiGraphics guiGraphics, int posX, int posY, MerchantOffer merchantOffer) {
int i = this.menu.getTraderLevel();
int j = this.menu.getTraderXp();
if (i < 5) {
guiGraphics.blitSprite(RenderType::guiTextured, EXPERIENCE_BAR_BACKGROUND_SPRITE, posX + 136, posY + 16, 102, 5);
int k = VillagerData.getMinXpPerLevel(i);
if (j >= k && VillagerData.canLevelUp(i)) {
int l = 102;
float f = 102.0F / (VillagerData.getMaxXpPerLevel(i) - k);
int m = Math.min(Mth.floor(f * (j - k)), 102);
guiGraphics.blitSprite(RenderType::guiTextured, EXPERIENCE_BAR_CURRENT_SPRITE, 102, 5, 0, 0, posX + 136, posY + 16, m, 5);
int n = this.menu.getFutureTraderXp();
if (n > 0) {
int o = Math.min(Mth.floor(n * f), 102 - m);
guiGraphics.blitSprite(RenderType::guiTextured, EXPERIENCE_BAR_RESULT_SPRITE, 102, 5, m, 0, posX + 136 + m, posY + 16, o, 5);
}
}
}
}
private void renderScroller(GuiGraphics guiGraphics, int posX, int posY, MerchantOffers merchantOffers) {
int i = merchantOffers.size() + 1 - 7;
if (i > 1) {
int j = 139 - (27 + (i - 1) * 139 / i);
int k = 1 + j / i + 139 / i;
int l = 113;
int m = Math.min(113, this.scrollOff * k);
if (this.scrollOff == i - 1) {
m = 113;
}
guiGraphics.blitSprite(RenderType::guiTextured, SCROLLER_SPRITE, posX + 94, posY + 18 + m, 6, 27);
} else {
guiGraphics.blitSprite(RenderType::guiTextured, SCROLLER_DISABLED_SPRITE, posX + 94, posY + 18, 6, 27);
}
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
super.render(guiGraphics, mouseX, mouseY, partialTick);
MerchantOffers merchantOffers = this.menu.getOffers();
if (!merchantOffers.isEmpty()) {
int i = (this.width - this.imageWidth) / 2;
int j = (this.height - this.imageHeight) / 2;
int k = j + 16 + 1;
int l = i + 5 + 5;
this.renderScroller(guiGraphics, i, j, merchantOffers);
int m = 0;
for (MerchantOffer merchantOffer : merchantOffers) {
if (!this.canScroll(merchantOffers.size()) || m >= this.scrollOff && m < 7 + this.scrollOff) {
ItemStack itemStack = merchantOffer.getBaseCostA();
ItemStack itemStack2 = merchantOffer.getCostA();
ItemStack itemStack3 = merchantOffer.getCostB();
ItemStack itemStack4 = merchantOffer.getResult();
guiGraphics.pose().pushPose();
guiGraphics.pose().translate(0.0F, 0.0F, 100.0F);
int n = k + 2;
this.renderAndDecorateCostA(guiGraphics, itemStack2, itemStack, l, n);
if (!itemStack3.isEmpty()) {
guiGraphics.renderFakeItem(itemStack3, i + 5 + 35, n);
guiGraphics.renderItemDecorations(this.font, itemStack3, i + 5 + 35, n);
}
this.renderButtonArrows(guiGraphics, merchantOffer, i, n);
guiGraphics.renderFakeItem(itemStack4, i + 5 + 68, n);
guiGraphics.renderItemDecorations(this.font, itemStack4, i + 5 + 68, n);
guiGraphics.pose().popPose();
k += 20;
m++;
} else {
m++;
}
}
int o = this.shopItem;
MerchantOffer merchantOfferx = (MerchantOffer)merchantOffers.get(o);
if (this.menu.showProgressBar()) {
this.renderProgressBar(guiGraphics, i, j, merchantOfferx);
}
if (merchantOfferx.isOutOfStock() && this.isHovering(186, 35, 22, 21, mouseX, mouseY) && this.menu.canRestock()) {
guiGraphics.renderTooltip(this.font, DEPRECATED_TOOLTIP, mouseX, mouseY);
}
for (MerchantScreen.TradeOfferButton tradeOfferButton : this.tradeOfferButtons) {
if (tradeOfferButton.isHoveredOrFocused()) {
tradeOfferButton.renderToolTip(guiGraphics, mouseX, mouseY);
}
tradeOfferButton.visible = tradeOfferButton.index < this.menu.getOffers().size();
}
}
this.renderTooltip(guiGraphics, mouseX, mouseY);
}
private void renderButtonArrows(GuiGraphics guiGraphics, MerchantOffer merchantOffers, int posX, int posY) {
if (merchantOffers.isOutOfStock()) {
guiGraphics.blitSprite(RenderType::guiTextured, TRADE_ARROW_OUT_OF_STOCK_SPRITE, posX + 5 + 35 + 20, posY + 3, 10, 9);
} else {
guiGraphics.blitSprite(RenderType::guiTextured, TRADE_ARROW_SPRITE, posX + 5 + 35 + 20, posY + 3, 10, 9);
}
}
private void renderAndDecorateCostA(GuiGraphics guiGraphics, ItemStack realCost, ItemStack baseCost, int x, int y) {
guiGraphics.renderFakeItem(realCost, x, y);
if (baseCost.getCount() == realCost.getCount()) {
guiGraphics.renderItemDecorations(this.font, realCost, x, y);
} else {
guiGraphics.renderItemDecorations(this.font, baseCost, x, y, baseCost.getCount() == 1 ? "1" : null);
guiGraphics.renderItemDecorations(this.font, realCost, x + 14, y, realCost.getCount() == 1 ? "1" : null);
guiGraphics.pose().pushPose();
guiGraphics.pose().translate(0.0F, 0.0F, 300.0F);
guiGraphics.blitSprite(RenderType::guiTextured, DISCOUNT_STRIKETHRUOGH_SPRITE, x + 7, y + 12, 9, 2);
guiGraphics.pose().popPose();
}
}
private boolean canScroll(int numOffers) {
return numOffers > 7;
}
@Override
public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
if (super.mouseScrolled(mouseX, mouseY, scrollX, scrollY)) {
return true;
} else {
int i = this.menu.getOffers().size();
if (this.canScroll(i)) {
int j = i - 7;
this.scrollOff = Mth.clamp((int)(this.scrollOff - scrollY), 0, j);
}
return true;
}
}
@Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) {
int i = this.menu.getOffers().size();
if (this.isDragging) {
int j = this.topPos + 18;
int k = j + 139;
int l = i - 7;
float f = ((float)mouseY - j - 13.5F) / (k - j - 27.0F);
f = f * l + 0.5F;
this.scrollOff = Mth.clamp((int)f, 0, l);
return true;
} else {
return super.mouseDragged(mouseX, mouseY, button, dragX, dragY);
}
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
this.isDragging = false;
int i = (this.width - this.imageWidth) / 2;
int j = (this.height - this.imageHeight) / 2;
if (this.canScroll(this.menu.getOffers().size()) && mouseX > i + 94 && mouseX < i + 94 + 6 && mouseY > j + 18 && mouseY <= j + 18 + 139 + 1) {
this.isDragging = true;
}
return super.mouseClicked(mouseX, mouseY, button);
}
@Environment(EnvType.CLIENT)
class TradeOfferButton extends Button {
final int index;
public TradeOfferButton(final int x, final int y, final int index, final OnPress onPress) {
super(x, y, 88, 20, CommonComponents.EMPTY, onPress, DEFAULT_NARRATION);
this.index = index;
this.visible = false;
}
public int getIndex() {
return this.index;
}
public void renderToolTip(GuiGraphics guiGraphics, int mouseX, int mouseY) {
if (this.isHovered && MerchantScreen.this.menu.getOffers().size() > this.index + MerchantScreen.this.scrollOff) {
if (mouseX < this.getX() + 20) {
ItemStack itemStack = ((MerchantOffer)MerchantScreen.this.menu.getOffers().get(this.index + MerchantScreen.this.scrollOff)).getCostA();
guiGraphics.renderTooltip(MerchantScreen.this.font, itemStack, mouseX, mouseY);
} else if (mouseX < this.getX() + 50 && mouseX > this.getX() + 30) {
ItemStack itemStack = ((MerchantOffer)MerchantScreen.this.menu.getOffers().get(this.index + MerchantScreen.this.scrollOff)).getCostB();
if (!itemStack.isEmpty()) {
guiGraphics.renderTooltip(MerchantScreen.this.font, itemStack, mouseX, mouseY);
}
} else if (mouseX > this.getX() + 65) {
ItemStack itemStack = ((MerchantOffer)MerchantScreen.this.menu.getOffers().get(this.index + MerchantScreen.this.scrollOff)).getResult();
guiGraphics.renderTooltip(MerchantScreen.this.font, itemStack, mouseX, mouseY);
}
}
}
}
}