e1f9fa8096
ci / verify (push) Successful in 45s
A failed connection left the connect buttons disabled forever. _show_menu() returned early when a menu already existed, so the set_busy(false) that re-enables them never ran on the way back from _abort_connect -- the player was locked out of both joining and hosting with no way out but a restart. Also guards against a second attempt stacking on an in-flight one. Contact damage is gone as a concept: nothing in this game hurts you by touching it, because every threat should be a bullet you can see and dodge. The Stalker walked at you and dealt contact damage and nothing else, so it now carries a point-blank shotgun instead -- five pellets, 62 degrees, 18-tick lifetime for about 78px of reach, so it still has to close and leaves nothing lingering. tests/unit/test_content.gd asserts every hostile enemy has an emitter, so a new enemy cannot quietly reintroduce the mechanic. The menu's "Disconnect" DOES get the anti-cheese protection -- verified, not assumed. It calls Net.shutdown(), the socket closes, and the server takes the same linkdead path as a SIGKILL, because the channel is keyed on the socket closing rather than on how it closed. Made permanent: a --leave-after bot flag and two smoke assertions covering the polite exit alongside the hard kill, so a future "clean leave" message that skipped the channel would fail a test. The menu now says so out loud in a dungeon -- the mechanic only reads as fair if the cost is known before clicking. 103 tests, 12 smoke assertions; check.sh, test.sh and smoke.sh all pass.
79 lines
2.7 KiB
GDScript
79 lines
2.7 KiB
GDScript
extends GutTest
|
|
## Invariants every piece of content has to satisfy. These are cheap to write
|
|
## and they are what stops a new enemy from quietly reintroducing a mechanic the
|
|
## game has decided against.
|
|
|
|
const HOSTILES := [
|
|
Content.ENEMY_DRIFTER,
|
|
Content.ENEMY_TURRET,
|
|
Content.ENEMY_STALKER,
|
|
]
|
|
|
|
|
|
## The rule: nothing in this game hurts you by touching you. Every threat is a
|
|
## bullet you can see and dodge, so an enemy with no emitters is either inert
|
|
## scenery or a design mistake.
|
|
func test_every_hostile_enemy_actually_fires_something() -> void:
|
|
for id in HOSTILES:
|
|
var def := Content.enemy(id)
|
|
assert_gt(def.emitters.size(), 0,
|
|
"%s has no emitters -- it would be a harmless obstacle" % id)
|
|
|
|
|
|
func test_the_practice_dummy_is_inert() -> void:
|
|
var def := Content.enemy(Content.ENEMY_DUMMY)
|
|
assert_eq(def.emitters.size(), 0, "the hub target must never shoot back")
|
|
|
|
|
|
## Standing inside an enemy costs nothing by itself. Uses the dummy so the test
|
|
## measures contact alone, with no bullets in play to confuse the result.
|
|
func test_touching_an_enemy_deals_no_damage() -> void:
|
|
var world := SimWorld.new(1)
|
|
var p := world.add_player(1, "tester")
|
|
var e := world.spawn_enemy(Content.dummy(), Vector2.ZERO)
|
|
p.pos = e.pos
|
|
p.spawn_grace = 0
|
|
for _i in 120:
|
|
world.step()
|
|
assert_eq(p.hp, SimConfig.PLAYER_MAX_HP,
|
|
"contact damage is not a damage type in this game")
|
|
|
|
|
|
## The Stalker's burst replaces its old contact damage, so it has to be a real
|
|
## threat once it closes -- and harmless from across the arena.
|
|
func test_the_stalker_threatens_at_point_blank_only() -> void:
|
|
var near := SimWorld.new(1)
|
|
var pn := near.add_player(1, "tester")
|
|
pn.pos = Vector2.ZERO
|
|
pn.spawn_grace = 0
|
|
near.spawn_enemy(Content.stalker(), Vector2(40.0, 0.0))
|
|
for _i in 90:
|
|
near.step()
|
|
assert_lt(pn.hp, SimConfig.PLAYER_MAX_HP, "point blank has to hurt")
|
|
|
|
var far := SimWorld.new(1)
|
|
var pf := far.add_player(1, "tester")
|
|
pf.pos = Vector2(0.0, 300.0)
|
|
pf.spawn_grace = 0
|
|
# Pinned in place so it cannot close the distance during the test.
|
|
var e := far.spawn_enemy(Content.stalker(), Vector2(0.0, -300.0))
|
|
e.def = Content.stalker()
|
|
e.def.speed = 0.0
|
|
for _i in 90:
|
|
far.step()
|
|
assert_eq(pf.hp, SimConfig.PLAYER_MAX_HP,
|
|
"its bullets must expire long before they cross the arena")
|
|
|
|
|
|
func test_enemy_bullets_from_the_stalker_do_not_litter_the_arena() -> void:
|
|
var world := SimWorld.new(1)
|
|
var p := world.add_player(1, "tester")
|
|
p.pos = Vector2(0.0, 300.0)
|
|
var e := world.spawn_enemy(Content.stalker(), Vector2(0.0, -300.0))
|
|
e.def = Content.stalker()
|
|
e.def.speed = 0.0
|
|
for _i in 300:
|
|
world.step()
|
|
assert_lt(world.pool.live_count, 12,
|
|
"a short lifetime should keep spent point-blank shots from accumulating")
|