Compare commits

...

2 commits

Author SHA1 Message Date
b4db4afd5f Bezier Map curves 2025-03-04 11:08:01 -06:00
e1178d5f5c tesitng maps 2025-03-04 11:03:28 -06:00
12 changed files with 1038 additions and 3 deletions

View file

@ -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")

View file

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

View file

@ -0,0 +1 @@
uid://6cmhvsug8nbv

View file

@ -0,0 +1,214 @@
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)
# 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

View file

@ -0,0 +1 @@
uid://bs0u5jjxm2c18

View file

@ -0,0 +1,418 @@
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_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 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)
# Draw connections first (so they're behind nodes)
# 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 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 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):
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 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)
# Create a bezier curve path
var points = []
# If nodes are on different levels (y positions)
if from_node.position.y != to_node.position.y:
# 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)
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)
# 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)

View file

@ -0,0 +1 @@
uid://ckpv3snpg6g34

View file

@ -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

View file

@ -0,0 +1 @@
uid://bmi14xk8tn7xc

View file

@ -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

View file

@ -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

116
map_screen.tscn Normal file
View file

@ -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")