-
Notifications
You must be signed in to change notification settings - Fork 6
en Basic TP Entity
Documentation moved: This page is archived. See the up-to-date guide at innovault.wiki.
TileProcessor attaches to tiles as an independent entity (similar to TileEntity) for per-tile logic and draw.
Differences from TileEntity:
- More independent: Standalone entity system; no special logic in Tile code
-
Auto-mounting: Set
TargetTileID; matching tiles get TP entities automatically - Built-in net sync: Multiplayer field sync out of the box
-
Persistence:
SaveData/LoadDatafor world save -
Multi-layer draw:
PreTileDraw,BackDraw,Draw,FrontDraw -
Idle culling:
IdleDistancestops updates for distant entities
| Property/Field | Type | Default | Description |
|---|---|---|---|
TargetTileID |
int |
-1 |
Target tile type ID, -1 means no auto-mounting |
ID |
int |
— | Globally unique type ID (read-only, assigned at load) |
WhoAmI |
int |
— | Local index (may differ per client) |
Mod |
Mod |
— | Owning mod instance |
FullName |
string |
— | Internal full name |
| Property/Field | Type | Default | Description |
|---|---|---|---|
Active |
bool |
— | Whether active, stops updating and drawing when false
|
Spwan |
bool |
— | Whether Initialize() has been called |
HoverTP |
bool |
— | Whether the mouse is hovering over the entity |
InScreen |
bool |
— | Whether on screen (updated in real-time during drawing) |
Tile |
Tile |
default |
The followed tile (updated once on load/place) |
TrackItem |
Item |
null |
Item used for placement, null when spawned during world init |
| Property | Type | Description |
|---|---|---|
Position |
Point16 |
Tile coordinates |
PosInWorld |
Vector2 |
World pixel coordinates (read-only) |
CenterInWorld |
Vector2 |
World center pixel coordinates (read-only) |
Size |
Vector2 |
Size in pixels (read-only) |
HitBox |
Rectangle |
Collision rectangle (read-only) |
Width |
int |
Width in pixels (auto-fetched from TileObjectData) |
Height |
int |
Height in pixels (auto-fetched from TileObjectData) |
| Property/Field | Type | Default | Description |
|---|---|---|---|
DrawExtendMode |
int |
160 |
Draw extension range (pixels beyond screen still drawn) |
IdleDistance |
int |
-1 |
Idle distance (pixels), pauses updates beyond this, -1 disables |
| Property/Field | Type | Default | Description |
|---|---|---|---|
LoadenWorldSendData |
bool |
true |
Request network data when entering world |
PlaceNet |
bool |
false |
Broadcast network data when placed |
SendCooldownTicks |
int |
0 |
Send cooldown (>0 prevents sending) |
SendpacketCount |
int |
0 |
Packets sent this second (resets every 60 ticks) |
SendpacketPeak |
int |
10 |
Max packets per second |
The best way to understand a class is to read example code directly. Here is a TileProcessor example from InnoVaultExample
// We recommend adding the TP suffix to any TileProcessor subclass
// This TP entity will be associated with the vanilla Wooden Workbench
// Unlike TileEntity, we don't need to write any code in the Tile — TileProcessor is quite independent
internal class ExampleWorkbenchTP : TileProcessor
{
// Override this property to point to TileID.WorkBenches (vanilla workbench tile ID)
// This will auto-generate this TP entity when the player places a workbench
// All existing workbenches in the world will also get this TP entity automatically
public override int TargetTileID => TileID.WorkBenches;
private Vector2 Center => PosInWorld + new Vector2(16, 8);
private Color tpColor = Color.White;
private float light;
public override void SetProperty() {
tpColor = new Color(Main.rand.NextFloat(), Main.rand.NextFloat(), Main.rand.NextFloat());
light = 1 + Main.rand.NextFloat();
}
// This update function runs on all clients and server, called every frame
public override void Update() {
Lighting.AddLight(Center, tpColor.ToVector3() * light);
}
// This draw function lets you draw something interesting on the tile
public override void Draw(SpriteBatch spriteBatch) {
int itemType = (Main.mouseItem.IsAir ? Main.LocalPlayer.inventory[Main.LocalPlayer.selectedItem] : Main.mouseItem).type;
Vector2 orig = default;
if (itemType > ItemID.None) {
Main.instance.LoadItem(itemType);
Texture2D texture = TextureAssets.Item[itemType].Value;
Rectangle? rectangle = Main.itemAnimations[itemType] != null
? Main.itemAnimations[itemType].GetFrame(texture) : texture.Frame(1, 1, 0, 0);
orig = texture.Size() / 2;
if (rectangle.HasValue) {
orig = rectangle.Value.Size() / 2;
}
float gfkY = -16 - rectangle.Value.Height / 2 + MathF.Sin(Main.GameUpdateCount * 0.1f) * 4;
Vector2 drawPos = Center + new Vector2(0, gfkY);
drawPos -= Main.screenPosition;
spriteBatch.Draw(texture, drawPos, rectangle, Color.White, 0, orig, 1, SpriteEffects.None, 0);
}
}
}As mentioned in the comments, it's recommended to add the TP suffix to any TileProcessor entity class for easier identification.
public override int TargetTileID => TileID.WorkBenches;This example creates a TileProcessor entity attached to TileID.WorkBenches. This means every workbench tile in the world will have a dedicated entity attached.
Note:
TargetTileIDshould not return0, because0is the tile ID for Dirt and is also the return value whenModContent.TileType<T>()fails.
| Method | When Called | Environment | Description |
|---|---|---|---|
LoadenTile() |
When entity spawns | All sides | Loads tile data, auto-sets Width/Height |
SetProperty() |
When entity spawns | All sides | Initialize instance data |
Initialize() |
When added to world (once) | All sides | Initialize secondary data, basic data is already set |
| Method | When Called | Environment | Description |
|---|---|---|---|
Update() |
Every frame | All clients and server | Main logic update |
SingleInstanceUpdate() |
Once per update cycle | All sides | Called only once per type (requires instances in world) |
IsDaed() |
Every frame | Server/singleplayer only | Returns true to mark entity as dead |
| Method | When Called | Environment | Description |
|---|---|---|---|
RightClick(i, j, tile, player) |
When tile is right-clicked | All sides | Return null to continue vanilla events, otherwise block |
HoverTile() |
When mouse hovers | Local client only | Set cursor icons, etc. |
| Method | Draw Layer | Description |
|---|---|---|
PreTileDraw(SpriteBatch) |
Before tile drawing | Bottom-most layer |
BackDraw(SpriteBatch) |
Below Draw | Background layer |
Draw(SpriteBatch) |
Main draw | Primary draw layer |
FrontDraw(SpriteBatch) |
Above Draw | Foreground layer |
| Method | When Called | Environment | Description |
|---|---|---|---|
LoadInWorld() |
When world loads | All sides | Save data not loaded on client |
UnLoadInWorld() |
When world unloads | Server/singleplayer only | Cleanup |
ClientSaveAndQuit() |
Save and quit | Client only |
| Method | Description |
|---|---|
Kill() |
Marks dead and triggers OnKill(). Not recommended to call directly — should be triggered by destroying the tile |
CheckDeath() |
Check and properly handle death (recommended, with network adaptation) |
OnKill() |
Death event callback |
KillMultiTileSet(frameX, frameY) |
Callback when a multi-tile is mined |
The TP system has built-in multiplayer network synchronization:
internal class MyStorageTP : TileProcessor
{
public override int TargetTileID => ModContent.TileType<MyStorageTile>();
private int storedValue;
public MyStorageTP() {
// Request data from server when entering world
LoadenWorldSendData = true;
// Broadcast data to other clients when placed
PlaceNet = true;
}
// Send data to clients
public override void SendData(ModPacket data) {
data.Write(storedValue);
}
// Receive data from server
public override void ReceiveData(BinaryReader reader, int whoAmI) {
storedValue = reader.ReadInt32();
}
// Actively trigger send (e.g., after player interaction)
public override bool? RightClick(int i, int j, Tile tile, Player player) {
storedValue++;
SendData(); // Call the parameterless version to trigger network send
return true;
}
}| Method | Description |
|---|---|
SendData() |
Trigger network send (subject to cooldown and peak limits) |
SendData(ModPacket) |
Write data to send. Also called by server to sync state when new clients join |
ReceiveData(BinaryReader, int) |
Receive data. Must match SendData(ModPacket) read/write order |
NetCloneSend(ModPacket) |
Network clone send (basic fields like Active, WhoAmI, Position) |
NetCloneRead(BinaryReader) |
Network clone receive |
Important:
SaveDatais only called on the server side. To get persistent data on the client, send it viaSendData(ModPacket).
internal class MyPersistentTP : TileProcessor
{
public override int TargetTileID => ModContent.TileType<MyTile>();
private int savedCounter;
// Server-side only
public override void SaveData(TagCompound tag) {
tag["counter"] = savedCounter;
}
// Server-side only
public override void LoadData(TagCompound tag) {
savedCounter = tag.GetInt("counter");
}
// Sync save data to clients via network
public override void SendData(ModPacket data) {
data.Write(savedCounter);
}
public override void ReceiveData(BinaryReader reader, int whoAmI) {
savedCounter = reader.ReadInt32();
}
}For simple field synchronization, use the [SyncVar] attribute with SyncVarManager to simplify network code:
internal class MySyncTP : TileProcessor
{
[SyncVar] private int health;
[SyncVar] private float speed;
[SyncVar] private bool isActive;
public override void SendData(ModPacket data) {
SyncVarManager.Send(this, data);
}
public override void ReceiveData(BinaryReader reader, int whoAmI) {
SyncVarManager.Receive(this, reader);
}
}SyncVarManager supported types: int, float, bool, string, byte, short, long, double, Color, Vector2, Point16, Point, Item.
Inherit GlobalTileProcessor to modify the behavior of all TP entities:
public class MyGlobalTP : GlobalTileProcessor
{
// When entity initializes
public override void Initialize(TileProcessor tp) { }
// Before update, return false to prevent this entity from updating
public override bool PreUpdate(TileProcessor tp) => true;
// After update
public override void PostUpdate(TileProcessor tp) { }
// Before single-instance update (once per type per frame)
public override bool PreSingleInstanceUpdate(TileProcessor tp) => true;
// Before/after draw
public override bool PreDraw(TileProcessor tp, SpriteBatch sb) => true;
public override void PostDraw(TileProcessor tp, SpriteBatch sb) { }
// Before/after global draw (all instances)
public override bool PreDrawEverything(SpriteBatch sb) => true;
public override void PostDrawEverything(SpriteBatch sb) { }
// Death check override (return null to not affect original check)
public override bool? IsDaed(TileProcessor tp) => null;
// Death event
public override void OnKill(TileProcessor tp) { }
// Custom multi-structure tile origin point lookup
public override Point16? GetTopLeftPoint(int x, int y) => null;
public override bool? TryIsTopLeftPoint(int x, int y, out Point16 position) {
position = default;
return null;
}
}| Method/Property | Description |
|---|---|
TileProcessorLoader.TP_InWorld |
List of all active TP instances in the world |
TileProcessorLoader.ByPositionGetTP(i, j, out tp) |
Get TP instance by tile coordinates |
TileProcessorLoader.AddInWorld(ID, pos, trackItem) |
Manually add a TP instance to the world |
TileProcessorLoader.MaxTPInWorldCount |
Max TP count in world (32767) |
TileProcessorLoader.WorldLoadProgress |
World load progress (0-100) |
TileProcessor tp = Mod.FindTP<ExampleWorkbenchTP>();
TileProcessor tp2 = Mod.FindTP("ExampleWorkbenchTP");| Previous | Home | Next |
|---|---|---|
| RenderHandle Pipeline | Home | Basic UI |