YAML Source is the Network Storage authoring standard.
Use the s&box Library Manager install for auto-updates. GitHub is best for agents, source review, contributions, and manual installs.

Game Values

Game Values are server-authoritative constants that your endpoints use at execution time. Instead of hardcoding XP rewards, shop prices, or progression curves i...

# Game Values

Game Values are server-authoritative constants that your endpoints use at execution time. Instead of hardcoding XP rewards, shop prices, or progression curves in your game code, you define them as Game Values and reference them in endpoint pipelines.

Change a value from the dashboard and every endpoint that references it picks up the new value immediately -- no game update required.

## Setting Up Game Values

From your project page, open the **Game Values** tab (or find them inside a collection's Constants section).

### Creating a Group

1. Click **Add Group**
2. Enter a group name (e.g. `combat`, `progression`, `economy`)
3. Add key-value pairs -- click **Add Key** for each constant
4. Click **Save**

### Creating a Table

1. Click **Add Table**
2. Enter a table name (e.g. `shop_items`, `mining_nodes`)
3. Define columns -- click **Add Column** and set the name and type (`string`, `number`, etc.)
4. Add rows -- click **Add Row** and fill in the values
5. Click **Save**

Reference groups in endpoints as `{{values.groupName.key}}` and look up table rows with `lookup` steps.

## Two Types

### Groups (Key-Value)

Simple key-value pairs, organized by group name. Use groups for configuration constants.

**Group: `progression`**

| Key | Value |
|-----|-------|
| `xp_per_level` | `1000` |
| `max_level` | `100` |

**Group: `combat`**

| Key | Value |
|-----|-------|
| `xp_per_kill` | `50` |
| `xp_per_assist` | `20` |
| `gold_per_kill` | `25` |

**Group: `economy`**

| Key | Value |
|-----|-------|
| `starting_gold` | `500` |
| `trade_tax_percent` | `5` |
| `daily_login_bonus` | `100` |

### Tables

Structured data with columns and rows. Use tables when you have a list of items, quests, recipes, or anything with multiple attributes per entry.

**Table: `shop_items`**

| item_id | name | cost | xp_required | category |
|---------|------|------|-------------|----------|
| `iron_sword` | Iron Sword | 250 | 0 | weapon |
| `steel_armor` | Steel Armor | 800 | 15000 | armor |
| `health_potion` | Health Potion | 50 | 0 | consumable |
| `fire_staff` | Fire Staff | 1200 | 25000 | weapon |

**Table: `mining_nodes`**

| node_id | ore_amount | xp_required | respawn_seconds |
|---------|-----------|-------------|-----------------|
| `copper_vein` | 5 | 0 | 60 |
| `iron_deposit` | 3 | 10000 | 120 |
| `gold_ore` | 2 | 25000 | 300 |
| `mythril_seam` | 1 | 50000 | 600 |

Note that tables use `xp_required` (not `level_required`). Since level is computed from XP using `floor(xp / xp_per_level)`, requirements should be expressed in XP. Your endpoints compare directly against the player's XP.

## Loading Values In Game Code

Use the Network Storage library to load all Game Values for your project:

```csharp
var values = await NetworkStorage.GetGameValues();
```

### Response

```yml
groups:
progression:
xp_per_level: 1000
max_level: 100
combat:
xp_per_kill: 50
xp_per_assist: 20
gold_per_kill: 25
economy:
starting_gold: 500
trade_tax_percent: 5
daily_login_bonus: 100
tables:
shop_items:
columns:
- item_id
- name
- cost
- xp_required
- category
rows:
- item_id: iron_sword
name: Iron Sword
cost: 250
xp_required: 0
category: weapon
- item_id: steel_armor
name: Steel Armor
cost: 800
xp_required: 15000
category: armor
mining_nodes:
columns:
- node_id
- ore_amount
- xp_required
- respawn_seconds
rows:
- node_id: copper_vein
ore_amount: 5
xp_required: 0
respawn_seconds: 60
- node_id: iron_deposit
ore_amount: 3
xp_required: 10000
respawn_seconds: 120
```

## Client-Side Display Cache

Game Values are useful for client-side display, such as item prices in a shop UI. Load them through `NetworkStorage.GetGameValues()` and keep a local copy if your UI reads them often. The values that matter for game logic are always read server-side by your endpoints, so a hacked client displaying wrong prices does not affect what the endpoint actually charges.

## Referencing in Endpoints

### Group values (inline)

Reference a group value directly in any endpoint step using `{{values.groupName.key}}`:

```yml
id: save
type: write
collection: player_data
key: "{{steamId}}"
ops:
- op: inc
path: xp
value: "{{values.combat.xp_per_kill}}"
- op: inc
path: gold
value: "{{values.combat.gold_per_kill}}"
```

### Group values (via lookup step)

Use a `lookup` step when you need to reference the value in multiple places or in transform expressions:

```yml
id: progression
type: lookup
source: values
group: progression
key: xp_per_level
```

After this step, `{{progression.value}}` contains `1000`. Use it in a transform to compute level:

```yml
id: level
type: transform
expression: floor({{player.xp}} / {{progression.value}})
```

### Table lookups

Use a `lookup` step to find a row in a table:

```yml
id: item
type: lookup
source: values
table: shop_items
where:
item_id: "{{input.itemId}}"
```

After the lookup, reference columns from the matched row: `{{item.cost}}`, `{{item.name}}`, `{{item.xp_required}}`.

If no row matches, the step fails with `NOT_FOUND` and the pipeline stops.

### Table filters

Use a `filter` step to get multiple matching rows:

```yml
id: weapons
type: filter
source: values
table: shop_items
where:
category: weapon
xp_required:
<=: "{{player.xp}}"
```

Result: `{{weapons.rows}}` contains all weapon items the player's XP qualifies for.

## Best Practice: Store Progression Curves Here

Keep your progression curve in a Game Values group:

**Group: `progression`**

| Key | Value |
|-----|-------|
| `xp_per_level` | `1000` |
| `max_level` | `100` |

Then compute level inside endpoints with a transform step:

```yml
id: player_level
type: transform
expression: min(floor({{player.xp}} / {{values.progression.xp_per_level}}), {{values.progression.max_level}})
```

This pattern means:
- You never store `level` as a field in your collection
- Changing `xp_per_level` from 1000 to 500 doubles everyone's level instantly
- No data migration is ever needed for rebalancing
- The endpoint response includes the computed level for display

## Complex Value Types via Tables

Game Values tables are how you represent **structured value types** (structs). Instead of a single number, each table row is a data object with multiple typed columns.

This is the answer to "How do I store a complex type like an upgrade with multiple stats?"

**Example: Equipment Upgrades**

Each upgrade level is not a single number — it's a struct with many fields:

**Table: `phaser_upgrades`**

| level | name | max_heat | heat_per_sec | cool_rate | max_beams | beam_range | ore_tier_unlock | cost | xp_required |
|-------|------|----------|-------------|-----------|-----------|------------|----------------|------|-------------|
| 1 | Mk.I | 100 | 20 | 15 | 2 | 2000 | 1 | 0 | 0 |
| 2 | Mk.II | 120 | 18 | 18 | 2 | 2200 | 2 | 500 | 1000 |
| 3 | Mk.III | 140 | 16 | 22 | 3 | 2500 | 2 | 1500 | 3000 |

**Using in an endpoint:**

Look up the row by a key column, then access any column:

```yml
id: phaser
type: lookup
source: values
table: phaser_upgrades
where:
level: "{{player.phaserLevel}}"
```

Now reference individual fields: `{{phaser.max_heat}}`, `{{phaser.cost}}`, `{{phaser.ore_tier_unlock}}`, etc.

**Validating with a complex type:**

```yml
id: can_upgrade
type: condition
check:
all:
- left: "{{player.currency}}"
op: '>='
right: "{{phaser.cost}}"
- left: "{{player.xp}}"
op: '>='
right: "{{phaser.xp_required}}"
```

**Loading in C# for display:**

```csharp
// Parse the phaser_upgrades table into a dictionary of level → struct
var phaserTable = data.GetProperty( "tables" ).GetProperty( "phaser_upgrades" );
foreach ( var row in phaserTable.GetProperty( "rows" ).EnumerateArray() )
{
int level = row.GetProperty( "level" ).GetInt32();
string name = row.GetProperty( "name" ).GetString();
float maxHeat = (float)row.GetProperty( "max_heat" ).GetDouble();
int maxBeams = row.GetProperty( "max_beams" ).GetInt32();
int cost = row.GetProperty( "cost" ).GetInt32();
// Use these to display upgrade info in your shop/upgrade UI
}
```

**Key insight:** You don't need a special "struct" or "object" value type in groups. Tables ARE your struct type — each row is a structured data object with as many typed columns as you need.

## When to Use Groups vs Tables

| Use Case | Type |
|----------|------|
| Single config values (XP per kill, XP per level) | Group |
| Lists of things (items, quests, enemies) | Table |
| Feature flags (enable_pvp, double_xp_weekend) | Group |
| Drop tables, recipe lists, spawn configs | Table |
| Progression thresholds | Group |
| **Complex value types (upgrade tiers, equipment stats)** | **Table** |

## C# Example: Loading Values on Game Start

```csharp
public static class GameConfig
{
public static int XpPerKill { get; private set; }
public static int GoldPerKill { get; private set; }
public static int XpPerLevel { get; private set; }
public static int MaxLevel { get; private set; }

public static async Task Load()
{
var data = await NetworkStorage.GetGameValues();
if ( !data.HasValue )
return;

var combat = data.Value.GetProperty( "groups" ).GetProperty( "combat" );
XpPerKill = combat.GetProperty( "xp_per_kill" ).GetInt32();
GoldPerKill = combat.GetProperty( "gold_per_kill" ).GetInt32();

var progression = data.Value.GetProperty( "groups" ).GetProperty( "progression" );
XpPerLevel = progression.GetProperty( "xp_per_level" ).GetInt32();
MaxLevel = progression.GetProperty( "max_level" ).GetInt32();

Log.Info( $"Game values loaded: {XpPerKill} XP/kill, {XpPerLevel} XP/level" );
}

/// <summary>
/// Compute level from XP locally for display purposes.
/// The server computes this authoritatively in endpoints.
/// </summary>
public static int ComputeLevel( int xp )
{
return Math.Min( xp / XpPerLevel, MaxLevel );
}
}

// Call on game start
await GameConfig.Load();

// Use locally for display
int displayLevel = GameConfig.ComputeLevel( playerXp );
```

## C# Example: Loading a Specific Table

```csharp
public static async Task LoadShopItems()
{
var data = await NetworkStorage.GetGameValues();
if ( !data.HasValue )
return;

var shopTable = data.Value.GetProperty( "tables" ).GetProperty( "shop_items" );
var rows = shopTable.GetProperty( "rows" );

foreach ( var row in rows.EnumerateArray() )
{
var itemId = row.GetProperty( "item_id" ).GetString();
var name = row.GetProperty( "name" ).GetString();
var cost = row.GetProperty( "cost" ).GetInt32();
Log.Info( $"Shop item: {name} ({itemId}) - {cost} gold" );
}
}
```