Can you set up an alchemy system using the 'Show Menu' function?

Hi!

I'm hoping to create a game with an 'alchemy' system that allows the player to create potions. This system would use the 'Show Menu' function to allow the player to pick the desired potion they want to create by using two ingredients.

I envision it to work as follows. Once the player has gathered an ingredient, that ingredient is visible in their inventory with an 'alchemy' verb attached. Let's say the example name of an ingredient is Ingredient1.
When clicking 'alchemy' or typing in 'alchemy [ingredient]', the game checks what combinations of X amount of Ingredient1 with X amount of Ingredient2 (or 3, or 4, ...) can create a specific potion. The game also checks how much of Ingredient1, and Ingredient2, 3, 4... the player currently has (does the player even have enough of that ingredient to make any potions?). (As a side note, I'm also open to suggestions as to how this process can be coded.)
When the game has determined what types of potions, if any, the player can currently create using X amount of Ingredient1 (and X amount of other ingredients), it shows the player a menu. Example:


Using Ingredient1, you can currently make the following potions:

  • 1 x Ingredient1 + 3 x Ingredient2:
    CLICKABLE BUTTON: Potion A

  • 3 x Ingredient1 + 2 x Ingredient3:
    CLICKABLE BUTTON: Potion B


Only potions that can actually be made given the current amounts of ingredients the player has, are displayed as options. Then when the player clicks on 'Potion A', for example, the game shows another menu.


You want to create Potion A. You can create at maximum [some calculated amount] of this item. Each item will take 1 x Ingredient1 + 3 x Ingredient2 to create.
How many would you like to create?
INPUT FIELD: ...


However, it is very unclear to me how you are supposed to get the game to display the various button texts, especially if these texts have to be dynamically 'generated' from checks and calculations that the game performs. I'm also not entirely sure how I could get the game to take the player input from the first menu and let it serve as a starting point for setting up the second menu appearance. After which, the game has to remove the appropriate amount of ingredient items and add the appropriate amount of potion items to the player's inventory.

Could someone explain, perhaps with an example of what the code would look like? Or do you have suggestions as to how I could arrange the system better? Please let me know.

Hope you all have a wonderful day!


I would say that's not going to be hard, but will be quite a lot of effort.

There are a few main ways to do something like that, with multiple menus. The first would be to put attributes on some object (usually the game, or maybe the alchemy command) to keep track of what objects the player was working with. The second would be to make the menu options contain all the details. By using a stringdictionary for the menu options, you can have the result containing as much detail as you need.

The third option is the one I'd use, but that doesn't use ShowMenu.

Click for more if you're interested in how I'd do this

You can have a command that allows you to enter different versions of a command, with more details in.

For example, you could have an alchemy command that lets the player enter alchemy object1, or alchemy object1 with object2, or alchemy 4 object1 with 2 object2.

In the first two cases, the command would respond by giving the player a list of possible options, each of which includes a command link. So they type the first option, or use the verb menu on an object, and it tells them what they can combine it with. Each option includes a command link that would effectively type "alchemy object1 with object2", or "alchemy object1 with object4".

Then when they click one of those (or type the command like that), it checks the numbers and prints out another list of options with the numbers in.

Clicking (or typing) the command with the number of items in signals that the player knows what they're doing, and actually does the alchemy.

This way, you don't need to track any state, because the command contains all the information you need.

In any case, how you actually implement this would depend on how you're handling multiple items, and how you're choosing to store the recipes. It's likely to end up being quite a lot of code, but none of it particularly complex. It's just a case of breaking the process down into a lot of smaller steps.

I'd show you a crafting system I quickly threw together last time someone asked about something similar; but that worked a little differently (commands were "craft" to get a list of known recipes, "craft (objectname)" to choose what to make, and "craft (number) (object)" to confirm)


This is a very good idea!


Thank you very much for the response! It's given me a few things to consider.

I do want to keep the system as simple and efficient for the user as possible (i.e. for the type of game I have in mind, they don't need to go through the lists of recipes and then manually calculate and type out how to combine the ingredients by typing in 'alchemy 4 X with 5 Y'). But you've given me good ideas to fiddle around with! If I find that it might help, I may ask about that crafting system later.

Thanks again!


OK… I should be able to come up with this pretty quickly.
(Note: I'm assuming that alchemy can include any number of ingredients, not just 2. It's one of those weird situations where the code is actually simpler for "any number" than it is for a specific number)

Here's an example. I'm assuming that

  • You have a room the player can't reach called RECIPES, which contains all the objects they can make. (These objects will be cloned into the player's inventory when created)
  • The craftable objects have a dictionary attribute ingredients, whose keys are object names and values are the number required
  • As you mentioned that the player can use more than one of an ingredient, I assumed that these are all clones of an original ingredient; created using CloneObject and related functions. (note that this won't work with the bare clone function)

The alchemy command has the pattern alchemy #object#, and the following script.

if (not ListContains (ScopeReachableInventory(), object)) {
  msg ("You haven't got " + object.article + ".")
}
else if (not HasObject (object, "prototype")) {
  msg (Capfirst (GetDisplayAlias (object)) + " is not an alchemy ingredient.")
}
else {
  // first, check how many of the ingredient we have
  ingredient = object.prototype
  number = ListCount (FilterByAttribute (ScopeReachable(), "prototype", ingredient))
  // then start making a list of recipes we can make using it
  recipes = NewStringDictionary()
  found_recipes = false
  // I'm also making a list of missing ingredients, for a more informative message if the player can't make anything.
  missing = NewObjectList()
  foreach (recipe, GetDirectChildren(RECIPES)) {
    if (TypeOf (recipe, "ingredients") = "dictionary") {
      if (DictionaryContains (recipe.ingredients, ingredient.name)) {
        // we've found a recipe that uses this object; do we have enough ingredients?
        can_make = true
        option_text = ""
        foreach (other_ingredient, recipe.ingredients) {
          if (DictionaryItem (recipe.ingredients, other_ingredient) > ListCount (FilterByAttribute (ScopeReachableInventory(), "prototype", GetObject (other_ingredient)))) {
            can_make = false
            list add (missing, GetObject (other_ingredient))
          }
          else {
            if (not option_text = "") {
              option_text = option_text + ", "
            }
            option_text = option_text + ToString (DictionaryItem (recipe.ingredients, other_ingredient)) + "x " + GetDisplayName (GetObject (other_ingredient))
          }
        }
        if (can_make) {
          dictionary add (recipes, recipe.name, option_text + ": " + GetDisplayName (recipe))
          found_recipes = true
        }
      }
    }
  }
  if (found_recipes) {
    // We now know which recipes can be made with this ingredient; so show the menu
    ShowMenu ("Using {object:"+ingredient.name + "}, you can currently make the following potions:", recipes, true) {
      // check what the player is trying to make, and store that for after we got the number
      recipe = GetObject (result)
      game.current_alchemy_recipe = recipe
      // Then make the message to tell them how many they can make:
      ingredients = NewStringList()
      max_number = ListCount (ScopeReachableInventory())
      foreach (ingredient_name, recipe.ingredients) {
        number_needed = DictionaryItem (recipe.ingredients, ingredient_name)
        number_got = ListCount (FilterByAttribute (ScopeReachableInventory(), "prototype", GetObject (ingredient_name)))
        if (max_number > number_got / number_needed) {
          max_number = number_got / number_needed
        }
        list add (ingredients, ToString(number_needed) + "x " + GetDisplayAlias (GetObject (ingredient_name)))
      }
      msg ("You want to create " + GetDisplayAlias (recipe) + ". You can create at maximum " + max_number + " of this item. Each item will take " Join (ingredients, "+") + " to create.")
      msg ("How many would you like to create?")
      get input {
        recipe = game.current_alchemy_recipe
        if (not IsInt (result)) {
          msg (CapFirst (result) + " is not a number.")
        }
        else if (IsRegexMatch ("^\s*(-|0*\s*$)", result)) {
          msg ("You didn't enter a number greater than zero.")
        }
        else {
          number = ToInt (result)
          // now, we can check again if the player has enough ingredients for the number they entered
          ingredients_used = NewStringList()
          failed = false
          foreach (ingredient_name, recipe.ingredients) {
            number_needed = DictionaryItem (recipe.ingredients, ingredient_name) * number
            current_ingredients = FilterByAttribute (ScopeReachableInventory(), "prototype", GetObject (ingredient_name))
            if (ListCount (current_ingredients) >= number_needed) {
              foreach (i, 0, number_needed - 1) {
                list add (ingredients_used, GetString (ListItem (current_ingredients, i), "name"))
              }
            }
            else {
              failed = true
              msg ("You don't have enough " + GetDisplayAlias (GetObject (ingredient_name)) + ".")
            }
          }
          if (not failed) {
            msg ("You create " + number + "× " + GetDisplayAlias (recipe))
            for (i, 0, number - 1) {
              CloneObjectAndMove (recipe, game.pov)
            }
            foreach (ingredient, ingredients_used) {
              destroy (ingredient)
            }
          }
        }
      }
    }
    // The following line is an ugly hack to change which part of the menu options is the link. Don't be surprised if you can't follow the code:
    JS.eval("$('#" + game.menuoutputsection + " a.cmdlink').each(function () {var l=$(this).css({display:'block',marginLeft:'3em'});l.text(l.text().replace(/^.+: /,function (t) {l.parent().prepend(t);return('');});});")
    if (ListCount (missing) > 0) {
      msg ("To unlock other recipes, try gathering more " + FormatList (ListCompact (missing), ", ", ", or", "") + ".")
    }
  }
  else if (ListCount (missing) > 0) {
    msg ("You can't make anything unless you gather more " + FormatList (ListCompact (missing), ", ", ", or", "") + ".")
  }
  else {
    msg (CapFirst (GetDisplayAlias (object)) + " is not an alchemy ingredient.")
  }
}

If you're doing this in the web editor, then you might need to add a list of ingredients in your start script. Like this:

Potion A.ingredients = NewDictionary()
dictionary add (Potion A.ingredients, "ingredient1", 2)
dictionary add (Potion A.ingredients, "ingredient2", 4)

Potion B.ingredients = NewDictionary()
dictionary add (Potion B.ingredients, "ingredient1", 3)
dictionary add (Potion B.ingredients, "ingredient4", 1)

Potion C.ingredients = NewDictionary()
dictionary add (Potion C.ingredients, "ingredient2", 1)
dictionary add (Potion C.ingredients, "ingredient3", 1)
dictionary add (Potion C.ingredients, "ingredient4", 1)

and so on.
On the desktop editor, I think you can do this on the potion's "Attributes" tab; but I don't use Windows so don't have access to the desktop version of Quest.
Note that this code uses object names rather than their aliases (just in case they're different)

Hope the code is all correct and typo-free. I just typed it straight out in the forum, so haven't tested it.


(that's code for the ShowMenu option in the original question)

Menus are good to make things easier for the player. But I often find that it's frustrating if I've played a game a lot, and I have to go through multiple menus to choose something when I already know what I want. That's why I like command links: It can look like a menu with options to click, but if I want I can remember the command that each option enters.

Actually, thinking about it now, I'd probably go with 3 separate commands: alchemy ingredient1, alchemy make Potion A, and alchemy make 4x Potion A. That makes it simpler to handle stuff like scope; as well as making the commands more natural.

If you're using a stacking system to handle multiple objects in your inventory, the code would probably need modifying to work with that.


Wow! That's a lot of information for me to take in ^ ^;

I haven't really played around with dictionary attributes, keys and values and how to call them. And my knowledge of the elements and logic of the code is limited to the point where I'll have to reread everything beside the 'ugly hack' as well :P But that looks pretty comprehensive in terms of functionality. Although a stacking system will probably be needed... It's meant to be a game where you can collect a ton of items, so I'm guessing that will be essential to keep things organized.

It would be kinda cool if you could innately tell Quest to treat any clones that will be created from a specific template object as either 'individual' objects or 'stackable' objects (using like a checkbox or dropdown menu on the template object in the editor). Then, when the player carries around multiple copies of an object that were marked as stackable, the inventory interface automatically shows these objects to the player as a stack of one type of object. Even though, you could then still use code like the one you presented, since only the display of the objects to the player is affected, while the actual objects are still sitting individually in the player inventory. But I don't know if this is something that would be easy to do (I imagine it's not) or if that is planned for an update.

As a sidenote, I was wondering if it would be possible to go super-multipurpose with the verb and use it in four distinct ways.

  • “alchemy” by itself: Give me general information. Responses:
    A) You do not have any ingredients.
    B) Displays a list of all possible potions to create from the ingredients the player currently has, noting when they do not have enough of the ingredients for a specific potion. If the player does not have any of the ingredients for a particular potion, that potion is not listed.

  • “alchemy [ingredient]”: Create something from ingredient. Responses:
    A) You do not have this ingredient in your inventory.
    B) You currently do not have enough of what is required to combine this ingredient with another into a potion. Displays creatable potions and amounts of ingredients missing for each potion.
    C) Using [ingredient], you can currently make the following potions: clickable potions with recipes stated, leading to telling the player how many they can make of said potion and asking for an integer input for the amount they want created.

  • “alchemy [potion]”: Create this specific potion if possible, amount yet to be specified. Responses:
    A) You do not have enough of the required ingredients to make a [potion].
    B) You can make # [potion] (s), using # [ingredient] + # [ingredient] for each potion. How many would you like to make? Request integer as player input.

  • “alchemy [amount] [potion]”: Create the already specified amount of this specific potion. Responses:
    A) You do not have enough of the required ingredients to make any [potion]. (Can’t make even one of said potion.)
    B) You do not have enough of the required ingredients to make [amount] [potion] (s). You can, however, make # [potion] (s), using # [ingredient] + # [ingredient] for each potion. How many would you like to make? Request integer as player input.
    C) Take away the ingredients and add the amount of potions, display a message as confirmation.

But... yeah, I would have to take any stacking system into account first, anyway. I'll need to think about this more.

I use the desktop version. :)


Here's a first guess at the "multiple commands" version.

First command. Its scope should be inventory, and its pattern is alchemy #object#.

if (not ListContains (ScopeReachableInventory(), object)) {
  msg ("You haven't got " + object.article + ".")
}
else if (not HasObject (object, "prototype")) {
  msg (Capfirst (GetDisplayAlias (object)) + " is not an alchemy ingredient.")
}
else {
  // first, check how many of the ingredient we have
  ingredient = object.prototype
  number = ListCount (FilterByAttribute (ScopeReachable(), "prototype", ingredient))
  // then start making a list of recipes we can make using it
  recipes = NewStringList()
  // I'm also making a list of missing ingredients, for a more informative message if the player can't make anything.
  missing = NewObjectList()
  foreach (recipe, GetDirectChildren(RECIPES)) {
    if (TypeOf (recipe, "ingredients") = "dictionary") {
      if (DictionaryContains (recipe.ingredients, ingredient.name)) {
        // we've found a recipe that uses this object; do we have enough ingredients?
        can_make = true
        option_text = ""
        foreach (other_ingredient, recipe.ingredients) {
          if (DictionaryItem (recipe.ingredients, other_ingredient) > ListCount (FilterByAttribute (ScopeReachableInventory(), "prototype", GetObject (other_ingredient)))) {
            can_make = false
            list add (missing, GetObject (other_ingredient))
          }
          else {
            if (not option_text = "") {
              option_text = option_text + ", "
            }
            option_text = option_text + ToString (DictionaryItem (recipe.ingredients, other_ingredient)) + "x " + GetDisplayName (GetObject (other_ingredient))
          }
        }
        if (can_make) {
          list add (recipes, option_text + ": {command:alchemy make " + GetDisplayAlias(recipe) + ":" + GetDisplayAlias(recipe) + "}")
        }
      }
    }
  }
  if (ListCount (recipes) > 0) {
    // We now know which recipes can be made with this ingredient; so show the menu
    msg ("Using " + GetDisplayAlias (ingredient) + ", you can currently make the following potions:")
    msg ("<ul><li>" + Join (recipes, "</li><li>") + "</li></ul>")
    if (ListCount (missing) > 0) {
      msg ("To unlock other recipes, try gathering more " + FormatList (ListCompact (missing), ", ", ", or", "") + ".")
    }
  }
  else if (ListCount (missing) > 0) {
    msg ("You can't make anything unless you gather more " + FormatList (ListCompact (missing), ", ", ", or", "") + ".")
  }
  else {
    msg (CapFirst (GetDisplayAlias (object)) + " is not an alchemy ingredient.")
  }
}

Second command. Its scope should be RECIPES, and its pattern is alchemy make #object#.

if (not object.parent = RECIPES) {
  msg ("That isn't a potion you can make.")
}
else {
  // Make the message to tell them how many they can make:
  max_number = ListCount (ScopeReachableInventory())
  foreach (ingredient_name, object.ingredients) {
    number_needed = DictionaryItem (object.ingredients, ingredient_name)
    number_got = ListCount (FilterByAttribute (ScopeReachableInventory(), "prototype", GetObject (ingredient_name)))
    if (number_needed > number_got) {
      msg ("You don't have enough {object:" + ingredient_name + "} to make this.")
    }
    if (max_number > number_got / number_needed) {
      max_number = number_got / number_needed
    }
  }
  if (max_number > 0) {
    msg ("You want to create " + GetDisplayAlias (object) + ". You can create at maximum " + max_number + " of this item.")
    msg ("How many would you like to create?")
    menu = "<ul>"
    for (i, 1, max_number) {
      menu = menu + "<li>{command:alchemy make "+ToString(i) + " " + GetDisplayAlias (object) +":" + ToWords (i) + "} using "
      foreach (ingredient, object.ingredients) {
        menu = menu + ToString (i * DictionaryItem (object.ingredients, ingredient)) + "x " + GetDisplayName (GetObject (ingredient)) + " + "
      }
      menu = Left (menu, LengthOf (menu) - 3) + "</li>"
    }
    msg (menu + "</ul>")
  }
}

And the third command. Its scope should be RECIPES, and its pattern this time is a regular expression so it's easier to check the number: ^alchemy make (?<text_number>\d+) ?x? (?<object>.+)$.

if (not object.parent = RECIPES) {
  msg ("That isn't a potion you can make.")
}
else {
  // Check if we have enough ingredients
  ingredients_used = NewStringList()
  failed = false
  foreach (ingredient_name, object.ingredients) {
    number_needed = DictionaryItem (object.ingredients, ingredient_name)
    got = FilterByAttribute (ScopeReachableInventory(), "prototype", GetObject (ingredient_name))
    if (ListCount (got) = 0) {
      msg ("You don't have any {object:" + ingredient_name + "}.")
      failed = true
    }
    else if (number > ListCount (got) / number_needed) {
      msg ("You only have enough {object:" + ingredient_name + "} to make " + ToString (ListCount (got) / number_needed) + ".")
      failed = true
    }
    else {
      for (i, 1, number_needed * number) {
        list add (ingredients_used, GetString (ListItem (got, i - 1), "name"))
      }
    }
  }
  if (not failed) {
    msg ("You create " + number + "× " + GetDisplayAlias (object))
    for (i, 0, number - 1) {
      CloneObjectAndMove (object, game.pov)
    }
    foreach (ingredient, ingredients_used) {
      destroy (ingredient)
    }
  }
}

Guess we were typing at the same time again :)

It would be kinda cool if you could innately tell Quest to treat any clones that will be created from a specific template object as either 'individual' objects or 'stackable' objects (using like a checkbox or dropdown menu on the template object in the editor). Then, when the player carries around multiple copies of an object that were marked as stackable, the inventory interface automatically shows these objects to the player as a stack of one type of object. Even though, you could then still use code like the one you presented, since only the display of the objects to the player is affected, while the actual objects are still sitting individually in the player inventory. But I don't know if this is something that would be easy to do (I imagine it's not) or if that is planned for an update.

It would be pretty neat.

There are various ways people have approached this with their own scripts. My favourite is a script that automatically renames stackable objects when one of them is moved. so if you have four rocks in a room, they are placed inside each other and their aliases are changed to "rock", "two rocks", "three rocks", and "four rocks"; and the first 3 are marked as "scenery" so they don't show up in the objects pane. Then the player sees a single item named "four rocks", but if they type "drop two rocks" it will do the right thing.

Making this work with the crafting commands I just posted would be a little more complex; but I don't think it would be too hard.

As a sidenote, I was wondering if it would be possible to go super-multipurpose with the verb and use it in four distinct ways.

That's pretty much what I thought of to start with. I didn't include "alchemy" on its own in the code above, because you didn't originally ask for it; but that's what I would have written.


Wow, that's pretty elaborate! Thank you very much for the examples!

Sorry if it's a dumb question, but what are the essential differences between the first iteration of alchemy #object# you gave as opposed to the second one?

One other question...

There are various ways people have approached this with their own scripts. My favourite is a script that automatically renames stackable objects when one of them is moved. so if you have four rocks in a room, they are placed inside each other and their aliases are changed to "rock", "two rocks", "three rocks", and "four rocks"; and the first 3 are marked as "scenery" so they don't show up in the objects pane. Then the player sees a single item named "four rocks", but if they type "drop two rocks" it will do the right thing.

Do you have a link to a thread or some other documentation where I can take a look at this stackable objects renaming script?

I greatly appreciate all of your help!


The two versions of the script are pretty similar. Main difference is that in the second version, instead of showing a menu and doing something with the response, it generates a "menu" which is really just printing out a list of other commands that the player can click on to run them. This means that if 'echo commands' is turned on, the player sees the intermediate commands like "alchemy make Potion A", and "alchemy make 3x Potion A" - so if they want to, the player can decide to just type that command the next time and skip the menu.

I'm not so good at explaining this stuff. I'm on my phone right now; but I'll test those scripts out later to make sure I haven't made any mistakes.

Do you have a link to a thread or some other documentation where I can take a look at this stackable objects renaming script?

I can't find the link in a cursory search. Unfortunately, it's not so easy to find older posts here.

I can probably recreate most of the code off the top of my head. It's relatively intuitive, even though it needs to deal with quite a few edge cases (handling the naming works out nearly as complex as moving the clones around).


Log in to post a reply.

Support

Forums