Replaces the fixed centred arena with per-world tile geometry, which is the foundation the remaining features sit on. - MapGrid: tile grid with three independent flags -- blocks movement, bullets, sight -- so a pit stops feet but not bullets or eyes, and a barricade stops feet and bullets but not eyes. Circle collision with per-axis sliding, and Bresenham line of sight shared by fog and aggro. - MapGen: rooms and corridors generated from (seed, depth), with hand-authored boss arenas from Rooms stamped in first so a corridor can never carve through a designed fight. Reachability from spawn to boss is asserted over 40 seeds -- "usually connected" is the failure mode that ruins one run in twenty. - Dungeons are populated at creation, per room, instead of gating on waves. You explore and choose your fights; the run ends when the boss dies, not when the map is swept. Boss rooms have no lock, so walking out is always available. - Aggro: enemies need range AND line of sight, so a dungeon stays quiet until engaged and cover actually protects. - Hard fog, scrolling camera, and terrain rendering. Maps are streamed per peer in chunks around that peer's player, and the seed is deliberately NOT sent -- a client holding it could regenerate the whole dungeon, which is a map hack for free. Stream radius (900u) is wider than view radius (460u) because the client predicts movement against walls and simulates bullets that die on them; the accepted cost is a cheater seeing a little further than the fog, never the floor plan. Partial map knowledge means wall deaths must be announced rather than derived. A test caught the subtle half of that: out-of-bounds tiles read as WALL by design, so checking geometry before bounds reported every bullet leaving the map as a wall kill. 128 tests (was 103); check.sh, test.sh and smoke.sh pass. 0.26 ms/tick with 4 players and a boss, ~65x headroom.
This commit is contained in:
+15
-2
@@ -11,15 +11,27 @@ var _bound: ClientRuntime = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
world_view.position = get_viewport_rect().size * 0.5
|
||||
_recentre()
|
||||
get_viewport().size_changed.connect(_recentre)
|
||||
menu.return_to_hub_requested.connect(_on_return_to_hub)
|
||||
menu.disconnect_requested.connect(_on_disconnect)
|
||||
hud.respawn_pressed.connect(_on_respawn_pressed)
|
||||
|
||||
|
||||
var _screen_centre := Vector2.ZERO
|
||||
|
||||
|
||||
func _recentre() -> void:
|
||||
world_view.position = get_viewport_rect().size * 0.5
|
||||
_screen_centre = get_viewport_rect().size * 0.5
|
||||
|
||||
|
||||
## Scroll the world so the local player stays centred. Dungeons are larger than
|
||||
## the viewport now, so a fixed camera would simply lose the player off-screen.
|
||||
func _follow_camera() -> void:
|
||||
var focus := Vector2.ZERO
|
||||
if _bound != null:
|
||||
focus = _bound.predicted_pos
|
||||
world_view.position = _screen_centre - focus
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
@@ -27,6 +39,7 @@ func _process(_delta: float) -> void:
|
||||
_bound = Net.client
|
||||
if _bound != null and not _bound.local_hit.is_connected(_on_local_hit):
|
||||
_bound.local_hit.connect(_on_local_hit)
|
||||
_follow_camera()
|
||||
menu.set_in_dungeon(_bound != null
|
||||
and _bound.instance_kind == Protocol.InstanceKind.DUNGEON)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user