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). in lava (default: 8).
'fire_damage' holds the damage per second inflicted to mobs when standing 'fire_damage' holds the damage per second inflicted to mobs when standing
in fire (default: 1). 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). bright (above 13 light).
'suffocation' when true causes mobs to suffocate inside solid blocks (2 damage per second). '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. 'floats' when set to 1 mob will float in water, 0 has them sink.

View File

@ -93,7 +93,7 @@ end
-- Spawn a child -- Spawn a child
function mcl_mobs.spawn_child(pos, mob_type) 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 if not child then return end
local ent = child:get_luaentity() local ent = child:get_luaentity()

View File

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

View File

@ -37,22 +37,25 @@ function mob_class:stand()
end end
-- Turn towards a (nearby) target, primarily for path following -- 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 if not b then return end
local s = self.object:get_pos() local s = self.object:get_pos()
if vector_distance(b,s) < .1 then return true end if vector_distance(b,s) < .4 then return true end
if b.y > s.y then self:do_jump() 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, 5) self:turn_in_direction(b.x - s.x, b.z - s.z, 2)
self:set_velocity(self.walk_velocity) speed = speed or self.walk_velocity
self:set_animation("walk") self:set_velocity(speed)
self:set_animation(speed <= self.walk_velocity and "walk" or "run")
end end
-- Returns true is node can deal damage to self, except water damage -- Returns true is node can deal damage to self, except water damage
function mob_class:is_node_dangerous(nodename) function mob_class:is_node_dangerous(nodename)
local ndef = minetest.registered_nodes[nodename] local ndef = minetest.registered_nodes[nodename]
return ndef return ndef
and ((self.lava_damage > 0 and ndef.groups.lava) and ((self.lava_damage > 0 and (ndef.groups.lava or 0) > 0)
or (self.fire_damage > 0 and ndef.groups.fire) or (self.fire_damage > 0 and (ndef.groups.fire or 0) > 0)
or ((ndef.damage_per_second or 0) > 0)) or ((ndef.damage_per_second or 0) > 0))
end end
@ -65,12 +68,17 @@ function mob_class:is_node_waterhazard(nodename)
end end
function mob_class:target_visible(origin) function mob_class:target_visible(origin)
if not origin then return false end if not origin then return end
if not self.attack then return false end if not self.attack then return end
local target_pos = self.attack:get_pos() 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) 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 local cbox = self.collisionbox
-- TODO also worth testing midway between feet and head? -- TODO also worth testing midway between feet and head?
-- to top of entity -- to top of entity
@ -81,11 +89,23 @@ function mob_class:target_visible(origin)
else 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 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 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 return false
end end
-- check line of sight -- check line of sight
-- @param stepsize is ignored now
function mob_class:line_of_sight(pos1, pos2, stepsize) 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) return line_of_sight(pos1, pos2, self.see_through_opaque or mobs_see_through_opaque, true)
end 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.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) minetest.after(.1, function() if self and self.object then self._jumping_cliff = false end end)
return true return true
else
self._jumping_cliff = false
return false
end end
self._jumping_cliff = false
return false
end end
-- is mob facing a cliff or danger -- 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 if self.fly then return false end -- also avoids checking fish
local pos, yaw = self.object:get_pos(), self.object:get_yaw() local pos, yaw = self.object:get_pos(), self.object:get_yaw()
local cbox = self.collisionbox 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 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, ypos, pos.z + dir_z),
vector_new(pos.x + dir_x, floor(ypos - self.fear_height), 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 height = ypos + 0.4 - blocker.y
local chance = self.jump_height / (height * height) local chance = self.jump_height / (height * height)
if height >= self.fear_height and random() < chance then 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 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 return false -- allow to get out of the immediate danger
end 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 return false
end end
-- copy the 'mob facing cliff_or_danger check' from above, and rework to avoid water -- copy the 'mob facing cliff_or_danger check' from above, and rework to avoid water
function mob_class:is_at_water_danger() function mob_class:is_at_water_danger()
if self.water_damage == 0 and self.breath_max == -1 then return false end if self.water_damage == 0 and self.breath_max == -1 then
if self.fly then return false end -- also avoids checking fish --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) 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 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 pos, yaw = self.object:get_pos(), self.object:get_yaw()
local cbox = self.collisionbox 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 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 if not los then
local bnode = minetest.get_node(blocker) 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 end
return false return false
end end
@ -245,7 +280,16 @@ function mob_class:do_jump()
-- what is in front of mob? -- what is in front of mob?
local nod = minetest.get_node(vector_offset(pos, dir_x, 0.5, dir_z)).name 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. -- 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. -- 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 -- we don't attempt to jump if there's a stack of blocks blocking, unless attacking
local ntdef = minetest.registered_nodes[node_top] 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 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(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)) v.y = math.min(-self.fall_speed, math.max(v.y, self.fall_speed))
self.object:set_velocity(v) self.object:set_velocity(v)
@ -277,7 +314,7 @@ function mob_class:do_jump()
end end
-- if we jumped against a block/wall 4 times then turn -- 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 self._jump_count = (self._jump_count or 0) + 1
if self._jump_count == 4 then if self._jump_count == 4 then
self:turn_by(TWOPI * (random() - 0.5), 8) 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 math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class 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 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 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 PATHFINDING = "gowp"
local one_down = vector.new(0,-1,0)
local one_up = vector.new(0,1,0)
local plane_adjacents = { local plane_adjacents = {
vector.new(1,0,0), vector.new(1,0,0),
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 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 LOG_MODULE = "[Mobs Pathfinding]"
local function mcl_log (message) 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 -- 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 function generate_enriched_path(wp_in, door_open_pos, door_close_pos, cur_door_pos)
local wp_out = {} 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 for i, cur_pos in pairs(wp_in) do
local action = nil 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") mcl_log ("Door open match")
action = {type = "door", action = "open", target = cur_door_pos} 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 elseif door_close_pos and vector.equals(cur_pos, door_close_pos) then
mcl_log ("Door close match") mcl_log ("Door close match")
action = {type = "door", action = "close", target = cur_door_pos} 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 elseif cur_door_pos and vector.equals(cur_pos, cur_door_pos) then
mcl_log("Current door pos") mcl_log("Current door pos")
action = {type = "door", action = "open", target = cur_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 end
wp_out[i] = {} 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]["failed_attempts"] = 0
wp_out[i]["action"] = action 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. -- 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) local function calculate_path_through_door (p, cur_door_pos, t)
if not cur_door_pos then return end
if t then 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 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 end
local enriched_path = nil for _,v in pairs(plane_adjacents) do
local wp, prospective_wp 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 prospective_wp = minetest.find_path(p, pos_closest_to_door, PATHFINDING_SEARCH_DISTANCE, 1, 4)
local other_side_of_door = nil
if cur_door_pos then if prospective_wp then
mcl_log("Found a door near: " .. minetest.pos_to_string(cur_door_pos)) 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 table.insert(prospective_wp, cur_door_pos)
pos_closest_to_door = vector.add(cur_door_pos,v)
other_side_of_door = vector.add(cur_door_pos,-v)
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 if wp_otherside_door_to_target and #wp_otherside_door_to_target > 0 then
mcl_log("We have air space next to door at: " .. minetest.pos_to_string(pos_closest_to_door)) append_paths (prospective_wp, wp_otherside_door_to_target)
mcl_log("We have a path from outside door to target")
prospective_wp = minetest.find_path(p, pos_closest_to_door, PATHFINDING_SEARCH_DISTANCE, 1, 4) return generate_enriched_path(prospective_wp, pos_closest_to_door, other_side_of_door, cur_door_pos)
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
else else
mcl_log("No t, just add other side of door") mcl_log("We cannot path from outside door to target")
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
end end
else 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 end
else
mcl_log("Cannot path to this air block next to door.")
end end
end end
else
mcl_log("No door found")
end 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 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) 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 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 self.order = nil
local p = self.object:get_pos() -- maybe feet are buried in solid?
local t = vector.offset(target,0,1,0) 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 --Check direct route
local wp = minetest.find_path(p, t, PATHFINDING_SEARCH_DISTANCE, 1, 4) 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 if not wp then
mcl_log("### No direct path. Path through door closest to target.") mcl_log("### No direct path. Path through door closest to target.")
local door_near_target = minetest.find_node_near(target, 16, {"group:door"}) 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) wp = calculate_path_through_door(p, door_near_target, t)
if not wp then if not wp then
mcl_log("### No path though door closest to target. Try door closest to origin.") 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 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) wp = calculate_path_through_door(p, door_closest, t)
-- Path through 2 doors -- 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] local pos_after_door_entry = path_through_closest_door[#path_through_closest_door]
if pos_after_door_entry then 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)) 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) 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 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 -- If cannot path, don't immediately try again
end 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 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) --output_table(wp)
self._target = t self._target = t
self.callback_arrived = callback_arrived self.callback_arrived = callback_arrived
self.current_target = table.remove(wp,1) self.current_target = table.remove(wp,1)
self.waypoints = wp while self.current_target and self.current_target.pos and vector.distance(p, self.current_target.pos) < 0.5 do
self.state = PATHFINDING --mcl_log("Skipping close initial waypoint")
return true self.current_target = table.remove(wp,1)
else end
self.state = "walk" if self.current_target and self.current_target.pos then
self.waypoints = nil self:turn_in_direction(self.current_target.pos.x - p.x, self.current_target.pos.z - p.z, 2)
self.current_target = nil self.waypoints = wp
-- minetest.log("no path found") self.state = PATHFINDING
return true
end
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 end
function mob_class:interact_with_door(action, target) 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 if closed and action == "open" and def.on_rightclick then
mcl_log("Open door") mcl_log("Open door")
def.on_rightclick(target,n,self) def.on_rightclick(target,n,self)
end elseif not closed and action == "close" and def.on_rightclick then
if not closed and action == "close" and def.on_rightclick then
mcl_log("Close door") mcl_log("Close door")
def.on_rightclick(target,n,self) def.on_rightclick(target,n,self)
end 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 else
mcl_log("Not door") mcl_log("Not door")
end end
@ -355,53 +428,56 @@ function mob_class:check_gowp(dtime)
-- More pathing to be done -- More pathing to be done
local distance_to_current_target = 50 local distance_to_current_target = 50
if self.current_target and self.current_target["pos"] then 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 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 = (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"]) --distance_to_current_target = vector.distance(p,self.current_target.pos)
end end
-- also check next target, maybe we were too fast -- also check next target, maybe we were too fast
local next_target = #self.waypoints > 1 and self.waypoints[1] 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 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 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 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 if distance_to_next_target < distance_to_current_target then
mcl_log("Skipped one waypoint.") mcl_log("Skipped one waypoint.")
self.current_target = table.remove(self.waypoints, 1) -- pop waypoint already self.current_target = table.remove(self.waypoints, 1) -- pop waypoint already
distance_to_current_target = distance_to_next_target distance_to_current_target = distance_to_next_target
end end
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.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.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 -- 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 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 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. -- 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"]) self:do_pathfind_action (self.current_target["action"])
local failed_attempts = self.current_target["failed_attempts"] 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) self.current_target = table.remove(self.waypoints, 1)
-- use smoothing -- use smoothing -- TODO: check for blockers before cutting corners?
--[[if #self.waypoints > 0 and not self.current_target["action"] then if #self.waypoints > 0 and not self.current_target["action"] then
local curwp, nextwp = self.current_target["pos"], self.waypoints[1]["pos"] 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)) 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 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 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 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. -- 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 self.current_target["failed_attempts"] = self.current_target["failed_attempts"] + 1
local failed_attempts = self.current_target["failed_attempts"] local failed_attempts = self.current_target["failed_attempts"]
if failed_attempts >= PATHFINDING_FAIL_THRESHOLD then 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.state = "stand"
self.current_target = nil self.current_target = nil
self.waypoints = nil self.waypoints = nil
@ -412,9 +488,8 @@ function mob_class:check_gowp(dtime)
return return
end 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: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. -- Do i just delete current_target, and return so we can find final path.
else else
-- Not at target, no current waypoints or current_target. Through the door and should be able to path to target. -- 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. -- I don't think we need the following anymore, but test first.
-- Maybe just need something to path to target if no waypoints left -- 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 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 updated_p = self.object:get_pos()
local distance_to_cur_targ = vector.distance(updated_p,self.current_target["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 end
return return
end end
--]]--
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 local mob_active_range = tonumber(minetest.settings:get("mcl_mob_active_range")) or 48
-- check if within physical map limits (-30911 to 30927) -- check if within physical map limits (-30911 to 30927)
local map_min, map_max = -30912, 30928 local function within_limits(pos, radius)
if mcl_vars and mcl_vars.mapgen_edge_min and mcl_vars.mapgen_edge_max then local wmin, wmax = -30912, 30928
map_min, map_max = mcl_vars.mapgen_edge_min, mcl_vars.mapgen_edge_max if mcl_vars then
end if mcl_vars.mapgen_edge_min and mcl_vars.mapgen_edge_max then
local function within_limits(pos) wmin, wmax = mcl_vars.mapgen_edge_min, mcl_vars.mapgen_edge_max
return pos end
and pos.x >= map_min and pos.x <= map_max end
and pos.y >= map_min and pos.y <= map_max if radius then
and pos.z >= map_min and pos.z <= map_max 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 end
-- Function that update some helpful variables on the mobs position: -- Function that update some helpful variables on the mobs position:
@ -222,7 +229,7 @@ end
-- @return target angle -- @return target angle
function mob_class:turn_in_direction(dx, dz, delay, dtime) 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 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 end
-- Absolute turn into a particular direction -- Absolute turn into a particular direction
-- @param yaw number: angle in radians -- @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.standing_in or self.standing_in.name == "ignore" then return true end -- unknown?
if not self.fly_in then return false end if not self.fly_in then return false end
local nod = self.standing_in.name 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 for _,checknode in pairs(self.fly_in) do
if nod == checknode then return true end if nod == checknode then return true end
end end
@ -315,15 +322,26 @@ end
-- check if mob is dead or only hurt -- check if mob is dead or only hurt
function mob_class:check_for_death(cause, cmi_cause) function mob_class:check_for_death(cause, cmi_cause)
if self.state == "die" then return true end
-- skip effects if unchanged if self.state == "die" then
if self.health == self.old_health and self.health > 0 then return false end 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 local damaged = self.health < self.old_health
self.old_health = self.health self.old_health = self.health
-- still got some health?
if self.health > 0 then 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. -- play damage sound if health was reduced and make mob flash red.
if damaged then if damaged then
@ -341,35 +359,57 @@ function mob_class:check_for_death(cause, cmi_cause)
self:mob_sound("death") self:mob_sound("death")
local function death_handle(self) 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 if cause == "lava" or cause == "fire" then
self:item_drop(true, 0) self:item_drop(true, 0)
return else
end local wielditem = ItemStack()
if cause == "rain" or cause == "water" or cause == "drowning" or cause == "suffocation" then if cause == "hit" then
self:item_drop(false, 0) local puncher = cmi_cause.puncher
return if puncher then
end wielditem = puncher:get_wielded_item()
local wielditem = cause == "hit" and cmi_cause.puncher and cmi_cause.puncher:get_wielded_item() end
local cooked = mcl_burning.is_burning(self.object) or (wielditem and mcl_enchanting.has_enchantment(wielditem, "fire_aspect")) end
local looting = wielditem and mcl_enchanting.get_enchantment(wielditem, "looting") local cooked = mcl_burning.is_burning(self.object) or mcl_enchanting.has_enchantment(wielditem, "fire_aspect")
self:item_drop(cooked, looting) 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 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 pos = self.object:get_pos()
local xp_amount = random(self.xp_min, self.xp_max) 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 if not mcl_sculk.handle_death(pos, xp_amount) then
mcl_experience.throw_xp(pos, xp_amount) --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
end end
end end
-- execute custom death function -- execute custom death function
if self.on_die then if self.on_die then
local pos = self.object:get_pos() 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" self.state = "die"
mcl_burning.extinguish(self.object) mcl_burning.extinguish(self.object)
self.object:remove() self.object:remove()
@ -382,6 +422,12 @@ function mob_class:check_for_death(cause, cmi_cause)
self.jockey = nil self.jockey = nil
end end
local collisionbox
if self.collisionbox then
collisionbox = table.copy(self.collisionbox)
end
self.state = "die" self.state = "die"
self.attack = nil self.attack = nil
self.v_start = false 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 frames = self.animation.die_end - self.animation.die_start
local speed = self.animation.die_speed or 15 local speed = self.animation.die_speed or 15
length = max(frames / speed, 0) + DEATH_DELAY length = max(frames / speed, 0) + DEATH_DELAY
self:set_animation("die") self:set_animation( "die")
else else
length = 1 + DEATH_DELAY length = 1 + DEATH_DELAY
self:set_animation("stand", true) self:set_animation( "stand", true)
end end
-- Remove body after a few seconds and drop stuff -- Remove body after a few seconds and drop stuff
local kill = function(self) 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) death_handle(self)
local dpos = self.object:get_pos() local dpos = self.object:get_pos()
local cbox = self.collisionbox local cbox = self.collisionbox
@ -429,6 +478,7 @@ function mob_class:check_for_death(cause, cmi_cause)
else else
minetest.after(length, kill, self) minetest.after(length, kill, self)
end end
return true return true
end end
@ -443,12 +493,15 @@ end
-- Deal light damage to mob, returns true if mob died -- Deal light damage to mob, returns true if mob died
function mob_class:deal_light_damage(pos, damage) function mob_class:deal_light_damage(pos, damage)
-- not during rain or snow if not ((mcl_weather.rain.raining or mcl_weather.state == "snow") and mcl_weather.is_outdoor(pos)) then
if (mcl_weather.rain.raining or mcl_weather.state == "snow") and mcl_weather.is_outdoor(pos) then return false end self.health = self.health - damage
self.health = self.health - damage mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png")
mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png")
return self:check_for_death("light", {type = "light"}) if self:check_for_death("light", {type = "light"}) then
return true
end
end
end end
-- environmental damage (water, lava, fire, light etc.) -- environmental damage (water, lava, fire, light etc.)
@ -457,27 +510,49 @@ function mob_class:do_env_damage()
local pos = self.object:get_pos() local pos = self.object:get_pos()
if not pos then return end if not pos then return end
self.time_of_day = minetest.get_timeofday()
-- remove mob if beyond map limits -- remove mob if beyond map limits
if not within_limits(pos) then if not within_limits(pos, 0) then
mcl_burning.extinguish(self.object) mcl_burning.extinguish(self.object)
self.object:remove() self.object:remove()
return true return true
end end
-- sunlight damage local node = minetest.get_node(pos)
if (self.light_damage > 0 or self.sunlight_damage > 0 or self.ignited_by_sunlight) and pos.y >= mcl_vars.mg_overworld_min then if node then
local sunlight = mcl_util.get_natural_light(pos) or 0 if node.name ~= "ignore" then
if self.light_damage > 0 and sunlight > 12 then -- put below code in this block if we can prove that unloaded maps are causing crash.
if self:deal_light_damage(pos, self.light_damage) then return true end -- it should warn then error
else
--minetest.log("warning", "Pos is ignored: " .. dump(pos))
end 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 local sunlight = mcl_util.get_natural_light(pos, self.time_of_day)
if self.ignited_by_sunlight then
mcl_burning.set_on_fire(self.object, 10) if self.light_damage ~= 0 and (sunlight or 0) > 12 then
end if self:deal_light_damage(pos, self.light_damage) then
if self.sunlight_damage > 0 and self:deal_light_damage(pos, self.sunlight_damage) then return true end return true
end end
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 end
local standin = self.standing_in 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) mcl_potions.give_effect_by_level("withering", self.object, 2, 2)
end 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 -- rain
if self.rain_damage > 0 and mcl_weather.rain.raining and mcl_weather.is_outdoor(pos) then if self.rain_damage > 0 and mcl_weather.rain.raining and mcl_weather.is_outdoor(pos) then
self.health = self.health - self.rain_damage self.health = self.health - self.rain_damage
@ -517,7 +596,7 @@ function mob_class:do_env_damage()
end end
if self.fire_damage > 0 and standin.groups.fire then if self.fire_damage > 0 and standin.groups.fire then
self.health = self.health - self.fire_damage 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) mcl_burning.set_on_fire(self.object, 5)
if self:check_for_death("fire", {type = "environment", pos = pos, node = standin.name}) then if self:check_for_death("fire", {type = "environment", pos = pos, node = standin.name}) then
return true return true
@ -575,6 +654,7 @@ function mob_class:do_env_damage()
-- Drowning damage -- Drowning damage
if self.breath_max ~= -1 then if self.breath_max ~= -1 then
local drowning = false local drowning = false
if self.breathes_in_water then if self.breathes_in_water then
if not standin.groups.water then drowning = true end if not standin.groups.water then drowning = true end
elseif standin.drowning > 0 and self.standing_under.drowning > 0 then elseif standin.drowning > 0 and self.standing_under.drowning > 0 then
@ -583,9 +663,7 @@ function mob_class:do_env_damage()
if drowning then if drowning then
self.breath = max(0, self.breath - 1) 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)
mcl_mobs.effect(pos, 2, "bubble.png", nil, nil, 1, nil)
end
if self.breath <= 0 then if self.breath <= 0 then
local dmg = standin.drowning > 0 and standin.drowning or 4 local dmg = standin.drowning > 0 and standin.drowning or 4
self:damage_effect(dmg) self:damage_effect(dmg)
@ -610,7 +688,7 @@ function mob_class:do_env_damage()
-- Short grace period before starting to take suffocation damage. -- Short grace period before starting to take suffocation damage.
-- This is different from players, who take damage instantly. -- This is different from players, who take damage instantly.
-- This has been done because mobs might briefly be inside solid nodes -- 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 -- This is a bit hacky because it assumes that do_env_damage
-- is called roughly every second only. -- is called roughly every second only.
self.suffocation_timer = self.suffocation_timer + 1 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"}) return self:check_for_death("unknown", {type = "unknown"})
end end
function mob_class:step_damage(dtime, pos) function mob_class:step_damage (dtime, pos)
if not self.fire_resistant then if not self.fire_resistant then
mcl_burning.tick(self.object, dtime, self) mcl_burning.tick(self.object, dtime, self)
if not self.object:get_pos() then return true end -- mcl_burning.tick may remove object immediately 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 local def = self.standing_in
-- Move item around on flowing liquids -- Move item around on flowing liquids
if def and def.liquidtype == "flowing" then if def and def.liquidtype == "flowing" then
--[[ Get flowing direction (function call from flowlib), if there's a liquid. --[[ 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. 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. ]] 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 return true
end end
end end

View File

@ -72,7 +72,7 @@ local axolotl = {
fly = true, fly = true,
fly_in = { "mcl_core:water_source", "mclx_core:river_water_source" }, fly_in = { "mcl_core:water_source", "mclx_core:river_water_source" },
breathes_in_water = true, 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, damage = 2,
reach = 2, reach = 2,
attack_type = "dogfight", attack_type = "dogfight",
@ -82,7 +82,13 @@ local axolotl = {
"mobs_mc:glow_squid", "mobs_mc:glow_squid",
"mobs_mc:salmon", "mobs_mc:salmon",
"mobs_mc:tropical_fish", "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, 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 .. "/tropical_fish.lua")
dofile(path .. "/dolphin.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") dofile(path .. "/glow_squid.lua")

Binary file not shown.

View File

@ -952,6 +952,9 @@ local function go_home(entity, sleep)
entity.order = nil entity.order = nil
return return
end 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) entity:gopath(b,function(entity,b)
local b = entity._bed local b = entity._bed
@ -1331,7 +1334,7 @@ local function do_work (self)
--mcl_log("Jobsite not valid") --mcl_log("Jobsite not valid")
return false return false
end 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!") --mcl_log("Made it to work ok callback!")
return true return true
else else
@ -2316,8 +2319,8 @@ mcl_mobs.register_mob("mobs_mc:villager", {
if cmi_cause and cmi_cause.puncher then if cmi_cause and cmi_cause.puncher then
local l = cmi_cause.puncher:get_luaentity() 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 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") mcl_util.replace_mob(self.object,"mobs_mc:villager_zombie")
return true return true
end end
end end

View File

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

View File

@ -2,6 +2,7 @@
local modname = minetest.get_current_modname() local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname) local modpath = minetest.get_modpath(modname)
local NIGHT_VISION_RATIO = 0.45 local NIGHT_VISION_RATIO = 0.45
local DEBUG = false
-- Settings -- Settings
local minimum_update_interval = { 250e3 } local minimum_update_interval = { 250e3 }
@ -190,8 +191,8 @@ end
function skycolor_utils.convert_to_rgb(minval, maxval, current_val, colors) function skycolor_utils.convert_to_rgb(minval, maxval, current_val, colors)
-- Clamp current_val to valid range -- Clamp current_val to valid range
current_val = math.min(minval, current_val) current_val = math.max(minval, current_val)
current_val = math.max(maxval, 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 -- 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 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 -- Get the first color's values
local index1 = math.floor(scaled_value) local index1 = math.floor(scaled_value)
local color1 = colors[index1] local color1 = colors[index1]
local frac1 = scaled_value - index1 local frac1 = 1.0 - (scaled_value - index1)
-- Get the second color's values -- Get the second color's values
local index2 = math.min(index1 + 1, #colors) -- clamp to maximum color index (will occur if index1 == #colors) 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] local color2 = colors[index2]
-- Interpolate between color1 and color2 -- Interpolate between color1 and color2
return { local res = {
r = math.floor(frac1 * color1.r + frac2 * color2.r), r = math.floor(frac1 * color1.r + frac2 * color2.r),
g = math.floor(frac1 * color1.g + frac2 * color2.g), g = math.floor(frac1 * color1.g + frac2 * color2.g),
b = math.floor(frac1 * color1.b + frac2 * color2.b), 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 end
-- Simple getter. Either returns user given players list or get all connected players if none provided -- 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 end
-- Use overworld defaults -- 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 dawn_color = mcl_weather.skycolor.get_sky_layer_color(0.27)
local night_color = mcl_weather.skycolor.get_sky_layer_color(0.1) local night_color = mcl_weather.skycolor.get_sky_layer_color(0.1)
sky_data.sky = { sky_data.sky = {
type = "regular", type = "regular",
sky_color = { sky_color = {
day_sky = day_color, day_sky = day_color or "#7BA4FF",
day_horizon = day_color, day_horizon = day_color or "#C0D8FF",
dawn_sky = dawn_color, dawn_sky = dawn_color or "7BA4FF",
dawn_horizon = dawn_color, dawn_horizon = dawn_color or "#C0D8FF",
night_sky = night_color, night_sky = night_color or "000000",
night_horizon = night_color, night_horizon = night_color or "4A6790",
fog_sun_tint = "#ff5f33",
fog_moon_tint = nil,
fog_tint_type = "custom",
}, },
clouds = true, 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 day_color = mcl_weather.skycolor.get_sky_layer_color(0.5)
local dawn_color = mcl_weather.skycolor.get_sky_layer_color(0.75) local dawn_color = mcl_weather.skycolor.get_sky_layer_color(0.75)
local night_color = mcl_weather.skycolor.get_sky_layer_color(0) local night_color = mcl_weather.skycolor.get_sky_layer_color(0)
sky_data.sky = { table.update(sky_data.sky.sky_color,{
type = "regular", day_sky = day_color or "#7BA4FF",
sky_color = { day_horizon = day_color or "#C0D8FF",
day_sky = day_color, dawn_sky = dawn_color or "7BA4FF",
day_horizon = day_color, dawn_horizon = dawn_color or "#C0D8FF",
dawn_sky = dawn_color, night_sky = night_color or "000000",
dawn_horizon = dawn_color, night_horizon = night_color or "4A6790",
night_sky = night_color, fog_tint_type = "default",
night_horizon = night_color, })
},
clouds = true,
}
sky_data.sun = {visible = false, sunrise_visible = false} sky_data.sun = {visible = false, sunrise_visible = false}
sky_data.moon = {visible = false} sky_data.moon = {visible = false}
sky_data.stars = {visible = false} sky_data.stars = {visible = false}
@ -164,7 +164,8 @@ function dimension_handlers.nether(player, sky_data)
end end
function dimension_handlers.void(player, sky_data) function dimension_handlers.void(player, sky_data)
sky_data.sky = { type = "plain", sky_data.sky = {
type = "plain",
base_color = "#000000", base_color = "#000000",
clouds = false, clouds = false,
} }

View File

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

View File

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

View File

@ -5,9 +5,6 @@
--- Copyright (C) 2022 - 2023, Michieal. See License.txt --- Copyright (C) 2022 - 2023, Michieal. See License.txt
-- CONSTS -- 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 = "mcl_bamboo:bamboo"
local BAMBOO_ENDCAP_NAME = "mcl_bamboo:bamboo_endcap" local BAMBOO_ENDCAP_NAME = "mcl_bamboo:bamboo_endcap"
local BAMBOO_PLANK = BAMBOO .. "_plank" local BAMBOO_PLANK = BAMBOO .. "_plank"
@ -16,7 +13,7 @@ local BAMBOO_PLANK = BAMBOO .. "_plank"
local modname = minetest.get_current_modname() local modname = minetest.get_current_modname()
local S = minetest.get_translator(modname) local S = minetest.get_translator(modname)
local node_sound = mcl_sounds.node_sound_wood_defaults() 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 local on_rotate
if minetest.get_modpath("screwdriver") then if minetest.get_modpath("screwdriver") then
@ -31,33 +28,7 @@ local bamboo_def = {
paramtype = "light", paramtype = "light",
groups = {handy = 1, axey = 1, choppy = 1, dig_by_piston = 1, plant = 1, non_mycelium_plant = 1, flammable = 3}, groups = {handy = 1, axey = 1, choppy = 1, dig_by_piston = 1, plant = 1, non_mycelium_plant = 1, flammable = 3},
sounds = node_sound, sounds = node_sound,
drop = BAMBOO,
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},
},
},
},
inventory_image = "mcl_bamboo_bamboo_shoot.png", inventory_image = "mcl_bamboo_bamboo_shoot.png",
wield_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_rotate = on_rotate,
on_place = function(itemstack, placer, pointed_thing) on_place = function(itemstack, placer, pointed_thing)
if not pointed_thing then if not pointed_thing then
return itemstack return itemstack
end 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 if node_above and ((bamboo_node and bamboo_node > 0) or node_above.name == BAMBOO_ENDCAP_NAME) then
minetest.remove_node(new_pos) minetest.remove_node(new_pos)
minetest.sound_play(node_sound.dug, sound_params, true) 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) minetest.add_item(new_pos, istack)
end end
end, end,

View File

@ -9,8 +9,6 @@ local SIDE_SCAFFOLDING = false
local SIDE_SCAFFOLD_NAME = "mcl_bamboo:scaffolding_horizontal" local SIDE_SCAFFOLD_NAME = "mcl_bamboo:scaffolding_horizontal"
-- --------------------------------------------------------------------------- -- ---------------------------------------------------------------------------
local SCAFFOLDING_NAME = "mcl_bamboo:scaffolding" 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 = "mcl_bamboo:bamboo"
local BAMBOO_PLANK = BAMBOO .. "_plank" local BAMBOO_PLANK = BAMBOO .. "_plank"

View File

@ -7,8 +7,6 @@
-- LOCALS -- LOCALS
local modname = minetest.get_current_modname() 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" local BAMBOO = "mcl_bamboo:bamboo"
mcl_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. --- 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 --- 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 = "mcl_bamboo:bamboo"
local BAMBOO_PLANK = BAMBOO .. "_plank" local BAMBOO_PLANK = BAMBOO .. "_plank"
-- Craftings -- 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