Adapting Item system from JSON system to TRES system is leading to non-sense values, help appreciated

Godot Version

4.2

Question

Hi! I implemented an inventory based on this tutorial. It was working great for awhile; however, I felt that storing my items in a JSON file wasn’t sustainable whenever I started to add more items. Using resources and classes I created an Item class (and classes that extend it for categories of items). Rather than completely rewrite the inventory from the ground up, I decided to try and adapt the inventory system. originally I was going to change the logic in the inventory itself; however, since the item nodes in my scene were already “translating” data from a JSON to something workable by the inventory I decided to translate data from the TRES. This worked perfectly for getting the texture of the item; however, it is somehow breaking the coordinate system that the item grid relies on.

While troubleshooting I tried adapting the inventory itself, but it led to about as many bugs, they didn’t lead to crashes but the logic errors they caused were more confusing. I also decided to try and adapt the Item class to store an array of arrays ( that always has a size of two) instead of an array of Vector2’s and it did work, but it made creating new items with the godot GUI obtuse. This happens to be a collaborative project, so that solution won’t do for me.

the code for the inventory is here:

extends Control
@export var inventory_size = 150
@onready var slot_scene = preload("res://UI/Inventory/inventory_slot.tscn")
@onready var grid_container = $ColorRect/MarginContainer/VBoxContainer/ScrollContainer/GridContainer
@onready var item_scene = preload("res://UI/Inventory/item.tscn") 
@onready var scroll_container = $ColorRect/MarginContainer/VBoxContainer/ScrollContainer
@onready var col_count = grid_container.columns
var grid_array := []
var item_held = null
var current_slot = null
var can_place := false
var icon_anchor : Vector2
var inventory_open := false
signal inventory_opened()
signal inventory_closed()
signal report_inventory_hotbar(hotbar : Array)
# Called when the node enters the scene tree for the first time.
func _ready():
	MenuHandler.change_state.connect(_on_menu_handler_change_state)
	MenuHandler.main()
	for i in range(inventory_size):
		create_slot()

func _process(delta):
	if item_held: 
		if Input.is_action_just_pressed("rotate item"):
			rotate_item()
		if Input.is_action_just_pressed("select"):
			if scroll_container.get_global_rect().has_point(get_global_mouse_position()):
				place_item()
	else: 
		if Input.is_action_just_pressed(("select")):
			if scroll_container.get_global_rect().has_point(get_global_mouse_position()):
				pick_item()

func create_slot():
	var new_slot = slot_scene.instantiate()
	new_slot.slot_ID = grid_array.size()
	grid_container.add_child(new_slot)
	grid_array.push_back(new_slot)
	new_slot.slot_entered.connect(_on_slot_mouse_entered)
	new_slot.slot_exited.connect(_on_slot_mouse_exited)
	pass


func _on_slot_mouse_entered(a_Slot):
	#arbitrary values because they are always supposed to be changed before being used for calculations
	icon_anchor = Vector2(10000,100000)
	current_slot = a_Slot
	if item_held:
		check_slot_availability(current_slot)
		set_grids.call_deferred(current_slot)

func _on_slot_mouse_exited(a_Slot):
	clear_grid()
	
	if not grid_container.get_global_rect().has_point(get_global_mouse_position()):
		current_slot = null

func _on_button_spawn_pressed():
	var new_item = item_scene.instantiate()
	add_child(new_item)
	new_item.load_item(load("res://Systems and Logic/item/repo/torch.tres"))    #randomize this for different items to spawn
	new_item.selected = true
	item_held = new_item
	
	
func check_slot_availability(a_Slot):
	for grid in item_held.item_grids:
		var grid_to_check = a_Slot.slot_ID + grid[0] + grid[1] * col_count
		var line_switch_check = a_Slot.slot_ID % col_count + grid[0]
		if line_switch_check < 0 or line_switch_check >= col_count:
			can_place = false
			return
		if grid_to_check < 0 or grid_to_check >= grid_array.size():
			can_place = false
			return
		if grid_array[grid_to_check].state == grid_array[grid_to_check].States.TAKEN:
			can_place = false
			return
		
	can_place = true

func set_grids(a_Slot):
	for grid in item_held.item_grids:
		var grid_to_check = a_Slot.slot_ID + grid[0] + grid[1] * col_count
		if grid_to_check < 0 or grid_to_check >= grid_array.size():
			continue
		#make sure the check don't wrap around boarders
		var line_switch_check = a_Slot.slot_ID % col_count + grid[0]
		if line_switch_check <0 or line_switch_check >= col_count:
			continue
		
		if can_place:
			grid_array[grid_to_check].set_color(grid_array[grid_to_check].States.FREE)
			#save anchor for snapping
			if grid[1] < icon_anchor.x: icon_anchor.x = grid[1]
			if grid[0] < icon_anchor.y: icon_anchor.y = grid[0]
				
		else:
			grid_array[grid_to_check].set_color(grid_array[grid_to_check].States.TAKEN)

func clear_grid():
	for grid in grid_array:
		grid.set_color(grid.States.DEFAULT)

func rotate_item():
	item_held.rotate_item()
	clear_grid()
	if current_slot:
		_on_slot_mouse_entered(current_slot)

func place_item():
	if not can_place or not current_slot: 
		return #put indication of placement failed, sound or visual here
		
	#for changing scene tree
	item_held.get_parent().remove_child(item_held)
	grid_container.add_child(item_held)
	item_held.global_position = get_global_mouse_position()
	####
	var calculated_grid_id = current_slot.slot_ID + icon_anchor.x * col_count + icon_anchor.y
	item_held._snap_to(grid_array[calculated_grid_id].global_position)
	item_held.grid_anchor = current_slot
	for grid in item_held.item_grids:
		var grid_to_check = current_slot.slot_ID + grid[0] + grid[1] * col_count
		grid_array[grid_to_check].state = grid_array[grid_to_check].States.TAKEN 
		grid_array[grid_to_check].item_stored = item_held
	
	#put item into a data storage here
	
	item_held = null
	clear_grid()
	
func pick_item():
	if not current_slot or not current_slot.item_stored: 
		return
	item_held = current_slot.item_stored
	item_held.selected = true
	#move node in the scene tree
	item_held.get_parent().remove_child(item_held)
	add_child(item_held)
	item_held.global_position = get_global_mouse_position()
	####
	
	for grid in item_held.item_grids:
		var grid_to_check = item_held.grid_anchor.slot_ID + grid[0] + grid[1] * col_count # use grid anchor instead of current slot to prevent bug
		grid_array[grid_to_check].state = grid_array[grid_to_check].States.FREE 
		grid_array[grid_to_check].item_stored = null
	
	check_slot_availability(current_slot)
	set_grids.call_deferred(current_slot)
	
	


func _on_add_slot_pressed():
	create_slot()


func _on_menu_handler_change_state(MenuState: Variant) -> void:
	if MenuState == MenuHandler.MenuStates.INVENTORY:
		set_visible(true)
		set_mouse_filter(Control.MOUSE_FILTER_STOP)
		Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
	else: 
		set_visible(false)
		set_mouse_filter(Control.MOUSE_FILTER_IGNORE)
		

the referenced slot code can be found here:

extends TextureRect

signal slot_entered(slot)
signal slot_exited(slot)
@onready var filter = $StatusFilter

var slot_ID
var is_hovering := false
enum States {DEFAULT, TAKEN, FREE}
var state = States.DEFAULT
var item_stored = null

func _process(delta: float) -> void:
	if get_global_rect().has_point(get_global_mouse_position()):
		if not is_hovering:
			is_hovering = true 
			emit_signal("slot_entered", self )
	else: 
		if is_hovering:
			is_hovering = false
			emit_signal("slot_exited", self)

func set_color(a_state = States.DEFAULT) -> void:
	match a_state:
		States.DEFAULT:
			filter.color = Color(Color.WHITE, 0.0)
		States.TAKEN:
			filter.color = Color(Color.RED, 0.1)
		States.FREE:
			filter.color = Color(Color.GREEN, 0.1)
			

and here’s the original item code:

extends Node2D

@onready var IconRect_path = $Icon

var item_ID : int
var item_grids := []
var selected = false
var grid_anchor = null

# Called when the node enters the scene tree for the first time.
func _ready():
	pass


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	if selected:
		global_position = lerp(global_position, get_global_mouse_position(), 25 * delta)

func load_item(a_ItemID : int) -> void:
	var Icon_path = "res://Assets/" + DataHandler.item_data[str(a_ItemID)]["Name"] + ".png"
	IconRect_path.texture = load(Icon_path)
	for grid in DataHandler.item_grid_data[str(a_ItemID)]:
		var converter_array := []
		for i in grid :
			converter_array.push_back(int(i))
		item_grids.push_back(converter_array)
	#print(item_grids)

#rotate 90 degress CW
func rotate_item():
	for grid in item_grids:
		var temp_y = grid[0]
		grid[0] = -grid[1]
		grid[1] = temp_y
	rotation_degrees += 90
	if rotation_degrees>=360:
		rotation_degrees = 0

func _snap_to(destination):
	var tween = get_tree().create_tween()
	#separate cases to avoid snapping errors
	if int(rotation_degrees) % 180 == 0:
		destination += IconRect_path.size/2
	else:
		var temp_xy_switch = Vector2(IconRect_path.size.y,IconRect_path.size.x)
		destination += temp_xy_switch/2
	tween.tween_property(self, "global_position", destination, 0.15).set_trans(Tween.TRANS_SINE)
	selected = false

and here is the function that I tried to change in adapting the code:

func load_item(a_Item : ITEM) -> void:
	IconRect_path.texture = a_Item.ITEM_TEXTURE_GRID
	$Area2D/CollisionShape2D.scale = Vector2(IconRect_path.texture.get_width(), IconRect_path.texture.get_height())
	for grid in a_Item.ITEM_GRID:
		var converter_array := []
		converter_array.append(int(grid.y))
		converter_array.append(int(grid.x))
		item_grids.push_back(converter_array)
	#print(item_grids)

I also made an ever so slight change to the inventory code, just to load in a tres instead of loading from the json at the _on_button_spawn_pressed() function here:

func _on_button_spawn_pressed():
	var new_item = item_scene.instantiate()
	add_child(new_item)
	new_item.load_item(load("res://Systems and Logic/item/repo/torch.tres"))    #randomize this for different items to spawn
	new_item.selected = true
	item_held = new_item

The error is: “Invalid access of index ‘200047’ on a base object of type: ‘Array’” at line 123 of the inventory.

Due to my own troubleshooting, I have come to realize that this is due to the icon_anchor value not being reset in the set_grids function, as it should be. Considering that the function itself is unmodified the only conclusion that I can come to is that grid[1] (essentially the x value) and grid[0] (essentially the y value) are being set improperly meaning the if statements at line 97 and 98 aren’t triggered. I genuinely have no idea how to fix this, I don’t see how from a perspective of raw data I am passing in something different.

a video of how the system is supposed to function can be found in the video above, but here’s a video of the issue:

as you can see the slot doesn’t have its usual green color rect to indicate that something could be placed there.

I know this is a really long issue, but any help would be much appreciated!

My apologies, in reality, my code works perfectly! I messed up the .tres for specifically the torch item I was testing this with. upon randomizing which test item was chosen, this was fixed.

1 Like