1089 lines
42 KiB
GDScript
1089 lines
42 KiB
GDScript
extends SceneTree
|
|
|
|
const SeedBagViewScript := preload("res://scripts/ui/farm/seed_bag_view.gd")
|
|
const PlantTipViewScript := preload("res://scripts/ui/farm/plant_tip_view.gd")
|
|
const LandGridViewScript := preload("res://scripts/ui/farm/land_grid_view.gd")
|
|
const LandActionMenuScript := preload("res://scripts/ui/farm/land_action_menu.gd")
|
|
const FarmInteractionCoordinatorScript := preload("res://scripts/ui/farm/farm_interaction_coordinator.gd")
|
|
const MainMenuControllerScript := preload("res://scripts/ui/menus/main_menu_controller.gd")
|
|
const TextureRegionCatalogScript := preload("res://scripts/resources/texture_region_catalog.gd")
|
|
const AtlasTextureCacheScript := preload("res://scripts/resources/atlas_texture_cache.gd")
|
|
const TextureProviderScript := preload("res://scripts/ui/common/texture_provider.gd")
|
|
const UiBuilderScript := preload("res://scripts/ui/common/ui_builder.gd")
|
|
const UiMotionScript := preload("res://scripts/ui/common/ui_motion.gd")
|
|
const I18nScript := preload("res://scripts/core/i18n.gd")
|
|
const FriendPanelScript := preload("res://scripts/ui/panels/friend_panel.gd")
|
|
const BuildingPanelScript := preload("res://scripts/ui/panels/building_panel.gd")
|
|
const MarketPanelScript := preload("res://scripts/ui/panels/market_panel.gd")
|
|
const PetPanelScript := preload("res://scripts/ui/panels/pet_panel.gd")
|
|
const GameConfigCatalogScript := preload("res://scripts/core/game_config_catalog.gd")
|
|
const GameScene := preload("res://scenes/ui/game_scene.tscn")
|
|
const BagBgTexture := preload("res://assets/egret/common/bag_bg_png.tres")
|
|
const BagLineTexture := preload("res://assets/egret/common/bag_line_png.tres")
|
|
const PlantIconTexture := preload("res://assets/egret/plant/1031.png")
|
|
const TipBgTexture := preload("res://assets/egret/common/log_itembg_png.tres")
|
|
const PhaseClockTexture := preload("res://assets/egret/common/m_phase_clock_png.tres")
|
|
const PhaseBarTexture := preload("res://assets/egret/common/m_phase_pro_bar_png.tres")
|
|
const PhaseBarBgTexture := preload("res://assets/egret/common/m_phase_pro_bg_png.tres")
|
|
const MainMenuHomeTexture := preload("res://assets/egret/menu2/menu2_m_home_png.tres")
|
|
const MainMenuShopTexture := preload("res://assets/egret/menu2/menu2_m_shop_png.tres")
|
|
const MainMenuWarehouseTexture := preload("res://assets/egret/menu2/menu2_m_warehouse_png.tres")
|
|
const MainMenuFriendTexture := preload("res://assets/egret/menu2/menu2_m_rank_png.tres")
|
|
const CommonAtlasPath := "res://assets/egret/common/common.png"
|
|
const FriendAtlasPath := "res://assets/egret/assets/game/friend/friend.png"
|
|
const RankAtlasPath := "res://assets/egret/assets/game/rank/rank.png"
|
|
const BattlePetAtlasPath := "res://assets/egret/assets/game/battlePet/battle_pet.png"
|
|
|
|
|
|
class FakeBattlePetApi:
|
|
extends RefCounted
|
|
|
|
var fetch_count := 0
|
|
|
|
func fetch_list(_token: String) -> Dictionary:
|
|
fetch_count += 1
|
|
return {
|
|
"ok": true,
|
|
"body": {
|
|
"data": {
|
|
"list": [
|
|
{
|
|
"id": 7,
|
|
"pet_config_id": 11001,
|
|
"is_enter_war": 1,
|
|
"feed": 10,
|
|
"level": 1,
|
|
"blood": 100,
|
|
"attack": 10,
|
|
"anti": 5,
|
|
"crit": 1,
|
|
"dodge": 1,
|
|
"hp": 100,
|
|
"max_hp": 100,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
}
|
|
|
|
var _failures: Array[String] = []
|
|
var _land_node: Control
|
|
var _land_nodes: Dictionary = {}
|
|
var _main_menu_action := ""
|
|
var _seed_bag_press: Dictionary = {}
|
|
var _land_action_menu_action := ""
|
|
var _land_action_menu_dismissed := false
|
|
var _persistent_land_data: Dictionary = {}
|
|
|
|
|
|
func _init() -> void:
|
|
_run.call_deferred()
|
|
|
|
|
|
func _run() -> void:
|
|
await process_frame
|
|
await _verify_seed_bag()
|
|
_verify_plant_tip()
|
|
await _verify_persistent_plant_tip()
|
|
_verify_friend_steal_overlay_removed()
|
|
await _verify_land_action_menu_direct_hit()
|
|
_verify_server_status_text()
|
|
await _verify_main_menu_direct_hit()
|
|
_verify_top_menu_single_row()
|
|
_verify_pet_list_item_background_region()
|
|
await _verify_ui_motion_patrol_loop()
|
|
_verify_land_extend_gold_icon_constraints()
|
|
_verify_welfare_removed()
|
|
_verify_market_replaces_world_entry()
|
|
_verify_market_panel_uses_fruit_rows()
|
|
_verify_pet_health_defaults()
|
|
_verify_battle_pet_shop_filters()
|
|
_verify_battle_pet_patrol_rules()
|
|
_verify_battle_pet_panel_hit_passthrough()
|
|
await _verify_battle_pet_replaces_guard_display_data()
|
|
_verify_friend_rank_row_alignment()
|
|
await _verify_friend_visit_mode()
|
|
if not _failures.is_empty():
|
|
for failure in _failures:
|
|
push_error(failure)
|
|
quit(1)
|
|
return
|
|
print("UI regression verification passed.")
|
|
quit()
|
|
|
|
|
|
func _verify_seed_bag() -> void:
|
|
var owner := Control.new()
|
|
owner.name = "SeedBagOwner"
|
|
owner.size = Vector2(640.0, 960.0)
|
|
get_root().add_child(owner)
|
|
|
|
var seed_bag = SeedBagViewScript.new()
|
|
seed_bag.setup({
|
|
"owner": owner,
|
|
"style_label": Callable(self, "_style_label"),
|
|
"get_item_icon_texture": Callable(self, "_get_item_icon_texture"),
|
|
"seed_icon_id": Callable(self, "_seed_icon_id"),
|
|
"textures": {
|
|
"bg": BagBgTexture,
|
|
"line": BagLineTexture,
|
|
},
|
|
"on_item_pressed": Callable(self, "_record_seed_bag_press"),
|
|
})
|
|
seed_bag.open("seed", [{"item_id": 101001, "icon_id": 1031, "num": 1}], [])
|
|
await create_timer(0.25).timeout
|
|
|
|
var panel := seed_bag.panel()
|
|
_expect(panel != null, "seed bag panel should be created")
|
|
if panel != null:
|
|
_expect(_near(panel.position.x, 526.0), "seed bag x should match BagPanelSkin x=526")
|
|
_expect(_near(panel.position.x + panel.size.x, 640.0), "seed bag right edge should be 640")
|
|
_expect(panel.mouse_filter == Control.MOUSE_FILTER_STOP, "seed bag panel should block inside clicks from outside dismiss")
|
|
var scroll := panel.get_node_or_null("SeedScroll") as ScrollContainer
|
|
_expect(scroll != null, "seed bag scroll should exist")
|
|
if scroll != null:
|
|
_expect(scroll.horizontal_scroll_mode == ScrollContainer.SCROLL_MODE_DISABLED, "seed bag horizontal scrollbar should be disabled")
|
|
_expect(scroll.vertical_scroll_mode == ScrollContainer.SCROLL_MODE_SHOW_NEVER, "seed bag vertical scrollbar should be hidden")
|
|
var seed_hit := panel.get_node_or_null("SeedScroll/SeedList/BagItem101001/SeedHit") as Button
|
|
_expect(seed_hit != null, "seed bag item hit should exist")
|
|
if seed_hit != null:
|
|
_seed_bag_press = {}
|
|
seed_hit.pressed.emit()
|
|
await process_frame
|
|
_expect(int(_seed_bag_press.get("item_id", 0)) == 101001, "seed bag item hit should dispatch item id")
|
|
_expect(str(_seed_bag_press.get("mode", "")) == "seed", "seed bag item hit should dispatch seed mode")
|
|
var dismiss := owner.get_node_or_null("SeedBagDismissHit") as Button
|
|
_expect(dismiss != null, "seed bag outside dismiss hit should exist")
|
|
if dismiss != null and panel != null:
|
|
_expect(dismiss.visible, "seed bag outside dismiss should be visible while bag is open")
|
|
_expect(not dismiss.disabled, "seed bag outside dismiss should be enabled while bag is open")
|
|
_expect(dismiss.z_index < panel.z_index, "seed bag outside dismiss should stay below bag panel")
|
|
dismiss.pressed.emit()
|
|
await process_frame
|
|
_expect(not panel.visible, "pressing outside dismiss should close seed bag")
|
|
_expect(not dismiss.visible and dismiss.disabled, "outside dismiss should disable after seed bag closes")
|
|
owner.queue_free()
|
|
|
|
|
|
func _verify_server_status_text() -> void:
|
|
var scene := GameScene.instantiate() as Control
|
|
var text := str(scene.call("_response_status_text", {"game_status": 1009}, "播种失败"))
|
|
_expect(text == I18nScript.t("当前等级不足"), "server status 1009 should use original H5 level limit text")
|
|
scene.queue_free()
|
|
|
|
|
|
func _verify_plant_tip() -> void:
|
|
var owner := Control.new()
|
|
owner.name = "PlantTipOwner"
|
|
owner.size = Vector2(640.0, 960.0)
|
|
get_root().add_child(owner)
|
|
|
|
_land_node = Control.new()
|
|
_land_node.position = Vector2(220.0, 420.0)
|
|
_land_node.size = Vector2(172.0, 102.0)
|
|
_land_nodes = {0: _land_node}
|
|
owner.add_child(_land_node)
|
|
|
|
var plant_tip = PlantTipViewScript.new()
|
|
plant_tip.setup({
|
|
"owner": owner,
|
|
"game_config": GameConfigCatalogScript.new(),
|
|
"get_land_node": Callable(self, "_get_land_node"),
|
|
"style_label": Callable(self, "_style_label"),
|
|
"format_duration": Callable(self, "_format_duration"),
|
|
"phase_names": ["发芽期", "生长期", "成熟期", "成熟期"],
|
|
"textures": {
|
|
"bg": TipBgTexture,
|
|
"clock": PhaseClockTexture,
|
|
"bar": PhaseBarTexture,
|
|
"bar_bg": PhaseBarBgTexture,
|
|
},
|
|
})
|
|
plant_tip.show(0, {
|
|
"crop_id": 1,
|
|
"crop_phase": 0,
|
|
"crop_start_time": int(Time.get_unix_time_from_system()) - 1,
|
|
})
|
|
|
|
var tip := plant_tip.node()
|
|
_expect(tip != null, "plant tip should be created")
|
|
if tip != null:
|
|
var bg := tip.get_node_or_null("Bg") as NinePatchRect
|
|
var bar_bg := tip.get_node_or_null("BarBg") as NinePatchRect
|
|
var bar_fill := tip.get_node_or_null("BarFill") as NinePatchRect
|
|
_expect(bg != null, "plant tip bg should use NinePatchRect")
|
|
_expect(bar_bg != null, "plant tip bar bg should use NinePatchRect")
|
|
_expect(bar_fill != null, "plant tip bar fill should use NinePatchRect")
|
|
if bg != null:
|
|
_expect(bg.patch_margin_left == 24 and bg.patch_margin_top == 20 and bg.patch_margin_right == 25 and bg.patch_margin_bottom == 26, "plant tip bg margins should convert PlantCdTipSkin scale9Grid")
|
|
_expect(_near(tip.size.x, 128.0), "plant tip width should be reduced for compact crop bubble")
|
|
if bar_bg != null:
|
|
_expect(bar_bg.patch_margin_left == 8 and bar_bg.patch_margin_top == 5 and bar_bg.patch_margin_right == 7 and bar_bg.patch_margin_bottom == 5, "plant tip bar bg margins should match CDBarSkin")
|
|
if bar_fill != null:
|
|
_expect(bar_fill.patch_margin_left == 8 and bar_fill.patch_margin_top == 5 and bar_fill.patch_margin_right == 7 and bar_fill.patch_margin_bottom == 6, "plant tip bar fill margins should match CDBarSkin")
|
|
var count_label := tip.get_node_or_null("LabelCount") as Label
|
|
_expect(count_label != null and count_label.text.find("/") >= 0, "plant tip should render current/total count line")
|
|
plant_tip.show(0, {
|
|
"crop_id": 1,
|
|
"crop_phase": 3,
|
|
"crop_start_time": int(Time.get_unix_time_from_system()) - 30,
|
|
})
|
|
var time_label := tip.get_node_or_null("LabelTime") as Label
|
|
_expect(time_label != null and not time_label.visible, "mature plant tip should hide time label")
|
|
_expect(bar_bg != null and not bar_bg.visible, "mature plant tip should hide progress bar background")
|
|
_expect(bar_fill != null and not bar_fill.visible, "mature plant tip should hide progress bar fill")
|
|
_expect(count_label != null and count_label.text.begins_with("count:"), "mature plant tip should use localized count field")
|
|
_expect(count_label != null and count_label.text == "count:30/30", "naturally mature plant tip should fallback to configured output count")
|
|
plant_tip.show(0, {
|
|
"crop_id": 1,
|
|
"crop_phase": 4,
|
|
"has_gather": 1,
|
|
})
|
|
_expect(not plant_tip.is_visible(), "plant tip should be logically hidden after crop has been harvested")
|
|
await create_timer(0.2).timeout
|
|
_expect(not tip.visible, "plant tip should fade out after crop has been harvested")
|
|
owner.queue_free()
|
|
|
|
|
|
func _verify_persistent_plant_tip() -> void:
|
|
var owner := Control.new()
|
|
owner.name = "PersistentPlantTipOwner"
|
|
owner.size = Vector2(640.0, 960.0)
|
|
get_root().add_child(owner)
|
|
|
|
_land_node = Control.new()
|
|
_land_node.name = "Land0"
|
|
_land_node.position = Vector2(220.0, 420.0)
|
|
_land_node.size = Vector2(172.0, 102.0)
|
|
_land_nodes = {0: _land_node}
|
|
owner.add_child(_land_node)
|
|
var second_land_node := Control.new()
|
|
second_land_node.name = "Land1"
|
|
second_land_node.position = Vector2(410.0, 500.0)
|
|
second_land_node.size = Vector2(172.0, 102.0)
|
|
_land_nodes[1] = second_land_node
|
|
owner.add_child(second_land_node)
|
|
|
|
var plant_tip = PlantTipViewScript.new()
|
|
plant_tip.setup({
|
|
"owner": owner,
|
|
"get_land_node": Callable(self, "_get_land_node"),
|
|
"style_label": Callable(self, "_style_label"),
|
|
"format_duration": Callable(self, "_format_duration"),
|
|
"phase_names": ["发芽期", "生长期", "成熟期", "成熟期"],
|
|
"textures": {
|
|
"bg": TipBgTexture,
|
|
"clock": PhaseClockTexture,
|
|
"bar": PhaseBarTexture,
|
|
"bar_bg": PhaseBarBgTexture,
|
|
},
|
|
})
|
|
var land_menu = LandActionMenuScript.new()
|
|
land_menu.setup({
|
|
"owner": owner,
|
|
"get_land_node": Callable(self, "_get_land_node"),
|
|
"on_action": Callable(self, "_record_land_action_menu_action"),
|
|
"on_dismiss": Callable(self, "_record_land_action_menu_dismiss"),
|
|
"button_size": Vector2(92.0, 88.0),
|
|
})
|
|
var coordinator = FarmInteractionCoordinatorScript.new()
|
|
coordinator.setup({
|
|
"owner": owner,
|
|
"land_action_menu": land_menu,
|
|
"plant_tip_view": plant_tip,
|
|
"constants": {
|
|
"land_count": 2,
|
|
"plant_tip_breathe_seconds": 0.1,
|
|
},
|
|
"callbacks": {
|
|
"get_land_node": Callable(self, "_get_land_node"),
|
|
"land_data": Callable(self, "_persistent_land_data_for"),
|
|
"land_actions": Callable(self, "_persistent_land_actions"),
|
|
"is_operation_in_progress": Callable(self, "_false"),
|
|
"is_visiting_friend": Callable(self, "_false"),
|
|
},
|
|
})
|
|
var growing_land := {
|
|
"position": 0,
|
|
"crop_id": 1,
|
|
"crop_phase": 1,
|
|
"crop_start_time": int(Time.get_unix_time_from_system()) - 1,
|
|
"has_gather": 0,
|
|
}
|
|
var second_growing_land := growing_land.duplicate(true)
|
|
second_growing_land["position"] = 1
|
|
_persistent_land_data = growing_land
|
|
coordinator.tick({0: growing_land, 1: second_growing_land})
|
|
await process_frame
|
|
_expect(plant_tip.is_visible(), "persistent plant tip should show above growing crop")
|
|
_expect(owner.get_node_or_null("PlantCdTip0") != null and owner.get_node("PlantCdTip0").visible, "persistent plant tip should show for first crop")
|
|
_expect(owner.get_node_or_null("PlantCdTip1") != null and owner.get_node("PlantCdTip1").visible, "persistent plant tip should show for second crop")
|
|
await create_timer(0.25).timeout
|
|
coordinator.tick({0: growing_land, 1: second_growing_land})
|
|
_expect(not plant_tip.is_visible(), "persistent plant tip should hide during breathe cycle")
|
|
coordinator.on_land_pressed(0)
|
|
await create_timer(0.25).timeout
|
|
_expect(land_menu.is_visible(), "land action menu should open while plant tip breathe cycle is hidden")
|
|
land_menu.hide()
|
|
await create_timer(0.25).timeout
|
|
coordinator.tick({0: growing_land, 1: second_growing_land})
|
|
_expect(plant_tip.is_visible(), "persistent plant tip should show again during breathe cycle")
|
|
land_menu.show(0, ["uproot", "manure"])
|
|
await process_frame
|
|
coordinator.tick({0: growing_land, 1: second_growing_land})
|
|
_expect(not plant_tip.is_visible(), "persistent plant tip should hide while land action menu is visible")
|
|
land_menu.hide()
|
|
coordinator.tick({0: growing_land, 1: second_growing_land})
|
|
await process_frame
|
|
_expect(plant_tip.is_visible(), "persistent plant tip should restore after land action menu closes")
|
|
var tip_node := plant_tip.node()
|
|
var old_tip_position := tip_node.position if tip_node != null else Vector2.ZERO
|
|
_land_node.position += Vector2(42.0, 58.0)
|
|
coordinator.tick({0: growing_land, 1: second_growing_land})
|
|
await process_frame
|
|
var moved_tip_position := tip_node.position if tip_node != null else Vector2.ZERO
|
|
_expect(moved_tip_position.distance_to(old_tip_position) > 40.0, "persistent plant tip should follow moved land position")
|
|
var harvested_land := growing_land.duplicate(true)
|
|
harvested_land["has_gather"] = 1
|
|
harvested_land["crop_phase"] = 4
|
|
coordinator.tick({0: harvested_land})
|
|
_expect(not plant_tip.is_visible(), "persistent plant tip should hide when no crop remains visible")
|
|
var empty_land := growing_land.duplicate(true)
|
|
empty_land["crop_id"] = 0
|
|
empty_land["has_gather"] = 0
|
|
coordinator.tick({0: empty_land})
|
|
_expect(not plant_tip.is_visible(), "persistent plant tip should hide for empty land")
|
|
owner.queue_free()
|
|
|
|
|
|
func _verify_friend_steal_overlay_removed() -> void:
|
|
var owner := Control.new()
|
|
owner.name = "FriendStealOverlayOwner"
|
|
owner.size = Vector2(640.0, 960.0)
|
|
get_root().add_child(owner)
|
|
|
|
var game_view := Control.new()
|
|
game_view.name = "GameView"
|
|
game_view.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
owner.add_child(game_view)
|
|
|
|
_land_node = TextureRect.new()
|
|
_land_node.name = "Land0"
|
|
_land_node.position = Vector2(240.0, 420.0)
|
|
_land_node.size = Vector2(172.0, 102.0)
|
|
game_view.add_child(_land_node)
|
|
|
|
var image := Image.create(8, 8, false, Image.FORMAT_RGBA8)
|
|
image.fill(Color.WHITE)
|
|
var texture := ImageTexture.create_from_image(image)
|
|
var land_grid = LandGridViewScript.new()
|
|
land_grid.setup({
|
|
"owner": owner,
|
|
"game_view": game_view,
|
|
"get_land_node": Callable(self, "_get_land_node"),
|
|
"land_count": 1,
|
|
"textures": {
|
|
"level_0": texture,
|
|
"level_1": texture,
|
|
"level_2": texture,
|
|
"level_3": texture,
|
|
"locked": texture,
|
|
},
|
|
})
|
|
land_grid.render({0: {
|
|
"position": 0,
|
|
"level": 0,
|
|
"crop_id": 1,
|
|
"crop_phase": 3,
|
|
"is_ripe": 1,
|
|
"crop_num": 30,
|
|
"crop_min_num": 15,
|
|
"has_gather": 0,
|
|
"interaction_type_list": [0],
|
|
}})
|
|
_expect(game_view.get_node_or_null("StealIconLayer") == null, "friend visit should not render the extra steal overlay icon")
|
|
owner.queue_free()
|
|
|
|
|
|
func _verify_land_action_menu_direct_hit() -> void:
|
|
var owner := Control.new()
|
|
owner.name = "LandActionMenuOwner"
|
|
owner.size = Vector2(640.0, 960.0)
|
|
get_root().add_child(owner)
|
|
|
|
_land_node = Control.new()
|
|
_land_node.name = "Land0"
|
|
_land_node.position = Vector2(340.0, 420.0)
|
|
_land_node.size = Vector2(172.0, 102.0)
|
|
owner.add_child(_land_node)
|
|
|
|
var menu = LandActionMenuScript.new()
|
|
menu.setup({
|
|
"owner": owner,
|
|
"get_land_node": Callable(self, "_get_land_node"),
|
|
"on_action": Callable(self, "_record_land_action_menu_action"),
|
|
"on_dismiss": Callable(self, "_record_land_action_menu_dismiss"),
|
|
"button_size": Vector2(92.0, 88.0),
|
|
})
|
|
menu.show(0, ["uproot", "manure"])
|
|
await create_timer(0.25).timeout
|
|
var layer := menu.layer()
|
|
_expect(layer != null and layer.visible, "land action menu should be visible")
|
|
if layer != null:
|
|
var manure := layer.get_node_or_null("MenuManure") as TextureButton
|
|
_expect(manure != null, "land action menu manure button should exist")
|
|
if manure != null:
|
|
_land_action_menu_action = ""
|
|
_expect(menu.handle_direct_hit(manure.get_global_rect().get_center()), "land action menu direct hit should consume button click")
|
|
_expect(_land_action_menu_action == "manure", "land action menu direct hit should dispatch manure before scene hits")
|
|
menu.show(0, ["steal"])
|
|
await create_timer(0.25).timeout
|
|
if layer != null:
|
|
var steal := layer.get_node_or_null("MenuSteal") as TextureButton
|
|
_expect(steal != null, "land action menu steal button should exist")
|
|
if steal != null:
|
|
_land_action_menu_action = ""
|
|
_expect(menu.handle_direct_hit(steal.get_global_rect().get_center()), "land action menu direct hit should consume steal click")
|
|
_expect(_land_action_menu_action == "steal", "land action menu direct hit should dispatch steal action")
|
|
menu.show(0, ["uproot"])
|
|
await create_timer(0.25).timeout
|
|
_land_action_menu_dismissed = false
|
|
_expect(menu.handle_direct_hit(Vector2(20.0, 20.0)), "land action menu outside click should be consumed")
|
|
_expect(_land_action_menu_dismissed, "land action menu outside click should dismiss menu")
|
|
owner.queue_free()
|
|
|
|
|
|
func _verify_welfare_removed() -> void:
|
|
var scene := GameScene.instantiate() as Control
|
|
_expect(scene.get_node_or_null("LeftMiddleMenu/Fuli") == null, "welfare menu button should be removed")
|
|
_expect(scene.get_node_or_null("LeftMiddleMenu/Notice") != null, "notice menu should remain")
|
|
_expect(scene.get_node_or_null("LeftMiddleMenu/Sign") != null, "sign menu should remain")
|
|
_expect(scene.get_node_or_null("LeftMiddleMenu/RewardGift") != null, "online gift menu should remain")
|
|
scene.queue_free()
|
|
|
|
|
|
func _verify_market_replaces_world_entry() -> void:
|
|
var scene := GameScene.instantiate() as Control
|
|
var market_art := scene.get_node_or_null("LeftMenu/MarketButtonArt") as TextureRect
|
|
_expect(market_art != null, "market menu button should replace world entry")
|
|
if market_art != null:
|
|
_expect(_near(market_art.size.x, 118.0) and _near(market_art.size.y, 118.0), "market menu icon should have fixed 118x118 visual size")
|
|
_expect(scene.get_node_or_null("LeftMenu/WorldButtonArt") == null, "world menu button should be removed")
|
|
scene.queue_free()
|
|
|
|
|
|
func _verify_market_panel_uses_fruit_rows() -> void:
|
|
var owner := Control.new()
|
|
owner.name = "MarketPanelOwner"
|
|
owner.size = Vector2(640.0, 960.0)
|
|
get_root().add_child(owner)
|
|
var atlas_cache = AtlasTextureCacheScript.new()
|
|
var texture_provider = TextureProviderScript.new()
|
|
texture_provider.setup(atlas_cache, {"common": CommonAtlasPath})
|
|
var ui_builder = UiBuilderScript.new()
|
|
ui_builder.setup({
|
|
"owner": owner,
|
|
"panel_screen_size": Vector2(640.0, 960.0),
|
|
"texture_provider": texture_provider,
|
|
"close_modal_callback": Callable(self, "_noop"),
|
|
"get_item_icon_id": Callable(self, "_seed_icon_id"),
|
|
})
|
|
var panel = MarketPanelScript.new()
|
|
panel.setup({
|
|
"owner": owner,
|
|
"ui_builder": ui_builder,
|
|
"texture_provider": texture_provider,
|
|
"game_config": GameConfigCatalogScript.new(),
|
|
"callbacks": {
|
|
"token": Callable(self, "_token"),
|
|
"player_data": Callable(self, "_market_player_data"),
|
|
"warehouse_items": Callable(self, "_market_warehouse_items"),
|
|
"owned_amount": Callable(self, "_market_owned_amount"),
|
|
"render_player": Callable(self, "_noop"),
|
|
"refresh_store_house": Callable(self, "_noop_async"),
|
|
"close_modal_panel": Callable(self, "_noop"),
|
|
"open_modal_panel": Callable(self, "_test_open_modal"),
|
|
"is_operation_in_progress": Callable(self, "_false"),
|
|
"set_operation_in_progress": Callable(self, "_noop"),
|
|
"show_toast": Callable(self, "_noop"),
|
|
"response_data": Callable(self, "_response_data_test"),
|
|
"response_status_text": Callable(self, "_response_status_text_test"),
|
|
"decrease_player_currency": Callable(self, "_noop"),
|
|
"decrease_warehouse_item": Callable(self, "_noop"),
|
|
},
|
|
})
|
|
var rows: Array = panel.call("_normalize_trade_rows", [
|
|
{"id": 1, "itemid": 102001, "itemname": "金桔", "nowOpen": 6, "HighestPrice": 7, "LowestPrice": 3, "successNum": 2},
|
|
{"id": 15, "itemid": 200350, "itemname": "猪肉", "nowOpen": 20},
|
|
])
|
|
_expect(rows is Array and rows.size() == 1, "market panel should only list crop fruit rows")
|
|
if rows is Array and rows.size() > 0:
|
|
_expect(int(rows[0].get("item_id", 0)) == 102001, "market fruit row should keep fruit item id")
|
|
_expect(int(rows[0].get("price", 0)) == 6, "market fruit row should use realtime nowOpen price")
|
|
owner.queue_free()
|
|
|
|
|
|
func _verify_pet_health_defaults() -> void:
|
|
var panel = PetPanelScript.new()
|
|
var default_pet = panel.call("_normalize_pet_row", {"id": 1, "blood": 360})
|
|
_expect(default_pet is Dictionary and int(default_pet.get("hp", 0)) == 100, "pet hp should default to 100")
|
|
_expect(default_pet is Dictionary and int(default_pet.get("max_hp", 0)) == 100, "pet max hp should default to 100")
|
|
_expect(str(panel.call("_pet_health_text", default_pet)) == "100/100", "pet health text should default to 100/100")
|
|
var custom_pet = panel.call("_normalize_pet_row", {"id": 2, "hp": 35, "max_hp": 80})
|
|
_expect(custom_pet is Dictionary and str(panel.call("_pet_health_text", custom_pet)) == "35/80", "pet health text should use explicit hp and max_hp")
|
|
|
|
|
|
func _verify_battle_pet_shop_filters() -> void:
|
|
var panel = PetPanelScript.new()
|
|
panel.setup({
|
|
"game_config": GameConfigCatalogScript.new(),
|
|
"dog_food_item_ids": [103005, 103006, 103007],
|
|
})
|
|
var items: Array = panel.call("_pet_shop_items")
|
|
var item_ids := {}
|
|
for shop_item in items:
|
|
if not shop_item is Dictionary:
|
|
continue
|
|
var item_config: Dictionary = shop_item.get("item", {})
|
|
item_ids[int(shop_item.get("item_id", item_config.get("id", 0)))] = true
|
|
_expect(item_ids.has(201017), "battle pet shop should include battle pet eggs")
|
|
_expect(item_ids.has(201021), "battle pet shop should include higher battle pet eggs")
|
|
_expect(item_ids.has(103005), "battle pet shop should include pet food")
|
|
_expect(not item_ids.has(106002), "battle pet shop should exclude ornamental guard pet items")
|
|
|
|
|
|
func _verify_battle_pet_patrol_rules() -> void:
|
|
var panel = PetPanelScript.new()
|
|
_expect(bool(panel.call("_pet_can_patrol", {"is_enter_war": 1, "feed": 1})), "battle pet should patrol when it is in war and has feed time")
|
|
_expect(not bool(panel.call("_pet_can_patrol", {"is_enter_war": 1, "feed": 0})), "battle pet should not patrol without feed time")
|
|
_expect(not bool(panel.call("_pet_can_patrol", {"is_enter_war": 0, "feed": 10})), "battle pet should not patrol when not in war")
|
|
|
|
|
|
func _verify_battle_pet_panel_hit_passthrough() -> void:
|
|
var owner := Control.new()
|
|
owner.name = "BattlePetPanelHitOwner"
|
|
owner.size = Vector2(640.0, 960.0)
|
|
get_root().add_child(owner)
|
|
var atlas_cache = AtlasTextureCacheScript.new()
|
|
var texture_provider = TextureProviderScript.new()
|
|
texture_provider.setup(atlas_cache, {
|
|
"common": CommonAtlasPath,
|
|
"battle_pet": BattlePetAtlasPath,
|
|
})
|
|
var ui_builder = UiBuilderScript.new()
|
|
ui_builder.setup({
|
|
"owner": owner,
|
|
"panel_screen_size": Vector2(640.0, 960.0),
|
|
"texture_provider": texture_provider,
|
|
"close_modal_callback": Callable(self, "_noop"),
|
|
"get_item_icon_texture": Callable(self, "_get_item_icon_texture"),
|
|
})
|
|
var panel = PetPanelScript.new()
|
|
panel.setup({
|
|
"owner": owner,
|
|
"ui_builder": ui_builder,
|
|
"texture_provider": texture_provider,
|
|
"game_config": GameConfigCatalogScript.new(),
|
|
"pet_api": FakeBattlePetApi.new(),
|
|
"callbacks": {
|
|
"token": Callable(self, "_token"),
|
|
"open_modal_panel": Callable(self, "_test_open_modal"),
|
|
"close_modal": Callable(self, "_noop"),
|
|
"refresh_store_house": Callable(self, "_noop_async"),
|
|
"response_data": Callable(self, "_response_data_test"),
|
|
"style_label": Callable(self, "_style_label"),
|
|
},
|
|
})
|
|
await panel.open()
|
|
var opened_panel := get_root().get_node_or_null("PetPanel") as Control
|
|
var content := opened_panel.get_node_or_null("PetList") as Control if opened_panel != null else null
|
|
_expect(content != null and content.mouse_filter == Control.MOUSE_FILTER_IGNORE, "battle pet content layer should not block tab or leave clicks")
|
|
if opened_panel != null:
|
|
opened_panel.queue_free()
|
|
owner.queue_free()
|
|
|
|
|
|
func _verify_battle_pet_replaces_guard_display_data() -> void:
|
|
var owner := Control.new()
|
|
owner.name = "BattlePetDisplayOwner"
|
|
owner.size = Vector2(640.0, 960.0)
|
|
get_root().add_child(owner)
|
|
var display := Control.new()
|
|
display.name = "BattlePetDisplay"
|
|
owner.add_child(display)
|
|
|
|
var atlas_cache = AtlasTextureCacheScript.new()
|
|
var texture_provider = TextureProviderScript.new()
|
|
texture_provider.setup(atlas_cache, {
|
|
"common": CommonAtlasPath,
|
|
"battle_pet": BattlePetAtlasPath,
|
|
})
|
|
var ui_builder = UiBuilderScript.new()
|
|
ui_builder.setup({
|
|
"owner": owner,
|
|
"texture_provider": texture_provider,
|
|
"get_item_icon_texture": Callable(self, "_get_item_icon_texture"),
|
|
})
|
|
var pet_api := FakeBattlePetApi.new()
|
|
var panel = PetPanelScript.new()
|
|
panel.setup({
|
|
"owner": owner,
|
|
"ui_builder": ui_builder,
|
|
"texture_provider": texture_provider,
|
|
"game_config": GameConfigCatalogScript.new(),
|
|
"pet_api": pet_api,
|
|
"callbacks": {
|
|
"token": Callable(self, "_token"),
|
|
"player_data": Callable(self, "_battle_pet_guard_player_data"),
|
|
"response_data": Callable(self, "_response_data_test"),
|
|
"style_label": Callable(self, "_style_label"),
|
|
},
|
|
})
|
|
panel.set_display(display)
|
|
await panel.refresh_display()
|
|
|
|
_expect(pet_api.fetch_count == 1, "battle pet display should fetch pet/get-list even when guard pet item is active")
|
|
_expect(display.visible, "battle pet display should stay visible after loading battle pet rows")
|
|
_expect(display.get_node_or_null("BattlePetMovieClip") != null, "battle pet display should render battle pet clip")
|
|
_expect(display.get_node_or_null("GuardDogMovieClip") == null, "battle pet display should not render guard pet clip")
|
|
owner.queue_free()
|
|
|
|
|
|
func _verify_friend_rank_row_alignment() -> void:
|
|
var owner := Control.new()
|
|
owner.name = "FriendPanelOwner"
|
|
owner.size = Vector2(640.0, 960.0)
|
|
get_root().add_child(owner)
|
|
|
|
var atlas_cache = AtlasTextureCacheScript.new()
|
|
var texture_provider = TextureProviderScript.new()
|
|
texture_provider.setup(atlas_cache, {
|
|
"common": CommonAtlasPath,
|
|
"friend": FriendAtlasPath,
|
|
"rank": RankAtlasPath,
|
|
})
|
|
var ui_builder = UiBuilderScript.new()
|
|
ui_builder.setup({
|
|
"owner": owner,
|
|
"panel_screen_size": Vector2(640.0, 960.0),
|
|
"texture_provider": texture_provider,
|
|
"close_modal_callback": Callable(self, "_noop"),
|
|
})
|
|
var panel = FriendPanelScript.new()
|
|
panel.setup({
|
|
"ui_builder": ui_builder,
|
|
"texture_provider": texture_provider,
|
|
"page_size": 7,
|
|
})
|
|
var root: Control = panel.create(0)
|
|
_expect(root.get_node_or_null("FriendAdd") == null, "friend add icon should be removed")
|
|
_expect(root.get_node_or_null("FriendReq") == null, "friend request icon should be removed")
|
|
var row: Control = panel.call("_create_row", {
|
|
"ranking": 1,
|
|
"nickname": "Jessica",
|
|
"level": 60,
|
|
"gold": 4294970000,
|
|
"user_id": 256,
|
|
})
|
|
var value := row.get_node_or_null("Value") as Label
|
|
var home := row.get_node_or_null("Home") as Control
|
|
var hand := row.get_node_or_null("Hand") as Control
|
|
_expect(value != null and _near(value.position.x, 200.0), "friend rank value column should match FriendRankItemSkin horizontalCenter=31.5")
|
|
_expect(home != null and _near(home.position.x, 308.0), "friend rank home button should match FriendRankItemSkin horizontalCenter=114.5")
|
|
_expect(hand == null, "friend rank hand button column should be removed")
|
|
_expect(_near(row.size.x, 450.0), "friend rank row should fill the list after removing hand button")
|
|
row.queue_free()
|
|
root.queue_free()
|
|
owner.queue_free()
|
|
|
|
|
|
func _verify_friend_visit_mode() -> void:
|
|
var scene := GameScene.instantiate() as Control
|
|
scene.setup({
|
|
"id": 600,
|
|
"user_id": 600,
|
|
"nickname": "1231",
|
|
"level": 14,
|
|
"gold": 100,
|
|
"gem": 1,
|
|
"score": 1998,
|
|
"exp": 0,
|
|
"farm_level": 6,
|
|
"bg_image_id": 104004,
|
|
"landList": [{
|
|
"position": 0,
|
|
"level": 0,
|
|
"crop_id": 0
|
|
}],
|
|
})
|
|
get_root().add_child(scene)
|
|
await process_frame
|
|
var friend_land := {
|
|
"position": 0,
|
|
"level": 0,
|
|
"crop_id": 1,
|
|
"crop_start_time": int(Time.get_unix_time_from_system()) - 30,
|
|
"crop_gather_time": int(Time.get_unix_time_from_system()) - 1,
|
|
"is_ripe": 1,
|
|
"crop_num": 30,
|
|
"crop_min_num": 15,
|
|
"has_dry": 0,
|
|
"has_bug": 0,
|
|
"has_grass": 0,
|
|
"has_gather": 0,
|
|
"crop_phase": 3,
|
|
"interaction_type_list": [0],
|
|
}
|
|
scene.call("_enter_friend_farm", {
|
|
"id": 256,
|
|
"user_id": 256,
|
|
"nickname": "aaaa",
|
|
"level": 60,
|
|
"gold": 200,
|
|
"gem": 2,
|
|
"score": 60,
|
|
"exp": 0,
|
|
"farm_level": 3,
|
|
"bg_image_id": 104004,
|
|
"use_dog_item_id": 0,
|
|
"landList": [friend_land],
|
|
})
|
|
await process_frame
|
|
_expect(scene.get_node("BackHome").visible, "friend visit should show back home button")
|
|
_expect(not scene.get_node("MainMenu").visible, "friend visit should hide home menu")
|
|
_expect(not scene.get_node("LeftMenu").visible, "friend visit should hide market menu")
|
|
_expect(not scene.get_node("LeftMiddleMenu").visible, "friend visit should hide left middle menu")
|
|
_expect(not scene.get_node("TopLayer/TopButtonGroup").visible, "friend visit should hide top menu buttons")
|
|
_expect(not scene.get_node("TopLayer/TopHide").visible, "friend visit should hide top toggle")
|
|
var add_land := scene.get_node_or_null("GameView/AddLand") as Control
|
|
if add_land == null:
|
|
add_land = scene.get_node_or_null("FarmSceneRoot/GameView/AddLand") as Control
|
|
_expect(add_land != null and not add_land.visible, "friend visit should hide add-land indicator")
|
|
var scene_hit_layer := scene.get_node_or_null("SceneHitLayer") as Control
|
|
var back_hit: Button = null
|
|
var notice_hit: Button = null
|
|
if scene_hit_layer != null:
|
|
back_hit = scene_hit_layer.get_node_or_null("BackHomeHit") as Button
|
|
notice_hit = scene_hit_layer.get_node_or_null("NoticeHit") as Button
|
|
_expect(back_hit != null and back_hit.visible and not back_hit.disabled, "friend visit should enable back home hit")
|
|
_expect(notice_hit != null and not notice_hit.visible and notice_hit.disabled, "friend visit should disable own menu hits")
|
|
var farm_hit_layer := scene.get_node_or_null("FarmSceneRoot/FarmHitLayer") as Control
|
|
_expect(farm_hit_layer != null and not farm_hit_layer.visible, "friend visit should disable building farm hits")
|
|
var actions = scene.call("_get_land_actions", friend_land)
|
|
var action_array := actions as Array
|
|
_expect(action_array != null and action_array.has("steal"), "friend ripe land should expose steal action")
|
|
var protected_land := friend_land.duplicate(true)
|
|
protected_land["crop_num"] = 15
|
|
var protected_actions = scene.call("_get_land_actions", protected_land) as Array
|
|
_expect(protected_actions != null and not protected_actions.has("steal"), "friend land at 50 percent protected output should not expose steal action")
|
|
scene.call("_back_to_own_farm")
|
|
await process_frame
|
|
_expect(not scene.get_node("BackHome").visible, "back to own farm should hide back home button")
|
|
_expect(scene.get_node("MainMenu").visible, "back to own farm should restore home menu")
|
|
_expect(scene.get_node("LeftMenu").visible, "back to own farm should restore market menu")
|
|
_expect(scene.get_node("LeftMiddleMenu").visible, "back to own farm should restore left middle menu")
|
|
scene.queue_free()
|
|
|
|
|
|
func _verify_top_menu_single_row() -> void:
|
|
var scene := GameScene.instantiate() as Control
|
|
var back_bar := scene.get_node_or_null("TopLayer/TopButtonGroup/TopPanelBack") as TextureRect
|
|
var front_bar := scene.get_node_or_null("TopLayer/TopButtonGroup/TopPanelFront") as TextureRect
|
|
var lottery := scene.get_node_or_null("TopLayer/TopButtonGroup/TopLottery") as TextureRect
|
|
var gift := scene.get_node_or_null("TopLayer/TopButtonGroup/TopPayGift") as TextureRect
|
|
var landup := scene.get_node_or_null("TopLayer/TopButtonGroup/TopLandup") as TextureRect
|
|
_expect(front_bar != null and front_bar.visible, "top menu front bar should remain visible")
|
|
_expect(back_bar != null and not back_bar.visible, "top menu back bar should be hidden for single row layout")
|
|
_expect(lottery != null, "top menu lottery item should exist")
|
|
if lottery != null and gift != null and landup != null:
|
|
_expect(_near(lottery.position.y, 101.0), "top menu lottery should be moved to the first row")
|
|
_expect(lottery.position.y <= gift.position.y + 1.0, "top menu lottery should align with pack row")
|
|
_expect(lottery.position.y <= landup.position.y + 1.0, "top menu lottery should align with land up row")
|
|
scene.queue_free()
|
|
|
|
|
|
func _verify_pet_list_item_background_region() -> void:
|
|
var region := TextureRegionCatalogScript.region_for(TextureRegionCatalogScript.COMMON_ATLAS_PATH, "shop_itembg")
|
|
_expect(_near(region.position.x, 262.0), "pet list item shop_itembg x should match common.json")
|
|
_expect(_near(region.position.y, 303.0), "pet list item shop_itembg y should match common.json")
|
|
_expect(_near(region.size.x, 179.0), "pet list item shop_itembg width should match common.json")
|
|
_expect(_near(region.size.y, 110.0), "pet list item shop_itembg height should match common.json")
|
|
|
|
|
|
func _verify_ui_motion_patrol_loop() -> void:
|
|
var owner := Control.new()
|
|
owner.name = "PatrolMotionOwner"
|
|
get_root().add_child(owner)
|
|
var target := Control.new()
|
|
target.name = "PatrolTarget"
|
|
owner.add_child(target)
|
|
var callbacks: Array[String] = []
|
|
var tween := UiMotionScript.play_patrol_loop(owner, target, Vector2(1.0, 2.0), Vector2(3.0, 4.0), {
|
|
"forward_duration": 0.01,
|
|
"back_duration": 0.01,
|
|
"idle_delay": 0.01,
|
|
"before_forward": func() -> void: callbacks.append("forward"),
|
|
"before_back": func() -> void: callbacks.append("back"),
|
|
"before_idle_tail": func() -> void: callbacks.append("tail"),
|
|
"before_idle": func() -> void: callbacks.append("idle"),
|
|
"meta_name": "patrol_tween",
|
|
})
|
|
_expect(tween != null, "patrol motion should create a tween")
|
|
_expect(target.has_meta("patrol_tween"), "patrol motion should store tween on target")
|
|
await create_timer(0.08).timeout
|
|
_expect(callbacks.has("forward"), "patrol motion should call forward callback")
|
|
_expect(callbacks.has("back"), "patrol motion should call back callback")
|
|
_expect(callbacks.has("tail"), "patrol motion should call idle-tail callback")
|
|
_expect(callbacks.has("idle"), "patrol motion should call idle callback")
|
|
owner.queue_free()
|
|
|
|
|
|
func _verify_land_extend_gold_icon_constraints() -> void:
|
|
var owner := Control.new()
|
|
owner.name = "LandExtendOwner"
|
|
owner.size = Vector2(640.0, 960.0)
|
|
get_root().add_child(owner)
|
|
|
|
var atlas_cache = AtlasTextureCacheScript.new()
|
|
var texture_provider = TextureProviderScript.new()
|
|
texture_provider.setup(atlas_cache, {
|
|
"common": CommonAtlasPath,
|
|
"landup": TextureRegionCatalogScript.LANDUP_ATLAS_PATH,
|
|
})
|
|
var ui_builder = UiBuilderScript.new()
|
|
ui_builder.setup({
|
|
"owner": owner,
|
|
"panel_screen_size": Vector2(640.0, 960.0),
|
|
"texture_provider": texture_provider,
|
|
"close_modal_callback": Callable(self, "_noop"),
|
|
})
|
|
var panel_module = BuildingPanelScript.new()
|
|
panel_module.setup({
|
|
"ui_builder": ui_builder,
|
|
"texture_provider": texture_provider,
|
|
"game_config": GameConfigCatalogScript.new(),
|
|
"panel_screen_size": Vector2(640.0, 960.0),
|
|
"land_count": 20,
|
|
"callbacks": {
|
|
"player_data": Callable(self, "_land_extend_player_data"),
|
|
"lands_by_position": Callable(self, "_land_extend_lands_by_position"),
|
|
"close_modal_panel": Callable(self, "_noop"),
|
|
"set_texture_button_enabled": Callable(self, "_set_texture_button_enabled"),
|
|
"is_operation_in_progress": Callable(self, "_false"),
|
|
},
|
|
})
|
|
var panel: Control = panel_module.create_land_extend()
|
|
_expect(panel != null, "land extend panel should be created")
|
|
if panel != null:
|
|
var gold_icon := panel.get_node_or_null("GoldIcon") as TextureRect
|
|
_expect(gold_icon != null, "land extend gold icon should exist")
|
|
if gold_icon != null:
|
|
_expect(_near(gold_icon.size.x, 35.0) and _near(gold_icon.size.y, 35.0), "land extend gold icon should stay 35x35, got %s" % gold_icon.size)
|
|
_expect(gold_icon.stretch_mode == TextureRect.STRETCH_KEEP_ASPECT_CENTERED, "land extend gold icon should constrain replacement gold bean")
|
|
panel.queue_free()
|
|
owner.queue_free()
|
|
|
|
|
|
func _verify_main_menu_direct_hit() -> void:
|
|
var owner := Control.new()
|
|
owner.name = "MainMenuOwner"
|
|
owner.size = Vector2(640.0, 960.0)
|
|
get_root().add_child(owner)
|
|
|
|
var menu := Control.new()
|
|
menu.name = "MainMenu"
|
|
menu.position = Vector2(451.0, 771.0)
|
|
menu.size = Vector2(189.0, 189.0)
|
|
owner.add_child(menu)
|
|
|
|
var home_art := TextureRect.new()
|
|
home_art.name = "HomeButtonArt"
|
|
home_art.position = Vector2(24.0, 34.0)
|
|
home_art.size = Vector2(155.0, 145.0)
|
|
menu.add_child(home_art)
|
|
|
|
_main_menu_action = ""
|
|
var controller = MainMenuControllerScript.new()
|
|
controller.setup(
|
|
owner,
|
|
menu,
|
|
home_art,
|
|
MainMenuHomeTexture,
|
|
{
|
|
"shop": MainMenuShopTexture,
|
|
"warehouse": MainMenuWarehouseTexture,
|
|
"friend": MainMenuFriendTexture,
|
|
},
|
|
Callable(self, "_style_menu_title"),
|
|
Callable(),
|
|
Callable(self, "_record_main_menu_action")
|
|
)
|
|
|
|
var home_point := home_art.get_global_rect().get_center()
|
|
_expect(controller.handle_direct_hit(home_point), "home menu direct hit should consume home button")
|
|
_expect(controller.is_open(), "home menu direct hit should open menu")
|
|
await create_timer(0.3).timeout
|
|
|
|
var shop_button := menu.get_node_or_null("MainMenuShop") as TextureButton
|
|
_expect(shop_button != null, "home menu shop item should exist")
|
|
if shop_button != null:
|
|
_expect(controller.handle_direct_hit(shop_button.get_global_rect().get_center()), "home menu direct hit should consume menu item")
|
|
_expect(_main_menu_action == "shop", "home menu direct hit should dispatch menu item action")
|
|
await create_timer(0.4).timeout
|
|
|
|
_main_menu_action = ""
|
|
_expect(controller.handle_direct_hit(home_point), "home menu direct hit should reopen home menu")
|
|
_expect(controller.is_open(), "home menu should be open before outside hit")
|
|
_expect(controller.handle_direct_hit(Vector2(280.0, 500.0)), "open home menu should consume outside hit")
|
|
_expect(_main_menu_action.is_empty(), "outside hit should only close home menu, not dispatch action")
|
|
owner.queue_free()
|
|
|
|
|
|
func _style_label(_label: Label, _size: int, _color: Color, _stroke := 0, _stroke_color := Color.TRANSPARENT) -> void:
|
|
pass
|
|
|
|
|
|
func _style_menu_title(_label: Label, _size: int) -> void:
|
|
pass
|
|
|
|
|
|
func _record_main_menu_action(action: String) -> void:
|
|
_main_menu_action = action
|
|
|
|
|
|
func _record_seed_bag_press(item: Dictionary, mode: String) -> void:
|
|
_seed_bag_press = item.duplicate(true)
|
|
_seed_bag_press["mode"] = mode
|
|
|
|
|
|
func _record_land_action_menu_action(action: String) -> void:
|
|
_land_action_menu_action = action
|
|
|
|
|
|
func _record_land_action_menu_dismiss() -> void:
|
|
_land_action_menu_dismissed = true
|
|
|
|
|
|
func _get_item_icon_texture(_icon_id: int) -> Texture2D:
|
|
return PlantIconTexture
|
|
|
|
|
|
func _seed_icon_id(_item_id: int) -> int:
|
|
return 1031
|
|
|
|
|
|
func _get_land_node(position: int) -> Control:
|
|
if _land_nodes.has(position):
|
|
var candidate = _land_nodes.get(position)
|
|
if is_instance_valid(candidate):
|
|
return candidate as Control
|
|
_land_nodes.erase(position)
|
|
return _land_node
|
|
|
|
|
|
func _persistent_land_data_for(position: int) -> Dictionary:
|
|
if position == 1:
|
|
var data := _persistent_land_data.duplicate(true)
|
|
data["position"] = 1
|
|
return data
|
|
if position != 0:
|
|
return {}
|
|
return _persistent_land_data
|
|
|
|
|
|
func _persistent_land_actions(_land_data: Dictionary) -> Array[String]:
|
|
return ["uproot", "manure"]
|
|
|
|
|
|
func _format_duration(seconds: int) -> String:
|
|
return "%s秒" % seconds
|
|
|
|
|
|
func _land_extend_player_data() -> Dictionary:
|
|
return {
|
|
"level": 17,
|
|
"gold": 4294940,
|
|
}
|
|
|
|
|
|
func _land_extend_lands_by_position() -> Dictionary:
|
|
var lands := {}
|
|
for index in range(17):
|
|
lands[index] = {
|
|
"position": index,
|
|
"level": 0,
|
|
"crop_id": 0,
|
|
}
|
|
return lands
|
|
|
|
|
|
func _market_player_data() -> Dictionary:
|
|
return {"gold": 1000}
|
|
|
|
|
|
func _battle_pet_guard_player_data() -> Dictionary:
|
|
return {
|
|
"use_dog_item_id": 106001,
|
|
"dog_hunger_end_time": int(Time.get_unix_time_from_system()) + 3600,
|
|
}
|
|
|
|
|
|
func _market_warehouse_items() -> Array[Dictionary]:
|
|
return [{"item_id": 102001, "num": 12}]
|
|
|
|
|
|
func _market_owned_amount(item_id: int) -> int:
|
|
return 12 if item_id == 102001 else 0
|
|
|
|
|
|
func _token() -> String:
|
|
return "token"
|
|
|
|
|
|
func _test_open_modal(panel: Control) -> void:
|
|
if panel != null:
|
|
get_root().add_child(panel)
|
|
|
|
|
|
func _noop_async() -> void:
|
|
pass
|
|
|
|
|
|
func _response_data_test(result: Dictionary, fallback: Variant = {}) -> Variant:
|
|
var body = result.get("body", {})
|
|
if body is Dictionary and body.has("data"):
|
|
return body.get("data")
|
|
return fallback
|
|
|
|
|
|
func _response_status_text_test(_result: Dictionary, fallback := "操作失败") -> String:
|
|
return fallback
|
|
|
|
|
|
func _set_texture_button_enabled(button_holder: Control, enabled: bool, normal_texture: Texture2D = null, disabled_texture: Texture2D = null) -> void:
|
|
if button_holder == null:
|
|
return
|
|
var art := button_holder.get_node_or_null("Art") as TextureRect
|
|
if art != null:
|
|
art.texture = normal_texture if enabled or disabled_texture == null else disabled_texture
|
|
var hit := button_holder.get_node_or_null("Hit") as Button
|
|
if hit != null:
|
|
hit.disabled = not enabled
|
|
|
|
|
|
func _false() -> bool:
|
|
return false
|
|
|
|
|
|
func _noop(_value: Variant = null) -> void:
|
|
pass
|
|
|
|
|
|
func _expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
_failures.append(message)
|
|
|
|
|
|
func _near(value: float, expected: float) -> bool:
|
|
return absf(value - expected) <= 0.5
|