Removing items from inventory resource

Godot Version 4.2.1.

Question

I have an inventory (as a resource) that contains slots which is an array of inventory slots (a resource), and every slot can have an inventory item (also a resource). I watched DevWorm’s YT tutorial on this, if it could help you with the code. Now, the items do add and they are shown in the inventory and can stack. However, I want to now be able to remove a specific item once a signal from a different script has been sent (an action is performed → it sends a signal with information on what exact material to remove and how many (amount). This is the thing – i want exact amount and material to remove on a signal emission. How could I achieve this?
Thanks in advance

Assuming an inventory slot knows how many of which material it holds:

  1. loop over the inventory slots.
  2. If an inventory slot contains the material, subtract as much as possible and save the remaining amount to be subtracted
  3. Continue the loop and if another slot has the material to be subtracted, subtract that also

You’d need to pass the material and the amount into the signal. You can see a function like that here: CataX/Scripts/item_manager.gd at 609df49686625167d8e63233434874efdd028e33 · snipercup/CataX · GitHub
But it uses item_id instead of material.

Thx for the help, however, my code looks a bit different already and I don’t have an idea how can I adapt this function to my inventory scripts:
This is the code:
Inventory.gd:

extends Resource

class_name Inventory

signal update

@export var slots: Array[InvSlot] 


 
func insert(item: InvItem):
	var itemslots = slots.filter(func(slot): return slot.item == item)
	if !itemslots.is_empty():
		if itemslots[0].can_add_to_stack(1):
			itemslots[0].amount += 1
	else:
		var emptySlots = slots.filter(func(slot): return slot.can_accept_item(item))
		if emptySlots != null:
			emptySlots[0].item = item
			emptySlots[0].amount = 1
	update.emit()

inventory_item.gd:

extends Resource

class_name InvItem

@export var name: String = ""
@export var texture: Texture2D
@export var item_id: int

inv_slot.gd:

extends Resource

class_name InvSlot

@export var item: InvItem
@export var amount: int
@export var maxStackSize: int = 20
@export var isFull: bool = false




func can_add_to_stack(amount_to_add: int) -> bool:
	return amount + amount_to_add <= maxStackSize

func add_item(item_to_add: InvItem) -> bool:
	if can_add_to_stack(1):
		item = item_to_add
		amount += 1
		if amount == maxStackSize:
			isFull = true
			return true
	return false

func remove_item():
	if amount > 0:
		amount -= 1
	if amount < maxStackSize:
		isFull = false
		return item
	return null


func can_accept_item(item_to_add: InvItem) -> bool:
	return item == null or (item == item_to_add and !isFull)

inv_ui.gd:

extends Control

@onready var inv: Inventory = preload("res://players_inventory.tres")
@onready var slots: Array = $NinePatchRect/GridContainer.get_children()

var is_open = false  


func _ready():
	inv.update.connect(update_slots)
	update_slots()
	close()

func update_slots():
	for i in range(min(inv.slots.size(), slots.size())):
		slots[i].update(inv.slots[i])


func _process(_delta):
	if Input.is_action_just_pressed("i"):
		if is_open:
			close()
		else:
			open()
	
func close():
	visible = false
	is_open = false

func open():
	self.visible = true
	is_open = true

inv_ui_slot.gd:

extends Panel

@onready var item_visual: Sprite2D = $CenterContainer/Panel/item_display
@onready var amount_text: Label = $CenterContainer/Panel/Label



func update (slot: InvSlot):
	if !slot.item:
		item_visual.visible = false
		amount_text.visible = false
	else:
		item_visual.visible = true
		item_visual.texture = slot.item.texture
		if slot.amount > 1:
			amount_text.visible = true
			amount_text.text = str(slot.amount)

I have 7 materials (item types); material1.tres, material2. tres and so on

So you’d adapt this function to remove an item:

 # Remove the specified amount of the specified item from the inventory
func remove(item: InvItem, amount: int) -> bool:
	var was_removed: bool = false
	var amount_to_remove: int = amount
	# Get all itemslots that have this item
	var itemslots = slots.filter(func(slot): return slot.item == item)
	if !itemslots.is_empty():
		for slot in itemslots:
			if amount_to_remove <= 0:
				was_removed = true
				break  # Stop if we have removed enough of the item.
			if slot.amount <= amount_to_remove:
				# This slot doesn't have enough of this item to satisfy the removal. Remove everything we can
				amount_to_remove -= slot.amount
				slot.clear()
			else:
				# If the current item stack has more than we need, reduce its stack size.
				slot.amount -= amount_to_remove
				amount_to_remove = 0  # Set to 0 as we have removed enough.
	if was_removed:
		update.emit()
	return was_removed

The problem with this function is that it will reduce the amount from the inventory and still returns false if the amount wasn’t satisfied. This could lead to items getting lost. You could return the remaing amount_to_remove instead and handle it a different way. You might need a new function that calculates if the reduction can be satisfied beforehand.

Another small thing in this function: Take the if statement that checks if amount_to_remove <= 0, and move it down after the else branch of the next if statement. Otherwise, if we’re trying to move exactly the amount that was present, it will also return false.

Calculating the amount present in the inventory is easy enough once you have itemslots:

var amount_present = 0
for slot in item_slots:
	amount_present += slot.amount
if amount_present < amount:
	return false

In full:

# Remove the specified amount of the specified item from the inventory
func remove(item: InvItem, amount: int) -> bool:
	var was_removed: bool = false
	var amount_to_remove: int = amount
	# Get all itemslots that have this item
	var itemslots = slots.filter(func(slot): return slot.item == item)
	if !itemslots.is_empty():
		# check if it's possible to remove the full amount
		var amount_present = 0
		for slot in item_slots:
			amount_present += slot.amount
		if amount_present < amount:
			return false
		# actually remove
		for slot in itemslots:
			if slot.amount <= amount_to_remove:
				# This slot doesn't have enough of this item to satisfy the removal. Remove everything we can
				amount_to_remove -= slot.amount
				slot.clear()
			else:
				# If the current item stack has more than we need, reduce its stack size.
				slot.amount -= amount_to_remove
				amount_to_remove = 0  # Set to 0 as we have removed enough.
			if amount_to_remove <= 0:
				was_removed = true
				break  # Stop if we have removed enough of the item.
	if was_removed:
		update.emit()
	return was_removed

A bit of a gnarly function at this point, and I haven’t tested it (as I don’t have an inventory system lying around that it would fit into…), but I think it’s probably pretty close. It’s also possible to get rid of was_removed, and just fire the signal + return true as soon as enough items are removed (and then return false or throw an error after the loop - if we get that far, something has gone wrong, since we checked in advance that it’s possible to remove the items).

But where do I specify the item: invitem and amoun:int first? Like lets say for a test: in a separate script where i press a button and then it’s:
inventory.remove(). So what’s in the parentheses?