Now and then, a group of enemies in a tunnel
Tunnels are wide enough to fight in now, so occasionally one is occupied: each tunnel leg rolls a 10% chance of a group of two or three of one kind, standing within two tiles of each other. Most legs are too short or too near the entrance to qualify, so in practice it is about one depth-1 run in three (60 of 200 measured), rising with depth as tunnels get longer. Rare on purpose -- tunnels are where you go between fights, and a tunnel that is usually empty is what makes the one that is not a surprise. Never within aggro range of the entrance, checked per member rather than per group: members stand up to two tiles off the group's centre, so a centre that clears the entrance does not mean everyone does. The first version checked the centre, and the nearest member landed 15 units outside aggro range by luck. Never a lone straggler: a leg that cannot fit two gets nobody. Placed after every room, so each room's population is exactly what its seed always produced, and every leg rolls the same dice whether or not it is used, so one tunnel's luck never depends on another's shape. Scaled by the dungeon like every other spawn, each member from its own fresh definition. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
855669a665
commit
069abdb3fa
4 files changed
+184
-6
No files matched your search
@@ -138,3 +138,76 @@ func test_interacting_away_from_the_portal_does_nothing() -> void:
|
||||
var used := lobby.world.events.filter(
|
||||
func(e: Dictionary) -> bool: return int(e["t"]) == SimEvent.Type.PORTAL_USED)
|
||||
assert_eq(used.size(), 0)
|
||||
|
||||
|
||||
# --- Tunnel groups ----------------------------------------------------------
|
||||
|
||||
func _tunnel_enemies(run: Instance) -> Array[SimEnemy]:
|
||||
var out: Array[SimEnemy] = []
|
||||
for e in run.world.enemies.values():
|
||||
var t := run.world.map.to_tile(e.pos)
|
||||
if run.boss_room.has_point(t):
|
||||
continue
|
||||
var in_room := false
|
||||
for r in run.rooms:
|
||||
if r.grow(1).has_point(t):
|
||||
in_room = true
|
||||
if not in_room:
|
||||
out.append(e)
|
||||
return out
|
||||
|
||||
|
||||
func test_tunnels_are_occasionally_occupied_and_usually_not() -> void:
|
||||
# "Rarely" in numbers: across many depth-1 runs, some have a group in a
|
||||
# tunnel and most do not.
|
||||
var occupied := 0
|
||||
var runs := 80
|
||||
for s in range(1, runs + 1):
|
||||
if not _tunnel_enemies(Instance.make_dungeon(2, s * 7919, 1)).is_empty():
|
||||
occupied += 1
|
||||
assert_gt(occupied, runs / 10, "tunnel groups exist (%d of %d runs)" % [occupied, runs])
|
||||
assert_lt(occupied, runs / 2, "but a run with one is the exception (%d of %d)" % [occupied, runs])
|
||||
|
||||
|
||||
func test_a_tunnel_group_is_never_awake_when_you_arrive() -> void:
|
||||
var widest := Instance._widest_aggro()
|
||||
for s in range(1, 120):
|
||||
var run := Instance.make_dungeon(2, s * 7919, 1 + (s % 3))
|
||||
for e in _tunnel_enemies(run):
|
||||
assert_gt(e.pos.distance_to(run.world.spawn_point), widest,
|
||||
"seed %d: a tunnel %s stands within aggro range of the entrance"
|
||||
% [s * 7919, e.def.id])
|
||||
|
||||
|
||||
func test_a_tunnel_group_is_a_group() -> void:
|
||||
# Never a lone straggler, and never spread down the whole tunnel: every
|
||||
# member has another within a few tiles.
|
||||
var near := MapGrid.TILE * (Instance.TUNNEL_GROUP_SPREAD * 2 + 1)
|
||||
var seen := 0
|
||||
for s in range(1, 120):
|
||||
var found := _tunnel_enemies(Instance.make_dungeon(2, s * 7919, 1 + (s % 3)))
|
||||
for e in found:
|
||||
seen += 1
|
||||
var has_company := false
|
||||
for other in found:
|
||||
if other != e and other.pos.distance_to(e.pos) <= near:
|
||||
has_company = true
|
||||
assert_true(has_company, "seed %d: a tunnel %s on its own" % [s * 7919, e.def.id])
|
||||
assert_gt(seen, 0, "the sample produced at least one group to check")
|
||||
|
||||
|
||||
func test_a_tunnel_group_is_scaled_like_everything_else() -> void:
|
||||
# The Proving Grounds scales every enemy it spawns; a tunnel group must not
|
||||
# be the one thing that arrives at full health in the easy harness.
|
||||
for s in range(1, 120):
|
||||
var run := Instance.make_dungeon(2, s * 7919, 2, Dungeons.PROVING)
|
||||
for e in _tunnel_enemies(run):
|
||||
assert_lt(e.hp, Content.enemy(e.def.id).max_hp,
|
||||
"seed %d: a tunnel %s skipped the dungeon's scaling" % [s * 7919, e.def.id])
|
||||
|
||||
|
||||
func test_boss_rush_leaves_the_tunnels_empty_too() -> void:
|
||||
GameOpts.boss_rush = true
|
||||
var run := Instance.make_dungeon(2, 4242, 3)
|
||||
GameOpts.boss_rush = false
|
||||
assert_eq(_tunnel_enemies(run).size(), 0)
|
||||
Reference in new issue
Block a user