Everything

This commit is contained in:
raymoo 2016-09-01 20:16:16 -07:00
commit 8e8a39fbfe
7 changed files with 500 additions and 0 deletions

88
IMPLEMENTING.md Normal file
View File

@ -0,0 +1,88 @@
This document explains how to implement the common mob interface in your mob mod.
It also serves as a reference for developing the interface itself.
luaentity fields
================
CMI requires mobs to add some fields to their luaentities.
Required
--------
#### cmi_is_mob
This must be a single boolean field set to `true` at all times.
#### cmi_components
This must contain active component data. See the components section.
Optional
--------
#### description
If implemented, this must be a string field with a player-readable mob name,
like "chicken" or "zombie". If absent, CMI will use the part of the entity name
after the colon.
#### cmi_attack
If implemented, this must be a method like punch, except taking another parameter
for an Attacker (see the usage documentation). Note that since this is a method
of the luaentity and not of the ObjectRef, the first argument will be a
luaentity, not an ObjectRef. This method must handle nil attackers. If absent,
CMI will default to using ordinary entity punches, throwing away the attacker
information.
Event Notification
==================
CMI requires mobs to notify it when certain things happen to the mob.
Required
--------
#### Punches
When your mob is punched, it must call notify_punch to signal that it got
punched (see the usage documentation). If attacker information is available, it
should be passed as the appropriate parameter. To be clear, attacker information
is always "indirect" attacker information. Generally you should only pass it when
it is passed through the optional attack method described earlier. If
notify_punch returns true, it means the punch was handled specially and you
should abort punch handling. You should notify_punch after damage calculation,
but before doing anything else.
#### Death
When your mob dies, it must call notify_die to signal that it died (see the usage
documentation). A cause of death should be passed if known.
#### Activation
When your mob is activated, it must call notify_activate. You should call it
after any other mob initialization that may change the mob's state.
#### Step
Your mob's on_step must call notify_step exactly once. It is suggested you call
it before or after all other processing, to avoid logic errors caused by
callbacks handling the same state as your other on_step processing.
Components
==========
CMI includes mob components, which are sort of like attributes, except there is
a fixed set of them and they are guaranteed to be present on every mob.
Implementing this part of the interface just involves doing some serialization
and deserialization.
Deserialization
---------------
In your mob's on_activate, you must call activate_components (see the usage
documentation) on serialized data (see below) if available, or else with no
arguments. Put the result in your mob's luaentity's cmi_components field.
Serialization
-------------
In your mob's get_staticdata, you must call serialize_components (see the usage
documentation) on your mob's luaentity's cmi_components field, and store the
result somewhere where it can be retrieved the next time the mob is activated.
Other
=====
Stuff I couldn't think of a category for.
Punch Damage Calculation
------------------------
CMI allows modders to switch out the damage calculation mechanism used for
punching mobs. When calculating the "default" damage, use calculate_damage from
CMI instead. This, along with punchplayer callbacks, allows modders to introduce
new damage systems to the game.

24
README.md Normal file
View File

@ -0,0 +1,24 @@
# Common Mob Interface
Purpose
=======
There are multiple mob frameworks which provide APIs for creating mobs. This is
good since it gives modders a variety of different ways to make mobs, and one
framework might be more suited to making a particular mob than another. But
implicitly they also all provide their own API for "using" mobs: things like
checking if an entity is a mob or doing things on mob death. This is bad because
modders who want to interface with mobs created by other modders need to code to
as many interfaces as there are mob frameworks. CMI is a mod that provides the
"one true mob interface", and is designed to be extensible to prevent bloat and
to be easy to integrate into existing mob frameworks or implement in a
stand-alone mob.
Usage
=====
Usage documentation is provided as LDoc comments in the source files.
Implementing in your mob mod/framework
======================================
Part of the implementation of this interface is in the mods that support it. If
you are a mob framework author or making a mob without a framework, and you want
to support CMI, take a look at IMPLEMENTING.md.

0
depends.txt Normal file
View File

7
description.txt Normal file
View File

@ -0,0 +1,7 @@
A standard for mob usage.
Provides some global callback registrations for things that can happen to mobs,
and also outlines a standard for the interface mobs expose to other mobs.
This mod also introduces "components" to mobs, in the sense of entity component
system.

14
devnotes Normal file
View File

@ -0,0 +1,14 @@
Here lie implementation details that should be explained but aren't part of the
public-facing API.
Component Representation
========================
Mobs implementing CMI are required to keep track of a black box containing a
mob's component data in a cmi_components field. The actual representation is a
table with these fields:
old_serialization: A map from component names to their serialized form from when
they were last deserialized. This is kept track of to prevent mobs from losing
information in case a mod is accidentally uninstalled.
components: A table from component names to their current data.

366
init.lua Normal file
View File

@ -0,0 +1,366 @@
--- 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

1
mod.conf Normal file
View File

@ -0,0 +1 @@
name=cmi