Add shop sell-back

This commit is contained in:
2026-06-18 03:17:25 +09:00
parent 618a780796
commit 6bba22a562
6 changed files with 148 additions and 8 deletions

View File

@@ -59,8 +59,11 @@ var briefing_panel: PanelContainer
var briefing_label: Label
var shop_button: Button
var shop_menu: VBoxContainer
var shop_buy_button: Button
var shop_sell_button: Button
var shop_list: VBoxContainer
var shop_status_label: Label
var shop_sell_mode := false
var armory_button: Button
var armory_menu: VBoxContainer
var armory_list: VBoxContainer
@@ -342,6 +345,20 @@ func _create_hud() -> void:
shop_status_label.custom_minimum_size = Vector2(600, 22)
shop_menu.add_child(shop_status_label)
var shop_mode_row := HBoxContainer.new()
shop_mode_row.add_theme_constant_override("separation", 8)
shop_menu.add_child(shop_mode_row)
shop_buy_button = Button.new()
shop_buy_button.text = "Buy"
shop_buy_button.pressed.connect(_on_shop_buy_mode_pressed)
shop_mode_row.add_child(shop_buy_button)
shop_sell_button = Button.new()
shop_sell_button.text = "Sell"
shop_sell_button.pressed.connect(_on_shop_sell_mode_pressed)
shop_mode_row.add_child(shop_sell_button)
var shop_scroll := ScrollContainer.new()
shop_scroll.custom_minimum_size = Vector2(600, 190)
shop_menu.add_child(shop_scroll)
@@ -573,7 +590,7 @@ func _play_log_sfx(message: String) -> void:
_play_sfx("fire_skill")
elif message.contains(" moved to ") or message.contains(" advances to ") or message.contains(" takes formation at ") or message.contains(" arrives at "):
_play_sfx("footstep")
elif message.contains(" uses ") or message.contains(" equips ") or message.begins_with("Bought "):
elif message.contains(" uses ") or message.contains(" equips ") or message.begins_with("Bought ") or message.begins_with("Sold "):
_play_sfx("item_pickup")
elif message.contains(" reaches Lv.") or message.contains(" promotes to "):
_play_sfx("menu_confirm")
@@ -1350,6 +1367,7 @@ func _on_restart_pressed() -> void:
battle_result_applied = false
battle_result_summary.clear()
post_battle_dialogue_started = false
shop_sell_mode = false
selected_skill_id = ""
selected_item_id = ""
armory_unit_id = ""
@@ -1438,7 +1456,7 @@ func _show_briefing() -> void:
_hide_formation_menu()
var prep_locked := _is_prebattle_prep_locked()
if shop_button != null:
shop_button.disabled = prep_locked or state.get_shop_item_ids().is_empty()
shop_button.disabled = prep_locked or (state.get_shop_item_ids().is_empty() and _get_sellable_item_ids().is_empty())
if armory_button != null:
armory_button.disabled = prep_locked or state.get_controllable_player_units().is_empty()
if roster_button != null:
@@ -1473,6 +1491,7 @@ func _on_shop_pressed() -> void:
if shop_menu != null and shop_menu.visible:
_hide_shop_menu()
else:
shop_sell_mode = state.get_shop_item_ids().is_empty() and not _get_sellable_item_ids().is_empty()
_hide_armory_menu()
_hide_roster_menu()
_hide_formation_menu()
@@ -1817,11 +1836,24 @@ func _rebuild_shop_menu() -> void:
return
_clear_shop_list()
if shop_status_label != null:
shop_status_label.text = "Gold %d | %s" % [
var mode_text := "Sell" if shop_sell_mode else "Buy"
shop_status_label.text = "%s | Gold %d | %s" % [
mode_text,
campaign_state.gold,
_format_inventory_status_text()
]
if shop_buy_button != null:
shop_buy_button.disabled = not shop_sell_mode
if shop_sell_button != null:
shop_sell_button.disabled = shop_sell_mode or _get_sellable_item_ids().is_empty()
if shop_sell_mode:
_rebuild_shop_sell_list()
else:
_rebuild_shop_buy_list()
func _rebuild_shop_buy_list() -> void:
var item_ids := state.get_shop_item_ids()
if item_ids.is_empty():
var empty_label := Label.new()
@@ -1842,6 +1874,26 @@ func _rebuild_shop_menu() -> void:
shop_list.add_child(buy_button)
func _rebuild_shop_sell_list() -> void:
var item_ids := _get_sellable_item_ids()
if item_ids.is_empty():
var empty_label := Label.new()
empty_label.text = "No sellable stock"
shop_list.add_child(empty_label)
return
var inventory := campaign_state.get_inventory_snapshot()
for item_id in item_ids:
var normalized_id := str(item_id)
var item := state.get_item_def(normalized_id)
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)
sell_button.pressed.connect(_on_shop_sell_item_pressed.bind(normalized_id))
shop_list.add_child(sell_button)
func _clear_shop_list() -> void:
for child in shop_list.get_children():
shop_list.remove_child(child)
@@ -1861,6 +1913,15 @@ func _format_shop_item_button_text(item_id: String, item: Dictionary, price: int
]
func _format_shop_sell_item_button_text(item_id: String, item: Dictionary, sale_price: int, owned: int) -> String:
return "%s Sell %dG %s Owned x%d" % [
_item_display_name(item_id),
sale_price,
_format_shop_item_effect_text(item),
owned
]
func _format_shop_item_effect_text(item: Dictionary) -> String:
var kind := str(item.get("kind", "item"))
if kind == "consumable":
@@ -1897,6 +1958,66 @@ func _on_shop_item_pressed(item_id: String) -> void:
_update_hud()
func _on_shop_sell_item_pressed(item_id: String) -> void:
if battle_started or _is_prebattle_prep_locked():
return
var item := state.get_item_def(item_id)
var sale_price := _sale_price_for_item(item)
var item_name := _item_display_name(item_id)
if item.is_empty() or sale_price <= 0:
return
if campaign_state.try_sell_item(item_id, sale_price):
state.set_inventory_snapshot(campaign_state.get_inventory_snapshot())
_on_log_added("Sold %s for %d gold." % [item_name, sale_price])
else:
_play_ui_cancel()
_on_log_added("Could not sell %s." % item_name)
if _get_sellable_item_ids().is_empty():
shop_sell_mode = false
_rebuild_shop_menu()
_update_hud()
func _on_shop_buy_mode_pressed() -> void:
if not shop_sell_mode:
return
_play_ui_click()
shop_sell_mode = false
_rebuild_shop_menu()
_update_hud()
func _on_shop_sell_mode_pressed() -> void:
if shop_sell_mode or _get_sellable_item_ids().is_empty():
return
_play_ui_click()
shop_sell_mode = true
_rebuild_shop_menu()
_update_hud()
func _get_sellable_item_ids() -> Array:
var item_ids := []
var inventory := campaign_state.get_inventory_snapshot()
for item_id in inventory.keys():
var normalized_id := str(item_id)
if normalized_id.is_empty() or int(inventory.get(normalized_id, 0)) <= 0:
continue
var item := state.get_item_def(normalized_id)
if _sale_price_for_item(item) <= 0:
continue
item_ids.append(normalized_id)
item_ids.sort()
return item_ids
func _sale_price_for_item(item: Dictionary) -> int:
var price := int(item.get("price", 0))
if price <= 0:
return 0
return max(1, int(floor(float(price) * 0.5)))
func _is_input_locked() -> bool:
return campaign_complete_screen or _is_dialogue_visible() or not battle_started or state.battle_status != BattleState.STATUS_ACTIVE