Batch 4-b: verify combat on the port route

The fake server spawns a stray dog outside the village's BANPK safe disc
and answers CG_TARGET/CG_ATTACK with GC_TARGET/GC_DAMAGE_INFO/GC_DEAD.
port.login_flow and python_game_render_test hold the left button on it
(MBF_SMART -> __OnPressActor) until it dies. Adds the host/test helper
PythonBoot::Evaluate / Metin2PythonHost.evaluate.

python_3d_surface: emission is now multiplied by the texture, like
D3D's emissive-then-MODULATE, which removes the grey click-glow quad and
the grey wash on characters. Damage numbers wait on CPythonSystem (4-g).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-24 09:13:20 +09:00
co-authored by Claude Opus 5.5
parent 6ea0fa9889
commit 85e176bdd5
11 changed files with 234 additions and 2 deletions
+81
View File
@@ -311,4 +311,85 @@ func run() -> void:
report["far_ground_texture_pairs"] = far_pairs
_check(far_pairs > 20, "map behind the actor visible (%d local contrasts)" % far_pairs)
ui.visible = true
# Combat (批次 4-b) with Godot mouse events: hold the left button on the fake server's stray dog; the port's
# CPythonPlayer walks out of the village's safe zone and attacks, the fake server's GC_TARGET updates reach
# uiTarget.TargetBoard.SetHP (wrapped here to record them) and GC_DEAD comes with the 0 % update.
if in_game:
var dog_vid := 0x23456
_check(_py("import uiTarget\n"
+ "_target_hp = []\n"
+ "_set_hp = uiTarget.TargetBoard.SetHP\n"
+ "def _record_hp(board, hp):\n"
+ "\t_target_hp.append(hp)\n"
+ "\t_set_hp(board, hp)\n"
+ "uiTarget.TargetBoard.SetHP = _record_hp"), "target HP recorder installed")
var dog_screen := func() -> Vector2:
var parts := Metin2PythonHost.evaluate("'%%d,%%d,%%d' %% __import__('chr').GetPixelPosition(%d)" % dog_vid).split(",")
if parts.size() != 3:
return Vector2(-1, -1)
var world_pos := Vector3(float(parts[0]), -float(parts[1]), float(parts[2]) + 40.0)
for draw in Metin2PythonHost.render3d_draws():
if draw["proj"][11] == -1.0:
return world.camera.unproject_position(world.d3d_transform(draw["view"]) * world_pos)
return Vector2(-1, -1)
var move := func(position: Vector2) -> void:
var event := InputEventMouseMotion.new()
event.position = position
event.global_position = position
root.push_input(event)
var button := func(pressed: bool, position: Vector2) -> void:
var event := InputEventMouseButton.new()
event.button_index = MOUSE_BUTTON_LEFT
event.pressed = pressed
event.position = position
event.global_position = position
root.push_input(event)
# The actor may still be walking from the earlier step, and the camera follows it: wait until the dog
# stands still on screen before every click.
var settled := func() -> Vector2:
var last := Vector2(-100, -100)
var now := Vector2.ZERO
for frame in 600:
await process_frame
now = dog_screen.call()
if now.distance_to(last) < 0.5:
break
last = now
return now
var at: Vector2 = await settled.call()
# The earlier walk does not end on the same spot every run, and the dog can land under the minimap or the
# taskbar: walk part of the way first (a ground click, which __OnClickGround turns into a move).
var clear := Rect2(40, 150, 780, 520)
for attempt in 3:
if clear.has_point(at):
break
var toward: Vector2 = clear.get_center().lerp(at, 0.5).clamp(clear.position, clear.end)
move.call(toward)
await process_frame
button.call(true, toward)
await process_frame
button.call(false, toward)
at = await settled.call()
report["dog_screen"] = str(at)
_check(at.x >= 0.0, "stray dog projected on screen (%s)" % str(at))
move.call(at)
await process_frame
button.call(true, at)
var mid_shot := false
var dead := await _pump_until(20, func():
var now: Vector2 = dog_screen.call()
if now.x >= 0.0:
at = now
move.call(at)
if not mid_shot and Metin2PythonHost.evaluate("33 in _target_hp") == "True" and DisplayServer.get_name() != "headless":
mid_shot = true
root.get_texture().get_image().save_png(output.path_join("combat_mid.png"))
return Metin2PythonHost.evaluate("_target_hp[-1:] == [0]") == "True")
button.call(false, at)
if DisplayServer.get_name() != "headless":
root.get_texture().get_image().save_png(output.path_join("combat.png"))
report["target_hp"] = Metin2PythonHost.evaluate("_target_hp")
_check(dead, "stray dog killed with the held left button (target HP %s)" % report["target_hp"])
_check(_py("uiTarget.TargetBoard.SetHP = _set_hp\ndel _set_hp, _record_hp, _target_hp"), "target HP recorder removed")
await _finish(output, report)