Browse/Crafting & Building/Tonic / Salve Making
Crafting & Building

Tonic / Salve Making

Implementation of tonic / salve making that defines how players interact with this aspect of the game, including feedback and progression.

Low complexity
3 examples
1 patterns

Overview

The tonic / salve making mechanic provides a framework that provides meaningful choices and consequences for player actions. When well-implemented, this mechanic creates a satisfying feedback loop that keeps players engaged and motivated to continue playing. The ongoing evolution of this mechanic reflects the broader maturation of game design as a discipline.

Game Examples

Mech Games

Mech Games use this mechanic where players coordinate with teammates to min-max their character. Randomized elements ensure variety across sessions, resulting in emergent storytelling.

Soulslike Games

Soulslike Games use this mechanic where players allocate limited resources to reach the highest tier. Visual and audio feedback make the interaction satisfying, resulting in narrative investment.

Boxing Games

Boxing Games use this mechanic where players balance risk and reward to maximize their effectiveness. Emergent gameplay arises from simple rules, resulting in build diversity.

Pros & Cons

Advantages

  • Rewards both mechanical skill and strategic thinking
  • Rewards both reaction time and game knowledge
  • Provides clear delayed feedback on player actions

Disadvantages

  • May overwhelm returning players with too many options
  • Risk of feature bloat in multiplayer contexts
  • Can feel punishing if progression is too slow
  • Requires extensive QA testing to avoid edge cases

Implementation Patterns

Assembly Pipeline

Core implementation pattern for handling tonic / salve making logic with clean state management.

class TonicSalveMakingEngine {
  recipes: Recipe[] = [];

  craft(recipeId: string, inventory: Inventory) {
    const recipe = this.recipes.find(r => r.id === recipeId);
    if (!recipe) return null;

    for (const ingredient of recipe.ingredients) {
      if (!inventory.has(ingredient.id, ingredient.amount)) {
        return null; // Missing materials
      }
    }

    for (const ingredient of recipe.ingredients) {
      inventory.remove(ingredient.id, ingredient.amount);
    }

    const quality = this.rollQuality(0.2);
    return { ...recipe.output, quality };
  }

  rollQuality(baseChance: number) {
    const roll = Math.random();
    if (roll < baseChance * 0.01) return "legendary";
    if (roll < baseChance * 0.2) return "rare";
    if (roll < baseChance) return "uncommon";
    return "common";
  }
}