Complete remaining client gap features

This commit is contained in:
shenlei
2026-09-04 13:25:05 +09:00
parent 13ccb8d02d
commit 9a4a044da5
47 changed files with 6667 additions and 140 deletions
+105
View File
@@ -0,0 +1,105 @@
# text_tail_arrange —— §8.8 seam ⑩ 收口:地面物品名条「投影后互相挤开防重叠」1:1。
# CPythonTextTail::ArrangeTextTail 的 m_ItemTextTailList 块(PythonTextTail.cpp:160-190):
# 逐条 item 尾标,与其它每条比 isIn(AABB 相交),命中就把自己顶到对方框底下
# `pCompareTextTail->y + pCompareTextTail->yEnd + yTemp`yTemp = 5),内层循环从头重跑,
# LimitCount 上限 20 次防死循环。isIn:489-507)是标准 AABB:两框在 x、y 都有交叠即真。
# 尾标框(:810-813 等):xStart = -w/2-2、xEnd = w/2+2、yStart = -2、yEnd = h。
# (与 name_color / text_tail / chat_tail / name_show / damage_effect / target_board /
# pick_show / name_tail_layout 同一套纯静态镜像套路,本模块是第 29 个 canonical。)
#
# REF40250 ClientVS22 source/ 为唯一基线):
# UserInterface/PythonTextTail.cpp:19 const float c_TextTail_Name_Position = -10.0f
# UserInterface/PythonTextTail.cpp:153 ArrangeTextTail()
# :160 for (pInsertTextTail : m_ItemTextTailList) —— 逐条 item 名条
# :164 int yTemp = 5; int LimitCount = 0;
# :167 for (pCompareTextTail : m_ItemTextTailList) —— 内层比对
# :171 if (*itorCompare == *itor) { ++itorCompare; continue; } —— 跳过自己
# :176 if (LimitCount >= 20) break;
# :180 if (isIn(pInsertTextTail, pCompareTextTail))
# :182 pInsertTextTail->y = pCompareTextTail->y + pCompareTextTail->yEnd + yTemp;
# :184 itorCompare = m_ItemTextTailList.begin(); ++LimitCount; continue; —— 从头重跑
# :189 ++itorCompare;
# UserInterface/PythonTextTail.cpp:489 isIn(pSource, pTarget)
# 框 Source = [x+xStart, x+xEnd] × [y+yStart, y+yEnd]Target 同;
# :500 if (x1S <= x2T && x2S >= x1T && y1S <= y2T && y2S >= y1T) return true;
# UserInterface/PythonTextTail.cpp:810-813 尾标框(SetTextTailOwnerText 等):
# xStart = -xSize/2 - 2、yStart = -2、xEnd = xSize/2 + 2、yEnd = ySizexSize/ySize = 文本像素尺寸)
# UserInterface/PythonTextTail.cpp:706-737 SetItemTextTailOwner(dwVID, c_szName):掉落名条
# 挂 owner 后缀。owner 非空时 owner 走独立 CGraphicTextInstance(黄 1,1,0 / CENTER / 文本
# = c_szName + strOwnershipIDS_POSSESSIVE_MORPHENE 为空取字面 "'s"),单独 GetTextSize
# :735 pTextTail->yEnd += float(yOwnerSize + 4) —— owner 另起一行、行距 4
# :736 pTextTail->xStart = fMIN(xStart, -xOwnerSize/2 - 1)
# :737 pTextTail->xEnd = fMAX(xEnd, xOwnerSize/2 + 1)
# owner 为空时删 owner 实例、框复位成纯名字 xStart=-w/2-2 / xEnd=w/2+2 / yEnd=h。
#
# seam ⑩:参考端在屏幕像素空间跑(y 向下),poc 地面物品名条是每物一个 billboard Label3D。
# 本模块保持屏幕像素语义(ground_items 把 tag 世界锚点投影到屏幕、按文本长度估框),算出
# 每条被下推的屏幕 y 后,poc 侧按 name_tail_layout 的同一 pixel_size 约定折成世界 y 向下的
# 位移(屏幕向下 Δpx → 世界向下 Δpx*pixel_size)。文本框尺寸按字符数估(参考端是
# GetTextSize 精确像素)= seam ⑩ 近似;只对 item 名条做,角色名字尾标不参与(同参考)。
extends RefCounted
const PUSH_GAP := 5.0 # ArrangeTextTail :164 的 yTemp
const MAX_ITERS := 20 # :176 的 LimitCount 上限(单条最多被下推的次数)
# 尾标框相对锚点 (x, y) 的四边(PythonTextTail.cpp:810-813)。屏幕坐标,y 向下。
static func box_start_x(w: float) -> float: return -w / 2.0 - 2.0
static func box_end_x(w: float) -> float: return w / 2.0 + 2.0
static func box_start_y() -> float: return -2.0
static func box_end_y(h: float) -> float: return h
# SetItemTextTailOwnerPythonTextTail.cpp:706-737)代入 poc 的 {w, h} 框:掉落名条挂上
# 「owner + 所有格语素」后,去重叠框在纯名字框(name_w / name_h)基础上按 owner 独立
# 文本度量(owner_w / owner_h)扩张:
# :735 yEnd += ownerH + 4 -> h = name_h + owner_h + 4
# :737 xEnd = fMAX(nameW/2+2, ownerW/2+1)
# box_end_x(w) = w/2 + 2,令它等于 fMAX 结果 -> w = max(name_w, owner_w - 2)
# (框 x 两边对称,box_start_x 自动 = 参考 :736 的 fMIN 结果,无需单独算)。
# owner_w / owner_h <= 0(无 owner,参考端删 owner 实例、框复位纯名字)-> {name_w, name_h}。
static func item_box_with_owner(name_w: float, name_h: float,
owner_w := 0.0, owner_h := 0.0) -> Dictionary:
if owner_w <= 0.0 or owner_h <= 0.0:
return {"w": name_w, "h": name_h}
return {"w": maxf(name_w, owner_w - 2.0), "h": name_h + owner_h + 4.0}
# isInPythonTextTail.cpp:489-507):两条尾标框在屏幕上是否相交(含边界,<= / >=)。
# a / b 为 {x, y, w, h}x, y = 锚点屏幕坐标;w, h = 文本像素尺寸)。
static func boxes_overlap(a: Dictionary, b: Dictionary) -> bool:
var ax1: float = a["x"] + box_start_x(a["w"])
var ay1: float = a["y"] + box_start_y()
var ax2: float = a["x"] + box_end_x(a["w"])
var ay2: float = a["y"] + box_end_y(a["h"])
var bx1: float = b["x"] + box_start_x(b["w"])
var by1: float = b["y"] + box_start_y()
var bx2: float = b["x"] + box_end_x(b["w"])
var by2: float = b["y"] + box_end_y(b["h"])
return ax1 <= bx2 and ax2 >= bx1 and ay1 <= by2 and ay2 >= by1
# ArrangeTextTail :160-190 的 item 块。tails: Array[{x, y, w, h}](屏幕坐标,y 向下),
# 列表顺序即参考端 m_ItemTextTailList 顺序。返回等长 Array[float]:每条排布后的屏幕 y
# (未重叠的保持原值)。逐条 pInsert 与其它每条 pCompare 比 isIn,命中就把 pInsert 顶到
# (pCompare.y + pCompare.yEnd + PUSH_GAP),内层从头重跑,单条最多下推 MAX_ITERS 次。
# pCompare 用的是「已排布后」的 y(与参考端共用一张链表、y 就地改写一致)。
static func arrange_item_tails(tails: Array) -> Array:
var ys: Array = []
for t in tails:
ys.append(float(t["y"]))
var n := tails.size()
for i in n:
var limit := 0
var j := 0
while j < n:
if j == i:
j += 1
continue
if limit >= MAX_ITERS:
break
var a := {"x": tails[i]["x"], "y": ys[i], "w": tails[i]["w"], "h": tails[i]["h"]}
var b := {"x": tails[j]["x"], "y": ys[j], "w": tails[j]["w"], "h": tails[j]["h"]}
if boxes_overlap(a, b):
ys[i] = ys[j] + box_end_y(tails[j]["h"]) + PUSH_GAP
j = 0
limit += 1
continue
j += 1
return ys