extends GutTest ## HUD and connect-screen behaviour that does not need a screen to check. ## What things LOOK like is screenshot.tscn's job; this is what they DO. const HUD := preload("res://src/ui/hud.gd") const MENU := preload("res://src/ui/main_menu.gd") const BACKDROP := preload("res://src/ui/menu_backdrop.gd") func _hud_with(hp: int, max_hp: int) -> Array: var hud: CanvasLayer = HUD.new() add_child_autofree(hud) var c: ClientRuntime = autofree(ClientRuntime.new()) c.my_hp = hp c.my_max_hp = max_hp hud.client = c hud._hp_trail = float(hp) / float(max_hp) hud._hp_last = hud._hp_trail return [hud, c] # --- The damage trail ------------------------------------------------------- func test_a_hit_leaves_a_trail_where_the_bar_was() -> void: var pair := _hud_with(1000, 1000) var hud: CanvasLayer = pair[0] var c: ClientRuntime = pair[1] c.my_hp = 400 hud._watch_health(0.016) assert_almost_eq(hud._hp_trail, 1.0, 0.001, "the trail holds at the old value") assert_almost_eq(hud._hp_last, 0.4, 0.001) func test_the_trail_holds_and_then_drains_to_meet_the_bar() -> void: var pair := _hud_with(1000, 1000) var hud: CanvasLayer = pair[0] var c: ClientRuntime = pair[1] c.my_hp = 400 hud._watch_health(0.016) # Within the hold, nothing moves -- that pause is what makes the amount # readable. for _i in int(HUD.TRAIL_HOLD / 0.016) - 2: hud._watch_health(0.016) assert_almost_eq(hud._hp_trail, 1.0, 0.001, "still holding") for _i in 200: hud._watch_health(0.016) assert_almost_eq(hud._hp_trail, 0.4, 0.001, "and then drained all the way down") func test_healing_leaves_no_trail() -> void: var pair := _hud_with(400, 1000) var hud: CanvasLayer = pair[0] var c: ClientRuntime = pair[1] c.my_hp = 900 hud._watch_health(0.016) assert_almost_eq(hud._hp_trail, 0.9, 0.001, "a trail is for what was lost; on a heal the bar and trail move together") func test_a_second_hit_during_the_hold_keeps_the_original_top() -> void: var pair := _hud_with(1000, 1000) var hud: CanvasLayer = pair[0] var c: ClientRuntime = pair[1] c.my_hp = 700 hud._watch_health(0.016) c.my_hp = 300 hud._watch_health(0.016) assert_almost_eq(hud._hp_trail, 1.0, 0.001, "two quick hits read as one big one, measured from where it started") # --- Experience ------------------------------------------------------------- func test_experience_arriving_quickly_is_summed_into_one_number() -> void: var pair := _hud_with(1000, 1000) var hud: CanvasLayer = pair[0] var c: ClientRuntime = pair[1] hud._seen_character = "a" c.my_total_xp = 100 hud._watch_experience(0.016) c.my_total_xp = 112 hud._watch_experience(0.016) c.my_total_xp = 120 hud._watch_experience(0.016) assert_eq(hud._xp_gain, 20, "+12 then +8 shows as +20, not as a flicker") func test_the_first_sight_of_a_character_is_not_a_gain() -> void: var pair := _hud_with(1000, 1000) var hud: CanvasLayer = pair[0] var c: ClientRuntime = pair[1] c.my_total_xp = 5000 hud._watch_experience(0.016) assert_eq(hud._xp_flash, 0.0, "logging in with 5000 experience did not just earn it") # --- The connect screen ----------------------------------------------------- func _menu() -> Control: var m: Control = MENU.new() add_child_autofree(m) return m func test_a_port_that_cannot_connect_disables_join_and_says_why() -> void: var m := _menu() m._port.text = "twenty" m._validate() assert_true(m._join_button.disabled) assert_true(m._host_button.disabled) assert_string_contains(m._status.text, "port") m._port.text = "70000" m._validate() assert_true(m._join_button.disabled, "out of range is as useless as not a number") m._port.text = "27015" m._validate() assert_false(m._join_button.disabled) assert_eq(m._status.text, "", "and the complaint goes when the problem does") func test_no_address_disables_join_but_not_host() -> void: var m := _menu() m._address.text = " " m._validate() assert_true(m._join_button.disabled, "nowhere to join") assert_false(m._host_button.disabled, "hosting needs no address") func test_busy_wins_over_valid() -> void: var m := _menu() m.set_busy(true) assert_true(m._join_button.disabled, "a connection in flight is not a second invitation") m.set_busy(false) assert_false(m._join_button.disabled) func test_the_connect_screen_asks_for_no_name() -> void: # It used to, and the answer went nowhere a person could see -- it only named # the characters bots create. var m := _menu() for node in m.find_children("*", "LineEdit", true, false): assert_ne((node as LineEdit).placeholder_text, "name") assert_eq(m.find_children("*", "LineEdit", true, false).size(), 2, "an address and a port, nothing else") func test_the_backdrop_opens_on_a_pattern_already_in_flow() -> void: # Every pattern starts at the centre of the screen, behind the panel. Primed, # the first frame already has bullets well clear of it. var b: Node2D = BACKDROP.new() add_child_autofree(b) var centre := b.get_viewport_rect().size * 0.5 var clear_of_panel := 0 for i in b._pool.high_water: if b._pool.alive[i] != 0 and b._pool.pos[i].distance_to(centre) > 320.0: clear_of_panel += 1 assert_gt(clear_of_panel, 10, "bullets beyond the panel on the very first frame") # --- The boss bar ----------------------------------------------------------- ## A HUD whose fog sees from the left of a room, with a wall down the middle. func _hud_with_sight() -> Array: var pair := _hud_with(1000, 1000) var hud: CanvasLayer = pair[0] var c: ClientRuntime = pair[1] var g := MapGrid.new(40, 20, MapGrid.Kind.FLOOR) for ty in 20: g.set_tile(20, ty, MapGrid.Kind.WALL) var sight := Visibility.new() sight.compute(g, Vector2(10.5, 10.5) * MapGrid.TILE, 460.0) hud.sight = sight c.instance_id = 3 c.boss_def = Content.boss(&"warden") return [hud, c] func _boss_at(x_tiles: float, phase: int = 0) -> Dictionary: return {"id": 1, "pos": Vector2(x_tiles, 10.5) * MapGrid.TILE, "phase": phase, "hp": 100} func test_the_bar_waits_until_the_boss_has_been_seen() -> void: var hud: CanvasLayer = _hud_with_sight()[0] # Behind the wall: in the snapshot -- the interest radius passes through # walls -- but not seen. assert_false(hud.track_boss(_boss_at(24.0)), "known about is not the same as seen") assert_true(hud.track_boss(_boss_at(16.0)), "in the light, it shows") func test_once_seen_it_stays_for_the_fight() -> void: var hud: CanvasLayer = _hud_with_sight()[0] hud.track_boss(_boss_at(16.0)) assert_true(hud.track_boss(_boss_at(24.0)), "stepping behind a pillar must not take the bar away") func test_a_new_dungeon_has_to_be_seen_again() -> void: var pair := _hud_with_sight() var hud: CanvasLayer = pair[0] var c: ClientRuntime = pair[1] hud.track_boss(_boss_at(16.0)) # Every dungeon's boss is actor 1, so the id alone would carry the last # fight's "seen" straight into the next one. c.instance_id = 4 assert_false(hud.track_boss(_boss_at(24.0))) func test_a_phase_change_is_announced_and_the_first_phase_is_not() -> void: var hud: CanvasLayer = _hud_with_sight()[0] hud.track_boss(_boss_at(16.0, 1)) assert_eq(hud._banner_title, "", "the phase it was in when found is not a change") hud.track_boss(_boss_at(16.0, 2)) assert_eq(hud._banner_title, Content.boss(&"warden").phases[2].name.to_upper())