Compare commits

..
3 Commits
Author SHA1 Message Date
DottsGit 5f2c2462e2 feat: preventing players from leaving the playable area 2026-06-06 12:44:16 -04:00
DottsGit 64e024eb32 fix movement through the map is easier 2026-06-06 12:28:48 -04:00
DottsGit 050912ca61 fix: better sites 2026-06-06 12:26:03 -04:00
3 changed files with 150 additions and 35 deletions
+52 -35
View File
@@ -62,8 +62,9 @@ func _build_dust2_layout() -> void:
var carve_open = func(pos: Vector3, size: Vector3): var carve_open = func(pos: Vector3, size: Vector3):
var hole = CSGBox3D.new() var hole = CSGBox3D.new()
hole.operation = CSGShape3D.OPERATION_SUBTRACTION hole.operation = CSGShape3D.OPERATION_SUBTRACTION
hole.size = Vector3(size.x, 25, size.z) var height = 25.0 - pos.y
hole.position = Vector3(pos.x, 12.5, pos.z) hole.size = Vector3(size.x, height, size.z)
hole.position = Vector3(pos.x, pos.y + (height / 2.0), pos.z)
root_csg.add_child(hole) root_csg.add_child(hole)
return hole return hole
@@ -76,6 +77,28 @@ func _build_dust2_layout() -> void:
root_csg.add_child(hole) root_csg.add_child(hole)
return hole return hole
# Helper function to carve a precise slope (no lips or hard stops)
var carve_ramp = func(start_pos: Vector3, width: float, length: float, height: float, is_x_axis: bool, reverse: bool):
var ramp_box = CSGBox3D.new()
ramp_box.operation = CSGShape3D.OPERATION_SUBTRACTION
var slope = atan2(height, length)
var dir = 1.0 if reverse else -1.0
if not is_x_axis: # Ramp goes along Z
ramp_box.size = Vector3(width, 50, 50)
ramp_box.rotation.x = slope * dir
var mid_point = start_pos + Vector3(0, height / 2.0, (length / 2.0) * dir)
ramp_box.position = mid_point + (Vector3.UP.rotated(Vector3.RIGHT, ramp_box.rotation.x) * 25.0)
else: # Ramp goes along X
ramp_box.size = Vector3(50, 50, width)
ramp_box.rotation.z = -slope * dir
var mid_point = start_pos + Vector3((length / 2.0) * dir, height / 2.0, 0)
ramp_box.position = mid_point + (Vector3.UP.rotated(Vector3.FORWARD, ramp_box.rotation.z) * 25.0)
root_csg.add_child(ramp_box)
return ramp_box
# --------------------------------------------------------- # ---------------------------------------------------------
# LAYOUT (with generous overlaps to prevent getting stuck) # LAYOUT (with generous overlaps to prevent getting stuck)
# --------------------------------------------------------- # ---------------------------------------------------------
@@ -98,8 +121,10 @@ func _build_dust2_layout() -> void:
# --------------------------------------------------------- # ---------------------------------------------------------
# Lower B Tunnels (Connects from Mid at X=0 to West) # Lower B Tunnels (Connects from Mid at X=0 to West)
carve_tunnel.call(Vector3(-18, 0, 30), Vector3(40, 6, 8)) carve_tunnel.call(Vector3(-18, 0, 30), Vector3(40, 6, 8))
# Spiral stairs up to Upper B Tunnels
carve_tunnel.call(Vector3(-34, 0, 30), Vector3(12, 10, 12)) # Spiral stairs up to Upper B Tunnels (Using a ramp instead of stairs for smoothness)
carve_ramp.call(Vector3(-34, 0, 34), 8.0, 15.0, 4.0, false, false) # Ramp from Lower to Upper B
# Upper B Tunnels # Upper B Tunnels
carve_tunnel.call(Vector3(-34, 4, 15), Vector3(8, 6, 30)) carve_tunnel.call(Vector3(-34, 4, 15), Vector3(8, 6, 30))
@@ -111,13 +136,7 @@ func _build_dust2_layout() -> void:
carve_tunnel.call(Vector3(-15, 4, -18), Vector3(20, 4, 4)) # Window carve_tunnel.call(Vector3(-15, 4, -18), Vector3(20, 4, 4)) # Window
# CT Spawn Ramp to B Site # CT Spawn Ramp to B Site
carve_open.call(Vector3(-15, 0, -15), Vector3(12, 8, 12)) carve_ramp.call(Vector3(-15, 0, -15), 10.0, 15.0, 4.0, true, false) # Ramp up from CT to B Door
var ramp_carve = CSGBox3D.new()
ramp_carve.operation = CSGShape3D.OPERATION_SUBTRACTION
ramp_carve.size = Vector3(8, 10, 15)
ramp_carve.rotation_degrees.x = -25
ramp_carve.position = Vector3(-15, 2, -15)
root_csg.add_child(ramp_carve)
# --------------------------------------------------------- # ---------------------------------------------------------
# A SIDE # A SIDE
@@ -140,39 +159,24 @@ func _build_dust2_layout() -> void:
carve_open.call(Vector3(30, 4, -15), Vector3(30, 8, 30)) carve_open.call(Vector3(30, 4, -15), Vector3(30, 8, 30))
# Ramp up to A site from Long # Ramp up to A site from Long
var a_ramp = CSGBox3D.new() carve_ramp.call(Vector3(30, 0, -5), 15.0, 15.0, 4.0, false, false)
a_ramp.operation = CSGShape3D.OPERATION_SUBTRACTION
a_ramp.size = Vector3(15, 10, 15)
a_ramp.rotation_degrees.x = 25
a_ramp.position = Vector3(30, 2, -5)
root_csg.add_child(a_ramp)
# Ramp down from A Site to CT Spawn # Ramp down from A Site to CT Spawn
var ct_ramp = CSGBox3D.new() carve_ramp.call(Vector3(15, 0, -25), 15.0, 15.0, 4.0, true, true)
ct_ramp.operation = CSGShape3D.OPERATION_SUBTRACTION
ct_ramp.size = Vector3(15, 10, 15)
ct_ramp.rotation_degrees.z = 25
ct_ramp.position = Vector3(15, 2, -25)
root_csg.add_child(ct_ramp)
# Catwalk / A Short # Catwalk / A Short
carve_open.call(Vector3(15, 4, 5), Vector3(20, 8, 8)) carve_open.call(Vector3(15, 4, 5), Vector3(20, 8, 8))
carve_open.call(Vector3(20, 4, -5), Vector3(8, 8, 15)) carve_open.call(Vector3(20, 4, -5), Vector3(8, 8, 15))
# Stairs from Mid to Catwalk # Stairs from Mid to Catwalk (Smooth ramp)
var mid_stairs = CSGBox3D.new() carve_ramp.call(Vector3(5, 0, 5), 8.0, 12.0, 4.0, true, true)
mid_stairs.operation = CSGShape3D.OPERATION_SUBTRACTION
mid_stairs.size = Vector3(8, 8, 15)
mid_stairs.rotation_degrees.x = 35
mid_stairs.position = Vector3(8, 2, 5)
root_csg.add_child(mid_stairs)
# Floor # Floor
var floor = CSGBox3D.new() var floor_box = CSGBox3D.new()
floor.size = Vector3(140, 1, 140) floor_box.size = Vector3(140, 1, 140)
floor.position = Vector3(0, -0.5, 0) floor_box.position = Vector3(0, -0.5, 0)
floor.material = floor_mat floor_box.material = floor_mat
root_csg.add_child(floor) root_csg.add_child(floor_box)
# Cover Boxes # Cover Boxes
var add_box = func(pos: Vector3, size: Vector3): var add_box = func(pos: Vector3, size: Vector3):
@@ -190,6 +194,19 @@ func _build_dust2_layout() -> void:
# Dummies # Dummies
_build_dust2_dummies() _build_dust2_dummies()
# Combat Area Bounds
var combat_area = load("res://entities/combat_area.gd").new()
combat_area.name = "CombatArea"
var area_shape = CollisionShape3D.new()
var cbox = BoxShape3D.new()
# Size the combat area to envelop the entire playable space (width and depth of 120, height of 20)
cbox.size = Vector3(120, 20, 120)
area_shape.shape = cbox
combat_area.add_child(area_shape)
# Center it on the map so it fits perfectly around the CSG boundaries
combat_area.position = Vector3(0, 10, 0)
add_child(combat_area)
func _build_dust2_dummies() -> void: func _build_dust2_dummies() -> void:
var spawn_dummy = func(pos: Vector3, rot_y: float): var spawn_dummy = func(pos: Vector3, rot_y: float):
var dummy = load("res://entities/killable_dummy.gd").new() var dummy = load("res://entities/killable_dummy.gd").new()
+97
View File
@@ -0,0 +1,97 @@
extends Area3D
class_name CombatArea
var time_left: float = 5.0
var time_safe: float = 0.0
var _player_out_of_bounds: Node3D = null
var _ui_layer: CanvasLayer = null
var _ui_label: Label = null
var _ui_overlay: ColorRect = null
func _ready() -> void:
collision_layer = 0
collision_mask = 4294967295 # All layers
body_exited.connect(_on_body_exited)
body_entered.connect(_on_body_entered)
set_process(false)
func _process(delta: float) -> void:
if _player_out_of_bounds:
# Player is outside the combat area!
time_left -= delta
if time_left <= 0.0:
time_left = 0.0
_kill_player()
# Update UI
if _ui_label and _ui_overlay:
_ui_label.text = "WARNING: RETURN TO COMBAT AREA\n%.1f" % time_left
_ui_overlay.color = Color(1.0, 0.0, 0.0, 0.8 * (1.0 - (time_left / 5.0)))
else:
# Player is inside the combat area, but might have a damaged timer
if time_left < 5.0:
time_safe += delta
if time_safe >= 10.0:
time_left = 5.0
time_safe = 0.0
set_process(false) # Fully recovered, sleep
func _on_body_exited(body: Node3D) -> void:
if body.name == "Player":
_player_out_of_bounds = body
time_safe = 0.0 # Reset recovery timer
_create_ui()
set_process(true)
func _on_body_entered(body: Node3D) -> void:
if body == _player_out_of_bounds:
_player_out_of_bounds = null
_remove_ui()
# set_process(true) stays on to tick the time_safe recovery
func _create_ui() -> void:
if _ui_layer: return
_ui_layer = CanvasLayer.new()
_ui_layer.layer = 100 # Put it on top of everything
_ui_overlay = ColorRect.new()
_ui_overlay.set_anchors_preset(Control.PRESET_FULL_RECT)
_ui_overlay.color = Color(1.0, 0.0, 0.0, 0.0)
_ui_layer.add_child(_ui_overlay)
_ui_label = Label.new()
_ui_label.set_anchors_preset(Control.PRESET_CENTER)
_ui_label.grow_horizontal = Control.GROW_DIRECTION_BOTH
_ui_label.grow_vertical = Control.GROW_DIRECTION_BOTH
_ui_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_ui_label.add_theme_font_size_override("font_size", 48)
_ui_label.add_theme_color_override("font_color", Color(1, 0.2, 0.2))
_ui_label.add_theme_color_override("font_outline_color", Color(0, 0, 0))
_ui_label.add_theme_constant_override("outline_size", 8)
_ui_label.text = "WARNING: RETURN TO COMBAT AREA\n%.1f" % time_left
_ui_layer.add_child(_ui_label)
get_tree().current_scene.add_child(_ui_layer)
func _remove_ui() -> void:
if _ui_layer:
_ui_layer.queue_free()
_ui_layer = null
_ui_label = null
_ui_overlay = null
func _kill_player() -> void:
if _player_out_of_bounds:
# PlayerMovementController's take_damage requires (amount, source)
if _player_out_of_bounds.has_method("take_damage"):
_player_out_of_bounds.take_damage(9999, Vector3.ZERO)
elif "health" in _player_out_of_bounds:
_player_out_of_bounds.health = 0
if "is_dead" in _player_out_of_bounds:
_player_out_of_bounds.is_dead = true
_player_out_of_bounds = null
_remove_ui()
+1
View File
@@ -0,0 +1 @@
uid://dlqngrm516ddt