fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
This commit is contained in:
@@ -95,7 +95,63 @@ int main() {
|
||||
CHECK(es.get(30)->mov_after_func == FUNC_WAIT, "after-func latch cleared post-arrival");
|
||||
}
|
||||
|
||||
// --- 4) __CanProcessNetworkStatePacket gate (dead / knocked-down) ------
|
||||
// --- 4) FUNC_SKILL: 40250 eFunc & FUNC_SKILL ----------------------------
|
||||
// InstanceBase::StateProcess treats skill states like attack states: if the
|
||||
// destination is far away, walk first and call NEW_UseSkill on arrival. A
|
||||
// plain equality check loses the motion bits and turns the actor back to WAIT.
|
||||
{
|
||||
EntityStore es;
|
||||
es.set_now(0);
|
||||
spawn(es, 35, 0, 0);
|
||||
const uint8_t skill_func = FUNC_SKILL | 7; // motion 7, same wire encoding
|
||||
es.mut_move(35, 90.0f, skill_func, 220.0f, 0.0f, 0, /*arg*/ 3, 0);
|
||||
CHECK(es.get(35)->moving && es.get(35)->func == FUNC_MOVE,
|
||||
"FUNC_SKILL far: walks before casting");
|
||||
CHECK(es.get(35)->mov_after_func == skill_func && es.get(35)->mov_after_arg == 3,
|
||||
"FUNC_SKILL far: motion and skill arg latched for arrival");
|
||||
es.set_now(100000);
|
||||
es.tick();
|
||||
CHECK(!es.get(35)->moving && es.get(35)->func == skill_func,
|
||||
"FUNC_SKILL far: skill state survives arrival");
|
||||
CHECK(es.get(35)->func_arg == 3,
|
||||
"FUNC_SKILL far: OnUseSkill arg survives the walk-to-skill transition");
|
||||
CHECK(es.get(35)->mov_after_func == FUNC_WAIT, "FUNC_SKILL latch cleared post-arrival");
|
||||
|
||||
// Near skills must also act immediately, not fall through to WAIT.
|
||||
es.mut_move(35, 180.0f, FUNC_SKILL | 2, 230.0f, 0.0f, 0, 1, 0);
|
||||
CHECK(!es.get(35)->moving && es.get(35)->func == (FUNC_SKILL | 2),
|
||||
"FUNC_SKILL near: snaps and casts immediately");
|
||||
CHECK(es.get(35)->func_arg == 1,
|
||||
"FUNC_SKILL near: OnUseSkill arg is retained on immediate cast");
|
||||
}
|
||||
|
||||
// --- 4b) FUNC_EMOTION: 100cm threshold + deferred action ---------------
|
||||
// InstanceBase::StateProcess gives emotion its own threshold: a remote actor
|
||||
// farther than 100cm walks first, then executes the emotion at the destination.
|
||||
{
|
||||
EntityStore es;
|
||||
es.set_now(0);
|
||||
spawn(es, 36, 0, 0);
|
||||
es.mut_move(36, 90.0f, FUNC_EMOTION, 150.0f, 0.0f, 0, /*arg*/ 42, 0);
|
||||
CHECK(es.get(36)->moving && es.get(36)->func == FUNC_MOVE,
|
||||
"FUNC_EMOTION far: walks before emotion");
|
||||
CHECK(es.get(36)->mov_after_func == FUNC_EMOTION && es.get(36)->mov_after_arg == 42,
|
||||
"FUNC_EMOTION far: emotion is latched for arrival");
|
||||
CHECK(!es.get(36)->skip_collision,
|
||||
"FUNC_EMOTION far: remote emotion walk keeps actor collision enabled");
|
||||
es.set_now(100000);
|
||||
es.tick();
|
||||
CHECK(!es.get(36)->moving && es.get(36)->func == FUNC_EMOTION &&
|
||||
es.get(36)->func_arg == 42,
|
||||
"FUNC_EMOTION far: emotion survives arrival");
|
||||
|
||||
es.mut_move(36, 180.0f, FUNC_EMOTION, 180.0f, 0.0f, 0, /*arg*/ 7, 0);
|
||||
CHECK(!es.get(36)->moving && es.get(36)->func == FUNC_EMOTION &&
|
||||
es.get(36)->func_arg == 7,
|
||||
"FUNC_EMOTION near: snaps and acts immediately");
|
||||
}
|
||||
|
||||
// --- 5) __CanProcessNetworkStatePacket gate (dead / knocked-down) ------
|
||||
{
|
||||
EntityStore es;
|
||||
es.set_now(0);
|
||||
@@ -117,6 +173,46 @@ int main() {
|
||||
"dead: state cmd held");
|
||||
}
|
||||
|
||||
// --- 5b) skill/emotion TCP gates -----------------------------------------
|
||||
// 40250 checks both __CanProcessNetworkStatePacket (non-cancellable skill)
|
||||
// and __IsEnableTCPProcess (active emotion / disabled TCP state). The gate
|
||||
// is presentation-owned and must release the already queued command when
|
||||
// the one-shot motion reaches its tail.
|
||||
{
|
||||
EntityStore es;
|
||||
es.set_now(0);
|
||||
spawn(es, 43, 0, 0);
|
||||
es.mut_network_state_gate(43, true, false, false, true);
|
||||
es.mut_move(43, 0.0f, FUNC_MOVE, 300.0f, 0.0f, 0, 0, 0);
|
||||
CHECK(es.get(43)->state_queue.size() == 1 && !es.get(43)->moving,
|
||||
"non-cancellable skill: state cmd held");
|
||||
es.mut_network_state_gate(43, false, true, false, true);
|
||||
es.process_states();
|
||||
CHECK(es.get(43)->state_queue.empty() && es.get(43)->moving,
|
||||
"skill motion tail: queued state releases");
|
||||
|
||||
spawn(es, 44, 0, 0);
|
||||
es.mut_network_state_gate(44, false, true, true, false);
|
||||
es.mut_move(44, 0.0f, FUNC_MOVE, 300.0f, 0.0f, 0, 0, 0);
|
||||
CHECK(es.get(44)->state_queue.size() == 1 && !es.get(44)->moving,
|
||||
"active emotion: later TCP state held");
|
||||
es.mut_network_state_gate(44, false, true, false, true);
|
||||
es.process_states();
|
||||
CHECK(es.get(44)->state_queue.empty() && es.get(44)->moving,
|
||||
"emotion motion tail: queued state releases");
|
||||
|
||||
spawn(es, 45, 0, 0);
|
||||
es.mut_network_state_gate(45, false, true, false, false);
|
||||
es.mut_move(45, 0.0f, FUNC_MOVE, 300.0f, 0.0f, 0, 0, 0);
|
||||
CHECK(es.get(45)->state_queue.size() == 1 && !es.get(45)->moving,
|
||||
"TCP disabled: non-emotion state held");
|
||||
spawn(es, 46, 0, 0);
|
||||
es.mut_network_state_gate(46, false, true, false, false);
|
||||
es.mut_move(46, 0.0f, FUNC_EMOTION, 0.5f, 0.0f, 0, 9, 0);
|
||||
CHECK(es.get(46)->state_queue.empty() && es.get(46)->func == FUNC_EMOTION,
|
||||
"TCP disabled: FUNC_EMOTION remains allowed by eCurFunc exception");
|
||||
}
|
||||
|
||||
// --- 5) queue preserves order and drains multiple due commands --------
|
||||
{
|
||||
EntityStore es;
|
||||
@@ -223,6 +319,26 @@ int main() {
|
||||
"FUNC_MOVE overshoot: returns to Dst and stops");
|
||||
}
|
||||
|
||||
// --- 8b) remote NPC/MOB FUNC_MOVE keeps advancing past Dst ---------------
|
||||
// 40250 MovementProcess has one non-main branch for PCs, NPCs and monsters.
|
||||
// When m_kMovAfterFunc is FUNC_MOVE, the arrival switch deliberately does
|
||||
// nothing: the actor remains in motion and the next network command (or the
|
||||
// >100cm overshoot guard) owns the eventual stop. A ch_type-based early stop
|
||||
// is therefore not equivalent and makes remote mobs stutter at every packet.
|
||||
{
|
||||
EntityStore es;
|
||||
es.set_now(0);
|
||||
es.mut_spawn(81, 101, 2, "Wolf", 0, 0, 0, 0, 100, 0);
|
||||
es.set_motion_speed(81, 0.0f, 1000.0f);
|
||||
es.tick();
|
||||
es.mut_move(81, 0.0f, FUNC_MOVE, 100.0f, 0.0f, 0, 0, 0);
|
||||
es.set_now(100);
|
||||
es.tick();
|
||||
CHECK(es.get(81)->moving && es.get(81)->x >= 100.0f &&
|
||||
es.get(81)->mov_after_func == FUNC_MOVE && es.get(81)->skip_collision,
|
||||
"remote NPC/MOB FUNC_MOVE: arrival does not stop network walk");
|
||||
}
|
||||
|
||||
// --- 9) unknown motion speed keeps the dwDuration lerp, heading Src->Dst -----
|
||||
{
|
||||
EntityStore es;
|
||||
@@ -235,6 +351,20 @@ int main() {
|
||||
CHECK(std::abs(es.get(90)->y + 250.0f) < 1e-3f, "duration walk: lerps over dwDuration");
|
||||
}
|
||||
|
||||
// --- 10) POINT_EXP: preserve the authoritative packet delta --------------
|
||||
// 40250 shows the point effect from TPacketGCPointChange.amount. The
|
||||
// absolute value can decrease at level-up, so comparing only old/new EXP is
|
||||
// not equivalent.
|
||||
{
|
||||
EntityStore es;
|
||||
es.mut_spawn_main(1, 1, "Hero", 0, 0, 0);
|
||||
es.mut_set_point(POINT_EXP, 5, 1, 125);
|
||||
es.mut_set_point(POINT_EXP, 20, 1, 15);
|
||||
CHECK(es.take_points_dirty(), "POINT_EXP changes dirty player points");
|
||||
CHECK(es.take_exp_gain() == 140, "POINT_EXP amount accumulates across one pump");
|
||||
CHECK(es.take_exp_gain() == 0, "POINT_EXP gain is consumed exactly once");
|
||||
}
|
||||
|
||||
if (g_fail == 0) {
|
||||
std::printf("PASS: net_state_queue_test (§3.2 TCP state queue / thresholds / gates)\n");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user