diff --git a/mods/ENTITIES/mcl_mobs/api.lua b/mods/ENTITIES/mcl_mobs/api.lua index a33b8853..bdb5dbf1 100644 --- a/mods/ENTITIES/mcl_mobs/api.lua +++ b/mods/ENTITIES/mcl_mobs/api.lua @@ -1599,6 +1599,7 @@ local monster_attack = function(self) if self.type ~= "monster" or not damage_enabled or minetest.is_creative_enabled("") + or self.passive or self.state == "attack" or day_docile(self) then return @@ -2579,6 +2580,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) @@ -3391,6 +3400,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 c159c92f..9b4857e2 100644 --- a/mods/ENTITIES/mobs_mc/enderman.lua +++ b/mods/ENTITIES/mobs_mc/enderman.lua @@ -3,13 +3,28 @@ --made for MC like Survival game --License for code WTFPL and otherwise stated in readmes --- ENDERMAN BEHAVIOUR: +-- ENDERMAN BEHAVIOUR (OLD): -- In this game, endermen attack the player on sight, like other monsters do. -- However, they have a reduced viewing range to make them less dangerous. -- This differs from MC, in which endermen only become hostile when provoked, -- 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,140 @@ 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 + if (minetest.get_timeofday() * 24000) > 5001 and (minetest.get_timeofday() * 24000) < 19000 then + self:teleport(nil) + self.state = "" + else + if self.attack then + target = self.attack + pos = target:get_pos() + if pos ~= nil then + if vector.distance(self.object:get_pos(), target:get_pos()) > 10 then + self:teleport(target) + end + end + end + end + end + -- ARROW / DAYTIME PEOPLE AVOIDANCE BEHAVIOUR HERE. + -- Check for arrows and people 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 + if minetest.is_player(obj) then + -- Warp from players during day. + if (minetest.get_timeofday() * 24000) > 5001 and (minetest.get_timeofday() * 24000) < 19000 then + self:teleport(nil) + end + else + lua = obj:get_luaentity() + if lua then + if lua.name == "mcl_bows:arrow_entity" then + self:teleport(nil) + end + end + end + end + end + -- PROVOKED BEHAVIOUR HERE. + local enderpos = self.object:get_pos() + if self.provoked == "broke_contact" then + self.provoked = "false" + if (minetest.get_timeofday() * 24000) > 5001 and (minetest.get_timeofday() * 24000) < 19000 then + self:teleport(nil) + self.state = "" + else + if self.attack ~= nil then + self.state = 'attack' + end + end + 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 @@ -218,44 +366,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 @@ -267,7 +418,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 @@ -283,34 +435,70 @@ 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 ~= nil then + 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 + else + -- Attempt to randomly teleport enderman + local pos = self.object:get_pos() + -- Up to 8 top-level attempts to teleport + for n=1, 8 do + local node_ok = false + -- We need to add (or subtract) different random numbers to each vector component, so it couldn't be done with a nice single vector.add() or .subtract(): + local randomCube = vector.new( pos.x + 8*(pr:next(0,16)-8), pos.y + 8*(pr:next(0,16)-8), pos.z + 8*(pr:next(0,16)-8) ) + local nodes = minetest.find_nodes_in_area_under_air(vector.subtract(randomCube, 4), vector.add(randomCube, 4), {"group:solid", "group:cracky", "group:crumbly"}) + if nodes ~= nil then + if #nodes > 0 then + -- Up to 8 low-level (in total up to 8*8 = 64) attempts to teleport + for n=1, math.min(8, #nodes) do + local r = pr:next(1, #nodes) + local nodepos = nodes[r] + node_ok = true + 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 + self.object:set_pos({x=nodepos.x, y=nodepos.y+1, z=nodepos.z}) + break + end + end end end if node_ok then - telepos = {x=nodepos.x, y=nodepos.y+1, z=nodepos.z} + break end end - if telepos then - self.object:set_pos(telepos) - end end end, on_die = function(self, pos) @@ -319,10 +507,20 @@ 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 and hitter ~= nil then + if (minetest.get_timeofday() * 24000) > 5001 and (minetest.get_timeofday() * 24000) < 19000 then + self:teleport(nil) + else + self:teleport(hitter) + self.attack=hitter + self.state="attack" + end + 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 80485d95..bcac2256 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 6b901bbe..6c0dae57 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 00000000..6695291e Binary files /dev/null and b/mods/ITEMS/mcl_portals/textures/mcl_portals_particle1.png differ 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 00000000..99b3a191 Binary files /dev/null and b/mods/ITEMS/mcl_portals/textures/mcl_portals_particle2.png differ 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 00000000..3000a799 Binary files /dev/null and b/mods/ITEMS/mcl_portals/textures/mcl_portals_particle3.png differ diff --git a/mods/ITEMS/mcl_portals/textures/mcl_portals_particle4.png b/mods/ITEMS/mcl_portals/textures/mcl_portals_particle4.png new file mode 100644 index 00000000..28d14cbd Binary files /dev/null and b/mods/ITEMS/mcl_portals/textures/mcl_portals_particle4.png differ diff --git a/mods/ITEMS/mcl_portals/textures/mcl_portals_particle5.png b/mods/ITEMS/mcl_portals/textures/mcl_portals_particle5.png new file mode 100644 index 00000000..fbd67375 Binary files /dev/null and b/mods/ITEMS/mcl_portals/textures/mcl_portals_particle5.png differ