Improve shop item previews

This commit is contained in:
2026-06-18 22:56:49 +09:00
parent 65d6dff0f0
commit 2183580d81
2 changed files with 131 additions and 6 deletions

View File

@@ -60,6 +60,7 @@ const DIALOGUE_COLUMN_SIZE := Vector2(820, 186)
const DIALOGUE_TEXT_SIZE := Vector2(800, 104)
const CAMP_TALK_PORTRAIT_SIZE := Vector2(58, 58)
const SHOP_MERCHANT_BADGE_SIZE := Vector2(50, 50)
const SHOP_ITEM_ICON_SIZE := Vector2(48, 48)
const HUD_UNIT_PORTRAIT_SIZE := Vector2(92, 104)
const HUD_UNIT_PORTRAIT_STACK_SIZE := Vector2(88, 100)
const POST_MOVE_MENU_SIZE := Vector2(228, 120)
@@ -5076,11 +5077,14 @@ func _rebuild_shop_buy_list() -> void:
var purchased := campaign_state.get_shop_purchase_count(active_scenario_id, normalized_id)
var remaining := stock_limit - purchased if stock_limit >= 0 else -1
var buy_button := Button.new()
buy_button.text = _format_shop_item_button_text(normalized_id, item, price, owned, stock_limit, remaining)
_apply_item_button_icon(buy_button, item)
var detail_text := _format_shop_buy_item_detail_text(normalized_id, item, price, owned, stock_limit, remaining)
buy_button.text = _format_shop_buy_action_text(normalized_id, item, price)
buy_button.custom_minimum_size = Vector2(500, 32)
buy_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
buy_button.tooltip_text = _format_shop_item_tooltip_text(_format_shop_item_button_text(normalized_id, item, price, owned, stock_limit, remaining), detail_text)
buy_button.disabled = campaign_state.gold < price or (stock_limit >= 0 and remaining <= 0)
buy_button.pressed.connect(_on_shop_item_pressed.bind(normalized_id))
shop_list.add_child(buy_button)
_add_shop_item_row(normalized_id, item, buy_button, detail_text)
func _rebuild_shop_sell_list() -> void:
@@ -5098,10 +5102,13 @@ func _rebuild_shop_sell_list() -> void:
var sale_price := _sale_price_for_item(item)
var owned := int(inventory.get(normalized_id, 0))
var sell_button := Button.new()
sell_button.text = _format_shop_sell_item_button_text(normalized_id, item, sale_price, owned)
_apply_item_button_icon(sell_button, item)
var detail_text := _format_shop_sell_item_detail_text(normalized_id, item, sale_price, owned)
sell_button.text = _format_shop_sell_action_text(normalized_id, item, sale_price)
sell_button.custom_minimum_size = Vector2(500, 32)
sell_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
sell_button.tooltip_text = _format_shop_item_tooltip_text(_format_shop_sell_item_button_text(normalized_id, item, sale_price, owned), detail_text)
sell_button.pressed.connect(_on_shop_sell_item_pressed.bind(normalized_id))
shop_list.add_child(sell_button)
_add_shop_item_row(normalized_id, item, sell_button, detail_text)
func _clear_shop_list() -> void:
@@ -5159,6 +5166,101 @@ func _make_initials_badge(label_text: String, badge_size: Vector2, font_size: in
return panel
func _add_shop_item_row(item_id: String, item: Dictionary, action_button: Button, detail_text: String) -> void:
var row := HBoxContainer.new()
row.custom_minimum_size = Vector2(580, 58)
row.add_theme_constant_override("separation", 8)
shop_list.add_child(row)
row.add_child(_make_shop_item_icon_panel(item_id, item))
var column := VBoxContainer.new()
column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
column.add_theme_constant_override("separation", 3)
row.add_child(column)
column.add_child(action_button)
var detail_label := Label.new()
detail_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
detail_label.custom_minimum_size = Vector2(500, 20)
detail_label.text = detail_text
column.add_child(detail_label)
func _make_shop_item_icon_panel(item_id: String, item: Dictionary) -> PanelContainer:
var icon_panel := PanelContainer.new()
icon_panel.custom_minimum_size = SHOP_ITEM_ICON_SIZE
var stack := Control.new()
stack.custom_minimum_size = SHOP_ITEM_ICON_SIZE
icon_panel.add_child(stack)
var icon_rect := TextureRect.new()
icon_rect.set_anchors_preset(Control.PRESET_FULL_RECT)
icon_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
stack.add_child(icon_rect)
var initials_label := Label.new()
initials_label.set_anchors_preset(Control.PRESET_FULL_RECT)
initials_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
initials_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
initials_label.add_theme_font_size_override("font_size", 16)
stack.add_child(initials_label)
var texture := _load_art_texture(str(item.get("icon", "")))
var has_texture := texture != null
icon_rect.texture = texture
icon_rect.visible = has_texture
initials_label.text = _dialogue_portrait_initials(_item_display_name(item_id))
initials_label.visible = not has_texture
return icon_panel
func _format_shop_buy_action_text(item_id: String, _item: Dictionary, price: int) -> String:
return "Buy %s %dG" % [_item_display_name(item_id), price]
func _format_shop_sell_action_text(item_id: String, _item: Dictionary, sale_price: int) -> String:
return "Sell %s %dG" % [_item_display_name(item_id), sale_price]
func _format_shop_buy_item_detail_text(_item_id: String, item: Dictionary, price: int, owned: int, stock_limit: int = -1, remaining: int = -1) -> String:
var parts := _shop_item_common_detail_parts(item, owned)
if stock_limit >= 0:
parts.append("Stock %d/%d" % [max(0, remaining), stock_limit])
if stock_limit >= 0 and remaining <= 0:
parts.append("Sold out")
elif campaign_state.gold < price:
parts.append("Need %dG" % (price - campaign_state.gold))
return _join_strings(parts, " | ")
func _format_shop_sell_item_detail_text(_item_id: String, item: Dictionary, _sale_price: int, owned: int) -> String:
var parts := _shop_item_common_detail_parts(item, owned)
parts.append("Half-price sale")
return _join_strings(parts, " | ")
func _shop_item_common_detail_parts(item: Dictionary, owned: int) -> Array:
var parts := []
var effect_text := _format_shop_item_effect_text(item)
if not effect_text.is_empty():
parts.append(effect_text)
var kind := str(item.get("kind", "item")).capitalize()
if not kind.is_empty():
parts.append(kind)
parts.append("Owned x%d" % owned)
return parts
func _format_shop_item_tooltip_text(primary_text: String, detail_text: String) -> String:
if detail_text.is_empty():
return primary_text
return "%s\n%s" % [primary_text, detail_text]
func _format_shop_item_button_text(item_id: String, item: Dictionary, price: int, owned: int, stock_limit: int = -1, remaining: int = -1) -> String:
var unavailable := ""
if stock_limit >= 0 and remaining <= 0: