local altar_item_offset = {
x = 0, y = -0.3, z = 0
}
local log = function(...) sorcery.log('altar',...) end
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
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";
minetest.register_node('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};
after_place_node = function(pos, placer, stack, pointat)
local meta = minetest.get_meta(pos)
local stackmeta = stack:get_meta()
meta:set_int('favor', stackmeta:get_int('favor'))
meta:set_string('last_sacrifice', stackmeta:get_string('last_sacrifice'))
minetest.get_node_timer(pos):start(60)
end;
drop = {
-- for some idiot reason this is necessary for
-- preserve_metadata to work right
max_items = 1;
items = {
{ items = {'sorcery:idol_' .. name} }
};
};
preserve_metadata = function(pos, node, meta, newstack)
newstack[1]:get_meta():from_table(meta)
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
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')
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 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(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
print(god.name.." has accepted a sacrifice of "..s..", raising divine favor by "..value.." points to "..divine_favor)
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
local cost, tx = cons[1], cons[2]
if type(tx) == "table" then
tx = tx[math.random(#tx)]
end
-- preserve wear
local gift = ItemStack(tx)
local wear = stack:get_wear()
if wear > 0 then
gift:set_wear(wear)
end
-- preserve meta
gift:get_meta():from_table(stack:get_meta():to_table())
-- 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 itemname == s then
if divine_favor >= cost then
bestow(gift)
divine_favor = divine_favor - cost
print(god.name..'has consecrated ' ..s.. ' into ' ..tx.. ', for the cost of ' ..cost.. ' points of divine favor')
goto refresh
end
end
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, oddly_breakable_by_hand = 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
})