Skip to content

Item stacking (again) #233

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion classes/Item.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ extends StaticBody2D

class_name Item

signal amount_changed(new_amount: int)

enum MODE { LOOT, ITEMSLOT }

enum ITEM_TYPE { EQUIPMENT, CONSUMABLE, CURRENCY }
Expand All @@ -24,7 +26,12 @@ var expire_timer: Timer

var drop_rate: float = 0.0

var amount: int = 1
var amount_max: int = 1
var amount: int = 1:
set(val):
amount = val
amount_changed.emit(amount)

var price: int = 0
var value: int = 0

Expand Down Expand Up @@ -66,6 +73,10 @@ func _ready():
add_child(expire_timer)


func get_icon() -> Texture:
return $Icon.texture


func start_expire_timer():
expire_timer.start(expire_time)

Expand All @@ -89,10 +100,18 @@ func server_use(player: Player) -> bool:
ITEM_TYPE.CONSUMABLE:
if boost.hp > 0:
player.stats.heal(self.name, boost.hp)

if amount <= 1:
player.inventory.server_remove_item(uuid)
else:
player.inventory.server_set_item_amount(uuid, amount - 1)
return true

ITEM_TYPE.EQUIPMENT:
if player.equipment and player.equipment.server_equip_item(self):
player.inventory.server_remove_item(uuid)
return true

else:
GodotLogger.info("%s could not equip item %s" % [player.name, item_class])
return false
Expand All @@ -115,5 +134,11 @@ func from_json(data: Dictionary) -> bool:
return true


static func instance_from_json(data: Dictionary) -> Item:
var new_item := Item.new()
new_item.from_json(data)
return new_item


func _on_expire_timer_timeout():
queue_free()
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ func _ready():
user_authenticator.server_player_logged_in.connect(_on_server_player_logged_in)

# Connect to the disconnect signal, this is the trigger to emit the signal to remove the player from the server instance
_multiplayer_connection.multiplayer_api.peer_disconnected.connect(_on_server_peer_disconnected)
_multiplayer_connection.multiplayer_api.peer_disconnected.connect(
_on_server_peer_disconnected
)
else:
pass

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var _target_node: Node

var _equipment_synchronizer_rpc: EquipmentSynchronizerRPC = null

var items = {
var items: Dictionary = {
"Head": null,
"Body": null,
"Legs": null,
Expand Down Expand Up @@ -144,7 +144,7 @@ func _unequip_item(item_uuid: String) -> Item:


func get_item(item_uuid: String) -> Item:
for equipment_slot in items:
for equipment_slot: String in items:
var item: Item = items[equipment_slot]
if item != null and item.uuid == item_uuid:
return item
Expand All @@ -156,7 +156,7 @@ func get_boost() -> Boost:
var boost: Boost = Boost.new()
boost.identifier = "equipment"

for equipment_slot in items:
for equipment_slot: String in items:
var item: Item = items[equipment_slot]
if item != null:
boost.combine_boost(item.boost)
Expand All @@ -167,7 +167,7 @@ func get_boost() -> Boost:
func to_json() -> Dictionary:
var output: Dictionary = {}

for slot in items:
for slot: String in items:
if items[slot] != null:
var item: Item = items[slot]
output[slot] = item.to_json()
Expand All @@ -176,7 +176,7 @@ func to_json() -> Dictionary:


func from_json(data: Dictionary) -> bool:
for slot in data:
for slot: String in data:
if not slot in items:
GodotLogger.warn("Slot=[%s] does not exist in equipment items" % slot)
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class_name InventorySynchronizerComponent
signal loaded
signal item_added(item_uuid: String, item_class: String)
signal item_removed(item_uuid: String)
signal item_amount_changed(item_uuid: String)

signal gold_added(total: int, amount: int)
signal gold_removed(total: int, amount: int)
Expand Down Expand Up @@ -65,26 +66,62 @@ func server_sync_inventory(peer_id: int):
_inventory_synchronizer_rpc.sync_response(peer_id, to_json())


## Attempts to stack items or add them if there's nowhere else to stack them. Stacking is purely server sided.
func server_add_item(item: Item) -> bool:
if not _target_node.multiplayer_connection.is_server():
return false

#Currency is directly added to gold
if item.item_type == Item.ITEM_TYPE.CURRENCY:
server_add_gold(item.amount)
return true

item.collision_layer = 0

#Inventory full
if items.size() >= size:
return false

items.append(item)
#Try to stack
var existing_item: Item = null

#Look for an item that has available space.
for i_item in get_items_by_class(item.item_class):
if i_item.amount < i_item.amount_max:
existing_item = i_item
break

#If found, start the stacking.
if existing_item is Item:
var remaining_space: int = existing_item.amount_max - existing_item.amount
var amount_to_add: int = min(item.amount, remaining_space)
var surplus: int = max(0, item.amount - remaining_space)

#If there's space remaining, add some of this item's amount to the existing one.
#And remove it from the former.
#This is delegated to the server_set_item_amount() function which synchronizes amounts by itself.
if remaining_space > 0:
server_set_item_amount(item.uuid, item.amount - amount_to_add)
server_set_item_amount(existing_item.uuid, existing_item.amount + amount_to_add)

#Any remaining amount is added as a separate item
if surplus > 0:
server_add_item(item)
_inventory_synchronizer_rpc.add_item(
_target_node.peer_id, item.name, item.item_class, item.amount
)

_inventory_synchronizer_rpc.add_item(
_target_node.peer_id, item.name, item.item_class, item.amount
)
return true

return true
#Adding the item from scratch
else:
items.append(item)

_inventory_synchronizer_rpc.add_item(
_target_node.peer_id, item.name, item.item_class, item.amount
)

return true


func client_add_item(item_uuid: String, item_class: String, amount: int):
Expand Down Expand Up @@ -118,6 +155,24 @@ func client_remove_item(item_uuid: String):
item_removed.emit(item_uuid)


func server_set_item_amount(item_uuid: String, amount: int):
if not _target_node.multiplayer_connection.is_server():
return false

var item: Item = get_item(item_uuid)
if item != null:
item.amount = amount
_inventory_synchronizer_rpc.change_item_amount(_target_node.peer_id, item_uuid, amount)


func client_change_item_amount(item_uuid: String, amount: int):
var item: Item = get_item(item_uuid)
if item != null:
item.amount = amount

item_amount_changed.emit(item_uuid)


func get_item(item_uuid: String) -> Item:
for item in items:
if item.uuid == item_uuid:
Expand All @@ -126,13 +181,21 @@ func get_item(item_uuid: String) -> Item:
return null


## Returns the first instance of an item of this class
func get_items_by_class(item_class: String) -> Array[Item]:
var output: Array[Item] = []
for item: Item in items:
if item.item_class == item_class:
output.append(item)
return output


func server_use_item(item_uuid: String):
if not _target_node.multiplayer_connection.is_server():
return false

var item: Item = get_item(item_uuid)
if item and item.server_use(_target_node):
server_remove_item(item_uuid)
return true

return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ func remove_item(peer_id: int, item_uuid: String):
_remove_item.rpc_id(peer_id, item_uuid)


func change_item_amount(peer_id: int, item_uuid: String, amount: int):
_change_item_amount.rpc_id(peer_id, item_uuid, amount)


func add_gold(peer_id: int, total: int, amount: int):
_add_gold.rpc_id(peer_id, total, amount)

Expand Down Expand Up @@ -128,6 +132,22 @@ func _remove_item(u: String):
)


@rpc("call_remote", "authority", "reliable")
func _change_item_amount(u: String, a: int):
if _multiplayer_connection.client_player == null:
return

if _multiplayer_connection.client_player.component_list.has(
InventorySynchronizerComponent.COMPONENT_NAME
):
(
_multiplayer_connection
. client_player
. component_list[InventorySynchronizerComponent.COMPONENT_NAME]
. client_change_item_amount(u, a)
)


@rpc("call_remote", "authority", "reliable")
func _add_gold(t: int, a: int):
if _multiplayer_connection.client_player == null:
Expand Down
1 change: 1 addition & 0 deletions scenes/items/consumables/healthpotion/HealthPotion.gd
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ func _init():
item_class = "HealthPotion"
item_type = ITEM_TYPE.CONSUMABLE
boost.hp = 25
amount_max = 6
65 changes: 64 additions & 1 deletion scenes/player/inventory/Inventory.gd
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ var gold := 0:
gold = amount
$VBoxContainer/GoldValue.text = str(amount)

var panels = []
var panels: Array[Array] = []
var mouse_above_this_panel: InventoryPanel
var location_cache = {}

Expand Down Expand Up @@ -50,6 +50,7 @@ func _input(event):
if visible:
hide()
else:
update_inventory()
show()


Expand All @@ -67,20 +68,76 @@ func get_panel_at_pos(pos: Vector2) -> InventoryPanel:
return $GridContainer.get_node(panel_path)


func get_panels(occupied_only: bool) -> Array[InventoryPanel]:
var output: Array[InventoryPanel] = []
var temp_arr: Array = []

if occupied_only:
for row: Array in panels:
for panel: InventoryPanel in row:
if panel.item is Item:
temp_arr.append(panel)
else:
for row: Array in panels:
temp_arr += row

output.assign(temp_arr)
return output


func swap_items(from: Panel, to: Panel):
var temp_item: Item = to.item

to.item = from.item
from.item = temp_item


func update_inventory():
var occupied_panels: Array[InventoryPanel] = get_panels(true)
var panel_item_uuid_dictionary: Dictionary = {}
var panels_with_invalid_items: Dictionary = {}

#Ensure all panels have an item
assert(
occupied_panels.all(
func(panel_occupied: InventoryPanel): return panel_occupied.item is Item
)
)

#Store the uuid of the item that each panel has to accelerate the rest of the update.
for panel: InventoryPanel in occupied_panels:
panel_item_uuid_dictionary[panel.item.uuid] = panel

#All panels are assumed invalid until proven otherwise.
panels_with_invalid_items = panel_item_uuid_dictionary.duplicate()

#Check every inventory item
for inv_item: Item in inventory_synchronizer.items:
#Unmark as invalid those who have been found in the inventory
panels_with_invalid_items.erase(inv_item.uuid)

#No item with this uuid has been found in the displayed inventory, add it.
if not inv_item.uuid in panel_item_uuid_dictionary.keys():
place_item_at_free_slot(inv_item)

else:
#Item found, update it.
panel_item_uuid_dictionary[inv_item.uuid].item = inv_item
panel_item_uuid_dictionary[inv_item.uuid].queue_redraw()

#Clear any panels with items that are NOT in the inventory
for item_uuid: String in panels_with_invalid_items:
panels_with_invalid_items[item_uuid].item = null


func place_item_at_free_slot(item: Item) -> bool:
for y in range(SIZE.y):
for x in range(SIZE.x):
var pos = Vector2(x, y)
var panel: InventoryPanel = get_panel_at_pos(pos)
if panel.item == null:
panel.item = item
panel.queue_redraw()
return true
return false

Expand Down Expand Up @@ -133,3 +190,9 @@ func _on_item_removed(item_uuid: String):
if panel.item and panel.item.uuid == item_uuid:
panel.item = null
return


func _on_redraw_required():
for row: Array in panels:
for panel: InventoryPanel in row:
panel.queue_redraw()
Loading