fix(m56): remove duplicate guild and market ownership

This commit is contained in:
2026-09-12 18:24:48 +08:00
parent df323bfe21
commit e4abb196d7
4 changed files with 16 additions and 311 deletions
-11
View File
@@ -15,7 +15,6 @@ const M52_Q_GUILD_BACKGROUND = preload("res://assets/backgrounds/m52_q_guild_bac
const M52_GUILD_RESOURCE_STATUS = preload("res://ui/m52/guild_resource_status.gd")
const M52_GUILD_CONTRACT_BOARD = preload("res://ui/m52/guild_contract_board.gd")
const M52_GUILD_MEMBER_CARD = preload("res://ui/m52/guild_member_card.gd")
const M52_GUILD_NAVIGATION = preload("res://ui/m52/guild_navigation.gd")
const M52_GUILD_HEADER = preload("res://ui/m52/guild_header.gd")
const GUILD_DESK_CONTRACT_BOARD = preload("res://assets/entry/guild_desk_v001/contract-board.png")
const GUILD_DESK_TOP_LEFT = preload("res://assets/entry/guild_desk_v001/top_rail/left.png")
@@ -258,16 +257,6 @@ func _build_overlay(data: Dictionary) -> void:
_add_nav_button(overlay, "GuildEquipment", "GuildEquipmentCommandLabel", localize("装备", "Gear", page_data), "equipment", "equipment", 1, not bool(equipment_state.get("enabled", false)), page_data)
_add_nav_button(overlay, "GuildParty", "GuildPartyCommandLabel", localize("队列", "Party", page_data), "party", "party", 2, not bool(party_state.get("enabled", false)), page_data)
_add_nav_button(overlay, "GuildRisk", "GuildRouteStatusLabel", localize("风险", "Risk", page_data), "risk", "risk", 3, not bool(risk_state.get("enabled", false)), page_data)
var navigation := M52_GUILD_NAVIGATION.new().configure([
localize("成员", "Roster", page_data), localize("装备", "Gear", page_data),
localize("队列", "Party", page_data), localize("风险", "Risk", page_data)
])
navigation.name = "GuildNavigation"
_set_normalized_rect(navigation, Rect2(0.035, 0.855, 0.48, 0.12))
navigation.z_index = 20
navigation.mouse_filter = Control.MOUSE_FILTER_IGNORE
overlay.add_child(navigation)
var contract_state: Dictionary = Dictionary(destinations.get("contract", {}))
var market_state: Dictionary = Dictionary(destinations.get("market", {}))
var contract_enabled := bool(contract_state.get("enabled", false))
+7 -298
View File
@@ -9,7 +9,6 @@ extends "res://presentation/page_view.gd"
const InitialCompanyArt = preload("res://entry/initial_company_art.gd")
const MARKET_STATIC_MASTER = preload("res://assets/entry/shp_001_market/market-static-master.png")
const M50_MARKET_BACKGROUND = preload("res://assets/ui_m50/market_background.png")
const M52_Q_MARKET_BACKGROUND = preload("res://assets/backgrounds/m52_q_market_world_only.png")
const MarketChrome = preload("res://ui/shared/market_chrome.gd")
const MarketSlots = preload("res://ui/shared/m52_market_slots.gd")
@@ -20,7 +19,6 @@ const M52MarketHeader = preload("res://ui/m52/market_header.gd")
const M52ResourceStatusGroup = preload("res://ui/m52/resource_status_group.gd")
const M52FooterNav = preload("res://ui/m52/footer_nav.gd")
const USE_M50_RESPONSIVE_SLICE := true
var design_canvas: Control
@@ -33,37 +31,7 @@ func _notification(what: int) -> void:
func _render_view(data: Dictionary) -> void:
clear_view_children()
design_canvas = null
if USE_M50_RESPONSIVE_SLICE:
_render_m52_market_restore(data)
return
var matte := ColorRect.new()
matte.name = "MarketMasterMatte"
matte.color = Color("#17252c")
matte.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
matte.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(matte)
design_canvas = Control.new()
design_canvas.name = "MarketMasterCanvas"
design_canvas.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(design_canvas)
_layout_design_canvas()
var master := TextureRect.new()
master.name = "MarketStaticMaster"
master.texture = MARKET_STATIC_MASTER
master.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
master.stretch_mode = TextureRect.STRETCH_SCALE
master.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS
master.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
master.mouse_filter = Control.MOUSE_FILTER_IGNORE
design_canvas.add_child(master)
if not bool(data.get("ok", false)):
_render_invalid(data)
return
_build_overlay(data)
_render_m52_market_restore(data)
func _layout_design_canvas() -> void:
@@ -500,269 +468,6 @@ func _market_command_button(node_name: String, area: Rect2, intent_id: String, p
return button
## M50 responsive slice -----------------------------------------------------
## This path is intentionally self-contained so the old SHP-001 composition
## can remain available while the owner evaluates the new component grammar.
## It uses a full-bleed world image plus stretch-safe generated elements; no
## runtime text is baked into those images.
func _render_m50_responsive_slice(data: Dictionary) -> void:
var background := TextureRect.new()
background.name = "M50MarketWorldBackground"
background.texture = M52_Q_MARKET_BACKGROUND
background.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
background.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
background.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS
background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
background.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(background)
var matte := ColorRect.new()
matte.name = "MarketMasterMatte"
matte.color = Color(0.08, 0.05, 0.03, 0.05)
matte.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
matte.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(matte)
var veil := ColorRect.new()
veil.name = "MarketLegacyVeil"
# The approved Q background already carries its own painted value structure;
# keep only a light readability veil so the scene does not become a dark
# dashboard beneath the runtime controls.
veil.color = Color(0.02, 0.035, 0.05, 0.08)
veil.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
veil.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(veil)
# Stable legacy identities remain present for route/capture contracts while
# the responsive slice owns the visible composition.
var legacy_canvas := Control.new()
legacy_canvas.name = "MarketMasterCanvas"
legacy_canvas.visible = false
add_child(legacy_canvas)
var legacy_available := size
if legacy_available.x > 0.0 and legacy_available.y > 0.0:
var legacy_canvas_size := Vector2(legacy_available.y * (16.0 / 9.0), legacy_available.y) if legacy_available.x / legacy_available.y >= (16.0 / 9.0) else Vector2(legacy_available.x, legacy_available.x / (16.0 / 9.0))
legacy_canvas.position = (legacy_available - legacy_canvas_size) * 0.5
legacy_canvas.size = legacy_canvas_size
var legacy_master := TextureRect.new()
legacy_master.name = "MarketStaticMaster"
legacy_master.texture = MARKET_STATIC_MASTER
legacy_master.visible = false
legacy_canvas.add_child(legacy_master)
var legacy_layout := Control.new()
legacy_layout.name = "MarketLayout"
legacy_layout.visible = false
legacy_canvas.add_child(legacy_layout)
if not bool(data.get("ok", false)):
var invalid := UIFactory.label(String(data.get("notice", data.get("code", _tr("市场不可用", "MARKET UNAVAILABLE")))), "body", 1.0, Tokens.COLOR_IVORY)
invalid.name = "MarketInvalidOverlay"
invalid.set_anchors_and_offsets_preset(Control.PRESET_CENTER)
invalid.custom_minimum_size = Vector2(280, 80)
invalid.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
invalid.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
invalid.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
add_child(invalid)
return
var safe := MarginContainer.new()
safe.name = "M50MarketSafeArea"
safe.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
var margin := 18 if size.x >= 900.0 else 12
safe.add_theme_constant_override("margin_left", margin)
safe.add_theme_constant_override("margin_right", margin)
safe.add_theme_constant_override("margin_top", 12)
safe.add_theme_constant_override("margin_bottom", 12)
add_child(safe)
var page := VBoxContainer.new()
page.name = "M50MarketPage"
page.add_theme_constant_override("separation", 8)
page.size_flags_vertical = Control.SIZE_EXPAND_FILL
safe.add_child(page)
var top := HBoxContainer.new()
top.name = "M50MarketTopBar"
top.custom_minimum_size.y = 58
top.add_theme_constant_override("separation", 10)
var top_surface := Panel.new()
top_surface.name = "MarketTopBarSurface"
top_surface.custom_minimum_size.y = 58
top_surface.size_flags_horizontal = Control.SIZE_EXPAND_FILL
var top_style := StyleBoxFlat.new()
top_style.bg_color = Color(0.08, 0.13, 0.15, 0.78)
top_style.border_color = Color(Tokens.COLOR_GOLD_LIGHT, 0.62)
top_style.set_border_width_all(1)
top_style.set_corner_radius_all(10)
top_surface.add_theme_stylebox_override("panel", top_style)
page.add_child(top_surface)
top.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
top_surface.add_child(top)
var title_plaque := PanelContainer.new()
title_plaque.name = "MarketTitlePlaque"
title_plaque.custom_minimum_size = Vector2(210, 52)
title_plaque.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
title_plaque.add_theme_stylebox_override("panel", _flat_style(Color(0.94, 0.87, 0.70, 0.97), Color(0.47, 0.31, 0.18, 0.95), 2, 10))
var title_stack := VBoxContainer.new()
title_stack.alignment = BoxContainer.ALIGNMENT_CENTER
title_stack.add_theme_constant_override("separation", 0)
var title := UIFactory.label(_tr("市场", "MARKET", data), "heading", 1.0, Tokens.COLOR_INK)
title.name = "M50MarketTitle"
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
title.add_theme_color_override("font_outline_color", Color(0.80, 0.65, 0.40, 0.45))
title.add_theme_constant_override("outline_size", 1)
title_stack.add_child(title)
var stage := UIFactory.label(_tr("准备出发", "PREPARE TO DEPART", data), "micro", 1.0, Color("#7b5228"))
stage.name = "M50MarketStage"
stage.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
title_stack.add_child(stage)
title_plaque.add_child(title_stack)
top.add_child(title_plaque)
var company_facts: Dictionary = Dictionary(data.get("company", {}))
var company_status := _m50_resource_chip("MarketCompanyStatus", "company", "%s %d/%d" % [_tr("队伍", "LINE", data), int(company_facts.get("owned_count", 0)), int(company_facts.get("line_capacity", 0))])
top.add_child(company_status)
var coin := _m50_resource_chip("MarketCoinStatus", "coin", "%s %d" % [_tr("金币", "COIN", data), int(company_facts.get("coin", 0))])
top.add_child(coin)
var settings := UIFactory.command_button("MarketSettings", "", "icon", "settings", "flat", "content", Vector2(52, 48))
settings.tooltip_text = _tr("打开设置", "Open settings", data)
settings.pressed.connect(func(): emit_intent("settings", {}), CONNECT_DEFERRED)
top.add_child(settings)
var body := HBoxContainer.new()
body.name = "M50MarketBody"
body.add_theme_constant_override("separation", 8 if size.x < 820.0 else 14)
body.size_flags_vertical = Control.SIZE_EXPAND_FILL
page.add_child(body)
var left := VBoxContainer.new()
left.name = "M50MarketOffersColumn"
left.size_flags_horizontal = Control.SIZE_EXPAND_FILL
left.size_flags_vertical = Control.SIZE_EXPAND_FILL
left.add_theme_constant_override("separation", 8)
body.add_child(left)
var offer_heading := UIFactory.label(_tr("可用招募", "AVAILABLE RECRUITS", data), "label", 1.0, Tokens.COLOR_IVORY)
offer_heading.name = "M50MarketOffersHeading"
var offer_heading_surface := PanelContainer.new()
offer_heading_surface.name = "MarketOffersHeadingSurface"
offer_heading_surface.custom_minimum_size.y = 32
var heading_style := StyleBoxFlat.new()
heading_style.bg_color = Color(0.08, 0.13, 0.15, 0.68)
heading_style.border_color = Color(Tokens.COLOR_GOLD_LIGHT, 0.48)
heading_style.set_border_width_all(1)
heading_style.set_corner_radius_all(8)
offer_heading_surface.add_theme_stylebox_override("panel", heading_style)
offer_heading_surface.add_child(offer_heading)
left.add_child(offer_heading_surface)
var offer_list := HBoxContainer.new()
offer_list.name = "MarketOfferList"
offer_list.add_theme_constant_override("separation", 10)
offer_list.size_flags_vertical = Control.SIZE_EXPAND_FILL
left.add_child(offer_list)
var offers: Dictionary = Dictionary(data.get("offers", {}))
var recruit_offers: Array = Array(offers.get("recruit", []))
var selected_offer_id := String(data.get("selected_offer_id", ""))
var selected_kind := String(data.get("selected_kind", ""))
for index in range(mini(3, recruit_offers.size())):
offer_list.add_child(_m50_recruit_card(index, recruit_offers[index], selected_kind, selected_offer_id, data))
if String(data.get("category", "recruit")) == "equipment":
# Keep the legacy four-socket contract while the first slice focuses on
# recruit cards; equipment presentation migrates after owner review.
while offer_list.get_child_count() < 4:
var compatibility_socket := Control.new()
compatibility_socket.name = "MarketEquipmentSocket%d" % offer_list.get_child_count()
compatibility_socket.visible = false
offer_list.add_child(compatibility_socket)
if recruit_offers.is_empty():
var empty := UIFactory.label(_tr("暂无可用招募", "NO RECRUITS AVAILABLE"), "body", 1.0, Tokens.COLOR_IVORY)
empty.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
offer_list.add_child(empty)
var selected: Dictionary = Dictionary(data.get("selected_offer", {}))
var board := Panel.new()
board.name = "MarketContextSurface"
board.add_theme_stylebox_override("panel", _flat_style(Color(0.15, 0.25, 0.25, 0.94), Color(Tokens.COLOR_GOLD_LIGHT, 0.78), 2, 10))
board.custom_minimum_size = Vector2(260 if size.x < 820.0 else 286, 0)
board.size_flags_horizontal = Control.SIZE_EXPAND
board.size_flags_vertical = Control.SIZE_EXPAND_FILL
body.add_child(board)
var board_margin := MarginContainer.new()
board_margin.name = "M50CommissionBoardContent"
board_margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
var board_side_margin := 18 if size.x < 820.0 else 28
board_margin.add_theme_constant_override("margin_left", board_side_margin)
board_margin.add_theme_constant_override("margin_right", board_side_margin)
board_margin.add_theme_constant_override("margin_top", 24 if size.x < 820.0 else 32)
board_margin.add_theme_constant_override("margin_bottom", 20 if size.x < 820.0 else 28)
board.add_child(board_margin)
var board_content := VBoxContainer.new()
board_content.add_theme_constant_override("separation", 7)
board_margin.add_child(board_content)
var board_title := UIFactory.label(_tr("候选人详情", "CANDIDATE DETAILS"), "heading", 1.0, Tokens.COLOR_IVORY)
board_title.name = "M50CommissionBoardTitle"
board_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
if size.x < 820.0:
board_title.add_theme_font_size_override("font_size", 18)
board_content.add_child(board_title)
var instruction := UIFactory.label(_tr("职业、特质与招募价格", "ROLE, TRAIT AND RECRUITMENT COST"), "micro", 1.0, Color("#ead49b"))
instruction.name = "M50CommissionInstruction"
instruction.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
instruction.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
board_content.add_child(instruction)
var divider := HSeparator.new()
divider.name = "M50CommissionDivider"
board_content.add_child(divider)
var detail_surface := PanelContainer.new()
detail_surface.name = "MarketCandidateFactSurface"
detail_surface.size_flags_vertical = Control.SIZE_EXPAND_FILL
detail_surface.add_theme_stylebox_override("panel", _flat_style(Color(0.94, 0.86, 0.68, 0.94), Color(0.47, 0.31, 0.18, 0.70), 1, 8))
var detail := UIFactory.label("", "body", 1.0, Tokens.COLOR_INK)
detail.name = "M50CommissionDetail"
detail.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
detail.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
detail_surface.add_child(detail)
board_content.add_child(detail_surface)
var review := UIFactory.command_button("MarketReviewRecruit", _tr("查看详情", "REVIEW DETAILS"), "primary", "inspect", "angular", "company", Vector2(0, 52))
review.size_flags_horizontal = Control.SIZE_EXPAND_FILL
review.disabled = selected_offer_id.is_empty() or selected_kind != "recruit"
UIFactory.refresh_command_button(review, review.disabled, false)
review.pressed.connect(func(): emit_intent("recruit_review", {}), CONNECT_DEFERRED)
board_content.add_child(review)
if selected_offer_id.is_empty():
detail.text = _tr("从左侧选择一名候选人。\n这里会显示职业、特质和招募价格。", "Select a candidate on the left.\nProfession, trait and recruitment cost will appear here.")
else:
var p := _offer_presentation(selected)
detail.text = "%s\n%s\n%s" % [String(p.get("title", "")), String(p.get("short_detail", "")), String(p.get("state_text", ""))]
var footer := HBoxContainer.new()
footer.name = "M50MarketFooter"
footer.add_theme_constant_override("separation", 10)
footer.custom_minimum_size.y = 52
page.add_child(footer)
var back := UIFactory.command_button("MarketBackGuild", _tr("返回公会", "BACK TO GUILD"), "utility", "back", "angular", "content", Vector2(150, 48))
back.pressed.connect(func(): emit_intent("back", {}), CONNECT_DEFERRED)
footer.add_child(back)
var footer_hint := UIFactory.label(_tr("准备 → 编队 → 风险 → 出发", "PREPARE → FORM → RISK → DEPART"), "micro", 1.0, Tokens.COLOR_GOLD_LIGHT)
footer_hint.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
footer_hint.size_flags_horizontal = Control.SIZE_EXPAND_FILL
footer.add_child(footer_hint)
var return_command_facts: Dictionary = Dictionary(Dictionary(data.get("commands", {})).get("return_contract", {}))
var return_enabled := bool(return_command_facts.get("enabled", false))
var return_contract := UIFactory.command_button("MarketReturnContract", _tr("返回委托", "RETURN"), "danger", "risk", "angular", "company", Vector2(148, 48))
return_contract.disabled = not return_enabled
UIFactory.refresh_command_button(return_contract, return_contract.disabled, false)
return_contract.set_meta("market_command_role", "contract_return")
return_contract.tooltip_text = _return_contract_tooltip(String(return_command_facts.get("reason", "market_required")), data)
return_contract.pressed.connect(func(): emit_intent("return_contract", {}), CONNECT_DEFERRED)
footer.add_child(return_contract)
# Category commands stay available to keyboard/touch contracts but remain
# secondary to the recruit decision in this first responsive slice.
for category in ["equipment", "battle"]:
var category_button := UIFactory.command_button("MarketCategory%s" % category.capitalize(), "", "secondary", "generic", "flat", "content")
category_button.visible = false
category_button.pressed.connect(func(): emit_intent("category", {"category": category}), CONNECT_DEFERRED)
add_child(category_button)
## M52 design restoration --------------------------------------------------
## The page follows the approved board's physical grammar: a market rail,
## hanging parchment recruit cards, a warm adventurer ledger and matching wood
@@ -823,6 +528,10 @@ func _render_m52_market_restore(data: Dictionary) -> void:
page.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
page.mouse_filter = Control.MOUSE_FILTER_PASS
canvas.add_child(page)
# Slot adapters choose the authored compact/desktop grammar from the
# fitted viewport, never from the fixed root's 1280 design units.
var fitted_width := minf(size.x, size.y * (16.0 / 9.0))
var fitted_layout_size := Vector2(fitted_width, fitted_width * (9.0 / 16.0))
# Header owns the plaque and its local text safety slots. Market only
# positions this module on the shared 16:9 design canvas.
@@ -879,7 +588,7 @@ func _render_m52_market_restore(data: Dictionary) -> void:
var market_category := String(data.get("category", "recruit"))
var selected_offer_id := String(data.get("selected_offer_id", ""))
var selected_kind := String(data.get("selected_kind", ""))
var card_areas: Array[Rect2] = M52MarketSlotAdapter.card_areas(design_canvas.size)
var card_areas: Array[Rect2] = M52MarketSlotAdapter.card_areas(fitted_layout_size)
if market_category == "recruit":
for index in range(mini(3, recruit_offers.size())):
offer_list.add_child(_m52_recruit_card_component(index, recruit_offers[index], card_areas[index], selected_kind, selected_offer_id, data))
@@ -922,7 +631,7 @@ func _render_m52_market_restore(data: Dictionary) -> void:
"action_disabled": not has_selection,
})
ledger.name = "MarketContextSurface"
_market_place(ledger, M52MarketSlotAdapter.ledger_area(design_canvas.size))
_market_place(ledger, M52MarketSlotAdapter.ledger_area(fitted_layout_size))
ledger.z_index = 12
ledger.action_requested.connect(func(): emit_intent("recruit_review", {}), CONNECT_DEFERRED)
page.add_child(ledger)
+5 -1
View File
@@ -128,7 +128,11 @@ func _apply_responsive_layout() -> void:
# The phone-landscape ledger is only ~240–280px wide and ~300–330px tall
# after the 16:9 canvas is fitted. Treat that whole range as compact so the
# 48px action dock is laid out before the shell's bottom edge.
var compact := size.x < 300.0 or size.y < 380.0
# `size` is now authored 1280x720 units. Decide compactness from the
# transformed play-size so a phone-landscape ledger still gets its 48px
# action dock layout.
var play_size := get_global_rect().size
var compact := play_size.x < 300.0 or play_size.y < 380.0
var portrait := get_node_or_null("PortraitModule") as Control
var identity := get_node_or_null("IdentityModule") as Control
var profession := get_node_or_null("ProfessionModule") as Control
+4 -1
View File
@@ -25,7 +25,10 @@ static func ledger_area(canvas_size: Vector2) -> Rect2:
# At 760x360 and 844x390 the ledger gets enough height for a 48px
# action dock while remaining clear of the card row horizontally.
# Start below the resource rail and end above the footer dock.
return Rect2(0.585, 0.150, 0.375, 0.675)
# Keep the complete ledger (including its minimum-height action dock)
# above the shared footer. The fixed design root makes the local ledger
# taller than its play-size, so reserve a clear lower gap here.
return Rect2(0.585, 0.080, 0.375, 0.700)
return Rect2(0.655, 0.205, 0.310, 0.635)
static func is_compact(canvas_size: Vector2) -> bool: