-
-
Notifications
You must be signed in to change notification settings - Fork 2
StatCodeGuide
This guide explains Formamorph's dynamic stat calculation — attach a small JavaScript snippet to a stat to derive its value from other stats. In a world file this snippet is a stat's code field; see the World Format for where it lives.
The dynamic stat calculation feature allows you to write JavaScript code that automatically calculates a stat's value based on other stats. This enables you to create:
- Derived stats that depend on other stats (e.g., carrying capacity based on strength)
- Compound stats that combine multiple stats (e.g., defense calculated from armor + agility)
- Threshold effects that change based on conditions (e.g., speed penalties when health is below 30%)
- Complex formulas for game mechanics (e.g., damage calculations, regeneration rates)
- Each stat can have an optional JavaScript code snippet
- When stats are updated during gameplay, the code is executed in a safe environment
- The code has access to all current stats and must return a number
- The returned number becomes the new value of the stat (constrained by min/max)
Your code should be valid JavaScript that returns a number. The code has access to a stats array containing all stats in the game.
// Example: Return a fixed value
return 50;To access other stats, use the stats array and the find method to locate stats by name:
// Example: Return the value of another stat
const health = stats.find(s => s.name === 'Health')?.value || 0;
return health;The ?. operator safely accesses the value property (returns undefined if the stat isn't found), and the || 0 provides a default value of 0 if the stat isn't found.
Each stat in the stats array exposes the following properties:
-
id: Unique identifier -
name: Display name of the stat (this is what you match on) -
type: Type of stat ('number'or'list') -
description: Text description -
min: Minimum value -
max: Maximum value -
value: Current value -
regen: Regeneration rate
ℹ️ Only these fields are passed into the sandbox. A stat's own
codeanddescriptorsare not available from inside a snippet.
Calculate a stat as a percentage of another stat:
// Make Stamina 75% of Health
const health = stats.find(s => s.name === 'Health')?.value || 0;
return health * 0.75;Calculate a stat as the average of multiple other stats:
// Make Defense the average of Strength and Agility
const strength = stats.find(s => s.name === 'Strength')?.value || 0;
const agility = stats.find(s => s.name === 'Agility')?.value || 0;
return (strength + agility) / 2;Calculate a stat differently based on conditions:
// Make Speed depend on Health
// Full speed when Health > 50, otherwise reduced
const health = stats.find(s => s.name === 'Health')?.value || 0;
const baseSpeed = 100;
if (health > 50) {
return baseSpeed;
} else {
// Reduce speed by up to 50% as health approaches 0
const healthPercent = health / 50;
return baseSpeed * (0.5 + (healthPercent * 0.5));
}Use more complex formulas for game mechanics:
// Calculate Damage based on Strength, Weapon Skill, and a random factor
const strength = stats.find(s => s.name === 'Strength')?.value || 0;
const weaponSkill = stats.find(s => s.name === 'Weapon Skill')?.value || 0;
// Base damage from strength
const baseDamage = strength * 0.8;
// Skill multiplier (1.0 to 2.0 based on skill)
const skillMultiplier = 1.0 + (weaponSkill / 100);
// Random factor (±20%)
const randomFactor = 0.8 + (Math.random() * 0.4);
return baseDamage * skillMultiplier * randomFactor;Implement diminishing returns for stat scaling:
// Calculate Dodge Chance with diminishing returns
const agility = stats.find(s => s.name === 'Agility')?.value || 0;
// Diminishing returns formula
// First 50 points give full value, after that diminishing returns
let dodgeChance = 0;
if (agility <= 50) {
dodgeChance = agility * 0.5; // 0.5% per point
} else {
// First 50 points give 25% dodge
// Additional points give less and less
const baseChance = 25;
const diminishedPoints = agility - 50;
const diminishedChance = 25 * (1 - Math.exp(-diminishedPoints / 50));
dodgeChance = baseChance + diminishedChance;
}
// Cap at 75%
return Math.min(dodgeChance, 75);Calculate resource consumption based on other stats:
// Calculate Hunger Rate based on activity and size
const activityLevel = stats.find(s => s.name === 'Activity')?.value || 0;
const size = stats.find(s => s.name === 'Size')?.value || 0;
// Base consumption rate
const baseRate = 1;
// Activity multiplier (1.0 to 3.0)
const activityMultiplier = 1.0 + (activityLevel / 50);
// Size factor (larger characters consume more)
const sizeFactor = size / 50;
return baseRate * activityMultiplier * sizeFactor;- Keep it simple: Complex code can be hard to debug and may impact performance
-
Handle missing stats: Always use default values (
|| 0) when accessing stats that might not exist - Stay within min/max: The system will automatically clamp your result to the stat's min/max range
- Avoid infinite loops: Don't create circular dependencies between stats
- Test your code: Use the "Test Code" button to validate your code before saving
- Add comments: Document your code for future reference
- Code execution has a timeout of 1 second to prevent infinite loops
- The code cannot access external resources (network, files, etc.)
- Circular dependencies between stats may cause unexpected behavior
- The code runs in a sandboxed environment with limited JavaScript features
If your code doesn't work as expected:
- Check for typos in stat names (they are case-sensitive)
- Ensure your code returns a number
- Verify that all stats you're referencing actually exist
- Use the "Test Code" button to see any error messages
- Add
console.log()statements to debug your code (output appears in browser console)
// Scale Health based on Level and Constitution
const level = stats.find(s => s.name === 'Level')?.value || 1;
const constitution = stats.find(s => s.name === 'Constitution')?.value || 10;
// Base health
const baseHealth = 50;
// Level scaling (10 health per level)
const levelBonus = (level - 1) * 10;
// Constitution scaling (2 health per point)
const constitutionBonus = (constitution - 10) * 2;
return baseHealth + levelBonus + constitutionBonus;// Calculate Fatigue based on recent actions and Stamina
const stamina = stats.find(s => s.name === 'Stamina')?.value || 0;
const staminaMax = stats.find(s => s.name === 'Stamina')?.max || 100;
const actions = stats.find(s => s.name === 'Recent Actions')?.value || 0;
// Base fatigue from actions
const actionFatigue = actions * 5;
// Recovery from stamina (higher stamina = less fatigue)
const staminaFactor = 1 - (stamina / staminaMax);
// Final fatigue value (0-100)
return Math.min(actionFatigue * staminaFactor, 100);// Calculate Carrying Capacity based on Strength
const strength = stats.find(s => s.name === 'Strength')?.value || 0;
// Base capacity
const baseCapacity = 50;
// Linear scaling for first 50 points (2 units per point)
let capacity = baseCapacity;
if (strength <= 50) {
capacity += strength * 2;
} else {
// First 50 points add 100 capacity
// After that, diminishing returns
capacity += 100;
capacity += Math.sqrt(strength - 50) * 10;
}
return capacity;// Calculate Magical Power based on Intelligence, Wisdom, and current Mana
const intelligence = stats.find(s => s.name === 'Intelligence')?.value || 0;
const wisdom = stats.find(s => s.name === 'Wisdom')?.value || 0;
const mana = stats.find(s => s.name === 'Mana')?.value || 0;
const maxMana = stats.find(s => s.name === 'Mana')?.max || 100;
// Base power from intelligence
const basePower = intelligence * 1.5;
// Wisdom bonus (diminishing returns)
const wisdomBonus = Math.sqrt(wisdom) * 5;
// Mana percentage factor (more effective with higher mana)
const manaFactor = 0.5 + (0.5 * (mana / maxMana));
return (basePower + wisdomBonus) * manaFactor;Playing
Reference
Project