diff --git a/Systems/Game/Map/MapGenerator.gd b/Systems/Game/Map/MapGenerator.gd index c836791..262db42 100644 --- a/Systems/Game/Map/MapGenerator.gd +++ b/Systems/Game/Map/MapGenerator.gd @@ -169,63 +169,7 @@ func generate_map(): if from_valid and to_valid: valid_connections.append(conn) - valid_connections = ensure_path_exists(valid_nodes, valid_connections) - - # Then filter out overlapping connections while maintaining connectivity - valid_connections = filter_overlapping_connections(valid_nodes, valid_connections) - - # Perform a final check to make sure all nodes are connected - var reachable_nodes = {} - reachable_nodes[0] = true # Start node id is always 0 - - # Build the graph again - var graph = {} - for node in valid_nodes: - graph[node.id] = [] - - for conn in valid_connections: - if not graph.has(conn.from): - graph[conn.from] = [] - graph[conn.from].append(conn.to) - - # Do a BFS from the start node - var queue = [0] # Starting node ID - while queue.size() > 0: - var current = queue.pop_front() - - if graph.has(current): - for neighbor in graph[current]: - if not reachable_nodes.has(neighbor): - reachable_nodes[neighbor] = true - queue.append(neighbor) - - # Check if all nodes are reachable - var all_reachable = true - for node in valid_nodes: - if not reachable_nodes.has(node.id): - all_reachable = false - print("Node not reachable: ", node.id, " - Type: ", node.type) - break - - # If not all nodes are reachable, only keep the reachable ones - if not all_reachable: - var truly_valid_nodes = [] - for node in valid_nodes: - if reachable_nodes.has(node.id): - truly_valid_nodes.append(node) - - # Update valid_nodes to only include reachable nodes - valid_nodes = truly_valid_nodes - - # Also update connections to only include those between reachable nodes - var truly_valid_connections = [] - for conn in valid_connections: - if reachable_nodes.has(conn.from) and reachable_nodes.has(conn.to): - truly_valid_connections.append(conn) - - valid_connections = truly_valid_connections - - space_nodes_horizontally(valid_nodes, valid_connections) + # Return the generated map return { "nodes": valid_nodes, @@ -268,363 +212,3 @@ func _get_next_id(): var id = _next_id _next_id += 1 return id - - -func filter_overlapping_connections(nodes, connections): - var valid_connections = [] - var node_positions = {} - - # Create a lookup for node positions - for node in nodes: - node_positions[node.id] = Vector2(node.position.x, node.position.y) - - # Create a graph representation to check connectivity - var graph = {} - for node in nodes: - graph[node.id] = [] - - for connection in connections: - if not graph.has(connection.from): - graph[connection.from] = [] - if not graph.has(connection.to): - graph[connection.to] = [] - graph[connection.from].append(connection.to) - - # Find start and end nodes - var start_node_id = -1 - var end_node_id = -1 - for node in nodes: - if node.type == RoomType.STARTING: - start_node_id = node.id - elif node.type == RoomType.FINAL: - end_node_id = node.id - - # Sort connections by priority, giving preference to: - # 1. Critical path connections - # 2. More vertical connections (to reduce crossing) - # 3. Shorter connections - var sorted_connections = connections.duplicate() - sorted_connections.sort_custom(func(a, b): - var a_from_pos = node_positions[a.from] - var a_to_pos = node_positions[a.to] - var b_from_pos = node_positions[b.from] - var b_to_pos = node_positions[b.to] - - # Critical path connections (ones needed to reach the final boss) get highest priority - var a_critical = is_on_critical_path(a.from, a.to, start_node_id, end_node_id, graph) - var b_critical = is_on_critical_path(b.from, b.to, start_node_id, end_node_id, graph) - - if a_critical and not b_critical: - return true - elif not a_critical and b_critical: - return false - - # Connections from the same node should be spread out by x-position - if a.from == b.from: - var a_x_diff = abs(a_from_pos.x - a_to_pos.x) - var b_x_diff = abs(b_from_pos.x - b_to_pos.x) - # Prefer more aligned connections (less horizontal distance) - return a_x_diff < b_x_diff - - # Calculate how vertical each connection is - var a_dx = abs(a_to_pos.x - a_from_pos.x) - var a_dy = abs(a_to_pos.y - a_from_pos.y) - var b_dx = abs(b_to_pos.x - b_from_pos.x) - var b_dy = abs(b_to_pos.y - b_from_pos.y) - - var a_vertical_ratio = a_dy / max(a_dx, 0.1) # Avoid division by zero - var b_vertical_ratio = b_dy / max(b_dx, 0.1) - - # Prefer more vertical connections - if a_vertical_ratio > b_vertical_ratio * 1.5: # Threshold to make the difference significant - return true - elif b_vertical_ratio > a_vertical_ratio * 1.5: - return false - - # Then prioritize by level progression (forward progress) - var a_forward = a_from_pos.y < a_to_pos.y - var b_forward = b_from_pos.y < b_to_pos.y - - if a_forward and not b_forward: - return true - elif not a_forward and b_forward: - return false - - # Finally by length - var a_dist = (a_to_pos - a_from_pos).length_squared() - var b_dist = (b_to_pos - b_from_pos).length_squared() - return a_dist < b_dist - ) - - # First pass: Add all critical path connections - for connection in sorted_connections: - if is_on_critical_path(connection.from, connection.to, start_node_id, end_node_id, graph): - valid_connections.append(connection) - - # Create a set of connected nodes from the critical path - var connected_nodes = {} - connected_nodes[start_node_id] = true - - for connection in valid_connections: - connected_nodes[connection.from] = true - connected_nodes[connection.to] = true - - # Second pass: Add remaining connections, avoiding overlaps - for connection in sorted_connections: - # Skip if already added - if connection in valid_connections: - continue - - # Only consider connections that link to already connected nodes - if not connected_nodes.has(connection.from) and not connected_nodes.has(connection.to): - continue - - var from_pos = node_positions[connection.from] - var to_pos = node_positions[connection.to] - - # Check if this connection would intersect with any valid connection - var has_intersection = false - for valid_conn in valid_connections: - var valid_from_pos = node_positions[valid_conn.from] - var valid_to_pos = node_positions[valid_conn.to] - - # Skip if they share a node - if connection.from == valid_conn.from or connection.from == valid_conn.to or \ - connection.to == valid_conn.from or connection.to == valid_conn.to: - continue - - # Check intersection - if do_lines_intersect(from_pos, to_pos, valid_from_pos, valid_to_pos): - has_intersection = true - break - - # If no intersections, add to valid connections - if not has_intersection: - valid_connections.append(connection) - connected_nodes[connection.from] = true - connected_nodes[connection.to] = true - - # Final pass: Ensure all nodes are reachable - # This part can remain the same as your current implementation - - return valid_connections -# Helper function to check if a connection is on a critical path -func is_on_critical_path(from_id, to_id, start_id, end_id, graph): - # Skip the check if we're directly connecting start to end - if (from_id == start_id and to_id == end_id): - return true - - # Use breadth-first search to find paths - var visited = {from_id: true} - var queue = [from_id] - - # First check if we can reach end_id from to_id - while queue.size() > 0: - var current = queue.pop_front() - - if current == end_id: - return true - - for neighbor in graph[current]: - if not visited.has(neighbor): - visited[neighbor] = true - queue.append(neighbor) - - # If we're not on a path to the end, this isn't critical - return false - -# Helper function to ensure at least one valid path exists from start to end -func ensure_path_exists(nodes, connections): - # Find start and end node IDs - var start_id = -1 - var end_id = -1 - for node in nodes: - if node.type == RoomType.STARTING: - start_id = node.id - elif node.type == RoomType.FINAL: - end_id = node.id - - # Build a graph representation - var graph = {} - for node in nodes: - graph[node.id] = [] - - for conn in connections: - if not graph.has(conn.from): - graph[conn.from] = [] - graph[conn.from].append(conn.to) - - # Use BFS to check if end is reachable from start - var visited = {start_id: true} - var queue = [start_id] - var path_exists = false - - while queue.size() > 0: - var current = queue.pop_front() - - if current == end_id: - path_exists = true # Path exists - break - - for neighbor in graph[current]: - if not visited.has(neighbor): - visited[neighbor] = true - queue.append(neighbor) - - # If path exists, return the existing connections - if path_exists: - return connections # Return the unmodified connections array - - # No path exists, need to add one - # Find nodes by level - var nodes_by_level = {} - for node in nodes: - if not nodes_by_level.has(node.level): - nodes_by_level[node.level] = [] - nodes_by_level[node.level].append(node) - - # Create a direct path from start to end through each level - var current_id = start_id - var levels = nodes_by_level.keys() - levels.sort() - - # Copy connections array to avoid modifying the original - var new_connections = connections.duplicate() - - for i in range(1, levels.size()): - var level = levels[i] - if nodes_by_level.has(level) and nodes_by_level[level].size() > 0: - # Pick the middle node in this level - var target_node = nodes_by_level[level][nodes_by_level[level].size() / 2] - - # Add a connection from current to this node - new_connections.append({ - "from": current_id, - "to": target_node.id - }) - - current_id = target_node.id - - # If we've reached the final level, connect to end - if level == levels[-1]: - new_connections.append({ - "from": current_id, - "to": end_id - }) - - return new_connections - - - -func do_lines_intersect(a_start: Vector2, a_end: Vector2, b_start: Vector2, b_end: Vector2) -> bool: - # Line segment intersection using the parametric equation approach - var s1_x = a_end.x - a_start.x - var s1_y = a_end.y - a_start.y - var s2_x = b_end.x - b_start.x - var s2_y = b_end.y - b_start.y - - var denominator = (-s2_x * s1_y + s1_x * s2_y) - if denominator == 0: - return false # Collinear or parallel lines - - var s = (-s1_y * (a_start.x - b_start.x) + s1_x * (a_start.y - b_start.y)) / denominator - var t = (s2_x * (a_start.y - b_start.y) - s2_y * (a_start.x - b_start.x)) / denominator - - # If s and t are between 0-1, the lines intersect - return (s >= 0 && s <= 1 && t >= 0 && t <= 1) - - -# Fixed version of space_nodes_horizontally with proper connection tracking -func space_nodes_horizontally(nodes, connections): - # Group nodes by level - var nodes_by_level = {} - for node in nodes: - if not nodes_by_level.has(node.level): - nodes_by_level[node.level] = [] - nodes_by_level[node.level].append(node) - - # Process each level to spread out nodes - for level in nodes_by_level.keys(): - var level_nodes = nodes_by_level[level] - - # Skip levels with just one node - if level_nodes.size() <= 1: - continue - - # Sort nodes by their current horizontal position - level_nodes.sort_custom(func(a, b): return a.position.x < b.position.x) - - # First pass: calculate connections for each node - var node_connections = {} - for node in level_nodes: - node_connections[node.id] = { - "above": [], - "below": [] - } - - # Find connections to this node - for conn in connections: - # Connection from above - if conn.to == node.id: - var from_node = null - for n in nodes: - if n.id == conn.from and n.level < node.level: - from_node = n - break - if from_node: - node_connections[node.id].above.append(from_node) - - # Connection to below - elif conn.from == node.id: - var to_node = null - for n in nodes: - if n.id == conn.to and n.level > node.level: - to_node = n - break - if to_node: - node_connections[node.id].below.append(to_node) - - # Second pass: adjust node positions based on connections - for node in level_nodes: - # Get this node's connections - var connections_above = node_connections[node.id].above - var connections_below = node_connections[node.id].below - - # Calculate "pull" forces from connected nodes - var pull_x = 0.0 - var total_pull = 0.0 - - # Look at connections to nodes above - for conn_node in connections_above: - pull_x += conn_node.position.x - total_pull += 1.0 - - # Look at connections to nodes below - for conn_node in connections_below: - pull_x += conn_node.position.x - total_pull += 1.0 - - # Adjust position based on pull if there are connections - if total_pull > 0: - # Calculate ideal x position (average of connected nodes) - var ideal_x = pull_x / total_pull - - # Move slightly toward ideal position (don't move fully to maintain ordering) - node.position.x = node.position.x * 0.7 + ideal_x * 0.3 - - # Ensure minimum spacing between nodes - var min_spacing = 1.0 # Minimum horizontal distance between nodes - - for i in range(1, level_nodes.size()): - var prev_node = level_nodes[i-1] - var curr_node = level_nodes[i] - - # Check if nodes are too close - if curr_node.position.x - prev_node.position.x < min_spacing: - # Shift this node and all subsequent nodes to the right - var shift = min_spacing - (curr_node.position.x - prev_node.position.x) - - for j in range(i, level_nodes.size()): - level_nodes[j].position.x += shift - - return nodes diff --git a/Systems/Game/Map/MapScreen.gd b/Systems/Game/Map/MapScreen.gd index 1545882..5943048 100644 --- a/Systems/Game/Map/MapScreen.gd +++ b/Systems/Game/Map/MapScreen.gd @@ -21,7 +21,6 @@ const NODE_SPACING_X = 150 const NODE_SPACING_Y = 120 const LINE_WIDTH = 3 const LINE_COLOR = Color(0.2, 0.2, 0.2) -const LINE_COLOR_CRIT = Color(0.9, 0.8, 0.2, 0.8) const LINE_COLOR_SELECTED = Color(0.2, 0.6, 0.2) const LINE_COLOR_ACCESSIBLE = Color(0.6, 0.6, 0.2) @@ -189,7 +188,6 @@ func display_map(): var map_height = max_y - min_y + NODE_SIZE.y * 2 + SCROLL_PADDING_TOP + SCROLL_PADDING_BOTTOM map_container.custom_minimum_size = Vector2(map_width, map_height) - # Create padding nodes # Create a padding node at the top var top_padding = Control.new() top_padding.custom_minimum_size = Vector2(map_width, SCROLL_PADDING_TOP) @@ -213,46 +211,24 @@ func display_map(): right_padding.position = Vector2(map_width - SCROLL_PADDING_RIGHT, 0) map_container.add_child(right_padding) - # Find the optimal path from start to end for highlighting - var critical_path = find_unique_path_to_end() - var critical_connections = [] + # Draw connections first (so they're behind nodes) - # Convert path nodes to connections - if critical_path.size() > 1: - for i in range(critical_path.size() - 1): - critical_connections.append({ - "from": critical_path[i], - "to": critical_path[i + 1] - }) - - # First draw non-critical connections (so critical ones are on top) + # Modify your display_map function to use the curved connections + # Replace your loop that draws connections with this: + + # Draw connections first (so they're behind nodes) for connection in map_connections: - var is_critical = false - for crit_conn in critical_connections: - if connection.from == crit_conn.from and connection.to == crit_conn.to: - is_critical = true - break - - if not is_critical: - var from_node = get_node_by_id(connection.from) - var to_node = get_node_by_id(connection.to) - - if from_node and to_node: - draw_curved_connection(from_node, to_node, false) - - # Then draw critical connections on top - for crit_conn in critical_connections: - var from_node = get_node_by_id(crit_conn.from) - var to_node = get_node_by_id(crit_conn.to) + var from_node = get_node_by_id(connection.from) + var to_node = get_node_by_id(connection.to) if from_node and to_node: - draw_curved_connection(from_node, to_node, true) - + draw_curved_connection(from_node, to_node) # Draw nodes for node_data in map_nodes: draw_node(node_data) + func get_node_by_id(id): for node in map_nodes: if node.id == id: @@ -365,12 +341,11 @@ func _on_back_button_pressed(): emit_signal("back_pressed") visible = false -func draw_curved_connection(from_node, to_node, isCritPath): + +func draw_curved_connection(from_node, to_node): var line = Line2D.new() line.width = LINE_WIDTH line.default_color = LINE_COLOR - # if isCritPath: - # line.default_color = LINE_COLOR_CRIT if from_node.id == current_node.id: line.default_color = LINE_COLOR_ACCESSIBLE elif traversed_map.has(to_node.id) and (traversed_map.has(from_node.id) || from_node.id == 0): @@ -380,92 +355,42 @@ func draw_curved_connection(from_node, to_node, isCritPath): var start_pos = from_node.position * Vector2(NODE_SPACING_X, NODE_SPACING_Y) + Vector2(SCROLL_PADDING_LEFT, SCROLL_PADDING_TOP) var end_pos = to_node.position * Vector2(NODE_SPACING_X, NODE_SPACING_Y) + Vector2(SCROLL_PADDING_LEFT, SCROLL_PADDING_TOP) - # Find all connections from the same starting node to help with path separation - var sibling_connections = [] - for conn in map_connections: - if conn.from == from_node.id and conn.to != to_node.id: - var other_node = get_node_by_id(conn.to) - if other_node: - sibling_connections.append(other_node) - - # Count how many connections this source node has - var connection_count = sibling_connections.size() + 1 # +1 for this connection - - # Determine position of this connection among siblings (for consistent curve offsets) - var connection_index = 0 - for i in range(sibling_connections.size()): - if sibling_connections[i].id < to_node.id: - connection_index += 1 - # Create a bezier curve path var points = [] - # Calculate direction vector between nodes - var direction = (end_pos - start_pos).normalized() - var perp_direction = Vector2(-direction.y, direction.x) # Perpendicular to direction - - # Different curve handling based on node positions + # If nodes are on different levels (y positions) if from_node.position.y != to_node.position.y: - # Vertical/diagonal connection between different levels + # Calculate control points for a curve + var mid_y = (start_pos.y + end_pos.y) / 2 + var control_offset = NODE_SPACING_Y * 0.5 * (1 + abs(from_node.position.x - to_node.position.x) * 0.2) - # Calculate curve offset based on connection count and index - var curve_offset = 0.0 - if connection_count > 1: - # Calculate spacing between -0.5 and 0.5 - var spacing = 1.0 / (connection_count + 1) - curve_offset = (connection_index + 1) * spacing - 0.5 - curve_offset *= NODE_SPACING_X * 0.8 # Scale by node spacing + var control1 = Vector2(start_pos.x, start_pos.y + control_offset) + var control2 = Vector2(end_pos.x, end_pos.y - control_offset) - # Generate control points with horizontal offset - var dist = (end_pos - start_pos).length() - var mid_point = start_pos + (end_pos - start_pos) * 0.5 - - # Apply the offset perpendicular to the path direction - mid_point += perp_direction * curve_offset - - # Calculate control points with vertical offsets - var control1 = start_pos + direction * (dist * 0.25) + perp_direction * curve_offset * 1.5 - var control2 = end_pos - direction * (dist * 0.25) + perp_direction * curve_offset * 1.5 - - # Generate points for a cubic bezier curve - for i in range(21): # More points for a smoother curve - var t = i / 20.0 + # Add more points for a smoother curve + for i in range(11): # 0.0, 0.1, 0.2, ..., 1.0 + var t = i / 10.0 var point = cubic_bezier(start_pos, control1, control2, end_pos, t) points.append(point) else: - # Horizontal connection (nodes at same level) - var curve_height = NODE_SPACING_Y * 0.4 # Base curve height - - # Adjust curve height based on connection position - if connection_count > 1: - curve_height *= (1.0 + 0.3 * connection_index) - - # Determine curve direction (up or down) - var curve_up = true - - # If nodes are further apart horizontally, make a higher curve - var x_distance = abs(from_node.position.x - to_node.position.x) - if x_distance > 1: - curve_height *= (1.0 + 0.2 * x_distance) - - # Calculate mid point with vertical offset + # For nodes on the same level, use a simple curved path var mid_x = (start_pos.x + end_pos.x) / 2 - var mid_y = null - if curve_up: - mid_y = start_pos - curve_height - else : - mid_y = start_pos - (-curve_height) + var mid_y = start_pos.y + var curve_height = NODE_SPACING_Y * 0.3 * (1 + abs(from_node.position.x - to_node.position.x) * 0.1) + + if to_node.position.x > from_node.position.x: + mid_y -= curve_height + else: + mid_y += curve_height + var mid_point = Vector2(mid_x, mid_y) - # Generate points for a quadratic bezier curve - for i in range(21): - var t = i / 20.0 + # Create a quadratic bezier curve with just 3 points + for i in range(11): + var t = i / 10.0 var point = quadratic_bezier(start_pos, mid_point, end_pos, t) points.append(point) - # Apply anti-aliasing - line.antialiased = true - # Add the points to the line for point in points: line.add_point(point) @@ -474,50 +399,6 @@ func draw_curved_connection(from_node, to_node, isCritPath): map_container.add_child(line) connection_lines.append(line) -func find_unique_path_to_end(): - # Find start and end nodes - var start_node = null - var end_node = null - - for node in map_nodes: - if node.type == RoomType.STARTING: - start_node = node - elif node.type == RoomType.FINAL: - end_node = node - - if not start_node or not end_node: - return [] - - # Build a graph representation - var graph = {} - for node in map_nodes: - graph[node.id] = [] - - for conn in map_connections: - if graph.has(conn.from): - graph[conn.from].append(conn.to) - - # Use BFS to find the shortest path - var queue = [[start_node.id]] # Queue of paths - var visited = {start_node.id: true} - - while queue.size() > 0: - var path = queue.pop_front() - var node_id = path[path.size() - 1] - - if node_id == end_node.id: - return path # Return the full path - - if graph.has(node_id): - for neighbor in graph[node_id]: - if not visited.has(neighbor): - visited[neighbor] = true - var new_path = path.duplicate() - new_path.append(neighbor) - queue.append(new_path) - - return [] # No path found - # Helper function for cubic bezier curve calculation func cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float) -> Vector2: var q0 = p0.lerp(p1, t)