Files
mcpe/source/client/gui/Gui.cpp
2023-12-02 21:04:21 +03:00

555 lines
13 KiB
C++

/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
The following code is licensed under the BSD 1 clause license.
SPDX-License-Identifier: BSD-1-Clause
********************************************************************/
#include "client/app/Minecraft.hpp"
#include "client/gui/screens/IngameBlockSelectionScreen.hpp"
#include "client/gui/screens/ChatScreen.hpp"
#include "client/renderer/entity/ItemRenderer.hpp"
#ifdef _WIN32
#pragma warning(disable : 4244)
#endif
#ifdef ENH_USE_GUI_SCALE_2
float Gui::InvGuiScale = 1.0f / 2.0f;
#else
float Gui::InvGuiScale = 1.0f / 3.0f;
#endif
Gui::Gui(Minecraft* pMinecraft)
{
field_8 = 0;
field_C = "";
field_24 = 0;
field_28 = 0;
field_2C = 0;
field_9FC = 0;
field_A00 = "";
field_A18 = 0;
field_A1C = false;
field_A20 = 1.0f;
field_A3C = true;
m_bRenderMessages = true;
m_pMinecraft = pMinecraft;
xglGenBuffers(1, &m_renderChunk.field_0);
}
void Gui::addMessage(const std::string& s)
{
// if the message contains a new line, add each line separately:
if (s.find("\n") != std::string::npos)
{
std::stringstream ss(s);
std::string line;
while (std::getline(ss, line))
addMessage(line);
return;
}
std::string str = s;
while (m_pMinecraft->m_pFont->width(str) > 320)
{
int i = 2;
for (; i < int(str.size()); i++)
{
std::string sstr = str.substr(0, i);
// this sucks
if (m_pMinecraft->m_pFont->width(sstr) > 320)
break;
}
std::string sstr = str.substr(0, i - 1);
addMessage(sstr);
str = str.substr(i - 1);
}
if (m_guiMessages.size() > 50)
{
m_guiMessages.erase(m_guiMessages.end());
}
m_guiMessages.insert(m_guiMessages.begin(), GuiMessage(str, 0));
}
void Gui::setNowPlaying(const std::string& str)
{
field_A00 = "Now playing: " + str;
field_A18 = 60;
field_A1C = true;
}
void Gui::renderVignette(float a2, int a3, int a4)
{
a2 = 1.0f - a2;
if (a2 > 1.0f)
a2 = 1.0f;
if (a2 < 0.0f)
a2 = 0.0f;
field_A20 += ((a2 - field_A20) * 0.01f);
glDisable(GL_DEPTH_TEST);
glDepthMask(false);
glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
glColor4f(field_A20, field_A20, field_A20, 1.0f);
//! @BUG: No misc/vignette.png to be found in the original.
//! This function is unused anyways
m_pMinecraft->m_pTextures->loadAndBindTexture("misc/vignette.png");
Tesselator& t = Tesselator::instance;
t.begin();
t.vertexUV(0.0f, a4, -90.0f, 0.0f, 1.0f);
t.vertexUV(a3, a4, -90.0f, 1.0f, 1.0f);
t.vertexUV(a3, 0.0f, -90.0f, 1.0f, 0.0f);
t.vertexUV(0.0f, 0.0f, -90.0f, 0.0f, 0.0f);
t.draw();
glDepthMask(true);
glEnable(GL_DEPTH_TEST);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
void Gui::inventoryUpdated()
{
field_A3C = true;
}
void Gui::render(float f, bool bHaveScreen, int mouseX, int mouseY)
{
Minecraft* m = m_pMinecraft;
m->m_pGameRenderer->setupGuiScreen();
if (!m->m_pLevel || !m->m_pLocalPlayer)
return;
field_4 = -90.0f;
#ifndef ENH_TRANSPARENT_HOTBAR
glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
#else
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
#endif
m->m_pTextures->loadAndBindTexture("gui/gui.png");
Inventory* pInventory = m->m_pLocalPlayer->m_pInventory;
field_4 = -90.0f;
int width = Minecraft::width * InvGuiScale,
height = Minecraft::height * InvGuiScale;
#ifdef ENH_TRANSPARENT_HOTBAR
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
#endif
int nSlots = getNumSlots();
int hotbarWidth = 2 + nSlots * 20;
// hotbar
int cenX = width / 2;
blit(cenX - hotbarWidth / 2, height - 22, 0, 0, hotbarWidth, 22, 0, 0);
// selection mark
blit(cenX - 1 - hotbarWidth / 2 + 20 * pInventory->m_SelectedHotbarSlot, height - 23, 0, 22, 24, 22, 0, 0);
m->m_pTextures->loadAndBindTexture("gui/icons.png");
if (m->useSplitControls())
{
#ifndef ENH_TRANSPARENT_HOTBAR
glEnable(GL_BLEND);
#endif
// draw crosshair
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR);
blit(cenX - 8, height / 2 - 8, 0, 0, 16, 16, 0, 0);
#ifndef ENH_TRANSPARENT_HOTBAR
glDisable(GL_BLEND);
#endif
}
else
{
// if needed, draw feedback
// NOTE: real Minecraft PE takes it directly from the gamemode as "current progress" and
// "last progress". Well guess what? The game mode in question updates our field_8 with
// the pre-interpolated break progress! Isn't that awesome?!
float breakProgress = field_8;
// don't know about this if-structure, it feels like it'd be like
// if (field_C >= 0.0f && breakProgress <= 0.0f)
// that;
// else
// this;
if (breakProgress > 0.0f || m_pMinecraft->m_pInputHolder->m_feedbackAlpha < 0.0f)
{
if (breakProgress > 0.0f)
{
float xPos = m_pMinecraft->m_pInputHolder->m_feedbackX;
float yPos = m_pMinecraft->m_pInputHolder->m_feedbackY;
m_pMinecraft->m_pTextures->loadAndBindTexture("gui/feedback_outer.png");
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
blit(InvGuiScale * xPos - 44.0f, InvGuiScale * yPos - 44.0f, 0, 0, 88, 88, 256, 256);
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR);
m_pMinecraft->m_pTextures->loadAndBindTexture("gui/feedback_fill.png");
// note: scale starts from 4.0f
float halfWidth = (40.0f * breakProgress + 48.0f) / 2.0f;
blit(InvGuiScale * xPos - halfWidth, InvGuiScale * yPos - halfWidth, 0, 0, halfWidth * 2, halfWidth * 2, 256, 256);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glDisable(GL_BLEND);
}
}
else
{
float xPos = m_pMinecraft->m_pInputHolder->m_feedbackX;
float yPos = m_pMinecraft->m_pInputHolder->m_feedbackY;
m_pMinecraft->m_pTextures->loadAndBindTexture("gui/feedback_outer.png");
glColor4f(1.0f, 1.0f, 1.0f, Mth::Min(1.0f, m_pMinecraft->m_pInputHolder->m_feedbackAlpha));
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
blit(InvGuiScale * xPos - 44.0f, InvGuiScale * yPos - 44.0f, 0, 0, 88, 88, 256, 256);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glDisable(GL_BLEND);
}
}
#ifdef ENH_TRANSPARENT_HOTBAR
glDisable(GL_BLEND);
#endif
if (m_pMinecraft->m_pGameMode->canHurtPlayer())
{
LocalPlayer* pLP = m_pMinecraft->m_pLocalPlayer;
// why??
m_random.init_genrand(312871 * field_9FC);
int emptyHeartX = 16;
bool b1 = false;
if (pLP->field_B8 < 10)
{
b1 = pLP->field_B8 / 3 % 2;
emptyHeartX += 9 * b1;
}
#ifdef ORIGINAL_CODE
// @NOTE: At the default scale, this would go off screen.
int heartX = cenX - 191; // why?
int heartYStart = height - 10;
#else
//@NOTE: Alpha-style health UI. I'll probably remove this on release.
int heartX = cenX - 91;
int heartYStart = height - 32;
#endif
int playerHealth = pLP->m_health;
for (int healthNo = 1; healthNo <= C_MAX_MOB_HEALTH; healthNo += 2)
{
int heartY = heartYStart;
if (playerHealth <= 4 && m_random.genrand_int32() % 2)
heartY++;
blit(heartX, heartY, emptyHeartX, 0, 9, 9, 0, 0);
if (b1)
{
if (healthNo < pLP->field_100)
blit(heartX, heartY, 70, 0, 9, 9, 0, 0);
else if (healthNo == pLP->field_100)
blit(heartX, heartY, 79, 0, 9, 9, 0, 0);
}
if (healthNo < playerHealth)
blit(heartX, heartY, 52, 0, 9, 9, 0, 0);
else if (healthNo == playerHealth)
blit(heartX, heartY, 61, 0, 9, 9, 0, 0);
heartX += 8;
}
if (m->m_pLocalPlayer->isUnderLiquid(Material::water))
{
int breathRaw = m->m_pLocalPlayer->field_BC;
int breathFull = int(ceilf((float(breathRaw - 2) * 10.0f) / 300.0f));
int breathMeter = int(ceilf((float(breathRaw) * 10.0f) / 300.0f)) - breathFull;
#ifdef ORIGINAL_CODE
int bubbleX = cenX - 191;
int bubbleY = height - 19;
#else
int bubbleX = cenX - 91;
int bubbleY = height - 41;
#endif
//@NOTE: Not sure this works as it should
for (int bubbleNo = 0; bubbleNo < breathFull + breathMeter; bubbleNo++)
{
if (bubbleNo < breathFull)
blit(bubbleX, bubbleY, 16, 18, 9, 9, 0, 0);
else
blit(bubbleX, bubbleY, 25, 18, 9, 9, 0, 0);
bubbleX += 8;
}
}
}
m->m_pTextures->loadAndBindTexture("gui/gui_blocks.png");
int diff = m->isTouchscreen();
int slotX = cenX - hotbarWidth / 2 + 3;
for (int i = 0; i < nSlots - diff; i++)
{
renderSlot(i, slotX, height - 19, f);
slotX += 20;
}
slotX = cenX - hotbarWidth / 2 + 3;
for (int i = 0; i < nSlots - diff; i++)
{
renderSlotOverlay(i, slotX, height - 19, f);
slotX += 20;
}
#undef DIFF
field_A3C = false;
// blit the "more items" button
if (m->isTouchscreen())
{
m->m_pTextures->loadAndBindTexture(C_TERRAIN_NAME);
blit(cenX + hotbarWidth / 2 - 19, height - 19, 208, 208, 16, 16, 0, 0);
}
// render messages
if (m_bRenderMessages)
{
renderMessages(false);
}
}
void Gui::tick()
{
if (field_A18 > 0)
field_A18--;
field_9FC++;
for (int i = 0; i < int(m_guiMessages.size()); i++)
{
GuiMessage& msg = m_guiMessages[i];
msg.field_18++;
}
}
void Gui::renderSlot(int slot, int x, int y, float f)
{
Inventory* pInv = m_pMinecraft->m_pLocalPlayer->m_pInventory;
ItemInstance* pInst = pInv->getQuickSlotItem(slot);
if (!pInst)
return;
if (!pInst->m_itemID)
return;
ItemRenderer::renderGuiItem(m_pMinecraft->m_pFont, m_pMinecraft->m_pTextures, pInst, x, y, true);
}
void Gui::renderSlotOverlay(int slot, int x, int y, float f)
{
Inventory* pInv = m_pMinecraft->m_pLocalPlayer->m_pInventory;
ItemInstance* pInst = pInv->getQuickSlotItem(slot);
if (!pInst)
return;
if (!pInst->m_itemID)
return;
ItemRenderer::renderGuiItemOverlay(m_pMinecraft->m_pFont, m_pMinecraft->m_pTextures, pInst, x, y);
}
int Gui::getSlotIdAt(int mouseX, int mouseY)
{
int scaledY = int(InvGuiScale * mouseY);
int scaledHeight = int(InvGuiScale * Minecraft::height);
if (scaledY >= scaledHeight)
return -1;
if (scaledY < scaledHeight - 19)
return -1;
int hotbarOffset = getNumSlots() * 20 / 2 - 2;
int slotX = (int(InvGuiScale * mouseX) - int(InvGuiScale * Minecraft::width) / 2 + hotbarOffset + 20) / 20;
if (slotX >= 0)
slotX--;
if (slotX >= getNumSlots())
slotX = -1;
return slotX;
}
bool Gui::isInside(int mouseX, int mouseY)
{
return getSlotIdAt(mouseX, mouseY) != -1;
}
void Gui::handleClick(int clickID, int mouseX, int mouseY)
{
if (clickID != 1)
return;
int slot = getSlotIdAt(mouseX, mouseY);
if (slot == -1)
return;
if (m_pMinecraft->isTouchscreen() && slot == getNumSlots() - 1)
m_pMinecraft->setScreen(new IngameBlockSelectionScreen);
else
m_pMinecraft->m_pLocalPlayer->m_pInventory->selectSlot(slot);
}
void Gui::handleKeyPressed(int keyCode)
{
if (m_pMinecraft->getOptions()->isKey(KM_INVENTORY, keyCode))
{
m_pMinecraft->setScreen(new IngameBlockSelectionScreen);
return;
}
int maxItems = getNumSlots() - 1;
if (m_pMinecraft->isTouchscreen())
maxItems--;
if (m_pMinecraft->getOptions()->isKey(KM_SLOT_R, keyCode))
{
int* slot = &m_pMinecraft->m_pLocalPlayer->m_pInventory->m_SelectedHotbarSlot;
if (*slot <= maxItems)
(*slot)++;
return;
}
if (m_pMinecraft->getOptions()->isKey(KM_SLOT_L, keyCode))
{
int* slot = &m_pMinecraft->m_pLocalPlayer->m_pInventory->m_SelectedHotbarSlot;
if (*slot > 0)
(*slot)--;
return;
}
if (m_pMinecraft->getOptions()->isKey(KM_CHAT_CMD, keyCode) || m_pMinecraft->getOptions()->isKey(KM_CHAT, keyCode))
{
if (m_pMinecraft->m_pScreen)
return;
m_pMinecraft->setScreen(new ChatScreen(m_pMinecraft->getOptions()->isKey(KM_CHAT_CMD, keyCode)));
return;
}
}
void Gui::renderMessages(bool bShowAll)
{
//int width = Minecraft::width * InvGuiScale,
int height = Minecraft::height * InvGuiScale;
int topEdge = height - 49;
for (int i = 0; i < int(m_guiMessages.size()); i++)
{
GuiMessage& msg = m_guiMessages[i];
if (!bShowAll && msg.field_18 > 199)
continue;
int bkgdColor = 0x7F000000, textColor = 0xFFFFFFFF;
float fade = 1.0f;
if (!bShowAll)
{
fade = 10.0f * (1.0f - (float(msg.field_18) / 200.0f));
if (fade <= 0.0f)
continue;
if (fade < 1.0f)
{
int x = int(fade * fade * 255.0f);
if (x == 0)
continue;
bkgdColor = (x / 2) << 24;
textColor = (x << 24) + 0xFFFFFF;
}
}
fill(2, topEdge, 322, topEdge + 9, bkgdColor);
glEnable(GL_BLEND);
m_pMinecraft->m_pFont->drawShadow(msg.msg, 2, topEdge + 1, textColor);
topEdge -= 9;
}
glDisable(GL_BLEND);
}
int Gui::getNumSlots()
{
if (m_pMinecraft->isTouchscreen())
return 4;
return 9;
}
int Gui::getNumUsableSlots()
{
return getNumSlots() - m_pMinecraft->isTouchscreen();
}
RectangleArea Gui::getRectangleArea(bool b)
{
float centerX = Minecraft::width / 2;
float hotbarWidthHalf = (10 * getNumSlots() + 5) / InvGuiScale;
return RectangleArea(
b ? (centerX - hotbarWidthHalf) : 0,
Minecraft::height - 24.0f / InvGuiScale,
centerX + hotbarWidthHalf,
Minecraft::height);
}