-
Notifications
You must be signed in to change notification settings - Fork 6
en Basic TP Entity
TileProcessor is an entity similar to TileEntity that attaches to tiles and performs independent logical updates and drawing updates, used for creating various advanced tile effects.
The differences from TileEntity:
-
More independent:
TileProcessoris a completely independent entity system — no need to write any special logic in Tile code -
Auto-mounting: Just specify
TargetTileID, and the system automatically creates TP entities for all matching tiles in the world - Built-in network sync: Provides a complete multiplayer data synchronization solution
-
Persistent storage: Supports
SaveData/LoadDatafor world save data persistence -
Multi-layer drawing: Provides four draw layers:
PreTileDraw,BackDraw,Draw,FrontDraw -
Performance optimization: Supports
IdleDistanceto automatically pause 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");