The Stirling engine has been this source of unending cosmic power this entire time, providing flat 25
units of power forever.
That’s useful, but not precisely balanced.
A Stirling engine has gas in its chamber, and applying heat causes it to expand and push the piston.
We should provide a slot that can accept nothing but fuel, like charcoal. The engine burns one piece of it at a time to produce power for a limited duration.
To do so, we need to add the ability only to accept some items.
Right now, we can put any item in any inventory slot. But we want the engine only to accept fuel sources. To do so, we’ll implement item filters.
Filters are a set of keywords that refer either to a category of objects, like “Fuels”, or to a specific item, like “Ore”.
When the user clicks on a panel with an item, we check that the selected item matches the defined filter, if applicable.
We’ll create the filter list as a space-delineated string on the InventoryBar
and split it into an array. We’ll then check the array’s content with the in
keyword.
Why have our filter as a string and turn it into an array? After all, we could export an array and edit it straight in the inspector.
However, in Godot 3.2, the experience editing arrays in the inspector is a bit tedious, which is why I went with a text string that I split.
Note that you can also use the in
keyword to test if a string is part of another, so you could alternatively implement everything we’ll see below without arrays.
With that explanation out of the way, open up InventoryBar.gd
to create the filter list.
To prevent items from automatically going into the inventory when the filter doesn’t fit, we’ll update add_to_first_available_inventory()
.
## A textual representation of the possible items or categories of items the ## panels can accept, separated by spaces. export var item_filters := "" ## The list split into an array of names. The second parameter prevents us from ## having empty strings ("") in the array. onready var _filter_list := item_filters.split(" ", false) func setup(gui: Control) -> void: #... #for panel in panels: # Here, we update the existing call to `setup()` and provide the filter # array to the panel children when we set them up. # We'll update the panel's `setup()` function definition in a moment. panel.setup(gui, _filter_list) #... func add_to_first_available_inventory(item: BlueprintEntity) -> bool: #var item_name := Library.get_entity_name_from(item) # We provide the filter list and the name of the item to a special function # in `Library` to report on whether this is a valid item for the bar. if not Library.is_valid_filter(_filter_list, item_name): return false # ... ## When we consume an item like fuel, we need a way to make sure the stack count ## is up to date. ## this helper function will keep the label up to date on its panel children. func update_labels() -> void: for panel in panels: if panel.held_item: panel._update_label()
As you can see, our new code depends on a special function in Library
: is_valid_filter()
.
The function will take an array of filter names and the item to compare.
This will allow us to compare items to entire categories like fuels.
Let’s open Library.gd
to add this function.
## Returns `true` if the provided item matches the provided filter array. func is_valid_filter(filter_list: Array, item_name: String) -> bool: # If the filter list is empty, then it's automatically allowed. # Otherwise, we check first if the item is explicitly listed in the list. if filter_list.empty() or item_name in filter_list: return true # If it's not, we check any listed item categories in the filter list and if # there's one defined, we look it up in the recipes. if filter_list.has("Fuels") and Recipes.Fuels.has(item_name): return true return false
This takes us to Recipes
, where we can check to see if the item belongs to that particular category of objects.
That lets us make an inventory that only accepts fuels by listing “Fuels” without going through and listing “Lumber” and “Branches” individually.
What if we decided to add “Coal” and “NuclearCell” in the future?
We’d have to go through every machine and entity that takes fuel and update each one!
It’s much easier just to say “Fuels” and use the category throughout our codebase.
Head to Recipes.gd
to add this new dictionary.
We can add some recipes to the Crafting
dictionary for the new entities we’ve been creating the last few lessons while we’re here.
const Crafting := { #... # We append new recipes to the existing list. Pickaxe = {inputs = {"Branches": 2, "Ingot": 3}, amount = 1}, CrudePickaxe = {inputs = {"Branches": 2, "Stone": 5}, amount = 1}, Axe = {inputs = {"Branches": 2, "Ingot": 3}, amount = 1}, CrudeAxe = {inputs = {"Branches": 2, "Stone": 5}, amount = 1}, Branches = {inputs = {"Lumber": 1, "Axe": 0}, amount = 5}, Chest = {inputs = {"Lumber": 2, "Branches": 3, "Ingot": 1}, amount = 1} } ## Holds a dictionary of the types of things that are considered fuels and how ## much work time they provide in seconds when burnt. const Fuels := {Lumber = 50.0, Branches = 10.0}
The last place to update with this new filtering ability is InventoryPanel.gd
, to prevent the user from putting stuff where it shouldn’t fit.
We need to update the setup()
function to store the list and then use it in _gui_input()
.
Below, we update three lines that checked for left_click
or right_click
to also check for the item filter.
var _filter_list := [] # Below, I included some existing lines of code in comments to help you track down the ones to update. func _gui_input(event: InputEvent) -> void: #... #if left_click or right_click: #if gui.blueprint: #var blueprint_name := Library.get_entity_name_from(gui.blueprint) #if held_item: #... #if item_is_same_type and stack_has_space: #... #else: # We replace the existing if left_click and Library.is_valid_filter(_filter_list, held_item_name): #_swap_items() #else: if left_click and Library.is_valid_filter(_filter_list, blueprint_name): #_grab_item() elif right_click and Library.is_valid_filter(_filter_list, blueprint_name): #... #... ## Notice the new argument below, `filter_list`. func setup(_gui: Control, filter_list := []) -> void: #... _filter_list = filter_list
That should do it for filtering the inventory.
In the next lesson, we’ll design the Stirling engine’s user interface and put our filter to use. The engine will only accept fuel.
In InventoryBar.gd
, we added two properties, the update_labels()
function, and updated the add_to_first_available_inventory()
and setup()
functions.
export var item_filters := "" onready var _filter_list := item_filters.split(" ", false) func setup(gui: Control) -> void: if setup: return setup = true for panel in panels: panel.setup(gui, _filter_list) panel.connect("held_item_changed", self, "_on_Panel_held_item_changed") func add_to_first_available_inventory(item: BlueprintEntity) -> bool: var item_name := Library.get_entity_name_from(item) if not Library.is_valid_filter(_filter_list, item_name): return false for panel in panels: if ( panel.held_item and Library.get_entity_name_from(panel.held_item) == item_name and panel.held_item.stack_count < panel.held_item.stack_size ): var available_space: int = panel.held_item.stack_size - panel.held_item.stack_count if item.stack_count > available_space: var transfer_count := item.stack_count - available_space panel.held_item.stack_count += transfer_count item.stack_count -= transfer_count else: panel.held_item.stack_count += item.stack_count item.queue_free() return true elif not panel.held_item: panel.held_item = item return true return false func update_labels() -> void: for panel in panels: if panel.held_item: panel._update_label()
In Library.gd
, we added one function, is_valid_filter()
.
func is_valid_filter(filter_list: Array, item_name: String) -> bool: if filter_list.empty() or item_name in filter_list: return true if filter_list.has("Fuels") and Recipes.Fuels.has(item_name): return true return false
Recipes.gd
class_name Recipes extends Reference const Crafting := { StirlingEngine = {inputs = {"Ingot": 8, "Wire": 3}, amount = 1}, Wire = {inputs = {"Ingot": 2}, amount = 5}, Battery = {inputs = {"Ingot": 12, "Wire": 5}, amount = 1}, Pickaxe = {inputs = {"Branches": 2, "Ingot": 3}, amount = 1}, CrudePickaxe = {inputs = {"Branches": 2, "Stone": 5}, amount = 1}, Axe = {inputs = {"Branches": 2, "Ingot": 3}, amount = 1}, CrudeAxe = {inputs = {"Branches": 2, "Stone": 5}, amount = 1}, Branches = {inputs = {"Lumber": 1, "Axe": 0}, amount = 5}, Chest = {inputs = {"Lumber": 2, "Branches": 3, "Ingot": 1}, amount = 1} } const Fuels := {Lumber = 50.0, Branches = 10.0}
In InventoryPanel.gd
, we added the _filter_list
property, and updated _gui_input()
and setup()
.
var _filter_list := [] func _gui_input(event: InputEvent) -> void: var left_click := event.is_action_pressed("left_click") var right_click := event.is_action_pressed("right_click") if left_click or right_click: if gui.blueprint: var blueprint_name := Library.get_entity_name_from(gui.blueprint) if held_item: var held_item_name := Library.get_entity_name_from(held_item) var item_is_same_type: bool = held_item_name == blueprint_name var stack_has_space: bool = held_item.stack_count < held_item.stack_size if item_is_same_type and stack_has_space: if left_click: _stack_items() elif right_click: _stack_items(true) else: if left_click and Library.is_valid_filter(_filter_list, held_item_name): _swap_items() else: if left_click and Library.is_valid_filter(_filter_list, blueprint_name): _grab_item() elif right_click and Library.is_valid_filter(_filter_list, blueprint_name): if gui.blueprint.stack_count > 1: _grab_split_items() else: _grab_item() elif held_item: if left_click: _release_item() elif right_click: if held_item.stack_count == 1: _release_item() else: _split_items() elif event is InputEventMouseMotion and held_item: Events.emit_signal("hovered_over_entity", held_item) func setup(_gui: Control, filter_list := []) -> void: gui = _gui _filter_list = filter_list