Skip to content

en Actor Entity System

HoCha113 edited this page Jun 22, 2026 · 4 revisions

Documentation moved: This page is archived. See the up-to-date guide at innovault.wiki.

Expanded coverage on innovault.wiki

中文版本

Actor Entity System

Custom entities outside vanilla NPCs/projectiles: spawn, AI, layered draw, net sync.


Overview

The Actor system is suitable for custom entities that need to exist in the game world but don't belong to the NPC/Projectile category, such as:

  • Custom traps, mechanisms
  • Environmental decorations
  • Interactive world objects
Component Description
Actor Entity base class, defines position, velocity, hitbox, draw layers, etc.
ActorLoader Global manager (ModSystem), handles spawning, updating, drawing, destroying
GlobalActor Global hook class, can modify all Actor behavior across mods
ActorDrawLayer Draw layer enumeration

Capacity Limit

  • Maximum simultaneous count: 32,767 (short.MaxValue)
  • Global array ActorLoader.Actors[] stores all active entities

Quick Start

1. Define an Actor

using InnoVault.Actors;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

public class FloatingOrb : Actor
{
    private int timer;

    public override void OnSpawn(params object[] args) {
        Width = 32;
        Height = 32;
        timer = 300; // Alive for 5 seconds
    }

    public override void AI() {
        timer--;
        Velocity = new Vector2(0, MathF.Sin(timer * 0.1f) * 2f);
        Rotation += 0.05f;

        if (timer <= 0) {
            Active = false; // Mark for destruction
        }
    }

    public override bool PreDraw(SpriteBatch spriteBatch, ref Color drawColor) {
        // Custom draw logic
        return true; // Return true to continue default draw
    }
}

2. Spawn an Actor

// Generic spawn (recommended)
int whoAmI = ActorLoader.NewActor<FloatingOrb>(spawnPosition, initialVelocity);

// Spawn by ID
int id = ActorLoader.GetActorID<FloatingOrb>();
int whoAmI = ActorLoader.NewActor(id, spawnPosition);

When calling NewActor on the client, a network request is automatically sent to the server to create the entity. The return value on the client is -1, with the actual slot assigned by the server.

3. Destroy an Actor

// External destruction
ActorLoader.KillActor(whoAmI);

// Self-destruction (in AI)
Active = false;

Actor Base Class Properties

Physical Properties

Property Type Description SyncVar
Active bool Whether alive
Width int Hitbox width (pixels)
Height int Hitbox height (pixels)
Position Vector2 World coordinates (top-left)
Velocity Vector2 Velocity (per tick)
Rotation float Rotation angle (radians)
Scale float Scale multiplier, default 1.0

Computed Properties

Property Type Description
Size Vector2 (Width, Height) * Scale
HitBox Rectangle Collision rectangle
Center Vector2 Center coordinates

System Properties

Property Type Description
ID int Actor type ID (assigned at registration)
WhoAmI int Slot index in the world array
HoverTP bool Whether mouse is hovering over the entity
InScreen bool Whether on the player's screen
DrawExtendMode int Off-screen draw extension range, default 160 pixels
NetUpdate bool Set to true to trigger network sync
DrawLayer ActorDrawLayer Draw layer

Lifecycle Hooks

Hook Timing Description
OnSpawn(params object[] args) On spawn Initialize size, state
AI() Every frame Main logic, Position += Velocity runs automatically after
PreDraw(SpriteBatch, ref Color) Before draw Return false to skip default draw
PostDraw(SpriteBatch, Color) After draw Additional draw effects

Draw Layers

Control draw order by setting the DrawLayer property:

Enum Value Description
ActorDrawLayer.BeforeTiles Before tile drawing
ActorDrawLayer.AfterTiles After tile drawing (PostDrawTiles)
ActorDrawLayer.BeforePlayers Before player drawing
ActorDrawLayer.AfterPlayers After player drawing
ActorDrawLayer.Default Default layer (at the DrawInfernoRings hook)
public override void OnSpawn(params object[] args) {
    DrawLayer = ActorDrawLayer.BeforePlayers; // Draw behind players
}

Scale Helper

The SetScaleCentered method scales with center as the pivot, automatically compensating Position to prevent hitbox offset:

SetScaleCentered(2.0f); // Scale to 2x, keeping center unchanged

Network Synchronization

All [SyncVar] fields on an Actor are automatically synced via SyncVarManager:

public class NetActor : Actor
{
    [SyncVar]
    public int Phase;

    public override void AI() {
        if (Phase != newPhase) {
            Phase = newPhase;
            NetUpdate = true; // Mark for sync
        }
    }
}

Underlying calls:

// Send
actor.SendSyncData(writer); // ≡ SyncVarManager.Send(this, writer)

// Receive
actor.ReceiveSyncData(reader); // ≡ SyncVarManager.Receive(this, reader)

GlobalActor — Global Hooks

GlobalActor allows cross-mod modification of all Actor behavior, similar to tModLoader's GlobalNPC:

using InnoVault.Actors;

public class MyGlobalActor : GlobalActor
{
    public override bool PreAI(Actor actor) {
        // Return false to skip this Actor's AI
        return true;
    }

    public override void PostAI(Actor actor) {
        // Processing after AI runs
    }

    public override void OnSpawn(Actor actor) {
        // Callback when any Actor spawns
    }

    public override bool PreDraw(SpriteBatch spriteBatch, Actor actor, Color drawColor) {
        // Return false to skip draw
        return true;
    }

    public override void PostDraw(SpriteBatch spriteBatch, Actor actor, Color drawColor) {
        // Additional draw
    }
}

ActorLoader API

Method Description
NewActor<T>(position, velocity) Spawn Actor via generic type
NewActor(typeID, position, velocity) Spawn by type ID
AddActor(type, slot, position, velocity) Directly add to specified slot
KillActor(whoAmI, network) Destroy specified Actor
FindNextFreeSlot() Find a free slot
GetActorID<T>() Get the ID for a type
DrawActors(spriteBatch, layer) Draw Actors of specified layer

Complete Example

using InnoVault.Actors;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Terraria;

public class DamageOrb : Actor
{
    [SyncVar]
    public int Damage = 20;

    private int lifetime;

    public override void OnSpawn(params object[] args) {
        Width = 24;
        Height = 24;
        lifetime = 180;
        DrawLayer = ActorDrawLayer.AfterPlayers;
    }

    public override void AI() {
        lifetime--;

        // Track nearest player
        Player target = Main.player[Player.FindClosest(Position, Width, Height)];
        Vector2 dir = (target.Center - Center);
        if (dir.Length() > 0) {
            dir.Normalize();
        }
        Velocity = Vector2.Lerp(Velocity, dir * 5f, 0.02f);

        Rotation += 0.1f;

        // Hit detection
        if (HitBox.Intersects(target.Hitbox)) {
            // Deal damage...
            Active = false;
        }

        if (lifetime <= 0) {
            Active = false;
        }
    }

    public override bool PreDraw(SpriteBatch spriteBatch, ref Color drawColor) {
        // Custom draw
        return false; // Skip default draw
    }
}

// Spawning
public class MyItem : ModItem
{
    public override bool? UseItem(Player player) {
        ActorLoader.NewActor<DamageOrb>(player.Center, Vector2.Zero);
        return true;
    }
}

Navigation

Previous Home Next
SyncVar Network Sync Home DataModules System

Clone this wiki locally