basic_machines/ball.lua
rnd1 0bdc55c32c /clockgen command: requires just privs now
ball: damages entities now, so you can kill mobs for example( except basic_robots)
keypad:  PROBLEM: if player doesnt move it takes another punch at same block for this function to run again, and it works normally if player moved at least one block from his previous position. it is caused by minetest bad handling of punches.
2017-06-11 08:37:52 +02:00

667 lines
22 KiB
Lua

-- BALL: energy ball that flies around, can bounce and activate stuff
-- rnd 2016:
-- TO DO: move mode: ball just rolling around on ground without hopping, also if inside slope it would "roll down", just increased velocity in slope direction
-- SETTINGS
basic_machines.ball = {};
basic_machines.ball.maxdamage = 10; -- player health 20
basic_machines.ball.bounce_materials = { -- to be used with bounce setting 2 in ball spawner: 1: bounce in x direction, 2: bounce in z direction, otherwise it bounces in y direction
["default:wood"]=1,
["xpanes:bar_2"]=1,
["xpanes:bar_10"]=1,
["darkage:iron_bars"]=1,
["default:glass"] = 2,
};
-- END OF SETTINGS
local ballcount = {};
local function round(x)
if x < 0 then
return -math.floor(-x+0.5);
else
return math.floor(x+0.5);
end
end
local ball_spawner_update_form = function (pos)
local meta = minetest.get_meta(pos);
local x0,y0,z0;
x0=meta:get_int("x0");y0=meta:get_int("y0");z0=meta:get_int("z0"); -- direction of velocity
local energy,bounce,g,puncheable, gravity,hp,hurt,solid;
local speed = meta:get_float("speed"); -- if positive sets initial ball speed
energy = meta:get_float("energy"); -- if positive activates, negative deactivates, 0 does nothing
bounce = meta:get_int("bounce"); -- if nonzero bounces when hit obstacle, 0 gets absorbed
gravity = meta:get_float("gravity"); -- gravity
hp = meta:get_float("hp");
hurt = meta:get_float("hurt");
puncheable = meta:get_int("puncheable"); -- if 1 can be punched by players in protection, if 2 can be punched by anyone
solid = meta:get_int("solid"); -- if 1 then entity is solid - cant be walked on
local texture = meta:get_string("texture") or "basic_machines_ball.png";
local visual = meta:get_string("visual") or "sprite";
local scale = meta:get_int("scale");
local form =
"size[4.25,4.75]" .. -- width, height
"field[0.25,0.5;1,1;x0;target;"..x0.."] field[1.25,0.5;1,1;y0;;"..y0.."] field[2.25,0.5;1,1;z0;;"..z0.."]"..
"field[3.25,0.5;1,1;speed;speed;"..speed.."]"..
--speed, jump, gravity,sneak
"field[0.25,1.5;1,1;energy;energy;"..energy.."]"..
"field[1.25,1.5;1,1;bounce;bounce;".. bounce.."]"..
"field[2.25,1.5;1,1;gravity;gravity;"..gravity.."]"..
"field[3.25,1.5;1,1;puncheable;puncheable;"..puncheable.."]"..
"field[3.25,2.5;1,1;solid;solid;"..solid.."]"..
"field[0.25,2.5;1,1;hp;hp;"..hp.."]".."field[1.25,2.5;1,1;hurt;hurt;"..hurt.."]"..
"field[0.25,3.5;4,1;texture;texture;"..minetest.formspec_escape(texture).."]"..
"field[0.25,4.5;1,1;scale;scale;"..scale.."]".."field[1.25,4.5;1,1;visual;visual;"..visual.."]"..
"button_exit[3.25,4.25;1,1;OK;OK]";
if meta:get_int("admin")==1 then
local lifetime = meta:get_int("lifetime");
if lifetime <= 0 then lifetime = 20 end
form = form .. "field[2.25,2.5;1,1;lifetime;lifetime;"..lifetime.."]"
end
meta:set_string("formspec",form);
end
minetest.register_entity("basic_machines:ball",{
timer = 0,
lifetime = 20, -- how long it exists before disappearing
energy = 0, -- if negative it will deactivate stuff, positive will activate, 0 wont do anything
puncheable = 1, -- can be punched by players in protection
bounce = 0, -- 0: absorbs in block, 1 = proper bounce=lag buggy, -- to do: 2 = line of sight bounce
gravity = 0,
speed = 5, -- velocity when punched
hurt = 0, -- how much damage it does to target entity, if 0 damage disabled
owner = "",
state = false,
origin = {x=0,y=0,z=0},
lastpos = {x=0,y=0,z=0}, -- last not-colliding position
hp_max = 100,
elasticity = 0.9, -- speed gets multiplied by this after bounce
visual="sprite",
visual_size={x=.6,y=.6},
collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5},
physical=false,
--textures={"basic_machines_ball"},
on_activate = function(self, staticdata)
self.object:set_properties({textures={"basic_machines_ball.png"}})
self.object:set_properties({visual_size = {x=1, y=1}});
self.timer = 0;self.owner = "";
self.origin = self.object:getpos();
self.lifetime = 20;
end,
get_staticdata = function(self) -- this gets called before object put in world and before it hides
if not self.state then return nil end
self.object:remove();
return nil
end,
on_punch = function (self, puncher, time_from_last_punch, tool_capabilities, dir)
if self.puncheable == 0 then return end
if self.puncheable == 1 then -- only those in protection
local name = puncher:get_player_name();
local pos = self.object:getpos();
if minetest.is_protected(pos,name) then return end
end
--minetest.chat_send_all(minetest.pos_to_string(dir))
if time_from_last_punch<0.5 then return end
local v = self.speed or 1;
local velocity = dir;
velocity.x = velocity.x*v;velocity.y = velocity.y*v;velocity.z = velocity.z*v;
self.object:setvelocity(velocity)
end,
on_step = function(self, dtime)
self.timer=self.timer+dtime
if self.timer>self.lifetime then
local count = ballcount[self.owner] or 1; count=count-1; ballcount[self.owner] = count;
self.object:remove()
return
end
if not self.state then self.state = true end
local pos=self.object:getpos()
local origin = self.origin;
local r = 30;-- maximal distance when balls disappear
local dist = math.max(math.abs(pos.x-origin.x), math.abs(pos.y-origin.y), math.abs(pos.z-origin.z));
if dist>r then -- remove if it goes too far
local count = ballcount[self.owner] or 1; count=count-1; ballcount[self.owner] = count;
self.object:remove()
return
end
local nodename = minetest.get_node(pos).name;
local walkable = false;
if nodename ~= "air" then
walkable = minetest.registered_nodes[nodename].walkable;
if nodename == "basic_machines:ball_spawner" and dist>0.5 then walkable = true end -- ball can activate spawner, just not originating one
end
if not walkable then
self.lastpos = pos
if self.hurt~=0 then -- check for coliding nearby objects
local objects = minetest.get_objects_inside_radius(pos,2);
if #objects>1 then
for _, obj in pairs(objects) do
local p = obj:getpos();
local d = math.sqrt((p.x-pos.x)^2+(p.y-pos.y)^2+(p.z-pos.z)^2);
if d>0 then
--if minetest.is_protected(p,self.owner) then return end
if math.abs(p.x)<32 and math.abs(p.y)<32 and math.abs(p.z)<32 then return end -- no damage around spawn
if obj:is_player() then --player
if obj:get_player_name()==self.owner then break end -- dont hurt owner
local hp = obj:get_hp()
local newhp = hp-self.hurt;
if newhp<=0 and boneworld and boneworld.killxp then
local killxp = boneworld.killxp[self.owner];
if killxp then
boneworld.killxp[self.owner] = killxp + 0.01;
end
end
obj:set_hp(newhp)
else -- non player
local lua_entity = obj:get_luaentity();
if lua_entity and lua_entity.itemstring then
local entname = lua_entity.itemstring;
if entname == "robot" then
self.object:remove()
return;
end
end
local hp = obj:get_hp()
local newhp = hp-self.hurt;
minetest.chat_send_player(self.owner,"#ball: target hp " .. newhp)
if newhp<=0 then obj:remove() else obj:set_hp(newhp) end
end
local count = ballcount[self.owner] or 1; count=count-1; ballcount[self.owner] = count;
self.object:remove();
return
end
end
end
end
end
if walkable then -- we hit a node
--minetest.chat_send_all(" hit node at " .. minetest.pos_to_string(pos))
local node = minetest.get_node(pos);
local table = minetest.registered_nodes[node.name];
if table and table.mesecons and table.mesecons.effector then -- activate target
local energy = self.energy;
if energy~=0 then
if minetest.is_protected(pos,self.owner) then return end
end
local effector = table.mesecons.effector;
self.object:remove();
if energy>0 then
if not effector.action_on then return end
effector.action_on(pos,node,16);
elseif energy<0 then
if not effector.action_off then return end
effector.action_off(pos,node,16);
end
else -- bounce ( copyright rnd, 2016 )
local bounce = self.bounce;
if self.bounce == 0 then
local count = ballcount[self.owner] or 1; count=count-1; ballcount[self.owner] = count;
self.object:remove()
return end
local n = {x=0,y=0,z=0}; -- this will be bounce normal
local v = self.object:getvelocity();
local opos = {x=round(pos.x),y=round(pos.y), z=round(pos.z)}; -- obstacle
local bpos ={ x=(pos.x-opos.x),y=(pos.y-opos.y),z=(pos.z-opos.z)}; -- boundary position on cube, approximate
if bounce == 2 then -- uses special blocks for non buggy lag proof bouncing: by default it bounces in y direction
local bounce_direction = basic_machines.ball.bounce_materials[node.name] or 0;
if bounce_direction == 0 then
if v.y>=0 then n.y = -1 else n.y = 1 end
elseif bounce_direction == 1 then
if v.x>=0 then n.x = -1 else n.x = 1 end
n.y = 0;
elseif bounce_direction == 2 then
if v.z>=0 then n.z = -1 else n.z = 1 end
n.y = 0;
end
else -- algorithm to determine bounce direction - problem: with lag its impossible to determine reliable which node was hit and which face ..
if v.x<=0 then n.x = 1 else n.x = -1 end -- possible bounce directions
if v.y<=0 then n.y = 1 else n.y = -1 end
if v.z<=0 then n.z = 1 else n.z = -1 end
local dpos = {};
dpos.x = 0.5*n.x; dpos.y = 0; dpos.z = 0; -- calculate distance to bounding surface midpoints
local d1 = (bpos.x-dpos.x)^2 + (bpos.y)^2 + (bpos.z)^2;
dpos.x = 0; dpos.y = 0.5*n.y; dpos.z = 0;
local d2 = (bpos.x)^2 + (bpos.y-dpos.y)^2 + (bpos.z)^2;
dpos.x = 0; dpos.y = 0; dpos.z = 0.5*n.z;
local d3 = (bpos.x)^2 + (bpos.y)^2 + (bpos.z-dpos.z)^2;
local d = math.min(d1,d2,d3); -- we obtain bounce direction from minimal distance
if d1==d then --x
n.y=0;n.z=0
elseif d2==d then --y
n.x=0;n.z=0
elseif d3==d then --z
n.x=0;n.y=0
end
nodename=minetest.get_node({x=opos.x+n.x,y=opos.y+n.y,z=opos.z+n.z}).name -- verify normal
walkable = nodename ~= "air";
if walkable then -- problem, nonempty node - incorrect normal, fix it
if n.x ~=0 then -- x direction is wrong, try something else
n.x=0;
if v.y>=0 then n.y = -1 else n.y = 1 end -- try y
nodename=minetest.get_node({x=opos.x+n.x,y=opos.y+n.y,z=opos.z+n.z}).name -- verify normal
walkable = nodename ~= "air";
if walkable then -- still problem, only remaining is z
n.y=0;
if v.z>=0 then n.z = -1 else n.z = 1 end
nodename=minetest.get_node({x=opos.x+n.x,y=opos.y+n.y,z=opos.z+n.z}).name -- verify normal
walkable = nodename ~= "air";
if walkable then -- messed up, just remove the ball
self.object:remove()
return
end
end
end
end
end
local elasticity = self.elasticity;
-- bounce
if n.x~=0 then
v.x=-elasticity*v.x
elseif n.y~=0 then
v.y=-elasticity*v.y
elseif n.z~=0 then
v.z=-elasticity*v.z
end
local r = 0.2
bpos = {x=pos.x+n.x*r,y=pos.y+n.y*r,z=pos.z+n.z*r}; -- point placed a bit further away from box
self.object:setpos(bpos) -- place object at last known outside point
self.object:setvelocity(v);
minetest.sound_play("default_dig_cracky", {pos=pos,gain=1.0,max_hear_distance = 8,})
end
end
return
end,
})
minetest.register_node("basic_machines:ball_spawner", {
description = "Spawns energy ball one block above",
tiles = {"basic_machines_ball.png"},
groups = {cracky=3, mesecon_effector_on = 1},
drawtype = "allfaces",
paramtype = "light",
param1=1,
walkable = false,
alpha = 150,
sounds = default.node_sound_wood_defaults(),
after_place_node = function(pos, placer)
local meta = minetest.env:get_meta(pos)
meta:set_string("owner", placer:get_player_name());
local privs = minetest.get_player_privs(placer:get_player_name()); if privs.privs then meta:set_int("admin",1) end
if privs.machines then meta:set_int("machines",1) end
meta:set_float("hurt",0);
meta:set_string("texture", "basic_machines_ball.png");
meta:set_float("hp",100);
meta:set_float("speed",5); -- if positive sets initial ball speed
meta:set_float("energy",1); -- if positive activates, negative deactivates, 0 does nothing
meta:set_int("bounce",0); -- if nonzero bounces when hit obstacle, 0 gets absorbed
meta:set_float("gravity",0); -- gravity
meta:set_int("puncheable",0); -- if 0 not puncheable, if 1 can be punched by players in protection, if 2 can be punched by anyone
meta:set_int("scale",100);
meta:set_string("visual","sprite");
ball_spawner_update_form(pos);
end,
mesecons = {effector = {
action_on = function (pos, node,ttl)
if ttl<0 then return end
local meta = minetest.get_meta(pos);
local t0 = meta:get_int("t");
local t1 = minetest.get_gametime();
local T = meta:get_int("T"); -- temperature
if t0>t1-2 then -- activated before natural time
T=T+1;
else
if T>0 then
T=T-1
if t1-t0>5 then T = 0 end
end
end
meta:set_int("T",T);
meta:set_int("t",t1); -- update last activation time
if T > 2 then -- overheat
minetest.sound_play("default_cool_lava",{pos = pos, max_hear_distance = 16, gain = 0.25})
meta:set_string("infotext","overheat: temperature ".. T)
return
end
if meta:get_int("machines")~=1 then -- no machines priv, limit ball count
local owner = meta:get_string("owner");
local count = ballcount[owner];
if not count or count<0 then count = 0 end
if count>=2 then
if t1-t0>20 then count = 0
else return
end
end
count = count + 1;
ballcount[owner]=count;
--minetest.chat_send_all("count " .. count);
end
pos.x = round(pos.x);pos.y = round(pos.y);pos.z = round(pos.z);
local obj = minetest.add_entity({x=pos.x,y=pos.y,z=pos.z}, "basic_machines:ball");
local luaent = obj:get_luaentity();
local meta = minetest.get_meta(pos);
local speed,energy,bounce,gravity,puncheable,solid;
speed = meta:get_float("speed");
energy = meta:get_float("energy"); -- if positive activates, negative deactivates, 0 does nothing
bounce = meta:get_int("bounce"); -- if nonzero bounces when hit obstacle, 0 gets absorbed
gravity = meta:get_float("gravity"); -- gravity
puncheable = meta:get_int("puncheable"); -- if 1 can be punched by players in protection, if 2 can be punched by anyone
solid = meta:get_int("solid");
if energy<0 then
obj:set_properties({textures={"basic_machines_ball.png^[colorize:blue:120"}})
end
luaent.bounce = bounce;
luaent.energy = energy;
if gravity>0 then
obj:setacceleration({x=0,y=-gravity,z=0});
end
luaent.puncheable = puncheable;
luaent.owner = meta:get_string("owner");
luaent.hurt = meta:get_float("hurt");
if solid==1 then
luaent.physical = true
end
obj:set_hp( meta:get_float("hp") );
local x0,y0,z0;
if speed>0 then luaent.speed = speed end
x0=meta:get_int("x0");y0=meta:get_int("y0");z0=meta:get_int("z0"); -- direction of velocity
if speed~=0 and (x0~=0 or y0~=0 or z0~=0) then -- set velocity direction
local velocity = {x=x0,y=y0,z=z0};
local v = math.sqrt(velocity.x^2+velocity.y^2+velocity.z^2); if v == 0 then v = 1 end
v = v / speed;
velocity.x=velocity.x/v;velocity.y=velocity.y/v;velocity.z=velocity.z/v;
obj:setvelocity(velocity);
end
if meta:get_int("admin")==1 then
luaent.lifetime = meta:get_float("lifetime");
end
local visual = meta:get_string("visual")
obj:set_properties({visual=visual});
local texture = meta:get_string("texture");
if visual=="sprite" then
obj:set_properties({textures={texture}})
elseif visual == "cube" then
obj:set_properties({textures={texture,texture,texture,texture,texture,texture}})
end
local scale = meta:get_int("scale");if scale<=0 then scale = 1 else scale = scale/100 end
obj:set_properties({visual_size = {x=scale, y=scale}});
end,
action_off = function (pos, node,ttl)
if ttl<0 then return end
pos.x = round(pos.x);pos.y = round(pos.y);pos.z = round(pos.z);
local obj = minetest.add_entity({x=pos.x,y=pos.y,z=pos.z}, "basic_machines:ball");
local luaent = obj:get_luaentity();
luaent.energy = -1;
obj:set_properties({textures={"basic_machines_ball.png^[colorize:blue:120"}})
end
}
},
on_receive_fields = function(pos, formname, fields, sender)
local name = sender:get_player_name();if minetest.is_protected(pos,name) then return end
if fields.OK then
local privs = minetest.get_player_privs(sender:get_player_name());
local meta = minetest.get_meta(pos);
local x0=0; local y0=0; local z0=0;
--minetest.chat_send_all("form at " .. dump(pos) .. " fields " .. dump(fields))
if fields.x0 then x0 = tonumber(fields.x0) or 0 end
if fields.y0 then y0 = tonumber(fields.y0) or 0 end
if fields.z0 then z0 = tonumber(fields.z0) or 0 end
if not privs.privs and (math.abs(x0)>10 or math.abs(y0)>10 or math.abs(z0) > 10) then return end
meta:set_int("x0",x0);meta:set_int("y0",y0);meta:set_int("z0",z0);
local speed,energy,bounce,gravity,puncheable,solid;
energy = meta:get_float("energy"); -- if positive activates, negative deactivates, 0 does nothing
bounce = meta:get_int("bounce"); -- if nonzero bounces when hit obstacle, 0 gets absorbed
gravity = meta:get_float("gravity"); -- gravity
puncheable = meta:get_int("puncheable"); -- if 1 can be punched by players in protection, if 2 can be punched by anyone
solid = meta:get_int("solid");
if fields.speed then
local speed = tonumber(fields.speed) or 0;
if (speed > 10 or speed < 0) and not privs.privs then return end
meta:set_float("speed", speed)
end
if fields.energy then
local energy = tonumber(fields.energy) or 1;
meta:set_float("energy", energy)
end
if fields.bounce then
local bounce = tonumber(fields.bounce) or 1;
meta:set_int("bounce",bounce)
end
if fields.gravity then
local gravity = tonumber(fields.gravity) or 1;
if (gravity<0 or gravity>30) and not privs.privs then return end
meta:set_float("gravity", gravity)
end
if fields.puncheable then
meta:set_int("puncheable", tonumber(fields.puncheable) or 0)
end
if fields.solid then
meta:set_int("solid", tonumber(fields.solid) or 0)
end
if fields.lifetime then
meta:set_int("lifetime", tonumber(fields.lifetime) or 0)
end
if fields.hurt then
meta:set_float("hurt", tonumber(fields.hurt) or 0)
end
if fields.hp then
meta:set_float("hp", math.abs(tonumber(fields.hp) or 0))
end
if fields.texture then
meta:set_string ("texture", fields.texture);
end
if fields.scale then
local scale = math.abs(tonumber(fields.scale) or 100);
if scale>1000 and not privs.privs then scale = 1000 end
meta:set_int("scale", scale)
end
if fields.visual then
local visual = fields.visual or "";
if visual~="sprite" and visual~="cube" then return end
meta:set_string ("visual", fields.visual);
end
ball_spawner_update_form(pos);
end
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
local name = digger:get_player_name();
local inv = digger:get_inventory();
inv:remove_item("main", ItemStack("basic_machines:ball_spawner"));
local stack = ItemStack("basic_machines:ball_spell");
local meta = oldmetadata["fields"];
meta["formspec"]=nil;
stack:set_metadata(minetest.serialize(meta));
inv:add_item("main",stack);
end
})
local spelltime = {};
-- ball as magic spell user can cast
minetest.register_tool("basic_machines:ball_spell", {
description = "ball spawner",
inventory_image = "basic_machines_ball.png",
tool_capabilities = {
full_punch_interval = 2,
max_drop_level=0,
},
on_use = function(itemstack, user, pointed_thing)
local pos = user:getpos();pos.y=pos.y+1;
local meta = minetest.deserialize(itemstack:get_metadata());
if not meta then return end
local owner = meta["owner"] or "";
--if minetest.is_protected(pos,owner) then return end
local t0 = spelltime[owner] or 0;
local t1 = minetest.get_gametime();
if t1-t0<2 then return end -- too soon
spelltime[owner]=t1;
local obj = minetest.add_entity({x=pos.x,y=pos.y,z=pos.z}, "basic_machines:ball");
local luaent = obj:get_luaentity();
local speed,energy,bounce,gravity,puncheable;
speed = tonumber(meta["speed"]) or 0;
energy = tonumber(meta["energy"]) or 0; -- if positive activates, negative deactivates, 0 does nothing
bounce = tonumber(meta["bounce"]) or 0; -- if nonzero bounces when hit obstacle, 0 gets absorbed
gravity = tonumber(meta["gravity"]) or 0; -- gravity
puncheable = tonumber(meta["puncheable"]) or 0; -- if 1 can be punched by players in protection, if 2 can be punched by anyone
if energy<0 then
obj:set_properties({textures={"basic_machines_ball.png^[colorize:blue:120"}})
end
luaent.bounce = bounce;
luaent.energy = energy;
if gravity>0 then
obj:setacceleration({x=0,y=-gravity,z=0});
end
luaent.puncheable = puncheable;
luaent.owner = meta["owner"];
luaent.hurt = math.min(tonumber(meta["hurt"]),basic_machines.ball.maxdamage);
obj:set_hp( tonumber(meta["hp"]) );
local x0,y0,z0;
if speed>0 then luaent.speed = speed end
local v = user:get_look_dir();
v.x=v.x*speed;v.y=v.y*speed;v.z=v.z*speed;
obj:setvelocity(v);
if tonumber(meta["admin"])==1 then
luaent.lifetime = tonumber(meta["lifetime"]);
end
obj:set_properties({textures={meta["texture"]}})
end,
})
-- minetest.register_craft({
-- output = "basic_machines:ball_spawner",
-- recipe = {
-- {"basic_machines:power_cell"},
-- {"basic_machines:keypad"}
-- }
-- })