> ## Documentation Index
> Fetch the complete documentation index at: https://docs.monolisk.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Macros

> Instance transformation and procedural generation for dynamic content

<Note>**Reading time:** \~20 minutes</Note>

Macros in CORP are powerful tools for transforming instance hierarchies during scene loading. They allow you to programmatically modify, replace, or generate Roblox instances based on tagged markers in your workspace or scene data.

## Table of Contents

* [What are Macros?](#what-are-macros)
* [Creating a Macro](#creating-a-macro)
* [Using Macros](#using-macros)
* [Macro Registration](#macro-registration)
* [Common Use Cases](#common-use-cases)
* [Best Practices](#best-practices)

## What are Macros?

Macros are classes that extend `Macro` and implement a `transformInstanceHierarchy()` method. When CORP encounters an instance tagged with a macro name, it invokes that macro to transform the instance hierarchy.

### Key Features

* **Instance Transformation**: Modify or replace instances during loading
* **Procedural Generation**: Generate complex hierarchies from simple markers
* **Template Expansion**: Convert placeholder instances into full structures
* **Dynamic Configuration**: Create instances based on game state or data

### How Macros Work

1. You create instances in Workspace or scenes
2. Tag them with a macro name (e.g., `%spawn_point_macro`)
3. During loading, CORP applies the macro transformation
4. The macro returns the transformed instance hierarchy

## Creating a Macro

### Basic Macro Class

```typescript theme={null}
import { Macro } from "CORP/shared/macros";

export class SpawnPointMacro extends Macro {
    public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
        // Create a new part for the spawn point
        const spawnPart = new Instance("Part");
        spawnPart.Name = instance.Name;
        spawnPart.Size = new Vector3(4, 1, 4);
        spawnPart.Color = new Color3(0, 1, 0);
        spawnPart.Anchored = true;
        spawnPart.CanCollide = false;
        spawnPart.Transparency = 0.5;
        
        // Copy position from original instance
        if (instance.IsA("BasePart")) {
            spawnPart.CFrame = instance.CFrame;
        }
        
        // Parent to the original instance's parent
        spawnPart.Parent = instance.Parent;
        
        // Destroy the original marker
        instance.Destroy();
        
        return spawnPart;
    }
}
```

### Macro with Configuration

Read attributes from the tagged instance:

```typescript theme={null}
import { Macro } from "CORP/shared/macros";

export class TeamSpawnMacro extends Macro {
    public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
        // Read configuration from attributes
        const teamId = instance.GetAttribute("teamId") as number ?? 1;
        const spawnCount = instance.GetAttribute("spawnCount") as number ?? 1;
        
        const container = new Instance("Folder");
        container.Name = `Team${teamId}Spawns`;
        container.Parent = instance.Parent;
        
        for (let i = 0; i < spawnCount; i++) {
            const spawnPoint = new Instance("Part");
            spawnPoint.Name = `Spawn_${i}`;
            spawnPoint.Size = new Vector3(4, 1, 4);
            spawnPoint.Anchored = true;
            
            // Color based on team
            if (teamId === 1) {
                spawnPoint.Color = new Color3(1, 0, 0); // Red
            } else {
                spawnPoint.Color = new Color3(0, 0, 1); // Blue
            }
            
            // Position in a line
            if (instance.IsA("BasePart")) {
                spawnPoint.CFrame = instance.CFrame.mul(new CFrame(i * 10, 0, 0));
            }
            
            spawnPoint.Parent = container;
        }
        
        instance.Destroy();
        
        return container;
    }
}
```

### Complex Macro with Children

Process child instances and generate complex structures:

```typescript theme={null}
import { Macro } from "CORP/shared/macros";

export class GridGeneratorMacro extends Macro {
    public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
        const width = instance.GetAttribute("width") as number ?? 10;
        const height = instance.GetAttribute("height") as number ?? 10;
        const tileSize = instance.GetAttribute("tileSize") as number ?? 10;
        
        // Find a template tile (first child)
        const template = instance.GetChildren()[0];
        
        const grid = new Instance("Folder");
        grid.Name = `Grid_${width}x${height}`;
        grid.Parent = instance.Parent;
        
        if (template && template.IsA("BasePart")) {
            const baseCFrame = instance.IsA("BasePart") ? instance.CFrame : new CFrame();
            
            for (let x = 0; x < width; x++) {
                for (let z = 0; z < height; z++) {
                    const tile = template.Clone();
                    tile.Name = `Tile_${x}_${z}`;
                    tile.CFrame = baseCFrame.mul(
                        new CFrame(x * tileSize, 0, z * tileSize)
                    );
                    tile.Parent = grid;
                }
            }
        }
        
        instance.Destroy();
        
        return grid;
    }
}
```

## Using Macros

### Tagging Instances in Studio

1. Create an instance (Part, Model, Folder, etc.)
2. Add a tag using the Tag Editor plugin
3. Use the format `%macro_name` (e.g., `%spawn_point_macro`)

```
Example in Workspace:
  SpawnMarker (Part)
    - Tag: %spawn_point_macro
    - Attribute: teamId = 1
```

### Programmatic Application

Macros are automatically applied when loading from workspace:

```typescript theme={null}
import { CORP } from "CORP/shared/CORP";

// Macros are applied automatically during bootstrap
CORP.startFromWorkspace();
```

Or apply macros manually to an instance tree:

```typescript theme={null}
import { Macro } from "CORP/shared/macros";

// Apply all registered macros to an instance hierarchy
Macro.applyMacros(workspace.GameScene);
```

### Getting Macro Information

```typescript theme={null}
import { Macro } from "CORP/shared/macros";
import { SpawnPointMacro } from "./macros/SpawnPointMacro";

// Get the registered name of a macro class
const macroName = Macro.getMacroName(SpawnPointMacro);
print(macroName); // "spawn_point_macro"

// Get the tag format for a macro
const macroTag = Macro.getMacroTag(SpawnPointMacro);
print(macroTag); // "%spawn_point_macro"
```

## Macro Registration

Macros must be registered in your Gamepack's exports to be available.

### Registering in Gamepack

Create `exports/macros.ts`:

```typescript theme={null}
import { Config } from "CORP/shared/unreal/gamepacks/config";
import { SpawnPointMacro } from "../src/macros/SpawnPointMacro";
import { TeamSpawnMacro } from "../src/macros/TeamSpawnMacro";
import { GridGeneratorMacro } from "../src/macros/GridGeneratorMacro";

const macros: Config.Macros = {
    mappings: {
        // Use snake_case for macro names
        "spawn_point_macro": SpawnPointMacro,
        "team_spawn_macro": TeamSpawnMacro,
        "grid_generator_macro": GridGeneratorMacro
    }
};

export = macros;
```

> **Naming Convention**: Always use `snake_case` for macro mapping names. This ensures consistency across your codebase and makes tags easy to read.

## Common Use Cases

### 1. Spawn Point Generation

```typescript theme={null}
export class SpawnPointMacro extends Macro {
    public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
        const spawnPoint = new Instance("Part");
        spawnPoint.Name = "SpawnPoint";
        spawnPoint.Size = new Vector3(5, 0.5, 5);
        spawnPoint.Transparency = 0.8;
        spawnPoint.Color = new Color3(0, 1, 0);
        spawnPoint.Anchored = true;
        spawnPoint.CanCollide = false;
        
        // Add a spawn location marker
        const attachment = new Instance("Attachment");
        attachment.Name = "SpawnAttachment";
        attachment.Parent = spawnPoint;
        
        if (instance.IsA("BasePart")) {
            spawnPoint.CFrame = instance.CFrame;
        }
        
        spawnPoint.Parent = instance.Parent;
        instance.Destroy();
        
        return spawnPoint;
    }
}
```

### 2. Prefab Instantiation

```typescript theme={null}
import { vfs } from "CORP/shared/utilities/vfs";

export class PrefabMacro extends Macro {
    public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
        // Read prefab path from attribute
        const prefabPath = instance.GetAttribute("prefabPath") as string;
        
        if (!prefabPath) {
            warn("PrefabMacro: No prefabPath attribute found");
            return instance;
        }
        
        // Load the prefab from assets
        const prefab = vfs.read<Model>(prefabPath, {
            classname: "Model"
        });
        
        if (prefab) {
            const clone = prefab.Clone();
            clone.Name = instance.Name;
            
            if (instance.IsA("BasePart")) {
                clone.SetPrimaryPartCFrame(instance.CFrame);
            }
            
            clone.Parent = instance.Parent;
            instance.Destroy();
            
            return clone;
        }
        
        return instance;
    }
}
```

### 3. Procedural Environment

```typescript theme={null}
export class ForestMacro extends Macro {
    public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
        const treeCount = instance.GetAttribute("treeCount") as number ?? 20;
        const radius = instance.GetAttribute("radius") as number ?? 100;
        
        const forest = new Instance("Folder");
        forest.Name = "Forest";
        forest.Parent = instance.Parent;
        
        const center = instance.IsA("BasePart") 
            ? instance.Position 
            : new Vector3(0, 0, 0);
        
        for (let i = 0; i < treeCount; i++) {
            const angle = (i / treeCount) * math.pi * 2;
            const distance = math.random() * radius;
            
            const tree = new Instance("Part");
            tree.Name = `Tree_${i}`;
            tree.Size = new Vector3(2, 10, 2);
            tree.Position = new Vector3(
                center.X + math.cos(angle) * distance,
                center.Y + 5,
                center.Z + math.sin(angle) * distance
            );
            tree.Anchored = true;
            tree.Color = new Color3(0.4, 0.2, 0.1);
            tree.Parent = forest;
        }
        
        instance.Destroy();
        
        return forest;
    }
}
```

### 4. Dynamic Layout

```typescript theme={null}
export class LayoutMacro extends Macro {
    public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
        const layoutType = instance.GetAttribute("layoutType") as string ?? "grid";
        const spacing = instance.GetAttribute("spacing") as number ?? 10;
        
        const children = instance.GetChildren();
        const container = new Instance("Folder");
        container.Name = instance.Name;
        container.Parent = instance.Parent;
        
        if (layoutType === "grid") {
            const columns = instance.GetAttribute("columns") as number ?? 5;
            
            children.forEach((child, index) => {
                if (child.IsA("BasePart")) {
                    const row = math.floor(index / columns);
                    const col = index % columns;
                    
                    const cloned = child.Clone();
                    cloned.CFrame = new CFrame(
                        col * spacing,
                        0,
                        row * spacing
                    );
                    cloned.Parent = container;
                }
            });
        } else if (layoutType === "circle") {
            children.forEach((child, index) => {
                if (child.IsA("BasePart")) {
                    const angle = (index / children.size()) * math.pi * 2;
                    
                    const cloned = child.Clone();
                    cloned.CFrame = new CFrame(
                        math.cos(angle) * spacing,
                        0,
                        math.sin(angle) * spacing
                    );
                    cloned.Parent = container;
                }
            });
        }
        
        instance.Destroy();
        
        return container;
    }
}
```

## Best Practices

### 1. Use snake\_case for Names

```typescript theme={null}
// ✓ Good - consistent snake_case
"spawn_point_macro": SpawnPointMacro,
"team_spawn_macro": TeamSpawnMacro,
"grid_generator_macro": GridGeneratorMacro

// ✗ Bad - inconsistent casing
"SpawnPointMacro": SpawnPointMacro,
"teamSpawn": TeamSpawnMacro
```

### 2. Clean Up Original Instances

Always destroy the original instance after transformation:

```typescript theme={null}
public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
    const newInstance = this.createTransformed(instance);
    
    // ✓ Clean up the original
    instance.Destroy();
    
    return newInstance;
}
```

### 3. Handle Edge Cases

```typescript theme={null}
public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
    // Validate attributes
    const value = instance.GetAttribute("value") as number;
    if (value === undefined || value < 0) {
        warn(`Invalid value for macro on ${instance.Name}`);
        return instance; // Return unchanged if invalid
    }
    
    // Proceed with transformation
    // ...
}
```

### 4. Use Attributes for Configuration

```typescript theme={null}
// In Studio, on the tagged instance:
// Attribute: teamId = 1
// Attribute: count = 5
// Attribute: enabled = true

public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
    const teamId = instance.GetAttribute("teamId") as number ?? 1;
    const count = instance.GetAttribute("count") as number ?? 1;
    const enabled = instance.GetAttribute("enabled") as boolean ?? true;
    
    if (!enabled) return instance;
    
    // Use configuration...
}
```

### 5. Preserve Hierarchy

Maintain parent relationships when transforming:

```typescript theme={null}
public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
    const transformed = this.createNew();
    
    // ✓ Preserve the parent relationship
    transformed.Parent = instance.Parent;
    
    // Copy children if needed
    instance.GetChildren().forEach(child => {
        child.Parent = transformed;
    });
    
    instance.Destroy();
    
    return transformed;
}
```

### 6. Document Your Macros

```typescript theme={null}
/**
 * Generates a grid of tiles based on configuration attributes.
 * 
 * Required Attributes:
 * - width: number (default: 10)
 * - height: number (default: 10)
 * - tileSize: number (default: 10)
 * 
 * Expected Children:
 * - First child should be a BasePart to use as tile template
 * 
 * Example tag: %grid_generator_macro
 */
export class GridGeneratorMacro extends Macro {
    // ...
}
```

## Next Steps

* **[ScriptableObjects](./scriptableobjects)**: Learn about data assets
* **[Unreal System](./unreal-system)**: Understand Gamepack integration
* **[Scene Management](./scene-management)**: Learn about scenes and instance hierarchies
* **[Examples](./examples)**: See complete implementations

***

Master macros for powerful procedural generation and instance transformation! 🔧
