From 046bca1080e7f51993954e375f9eb2049b12c337 Mon Sep 17 00:00:00 2001 From: rootyjr <41842051+Rootyjr@users.noreply.github.com> Date: Mon, 8 Jun 2020 00:51:48 -0500 Subject: [PATCH 1/2] implemented ability to detect when seen / break eye contact and aggressive response / implemented teleport to avoid arrows. / implemented teleport to avoid rain. / implemented teleport to chase. / added enderman particles. / drew particles 1 through 5 / added rain damage. / fixed the grass_with_dirt issue. --- mods/ENTITIES/mcl_mobs/api.lua | 10 + mods/ENTITIES/mobs_mc/enderman.lua | 214 +++++++++++++++--- mods/ENTITIES/mobs_mc_gameconfig/init.lua | 15 +- mods/ITEMS/mcl_bows/arrow.lua | 19 +- .../textures/mcl_portals_particle1.png | Bin 0 -> 603 bytes .../textures/mcl_portals_particle2.png | Bin 0 -> 602 bytes .../textures/mcl_portals_particle3.png | Bin 0 -> 606 bytes .../textures/mcl_portals_particle4.png | Bin 0 -> 606 bytes .../textures/mcl_portals_particle5.png | Bin 0 -> 618 bytes 9 files changed, 220 insertions(+), 38 deletions(-) create mode 100644 mods/ITEMS/mcl_portals/textures/mcl_portals_particle1.png create mode 100644 mods/ITEMS/mcl_portals/textures/mcl_portals_particle2.png create mode 100644 mods/ITEMS/mcl_portals/textures/mcl_portals_particle3.png create mode 100644 mods/ITEMS/mcl_portals/textures/mcl_portals_particle4.png create mode 100644 mods/ITEMS/mcl_portals/textures/mcl_portals_particle5.png diff --git a/mods/ENTITIES/mcl_mobs/api.lua b/mods/ENTITIES/mcl_mobs/api.lua index 5f44d656f..ecf6f61be 100644 --- a/mods/ENTITIES/mcl_mobs/api.lua +++ b/mods/ENTITIES/mcl_mobs/api.lua @@ -2581,6 +2581,14 @@ local falling = function(self, pos) end end +local teleport = function(self, target) + if self.do_teleport then + if self.do_teleport(self, target) == false then + return + end + end +end + -- deal damage and effects when mob punched local mob_punch = function(self, hitter, tflp, tool_capabilities, dir) @@ -3393,6 +3401,8 @@ minetest.register_entity(name, { _cmi_is_mob = true, -- MCL2 extensions + teleport = teleport, + do_teleport = def.do_teleport, spawn_class = def.spawn_class, ignores_nametag = def.ignores_nametag or false, rain_damage = def.rain_damage or 0, diff --git a/mods/ENTITIES/mobs_mc/enderman.lua b/mods/ENTITIES/mobs_mc/enderman.lua index c159c92f5..67129277f 100644 --- a/mods/ENTITIES/mobs_mc/enderman.lua +++ b/mods/ENTITIES/mobs_mc/enderman.lua @@ -10,6 +10,21 @@ -- and they are provoked by looking directly at them. -- TODO: Implement MC behaviour. +-- Rootyjr +----------------------------- +-- implemented ability to detect when seen / break eye contact and aggressive response +-- implemented teleport to avoid arrows. +-- implemented teleport to avoid rain. +-- implemented teleport to chase. +-- added enderman particles. +-- drew mcl_portal_particle1.png +-- drew mcl_portal_particle2.png +-- drew mcl_portal_particle3.png +-- drew mcl_portal_particle4.png +-- drew mcl_portal_particle5.png +-- added rain damage. +-- fixed the grass_with_dirt issue. + local S = minetest.get_translator("mobs_mc") --################### @@ -163,13 +178,13 @@ local select_enderman_animation = function(animation_type) end end -local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false +local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false mobs:register_mob("mobs_mc:enderman", { -- TODO: Endermen should be classified as passive type = "monster", spawn_class = "passive", - passive = false, + passive = true, pathfinding = 1, hp_min = 40, hp_max = 40, @@ -197,7 +212,116 @@ mobs:register_mob("mobs_mc:enderman", { }, animation = select_enderman_animation("normal"), _taken_node = "", + -- TODO: Teleport enderman on damage, etc. do_custom = function(self, dtime) + -- PARTICLE BEHAVIOUR HERE. + local enderpos = self.object:get_pos() + local chanceOfParticle = math.random(0, 1) + if chanceOfParticle == 1 then + minetest.add_particle({ + pos = {x=enderpos.x+math.random(-1,1)*math.random()/2,y=enderpos.y+math.random(0,3),z=enderpos.z+math.random(-1,1)*math.random()/2}, + velocity = {x=math.random(-.25,.25), y=math.random(-.25,.25), z=math.random(-.25,.25)}, + acceleration = {x=math.random(-.5,.5), y=math.random(-.5,.5), z=math.random(-.5,.5)}, + expirationtime = math.random(), + size = math.random(), + collisiondetection = true, + vertical = false, + texture = "mcl_portals_particle"..math.random(1, 5)..".png", + }) + end + -- RAIN DAMAGE / EVASIVE WARP BEHAVIOUR HERE. + if mcl_weather.state == "rain" or mcl_weather.state == "lightning" then + local damage = true + local enderpos = self.object:get_pos() + enderpos.y = enderpos.y+2.89 + local height = {x=enderpos.x, y=enderpos.y+512,z=enderpos.z} + local ray = minetest.raycast(enderpos, height, true) + -- Check for blocks above enderman. + for pointed_thing in ray do + if pointed_thing.type == "node" then + local nn = minetest.get_node(minetest.get_pointed_thing_position(pointed_thing)).name + local def = minetest.registered_nodes[nn] + if (not def) or def.walkable then + -- There's a node in the way. Delete arrow without damage + damage = false + break + end + end + end + + if damage == true then + self.state = "" + --rain hurts enderman + self.object:punch(self.object, 1.0, { + full_punch_interval=1.0, + damage_groups={fleshy=self._damage}, + }, nil) + --randomly teleport hopefully under something. + self:teleport(nil) + end + end + -- AGRESSIVELY WARP/CHASE PLAYER BEHAVIOUR HERE. + if self.state == "attack" then + target = self.attack + if vector.distance(self.object:get_pos(), target:get_pos()) > 10 then + self:teleport(target) + end + end + -- ARROW AVOIDANCE BEHAVIOUR HERE. + -- Check for arrows nearby. + local enderpos = self.object:get_pos() + local objs = minetest.get_objects_inside_radius(enderpos, 4) + for n = 1, #objs do + obj = objs[n] + if obj then + lua = obj:get_luaentity() + if lua then + if lua.name == "mcl_bows:arrow_entity" then + self:teleport(nil) + end + end + end + end + -- PROVOKED BEHAVIOUR HERE. + local enderpos = self.object:get_pos() + if self.provoked == "broke_contact" then + self.provoked = "false" + self.state = 'attack' + end + -- Check to see if people are near by enough to look at us. + local objs = minetest.get_objects_inside_radius(enderpos, 64) + for n = 1, #objs do + obj = objs[n] + if obj then + if minetest.is_player(obj) then + -- Check if they are looking at us. + local player_pos = obj:get_pos() + local look_dir_not_normalized = obj:get_look_dir() + local look_dir = vector.normalize(look_dir_not_normalized) + local look_pos = vector.new({x = look_dir.x+player_pos.x, y = look_dir.y+player_pos.y + 1.5, z = look_dir.z+player_pos.z}) -- Arbitrary value (1.5) is head level according to player info mod. + -- Cast up to 64 to see if player is looking at enderman. + for n = 1,64,.25 do + local node = minetest.get_node(look_pos) + if node.name ~= "air" then + break + end + if look_pos.x-1enderpos.x and look_pos.y-2.89enderpos.y and look_pos.z-1enderpos.z then + self.provoked = "staring" + self.attack = minetest.get_player_by_name(obj:get_player_name()) + break + else + if self.provoked == "staring" then + self.provoked = "broke_contact" + end + end + look_pos.x = look_pos.x + (.25 * look_dir.x) + look_pos.y = look_pos.y + (.25 * look_dir.y) + look_pos.z = look_pos.z + (.25 * look_dir.z) + end + end + end + end + -- TAKE AND PLACE STUFF BEHAVIOUR BELOW. if not mobs_griefing then return end @@ -283,33 +407,61 @@ mobs:register_mob("mobs_mc:enderman", { end end end, - -- TODO: Teleport enderman on damage, etc. - _do_teleport = function(self) - -- Attempt to randomly teleport enderman - local pos = self.object:get_pos() - -- Find all solid nodes below air in a 65×65×65 cuboid centered on the enderman - local nodes = minetest.find_nodes_in_area_under_air(vector.subtract(pos, 32), vector.add(pos, 32), {"group:solid", "group:cracky", "group:crumbly"}) - local telepos - if #nodes > 0 then - -- Up to 64 attempts to teleport - for n=1, math.min(64, #nodes) do - local r = pr:next(1, #nodes) - local nodepos = nodes[r] - local node_ok = true - -- Selected node needs to have 3 nodes of free space above - for u=1, 3 do - local node = minetest.get_node({x=nodepos.x, y=nodepos.y+u, z=nodepos.z}) - if minetest.registered_nodes[node.name].walkable then - node_ok = false - break + do_teleport = function(self, target) + if target ~= nil then + local target_pos = target:get_pos() + -- Find all solid nodes below air in a 10×10×10 cuboid centered on the target + local nodes = minetest.find_nodes_in_area_under_air(vector.subtract(target_pos, 5), vector.add(target_pos, 5), {"group:solid", "group:cracky", "group:crumbly"}) + local telepos + if #nodes > 0 then + -- Up to 64 attempts to teleport + for n=1, math.min(64, #nodes) do + local r = pr:next(1, #nodes) + local nodepos = nodes[r] + local node_ok = true + -- Selected node needs to have 3 nodes of free space above + for u=1, 3 do + local node = minetest.get_node({x=nodepos.x, y=nodepos.y+u, z=nodepos.z}) + if minetest.registered_nodes[node.name].walkable then + node_ok = false + break + end + end + if node_ok then + telepos = {x=nodepos.x, y=nodepos.y+1, z=nodepos.z} end end - if node_ok then - telepos = {x=nodepos.x, y=nodepos.y+1, z=nodepos.z} + if telepos then + self.object:set_pos(telepos) end end - if telepos then - self.object:set_pos(telepos) + else + -- Attempt to randomly teleport enderman + local pos = self.object:get_pos() + -- Find all solid nodes below air in a 65×65×65 cuboid centered on the enderman + local nodes = minetest.find_nodes_in_area_under_air(vector.subtract(pos, 32), vector.add(pos, 32), {"group:solid", "group:cracky", "group:crumbly"}) + local telepos + if #nodes > 0 then + -- Up to 64 attempts to teleport + for n=1, math.min(64, #nodes) do + local r = pr:next(1, #nodes) + local nodepos = nodes[r] + local node_ok = true + -- Selected node needs to have 3 nodes of free space above + for u=1, 3 do + local node = minetest.get_node({x=nodepos.x, y=nodepos.y+u, z=nodepos.z}) + if minetest.registered_nodes[node.name].walkable then + node_ok = false + break + end + end + if node_ok then + telepos = {x=nodepos.x, y=nodepos.y+1, z=nodepos.z} + end + end + if telepos then + self.object:set_pos(telepos) + end end end end, @@ -319,10 +471,16 @@ mobs:register_mob("mobs_mc:enderman", { minetest.add_item(pos, self._taken_node) end end, + do_punch = function(self, hitter, tflp, tool_caps, dir) + -- damage from rain caused by itself so we don't want it to attack itself. + if hitter ~= self.object then + self:teleport(hitter) + self.state="attack" + self.attack=hitter + end + end, water_damage = 8, - -- TODO: Increase view range when it detects being seen - -- Low view range to emulate that behaviour somehow - view_range = 4, + view_range = 64, fear_height = 4, attack_type = "dogfight", }) diff --git a/mods/ENTITIES/mobs_mc_gameconfig/init.lua b/mods/ENTITIES/mobs_mc_gameconfig/init.lua index 80485d950..bcac2256a 100644 --- a/mods/ENTITIES/mobs_mc_gameconfig/init.lua +++ b/mods/ENTITIES/mobs_mc_gameconfig/init.lua @@ -177,11 +177,6 @@ mobs_mc.override.enderman_takable = { "group:enderman_takable", } mobs_mc.override.enderman_replace_on_take = { - -- Turn covered dirt blocks to normal dirt. - -- This is a workaround because the dirt with grass texture fails when held by the enderman - -- (because of the node coloring). - -- FIXME: Remove these lines as soon we support rendering dirt with grass - ["mcl_core:dirt_with_grass"] = "mcl_core:dirt", } mobs_mc.override.misc = { totem_fail_nodes = { "mcl_core:void", "mcl_core:realm_barrier" }, @@ -200,8 +195,18 @@ for i=1, 6 do end table.insert(ctable, cbackground .. "^" .. last) end + mobs_mc.override.enderman_block_texture_overrides = { ["mcl_core:cactus"] = ctable, + -- FIXME: replace colorize colors with colors from palette + ["mcl_core:dirt_with_grass"] = + { + "mcl_core_grass_block_top.png^[colorize:green:90", + "default_dirt.png", + "default_dirt.png^(mcl_core_grass_block_side_overlay.png^[colorize:green:90)", + "default_dirt.png^(mcl_core_grass_block_side_overlay.png^[colorize:green:90)", + "default_dirt.png^(mcl_core_grass_block_side_overlay.png^[colorize:green:90)", + "default_dirt.png^(mcl_core_grass_block_side_overlay.png^[colorize:green:90)"} } -- List of nodes on which mobs can spawn diff --git a/mods/ITEMS/mcl_bows/arrow.lua b/mods/ITEMS/mcl_bows/arrow.lua index 9ae6e85a4..526f37faf 100644 --- a/mods/ITEMS/mcl_bows/arrow.lua +++ b/mods/ITEMS/mcl_bows/arrow.lua @@ -215,11 +215,20 @@ ARROW_ENTITY.on_step = function(self, dtime) end end - -- Punch target object - obj:punch(self.object, 1.0, { - full_punch_interval=1.0, - damage_groups={fleshy=self._damage}, - }, nil) + -- Punch target object but avoid hurting enderman. + if lua then + if lua.name ~= "mobs_mc:enderman" then + obj:punch(self.object, 1.0, { + full_punch_interval=1.0, + damage_groups={fleshy=self._damage}, + }, nil) + end + else + obj:punch(self.object, 1.0, { + full_punch_interval=1.0, + damage_groups={fleshy=self._damage}, + }, nil) + end if is_player then if self._shooter and self._shooter:is_player() then diff --git a/mods/ITEMS/mcl_portals/textures/mcl_portals_particle1.png b/mods/ITEMS/mcl_portals/textures/mcl_portals_particle1.png new file mode 100644 index 0000000000000000000000000000000000000000..6695291eb71d7a5ed55ce112b9c7e125e896baed GIT binary patch literal 603 zcmV-h0;K(kP)EX>4Tx04R}tkv&MmKp2MKrfNkh4rVCgkfAzR5EXHhDi*;)X)CnqU~=gnG%+M8 zE{=k0!NH%!s)LKOt`4q(Aov5~*Zn#A)q=%NmL3aJ%fAG7vR&jE|OA5zR-^Y&AJOP5wz?I(iZ#00JPtxmc zEqVkDYy%h9ZB5w&E_Z;)lOdb3D+Or@#Uk*2M&FbN25*7BRj+TYbDTZ^S?bl&4RCM> zjFu>S&F9^{oqhYarq#b6Di(5zvRmNN00006VoOIv00000008+zyMF)x010qNS#tmY zE+YT{E+YYWr9XB6000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{001*dL_t(I%VS(C^YlLh5(s`G&B#CkU}B&U47>pt{v{M9 p=<>rM3`q4ey8K8?2g5Kg000GD7Ru~wp;iC@002ovPDHLkV1i?v_XPj| literal 0 HcmV?d00001 diff --git a/mods/ITEMS/mcl_portals/textures/mcl_portals_particle2.png b/mods/ITEMS/mcl_portals/textures/mcl_portals_particle2.png new file mode 100644 index 0000000000000000000000000000000000000000..99b3a191a193ee93a17a4a558f66958af5dfd442 GIT binary patch literal 602 zcmV-g0;TEX>4Tx04R}tkv&MmKp2MKrfNkh4rVCgkfAzR5EXHhDi*;)X)CnqU~=gnG%+M8 zE{=k0!NH%!s)LKOt`4q(Aov5~*Zn#A)q=%NmL3aJ%fAG7vR&jE|OA5zR-^Y&AJOP5wz?I(iZ#00JPtxmc zEqVkDYy%h9ZB5w&E_Z;)lOdb3D+Or@#Uk*2M&FbN25*7BRj+TYbDTZ^S?bl&4RCM> zjFu>S&F9^{oqhYarq#b6Di(5zvRmNN00006VoOIv00000008+zyMF)x010qNS#tmY zE+YT{E+YYWr9XB6000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{001&cL_t(I%VV6xaPmI`63AmPXJjA&FfmXF2HpS+{}Kul obot>B2Bi8KU4A5{gJGB#0B8LXce=6(O#lD@07*qoM6N<$f+y$ot^fc4 literal 0 HcmV?d00001 diff --git a/mods/ITEMS/mcl_portals/textures/mcl_portals_particle3.png b/mods/ITEMS/mcl_portals/textures/mcl_portals_particle3.png new file mode 100644 index 0000000000000000000000000000000000000000..3000a799cb528589e8c84628660918ac1d5f7a81 GIT binary patch literal 606 zcmV-k0-^nhP)EX>4Tx04R}tkv&MmKp2MKrfNkh4rVCgkfAzR5EXHhDi*;)X)CnqU~=gnG%+M8 zE{=k0!NH%!s)LKOt`4q(Aov5~*Zn#A)q=%NmL3aJ%fAG7vR&jE|OA5zR-^Y&AJOP5wz?I(iZ#00JPtxmc zEqVkDYy%h9ZB5w&E_Z;)lOdb3D+Or@#Uk*2M&FbN25*7BRj+TYbDTZ^S?bl&4RCM> zjFu>S&F9^{oqhYarq#b6Di(5zvRmNN00006VoOIv00000008+zyMF)x010qNS#tmY zE+YT{E+YYWr9XB6000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{001^gL_t(I%VV6xaFPKTEX>4Tx04R}tkv&MmKp2MKrfNkh4rVCgkfAzR5EXHhDi*;)X)CnqU~=gnG%+M8 zE{=k0!NH%!s)LKOt`4q(Aov5~*Zn#A)q=%NmL3aJ%fAG7vR&jE|OA5zR-^Y&AJOP5wz?I(iZ#00JPtxmc zEqVkDYy%h9ZB5w&E_Z;)lOdb3D+Or@#Uk*2M&FbN25*7BRj+TYbDTZ^S?bl&4RCM> zjFu>S&F9^{oqhYarq#b6Di(5zvRmNN00006VoOIv00000008+zyMF)x010qNS#tmY zE+YT{E+YYWr9XB6000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{001^gL_t(I%VV6xaFPKTEX>4Tx04R}tkv&MmKp2MKrfNkh4rVCgkfAzR5EXHhDi*;)X)CnqU~=gnG%+M8 zE{=k0!NH%!s)LKOt`4q(Aov5~*Zn#A)q=%NmL3aJ%fAG7vR&jE|OA5zR-^Y&AJOP5wz?I(iZ#00JPtxmc zEqVkDYy%h9ZB5w&E_Z;)lOdb3D+Or@#Uk*2M&FbN25*7BRj+TYbDTZ^S?bl&4RCM> zjFu>S&F9^{oqhYarq#b6Di(5zvRmNN00006VoOIv00000008+zyMF)x010qNS#tmY zE+YT{E+YYWr9XB6000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{002TsL_t(I%VV6xaFPKT<^TWy07*qoM6N<$ Ef}|+`p8x;= literal 0 HcmV?d00001 From a7b9107d3125d0a49336c06a83951a69a65a6f77 Mon Sep 17 00:00:00 2001 From: Rootyjr Date: Fri, 12 Jun 2020 01:22:01 -0500 Subject: [PATCH 2/2] Fix enderman griefing in protected areas. --- mods/ENTITIES/mobs_mc/enderman.lua | 82 ++++++++++++++++-------------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/mods/ENTITIES/mobs_mc/enderman.lua b/mods/ENTITIES/mobs_mc/enderman.lua index 67129277f..36a90d66c 100644 --- a/mods/ENTITIES/mobs_mc/enderman.lua +++ b/mods/ENTITIES/mobs_mc/enderman.lua @@ -342,44 +342,47 @@ mobs:register_mob("mobs_mc:enderman", { local r = pr:next(1, #takable_nodes) local take_pos = takable_nodes[r] local node = minetest.get_node(take_pos) - local dug = minetest.dig_node(take_pos) - if dug then - if mobs_mc.enderman_replace_on_take[node.name] then - self._taken_node = mobs_mc.enderman_replace_on_take[node.name] - else - self._taken_node = node.name - end - local def = minetest.registered_nodes[self._taken_node] - -- Update animation and texture accordingly (adds visibly carried block) - local block_type - -- Cube-shaped - if def.drawtype == "normal" or - def.drawtype == "nodebox" or - def.drawtype == "liquid" or - def.drawtype == "flowingliquid" or - def.drawtype == "glasslike" or - def.drawtype == "glasslike_framed" or - def.drawtype == "glasslike_framed_optional" or - def.drawtype == "allfaces" or - def.drawtype == "allfaces_optional" or - def.drawtype == nil then - block_type = "cube" - elseif def.drawtype == "plantlike" then - -- Flowers and stuff - block_type = "plantlike45" - elseif def.drawtype == "airlike" then - -- Just air - block_type = nil - else - -- Fallback for complex drawtypes - block_type = "unknown" - end - self.base_texture = create_enderman_textures(block_type, self._taken_node) - self.object:set_properties({ textures = self.base_texture }) - self.animation = select_enderman_animation("block") - mobs:set_animation(self, self.animation.current) - if def.sounds and def.sounds.dug then - minetest.sound_play(def.sounds.dug, {pos = take_pos, max_hear_distance = 16}, true) + -- Don't destroy protected stuff. + if not minetest.is_protected(take_pos, "") then + local dug = minetest.dig_node(take_pos) + if dug then + if mobs_mc.enderman_replace_on_take[node.name] then + self._taken_node = mobs_mc.enderman_replace_on_take[node.name] + else + self._taken_node = node.name + end + local def = minetest.registered_nodes[self._taken_node] + -- Update animation and texture accordingly (adds visibly carried block) + local block_type + -- Cube-shaped + if def.drawtype == "normal" or + def.drawtype == "nodebox" or + def.drawtype == "liquid" or + def.drawtype == "flowingliquid" or + def.drawtype == "glasslike" or + def.drawtype == "glasslike_framed" or + def.drawtype == "glasslike_framed_optional" or + def.drawtype == "allfaces" or + def.drawtype == "allfaces_optional" or + def.drawtype == nil then + block_type = "cube" + elseif def.drawtype == "plantlike" then + -- Flowers and stuff + block_type = "plantlike45" + elseif def.drawtype == "airlike" then + -- Just air + block_type = nil + else + -- Fallback for complex drawtypes + block_type = "unknown" + end + self.base_texture = create_enderman_textures(block_type, self._taken_node) + self.object:set_properties({ textures = self.base_texture }) + self.animation = select_enderman_animation("block") + mobs:set_animation(self, self.animation.current) + if def.sounds and def.sounds.dug then + minetest.sound_play(def.sounds.dug, {pos = take_pos, max_hear_distance = 16}, true) + end end end end @@ -391,7 +394,8 @@ mobs:register_mob("mobs_mc:enderman", { local yaw = self.object:get_yaw() -- Place node at looking direction local place_pos = vector.subtract(pos, minetest.facedir_to_dir(minetest.dir_to_facedir(minetest.yaw_to_dir(yaw)))) - if minetest.get_node(place_pos).name == "air" then + -- Also check to see if protected. + if minetest.get_node(place_pos).name == "air" and not minetest.is_protected(place_pos, "") then -- ... but only if there's a free space local success = minetest.place_node(place_pos, {name = self._taken_node}) if success then