extends Control class_name MapScreen signal back_pressed signal deckmanager_open_requested(options) signal node_selected(node_data) signal map_visibility_changed(is_visible) const MapGenerator = preload("res://Systems/Game/Map/MapGenerator.gd") # Room type constants # 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_SELECTED = Color(0.2, 0.6, 0.2) const LINE_COLOR_ACCESSIBLE = Color(0.6, 0.6, 0.2) const NODE_COLOR = Color(0.1, 0.1, 0.1, 0.5) const NODE_LEAF = Color(0.2, 0.6, 0.2) # Node symbols and colors const NODE_SYMBOLS = { Utils.RoomType.STARTING: "O", # Circle Utils.RoomType.NORMAL: "■", # Square Utils.RoomType.BOSS: "★", # Star Utils.RoomType.FINAL: "✱", # Burst Utils.RoomType.SHOP: "₵", # Star Utils.RoomType.EVENT: "?" # Burst } const NODE_COLORS = { Utils.RoomType.STARTING: Color(1, 1, 1), # White Utils.RoomType.NORMAL: Color(0.2, 0.2, 0.2), # Dark gray Utils.RoomType.BOSS: Color(0.9, 0.2, 0.2), # Red Utils.RoomType.FINAL: Color(0.9, 0.9, 0.2), # Yellow Utils.RoomType.SHOP: Color(0.2, 0.7, 0.9), # Yellow Utils.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 const SCROLL_PADDING_TOP = 80 const SCROLL_PADDING_BOTTOM = 80 const SCROLL_PADDING_LEFT = 60 const SCROLL_PADDING_RIGHT = 100 var node_popup_scene = preload("res://node_map_popup.tscn") var selected_node_panel: NodePopup = null @onready var map_container = $MapScrollContainer/MapContainer @onready var back_button = $BackButton @onready var deck_manager_button = $DeckManagerButton @onready var title_label = $TitleLabel @onready var legend_container = $LegendContainer @onready var gold_label = $GoldLabel func _ready(): # Connect back button if back_button: back_button.connect("pressed", Callable(self, "_on_back_button_pressed")) if deck_manager_button: deck_manager_button.connect("pressed", Callable(self, "_on_deck_button_pressed")) # Create legend create_legend() generate_map() display_map() selected_node_panel = node_popup_scene.instantiate() selected_node_panel.visible = false add_child(selected_node_panel) if selected_node_panel: selected_node_panel.pressed.connect(on_node_panel_pressed) func create_legend(): # Create legend for room types var legend_items = [ {"type": Utils.RoomType.BOSS, "text": "Boss"}, {"type": Utils.RoomType.STARTING, "text": "Starting Room"}, {"type": Utils.RoomType.FINAL, "text": "Final Boss"}, {"type": Utils.RoomType.NORMAL, "text": "Normal Room"}, {"type": Utils.RoomType.SHOP, "text": "Shop"}, {"type": Utils.RoomType.EVENT, "text": "Event"}, {"type": Utils.RoomType.NORMAL, "text": "Escape 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] if item.text != "Escape Room": symbol.add_theme_color_override("font_color", NODE_COLORS[item.type]) else: symbol.add_theme_color_override("font_color", NODE_LEAF) 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() connection_lines.clear() traversed_map.clear() var mapGen = MapGenerator.new().generate_map() # Create starting node # var start_node = { # "id": 0, # "type": Utils.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": Utils.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, node_data.metadata ) # 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, metadata): map_nodes.append({ "id": id, "type": type, "level": level, "position": position, "elo": elo, "metadata": metadata, }) 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) 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) 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) 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) 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) # Draw connections first (so they're behind nodes) for connection in map_connections: 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) # 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 isLeaf = true; for connection in map_connections: # if theres outgoing connection we arent at a dead end # dont change final node colour if connection.from == node_data.id || node_data.id == 1: isLeaf = false break; 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 = NODE_COLOR 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 if isLeaf: style.bg_color = NODE_LEAF style.border_color = NODE_COLORS[node_data.type] button.add_theme_font_size_override("font_size", 28) if isLeaf: button.add_theme_color_override("font_color", NODE_LEAF) # style.bg_color = NODE_LEAF else: 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 on_node_panel_pressed(id): # print("on_node_panel_pressed ", id ) if id != null: current_node = null for node in map_nodes: if node.id == id: current_node = node break traversed_node(current_node) # Refresh display to update highlights display_map() # Emit signal with selected node data emit_signal("node_selected", current_node) selected_node_panel.visible = false; 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): if is_node_accessible(node_data) || is_node_path_accessible(node_data): # Get node type description var node_name = get_node_type_name(node_data.type) var node_desc = get_node_description(node_data) # Position the popup near the clicked node var button_pos = node_buttons[node_data.id].global_position var popup_pos = button_pos + Vector2(NODE_SIZE.x/2, NODE_SIZE.y/2) selected_node_panel.global_position = popup_pos # Set popup data and make it visible selected_node_panel.set_card(node_data.id, node_name, node_desc) if traversed_map.has(node_data.id): selected_node_panel._disable_enter() else: selected_node_panel._enable_enter() selected_node_panel.visible = true else: print("Node not accessible from current position", node_data) func traversed_node(node_data): traversed_map.append(node_data.id) func is_node_path_accessible(node_data): if current_node == null: return node_data.type == Utils.RoomType.STARTING # Check if there's a direct connection from current node to the path travelled for id in traversed_map: var cur = get_node_by_id(id) for connection in map_connections: if connection.from == cur.id and connection.to == node_data.id: return true return false func is_node_accessible(node_data): if current_node == null: return node_data.type == Utils.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 _on_deck_button_pressed(): emit_signal("deckmanager_open_requested", {}) visible = false func draw_curved_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 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) # Create a bezier curve path var points = [] # If nodes are on different levels (y positions) if from_node.position.y != to_node.position.y: 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) var control1 = Vector2(start_pos.x, start_pos.y + control_offset) var control2 = Vector2(end_pos.x, end_pos.y - control_offset) # 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: # For nodes on the same level, use a simple curved path var mid_x = (start_pos.x + end_pos.x) / 2 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) # 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) # 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 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) 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) func get_node_type_name(type): match type: Utils.RoomType.STARTING: return "Starting Room" Utils.RoomType.NORMAL: return "Chess Match" Utils.RoomType.BOSS: return "Boss Battle" Utils.RoomType.FINAL: return "Final Boss" Utils.RoomType.SHOP: return "Card Shop" Utils.RoomType.EVENT: return "Random Event" _: return "Unknown Room" func get_node_description(node_data): var desc = "" match node_data.type: Utils.RoomType.STARTING: desc = "Begin the dungeon" Utils.RoomType.NORMAL: desc = "A chess match \n\n ELO: " + str(node_data.elo) Utils.RoomType.BOSS: desc = "Maybe add some Boss lore? \n\n ELO: " + str(node_data.elo) Utils.RoomType.FINAL: desc = "BHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHA" Utils.RoomType.SHOP: desc = "Purchase new cards" Utils.RoomType.EVENT: desc = "A random event from pawn zerg to slot machine" _: desc = "An unknown room type." # Add additional info if this is a leaf node (dead end) var isLeaf = true for connection in map_connections: if connection.from == node_data.id || node_data.id == 1: isLeaf = false break if isLeaf && node_data.type != Utils.RoomType.FINAL: desc += "\n\nWarning: This is an escape node" return desc func update_gold_display(): var game = get_node_or_null("/root/Board") as ChessGame if gold_label: gold_label.text = str(game.player.get_gold()) + " GOLD" # Notification func _notification(what): if what == NOTIFICATION_VISIBILITY_CHANGED: _on_visibility_changed(visible) func _on_visibility_changed(is_visible): print("MapScreen visibility changed to: ", is_visible) if is_visible: update_gold_display() else: print("MapScreen is now invisible") emit_signal("map_visibility_changed", is_visible)