I've started making progress on one of the core mechanics of The Alchemist, that of processing machines. They take raw ingredients and either transform them in some way (grinding seeds, perhaps), or combining ingredients to create a new product. There has been a lot of UI work, including changes to my custom flow container. I have introduced the concept of a Recipe, which starts as a JSON file, something like this:
{
"key": "basic-healing-potion",
"name:": "Basic healing potion",
"ingredients": [
{
"key": "ground-turnip",
"type": "GROUND"
},
{
"key": "seed-tomato",
"type": "SEED"
}
],
"input-count": 2,
"output-count": 1,
"processing-time:": 5,
"requires-machine": "Blender",
"results": [
{
"result-key": "basic-healing-potion",
"result-type": "POTION"
}
]
}
But the closer I get to making this actually work, I'm finding more issues with my underlying data model. In particular, I want the machine to understand the recipe sufficiently so the player can only drag the correct items into the various slots. In the recipe above, the player should only be able to drag a "seed-tomato" or a "ground-turnip" to the machine. This example probably does work, but some recipes don't need to specify the specific ingredient, only the type of ingredient it can process. The Grinder machine should be able to grind any ingredient with a type of RAW. And currently, this is not working.
I've got a fairly typical type hierarchy defined for anything which can be added to the player inventory, starting with an abstract base class of Item
:
sealed class Item {
abstract val key: String
abstract val atlasKey: String
}
There are a number of interfaces which the implementors of the Item
class can adopt:
interface Grows {
fun grow(dT: TimeSpan): TileUpdateEvent?
}
interface Equipable
interface Stackable {
var maxStack: Int
var count: Int
}
// a concrete implementation, a Tool such as an axe:
class Tool(override val key: String, override val atlasKey: String, val canHarvest: Boolean = true) : Item(),
Equipable {
override fun toString(): String = "Tool: $key - $atlasKey"
}
// a `Seed` is an Item which transform into a `Crop` when it grows in a field:
data class Seed(
override val key: String, override val atlasKey: String, val crop: Crop, override var maxStack: Int = 50,
override var count: Int = 0
) : Item(), Stackable {
override fun toString(): String = "Seed: $key - $atlasKey ($count of $maxStack)"
}
I think this design is wrong. It's very object-orientated, very hierarchical, and definitely causing me problems. In my game, a Seed
grows in a field as Crop
, which is then harvested. Tomato seed > Tomato crop -> Tomatoes. Then a Machine may transform these - a Grinder could produce Ground Tomatoes. Do I need to start introducing more classes to represent the different possible states of things?
Instead, I'm trying to go for a more composition over inheritance approach, but it doesn't fit naturally into my way of thinking. I am certain that the current approach is wrong, but I haven't fully worked out the alternative. One thing which I know needs to change is that a Crop
inherits from Item
, and it shouldn't - a Crop is a plant, growing in a field, updating when its grow()
function is called. It can never be in the players inventory, and when a Crop is harvested it should return something new.
More thinking is needed.