Compare commits
44 Commits
658ac47d71
...
4c3b08c68d
Author | SHA1 | Date |
---|---|---|
kno10 | 4c3b08c68d | |
kno10 | 98600c87de | |
kno10 | a5cfaed843 | |
kno10 | 8c38745f6c | |
kno10 | 19c1863100 | |
kno10 | 250fc73e92 | |
kno10 | 9ea8945ff4 | |
kno10 | 488f61ba1b | |
kno10 | 16e17ba1ed | |
kno10 | 8ae66b5fad | |
kno10 | 8842e4224c | |
kno10 | a94b3a8d4a | |
kno10 | 8fce90111b | |
kno10 | 645f884e7f | |
kno10 | 46f7c29451 | |
kno10 | ddd180f5be | |
kno10 | b473d55644 | |
kno10 | 265dce301b | |
kno10 | 3538efbf52 | |
kno10 | ab673cdf8b | |
kno10 | 455353de27 | |
kno10 | b99e1a4b08 | |
kno10 | 0730cce236 | |
kno10 | 98f899fd67 | |
kno10 | 4b9e8aabb2 | |
kno10 | 936e47e5f9 | |
kno10 | 46b5416359 | |
kno10 | 72f825ade3 | |
kno10 | ec7e7c14bc | |
kno10 | f8a6da10cb | |
kno10 | 2e5b0bb3c7 | |
Mikita Wiśniewski | 41b188caea | |
kno10 | ae7995d195 | |
kno10 | e293cbe631 | |
the-real-herowl | fd6cac5f0c | |
teknomunk | e864cc19ed | |
teknomunk | 66c3c014a1 | |
teknomunk | 7807093b50 | |
teknomunk | f6c3f4bd16 | |
teknomunk | 96a03b1923 | |
teknomunk | 2145470f63 | |
teknomunk | 2ca0ccd8fe | |
teknomunk | 614518c6cd | |
kno10 | 253a06fa08 |
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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)
|
|
@ -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.
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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"})
|
||||
|
|
|
@ -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"})
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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
|
||||
|
|
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 |
Loading…
Reference in New Issue