Advanced Logic ā
Once you've mastered the Basic Logic concepts, these advanced features let you create sophisticated game mechanics and automation.
Functions ā
Functions let you create reusable logic that can be called from multiple places, keeping your code organized and reducing duplication.
Function Definition ā
Syntax: function <name>(<parameters>) returns <type> { <body> }
Return Types:
number- Returns a numeric valueboolean- Returns true/falsestring- Returns textnothing- Function doesn't return a value
// Simple calculation function
function calculateDamageBonus(string weaponType) returns number {
if (weaponType equals "Sword") {
return 5
}
else if (weaponType equals "Axe") {
return 8
}
else {
return 2
}
}
// Function with default parameter
function applyLevelBonus(number baseValue, number multiplier = 1) returns number {
fleeting bonus = self.Level * multiplier
return baseValue + bonus
}
// Boolean check function
function canCastSpell(number manaCost) returns boolean {
return self.MP >= manaCost and !self.IsSilenced
}Using Functions ā
Call functions with self.functionName():
action Attack {
fleeting weaponDamage = roll(2d6)
fleeting damageBonus = self.calculateDamageBonus(self.WeaponType)
fleeting totalDamage = weaponDamage + damageBonus
if (self.canCastSpell(5)) {
// Add magical enhancement
totalDamage += roll(1d4)
self.MP -= 5
}
self.Target.HP -= totalDamage
}Functions in Calculated Values ā
Functions can also be called from calculated fields ā value:, min:, max:, initial:, and an attribute's mod:. This lets you break stat math into small, reusable, named pieces instead of repeating it in every field:
function GetPowerBonuses() returns number {
fleeting total = 0
each item in self.Equipment {
total += item.PowerBonus
}
return total
}
number Power(value: { return self.BasePower + self.GetPowerBonuses() })
number Damage(value: { return self.Power + self.GetPowerBonuses() })Functions used in a calculated value must be pure. Calculated values are recomputed during data preparation, which runs synchronously, so such a function may only read fields and return a result. It cannot:
- roll dice ā
roll(...),damage(...) - post chat cards ā
chat { ... } - modify documents ā
self.X = ...,self.X++,self.X += ...,parent.X = ...,target.X = ... - prompt the user, play audio,
wait, or execute macros
If you call an impure function from a calculated value, ISDL reports an error pointing right at it ā move that logic into an action instead. Purity is transitive: a "pure" function that calls an impure one is itself impure.
This is also why a function like
roll this stat and post the resultbelongs in anaction(or an attribute'sroll:/function:click handler), not in avalue:.
Complex Function Example ā
function evaluateSkillCheck(string skillName, number difficulty, boolean hasAdvantage = false) returns boolean {
fleeting skillBonus = parent[skillName].mod
fleeting roll1 = roll(d20)
fleeting finalRoll = roll1
if (hasAdvantage) {
fleeting roll2 = roll(d20)
finalRoll = Math.max(roll1, roll2)
}
fleeting total = finalRoll + skillBonus + self.Level
return total >= difficulty
}
action AttemptLockpicking {
fleeting success = self.evaluateSkillCheck("Dexterity", 18, self.HasThievesTools)
if (success) {
chat Success { "Lock picked successfully!" }
} else {
chat Failure { "Failed to pick the lock." }
}
}Loops and Iteration ā
Each Loops ā
Iterate over collections and arrays to process multiple items:
action CalculateTotalBonus {
fleeting totalBonus = 0
each item in self.Equipment {
totalBonus += item.BonusValue
}
each skill in self.Skills {
if (skill.IsProficient) {
totalBonus += skill.ProficiencyBonus
}
}
self.TotalBonus = totalBonus
}Number Range Iteration ā
Iterate over ranges of numbers:
action ProcessMultipleLevels {
fleeting totalExperience = 0
// Iterate from level 1 to current level
each level in [1 to self.Level] {
totalExperience += level * 100
}
self.LifetimeExperience = totalExperience
}
action CalculateProgression {
fleeting progressionBonus = 0
// Dynamic range based on character properties
each tier in [self.MinTier to self.MaxTier] {
if (tier <= self.CurrentTier) {
progressionBonus += tier * 2
}
}
self.ProgressionBonus = progressionBonus
}Complex Iteration Logic ā
action EvaluateAllConditions {
fleeting activeConditions = 0
fleeting totalPenalty = 0
each condition in self.StatusEffects {
if (condition.IsActive) {
activeConditions += 1
totalPenalty += condition.Penalty
// Apply specific condition effects
if (condition.Type equals "Poison") {
self.HP -= roll(1d4)
}
else if (condition.Type equals "Blindness") {
self.AccuracyPenalty += 4
}
}
}
chat ConditionSummary {
"Active conditions evaluated"
tag activeConditions
tag totalPenalty
}
}Advanced Mathematical Functions ā
Beyond basic math, ISDL provides sophisticated mathematical operations:
action AdvancedCalculations {
fleeting rawValue = self.BaseSTR + roll(2d6) - 7
// Ensure minimum value
fleeting finalStr = Math.max(1, rawValue)
// Apply random variance
fleeting variance = Math.random() * 0.2 // ±10%
fleeting adjustedDamage = Math.round(self.WeaponDamage * (1 + variance))
// Choose best of multiple values
fleeting bestRoll = Math.max(roll(d20), roll(d20), roll(d20))
// Calculate precise bonuses
fleeting healthBonus = Math.ceil(self.CON / 2)
fleeting penaltyReduction = Math.floor(self.Level / 4)
fleeting absoluteValue = Math.abs(self.NegativeModifier)
}Logical Operators ā
Advanced Comparisons ā
and- Both conditions must be trueor- Either condition can be trueexists- Check if value exists!exists- Check if value doesn't exist!equals- Not equal to
action ComplexConditions {
if (self.Level >= 5 and self.Class equals "Mage" and self.MP >= 10) {
// Can cast advanced spells
}
if (self.HP <= 0 or self.Status equals "Unconscious") {
// Character is down
}
if (self.OptionalPower exists and self.OptionalPower > 0) {
// Has and can use special power
}
if (self.Weakness !exists or self.Resistance equals "Fire") {
// No weakness or has fire resistance
}
}Complex Nested Conditions ā
action EvaluateCombatOptions {
if (self.IsInCombat) {
if (self.HP <= self.MaxHP / 4) {
// Critically wounded - defensive options only
if (self.HasDefensiveSpells and self.MP >= 5) {
// Cast protective spell
}
else if (self.Potions > 0) {
// Use healing potion
}
}
else if (self.Initiative > self.Target.Initiative) {
// Acting first - aggressive options
if (self.WeaponType equals "Ranged" and self.Distance > 5) {
// Ranged attack with distance bonus
}
else if (self.SpecialAttacks > 0) {
// Use special attack
}
}
}
}Target and Parent Access ā
Target Properties ā
Access properties of targeted documents:
action AttackTarget {
if (target is Actor) {
fleeting damage = roll(2d6) + self.STR
target.HP -= damage
// Check target's resistances
if (target.FireResistance and self.WeaponType equals "Fire") {
target.HP += Math.floor(damage / 2) // Resist half damage
}
chat AttackResult {
"Attacking " + target.Name
"Dealt " + damage + " damage"
tag target.HP
}
}
}
action HealAlly {
if (target is Actor and target.HP < target.MaxHP) {
fleeting healing = roll(1d8) + self.WIS
target.HP = Math.min(target.HP + healing, target.MaxHP)
chat HealResult {
target.Name + " healed for " + healing + " points"
tag target.HP
}
}
}Parent Properties ā
Access properties from parent documents (useful for items referencing their owner):
// In an item document
action UseItem {
fleeting ownerLevel = parent.Level
fleeting effectiveness = self.BasePower + Math.floor(ownerLevel / 2)
if (parent.canCastSpell(self.ManaCost)) {
parent.MP -= self.ManaCost
parent.Target.HP -= effectiveness
chat ItemUse {
parent.Name + " uses " + self.Name
"Effectiveness: " + effectiveness
}
}
}Error Prevention and Validation ā
Safe Array Access ā
action SafeArrayAccess {
eternal bonusTable = [1, 2, 4, 6, 8]
fleeting level = self.Level
// Ensure valid array index
if (level >= 0 and level < 5) {
fleeting bonus = bonusTable[level]
} else {
fleeting bonus = 0 // Default value
}
}Division by Zero Prevention ā
action SafeDivision {
fleeting totalPoints = self.Strength + self.Dexterity + self.Intelligence
fleeting averageAttribute = 10 // Default value
if (totalPoints > 0) {
averageAttribute = totalPoints / 3
}
self.AverageAttribute = Math.round(averageAttribute)
}Null/Undefined Checks ā
action ValidateData {
if (self.Equipment exists and self.Equipment.count > 0) {
each item in self.Equipment {
if (item.Durability exists and item.Durability <= 0) {
// Handle broken item
item.IsBroken = true
}
}
}
}Performance Optimization ā
Minimize Complex Calculations in Loops ā
// Good: Calculate once, use many times
action EfficientCalculation {
eternal experienceMultiplier = 1.5
fleeting baseXP = self.Level * 100 * experienceMultiplier
each quest in self.CompletedQuests {
quest.FinalXP = quest.BaseXP + baseXP
}
}
// Avoid: Calculating the same thing repeatedly in loops
action InefficientCalculation {
each quest in self.CompletedQuests {
// This recalculates the same values every iteration
quest.FinalXP = quest.BaseXP + (self.Level * 100 * 1.5)
}
}Use Functions for Repeated Logic ā
function getArmorClass(number baseAC, number dexMod, boolean hasShield) returns number {
fleeting totalAC = baseAC + dexMod
if (hasShield) {
totalAC += 2
}
return totalAC
}
// Use the function instead of repeating the calculation
action UpdateDefenses {
self.AC = self.getArmorClass(self.BaseAC, self.DEX, self.HasShield)
}Advanced Examples ā
Spell System with Components ā
function canCastSpell(string spellName, number level, number manaCost) returns boolean {
if (self.Level < level) {
return false
}
if (self.MP < manaCost) {
return false
}
if (self.IsSilenced or self.IsStunned) {
return false
}
return true
}
action CastFireball {
eternal spellLevel = 3
eternal manaCost = 15
if (self.canCastSpell("Fireball", spellLevel, manaCost)) {
self.MP -= manaCost
fleeting damage = roll(spellLevel + "d6")
fleeting targets = self.getTargetsInArea("Fireball")
each target in targets {
fleeting finalDamage = damage
if (target.FireResistance) {
finalDamage = Math.floor(damage / 2)
}
else if (target.FireVulnerability) {
finalDamage = damage * 2
}
target.HP -= finalDamage
}
chat Fireball {
"Fireball explodes for " + damage + " fire damage!"
tag damage
tag targets.length + " targets affected"
}
}
}Dynamic Difficulty System ā
function calculateDifficulty() returns number {
eternal baseDifficulty = 10
fleeting levelAdjustment = Math.floor(self.Level / 2)
fleeting partyModifier = self.PartySize * 2
fleeting finalDifficulty = baseDifficulty + levelAdjustment - partyModifier
// Clamp between reasonable bounds
return Math.max(5, Math.min(25, finalDifficulty))
}
action AttemptChallenge {
fleeting difficulty = self.calculateDifficulty()
fleeting roll = roll(d20) + self.SkillBonus
fleeting success = roll >= difficulty
fleeting criticalSuccess = roll >= (difficulty + 10)
fleeting criticalFailure = roll <= (difficulty - 10)
if (criticalSuccess) {
self.Experience += 100
chat Result { "Critical Success!" }
}
else if (success) {
self.Experience += 50
chat Result { "Success!" }
}
else if (criticalFailure) {
self.HP -= roll(1d4)
chat Result { "Critical Failure!" }
}
else {
chat Result { "Failure." }
}
}Next Steps ā
Ready for the ultimate ISDL features?
- Interactivity - Master prompts, events, and advanced user interaction
- Logic Reference - Complete syntax and function reference
- Recipes - Copy-paste solutions for typical RPG mechanics
- Custom Code & Styles - Drop to native Foundry JS/CSS for what the language doesn't cover (settings, extra sheets, UI hooks)
You now have the tools to create sophisticated RPG mechanics! Check out Recipes for solutions to typical game design challenges.