921 lines
41 KiB
GDScript
921 lines
41 KiB
GDScript
class_name PetPanel
|
||
extends PanelModule
|
||
|
||
const EgretMovieClipCacheScript := preload("res://scripts/resources/egret_movie_clip_cache.gd")
|
||
const NumberFormatterScript := preload("res://scripts/core/number_formatter.gd")
|
||
const UiMotionScript := preload("res://scripts/ui/common/ui_motion.gd")
|
||
|
||
const BATTLE_PET_SCENE_ATLAS_PATH := "res://assets/egret/assets/swf/dog1.png"
|
||
const BATTLE_PET_BG_DIR := "res://assets/egret/assets/game/battlePet"
|
||
const PET_PATROL_START := Vector2(269.34, 490.0)
|
||
const PET_PATROL_END := Vector2(599.34, 625.0)
|
||
const PET_SCENE_POSITION := Vector2(182.0, 374.0)
|
||
const PET_SCENE_SIZE := Vector2(132.0, 112.0)
|
||
const PET_SCENE_ORIGIN := Vector2(72.0, 86.0)
|
||
const PET_SCENE_SCALE := 0.68
|
||
const PET_PATROL_SCENE_SCALE := 0.8
|
||
const DEFAULT_PET_HP := 100
|
||
const PET_TYPE_TEXT := ["无类型", "近身攻击", "远程攻击"]
|
||
|
||
# 宠物面板:
|
||
# - 负责读取宠物列表、场景宠物牌展示、出战/喂养等宠物相关入口。
|
||
# - 场景上的宠物展示节点由外部注入,本面板只重绘其子节点,避免重复创建全局层。
|
||
# - 宠物排序优先出战状态,再按 id,保证主场景默认展示当前出战宠物。
|
||
var _pet_api
|
||
var _shop_api
|
||
var _dog_food_item_ids: Array = []
|
||
var _movie_clip_cache = EgretMovieClipCacheScript.new()
|
||
var _rows: Array[Dictionary] = []
|
||
var _selected_id := 0
|
||
var _tab_index := 0
|
||
var _display: Control
|
||
var _list_container: Control
|
||
var _panel: Control
|
||
var _food_popup: Control
|
||
var _selected_food_item_id := 0
|
||
var _feeding_pet_id := 0
|
||
var _display_sprite: TextureRect
|
||
var _display_frames: Array[Dictionary] = []
|
||
var _display_frame_index := 0
|
||
var _display_frame_rate := 12
|
||
var _display_frame_timer_node: Timer
|
||
var _display_frame_origin := PET_SCENE_ORIGIN
|
||
var _display_frame_scale := PET_SCENE_SCALE
|
||
var _display_fallback_icon_id := 0
|
||
|
||
|
||
func setup(options: Dictionary) -> void:
|
||
super.setup(options)
|
||
_pet_api = options.get("pet_api")
|
||
_shop_api = options.get("shop_api")
|
||
_dog_food_item_ids = options.get("dog_food_item_ids", _dog_food_item_ids)
|
||
|
||
|
||
func set_display(display: Control) -> void:
|
||
_display = display
|
||
|
||
|
||
func refresh_display() -> void:
|
||
await _refresh_pet_rows()
|
||
render_display()
|
||
|
||
|
||
func _refresh_pet_rows() -> void:
|
||
var token := _token()
|
||
if token.is_empty() or _pet_api == null:
|
||
return
|
||
var result: Dictionary = await _pet_api.fetch_list(token)
|
||
if not _is_live_node(_display):
|
||
return
|
||
if not result.get("ok", false):
|
||
push_warning("pet/get-list failed: %s" % result.get("game_status", result.get("status", 0)))
|
||
return
|
||
var data = _response_data(result, {})
|
||
_rows.clear()
|
||
if data is Dictionary:
|
||
for row in data.get("list", []):
|
||
if row is Dictionary:
|
||
_rows.append(_normalize_pet_row(row))
|
||
_rows.sort_custom(func(left: Dictionary, right: Dictionary) -> bool:
|
||
var left_war := int(left.get("is_enter_war", 0))
|
||
var right_war := int(right.get("is_enter_war", 0))
|
||
if left_war == right_war:
|
||
return int(left.get("id", 0)) < int(right.get("id", 0))
|
||
return left_war > right_war
|
||
)
|
||
if not _rows.is_empty():
|
||
_selected_id = int(_rows[0].get("id", 0))
|
||
|
||
|
||
func render_display() -> void:
|
||
var display := _display
|
||
if not _is_live_node(display):
|
||
return
|
||
_stop_display_animation()
|
||
_clear_children(display)
|
||
if _rows.is_empty():
|
||
display.visible = false
|
||
return
|
||
display.visible = true
|
||
var pet: Dictionary = _rows[0]
|
||
var config: Dictionary = _pet_config(pet)
|
||
var icon_id: int = int(config.get("graphical_id", pet.get("graphical_id", 11001)))
|
||
var can_patrol := _pet_can_patrol(pet)
|
||
if can_patrol:
|
||
display.position = PET_PATROL_START
|
||
display.size = Vector2(140.0, 140.0)
|
||
display.pivot_offset = Vector2.ZERO
|
||
display.scale = Vector2.ONE
|
||
else:
|
||
display.position = PET_SCENE_POSITION
|
||
display.size = PET_SCENE_SIZE
|
||
display.pivot_offset = display.size * 0.5
|
||
display.scale = Vector2.ONE
|
||
_ui.add_texture(_display, "Shadow", _texture_provider.common("top_cir"), Vector2(28.0, 84.0), Vector2(78.0, 28.0))
|
||
_render_scene_pet(pet, icon_id)
|
||
_render_scene_speech(pet)
|
||
if can_patrol:
|
||
_start_battle_pet_patrol()
|
||
else:
|
||
_start_scene_motion(false)
|
||
|
||
|
||
func open() -> void:
|
||
_close_modal(false)
|
||
await _refresh_store_house()
|
||
if _rows.is_empty():
|
||
await _refresh_pet_rows()
|
||
_panel = _ui.create_panel_shell("PetPanel", Vector2(640.0, 900.0), _texture_provider.frame(_texture_provider.atlas_path("battle_pet"), "battle_pet_canzhan_26_png"), _texture_provider.frame(_texture_provider.atlas_path("battle_pet"), "battle_pet_canzhan12_png"))
|
||
var icon_node := _panel.get_node_or_null("PanelIcon") as TextureRect
|
||
if icon_node != null:
|
||
icon_node.visible = false
|
||
_ui.add_content_panel_background(_panel, "PetPageBg", Vector2(50.0, 167.0), Vector2(540.0, 714.0))
|
||
_tab_index = 0
|
||
_ui.create_tab_bar(_panel, "PetTabs", ["战宠", "宠物列表", "宠物商店"], Vector2(89.0, 118.0), _tab_index, Callable(self, "_on_tab_pressed"), 40.0)
|
||
_list_container = Control.new()
|
||
_list_container.name = "PetList"
|
||
_list_container.position = Vector2.ZERO
|
||
_list_container.size = _panel_screen_size
|
||
_list_container.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
_panel.add_child(_list_container)
|
||
_open_modal(_panel)
|
||
render()
|
||
|
||
|
||
func render() -> void:
|
||
var list_container := _list_container
|
||
if not _clear_children_if_live(list_container):
|
||
return
|
||
match _tab_index:
|
||
0:
|
||
_render_current_pet_page(list_container)
|
||
1:
|
||
_render_pet_list_page(list_container)
|
||
2:
|
||
_render_pet_shop_page(list_container)
|
||
|
||
|
||
func _render_current_pet_page(list_container: Control) -> void:
|
||
if _rows.is_empty():
|
||
_ui.add_panel_label(list_container, "Empty", "您没有宠物", Vector2(120.0, 430.0), Vector2(400.0, 60.0), 28, Color.WHITE, 2, Color.html("#7d1c1c"))
|
||
return
|
||
var pet: Dictionary = _selected_pet()
|
||
if pet.is_empty():
|
||
pet = _rows[0]
|
||
_selected_id = int(pet.get("id", 0))
|
||
var config: Dictionary = _pet_config(pet)
|
||
var icon_id: int = int(config.get("graphical_id", pet.get("graphical_id", 11001)))
|
||
_render_base_panel(pet, config, icon_id)
|
||
_render_pet_radio_list()
|
||
|
||
|
||
func _on_tab_pressed(index: int) -> void:
|
||
_tab_index = index
|
||
_update_tab_bar(_panel.get_node_or_null("PetTabs") as Control, _tab_index)
|
||
render()
|
||
|
||
|
||
func _selected_pet() -> Dictionary:
|
||
for pet in _rows:
|
||
if int(pet.get("id", 0)) == _selected_id:
|
||
return pet
|
||
return {}
|
||
|
||
|
||
func _select(pet_id: int) -> void:
|
||
_selected_id = pet_id
|
||
render()
|
||
|
||
|
||
func _power(pet: Dictionary) -> int:
|
||
if int(pet.get("fighting_capacity", 0)) > 0:
|
||
return int(pet.get("fighting_capacity", 0))
|
||
var config := _pet_config(pet)
|
||
return ceili(0.2 * _pet_attr(pet, config, "blood") + _pet_attr(pet, config, "attack") + 2.0 * _pet_attr(pet, config, "anti") + 3.0 * _pet_attr(pet, config, "crit") + 3.0 * _pet_attr(pet, config, "dodge"))
|
||
|
||
|
||
func _render_scene_pet(pet: Dictionary, icon_id: int) -> void:
|
||
var clip_name := "hungry" if int(pet.get("feed", 0)) <= 0 else "idle"
|
||
var label_name := "0" if int(pet.get("feed", 0)) > 0 else ""
|
||
_display_sprite = TextureRect.new()
|
||
_display_sprite.name = "BattlePetMovieClip"
|
||
_display_sprite.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||
_display_sprite.stretch_mode = TextureRect.STRETCH_SCALE
|
||
_display_sprite.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
_display.add_child(_display_sprite)
|
||
var origin := Vector2.ZERO if _pet_can_patrol(pet) else PET_SCENE_ORIGIN
|
||
var scale := PET_PATROL_SCENE_SCALE if _pet_can_patrol(pet) else PET_SCENE_SCALE
|
||
_set_display_clip(BATTLE_PET_SCENE_ATLAS_PATH, clip_name, label_name, origin, scale, icon_id)
|
||
_ensure_display_frame_timer()
|
||
|
||
|
||
func _render_scene_speech(pet: Dictionary) -> void:
|
||
var label := Label.new()
|
||
label.name = "Speech"
|
||
label.text = I18nScript.t("饿了..." if int(pet.get("feed", 0)) <= 0 else "汪~")
|
||
label.position = Vector2(18.0, 0.0)
|
||
label.size = Vector2(94.0, 28.0)
|
||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
label.modulate.a = 0.0
|
||
_style_label(label, 18, Color.html("#fffaf0"), 2, Color.html("#7d1c1c"))
|
||
_display.add_child(label)
|
||
|
||
if _owner == null:
|
||
label.modulate.a = 1.0
|
||
return
|
||
var tween := _owner.create_tween().set_loops()
|
||
tween.tween_interval(0.4)
|
||
tween.tween_property(label, "modulate:a", 1.0, 0.22)
|
||
tween.tween_interval(1.3)
|
||
tween.tween_property(label, "modulate:a", 0.0, 0.28)
|
||
tween.tween_interval(3.4)
|
||
_display.set_meta("speech_tween", tween)
|
||
|
||
|
||
func _set_display_clip(atlas_path: String, clip_name: String, label_name: String, origin: Vector2, frame_scale: float, fallback_icon_id: int) -> void:
|
||
if _display_sprite == null or not is_instance_valid(_display_sprite):
|
||
return
|
||
_display_frame_origin = origin
|
||
_display_frame_scale = frame_scale
|
||
_display_fallback_icon_id = fallback_icon_id
|
||
_display_frames = _movie_clip_cache.frames_for_label(atlas_path, clip_name, label_name).duplicate(false) if not label_name.is_empty() else _movie_clip_cache.frames(atlas_path, clip_name).duplicate(false)
|
||
_display_frame_index = 0
|
||
if _display_frames.is_empty():
|
||
_display_sprite.texture = _get_item_icon_texture(_display_fallback_icon_id)
|
||
_display_sprite.position = _display_frame_origin + Vector2(16.0, 10.0) * _display_frame_scale
|
||
_display_sprite.size = Vector2(98.0, 98.0) * _display_frame_scale
|
||
return
|
||
_display_frame_rate = int(_display_frames[0].get("frame_rate", 12))
|
||
_apply_display_frame()
|
||
var timer := _display_frame_timer()
|
||
if timer != null:
|
||
timer.wait_time = 1.0 / maxf(float(_display_frame_rate), 1.0)
|
||
|
||
|
||
func _ensure_display_frame_timer() -> void:
|
||
if _display == null or not is_instance_valid(_display):
|
||
return
|
||
var timer := _display_frame_timer()
|
||
if timer != null:
|
||
timer.wait_time = 1.0 / maxf(float(_display_frame_rate), 1.0)
|
||
if timer.is_stopped():
|
||
timer.start()
|
||
return
|
||
timer = Timer.new()
|
||
timer.name = "BattlePetFrameTimer"
|
||
timer.wait_time = 1.0 / maxf(float(_display_frame_rate), 1.0)
|
||
timer.timeout.connect(Callable(self, "_advance_display_frame"))
|
||
_display.add_child(timer)
|
||
_display_frame_timer_node = timer
|
||
timer.start()
|
||
|
||
|
||
func _display_frame_timer() -> Timer:
|
||
if _display_frame_timer_node != null and is_instance_valid(_display_frame_timer_node) and not _display_frame_timer_node.is_queued_for_deletion():
|
||
return _display_frame_timer_node
|
||
if _display == null or not is_instance_valid(_display):
|
||
_display_frame_timer_node = null
|
||
return null
|
||
var timer := _display.get_node_or_null("BattlePetFrameTimer") as Timer
|
||
if timer == null or timer.is_queued_for_deletion():
|
||
_display_frame_timer_node = null
|
||
return null
|
||
_display_frame_timer_node = timer
|
||
return timer
|
||
|
||
|
||
func _stop_display_frame_timer() -> void:
|
||
var timer := _display_frame_timer()
|
||
if timer != null:
|
||
timer.stop()
|
||
var parent := timer.get_parent()
|
||
if parent != null:
|
||
parent.remove_child(timer)
|
||
timer.queue_free()
|
||
_display_frame_timer_node = null
|
||
|
||
|
||
func _start_battle_pet_patrol() -> void:
|
||
if _owner == null or _display == null:
|
||
return
|
||
UiMotionScript.play_patrol_loop(_owner, _display, PET_PATROL_START, PET_PATROL_END, {
|
||
"start_delay": 3.0,
|
||
"forward_duration": 5.0,
|
||
"back_duration": 5.0,
|
||
"idle_delay": 2.0,
|
||
"before_forward": Callable(self, "_on_battle_pet_walk_forward"),
|
||
"before_back": Callable(self, "_on_battle_pet_walk_back"),
|
||
"before_idle_tail": Callable(self, "_on_battle_pet_idle_tail"),
|
||
"before_idle": Callable(self, "_on_battle_pet_idle"),
|
||
"meta_name": "scene_tween",
|
||
})
|
||
|
||
|
||
func _on_battle_pet_walk_forward() -> void:
|
||
_set_display_clip(BATTLE_PET_SCENE_ATLAS_PATH, "idle", "1", Vector2.ZERO, PET_PATROL_SCENE_SCALE, _display_fallback_icon_id)
|
||
|
||
|
||
func _on_battle_pet_walk_back() -> void:
|
||
_set_display_clip(BATTLE_PET_SCENE_ATLAS_PATH, "idle", "3", Vector2.ZERO, PET_PATROL_SCENE_SCALE, _display_fallback_icon_id)
|
||
|
||
|
||
func _on_battle_pet_idle_tail() -> void:
|
||
_set_display_clip(BATTLE_PET_SCENE_ATLAS_PATH, "idle", "4", Vector2.ZERO, PET_PATROL_SCENE_SCALE, _display_fallback_icon_id)
|
||
|
||
|
||
func _on_battle_pet_idle() -> void:
|
||
_set_display_clip(BATTLE_PET_SCENE_ATLAS_PATH, "idle", "0", Vector2.ZERO, PET_PATROL_SCENE_SCALE, _display_fallback_icon_id)
|
||
|
||
|
||
func _start_scene_motion(can_walk: bool) -> void:
|
||
if _owner == null or _display == null:
|
||
return
|
||
var tween := _owner.create_tween().set_loops()
|
||
if can_walk:
|
||
tween.tween_property(_display, "position:x", PET_SCENE_POSITION.x + 8.0, 1.8).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
||
tween.tween_property(_display, "position:x", PET_SCENE_POSITION.x, 1.8).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
||
else:
|
||
tween.tween_property(_display, "position:y", PET_SCENE_POSITION.y - 2.0, 1.1).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
||
tween.tween_property(_display, "position:y", PET_SCENE_POSITION.y, 1.1).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
||
_display.set_meta("scene_tween", tween)
|
||
|
||
|
||
func _stop_display_animation() -> void:
|
||
if not _is_live_node(_display):
|
||
return
|
||
_stop_display_frame_timer()
|
||
for meta_name in ["scene_tween", "speech_tween"]:
|
||
var value = _display.get_meta(meta_name) if _display.has_meta(meta_name) else null
|
||
if value is Tween:
|
||
(value as Tween).kill()
|
||
_display.set_meta(meta_name, null)
|
||
_display_sprite = null
|
||
_display_frames = []
|
||
_display_frame_index = 0
|
||
|
||
|
||
func _advance_display_frame() -> void:
|
||
if _display_sprite == null or not is_instance_valid(_display_sprite) or _display_frames.is_empty():
|
||
return
|
||
_display_frame_index = (_display_frame_index + 1) % _display_frames.size()
|
||
_apply_display_frame()
|
||
|
||
|
||
func _apply_display_frame() -> void:
|
||
if _display_sprite == null or _display_frames.is_empty():
|
||
return
|
||
var frame: Dictionary = _display_frames[_display_frame_index]
|
||
var texture := frame.get("texture") as Texture2D
|
||
if texture == null:
|
||
return
|
||
var offset := Vector2.ZERO
|
||
var offset_value = frame.get("offset", Vector2.ZERO)
|
||
if offset_value is Vector2:
|
||
offset = offset_value
|
||
var frame_size := Vector2(1.0, 1.0)
|
||
var size_value = frame.get("size", frame_size)
|
||
if size_value is Vector2:
|
||
frame_size = size_value
|
||
_display_sprite.texture = texture
|
||
_display_sprite.position = _display_frame_origin + offset * _display_frame_scale
|
||
_display_sprite.size = frame_size * _display_frame_scale
|
||
|
||
|
||
func _render_base_panel(pet: Dictionary, config: Dictionary, icon_id: int) -> void:
|
||
_ui.add_texture(_list_container, "PetHeadBg", _battle_bg("battle_pet_bg8"), Vector2(56.0, 168.0), Vector2(163.0, 216.0))
|
||
_ui.add_texture(_list_container, "PetHead", _get_item_icon_texture(icon_id), Vector2(79.0, 194.0), Vector2(120.0, 120.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED)
|
||
|
||
_ui.add_panel_label(_list_container, "PowerText", "宠物战力:", Vector2(234.0, 196.0), Vector2(110.0, 30.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(_list_container, "PowerValue", str(_power(pet)), Vector2(340.0, 196.0), Vector2(170.0, 30.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(_list_container, "WayText", "攻击方式:", Vector2(236.0, 232.0), Vector2(104.0, 30.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(_list_container, "WayValue", _pet_type_name(int(config.get("type", 0))), Vector2(340.0, 233.0), Vector2(160.0, 30.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(_list_container, "LevelText", "宠物等级:", Vector2(236.0, 270.0), Vector2(104.0, 30.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(_list_container, "LevelValue", str(int(pet.get("level", 1))), Vector2(340.0, 270.0), Vector2(70.0, 30.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(_list_container, "FightFeedText", "战斗消耗:", Vector2(419.0, 271.0), Vector2(108.0, 30.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(_list_container, "FightFeedValue", str(int(config.get("hunger", 0))), Vector2(523.0, 271.0), Vector2(48.0, 30.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
|
||
var war_texture := _battle_frame("battle_pet_canzhan24_png") if int(pet.get("is_enter_war", 0)) == 1 else _battle_frame("battle_pet_canzhan_png")
|
||
_ui.add_texture_button(_list_container, "SetWar", war_texture, Vector2(96.5, 358.0), Vector2(90.0, 38.0), "", Callable(self, "_set_war"), 1)
|
||
_ui.add_texture_button(_list_container, "FeedPet", _texture_provider.common("c_btn2"), Vector2(202.0, 356.0), Vector2(86.0, 42.0), "喂养", Callable(self, "_open_food_popup").bind(int(pet.get("id", 0))), 20)
|
||
_ui.add_panel_label(_list_container, "ExpText", "经验值:", Vector2(237.0, 317.0), Vector2(86.0, 28.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_add_battle_progress_bar(_list_container, Vector2(326.0, 312.0), int(pet.get("exp", 0)), _next_level_exp(int(pet.get("level", 1))))
|
||
_ui.add_panel_label(_list_container, "HungerText", "饱食度:", Vector2(237.5, 352.5), Vector2(88.0, 28.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(_list_container, "HungerValue", str(int(pet.get("feed", 0))), Vector2(332.0, 352.5), Vector2(92.0, 28.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
|
||
_ui.add_texture(_list_container, "AttrBg", _battle_bg("battle_pet_bg3"), Vector2(74.0, 403.0), Vector2(498.0, 128.0))
|
||
_ui.add_panel_label(_list_container, "HpText", "血量:", Vector2(106.5, 418.0), Vector2(70.0, 28.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(_list_container, "HpValue", _pet_health_text(pet), Vector2(175.5, 418.0), Vector2(132.0, 28.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(_list_container, "AttackText", "攻击:", Vector2(107.0, 456.0), Vector2(70.0, 28.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(_list_container, "AttackValue", str(_pet_attr(pet, config, "attack")), Vector2(175.5, 454.0), Vector2(132.0, 28.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(_list_container, "CritText", "暴击:", Vector2(107.0, 495.0), Vector2(70.0, 28.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(_list_container, "CritValue", str(_pet_attr(pet, config, "crit")), Vector2(176.5, 491.0), Vector2(132.0, 28.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(_list_container, "DodgeText", "闪避:", Vector2(332.0, 417.0), Vector2(70.0, 28.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(_list_container, "DodgeValue", str(_pet_attr(pet, config, "dodge")), Vector2(388.5, 416.0), Vector2(132.0, 28.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(_list_container, "DefenseText", "防御:", Vector2(332.0, 456.0), Vector2(70.0, 28.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(_list_container, "DefenseValue", str(_pet_attr(pet, config, "anti")), Vector2(388.5, 455.0), Vector2(132.0, 28.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
|
||
_ui.add_texture(_list_container, "DescTitle", _battle_frame("battle_pet_canzhan7_png"), Vector2(214.5, 537.0), Vector2(211.0, 39.0))
|
||
_ui.add_texture(_list_container, "DescBg", _battle_bg("battle_pet_bg9"), Vector2(62.0, 562.0), Vector2(516.0, 162.0))
|
||
var desc: Label = _ui.add_panel_label(_list_container, "Desc", str(config.get("desc", "")), Vector2(93.0, 589.0), Vector2(454.0, 106.0), 22, Color.WHITE, 2, Color.html("#7d1c1c"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
desc.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
desc.clip_text = false
|
||
|
||
|
||
func _render_pet_list_page(parent: Control) -> void:
|
||
_ui.add_content_panel_background(parent, "PetListBg", Vector2(50.0, 237.0), Vector2(540.0, 507.0))
|
||
if _rows.is_empty():
|
||
_ui.add_panel_label(parent, "PetListEmpty", "暂无战宠,请到宠物商店购买宠物蛋。", Vector2(92.0, 445.0), Vector2(456.0, 50.0), 24, Color.html("#ce5f5f"), 0, Color.TRANSPARENT)
|
||
return
|
||
var scroll := ScrollContainer.new()
|
||
scroll.name = "PetListScroll"
|
||
scroll.position = Vector2(62.0, 249.0)
|
||
scroll.size = Vector2(516.0, 488.0)
|
||
scroll.clip_contents = true
|
||
_ui.configure_vertical_scroll(scroll)
|
||
parent.add_child(scroll)
|
||
|
||
var list := VBoxContainer.new()
|
||
list.name = "PetListItems"
|
||
list.custom_minimum_size = Vector2(516.0, 0.0)
|
||
list.add_theme_constant_override("separation", 0)
|
||
scroll.add_child(list)
|
||
|
||
for pet in _rows:
|
||
list.add_child(_create_pet_list_item(pet))
|
||
|
||
|
||
func _create_pet_list_item(pet: Dictionary) -> Control:
|
||
var config := _pet_config(pet)
|
||
var pet_id := int(pet.get("id", 0))
|
||
var holder := Control.new()
|
||
holder.name = "PetListItem%s" % pet_id
|
||
holder.custom_minimum_size = Vector2(508.0, 244.0)
|
||
holder.size = Vector2(508.0, 244.0)
|
||
|
||
_ui.add_scale9_patch(holder, "Bg", _texture_provider.common("shop_itembg"), Vector2(8.0, 6.0), Vector2(500.0, 230.0), Rect2(90.0, 57.0, 4.0, 2.0))
|
||
_ui.add_panel_label(holder, "Name", str(config.get("name", pet.get("name", "战宠"))), Vector2(18.0, 41.0), Vector2(132.0, 26.0), 18, Color.html("#784226"), 2, Color.WHITE)
|
||
var icon_id := int(config.get("graphical_id", pet.get("graphical_id", 11001)))
|
||
_ui.add_texture(holder, "Head", _get_item_icon_texture(icon_id), Vector2(26.0, 72.5), Vector2(95.0, 95.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED)
|
||
_ui.add_panel_label(holder, "PowerText", "战力:", Vector2(142.5, 52.0), Vector2(82.0, 27.0), 19, Color.html("#f8c903"), 3, Color.html("#97480e"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(holder, "Power", str(_power(pet)), Vector2(220.0, 52.0), Vector2(94.0, 27.0), 19, Color.html("#fffef7"), 2, Color.html("#685036"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(holder, "LevelText", "等级:", Vector2(318.0, 52.0), Vector2(72.0, 27.0), 19, Color.html("#f8c903"), 3, Color.html("#97480e"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(holder, "Level", str(int(pet.get("level", 1))), Vector2(388.0, 52.0), Vector2(80.0, 27.0), 19, Color.html("#fffef7"), 2, Color.html("#685036"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(holder, "FeedText", "巡逻时间:", Vector2(142.5, 86.0), Vector2(112.0, 27.0), 19, Color.html("#f8c903"), 3, Color.html("#97480e"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
_ui.add_panel_label(holder, "FeedTime", _pet_feed_time_text(pet), Vector2(252.0, 86.0), Vector2(194.0, 27.0), 19, Color.html("#fffef7"), 2, Color.html("#685036"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
var desc: Label = _ui.add_panel_label(holder, "Desc", str(config.get("desc", "")), Vector2(26.0, 188.0), Vector2(456.0, 42.0), 19, Color.html("#f8c903"), 3, Color.html("#97480e"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
desc.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
|
||
var active := int(pet.get("is_enter_war", 0)) == 1
|
||
var button_label := "喂养" if active else "参战"
|
||
var button_callback := Callable(self, "_open_food_popup").bind(pet_id) if active else Callable(self, "_set_war_for_pet").bind(pet_id)
|
||
_ui.add_texture_button(holder, "Action", _texture_provider.common("c_btn2"), Vector2(144.0, 124.0), Vector2(110.0, 45.0), button_label, button_callback, 22)
|
||
return holder
|
||
|
||
|
||
func _render_pet_shop_page(parent: Control) -> void:
|
||
_ui.add_content_panel_background(parent, "PetShopBg", Vector2(50.0, 237.0), Vector2(540.0, 507.0))
|
||
var items := _pet_shop_items()
|
||
if items.is_empty():
|
||
_ui.add_panel_label(parent, "PetShopEmpty", "暂无战宠商品", Vector2(120.0, 445.0), Vector2(400.0, 50.0), 24, Color.html("#ce5f5f"), 0, Color.TRANSPARENT)
|
||
return
|
||
var scroll := ScrollContainer.new()
|
||
scroll.name = "PetShopScroll"
|
||
scroll.position = Vector2(62.0, 249.0)
|
||
scroll.size = Vector2(516.0, 488.0)
|
||
scroll.clip_contents = true
|
||
_ui.configure_vertical_scroll(scroll)
|
||
parent.add_child(scroll)
|
||
|
||
var holder := MarginContainer.new()
|
||
holder.name = "PetShopListHolder"
|
||
holder.custom_minimum_size = Vector2(516.0, 0.0)
|
||
holder.add_theme_constant_override("margin_left", 20)
|
||
holder.add_theme_constant_override("margin_top", 8)
|
||
holder.add_theme_constant_override("margin_right", 8)
|
||
holder.add_theme_constant_override("margin_bottom", 8)
|
||
scroll.add_child(holder)
|
||
|
||
var grid := GridContainer.new()
|
||
grid.name = "PetShopList"
|
||
grid.columns = 2
|
||
grid.custom_minimum_size = Vector2(464.0, 0.0)
|
||
grid.add_theme_constant_override("h_separation", 16)
|
||
grid.add_theme_constant_override("v_separation", 12)
|
||
holder.add_child(grid)
|
||
for shop_item in items:
|
||
grid.add_child(_create_pet_shop_card(shop_item))
|
||
|
||
|
||
func _create_pet_shop_card(shop_item: Dictionary) -> Control:
|
||
var holder := Control.new()
|
||
holder.name = "PetShopItem%s" % int(shop_item.get("id", 0))
|
||
holder.custom_minimum_size = Vector2(224.0, 163.0)
|
||
holder.size = Vector2(224.0, 163.0)
|
||
|
||
_ui.add_nine_patch(holder, "Bg", _texture_provider.common("tipbg"), Vector2.ZERO, holder.size, Vector4(45.0, 21.0, 57.0, 69.0))
|
||
_ui.add_nine_patch(holder, "PriceBg", _texture_provider.common("blue_rect"), Vector2(116.0, 7.0), Vector2(89.0, 21.0), Vector4(22.0, 10.0, 13.0, 1.0))
|
||
_ui.add_panel_label(holder, "PriceTitle", "单价", Vector2(146.0, 10.0), Vector2(48.0, 18.0), 16, Color.WHITE, 0, Color.TRANSPARENT)
|
||
|
||
var item_config: Dictionary = shop_item.get("item", {})
|
||
var item_id := int(shop_item.get("item_id", item_config.get("id", 0)))
|
||
_ui.add_panel_label(holder, "Name", str(item_config.get("name", "物品")), Vector2(13.0, 121.0), Vector2(193.0, 28.0), 24, Color.html("#753f24"), 0, Color.TRANSPARENT)
|
||
|
||
var group_icon := Control.new()
|
||
group_icon.name = "IconGroup"
|
||
group_icon.position = Vector2(14.0, 22.0)
|
||
group_icon.size = Vector2(86.0, 91.0)
|
||
holder.add_child(group_icon)
|
||
_ui.add_texture(group_icon, "ItemBg", _texture_provider.common("item_bg"), Vector2.ZERO, Vector2(90.0, 90.0))
|
||
_ui.add_texture(group_icon, "Icon", _get_item_icon_texture(int(item_config.get("icon", item_id))), Vector2(8.0, 8.0), Vector2(74.0, 70.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED)
|
||
_ui.add_nine_patch(group_icon, "LvBg", _texture_provider.common("blue_rect"), Vector2(-1.0, -1.0), Vector2(52.0, 21.0), Vector4(0.0, 2.0, 5.0, 17.0))
|
||
var level_required := int(shop_item.get("playerLevel", shop_item.get("player_level", 1)))
|
||
_ui.add_panel_label(group_icon, "Lv", "Lv.%s" % level_required, Vector2(6.0, 1.0), Vector2(50.0, 20.0), 16, Color.WHITE, 0, Color.TRANSPARENT, HORIZONTAL_ALIGNMENT_LEFT)
|
||
|
||
var is_locked := int(_player_data().get("level", 1)) < level_required
|
||
var lock: TextureRect = _ui.add_texture(group_icon, "Lock", _texture_provider.common("img_lock"), Vector2(53.0, 49.0), Vector2(35.0, 40.0))
|
||
lock.visible = is_locked
|
||
|
||
var price_icon_key := "sicon_gold" if int(shop_item.get("gold", 0)) > 0 else "sicon_diamond"
|
||
_ui.add_texture(holder, "PriceIcon", _texture_provider.common(price_icon_key), Vector2(125.0, 42.0), Vector2(35.0, 35.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED)
|
||
var price := int(shop_item.get("gold", 0)) if int(shop_item.get("gold", 0)) > 0 else int(shop_item.get("diamond", shop_item.get("gem", 0)))
|
||
_ui.add_panel_label(holder, "Price", NumberFormatterScript.compact(price), Vector2(150.0, 43.0), Vector2(68.0, 28.0), 22, Color.html("#fffef7"), 2, Color.html("#685036"), HORIZONTAL_ALIGNMENT_LEFT)
|
||
var buy_button: Control = _ui.add_texture_button(holder, "Buy", _texture_provider.common("c_btn2"), Vector2(123.0, 70.0), Vector2(88.0, 44.0), "购买", Callable(self, "_buy_shop_item").bind(shop_item.duplicate(true)), 22)
|
||
if is_locked:
|
||
buy_button.modulate = Color(0.72, 0.72, 0.72, 1.0)
|
||
var hit := buy_button.get_node_or_null("Hit") as Button
|
||
if hit != null:
|
||
hit.disabled = true
|
||
return holder
|
||
|
||
|
||
func _render_pet_radio_list() -> void:
|
||
for index in range(mini(_rows.size(), 4)):
|
||
var row_pet: Dictionary = _rows[index]
|
||
var row_config := _pet_config(row_pet)
|
||
var row_icon := int(row_config.get("graphical_id", row_pet.get("graphical_id", 11001)))
|
||
var holder := Control.new()
|
||
holder.name = "PetRadio%s" % index
|
||
holder.position = Vector2(65.0 + index * 122.0, 753.0)
|
||
holder.size = Vector2(87.0, 99.0)
|
||
holder.mouse_filter = Control.MOUSE_FILTER_STOP
|
||
_list_container.add_child(holder)
|
||
_ui.add_texture(holder, "Bg", _battle_bg("battle_pet_bg4"), Vector2.ZERO, Vector2(87.0, 99.0))
|
||
if int(row_pet.get("id", 0)) == _selected_id:
|
||
_ui.add_texture(holder, "Selected", _battle_frame("battle_pet_canzhan_26_png"), Vector2.ZERO, Vector2(87.0, 99.0))
|
||
_ui.add_texture(holder, "Head", _get_item_icon_texture(row_icon), Vector2(13.0, 11.0), Vector2(61.0, 63.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED)
|
||
_ui.add_panel_label(holder, "Name", str(row_config.get("name", row_pet.get("name", "战宠"))), Vector2(0.0, 79.0), Vector2(87.0, 20.0), 14, Color.WHITE, 2, Color.html("#7d1c1c"))
|
||
var hit := Button.new()
|
||
hit.name = "Hit"
|
||
hit.size = holder.size
|
||
hit.text = ""
|
||
hit.focus_mode = Control.FOCUS_NONE
|
||
_apply_empty_button_style(hit)
|
||
hit.pressed.connect(Callable(self, "_select").bind(int(row_pet.get("id", 0))))
|
||
holder.add_child(hit)
|
||
|
||
|
||
func _add_battle_progress_bar(parent: Control, position: Vector2, current: int, total: int) -> void:
|
||
_ui.add_scale9_patch(parent, "ExpBarBg", _battle_frame("battle_pet_canzhan13_png"), position, Vector2(234.0, 30.0), Rect2(44.0, 8.0, 268.0, 14.0))
|
||
var ratio := clampf(float(current) / maxf(float(total), 1.0), 0.0, 1.0)
|
||
var fill_width := maxf(1.0, 230.0 * ratio)
|
||
if ratio > 0.0:
|
||
var fill_clip := Control.new()
|
||
fill_clip.name = "ExpBarClip"
|
||
fill_clip.position = position + Vector2(2.0, 2.0)
|
||
fill_clip.size = Vector2(fill_width, 26.0)
|
||
fill_clip.clip_contents = true
|
||
fill_clip.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
parent.add_child(fill_clip)
|
||
_ui.add_scale9_patch(fill_clip, "ExpBarFill", _battle_frame("battle_pet_canzhan14_png"), Vector2.ZERO, Vector2(230.0, 26.0), Rect2(43.0, 3.0, 262.0, 20.0))
|
||
_ui.add_panel_label(parent, "ExpBarText", "%s / %s" % [current, total], position + Vector2(17.0, 5.0), Vector2(200.0, 19.0), 20, Color.WHITE, 2, Color.html("#7d1c1c"))
|
||
|
||
|
||
func _battle_frame(name: String) -> Texture2D:
|
||
return _texture_provider.frame(_texture_provider.atlas_path("battle_pet"), name)
|
||
|
||
|
||
func _battle_bg(name: String) -> Texture2D:
|
||
return _texture_provider.standalone("%s/%s.png" % [BATTLE_PET_BG_DIR, name])
|
||
|
||
|
||
func _pet_config(pet: Dictionary) -> Dictionary:
|
||
if _game_config == null:
|
||
return {}
|
||
return _game_config.get_pet_config(int(pet.get("pet_config_id", pet.get("item_id", 0))))
|
||
|
||
|
||
func _next_level_exp(level: int) -> int:
|
||
if _game_config == null:
|
||
return 1
|
||
var next_level := clampi(level + 1, 1, 60)
|
||
var config: Dictionary = _game_config.get_pet_level_config(next_level)
|
||
return maxi(int(config.get("exp", 1)), 1)
|
||
|
||
|
||
func _pet_type_name(type_id: int) -> String:
|
||
if type_id >= 0 and type_id < PET_TYPE_TEXT.size():
|
||
return str(PET_TYPE_TEXT[type_id])
|
||
return str(PET_TYPE_TEXT[0])
|
||
|
||
|
||
func _pet_attr(pet: Dictionary, config: Dictionary, field: String) -> int:
|
||
var pet_data = pet.get("petData", {})
|
||
var base := int(config.get(field, 0))
|
||
if pet.has(field):
|
||
base = int(pet.get(field, base))
|
||
elif pet_data is Dictionary and pet_data.has(field):
|
||
base = int(pet_data.get(field, base))
|
||
return base + int(pet.get("%s_add" % field, 0))
|
||
|
||
|
||
func _normalize_pet_row(row: Dictionary) -> Dictionary:
|
||
var pet := row.duplicate(true)
|
||
var max_hp := _pet_health_max(pet)
|
||
var current_hp := _pet_health_current(pet, max_hp)
|
||
pet["max_hp"] = max_hp
|
||
pet["hp"] = current_hp
|
||
return pet
|
||
|
||
|
||
func _pet_health_text(pet: Dictionary) -> String:
|
||
var max_hp := _pet_health_max(pet)
|
||
var current_hp := _pet_health_current(pet, max_hp)
|
||
return "%s/%s" % [current_hp, max_hp]
|
||
|
||
|
||
func _pet_health_current(pet: Dictionary, max_hp := DEFAULT_PET_HP) -> int:
|
||
var value := _first_pet_int(pet, ["hp", "current_hp", "health", "current_health", "cur_hp", "curBlood", "cur_blood"], max_hp)
|
||
return clampi(value, 0, maxi(max_hp, 1))
|
||
|
||
|
||
func _pet_health_max(pet: Dictionary) -> int:
|
||
var value := _first_pet_int(pet, ["max_hp", "hp_max", "health_max", "max_health", "maxBlood", "max_blood"], DEFAULT_PET_HP)
|
||
return maxi(value, 1)
|
||
|
||
|
||
func _first_pet_int(pet: Dictionary, keys: Array, fallback: int) -> int:
|
||
for key in keys:
|
||
var key_text := str(key)
|
||
if pet.has(key_text):
|
||
return int(pet.get(key_text, fallback))
|
||
var pet_data = pet.get("petData", {})
|
||
if pet_data is Dictionary:
|
||
for key in keys:
|
||
var key_text := str(key)
|
||
if pet_data.has(key_text):
|
||
return int(pet_data.get(key_text, fallback))
|
||
return fallback
|
||
|
||
|
||
func _pet_can_patrol(pet: Dictionary) -> bool:
|
||
return int(pet.get("is_enter_war", 0)) == 1 and int(pet.get("feed", 0)) > 0
|
||
|
||
|
||
func _pet_feed_time_text(pet: Dictionary) -> String:
|
||
var feed_left := int(pet.get("feed", 0))
|
||
if feed_left <= 0:
|
||
return "0秒"
|
||
return _format_duration(feed_left)
|
||
|
||
|
||
func _set_war_for_pet(pet_id: int) -> void:
|
||
_selected_id = pet_id
|
||
await _set_war()
|
||
|
||
|
||
func _set_war() -> void:
|
||
if _is_busy():
|
||
return
|
||
var pet := _selected_pet()
|
||
if pet.is_empty():
|
||
return
|
||
_set_busy(true)
|
||
var result: Dictionary = await _pet_api.set_enter_war(_token(), int(pet.get("id", 0)))
|
||
_set_busy(false)
|
||
if not result.get("ok", false):
|
||
_show_toast(_response_status_text(result, "参战失败"))
|
||
return
|
||
_show_toast("参战成功")
|
||
await refresh_display()
|
||
render()
|
||
|
||
|
||
func _open_food_popup(pet_id := 0) -> void:
|
||
if _is_busy():
|
||
return
|
||
var target_pet_id := int(pet_id)
|
||
if target_pet_id <= 0:
|
||
target_pet_id = _selected_id
|
||
if target_pet_id <= 0:
|
||
_show_toast("请选择战宠")
|
||
return
|
||
_feeding_pet_id = target_pet_id
|
||
if _food_popup != null and is_instance_valid(_food_popup):
|
||
_food_popup.queue_free()
|
||
await _refresh_store_house()
|
||
var foods := _pet_food_items()
|
||
_selected_food_item_id = 0
|
||
for item in foods:
|
||
if int(item.get("num", 0)) > 0:
|
||
_selected_food_item_id = int(item.get("item_id", 0))
|
||
break
|
||
if _selected_food_item_id == 0 and not foods.is_empty():
|
||
_selected_food_item_id = int(foods[0].get("item_id", 0))
|
||
if foods.is_empty():
|
||
_show_toast("没有可用宠物粮")
|
||
return
|
||
if _panel == null or not is_instance_valid(_panel):
|
||
return
|
||
|
||
var popup := Control.new()
|
||
popup.name = "PetFoodPopup"
|
||
popup.size = _panel_screen_size
|
||
popup.mouse_filter = Control.MOUSE_FILTER_STOP
|
||
popup.z_index = 50
|
||
_food_popup = popup
|
||
_panel.add_child(popup)
|
||
|
||
var mask := Button.new()
|
||
mask.name = "PetFoodMask"
|
||
mask.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
mask.text = ""
|
||
mask.focus_mode = Control.FOCUS_NONE
|
||
_apply_empty_button_style(mask)
|
||
mask.pressed.connect(func() -> void: popup.queue_free())
|
||
popup.add_child(mask)
|
||
|
||
_ui.add_nine_patch(popup, "Bg", _texture_provider.common("c_panel2"), Vector2(88.0, 302.0), Vector2(464.0, 316.0), Vector4(136.0, 128.0, 4.0, 2.0))
|
||
_ui.add_texture(popup, "Trim1", _texture_provider.common("trimming1"), Vector2(127.0, 279.0), Vector2(70.0, 63.0))
|
||
_ui.add_texture(popup, "Trim2", _texture_provider.common("trimming2"), Vector2(441.0, 302.0), Vector2(35.0, 26.0))
|
||
_ui.add_panel_label(popup, "Title", "选择宠物粮", Vector2(190.0, 284.0), Vector2(260.0, 42.0), 30, Color.WHITE, 2, Color.html("#7d1c1c"))
|
||
_ui.add_texture_button(popup, "Close", _texture_provider.common("btn_close"), Vector2(480.0, 281.0), Vector2(80.0, 74.0), "", func() -> void: popup.queue_free(), 20)
|
||
for index in range(foods.size()):
|
||
_add_food_option(popup, foods[index], index)
|
||
var confirm: Control = _ui.add_texture_button(popup, "Eat", _texture_provider.common("c_btn3"), Vector2(237.0, 554.0), Vector2(166.0, 58.0), "确定", Callable(self, "_feed_selected_food"), 30)
|
||
_set_texture_button_enabled(confirm, _owned_amount(_selected_food_item_id) > 0, _texture_provider.common("c_btn3"), _texture_provider.common("c_btn3_gray"))
|
||
_update_food_selection(popup)
|
||
|
||
|
||
func _add_food_option(popup: Control, item: Dictionary, index: int) -> void:
|
||
var positions := [Vector2(140.0, 371.0), Vector2(260.0, 371.0), Vector2(380.0, 371.0)]
|
||
if index >= positions.size():
|
||
return
|
||
var holder := Control.new()
|
||
holder.name = "Food%s" % index
|
||
holder.position = positions[index]
|
||
holder.size = Vector2(104.0, 144.0)
|
||
holder.set_meta("item_id", int(item.get("item_id", 0)))
|
||
holder.mouse_filter = Control.MOUSE_FILTER_STOP
|
||
popup.add_child(holder)
|
||
_ui.add_texture(holder, "ItemBg", _texture_provider.common("item_bg"), Vector2(7.0, 7.0), Vector2(90.0, 90.0))
|
||
_ui.add_texture(holder, "Selected", _texture_provider.common("icon_bg_selected"), Vector2.ZERO, Vector2(104.0, 104.0))
|
||
_ui.add_texture(holder, "Icon", _get_item_icon_texture(int(item.get("icon_id", 0))), Vector2(15.0, 15.0), Vector2(74.0, 70.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED)
|
||
_ui.add_panel_label(holder, "Num", "x%s" % int(item.get("num", 0)), Vector2(19.0, 79.0), Vector2(76.0, 24.0), 20, Color.html("#b36011"), 1, Color.html("#ffe59d"), HORIZONTAL_ALIGNMENT_RIGHT)
|
||
_ui.add_panel_label(holder, "Name", _pet_food_name(int(item.get("item_id", 0))), Vector2(-6.0, 113.0), Vector2(116.0, 26.0), 18, Color.html("#b36011"), 0, Color.TRANSPARENT)
|
||
var hit := Button.new()
|
||
hit.name = "Hit"
|
||
hit.size = holder.size
|
||
hit.text = ""
|
||
hit.focus_mode = Control.FOCUS_NONE
|
||
_apply_empty_button_style(hit)
|
||
hit.pressed.connect(Callable(self, "_select_food").bind(int(item.get("item_id", 0)), popup))
|
||
holder.add_child(hit)
|
||
|
||
|
||
func _select_food(item_id: int, popup: Control) -> void:
|
||
_selected_food_item_id = item_id
|
||
_update_food_selection(popup)
|
||
var confirm := popup.get_node_or_null("Eat") as Control
|
||
_set_texture_button_enabled(confirm, _owned_amount(item_id) > 0, _texture_provider.common("c_btn3"), _texture_provider.common("c_btn3_gray"))
|
||
|
||
|
||
func _update_food_selection(popup: Control) -> void:
|
||
if popup == null:
|
||
return
|
||
for child in popup.get_children():
|
||
if not child is Control or not child.has_meta("item_id"):
|
||
continue
|
||
var selected := child.get_node_or_null("Selected") as TextureRect
|
||
if selected != null:
|
||
selected.visible = int(child.get_meta("item_id", 0)) == _selected_food_item_id
|
||
|
||
|
||
func _feed_selected_food() -> void:
|
||
if _is_busy():
|
||
return
|
||
if _feeding_pet_id <= 0:
|
||
_show_toast("请选择战宠")
|
||
return
|
||
var item_id := _selected_food_item_id
|
||
if item_id <= 0 or _owned_amount(item_id) <= 0:
|
||
_show_toast("没有可用宠物粮")
|
||
return
|
||
_set_busy(true)
|
||
var result: Dictionary = await _pet_api.feed(_token(), _feeding_pet_id, item_id)
|
||
_set_busy(false)
|
||
if not result.get("ok", false):
|
||
_show_toast(_response_status_text(result, "喂养失败"))
|
||
return
|
||
_decrease_warehouse_item(item_id, 1)
|
||
if _food_popup != null and is_instance_valid(_food_popup):
|
||
_food_popup.queue_free()
|
||
_food_popup = null
|
||
_show_toast("喂养成功")
|
||
await refresh_display()
|
||
await _refresh_store_house()
|
||
render()
|
||
|
||
|
||
func _feed() -> void:
|
||
if _is_busy():
|
||
return
|
||
var pet := _selected_pet()
|
||
if pet.is_empty():
|
||
return
|
||
await _open_food_popup(int(pet.get("id", 0)))
|
||
|
||
|
||
func _pet_food_items() -> Array[Dictionary]:
|
||
var foods: Array[Dictionary] = []
|
||
for item_id in _dog_food_item_ids:
|
||
var normalized_id := int(item_id)
|
||
var config: Dictionary = _game_config.normalize_item_config(_game_config.get_item_config(normalized_id)) if _game_config != null else {}
|
||
if config.is_empty():
|
||
continue
|
||
foods.append({
|
||
"item_id": normalized_id,
|
||
"num": _owned_amount(normalized_id),
|
||
"icon_id": int(config.get("icon", normalized_id)),
|
||
"config": config,
|
||
})
|
||
return foods
|
||
|
||
|
||
func _pet_food_name(item_id: int) -> String:
|
||
if _game_config == null:
|
||
return "宠物粮"
|
||
return _game_config.get_item_name(item_id)
|
||
|
||
|
||
func _pet_shop_items() -> Array[Dictionary]:
|
||
if _game_config == null:
|
||
return []
|
||
var result: Array[Dictionary] = []
|
||
var seen: Dictionary = {}
|
||
for shop_item in _game_config.get_shop_config_by_type(3):
|
||
var store_id := int(shop_item.get("id", 0))
|
||
if seen.has(store_id):
|
||
continue
|
||
var item_config: Dictionary = shop_item.get("item", {})
|
||
if not _is_pet_shop_item(shop_item, item_config):
|
||
continue
|
||
seen[store_id] = true
|
||
result.append(shop_item)
|
||
result.sort_custom(func(left: Dictionary, right: Dictionary) -> bool:
|
||
var left_level := int(left.get("playerLevel", left.get("player_level", 1)))
|
||
var right_level := int(right.get("playerLevel", right.get("player_level", 1)))
|
||
if left_level == right_level:
|
||
return int(left.get("id", 0)) < int(right.get("id", 0))
|
||
return left_level < right_level
|
||
)
|
||
return result
|
||
|
||
|
||
func _is_pet_shop_item(shop_item: Dictionary, item_config: Dictionary) -> bool:
|
||
var item_id := int(shop_item.get("item_id", item_config.get("id", 0)))
|
||
var small_type := int(item_config.get("smallType", 0))
|
||
return small_type == 500 or _dog_food_item_ids.has(item_id)
|
||
|
||
|
||
func _buy_shop_item(shop_item: Dictionary) -> void:
|
||
if _is_busy():
|
||
return
|
||
if _shop_api == null:
|
||
_show_toast("购买失败:商店接口未初始化")
|
||
return
|
||
var level_required := int(shop_item.get("playerLevel", shop_item.get("player_level", 1)))
|
||
if int(_player_data().get("level", 1)) < level_required:
|
||
_show_toast("购买失败:等级不足")
|
||
return
|
||
var gold_cost := int(shop_item.get("gold", 0))
|
||
var gem_cost := int(shop_item.get("diamond", shop_item.get("gem", 0)))
|
||
if gold_cost > int(_player_data().get("gold", 0)):
|
||
_show_toast("购买失败:金豆不足")
|
||
return
|
||
if gem_cost > int(_player_data().get("gem", 0)):
|
||
_show_toast("购买失败:资源不足,请充值!")
|
||
return
|
||
_set_busy(true)
|
||
var result: Dictionary = await _shop_api.buy(_token(), int(shop_item.get("id", 0)), 1)
|
||
_set_busy(false)
|
||
if not result.get("ok", false):
|
||
_show_toast(_response_status_text(result, "购买失败"))
|
||
return
|
||
_call("decrease_player_currency", [gold_cost, gem_cost])
|
||
_render_player()
|
||
_show_toast("购买成功")
|
||
await _refresh_store_house()
|
||
render()
|