-- Ball: energy ball that flies around, can bounce and activate stuff -- rnd 2016 -- Copyright (C) 2022-2025 мтест -- See README.md for license details -- 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 local F, S = basic_machines.F, basic_machines.S local machines_TTL = basic_machines.properties.machines_TTL local machines_minstep = basic_machines.properties.machines_minstep local machines_timer = basic_machines.properties.machines_timer local max_balls = math.max(0, basic_machines.settings.max_balls) local max_range = basic_machines.properties.max_range local max_damage = minetest.PLAYER_MAX_HP_DEFAULT / 4 -- player health 20 -- 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 local bounce_materials = {} local bounce_materials_help, axis = {S(", and for the next blocks:")}, {"x", "z"} local function add_bounce_material(name, direction) bounce_materials[name] = direction bounce_materials_help[#bounce_materials_help + 1] = ("%s: %s"):format(name, axis[direction]) end if minetest.get_modpath("darkage") then add_bounce_material("darkage:iron_bars", 1) end if basic_machines.use_default then add_bounce_material("default:glass", 2) add_bounce_material("default:wood", 1) end if minetest.get_modpath("xpanes") then add_bounce_material("xpanes:bar_2", 1) add_bounce_material("xpanes:bar_10", 1) end if #bounce_materials_help > 1 then bounce_materials_help = table.concat(bounce_materials_help, "\n") else bounce_materials_help = "" end local ball_default = { x0 = 0, y0 = 0, z0 = 0, speed = 5, energy = 1, bounce = 0, gravity = 1, punchable = 1, hp = 100, hurt = 0, lifetime = 20, solid = 0, texture = "basic_machines_ball.png", scale = 100, visual = "sprite" } local scale_factor = 100 local ballcount = {} local abs = math.abs local elasticity = 0.9 -- speed gets multiplied by this when bouncing local use_boneworld = minetest.global_exists("boneworld") minetest.register_entity("basic_machines:ball", { initial_properties = { hp_max = ball_default.hp, physical = ball_default.solid == 1, collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}, visual = ball_default.visual, visual_size = { x = ball_default.scale / scale_factor, y = ball_default.scale / scale_factor }, textures = {ball_default.texture}, static_save = false }, _in_air = false, _origin = { x = ball_default.x0, y = ball_default.y0, z = ball_default.z0 }, _owner = "", _timer = 0, _speed = ball_default.speed, -- velocity when punched _energy = ball_default.energy, -- if negative it will deactivate stuff, positive will activate, 0 wont do anything _bounce = ball_default.bounce, -- 0: absorbs in block, 1: proper bounce = lag buggy, to do: line of sight bounce _punchable = ball_default.punchable, -- can be punched by players in protection _hurt = ball_default.hurt, -- how much damage it does to target entity, if 0 damage disabled _lifetime = ball_default.lifetime, -- how long it exists before disappearing _solid = ball_default.solid, -- wether physical or not on_deactivate = function(self) ballcount[self._owner] = (ballcount[self._owner] or 1) - 1 end, on_step = function(self, dtime) self._timer = self._timer + dtime if self._timer > self._lifetime then self.object:remove(); return end local pos = self.object:get_pos() local origin = self._origin local dist = math.max(abs(pos.x - origin.x), abs(pos.y - origin.y), abs(pos.z - origin.z)) if dist > 50 then -- maximal distance when balls disappear, remove if it goes too far self.object:remove(); return end local node_name, def, walkable local energy, bounce = self._energy, self._bounce if self._solid == 0 and (energy ~= 0 or bounce > 0) then node_name = minetest.get_node(pos).name if node_name == "air" then if bounce > 0 then self._in_air = true end elseif energy ~= 0 and node_name == "basic_machines:ball_spawner" and dist > 0.5 then -- ball can activate spawner, just not originating one def = minetest.registered_nodes[node_name] walkable = true elseif node_name == "ignore" then self.object:remove(); return else def = minetest.registered_nodes[node_name] if def then if bounce > 0 and self._in_air and ((def.groups or {}).liquid or 0) > 0 and self._speed < 7 then -- bounce on liquids surface walkable = true else walkable = def.walkable end end end end if walkable then -- we hit a node - only with physical = false (or solid = 0) if energy ~= 0 and def and (def.effector or def.mesecons and def.mesecons.effector) then -- activate target if minetest.is_protected(pos, self._owner) then return end self.object:remove() local effector = def.effector or def.mesecons.effector local param = def.effector and machines_TTL or minetest.get_node(pos) if energy > 0 and effector.action_on then effector.action_on(pos, param) elseif energy < 0 and effector.action_off then effector.action_off(pos, param) end elseif bounce > 0 then -- bounce (copyright rnd, 2016) local n = {x = 0, y = 0, z = 0} -- this will be bounce normal local v = self.object:get_velocity() local opos = vector.round(pos) -- obstacle if bounce == 1 then -- algorithm to determine bounce direction - problem: -- with lag it's impossible to determine reliably which node was hit and which face... -- possible bounce directions if v.x > 0 then n.x = -1 else n.x = 1 end 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 -- obtain bounce direction local bpos = vector.subtract(pos, opos) -- boundary position on cube, approximate local dpos = vector.multiply(n, 0.5) -- calculate distance to bounding surface midpoints local d1 = (bpos.x - dpos.x)^2 + bpos.y^2 + bpos.z^2 local d2 = bpos.x^2 + (bpos.y - dpos.y)^2 + bpos.z^2 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, n.z = 0, 0 elseif d2 == d then -- y n.x, n.z = 0, 0 elseif d3 == d then -- z n.x, n.y = 0, 0 end else -- bounce == 2, uses special blocks for non buggy lag proof bouncing: by default it bounces in y direction local bounce_direction = 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 elseif bounce_direction == 2 then if v.z > 0 then n.z = -1 else n.z = 1 end end end -- verify new ball position local new_pos = vector.add(pos, vector.multiply(n, 0.2)) local new_pos_node_name = minetest.get_node(new_pos).name if new_pos_node_name == "air" then self._in_air = true else local new_pos_node_def = minetest.registered_nodes[new_pos_node_name] if new_pos_node_def then local new_pos_is_walkable = new_pos_node_def.walkable if new_pos_is_walkable then -- problem, nonempty node - incorrect position self.object:remove(); return -- just remove the ball elseif ((new_pos_node_def.groups or {}).liquid or 0) > 0 then if self._in_air and self._speed < 7 then self._speed = 7 -- sink the ball end self._in_air = false end end end -- elastify velocity 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 -- bounce self.object:set_pos(new_pos) -- ball placed a bit further away from box self.object:set_velocity(v) minetest.sound_play(basic_machines.sound_ball_bounce, {pos = pos, gain = 1, max_hear_distance = 8}, true) else self.object:remove(); return end elseif self._hurt ~= 0 then -- check for colliding nearby objects local objects = minetest.get_objects_inside_radius(pos, 2) if #objects > 1 then for _, obj in ipairs(objects) do if obj:is_player() then -- player if obj:get_player_name() ~= self._owner then -- don't hurt owner local hp = obj:get_hp() local newhp = hp - self._hurt if newhp <= 0 and use_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) if newhp > 0 and newhp < hp then obj:add_velocity(vector.divide(self.object:get_velocity(), 3)) end self.object:remove(); break end elseif obj ~= self.object then -- non player local lua_entity = obj:get_luaentity() if lua_entity then if lua_entity.itemstring == "robot" then self.object:remove(); break -- if protection (mobs_redo) is on level 2 then don't let ball harm mobs elseif lua_entity.protected ~= 2 and obj:get_armor_groups().fleshy then local hp = obj:get_hp() local newhp = math.min(obj:get_properties().hp_max, hp - self._hurt) if newhp > 0 then obj:set_hp(newhp) if newhp < hp then obj:add_velocity(vector.divide(self.object:get_velocity(), 4)) end else obj:remove() end self.object:remove(); break end end end end end end end, on_punch = function(self, puncher, time_from_last_punch, _, dir) local punchable = self._punchable if punchable == 0 then -- no punch return elseif time_from_last_punch > 0.5 then if punchable == 1 then -- only those in protection local obj_pos = self.object:get_pos() if minetest.is_protected(obj_pos, "") or puncher and minetest.is_protected(obj_pos, puncher:get_player_name()) then self.object:set_velocity(vector.multiply(dir, self._speed)) else return end else -- everywhere self.object:set_velocity(vector.multiply(dir, self._speed)) end end end }) local function ball_spawner_update_form(meta) local field_lifetime if meta:get_int("admin") == 1 then field_lifetime = ("field[2.75,3;1,0.8;lifetime;%s;%i]"):format(F(S("Lifetime")), meta:get_int("lifetime")) else field_lifetime = "" end local function twodigits(f) return ("%.2f"):format(f) end meta:set_string("formspec", "formspec_version[4]size[5.25,6.6]" .. "field[0.25,0.5;1,0.8;x0;" .. F(S("Target")) .. ";" .. twodigits(meta:get_float("x0")) .. "]field[1.5,0.5;1,0.8;y0;;" .. twodigits(meta:get_float("y0")) .. "]field[2.75,0.5;1,0.8;z0;;" .. twodigits(meta:get_float("z0")) .. "]field[4,0.5;1,0.8;speed;" .. F(S("Speed")) .. ";" .. twodigits(meta:get_float("speed")) .. "]field[0.25,1.75;1,0.8;energy;" .. F(S("Energy")) .. ";" .. meta:get_int("energy") .. "]field[1.5,1.75;1,0.8;bounce;" .. F(S("Bounce")) .. ";" .. meta:get_int("bounce") .. "]field[2.75,1.75;1,0.8;gravity;" .. F(S("Gravity")) .. ";" .. twodigits(meta:get_float("gravity")) .. "]field[4,1.75;1,0.8;punchable;" .. F(S("Punch.")) .. ";" .. meta:get_int("punchable") .. "]tooltip[4,1.35;1,0.4;" .. F(S("Punchable")) .. "]field[0.25,3;1,0.8;hp;" .. F(S("HP")) .. ";" .. twodigits(meta:get_float("hp")) .. "]field[1.5,3;1,0.8;hurt;" .. F(S("Hurt")) .. ";" .. twodigits(meta:get_float("hurt")) .. "]" .. field_lifetime .. "field[4,3;1,0.8;solid;" .. F(S("Solid")) .. ";" .. meta:get_int("solid") .. "]field[0.25,4.25;4.75,0.8;texture;" .. F(S("Texture")) .. ";" .. F(meta:get_string("texture")) .. "]field[0.25,5.5;1,0.8;scale;" .. F(S("Scale")) .. ";" .. meta:get_int("scale") .. "]field[1.5,5.5;1,0.8;visual;" .. F(S("Visual")) .. ";" .. F(meta:get_string("visual")) .. "]button[2.75,5.5;1,0.8;help;" .. F(S("help")) .. "]button_exit[4,5.5;1,0.8;OK;" .. F(S("OK")) .. "]") end minetest.register_node("basic_machines:ball_spawner", { description = S("Ball Spawner"), groups = {cracky = 3, oddly_breakable_by_hand = 1}, drawtype = "allfaces", tiles = {"basic_machines_ball.png"}, use_texture_alpha = "clip", paramtype = "light", param1 = 1, walkable = false, sounds = basic_machines.sound_node_machine(), drop = "", after_place_node = function(pos, placer) if not placer then return end local meta, name = minetest.get_meta(pos), placer:get_player_name() meta:set_string("owner", name) local privs = minetest.get_player_privs(name) if privs.privs then meta:set_int("admin", 1) end if privs.machines then meta:set_int("machines", 1) end meta:set_float("x0", ball_default.x0) -- target meta:set_float("y0", ball_default.y0) meta:set_float("z0", ball_default.z0) meta:set_float("speed", ball_default.speed) -- if positive sets initial ball speed meta:set_int("energy", ball_default.energy) -- if positive activates, negative deactivates, 0 does nothing meta:set_int("bounce", ball_default.bounce) -- if nonzero bounces when hit obstacle, 0 gets absorbed meta:set_float("gravity", ball_default.gravity) -- if 0 not punchable, if 1 can be punched by players in protection, if 2 can be punched by anyone meta:set_int("punchable", ball_default.punchable) meta:set_float("hp", ball_default.hp) meta:set_float("hurt", ball_default.hurt) meta:set_int("lifetime", ball_default.lifetime) meta:set_int("solid", ball_default.solid) meta:set_string("texture", ball_default.texture) meta:set_int("scale", ball_default.scale) meta:set_string("visual", ball_default.visual) -- cube or sprite meta:set_int("t", 0); meta:set_int("T", 0) ball_spawner_update_form(meta) end, after_dig_node = function(pos, _, oldmetadata, digger) local stack; local inv = digger:get_inventory() if (digger:get_player_control() or {}).sneak then stack = ItemStack("basic_machines:ball_spawner") else local meta = oldmetadata["fields"] meta["formspec"] = nil meta["x0"], meta["y0"], meta["z0"] = nil, nil, nil meta["solid"] = nil meta["scale"] = nil meta["visual"] = nil stack = ItemStack({name = "basic_machines:ball_spell", metadata = minetest.serialize(meta)}) end if inv:room_for_item("main", stack) then inv:add_item("main", stack) else minetest.add_item(pos, stack) end end, on_receive_fields = function(pos, _, fields, sender) local name = sender:get_player_name() if fields.OK then if minetest.is_protected(pos, name) then return end local privs = minetest.check_player_privs(name, "privs") local meta = minetest.get_meta(pos) -- target local x0 = tonumber(fields.x0) or ball_default.x0 local y0 = tonumber(fields.y0) or ball_default.y0 local z0 = tonumber(fields.z0) or ball_default.z0 if not privs and (abs(x0) > max_range or abs(y0) > max_range or abs(z0) > max_range) then return end meta:set_float("x0", ("%.2f"):format(x0)) meta:set_float("y0", ("%.2f"):format(y0)) meta:set_float("z0", ("%.2f"):format(z0)) -- speed local speed = tonumber(fields.speed) or ball_default.speed if (speed < -10 or speed > 10) and not privs then return end meta:set_float("speed", ("%.2f"):format(speed)) -- energy local energy = tonumber(fields.energy) or ball_default.energy if energy < -1 or energy > 1 then return end meta:set_int("energy", energy) -- bounce local bounce = tonumber(fields.bounce) or ball_default.bounce if bounce < 0 or bounce > 2 then return end meta:set_int("bounce", bounce) -- gravity local gravity = tonumber(fields.gravity) or ball_default.gravity if (gravity < 0 or gravity > 40) and not privs then return end meta:set_float("gravity", ("%.2f"):format(gravity)) -- punchable local punchable = tonumber(fields.punchable) or ball_default.punchable if punchable < 0 or punchable > 2 then return end meta:set_int("punchable", punchable) -- hp local hp = tonumber(fields.hp) or ball_default.hp if hp < 0 then return end meta:set_float("hp", ("%.2f"):format(hp)) -- hurt local hurt = tonumber(fields.hurt) or ball_default.hurt if hurt > max_damage and not privs then return end meta:set_float("hurt", ("%.2f"):format(hurt)) if fields.lifetime then local lifetime = tonumber(fields.lifetime) or ball_default.lifetime if lifetime <= 0 then lifetime = ball_default.lifetime end meta:set_int("lifetime", lifetime) end -- solid local solid = tonumber(fields.solid) or ball_default.solid if solid < 0 or solid > 1 then return end meta:set_int("solid", solid) -- texture local texture = fields.texture or "" if texture:len() > 512 and not privs then return end meta:set_string ("texture", texture) -- scale local scale = tonumber(fields.scale) or ball_default.scale if scale < 1 or scale > 1000 and not privs then return end meta:set_int("scale", scale) -- visual local visual = fields.visual if visual ~= "cube" and visual ~= "sprite" then return end meta:set_string ("visual", fields.visual) ball_spawner_update_form(meta) elseif fields.help then local lifetime = minetest.get_meta(pos):get_int("admin") == 1 and S("\nLifetime: [1, +∞[") or "" minetest.show_formspec(name, "basic_machines:help_ball", "formspec_version[4]size[8,9.3]textarea[0,0.35;8,8.95;help;" .. F(S("Ball spawner help")) .. ";" .. F(S([[Values: Target*: Direction of velocity x: [-@1, @2], y: [-@3, @4], z: [-@5, @6] Speed: [-10, 10] Energy: [-1, 1] Bounce**: [0, 2] Gravity: [0, 40] Punchable***: [0, 2] Hp: [0, +∞[ Hurt: ]-∞, @7]@8 Solid*: [0, 1] Texture: Texture name with extension, up to 512 characters Scale*: [1, 1000] Visual*: "cube" or "sprite" *: Not available as individual Ball Spawner **: Set to 2, the ball bounces following y direction@9 ***: 0: not punchable, 1: only in protected area, 2: everywhere Note: Hold sneak while digging to get the Ball Spawner ]], max_range, max_range, max_range, max_range, max_range, max_range, max_damage, lifetime, bounce_materials_help)) .. "]") end end, effector = { action_on = function(pos, _) local meta = minetest.get_meta(pos) local t0, t1 = meta:get_int("t"), minetest.get_gametime() local T = meta:get_int("T") -- temperature if t0 > t1 - 2 * machines_minstep then -- activated before natural time T = T + 1 elseif T > 0 then if t1 - t0 > machines_timer then -- reset temperature if more than 5s (by default) elapsed since last activation T = 0; meta:set_string("infotext", "") else T = T - 1 end end meta:set_int("t", t1); meta:set_int("T", T) if T > 2 then -- overheat minetest.sound_play(basic_machines.sound_overheat, {pos = pos, max_hear_distance = 16, gain = 0.25}, true) meta:set_string("infotext", S("Overheat! Temperature: @1", T)) return end local owner = meta:get_string("owner"); if owner == "" then return end if meta:get_int("machines") ~= 1 then -- no machines priv, limit ball count local count = ballcount[owner] if not count or count < 0 then count = 0 end if count >= max_balls then if max_balls > 0 and t1 - t0 > 10 then count = 0 else return end end ballcount[owner] = count + 1 end local obj = minetest.add_entity(pos, "basic_machines:ball") if obj then local lua_entity = obj:get_luaentity(); lua_entity._origin, lua_entity._owner = pos, owner -- x, y , z local x0, y0, z0 = meta:get_float("x0"), meta:get_float("y0"), meta:get_float("z0") -- direction of velocity -- speed local speed = meta:get_float("speed") 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 obj:set_velocity(vector.divide(velocity, v)) end lua_entity._speed = speed -- energy local energy = meta:get_int("energy") -- if positive activates, negative deactivates, 0 does nothing if energy ~= 0 then -- make the ball glow obj:set_properties({glow = 9}) end local colorize = energy < 0 and "^[colorize:blue:120" or "" lua_entity._energy = energy -- bounce lua_entity._bounce = meta:get_int("bounce") -- if nonzero bounces when hit obstacle, 0 gets absorbed -- gravity obj:set_acceleration({x = 0, y = -meta:get_float("gravity"), z = 0}) -- punchable -- if 0 not punchable, if 1 can be punched by players in protection, if 2 can be punched by anyone lua_entity._punchable = meta:get_int("punchable") -- hp obj:set_hp(meta:get_float("hp")) -- hurt lua_entity._hurt = meta:get_float("hurt") -- lifetime if meta:get_int("admin") == 1 then lua_entity._lifetime = meta:get_int("lifetime") end -- solid if meta:get_int("solid") == 1 then obj:set_properties({physical = true}) lua_entity._solid = 1 end local visual = meta:get_string("visual") -- texture local texture = meta:get_string("texture") .. colorize if visual == "cube" then obj:set_properties({textures = {texture, texture, texture, texture, texture, texture}}) elseif visual == "sprite" then obj:set_properties({textures = {texture}}) end -- scale local scale = meta:get_int("scale"); scale = scale / scale_factor obj:set_properties({visual_size = {x = scale, y = scale}}) -- visual obj:set_properties({visual = visual}) end end, action_off = function(pos, _) local meta = minetest.get_meta(pos) local t0, t1 = meta:get_int("t"), minetest.get_gametime() local T = meta:get_int("T") -- temperature if t0 > t1 - 2 * machines_minstep then -- activated before natural time T = T + 1 elseif T > 0 then if t1 - t0 > machines_timer then -- reset temperature if more than 5s (by default) elapsed since last activation T = 0; meta:set_string("infotext", "") else T = T - 1 end end meta:set_int("t", t1); meta:set_int("T", T) if T > 2 then -- overheat minetest.sound_play(basic_machines.sound_overheat, {pos = pos, max_hear_distance = 16, gain = 0.25}, true) meta:set_string("infotext", S("Overheat! Temperature: @1", T)) return end local owner = meta:get_string("owner"); if owner == "" then return end if meta:get_int("machines") ~= 1 then -- no machines priv, limit ball count local count = ballcount[owner] if not count or count < 0 then count = 0 end if count >= max_balls then if max_balls > 0 and t1 - t0 > 10 then count = 0 else return end end ballcount[owner] = count + 1 end local obj = minetest.add_entity(pos, "basic_machines:ball") if obj then local lua_entity = obj:get_luaentity(); lua_entity._origin, lua_entity._owner = pos, owner -- x, y , z local x0, y0, z0 = meta:get_float("x0"), meta:get_float("y0"), meta:get_float("z0") -- direction of velocity -- speed local speed = meta:get_float("speed") 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 obj:set_velocity(vector.divide(velocity, v)) end lua_entity._speed = speed -- energy obj:set_properties({glow = 9}) -- make the ball glow obj:get_luaentity()._energy = -1 -- hp obj:set_hp(meta:get_float("hp")) -- lifetime if meta:get_int("admin") == 1 then lua_entity._lifetime = meta:get_int("lifetime") end local visual = meta:get_string("visual") -- texture local texture = meta:get_string("texture") .. "^[colorize:blue:120" if visual == "cube" then obj:set_properties({textures = {texture, texture, texture, texture, texture, texture}}) elseif visual == "sprite" then obj:set_properties({textures = {texture}}) end -- visual obj:set_properties({visual = visual}) end end } }) local spelltime = {} minetest.register_on_leaveplayer(function(player) local name = player:get_player_name() ballcount[name] = nil spelltime[name] = nil end) -- ball as magic spell user can cast minetest.register_tool("basic_machines:ball_spell", { description = S("Ball Spell"), groups = {not_in_creative_inventory = 1}, inventory_image = "basic_machines_ball.png", light_source = 10, tool_capabilities = { full_punch_interval = 2, max_drop_level = 0 }, on_use = function(itemstack, user) if not user then return end local pos = user:get_pos(); pos.y = pos.y + 1 local meta = minetest.deserialize(itemstack:get_meta():get_string("")) or {} local owner = user:get_player_name() local privs = minetest.check_player_privs(owner, "privs") -- if minetest.is_protected(pos, owner) then return end local t1 = minetest.get_gametime() if t1 - (spelltime[owner] or 0) < 2 then return end -- too soon spelltime[owner] = t1 local obj = minetest.add_entity(pos, "basic_machines:ball") if obj then local lua_entity = obj:get_luaentity(); lua_entity._origin, lua_entity._owner = pos, owner -- speed local speed = tonumber(meta["speed"]) or ball_default.speed speed = privs and speed or math.min(math.max(speed, -10), 10) obj:set_velocity(vector.multiply(user:get_look_dir(), speed)) lua_entity._speed = speed -- energy -- if positive activates, negative deactivates, 0 does nothing local energy = tonumber(meta["energy"]) or ball_default.energy if energy ~= 0 then -- make the ball glow obj:set_properties({glow = 9}) end local colorize = energy < 0 and "^[colorize:blue:120" or "" lua_entity._energy = energy -- bounce -- if nonzero bounces when hit obstacle, 0 gets absorbed lua_entity._bounce = tonumber(meta["bounce"]) or ball_default.bounce -- gravity local gravity = tonumber(meta["gravity"]) or ball_default.gravity gravity = privs and gravity or math.min(math.max(gravity, 0.1), 40) obj:set_acceleration({x = 0, y = -gravity , z = 0}) -- punchable -- if 0 not punchable, if 1 can be punched by players in protection, if 2 can be punched by anyone lua_entity._punchable = tonumber(meta["punchable"]) or ball_default.punchable -- hp obj:set_hp(tonumber(meta["hp"]) or ball_default.hp) -- hurt local hurt = tonumber(meta["hurt"]) or ball_default.hurt hurt = privs and hurt or math.min(hurt, max_damage) lua_entity._hurt = hurt -- lifetime if privs then lua_entity._lifetime = tonumber(meta["lifetime"]) or ball_default.lifetime end -- texture local texture = meta["texture"] or ball_default.texture if texture:len() > 512 and not privs then texture = texture:sub(1, 512) end obj:set_properties({textures = {texture .. colorize}}) end end }) if basic_machines.settings.register_crafts then minetest.register_craft({ output = "basic_machines:ball_spawner", recipe = { {"basic_machines:power_cell"}, {"basic_machines:keypad"} } }) end