-- in theory, minetest groups are supposed to allow us to
-- give consistent, cross-mod classes to items, and easily
-- detect whether items fit into a particular class. unfortunately,
-- they don't really work for this purpose because we often
-- need to attach additional data to items that are outside
-- of our control (and the default mod's authors are amazingly
-- lax in grouping items; for instance, diamonds and mese
-- crystals aren't even part of a 'gem' or 'crystal' group!)
-- this module allows us to consistently classify items, and
-- easily maintain complex hierarchies of subclasses. whether
-- an item belongs to a class can be determined by checking
-- its groups, consulting compat tables, calling a custom
-- predicate function (possibly to check for a _sorcery
-- defprop), or recursing through a list of subclasses.
-- this also means that matters of identity are all controlled
-- from a central location.
sorcery.itemclass = {
classes = {
-- gem/crystalline and metal/metallic differentiate
-- between crafting materials (i.e. gems or ingots
-- themselves) and items crafted from those materials.
-- the former includes only crafting materials, the
-- latter includes both.
gem = {
compat = 'gems';
groups = { 'gem', 'crystal'; };
predicate = function(name)
if minetest.get_item_group(name, 'sorcery_gem') ~= 0
or minetest.get_item_group(name, 'sorcery_shard') ~= 0 then
return minetest.registered_items[name]._sorcery.material;
end
end;
};
crystalline = {
subclass = {'gem'};
predicate = function(name)
local mat = sorcery.matreg.lookup[name]
if mat and mat.gem then return mat end
end;
};
grindable = {
compat = 'grindables';
subclass = {'metallic'};
conform = {
metallic = function(m)
if m and m.data and m.data.parts and m.data.parts.powder then
return {
hardness = m.data.hardness;
grindcost = 1;
grindvalue = m.value or 1;
powder = m.data.parts.powder;
}
end
end;
};
predicate = function(name)
local def = minetest.registered_items[name]._sorcery
if not def then return nil end
def = def.material
if def and def.grindvalue then
return {
hardness = def.hardness or def.data.hardness;
grindcost = def.grindcost or 1;
grindvalue = def.grindvalue or def.value;
powder = def.powder or def.data.parts.powder;
}
end
end;
};
metal = {
predicate = function(name)
-- metallookup is a table of 'primary' metal
-- items, like ingots, fragments, and powders
return sorcery.data.metallookup[name]
end;
};
metallic = {
subclass = {'metal'};
predicate = function(name)
-- matreg is a registry binding crafted items,
-- like armors and tools, to the material they
-- are made out of. it's necessary because not
-- all items we want to interact with have
-- definitions under our control
local mat = sorcery.matreg.lookup[name]
if mat and mat.metal then return mat end
local prop = minetest.registered_items[name]._sorcery
if prop and prop.material and prop.material.metal then
return prop.material
end
end;
};
material = {
subclass = {'metallic','crystalline'};
};
ore = {
groups = { 'ore' };
compat = 'ore';
predicate = function(name)
-- maybe revise this at some point once sorcery is extricated
-- from instant_ores and we have more control over the items
-- we generate
local orepfx = "stone_with_" -- }:<
local barename = string.sub(name, string.find(name, ':') + 1)
if string.sub(barename,1,string.len(orepfx)) == orepfx then
local iname = string.sub(barename,string.len(orepfx) + 1)
if sorcery.data.metals[iname] then
return { metal = true, id = iname }
elseif sorcery.data.gems[iname] then
return { gem = true, id = iname }
end
end
end;
};
fuel = {
groups = {'fuel','flammable'};
predicate = function(name)
local c,di = minetest.get_craft_result {
method = 'fuel';
width = 1;
items = { ItemStack(name) };
}
if c.time and c.time > 0 then
return {
burntime = c.time;
leftover = di and di[1];
}
end
end;
};
vessel = {
compat = 'vessels';
finagle = { type = 'vessel'; charge = 3; hold = 'liquid' };
predicate = function(name)
local item = minetest.registered_items[name]
if item and item._sorcery and item._sorcery.container then
local ct = item._sorcery.container
if ct.type == 'vessel' then return sorcery.lib.tbl.merge({
charge = 3;
}, ct) end
end
end;
};
box = {
compat = 'boxes';
predicate = function(name)
local item = minetest.registered_items[name]
if item._sorcery and item._sorcery.container then
local ct = item._sorcery.container
if ct.type == 'box' then return sorcery.lib.tbl.proto(ct, {
charge = 8;
}) end
end
end;
};
bucket = {
compat = 'buckets';
finagle = { type = 'bucket', hold = 'liquid', charge = 3 * 3, empty = 'bucket:bucket_empty'};
predicate = function(name)
local item = minetest.registered_items[name]
if item._sorcery and item._sorcery.container then
local ct = item._sorcery.container
if ct.type == 'bucket' then return sorcery.lib.tbl.proto(ct, {
charge = 3 * 3;
}) end
end
end;
};
container = {
compat = 'containers';
subclass = {'vessel', 'box', 'bucket'}
};
};
get = function(name,class)
local c = sorcery.itemclass.classes[class]
local o
if not c then return false end
if type(name) ~= 'string' then name = name:get_name() end
if c.predicate then
o = c.predicate(name)
if o then return o end
end
if c.compat then
o = sorcery.data.compat[c.compat][name]
if o then
if c.finagle then
if type(c.finagle) == 'function' then
return sorcery.lib.tbl.proto(c.finagle(o), o)
elseif type(o) == 'table' then
return sorcery.lib.tbl.merge(c.finagle, o)
end
end
return o
end
end
if c.subclass then
for _,s in pairs(c.subclass) do
o = sorcery.itemclass.get(name,s)
if o then
if c.conform and c.conform[s] then
return c.conform[s](o)
else return o end
end
end
end
if c.groups then
for _,g in pairs(c.groups) do
o = minetest.get_item_group(name,g)
if o > 0 then return { kind = o } end
end
o = nil
end
return false
end;
}