#include "Gui.h" #include "Font.h" #include "client/Options.h" #include "platform/input/Keyboard.h" #include "screens/IngameBlockSelectionScreen.h" #include "../Minecraft.h" #include "../player/LocalPlayer.h" #include "../renderer/Tesselator.h" #include "../renderer/TileRenderer.h" #include "../renderer/LevelRenderer.h" #include "../renderer/GameRenderer.h" #include "../renderer/entity/ItemRenderer.h" #include "../player/input/IInputHolder.h" #include "../gamemode/GameMode.h" #include "../gamemode/CreativeMode.h" #include "../renderer/Textures.h" #include "../../AppConstants.h" #include "../../world/entity/player/Inventory.h" #include "../../world/level/material/Material.h" #include "../../world/item/Item.h" #include "../../world/item/ItemInstance.h" #include "../../platform/input/Mouse.h" #include "../../world/level/Level.h" #include "../../world/PosTranslator.h" #include "../../platform/time.h" #include float Gui::InvGuiScale = 1.0f / 3.0f; float Gui::GuiScale = 1.0f / Gui::InvGuiScale; const float Gui::DropTicks = 40.0f; //#include Gui::Gui(Minecraft* minecraft) : minecraft(minecraft), tickCount(0), progress(0), overlayMessageTime(0), animateOverlayMessageColor(false), tbr(1), _inventoryNeedsUpdate(true), _flashSlotId(-1), _flashSlotStartTime(-1), _slotFont(NULL), _numSlots(4), _currentDropTicks(-1), _currentDropSlot(-1), MAX_MESSAGE_WIDTH(240), itemNameOverlayTime(2) { glGenBuffers2(1, &_inventoryRc.vboId); glGenBuffers2(1, &rcFeedbackInner.vboId); glGenBuffers2(1, &rcFeedbackOuter.vboId); //Gui::InvGuiScale = 1.0f / (int) (3 * Minecraft::width / 854); } Gui::~Gui() { if (_slotFont) delete _slotFont; glDeleteBuffers(1, &_inventoryRc.vboId); } void Gui::render(float a, bool mouseFree, int xMouse, int yMouse) { if (!minecraft->level || !minecraft->player) return; //minecraft->gameRenderer->setupGuiScreen(); Font* font = minecraft->font; #ifdef PLATFORM_DESKTOP const bool isTouchInterface = false; #else const bool isTouchInterface = minecraft->useTouchscreen(); #endif const int screenWidth = (int)(minecraft->width * InvGuiScale); const int screenHeight = (int)(minecraft->height * InvGuiScale); blitOffset = -90; renderProgressIndicator(isTouchInterface, screenWidth, screenHeight, a); glColor4f2(1, 1, 1, 1); // H: 4 // T: 7 // L: 6 // F: 3 int ySlot = screenHeight - 16 - 3; if (!minecraft->options.getBooleanValue(OPTIONS_HIDEGUI)) { if (minecraft->gameMode->canHurtPlayer()) { minecraft->textures->loadAndBindTexture("gui/icons.png"); Tesselator& t = Tesselator::instance; t.beginOverride(); t.colorABGR(0xffffffff); renderHearts(); renderBubbles(); t.endOverrideAndDraw(); } } if(minecraft->player->getSleepTimer() > 0) { glDisable(GL_DEPTH_TEST); glDisable(GL_ALPHA_TEST); renderSleepAnimation(screenWidth, screenHeight); glEnable(GL_ALPHA_TEST); glEnable(GL_DEPTH_TEST); } if (!minecraft->options.getBooleanValue(OPTIONS_HIDEGUI)) { renderToolBar(a, ySlot, screenWidth); glEnable(GL_BLEND); unsigned int max = 10; bool isChatting = false; renderChatMessages(screenHeight, max, isChatting, font); #if !defined(RPI) renderOnSelectItemNameText(screenWidth, font, ySlot); #endif #if defined(RPI) renderDebugInfo(); #elif defined(PLATFORM_DESKTOP) if (minecraft->options.getBooleanValue(OPTIONS_RENDER_DEBUG)) renderDebugInfo(); #endif } glDisable(GL_BLEND); glEnable2(GL_ALPHA_TEST); } int Gui::getSlotIdAt(int x, int y) { int screenWidth = (int)(minecraft->width * InvGuiScale); int screenHeight = (int)(minecraft->height * InvGuiScale); x = (int)(x * InvGuiScale); y = (int)(y * InvGuiScale); if (y < (screenHeight - 16 - 3) || y > screenHeight) return -1; int xBase = 2 + screenWidth / 2 - getNumSlots() * 10; int xRel = (x - xBase); if (xRel < 0) return -1; int slot = xRel / 20; return (slot >= 0 && slot < getNumSlots())? slot : -1; } bool Gui::isInside(int x, int y) { return getSlotIdAt(x, y) != -1; } int Gui::getNumSlots() { return _numSlots; } void Gui::flashSlot(int slotId) { _flashSlotId = slotId; _flashSlotStartTime = getTimeS(); } void Gui::getSlotPos(int slot, int& posX, int& posY) { int screenWidth = (int)(minecraft->width * InvGuiScale); int screenHeight = (int)(minecraft->height * InvGuiScale); posX = screenWidth / 2 - getNumSlots() * 10 + slot * 20, posY = screenHeight - 22; } RectangleArea Gui::getRectangleArea(int extendSide) { const int Spacing = 3; const float pCenterX = 2.0f + (float)(minecraft->width / 2); const float pHalfWidth = (1.0f + (getNumSlots() * 10 + Spacing)) * Gui::GuiScale; const float pHeight = (22 + Spacing) * Gui::GuiScale; if (extendSide < 0) return RectangleArea(0, (float)minecraft->height-pHeight, pCenterX+pHalfWidth+2, (float)minecraft->height); if (extendSide > 0) return RectangleArea(pCenterX-pHalfWidth, (float)minecraft->height-pHeight, (float)minecraft->width, (float)minecraft->height); return RectangleArea(pCenterX-pHalfWidth, (float)minecraft->height-pHeight, pCenterX+pHalfWidth+2, (float)minecraft->height); } void Gui::handleClick(int button, int x, int y) { if (button != MouseAction::ACTION_LEFT) return; int slot = getSlotIdAt(x, y); if (slot != -1) { #ifndef PLATFORM_DESKTOP if (slot == (getNumSlots()-1)) { minecraft->screenChooser.setScreen(SCREEN_BLOCKSELECTION); } else #endif { minecraft->player->inventory->selectSlot(slot); itemNameOverlayTime = 0; } } } void Gui::handleKeyPressed(int key) { if (key == Keyboard::KEY_F1) { minecraft->options.toggle(OPTIONS_HIDEGUI); } if (key == 99) { if (minecraft->player->inventory->selected > 0) { minecraft->player->inventory->selected--; } } else if (key == 4) { if (minecraft->player->inventory->selected < (getNumSlots() - 2)) { minecraft->player->inventory->selected++; } } else if (key == 100) { minecraft->screenChooser.setScreen(SCREEN_BLOCKSELECTION); } else if (key == minecraft->options.getIntValue(OPTIONS_KEY_DROP)) { minecraft->player->inventory->dropSlot(minecraft->player->inventory->selected, false); } } void Gui::tick() { if (overlayMessageTime > 0) overlayMessageTime--; tickCount++; if(itemNameOverlayTime < 2) itemNameOverlayTime += 1.0f / SharedConstants::TicksPerSecond; for (unsigned int i = 0; i < guiMessages.size(); i++) { guiMessages.at(i).ticks++; } if (!minecraft->isCreativeMode()) tickItemDrop(); } void Gui::addMessage(const std::string& _string) { if (!minecraft->font) return; std::string string = _string; while (minecraft->font->width(string) > MAX_MESSAGE_WIDTH) { unsigned int i = 1; while (i < string.length() && minecraft->font->width(string.substr(0, i + 1)) <= MAX_MESSAGE_WIDTH) { i++; } addMessage(string.substr(0, i)); string = string.substr(i); } GuiMessage message; message.message = string; message.ticks = 0; guiMessages.insert(guiMessages.begin(), message); while (guiMessages.size() > 30) { guiMessages.pop_back(); } } void Gui::setNowPlaying(const std::string& string) { overlayMessageString = "Now playing: " + string; overlayMessageTime = 20 * 3; animateOverlayMessageColor = true; } void Gui::displayClientMessage(const std::string& messageId) { //Language language = Language.getInstance(); //std::string languageString = language.getElement(messageId); addMessage(std::string("Client message: ") + messageId); } void Gui::renderVignette(float br, int w, int h) { br = 1 - br; if (br < 0) br = 0; if (br > 1) br = 1; tbr += (br - tbr) * 0.01f; glDisable(GL_DEPTH_TEST); glDepthMask(false); glBlendFunc2(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); glColor4f2(tbr, tbr, tbr, 1); minecraft->textures->loadAndBindTexture("misc/vignette.png"); Tesselator& t = Tesselator::instance; t.begin(); t.vertexUV(0, (float)h, -90, 0, 1); t.vertexUV((float)w, (float)h, -90, 1, 1); t.vertexUV((float)w, 0, -90, 1, 0); t.vertexUV(0, 0, -90, 0, 0); t.draw(); glDepthMask(true); glEnable(GL_DEPTH_TEST); glColor4f2(1, 1, 1, 1); glBlendFunc2(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } void Gui::renderSlot(int slot, int x, int y, float a) { ItemInstance* item = minecraft->player->inventory->getItem(slot); if (!item) { //LOGW("Warning: item @ Gui::renderSlot is NULL\n"); return; } const bool fancy = true; ItemRenderer::renderGuiItem(minecraft->font, minecraft->textures, item, (float)x, (float)y, fancy); } void Gui::renderSlotText( const ItemInstance* item, float x, float y, bool hasFinite, bool shadow ) { //if (!item || item->getItem()->getMaxStackSize() <= 1) { if (item->count <= 1) { return; } int c = item->count; char buffer[4] = {0,0,0,0}; if (hasFinite) itemCountItoa(buffer, c); else buffer[0] = (char)157; //LOGI("slot: %d - %s\n", slot, buffer); if (shadow) minecraft->font->drawShadow(buffer, x, y, item->count>0?0xffcccccc:0x60cccccc); else minecraft->font->draw(buffer, x, y, item->count>0?0xffcccccc:0x60cccccc); } void Gui::inventoryUpdated() { _inventoryNeedsUpdate = true; } void Gui::onGraphicsReset() { inventoryUpdated(); } void Gui::texturesLoaded( Textures* textures ) { //_slotFont = new Font(&minecraft->options, "gui/gui_blocks.png", textures, 0, 504, 10, 1, '0'); } void Gui::onConfigChanged( const Config& c ) { Tesselator& t = Tesselator::instance; t.begin(); // // Create outer feedback circle // #ifdef ANDROID const float mm = 12; #else const float mm = 12; #endif const float maxRadius = minecraft->pixelCalcUi.millimetersToPixels(mm); const float radius = Mth::Min(80.0f/2, maxRadius); //LOGI("radius, maxradius: %f, %f\n", radius, maxRadius); const float radiusInner = radius * 0.95f; const int steps = 24; const float fstep = Mth::TWO_PI / steps; for (int i = 0; i < steps; ++i) { float a = i * fstep;; float b = a + fstep; float aCos = Mth::cos(a); float bCos = Mth::cos(b); float aSin = Mth::sin(a); float bSin = Mth::sin(b); float x00 = radius * aCos; float x01 = radiusInner * aCos; float x10 = radius * bCos; float x11 = radiusInner * bCos; float y00 = radius * aSin; float y01 = radiusInner * aSin; float y10 = radius * bSin; float y11 = radiusInner * bSin; t.vertexUV(x01, y01, 0, 0, 1); t.vertexUV(x11, y11, 0, 1, 1); t.vertexUV(x10, y10, 0, 1, 0); t.vertexUV(x00, y00, 0, 0, 0); } rcFeedbackOuter = t.end(true, rcFeedbackOuter.vboId); // // Create the inner feedback ring // t.begin(GL_TRIANGLE_FAN); t.vertex(0, 0, 0); for (int i = 0; i < steps + 1; ++i) { float a = -i * fstep; float xx = radiusInner * Mth::cos(a); float yy = radiusInner * Mth::sin(a); t.vertex(xx, yy, 0); //LOGI("x,y: %f, %f\n", xx, yy); } rcFeedbackInner = t.end(true, rcFeedbackInner.vboId); if (c.minecraft->useTouchscreen()) { // I'll bump this up to 6. int num = 6; // without "..." dots if (!c.minecraft->options.getBooleanValue(OPTIONS_IS_JOY_TOUCH_AREA) && c.width > 480) { while (num < Inventory::MAX_SELECTION_SIZE - 1) { int x0, x1, y; getSlotPos(0, x0, y); getSlotPos(num, x1, y); int width = x1 - x0; float leftoverPixels = c.width - c.guiScale*width; if (c.pixelCalc.pixelsToMillimeters(leftoverPixels) < 80) break; num++; } } _numSlots = num; #if defined(__APPLE__) _numSlots = Mth::Min(7, _numSlots); #endif } else { _numSlots = Inventory::MAX_SELECTION_SIZE; // Xperia Play } MAX_MESSAGE_WIDTH = c.guiWidth; } float Gui::floorAlignToScreenPixel(float v) { return (int)(v * Gui::GuiScale) * Gui::InvGuiScale; } int Gui::itemCountItoa( char* buffer, int count ) { if (count < 0) return 0; if (count < 10) { // 1 digit buffer[0] = '0' + count; buffer[1] = 0; return 1; } else if (count < 100) { // 2 digits int digit = count/10; buffer[0] = '0' + digit; buffer[1] = '0' + count - digit*10; buffer[2] = 0; } else { // 3 digits -> "99+" buffer[0] = buffer[1] = '9'; buffer[2] = '+'; buffer[3] = 0; return 3; } return 2; } void Gui::tickItemDrop() { // Handle item drop static bool isCurrentlyActive = false; isCurrentlyActive = false; int slots = getNumSlots(); #ifndef PLATFORM_DESKTOP slots--; #endif if (Mouse::isButtonDown(MouseAction::ACTION_LEFT)) { int slot = getSlotIdAt(Mouse::getX(), Mouse::getY()); if (slot >= 0 && slot < slots) { if (slot != _currentDropSlot) { _currentDropTicks = 0; _currentDropSlot = slot; } isCurrentlyActive = true; if ((_currentDropTicks += 1.0f) >= DropTicks) { minecraft->player->inventory->dropSlot(slot, false); minecraft->level->playSound(minecraft->player, "random.pop", 0.3f, 1); isCurrentlyActive = false; } } } if (!isCurrentlyActive) { _currentDropSlot = -1; _currentDropTicks = -1; } } void Gui::postError( int errCode ) { static std::set posted; if (posted.find(errCode) != posted.end()) return; posted.insert(errCode); std::stringstream s; s << "Something went wrong! (errcode " << errCode << ")\n"; addMessage(s.str()); } void Gui::setScissorRect( const IntRectangle& bbox ) { GLuint x = (GLuint)(GuiScale * bbox.x); GLuint y = minecraft->height - (GLuint)(GuiScale * (bbox.y + bbox.h)); GLuint w = (GLuint)(GuiScale * bbox.w); GLuint h = (GLuint)(GuiScale * bbox.h); glScissor(x, y, w, h); } float Gui::cubeSmoothStep(float percentage, float min, float max) { //percentage = percentage * percentage; //return (min * percentage) + (max * (1 - percentage)); return (percentage) * (percentage) * (3 - 2 * (percentage)); } void Gui::renderProgressIndicator( const bool isTouchInterface, const int screenWidth, const int screenHeight, float a ) { ItemInstance* currentItem = minecraft->player->inventory->getSelected(); bool bowEquipped = currentItem != NULL ? currentItem->getItem() == Item::bow : false; bool itemInUse = currentItem != NULL ? currentItem->getItem() == minecraft->player->getUseItem()->getItem() : false; if ((!isTouchInterface || minecraft->options.getBooleanValue(OPTIONS_IS_JOY_TOUCH_AREA) || (bowEquipped && itemInUse)) && !minecraft->options.getBooleanValue(OPTIONS_HIDEGUI)) { minecraft->textures->loadAndBindTexture("gui/icons.png"); glEnable(GL_BLEND); glBlendFunc2(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR); blit(screenWidth/2 - 8, screenHeight/2 - 8, 0, 0, 16, 16); glDisable(GL_BLEND); } else if(!bowEquipped) { const float tprogress = minecraft->gameMode->destroyProgress; const float alpha = Mth::clamp(minecraft->inputHolder->alpha, 0.0f, 1.0f); //LOGI("alpha: %f\n", alpha); if (tprogress <= 0 && minecraft->inputHolder->alpha >= 0) { glDisable2(GL_TEXTURE_2D); glEnable2(GL_BLEND); glBlendFunc2(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); if (minecraft->hitResult.isHit()) glColor4f2(1, 1, 1, 0.8f * alpha); else glColor4f2(1, 1, 1, Mth::Min(0.4f, alpha*0.4f)); //LOGI("alpha2: %f\n", alpha); const float x = InvGuiScale * minecraft->inputHolder->mousex; const float y = InvGuiScale * minecraft->inputHolder->mousey; glTranslatef2(x, y, 0); drawArrayVT(rcFeedbackOuter.vboId, rcFeedbackOuter.vertexCount, 24); glTranslatef2(-x, -y, 0); glEnable2(GL_TEXTURE_2D); glDisable(GL_BLEND); } else if (tprogress > 0) { const float oProgress = minecraft->gameMode->oDestroyProgress; const float progress = 0.5f * (oProgress + (tprogress - oProgress) * a); //static Stopwatch w; //w.start(); glDisable2(GL_TEXTURE_2D); glColor4f2(1, 1, 1, 0.8f * alpha); glEnable(GL_BLEND); glBlendFunc2(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); const float x = InvGuiScale * minecraft->inputHolder->mousex; const float y = InvGuiScale * minecraft->inputHolder->mousey; glPushMatrix2(); glTranslatef2(x, y, 0); drawArrayVT(rcFeedbackOuter.vboId, rcFeedbackOuter.vertexCount, 24); glScalef2(0.5f + progress, 0.5f + progress, 1); //glDisable2(GL_CULL_FACE); glColor4f2(1, 1, 1, 1); glBlendFunc2(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR); drawArrayVT(rcFeedbackInner.vboId, rcFeedbackInner.vertexCount, 24, GL_TRIANGLE_FAN); glPopMatrix2(); glDisable(GL_BLEND); glEnable2(GL_TEXTURE_2D); //w.stop(); //w.printEvery(100, "feedback-r "); } } } void Gui::renderHearts() { bool blink = (minecraft->player->invulnerableTime / 3) % 2 == 1; if (minecraft->player->invulnerableTime < 10) blink = false; int h = minecraft->player->health; int oh = minecraft->player->lastHealth; random.setSeed(tickCount * 312871); int screenWidth = (int)(minecraft->width * InvGuiScale); int screenHeight = (int)(minecraft->height * InvGuiScale); int xx = (minecraft->options.getBooleanValue(OPTIONS_BAR_ON_TOP)) ? screenWidth / 2 - getNumSlots() * 10 - 1 : 2; int armor = minecraft->player->getArmorValue(); for (int i = 0; i < Player::MAX_HEALTH / 2; i++) { int yo = (minecraft->options.getBooleanValue(OPTIONS_BAR_ON_TOP)) ? screenHeight - 32 : 2; int ip2 = i + i + 1; if (armor > 0) { int xo = xx + 80 + i * 8 + 4; if (ip2 < armor) blit(xo, yo, 16 + 2 * 9, 9 * 1, 9, 9); else if (ip2 == armor) blit(xo, yo, 16 + 4 * 9, 9 * 1, 9, 9); else if (ip2 > armor) blit(xo, yo, 16 + 0 * 9, 9 * 1, 9, 9); } int bg = 0; if (blink) bg = 1; int xo = xx + i * 8; if (h <= 4) { yo = yo + random.nextInt(2) - 1; } blit(xo, yo, 16 + bg * 9, 9 * 0, 9, 9); if (blink) { if (ip2 < oh) blit(xo, yo, 16 + 6 * 9, 9 * 0, 9, 9); else if (ip2 == oh) blit(xo, yo, 16 + 7 * 9, 9 * 0, 9, 9); } if (ip2 < h) blit(xo, yo, 16 + 4 * 9, 9 * 0, 9, 9); else if (ip2 == h) blit(xo, yo, 16 + 5 * 9, 9 * 0, 9, 9); } } void Gui::renderBubbles() { if (minecraft->player->isUnderLiquid(Material::water)) { int screenWidth = (int)(minecraft->width * InvGuiScale); int screenHeight = (int)(minecraft->height * InvGuiScale); int xx = (minecraft->options.getBooleanValue(OPTIONS_BAR_ON_TOP)) ? screenWidth / 2 - getNumSlots() * 10 - 1 : 2; int yo = (minecraft->options.getBooleanValue(OPTIONS_BAR_ON_TOP)) ? screenHeight - 42 : 12; int count = (int) std::ceil((minecraft->player->airSupply - 2) * 10.0f / Player::TOTAL_AIR_SUPPLY); int extra = (int) std::ceil((minecraft->player->airSupply) * 10.0f / Player::TOTAL_AIR_SUPPLY) - count; for (int i = 0; i < count + extra; i++) { int xo = i * 8 + xx; if (i < count) blit(xo, yo, 16, 9 * 2, 9, 9); else blit(xo, yo, 16 + 9, 9 * 2, 9, 9); } } } static OffsetPosTranslator posTranslator; void Gui::onLevelGenerated() { if (Level* level = minecraft->level) { Pos p = level->getSharedSpawnPos(); posTranslator = OffsetPosTranslator((float)-p.x, (float)-p.y, (float)-p.z); } } void Gui::renderDebugInfo() { // FPS counter (updates once per second) static float fps = 0.0f; static float fpsLastTime = 0.0f; static int fpsFrames = 0; float now = getTimeS(); fpsFrames++; if (now - fpsLastTime >= 1.0f) { fps = fpsFrames / (now - fpsLastTime); fpsFrames = 0; fpsLastTime = now; } LocalPlayer* p = minecraft->player; Level* lvl = minecraft->level; // Position float px = p->x, py = p->y - p->heightOffset, pz = p->z; posTranslator.to(px, py, pz); int bx = (int)floorf(px), by = (int)floorf(py), bz = (int)floorf(pz); int cx = bx >> 4, cz = bz >> 4; // Facing direction float yMod = fmodf(p->yRot, 360.0f); if (yMod < 0) yMod += 360.0f; const char* facing; const char* axis; if (yMod < 45 || yMod >= 315) { facing = "South"; axis = "+Z"; } else if (yMod < 135) { facing = "West"; axis = "-X"; } else if (yMod < 225) { facing = "North"; axis = "-Z"; } else { facing = "East"; axis = "+X"; } // Biome const char* biomeName = "unknown"; if (lvl) { Biome* biome = lvl->getBiome(bx, bz); if (biome) biomeName = biome->name.c_str(); } // Time long worldTime = lvl ? lvl->getTime() : 0; long dayTime = worldTime % Level::TICKS_PER_DAY; long day = worldTime / Level::TICKS_PER_DAY; long seed = lvl ? lvl->getSeed() : 0; // Build lines (NULL entry = blank gap) static char ln[8][96]; sprintf(ln[0], "Minecraft PE 0.6.1 alpha (mcpe64)"); sprintf(ln[1], "%.1f fps", fps); ln[2][0] = '\0'; // blank separator sprintf(ln[3], "XYZ: %.3f / %.3f / %.3f", px, py, pz); sprintf(ln[4], "Block: %d %d %d Chunk: %d %d", bx, by, bz, cx, cz); sprintf(ln[5], "Facing: %s (%s) (%.1f / %.1f)", facing, axis, p->yRot, p->xRot); sprintf(ln[6], "Biome: %s", biomeName); sprintf(ln[7], "Day %ld Time: %ld Seed: %ld", day, dayTime, seed); const int N = 8; const float LH = (float)Font::DefaultLineHeight; // 10 font-pixels const float MGN = 2.0f; // left/top margin in font-pixels const float PAD = 2.0f; // horizontal padding for background Font* font = minecraft->font; // 1) Draw semi-transparent background boxes behind each line for (int i = 0; i < N; i++) { if (ln[i][0] == '\0') continue; float w = (float)font->width(ln[i]); float x0 = MGN - PAD; float y0 = MGN + i * LH - 1.0f; float x1 = MGN + w + PAD; float y1 = MGN + (i + 1) * LH - 1.0f; fill(x0, y0, x1, y1, 0x90000000); } // 2) Draw text (no extra scale — font coords are in GUI units, same as fill) Tesselator& t = Tesselator::instance; t.beginOverride(); for (int i = 0; i < N; i++) { if (ln[i][0] == '\0') continue; float y = MGN + i * LH; int col = (i == 0) ? 0xffFFFF55 : 0xffffffff; // title yellow, rest white font->draw(ln[i], MGN, y, col); } t.endOverrideAndDraw(); } void Gui::renderSleepAnimation( const int screenWidth, const int screenHeight ) { int timer = minecraft->player->getSleepTimer(); float amount = (float) timer / (float) Player::SLEEP_DURATION; if (amount > 1) { // waking up amount = 1.0f - ((float) (timer - Player::SLEEP_DURATION) / (float) Player::WAKE_UP_DURATION); } int color = (int) (220.0f * amount) << 24 | (0x101020); fill(0, 0, screenWidth, screenHeight, color); } void Gui::renderOnSelectItemNameText( const int screenWidth, Font* font, int ySlot ) { if(itemNameOverlayTime < 1.0f) { ItemInstance* item = minecraft->player->inventory->getSelected(); if(item != NULL) { float x = float(screenWidth / 2 - font->width(item->getName()) / 2); float y = float(ySlot - 22); int alpha = 255; if(itemNameOverlayTime > 0.75) { float time = 0.25f - (itemNameOverlayTime - 0.75f); float percentage = cubeSmoothStep(time * 4, 0.0f, 1.0f); alpha = int(percentage * 255); } if(alpha != 0) font->drawShadow(item->getName(), x, y, 0x00ffffff + (alpha << 24)); } } } // helper structure used by drawColoredString struct ColorSegment { std::string text; uint32_t color; }; // parse [tag] and [/tag] markers; tags may contain a color name (gold, green, etc.) static void parseColorTags(const std::string& in, std::vector& out) { uint32_t curColor = 0xffffff; size_t pos = 0; while (pos < in.size()) { size_t open = in.find('[', pos); if (open == std::string::npos) { out.push_back({in.substr(pos), curColor}); break; } if (open > pos) { out.push_back({in.substr(pos, open - pos), curColor}); } size_t close = in.find(']', open); if (close == std::string::npos) { out.push_back({in.substr(open), curColor}); break; } std::string tag = in.substr(open + 1, close - open - 1); if (!tag.empty() && tag[0] == '/') { curColor = 0xffffff; } else { std::string lower; lower.resize(tag.size()); std::transform(tag.begin(), tag.end(), lower.begin(), ::tolower); if (lower.find("gold") != std::string::npos) curColor = 0xffd700; else if (lower.find("green") != std::string::npos) curColor = 0x00ff00; else if (lower.find("yellow") != std::string::npos) curColor = 0xffff00; else if (lower.find("red") != std::string::npos) curColor = 0xff0000; else if (lower.find("blue") != std::string::npos) curColor = 0x0000ff; } pos = close + 1; } } void Gui::drawColoredString(Font* font, const std::string& text, float x, float y, int alpha) { std::vector segs; parseColorTags(text, segs); float cx = x; for (auto &s : segs) { int color = s.color + (alpha << 24); font->drawShadow(s.text, cx, y, color); cx += font->width(s.text); } } float Gui::getColoredWidth(Font* font, const std::string& text) { std::vector segs; parseColorTags(text, segs); float w = 0; for (auto &s : segs) { w += font->width(s.text); } return w; } void Gui::renderChatMessages( const int screenHeight, unsigned int max, bool isChatting, Font* font ) { // if (minecraft.screen instanceof ChatScreen) { // max = 20; // isChatting = true; // } // // glEnable(GL_BLEND); // glBlendFunc2(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // glDisable(GL_ALPHA_TEST); // // glPushMatrix2(); // glTranslatef2(0, screenHeight - 48, 0); // // glScalef2(1.0f / ssc.scale, 1.0f / ssc.scale, 1); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); int baseY = screenHeight - 48; for (unsigned int i = 0; i < guiMessages.size() && i < max; i++) { if (guiMessages.at(i).ticks < 20 * 10 || isChatting) { float t = guiMessages.at(i).ticks / (20 * 10.0f); t = 1 - t; t = t * 10; if (t < 0) t = 0; if (t > 1) t = 1; t = t * t; int alpha = (int) (255 * t); if (isChatting) alpha = 255; if (alpha > 0) { const float x = 2; const float y = (float)(baseY - i * 9); std::string msg = guiMessages.at(i).message; this->fill(x, y - 1, x + MAX_MESSAGE_WIDTH, y + 8, (alpha / 2) << 24); glEnable(GL_BLEND); // special-case join/leave announcements int baseColor = 0xffffff; if (msg.find(" joined the game") != std::string::npos || msg.find(" left the game") != std::string::npos) { baseColor = 0xffff00; // yellow } // replace previous logic; allow full colour tags now Gui::drawColoredString(font, msg, x, y, alpha); } } } } void Gui::renderToolBar( float a, int ySlot, const int screenWidth ) { glColor4f2(1, 1, 1, .5); minecraft->textures->loadAndBindTexture("gui/gui.png"); Inventory* inventory = minecraft->player->inventory; int xBase, yBase; getSlotPos(0, xBase, yBase); const float baseItemX = (float)xBase + 3; const int slotsWidth = 20 * getNumSlots(); // Left + right side of the selection bar blit(xBase, yBase, 0, 0, slotsWidth, 22); blit(xBase + slotsWidth, yBase, 180, 0, 2, 22); if (_currentDropSlot >= 0 && inventory->getItem(_currentDropSlot)) { int x = xBase + 3 + _currentDropSlot * 20; int color = 0x8000ff00; int yy = (int)(17.0f * (_currentDropTicks + a) / DropTicks); if (_currentDropTicks >= 3) { glColor4f2(0, 1, 0, 0.5f); } fill(x, ySlot+16-yy, x+16, ySlot+16, color); } blit(xBase-1 + 20*inventory->selected, yBase - 1, 0, 22, 24, 22); glColor4f2(1, 1, 1, 1); // Flash a slot background if (_flashSlotId >= 0) { const float since = getTimeS() - _flashSlotStartTime; if (since > 0.2f) _flashSlotId = -1; else { int x = screenWidth / 2 - getNumSlots() * 10 + _flashSlotId * 20 + 2; int color = 0xffffff + (((int)(/*0x80 * since +*/ 0x51 - 0x50 * Mth::cos(10 * 6.28f * since))) << 24); //LOGI("Color: %.8x\n", color); fill(x, ySlot, x+16, ySlot+16, color); } } glColor4f2(1, 1, 1, 1); //static Stopwatch w; //w.start(); Tesselator& t = Tesselator::instance; t.beginOverride(); float x = baseItemX; int slots = getNumSlots(); // TODO: if using touchscreen #ifndef PLATFORM_DESKTOP slots--; #endif for (int i = 0; i < slots; i++) { renderSlot(i, (int)x, ySlot, a); x += 20; } _inventoryNeedsUpdate = false; #ifndef PLATFORM_DESKTOP blit(screenWidth / 2 + 10 * getNumSlots() - 20 + 4, ySlot + 6, 242, 252, 14, 4, 14, 4); #endif minecraft->textures->loadAndBindTexture("gui/gui_blocks.png"); t.endOverrideAndDraw(); // Render damaged items (@todo: investigate if it's faster by drawing in same batch) glDisable2(GL_DEPTH_TEST); glDisable2(GL_TEXTURE_2D); t.beginOverride(); x = baseItemX; for (int i = 0; i < slots; i++) { ItemRenderer::renderGuiItemDecorations(minecraft->player->inventory->getItem(i), x, (float)ySlot); x += 20; } t.endOverrideAndDraw(); glEnable(GL_DEPTH_TEST); glEnable(GL_TEXTURE_2D); //w.stop(); //w.printEvery(100, "gui-slots"); // Draw count //Tesselator& t = Tesselator::instance; glPushMatrix2(); glScalef2(InvGuiScale + InvGuiScale, InvGuiScale + InvGuiScale, 1); const float k = 0.5f * GuiScale; t.beginOverride(); if (minecraft->gameMode->isSurvivalType()) { x = baseItemX; for (int i = 0; i < slots; i++) { ItemInstance* item = minecraft->player->inventory->getItem(i); if (item && item->count >= 0) renderSlotText(item, k*x, k*ySlot + 1, true, true); x += 20; } } minecraft->textures->loadAndBindTexture("font/default8.png"); t.endOverrideAndDraw(); glPopMatrix2(); }