diff --git a/Minecraft.Client/ChatScreen.cpp b/Minecraft.Client/ChatScreen.cpp index b68e6cac..543cab76 100644 --- a/Minecraft.Client/ChatScreen.cpp +++ b/Minecraft.Client/ChatScreen.cpp @@ -1,14 +1,27 @@ #include "stdafx.h" #include "ChatScreen.h" +#include "ClientConnection.h" +#include "Font.h" #include "MultiplayerLocalPlayer.h" #include "..\Minecraft.World\SharedConstants.h" #include "..\Minecraft.World\StringHelpers.h" +#include "..\Minecraft.World\ChatPacket.h" const wstring ChatScreen::allowedChars = SharedConstants::acceptableLetters; +vector ChatScreen::s_chatHistory; +int ChatScreen::s_historyIndex = -1; +wstring ChatScreen::s_historyDraft; + +bool ChatScreen::isAllowedChatChar(wchar_t c) +{ + return c >= 0x20 && (c == L'\u00A7' || allowedChars.empty() || allowedChars.find(c) != wstring::npos); +} ChatScreen::ChatScreen() { frame = 0; + cursorIndex = 0; + s_historyIndex = -1; } void ChatScreen::init() @@ -24,6 +37,50 @@ void ChatScreen::removed() void ChatScreen::tick() { frame++; + if (cursorIndex > static_cast(message.length())) + cursorIndex = static_cast(message.length()); +} + +void ChatScreen::handlePasteRequest() +{ + wstring pasted = Screen::getClipboard(); + for (size_t i = 0; i < pasted.length() && static_cast(message.length()) < SharedConstants::maxChatLength; i++) + { + if (isAllowedChatChar(pasted[i])) + { + message.insert(cursorIndex, 1, pasted[i]); + cursorIndex++; + } + } +} + +void ChatScreen::applyHistoryMessage() +{ + message = s_historyIndex >= 0 ? s_chatHistory[s_historyIndex] : s_historyDraft; + cursorIndex = static_cast(message.length()); +} + +void ChatScreen::handleHistoryUp() +{ + if (s_chatHistory.empty()) return; + if (s_historyIndex == -1) + { + s_historyDraft = message; + s_historyIndex = static_cast(s_chatHistory.size()) - 1; + } + else if (s_historyIndex > 0) + s_historyIndex--; + applyHistoryMessage(); +} + +void ChatScreen::handleHistoryDown() +{ + if (s_chatHistory.empty()) return; + if (s_historyIndex < static_cast(s_chatHistory.size()) - 1) + s_historyIndex++; + else + s_historyIndex = -1; + applyHistoryMessage(); } void ChatScreen::keyPressed(wchar_t ch, int eventKey) @@ -35,31 +92,67 @@ void ChatScreen::keyPressed(wchar_t ch, int eventKey) } if (eventKey == Keyboard::KEY_RETURN) { - wstring msg = trimString(message); - if (msg.length() > 0) + wstring trim = trimString(message); + if (trim.length() > 0) { - wstring trim = trimString(message); if (!minecraft->handleClientSideCommand(trim)) { - minecraft->player->chat(trim); + MultiplayerLocalPlayer* mplp = dynamic_cast(minecraft->player.get()); + if (mplp && mplp->connection) + mplp->connection->send(shared_ptr(new ChatPacket(trim))); + } + if (s_chatHistory.empty() || s_chatHistory.back() != trim) + { + s_chatHistory.push_back(trim); + if (s_chatHistory.size() > CHAT_HISTORY_MAX) + s_chatHistory.erase(s_chatHistory.begin()); } } minecraft->setScreen(NULL); return; } - if (eventKey == Keyboard::KEY_BACK && message.length() > 0) message = message.substr(0, message.length() - 1); - if (allowedChars.find(ch) >= 0 && message.length() < SharedConstants::maxChatLength) + if (eventKey == Keyboard::KEY_UP) { handleHistoryUp(); return; } + if (eventKey == Keyboard::KEY_DOWN) { handleHistoryDown(); return; } + if (eventKey == Keyboard::KEY_LEFT) { - message += ch; + if (cursorIndex > 0) + cursorIndex--; + return; + } + if (eventKey == Keyboard::KEY_RIGHT) + { + if (cursorIndex < static_cast(message.length())) + cursorIndex++; + return; + } + if (eventKey == Keyboard::KEY_BACK && cursorIndex > 0) + { + message.erase(cursorIndex - 1, 1); + cursorIndex--; + return; + } + if (isAllowedChatChar(ch) && static_cast(message.length()) < SharedConstants::maxChatLength) + { + message.insert(cursorIndex, 1, ch); + cursorIndex++; } - } void ChatScreen::render(int xm, int ym, float a) { fill(2, height - 14, width - 2, height - 2, 0x80000000); - drawString(font, L"> " + message + (frame / 6 % 2 == 0 ? L"_" : L""), 4, height - 12, 0xe0e0e0); - + const wstring prefix = L"> "; + int x = 4; + drawString(font, prefix, x, height - 12, 0xe0e0e0); + x += font->width(prefix); + wstring beforeCursor = message.substr(0, cursorIndex); + wstring afterCursor = message.substr(cursorIndex); + drawStringLiteral(font, beforeCursor, x, height - 12, 0xe0e0e0); + x += font->widthLiteral(beforeCursor); + if (frame / 6 % 2 == 0) + drawString(font, L"_", x, height - 12, 0xe0e0e0); + x += font->width(L"_"); + drawStringLiteral(font, afterCursor, x, height - 12, 0xe0e0e0); Screen::render(xm, ym, a); } @@ -71,13 +164,15 @@ void ChatScreen::mouseClicked(int x, int y, int buttonNum) { if (message.length() > 0 && message[message.length()-1]!=L' ') { - message += L" "; + message = message.substr(0, cursorIndex) + L" " + message.substr(cursorIndex); + cursorIndex++; } - message += minecraft->gui->selectedName; - unsigned int maxLength = SharedConstants::maxChatLength; - if (message.length() > maxLength) + size_t nameLen = minecraft->gui->selectedName.length(); + size_t insertLen = (message.length() + nameLen <= SharedConstants::maxChatLength) ? nameLen : (SharedConstants::maxChatLength - message.length()); + if (insertLen > 0) { - message = message.substr(0, maxLength); + message = message.substr(0, cursorIndex) + minecraft->gui->selectedName.substr(0, insertLen) + message.substr(cursorIndex); + cursorIndex += static_cast(insertLen); } } else @@ -85,5 +180,4 @@ void ChatScreen::mouseClicked(int x, int y, int buttonNum) Screen::mouseClicked(x, y, buttonNum); } } - } \ No newline at end of file diff --git a/Minecraft.Client/ChatScreen.h b/Minecraft.Client/ChatScreen.h index d7158478..c4e37a93 100644 --- a/Minecraft.Client/ChatScreen.h +++ b/Minecraft.Client/ChatScreen.h @@ -1,21 +1,33 @@ #pragma once #include "Screen.h" +#include using namespace std; class ChatScreen : public Screen { protected: wstring message; + int cursorIndex; + void applyHistoryMessage(); + private: int frame; + static const size_t CHAT_HISTORY_MAX = 100; + static std::vector s_chatHistory; + static int s_historyIndex; + static wstring s_historyDraft; + static const wstring allowedChars; + static bool isAllowedChatChar(wchar_t c); public: - ChatScreen(); //4J added + ChatScreen(); virtual void init(); - virtual void removed(); - virtual void tick(); -private: - static const wstring allowedChars; + virtual void removed(); + virtual void tick(); + virtual void handlePasteRequest(); + virtual void handleHistoryUp(); + virtual void handleHistoryDown(); + protected: void keyPressed(wchar_t ch, int eventKey); public: diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp index 9b9bb86a..315a8032 100644 --- a/Minecraft.Client/ClientConnection.cpp +++ b/Minecraft.Client/ClientConnection.cpp @@ -1441,6 +1441,9 @@ void ClientConnection::handleChat(shared_ptr packet) switch(packet->m_messageType) { + case ChatPacket::e_ChatCustom: + message = (packet->m_stringArgs.size() >= 1) ? packet->m_stringArgs[0] : L""; + break; case ChatPacket::e_ChatBedOccupied: message = app.GetString(IDS_TILE_BED_OCCUPIED); break; diff --git a/Minecraft.Client/Common/UI/UIComponent_Chat.cpp b/Minecraft.Client/Common/UI/UIComponent_Chat.cpp index 98b4f165..901b5a77 100644 --- a/Minecraft.Client/Common/UI/UIComponent_Chat.cpp +++ b/Minecraft.Client/Common/UI/UIComponent_Chat.cpp @@ -55,10 +55,16 @@ void UIComponent_Chat::handleTimerComplete(int id) float opacity = pGui->getOpacity(m_iPad, i); if( opacity > 0 ) { +#ifdef _WINDOWS64 + // Chat drawn by Gui::render with color codes. Hides Iggy chat to avoid double chats. + m_controlLabelBackground[i].setOpacity(0); + m_labelChatText[i].setOpacity(0); + m_labelChatText[i].setLabel(L""); +#else m_controlLabelBackground[i].setOpacity(opacity); m_labelChatText[i].setOpacity(opacity); m_labelChatText[i].setLabel( pGui->getMessage(m_iPad,i) ); - +#endif anyVisible = true; } else diff --git a/Minecraft.Client/Common/UI/UIScene_HUD.cpp b/Minecraft.Client/Common/UI/UIScene_HUD.cpp index c3d52cf9..e01757ab 100644 --- a/Minecraft.Client/Common/UI/UIScene_HUD.cpp +++ b/Minecraft.Client/Common/UI/UIScene_HUD.cpp @@ -753,10 +753,16 @@ void UIScene_HUD::handleTimerComplete(int id) float opacity = pGui->getOpacity(m_iPad, i); if( opacity > 0 ) { +#ifdef _WINDOWS64 + // Chat drawn by Gui::render with color codes. Hides Iggy chat to avoid double chats. + m_controlLabelBackground[i].setOpacity(0); + m_labelChatText[i].setOpacity(0); + m_labelChatText[i].setLabel(L""); +#else m_controlLabelBackground[i].setOpacity(opacity); m_labelChatText[i].setOpacity(opacity); m_labelChatText[i].setLabel( pGui->getMessagesCount(m_iPad) ? pGui->getMessage(m_iPad,i) : L"" ); - +#endif anyVisible = true; } else diff --git a/Minecraft.Client/Font.cpp b/Minecraft.Client/Font.cpp index 8a90711b..51b50ca1 100644 --- a/Minecraft.Client/Font.cpp +++ b/Minecraft.Client/Font.cpp @@ -21,6 +21,10 @@ Font::Font(Options *options, const wstring& name, Textures* textures, bool enfor enforceUnicodeSheet = false; bidirectional = false; xPos = yPos = 0.0f; + m_bold = false; + m_italic = false; + m_underline = false; + m_strikethrough = false; // Set up member variables m_cols = cols; @@ -126,6 +130,22 @@ Font::~Font() } #endif +void Font::renderStyleLine(float x0, float y0, float x1, float y1) +{ + Tesselator* t = Tesselator::getInstance(); + float u = 0.0f, v = 0.0f; + t->begin(); + t->tex(u, v); + t->vertex(x0, y1, 0.0f); + t->tex(u, v); + t->vertex(x1, y1, 0.0f); + t->tex(u, v); + t->vertex(x1, y0, 0.0f); + t->tex(u, v); + t->vertex(x0, y0, 0.0f); + t->end(); +} + void Font::renderCharacter(wchar_t c) { float xOff = c % m_cols * m_charWidth; @@ -137,37 +157,47 @@ void Font::renderCharacter(wchar_t c) float fontWidth = m_cols * m_charWidth; float fontHeight = m_rows * m_charHeight; - Tesselator *t = Tesselator::getInstance(); - // 4J Stu - Changed to a quad so that we can use within a command buffer -#if 1 + const float shear = m_italic ? (height * 0.25f) : 0.0f; + float x0 = xPos, x1 = xPos + width + shear; + float y0 = yPos, y1 = yPos + height; + + Tesselator *t = Tesselator::getInstance(); t->begin(); t->tex(xOff / fontWidth, (yOff + 7.99f) / fontHeight); - t->vertex(xPos, yPos + height, 0.0f); - + t->vertex(x0, y1, 0.0f); t->tex((xOff + width) / fontWidth, (yOff + 7.99f) / fontHeight); - t->vertex(xPos + width, yPos + height, 0.0f); - + t->vertex(x1, y1, 0.0f); t->tex((xOff + width) / fontWidth, yOff / fontHeight); - t->vertex(xPos + width, yPos, 0.0f); - + t->vertex(x1, y0, 0.0f); t->tex(xOff / fontWidth, yOff / fontHeight); - t->vertex(xPos, yPos, 0.0f); - + t->vertex(x0, y0, 0.0f); t->end(); -#else - t->begin(GL_TRIANGLE_STRIP); - t->tex(xOff / 128.0F, yOff / 128.0F); - t->vertex(xPos, yPos, 0.0f); - t->tex(xOff / 128.0F, (yOff + 7.99f) / 128.0F); - t->vertex(xPos, yPos + 7.99f, 0.0f); - t->tex((xOff + width) / 128.0F, yOff / 128.0F); - t->vertex(xPos + width, yPos, 0.0f); - t->tex((xOff + width) / 128.0F, (yOff + 7.99f) / 128.0F); - t->vertex(xPos + width, yPos + 7.99f, 0.0f); - t->end(); -#endif - xPos += (float) charWidths[c]; + if (m_bold) + { + float dx = 1.0f; + t->begin(); + t->tex(xOff / fontWidth, (yOff + 7.99f) / fontHeight); + t->vertex(x0 + dx, y1, 0.0f); + t->tex((xOff + width) / fontWidth, (yOff + 7.99f) / fontHeight); + t->vertex(x1 + dx, y1, 0.0f); + t->tex((xOff + width) / fontWidth, yOff / fontHeight); + t->vertex(x1 + dx, y0, 0.0f); + t->tex(xOff / fontWidth, yOff / fontHeight); + t->vertex(x0 + dx, y0, 0.0f); + t->end(); + } + + if (m_underline) + renderStyleLine(x0, y1 - 1.0f, xPos + static_cast(charWidths[c]), y1); + + if (m_strikethrough) + { + float mid = y0 + height * 0.5f; + renderStyleLine(x0, mid - 0.5f, xPos + static_cast(charWidths[c]), mid + 0.5f); + } + + xPos += static_cast(charWidths[c]); } void Font::drawShadow(const wstring& str, int x, int y, int color) @@ -176,6 +206,26 @@ void Font::drawShadow(const wstring& str, int x, int y, int color) draw(str, x, y, color, false); } +void Font::drawShadowLiteral(const wstring& str, int x, int y, int color) +{ + int shadowColor = (color & 0xFCFCFC) >> 2 | (color & 0xFF000000); + drawLiteral(str, x + 1, y + 1, shadowColor); + drawLiteral(str, x, y, color); +} + +void Font::drawLiteral(const wstring& str, int x, int y, int color) +{ + if (str.empty()) return; + if ((color & 0xFC000000) == 0) color |= 0xFF000000; + textures->bindTexture(m_textureLocation); + glColor4f((color >> 16 & 255) / 255.0F, (color >> 8 & 255) / 255.0F, (color & 255) / 255.0F, (color >> 24 & 255) / 255.0F); + xPos = static_cast(x); + yPos = static_cast(y); + wstring cleanStr = sanitize(str); + for (size_t i = 0; i < cleanStr.length(); ++i) + renderCharacter(cleanStr.at(i)); +} + void Font::drawShadowWordWrap(const wstring &str, int x, int y, int w, int color, int h) { drawWordWrapInternal(str, x + 1, y + 1, w, color, true, h); @@ -193,24 +243,38 @@ wstring Font::reorderBidi(const wstring &str) return str; } +static bool isSectionFormatCode(wchar_t ca) +{ + if ((ca >= L'0' && ca <= L'9') || (ca >= L'a' && ca <= L'f') || (ca >= L'A' && ca <= L'F')) + return true; + wchar_t l = static_cast(ca | 32); + return l == L'l' || l == L'o' || l == L'n' || l == L'm' || l == L'r' || l == L'k'; +} + void Font::draw(const wstring &str, bool dropShadow) { // Bind the texture textures->bindTexture(m_textureLocation); bool noise = false; + m_bold = m_italic = m_underline = m_strikethrough = false; wstring cleanStr = sanitize(str); - for (int i = 0; i < (int)cleanStr.length(); ++i) + for (int i = 0; i < static_cast(cleanStr.length()); ++i) { // Map character wchar_t c = cleanStr.at(i); if (c == 167 && i + 1 < cleanStr.length()) { - // 4J - following block was: - // int colorN = L"0123456789abcdefk".indexOf(str.toLowerCase().charAt(i + 1)); wchar_t ca = cleanStr[i+1]; + if (!isSectionFormatCode(ca)) + { + renderCharacter(167); + renderCharacter(ca); + i += 1; + continue; + } int colorN = 16; if(( ca >= L'0' ) && (ca <= L'9')) colorN = ca - L'0'; else if(( ca >= L'a' ) && (ca <= L'f')) colorN = (ca - L'a') + 10; @@ -218,7 +282,13 @@ void Font::draw(const wstring &str, bool dropShadow) if (colorN == 16) { - noise = true; + wchar_t l = static_cast(ca | 32); + if (l == L'l') m_bold = true; + else if (l == L'o') m_italic = true; + else if (l == L'n') m_underline = true; + else if (l == L'm') m_strikethrough = true; + else if (l == L'r') m_bold = m_italic = m_underline = m_strikethrough = noise = false; + else if (l == L'k') noise = true; } else { @@ -280,20 +350,34 @@ int Font::width(const wstring& str) { wchar_t c = cleanStr.at(i); - if(c == 167) + if (c == 167) { - // Ignore the character used to define coloured text - ++i; + if (i + 1 < cleanStr.length() && isSectionFormatCode(cleanStr[i+1])) + ++i; + else + { + len += charWidths[167]; + if (i + 1 < cleanStr.length()) + len += charWidths[static_cast(cleanStr[++i])]; + } } else - { len += charWidths[c]; - } } return len; } +int Font::widthLiteral(const wstring& str) +{ + wstring cleanStr = sanitize(str); + if (cleanStr == L"") return 0; + int len = 0; + for (size_t i = 0; i < cleanStr.length(); ++i) + len += charWidths[static_cast(cleanStr.at(i))]; + return len; +} + wstring Font::sanitize(const wstring& str) { wstring sb = str; @@ -467,7 +551,7 @@ void Font::setBidirectional(bool bidirectional) bool Font::AllCharactersValid(const wstring &str) { - for (int i = 0; i < (int)str.length(); ++i) + for (int i = 0; i < static_cast(str.length()); ++i) { wchar_t c = str.at(i); @@ -512,15 +596,15 @@ void Font::renderFakeCB(IntBuffer *ib) float uo = (0.0f) / 128.0f; float vo = (0.0f) / 128.0f; - t->vertexUV((float)(0), (float)( 0 + s), (float)( 0), (float)( ix / 128.0f + uo), (float)( (iy + s) / 128.0f + vo)); - t->vertexUV((float)(0 + s), (float)( 0 + s), (float)( 0), (float)( (ix + s) / 128.0f + uo), (float)( (iy + s) / 128.0f + vo)); - t->vertexUV((float)(0 + s), (float)( 0), (float)( 0), (float)( (ix + s) / 128.0f + uo), (float)( iy / 128.0f + vo)); - t->vertexUV((float)(0), (float)( 0), (float)( 0), (float)( ix / 128.0f + uo), (float)( iy / 128.0f + vo)); + t->vertexUV(static_cast(0), static_cast(0 + s), static_cast(0), static_cast(ix / 128.0f + uo), static_cast((iy + s) / 128.0f + vo)); + t->vertexUV(static_cast(0 + s), static_cast(0 + s), static_cast(0), static_cast((ix + s) / 128.0f + uo), static_cast((iy + s) / 128.0f + vo)); + t->vertexUV(static_cast(0 + s), static_cast(0), static_cast(0), static_cast((ix + s) / 128.0f + uo), static_cast(iy / 128.0f + vo)); + t->vertexUV(static_cast(0), static_cast(0), static_cast(0), static_cast(ix / 128.0f + uo), static_cast(iy / 128.0f + vo)); // target.colorBlit(texture, x + xo, y, color, ix, iy, // charWidths[chars[i]], 8); t->end(); - glTranslatef((float)charWidths[i], 0, 0); + glTranslatef(static_cast(charWidths[i]), 0, 0); } else { diff --git a/Minecraft.Client/Font.h b/Minecraft.Client/Font.h index 18d9bf91..3d25880d 100644 --- a/Minecraft.Client/Font.h +++ b/Minecraft.Client/Font.h @@ -21,6 +21,12 @@ private: float xPos; float yPos; + // § format state (bold, italic, underline, strikethrough); set during draw(), reset by §r + bool m_bold; + bool m_italic; + bool m_underline; + bool m_strikethrough; + bool enforceUnicodeSheet; // use unicode sheet for ascii bool bidirectional; // use bidi to flip strings @@ -41,9 +47,11 @@ public: private: void renderCharacter(wchar_t c); // 4J added + void renderStyleLine(float x0, float y0, float x1, float y1); // solid line for underline/strikethrough public: void drawShadow(const wstring& str, int x, int y, int color); + void drawShadowLiteral(const wstring& str, int x, int y, int color); // draw without interpreting § codes void drawShadowWordWrap(const wstring &str, int x, int y, int w, int color, int h); // 4J Added h param void draw(const wstring &str, int x, int y, int color); /** @@ -58,11 +66,13 @@ private: void draw(const wstring &str, bool dropShadow); void draw(const wstring& str, int x, int y, int color, bool dropShadow); + void drawLiteral(const wstring& str, int x, int y, int color); // no § parsing int MapCharacter(wchar_t c); // 4J added bool CharacterExists(wchar_t c); // 4J added public: int width(const wstring& str); + int widthLiteral(const wstring& str); // width without skipping § codes (for chat input) wstring sanitize(const wstring& str); void drawWordWrap(const wstring &string, int x, int y, int w, int col, int h); // 4J Added h param diff --git a/Minecraft.Client/Gui.cpp b/Minecraft.Client/Gui.cpp index 1199c138..365ddeac 100644 --- a/Minecraft.Client/Gui.cpp +++ b/Minecraft.Client/Gui.cpp @@ -971,15 +971,10 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_ALPHA_TEST); -// 4J Stu - We have moved the chat text to a xui -#if 0 +#if defined(_WINDOWS64) glPushMatrix(); - // 4J-PB we need to move this up a bit because we've moved the quick select - //glTranslatef(0, ((float)screenHeight) - 48, 0); - glTranslatef(0.0f, (float)(screenHeight - iSafezoneYHalf - iTooltipsYOffset - 16 - 3 + 22) - 24.0f, 0.0f); - // glScalef(1.0f / ssc.scale, 1.0f / ssc.scale, 1); + glTranslatef(0.0f, static_cast(screenHeight - iSafezoneYHalf - iTooltipsYOffset - 16 - 3 + 22) - 24.0f, 0.0f); - // 4J-PB - we need gui messages for each of the possible 4 splitscreen players if(bDisplayGui) { int iPad=minecraft->player->GetXboxPad(); @@ -993,23 +988,21 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse) if (t < 0) t = 0; if (t > 1) t = 1; t = t * t; - int alpha = (int) (255 * t); + int alpha = static_cast(255 * t); if (isChatting) alpha = 255; if (alpha > 0) { int x = iSafezoneXHalf+2; - int y = -((int)i) * 9; + int y = -(static_cast(i)) * 9; if(bTwoPlayerSplitscreen) { y+= iHeightOffset; } wstring msg = guiMessages[iPad][i].string; - // 4J-PB - fill the black bar across the whole screen, otherwise it looks odd due to the safe area this->fill(0, y - 1, screenWidth/fScaleFactorWidth, y + 8, (alpha / 2) << 24); glEnable(GL_BLEND); - font->drawShadow(msg, iSafezoneXHalf+4, y, 0xffffff + (alpha << 24)); } } diff --git a/Minecraft.Client/GuiComponent.cpp b/Minecraft.Client/GuiComponent.cpp index 92abf36d..c6b5f518 100644 --- a/Minecraft.Client/GuiComponent.cpp +++ b/Minecraft.Client/GuiComponent.cpp @@ -48,10 +48,10 @@ void GuiComponent::fill(int x0, int y0, int x1, int y1, int col) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4f(r, g, b, a); t->begin(); - t->vertex((float)(x0), (float)( y1), (float)( 0)); - t->vertex((float)(x1), (float)( y1), (float)( 0)); - t->vertex((float)(x1), (float)( y0), (float)( 0)); - t->vertex((float)(x0), (float)( y0), (float)( 0)); + t->vertex(static_cast(x0), static_cast(y1), static_cast(0)); + t->vertex(static_cast(x1), static_cast(y1), static_cast(0)); + t->vertex(static_cast(x1), static_cast(y0), static_cast(0)); + t->vertex(static_cast(x0), static_cast(y0), static_cast(0)); t->end(); glEnable(GL_TEXTURE_2D); glDisable(GL_BLEND); @@ -77,11 +77,11 @@ void GuiComponent::fillGradient(int x0, int y0, int x1, int y1, int col1, int co Tesselator *t = Tesselator::getInstance(); t->begin(); t->color(r1, g1, b1, a1); - t->vertex((float)(x1), (float)( y0), blitOffset); - t->vertex((float)(x0), (float)( y0), blitOffset); + t->vertex(static_cast(x1), static_cast(y0), blitOffset); + t->vertex(static_cast(x0), static_cast(y0), blitOffset); t->color(r2, g2, b2, a2); - t->vertex((float)(x0), (float)( y1), blitOffset); - t->vertex((float)(x1), (float)( y1), blitOffset); + t->vertex(static_cast(x0), static_cast(y1), blitOffset); + t->vertex(static_cast(x1), static_cast(y1), blitOffset); t->end(); glShadeModel(GL_FLAT); @@ -105,6 +105,11 @@ void GuiComponent::drawString(Font *font, const wstring& str, int x, int y, int font->drawShadow(str, x, y, color); } +void GuiComponent::drawStringLiteral(Font *font, const wstring& str, int x, int y, int color) +{ + font->drawShadowLiteral(str, x, y, color); +} + void GuiComponent::blit(int x, int y, int sx, int sy, int w, int h) { float us = 1 / 256.0f; @@ -117,20 +122,20 @@ void GuiComponent::blit(int x, int y, int sx, int sy, int w, int h) const float extraShift = 0.75f; // 4J - subtracting extraShift (actual screen pixels, so need to compensate for physical & game width) from each x & y coordinate to compensate for centre of pixels in directx vs openGL - float dx = ( extraShift * (float)Minecraft::GetInstance()->width ) / (float)Minecraft::GetInstance()->width_phys; + float dx = ( extraShift * static_cast(Minecraft::GetInstance()->width) ) / static_cast(Minecraft::GetInstance()->width_phys); // 4J - Also factor in the scaling from gui coordinate space to the screen. This varies based on user-selected gui scale, and whether we are in a viewport mode or not dx /= Gui::currentGuiScaleFactor; float dy = extraShift / Gui::currentGuiScaleFactor; // Ensure that the x/y, width and height are actually pixel aligned at our current scale factor - in particular, for split screen mode with the default (3X) // scale, we have an overall scale factor of 3 * 0.5 = 1.5, and so any odd pixels won't align - float fx = (floorf((float)x * Gui::currentGuiScaleFactor)) / Gui::currentGuiScaleFactor; - float fy = (floorf((float)y * Gui::currentGuiScaleFactor)) / Gui::currentGuiScaleFactor; - float fw = (floorf((float)w * Gui::currentGuiScaleFactor)) / Gui::currentGuiScaleFactor; - float fh = (floorf((float)h * Gui::currentGuiScaleFactor)) / Gui::currentGuiScaleFactor; + float fx = (floorf(static_cast(x) * Gui::currentGuiScaleFactor)) / Gui::currentGuiScaleFactor; + float fy = (floorf(static_cast(y) * Gui::currentGuiScaleFactor)) / Gui::currentGuiScaleFactor; + float fw = (floorf(static_cast(w) * Gui::currentGuiScaleFactor)) / Gui::currentGuiScaleFactor; + float fh = (floorf(static_cast(h) * Gui::currentGuiScaleFactor)) / Gui::currentGuiScaleFactor; - t->vertexUV(fx + 0 - dx, fy + fh - dy, (float)( blitOffset), (float)( (sx + 0) * us), (float)( (sy + h) * vs)); - t->vertexUV(fx + fw - dx, fy + fh - dy, (float)( blitOffset), (float)( (sx + w) * us), (float)( (sy + h) * vs)); - t->vertexUV(fx + fw - dx, fy + 0 - dy, (float)( blitOffset), (float)( (sx + w) * us), (float)( (sy + 0) * vs)); - t->vertexUV(fx + 0 - dx, fy + 0 - dy, (float)( blitOffset), (float)( (sx + 0) * us), (float)( (sy + 0) * vs)); + t->vertexUV(fx + 0 - dx, fy + fh - dy, static_cast(blitOffset), static_cast((sx + 0) * us), static_cast((sy + h) * vs)); + t->vertexUV(fx + fw - dx, fy + fh - dy, static_cast(blitOffset), static_cast((sx + w) * us), static_cast((sy + h) * vs)); + t->vertexUV(fx + fw - dx, fy + 0 - dy, static_cast(blitOffset), static_cast((sx + w) * us), static_cast((sy + 0) * vs)); + t->vertexUV(fx + 0 - dx, fy + 0 - dy, static_cast(blitOffset), static_cast((sx + 0) * us), static_cast((sy + 0) * vs)); t->end(); } \ No newline at end of file diff --git a/Minecraft.Client/GuiComponent.h b/Minecraft.Client/GuiComponent.h index 7073db40..41496664 100644 --- a/Minecraft.Client/GuiComponent.h +++ b/Minecraft.Client/GuiComponent.h @@ -15,5 +15,6 @@ public: GuiComponent(); // 4J added void drawCenteredString(Font *font, const wstring& str, int x, int y, int color); void drawString(Font *font, const wstring& str, int x, int y, int color); + void drawStringLiteral(Font* font, const wstring& str, int x, int y, int color); void blit(int x, int y, int sx, int sy, int w, int h); }; diff --git a/Minecraft.Client/PlayerConnection.cpp b/Minecraft.Client/PlayerConnection.cpp index ab8ae20b..9404a5d6 100644 --- a/Minecraft.Client/PlayerConnection.cpp +++ b/Minecraft.Client/PlayerConnection.cpp @@ -21,6 +21,8 @@ #include "..\Minecraft.World\AABB.h" #include "..\Minecraft.World\Pos.h" #include "..\Minecraft.World\SharedConstants.h" +#include "..\Minecraft.World\ChatPacket.h" +#include "..\Minecraft.World\StringHelpers.h" #include "..\Minecraft.World\Socket.h" #include "..\Minecraft.World\Achievements.h" #include "..\Minecraft.World\net.minecraft.h" @@ -607,38 +609,26 @@ void PlayerConnection::handleSetCarriedItem(shared_ptr pac void PlayerConnection::handleChat(shared_ptr packet) { - // 4J - TODO -#if 0 - wstring message = packet->message; + if (packet->m_stringArgs.empty()) return; + wstring message = trimString(packet->m_stringArgs[0]); if (message.length() > SharedConstants::maxChatLength) { - disconnect(L"Chat message too long"); + disconnect(DisconnectPacket::eDisconnect_None); // or a specific reason return; } - message = message.trim(); - for (int i = 0; i < message.length(); i++) - { - if (SharedConstants.acceptableLetters.indexOf(message.charAt(i)) < 0 && (int) message.charAt(i) < 32) - { - disconnect(L"Illegal characters in chat"); - return; - } - } - - if (message.startsWith("/")) + // Optional: validate characters (acceptableLetters) + if (message.length() > 0 && message[0] == L'/') { handleCommand(message); - } else { - message = "<" + player.name + "> " + message; - logger.info(message); - server.players.broadcastAll(new ChatPacket(message)); + return; } + wstring formatted = L"<" + player->name + L"> " + message; + server->getPlayers()->broadcastAll(shared_ptr(new ChatPacket(formatted))); chatSpamTickCount += SharedConstants::TICKS_PER_SECOND; if (chatSpamTickCount > SharedConstants::TICKS_PER_SECOND * 10) { - disconnect("disconnect.spam"); + disconnect(DisconnectPacket::eDisconnect_None); // spam } -#endif } void PlayerConnection::handleCommand(const wstring& message) diff --git a/Minecraft.Client/Screen.cpp b/Minecraft.Client/Screen.cpp index 131cf013..9b753146 100644 --- a/Minecraft.Client/Screen.cpp +++ b/Minecraft.Client/Screen.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "Screen.h" #include "Button.h" +#include "ChatScreen.h" #include "GuiParticles.h" #include "Tesselator.h" #include "Textures.h" @@ -42,13 +43,32 @@ void Screen::keyPressed(wchar_t eventCharacter, int eventKey) wstring Screen::getClipboard() { - // 4J - removed - return NULL; +#ifdef _WINDOWS64 + if (!OpenClipboard(NULL)) return wstring(); + HANDLE h = GetClipboardData(CF_UNICODETEXT); + wstring out; + if (h) + { + const wchar_t *p = reinterpret_cast(GlobalLock(h)); + if (p) { out = p; GlobalUnlock(h); } + } + CloseClipboard(); + return out; +#else + return wstring(); +#endif } void Screen::setClipboard(const wstring& str) { - // 4J - removed +#ifdef _WINDOWS64 + if (!OpenClipboard(NULL)) return; + EmptyClipboard(); + size_t len = (str.length() + 1) * sizeof(wchar_t); + HGLOBAL h = GlobalAlloc(GMEM_MOVEABLE, len); + if (h) { memcpy(GlobalLock(h), str.c_str(), len); GlobalUnlock(h); SetClipboardData(CF_UNICODETEXT, h); } + CloseClipboard(); +#endif } void Screen::mouseClicked(int x, int y, int buttonNum) @@ -121,34 +141,88 @@ void Screen::updateEvents() } } - // Poll keyboard events + // Only drain WM_CHAR when this screen wants text input (e.g. ChatScreen); otherwise we'd steal keys from the game + if (dynamic_cast(this) != nullptr) + { + wchar_t ch; + while (g_KBMInput.ConsumeChar(ch)) + { + if (ch >= 0x20) + keyPressed(ch, -1); + else if (ch == 0x08) + keyPressed(0, Keyboard::KEY_BACK); + else if (ch == 0x0D) + keyPressed(0, Keyboard::KEY_RETURN); + } + } + + // Arrow key repeat: deliver on first press (when key down and last==0) and while held (throttled) + static DWORD s_arrowLastTime[2] = { 0, 0 }; + static bool s_arrowFirstRepeat[2] = { false, false }; + const DWORD ARROW_REPEAT_DELAY_MS = 250; + const DWORD ARROW_REPEAT_INTERVAL_MS = 50; + DWORD now = GetTickCount(); + + // Poll keyboard events (special keys that may not come through WM_CHAR, e.g. Escape, arrows) for (int vk = 0; vk < 256; vk++) { - if (g_KBMInput.IsKeyPressed(vk)) + bool deliver = g_KBMInput.IsKeyPressed(vk); + if (vk == VK_LEFT || vk == VK_RIGHT) { - // Map Windows virtual key to the Keyboard constants used by Screen::keyPressed - int mappedKey = -1; - wchar_t ch = 0; - if (vk == VK_ESCAPE) mappedKey = Keyboard::KEY_ESCAPE; - else if (vk == VK_RETURN) mappedKey = Keyboard::KEY_RETURN; - else if (vk == VK_BACK) mappedKey = Keyboard::KEY_BACK; - else if (vk == VK_UP) mappedKey = Keyboard::KEY_UP; - else if (vk == VK_DOWN) mappedKey = Keyboard::KEY_DOWN; - else if (vk == VK_LEFT) mappedKey = Keyboard::KEY_LEFT; - else if (vk == VK_RIGHT) mappedKey = Keyboard::KEY_RIGHT; - else if (vk == VK_LSHIFT || vk == VK_RSHIFT) mappedKey = Keyboard::KEY_LSHIFT; - else if (vk == VK_TAB) mappedKey = Keyboard::KEY_TAB; - else if (vk >= 'A' && vk <= 'Z') + int idx = (vk == VK_LEFT) ? 0 : 1; + if (!g_KBMInput.IsKeyDown(vk)) { - ch = (wchar_t)(vk - 'A' + L'a'); - if (g_KBMInput.IsKeyDown(VK_LSHIFT) || g_KBMInput.IsKeyDown(VK_RSHIFT)) ch = (wchar_t)vk; + s_arrowLastTime[idx] = 0; + s_arrowFirstRepeat[idx] = false; + } + else + { + DWORD last = s_arrowLastTime[idx]; + if (last == 0) + deliver = true; + else if (!deliver) + { + DWORD interval = s_arrowFirstRepeat[idx] ? ARROW_REPEAT_INTERVAL_MS : ARROW_REPEAT_DELAY_MS; + if ((now - last) >= interval) + { + deliver = true; + s_arrowFirstRepeat[idx] = true; + } + } + if (deliver) + s_arrowLastTime[idx] = now; } - else if (vk >= '0' && vk <= '9') ch = (wchar_t)vk; - else if (vk == VK_SPACE) ch = L' '; - - if (mappedKey != -1) keyPressed(ch, mappedKey); - else if (ch != 0) keyPressed(ch, -1); } + // Escape: deliver when key is down so we don't miss it (IsKeyPressed can be one frame late) + if (vk == VK_ESCAPE && g_KBMInput.IsKeyDown(VK_ESCAPE)) + deliver = true; + if (!deliver) continue; + + if (dynamic_cast(this) != nullptr && + (vk >= 'A' && vk <= 'Z' || vk >= '0' && vk <= '9' || vk == VK_SPACE || vk == VK_RETURN || vk == VK_BACK)) + continue; + // Map to Screen::keyPressed + int mappedKey = -1; + wchar_t ch = 0; + if (vk == VK_ESCAPE) mappedKey = Keyboard::KEY_ESCAPE; + else if (vk == VK_RETURN) mappedKey = Keyboard::KEY_RETURN; + else if (vk == VK_BACK) mappedKey = Keyboard::KEY_BACK; + else if (vk == VK_UP) mappedKey = Keyboard::KEY_UP; + else if (vk == VK_DOWN) mappedKey = Keyboard::KEY_DOWN; + else if (vk == VK_LEFT) mappedKey = Keyboard::KEY_LEFT; + else if (vk == VK_RIGHT) mappedKey = Keyboard::KEY_RIGHT; + else if (vk == VK_LSHIFT || vk == VK_RSHIFT) mappedKey = Keyboard::KEY_LSHIFT; + else if (vk == VK_TAB) mappedKey = Keyboard::KEY_TAB; + else if (vk >= 'A' && vk <= 'Z') + { + ch = static_cast(vk - 'A' + L'a'); + if (g_KBMInput.IsKeyDown(VK_LSHIFT) || g_KBMInput.IsKeyDown(VK_RSHIFT)) ch = static_cast(vk); + } + else if (vk >= '0' && vk <= '9') ch = static_cast(vk); + else if (vk == VK_SPACE) ch = L' '; + + if (mappedKey != -1) keyPressed(ch, mappedKey); + else if (ch != 0) keyPressed(ch, -1); } #else /* 4J - TODO @@ -232,10 +306,10 @@ void Screen::renderDirtBackground(int vo) float s = 32; t->begin(); t->color(0x404040); - t->vertexUV((float)(0), (float)( height), (float)( 0), (float)( 0), (float)( height / s + vo)); - t->vertexUV((float)(width), (float)( height), (float)( 0), (float)( width / s), (float)( height / s + vo)); - t->vertexUV((float)(width), (float)( 0), (float)( 0), (float)( width / s), (float)( 0 + vo)); - t->vertexUV((float)(0), (float)( 0), (float)( 0), (float)( 0), (float)( 0 + vo)); + t->vertexUV(static_cast(0), static_cast(height), static_cast(0), static_cast(0), static_cast(height / s + vo)); + t->vertexUV(static_cast(width), static_cast(height), static_cast(0), static_cast(width / s), static_cast(height / s + vo)); + t->vertexUV(static_cast(width), static_cast(0), static_cast(0), static_cast(width / s), static_cast(0 + vo)); + t->vertexUV(static_cast(0), static_cast(0), static_cast(0), static_cast(0), static_cast(0 + vo)); t->end(); #endif } diff --git a/Minecraft.Client/Screen.h b/Minecraft.Client/Screen.h index 50deb1d0..6b2cb945 100644 --- a/Minecraft.Client/Screen.h +++ b/Minecraft.Client/Screen.h @@ -36,9 +36,12 @@ protected: virtual void mouseReleased(int x, int y, int buttonNum); virtual void buttonClicked(Button *button); public: - virtual void init(Minecraft *minecraft, int width, int height); + virtual void init(Minecraft *minecraft, int width, int height); virtual void setSize(int width, int height); virtual void init(); + virtual void handlePasteRequest() {} + virtual void handleHistoryUp() {} + virtual void handleHistoryDown() {} virtual void updateEvents(); virtual void mouseEvent(); virtual void keyboardEvent(); diff --git a/Minecraft.Client/Windows64/KeyboardMouseInput.cpp b/Minecraft.Client/Windows64/KeyboardMouseInput.cpp index fe3c3919..d3e40806 100644 --- a/Minecraft.Client/Windows64/KeyboardMouseInput.cpp +++ b/Minecraft.Client/Windows64/KeyboardMouseInput.cpp @@ -257,8 +257,8 @@ bool KeyboardMouseInput::IsMouseButtonReleased(int button) const void KeyboardMouseInput::ConsumeMouseDelta(float &dx, float &dy) { - dx = (float)m_mouseDeltaAccumX; - dy = (float)m_mouseDeltaAccumY; + dx = static_cast(m_mouseDeltaAccumX); + dy = static_cast(m_mouseDeltaAccumY); m_mouseDeltaAccumX = 0; m_mouseDeltaAccumY = 0; } @@ -375,12 +375,12 @@ float KeyboardMouseInput::GetMoveY() const float KeyboardMouseInput::GetLookX(float sensitivity) const { - return (float)m_mouseDeltaX * sensitivity; + return static_cast(m_mouseDeltaX) * sensitivity; } float KeyboardMouseInput::GetLookY(float sensitivity) const { - return (float)(-m_mouseDeltaY) * sensitivity; + return static_cast(-m_mouseDeltaY) * sensitivity; } void KeyboardMouseInput::OnChar(wchar_t c) diff --git a/Minecraft.Client/Windows64/KeyboardMouseInput.h b/Minecraft.Client/Windows64/KeyboardMouseInput.h index fff924bf..0f14dfa1 100644 --- a/Minecraft.Client/Windows64/KeyboardMouseInput.h +++ b/Minecraft.Client/Windows64/KeyboardMouseInput.h @@ -143,4 +143,4 @@ private: extern KeyboardMouseInput g_KBMInput; -#endif // _WINDOWS64 +#endif diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp index fa4b8366..208fd3f7 100644 --- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp +++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp @@ -22,6 +22,9 @@ #include "..\..\Minecraft.World\net.minecraft.world.level.tile.h" #include "..\ClientConnection.h" +#include "..\Minecraft.h" +#include "..\ChatScreen.h" +#include "KeyboardMouseInput.h" #include "..\User.h" #include "..\..\Minecraft.World\Socket.h" #include "..\..\Minecraft.World\ThreadName.h" @@ -572,14 +575,28 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_CHAR: // Buffer typed characters so UIScene_Keyboard can dispatch them to the Iggy Flash player if (wParam >= 0x20 || wParam == 0x08 || wParam == 0x0D) // printable chars + backspace + enter - g_KBMInput.OnChar((wchar_t)wParam); + g_KBMInput.OnChar(static_cast(wParam)); break; case WM_KEYDOWN: case WM_SYSKEYDOWN: { - int vk = (int)wParam; - if (lParam & 0x40000000) break; // ignore auto-repeat + int vk = static_cast(wParam); + if ((lParam & 0x40000000) && vk != VK_LEFT && vk != VK_RIGHT && vk != VK_BACK) + break; +#ifdef _WINDOWS64 + Minecraft* pm = Minecraft::GetInstance(); + ChatScreen* chat = pm && pm->screen ? dynamic_cast(pm->screen) : nullptr; + if (chat) + { + if (vk == 'V' && (GetKeyState(VK_CONTROL) & 0x8000)) + { chat->handlePasteRequest(); break; } + if ((vk == VK_UP || vk == VK_DOWN) && !(lParam & 0x40000000)) + { if (vk == VK_UP) chat->handleHistoryUp(); else chat->handleHistoryDown(); break; } + if (vk >= '1' && vk <= '9') // Prevent hotkey conflicts + break; + } +#endif if (vk == VK_SHIFT) vk = (MapVirtualKey((lParam >> 16) & 0xFF, MAPVK_VSC_TO_VK_EX) == VK_RSHIFT) ? VK_RSHIFT : VK_LSHIFT; else if (vk == VK_CONTROL) @@ -592,7 +609,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_KEYUP: case WM_SYSKEYUP: { - int vk = (int)wParam; + int vk = static_cast(wParam); if (vk == VK_SHIFT) vk = (MapVirtualKey((lParam >> 16) & 0xFF, MAPVK_VSC_TO_VK_EX) == VK_RSHIFT) ? VK_RSHIFT : VK_LSHIFT; else if (vk == VK_CONTROL) @@ -1612,6 +1629,14 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, } } + // Open chat + if (g_KBMInput.IsKeyPressed('T') && app.GetGameStarted() && !ui.GetMenuDisplayed(0) && pMinecraft->screen == NULL) + { + g_KBMInput.ClearCharBuffer(); + pMinecraft->setScreen(new ChatScreen()); + SetFocus(g_hWnd); + } + #if 0 // has the game defined profile data been changed (by a profile load) if(app.uiGameDefinedDataChangedBitmask!=0)