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 #
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 #
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 #
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 #
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 #
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 #
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.
- Constants
- fn find_path
- fn load_entities
- fn new_custom_registry
- fn new_item_behaviour
- fn new_manager
- fn new_registry
- fn rand_int_range
- fn register_defaults
- fn save_entities
- interface Actor
- interface Behaviour
- interface DeathBehaviour
- interface Host
- interface HurtBehaviour
- interface Mergeable
- type BehaviourFactory
- struct CustomEntityDefinition
- struct CustomRegistry
- struct DespawnPolicy
- struct Dimensions
- struct Entity
- struct HostileBehaviour
- struct ItemBehaviour
- struct Manager
- struct PassiveBehaviour
- struct ProjectileBehaviour
- struct Registry
- struct SaveData