Skip to content

server.entity

Constants #

const custom_entity_runtime_id_start = 10000

Runtime ids for custom entity types start above the vanilla list so they never collide with it.

const natural_mob_despawn_policy = DespawnPolicy{
	distance:        true
	random_chance:   true
	inactivity:      true
	simulation_edge: true
}
const item_pickup_delay_ticks = i64(10)
const item_drop_pickup_delay_ticks = i64(40)
const item_existence_duration_ticks = i64(6000)
const path_max_search_radius = f32(32.0)

Pathfinding is intentionally bounded to keep per tick work predictable. path_max_search_radius limits how far a search may reach while path_max_nodes_expanded limits how much of that area it may explore.

Searches that exhaust either limit return no path. Callers then fall back to direct movement instead of leaving the entity stuck.

const path_max_nodes_expanded = 400
const path_max_safe_drop = 3

fn find_path #

fn find_path(mut host Host, start types.Vector3, target types.Vector3, dims Dimensions) ?[]types.Vector3

find_path searches for a bounded route from start to the target column. It returns block centered waypoints excluding the starting cell, or none when the start is not walkable, no route exists or a search limit is hit.

Entity height determines required headroom. Width is intentionally ignored, so wide entities may clip corners.

fn load_entities #

fn load_entities(dir string) []SaveData

load_entities returns the entities saved in dir. Missing, unreadable or invalid save files are treated as an empty entity list.

fn new_custom_registry #

fn new_custom_registry() CustomRegistry

fn new_item_behaviour #

fn new_item_behaviour(stack types.ItemStack, max_stack_size int, pickup_delay_ticks i64) &ItemBehaviour

fn new_manager #

fn new_manager(host Host) &Manager

fn new_registry #

fn new_registry() Registry

fn rand_int_range #

fn rand_int_range(min int, max int) int

rand_int_range returns a random value between min and max, inclusive. If max is not greater than min, it returns min.

fn register_defaults #

fn register_defaults(mut r Registry)

register_defaults registers the entity types Vedrock ships with.

Dimensions approximate each entity type's Bedrock hitbox closely enough for collision and hit testing, but are not treated as exact vanilla data. eye_height is currently unused for non-player entities and is included for future features that need an eye level position.

Wild mobs use natural_mob_despawn_policy. Persistent, named, tamed or boss entities can disable natural despawning with DespawnPolicy{}.

fn save_entities #

fn save_entities(dir string, entities []SaveData) !

save_entities atomically writes the entity list to dir, preserving the previous save if writing or renaming the temporary file fails.

interface Actor #

interface Actor {
	runtime_id() u64
	current_position() types.Vector3
	// feet_position returns the bottom of the actor's collision box.
	// Use it for collision and hit testing because current_position may refer
	// to eye level for players but already represents feet for entities.
	feet_position() types.Vector3
	dimensions() Dimensions
	is_dead() bool
}

Actor is the shared identity of players and non-player entities rendered in a world. It allows world scoped queries to use one registry while each concrete type keeps its own movement and tick behavior.

When narrowing an Actor to a concrete type, use the bare type name.

interface Behaviour #

interface Behaviour {
	identifier() string
	dimensions() Dimensions
	// despawn_policy defines which distance based despawn rules apply to this
	// entity. The zero value disables them all and is valid for persistent
	// entities or those that manage their own lifetime.
	despawn_policy() DespawnPolicy
	takes_fall_damage() bool
mut:
	tick(mut e Entity, mut host Host)
}

Behaviour drives an Entity's per tick logic. identifier() returns the network type id used when the entity is spawned for clients; tick() runs once per server tick before physics is applied, with Host access for querying and affecting the rest of the world. A Behaviour mutates the Entity directly (velocity, kill, etc.).

interface DeathBehaviour #

interface DeathBehaviour {
mut:
	on_death(mut e Entity, mut host Host)
}

DeathBehaviour is an opt-in capability notified once, right before an Entity that died from Entity.hurt is despawned , as opposed to Behaviour.tick calling kill() directly for other reasons (a projectile expiring, a wandering mob walking into the void). host is passed through so a mob can drop loot without the entity package needing to know numeric item ids itself (see Host.spawn_dropped_item).

interface Host #

interface Host {
mut:
	broadcast(p protocol.Packet)
	broadcast_near(x f32, y f32, z f32, radius f32, p protocol.Packet)
	allocate_runtime_id() u64
	get_block(x int, y int, z int) int
	collision_boxes(x int, y int, z int) []world.AABB
	// entity_position returns the current position of any live actor (player
	// or non player entity) the Host knows about or none if runtime_id no
	// longer exists. Lets a Behaviour target something outside the entity
	// Manager's own bookkeeping without the entity package
	// importing session.
	entity_position(runtime_id u64) ?types.Vector3
	// entity_hit_test returns the runtime id of the first live actor other
	// than any id in exclude_runtime_ids whose box contains pos or none.
	// Used for projectile vs entity/player collision.
	entity_hit_test(pos types.Vector3, exclude_runtime_ids []u64) ?u64
	// damage_entity applies damage to the actor at runtime_id (player or
	// entity), attributed to source_name/source_runtime_id, with
	// knockback_from as the origin used to compute knockback direction.
	damage_entity(runtime_id u64, amount f32, source_name string, source_runtime_id u64, knockback_from types.Vector3)
	// mob_attack applies a hostile mob's own melee attack to the actor at
	// runtime_id, attributed to source_name/source_runtime_id the same way
	// as damage_entity.
	mob_attack(runtime_id u64, amount f32, source_name string, source_runtime_id u64, knockback_from types.Vector3)
	// nearest_player returns the runtime id of the closest connected player
	// within radius of pos or none if nobody is that close. Used for
	// proactive mob targeting (HostileBehaviour scanning for a target it
	// hasn't been hit by yet).
	nearest_player(pos types.Vector3, radius f32) ?u64
	// has_line_of_sight reports whether no solid block obstructs the segment
	// between from and to. It uses sampled points rather than exact voxel
	// traversal and currently treats all blocks with collision boxes as opaque.
	has_line_of_sight(from types.Vector3, to types.Vector3) bool
	// notify_entity_despawn lets the entity package announce a despawn.
	notify_entity_despawn(identifier string, x f32, y f32, z f32)
	// spawn_dropped_item spawns count units of a registered item at pos.
	// Unknown items and non-positive counts are ignored; name to ID resolution
	// remains in the session item registry.
	spawn_dropped_item(item_name string, count int, pos types.Vector3)
	// collect_item adds as much of stack as possible to the player identified
	// by runtime_id and returns the number of units accepted. It returns 0 if
	// no live player is registered there or the inventory cannot take any.
	collect_item(runtime_id u64, stack types.ItemStack) int
	// notify_item_taken lets viewers see the pickup animation.
	notify_item_taken(item_runtime_id u64, taker_runtime_id u64)
}

Host is the slice of the server the entity Manager needs: world scoped broadcasting, collision queries and the shared runtime-id allocator. session.WorldEntityHost satisfies it for one world.

interface HurtBehaviour #

interface HurtBehaviour {
mut:
	on_hurt(mut e Entity, amount f32, source_runtime_id u64)
}

HurtBehaviour is an opt-in capability: a Behaviour implementing it is notified whenever its Entity survives a hit, letting e.g. a hostile mob start targeting whoever hit it.

interface Mergeable #

interface Mergeable {
	merge_radius() f32
	merge_stack() types.ItemStack
	stack_room() int
	mergeable_now(age i64) bool
mut:
	add_count(n int)
}

Mergeable allows an entity to combine with another compatible entity. merge_stack provides the stack to compare and update while mergeable_now prevents merging until the entity's pickup delay has elapsed.

type BehaviourFactory #

type BehaviourFactory = fn () Behaviour

BehaviourFactory builds a fresh Behaviour for a registered entity type. Each spawn gets its own instance so per-entity behaviour state stays isolated.

struct CustomEntityDefinition #

struct CustomEntityDefinition {
pub mut:
	id            string
	summonable    bool = true
	has_spawn_egg bool
	runtime_id    int
}

CustomEntityDefinition describes an entity type a plugin registers. The definition ends up in the AvailableActorIdentifiersPacket idlist so the client accepts AddActor packets with the custom identifier.

fn (CustomEntityDefinition) short_name #

fn (d &CustomEntityDefinition) short_name() string

short_name derives the registry name from the namespaced id, e.g. 'myplugin:fire_golem' -> 'fire_golem'.

struct CustomRegistry #

struct CustomRegistry {
mut:
	defs    []CustomEntityDefinition
	ids     map[string]int
	next_id int = custom_entity_runtime_id_start
}

CustomRegistry owns every registered custom entity definition and hands out runtime ids sequentially, starting at custom_entity_runtime_id_start.

fn (CustomRegistry) register #

fn (mut r CustomRegistry) register(def CustomEntityDefinition) bool

register allocates a runtime id for def and stores it, returning false when the id was already registered.

fn (CustomRegistry) all #

fn (r &CustomRegistry) all() []CustomEntityDefinition

fn (CustomRegistry) len #

fn (r &CustomRegistry) len() int

fn (CustomRegistry) names #

fn (r &CustomRegistry) names() []string

fn (CustomRegistry) identifiers_nbt #

fn (r &CustomRegistry) identifiers_nbt() nbt.RootTag

identifiers_nbt builds the AvailableActorIdentifiers root tag: an idlist of one compound per custom entity, in the format the client expects.

struct DespawnPolicy #

struct DespawnPolicy {
pub:
	distance        bool
	random_chance   bool
	inactivity      bool
	simulation_edge bool

	min_distance         f32 = 32.0
	max_distance         f32 = 128.0
	random_chance_one_in i64 = 800
	inactivity_ticks     i64 = 600
}

DespawnPolicy configures how an entity despawns naturally once players are far away, mirroring Bedrock's minecraft:despawn component: distance, random chance, inactivity and simulation-distance edge can each be switched on per entity type. The zero value turns everything off.

random_chance and inactivity only take effect once distance is also on - they refine the distance rule rather than triggering independently. simulation_edge isn't implemented yet.

struct Dimensions #

struct Dimensions {
pub:
	width         f32  = 0.6
	height        f32  = 1.8
	eye_height    f32  = 1.62
	step_height   f32  = 0.6
	has_collision bool = true
}

Dimensions describes an actor's collision box and related vertical offsets. Width and height are full dimensions.

step_height is reserved for future auto step behavior and is not yet used by entity physics.

struct Entity #

@[heap]
struct Entity {
pub:
	unique_id  i64
	runtime_id u64
	identifier string     // network type id, e.g. "minecraft:pig"
	dimensions Dimensions // physical footprint, fixed at spawn from the Behaviour that created this entity
pub mut:
	pos       types.Vector3
	velocity  types.Vector3
	pitch     f32
	yaw       f32
	head_yaw  f32
	floor_y   f32
	on_ground bool
	// fall_distance accumulates actual downward movement while airborne and
	// resets on landing - see apply_landing/fall_damage_amount below.
	fall_distance f32
	no_gravity    bool
	gravity_accel f32 = gravity
	drag_factor   f32 = drag
	hit_block     bool
	health        f32 = 20.0
	dead          bool
	age           i64
	// ticks_inactive counts ticks since this entity was last hurt.
	ticks_inactive i64
	behaviour      Behaviour
	effects        effect.Manager
}

Entity is a non-player actor living in the world - a mob, item or projectile. It owns shared state and delegates per-tick logic to its Behaviour. Players stay as NetworkSession; this system covers everything else the client renders as an actor.

fn (Entity) active_effects #

fn (e &Entity) active_effects() []effect.Effect

active_effects lists every effect currently active on e.

fn (Entity) add_effect #

fn (mut e Entity) add_effect(mut host Host, ef effect.Effect)

add_effect stores ef on e and syncs it to viewers.

fn (Entity) current_position #

fn (e &Entity) current_position() types.Vector3

current_position returns the entity's current position.

fn (Entity) despawn_packet #

fn (e &Entity) despawn_packet() &proto.RemoveActorPacket

despawn_packet builds the RemoveActorPacket that removes this entity from a viewer.

fn (Entity) dimensions #

fn (e &Entity) dimensions() Dimensions

dimensions satisfies entity.Actor.

fn (Entity) feet_position #

fn (e &Entity) feet_position() types.Vector3

feet_position satisfies entity.Actor. An Entity's pos is already its feet , so this is the same value as current_position().

fn (Entity) heal #

fn (mut e Entity) heal(mut host Host, amount f32)

fn (Entity) hurt #

fn (mut e Entity) hurt(mut host Host, amount f32, fatal bool, source_runtime_id u64)

fn (Entity) is_dead #

fn (e &Entity) is_dead() bool

is_dead reports whether the entity is scheduled for removal.

fn (Entity) kill #

fn (mut e Entity) kill()

kill marks the entity dead. The Manager removes and despawns it on the next tick.

fn (Entity) move_packet #

fn (e &Entity) move_packet() &proto.MoveActorAbsolutePacket

move_packet builds the movement update broadcast each tick the entity moves.

fn (Entity) remove_effect #

fn (mut e Entity) remove_effect(mut host Host, typ effect.Type)

remove_effect strips typ from e, if present, and tells viewers.

fn (Entity) runtime_id #

fn (e &Entity) runtime_id() u64

runtime_id satisfies entity.Actor.

fn (Entity) set_velocity #

fn (mut e Entity) set_velocity(v types.Vector3)

set_velocity replaces the entity's velocity (blocks/tick).

fn (Entity) spawn_packet #

fn (e &Entity) spawn_packet() protocol.Packet

spawn_packet builds the packet that makes this entity appear for a viewer. Public so the session layer can send it to players joining late.

fn (Entity) teleport #

fn (mut e Entity) teleport(pos types.Vector3)

teleport moves the entity to pos and resets its ground clamp there.

struct HostileBehaviour #

@[heap]
struct HostileBehaviour {
pub mut:
	network_id       string
	detection_radius f32 = 16.0
	attack_damage    f32 = 3.0
	dimensions       Dimensions
	despawn_policy   DespawnPolicy
mut:
	wander_cooldown         i64 = wander_interval_ticks
	scan_cooldown           i64
	target_runtime_id       u64
	has_target              bool
	path                    []types.Vector3
	path_index              int
	path_recompute_cooldown i64
	attack_cooldown         i64
	los_lost_ticks          i64
}

HostileBehaviour wanders until it detects a visible player or is attacked, then chases and attacks that target. It gives up when the target is gone, too far away or out of sight for too long.

Sound based detection and group aggression are not yet supported.

fn (HostileBehaviour) identifier #

fn (b &HostileBehaviour) identifier() string

fn (HostileBehaviour) dimensions #

fn (b &HostileBehaviour) dimensions() Dimensions

fn (HostileBehaviour) despawn_policy #

fn (b &HostileBehaviour) despawn_policy() DespawnPolicy

fn (HostileBehaviour) takes_fall_damage #

fn (b &HostileBehaviour) takes_fall_damage() bool

fn (HostileBehaviour) tick #

fn (mut b HostileBehaviour) tick(mut e Entity, mut host Host)

fn (HostileBehaviour) on_hurt #

fn (mut b HostileBehaviour) on_hurt(mut e Entity, amount f32, source_runtime_id u64)

on_hurt makes the mob start chasing whoever/whatever just hit it. Plain PassiveBehaviour mobs don't implement HurtBehaviour, so being attacked never gives them a target.

fn (HostileBehaviour) on_death #

fn (mut b HostileBehaviour) on_death(mut e Entity, mut host Host)

on_death drops this mob's loot table (see mob_loot_drops) at its death position.

struct ItemBehaviour #

@[heap]
struct ItemBehaviour {
pub mut:
	stack              types.ItemStack
	max_stack_size     int = 64
	pickup_delay_ticks i64 = item_pickup_delay_ticks
}

ItemBehaviour represents a dropped item stack. It falls, becomes collectible and mergeable after its pickup delay, and despawns when its lifetime expires.

fn (ItemBehaviour) identifier #

fn (b &ItemBehaviour) identifier() string

fn (ItemBehaviour) dimensions #

fn (b &ItemBehaviour) dimensions() Dimensions

fn (ItemBehaviour) despawn_policy #

fn (b &ItemBehaviour) despawn_policy() DespawnPolicy

fn (ItemBehaviour) takes_fall_damage #

fn (b &ItemBehaviour) takes_fall_damage() bool

fn (ItemBehaviour) tick #

fn (mut b ItemBehaviour) tick(mut e Entity, mut host Host)

fn (ItemBehaviour) merge_radius #

fn (b &ItemBehaviour) merge_radius() f32

fn (ItemBehaviour) merge_stack #

fn (b &ItemBehaviour) merge_stack() types.ItemStack

fn (ItemBehaviour) stack_room #

fn (b &ItemBehaviour) stack_room() int

fn (ItemBehaviour) mergeable_now #

fn (b &ItemBehaviour) mergeable_now(age i64) bool

fn (ItemBehaviour) add_count #

fn (mut b ItemBehaviour) add_count(n int)

struct Manager #

@[heap]
struct Manager {
mut:
	mutex  &sync.Mutex = sync.new_mutex()
	actors map[u64]ActorEntry
	host   Host
}

Manager stores every live actor in a world, keyed by runtime ID.

The mutex protects registry membership only. Live entity pointers must remain on the owning world thread; cross thread callers must copy plain values through a world task.

fn (Manager) actor_by_runtime_id #

fn (mut m Manager) actor_by_runtime_id(runtime_id u64) ?Actor

actor_by_runtime_id returns the player or non-player entity registered under the runtime ID. Use it only when both actor kinds are valid.

fn (Manager) all_actors #

fn (mut m Manager) all_actors() []Actor

all_actors returns every registered actor, mob and player alike.

fn (Manager) by_runtime_id #

fn (mut m Manager) by_runtime_id(runtime_id u64) ?&Entity

by_runtime_id returns the live entity with runtime_id or none if it does not exist or belongs to a player.

fn (Manager) count #

fn (mut m Manager) count() int

count reports how many mob entities are alive.

fn (Manager) damage #

fn (mut m Manager) damage(runtime_id u64, amount f32, fatal bool, mut host Host, source_runtime_id u64)

fn (Manager) deregister_player_actor #

fn (mut m Manager) deregister_player_actor(runtime_id u64)

deregister_player_actor removes a player actor entry.

fn (Manager) despawn #

fn (mut m Manager) despawn(runtime_id u64)

despawn removes the entity with runtime_id and tells viewers to drop it.

fn (Manager) is_player_actor #

fn (mut m Manager) is_player_actor(runtime_id u64) bool

is_player_actor reports whether the runtime ID belongs to a registered player. PlayerMoveTask uses it without an epoch check so stale tasks can still resolve the session and clear movement_scheduled.

fn (Manager) player_actor_count #

fn (mut m Manager) player_actor_count() i64

player_actor_count reports how many players are registered.

fn (Manager) player_actor_for_epoch #

fn (mut m Manager) player_actor_for_epoch(runtime_id u64, epoch i64) ?Actor

player_actor_for_epoch resolves a player actor and rejects a stale or missing registration before any side effects occur.

fn (Manager) player_actors #

fn (mut m Manager) player_actors() []Actor

player_actors returns every registered player actor.

fn (Manager) register_player_actor #

fn (mut m Manager) register_player_actor(a Actor, runtime_id u64, epoch i64)

register_player_actor adds or replaces a player actor entry.

fn (Manager) restore_from_save #

fn (mut m Manager) restore_from_save(reg &Registry, saved []SaveData)

restore_from_save recreates saved entities whose types are still registered. Unknown types are skipped without failing the world load.

This must run on the owning world thread because live entity pointers are confined to that thread.

fn (Manager) save_snapshot #

fn (mut m Manager) save_snapshot() []SaveData

save_snapshot returns the persistent state of all non-transient entities. Projectiles and dropped items are excluded because their short lived state is not restored across restarts.

fn (Manager) snapshot #

fn (mut m Manager) snapshot() []&Entity

snapshot returns a stable slice of live mob entity pointers, excluding registered players.

fn (Manager) spawn #

fn (mut m Manager) spawn(behaviour Behaviour, pos types.Vector3) &Entity

spawn creates an entity driven by behaviour at pos, registers it and broadcasts its appearance to all viewers. Returns the live Entity.

fn (Manager) tick #

fn (mut m Manager) tick()

tick advances every entity one server tick: run its Behaviour, apply physics, remove the dead, and broadcast movement for the ones that moved.

struct PassiveBehaviour #

@[heap]
struct PassiveBehaviour {
pub mut:
	network_id     string
	dimensions     Dimensions
	despawn_policy DespawnPolicy
mut:
	wander_cooldown i64 = wander_interval_ticks
}

PassiveBehaviour wanders in a new random direction roughly every wander_interval_ticks while grounded and otherwise does nothing. Physics still applies, so the entity falls to floor_y and rests there between wanders.

fn (PassiveBehaviour) identifier #

fn (b &PassiveBehaviour) identifier() string

fn (PassiveBehaviour) dimensions #

fn (b &PassiveBehaviour) dimensions() Dimensions

fn (PassiveBehaviour) despawn_policy #

fn (b &PassiveBehaviour) despawn_policy() DespawnPolicy

fn (PassiveBehaviour) takes_fall_damage #

fn (b &PassiveBehaviour) takes_fall_damage() bool

fn (PassiveBehaviour) on_death #

fn (mut b PassiveBehaviour) on_death(mut e Entity, mut host Host)

on_death drops this mob's loot table (see mob_loot_drops) at its death position.

fn (PassiveBehaviour) tick #

fn (mut b PassiveBehaviour) tick(mut e Entity, mut host Host)

struct ProjectileBehaviour #

@[heap]
struct ProjectileBehaviour {
pub mut:
	network_id              string
	max_age                 i64 = 100
	damage                  f32
	gravity_accel           f32 = gravity
	drag_factor             f32 = drag
	survive_block_collision bool
	dimensions              Dimensions
	owner_runtime_id        u64
	flying_despawn_policy   DespawnPolicy = DespawnPolicy{
		distance:      true
		random_chance: true
	}
mut:
	stuck     bool
	stuck_age i64
}

ProjectileBehaviour flies with its initial velocity, deals damage to the first entity or player its path touches and either despawns on its first block collision (survive_block_collision: false, e.g. a snowball) or freezes in place there until max_age (survive_block_collision: true, e.g. an arrow).

owner_runtime_id identifies the entity that fired this projectile. The owner is ignored during hit detection only for owner_immunity_ticks after spawn, not for the projectile's whole lifetime.

fn (ProjectileBehaviour) identifier #

fn (b &ProjectileBehaviour) identifier() string

fn (ProjectileBehaviour) dimensions #

fn (b &ProjectileBehaviour) dimensions() Dimensions

fn (ProjectileBehaviour) despawn_policy #

fn (b &ProjectileBehaviour) despawn_policy() DespawnPolicy

despawn_policy changes with projectile state. While flying, the configured distance policy applies because no other lifetime rule can remove a projectile that never lands. Since this engine doesn't cull entities when chunks unload, distance despawning fills that role.

Once stuck, distance despawning is disabled and stuck_age/max_age becomes the sole authority for removal. This is intentionally not configurable per instance, preventing distance rules from interfering with the stuck projectile lifetime.

fn (ProjectileBehaviour) takes_fall_damage #

fn (b &ProjectileBehaviour) takes_fall_damage() bool

fn (ProjectileBehaviour) tick #

fn (mut b ProjectileBehaviour) tick(mut e Entity, mut host Host)

struct Registry #

struct Registry {
mut:
	factories map[string]BehaviourFactory
}

Registry maps short type names (e.g. 'pig') to Behaviour factories. It is the lookup /summon and plugins use to spawn entities by name.

fn (Registry) register #

fn (mut r Registry) register(name string, factory BehaviourFactory)

register adds a named entity type. Re-registering a name overwrites it.

fn (Registry) create #

fn (r &Registry) create(name string) ?Behaviour

create builds a new Behaviour for name, or none if the type is unknown.

fn (Registry) names #

fn (r &Registry) names() []string

names lists every registered type name.

struct SaveData #

struct SaveData {
pub mut:
	type_name  string // Registry key (e.g. "zombie") used to reconstruct the Behaviour via Registry.create
	x          f32
	y          f32
	z          f32
	velocity_x f32
	velocity_y f32
	velocity_z f32
	pitch      f32
	yaw        f32
	head_yaw   f32
	health     f32
}

SaveData contains the persistent state needed to recreate an entity. Temporary Behaviour state such as targets, cooldowns and paths is rebuilt after loading and is intentionally not stored.

Projectiles are excluded from persistence by save_snapshot.