Entity gameplay
Lesson 12.7
Entities are more than a position and a model: they find each other, get stronger or weaker, hurt each other, spawn on their own and ride each other. This lesson does all of that, on your own types and on vanilla mobs alike.
Find entities
local world = player.level
local near = world:entities_near(player.pos, 10) -- everything within 10 blocks
local zombies = world:entities_near(player.pos, 20, { type = "minecraft:zombie" })
local undead = world:entities_near(player.pos, 20, { type = "#minecraft:undead", nearest = true, limit = 3 })
local animals = world:entities_near(player.pos, 20, { category = "creature" })
local other = world:nearest_player(player.pos, 32) -- nil if nobody is that close
entities_near looks in a cube around a position; entities_in_box(min, max, query) takes two corners. The query can also filter by tag (tags you give with e:add_tag("boss") or /tag) and leave one entity out with exclude = me. cl.entity.by_uuid(text) finds an entity again from a UUID you saved (e.uuid).
Stronger and weaker
Attributes are the numbers behind a mob: max_health, movement_speed, attack_damage, armor, scale and many more.
print(zombie:attribute("movement_speed")) -- 0.23
zombie:add_attribute_modifier("movement_speed", "haste", 0.5, "add_multiplied_base") -- 50% faster
zombie:remove_attribute_modifier("movement_speed", "haste")
zombie:set_attribute_base("max_health", 40)
A modifier has a name ("haste" becomes "my_mod:haste"), so several can stack and you can take one back later. Effects are the potion effects:
zombie:add_effect("minecraft:speed", 200, 1) -- Speed II for 10 seconds (200 ticks)
zombie:add_effect("glowing", -1) -- forever
if zombie:has_effect("speed") then zombie:remove_effect("speed") end
Tell the clients
The server knows things the client’s code would like to know: how angry a boss is, how much a charge is filled. set_synced sends a small value along with the entity:
-- on the server
boss:set_synced("anger", 3)
-- on a client
cl.on("entity.synced", function(ev)
cl.log("anger is now", ev.entity:synced("anger"))
end, { filter = "my_mod:anger" })
Only Celestial players with your mod get it; it is saved with the entity.
Hurt
target:hurt(4, "minecraft:magic") -- 4 damage (2 hearts) of magic
target:hurt(6, "minecraft:mob_attack", boss) -- boss gets the credit
target:hurt(2, "minecraft:generic", { attacker = boss, knockback = 0 }) -- no push
Armor, enchantments, shields and totems all work like a real hit. Your own damage type gets its own death message:
cl.on("lifecycle.register", function()
cl.damage_types.register{ id = "zap", death_message = "%1$s was zapped", death_message_player = "%1$s was zapped by %2$s" }
end)
To stop a mob from being pushed around, cancel its knockback:
cl.on("entity.knockback", function(ev) ev:cancel() end, { filter = "my_mod:golem" })
Spawn on their own
cl.on("lifecycle.register", function()
cl.registry.spawn_rule{ type = "golem", category = "monster", biomes = { "minecraft:dark_forest" }, weight = 10, max_light = 7 }
end)
Now golems spawn in dark forests at night like any monster, counted in the monster cap.
Ride
horse:set_passenger(zombie) -- or zombie:ride(horse)
print(zombie:vehicle()) -- the horse
zombie:dismount()
With a model on the vehicle the rider can sit on a bone: boss:set_passenger(rider, { bone = "back" }).
Equipment
What a mob or player wears and holds (API 1.20):
zombie:set_equipment("head", "minecraft:diamond_helmet")
zombie:set_drop_chance("head", 1) -- always drops it
print(zombie:equipment("head"):is("minecraft:diamond_helmet")) --> true
zombie:set_equipment("mainhand", nil) -- empty hand
Slots: "mainhand", "offhand", "feet", "legs", "chest", "head", "body" (horse and wolf armour) and "saddle".
Spawning, using, travelling
Three events tell you about entities (API 1.20):
cl.on("entity.spawn", function(e) e:cancel() end, { filter = "minecraft:phantom" }) -- no phantoms at all
cl.on("player.interact_entity", function(e)
e.player:send_message("You clicked a villager")
e:cancel() -- no trading screen
end, { filter = "minecraft:villager" })
cl.on("entity.change_dimension", function(e)
print(e.entity.name, "is now in", e.to:dimension())
end)
entity.spawn comes once an entity is placed in a level, also when it was loaded with its chunk (e.loaded), before players see it.
Sparks on a mob
An emitter spawns particles on an entity every few ticks, at its feet or at a bone of its model:
local em = zombie:emit("minecraft:flame", { every = 2, offset = { 0, 2, 0 }, count = 1 })
em:pause()
em:resume()
em:remove()
Try it
Make the command /bully: every zombie within 16 blocks of you gets Strength I for 30 seconds, and the nearest one gets twice as fast until it is hit by a player.
Show a solution
cl.on("server.command_register", function()
cl.cmd.register(cl.cmd.literal("bully"):executes(function(ctx)
local p = ctx:player()
if not p then return 0 end
local zs = ctx:level():entities_near(p.pos, 16, { type = "minecraft:zombie", nearest = true })
for _, z in ipairs(zs) do z:add_effect("strength", 600) end
if zs[1] then zs[1]:add_attribute_modifier("movement_speed", "bully", 1.0, "add_multiplied_total") end
return #zs
end))
end)
cl.on("entity.damage", function(ev)
if ev.attacker and ev.attacker:is_player() and ev.entity:has_attribute_modifier("movement_speed", "bully") then
ev.entity:remove_attribute_modifier("movement_speed", "bully")
end
end, { filter = "minecraft:zombie" })
ctx:level() is the level the command runs in, nearest = true puts the closest zombie first.
Recap: entities_near / entities_in_box / nearest_player find entities, attributes and effects change them, set_synced tells clients, hurt damages through the vanilla pipeline, cl.damage_types and cl.registry.spawn_rule add damage types and natural spawning, set_passenger makes them ride. More in the guide Entity Gameplay.