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:
co-authored by
Claude Opus 5.5
parent
6ea0fa9889
commit
85e176bdd5
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user