Compare commits
5
Commits
6a0a4a8aeb
...
83c6ce7993
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83c6ce7993 | ||
|
|
5f2c2462e2 | ||
|
|
64e024eb32 | ||
|
|
050912ca61 | ||
|
|
775e57ba4a |
@@ -0,0 +1,224 @@
|
|||||||
|
extends TestLevelBuilder
|
||||||
|
|
||||||
|
func _build_geometry() -> void:
|
||||||
|
# Build environment setup
|
||||||
|
_build_environment()
|
||||||
|
|
||||||
|
var env = get_node_or_null("WorldEnvironment")
|
||||||
|
if env:
|
||||||
|
var environment = env.environment
|
||||||
|
environment.tonemap_mode = Environment.TONE_MAPPER_ACES
|
||||||
|
if environment.sky and environment.sky.sky_material:
|
||||||
|
var sky_mat = environment.sky.sky_material
|
||||||
|
sky_mat.sky_top_color = Color(0.3, 0.5, 0.8)
|
||||||
|
sky_mat.sky_horizon_color = Color(0.8, 0.7, 0.5)
|
||||||
|
sky_mat.ground_bottom_color = Color(0.2, 0.15, 0.1)
|
||||||
|
sky_mat.ground_horizon_color = Color(0.8, 0.7, 0.5)
|
||||||
|
|
||||||
|
_build_lighting()
|
||||||
|
var sun = get_node_or_null("Sun")
|
||||||
|
if sun:
|
||||||
|
sun.light_color = Color(1.0, 0.95, 0.85)
|
||||||
|
sun.light_energy = 1.5
|
||||||
|
sun.rotation_degrees = Vector3(-45, 45, 0)
|
||||||
|
|
||||||
|
var fill = get_node_or_null("FillLight")
|
||||||
|
if fill:
|
||||||
|
fill.rotation_degrees = Vector3(-45, -135, 0)
|
||||||
|
fill.light_energy = 0.3
|
||||||
|
|
||||||
|
_build_dust2_layout()
|
||||||
|
_build_player()
|
||||||
|
|
||||||
|
# Override player spawn point for Dust 2
|
||||||
|
if _player:
|
||||||
|
_player.global_position = Vector3(0, 1, 50)
|
||||||
|
_player.look_at(Vector3(0, 1, 0), Vector3.UP)
|
||||||
|
|
||||||
|
func _build_dust2_layout() -> void:
|
||||||
|
# Materials
|
||||||
|
var wall_mat = StandardMaterial3D.new()
|
||||||
|
wall_mat.albedo_color = Color(0.85, 0.75, 0.6) # Sandstone
|
||||||
|
|
||||||
|
var floor_mat = StandardMaterial3D.new()
|
||||||
|
floor_mat.albedo_color = Color(0.7, 0.65, 0.55) # Dusty ground
|
||||||
|
|
||||||
|
var box_mat = StandardMaterial3D.new()
|
||||||
|
box_mat.albedo_color = Color(0.4, 0.3, 0.2) # Wood crates
|
||||||
|
|
||||||
|
# Root CSG
|
||||||
|
var root_csg = CSGCombiner3D.new()
|
||||||
|
root_csg.use_collision = true
|
||||||
|
add_child(root_csg)
|
||||||
|
|
||||||
|
# Huge solid block representing the world bounds
|
||||||
|
var world_block = CSGBox3D.new()
|
||||||
|
world_block.size = Vector3(140, 20, 140)
|
||||||
|
world_block.position = Vector3(0, 10, 0)
|
||||||
|
world_block.material = wall_mat
|
||||||
|
root_csg.add_child(world_block)
|
||||||
|
|
||||||
|
# Helper function to carve out open-air areas (cuts through the ceiling)
|
||||||
|
var carve_open = func(pos: Vector3, size: Vector3):
|
||||||
|
var hole = CSGBox3D.new()
|
||||||
|
hole.operation = CSGShape3D.OPERATION_SUBTRACTION
|
||||||
|
var height = 25.0 - pos.y
|
||||||
|
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)
|
||||||
|
return hole
|
||||||
|
|
||||||
|
# Helper function to carve out tunnels (leaves a ceiling)
|
||||||
|
var carve_tunnel = func(pos: Vector3, size: Vector3):
|
||||||
|
var hole = CSGBox3D.new()
|
||||||
|
hole.operation = CSGShape3D.OPERATION_SUBTRACTION
|
||||||
|
hole.size = size
|
||||||
|
hole.position = pos + Vector3(0, size.y / 2.0, 0)
|
||||||
|
root_csg.add_child(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)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
# T Spawn (South)
|
||||||
|
carve_open.call(Vector3(0, 0, 50), Vector3(25, 8, 15))
|
||||||
|
|
||||||
|
# Suicide & Mid Corridor
|
||||||
|
carve_open.call(Vector3(0, 0, 42), Vector3(8, 8, 10)) # Connects T spawn to Mid
|
||||||
|
carve_open.call(Vector3(0, 0, 15), Vector3(12, 8, 55)) # Main Mid
|
||||||
|
|
||||||
|
# Mid Doors (Pinch point)
|
||||||
|
carve_open.call(Vector3(0, 0, -14), Vector3(2, 8, 5))
|
||||||
|
|
||||||
|
# CT Spawn
|
||||||
|
carve_open.call(Vector3(0, 0, -30), Vector3(30, 8, 30))
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# B SIDE
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Lower B Tunnels (Connects from Mid at X=0 to West)
|
||||||
|
carve_tunnel.call(Vector3(-18, 0, 30), Vector3(40, 6, 8))
|
||||||
|
|
||||||
|
# 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
|
||||||
|
carve_tunnel.call(Vector3(-34, 4, 15), Vector3(8, 6, 30))
|
||||||
|
|
||||||
|
# B Site
|
||||||
|
carve_open.call(Vector3(-35, 4, -10), Vector3(25, 8, 25))
|
||||||
|
|
||||||
|
# B Window & Door (Connecting B Site to CT Spawn)
|
||||||
|
carve_tunnel.call(Vector3(-15, 4, -10), Vector3(25, 6, 5)) # Door
|
||||||
|
carve_tunnel.call(Vector3(-15, 4, -18), Vector3(20, 4, 4)) # Window
|
||||||
|
|
||||||
|
# CT Spawn Ramp to B Site
|
||||||
|
carve_ramp.call(Vector3(-15, 0, -15), 10.0, 15.0, 4.0, true, false) # Ramp up from CT to B Door
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# A SIDE
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# A Long Doors (Connects T Spawn/Mid to A Long)
|
||||||
|
carve_tunnel.call(Vector3(15, 0, 40), Vector3(25, 6, 8))
|
||||||
|
carve_tunnel.call(Vector3(30, 0, 40), Vector3(8, 6, 15))
|
||||||
|
|
||||||
|
# A Long
|
||||||
|
carve_open.call(Vector3(30, 0, 15), Vector3(15, 8, 45))
|
||||||
|
|
||||||
|
# A Pit (A hole downwards at the end of A Long)
|
||||||
|
var pit = CSGBox3D.new()
|
||||||
|
pit.operation = CSGShape3D.OPERATION_SUBTRACTION
|
||||||
|
pit.size = Vector3(15, 5, 12)
|
||||||
|
pit.position = Vector3(30, -2.5, 35)
|
||||||
|
root_csg.add_child(pit)
|
||||||
|
|
||||||
|
# A Site
|
||||||
|
carve_open.call(Vector3(30, 4, -15), Vector3(30, 8, 30))
|
||||||
|
|
||||||
|
# Ramp up to A site from Long
|
||||||
|
carve_ramp.call(Vector3(30, 0, -5), 15.0, 15.0, 4.0, false, false)
|
||||||
|
|
||||||
|
# Ramp down from A Site to CT Spawn
|
||||||
|
carve_ramp.call(Vector3(15, 0, -25), 15.0, 15.0, 4.0, true, true)
|
||||||
|
|
||||||
|
# Catwalk / A Short
|
||||||
|
carve_open.call(Vector3(15, 4, 5), Vector3(20, 8, 8))
|
||||||
|
carve_open.call(Vector3(20, 4, -5), Vector3(8, 8, 15))
|
||||||
|
|
||||||
|
# Stairs from Mid to Catwalk (Smooth ramp)
|
||||||
|
carve_ramp.call(Vector3(5, 0, 5), 8.0, 12.0, 4.0, true, true)
|
||||||
|
|
||||||
|
# Floor
|
||||||
|
var floor_box = CSGBox3D.new()
|
||||||
|
floor_box.size = Vector3(140, 1, 140)
|
||||||
|
floor_box.position = Vector3(0, -0.5, 0)
|
||||||
|
floor_box.material = floor_mat
|
||||||
|
root_csg.add_child(floor_box)
|
||||||
|
|
||||||
|
# Cover Boxes
|
||||||
|
var add_box = func(pos: Vector3, size: Vector3):
|
||||||
|
var b = CSGBox3D.new()
|
||||||
|
b.size = size
|
||||||
|
b.position = pos + Vector3(0, size.y / 2.0, 0)
|
||||||
|
b.material = box_mat
|
||||||
|
root_csg.add_child(b)
|
||||||
|
|
||||||
|
add_box.call(Vector3(4, 0, -12), Vector3(2, 2, 2))
|
||||||
|
add_box.call(Vector3(30, 4, -15), Vector3(4, 2, 4))
|
||||||
|
add_box.call(Vector3(30, 6, -15), Vector3(2, 2, 2))
|
||||||
|
add_box.call(Vector3(-35, 4, -10), Vector3(2, 2, 2))
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
var spawn_dummy = func(pos: Vector3, rot_y: float):
|
||||||
|
var dummy = load("res://entities/killable_dummy.gd").new()
|
||||||
|
dummy.position = pos
|
||||||
|
dummy.rotation_degrees.y = rot_y
|
||||||
|
add_child(dummy)
|
||||||
|
|
||||||
|
# Mid Doors CT side looking out
|
||||||
|
spawn_dummy.call(Vector3(0, 0, -20), 0)
|
||||||
|
# A Long Corner looking towards doors
|
||||||
|
spawn_dummy.call(Vector3(34, 0, 10), 180)
|
||||||
|
# B Site Car
|
||||||
|
spawn_dummy.call(Vector3(-30, 4, -5), 90)
|
||||||
|
# A Site Goose
|
||||||
|
spawn_dummy.call(Vector3(40, 4, -25), -135)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dn5ndscghwovm
|
||||||
@@ -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()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dlqngrm516ddt
|
||||||
+1
-1
@@ -11,7 +11,7 @@ config_version=5
|
|||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="Papaya-Shooter"
|
config/name="Papaya-Shooter"
|
||||||
run/main_scene="res://scenes/test_level/test_level.tscn"
|
run/main_scene="res://scenes/dust2_level/dust2_level.tscn"
|
||||||
config/features=PackedStringArray("4.6", "Forward Plus")
|
config/features=PackedStringArray("4.6", "Forward Plus")
|
||||||
config/icon="res://icon.svg"
|
config/icon="res://icon.svg"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://debug/dust2_level_builder.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="Dust2Level" type="Node3D"]
|
||||||
|
script = ExtResource("1")
|
||||||
Reference in New Issue
Block a user