Add minimap map navigation

This commit is contained in:
2026-06-19 18:04:41 +09:00
parent 71e6abb986
commit 49cf3a735f
4 changed files with 418 additions and 95 deletions

View File

@@ -51,6 +51,10 @@ const EDGE_SCROLL_MARGIN := 48.0
const EDGE_SCROLL_OUTER_MARGIN := 28.0
const EDGE_SCROLL_MIN_SPEED := 80.0
const EDGE_SCROLL_MAX_SPEED := 430.0
const MINIMAP_SIZE := Vector2(178, 134)
const MINIMAP_PADDING := 8.0
const MINIMAP_TITLE_HEIGHT := 18.0
const MINIMAP_MARGIN := Vector2(18, 18)
const UNIT_MOVE_ANIMATION_DURATION := 0.18
const TARGET_PREVIEW_BADGE_SIZE := Vector2(104, 22)
const LOW_HP_WARNING_RATIO := 0.35
@@ -431,6 +435,7 @@ var battle_background_texture: Texture2D
var ui_regular_font: Font
var ui_bold_font: Font
var board_scroll_offset := Vector2.ZERO
var minimap_dragging := false
func _ready() -> void:
@@ -467,14 +472,22 @@ func _unhandled_input(event: InputEvent) -> void:
return
if event is InputEventMouseMotion:
if _handle_minimap_mouse_motion(event):
return
var next_hover := _cell_from_screen(event.position)
if next_hover != hover_cell:
hover_cell = next_hover
_update_hud()
queue_redraw()
elif event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
if _handle_minimap_mouse_button(event):
return
_handle_board_click(event.position)
elif event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
if _handle_minimap_mouse_button(event):
return
elif event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
minimap_dragging = false
_handle_cancel_input()
elif event is InputEventKey and event.pressed and not event.echo:
_handle_key(event)
@@ -489,6 +502,8 @@ func _draw() -> void:
_draw_attack_effects()
_draw_target_preview_badge()
_draw_floating_texts()
_draw_map_view_chrome()
_draw_minimap()
func _make_panel_style(fill: Color, border: Color, border_width: int, radius: int, margin: int, shadow_size: int) -> StyleBoxFlat:
@@ -583,17 +598,29 @@ func _apply_panel_style(panel: PanelContainer, variant: String) -> void:
border_width = 5
margin = 3
"dialogue":
fill = Color(0.070, 0.024, 0.012, 0.986)
border = Color(0.37, 0.20, 0.070, 1.0)
border_width = 13
margin = 22
shadow_size = 22
fill = Color(0.055, 0.028, 0.016, 0.982)
border = Color(0.30, 0.18, 0.075, 1.0)
border_width = 11
margin = 20
shadow_size = 18
"speech_scroll":
fill = Color(0.72, 0.53, 0.25, 0.998)
border = UI_BRUSH_RULE
border_width = 9
margin = 17
shadow_size = 9
fill = Color(0.61, 0.47, 0.26, 0.996)
border = Color(0.17, 0.085, 0.034, 0.98)
border_width = 7
margin = 15
shadow_size = 6
"hud_info":
fill = Color(0.075, 0.050, 0.034, 0.88)
border = Color(0.31, 0.22, 0.12, 0.96)
border_width = 3
margin = 7
shadow_size = 3
"target_hint":
fill = Color(0.11, 0.070, 0.040, 0.94)
border = UI_TARNISHED_BRONZE
border_width = 4
margin = 9
shadow_size = 6
"notice":
fill = Color(0.070, 0.020, 0.014, 0.982)
border = UI_SEAL_RED
@@ -1357,17 +1384,31 @@ func _create_hud() -> void:
mission_detail_row.add_child(_make_edict_marker_stack(["", "", ""], 112))
mission_detail_row.add_child(_make_bamboo_gutter(12, 112))
var cell_info_panel := PanelContainer.new()
cell_info_panel.custom_minimum_size = Vector2(420, 82)
cell_info_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_apply_panel_style(cell_info_panel, "hud_info")
side_column.add_child(cell_info_panel)
cell_info_label = Label.new()
cell_info_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
cell_info_label.custom_minimum_size = Vector2(420, 78)
_apply_label_style(cell_info_label, UI_PARCHMENT_TEXT)
side_column.add_child(cell_info_label)
cell_info_label.custom_minimum_size = Vector2(396, 62)
cell_info_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_apply_label_style(cell_info_label, Color(0.92, 0.78, 0.54, 1.0), 13)
cell_info_panel.add_child(cell_info_label)
var forecast_panel := PanelContainer.new()
forecast_panel.custom_minimum_size = Vector2(420, 66)
forecast_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_apply_panel_style(forecast_panel, "hud_info")
side_column.add_child(forecast_panel)
forecast_label = Label.new()
forecast_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
forecast_label.custom_minimum_size = Vector2(420, 60)
_apply_label_style(forecast_label, UI_PARCHMENT_TEXT)
side_column.add_child(forecast_label)
forecast_label.custom_minimum_size = Vector2(396, 46)
forecast_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_apply_label_style(forecast_label, Color(0.95, 0.80, 0.56, 1.0), 13)
forecast_panel.add_child(forecast_label)
inventory_label = Label.new()
inventory_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
@@ -1575,7 +1616,7 @@ func _create_hud() -> void:
targeting_hint_panel.mouse_filter = Control.MOUSE_FILTER_STOP
targeting_hint_panel.custom_minimum_size = TARGETING_HINT_PANEL_SIZE
targeting_hint_panel.size = TARGETING_HINT_PANEL_SIZE
_apply_panel_style(targeting_hint_panel, "command_seal")
_apply_panel_style(targeting_hint_panel, "target_hint")
root.add_child(targeting_hint_panel)
var targeting_column := VBoxContainer.new()
@@ -2012,16 +2053,14 @@ func _create_hud() -> void:
dialogue_column = VBoxContainer.new()
dialogue_column.custom_minimum_size = DIALOGUE_COLUMN_SIZE
dialogue_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
dialogue_column.add_theme_constant_override("separation", 4)
dialogue_column.add_theme_constant_override("separation", 6)
dialogue_row.add_child(dialogue_column)
dialogue_right_tassel = _make_hanging_tassel(DIALOGUE_PORTRAIT_SIZE.y)
dialogue_row.add_child(dialogue_right_tassel)
dialogue_column.add_child(_make_tablet_binding(DIALOGUE_TEXT_SIZE.x, 13))
dialogue_column.add_child(_make_edict_title_strip(["", "", ""], DIALOGUE_TEXT_SIZE.x, 22))
dialogue_seal_ribbon = _make_seal_ribbon(DIALOGUE_TEXT_SIZE.x, 18)
dialogue_seal_ribbon.modulate = Color(1.0, 1.0, 1.0, 0.62)
dialogue_column.add_child(dialogue_seal_ribbon)
dialogue_speaker_panel = PanelContainer.new()
@@ -2032,15 +2071,12 @@ func _create_hud() -> void:
var dialogue_speaker_row := HBoxContainer.new()
dialogue_speaker_row.add_theme_constant_override("separation", 8)
dialogue_speaker_panel.add_child(dialogue_speaker_row)
dialogue_speaker_row.add_child(_make_seal_tile("", 24))
dialogue_speaker_label = Label.new()
dialogue_speaker_label.custom_minimum_size = Vector2(DIALOGUE_TEXT_SIZE.x - 88, 24)
dialogue_speaker_label.custom_minimum_size = Vector2(DIALOGUE_TEXT_SIZE.x - 24, 24)
dialogue_speaker_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
dialogue_speaker_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_apply_label_style(dialogue_speaker_label, UI_PARCHMENT_TEXT, DIALOGUE_SPEAKER_FONT_SIZE)
dialogue_speaker_row.add_child(dialogue_speaker_label)
dialogue_speaker_row.add_child(_make_seal_tile("", 24))
dialogue_text_panel = PanelContainer.new()
dialogue_text_panel.custom_minimum_size = Vector2(DIALOGUE_TEXT_SIZE.x, DIALOGUE_TEXT_SIZE.y)
@@ -2048,24 +2084,23 @@ func _create_hud() -> void:
dialogue_column.add_child(dialogue_text_panel)
var dialogue_text_row := HBoxContainer.new()
dialogue_text_row.add_theme_constant_override("separation", 8)
dialogue_text_row.add_theme_constant_override("separation", 6)
dialogue_text_panel.add_child(dialogue_text_row)
dialogue_text_row.add_child(_make_bamboo_gutter(18, DIALOGUE_TEXT_SIZE.y - 20))
dialogue_text_row.add_child(_make_edict_marker_stack(["", "", ""], DIALOGUE_TEXT_SIZE.y - 20))
dialogue_text_row.add_child(_make_bamboo_gutter(10, DIALOGUE_TEXT_SIZE.y - 20))
dialogue_text_label = Label.new()
dialogue_text_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
dialogue_text_label.custom_minimum_size = Vector2(DIALOGUE_TEXT_SIZE.x - 164, DIALOGUE_TEXT_SIZE.y - 20)
dialogue_text_label.custom_minimum_size = Vector2(DIALOGUE_TEXT_SIZE.x - 44, DIALOGUE_TEXT_SIZE.y - 20)
dialogue_text_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
dialogue_text_label.size_flags_vertical = Control.SIZE_EXPAND_FILL
_apply_label_style(dialogue_text_label, UI_AGED_INK, DIALOGUE_TEXT_FONT_SIZE)
dialogue_text_row.add_child(dialogue_text_label)
dialogue_text_row.add_child(_make_edict_marker_stack(["", "", ""], DIALOGUE_TEXT_SIZE.y - 20))
dialogue_text_row.add_child(_make_bamboo_gutter(18, DIALOGUE_TEXT_SIZE.y - 20))
dialogue_text_row.add_child(_make_bamboo_gutter(10, DIALOGUE_TEXT_SIZE.y - 20))
dialogue_column.add_child(_make_scroll_rod(DIALOGUE_TEXT_SIZE.x, 3))
dialogue_column.add_child(_make_ink_wash_rule(DIALOGUE_TEXT_SIZE.x, 6))
var dialogue_bottom_rule := _make_ink_wash_rule(DIALOGUE_TEXT_SIZE.x, 5)
dialogue_bottom_rule.modulate = Color(1.0, 1.0, 1.0, 0.62)
dialogue_column.add_child(dialogue_bottom_rule)
var dialogue_control_row := HBoxContainer.new()
dialogue_control_row.add_theme_constant_override("separation", 8)
@@ -2649,17 +2684,23 @@ func _can_select(unit: Dictionary) -> bool:
func _draw_map() -> void:
var board_rect := _board_rect()
var view_rect := _map_view_rect()
var visible_rect := board_rect.intersection(view_rect)
var background := _current_battle_background_texture()
var has_background := background != null
draw_rect(view_rect, Color(0.025, 0.020, 0.014, 0.96))
if visible_rect.size.x <= 0.0 or visible_rect.size.y <= 0.0:
return
if background != null:
_draw_texture_cover(background, board_rect, Color(1.0, 1.0, 1.0, 0.96))
draw_rect(board_rect, Color(0.02, 0.025, 0.03, 0.12))
draw_rect(board_rect, Color(0.35, 0.22, 0.10, 0.10))
_draw_texture_cover_region(background, visible_rect, board_rect, Color(1.0, 1.0, 1.0, 0.96))
draw_rect(visible_rect, Color(0.02, 0.025, 0.03, 0.12))
draw_rect(visible_rect, Color(0.35, 0.22, 0.10, 0.10))
else:
draw_rect(board_rect, Color(0.18, 0.26, 0.18))
draw_rect(visible_rect, Color(0.18, 0.26, 0.18))
for y in range(state.map_size.y):
for x in range(state.map_size.x):
var bounds := _visible_cell_bounds()
for y in range(bounds.position.y, bounds.position.y + bounds.size.y):
for x in range(bounds.position.x, bounds.position.x + bounds.size.x):
var cell := Vector2i(x, y)
var rect := _rect_for_cell(cell)
var terrain_key := state.get_terrain_key(cell)
@@ -2668,8 +2709,7 @@ func _draw_map() -> void:
_draw_terrain_edge_blend(cell, rect, terrain_key)
draw_rect(rect, _map_grid_color(has_background), false, 1.0)
_draw_silk_map_patina(board_rect, has_background)
_draw_silk_map_frame(board_rect)
_draw_silk_map_patina(visible_rect, has_background)
func _draw_silk_map_patina(board_rect: Rect2, has_background: bool) -> void:
@@ -2737,6 +2777,79 @@ func _draw_silk_map_frame(board_rect: Rect2) -> void:
draw_line(seal_rect.position + Vector2(seal_rect.size.x * 0.5, 8), seal_rect.end - Vector2(seal_rect.size.x * 0.5, 8), Color(UI_OLD_BRONZE.r, UI_OLD_BRONZE.g, UI_OLD_BRONZE.b, 0.34), 1.2)
func _draw_map_view_chrome() -> void:
var view_rect := _map_view_rect()
var viewport_size := _viewport_size_for_layout()
var mask := Color(0.018, 0.012, 0.008, 0.94)
draw_rect(Rect2(Vector2.ZERO, Vector2(viewport_size.x, view_rect.position.y)), mask)
draw_rect(Rect2(Vector2.ZERO, Vector2(view_rect.position.x, viewport_size.y)), mask)
draw_rect(Rect2(Vector2(view_rect.end.x, 0.0), Vector2(maxf(0.0, viewport_size.x - view_rect.end.x), viewport_size.y)), mask)
draw_rect(Rect2(Vector2(view_rect.position.x, view_rect.end.y), Vector2(view_rect.size.x, maxf(0.0, viewport_size.y - view_rect.end.y))), mask)
_draw_edge_scroll_cues(view_rect)
_draw_silk_map_frame(view_rect)
func _draw_edge_scroll_cues(view_rect: Rect2) -> void:
if not _is_minimap_visible():
return
var min_offset := _clamped_board_scroll_offset(Vector2(-9999.0, -9999.0))
var cue_color := Color(UI_OLD_BRONZE.r, UI_OLD_BRONZE.g, UI_OLD_BRONZE.b, 0.18)
var hot_color := Color(UI_OLD_BRONZE.r, UI_OLD_BRONZE.g, UI_OLD_BRONZE.b, 0.36)
var strip := 14.0
if board_scroll_offset.x < -0.5:
draw_rect(Rect2(view_rect.position, Vector2(strip, view_rect.size.y)), cue_color)
draw_line(view_rect.position + Vector2(5.0, view_rect.size.y * 0.5), view_rect.position + Vector2(11.0, view_rect.size.y * 0.5 - 8.0), hot_color, 2.0)
draw_line(view_rect.position + Vector2(5.0, view_rect.size.y * 0.5), view_rect.position + Vector2(11.0, view_rect.size.y * 0.5 + 8.0), hot_color, 2.0)
if board_scroll_offset.x > min_offset.x + 0.5:
draw_rect(Rect2(Vector2(view_rect.end.x - strip, view_rect.position.y), Vector2(strip, view_rect.size.y)), cue_color)
draw_line(Vector2(view_rect.end.x - 5.0, view_rect.position.y + view_rect.size.y * 0.5), Vector2(view_rect.end.x - 11.0, view_rect.position.y + view_rect.size.y * 0.5 - 8.0), hot_color, 2.0)
draw_line(Vector2(view_rect.end.x - 5.0, view_rect.position.y + view_rect.size.y * 0.5), Vector2(view_rect.end.x - 11.0, view_rect.position.y + view_rect.size.y * 0.5 + 8.0), hot_color, 2.0)
if board_scroll_offset.y < -0.5:
draw_rect(Rect2(view_rect.position, Vector2(view_rect.size.x, strip)), cue_color)
draw_line(view_rect.position + Vector2(view_rect.size.x * 0.5, 5.0), view_rect.position + Vector2(view_rect.size.x * 0.5 - 8.0, 11.0), hot_color, 2.0)
draw_line(view_rect.position + Vector2(view_rect.size.x * 0.5, 5.0), view_rect.position + Vector2(view_rect.size.x * 0.5 + 8.0, 11.0), hot_color, 2.0)
if board_scroll_offset.y > min_offset.y + 0.5:
draw_rect(Rect2(Vector2(view_rect.position.x, view_rect.end.y - strip), Vector2(view_rect.size.x, strip)), cue_color)
draw_line(Vector2(view_rect.position.x + view_rect.size.x * 0.5, view_rect.end.y - 5.0), Vector2(view_rect.position.x + view_rect.size.x * 0.5 - 8.0, view_rect.end.y - 11.0), hot_color, 2.0)
draw_line(Vector2(view_rect.position.x + view_rect.size.x * 0.5, view_rect.end.y - 5.0), Vector2(view_rect.position.x + view_rect.size.x * 0.5 + 8.0, view_rect.end.y - 11.0), hot_color, 2.0)
func _draw_minimap() -> void:
if not _is_minimap_visible():
return
var rect := _minimap_rect()
var map_rect := _minimap_map_rect()
draw_rect(rect, Color(0.040, 0.024, 0.014, 0.94))
draw_rect(rect, Color(UI_TARNISHED_BRONZE.r, UI_TARNISHED_BRONZE.g, UI_TARNISHED_BRONZE.b, 0.92), false, 3.0)
draw_rect(Rect2(rect.position + Vector2(4.0, 4.0), Vector2(rect.size.x - 8.0, MINIMAP_TITLE_HEIGHT + 2.0)), Color(0.12, 0.055, 0.026, 0.72))
var font := _draw_ui_font(true)
_draw_ink_text(font, rect.position + Vector2(0.0, 19.0), "전장도", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x, 13, UI_PARCHMENT_TEXT)
draw_rect(map_rect, Color(0.075, 0.070, 0.050, 0.94))
for y in range(state.map_size.y):
for x in range(state.map_size.x):
var cell := Vector2i(x, y)
var terrain_color: Color = state.get_terrain_color(cell)
terrain_color.a = 0.76
draw_rect(_minimap_cell_rect(cell), terrain_color)
for unit in state.get_living_units():
var cell: Vector2i = unit.get("pos", Vector2i(-1, -1))
if not state.is_inside(cell):
continue
var dot_color := PLAYER_COLOR.lightened(0.36) if unit.get("team", "") == BattleState.TEAM_PLAYER else ENEMY_COLOR.lightened(0.30)
var cell_rect := _minimap_cell_rect(cell)
var radius := maxf(2.4, minf(cell_rect.size.x, cell_rect.size.y) * 0.70)
draw_circle(cell_rect.get_center(), radius + 1.1, Color(0.020, 0.014, 0.010, 0.82))
draw_circle(cell_rect.get_center(), radius, dot_color)
var current_view := _minimap_view_rect()
if current_view.size.x > 0.0 and current_view.size.y > 0.0:
draw_rect(current_view, Color(UI_OLD_BRONZE.r, UI_OLD_BRONZE.g, UI_OLD_BRONZE.b, 0.22))
draw_rect(current_view, Color(UI_OLD_BRONZE.r, UI_OLD_BRONZE.g, UI_OLD_BRONZE.b, 0.98), false, 2.0)
draw_rect(map_rect, Color(UI_BRUSH_RULE.r, UI_BRUSH_RULE.g, UI_BRUSH_RULE.b, 0.90), false, 1.5)
func _current_battle_background_texture() -> Texture2D:
var next_path := state.get_map_background_path()
if next_path.is_empty():
@@ -2749,9 +2862,23 @@ func _current_battle_background_texture() -> Texture2D:
func _draw_texture_cover(texture: Texture2D, rect: Rect2, modulate: Color) -> void:
var source_rect := _texture_cover_source_rect(texture, rect)
if source_rect.size.x <= 0.0 or source_rect.size.y <= 0.0:
return
draw_texture_rect_region(texture, rect, source_rect, modulate)
func _draw_texture_cover_region(texture: Texture2D, visible_rect: Rect2, full_rect: Rect2, modulate: Color) -> void:
var source_rect := _texture_cover_region_source_rect(texture, visible_rect, full_rect)
if source_rect.size.x <= 0.0 or source_rect.size.y <= 0.0:
return
draw_texture_rect_region(texture, visible_rect, source_rect, modulate)
func _texture_cover_source_rect(texture: Texture2D, rect: Rect2) -> Rect2:
var texture_size := texture.get_size()
if texture_size.x <= 0.0 or texture_size.y <= 0.0 or rect.size.x <= 0.0 or rect.size.y <= 0.0:
return
return Rect2()
var target_ratio := rect.size.x / rect.size.y
var source_ratio := texture_size.x / texture_size.y
var source_rect := Rect2(Vector2.ZERO, texture_size)
@@ -2763,7 +2890,24 @@ func _draw_texture_cover(texture: Texture2D, rect: Rect2, modulate: Color) -> vo
var source_height := texture_size.x / target_ratio
source_rect.position.y = (texture_size.y - source_height) * 0.5
source_rect.size.y = source_height
draw_texture_rect_region(texture, rect, source_rect, modulate)
return source_rect
func _texture_cover_region_source_rect(texture: Texture2D, visible_rect: Rect2, full_rect: Rect2) -> Rect2:
if full_rect.size.x <= 0.0 or full_rect.size.y <= 0.0 or visible_rect.size.x <= 0.0 or visible_rect.size.y <= 0.0:
return Rect2()
var full_source := _texture_cover_source_rect(texture, full_rect)
if full_source.size.x <= 0.0 or full_source.size.y <= 0.0:
return Rect2()
var relative_position := Vector2(
(visible_rect.position.x - full_rect.position.x) / full_rect.size.x,
(visible_rect.position.y - full_rect.position.y) / full_rect.size.y
)
var relative_size := Vector2(visible_rect.size.x / full_rect.size.x, visible_rect.size.y / full_rect.size.y)
return Rect2(
full_source.position + Vector2(full_source.size.x * relative_position.x, full_source.size.y * relative_position.y),
Vector2(full_source.size.x * relative_size.x, full_source.size.y * relative_size.y)
)
func _terrain_fill_color(cell: Vector2i, terrain_key: String, has_background: bool) -> Color:
@@ -4523,6 +4667,36 @@ func _map_view_rect() -> Rect2:
)
func _visible_board_local_rect() -> Rect2:
var visible_rect := _board_rect().intersection(_map_view_rect())
if visible_rect.size.x <= 0.0 or visible_rect.size.y <= 0.0:
return Rect2()
return Rect2(visible_rect.position - _board_origin(), visible_rect.size)
func _visible_cell_bounds(extra_margin: int = 1) -> Rect2i:
if state.map_size.x <= 0 or state.map_size.y <= 0:
return Rect2i()
var local_rect := _visible_board_local_rect()
if local_rect.size.x <= 0.0 or local_rect.size.y <= 0.0:
return Rect2i()
var start_x := clampi(int(floor(local_rect.position.x / TILE_SIZE)) - extra_margin, 0, state.map_size.x)
var start_y := clampi(int(floor(local_rect.position.y / TILE_SIZE)) - extra_margin, 0, state.map_size.y)
var end_x := clampi(int(ceil((local_rect.position.x + local_rect.size.x) / TILE_SIZE)) + extra_margin, 0, state.map_size.x)
var end_y := clampi(int(ceil((local_rect.position.y + local_rect.size.y) / TILE_SIZE)) + extra_margin, 0, state.map_size.y)
return Rect2i(Vector2i(start_x, start_y), Vector2i(maxi(0, end_x - start_x), maxi(0, end_y - start_y)))
func _cell_is_in_visible_bounds(cell: Vector2i, extra_margin: int = 1) -> bool:
var bounds := _visible_cell_bounds(extra_margin)
return (
cell.x >= bounds.position.x
and cell.y >= bounds.position.y
and cell.x < bounds.position.x + bounds.size.x
and cell.y < bounds.position.y + bounds.size.y
)
func _viewport_size_for_layout() -> Vector2:
if is_inside_tree():
return get_viewport_rect().size
@@ -4544,6 +4718,115 @@ func _clamped_board_scroll_offset(offset: Vector2) -> Vector2:
return Vector2(clampf(offset.x, min_x, 0.0), clampf(offset.y, min_y, 0.0))
func _minimap_rect() -> Rect2:
var view_rect := _map_view_rect()
var position := view_rect.end - MINIMAP_SIZE - MINIMAP_MARGIN
position.x = maxf(view_rect.position.x + MINIMAP_MARGIN.x, position.x)
position.y = maxf(view_rect.position.y + MINIMAP_MARGIN.y, position.y)
return Rect2(position, MINIMAP_SIZE)
func _minimap_map_rect() -> Rect2:
var rect := _minimap_rect()
return Rect2(
rect.position + Vector2(MINIMAP_PADDING, MINIMAP_PADDING + MINIMAP_TITLE_HEIGHT),
Vector2(
maxf(1.0, rect.size.x - MINIMAP_PADDING * 2.0),
maxf(1.0, rect.size.y - MINIMAP_PADDING * 2.0 - MINIMAP_TITLE_HEIGHT)
)
)
func _minimap_cell_rect(cell: Vector2i) -> Rect2:
var map_rect := _minimap_map_rect()
if state.map_size.x <= 0 or state.map_size.y <= 0:
return Rect2(map_rect.position, Vector2.ZERO)
var cell_size := Vector2(map_rect.size.x / float(state.map_size.x), map_rect.size.y / float(state.map_size.y))
return Rect2(map_rect.position + Vector2(cell.x * cell_size.x, cell.y * cell_size.y), cell_size)
func _minimap_view_rect() -> Rect2:
var board_size := _board_size()
if board_size.x <= 0.0 or board_size.y <= 0.0:
return Rect2()
var local_rect := _visible_board_local_rect()
if local_rect.size.x <= 0.0 or local_rect.size.y <= 0.0:
return Rect2()
var map_rect := _minimap_map_rect()
return Rect2(
map_rect.position + Vector2(
(local_rect.position.x / board_size.x) * map_rect.size.x,
(local_rect.position.y / board_size.y) * map_rect.size.y
),
Vector2(
(local_rect.size.x / board_size.x) * map_rect.size.x,
(local_rect.size.y / board_size.y) * map_rect.size.y
)
)
func _board_scroll_offset_for_minimap_position(screen_position: Vector2) -> Vector2:
var map_rect := _minimap_map_rect()
var board_size := _board_size()
if board_size.x <= 0.0 or board_size.y <= 0.0:
return _clamped_board_scroll_offset(board_scroll_offset)
var local_fraction := Vector2(
clampf((screen_position.x - map_rect.position.x) / map_rect.size.x, 0.0, 1.0),
clampf((screen_position.y - map_rect.position.y) / map_rect.size.y, 0.0, 1.0)
)
var target_local := Vector2(board_size.x * local_fraction.x, board_size.y * local_fraction.y)
var view_rect := _map_view_rect()
var desired_offset := view_rect.get_center() - BOARD_OFFSET - target_local
return _clamped_board_scroll_offset(desired_offset)
func _scroll_board_to_minimap_position(screen_position: Vector2) -> bool:
var previous_offset := board_scroll_offset
board_scroll_offset = _board_scroll_offset_for_minimap_position(screen_position)
var next_hover := _cell_from_screen(_map_view_rect().get_center())
if next_hover != hover_cell:
hover_cell = next_hover
if status_label != null:
_update_hud()
queue_redraw()
return previous_offset.distance_squared_to(board_scroll_offset) > 0.01
func _is_minimap_visible() -> bool:
return battle_started and state.map_size.x > 0 and state.map_size.y > 0 and state.battle_status == BattleState.STATUS_ACTIVE
func _handle_minimap_mouse_button(event: InputEventMouseButton) -> bool:
if not _is_minimap_visible():
minimap_dragging = false
return false
if event.button_index != MOUSE_BUTTON_LEFT:
return false
var inside_minimap := _minimap_rect().has_point(event.position)
var inside_map_body := _minimap_map_rect().has_point(event.position)
if event.pressed:
if not inside_minimap:
return false
if inside_map_body:
minimap_dragging = true
_scroll_board_to_minimap_position(event.position)
return true
if minimap_dragging:
minimap_dragging = false
return true
return inside_minimap
func _handle_minimap_mouse_motion(event: InputEventMouseMotion) -> bool:
if not _is_minimap_visible():
minimap_dragging = false
return false
if minimap_dragging:
_scroll_board_to_minimap_position(event.position)
return true
return _minimap_rect().has_point(event.position)
func _edge_scroll_velocity_for_position(mouse_position: Vector2, view_rect: Rect2) -> Vector2:
if not _edge_scroll_activation_rect(view_rect).has_point(mouse_position):
return Vector2.ZERO
@@ -4595,6 +4878,8 @@ func _update_edge_scroll(delta: float) -> bool:
return false
var view_rect := _map_view_rect()
var mouse_position := get_viewport().get_mouse_position()
if minimap_dragging or (_is_minimap_visible() and _minimap_rect().has_point(mouse_position)):
return false
var velocity := _edge_scroll_velocity_for_position(mouse_position, view_rect)
if velocity == Vector2.ZERO:
return false
@@ -8888,13 +9173,7 @@ func _is_targeting_mode() -> bool:
func _show_targeting_hint_panel() -> void:
if targeting_hint_panel == null:
return
if basic_attack_targeting:
_hide_targeting_hint_panel()
return
_update_targeting_hint_panel()
if targeting_hint_panel != null and _is_targeting_mode():
targeting_hint_panel.visible = true
targeting_hint_panel.move_to_front()
_hide_targeting_hint_panel()
func _hide_targeting_hint_panel() -> void:
@@ -8906,38 +9185,7 @@ func _hide_targeting_hint_panel() -> void:
func _update_targeting_hint_panel() -> void:
if targeting_hint_panel == null:
return
if basic_attack_targeting or not _is_targeting_mode():
_hide_targeting_hint_panel()
return
var selected := state.get_selected_unit()
if selected.is_empty() or bool(selected.get("acted", false)) or _is_input_locked():
_hide_targeting_hint_panel()
return
_position_targeting_hint_panel(selected)
if targeting_hint_title_label != null:
_set_fitted_label_text(
targeting_hint_title_label,
_targeting_hint_title_text(),
LOCAL_COMMAND_TITLE_FONT_SIZE,
LOCAL_COMMAND_TITLE_MIN_FONT_SIZE,
TARGETING_HINT_TITLE_SIZE
)
if targeting_hint_detail_label != null:
_set_fitted_label_text(
targeting_hint_detail_label,
_targeting_hint_detail_text(),
LOCAL_COMMAND_DETAIL_FONT_SIZE,
LOCAL_COMMAND_DETAIL_MIN_FONT_SIZE,
TARGETING_HINT_DETAIL_SIZE
)
if targeting_hint_back_button != null:
targeting_hint_back_button.text = "죽간 거두기"
targeting_hint_back_button.tooltip_text = "군령 선택으로 되돌립니다." if _has_pending_move() else "지목을 거둡니다."
if targeting_hint_cancel_move_button != null:
targeting_hint_cancel_move_button.visible = _has_pending_move()
targeting_hint_cancel_move_button.disabled = not _has_pending_move()
targeting_hint_cancel_move_button.tooltip_text = "발걸음을 거두면 원래 자리로 되돌립니다."
targeting_hint_panel.visible = true
_hide_targeting_hint_panel()
func _position_targeting_hint_panel(selected: Dictionary) -> void: