Skip to content

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 value
  • boolean - Returns true/false
  • string - Returns text
  • nothing - Function doesn't return a value
kotlin
// 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():

kotlin
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:

kotlin
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 result belongs in an action (or an attribute's roll:/function: click handler), not in a value:.

Complex Function Example ​

kotlin
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:

kotlin
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:

kotlin
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 ​

kotlin
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:

kotlin
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 true
  • or - Either condition can be true
  • exists - Check if value exists
  • !exists - Check if value doesn't exist
  • !equals - Not equal to
kotlin
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 ​

kotlin
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:

kotlin
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):

kotlin
// 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 ​

kotlin
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 ​

kotlin
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 ​

kotlin
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 ​

kotlin
// 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 ​

kotlin
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 ​

kotlin
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 ​

kotlin
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.