367 lines
11 KiB
Lua
367 lines
11 KiB
Lua
--- The Common Mob Interface
|
|
-- @module cmi
|
|
-- @author raymoo
|
|
|
|
cmi = {}
|
|
|
|
--- Types.
|
|
-- The various data structures used in the API.
|
|
-- @section types
|
|
|
|
--- Object Identifiers.
|
|
-- @string type Either "player" or "mob"
|
|
-- @string identifier For players, is a player name. For mobs, is a unique ID.
|
|
-- @table Id
|
|
|
|
--- Punch callbacks.
|
|
-- @tparam ObjectRef mob
|
|
-- @tparam ?ObjectRef hitter
|
|
-- @number time_from_last_punch
|
|
-- @tab tool_capabilities
|
|
-- @tparam ?vector dir
|
|
-- @number damage
|
|
-- @tparam ?Id attacker Any indirect owner of the punch, for example a
|
|
-- player who fired an arrow.
|
|
-- @function PunchCallback
|
|
|
|
--- Reasons a mob could die.
|
|
-- The type field determines what kind of cause a @{DeathCause} is. It can be one
|
|
-- of those specified here, or a custom one provided by a mod. For custom types,
|
|
-- the fields should be specified by the mod introducing it.
|
|
-- @string type The predefined types are "punch" and "environment".
|
|
-- @tparam ?ObjectRef puncher If type == "punch", contains the puncher. The
|
|
-- puncher can be nil.
|
|
-- @tparam ?Id attacker If type == "punch", contains the attacker if it exists
|
|
-- and is known.
|
|
-- @tparam ?vector pos If type == "environment", is the position of the damaging
|
|
-- node.
|
|
-- @tparam ?node node If type == "environment", describes the damaging node.
|
|
-- @table DeathCause
|
|
|
|
--- Death callbacks.
|
|
-- @tparam ObjectRef mob the dying mob
|
|
-- @tparam DeathCause cause cause of death
|
|
-- @function DeathCallback
|
|
|
|
--- Activation callbacks.
|
|
-- @tparam ObjectRef mob the mob being activated
|
|
-- @function ActivationCallback
|
|
|
|
--- Step callbacks.
|
|
-- @tparam ObjectRef mob
|
|
-- @number dtime
|
|
-- @function StepCallback
|
|
|
|
--- Component definitions.
|
|
-- @string name a unique name for the component, prefixed with the mod name
|
|
-- @func initialize a function taking no arguments and returning a new instance
|
|
-- of the data
|
|
-- @func serialize a function taking your component's data as an input and
|
|
-- returning it serialized as a string
|
|
-- @func deserialize a function taking the serialized form of your data and
|
|
-- turning it back into the original data
|
|
-- @table ComponentDef
|
|
|
|
-- Returns a table and the registration callback for it
|
|
local function make_callback_table()
|
|
local callbacks = {}
|
|
local function registerer(entry)
|
|
table.insert(callbacks, entry)
|
|
end
|
|
|
|
return callbacks, registerer
|
|
end
|
|
|
|
-- Returns a notification function
|
|
local function make_notifier(cb_table)
|
|
return function(...)
|
|
for i, cb in ipairs(cb_table) do
|
|
cb(...)
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Callback Registration.
|
|
-- Functions for registering mob callbacks.
|
|
-- @section callbacks
|
|
|
|
--- Register a callback to be run when a mob is punched.
|
|
-- @tparam PunchCallback func
|
|
-- @function register_on_punchmob
|
|
local punch_callbacks
|
|
punch_callbacks, cmi.register_on_punchmob = make_callback_table()
|
|
|
|
--- Register a callback to be run when a mob dies.
|
|
-- @tparam DeathCallback func
|
|
-- @function register_on_diemob
|
|
local die_callbacks
|
|
die_callbacks, cmi.register_on_diemob = make_callback_table()
|
|
|
|
--- Register a callback to be run when a mob is activated.
|
|
-- @tparam ActivationCallback func
|
|
-- @function register_on_activatemob
|
|
local activate_callbacks
|
|
activate_callbacks, cmi.register_on_activatemob = make_callback_table()
|
|
|
|
--- Register a callback to be run on mob step.
|
|
-- @tparam StepCallback func
|
|
-- @function register_on_stepmob
|
|
local step_callbacks
|
|
step_callbacks, cmi.register_on_stepmob = make_callback_table()
|
|
|
|
--- Querying.
|
|
-- Functions for getting information about mobs.
|
|
-- @section misc
|
|
|
|
-- Wraps an entity-accepting function to accept entities or ObjectRefs.
|
|
local function on_entity(name, func)
|
|
return function(object, ...)
|
|
local o_type = type(object)
|
|
|
|
-- luaentities are tables
|
|
if o_type == "table" then
|
|
return func(object, ...)
|
|
end
|
|
|
|
if o_type == "userdata" then
|
|
local ent = object:get_luaentity()
|
|
return ent and func(ent, ...)
|
|
end
|
|
|
|
-- If no error, it's still possible that the input was bad.
|
|
error("Non-luaentity Non-ObjectRef input to" .. name)
|
|
end
|
|
end
|
|
|
|
-- Same as on_entity but for ObjectRefs
|
|
local function on_object(name, func)
|
|
return on_entity(function(ent, ...)
|
|
return func(ent.object, ...)
|
|
end)
|
|
end
|
|
|
|
--- Checks if an object is a mob.
|
|
-- @tparam ObjectRef|luaentity object
|
|
-- @treturn bool true if the object is a mob, otherwise returns a falsey value
|
|
-- @function is_mob
|
|
cmi.is_mob = on_entity("is_mob", function(ent)
|
|
return ent.cmi_is_mob
|
|
end)
|
|
|
|
--- Gets a player-readable mob name.
|
|
-- @tparam ObjectRef|luaentity object
|
|
-- @treturn string
|
|
-- @function get_mob_description
|
|
cmi.get_mob_description = on_entity("get_mob_description", function(mob)
|
|
local desc = mob.description
|
|
if desc then return desc end
|
|
|
|
local name = mob.name
|
|
local colon_pos = string.find(name, ":")
|
|
if colon_pos then
|
|
return string.sub(name, colon_pos + 1)
|
|
else
|
|
return name
|
|
end
|
|
end)
|
|
|
|
--- Health-related.
|
|
-- Functions related to hurting or healing mobs.
|
|
-- @section health
|
|
|
|
--- Attack a mob.
|
|
-- Functions like the punch method of ObjectRef, but takes an additional optional
|
|
-- argument for an indirect attacker. Also works on non-mob entities that define
|
|
-- an appropriate cmi_attack method.
|
|
-- @tparam ObjectRef|luaentity mob
|
|
-- @tparam ObjectRef puncher
|
|
-- @number time_from_last_punch
|
|
-- @tab tool_capabilities
|
|
-- @tparam vector direction
|
|
-- @tparam ?Id attacker An indirect owner of the punch. For example, the player
|
|
-- who fired an arrow that punches the mob.
|
|
-- @function attack
|
|
local function attack_ent(mob, puncher, time_from_last_punch, tool_capabilities,
|
|
direction, attacker)
|
|
|
|
-- It's a method in the mob but I don't want to index it twice
|
|
local atk = mob.cmi_attack
|
|
if not atk then mob.object:punch(puncher, time_from_last_punch,
|
|
tool_capabilities, direction)
|
|
else
|
|
atk(mob, puncher, time_from_last_punch, tool_capabilities,
|
|
direction, attacker)
|
|
end
|
|
end
|
|
cmi.attack = on_entity("attack", attack_ent)
|
|
|
|
local function bound(x, minb, maxb)
|
|
if x < minb then
|
|
return minb
|
|
elseif x > maxb then
|
|
return maxb
|
|
else
|
|
return x
|
|
end
|
|
end
|
|
|
|
--- Punch damage calculator.
|
|
-- By default, this just calculates damage in the vanilla way. Switch it out for
|
|
-- something else to change the default damage mechanism for mobs.
|
|
-- @tparam ObjectRef mob
|
|
-- @tparam ?ObjectRef puncher
|
|
-- @tparam number time_from_last_punch
|
|
-- @tparam table tool_capabilities
|
|
-- @tparam ?vector direction
|
|
-- @tparam ?Id attacker
|
|
function cmi.damage_calculator(mob, puncher, tflp, caps, direction, attacker)
|
|
local a_groups = mob:get_armor_groups() or {}
|
|
local full_punch_interval = caps.full_punch_interval or 1.4
|
|
local time_prorate = bound(tflp / full_punch_interval, 0, 1)
|
|
|
|
local damage = 0
|
|
for group, damage_rating in pairs(caps.damage_groups or {}) do
|
|
local armor_rating = a_groups[group] or 0
|
|
damage = damage + damage_rating * (armor_rating / 100)
|
|
end
|
|
|
|
return math.floor(damage * time_prorate)
|
|
end
|
|
|
|
--- Components.
|
|
-- Components are data stored in a mob, that every mob is guaranteed to contain.
|
|
-- You can use them in conjunction with callbacks to extend mobs with new
|
|
-- functionality, without explicit support from mob mods.
|
|
-- @section components
|
|
|
|
--- Register a mob component.
|
|
-- @tparam ComponentDef component_def
|
|
-- @function register_component
|
|
local component_defs
|
|
component_defs, cmi.register_component = make_callback_table()
|
|
|
|
--- Get a component from a mob.
|
|
-- @tparam mob ObjectRef|luaentity mob
|
|
-- @string component_name
|
|
-- @function get_mob_component
|
|
cmi.get_mob_component = on_entity("get_mob_component", function(mob, c_name)
|
|
return mob.cmi_components.components[c_name]
|
|
end)
|
|
|
|
--- Set a component in a mob.
|
|
-- @tparam mob ObjectRef|luaentity mob
|
|
-- @string component_name
|
|
-- @param new_value
|
|
-- @function set_mob_component
|
|
cmi.set_mob_component = on_entity("set_mob_component", function(mob, c_name, new)
|
|
mob.cmi_components.components[c_name] = new
|
|
end)
|
|
|
|
--- Implementation: event notification.
|
|
-- Functions used to notify CMI when things happen to your mob. Only necessary
|
|
-- when you are implementing the interface.
|
|
-- @section impl_events
|
|
|
|
--- Notify CMI that your mob has been punched.
|
|
-- Call this before doing any punch handling that is not "read-only".
|
|
-- @tparam ObjectRef mob
|
|
-- @tparam ?ObjectRef hitter
|
|
-- @number time_from_last_punch
|
|
-- @tab tool_capabilities
|
|
-- @tparam ?vector dir
|
|
-- @number damage
|
|
-- @tparam ?Id attacker
|
|
-- unknown.
|
|
-- @return Returns true if punch handling should be aborted.
|
|
-- @function notify_punch
|
|
cmi.notify_punch = make_notifier(punch_callbacks)
|
|
|
|
--- Notify CMI that your mob has died.
|
|
-- Call this right before calling remove.
|
|
-- @tparam ObjectRef mob the dying mob
|
|
-- @tparam DeathCause cause cause of death
|
|
-- @function notify_die
|
|
cmi.notify_die = make_notifier(die_callbacks)
|
|
|
|
--- Notify CMI that your mob has been activated.
|
|
-- Call this after all other mob initialization.
|
|
-- @tparam ObjectRef mob the mob being activated
|
|
-- @function notify_activate
|
|
cmi.notify_activate = make_notifier(activate_callbacks)
|
|
|
|
--- Notify CMI that your mob is taking a step.
|
|
-- Call this on every step. It is suggested to call it before or after all other
|
|
-- processing, to avoid logic errors caused by callbacks handling the same state
|
|
-- as your entity's normal step logic.
|
|
-- @tparam ObjectRef mob
|
|
-- @number dtime
|
|
-- @function notify_step
|
|
cmi.notify_step = make_notifier(step_callbacks)
|
|
|
|
--- Implementation: components.
|
|
-- Functions related to implementing entity components. Only necessary when you
|
|
-- are implementing the interface.
|
|
-- @section impl_components
|
|
|
|
--- Activates component data.
|
|
-- On mob activation, call this and put the result in the cmi_components field of
|
|
-- its luaentity.
|
|
-- @tparam ?string serialized_data the serialized form of the string, if
|
|
-- available. If the mob has never had component data, do not pass this argument.
|
|
function cmi.activate_components(serialized)
|
|
local serial_table = serialized and minetest.parse_json(serialized) or {}
|
|
|
|
local components = {}
|
|
for i = 1, #component_defs do
|
|
local def = component_defs[i]
|
|
local name = def.name
|
|
local serialized = serial_table[name]
|
|
components[name] = serialized
|
|
and def.deserialize(serialized)
|
|
or def.initialize()
|
|
end
|
|
|
|
return {
|
|
components = components,
|
|
old_serialization = serial_table,
|
|
}
|
|
end
|
|
|
|
--- Serialized component data.
|
|
-- When serializing your mob data, call this and put the result somewhere safe,
|
|
-- where it can be retrieved on activation to be passed to
|
|
-- #{activate_components}.
|
|
-- @param component_data
|
|
function cmi.serialize_components(component_data)
|
|
local serial_table = component_data.old_serialization
|
|
local components = component_data.components
|
|
|
|
for i = 1, #component_defs do
|
|
local def = compnent_defs[i]
|
|
local name = def.name
|
|
local component = components[name]
|
|
serial_table[name] = def.serialize(component)
|
|
end
|
|
|
|
return minetest.write_json(serial_table)
|
|
end
|
|
|
|
--- Implementation: health
|
|
-- Functions related to health that are needed for implementation of the
|
|
-- interface. Only necessary if you are implementing the interface.
|
|
-- @section impl_damage
|
|
|
|
--- Calculate damage
|
|
-- Use this function when you want to calculate the "default" damage. If you
|
|
-- are a modder who wants to switch out the damage mechanism, do not replace
|
|
-- this function. Replace #{damage_calculator} instead.
|
|
-- @tparam ObjectRef mob
|
|
-- @tparam ?ObjectRef puncher
|
|
-- @tparam number time_from_last_punch
|
|
-- @tparam table tool_capabilities
|
|
-- @tparam ?vector direction
|
|
-- @tparam ?Id attacker
|
|
function cmi.calculate_damage(...)
|
|
return cmi.damage_calculator(...)
|
|
end
|