How to export or check extended classes variables?

Godot Version

Question

I am working on an inventory system and I am trying to limit the type of items i can place into a inventory_slot. As of now I have made the inventory_slot type set to the itemType that is the enum in the item_data script. But now I am struggling to figure out how to include the other item types from other classes that extend from item_data.

What im tyring to accomplish is an inventory slot that is set to a specific item type and its extended types. For example having an inventory slot that is set to equipment type, but more specifically head type equipment.

I have the base item types set in item_data script and the extended types in their own scripts, such as equipment_data, that extends from item_data.

I am not sure how to check the Item_Data.itemType and add the extended types to the inventory_slot class.

I tried so many ways to @export var equipmentType in inventory_slot if Item_Data.ItemType is equal to Item_Data.ItemType.Equipment,i tried matching the EquipmentType to the name of the inventory slot node, but i cant figure out how to get the extended variables to the inventory_slot class. I also kept running into the issue of not being able to export a var in an if statement.

I thought about trying to set the init based off the extended item data but i ran into the same confusion of how to set the item data from the extended class.

class_name InventorySlot
extends PanelContainer


@export var type: Item_Data.ItemType

func init(t: Item_Data.ItemType, cms: Vector2) -> void:
	type = t
	custom_minimum_size = cms

func _can_drop_data(at_position: Vector2, data: Variant) -> bool:
	if data is InventoryItem:
		if type == Item_Data.ItemType.MAIN:
			if get_child_count() == 0:
				return true
			else:
				if type == data.get_parent().type:
					return true
				return get_child(0).data.type == data.data.type
		else:
			return data.data.type == type
	else:
		return false

func _drop_data(at_position: Vector2, data: Variant) -> void:
	if get_child_count() > 0:
		var item := get_child(0)
		if item == data:
			return
		item.reparent(data.get_parent())
	data.reparent(self)

func checkEquipment():
	#Check equipment
	if get_child_count() > 0:
		var item := get_child(0)
		match type:
			Item_Data.ItemType.WEAPON:
				Game.rightHandEquipped = item.data
				Game.leftHandEquipped = item.data
			Item_Data.ItemType.EQUIPMENT:
				Game.headEquipped = item.data
				Game.chestEquipped = item.data
				Game.legEquipped = item.data
				Game.feetEquipped = item.data

func _physics_process(delta: float) -> void:
	checkEquipment()

class_name Item_Data
extends Resource

enum ItemType {RESOURCE, CONSUMABLE, EQUIPMENT, WEAPON, KEY, MAIN}

@export var itemType: ItemType
@export var itemName: String
@export var stackable: bool
@export var stackCount: int
@export var itemValue: int
@export var itemTexture: Texture2D
@export_multiline var description: String


class_name Equipment_Data
extends Item_Data

enum EquipmentType {HEAD, CHEST, LEGS, FEET}

var type: Item_Data.ItemType = Item_Data.ItemType.EQUIPMENT

@export var equipmentType: EquipmentType
@export var itemWeight: int

#Damage Negation
@export var physical: int
@export var magic: int
@export var fire: int
@export var ice: int
@export var lightning: int

You will have to make it a @tool script and override _get_property_list()

1 Like

gertkeno’s suggestion is valid, and that’s definitely a way of doing this. However, I’m not so much a fan of that method for this particular situation, since it may be easier to structure your inventory with different kinds of InventorySlot, as well.

Using the @tool annotation and changing which properties are visible to the editor is, to me, an indication that the underlying system is a bit Funny. Not wrong, but a bit Funny. It means that you should double-check the reason why you’re using it in the first place, before using it. It’s an extended/advanced thing that has uses, but often, it’s not the best choice.

For instance, have you considered having multiple classes all derive from InventorySlot? You can have an array of InventorySlot like this:

@export var _slots:InventorySlot

And in the editor, you can assign any class that derives from InventorySlot, as if it were, itself, a “regular” InventorySlot:

class_name InventorySlotConsumable
extends InventorySlot

This may be better than configuring exports directly, especially since every class that inherits can then make use of overridden or duck-typed properties and methods.

Mind you, I’m not saying that using the _get_property_list is evil or yucky. All tools are there for a reason. I just don’t think that it is the best tool for this job.

If you’re unfamiliar with the concept of upcasting (in GDScript, duck-typing) one class as another, it’s worth a good read. GDScript: An introduction to dynamic languages — Godot Engine (stable) documentation in English

It basically means that if your Item_Data is a resource, then any variable that is a Resource can actually hold an Item_Data. And any variable that is an Item_Data can hold any object that is a class that extends Item_Data.

var some_resource:Resource = Item_Data.new()

some_resource.itemName = "My Item" # This is valid, because this particular resource is an Item_Data.
2 Likes

are you saying replace the var type: item_data.itemType in Inventory_Slots with the _slots:InventorySlots and create separate scripts that extend from inventorySlots for each of the different slot types? Then add the var some_resource: Item_Data.new() into that script to get the resource data.

I’ll clarify, sorry for the confusion. I used var some_resource: Item_Data.new() as an example of putting a more specific type into a more general type.

With inheritance, any script (class) that extends from Item_Data will also inherit all of the variables and functions inside Item_Data.

I’m not 100% sure what the script is meant to do, here, since the logic seems a little bit entangled, but this should (hopefully) explain the design pattern I want to communicate.

InventorySlot doesn’t have anything to do with equipment on it. It has the common functionality every InventorySlot should have.

class_name InventorySlot
extends PanelContainer

func _init(cms: Vector2) -> void:
	custom_minimum_size = cms

func _can_drop_data(at_position: Vector2, data: Variant) -> bool:
	if data is InventoryItem:
		if type == Item_Data.ItemType.MAIN:
			if get_child_count() == 0:
				return true
			else:
				if type == data.get_parent().type:
					return true
				return get_child(0).data.type == data.data.type
		else:
			return data.data.type == type
	else:
		return false

func _drop_data(at_position: Vector2, data: Variant) -> void:
	if get_child_count() > 0:
		var item := get_child(0)
		if item == data:
			return
		item.reparent(data.get_parent())
	data.reparent(self)

InventorySlotEquipment inherits from InventorySlot, so it has all the things InventorySlot does, too.

class_name InventorySlotEquipment
extends InventorySlot

var type := Item_Data.ItemType.EQUIPMENT

func checkEquipment():
	#Check equipment
	if get_child_count() > 0:
		var item := get_child(0)
		match type:
			Item_Data.ItemType.WEAPON:
				Game.rightHandEquipped = item.data
				Game.leftHandEquipped = item.data
			Item_Data.ItemType.EQUIPMENT:
				Game.headEquipped = item.data
				Game.chestEquipped = item.data
				Game.legEquipped = item.data
				Game.feetEquipped = item.data

func _physics_process(delta: float) -> void:
	checkEquipment()

If you need to have an array of inventory slots (of any/mixed types) you can do it like this:

var all_slots:Array[InventorySlot]

And if you need to export something in one kind of inventory slot, but not the others, you can do that like this:

class_name InventorySlotConsumable

@export var heal_amount:int

# Etc...

Does this help explain it?

(To clarify, “duck typing” is when you have a variable of some unknown specific type, and you call a function or set a variable on it, and regardless of what type it is, as long as it has the variable or function of that name, it will work. To my knowledge, that mechanism is also how GDScript supports calling a derived method on an object of an inherited class. It’s a nuance unimportant to the question, but if someone with more knowledge of Godot’s engine/C++ could correct me if I’m wrong.)

1 Like