@tool extends Node3D class_name ArenaGenerator @export var generate: bool = false: set(value): if value: if not is_inside_tree(): call_deferred("_generate_tactical_arena") else: _generate_tactical_arena() generate = false @export var clear: bool = false: set(value): if value: _clear_arena() clear = false @export var sector_size = 24.0 @export var step_height = 3.0 @export var wall_height = 12.0 @export var floor_color = Color(0.2, 0.22, 0.25) @export var wall_color = Color(0.3, 0.4, 0.5) @export var obstacle_color = Color(0.6, 0.35, 0.15) @export var ramp_color = Color(0.7, 0.4, 0.2) func _clear_arena() -> void: var children = get_children() for c in children: remove_child(c) c.free() func _generate_tactical_arena() -> void: _clear_arena() var cols = 3 # Left, Mid, Right var rows = 5 # Spawn A, Conn A, Mid, Conn B, Spawn B # Generate elevations (symmetric for fairness) var elevations = [] for x in range(cols): elevations.append([]) for z in range(rows): elevations[x].append(0) # Randomize half, mirror other half for x in range(cols): for z in range(3): # 0, 1, 2 elevations[x][z] = randi_range(0, 2) # Mirror to the other side (point symmetry) # Or mirror horizontally (Spawn A == Spawn B setup) # A standard mirror across mid: for x in range(cols): elevations[x][3] = elevations[cols - 1 - x][1] elevations[x][4] = elevations[cols - 1 - x][0] # Build Sector Floors var _sec_size = float(sector_size if sector_size != null else 24.0) var _step_h = float(step_height if step_height != null else 3.0) var _wall_h = float(wall_height if wall_height != null else 12.0) var _f_col = floor_color if floor_color != null else Color(0.2, 0.22, 0.25) var _w_col = wall_color if wall_color != null else Color(0.3, 0.4, 0.5) var hw = (cols * _sec_size) / 2.0 var hl = (rows * _sec_size) / 2.0 # Global origin is center of grid for x in range(cols): for z in range(rows): var cx = -hw + x * _sec_size + _sec_size / 2.0 var cz = -hl + z * _sec_size + _sec_size / 2.0 var y = float(elevations[x][z]) * _step_h # Make floors incredibly thick (40 units) so they act as solid pillars all the way down # This prevents players from walking underneath higher sectors and falling into the void _create_box("Sector_%d_%d" % [x, z], Vector3(cx, y - 20.0, cz), Vector3(_sec_size, 40.0, _sec_size), _f_col) # Add cover in sector (except spawns z=0 and z=4) if z > 0 and z < 4: _generate_cover("Cover_%d_%d" % [x, z], Vector3(cx, y, cz)) # Ramps between Z adjacent (North/South) if z < rows - 1: var diff = elevations[x][z+1] - elevations[x][z] if diff != 0: _create_ramp_z("RampZ_%d_%d" % [x, z], cx, cz + _sec_size / 2.0, y, diff) # Ramps/Walls between X adjacent (East/West) if x < cols - 1: var is_connector = randf() > 0.4 # 60% chance to be an open connector var diff = elevations[x+1][z] - elevations[x][z] if is_connector: if diff != 0: _create_ramp_x("RampX_%d_%d" % [x, z], cx + _sec_size / 2.0, cz, y, diff) else: # Solid wall var max_y = max(elevations[x][z], elevations[x+1][z]) * _step_h _create_box("InnerWall_%d_%d" % [x, z], Vector3(cx + _sec_size / 2.0, max_y + _wall_h/2, cz), Vector3(1.0, _wall_h, _sec_size), _w_col) # Outer Walls _create_box("Wall_North", Vector3(0, _wall_h * 0.5, -hl), Vector3(hw * 2, _wall_h, 1.0), _w_col) _create_box("Wall_South", Vector3(0, _wall_h * 0.5, hl), Vector3(hw * 2, _wall_h, 1.0), _w_col) _create_box("Wall_East", Vector3(hw, _wall_h * 0.5, 0), Vector3(1.0, _wall_h, hl * 2), _w_col) _create_box("Wall_West", Vector3(-hw, _wall_h * 0.5, 0), Vector3(1.0, _wall_h, hl * 2), _w_col) func _generate_cover(base_name: String, center: Vector3) -> void: var _sec_size = float(sector_size if sector_size != null else 24.0) var _o_col = obstacle_color if obstacle_color != null else Color(0.6, 0.35, 0.15) # Generate 1-3 blocks of cover var count = randi_range(1, 3) for i in range(count): var ox = randf_range(-_sec_size*0.3, _sec_size*0.3) var oz = randf_range(-_sec_size*0.3, _sec_size*0.3) var sx = randf_range(2.0, 6.0) var sy = randf_range(1.0, 4.0) var sz = randf_range(2.0, 6.0) _create_box("%s_%d" % [base_name, i], center + Vector3(ox, sy * 0.5, oz), Vector3(sx, sy, sz), _o_col) func _create_ramp_z(node_name: String, cx: float, cz: float, base_y: float, diff_levels: int) -> void: var _step_h = float(step_height if step_height != null else 3.0) var _sec_size = float(sector_size if sector_size != null else 24.0) var _r_col = ramp_color if ramp_color != null else Color(0.7, 0.4, 0.2) # Ramp along Z axis (North/South) var h = abs(diff_levels) * _step_h var length = _sec_size * 0.4 # Ramp takes up 40% of sector depth var center_y = base_y + h / 2.0 var dir = sign(diff_levels) var rot_x = 0 if dir > 0: rot_x = rad_to_deg(atan2(h, length)) else: rot_x = -rad_to_deg(atan2(h, length)) var true_length = sqrt(length*length + h*h) _create_ramp(node_name, Vector3(cx, center_y, cz), Vector3(_sec_size * 0.5, 0.5, true_length), Vector3(rot_x, 0, 0), _r_col) func _create_ramp_x(node_name: String, cx: float, cz: float, base_y: float, diff_levels: int) -> void: var _step_h = float(step_height if step_height != null else 3.0) var _sec_size = float(sector_size if sector_size != null else 24.0) var _r_col = ramp_color if ramp_color != null else Color(0.7, 0.4, 0.2) # Ramp along X axis (East/West) var h = abs(diff_levels) * _step_h var length = _sec_size * 0.4 var center_y = base_y + h / 2.0 var dir = sign(diff_levels) var rot_z = 0 if dir > 0: rot_z = -rad_to_deg(atan2(h, length)) else: rot_z = rad_to_deg(atan2(h, length)) var true_length = sqrt(length*length + h*h) _create_ramp(node_name, Vector3(cx, center_y, cz), Vector3(true_length, 0.5, _sec_size * 0.5), Vector3(0, 0, rot_z), _r_col) func _create_box(node_name: String, pos: Vector3, size: Vector3, color: Color) -> void: var body := StaticBody3D.new() body.name = node_name body.position = pos var shape := CollisionShape3D.new() shape.name = "CollisionShape3D" shape.shape = BoxShape3D.new() shape.shape.size = size body.add_child(shape) var mesh := MeshInstance3D.new() mesh.name = "MeshInstance3D" mesh.mesh = BoxMesh.new() mesh.mesh.size = size mesh.mesh.surface_set_material(0, LevelMaterials.tinted(color)) body.add_child(mesh) add_child(body) if Engine.is_editor_hint(): var scene_root = get_tree().edited_scene_root if scene_root: body.owner = scene_root shape.owner = scene_root mesh.owner = scene_root func _create_ramp(node_name: String, pos: Vector3, size: Vector3, rot_deg: Vector3, color: Color) -> void: var body := StaticBody3D.new() body.name = node_name body.position = pos body.rotation_degrees = rot_deg var shape := CollisionShape3D.new() shape.name = "CollisionShape3D" shape.shape = BoxShape3D.new() shape.shape.size = size body.add_child(shape) var mesh := MeshInstance3D.new() mesh.name = "MeshInstance3D" mesh.mesh = BoxMesh.new() mesh.mesh.size = size mesh.mesh.surface_set_material(0, LevelMaterials.tinted(color)) body.add_child(mesh) add_child(body) if Engine.is_editor_hint(): var scene_root = get_tree().edited_scene_root if scene_root: body.owner = scene_root shape.owner = scene_root mesh.owner = scene_root