diff --git a/Systems/Game/GameMenuScreen.gd b/Systems/Game/GameMenuScreen.gd index 6fbb837..ba302bb 100644 --- a/Systems/Game/GameMenuScreen.gd +++ b/Systems/Game/GameMenuScreen.gd @@ -58,8 +58,8 @@ func _on_deck_button_pressed(): func _on_map_button_pressed(): print("Map button pressed") - # Emit signal with null options emit_signal("map_open_requested", null) + visible = false func _on_start_button_pressed(): print("Start button pressed") diff --git a/Systems/Game/Map/DotPatternGenerator.gd b/Systems/Game/Map/DotPatternGenerator.gd new file mode 100644 index 0000000..89e4f4e --- /dev/null +++ b/Systems/Game/Map/DotPatternGenerator.gd @@ -0,0 +1,109 @@ +extends Control +class_name DotPatternGenerator +# Dot pattern settings +const DOT_SPACING = 30 +const DOT_SIZE = 2 +const DOT_COLOR = Color(0.3, 0.3, 0.3, 0.5) +const PATH_DOT_COLOR = Color(0.4, 0.4, 0.6, 0.8) +const PATH_DOT_SIZE = 3 +const HIGHLIGHT_RADIUS = 150 + +# For path highlights +var map_screen = null +var highlight_dots = false + +func _init(highlight: bool = false): + highlight_dots = highlight + # Don't process input + mouse_filter = Control.MOUSE_FILTER_IGNORE + +func _ready(): + # Apply the pattern + generate_dots() + + # Find map screen if we're highlighting paths + if highlight_dots: + map_screen = find_map_screen() + +func find_map_screen(): + var parent = get_parent() + while parent != null: + if parent is MapScreen: + return parent + parent = parent.get_parent() + return null + +func generate_dots(): + # Clear existing dots + for child in get_children(): + child.queue_free() + + # Get the size of this control + var control_size = size + + # Calculate how many dots we need + var cols = int(control_size.x / DOT_SPACING) + 1 + var rows = int(control_size.y / DOT_SPACING) + 1 + + # Create dots + for i in range(rows): + for j in range(cols): + var dot_position = Vector2(j * DOT_SPACING, i * DOT_SPACING) + var on_path = false + + # If highlighting, check if dot is near a path + if highlight_dots and map_screen != null: + on_path = is_near_path(dot_position) + + # Create the dot with appropriate style + var dot = ColorRect.new() + dot.size = Vector2(DOT_SIZE, DOT_SIZE) if not on_path else Vector2(PATH_DOT_SIZE, PATH_DOT_SIZE) + dot.color = DOT_COLOR if not on_path else PATH_DOT_COLOR + dot.position = dot_position - (dot.size / 2) + add_child(dot) + +func is_near_path(position: Vector2) -> bool: + # If no map screen, can't check paths + if map_screen == null: + return false + + # Get connections from map screen + for line in map_screen.connection_lines: + if line.get_point_count() < 2: + continue + + # Check distance to line segment + var start = line.get_point_position(0) + var end = line.get_point_position(1) + + # Adjust for potential offset in the map container + start += map_screen.map_container.position + end += map_screen.map_container.position + + # Check if point is near the line + var distance = distance_to_line_segment(position, start, end) + if distance < HIGHLIGHT_RADIUS: + return true + + return false + +# Calculate distance from point to line segment +func distance_to_line_segment(point: Vector2, line_start: Vector2, line_end: Vector2) -> float: + var line_vec = line_end - line_start + var point_vec = point - line_start + + var line_length_squared = line_vec.length_squared() + if line_length_squared == 0: + return point_vec.length() # Line segment is a point + + # Calculate projection of point onto line + var t = max(0, min(1, point_vec.dot(line_vec) / line_length_squared)) + var projection = line_start + t * line_vec + + # Return distance from point to projection + return (point - projection).length() + +# Call this when the map changes to update dot highlights +func update_dots(): + if highlight_dots and map_screen != null: + generate_dots() diff --git a/Systems/Game/Map/DotPatternGenerator.gd.uid b/Systems/Game/Map/DotPatternGenerator.gd.uid new file mode 100644 index 0000000..43522d3 --- /dev/null +++ b/Systems/Game/Map/DotPatternGenerator.gd.uid @@ -0,0 +1 @@ +uid://6cmhvsug8nbv diff --git a/Systems/Game/Map/MapGenerator.gd b/Systems/Game/Map/MapGenerator.gd new file mode 100644 index 0000000..c836791 --- /dev/null +++ b/Systems/Game/Map/MapGenerator.gd @@ -0,0 +1,630 @@ +extends RefCounted +class_name ChessMapGenerator + +# Room type enum +enum RoomType { + STARTING, + NORMAL, + BOSS, + FINAL, + SHOP, + EVENT +} + +# Configuration +var min_levels = 6 +var max_levels = 12 +var min_nodes_per_level = 2 +var max_nodes_per_level = 4 +var positions_per_level = 7 # How many horizontal positions are available (0-6) +var starting_elo = 1000 +var final_elo = 2100 + +# Internal variables +var _rng = RandomNumberGenerator.new() +var _next_id = 0 + +func _init(seed_value = null): + # Set seed for reproducible maps if needed + if seed_value != null: + _rng.seed = seed_value + else: + _rng.randomize() + +# Main function to generate the map +func generate_map(): + var nodes = [] + var connections = [] + _next_id = 0 + + # Determine the number of levels + var num_levels = _rng.randi_range(min_levels, max_levels) + + # Calculate ELO for each level + var elo_step = float(final_elo - starting_elo) / (num_levels - 1) + + # Create starting node (always at position 3, level 0) + var start_node = { + "id": _get_next_id(), + "type": RoomType.STARTING, + "level": 0, + "position": Vector2(3, 0), + "elo": starting_elo + } + nodes.append(start_node) + + # Create final boss node (always at position 3, highest level) + var final_node = { + "id": _get_next_id(), + "type": RoomType.FINAL, + "level": num_levels - 1, + "position": Vector2(3, num_levels - 1), + "elo": final_elo + } + nodes.append(final_node) + + # Generate intermediate levels + var levels_nodes = {0: [start_node], (num_levels - 1): [final_node]} + + for level in range(1, num_levels - 1): + var level_nodes = [] + + # Calculate ELO for this level + var level_elo = starting_elo + (elo_step * level) + + # Determine number of nodes for this level + var num_nodes = _rng.randi_range(min_nodes_per_level, max_nodes_per_level) + + # Generate available positions and shuffle them + var available_positions = [] + for pos in range(positions_per_level): + available_positions.append(pos) + available_positions.shuffle() + + # Create nodes for this level + for i in range(num_nodes): + var node_type = _get_random_room_type(level, num_levels) + var node = { + "id": _get_next_id(), + "type": node_type, + "level": level, + "position": Vector2(available_positions[i], level), + "elo": level_elo + } + nodes.append(node) + level_nodes.append(node) + + levels_nodes[level] = level_nodes + + # Connect nodes between levels + # First connect starting node to level 1 + if levels_nodes.has(1) and levels_nodes[1].size() > 0: + var num_connections = min(_rng.randi_range(2, 3), levels_nodes[1].size()) + var targets = levels_nodes[1].duplicate() + targets.shuffle() + + for i in range(num_connections): + connections.append({ + "from": start_node.id, + "to": targets[i].id + }) + + # Keep track of which nodes are connected + var connected_nodes = [start_node.id] + for level in range(1, num_levels - 1): + if not levels_nodes.has(level) or not levels_nodes.has(level + 1): + continue + + var current_level_nodes = levels_nodes[level] + var next_level_nodes = levels_nodes[level + 1].duplicate() + next_level_nodes.shuffle() + + # For each node in current level that is connected from previous level + for node in current_level_nodes: + if _is_node_connected_to(node.id, connections): + # Add to connected nodes + connected_nodes.append(node.id) + + # Connect to 1-2 nodes in next level if not the final level + if level < num_levels - 2: + var num_next_connections = _rng.randi_range(1, 2) + num_next_connections = min(num_next_connections, next_level_nodes.size()) + + for i in range(num_next_connections): + if i < next_level_nodes.size(): + connections.append({ + "from": node.id, + "to": next_level_nodes[i].id + }) + + # Remove the selected nodes so they aren't picked again + for i in range(num_next_connections): + if next_level_nodes.size() > 0: + next_level_nodes.pop_front() + + # Connect to final boss if at the level before + elif level == num_levels - 2: + connections.append({ + "from": node.id, + "to": final_node.id + }) + + # Remove any nodes that aren't connected + var valid_nodes = [] + for node in nodes: + if connected_nodes.has(node.id) or node.id == final_node.id: + valid_nodes.append(node) + + # Clean up connections to removed nodes + var valid_connections = [] + for conn in connections: + var from_valid = false + var to_valid = false + + for node in valid_nodes: + if node.id == conn.from: + from_valid = true + if node.id == conn.to: + to_valid = true + + 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, + "connections": valid_connections, + "levels": num_levels, + "seed": _rng.seed + } + +# Get a node type based on level - more shops and events in middle levels +func _get_random_room_type(level, total_levels): + # Chance of special rooms based on level + var boss_chance = 0.1 + (level / float(total_levels) * 0.1) # Higher chance in later levels + var shop_chance = 0.1 + (level / float(total_levels) * 0.05) # Higher chance in later levels + var event_chance = 0.05 + + # Last non-final level should have more bosses + if level == total_levels - 2: + boss_chance += 0.3 + + var roll = _rng.randf() + print("_get_random_room_type ", roll, " ", boss_chance, " ", shop_chance, " ", event_chance) + if roll < boss_chance: + return RoomType.BOSS + elif roll < boss_chance + shop_chance: + return RoomType.SHOP + elif roll < boss_chance + shop_chance + event_chance: + return RoomType.EVENT + else: + return RoomType.NORMAL + +# Check if a node is connected as a destination in any connection +func _is_node_connected_to(node_id, connections): + for conn in connections: + if conn.to == node_id: + return true + return false + +# Get a unique ID for a new node +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/MapGenerator.gd.uid b/Systems/Game/Map/MapGenerator.gd.uid new file mode 100644 index 0000000..933dff1 --- /dev/null +++ b/Systems/Game/Map/MapGenerator.gd.uid @@ -0,0 +1 @@ +uid://bs0u5jjxm2c18 diff --git a/Systems/Game/Map/MapScreen.gd b/Systems/Game/Map/MapScreen.gd new file mode 100644 index 0000000..1545882 --- /dev/null +++ b/Systems/Game/Map/MapScreen.gd @@ -0,0 +1,537 @@ +extends Control +class_name MapScreen + +signal back_pressed +signal node_selected(node_data) + +const MapGenerator = preload("res://Systems/Game/Map/MapGenerator.gd") +# Room type constants +enum RoomType { + STARTING, + NORMAL, + BOSS, + FINAL, + SHOP, + EVENT +} + +# Node config +const NODE_SIZE = Vector2(60, 60) +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) + +# Node symbols and colors +const NODE_SYMBOLS = { + RoomType.STARTING: "O", # Circle + RoomType.NORMAL: "■", # Square + RoomType.BOSS: "★", # Star + RoomType.FINAL: "✱", # Burst + RoomType.SHOP: "₵", # Star + RoomType.EVENT: "?" # Burst +} + +const NODE_COLORS = { + RoomType.STARTING: Color(1, 1, 1), # White + RoomType.NORMAL: Color(0.2, 0.2, 0.2), # Dark gray + RoomType.BOSS: Color(0.9, 0.2, 0.2), # Red + RoomType.FINAL: Color(0.9, 0.9, 0.2), # Yellow + RoomType.SHOP: Color(0.2, 0.7, 0.9), # Yellow + RoomType.EVENT: Color(0.8, 0.4, 0.9) # Yellow +} + +var map_nodes = [] +var map_connections = [] +var node_buttons = {} +var connection_lines = [] +var traversed_map = [] +var current_node = null +var levels = 5 + +const SCROLL_PADDING_TOP = 80 +const SCROLL_PADDING_BOTTOM = 80 +const SCROLL_PADDING_LEFT = 60 +const SCROLL_PADDING_RIGHT = 60 + + +@onready var map_container = $MapScrollContainer/MapContainer +@onready var back_button = $BackButton +@onready var title_label = $TitleLabel +@onready var legend_container = $LegendContainer + +func _ready(): + # Connect back button + if back_button: + back_button.connect("pressed", Callable(self, "_on_back_button_pressed")) + + # Create legend + create_legend() + + # Generate default map + generate_map() + + # Display the map + display_map() + +func create_legend(): + # Create legend for room types + var legend_items = [ + {"type": RoomType.BOSS, "text": "Boss"}, + {"type": RoomType.STARTING, "text": "Starting Room"}, + {"type": RoomType.FINAL, "text": "Final Boss"}, + {"type": RoomType.NORMAL, "text": "Normal Room"} + ] + + for item in legend_items: + var hbox = HBoxContainer.new() + hbox.add_theme_constant_override("separation", 10) + + var symbol = Label.new() + symbol.text = NODE_SYMBOLS[item.type] + symbol.add_theme_color_override("font_color", NODE_COLORS[item.type]) + symbol.add_theme_font_size_override("font_size", 24) + + var text = Label.new() + text.text = item.text + + hbox.add_child(symbol) + hbox.add_child(text) + legend_container.add_child(hbox) + +func generate_map(): + # Clear existing map + map_nodes.clear() + map_connections.clear() + var mapGen = MapGenerator.new().generate_map() + print("MapGen ", mapGen) + # Create starting node + # var start_node = { + # "id": 0, + # "type": RoomType.STARTING, + # "level": 0, + # "position": Vector2(3, 4) # Column, Row + # } + # map_nodes.append(start_node) + # current_node = start_node + + # # Create final boss node + # var final_node = { + # "id": 1, + # "type": RoomType.FINAL, + # "level": levels - 1, + # "position": Vector2(3, 0) # Column, Row + # } + # map_nodes.append(final_node) + for node_data in mapGen.nodes: + add_node( + node_data.id, + node_data.type, + node_data.level, + node_data.position, + node_data.elo + ) + + # Store elo rating if needed + map_nodes[map_nodes.size() - 1].elo = node_data.elo + + # Set current node to starting node (id 0) + if node_data.id == 0: + current_node = map_nodes[map_nodes.size() - 1] + + # Process connections + for connection in mapGen.connections: + add_connection(connection.from, connection.to) + + +func add_node(id, type, level, position, elo): + map_nodes.append({ + "id": id, + "type": type, + "level": level, + "position": position, + "elo": elo + }) + +func add_connection(from_id, to_id): + map_connections.append({ + "from": from_id, + "to": to_id + }) + +func display_map(): + # Clear previous display + for child in map_container.get_children(): + child.queue_free() + node_buttons.clear() + connection_lines = [] + + # Calculate map dimensions for proper padding + var min_y = 9999 + var max_y = 0 + var min_x = 9999 + var max_x = 0 + + # Find boundaries of the map + for node_data in map_nodes: + var x_pos = node_data.position.x * NODE_SPACING_X + var y_pos = node_data.position.y * NODE_SPACING_Y + min_x = min(min_x, x_pos) + max_x = max(max_x, x_pos) + min_y = min(min_y, y_pos) + max_y = max(max_y, y_pos) + + # Add padding to container size + var map_width = max_x - min_x + NODE_SIZE.x * 2 + SCROLL_PADDING_LEFT + SCROLL_PADDING_RIGHT + 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) + top_padding.position = Vector2(0, 0) + map_container.add_child(top_padding) + + # Create a padding node at the bottom + var bottom_padding = Control.new() + bottom_padding.custom_minimum_size = Vector2(map_width, SCROLL_PADDING_BOTTOM) + bottom_padding.position = Vector2(0, map_height - SCROLL_PADDING_BOTTOM) + map_container.add_child(bottom_padding) + + # Create padding nodes for left and right + var left_padding = Control.new() + left_padding.custom_minimum_size = Vector2(SCROLL_PADDING_LEFT, map_height) + left_padding.position = Vector2(0, 0) + map_container.add_child(left_padding) + + var right_padding = Control.new() + right_padding.custom_minimum_size = Vector2(SCROLL_PADDING_RIGHT, map_height) + 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 = [] + + # 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) + 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) + + if from_node and to_node: + draw_curved_connection(from_node, to_node, true) + + + # 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: + return node + return null + +func draw_node(node_data): + var button = Button.new() + button.text = NODE_SYMBOLS[node_data.type] + button.custom_minimum_size = NODE_SIZE + button.flat = true + + # Add some styling + var font = FontFile.new() + var style = StyleBoxFlat.new() + style.bg_color = Color(0.1, 0.1, 0.1, 0.5) + style.corner_radius_top_left = 30 + style.corner_radius_top_right = 30 + style.corner_radius_bottom_left = 30 + style.corner_radius_bottom_right = 30 + style.border_width_left = 2 + style.border_width_top = 2 + style.border_width_right = 2 + style.border_width_bottom = 2 + style.border_color = NODE_COLORS[node_data.type] + + button.add_theme_font_size_override("font_size", 28) + button.add_theme_color_override("font_color", NODE_COLORS[node_data.type]) + button.add_theme_stylebox_override("normal", style) + + # Position the node with top and left padding adjustments + var x_pos = node_data.position.x * NODE_SPACING_X + SCROLL_PADDING_LEFT + var y_pos = node_data.position.y * NODE_SPACING_Y + SCROLL_PADDING_TOP + button.position = Vector2(x_pos, y_pos) - NODE_SIZE/2 + + # Store data + button.set_meta("node_data", node_data) + + # Connect signal + button.pressed.connect(func(): _on_node_pressed(node_data)) + + # Add to map + map_container.add_child(button) + node_buttons[node_data.id] = button + + # Highlight current node + if current_node and node_data.id == current_node.id: + highlight_current_node(button) + +func draw_connection(from_node, to_node): + var line = Line2D.new() + line.width = LINE_WIDTH + line.default_color = LINE_COLOR + 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): + line.default_color = LINE_COLOR_SELECTED + + # Calculate start and end positions with top and left padding adjustments + 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) + + # Add points + line.add_point(start_pos) + line.add_point(end_pos) + + # Add to map + map_container.add_child(line) + connection_lines.append(line) + +func highlight_current_node(button): + var style = button.get_theme_stylebox("normal").duplicate() + style.bg_color = Color(0.3, 0.3, 0.3, 0.7) + style.border_width_left = 4 + style.border_width_top = 4 + style.border_width_right = 4 + style.border_width_bottom = 4 + button.add_theme_stylebox_override("normal", style) + +func _on_node_pressed(node_data): + # Check if the node is accessible from current node + if is_node_accessible(node_data): + # Update current node + current_node = node_data + traversed_node(node_data) + # Refresh display to update highlights + display_map() + + # Emit signal with selected node data + emit_signal("node_selected", node_data) + else: + print("Node not accessible from current position", node_data) + +func traversed_node(node_data): + traversed_map.append(node_data.id) + +func is_node_accessible(node_data): + # If no current node, only starting node is accessible + if current_node == null: + return node_data.type == RoomType.STARTING + + # Check if there's a direct connection from current node to target node + for connection in map_connections: + if connection.from == current_node.id and connection.to == node_data.id: + return true + + return false + +func _on_back_button_pressed(): + emit_signal("back_pressed") + visible = false + +func draw_curved_connection(from_node, to_node, isCritPath): + 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): + line.default_color = LINE_COLOR_SELECTED + + # Calculate start and end positions with padding adjustments + 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 from_node.position.y != to_node.position.y: + # Vertical/diagonal connection between different levels + + # 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 + + # 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 + 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 + 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_point = Vector2(mid_x, mid_y) + + # Generate points for a quadratic bezier curve + for i in range(21): + var t = i / 20.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) + + # Add to map + 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) + var q1 = p1.lerp(p2, t) + var q2 = p2.lerp(p3, t) + + var r0 = q0.lerp(q1, t) + var r1 = q1.lerp(q2, t) + + return r0.lerp(r1, t) + +# Helper function for quadratic bezier curve calculation +func quadratic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, t: float) -> Vector2: + var q0 = p0.lerp(p1, t) + var q1 = p1.lerp(p2, t) + + return q0.lerp(q1, t) diff --git a/Systems/Game/Map/MapScreen.gd.uid b/Systems/Game/Map/MapScreen.gd.uid new file mode 100644 index 0000000..c18836b --- /dev/null +++ b/Systems/Game/Map/MapScreen.gd.uid @@ -0,0 +1 @@ +uid://ckpv3snpg6g34 diff --git a/Systems/Game/Map/NodeLayoutHelper.gd b/Systems/Game/Map/NodeLayoutHelper.gd new file mode 100644 index 0000000..e8beddb --- /dev/null +++ b/Systems/Game/Map/NodeLayoutHelper.gd @@ -0,0 +1,147 @@ +extends RefCounted +class_name NodeLayoutHelper + +# Configuration +var map_width: int = 6 # Available horizontal positions (0-5) +var level_height: float = 1.0 # Vertical spacing between levels +var node_spread: float = 0.7 # How much to spread nodes horizontally (0-1) +var path_smoothness: float = 0.3 # How much to align nodes with their connections (0-1) + +# Internal +var _rng = RandomNumberGenerator.new() + +func _init(seed_value: int = 0): + if seed_value != 0: + _rng.seed = seed_value + else: + _rng.randomize() + +# Organize node positions for better visual layout +func organize_layout(nodes: Array, connections: Array) -> Array: + # Clone the nodes so we don't modify the original array + var updated_nodes = [] + for node in nodes: + updated_nodes.append(node.duplicate()) + + # Group nodes by level + var nodes_by_level = {} + + for node in updated_nodes: + var level = node.level + if not nodes_by_level.has(level): + nodes_by_level[level] = [] + nodes_by_level[level].append(node) + + # Process each level + for level in nodes_by_level: + var level_nodes = nodes_by_level[level] + + # Skip levels with fixed positions + if level == 0 or level == nodes_by_level.size() - 1: + continue + + # Calculate optimal positions based on connections + for node in level_nodes: + _adjust_node_position(node, updated_nodes, connections, nodes_by_level) + + # Resolve overlaps after initial placement + for level in nodes_by_level: + var level_nodes = nodes_by_level[level] + if level_nodes.size() > 1: + _resolve_horizontal_overlaps(level_nodes) + + return updated_nodes + +# Adjust node position based on its connections +func _adjust_node_position(node, all_nodes, connections, nodes_by_level): + # Find all connections to/from this node + var connected_from = [] # Nodes in the previous level connecting to this node + var connected_to = [] # Nodes in the next level this node connects to + + for conn in connections: + if conn.to == node.id: + var from_node = _find_node_by_id(all_nodes, conn.from) + if from_node and from_node.level == node.level - 1: + connected_from.append(from_node) + + if conn.from == node.id: + var to_node = _find_node_by_id(all_nodes, conn.to) + if to_node and to_node.level == node.level + 1: + connected_to.append(to_node) + + # Calculate optimal x position based on connected nodes + var optimal_x = node.position.x # Start with current position + + if connected_from.size() > 0 or connected_to.size() > 0: + var avg_x = 0.0 + var total_connected = 0 + + # Consider positions of connected nodes + for from_node in connected_from: + avg_x += from_node.position.x + total_connected += 1 + + for to_node in connected_to: + avg_x += to_node.position.x + total_connected += 1 + + if total_connected > 0: + avg_x /= total_connected + + # Blend between current position and optimal position + optimal_x = lerp(node.position.x, avg_x, path_smoothness) + + # Add some random variation to prevent everything aligning too perfectly + optimal_x += (_rng.randf() - 0.5) * node_spread + + # Keep within bounds + optimal_x = clamp(optimal_x, 0, map_width) + + # Update node position + node.position.x = optimal_x + + # Y position is determined by level + node.position.y = node.level * level_height + +# Resolve horizontal overlaps between nodes on the same level +func _resolve_horizontal_overlaps(level_nodes): + # Sort nodes by x position + level_nodes.sort_custom(func(a, b): return a.position.x < b.position.x) + + # Minimum distance between nodes + var min_distance = 1.0 + + # Check for overlaps and adjust + for i in range(1, level_nodes.size()): + var prev_node = level_nodes[i-1] + var curr_node = level_nodes[i] + + if curr_node.position.x - prev_node.position.x < min_distance: + # If too close, move them apart + var midpoint = (prev_node.position.x + curr_node.position.x) / 2 + var half_distance = min_distance / 2 + + # Try to maintain relative positions + prev_node.position.x = midpoint - half_distance + curr_node.position.x = midpoint + half_distance + + # Ensure we stay within bounds + if prev_node.position.x < 0: + var shift = -prev_node.position.x + prev_node.position.x += shift + curr_node.position.x += shift + + if curr_node.position.x > map_width: + var shift = curr_node.position.x - map_width + prev_node.position.x -= shift + curr_node.position.x -= shift + + # Final boundary check for previous node + prev_node.position.x = max(0, prev_node.position.x) + +# Find a node by its ID +func _find_node_by_id(nodes, id): + for node in nodes: + if node.id == id: + return node + return null \ No newline at end of file diff --git a/Systems/Game/Map/NodeLayoutHelper.gd.uid b/Systems/Game/Map/NodeLayoutHelper.gd.uid new file mode 100644 index 0000000..7ee6978 --- /dev/null +++ b/Systems/Game/Map/NodeLayoutHelper.gd.uid @@ -0,0 +1 @@ +uid://bmi14xk8tn7xc diff --git a/Systems/Game/Menu/MenuContainer.gd b/Systems/Game/Menu/MenuContainer.gd index bd8db97..abbfc1e 100644 --- a/Systems/Game/Menu/MenuContainer.gd +++ b/Systems/Game/Menu/MenuContainer.gd @@ -16,7 +16,7 @@ signal map_open_requested(options) @onready var developerText = $HBoxContainer/VBoxContainer/DeveloperText @onready var gameMenuScreen = get_node("/root/Board/GameMenuScreen") @onready var deckManagerScreen = get_node("/root/Board/DeckManagerScreen") - +@onready var mapScreen = get_node("/root/Board/MapScreen") @onready var stateMachine = get_node("/root/Board/StateMachine") func _ready(): # Connect menu option signals @@ -34,6 +34,11 @@ func _ready(): if deckManagerScreen: deckManagerScreen.connect("back_pressed", Callable(self, "_on_deck_manager_back_pressed")) deckManagerScreen.visible = false + + if mapScreen: + mapScreen.connect("back_pressed", Callable(self, "_on_map_back_pressed")) + mapScreen.connect("node_selected", Callable(self, "_on_map_node_selected")) + mapScreen.visible = false load_version() @@ -124,6 +129,9 @@ func _on_deck_manager_back_pressed(): func _on_map_open_requested(options): print("Map requested with options:", options) + gameMenuScreen.visible = false + if mapScreen: + mapScreen.visible = true emit_signal("map_open_requested", options) func _on_start_game_pressed(options): @@ -131,8 +139,22 @@ func _on_start_game_pressed(options): emit_signal("new_game_requested", options) +func _on_map_back_pressed(): + mapScreen.visible = false + gameMenuScreen.visible = true + +# Map node selection +func _on_map_node_selected(node_data): + print("Selected map node: ", node_data) + # Implement logic for map node selection + # For example, start a battle based on the node type + # Public method to show the menu func show_menu(): self.visible = true if gameMenuScreen: gameMenuScreen.visible = false + if deckManagerScreen: + deckManagerScreen.visible = false + if mapScreen: + mapScreen.visible = false diff --git a/board.tscn b/board.tscn index 440358c..8cbae20 100644 --- a/board.tscn +++ b/board.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=29 format=3 uid="uid://d0qyk6v20uief"] +[gd_scene load_steps=30 format=3 uid="uid://d0qyk6v20uief"] [ext_resource type="Script" uid="uid://cbcu68o863pfp" path="res://Systems/Game/ChessGame.gd" id="1_fkb2r"] [ext_resource type="Script" uid="uid://d2bfw6edgkhfa" path="res://Systems/StateMachine/GameStates/WhiteTurn.gd" id="3_276ip"] @@ -28,6 +28,7 @@ [ext_resource type="Script" uid="uid://j0m4rwr86oi6" path="res://Systems/Game/GameMenuScreen.gd" id="26_pb4ja"] [ext_resource type="Script" uid="uid://bfjmon81nckns" path="res://Systems/Game/GameMenuButton.gd" id="26_t2e38"] [ext_resource type="PackedScene" uid="uid://c7uqbcxdjoais" path="res://deck_manager_screen.tscn" id="28_4nyv8"] +[ext_resource type="PackedScene" uid="uid://dxiw67f3rrwue" path="res://map_screen.tscn" id="29_y7cv2"] [node name="Board" type="Control"] layout_mode = 3 @@ -388,3 +389,7 @@ texture = ExtResource("23_vmvai") [node name="DeckManagerScreen" parent="." instance=ExtResource("28_4nyv8")] visible = false layout_mode = 1 + +[node name="MapScreen" parent="." instance=ExtResource("29_y7cv2")] +visible = false +layout_mode = 1 diff --git a/map_screen.tscn b/map_screen.tscn new file mode 100644 index 0000000..cda1a31 --- /dev/null +++ b/map_screen.tscn @@ -0,0 +1,116 @@ +[gd_scene load_steps=4 format=3 uid="uid://dxiw67f3rrwue"] + +[ext_resource type="Script" uid="uid://6cmhvsug8nbv" path="res://Systems/Game/Map/DotPatternGenerator.gd" id="3_wcttb"] +[ext_resource type="Script" uid="uid://bfjmon81nckns" path="res://Systems/Game/GameMenuButton.gd" id="bfjmon81nckns"] +[ext_resource type="Script" uid="uid://ckpv3snpg6g34" path="res://Systems/Game/Map/MapScreen.gd" id="ckpv3snpg6g34"] + +[node name="MapScreen" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("ckpv3snpg6g34") + +[node name="Background" type="ColorRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0.08, 0.08, 0.12, 1) + +[node name="DotPattern" type="Control" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="TitleLabel" type="Label" parent="."] +layout_mode = 1 +anchors_preset = 10 +anchor_right = 1.0 +offset_top = 20.0 +offset_bottom = 72.0 +grow_horizontal = 2 +theme_override_font_sizes/font_size = 36 +text = "MAP" +horizontal_alignment = 1 + +[node name="MapScrollContainer" type="ScrollContainer" parent="."] +layout_mode = 1 +anchors_preset = -1 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -404.0 +offset_top = -250.0 +offset_right = 404.0 +offset_bottom = 242.0 +grow_horizontal = 2 +grow_vertical = 2 +horizontal_scroll_mode = 0 +vertical_scroll_mode = 2 + +[node name="MapContainer" type="Control" parent="MapScrollContainer"] +clip_contents = true +custom_minimum_size = Vector2(800, 600) +layout_mode = 2 + +[node name="LegendContainer" type="VBoxContainer" parent="."] +visible = false +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -200.0 +offset_top = 80.0 +offset_right = -20.0 +offset_bottom = 250.0 +grow_horizontal = 0 +theme_override_constants/separation = 10 + +[node name="BackButton" type="RichTextLabel" parent="."] +layout_mode = 1 +anchors_preset = 2 +anchor_top = 1.0 +anchor_bottom = 1.0 +offset_left = 20.0 +offset_top = -50.0 +offset_right = 155.0 +offset_bottom = -20.0 +grow_vertical = 0 +text = "BACK" +script = ExtResource("bfjmon81nckns") + +[node name="MapInfo" type="Label" parent="."] +layout_mode = 1 +anchors_preset = 7 +anchor_left = 0.5 +anchor_top = 1.0 +anchor_right = 0.5 +anchor_bottom = 1.0 +offset_left = -377.0 +offset_top = -100.0 +offset_right = 377.0 +offset_bottom = -24.0 +grow_horizontal = 2 +grow_vertical = 0 +text = "After a boss the player selects 1 found card to keep" +horizontal_alignment = 1 +vertical_alignment = 1 +autowrap_mode = 3 + +[node name="DotPatternGenerator" type="Control" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("3_wcttb")