Add gathering, unified inventory, and real-time world
This commit is contained in:
parent
173371e6fc
commit
fa57edca83
113 changed files with 6700 additions and 1307 deletions
|
|
@ -18,11 +18,10 @@ const VoiceProfilesType = preload(
|
|||
)
|
||||
const PlayerMenuScene = preload("res://ui/player_menu.tscn")
|
||||
const PlayerMenuType = preload("res://ui/player_menu.gd")
|
||||
const BagItemSpriteScene = preload(
|
||||
"res://ui/components/bubble_menu/bag_item_sprite.tscn"
|
||||
)
|
||||
const OwnedItemType = preload("res://items/owned_item.gd")
|
||||
const PlayerHotbarType = preload("res://inventory/player_hotbar.gd")
|
||||
const ItemCatalogResource: ItemCatalog = preload(
|
||||
"res://items/catalog/item_catalog.tres"
|
||||
)
|
||||
const PlayersPageType = preload("res://ui/players_page.gd")
|
||||
const TheNetPageType = preload("res://ui/the_net_page.gd")
|
||||
const ControllerMappingManagerType = preload(
|
||||
|
|
@ -1077,21 +1076,63 @@ func _validate_inventory_tab_zone_transitions() -> void:
|
|||
var menu := PlayerMenuScene.instantiate() as Control
|
||||
root.add_child(menu)
|
||||
await process_frame
|
||||
var inventory_state := Node.new()
|
||||
root.add_child(inventory_state)
|
||||
var bag := PlayerBag.new()
|
||||
var catches := FishInventory.new()
|
||||
var storage_capacity := PlayerCoolerCapacity.new()
|
||||
var layout := PlayerInventoryLayout.new()
|
||||
var hotbar := PlayerHotbarType.new()
|
||||
for node: Node in [bag, catches, storage_capacity, layout, hotbar]:
|
||||
inventory_state.add_child(node)
|
||||
bag.setup(ItemCatalogResource)
|
||||
layout.setup(bag, catches, ItemCatalogResource, storage_capacity)
|
||||
bag.set_inventory_layout(layout)
|
||||
catches.set_inventory_layout(layout)
|
||||
hotbar.setup(bag, ItemCatalogResource, catches, layout)
|
||||
assert(bag.add_item(&"basic_fishing_rod", 1))
|
||||
assert(bag.add_item(&"coffee", 1))
|
||||
assert(layout.move_entry(
|
||||
PlayerInventoryLayout.EntryKind.ITEM,
|
||||
&"basic_fishing_rod",
|
||||
PlayerInventoryLayout.InventoryContainer.INVENTORY,
|
||||
9,
|
||||
))
|
||||
assert(hotbar.assign_item(0, &"coffee"))
|
||||
menu.set("_bag", bag)
|
||||
menu.set("_inventory", catches)
|
||||
menu.set("_hotbar", hotbar)
|
||||
menu.set("_inventory_layout", layout)
|
||||
menu.set("_item_catalog", ItemCatalogResource)
|
||||
var inventory_grid := menu.get("_general_inventory_grid") as GeneralInventoryGrid
|
||||
inventory_grid.setup(
|
||||
layout,
|
||||
bag,
|
||||
catches,
|
||||
hotbar,
|
||||
ItemCatalogResource,
|
||||
PlayerInventoryLayout.InventoryContainer.INVENTORY,
|
||||
)
|
||||
menu.visible = true
|
||||
menu.call(
|
||||
"_show_section_immediate",
|
||||
PlayerMenuType.Section.COOLER,
|
||||
PlayerMenuType.Section.BAG,
|
||||
)
|
||||
menu.call("_set_content_interactive", true)
|
||||
menu.call("_enter_inventory_tabs_zone")
|
||||
for _frame: int in 2:
|
||||
await process_frame
|
||||
var cooler_tab := menu.get_node("%CoolerSubTab") as Button
|
||||
var equipment_tab := menu.get_node("%BagSubTab") as Button
|
||||
var items_tab := menu.get_node("%ItemsSubTab") as Button
|
||||
var inventory_tab := menu.get_node("%BagSubTab") as Button
|
||||
var tackle_tab := menu.get_node("%TackleSubTab") as Button
|
||||
_expect(
|
||||
root.gui_get_focus_owner() == cooler_tab,
|
||||
"Inventory tab zone did not begin on the active Cooler tab.",
|
||||
root.gui_get_focus_owner() == inventory_tab,
|
||||
"Inventory tab zone did not begin on the unified Inventory tab.",
|
||||
)
|
||||
_expect(inventory_tab.text == "Inventory", "Unified tab is not labelled Inventory.")
|
||||
_expect(
|
||||
not (menu.get_node("%CoolerSubTab") as Button).visible
|
||||
and not (menu.get_node("%ItemsSubTab") as Button).visible,
|
||||
"Legacy Cooler or Items tabs remain visible.",
|
||||
)
|
||||
|
||||
var right := InputEventAction.new()
|
||||
|
|
@ -1103,8 +1144,8 @@ func _validate_inventory_tab_zone_transitions() -> void:
|
|||
)
|
||||
await _wait_for_player_menu_page_transition(menu)
|
||||
_expect(
|
||||
menu.get("_current_section") == PlayerMenuType.Section.BAG,
|
||||
"Controller Right did not switch from Cooler to Equipment.",
|
||||
menu.get("_current_section") == PlayerMenuType.Section.TACKLE_BOX,
|
||||
"Controller Right did not switch from Inventory to Tackle.",
|
||||
)
|
||||
_expect(
|
||||
menu.get("_controller_ownership")
|
||||
|
|
@ -1112,7 +1153,7 @@ func _validate_inventory_tab_zone_transitions() -> void:
|
|||
"Changing Inventory tabs entered the item-content zone without A.",
|
||||
)
|
||||
_expect(
|
||||
root.gui_get_focus_owner() == equipment_tab,
|
||||
root.gui_get_focus_owner() == tackle_tab,
|
||||
"Changing Inventory tabs did not keep focus on the selected tab.",
|
||||
)
|
||||
var down := InputEventAction.new()
|
||||
|
|
@ -1124,59 +1165,33 @@ func _validate_inventory_tab_zone_transitions() -> void:
|
|||
)
|
||||
await process_frame
|
||||
_expect(
|
||||
root.gui_get_focus_owner() == equipment_tab,
|
||||
root.gui_get_focus_owner() == tackle_tab,
|
||||
"Controller Down escaped the Inventory tab zone before A.",
|
||||
)
|
||||
|
||||
_expect(
|
||||
bool(menu.call("_handle_controller_ownership_input", right)),
|
||||
"Equipment-to-Items navigation did not consume controller Right.",
|
||||
)
|
||||
await _wait_for_player_menu_page_transition(menu)
|
||||
_expect(
|
||||
root.gui_get_focus_owner() == items_tab,
|
||||
"Items tab selection did not retain tab focus before A (focus=%s)."
|
||||
% root.gui_get_focus_owner(),
|
||||
)
|
||||
_expect(
|
||||
menu.get("_controller_ownership")
|
||||
== PlayerMenuType.ControllerOwnership.INVENTORY_TABS,
|
||||
"Items tab selection changed controller zones before A.",
|
||||
)
|
||||
var left := InputEventAction.new()
|
||||
left.action = &"ui_left"
|
||||
left.pressed = true
|
||||
_expect(
|
||||
bool(menu.call("_handle_controller_ownership_input", left)),
|
||||
"Items-to-Equipment navigation did not consume controller Left.",
|
||||
"Tackle-to-Inventory navigation did not consume controller Left.",
|
||||
)
|
||||
_expect(
|
||||
menu.get("_current_section") == PlayerMenuType.Section.BAG
|
||||
or bool(menu.get("_page_transitioning")),
|
||||
"Tackle-to-Inventory input did not start the Inventory transition.",
|
||||
)
|
||||
await _wait_for_player_menu_page_transition(menu)
|
||||
_expect(
|
||||
root.gui_get_focus_owner() == equipment_tab,
|
||||
"Returning to Equipment did not retain tab focus before A.",
|
||||
root.gui_get_focus_owner() == inventory_tab,
|
||||
"Returning to Inventory did not retain tab focus before A.",
|
||||
)
|
||||
|
||||
# Add three representative equipment entries so entering the content zone
|
||||
# exercises the real five-column directional layout.
|
||||
var item_field := menu.get_node("%BagItemField") as Control
|
||||
var bag_nodes: Dictionary = menu.get("_bag_item_nodes")
|
||||
var owned_items: Array[OwnedItemType] = []
|
||||
for index: int in 3:
|
||||
var owned := OwnedItemType.new()
|
||||
owned.item_id = StringName("controller_test_item_%d" % index)
|
||||
owned_items.append(owned)
|
||||
var item_node := BagItemSpriteScene.instantiate() as Button
|
||||
item_node.set("item_id", owned.item_id)
|
||||
item_node.position = Vector2(60.0 + float(index) * 180.0, 40.0)
|
||||
item_field.add_child(item_node)
|
||||
bag_nodes[owned.item_id] = item_node
|
||||
menu.set("_sorted_bag_items", owned_items)
|
||||
menu.call("_set_content_interactive", true)
|
||||
menu.call("_configure_bag_item_focus")
|
||||
for item_node: Control in bag_nodes.values():
|
||||
var active_slots := inventory_grid.get_slots()
|
||||
for slot: GeneralInventorySlot in active_slots:
|
||||
_expect(
|
||||
item_node.focus_mode == Control.FOCUS_NONE,
|
||||
"Equipment content remained focusable while tabs owned the controller.",
|
||||
slot.focus_mode == Control.FOCUS_NONE,
|
||||
"Inventory slots remained focusable while tabs owned the controller.",
|
||||
)
|
||||
|
||||
var accept := InputEventJoypadButton.new()
|
||||
|
|
@ -1193,34 +1208,45 @@ func _validate_inventory_tab_zone_transitions() -> void:
|
|||
)
|
||||
for _frame: int in 2:
|
||||
await process_frame
|
||||
var first_item := bag_nodes[owned_items[0].item_id] as Control
|
||||
var second_item := bag_nodes[owned_items[1].item_id] as Control
|
||||
for item_node: Control in bag_nodes.values():
|
||||
_expect(
|
||||
menu.get("_current_section") == PlayerMenuType.Section.BAG,
|
||||
"Entering Inventory contents changed section to %s."
|
||||
% menu.get("_current_section"),
|
||||
)
|
||||
var first_item := active_slots[0] as Control
|
||||
var second_item := active_slots[1] as Control
|
||||
for slot: GeneralInventorySlot in active_slots:
|
||||
_expect(
|
||||
item_node.focus_mode == Control.FOCUS_ALL,
|
||||
"Accepting Equipment did not enable its content focus zone.",
|
||||
slot.focus_mode == Control.FOCUS_ALL,
|
||||
"Accepting Inventory did not enable its content focus zone.",
|
||||
)
|
||||
_expect(
|
||||
root.gui_get_focus_owner() == first_item,
|
||||
"Entering Equipment did not focus its first item.",
|
||||
"Entering Inventory did not focus its first slot.",
|
||||
)
|
||||
_expect(
|
||||
first_item.focus_neighbor_right == first_item.get_path_to(second_item),
|
||||
"Equipment items do not provide horizontal controller navigation.",
|
||||
"Inventory slots do not provide horizontal controller navigation.",
|
||||
)
|
||||
Input.parse_input_event(right)
|
||||
for _frame: int in 2:
|
||||
await process_frame
|
||||
_expect(
|
||||
root.gui_get_focus_owner() == second_item,
|
||||
"Controller Right did not move between Equipment items.",
|
||||
menu.get("_current_section") == PlayerMenuType.Section.BAG,
|
||||
"Inventory slot navigation changed section to %s."
|
||||
% menu.get("_current_section"),
|
||||
)
|
||||
right.pressed = false
|
||||
Input.parse_input_event(right)
|
||||
right.pressed = true
|
||||
_expect(
|
||||
root.gui_get_focus_owner() == second_item,
|
||||
"Controller Right did not move between Inventory slots.",
|
||||
)
|
||||
var right_release := InputEventAction.new()
|
||||
right_release.action = &"ui_right"
|
||||
right_release.pressed = false
|
||||
Input.parse_input_event(right_release)
|
||||
_expect(
|
||||
bool(menu.call("consume_escape")),
|
||||
"Global player-menu Back did not consume Equipment contents.",
|
||||
"Global player-menu Back did not consume Inventory contents.",
|
||||
)
|
||||
_expect(
|
||||
menu.visible,
|
||||
|
|
@ -1233,90 +1259,102 @@ func _validate_inventory_tab_zone_transitions() -> void:
|
|||
)
|
||||
for _frame: int in 2:
|
||||
await process_frame
|
||||
_expect(
|
||||
menu.get("_current_section") == PlayerMenuType.Section.BAG,
|
||||
"Returning from Inventory contents changed section to %s."
|
||||
% menu.get("_current_section"),
|
||||
)
|
||||
_expect(
|
||||
bool(menu.call("_handle_controller_ownership_input", accept)),
|
||||
"Inventory tab zone did not re-enter Equipment contents.",
|
||||
"Inventory tab zone did not re-enter Inventory contents.",
|
||||
)
|
||||
for _frame: int in 2:
|
||||
await process_frame
|
||||
|
||||
var favorite := menu.get_node("%FavoriteBubble") as BaseButton
|
||||
var sell := menu.get_node("%SellBubble") as BaseButton
|
||||
var sell_all := menu.get_node("%SellAllBubble") as BaseButton
|
||||
for action: BaseButton in [favorite, sell, sell_all]:
|
||||
action.disabled = false
|
||||
action.focus_mode = Control.FOCUS_ALL
|
||||
var notepad_actions: Array[BaseButton] = [favorite, sell, sell_all]
|
||||
menu.call(
|
||||
"_configure_controller_notepad_action_focus",
|
||||
notepad_actions,
|
||||
)
|
||||
var notepad_source := active_slots[9]
|
||||
notepad_source.grab_focus()
|
||||
var context_press := InputEventJoypadButton.new()
|
||||
context_press.button_index = JOY_BUTTON_Y
|
||||
context_press.pressed = true
|
||||
_expect(
|
||||
sell.get_node(sell.focus_neighbor_bottom) == sell_all,
|
||||
"Sell All is not reachable below Sell Fish in the notepad zone.",
|
||||
)
|
||||
_expect(
|
||||
sell_all.get_node(sell_all.focus_neighbor_top) == sell,
|
||||
"Sell All does not return to the upper notepad actions.",
|
||||
)
|
||||
menu.set(
|
||||
"_controller_ownership",
|
||||
PlayerMenuType.ControllerOwnership.NOTEPAD_ACTIONS,
|
||||
)
|
||||
menu.set("_controller_source_section", PlayerMenuType.Section.BAG)
|
||||
menu.set("_controller_source_identity", owned_items[1].item_id)
|
||||
menu.call("_apply_inventory_controller_zone_focus_modes")
|
||||
_expect(
|
||||
bool(menu.call("consume_escape")),
|
||||
"Global player-menu Back did not consume the Inventory notepad zone.",
|
||||
)
|
||||
_expect(
|
||||
menu.visible,
|
||||
"Global player-menu Back closed Inventory from its notepad zone.",
|
||||
bool(menu.call(
|
||||
"_handle_controller_ownership_input", context_press
|
||||
)),
|
||||
"Controller Y did not open the selected Inventory notepad.",
|
||||
)
|
||||
_expect(
|
||||
menu.get("_controller_ownership")
|
||||
== PlayerMenuType.ControllerOwnership.NOTEPAD_ACTIONS,
|
||||
"Inventory notepad did not become the active controller zone.",
|
||||
)
|
||||
_expect(
|
||||
(menu.get_node("%BagDetailConstellation") as Control).visible,
|
||||
"Inventory notepad remained hidden after controller Y.",
|
||||
)
|
||||
_expect(
|
||||
bool(menu.call("consume_escape")),
|
||||
"Controller B did not close the Inventory notepad.",
|
||||
)
|
||||
_expect(
|
||||
not (menu.get_node("%BagDetailConstellation") as Control).visible
|
||||
and menu.get("_controller_ownership")
|
||||
== PlayerMenuType.ControllerOwnership.ITEM_LIST,
|
||||
"Global player-menu Back did not return the notepad to Inventory contents.",
|
||||
"Closing the Inventory notepad did not restore the item zone.",
|
||||
)
|
||||
for _frame: int in 2:
|
||||
await process_frame
|
||||
|
||||
var hotbar := PlayerHotbarType.new()
|
||||
menu.set("_hotbar", hotbar)
|
||||
var hotbar_slots: Array[StringName] = []
|
||||
hotbar_slots.resize(PlayerHotbarType.SLOT_COUNT)
|
||||
hotbar_slots.fill(StringName())
|
||||
hotbar.set("_slots", hotbar_slots)
|
||||
var hotbar_fish_slots: Array[StringName] = []
|
||||
hotbar_fish_slots.resize(PlayerHotbarType.SLOT_COUNT)
|
||||
hotbar_fish_slots.fill(StringName())
|
||||
hotbar_fish_slots[0] = &"controller_test_fish"
|
||||
hotbar.set("_fish_slots", hotbar_fish_slots)
|
||||
var accept_release := InputEventJoypadButton.new()
|
||||
accept_release.button_index = JOY_BUTTON_A
|
||||
accept_release.pressed = false
|
||||
notepad_source.grab_focus()
|
||||
menu.call("_handle_controller_ownership_input", accept)
|
||||
menu.call("_handle_controller_ownership_input", accept_release)
|
||||
_expect(
|
||||
str(menu.get("_inventory_move_identity")) == "basic_fishing_rod",
|
||||
"Controller A did not pick up the focused Inventory item.",
|
||||
)
|
||||
var move_target := active_slots[10]
|
||||
move_target.grab_focus()
|
||||
menu.call("_handle_controller_ownership_input", accept)
|
||||
menu.call("_handle_controller_ownership_input", accept_release)
|
||||
_expect(
|
||||
layout.get_key_at(
|
||||
PlayerInventoryLayout.InventoryContainer.INVENTORY, 10
|
||||
) == PlayerInventoryLayout.item_key(&"basic_fishing_rod"),
|
||||
"Controller A did not place the picked-up item in its target slot.",
|
||||
)
|
||||
var management_requests: Array[int] = [0]
|
||||
menu.controller_hotbar_management_requested.connect(
|
||||
func(_initial_slot: int) -> void:
|
||||
management_requests[0] += 1
|
||||
)
|
||||
var hotbar_owned := OwnedItemType.new()
|
||||
hotbar_owned.item_id = &"controller_hotbar_source"
|
||||
var hotbar_source := BagItemSpriteScene.instantiate() as Button
|
||||
hotbar_source.set("item_id", hotbar_owned.item_id)
|
||||
hotbar_source.position = Vector2(60.0, 40.0)
|
||||
item_field.add_child(hotbar_source)
|
||||
bag_nodes[hotbar_owned.item_id] = hotbar_source
|
||||
var hotbar_items: Array[OwnedItemType] = [hotbar_owned]
|
||||
menu.set("_sorted_bag_items", hotbar_items)
|
||||
menu.set(
|
||||
"_controller_ownership",
|
||||
PlayerMenuType.ControllerOwnership.ITEM_LIST,
|
||||
)
|
||||
menu.call("_apply_inventory_controller_zone_focus_modes")
|
||||
menu.call("_configure_bag_item_focus")
|
||||
var hotbar_source := active_slots[10]
|
||||
_expect(
|
||||
menu.get("_current_section") == PlayerMenuType.Section.BAG,
|
||||
"Hotbar navigation test left the unified Inventory section (%s)."
|
||||
% menu.get("_current_section"),
|
||||
)
|
||||
_expect(
|
||||
hotbar_source.focus_mode == Control.FOCUS_ALL,
|
||||
"Final active Inventory row was not controller-focusable.",
|
||||
)
|
||||
hotbar_source.grab_focus()
|
||||
_expect(
|
||||
root.gui_get_focus_owner() == hotbar_source,
|
||||
"Final Inventory row did not accept controller focus before Hotbar entry.",
|
||||
)
|
||||
_expect(
|
||||
bool(menu.call("_controller_focus_is_on_last_inventory_row")),
|
||||
"Focused Inventory slot was not recognized as part of the final row.",
|
||||
)
|
||||
_expect(
|
||||
bool(menu.call("_handle_controller_ownership_input", down)),
|
||||
"Down from the final Equipment row did not enter the hotbar zone.",
|
||||
"Down from the final Inventory row did not enter the hotbar zone.",
|
||||
)
|
||||
_expect(
|
||||
menu.get("_controller_ownership")
|
||||
|
|
@ -1358,9 +1396,10 @@ func _validate_inventory_tab_zone_transitions() -> void:
|
|||
"Hotbar management did not consume controller A.",
|
||||
)
|
||||
_expect(
|
||||
hotbar.get_fish_catch_id(0).is_empty(),
|
||||
"Controller A did not remove the selected fish hotbar assignment.",
|
||||
hotbar.get_item_id(0).is_empty(),
|
||||
"Controller A did not return the selected hotbar item to Inventory.",
|
||||
)
|
||||
_expect(layout.is_item_in_inventory(&"coffee"), "Cleared hotbar item was lost.")
|
||||
_expect(
|
||||
bool(menu.call("consume_escape")),
|
||||
"Global player-menu Back did not consume hotbar management.",
|
||||
|
|
@ -1376,8 +1415,8 @@ func _validate_inventory_tab_zone_transitions() -> void:
|
|||
)
|
||||
for _frame: int in 2:
|
||||
await process_frame
|
||||
hotbar.free()
|
||||
menu.queue_free()
|
||||
inventory_state.queue_free()
|
||||
await process_frame
|
||||
|
||||
|
||||
|
|
|
|||
91
tests/digging_prototype_validation.gd
Normal file
91
tests/digging_prototype_validation.gd
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
extends SceneTree
|
||||
|
||||
const StarterIslandScene = preload(
|
||||
"res://world/regions/starter_island_region.tscn"
|
||||
)
|
||||
const ShovelAttachmentScene = preload("res://player/shovel_attachment.tscn")
|
||||
const ItemCatalogResource: ItemCatalog = preload(
|
||||
"res://items/catalog/item_catalog.tres"
|
||||
)
|
||||
const Gatherables: GatherableCatalog = preload(
|
||||
"res://gathering/catalog/gatherable_catalog.tres"
|
||||
)
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
_validate_catalog_content()
|
||||
_validate_flat_shovel()
|
||||
await _validate_beach_authoring()
|
||||
print("Digging prototype validation: PASS")
|
||||
quit()
|
||||
|
||||
|
||||
func _validate_catalog_content() -> void:
|
||||
var shovel: ItemData = ItemCatalogResource.get_available_item_by_id(
|
||||
&"standard_shovel"
|
||||
)
|
||||
assert(shovel != null)
|
||||
assert(shovel.category == ItemData.Category.TOOL)
|
||||
assert(shovel.equippable and shovel.hotbar_allowed)
|
||||
var clam: GatherableData = Gatherables.get_entry(&"clam_manila")
|
||||
assert(clam != null and clam.is_available())
|
||||
assert(clam.required_tool_id == shovel.item_id)
|
||||
assert(clam.diggable_area_id == &"starter_beach")
|
||||
assert(clam.presentation_mode == GatherableData.PresentationMode.WATER_SPURT)
|
||||
assert(not clam.requires_sneaking)
|
||||
assert(is_equal_approx(clam.active_lifetime_seconds, 10.0))
|
||||
|
||||
|
||||
func _validate_flat_shovel() -> void:
|
||||
var attachment := ShovelAttachmentScene.instantiate() as BoneAttachment3D
|
||||
assert(attachment != null)
|
||||
var shovel := attachment.get_node("Shovel") as Node3D
|
||||
assert(shovel != null)
|
||||
var meshes: Array[MeshInstance3D] = []
|
||||
_collect_meshes(shovel, meshes)
|
||||
assert(meshes.size() == 5)
|
||||
for mesh_instance: MeshInstance3D in meshes:
|
||||
assert(
|
||||
mesh_instance.cast_shadow
|
||||
== GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
)
|
||||
var material := mesh_instance.mesh.surface_get_material(0) as StandardMaterial3D
|
||||
assert(material != null)
|
||||
assert(material.shading_mode == BaseMaterial3D.SHADING_MODE_UNSHADED)
|
||||
assert(material.transparency == BaseMaterial3D.TRANSPARENCY_DISABLED)
|
||||
assert(material.albedo_texture == null)
|
||||
assert(is_equal_approx(material.albedo_color.a, 1.0))
|
||||
attachment.free()
|
||||
|
||||
|
||||
func _validate_beach_authoring() -> void:
|
||||
var region := StarterIslandScene.instantiate() as WorldRegion
|
||||
root.add_child(region)
|
||||
await process_frame
|
||||
var area: DiggableArea3D = region.get_diggable_area(&"starter_beach")
|
||||
assert(area != null)
|
||||
assert(area.terrain_source == NodePath("../../Terrain/Visual"))
|
||||
assert(area.surface_materials.size() == 1)
|
||||
assert(area.surface_materials[0] == &"sand")
|
||||
var triangles: Array[PackedVector3Array] = area.get_surface_triangles()
|
||||
assert(not triangles.is_empty())
|
||||
for triangle: PackedVector3Array in triangles:
|
||||
assert(triangle.size() == 3)
|
||||
var center := (triangle[0] + triangle[1] + triangle[2]) / 3.0
|
||||
assert(area.generation_bounds.has_point(Vector2(center.x, center.z)))
|
||||
assert(center.y <= area.maximum_global_y + 0.001)
|
||||
region.queue_free()
|
||||
|
||||
|
||||
func _collect_meshes(
|
||||
root_node: Node,
|
||||
result: Array[MeshInstance3D],
|
||||
) -> void:
|
||||
for child: Node in root_node.get_children():
|
||||
if child is MeshInstance3D:
|
||||
result.append(child as MeshInstance3D)
|
||||
_collect_meshes(child, result)
|
||||
1
tests/digging_prototype_validation.gd.uid
Normal file
1
tests/digging_prototype_validation.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c1dt44ieacnvb
|
||||
|
|
@ -48,8 +48,8 @@ func _run() -> void:
|
|||
as PlayerAssetReservationService
|
||||
)
|
||||
assert(player != null)
|
||||
assert(catalog != null and catalog.candidates.size() == 314)
|
||||
assert(LogbookCatalog.ordered_species(catalog.candidates).size() == 54)
|
||||
assert(catalog != null and catalog.candidates.size() == 316)
|
||||
assert(LogbookCatalog.ordered_species(catalog.candidates).size() == 56)
|
||||
assert(sale_service != null)
|
||||
assert(shop_service != null)
|
||||
assert(session != null and session.is_host())
|
||||
|
|
@ -62,7 +62,6 @@ func _run() -> void:
|
|||
_test_multi_sale(player, catalog, sale_service)
|
||||
_test_reservations(player, catalog, sale_service, reservations)
|
||||
_test_anywhere_sale(player, catalog, sale_service)
|
||||
await _test_player_menu_sale(main, player, catalog, sale_service)
|
||||
await _test_host_shop_sale(main, player, catalog, sale_service)
|
||||
|
||||
assert(session.set_host_open(true))
|
||||
|
|
@ -78,6 +77,8 @@ func _run() -> void:
|
|||
await _test_fishing_shop_sale_ui(
|
||||
main, player, catalog, sale_service, reservations
|
||||
)
|
||||
_test_host_backpack_purchase(player, shop_service)
|
||||
_test_host_storage_purchase(player, shop_service)
|
||||
assert(not sale_service.is_local_sale_pending())
|
||||
assert(not shop_service.is_local_purchase_pending())
|
||||
|
||||
|
|
@ -217,6 +218,17 @@ func _run_multiplayer_client() -> void:
|
|||
print("Client shop result: ", _shop_result)
|
||||
assert(not _shop_result.is_empty() and bool(_shop_result[1]))
|
||||
assert(not shop_service.is_local_purchase_pending())
|
||||
var backpack_cost := player.inventory_layout.get_next_backpack_cost()
|
||||
if player.wallet.get_balance() < backpack_cost:
|
||||
assert(player.wallet.credit(
|
||||
backpack_cost - player.wallet.get_balance()
|
||||
))
|
||||
_shop_result.clear()
|
||||
assert(not shop_service.request_backpack_capacity_upgrade().is_empty())
|
||||
while _shop_result.is_empty():
|
||||
await process_frame
|
||||
assert(bool(_shop_result[1]))
|
||||
assert(player.inventory_layout.get_inventory_capacity() == 18)
|
||||
var required_art_balance: int = (
|
||||
ArtShopStock.ART_KIT_PRICE + ArtShopStock.UPGRADE_PRICE
|
||||
)
|
||||
|
|
@ -377,80 +389,39 @@ func _test_host_shop_sale(
|
|||
)
|
||||
var species_ids: Array[StringName] = []
|
||||
var expected_payout: int = 0
|
||||
var species_balance_before: int = player.wallet.get_balance()
|
||||
for fish: FishData in catalog.candidates:
|
||||
if not fish.active:
|
||||
continue
|
||||
var fish_catch := _make_catch(fish)
|
||||
player.inventory.add_catch(fish_catch)
|
||||
assert(player.inventory.add_catch(fish_catch))
|
||||
species_ids.append(fish_catch.catch_id)
|
||||
expected_payout += fish_catch.sale_value
|
||||
var species_balance_before: int = player.wallet.get_balance()
|
||||
_assert_sale(
|
||||
player,
|
||||
sale_service,
|
||||
species_ids,
|
||||
true,
|
||||
true,
|
||||
NetworkSaleService.MAIN_SHOP_BUYER_ID,
|
||||
)
|
||||
if species_ids.size() >= 5:
|
||||
_assert_sale(
|
||||
player,
|
||||
sale_service,
|
||||
species_ids,
|
||||
true,
|
||||
true,
|
||||
NetworkSaleService.MAIN_SHOP_BUYER_ID,
|
||||
)
|
||||
species_ids.clear()
|
||||
if not species_ids.is_empty():
|
||||
_assert_sale(
|
||||
player,
|
||||
sale_service,
|
||||
species_ids,
|
||||
true,
|
||||
true,
|
||||
NetworkSaleService.MAIN_SHOP_BUYER_ID,
|
||||
)
|
||||
assert(
|
||||
player.wallet.get_balance()
|
||||
== species_balance_before + expected_payout
|
||||
)
|
||||
|
||||
|
||||
func _test_player_menu_sale(
|
||||
main: Node,
|
||||
player: Player,
|
||||
catalog: FishPool,
|
||||
sale_service: NetworkSaleService,
|
||||
) -> void:
|
||||
var game_ui := main.get_node("%GameUI") as GameUI
|
||||
var player_menu := game_ui.get_node("%PlayerMenu") as PlayerMenu
|
||||
var fish_catch := _make_catch(catalog.candidates[6])
|
||||
player.inventory.add_catch(fish_catch)
|
||||
player.global_position = Vector3(0.0, 3.95, 13.0)
|
||||
player_menu.open_menu()
|
||||
await create_timer(2.2).timeout
|
||||
player_menu.call("_on_catch_card_pressed", fish_catch.catch_id)
|
||||
await process_frame
|
||||
var sell_action := player_menu.get_node("%SellBubble") as Button
|
||||
var confirmation := player_menu.get_node("%SaleConfirmation") as Control
|
||||
var confirm_button := player_menu.get_node("%ConfirmSaleButton") as Button
|
||||
var ui_viewport := main.get_node(
|
||||
"UIPresentation/UIViewport"
|
||||
) as SubViewport
|
||||
assert(sell_action.visible and not sell_action.disabled)
|
||||
assert(sell_action.mouse_filter == Control.MOUSE_FILTER_STOP)
|
||||
assert(ui_viewport != null)
|
||||
await _activate_pointer_control(sell_action, ui_viewport)
|
||||
await process_frame
|
||||
assert(confirmation.visible)
|
||||
assert(confirm_button.visible and not confirm_button.disabled)
|
||||
assert(
|
||||
confirmation.z_index
|
||||
> (player_menu.get_node("%CoolerOuterWall") as Control).z_index
|
||||
)
|
||||
_sale_result.clear()
|
||||
await _activate_pointer_control(confirm_button, ui_viewport)
|
||||
await process_frame
|
||||
assert(not _sale_result.is_empty() and bool(_sale_result[1]))
|
||||
assert(not player.inventory.contains_catch_id(fish_catch.catch_id))
|
||||
assert(not sale_service.is_local_sale_pending())
|
||||
player_menu.close_menu()
|
||||
await create_timer(2.2).timeout
|
||||
assert(not player_menu.visible)
|
||||
player_menu.open_menu()
|
||||
await create_timer(2.2).timeout
|
||||
assert(player_menu.visible)
|
||||
assert(
|
||||
not sell_action.disabled
|
||||
or player.inventory.get_all_catches().is_empty()
|
||||
)
|
||||
player_menu.close_menu()
|
||||
await create_timer(2.2).timeout
|
||||
|
||||
|
||||
func _activate_pointer_control(
|
||||
control: Control,
|
||||
ui_viewport: SubViewport,
|
||||
|
|
@ -547,6 +518,48 @@ func _test_host_rod_purchase(
|
|||
assert(not shop_service.is_local_purchase_pending())
|
||||
|
||||
|
||||
func _test_host_backpack_purchase(
|
||||
player: Player,
|
||||
shop_service: NetworkShopService,
|
||||
) -> void:
|
||||
var layout := player.inventory_layout
|
||||
assert(layout.get_inventory_capacity() == 9)
|
||||
var total_cost: int = 0
|
||||
for cost: int in PlayerInventoryLayout.BACKPACK_EXPANSION_COSTS:
|
||||
total_cost += cost
|
||||
if player.wallet.get_balance() < total_cost:
|
||||
assert(player.wallet.credit(total_cost - player.wallet.get_balance()))
|
||||
var balance_before := player.wallet.get_balance()
|
||||
for expected_capacity: int in [18, 27, 36]:
|
||||
_shop_result.clear()
|
||||
assert(not shop_service.request_backpack_capacity_upgrade().is_empty())
|
||||
assert(not _shop_result.is_empty() and bool(_shop_result[1]))
|
||||
assert(layout.get_inventory_capacity() == expected_capacity)
|
||||
assert(player.wallet.get_balance() == balance_before - total_cost)
|
||||
assert(layout.get_next_backpack_cost() == -1)
|
||||
|
||||
|
||||
func _test_host_storage_purchase(
|
||||
player: Player,
|
||||
shop_service: NetworkShopService,
|
||||
) -> void:
|
||||
var capacity := player.cooler_capacity
|
||||
assert(capacity.get_capacity() == 9)
|
||||
var total_cost: int = 0
|
||||
for cost: int in PlayerCoolerCapacity.EXPANSION_COSTS:
|
||||
total_cost += cost
|
||||
if player.wallet.get_balance() < total_cost:
|
||||
assert(player.wallet.credit(total_cost - player.wallet.get_balance()))
|
||||
var balance_before := player.wallet.get_balance()
|
||||
for expected_capacity: int in [18, 27, 36, 45, 54, 63, 72]:
|
||||
_shop_result.clear()
|
||||
assert(not shop_service.request_cooler_capacity_upgrade().is_empty())
|
||||
assert(not _shop_result.is_empty() and bool(_shop_result[1]))
|
||||
assert(capacity.get_capacity() == expected_capacity)
|
||||
assert(player.wallet.get_balance() == balance_before - total_cost)
|
||||
assert(capacity.get_next_cost() == -1)
|
||||
|
||||
|
||||
func _test_fishing_shop_sale_ui(
|
||||
main: Node,
|
||||
player: Player,
|
||||
|
|
@ -608,7 +621,11 @@ func _test_fishing_shop_sale_ui(
|
|||
"QuantityBadge/Quantity"
|
||||
) as Label
|
||||
assert(tackle_quantity.text == expected_bait_supply)
|
||||
assert(tackle_bait_button.tooltip_text.contains(expected_bait_supply))
|
||||
assert(
|
||||
str(tackle_bait_button.get_meta(
|
||||
&"inventory_context_text", ""
|
||||
)).contains(expected_bait_supply)
|
||||
)
|
||||
var tackle_badge := tackle_quantity.get_parent() as Panel
|
||||
assert(
|
||||
is_equal_approx(
|
||||
|
|
@ -654,7 +671,10 @@ func _test_fishing_shop_sale_ui(
|
|||
assert((shop.get_node("%Upgrades") as Control).visible)
|
||||
assert(not (shop.get_node("%Supplies") as Control).visible)
|
||||
for upgrade_name: String in [
|
||||
"ReelPurchase", "BarrierPurchase", "CoolerPurchase"
|
||||
"ReelPurchase",
|
||||
"BarrierPurchase",
|
||||
"CoolerPurchase",
|
||||
"BackpackPurchase",
|
||||
]:
|
||||
var upgrade_button := shop.get_node("%%%s" % upgrade_name) as Button
|
||||
assert(upgrade_button != null)
|
||||
|
|
@ -662,6 +682,15 @@ func _test_fishing_shop_sale_ui(
|
|||
assert(upgrade_button.text.is_empty())
|
||||
assert(upgrade_button.icon != null)
|
||||
assert(upgrade_button.tooltip_text.contains("level"))
|
||||
for placeholder_name: String in ["CoolerPurchase", "BackpackPurchase"]:
|
||||
var placeholder_button := shop.get_node(
|
||||
"%%%s" % placeholder_name
|
||||
) as Button
|
||||
assert(
|
||||
placeholder_button.icon.resource_path.ends_with(
|
||||
"/pictograms/x_light.png"
|
||||
)
|
||||
)
|
||||
assert(not shop.has_node("%ReelLevel"))
|
||||
assert(not shop.has_node("%BarrierEffect"))
|
||||
assert(not shop.has_node("%CoolerLevel"))
|
||||
|
|
@ -674,7 +703,9 @@ func _test_fishing_shop_sale_ui(
|
|||
"/shop/32_currency.png"
|
||||
)
|
||||
)
|
||||
for cost_name: String in ["ReelCost", "BarrierCost", "CoolerCost"]:
|
||||
for cost_name: String in [
|
||||
"ReelCost", "BarrierCost", "CoolerCost", "BackpackCost"
|
||||
]:
|
||||
var cost_display := shop.get_node("%%%s" % cost_name) as CurrencyAmount
|
||||
assert(cost_display != null)
|
||||
var cost_icon := cost_display.get_node("Icon") as TextureRect
|
||||
|
|
@ -690,7 +721,7 @@ func _test_fishing_shop_sale_ui(
|
|||
var art_supplies_tab := shop_tabs[4] as Button
|
||||
assert(art_supplies_tab != null and art_supplies_tab.text == "Art Supplies")
|
||||
var sell_mode := shop_tabs[5] as Button
|
||||
assert(sell_mode != null and sell_mode.text == "Sell Fish")
|
||||
assert(sell_mode != null and sell_mode.text == "Sell")
|
||||
var equipment_tab := shop_tabs[3] as Button
|
||||
assert(equipment_tab != null and equipment_tab.text == "Equipment")
|
||||
var supplies_list := shop.get_node("%SuppliesList") as VBoxContainer
|
||||
|
|
@ -881,44 +912,47 @@ func _test_fishing_shop_sale_ui(
|
|||
assert((shop.get_node("%ShopPanel") as Control).visible)
|
||||
assert(not (shop.get_node("ShopPanel/Margin/Layout/Body") as Control).visible)
|
||||
assert(not (shop.get_node("%Feedback") as Control).visible)
|
||||
var mounted_cooler := player_menu.get("_cooler_page") as Control
|
||||
assert(mounted_cooler != null and mounted_cooler.visible)
|
||||
var sell_inventory := shop.get("_sell_inventory") as ShopSellInventory
|
||||
assert(sell_inventory != null and sell_inventory.visible)
|
||||
var sell_tray_grid := sell_inventory.get("_tray_grid") as GridContainer
|
||||
assert(
|
||||
mounted_cooler.get_parent() == shop.get_node("%ShopCoolerMount")
|
||||
sell_tray_grid.columns == PlayerInventoryLayout.INVENTORY_COLUMNS
|
||||
)
|
||||
var cooler_outer_wall := player_menu.get("_cooler_outer_wall") as Control
|
||||
var water_surface := player_menu.get("_cooler_water_surface") as ColorRect
|
||||
assert(cooler_outer_wall != null and cooler_outer_wall.visible)
|
||||
assert(water_surface.visible and water_surface.material is ShaderMaterial)
|
||||
var cooler_sort_option := player_menu.get("_cooler_sort_option") as Control
|
||||
await _activate_pointer_control(cooler_sort_option, ui_viewport)
|
||||
var cooler_choice_panel := cooler_sort_option.get("_choice_panel") as Control
|
||||
assert(cooler_choice_panel.visible)
|
||||
cooler_sort_option.call("close_choices")
|
||||
assert(
|
||||
StringName(
|
||||
(player_menu.get("_sale_buyer_override") as FishBuyerProfile).id
|
||||
) == NetworkSaleService.MAIN_SHOP_BUYER_ID
|
||||
sell_tray_grid.get_theme_constant("h_separation")
|
||||
== GeneralInventoryGrid.DEFAULT_SLOT_SEPARATION
|
||||
)
|
||||
var fish_nodes: Dictionary = player_menu.get("_fish_nodes")
|
||||
var fish_button := fish_nodes.get(fish_catch.catch_id) as Button
|
||||
var reserved_button := fish_nodes.get(reserved_catch.catch_id) as Button
|
||||
assert(fish_button != null and fish_button.visible)
|
||||
assert(reserved_button != null and reserved_button.visible)
|
||||
await _activate_pointer_control(fish_button, ui_viewport)
|
||||
var sell_button := player_menu.get("_sell_bubble") as Button
|
||||
assert(sell_button.visible and not sell_button.disabled)
|
||||
await _activate_pointer_control(sell_button, ui_viewport)
|
||||
await process_frame
|
||||
var confirmation := player_menu.get("_sale_confirmation") as Control
|
||||
var confirm_button := player_menu.get("_confirm_sale_button") as Button
|
||||
assert(confirmation.visible)
|
||||
assert(
|
||||
confirmation.z_index
|
||||
> cooler_outer_wall.z_index
|
||||
sell_inventory.get_parent() == shop.get_node("%ShopCoolerMount")
|
||||
)
|
||||
var staged: Dictionary = sell_inventory.get("_staged")
|
||||
sell_inventory.call(
|
||||
"_stage",
|
||||
PlayerInventoryLayout.EntryKind.CATCH,
|
||||
reserved_catch.catch_id,
|
||||
)
|
||||
assert(staged.is_empty())
|
||||
sell_inventory.call(
|
||||
"_stage",
|
||||
PlayerInventoryLayout.EntryKind.CATCH,
|
||||
fish_catch.catch_id,
|
||||
)
|
||||
assert(staged.has(PlayerInventoryLayout.catch_key(fish_catch.catch_id)))
|
||||
assert((sell_inventory.get("_feedback") as Label).text.is_empty())
|
||||
var source_grid := sell_inventory.get("_inventory_grid") as GeneralInventoryGrid
|
||||
var staged_source_found := false
|
||||
for source_slot: GeneralInventorySlot in source_grid.get_slots():
|
||||
if source_slot.entry_identity == fish_catch.catch_id:
|
||||
staged_source_found = bool(source_slot.get("_staged"))
|
||||
break
|
||||
assert(staged_source_found)
|
||||
var feedback := sell_inventory.get("_feedback") as Label
|
||||
var total_label := sell_inventory.get("_total_label") as Label
|
||||
var sell_button := sell_inventory.get("_sell_button") as Button
|
||||
assert(feedback.get_index() < total_label.get_parent().get_index())
|
||||
assert(total_label.get_parent().get_index() < sell_button.get_index())
|
||||
_sale_result.clear()
|
||||
await _activate_pointer_control(confirm_button, ui_viewport)
|
||||
sell_inventory.call("_submit_sale")
|
||||
await process_frame
|
||||
assert(not _sale_result.is_empty() and bool(_sale_result[1]))
|
||||
assert(not player.inventory.contains_catch_id(fish_catch.catch_id))
|
||||
|
|
@ -929,7 +963,7 @@ func _test_fishing_shop_sale_ui(
|
|||
await process_frame
|
||||
assert(shop.visible and (shop.get_node("%ShopPanel") as Control).visible)
|
||||
assert(not (shop.get_node("%ShopCoolerPage") as Control).visible)
|
||||
assert(not player_menu.is_shop_cooler_mounted())
|
||||
assert(not sell_inventory.is_visible_in_tree())
|
||||
shop.close_shop()
|
||||
await shop.menu_visibility_changed
|
||||
assert(not shop.visible)
|
||||
|
|
|
|||
|
|
@ -55,6 +55,17 @@ func _initialize() -> void:
|
|||
assert(crab_net != null)
|
||||
assert(crab_net.icon != null)
|
||||
assert(crab_net.icon.resource_path.ends_with("/equipment/temp_net.png"))
|
||||
var shovel: ItemDataType = ItemCatalogResource.get_available_item_by_id(
|
||||
&"standard_shovel"
|
||||
)
|
||||
assert(shovel != null)
|
||||
assert(shovel.category == ItemDataType.Category.TOOL)
|
||||
assert(shovel.icon != null)
|
||||
assert(shovel.equippable)
|
||||
assert(shovel.hotbar_allowed)
|
||||
assert(FishingShopStockType.get_price(&"standard_shovel") == 75)
|
||||
assert(FishingShopStockType.get_stock_item_ids().has(&"standard_shovel"))
|
||||
assert(FishingShopStockType.is_permanent_unlock(&"standard_shovel", shovel))
|
||||
|
||||
var wallet := PlayerWalletType.new()
|
||||
wallet.current_balance = 250
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ func _validate_weight_based_display_scale() -> void:
|
|||
|
||||
|
||||
func _validate_catalog_and_pools() -> void:
|
||||
assert(Catalog.candidates.size() == 314)
|
||||
assert(Catalog.candidates.size() == 316)
|
||||
assert(PondPool.candidates.size() == 19)
|
||||
assert(OceanPool.candidates.size() == 34)
|
||||
var active_count: int = 0
|
||||
|
|
@ -142,7 +142,7 @@ func _validate_catalog_and_pools() -> void:
|
|||
inactive_count += 1
|
||||
assert(not fish.is_selectable())
|
||||
assert(fish.display_texture == null)
|
||||
assert(active_count == 54)
|
||||
assert(active_count == 56)
|
||||
assert(inactive_count == 260)
|
||||
var inactive_fish: FishDataType = Catalog.get_fish_by_id(&"bowfin")
|
||||
assert(inactive_fish != null and not inactive_fish.active)
|
||||
|
|
@ -455,6 +455,7 @@ func _validate_catches_and_authoritative_sale() -> void:
|
|||
1,
|
||||
"catalog_sale_%d" % catch_sequence,
|
||||
[loaded.to_network_dict()],
|
||||
[],
|
||||
PelicanBuyer,
|
||||
)
|
||||
assert(bool(sale_result.get("accepted", false)))
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ func _run() -> void:
|
|||
var hotbar_data: Dictionary = (parsed as Dictionary)["hotbar"]
|
||||
assert(typeof(hotbar_data.get("fish_slots")) == TYPE_ARRAY)
|
||||
assert(str((hotbar_data["fish_slots"] as Array)[1]) == fish_catch.catch_id)
|
||||
assert(int((parsed as Dictionary)["save_version"]) == 7)
|
||||
assert(int((parsed as Dictionary)["save_version"]) == 8)
|
||||
assert(
|
||||
int((parsed as Dictionary)["experience"]["total_experience"])
|
||||
== 125
|
||||
|
|
|
|||
|
|
@ -305,6 +305,7 @@ func _validate_catch_round_trip_and_sale() -> void:
|
|||
1,
|
||||
"quality_sale",
|
||||
[network_data],
|
||||
[],
|
||||
PelicanBuyer,
|
||||
)
|
||||
assert(bool(accepted.get("accepted", false)))
|
||||
|
|
@ -336,6 +337,7 @@ func _validate_catch_round_trip_and_sale() -> void:
|
|||
1,
|
||||
"quality_sale_forged",
|
||||
[forged],
|
||||
[],
|
||||
PelicanBuyer,
|
||||
)
|
||||
assert(not bool(rejected.get("accepted", false)))
|
||||
|
|
@ -431,7 +433,7 @@ func _validate_version_four_migration() -> void:
|
|||
version_four,
|
||||
4,
|
||||
)
|
||||
assert(int(migrated.get("save_version", -1)) == 7)
|
||||
assert(int(migrated.get("save_version", -1)) == 8)
|
||||
assert(int((migrated["experience"] as Dictionary)["total_experience"]) == 0)
|
||||
assert(
|
||||
is_equal_approx(
|
||||
|
|
|
|||
|
|
@ -288,9 +288,9 @@ func _validate_fur_color_ui(snapshot: Dictionary) -> void:
|
|||
var picker_popup := picker.get_popup()
|
||||
var picker_control := picker.get_picker()
|
||||
assert(color_panel != null and color_panel.size.x > 360.0)
|
||||
assert(channel_grid != null and channel_grid.columns == 4)
|
||||
assert(channel_grid != null and channel_grid.columns == 2)
|
||||
assert(channel_grid.get_child_count() == 4)
|
||||
assert(palette_grid != null and palette_grid.columns == 10)
|
||||
assert(palette_grid != null and palette_grid.columns == 6)
|
||||
assert(palette_grid.get_child_count() == 30)
|
||||
for option_button: Button in palette_grid.get_children():
|
||||
assert(
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
extends SceneTree
|
||||
|
||||
const PlayerMenuScene = preload("res://ui/player_menu.tscn")
|
||||
const HotbarScene = preload("res://ui/hotbar.tscn")
|
||||
const UIReferencePresentationType = preload(
|
||||
"res://ui/ui_reference_presentation.gd"
|
||||
)
|
||||
const EXPECTED_HOST_SIZE := Vector2(278.0, 484.0)
|
||||
const EXPECTED_ART_SIZE := Vector2(306.28125, 580.8)
|
||||
const EXPECTED_ART_POSITION := Vector2(-4.140625, -28.4)
|
||||
const INVENTORY_PANEL_RECT := Rect2(54.0, 166.0, 882.0, 484.0)
|
||||
const NOTEPAD_HOST_RECT := Rect2(952.0, 166.0, 278.0, 484.0)
|
||||
const LEGACY_COOLER_PANEL_RECT := Rect2(54.0, 166.0, 882.0, 484.0)
|
||||
const LEGACY_COOLER_NOTEPAD_RECT := Rect2(952.0, 166.0, 278.0, 484.0)
|
||||
const INVENTORY_PANEL_RECT := Rect2(199.0, 166.0, 882.0, 484.0)
|
||||
const NOTEPAD_HOST_RECT := Rect2(501.0, 166.0, 278.0, 484.0)
|
||||
const NOTEPAD_ART_RECT := Rect2(
|
||||
NOTEPAD_HOST_RECT.position + EXPECTED_ART_POSITION,
|
||||
EXPECTED_ART_SIZE,
|
||||
|
|
@ -57,6 +60,11 @@ func _run() -> void:
|
|||
+ InventoryNotepad.NOTEPAD_ART_OFFSET
|
||||
))
|
||||
_validate_shared_inventory_geometry(player_menu)
|
||||
_validate_first_open_inventory(player_menu)
|
||||
await _validate_inventory_grid_centering(player_menu, presentation_stage)
|
||||
_validate_utility_page_geometry(player_menu)
|
||||
await _validate_profile_content_bounds(player_menu)
|
||||
await _validate_hotbar_centering(presentation_stage)
|
||||
_validate_inventory_layering(player_menu)
|
||||
_validate_tackle_to_items_transition(player_menu)
|
||||
_validate_cooler_notepad_typography(player_menu)
|
||||
|
|
@ -103,23 +111,151 @@ func _apply_stage_layout(stage: Control, display_size: Vector2) -> void:
|
|||
|
||||
func _validate_shared_inventory_geometry(player_menu: PlayerMenu) -> void:
|
||||
for node_name: StringName in [
|
||||
&"CoolerOuterWall",
|
||||
&"BagOuterWall",
|
||||
&"TackleMainPanel",
|
||||
]:
|
||||
var main_panel := player_menu.get_node("%%%s" % node_name) as Control
|
||||
assert(main_panel != null)
|
||||
assert(main_panel.position.is_equal_approx(INVENTORY_PANEL_RECT.position))
|
||||
assert(main_panel.size.is_equal_approx(INVENTORY_PANEL_RECT.size))
|
||||
assert(
|
||||
main_panel.size.is_equal_approx(INVENTORY_PANEL_RECT.size),
|
||||
"%s was %s, expected %s" % [
|
||||
node_name,
|
||||
main_panel.size,
|
||||
INVENTORY_PANEL_RECT.size,
|
||||
],
|
||||
)
|
||||
var cooler_panel := player_menu.get_node("%CoolerOuterWall") as Control
|
||||
var cooler_notepad := player_menu.get_node("%DetailConstellation") as Control
|
||||
assert(cooler_panel.position.is_equal_approx(
|
||||
LEGACY_COOLER_PANEL_RECT.position
|
||||
))
|
||||
assert(cooler_panel.size.is_equal_approx(LEGACY_COOLER_PANEL_RECT.size))
|
||||
assert(cooler_notepad.position.is_equal_approx(
|
||||
LEGACY_COOLER_NOTEPAD_RECT.position
|
||||
))
|
||||
assert(cooler_notepad.size.is_equal_approx(
|
||||
LEGACY_COOLER_NOTEPAD_RECT.size
|
||||
))
|
||||
for node_name: StringName in [
|
||||
&"DetailConstellation",
|
||||
&"BagDetailConstellation",
|
||||
&"TackleDetailPanel",
|
||||
]:
|
||||
var host := player_menu.get_node("%%%s" % node_name) as Control
|
||||
assert(host != null)
|
||||
assert(host.position.is_equal_approx(Vector2(952.0, 166.0)))
|
||||
assert(host.position.is_equal_approx(NOTEPAD_HOST_RECT.position))
|
||||
assert(host.size.is_equal_approx(EXPECTED_HOST_SIZE))
|
||||
assert(is_equal_approx(INVENTORY_PANEL_RECT.get_center().x, 640.0))
|
||||
|
||||
|
||||
func _validate_first_open_inventory(player_menu: PlayerMenu) -> void:
|
||||
var legacy_slots: Array = player_menu.get("_bag_slot_nodes") as Array
|
||||
assert(legacy_slots.is_empty())
|
||||
var old_slot_nodes: Array[Node] = player_menu.find_children(
|
||||
"*", "BagStorageSlot", true, false
|
||||
)
|
||||
assert(old_slot_nodes.is_empty())
|
||||
|
||||
|
||||
func _validate_inventory_grid_centering(
|
||||
player_menu: PlayerMenu,
|
||||
stage: Control,
|
||||
) -> void:
|
||||
var grid := player_menu.get("_general_inventory_grid") as Control
|
||||
assert(grid != null)
|
||||
grid.custom_minimum_size = Vector2(782.0, 342.0)
|
||||
grid.size = grid.custom_minimum_size
|
||||
player_menu.call("_layout_general_inventory_grid")
|
||||
await process_frame
|
||||
assert(is_equal_approx(grid.position.y, 39.0))
|
||||
assert(
|
||||
is_equal_approx(
|
||||
grid.get_global_rect().get_center().x,
|
||||
stage.get_global_rect().get_center().x,
|
||||
),
|
||||
"inventory center %s did not match stage center %s" % [
|
||||
grid.get_global_rect().get_center().x,
|
||||
stage.get_global_rect().get_center().x,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
func _validate_utility_page_geometry(player_menu: PlayerMenu) -> void:
|
||||
for page_name: StringName in [
|
||||
&"TheNetPage",
|
||||
&"MailPage",
|
||||
&"ProfilePage",
|
||||
&"PlayersPage",
|
||||
]:
|
||||
var page := player_menu.get_node("%%%s" % page_name) as Control
|
||||
assert(page != null)
|
||||
var shell := page.find_child(
|
||||
"UtilityMainBox", true, false
|
||||
) as Control
|
||||
assert(shell != null)
|
||||
assert(shell.position.is_equal_approx(UtilityPageStyle.LAPTOP_RECT.position))
|
||||
assert(shell.size.is_equal_approx(UtilityPageStyle.LAPTOP_RECT.size))
|
||||
assert(is_equal_approx(shell.get_rect().get_center().x, 640.0))
|
||||
|
||||
|
||||
func _validate_profile_content_bounds(player_menu: PlayerMenu) -> void:
|
||||
var profile := player_menu.get_node("%ProfilePage") as ProfilePage
|
||||
assert(profile != null)
|
||||
var menu_was_visible: bool = player_menu.visible
|
||||
var profile_was_visible: bool = profile.visible
|
||||
player_menu.visible = true
|
||||
profile.visible = true
|
||||
await process_frame
|
||||
await process_frame
|
||||
var option_list := profile.get("_option_list") as Control
|
||||
var body := option_list.get_parent() as Control
|
||||
var preview := profile.get("_preview") as Control
|
||||
var shell := profile.find_child("UtilityMainBox", true, false) as Control
|
||||
assert(option_list != null and body != null and preview != null and shell != null)
|
||||
for category_id: String in ["fur_pattern", "voice"]:
|
||||
profile.call("_select_category", category_id)
|
||||
await process_frame
|
||||
await process_frame
|
||||
assert(
|
||||
body.get_combined_minimum_size().x <= body.size.x,
|
||||
"%s customization content overflowed its body: %s > %s" % [
|
||||
category_id,
|
||||
body.get_combined_minimum_size().x,
|
||||
body.size.x,
|
||||
],
|
||||
)
|
||||
assert(
|
||||
preview.get_global_rect().end.x
|
||||
<= shell.get_global_rect().end.x,
|
||||
"%s preview overflowed the utility content area" % category_id,
|
||||
)
|
||||
profile.visible = profile_was_visible
|
||||
player_menu.visible = menu_was_visible
|
||||
|
||||
|
||||
func _validate_hotbar_centering(parent: Control) -> void:
|
||||
var hotbar := HotbarScene.instantiate() as HotbarUI
|
||||
parent.add_child(hotbar)
|
||||
await process_frame
|
||||
var presentation := hotbar.get_node(
|
||||
"%HotbarPresentationScaleRoot"
|
||||
) as Control
|
||||
var field := hotbar.get_node("%BubbleField") as Control
|
||||
hotbar.set_player_menu_context(true)
|
||||
var displayed_center_x: float = (
|
||||
presentation.position.x
|
||||
+ field.get_rect().get_center().x * presentation.scale.x
|
||||
)
|
||||
assert(is_equal_approx(displayed_center_x, 640.0))
|
||||
var displayed_top: float = (
|
||||
presentation.position.y + field.position.y * presentation.scale.y
|
||||
)
|
||||
var displayed_bottom: float = (
|
||||
displayed_top + field.size.y * presentation.scale.y
|
||||
)
|
||||
assert(displayed_top < INVENTORY_PANEL_RECT.end.y)
|
||||
assert(displayed_bottom > INVENTORY_PANEL_RECT.end.y)
|
||||
hotbar.queue_free()
|
||||
|
||||
|
||||
func _validate_inventory_layering(player_menu: PlayerMenu) -> void:
|
||||
|
|
@ -136,13 +272,31 @@ func _validate_inventory_layering(player_menu: PlayerMenu) -> void:
|
|||
assert(cooler_panel != null)
|
||||
assert(tackle_panel != null)
|
||||
assert(inventory_tabs != null)
|
||||
assert(items_tab != null and items_tab.text == "Items")
|
||||
assert(bait_list != null and bait_list.columns == 3)
|
||||
assert(lure_list != null and lure_list.columns == 3)
|
||||
assert(items_tab != null and not items_tab.visible)
|
||||
assert(not (player_menu.get_node("%CoolerSubTab") as Button).visible)
|
||||
assert((player_menu.get_node("%BagSubTab") as Button).text == "Inventory")
|
||||
assert(bait_list != null and bait_list.columns == 4)
|
||||
assert(lure_list != null and lure_list.columns == 4)
|
||||
assert(bait_list.get_theme_constant("h_separation") == 28)
|
||||
assert(lure_list.get_theme_constant("h_separation") == 28)
|
||||
assert(sale_confirmation.z_index > cooler_panel.z_index)
|
||||
assert(sale_confirmation.z_index > tackle_panel.z_index)
|
||||
assert(
|
||||
(player_menu.get_node("%BagDetailConstellation") as Control).z_index
|
||||
> bag_panel.z_index
|
||||
)
|
||||
assert(
|
||||
(player_menu.get_node("%BagModalBlocker") as Control).z_index
|
||||
> bag_panel.z_index
|
||||
)
|
||||
assert(
|
||||
(player_menu.get_node("%BagDetailConstellation") as Control).z_index
|
||||
> (player_menu.get_node("%BagModalBlocker") as Control).z_index
|
||||
)
|
||||
assert(
|
||||
(player_menu.get_node("%TackleDetailPanel") as Control).z_index
|
||||
> tackle_panel.z_index
|
||||
)
|
||||
# The contextual Hotbar uses z=90 while the Player Menu is open.
|
||||
assert(sale_confirmation.z_index > 90)
|
||||
assert(sale_confirmation.position.is_equal_approx(Vector2(380.0, 265.0)))
|
||||
|
|
@ -159,25 +313,55 @@ func _validate_inventory_layering(player_menu: PlayerMenu) -> void:
|
|||
|
||||
|
||||
func _validate_tackle_to_items_transition(player_menu: PlayerMenu) -> void:
|
||||
var empty_state := player_menu.get_node("%BagEmptyState") as Label
|
||||
player_menu.set("_bag_view", PlayerMenu.BagView.EQUIPMENT)
|
||||
player_menu.call("_refresh_bag")
|
||||
assert(empty_state.text == "No equipment in your Bag.")
|
||||
var inventory_notepad := player_menu.get_node(
|
||||
"%BagDetailBubble"
|
||||
) as InventoryNotepad
|
||||
assert(inventory_notepad != null)
|
||||
assert(inventory_notepad.title_text == "inventory notes")
|
||||
player_menu.call(
|
||||
"_show_section_immediate", PlayerMenu.Section.TACKLE_BOX
|
||||
)
|
||||
player_menu.call("_show_bag_view", PlayerMenu.BagView.CONSUMABLES)
|
||||
assert(
|
||||
player_menu.get("_current_section") == PlayerMenu.Section.BAG
|
||||
)
|
||||
assert(
|
||||
player_menu.get("_bag_view") == PlayerMenu.BagView.CONSUMABLES
|
||||
)
|
||||
assert(empty_state.text == "No items in your Bag.")
|
||||
player_menu.call("_show_inventory_tab", 0)
|
||||
player_menu.call("_cancel_page_tween")
|
||||
player_menu.call(
|
||||
"_show_section_immediate", PlayerMenu.Section.COOLER
|
||||
player_menu.call("_show_section_immediate", PlayerMenu.Section.BAG)
|
||||
player_menu.call("_update_bag_detail")
|
||||
assert(player_menu.get("_current_section") == PlayerMenu.Section.BAG)
|
||||
assert(not (player_menu.get_node("%BagDetailConstellation") as Control).visible)
|
||||
assert(
|
||||
(player_menu.get_node("%BagSpriteDetailData") as Label).text
|
||||
== "select an item for details."
|
||||
)
|
||||
player_menu.visible = true
|
||||
player_menu.call("_set_content_interactive", true)
|
||||
var no_actions: Array[BaseButton] = []
|
||||
player_menu.call(
|
||||
"_open_inventory_notepad",
|
||||
PlayerMenu.Section.BAG,
|
||||
StringName("modal-test"),
|
||||
no_actions,
|
||||
)
|
||||
assert((player_menu.get_node("%BagDetailConstellation") as Control).visible)
|
||||
assert((player_menu.get_node("%BagModalBlocker") as Control).visible)
|
||||
assert(
|
||||
(player_menu.get_node("%BagModalBlocker") as Control).mouse_filter
|
||||
== Control.MOUSE_FILTER_STOP
|
||||
)
|
||||
assert(
|
||||
(player_menu.get_node("%InventoryTab") as Button).focus_mode
|
||||
== Control.FOCUS_NONE
|
||||
)
|
||||
assert(
|
||||
(player_menu.get_node("%BagSpriteDetailData") as Label).get_theme_font(
|
||||
"font"
|
||||
) == InventoryNotepad.NOTEPAD_FONT
|
||||
)
|
||||
player_menu.call(
|
||||
"_release_controller_ownership", false, false
|
||||
)
|
||||
assert(not (player_menu.get_node("%BagModalBlocker") as Control).visible)
|
||||
player_menu.call("_set_content_interactive", false)
|
||||
player_menu.visible = false
|
||||
player_menu.call("_cancel_page_tween")
|
||||
|
||||
|
||||
func _validate_cooler_notepad_typography(player_menu: PlayerMenu) -> void:
|
||||
|
|
@ -314,17 +498,16 @@ func _capture_inventory_pages(player_menu: PlayerMenu) -> void:
|
|||
return
|
||||
player_menu.visible = true
|
||||
var sections: Array[PlayerMenu.Section] = [
|
||||
PlayerMenu.Section.COOLER,
|
||||
PlayerMenu.Section.BAG,
|
||||
PlayerMenu.Section.TACKLE_BOX,
|
||||
]
|
||||
var suffixes: Array[String] = ["cooler", "equipment", "tackle"]
|
||||
var suffixes: Array[String] = ["inventory", "tackle"]
|
||||
for index: int in sections.size():
|
||||
player_menu.call("_show_section_immediate", sections[index])
|
||||
await process_frame
|
||||
await process_frame
|
||||
await _save_capture(suffixes[index])
|
||||
player_menu.call("_show_section_immediate", PlayerMenu.Section.COOLER)
|
||||
player_menu.call("_show_section_immediate", PlayerMenu.Section.BAG)
|
||||
var sale_confirmation := player_menu.get_node("%SaleConfirmation") as Control
|
||||
var confirmation_message := player_menu.get_node(
|
||||
"%ConfirmationMessage"
|
||||
|
|
|
|||
|
|
@ -1,31 +1,7 @@
|
|||
extends SceneTree
|
||||
|
||||
const BagItemSpriteType = preload(
|
||||
"res://ui/components/bubble_menu/bag_item_sprite.gd"
|
||||
)
|
||||
const BagStorageSlotType = preload(
|
||||
"res://ui/components/bubble_menu/bag_storage_slot.gd"
|
||||
)
|
||||
const Catalog: ItemCatalog = preload(
|
||||
"res://items/catalog/item_catalog.tres"
|
||||
)
|
||||
const OwnedItemType = preload("res://items/owned_item.gd")
|
||||
const PlayerBagType = preload("res://inventory/player_bag.gd")
|
||||
const PlayerHotbarType = preload("res://inventory/player_hotbar.gd")
|
||||
const PlayerMenuScene = preload("res://ui/player_menu.tscn")
|
||||
const PlayerMenuType = preload("res://ui/player_menu.gd")
|
||||
|
||||
const EQUIPMENT_IDS: Array[StringName] = [
|
||||
&"basic_fishing_rod",
|
||||
&"art_kit",
|
||||
&"crab_net",
|
||||
&"magnet",
|
||||
]
|
||||
const ITEM_IDS: Array[StringName] = [
|
||||
&"coffee",
|
||||
&"energy_drink",
|
||||
&"snack",
|
||||
]
|
||||
const Catalog: ItemCatalog = preload("res://items/catalog/item_catalog.tres")
|
||||
const Bluegill: FishData = preload("res://fish/species/bluegill/bluegill.tres")
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
|
|
@ -33,140 +9,119 @@ func _initialize() -> void:
|
|||
|
||||
|
||||
func _run() -> void:
|
||||
root.size = Vector2i(1280, 720)
|
||||
var bag := PlayerBagType.new()
|
||||
var state := Node.new()
|
||||
root.add_child(state)
|
||||
var bag := PlayerBag.new()
|
||||
var catches := FishInventory.new()
|
||||
var storage_capacity := PlayerCoolerCapacity.new()
|
||||
var layout := PlayerInventoryLayout.new()
|
||||
var hotbar := PlayerHotbar.new()
|
||||
for node: Node in [bag, catches, storage_capacity, layout, hotbar]:
|
||||
state.add_child(node)
|
||||
bag.setup(Catalog)
|
||||
for item_id: StringName in EQUIPMENT_IDS + ITEM_IDS:
|
||||
assert(bag.add_item(item_id))
|
||||
for index: int in EQUIPMENT_IDS.size():
|
||||
assert(bag.get_storage_slot(EQUIPMENT_IDS[index]) == index)
|
||||
for index: int in ITEM_IDS.size():
|
||||
assert(bag.get_storage_slot(ITEM_IDS[index]) == index)
|
||||
layout.setup(bag, catches, Catalog, storage_capacity)
|
||||
bag.set_inventory_layout(layout)
|
||||
catches.set_inventory_layout(layout)
|
||||
hotbar.setup(bag, Catalog, catches, layout)
|
||||
|
||||
assert(bag.move_item_to_storage_slot(&"basic_fishing_rod", 14))
|
||||
assert(bag.get_storage_slot(&"basic_fishing_rod") == 14)
|
||||
assert(bag.move_item_to_storage_slot(&"crab_net", 14))
|
||||
assert(bag.get_storage_slot(&"crab_net") == 14)
|
||||
assert(bag.get_storage_slot(&"basic_fishing_rod") == 2)
|
||||
var saved_record: Dictionary = bag.get_owned_item(
|
||||
&"crab_net"
|
||||
).to_save_dict()
|
||||
assert(int(saved_record.get("storage_slot", -1)) == 14)
|
||||
assert(bag.add_item(&"basic_fishing_rod", 1))
|
||||
assert(bag.add_item(&"coffee", 3))
|
||||
var fish_catch := _make_bluegill()
|
||||
assert(catches.add_catch(fish_catch))
|
||||
|
||||
var legacy_equipment := OwnedItemType.new()
|
||||
legacy_equipment.item_id = &"basic_fishing_rod"
|
||||
var legacy_item := OwnedItemType.new()
|
||||
legacy_item.item_id = &"coffee"
|
||||
var legacy_records: Array[OwnedItemType] = [
|
||||
legacy_equipment,
|
||||
legacy_item,
|
||||
]
|
||||
var legacy_bag := PlayerBagType.new()
|
||||
legacy_bag.setup(Catalog)
|
||||
assert(legacy_bag.replace_all_items(legacy_records))
|
||||
assert(legacy_bag.get_storage_slot(&"basic_fishing_rod") == 0)
|
||||
assert(legacy_bag.get_storage_slot(&"coffee") == 0)
|
||||
legacy_bag.free()
|
||||
|
||||
var menu := PlayerMenuScene.instantiate() as PlayerMenu
|
||||
root.add_child(menu)
|
||||
await process_frame
|
||||
menu.set("_bag", bag)
|
||||
var hotbar := PlayerHotbarType.new()
|
||||
hotbar.setup(bag, Catalog)
|
||||
menu.set("_hotbar", hotbar)
|
||||
menu.set("_item_catalog", Catalog)
|
||||
menu.set("_bag_view", PlayerMenuType.BagView.EQUIPMENT)
|
||||
menu.visible = true
|
||||
menu.call("_show_section_immediate", PlayerMenuType.Section.BAG)
|
||||
menu.call("_set_content_interactive", true)
|
||||
menu.call("_refresh_bag")
|
||||
await process_frame
|
||||
_validate_grid(menu, EQUIPMENT_IDS.size())
|
||||
|
||||
var item_nodes: Dictionary = menu.get("_bag_item_nodes")
|
||||
var crab_node := item_nodes.get(&"crab_net") as BagItemSpriteType
|
||||
assert(crab_node != null)
|
||||
menu.set(
|
||||
"_controller_ownership",
|
||||
PlayerMenuType.ControllerOwnership.ITEM_LIST,
|
||||
var inventory_grid := GeneralInventoryGrid.new()
|
||||
inventory_grid.set_slot_presentation(Vector2(78.0, 78.0), 10)
|
||||
root.add_child(inventory_grid)
|
||||
inventory_grid.setup(
|
||||
layout,
|
||||
bag,
|
||||
catches,
|
||||
hotbar,
|
||||
Catalog,
|
||||
PlayerInventoryLayout.InventoryContainer.INVENTORY,
|
||||
)
|
||||
menu.call("_apply_inventory_controller_zone_focus_modes")
|
||||
crab_node.grab_focus()
|
||||
assert(bool(menu.call("_try_begin_controller_storage_placement")))
|
||||
await process_frame
|
||||
assert(
|
||||
menu.get("_controller_ownership")
|
||||
== PlayerMenuType.ControllerOwnership.STORAGE_PLACEMENT
|
||||
)
|
||||
var slots: Array = menu.get("_bag_slot_nodes")
|
||||
var bottom_slot := slots[12] as BagStorageSlotType
|
||||
bottom_slot.grab_focus()
|
||||
var down := InputEventAction.new()
|
||||
down.action = &"ui_down"
|
||||
down.pressed = true
|
||||
assert(bool(menu.call("_handle_controller_ownership_input", down)))
|
||||
assert(
|
||||
menu.get("_controller_ownership")
|
||||
== PlayerMenuType.ControllerOwnership.HOTBAR_PLACEMENT
|
||||
)
|
||||
var up := InputEventAction.new()
|
||||
up.action = &"ui_up"
|
||||
up.pressed = true
|
||||
assert(bool(menu.call("_handle_controller_ownership_input", up)))
|
||||
assert(
|
||||
menu.get("_controller_ownership")
|
||||
== PlayerMenuType.ControllerOwnership.STORAGE_PLACEMENT
|
||||
var storage_grid := GeneralInventoryGrid.new()
|
||||
root.add_child(storage_grid)
|
||||
storage_grid.setup(
|
||||
layout,
|
||||
bag,
|
||||
catches,
|
||||
hotbar,
|
||||
Catalog,
|
||||
PlayerInventoryLayout.InventoryContainer.STORAGE,
|
||||
)
|
||||
await process_frame
|
||||
assert(root.gui_get_focus_owner() == bottom_slot)
|
||||
assert(StringName(menu.get("_controller_storage_identity")) == &"crab_net")
|
||||
var target_slot := slots[7] as BagStorageSlotType
|
||||
target_slot.grab_focus()
|
||||
menu.call("_confirm_controller_storage_placement")
|
||||
await process_frame
|
||||
assert(bag.get_storage_slot(&"crab_net") == 7)
|
||||
assert(
|
||||
menu.get("_controller_ownership")
|
||||
== PlayerMenuType.ControllerOwnership.ITEM_LIST
|
||||
|
||||
var inventory_slots: Array = inventory_grid.get("_slots")
|
||||
var storage_slots: Array = storage_grid.get("_slots")
|
||||
assert(inventory_slots.size() == 36)
|
||||
assert(inventory_grid.get_slots().size() == 9)
|
||||
assert(inventory_grid.custom_minimum_size == Vector2(782.0, 342.0))
|
||||
assert(storage_capacity.get_capacity() == 9)
|
||||
assert(storage_slots.size() == 72)
|
||||
assert(storage_grid.get_slots().size() == 9)
|
||||
assert((storage_slots[0] as Control).custom_minimum_size == Vector2(52.0, 52.0))
|
||||
assert((storage_slots[9] as GeneralInventorySlot).disabled)
|
||||
|
||||
var coffee_slot := _find_slot(inventory_slots, &"coffee")
|
||||
assert(coffee_slot != null)
|
||||
(inventory_slots[7] as GeneralInventorySlot).call(
|
||||
"_drop_data",
|
||||
Vector2.ZERO,
|
||||
{"kind": "bag_item", "item_id": "coffee"},
|
||||
)
|
||||
assert(int(layout.get_entry(
|
||||
PlayerInventoryLayout.EntryKind.ITEM, &"coffee"
|
||||
).get("slot", -1)) == 7)
|
||||
assert(coffee_slot.entry_identity.is_empty())
|
||||
|
||||
menu.call("_show_bag_view", PlayerMenuType.BagView.CONSUMABLES)
|
||||
await process_frame
|
||||
_validate_grid(menu, ITEM_IDS.size())
|
||||
menu.call("_on_bag_item_dropped", &"coffee", 12)
|
||||
await process_frame
|
||||
assert(bag.get_storage_slot(&"coffee") == 12)
|
||||
(storage_slots[0] as GeneralInventorySlot).call(
|
||||
"_drop_data",
|
||||
Vector2.ZERO,
|
||||
{"kind": "cooler_fish", "catch_id": String(fish_catch.catch_id)},
|
||||
)
|
||||
assert(layout.get_container(
|
||||
PlayerInventoryLayout.EntryKind.CATCH, fish_catch.catch_id
|
||||
) == PlayerInventoryLayout.InventoryContainer.STORAGE)
|
||||
|
||||
assert(hotbar.assign_item(0, &"basic_fishing_rod"))
|
||||
assert(not layout.is_item_in_inventory(&"basic_fishing_rod"))
|
||||
(inventory_slots[8] as GeneralInventorySlot).call(
|
||||
"_drop_data",
|
||||
Vector2.ZERO,
|
||||
{"kind": "hotbar_slot", "slot_index": 0},
|
||||
)
|
||||
assert(hotbar.get_item_id(0).is_empty())
|
||||
assert(layout.is_item_in_inventory(&"basic_fishing_rod"))
|
||||
assert(int(layout.get_entry(
|
||||
PlayerInventoryLayout.EntryKind.ITEM, &"basic_fishing_rod"
|
||||
).get("slot", -1)) == 8)
|
||||
|
||||
menu.queue_free()
|
||||
hotbar.free()
|
||||
bag.free()
|
||||
await process_frame
|
||||
print("Inventory storage validation: PASS")
|
||||
state.free()
|
||||
inventory_grid.free()
|
||||
storage_grid.free()
|
||||
quit()
|
||||
|
||||
|
||||
func _validate_grid(menu: PlayerMenu, expected_items: int) -> void:
|
||||
var slots: Array = menu.get("_bag_slot_nodes")
|
||||
assert(slots.size() == PlayerBagType.MIN_STORAGE_SLOT_COUNT)
|
||||
var seen_positions: Dictionary[Vector2, bool] = {}
|
||||
for index: int in slots.size():
|
||||
var slot := slots[index] as BagStorageSlotType
|
||||
assert(slot != null)
|
||||
assert(slot.storage_slot_index == index)
|
||||
assert(not seen_positions.has(slot.position))
|
||||
seen_positions[slot.position] = true
|
||||
var normal := slot.get_theme_stylebox("normal") as StyleBoxFlat
|
||||
assert(normal != null)
|
||||
assert(normal.bg_color.a >= 0.7)
|
||||
assert((slots[1] as Control).position.x > (slots[0] as Control).position.x)
|
||||
assert((slots[5] as Control).position.y > (slots[0] as Control).position.y)
|
||||
var item_nodes := menu.get("_bag_item_nodes") as Dictionary
|
||||
assert(
|
||||
item_nodes.size() == expected_items,
|
||||
"expected %d visible bag items, found %d: %s" % [
|
||||
expected_items,
|
||||
item_nodes.size(),
|
||||
str(item_nodes.keys()),
|
||||
],
|
||||
func _make_bluegill() -> FishCatch:
|
||||
var fish_catch := FishCatch.new()
|
||||
fish_catch.fish = Bluegill
|
||||
fish_catch.fish_id = Bluegill.id
|
||||
fish_catch.catch_id = &"bluegill:inventory_storage_test"
|
||||
fish_catch.weight_lb = Bluegill.get_minimum_weight()
|
||||
fish_catch.display_scale = Bluegill.get_display_scale_for_weight(
|
||||
fish_catch.weight_lb
|
||||
)
|
||||
fish_catch.sale_value = Bluegill.get_sale_value_for_weight(
|
||||
fish_catch.weight_lb
|
||||
)
|
||||
return fish_catch
|
||||
|
||||
|
||||
func _find_slot(slots: Array, identity: StringName) -> GeneralInventorySlot:
|
||||
for candidate: Variant in slots:
|
||||
var slot := candidate as GeneralInventorySlot
|
||||
if slot != null and slot.entry_identity == identity:
|
||||
return slot
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ func _run() -> void:
|
|||
save_file.close()
|
||||
assert(typeof(parsed) == TYPE_DICTIONARY)
|
||||
var save_data: Dictionary = parsed
|
||||
assert(int(save_data.get("save_version", -1)) == 7)
|
||||
assert(int(save_data.get("save_version", -1)) == 8)
|
||||
assert(PlayerJobService.validate_save_data(save_data.get("jobs", {})))
|
||||
|
||||
_validate_pause_session_switch(main, session)
|
||||
|
|
|
|||
|
|
@ -108,9 +108,9 @@ func _validate_save_round_trip(
|
|||
var player := main.get("_player") as Player
|
||||
var catalog := main.get("fish_catalog") as FishPool
|
||||
assert(catalog != null)
|
||||
assert(catalog.candidates.size() == 314)
|
||||
assert(catalog.candidates.size() == 316)
|
||||
var active_species := LogbookCatalog.ordered_species(catalog.candidates)
|
||||
assert(active_species.size() == 54)
|
||||
assert(active_species.size() == 56)
|
||||
for index: int in 4:
|
||||
_add_test_catch(player, active_species[index])
|
||||
assert(save_manager.save_now())
|
||||
|
|
@ -131,7 +131,7 @@ func _validate_save_round_trip(
|
|||
assert(player.inventory.replace_all_catches(no_catches, 1))
|
||||
assert(player.collection_log.replace_discovered_ids(no_discoveries))
|
||||
assert(save_manager.load_player_data())
|
||||
assert(player.inventory.get_all_catches().size() == 54)
|
||||
assert(player.inventory.get_all_catches().size() == 56)
|
||||
for fish: FishData in active_species:
|
||||
assert(player.inventory.get_count(fish.id) == 1)
|
||||
assert(player.collection_log.has_discovered(fish.id))
|
||||
|
|
|
|||
|
|
@ -40,9 +40,9 @@ func _run() -> void:
|
|||
|
||||
|
||||
func _validate_catalog() -> void:
|
||||
assert(CatalogResource.candidates.size() == 314)
|
||||
assert(CatalogResource.candidates.size() == 316)
|
||||
var ordered := LogbookCatalog.ordered_species(CatalogResource.candidates)
|
||||
assert(ordered.size() == 54)
|
||||
assert(ordered.size() == 56)
|
||||
var previous_number: int = 0
|
||||
var catalog_numbers: Dictionary[int, bool] = {}
|
||||
for fish: FishDataType in CatalogResource.candidates:
|
||||
|
|
@ -113,6 +113,10 @@ func _validate_page() -> void:
|
|||
LogbookCatalog.Category.OTHER,
|
||||
])
|
||||
assert(category_tabs.size() == category_tab_categories.size())
|
||||
assert(
|
||||
LogbookCatalog.category_label(LogbookCatalog.Category.OTHER)
|
||||
== "Insects"
|
||||
)
|
||||
for tab_node: Variant in category_tabs:
|
||||
var category_tab := tab_node as Button
|
||||
assert(category_tab.size == LogbookPage.LOGBOOK_TAB_SIZE)
|
||||
|
|
@ -203,16 +207,18 @@ func _validate_page() -> void:
|
|||
|
||||
page.call("_select_category", LogbookCatalog.Category.SHELLFISH)
|
||||
await create_timer(0.25).timeout
|
||||
assert((page.get("_entry_buttons") as Dictionary).size() == 1)
|
||||
assert((page.get("_entry_buttons") as Dictionary).size() == 2)
|
||||
assert(
|
||||
(page.get("_entry_buttons") as Dictionary).has(&"unknown_3906")
|
||||
)
|
||||
assert(
|
||||
(page.get("_entry_buttons") as Dictionary).has(&"unknown_6406")
|
||||
)
|
||||
page.call("_select_category", LogbookCatalog.Category.OTHER)
|
||||
await create_timer(0.25).timeout
|
||||
assert((page.get("_entry_buttons") as Dictionary).is_empty())
|
||||
assert((page.get("_entry_buttons") as Dictionary).size() == 1)
|
||||
assert(
|
||||
(page.get("_empty_state") as Label).text
|
||||
== "No entries available."
|
||||
(page.get("_entry_buttons") as Dictionary).has(&"unknown_8001")
|
||||
)
|
||||
page.call("_select_category", LogbookCatalog.Category.FRESH_WATER)
|
||||
await create_timer(0.25).timeout
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ func _validate_save_migration() -> void:
|
|||
version_five,
|
||||
5,
|
||||
)
|
||||
assert(int(migrated.get("save_version", -1)) == 7)
|
||||
assert(int(migrated.get("save_version", -1)) == 8)
|
||||
var experience_data: Dictionary = migrated.get("experience", {})
|
||||
assert(int(experience_data.get("total_experience", -1)) == 0)
|
||||
var world_data: Dictionary = migrated.get("world", {})
|
||||
|
|
|
|||
|
|
@ -62,10 +62,14 @@ func _run() -> void:
|
|||
assert(StringName(player.get("_animation_action_id")).is_empty())
|
||||
assert(animation_player.current_animation == &"idle")
|
||||
Input.action_press(&"sneak")
|
||||
var visuals := player.get_node("Visuals") as Node3D
|
||||
|
||||
assert(player.play_net_strike_visual())
|
||||
player.resolve_net_strike_visual(true)
|
||||
player.net_success_contact_pause_duration = 0.05
|
||||
player.showcase_turn_duration = 0.05
|
||||
player.showcase_camera_transition_duration = 0.05
|
||||
player.net_showcase_camera_yaw_offset = 0.0
|
||||
var crab_catch := FishCatch.new()
|
||||
crab_catch.fish = CrabBrown
|
||||
crab_catch.fish_id = CrabBrown.id
|
||||
|
|
@ -89,9 +93,27 @@ func _run() -> void:
|
|||
await contact_pause.finished
|
||||
assert(StringName(player.get("_animation_action_id")).is_empty())
|
||||
assert(bool(player.get("_showcase_animation_active")))
|
||||
var stored_showcase_rotation: Vector3 = player.get(
|
||||
"_showcase_visual_rotation"
|
||||
)
|
||||
await create_timer(0.1).timeout
|
||||
assert(
|
||||
absf(wrapf(
|
||||
visuals.rotation.y - stored_showcase_rotation.y,
|
||||
-PI,
|
||||
PI,
|
||||
)) > 3.0
|
||||
)
|
||||
var catch_display := player.get("_catch_display") as Node3D
|
||||
assert(catch_display != null and catch_display.visible)
|
||||
player.end_catch_showcase(Callable(), true)
|
||||
player.end_catch_showcase(Callable(), true, true)
|
||||
assert(
|
||||
absf(wrapf(
|
||||
visuals.rotation.y - stored_showcase_rotation.y,
|
||||
-PI,
|
||||
PI,
|
||||
)) < 0.01
|
||||
)
|
||||
|
||||
Input.action_release(&"sneak")
|
||||
player.queue_free()
|
||||
|
|
|
|||
|
|
@ -45,6 +45,17 @@ func _run() -> void:
|
|||
for owned: OwnedItem in tackle_items:
|
||||
sorted_ids.append(owned.item_id)
|
||||
assert(sorted_ids == EXPECTED_BAIT_ORDER)
|
||||
var bag := PlayerBag.new()
|
||||
bag.setup(Catalog)
|
||||
assert(bag.add_item(&"worms", 1))
|
||||
assert(bag.add_item(&"shrimp", 1))
|
||||
var worms_slot: int = bag.get_storage_slot(&"worms")
|
||||
var shrimp_slot: int = bag.get_storage_slot(&"shrimp")
|
||||
assert(worms_slot >= 0 and shrimp_slot >= 0 and worms_slot != shrimp_slot)
|
||||
assert(bag.move_item_to_storage_slot(&"worms", shrimp_slot))
|
||||
assert(bag.get_storage_slot(&"worms") == shrimp_slot)
|
||||
assert(bag.get_storage_slot(&"shrimp") == worms_slot)
|
||||
bag.free()
|
||||
player_menu.free()
|
||||
print("Tackle order validation: PASS")
|
||||
quit()
|
||||
|
|
|
|||
99
tests/tree_gathering_prototype_validation.gd
Normal file
99
tests/tree_gathering_prototype_validation.gd
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
extends SceneTree
|
||||
|
||||
const StarterIslandScene = preload(
|
||||
"res://world/regions/starter_island_region.tscn"
|
||||
)
|
||||
const Gatherables: GatherableCatalog = preload(
|
||||
"res://gathering/catalog/gatherable_catalog.tres"
|
||||
)
|
||||
const FishCatalog: FishPool = preload("res://fish/pools/fish_catalog.tres")
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
_validate_beetle_data()
|
||||
await _validate_tree_anchors()
|
||||
_validate_anchored_presentation()
|
||||
_validate_three_dimensional_targeting()
|
||||
print("Tree gathering prototype validation: PASS")
|
||||
quit()
|
||||
|
||||
|
||||
func _validate_beetle_data() -> void:
|
||||
var beetle: GatherableData = Gatherables.get_entry(&"beetle_stag_common")
|
||||
assert(beetle != null and beetle.is_available())
|
||||
assert(beetle.catch_data == FishCatalog.get_fish_by_id(&"beetle_stag_common"))
|
||||
assert(beetle.catch_data.display_texture != null)
|
||||
assert(beetle.catch_data.collection_method == FishData.CollectionMethod.NET)
|
||||
assert(beetle.catch_data.collection_group == &"Beetles")
|
||||
assert(beetle.required_tool_id == &"crab_net")
|
||||
assert(beetle.spawn_anchor_set_id == &"starter_reachable_tree_trunks")
|
||||
assert(beetle.population == 3)
|
||||
assert(is_equal_approx(beetle.sprite_pixel_size, 0.005))
|
||||
assert(beetle.is_stationary_spawn())
|
||||
assert(not beetle.can_be_scared())
|
||||
|
||||
|
||||
func _validate_tree_anchors() -> void:
|
||||
var region := StarterIslandScene.instantiate() as WorldRegion
|
||||
root.add_child(region)
|
||||
await process_frame
|
||||
var anchor_set: GatherableAnchorSet3D = region.get_gatherable_anchor_set(
|
||||
&"starter_reachable_tree_trunks"
|
||||
)
|
||||
assert(anchor_set != null)
|
||||
var positions: PackedVector3Array = anchor_set.get_spawn_positions()
|
||||
assert(positions.size() == 8)
|
||||
for position: Vector3 in positions:
|
||||
assert(position.is_finite())
|
||||
assert(position.y >= 4.3 and position.y <= 4.5)
|
||||
region.queue_free()
|
||||
|
||||
|
||||
func _validate_anchored_presentation() -> void:
|
||||
var beetle: GatherableData = Gatherables.get_entry(&"beetle_stag_common")
|
||||
var presentation := WorldGatherable.new()
|
||||
root.add_child(presentation)
|
||||
presentation.configure("beetle-visual", beetle, Vector3.ZERO, 0.0)
|
||||
var sprite := presentation.get_node("GatherableSprite") as Sprite3D
|
||||
assert(sprite != null)
|
||||
assert(is_zero_approx(sprite.position.y))
|
||||
assert(is_equal_approx(sprite.pixel_size, 0.005))
|
||||
assert(not sprite.shaded)
|
||||
assert(sprite.billboard == BaseMaterial3D.BILLBOARD_ENABLED)
|
||||
assert(sprite.texture_filter == BaseMaterial3D.TEXTURE_FILTER_NEAREST)
|
||||
presentation.queue_free()
|
||||
|
||||
|
||||
func _validate_three_dimensional_targeting() -> void:
|
||||
var service := NetworkWorldSpawnService.new()
|
||||
var beetle: GatherableData = Gatherables.get_entry(&"beetle_stag_common")
|
||||
service.set(
|
||||
"_entities",
|
||||
{
|
||||
"beetle-test": {
|
||||
"entity_id": "beetle-test",
|
||||
"type_id": &"beetle_stag_common",
|
||||
"data": beetle,
|
||||
"position": Vector3(2.0, 4.4, 3.0),
|
||||
"locked": false,
|
||||
},
|
||||
},
|
||||
)
|
||||
assert(
|
||||
service.find_capture_target(
|
||||
Vector3(2.0, 4.4, 3.0),
|
||||
&"crab_net",
|
||||
)
|
||||
== "beetle-test"
|
||||
)
|
||||
assert(
|
||||
service.find_capture_target(
|
||||
Vector3(2.0, 3.0, 3.0),
|
||||
&"crab_net",
|
||||
).is_empty()
|
||||
)
|
||||
service.free()
|
||||
1
tests/tree_gathering_prototype_validation.gd.uid
Normal file
1
tests/tree_gathering_prototype_validation.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://d0g1xubbi48n
|
||||
199
tests/unified_inventory_validation.gd
Normal file
199
tests/unified_inventory_validation.gd
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
extends SceneTree
|
||||
|
||||
const ItemCatalogResource: ItemCatalog = preload(
|
||||
"res://items/catalog/item_catalog.tres"
|
||||
)
|
||||
const Bluegill: FishData = preload(
|
||||
"res://fish/species/bluegill/bluegill.tres"
|
||||
)
|
||||
const MainShopBuyer: FishBuyerProfile = preload(
|
||||
"res://economy/buyers/main_fishing_shop.tres"
|
||||
)
|
||||
const FishCatalogResource: FishPool = preload(
|
||||
"res://fish/pools/fish_catalog.tres"
|
||||
)
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var root := Node.new()
|
||||
get_root().add_child(root)
|
||||
var bag := PlayerBag.new()
|
||||
var catches := FishInventory.new()
|
||||
var capacity := PlayerCoolerCapacity.new()
|
||||
var layout := PlayerInventoryLayout.new()
|
||||
var hotbar := PlayerHotbar.new()
|
||||
for node: Node in [bag, catches, capacity, layout, hotbar]:
|
||||
root.add_child(node)
|
||||
bag.setup(ItemCatalogResource)
|
||||
layout.setup(bag, catches, ItemCatalogResource, capacity)
|
||||
bag.set_inventory_layout(layout)
|
||||
catches.set_inventory_layout(layout)
|
||||
hotbar.setup(bag, ItemCatalogResource, catches, layout)
|
||||
assert(PlayerInventoryLayout.INVENTORY_CAPACITIES == [9, 18, 27, 36])
|
||||
assert(PlayerCoolerCapacity.CAPACITIES == [9, 18, 27, 36, 45, 54, 63, 72])
|
||||
|
||||
assert(bag.add_item(&"coffee", 3))
|
||||
assert(layout.get_inventory_count() == 1)
|
||||
assert(layout.get_inventory_capacity() == 9)
|
||||
assert(layout.get_hotbar_count() == 0)
|
||||
assert(hotbar.assign_item(2, &"coffee"))
|
||||
assert(layout.get_inventory_count() == 0)
|
||||
assert(layout.get_hotbar_count() == 1)
|
||||
assert(
|
||||
layout.get_container(PlayerInventoryLayout.EntryKind.ITEM, &"coffee")
|
||||
== PlayerInventoryLayout.InventoryContainer.HOTBAR
|
||||
)
|
||||
assert(hotbar.clear_slot(2))
|
||||
assert(layout.get_inventory_count() == 1)
|
||||
assert(layout.get_hotbar_count() == 0)
|
||||
|
||||
var fish_catch := FishCatch.new()
|
||||
fish_catch.fish = Bluegill
|
||||
fish_catch.fish_id = Bluegill.id
|
||||
fish_catch.catch_id = &"bluegill:unified_inventory_test"
|
||||
fish_catch.weight_lb = Bluegill.get_minimum_weight()
|
||||
fish_catch.display_scale = Bluegill.get_display_scale_for_weight(
|
||||
fish_catch.weight_lb
|
||||
)
|
||||
fish_catch.sale_value = Bluegill.get_sale_value_for_weight(
|
||||
fish_catch.weight_lb
|
||||
)
|
||||
assert(catches.add_catch(fish_catch))
|
||||
assert(layout.get_inventory_count() == 2)
|
||||
assert(layout.move_entry_to_first_free(
|
||||
PlayerInventoryLayout.EntryKind.CATCH,
|
||||
fish_catch.catch_id,
|
||||
PlayerInventoryLayout.InventoryContainer.STORAGE,
|
||||
))
|
||||
assert(layout.get_inventory_count() == 1)
|
||||
assert(layout.get_storage_count() == 1)
|
||||
assert(layout.get_storage_capacity() == 9)
|
||||
var inventory_grid := GeneralInventoryGrid.new()
|
||||
root.add_child(inventory_grid)
|
||||
inventory_grid.set_slot_presentation(Vector2(78.0, 78.0), 10)
|
||||
inventory_grid.setup(
|
||||
layout,
|
||||
bag,
|
||||
catches,
|
||||
hotbar,
|
||||
ItemCatalogResource,
|
||||
PlayerInventoryLayout.InventoryContainer.INVENTORY,
|
||||
)
|
||||
var all_inventory_slots: Array = inventory_grid.get("_slots")
|
||||
assert(all_inventory_slots.size() == 36)
|
||||
assert(inventory_grid.get_slots().size() == 9)
|
||||
for slot_index: int in all_inventory_slots.size():
|
||||
var slot := all_inventory_slots[slot_index] as GeneralInventorySlot
|
||||
assert(slot.custom_minimum_size == Vector2(78.0, 78.0))
|
||||
assert(slot.disabled == (slot_index >= 9))
|
||||
assert(slot.tooltip_text.is_empty())
|
||||
var normal := slot.get_theme_stylebox("normal") as StyleBoxFlat
|
||||
assert(normal != null and normal.corner_radius_top_left == 39)
|
||||
var locked_slot := all_inventory_slots[9] as GeneralInventorySlot
|
||||
var locked_icon := locked_slot.get("_icon") as TextureRect
|
||||
assert(locked_icon != null)
|
||||
assert(is_equal_approx(locked_icon.size.x, 24.0))
|
||||
assert(is_equal_approx(locked_icon.size.y, 24.0))
|
||||
assert(is_equal_approx(locked_icon.modulate.a, 0.18))
|
||||
var staged_slot := all_inventory_slots[0] as GeneralInventorySlot
|
||||
staged_slot.set_staged(true)
|
||||
assert(
|
||||
(staged_slot.get_theme_stylebox("normal") as StyleBoxFlat).bg_color
|
||||
== Color(UtilityPageStyle.OCEAN_SELECTED, 0.92)
|
||||
)
|
||||
staged_slot.set_staged(false)
|
||||
var sale_tray_slot := ShopSaleTraySlot.new()
|
||||
root.add_child(sale_tray_slot)
|
||||
assert(
|
||||
sale_tray_slot.custom_minimum_size
|
||||
== GeneralInventoryGrid.DEFAULT_SLOT_SIZE
|
||||
)
|
||||
var sale_tray_style := sale_tray_slot.get_theme_stylebox(
|
||||
"normal"
|
||||
) as StyleBoxFlat
|
||||
assert(sale_tray_style != null)
|
||||
assert(sale_tray_style.corner_radius_top_left == 26)
|
||||
var wallet := PlayerWallet.new()
|
||||
root.add_child(wallet)
|
||||
assert(wallet.restore_balance(15000))
|
||||
assert(layout.get_next_backpack_cost() == 1500)
|
||||
assert(layout.purchase_backpack(wallet))
|
||||
assert(layout.get_inventory_capacity() == 18)
|
||||
assert(inventory_grid.get_slots().size() == 18)
|
||||
assert(layout.get_next_backpack_cost() == 4500)
|
||||
assert(layout.purchase_backpack(wallet))
|
||||
assert(layout.get_inventory_capacity() == 27)
|
||||
assert(inventory_grid.get_slots().size() == 27)
|
||||
assert(layout.get_next_backpack_cost() == 9000)
|
||||
assert(layout.purchase_backpack(wallet))
|
||||
assert(layout.get_inventory_capacity() == 36)
|
||||
assert(inventory_grid.get_slots().size() == 36)
|
||||
assert(layout.get_next_backpack_cost() == -1)
|
||||
var storage_grid := GeneralInventoryGrid.new()
|
||||
root.add_child(storage_grid)
|
||||
storage_grid.setup(
|
||||
layout,
|
||||
bag,
|
||||
catches,
|
||||
hotbar,
|
||||
ItemCatalogResource,
|
||||
PlayerInventoryLayout.InventoryContainer.STORAGE,
|
||||
)
|
||||
var all_storage_slots: Array = storage_grid.get("_slots")
|
||||
assert(all_storage_slots.size() == 72)
|
||||
assert(storage_grid.get_slots().size() == 9)
|
||||
assert(
|
||||
(all_storage_slots[9] as GeneralInventorySlot).accessibility_name
|
||||
== "locked storage slot 10"
|
||||
)
|
||||
var storage_wallet := PlayerWallet.new()
|
||||
root.add_child(storage_wallet)
|
||||
assert(storage_wallet.restore_balance(9500))
|
||||
for expected_capacity: int in [18, 27, 36, 45, 54, 63, 72]:
|
||||
assert(capacity.purchase(storage_wallet))
|
||||
assert(layout.get_storage_capacity() == expected_capacity)
|
||||
assert(storage_grid.get_slots().size() == expected_capacity)
|
||||
assert(capacity.get_next_capacity() == -1)
|
||||
assert(capacity.get_next_cost() == -1)
|
||||
|
||||
var saved := layout.to_save_data()
|
||||
assert(layout.move_entry_to_first_free(
|
||||
PlayerInventoryLayout.EntryKind.ITEM,
|
||||
&"coffee",
|
||||
PlayerInventoryLayout.InventoryContainer.STORAGE,
|
||||
))
|
||||
assert(layout.restore_from_save_data(saved))
|
||||
assert(layout.is_item_in_inventory(&"coffee"))
|
||||
assert(
|
||||
layout.get_container(
|
||||
PlayerInventoryLayout.EntryKind.CATCH,
|
||||
fish_catch.catch_id,
|
||||
) == PlayerInventoryLayout.InventoryContainer.STORAGE
|
||||
)
|
||||
|
||||
var network_sale := NetworkSaleService.new()
|
||||
var session := NetworkSession.new()
|
||||
root.add_child(session)
|
||||
root.add_child(network_sale)
|
||||
network_sale.set("_session", session)
|
||||
network_sale.set("_item_catalog", ItemCatalogResource)
|
||||
network_sale.set("_fish_catalog", FishCatalogResource)
|
||||
var result: Dictionary = network_sale.call(
|
||||
"_build_authoritative_result",
|
||||
1,
|
||||
"mixed_sale_test",
|
||||
[],
|
||||
[{"item_id": "coffee", "quantity": 2}],
|
||||
MainShopBuyer,
|
||||
)
|
||||
assert(bool(result.get("accepted", false)))
|
||||
assert(int(result.get("payout", -1)) == 20)
|
||||
assert((result.get("items", []) as Array).size() == 1)
|
||||
|
||||
print("Unified inventory validation: PASS")
|
||||
root.free()
|
||||
quit()
|
||||
1
tests/unified_inventory_validation.gd.uid
Normal file
1
tests/unified_inventory_validation.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://osuf2xa2bxht
|
||||
|
|
@ -2,7 +2,12 @@ extends SceneTree
|
|||
|
||||
const MainScene: PackedScene = preload("res://main/main.tscn")
|
||||
const TEST_PORT: int = 18170
|
||||
const EXPECTED_POPULATION: int = 2
|
||||
const EXPECTED_POPULATION: int = 8
|
||||
const EXPECTED_BY_TYPE: Dictionary = {
|
||||
&"crab_brown": 2,
|
||||
&"clam_manila": 3,
|
||||
&"beetle_stag_common": 3,
|
||||
}
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
|
|
@ -28,6 +33,7 @@ func _run_host() -> void:
|
|||
assert(session.start_dedicated_host(TEST_PORT, 8, "127.0.0.1"))
|
||||
assert(session.set_host_open(true))
|
||||
await _wait_for_population(service)
|
||||
_freeze_timed_entries(service)
|
||||
_validate_population(service, true)
|
||||
|
||||
var remote_peer_id: int = 0
|
||||
|
|
@ -83,6 +89,9 @@ func _run_client() -> void:
|
|||
))
|
||||
await _wait_for_population(service)
|
||||
_validate_population(service, false)
|
||||
# Give the host process time to observe the authenticated peer before this
|
||||
# deliberately short client validation disconnects.
|
||||
await create_timer(0.75).timeout
|
||||
print("World spawn multiplayer client validation: PASS")
|
||||
session.disconnect_session("")
|
||||
main.queue_free()
|
||||
|
|
@ -106,8 +115,10 @@ func _validate_population(service: Node, expect_authoritative_state: bool) -> vo
|
|||
var presentations: Dictionary = service.get("_presentations")
|
||||
assert(entities.size() == EXPECTED_POPULATION)
|
||||
assert(presentations.size() == EXPECTED_POPULATION)
|
||||
var counts: Dictionary = {}
|
||||
for state: Dictionary in entities.values():
|
||||
assert(state.get("type_id") == &"crab_brown")
|
||||
var type_id := state.get("type_id") as StringName
|
||||
counts[type_id] = int(counts.get(type_id, 0)) + 1
|
||||
var position: Variant = state.get("position")
|
||||
assert(typeof(position) == TYPE_VECTOR3)
|
||||
assert((position as Vector3).is_finite())
|
||||
|
|
@ -120,27 +131,43 @@ func _validate_population(service: Node, expect_authoritative_state: bool) -> vo
|
|||
if expect_authoritative_state:
|
||||
var quality: int = int(state.get("quality", -1))
|
||||
assert(FishQuality.is_valid(quality))
|
||||
assert(entry.get_movement_speed_for_quality(quality) > 0.0)
|
||||
assert(entry.get_scare_radius_for_quality(quality) > 0.0)
|
||||
if entry.is_stationary_spawn():
|
||||
assert(entry.get_movement_speed_for_quality(quality) <= 0.0)
|
||||
assert(not entry.can_be_scared())
|
||||
else:
|
||||
assert(entry.get_movement_speed_for_quality(quality) > 0.0)
|
||||
assert(entry.get_scare_radius_for_quality(quality) > 0.0)
|
||||
assert(counts == EXPECTED_BY_TYPE)
|
||||
|
||||
|
||||
func _freeze_timed_entries(service: Node) -> void:
|
||||
for state: Dictionary in (service.get("_entities") as Dictionary).values():
|
||||
var entry := state.get("data") as GatherableData
|
||||
if entry != null and entry.active_lifetime_seconds > 0.0:
|
||||
state["expires_at"] = INF
|
||||
|
||||
|
||||
func _validate_respawn_budget(service: Node) -> void:
|
||||
var entities: Dictionary = service.get("_entities")
|
||||
var entity_ids: Array = entities.keys()
|
||||
assert(entity_ids.size() == EXPECTED_POPULATION)
|
||||
for entity_id: Variant in entity_ids:
|
||||
var crab_ids: Array[String] = []
|
||||
for entity_id: String in entities:
|
||||
var state: Dictionary = entities[entity_id]
|
||||
if state.get("type_id") == &"crab_brown":
|
||||
crab_ids.append(entity_id)
|
||||
assert(crab_ids.size() == 2)
|
||||
for entity_id: String in crab_ids:
|
||||
service.call(
|
||||
"_despawn_entity",
|
||||
str(entity_id),
|
||||
entity_id,
|
||||
&"captured",
|
||||
false,
|
||||
true,
|
||||
)
|
||||
assert((service.get("_entities") as Dictionary).is_empty())
|
||||
assert((service.get("_entities") as Dictionary).size() == 6)
|
||||
|
||||
var now: float = Time.get_ticks_msec() / 1000.0
|
||||
var respawns: Array = service.get("_respawns")
|
||||
assert(respawns.size() == EXPECTED_POPULATION)
|
||||
assert(respawns.size() == crab_ids.size())
|
||||
for index: int in respawns.size():
|
||||
var respawn: Dictionary = respawns[index]
|
||||
var delay: float = float(respawn.get("due", 0.0)) - now
|
||||
|
|
@ -149,19 +176,20 @@ func _validate_respawn_budget(service: Node) -> void:
|
|||
respawns[index] = respawn
|
||||
|
||||
service.call("_update_respawns")
|
||||
assert((service.get("_entities") as Dictionary).size() == 1)
|
||||
assert((service.get("_entities") as Dictionary).size() == 7)
|
||||
assert((service.get("_respawns") as Array).size() == 1)
|
||||
var next_by_type: Dictionary = service.get("_next_respawn_by_type")
|
||||
var next_allowed: float = float(next_by_type.get(&"crab_brown", 0.0))
|
||||
assert(next_allowed - now >= 179.0)
|
||||
|
||||
service.call("_update_respawns")
|
||||
assert((service.get("_entities") as Dictionary).size() == 1)
|
||||
assert((service.get("_entities") as Dictionary).size() == 7)
|
||||
assert((service.get("_respawns") as Array).size() == 1)
|
||||
next_by_type[&"crab_brown"] = now - 1.0
|
||||
service.call("_update_respawns")
|
||||
assert((service.get("_entities") as Dictionary).size() == 2)
|
||||
assert((service.get("_entities") as Dictionary).size() == 8)
|
||||
assert((service.get("_respawns") as Array).is_empty())
|
||||
_validate_population(service, true)
|
||||
|
||||
|
||||
func _create_initialized_main() -> Node:
|
||||
|
|
|
|||
|
|
@ -52,6 +52,30 @@ func _validate_catalog_statuses() -> void:
|
|||
assert(is_equal_approx(brown.minimum_surface_y, -0.44))
|
||||
assert(FishCatalog.get_fish_by_id(&"crab_brown") == brown.catch_data)
|
||||
assert(not brown.catch_data.is_fishable())
|
||||
var clam: GatherableData = Gatherables.get_entry(&"clam_manila")
|
||||
assert(clam != null and clam.is_available())
|
||||
assert(clam.catch_data.collection_method == FishData.CollectionMethod.DIGGING)
|
||||
assert(clam.catch_data.logbook_section == FishData.LogbookSection.SHELLFISH)
|
||||
assert(clam.required_tool_id == &"standard_shovel")
|
||||
assert(clam.diggable_area_id == &"starter_beach")
|
||||
assert(clam.presentation_mode == GatherableData.PresentationMode.WATER_SPURT)
|
||||
assert(clam.is_stationary_hotspot())
|
||||
assert(not clam.requires_sneaking)
|
||||
assert(not clam.can_be_scared())
|
||||
assert(is_equal_approx(clam.active_lifetime_seconds, 10.0))
|
||||
assert(FishCatalog.get_fish_by_id(&"clam_manila") == clam.catch_data)
|
||||
assert(not clam.catch_data.is_fishable())
|
||||
var beetle: GatherableData = Gatherables.get_entry(&"beetle_stag_common")
|
||||
assert(beetle != null and beetle.is_available())
|
||||
assert(beetle.catch_data.collection_method == FishData.CollectionMethod.NET)
|
||||
assert(beetle.required_tool_id == &"crab_net")
|
||||
assert(beetle.spawn_anchor_set_id == &"starter_reachable_tree_trunks")
|
||||
assert(beetle.is_stationary_spawn())
|
||||
assert(not beetle.is_stationary_hotspot())
|
||||
assert(not beetle.requires_sneaking)
|
||||
assert(not beetle.can_be_scared())
|
||||
assert(FishCatalog.get_fish_by_id(&"beetle_stag_common") == beetle.catch_data)
|
||||
assert(not beetle.catch_data.is_fishable())
|
||||
|
||||
for type_id: StringName in [
|
||||
&"crab_ghost",
|
||||
|
|
@ -76,8 +100,10 @@ func _validate_catalog_statuses() -> void:
|
|||
)
|
||||
|
||||
var available: Array[GatherableData] = Gatherables.get_available_entries()
|
||||
assert(available.size() == 1)
|
||||
assert(available.front() == brown)
|
||||
assert(available.size() == 3)
|
||||
assert(available.has(brown))
|
||||
assert(available.has(clam))
|
||||
assert(available.has(beetle))
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = 24680
|
||||
var captured_delay: float = brown.get_respawn_delay(&"captured", rng)
|
||||
|
|
@ -111,6 +137,18 @@ func _validate_billboard_presentation() -> void:
|
|||
assert(not sprite.shaded)
|
||||
gatherable.free()
|
||||
|
||||
var hotspot := WorldGatherableType.new()
|
||||
hotspot.call("_ensure_water_spurt_visual")
|
||||
var hole := hotspot.get_node("WaterSpurt/BurrowMark") as MeshInstance3D
|
||||
assert(hole != null)
|
||||
assert(hole.cast_shadow == GeometryInstance3D.SHADOW_CASTING_SETTING_OFF)
|
||||
var hole_material := hole.mesh.surface_get_material(0) as StandardMaterial3D
|
||||
assert(hole_material != null)
|
||||
assert(hole_material.shading_mode == BaseMaterial3D.SHADING_MODE_UNSHADED)
|
||||
assert(hole_material.transparency == BaseMaterial3D.TRANSPARENCY_DISABLED)
|
||||
assert(is_equal_approx(hole_material.albedo_color.a, 1.0))
|
||||
hotspot.queue_free()
|
||||
|
||||
|
||||
func _validate_quality_behavior(entry: GatherableData) -> void:
|
||||
var prior_speed: float = -1.0
|
||||
|
|
|
|||
|
|
@ -31,19 +31,10 @@ func _run_host() -> void:
|
|||
main.get_node("%WorldWeatherService") as WorldWeatherService
|
||||
)
|
||||
assert(session.start_private_host(TEST_PORT))
|
||||
var game_ui := main.get_node("%GameUI") as CanvasLayer
|
||||
var chat_ui := game_ui.get_node("%ChatUI") as ChatUI
|
||||
assert(chat_ui.call("_handle_chat_command", "/weather clear"))
|
||||
assert(world_weather.get_weather() == WorldWeatherService.Weather.SUNNY)
|
||||
world_time.synchronize_time(INITIAL_HOST_TIME)
|
||||
assert(is_equal_approx(
|
||||
world_time.get_persistent_time_hours(), INITIAL_HOST_TIME
|
||||
assert(world_time.set_authoritative_time(INITIAL_HOST_TIME))
|
||||
assert(world_weather.set_authoritative_weather(
|
||||
WorldWeatherService.Weather.RAINY
|
||||
))
|
||||
assert(chat_ui.call("_handle_chat_command", "/weather rainy"))
|
||||
assert(
|
||||
world_weather.get_persistent_weather()
|
||||
== WorldWeatherService.Weather.RAINY
|
||||
)
|
||||
assert(session.set_host_open(true))
|
||||
|
||||
var remote_peer_id: int = 0
|
||||
|
|
@ -61,35 +52,12 @@ func _run_host() -> void:
|
|||
assert(session.peer_supports_capability(
|
||||
remote_peer_id, NetworkProtocol.WORLD_WEATHER_CAPABILITY
|
||||
))
|
||||
var remote_record: PeerRegistry.PeerRecord = session.get_peer_record(
|
||||
remote_peer_id
|
||||
)
|
||||
assert(remote_record != null and remote_record.identity_authenticated)
|
||||
assert(session.set_peer_operator(
|
||||
remote_peer_id,
|
||||
remote_record.identity_fingerprint,
|
||||
true,
|
||||
))
|
||||
assert(world_time.get_phase() == WorldTimeService.Phase.DUSK)
|
||||
|
||||
var command_deadline: int = Time.get_ticks_msec() + 10000
|
||||
while (
|
||||
Time.get_ticks_msec() < command_deadline
|
||||
and _wrapped_time_difference(
|
||||
world_time.get_time_hours(), UPDATED_HOST_TIME
|
||||
) > TIME_TOLERANCE_HOURS
|
||||
):
|
||||
await process_frame
|
||||
assert(is_equal_approx(
|
||||
world_time.get_persistent_time_hours(), UPDATED_HOST_TIME
|
||||
await create_timer(1.0).timeout
|
||||
assert(world_time.set_authoritative_time(UPDATED_HOST_TIME))
|
||||
assert(world_weather.set_authoritative_weather(
|
||||
WorldWeatherService.Weather.FOGGY
|
||||
))
|
||||
var fog_deadline: int = Time.get_ticks_msec() + 10000
|
||||
while Time.get_ticks_msec() < fog_deadline and not world_weather.is_foggy():
|
||||
await process_frame
|
||||
assert(
|
||||
world_weather.get_persistent_weather()
|
||||
== WorldWeatherService.Weather.FOGGY
|
||||
)
|
||||
var disconnect_deadline: int = Time.get_ticks_msec() + 12000
|
||||
while (
|
||||
Time.get_ticks_msec() < disconnect_deadline
|
||||
|
|
@ -131,12 +99,6 @@ func _run_client() -> void:
|
|||
assert(session.supports_server_capability(
|
||||
NetworkProtocol.WORLD_WEATHER_CAPABILITY
|
||||
))
|
||||
assert(world_time.restore_persistent_time_hours(15.25))
|
||||
assert(world_weather.restore_persistent_state(
|
||||
WorldWeatherService.Weather.CLOUDY,
|
||||
222.0,
|
||||
))
|
||||
|
||||
var initial_deadline: int = Time.get_ticks_msec() + 8000
|
||||
while (
|
||||
Time.get_ticks_msec() < initial_deadline
|
||||
|
|
@ -170,11 +132,7 @@ func _run_client() -> void:
|
|||
assert(is_equal_approx(clock_panel.position.y, 10.0))
|
||||
assert(is_equal_approx(weather_icon.position.y, 10.0))
|
||||
assert(clock_panel.position.y + clock_panel.size.y < chat_panel.position.y)
|
||||
var operator_deadline: int = Time.get_ticks_msec() + 10000
|
||||
while Time.get_ticks_msec() < operator_deadline and not session.is_local_operator():
|
||||
await process_frame
|
||||
assert(session.is_local_operator())
|
||||
assert(chat_ui.call("_handle_chat_command", "/time night"))
|
||||
assert(not chat_ui.has_method("_handle_chat_command"))
|
||||
|
||||
var update_deadline: int = Time.get_ticks_msec() + 10000
|
||||
while (
|
||||
|
|
@ -188,10 +146,7 @@ func _run_client() -> void:
|
|||
world_time.get_time_hours(), UPDATED_HOST_TIME
|
||||
) <= TIME_TOLERANCE_HOURS)
|
||||
assert(world_time.get_phase() == WorldTimeService.Phase.NIGHT)
|
||||
assert(is_equal_approx(world_time.get_persistent_time_hours(), 15.25))
|
||||
assert(clock_label.text == world_time.get_clock_text())
|
||||
await create_timer(0.6).timeout
|
||||
assert(chat_ui.call("_handle_chat_command", "/weather foggy"))
|
||||
var fog_deadline: int = Time.get_ticks_msec() + 8000
|
||||
while (
|
||||
Time.get_ticks_msec() < fog_deadline
|
||||
|
|
@ -200,14 +155,6 @@ func _run_client() -> void:
|
|||
await process_frame
|
||||
assert(world_weather.is_foggy())
|
||||
assert(weather_icon.get_weather() == WorldWeatherService.Weather.FOGGY)
|
||||
assert(
|
||||
world_weather.get_persistent_weather()
|
||||
== WorldWeatherService.Weather.CLOUDY
|
||||
)
|
||||
assert(is_equal_approx(
|
||||
world_weather.get_persistent_seconds_remaining(),
|
||||
222.0,
|
||||
))
|
||||
chat_ui.call("set_dock_right", true)
|
||||
await process_frame
|
||||
assert(clock_panel.position.x > 1000.0)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ func _initialize() -> void:
|
|||
|
||||
func _run() -> void:
|
||||
_validate_clock_boundaries_and_duration()
|
||||
_validate_system_clock_authority()
|
||||
_validate_persistent_host_clock()
|
||||
_validate_fishing_availability()
|
||||
_validate_fishing_spot_context()
|
||||
|
|
@ -40,7 +41,34 @@ func _run() -> void:
|
|||
|
||||
|
||||
func _validate_clock_boundaries_and_duration() -> void:
|
||||
assert(WorldTimeServiceType.REAL_SECONDS_PER_CYCLE == 3600.0)
|
||||
assert(WorldTimeServiceType.REAL_SECONDS_PER_CYCLE == 86400.0)
|
||||
assert(is_equal_approx(
|
||||
WorldTimeServiceType.HOURS_PER_REAL_SECOND,
|
||||
1.0 / 3600.0,
|
||||
))
|
||||
assert(is_equal_approx(
|
||||
WorldTimeServiceType.time_hours_from_datetime({
|
||||
"hour": 17, "minute": 30, "second": 18,
|
||||
}),
|
||||
17.505,
|
||||
))
|
||||
assert(
|
||||
WorldTimeServiceType.date_id_from_datetime({
|
||||
"year": 2026, "month": 8, "day": 18,
|
||||
}) == "2026-08-18"
|
||||
)
|
||||
assert(
|
||||
WorldTimeServiceType.calendar_cycle_id_from_datetime({
|
||||
"year": 2026, "month": 8, "day": 18,
|
||||
"hour": 7, "minute": 59, "second": 0,
|
||||
}, WorldTimeServiceType.DAY_START_HOUR) == "2026-08-17"
|
||||
)
|
||||
assert(
|
||||
WorldTimeServiceType.calendar_cycle_id_from_datetime({
|
||||
"year": 2026, "month": 8, "day": 18,
|
||||
"hour": 8, "minute": 0, "second": 0,
|
||||
}, WorldTimeServiceType.DAY_START_HOUR) == "2026-08-18"
|
||||
)
|
||||
assert(
|
||||
WorldTimeServiceType.phase_for_hour(7.49)
|
||||
== WorldTimeServiceType.Phase.NIGHT
|
||||
|
|
@ -71,12 +99,12 @@ func _validate_clock_boundaries_and_duration() -> void:
|
|||
|
||||
var clock := WorldTimeServiceType.new()
|
||||
root.add_child(clock)
|
||||
clock.begin_session(8.0)
|
||||
clock.advance_time(1800.0)
|
||||
clock.begin_test_session(8.0)
|
||||
clock.advance_time(12.0 * 60.0 * 60.0)
|
||||
assert(is_equal_approx(clock.get_time_hours(), 20.0))
|
||||
assert(clock.is_night_period())
|
||||
assert(clock.is_transition())
|
||||
clock.advance_time(1800.0)
|
||||
clock.advance_time(12.0 * 60.0 * 60.0)
|
||||
assert(is_equal_approx(clock.get_time_hours(), 8.0))
|
||||
assert(not clock.is_night_period())
|
||||
assert(clock.is_transition())
|
||||
|
|
@ -88,10 +116,10 @@ func _validate_clock_boundaries_and_duration() -> void:
|
|||
func(time_hours: float, _phase: WorldTimeService.Phase) -> void:
|
||||
emitted_times.append(time_hours)
|
||||
)
|
||||
clock.begin_session(12.0 + 31.0 / 60.0)
|
||||
clock.begin_test_session(12.0 + 31.0 / 60.0)
|
||||
emitted_times.clear()
|
||||
for _frame: int in 1201:
|
||||
clock.advance_time(1.0 / 240.0)
|
||||
for _frame: int in 7201:
|
||||
clock.advance_time(1.0 / 60.0)
|
||||
assert(clock.get_clock_text() == "12:33 pm")
|
||||
assert(emitted_times.size() >= 2)
|
||||
clock.queue_free()
|
||||
|
|
@ -101,20 +129,41 @@ func _validate_persistent_host_clock() -> void:
|
|||
var clock := WorldTimeServiceType.new()
|
||||
root.add_child(clock)
|
||||
assert(clock.restore_persistent_time_hours(18.75))
|
||||
clock.set_persistence_tracking_enabled(true)
|
||||
clock.begin_session(clock.get_persistent_time_hours())
|
||||
clock.begin_test_session(18.75)
|
||||
assert(is_equal_approx(clock.get_time_hours(), 18.75))
|
||||
clock.synchronize_time(19.25)
|
||||
assert(is_equal_approx(clock.get_persistent_time_hours(), 19.25))
|
||||
clock.set_persistence_tracking_enabled(false)
|
||||
clock.synchronize_time(6.5)
|
||||
assert(is_equal_approx(clock.get_time_hours(), 6.5))
|
||||
assert(is_equal_approx(clock.get_persistent_time_hours(), 19.25))
|
||||
assert(is_equal_approx(clock.get_persistent_time_hours(), 6.5))
|
||||
assert(not clock.restore_persistent_time_hours(-1.0))
|
||||
assert(not clock.restore_persistent_time_hours(24.0))
|
||||
clock.queue_free()
|
||||
|
||||
|
||||
func _validate_system_clock_authority() -> void:
|
||||
var clock := WorldTimeServiceType.new()
|
||||
root.add_child(clock)
|
||||
clock.begin_authoritative_session()
|
||||
var system_datetime: Dictionary = Time.get_datetime_dict_from_system(false)
|
||||
var system_hour: float = WorldTimeServiceType.time_hours_from_datetime(
|
||||
system_datetime
|
||||
)
|
||||
assert(clock.is_using_system_clock())
|
||||
assert(_wrapped_time_difference(
|
||||
clock.get_time_hours(), system_hour
|
||||
) < 2.0 / 3600.0)
|
||||
assert(
|
||||
clock.get_calendar_date_id()
|
||||
== WorldTimeServiceType.date_id_from_datetime(system_datetime)
|
||||
)
|
||||
assert(clock.set_authoritative_time(12.0))
|
||||
assert(not clock.is_using_system_clock())
|
||||
clock.clear_editor_time_override()
|
||||
assert(clock.is_using_system_clock())
|
||||
clock.queue_free()
|
||||
|
||||
|
||||
func _validate_fishing_availability() -> void:
|
||||
var day_context := FishingContextType.new()
|
||||
day_context.location_tags = [&"starter_pond"]
|
||||
|
|
@ -165,7 +214,7 @@ func _validate_fishing_availability() -> void:
|
|||
|
||||
func _validate_fishing_spot_context() -> void:
|
||||
var clock := WorldTimeServiceType.new()
|
||||
clock.begin_session(20.25)
|
||||
clock.begin_test_session(20.25)
|
||||
var fishing_spot := FishingSpotType.new()
|
||||
fishing_spot.set("_world_time", clock)
|
||||
var region := FishableWaterRegionType.new()
|
||||
|
|
@ -246,6 +295,10 @@ func _validate_environment_presentation() -> void:
|
|||
float(runtime_sky_material.get_shader_parameter("moon_visibility"))
|
||||
< 0.01
|
||||
)
|
||||
assert(
|
||||
float(runtime_sky_material.get_shader_parameter("star_visibility"))
|
||||
< 0.01
|
||||
)
|
||||
var day_ambient_energy: float = runtime_environment.ambient_light_energy
|
||||
assert(not runtime_environment.fog_enabled)
|
||||
assert(is_equal_approx(runtime_environment.fog_sky_affect, 0.35))
|
||||
|
|
@ -257,6 +310,9 @@ func _validate_environment_presentation() -> void:
|
|||
)
|
||||
assert("uniform float fog_horizon_occlusion" in sky_shader.code)
|
||||
assert("fog_horizon_color.rgb" in sky_shader.code)
|
||||
assert("uniform float star_visibility" in sky_shader.code)
|
||||
assert("float procedural_star_field" in sky_shader.code)
|
||||
assert("upper_sky_fade" in sky_shader.code)
|
||||
assert("fog_disabled" in water_shader.code)
|
||||
assert("surface_view_position = view_vertex.xyz" in water_shader.code)
|
||||
assert("surface_view_distance = length(surface_view_position)" in water_shader.code)
|
||||
|
|
@ -301,6 +357,17 @@ func _validate_environment_presentation() -> void:
|
|||
runtime_sky_material.get_shader_parameter("sun_direction") as Vector3
|
||||
)
|
||||
assert(night_moon_visibility > 0.99)
|
||||
assert(
|
||||
float(runtime_sky_material.get_shader_parameter("star_visibility"))
|
||||
> 0.99
|
||||
)
|
||||
assert((
|
||||
runtime_sky_material.get_shader_parameter("star_color") as Color
|
||||
).is_equal_approx(WorldTimeVisualController.STAR_COLOR))
|
||||
assert(is_equal_approx(
|
||||
float(runtime_sky_material.get_shader_parameter("star_strength")),
|
||||
WorldTimeVisualController.STAR_STRENGTH,
|
||||
))
|
||||
assert(night_moon_direction.y > 0.85)
|
||||
assert(night_moon_direction.is_equal_approx(-night_sun_direction))
|
||||
assert(runtime_environment.ambient_light_energy < day_ambient_energy)
|
||||
|
|
@ -339,6 +406,9 @@ func _validate_environment_presentation() -> void:
|
|||
assert(is_equal_approx(float(
|
||||
runtime_sky_material.get_shader_parameter("fog_horizon_occlusion")
|
||||
), 1.0))
|
||||
assert(is_zero_approx(float(
|
||||
runtime_sky_material.get_shader_parameter("star_visibility")
|
||||
)))
|
||||
assert((
|
||||
runtime_sky_material.get_shader_parameter("fog_horizon_color") as Color
|
||||
).is_equal_approx(WorldTimeVisualController.NIGHT_FOG))
|
||||
|
|
@ -425,6 +495,10 @@ func _validate_environment_presentation() -> void:
|
|||
assert(is_zero_approx(float(
|
||||
runtime_sky_material.get_shader_parameter("fog_horizon_occlusion")
|
||||
)))
|
||||
var dusk_star_visibility := float(
|
||||
runtime_sky_material.get_shader_parameter("star_visibility")
|
||||
)
|
||||
assert(dusk_star_visibility > 0.45 and dusk_star_visibility < 0.55)
|
||||
var dusk_horizon: Color = runtime_sky_material.get_shader_parameter(
|
||||
"sky_horizon_color"
|
||||
) as Color
|
||||
|
|
@ -442,3 +516,8 @@ func _validate_weather_clock_icon() -> void:
|
|||
weather_icon.set_weather(WorldWeatherService.Weather.CLOUDY)
|
||||
assert(weather_icon.tooltip_text == "cloudy")
|
||||
weather_icon.queue_free()
|
||||
|
||||
|
||||
static func _wrapped_time_difference(left: float, right: float) -> float:
|
||||
var difference: float = absf(left - right)
|
||||
return minf(difference, WorldTimeServiceType.HOURS_PER_DAY - difference)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ func _run() -> void:
|
|||
_validate_weather_scheduler()
|
||||
_validate_authoritative_weather_override()
|
||||
_validate_weather_persistence()
|
||||
_validate_legacy_forecast_save_migration()
|
||||
_validate_snapshot_bounds()
|
||||
_validate_fishing_weather_seams()
|
||||
_validate_fishing_spot_context()
|
||||
|
|
@ -65,14 +66,9 @@ func _validate_weather_scheduler() -> void:
|
|||
|
||||
func _duration_is_valid(weather: WorldWeatherServiceType) -> bool:
|
||||
var seconds: float = weather.get_seconds_remaining()
|
||||
match weather.get_weather():
|
||||
WorldWeatherServiceType.Weather.SUNNY:
|
||||
return seconds >= 480.0 and seconds <= 900.0
|
||||
WorldWeatherServiceType.Weather.CLOUDY:
|
||||
return seconds >= 300.0 and seconds <= 720.0
|
||||
WorldWeatherServiceType.Weather.RAINY, WorldWeatherServiceType.Weather.FOGGY:
|
||||
return seconds >= 300.0 and seconds <= 600.0
|
||||
return false
|
||||
return is_equal_approx(
|
||||
seconds, WorldWeatherServiceType.WEATHER_PERIOD_SECONDS
|
||||
)
|
||||
|
||||
|
||||
func _validate_authoritative_weather_override() -> void:
|
||||
|
|
@ -94,7 +90,7 @@ func _validate_authoritative_weather_override() -> void:
|
|||
|
||||
var clock := WorldTimeServiceType.new()
|
||||
root.add_child(clock)
|
||||
clock.begin_session(WorldTimeServiceType.DAY_START_HOUR)
|
||||
clock.begin_test_session(WorldTimeServiceType.DAY_START_HOUR)
|
||||
var schedule: Array[Dictionary] = []
|
||||
for index: int in WorldWeatherServiceType.DAILY_PLAN_SEGMENT_COUNT:
|
||||
schedule.append({
|
||||
|
|
@ -159,8 +155,11 @@ func _validate_weather_persistence() -> void:
|
|||
))
|
||||
weather.set_persistence_tracking_enabled(true)
|
||||
weather.begin_authoritative_session(20260803)
|
||||
assert(weather.is_raining())
|
||||
assert(is_equal_approx(weather.get_seconds_remaining(), 187.5))
|
||||
assert(weather.get_weather() == WorldWeatherServiceType.DEFAULT_WEATHER)
|
||||
assert(is_equal_approx(
|
||||
weather.get_seconds_remaining(),
|
||||
WorldWeatherServiceType.WEATHER_PERIOD_SECONDS,
|
||||
))
|
||||
assert(not weather.restore_persistent_state(
|
||||
WorldWeatherServiceType.Weather.RAINY,
|
||||
WorldWeatherServiceType.MAX_PERSISTED_SECONDS + 1.0,
|
||||
|
|
@ -184,11 +183,49 @@ func _validate_snapshot_bounds() -> void:
|
|||
assert(not NetworkWorldWeatherServiceType.validate_snapshot({
|
||||
"session_id": "session",
|
||||
"weather": int(WorldWeatherServiceType.Weather.RAINY),
|
||||
"seconds_remaining": 1801.0,
|
||||
"seconds_remaining": 3601.0,
|
||||
"sequence": 2,
|
||||
}))
|
||||
|
||||
|
||||
func _validate_legacy_forecast_save_migration() -> void:
|
||||
var legacy_schedule: Array[Dictionary] = []
|
||||
for index: int in JobCatalog.LEGACY_WEATHER_SEGMENT_COUNT:
|
||||
legacy_schedule.append({
|
||||
"start_hour": fposmod(
|
||||
WorldTimeService.DAY_START_HOUR
|
||||
+ float(index) * JobCatalog.LEGACY_WEATHER_SEGMENT_HOURS,
|
||||
WorldTimeService.HOURS_PER_DAY,
|
||||
),
|
||||
"weather": int(WorldWeatherService.Weather.SUNNY),
|
||||
})
|
||||
var legacy_job: Dictionary = {
|
||||
"id": "legacy-catch",
|
||||
"title": "legacy catch",
|
||||
"description": "catch one fish",
|
||||
"kind": int(JobCatalog.Kind.CATCH_TOTAL),
|
||||
"target": 1,
|
||||
"fish_coin": 1,
|
||||
"experience": 1,
|
||||
}
|
||||
var save_data: Dictionary = PlayerJobService.default_save_data()
|
||||
save_data["host_board"] = {
|
||||
"plan_id": "legacy-plan",
|
||||
"cycle": 4,
|
||||
"schedule_anchor_index": 0,
|
||||
"jobs": [legacy_job],
|
||||
"weather_schedule": legacy_schedule,
|
||||
}
|
||||
save_data["active_plan_id"] = "legacy-plan"
|
||||
assert(PlayerJobService.validate_save_data(save_data))
|
||||
var jobs := PlayerJobService.new()
|
||||
assert(jobs.restore_from_save_data(save_data))
|
||||
var migrated: Dictionary = jobs.to_save_data()
|
||||
assert((migrated.get("host_board", {}) as Dictionary).is_empty())
|
||||
assert(str(migrated.get("active_plan_id", "")).is_empty())
|
||||
jobs.free()
|
||||
|
||||
|
||||
func _validate_fishing_weather_seams() -> void:
|
||||
var clear_context := FishingContextType.new()
|
||||
var rain_context := FishingContextType.new()
|
||||
|
|
@ -255,7 +292,7 @@ func _validate_weather_presentation() -> void:
|
|||
world_root.add_child(sun)
|
||||
var clock := WorldTimeServiceType.new()
|
||||
world_root.add_child(clock)
|
||||
clock.begin_session(14.0)
|
||||
clock.begin_test_session(14.0)
|
||||
var weather := WorldWeatherServiceType.new()
|
||||
world_root.add_child(weather)
|
||||
weather.begin_remote_session()
|
||||
|
|
@ -279,6 +316,34 @@ func _validate_weather_presentation() -> void:
|
|||
var runtime_sky_material := (
|
||||
runtime_environment.sky.sky_material as ShaderMaterial
|
||||
)
|
||||
clock.set_authoritative_time(0.0)
|
||||
visuals.apply_time_immediately(0.0)
|
||||
assert(float(
|
||||
runtime_sky_material.get_shader_parameter("star_visibility")
|
||||
) > 0.99)
|
||||
visuals.apply_weather_immediately(
|
||||
WorldWeatherServiceType.Weather.CLOUDY
|
||||
)
|
||||
assert(is_equal_approx(float(
|
||||
runtime_sky_material.get_shader_parameter("star_visibility")
|
||||
), 0.18))
|
||||
visuals.apply_weather_immediately(
|
||||
WorldWeatherServiceType.Weather.RAINY
|
||||
)
|
||||
assert(is_equal_approx(float(
|
||||
runtime_sky_material.get_shader_parameter("star_visibility")
|
||||
), 0.03))
|
||||
visuals.apply_weather_immediately(
|
||||
WorldWeatherServiceType.Weather.FOGGY
|
||||
)
|
||||
assert(is_zero_approx(float(
|
||||
runtime_sky_material.get_shader_parameter("star_visibility")
|
||||
)))
|
||||
visuals.apply_weather_immediately(
|
||||
WorldWeatherServiceType.Weather.SUNNY
|
||||
)
|
||||
clock.set_authoritative_time(14.0)
|
||||
visuals.apply_time_immediately(14.0)
|
||||
var clear_background_energy: float = (
|
||||
runtime_environment.background_energy_multiplier
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue