local altar_item_offset = {
x = 0, y = -0.3, z = 0
}
local log = sorcery.logger('altar')
local L = sorcery.lib
local range = function(min, max)
local span = max - min
local val = math.random() * span
return val + min
end
local get_altar_ent = function(pos)
for _, obj in pairs(minetest.get_objects_inside_radius(pos,0.9)) do
if not obj then goto nextloop end
local ent = obj:get_luaentity()
if not ent then goto nextloop end
if ent.name == "sorcery:altar_item" then
return obj
end
::nextloop::
end
return nil
end
local update_altar = function(pos, object)
local meta = minetest.get_meta(pos)
local contents = meta:get_inventory()
if object == nil then
object = get_altar_ent(pos)
if object == nil then
object = minetest.add_entity({
x = pos.x + altar_item_offset.x,
y = pos.y + altar_item_offset.y,
z = pos.z + altar_item_offset.z
}, "sorcery:altar_item")
end
end
if contents:is_empty('item') then
if object ~= nil then object:remove() end
else
local itemstring = contents:get_stack('item',1):to_string()
local props = object:get_properties()
local node = minetest.get_node(pos)
props.wield_item = itemstring
object:set_properties(props)
object:set_yaw(math.pi*2 - node.param2*(math.pi / 2))
end
end
-- remove unknown gifts
minetest.register_on_mods_loaded(function()
for name, god in pairs(sorcery.data.gods) do
local bad = {}
for g in pairs(god.gifts) do
-- can't mutate table while we're iterating it
if not minetest.registered_nodes[g] then bad[#bad+1] = g end
end
for _, g in ipairs(bad) do god.gifts[g] = nil end
end
end)
local std_god_interval = 40
for name, god in pairs(sorcery.data.gods) do
local hitbox = {
0-(god.idol.width / 2.0), 0-(god.idol.height / 2.0), -0.15,
god.idol.width / 2.0, god.idol.height / 2.0, 0.15
} -- {xmin, ymin, zmin,
-- xmax, ymax, zmax} in nodes from node center.
paramtype = "light";
if god.idol.craft then
minetest.register_craft {
output = 'sorcery:idol_' .. name;
recipe = god.idol.craft;
};
end
local god_interval = std_god_interval * (god.freq or 1)
local add_sparkles = function(pos,divine_favor)
divine_favor = divine_favor or minetest.get_meta(pos):get_int('favor')
if divine_favor > 5 then
local ct = divine_favor / 5
minetest.add_particlespawner {
texture = L.image('sorcery_glitter.png'):glow(L.color(god.color)):render();
glow = 14;
amount = ct / (1 / god_interval), time = god_interval;
minpos = pos:offset(-0.4, -0.5, -0.4);
maxpos = pos:offset( 0.4,god.idol.height-0.5,0.4);
minvel = vector.new(0, 0.05, 0);
minvel = vector.new(0, 0.1, 0);
minacc = vector.new(0, 0.1, 0);
maxacc = vector.new(0, 0.2, 0);
minexptime = 4, maxexptime = 4;
minsize = 0.3, maxsize = 1;
animation = {
type = 'vertical_frames';
length = 0.1;
aspect_w = 16, aspect_h = 16;
}
}
end
end
sorcery.lib.node.reg_autopreserve('sorcery:idol_' .. name, {
description = god.idol.desc;
drawtype = "mesh";
mesh = 'sorcery-idol-' .. name .. '.obj';
paramtype = 'light';
paramtype2 = 'facedir';
sunlight_propagates = true;
stack_max = 1;
tiles = god.idol.tex;
selection_box = { type = "fixed"; fixed = {hitbox}; };
collision_box = { type = "fixed"; fixed = {hitbox}; };
groups = { cracky = 2, sorcery_idol = 1, heavy = 1, sorcery_worship = 1, sorcery_instantiate = 1};
_sorcery = {
idol_god = name;
on_load = function(pos) add_sparkles(pos) end;
};
on_construct = function(pos)
minetest.get_node_timer(pos):start(god_interval)
add_sparkles(pos)
end;
on_timer = function(pos, elapsed)
local altar = minetest.find_node_near(pos, 3, "sorcery:altar")
-- TODO even without an altar, an idol with high favor could still be the source of miracles
-- refills nearby partly empty troughs at cost to favor?
if not altar then return true end
local altarmeta = minetest.get_meta(altar)
local inv = altarmeta:get_inventory()
local idolmeta = minetest.get_meta(pos)
local divine_favor = idolmeta:get_int('favor')
add_sparkles(pos,divine_favor)
local bestow = function(item,color)
if type(item) == 'string' then
item = ItemStack(item)
end
if color == nil then
color = sorcery.lib.color(god.color)
end
for i=0,32 do
minetest.add_particle{
pos = {
x = altar.x + range(-0.4,0.4);
z = altar.z + range(-0.4,0.4);
y = altar.y + range(-0.2,0.3);
};
expirationtime = range(3,8);
size = range(1,3);
velocity = {
x = range(-0.6, 0.6);
z = range(-0.6, 0.6);
y = range(-0.6, 0.6);
};
acceleration = {
x = 0; y = range(0,1); z = 0;
};
texture = sorcery.lib.image('sorcery_sparkle.png'):
transform(math.random(8) - 1):
multiply(color:brighten(1.7)):
render();
glow = 14;
}
end
for i=0,48 do
minetest.add_particle{
pos = {
x = altar.x + range(-0.3,0.3);
z = altar.z + range(-0.3,0.3);
y = altar.y + range(-0.3,0.3);
};
expirationtime = range(3,8);
size = range(2,9);
velocity = {
x = range(-0.5, 0.5);
z = range(-0.5, 0.5);
y = range(-0.3, 0.8);
};
acceleration = {
x = 0; y = range(0,1); z = 0;
};
texture = 'sorcery_divine_radiance_'..math.random(9)..'.png' ..
'^[multiply:' .. color:hex() ..
'^[opacity:' .. (math.random(127) + 127) ..
'^[transform' .. (math.random(8) - 1);
glow = math.random(14 - 9) + 9;
}
end
inv:set_stack('item',1,item)
end
if inv:is_empty('item') then
-- nothing on the altar. decide if we're going to do a
-- gift cycle by rolling against the god's stinginess
if god.gifts and next(god.gifts) and math.random(god.stinginess) == 1 then
-- we've beat the odds and started a gift cycle. now
-- we pick a random gift and roll against its rarity
-- to determine if the god is feeling generous
local gift = sorcery.lib.tbl.pick(god.gifts)
local data = god.gifts[gift]
local value, rarity = data[1], data[2]
if value <= divine_favor and math.random(rarity) == 1 then
bestow(gift)
log.act(god.name,'has produced',gift,'upon an altar as a gift')
if math.random(god.generosity) == 1 then
-- unappreciated gifts may incur divine
-- irritation
divine_favor = divine_favor - 1
end
end
end
goto refresh
end
-- gods are taciturn and unpredictable creatures, but if you
-- have won their favor for your idol through sacrifice and
-- obeisance, they are more likely to pay attention to you
-- and lay blessings more rapidly upon you
do
local chance_to_act = math.max(2, god.laziness - divine_favor)
if math.random(chance_to_act) ~= 1 then goto refresh end
local stack = inv:get_stack('item',1)
local itemname = stack:get_name()
-- loop through the sacrifices accepted by this god and check
-- whether the item on the altar is any of them
for s, value in pairs(god.sacrifice) do
if itemname == s then
if value < 0 then
bestow("sorcery:ash", sorcery.lib.color(254,117,103))
else
if idolmeta:get_string('last_sacrifice') == s then
-- the gods are getting bored
value = math.floor(value / 2)
end
bestow(nil)
end
divine_favor = divine_favor + value
log.actf("%s has accepted a sacrifice of %s, raising divine favor by %u points to %u at altar %s", god.name, s, value, divine_favor, minetest.pos_to_string(pos))
idolmeta:set_string('last_sacrifice', s)
goto refresh
end
end
-- loop through the list of things this god will consecrate and
-- check whether the item on the altar is any of them
for s, cons in pairs(god.consecrate) do
if itemname == s then
local cost, tx
if type(cons) == "table" then
cost, tx = cons[1], cons[2]
tx = tx[math.random(#tx)]
elseif type(cons) == 'function' then
cost, tx = cons {
favor = divine_favor;
pos = pos;
altar = altarmeta;
idol = idolmeta;
god = god;
}
end
-- preserve wear
local gift
if type(tx) == 'string' then
gift = ItemStack(tx)
else gift = tx end
if not gift:is_known() then goto skip end
local wear = stack:get_wear()
if wear > 0 then
gift:set_wear(wear)
end
-- preserve meta
local gm = gift:get_meta()
gm:from_table(
sorcery.lib.tbl.merge(
stack:get_meta():to_table(),
gm:to_table()
)
) -- oof
-- reflash enchantments to ensure label is accurate
local ench = sorcery.enchant.get(gift)
if #ench.spells > 0 then
-- add a bit of energy as a gift?
if math.random(math.ceil(god.stinginess * 0.5)) == 1 then
local max = 0.05 * god.generosity
ench.energy = ench.energy * range(0.7*max,max)
end
sorcery.enchant.set(gift,ench)
end
if divine_favor >= cost then
bestow(gift)
divine_favor = divine_favor - cost
log.act(god.name, 'has consecrated', s, 'into', tx, 'for the cost of', cost, 'points of divine favor')
goto refresh
end
end
::skip::end
end
::refresh::
idolmeta:set_int('favor', divine_favor)
update_altar(altar,nil)
return true
end;
})
end
minetest.register_entity("sorcery:altar_item", {
initial_properties = {
visual = "wielditem";
visual_size = { x = 0.2, y = 0.2 };
wield_item = "air";
glow = 11; -- why the fuck isn't light working normally?
collisionbox = {0};
physical = false;
pointable = false;
};
on_activate = function(self,data,dtime)
local pos = self.object:get_pos()
local nodepos = {
x = pos.x - altar_item_offset.x,
y = pos.y - altar_item_offset.y,
z = pos.z - altar_item_offset.z
}
if minetest.get_node(nodepos).name ~= "sorcery:altar" then
self.object:remove()
else
update_altar(nodepos,self.object)
end
end
})
minetest.register_node("sorcery:altar", {
description = "Altar";
drawtype = "mesh";
mesh = "sorcery-altar.obj";
paramtype = "light";
paramtype2 = "facedir";
sunlight_propagates = true;
tiles = {
"default_sandstone.png",
"default_silver_sandstone.png",
"default_copper_block.png",
"default_steel_block.png",
"default_gold_block.png",
"default_coal_block.png"
};
selection_box = {
type = "fixed",
fixed = { {-0.5, -0.5, -0.5, 0.5, -0.09, 0.5} }
};
collision_box = {
type = "fixed",
fixed = { {-0.5, -0.5, -0.5, 0.5, -0.09, 0.5} }
};
groups = {cracky = 2, dig_immediate = 2, sorcery_worship = 1};
on_construct = function(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
inv:set_size('item', 1)
end;
on_destruct = function(pos)
local ent = get_altar_ent(pos)
if not ent then return nil end
ent:remove()
end;
on_rightclick = function(pos,node,user,stack)
local meta = minetest.get_meta(pos)
local contents = meta:get_inventory()
if contents:is_empty('item') then
if stack:is_empty() then return stack end
local new_item = stack:take_item(1)
contents:set_stack('item',1,new_item)
else
local pinv = user:get_inventory()
if pinv == nil
then return stack end
local give_item = contents:get_stack('item',1)
-- this is more complex than it should really need to
-- be. because minetest implements a modify-current-
-- stack-through-return-value feature, which is very
-- poorly integrated with features for controlling the
-- player's inventory, it's not enough to just say
-- "give them the item" and let minetest work out where
-- to place it. the value returned by this function
-- always overrides any changes to the inventory made
-- by this function, so if the current stack matches
-- the object being removed, it will seem to just
-- disappear into the ether, as the original stack
-- argument overwrites it. (yes, returning nil has the
-- same effect, i tried. bug imo.)
if stack:item_fits(give_item) then
-- first check if the item we're taking fits onto
-- the selected stack, and just increment it if so.
stack:add_item(give_item)
elseif not pinv:room_for_item('main',give_item) then
-- it doesn't fit onto the current stack, but does
-- it fit into the inventory somewhere else? if not,
-- we need to bail without changing anything
return stack
else -- it fits in the inventory
pinv:add_item('main',give_item)
end
-- clear the contents of the altar
contents:set_stack('item',1,ItemStack(nil))
end
update_altar(pos,nil)
return stack
end
})