Code Example Crafting System In Ink Code Using Pseudo Objects

This is an example of a Crafting System created in Ink code using pseudo objects. The Ink scripting language does not yet support true objects, so if you want to use object like data structures you have to be creative with functions. This code example is intended to show both pseudo objects (created with a function) and a simple crafting system. The crafting system used in this example could be done much simpler without using the object function since it only has two recipes. However, when you're developing a real game, you may want to use objects for things like inventory, player stats, and other important game mechanics. This code is intended to show one way to use object like structures in Ink. This code should be fully playable if you copy and paste it into Inky.

Code:
// Inventory items made from recipes
VAR inventory_Apple_Pie = 0
VAR inventory_Cherry_Pie = 0

// Inventory of ingredients used in recipes in game
VAR ingredient_Flour = 0
VAR ingredient_Sugar = 0
VAR ingredient_Cherries = 0
VAR ingredient_Apples = 0
VAR ingredient_Water = 0

// Variables to hold recipe requirements for each element
VAR require_ingredient_Flour = 0
VAR require_ingredient_Sugar = 0
VAR require_ingredient_Cherries = 0
VAR require_ingredient_Apples = 0
VAR require_ingredient_Water = 0

// Variable to hold recipe name
VAR recipe_name = ""

// Number of recipes in the game
CONST number_of_Recipes = 2

// Recipe IDs, a unique number for each recipe in the game
CONST id_recipe_Apple_Pie = 1
CONST id_recipe_Cherry_Pie = 2

// Inventory of Cooking Gear
VAR inventory_cookinggear_Oven = 0
VAR inventory_cookinggear_Stove = 0
VAR inventory_cookinggear_Microwave = 0
VAR inventory_cookinggear_Toaster = 0

// Variables to hold recipe requirements for cooking gear
VAR require_cookinggear_ID = 0

// Cooking Gear IDs, a unique number for each type of cooking gear
CONST id_cookinggear_Oven = 1
CONST id_cookinggear_Stove = 2
CONST id_cookinggear_Microwave = 3
CONST id_cookinggear_Toaster = 4

-> start

// The function that works as a series of recipe objects, each with a list of ingredients required to make a new food item
=== function recipe_ingredients(recipeID)
{
    - recipeID == id_recipe_Apple_Pie:
        ~ require_ingredient_Flour = 20
        ~ require_ingredient_Sugar = 10
        ~ require_ingredient_Cherries = 0
        ~ require_ingredient_Apples = 40
        ~ require_ingredient_Water = 7
        ~ require_cookinggear_ID = 1
        ~ recipe_name = "Apple Pie"
   - recipeID == id_recipe_Cherry_Pie:
        ~ require_ingredient_Flour = 20
        ~ require_ingredient_Sugar = 10
        ~ require_ingredient_Cherries = 80
        ~ require_ingredient_Apples = 0
        ~ require_ingredient_Water = 7
        ~ require_cookinggear_ID = 2
        ~ recipe_name = "Cherry Pie"
}

// Reset the recipe object when needed to be sure we don't accidentally use the wrong recipe
=== function recipe_ingredients_reset
    ~ require_ingredient_Flour = 0
    ~ require_ingredient_Sugar = 0
    ~ require_ingredient_Cherries = 0
    ~ require_ingredient_Apples = 0
    ~ require_ingredient_Water = 0

// Return true if the player has all of the ingredients and the cooking gear needed in their inventory
=== function CheckIngredients
~ return ingredient_Flour >= require_ingredient_Flour && ingredient_Sugar >= require_ingredient_Sugar && ingredient_Cherries >= require_ingredient_Cherries && ingredient_Apples >= require_ingredient_Apples && ingredient_Water >= require_ingredient_Water && isCookingGearAvailable(require_cookinggear_ID)

// Make the food item. Check to make sure that the player has all of the ingredients, remove the needed ingredients from the player's inventory, and then add the newly made food item to the player's inventory
=== function MakeRecipe(recipeID)
{   CheckIngredients :
   ~ ingredient_Flour -=  require_ingredient_Flour
   ~ ingredient_Sugar -=  require_ingredient_Sugar
   ~ ingredient_Cherries -=  require_ingredient_Cherries
   ~ ingredient_Apples -=  require_ingredient_Apples
   ~ ingredient_Water -=  require_ingredient_Water
  
   {
   - recipeID == 1:
        ~ inventory_Apple_Pie += 1
        ~ return 1
    - recipeID == 2:
        ~ inventory_Cherry_Pie += 1
        ~ return 2
    - else:
        ~ return 0
    }
}

// Get the name of the cooking gear based on the unique ID
=== function getCookingGearName(cookinggearID)
{
    - cookinggearID == 1:
        ~ return "oven"
    - cookinggearID == 2:
        ~ return "stove"
    - cookinggearID == 3:
        ~ return "microwave"
    - cookinggearID == 4:
        ~ return "toaster"
    - else:
        ~ return "unknown"
}

// Get the number of each food item made from a recipe by unique ID
=== function getInventoryQuantity(recipeID)
{
    - recipeID == 1:
        ~ return inventory_Apple_Pie
    - recipeID == 2:
        ~ return inventory_Cherry_Pie
    - else:
        ~ return "unknown"
}

// Find out whether the player has the needed cooking gear for the recipe using the unique ID
=== function isCookingGearAvailable(cookinggearID)
{
    - cookinggearID == 1 && inventory_cookinggear_Oven:
        ~ return true
    - cookinggearID == 2 && inventory_cookinggear_Stove:
        ~ return true
    - cookinggearID == 3 && inventory_cookinggear_Microwave:
        ~ return true
    - cookinggearID == 4 && inventory_cookinggear_Toaster:
        ~ return true
    - else:
        ~ return false
}

// Start the story by telling the player how much of each item they have
== start

A chef.

With {ingredient_Flour: {ingredient_Flour} | no} flour, {ingredient_Sugar: {ingredient_Sugar} | no} sugar, {ingredient_Cherries: {ingredient_Cherries} | no} cherries, {ingredient_Apples: {ingredient_Apples} | no} apples, and {ingredient_Water: {ingredient_Water} | no} water. You also have {inventory_cookinggear_Oven} oven, {inventory_cookinggear_Stove} stove, {inventory_Apple_Pie} apple pies and {inventory_Cherry_Pie} cherry pies.

+ Check all recipes.
-> check_required_ingredients(0)
+ Check Apple Pie recipe.
-> check_required_ingredients(1)
+ Check Cherry Pie recipe.
-> check_required_ingredients(2)
+ Get 10 Flour.
    ~ ingredient_Flour += 10
-> start
+ Get 10 Sugar.
    ~ ingredient_Sugar += 10
-> start
+ Get 10 Cherries.
    ~ ingredient_Cherries += 10
-> start
+ Get 10 Apples.
    ~ ingredient_Apples += 10
-> start
+ Get 10 Water.
    ~ ingredient_Water += 10
-> start
* Get 1 Oven.
    ~ inventory_cookinggear_Oven += 1
-> start
* Get 1 Stove.
    ~ inventory_cookinggear_Stove += 1
-> start

// Check to see if the player has the required ingredients for each recipe
== check_required_ingredients(recipeID)
// If the player wants to check all recipes, we send a zero so that we know to set a loop to loop through all of the recipes in the game. This could be annoying or crash the game if you have hundreds of recipes.
~ temp loop = 0
{recipeID == 0:
~ loop = 1
~ recipeID = 1
}

// Set the required ingredients to this recipe
{recipe_ingredients(recipeID)}

Checking {recipe_name} recipe.

// Tell the player if they have enough of each ingredient for this recipe
You {ingredient_Flour >= require_ingredient_Flour: have | don't have} enough flour to make a {recipe_name}.
You {ingredient_Sugar >= require_ingredient_Sugar: have | don't have} enough sugar to make a {recipe_name}.
You {ingredient_Cherries >= require_ingredient_Cherries: have | don't have} enough cherries to make a {recipe_name}.
You {ingredient_Apples >= require_ingredient_Apples: have | don't have} enough apples to make a {recipe_name}.
You {ingredient_Water >= require_ingredient_Water: have | don't have} enough water to make a {recipe_name}.
You need an {getCookingGearName(require_cookinggear_ID)} to make a {recipe_name}. {isCookingGearAvailable(require_cookinggear_ID) : You have one. | You don't have one.}

// If the player has enough of each ingredient for the current recipe, they are sent to a tunnel where they can make the recipe or go back to the start. If they don't have the needed ingredients, we send them back to the start to get more.
{ CheckIngredients():
You can make an {recipe_name}!
-> make_recipe(recipeID,recipe_name) ->
- else:
Sorry, not enough ingredients for that {recipe_name} yet.
}

// Loop logic for if the player wanted to check all recipes
{recipe_ingredients_reset()}
{loop == 1 && recipeID <= number_of_Recipes:
~ recipeID += recipeID
-> check_required_ingredients(recipeID)
}

-> start

// The stitch with a tunnel where a player with all of the needed ingredients can make the recipe or go back to the start
= make_recipe(recipeID,recipename)

Do you want to make a {recipename}?

+ Make it!
-> make_it(recipeID,recipename)
// If the player doesn't make the recipe, they are returned to the narrative using a tunnel return statement
+ Don't make a {recipename}
->->

// The player with all needed ingredients who decides to make the recipe goes here to have the food item made and the inventory items removed from their inventory. We then return them to the narrative where they left off using a tunnel return statement
= make_it(recipeID,recipename)
{MakeRecipe(recipeID): You made the {recipename} (you now have {getInventoryQuantity(recipeID)}). | Something went wrong!}
-> continue
= continue
+ [Continue]
->->

Notes:
  • Using functions as objects is fairly advanced Ink coding, so please check the Ink documentation if any of the above code doesn't make sense.
  • There are a few things in the code that I intended to use but ended up not using (like the toaster), so feel free to ignore any of the unused elements.
  • I did not clean up the text in this code example as much as I would in a real story. For example, singular/plural quantities and other places where it would benefit from more logic to improve the text grammar for readers.
  • There are some obvious places where you could add more reusable logic for crafting. For example, functions to get the name of each inventory item, better getter functions for recipe data, etc. If I was creating a system for a real game, I would put more time into optimizing the functions so that they would be reusable and scalable during the writing of the narrative.
  • Using loops in Ink can be a little dangerous because your story/reader could get stuck in one of them. Always use loops with caution and do a lot of testing.
If you have any suggestions for improving the code or questions about the code, let me know in a reply.

Sources: The concept of using Ink Functions as Pseudo Objects in the above code is inspired by the work of "jkant" on this thread on the official Ink GitHub page. That user has done a lot of work on ways to create objects in Ink and has released their work under the MIT license, so that it's available for other coders to share.
 
Back
Top