# Permissive DSL

### Overview

The Permissive DSL allows you to create sophisticated state logic using a fluent, readable syntax. Each state extends `PermissiveDSL<ScriptType>` and implements the `create()` method with a `StateBuilder`.

### Basic Structure

```kotlin
class MyState(script: MyScript, name: String) : PermissiveDSL<MyScript>(script, name) {
    override fun StateBuilder<MyScript>.create() {
        // Define your state logic here using the DSL
        branch(conditionName, condition) {
            onSuccess(actionName) {
                // Action when condition is true
            }
            onFailure(actionName) {
                // Action when condition is false
            }
        }
        
        root(conditionName) // Set the root condition to start from
    }
}
```

### Key Components

#### **Branch Conditions**

Branches are the core building blocks of the Permissive DSL. They evaluate conditions and execute different actions based on the result.

```kotlin
branch(StateNames.IS_BACKPACK_FULL, Backpack::isFull) {
    onSuccess(StateNames.DEPOSIT_ITEMS) {
        script.status = "Depositing items"
        // Deposit logic here
    }
    onFailure(StateNames.CONTINUE_PICKING) {
        script.status = "Continuing to pick"
        // Picking logic here
    }
}
```

#### **Condition Types**

**Simple Conditions**

```kotlin
// Boolean functions
Backpack::isFull
Bank::isOpen
Player::isIdle

// Custom conditions
{ player.getCurrentHealth() > 50 }
{ inventory.contains("Food") }
```

**Interlock Conditions**

Interlock conditions provide more complex state management with custom logic:

```kotlin
Interlock("isNearBank", 
    Permissive("bankNearby") { 
        Distance.to(script.selectedLocation.bankArea) < 20 
    }
)
```

#### **State Names**

Use descriptive state names to identify different conditions and actions:

```kotlin
object StateNames {
    const val IS_BACKPACK_FULL = "isBackpackFull"
    const val IS_BANK_OPEN = "isBankOpen"
    const val IS_NEAR_BANK = "isNearBank"
    const val DEPOSIT_ITEMS = "depositItems"
    const val CONTINUE_PICKING = "continuePicking"
    const val OPEN_BANK = "openBank"
    const val TRAVERSE_TO_BANK = "traverseToBank"
}
```

### Complete Example

Here's a comprehensive example showing various Permissive DSL patterns:

```kotlin
class Banking(script: TestScript, name: String) : PermissiveDSL<TestScript>(script, name) {
    
    private companion object {
        const val NEARBY_DISTANCE = 20
    }

    override fun StateBuilder<TestScript>.create() {
        // First branch: Check if backpack is full
        branch(StateNames.IS_BACKPACK_FULL, Backpack::isFull) {
            onSuccess(StateNames.IS_BANK_OPEN)
            onFailure(StateNames.TO_PICKING_STATE_LEAF) {
                script.setState(BotState.DOOGLE_PICKING)
            }
        }

        // Second branch: Check if bank is open
        branch(StateNames.IS_BANK_OPEN, Bank::isOpen) {
            onSuccess(StateNames.DEPOSIT_BACKPACK) {
                script.status = "Depositing items in bank"
                if (Bank.depositAll()) {
                    script.delayUntil(Backpack::isEmpty, Rand.nextInt(10, 14))
                } else {
                    script.debug("Failed to deposit items in bank")
                }
            }
            onFailure(StateNames.IS_NEAR_BANK)
        }

        // Third branch: Check if near bank (using Interlock)
        branch(
            StateNames.IS_NEAR_BANK, 
            Interlock(
                "isNearBank",
                Permissive("bankNearby") { 
                    Distance.to(script.selectedLocation.bankArea) < NEARBY_DISTANCE 
                }
            )
        ) {
            onSuccess(StateNames.OPEN_BANK) { script ->
                script.status = "Opening bank"
                SceneObjectQuery().name("Bank booth").results().nearest()?.let { bank ->
                    bank.interact("Bank")
                    script.delayUntil(Bank::isOpen, Rand.nextInt(10, 14))
                } ?: run {
                    script.debug("Failed to open bank")
                    script.delay(6)
                }
            }
            onFailure(StateNames.TRAVERSE_TO_BANK) { script ->
                script.status = "Traversing to bank"
                moveToBankArea()
            }
        }

        // Set the root condition to start evaluation from
        root(StateNames.IS_BACKPACK_FULL)
    }
}
```

### Advanced Patterns

#### **Chained Conditions**

You can create complex decision trees by chaining branches:

```kotlin
override fun StateBuilder<MyScript>.create() {
    branch(StateNames.HAS_FOOD, { inventory.contains("Food") }) {
        onSuccess(StateNames.IS_HEALTH_LOW)
        onFailure(StateNames.GO_TO_BANK)
    }
    
    branch(StateNames.IS_HEALTH_LOW, { player.getCurrentHealth() < 50 }) {
        onSuccess(StateNames.EAT_FOOD) {
            script.status = "Eating food"
            inventory.interact("Food", "Eat")
        }
        onFailure(StateNames.CONTINUE_ACTIVITY)
    }
    
    branch(StateNames.GO_TO_BANK, { true }) {
        onSuccess(StateNames.TRAVERSE_TO_BANK) {
            script.status = "Going to bank"
            script.movement.traverseTo(script.bankArea)
        }
    }
    
    root(StateNames.HAS_FOOD)
}
```

#### **Dynamic Conditions**

Use script variables to create dynamic conditions:

```kotlin
override fun StateBuilder<MyScript>.create() {
    branch(StateNames.SHOULD_BANK, { 
        Backpack.isFull() || script.needsMoreSupplies() 
    }) {
        onSuccess(StateNames.GO_TO_BANK) {
            script.status = "Banking required"
        }
        onFailure(StateNames.CONTINUE_ACTIVITY) {
            script.status = "Continuing activity"
        }
    }
    
    root(StateNames.SHOULD_BANK)
}
```

#### **State Transitions**

Use state transitions to switch between different script states:

```kotlin
override fun StateBuilder<MyScript>.create() {
    branch(StateNames.IS_BACKPACK_FULL, Backpack::isFull) {
        onSuccess(StateNames.SWITCH_TO_BANKING) {
            script.setState(BotState.BANKING)
        }
        onFailure(StateNames.SWITCH_TO_PICKING) {
            script.setState(BotState.DOOGLE_PICKING)
        }
    }
    
    root(StateNames.IS_BACKPACK_FULL)
}
```

### Best Practices

#### **1. Use Descriptive Names**

```kotlin
// Good
const val IS_BACKPACK_FULL = "isBackpackFull"
const val DEPOSIT_ALL_ITEMS = "depositAllItems"

// Avoid
const val COND1 = "cond1"
const val ACTION = "action"
```

#### **2. Keep Branches Focused**

Each branch should handle one specific condition or decision:

```kotlin
// Good - focused on one condition
branch(StateNames.IS_BACKPACK_FULL, Backpack::isFull) {
    onSuccess(StateNames.GO_TO_BANK)
    onFailure(StateNames.CONTINUE_PICKING)
}

// Avoid - multiple conditions in one branch
branch(StateNames.CHECK_MULTIPLE, { 
    Backpack.isFull() && Bank.isOpen() && player.isIdle() 
}) {
    // Too complex
}
```

#### **3. Use Meaningful Actions**

Actions should clearly describe what they do:

```kotlin
onSuccess(StateNames.DEPOSIT_ALL_ITEMS) {
    script.status = "Depositing all items"
    Bank.depositAll()
    script.delayUntil(Backpack::isEmpty, 5000)
}
```

#### **4. Handle Edge Cases**

Always provide both success and failure paths:

```kotlin
branch(StateNames.OPEN_BANK, { true }) {
    onSuccess(StateNames.BANK_OPENED) {
        script.status = "Bank opened successfully"
    }
    onFailure(StateNames.BANK_OPEN_FAILED) {
        script.status = "Failed to open bank, retrying..."
        script.delay(1000)
    }
}
```

### Integration with Scripts

The Permissive DSL integrates seamlessly with different script types:

#### **With PermissiveScript**

```kotlin
enum class BotState(override val description: String, override val classz: KClass<out PermissiveDSL<*>>) : PermissiveStateEnum {
    BANKING("Banking", BankingState::class),
    PICKING("Picking", PickingState::class);
}

class MyScript : PermissiveScript<BotState>(debug = true) {
    // Script implementation
}
```

#### **State Transitions**

```kotlin
// In your state class
onSuccess(StateNames.SWITCH_TO_BANKING) {
    script.setState(BotState.BANKING)
}
```

### Troubleshooting

#### **Common Issues**

1. **Missing Root Condition**

   ```kotlin
   // Always set a root condition
   root(StateNames.START_CONDITION)
   ```
2. **Infinite Loops**

   ```kotlin
   // Ensure conditions can change
   branch(StateNames.IS_BANK_OPEN, Bank::isOpen) {
       onSuccess(StateNames.DEPOSIT_ITEMS) {
           Bank.depositAll()
           // This will make Bank::isOpen return false eventually
       }
   }
   ```
3. **Undefined State Names**

   ```kotlin
   // Define all state names
   object StateNames {
       const val IS_BANK_OPEN = "isBankOpen"
       // ... other names
   }
   ```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://kxapi.gitbook.io/kxapi/scripting/permissive-dsl.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
