- 桥梁与静态物体高度采样修复: - 严格对齐 40250 CMapOutdoor::GetHeight 与 CAttributeInstance::GetHeight - 解析 .mdatr 中的 AttributeHeight 网格,使用 is_in_triangle_2d 准确计算桥面多边形平面方程 - sample_height 查询邻近区块并返回 fMAX(fObjectHeight, fTerrainHeight),彻底解决走上桥面穿透掉入水底/河床的问题 - 新增 test_bridge_height_parity.gd 自动化对拍测试 - 40250 怪物击杀经验动效: - 1:1 实现 FLY_EXP(0) / FLY_HP / FLY_SP 粒子轨迹与爆炸吸附 - 40250 客户端全系统功能对齐(Batches 1-31): - 包含公会、交易、骑乘、变身、钓鱼、采矿、商城、信件、结婚、地牢等 134 套对拍系统与自动化回归测试 - 文档沉淀: - 新增 docs/CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md 客户端对拍缺陷发现与修复工程指南
125 lines
4.2 KiB
GDScript
125 lines
4.2 KiB
GDScript
# test_intro_select_parity.gd —— 40250 3D 角色选择大厅回归测试
|
|
extends SceneTree
|
|
|
|
const SystemClass = preload("res://intro_select_phase.gd")
|
|
|
|
var _passed: int = 0
|
|
var _failed: int = 0
|
|
|
|
func _assert(cond: bool, msg: String) -> void:
|
|
if cond:
|
|
_passed += 1
|
|
print(" [PASS] %s" % msg)
|
|
else:
|
|
_failed += 1
|
|
printerr(" [FAIL] %s" % msg)
|
|
|
|
func _init() -> void:
|
|
print("\n=== Running test_intro_select_parity (Batch 29 Direction 3) ===")
|
|
_test_slot_rotations_and_empty_selection()
|
|
_test_character_selection_and_stepping_motion()
|
|
_test_character_card_data()
|
|
_test_start_game_validation()
|
|
_test_7digit_pin_character_deletion()
|
|
|
|
print("\n=== Result: %d Passed, %d Failed ===" % [_passed, _failed])
|
|
if _failed == 0:
|
|
print("ALL TESTS PASSED!\n")
|
|
else:
|
|
printerr("SOME TESTS FAILED!\n")
|
|
quit(0 if _failed == 0 else 1)
|
|
|
|
func _test_slot_rotations_and_empty_selection() -> void:
|
|
print("\n--- Test 1: Slot Rotations & Empty Slot Handling ---")
|
|
var sel = SystemClass.new()
|
|
|
|
_assert(sel.character_slots.size() == 4, "Total 4 character slots")
|
|
_assert(SystemClass.SLOT_ROTATION == [135.0, 225.0, 315.0, 45.0], "Official 4-slot rotation angles match")
|
|
|
|
# Select empty slot 0
|
|
var r0 = sel.select_slot(0)
|
|
_assert(r0["is_empty_slot"] == true, "Slot 0 is empty")
|
|
_assert(r0["action"] == "PROMPT_CREATE", "Empty slot prompts to CREATE")
|
|
_assert(sel.is_character_stepped_forward == false, "No step forward motion for empty slot")
|
|
|
|
func _test_character_selection_and_stepping_motion() -> void:
|
|
print("\n--- Test 2: Character Selection & Step Forward Motion ---")
|
|
var sel = SystemClass.new()
|
|
var warrior = {
|
|
"name": "DragonSlayer",
|
|
"level": 75,
|
|
"job": SystemClass.JOB_WARRIOR,
|
|
"horse_level": 21,
|
|
"playtime_min": 12000,
|
|
"delete_code": "7654321",
|
|
"stats": { "con": 90, "int": 10, "str": 90, "dex": 40 }
|
|
}
|
|
sel.set_character_slot(1, warrior)
|
|
|
|
var r1 = sel.select_slot(1)
|
|
_assert(r1["success"] == true, "Selected slot 1")
|
|
_assert(r1["is_empty_slot"] == false, "Slot 1 is populated")
|
|
_assert(sel.is_character_stepped_forward == true, "Character stepped forward (MOTION_INTRO_SELECTED)")
|
|
_assert(r1["rotation_deg"] == 225.0, "Slot 1 rotation is 225 degrees")
|
|
|
|
func _test_character_card_data() -> void:
|
|
print("\n--- Test 3: Character Card Attributes ---")
|
|
var sel = SystemClass.new()
|
|
var sura = {
|
|
"name": "DarkMage",
|
|
"level": 60,
|
|
"job": SystemClass.JOB_SURA,
|
|
"horse_level": 11,
|
|
"playtime_min": 5000,
|
|
"stats": { "con": 50, "int": 90, "str": 40, "dex": 20 }
|
|
}
|
|
sel.set_character_slot(0, sura)
|
|
sel.select_slot(0)
|
|
|
|
var card = sel.get_selected_character_card()
|
|
_assert(card["name"] == "DarkMage", "Card name is DarkMage")
|
|
_assert(card["level"] == 60, "Card level is 60")
|
|
_assert(card["job_name"].contains("修罗"), "Job name is Sura")
|
|
_assert(card["stats"]["int"] == 90, "INT stat is 90")
|
|
|
|
func _test_start_game_validation() -> void:
|
|
print("\n--- Test 4: Start Game Flow ---")
|
|
var sel = SystemClass.new()
|
|
|
|
# Start on empty slot -> Rejected
|
|
sel.select_slot(2) # empty
|
|
var r_fail = sel.start_game()
|
|
_assert(r_fail["error_code"] == "EMPTY_SLOT", "Reject start game on empty slot")
|
|
|
|
# Populate and start
|
|
sel.set_character_slot(2, { "name": "Ninja", "level": 10, "job": 1 })
|
|
sel.select_slot(2)
|
|
var r_ok = sel.start_game()
|
|
_assert(r_ok["success"] == true, "Start game succeeded with valid character")
|
|
_assert(r_ok["character"]["name"] == "Ninja", "Transferred character into game")
|
|
|
|
func _test_7digit_pin_character_deletion() -> void:
|
|
print("\n--- Test 5: 7-Digit Security PIN Character Deletion ---")
|
|
var sel = SystemClass.new()
|
|
var char_to_del = {
|
|
"name": "DeleteMe",
|
|
"level": 1,
|
|
"job": 0,
|
|
"delete_code": "1234567"
|
|
}
|
|
sel.set_character_slot(3, char_to_del)
|
|
|
|
# 1. Invalid PIN length
|
|
var r_len = sel.delete_character(3, "123")
|
|
_assert(r_len["error_code"] == "INVALID_PIN_LENGTH", "Reject deletion if PIN != 7 digits")
|
|
|
|
# 2. Wrong PIN
|
|
var r_wrong = sel.delete_character(3, "9999999")
|
|
_assert(r_wrong["error_code"] == "WRONG_DELETE_PIN", "Reject deletion on incorrect PIN")
|
|
_assert(sel.character_slots[3] != null, "Character preserved on failed PIN")
|
|
|
|
# 3. Correct PIN (1234567)
|
|
var r_ok = sel.delete_character(3, "1234567")
|
|
_assert(r_ok["success"] == true, "Character deleted on correct 7-digit PIN")
|
|
_assert(sel.character_slots[3] == null, "Slot 3 is now null/empty")
|