extends GutTest ## Aggro is what turns a populated map into an explorable one: a dungeon has to ## be quiet until you engage it, and cover has to actually protect you. var world: SimWorld var map: MapGrid func before_each() -> void: map = MapGrid.new(40, 20, MapGrid.Kind.WALL) map.fill_rect(Rect2i(1, 1, 38, 18), MapGrid.Kind.FLOOR) map.centre_on_origin() world = SimWorld.new(1) world.set_map(map) func _turret_at(tx: int, ty: int) -> SimEnemy: return world.spawn_enemy(Content.turret(), map.tile_centre(tx, ty)) func _player_at(tx: int, ty: int) -> SimPlayer: var p := world.add_player(1, "tester") p.pos = map.tile_centre(tx, ty) p.spawn_grace = 0 return p func test_an_enemy_out_of_range_never_fires() -> void: var e := _turret_at(2, 10) _player_at(37, 10) assert_gt(e.pos.distance_to(world.players[1].pos), Content.turret().aggro_range, "setup: the player must actually be out of range") for _i in 400: world.step() assert_eq(world.pool.live_count, 0, "a dungeon has to stay quiet until you walk into it") assert_null(e.target) func test_an_enemy_in_range_and_in_sight_fires() -> void: var e := _turret_at(10, 10) _player_at(16, 10) for _i in 400: world.step() assert_gt(world.pool.live_count, 0) assert_not_null(e.target) ## Cover is the whole point of walls. An enemy that shoots through them makes ## every wall decorative. func test_a_wall_between_them_breaks_aggro() -> void: var e := _turret_at(10, 10) _player_at(16, 10) for ty in range(1, 19): map.set_tile(13, ty, MapGrid.Kind.WALL) for _i in 400: world.step() assert_eq(world.pool.live_count, 0, "no line of sight, no shooting") assert_null(e.target) func test_a_barricade_does_not_break_aggro() -> void: # You can see over a barricade, so it does not hide you -- it only stops # what you and it are shooting. var e := _turret_at(10, 10) _player_at(16, 10) for ty in range(1, 19): map.set_tile(13, ty, MapGrid.Kind.BARRICADE) for _i in 200: world.step() assert_not_null(e.target, "a chest-high barricade is not concealment") func test_a_chasing_enemy_stops_when_it_loses_you() -> void: var e := world.spawn_enemy(Content.stalker(), map.tile_centre(10, 10)) var p := _player_at(14, 10) for _i in 30: world.step() var closed: float = e.pos.distance_to(p.pos) assert_lt(closed, map.tile_centre(10, 10).distance_to(p.pos), "setup: the stalker should have closed some distance") # Walk out of range; it must give up rather than follow across the map. p.pos = map.tile_centre(37, 18) var before: Vector2 = e.pos for _i in 120: world.step() assert_almost_eq(e.pos.distance_to(before), 0.0, 0.001, "an enemy that never disengages makes retreating impossible")