Improve camp talk and merchant previews
This commit is contained in:
@@ -58,6 +58,8 @@ const DIALOGUE_PANEL_SIZE := Vector2(1040, 210)
|
|||||||
const DIALOGUE_PORTRAIT_SIZE := Vector2(164, 186)
|
const DIALOGUE_PORTRAIT_SIZE := Vector2(164, 186)
|
||||||
const DIALOGUE_COLUMN_SIZE := Vector2(820, 186)
|
const DIALOGUE_COLUMN_SIZE := Vector2(820, 186)
|
||||||
const DIALOGUE_TEXT_SIZE := Vector2(800, 104)
|
const DIALOGUE_TEXT_SIZE := Vector2(800, 104)
|
||||||
|
const CAMP_TALK_PORTRAIT_SIZE := Vector2(58, 58)
|
||||||
|
const SHOP_MERCHANT_BADGE_SIZE := Vector2(50, 50)
|
||||||
const HUD_UNIT_PORTRAIT_SIZE := Vector2(92, 104)
|
const HUD_UNIT_PORTRAIT_SIZE := Vector2(92, 104)
|
||||||
const HUD_UNIT_PORTRAIT_STACK_SIZE := Vector2(88, 100)
|
const HUD_UNIT_PORTRAIT_STACK_SIZE := Vector2(88, 100)
|
||||||
const POST_MOVE_MENU_SIZE := Vector2(228, 120)
|
const POST_MOVE_MENU_SIZE := Vector2(228, 120)
|
||||||
@@ -4230,11 +4232,66 @@ func _rebuild_talk_menu() -> void:
|
|||||||
talk_list.add_child(empty_label)
|
talk_list.add_child(empty_label)
|
||||||
return
|
return
|
||||||
for conversation in conversations:
|
for conversation in conversations:
|
||||||
var conversation_id := str(conversation.get("id", ""))
|
_add_camp_conversation_row(conversation)
|
||||||
var button := Button.new()
|
|
||||||
button.text = _format_camp_conversation_button_text(conversation)
|
|
||||||
button.pressed.connect(_on_camp_conversation_pressed.bind(conversation_id))
|
func _add_camp_conversation_row(conversation: Dictionary) -> void:
|
||||||
talk_list.add_child(button)
|
var row := HBoxContainer.new()
|
||||||
|
row.custom_minimum_size = Vector2(580, 64)
|
||||||
|
row.add_theme_constant_override("separation", 8)
|
||||||
|
talk_list.add_child(row)
|
||||||
|
|
||||||
|
row.add_child(_make_camp_conversation_portrait(conversation))
|
||||||
|
|
||||||
|
var column := VBoxContainer.new()
|
||||||
|
column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
column.add_theme_constant_override("separation", 3)
|
||||||
|
row.add_child(column)
|
||||||
|
|
||||||
|
var conversation_id := str(conversation.get("id", ""))
|
||||||
|
var button := Button.new()
|
||||||
|
button.text = _format_camp_conversation_row_title(conversation)
|
||||||
|
button.custom_minimum_size = Vector2(500, 32)
|
||||||
|
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
button.pressed.connect(_on_camp_conversation_pressed.bind(conversation_id))
|
||||||
|
column.add_child(button)
|
||||||
|
|
||||||
|
var preview_label := Label.new()
|
||||||
|
preview_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
|
preview_label.custom_minimum_size = Vector2(500, 24)
|
||||||
|
preview_label.text = _format_camp_conversation_preview_text(conversation)
|
||||||
|
column.add_child(preview_label)
|
||||||
|
|
||||||
|
|
||||||
|
func _make_camp_conversation_portrait(conversation: Dictionary) -> PanelContainer:
|
||||||
|
var portrait_panel := PanelContainer.new()
|
||||||
|
portrait_panel.custom_minimum_size = CAMP_TALK_PORTRAIT_SIZE
|
||||||
|
|
||||||
|
var stack := Control.new()
|
||||||
|
stack.custom_minimum_size = CAMP_TALK_PORTRAIT_SIZE
|
||||||
|
portrait_panel.add_child(stack)
|
||||||
|
|
||||||
|
var texture_rect := TextureRect.new()
|
||||||
|
texture_rect.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
texture_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||||
|
texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||||
|
stack.add_child(texture_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", 18)
|
||||||
|
stack.add_child(initials_label)
|
||||||
|
|
||||||
|
var speaker := _camp_conversation_preview_speaker(conversation)
|
||||||
|
var texture := _load_art_texture(_camp_conversation_preview_portrait_path(conversation))
|
||||||
|
var has_texture := texture != null
|
||||||
|
texture_rect.texture = texture
|
||||||
|
texture_rect.visible = has_texture
|
||||||
|
initials_label.text = _dialogue_portrait_initials(speaker)
|
||||||
|
initials_label.visible = not has_texture
|
||||||
|
return portrait_panel
|
||||||
|
|
||||||
|
|
||||||
func _format_talk_status_text(conversations: Array) -> String:
|
func _format_talk_status_text(conversations: Array) -> String:
|
||||||
@@ -4307,6 +4364,83 @@ func _format_camp_conversation_button_text(conversation: Dictionary) -> String:
|
|||||||
return _join_strings(parts, " - ")
|
return _join_strings(parts, " - ")
|
||||||
|
|
||||||
|
|
||||||
|
func _format_camp_conversation_row_title(conversation: Dictionary) -> String:
|
||||||
|
var label := str(conversation.get("label", "Camp Talk"))
|
||||||
|
var speaker := _camp_conversation_preview_speaker(conversation)
|
||||||
|
var group := str(conversation.get("group", "topic")).capitalize()
|
||||||
|
var parts := [group, label]
|
||||||
|
if not speaker.is_empty():
|
||||||
|
parts.append(speaker)
|
||||||
|
var effects_text := _format_camp_conversation_effects_text(conversation)
|
||||||
|
if not effects_text.is_empty():
|
||||||
|
parts.append(effects_text)
|
||||||
|
return _join_strings(parts, " - ")
|
||||||
|
|
||||||
|
|
||||||
|
func _format_camp_conversation_preview_text(conversation: Dictionary) -> String:
|
||||||
|
var parts := []
|
||||||
|
var summary := str(conversation.get("summary", "")).strip_edges()
|
||||||
|
if not summary.is_empty():
|
||||||
|
parts.append(summary)
|
||||||
|
var first_text := _camp_conversation_preview_line(conversation)
|
||||||
|
if not first_text.is_empty():
|
||||||
|
parts.append(_short_preview_text(first_text, 96))
|
||||||
|
if parts.is_empty():
|
||||||
|
return "No preview text"
|
||||||
|
return _join_strings(parts, " | ")
|
||||||
|
|
||||||
|
|
||||||
|
func _camp_conversation_preview_speaker(conversation: Dictionary) -> String:
|
||||||
|
var speaker := str(conversation.get("speaker", "")).strip_edges()
|
||||||
|
if not speaker.is_empty():
|
||||||
|
return speaker
|
||||||
|
for line in conversation.get("lines", []):
|
||||||
|
if typeof(line) != TYPE_DICTIONARY:
|
||||||
|
continue
|
||||||
|
speaker = str(line.get("speaker", "")).strip_edges()
|
||||||
|
if not speaker.is_empty():
|
||||||
|
return speaker
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
func _camp_conversation_preview_line(conversation: Dictionary) -> String:
|
||||||
|
for line in conversation.get("lines", []):
|
||||||
|
if typeof(line) != TYPE_DICTIONARY:
|
||||||
|
continue
|
||||||
|
var text := str(line.get("text", "")).strip_edges()
|
||||||
|
if not text.is_empty():
|
||||||
|
return text
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
func _camp_conversation_preview_portrait_path(conversation: Dictionary) -> String:
|
||||||
|
var portrait := str(conversation.get("portrait", "")).strip_edges()
|
||||||
|
if not portrait.is_empty():
|
||||||
|
return portrait
|
||||||
|
for line in conversation.get("lines", []):
|
||||||
|
if typeof(line) != TYPE_DICTIONARY:
|
||||||
|
continue
|
||||||
|
portrait = str(line.get("portrait", "")).strip_edges()
|
||||||
|
if not portrait.is_empty():
|
||||||
|
return portrait
|
||||||
|
var officer_id := str(conversation.get("officer_id", "")).strip_edges()
|
||||||
|
if not officer_id.is_empty():
|
||||||
|
portrait = state.data_catalog.get_portrait_for_officer(officer_id)
|
||||||
|
if not portrait.is_empty():
|
||||||
|
return portrait
|
||||||
|
var speaker := _camp_conversation_preview_speaker(conversation)
|
||||||
|
if not speaker.is_empty():
|
||||||
|
return state.data_catalog.get_portrait_for_speaker(speaker)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
func _short_preview_text(text: String, max_length: int) -> String:
|
||||||
|
var normalized := text.strip_edges()
|
||||||
|
if max_length <= 3 or normalized.length() <= max_length:
|
||||||
|
return normalized
|
||||||
|
return "%s..." % normalized.substr(0, max_length - 3).strip_edges()
|
||||||
|
|
||||||
|
|
||||||
func _on_camp_conversation_pressed(conversation_id: String) -> void:
|
func _on_camp_conversation_pressed(conversation_id: String) -> void:
|
||||||
var conversation := _camp_conversation_by_id(conversation_id)
|
var conversation := _camp_conversation_by_id(conversation_id)
|
||||||
if conversation.is_empty():
|
if conversation.is_empty():
|
||||||
@@ -4978,23 +5112,51 @@ func _clear_shop_list() -> void:
|
|||||||
|
|
||||||
func _add_shop_merchant_notice() -> void:
|
func _add_shop_merchant_notice() -> void:
|
||||||
var merchant := state.get_shop_merchant()
|
var merchant := state.get_shop_merchant()
|
||||||
if merchant.is_empty():
|
var notice_text := _format_shop_merchant_notice_text(merchant)
|
||||||
|
if notice_text.is_empty():
|
||||||
return
|
return
|
||||||
|
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_initials_badge(str(merchant.get("name", "Merchant")), SHOP_MERCHANT_BADGE_SIZE, 18))
|
||||||
|
|
||||||
|
var merchant_label := Label.new()
|
||||||
|
merchant_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
|
merchant_label.custom_minimum_size = Vector2(500, 0)
|
||||||
|
merchant_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
merchant_label.text = notice_text
|
||||||
|
row.add_child(merchant_label)
|
||||||
|
|
||||||
|
|
||||||
|
func _format_shop_merchant_notice_text(merchant: Dictionary) -> String:
|
||||||
|
if merchant.is_empty():
|
||||||
|
return ""
|
||||||
var lines := []
|
var lines := []
|
||||||
for line in merchant.get("lines", []):
|
for line in merchant.get("lines", []):
|
||||||
var text := str(line).strip_edges()
|
var text := str(line).strip_edges()
|
||||||
if not text.is_empty():
|
if not text.is_empty():
|
||||||
lines.append(text)
|
lines.append(text)
|
||||||
if lines.is_empty():
|
if lines.is_empty():
|
||||||
return
|
return ""
|
||||||
var merchant_label := Label.new()
|
return "%s: %s" % [
|
||||||
merchant_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
||||||
merchant_label.custom_minimum_size = Vector2(560, 0)
|
|
||||||
merchant_label.text = "%s: %s" % [
|
|
||||||
str(merchant.get("name", "Merchant")),
|
str(merchant.get("name", "Merchant")),
|
||||||
_join_strings(lines, " ")
|
_join_strings(lines, " ")
|
||||||
]
|
]
|
||||||
shop_list.add_child(merchant_label)
|
|
||||||
|
|
||||||
|
func _make_initials_badge(label_text: String, badge_size: Vector2, font_size: int) -> PanelContainer:
|
||||||
|
var panel := PanelContainer.new()
|
||||||
|
panel.custom_minimum_size = badge_size
|
||||||
|
var label := Label.new()
|
||||||
|
label.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||||
|
label.add_theme_font_size_override("font_size", font_size)
|
||||||
|
label.text = _dialogue_portrait_initials(label_text)
|
||||||
|
panel.add_child(label)
|
||||||
|
return panel
|
||||||
|
|
||||||
|
|
||||||
func _format_shop_item_button_text(item_id: String, item: Dictionary, price: int, owned: int, stock_limit: int = -1, remaining: int = -1) -> String:
|
func _format_shop_item_button_text(item_id: String, item: Dictionary, price: int, owned: int, stock_limit: int = -1, remaining: int = -1) -> String:
|
||||||
|
|||||||
@@ -179,11 +179,22 @@ func _check_scene_texture_loading(failures: Array[String]) -> void:
|
|||||||
failures.append("battle scene should find Cao Cao camp conversation by id")
|
failures.append("battle scene should find Cao Cao camp conversation by id")
|
||||||
elif not scene._format_camp_conversation_button_text(strategy).contains("Cao Cao"):
|
elif not scene._format_camp_conversation_button_text(strategy).contains("Cao Cao"):
|
||||||
failures.append("camp conversation button text should include label/speaker")
|
failures.append("camp conversation button text should include label/speaker")
|
||||||
|
elif scene._camp_conversation_preview_portrait_path(strategy).is_empty():
|
||||||
|
failures.append("camp conversation preview should resolve Cao Cao portrait")
|
||||||
|
elif scene._load_art_texture(scene._camp_conversation_preview_portrait_path(strategy)) == null:
|
||||||
|
failures.append("camp conversation preview portrait should load")
|
||||||
|
elif not scene._format_camp_conversation_row_title(strategy).contains("Officer"):
|
||||||
|
failures.append("camp conversation row title should include group")
|
||||||
|
elif not scene._format_camp_conversation_preview_text(strategy).contains("Yingchuan"):
|
||||||
|
failures.append("camp conversation preview should include first dialogue text")
|
||||||
var cache := scene._camp_conversation_by_id("northern_woods_cache")
|
var cache := scene._camp_conversation_by_id("northern_woods_cache")
|
||||||
if cache.is_empty():
|
if cache.is_empty():
|
||||||
failures.append("battle scene should find cache camp conversation by id")
|
failures.append("battle scene should find cache camp conversation by id")
|
||||||
elif not scene._format_camp_conversation_button_text(cache).contains("Supply Bean"):
|
elif not scene._format_camp_conversation_button_text(cache).contains("Supply Bean"):
|
||||||
failures.append("camp conversation button text should preview supply effect")
|
failures.append("camp conversation button text should preview supply effect")
|
||||||
|
var merchant_notice := scene._format_shop_merchant_notice_text(scene.state.get_shop_merchant())
|
||||||
|
if not merchant_notice.contains("Camp Merchant") or not merchant_notice.contains("Roadside stores"):
|
||||||
|
failures.append("shop merchant notice should include name and flavor lines")
|
||||||
scene.free()
|
scene.free()
|
||||||
|
|
||||||
_check_scenario_background_texture_loading(failures)
|
_check_scenario_background_texture_loading(failures)
|
||||||
|
|||||||
Reference in New Issue
Block a user