Compare commits

..

44 Commits

Author SHA1 Message Date
kno10 4c3b08c68d Add fox basics 2024-10-29 10:41:34 +01:00
kno10 98600c87de luacheck and other improvements 2024-10-29 10:41:28 +01:00
kno10 a5cfaed843 Further movement improvements 2024-10-29 10:37:47 +01:00
kno10 8c38745f6c Making movement work better. 2024-10-29 10:37:46 +01:00
kno10 19c1863100 Movement improvements 2024-10-29 10:34:01 +01:00
kno10 250fc73e92 More tweaks to pathfinding 2024-10-29 00:57:42 +01:00
kno10 9ea8945ff4 Fixup fences 2024-10-28 23:59:28 +01:00
kno10 488f61ba1b Make villagers hurry for long paths and night 2024-10-28 23:54:41 +01:00
kno10 16e17ba1ed avoid trivial fences, open some fence gates 2024-10-28 23:52:44 +01:00
kno10 8ae66b5fad Improve starting and end point of pathfinding. 2024-10-28 22:43:37 +01:00
kno10 8842e4224c pathfinding improvements 2024-10-28 17:58:02 +01:00
kno10 a94b3a8d4a Improve danger avoidance code. 2024-10-27 19:25:29 +01:00
kno10 8fce90111b fix and optimize Fleckenstein 2024-10-27 19:25:29 +01:00
kno10 645f884e7f small code cleanups 2024-10-27 19:25:29 +01:00
kno10 46f7c29451 also cleanup mount.lua 2024-10-27 19:25:29 +01:00
kno10 ddd180f5be some more cleanups, from code review 2024-10-27 19:25:29 +01:00
kno10 b473d55644 code cleanups 2024-10-27 19:24:40 +01:00
kno10 265dce301b movement improvements, door opening 2024-10-27 19:23:00 +01:00
kno10 3538efbf52 further movement tweaks 2024-10-27 19:21:36 +01:00
kno10 ab673cdf8b Movement and path finding improvements. 2024-10-27 19:21:36 +01:00
kno10 455353de27 Mob pushing improvements 2024-10-27 19:21:36 +01:00
kno10 b99e1a4b08 add and use turn_by/turn_in_direction methods 2024-10-27 19:21:36 +01:00
kno10 0730cce236 reduce code duplication, add mob:stand() 2024-10-27 19:21:36 +01:00
kno10 98f899fd67 cleanups 2024-10-27 19:21:36 +01:00
kno10 4b9e8aabb2 fix delay=0 in combat code, tune turning parameters 2024-10-27 19:21:35 +01:00
kno10 936e47e5f9 More help getting out of water 2024-10-27 19:21:35 +01:00
kno10 46b5416359 More movement code cleanups.
Closes #4506 #4502
2024-10-27 19:21:35 +01:00
kno10 72f825ade3 More cleanup and improvements to movement code 2024-10-27 19:21:35 +01:00
kno10 ec7e7c14bc More movement code improvements. 2024-10-27 19:21:35 +01:00
kno10 f8a6da10cb Try to reduce how much mobs fall off cliffs.
See #4464 and many more.
2024-10-27 19:21:35 +01:00
kno10 2e5b0bb3c7 Improve head swivel code. 2024-10-27 19:20:58 +01:00
Mikita Wiśniewski 41b188caea Remove "double drop" mechanics for bamboo (fixes #4514) (#4642)
Reviewed-on: VoxeLibre/VoxeLibre#4642
Reviewed-by: the-real-herowl <the-real-herowl@noreply.git.minetest.land>
Co-authored-by: Mikita Wiśniewski <rudzik8@protonmail.com>
Co-committed-by: Mikita Wiśniewski <rudzik8@protonmail.com>
2024-10-27 14:16:06 +01:00
kno10 ae7995d195 Fix axolotl attacking water mobs (#4675)
Also avoid jumping out of the water closes #4644

Reviewed-on: VoxeLibre/VoxeLibre#4675
Reviewed-by: the-real-herowl <the-real-herowl@noreply.git.minetest.land>
Co-authored-by: kno10 <kno10@noreply.git.minetest.land>
Co-committed-by: kno10 <kno10@noreply.git.minetest.land>
2024-10-27 14:10:11 +01:00
kno10 e293cbe631 Better handling of touching_ground for bouncing on beds (#4689)
Reviewed-on: VoxeLibre/VoxeLibre#4689
Reviewed-by: the-real-herowl <the-real-herowl@noreply.git.minetest.land>
Co-authored-by: kno10 <erich.schubert@gmail.com>
Co-committed-by: kno10 <erich.schubert@gmail.com>
2024-10-27 14:03:50 +01:00
the-real-herowl fd6cac5f0c Merge pull request 'Fix fog tint in overworld, apply memory leak fix, fix rain->clear clouds' (#4669) from weather-fixes into master
Reviewed-on: VoxeLibre/VoxeLibre#4669
Reviewed-by: kno10 <kno10@noreply.git.minetest.land>
2024-10-11 07:14:01 +02:00
teknomunk e864cc19ed Make fog_tint_type = "default" when weather is present to match behavior at 0.87.2 2024-10-09 01:05:20 +02:00
teknomunk 66c3c014a1 Make sure fog tints are preserved during weather is present 2024-10-09 01:05:20 +02:00
teknomunk 7807093b50 Another correction to color interpolation, change day color from layer position 0.15 to 0.50 2024-10-09 01:05:20 +02:00
teknomunk f6c3f4bd16 Correct value clamping 2024-10-09 01:05:20 +02:00
teknomunk 96a03b1923 Remove posibility of nil sky colors in overworld, add line break 2024-10-09 01:05:20 +02:00
teknomunk 2145470f63 Fix clouds during rain->clear weather transition 2024-10-09 01:05:20 +02:00
teknomunk 2ca0ccd8fe Fix fog tint in overworld, apply memory leak fix from rain.lua to snow.lua and thunder.lua 2024-10-09 01:05:20 +02:00
teknomunk 614518c6cd Revert minetest.add_entity() -> mcl_mobs.spawn() from #4445 (#4679)
Reviewed-on: VoxeLibre/VoxeLibre#4679
Reviewed-by: kno10 <kno10@noreply.git.minetest.land>
Co-authored-by: teknomunk <teknomunk@protonmail.com>
Co-committed-by: teknomunk <teknomunk@protonmail.com>
2024-10-08 15:34:30 +02:00
kno10 253a06fa08 Fix mob egg double-spawns (#4657)
If you spawn a mob clicking on a wall, two mobs will be spawned.

To reproduce: face a stack of stones, with a spawn egg click on the side of a stone. It does not happen when you click the top of a node, because spawning below fails and only the second one succeeds.

Reviewed-on: VoxeLibre/VoxeLibre#4657
Reviewed-by: the-real-herowl <the-real-herowl@noreply.git.minetest.land>
Co-authored-by: kno10 <kno10@noreply.git.minetest.land>
Co-committed-by: kno10 <kno10@noreply.git.minetest.land>
2024-09-30 19:21:40 +02:00
24 changed files with 573 additions and 283 deletions

View File

@ -73,7 +73,7 @@ functions needed for the mob to work properly which contains the following:
in lava (default: 8).
'fire_damage' holds the damage per second inflicted to mobs when standing
in fire (default: 1).
'light_damage' holds the damage per second inflicted to mobs when it's too
'light_damage'holds the damage per second inflicted to mobs when it's too
bright (above 13 light).
'suffocation' when true causes mobs to suffocate inside solid blocks (2 damage per second).
'floats' when set to 1 mob will float in water, 0 has them sink.

View File

@ -93,7 +93,7 @@ end
-- Spawn a child
function mcl_mobs.spawn_child(pos, mob_type)
local child = mcl_mobs.spawn(pos, mob_type)
local child = minetest.add_entity(pos, mob_type)
if not child then return end
local ent = child:get_luaentity()

View File

@ -230,7 +230,7 @@ function mob_class:set_animation(anim, fixed_frame)
y = a_end},
self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
0, self.animation[anim .. "_loop"] ~= false)
end
end
end
local function who_are_you_looking_at (self, dtime)

View File

@ -37,22 +37,25 @@ function mob_class:stand()
end
-- Turn towards a (nearby) target, primarily for path following
function mob_class:go_to_pos(b)
function mob_class:go_to_pos(b, speed)
if not self then return end
if not b then return end
local s = self.object:get_pos()
if vector_distance(b,s) < .1 then return true end
if b.y > s.y then self:do_jump() end
self:turn_in_direction(b.x - s.x, b.z - s.z, 5)
self:set_velocity(self.walk_velocity)
self:set_animation("walk")
if vector_distance(b,s) < .4 then return true end
if b.y > s.y + 0.2 then self:do_jump() end
self:turn_in_direction(b.x - s.x, b.z - s.z, 2)
speed = speed or self.walk_velocity
self:set_velocity(speed)
self:set_animation(speed <= self.walk_velocity and "walk" or "run")
end
-- Returns true is node can deal damage to self, except water damage
function mob_class:is_node_dangerous(nodename)
local ndef = minetest.registered_nodes[nodename]
return ndef
and ((self.lava_damage > 0 and ndef.groups.lava)
or (self.fire_damage > 0 and ndef.groups.fire)
and ((self.lava_damage > 0 and (ndef.groups.lava or 0) > 0)
or (self.fire_damage > 0 and (ndef.groups.fire or 0) > 0)
or ((ndef.damage_per_second or 0) > 0))
end
@ -65,12 +68,17 @@ function mob_class:is_node_waterhazard(nodename)
end
function mob_class:target_visible(origin)
if not origin then return false end
if not self.attack then return false end
if not origin then return end
if not self.attack then return end
local target_pos = self.attack:get_pos()
if not target_pos then return false end
if not target_pos then return end
local origin_eye_pos = vector_offset(origin, 0, self.head_eye_height, 0)
--minetest.log("origin: " .. dump(origin))
--minetest.log("origin_eye_pos: " .. dump(origin_eye_pos))
local targ_head_height, targ_feet_height
local cbox = self.collisionbox
-- TODO also worth testing midway between feet and head?
-- to top of entity
@ -81,11 +89,23 @@ function mob_class:target_visible(origin)
else
if line_of_sight(origin_eye_pos, vector_offset(target_pos, 0, cbox[2], 0), self.see_through_opaque or mobs_see_through_opaque, true) then return true end
end
--minetest.log("start targ_head_height: " .. dump(targ_head_height))
if raycast_line_of_sight (origin_eye_pos, targ_head_height) then
return true
end
--minetest.log("Start targ_feet_height: " .. dump(targ_feet_height))
if raycast_line_of_sight (origin_eye_pos, targ_feet_height) then
return true
end
-- TODO mid way between feet and head
return false
end
-- check line of sight
-- @param stepsize is ignored now
function mob_class:line_of_sight(pos1, pos2, stepsize)
return line_of_sight(pos1, pos2, self.see_through_opaque or mobs_see_through_opaque, true)
end
@ -117,9 +137,10 @@ function mob_class:can_jump_cliff()
--minetest.log("Jumping cliff: " .. self.name .. " nodes " .. node_low.name .. " - " .. node_far.name .. " - " .. node_far2.name)
minetest.after(.1, function() if self and self.object then self._jumping_cliff = false end end)
return true
else
self._jumping_cliff = false
return false
end
self._jumping_cliff = false
return false
end
-- is mob facing a cliff or danger
@ -131,7 +152,8 @@ function mob_class:is_at_cliff_or_danger()
if self.fly then return false end -- also avoids checking fish
local pos, yaw = self.object:get_pos(), self.object:get_yaw()
local cbox = self.collisionbox
local dir_x, dir_z = -sin(yaw) * (cbox[4] + 0.5), cos(yaw) * (cbox[4] + 0.5)
local dir_x = -sin(yaw) * (cbox[4] + 0.5)
local dir_z = cos(yaw) * (cbox[4] + 0.5)
local ypos = pos.y + cbox[2] + 0.1 -- just above floor
@ -139,7 +161,9 @@ function mob_class:is_at_cliff_or_danger()
vector_new(pos.x + dir_x, ypos, pos.z + dir_z),
vector_new(pos.x + dir_x, floor(ypos - self.fear_height), pos.z + dir_z))
if free_fall then return "free fall" end
if free_fall then
return "free fall"
end
local height = ypos + 0.4 - blocker.y
local chance = self.jump_height / (height * height)
if height >= self.fear_height and random() < chance then
@ -153,15 +177,22 @@ function mob_class:is_at_cliff_or_danger()
if self:is_node_dangerous(self.standing_in.name) or self:is_node_waterhazard(self.standing_in.name) then
return false -- allow to get out of the immediate danger
end
if self:is_node_dangerous(bnode.name) or self:is_node_waterhazard(bnode.name) then return bnode.name end
if self:is_node_dangerous(bnode.name) or self:is_node_waterhazard(bnode.name) then
return bnode.name
end
return false
end
-- copy the 'mob facing cliff_or_danger check' from above, and rework to avoid water
function mob_class:is_at_water_danger()
if self.water_damage == 0 and self.breath_max == -1 then return false end
if self.fly then return false end -- also avoids checking fish
if self.water_damage == 0 and self.breath_max == -1 then
--minetest.log("Do not need a water check for: " .. self.name)
return false
end
if self.fly then -- also avoids checking fish
return false
end
local in_water_danger = self:is_node_waterhazard(self.standing_in.name) or self:is_node_waterhazard(self.standing_on.name)
if in_water_danger then return false end -- If you're in trouble, do not stop
@ -170,7 +201,8 @@ function mob_class:is_at_water_danger()
local pos, yaw = self.object:get_pos(), self.object:get_yaw()
local cbox = self.collisionbox
local dir_x, dir_z = -sin(yaw) * (cbox[4] + 0.5), cos(yaw) * (cbox[4] + 0.5)
local dir_x = -sin(yaw) * (cbox[4] + 0.5)
local dir_z = cos(yaw) * (cbox[4] + 0.5)
local ypos = pos.y + cbox[2] + 0.1 -- just above floor
@ -180,7 +212,10 @@ function mob_class:is_at_water_danger()
if not los then
local bnode = minetest.get_node(blocker)
if self:is_node_waterhazard(bnode.name) then return bnode.name end
local waterdanger = self:is_node_waterhazard(bnode.name)
if waterdanger then
return bnode.name
end
end
return false
end
@ -245,7 +280,16 @@ function mob_class:do_jump()
-- what is in front of mob?
local nod = minetest.get_node(vector_offset(pos, dir_x, 0.5, dir_z)).name
if nod == NODE_SNOW then return false end -- no need to jump
local ndef = minetest.registered_nodes[nod.name]
-- thin blocks that do not need to be jumped
if nod.name == NODE_SNOW or (ndef and ndef.groups.carpet or 0) > 0 then return false end
-- nothing to jump on?
if self.walk_chance ~= 0 and not (ndef and ndef.walkable) and not self._can_jump_cliff then return false end
-- facing a fence? jumping will not help (FIXME: consider jump height)
if (ndef.groups.fence or 0) ~= 0 or (ndef.groups.fence_gate or 0) ~= 0 or (ndef.groups.wall or 0) ~= 0 then
self.facing_fence = true
return false
end
-- this is used to detect if there's a block on top of the block in front of the mob.
-- If there is, there is no point in jumping as we won't manage.
@ -254,16 +298,9 @@ function mob_class:do_jump()
-- we don't attempt to jump if there's a stack of blocks blocking, unless attacking
local ntdef = minetest.registered_nodes[node_top]
-- TODO: snow, carpet?
if ntdef and ntdef.walkable == true --[[and not (self.attack and self.state == "attack")]] then return false end
local ndef = minetest.registered_nodes[nod]
if self.walk_chance ~= 0 and not (ndef and ndef.walkable) and not self._can_jump_cliff then return false end
if ndef.groups.fence or ndef.groups.fence_gate or ndef.groups.wall then
self.facing_fence = true
return false
end
v.y = math.min(v.y, 0) + math.sqrt(self.jump_height * 20 + (in_water or self._can_jump_cliff and 10 or 0))
v.y = math.min(-self.fall_speed, math.max(v.y, self.fall_speed))
self.object:set_velocity(v)
@ -277,7 +314,7 @@ function mob_class:do_jump()
end
-- if we jumped against a block/wall 4 times then turn
if v.x * v.x + v.z * v.z < 0.1 then
if (v.x * v.x + v.z * v.z) < 0.1 then
self._jump_count = (self._jump_count or 0) + 1
if self._jump_count == 4 then
self:turn_by(TWOPI * (random() - 0.5), 8)

View File

@ -1,17 +1,14 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
local PATHFINDING_FAIL_THRESHOLD = 100 -- no. of ticks to fail before giving up. 20p/s. 5s helps them get through door
local PATHFINDING_FAIL_THRESHOLD = 200 -- no. of ticks to fail before giving up. 20p/s. 5s helps them get through door
local PATHFINDING_FAIL_WAIT = 30 -- how long to wait before trying to path again
local PATHING_START_DELAY = 4 -- When doing non-prioritised pathing, how long to wait until last mob pathed
local PATHFINDING_SEARCH_DISTANCE = 50 -- How big the square is that pathfinding will look
local PATHFINDING_SEARCH_DISTANCE = 25 -- How big the square is that pathfinding will look
local PATHFINDING = "gowp"
local one_down = vector.new(0,-1,0)
local one_up = vector.new(0,1,0)
local plane_adjacents = {
vector.new(1,0,0),
vector.new(-1,0,0),
@ -20,6 +17,7 @@ local plane_adjacents = {
}
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_pathfinding",false)
local visualize = minetest.settings:get_bool("mcl_mobs_pathfinding_visualize",false)
local LOG_MODULE = "[Mobs Pathfinding]"
local function mcl_log (message)
@ -68,33 +66,22 @@ end
-- an action, such as to open or close a door where we know that pos requires that action
local function generate_enriched_path(wp_in, door_open_pos, door_close_pos, cur_door_pos)
local wp_out = {}
-- TODO Just pass in door position and the index before is open, the index after is close
local current_door_index = -1
for i, cur_pos in pairs(wp_in) do
local action = nil
local cur_pos_to_add = vector.add(cur_pos, one_down)
if door_open_pos and vector.equals (cur_pos, door_open_pos) then
if door_open_pos and vector.equals(cur_pos, door_open_pos) then
mcl_log ("Door open match")
action = {type = "door", action = "open", target = cur_door_pos}
cur_pos_to_add = vector.add(cur_pos, one_down)
elseif door_close_pos and vector.equals(cur_pos, door_close_pos) then
mcl_log ("Door close match")
action = {type = "door", action = "close", target = cur_door_pos}
cur_pos_to_add = vector.add(cur_pos, one_down)
elseif cur_door_pos and vector.equals(cur_pos, cur_door_pos) then
mcl_log("Current door pos")
action = {type = "door", action = "open", target = cur_door_pos}
cur_pos_to_add = vector.add(cur_pos, one_down)
else
cur_pos_to_add = cur_pos
--mcl_log ("Pos doesn't match")
end
wp_out[i] = {}
wp_out[i]["pos"] = cur_pos_to_add
wp_out[i]["pos"] = cur_pos
wp_out[i]["failed_attempts"] = 0
wp_out[i]["action"] = action
@ -124,77 +111,66 @@ end
-- This function is used to see if we can path. We could use to check a route, rather than making people move.
local function calculate_path_through_door (p, cur_door_pos, t)
if not cur_door_pos then return end
if t then
mcl_log("Plot route through door from pos: " .. minetest.pos_to_string(p) .. ", to target: " .. minetest.pos_to_string(t))
mcl_log("Plot route through door from pos: " .. minetest.pos_to_string(p) .. " through " .. minetest.pos_to_string(cur_door_pos) .. ", to target: " .. minetest.pos_to_string(t))
else
mcl_log("Plot route through door from pos: " .. minetest.pos_to_string(p))
mcl_log("Plot route through door from pos: " .. minetest.pos_to_string(p) .. " through " .. minetest.pos_to_string(cur_door_pos))
end
local enriched_path = nil
local wp, prospective_wp
for _,v in pairs(plane_adjacents) do
local pos_closest_to_door = vector.add(cur_door_pos,v)
local n = minetest.get_node(pos_closest_to_door)
if not n.walkable then
mcl_log("We have open space next to door at: " .. minetest.pos_to_string(pos_closest_to_door))
local pos_closest_to_door = nil
local other_side_of_door = nil
local prospective_wp = minetest.find_path(p, pos_closest_to_door, PATHFINDING_SEARCH_DISTANCE, 1, 4)
if cur_door_pos then
mcl_log("Found a door near: " .. minetest.pos_to_string(cur_door_pos))
if prospective_wp then
local other_side_of_door = vector.add(cur_door_pos,-v)
mcl_log("Found a path to next to door".. minetest.pos_to_string(pos_closest_to_door))
mcl_log("Opposite is: ".. minetest.pos_to_string(other_side_of_door))
for _,v in pairs(plane_adjacents) do
pos_closest_to_door = vector.add(cur_door_pos,v)
other_side_of_door = vector.add(cur_door_pos,-v)
table.insert(prospective_wp, cur_door_pos)
local n = minetest.get_node(pos_closest_to_door)
if t then
mcl_log("We have t, lets go from door to target")
local wp_otherside_door_to_target = minetest.find_path(other_side_of_door, t, PATHFINDING_SEARCH_DISTANCE, 1, 4)
if n.name == "air" then
mcl_log("We have air space next to door at: " .. minetest.pos_to_string(pos_closest_to_door))
prospective_wp = minetest.find_path(p, pos_closest_to_door, PATHFINDING_SEARCH_DISTANCE, 1, 4)
if prospective_wp then
mcl_log("Found a path to next to door".. minetest.pos_to_string(pos_closest_to_door))
mcl_log("Opposite is: ".. minetest.pos_to_string(other_side_of_door))
table.insert(prospective_wp, cur_door_pos)
if t then
mcl_log("We have t, lets go from door to target")
local wp_otherside_door_to_target = minetest.find_path(other_side_of_door, t, PATHFINDING_SEARCH_DISTANCE, 1, 4)
if wp_otherside_door_to_target and #wp_otherside_door_to_target > 0 then
append_paths (prospective_wp, wp_otherside_door_to_target)
wp = prospective_wp
mcl_log("We have a path from outside door to target")
else
mcl_log("We cannot path from outside door to target")
end
if wp_otherside_door_to_target and #wp_otherside_door_to_target > 0 then
append_paths (prospective_wp, wp_otherside_door_to_target)
mcl_log("We have a path from outside door to target")
return generate_enriched_path(prospective_wp, pos_closest_to_door, other_side_of_door, cur_door_pos)
else
mcl_log("No t, just add other side of door")
table.insert(prospective_wp, other_side_of_door)
wp = prospective_wp
end
if wp then
enriched_path = generate_enriched_path(wp, pos_closest_to_door, other_side_of_door, cur_door_pos)
break
mcl_log("We cannot path from outside door to target")
end
else
mcl_log("Cannot path to this air block next to door.")
mcl_log("No t, just add other side of door")
table.insert(prospective_wp, other_side_of_door)
return generate_enriched_path(prospective_wp, pos_closest_to_door, other_side_of_door, cur_door_pos)
end
else
mcl_log("Cannot path to this air block next to door.")
end
end
else
mcl_log("No door found")
end
if wp and not enriched_path then
mcl_log("Wp but not enriched")
enriched_path = generate_enriched_path(wp)
end
return enriched_path
end
-- we treat ignore as solid, as we cannot path there
local function is_solid(pos)
local ndef = minetest.registered_nodes[minetest.get_node(pos).name]
return (not ndef) or ndef.walkable
end
local function find_open_node(pos, radius)
local r = vector.round(pos)
if not is_solid(r) then return r end
local above = vector.offset(r, 0, 1, 0)
if not is_solid(above) then return above, true end -- additional return: drop last
local n = minetest.find_node_near(pos, radius or 1, {"air"})
if n then return n end
return nil
end
function mob_class:gopath(target, callback_arrived, prioritised)
if self.state == PATHFINDING then mcl_log("Already pathfinding, don't set another until done.") return end
@ -204,8 +180,19 @@ function mob_class:gopath(target, callback_arrived, prioritised)
self.order = nil
local p = self.object:get_pos()
local t = vector.offset(target,0,1,0)
-- maybe feet are buried in solid?
local start = self.object:get_pos()
local p = find_open_node(start, 1)
if not p then -- buried?
minetest.log("action", "Cannot path from "..minetest.pos_to_string(start).." because it is solid. Nodetype: "..minetest.get_node(start).name)
return
end
-- target might be a job-site that is solid
local t, drop_last_wp = find_open_node(target, 1)
if not t then
minetest.log("action", "Cannot path to "..minetest.pos_to_string(target).." because it is solid. Nodetype: "..minetest.get_node(target).name)
return
end
--Check direct route
local wp = minetest.find_path(p, t, PATHFINDING_SEARCH_DISTANCE, 1, 4)
@ -213,11 +200,15 @@ function mob_class:gopath(target, callback_arrived, prioritised)
if not wp then
mcl_log("### No direct path. Path through door closest to target.")
local door_near_target = minetest.find_node_near(target, 16, {"group:door"})
local below = door_near_target and vector.offset(door_near_target, 0, -1, 0)
if below and minetest.get_item_group(minetest.get_node(below), "door") > 0 then door_near_target = below end
wp = calculate_path_through_door(p, door_near_target, t)
if not wp then
mcl_log("### No path though door closest to target. Try door closest to origin.")
local door_closest = minetest.find_node_near(p, 16, {"group:door"})
local below = door_closest and vector.offset(door_closest, 0, -1, 0)
if below and minetest.get_item_group(minetest.get_node(below), "door") > 0 then door_closest = below end
wp = calculate_path_through_door(p, door_closest, t)
-- Path through 2 doors
@ -231,7 +222,7 @@ function mob_class:gopath(target, callback_arrived, prioritised)
local pos_after_door_entry = path_through_closest_door[#path_through_closest_door]
if pos_after_door_entry then
local pos_after_door = vector.add(pos_after_door_entry["pos"], one_up)
local pos_after_door = pos_after_door_entry["pos"]
mcl_log("pos_after_door: " .. minetest.pos_to_string(pos_after_door))
local path_after_door = calculate_path_through_door(pos_after_door, door_near_target, t)
if path_after_door and #path_after_door > 1 then
@ -263,20 +254,92 @@ function mob_class:gopath(target, callback_arrived, prioritised)
-- If cannot path, don't immediately try again
end
-- todo: we would also need to avoid overhangs, but minetest.find_path cannot help us there
-- we really need a better pathfinder overall.
-- try to find a way around fences and walls. This is very barebones, but at least it should
-- help path around very simple fences *IF* there is a detour that does not require jumping or gates.
if wp and #wp > 0 then
local i = 1
while i < #wp do
-- fence or wall underneath?
local bdef = minetest.registered_nodes[minetest.get_node(vector.offset(wp[i].pos, 0, -1, 0)).name]
if not bdef then minetest.log("warning", "There must not be unknown nodes on path") end
-- carpets are fine
if bdef and (bdef.groups.carpet or 0) > 0 then
wp[i].pos = vector.offset(wp[i].pos, 0, -1, 0)
-- target bottom of door
elseif bdef and (bdef.groups.door or 0) > 0 then
wp[i].pos = vector.offset(wp[i].pos, 0, -1, 0)
-- not walkable?
elseif bdef and not bdef.walkable then
wp[i].pos = vector.offset(wp[i].pos, 0, -1, 0)
i = i - 1
-- plan opening fence gates
elseif bdef and (bdef.groups.fence_gate or 0) > 0 then
wp[i].pos = vector.offset(wp[i].pos, 0, -1, 0)
wp[math.max(1,i-1)].action = {type = "door", action = "open", target = wp[i].pos}
if i+1 < #wp then
wp[i+1].action = {type = "door", action = "close", target = wp[i].pos}
end
-- do not jump on fences and walls, but try to walk around
elseif bdef and i > 1 and ((bdef.groups.fence or 0) > 0 or (bdef.groups.wall or 0) > 0) and wp[i].pos.y > wp[i-1].pos.y then
-- find end of wall(s)
local j = i + 1
while j <= #wp do
local below = vector.offset(wp[j].pos, 0, -1, 0)
local bdef = minetest.registered_nodes[minetest.get_node(below).name]
if not bdef or ((bdef.groups.fence or 0) == 0 and (bdef.groups.wall or 0) == 0) then
break
end
j = j + 1
end
minetest.log("warning", bdef.name .. " at "..tostring(i).." end at "..(j <= #wp and tostring(j) or "nil"))
if j <= #wp and wp[i-1].pos.y == wp[j].pos.y then
local swp = minetest.find_path(wp[i-1].pos, wp[j].pos, PATHFINDING_SEARCH_DISTANCE, 0, 0)
-- TODO: if we do not find a path here, consider pathing through a fence gate!
if swp and #swp > 0 then
for k = j-1,i,-1 do table.remove(wp, k) end
for k = 2, #swp-1 do table.insert(wp, i-2+k, {pos = swp[k], failed_attempts = 0}) end
minetest.log("warning", "Monkey patch pathfinding around "..bdef.name.." successful.")
i = i + #swp - 4
else
minetest.log("warning", "Monkey patch pathfinding around "..bdef.name.." failed.")
end
end
end
i = i + 1
end
end
if wp and drop_last_wp and vector.equals(wp[#wp], t) then table.remove(wp, #wp) end
if wp and #wp > 0 then
if visualize then
for i = 1,#wp do
core.add_particle({pos = wp[i].pos, expirationtime=3+i/3, size=3+2/i, velocity=vector.new(0,-0.02,0),
texture="mcl_copper_anti_oxidation_particle.png"}) -- white stars
end
end
--output_table(wp)
self._target = t
self.callback_arrived = callback_arrived
self.current_target = table.remove(wp,1)
self.waypoints = wp
self.state = PATHFINDING
return true
else
self.state = "walk"
self.waypoints = nil
self.current_target = nil
-- minetest.log("no path found")
while self.current_target and self.current_target.pos and vector.distance(p, self.current_target.pos) < 0.5 do
--mcl_log("Skipping close initial waypoint")
self.current_target = table.remove(wp,1)
end
if self.current_target and self.current_target.pos then
self:turn_in_direction(self.current_target.pos.x - p.x, self.current_target.pos.z - p.z, 2)
self.waypoints = wp
self.state = PATHFINDING
return true
end
end
self:turn_in_direction(target.x - p.x, target.z - p.z, 4)
self.state = "walk"
self.waypoints = nil
self.current_target = nil
--minetest.log("no path found")
end
function mob_class:interact_with_door(action, target)
@ -295,11 +358,21 @@ function mob_class:interact_with_door(action, target)
if closed and action == "open" and def.on_rightclick then
mcl_log("Open door")
def.on_rightclick(target,n,self)
end
if not closed and action == "close" and def.on_rightclick then
elseif not closed and action == "close" and def.on_rightclick then
mcl_log("Close door")
def.on_rightclick(target,n,self)
end
elseif n.name:find("_gate") then
local def = minetest.registered_nodes[n.name]
local meta = minetest.get_meta(target)
local closed = meta:get_int("state") == 0
if closed and action == "open" and def.on_rightclick then
mcl_log("Open gate")
def.on_rightclick(target,n,self)
elseif not closed and action == "close" and def.on_rightclick then
mcl_log("Close gate")
def.on_rightclick(target,n,self)
end
else
mcl_log("Not door")
end
@ -355,53 +428,56 @@ function mob_class:check_gowp(dtime)
-- More pathing to be done
local distance_to_current_target = 50
if self.current_target and self.current_target["pos"] then
local dx, dy, dz = self.current_target["pos"].x-p.x, self.current_target["pos"].y-p.y, self.current_target["pos"].z-p.z
distance_to_current_target = (dx*dx+dy*dy*0.25+dz*dz)^0.5 -- reduced weight on y
--distance_to_current_target = vector.distance(p,self.current_target["pos"])
if self.current_target and self.current_target.pos then
local dx, dy, dz = self.current_target.pos.x-p.x, self.current_target.pos.y-p.y, self.current_target.pos.z-p.z
distance_to_current_target = (dx*dx+dy*dy*0.5+dz*dz)^0.5 -- reduced weight on y
--distance_to_current_target = vector.distance(p,self.current_target.pos)
end
-- also check next target, maybe we were too fast
local next_target = #self.waypoints > 1 and self.waypoints[1]
if not self.current_target["action"] and next_target and next_target["pos"] and distance_to_current_target < 1.5 then
local dx, dy, dz = next_target["pos"].x-p.x, next_target["pos"].y-p.y, next_target["pos"].z-p.z
local distance_to_next_target = (dx*dx+dy*dy*0.25+dz*dz)^0.5 -- reduced weight on y
if not self.current_target["action"] and next_target and next_target.pos and distance_to_current_target < 1.5 then
local dx, dy, dz = next_target.pos.x-p.x, next_target.pos.y-p.y, next_target.pos.z-p.z
local distance_to_next_target = (dx*dx+dy*dy*0.5+dz*dz)^0.5 -- reduced weight on y
if distance_to_next_target < distance_to_current_target then
mcl_log("Skipped one waypoint.")
self.current_target = table.remove(self.waypoints, 1) -- pop waypoint already
distance_to_current_target = distance_to_next_target
end
end
-- debugging tool
if visualize and self.current_target and self.current_target.pos then
core.add_particle({pos = self.current_target.pos, expirationtime=.1, size=3, velocity=vector.new(0,-0.2,0), texture="mcl_particles_flame.png"})
end
-- 0.6 is working but too sensitive. sends villager back too frequently. 0.7 is quite good, but not with heights
-- 0.8 is optimal for 0.025 frequency checks and also 1... Actually. 0.8 is winning
-- 0.9 and 1.0 is also good. Stick with unless door open or closing issues
local threshold = self.current_target["action"] and 0.8 or 1.2
if self.waypoints and #self.waypoints > 0 and ( not self.current_target or not self.current_target["pos"] or distance_to_current_target < threshold ) then
local threshold = self.current_target["action"] and 0.7 or 0.9
if self.waypoints and #self.waypoints > 0 and ( not self.current_target or not self.current_target.pos or distance_to_current_target < threshold ) then
-- We have waypoints, and are at current_target or have no current target. We need a new current_target.
self:do_pathfind_action (self.current_target["action"])
local failed_attempts = self.current_target["failed_attempts"]
mcl_log("There after " .. failed_attempts .. " failed attempts. current target:".. minetest.pos_to_string(self.current_target["pos"]) .. ". Distance: " .. distance_to_current_target)
mcl_log("There after " .. failed_attempts .. " failed attempts. current target:".. minetest.pos_to_string(self.current_target.pos) .. ". Distance: " .. distance_to_current_target)
local hurry = (self.order == "sleep" or #self.waypoints > 15) and self.run_velocity or self.walk_velocity
self.current_target = table.remove(self.waypoints, 1)
-- use smoothing
--[[if #self.waypoints > 0 and not self.current_target["action"] then
local curwp, nextwp = self.current_target["pos"], self.waypoints[1]["pos"]
self:go_to_pos(vector.new(curwp.x*0.7+nextwp.x*0.3,curwp.y,curwp.z*0.7+nextwp.z*0.3))
-- use smoothing -- TODO: check for blockers before cutting corners?
if #self.waypoints > 0 and not self.current_target["action"] then
local curwp, nextwp = self.current_target.pos, self.waypoints[1].pos
self:go_to_pos(vector.new(curwp.x*0.7+nextwp.x*0.3,curwp.y,curwp.z*0.7+nextwp.z*0.3), hurry)
return
end]]--
self:go_to_pos(self.current_target["pos"])
if self.current_target["action"] then
self:set_velocity(self.walk_velocity * 0.25)
end
self:go_to_pos(self.current_target.pos, hurry)
--if self.current_target["action"] then self:set_velocity(self.walk_velocity * 0.5) end
return
elseif self.current_target and self.current_target["pos"] then
elseif self.current_target and self.current_target.pos then
-- No waypoints left, but have current target and not close enough. Potentially last waypoint to go to.
self.current_target["failed_attempts"] = self.current_target["failed_attempts"] + 1
local failed_attempts = self.current_target["failed_attempts"]
if failed_attempts >= PATHFINDING_FAIL_THRESHOLD then
mcl_log("Failed to reach position " .. minetest.pos_to_string(self.current_target["pos"]) .. " too many times. At: "..minetest.pos_to_string(p).." Abandon route. Times tried: " .. failed_attempts .. " current distance "..distance_to_current_target)
mcl_log("Failed to reach position " .. minetest.pos_to_string(self.current_target.pos) .. " too many times. At: "..minetest.pos_to_string(p).." Abandon route. Times tried: " .. failed_attempts .. " current distance "..distance_to_current_target)
self.state = "stand"
self.current_target = nil
self.waypoints = nil
@ -412,9 +488,8 @@ function mob_class:check_gowp(dtime)
return
end
--mcl_log("Not at pos with failed attempts ".. failed_attempts ..": ".. minetest.pos_to_string(p) .. "self.current_target: ".. minetest.pos_to_string(self.current_target["pos"]) .. ". Distance: ".. distance_to_current_target)
--mcl_log("Not at pos with failed attempts ".. failed_attempts ..": ".. minetest.pos_to_string(p) .. "self.current_target: ".. minetest.pos_to_string(self.current_target.pos) .. ". Distance: ".. distance_to_current_target)
self:go_to_pos(self.current_target["pos"])
self:turn_by((math.random() - 0.5), 2) -- but try turning left or right
-- Do i just delete current_target, and return so we can find final path.
else
-- Not at target, no current waypoints or current_target. Through the door and should be able to path to target.
@ -447,6 +522,7 @@ function mob_class:check_gowp(dtime)
-- I don't think we need the following anymore, but test first.
-- Maybe just need something to path to target if no waypoints left
--[[ ok, let's try
if self.current_target and self.current_target["pos"] and (self.waypoints and #self.waypoints == 0) then
local updated_p = self.object:get_pos()
local distance_to_cur_targ = vector.distance(updated_p,self.current_target["pos"])
@ -465,4 +541,5 @@ function mob_class:check_gowp(dtime)
end
return
end
--]]--
end

View File

@ -28,15 +28,22 @@ local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
local mob_active_range = tonumber(minetest.settings:get("mcl_mob_active_range")) or 48
-- check if within physical map limits (-30911 to 30927)
local map_min, map_max = -30912, 30928
if mcl_vars and mcl_vars.mapgen_edge_min and mcl_vars.mapgen_edge_max then
map_min, map_max = mcl_vars.mapgen_edge_min, mcl_vars.mapgen_edge_max
end
local function within_limits(pos)
return pos
and pos.x >= map_min and pos.x <= map_max
and pos.y >= map_min and pos.y <= map_max
and pos.z >= map_min and pos.z <= map_max
local function within_limits(pos, radius)
local wmin, wmax = -30912, 30928
if mcl_vars then
if mcl_vars.mapgen_edge_min and mcl_vars.mapgen_edge_max then
wmin, wmax = mcl_vars.mapgen_edge_min, mcl_vars.mapgen_edge_max
end
end
if radius then
wmin = wmin - radius
wmax = wmax + radius
end
if not pos then return true end
for _,v in pairs(pos) do
if v < wmin or v > wmax then return false end
end
return true
end
-- Function that update some helpful variables on the mobs position:
@ -222,7 +229,7 @@ end
-- @return target angle
function mob_class:turn_in_direction(dx, dz, delay, dtime)
if abs(dx) == 0 and abs(dz) == 0 then return self.object:get_yaw() + self.rotate end
return self:set_yaw(-atan2(dx, dz), delay, dtime)
return self:set_yaw(-atan2(dx, dz) - self.rotate, delay, dtime) + self.rotate
end
-- Absolute turn into a particular direction
-- @param yaw number: angle in radians
@ -306,7 +313,7 @@ function mob_class:flight_check()
if not self.standing_in or self.standing_in.name == "ignore" then return true end -- unknown?
if not self.fly_in then return false end
local nod = self.standing_in.name
-- todo: allow flowers etc. for birds
-- todo: allow flowers etc. for birds
for _,checknode in pairs(self.fly_in) do
if nod == checknode then return true end
end
@ -315,15 +322,26 @@ end
-- check if mob is dead or only hurt
function mob_class:check_for_death(cause, cmi_cause)
if self.state == "die" then return true end
-- skip effects if unchanged
if self.health == self.old_health and self.health > 0 then return false end
if self.state == "die" then
return true
end
-- has health actually changed?
if self.health == self.old_health and self.health > 0 then
return false
end
local damaged = self.health < self.old_health
self.old_health = self.health
-- still got some health?
if self.health > 0 then
if self.health > self.hp_max then self.health = self.hp_max end
-- make sure health isn't higher than max
if self.health > self.hp_max then
self.health = self.hp_max
end
-- play damage sound if health was reduced and make mob flash red.
if damaged then
@ -341,35 +359,57 @@ function mob_class:check_for_death(cause, cmi_cause)
self:mob_sound("death")
local function death_handle(self)
-- dropped cooked item if mob died in fire or lava, no XP
if cmi_cause and cmi_cause["type"] then
--minetest.log("cmi_cause: " .. tostring(cmi_cause["type"]))
end
--minetest.log("cause: " .. tostring(cause))
-- TODO other env damage shouldn't drop xp
-- "rain", "water", "drowning", "suffocation"
-- dropped cooked item if mob died in fire or lava
if cause == "lava" or cause == "fire" then
self:item_drop(true, 0)
return
end
if cause == "rain" or cause == "water" or cause == "drowning" or cause == "suffocation" then
self:item_drop(false, 0)
return
end
local wielditem = cause == "hit" and cmi_cause.puncher and cmi_cause.puncher:get_wielded_item()
local cooked = mcl_burning.is_burning(self.object) or (wielditem and mcl_enchanting.has_enchantment(wielditem, "fire_aspect"))
local looting = wielditem and mcl_enchanting.get_enchantment(wielditem, "looting")
self:item_drop(cooked, looting)
else
local wielditem = ItemStack()
if cause == "hit" then
local puncher = cmi_cause.puncher
if puncher then
wielditem = puncher:get_wielded_item()
end
end
local cooked = mcl_burning.is_burning(self.object) or mcl_enchanting.has_enchantment(wielditem, "fire_aspect")
local looting = mcl_enchanting.get_enchantment(wielditem, "looting")
self:item_drop(cooked, looting)
if (not self.child or self.type ~= "animal") and (minetest.get_us_time() - self.xp_timestamp <= math.huge) then
local pos = self.object:get_pos()
local xp_amount = random(self.xp_min, self.xp_max)
if not mcl_sculk.handle_death(pos, xp_amount) then
if minetest.is_creative_enabled("") ~= true then
mcl_experience.throw_xp(pos, xp_amount)
if ((not self.child) or self.type ~= "animal") and (minetest.get_us_time() - self.xp_timestamp <= math.huge) then
local pos = self.object:get_pos()
local xp_amount = random(self.xp_min, self.xp_max)
if not mcl_sculk.handle_death(pos, xp_amount) then
--minetest.log("Xp not thrown")
if minetest.is_creative_enabled("") ~= true then
mcl_experience.throw_xp(pos, xp_amount)
end
else
--minetest.log("xp thrown")
end
end
end
end
-- execute custom death function
if self.on_die then
local pos = self.object:get_pos()
if self.on_die(self, pos, cmi_cause) then
local on_die_exit = self.on_die(self, pos, cmi_cause)
if on_die_exit ~= true then
death_handle(self)
end
if on_die_exit == true then
self.state = "die"
mcl_burning.extinguish(self.object)
self.object:remove()
@ -382,6 +422,12 @@ function mob_class:check_for_death(cause, cmi_cause)
self.jockey = nil
end
local collisionbox
if self.collisionbox then
collisionbox = table.copy(self.collisionbox)
end
self.state = "die"
self.attack = nil
self.v_start = false
@ -406,15 +452,18 @@ function mob_class:check_for_death(cause, cmi_cause)
local frames = self.animation.die_end - self.animation.die_start
local speed = self.animation.die_speed or 15
length = max(frames / speed, 0) + DEATH_DELAY
self:set_animation("die")
self:set_animation( "die")
else
length = 1 + DEATH_DELAY
self:set_animation("stand", true)
self:set_animation( "stand", true)
end
-- Remove body after a few seconds and drop stuff
local kill = function(self)
if not self.object:get_luaentity() then return end
if not self.object:get_luaentity() then
return
end
death_handle(self)
local dpos = self.object:get_pos()
local cbox = self.collisionbox
@ -429,6 +478,7 @@ function mob_class:check_for_death(cause, cmi_cause)
else
minetest.after(length, kill, self)
end
return true
end
@ -443,12 +493,15 @@ end
-- Deal light damage to mob, returns true if mob died
function mob_class:deal_light_damage(pos, damage)
-- not during rain or snow
if (mcl_weather.rain.raining or mcl_weather.state == "snow") and mcl_weather.is_outdoor(pos) then return false end
if not ((mcl_weather.rain.raining or mcl_weather.state == "snow") and mcl_weather.is_outdoor(pos)) then
self.health = self.health - damage
self.health = self.health - damage
mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png")
return self:check_for_death("light", {type = "light"})
mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png")
if self:check_for_death("light", {type = "light"}) then
return true
end
end
end
-- environmental damage (water, lava, fire, light etc.)
@ -457,27 +510,49 @@ function mob_class:do_env_damage()
local pos = self.object:get_pos()
if not pos then return end
self.time_of_day = minetest.get_timeofday()
-- remove mob if beyond map limits
if not within_limits(pos) then
if not within_limits(pos, 0) then
mcl_burning.extinguish(self.object)
self.object:remove()
return true
end
-- sunlight damage
if (self.light_damage > 0 or self.sunlight_damage > 0 or self.ignited_by_sunlight) and pos.y >= mcl_vars.mg_overworld_min then
local sunlight = mcl_util.get_natural_light(pos) or 0
if self.light_damage > 0 and sunlight > 12 then
if self:deal_light_damage(pos, self.light_damage) then return true end
local node = minetest.get_node(pos)
if node then
if node.name ~= "ignore" then
-- put below code in this block if we can prove that unloaded maps are causing crash.
-- it should warn then error
else
--minetest.log("warning", "Pos is ignored: " .. dump(pos))
end
if sunlight >= minetest.LIGHT_MAX and (self.sunlight_damage > 0 or self.ignited_by_sunlight) then
if not self.armor_list or not self.armor_list.helmet or (self.armor_list.helmet and self.armor_list.helmet == "") then
if self.ignited_by_sunlight then
mcl_burning.set_on_fire(self.object, 10)
end
if self.sunlight_damage > 0 and self:deal_light_damage(pos, self.sunlight_damage) then return true end
local sunlight = mcl_util.get_natural_light(pos, self.time_of_day)
if self.light_damage ~= 0 and (sunlight or 0) > 12 then
if self:deal_light_damage(pos, self.light_damage) then
return true
end
end
local _, dim = mcl_worlds.y_to_layer(pos.y)
if (self.sunlight_damage ~= 0 or self.ignited_by_sunlight) and (sunlight or 0) >= minetest.LIGHT_MAX and dim == "overworld" then
if self.armor_list and not self.armor_list.helmet or not self.armor_list or self.armor_list and self.armor_list.helmet and self.armor_list.helmet == "" then
if self.ignited_by_sunlight then
mcl_burning.set_on_fire(self.object, 10)
else
self:deal_light_damage(pos, self.sunlight_damage)
return true
end
end
end
end
local y_level = self.collisionbox[2]
if self.child then
y_level = self.collisionbox[2] * 0.5
end
local standin = self.standing_in
@ -486,6 +561,10 @@ function mob_class:do_env_damage()
mcl_potions.give_effect_by_level("withering", self.object, 2, 2)
end
local nodef = minetest.registered_nodes[self.standing_in]
local nodef2 = minetest.registered_nodes[self.standing_on]
local nodef3 = minetest.registered_nodes[self.standing_under]
-- rain
if self.rain_damage > 0 and mcl_weather.rain.raining and mcl_weather.is_outdoor(pos) then
self.health = self.health - self.rain_damage
@ -517,7 +596,7 @@ function mob_class:do_env_damage()
end
if self.fire_damage > 0 and standin.groups.fire then
self.health = self.health - self.fire_damage
mcl_mobs.effect(vector.offset(pos, 0, 1, 0), 5, "fire_basic_flame.png", nil, nil, 1, nil)
mcl_mobs.effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
mcl_burning.set_on_fire(self.object, 5)
if self:check_for_death("fire", {type = "environment", pos = pos, node = standin.name}) then
return true
@ -575,6 +654,7 @@ function mob_class:do_env_damage()
-- Drowning damage
if self.breath_max ~= -1 then
local drowning = false
if self.breathes_in_water then
if not standin.groups.water then drowning = true end
elseif standin.drowning > 0 and self.standing_under.drowning > 0 then
@ -583,9 +663,7 @@ function mob_class:do_env_damage()
if drowning then
self.breath = max(0, self.breath - 1)
if not self.breathes_in_water then
mcl_mobs.effect(pos, 2, "bubble.png", nil, nil, 1, nil)
end
mcl_mobs.effect(pos, 2, "bubble.png", nil, nil, 1, nil)
if self.breath <= 0 then
local dmg = standin.drowning > 0 and standin.drowning or 4
self:damage_effect(dmg)
@ -610,7 +688,7 @@ function mob_class:do_env_damage()
-- Short grace period before starting to take suffocation damage.
-- This is different from players, who take damage instantly.
-- This has been done because mobs might briefly be inside solid nodes
-- when, e.g., climbing up stairs.
-- when e.g. climbing up stairs.
-- This is a bit hacky because it assumes that do_env_damage
-- is called roughly every second only.
self.suffocation_timer = self.suffocation_timer + 1
@ -630,7 +708,7 @@ function mob_class:do_env_damage()
return self:check_for_death("unknown", {type = "unknown"})
end
function mob_class:step_damage(dtime, pos)
function mob_class:step_damage (dtime, pos)
if not self.fire_resistant then
mcl_burning.tick(self.object, dtime, self)
if not self.object:get_pos() then return true end -- mcl_burning.tick may remove object immediately
@ -829,6 +907,7 @@ function mob_class:check_water_flow(dtime, pos)
local def = self.standing_in
-- Move item around on flowing liquids
if def and def.liquidtype == "flowing" then
--[[ Get flowing direction (function call from flowlib), if there's a liquid.
NOTE: According to Qwertymine, flowlib.quickflow is only reliable for liquids with a flowing distance of 7.
Luckily, this is exactly what we need if we only care about water, which has this flowing distance. ]]
@ -874,4 +953,3 @@ function mob_class:check_suspend(player_in_active_range)
return true
end
end

View File

@ -72,7 +72,7 @@ local axolotl = {
fly = true,
fly_in = { "mcl_core:water_source", "mclx_core:river_water_source" },
breathes_in_water = true,
jump = false, -- disable for now, because they will not find back
jump = false, -- would get them out of the water too often
damage = 2,
reach = 2,
attack_type = "dogfight",
@ -82,7 +82,13 @@ local axolotl = {
"mobs_mc:glow_squid",
"mobs_mc:salmon",
"mobs_mc:tropical_fish",
"mobs_mc:squid"
"mobs_mc:squid",
"mobs_mc:zombie", -- todo: only drowned?
"mobs_mc:baby_zombie",
"mobs_mc:husk",
"mobs_mc:baby_husk",
"mobs_mc:guardian_elder",
"mobs_mc:guardian",
},
runaway = true,
}

View File

@ -0,0 +1,99 @@
local S = minetest.get_translator("mobs_mc")
-- TODO: sounds
-- TODO: carry one item, spawn with item
-- TODO: add sleeping behavior
-- TODO: snow color depending on biome not randomly
-- TODO: pouncing - jump to attack behavior
-- TODO: use totem of undying when carried
-- Fox
local fox = {
description = S("Fox"),
type = "animal",
spawn_class = "passive",
can_despawn = true,
hp_min = 10,
hp_max = 10,
xp_min = 1,
xp_max = 3,
passive = false,
group_attack = false,
spawn_in_group = 4,
collisionbox = { -0.3, 0, -0.3, 0.3, 0.7, 0.3 },
visual = "mesh",
mesh = "mobs_mc_fox.b3d",
textures = {
{ "mobs_mc_fox.png", "mobs_mc_fox_sleep.png" },
{ "mobs_mc_snow_fox.png", "mobs_mc_snow_fox_sleep.png" }
},
makes_footstep_sound = true,
head_swivel = "Bone.001",
bone_eye_height = 0.5,
head_eye_height = 0.1,
horizontal_head_height = 0,
curiosity = 5,
head_yaw = "z",
sounds = { }, -- FIXME
pathfinding = 1,
floats = 1,
view_range = 16,
walk_chance = 50,
walk_velocity = 2,
run_velocity = 3,
damage = 2,
reach = 1,
attack_type = "dogfight",
fear_height = 5,
-- drops = { }, -- TODO: only what they are carrying
follow = { "mcl_farming:sweet_berry" }, -- TODO: and glow berries, taming
animation = {
stand_start = 1, stand_end = 20, stand_speed = 20,
walk_start = 120, walk_end = 160, walk_speed = 80,
run_start = 160, run_end = 199, run_speed = 80,
punch_start = 80, punch_end = 105, punch_speed = 80,
sit_start = 30 , sit_end = 50,
sleep_start = 55, sleep_end = 75,
--wiggle_start = 170, wiggle_end = 230,
--die_start = 0, die_end = 0, die_speed = 0,--die_loop = 0,
},
jump = true,
jump_height = 2,
attacks_monsters = true,
attack_animals = true,
specific_attack = {
"mobs_mc:chicken", "mobs_mc:rabbit",
"mobs_mc:cod", "mobs_mc:salmon", "mobs_mc:tropical_fish"
-- TODO: baby turtles, monsters?
},
runaway_from = {
-- they are too cute for this: "player",
"mobs_mc:wolf",
-- TODO: and polar bear
},
}
mcl_mobs.register_mob("mobs_mc:fox", fox)
-- Spawn
mcl_mobs:spawn_specific(
"mobs_mc:fox",
"overworld",
"ground",
{
"Taiga",
"Taiga_beach",
"MegaTaiga",
"MegaSpruceTaiga",
"ColdTaiga",
"ColdTaiga_beach",
},
0,
minetest.LIGHT_MAX+1,
30,
80,
7,
mobs_mc.water_level+3,
mcl_vars.mg_overworld_max)
mcl_mobs.register_egg("mobs_mc:fox", "Fox", "#ba9f8b", "#9f5219", 0)

View File

@ -149,6 +149,7 @@ dofile(path .. "/salmon.lua")
dofile(path .. "/tropical_fish.lua")
dofile(path .. "/dolphin.lua")
dofile(path .. "/fox.lua") -- Mesh and animation by https://codeberg.org/pixelzone texture https://github.com/NovaWostra/Pixel-Perfection-Chorus-Eddit
dofile(path .. "/glow_squid.lua")

Binary file not shown.

View File

@ -952,6 +952,9 @@ local function go_home(entity, sleep)
entity.order = nil
return
end
-- in case pathfinding fails, turn into the right direction anyways
local p = entity.object:get_pos()
entity:turn_in_direction(b.x - p.x, b.z - p.z, 8)
entity:gopath(b,function(entity,b)
local b = entity._bed
@ -1331,7 +1334,7 @@ local function do_work (self)
--mcl_log("Jobsite not valid")
return false
end
if vector.distance(self.object:get_pos(),self._jobsite) < 2 then
if vector.distance(self.object:get_pos(),self._jobsite) < 1.5 then
--mcl_log("Made it to work ok callback!")
return true
else
@ -2316,8 +2319,8 @@ mcl_mobs.register_mob("mobs_mc:villager", {
if cmi_cause and cmi_cause.puncher then
local l = cmi_cause.puncher:get_luaentity()
if l and math.random(2) == 1 and (l.name == "mobs_mc:zombie" or l.name == "mobs_mc:baby_zombie" or l.name == "mobs_mc:villager_zombie" or l.name == "mobs_mc:husk") then
mcl_util.replace_mob(self.object, "mobs_mc:villager_zombie")
if l and math.random(2) == 1 and( l.name == "mobs_mc:zombie" or l.name == "mobs_mc:baby_zombie" or l.name == "mobs_mc:villager_zombie" or l.name == "mobs_mc:husk") then
mcl_util.replace_mob(self.object,"mobs_mc:villager_zombie")
return true
end
end

View File

@ -157,6 +157,7 @@ function mcl_weather.rain.clear()
mcl_weather.rain.remove_sound(player)
mcl_weather.rain.remove_player(player)
mcl_weather.remove_spawners_player(player)
player:set_clouds({color="#FFF0EF"})
end
end

View File

@ -2,6 +2,7 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local NIGHT_VISION_RATIO = 0.45
local DEBUG = false
-- Settings
local minimum_update_interval = { 250e3 }
@ -190,8 +191,8 @@ end
function skycolor_utils.convert_to_rgb(minval, maxval, current_val, colors)
-- Clamp current_val to valid range
current_val = math.min(minval, current_val)
current_val = math.max(maxval, current_val)
current_val = math.max(minval, current_val)
current_val = math.min(maxval, current_val)
-- Rescale current_val from a number between minval and maxval to a number between 1 and #colors
local scaled_value = (current_val - minval) / (maxval - minval) * (#colors - 1) + 1.0
@ -199,7 +200,7 @@ function skycolor_utils.convert_to_rgb(minval, maxval, current_val, colors)
-- Get the first color's values
local index1 = math.floor(scaled_value)
local color1 = colors[index1]
local frac1 = scaled_value - index1
local frac1 = 1.0 - (scaled_value - index1)
-- Get the second color's values
local index2 = math.min(index1 + 1, #colors) -- clamp to maximum color index (will occur if index1 == #colors)
@ -207,11 +208,32 @@ function skycolor_utils.convert_to_rgb(minval, maxval, current_val, colors)
local color2 = colors[index2]
-- Interpolate between color1 and color2
return {
local res = {
r = math.floor(frac1 * color1.r + frac2 * color2.r),
g = math.floor(frac1 * color1.g + frac2 * color2.g),
b = math.floor(frac1 * color1.b + frac2 * color2.b),
}
if DEBUG then
minetest.log(dump({
minval = minval,
maxval = maxval,
current_val = current_val,
colors = colors,
res = res,
scaled_value = scaled_value,
frac1 = frac1,
index1 = index1,
color1 = color1,
frac2 = frac2,
index2 = index2,
color2 = color2,
}))
end
return res
end
-- Simple getter. Either returns user given players list or get all connected players if none provided

View File

@ -40,18 +40,21 @@ function dimension_handlers.overworld(player, sky_data)
end
-- Use overworld defaults
local day_color = mcl_weather.skycolor.get_sky_layer_color(0.15)
local day_color = mcl_weather.skycolor.get_sky_layer_color(0.5)
local dawn_color = mcl_weather.skycolor.get_sky_layer_color(0.27)
local night_color = mcl_weather.skycolor.get_sky_layer_color(0.1)
sky_data.sky = {
type = "regular",
sky_color = {
day_sky = day_color,
day_horizon = day_color,
dawn_sky = dawn_color,
dawn_horizon = dawn_color,
night_sky = night_color,
night_horizon = night_color,
day_sky = day_color or "#7BA4FF",
day_horizon = day_color or "#C0D8FF",
dawn_sky = dawn_color or "7BA4FF",
dawn_horizon = dawn_color or "#C0D8FF",
night_sky = night_color or "000000",
night_horizon = night_color or "4A6790",
fog_sun_tint = "#ff5f33",
fog_moon_tint = nil,
fog_tint_type = "custom",
},
clouds = true,
}
@ -75,18 +78,15 @@ function dimension_handlers.overworld(player, sky_data)
local day_color = mcl_weather.skycolor.get_sky_layer_color(0.5)
local dawn_color = mcl_weather.skycolor.get_sky_layer_color(0.75)
local night_color = mcl_weather.skycolor.get_sky_layer_color(0)
sky_data.sky = {
type = "regular",
sky_color = {
day_sky = day_color,
day_horizon = day_color,
dawn_sky = dawn_color,
dawn_horizon = dawn_color,
night_sky = night_color,
night_horizon = night_color,
},
clouds = true,
}
table.update(sky_data.sky.sky_color,{
day_sky = day_color or "#7BA4FF",
day_horizon = day_color or "#C0D8FF",
dawn_sky = dawn_color or "7BA4FF",
dawn_horizon = dawn_color or "#C0D8FF",
night_sky = night_color or "000000",
night_horizon = night_color or "4A6790",
fog_tint_type = "default",
})
sky_data.sun = {visible = false, sunrise_visible = false}
sky_data.moon = {visible = false}
sky_data.stars = {visible = false}
@ -164,7 +164,8 @@ function dimension_handlers.nether(player, sky_data)
end
function dimension_handlers.void(player, sky_data)
sky_data.sky = { type = "plain",
sky_data.sky = {
type = "plain",
base_color = "#000000",
clouds = false,
}

View File

@ -75,13 +75,15 @@ function mcl_weather.has_snow(pos)
end
function mcl_weather.snow.set_sky_box()
mcl_weather.skycolor.add_layer(
"weather-pack-snow-sky",
{{r=0, g=0, b=0},
{r=85, g=86, b=86},
{r=135, g=135, b=135},
{r=85, g=86, b=86},
{r=0, g=0, b=0}})
if mcl_weather.skycolor.current_layer_name() ~= "weather-pack-snow-sky" then
mcl_weather.skycolor.add_layer(
"weather-pack-snow-sky",
{{r=0, g=0, b=0},
{r=85, g=86, b=86},
{r=135, g=135, b=135},
{r=85, g=86, b=86},
{r=0, g=0, b=0}})
end
mcl_weather.skycolor.active = true
for _, player in pairs(get_connected_players()) do
player:set_clouds({color="#ADADADE8"})

View File

@ -23,13 +23,15 @@ minetest.register_globalstep(function(dtime)
mcl_weather.rain.make_weather()
if mcl_weather.thunder.init_done == false then
mcl_weather.skycolor.add_layer("weather-pack-thunder-sky", {
{r=0, g=0, b=0},
{r=40, g=40, b=40},
{r=85, g=86, b=86},
{r=40, g=40, b=40},
{r=0, g=0, b=0},
})
if mcl_weather.skycolor.current_layer_name() ~= "weather-pack-thunder-sky" then
mcl_weather.skycolor.add_layer("weather-pack-thunder-sky", {
{r=0, g=0, b=0},
{r=40, g=40, b=40},
{r=85, g=86, b=86},
{r=40, g=40, b=40},
{r=0, g=0, b=0},
})
end
mcl_weather.skycolor.active = true
for _, player in pairs(get_connected_players()) do
player:set_clouds({color="#3D3D3FE8"})

View File

@ -5,9 +5,6 @@
--- Copyright (C) 2022 - 2023, Michieal. See License.txt
-- CONSTS
local DOUBLE_DROP_CHANCE = 8
-- Used everywhere. Often this is just the name, but it makes sense to me as BAMBOO, because that's how I think of it...
-- "BAMBOO" goes here.
local BAMBOO = "mcl_bamboo:bamboo"
local BAMBOO_ENDCAP_NAME = "mcl_bamboo:bamboo_endcap"
local BAMBOO_PLANK = BAMBOO .. "_plank"
@ -16,7 +13,7 @@ local BAMBOO_PLANK = BAMBOO .. "_plank"
local modname = minetest.get_current_modname()
local S = minetest.get_translator(modname)
local node_sound = mcl_sounds.node_sound_wood_defaults()
local pr = PseudoRandom((os.time() + 15766) * 12) -- switched from math.random() to PseudoRandom because the random wasn't very random.
local pr = PseudoRandom((os.time() + 15766) * 12)
local on_rotate
if minetest.get_modpath("screwdriver") then
@ -31,33 +28,7 @@ local bamboo_def = {
paramtype = "light",
groups = {handy = 1, axey = 1, choppy = 1, dig_by_piston = 1, plant = 1, non_mycelium_plant = 1, flammable = 3},
sounds = node_sound,
drop = {
max_items = 1,
-- From the API:
-- max_items: Maximum number of item lists to drop.
-- The entries in 'items' are processed in order. For each:
-- Item filtering is applied, chance of drop is applied, if both are
-- successful the entire item list is dropped.
-- Entry processing continues until the number of dropped item lists
-- equals 'max_items'.
-- Therefore, entries should progress from low to high drop chance.
items = {
-- Examples:
{
-- 1 in DOUBLE_DROP_CHANCE chance of dropping.
-- Default rarity is '1'.
rarity = DOUBLE_DROP_CHANCE,
items = {BAMBOO .. " 2"},
},
{
-- 1 in 1 chance of dropping. (Note: this means that it will drop 100% of the time.)
-- Default rarity is '1'.
rarity = 1,
items = {BAMBOO},
},
},
},
drop = BAMBOO,
inventory_image = "mcl_bamboo_bamboo_shoot.png",
wield_image = "mcl_bamboo_bamboo_shoot.png",
@ -86,7 +57,6 @@ local bamboo_def = {
on_rotate = on_rotate,
on_place = function(itemstack, placer, pointed_thing)
if not pointed_thing then
return itemstack
end
@ -241,9 +211,6 @@ local bamboo_def = {
if node_above and ((bamboo_node and bamboo_node > 0) or node_above.name == BAMBOO_ENDCAP_NAME) then
minetest.remove_node(new_pos)
minetest.sound_play(node_sound.dug, sound_params, true)
if pr:next(1, DOUBLE_DROP_CHANCE) == 1 then
minetest.add_item(new_pos, istack)
end
minetest.add_item(new_pos, istack)
end
end,

View File

@ -9,8 +9,6 @@ local SIDE_SCAFFOLDING = false
local SIDE_SCAFFOLD_NAME = "mcl_bamboo:scaffolding_horizontal"
-- ---------------------------------------------------------------------------
local SCAFFOLDING_NAME = "mcl_bamboo:scaffolding"
-- Used everywhere. Often this is just the name, but it makes sense to me as BAMBOO, because that's how I think of it...
-- "BAMBOO" goes here.
local BAMBOO = "mcl_bamboo:bamboo"
local BAMBOO_PLANK = BAMBOO .. "_plank"

View File

@ -7,8 +7,6 @@
-- LOCALS
local modname = minetest.get_current_modname()
-- Used everywhere. Often this is just the name, but it makes sense to me as BAMBOO, because that's how I think of it...
-- "BAMBOO" goes here.
local BAMBOO = "mcl_bamboo:bamboo"
mcl_bamboo = {}

View File

@ -5,8 +5,6 @@
--- These are all of the fuel recipes and all of the crafting recipes, consolidated into one place.
--- Copyright (C) 2022 - 2023, Michieal. See License.txt
-- Used everywhere. Often this is just the name, but it makes sense to me as BAMBOO, because that's how I think of it...
-- "BAMBOO" goes here.
local BAMBOO = "mcl_bamboo:bamboo"
local BAMBOO_PLANK = BAMBOO .. "_plank"
-- Craftings

BIN
textures/mobs_mc_fox.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B