Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Settings & Objectives

Settings let you define configurable game modes. Objectives let you define trackable achievements. Both are optional but powerful ways to extend your game's composability.

Settings

Settings are named configurations that define how a game plays. For example, a Number Guess game might have:

Settings IDNameRangeMax Attempts
1Easy1-10Unlimited
2Medium1-10010
3Hard1-100010

When a token is minted, the minter specifies a settings_id. The game reads that ID to determine the rules for that session.

IMinigameTokenSettings

#[starknet::interface]
pub trait IMinigameTokenSettings<TState> {
    /// Create a new settings configuration. Returns the new settings_id.
    /// This is intentionally permissionless - anyone can create settings.
    fn create_settings(
        ref self: TState,
        name: ByteArray,
        description: ByteArray,
        settings: Span<GameSetting>,
    ) -> u32;
 
    /// Check if a settings_id exists.
    fn settings_exists(self: @TState, settings_id: u32) -> bool;
 
    /// Get the total number of settings configurations.
    fn settings_count(self: @TState) -> u32;
 
    /// Get the details of a settings configuration.
    fn settings_details(self: @TState, settings_id: u32) -> GameSettingDetails;
 
    /// Batch version of settings_details.
    fn settings_details_batch(
        self: @TState, settings_ids: Span<u32>,
    ) -> Array<GameSettingDetails>;
}

GameSettingDetails Struct

#[derive(Drop, Serde)]
pub struct GameSettingDetails {
    pub name: ByteArray,             // "Hard Mode"
    pub description: ByteArray,      // "Range 1-1000, 10 attempts max"
    pub settings: Span<GameSetting>, // Key-value pairs
}
 
#[derive(Drop, Serde)]
pub struct GameSetting {
    pub name: ByteArray,   // "range_min", "range_max", "max_attempts"
    pub value: ByteArray,  // "1", "1000", "10"
}

Implementation Pattern

Your game stores the parsed setting values in its own storage, then delegates the metadata to SettingsComponent:

use game_components_embeddable_game_standard::minigame::settings::SettingsComponent;
 
component!(path: SettingsComponent, storage: settings, event: SettingsEvent);
 
#[abi(embed_v0)]
impl SettingsImpl of IMinigameTokenSettings<ContractState> {
    fn create_settings(
        ref self: ContractState,
        name: ByteArray,
        description: ByteArray,
        settings: Span<GameSetting>,
    ) -> u32 {
        // Parse the key-value pairs into your game's data
        let mut min: u32 = 1;
        let mut max: u32 = 100;
        let mut max_attempts: u32 = 0;
 
        for setting in settings {
            if *setting.name == "range_min" {
                min = parse_u32(*setting.value);
            } else if *setting.name == "range_max" {
                max = parse_u32(*setting.value);
            } else if *setting.name == "max_attempts" {
                max_attempts = parse_u32(*setting.value);
            }
        };
 
        assert!(max > min, "max must be greater than min");
 
        // Store in component (handles ID allocation and metadata)
        let settings_id = self.settings.create(name, description, settings);
 
        // Store parsed values in game-specific storage
        self.settings_data.write(settings_id, (min, max, max_attempts));
 
        settings_id
    }
 
    fn settings_exists(self: @ContractState, settings_id: u32) -> bool {
        self.settings.exists(settings_id)
    }
 
    fn settings_count(self: @ContractState) -> u32 {
        self.settings.count()
    }
 
    fn settings_details(self: @ContractState, settings_id: u32) -> GameSettingDetails {
        self.settings.details(settings_id)
    }
 
    fn settings_details_batch(
        self: @ContractState, settings_ids: Span<u32>,
    ) -> Array<GameSettingDetails> {
        self.settings.details_batch(settings_ids)
    }
}

Permissionless Creation

Settings creation is intentionally permissionless. Anyone can create a new difficulty configuration. This enables:

  • Community-created challenge modes
  • Tournament organizers defining custom rules
  • Seasonal events with special settings

The game validates inputs (e.g., max > min) but doesn't restrict who can create them.

Objectives

Objectives are trackable achievements that can be completed during gameplay. They provide structured goals beyond just scoring.

IMinigameTokenObjectives

#[starknet::interface]
pub trait IMinigameTokenObjectives<TState> {
    /// Create a new objective. Returns the new objective_id.
    /// Permissionless - anyone can create objectives.
    fn create_objective(
        ref self: TState,
        name: ByteArray,
        description: ByteArray,
        objectives: Span<GameObjective>,
    ) -> u32;
 
    /// Check if an objective exists.
    fn objective_exists(self: @TState, objective_id: u32) -> bool;
 
    /// Get the total number of objectives.
    fn objectives_count(self: @TState) -> u32;
 
    /// Check if a token has completed a specific objective.
    fn completed_objective(self: @TState, token_id: felt252, objective_id: u32) -> bool;
 
    /// Get the details of an objective.
    fn objectives_details(self: @TState, objective_id: u32) -> GameObjectiveDetails;
 
    /// Batch versions
    fn objectives_details_batch(
        self: @TState, objective_ids: Span<u32>,
    ) -> Array<GameObjectiveDetails>;
    fn completed_objective_batch(
        self: @TState, token_ids: Span<felt252>, objective_id: u32,
    ) -> Array<bool>;
}

GameObjectiveDetails Struct

#[derive(Drop, Serde)]
pub struct GameObjectiveDetails {
    pub name: ByteArray,                 // "Quick Thinker"
    pub description: ByteArray,          // "Win a game in 5 or fewer guesses"
    pub objectives: Span<GameObjective>, // Structured criteria
}
 
#[derive(Drop, Serde)]
pub struct GameObjective {
    pub name: ByteArray,   // "type", "threshold"
    pub value: ByteArray,  // "2", "5"
}

Example: Number Guess Objectives

Objective 1: "First Win"
  type = 1 (Win any game)
 
Objective 2: "Quick Thinker"
  type = 2, threshold = 5 (Win in 5 or fewer guesses)
 
Objective 3: "Lucky Guess"
  type = 3 (Perfect 1-guess win)

Checking Objective Completion

When a game event occurs (e.g., player wins), check all active objectives for the token:

fn check_objectives(ref self: ContractState, token_id: felt252) {
    let objective_count = self.objectives.count();
    let mut i: u32 = 1;
 
    loop {
        if i > objective_count { break; }
 
        // Skip already completed objectives for this token
        if !self.token_objectives_completed.read((token_id, i)) {
            let details = self.objective_data.read(i);
            let completed = match details.objective_type {
                1 => true,  // Win: always true if we got here
                2 => self.current_guess_count.read(token_id) <= details.threshold,
                3 => self.current_guess_count.read(token_id) == 1,
                _ => false,
            };
 
            if completed {
                self.token_objectives_completed.write((token_id, i), true);
            }
        }
 
        i += 1;
    };
}

Objectives vs Settings

AspectSettingsObjectives
PurposeDefine how the game playsDefine what the player achieves
When setAt mint time (settings_id in token)Checked during gameplay
ScopePer-token (one settings_id per game session)Per-token per-objective
CreationPermissionlessPermissionless
Example"Hard Mode: range 1-1000, 10 attempts""Win in under 5 guesses"

Embedding Components

To add settings and objectives to your game, compose the components:

component!(path: SettingsComponent, storage: settings, event: SettingsEvent);
component!(path: ObjectivesComponent, storage: objectives, event: ObjectivesEvent);
 
#[storage]
struct Storage {
    #[substorage(v0)]
    settings: SettingsComponent::Storage,
    #[substorage(v0)]
    objectives: ObjectivesComponent::Storage,
    // ... your game storage
}
 
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
    #[flat]
    SettingsEvent: SettingsComponent::Event,
    #[flat]
    ObjectivesEvent: ObjectivesComponent::Event,
    // ...
}

Initialize default settings and objectives in your constructor or initializer.