ChessBuilder/Systems/Game/Map/MapScreen.gd

549 lines
18 KiB
GDScript

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()
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.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)