171 lines
7.3 KiB
GDScript
171 lines
7.3 KiB
GDScript
# p9_test —— P9:小地图 + 昼夜 + 天气 + 传送 headless 自检。
|
||
# godot --headless --path project --script p9_test.gd
|
||
extends SceneTree
|
||
|
||
const Minimap = preload("res://ui/minimap.gd")
|
||
const WorldTime = preload("res://world/world_time.gd")
|
||
const Weather = preload("res://fx/weather.gd")
|
||
|
||
class FakeClient extends Node:
|
||
signal time_changed(server_epoch: int)
|
||
signal channel_changed(channel: int)
|
||
signal npc_marks_changed()
|
||
signal land_areas_changed()
|
||
signal observer_event(kind: int, vid: int, pos: Vector2)
|
||
signal world_reset()
|
||
signal world_markers_changed()
|
||
signal warp(pos: Vector3, same_server: bool)
|
||
var _time := 0
|
||
var entities := []
|
||
var npc_marks := []
|
||
var markers := []
|
||
var observers := []
|
||
var land_areas := []
|
||
var party := []
|
||
func get_server_time() -> int: return _time
|
||
func get_entities() -> Array: return entities
|
||
func get_npc_marks() -> Array: return npc_marks
|
||
func get_world_markers() -> Array: return markers
|
||
func get_observers() -> Array: return observers
|
||
func get_land_areas() -> Array: return land_areas
|
||
func get_party() -> Array: return party
|
||
func get_channel() -> int: return 3
|
||
|
||
var _fail := 0
|
||
func _ck(c: bool, m: String) -> void:
|
||
if not c:
|
||
_fail += 1
|
||
printerr("FAIL: " + m)
|
||
|
||
func _init() -> void:
|
||
_run()
|
||
if _fail == 0:
|
||
print("PASS: p9_test (minimap + world_time + weather + warp)")
|
||
quit(0)
|
||
else:
|
||
printerr("%d check(s) failed" % _fail)
|
||
quit(1)
|
||
|
||
func _run() -> void:
|
||
var canvas := CanvasLayer.new()
|
||
get_root().add_child(canvas)
|
||
var fc := FakeClient.new()
|
||
get_root().add_child(fc)
|
||
var player := Node3D.new()
|
||
player.position = Vector3.ZERO
|
||
get_root().add_child(player)
|
||
|
||
# --- 小地图 ---
|
||
var mm: Node = Minimap.new()
|
||
get_root().add_child(mm)
|
||
mm.setup(fc, canvas, func() -> Node3D: return player)
|
||
_ck(mm._view != null, "minimap: view built")
|
||
fc.channel_changed.emit(7)
|
||
_ck(mm._ch_label.text == "CH 7", "minimap: channel label")
|
||
# _to_map: 北(−z 100m)应在圆心正上方附近;夹到半径内
|
||
var north: Vector2 = mm._to_map(Vector3(0, 0, -1000), Vector3.ZERO)
|
||
_ck(north.x == mm.RADIUS and north.y < mm.RADIUS, "minimap: north maps up, clamped")
|
||
var east: Vector2 = mm._to_map(Vector3(10, 0, 0), Vector3.ZERO)
|
||
_ck(east.x > mm.RADIUS and abs(east.y - mm.RADIUS) < 0.01, "minimap: east maps right")
|
||
var base_scale: float = mm.get_scale()
|
||
mm.set_scale(base_scale * 2.0)
|
||
var zoomed_east: Vector2 = mm._to_map(Vector3(10, 0, 0), Vector3.ZERO)
|
||
_ck(mm.get_scale() > base_scale and zoomed_east.x > east.x, "minimap: scale zooms world projection")
|
||
var wheel_scale: float = mm.get_scale()
|
||
var wheel := InputEventMouseButton.new()
|
||
wheel.button_index = MOUSE_BUTTON_WHEEL_DOWN
|
||
wheel.pressed = true
|
||
mm._on_gui_input(wheel)
|
||
_ck(mm.get_scale() < wheel_scale, "minimap: wheel down zooms out")
|
||
fc.entities = [{"vid": 11, "is_main": false, "ch_type": 0, "pos": Vector3(5, 0, 5)}]
|
||
fc.party = [{"vid": 11, "leader": true}]
|
||
fc.npc_marks = [{"type": 2, "name": "Gate", "pos": Vector3(3, 0, 3)}]
|
||
fc.markers = [{"id": 1, "name": "Quest", "pos": Vector3(-4, 0, -4)}]
|
||
fc.observers = [{"vid": 9001, "pos": Vector3(2, 0, -2)}]
|
||
fc.land_areas = [{"id": 42, "guild_id": 77, "x": -200, "y": -200, "width": 400, "height": 400}]
|
||
# ClientVS22's CPythonMiniMap interpolates observer moves for 1 second.
|
||
fc.observer_event.emit(mm.OBSERVER_ADD, 9001, Vector2(0, 0))
|
||
var observer_start: Vector3 = mm._observer_position(9001, Vector3.ZERO)
|
||
_ck(observer_start.distance_to(Vector3.ZERO) < 0.01, "minimap: observer add seeds current position")
|
||
fc.observer_event.emit(mm.OBSERVER_MOVE, 9001, Vector2(10, 4))
|
||
var observer_immediate: Vector3 = mm._observer_position(9001, Vector3.ZERO)
|
||
_ck(observer_immediate.distance_to(Vector3.ZERO) < 0.1, "minimap: observer move starts at current position")
|
||
await create_timer(1.05).timeout
|
||
var observer_final: Vector3 = mm._observer_position(9001, Vector3.ZERO)
|
||
_ck(observer_final.distance_to(Vector3(10, 0, 4)) < 0.1, "minimap: observer move reaches destination")
|
||
fc.observer_event.emit(mm.OBSERVER_REMOVE, 9001, Vector2.ZERO)
|
||
_ck(not mm._observer_tracks.has(9001), "minimap: observer remove clears track")
|
||
fc.observer_event.emit(mm.OBSERVER_ADD, 9001, Vector2(2, -2))
|
||
fc.world_reset.emit()
|
||
_ck(mm._observer_tracks.is_empty(), "minimap: world reset clears observer tracks")
|
||
mm._view.queue_redraw()
|
||
await process_frame
|
||
_ck(mm._view.is_visible_in_tree(), "minimap: redraw scheduled, view live")
|
||
|
||
# --- 任务信标:圆内画星,越出半径贴圆周画方向箭头(§5.2 世界箭头 / 屏幕边缘指示)---
|
||
var rim: float = mm.RADIUS - mm.SIGNAL_RIM_INSET
|
||
var near_geo: Dictionary = mm.signal_marker_geometry(Vector2(10, 0), rim)
|
||
_ck(not near_geo["clamped"] and near_geo["offset"] == Vector2(10, 0),
|
||
"minimap signal: inside rim keeps true offset")
|
||
var far_geo: Dictionary = mm.signal_marker_geometry(Vector2(400, 0), rim)
|
||
_ck(far_geo["clamped"] and abs(far_geo["offset"].length() - rim) < 0.01,
|
||
"minimap signal: outside rim clamps to circle")
|
||
_ck(abs(far_geo["angle"]) < 0.001 and abs(far_geo["offset"].x - rim) < 0.01,
|
||
"minimap signal: clamp keeps bearing to target (east)")
|
||
var diag_geo: Dictionary = mm.signal_marker_geometry(Vector2(-300, -300), rim)
|
||
_ck(diag_geo["clamped"] and abs(diag_geo["angle"] - atan2(-300.0, -300.0)) < 0.001
|
||
and abs(diag_geo["offset"].length() - rim) < 0.01,
|
||
"minimap signal: diagonal clamp preserves atan2 bearing")
|
||
var edge_geo: Dictionary = mm.signal_marker_geometry(Vector2(rim, 0), rim)
|
||
_ck(edge_geo["clamped"], "minimap signal: exactly on rim counts as clamped (>=)")
|
||
mm.add_signal_point(120000.0, 90000.0)
|
||
_ck(mm.signal_point_count() == 1, "minimap signal: add_signal_point stores waypoint")
|
||
mm._view.queue_redraw()
|
||
await process_frame
|
||
mm.clear_signal_points()
|
||
_ck(mm.signal_point_count() == 0, "minimap signal: clear_signal_points empties waypoints")
|
||
|
||
# --- 昼夜 ---
|
||
var sun := DirectionalLight3D.new()
|
||
get_root().add_child(sun)
|
||
var env := Environment.new()
|
||
env.background_mode = Environment.BG_COLOR
|
||
var wt: Node = WorldTime.new()
|
||
get_root().add_child(wt)
|
||
wt.setup(fc, sun, env)
|
||
# 正午:2025-01-01 12:00:00 UTC = 1735732800
|
||
fc._time = 1735732800
|
||
fc.time_changed.emit(1735732800)
|
||
_ck(abs(wt.day_fraction() - 0.5) < 0.01, "world_time: noon -> day_fraction ~0.5")
|
||
wt._process(0.016)
|
||
var noon_energy: float = sun.light_energy
|
||
_ck(noon_energy > 0.9, "world_time: noon sun bright (%.2f)" % noon_energy)
|
||
# 午夜
|
||
fc.time_changed.emit(1735732800 - 43200)
|
||
_ck(abs(wt.day_fraction()) < 0.01 or abs(wt.day_fraction() - 1.0) < 0.01,
|
||
"world_time: midnight -> fraction ~0")
|
||
wt._process(0.016)
|
||
_ck(sun.light_energy < 0.2, "world_time: midnight sun dark (%.3f)" % sun.light_energy)
|
||
|
||
# --- 天气 ---
|
||
var cam := Camera3D.new()
|
||
get_root().add_child(cam)
|
||
var w: Node3D = Weather.new()
|
||
get_root().add_child(w)
|
||
w.setup(cam)
|
||
_ck(w.kind() == "none" and not w._p.emitting, "weather: starts off")
|
||
w.set_weather("snow")
|
||
_ck(w._p.emitting and w.kind() == "snow", "weather: snow on")
|
||
var g_snow: Vector3 = w._p.process_material.gravity
|
||
w.set_weather("rain")
|
||
var g_rain: Vector3 = w._p.process_material.gravity
|
||
_ck(g_rain.y < g_snow.y, "weather: rain falls faster than snow")
|
||
w.set_weather("none")
|
||
_ck(not w._p.emitting, "weather: off again")
|
||
|
||
# --- 传送信号(同服)---
|
||
var got := []
|
||
fc.warp.connect(func(pos, same): got.append([pos, same]))
|
||
fc.warp.emit(Vector3(50, 0, -30), true)
|
||
_ck(got.size() == 1 and got[0][1] == true, "warp: same-server signal received")
|