fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
// net_text_codec_test — §1.10 fixed-length wire text: to_wire() / from_wire_str()
|
||||
// must apply a hard BYTE cap (like the stock client's strncpy) while never
|
||||
// splitting a UTF-8 multi-byte sequence.
|
||||
// net_text_codec_test — §1.10 fixed-length wire text: the client keeps UTF-8
|
||||
// internally, while the Chinese 40250 wire uses GB2312-compatible bytes.
|
||||
#include "../src/net/text_codec.h"
|
||||
|
||||
#include <cstdio>
|
||||
@@ -16,7 +15,7 @@ static int g_fail = 0;
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// A CJK code point is 3 bytes in UTF-8 (U+6C49 "汉" = e6 b1 89).
|
||||
// U+6C49 "汉": 3 bytes in UTF-8, 2 bytes in GB2312 (BA BA).
|
||||
static std::string cjk(int n) {
|
||||
std::string s;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
@@ -26,75 +25,100 @@ static std::string cjk(int n) {
|
||||
}
|
||||
|
||||
int main() {
|
||||
using mtnet::to_wire;
|
||||
using mtnet::from_wire_str;
|
||||
using mtnet::to_wire;
|
||||
|
||||
// --- CHARACTER_NAME_MAX_LEN-equivalent cap = 24 bytes ---
|
||||
// 8 CJK chars == exactly 24 bytes: accepted whole.
|
||||
// CHARACTER_NAME_MAX_LEN is a 24-byte wire capacity. Twelve Chinese
|
||||
// characters fit; measuring the UTF-8 input would incorrectly allow eight.
|
||||
{
|
||||
const std::string in = cjk(8);
|
||||
CHECK(in.size() == 24, "8 CJK == 24 bytes");
|
||||
const std::string in = cjk(12);
|
||||
CHECK(in.size() == 36, "12 CJK == 36 UTF-8 bytes");
|
||||
const std::string w = to_wire(in, 24);
|
||||
CHECK(w.size() == 24 && w == in, "exactly-24 accepted unchanged");
|
||||
CHECK(w.size() == 24, "12 CJK == 24 GB2312 bytes");
|
||||
CHECK(from_wire_str(w.data(), w.size()) == in, "12 CJK round-trip through GB2312");
|
||||
CHECK(mtnet::wire_text_fits(in, 24), "12 CJK fits 24-byte name field");
|
||||
}
|
||||
|
||||
// 9 CJK chars == 27 bytes: truncated to 24 (8 chars), never 25/26.
|
||||
// Thirteen characters need 26 GB2312 bytes. Conversion truncates only at a
|
||||
// legacy character boundary, while input validation rejects the overlong name.
|
||||
{
|
||||
const std::string in = cjk(9);
|
||||
CHECK(in.size() == 27, "9 CJK == 27 bytes");
|
||||
const std::string in = cjk(13);
|
||||
CHECK(in.size() == 39, "13 CJK == 39 UTF-8 bytes");
|
||||
const std::string w = to_wire(in, 24);
|
||||
CHECK(w.size() == 24, "27 -> 24 bytes");
|
||||
CHECK(w == cjk(8), "truncated on the code-point boundary (8 chars)");
|
||||
CHECK(w.size() == 24, "13 CJK is capped at 24 wire bytes");
|
||||
CHECK(from_wire_str(w.data(), w.size()) == cjk(12), "cap keeps 12 whole CJK chars");
|
||||
CHECK(!mtnet::wire_text_fits(in, 24), "13 CJK rejected by 24-byte name field");
|
||||
}
|
||||
|
||||
// 25-byte input (8 CJK + 1 stray lead byte): a 24-byte cap keeps 8 chars,
|
||||
// and even a 25- or 26-byte cap cannot fit the 9th char -> still 24.
|
||||
// The preflight used by M2Client must use the selected protocol's rule.
|
||||
// A 32-byte 40250 shop-sign field therefore accepts sixteen GB2312 CJK
|
||||
// characters even though the UTF-8 input occupies 48 bytes. The generic
|
||||
// transport intentionally keeps its own byte contract.
|
||||
{
|
||||
std::string in = cjk(8);
|
||||
in += '\xE6'; // dangling lead byte of a 9th char
|
||||
CHECK(in.size() == 25, "25-byte input");
|
||||
CHECK(to_wire(in, 24).size() == 24, "cap 24 -> 24");
|
||||
CHECK(to_wire(in, 25) == cjk(8), "cap 25 -> 24 (no split)");
|
||||
CHECK(to_wire(in, 26) == cjk(8), "cap 26 -> 24 (9th char needs 3)");
|
||||
CHECK(mtnet::protocol_text_fits(cjk(16), 32, true),
|
||||
"classic preflight measures the encoded 32-byte sign");
|
||||
CHECK(!mtnet::protocol_text_fits(cjk(17), 32, true),
|
||||
"classic preflight rejects a 34-byte sign");
|
||||
CHECK(!mtnet::protocol_text_fits(cjk(16), 32, false),
|
||||
"generic preflight keeps its UTF-8 byte contract");
|
||||
}
|
||||
|
||||
// Mid-sequence cap: cutting 2 bytes into a 3-byte char drops the whole char.
|
||||
// Invalid UTF-8 is not leaked to the legacy wire and is rejected by the
|
||||
// strict validation helper.
|
||||
{
|
||||
const std::string in = cjk(4); // 12 bytes
|
||||
CHECK(to_wire(in, 11).size() == 9, "cap 11 -> 9 (3 whole chars)");
|
||||
CHECK(to_wire(in, 10).size() == 9, "cap 10 -> 9");
|
||||
CHECK(to_wire(in, 12) == in, "cap 12 -> all 12");
|
||||
std::string in = cjk(12);
|
||||
in += '\xE6'; // dangling UTF-8 lead byte
|
||||
CHECK(to_wire(in, 24).size() == 24, "invalid suffix cannot overflow cap");
|
||||
CHECK(!mtnet::wire_text_fits(in, 24), "invalid UTF-8 rejected");
|
||||
}
|
||||
|
||||
// ASCII round-trips against the same cap unchanged.
|
||||
// ASCII remains one byte per character and round-trips unchanged.
|
||||
{
|
||||
const std::string in = "Warrior";
|
||||
CHECK(to_wire(in, 24) == in, "ascii under cap unchanged");
|
||||
CHECK(to_wire(std::string(40, 'x'), 24).size() == 24, "ascii over cap -> 24");
|
||||
CHECK(from_wire_str(in.data(), in.size()) == in, "ascii round-trip");
|
||||
}
|
||||
|
||||
// --- from_wire_str: stop at NUL, then guard a dangling partial sequence ---
|
||||
// The user-visible UTF-8 text is not limited to the test character 汉. A
|
||||
// real Chinese name must round-trip through the 40250 GB2312 boundary too.
|
||||
{
|
||||
const std::string in = "龙驹简体中文";
|
||||
const std::string wire = to_wire(in, 24);
|
||||
CHECK(wire.size() == 12, "Chinese name uses GB2312 bytes, not UTF-8 bytes");
|
||||
CHECK(from_wire_str(wire.data(), wire.size()) == in,
|
||||
"Chinese name round-trips through the 40250 wire code page");
|
||||
}
|
||||
{
|
||||
const std::string in = "龙驹简体中文免费版";
|
||||
const std::string wire = to_wire(in, 24);
|
||||
CHECK(in.size() == 27, "target Chinese name is 27 UTF-8 bytes");
|
||||
CHECK(wire.size() == 18 && mtnet::wire_text_fits(in, 24),
|
||||
"target Chinese name fits the 24-byte GB2312 field");
|
||||
CHECK(from_wire_str(wire.data(), wire.size()) == in,
|
||||
"target Chinese name round-trips through the 40250 wire code page");
|
||||
}
|
||||
|
||||
// Decode legacy bytes up to NUL.
|
||||
{
|
||||
char buf[25] = {};
|
||||
const std::string src = cjk(3); // 9 bytes
|
||||
std::memcpy(buf, src.data(), src.size());
|
||||
CHECK(from_wire_str(buf, sizeof(buf) - 1) == src, "reads up to NUL");
|
||||
const std::string legacy = "\xBA\xBA\xBA\xBA\xBA\xBA";
|
||||
std::memcpy(buf, legacy.data(), legacy.size());
|
||||
CHECK(from_wire_str(buf, sizeof(buf) - 1) == cjk(3), "decodes GB2312 up to NUL");
|
||||
}
|
||||
{
|
||||
char buf[25];
|
||||
std::memset(buf, 0, sizeof(buf));
|
||||
const std::string src = cjk(8);
|
||||
std::memcpy(buf, src.data(), src.size());
|
||||
buf[24] = '\xE6'; // 25th byte: lead of a truncated char, no NUL room
|
||||
const std::string got = from_wire_str(buf, 25);
|
||||
CHECK(got == cjk(8), "dangling lead byte trimmed on read");
|
||||
char buf[25] = {};
|
||||
const std::string legacy = to_wire(cjk(12), 24);
|
||||
std::memcpy(buf, legacy.data(), legacy.size());
|
||||
buf[24] = '\xBA'; // dangling lead byte at the field boundary
|
||||
CHECK(from_wire_str(buf, 25) == cjk(12), "dangling legacy lead byte is ignored");
|
||||
}
|
||||
{
|
||||
// Full 24-byte field, no NUL terminator: read all 8 chars.
|
||||
// Full 24-byte field, no NUL terminator: read all twelve characters.
|
||||
char buf[24];
|
||||
const std::string src = cjk(8);
|
||||
std::memcpy(buf, src.data(), src.size());
|
||||
CHECK(from_wire_str(buf, 24) == src, "unterminated full field read whole");
|
||||
const std::string legacy = to_wire(cjk(12), 24);
|
||||
std::memcpy(buf, legacy.data(), legacy.size());
|
||||
CHECK(from_wire_str(buf, 24) == cjk(12), "unterminated full field read whole");
|
||||
}
|
||||
|
||||
if (g_fail) {
|
||||
|
||||
Reference in New Issue
Block a user