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
43 changed files with 1772 additions and 2772 deletions

View File

@ -1,29 +1,23 @@
local mob_class = mcl_mobs.mob_class local mob_class = mcl_mobs.mob_class
local mob_class_meta = {__index = mcl_mobs.mob_class}
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
-- API for Mobs Redo: VoxeLibre Edition
local PATHFINDING = "gowp" local PATHFINDING = "gowp"
local CRASH_WARN_FREQUENCY = 60 local CRASH_WARN_FREQUENCY = 60
local LIFETIMER_DISTANCE = 47 local LIFETIMER_DISTANCE = 47
local MAPGEN_LIMIT = mcl_vars.mapgen_limit
local MAPGEN_MOB_LIMIT = MAPGEN_LIMIT - 90
-- 30927 seems to be the edge of the world, so could be closer, but this is safer
-- Localize
local S = minetest.get_translator("mcl_mobs") local S = minetest.get_translator("mcl_mobs")
local DEVELOPMENT = minetest.settings:get_bool("mcl_development",false)
-- Invisibility mod check -- Invisibility mod check
mcl_mobs.invis = {} mcl_mobs.invis = {}
local remove_far = true local remove_far = true
local mobs_debug = minetest.settings:get_bool("mobs_debug", false) -- Shows helpful debug info above each mob local mobs_debug = minetest.settings:get_bool("mobs_debug", false) -- Shows helpful debug info above each mob
local spawn_logging = minetest.settings:get_bool("mcl_logging_mobs_spawn", false) local spawn_logging = minetest.settings:get_bool("mcl_logging_mobs_spawn", true)
local DEVELOPMENT = minetest.settings:get_bool("mcl_development", false)
local MAPGEN_LIMIT = mcl_vars.mapgen_limit
local MAPGEN_MOB_LIMIT = MAPGEN_LIMIT - 90
-- 30927 seems to be the edge of the world, so could be closer, but this is safer
-- Peaceful mode message so players will know there are no monsters -- Peaceful mode message so players will know there are no monsters
if minetest.settings:get_bool("only_peaceful_mobs", false) then if minetest.settings:get_bool("only_peaceful_mobs", false) then
@ -33,13 +27,21 @@ if minetest.settings:get_bool("only_peaceful_mobs", false) then
end) end)
end end
-- not used yet
function mob_class:safe_remove()
self._removed = true
minetest.after(0,function(obj)
if obj and obj:get_pos() then
mcl_burning.extinguish(obj)
obj:remove()
end
end,self.object)
end
function mob_class:update_tag() --update nametag and/or the debug box function mob_class:update_tag() --update nametag and/or the debug box
local tag local tag
if mobs_debug then if mobs_debug then
local name = self.name local name = self.nametag ~= "" and self.nametag or self.name
if self.nametag and self.nametag ~= "" then
name = self.nametag
end
tag = "name = '"..tostring(name).."'\n".. tag = "name = '"..tostring(name).."'\n"..
"state = '"..tostring(self.state).."'\n".. "state = '"..tostring(self.state).."'\n"..
"order = '"..tostring(self.order).."'\n".. "order = '"..tostring(self.order).."'\n"..
@ -56,9 +58,7 @@ function mob_class:update_tag() --update nametag and/or the debug box
else else
tag = self.nametag tag = self.nametag
end end
self.object:set_properties({ self.object:set_properties({ nametag = tag })
nametag = tag,
})
end end
function mob_class:jock_to(mob, reletive_pos, rot) function mob_class:jock_to(mob, reletive_pos, rot)
@ -74,19 +74,15 @@ function mob_class:jock_to(mob, reletive_pos, rot)
end end
function mob_class:get_staticdata() function mob_class:get_staticdata()
for _,p in pairs(minetest.get_connected_players()) do for _,p in pairs(minetest.get_connected_players()) do
self:remove_particlespawners(p:get_player_name()) self:remove_particlespawners(p:get_player_name())
end end
-- remove mob when out of range unless tamed -- remove mob when out of range unless tamed
if remove_far if remove_far and self:despawn_allowed() and self.lifetimer <= 20 then
and self:despawn_allowed()
and self.lifetimer <= 20 then
if spawn_logging then if spawn_logging then
minetest.log("action", "[mcl_mobs] Mob "..tostring(self.name).." despawns at "..minetest.pos_to_string(vector.round(self.object:get_pos())) .. " - out of range") minetest.log("action", "[mcl_mobs] Mob "..tostring(self.name).." despawns at "..minetest.pos_to_string(vector.round(self.object:get_pos())) .. " - out of range")
end end
return "remove"-- nil return "remove"-- nil
end end
@ -95,17 +91,9 @@ function mob_class:get_staticdata()
self.state = "stand" self.state = "stand"
local tmp = {} local tmp = {}
for tag, stat in pairs(self) do for tag, stat in pairs(self) do
local t = type(stat) local t = type(stat)
if t ~= "function" and t ~= "nil" and t ~= "userdata" and tag ~= "_cmi_components" then tmp[tag] = self[tag] end
if t ~= "function"
and t ~= "nil"
and t ~= "userdata"
and tag ~= "_cmi_components" then
tmp[tag] = self[tag]
end
end end
tmp._mcl_potions = self._mcl_potions tmp._mcl_potions = self._mcl_potions
@ -120,10 +108,7 @@ function mob_class:get_staticdata()
end end
local function valid_texture(self, def_textures) local function valid_texture(self, def_textures)
if not self.base_texture then if not self.base_texture then return false end
return false
end
if self.texture_selected then if self.texture_selected then
if #def_textures < self.texture_selected then if #def_textures < self.texture_selected then
self.texture_selected = nil self.texture_selected = nil
@ -136,44 +121,27 @@ end
function mob_class:mob_activate(staticdata, def, dtime) function mob_class:mob_activate(staticdata, def, dtime)
if not self.object:get_pos() or staticdata == "remove" then if not self.object:get_pos() or staticdata == "remove" then
mcl_burning.extinguish(self.object) self:safe_remove()
self.object:remove()
return return
end end
if self.type == "monster" if self.type == "monster" and minetest.settings:get_bool("only_peaceful_mobs", false) then
and minetest.settings:get_bool("only_peaceful_mobs", false) then self:safe_remove()
mcl_burning.extinguish(self.object)
self.object:remove()
return return
end end
local tmp = minetest.deserialize(staticdata) local tmp = minetest.deserialize(staticdata)
if tmp then if tmp then
-- Patch incorrectly converted mobs -- Patch incorrectly converted mobs
if tmp.base_mesh ~= minetest.registered_entities[self.name].mesh then if tmp.base_mesh ~= minetest.registered_entities[self.name].mesh then mcl_mobs.strip_staticdata(tmp) end
mcl_mobs.strip_staticdata(tmp) for _, stat in pairs(tmp) do self[_] = stat end
end
for _,stat in pairs(tmp) do
self[_] = stat
end
end end
--If textures in definition change, reload textures --If textures in definition change, reload textures
if not valid_texture(self, def.textures) then if not valid_texture(self, def.textures) then
-- compatiblity with old simple mobs textures -- compatiblity with old simple mobs textures
if type(def.textures[1]) == "string" then if type(def.textures[1]) == "string" then def.textures = {def.textures} end
def.textures = {def.textures}
end
if not self.texture_selected then
local c = 1
if #def.textures > c then c = #def.textures end
self.texture_selected = math.random(c)
end
self.texture_selected = self.texture_selected or math.random(#def.textures)
self.base_texture = def.textures[self.texture_selected] self.base_texture = def.textures[self.texture_selected]
self.base_mesh = def.mesh self.base_mesh = def.mesh
self.base_size = self.visual_size self.base_size = self.visual_size
@ -181,38 +149,19 @@ function mob_class:mob_activate(staticdata, def, dtime)
self.base_selbox = self.selectionbox self.base_selbox = self.selectionbox
end end
if not self.base_selbox then self.base_selbox = self.base_selbox or self.selectionbox or self.base_colbox
self.base_selbox = self.selectionbox or self.base_colbox
end
local textures = self.base_texture self.textures = self.gotten and def.gotten_texture or self.base_texture
local mesh = self.base_mesh self.mesh = self.gotten and def.gotten_mesh or self.base_mesh
local vis_size = self.base_size self.visual_size = self.base_size
local colbox = self.base_colbox self.collisionbox = self.base_colbox
local selbox = self.base_selbox self.selectionbox = self.base_selbox
if self.gotten == true if self.child then
and def.gotten_texture then self.visual_size = { x = self.base_size.x * .5, y = self.base_size.y * .5 }
textures = def.gotten_texture self.textures = def.child_texture and def.child_texture[1] or self.textures
end
if self.gotten == true self.collisionbox = {
and def.gotten_mesh then
mesh = def.gotten_mesh
end
if self.child == true then
vis_size = {
x = self.base_size.x * .5,
y = self.base_size.y * .5,
}
if def.child_texture then
textures = def.child_texture[1]
end
colbox = {
self.base_colbox[1] * .5, self.base_colbox[1] * .5,
self.base_colbox[2] * .5, self.base_colbox[2] * .5,
self.base_colbox[3] * .5, self.base_colbox[3] * .5,
@ -220,7 +169,7 @@ function mob_class:mob_activate(staticdata, def, dtime)
self.base_colbox[5] * .5, self.base_colbox[5] * .5,
self.base_colbox[6] * .5 self.base_colbox[6] * .5
} }
selbox = { self.selectionbox = {
self.base_selbox[1] * .5, self.base_selbox[1] * .5,
self.base_selbox[2] * .5, self.base_selbox[2] * .5,
self.base_selbox[3] * .5, self.base_selbox[3] * .5,
@ -230,16 +179,12 @@ function mob_class:mob_activate(staticdata, def, dtime)
} }
end end
if self.health == 0 then self.health = (self.health and self.health > 0 and self.health) or math.random(self.hp_min, self.hp_max)
self.health = math.random (self.hp_min, self.hp_max) self.breath = self.breath or self.breath_max
end
if self.breath == nil then
self.breath = self.breath_max
end
self.path = {} self.path = {}
self.path.way = {} -- path to follow, table of positions self.path.way = {} -- path to follow, table of positions
self.path.lastpos = {x = 0, y = 0, z = 0} self.path.lastpos = vector.zero()
self.path.stuck = false self.path.stuck = false
self.path.following = false -- currently following path? self.path.following = false -- currently following path?
self.path.stuck_timer = 0 -- if stuck for too long search for path self.path.stuck_timer = 0 -- if stuck for too long search for path
@ -258,13 +203,11 @@ function mob_class:mob_activate(staticdata, def, dtime)
self.old_y = self.object:get_pos().y self.old_y = self.object:get_pos().y
self.old_health = self.health self.old_health = self.health
self.sounds.distance = self.sounds.distance or 10 self.sounds.distance = self.sounds.distance or 10
self.textures = textures self.standing_in = mcl_mobs.NODE_IGNORE
self.mesh = mesh self.standing_on = mcl_mobs.NODE_IGNORE
self.collisionbox = colbox self.standing_under = mcl_mobs.NODE_IGNORE
self.selectionbox = selbox self.standing_depth = 0
self.visual_size = vis_size self.state = self.state or "stand"
self.standing_in = "ignore"
self.standing_on = "ignore"
self.jump_sound_cooloff = 0 -- used to prevent jump sound from being played too often in short time self.jump_sound_cooloff = 0 -- used to prevent jump sound from being played too often in short time
self.opinion_sound_cooloff = 0 -- used to prevent sound spam of particular sound types self.opinion_sound_cooloff = 0 -- used to prevent sound spam of particular sound types
@ -276,42 +219,24 @@ function mob_class:mob_activate(staticdata, def, dtime)
self.blinktimer = 0 self.blinktimer = 0
self.blinkstatus = false self.blinkstatus = false
if not self.nametag then self.acceleration = vector.zero()
self.nametag = def.nametag
end self.nametag = self.nametag or def.nametag
if not self.custom_visual_size then
self.visual_size = nil
self.base_size = self.visual_size
if self.child then
self.visual_size = {
x = self.visual_size.x * 0.5,
y = self.visual_size.y * 0.5,
}
end
end
self.object:set_properties(self) self.object:set_properties(self)
self:set_yaw( (math.random(0, 360) - 180) / 180 * math.pi, 6) self:set_yaw(math.random() * math.pi * 2, 6)
self:update_tag() self:update_tag()
self._current_animation = nil self._current_animation = nil
self:set_animation( "stand") self:set_animation("stand")
if self.riden_by_jock then --- Keep this function before self:on_spawn()
if self.riden_by_jock then --- Keep this function before self.on_spawn() is run.
self.object:remove() self.object:remove()
return return
end end
if self.on_spawn and not self.on_spawn_run and self:on_spawn() then self.on_spawn_run = true end
if self.on_spawn and not self.on_spawn_run then if not self.wears_armor and self.armor_list then self.armor_list = nil end
if self.on_spawn(self) then
self.on_spawn_run = true
end
end
if not self.wears_armor and self.armor_list then
self.armor_list = nil
end
if not self._run_armor_init and self.wears_armor then if not self._run_armor_init and self.wears_armor then
self.armor_list={helmet="",chestplate="",boots="",leggings=""} self.armor_list={helmet="",chestplate="",boots="",leggings=""}
@ -319,15 +244,10 @@ function mob_class:mob_activate(staticdata, def, dtime)
self._run_armor_init = true self._run_armor_init = true
end end
if not self._mcl_potions then if not self._mcl_potions then self._mcl_potions = {} end
self._mcl_potions = {}
end
mcl_potions._load_entity_effects(self) mcl_potions._load_entity_effects(self)
if def.after_activate then def.after_activate(self, staticdata, def, dtime) end
if def.after_activate then
def.after_activate(self, staticdata, def, dtime)
end
end end
-- execute current state (stand, walk, run, attacks) -- execute current state (stand, walk, run, attacks)
@ -346,9 +266,7 @@ function mob_class:do_states(dtime, player_in_active_range)
if self.state == PATHFINDING then if self.state == PATHFINDING then
self:check_gowp(dtime) self:check_gowp(dtime)
elseif self.state == "attack" then elseif self.state == "attack" then
if self:do_states_attack(dtime) then if self:do_states_attack(dtime) then return true end
return true
end
else else
if mcl_util.check_dtime_timer(self, dtime, "onstep_dostates", 1) then if mcl_util.check_dtime_timer(self, dtime, "onstep_dostates", 1) then
if self.state == "stand" then if self.state == "stand" then
@ -364,68 +282,46 @@ end
function mob_class:outside_limits() function mob_class:outside_limits()
local pos = self.object:get_pos() local pos = self.object:get_pos()
if pos then if not pos then return end
local posx = math.abs(pos.x) local posx, posy, posz = math.abs(pos.x), math.abs(pos.y), math.abs(pos.z)
local posy = math.abs(pos.y) if posx > MAPGEN_MOB_LIMIT or posy > MAPGEN_MOB_LIMIT or posz > MAPGEN_MOB_LIMIT then
local posz = math.abs(pos.z) --minetest.log("action", "Getting close to limits of worldgen: " .. minetest.pos_to_string(pos))
if posx > MAPGEN_MOB_LIMIT or posy > MAPGEN_MOB_LIMIT or posz > MAPGEN_MOB_LIMIT then if posx > MAPGEN_LIMIT or posy > MAPGEN_LIMIT or posz > MAPGEN_LIMIT then
--minetest.log("action", "Getting close to limits of worldgen: " .. minetest.pos_to_string(pos)) minetest.log("action", "Warning mob past limits of worldgen: " .. minetest.pos_to_string(pos))
if posx > MAPGEN_LIMIT or posy > MAPGEN_LIMIT or posz > MAPGEN_LIMIT then else
minetest.log("action", "Warning mob past limits of worldgen: " .. minetest.pos_to_string(pos)) self:turn_in_direction(-posx, -posz, 1) -- turn to world spawn
else self.state = "walk"
if self.state ~= "stand" then self:set_animation("walk")
minetest.log("action", "Warning mob close to limits of worldgen: " .. minetest.pos_to_string(pos)) self:set_velocity(self.walk_velocity)
self.state = "stand"
self:set_animation("stand")
self.object:set_acceleration(vector.zero())
self.object:set_velocity(vector.zero())
end
end
return true
end end
return true
end end
end end
local function on_step_work(self, dtime, moveresult) local function on_step_work(self, dtime, moveresult)
local pos = self.object:get_pos() local pos = self.object:get_pos()
if not pos then return end if not pos or self._removed then return end
if self:check_despawn(pos, dtime) then return true end
if self:outside_limits() then return end if self:outside_limits() then return end
if self:check_despawn(pos, dtime) then return end
-- Start: Death/damage processing pos = self:limit_vel_acc_for_large_dtime(pos, dtime, moveresult) -- limit maximum movement to reduce lag effects
-- All damage needs to be undertaken at the start. We need to exit processing if the mob dies. self:update_standing(pos, moveresult) -- update what we know of the mobs environment for physics and movement
if self:check_death_and_slow_mob() then local player_in_active_range = self:player_in_active_range()
--minetest.log("action", "Mob is dying: ".. tostring(self.name)) -- The following functions return true when the mob died and we should stop processing
-- Do we abandon out of here now? if self:check_suspend(player_in_active_range) then return end
end -- initializes self.acceleration:
if self:gravity_and_floating(pos, dtime, moveresult) then return end -- keep early, for gravity!
if self:falling(pos, moveresult) then return end if self:check_dying() then return end
if self:step_damage (dtime, pos) then return end if self:step_damage(dtime, pos) then return end
self:check_water_flow(dtime, pos)
if self.state == "die" then return end if self.state == "die" then return end
-- End: Death/damage processing self._can_jump_cliff = not self._jumping_cliff and self:can_jump_cliff()
local player_in_active_range = self:player_in_active_range()
self:check_suspend(player_in_active_range)
self:check_water_flow()
if not self._jumping_cliff then
self._can_jump_cliff = self:can_jump_cliff()
else
self._can_jump_cliff = false
end
self:flop() self:flop()
self:smooth_rotation(dtime)
self:check_smooth_rotation(dtime)
if player_in_active_range then if player_in_active_range then
self:set_animation_speed() -- set animation speed relative to velocity self:set_animation_speed() -- set animation speed relative to velocity
self:check_head_swivel(dtime) self:check_head_swivel(dtime)
if mcl_util.check_dtime_timer(self, dtime, "onstep_engage", 0.2) then if mcl_util.check_dtime_timer(self, dtime, "onstep_engage", 0.2) then
@ -442,91 +338,76 @@ local function on_step_work(self, dtime, moveresult)
end end
if mcl_util.check_dtime_timer(self, dtime, "onstep_occassional", 1) then if mcl_util.check_dtime_timer(self, dtime, "onstep_occassional", 1) then
if player_in_active_range then if player_in_active_range then
self:check_item_pickup() self:check_item_pickup()
self:set_armor_texture() self:set_armor_texture()
self:step_opinion_sound(dtime) self:step_opinion_sound(dtime)
end end
self:check_breeding() self:check_breeding()
end end
self:check_aggro(dtime) self:check_aggro(dtime)
self:check_particlespawners(dtime) self:check_particlespawners(dtime)
if self.do_custom and self:do_custom(dtime) == false then return end
if self.do_custom and self.do_custom(self, dtime) == false then return end
if self:do_states(dtime, player_in_active_range) then return end if self:do_states(dtime, player_in_active_range) then return end
self:smooth_acceleration(dtime)
local cx, cz = self:collision()
self.object:add_velocity(vector.new(cx, 0, cz))
self:update_vel_acc(dtime) -- applies self.acceleration
if mobs_debug then self:update_tag() end if mobs_debug then self:update_tag() end
if not self.object:get_luaentity() then return false end
if not self.object:get_luaentity() then
return false
end
end end
local last_crash_warn_time = 0 local last_crash_warn_time = 0
local function log_error (stack_trace, info, info2) local function log_error(stack_trace, info, info2)
minetest.log("action", "--- Bug report start (please provide a few lines before this also for context) ---") minetest.log("action", "--- Bug report start (please provide a few lines before this also for context) ---")
minetest.log("action", "Error: " .. stack_trace) minetest.log("action", "Error: " .. stack_trace)
minetest.log("action", "Bug info: " .. info) minetest.log("action", "Bug info: " .. info)
if info2 then if info2 then minetest.log("action", "Bug info additional: " .. info2) end
minetest.log("action", "Bug info additional: " .. info2)
end
minetest.log("action", "--- Bug report end ---") minetest.log("action", "--- Bug report end ---")
end end
local function warn_user_error () local function warn_user_error ()
local current_time = os.time() local current_time = os.time()
local time_since_warning = current_time - last_crash_warn_time local time_since_warning = current_time - last_crash_warn_time
--minetest.log("previous_crash_time: " .. current_time) --minetest.log("previous_crash_time: " .. current_time)
--minetest.log("last_crash_time: " .. last_crash_warn_time) --minetest.log("last_crash_time: " .. last_crash_warn_time)
--minetest.log("time_since_warning: " .. time_since_warning) --minetest.log("time_since_warning: " .. time_since_warning)
if time_since_warning > CRASH_WARN_FREQUENCY then if time_since_warning > CRASH_WARN_FREQUENCY then
last_crash_warn_time = current_time last_crash_warn_time = current_time
minetest.log("A game crashing bug was prevented. Please provide debug.log information to VoxeLibre dev team for investigation. (Search for: --- Bug report start)") minetest.log("A game crashing bug was prevented. Please provide debug.log information to VoxeLibre dev team for investigation. (Search for: --- Bug report start)")
end end
end end
local on_step_error_handler = function () local on_step_error_handler = function()
warn_user_error () warn_user_error()
local info = debug.getinfo(1, "SnlufL") local info = debug.getinfo(1, "SnlufL")
log_error(tostring(debug.traceback()), dump(info)) log_error(tostring(debug.traceback()), dump(info))
end end
-- main mob function -- main mob function
function mob_class:on_step(dtime, moveresult) function mob_class:on_step(dtime, moveresult)
if not DEVELOPMENT then -- allow crash in development mode
-- Removed as bundled Lua (5.1 doesn't support xpcall) if DEVELOPMENT then return on_step_work(self, dtime, moveresult) end
--local status, retVal = xpcall(on_step_work, on_step_error_handler, self, dtime) -- Removed as bundled Lua (5.1 doesn't support xpcall)
local status, retVal = pcall(on_step_work, self, dtime, moveresult) local status, retVal
if status then if xpcall then
return retVal status, retVal = xpcall(on_step_work, on_step_error_handler, self, dtime, moveresult)
else
warn_user_error ()
local pos = self.object:get_pos()
if pos then
local node = minetest.get_node(pos)
if node and node.name == "ignore" then
minetest.log("warning", "Pos is ignored: " .. dump(pos))
end
end
log_error (dump(retVal), dump(pos), dump(self))
end
else else
return on_step_work (self, dtime, moveresult) status, retVal = pcall(on_step_work, self, dtime, moveresult)
end end
if status then return retVal end
warn_user_error()
local pos = self.object:get_pos()
if pos then
local node = minetest.get_node(pos)
if node and node.name == "ignore" then minetest.log("warning", "Pos is ignored: " .. dump(pos)) end
end
log_error(dump(retVal), dump(pos), dump(self))
end end
local timer = 0 local timer = 0
local function update_lifetimer(dtime) local function update_lifetimer(dtime)
timer = timer + dtime timer = timer + dtime
if timer < 1 then return end if timer < 1 then return end
@ -543,10 +424,7 @@ local function update_lifetimer(dtime)
timer = 0 timer = 0
end end
minetest.register_globalstep(function(dtime) minetest.register_globalstep(update_lifetimer)
update_lifetimer(dtime)
end)
minetest.register_chatcommand("clearmobs", { minetest.register_chatcommand("clearmobs", {
privs = { maphack = true }, privs = { maphack = true },
@ -560,11 +438,7 @@ minetest.register_chatcommand("clearmobs", {
S("Default usage. Clearing hostile mobs. For more options please type: /help clearmobs")) S("Default usage. Clearing hostile mobs. For more options please type: /help clearmobs"))
end end
local mob, unsafe = param:match("^([%w]+)[ ]?([%w%d]*)$") local mob, unsafe = param:match("^([%w]+)[ ]?([%w%d]*)$")
local all, nametagged, tamed = false, false, false
local all = false
local nametagged = false
local tamed = false
local mob_name, mob_type, range local mob_name, mob_type, range
-- Param 1 resolve -- Param 1 resolve
@ -578,12 +452,7 @@ minetest.register_chatcommand("clearmobs", {
end end
--minetest.log ("mob: [" .. mob .. "]") --minetest.log ("mob: [" .. mob .. "]")
else else
--minetest.log("No valid first param") if default then mob_type = "monster" end
if default then
--minetest.log("Use default")
mob_type = "monster"
end
--return
end end
-- Param 2 resolve -- Param 2 resolve
@ -600,7 +469,6 @@ minetest.register_chatcommand("clearmobs", {
end end
local p = minetest.get_player_by_name(player) local p = minetest.get_player_by_name(player)
for _,o in pairs(minetest.luaentities) do for _,o in pairs(minetest.luaentities) do
if o and o.is_mob then if o and o.is_mob then
local mob_match = false local mob_match = false
@ -609,7 +477,6 @@ minetest.register_chatcommand("clearmobs", {
--minetest.log("Match - All mobs specified") --minetest.log("Match - All mobs specified")
mob_match = true mob_match = true
elseif mob_type then elseif mob_type then
--minetest.log("Match - o.type: ".. tostring(o.type)) --minetest.log("Match - o.type: ".. tostring(o.type))
--minetest.log("mob_type: ".. tostring(mob_type)) --minetest.log("mob_type: ".. tostring(mob_type))
if mob_type == "monster" and o.type == mob_type then if mob_type == "monster" and o.type == mob_type then
@ -618,49 +485,29 @@ minetest.register_chatcommand("clearmobs", {
elseif mob_type == "passive" and o.type ~= "monster" and o.type ~= "npc" then elseif mob_type == "passive" and o.type ~= "monster" and o.type ~= "npc" then
--minetest.log("Match - passive") --minetest.log("Match - passive")
mob_match = true mob_match = true
else --else
--minetest.log("No match for type.") -- minetest.log("No match for type.")
end end
elseif mob_name and (o.name == mob_name or string.find(o.name, mob_name)) then elseif mob_name and (o.name == mob_name or string.find(o.name, mob_name)) then
--minetest.log("Match - mob_name = ".. tostring(o.name)) --minetest.log("Match - mob_name = ".. tostring(o.name))
mob_match = true mob_match = true
else --else
--minetest.log("No match - o.type = ".. tostring(o.type)) -- minetest.log("No match - o.type = ".. tostring(o.type))
--minetest.log("No match - mob_name = ".. tostring(o.name)) -- minetest.log("No match - mob_name = ".. tostring(o.name))
--minetest.log("No match - mob_type = ".. tostring(mob_name)) -- minetest.log("No match - mob_type = ".. tostring(mob_name))
end end
if mob_match then if mob_match then
local in_range = true local in_range = (not range or range <= 0) or vector.distance(p:get_pos(), o.object:get_pos()) <= range
if (not range or range <= 0 ) then
in_range = true
else
if ( vector.distance(p:get_pos(),o.object:get_pos()) <= range ) then
in_range = true
else
--minetest.log("Out of range")
in_range = false
end
end
--minetest.log("o.nametag: ".. tostring(o.nametag))
if nametagged then if nametagged then
if o.nametag then if o.nametag then o.object:remove() end
--minetest.log("Namedtagged and it has a name tag. Kill it")
o.object:remove()
end
elseif tamed then elseif tamed then
if o.tamed then if o.tamed then o.object:remove() end
--minetest.log("Tamed. Kill it")
o.object:remove()
end
elseif in_range and (not o.nametag or o.nametag == "") and not o.tamed then elseif in_range and (not o.nametag or o.nametag == "") and not o.tamed then
--minetest.log("No nametag or tamed. Kill it")
o.object:remove() o.object:remove()
end end
end end
end end
end end
end}) end
})

View File

@ -58,6 +58,7 @@ functions needed for the mob to work properly which contains the following:
that are not liquids that are not liquids
'runaway' if true causes animals to turn and run away when hit. 'runaway' if true causes animals to turn and run away when hit.
'view_range' how many nodes in distance the mob can see a player. 'view_range' how many nodes in distance the mob can see a player.
'see_through_opaque' override whether the mob can see through opaque nodes
'damage' how many health points the mob does to a player or another 'damage' how many health points the mob does to a player or another
mob when melee attacking. mob when melee attacking.
'knock_back' when true has mobs falling backwards when hit, the greater 'knock_back' when true has mobs falling backwards when hit, the greater

View File

@ -5,7 +5,7 @@ local HORNY_TIME = 30
local HORNY_AGAIN_TIME = 30 -- was 300 or 15*20 local HORNY_AGAIN_TIME = 30 -- was 300 or 15*20
local CHILD_GROW_TIME = 60 local CHILD_GROW_TIME = 60
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_villager",false) local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_villager", false)
local LOG_MODULE = "[mcl_mobs]" local LOG_MODULE = "[mcl_mobs]"
local function mcl_log (message) local function mcl_log (message)
@ -29,18 +29,13 @@ end
-- feeding, taming and breeding (thanks blert2112) -- feeding, taming and breeding (thanks blert2112)
function mob_class:feed_tame(clicker, feed_count, breed, tame, notake) function mob_class:feed_tame(clicker, feed_count, breed, tame, notake)
if not self.follow then if not self.follow then return false end
return false if clicker:get_wielded_item():get_definition()._mcl_not_consumable then return false end
end
if clicker:get_wielded_item():get_definition()._mcl_not_consumable then
return false
end
-- can eat/tame with item in hand -- can eat/tame with item in hand
if self.nofollow or self:follow_holding(clicker) then if self.nofollow or self:follow_holding(clicker) then
local consume_food = false local consume_food = false
-- tame if not still a baby -- tame if not still a baby
if tame and not self.child then if tame and not self.child then
if not self.owner or self.owner == "" then if not self.owner or self.owner == "" then
self.tamed = true self.tamed = true
@ -50,27 +45,20 @@ function mob_class:feed_tame(clicker, feed_count, breed, tame, notake)
end end
-- increase health -- increase health
if self.health < self.hp_max and not consume_food then if self.health < self.hp_max and not consume_food then
consume_food = true consume_food = true
self.health = math.min(self.health + 4, self.hp_max) self.health = math.min(self.health + 4, self.hp_max)
if self.htimer < 1 then
self.htimer = 5
end
self.object:set_hp(self.health) self.object:set_hp(self.health)
end end
-- make children grow quicker -- make children grow quicker
if not consume_food and self.child then
if not consume_food and self.child == true then
consume_food = true consume_food = true
-- deduct 10% of the time to adulthood -- deduct 10% of the time to adulthood
self.hornytimer = self.hornytimer + ((CHILD_GROW_TIME - self.hornytimer) * 0.1) self.hornytimer = self.hornytimer + ((CHILD_GROW_TIME - self.hornytimer) * 0.1)
end end
-- breed animals -- breed animals
if breed and not consume_food and self.hornytimer == 0 and not self.horny then if breed and not consume_food and self.hornytimer == 0 and not self.horny then
self.food = (self.food or 0) + 1 self.food = (self.food or 0) + 1
consume_food = true consume_food = true
@ -105,25 +93,16 @@ 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 if not child then return end
return
end
local ent = child:get_luaentity() local ent = child:get_luaentity()
mcl_mobs.effect(pos, 15, "mcl_particles_smoke.png", 1, 2, 2, 15, 5)
ent.child = true ent.child = true
mcl_mobs.effect(pos, 15, "mcl_particles_smoke.png", 1, 2, 2, 15, 5)
local textures
-- using specific child texture (if found)
if ent.child_texture then
textures = ent.child_texture[1]
end
-- and resize to half height -- and resize to half height
child:set_properties({ child:set_properties({
textures = textures, textures = ent.child_texture and ent.child_texture[1],
visual_size = { visual_size = {
x = ent.base_size.x * .5, x = ent.base_size.x * .5,
y = ent.base_size.y * .5, y = ent.base_size.y * .5,
@ -155,16 +134,12 @@ end
-- find two animals of same type and breed if nearby and horny -- find two animals of same type and breed if nearby and horny
function mob_class:check_breeding() function mob_class:check_breeding()
--mcl_log("In breed function") --mcl_log("In breed function")
-- child takes a long time before growing into adult -- child takes a long time before growing into adult
if self.child == true then if self.child == true then
-- When a child, hornytimer is used to count age until adulthood -- When a child, hornytimer is used to count age until adulthood
self.hornytimer = self.hornytimer + 1 self.hornytimer = self.hornytimer + 1
if self.hornytimer >= CHILD_GROW_TIME then if self.hornytimer >= CHILD_GROW_TIME then
self.child = false self.child = false
self.hornytimer = 0 self.hornytimer = 0
@ -181,11 +156,7 @@ function mob_class:check_breeding()
self.on_grown(self) self.on_grown(self)
else else
-- jump when fully grown so as not to fall into ground -- jump when fully grown so as not to fall into ground
self.object:set_velocity({ self.object:set_velocity(vector.new(0, self.jump_height, 0))
x = 0,
y = self.jump_height,
z = 0
})
end end
self.animation = nil self.animation = nil
@ -193,121 +164,79 @@ function mob_class:check_breeding()
self._current_animation = nil -- Mobs Redo does nothing otherwise self._current_animation = nil -- Mobs Redo does nothing otherwise
self:set_animation(anim) self:set_animation(anim)
end end
return return
else end
-- horny animal can mate for HORNY_TIME seconds, -- horny animal can mate for HORNY_TIME seconds,
-- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds -- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds
if self.horny == true then if self.horny == true then
self.hornytimer = self.hornytimer + 1 self.hornytimer = self.hornytimer + 1
if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then
self.hornytimer = 0 self.hornytimer = 0
self.horny = false self.horny = false
end
end end
end end
-- find another same animal who is also horny and mate if nearby -- find another same animal who is also horny and mate if nearby
if self.horny == true if self.horny and self.hornytimer <= HORNY_TIME then
and self.hornytimer <= HORNY_TIME then
mcl_log("In breed function. All good. Do the magic.") mcl_log("In breed function. All good. Do the magic.")
local pos = self.object:get_pos() local pos = self.object:get_pos()
mcl_mobs.effect(vector.new(pos.x, pos.y + 1, pos.z), 8, "heart.png", 3, 4, 1, 0.1)
mcl_mobs.effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
local objs = minetest.get_objects_inside_radius(pos, 3) local objs = minetest.get_objects_inside_radius(pos, 3)
local num = 0 local num = 0
local ent = nil
for n = 1, #objs do for n = 1, #objs do
local ent = objs[n]:get_luaentity()
ent = objs[n]:get_luaentity()
-- check for same animal with different colour -- check for same animal with different colour
local canmate = false local canmate = false
if ent then if ent then
if ent.name == self.name then if ent.name == self.name then
canmate = true canmate = true
else else
local entname = string.split(ent.name,":") local entname = string.split(ent.name,":")
local selfname = string.split(self.name,":") local selfname = string.split(self.name,":")
if entname[1] == selfname[1] then if entname[1] == selfname[1] then
entname = string.split(entname[2],"_") entname = string.split(entname[2],"_")
selfname = string.split(selfname[2],"_") selfname = string.split(selfname[2],"_")
if entname[1] == selfname[1] then if entname[1] == selfname[1] then canmate = true end
canmate = true
end
end end
end end
end end
if canmate then mcl_log("In breed function. Can mate.") end if canmate then mcl_log("In breed function. Can mate.") end
if ent and canmate and ent.horny and ent.hornytimer <= HORNY_TIME then
if ent
and canmate == true
and ent.horny == true
and ent.hornytimer <= HORNY_TIME then
num = num + 1 num = num + 1
end end
-- found your mate? then have a baby -- found your mate? then have a baby
if num > 1 then if num > 1 then
self.hornytimer = HORNY_TIME + 1 self.hornytimer = HORNY_TIME + 1
ent.hornytimer = HORNY_TIME + 1 ent.hornytimer = HORNY_TIME + 1
-- spawn baby -- spawn baby
minetest.after(5, function(parent1, parent2, pos) minetest.after(5, function(parent1, parent2, pos)
if not parent1.object:get_luaentity() then if not parent1.object:get_luaentity() then return end
return if not parent2.object:get_luaentity() then return end
end
if not parent2.object:get_luaentity() then
return
end
mcl_experience.throw_xp(pos, math.random(1, 7) + (parent1._luck or 0) + (parent2._luck or 0)) mcl_experience.throw_xp(pos, math.random(1, 7) + (parent1._luck or 0) + (parent2._luck or 0))
-- custom breed function if parent1.on_breed and not parent1.on_breed(parent1, parent2) then return end
if parent1.on_breed then pos = vector.round(pos)
-- when false, skip going any further
if parent1.on_breed(parent1, parent2) == false then
return
end
end
local child = mcl_mobs.spawn_child(pos, parent1.name) local child = mcl_mobs.spawn_child(pos, parent1.name)
if not child then return end if not child then return end
local ent_c = child:get_luaentity() local ent_c = child:get_luaentity()
-- Use texture of one of the parents -- Use texture of one of the parents
local p = math.random(1, 2) ent_c.base_texture = math.random(1, 2) == 1 and parent1.base_texture or parent2.base_texture
if p == 1 then child:set_properties({ textures = ent_c.base_texture })
ent_c.base_texture = parent1.base_texture
else
ent_c.base_texture = parent2.base_texture
end
child:set_properties({
textures = ent_c.base_texture
})
-- tamed and owned by parents' owner -- tamed and owned by parents' owner
ent_c.tamed = true ent_c.tamed = true
ent_c.owner = parent1.owner ent_c.owner = parent1.owner
end, self, ent, pos) end, self, ent, pos)
num = 0
break break
end end
end end
@ -315,9 +244,7 @@ function mob_class:check_breeding()
end end
function mob_class:toggle_sit(clicker,p) function mob_class:toggle_sit(clicker,p)
if not self.tamed or self.child or self.owner ~= clicker:get_player_name() then if not self.tamed or self.child or self.owner ~= clicker:get_player_name() then return end
return
end
local pos = self.object:get_pos() local pos = self.object:get_pos()
local particle local particle
if not self.order or self.order == "" or self.order == "sit" then if not self.order or self.order == "" or self.order == "sit" then
@ -345,7 +272,7 @@ function mob_class:toggle_sit(clicker,p)
-- Display icon to show current order (sit or roam) -- Display icon to show current order (sit or roam)
minetest.add_particle({ minetest.add_particle({
pos = vector.add(pos, pp), pos = vector.add(pos, pp),
velocity = {x=0,y=0.2,z=0}, velocity = vector.new(0, 0.2, 0),
expirationtime = 1, expirationtime = 1,
size = 4, size = 4,
texture = particle, texture = particle,

View File

@ -3,6 +3,7 @@ local mob_class = mcl_mobs.mob_class
local damage_enabled = minetest.settings:get_bool("enable_damage") local damage_enabled = minetest.settings:get_bool("enable_damage")
local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
local mobs_see_through_opaque = mcl_mobs.see_through_opaque
-- pathfinding settings -- pathfinding settings
local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
@ -11,39 +12,33 @@ local stuck_path_timeout = 10 -- how long will mob follow path before giving up
local enable_pathfinding = true local enable_pathfinding = true
local TIME_TO_FORGET_TARGET = 15 local TIME_TO_FORGET_TARGET = 15
local PI = math.pi
local atann = math.atan local HALFPI = PI * 0.5
local function atan(x) local random = math.random
if not x or x ~= x then local min = math.min
return 0 local floor = math.floor
else local ceil = math.ceil
return atann(x) local abs = math.abs
end local cos = math.cos
end local sin = math.sin
local atan2 = math.atan2
local sqrt = math.sqrt
local vector_offset = vector.offset
local vector_new = vector.new
local vector_copy = vector.copy
local vector_distance = vector.distance
local vector_zero = vector.zero
local node_ok = mcl_mobs.node_ok -- TODO: remove
-- check if daytime and also if mob is docile during daylight hours -- check if daytime and also if mob is docile during daylight hours
function mob_class:day_docile() function mob_class:day_docile()
if self.docile_by_day == false then return self.docile_by_day and self.time_of_day > 0.2 and self.time_of_day < 0.8
return false
elseif self.docile_by_day == true
and self.time_of_day > 0.2
and self.time_of_day < 0.8 then
return true
end
end end
-- get this mob to attack the object -- get this mob to attack the object
function mob_class:do_attack(object) function mob_class:do_attack(object)
if self.state == "attack" or self.state == "die" then return end
if self.state == "attack" or self.state == "die" then if object:is_player() and not minetest.settings:get_bool("enable_damage") then return end
return
end
if object:is_player() and not minetest.settings:get_bool("enable_damage") then
return
end
self.attack = object self.attack = object
self.state = "attack" self.state = "attack"
@ -55,22 +50,19 @@ function mob_class:do_attack(object)
end end
-- blast damage to entities nearby -- blast damage to entities nearby
local function entity_physics(pos,radius) local function entity_physics(pos, radius)
radius = radius * 2 radius = radius * 2
local objs = minetest.get_objects_inside_radius(pos, radius) local objs = minetest.get_objects_inside_radius(pos, radius)
local obj_pos, dist local obj_pos, dist
for n = 1, #objs do for n = 1, #objs do
obj_pos = objs[n]:get_pos() obj_pos = objs[n]:get_pos()
dist = vector.distance(pos, obj_pos) dist = vector_distance(pos, obj_pos)
if dist < 1 then dist = 1 end if dist < 1 then dist = 1 end
local damage = math.floor((4 / dist) * radius) local damage = floor((4 / dist) * radius)
local ent = objs[n]:get_luaentity() --local ent = objs[n]:get_luaentity()
-- punches work on entities AND players -- punches work on entities AND players
objs[n]:punch(objs[n], 1.0, { objs[n]:punch(objs[n], 1.0, {
@ -87,75 +79,58 @@ local height_switcher = false
-- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3 -- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3
function mob_class:smart_mobs(s, p, dist, dtime) function mob_class:smart_mobs(s, p, dist, dtime)
local s1 = self.path.lastpos local s1 = self.path.lastpos
local target_pos = self.attack:get_pos() local target_pos = self.attack:get_pos()
-- is it becoming stuck? -- is it becoming stuck?
if math.abs(s1.x - s.x) + math.abs(s1.z - s.z) < .5 then if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then
self.path.stuck_timer = self.path.stuck_timer + dtime self.path.stuck_timer = self.path.stuck_timer + dtime
else else
self.path.stuck_timer = 0 self.path.stuck_timer = 0
end end
self.path.lastpos = {x = s.x, y = s.y, z = s.z} self.path.lastpos = vector_copy(s)
local use_pathfind = false local use_pathfind = false
local has_lineofsight = minetest.line_of_sight( local has_lineofsight = self:line_of_sight(vector_offset(s, 0, .5, 0), vector_offset(target_pos, 0, 1.5, 0),
{x = s.x, y = (s.y) + .5, z = s.z}, self.see_through_opaque or mobs_see_through_opaque, false)
{x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z}, .2)
-- im stuck, search for path -- im stuck, search for path
if not has_lineofsight then if not has_lineofsight then
if los_switcher == true then if los_switcher == true then
use_pathfind = true use_pathfind = true
los_switcher = false los_switcher = false
end -- cannot see target! end -- cannot see target!
else else
if los_switcher == false then if los_switcher == false then
los_switcher = true los_switcher = true
use_pathfind = false use_pathfind = false
minetest.after(1, function(self) minetest.after(1, function(self)
if not self.object:get_luaentity() then if not self.object:get_luaentity() then return end
return
end
if has_lineofsight then self.path.following = false end if has_lineofsight then self.path.following = false end
end, self) end, self)
end -- can see target! end -- can see target!
end end
if (self.path.stuck_timer > stuck_timeout and not self.path.following) then if (self.path.stuck_timer > stuck_timeout and not self.path.following) then
use_pathfind = true use_pathfind = true
self.path.stuck_timer = 0 self.path.stuck_timer = 0
minetest.after(1, function(self) minetest.after(1, function(self)
if not self.object:get_luaentity() then if not self.object:get_luaentity() then return end
return
end
if has_lineofsight then self.path.following = false end if has_lineofsight then self.path.following = false end
end, self) end, self)
end end
if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then
use_pathfind = true use_pathfind = true
self.path.stuck_timer = 0 self.path.stuck_timer = 0
minetest.after(1, function(self) minetest.after(1, function(self)
if not self.object:get_luaentity() then if not self.object:get_luaentity() then return end
return
end
if has_lineofsight then self.path.following = false end if has_lineofsight then self.path.following = false end
end, self) end, self)
end end
if math.abs(vector.subtract(s,target_pos).y) > self.stepheight then if abs(s.y - target_pos.y) > self.stepheight then
if height_switcher then if height_switcher then
use_pathfind = true use_pathfind = true
height_switcher = false height_switcher = false
@ -170,35 +145,23 @@ function mob_class:smart_mobs(s, p, dist, dtime)
if use_pathfind then if use_pathfind then
-- lets try find a path, first take care of positions -- lets try find a path, first take care of positions
-- since pathfinder is very sensitive -- since pathfinder is very sensitive
local sheight = self.collisionbox[5] - self.collisionbox[2] --local sheight = self.collisionbox[5] - self.collisionbox[2]
-- round position to center of node to avoid stuck in walls -- round position to center of node to avoid stuck in walls
-- also adjust height for player models! -- also adjust height for player models!
s.x = math.floor(s.x + 0.5) s.x, s.z = floor(s.x + 0.5), floor(s.z + 0.5)
s.z = math.floor(s.z + 0.5)
local ssight, sground = minetest.line_of_sight(s, { local ssight, sground = minetest.line_of_sight(s, vector_offset(s, 0, -4, 0), 1)
x = s.x, y = s.y - 4, z = s.z}, 1)
-- determine node above ground -- determine node above ground
if not ssight then if not ssight then s.y = sground.y + 1 end
s.y = sground.y + 1
end
local p1 = self.attack:get_pos() local p1 = self.attack:get_pos()
p1 = vector_new(floor(p1.x + 0.5), floor(p1.y + 0.5), floor(p1.z + 0.5))
p1.x = math.floor(p1.x + 0.5)
p1.y = math.floor(p1.y + 0.5)
p1.z = math.floor(p1.z + 0.5)
local dropheight = 12 local dropheight = 12
if self.fear_height ~= 0 then dropheight = self.fear_height end if self.fear_height ~= 0 then dropheight = self.fear_height end
local jumpheight = 0 local jumpheight = self.jump and floor(self.jump_height + 0.1) or 0
if self.jump and self.jump_height >= 4 then
jumpheight = math.min(math.ceil(self.jump_height / 4), 4)
elseif self.stepheight > 0.5 then
jumpheight = 1
end
self.path.way = minetest.find_path(s, p1, 16, jumpheight, dropheight, "A*_noprefetch") self.path.way = minetest.find_path(s, p1, 16, jumpheight, dropheight, "A*_noprefetch")
self.state = "" self.state = ""
@ -206,34 +169,26 @@ function mob_class:smart_mobs(s, p, dist, dtime)
-- no path found, try something else -- no path found, try something else
if not self.path.way then if not self.path.way then
self.path.following = false self.path.following = false
-- lets make way by digging/building if not accessible -- lets make way by digging/building if not accessible
if self.pathfinding == 2 and mobs_griefing then if self.pathfinding == 2 and mobs_griefing then
-- is player higher than mob? -- is player higher than mob?
if s.y < p1.y then if s.y < p1.y then
-- build upwards -- build upwards
if not minetest.is_protected(s, "") then if not minetest.is_protected(s, "") then
if self.standing_in.buildable_to or self.standing_in.groups.liquid then
local ndef1 = minetest.registered_nodes[self.standing_in] minetest.set_node(s, {name = mcl_mobs.fallback_node})
if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
minetest.set_node(s, {name = mcl_mobs.fallback_node})
end end
end end
local sheight = math.ceil(self.collisionbox[5]) + 1 local sheight = ceil(self.collisionbox[5]) + 1
-- assume mob is 2 blocks high so it digs above its head -- assume mob is 2 blocks high so it digs above its head
s.y = s.y + sheight s.y = s.y + sheight
-- remove one block above to make room to jump -- remove one block above to make room to jump
if not minetest.is_protected(s, "") then if not minetest.is_protected(s, "") then
local node1 = node_ok(s, "air").name local node1 = node_ok(s, "air").name
local ndef1 = minetest.registered_nodes[node1] local ndef1 = minetest.registered_nodes[node1]
@ -243,32 +198,21 @@ function mob_class:smart_mobs(s, p, dist, dtime)
and not ndef1.groups.level and not ndef1.groups.level
and not ndef1.groups.unbreakable and not ndef1.groups.unbreakable
and not ndef1.groups.liquid then and not ndef1.groups.liquid then
minetest.set_node(s, {name = "air"}) minetest.set_node(s, {name = "air"})
minetest.add_item(s, ItemStack(node1)) minetest.add_item(s, ItemStack(node1))
end end
end end
s.y = s.y - sheight s.y = s.y - sheight
self.object:set_pos({x = s.x, y = s.y + 2, z = s.z}) self.object:set_pos(vector_offset(s, 0, 2, 0))
else -- dig 2 blocks to make door toward player direction else -- dig 2 blocks to make door toward player direction
local yaw1 = self.object:get_yaw() + HALFPI
local yaw1 = self.object:get_yaw() + math.pi / 2 local p1 = vector_offset(s, cos(yaw1), 0, sin(yaw1))
local p1 = {
x = s.x + math.cos(yaw1),
y = s.y,
z = s.z + math.sin(yaw1)
}
if not minetest.is_protected(p1, "") then if not minetest.is_protected(p1, "") then
local node1 = node_ok(p1, "air").name local node1 = node_ok(p1, "air").name
local ndef1 = minetest.registered_nodes[node1] local ndef1 = minetest.registered_nodes[node1]
if node1 ~= "air" and node1 ~= "ignore"
if node1 ~= "air"
and node1 ~= "ignore"
and ndef1 and ndef1
and not ndef1.groups.level and not ndef1.groups.level
and not ndef1.groups.unbreakable and not ndef1.groups.unbreakable
@ -282,8 +226,7 @@ function mob_class:smart_mobs(s, p, dist, dtime)
node1 = node_ok(p1, "air").name node1 = node_ok(p1, "air").name
ndef1 = minetest.registered_nodes[node1] ndef1 = minetest.registered_nodes[node1]
if node1 ~= "air" if node1 ~= "air" and node1 ~= "ignore"
and node1 ~= "ignore"
and ndef1 and ndef1
and not ndef1.groups.level and not ndef1.groups.level
and not ndef1.groups.unbreakable and not ndef1.groups.unbreakable
@ -317,28 +260,19 @@ end
-- specific attacks -- specific attacks
local specific_attack = function(list, what) local specific_attack = function(list, what)
-- no list so attack default (player, animals etc.) -- no list so attack default (player, animals etc.)
if list == nil then if list == nil then return true end
return true
end
-- found entity on list to attack? -- found entity on list to attack?
for no = 1, #list do for no = 1, #list do
if list[no] == what then return true end
if list[no] == what then
return true
end
end end
return false return false
end end
-- find someone to attack -- find someone to attack
function mob_class:monster_attack() function mob_class:monster_attack()
if not damage_enabled or self.passive ~= false or self.state == "attack" or self:day_docile() then if not damage_enabled or self.passive ~= false or self.state == "attack" or self:day_docile() then return end
return
end
local s = self.object:get_pos() local s = self.object:get_pos()
local p, sp, dist local p, sp, dist
@ -392,7 +326,7 @@ function mob_class:monster_attack()
p = player:get_pos() p = player:get_pos()
sp = s sp = s
dist = vector.distance(p, s) dist = vector_distance(p, s)
-- aim higher to make looking up hills more realistic -- aim higher to make looking up hills more realistic
p.y = p.y + 1 p.y = p.y + 1
@ -414,7 +348,7 @@ function mob_class:monster_attack()
end end
end end
if not min_player and #blacklist_attack > 0 then if not min_player and #blacklist_attack > 0 then
min_player=blacklist_attack[math.random(#blacklist_attack)] min_player=blacklist_attack[random(#blacklist_attack)]
end end
-- attack player -- attack player
if min_player then if min_player then
@ -425,7 +359,6 @@ end
-- npc, find closest monster to attack -- npc, find closest monster to attack
function mob_class:npc_attack() function mob_class:npc_attack()
if self.type ~= "npc" if self.type ~= "npc"
or not self.attacks_monsters or not self.attacks_monsters
or self.state == "attack" then or self.state == "attack" then
@ -444,13 +377,13 @@ function mob_class:npc_attack()
p = obj.object:get_pos() p = obj.object:get_pos()
sp = s sp = s
local dist = vector.distance(p, s) local dist = vector_distance(p, s)
-- aim higher to make looking up hills more realistic -- aim higher to make looking up hills more realistic
p.y = p.y + 1 p.y = p.y + 1
sp.y = sp.y + 1 sp.y = sp.y + 1
if dist < min_dist and self:line_of_sight( sp, p, 2) == true then if dist < min_dist and self:line_of_sight(sp, p, 2) == true then
min_dist = dist min_dist = dist
min_player = obj.object min_player = obj.object
end end
@ -466,19 +399,13 @@ end
-- dogshoot attack switch and counter function -- dogshoot attack switch and counter function
function mob_class:dogswitch(dtime) function mob_class:dogswitch(dtime)
-- switch mode not activated -- switch mode not activated
if not self.dogshoot_switch if not self.dogshoot_switch or not dtime then return 0 end
or not dtime then
return 0
end
self.dogshoot_count = self.dogshoot_count + dtime self.dogshoot_count = self.dogshoot_count + dtime
if (self.dogshoot_switch == 1 if (self.dogshoot_switch == 1 and self.dogshoot_count > self.dogshoot_count_max)
and self.dogshoot_count > self.dogshoot_count_max) or (self.dogshoot_switch == 2 and self.dogshoot_count > self.dogshoot_count2_max) then
or (self.dogshoot_switch == 2
and self.dogshoot_count > self.dogshoot_count2_max) then
self.dogshoot_count = 0 self.dogshoot_count = 0
@ -525,13 +452,9 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
if is_player then if is_player then
-- is mob out of reach? -- is mob out of reach?
if vector.distance(mob_pos, player_pos) > 3 then if vector_distance(mob_pos, player_pos) > 3 then return end
return
end
-- is mob protected? -- is mob protected?
if self.protected and minetest.is_protected(mob_pos, hitter:get_player_name()) then if self.protected and minetest.is_protected(mob_pos, hitter:get_player_name()) then return end
return
end
mcl_potions.update_haste_and_fatigue(hitter) mcl_potions.update_haste_and_fatigue(hitter)
end end
@ -540,13 +463,10 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
local time_diff = time_now - self.invul_timestamp local time_diff = time_now - self.invul_timestamp
-- check for invulnerability time in microseconds (0.5 second) -- check for invulnerability time in microseconds (0.5 second)
if time_diff <= 500000 and time_diff >= 0 then if time_diff <= 500000 and time_diff >= 0 then return end
return
end
-- custom punch function -- custom punch function
if self.do_punch then if self.do_punch then
-- when false skip going any further -- when false skip going any further
if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
return return
@ -562,15 +482,11 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
local time_now = minetest.get_us_time() local time_now = minetest.get_us_time()
if is_player then if is_player then
if minetest.is_creative_enabled(hitter:get_player_name()) then if minetest.is_creative_enabled(hitter:get_player_name()) then self.health = 0 end
self.health = 0
end
-- set/update 'drop xp' timestamp if hitted by player -- set/update 'drop xp' timestamp if hitted by player
self.xp_timestamp = time_now self.xp_timestamp = time_now
end end
-- punch interval -- punch interval
local weapon = hitter:get_wielded_item() local weapon = hitter:get_wielded_item()
local punch_interval = 1.4 local punch_interval = 1.4
@ -591,18 +507,10 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
end end
for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do for group,_ in pairs((tool_capabilities.damage_groups or {}) ) do
tmp = tflp / (tool_capabilities.full_punch_interval or 1.4) tmp = tflp / (tool_capabilities.full_punch_interval or 1.4)
tmp = tmp < 0 and 0 or (tmp > 1 and 1 or tmp)
if tmp < 0 then damage = damage + (tool_capabilities.damage_groups[group] or 0) * tmp * ((armor[group] or 0) / 100.0)
tmp = 0.0
elseif tmp > 1 then
tmp = 1.0
end
damage = damage + (tool_capabilities.damage_groups[group] or 0)
* tmp * ((armor[group] or 0) / 100.0)
end end
-- strength and weakness effects -- strength and weakness effects
@ -621,9 +529,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
-- check for tool immunity or special damage -- check for tool immunity or special damage
for n = 1, #self.immune_to do for n = 1, #self.immune_to do
if self.immune_to[n][1] == weapon:get_name() then if self.immune_to[n][1] == weapon:get_name() then
damage = self.immune_to[n][2] or 0 damage = self.immune_to[n][2] or 0
break break
end end
@ -631,7 +537,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
-- healing -- healing
if damage <= -1 then if damage <= -1 then
self.health = self.health - math.floor(damage) self.health = self.health - floor(damage)
return return
end end
@ -651,7 +557,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
local weapon = hitter:get_wielded_item(player) local weapon = hitter:get_wielded_item(player)
local def = weapon:get_definition() local def = weapon:get_definition()
if def.tool_capabilities and def.tool_capabilities.punch_attack_uses then if def.tool_capabilities and def.tool_capabilities.punch_attack_uses then
local wear = math.floor(65535/tool_capabilities.punch_attack_uses) local wear = floor(65535/tool_capabilities.punch_attack_uses)
weapon:add_wear(wear) weapon:add_wear(wear)
tt.reload_itemstack_description(weapon) -- update tooltip tt.reload_itemstack_description(weapon) -- update tooltip
hitter:set_wielded_item(weapon) hitter:set_wielded_item(weapon)
@ -662,14 +568,12 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
local die = false local die = false
if damage >= 0 then if damage >= 0 then
-- only play hit sound and show blood effects if damage is 1 or over; lower to 0.1 to ensure armor works appropriately. -- only play hit sound and show blood effects if damage is 1 or over; lower to 0.1 to ensure armor works appropriately.
if damage >= 0.1 then if damage >= 0.1 then
-- weapon sounds -- weapon sounds
if weapon:get_definition().sounds ~= nil then if weapon:get_definition().sounds ~= nil then
local s = random(0, #weapon:get_definition().sounds)
local s = math.random(0, #weapon:get_definition().sounds)
minetest.sound_play(weapon:get_definition().sounds[s], { minetest.sound_play(weapon:get_definition().sounds[s], {
object = self.object, --hitter, object = self.object, --hitter,
@ -696,27 +600,20 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
end end
end end
-- knock back effect (only on full punch) -- knock back effect (only on full punch)
if self.knock_back if self.knock_back and tflp >= punch_interval then
and tflp >= punch_interval then
-- direction error check -- direction error check
dir = dir or {x = 0, y = 0, z = 0} dir = dir or vector_zero()
local v = self.object:get_velocity() local v = self.object:get_velocity()
if not v then return end if not v then return end
local r = 1.4 - math.min(punch_interval, 1.4) local r = 1.4 - min(punch_interval, 1.4)
local kb = r * (math.abs(v.x)+math.abs(v.z)) local kb = r * sqrt(v.x*v.x+v.z*v.z)
local up = 2.625 local up = 2.625
if die==true then if die then kb = kb * 1.25 end
kb=kb*1.25
end
-- if already in air then dont go up anymore when hit -- if already in air then dont go up anymore when hit
if math.abs(v.y) > 0.1 if abs(v.y) > 0.1 or self.fly then up = 0 end
or self.fly then
up = 0
end
-- check if tool already has specific knockback value -- check if tool already has specific knockback value
if tool_capabilities.damage_groups["knockback"] then if tool_capabilities.damage_groups["knockback"] then
@ -725,34 +622,30 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
kb = kb * 1.25 kb = kb * 1.25
end end
local luaentity = hitter and hitter:get_luaentity()
local luaentity
if hitter then
luaentity = hitter:get_luaentity()
end
if hitter and is_player then if hitter and is_player then
local wielditem = hitter:get_wielded_item() local wielditem = hitter:get_wielded_item()
kb = kb + 9 * mcl_enchanting.get_enchantment(wielditem, "knockback") kb = kb + 9 * mcl_enchanting.get_enchantment(wielditem, "knockback")
-- add player velocity to mob knockback -- add player velocity to mob knockback
local hv = hitter:get_velocity() local hv = hitter:get_velocity()
local dir_dot = (hv.x * dir.x) + (hv.z * dir.z) local dir_dot = hv.x * dir.x + hv.z * dir.z
local player_mag = math.sqrt((hv.x * hv.x) + (hv.z * hv.z)) local player_mag = sqrt(hv.x * hv.x + hv.z * hv.z)
local mob_mag = math.sqrt((v.x * v.x) + (v.z * v.z)) local mob_mag = sqrt(v.x * v.x + v.z * v.z)
if dir_dot > 0 and mob_mag <= player_mag * 0.625 then if dir_dot > 0 and mob_mag <= player_mag * 0.625 then
kb = kb + ((math.abs(hv.x) + math.abs(hv.z)) * r) kb = kb + (abs(hv.x) + abs(hv.z)) * r
end end
elseif luaentity and luaentity._knockback and die == false then elseif luaentity and luaentity._knockback and die == false then
kb = kb + luaentity._knockback kb = kb + luaentity._knockback * 0.25
elseif luaentity and luaentity._knockback and die == true then elseif luaentity and luaentity._knockback and die == true then
kb = kb + luaentity._knockback * 0.25 kb = kb + luaentity._knockback * 0.25
end end
self._kb_turn = true self._kb_turn = true
self._turn_to=self.object:get_yaw()-1.57 self:turn_by(HALFPI, .1) -- knockback turn
self.frame_speed_multiplier=2.3 self.frame_speed_multiplier=2.3
if self.animation.run_end then if self.animation.run_end then
self:set_animation( "run") self:set_animation("run")
elseif self.animation.walk_end then elseif self.animation.walk_end then
self:set_animation( "walk") self:set_animation("walk")
end end
minetest.after(0.2, function() minetest.after(0.2, function()
if self and self.object then if self and self.object then
@ -760,11 +653,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
self._kb_turn = false self._kb_turn = false
end end
end) end)
self.object:add_velocity({ self.object:add_velocity(vector_new(dir.x * kb, up, dir.z * kb ))
x = dir.x * kb,
y = up*2,
z = dir.z * kb
})
self.pause_timer = 0.25 self.pause_timer = 0.25
end end
@ -772,12 +661,15 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
-- if skittish then run away -- if skittish then run away
if hitter and is_player and hitter:get_pos() and not die and self.runaway == true and self.state ~= "flop" then if hitter and is_player and hitter:get_pos() and not die and self.runaway == true and self.state ~= "flop" then
local hp, sp = hitter:get_pos(), self.object:get_pos()
local yaw = self:set_yaw( minetest.dir_to_yaw(vector.direction(hitter:get_pos(), self.object:get_pos()))) self:turn_in_direction(sp.x - hp.x, sp.z - hp.z, 1)
minetest.after(0.2,function() minetest.after(0.2,function()
if self and self.object and self.object:get_pos() and hitter and is_player and hitter:get_pos() then if self and self.object and hitter and is_player then
yaw = self:set_yaw( minetest.dir_to_yaw(vector.direction(hitter:get_pos(), self.object:get_pos()))) local hp, sp = hitter:get_pos(), self.object:get_pos()
self:set_velocity( self.run_velocity) if hp and sp then
self:turn_in_direction(sp.x - hp.x, sp.z - hp.z, 1)
self:set_velocity(self.run_velocity)
end
end end
end) end)
self.state = "runaway" self.state = "runaway"
@ -805,12 +697,8 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
local alert_pos = hitter:get_pos() local alert_pos = hitter:get_pos()
if alert_pos then if alert_pos then
local objs = minetest.get_objects_inside_radius(alert_pos, self.view_range) local objs = minetest.get_objects_inside_radius(alert_pos, self.view_range)
local obj = nil
for n = 1, #objs do for n = 1, #objs do
local obj = objs[n]:get_luaentity()
obj = objs[n]:get_luaentity()
if obj then if obj then
-- only alert members of same mob or friends -- only alert members of same mob or friends
if obj.group_attack if obj.group_attack
@ -840,11 +728,7 @@ end
function mob_class:check_aggro(dtime) function mob_class:check_aggro(dtime)
if not self._aggro or not self.attack then return end if not self._aggro or not self.attack then return end
if not self._check_aggro_timer then self._check_aggro_timer = 0 end
if not self._check_aggro_timer then
self._check_aggro_timer = 0
end
if self._check_aggro_timer > 5 then if self._check_aggro_timer > 5 then
self._check_aggro_timer = 0 self._check_aggro_timer = 0
@ -852,7 +736,7 @@ function mob_class:check_aggro(dtime)
-- TODO consider removing this in favour of what is done in do_states_attack -- TODO consider removing this in favour of what is done in do_states_attack
-- Attack is dropped in do_states_attack if out of range, so won't even trigger here -- Attack is dropped in do_states_attack if out of range, so won't even trigger here
-- I do not think this code does anything. Are mobs still loaded in at 128? -- I do not think this code does anything. Are mobs still loaded in at 128?
if not self.attack:get_pos() or vector.distance(self.attack:get_pos(),self.object:get_pos()) > 128 then if not self.attack:get_pos() or vector_distance(self.attack:get_pos(),self.object:get_pos()) > 128 then
self._aggro = nil self._aggro = nil
self.attack = nil self.attack = nil
self.state = "stand" self.state = "stand"
@ -878,25 +762,21 @@ local function clear_aggro(self)
self.path.way = nil self.path.way = nil
end end
function mob_class:do_states_attack (dtime) function mob_class:do_states_attack(dtime)
self.timer = self.timer + dtime self.timer = self.timer + dtime
if self.timer > 100 then if self.timer > 100 then self.timer = 1 end
self.timer = 1
end
local s = self.object:get_pos() local s = self.object:get_pos()
if not s then return end local p = self.attack:get_pos()
if not s or not p then return end
local p = self.attack:get_pos() or s
local yaw = self.object:get_yaw() or 0
-- stop attacking if player invisible or out of range -- stop attacking if player invisible or out of range
if not self.attack if not self.attack
or not self.attack:get_pos() or not self.attack:get_pos()
or not self:object_in_range(self.attack) or not self:object_in_range(self.attack)
or self.attack:get_hp() <= 0 or self.attack:get_hp() <= 0
or (self.attack:is_player() and mcl_mobs.invis[ self.attack:get_player_name() ]) then or (self.attack:is_player() and mcl_mobs.invis[self.attack:get_player_name()]) then
clear_aggro(self) clear_aggro(self)
return return
@ -920,20 +800,15 @@ function mob_class:do_states_attack (dtime)
end end
-- calculate distance from mob and enemy -- calculate distance from mob and enemy
local dist = vector.distance(p, s) local dist = vector_distance(p, s)
if self.attack_type == "explode" then if self.attack_type == "explode" then
if target_line_of_sight then if target_line_of_sight then
local vec = { x = p.x - s.x, z = p.z - s.z } self:turn_in_direction(p.x - s.x, p.z - s.z, 1)
yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate
if p.x > s.x then yaw = yaw +math.pi end
yaw = self:set_yaw( yaw, 0, dtime)
end end
local node_break_radius = self.explosion_radius or 1 local node_break_radius = self.explosion_radius or 1
local entity_damage_radius = self.explosion_damage_radius local entity_damage_radius = self.explosion_damage_radius or (node_break_radius * 2)
or (node_break_radius * 2)
-- start timer when in reach and line of sight -- start timer when in reach and line of sight
if not self.v_start and dist <= self.reach and target_line_of_sight then if not self.v_start and dist <= self.reach and target_line_of_sight then
@ -960,9 +835,9 @@ function mob_class:do_states_attack (dtime)
end end
if self.animation and self.animation.run_start then if self.animation and self.animation.run_start then
self:set_animation( "run") self:set_animation("run")
else else
self:set_animation( "walk") self:set_animation("walk")
end end
if self.v_start then if self.v_start then
@ -1005,92 +880,50 @@ function mob_class:do_states_attack (dtime)
or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2) and (dist >= self.avoid_distance or not self.shooter_avoid_enemy) or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2) and (dist >= self.avoid_distance or not self.shooter_avoid_enemy)
or (self.attack_type == "dogshoot" and dist <= self.reach and self:dogswitch() == 0) then or (self.attack_type == "dogshoot" and dist <= self.reach and self:dogswitch() == 0) then
if self.fly if self.fly and dist > self.reach then
and dist > self.reach then local p1, p2 = s, p
local me_y, p_y = floor(p1.y), floor(p2.y + 1)
local p1 = s
local me_y = math.floor(p1.y)
local p2 = p
local p_y = math.floor(p2.y + 1)
local v = self.object:get_velocity() local v = self.object:get_velocity()
if self:flight_check( s) then if self:flight_check( s) then
if me_y < p_y then if me_y < p_y then
self.object:set_velocity(vector_new(v.x, 1 * self.walk_velocity, v.z))
self.object:set_velocity({
x = v.x,
y = 1 * self.walk_velocity,
z = v.z
})
elseif me_y > p_y then elseif me_y > p_y then
self.object:set_velocity(vector_new(v.x, -1 * self.walk_velocity, v.z))
self.object:set_velocity({
x = v.x,
y = -1 * self.walk_velocity,
z = v.z
})
end end
else else
if me_y < p_y then if me_y < p_y then
self.object:set_velocity(vector_new(v.x, 0.01, v.z))
self.object:set_velocity({
x = v.x,
y = 0.01,
z = v.z
})
elseif me_y > p_y then elseif me_y > p_y then
self.object:set_velocity(vector_new(v.x, -0.01, v.z))
self.object:set_velocity({
x = v.x,
y = -0.01,
z = v.z
})
end end
end end
end end
-- rnd: new movement direction -- rnd: new movement direction
if self.path.following if self.path.following and self.path.way and self.attack_type ~= "dogshoot" then
and self.path.way
and self.attack_type ~= "dogshoot" then
-- no paths longer than 50 -- no paths longer than 50
if #self.path.way > 50 if #self.path.way > 50 or dist < self.reach then
or dist < self.reach then
self.path.following = false self.path.following = false
return return
end end
local p1 = self.path.way[1] local p1 = self.path.way[1]
if not p1 then if not p1 then
self.path.following = false self.path.following = false
return return
end end
if math.abs(p1.x-s.x) + math.abs(p1.z - s.z) < 0.6 then if abs(p1.x - s.x) + abs(p1.z - s.z) < 0.6 then
-- reached waypoint, remove it from queue -- reached waypoint, remove it from queue
table.remove(self.path.way, 1) table.remove(self.path.way, 1)
end end
-- set new temporary target -- set new temporary target
p = {x = p1.x, y = p1.y, z = p1.z} p = vector_copy(p1)
end end
local vec = { self:turn_in_direction(p.x - s.x, p.z - s.z, 1)
x = p.x - s.x,
z = p.z - s.z
}
yaw = (atan(vec.z / vec.x) + math.pi / 2) - self.rotate
if p.x > s.x then yaw = yaw + math.pi end
yaw = self:set_yaw( yaw, 0, dtime)
-- move towards enemy if beyond mob reach -- move towards enemy if beyond mob reach
if dist > self.reach then if dist > self.reach then
@ -1100,10 +933,9 @@ function mob_class:do_states_attack (dtime)
end end
if self:is_at_cliff_or_danger() then if self:is_at_cliff_or_danger() then
self:set_velocity( 0) self:set_velocity(0)
self:set_animation( "stand") self:set_animation("stand")
local yaw = self.object:get_yaw() or 0 self:turn_by(PI * (random() - 0.5), 10)
yaw = self:set_yaw( yaw + 0.78, 8)
else else
if self.path.stuck then if self.path.stuck then
self:set_velocity(self.walk_velocity) self:set_velocity(self.walk_velocity)
@ -1121,7 +953,7 @@ function mob_class:do_states_attack (dtime)
self.path.stuck_timer = 0 self.path.stuck_timer = 0
self.path.following = false -- not stuck anymore self.path.following = false -- not stuck anymore
self:set_velocity( 0) self:set_velocity(0)
local attack_frequency = self.attack_frequency or 1 local attack_frequency = self.attack_frequency or 1
@ -1129,19 +961,13 @@ function mob_class:do_states_attack (dtime)
self.timer = 0 self.timer = 0
if not self.custom_attack then if not self.custom_attack then
if self.double_melee_attack and math.random(1, 2) == 1 then if self.double_melee_attack and random(1, 2) == 1 then
self:set_animation("punch2") self:set_animation("punch2")
else else
self:set_animation("punch") self:set_animation("punch")
end end
local p2 = p if self:line_of_sight(vector_offset(p, 0, .5, 0), vector_offset(s, 0, .5, 0)) == true then
local s2 = s
p2.y = p2.y + .5
s2.y = s2.y + .5
if self:line_of_sight( p2, s2) == true then
self:mob_sound("attack") self:mob_sound("attack")
-- punch player (or what player is attached to) -- punch player (or what player is attached to)
@ -1167,68 +993,43 @@ function mob_class:do_states_attack (dtime)
elseif self.attack_type == "shoot" elseif self.attack_type == "shoot"
or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1) or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1)
or (self.attack_type == "dogshoot" and (dist > self.reach or dist < self.avoid_distance and self.shooter_avoid_enemy) and self:dogswitch() == 0) then or (self.attack_type == "dogshoot" and (dist > self.reach or dist < self.avoid_distance and self.shooter_avoid_enemy) and self:dogswitch() == 0) then
local vec = vector_new(p.x - s.x, p.y - s.y - 1, p.z - s.z)
p.y = p.y - .5 local dist = sqrt(vec.x*vec.x + vec.y*vec.y + vec.z*vec.z)
s.y = s.y + .5 local dir = -atan2(p.x - s.x, p.z - s.z)
self:set_yaw(dir, 4)
local dist = vector.distance(p, s)
local vec = {
x = p.x - s.x,
y = p.y - s.y,
z = p.z - s.z
}
yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate
if p.x > s.x then yaw = yaw +math.pi end
yaw = self:set_yaw( yaw, 0, dtime)
local stay_away_from_player = vector.zero()
--strafe back and fourth
--stay away from player so as to shoot them
if dist < self.avoid_distance and self.shooter_avoid_enemy then
self:set_animation( "shoot")
stay_away_from_player=vector.multiply(vector.direction(p, s), 0.33)
end
if self.strafes then if self.strafes then
if not self.strafe_direction then if not self.strafe_direction then self.strafe_direction = math.random(0, 1) * 2 - 1 end
self.strafe_direction = 1.57 if random(50) == 1 then self.strafe_direction = -self.strafe_direction end
end
if math.random(40) == 1 then
self.strafe_direction = self.strafe_direction*-1
end
local dir = vector.rotate_around_axis(vector.direction(s, p), vector.new(0,1,0), self.strafe_direction) --stay away from player so as to shoot them
local dir2 = vector.multiply(dir, 0.3 * self.walk_velocity) if self.avoid_distance and self.shooter_avoid_enemy then
local f = (dist - self.avoid_distance) / self.avoid_distance
if dir2 and stay_away_from_player then f = math.max(-1, math.min(1, f))
self.acc = vector.add(dir2, stay_away_from_player) f = f * math.abs(f)
self:set_velocity(f * self.walk_velocity, (1 - math.abs(f)) * self.strafe_direction * self.walk_velocity * 1.5)
elseif dist > 1 then
self:set_velocity(self.walk_velocity)
else
self:set_velocity(0)
end end
else else
self:set_velocity( 0) self:set_velocity(0)
end end
local p = self.object:get_pos() local p = self.object:get_pos()
p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2 p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) * 0.5
if self.shoot_interval
and self.timer > self.shoot_interval
and not minetest.raycast(vector.add(p, vector.new(0,self.shoot_offset,0)), vector.add(self.attack:get_pos(), vector.new(0,1.5,0)), false, false):next()
and math.random(1, 100) <= 60 then
if self.shoot_interval and self.timer > self.shoot_interval and random(1, 100) <= 60
and not minetest.raycast(vector_offset(p, 0, self.shoot_offset, 0), vector_offset(self.attack:get_pos(), 0, 1.5, 0), false, false):next() then
self.timer = 0 self.timer = 0
self:set_animation( "shoot") self:set_animation("shoot")
-- play shoot attack sound -- play shoot attack sound
self:mob_sound("shoot_attack") self:mob_sound("shoot_attack")
-- Shoot arrow -- Shoot arrow
if minetest.registered_entities[self.arrow] then if minetest.registered_entities[self.arrow] then
local arrow, ent local arrow, ent
local v = 1 local v = 1
if not self.shoot_arrow then if not self.shoot_arrow then
@ -1238,9 +1039,7 @@ function mob_class:do_states_attack (dtime)
end) end)
arrow = minetest.add_entity(p, self.arrow) arrow = minetest.add_entity(p, self.arrow)
ent = arrow:get_luaentity() ent = arrow:get_luaentity()
if ent.velocity then v = ent.velocity or v
v = ent.velocity
end
ent.switch = 1 ent.switch = 1
ent.owner_id = tostring(self.object) -- add unique owner id to arrow ent.owner_id = tostring(self.object) -- add unique owner id to arrow
@ -1252,12 +1051,9 @@ function mob_class:do_states_attack (dtime)
end end
end end
local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
-- offset makes shoot aim accurate -- offset makes shoot aim accurate
vec.y = vec.y + self.shoot_offset vec.y = vec.y + self.shoot_offset
vec.x = vec.x * (v / amount) vec.x, vec.y, vec.z = vec.x * (v / dist), vec.y * (v / dist), vec.z * (v / dist)
vec.y = vec.y * (v / amount)
vec.z = vec.z * (v / amount)
if self.shoot_arrow then if self.shoot_arrow then
vec = vector.normalize(vec) vec = vector.normalize(vec)
self:shoot_arrow(p, vec) self:shoot_arrow(p, vec)
@ -1266,13 +1062,9 @@ function mob_class:do_states_attack (dtime)
end end
end end
end end
elseif self.attack_type == "custom" and self.attack_state then elseif self.attack_type == "custom" and self.attack_state then
self.attack_state(self, dtime) self.attack_state(self, dtime)
end end
if self.on_attack then if self.on_attack then self.on_attack(self, dtime) end
self.on_attack(self, dtime)
end
end end

View File

@ -1,10 +1,11 @@
local math, tonumber, vector, minetest, mcl_mobs = math, tonumber, vector, minetest, mcl_mobs local math, tonumber, vector, minetest, mcl_mobs = math, tonumber, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class local mob_class = mcl_mobs.mob_class
local validate_vector = mcl_util.validate_vector --local validate_vector = mcl_util.validate_vector
local active_particlespawners = {} local active_particlespawners = {}
local disable_blood = minetest.settings:get_bool("mobs_disable_blood") local disable_blood = minetest.settings:get_bool("mobs_disable_blood")
local DEFAULT_FALL_SPEED = -9.81*1.5 local DEFAULT_FALL_SPEED = -9.81*1.5
local PI_THIRD = math.pi / 3 -- 60 degrees
local PATHFINDING = "gowp" local PATHFINDING = "gowp"
@ -13,51 +14,31 @@ if player_transfer_distance == 0 then player_transfer_distance = math.huge end
-- custom particle effects -- custom particle effects
function mcl_mobs.effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, go_down) function mcl_mobs.effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, go_down)
radius = radius or 2 radius = radius or 2
min_size = min_size or 0.5
max_size = max_size or 1
gravity = gravity or DEFAULT_FALL_SPEED
glow = glow or 0
go_down = go_down or false
local ym
if go_down then
ym = 0
else
ym = -radius
end
minetest.add_particlespawner({ minetest.add_particlespawner({
amount = amount, amount = amount,
time = 0.25, time = 0.25,
minpos = pos, minpos = pos,
maxpos = pos, maxpos = pos,
minvel = {x = -radius, y = ym, z = -radius}, minvel = vector.new(-radius, go_down and 0 or -radius, -radius),
maxvel = {x = radius, y = radius, z = radius}, maxvel = vector.new(radius, radius, radius),
minacc = {x = 0, y = gravity, z = 0}, minacc = vector.new(0, gravity or DEFAULT_FALL_SPEED, 0),
maxacc = {x = 0, y = gravity, z = 0}, maxacc = vector.new(0, gravity or DEFAULT_FALL_SPEED, 0),
minexptime = 0.1, minexptime = 0.1,
maxexptime = 1, maxexptime = 1,
minsize = min_size, minsize = min_size or 0.5,
maxsize = max_size, maxsize = max_size or 1,
texture = texture, texture = texture,
glow = glow, glow = glow or 0,
}) })
end end
function mcl_mobs.death_effect(pos, yaw, collisionbox, rotate) function mcl_mobs.death_effect(pos, yaw, collisionbox, rotate)
local min, max local min = collisionbox and vector.new(collisionbox[1], collisionbox[2], collisionbox[3]) or vector.new(-0.5, 0, -0.5)
if collisionbox then local max = collisionbox and vector.new(collisionbox[4], collisionbox[5], collisionbox[6]) or vector.new(0.5, 0.5, 0.5)
min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]}
max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]}
else
min = { x = -0.5, y = 0, z = -0.5 }
max = { x = 0.5, y = 0.5, z = 0.5 }
end
if rotate then if rotate then
min = vector.rotate(min, {x=0, y=yaw, z=math.pi/2}) min = vector.rotate(min, vector.new(0, yaw, math.pi/2))
max = vector.rotate(max, {x=0, y=yaw, z=math.pi/2}) max = vector.rotate(max, vector.new(0, yaw, math.pi/2))
min, max = vector.sort(min, max) min, max = vector.sort(min, max)
min = vector.multiply(min, 0.5) min = vector.multiply(min, 0.5)
max = vector.multiply(max, 0.5) max = vector.multiply(max, 0.5)
@ -89,57 +70,35 @@ end
-- play sound -- play sound
function mob_class:mob_sound(soundname, is_opinion, fixed_pitch) function mob_class:mob_sound(soundname, is_opinion, fixed_pitch)
local soundinfo = self.child and self.sounds_child or self.sounds
local soundinfo if not soundinfo then return end
if self.sounds_child and self.child then
soundinfo = self.sounds_child
elseif self.sounds then
soundinfo = self.sounds
end
if not soundinfo then
return
end
local sound = soundinfo[soundname] local sound = soundinfo[soundname]
if sound then if not sound then return end
if is_opinion and self.opinion_sound_cooloff > 0 then if is_opinion and self.opinion_sound_cooloff > 0 then return end
return local pitch
end if not fixed_pitch then
local pitch pitch = soundinfo.base_pitch or 1
if not fixed_pitch then if self.child and not self.sounds_child then pitch = pitch * 1.5 end
local base_pitch = soundinfo.base_pitch pitch = pitch + (math.random() - 0.5) * 0.2
if not base_pitch then
base_pitch = 1
end
if self.child and (not self.sounds_child) then
-- Children have higher pitch
pitch = base_pitch * 1.5
else
pitch = base_pitch
end
-- randomize the pitch a bit
pitch = pitch + math.random(-10, 10) * 0.005
end
-- Should be 0.1 to 0.2 for mobs. Cow and zombie farms loud. At least have cool down.
minetest.sound_play(sound, {
object = self.object,
gain = 1.0,
max_hear_distance = self.sounds.distance,
pitch = pitch,
}, true)
self.opinion_sound_cooloff = 1
end end
-- Should be 0.1 to 0.2 for mobs. Cow and zombie farms loud. At least have cool down.
minetest.sound_play(sound, {
object = self.object,
gain = 1.0,
max_hear_distance = self.sounds.distance,
pitch = pitch,
}, true)
self.opinion_sound_cooloff = 1
end end
function mob_class:step_opinion_sound(dtime) function mob_class:step_opinion_sound(dtime)
if self.state ~= "attack" and self.state ~= PATHFINDING then if self.state == "attack" or self.state == PATHFINDING then return end
if self.opinion_sound_cooloff > 0 then
if self.opinion_sound_cooloff > 0 then self.opinion_sound_cooloff = self.opinion_sound_cooloff - dtime
self.opinion_sound_cooloff = self.opinion_sound_cooloff - dtime end
end -- mob plays random sound at times. Should be 120. Zombie and mob farms are ridiculous
-- mob plays random sound at times. Should be 120. Zombie and mob farms are ridiculous if math.random(1, 70) == 1 then
if math.random(1, 70) == 1 then self:mob_sound("random", true)
self:mob_sound("random", true)
end
end end
end end
@ -169,33 +128,29 @@ function mob_class:remove_texture_mod(mod)
table.insert(remove, i) table.insert(remove, i)
end end
end end
for i=#remove, 1 do for i=#remove, 1, -1 do
table.remove(self.texture_mods, remove[i]) table.remove(self.texture_mods, remove[i])
end end
self.object:set_texture_mod(full_mod) self.object:set_texture_mod(full_mod)
end end
function mob_class:damage_effect(damage) function mob_class:damage_effect(damage)
-- damage particles if disable_blood or damage <= 0 then return end
if (not disable_blood) and damage > 0 then local amount_large = math.floor(damage / 2)
local amount_small = damage % 2
local amount_large = math.floor(damage / 2) local pos = self.object:get_pos()
local amount_small = damage % 2 pos.y = pos.y + (self.collisionbox[5] - self.collisionbox[2]) * .5
local pos = self.object:get_pos() local texture = "mobs_blood.png"
-- full heart damage (one particle for each 2 HP damage)
pos.y = pos.y + (self.collisionbox[5] - self.collisionbox[2]) * .5 if amount_large > 0 then
mcl_mobs.effect(pos, amount_large, texture, 2, 2, 1.75, 0, nil, true)
local texture = "mobs_blood.png" end
-- full heart damage (one particle for each 2 HP damage) -- half heart damage (one additional particle if damage is an odd number)
if amount_large > 0 then if amount_small > 0 then
mcl_mobs.effect(pos, amount_large, texture, 2, 2, 1.75, 0, nil, true) -- TODO: Use "half heart"
end mcl_mobs.effect(pos, amount_small, texture, 1, 1, 1.75, 0, nil, true)
-- half heart damage (one additional particle if damage is an odd number)
if amount_small > 0 then
-- TODO: Use "half heart"
mcl_mobs.effect(pos, amount_small, texture, 1, 1, 1.75, 0, nil, true)
end
end end
end end
@ -245,21 +200,15 @@ end
-- set defined animation -- set defined animation
function mob_class:set_animation(anim, fixed_frame) function mob_class:set_animation(anim, fixed_frame)
if not self.animation or not anim then if not self.animation or not anim then return end
return
end
if self.jockey and self.object:get_attach() then if self.jockey and self.object:get_attach() then
anim = "jockey" anim = "jockey"
elseif not self.object:get_attach() then elseif not self.object:get_attach() then
self.jockey = nil self.jockey = nil
end end
if self.state == "die" and anim ~= "die" and anim ~= "stand" then
return
end
if self.state == "die" and anim ~= "die" and anim ~= "stand" then return end
if self.fly and self:flight_check() and anim == "walk" then anim = "fly" end if self.fly and self:flight_check() and anim == "walk" then anim = "fly" end
@ -274,12 +223,7 @@ function mob_class:set_animation(anim, fixed_frame)
self._current_animation = anim self._current_animation = anim
local a_start = self.animation[anim .. "_start"] local a_start = self.animation[anim .. "_start"]
local a_end local a_end = fixed_frame and a_start or self.animation[anim .. "_end"]
if fixed_frame then
a_end = a_start
else
a_end = self.animation[anim .. "_end"]
end
if a_start and a_end then if a_start and a_end then
self.object:set_animation({ self.object:set_animation({
x = a_start, x = a_start,
@ -289,91 +233,67 @@ function mob_class:set_animation(anim, fixed_frame)
end end
end end
-- above function exported for mount.lua
function mcl_mobs:set_animation(self, anim)
self:set_animation(anim)
end
local function dir_to_pitch(dir)
--local dir2 = vector.normalize(dir)
local xz = math.abs(dir.x) + math.abs(dir.z)
return -math.atan2(-dir.y, xz)
end
local function who_are_you_looking_at (self, dtime) local function who_are_you_looking_at (self, dtime)
local pos = self.object:get_pos() if self.order == "sleep" then
self._locked_object = nil
return
end
local stop_look_at_player_chance = math.random(833/self.curiosity)
-- was 10000 - div by 12 for avg entities as outside loop -- was 10000 - div by 12 for avg entities as outside loop
local stop_look_at_player = math.random() * 833 <= self.curiosity
local stop_look_at_player = stop_look_at_player_chance == 1
if self.attack then if self.attack then
if not self.target_time_lost then self._locked_object = not self.target_time_lost and self.attack or nil
self._locked_object = self.attack
else
self._locked_object = nil
end
elseif self.following then elseif self.following then
self._locked_object = self.following self._locked_object = self.following
elseif self._locked_object then elseif self._locked_object then
if stop_look_at_player then if stop_look_at_player then self._locked_object = nil end
--minetest.log("Stop look: ".. self.name)
self._locked_object = nil
end
elseif not self._locked_object then elseif not self._locked_object then
if mcl_util.check_dtime_timer(self, dtime, "step_look_for_someone", 0.2) then if mcl_util.check_dtime_timer(self, dtime, "step_look_for_someone", 0.2) then
--minetest.log("Change look check: ".. self.name) local pos = self.object:get_pos()
-- For the wither this was 20/60=0.33, so probably need to rebalance and divide rates.
-- but frequency of check isn't good as it is costly. Making others too infrequent requires testing
local chance = 150/self.curiosity
if chance < 1 then chance = 1 end
local look_at_player_chance = math.random(chance)
-- was 5000 but called in loop based on entities. so div by 12 as estimate avg of entities found,
-- then div by 20 as less freq lookup
local look_at_player = look_at_player_chance == 1
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 8)) do for _, obj in pairs(minetest.get_objects_inside_radius(pos, 8)) do
if obj:is_player() and vector.distance(pos,obj:get_pos()) < 4 then if obj:is_player() and vector.distance(pos, obj:get_pos()) < 4 then
--minetest.log("Change look to player: ".. self.name)
self._locked_object = obj self._locked_object = obj
break break
elseif obj:is_player() or (obj:get_luaentity() and obj:get_luaentity().name == self.name and self ~= obj:get_luaentity()) then elseif obj:is_player() or (obj:get_luaentity() and self ~= obj:get_luaentity() and obj:get_luaentity().name == self.name) then
if look_at_player then -- For the wither this was 20/60=0.33, so probably need to rebalance and divide rates.
--minetest.log("Change look to mob: ".. self.name) -- but frequency of check isn't good as it is costly. Making others too infrequent requires testing
-- was 5000 but called in loop based on entities. so div by 12 as estimate avg of entities found,
-- then div by 20 as less freq lookup
if math.random() * 150 <= self.curiosity then
self._locked_object = obj self._locked_object = obj
break break
end end
end end
end end
end end
end end
end end
function mob_class:check_head_swivel(dtime) function mob_class:check_head_swivel(dtime)
if not self.head_swivel or type(self.head_swivel) ~= "string" then return end if not self.head_swivel or type(self.head_swivel) ~= "string" then return end
who_are_you_looking_at(self, dtime)
who_are_you_looking_at (self, dtime) local newr = vector.zero()
local oldp, oldr
if self.object.get_bone_override then -- minetest >= 5.9
local ov = self.object:get_bone_override(self.head_swivel)
oldp, oldr = ov.position.vec, ov.rotation.vec
else -- minetest < 5.9
oldp, oldr = self.object:get_bone_position(self.head_swivel)
oldr = vector.apply(oldr, math.rad) -- old API uses radians
end
local final_rotation = vector.zero() local locked_object = self._locked_object
local oldp,oldr = self.object:get_bone_position(self.head_swivel) if locked_object and (locked_object:is_player() or locked_object:get_luaentity()) and locked_object:get_hp() > 0 then
if self._locked_object and (self._locked_object:is_player() or self._locked_object:get_luaentity()) and self._locked_object:get_hp() > 0 then
local _locked_object_eye_height = 1.5 local _locked_object_eye_height = 1.5
if self._locked_object:get_luaentity() then if locked_object:is_player() then
_locked_object_eye_height = self._locked_object:get_luaentity().head_eye_height _locked_object_eye_height = locked_object:get_properties().eye_height
end elseif locked_object:get_luaentity() then
if self._locked_object:is_player() then _locked_object_eye_height = locked_object:get_luaentity().head_eye_height
_locked_object_eye_height = self._locked_object:get_properties().eye_height
end end
if _locked_object_eye_height then if _locked_object_eye_height then
local self_rot = self.object:get_rotation() local self_rot = self.object:get_rotation()
-- If a mob is attached, should we really be messing with what they are looking at? -- If a mob is attached, should we really be messing with what they are looking at?
-- Should this be excluded? -- Should this be excluded?
@ -381,58 +301,63 @@ function mob_class:check_head_swivel(dtime)
self_rot = self.object:get_attach():get_rotation() self_rot = self.object:get_attach():get_rotation()
end end
local player_pos = self._locked_object:get_pos() local ps = self.object:get_pos()
local direction_player = vector.direction(vector.add(self.object:get_pos(), vector.new(0, self.head_eye_height*.7, 0)), vector.add(player_pos, vector.new(0, _locked_object_eye_height, 0))) ps.y = ps.y + self.head_eye_height * .7
local mob_yaw = math.deg(-(-(self_rot.y)-(-minetest.dir_to_yaw(direction_player))))+self.head_yaw_offset local pt = locked_object:get_pos()
local mob_pitch = math.deg(-dir_to_pitch(direction_player))*self.head_pitch_multiplier pt.y = pt.y + _locked_object_eye_height
local dir = vector.direction(ps, pt)
local mob_yaw = self_rot.y + math.atan2(dir.x, dir.z) + self.head_yaw_offset
local mob_pitch = math.asin(-dir.y) * self.head_pitch_multiplier
if (mob_yaw < -60 or mob_yaw > 60) and not (self.attack and self.state == "attack" and not self.runaway) then if (mob_yaw < -PI_THIRD or mob_yaw > PI_THIRD) and not (self.attack and self.state == "attack" and not self.runaway) then
final_rotation = vector.multiply(oldr, 0.9) newr = vector.multiply(oldr, 0.9)
elseif self.attack and self.state == "attack" and not self.runaway then elseif self.attack and self.state == "attack" and not self.runaway then
if self.head_yaw == "y" then if self.head_yaw == "y" then
final_rotation = vector.new(mob_pitch, mob_yaw, 0) newr = vector.new(mob_pitch, mob_yaw, 0)
elseif self.head_yaw == "z" then elseif self.head_yaw == "z" then
final_rotation = vector.new(mob_pitch, 0, -mob_yaw) newr = vector.new(mob_pitch, 0, -mob_yaw)
end end
else else
if self.head_yaw == "y" then if self.head_yaw == "y" then
final_rotation = vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, ((mob_yaw-oldr.y)*.3)+oldr.y, 0) newr = vector.new((mob_pitch-oldr.x)*.3+oldr.x, (mob_yaw-oldr.y)*.3+oldr.y, 0)
elseif self.head_yaw == "z" then elseif self.head_yaw == "z" then
final_rotation = vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, 0, -(((mob_yaw-oldr.y)*.3)+oldr.y)*3) newr = vector.new((mob_pitch-oldr.x)*.3+oldr.x, 0, ((mob_yaw-oldr.y)*.3+oldr.y)*-3)
end end
end end
end end
elseif not self._locked_object and math.abs(oldr.y) > 3 and math.abs(oldr.x) < 3 then elseif not locked_object and math.abs(oldr.y) > 0.05 and math.abs(oldr.x) < 0.05 then
final_rotation = vector.multiply(oldr, 0.9) newr = vector.multiply(oldr, 0.9)
else
--final_rotation = vector.new(0,0,0)
end end
mcl_util.set_bone_position(self.object,self.head_swivel, vector.new(0,self.bone_eye_height,self.horizontal_head_height), final_rotation) -- 0.02 is about 1.14 degrees tolerance, to update less often
local newp = vector.new(0, self.bone_eye_height, self.horizontal_head_height)
if math.abs(oldr.x-newr.x) + math.abs(oldr.y-newr.y) + math.abs(oldr.z-newr.z) < 0.02 and vector.equals(oldp, newp) then return end
if self.object.get_bone_override then -- minetest >= 5.9
self.object:set_bone_override(self.head_swivel, {
position = { vec = newp, absolute = true },
rotation = { vec = newr, absolute = true } })
else -- minetest < 5.9
-- old API uses degrees not radians
self.object:set_bone_position(self.head_swivel, newp, vector.apply(newr, math.deg))
end
end end
function mob_class:set_animation_speed() function mob_class:set_animation_speed()
local v = self.object:get_velocity() local v = self:get_velocity()
if v then if v > 0 then
if self.frame_speed_multiplier then if self.frame_speed_multiplier then
local v2 = math.abs(v.x)+math.abs(v.z)*.833 self.animation.walk_speed = self.animation.walk_speed or 25 -- TODO: move to initialization
if not self.animation.walk_speed then if v > 0.5 then
self.animation.walk_speed = 25 self.object:set_animation_frame_speed((v/math.max(1,self.run_velocity))*self.animation.walk_speed*self.frame_speed_multiplier)
end
if math.abs(v.x)+math.abs(v.z) > 0.5 then
self.object:set_animation_frame_speed((v2/math.max(1,self.run_velocity))*self.animation.walk_speed*self.frame_speed_multiplier)
else else
self.object:set_animation_frame_speed(25) self.object:set_animation_frame_speed(25)
end end
end end
--set_speed --set_speed
if validate_vector(self.acc) then --if validate_vector(self.acc) then
self.object:add_velocity(self.acc) -- self.object:add_velocity(self.acc)
end --end
end end
end end

View File

@ -6,6 +6,40 @@ local modname = minetest.get_current_modname()
local path = minetest.get_modpath(modname) local path = minetest.get_modpath(modname)
local S = minetest.get_translator(modname) local S = minetest.get_translator(modname)
mcl_mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core:dirt" mcl_mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core:dirt"
mcl_mobs.see_through_opaque = minetest.settings:get_bool("mobs_see_through_opaque", false)
-- used by the libaries below.
-- get node but use fallback for nil or unknown
local function node_ok(pos, fallback)
fallback = fallback or mcl_mobs.fallback_node
local node = minetest.get_node_or_nil(pos)
if node and minetest.registered_nodes[node.name] then
return node
end
return minetest.registered_nodes[fallback]
end
mcl_mobs.node_ok = node_ok
local function line_of_sight(origin, target, see_through_opaque, liquids)
local raycast = minetest.raycast(origin, target, false, liquids or false)
for hitpoint in raycast do
if hitpoint.type == "node" then
local node = minetest.get_node(minetest.get_pointed_thing_position(hitpoint))
if node.name ~= "air" then
local nodef = minetest.registered_nodes[node.name]
if nodef and nodef.walkable and not (see_through_opaque and not nodef.groups.opaque) then
return false
end
end
end
--TODO type object could block vision, for example chests
end
return true
end
mcl_mobs.line_of_sight = line_of_sight
mcl_mobs.NODE_IGNORE = { name = "ignore", groups = {} } -- fallback for unknown nodes
--api and helpers --api and helpers
-- effects: sounds and particles mostly -- effects: sounds and particles mostly
dofile(path .. "/effects.lua") dofile(path .. "/effects.lua")
@ -19,10 +53,9 @@ dofile(path .. "/items.lua")
dofile(path .. "/pathfinding.lua") dofile(path .. "/pathfinding.lua")
-- combat: attack logic -- combat: attack logic
dofile(path .. "/combat.lua") dofile(path .. "/combat.lua")
-- the enity functions themselves -- the entity functions themselves
dofile(path .. "/api.lua") dofile(path .. "/api.lua")
--utility functions --utility functions
dofile(path .. "/breeding.lua") dofile(path .. "/breeding.lua")
dofile(path .. "/spawning.lua") dofile(path .. "/spawning.lua")
@ -37,16 +70,6 @@ local old_spawn_icons = minetest.settings:get_bool("mcl_old_spawn_icons",false)
local extended_pet_control = minetest.settings:get_bool("mcl_extended_pet_control",true) local extended_pet_control = minetest.settings:get_bool("mcl_extended_pet_control",true)
local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0 local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0
-- get node but use fallback for nil or unknown
local node_ok = function(pos, fallback)
fallback = fallback or mcl_mobs.fallback_node
local node = minetest.get_node_or_nil(pos)
if node and minetest.registered_nodes[node.name] then
return node
end
return minetest.registered_nodes[fallback]
end
--#### REGISTER FUNCS --#### REGISTER FUNCS
-- Code to execute before custom on_rightclick handling -- Code to execute before custom on_rightclick handling
@ -114,14 +137,8 @@ function mcl_mobs.register_mob(name, def)
mcl_mobs.spawning_mobs[name] = true mcl_mobs.spawning_mobs[name] = true
mcl_mobs.registered_mobs[name] = def mcl_mobs.registered_mobs[name] = def
local can_despawn local can_despawn = def.can_despawn
if def.can_despawn ~= nil then if def.can_despawn == nil then can_despawn = def.spawn_class ~= "passive" end
can_despawn = def.can_despawn
elseif def.spawn_class == "passive" then
can_despawn = false
else
can_despawn = true
end
local function scale_difficulty(value, default, min, special) local function scale_difficulty(value, default, min, special)
if (not value) or (value == default) or (value == special) then if (not value) or (value == default) or (value == special) then
@ -131,20 +148,25 @@ function mcl_mobs.register_mob(name, def)
end end
end end
if type(def.fly_in) == "string" then
def.fly_in = { def.fly_in }
end
local collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25} local collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25}
-- Workaround for <https://github.com/minetest/minetest/issues/5966>: -- Workaround for <https://github.com/minetest/minetest/issues/5966>:
-- Increase upper Y limit to avoid mobs glitching through solid nodes. -- Increase upper Y limit to avoid mobs glitching through solid nodes.
-- Removed now, as this was supposedly fixed in 5.3.0?
-- FIXME: Remove workaround if it's no longer needed. -- FIXME: Remove workaround if it's no longer needed.
if collisionbox[5] < 0.79 then --if collisionbox[5] < 0.79 then
collisionbox[5] = 0.79 -- collisionbox[5] = 0.79
end --end
local final_def = { local final_def = {
use_texture_alpha = def.use_texture_alpha, use_texture_alpha = def.use_texture_alpha,
head_swivel = def.head_swivel or nil, -- bool to activate this function head_swivel = def.head_swivel or nil, -- bool to activate this function
head_yaw_offset = def.head_yaw_offset or 0, -- for wonkey model bones head_yaw_offset = math.rad(def.head_yaw_offset or 0), -- for wonkey model bones
head_pitch_multiplier = def.head_pitch_multiplier or 1, --for inverted pitch head_pitch_multiplier = def.head_pitch_multiplier or 1, --for inverted pitch
bone_eye_height = def.bone_eye_height or 1.4, -- head bone offset bone_eye_height = def.bone_eye_height or 1.4, -- head bone offset
head_eye_height = def.head_eye_height or def.bone_eye_height or 0, -- how hight aproximatly the mobs head is fromm the ground to tell the mob how high to look up at the player head_eye_height = def.head_eye_height or def.bone_eye_height or 0, -- how high aproximately the mobs head is from the ground to tell the mob how high to look up at the player
curiosity = def.curiosity or 1, -- how often mob will look at player on idle curiosity = def.curiosity or 1, -- how often mob will look at player on idle
head_yaw = def.head_yaw or "y", -- axis to rotate head on head_yaw = def.head_yaw or "y", -- axis to rotate head on
horizontal_head_height = def.horizontal_head_height or 0, horizontal_head_height = def.horizontal_head_height or 0,
@ -163,7 +185,7 @@ function mcl_mobs.register_mob(name, def)
spawn_small_alternative = def.spawn_small_alternative, spawn_small_alternative = def.spawn_small_alternative,
do_custom = def.do_custom, do_custom = def.do_custom,
detach_child = def.detach_child, detach_child = def.detach_child,
jump_height = def.jump_height or 4, -- was 6 jump_height = def.jump_height or 1,
rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2 rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2
lifetimer = def.lifetimer or 57.73, lifetimer = def.lifetimer or 57.73,
hp_min = scale_difficulty(def.hp_min, 5, 1), hp_min = scale_difficulty(def.hp_min, 5, 1),
@ -229,7 +251,6 @@ function mcl_mobs.register_mob(name, def)
health = 0, health = 0,
frame_speed_multiplier = 1, frame_speed_multiplier = 1,
reach = def.reach or 3, reach = def.reach or 3,
htimer = 0,
texture_list = def.textures, texture_list = def.textures,
child_texture = def.child_texture, child_texture = def.child_texture,
docile_by_day = def.docile_by_day or false, docile_by_day = def.docile_by_day or false,
@ -383,11 +404,8 @@ end
-- register arrow for shoot attack -- register arrow for shoot attack
function mcl_mobs.register_arrow(name, def) function mcl_mobs.register_arrow(name, def)
if not name or not def then return end -- errorcheck if not name or not def then return end -- errorcheck
minetest.register_entity(name, { minetest.register_entity(name, {
physical = false, physical = false,
visual = def.visual, visual = def.visual,
visual_size = def.visual_size, visual_size = def.visual_size,
@ -399,7 +417,6 @@ function mcl_mobs.register_arrow(name, def)
hit_object = def.hit_object, hit_object = def.hit_object,
homing = def.homing, homing = def.homing,
drop = def.drop or false, -- drops arrow as registered item when true drop = def.drop or false, -- drops arrow as registered item when true
collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows
timer = 0, timer = 0,
switch = 0, switch = 0,
_lifetime = def._lifetime or 7, _lifetime = def._lifetime or 7,
@ -411,31 +428,21 @@ function mcl_mobs.register_arrow(name, def)
self._puncher = puncher self._puncher = puncher
end, end,
collisionbox = def.collisionbox or {0, 0, 0, 0, 0, 0}, collisionbox = def.collisionbox or {0, 0, 0, 0, 0, 0},
automatic_face_movement_dir = def.rotate automatic_face_movement_dir = def.rotate and (def.rotate - (math.pi / 180)) or false,
and (def.rotate - (math.pi / 180)) or false,
on_activate = def.on_activate, on_activate = def.on_activate,
on_step = def.on_step or function(self, dtime) on_step = def.on_step or function(self, dtime)
self.timer = self.timer + dtime self.timer = self.timer + dtime
local pos = self.object:get_pos() local pos = self.object:get_pos()
if self.switch == 0 or self.timer > self._lifetime or not within_limits(pos, 0) then
if self.switch == 0
or self.timer > self._lifetime
or not within_limits(pos, 0) then
mcl_burning.extinguish(self.object) mcl_burning.extinguish(self.object)
self.object:remove(); self.object:remove();
return return
end end
-- does arrow have a tail (fireball) -- does arrow have a tail (fireball)
if def.tail if def.tail == 1 and def.tail_texture then
and def.tail == 1
and def.tail_texture then
minetest.add_particle({ minetest.add_particle({
pos = pos, pos = pos,
velocity = {x = 0, y = 0, z = 0}, velocity = {x = 0, y = 0, z = 0},
@ -449,24 +456,17 @@ function mcl_mobs.register_arrow(name, def)
end end
if self.hit_node then if self.hit_node then
local node = node_ok(pos).name local node = node_ok(pos).name
if minetest.registered_nodes[node].walkable then if minetest.registered_nodes[node].walkable then
self.hit_node(self, pos, node) self.hit_node(self, pos, node)
if self.drop == true then if self.drop == true then
pos.y = pos.y + 1 pos.y = pos.y + 1
self.lastpos = (self.lastpos or pos) self.lastpos = (self.lastpos or pos)
minetest.add_item(self.lastpos, self.object:get_luaentity().name) minetest.add_item(self.lastpos, self.object:get_luaentity().name)
end end
self.object:remove(); self.object:remove();
return return
end end
end end
@ -483,12 +483,8 @@ function mcl_mobs.register_arrow(name, def)
end end
if self.hit_player or self.hit_mob or self.hit_object then if self.hit_player or self.hit_mob or self.hit_object then
for _,object in pairs(minetest.get_objects_inside_radius(pos, 1.5)) do for _,object in pairs(minetest.get_objects_inside_radius(pos, 1.5)) do
if self.hit_player and object:is_player() then
if self.hit_player
and object:is_player() then
self.hit_player(self, object) self.hit_player(self, object)
self.object:remove(); self.object:remove();
return return
@ -529,30 +525,20 @@ end
-- * spawn_egg=1: Spawn egg (generic mob, no metadata) -- * spawn_egg=1: Spawn egg (generic mob, no metadata)
-- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata) -- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata)
function mcl_mobs.register_egg(mob_id, desc, background_color, overlay_color, addegg, no_creative) function mcl_mobs.register_egg(mob_id, desc, background_color, overlay_color, addegg, no_creative)
local grp = { spawn_egg = 1 }
local grp = {spawn_egg = 1} if no_creative == true then grp.not_in_creative_inventory = 1 end
-- do NOT add this egg to creative inventory (e.g. dungeon master)
if no_creative == true then
grp.not_in_creative_inventory = 1
end
local invimg = "(spawn_egg.png^[multiply:" .. background_color ..")^(spawn_egg_overlay.png^[multiply:" .. overlay_color .. ")" local invimg = "(spawn_egg.png^[multiply:" .. background_color ..")^(spawn_egg_overlay.png^[multiply:" .. overlay_color .. ")"
if old_spawn_icons then if old_spawn_icons then
local mobname = mob_id:gsub("mobs_mc:","") local fn = "mobs_mc_spawn_icon_" .. mob_id:gsub("mobs_mc:","") .. ".png"
local fn = "mobs_mc_spawn_icon_"..mobname..".png" if mcl_util.file_exists(minetest.get_modpath("mobs_mc").."/textures/"..fn) then invimg = fn end
if mcl_util.file_exists(minetest.get_modpath("mobs_mc").."/textures/"..fn) then
invimg = fn
end
end end
if addegg == 1 then if addegg == 1 then
invimg = "mobs_chicken_egg.png^(" .. invimg .. invimg = "mobs_chicken_egg.png^(" .. invimg .. "^[mask:mobs_chicken_egg_overlay.png)"
"^[mask:mobs_chicken_egg_overlay.png)"
end end
-- register old stackable mob egg -- register old stackable mob egg
minetest.register_craftitem(mob_id, { minetest.register_craftitem(mob_id, {
description = desc, description = desc,
inventory_image = invimg, inventory_image = invimg,
groups = grp, groups = grp,
@ -589,9 +575,6 @@ function mcl_mobs.register_egg(mob_id, desc, background_color, overlay_color, ad
local dim = mcl_worlds.pos_to_dimension(placer:get_pos()) local dim = mcl_worlds.pos_to_dimension(placer:get_pos())
local mob_light_lvl = {mcl_mobs:mob_light_lvl(itemstack:get_name(),dim)} local mob_light_lvl = {mcl_mobs:mob_light_lvl(itemstack:get_name(),dim)}
--minetest.log("min light: " .. mob_light_lvl[1])
--minetest.log("max light: " .. mob_light_lvl[2])
-- Handle egg conversion -- Handle egg conversion
local convert_to = (minetest.registered_entities[mob_name] or {})._convert_to local convert_to = (minetest.registered_entities[mob_name] or {})._convert_to
if convert_to then mob_name = convert_to end if convert_to then mob_name = convert_to end
@ -603,9 +586,7 @@ function mcl_mobs.register_egg(mob_id, desc, background_color, overlay_color, ad
return itemstack return itemstack
end end
if not minetest.registered_entities[mob_name] then if not minetest.registered_entities[mob_name] then return itemstack end
return itemstack
end
if minetest.settings:get_bool("only_peaceful_mobs", false) if minetest.settings:get_bool("only_peaceful_mobs", false)
and minetest.registered_entities[mob_name].type == "monster" then and minetest.registered_entities[mob_name].type == "monster" then
@ -615,7 +596,7 @@ function mcl_mobs.register_egg(mob_id, desc, background_color, overlay_color, ad
pos.y = pos.y - 1 pos.y = pos.y - 1
local mob = mcl_mobs.spawn(pos, mob_name) local mob = mcl_mobs.spawn(pos, mob_name)
if not object then if not mob then
pos.y = pos.y + 1 pos.y = pos.y + 1
mob = mcl_mobs.spawn(pos, mob_name) mob = mcl_mobs.spawn(pos, mob_name)
if not mob then return end if not mob then return end

View File

@ -1,4 +1,4 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs local minetest, mcl_mobs = minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class local mob_class = mcl_mobs.mob_class
--- Item and armor management --- Item and armor management

View File

@ -1,5 +1,5 @@
name = mcl_mobs name = mcl_mobs
author = PilzAdam author = PilzAdam, kno10
description = Adds a mob API for mods to add animals or monsters, etc. description = Adds a mob API for mods to add animals or monsters, etc.
depends = mcl_particles, mcl_luck depends = mcl_particles, mcl_luck
optional_depends = mcl_weather, mcl_explosions, mcl_hunger, mcl_worlds, invisibility, lucky_block, cmi, doc_identifier, mcl_armor, mcl_portals, mcl_experience, mcl_sculk optional_depends = mcl_weather, mcl_explosions, mcl_hunger, mcl_worlds, invisibility, lucky_block, cmi, doc_identifier, mcl_armor, mcl_portals, mcl_experience, mcl_sculk

View File

@ -1,110 +1,41 @@
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
-- lib_mount by Blert2112 (edited by TenPlus1) -- based on lib_mount by Blert2112 (edited by TenPlus1)
local enable_crash = false local enable_crash = false
local crash_threshold = 6.5 -- ignored if enable_crash=false local crash_threshold = 6.5 -- ignored if enable_crash=false
local GRAVITY = -9.8
------------------------------------------------------------------------------ local node_ok = mcl_mobs.node_ok
local sign = math.sign -- minetest extension
--
-- Helper functions
--
local node_ok = function(pos, fallback)
fallback = fallback or mcl_mobs.fallback_node
local node = minetest.get_node_or_nil(pos)
if node and minetest.registered_nodes[node.name] then
return node
end
return {name = fallback}
end
local function node_is(pos) local function node_is(pos)
local node = node_ok(pos) local node = node_ok(pos)
if node.name == "air" then return "air" end
if node.name == "air" then local ndef = minetest.registered_nodes[node.name]
return "air" if not ndef then return "other" end -- unknown/ignore
end if ndef.groups.lava then return "lava" end
if ndef.groups.liquid then return "liquid" end
if minetest.get_item_group(node.name, "lava") ~= 0 then if ndef.walkable then return "walkable" end
return "lava"
end
if minetest.get_item_group(node.name, "liquid") ~= 0 then
return "liquid"
end
if minetest.registered_nodes[node.name].walkable == true then
return "walkable"
end
return "other" return "other"
end end
local function get_sign(i)
i = i or 0
if i == 0 then
return 0
else
return i / math.abs(i)
end
end
local function get_velocity(v, yaw, y)
local x = -math.sin(yaw) * v
local z = math.cos(yaw) * v
return {x = x, y = y, z = z}
end
local function get_v(v)
return math.sqrt(v.x * v.x + v.z * v.z)
end
local function force_detach(player) local function force_detach(player)
local attached_to = player:get_attach() local attached_to = player:get_attach()
if not attached_to then return end
if not attached_to then
return
end
local entity = attached_to:get_luaentity() local entity = attached_to:get_luaentity()
if entity.driver and entity.driver == player then entity.driver = nil end
if entity.driver
and entity.driver == player then
entity.driver = nil
end
player:set_detach() player:set_detach()
mcl_player.player_attached[player:get_player_name()] = false mcl_player.player_attached[player:get_player_name()] = false
player:set_eye_offset({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0}) player:set_eye_offset(vector.zero(), vector.zero())
mcl_player.player_set_animation(player, "stand" , 30) mcl_player.player_set_animation(player, "stand" , 30)
player:set_properties({visual_size = {x = 1, y = 1} }) player:set_properties({visual_size = {x = 1, y = 1} })
end end
------------------------------------------------------------------------------- minetest.register_on_leaveplayer(force_detach)
minetest.register_on_leaveplayer(function(player)
force_detach(player)
end)
minetest.register_on_shutdown(function() minetest.register_on_shutdown(function()
local players = minetest.get_connected_players() local players = minetest.get_connected_players()
@ -118,39 +49,24 @@ minetest.register_on_dieplayer(function(player)
return true return true
end) end)
-------------------------------------------------------------------------------
function mcl_mobs.attach(entity, player) function mcl_mobs.attach(entity, player)
entity.player_rotation = entity.player_rotation or vector.zero()
local attach_at, eye_offset entity.driver_attach_at = entity.driver_attach_at or vector.zero()
entity.driver_eye_offset = entity.driver_eye_offset or vector.zero()
entity.player_rotation = entity.player_rotation or {x = 0, y = 0, z = 0}
entity.driver_attach_at = entity.driver_attach_at or {x = 0, y = 0, z = 0}
entity.driver_eye_offset = entity.driver_eye_offset or {x = 0, y = 0, z = 0}
entity.driver_scale = entity.driver_scale or {x = 1, y = 1} entity.driver_scale = entity.driver_scale or {x = 1, y = 1}
local rot_view = 0 local rot_view = entity.player_rotation.y == 90 and math.pi/2 or 0
local attach_at = entity.driver_attach_at
if entity.player_rotation.y == 90 then local eye_offset = entity.driver_eye_offset
rot_view = math.pi/2
end
attach_at = entity.driver_attach_at
eye_offset = entity.driver_eye_offset
entity.driver = player entity.driver = player
force_detach(player) force_detach(player)
player:set_attach(entity.object, "", attach_at, entity.player_rotation) player:set_attach(entity.object, "", attach_at, entity.player_rotation)
mcl_player.player_attached[player:get_player_name()] = true mcl_player.player_attached[player:get_player_name()] = true
player:set_eye_offset(eye_offset, {x = 0, y = 0, z = 0}) player:set_eye_offset(eye_offset, vector.zero())
player:set_properties({ player:set_properties({ visual_size = entity.driver_scale })
visual_size = {
x = entity.driver_scale.x,
y = entity.driver_scale.y
}
})
minetest.after(0.2, function(name) minetest.after(0.2, function(name)
local player = minetest.get_player_by_name(name) local player = minetest.get_player_by_name(name)
@ -164,162 +80,88 @@ end
function mcl_mobs.detach(player, offset) function mcl_mobs.detach(player, offset)
force_detach(player) force_detach(player)
mcl_player.player_set_animation(player, "stand" , 30) mcl_player.player_set_animation(player, "stand" , 30)
player:add_velocity(vector.new(math.random()*12-6,math.random()*3+5,math.random()*12-6)) --throw the rider off
--local pos = player:get_pos()
--pos = {x = pos.x + offset.x, y = pos.y + 0.2 + offset.y, z = pos.z + offset.z}
player:add_velocity(vector.new(math.random(-6,6),math.random(5,8),math.random(-6,6))) --throw the rider off
--[[
minetest.after(0.1, function(name, pos)
local player = minetest.get_player_by_name(name)
if player then
player:set_pos(pos)
end
end, player:get_player_name(), pos)
]]--
end end
function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime) function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
local rot_view = 0
if entity.player_rotation.y == 90 then
rot_view = math.pi/2
end
local acce_y = 0
local velo = entity.object:get_velocity() local velo = entity.object:get_velocity()
local v = math.sqrt(velo.x * velo.x + velo.y * velo.y)
entity.v = get_v(velo) * get_sign(entity.v) local acce_y = GRAVITY
-- process controls -- process controls
if entity.driver then if entity.driver then
local ctrl = entity.driver:get_player_control() local ctrl = entity.driver:get_player_control()
if ctrl.up then -- forward
-- move forwards v = v + entity.accel * 0.1 * entity.run_velocity * 0.385
if ctrl.up then elseif ctrl.down then -- backwards
if entity.max_speed_reverse == 0 and v == 0 then return end
entity.v = entity.v + entity.accel / 10 * entity.run_velocity / 2.6 v = v - entity.accel * 0.1 * entity.run_velocity * 0.385
-- move backwards
elseif ctrl.down then
if entity.max_speed_reverse == 0 and entity.v == 0 then
return
end
entity.v = entity.v - entity.accel / 10
end end
-- fix mob rotation entity:set_yaw(entity.driver:get_look_horizontal() - entity.rotate, 2)
entity.object:set_yaw(entity.driver:get_look_horizontal() - entity.rotate)
if can_fly then if can_fly then
-- FIXME: use acce_y instead?
-- fly up -- fly up
if ctrl.jump then if ctrl.jump then
velo.y = velo.y + 1 velo.y = math.min(velo.y + 1, entity.accel)
if velo.y > entity.accel then velo.y = entity.accel end elseif velo.y > 0.1 then
elseif velo.y > 0 then
velo.y = velo.y - 0.1 velo.y = velo.y - 0.1
if velo.y < 0 then velo.y = 0 end elseif velo.y > 0 then
velo.y = 0
end end
-- fly down -- fly down
if ctrl.sneak then if ctrl.sneak then
velo.y = velo.y - 1 velo.y = math.max(velo.y - 1, -entity.accel)
if velo.y < -entity.accel then velo.y = -entity.accel end elseif velo.y < -0.1 then
elseif velo.y < 0 then
velo.y = velo.y + 0.1 velo.y = velo.y + 0.1
if velo.y > 0 then velo.y = 0 end elseif velo.y < 0 then
velo.y = 0
end end
else else
-- jump -- jump
if ctrl.jump then if ctrl.jump then
if velo.y == 0 then if velo.y == 0 then
velo.y = velo.y + entity.jump_height velo.y = velo.y + math.sqrt(entity.jump_height * 20)
acce_y = acce_y + (acce_y * 3) + 1 acce_y = acce_y + 1
end end
end end
end end
end end
-- Stop! if math.abs(v) < 0.02 then -- stop
local s = get_sign(entity.v) entity.object:set_velocity(vector.zero())
v = 0
entity.v = entity.v - 0.02 * s else
v = v - 0.02 * sign(v) -- slow down
if s ~= get_sign(entity.v) then
entity.object:set_velocity({x = 0, y = 0, z = 0})
entity.v = 0
return
end end
-- if not moving then set animation and return -- if not moving then set animation and return
if entity.v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then if v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then
entity:set_animation(stand_anim)
if stand_anim then
mcl_mobs:set_animation(entity, stand_anim)
end
return return
end else
entity:set_animation(moving_anim)
-- set moving animation
if moving_anim then
mcl_mobs:set_animation(entity, moving_anim)
end end
-- enforce speed limit forward and reverse -- enforce speed limit forward and reverse
local max_spd = entity.max_speed_reverse v = math.max(-entity.max_speed_reverse, math.min(v, entity.max_speed_forward))
if get_sign(entity.v) >= 0 then
max_spd = entity.max_speed_forward
end
if math.abs(entity.v) > max_spd then
entity.v = entity.v - get_sign(entity.v)
end
-- Set position, velocity and acceleration -- Set position, velocity and acceleration
local p = entity.object:get_pos() local p = entity.object:get_pos()
local new_velo
local new_acce = {x = 0, y = -9.8, z = 0}
p.y = p.y - 0.5 p.y = p.y - 0.5
local ni = node_is(p) local ni = node_is(p)
local v = entity.v
if ni == "air" then if ni == "air" then
if can_fly then acce_y = acce_y - GRAVITY end
if can_fly == true then
new_acce.y = 0
end
elseif ni == "liquid" or ni == "lava" then elseif ni == "liquid" or ni == "lava" then
if ni == "lava" and entity.lava_damage ~= 0 then if ni == "lava" and entity.lava_damage ~= 0 then
entity.lava_counter = (entity.lava_counter or 0) + dtime entity.lava_counter = (entity.lava_counter or 0) + dtime
if entity.lava_counter > 1 then if entity.lava_counter > 1 then
minetest.sound_play("default_punch", { minetest.sound_play("default_punch", {
object = entity.object, object = entity.object,
max_hear_distance = 5 max_hear_distance = 5
@ -336,18 +178,15 @@ function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
if entity.terrain_type == 2 if entity.terrain_type == 2
or entity.terrain_type == 3 then or entity.terrain_type == 3 then
acce_y = 0
new_acce.y = 0
p.y = p.y + 1 p.y = p.y + 1
if node_is(p) == "liquid" then if node_is(p) == "liquid" then
if velo.y >= 5 then if velo.y >= 5 then
velo.y = 5 velo.y = 5
elseif velo.y < 0 then elseif velo.y < 0 then
new_acce.y = 20 acce_y = 20
else else
new_acce.y = 5 acce_y = 5
end end
else else
if math.abs(velo.y) < 1 then if math.abs(velo.y) < 1 then
@ -362,75 +201,51 @@ function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
end end
end end
new_velo = get_velocity(v, entity.object:get_yaw() - rot_view, velo.y) local rot_view = entity.player_rotation.y == 90 and math.pi/2 or 0
new_acce.y = new_acce.y + acce_y local new_yaw = entity.object:get_yaw() - rot_view
local new_velo = vector.new(-math.sin(new_yaw) * v, velo.y, math.cos(new_yaw) * v)
entity.object:set_velocity(new_velo) entity.object:set_velocity(new_velo)
entity.object:set_acceleration(new_acce) entity.object:set_acceleration(vector.new(0, acce_y, 0))
-- CRASH!
if enable_crash then if enable_crash then
if v >= crash_threshold then
local intensity = entity.v2 - v
if intensity >= crash_threshold then
entity.object:punch(entity.object, 1.0, { entity.object:punch(entity.object, 1.0, {
full_punch_interval = 1.0, full_punch_interval = 1.0,
damage_groups = {fleshy = intensity} damage_groups = {fleshy = v}
}, nil) }, nil)
end end
end end
entity.v2 = v
end end
-- directional flying routine by D00Med (edited by TenPlus1) -- directional flying routine by D00Med (edited by TenPlus1)
function mcl_mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_anim) function mcl_mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_anim)
local ctrl = entity.driver:get_player_control() local ctrl = entity.driver:get_player_control()
local velo = entity.object:get_velocity() local velo = entity.object:get_velocity()
local dir = entity.driver:get_look_dir() local dir = entity.driver:get_look_dir()
local yaw = entity.driver:get_look_horizontal() + 1.57 -- offset fix between old and new commands local yaw = entity.driver:get_look_horizontal()
if ctrl.up then if ctrl.up then
entity.object:set_velocity({ entity.object:set_velocity(vector.new(dir.x * speed, dir.y * speed + 2, dir.z * speed))
x = dir.x * speed,
y = dir.y * speed + 2,
z = dir.z * speed
})
elseif ctrl.down then elseif ctrl.down then
entity.object:set_velocity({ entity.object:set_velocity(vector.new(-dir.x * speed, dir.y * speed + 2, -dir.z * speed))
x = -dir.x * speed,
y = dir.y * speed + 2,
z = -dir.z * speed
})
elseif not ctrl.down or ctrl.up or ctrl.jump then elseif not ctrl.down or ctrl.up or ctrl.jump then
entity.object:set_velocity({x = 0, y = -2, z = 0}) entity.object:set_velocity(vector.new(0, -2, 0))
end end
entity.object:set_yaw(yaw + math.pi + math.pi / 2 - entity.rotate) entity:set_yaw(yaw - entity.rotate, 2)
-- firing arrows -- firing arrows
if ctrl.LMB and ctrl.sneak and shoots then if ctrl.LMB and ctrl.sneak and shoots then
local pos = entity.object:get_pos() local pos = entity.object:get_pos()
local obj = minetest.add_entity({ local obj = minetest.add_entity(vector.offset(pos, dir.x * 2.5, 1.5 + dir.y, dir.z * 2.5), arrow)
x = pos.x + 0 + dir.x * 2.5,
y = pos.y + 1.5 + dir.y,
z = pos.z + 0 + dir.z * 2.5}, arrow)
local ent = obj:get_luaentity() local ent = obj:get_luaentity()
if ent then if ent then
ent.switch = 1 -- for mob specific arrows ent.switch = 1 -- for mob specific arrows
ent.owner_id = tostring(entity.object) -- so arrows dont hurt entity you are riding ent.owner_id = tostring(entity.object) -- so arrows dont hurt entity you are riding
local vec = {x = dir.x * 6, y = dir.y * 6, z = dir.z * 6} local vec = vector.new(dir.x * 6, dir.y * 6, dir.z * 6)
local yaw = entity.driver:get_look_horizontal() local yaw = entity.driver:get_look_horizontal()
obj:set_yaw(yaw + math.pi / 2) obj:set_yaw(yaw)
obj:set_velocity(vec) obj:set_velocity(vec)
else else
obj:remove() obj:remove()
@ -439,11 +254,9 @@ function mcl_mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_an
-- change animation if stopped -- change animation if stopped
if velo.x == 0 and velo.y == 0 and velo.z == 0 then if velo.x == 0 and velo.y == 0 and velo.z == 0 then
entity:set_animation(stand_anim)
mcl_mobs:set_animation(entity, stand_anim)
else else
-- moving animation entity:set_animation(moving_anim)
mcl_mobs:set_animation(entity, moving_anim)
end end
end end
@ -452,12 +265,7 @@ mcl_mobs.mob_class.fly = mcl_mobs.fly
mcl_mobs.mob_class.attach = mcl_mobs.attach mcl_mobs.mob_class.attach = mcl_mobs.attach
function mob_class:on_detach_child(child) function mob_class:on_detach_child(child)
if self.detach_child then if self.detach_child and self.detach_child(self, child) then return end
if self.detach_child(self, child) then if self.driver == child then self.driver = nil end
return
end
end
if self.driver == child then
self.driver = nil
end
end end

File diff suppressed because it is too large Load Diff

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)
@ -42,8 +40,8 @@ function append_paths (wp1, wp2)
mcl_log("Cannot append wp's") mcl_log("Cannot append wp's")
return return
end end
output_table(wp1) --output_table(wp1)
output_table(wp2) --output_table(wp2)
for _,a in pairs (wp2) do for _,a in pairs (wp2) do
table.insert(wp1, a) table.insert(wp1, a)
end end
@ -51,18 +49,13 @@ function append_paths (wp1, wp2)
end end
local function output_enriched (wp_out) local function output_enriched (wp_out)
mcl_log("Output enriched path") --mcl_log("Output enriched path")
local i = 0 local i = 0
for _,outy in pairs (wp_out) do for _,outy in pairs (wp_out) do
i = i + 1 i = i + 1
mcl_log("Pos ".. i ..":" .. minetest.pos_to_string(outy["pos"]))
local action = outy["action"] local action = outy["action"]
if action then if action then
--mcl_log("Pos ".. i ..":" .. minetest.pos_to_string(outy["pos"])) mcl_log("Pos ".. i ..":" .. minetest.pos_to_string(outy["pos"])..", type: " .. action["type"]..", action: " .. action["action"]..", target: " .. minetest.pos_to_string(action["target"]))
mcl_log("type: " .. action["type"])
mcl_log("action: " .. action["action"])
mcl_log("target: " .. minetest.pos_to_string(action["target"]))
end end
--mcl_log("failed attempts: " .. outy["failed_attempts"]) --mcl_log("failed attempts: " .. outy["failed_attempts"])
end end
@ -73,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
@ -113,93 +95,82 @@ end
local last_pathing_time = os.time() local last_pathing_time = os.time()
function mob_class:ready_to_path(prioritised) function mob_class:ready_to_path(prioritised)
mcl_log("Check ready to path") -- mcl_log("Check ready to path")
if self._pf_last_failed and (os.time() - self._pf_last_failed) < PATHFINDING_FAIL_WAIT then if self._pf_last_failed and (os.time() - self._pf_last_failed) < PATHFINDING_FAIL_WAIT then
mcl_log("Not ready to path as last fail is less than threshold: " .. (os.time() - self._pf_last_failed)) -- mcl_log("Not ready to path as last fail is less than threshold: " .. (os.time() - self._pf_last_failed))
return false return false
else else
local time_since_path_start = os.time() - last_pathing_time local time_since_path_start = os.time() - last_pathing_time
mcl_log("time_since_path_start: " .. tostring(time_since_path_start))
if prioritised or (time_since_path_start) > PATHING_START_DELAY then if prioritised or (time_since_path_start) > PATHING_START_DELAY then
mcl_log("We are ready to pathfind, no previous fail or we are past threshold") mcl_log("We are ready to pathfind, no previous fail or we are past threshold: "..tostring(time_since_path_start))
return true return true
end end
mcl_log("time_since_path_start: " .. tostring(time_since_path_start))
end end
end 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
@ -209,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)
@ -218,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
@ -236,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
@ -268,26 +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
local current_location = table.remove(wp,1) self.current_target = table.remove(wp,1)
if current_location and current_location["pos"] then while self.current_target and self.current_target.pos and vector.distance(p, self.current_target.pos) < 0.5 do
mcl_log("Removing first co-ord? " .. tostring(current_location["pos"])) --mcl_log("Skipping close initial waypoint")
else self.current_target = table.remove(wp,1)
mcl_log("Nil pos") 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.current_target = current_location
self.waypoints = wp
self.state = PATHFINDING
return true
else
self.state = "walk"
self.waypoints = nil
self.current_target = nil
-- minetest.log("no path found")
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)
@ -300,19 +352,27 @@ function mob_class:interact_with_door(action, target)
local n = minetest.get_node(target) local n = minetest.get_node(target)
if n.name:find("_b_") or n.name:find("_t_") then if n.name:find("_b_") or n.name:find("_t_") then
mcl_log("Door")
local def = minetest.registered_nodes[n.name] local def = minetest.registered_nodes[n.name]
local closed = n.name:find("_b_1") or n.name:find("_t_1") local meta = minetest.get_meta(target)
--if self.state == PATHFINDING then local closed = meta:get_int("is_open") == 0
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
--else 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
@ -333,6 +393,7 @@ function mob_class:do_pathfind_action(action)
end end
if type and type == "door" then if type and type == "door" then
mcl_log("Type is door") mcl_log("Type is door")
self.object:set_velocity(vector.zero())
self:interact_with_door(action_val, target) self:interact_with_door(action_val, target)
end end
end end
@ -343,8 +404,7 @@ function mob_class:check_gowp(dtime)
-- no destination -- no destination
if not p or not self._target then if not p or not self._target then
mcl_log("p: ".. tostring(p)) mcl_log("p: ".. tostring(p)..", self._target: ".. tostring(self._target))
mcl_log("self._target: ".. tostring(self._target))
return return
end end
@ -358,8 +418,8 @@ function mob_class:check_gowp(dtime)
self.current_target = nil self.current_target = nil
self.state = "stand" self.state = "stand"
self.order = "stand" self.order = "stand"
self.object:set_velocity({x = 0, y = 0, z = 0}) self.object:set_velocity(vector.zero())
self.object:set_acceleration({x = 0, y = 0, z = 0}) self.object:set_acceleration(vector.zero())
if self.callback_arrived then return self.callback_arrived(self) end if self.callback_arrived then return self.callback_arrived(self) end
return true return true
elseif not self.current_target then elseif not self.current_target then
@ -368,41 +428,67 @@ 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
distance_to_current_target = vector.distance(p,self.current_target["pos"]) 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.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 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
if self.waypoints and #self.waypoints > 0 and ( not self.current_target or not self.current_target["pos"] or distance_to_current_target < 0.9 ) 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. -- 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)
self:go_to_pos(self.current_target["pos"]) -- 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, 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. Abandon route. Times tried: " .. failed_attempts) 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
self._target = nil self._target = nil
self._pf_last_failed = os.time() self._pf_last_failed = os.time()
self.object:set_velocity({x = 0, y = 0, z = 0}) self.object:set_velocity(vector.zero())
self.object:set_acceleration({x = 0, y = 0, z = 0}) self.object:set_acceleration(vector.zero())
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"])
-- 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
@ -436,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"])
@ -444,7 +531,7 @@ function mob_class:check_gowp(dtime)
mcl_log("Current p: ".. minetest.pos_to_string(updated_p)) mcl_log("Current p: ".. minetest.pos_to_string(updated_p))
-- 1.6 is good. is 1.9 better? It could fail less, but will it path to door when it isn't after door -- 1.6 is good. is 1.9 better? It could fail less, but will it path to door when it isn't after door
if distance_to_cur_targ > 1.9 then if distance_to_cur_targ > 1.6 then
mcl_log("not close to current target: ".. minetest.pos_to_string(self.current_target["pos"])) mcl_log("not close to current target: ".. minetest.pos_to_string(self.current_target["pos"]))
self:go_to_pos(self._current_target) self:go_to_pos(self._current_target)
else else
@ -454,4 +541,5 @@ function mob_class:check_gowp(dtime)
end end
return return
end end
--]]--
end end

File diff suppressed because it is too large Load Diff

View File

@ -72,18 +72,24 @@ 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 = true, 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",
attack_animals = true, attack_animals = true,
specific_attack = { specific_attack = {
"extra_mobs_cod", "mobs_mc:cod",
"extra_mobs_glow_squid", "mobs_mc:glow_squid",
"extra_mobs_salmon", "mobs_mc:salmon",
"extra_mobs_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

@ -83,7 +83,7 @@ mcl_mobs.register_mob("mobs_mc:blaze", {
shoot_offset = 1.0, shoot_offset = 1.0,
passive = false, passive = false,
jump = true, jump = true,
jump_height = 4, jump_height = 1,
fly = true, fly = true,
makes_footstep_sound = false, makes_footstep_sound = false,
fear_height = 0, fear_height = 0,

View File

@ -22,7 +22,7 @@ mcl_mobs.register_mob("mobs_mc:chicken", {
floats = 1, floats = 1,
head_swivel = "head.control", head_swivel = "head.control",
bone_eye_height = 4, bone_eye_height = 4,
head_eye_height = 1.5, head_eye_height = .5,
horizontal_head_height = -.3, horizontal_head_height = -.3,
curiosity = 10, curiosity = 10,
head_yaw="z", head_yaw="z",

View File

@ -82,7 +82,7 @@ local cod = {
do_custom = function(self) do_custom = function(self)
--[[ this is supposed to make them jump out the water but doesn't appear to work very well --[[ this is supposed to make them jump out the water but doesn't appear to work very well
self.object:set_bone_position("body", vector.new(0,1,0), vector.new(degrees(dir_to_pitch(self.object:get_velocity())) * -1 + 90,0,0)) self.object:set_bone_position("body", vector.new(0,1,0), vector.new(degrees(dir_to_pitch(self.object:get_velocity())) * -1 + 90,0,0))
if minetest.get_item_group(self.standing_in, "water") ~= 0 then if self.standing_in.groups.water then
if self.object:get_velocity().y < 5 then if self.object:get_velocity().y < 5 then
self.object:add_velocity({ x = 0 , y = math.random()*.014-.007, z = 0 }) self.object:add_velocity({ x = 0 , y = math.random()*.014-.007, z = 0 })
end end

View File

@ -84,7 +84,7 @@ local dolphin = {
--[[ this is supposed to make them jump out the water but doesn't appear to work very well --[[ this is supposed to make them jump out the water but doesn't appear to work very well
do_custom = function(self,dtime) do_custom = function(self,dtime)
self.object:set_bone_position("body", vector.new(0,1,0), vector.new(degrees(dir_to_pitch(self.object:get_velocity())) * -1 + 90,0,0)) self.object:set_bone_position("body", vector.new(0,1,0), vector.new(degrees(dir_to_pitch(self.object:get_velocity())) * -1 + 90,0,0))
if minetest.get_item_group(self.standing_in, "water") ~= 0 then if self.standing_in.groups.water then
if self.object:get_velocity().y < 5 then if self.object:get_velocity().y < 5 then
self.object:add_velocity({ x = 0 , y = math.random()*.014-.007, z = 0 }) self.object:add_velocity({ x = 0 , y = math.random()*.014-.007, z = 0 })
end end

View File

@ -77,7 +77,7 @@ mcl_mobs.register_mob("mobs_mc:enderdragon", {
damage = 10, damage = 10,
knock_back = false, knock_back = false,
jump = true, jump = true,
jump_height = 14, jump_height = 10,
fly = true, fly = true,
makes_footstep_sound = false, makes_footstep_sound = false,
dogshoot_switch = 1, dogshoot_switch = 1,

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

@ -52,6 +52,7 @@ mcl_mobs.register_mob("mobs_mc:ghast", {
fall_damage = 0, fall_damage = 0,
view_range = 64, view_range = 64,
attack_type = "dogshoot", attack_type = "dogshoot",
see_through_opaque = false,
arrow = "mobs_mc:fireball", arrow = "mobs_mc:fireball",
shoot_interval = 5, shoot_interval = 5,
shoot_offset = -0.5, shoot_offset = -0.5,
@ -59,7 +60,7 @@ mcl_mobs.register_mob("mobs_mc:ghast", {
dogshoot_count_max =1, dogshoot_count_max =1,
passive = false, passive = false,
jump = true, jump = true,
jump_height = 4, jump_height = 1,
floats=1, floats=1,
fly = true, fly = true,
makes_footstep_sound = false, makes_footstep_sound = false,

View File

@ -158,7 +158,7 @@ local horse = {
floats = 1, floats = 1,
makes_footstep_sound = true, makes_footstep_sound = true,
jump = true, jump = true,
jump_height = 5.75, -- can clear 2.5 blocks jump_height = 2.5, -- can clear 2.5 blocks
drops = { drops = {
{name = "mcl_mobitems:leather", {name = "mcl_mobitems:leather",
chance = 1, chance = 1,
@ -560,7 +560,7 @@ donkey.collisionbox = {
horse.collisionbox[6] * d, horse.collisionbox[6] * d,
} }
donkey.jump = true donkey.jump = true
donkey.jump_height = 3.75 -- can clear 1 block height donkey.jump_height = 1 -- can clear 1 block height
mcl_mobs.register_mob("mobs_mc:donkey", donkey) mcl_mobs.register_mob("mobs_mc:donkey", donkey)

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

@ -83,7 +83,6 @@ mcl_mobs.register_mob("mobs_mc:shulker", {
local pos = self.object:get_pos() local pos = self.object:get_pos()
if math.floor(self.object:get_yaw()) ~=0 then if math.floor(self.object:get_yaw()) ~=0 then
self.object:set_yaw(0) self.object:set_yaw(0)
mcl_mobs:yaw(self, 0, 0, dtime)
end end
if self.state == "attack" then if self.state == "attack" then
self:set_animation("run") self:set_animation("run")

View File

@ -134,7 +134,7 @@ local slime_big = {
walk_velocity = 1.9, walk_velocity = 1.9,
run_velocity = 1.9, run_velocity = 1.9,
walk_chance = 0, walk_chance = 0,
jump_height = 5.2, jump_height = 1,
fear_height = 0, fear_height = 0,
spawn_small_alternative = "mobs_mc:slime_small", spawn_small_alternative = "mobs_mc:slime_small",
on_die = spawn_children_on_die("mobs_mc:slime_small", 1.0, 1.5), on_die = spawn_children_on_die("mobs_mc:slime_small", 1.0, 1.5),
@ -156,7 +156,6 @@ slime_small.damage = 3
slime_small.reach = 2.25 slime_small.reach = 2.25
slime_small.walk_velocity = 1.8 slime_small.walk_velocity = 1.8
slime_small.run_velocity = 1.8 slime_small.run_velocity = 1.8
slime_small.jump_height = 4.3
slime_small.spawn_small_alternative = "mobs_mc:slime_tiny" slime_small.spawn_small_alternative = "mobs_mc:slime_tiny"
slime_small.on_die = spawn_children_on_die("mobs_mc:slime_tiny", 0.6, 1.0) slime_small.on_die = spawn_children_on_die("mobs_mc:slime_tiny", 0.6, 1.0)
mcl_mobs.register_mob("mobs_mc:slime_small", slime_small) mcl_mobs.register_mob("mobs_mc:slime_small", slime_small)
@ -181,7 +180,6 @@ slime_tiny.drops = {
} }
slime_tiny.walk_velocity = 1.7 slime_tiny.walk_velocity = 1.7
slime_tiny.run_velocity = 1.7 slime_tiny.run_velocity = 1.7
slime_tiny.jump_height = 3
slime_tiny.spawn_small_alternative = nil slime_tiny.spawn_small_alternative = nil
slime_tiny.on_die = nil slime_tiny.on_die = nil
@ -363,7 +361,7 @@ local magma_cube_big = {
attack_type = "dogfight", attack_type = "dogfight",
passive = false, passive = false,
jump = true, jump = true,
jump_height = 8, jump_height = 4,
walk_chance = 0, walk_chance = 0,
fear_height = 0, fear_height = 0,
spawn_small_alternative = "mobs_mc:magma_cube_small", spawn_small_alternative = "mobs_mc:magma_cube_small",
@ -386,7 +384,7 @@ magma_cube_small.damage = 3
magma_cube_small.reach = 2.1 magma_cube_small.reach = 2.1
magma_cube_small.walk_velocity = .8 magma_cube_small.walk_velocity = .8
magma_cube_small.run_velocity = 2.0 magma_cube_small.run_velocity = 2.0
magma_cube_small.jump_height = 6 magma_cube_small.jump_height = 2
magma_cube_small.damage = 4 magma_cube_small.damage = 4
magma_cube_small.reach = 2.75 magma_cube_small.reach = 2.75
magma_cube_small.armor = 66 magma_cube_small.armor = 66
@ -407,7 +405,7 @@ magma_cube_tiny.collisionbox = {-0.2505, -0.01, -0.2505, 0.2505, 0.50, 0.2505, r
magma_cube_tiny.visual_size = {x=3.125, y=3.125} magma_cube_tiny.visual_size = {x=3.125, y=3.125}
magma_cube_tiny.walk_velocity = 1.02 magma_cube_tiny.walk_velocity = 1.02
magma_cube_tiny.run_velocity = 1.02 magma_cube_tiny.run_velocity = 1.02
magma_cube_tiny.jump_height = 4 magma_cube_tiny.jump_height = 1
magma_cube_tiny.damage = 3 magma_cube_tiny.damage = 3
magma_cube_tiny.reach = 2 magma_cube_tiny.reach = 2
magma_cube_tiny.armor = 50 magma_cube_tiny.armor = 50

View File

@ -86,7 +86,7 @@ local spider = {
walk_velocity = 1.3, walk_velocity = 1.3,
run_velocity = 2.4, run_velocity = 2.4,
jump = true, jump = true,
jump_height = 4, jump_height = 1,
view_range = 16, view_range = 16,
floats = 1, floats = 1,
drops = { drops = {

View File

@ -2,17 +2,35 @@
local S = minetest.get_translator("mobs_mc") local S = minetest.get_translator("mobs_mc")
--################### -- foliage and grass palettes, loaded from mcl_maps
--################### STALKER local palettes = {}
--###################
local function load_json_file(name)
local file = assert(io.open(name, "r"))
local data = minetest.parse_json(file:read("*all"))
file:close()
return data
end
local mapmodpath = minetest.get_modpath("mcl_maps")
if mapmodpath then
for k,v in pairs(load_json_file(mapmodpath .. "/palettes_grass.json")) do
palettes[k] = v
end
for k,v in pairs(load_json_file(mapmodpath .. "/palettes_foliage.json")) do
palettes[k] = v
end
for k,v in pairs(load_json_file(mapmodpath .. "/palettes_water.json")) do
palettes[k] = v
end
end
local function get_texture(self) local function get_texture(self, prev)
local on_name = self.standing_on local standing_on = self.standing_on
local texture local texture
local texture_suff = "" local texture_suff = ""
if on_name and on_name ~= "air" then if standing_on and (standing_on.walkable or standing_on.groups.liquid) then
local tiles = minetest.registered_nodes[on_name].tiles local tiles = standing_on.tiles
if tiles then if tiles then
local tile = tiles[1] local tile = tiles[1]
local color local color
@ -25,22 +43,49 @@ local function get_texture(self)
texture = tile texture = tile
end end
if not color then if not color then
color = minetest.colorspec_to_colorstring(minetest.registered_nodes[on_name].color) color = minetest.colorspec_to_colorstring(standing_on.color)
end
-- handle param2
if standing_on.palette and self.standing_on_node then
local param2
if standing_on.paramtype2 == "color" then
param2 = self.standing_on_node.param2
elseif standing_on.paramtype2 == "colorfacedir" then
param2 = math.floor(self.standing_on_node.param2 / 8)
elseif standing_on.paramtype2 == "colorwallmounted" then
param2 = math.floor(self.standing_on_node.param2 / 32)
elseif standing_on.paramtype2 == "color4dir" then
param2 = math.floor(self.standing_on_node.param2 / 64)
elseif standing_on.paramtype2 == "colordegrotate" then
param2 = math.floor(self.standing_on_node.param2 / 8)
end
local palette = palettes[standing_on.palette]
local oldcol = color
if param2 and palette then
local c = palette[param2 + 1]
if c then color = minetest.rgba(c[1], c[2], c[3], c[4]) end
end
end end
if color then if color then
texture_suff = "^[multiply:" .. color .. "^[hsl:0:0:20" texture_suff = "^[multiply:" .. color .. "^[contrast:20:10" --"^[hsl:0:0:20"
end end
end end
end end
if not texture or texture == "" then if not texture or texture == "" then
-- try to keep last texture when, e.g., falling
if prev and (not (not self.attack)) == (string.find(prev, "vl_mobs_starker_overlay_angry.png") ~= nil) then
return prev
end
texture = "vl_stalker_default.png" texture = "vl_stalker_default.png"
end if texture_suff then texture = texture .. texture_suff end
texture = texture:gsub("([\\^:\\[])","\\%1") -- escape texture modifiers
texture = "([combine:16x24:0,0=(" .. texture .. "):0,16=(" .. texture ..")".. texture_suff
if self.attack then
texture = texture .. ")^vl_mobs_stalker_overlay_angry.png"
else else
texture = texture .. ")^vl_mobs_stalker_overlay.png" texture = texture:gsub("([\\^:\\[])", "\\%1") -- escape texture modifiers
texture = "(vl_stalker_default.png^[combine:16x24:0,0=(" .. texture .. "):0,16=(" .. texture .. ")" .. texture_suff .. ")"
end
if self.attack then
texture = texture .. "^vl_mobs_stalker_overlay_angry.png"
else
texture = texture .. "^vl_mobs_stalker_overlay.png"
end end
return texture return texture
end end
@ -98,6 +143,7 @@ mcl_mobs.register_mob("mobs_mc:stalker", {
explosion_damage_radius = 3.5, explosion_damage_radius = 3.5,
explosiontimer_reset_radius = 3, explosiontimer_reset_radius = 3,
reach = 3, reach = 3,
see_through_opaque = false,
explosion_timer = 1.5, explosion_timer = 1.5,
allow_fuse_reset = true, allow_fuse_reset = true,
stop_to_explode = true, stop_to_explode = true,
@ -132,7 +178,7 @@ mcl_mobs.register_mob("mobs_mc:stalker", {
self:boom(mcl_util.get_object_center(self.object), self.explosion_strength) self:boom(mcl_util.get_object_center(self.object), self.explosion_strength)
end end
end end
local new_texture = get_texture(self) local new_texture = get_texture(self, self._stalker_texture)
if self._stalker_texture ~= new_texture then if self._stalker_texture ~= new_texture then
self.object:set_properties({textures={new_texture, "mobs_mc_empty.png"}}) self.object:set_properties({textures={new_texture, "mobs_mc_empty.png"}})
self._stalker_texture = new_texture self._stalker_texture = new_texture

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

View File

@ -13,14 +13,6 @@ local anti_troll = minetest.settings:get_bool("wither_anti_troll_measures", fals
local WITHER_INIT_BOOM = 7 local WITHER_INIT_BOOM = 7
local WITHER_MELEE_COOLDOWN = 3 local WITHER_MELEE_COOLDOWN = 3
local function atan(x)
if not x or x ~= x then
return 0
else
return math.atan(x)
end
end
--################### --###################
--################### WITHER --################### WITHER
--################### --###################
@ -96,7 +88,7 @@ mcl_mobs.register_mob("mobs_mc:wither", {
distance = 60, distance = 60,
}, },
jump = true, jump = true,
jump_height = 10, jump_height = 5,
fly = true, fly = true,
makes_footstep_sound = false, makes_footstep_sound = false,
dogshoot_switch = 1, -- unused dogshoot_switch = 1, -- unused
@ -125,6 +117,7 @@ mcl_mobs.register_mob("mobs_mc:wither", {
}, },
harmed_by_heal = true, harmed_by_heal = true,
is_boss = true, is_boss = true,
see_through_opaque = false,
extra_hostile = true, extra_hostile = true,
attack_exception = function(p) attack_exception = function(p)
local ent = p:get_luaentity() local ent = p:get_luaentity()
@ -260,7 +253,7 @@ mcl_mobs.register_mob("mobs_mc:wither", {
z = p.z - s.z z = p.z - s.z
} }
local yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate local yaw = (math.atan2(vec.z, vec.x) +math.pi/ 2) - self.rotate
if p.x > s.x then yaw = yaw +math.pi end if p.x > s.x then yaw = yaw +math.pi end
yaw = self:set_yaw( yaw, 0, dtime) yaw = self:set_yaw( yaw, 0, dtime)

View File

@ -85,7 +85,7 @@ local zombie = {
fear_height = 4, fear_height = 4,
pathfinding = 1, pathfinding = 1,
jump = true, jump = true,
jump_height = 4, jump_height = 1,
group_attack = { "mobs_mc:zombie", "mobs_mc:baby_zombie", "mobs_mc:husk", "mobs_mc:baby_husk" }, group_attack = { "mobs_mc:zombie", "mobs_mc:baby_zombie", "mobs_mc:husk", "mobs_mc:baby_husk" },
drops = drops_zombie, drops = drops_zombie,
animation = { animation = {

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