From 97c6704d82cdb61e512fbcbf1534ab3425cc1c2e Mon Sep 17 00:00:00 2001 From: Sally Knight Date: Sun, 29 Mar 2026 05:15:30 +0300 Subject: [PATCH] feat(jui): add merchant screen --- .../Common/res/1_2_2/gui/villager.png | Bin 0 -> 3130 bytes Minecraft.Client/Network/ClientConnection.cpp | 14 +- Minecraft.Client/Player/LocalPlayer.cpp | 7 +- Minecraft.Client/Textures/Textures.cpp | 1 + Minecraft.Client/Textures/Textures.h | 1 + .../UI/Screens/MerchantScreen.cpp | 205 ++++++++++++++++++ Minecraft.Client/UI/Screens/MerchantScreen.h | 32 +++ Minecraft.Client/UI/TradeSwitchButton.cpp | 46 ++++ Minecraft.Client/UI/TradeSwitchButton.h | 14 ++ 9 files changed, 318 insertions(+), 2 deletions(-) create mode 100644 Minecraft.Assets/Common/res/1_2_2/gui/villager.png create mode 100644 Minecraft.Client/UI/Screens/MerchantScreen.cpp create mode 100644 Minecraft.Client/UI/Screens/MerchantScreen.h create mode 100644 Minecraft.Client/UI/TradeSwitchButton.cpp create mode 100644 Minecraft.Client/UI/TradeSwitchButton.h diff --git a/Minecraft.Assets/Common/res/1_2_2/gui/villager.png b/Minecraft.Assets/Common/res/1_2_2/gui/villager.png new file mode 100644 index 0000000000000000000000000000000000000000..3211a7a00b530648de14bd794a308a2a0f560156 GIT binary patch literal 3130 zcmd5;XH=8P8lHp*0YO-irXoRUHV8$@N()V-D@A${MPLEF^d=B+5d7YDFK2~r8jAY6mpY2yLJ?H+t-#2sSJI}n|Gw=JHXXeb^i&iE=0tW>E z079l0jBEgaa6Tab&dV8GgUhaS23{|76C-em``$YmIl)2r|8>DB1b}_7xg8>=WQGA? z?}(|9zFp+V@)x%hyIJXu?_)XYAA%J8AIs018{B1YE4Ksd3mJGb(E zB%?UL9W;hVstD+cK z-S><5q(r&S2GLBpo|E(Iifm^c2P*}ZUK+XBm6A1udVfuAU0bEU=f`x(VrWo|vW>pS zX#T(hZ1uor!fHD`TRw8c^Zv{moOkDj_GxA11?o+bebL2>HI9{0F}BRi%{6BeVM3Tj zuI0(F)ukiTQTc3dBA|pFlVI1acF!+RBA6oZjo!Ny)+Ban$r^YfqVk_be2p^6)KF6^ zKI1=F`J$=w+84&l9sDd4Nn2%)rd}+&PHhNZ8Y;3o@vVRrNX=kw81pBL=df2+40H4H zLiLDNxJ8nIev*M~Xx8WpmICaEdvoxNN2?}5IM~g>+}u2~G*2sn9*-S?81W}8 zObS~C!a*UwNyg#t&51@wFFm`eWFHEhS@Q4~7)NE0))Y-+wwHdJLIt}~Jm3L}um!RB zQQ7;`<%#-R3l)Tg&G~^7S!PAjn40BBSYP_>c#1TE*sFQ-Yh|5J&tOZeZ{Lb;X~mBj z`n^;=)K?k}B@KGtb;!h5s{4GrVDrVeZGoCBMw*AF<27&I5syC*5h1g#m2jRcXyVD+?WMzB{E-3e6w zP$F#}ATJ6eC0gZcmy^lHS!VLyYEn-I=866w-(fwCLx7vj+UzzcnmJk+Nacv3vK!pA zqyjx#L8Lu-pY+N(4}F+y&4)<{o_Z@3-&$byBy@4Gibz&5-PkCwe>wd~;AebH{%par zW!Fu2@qn2Yjr!EFG75jYJu?(YD%~|Q=)7jbX@ zPxNAC(cF-Z$2+&(^oMYspUEbe6Dkp|LoQHkJIdj+V62+|bBF>*t#R`>j#?xg$ClHta5&sFD#NDyq1^n^a9wR+^JMn&t^S6n z2Uc4J^*0QGpl{8fR+laFUYNf*QvC_iB{2u>>rgdGP^MjXVDN*FOv5v5O|oD0;9iDn z+d{EN_?KQ0q{FUQnz0kkK4goOH!0{|Hc%yk07|;-tMbE0ranlw-&ouXVP8_kca;O+7n0D+OJ0c!u;7U@G^a-A~k;1 z?n?NEBMkJyNC^4kP3J{P(N23Qx}RNcuB_6ei1?190K@AuEurAPxi@%1MncYXc@uCcTlqX01+Nr%TX>x83^t-A=piJlhS| z9UH#<65rt^fQ%eslj@WocI}eS;!l}Nh%3*J){L3kbo8`;(H4iG;f2GmUFkM>HLSxe zQUR8`=X@uYv@dl0A|V0Yii8&)FQO)d)ge?iI~%TX4gk!N;8k@{d0kJsY}i^*4KFMT zeg$`4(42VYN{YQL3+uN@UDTxr1W#pZLGxgb%vOvA1fEr-^MF^^lCn@js4n4|JI2{i zKI}H9-bqPVWPa#boU?%w>AXNs&HEnXfGi;O? zR`K-dQ{EZW0X;ZIp@8{i=vs+O2P7K`5CDh(;K+kM932%T3IkqmCl&sN;Q+t@fO7A0 zf-Sv^;M|t#h`PFZ`Ww_~>>DjPnTsIX1Fgkj)=*b}gGwJ*N4bu+AK->_?s91GC;2~E z{)VBLWbUkBQ7?8)?7+bkg5 zYT!W7je-4m!L0UF*NL9)?(gGJ!x4yMlWT?vPweeIZ6C|xK$o`M+`z0d_`i5~#@U%u VDd@O69>UywOpUFKstr67{{!+^XTJad literal 0 HcmV?d00001 diff --git a/Minecraft.Client/Network/ClientConnection.cpp b/Minecraft.Client/Network/ClientConnection.cpp index e41f43d77..78cc2c789 100644 --- a/Minecraft.Client/Network/ClientConnection.cpp +++ b/Minecraft.Client/Network/ClientConnection.cpp @@ -46,6 +46,7 @@ #include "../ClientConstants.h" #include "../../Minecraft.World/Util/SoundTypes.h" #include "../Textures/Packs/TexturePackRepository.h" +#include "UI/Screens/MerchantScreen.h" #ifdef _XBOX #include "../Platform/Common/XUI/XUI_Scene_Trading.h" #else @@ -3707,6 +3708,17 @@ void ClientConnection::handleCustomPayload( ByteArrayInputStream bais(customPayloadPacket->data); DataInputStream input(&bais); int containerId = input.readInt(); +#ifdef ENABLE_JAVA_GUIS + // 4jcraft: use the java gui getMerchant() to get trader as we don't + // have iggy's screen anymore + if (minecraft->screen && + dynamic_cast(minecraft->screen) && + containerId == minecraft->localplayers[m_userIndex] + ->containerMenu->containerId) { + std::shared_ptr trader = nullptr; + MerchantScreen* screen = (MerchantScreen*)minecraft->screen; + trader = screen->getMerchant(); +#else if (ui.IsSceneInStack(m_userIndex, eUIScene_TradingMenu) && containerId == minecraft->localplayers[m_userIndex] ->containerMenu->containerId) { @@ -3729,7 +3741,7 @@ void ClientConnection::handleCustomPayload( UIScene_TradingMenu* screen = (UIScene_TradingMenu*)scene; trader = screen->getMerchant(); #endif - +#endif MerchantRecipeList* recipeList = MerchantRecipeList::createFromStream(&input); trader->overrideOffers(recipeList); diff --git a/Minecraft.Client/Player/LocalPlayer.cpp b/Minecraft.Client/Player/LocalPlayer.cpp index a74161798..79003a286 100644 --- a/Minecraft.Client/Player/LocalPlayer.cpp +++ b/Minecraft.Client/Player/LocalPlayer.cpp @@ -4,6 +4,7 @@ #include "UI/Screens/EnchantmentScreen.h" #include "UI/Screens/HopperScreen.h" #include "UI/Screens/HorseInventoryScreen.h" +#include "UI/Screens/MerchantScreen.h" #include "UI/Screens/RepairScreen.h" #include "User.h" #include "../Input/Input.h" @@ -714,10 +715,14 @@ bool LocalPlayer::openTrap(std::shared_ptr trap) { bool LocalPlayer::openTrading(std::shared_ptr traderTarget, const std::wstring& name) { +#ifdef ENABLE_JAVA_GUIS + minecraft->setScreen(new MerchantScreen(inventory, traderTarget, level)); + bool success = true; +#else bool success = app.LoadTradingMenu(GetXboxPad(), inventory, traderTarget, level, name); if (success) ui.PlayUISFX(eSFX_Press); - // minecraft.setScreen(new MerchantScreen(inventory, traderTarget, level)); +#endif return success; } diff --git a/Minecraft.Client/Textures/Textures.cpp b/Minecraft.Client/Textures/Textures.cpp index deef91211..bbd96f14a 100644 --- a/Minecraft.Client/Textures/Textures.cpp +++ b/Minecraft.Client/Textures/Textures.cpp @@ -182,6 +182,7 @@ const wchar_t* Textures::preLoaded[TN_COUNT] = { L"gui/trap", L"gui/hopper", L"gui/enchant", + L"gui/villager", L"gui/brewing_stand", L"title/bg/panorama", L"title/bg/panorama0", diff --git a/Minecraft.Client/Textures/Textures.h b/Minecraft.Client/Textures/Textures.h index b073a77de..3edec4b5d 100644 --- a/Minecraft.Client/Textures/Textures.h +++ b/Minecraft.Client/Textures/Textures.h @@ -164,6 +164,7 @@ typedef enum _TEXTURE_NAME { TN_GUI_TRAP, TN_GUI_HOPPER, TN_GUI_ENCHANT, + TN_GUI_VILLAGER, TN_GUI_BREWING_STAND, TN_TITLE_BG_PANORAMA, TN_TITLE_BG_PANORAMA0, diff --git a/Minecraft.Client/UI/Screens/MerchantScreen.cpp b/Minecraft.Client/UI/Screens/MerchantScreen.cpp new file mode 100644 index 000000000..b069e89e8 --- /dev/null +++ b/Minecraft.Client/UI/Screens/MerchantScreen.cpp @@ -0,0 +1,205 @@ +#include "../../Platform/stdafx.h" +#include "MerchantScreen.h" +#include +#include +#include "../TradeSwitchButton.h" +#include "../../Player/MultiPlayerLocalPlayer.h" +#include "../../Rendering/Lighting.h" +#include "../../Textures/Textures.h" +#include "../../Rendering/EntityRenderers/ItemRenderer.h" +#include "../../../Minecraft.World/Headers/net.minecraft.locale.h" +#include "../../../Minecraft.World/Containers/MerchantMenu.h" +#include "../../../Minecraft.World/Containers/Slot.h" +#include "../../../Minecraft.World/Containers/MerchantContainer.h" +#include "../../../Minecraft.World/Headers/net.minecraft.world.item.trading.h" +#include "../../../Minecraft.Client/Minecraft.h" +#include "../../../Minecraft.Client/Network/ClientConnection.h" +#include "../../../Minecraft.World/IO/Streams/ByteArrayOutputStream.h" +#include "../../../Minecraft.World/IO/Streams/DataOutputStream.h" +#include "../../../Minecraft.World/Util/Rarity.h" + +// 4jcraft: referenced from MCP 8.11 (JE 1.6.4) and the existing +// container classes (and iggy too) +#ifdef ENABLE_JAVA_GUIS +ResourceLocation GUI_VILLAGER_LOCATION = ResourceLocation(TN_GUI_VILLAGER); +#endif + +MerchantScreen::MerchantScreen(std::shared_ptr inventory, + std::shared_ptr merchant, Level* level) + : AbstractContainerScreen(new MerchantMenu(inventory, merchant, level)) { + this->inventory = inventory; + this->merchantMenu = static_cast(menu); + this->merchant = merchant; + this->currentRecipeIndex = 0; + this->nextRecipeButton = nullptr; + this->prevRecipeButton = nullptr; +} + +MerchantScreen::~MerchantScreen() = default; + +void MerchantScreen::init() { + AbstractContainerScreen::init(); + + int xo = (width - imageWidth) / 2; + int yo = (height - imageHeight) / 2; + + nextRecipeButton = new TradeSwitchButton(1, xo + 120 + 27, yo + 24 - 1, true); + prevRecipeButton = new TradeSwitchButton(2, xo + 36 - 19, yo + 24 - 1, false); + + nextRecipeButton->active = false; + prevRecipeButton->active = false; + + buttons.push_back(nextRecipeButton); + buttons.push_back(prevRecipeButton); +} + +void MerchantScreen::removed() { AbstractContainerScreen::removed(); } + +void MerchantScreen::renderLabels() { + font->draw(merchant->getDisplayName(), + (imageWidth / 2) - (font->width(merchant->getDisplayName()) / 2), + 6, 0x404040); + + font->draw(inventory->getName(), 8, imageHeight - 96 + 2, 0x404040); +} + +void MerchantScreen::renderBg(float a) { +#ifdef ENABLE_JAVA_GUIS + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + minecraft->textures->bindTexture(&GUI_VILLAGER_LOCATION); + int xo = (width - imageWidth) / 2; + int yo = (height - imageHeight) / 2; + blit(xo, yo, 0, 0, imageWidth, imageHeight); + + MerchantRecipe* activeRecipe = + merchantMenu->getTradeContainer()->getActiveRecipe(); + if (activeRecipe != nullptr && !activeRecipe->isDeprecated()) { + blit(xo + 83, yo + 21, 212, 0, 28, 21); + blit(xo + 83, yo + 51, 212, 0, 28, 21); + } +#endif +} + +void MerchantScreen::render(int xm, int ym, float a) { + AbstractContainerScreen::render(xm, ym, a); + +#ifdef ENABLE_JAVA_GUIS + std::shared_ptr player = std::dynamic_pointer_cast( + inventory->player->shared_from_this()); + MerchantRecipeList* offers = merchant->getOffers(player); + + if (offers != nullptr && !offers->empty()) { + int xo = (width - imageWidth) / 2; + int yo = (height - imageHeight) / 2; + + MerchantRecipe* recipe = offers->at(currentRecipeIndex); + if (recipe != nullptr && !recipe->isDeprecated()) { + std::shared_ptr buyItem1 = recipe->getBuyAItem(); + std::shared_ptr buyItem2 = recipe->getBuyBItem(); + std::shared_ptr sellItem = recipe->getSellItem(); + + glPushMatrix(); + glTranslatef((float)xo, (float)yo, 0.0f); + + Lighting::turnOnGui(); + glEnable(GL_RESCALE_NORMAL); + glEnable(GL_LIGHTING); + + if (buyItem1 != nullptr) { + itemRenderer->renderGuiItem(font, minecraft->textures, buyItem1, + 36, 24); + itemRenderer->renderGuiItemDecorations( + font, minecraft->textures, buyItem1, 36, 24); + } + + if (buyItem2 != nullptr) { + itemRenderer->renderGuiItem(font, minecraft->textures, buyItem2, + 62, 24); + itemRenderer->renderGuiItemDecorations( + font, minecraft->textures, buyItem2, 62, 24); + } + + if (sellItem != nullptr) { + itemRenderer->renderGuiItem(font, minecraft->textures, sellItem, + 120, 24); + itemRenderer->renderGuiItemDecorations( + font, minecraft->textures, sellItem, 120, 24); + } + + glDisable(GL_LIGHTING); + glDisable(GL_RESCALE_NORMAL); + Lighting::turnOff(); + + glPopMatrix(); + + if (buyItem1 != nullptr && isHoveringOver(36, 24, 16, 16, xm, ym)) { + renderTooltip(buyItem1, xm, ym); + } else if (buyItem2 != nullptr && + isHoveringOver(62, 24, 16, 16, xm, ym)) { + renderTooltip(buyItem2, xm, ym); + } else if (sellItem != nullptr && + isHoveringOver(120, 24, 16, 16, xm, ym)) { + renderTooltip(sellItem, xm, ym); + } + } + } +#endif +} + +void MerchantScreen::tick() { + AbstractContainerScreen::tick(); + + std::shared_ptr player = std::dynamic_pointer_cast( + inventory->player->shared_from_this()); + + MerchantRecipeList* offers = merchant->getOffers(player); + + if (offers != nullptr) { + int offerCount = (int)offers->size(); + + nextRecipeButton->active = (currentRecipeIndex < offerCount - 1); + prevRecipeButton->active = (currentRecipeIndex > 0); + + if (currentRecipeIndex >= offerCount && offerCount > 0) { + currentRecipeIndex = offerCount - 1; + merchantMenu->setSelectionHint(currentRecipeIndex); + + // 4jcraft: taken from IUIScene_TradingMenu + ByteArrayOutputStream rawOutput; + DataOutputStream output(&rawOutput); + output.writeInt(currentRecipeIndex); + minecraft->player->connection->send( + std::shared_ptr(new CustomPayloadPacket( + CustomPayloadPacket::TRADER_SELECTION_PACKET, + rawOutput.toByteArray()))); + } + } else { + nextRecipeButton->active = false; + prevRecipeButton->active = false; + } +} + +void MerchantScreen::buttonClicked(Button* button) { + bool changed = false; + + if (button == nextRecipeButton) { + ++currentRecipeIndex; + changed = true; + } else if (button == prevRecipeButton) { + --currentRecipeIndex; + changed = true; + } + + if (changed) { + merchantMenu->setSelectionHint(currentRecipeIndex); + + // 4jcraft: taken from IUIScene_TradingMenu + ByteArrayOutputStream rawOutput; + DataOutputStream output(&rawOutput); + output.writeInt(currentRecipeIndex); + minecraft->player->connection->send( + std::shared_ptr(new CustomPayloadPacket( + CustomPayloadPacket::TRADER_SELECTION_PACKET, + rawOutput.toByteArray()))); + } +} \ No newline at end of file diff --git a/Minecraft.Client/UI/Screens/MerchantScreen.h b/Minecraft.Client/UI/Screens/MerchantScreen.h new file mode 100644 index 000000000..4a40bbb72 --- /dev/null +++ b/Minecraft.Client/UI/Screens/MerchantScreen.h @@ -0,0 +1,32 @@ +#pragma once + +#include "AbstractContainerScreen.h" +#include "../../../Minecraft.World/Containers/MerchantMenu.h" +#include "../../../Minecraft.World/Headers/net.minecraft.world.item.trading.h" + +class TradeSwitchButton; + +class MerchantScreen : public AbstractContainerScreen { +public: + MerchantScreen(std::shared_ptr inventory, + std::shared_ptr merchant, Level* level); + virtual ~MerchantScreen(); + + void init() override; + void removed() override; + void renderLabels() override; + void renderBg(float a) override; + void render(int xm, int ym, float a) override; + void tick() override; + void buttonClicked(Button* button) override; + + std::shared_ptr getMerchant() { return merchant; } + +private: + std::shared_ptr inventory; + std::shared_ptr merchant; + MerchantMenu* merchantMenu; + TradeSwitchButton* nextRecipeButton; + TradeSwitchButton* prevRecipeButton; + int currentRecipeIndex; +}; \ No newline at end of file diff --git a/Minecraft.Client/UI/TradeSwitchButton.cpp b/Minecraft.Client/UI/TradeSwitchButton.cpp new file mode 100644 index 000000000..860a2c43f --- /dev/null +++ b/Minecraft.Client/UI/TradeSwitchButton.cpp @@ -0,0 +1,46 @@ +#include "../../Platform/stdafx.h" +#include "TradeSwitchButton.h" +#include "../Textures/Textures.h" +#include "../Rendering/Tesselator.h" +#include "../../../Minecraft.Client/Minecraft.h" +#include "../../../Minecraft.World/Headers/net.minecraft.locale.h" +#include "../../../Minecraft.World/Containers/MerchantMenu.h" + +// 4jcraft: referenced from MCP 8.11 (JE 1.6.4) +#ifdef ENABLE_JAVA_GUIS +// ResourceLocation GUI_VILLAGER_LOCATION = ResourceLocation(TN_GUI_VILLAGER); +extern ResourceLocation GUI_VILLAGER_LOCATION; +#endif + +TradeSwitchButton::TradeSwitchButton(int id, int x, int y, bool mirrored) + : Button(id, x, y, 12, 19, L"") { + this->mirrored = mirrored; +} + +int TradeSwitchButton::getYImage(bool hovered) { return 0; } + +void TradeSwitchButton::renderBg(Minecraft* minecraft, int xm, int ym) { +#ifdef ENABLE_JAVA_GUIS + if (!visible) return; + + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + minecraft->textures->bindTexture(&GUI_VILLAGER_LOCATION); + + bool hovered = (xm >= x && ym >= y && xm < x + w && ym < y + h); + + int textureX = 176; + int textureY = 0; + + if (!active) { + textureX += w * 2; + } else if (hovered) { + textureX += w; + } + + if (!mirrored) { + textureY += h; + } + + blit(x, y, textureX, textureY, w, h); +#endif +} \ No newline at end of file diff --git a/Minecraft.Client/UI/TradeSwitchButton.h b/Minecraft.Client/UI/TradeSwitchButton.h new file mode 100644 index 000000000..f5e8e0f0b --- /dev/null +++ b/Minecraft.Client/UI/TradeSwitchButton.h @@ -0,0 +1,14 @@ +#pragma once +#include "Button.h" + +class TradeSwitchButton : public Button { +private: + bool mirrored; + +public: + TradeSwitchButton(int id, int x, int y, bool mirrored); + +protected: + int getYImage(bool hovered) override; + void renderBg(Minecraft* minecraft, int xm, int ym) override; +}; \ No newline at end of file