Merge pull request 'grand mcl_mobs cleanup part 1' (#2925) from mobs_cleanup into master

Reviewed-on: https://git.minetest.land/MineClone2/MineClone2/pulls/2925
This commit is contained in:
cora 2022-11-27 13:57:35 +00:00
commit 7cc25c4670
63 changed files with 5361 additions and 5257 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,352 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
local MAX_MOB_NAME_LENGTH = 30
local HORNY_TIME = 30
local HORNY_AGAIN_TIME = 300
local CHILD_GROW_TIME = 60*20
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_villager",false)
local LOG_MODULE = "[mcl_mobs]"
local function mcl_log (message)
if LOGGING_ON and message then
minetest.log(LOG_MODULE .. " " .. message)
end
end
-- No-op in MCL2 (capturing mobs is not possible).
-- Provided for compability with Mobs Redo
function mcl_mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso, force_take, replacewith)
return false
end
-- No-op in MCL2 (protecting mobs is not possible).
function mcl_mobs:protect(self, clicker)
return false
end
-- feeding, taming and breeding (thanks blert2112)
function mob_class:feed_tame(clicker, feed_count, breed, tame, notake)
if not self.follow then
return false
end
-- can eat/tame with item in hand
if self.nofollow or self:follow_holding(clicker) then
local consume_food = false
-- tame if not still a baby
if tame and not self.child then
if not self.owner or self.owner == "" then
self.tamed = true
self.owner = clicker:get_player_name()
consume_food = true
end
end
-- increase health
if self.health < self.hp_max and not consume_food then
consume_food = true
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)
end
-- make children grow quicker
if not consume_food and self.child == true then
consume_food = true
-- deduct 10% of the time to adulthood
self.hornytimer = self.hornytimer + ((CHILD_GROW_TIME - self.hornytimer) * 0.1)
end
-- breed animals
if breed and not consume_food and self.hornytimer == 0 and not self.horny then
self.food = (self.food or 0) + 1
consume_food = true
if self.food >= feed_count then
self.food = 0
self.horny = true
end
end
self:update_tag()
-- play a sound if the animal used the item and take the item if not in creative
if consume_food then
-- don't consume food if clicker is in creative
if not minetest.is_creative_enabled(clicker:get_player_name()) and not notake then
local item = clicker:get_wielded_item()
item:take_item()
clicker:set_wielded_item(item)
end
-- always play the eat sound if food is used, even in creative
self:mob_sound("eat", nil, true)
else
-- make sound when the mob doesn't want food
self:mob_sound("random", true)
end
return true
end
return false
end
-- Spawn a child
function mcl_mobs.spawn_child(pos, mob_type)
local child = minetest.add_entity(pos, mob_type)
if not child then
return
end
local ent = child:get_luaentity()
mcl_mobs.effect(pos, 15, "mcl_particles_smoke.png", 1, 2, 2, 15, 5)
ent.child = true
local textures
-- using specific child texture (if found)
if ent.child_texture then
textures = ent.child_texture[1]
end
-- and resize to half height
child:set_properties({
textures = textures,
visual_size = {
x = ent.base_size.x * .5,
y = ent.base_size.y * .5,
},
collisionbox = {
ent.base_colbox[1] * .5,
ent.base_colbox[2] * .5,
ent.base_colbox[3] * .5,
ent.base_colbox[4] * .5,
ent.base_colbox[5] * .5,
ent.base_colbox[6] * .5,
},
selectionbox = {
ent.base_selbox[1] * .5,
ent.base_selbox[2] * .5,
ent.base_selbox[3] * .5,
ent.base_selbox[4] * .5,
ent.base_selbox[5] * .5,
ent.base_selbox[6] * .5,
},
})
ent.animation = ent._child_animations
ent._current_animation = nil
ent:set_animation("stand")
return child
end
-- find two animals of same type and breed if nearby and horny
function mob_class:check_breeding()
--mcl_log("In breed function")
-- child takes a long time before growing into adult
if self.child == true then
-- When a child, hornytimer is used to count age until adulthood
self.hornytimer = self.hornytimer + 1
if self.hornytimer >= CHILD_GROW_TIME then
self.child = false
self.hornytimer = 0
self.object:set_properties({
textures = self.base_texture,
mesh = self.base_mesh,
visual_size = self.base_size,
collisionbox = self.base_colbox,
selectionbox = self.base_selbox,
})
-- custom function when child grows up
if self.on_grown then
self.on_grown(self)
else
-- jump when fully grown so as not to fall into ground
self.object:set_velocity({
x = 0,
y = self.jump_height*3,
z = 0
})
end
self.animation = nil
local anim = self._current_animation
self._current_animation = nil -- Mobs Redo does nothing otherwise
self:set_animation(anim)
end
return
end
-- horny animal can mate for HORNY_TIME seconds,
-- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds
if self.horny == true
and self.hornytimer < HORNY_TIME + HORNY_AGAIN_TIME then
self.hornytimer = self.hornytimer + 1
if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then
self.hornytimer = 0
self.horny = false
end
end
-- find another same animal who is also horny and mate if nearby
if self.horny == true
and self.hornytimer <= HORNY_TIME then
mcl_log("In breed function. All good. Do the magic.")
local pos = self.object:get_pos()
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 num = 0
local ent = nil
for n = 1, #objs do
ent = objs[n]:get_luaentity()
-- check for same animal with different colour
local canmate = false
if ent then
if ent.name == self.name then
canmate = true
else
local entname = string.split(ent.name,":")
local selfname = string.split(self.name,":")
if entname[1] == selfname[1] then
entname = string.split(entname[2],"_")
selfname = string.split(selfname[2],"_")
if entname[1] == selfname[1] then
canmate = true
end
end
end
end
if canmate then mcl_log("In breed function. Can mate.") end
if ent
and canmate == true
and ent.horny == true
and ent.hornytimer <= HORNY_TIME then
num = num + 1
end
-- found your mate? then have a baby
if num > 1 then
self.hornytimer = HORNY_TIME + 1
ent.hornytimer = HORNY_TIME + 1
-- spawn baby
minetest.after(5, function(parent1, parent2, pos)
if not parent1.object:get_luaentity() then
return
end
if not parent2.object:get_luaentity() then
return
end
mcl_experience.throw_xp(pos, math.random(1, 7))
-- custom breed function
if parent1.on_breed then
-- 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 ent_c = child:get_luaentity()
-- Use texture of one of the parents
local p = math.random(1, 2)
if p == 1 then
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
ent_c.tamed = true
ent_c.owner = parent1.owner
end, self, ent, pos)
num = 0
break
end
end
end
end
function mob_class:toggle_sit(clicker,p)
if not self.tamed or self.child or self.owner ~= clicker:get_player_name() then
return
end
local pos = self.object:get_pos()
local particle
if not self.order or self.order == "" or self.order == "sit" then
particle = "mobs_mc_wolf_icon_roam.png"
self.order = "roam"
self.state = "stand"
self.walk_chance = default_walk_chance
self.jump = true
self:set_animation("stand")
-- TODO: Add sitting model
else
particle = "mobs_mc_wolf_icon_sit.png"
self.order = "sit"
self.state = "stand"
self.walk_chance = 0
self.jump = false
if self.animation.sit_start then
self:set_animation("sit")
else
self:set_animation("stand")
end
end
local pp = vector.new(0,1.4,0)
if p then pp = vector.offset(pp,0,p,0) end
-- Display icon to show current order (sit or roam)
minetest.add_particle({
pos = vector.add(pos, pp),
velocity = {x=0,y=0.2,z=0},
expirationtime = 1,
size = 4,
texture = particle,
playername = self.owner,
glow = minetest.LIGHT_MAX,
})
end

View File

@ -0,0 +1,796 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
local damage_enabled = minetest.settings:get_bool("enable_damage")
local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
-- pathfinding settings
local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
local stuck_path_timeout = 10 -- how long will mob follow path before giving up
-- check if daytime and also if mob is docile during daylight hours
function mob_class:day_docile()
if self.docile_by_day == false then
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
-- attack player/mob
function mob_class:do_attack(player)
if self.state == "attack" or self.state == "die" then
return
end
self.attack = player
self.state = "attack"
-- TODO: Implement war_cry sound without being annoying
--if random(0, 100) < 90 then
--self:mob_sound("war_cry", true)
--end
end
-- blast damage to entities nearby
local function entity_physics(pos,radius)
radius = radius * 2
local objs = minetest.get_objects_inside_radius(pos, radius)
local obj_pos, dist
for n = 1, #objs do
obj_pos = objs[n]:get_pos()
dist = vector.distance(pos, obj_pos)
if dist < 1 then dist = 1 end
local damage = math.floor((4 / dist) * radius)
local ent = objs[n]:get_luaentity()
-- punches work on entities AND players
objs[n]:punch(objs[n], 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = damage},
}, pos)
end
end
function mob_class:entity_physics(self,pos,radius) return entity_physics(pos,radius) end
local los_switcher = false
local height_switcher = false
-- 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)
local s1 = self.path.lastpos
local target_pos = self.attack:get_pos()
-- is it becoming stuck?
if math.abs(s1.x - s.x) + math.abs(s1.z - s.z) < .5 then
self.path.stuck_timer = self.path.stuck_timer + dtime
else
self.path.stuck_timer = 0
end
self.path.lastpos = {x = s.x, y = s.y, z = s.z}
local use_pathfind = false
local has_lineofsight = minetest.line_of_sight(
{x = s.x, y = (s.y) + .5, z = s.z},
{x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z}, .2)
-- im stuck, search for path
if not has_lineofsight then
if los_switcher == true then
use_pathfind = true
los_switcher = false
end -- cannot see target!
else
if los_switcher == false then
los_switcher = true
use_pathfind = false
minetest.after(1, function(self)
if not self.object:get_luaentity() then
return
end
if has_lineofsight then self.path.following = false end
end, self)
end -- can see target!
end
if (self.path.stuck_timer > stuck_timeout and not self.path.following) then
use_pathfind = true
self.path.stuck_timer = 0
minetest.after(1, function(self)
if not self.object:get_luaentity() then
return
end
if has_lineofsight then self.path.following = false end
end, self)
end
if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then
use_pathfind = true
self.path.stuck_timer = 0
minetest.after(1, function(self)
if not self.object:get_luaentity() then
return
end
if has_lineofsight then self.path.following = false end
end, self)
end
if math.abs(vector.subtract(s,target_pos).y) > self.stepheight then
if height_switcher then
use_pathfind = true
height_switcher = false
end
else
if not height_switcher then
use_pathfind = false
height_switcher = true
end
end
if use_pathfind then
-- lets try find a path, first take care of positions
-- since pathfinder is very sensitive
local sheight = self.collisionbox[5] - self.collisionbox[2]
-- round position to center of node to avoid stuck in walls
-- also adjust height for player models!
s.x = math.floor(s.x + 0.5)
s.z = math.floor(s.z + 0.5)
local ssight, sground = minetest.line_of_sight(s, {
x = s.x, y = s.y - 4, z = s.z}, 1)
-- determine node above ground
if not ssight then
s.y = sground.y + 1
end
local p1 = self.attack:get_pos()
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
if self.fear_height ~= 0 then dropheight = self.fear_height end
local jumpheight = 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.state = ""
self:do_attack(self.attack)
-- no path found, try something else
if not self.path.way then
self.path.following = false
-- lets make way by digging/building if not accessible
if self.pathfinding == 2 and mobs_griefing then
-- is player higher than mob?
if s.y < p1.y then
-- build upwards
if not minetest.is_protected(s, "") then
local ndef1 = minetest.registered_nodes[self.standing_in]
if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
minetest.set_node(s, {name = mcl_mobs.fallback_node})
end
end
local sheight = math.ceil(self.collisionbox[5]) + 1
-- assume mob is 2 blocks high so it digs above its head
s.y = s.y + sheight
-- remove one block above to make room to jump
if not minetest.is_protected(s, "") then
local node1 = node_ok(s, "air").name
local ndef1 = minetest.registered_nodes[node1]
if node1 ~= "air"
and node1 ~= "ignore"
and ndef1
and not ndef1.groups.level
and not ndef1.groups.unbreakable
and not ndef1.groups.liquid then
minetest.set_node(s, {name = "air"})
minetest.add_item(s, ItemStack(node1))
end
end
s.y = s.y - sheight
self.object:set_pos({x = s.x, y = s.y + 2, z = s.z})
else -- dig 2 blocks to make door toward player direction
local yaw1 = self.object:get_yaw() + math.pi / 2
local p1 = {
x = s.x + math.cos(yaw1),
y = s.y,
z = s.z + math.sin(yaw1)
}
if not minetest.is_protected(p1, "") then
local node1 = node_ok(p1, "air").name
local ndef1 = minetest.registered_nodes[node1]
if node1 ~= "air"
and node1 ~= "ignore"
and ndef1
and not ndef1.groups.level
and not ndef1.groups.unbreakable
and not ndef1.groups.liquid then
minetest.add_item(p1, ItemStack(node1))
minetest.set_node(p1, {name = "air"})
end
p1.y = p1.y + 1
node1 = node_ok(p1, "air").name
ndef1 = minetest.registered_nodes[node1]
if node1 ~= "air"
and node1 ~= "ignore"
and ndef1
and not ndef1.groups.level
and not ndef1.groups.unbreakable
and not ndef1.groups.liquid then
minetest.add_item(p1, ItemStack(node1))
minetest.set_node(p1, {name = "air"})
end
end
end
end
-- will try again in 2 seconds
self.path.stuck_timer = stuck_timeout - 2
elseif s.y < p1.y and (not self.fly) then
self:do_jump() --add jump to pathfinding
self.path.following = true
-- Yay, I found path!
-- TODO: Implement war_cry sound without being annoying
--self:mob_sound("war_cry", true)
else
self:set_velocity(self.walk_velocity)
-- follow path now that it has it
self.path.following = true
end
end
end
-- specific attacks
local specific_attack = function(list, what)
-- no list so attack default (player, animals etc.)
if list == nil then
return true
end
-- found entity on list to attack?
for no = 1, #list do
if list[no] == what then
return true
end
end
return false
end
-- find someone to attack
function mob_class:monster_attack()
if not damage_enabled
or self.passive ~= false
or self.state == "attack"
or self:day_docile() then
return
end
local s = self.object:get_pos()
local p, sp, dist
local player, obj, min_player
local type, name = "", ""
local min_dist = self.view_range + 1
local objs = minetest.get_objects_inside_radius(s, self.view_range)
local blacklist_attack = {}
for n = 1, #objs do
if not objs[n]:is_player() then
obj = objs[n]:get_luaentity()
if obj then
player = obj.object
name = obj.name or ""
end
if obj and obj.type == self.type and obj.passive == false and obj.state == "attack" and obj.attack then
table.insert(blacklist_attack, obj.attack)
end
end
end
for n = 1, #objs do
if objs[n]:is_player() then
if mcl_mobs.invis[ objs[n]:get_player_name() ] or (not self:object_in_range(objs[n])) then
type = ""
elseif (self.type == "monster" or self._aggro) then
player = objs[n]
type = "player"
name = "player"
end
else
obj = objs[n]:get_luaentity()
if obj then
player = obj.object
type = obj.type
name = obj.name or ""
end
end
-- find specific mob to attack, failing that attack player/npc/animal
if specific_attack(self.specific_attack, name)
and (type == "player" or ( type == "npc" and self.attack_npcs )
or (type == "animal" and self.attack_animals == true)) then
p = player:get_pos()
sp = s
dist = vector.distance(p, s)
-- aim higher to make looking up hills more realistic
p.y = p.y + 1
sp.y = sp.y + 1
local attacked_p = false
for c=1, #blacklist_attack do
if blacklist_attack[c] == player then
attacked_p = true
end
end
-- choose closest player to attack
if dist < min_dist
and not attacked_p
and self:line_of_sight( sp, p, 2) == true then
min_dist = dist
min_player = player
end
end
end
if not min_player and #blacklist_attack > 0 then
min_player=blacklist_attack[math.random(#blacklist_attack)]
end
-- attack player
if min_player then
self:do_attack(min_player)
end
end
-- npc, find closest monster to attack
function mob_class:npc_attack()
if self.type ~= "npc"
or not self.attacks_monsters
or self.state == "attack" then
return
end
local p, sp, obj, min_player
local s = self.object:get_pos()
local min_dist = self.view_range + 1
local objs = minetest.get_objects_inside_radius(s, self.view_range)
for n = 1, #objs do
obj = objs[n]:get_luaentity()
if obj and obj.type == "monster" then
p = obj.object:get_pos()
sp = s
local dist = vector.distance(p, s)
-- aim higher to make looking up hills more realistic
p.y = p.y + 1
sp.y = sp.y + 1
if dist < min_dist
and self:line_of_sight( sp, p, 2) == true then
min_dist = dist
min_player = obj.object
end
end
end
if min_player then
self:do_attack(min_player)
end
end
-- dogshoot attack switch and counter function
function mob_class:dogswitch(dtime)
-- switch mode not activated
if not self.dogshoot_switch
or not dtime then
return 0
end
self.dogshoot_count = self.dogshoot_count + dtime
if (self.dogshoot_switch == 1
and self.dogshoot_count > self.dogshoot_count_max)
or (self.dogshoot_switch == 2
and self.dogshoot_count > self.dogshoot_count2_max) then
self.dogshoot_count = 0
if self.dogshoot_switch == 1 then
self.dogshoot_switch = 2
else
self.dogshoot_switch = 1
end
end
return self.dogshoot_switch
end
-- no damage to nodes explosion
function mob_class:safe_boom(pos, strength)
minetest.sound_play(self.sounds and self.sounds.explode or "tnt_explode", {
pos = pos,
gain = 1.0,
max_hear_distance = self.sounds and self.sounds.distance or 32
}, true)
local radius = strength
entity_physics(pos, radius)
mcl_mobs.effect(pos, 32, "mcl_particles_smoke.png", radius * 3, radius * 5, radius, 1, 0)
end
-- make explosion with protection and tnt mod check
function mob_class:boom(pos, strength, fire)
if mobs_griefing and not minetest.is_protected(pos, "") then
mcl_explosions.explode(pos, strength, { drop_chance = 1.0, fire = fire }, self.object)
else
mcl_mobs.mob_class.safe_boom(self, pos, strength) --need to call it this way bc self is the "arrow" object here
end
-- delete the object after it punched the player to avoid nil entities in e.g. mcl_shields!!
self.object:remove()
end
-- deal damage and effects when mob punched
function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
-- custom punch function
if self.do_punch then
-- when false skip going any further
if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
return
end
end
-- error checking when mod profiling is enabled
if not tool_capabilities then
minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
return
end
local is_player = hitter:is_player()
if is_player then
-- is mob protected?
if self.protected and minetest.is_protected(self.object:get_pos(), hitter:get_player_name()) then
return
end
if minetest.is_creative_enabled(hitter:get_player_name()) then
self.health = 0
end
-- set/update 'drop xp' timestamp if hitted by player
self.xp_timestamp = minetest.get_us_time()
end
-- punch interval
local weapon = hitter:get_wielded_item()
local punch_interval = 1.4
-- exhaust attacker
if is_player then
mcl_hunger.exhaust(hitter:get_player_name(), mcl_hunger.EXHAUST_ATTACK)
end
-- calculate mob damage
local damage = 0
local armor = self.object:get_armor_groups() or {}
local tmp
-- quick error check incase it ends up 0 (serialize.h check test)
if tflp == 0 then
tflp = 0.2
end
for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do
tmp = tflp / (tool_capabilities.full_punch_interval or 1.4)
if tmp < 0 then
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
if weapon then
local fire_aspect_level = mcl_enchanting.get_enchantment(weapon, "fire_aspect")
if fire_aspect_level > 0 then
mcl_burning.set_on_fire(self.object, fire_aspect_level * 4)
end
end
-- check for tool immunity or special damage
for n = 1, #self.immune_to do
if self.immune_to[n][1] == weapon:get_name() then
damage = self.immune_to[n][2] or 0
break
end
end
-- healing
if damage <= -1 then
self.health = self.health - math.floor(damage)
return
end
if tool_capabilities then
punch_interval = tool_capabilities.full_punch_interval or 1.4
end
-- add weapon wear manually
-- Required because we have custom health handling ("health" property)
if minetest.is_creative_enabled("") ~= true
and tool_capabilities then
if tool_capabilities.punch_attack_uses then
-- Without this delay, the wear does not work. Quite hacky ...
minetest.after(0, function(name)
local player = minetest.get_player_by_name(name)
if not player then return end
local weapon = hitter:get_wielded_item(player)
local def = weapon:get_definition()
if def.tool_capabilities and def.tool_capabilities.punch_attack_uses then
local wear = math.floor(65535/tool_capabilities.punch_attack_uses)
weapon:add_wear(wear)
hitter:set_wielded_item(weapon)
end
end, hitter:get_player_name())
end
end
local die = false
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.
if damage >= 0.1 then
-- weapon sounds
if weapon:get_definition().sounds ~= nil then
local s = math.random(0, #weapon:get_definition().sounds)
minetest.sound_play(weapon:get_definition().sounds[s], {
object = self.object, --hitter,
max_hear_distance = 8
}, true)
else
minetest.sound_play("default_punch", {
object = self.object,
max_hear_distance = 5
}, true)
end
self:damage_effect(damage)
-- do damage
self.health = self.health - damage
-- skip future functions if dead, except alerting others
if self:check_for_death( "hit", {type = "punch", puncher = hitter}) then
die = true
end
end
-- knock back effect (only on full punch)
if self.knock_back
and tflp >= punch_interval then
-- direction error check
dir = dir or {x = 0, y = 0, z = 0}
local v = self.object:get_velocity()
if not v then return end
local r = 1.4 - math.min(punch_interval, 1.4)
local kb = r * (math.abs(v.x)+math.abs(v.z))
local up = 2
if die==true then
kb=kb*2
end
-- if already in air then dont go up anymore when hit
if math.abs(v.y) > 0.1
or self.fly then
up = 0
end
-- check if tool already has specific knockback value
if tool_capabilities.damage_groups["knockback"] then
kb = tool_capabilities.damage_groups["knockback"]
else
kb = kb * 1.5
end
local luaentity
if hitter then
luaentity = hitter:get_luaentity()
end
if hitter and is_player then
local wielditem = hitter:get_wielded_item()
kb = kb + 3 * mcl_enchanting.get_enchantment(wielditem, "knockback")
elseif luaentity and luaentity._knockback then
kb = kb + luaentity._knockback
end
self._kb_turn = true
self._turn_to=self.object:get_yaw()-1.57
self.frame_speed_multiplier=2.3
if self.animation.run_end then
self:set_animation( "run")
elseif self.animation.walk_end then
self:set_animation( "walk")
end
minetest.after(0.2, function()
if self and self.object then
self.frame_speed_multiplier=1
self._kb_turn = false
end
end)
self.object:add_velocity({
x = dir.x * kb,
y = up*2,
z = dir.z * kb
})
self.pause_timer = 0.25
end
end -- END if damage
-- 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
local yaw = self:set_yaw( minetest.dir_to_yaw(vector.direction(hitter:get_pos(), self.object:get_pos())))
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
yaw = self:set_yaw( minetest.dir_to_yaw(vector.direction(hitter:get_pos(), self.object:get_pos())))
self:set_velocity( self.run_velocity)
end
end)
self.state = "runaway"
self.runaway_timer = 0
self.following = nil
end
local name = hitter:get_player_name() or ""
-- attack puncher and call other mobs for help
if self.passive == false
and self.state ~= "flop"
and (self.child == false or self.type == "monster")
and hitter:get_player_name() ~= self.owner
and not mcl_mobs.invis[ name ] then
if not die then
-- attack whoever punched mob
self.state = ""
self:do_attack(hitter)
self._aggro= true
end
-- alert others to the attack
local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
local obj = nil
for n = 1, #objs do
obj = objs[n]:get_luaentity()
if obj then
-- only alert members of same mob or friends
if obj.group_attack
and obj.state ~= "attack"
and obj.owner ~= name then
if obj.name == self.name then
obj:do_attack(hitter)
elseif type(obj.group_attack) == "table" then
for i=1, #obj.group_attack do
if obj.name == obj.group_attack[i] then
obj._aggro = true
obj:do_attack(hitter)
break
end
end
end
end
-- have owned mobs attack player threat
if obj.owner == name and obj.owner_loyal then
obj:do_attack(self.object)
end
end
end
end
end
function mob_class:check_aggro(dtime)
if not self._aggro or not self.attack then return end
if not self._check_aggro_timer or self._check_aggro_timer > 5 then
self._check_aggro_timer = 0
if not self.attack:get_pos() or vector.distance(self.attack:get_pos(),self.object:get_pos()) > 128 then
self._aggro = nil
self.attack = nil
self.state = "stand"
end
end
self._check_aggro_timer = self._check_aggro_timer + dtime
end

View File

@ -0,0 +1,32 @@
-- this is to make the register_mob and register egg functions commonly used by mods not break
-- when they use the weird old : notation AND self as first argument
local oldregmob = mcl_mobs.register_mob
function mcl_mobs.register_mob(self,name,def)
if type(self) == "string" then
def = name
name = self
end
return oldregmob(name,def)
end
local oldregegg = mcl_mobs.register_egg
function mcl_mobs.register_egg(self, mob, desc, background_color, overlay_color, addegg, no_creative)
if type(self) == "string" then
no_creative = addegg
addegg = overlay_color
overlay_color = background_color
background_color = desc
desc = mob
mob = self
end
return oldregegg(mob, desc, background_color, overlay_color, addegg, no_creative)
end
local oldregarrow = mcl_mobs.register_mob
function mcl_mobs.register_mob(self,name,def)
if type(self) == "string" then
def = name
name = self
end
return oldregarrow(name,def)
end

View File

@ -0,0 +1,384 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
local active_particlespawners = {}
local disable_blood = minetest.settings:get_bool("mobs_disable_blood")
local DEFAULT_FALL_SPEED = -9.81*1.5
local player_transfer_distance = tonumber(minetest.settings:get("player_transfer_distance")) or 128
if player_transfer_distance == 0 then player_transfer_distance = math.huge end
-- custom particle effects
function mcl_mobs.effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, go_down)
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({
amount = amount,
time = 0.25,
minpos = pos,
maxpos = pos,
minvel = {x = -radius, y = ym, z = -radius},
maxvel = {x = radius, y = radius, z = radius},
minacc = {x = 0, y = gravity, z = 0},
maxacc = {x = 0, y = gravity, z = 0},
minexptime = 0.1,
maxexptime = 1,
minsize = min_size,
maxsize = max_size,
texture = texture,
glow = glow,
})
end
function mcl_mobs.death_effect(pos, yaw, collisionbox, rotate)
local min, max
if collisionbox then
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
min = vector.rotate(min, {x=0, y=yaw, z=math.pi/2})
max = vector.rotate(max, {x=0, y=yaw, z=math.pi/2})
min, max = vector.sort(min, max)
min = vector.multiply(min, 0.5)
max = vector.multiply(max, 0.5)
end
minetest.add_particlespawner({
amount = 50,
time = 0.001,
minpos = vector.add(pos, min),
maxpos = vector.add(pos, max),
minvel = vector.new(-5,-5,-5),
maxvel = vector.new(5,5,5),
minexptime = 1.1,
maxexptime = 1.5,
minsize = 1,
maxsize = 2,
collisiondetection = false,
vertical = false,
texture = "mcl_particles_mob_death.png^[colorize:#000000:255",
})
minetest.sound_play("mcl_mobs_mob_poof", {
pos = pos,
gain = 1.0,
max_hear_distance = 8,
}, true)
end
-- play sound
function mob_class:mob_sound(soundname, is_opinion, fixed_pitch)
local soundinfo
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]
if sound then
if is_opinion and self.opinion_sound_cooloff > 0 then
return
end
local pitch
if not fixed_pitch then
local base_pitch = soundinfo.base_pitch
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
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:add_texture_mod(mod)
local full_mod = ""
local already_added = false
for i=1, #self.texture_mods do
if mod == self.texture_mods[i] then
already_added = true
end
full_mod = full_mod .. self.texture_mods[i]
end
if not already_added then
full_mod = full_mod .. mod
table.insert(self.texture_mods, mod)
end
self.object:set_texture_mod(full_mod)
end
function mob_class:remove_texture_mod(mod)
local full_mod = ""
local remove = {}
for i=1, #self.texture_mods do
if self.texture_mods[i] ~= mod then
full_mod = full_mod .. self.texture_mods[i]
else
table.insert(remove, i)
end
end
for i=#remove, 1 do
table.remove(self.texture_mods, remove[i])
end
self.object:set_texture_mod(full_mod)
end
function mob_class:damage_effect(damage)
-- damage particles
if (not disable_blood) and damage > 0 then
local amount_large = math.floor(damage / 2)
local amount_small = damage % 2
local pos = self.object:get_pos()
pos.y = pos.y + (self.collisionbox[5] - self.collisionbox[2]) * .5
local texture = "mobs_blood.png"
-- full heart damage (one particle for each 2 HP damage)
if amount_large > 0 then
mcl_mobs.effect(pos, amount_large, texture, 2, 2, 1.75, 0, nil, true)
end
-- 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
function mob_class:remove_particlespawners(pn)
if not active_particlespawners[pn] then return end
if not active_particlespawners[pn][self.object] then return end
for k,v in pairs(active_particlespawners[pn][self.object]) do
minetest.delete_particlespawner(v)
end
end
function mob_class:add_particlespawners(pn)
if not active_particlespawners[pn] then active_particlespawners[pn] = {} end
if not active_particlespawners[pn][self.object] then active_particlespawners[pn][self.object] = {} end
for _,ps in pairs(self.particlespawners) do
ps.attached = self.object
ps.playername = pn
table.insert(active_particlespawners[pn][self.object],minetest.add_particlespawner(ps))
end
end
function mob_class:check_particlespawners(dtime)
if not self.particlespawners then return end
--minetest.log(dump(active_particlespawners))
if self._particle_timer and self._particle_timer >= 1 then
self._particle_timer = 0
local players = {}
for _,player in pairs(minetest.get_connected_players()) do
local pn = player:get_player_name()
table.insert(players,pn)
if not active_particlespawners[pn] then
active_particlespawners[pn] = {} end
local dst = vector.distance(player:get_pos(),self.object:get_pos())
if dst < player_transfer_distance and not active_particlespawners[pn][self.object] then
self:add_particlespawners(pn)
elseif dst >= player_transfer_distance and active_particlespawners[pn][self.object] then
self:remove_particlespawners(pn)
end
end
elseif not self._particle_timer then
self._particle_timer = 0
end
self._particle_timer = self._particle_timer + dtime
end
-- set defined animation
function mob_class:set_animation(anim, fixed_frame)
if not self.animation or not anim then
return
end
if self.state == "die" and anim ~= "die" and anim ~= "stand" then
return
end
if self.jockey then
anim = "jockey"
end
if self:flight_check() and self.fly and anim == "walk" then anim = "fly" end
self._current_animation = self._current_animation or ""
if (anim == self._current_animation
or not self.animation[anim .. "_start"]
or not self.animation[anim .. "_end"]) and self.state ~= "die" then
return
end
self._current_animation = anim
local a_start = self.animation[anim .. "_start"]
local a_end
if fixed_frame then
a_end = a_start
else
a_end = self.animation[anim .. "_end"]
end
if a_start and a_end then
self.object:set_animation({
x = a_start,
y = a_end},
self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
0, self.animation[anim .. "_loop"] ~= false)
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
function mob_class:check_head_swivel(dtime)
if not self.head_swivel or type(self.head_swivel) ~= "string" then return end
local final_rotation = vector.new(0,0,0)
local oldp,oldr = self.object:get_bone_position(self.head_swivel)
local pos = self.object:get_pos()
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 10)) do
if obj:is_player() and not self.attack or obj:get_luaentity() and obj:get_luaentity().name == self.name and self ~= obj:get_luaentity() then
if not self._locked_object then
if math.random(5000/self.curiosity) == 1 or vector.distance(pos,obj:get_pos())<4 and obj:is_player() then
self._locked_object = obj
end
else
if math.random(10000/self.curiosity) == 1 then
self._locked_object = nil
end
end
end
end
if self.attack or self.following then
self._locked_object = self.attack or self.following
end
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
if self._locked_object:get_luaentity() then
_locked_object_eye_height = self._locked_object:get_luaentity().head_eye_height
end
if self._locked_object:is_player() then
_locked_object_eye_height = self._locked_object:get_properties().eye_height
end
if _locked_object_eye_height then
local self_rot = self.object:get_rotation()
if self.object:get_attach() then
self_rot = self.object:get_attach():get_rotation()
end
if self.rot then
local player_pos = self._locked_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)))
local mob_yaw = math.deg(-(-(self_rot.y)-(-minetest.dir_to_yaw(direction_player))))+self.head_yaw_offset
local mob_pitch = math.deg(-dir_to_pitch(direction_player))*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
final_rotation = vector.multiply(oldr, 0.9)
elseif self.attack and self.state == "attack" and not self.runaway then
if self.head_yaw == "y" then
final_rotation = vector.new(mob_pitch, mob_yaw, 0)
elseif self.head_yaw == "z" then
final_rotation = vector.new(mob_pitch, 0, -mob_yaw)
end
else
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)
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)
end
end
end
end
elseif not self._locked_object and math.abs(oldr.y) > 3 and math.abs(oldr.x) < 3 then
final_rotation = vector.multiply(oldr, 0.9)
else
final_rotation = vector.new(0,0,0)
end
mcl_util.set_bone_position(self.object,self.head_swivel, vector.new(0,self.bone_eye_height,self.horrizonatal_head_height), final_rotation)
end
function mob_class:set_animation_speed()
local v = self.object:get_velocity()
if v then
if self.frame_speed_multiplier then
local v2 = math.abs(v.x)+math.abs(v.z)*.833
if not self.animation.walk_speed then
self.animation.walk_speed = 25
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
self.object:set_animation_frame_speed(25)
end
end
--set_speed
if self.acc then
self.object:add_velocity(self.acc)
end
end
end
minetest.register_on_leaveplayer(function(player)
local pn = player:get_player_name()
if not active_particlespawners[pn] then return end
for _,m in pairs(active_particlespawners[pn]) do
for k,v in pairs(m) do
minetest.delete_particlespawner(v)
end
end
active_particlespawners[pn] = nil
end)

View File

@ -1,14 +1,566 @@
local path = minetest.get_modpath(minetest.get_current_modname())
-- Mob API
mcl_mobs = {}
mcl_mobs.mob_class = {}
mcl_mobs.mob_class_meta = {__index = mcl_mobs.mob_class}
mcl_mobs.registered_mobs = {}
local modname = minetest.get_current_modname()
local path = minetest.get_modpath(modname)
local S = minetest.get_translator(modname)
mcl_mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core:dirt"
--api and helpers
-- effects: sounds and particles mostly
dofile(path .. "/effects.lua")
-- physics: involuntary mob movement - particularly falling and death
dofile(path .. "/physics.lua")
-- movement: general voluntary mob movement, walking avoiding cliffs etc.
dofile(path .. "/movement.lua")
-- items: item management for mobs
dofile(path .. "/items.lua")
-- pathfinding: pathfinding to target positions
dofile(path .. "/pathfinding.lua")
-- combat: attack logic
dofile(path .. "/combat.lua")
-- the enity functions themselves
dofile(path .. "/api.lua")
-- Spawning Algorithm
--utility functions
dofile(path .. "/breeding.lua")
dofile(path .. "/spawning.lua")
-- Rideable Mobs
dofile(path .. "/mount.lua")
-- Mob Items
dofile(path .. "/crafts.lua")
dofile(path .. "/compat.lua")
local DEFAULT_FALL_SPEED = -9.81*1.5
local MAX_MOB_NAME_LENGTH = 30
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 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
-- Code to execute before custom on_rightclick handling
local on_rightclick_prefix = function(self, clicker)
if not clicker:is_player() then return end
local item = clicker:get_wielded_item()
if extended_pet_control and self.tamed and self.owner == clicker:get_player_name() then
self:toggle_sit(clicker)
end
-- Name mob with nametag
if not self.ignores_nametag and item:get_name() == "mcl_mobs:nametag" then
local tag = item:get_meta():get_string("name")
if tag ~= "" then
if string.len(tag) > MAX_MOB_NAME_LENGTH then
tag = string.sub(tag, 1, MAX_MOB_NAME_LENGTH)
end
self.nametag = tag
self:update_tag()
if not minetest.is_creative_enabled(clicker:get_player_name()) then
item:take_item()
clicker:set_wielded_item(item)
end
return true
end
end
return false
end
local create_mob_on_rightclick = function(on_rightclick)
return function(self, clicker)
local stop = on_rightclick_prefix(self, clicker)
if (not stop) and (on_rightclick) then
on_rightclick(self, clicker)
end
end
end
-- check if within physical map limits (-30911 to 30927)
local function within_limits(pos, radius)
local wmin, wmax = -30912, 30928
if mcl_vars then
if mcl_vars.mapgen_edge_min and mcl_vars.mapgen_edge_max then
wmin, wmax = mcl_vars.mapgen_edge_min, mcl_vars.mapgen_edge_max
end
end
if radius then
wmin = wmin - radius
wmax = wmax + radius
end
for _,v in pairs(pos) do
if v < wmin or v > wmax then return false end
end
return true
end
mcl_mobs.spawning_mobs = {}
-- register mob entity
function mcl_mobs.register_mob(name, def)
mcl_mobs.spawning_mobs[name] = true
mcl_mobs.registered_mobs[name] = def
local can_despawn
if def.can_despawn ~= nil then
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)
if (not value) or (value == default) or (value == special) then
return default
else
return math.max(min, value * difficulty)
end
end
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>:
-- Increase upper Y limit to avoid mobs glitching through solid nodes.
-- FIXME: Remove workaround if it's no longer needed.
if collisionbox[5] < 0.79 then
collisionbox[5] = 0.79
end
local final_def = {
use_texture_alpha = def.use_texture_alpha,
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_pitch_multiplier = def.head_pitch_multiplier or 1, --for inverted pitch
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
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
horrizonatal_head_height = def.horrizonatal_head_height or 0,
wears_armor = def.wears_armor, -- a number value used to index texture slot for armor
stepheight = def.stepheight or 0.6,
name = name,
description = def.description,
type = def.type,
attack_type = def.attack_type,
fly = def.fly or false,
fly_in = def.fly_in or {"air", "__airlike"},
owner = def.owner or "",
order = def.order or "",
on_die = def.on_die,
spawn_small_alternative = def.spawn_small_alternative,
do_custom = def.do_custom,
detach_child = def.detach_child,
jump_height = def.jump_height or 4, -- was 6
rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2
lifetimer = def.lifetimer or 57.73,
hp_min = scale_difficulty(def.hp_min, 5, 1),
hp_max = scale_difficulty(def.hp_max, 10, 1),
xp_min = def.xp_min or 0,
xp_max = def.xp_max or 0,
xp_timestamp = 0,
breath_max = def.breath_max or 15,
breathes_in_water = def.breathes_in_water or false,
physical = true,
collisionbox = collisionbox,
selectionbox = def.selectionbox or def.collisionbox,
visual = def.visual,
visual_size = def.visual_size or {x = 1, y = 1},
mesh = def.mesh,
makes_footstep_sound = def.makes_footstep_sound or false,
view_range = def.view_range or 16,
walk_velocity = def.walk_velocity or 1,
run_velocity = def.run_velocity or 2,
damage = scale_difficulty(def.damage, 0, 0),
light_damage = def.light_damage or 0,
sunlight_damage = def.sunlight_damage or 0,
water_damage = def.water_damage or 0,
lava_damage = def.lava_damage or 8,
fire_damage = def.fire_damage or 1,
suffocation = def.suffocation or true,
fall_damage = def.fall_damage or 1,
fall_speed = def.fall_speed or DEFAULT_FALL_SPEED, -- must be lower than -2
drops = def.drops or {},
armor = def.armor or 100,
on_rightclick = create_mob_on_rightclick(def.on_rightclick),
arrow = def.arrow,
shoot_interval = def.shoot_interval,
sounds = def.sounds or {},
animation = def.animation or {},
follow = def.follow,
nofollow = def.nofollow,
can_open_doors = def.can_open_doors,
jump = def.jump ~= false,
automatic_face_movement_max_rotation_per_sec = 300,
walk_chance = def.walk_chance or 50,
attacks_monsters = def.attacks_monsters or false,
group_attack = def.group_attack or false,
passive = def.passive or false,
knock_back = def.knock_back ~= false,
shoot_offset = def.shoot_offset or 0,
floats = def.floats or 1, -- floats in water by default
floats_on_lava = def.floats_on_lava or 0,
replace_rate = def.replace_rate,
replace_what = def.replace_what,
replace_with = def.replace_with,
replace_offset = def.replace_offset or 0,
on_replace = def.on_replace,
timer = 0,
env_damage_timer = 0,
tamed = false,
pause_timer = 0,
horny = false,
hornytimer = 0,
gotten = false,
health = 0,
frame_speed_multiplier = 1,
reach = def.reach or 3,
htimer = 0,
texture_list = def.textures,
child_texture = def.child_texture,
docile_by_day = def.docile_by_day or false,
time_of_day = 0.5,
fear_height = def.fear_height or 0,
runaway = def.runaway,
runaway_timer = 0,
pathfinding = def.pathfinding,
immune_to = def.immune_to or {},
explosion_radius = def.explosion_radius, -- LEGACY
explosion_damage_radius = def.explosion_damage_radius, -- LEGACY
explosiontimer_reset_radius = def.explosiontimer_reset_radius,
explosion_timer = def.explosion_timer or 3,
allow_fuse_reset = def.allow_fuse_reset ~= false,
stop_to_explode = def.stop_to_explode ~= false,
custom_attack = def.custom_attack,
double_melee_attack = def.double_melee_attack,
dogshoot_switch = def.dogshoot_switch,
dogshoot_count = 0,
dogshoot_count_max = def.dogshoot_count_max or 5,
dogshoot_count2_max = def.dogshoot_count2_max or (def.dogshoot_count_max or 5),
attack_animals = def.attack_animals or false,
attack_npcs = def.attack_npcs or false,
specific_attack = def.specific_attack,
runaway_from = def.runaway_from,
owner_loyal = def.owner_loyal,
facing_fence = false,
is_mob = true,
pushable = def.pushable or true,
-- MCL2 extensions
shooter_avoid_enemy = def.shooter_avoid_enemy,
strafes = def.strafes,
avoid_distance = def.avoid_distance or 9,
do_teleport = def.do_teleport,
spawn_class = def.spawn_class,
can_spawn = def.can_spawn,
ignores_nametag = def.ignores_nametag or false,
rain_damage = def.rain_damage or 0,
glow = def.glow,
can_despawn = can_despawn,
child = def.child or false,
texture_mods = {},
shoot_arrow = def.shoot_arrow,
sounds_child = def.sounds_child,
_child_animations = def.child_animations,
pick_up = def.pick_up,
explosion_strength = def.explosion_strength,
suffocation_timer = 0,
follow_velocity = def.follow_velocity or 2.4,
instant_death = def.instant_death or false,
fire_resistant = def.fire_resistant or false,
fire_damage_resistant = def.fire_damage_resistant or false,
ignited_by_sunlight = def.ignited_by_sunlight or false,
spawn_in_group = def.spawn_in_group,
spawn_in_group_min = def.spawn_in_group_min,
noyaw = def.noyaw or false,
particlespawners = def.particlespawners,
-- End of MCL2 extensions
on_spawn = def.on_spawn,
on_blast = def.on_blast or function(self,damage)
self.object:punch(self.object, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = damage},
}, nil)
return false, true, {}
end,
do_punch = def.do_punch,
on_breed = def.on_breed,
on_grown = def.on_grown,
on_pick_up = def.on_pick_up,
on_activate = function(self, staticdata, dtime)
--this is a temporary hack so mobs stop
--glitching and acting really weird with the
--default built in engine collision detection
self.is_mob = true
self.object:set_properties({
collide_with_objects = false,
})
return self:mob_activate(staticdata, def, dtime)
end,
harmed_by_heal = def.harmed_by_heal,
on_lightning_strike = def.on_lightning_strike
}
minetest.register_entity(name, setmetatable(final_def,mcl_mobs.mob_class_meta))
if minetest.get_modpath("doc_identifier") ~= nil then
doc.sub.identifier.register_object(name, "basics", "mobs")
end
end -- END mcl_mobs.register_mob function
-- register arrow for shoot attack
function mcl_mobs.register_arrow(name, def)
if not name or not def then return end -- errorcheck
minetest.register_entity(name, {
physical = false,
visual = def.visual,
visual_size = def.visual_size,
textures = def.textures,
velocity = def.velocity,
hit_player = def.hit_player,
hit_node = def.hit_node,
hit_mob = def.hit_mob,
hit_object = def.hit_object,
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,
switch = 0,
owner_id = def.owner_id,
rotate = def.rotate,
on_punch = function(self)
local vel = self.object:get_velocity()
self.object:set_velocity({x=vel.x * -1, y=vel.y * -1, z=vel.z * -1})
end,
collisionbox = def.collisionbox or {0, 0, 0, 0, 0, 0},
automatic_face_movement_dir = def.rotate
and (def.rotate - (math.pi / 180)) or false,
on_activate = def.on_activate,
on_step = def.on_step or function(self, dtime)
self.timer = self.timer + 1
local pos = self.object:get_pos()
if self.switch == 0
or self.timer > 150
or not within_limits(pos, 0) then
mcl_burning.extinguish(self.object)
self.object:remove();
return
end
-- does arrow have a tail (fireball)
if def.tail
and def.tail == 1
and def.tail_texture then
minetest.add_particle({
pos = pos,
velocity = {x = 0, y = 0, z = 0},
acceleration = {x = 0, y = 0, z = 0},
expirationtime = def.expire or 0.25,
collisiondetection = false,
texture = def.tail_texture,
size = def.tail_size or 5,
glow = def.glow or 0,
})
end
if self.hit_node then
local node = node_ok(pos).name
if minetest.registered_nodes[node].walkable then
self.hit_node(self, pos, node)
if self.drop == true then
pos.y = pos.y + 1
self.lastpos = (self.lastpos or pos)
minetest.add_item(self.lastpos, self.object:get_luaentity().name)
end
self.object:remove();
return
end
end
if self.hit_player or self.hit_mob or self.hit_object then
for _,player in pairs(minetest.get_objects_inside_radius(pos, 1.5)) do
if self.hit_player
and player:is_player() then
self.hit_player(self, player)
self.object:remove();
return
end
local entity = player:get_luaentity()
if entity
and self.hit_mob
and entity.is_mob == true
and tostring(player) ~= self.owner_id
and entity.name ~= self.object:get_luaentity().name then
self.hit_mob(self, player)
self.object:remove();
return
end
if entity
and self.hit_object
and (not entity.is_mob)
and tostring(player) ~= self.owner_id
and entity.name ~= self.object:get_luaentity().name then
self.hit_object(self, player)
self.object:remove();
return
end
end
end
self.lastpos = pos
end
})
end
-- Register spawn eggs
-- Note: This also introduces the “spawn_egg” group:
-- * spawn_egg=1: Spawn egg (generic mob, no metadata)
-- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata)
function mcl_mobs.register_egg(mob, desc, background_color, overlay_color, addegg, no_creative)
local grp = {spawn_egg = 1}
-- 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 .. ")"
if old_spawn_icons then
local mobname = mob:gsub("mobs_mc:","")
local fn = "mobs_mc_spawn_icon_"..mobname..".png"
if mcl_util.file_exists(minetest.get_modpath("mobs_mc").."/textures/"..fn) then
invimg = fn
end
end
if addegg == 1 then
invimg = "mobs_chicken_egg.png^(" .. invimg ..
"^[mask:mobs_chicken_egg_overlay.png)"
end
-- register old stackable mob egg
minetest.register_craftitem(mob, {
description = desc,
inventory_image = invimg,
groups = grp,
_doc_items_longdesc = S("This allows you to place a single mob."),
_doc_items_usagehelp = S("Just place it where you want the mob to appear. Animals will spawn tamed, unless you hold down the sneak key while placing. If you place this on a mob spawner, you change the mob it spawns."),
on_place = function(itemstack, placer, pointed_thing)
local pos = pointed_thing.above
-- am I clicking on something with existing on_rightclick function?
local under = minetest.get_node(pointed_thing.under)
local def = minetest.registered_nodes[under.name]
if def and def.on_rightclick then
return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
end
if pos
and within_limits(pos, 0)
and not minetest.is_protected(pos, placer:get_player_name()) then
local name = placer:get_player_name()
local privs = minetest.get_player_privs(name)
if under.name == "mcl_mobspawners:spawner" then
if minetest.is_protected(pointed_thing.under, name) then
minetest.record_protection_violation(pointed_thing.under, name)
return itemstack
end
if not privs.maphack then
minetest.chat_send_player(name, S("You need the “maphack” privilege to change the mob spawner."))
return itemstack
end
mcl_mobspawners.setup_spawner(pointed_thing.under, itemstack:get_name())
if not minetest.is_creative_enabled(name) then
itemstack:take_item()
end
return itemstack
end
if not minetest.registered_entities[mob] then
return itemstack
end
if minetest.settings:get_bool("only_peaceful_mobs", false)
and minetest.registered_entities[mob].type == "monster" then
minetest.chat_send_player(name, S("Only peaceful mobs allowed!"))
return itemstack
end
pos.y = pos.y - 0.5
local mob = minetest.add_entity(pos, mob)
local entityname = itemstack:get_name()
minetest.log("action", "Player " ..name.." spawned "..entityname.." at "..minetest.pos_to_string(pos))
local ent = mob:get_luaentity()
-- don't set owner if monster or sneak pressed
if ent.type ~= "monster"
and not placer:get_player_control().sneak then
ent.owner = placer:get_player_name()
ent.tamed = true
end
-- set nametag
local nametag = itemstack:get_meta():get_string("name")
if nametag ~= "" then
if string.len(nametag) > MAX_MOB_NAME_LENGTH then
nametag = string.sub(nametag, 1, MAX_MOB_NAME_LENGTH)
end
ent.nametag = nametag
update_tag(ent)
end
-- if not in creative then take item
if not minetest.is_creative_enabled(placer:get_player_name()) then
itemstack:take_item()
end
end
return itemstack
end,
})
end

View File

@ -0,0 +1,103 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
--- Item and armor management
local function player_near(pos)
for _,o in pairs(minetest.get_objects_inside_radius(pos,2)) do
if o:is_player() then return true end
end
end
local function get_armor_texture(armor_name)
if armor_name == "" then
return ""
end
if armor_name=="blank.png" then
return "blank.png"
end
local seperator = string.find(armor_name, ":")
return "mcl_armor_"..string.sub(armor_name, seperator+1, -1)..".png^"
end
function mob_class:set_armor_texture()
if self.armor_list then
local chestplate=minetest.registered_items[self.armor_list.chestplate] or {name=""}
local boots=minetest.registered_items[self.armor_list.boots] or {name=""}
local leggings=minetest.registered_items[self.armor_list.leggings] or {name=""}
local helmet=minetest.registered_items[self.armor_list.helmet] or {name=""}
if helmet.name=="" and chestplate.name=="" and leggings.name=="" and boots.name=="" then
helmet={name="blank.png"}
end
local texture = get_armor_texture(chestplate.name)..get_armor_texture(helmet.name)..get_armor_texture(boots.name)..get_armor_texture(leggings.name)
if string.sub(texture, -1,-1) == "^" then
texture=string.sub(texture,1,-2)
end
if self.textures[self.wears_armor] then
self.textures[self.wears_armor]=texture
end
self.object:set_properties({textures=self.textures})
local armor_
if type(self.armor) == "table" then
armor_ = table.copy(self.armor)
armor_.immortal = 1
else
armor_ = {immortal=1, fleshy = self.armor}
end
for _,item in pairs(self.armor_list) do
if not item then return end
if type(minetest.get_item_group(item, "mcl_armor_points")) == "number" then
armor_.fleshy=armor_.fleshy-(minetest.get_item_group(item, "mcl_armor_points")*3.5)
end
end
self.object:set_armor_groups(armor_)
end
end
function mob_class:check_item_pickup()
if self.pick_up and #self.pick_up > 0 or self.wears_armor then
local p = self.object:get_pos()
if not p then return end
for _,o in pairs(minetest.get_objects_inside_radius(p,2)) do
local l=o:get_luaentity()
if l and l.name == "__builtin:item" then
if not player_near(p) and l.itemstring:find("mcl_armor") and self.wears_armor then
local armor_type
if l.itemstring:find("chestplate") then
armor_type = "chestplate"
elseif l.itemstring:find("boots") then
armor_type = "boots"
elseif l.itemstring:find("leggings") then
armor_type = "leggings"
elseif l.itemstring:find("helmet") then
armor_type = "helmet"
end
if not armor_type then
return
end
if not self.armor_list then
self.armor_list={helmet="",chestplate="",boots="",leggings=""}
elseif self.armor_list[armor_type] and self.armor_list[armor_type] ~= "" then
return
end
self.armor_list[armor_type]=ItemStack(l.itemstring):get_name()
o:remove()
end
if self.pick_up then
for k,v in pairs(self.pick_up) do
if not player_near(p) and self.on_pick_up and l.itemstring:find(v) then
local r = self.on_pick_up(self,l)
if r and r.is_empty and not r:is_empty() then
l.itemstring = r:to_string()
elseif r and r.is_empty and r:is_empty() then
o:remove()
end
end
end
end
end
end
end
end

View File

@ -1,4 +1,5 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
-- lib_mount by Blert2112 (edited by TenPlus1)
local enable_crash = false
@ -385,7 +386,6 @@ function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
entity.v2 = v
end
-- directional flying routine by D00Med (edited by TenPlus1)
function mcl_mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_anim)
@ -446,3 +446,18 @@ function mcl_mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_an
mcl_mobs:set_animation(entity, moving_anim)
end
end
mcl_mobs.mob_class.drive = mcl_mobs.drive
mcl_mobs.mob_class.fly = mcl_mobs.fly
mcl_mobs.mob_class.attach = mcl_mobs.attach
function mob_class:on_detach_child(child)
if self.detach_child then
if self.detach_child(self, child) then
return
end
end
if self.driver == child then
self.driver = nil
end
end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,382 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_villager",false)
local PATHFINDING = "gowp"
local enable_pathfinding = true
local LOG_MODULE = "[Mobs]"
local function mcl_log (message)
if LOGGING_ON and message then
minetest.log(LOG_MODULE .. " " .. message)
end
end
function output_table (wp)
if not wp then return end
mcl_log("wp items: ".. tostring(#wp))
for a,b in pairs(wp) do
mcl_log(a.. ": ".. tostring(b))
end
end
function append_paths (wp1, wp2)
mcl_log("Start append")
if not wp1 or not wp2 then
mcl_log("Cannot append wp's")
return
end
output_table(wp1)
output_table(wp2)
for _,a in pairs (wp2) do
table.insert(wp1, a)
end
mcl_log("End append")
end
local function output_enriched (wp_out)
mcl_log("Output enriched path")
local i = 0
for _,outy in pairs (wp_out) do
i = i + 1
mcl_log("Pos ".. i ..":" .. minetest.pos_to_string(outy["pos"]))
local action = outy["action"]
if action then
mcl_log("type: " .. action["type"])
mcl_log("action: " .. action["action"])
mcl_log("target: " .. minetest.pos_to_string(action["target"]))
end
mcl_log("failed attempts: " .. outy["failed_attempts"])
end
end
-- This function will take a list of paths, and enrich it with:
-- a var for failed attempts
-- an action, such as to open or close a door where we know that pos requires that action
local function generate_enriched_path(wp_in, door_open_pos, door_close_pos, cur_door_pos)
local wp_out = {}
for i, cur_pos in pairs(wp_in) do
local action = nil
local one_down = vector.new(0,-1,0)
local cur_pos_to_add = vector.add(cur_pos, one_down)
if door_open_pos and vector.equals (cur_pos, door_open_pos) then
mcl_log ("Door open match")
--action = {type = "door", action = "open"}
action = {}
action["type"] = "door"
action["action"] = "open"
action["target"] = cur_door_pos
cur_pos_to_add = vector.add(cur_pos, one_down)
elseif door_close_pos and vector.equals(cur_pos, door_close_pos) then
mcl_log ("Door close match")
--action = {type = "door", action = "closed"}
action = {}
action["type"] = "door"
action["action"] = "close"
action["target"] = cur_door_pos
cur_pos_to_add = vector.add(cur_pos, one_down)
elseif cur_door_pos and vector.equals(cur_pos, cur_door_pos) then
mcl_log("Current door pos")
cur_pos_to_add = vector.add(cur_pos, one_down)
action = {}
action["type"] = "door"
action["action"] = "open"
action["target"] = cur_door_pos
else
cur_pos_to_add = cur_pos
--mcl_log ("Pos doesn't match")
end
wp_out[i] = {}
wp_out[i]["pos"] = cur_pos_to_add
wp_out[i]["failed_attempts"] = 0
wp_out[i]["action"] = action
--wp_out[i] = {"pos" = cur_pos, "failed_attempts" = 0, "action" = action}
--output_pos(cur_pos, i)
end
output_enriched(wp_out)
return wp_out
end
local plane_adjacents = {
vector.new(1,0,0),
vector.new(-1,0,0),
vector.new(0,0,1),
vector.new(0,0,-1),
}
-- 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, t, target)
-- target is the same as t, just 1 square difference. Maybe we don't need target
mcl_log("Plot route from mob: " .. minetest.pos_to_string(p) .. ", to target: " .. minetest.pos_to_string(t))
local enriched_path = nil
local cur_door_pos = nil
local pos_closest_to_door = nil
local other_side_of_door = nil
--Path to door first
local wp = minetest.find_path(p,t,150,1,4)
if not wp then
mcl_log("No direct path. Path through door")
-- This could improve. There could be multiple doors. Check you can path from door to target first.
local cur_door_pos = minetest.find_node_near(target,16,{"group:door"})
if cur_door_pos then
mcl_log("Found a door near: " .. minetest.pos_to_string(cur_door_pos))
for _,v in pairs(plane_adjacents) do
pos_closest_to_door = vector.add(cur_door_pos,v)
local n = minetest.get_node(pos_closest_to_door)
if n.name == "air" then
wp = minetest.find_path(p,pos_closest_to_door,150,1,4)
if wp then
mcl_log("Found a path to next to door".. minetest.pos_to_string(pos_closest_to_door))
other_side_of_door = vector.add(cur_door_pos,-v)
mcl_log("Opposite is: ".. minetest.pos_to_string(other_side_of_door))
local wp_otherside_door_to_target = minetest.find_path(other_side_of_door,t,150,1,4)
if wp_otherside_door_to_target and #wp_otherside_door_to_target > 0 then
table.insert(wp, cur_door_pos)
append_paths (wp, wp_otherside_door_to_target)
enriched_path = generate_enriched_path(wp, pos_closest_to_door, other_side_of_door, cur_door_pos)
mcl_log("We have a path from outside door to target")
else
mcl_log("We cannot path from outside door to target")
end
break
else
mcl_log("This block next to door doesn't work.")
end
else
mcl_log("Block is not air, it is: ".. n.name)
end
end
else
mcl_log("No door found")
end
else
mcl_log("We have a direct route")
end
if wp and not enriched_path then
enriched_path = generate_enriched_path(wp)
end
return enriched_path
end
local gopath_last = os.time()
function mob_class:gopath(target,callback_arrived)
if self.state == PATHFINDING then mcl_log("Already pathfinding, don't set another until done.") return end
if self._pf_last_failed and (os.time() - self._pf_last_failed) < 30 then
mcl_log("We are not ready to path as last fail is less than threshold: " .. (os.time() - self._pf_last_failed))
return
else
mcl_log("We are ready to pathfind, no previous fail or we are past threshold")
end
--if os.time() - gopath_last < 5 then
-- mcl_log("Not ready to path yet")
-- return
--end
--gopath_last = os.time()
self.order = nil
local p = self.object:get_pos()
local t = vector.offset(target,0,1,0)
local wp = calculate_path_through_door(p, t, target)
if not wp then
mcl_log("Could not calculate path")
self._pf_last_failed = os.time()
-- Cover for a flaw in pathfind where it chooses the wrong door and gets stuck. Take a break, allow others.
end
--output_table(wp)
if wp and #wp > 0 then
self._target = t
self.callback_arrived = callback_arrived
local current_location = table.remove(wp,1)
if current_location and current_location["pos"] then
mcl_log("Removing first co-ord? " .. tostring(current_location["pos"]))
else
mcl_log("Nil pos")
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
function mob_class:interact_with_door(action, target)
local p = self.object:get_pos()
--local t = minetest.get_timeofday()
--local dd = minetest.find_nodes_in_area(vector.offset(p,-1,-1,-1),vector.offset(p,1,1,1),{"group:door"})
--for _,d in pairs(dd) do
if target then
mcl_log("Door target is: ".. minetest.pos_to_string(target))
local n = minetest.get_node(target)
if n.name:find("_b_") or n.name:find("_t_") then
mcl_log("Door")
local def = minetest.registered_nodes[n.name]
local closed = n.name:find("_b_1") or n.name:find("_t_1")
--if self.state == PATHFINDING then
if closed and action == "open" and def.on_rightclick then
mcl_log("Open door")
def.on_rightclick(target,n,self)
end
if not closed and action == "close" and def.on_rightclick then
mcl_log("Close door")
def.on_rightclick(target,n,self)
end
--else
else
mcl_log("Not door")
end
else
mcl_log("no target. cannot try and open or close door")
end
--end
end
function mob_class:do_pathfind_action(action)
if action then
mcl_log("Action present")
local type = action["type"]
local action_val = action["action"]
local target = action["target"]
if target then
mcl_log("Target: ".. minetest.pos_to_string(target))
end
if type and type == "door" then
mcl_log("Type is door")
self:interact_with_door(action_val, target)
end
end
end
local gowp_etime = 0
function mob_class:check_gowp(dtime)
gowp_etime = gowp_etime + dtime
-- 0.1 is optimal.
--less frequently = villager will get sent back after passing a point.
--more frequently = villager will fail points they shouldn't they just didn't get there yet
--if gowp_etime < 0.05 then return end
--gowp_etime = 0
local p = self.object:get_pos()
-- no destination
if not p or not self._target then
mcl_log("p: ".. tostring(p))
mcl_log("self._target: ".. tostring(self._target))
return
end
-- arrived at location, finish gowp
local distance_to_targ = vector.distance(p,self._target)
--mcl_log("Distance to targ: ".. tostring(distance_to_targ))
if distance_to_targ < 2 then
mcl_log("Arrived at _target")
self.waypoints = nil
self._target = nil
self.current_target = nil
self.state = "stand"
self.order = "stand"
self.object:set_velocity({x = 0, y = 0, z = 0})
self.object:set_acceleration({x = 0, y = 0, z = 0})
if self.callback_arrived then return self.callback_arrived(self) end
return true
end
-- More pathing to be done
local distance_to_current_target = 50
if self.current_target and self.current_target["pos"] then
distance_to_current_target = vector.distance(p,self.current_target["pos"])
end
-- 0.6 is working but too sensitive. sends villager back too frequently. 0.7 is quite good, but not with heights
-- 0.8 is optimal for 0.025 frequency checks and also 1... Actually. 0.8 is winning
-- 0.9 and 1.0 is also good. Stick with unless door open or closing issues
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
-- We have waypoints, and no current target, or we're at it. We need a new current_target.
self:do_pathfind_action (self.current_target["action"])
local failed_attempts = self.current_target["failed_attempts"]
mcl_log("There after " .. failed_attempts .. " failed attempts. current target:".. minetest.pos_to_string(self.current_target["pos"]) .. ". Distance: " .. distance_to_current_target)
self.current_target = table.remove(self.waypoints, 1)
self:go_to_pos(self.current_target["pos"])
return
elseif self.current_target and self.current_target["pos"] then
-- No waypoints left, but have current target. Potentially last waypoint to go to.
self.current_target["failed_attempts"] = self.current_target["failed_attempts"] + 1
local failed_attempts = self.current_target["failed_attempts"]
if failed_attempts >= 50 then
mcl_log("Failed to reach position (" .. minetest.pos_to_string(self.current_target["pos"]) .. ") too many times. Abandon route. Times tried: " .. failed_attempts)
self.state = "stand"
self.current_target = nil
self.waypoints = nil
self._target = nil
self._pf_last_failed = os.time()
self.object:set_velocity({x = 0, y = 0, z = 0})
self.object:set_acceleration({x = 0, y = 0, z = 0})
return
end
--mcl_log("Not at pos with failed attempts ".. failed_attempts ..": ".. minetest.pos_to_string(p) .. "self.current_target: ".. minetest.pos_to_string(self.current_target["pos"]) .. ". Distance: ".. distance_to_current_target)
self:go_to_pos(self.current_target["pos"])
-- Do i just delete current_target, and return so we can find final path.
else
-- Not at target, no current waypoints or current_target. Through the door and should be able to path to target.
-- Is a little sensitive and could take 1 - 7 times. A 10 fail count might be a good exit condition.
mcl_log("We don't have waypoints or a current target. Let's try to path to target")
local final_wp = minetest.find_path(p,self._target,150,1,4)
if final_wp then
mcl_log("We might be able to get to target here.")
-- self.waypoints = final_wp
self:go_to_pos(self._target)
else
-- Abandon route?
mcl_log("Cannot plot final route to target")
end
end
-- I don't think we need the following anymore, but test first.
-- Maybe just need something to path to target if no waypoints left
if self.current_target and self.current_target["pos"] and (self.waypoints and #self.waypoints == 0) then
local updated_p = self.object:get_pos()
local distance_to_cur_targ = vector.distance(updated_p,self.current_target["pos"])
mcl_log("Distance to current target: ".. tostring(distance_to_cur_targ))
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
if distance_to_cur_targ > 1.9 then
mcl_log("not close to current target: ".. minetest.pos_to_string(self.current_target["pos"]))
self:go_to_pos(self._current_target)
else
mcl_log("close to current target: ".. minetest.pos_to_string(self.current_target["pos"]))
self.current_target = nil
end
return
end
end

View File

@ -0,0 +1,976 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
local ENTITY_CRAMMING_MAX = 24
local CRAMMING_DAMAGE = 3
local DEATH_DELAY = 0.5
local DEFAULT_FALL_SPEED = -9.81*1.5
local FLOP_HEIGHT = 6
local FLOP_HOR_SPEED = 1.5
local PATHFINDING = "gowp"
local mobs_debug = minetest.settings:get_bool("mobs_debug", false)
local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
local mob_active_range = tonumber(minetest.settings:get("mcl_mob_active_range")) or 48
local show_health = false
-- 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
-- check if within physical map limits (-30911 to 30927)
local function within_limits(pos, radius)
local wmin, wmax = -30912, 30928
if mcl_vars then
if mcl_vars.mapgen_edge_min and mcl_vars.mapgen_edge_max then
wmin, wmax = mcl_vars.mapgen_edge_min, mcl_vars.mapgen_edge_max
end
end
if radius then
wmin = wmin - radius
wmax = wmax + radius
end
if not pos then return true end
for _,v in pairs(pos) do
if v < wmin or v > wmax then return false end
end
return true
end
function mob_class:player_in_active_range()
for _,p in pairs(minetest.get_connected_players()) do
if vector.distance(self.object:get_pos(),p:get_pos()) <= mob_active_range then return true end
-- slightly larger than the mc 32 since mobs spawn on that circle and easily stand still immediately right after spawning.
end
end
-- Return true if object is in view_range
function mob_class:object_in_range(object)
if not object then
return false
end
local factor
-- Apply view range reduction for special player armor
if object:is_player() then
local factors = mcl_armor.player_view_range_factors[object]
factor = factors and factors[self.name]
end
-- Distance check
local dist
if factor and factor == 0 then
return false
elseif factor then
dist = self.view_range * factor
else
dist = self.view_range
end
local p1, p2 = self.object:get_pos(), object:get_pos()
return p1 and p2 and (vector.distance(p1, p2) <= dist)
end
function mob_class:item_drop(cooked, looting_level)
if not mobs_drop_items then return end
looting_level = looting_level or 0
if (self.child and self.type ~= "monster") then
return
end
local obj, item, num
local pos = self.object:get_pos()
self.drops = self.drops or {}
for n = 1, #self.drops do
local dropdef = self.drops[n]
local chance = 1 / dropdef.chance
local looting_type = dropdef.looting
if looting_level > 0 then
local chance_function = dropdef.looting_chance_function
if chance_function then
chance = chance_function(looting_level)
elseif looting_type == "rare" then
chance = chance + (dropdef.looting_factor or 0.01) * looting_level
end
end
local num = 0
local do_common_looting = (looting_level > 0 and looting_type == "common")
if math.random() < chance then
num = math.random(dropdef.min or 1, dropdef.max or 1)
elseif not dropdef.looting_ignore_chance then
do_common_looting = false
end
if do_common_looting then
num = num + math.floor(math.random(0, looting_level) + 0.5)
end
if num > 0 then
item = dropdef.name
if cooked then
local output = minetest.get_craft_result({
method = "cooking", width = 1, items = {item}})
if output and output.item and not output.item:is_empty() then
item = output.item:get_name()
end
end
for x = 1, num do
obj = minetest.add_item(pos, ItemStack(item .. " " .. 1))
end
if obj and obj:get_luaentity() then
obj:set_velocity({
x = math.random(-10, 10) / 9,
y = 6,
z = math.random(-10, 10) / 9,
})
elseif obj then
obj:remove() -- item does not exist
end
end
end
self.drops = {}
end
-- collision function borrowed amended from jordan4ibanez open_ai mod
function mob_class:collision()
local pos = self.object:get_pos()
if not pos then return {0,0} end
local vel = self.object:get_velocity()
local x = 0
local z = 0
local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5
for _,object in pairs(minetest.get_objects_inside_radius(pos, width)) do
local ent = object:get_luaentity()
if object:is_player() or (ent and ent.is_mob and object ~= self.object) then
if object:is_player() and mcl_burning.is_burning(self.object) then
mcl_burning.set_on_fire(object, 4)
end
local pos2 = object:get_pos()
local vec = {x = pos.x - pos2.x, z = pos.z - pos2.z}
local force = (width + 0.5) - vector.distance(
{x = pos.x, y = 0, z = pos.z},
{x = pos2.x, y = 0, z = pos2.z})
x = x + (vec.x * force)
z = z + (vec.z * force)
end
end
return({x,z})
end
-- move mob in facing direction
function mob_class:set_velocity(v)
local c_x, c_y = 0, 0
-- can mob be pushed, if so calculate direction
if self.pushable then
c_x, c_y = unpack(self:collision())
end
-- halt mob if it has been ordered to stay
if self.order == "stand" or self.order == "sit" then
self.acc=vector.new(0,0,0)
return
end
local yaw = (self.object:get_yaw() or 0) + self.rotate
local vv = self.object:get_velocity()
if vv then
self.acc={
x = ((math.sin(yaw) * -v) + c_x)*.27,
y = 0,
z = ((math.cos(yaw) * v) + c_y)*.27,
}
end
end
-- calculate mob velocity
function mob_class:get_velocity()
local v = self.object:get_velocity()
if v then
return (v.x * v.x + v.z * v.z) ^ 0.5
end
return 0
end
function mob_class:update_roll()
local is_Fleckenstein = self.nametag == "Fleckenstein"
local was_Fleckenstein = false
local rot = self.object:get_rotation()
rot.z = is_Fleckenstein and pi or 0
self.object:set_rotation(rot)
local cbox = table.copy(self.collisionbox)
local acbox = self.object:get_properties().collisionbox
if math.abs(cbox[2] - acbox[2]) > 0.1 then
was_Fleckenstein = true
end
if is_Fleckenstein ~= was_Fleckenstein then
local pos = self.object:get_pos()
pos.y = pos.y + (acbox[2] + acbox[5])
self.object:set_pos(pos)
end
if is_Fleckenstein then
cbox[2], cbox[5] = -cbox[5], -cbox[2]
self.object:set_properties({collisionbox = cbox})
-- This leads to child mobs having the wrong collisionbox
-- and seeing as it seems to be nothing but an easter egg
-- i've put it inside the if. Which just makes it be upside
-- down lol.
end
end
local function shortest_term_of_yaw_rotation(self, rot_origin, rot_target, nums)
if not rot_origin or not rot_target then
return
end
rot_origin = math.deg(rot_origin)
rot_target = math.deg(rot_target)
if rot_origin < rot_target then
if math.abs(rot_origin-rot_target)<180 then
if nums then
return rot_target-rot_origin
else
return 1
end
else
if nums then
return -(rot_origin-(rot_target-360))
else
return -1
end
end
else
if math.abs(rot_origin-rot_target)<180 then
if nums then
return rot_target-rot_origin
else
return -1
end
else
if nums then
return (rot_target-(rot_origin-360))
else
return 1
end
end
end
end
-- set and return valid yaw
function mob_class:set_yaw(yaw, delay, dtime)
if self.noyaw then return end
if self.state ~= PATHFINDING then
self._turn_to = yaw
end
--mcl_log("Yaw is: \t\t" .. tostring(math.deg(yaw)))
--mcl_log("self.object:get_yaw() is: \t" .. tostring(math.deg(self.object:get_yaw())))
--clamp our yaw to a 360 range
if math.deg(self.object:get_yaw()) > 360 then
self.object:set_yaw(math.rad(0))
elseif math.deg(self.object:get_yaw()) < 0 then
self.object:set_yaw(math.rad(360))
end
if math.deg(yaw) > 360 then
yaw=yaw%360
elseif math.deg(yaw) < 0 then
yaw=((360*5)-yaw)%360
end
--calculate the shortest way to turn to find our target
local target_shortest_path = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), yaw, false)
local target_shortest_path_nums = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), yaw, true)
--turn in the shortest path possible toward our target. if we are attacking, don't dance.
if (math.abs(target_shortest_path) > 50 and not self._kb_turn) and (self.attack and self.attack:get_pos() or self.following and self.following:get_pos()) then
if self.following then
target_shortest_path = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.following:get_pos())), true)
target_shortest_path_nums = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.following:get_pos())), false)
else
target_shortest_path = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.attack:get_pos())), true)
target_shortest_path_nums = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.attack:get_pos())), false)
end
end
local ddtime = 0.05 --set_tick_rate
if dtime then
ddtime = dtime
end
if math.abs(target_shortest_path_nums) > 5 then
self.object:set_yaw(self.object:get_yaw()+(target_shortest_path*(3.6*ddtime)))
if self.acc then
self.acc=vector.rotate_around_axis(self.acc,vector.new(0,1,0), target_shortest_path*(3.6*ddtime))
end
end
delay = delay or 0
yaw = self.object:get_yaw()
if delay == 0 then
if self.shaking and dtime then
yaw = yaw + (math.random() * 2 - 1) * 5 * dtime
end
self:update_roll()
return yaw
end
self.target_yaw = yaw
self.delay = delay
return self.target_yaw
end
-- global function to set mob yaw
function mcl_mobs.yaw(self, yaw, delay, dtime)
return mob_class.set_yaw(self, yaw, delay, dtime)
end
-- are we flying in what we are suppose to? (taikedz)
function mob_class:flight_check()
local nod = self.standing_in
local def = minetest.registered_nodes[nod]
if not def then return false end -- nil check
local fly_in
if type(self.fly_in) == "string" then
fly_in = { self.fly_in }
elseif type(self.fly_in) == "table" then
fly_in = self.fly_in
else
return false
end
for _,checknode in pairs(fly_in) do
if nod == checknode or nod == "ignore" then
return true
end
end
return false
end
-- check if mob is dead or only hurt
function mob_class:check_for_death(cause, cmi_cause)
if self.state == "die" then
return true
end
-- has health actually changed?
if self.health == self.old_health and self.health > 0 then
return false
end
local damaged = self.health < self.old_health
self.old_health = self.health
-- still got some health?
if self.health > 0 then
-- make sure health isn't higher than max
if self.health > self.hp_max then
self.health = self.hp_max
end
-- play damage sound if health was reduced and make mob flash red.
if damaged then
self:add_texture_mod("^[colorize:#d42222:175")
minetest.after(1, function(self)
if self and self.object then
self:remove_texture_mod("^[colorize:#d42222:175")
end
end, self)
self:mob_sound("damage")
end
-- backup nametag so we can show health stats
if not self.nametag2 then
self.nametag2 = self.nametag or ""
end
if show_health
and (cmi_cause and cmi_cause.type == "punch") then
self.htimer = 2
self.nametag = "" .. self.health .. " / " .. self.hp_max
self:update_tag()
end
return false
end
self:mob_sound("death")
local function death_handle(self)
-- dropped cooked item if mob died in fire or lava
if cause == "lava" or cause == "fire" then
self:item_drop(true, 0)
else
local wielditem = ItemStack()
if cause == "hit" then
local puncher = cmi_cause.puncher
if puncher then
wielditem = puncher:get_wielded_item()
end
end
local cooked = mcl_burning.is_burning(self.object) or mcl_enchanting.has_enchantment(wielditem, "fire_aspect")
local looting = mcl_enchanting.get_enchantment(wielditem, "looting")
self:item_drop(cooked, looting)
if ((not self.child) or self.type ~= "animal") and (minetest.get_us_time() - self.xp_timestamp <= math.huge) then
mcl_experience.throw_xp(self.object:get_pos(), math.random(self.xp_min, self.xp_max))
end
end
end
-- execute custom death function
if self.on_die then
local pos = self.object:get_pos()
local on_die_exit = self.on_die(self, pos, cmi_cause)
if on_die_exit ~= true then
death_handle(self)
end
if on_die_exit == true then
self.state = "die"
mcl_burning.extinguish(self.object)
self.object:remove()
return true
end
end
local collisionbox
if self.collisionbox then
collisionbox = table.copy(self.collisionbox)
end
self.state = "die"
self.attack = nil
self.v_start = false
self.fall_speed = DEFAULT_FALL_SPEED
self.timer = 0
self.blinktimer = 0
self:remove_texture_mod("^[colorize:#FF000040")
self:remove_texture_mod("^[brighten")
self.passive = true
self.object:set_properties({
pointable = false,
collide_with_objects = false,
})
self:set_velocity(0)
local acc = self.object:get_acceleration()
acc.x, acc.y, acc.z = 0, DEFAULT_FALL_SPEED, 0
self.object:set_acceleration(acc)
local length
-- default death function and die animation (if defined)
if self.instant_death then
length = 0
elseif self.animation
and self.animation.die_start
and self.animation.die_end then
local frames = self.animation.die_end - self.animation.die_start
local speed = self.animation.die_speed or 15
length = math.max(frames / speed, 0) + DEATH_DELAY
self:set_animation( "die")
else
length = 1 + DEATH_DELAY
self:set_animation( "stand", true)
end
-- Remove body after a few seconds and drop stuff
local kill = function(self)
if not self.object:get_luaentity() then
return
end
death_handle(self)
local dpos = self.object:get_pos()
local cbox = self.collisionbox
local yaw = self.object:get_rotation().y
mcl_burning.extinguish(self.object)
self.object:remove()
mcl_mobs.death_effect(dpos, yaw, cbox, not self.instant_death)
end
if length <= 0 then
kill(self)
else
minetest.after(length, kill, self)
end
return true
end
-- Deal light damage to mob, returns true if mob died
function mob_class:deal_light_damage(pos, damage)
if not ((mcl_weather.rain.raining or mcl_weather.state == "snow") and mcl_weather.is_outdoor(pos)) then
self.health = self.health - damage
mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png")
if self:check_for_death("light", {type = "light"}) then
return true
end
end
end
-- environmental damage (water, lava, fire, light etc.)
function mob_class:do_env_damage()
-- feed/tame text timer (so mob 'full' messages dont spam chat)
if self.htimer > 0 then
self.htimer = self.htimer - 1
end
-- reset nametag after showing health stats
if self.htimer < 1 and self.nametag2 then
self.nametag = self.nametag2
self.nametag2 = nil
self:update_tag()
end
local pos = self.object:get_pos()
if not pos then return end
self.time_of_day = minetest.get_timeofday()
-- remove mob if beyond map limits
if not within_limits(pos, 0) then
mcl_burning.extinguish(self.object)
self.object:remove()
return true
end
local sunlight = minetest.get_natural_light(pos, self.time_of_day)
-- bright light harms mob
if self.light_damage ~= 0 and (sunlight or 0) > 12 then
if self:deal_light_damage(pos, self.light_damage) then
return true
end
end
local _, dim = mcl_worlds.y_to_layer(pos.y)
if (self.sunlight_damage ~= 0 or self.ignited_by_sunlight) and (sunlight or 0) >= minetest.LIGHT_MAX and dim == "overworld" then
if self.armor_list and not self.armor_list.helmet or not self.armor_list or self.armor_list and self.armor_list.helmet and self.armor_list.helmet == "" then
if self.ignited_by_sunlight then
mcl_burning.set_on_fire(self.object, 10)
else
self:deal_light_damage(pos, self.sunlight_damage)
return true
end
end
end
local y_level = self.collisionbox[2]
if self.child then
y_level = self.collisionbox[2] * 0.5
end
-- what is mob standing in?
pos.y = pos.y + y_level + 0.25 -- foot level
local pos2 = {x=pos.x, y=pos.y-1, z=pos.z}
self.standing_in = node_ok(pos, "air").name
self.standing_on = node_ok(pos2, "air").name
-- don't fall when on ignore, just stand still
if self.standing_in == "ignore" then
self.object:set_velocity({x = 0, y = 0, z = 0})
end
local nodef = minetest.registered_nodes[self.standing_in]
-- rain
if self.rain_damage > 0 then
if mcl_weather.rain.raining and mcl_weather.is_outdoor(pos) then
self.health = self.health - self.rain_damage
if self:check_for_death("rain", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
end
pos.y = pos.y + 1 -- for particle effect position
-- water damage
if self.water_damage > 0
and nodef.groups.water then
if self.water_damage ~= 0 then
self.health = self.health - self.water_damage
mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png", nil, nil, 1, nil)
if self:check_for_death("water", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
-- lava damage
elseif self.lava_damage > 0
and (nodef.groups.lava) then
if self.lava_damage ~= 0 then
self.health = self.health - self.lava_damage
mcl_mobs.effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
mcl_burning.set_on_fire(self.object, 10)
if self:check_for_death("lava", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
-- fire damage
elseif self.fire_damage > 0
and (nodef.groups.fire) then
if self.fire_damage ~= 0 then
self.health = self.health - self.fire_damage
mcl_mobs.effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
mcl_burning.set_on_fire(self.object, 5)
if self:check_for_death("fire", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
-- damage_per_second node check
elseif nodef.damage_per_second ~= 0 and not nodef.groups.lava and not nodef.groups.fire then
self.health = self.health - nodef.damage_per_second
mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png")
if self:check_for_death("dps", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
-- Drowning damage
if self.breath_max ~= -1 then
local drowning = false
if self.breathes_in_water then
if minetest.get_item_group(self.standing_in, "water") == 0 then
drowning = true
end
elseif nodef.drowning > 0 then
drowning = true
end
if drowning then
self.breath = math.max(0, self.breath - 1)
mcl_mobs.effect(pos, 2, "bubble.png", nil, nil, 1, nil)
if self.breath <= 0 then
local dmg
if nodef.drowning > 0 then
dmg = nodef.drowning
else
dmg = 4
end
self:damage_effect(dmg)
self.health = self.health - dmg
end
if self:check_for_death("drowning", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
else
self.breath = math.min(self.breath_max, self.breath + 1)
end
end
--- suffocation inside solid node
-- FIXME: Redundant with mcl_playerplus
if (self.suffocation == true)
and (nodef.walkable == nil or nodef.walkable == true)
and (nodef.collision_box == nil or nodef.collision_box.type == "regular")
and (nodef.node_box == nil or nodef.node_box.type == "regular")
and (nodef.groups.disable_suffocation ~= 1)
and (nodef.groups.opaque == 1) then
-- Short grace period before starting to take suffocation damage.
-- This is different from players, who take damage instantly.
-- This has been done because mobs might briefly be inside solid nodes
-- when e.g. climbing up stairs.
-- This is a bit hacky because it assumes that do_env_damage
-- is called roughly every second only.
self.suffocation_timer = self.suffocation_timer + 1
if self.suffocation_timer >= 3 then
-- 2 damage per second
-- TODO: Deal this damage once every 1/2 second
self.health = self.health - 2
if self:check_for_death("suffocation", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
else
self.suffocation_timer = 0
end
return self:check_for_death("", {type = "unknown"})
end
function mob_class:damage_mob(reason,damage)
if not self.health then return end
damage = math.floor(damage)
if damage > 0 then
self.health = self.health - damage
mcl_mobs.effect(self.object:get_pos(), 5, "mcl_particles_smoke.png", 1, 2, 2, nil)
if self:check_for_death(reason, {type = reason}) then
return true
end
end
end
function mob_class:check_entity_cramming()
local p = self.object:get_pos()
if not p then return end
local oo = minetest.get_objects_inside_radius(p,1)
local mobs = {}
for _,o in pairs(oo) do
local l = o:get_luaentity()
if l and l.is_mob and l.health > 0 then table.insert(mobs,l) end
end
local clear = #mobs < ENTITY_CRAMMING_MAX
local ncram = {}
for _,l in pairs(mobs) do
if l then
if clear then
l.cram = nil
elseif l.cram == nil and not self.child then
table.insert(ncram,l)
elseif l.cram then
l:damage_mob("cramming",CRAMMING_DAMAGE)
end
end
end
for i,l in pairs(ncram) do
if i > ENTITY_CRAMMING_MAX then
l.cram = true
else
l.cram = nil
end
end
end
-- falling and fall damage
-- returns true if mob died
function mob_class:falling(pos)
if self.fly and self.state ~= "die" then
return
end
if not self.fall_speed then self.fall_speed = DEFAULT_FALL_SPEED end
if mcl_portals ~= nil then
if mcl_portals.nether_portal_cooloff(self.object) then
return false -- mob has teleported through Nether portal - it's 99% not falling
end
end
-- floating in water (or falling)
local v = self.object:get_velocity()
if v.y > 0 then
-- apply gravity when moving up
self.object:set_acceleration({
x = 0,
y = DEFAULT_FALL_SPEED,
z = 0
})
elseif v.y <= 0 and v.y > self.fall_speed then
-- fall downwards at set speed
self.object:set_acceleration({
x = 0,
y = self.fall_speed,
z = 0
})
else
-- stop accelerating once max fall speed hit
self.object:set_acceleration({x = 0, y = 0, z = 0})
end
if minetest.registered_nodes[node_ok(pos).name].groups.lava then
if self.floats_on_lava == 1 then
self.object:set_acceleration({
x = 0,
y = -self.fall_speed / (math.max(1, v.y) ^ 2),
z = 0
})
end
end
-- in water then float up
if minetest.registered_nodes[node_ok(pos).name].groups.water then
if self.floats == 1 then
self.object:set_acceleration({
x = 0,
y = -self.fall_speed / (math.max(1, v.y) ^ 2),
z = 0
})
end
else
-- fall damage onto solid ground
if self.fall_damage == 1
and self.object:get_velocity().y == 0 then
local n = node_ok(vector.offset(pos,0,-1,0)).name
local d = (self.old_y or 0) - self.object:get_pos().y
if d > 5 and n ~= "air" and n ~= "ignore" then
local add = minetest.get_item_group(self.standing_on, "fall_damage_add_percent")
local damage = d - 5
if add ~= 0 then
damage = damage + damage * (add/100)
end
self:damage_mob("fall",damage)
end
self.old_y = self.object:get_pos().y
end
end
end
function mob_class:check_water_flow()
-- Add water flowing for mobs from mcl_item_entity
local p, node, nn, def
p = self.object:get_pos()
node = minetest.get_node_or_nil(p)
if node then
nn = node.name
def = minetest.registered_nodes[nn]
end
-- Move item around on flowing liquids
if def and def.liquidtype == "flowing" then
--[[ Get flowing direction (function call from flowlib), if there's a liquid.
NOTE: According to Qwertymine, flowlib.quickflow is only reliable for liquids with a flowing distance of 7.
Luckily, this is exactly what we need if we only care about water, which has this flowing distance. ]]
local vec = flowlib.quick_flow(p, node)
-- Just to make sure we don't manipulate the speed for no reason
if vec.x ~= 0 or vec.y ~= 0 or vec.z ~= 0 then
-- Minecraft Wiki: Flowing speed is "about 1.39 meters per second"
local f = 1.39
-- Set new item moving speed into the direciton of the liquid
local newv = vector.multiply(vec, f)
self.object:set_acceleration({x = 0, y = 0, z = 0})
self.object:set_velocity({x = newv.x, y = -0.22, z = newv.z})
self.physical_state = true
self._flowing = true
self.object:set_properties({
physical = true
})
return
end
elseif self._flowing == true then
-- Disable flowing physics if not on/in flowing liquid
self._flowing = false
return
end
end
function mob_class:check_dying()
if ((self.state and self.state=="die") or self:check_for_death()) and not self.animation.die_end then
local rot = self.object:get_rotation()
rot.z = ((math.pi/2-rot.z)*.2)+rot.z
self.object:set_rotation(rot)
return true
end
end
function mob_class:check_suspend()
if not self:player_in_active_range() then
local pos = self.object:get_pos()
local node_under = node_ok(vector.offset(pos,0,-1,0)).name
local acc = self.object:get_acceleration()
self:set_animation( "stand", true)
if acc.y > 0 or node_under ~= "air" then
self.object:set_acceleration(vector.new(0,0,0))
self.object:set_velocity(vector.new(0,0,0))
end
if acc.y == 0 and node_under == "air" then
self:falling(pos)
end
return true
end
end

View File

@ -1,5 +1,7 @@
--lua locals
local minetest,vector,math,table = minetest,vector,math,table
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
local get_node = minetest.get_node
local get_item_group = minetest.get_item_group
local get_node_light = minetest.get_node_light
@ -29,6 +31,7 @@ local dbg_spawn_succ = 0
local dbg_spawn_counts = {}
-- range for mob count
local aoc_range = 136
local remove_far = true
local mob_cap = {
monster = tonumber(minetest.settings:get("mcl_mob_cap_monster")) or 70,
@ -661,6 +664,7 @@ if mobs_spawn then
end
local mob_def = mob_library_worker_table[mob_index]
--minetest.log(mob_def.name.." "..step_chance.. " "..mob_chance)
if mob_def and mob_def.name and minetest.registered_entities[mob_def.name] then
local spawn_in_group = minetest.registered_entities[mob_def.name].spawn_in_group or 4
local spawn_in_group_min = minetest.registered_entities[mob_def.name].spawn_in_group_min or 1
local mob_type = minetest.registered_entities[mob_def.name].type
@ -691,6 +695,7 @@ if mobs_spawn then
object = mcl_mobs.spawn(spawning_position, mob_def.name)
end
end
end
current_summary_chance = current_summary_chance - mob_chance
table_remove(mob_library_worker_table, mob_index)
end
@ -722,6 +727,30 @@ if mobs_spawn then
end)
end
function mob_class:check_despawn(pos)
-- Despawning: when lifetimer expires, remove mob
if remove_far
and self.can_despawn == true
and ((not self.nametag) or (self.nametag == ""))
and self.state ~= "attack"
and self.following == nil then
if self.despawn_immediately or self.lifetimer <= 0 then
if logging then
minetest.log("action", "[mcl_mobs] Mob "..self.name.." despawns at "..minetest.pos_to_string(pos, 1) .. " lifetimer ran out")
end
mcl_burning.extinguish(self.object)
self.object:remove()
return true
elseif self.lifetimer <= 10 then
if math.random(10) < 4 then
self.despawn_immediately = true
else
self.lifetimer = 20
end
end
end
end
minetest.register_chatcommand("mobstats",{
privs = { debug = true },
func = function(n,param)

View File

@ -87,7 +87,7 @@ local axolotl = {
runaway = true,
}
mcl_mobs:register_mob("mobs_mc:axolotl", axolotl)
mcl_mobs.register_mob("mobs_mc:axolotl", axolotl)
local water = 0
@ -178,4 +178,4 @@ water-16,
water+1)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:axolotl", S("Axolotl"), "#e890bf", "#b83D7e", 0)
mcl_mobs.register_egg("mobs_mc:axolotl", S("Axolotl"), "#e890bf", "#b83D7e", 0)

View File

@ -2,7 +2,7 @@
local S = minetest.get_translator("mobs_mc")
mcl_mobs:register_mob("mobs_mc:bat", {
mcl_mobs.register_mob("mobs_mc:bat", {
description = S("Bat"),
type = "animal",
spawn_class = "ambient",
@ -144,4 +144,4 @@ mobs_mc.water_level-1)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:bat", S("Bat"), "#4c3e30", "#0f0f0f", 0)
mcl_mobs.register_egg("mobs_mc:bat", S("Bat"), "#4c3e30", "#0f0f0f", 0)

View File

@ -12,7 +12,7 @@ local mod_target = minetest.get_modpath("mcl_target")
--###################
mcl_mobs:register_mob("mobs_mc:blaze", {
mcl_mobs.register_mob("mobs_mc:blaze", {
description = S("Blaze"),
type = "monster",
spawn_class = "hostile",
@ -153,7 +153,7 @@ mcl_vars.mg_nether_min,
mcl_vars.mg_nether_max)
-- Blaze fireball
mcl_mobs:register_arrow("mobs_mc:blaze_fireball", {
mcl_mobs.register_arrow("mobs_mc:blaze_fireball", {
visual = "sprite",
visual_size = {x = 0.3, y = 0.3},
textures = {"mcl_fire_fire_charge.png"},
@ -208,4 +208,4 @@ mcl_mobs:register_arrow("mobs_mc:blaze_fireball", {
})
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:blaze", S("Blaze"), "#f6b201", "#fff87e", 0)
mcl_mobs.register_egg("mobs_mc:blaze", S("Blaze"), "#f6b201", "#fff87e", 0)

View File

@ -8,7 +8,7 @@ local S = minetest.get_translator("mobs_mc")
mcl_mobs:register_mob("mobs_mc:chicken", {
mcl_mobs.register_mob("mobs_mc:chicken", {
description = S("Chicken"),
type = "animal",
spawn_class = "passive",
@ -83,7 +83,7 @@ mcl_mobs:register_mob("mobs_mc:chicken", {
fear_height = 4,
on_rightclick = function(self, clicker)
if mcl_mobs:feed_tame(self, clicker, 1, true, false) then return end
if self:feed_tame(clicker, 1, true, false) then return end
if mcl_mobs:protect(self, clicker) then return end
if mcl_mobs:capture_mob(self, clicker, 0, 60, 5, false, nil) then return end
end,
@ -163,4 +163,4 @@ mobs_mc.water_level,
mcl_vars.mg_overworld_max)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:chicken", S("Chicken"), "#a1a1a1", "#ff0000", 0)
mcl_mobs.register_egg("mobs_mc:chicken", S("Chicken"), "#a1a1a1", "#ff0000", 0)

View File

@ -111,7 +111,7 @@ local cod = {
end
}
mcl_mobs:register_mob("mobs_mc:cod", cod)
mcl_mobs.register_mob("mobs_mc:cod", cod)
--spawning TODO: in schools
@ -272,4 +272,4 @@ water-16,
water+1)
--spawn egg
mcl_mobs:register_egg("mobs_mc:cod", S("Cod"), "#c1a76a", "#e5c48b", 0)
mcl_mobs.register_egg("mobs_mc:cod", S("Cod"), "#c1a76a", "#e5c48b", 0)

View File

@ -59,7 +59,7 @@ local cow_def = {
run_start = 41, run_end = 81, run_speed = 60,
},
on_rightclick = function(self, clicker)
if mcl_mobs:feed_tame(self, clicker, 1, true, false) then return end
if self:feed_tame(clicker, 1, true, false) then return end
if mcl_mobs:protect(self, clicker) then return end
if self.child then
@ -88,7 +88,7 @@ local cow_def = {
fear_height = 4,
}
mcl_mobs:register_mob("mobs_mc:cow", cow_def)
mcl_mobs.register_mob("mobs_mc:cow", cow_def)
-- Mooshroom
local mooshroom_def = table.copy(cow_def)
@ -97,7 +97,7 @@ mooshroom_def.spawn_in_group_min = 4
mooshroom_def.spawn_in_group = 8
mooshroom_def.textures = { {"mobs_mc_mooshroom.png", "mobs_mc_mushroom_red.png"}, {"mobs_mc_mooshroom_brown.png", "mobs_mc_mushroom_brown.png" } }
mooshroom_def.on_rightclick = function(self, clicker)
if mcl_mobs:feed_tame(self, clicker, 1, true, false) then return end
if self:feed_tame(clicker, 1, true, false) then return end
if mcl_mobs:protect(self, clicker) then return end
if self.child then
@ -163,7 +163,7 @@ mooshroom_def.on_lightning_strike = function(self, pos, pos2, objects)
self.object:set_properties({ textures = self.base_texture })
return true
end
mcl_mobs:register_mob("mobs_mc:mooshroom", mooshroom_def)
mcl_mobs.register_mob("mobs_mc:mooshroom", mooshroom_def)
-- Spawning
@ -231,5 +231,5 @@ mcl_vars.mg_overworld_min,
mcl_vars.mg_overworld_max)
-- spawn egg
mcl_mobs:register_egg("mobs_mc:cow", S("Cow"), "#443626", "#a1a1a1", 0)
mcl_mobs:register_egg("mobs_mc:mooshroom", S("Mooshroom"), "#a00f10", "#b7b7b7", 0)
mcl_mobs.register_egg("mobs_mc:cow", S("Cow"), "#443626", "#a1a1a1", 0)
mcl_mobs.register_egg("mobs_mc:mooshroom", S("Mooshroom"), "#a00f10", "#b7b7b7", 0)

View File

@ -9,7 +9,7 @@ local S = minetest.get_translator("mobs_mc")
mcl_mobs:register_mob("mobs_mc:creeper", {
mcl_mobs.register_mob("mobs_mc:creeper", {
type = "monster",
spawn_class = "hostile",
spawn_in_group = 1,
@ -81,7 +81,7 @@ mcl_mobs:register_mob("mobs_mc:creeper", {
if self._forced_explosion_countdown_timer ~= nil then
self._forced_explosion_countdown_timer = self._forced_explosion_countdown_timer - dtime
if self._forced_explosion_countdown_timer <= 0 then
mcl_mobs:boom(self, 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,
@ -133,7 +133,7 @@ mcl_mobs:register_mob("mobs_mc:creeper", {
view_range = 16,
})
mcl_mobs:register_mob("mobs_mc:creeper_charged", {
mcl_mobs.register_mob("mobs_mc:creeper_charged", {
description = S("Creeper"),
type = "monster",
spawn_class = "hostile",
@ -203,7 +203,7 @@ mcl_mobs:register_mob("mobs_mc:creeper_charged", {
if self._forced_explosion_countdown_timer ~= nil then
self._forced_explosion_countdown_timer = self._forced_explosion_countdown_timer - dtime
if self._forced_explosion_countdown_timer <= 0 then
mcl_mobs:boom(self, 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,
@ -411,4 +411,4 @@ mcl_vars.mg_overworld_min,
mcl_vars.mg_overworld_max)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:creeper", S("Creeper"), "#0da70a", "#000000", 0)
mcl_mobs.register_egg("mobs_mc:creeper", S("Creeper"), "#0da70a", "#000000", 0)

View File

@ -92,7 +92,7 @@ local dolphin = {
end,
}
mcl_mobs:register_mob("mobs_mc:dolphin", dolphin)
mcl_mobs.register_mob("mobs_mc:dolphin", dolphin)
--spawning TO DO: in schools
@ -250,4 +250,4 @@ water-16,
water+1)
--spawn egg
mcl_mobs:register_egg("mobs_mc:dolphin", S("Dolphin"), "#223b4d", "#f9f9f9", 0)
mcl_mobs.register_egg("mobs_mc:dolphin", S("Dolphin"), "#223b4d", "#f9f9f9", 0)

View File

@ -48,7 +48,7 @@ local function check_pos(self)
end
end
mcl_mobs:register_mob("mobs_mc:enderdragon", {
mcl_mobs.register_mob("mobs_mc:enderdragon", {
description = S("Ender Dragon"),
type = "monster",
spawn_class = "hostile",
@ -142,7 +142,7 @@ mcl_mobs:register_mob("mobs_mc:enderdragon", {
local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
-- dragon fireball (projectile)
mcl_mobs:register_arrow("mobs_mc:dragon_fireball", {
mcl_mobs.register_arrow("mobs_mc:dragon_fireball", {
visual = "sprite",
visual_size = {x = 1.25, y = 1.25},
textures = {"mobs_mc_dragon_fireball.png"},
@ -166,11 +166,11 @@ mcl_mobs:register_arrow("mobs_mc:dragon_fireball", {
-- node hit, explode
hit_node = function(self, pos, node)
mcl_mobs:boom(self, pos, 2)
mcl_mobs.mob_class.boom(self,pos, 2)
end
})
mcl_mobs:register_egg("mobs_mc:enderdragon", S("Ender Dragon"), "#252525", "#b313c9", 0, true)
mcl_mobs.register_egg("mobs_mc:enderdragon", S("Ender Dragon"), "#252525", "#b313c9", 0, true)
mcl_wip.register_wip_item("mobs_mc:enderdragon")

View File

@ -255,7 +255,7 @@ local psdefs = {{
texture = "mcl_portals_particle"..math.random(1, 5)..".png",
}}
mcl_mobs:register_mob("mobs_mc:enderman", {
mcl_mobs.register_mob("mobs_mc:enderman", {
description = S("Enderman"),
type = "monster",
spawn_class = "passive",
@ -497,7 +497,7 @@ mcl_mobs:register_mob("mobs_mc:enderman", {
self.base_texture = create_enderman_textures(block_type, self._taken_node)
self.object:set_properties({ textures = self.base_texture })
self.animation = select_enderman_animation("block")
mcl_mobs:set_animation(self, self.animation.current)
self:set_animation(self.animation.current)
if def.sounds and def.sounds.dug then
minetest.sound_play(def.sounds.dug, {pos = take_pos, max_hear_distance = 16}, true)
end
@ -520,7 +520,7 @@ mcl_mobs:register_mob("mobs_mc:enderman", {
local def = minetest.registered_nodes[self._taken_node]
-- Update animation accordingly (removes visible block)
self.animation = select_enderman_animation("normal")
mcl_mobs:set_animation(self, self.animation.current)
self:set_animation(self.animation.current)
if def.sounds and def.sounds.place then
minetest.sound_play(def.sounds.place, {pos = place_pos, max_hear_distance = 16}, true)
end
@ -832,4 +832,4 @@ mcl_vars.mg_nether_min,
mcl_vars.mg_nether_max)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:enderman", S("Enderman"), "#252525", "#151515", 0)
mcl_mobs.register_egg("mobs_mc:enderman", S("Enderman"), "#252525", "#151515", 0)

View File

@ -4,7 +4,7 @@
local S = minetest.get_translator("mobs_mc")
mcl_mobs:register_mob("mobs_mc:endermite", {
mcl_mobs.register_mob("mobs_mc:endermite", {
description = S("Endermite"),
type = "monster",
spawn_class = "hostile",
@ -38,4 +38,4 @@ mcl_mobs:register_mob("mobs_mc:endermite", {
reach = 1,
})
mcl_mobs:register_egg("mobs_mc:endermite", S("Endermite"), "#161616", "#6d6d6d", 0)
mcl_mobs.register_egg("mobs_mc:endermite", S("Endermite"), "#161616", "#6d6d6d", 0)

View File

@ -10,7 +10,7 @@ local S = minetest.get_translator("mobs_mc")
--###################
mcl_mobs:register_mob("mobs_mc:ghast", {
mcl_mobs.register_mob("mobs_mc:ghast", {
description = S("Ghast"),
type = "monster",
spawn_class = "hostile",
@ -103,7 +103,7 @@ mcl_vars.mg_nether_min,
mcl_vars.mg_nether_max)
-- fireball (projectile)
mcl_mobs:register_arrow("mobs_mc:fireball", {
mcl_mobs.register_arrow("mobs_mc:fireball", {
visual = "sprite",
visual_size = {x = 1, y = 1},
textures = {"mcl_fire_fire_charge.png"},
@ -118,9 +118,9 @@ mcl_mobs:register_arrow("mobs_mc:fireball", {
}, nil)
local p = self.object:get_pos()
if p then
mcl_mobs:boom(self, p, 1, true)
mcl_mobs.mob_class.boom(self,p, 1, true)
else
mcl_mobs:boom(self, player:get_pos(), 1, true)
mcl_mobs.mob_class.boom(self,player:get_pos(), 1, true)
end
end,
@ -129,11 +129,11 @@ mcl_mobs:register_arrow("mobs_mc:fireball", {
full_punch_interval = 1.0,
damage_groups = {fleshy = 6},
}, nil)
mcl_mobs:boom(self, self.object:get_pos(), 1, true)
mcl_mobs.mob_class.boom(self,self.object:get_pos(), 1, true)
end,
hit_node = function(self, pos, node)
mcl_mobs:boom(self, pos, 1, true)
mcl_mobs.mob_class.boom(self,pos, 1, true)
end
})
@ -141,4 +141,4 @@ mcl_mobs:register_arrow("mobs_mc:fireball", {
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:ghast", S("Ghast"), "#f9f9f9", "#bcbcbc", 0)
mcl_mobs.register_egg("mobs_mc:ghast", S("Ghast"), "#f9f9f9", "#bcbcbc", 0)

View File

@ -29,7 +29,7 @@ for i=1,4 do
table.insert(psdefs,p)
end
mcl_mobs:register_mob("mobs_mc:glow_squid", {
mcl_mobs.register_mob("mobs_mc:glow_squid", {
type = "animal",
spawn_class = "water",
can_despawn = true,
@ -243,4 +243,4 @@ mcl_mobs:spawn_specific(
water)
-- spawn egg
mcl_mobs:register_egg("mobs_mc:glow_squid", S("Glow Squid"), "#095757", "#87f6c0", 0)
mcl_mobs.register_egg("mobs_mc:glow_squid", S("Glow Squid"), "#095757", "#87f6c0", 0)

View File

@ -4,7 +4,7 @@
local S = minetest.get_translator("mobs_mc")
mcl_mobs:register_mob("mobs_mc:guardian", {
mcl_mobs.register_mob("mobs_mc:guardian", {
description = S("Guardian"),
type = "monster",
spawn_class = "hostile",
@ -104,4 +104,4 @@ mcl_mobs:register_mob("mobs_mc:guardian", {
--mcl_mobs:spawn_specific("mobs_mc:guardian", { "mcl_core:water_source", "mclx_core:river_water_source" }, { "mcl_core:water_source", "mclx_core:river_water_source" }, 0, minetest.LIGHT_MAX+1, 30, 25000, 2, mcl_vars.mg_overworld_min, mobs_mc.water_level - 10)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:guardian", S("Guardian"), "#5a8272", "#f17d31", 0)
mcl_mobs.register_egg("mobs_mc:guardian", S("Guardian"), "#5a8272", "#f17d31", 0)

View File

@ -6,7 +6,7 @@
local S = minetest.get_translator("mobs_mc")
mcl_mobs:register_mob("mobs_mc:guardian_elder", {
mcl_mobs.register_mob("mobs_mc:guardian_elder", {
description = S("Elder Guardian"),
type = "monster",
spawn_class = "hostile",
@ -112,6 +112,6 @@ mcl_mobs:register_mob("mobs_mc:guardian_elder", {
-- mcl_mobs:spawn_specific("mobs_mc:guardian_elder", { "mcl_core:water_source", "mclx_core:river_water_source" }, { "mcl_core:water_source", "mclx_core:river_water_source" }, 0, minetest.LIGHT_MAX+1, 30, 40000, 2, mcl_vars.mg_overworld_min, mobs_mc.water_level-18)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:guardian_elder", S("Elder Guardian"), "#ceccba", "#747693", 0)
mcl_mobs.register_egg("mobs_mc:guardian_elder", S("Elder Guardian"), "#ceccba", "#747693", 0)

View File

@ -84,7 +84,7 @@ local hoglin = {
attack_animals = true,
}
mcl_mobs:register_mob("mobs_mc:hoglin", hoglin)
mcl_mobs.register_mob("mobs_mc:hoglin", hoglin)
local zoglin = table.copy(hoglin)
zoglin.fire_resistant = 1
@ -95,7 +95,7 @@ end
zoglin.attacks_monsters = true
zoglin.lava_damage = 0
zoglin.fire_damage = 0
mcl_mobs:register_mob("mobs_mc:zoglin", zoglin)
mcl_mobs.register_mob("mobs_mc:zoglin", zoglin)
-- Baby hoglin.
@ -112,7 +112,7 @@ baby_hoglin.walk_velocity = 1.2
baby_hoglin.run_velocity = 2.4
baby_hoglin.child = 1
mcl_mobs:register_mob("mobs_mc:baby_hoglin", baby_hoglin)
mcl_mobs.register_mob("mobs_mc:baby_hoglin", baby_hoglin)
-- Regular spawning in the Nether
mcl_mobs:spawn_specific(
@ -132,4 +132,4 @@ mcl_vars.mg_nether_min,
mcl_vars.mg_nether_max)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:hoglin", S("Hoglin"), "#85682e", "#2b2140", 0)
mcl_mobs.register_egg("mobs_mc:hoglin", S("Hoglin"), "#85682e", "#2b2140", 0)

View File

@ -337,7 +337,7 @@ local horse = {
elseif (iname == "mcl_farming:carrot_item_gold") then
heal = 4
end
if heal > 0 and mcl_mobs:feed_tame(self, clicker, heal, true, false) then
if heal > 0 and self:feed_tame(clicker, heal, true, false) then
return
end
end
@ -352,7 +352,7 @@ local horse = {
elseif (iname == "mcl_farming:hay_block") then
heal = 20
end
if heal > 0 and mcl_mobs:feed_tame(self, clicker, heal, false, false) then
if heal > 0 and self:feed_tame(clicker, heal, false, false) then
return
end
@ -490,7 +490,7 @@ local horse = {
end,
}
mcl_mobs:register_mob("mobs_mc:horse", horse)
mcl_mobs.register_mob("mobs_mc:horse", horse)
-- Skeleton horse
local skeleton_horse = table.copy(horse)
@ -513,7 +513,7 @@ skeleton_horse.sounds = {
distance = 16,
}
skeleton_horse.harmed_by_heal = true
mcl_mobs:register_mob("mobs_mc:skeleton_horse", skeleton_horse)
mcl_mobs.register_mob("mobs_mc:skeleton_horse", skeleton_horse)
-- Zombie horse
local zombie_horse = table.copy(horse)
@ -537,7 +537,7 @@ zombie_horse.sounds = {
distance = 16,
}
zombie_horse.harmed_by_heal = true
mcl_mobs:register_mob("mobs_mc:zombie_horse", zombie_horse)
mcl_mobs.register_mob("mobs_mc:zombie_horse", zombie_horse)
-- Donkey
local d = 0.86 -- donkey scale
@ -571,7 +571,7 @@ donkey.jump = true
donkey.jump_height = 3.75 -- can clear 1 block height
mcl_mobs:register_mob("mobs_mc:donkey", donkey)
mcl_mobs.register_mob("mobs_mc:donkey", donkey)
mcl_entity_invs.register_inv("mobs_mc:donkey","Donkey",15,true)
-- Mule
local m = 0.94
@ -589,7 +589,7 @@ mule.collisionbox = {
horse.collisionbox[5] * m,
horse.collisionbox[6] * m,
}
mcl_mobs:register_mob("mobs_mc:mule", mule)
mcl_mobs.register_mob("mobs_mc:mule", mule)
mcl_entity_invs.register_inv("mobs_mc:mule","Mule",15,true)
--===========================
@ -641,8 +641,8 @@ mobs_mc.water_level+3,
mcl_vars.mg_overworld_max)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:horse", S("Horse"), "#c09e7d", "#eee500", 0)
mcl_mobs:register_egg("mobs_mc:skeleton_horse", S("Skeleton Horse"), "#68684f", "#e5e5d8", 0)
mcl_mobs.register_egg("mobs_mc:horse", S("Horse"), "#c09e7d", "#eee500", 0)
mcl_mobs.register_egg("mobs_mc:skeleton_horse", S("Skeleton Horse"), "#68684f", "#e5e5d8", 0)
--mobs:register_egg("mobs_mc:zombie_horse", S("Zombie Horse"), "#2a5a37", "#84d080", 0)
mcl_mobs:register_egg("mobs_mc:donkey", S("Donkey"), "#534539", "#867566", 0)
mcl_mobs:register_egg("mobs_mc:mule", S("Mule"), "#1b0200", "#51331d", 0)
mcl_mobs.register_egg("mobs_mc:donkey", S("Donkey"), "#534539", "#867566", 0)
mcl_mobs.register_egg("mobs_mc:mule", S("Mule"), "#1b0200", "#51331d", 0)

View File

@ -11,7 +11,7 @@ local S = minetest.get_translator("mobs_mc")
local etime = 0
mcl_mobs:register_mob("mobs_mc:iron_golem", {
mcl_mobs.register_mob("mobs_mc:iron_golem", {
description = S("Iron Golem"),
type = "npc",
spawn_class = "passive",
@ -89,7 +89,7 @@ mcl_mobs:register_mob("mobs_mc:iron_golem", {
etime = etime + dtime
if etime > 10 then
if self._home and vector.distance(self._home,self.object:get_pos()) > 50 then
mcl_mobs:gopath(self,self._home)
self:gopath(self._home)
end
end
end,
@ -97,7 +97,7 @@ mcl_mobs:register_mob("mobs_mc:iron_golem", {
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:iron_golem", S("Iron Golem"), "#3b3b3b", "#f57223", 0)
mcl_mobs.register_egg("mobs_mc:iron_golem", S("Iron Golem"), "#3b3b3b", "#f57223", 0)
--[[ This is to be called when a pumpkin or jack'o lantern has been placed. Recommended: In the on_construct function of the node.
This summons an iron golen if placing the pumpkin created an iron golem summon pattern:

View File

@ -47,7 +47,7 @@ local function get_drops(self)
end
end
mcl_mobs:register_mob("mobs_mc:llama", {
mcl_mobs.register_mob("mobs_mc:llama", {
description = S("Llama"),
type = "animal",
spawn_class = "passive",
@ -156,7 +156,7 @@ mcl_mobs:register_mob("mobs_mc:llama", {
local item = clicker:get_wielded_item()
if item:get_name() == "mcl_farming:hay_block" then
-- Breed with hay bale
if mcl_mobs:feed_tame(self, clicker, 1, true, false) then return end
if self:feed_tame(clicker, 1, true, false) then return end
elseif not self._has_chest and item:get_name() == "mcl_chests:chest" then
item:take_item()
clicker:set_wielded_item(item)
@ -173,7 +173,7 @@ mcl_mobs:register_mob("mobs_mc:llama", {
return
else
-- Feed with anything else
if mcl_mobs:feed_tame(self, clicker, 1, false, true) then return end
if self:feed_tame(clicker, 1, false, true) then return end
end
if mcl_mobs:protect(self, clicker) then return end
@ -254,7 +254,7 @@ mcl_mobs:register_mob("mobs_mc:llama", {
mcl_entity_invs.register_inv("mobs_mc:llama","Llama",nil,true)
-- spit arrow (weapon)
mcl_mobs:register_arrow("mobs_mc:llamaspit", {
mcl_mobs.register_arrow("mobs_mc:llamaspit", {
visual = "sprite",
visual_size = {x = 0.10, y = 0.10},
textures = {"mobs_mc_llama_spit.png"},
@ -297,4 +297,4 @@ mobs_mc.water_level+15,
mcl_vars.mg_overworld_max)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:llama", S("Llama"), "#c09e7d", "#995f40", 0)
mcl_mobs.register_egg("mobs_mc:llama", S("Llama"), "#c09e7d", "#995f40", 0)

View File

@ -107,7 +107,7 @@ local ocelot = {
end,
}
mcl_mobs:register_mob("mobs_mc:ocelot", ocelot)
mcl_mobs.register_mob("mobs_mc:ocelot", ocelot)
-- Cat
local cat = table.copy(ocelot)
@ -130,7 +130,7 @@ cat.sounds = {
distance = 16,
}
cat.on_rightclick = function(self, clicker)
if mcl_mobs:feed_tame(self, clicker, 1, true, false) then return end
if self:feed_tame(clicker, 1, true, false) then return end
if mcl_mobs:capture_mob(self, clicker, 0, 60, 5, false, nil) then return end
if mcl_mobs:protect(self, clicker) then return end
@ -167,7 +167,7 @@ cat.on_spawn = function(self)
self.object:set_properties({textures = {self._texture}})
end
mcl_mobs:register_mob("mobs_mc:cat", cat)
mcl_mobs.register_mob("mobs_mc:cat", cat)
local base_spawn_chance = 5000
@ -249,4 +249,4 @@ mobs:spawn({
]]--
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:ocelot", S("Ocelot"), "#efde7d", "#564434", 0)
mcl_mobs.register_egg("mobs_mc:ocelot", S("Ocelot"), "#efde7d", "#564434", 0)

View File

@ -87,7 +87,7 @@ local function perch(self,player)
local shoulder = get_shoulder(player)
if not shoulder then return true end
self.object:set_attach(player,"",shoulder,vector.new(0,0,0),true)
mcl_mobs:set_animation(self, "stand")
self:set_animation("stand")
end
end
@ -125,7 +125,7 @@ local function check_perch(self,dtime)
end
end
mcl_mobs:register_mob("mobs_mc:parrot", {
mcl_mobs.register_mob("mobs_mc:parrot", {
description = S("Parrot"),
type = "animal",
spawn_class = "passive",
@ -207,7 +207,7 @@ mcl_mobs:register_mob("mobs_mc:parrot", {
return
end
-- Feed to tame, but not breed
if mcl_mobs:feed_tame(self, clicker, 1, false, true) then return end
if self:feed_tame(clicker, 1, false, true) then return end
perch(self,clicker)
end,
do_custom = function(self,dtime)
@ -241,4 +241,4 @@ mobs_mc.water_level+7,
mcl_vars.mg_overworld_max)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:parrot", S("Parrot"), "#0da70a", "#ff0000", 0)
mcl_mobs.register_egg("mobs_mc:parrot", S("Parrot"), "#0da70a", "#ff0000", 0)

View File

@ -2,7 +2,7 @@
local S = minetest.get_translator("mobs_mc")
mcl_mobs:register_mob("mobs_mc:pig", {
mcl_mobs.register_mob("mobs_mc:pig", {
description = S("Pig"),
type = "animal",
spawn_class = "passive",
@ -108,7 +108,7 @@ mcl_mobs:register_mob("mobs_mc:pig", {
local wielditem = clicker:get_wielded_item()
-- Feed pig
if wielditem:get_name() ~= "mcl_mobitems:carrot_on_a_stick" then
if mcl_mobs:feed_tame(self, clicker, 1, true, false) then return end
if self:feed_tame(clicker, 1, true, false) then return end
end
if mcl_mobs:protect(self, clicker) then return end
@ -242,4 +242,4 @@ mcl_vars.mg_overworld_min,
mcl_vars.mg_overworld_max)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:pig", S("Pig"), "#f0a5a2", "#db635f", 0)
mcl_mobs.register_egg("mobs_mc:pig", S("Pig"), "#f0a5a2", "#db635f", 0)

View File

@ -174,7 +174,7 @@ local piglin = {
specific_attack = { "player", "mobs_mc:hoglin" },
}
mcl_mobs:register_mob("mobs_mc:piglin", piglin)
mcl_mobs.register_mob("mobs_mc:piglin", piglin)
local sword_piglin = table.copy(piglin)
@ -206,7 +206,7 @@ sword_piglin.animation = {
punch_start = 189,
punch_end = 198,
}
mcl_mobs:register_mob("mobs_mc:sword_piglin", sword_piglin)
mcl_mobs.register_mob("mobs_mc:sword_piglin", sword_piglin)
local zombified_piglin = table.copy(piglin)
zombified_piglin.fire_resistant = 1
@ -239,7 +239,7 @@ zombified_piglin.animation = {
punch_start = 189,
punch_end = 198,
}
mcl_mobs:register_mob("mobs_mc:zombified_piglin", zombified_piglin)
mcl_mobs.register_mob("mobs_mc:zombified_piglin", zombified_piglin)
local piglin_brute = table.copy(piglin)
@ -280,7 +280,7 @@ piglin_brute.animation = {
}
piglin_brute.can_despawn = false
piglin_brute.group_attack = { "mobs_mc:piglin", "mobs_mc:piglin_brute" }
mcl_mobs:register_mob("mobs_mc:piglin_brute", piglin_brute)
mcl_mobs.register_mob("mobs_mc:piglin_brute", piglin_brute)
-- Regular spawning in the Nether
@ -316,5 +316,5 @@ minetest.LIGHT_MAX+1,
mcl_vars.mg_lava_nether_max,
mcl_vars.mg_nether_max)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:piglin", S("Piglin"), "#7b4a17","#d5c381", 0)
mcl_mobs:register_egg("mobs_mc:piglin_brute", S("Piglin Brute"), "#562b0c","#ddc89d", 0)
mcl_mobs.register_egg("mobs_mc:piglin", S("Piglin"), "#7b4a17","#d5c381", 0)
mcl_mobs.register_egg("mobs_mc:piglin_brute", S("Piglin Brute"), "#562b0c","#ddc89d", 0)

View File

@ -12,7 +12,7 @@ end
local function reset_animation(self, animation)
if not self.object:get_pos() or self._current_animation ~= animation then return end
self._current_animation = "stand_reload" -- Mobs Redo won't set the animation unless we do this
mcl_mobs:set_animation(self, animation)
self:set_animation(animation)
end
pillager = {
@ -96,10 +96,10 @@ pillager = {
self.object:set_properties(props)
local old_anim = self._current_animation
if old_anim == "run" or old_anim == "walk" then
mcl_mobs:set_animation(self, "reload_run")
self:set_animation("reload_run")
end
if old_anim == "stand" then
mcl_mobs:set_animation(self, "reload_stand")
self:set_animation("reload_stand")
end
self._current_animation = old_anim -- Mobs Redo will imediately reset the animation otherwise
minetest.after(1, reload, self)
@ -120,5 +120,5 @@ pillager = {
end,
}
mcl_mobs:register_mob("mobs_mc:pillager", pillager)
mcl_mobs:register_egg("mobs_mc:pillager", S("Pillager"), "#532f36", "#959b9b", 0)
mcl_mobs.register_mob("mobs_mc:pillager", pillager)
mcl_mobs.register_egg("mobs_mc:pillager", S("Pillager"), "#532f36", "#959b9b", 0)

View File

@ -7,7 +7,7 @@ local S = minetest.get_translator("mobs_mc")
--###################
mcl_mobs:register_mob("mobs_mc:polar_bear", {
mcl_mobs.register_mob("mobs_mc:polar_bear", {
description = S("Polar Bear"),
type = "animal",
spawn_class = "passive",
@ -92,4 +92,4 @@ mcl_vars.mg_overworld_min,
mcl_vars.mg_overworld_max)
-- spawn egg
mcl_mobs:register_egg("mobs_mc:polar_bear", S("Polar Bear"), "#f2f2f2", "#959590", 0)
mcl_mobs.register_egg("mobs_mc:polar_bear", S("Polar Bear"), "#f2f2f2", "#959590", 0)

View File

@ -83,7 +83,7 @@ local rabbit = {
},
on_rightclick = function(self, clicker)
-- Feed, tame protect or capture
if mcl_mobs:feed_tame(self, clicker, 1, true, false) then return end
if self:feed_tame(clicker, 1, true, false) then return end
if mcl_mobs:protect(self, clicker) then return end
if mcl_mobs:capture_mob(self, clicker, 0, 50, 80, false, nil) then return end
end,
@ -102,7 +102,7 @@ local rabbit = {
end,
}
mcl_mobs:register_mob("mobs_mc:rabbit", rabbit)
mcl_mobs.register_mob("mobs_mc:rabbit", rabbit)
-- The killer bunny (Only with spawn egg)
local killer_bunny = table.copy(rabbit)
@ -128,7 +128,7 @@ killer_bunny.do_custom = function(self)
end
end
mcl_mobs:register_mob("mobs_mc:killer_bunny", killer_bunny)
mcl_mobs.register_mob("mobs_mc:killer_bunny", killer_bunny)
-- Mob spawning rules.
-- Different skins depending on spawn location <- we'll get to this when the spawning algorithm is fleshed out
@ -213,7 +213,7 @@ mcl_mobs:spawn(spawn_grass)
]]--
-- Spawn egg
mcl_mobs:register_egg("mobs_mc:rabbit", S("Rabbit"), "#995f40", "#734831", 0)
mcl_mobs.register_egg("mobs_mc:rabbit", S("Rabbit"), "#995f40", "#734831", 0)
-- Note: This spawn egg does not exist in Minecraft
mcl_mobs:register_egg("mobs_mc:killer_bunny", S("Killer Bunny"), "#f2f2f2", "#ff0000", 0)
mcl_mobs.register_egg("mobs_mc:killer_bunny", S("Killer Bunny"), "#f2f2f2", "#ff0000", 0)

View File

@ -67,7 +67,7 @@ local salmon = {
end
}
mcl_mobs:register_mob("mobs_mc:salmon", salmon)
mcl_mobs.register_mob("mobs_mc:salmon", salmon)
--spawning TODO: in schools
@ -226,4 +226,4 @@ water-16,
water+1)
--spawn egg
mcl_mobs:register_egg("mobs_mc:salmon", S("Salmon"), "#a00f10", "#0e8474", 0)
mcl_mobs.register_egg("mobs_mc:salmon", S("Salmon"), "#a00f10", "#0e8474", 0)

View File

@ -52,7 +52,7 @@ end
local gotten_texture = { "blank.png", "mobs_mc_sheep.png" }
--mcsheep
mcl_mobs:register_mob("mobs_mc:sheep", {
mcl_mobs.register_mob("mobs_mc:sheep", {
description = S("Sheep"),
type = "animal",
spawn_class = "passive",
@ -205,7 +205,7 @@ mcl_mobs:register_mob("mobs_mc:sheep", {
on_rightclick = function(self, clicker)
local item = clicker:get_wielded_item()
if mcl_mobs:feed_tame(self, clicker, 1, true, false) then return end
if self:feed_tame(clicker, 1, true, false) then return end
if mcl_mobs:protect(self, clicker) then return end
if item:get_name() == "mcl_tools:shears" and not self.gotten and not self.child then
@ -365,4 +365,4 @@ mcl_vars.mg_overworld_min,
mcl_vars.mg_overworld_max)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:sheep", S("Sheep"), "#e7e7e7", "#ffb5b5", 0)
mcl_mobs.register_egg("mobs_mc:sheep", S("Sheep"), "#e7e7e7", "#ffb5b5", 0)

View File

@ -30,7 +30,7 @@ local function check_spot(pos)
end
local pr = PseudoRandom(os.time()*(-334))
-- animation 45-80 is transition between passive and attack stance
mcl_mobs:register_mob("mobs_mc:shulker", {
mcl_mobs.register_mob("mobs_mc:shulker", {
description = S("Shulker"),
type = "monster",
spawn_class = "hostile",
@ -83,10 +83,10 @@ mcl_mobs:register_mob("mobs_mc:shulker", {
end
if self.state == "walk" or self.state == "stand" then
self.state = "stand"
mcl_mobs:set_animation(self, "stand")
self:set_animation("stand")
end
if self.state == "attack" then
mcl_mobs:set_animation(self, "punch")
self:set_animation("punch")
end
self.path.way = false
self.look_at_players = false
@ -134,7 +134,7 @@ mcl_mobs:register_mob("mobs_mc:shulker", {
for n=1, math.min(8, #nodes) do
local r = pr:next(1, #nodes)
local nodepos = nodes[r]
local tg = vector.offset(nodepos,0,1,0)
local tg = vector.offset(nodepos,0,0.5,0)
if check_spot(tg) then
self.object:set_pos(tg)
node_ok = true
@ -152,7 +152,7 @@ mcl_mobs:register_mob("mobs_mc:shulker", {
})
-- bullet arrow (weapon)
mcl_mobs:register_arrow("mobs_mc:shulkerbullet", {
mcl_mobs.register_arrow("mobs_mc:shulkerbullet", {
visual = "sprite",
visual_size = {x = 0.25, y = 0.25},
textures = {"mobs_mc_shulkerbullet.png"},
@ -177,7 +177,7 @@ mcl_mobs:register_arrow("mobs_mc:shulkerbullet", {
})
mcl_mobs:register_egg("mobs_mc:shulker", S("Shulker"), "#946694", "#4d3852", 0)
mcl_mobs.register_egg("mobs_mc:shulker", S("Shulker"), "#946694", "#4d3852", 0)
--[[
mcl_mobs:spawn_specific(

View File

@ -4,7 +4,7 @@
local S = minetest.get_translator("mobs_mc")
mcl_mobs:register_mob("mobs_mc:silverfish", {
mcl_mobs.register_mob("mobs_mc:silverfish", {
description = S("Silverfish"),
type = "monster",
spawn_class = "hostile",
@ -56,7 +56,7 @@ mcl_mobs:register_mob("mobs_mc:silverfish", {
reach = 1,
})
mcl_mobs:register_egg("mobs_mc:silverfish", S("Silverfish"), "#6d6d6d", "#313131", 0)
mcl_mobs.register_egg("mobs_mc:silverfish", S("Silverfish"), "#6d6d6d", "#313131", 0)
-- Monster egg blocks (Minetest Game)
if minetest.get_modpath("default") and mobs_mc.create_monster_egg_nodes then

View File

@ -129,7 +129,7 @@ local skeleton = {
harmed_by_heal = true,
}
mcl_mobs:register_mob("mobs_mc:skeleton", skeleton)
mcl_mobs.register_mob("mobs_mc:skeleton", skeleton)
--###################
@ -166,7 +166,7 @@ table.insert(stray.drops, {
end,
})
mcl_mobs:register_mob("mobs_mc:stray", stray)
mcl_mobs.register_mob("mobs_mc:stray", stray)
-- Overworld spawn
mcl_mobs:spawn_specific(
@ -356,6 +356,6 @@ mcl_vars.mg_overworld_max)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:skeleton", S("Skeleton"), "#c1c1c1", "#494949", 0)
mcl_mobs.register_egg("mobs_mc:skeleton", S("Skeleton"), "#c1c1c1", "#494949", 0)
mcl_mobs:register_egg("mobs_mc:stray", S("Stray"), "#5f7476", "#dae8e7", 0)
mcl_mobs.register_egg("mobs_mc:stray", S("Stray"), "#5f7476", "#dae8e7", 0)

View File

@ -9,7 +9,7 @@ local S = minetest.get_translator("mobs_mc")
--################### WITHER SKELETON
--###################
mcl_mobs:register_mob("mobs_mc:witherskeleton", {
mcl_mobs.register_mob("mobs_mc:witherskeleton", {
description = S("Wither Skeleton"),
type = "monster",
spawn_class = "hostile",
@ -117,4 +117,4 @@ mcl_vars.mg_nether_min,
mcl_vars.mg_nether_max)
--]]
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:witherskeleton", S("Wither Skeleton"), "#141414", "#474d4d", 0)
mcl_mobs.register_egg("mobs_mc:witherskeleton", S("Wither Skeleton"), "#141414", "#474d4d", 0)

View File

@ -109,7 +109,7 @@ local slime_big = {
on_die = spawn_children_on_die("mobs_mc:slime_small", 4, 1.0, 1.5),
use_texture_alpha = true,
}
mcl_mobs:register_mob("mobs_mc:slime_big", slime_big)
mcl_mobs.register_mob("mobs_mc:slime_big", slime_big)
local slime_small = table.copy(slime_big)
slime_small.sounds.base_pitch = 1.15
@ -126,7 +126,7 @@ slime_small.run_velocity = 1.3
slime_small.jump_height = 4.3
slime_small.spawn_small_alternative = "mobs_mc:slime_tiny"
slime_small.on_die = spawn_children_on_die("mobs_mc:slime_tiny", 4, 0.6, 1.0)
mcl_mobs:register_mob("mobs_mc:slime_small", slime_small)
mcl_mobs.register_mob("mobs_mc:slime_small", slime_small)
local slime_tiny = table.copy(slime_big)
slime_tiny.sounds.base_pitch = 1.3
@ -151,7 +151,7 @@ slime_tiny.jump_height = 3
slime_tiny.spawn_small_alternative = nil
slime_tiny.on_die = nil
mcl_mobs:register_mob("mobs_mc:slime_tiny", slime_tiny)
mcl_mobs.register_mob("mobs_mc:slime_tiny", slime_tiny)
local smin = mcl_vars.mg_overworld_min
local smax = mobs_mc.water_level - 23
@ -348,7 +348,7 @@ local magma_cube_big = {
on_die = spawn_children_on_die("mobs_mc:magma_cube_small", 3, 0.8, 1.5),
fire_resistant = true,
}
mcl_mobs:register_mob("mobs_mc:magma_cube_big", magma_cube_big)
mcl_mobs.register_mob("mobs_mc:magma_cube_big", magma_cube_big)
local magma_cube_small = table.copy(magma_cube_big)
magma_cube_small.sounds.jump = "mobs_mc_magma_cube_small"
@ -369,7 +369,7 @@ magma_cube_small.reach = 2.75
magma_cube_small.armor = 66
magma_cube_small.spawn_small_alternative = "mobs_mc:magma_cube_tiny"
magma_cube_small.on_die = spawn_children_on_die("mobs_mc:magma_cube_tiny", 4, 0.6, 1.0)
mcl_mobs:register_mob("mobs_mc:magma_cube_small", magma_cube_small)
mcl_mobs.register_mob("mobs_mc:magma_cube_small", magma_cube_small)
local magma_cube_tiny = table.copy(magma_cube_big)
magma_cube_tiny.sounds.jump = "mobs_mc_magma_cube_small"
@ -391,7 +391,7 @@ magma_cube_tiny.drops = {}
magma_cube_tiny.spawn_small_alternative = nil
magma_cube_tiny.on_die = nil
mcl_mobs:register_mob("mobs_mc:magma_cube_tiny", magma_cube_tiny)
mcl_mobs.register_mob("mobs_mc:magma_cube_tiny", magma_cube_tiny)
local mmin = mcl_vars.mg_nether_min
@ -447,6 +447,6 @@ mmin,
mmax)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:magma_cube_big", S("Magma Cube"), "#350000", "#fcfc00")
mcl_mobs.register_egg("mobs_mc:magma_cube_big", S("Magma Cube"), "#350000", "#fcfc00")
mcl_mobs:register_egg("mobs_mc:slime_big", S("Slime"), "#52a03e", "#7ebf6d")
mcl_mobs.register_egg("mobs_mc:slime_big", S("Slime"), "#52a03e", "#7ebf6d")

View File

@ -20,7 +20,7 @@ local gotten_texture = {
"blank.png",
}
mcl_mobs:register_mob("mobs_mc:snowman", {
mcl_mobs.register_mob("mobs_mc:snowman", {
description = S("Snow Golem"),
type = "npc",
spawn_class = "passive",
@ -196,4 +196,4 @@ function mobs_mc.check_snow_golem_summon(pos)
end
-- Spawn egg
mcl_mobs:register_egg("mobs_mc:snowman", S("Snow Golem"), "#f2f2f2", "#fd8f47", 0)
mcl_mobs.register_egg("mobs_mc:snowman", S("Snow Golem"), "#f2f2f2", "#fd8f47", 0)

View File

@ -111,7 +111,7 @@ local spider = {
run_end = 20,
},
}
mcl_mobs:register_mob("mobs_mc:spider", spider)
mcl_mobs.register_mob("mobs_mc:spider", spider)
-- Cave spider
local cave_spider = table.copy(spider)
@ -141,7 +141,7 @@ cave_spider.walk_velocity = 1.3
cave_spider.run_velocity = 3.2
cave_spider.sounds = table.copy(spider.sounds)
cave_spider.sounds.base_pitch = 1.25
mcl_mobs:register_mob("mobs_mc:cave_spider", cave_spider)
mcl_mobs.register_mob("mobs_mc:cave_spider", cave_spider)
mcl_mobs:spawn_specific(
@ -293,5 +293,5 @@ mcl_vars.mg_overworld_min,
mcl_vars.mg_overworld_max)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:spider", S("Spider"), "#342d26", "#a80e0e", 0)
mcl_mobs:register_egg("mobs_mc:cave_spider", S("Cave Spider"), "#0c424e", "#a80e0e", 0)
mcl_mobs.register_egg("mobs_mc:spider", S("Spider"), "#342d26", "#a80e0e", 0)
mcl_mobs.register_egg("mobs_mc:cave_spider", S("Cave Spider"), "#0c424e", "#a80e0e", 0)

View File

@ -6,7 +6,7 @@
local S = minetest.get_translator("mobs_mc")
mcl_mobs:register_mob("mobs_mc:squid", {
mcl_mobs.register_mob("mobs_mc:squid", {
description = S("Squid"),
type = "animal",
spawn_class = "water",
@ -217,4 +217,4 @@ water-16,
water+1)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:squid", S("Squid"), "#223b4d", "#708999", 0)
mcl_mobs.register_egg("mobs_mc:squid", S("Squid"), "#223b4d", "#708999", 0)

View File

@ -123,7 +123,7 @@ local strider = {
local wielditem = clicker:get_wielded_item()
if wielditem:get_name() ~= "mcl_crimson:warped_fungus" then
if mcl_mobs:feed_tame(self, clicker, 1, true, true) then return end
if self:feed_tame(clicker, 1, true, true) then return end
end
if self.child then
@ -192,7 +192,7 @@ local strider = {
end,
}
mcl_mobs:register_mob("mobs_mc:strider", strider)
mcl_mobs.register_mob("mobs_mc:strider", strider)
-- Baby strider.
@ -208,7 +208,7 @@ baby_strider.walk_velocity = 1.2
baby_strider.run_velocity = 2.4
baby_strider.child = 1
mcl_mobs:register_mob("mobs_mc:baby_strider", baby_strider)
mcl_mobs.register_mob("mobs_mc:baby_strider", baby_strider)
-- Regular spawning in the Nether
@ -245,4 +245,4 @@ mcl_mobs:spawn_setup({
})
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:strider", S("Strider"), "#000000", "#FF0000", 0)
mcl_mobs.register_egg("mobs_mc:strider", S("Strider"), "#000000", "#FF0000", 0)

View File

@ -118,7 +118,7 @@ local tropical_fish = {
on_spawn = set_textures,
}
mcl_mobs:register_mob("mobs_mc:tropical_fish", tropical_fish)
mcl_mobs.register_mob("mobs_mc:tropical_fish", tropical_fish)
local water = 0
mcl_mobs:spawn_specific(
@ -189,4 +189,4 @@ water-16,
water+1)
--spawn egg
mcl_mobs:register_egg("mobs_mc:tropical_fish", S("Tropical fish"), "#ef6915", "#fff9ef", 0)
mcl_mobs.register_egg("mobs_mc:tropical_fish", S("Tropical fish"), "#ef6915", "#fff9ef", 0)

View File

@ -9,7 +9,7 @@ local S = minetest.get_translator("mobs_mc")
--################### VEX
--###################
mcl_mobs:register_mob("mobs_mc:vex", {
mcl_mobs.register_mob("mobs_mc:vex", {
description = S("Vex"),
type = "monster",
spawn_class = "hostile",
@ -94,4 +94,4 @@ mcl_mobs:register_mob("mobs_mc:vex", {
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:vex", S("Vex"), "#7a90a4", "#e8edf1", 0)
mcl_mobs.register_egg("mobs_mc:vex", S("Vex"), "#7a90a4", "#e8edf1", 0)

View File

@ -809,7 +809,7 @@ local function go_home(entity, sleep)
return
end
mcl_mobs:gopath(entity,b,function(entity,b)
entity:gopath(b,function(entity,b)
local b = entity._bed
if not b then
@ -845,7 +845,6 @@ local function take_bed (entity)
mcl_log("Can we path to bed: "..minetest.pos_to_string(closest_block) )
local distance_to_block = vector.distance(p, closest_block)
mcl_log("Distance: " .. distance_to_block)
if distance_to_block < 2 then
local m = minetest.get_meta(closest_block)
local owner = m:get_string("villager")
@ -866,7 +865,7 @@ local function take_bed (entity)
mcl_log("Set as sleep already..." )
end
else
local gp = mcl_mobs:gopath(entity, closest_block,function(self) end)
local gp = entity:gopath(closest_block,function(self) end)
if gp then
mcl_log("Nice bed. I'll defintely take it as I can path")
else
@ -1035,7 +1034,7 @@ local function look_for_job(self, requested_jobsites)
if closest_block then
mcl_log("It's a free job for me (" .. minetest.pos_to_string(p) .. ")! I might be interested: ".. minetest.pos_to_string(closest_block) )
local gp = mcl_mobs:gopath(self, closest_block,function(self)
local gp = self:gopath(closest_block,function(self)
mcl_log("Arrived at block callback")
if self and self.state == "stand" then
self.order = WORK
@ -1168,7 +1167,7 @@ local function do_work (self)
self.order = nil
return
end
mcl_mobs:gopath(self, jobsite, function(self,jobsite)
self:gopath(jobsite, function(self,jobsite)
if not self then
--mcl_log("missing self. not good")
return false
@ -1210,7 +1209,7 @@ local function go_to_town_bell(self)
for _,n in pairs(nn) do
mcl_log("Found bell")
local gp = mcl_mobs:gopath(self,n,function(self)
local gp = self:gopath(n,function(self)
if self then
self.order = GATHERING
mcl_log("Callback has a self")
@ -1898,7 +1897,7 @@ end)
local pick_up = { "mcl_farming:bread", "mcl_farming:carrot_item", "mcl_farming:beetroot_item" , "mcl_farming:potato_item" }
mcl_mobs:register_mob("mobs_mc:villager", {
mcl_mobs.register_mob("mobs_mc:villager", {
description = S("Villager"),
type = "npc",
spawn_class = "passive",
@ -1963,7 +1962,7 @@ mcl_mobs:register_mob("mobs_mc:villager", {
end
end
if clicker and not self.horny then
mcl_mobs:feed_tame(self, clicker, 1, true, false, true)
self:feed_tame(clicker, 1, true, false, true)
it:take_item(1)
end
return it
@ -1986,7 +1985,7 @@ mcl_mobs:register_mob("mobs_mc:villager", {
end
-- Don't do at night. Go to bed? Maybe do_activity needs it's own method
if validate_jobsite(self) and not self.order == WORK then
--mcl_mobs:gopath(self,self._jobsite,function()
--self:gopath(self._jobsite,function()
-- minetest.log("sent to jobsite")
--end)
else
@ -2157,4 +2156,4 @@ mobs_mc.water_level+1,
mcl_vars.mg_overworld_max)
--]]
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:villager", S("Villager"), "#563d33", "#bc8b72", 0)
mcl_mobs.register_egg("mobs_mc:villager", S("Villager"), "#563d33", "#bc8b72", 0)

View File

@ -13,7 +13,7 @@ local pr = PseudoRandom(os.time()*666)
local spawned_vexes = {} --this is stored locally so the mobs engine doesn't try to store it in staticdata
mcl_mobs:register_mob("mobs_mc:evoker", {
mcl_mobs.register_mob("mobs_mc:evoker", {
description = S("Evoker"),
type = "monster",
spawn_class = "hostile",
@ -89,4 +89,4 @@ mcl_mobs:register_mob("mobs_mc:evoker", {
})
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:evoker", S("Evoker"), "#959b9b", "#1e1c1a", 0)
mcl_mobs.register_egg("mobs_mc:evoker", S("Evoker"), "#959b9b", "#1e1c1a", 0)

View File

@ -6,7 +6,7 @@
local S = minetest.get_translator("mobs_mc")
local mod_bows = minetest.get_modpath("mcl_bows") ~= nil
mcl_mobs:register_mob("mobs_mc:illusioner", {
mcl_mobs.register_mob("mobs_mc:illusioner", {
description = S("Illusioner"),
type = "monster",
spawn_class = "hostile",
@ -65,4 +65,4 @@ mcl_mobs:register_mob("mobs_mc:illusioner", {
fear_height = 4,
})
mcl_mobs:register_egg("mobs_mc:illusioner", S("Illusioner"), "#3f5cbb", "#8a8686", 0)
mcl_mobs.register_egg("mobs_mc:illusioner", S("Illusioner"), "#3f5cbb", "#8a8686", 0)

View File

@ -10,7 +10,7 @@ local S = minetest.get_translator("mobs_mc")
--###################
mcl_mobs:register_mob("mobs_mc:vindicator", {
mcl_mobs.register_mob("mobs_mc:vindicator", {
description = S("Vindicator"),
type = "monster",
spawn_class = "hostile",
@ -77,4 +77,4 @@ mcl_mobs:register_mob("mobs_mc:vindicator", {
})
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:vindicator", S("Vindicator"), "#959b9b", "#275e61", 0)
mcl_mobs.register_egg("mobs_mc:vindicator", S("Vindicator"), "#959b9b", "#275e61", 0)

View File

@ -25,7 +25,7 @@ local professions = {
nitwit = "mobs_mc_villager.png",
}
mcl_mobs:register_mob("mobs_mc:villager_zombie", {
mcl_mobs.register_mob("mobs_mc:villager_zombie", {
description = S("Zombie Villager"),
type = "monster",
spawn_class = "hostile",
@ -231,4 +231,4 @@ mcl_vars.mg_overworld_max)
--mcl_mobs:spawn_specific("mobs_mc:villager_zombie", "overworld", "ground", 0, 7, 30, 60000, 4, mcl_vars.mg_overworld_min, mcl_vars.mg_overworld_max)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:villager_zombie", S("Zombie Villager"), "#563d33", "#799c66", 0)
mcl_mobs.register_egg("mobs_mc:villager_zombie", S("Zombie Villager"), "#563d33", "#799c66", 0)

View File

@ -12,7 +12,7 @@ local S = minetest.get_translator("mobs_mc")
mcl_mobs:register_mob("mobs_mc:witch", {
mcl_mobs.register_mob("mobs_mc:witch", {
description = S("Witch"),
type = "monster",
spawn_class = "hostile",
@ -73,7 +73,7 @@ mcl_mobs:register_mob("mobs_mc:witch", {
})
-- potion projectile (EXPERIMENTAL)
mcl_mobs:register_arrow("mobs_mc:potion_arrow", {
mcl_mobs.register_arrow("mobs_mc:potion_arrow", {
visual = "sprite",
visual_size = {x = 0.5, y = 0.5},
--textures = {"vessels_glass_bottle.png"}, --TODO fix to else if default
@ -105,6 +105,6 @@ mcl_mobs:register_arrow("mobs_mc:potion_arrow", {
--mcl_mobs:spawn_specific("mobs_mc:witch", { "mcl_core:jungletree", "mcl_core:jungleleaves", "mcl_flowers:fern", "mcl_core:vine" }, {"air"}, 0, minetest.LIGHT_MAX-6, 12, 20000, 2, mobs_mc.water_level-6, mcl_vars.mg_overworld_max)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:witch", S("Witch"), "#340000", "#51a03e", 0, true)
mcl_mobs.register_egg("mobs_mc:witch", S("Witch"), "#340000", "#51a03e", 0, true)
mcl_wip.register_wip_item("mobs_mc:witch")

View File

@ -9,7 +9,7 @@ local S = minetest.get_translator("mobs_mc")
--################### WITHER
--###################
mcl_mobs:register_mob("mobs_mc:wither", {
mcl_mobs.register_mob("mobs_mc:wither", {
description = S("Wither"),
type = "monster",
spawn_class = "hostile",
@ -85,7 +85,7 @@ mcl_mobs:register_mob("mobs_mc:wither", {
local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
local wither_rose_soil = { "group:grass_block", "mcl_core:dirt", "mcl_core:coarse_dirt", "mcl_nether:netherrack", "group:soul_block", "mcl_mud:mud", "mcl_moss:moss" }
mcl_mobs:register_arrow("mobs_mc:wither_skull", {
mcl_mobs.register_arrow("mobs_mc:wither_skull", {
visual = "sprite",
visual_size = {x = 0.75, y = 0.75},
-- TODO: 3D projectile, replace tetxture
@ -98,7 +98,7 @@ mcl_mobs:register_arrow("mobs_mc:wither_skull", {
full_punch_interval = 0.5,
damage_groups = {fleshy = 8},
}, nil)
mcl_mobs:boom(self, self.object:get_pos(), 1)
mcl_mobs.mob_class.boom(self,self.object:get_pos(), 1)
end,
hit_mob = function(self, mob)
@ -106,7 +106,7 @@ mcl_mobs:register_arrow("mobs_mc:wither_skull", {
full_punch_interval = 0.5,
damage_groups = {fleshy = 8},
}, nil)
mcl_mobs:boom(self, self.object:get_pos(), 1)
mcl_mobs.mob_class.boom(self,self.object:get_pos(), 1)
local l = mob:get_luaentity()
if l and l.health - 8 <= 0 then
local n = minetest.find_node_near(mob:get_pos(),2,wither_rose_soil)
@ -123,12 +123,12 @@ mcl_mobs:register_arrow("mobs_mc:wither_skull", {
-- node hit, explode
hit_node = function(self, pos, node)
mcl_mobs:boom(self, pos, 1)
mcl_mobs.mob_class.boom(self,pos, 1)
end
})
-- TODO: Add blue wither skull
--Spawn egg
mcl_mobs:register_egg("mobs_mc:wither", S("Wither"), "#4f4f4f", "#4f4f4f", 0, true)
mcl_mobs.register_egg("mobs_mc:wither", S("Wither"), "#4f4f4f", "#4f4f4f", 0, true)
mcl_wip.register_wip_item("mobs_mc:wither")

View File

@ -71,7 +71,7 @@ local wolf = {
ent = dog:get_luaentity()
ent.owner = clicker:get_player_name()
ent.tamed = true
mcl_mobs:set_animation(ent, "sit")
ent:set_animation("sit")
ent.walk_chance = 0
ent.jump = false
ent.health = self.health
@ -100,7 +100,7 @@ local wolf = {
specific_attack = { "player", "mobs_mc:sheep" },
}
mcl_mobs:register_mob("mobs_mc:wolf", wolf)
mcl_mobs.register_mob("mobs_mc:wolf", wolf)
-- Tamed wolf
@ -167,7 +167,7 @@ end
dog.on_rightclick = function(self, clicker)
local item = clicker:get_wielded_item()
if mcl_mobs:feed_tame(self, clicker, 1, true, false) then
if self:feed_tame(clicker, 1, true, false) then
return
elseif mcl_mobs:protect(self, clicker) then
return
@ -194,45 +194,17 @@ dog.on_rightclick = function(self, clicker)
end
end
else
-- Toggle sitting order
if not self.owner or self.owner == "" then
-- Huh? This wolf has no owner? Let's fix this! This should never happen.
-- Huh? This dog has no owner? Let's fix this! This should never happen.
self.owner = clicker:get_player_name()
end
local pos = self.object:get_pos()
local particle
if not self.order or self.order == "" or self.order == "sit" then
particle = "mobs_mc_wolf_icon_roam.png"
self.order = "roam"
self.state = "stand"
self.walk_chance = default_walk_chance
self.jump = true
mcl_mobs:set_animation(self, "stand")
-- TODO: Add sitting model
else
particle = "mobs_mc_wolf_icon_sit.png"
self.order = "sit"
self.state = "stand"
self.walk_chance = 0
self.jump = false
mcl_mobs:set_animation(self, "sit")
if not minetest.settings:get_bool("mcl_extended_pet_control",true) then
self:toggle_sit(clicker,-0.4)
end
-- Display icon to show current order (sit or roam)
minetest.add_particle({
pos = vector.add(pos, {x=0,y=1,z=0}),
velocity = {x=0,y=0.2,z=0},
expirationtime = 1,
size = 4,
texture = particle,
playername = self.owner,
glow = minetest.LIGHT_MAX,
})
end
end
mcl_mobs:register_mob("mobs_mc:dog", dog)
mcl_mobs.register_mob("mobs_mc:dog", dog)
-- Spawn
mcl_mobs:spawn_specific(
"mobs_mc:wolf",
@ -258,4 +230,4 @@ minetest.LIGHT_MAX+1,
mobs_mc.water_level+3,
mcl_vars.mg_overworld_max)
mcl_mobs:register_egg("mobs_mc:wolf", S("Wolf"), "#d7d3d3", "#ceaf96", 0)
mcl_mobs.register_egg("mobs_mc:wolf", S("Wolf"), "#d7d3d3", "#ceaf96", 0)

View File

@ -102,7 +102,7 @@ local zombie = {
attack_npcs = true,
}
mcl_mobs:register_mob("mobs_mc:zombie", zombie)
mcl_mobs.register_mob("mobs_mc:zombie", zombie)
-- Baby zombie.
-- A smaller and more dangerous variant of the zombie
@ -123,7 +123,7 @@ baby_zombie.animation = {
punch_start = 109, punch_end = 119
}
mcl_mobs:register_mob("mobs_mc:baby_zombie", baby_zombie)
mcl_mobs.register_mob("mobs_mc:baby_zombie", baby_zombie)
-- Husk.
-- Desert variant of the zombie
@ -140,7 +140,7 @@ husk.sunlight_damage = 0
husk.drops = drops_common
-- TODO: Husks avoid water
mcl_mobs:register_mob("mobs_mc:husk", husk)
mcl_mobs.register_mob("mobs_mc:husk", husk)
-- Baby husk.
-- A smaller and more dangerous variant of the husk
@ -154,7 +154,7 @@ baby_husk.ignited_by_sunlight = false
baby_husk.sunlight_damage = 0
baby_husk.drops = drops_common
mcl_mobs:register_mob("mobs_mc:baby_husk", baby_husk)
mcl_mobs.register_mob("mobs_mc:baby_husk", baby_husk)
-- Spawning
@ -368,5 +368,5 @@ mcl_vars.mg_overworld_min,
mcl_vars.mg_overworld_max)
-- Spawn eggs
mcl_mobs:register_egg("mobs_mc:husk", S("Husk"), "#777361", "#ded88f", 0)
mcl_mobs:register_egg("mobs_mc:zombie", S("Zombie"), "#00afaf", "#799c66", 0)
mcl_mobs.register_egg("mobs_mc:husk", S("Husk"), "#777361", "#ded88f", 0)
mcl_mobs.register_egg("mobs_mc:zombie", S("Zombie"), "#00afaf", "#799c66", 0)

View File

@ -93,7 +93,7 @@ local pigman = {
fire_damage_resistant = true,
}
mcl_mobs:register_mob("mobs_mc:pigman", pigman)
mcl_mobs.register_mob("mobs_mc:pigman", pigman)
-- Baby pigman.
-- A smaller and more dangerous variant of the pigman
@ -113,7 +113,7 @@ baby_pigman.run_velocity = 2.4
baby_pigman.light_damage = 0
baby_pigman.child = 1
mcl_mobs:register_mob("mobs_mc:baby_pigman", baby_pigman)
mcl_mobs.register_mob("mobs_mc:baby_pigman", baby_pigman)
-- Regular spawning in the Nether
mcl_mobs:spawn_specific(
@ -152,4 +152,4 @@ mcl_vars.mg_nether_max)
--mobs:spawn_specific("mobs_mc:pigman", {"mcl_portals:portal"}, {"air"}, 0, minetest.LIGHT_MAX+1, 30, 500, 4, mcl_vars.mg_overworld_min, mcl_vars.mg_overworld_max)
-- spawn eggs
mcl_mobs:register_egg("mobs_mc:pigman", S("Zombie Pigman"), "#ea9393", "#4c7129", 0)
mcl_mobs.register_egg("mobs_mc:pigman", S("Zombie Pigman"), "#ea9393", "#4c7129", 0)

View File

@ -9,7 +9,7 @@ function mcl_bells.ring_once(pos)
local vv=minetest.get_objects_inside_radius(pos,150)
for _,o in pairs(vv) do
if o.type == "npc" then
mcl_mobs:gopath(o:get_luaentity(),pos,function() end)
o:get_luaentity():gopath(pos,function() end)
end
end
end

View File

@ -222,6 +222,9 @@ mcl_bookshelf_inventories (Enable bookshelf inventories) bool true
# Enable swiftness on enchanted golden apples
mcl_enable_fapples (Enable swiftness on enchanted golden apples) bool true
# All tameable mobs listen to the "sit" righclick like dogs
mcl_extended_pet_control (Extended pet control) bool true
[Debugging]
# If enabled, this will show the itemstring of an item in the description.
mcl_item_id_debug (Item ID Debug) bool false