cleaning againnn, fix /clockgen, read float with 2 digits precision mark: looop, add 2 markers mover: reeorganize things, reduce infotext overwriting, add upgradetype, add friendly overheat, add 2 buttons to formspec recycler: no fuel check with priv privs
774 lines
26 KiB
Lua
774 lines
26 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
|
|
|
|
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 / 2 -- 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 = {
|
|
["default:glass"] = 2, ["default:wood"] = 1
|
|
}
|
|
|
|
if minetest.get_modpath("darkage") then
|
|
bounce_materials["darkage:iron_bars"] = 1
|
|
end
|
|
|
|
if minetest.get_modpath("xpanes") then
|
|
bounce_materials["xpanes:bar_10"] = 1
|
|
bounce_materials["xpanes:bar_2"] = 1
|
|
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 use_boneworld = minetest.global_exists("boneworld")
|
|
|
|
local function round(x)
|
|
if x < 0 then
|
|
return -math.floor(-x + 0.5)
|
|
else
|
|
return math.floor(x + 0.5)
|
|
end
|
|
end
|
|
|
|
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
|
|
},
|
|
|
|
_origin = {
|
|
x = ball_default.x0,
|
|
y = ball_default.y0,
|
|
z = ball_default.z0
|
|
},
|
|
_owner = "",
|
|
_elasticity = 0.9, -- speed gets multiplied by this after bounce
|
|
_is_arrow = false, -- advanced mob protection
|
|
_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
|
|
_gravity = ball_default.gravity,
|
|
_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
|
|
|
|
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 nodename = minetest.get_node(pos).name
|
|
local walkable = false
|
|
if nodename ~= "air" then
|
|
walkable = minetest.registered_nodes[nodename].walkable
|
|
-- ball can activate spawner, just not originating one
|
|
if nodename == "basic_machines:ball_spawner" and dist > 0.5 then walkable = true end
|
|
end
|
|
|
|
if not walkable then
|
|
if 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
|
|
local p, d = obj:get_pos(), 0
|
|
if p then
|
|
d = math.sqrt((p.x - pos.x)^2 + (p.y - pos.y)^2 + (p.z - pos.z)^2)
|
|
end
|
|
if d > 0 then
|
|
-- if minetest.is_protected(p, self._owner) then break end
|
|
-- if abs(p.x) < 32 and abs(p.y) < 32 and abs(p.z) < 32 then break end -- no damage around spawn
|
|
|
|
if obj:is_player() then -- player
|
|
if obj:get_player_name() == self._owner then break end -- don't hurt owner
|
|
|
|
local newhp = obj:get_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)
|
|
else -- 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 arrows harm mobs
|
|
elseif self._is_arrow and lua_entity.protected == 2 then
|
|
break
|
|
end
|
|
end
|
|
local newhp = obj:get_hp() - self._hurt
|
|
minetest.chat_send_player(self._owner, S("#BALL: target hp @1", newhp))
|
|
if newhp > 0 then obj:set_hp(newhp) else obj:remove() end
|
|
end
|
|
|
|
self.object:remove(); break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
elseif 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 def = minetest.registered_nodes[node.name]
|
|
if def and (def.effector or def.mesecons and def.mesecons.effector) then -- activate target
|
|
local energy = self._energy
|
|
|
|
if energy ~= 0 and minetest.is_protected(pos, self._owner) then
|
|
return
|
|
end
|
|
|
|
local effector = def.effector or def.mesecons.effector
|
|
local param = def.effector and machines_TTL or node
|
|
|
|
self.object:remove()
|
|
|
|
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
|
|
else -- bounce (copyright rnd, 2016)
|
|
local bounce = self._bounce
|
|
|
|
if bounce == 0 then
|
|
self.object:remove(); return
|
|
end
|
|
|
|
local n = {x = 0, y = 0, z = 0} -- this will be bounce normal
|
|
local v = self.object:get_velocity()
|
|
|
|
if bounce == 2 then -- 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
|
|
else
|
|
-- algorithm to determine bounce direction - problem:
|
|
-- with lag it's 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 opos = {x = round(pos.x), y = round(pos.y), z = round(pos.z)} -- obstacle
|
|
local bpos = vector.subtract(pos, opos) -- boundary position on cube, approximate
|
|
local dpos = {x = 0.5 * n.x, y = 0.5 * n.y, z = 0.5 * n.z} -- 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
|
|
|
|
nodename = minetest.get_node(vector.add(opos, n)).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(vector.add(opos, n)).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(vector.add(opos, n)).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 bpos = vector.add(pos, vector.multiply(n, 0.2)) -- point placed a bit further away from box
|
|
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
|
|
|
|
self.object:set_pos(bpos) -- place object at last known outside point
|
|
self.object:set_velocity(v)
|
|
|
|
minetest.sound_play("default_dig_cracky", {pos = pos, gain = 1, max_hear_distance = 8}, true)
|
|
end
|
|
end
|
|
end,
|
|
|
|
on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
|
|
if self._punchable == 0 then return end
|
|
if self._punchable == 1 then -- only those in protection
|
|
local obj_pos = self.object:get_pos()
|
|
if not minetest.is_protected(obj_pos) or
|
|
not puncher or minetest.is_protected(obj_pos, puncher:get_player_name())
|
|
then
|
|
return
|
|
end
|
|
end
|
|
if time_from_last_punch < 0.5 then return end
|
|
self.object:set_velocity(vector.multiply(dir, (self._speed or ball_default.speed)))
|
|
end
|
|
})
|
|
|
|
local function ball_spawner_update_form(meta)
|
|
local field_lifetime = ""
|
|
|
|
if meta:get_int("admin") == 1 then
|
|
field_lifetime = ("field[2.25,2.5;1,1;lifetime;%s;%i]"):format(F(S("Lifetime")), meta:get_int("lifetime"))
|
|
end
|
|
|
|
meta:set_string("formspec", ([[
|
|
size[4,5]
|
|
field[0.25,0.5;1,1;x0;%s;%.2f]
|
|
field[1.25,0.5;1,1;y0;;%.2f]
|
|
field[2.25,0.5;1,1;z0;;%.2f]
|
|
field[3.25,0.5;1,1;speed;%s;%.2f]
|
|
field[0.25,1.5;1,1;energy;%s;%i]
|
|
field[1.25,1.5;1,1;bounce;%s;%i]
|
|
field[2.25,1.5;1,1;gravity;%s;%.2f]
|
|
field[3.25,1.5;1,1;punchable;%s;%i]
|
|
tooltip[2.95,0.97;0.8,0.34;%s]
|
|
field[0.25,2.5;1,1;hp;%s;%.2f]
|
|
field[1.25,2.5;1,1;hurt;%s;%.2f]
|
|
%s
|
|
field[3.25,2.5;1,1;solid;%s;%i]
|
|
field[0.25,3.5;4,1;texture;%s;%s]
|
|
field[0.25,4.5;1,1;scale;%s;%i]
|
|
field[1.25,4.5;1,1;visual;%s;%s]
|
|
button[2,4.2;1,1;help;%s]button_exit[3,4.2;1,1;OK;%s]
|
|
]]):format(F(S("Target")), meta:get_float("x0"), meta:get_float("y0"), meta:get_float("z0"),
|
|
F(S("Speed")), meta:get_float("speed"), F(S("Energy")), meta:get_int("energy"),
|
|
F(S("Bounce")), meta:get_int("bounce"), F(S("Gravity")), meta:get_float("gravity"),
|
|
F(S("Punch.")), meta:get_int("punchable"), F(S("Punchable")),
|
|
F(S("HP")), meta:get_float("hp"), F(S("Hurt")), meta:get_float("hurt"),
|
|
field_lifetime, F(S("Solid")), meta:get_int("solid"),
|
|
F(S("Texture")), F(meta:get_string("texture")), F(S("Scale")), meta:get_int("scale"),
|
|
F(S("Visual")), meta:get_string("visual"), F(S("help")), F(S("OK"))
|
|
))
|
|
end
|
|
|
|
-- 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_materialslist, dirs, i = {}, {"x", "z"}, 1
|
|
for material, v in pairs(bounce_materials) do
|
|
bounce_materialslist[i] = ("%s: %s"):format(material, dirs[v]); i = i + 1
|
|
end
|
|
bounce_materialslist = table.concat(bounce_materialslist, "\n")
|
|
|
|
minetest.register_node("basic_machines:ball_spawner", {
|
|
description = S("Spawns energy ball"),
|
|
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 = default.node_sound_wood_defaults(),
|
|
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, oldnode, 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, formname, 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)
|
|
|
|
-- minetest.chat_send_all("form at " .. dump(pos) .. " fields " .. dump(fields))
|
|
|
|
-- 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.1 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",
|
|
"size[6,7]textarea[0,0;6.5,8.5;help;" .. F(S("BALL SPAWNER CAPABILITIES")) .. ";" .. 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.1, 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 bounce following y direction and for the next blocks:
|
|
@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_materialslist)) .. "]")
|
|
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 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("default_cool_lava", {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 luaent = obj:get_luaentity(); luaent._origin, luaent._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
|
|
luaent._speed = speed
|
|
|
|
-- energy
|
|
local energy = meta:get_int("energy") -- if positive activates, negative deactivates, 0 does nothing
|
|
local colorize = energy < 0 and "^[colorize:blue:120" or ""
|
|
luaent._energy = energy
|
|
|
|
-- bounce
|
|
luaent._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
|
|
luaent._punchable = meta:get_int("punchable")
|
|
|
|
-- hp
|
|
obj:set_hp(meta:get_float("hp"))
|
|
|
|
-- hurt
|
|
local hurt = meta:get_float("hurt")
|
|
if hurt > 0 then luaent._is_arrow = true end -- tell advanced mob protection this is an arrow
|
|
luaent._hurt = hurt
|
|
|
|
-- lifetime
|
|
if meta:get_int("admin") == 1 then
|
|
luaent._lifetime = meta:get_int("lifetime")
|
|
end
|
|
|
|
-- solid
|
|
if meta:get_int("solid") == 1 then
|
|
obj:set_properties({physical = true})
|
|
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 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("default_cool_lava", {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 luaent = obj:get_luaentity(); luaent._origin, luaent._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
|
|
luaent._speed = speed
|
|
|
|
-- energy
|
|
obj:get_luaentity()._energy = -1
|
|
|
|
-- hp
|
|
obj:set_hp(meta:get_float("hp"))
|
|
|
|
-- lifetime
|
|
if meta:get_int("admin") == 1 then
|
|
luaent._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 Spawner"),
|
|
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, pointed_thing)
|
|
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 luaent = obj:get_luaentity(); luaent._origin, luaent._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))
|
|
luaent._speed = speed
|
|
|
|
-- energy
|
|
-- if positive activates, negative deactivates, 0 does nothing
|
|
local energy = tonumber(meta["energy"]) or ball_default.energy
|
|
local colorize = energy < 0 and "^[colorize:blue:120" or ""
|
|
luaent._energy = energy
|
|
|
|
-- bounce
|
|
-- if nonzero bounces when hit obstacle, 0 gets absorbed
|
|
luaent._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
|
|
luaent._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)
|
|
if hurt > 0 then luaent._is_arrow = true end -- tell advanced mob protection this is an arrow
|
|
luaent._hurt = hurt
|
|
|
|
-- lifetime
|
|
if privs then
|
|
luaent._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 |