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

# Decorators

> Component decorators for serialization, dependencies, and RPCs

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

CORP provides several powerful decorators to enhance your components with additional functionality and constraints. Decorators are applied using TypeScript's `@decorator` syntax.

## Table of Contents

* [@SerializeField](#serializefield)
* [@RequiresComponent](#requirescomponent)
* [@RPC.Method](#rpcmethod)
* [Combining Decorators](#combining-decorators)

## @SerializeField

The `@SerializeField` decorator marks properties for serialization, allowing them to:

* Be saved and restored with scenes
* Be configured in scene descriptions
* Be editable in future editor tools

### Usage

```typescript theme={null}
import { Behavior } from "CORP/shared/componentization/behavior";
import { SerializeField } from "CORP/shared/serialization/serialize-field";

export class GameSettings extends Behavior {
    @SerializeField
    public playerSpeed: number = 16;

    @SerializeField
    public maxPlayers: number = 10;

    @SerializeField
    public gameDuration: number = 300;

    // Not serialized
    private currentTime: number = 0;

    protected getSourceScript(): ModuleScript {
        return script as ModuleScript;
    }

    public onStart(): void {}
    public willRemove(): void {}
}
```

### Supported Types

`@SerializeField` supports the following types:

```typescript theme={null}
export class AllTypes extends Behavior {
    // Primitives
    @SerializeField
    public numberValue: number = 42;

    @SerializeField
    public stringValue: string = "hello";

    @SerializeField
    public booleanValue: boolean = true;

    // Roblox types
    @SerializeField
    public position: Vector3 = new Vector3(0, 0, 0);

    @SerializeField
    public color: Color3 = new Color3(1, 1, 1);

    // Plain objects/tables
    @SerializeField
    public config: { [key: string]: number } = {
        health: 100,
        armor: 50
    };

    // Arrays
    @SerializeField
    public items: string[] = ["sword", "shield"];

    protected getSourceScript(): ModuleScript {
        return script as ModuleScript;
    }

    public onStart(): void {}
    public willRemove(): void {}
}
```

### Unsupported Types

The following cannot be serialized:

```typescript theme={null}
// ✗ Functions
@SerializeField
public callback: () => void = () => {}; // Error!

// ✗ Instances
@SerializeField
public part: Part; // Error!

// ✗ Components
@SerializeField
public otherComponent: OtherComponent; // Error!

// ✗ Complex objects with metatables
@SerializeField
public signal: Signal; // Error!
```

### Scene Configuration

Serialized fields can be set in scene descriptions:

```typescript theme={null}
// Scene definition
const scene: SceneSerialization.SceneDescription = {
    children: [
        {
            name: "GameSettings",
            components: [
                {
                    path: ["src", "shared", "components", "GameSettings"],
                    data: {
                        // These values override the defaults
                        playerSpeed: 20,
                        maxPlayers: 16,
                        gameDuration: 600
                    }
                }
            ],
            children: []
        }
    ]
};
```

### Getting Serialized State

```typescript theme={null}
export class Checkpoint extends Behavior {
    @SerializeField
    public checkpointId: number = 1;

    @SerializeField
    public activated: boolean = false;

    public save(): void {
        // Get all serialized fields as an object
        const state = this.serializeState();
        print(state); // { checkpointId: 1, activated: false }
        
        // Save to data store or similar
    }

    protected getSourceScript(): ModuleScript {
        return script as ModuleScript;
    }

    public onStart(): void {}
    public willRemove(): void {}
}
```

## @RequiresComponent

The `@RequiresComponent` decorator enforces that a component's dependencies are present on the GameObject. If the required component is missing, it will be automatically added.

### Usage

```typescript theme={null}
import RequiresComponent from "CORP/shared/componentization/decorators/requires-component";
import { NetworkObject } from "CORP/shared/networking/network-object";
import { NetworkBehavior } from "CORP/shared/networking/network-behavior";

@RequiresComponent(NetworkObject)
export class PlayerSync extends NetworkBehavior {
    public onStart(): void {
        // NetworkObject is guaranteed to exist
        const netObj = this.getComponent(NetworkObject)!;
        print(`Network ID: ${netObj.getId()}`);
    }

    protected getSourceScript(): ModuleScript {
        return script as ModuleScript;
    }

    public willRemove(): void {}
}
```

### Multiple Requirements

You can stack multiple `@RequiresComponent` decorators:

```typescript theme={null}
import RequiresComponent from "CORP/shared/componentization/decorators/requires-component";

@RequiresComponent(Rigidbody)
@RequiresComponent(Collider)
export class PhysicsController extends Behavior {
    public onStart(): void {
        // Both Rigidbody and Collider are guaranteed
        const rb = this.getComponent(Rigidbody)!;
        const col = this.getComponent(Collider)!;
    }

    protected getSourceScript(): ModuleScript {
        return script as ModuleScript;
    }

    public willRemove(): void {}
}
```

### Common Use Cases

#### Network Components

```typescript theme={null}
@RequiresComponent(NetworkObject)
export class NetworkedHealth extends NetworkBehavior {
    public readonly health = new NetworkedVariable<number>(100);
    
    // ... implementation
}
```

#### Physics Dependencies

```typescript theme={null}
@RequiresComponent(PhysicsBody)
export class Movement extends Behavior {
    private body!: PhysicsBody;

    public onStart(): void {
        this.body = this.getComponent(PhysicsBody)!;
    }

    // ... implementation
}
```

#### Component Chains

```typescript theme={null}
// Transform requires a base component
@RequiresComponent(BaseTransform)
export class AdvancedTransform extends Behavior {
    // ... implementation
}

// Character requires both Transform and Health
@RequiresComponent(AdvancedTransform)
@RequiresComponent(HealthSystem)
export class CharacterController extends Behavior {
    // ... implementation
}
```

### Benefits

1. **Automatic Dependency Resolution**: Required components are added automatically
2. **Type Safety**: You can safely use non-null assertions (`!`)
3. **Documentation**: Makes dependencies explicit in code
4. **Prevention**: Avoids runtime errors from missing components

> **Note**: Components are disallowed from having multiple instances by default in CORP. The `@DisallowMultipleComponents` decorator is reserved for future use.

## @RPC.Method

The `@RPC.Method` decorator marks methods as Remote Procedure Calls, allowing them to be invoked across the network. See the [Networking documentation](./networking#remote-procedure-calls-rpcs) for complete details.

### Basic Usage

```typescript theme={null}
import { NetworkBehavior } from "CORP/shared/networking/network-behavior";
import { RPC } from "CORP/shared/networking/RPC";

export class PlayerActions extends NetworkBehavior {
    @RPC.Method({
        endpoints: [RPC.Endpoint.CLIENT_TO_SERVER],
        accessPolicy: RPC.AccessPolicy.OWNER
    })
    public jump(): void {
        if (RunService.IsServer()) {
            print("Player jumped!");
            // Apply jump force
        }
    }

    protected getSourceScript(): ModuleScript {
        return script as ModuleScript;
    }

    public onStart(): void {}
    public willRemove(): void {}
}
```

### Configuration Options

```typescript theme={null}
interface RPCConfig {
    // Where the RPC can be called from/to
    endpoints: RPC.Endpoint[];
    
    // Who can call the RPC
    accessPolicy?: RPC.AccessPolicy;
    
    // Whether it returns a value
    returnMode?: RPC.ReturnMode;
    
    // Transmission reliability
    reliability?: RPC.Reliability;
}
```

### Examples by Use Case

#### Client Request to Server

```typescript theme={null}
@RPC.Method({
    endpoints: [RPC.Endpoint.CLIENT_TO_SERVER],
    accessPolicy: RPC.AccessPolicy.OWNER
})
public requestPickup(itemId: string): void {
    if (RunService.IsServer()) {
        // Server validates and processes
        this.giveItem(itemId);
    }
}
```

#### Server Notification to Clients

```typescript theme={null}
@RPC.Method({
    endpoints: [RPC.Endpoint.SERVER_TO_CLIENT],
    reliability: RPC.Reliability.UNRELIABLE
})
public updatePosition(position: Vector3): void {
    if (RunService.IsClient()) {
        // Update visual position
        this.setTransform(new CFrame(position));
    }
}
```

#### RPC with Return Value

```typescript theme={null}
@RPC.Method({
    endpoints: [RPC.Endpoint.CLIENT_TO_SERVER],
    returnMode: RPC.ReturnMode.RETURNS,
    accessPolicy: RPC.AccessPolicy.OWNER
})
public getInventory(): string[] {
    if (RunService.IsServer()) {
        return this.inventory;
    }
    return [];
}
```

#### Unreliable for Performance

```typescript theme={null}
@RPC.Method({
    endpoints: [RPC.Endpoint.CLIENT_TO_SERVER],
    reliability: RPC.Reliability.UNRELIABLE,
    accessPolicy: RPC.AccessPolicy.OWNER
})
public sendPosition(pos: Vector3): void {
    // Sent frequently, ok to drop some packets
}
```

## Combining Decorators

Decorators can be combined for powerful component definitions:

### Network Component with Dependencies

```typescript theme={null}
import RequiresComponent from "CORP/shared/componentization/decorators/requires-component";
import DisallowMultipleComponents from "CORP/shared/componentization/decorators/disallow-multiple-components";
import { SerializeField } from "CORP/shared/serialization/serialize-field";
import { NetworkObject } from "CORP/shared/networking/network-object";
import { NetworkBehavior } from "CORP/shared/networking/network-behavior";
import { RPC } from "CORP/shared/networking/RPC";

@DisallowMultipleComponents
@RequiresComponent(NetworkObject)
export class CombatSystem extends NetworkBehavior {
    @SerializeField
    public maxHealth: number = 100;

    @SerializeField
    public attackDamage: number = 25;

    private currentHealth: number = 100;

    public onStart(): void {
        this.currentHealth = this.maxHealth;
    }

    @RPC.Method({
        endpoints: [RPC.Endpoint.CLIENT_TO_SERVER],
        accessPolicy: RPC.AccessPolicy.OWNER
    })
    public attack(targetId: string): void {
        if (RunService.IsServer()) {
            // Process attack
            print(`Attacking ${targetId} for ${this.attackDamage} damage`);
        }
    }

    @RPC.Method({
        endpoints: [RPC.Endpoint.SERVER_TO_CLIENT]
    })
    public takeDamage(amount: number): void {
        if (RunService.IsClient()) {
            // Show damage effect
            print(`Took ${amount} damage!`);
        }
    }

    protected getSourceScript(): ModuleScript {
        return script as ModuleScript;
    }

    public willRemove(): void {}
}
```

### Complex Character Controller

```typescript theme={null}
@DisallowMultipleComponents
@RequiresComponent(NetworkObject)
@RequiresComponent(CharacterModel)
@RequiresComponent(AnimationController)
export class AdvancedCharacterController extends NetworkBehavior {
    @SerializeField
    public walkSpeed: number = 16;

    @SerializeField
    public runSpeed: number = 24;

    @SerializeField
    public jumpPower: number = 50;

    private model!: CharacterModel;
    private animator!: AnimationController;

    public onStart(): void {
        this.model = this.getComponent(CharacterModel)!;
        this.animator = this.getComponent(AnimationController)!;

        this.setupControls();
    }

    @RPC.Method({
        endpoints: [RPC.Endpoint.CLIENT_TO_SERVER],
        accessPolicy: RPC.AccessPolicy.OWNER,
        reliability: RPC.Reliability.UNRELIABLE
    })
    public move(direction: Vector3): void {
        if (RunService.IsServer()) {
            // Server validates and applies movement
            this.applyMovement(direction);
        }
    }

    @RPC.Method({
        endpoints: [RPC.Endpoint.CLIENT_TO_SERVER],
        accessPolicy: RPC.AccessPolicy.OWNER
    })
    public jump(): void {
        if (RunService.IsServer()) {
            this.applyJump();
        }
    }

    private setupControls(): void {
        if (this.getNetworkObject().getOwner() === Players.LocalPlayer) {
            // Setup input handling
        }
    }

    private applyMovement(direction: Vector3): void {
        // Movement logic
    }

    private applyJump(): void {
        // Jump logic
    }

    protected getSourceScript(): ModuleScript {
        return script as ModuleScript;
    }

    public willRemove(): void {}
}
```

## Best Practices

### 1. Always Use @RequiresComponent for Dependencies

```typescript theme={null}
// ✓ Good - explicit dependencies
@RequiresComponent(NetworkObject)
export class MyNetworkComponent extends NetworkBehavior {}

// ✗ Bad - implicit dependencies, may fail at runtime
export class MyNetworkComponent extends NetworkBehavior {
    public onStart(): void {
        const netObj = this.getComponent(NetworkObject); // May be undefined!
    }
}
```

### 2. Serialize Configuration, Not State

```typescript theme={null}
// ✓ Good - serialize configuration
@SerializeField
public maxHealth: number = 100;

private currentHealth: number = 100; // Runtime state

// ✗ Avoid - serializing runtime state can cause issues
@SerializeField
public currentHealth: number = 100;
```

### 3. Use @DisallowMultipleComponents for Singletons

```typescript theme={null}
// ✓ Good - prevents duplicates
@DisallowMultipleComponents
export class PlayerController extends Behavior {}

// For components that can have multiples
export class Weapon extends Behavior {} // Can have multiple weapons
```

### 4. Configure RPCs Appropriately

```typescript theme={null}
// ✓ Good - appropriate reliability for use case
@RPC.Method({
    endpoints: [RPC.Endpoint.CLIENT_TO_SERVER],
    reliability: RPC.Reliability.UNRELIABLE // OK for frequent updates
})
public sendPosition(pos: Vector3): void {}

@RPC.Method({
    endpoints: [RPC.Endpoint.CLIENT_TO_SERVER],
    reliability: RPC.Reliability.RELIABLE // Important for critical actions
})
public purchaseItem(itemId: string): void {}
```

### 4. Document Decorator Choices

```typescript theme={null}
/**
 * Handles player combat actions.
 * Requires NetworkObject for replication.
 */
@RequiresComponent(NetworkObject)
export class CombatHandler extends NetworkBehavior {
    // ... implementation
}
```

## Next Steps

* **[Advanced Topics](./advanced-topics)**: Learn about advanced patterns
* **[API Reference](./api-reference)**: Complete API documentation
* **[Examples](./examples)**: See decorators in action

***

Use decorators to create robust, well-structured components! 🎯
