Help removing item from inventory array

Godot Version

4.2.2

Question

I’ve followed this video https://youtu.be/V79YabQZC1s?si=MWnAHYEllFRONo2_ and I’ve been able to tweak it to what I need but I just can not get it to remove an item. Even calling out the items specifically like ‘press E to remove 1 apple’, I can’t get it to work. Has anyone used this video as a reference and knows how to remove an item?

Show your code here using the preformatted text ```, and screenshots of your scene structure.

1 Like

inventory_interface script:

extends Control

signal drop_slot_data(slot_data: SlotData)
signal force_close

var grabbed_slot_data: SlotData
var external_inventory_owner


@onready var player_inventory: PanelContainer = $PlayerInventory
@onready var grabbed_slot: PanelContainer = $GrabbedSlot
@onready var external_inventory: PanelContainer = $ExternalInventory
@onready var equip_inventory: PanelContainer = $EquipInventory
@onready var bank_inventory: PanelContainer = $BankInventory
@onready var hotbar_inventory: PanelContainer = $HotBarInventory


func _physics_process(delta: float) -> void:
	if grabbed_slot.visible:
		grabbed_slot.global_position = get_global_mouse_position() + Vector2(2, 2)
		
	if external_inventory_owner \
		and external_inventory_owner.global_position.distance_to(Global.get_global_position()) > 40:
			force_close.emit()

func set_player_inventory_data(inventory_data: InventoryData) -> void:
	inventory_data.inventory_interact.connect(on_inventory_interact)
	player_inventory.set_inventory_data(inventory_data)
	
func set_bank_inventory_data(inventory_data: InventoryData) -> void:
	inventory_data.inventory_interact.connect(on_inventory_interact)
	bank_inventory.set_inventory_data(inventory_data)
	
func set_equip_inventory_data(inventory_data: InventoryData) -> void:
	inventory_data.inventory_interact.connect(on_inventory_interact)
	equip_inventory.set_inventory_data(inventory_data)

func set_hotbar_inventory_data(inventory_data: InventoryData) -> void:
	inventory_data.inventory_interact.connect(on_inventory_interact)
	hotbar_inventory.set_inventory_data(inventory_data)


func clear_external_inventory() -> void:
	if external_inventory_owner:
		var inventory_data = external_inventory_owner.inventory_data
		
		inventory_data.inventory_interact.disconnect(on_inventory_interact)
		external_inventory.clear_inventory_data(inventory_data)
		
		external_inventory.hide()
		external_inventory_owner = null

func on_inventory_interact(inventory_data: InventoryData, index: int, button: int) -> void:
	match [grabbed_slot_data, button]:
		[null, MOUSE_BUTTON_LEFT]:
			grabbed_slot_data = inventory_data.grab_slot_data(index)
		[_, MOUSE_BUTTON_LEFT]:
			grabbed_slot_data = inventory_data.drop_slot_data(grabbed_slot_data, index)
		[null, MOUSE_BUTTON_RIGHT]:
			inventory_data.use_slot_data(index)
		[_, MOUSE_BUTTON_RIGHT]:
			grabbed_slot_data = inventory_data.drop_single_slot_data(grabbed_slot_data, index)
	update_grabbed_slot()

func update_grabbed_slot() -> void:
	if grabbed_slot_data:
		grabbed_slot.show()
		grabbed_slot.set_slot_data(grabbed_slot_data)
		
	else:
		grabbed_slot.hide()

func _on_visibility_changed():
	if not visible and grabbed_slot_data:
		drop_slot_data.emit(grabbed_slot_data)
		grabbed_slot_data = null
		update_grabbed_slot()

inventory_data script:

extends Resource
class_name InventoryData

signal inventory_updated(inventory_data: InventoryData)
signal inventory_interact(inventory_data: InventoryData, index: int, button: int)

@export var slot_datas: Array[SlotData]

func grab_slot_data(index: int) -> SlotData:
	var slot_data = slot_datas[index]

	if slot_data:
		slot_datas[index] = null
		inventory_updated.emit(self)
		Global.item_grabbed = true
		return slot_data
	else:
		return null
		

func drop_slot_data(grabbed_slot_data: SlotData, index: int) -> SlotData:
	var slot_data = slot_datas[index]

	var return_slot_data: SlotData
	if slot_data and slot_data.can_fully_merge_with(grabbed_slot_data):
		slot_data.fully_merge_with(grabbed_slot_data)
	else:
		slot_datas[index] = grabbed_slot_data
		return_slot_data = slot_data
		
	inventory_updated.emit(self)
	Global.item_grabbed = false
	return return_slot_data

func drop_single_slot_data(grabbed_slot_data: SlotData, index: int) -> SlotData:
	var slot_data = slot_datas[index]
	
	if not slot_data:
		slot_datas[index] = grabbed_slot_data.create_single_slot_data()
	elif slot_data.can_merge_with(grabbed_slot_data):
		slot_data.fully_merge_with(grabbed_slot_data.create_single_slot_data())
		
	inventory_updated.emit(self)
	
	if grabbed_slot_data.quantity > 0:
		return grabbed_slot_data
	else:
		Global.item_grabbed = false
		return null

func use_slot_data(index: int) -> void:
	var slot_data = slot_datas[index]
	
	if not slot_data:
		return
		
	if slot_data.item_data is ItemDataConsumable:
		slot_data.quantity -= 1
		if slot_data.quantity < 1:
			slot_datas[index] = null

	
	inventory_updated.emit(self)
	Global.use_slot_data(slot_data)


func pick_up_slot_data(slot_data: SlotData) -> bool:
	
	for index in slot_datas.size():
		if slot_datas[index] and slot_datas[index].can_fully_merge_with(slot_data):
			slot_datas[index].fully_merge_with(slot_data)
			inventory_updated.emit(self)
			return true
	
	for index in slot_datas.size():
		if not slot_datas[index]:
			slot_datas[index] = SlotData.new()
			var picked_up_slot_data = slot_datas[index]
			picked_up_slot_data.item_data = slot_data.item_data
			picked_up_slot_data.quantity = slot_data.quantity
			inventory_updated.emit(self)
			return true
	
	return false

func on_slot_clicked(index: int, button: int) -> void:
	inventory_interact.emit(self, index, button)

inventory script:

extends PanelContainer


const Slot = preload("res://Inventory/slot.tscn")

@onready var item_grid: GridContainer = $MarginContainer/ItemGrid

func set_inventory_data(inventory_data: InventoryData) -> void:
	inventory_data.inventory_updated.connect(populate_item_grid)
	populate_item_grid(inventory_data)

func clear_inventory_data(inventory_data: InventoryData) -> void:
	inventory_data.inventory_updated.disconnect(populate_item_grid)

func populate_item_grid(inventory_data: InventoryData) -> void:
	for child in item_grid.get_children():
		child.queue_free()
	
	for slot_data in inventory_data.slot_datas:
		var slot = Slot.instantiate()
		item_grid.add_child(slot)
		
		
		slot.slot_clicked.connect(inventory_data.on_slot_clicked)
		
		if slot_data:
			slot.set_slot_data(slot_data)		

item_data script:

extends Resource
class_name ItemData

@export var name: String = ""
@export_multiline var description: String = ""
@export var stackable: bool = false
@export var texture = Texture2D


func use(target) -> void:
	pass

player script:

extends CharacterBody2D
class_name Player

const SPEED = 100.0
const PickUp = preload("res://Inventory/Item/pick_up_ore_iron.tscn")

@export var inventory_data: InventoryData
@export var equip_inventory_data: InventoryDataEquip
@export var bank_inventory_data: InventoryDataBank
@export var hotbar_inventory_data: InventoryDataHotBar

signal toggle_inventory()

@onready var player: CharacterBody2D = $"."
@onready var inventory_interface: Control = $InventoryUI/InventoryInterface
@onready var hot_bar_inventory: PanelContainer = $InventoryUI/InventoryInterface/HotBarInventory
@onready var player_interface: PanelContainer = $InventoryUI/InventoryInterface/PlayerInventory
@onready var equip_interface: PanelContainer = $InventoryUI/InventoryInterface/EquipInventory
@onready var bank_interface: PanelContainer = $InventoryUI/InventoryInterface/BankInventory

func _ready() -> void:
	Global.player = self
	player.toggle_inventory.connect(toggle_inventory_interface)
	inventory_interface.set_player_inventory_data(player.inventory_data)
	inventory_interface.set_equip_inventory_data(player.equip_inventory_data)
	inventory_interface.set_bank_inventory_data(player.bank_inventory_data)
	inventory_interface.force_close.connect(toggle_inventory_interface)
	inventory_interface.set_hotbar_inventory_data(player.hotbar_inventory_data)
	
	for node in get_tree().get_nodes_in_group("external_inventory"):
		node.toggle_inventory.connect(toggle_inventory_interface)

func toggle_inventory_interface(external_inventory_owner = null) -> void:

	player_interface.visible = not player_interface.visible
	equip_interface.visible = not equip_interface.visible



	if external_inventory_owner and inventory_interface.visible:
		inventory_interface.set_external_inventory(external_inventory_owner)
	else:
		inventory_interface.clear_external_inventory()
		
func _unhandled_input(event: InputEvent) -> void:
	
	if Input.is_action_just_pressed("ui_cancel"):
		get_tree().quit()
	
	if Input.is_action_just_pressed("inventory"):
		if Global.item_grabbed == false:
			toggle_inventory.emit()
			
	if Global.can_bank == true and Global.bank_open == false and Input.is_action_just_pressed("interact"):
		$InventoryUI/InventoryInterface/BankInventory.show()
		$InventoryUI/InventoryInterface/PlayerInventory.show()
		$InventoryUI/InventoryInterface/EquipInventory.show()
		Global.bank_open = true
		
	elif  Global.bank_open == true and Input.is_action_just_pressed("interact"):
		$InventoryUI/InventoryInterface/BankInventory.hide()
		Global.bank_open = false
func heal(heal_value: int) -> void:
	if heal_value > Global.level_health or heal_value > Global.level_health - Global.health_current:
		Global.health_current = Global.level_health
	else:
		Global.health_current += heal_value

func _on_inventory_interface_drop_slot_data(slot_data) -> void:
	var pick_up = PickUp.instantiate()
	pick_up.slot_data = slot_data
	pick_up.position = Global.player.global_position
	add_child(pick_up)

Player scene that has the inventory nodes:
image

It feels like a lot to paste sorry. I have a static body that’s on a timer and when that timer ends I’m looking to add whatever item is being used. Something like below that’s in the world scene along with the player.

func _on_smelt_timer_timeout():
	remove_item(iron_ore)

I already detect the player in the forge using this

func _on_can_forge_area_body_entered(body):
	if body is Player:
		player = body

I have a way to add items but its absolutely terrible but it works and I would at least like to get some way to remove items working and then I can continue. I’ve been stuck on removing items for a while and any help would be appreciated thanks.

I don’t see the remove_item() function anywhere in your code. You mentioned that you tried doing it, but it doesn’t work - please share what you tried and we can debug it together.

Yea that was more, ‘can you help me make it like that’ request. What connections would I need in my forge scene in order to remove an item (in this case iron ore). Below is how I would think it could be done using an ItemData resource called ore_iron.

this would be in the forge scene

extends StaticBody2D

@export var ore_iron: ItemData(? Not sure which resource to pull from)

var player = null

func _on_can_forge_area_body_entered(body):
	if body is Player:
		player = body

func _on_forge_timer_iron_timeout():
	player.remove(ore_iron)

While the player would have the inventory nodes and something like the below.

@export var inventory_data: InventoryData

func remove(item):
	inventory_data.remove(item)

Gotcha. Can you also share the code of the SlotData class? Seems to be important in the code how it handle merges and what not.

slot_data script:

extends Resource
class_name SlotData

const MAX_STACK_SIZE: int = 99

@export var item_data: ItemData
@export_range(1, MAX_STACK_SIZE) var quantity: int = 1: set = set_quantity

func can_merge_with(other_slot_data: SlotData) -> bool:
	return item_data == other_slot_data.item_data \
		and item_data.stackable \
		and quantity < MAX_STACK_SIZE

func can_fully_merge_with(other_slot_data: SlotData) -> bool:
	return item_data == other_slot_data.item_data \
		and item_data.stackable \
		and quantity + other_slot_data.quantity <= MAX_STACK_SIZE
		
func fully_merge_with(other_slot_data: SlotData) -> void:
	quantity += other_slot_data.quantity

func create_single_slot_data() -> SlotData:
	var new_slot_data = duplicate()
	new_slot_data.quantity = 1
	quantity -= 1
	return new_slot_data

func set_quantity(value: int) -> void:
	quantity = value
	if quantity > 1 and not item_data.stackable:
		quantity = 1
		push_error("%s is not stackable, setting quantity to 1" % item_data.name)

I think something like that should work, this goes to your InventoryData class script.

func remove_item(item_data: ItemData, quantity: int) -> bool:
	var counter: int = -1
	for slot_data in slot_datas:
		counter += 1
		if slot_data == null:
			continue
		elif slot_data.item_data != item_data:
			continue
		elif slot_data.quantity < quantity:
			continue
		elif slot_data.quantity == quantity:
			slot_datas[counter] = null
			print("SlotData removed")
			return true
		else:
			slot_data.quantity -= quantity
			print("SlotData quantity updated")
			return true

	print("No item of this type found")
	return false

Not sure though if you are allowed to remove the SlotData from the slot_datas array, so in case anything breaks with this code - let me know.

Getting an error, was it a bad paste?
image

Added 2 lines at the end of my code in the previous message, try with these.

Error is gone. Would I still call it like in the example I sent? Call player.remove(item) in the forge and this in player? In short, where would I need to call this new remove_item function.

image

I was able to at least get past that last error I sent but getting one here now. Is this what you was worried about?

I changed the player function to look like this, in case it’s wrong as well
image

Ah, so it’s only looking at the first slot? I moved an iron ore to the first slot and it printed ‘SlotData removed’ but the next attempt threw that error again because the slot was now empty.

I need to go out for a couple hours now, I’ll get back to it later when I return.

No worries! Thanks for the help so far. If we can get it looking over the whole inventory that should be enough for me to start looking at it on my own again.

I updated the code again in this reply.

I didn’t realize before that a SlotData item in the slot_datas array can be null, so I added checks for it now.
The only scenario where it won’t work, which I think you might still need, is when you have the same item in multiple slots, e.g. 10 iron ores in slot 1, 20 iron ores in slot 2, and when you want to remove 30 iron ores - it’ll tell you you don’t have enough. You’d need to combine it into one stack for this to work.
Let me know if you need this functionality to check all stacks, we can add it in.

And in your calling function, you can check if the removal was successful by checking if the return value is true or false, that’s why I added the boolean return type. Instead of just calling the function like that:

inventory_data.remove_item(item, 1)

you can do that:

if inventory_data.remove_item(item, 1):
	# removal was successful, so do something
else:
	# removal was not successful, so do something else

We have some progress. I thought it wasn’t working at all but then I noticed I couldn’t move the ore around and it looks like it’s just not updating visually. I mined 2 iron ore, I smelt them both but the inventory still shows iron ore x2. I couldn’t move the item like you should be able to so I went and mined another ore and the iron ore x2 was replaced with a single iron ore. Any thoughts on why it’s not updating visually? Hope this explanation makes sense. If not I can try some screen shots.

I think I got it by adding an update inventory in what you sent (the highlighted section)

Let me tinker with it tonight and I’ll get back to you. I really appreciate you taking the time to help me out. I have spent a lot of time getting this to work to where it is and I was very much stuck on just getting an item to remove. Wish me luck!

1 Like

Good point. You might want to add this signal emit in this next elif section as well.
Does it all work correctly now?