Add Cooler, Bag, and compact hotbar systems
This commit is contained in:
parent
8cd3e83285
commit
49e7faaf84
32 changed files with 1613 additions and 99 deletions
|
|
@ -9,6 +9,10 @@ const CatchControllerType = preload("res://fishing/catch_controller.gd")
|
|||
const FishingContextType = preload("res://fishing/fishing_context.gd")
|
||||
const FishingPresentationType = preload("res://fishing/fishing_presentation.gd")
|
||||
const FishInventoryType = preload("res://inventory/fish_inventory.gd")
|
||||
const ItemCatalogType = preload("res://items/item_catalog.gd")
|
||||
const ItemDataType = preload("res://items/item_data.gd")
|
||||
const PlayerBagType = preload("res://inventory/player_bag.gd")
|
||||
const PlayerHotbarType = preload("res://inventory/player_hotbar.gd")
|
||||
const PlayerType = preload("res://player/player.gd")
|
||||
const FishableWaterRegionType = preload(
|
||||
"res://world/fishable_water_region.gd"
|
||||
|
|
@ -31,6 +35,7 @@ signal showcase_changed(
|
|||
visible: bool,
|
||||
)
|
||||
signal bite_activated
|
||||
signal ready_for_equipment_refresh
|
||||
|
||||
enum FishingState {
|
||||
READY,
|
||||
|
|
@ -84,6 +89,9 @@ var _local_menu_input_owners: Dictionary[StringName, bool] = {}
|
|||
var _local_player: PlayerType
|
||||
var _local_inventory: FishInventoryType
|
||||
var _local_collection_log: CollectionLogType
|
||||
var _local_bag: PlayerBagType
|
||||
var _local_hotbar: PlayerHotbarType
|
||||
var _item_catalog: ItemCatalogType
|
||||
var _active_player: PlayerType
|
||||
var _state_time_remaining: float = 0.0
|
||||
var _cast_charge: float = 0.0
|
||||
|
|
@ -121,10 +129,16 @@ func setup(
|
|||
local_player: PlayerType,
|
||||
local_inventory: FishInventoryType,
|
||||
local_collection_log: CollectionLogType,
|
||||
local_bag: PlayerBagType,
|
||||
local_hotbar: PlayerHotbarType,
|
||||
item_catalog: ItemCatalogType,
|
||||
) -> void:
|
||||
_local_player = local_player
|
||||
_local_inventory = local_inventory
|
||||
_local_collection_log = local_collection_log
|
||||
_local_bag = local_bag
|
||||
_local_hotbar = local_hotbar
|
||||
_item_catalog = item_catalog
|
||||
|
||||
|
||||
func can_open_player_menu() -> bool:
|
||||
|
|
@ -151,6 +165,15 @@ func can_open_system_menu() -> bool:
|
|||
)
|
||||
|
||||
|
||||
func can_change_hotbar_selection() -> bool:
|
||||
return (
|
||||
_gameplay_input_enabled
|
||||
and not _external_input_blocked
|
||||
and _local_menu_input_owners.is_empty()
|
||||
and state == FishingState.READY
|
||||
)
|
||||
|
||||
|
||||
func handle_escape_action() -> bool:
|
||||
if not _gameplay_input_enabled or _external_input_blocked:
|
||||
return false
|
||||
|
|
@ -301,6 +324,10 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||
FishingState.READY:
|
||||
if not _new_cast_press_armed:
|
||||
return
|
||||
if not has_active_fishing_rod():
|
||||
status_changed.emit("Select a fishing rod to cast.")
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
_begin_aiming(_local_player)
|
||||
FishingState.WAITING_FOR_BITE:
|
||||
_withdrawal_input_held = true
|
||||
|
|
@ -354,6 +381,35 @@ func _begin_aiming(player: PlayerType) -> void:
|
|||
)
|
||||
|
||||
|
||||
func has_active_fishing_rod() -> bool:
|
||||
if (
|
||||
_local_bag == null
|
||||
or _local_hotbar == null
|
||||
or _item_catalog == null
|
||||
):
|
||||
return false
|
||||
var item_id: StringName = _local_hotbar.get_selected_item_id()
|
||||
if item_id.is_empty() or not _local_bag.owns_item(item_id):
|
||||
return false
|
||||
var item: ItemDataType = _item_catalog.get_item_by_id(item_id)
|
||||
return (
|
||||
item != null
|
||||
and item.is_valid()
|
||||
and item.category == ItemDataType.Category.ROD
|
||||
and item.usable
|
||||
and item.equippable
|
||||
)
|
||||
|
||||
|
||||
func is_fighting() -> bool:
|
||||
return state == FishingState.FIGHTING
|
||||
|
||||
|
||||
func refresh_active_item_status() -> void:
|
||||
if state == FishingState.READY and has_active_fishing_rod():
|
||||
status_changed.emit("")
|
||||
|
||||
|
||||
func _update_cast_charge(delta: float) -> void:
|
||||
if state != FishingState.AIMING_CAST:
|
||||
return
|
||||
|
|
@ -581,20 +637,6 @@ func _on_catch_encounter_updated(
|
|||
_bobber_water_position,
|
||||
Input.is_action_pressed("fish_primary")
|
||||
)
|
||||
if (
|
||||
active_barrier_index >= 0
|
||||
and active_barrier_index < barrier_health.size()
|
||||
and active_barrier_index < barrier_max_health.size()
|
||||
):
|
||||
status_changed.emit(
|
||||
"Barrier: %d / %d"
|
||||
% [
|
||||
barrier_health[active_barrier_index],
|
||||
barrier_max_health[active_barrier_index],
|
||||
]
|
||||
)
|
||||
else:
|
||||
status_changed.emit("Hold left click to reel")
|
||||
|
||||
|
||||
func _on_catch_completed() -> void:
|
||||
|
|
@ -731,6 +773,7 @@ func _cleanup_attempt(
|
|||
state = FishingState.READY
|
||||
_cooldown_status = ""
|
||||
status_changed.emit("")
|
||||
ready_for_equipment_refresh.emit()
|
||||
else:
|
||||
state = FishingState.COOLDOWN
|
||||
_state_time_remaining = cooldown_duration
|
||||
|
|
@ -743,6 +786,7 @@ func _return_to_ready() -> void:
|
|||
_state_time_remaining = 0.0
|
||||
_cooldown_status = ""
|
||||
status_changed.emit("")
|
||||
ready_for_equipment_refresh.emit()
|
||||
|
||||
|
||||
func _cancel_attempt() -> void:
|
||||
|
|
|
|||
104
inventory/player_bag.gd
Normal file
104
inventory/player_bag.gd
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
class_name PlayerBag
|
||||
extends Node
|
||||
|
||||
const ItemCatalogType = preload("res://items/item_catalog.gd")
|
||||
const ItemDataType = preload("res://items/item_data.gd")
|
||||
const OwnedItemType = preload("res://items/owned_item.gd")
|
||||
|
||||
signal contents_changed
|
||||
|
||||
var _catalog: ItemCatalogType
|
||||
var _items: Array[OwnedItemType] = []
|
||||
|
||||
|
||||
func setup(catalog: ItemCatalogType) -> void:
|
||||
_catalog = catalog
|
||||
|
||||
|
||||
func add_item(item_id: StringName, quantity: int = 1) -> bool:
|
||||
var item: ItemDataType = _resolve_valid_item(item_id)
|
||||
if item == null or quantity <= 0:
|
||||
return false
|
||||
var existing: OwnedItemType = get_owned_item(item_id)
|
||||
if existing != null:
|
||||
if not item.stackable:
|
||||
return false
|
||||
if existing.quantity + quantity > item.max_stack:
|
||||
return false
|
||||
existing.quantity += quantity
|
||||
else:
|
||||
if quantity > (item.max_stack if item.stackable else 1):
|
||||
return false
|
||||
var owned := OwnedItemType.new()
|
||||
owned.item_id = item_id
|
||||
owned.quantity = quantity
|
||||
_items.append(owned)
|
||||
contents_changed.emit()
|
||||
return true
|
||||
|
||||
|
||||
func remove_item(item_id: StringName, quantity: int = 1) -> bool:
|
||||
if quantity <= 0:
|
||||
return false
|
||||
for index: int in range(_items.size()):
|
||||
var owned: OwnedItemType = _items[index]
|
||||
if owned.item_id != item_id or owned.quantity < quantity:
|
||||
continue
|
||||
owned.quantity -= quantity
|
||||
if owned.quantity == 0:
|
||||
_items.remove_at(index)
|
||||
contents_changed.emit()
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func get_quantity(item_id: StringName) -> int:
|
||||
var owned: OwnedItemType = get_owned_item(item_id)
|
||||
return owned.quantity if owned != null else 0
|
||||
|
||||
|
||||
func owns_item(item_id: StringName) -> bool:
|
||||
return get_quantity(item_id) > 0
|
||||
|
||||
|
||||
func get_owned_item(item_id: StringName) -> OwnedItemType:
|
||||
if item_id.is_empty():
|
||||
return null
|
||||
for owned: OwnedItemType in _items:
|
||||
if owned != null and owned.item_id == item_id:
|
||||
return owned
|
||||
return null
|
||||
|
||||
|
||||
func get_all_items() -> Array[OwnedItemType]:
|
||||
var result: Array[OwnedItemType] = []
|
||||
for owned: OwnedItemType in _items:
|
||||
if owned != null:
|
||||
result.append(owned.duplicate_record())
|
||||
return result
|
||||
|
||||
|
||||
func replace_all_items(items: Array[OwnedItemType]) -> bool:
|
||||
var validated: Array[OwnedItemType] = []
|
||||
var seen: Dictionary[StringName, bool] = {}
|
||||
for owned: OwnedItemType in items:
|
||||
if owned == null or not owned.is_valid() or seen.has(owned.item_id):
|
||||
return false
|
||||
var item: ItemDataType = _resolve_valid_item(owned.item_id)
|
||||
if item == null:
|
||||
return false
|
||||
var maximum: int = item.max_stack if item.stackable else 1
|
||||
if owned.quantity > maximum:
|
||||
return false
|
||||
seen[owned.item_id] = true
|
||||
validated.append(owned.duplicate_record())
|
||||
_items = validated
|
||||
contents_changed.emit()
|
||||
return true
|
||||
|
||||
|
||||
func _resolve_valid_item(item_id: StringName) -> ItemDataType:
|
||||
if _catalog == null:
|
||||
return null
|
||||
var item: ItemDataType = _catalog.get_item_by_id(item_id)
|
||||
return item if item != null and item.is_valid() else null
|
||||
1
inventory/player_bag.gd.uid
Normal file
1
inventory/player_bag.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c2033c62stp2d
|
||||
171
inventory/player_hotbar.gd
Normal file
171
inventory/player_hotbar.gd
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
class_name PlayerHotbar
|
||||
extends Node
|
||||
|
||||
const SLOT_COUNT: int = 9
|
||||
const ItemCatalogType = preload("res://items/item_catalog.gd")
|
||||
const ItemDataType = preload("res://items/item_data.gd")
|
||||
const PlayerBagType = preload("res://inventory/player_bag.gd")
|
||||
|
||||
signal slots_changed
|
||||
signal selected_slot_changed(slot_index: int, item_id: StringName)
|
||||
|
||||
var _bag: PlayerBagType
|
||||
var _catalog: ItemCatalogType
|
||||
var _slots: Array[StringName] = []
|
||||
var _selected_slot: int = 0
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
_slots.resize(SLOT_COUNT)
|
||||
_slots.fill(StringName())
|
||||
|
||||
|
||||
func setup(bag: PlayerBagType, catalog: ItemCatalogType) -> void:
|
||||
_bag = bag
|
||||
_catalog = catalog
|
||||
if _bag != null and not _bag.contents_changed.is_connected(
|
||||
_on_bag_contents_changed
|
||||
):
|
||||
_bag.contents_changed.connect(_on_bag_contents_changed)
|
||||
_validate_assignments()
|
||||
|
||||
|
||||
func assign_item(slot_index: int, item_id: StringName) -> bool:
|
||||
if not _is_slot_valid(slot_index) or not _can_assign(item_id):
|
||||
return false
|
||||
if _slots[slot_index] == item_id:
|
||||
return true
|
||||
for index: int in range(SLOT_COUNT):
|
||||
if index != slot_index and _slots[index] == item_id:
|
||||
_slots[index] = StringName()
|
||||
_slots[slot_index] = item_id
|
||||
slots_changed.emit()
|
||||
if slot_index == _selected_slot:
|
||||
selected_slot_changed.emit(_selected_slot, item_id)
|
||||
return true
|
||||
|
||||
|
||||
func clear_slot(slot_index: int) -> bool:
|
||||
if not _is_slot_valid(slot_index) or _slots[slot_index].is_empty():
|
||||
return false
|
||||
_slots[slot_index] = StringName()
|
||||
slots_changed.emit()
|
||||
if slot_index == _selected_slot:
|
||||
selected_slot_changed.emit(_selected_slot, StringName())
|
||||
return true
|
||||
|
||||
|
||||
func swap_slots(from_index: int, to_index: int) -> bool:
|
||||
if (
|
||||
not _is_slot_valid(from_index)
|
||||
or not _is_slot_valid(to_index)
|
||||
or from_index == to_index
|
||||
):
|
||||
return false
|
||||
var temporary: StringName = _slots[from_index]
|
||||
_slots[from_index] = _slots[to_index]
|
||||
_slots[to_index] = temporary
|
||||
slots_changed.emit()
|
||||
if from_index == _selected_slot or to_index == _selected_slot:
|
||||
selected_slot_changed.emit(
|
||||
_selected_slot,
|
||||
_slots[_selected_slot]
|
||||
)
|
||||
return true
|
||||
|
||||
|
||||
func select_slot(slot_index: int) -> bool:
|
||||
if not _is_slot_valid(slot_index):
|
||||
return false
|
||||
if _selected_slot == slot_index:
|
||||
return true
|
||||
_selected_slot = slot_index
|
||||
selected_slot_changed.emit(_selected_slot, _slots[_selected_slot])
|
||||
return true
|
||||
|
||||
|
||||
func cycle_selection(direction: int) -> bool:
|
||||
if direction == 0:
|
||||
return false
|
||||
return select_slot(
|
||||
posmod(_selected_slot + signi(direction), SLOT_COUNT)
|
||||
)
|
||||
|
||||
|
||||
func get_selected_slot() -> int:
|
||||
return _selected_slot
|
||||
|
||||
|
||||
func get_selected_item_id() -> StringName:
|
||||
return _slots[_selected_slot]
|
||||
|
||||
|
||||
func get_item_id(slot_index: int) -> StringName:
|
||||
return _slots[slot_index] if _is_slot_valid(slot_index) else StringName()
|
||||
|
||||
|
||||
func get_slots() -> Array[StringName]:
|
||||
return _slots.duplicate()
|
||||
|
||||
|
||||
func replace_state(
|
||||
slots: Array[StringName],
|
||||
selected_slot: int,
|
||||
) -> bool:
|
||||
var normalized: Array[StringName] = []
|
||||
normalized.resize(SLOT_COUNT)
|
||||
normalized.fill(StringName())
|
||||
var seen_assignments: Dictionary[StringName, bool] = {}
|
||||
for index: int in range(mini(slots.size(), SLOT_COUNT)):
|
||||
var item_id: StringName = slots[index]
|
||||
if item_id.is_empty():
|
||||
continue
|
||||
if _can_assign(item_id) and not seen_assignments.has(item_id):
|
||||
normalized[index] = item_id
|
||||
seen_assignments[item_id] = true
|
||||
_slots = normalized
|
||||
_selected_slot = clampi(selected_slot, 0, SLOT_COUNT - 1)
|
||||
slots_changed.emit()
|
||||
selected_slot_changed.emit(
|
||||
_selected_slot,
|
||||
_slots[_selected_slot]
|
||||
)
|
||||
return true
|
||||
|
||||
|
||||
func has_any_assignment() -> bool:
|
||||
for item_id: StringName in _slots:
|
||||
if not item_id.is_empty():
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _can_assign(item_id: StringName) -> bool:
|
||||
if item_id.is_empty() or _bag == null or not _bag.owns_item(item_id):
|
||||
return false
|
||||
if _catalog == null:
|
||||
return false
|
||||
var item: ItemDataType = _catalog.get_item_by_id(item_id)
|
||||
return item != null and item.is_valid() and item.hotbar_allowed
|
||||
|
||||
|
||||
func _validate_assignments() -> void:
|
||||
var changed: bool = false
|
||||
for index: int in range(SLOT_COUNT):
|
||||
if not _slots[index].is_empty() and not _can_assign(_slots[index]):
|
||||
_slots[index] = StringName()
|
||||
changed = true
|
||||
if changed:
|
||||
slots_changed.emit()
|
||||
selected_slot_changed.emit(
|
||||
_selected_slot,
|
||||
_slots[_selected_slot]
|
||||
)
|
||||
|
||||
|
||||
func _on_bag_contents_changed() -> void:
|
||||
_validate_assignments()
|
||||
|
||||
|
||||
func _is_slot_valid(slot_index: int) -> bool:
|
||||
return slot_index >= 0 and slot_index < SLOT_COUNT
|
||||
1
inventory/player_hotbar.gd.uid
Normal file
1
inventory/player_hotbar.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bl60rgr4hqxdc
|
||||
26
items/catalog/basic_fishing_rod.tres
Normal file
26
items/catalog/basic_fishing_rod.tres
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
[gd_resource type="Resource" script_class="ItemData" load_steps=4 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://items/item_data.gd" id="1_script"]
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_rod"]
|
||||
colors = PackedColorArray(0.302, 0.184, 0.11, 1, 0.957, 0.827, 0.369, 1, 0.439, 0.839, 0.82, 1)
|
||||
|
||||
[sub_resource type="GradientTexture2D" id="GradientTexture_rod"]
|
||||
gradient = SubResource("Gradient_rod")
|
||||
width = 128
|
||||
height = 128
|
||||
fill_from = Vector2(0.15, 0.85)
|
||||
fill_to = Vector2(0.85, 0.15)
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_script")
|
||||
item_id = &"basic_fishing_rod"
|
||||
display_name = "Basic Fishing Rod"
|
||||
description = "A dependable starter rod. Select it to cast."
|
||||
category = 0
|
||||
icon = SubResource("GradientTexture_rod")
|
||||
stackable = false
|
||||
max_stack = 1
|
||||
usable = true
|
||||
equippable = true
|
||||
hotbar_allowed = true
|
||||
8
items/catalog/item_catalog.tres
Normal file
8
items/catalog/item_catalog.tres
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[gd_resource type="Resource" script_class="ItemCatalog" load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://items/item_catalog.gd" id="1_script"]
|
||||
[ext_resource type="Resource" path="res://items/catalog/basic_fishing_rod.tres" id="2_rod"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_script")
|
||||
items = [ExtResource("2_rod")]
|
||||
30
items/item_catalog.gd
Normal file
30
items/item_catalog.gd
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
class_name ItemCatalog
|
||||
extends Resource
|
||||
|
||||
const ItemDataType = preload("res://items/item_data.gd")
|
||||
|
||||
@export var items: Array[ItemDataType] = []
|
||||
|
||||
|
||||
func get_item_by_id(item_id: StringName) -> ItemDataType:
|
||||
if item_id.is_empty():
|
||||
return null
|
||||
for item: ItemDataType in items:
|
||||
if item != null and item.item_id == item_id:
|
||||
return item
|
||||
return null
|
||||
|
||||
|
||||
func get_valid_items() -> Array[ItemDataType]:
|
||||
var result: Array[ItemDataType] = []
|
||||
var seen: Dictionary[StringName, bool] = {}
|
||||
for item: ItemDataType in items:
|
||||
if (
|
||||
item == null
|
||||
or not item.is_valid()
|
||||
or seen.has(item.item_id)
|
||||
):
|
||||
continue
|
||||
seen[item.item_id] = true
|
||||
result.append(item)
|
||||
return result
|
||||
1
items/item_catalog.gd.uid
Normal file
1
items/item_catalog.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://citlop0eiu8rx
|
||||
36
items/item_data.gd
Normal file
36
items/item_data.gd
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
class_name ItemData
|
||||
extends Resource
|
||||
|
||||
enum Category {
|
||||
ROD,
|
||||
TOOL,
|
||||
BAIT,
|
||||
CONSUMABLE,
|
||||
UTILITY,
|
||||
QUEST,
|
||||
COSMETIC,
|
||||
}
|
||||
|
||||
@export var item_id: StringName
|
||||
@export var display_name: String
|
||||
@export_multiline var description: String
|
||||
@export var category: Category = Category.UTILITY
|
||||
@export var icon: Texture2D
|
||||
@export var stackable: bool = false
|
||||
@export_range(1, 999, 1) var max_stack: int = 1
|
||||
@export var usable: bool = false
|
||||
@export var equippable: bool = false
|
||||
@export var hotbar_allowed: bool = false
|
||||
|
||||
|
||||
func is_valid() -> bool:
|
||||
return (
|
||||
not item_id.is_empty()
|
||||
and not display_name.strip_edges().is_empty()
|
||||
and max_stack >= 1
|
||||
and (stackable or max_stack == 1)
|
||||
)
|
||||
|
||||
|
||||
func get_category_name() -> String:
|
||||
return Category.keys()[category].capitalize()
|
||||
1
items/item_data.gd.uid
Normal file
1
items/item_data.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cah5thda5aohb
|
||||
20
items/owned_item.gd
Normal file
20
items/owned_item.gd
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
class_name OwnedItem
|
||||
extends Resource
|
||||
|
||||
@export var item_id: StringName
|
||||
@export var quantity: int = 1
|
||||
|
||||
|
||||
func is_valid() -> bool:
|
||||
return not item_id.is_empty() and quantity > 0
|
||||
|
||||
|
||||
func duplicate_record():
|
||||
return duplicate(true)
|
||||
|
||||
|
||||
func to_save_dict() -> Dictionary:
|
||||
return {
|
||||
"item_id": String(item_id),
|
||||
"quantity": quantity,
|
||||
}
|
||||
1
items/owned_item.gd.uid
Normal file
1
items/owned_item.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c7julskwitfxi
|
||||
56
main/main.gd
56
main/main.gd
|
|
@ -18,9 +18,12 @@ const PlayerSettingsManagerType = preload(
|
|||
const PlayerSettingsType = preload("res://settings/player_settings.gd")
|
||||
const TitleScreenType = preload("res://ui/title_screen.gd")
|
||||
const PauseMenuType = preload("res://ui/pause_menu.gd")
|
||||
const ItemCatalogType = preload("res://items/item_catalog.gd")
|
||||
const ItemDataType = preload("res://items/item_data.gd")
|
||||
|
||||
@export var fish_catalog: FishPoolType
|
||||
@export var pelican_buyer_profile: FishBuyerProfileType
|
||||
@export var item_catalog: ItemCatalogType
|
||||
|
||||
@onready var _test_world: TestWorldType = $TestWorld
|
||||
@onready var _player: PlayerType = %Player
|
||||
|
|
@ -38,17 +41,25 @@ func _ready() -> void:
|
|||
_player.inventory,
|
||||
_player.wallet
|
||||
)
|
||||
_player.bag.setup(item_catalog)
|
||||
_player.hotbar.setup(_player.bag, item_catalog)
|
||||
_save_manager.setup(
|
||||
_player.inventory,
|
||||
_player.collection_log,
|
||||
_player.wallet,
|
||||
fish_catalog
|
||||
fish_catalog,
|
||||
_player.bag,
|
||||
_player.hotbar,
|
||||
item_catalog
|
||||
)
|
||||
_save_manager.set_autosave_enabled(false)
|
||||
_fishing_spot.setup(
|
||||
_player,
|
||||
_player.inventory,
|
||||
_player.collection_log
|
||||
_player.collection_log,
|
||||
_player.bag,
|
||||
_player.hotbar,
|
||||
item_catalog
|
||||
)
|
||||
_game_ui.setup(
|
||||
_player,
|
||||
|
|
@ -58,7 +69,10 @@ func _ready() -> void:
|
|||
_player.fish_sale_service,
|
||||
pelican_buyer_profile,
|
||||
fish_catalog,
|
||||
_fishing_spot
|
||||
_fishing_spot,
|
||||
_player.bag,
|
||||
_player.hotbar,
|
||||
item_catalog
|
||||
)
|
||||
_water_recovery.setup(
|
||||
_player,
|
||||
|
|
@ -93,9 +107,22 @@ func _ready() -> void:
|
|||
_on_reset_progress_requested
|
||||
)
|
||||
pause_menu.quit_requested.connect(_on_quit_requested)
|
||||
pause_menu.menu_visibility_changed.connect(
|
||||
_game_ui.set_system_menu_open
|
||||
)
|
||||
_player.hotbar.selected_slot_changed.connect(
|
||||
_on_active_hotbar_item_changed
|
||||
)
|
||||
_fishing_spot.ready_for_equipment_refresh.connect(
|
||||
_refresh_active_hotbar_item
|
||||
)
|
||||
_water_recovery.recovery_starting.connect(
|
||||
_on_water_recovery_starting
|
||||
)
|
||||
_on_active_hotbar_item_changed(
|
||||
_player.hotbar.get_selected_slot(),
|
||||
_player.hotbar.get_selected_item_id()
|
||||
)
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
|
|
@ -182,6 +209,29 @@ func _on_water_recovery_starting() -> void:
|
|||
_game_ui.get_pause_menu().close_for_water_recovery()
|
||||
|
||||
|
||||
func _on_active_hotbar_item_changed(
|
||||
_slot_index: int,
|
||||
item_id: StringName,
|
||||
) -> void:
|
||||
if _fishing_spot.state != FishingSpotType.FishingState.READY:
|
||||
return
|
||||
var item = item_catalog.get_item_by_id(item_id)
|
||||
var active_is_rod: bool = (
|
||||
item != null
|
||||
and item.category == ItemDataType.Category.ROD
|
||||
and _player.bag.owns_item(item_id)
|
||||
)
|
||||
_player.set_active_item_is_rod(active_is_rod)
|
||||
_fishing_spot.refresh_active_item_status()
|
||||
|
||||
|
||||
func _refresh_active_hotbar_item() -> void:
|
||||
_on_active_hotbar_item_changed(
|
||||
_player.hotbar.get_selected_slot(),
|
||||
_player.hotbar.get_selected_item_id()
|
||||
)
|
||||
|
||||
|
||||
func _on_quit_requested() -> void:
|
||||
_settings_manager.save_if_dirty()
|
||||
if _gameplay_started:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=11 format=3]
|
||||
[gd_scene load_steps=12 format=3]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://world/test_world.tscn" id="1_world"]
|
||||
[ext_resource type="PackedScene" path="res://player/player.tscn" id="2_player"]
|
||||
|
|
@ -10,11 +10,13 @@
|
|||
[ext_resource type="Script" path="res://world/water_recovery_controller.gd" id="8_recovery"]
|
||||
[ext_resource type="Script" path="res://save/player_save_manager.gd" id="9_save"]
|
||||
[ext_resource type="Script" path="res://settings/player_settings_manager.gd" id="10_settings"]
|
||||
[ext_resource type="Resource" path="res://items/catalog/item_catalog.tres" id="11_items"]
|
||||
|
||||
[node name="Main" type="Node3D"]
|
||||
script = ExtResource("3_main")
|
||||
fish_catalog = ExtResource("6_pool")
|
||||
pelican_buyer_profile = ExtResource("7_pelicans")
|
||||
item_catalog = ExtResource("11_items")
|
||||
|
||||
[node name="TestWorld" parent="." instance=ExtResource("1_world")]
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ const CollectionLogType = preload("res://collection/collection_log.gd")
|
|||
const FishCatchType = preload("res://fish/fish_catch.gd")
|
||||
const FishSaleServiceType = preload("res://economy/fish_sale_service.gd")
|
||||
const PlayerWalletType = preload("res://economy/player_wallet.gd")
|
||||
const PlayerBagType = preload("res://inventory/player_bag.gd")
|
||||
const PlayerHotbarType = preload("res://inventory/player_hotbar.gd")
|
||||
|
||||
class ShowcaseCameraSnapshot:
|
||||
extends RefCounted
|
||||
|
|
@ -67,6 +69,8 @@ class ShowcaseCameraSnapshot:
|
|||
@onready var collection_log: CollectionLogType = %CollectionLog
|
||||
@onready var wallet: PlayerWalletType = %Wallet
|
||||
@onready var fish_sale_service: FishSaleServiceType = %FishSaleService
|
||||
@onready var bag: PlayerBagType = %Bag
|
||||
@onready var hotbar: PlayerHotbarType = %Hotbar
|
||||
@onready var _cast_origin: Marker3D = %CastOrigin
|
||||
@onready var _fishing_rod: Node3D = %FishingRod
|
||||
@onready var _fishing_rod_tip: Marker3D = %FishingRodTip
|
||||
|
|
@ -198,10 +202,18 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
|
||||
if event.is_action_pressed("camera_zoom_in"):
|
||||
if (
|
||||
event is InputEventMouseButton
|
||||
and event.shift_pressed
|
||||
and event.is_action_pressed("camera_zoom_in")
|
||||
):
|
||||
_set_target_zoom(_target_zoom - zoom_step)
|
||||
get_viewport().set_input_as_handled()
|
||||
elif event.is_action_pressed("camera_zoom_out"):
|
||||
elif (
|
||||
event is InputEventMouseButton
|
||||
and event.shift_pressed
|
||||
and event.is_action_pressed("camera_zoom_out")
|
||||
):
|
||||
_set_target_zoom(_target_zoom + zoom_step)
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
|
@ -298,6 +310,13 @@ func get_fishing_rod() -> Node3D:
|
|||
return _fishing_rod
|
||||
|
||||
|
||||
func set_active_item_is_rod(active_is_rod: bool) -> void:
|
||||
if _showcase_rod_state_stored:
|
||||
_showcase_rod_visibility = active_is_rod
|
||||
else:
|
||||
_fishing_rod.visible = active_is_rod
|
||||
|
||||
|
||||
func get_cast_origin_position() -> Vector3:
|
||||
return _cast_origin.global_position
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
[gd_scene load_steps=13 format=3]
|
||||
[gd_scene load_steps=15 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://player/player.gd" id="1_script"]
|
||||
[ext_resource type="Script" path="res://inventory/fish_inventory.gd" id="2_inventory"]
|
||||
[ext_resource type="Script" path="res://collection/collection_log.gd" id="3_collection"]
|
||||
[ext_resource type="Script" path="res://economy/player_wallet.gd" id="4_wallet"]
|
||||
[ext_resource type="Script" path="res://economy/fish_sale_service.gd" id="5_sale_service"]
|
||||
[ext_resource type="Script" path="res://inventory/player_bag.gd" id="6_bag"]
|
||||
[ext_resource type="Script" path="res://inventory/player_hotbar.gd" id="7_hotbar"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="PlayerShape"]
|
||||
radius = 0.45
|
||||
|
|
@ -88,6 +90,14 @@ script = ExtResource("4_wallet")
|
|||
unique_name_in_owner = true
|
||||
script = ExtResource("5_sale_service")
|
||||
|
||||
[node name="Bag" type="Node" parent="."]
|
||||
unique_name_in_owner = true
|
||||
script = ExtResource("6_bag")
|
||||
|
||||
[node name="Hotbar" type="Node" parent="."]
|
||||
unique_name_in_owner = true
|
||||
script = ExtResource("7_hotbar")
|
||||
|
||||
[node name="CatchDisplayAnchor" type="Marker3D" parent="Visuals"]
|
||||
unique_name_in_owner = true
|
||||
position = Vector3(0, 1.45, -1.15)
|
||||
|
|
|
|||
|
|
@ -29,9 +29,11 @@ Sprint Shift
|
|||
Sneak Ctrl
|
||||
Slow walk Alt
|
||||
Rotate camera Hold right mouse, or use right stick
|
||||
Zoom camera Mouse wheel
|
||||
Cycle active hotbar slot Mouse wheel
|
||||
Select hotbar slot 1 through 9
|
||||
Zoom camera Shift + mouse wheel
|
||||
Cast / withdraw / reel Left mouse
|
||||
Inventory and Logbook Tab
|
||||
Cooler, Bag, and Logbook Tab
|
||||
Game Menu / back Escape
|
||||
|
||||
FISHING
|
||||
|
|
@ -54,7 +56,8 @@ FEATURES TO TRY
|
|||
- Barrier-and-chase catching
|
||||
- Accessibility auto-click settings
|
||||
- Catch showcase and fish size variation
|
||||
- Inventory, Logbook, favorites, and sorting
|
||||
- Cooler, Bag, Logbook, favorites, and sorting
|
||||
- Basic Fishing Rod equipment and the 1–9 hotbar
|
||||
- Pelican selling and wallet updates
|
||||
- Save, Continue, New Game, and Delete Save
|
||||
- Game Menu and persistent camera settings
|
||||
|
|
|
|||
|
|
@ -92,6 +92,51 @@ camera_zoom_out={
|
|||
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":32,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":5,"canceled":false,"pressed":false,"double_click":false,"script":null)
|
||||
]
|
||||
}
|
||||
hotbar_1={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":49,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
hotbar_2={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":50,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
hotbar_3={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":51,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
hotbar_4={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":52,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
hotbar_5={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":53,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
hotbar_6={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":54,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
hotbar_7={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":55,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
hotbar_8={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":56,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
hotbar_9={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":57,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
open_backpack={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
|
|
|
|||
|
|
@ -7,8 +7,13 @@ const CollectionLogType = preload("res://collection/collection_log.gd")
|
|||
const PlayerWalletType = preload("res://economy/player_wallet.gd")
|
||||
const FishPoolType = preload("res://fish/fish_pool.gd")
|
||||
const SaveInspectionType = preload("res://save/player_save_inspection.gd")
|
||||
const ItemCatalogType = preload("res://items/item_catalog.gd")
|
||||
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 SAVE_VERSION: int = 1
|
||||
const SAVE_VERSION: int = 2
|
||||
const BASIC_ROD_ID: StringName = &"basic_fishing_rod"
|
||||
const SAVE_PATH: String = "user://player_save.json"
|
||||
const TEMP_PATH: String = "user://player_save.json.tmp"
|
||||
const BACKUP_PATH: String = "user://player_save.json.backup"
|
||||
|
|
@ -21,6 +26,9 @@ class LoadSnapshot:
|
|||
var discovered_ids: Array[StringName] = []
|
||||
var wallet_balance: int = 0
|
||||
var next_catch_sequence: int = 1
|
||||
var bag_items: Array[OwnedItemType] = []
|
||||
var hotbar_slots: Array[StringName] = []
|
||||
var selected_hotbar_slot: int = 0
|
||||
|
||||
|
||||
@export_range(0.05, 5.0, 0.05) var autosave_delay: float = 0.5
|
||||
|
|
@ -29,6 +37,9 @@ var _inventory: FishInventoryType
|
|||
var _collection_log: CollectionLogType
|
||||
var _wallet: PlayerWalletType
|
||||
var _catalog: FishPoolType
|
||||
var _bag: PlayerBagType
|
||||
var _hotbar: PlayerHotbarType
|
||||
var _item_catalog: ItemCatalogType
|
||||
var _autosave_timer: Timer
|
||||
var _is_configured: bool = false
|
||||
var _is_restoring: bool = false
|
||||
|
|
@ -49,16 +60,25 @@ func setup(
|
|||
collection_log: CollectionLogType,
|
||||
wallet: PlayerWalletType,
|
||||
catalog: FishPoolType,
|
||||
bag: PlayerBagType,
|
||||
hotbar: PlayerHotbarType,
|
||||
item_catalog: ItemCatalogType,
|
||||
) -> void:
|
||||
_inventory = inventory
|
||||
_collection_log = collection_log
|
||||
_wallet = wallet
|
||||
_catalog = catalog
|
||||
_bag = bag
|
||||
_hotbar = hotbar
|
||||
_item_catalog = item_catalog
|
||||
_is_configured = (
|
||||
_inventory != null
|
||||
and _collection_log != null
|
||||
and _wallet != null
|
||||
and _catalog != null
|
||||
and _bag != null
|
||||
and _hotbar != null
|
||||
and _item_catalog != null
|
||||
)
|
||||
if not _is_configured:
|
||||
push_error("PlayerSaveManager setup is missing required references.")
|
||||
|
|
@ -71,6 +91,16 @@ func setup(
|
|||
_collection_log.fish_discovered.connect(_on_fish_discovered)
|
||||
if not _wallet.balance_changed.is_connected(_on_balance_changed):
|
||||
_wallet.balance_changed.connect(_on_balance_changed)
|
||||
if not _bag.contents_changed.is_connected(_mark_dirty):
|
||||
_bag.contents_changed.connect(_mark_dirty)
|
||||
if not _hotbar.slots_changed.is_connected(_mark_dirty):
|
||||
_hotbar.slots_changed.connect(_mark_dirty)
|
||||
if not _hotbar.selected_slot_changed.is_connected(
|
||||
_on_selected_hotbar_slot_changed
|
||||
):
|
||||
_hotbar.selected_slot_changed.connect(
|
||||
_on_selected_hotbar_slot_changed
|
||||
)
|
||||
|
||||
|
||||
func load_player_data() -> bool:
|
||||
|
|
@ -131,11 +161,18 @@ func load_player_data() -> bool:
|
|||
var wallet_restored: bool = _wallet.restore_balance(
|
||||
snapshot.wallet_balance
|
||||
)
|
||||
var bag_restored: bool = _bag.replace_all_items(snapshot.bag_items)
|
||||
var hotbar_restored: bool = _hotbar.replace_state(
|
||||
snapshot.hotbar_slots,
|
||||
snapshot.selected_hotbar_slot
|
||||
)
|
||||
_is_restoring = false
|
||||
if (
|
||||
not inventory_restored
|
||||
or not collection_restored
|
||||
or not wallet_restored
|
||||
or not bag_restored
|
||||
or not hotbar_restored
|
||||
):
|
||||
push_error("Validated player save could not be restored.")
|
||||
return false
|
||||
|
|
@ -174,7 +211,13 @@ func inspect_save() -> SaveInspectionType:
|
|||
result.status = SaveInspectionType.Status.UNSUPPORTED_VERSION
|
||||
result.message = "This save belongs to a newer game version."
|
||||
return result
|
||||
if result.detected_version < 1:
|
||||
result.status = SaveInspectionType.Status.MALFORMED
|
||||
result.message = "The save version is unsupported."
|
||||
return result
|
||||
if result.detected_version != SAVE_VERSION:
|
||||
save_data = _migrate_save(save_data, result.detected_version)
|
||||
if save_data.is_empty():
|
||||
result.status = SaveInspectionType.Status.MALFORMED
|
||||
result.message = "The save version is unsupported."
|
||||
return result
|
||||
|
|
@ -300,6 +343,14 @@ func _build_save_dictionary() -> Dictionary:
|
|||
if fish_id.is_empty():
|
||||
return {}
|
||||
discovered_strings.append(String(fish_id))
|
||||
var serialized_items: Array[Dictionary] = []
|
||||
for owned: OwnedItemType in _bag.get_all_items():
|
||||
if owned == null or not owned.is_valid():
|
||||
return {}
|
||||
serialized_items.append(owned.to_save_dict())
|
||||
var serialized_slots: Array[String] = []
|
||||
for item_id: StringName in _hotbar.get_slots():
|
||||
serialized_slots.append(String(item_id))
|
||||
if (
|
||||
_wallet.get_balance() < 0
|
||||
or _wallet.get_balance() > MAX_SAFE_BALANCE
|
||||
|
|
@ -323,6 +374,13 @@ func _build_save_dictionary() -> Dictionary:
|
|||
"next_catch_sequence": _inventory.get_next_catch_sequence(),
|
||||
"catches": serialized_catches,
|
||||
},
|
||||
"bag": {
|
||||
"items": serialized_items,
|
||||
},
|
||||
"hotbar": {
|
||||
"selected_slot": _hotbar.get_selected_slot(),
|
||||
"slots": serialized_slots,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -331,16 +389,23 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot:
|
|||
typeof(save_data.get("wallet")) != TYPE_DICTIONARY
|
||||
or typeof(save_data.get("collection")) != TYPE_DICTIONARY
|
||||
or typeof(save_data.get("inventory")) != TYPE_DICTIONARY
|
||||
or typeof(save_data.get("bag")) != TYPE_DICTIONARY
|
||||
or typeof(save_data.get("hotbar")) != TYPE_DICTIONARY
|
||||
):
|
||||
return null
|
||||
var wallet_data: Dictionary = save_data["wallet"]
|
||||
var collection_data: Dictionary = save_data["collection"]
|
||||
var inventory_data: Dictionary = save_data["inventory"]
|
||||
var bag_data: Dictionary = save_data["bag"]
|
||||
var hotbar_data: Dictionary = save_data["hotbar"]
|
||||
if (
|
||||
not wallet_data.has("balance")
|
||||
or typeof(collection_data.get("discovered_fish_ids")) != TYPE_ARRAY
|
||||
or typeof(inventory_data.get("catches")) != TYPE_ARRAY
|
||||
or not inventory_data.has("next_catch_sequence")
|
||||
or typeof(bag_data.get("items")) != TYPE_ARRAY
|
||||
or typeof(hotbar_data.get("slots")) != TYPE_ARRAY
|
||||
or not hotbar_data.has("selected_slot")
|
||||
):
|
||||
return null
|
||||
|
||||
|
|
@ -424,6 +489,81 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot:
|
|||
requested_next_sequence,
|
||||
maximum_sequence + 1
|
||||
)
|
||||
|
||||
var seen_items: Dictionary[StringName, bool] = {}
|
||||
var item_values: Array = bag_data["items"]
|
||||
for item_index: int in range(item_values.size()):
|
||||
var value: Variant = item_values[item_index]
|
||||
if typeof(value) != TYPE_DICTIONARY:
|
||||
push_warning("Skipped invalid saved Bag item.")
|
||||
continue
|
||||
var item_record: Dictionary = value
|
||||
var item_id: StringName = StringName(
|
||||
str(item_record.get("item_id", ""))
|
||||
)
|
||||
var item_data = _item_catalog.get_item_by_id(item_id)
|
||||
var quantity: int = _read_integer(
|
||||
item_record.get("quantity"),
|
||||
-1,
|
||||
999
|
||||
)
|
||||
if (
|
||||
item_id.is_empty()
|
||||
or item_data == null
|
||||
or not item_data.is_valid()
|
||||
or quantity <= 0
|
||||
or seen_items.has(item_id)
|
||||
):
|
||||
push_warning(
|
||||
"Skipped invalid or unknown saved Bag item '%s'."
|
||||
% String(item_id)
|
||||
)
|
||||
continue
|
||||
var maximum: int = (
|
||||
item_data.max_stack if item_data.stackable else 1
|
||||
)
|
||||
if quantity > maximum:
|
||||
push_warning(
|
||||
"Skipped saved Bag item with invalid quantity '%s'."
|
||||
% String(item_id)
|
||||
)
|
||||
continue
|
||||
var owned := OwnedItemType.new()
|
||||
owned.item_id = item_id
|
||||
owned.quantity = quantity
|
||||
seen_items[item_id] = true
|
||||
snapshot.bag_items.append(owned)
|
||||
|
||||
var slot_values: Array = hotbar_data["slots"]
|
||||
snapshot.hotbar_slots.resize(PlayerHotbarType.SLOT_COUNT)
|
||||
snapshot.hotbar_slots.fill(StringName())
|
||||
for slot_index: int in range(
|
||||
mini(slot_values.size(), PlayerHotbarType.SLOT_COUNT)
|
||||
):
|
||||
var slot_value: Variant = slot_values[slot_index]
|
||||
if typeof(slot_value) not in [TYPE_STRING, TYPE_STRING_NAME]:
|
||||
continue
|
||||
var slot_item_id: StringName = StringName(str(slot_value))
|
||||
if slot_item_id.is_empty():
|
||||
continue
|
||||
var slot_item = _item_catalog.get_item_by_id(slot_item_id)
|
||||
if (
|
||||
slot_item != null
|
||||
and slot_item.is_valid()
|
||||
and slot_item.hotbar_allowed
|
||||
and seen_items.has(slot_item_id)
|
||||
):
|
||||
snapshot.hotbar_slots[slot_index] = slot_item_id
|
||||
var selected_slot: int = _read_integer(
|
||||
hotbar_data["selected_slot"],
|
||||
0,
|
||||
PlayerHotbarType.SLOT_COUNT - 1
|
||||
)
|
||||
snapshot.selected_hotbar_slot = clampi(
|
||||
selected_slot,
|
||||
0,
|
||||
PlayerHotbarType.SLOT_COUNT - 1
|
||||
)
|
||||
return snapshot
|
||||
|
||||
|
||||
|
|
@ -433,6 +573,26 @@ func _migrate_save(
|
|||
) -> Dictionary:
|
||||
if from_version == SAVE_VERSION:
|
||||
return data
|
||||
if from_version == 1:
|
||||
var migrated: Dictionary = data.duplicate(true)
|
||||
migrated["save_version"] = SAVE_VERSION
|
||||
migrated["bag"] = {
|
||||
"items": [
|
||||
{
|
||||
"item_id": String(BASIC_ROD_ID),
|
||||
"quantity": 1,
|
||||
},
|
||||
],
|
||||
}
|
||||
var slots: Array[String] = []
|
||||
slots.resize(PlayerHotbarType.SLOT_COUNT)
|
||||
slots.fill("")
|
||||
slots[0] = String(BASIC_ROD_ID)
|
||||
migrated["hotbar"] = {
|
||||
"selected_slot": 0,
|
||||
"slots": slots,
|
||||
}
|
||||
return migrated
|
||||
return {}
|
||||
|
||||
|
||||
|
|
@ -456,6 +616,13 @@ func _on_balance_changed(_new_balance: int, _delta: int) -> void:
|
|||
_mark_dirty()
|
||||
|
||||
|
||||
func _on_selected_hotbar_slot_changed(
|
||||
_slot_index: int,
|
||||
_item_id: StringName,
|
||||
) -> void:
|
||||
_mark_dirty()
|
||||
|
||||
|
||||
func _on_autosave_timeout() -> void:
|
||||
if _is_dirty:
|
||||
save_now()
|
||||
|
|
@ -496,9 +663,20 @@ func _restore_defaults() -> void:
|
|||
_is_restoring = true
|
||||
var empty_catches: Array[FishCatchType] = []
|
||||
var empty_discoveries: Array[StringName] = []
|
||||
var default_items: Array[OwnedItemType] = []
|
||||
var basic_rod := OwnedItemType.new()
|
||||
basic_rod.item_id = BASIC_ROD_ID
|
||||
basic_rod.quantity = 1
|
||||
default_items.append(basic_rod)
|
||||
var default_slots: Array[StringName] = []
|
||||
default_slots.resize(PlayerHotbarType.SLOT_COUNT)
|
||||
default_slots.fill(StringName())
|
||||
default_slots[0] = BASIC_ROD_ID
|
||||
_inventory.replace_all_catches(empty_catches, 1)
|
||||
_collection_log.replace_discovered_ids(empty_discoveries)
|
||||
_wallet.restore_balance(0)
|
||||
_bag.replace_all_items(default_items)
|
||||
_hotbar.replace_state(default_slots, 0)
|
||||
_is_restoring = false
|
||||
_is_dirty = false
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ const FishingSpotType = preload("res://fishing/fishing_spot.gd")
|
|||
const PlayerMenuType = preload("res://ui/player_menu.gd")
|
||||
const PlayerType = preload("res://player/player.gd")
|
||||
const PlayerWalletType = preload("res://economy/player_wallet.gd")
|
||||
const ItemCatalogType = preload("res://items/item_catalog.gd")
|
||||
const PlayerBagType = preload("res://inventory/player_bag.gd")
|
||||
const PlayerHotbarType = preload("res://inventory/player_hotbar.gd")
|
||||
const HotbarUIType = preload("res://ui/hotbar.gd")
|
||||
const TitleScreenType = preload("res://ui/title_screen.gd")
|
||||
const PauseMenuType = preload("res://ui/pause_menu.gd")
|
||||
|
||||
|
|
@ -26,10 +30,12 @@ const PauseMenuType = preload("res://ui/pause_menu.gd")
|
|||
@onready var _screen_fade: ScreenFade = %ScreenFade
|
||||
@onready var _title_screen: TitleScreenType = %TitleScreen
|
||||
@onready var _pause_menu: PauseMenuType = %PauseMenu
|
||||
@onready var _hotbar_ui: HotbarUIType = %Hotbar
|
||||
|
||||
var _showcase_active: bool = false
|
||||
var _player_menu_open: bool = false
|
||||
var _gameplay_ui_enabled: bool = false
|
||||
var _fishing_spot: FishingSpotType
|
||||
|
||||
|
||||
func setup(
|
||||
|
|
@ -41,7 +47,11 @@ func setup(
|
|||
default_buyer: FishBuyerProfileType,
|
||||
catalog: FishPoolType,
|
||||
fishing_spot: FishingSpotType,
|
||||
bag: PlayerBagType,
|
||||
hotbar: PlayerHotbarType,
|
||||
item_catalog: ItemCatalogType,
|
||||
) -> void:
|
||||
_fishing_spot = fishing_spot
|
||||
fishing_spot.status_changed.connect(_on_fishing_status_changed)
|
||||
fishing_spot.catch_display_changed.connect(_on_catch_display_changed)
|
||||
fishing_spot.showcase_changed.connect(_on_showcase_changed)
|
||||
|
|
@ -56,8 +66,12 @@ func setup(
|
|||
sale_service,
|
||||
default_buyer,
|
||||
catalog,
|
||||
fishing_spot
|
||||
fishing_spot,
|
||||
bag,
|
||||
hotbar,
|
||||
item_catalog
|
||||
)
|
||||
_hotbar_ui.setup(hotbar, bag, item_catalog, fishing_spot)
|
||||
|
||||
|
||||
func close_player_menu() -> void:
|
||||
|
|
@ -89,10 +103,22 @@ func set_gameplay_ui_enabled(enabled: bool) -> void:
|
|||
if not enabled:
|
||||
close_player_menu_for_session_end()
|
||||
_fishing_panel.visible = false
|
||||
_hotbar_ui.hide()
|
||||
_hotbar_ui.set_gameplay_input_enabled(false)
|
||||
else:
|
||||
_hotbar_ui.show()
|
||||
_hotbar_ui.set_gameplay_input_enabled(true)
|
||||
_refresh_fishing_panel_visibility()
|
||||
|
||||
|
||||
func set_system_menu_open(is_open: bool) -> void:
|
||||
_hotbar_ui.visible = _gameplay_ui_enabled and not is_open
|
||||
_hotbar_ui.set_gameplay_input_enabled(
|
||||
_gameplay_ui_enabled and not is_open and not _player_menu_open
|
||||
)
|
||||
_hotbar_ui.set_drag_enabled(_player_menu_open and not is_open)
|
||||
|
||||
|
||||
func get_screen_fade() -> ScreenFade:
|
||||
return _screen_fade
|
||||
|
||||
|
|
@ -104,6 +130,8 @@ func get_title_screen() -> TitleScreenType:
|
|||
func _on_fishing_status_changed(status: String) -> void:
|
||||
if _showcase_active:
|
||||
return
|
||||
if _fishing_spot != null and _fishing_spot.is_fighting():
|
||||
return
|
||||
_set_fishing_status(status)
|
||||
|
||||
|
||||
|
|
@ -125,7 +153,6 @@ func _refresh_fishing_panel_visibility() -> void:
|
|||
_fishing_panel.visible = (
|
||||
_gameplay_ui_enabled
|
||||
and has_content
|
||||
and not _player_menu_open
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -138,17 +165,26 @@ func _on_catch_display_changed(
|
|||
active_barrier_index: int,
|
||||
visible: bool,
|
||||
) -> void:
|
||||
var encounter_visible: bool = (
|
||||
visible
|
||||
and _fishing_spot != null
|
||||
and _fishing_spot.is_fighting()
|
||||
)
|
||||
_hotbar_ui.set_item_name_suppressed(encounter_visible)
|
||||
_green_catch_progress.value = progress * 100.0
|
||||
_red_chase_progress.value = maxf(chase_progress, 0.0) * 100.0
|
||||
_catch_track.visible = visible
|
||||
_barrier_summary.visible = visible
|
||||
if not visible:
|
||||
_catch_track.visible = encounter_visible
|
||||
_barrier_summary.visible = encounter_visible
|
||||
_barrier_health.visible = encounter_visible
|
||||
if not encounter_visible:
|
||||
_barrier_health.visible = false
|
||||
_clear_barrier_markers()
|
||||
_barrier_summary.text = ""
|
||||
_barrier_health.text = ""
|
||||
_refresh_fishing_panel_visibility()
|
||||
return
|
||||
_status_label.text = ""
|
||||
_status_label.visible = false
|
||||
|
||||
_update_barrier_markers(
|
||||
barrier_positions,
|
||||
|
|
@ -183,7 +219,7 @@ func _on_catch_display_changed(
|
|||
]
|
||||
)
|
||||
else:
|
||||
_barrier_health.text = ""
|
||||
_barrier_health.text = "Hold left click to reel"
|
||||
|
||||
var chase_gap: float = progress - chase_progress
|
||||
if chase_gap <= 0.05:
|
||||
|
|
@ -194,10 +230,6 @@ func _on_catch_display_changed(
|
|||
"\nRed is closing in!",
|
||||
]
|
||||
).strip_edges()
|
||||
_barrier_health.visible = (
|
||||
visible
|
||||
and (active_barrier_index >= 0 or chase_gap <= 0.05)
|
||||
)
|
||||
_refresh_fishing_panel_visibility()
|
||||
|
||||
|
||||
|
|
@ -262,4 +294,8 @@ func _on_showcase_changed(
|
|||
|
||||
func _on_player_menu_visibility_changed(is_open: bool) -> void:
|
||||
_player_menu_open = is_open
|
||||
_hotbar_ui.set_gameplay_input_enabled(
|
||||
_gameplay_ui_enabled and not is_open
|
||||
)
|
||||
_hotbar_ui.set_drag_enabled(is_open)
|
||||
_refresh_fishing_panel_visibility()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=11 format=3]
|
||||
[gd_scene load_steps=12 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/game_ui.gd" id="1_ui"]
|
||||
[ext_resource type="PackedScene" path="res://ui/player_menu.tscn" id="2_menu"]
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
[ext_resource type="Script" path="res://ui/screen_fade.gd" id="4_fade"]
|
||||
[ext_resource type="PackedScene" path="res://ui/title_screen.tscn" id="5_title"]
|
||||
[ext_resource type="PackedScene" path="res://ui/pause_menu.tscn" id="6_pause"]
|
||||
[ext_resource type="PackedScene" path="res://ui/hotbar.tscn" id="7_hotbar"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBox_chase_background"]
|
||||
bg_color = Color(0.055, 0.105, 0.125, 1)
|
||||
|
|
@ -37,35 +38,36 @@ script = ExtResource("1_ui")
|
|||
[node name="FishingPanel" type="PanelContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
z_index = 60
|
||||
anchors_preset = 7
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -210.0
|
||||
offset_top = -130.0
|
||||
offset_right = 210.0
|
||||
offset_bottom = -24.0
|
||||
offset_left = -205.0
|
||||
offset_top = -216.0
|
||||
offset_right = 205.0
|
||||
offset_bottom = -128.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
mouse_filter = 2
|
||||
theme = ExtResource("3_theme")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="FishingPanel"]
|
||||
theme_override_constants/margin_left = 18
|
||||
theme_override_constants/margin_top = 12
|
||||
theme_override_constants/margin_right = 18
|
||||
theme_override_constants/margin_bottom = 12
|
||||
theme_override_constants/margin_left = 14
|
||||
theme_override_constants/margin_top = 8
|
||||
theme_override_constants/margin_right = 14
|
||||
theme_override_constants/margin_bottom = 8
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="FishingPanel/MarginContainer"]
|
||||
theme_override_constants/separation = 6
|
||||
theme_override_constants/separation = 4
|
||||
|
||||
[node name="StatusLabel" type="Label" parent="FishingPanel/MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
text = ""
|
||||
horizontal_alignment = 1
|
||||
theme_override_font_sizes/font_size = 17
|
||||
theme_override_font_sizes/font_size = 16
|
||||
|
||||
[node name="ShowcaseDetails" type="Label" parent="FishingPanel/MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -75,7 +77,7 @@ horizontal_alignment = 1
|
|||
[node name="CatchTrack" type="Control" parent="FishingPanel/MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(0, 22)
|
||||
custom_minimum_size = Vector2(0, 20)
|
||||
|
||||
[node name="GreenCatchProgress" type="ProgressBar" parent="FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -128,6 +130,9 @@ horizontal_alignment = 1
|
|||
[node name="PlayerMenu" parent="." instance=ExtResource("2_menu")]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="Hotbar" parent="." instance=ExtResource("7_hotbar")]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="ScreenFade" type="ColorRect" parent="."]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
|
|
|
|||
185
ui/hotbar.gd
Normal file
185
ui/hotbar.gd
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
class_name HotbarUI
|
||||
extends Control
|
||||
|
||||
const ItemCatalogType = preload("res://items/item_catalog.gd")
|
||||
const PlayerBagType = preload("res://inventory/player_bag.gd")
|
||||
const PlayerHotbarType = preload("res://inventory/player_hotbar.gd")
|
||||
const HotbarSlotType = preload("res://ui/hotbar_slot.gd")
|
||||
const FishingSpotType = preload("res://fishing/fishing_spot.gd")
|
||||
|
||||
@onready var _slot_row: HBoxContainer = %SlotRow
|
||||
@onready var _selected_item_label: Label = %SelectedItemLabel
|
||||
@onready var _item_name_timer: Timer = %ItemNameTimer
|
||||
|
||||
var _hotbar: PlayerHotbarType
|
||||
var _bag: PlayerBagType
|
||||
var _catalog: ItemCatalogType
|
||||
var _fishing_spot: FishingSpotType
|
||||
var _slots: Array[HotbarSlotType] = []
|
||||
var _gameplay_input_enabled: bool = false
|
||||
var _drag_enabled: bool = false
|
||||
var _hovered_slot_index: int = -1
|
||||
var _item_name_suppressed: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_item_name_timer.timeout.connect(_on_item_name_timer_timeout)
|
||||
|
||||
|
||||
func setup(
|
||||
hotbar: PlayerHotbarType,
|
||||
bag: PlayerBagType,
|
||||
catalog: ItemCatalogType,
|
||||
fishing_spot: FishingSpotType,
|
||||
) -> void:
|
||||
_hotbar = hotbar
|
||||
_bag = bag
|
||||
_catalog = catalog
|
||||
_fishing_spot = fishing_spot
|
||||
if not _hotbar.slots_changed.is_connected(_refresh):
|
||||
_hotbar.slots_changed.connect(_refresh)
|
||||
if not _hotbar.selected_slot_changed.is_connected(
|
||||
_on_selected_slot_changed
|
||||
):
|
||||
_hotbar.selected_slot_changed.connect(_on_selected_slot_changed)
|
||||
if not _bag.contents_changed.is_connected(_refresh):
|
||||
_bag.contents_changed.connect(_refresh)
|
||||
_build_slots()
|
||||
_refresh()
|
||||
|
||||
|
||||
func set_gameplay_input_enabled(enabled: bool) -> void:
|
||||
_gameplay_input_enabled = enabled
|
||||
|
||||
|
||||
func set_drag_enabled(enabled: bool) -> void:
|
||||
_drag_enabled = enabled
|
||||
for slot: HotbarSlotType in _slots:
|
||||
slot.set_drag_enabled(enabled)
|
||||
if not enabled:
|
||||
_hovered_slot_index = -1
|
||||
_hide_item_name()
|
||||
|
||||
|
||||
func set_item_name_suppressed(suppressed: bool) -> void:
|
||||
_item_name_suppressed = suppressed
|
||||
if suppressed:
|
||||
_hovered_slot_index = -1
|
||||
_hide_item_name()
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if (
|
||||
not _gameplay_input_enabled
|
||||
or _hotbar == null
|
||||
or _fishing_spot == null
|
||||
or not _fishing_spot.can_change_hotbar_selection()
|
||||
):
|
||||
return
|
||||
if event is InputEventKey and event.pressed and not event.echo:
|
||||
for index: int in range(PlayerHotbarType.SLOT_COUNT):
|
||||
if event.is_action_pressed("hotbar_%d" % (index + 1)):
|
||||
_hotbar.select_slot(index)
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if (
|
||||
event is InputEventMouseButton
|
||||
and event.pressed
|
||||
and not event.shift_pressed
|
||||
):
|
||||
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
|
||||
_hotbar.cycle_selection(-1)
|
||||
get_viewport().set_input_as_handled()
|
||||
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
||||
_hotbar.cycle_selection(1)
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _build_slots() -> void:
|
||||
for child: Node in _slot_row.get_children():
|
||||
child.queue_free()
|
||||
_slots.clear()
|
||||
for index: int in range(PlayerHotbarType.SLOT_COUNT):
|
||||
var slot := HotbarSlotType.new()
|
||||
slot.toggle_mode = true
|
||||
slot.setup(index, _hotbar, _bag, _catalog)
|
||||
slot.set_drag_enabled(_drag_enabled)
|
||||
slot.item_hovered.connect(_on_slot_item_hovered)
|
||||
slot.item_hover_ended.connect(_on_slot_item_hover_ended)
|
||||
slot.item_drag_started.connect(_on_slot_drag_started)
|
||||
slot.item_drag_finished.connect(_on_slot_drag_finished)
|
||||
_slot_row.add_child(slot)
|
||||
_slots.append(slot)
|
||||
|
||||
|
||||
func _refresh() -> void:
|
||||
for slot: HotbarSlotType in _slots:
|
||||
slot.refresh()
|
||||
|
||||
|
||||
func _on_selected_slot_changed(
|
||||
_slot_index: int,
|
||||
_item_id: StringName,
|
||||
) -> void:
|
||||
_refresh()
|
||||
if _hovered_slot_index < 0:
|
||||
_show_selected_item_briefly()
|
||||
|
||||
|
||||
func _on_slot_item_hovered(
|
||||
slot_index: int,
|
||||
item_id: StringName,
|
||||
) -> void:
|
||||
if _item_name_suppressed:
|
||||
return
|
||||
_hovered_slot_index = slot_index
|
||||
_item_name_timer.stop()
|
||||
_show_item_name(item_id)
|
||||
|
||||
|
||||
func _on_slot_item_hover_ended(slot_index: int) -> void:
|
||||
if slot_index != _hovered_slot_index:
|
||||
return
|
||||
_hovered_slot_index = -1
|
||||
_show_selected_item_briefly()
|
||||
|
||||
|
||||
func _on_slot_drag_started() -> void:
|
||||
_hovered_slot_index = -1
|
||||
_hide_item_name()
|
||||
|
||||
|
||||
func _on_slot_drag_finished() -> void:
|
||||
_show_selected_item_briefly()
|
||||
|
||||
|
||||
func _show_selected_item_briefly() -> void:
|
||||
if _item_name_suppressed or _hotbar == null:
|
||||
_hide_item_name()
|
||||
return
|
||||
_show_item_name(_hotbar.get_selected_item_id())
|
||||
if _selected_item_label.visible:
|
||||
_item_name_timer.start()
|
||||
|
||||
|
||||
func _show_item_name(item_id: StringName) -> void:
|
||||
if item_id.is_empty() or _catalog == null:
|
||||
_hide_item_name()
|
||||
return
|
||||
var item := _catalog.get_item_by_id(item_id)
|
||||
if item == null:
|
||||
_hide_item_name()
|
||||
return
|
||||
_selected_item_label.text = item.display_name
|
||||
_selected_item_label.visible = true
|
||||
|
||||
|
||||
func _hide_item_name() -> void:
|
||||
_item_name_timer.stop()
|
||||
_selected_item_label.text = ""
|
||||
_selected_item_label.visible = false
|
||||
|
||||
|
||||
func _on_item_name_timer_timeout() -> void:
|
||||
if _hovered_slot_index < 0:
|
||||
_hide_item_name()
|
||||
1
ui/hotbar.gd.uid
Normal file
1
ui/hotbar.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://642w0srh4vwj
|
||||
74
ui/hotbar.tscn
Normal file
74
ui/hotbar.tscn
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
[gd_scene load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/hotbar.gd" id="1_script"]
|
||||
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
|
||||
|
||||
[node name="Hotbar" type="Control"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
z_index = 20
|
||||
layout_mode = 3
|
||||
anchors_preset = 12
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -84.0
|
||||
offset_bottom = -8.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
mouse_filter = 2
|
||||
theme = ExtResource("2_theme")
|
||||
script = ExtResource("1_script")
|
||||
|
||||
[node name="Center" type="CenterContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="Panel" type="PanelContainer" parent="Center"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="Center/Panel"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
theme_override_constants/margin_left = 5
|
||||
theme_override_constants/margin_top = 5
|
||||
theme_override_constants/margin_right = 5
|
||||
theme_override_constants/margin_bottom = 5
|
||||
|
||||
[node name="SlotRow" type="HBoxContainer" parent="Center/Panel/Margin"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
theme_override_constants/separation = 4
|
||||
|
||||
[node name="SelectedItemLabel" type="Label" parent="."]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 10
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -150.0
|
||||
offset_top = -28.0
|
||||
offset_right = 150.0
|
||||
offset_bottom = -8.0
|
||||
grow_horizontal = 2
|
||||
mouse_filter = 2
|
||||
clip_text = true
|
||||
text_overrun_behavior = 3
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
theme_override_colors/font_color = Color(0.925, 0.953, 0.965, 1)
|
||||
theme_override_colors/font_outline_color = Color(0.015, 0.02, 0.03, 0.95)
|
||||
theme_override_constants/outline_size = 2
|
||||
theme_override_font_sizes/font_size = 12
|
||||
|
||||
[node name="ItemNameTimer" type="Timer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
wait_time = 1.5
|
||||
one_shot = true
|
||||
187
ui/hotbar_slot.gd
Normal file
187
ui/hotbar_slot.gd
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
class_name HotbarSlot
|
||||
extends Button
|
||||
|
||||
signal item_hovered(slot_index: int, item_id: StringName)
|
||||
signal item_hover_ended(slot_index: int)
|
||||
signal item_drag_started
|
||||
signal item_drag_finished
|
||||
|
||||
const ItemCatalogType = preload("res://items/item_catalog.gd")
|
||||
const ItemDataType = preload("res://items/item_data.gd")
|
||||
const PlayerBagType = preload("res://inventory/player_bag.gd")
|
||||
const PlayerHotbarType = preload("res://inventory/player_hotbar.gd")
|
||||
|
||||
var slot_index: int = 0
|
||||
var _hotbar: PlayerHotbarType
|
||||
var _bag: PlayerBagType
|
||||
var _catalog: ItemCatalogType
|
||||
var _drag_enabled: bool = false
|
||||
var _drag_in_progress: bool = false
|
||||
var _slot_number_label: Label
|
||||
var _quantity_label: Label
|
||||
|
||||
|
||||
func setup(
|
||||
index: int,
|
||||
hotbar: PlayerHotbarType,
|
||||
bag: PlayerBagType,
|
||||
catalog: ItemCatalogType,
|
||||
) -> void:
|
||||
slot_index = index
|
||||
_hotbar = hotbar
|
||||
_bag = bag
|
||||
_catalog = catalog
|
||||
custom_minimum_size = Vector2(50, 50)
|
||||
expand_icon = true
|
||||
focus_mode = Control.FOCUS_ALL
|
||||
_slot_number_label = _create_corner_label(str(slot_index + 1))
|
||||
_quantity_label = _create_corner_label("", true)
|
||||
pressed.connect(_select_slot)
|
||||
mouse_entered.connect(_on_mouse_entered)
|
||||
mouse_exited.connect(_on_mouse_exited)
|
||||
refresh()
|
||||
|
||||
|
||||
func set_drag_enabled(enabled: bool) -> void:
|
||||
_drag_enabled = enabled
|
||||
mouse_filter = (
|
||||
Control.MOUSE_FILTER_STOP
|
||||
if enabled
|
||||
else Control.MOUSE_FILTER_IGNORE
|
||||
)
|
||||
|
||||
|
||||
func refresh() -> void:
|
||||
if _hotbar == null:
|
||||
return
|
||||
var item_id: StringName = _hotbar.get_item_id(slot_index)
|
||||
var item: ItemDataType = (
|
||||
_catalog.get_item_by_id(item_id)
|
||||
if _catalog != null and not item_id.is_empty()
|
||||
else null
|
||||
)
|
||||
icon = item.icon if item != null else null
|
||||
var quantity: int = (
|
||||
_bag.get_quantity(item_id)
|
||||
if _bag != null and not item_id.is_empty()
|
||||
else 0
|
||||
)
|
||||
var quantity_text: String = (
|
||||
"×%d" % quantity
|
||||
if item != null and item.stackable and quantity > 1
|
||||
else ""
|
||||
)
|
||||
text = ""
|
||||
_quantity_label.text = quantity_text
|
||||
_quantity_label.visible = not quantity_text.is_empty()
|
||||
tooltip_text = (
|
||||
item.display_name if item != null else "Empty hotbar slot"
|
||||
)
|
||||
button_pressed = slot_index == _hotbar.get_selected_slot()
|
||||
|
||||
|
||||
func _create_corner_label(
|
||||
label_text: String,
|
||||
bottom_right: bool = false,
|
||||
) -> Label:
|
||||
var label := Label.new()
|
||||
label.text = label_text
|
||||
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
label.add_theme_font_size_override("font_size", 11)
|
||||
label.add_theme_color_override("font_color", UIPalette.TEXT)
|
||||
if bottom_right:
|
||||
label.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
|
||||
label.position = Vector2(-22.0, -20.0)
|
||||
else:
|
||||
label.position = Vector2(5.0, 2.0)
|
||||
add_child(label)
|
||||
return label
|
||||
|
||||
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
if (
|
||||
_drag_enabled
|
||||
and event is InputEventMouseButton
|
||||
and event.button_index == MOUSE_BUTTON_RIGHT
|
||||
and event.pressed
|
||||
and _hotbar != null
|
||||
):
|
||||
_hotbar.clear_slot(slot_index)
|
||||
accept_event()
|
||||
|
||||
|
||||
func _get_drag_data(_at_position: Vector2) -> Variant:
|
||||
if not _drag_enabled or _hotbar == null:
|
||||
return null
|
||||
var item_id: StringName = _hotbar.get_item_id(slot_index)
|
||||
if item_id.is_empty():
|
||||
return null
|
||||
var item: ItemDataType = _catalog.get_item_by_id(item_id)
|
||||
var preview := TextureRect.new()
|
||||
preview.custom_minimum_size = Vector2(44, 44)
|
||||
preview.texture = item.icon if item != null else null
|
||||
preview.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
preview.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
preview.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
set_drag_preview(preview)
|
||||
_drag_in_progress = true
|
||||
item_drag_started.emit()
|
||||
return {
|
||||
"kind": "hotbar_slot",
|
||||
"slot_index": slot_index,
|
||||
"item_id": String(item_id),
|
||||
}
|
||||
|
||||
|
||||
func _can_drop_data(_at_position: Vector2, data: Variant) -> bool:
|
||||
if not _drag_enabled or typeof(data) != TYPE_DICTIONARY:
|
||||
return false
|
||||
var payload: Dictionary = data
|
||||
var kind: String = str(payload.get("kind", ""))
|
||||
if kind == "hotbar_slot":
|
||||
var source_index: int = int(payload.get("slot_index", -1))
|
||||
return (
|
||||
source_index >= 0
|
||||
and source_index < PlayerHotbarType.SLOT_COUNT
|
||||
and source_index != slot_index
|
||||
)
|
||||
if kind != "bag_item":
|
||||
return false
|
||||
var item_id: StringName = StringName(str(payload.get("item_id", "")))
|
||||
if _bag == null or not _bag.owns_item(item_id) or _catalog == null:
|
||||
return false
|
||||
var item: ItemDataType = _catalog.get_item_by_id(item_id)
|
||||
return item != null and item.is_valid() and item.hotbar_allowed
|
||||
|
||||
|
||||
func _drop_data(_at_position: Vector2, data: Variant) -> void:
|
||||
if not _can_drop_data(Vector2.ZERO, data) or _hotbar == null:
|
||||
return
|
||||
var payload: Dictionary = data
|
||||
if str(payload.get("kind", "")) == "hotbar_slot":
|
||||
_hotbar.swap_slots(int(payload["slot_index"]), slot_index)
|
||||
else:
|
||||
_hotbar.assign_item(
|
||||
slot_index,
|
||||
StringName(str(payload["item_id"]))
|
||||
)
|
||||
|
||||
|
||||
func _select_slot() -> void:
|
||||
if _hotbar != null:
|
||||
_hotbar.select_slot(slot_index)
|
||||
|
||||
|
||||
func _on_mouse_entered() -> void:
|
||||
if _hotbar != null:
|
||||
item_hovered.emit(slot_index, _hotbar.get_item_id(slot_index))
|
||||
|
||||
|
||||
func _on_mouse_exited() -> void:
|
||||
item_hover_ended.emit(slot_index)
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_DRAG_END and _drag_in_progress:
|
||||
_drag_in_progress = false
|
||||
item_drag_finished.emit()
|
||||
1
ui/hotbar_slot.gd.uid
Normal file
1
ui/hotbar_slot.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://swcob5q48f8e
|
||||
46
ui/item_drag_source.gd
Normal file
46
ui/item_drag_source.gd
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
class_name ItemDragSource
|
||||
extends Button
|
||||
|
||||
var item_id: StringName
|
||||
var item_icon: Texture2D
|
||||
var item_name: String
|
||||
|
||||
|
||||
func setup(
|
||||
new_item_id: StringName,
|
||||
new_item_name: String,
|
||||
new_icon: Texture2D,
|
||||
) -> void:
|
||||
item_id = new_item_id
|
||||
item_name = new_item_name
|
||||
item_icon = new_icon
|
||||
|
||||
|
||||
func _get_drag_data(_at_position: Vector2) -> Variant:
|
||||
if item_id.is_empty() or disabled:
|
||||
return null
|
||||
set_drag_preview(_create_drag_preview())
|
||||
return {
|
||||
"kind": "bag_item",
|
||||
"item_id": String(item_id),
|
||||
}
|
||||
|
||||
|
||||
func _create_drag_preview() -> Control:
|
||||
var preview := VBoxContainer.new()
|
||||
preview.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
preview.add_theme_constant_override("separation", 2)
|
||||
var texture := TextureRect.new()
|
||||
texture.custom_minimum_size = Vector2(44, 44)
|
||||
texture.texture = item_icon
|
||||
texture.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
texture.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
texture.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
preview.add_child(texture)
|
||||
var label := Label.new()
|
||||
label.text = item_name
|
||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
label.add_theme_font_size_override("font_size", 12)
|
||||
preview.add_child(label)
|
||||
return preview
|
||||
1
ui/item_drag_source.gd.uid
Normal file
1
ui/item_drag_source.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cfv418ixq3eeb
|
||||
|
|
@ -13,6 +13,7 @@ const FishingSpotType = preload("res://fishing/fishing_spot.gd")
|
|||
signal return_to_title_requested
|
||||
signal reset_progress_requested
|
||||
signal quit_requested
|
||||
signal menu_visibility_changed(is_open: bool)
|
||||
|
||||
enum ConfirmationAction {
|
||||
NONE,
|
||||
|
|
@ -100,6 +101,7 @@ func open_menu() -> void:
|
|||
_confirmation_action = ConfirmationAction.NONE
|
||||
show()
|
||||
%ResumeButton.grab_focus()
|
||||
menu_visibility_changed.emit(true)
|
||||
|
||||
|
||||
func resume() -> void:
|
||||
|
|
@ -138,6 +140,7 @@ func close_menu(
|
|||
else:
|
||||
_control_snapshot_stored = false
|
||||
_apply_mouse_close_policy(reason)
|
||||
menu_visibility_changed.emit(false)
|
||||
|
||||
|
||||
func handle_escape() -> bool:
|
||||
|
|
|
|||
|
|
@ -13,11 +13,18 @@ const FishPoolType = preload("res://fish/fish_pool.gd")
|
|||
const FishingSpotType = preload("res://fishing/fishing_spot.gd")
|
||||
const PlayerType = preload("res://player/player.gd")
|
||||
const PlayerWalletType = preload("res://economy/player_wallet.gd")
|
||||
const ItemCatalogType = preload("res://items/item_catalog.gd")
|
||||
const ItemDataType = preload("res://items/item_data.gd")
|
||||
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 ItemDragSourceType = preload("res://ui/item_drag_source.gd")
|
||||
|
||||
signal menu_visibility_changed(is_open: bool)
|
||||
|
||||
enum Section {
|
||||
INVENTORY,
|
||||
COOLER,
|
||||
BAG,
|
||||
LOGBOOK,
|
||||
}
|
||||
|
||||
|
|
@ -37,11 +44,18 @@ enum CloseReason {
|
|||
}
|
||||
|
||||
@onready var _inventory_tab: Button = %InventoryTab
|
||||
@onready var _bag_tab: Button = %BagTab
|
||||
@onready var _logbook_tab: Button = %LogbookTab
|
||||
@onready var _close_button: Button = %CloseButton
|
||||
@onready var _wallet_balance: Label = %WalletBalance
|
||||
@onready var _inventory_section: Control = %InventorySection
|
||||
@onready var _bag_section: Control = %BagSection
|
||||
@onready var _logbook_section: Control = %LogbookSection
|
||||
@onready var _bag_empty: Label = %BagEmpty
|
||||
@onready var _bag_grid: GridContainer = %BagGrid
|
||||
@onready var _bag_detail_texture: TextureRect = %BagDetailTexture
|
||||
@onready var _bag_detail_name: Label = %BagDetailName
|
||||
@onready var _bag_detail_data: Label = %BagDetailData
|
||||
@onready var _sort_option: OptionButton = %SortOption
|
||||
@onready var _sort_direction: Button = %SortDirection
|
||||
@onready var _held_value: Label = %HeldValue
|
||||
|
|
@ -69,10 +83,14 @@ var _sale_service: FishSaleServiceType
|
|||
var _default_buyer: FishBuyerProfileType
|
||||
var _catalog: FishPoolType
|
||||
var _fishing_spot: FishingSpotType
|
||||
var _current_section: Section = Section.INVENTORY
|
||||
var _bag: PlayerBagType
|
||||
var _hotbar: PlayerHotbarType
|
||||
var _item_catalog: ItemCatalogType
|
||||
var _current_section: Section = Section.COOLER
|
||||
var _sort_mode: SortMode = SortMode.CATCH_ORDER
|
||||
var _sort_descending: bool = true
|
||||
var _selected_catch_id: StringName
|
||||
var _selected_bag_item_id: StringName
|
||||
var _prior_movement_enabled: bool = true
|
||||
var _prior_camera_input_enabled: bool = true
|
||||
var _prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
|
||||
|
|
@ -87,8 +105,9 @@ var _sale_in_progress: bool = false
|
|||
|
||||
func _ready() -> void:
|
||||
_inventory_tab.pressed.connect(
|
||||
_show_section.bind(Section.INVENTORY)
|
||||
_show_section.bind(Section.COOLER)
|
||||
)
|
||||
_bag_tab.pressed.connect(_show_section.bind(Section.BAG))
|
||||
_logbook_tab.pressed.connect(
|
||||
_show_section.bind(Section.LOGBOOK)
|
||||
)
|
||||
|
|
@ -116,6 +135,9 @@ func setup(
|
|||
default_buyer: FishBuyerProfileType,
|
||||
catalog: FishPoolType,
|
||||
fishing_spot: FishingSpotType,
|
||||
bag: PlayerBagType,
|
||||
hotbar: PlayerHotbarType,
|
||||
item_catalog: ItemCatalogType,
|
||||
) -> void:
|
||||
_player = player
|
||||
_inventory = inventory
|
||||
|
|
@ -125,6 +147,9 @@ func setup(
|
|||
_default_buyer = default_buyer
|
||||
_catalog = catalog
|
||||
_fishing_spot = fishing_spot
|
||||
_bag = bag
|
||||
_hotbar = hotbar
|
||||
_item_catalog = item_catalog
|
||||
if not _inventory.catches_changed.is_connected(_on_inventory_changed):
|
||||
_inventory.catches_changed.connect(_on_inventory_changed)
|
||||
if not _collection_log.fish_discovered.is_connected(_on_fish_discovered):
|
||||
|
|
@ -133,6 +158,8 @@ func setup(
|
|||
_wallet.balance_changed.connect(_on_wallet_balance_changed)
|
||||
if not _fishing_spot.bite_activated.is_connected(_on_bite_activated):
|
||||
_fishing_spot.bite_activated.connect(_on_bite_activated)
|
||||
if not _bag.contents_changed.is_connected(_on_bag_changed):
|
||||
_bag.contents_changed.connect(_on_bag_changed)
|
||||
_refresh_all()
|
||||
|
||||
|
||||
|
|
@ -194,6 +221,7 @@ func close_menu(
|
|||
) -> void:
|
||||
if not visible:
|
||||
return
|
||||
get_viewport().gui_cancel_drag()
|
||||
_close_sale_confirmation()
|
||||
var closing_generation: int = _menu_generation
|
||||
visible = false
|
||||
|
|
@ -268,17 +296,21 @@ func _show_section(section: Section) -> void:
|
|||
_current_section = section
|
||||
if not is_node_ready():
|
||||
return
|
||||
_inventory_section.visible = section == Section.INVENTORY
|
||||
_inventory_section.visible = section == Section.COOLER
|
||||
_bag_section.visible = section == Section.BAG
|
||||
_logbook_section.visible = section == Section.LOGBOOK
|
||||
_inventory_tab.button_pressed = section == Section.INVENTORY
|
||||
_inventory_tab.button_pressed = section == Section.COOLER
|
||||
_bag_tab.button_pressed = section == Section.BAG
|
||||
_logbook_tab.button_pressed = section == Section.LOGBOOK
|
||||
if visible:
|
||||
_focus_current_section()
|
||||
|
||||
|
||||
func _focus_current_section() -> void:
|
||||
if _current_section == Section.INVENTORY:
|
||||
if _current_section == Section.COOLER:
|
||||
_inventory_tab.grab_focus()
|
||||
elif _current_section == Section.BAG:
|
||||
_bag_tab.grab_focus()
|
||||
else:
|
||||
_logbook_tab.grab_focus()
|
||||
|
||||
|
|
@ -307,9 +339,99 @@ func _update_sort_direction_text() -> void:
|
|||
func _refresh_all() -> void:
|
||||
_refresh_economy_summary()
|
||||
_refresh_inventory()
|
||||
_refresh_bag()
|
||||
_refresh_logbook()
|
||||
|
||||
|
||||
func _on_bag_changed() -> void:
|
||||
_refresh_bag()
|
||||
|
||||
|
||||
func _refresh_bag() -> void:
|
||||
if not is_node_ready():
|
||||
return
|
||||
_clear_container(_bag_grid)
|
||||
var owned_items: Array[OwnedItemType] = (
|
||||
_bag.get_all_items() if _bag != null else []
|
||||
)
|
||||
owned_items.sort_custom(_sort_bag_items)
|
||||
_bag_empty.visible = owned_items.is_empty()
|
||||
if (
|
||||
not _selected_bag_item_id.is_empty()
|
||||
and (_bag == null or not _bag.owns_item(_selected_bag_item_id))
|
||||
):
|
||||
_selected_bag_item_id = StringName()
|
||||
for owned: OwnedItemType in owned_items:
|
||||
var item: ItemDataType = _item_catalog.get_item_by_id(owned.item_id)
|
||||
if item == null:
|
||||
continue
|
||||
var card := ItemDragSourceType.new()
|
||||
card.custom_minimum_size = Vector2(140, 88)
|
||||
card.expand_icon = true
|
||||
card.icon = item.icon
|
||||
card.text = "%s%s\n%s" % [
|
||||
item.display_name,
|
||||
" ×%d" % owned.quantity if owned.quantity > 1 else "",
|
||||
item.get_category_name(),
|
||||
]
|
||||
card.tooltip_text = (
|
||||
"Drag to a hotbar slot."
|
||||
if item.hotbar_allowed
|
||||
else "This item cannot be assigned to the hotbar."
|
||||
)
|
||||
card.setup(item.item_id, item.display_name, item.icon)
|
||||
card.pressed.connect(_select_bag_item.bind(item.item_id))
|
||||
_bag_grid.add_child(card)
|
||||
_update_bag_detail()
|
||||
|
||||
|
||||
func _sort_bag_items(a: OwnedItemType, b: OwnedItemType) -> bool:
|
||||
var item_a: ItemDataType = _item_catalog.get_item_by_id(a.item_id)
|
||||
var item_b: ItemDataType = _item_catalog.get_item_by_id(b.item_id)
|
||||
if item_a == null:
|
||||
return false
|
||||
if item_b == null:
|
||||
return true
|
||||
if item_a.category != item_b.category:
|
||||
return item_a.category < item_b.category
|
||||
return item_a.display_name.naturalnocasecmp_to(item_b.display_name) < 0
|
||||
|
||||
|
||||
func _select_bag_item(item_id: StringName) -> void:
|
||||
_selected_bag_item_id = item_id
|
||||
_update_bag_detail()
|
||||
|
||||
|
||||
func _update_bag_detail() -> void:
|
||||
var item: ItemDataType = (
|
||||
_item_catalog.get_item_by_id(_selected_bag_item_id)
|
||||
if _item_catalog != null and not _selected_bag_item_id.is_empty()
|
||||
else null
|
||||
)
|
||||
var quantity: int = (
|
||||
_bag.get_quantity(_selected_bag_item_id)
|
||||
if _bag != null and item != null
|
||||
else 0
|
||||
)
|
||||
_bag_detail_texture.texture = item.icon if item != null else null
|
||||
_bag_detail_name.text = item.display_name if item != null else ""
|
||||
_bag_detail_data.text = (
|
||||
"%s\nQuantity: %d\n%s\n%s"
|
||||
% [
|
||||
item.get_category_name(),
|
||||
quantity,
|
||||
item.description,
|
||||
(
|
||||
"Can be assigned to the hotbar."
|
||||
if item.hotbar_allowed
|
||||
else "Cannot be assigned to the hotbar."
|
||||
),
|
||||
]
|
||||
if item != null
|
||||
else "Select a Bag item for details."
|
||||
)
|
||||
|
||||
|
||||
func _on_inventory_changed() -> void:
|
||||
_refresh_economy_summary()
|
||||
_revalidate_confirmation()
|
||||
|
|
@ -384,7 +506,7 @@ func _compare_catches(left: FishCatchType, right: FishCatchType) -> bool:
|
|||
func _create_inventory_card(fish_catch: FishCatchType) -> Button:
|
||||
var card := Button.new()
|
||||
card.theme_type_variation = &"CardButton"
|
||||
card.custom_minimum_size = Vector2(152.0, 140.0)
|
||||
card.custom_minimum_size = Vector2(132.0, 118.0)
|
||||
card.toggle_mode = true
|
||||
card.button_pressed = fish_catch.catch_id == _selected_catch_id
|
||||
card.pressed.connect(_select_catch.bind(fish_catch.catch_id))
|
||||
|
|
@ -400,7 +522,7 @@ func _create_inventory_card(fish_catch: FishCatchType) -> Button:
|
|||
card.add_child(content)
|
||||
content.add_child(_create_texture_frame(
|
||||
fish_catch.fish.display_texture,
|
||||
Vector2(124.0, 78.0)
|
||||
Vector2(108.0, 62.0)
|
||||
))
|
||||
var name_label := Label.new()
|
||||
name_label.text = fish_catch.fish.display_name
|
||||
|
|
|
|||
|
|
@ -5,17 +5,19 @@
|
|||
|
||||
[node name="PlayerMenu" type="Control"]
|
||||
visible = false
|
||||
z_index = 30
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 0
|
||||
mouse_filter = 2
|
||||
theme = ExtResource("2_theme")
|
||||
script = ExtResource("1_menu")
|
||||
|
||||
[node name="Dimmer" type="ColorRect" parent="."]
|
||||
z_index = -20
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
|
|
@ -27,23 +29,23 @@ color = Color(0.015, 0.02, 0.03, 0.72)
|
|||
|
||||
[node name="MenuPanel" type="PanelContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchor_left = 0.055
|
||||
anchor_top = 0.045
|
||||
anchor_right = 0.945
|
||||
anchor_bottom = 0.955
|
||||
anchor_left = 0.07
|
||||
anchor_top = 0.055
|
||||
anchor_right = 0.93
|
||||
anchor_bottom = 0.72
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="MenuPanel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 22
|
||||
theme_override_constants/margin_top = 18
|
||||
theme_override_constants/margin_right = 22
|
||||
theme_override_constants/margin_bottom = 20
|
||||
theme_override_constants/margin_left = 14
|
||||
theme_override_constants/margin_top = 12
|
||||
theme_override_constants/margin_right = 14
|
||||
theme_override_constants/margin_bottom = 12
|
||||
|
||||
[node name="Layout" type="VBoxContainer" parent="MenuPanel/Margin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 12
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="Header" type="HBoxContainer" parent="MenuPanel/Margin/Layout"]
|
||||
layout_mode = 2
|
||||
|
|
@ -53,7 +55,7 @@ theme_override_constants/separation = 8
|
|||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
|
||||
theme_override_font_sizes/font_size = 26
|
||||
theme_override_font_sizes/font_size = 22
|
||||
text = "Player Menu"
|
||||
|
||||
[node name="WalletBalance" type="Label" parent="MenuPanel/Margin/Layout/Header"]
|
||||
|
|
@ -66,21 +68,28 @@ theme_override_colors/font_color = Color(1, 0.82, 0.4, 1)
|
|||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "Inventory"
|
||||
custom_minimum_size = Vector2(104, 36)
|
||||
text = "Cooler"
|
||||
custom_minimum_size = Vector2(88, 30)
|
||||
|
||||
[node name="BagTab" type="Button" parent="MenuPanel/Margin/Layout/Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "Bag"
|
||||
custom_minimum_size = Vector2(76, 30)
|
||||
|
||||
[node name="LogbookTab" type="Button" parent="MenuPanel/Margin/Layout/Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "Logbook"
|
||||
custom_minimum_size = Vector2(104, 36)
|
||||
custom_minimum_size = Vector2(88, 30)
|
||||
|
||||
[node name="CloseButton" type="Button" parent="MenuPanel/Margin/Layout/Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Close"
|
||||
custom_minimum_size = Vector2(76, 36)
|
||||
custom_minimum_size = Vector2(68, 30)
|
||||
|
||||
[node name="Separator" type="HSeparator" parent="MenuPanel/Margin/Layout"]
|
||||
layout_mode = 2
|
||||
|
|
@ -91,10 +100,10 @@ layout_mode = 2
|
|||
text = ""
|
||||
horizontal_alignment = 1
|
||||
theme_override_colors/font_color = Color(1, 0.82, 0.4, 1)
|
||||
theme_override_font_sizes/font_size = 16
|
||||
theme_override_font_sizes/font_size = 14
|
||||
|
||||
[node name="Content" type="Control" parent="MenuPanel/Margin/Layout"]
|
||||
custom_minimum_size = Vector2(0, 390)
|
||||
custom_minimum_size = Vector2(0, 260)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
|
|
@ -106,11 +115,11 @@ anchor_right = 1.0
|
|||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/separation = 10
|
||||
theme_override_constants/separation = 7
|
||||
|
||||
[node name="SortBar" type="HBoxContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
theme_override_constants/separation = 6
|
||||
|
||||
[node name="SortLabel" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
||||
layout_mode = 2
|
||||
|
|
@ -119,12 +128,12 @@ text = "Sort:"
|
|||
[node name="SortOption" type="OptionButton" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(145, 36)
|
||||
custom_minimum_size = Vector2(130, 30)
|
||||
|
||||
[node name="SortDirection" type="Button" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(125, 36)
|
||||
custom_minimum_size = Vector2(112, 30)
|
||||
text = "Newest first"
|
||||
|
||||
[node name="SortSpacer" type="Control" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
||||
|
|
@ -140,28 +149,28 @@ theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1)
|
|||
[node name="InventoryBody" type="HSplitContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
split_offset = 560
|
||||
theme_override_constants/separation = 12
|
||||
split_offset = 510
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="InventoryList" type="PanelContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody"]
|
||||
custom_minimum_size = Vector2(400, 0)
|
||||
custom_minimum_size = Vector2(350, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="InventoryListMargin" type="MarginContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 12
|
||||
theme_override_constants/margin_top = 12
|
||||
theme_override_constants/margin_right = 12
|
||||
theme_override_constants/margin_bottom = 12
|
||||
theme_override_constants/margin_left = 8
|
||||
theme_override_constants/margin_top = 8
|
||||
theme_override_constants/margin_right = 8
|
||||
theme_override_constants/margin_bottom = 8
|
||||
|
||||
[node name="InventoryStack" type="VBoxContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList/InventoryListMargin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
theme_override_constants/separation = 6
|
||||
|
||||
[node name="InventoryEmpty" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList/InventoryListMargin/InventoryStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "No fish in your inventory."
|
||||
text = "Your Cooler is empty."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="InventoryScroll" type="ScrollContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList/InventoryListMargin/InventoryStack"]
|
||||
|
|
@ -173,28 +182,28 @@ horizontal_scroll_mode = 0
|
|||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/h_separation = 10
|
||||
theme_override_constants/v_separation = 10
|
||||
theme_override_constants/h_separation = 7
|
||||
theme_override_constants/v_separation = 7
|
||||
columns = 3
|
||||
|
||||
[node name="DetailPanel" type="PanelContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody"]
|
||||
custom_minimum_size = Vector2(240, 0)
|
||||
custom_minimum_size = Vector2(210, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="DetailMargin" type="MarginContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 16
|
||||
theme_override_constants/margin_top = 14
|
||||
theme_override_constants/margin_right = 16
|
||||
theme_override_constants/margin_bottom = 14
|
||||
theme_override_constants/margin_left = 10
|
||||
theme_override_constants/margin_top = 9
|
||||
theme_override_constants/margin_right = 10
|
||||
theme_override_constants/margin_bottom = 9
|
||||
|
||||
[node name="DetailStack" type="VBoxContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
theme_override_constants/separation = 6
|
||||
|
||||
[node name="DetailTexture" type="TextureRect" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(208, 140)
|
||||
custom_minimum_size = Vector2(178, 100)
|
||||
layout_mode = 2
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
|
@ -204,7 +213,7 @@ texture_filter = 1
|
|||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
|
||||
theme_override_font_sizes/font_size = 22
|
||||
theme_override_font_sizes/font_size = 19
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="DetailData" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"]
|
||||
|
|
@ -234,6 +243,98 @@ autowrap_mode = 2
|
|||
horizontal_alignment = 1
|
||||
theme_override_colors/font_color = Color(1, 0.702, 0.278, 1)
|
||||
|
||||
[node name="BagSection" type="VBoxContainer" parent="MenuPanel/Margin/Layout/Content"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/separation = 7
|
||||
|
||||
[node name="BagHint" type="Label" parent="MenuPanel/Margin/Layout/Content/BagSection"]
|
||||
layout_mode = 2
|
||||
text = "Drag hotbar-compatible items onto a slot below. Right-click a slot to clear it."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="BagBody" type="HSplitContainer" parent="MenuPanel/Margin/Layout/Content/BagSection"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
split_offset = 540
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="BagList" type="PanelContainer" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody"]
|
||||
custom_minimum_size = Vector2(360, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagList"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 8
|
||||
theme_override_constants/margin_top = 8
|
||||
theme_override_constants/margin_right = 8
|
||||
theme_override_constants/margin_bottom = 8
|
||||
|
||||
[node name="Stack" type="VBoxContainer" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagList/Margin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 6
|
||||
|
||||
[node name="BagEmpty" type="Label" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagList/Margin/Stack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Your Bag is empty."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="BagScroll" type="ScrollContainer" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagList/Margin/Stack"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
horizontal_scroll_mode = 0
|
||||
|
||||
[node name="BagGrid" type="GridContainer" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagList/Margin/Stack/BagScroll"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/h_separation = 7
|
||||
theme_override_constants/v_separation = 7
|
||||
columns = 3
|
||||
|
||||
[node name="BagDetail" type="PanelContainer" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody"]
|
||||
custom_minimum_size = Vector2(220, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagDetail"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 10
|
||||
theme_override_constants/margin_top = 9
|
||||
theme_override_constants/margin_right = 10
|
||||
theme_override_constants/margin_bottom = 9
|
||||
|
||||
[node name="Stack" type="VBoxContainer" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagDetail/Margin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 6
|
||||
|
||||
[node name="BagDetailTexture" type="TextureRect" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagDetail/Margin/Stack"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(180, 96)
|
||||
layout_mode = 2
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
texture_filter = 1
|
||||
|
||||
[node name="BagDetailName" type="Label" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagDetail/Margin/Stack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 19
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="BagDetailData" type="Label" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagDetail/Margin/Stack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Select a Bag item for details."
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="LogbookSection" type="VBoxContainer" parent="MenuPanel/Margin/Layout/Content"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
|
|
@ -243,7 +344,7 @@ anchor_right = 1.0
|
|||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/separation = 10
|
||||
theme_override_constants/separation = 7
|
||||
|
||||
[node name="LogbookEmpty" type="Label" parent="MenuPanel/Margin/Layout/Content/LogbookSection"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -260,13 +361,14 @@ horizontal_scroll_mode = 0
|
|||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/h_separation = 10
|
||||
theme_override_constants/v_separation = 10
|
||||
theme_override_constants/h_separation = 7
|
||||
theme_override_constants/v_separation = 7
|
||||
columns = 4
|
||||
|
||||
[node name="SaleConfirmation" type="PanelContainer" parent="MenuPanel/Margin/Layout/Content"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
z_index = 40
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue