Mob API: Merge mobs_mc and mcl_mobs into one mod

DO NOT USE IN PRODUCTION, DO NOT START OLD WORLDS WITHOUT A BACKUP
These are the first steps of the new mob API. The game does actually start, but mobs do not work yet.
You will also get some warnings about mob spawners, but don't worry about that.
This is really just some 'first impression' of how the mob API is gonna look like. Some things are already complete, like the agression system.
AI and attacking have not been worked on yet.
mobs_mc and mcl_mobs have actually been merged into one piece but I will probably change that again in the future actually, and split the different mobs into different mods.
There are also a few usefull things like the universal mount API and a more general purpose smoke API, but all of this is still far from complete.
I'll put some work into the API this week but probably not next week, then I'll see but don't expect this to be done before 2022.
I'll work on it, but I'll do it slowly and progressively to not get burned out again and to still have enough time to graduate from school in the meantime.
This commit is contained in:
Elias Fleckenstein 2021-09-01 23:27:47 +02:00
parent 1601437510
commit 0b27b6bec3
607 changed files with 4664 additions and 7678 deletions

@ -13,7 +13,7 @@ mcl_damage = {
starve = {bypasses_armor = true, bypasses_magic = true}, starve = {bypasses_armor = true, bypasses_magic = true},
cactus = {}, cactus = {},
fall = {bypasses_armor = true}, fall = {bypasses_armor = true},
fly_into_wall = {bypasses_armor = true}, -- unused fly_into_wall = {bypasses_armor = true},
out_of_world = {bypasses_armor = true, bypasses_magic = true, bypasses_invulnerability = true}, out_of_world = {bypasses_armor = true, bypasses_magic = true, bypasses_invulnerability = true},
generic = {bypasses_armor = true}, generic = {bypasses_armor = true},
magic = {is_magic = true, bypasses_armor = true}, magic = {is_magic = true, bypasses_armor = true},
@ -28,7 +28,7 @@ mcl_damage = {
fireball = {is_projectile = true, is_fire = true}, fireball = {is_projectile = true, is_fire = true},
thorns = {is_magic = true}, thorns = {is_magic = true},
explosion = {is_explosion = true}, explosion = {is_explosion = true},
cramming = {bypasses_armor = true}, -- unused cramming = {bypasses_armor = true},
fireworks = {is_explosion = true}, -- unused fireworks = {is_explosion = true}, -- unused
} }
} }
@ -74,11 +74,11 @@ function mcl_damage.from_punch(mcl_reason, object)
mcl_reason.direct = object mcl_reason.direct = object
local luaentity = mcl_reason.direct:get_luaentity() local luaentity = mcl_reason.direct:get_luaentity()
if luaentity then if luaentity then
if luaentity._is_arrow then if luaentity.is_arrow then
mcl_reason.type = "arrow" mcl_reason.type = "arrow"
elseif luaentity._is_fireball then elseif luaentity.is_fireball then
mcl_reason.type = "fireball" mcl_reason.type = "fireball"
elseif luaentity._cmi_is_mob then elseif luaentity.is_mob then
mcl_reason.type = "mob" mcl_reason.type = "mob"
end end
mcl_reason.source = mcl_reason.source or luaentity._source_object mcl_reason.source = mcl_reason.source or luaentity._source_object

@ -69,55 +69,61 @@ end
-- 3 exptime variants because the animation is not tied to particle expiration time. -- 3 exptime variants because the animation is not tied to particle expiration time.
-- 3 colorized variants to imitate minecraft's -- 3 colorized variants to imitate minecraft's
local smoke_pdef_cached = {}
function mcl_particles.spawn_smoke(pos, name, smoke_pdef_base) function mcl_particles.get_smoke_def(def_base)
local min = math.min local defs = {}
local new_minpos = vector.add(pos, smoke_pdef_base.minrelpos)
local new_maxpos = vector.add(pos, smoke_pdef_base.maxrelpos)
-- populate the cache local def = table.copy(def_base)
if smoke_pdef_cached[name] then def.amount = def.amount / 9
for i, smoke_pdef in ipairs(smoke_pdef_cached[name]) do def.time = 0
smoke_pdef.minpos = new_minpos def.animation = {
smoke_pdef.maxpos = new_maxpos type = "vertical_frames",
mcl_particles.add_node_particlespawner(pos, smoke_pdef, "high") aspect_w = 8,
end aspect_h = 8,
-- cache already populated -- length = 3 exptime variants
else }
smoke_pdef_cached[name] = {} def.collisiondetection = true
local smoke_pdef = table.copy(smoke_pdef_base) -- the last frame plays for 1/8 * N seconds, so we can take advantage of it
smoke_pdef.amount = smoke_pdef_base.amount / 9 -- to have varying exptime for each variant.
smoke_pdef.time = 0 local exptimes = {0.175, 0.375, 1.0}
smoke_pdef.animation = { local colorizes = {"199", "209", "243"} -- round(78%, 82%, 90% of 256) - 1
type = "vertical_frames", for _, exptime in ipairs(exptimes) do
aspect_w = 8, for _, colorize in ipairs(colorizes) do
aspect_h = 8, def.maxexptime = exptime * def_base.maxexptime
-- length = 3 exptime variants def.animation.length = exptime + 0.1
} -- minexptime must be set such that the last frame is actully rendered,
smoke_pdef.collisiondetection = true -- even if its very short. Larger exptime -> larger range
smoke_pdef.minpos = new_minpos def.minexptime = math.min(exptime, (7.0 / 8.0 * (exptime + 0.1) + 0.1))
smoke_pdef.maxpos = new_maxpos def.texture = "mcl_particles_smoke_anim.png^[colorize:#000000:" .. colorize
-- the last frame plays for 1/8 * N seconds, so we can take advantage of it table.insert(defs, table.copy(def))
-- to have varying exptime for each variant.
local exptimes = { 0.175, 0.375, 1.0 }
local colorizes = { "199", "209", "243" } -- round(78%, 82%, 90% of 256) - 1
for _,exptime in ipairs(exptimes) do
for _,colorize in ipairs(colorizes) do
smoke_pdef.maxexptime = exptime * smoke_pdef_base.maxexptime
smoke_pdef.animation.length = exptime + 0.1
-- minexptime must be set such that the last frame is actully rendered,
-- even if its very short. Larger exptime -> larger range
smoke_pdef.minexptime = min(exptime, (7.0/8.0 * (exptime + 0.1) + 0.1))
smoke_pdef.texture = "mcl_particles_smoke_anim.png^[colorize:#000000:" ..colorize
mcl_particles.add_node_particlespawner(pos, smoke_pdef, "high")
table.insert(smoke_pdef_cached[name], table.copy(smoke_pdef))
end
end end
end end
return defs
end
function mcl_particles.add_node_smoke_particlespawner(pos, defs)
local minpos = vector.add(pos, defs[1].minrelpos)
local maxpos = vector.add(pos, defs[1].maxrelpos)
for i, def in ipairs(defs) do
def.minpos = minpos
def.maxpos = maxpos
def.attached = nil
mcl_particles.add_node_particlespawner(pos, def, "high")
end
end
function mcl_particles.add_object_smoke_particlespawner(obj, defs)
local minpos = defs[1].minrelpos
local maxpos = defs[1].maxrelpos
for i, def in ipairs(defs) do
def.minpos = def.minrelpos
def.maxpos = def.maxrelpos
def.attached = obj
minetest.add_particlespawner(def)
end
end end

Before

Width:  |  Height:  |  Size: 895 B

After

Width:  |  Height:  |  Size: 895 B

@ -17,6 +17,8 @@ Glass breaking sounds (CC BY 3.0):
default_tool_breaks.ogg by EdgardEdition (CC BY 3.0), http://www.freesound.org/people/EdgardEdition default_tool_breaks.ogg by EdgardEdition (CC BY 3.0), http://www.freesound.org/people/EdgardEdition
mcl_sounds_poof.ogg by Planman (CC 0), https://freesound.org/people/Planman/sounds/208111/
Mito551 (sounds) (CC BY-SA 3.0): Mito551 (sounds) (CC BY-SA 3.0):
default_dig_choppy.ogg default_dig_choppy.ogg
default_dig_cracky.ogg default_dig_cracky.ogg

@ -477,32 +477,22 @@ end
function mcl_util.deal_damage(target, damage, mcl_reason) function mcl_util.deal_damage(target, damage, mcl_reason)
local luaentity = target:get_luaentity() local luaentity = target:get_luaentity()
if luaentity then if luaentity and luaentity.deal_damage then
if luaentity.deal_damage then luaentity:deal_damage(damage, mcl_reason or {type = "generic"})
luaentity:deal_damage(damage, mcl_reason or {type = "generic"}) else
return local hp = target:get_hp()
elseif luaentity._cmi_is_mob then
-- local puncher = mcl_reason and mcl_reason.direct or target if hp > 0 then
-- target:punch(puncher, 1.0, {full_punch_interval = 1.0, damage_groups = {fleshy = damage}}, vector.direction(puncher:get_pos(), target:get_pos()), damage) target:set_hp(hp - damage, {_mcl_reason = mcl_reason})
if luaentity.health > 0 then
luaentity.health = luaentity.health - damage
end
return
end end
end end
local hp = target:get_hp()
if hp > 0 then
target:set_hp(hp - damage, {_mcl_reason = mcl_reason})
end
end end
function mcl_util.get_hp(obj) function mcl_util.get_hp(obj)
local luaentity = obj:get_luaentity() local luaentity = obj:get_luaentity()
if luaentity and luaentity._cmi_is_mob then if luaentity and luaentity.is_mob then
return luaentity.health return luaentity.data.health
else else
return obj:get_hp() return obj:get_hp()
end end

@ -12,51 +12,40 @@ local function is_group(pos, group)
return minetest.get_item_group(nn, group) ~= 0 return minetest.get_item_group(nn, group) ~= 0
end end
local is_water = flowlib.is_water
local function is_ice(pos)
return is_group(pos, "ice")
end
local function get_sign(i)
if i == 0 then
return 0
else
return i / math.abs(i)
end
end
local function get_velocity(v, yaw, y) local function get_velocity(v, yaw, y)
local x = -math.sin(yaw) * v return vector.add(vector.new(0, y, 0), vector.multiply(minetest.yaw_to_dir(yaw), v))
local z = math.cos(yaw) * v
return {x = x, y = y, z = z}
end end
local function get_v(v) local function get_v(v)
return math.sqrt(v.x ^ 2 + v.z ^ 2) return math.sqrt(v.x ^ 2 + v.z ^ 2)
end end
local function check_object(obj) function get_sign(i)
return obj and (obj:is_player() or obj:get_luaentity()) and obj if not i or i == 0 then
return 0
else
return i / math.abs(i)
end
end end
local function get_visual_size(obj) local is_water = flowlib.is_water
return obj:is_player() and {x = 1, y = 1, z = 1} or obj:get_luaentity()._old_visual_size or obj:get_properties().visual_size
local function is_ice(pos)
return is_group(pos, "ice")
end end
local function set_attach(boat) local function set_attach(boat)
boat._driver:set_attach(boat.object, "", boat._driver:set_attach(boat.object, "", vector.new(0, 0.42, -1), vector.new(0, 0, 0))
{x = 0, y = 0.42, z = -1}, {x = 0, y = 0, z = 0})
end end
local function set_double_attach(boat) local function set_double_attach(boat)
boat._driver:set_attach(boat.object, "", boat._driver:set_attach(boat.object, "", vector.new(0, 0.42, 0.8), vector.new(0, 0, 0))
{x = 0, y = 0.42, z = 0.8}, {x = 0, y = 0, z = 0}) boat._passenger:set_attach(boat.object, "", vector.new(0, 0.42, -2.2), vector.new(0, 0, 0))
boat._passenger:set_attach(boat.object, "",
{x = 0, y = 0.42, z = -2.2}, {x = 0, y = 0, z = 0})
end end
local function attach_object(self, obj) local function enter_boat(self, obj)
mcl_mount.mount(obj, self.object)
if self._driver then if self._driver then
if self._driver:is_player() then if self._driver:is_player() then
self._passenger = obj self._passenger = obj
@ -69,39 +58,6 @@ local function attach_object(self, obj)
self._driver = obj self._driver = obj
set_attach(self) set_attach(self)
end end
local visual_size = get_visual_size(obj)
local yaw = self.object:get_yaw()
obj:set_properties({visual_size = vector.divide(visual_size, boat_visual_size)})
if obj:is_player() then
local name = obj:get_player_name()
mcl_player.player_attached[name] = true
minetest.after(0.2, function(name)
local player = minetest.get_player_by_name(name)
if player then
mcl_player.player_set_animation(player, "sit" , 30)
end
end, name)
obj:set_look_horizontal(yaw)
mcl_tmp_message.message(obj, S("Sneak to dismount"))
else
obj:get_luaentity()._old_visual_size = visual_size
end
end
local function detach_object(obj, change_pos)
obj:set_detach()
obj:set_properties({visual_size = get_visual_size(obj)})
if obj:is_player() then
mcl_player.player_attached[obj:get_player_name()] = false
mcl_player.player_set_animation(obj, "stand" , 30)
else
obj:get_luaentity()._old_visual_size = nil
end
if change_pos then
obj:set_pos(vector.add(obj:get_pos(), vector.new(0, 0.2, 0)))
end
end end
-- --
@ -131,16 +87,13 @@ local boat = {
_damage_anim = 0, _damage_anim = 0,
} }
minetest.register_on_respawnplayer(detach_object)
function boat.on_rightclick(self, clicker) function boat.on_rightclick(self, clicker)
if self._passenger or not clicker or clicker:get_attach() then if self._passenger or not clicker or clicker:get_attach() then
return return
end end
attach_object(self, clicker) enter_boat(self, clicker)
end end
function boat.on_activate(self, staticdata, dtime_s) function boat.on_activate(self, staticdata, dtime_s)
self.object:set_armor_groups({fleshy = 100}) self.object:set_armor_groups({fleshy = 100})
local data = minetest.deserialize(staticdata) local data = minetest.deserialize(staticdata)
@ -172,10 +125,10 @@ function boat.on_death(self, killer)
minetest.add_item(self.object:get_pos(), self._itemstring) minetest.add_item(self.object:get_pos(), self._itemstring)
end end
if self._driver then if self._driver then
detach_object(self._driver) mcl_mount.throw_off(self._driver)
end end
if self._passenger then if self._passenger then
detach_object(self._passenger) mcl_mount.throw_off(self._passenger)
end end
self._driver = nil self._driver = nil
self._passenger = nil self._passenger = nil
@ -235,19 +188,13 @@ function boat.on_step(self, dtime, moveresult)
local had_passenger = self._passenger local had_passenger = self._passenger
self._driver = check_object(self._driver) self._driver = self._driver and self._driver:get_attach() == self.object and self._driver
self._passenger = check_object(self._passenger) self._passenger = self._passenger and self._passenger:get_attach() == self.object and self._passenger
if self._passenger then if self._passenger then
if not self._driver then if not self._driver then
self._driver = self._passenger self._driver = self._passenger
self._passenger = nil self._passenger = nil
else
local ctrl = self._passenger:get_player_control()
if ctrl and ctrl.sneak then
detach_object(self._passenger, true)
self._passenger = nil
end
end end
end end
@ -256,13 +203,8 @@ function boat.on_step(self, dtime, moveresult)
set_attach(self) set_attach(self)
end end
local ctrl = self._driver:get_player_control() local ctrl = self._driver:get_player_control()
if ctrl and ctrl.sneak then
detach_object(self._driver, true)
self._driver = nil
return
end
local yaw = self.object:get_yaw() local yaw = self.object:get_yaw()
if ctrl.up then if ctrl and ctrl.up then
-- Forwards -- Forwards
self._v = self._v + 0.1 * v_factor self._v = self._v + 0.1 * v_factor
@ -271,7 +213,7 @@ function boat.on_step(self, dtime, moveresult)
self.object:set_animation({x=0, y=40}, paddling_speed, 0, true) self.object:set_animation({x=0, y=40}, paddling_speed, 0, true)
self._animation = 1 self._animation = 1
end end
elseif ctrl.down then elseif ctrl and ctrl.down then
-- Backwards -- Backwards
self._v = self._v - 0.1 * v_factor self._v = self._v - 0.1 * v_factor
@ -309,8 +251,8 @@ function boat.on_step(self, dtime, moveresult)
for _, obj in pairs(minetest.get_objects_inside_radius(self.object:get_pos(), 1.3)) do for _, obj in pairs(minetest.get_objects_inside_radius(self.object:get_pos(), 1.3)) do
local entity = obj:get_luaentity() local entity = obj:get_luaentity()
if entity and entity._cmi_is_mob then if entity and entity.is_mob then
attach_object(self, obj) enter_boat(self, obj)
break break
end end
end end

@ -1,7 +1,17 @@
local S = minetest.get_translator("mcl_burning") local S = minetest.get_translator("mcl_burning")
function mcl_burning.get_storage(obj) function mcl_burning.get_storage(obj)
return obj:is_player() and mcl_burning.storage[obj] or obj:get_luaentity() if obj:is_player() then
return mcl_burning.storage[obj]
else
local luaentity = obj:get_luaentity()
if luaentity.is_mob then
return luaentity.data
end
return luaentity
end
end end
function mcl_burning.is_burning(obj) function mcl_burning.is_burning(obj)
@ -82,21 +92,8 @@ function mcl_burning.set_on_fire(obj, burn_time)
storage.fire_damage_timer = 0 storage.fire_damage_timer = 0
local fire_entity = minetest.add_entity(obj:get_pos(), "mcl_burning:fire") local fire_entity = minetest.add_entity(obj:get_pos(), "mcl_burning:fire")
local minp, maxp = mcl_burning.get_collisionbox(obj, false, storage)
local obj_size = obj:get_properties().visual_size
local vertical_grow_factor = 1.2
local horizontal_grow_factor = 1.1
local grow_vector = vector.new(horizontal_grow_factor, vertical_grow_factor, horizontal_grow_factor)
local size = vector.subtract(maxp, minp)
size = vector.multiply(size, grow_vector)
size = vector.divide(size, obj_size)
local offset = vector.new(0, size.y * 10 / 2, 0)
fire_entity:set_properties({visual_size = size})
fire_entity:set_attach(obj, "", offset, {x = 0, y = 0, z = 0})
local fire_luaentity = fire_entity:get_luaentity() local fire_luaentity = fire_entity:get_luaentity()
fire_luaentity:update_visual_size(obj, storage)
fire_luaentity:update_frame(obj, storage) fire_luaentity:update_frame(obj, storage)
for _, other in pairs(minetest.get_objects_inside_radius(fire_entity:get_pos(), 0)) do for _, other in pairs(minetest.get_objects_inside_radius(fire_entity:get_pos(), 0)) do
@ -136,12 +133,7 @@ function mcl_burning.tick(obj, dtime, storage)
if storage.fire_damage_timer >= 1 then if storage.fire_damage_timer >= 1 then
storage.fire_damage_timer = 0 storage.fire_damage_timer = 0
mcl_util.deal_damage(obj, 1, {type = "on_fire"})
local luaentity = obj:get_luaentity()
if not luaentity or not luaentity.fire_damage_resistant then
mcl_util.deal_damage(obj, 1, {type = "on_fire"})
end
end end
end end
end end

@ -35,6 +35,21 @@ minetest.register_globalstep(function(dtime)
end end
end) end)
mcl_damage.register_modifier(function(obj, damage, reason)
if reason.is_fire then
local luaentity = obj:get_luaentity()
if luaentity and luaentity.no_fire_damage then
return 0
end
end
end, -200)
mcl_damage.register_on_damage(function(obj, damage, reason)
if reason.direct and mcl_burning.is_burning(obj) then
mcl_burning.set_on_fire(obj, 5)
end
end)
minetest.register_on_respawnplayer(function(player) minetest.register_on_respawnplayer(function(player)
mcl_burning.extinguish(player) mcl_burning.extinguish(player)
end) end)
@ -113,4 +128,23 @@ minetest.register_entity("mcl_burning:fire", {
parent:hud_change(storage.fire_hud_id, "text", "mcl_burning_hud_flame_animated.png" .. frame_overlay) parent:hud_change(storage.fire_hud_id, "text", "mcl_burning_hud_flame_animated.png" .. frame_overlay)
end end
end, end,
update_visual_size = function(self, parent, storage)
parent = parent or self.object:get_attach()
storage = storage or mcl_burning.get_storage(parent)
local minp, maxp = mcl_burning.get_collisionbox(parent, false, storage)
local obj_size = parent:get_properties().visual_size
local vertical_grow_factor = 1.2
local horizontal_grow_factor = 1.1
local grow_vector = vector.new(horizontal_grow_factor, vertical_grow_factor, horizontal_grow_factor)
local size = vector.subtract(maxp, minp)
size = vector.multiply(size, grow_vector)
size = vector.divide(size, obj_size)
local offset = vector.new(0, size.y * 10 / 2, 0)
self.object:set_properties({visual_size = size})
self.object:set_attach(parent, "", offset)
end,
}) })

@ -1,3 +1,4 @@
name = mcl_burning name = mcl_burning
description = Burning Objects for MineClone2 description = Burning Objects for MineClone2
author = Fleckenstein author = Fleckenstein
depends = mcl_damage

@ -1,4 +1,4 @@
# Credits licensing for media files in `mobs_mc` # Credits licensing for media files in `mcl_mobs`
## Licenses used ## Licenses used
@ -69,7 +69,7 @@ Origin of those models:
* `mobs_mc_chicken.png` * `mobs_mc_chicken.png`
* `mobs_mc_wither.png` * `mobs_mc_wither.png`
* `mobs_mc_wither_skeleton.png` * `mobs_mc_wither_skeleton.png`
* `mobs_mc_TEMP_wither_projectile.png` * `mobs_mc_TEMP_wither_projectile.png`
* Gerold55 * Gerold55
* `mobs_mc_mooshroom_brown.png` (CC0) * `mobs_mc_mooshroom_brown.png` (CC0)
* `mobs_mc_mushroom_brown.png` (CC0) * `mobs_mc_mushroom_brown.png` (CC0)

@ -0,0 +1,33 @@
# MineClone2 Mobs
This is a merged version of Mobs redo MineClone2 Edition (API) and mobs_mc (Mobs content).
The API was rewritten by jordan4ibanez and later Fleckenstein from Mobs redo MineClone2 Edition and merged with mobs_mc by Fleckenstein.
Mobs redo MineClone2 Edition was built by Wuzzy2 and contributors from Mobs Redo. Mobs redo was built by TenPlus1 from Simple Mobs by PilzAdam and mobs_mc was built by maikerumine.
Seems like we've come a long way since 2012.
## Credits
* Fleckenstein: Rewrite of jordan's work, merged mobs_mc and mcl_mobs
* jordan4ibanez: Rewrite of the mcl_mobs API
* [maikerumine](https://github.com/maikerumine): Creator of mobs_mc (Coding behaviour, spawning, drops, and misc)
* TenPlus1: Built the original Mobs Redo API
* PilzAdam: Created Simple Mobs which Mobs Redo is based on together with KrupnoPavel, Zeg9, ExeterDad and AspireMint
* [Wuzzy2](https://github.com/Wuzzy2): Zombies, husks, item textures, and code, created Mobs redo MineClone2 Edition
* [toby109tt](https://github.com/tobyplowy): Mapping fixes - better 2D planes
* [22i](https://github.com/22i): Models (done in Blender) and mob icons for spawn eggs
* [XSSheep](https://www.planetminecraft.com/member/xssheep/): Mob and item textures (from [Pixel Perfection](https://www.planetminecraft.com/texture_pack/131pixel-perfection/))
* MysticTempest: More mob textures
* See `LICENSE-MEDIA.md` for detailed credits about each file
## Licensing
* Media: MIT, CC0, CC BY 3.0 CC BY-SA 4.0, LGPLv2.1, GPLv3. See `LICENSE-MEDIA.md` for details
* License of mobs_mc code: GPLv3
* Mobs Redo: See `LICENSE-API.txt`
### Links
* [`mobs_mc`](https://github.com/maikerumine/mobs_mc)
* [Blender models](https://github.com/22i/minecraft-voxel-blender-models)
* [How to recreate mobs from textures with Blender and Gimp](http://imgur.com/a/Iqg88)

@ -1,6 +1,8 @@
Mobs Redo: MineClone 2 Edition Mobs Redo: MineClone 2 Edition
API documentation API documentation
IMPORTANT NOTE: This is completely outdated and needs to be rewritten
============================== ==============================
Welcome to the world of mobs in Minetest and hopefully an easy guide to defining Welcome to the world of mobs in Minetest and hopefully an easy guide to defining
@ -641,7 +643,7 @@ mobs:register_mob("mob_horse:horse", {
visual_size = {x = 1.20, y = 1.20}, visual_size = {x = 1.20, y = 1.20},
mesh = "mobs_horse.x", mesh = "mobs_horse.x",
collisionbox = {-0.4, -0.01, -0.4, 0.4, 1.25, 0.4}, collisionbox = {-0.4, -0.01, -0.4, 0.4, 1.25, 0.4},
animation = { animation = {
speed_normal = 15, speed_normal = 15,
speed_run = 30, speed_run = 30,
stand_start = 25, stand_start = 25,

@ -0,0 +1,29 @@
function mcl_mobs.mob:ai_step(dtime)
--[[
if self.has_head then
mobs.do_head_logic(self,dtime)
end
--]]
self:float_step()
if self.jump_only then
jump_state_switch(self, dtime)
jump_state_execution(self, dtime)
--swimming
elseif self.swim then
swim_state_switch(self, dtime)
swim_state_execution(self, dtime)
--flying
elseif self.fly then
fly_state_switch(self, dtime)
fly_state_execution(self, dtime)
--regular mobs that walk around
else
land_state_switch(self, dtime)
land_state_execution(self, dtime)
end
--make it so mobs do not glitch out when walking around/jumping
self:swap_auto_step_height_adjust()
end

@ -0,0 +1,74 @@
--[[
mobs.explode_attack_walk = function(self,dtime)
--this needs an exception
if self.attacking == nil or not self.attacking:is_player() then
self.attacking = nil
return
end
self:look_at(self.attack)
local distance_from_attacking = vector.distance(self.object:get_pos(), self.attacking:get_pos())
--make mob walk up to player within 2 nodes distance then start exploding
if distance_from_attacking >= self.reach and
--don't allow explosion to cancel unless out of the reach boundary
not (self.explosion_animation ~= nil and self.explosion_animation > 0 and distance_from_attacking <= self.defuse_reach) then
mobs.set_velocity(self, self.run_velocity)
mobs.set_mob_animation(self,"run")
mobs.reverse_explosion_animation(self,dtime)
else
mobs.set_velocity(self,0)
--this is the only way I can reference this without dumping extra data on all mobs
if not self.explosion_animation then
self.explosion_animation = 0
end
--play ignite sound
if self.explosion_animation == 0 then
mobs.play_sound(self,"attack")
end
mobs.set_mob_animation(self,"stand")
mobs.handle_explosion_animation(self)
self.explosion_animation = self.explosion_animation + (dtime/2.5)
end
--make explosive mobs jump
--check for nodes to jump over
--explosive mobs will just ride against walls for now
local node_in_front_of = mobs.jump_check(self)
if node_in_front_of == 1 then
mobs.jump(self)
end
--do biggening explosion thing
if self.explosion_animation and self.explosion_animation > self.explosion_timer then
mcl_explosions.explode(self.object:get_pos(), self.explosion_strength,{ drop_chance = 1.0 })
self.object:remove()
end
end
--this is a small helper function to make working with explosion animations easier
mobs.reverse_explosion_animation = function(self,dtime)
--if explosion animation was greater than 0 then reverse it
if self.explosion_animation ~= nil and self.explosion_animation > 0 then
self.explosion_animation = self.explosion_animation - dtime
if self.explosion_animation < 0 then
self.explosion_animation = 0
end
end
mobs.handle_explosion_animation(self)
end
--]]

@ -0,0 +1,31 @@
--[[
mobs.shoot_projectile_handling = function(arrow_item, pos, dir, yaw, shooter, power, damage, is_critical, bow_stack, collectable, gravity)
local obj = mcl_bows.shoot_arrow(arrow_item, pos, dir, yaw, shooter, power, damage, is_critical, bow_stack, collectable, gravity, true)
--play custom shoot sound
if shooter ~= nil and shooter.shoot_sound then
minetest.sound_play(shooter.shoot_sound, {pos=pos, max_hear_distance=16}, true)
end
return obj
end
--do internal per mob projectile calculations
mobs.shoot_projectile = function(self)
local pos1 = self.object:get_pos()
--add mob eye height
pos1.y = pos1.y + self.eye_height
local pos2 = self.attacking:get_pos()
--add player eye height
pos2.y = pos2.y + mcl_mobs.util.get_eye_height(self.attacking)
--get direction
local dir = vector.direction(pos1,pos2)
--call internal shoot_arrow function
self.shoot_arrow(self,pos1,dir)
end
]]--

@ -0,0 +1,72 @@
--[[
local random_pitch_multiplier = {-1,1}
mobs.projectile_attack_fly = function(self, dtime)
--this needs an exception
if self.attacking == nil or not self.attacking:is_player() then
self.attacking = nil
return
end
--this is specifically for random ghast movement
if self.fly_random_while_attack then
--enable rotation locking
mobs.movement_rotation_lock(self)
self.walk_timer = self.walk_timer - dtime
--reset the walk timer
if self.walk_timer <= 0 then
--re-randomize the walk timer
self.walk_timer = math.random(1,6) + math.random()
--set the mob into a random direction
self.yaw = (math.random() * (math.pi * 2))
--create a truly random pitch, since there is no easy access to pitch math that I can find
self.pitch = math.random() * math.random(1,3) * random_pitch_multiplier[math.random(1,2)]
end
mobs.set_fly_velocity(self, self.run_velocity)
else
self:look_at(self.attack)
local distance_from_attacking = vector.distance(self.object:get_pos(), self.attacking:get_pos())
if distance_from_attacking >= self.reach then
mobs.set_pitch_while_attacking(self)
mobs.set_fly_velocity(self, self.run_velocity)
mobs.set_mob_animation(self,"run")
else
mobs.set_pitch_while_attacking(self)
mobs.set_fly_velocity(self, 0)
mobs.set_mob_animation(self,"stand")
end
end
--do this to not load data into other mobs
if not self.projectile_timer then
self.projectile_timer = math.random(self.projectile_cooldown_min, self.projectile_cooldown_max)
end
--run projectile timer
if self.projectile_timer > 0 then
self.projectile_timer = self.projectile_timer - dtime
--shoot
if self.projectile_timer <= 0 then
if self.fly_random_while_attack then
self:look_at(self.attack)
self.walk_timer = 0
end
--reset timer
self.projectile_timer = math.random(self.projectile_cooldown_min, self.projectile_cooldown_max)
mobs.shoot_projectile(self)
end
end
end
]]--

@ -0,0 +1,52 @@
--[[
mobs.projectile_attack_walk = function(self,dtime)
--this needs an exception
if self.attacking == nil or not self.attacking:is_player() then
self.attacking = nil
return
end
self:look_at(self.attack)
local distance_from_attacking = vector.distance(self.object:get_pos(), self.attacking:get_pos())
if distance_from_attacking >= self.reach then
mobs.set_velocity(self, self.run_velocity)
mobs.set_mob_animation(self,"run")
else
mobs.set_velocity(self,0)
mobs.set_mob_animation(self,"stand")
end
--do this to not load data into other mobs
if not self.projectile_timer then
self.projectile_timer = math.random(self.projectile_cooldown_min, self.projectile_cooldown_max)
end
--run projectile timer
if self.projectile_timer > 0 then
self.projectile_timer = self.projectile_timer - dtime
--shoot
if self.projectile_timer <= 0 then
--reset timer
self.projectile_timer = math.random(self.projectile_cooldown_min, self.projectile_cooldown_max)
mobs.shoot_projectile(self)
end
end
--make shooty mobs jump
--check for nodes to jump over
--explosive mobs will just ride against walls for now
local node_in_front_of = mobs.jump_check(self)
if node_in_front_of == 1 then
mobs.jump(self)
end
end
]]--

@ -0,0 +1,87 @@
--[[
mobs.punch_attack_walk = function(self,dtime)
--this needs an exception
if self.attacking == nil or not self.attacking:is_player() then
self.attacking = nil
return
end
local distance_from_attacking = mobs.get_2d_distance(self.object:get_pos(), self.attacking:get_pos())
if distance_from_attacking >= self.minimum_follow_distance then
mobs.set_velocity(self, self.run_velocity)
mobs.set_mob_animation(self, "run")
else
mobs.set_velocity(self, 0)
mobs.set_mob_animation(self, "stand")
end
self:look_at(self.attack)
--make punchy mobs jump
--check for nodes to jump over
--explosive mobs will just ride against walls for now
local node_in_front_of = mobs.jump_check(self)
if node_in_front_of == 1 then
mobs.jump(self)
end
--mobs that can climb over stuff
if self.always_climb and node_in_front_of > 0 then
mobs.climb(self)
end
--auto reset punch_timer
if not self.punch_timer then
self.punch_timer = 0
end
if self.punch_timer > 0 then
self.punch_timer = self.punch_timer - dtime
end
end
mobs.punch_attack = function(self)
self.attacking:punch(self.object, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = self.damage}
}, nil)
self.punch_timer = self.punch_timer_cooloff
--knockback
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = self.attacking:get_pos()
pos2.y = 0
local dir = vector.direction(pos1,pos2)
dir = vector.multiply(dir,3)
if self.attacking:get_velocity().y <= 1 then
dir.y = 5
end
self.attacking:add_velocity(dir)
end
--]]
--[[
--integrate mob punching into collision detection
local check_for_attack = false
if self.attack_type == "punch" and self.hostile and self.attacking then
check_for_attack = true
end
if check_for_attack and self.punch_timer <= 0 then
if object == self.attacking then
mobs.punch_attack(self)
end
end
]]--

@ -0,0 +1,22 @@
--[[
mobs.climb = function(self)
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = 0,
y = DEFAULT_CLIMB_SPEED,
z = 0,
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
new_velocity_addition.x = 0
new_velocity_addition.z = 0
--smooths out mobs a bit
if vector.length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
]]

@ -0,0 +1,13 @@
function mcl_mobs.mob:float_step()
local vertical_speed
if self.node_type ~= self.last_node_type then
if self.node_type == "air" then
vertical_speed = self.def.float_in_air
elseif self.node_type == "water" then
vertical_speed = self.def.float_in_water
elseif self.node_type == "lava" then
vertical_speed = self.def.float_in_lava
end
end
end

@ -0,0 +1,214 @@
--[[
______ _
| ___| |
| |_ | |_ _
| _| | | | | |
| | | | |_| |
\_| |_|\__, |
__/ |
|___/
]]--
--[[
-- state switching logic (stand, walk, run, attacks)
local fly_state_list_wandering = {"stand", "fly"}
local function fly_state_switch(self, dtime)
if self.hostile and self.attacking then
self.state = "attack"
return
end
self.state_timer = self.state_timer - dtime
if self.state_timer <= 0 then
self.state_timer = math.random(4, 10) + math.random()
self.state = fly_state_list_wandering[math.random(1, #fly_state_list_wandering)]
end
end
--check if a mob needs to turn while flying
local function fly_turn_check(self, dtime)
local pos = self.object:get_pos()
pos.y = pos.y + 0.1
local dir = minetest.yaw_to_dir(self.yaw)
local collisionbox = self.object:get_properties().collisionbox
local radius = collisionbox[4] + 0.5
vector.multiply(dir, radius)
local test_dir = vector.add(pos,dir)
return minetest.get_item_group(minetest.get_node(test_dir).name, "solid") ~= 0
end
--this is to swap the built in engine acceleration modifier
local function fly_physics_swapper(self, inside_fly_node)
--should be flyming, gravity is applied, switch to floating
if inside_fly_node and self.object:get_acceleration().y ~= 0 then
self.object:set_acceleration(vector.new(0, 0, 0))
--not be fly, gravity isn't applied, switch to falling
elseif not inside_fly_node and self.object:get_acceleration().y == 0 then
self.pitch = 0
self.object:set_acceleration(vector.new(0, -self.gravity, 0))
end
end
local random_pitch_multiplier = {-1, 1}
-- states are executed here
local function fly_state_execution(self, dtime)
local pos = self.object:get_pos()
pos.y = pos.y + 0.1
local current_node = minetest.get_node(pos).name
local inside_fly_node = minetest.get_item_group(current_node, "solid") == 0
local float_now = false
--recheck if in water or lava
if minetest.get_item_group(current_node, "water") ~= 0 or minetest.get_item_group(current_node, "lava") ~= 0 then
inside_fly_node = false
float_now = true
end
--turn gravity on or off
fly_physics_swapper(self, inside_fly_node)
--fly properly if inside fly node
if inside_fly_node then
if self.state == "stand" then
--do animation
self:set_animation("stand")
self:set_fly_velocity(0)
if self.tilt_fly then
self:set_static_pitch()
end
self:lock_yaw()
elseif self.state == "fly" then
self.walk_timer = self.walk_timer - dtime
--reset the walk timer
if self.walk_timer <= 0 then
--re-randomize the walk timer
self.walk_timer = math.random(1, 6) + math.random()
--set the mob into a random direction
self.yaw = (math.random() * (math.pi * 2))
--create a truly random pitch, since there is no easy access to pitch math that I can find
self.pitch = math.random() * math.random(1, 3) * random_pitch_multiplier[math.random(1,2)]
end
--do animation
self:set_animation("walk")
--do a quick turn to make mob continuously move
--if in a bird cage or something
if fly_turn_check(self, dtime) then
quick_rotate(self, dtime)
end
if self.tilt_fly then
self:set_dynamic_pitch()
end
self:set_fly_velocity(self.walk_velocity)
--enable rotation locking
self:movement_rotation_lock()
elseif self.state == "attack" then
--execute mob attack type
--if self.attack_type == "explode" then
--mobs.explode_attack_fly(self, dtime)
--elseif self.attack_type == "punch" then
--mobs.punch_attack_fly(self,dtime)
if self.attack_type == "projectile" then
self:projectile_attack_fly(dtime)
end
end
else
--make the mob float
if self.floats and float_now then
self:set_velocity(0)
self:float()
if self.tilt_fly then
self:set_static_pitch()
end
end
end
end
-- move mob in facing direction
--this has been modified to be internal
--internal = lua (self.yaw)
--engine = c++ (self.object:get_yaw())
mobs.set_fly_velocity = function(self, v)
local yaw = (self.yaw or 0)
local pitch = (self.pitch or 0)
if v == 0 then
pitch = 0
end
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = (math.sin(yaw) * -v),
y = pitch,
z = (math.cos(yaw) * v),
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
if vector.length(new_velocity_addition) > vector.length(goal_velocity) then
vector.multiply(new_velocity_addition, (vector.length(goal_velocity) / vector.length(new_velocity_addition)))
end
--smooths out mobs a bit
if vector.length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
--a quick and simple pitch calculation between two vector positions
mobs.calculate_pitch = function(pos1, pos2)
if pos1 == nil or pos2 == nil then
return false
end
return(minetest.dir_to_yaw(vector.new(vector.distance(vector.new(pos1.x,0,pos1.z),vector.new(pos2.x,0,pos2.z)),0,pos1.y - pos2.y)) + HALF_PI)
end
--make mobs fly up or down based on their y difference
mobs.set_pitch_while_attacking = function(self)
local pos1 = self.object:get_pos()
local pos2 = self.attacking:get_pos()
local pitch = mobs.calculate_pitch(pos2,pos1)
self.pitch = pitch
end
]]--

@ -0,0 +1,31 @@
function mcl_mobs.mob:check_following()
--ignore
if not self.follow then
self.following_person = nil
return false
end
--hey look, this thing works for passive mobs too!
local follower = mobs.detect_closest_player_within_radius(self,true,self.view_range,self.eye_height)
--check if the follower is a player incase they log out
if follower and follower:is_player() then
local stack = follower:get_wielded_item()
--safety check
if not stack then
self.following_person = nil
return(false)
end
local item_name = stack:get_name()
--all checks have passed, that guy has some good looking food
if item_name == self.follow then
self.following_person = follower
return(true)
end
end
--everything failed
self.following_person = nil
return(false)
end

@ -0,0 +1,169 @@
--[[
--check if a mob needs to jump
mobs.jump_check = function(self,dtime)
local pos = self.object:get_pos()
pos.y = pos.y + 0.1
local dir = minetest.yaw_to_dir(self.yaw)
local collisionbox = self.object:get_properties().collisionbox
local radius = collisionbox[4] + 0.5
vector.multiply(dir, radius)
--only jump if there's a node and a non-solid node above it
local test_dir = vector.add(pos,dir)
local green_flag_1 = minetest.get_item_group(minetest.get_node(test_dir).name, "solid") ~= 0
test_dir.y = test_dir.y + 1
local green_flag_2 = minetest.get_item_group(minetest.get_node(test_dir).name, "solid") == 0
if green_flag_1 and green_flag_2 then
--can jump over node
return(1)
elseif green_flag_1 and not green_flag_2 then
--wall in front of mob
return(2)
end
--nothing to jump over
return(0)
end
--check if a mob needs to turn while jumping
local function jump_turn_check(self, dtime)
local pos = self.object:get_pos()
pos.y = pos.y + 0.1
local dir = minetest.yaw_to_dir(self.yaw)
local collisionbox = self.object:get_properties().collisionbox
local radius = collisionbox[4] + 0.5
vector.multiply(dir, radius)
local test_dir = vector.add(pos,dir)
return minetest.get_item_group(minetest.get_node(test_dir).name, "solid") ~= 0
end
-- state switching logic (stand, jump, run, attacks)
local jump_state_list_wandering = {"stand", "jump"}
local function jump_state_switch(self, dtime)
self.state_timer = self.state_timer - dtime
if self.state_timer <= 0 then
self.state_timer = math.random(4, 10) + math.random()
self.state = jump_state_list_wandering[math.random(1, #jump_state_list_wandering)]
end
end
-- states are executed here
local function jump_state_execution(self, dtime)
local pos = self.object:get_pos()
local collisionbox = self.object:get_properties().collisionbox
--get the center of the mob
pos.y = pos.y + (collisionbox[2] + collisionbox[5] / 2)
local current_node = minetest.get_node(pos).name
local float_now = false
--recheck if in water or lava
if minetest.get_item_group(current_node, "water") ~= 0 or minetest.get_item_group(current_node, "lava") ~= 0 then
float_now = true
end
if self.state == "stand" then
--do animation
self:set_animation("stand")
--set the velocity of the mob
self:set_velocity(0)
self:lock_yaw()
elseif self.state == "jump" then
self.walk_timer = self.walk_timer - dtime
--reset the jump timer
if self.walk_timer <= 0 then
--re-randomize the jump timer
self.walk_timer = math.random(1, 6) + math.random()
--set the mob into a random direction
self.yaw = (math.random() * (math.pi * 2))
end
--do animation
self:set_animation("walk")
--enable rotation locking
self:movement_rotation_lock()
--jumping mobs are more loosey goosey
if node_in_front_of == 1 then
quick_rotate(self, dtime)
end
--only move forward if path is clear
self:jump_move(self.walk_velocity)
elseif self.state == "run" then
print("run")
elseif self.state == "attack" then
print("attack")
end
if float_now then
self:float()
end
end
--special mob jump movement
mobs.jump_move = function(self, velocity)
if self.object:get_velocity().y ~= 0 or not self.old_velocity or (self.old_velocity and self.old_velocity.y > 0) then
return
end
--make the mob stick for a split second
mobs.set_velocity(self,0)
--fallback velocity to allow modularity
jump_height = DEFAULT_JUMP_HEIGHT
local yaw = (self.yaw or 0)
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = (math.sin(yaw) * -velocity),
y = jump_height,
z = (math.cos(yaw) * velocity),
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
if vector.length(new_velocity_addition) > vector.length(goal_velocity) then
vector.multiply(new_velocity_addition, (vector.length(goal_velocity) / vector.length(new_velocity_addition)))
end
--smooths out mobs a bit
if vector.length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
]]--

@ -0,0 +1,436 @@
--[[
_ _
| | | |
| | __ _ _ __ __| |
| | / _` | '_ \ / _` |
| |___| (_| | | | | (_| |
\_____/\__,_|_| |_|\__,_|
]]--
--[[
--this is basically reverse jump_check
local function cliff_check(self, dtime)
--mobs will flip out if they are falling without this
if self.object:get_velocity().y ~= 0 then
return false
end
local pos = self.object:get_pos()
local dir = minetest.yaw_to_dir(self.yaw)
local collisionbox = self.properties.collisionbox
local radius = collisionbox[4] + 0.5
dir = vector.multiply(dir, radius)
local free_fall, blocker = minetest.line_of_sight(
{x = pos.x + dir.x, y = pos.y, z = pos.z + dir.z},
{x = pos.x + dir.x, y = pos.y - self.def.fear_height, z = pos.z + dir.z})
return free_fall
end
-- state switching logic (stand, walk, run, attacks)
local land_state_list_wandering = {"stand", "walk"}
local function land_state_switch(self, dtime)
--do math before sure not attacking, following, or running away so continue
--doing random walking for mobs if all states are not met
self.state_timer = self.state_timer - dtime
--only run away
if self.def.skittish and self.state == "run" then
self.run_timer = self.run_timer - dtime
if self.run_timer > 0 then
return
end
--continue
end
--ignore everything else if breeding
if self.breed_lookout_timer and self.breed_lookout_timer > 0 then
self.state = "breed"
return
--reset the state timer to get the mob out of
--the breed state
elseif self.state == "breed" then
self.state_timer = 0
end
--ignore everything else if following
if self:check_following() and self.breed_lookout_timer == 0 and self.breed_timer == 0 then
self.state = "follow"
return
--reset the state timer to get the mob out of
--the follow state - not the cleanest option
--but the easiest
elseif self.state == "follow" then
self.state_timer = 0
end
--only attack
if self.def.hostile and self.attacking then
self.state = "attack"
return
end
--if finally reached here then do random wander
if self.state_timer <= 0 then
self.state_timer = math.random(4, 10) + math.random()
self.state = land_state_list_wandering[math.random(1, #land_state_list_wandering)]
end
end
-- states are executed here
local function land_state_execution(self, dtime)
--[[ -- this is a debug which shows the timer and makes mobs breed 100 times faster
print(self.breed_timer)
if self.breed_timer > 0 then
self.breed_timer = self.breed_timer - (dtime * 100)
if self.breed_timer <= 0 then
self.breed_timer = 0
end
end
] ]--
--timer to time out looking for mate
if self.breed_lookout_timer > 0 then
self.breed_lookout_timer = self.breed_lookout_timer - dtime
--looking for mate failed
if self.breed_lookout_timer < 0 then
self.breed_lookout_timer = 0
end
end
--cool off after breeding
if self.breed_timer > 0 then
self.breed_timer = self.breed_timer - dtime
--do this to skip the first check, using as switch
if self.breed_timer <= 0 then
self.breed_timer = 0
end
end
local pos = self.object:get_pos()
local collisionbox = self.properties.collisionbox
--get the center of the mob
pos.y = pos.y + (collisionbox[2] + collisionbox[5] / 2)
local current_node = minetest.get_node(pos).name
local float_now = false
--recheck if in water or lava
if minetest.get_item_group(current_node, "water") ~= 0 or minetest.get_item_group(current_node, "lava") ~= 0 then
float_now = true
end
--calculate fall damage
if self.fall_damage then
self:calculate_fall_damage()
end
if self.state == "stand" then
--do animation
self:set_animation("stand")
--set the velocity of the mob
self:set_velocity(0)
--animation fixes for explosive mobs
if self.attack_type == "explode" then
self:reverse_explosion_animation(dtime)
end
self:lock_yaw()
elseif self.state == "follow" then
--always look at players
self:look_at(self.following_person)
--check distance
local distance_from_follow_person = vector.distance(self.object:get_pos(), self.following_person:get_pos())
local distance_2d = mobs.get_2d_distance(self.object:get_pos(), self.following_person:get_pos())
--don't push the player if too close
--don't spin around randomly
if self.follow_distance < distance_from_follow_person and self.minimum_follow_distance < distance_2d then
self:set_animation("run")
self:set_velocity(self.run_velocity)
if self:jump_check() == 1 then
self:jump(self)
end
else
self:set_mob_animation("stand")
self:set_velocity(0)
end
elseif self.state == "walk" then
self.walk_timer = self.walk_timer - dtime
--reset the walk timer
if self.walk_timer <= 0 then
--re-randomize the walk timer
self.walk_timer = math.random(1, 6) + math.random()
--set the mob into a random direction
self.yaw = (math.random() * (math.pi * 2))
end
--do animation
self:set_animation("walk")
--enable rotation locking
self:movement_rotation_lock()
--check for nodes to jump over
local node_in_front_of = self:jump_check()
if node_in_front_of == 1 then
self:jump()
--turn if on the edge of cliff
--(this is written like this because unlike
--jump_check which simply tells the mob to jump
--this requires a mob to turn, removing the
--ease of a full implementation for it in a single
--function)
elseif node_in_front_of == 2 or (self.fear_height ~= 0 and cliff_check(self, dtime)) then
--turn 45 degrees if so
quick_rotate(self,dtime)
--stop the mob so it doesn't fall off
self:set_velocity(0)
end
--only move forward if path is clear
if node_in_front_of == 0 or node_in_front_of == 1 then
--set the velocity of the mob
self:set_velocity(self.walk_velocity)
end
--animation fixes for explosive mobs
if self.attack_type == "explode" then
self:reverse_explosion_animation(dtime)
end
elseif self.state == "run" then
--do animation
self:set_animation("run")
--enable rotation locking
self:movement_rotation_lock()
--check for nodes to jump over
local node_in_front_of = self:jump_check()
if node_in_front_of == 1 then
self:jump()
--turn if on the edge of cliff
--(this is written like this because unlike
--jump_check which simply tells the mob to jump
--this requires a mob to turn, removing the
--ease of a full implementation for it in a single
--function)
elseif node_in_front_of == 2 or (self.fear_height ~= 0 and cliff_check(self, dtime)) then
--turn 45 degrees if so
quick_rotate(self, dtime)
--stop the mob so it doesn't fall off
self:set_velocity(0)
end
--only move forward if path is clear
if node_in_front_of == 0 or node_in_front_of == 1 then
--set the velocity of the mob
self:set_velocity(self.run_velocity)
end
elseif self.state == "attack" then
--execute mob attack type
if self.attack_type == "explode" then
self:explode_attack_walk(dtime)
elseif self.attack_type == "punch" then
self:punch_attack_walk(dtime)
elseif self.attack_type == "projectile" then
self:projectile_attack_walk(dtime)
end
elseif self.state == "breed" then
minetest.add_particlespawner({
amount = 2,
time = 0.0001,
minpos = vector.add(pos, min),
maxpos = vector.add(pos, max),
minvel = vector.new(-1,1,-1),
maxvel = vector.new(1,3,1),
minexptime = 0.7,
maxexptime = 1,
minsize = 1,
maxsize = 2,
collisiondetection = false,
vertical = false,
texture = "heart.png",
})
local mate = self:look_for_mate()
--found a mate
if mate then
self:look_at(mate)
self:set_velocity(self.walk_velocity)
--smoosh together basically
if vector.distance(self.object:get_pos(), mate:get_pos()) <= self.breed_distance then
self:set_animation("stand")
if self.special_breed_timer == 0 then
self.special_breed_timer = 2 --breeding takes 2 seconds
end
self.special_breed_timer = self.special_breed_timer - dtime
if self.special_breed_timer <= 0 then
--pop a baby out, it's a miracle!
local baby_pos = vector.divide(vector.add(self.object:get_pos(), mate:get_pos()), 2)
local baby_mob = minetest.add_entity(pos, self.name, minetest.serialize({baby = true, grow_up_timer = self.grow_up_goal, bred = true}))
self:play_sound_specific("item_drop_pickup")
self.special_breed_timer = 0
self.breed_lookout_timer = 0
self.breed_timer = self.breed_timer_cooloff
local mate_entity = mate:get_luaentity()
mate_entity.special_breed_timer = 0
mate_entity.breed_lookout_timer = 0
mate_entity.breed_timer = self.breed_timer_cooloff -- can reuse because it's the same mob
end
else
self:set_animation("walk")
end
--couldn't find a mate, just stand there until the player pushes it towards one
--or the timer runs out
else
self:set_mob_animation("stand")
self:set_velocity(0)
end
end
if float_now then
self:float()
else
local acceleration = self.object:get_acceleration()
if acceleration and acceleration.y == 0 then
self.object:set_acceleration(vector.new(0, -self.gravity, 0))
end
end
end
-- move mob in facing direction
--this has been modified to be internal
--internal = lua (self.yaw)
--engine = c++ (self.object:get_yaw())
mobs.set_velocity = function(self, v)
local yaw = (self.yaw or 0)
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = (math.sin(yaw) * -v),
y = 0,
z = (math.cos(yaw) * v),
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
if vector.length(new_velocity_addition) > vector.length(goal_velocity) then
vector.multiply(new_velocity_addition, (vector.length(goal_velocity) / vector.length(new_velocity_addition)))
end
new_velocity_addition.y = 0
--smooths out mobs a bit
if vector.length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
-- calculate mob velocity
mobs.get_velocity = function(self)
local v = self.object:get_velocity()
v.y = 0
if v then
return vector.length(v)
end
return 0
end
--make mobs jump
mobs.jump = function(self, velocity)
if self.object:get_velocity().y ~= 0 or not self.old_velocity or (self.old_velocity and self.old_velocity.y > 0) then
return
end
--fallback velocity to allow modularity
velocity = velocity or DEFAULT_JUMP_HEIGHT
self.object:add_velocity(vector.new(0,velocity,0))
end
--make mobs fall slowly
mobs.mob_fall_slow = function(self)
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = 0,
y = -2,
z = 0,
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
new_velocity_addition.x = 0
new_velocity_addition.z = 0
if vector.length(new_velocity_addition) > vector.length(goal_velocity) then
vector.multiply(new_velocity_addition, (vector.length(goal_velocity) / vector.length(new_velocity_addition)))
end
new_velocity_addition.x = 0
new_velocity_addition.z = 0
--smooths out mobs a bit
if vector.length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
]]--

@ -0,0 +1,9 @@
function mcl_mobs.mob:swap_auto_step_height_adjust()
local y_vel = self.object:get_velocity().y
if y_vel == 0 and self.stepheight ~= self.stepheight_backup then
self.stepheight = self.stepheight_backup
elseif y_vel ~= 0 and self.stepheight ~= 0 then
self.stepheight = 0
end
end

@ -0,0 +1,206 @@
--[[
_____ _
/ ___| (_)
\ `--.__ ___ _ __ ___
`--. \ \ /\ / / | '_ ` _ \
/\__/ /\ V V /| | | | | | |
\____/ \_/\_/ |_|_| |_| |_|
]]--
--[[
-- state switching logic (stand, walk, run, attacks)
local swim_state_list_wandering = {"stand", "swim"}
local function swim_state_switch(self, dtime)
self.state_timer = self.state_timer - dtime
if self.state_timer <= 0 then
self.state_timer = math.random(4,10) + math.random()
self.state = swim_state_list_wandering[math.random(1, #swim_state_list_wandering)]
end
end
--check if a mob needs to turn while swimming
local swim_turn_check = function(self,dtime)
local pos = self.object:get_pos()
pos.y = pos.y + 0.1
local dir = minetest_yaw_to_dir(self.yaw)
local collisionbox = self.object:get_properties().collisionbox
local radius = collisionbox[4] + 0.5
vector.multiply(dir, radius)
local test_dir = vector.add(pos,dir)
return minetest_get_item_group(minetest_get_node(test_dir).name, "solid") ~= 0
end
--this is to swap the built in engine acceleration modifier
local function swim_physics_swapper(self, inside_swim_node)
--should be swimming, gravity is applied, switch to floating
if inside_swim_node and self.object:get_acceleration().y ~= 0 then
self.object:set_acceleration(vector.new(0, 0, 0))
--not be swim, gravity isn't applied, switch to falling
elseif not inside_swim_node and self.object:get_acceleration().y == 0 then
self.pitch = 0
self.object:set_acceleration(vector.new(0, -self.gravity, 0))
end
end
local random_pitch_multiplier = {-1,1}
-- states are executed here
local function swim_state_execution(self, dtime)
local pos = self.object:get_pos()
pos.y = pos.y + self.object:get_properties().collisionbox[5]
local current_node = minetest_get_node(pos).name
local inside_swim_node = false
--quick scan everything to see if inside swim node
for _,id in pairs(self.swim_in) do
if id == current_node then
inside_swim_node = true
break
end
end
--turn gravity on or off
swim_physics_swapper(self, inside_swim_node)
--swim properly if inside swim node
if inside_swim_node then
if self.state == "stand" then
--do animation
self:set_animation("stand")
self:set_swim_velocity(0)
if self.tilt_swim then
self:set_static_pitch()
end
self:lock_yaw()
elseif self.state == "swim" then
self.walk_timer = self.walk_timer - dtime
--reset the walk timer
if self.walk_timer <= 0 then
--re-randomize the walk timer
self.walk_timer = math.random(1, 6) + math.random()
--set the mob into a random direction
self.yaw = (math.random() * (math.pi * 2))
--create a truly random pitch, since there is no easy access to pitch math that I can find
self.pitch = math.random() * math.random(1, 3) * random_pitch_multiplier[math.random(1, 2)]
end
--do animation
self:set_animation("walk")
--do a quick turn to make mob continuously move
--if in a fish tank or something
if swim_turn_check(self, dtime) then
quick_rotate(self, dtime)
end
self:set_swim_velocity(self.walk_velocity)
--only enable tilt swimming if enabled
if self.tilt_swim then
self:set_dynamic_pitch()
end
--enable rotation locking
self:movement_rotation_lock()
end
--flop around if not inside swim node
else
--do animation
self:set_mob_animation("stand")
self:flop()
if self.tilt_swim then
self:set_static_pitch()
end
end
end
--make mobs flop
mobs.flop = function(self, velocity)
if self.object:get_velocity().y ~= 0 or not self.old_velocity or (self.old_velocity and self.old_velocity.y > 0) then
return false
end
mobs.set_velocity(self, 0)
--fallback velocity to allow modularity
velocity = velocity or DEFAULT_JUMP_HEIGHT
--create a random direction (2d yaw)
local dir = DOUBLE_PI * math.random()
--create a random force value
local force = math.random(0,3) + math.random()
--convert the yaw to a direction vector then multiply it times the force
local final_additional_force = vector.multiply(minetest_yaw_to_dir(dir), force)
--place in the "flop" velocity to make the mob flop
final_additional_force.y = velocity
self.object:add_velocity(final_additional_force)
return true
end
-- move mob in facing direction
--this has been modified to be internal
--internal = lua (self.yaw)
--engine = c++ (self.object:get_yaw())
mobs.set_swim_velocity = function(self, v)
local yaw = (self.yaw or 0)
local pitch = (self.pitch or 0)
if v == 0 then
pitch = 0
end
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = (math.sin(yaw) * -v),
y = pitch,
z = (math.cos(yaw) * v),
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
if vector.length(new_velocity_addition) > vector.length(goal_velocity) then
vector.multiply(new_velocity_addition, (vector.length(goal_velocity) / vector.length(new_velocity_addition)))
end
--smooths out mobs a bit
if vector.length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
]]--

@ -0,0 +1,205 @@
--[[
Implementation of the Minecraft 1.16 Anger System (copied from https://www.minecraft.net/ru-ru/article/nether-update-java, with modifications):
Forgive dead players
If this gamerule is disabled, then angered mobs will stay angry even if the targeted player dies
If both forgiveDeadPlayers and universalAnger are enabled, an angered neutral mob will stop being angry when their target dies. They won't seek any new targets after that
Neutral mob anger
When hurt by a player, the neutral mob will target that player and try to kill it
The mob will stay angry until the player is dead or out of sight for a while
Anger is persistent, so a player can't escape by temporarily logging out or switching dimension
If a targeted player dies near the angered mob, it will stop being angry (unless forgiveDeadPlayers is disabled)
Neutral mobs also get angry at other mobs who hurt them. However, that anger is not persistent
Angered neutral mobs will only attack the offending player, not innocent bystanders
Some mobs spread anger (wolf, Zombie Pigman). If a player attacks one, all nearby mobs of the same type will get angry at that player
Universal anger
Universal anger is basically guilt by association. A neutral mob attacked by players will be angry at players in general, regardless of who attacked them. More specifically:
A neutral mob attacked by a player will target the nearest player, even if that player wasn't the attacker
Every time the neutral mob is hit by a player it will update its attack target to the nearest player
Players can use this to make neutral mobs attack other players. Who would ever do something that devious?
Universal anger does not apply when a neutral mob is attacked by another mob - only when it is attacked by a player
Universal anger is persistent. The angered mob will stay angry even if the player logs out and logs in, or jumps through a portal and back
mcl_mobs.mobs that spread anger will also spread universal anger. So if a player attacks a Zombie Pigman, all other Zombie Pigmen within sight will be universally angry and attack their nearest player
An angered neutral mob will stop being angry if it can't see any eligible target for a while
--]]
function mcl_mobs.mob:anger_on_staticdata()
if self.anger_persistent then
self.data.anger_target_name = self.anger_target_name
self.data.anger_hurt_timestamp = self.anger_hurt_timestamp
end
end
function mcl_mobs.mob:anger_on_activate()
if self.data.anger_target_name then
self.anger = true
self.anger_persistent = true
self.anger_target_name = self.data.anger_target_name
self.anger_hurt_timestamp = self.data.anger_hurt_timestamp
self.data.anger_target_name = nil
self.data.anger_hurt_timestamp = nil
end
end
function mcl_mobs.mob:get_anger_attack_target()
if not self.anger then
return
end
-- if the mob is universally angry and the current target is unreachable, search a new one
local search_new_target = self.anger_universal and (
not self.anger_current_target -- does a current target even exist?
or not self.anger_current_target:is_player() -- universal anger only applies to players, so this is just a check whether the ObjectRef is still valid
or not self:can_see(self.anger_current_target) -- dimension check is not done since it is covered by the view distance check
)
if search_new_target then
self.anger_current_target = self:get_player_in_sight()
if self.anger_current_target then
self:debug("found new universal anger target: " .. self.anger_current_target:get_player_name())
end
end
-- if the anger is not persistant (e.g. enderman provocation, angry at mobs)
if not self.anger_persistent then
-- calm down if either the target ObjectRef is invalid or changed its dimension
if not self.anger_target:is_player() and not self.anger_target:get_luaentity() or not self:same_dimension_as(self.anger_target) then
self:debug("non persistent anger target unreachable, calming down" .. (self.anger_target_name and "[anger_target_name = " .. self.anger_target_name .. "]" or ""))
return nil, true
end
end
-- if this is a player, special rules apply (don't use anger_target:is_player() since the player may have logged out so it's not a valid check)
if self.anger_target_name then
-- check if player logged out (if the player had already logged out in the last step anger_target will be nil, else it will be a dangling ObjectRef that can be validated by calling is_player())
if not self.anger_target or not self.anger_target:is_player() then
if self.anger_target then
self:debug("anger target logged out: " .. self.anger_target_name)
end
-- in case the player relogged (if the player did not relog anger_target becomes nil and this is run in the next step as well)
self.anger_target = minetest.get_player_by_name(self.anger_target_name)
if self.anger_target then
self:debug("anger target relogged: " .. self.anger_target_name)
end
end
-- if forgiveDeadPlayers is true (it is by default)
if self.anger_target and minetest.settings:get_bool("mclForgiveDeadPlayers", true) then
-- check death timestamp of player and forget about the player in case it was killed
if self.anger_target:get_meta():get_int("mcl_mobs:last_death") >= self.hurt_timestamp then
self:debug("forgave " .. self.anger_target_name .. " since they died")
return nil, true
end
end
end
-- the actual target we want to attack
local target
-- note: dont use a selfmade ternary expression (v = x and a or b, in other languages that have real ternary expressions this would be v = x ? a : b) here
-- because anger_current_target might be nil and we don't care about the original player if they are not in the area (anger_current_target is only nil if there is absolutely no player in the area)
if self.anger_universal then
target = self.anger_current_target
else
target = self.anger_target
-- if the target is out of reach, it counts as not existant in terms of the reset timer
if target and not self:can_see(target) then
target = nil
if not self.anger_calm_timer then
self:debug("cannot see anger target " .. self.anger_target_name .. " anymore")
end
end
end
if not target and not self.anger_calm_timer then
-- start to calm down if noone to attack in sight
self:debug("anger target " .. (self.anger_universal and self.anger_target_name .. " " or "") .. "is not reachable anymore, starting calm timer")
self.anger_calm_timer = mcl_mobs.const.calm_down_timer
elseif target and self.anger_calm_timer then
-- stop calming down if there is someone in sight again
self:debug("anger target " .. (self.anger_universal and self.anger_target_name .. " " or "") .. "is reachable again, resetting calm timer")
self.anger_calm_timer = nil
end
if target then
return target
end
-- wait for the mob to calm down if there is no target, then clear variables
-- do_timer returns true if the timer has not elapsed yet
if not self:do_timer("anger_calm") then
self:debug("calmed down")
self.anger = nil
self.anger_target = nil
self.anger_target_name = nil
self.anger_universal = nil
self.anger_current_target = nil
self.anger_persistent = nil
self.anger_hurt_timestamp = nil
end
end
function mcl_mobs.mob:get_angry_raw(target, target_name, timestamp, universal, persistent)
if self.owner == target_name then
return false
end
self:debug("getting angry at " .. (target_name or tostring(target))
.. " persistent: " .. (persistent and "yes" or "no")
.. " universal: " .. (universal and "yes" or "no")
)
self.anger = true
self.anger_target = target -- even if universally angry, still remember the actual cause to apply forgiveDeadPlayers properly. anger_current_target is used to get the actual attack target
self.anger_target_name = target_name -- remember player name separately to work around the ObjectRef becoming invalid when the player logs out
-- the persistent field is used to optionally forget about the player when they log out or change dimension (e.g. provoking endermen by looking at them) or for when attacked by another mob
self.anger_persistent = persistent
self.anger_hurt_timestamp = timestamp
if universal then
self.anger_universal = true
-- set this to nil because then universal anger is enabled every mob will look for a new target everytime a provocation happens
self.anger_current_target = nil
end
return true
end
function mcl_mobs.mob:get_angry(target)
local timestamp = os.time()
local is_player = target:is_player()
local universal = is_player and minetest.settings:get_bool("mclUniversalAnger")
local target_name = is_player and target:get_player_name() or ""
local persistent = not is_player
self:debug("provoked by " .. (target_name or tostring(target))
.. " persistent: " .. (persistent and "yes" or "no")
.. " universal: " .. (universal and "yes" or "no")
)
if not self:get_angry_raw(target, target_name, timestamp, universal, persistent) then
return false
end
if self.def.group_attack then
for _, obj in pairs(minetest.get_objects_inside_radius(self.object:get_pos(), self.def.view_range)) do
local luaentity = obj:get_luaentity()
if luaentity and self.def.group_attack[luaentity.name] then
luaentity:get_angry_raw(target, target_name, timestamp, universal_anger, persistent)
end
end
end
return true
end
minetest.register_on_dieplayer(function(player)
player:get_meta():set_int("mcl_mobs:last_death", os.time())
end)

@ -1,761 +0,0 @@
-- API for Mobs Redo: MineClone 2 Delux 2.0 DRM Free Early Access Super Extreme Edition
-- mobs library
mobs = {}
-- lua locals - can grab from this to easily plop them into the api lua files
--localize minetest functions
local minetest_settings = minetest.settings
local minetest_get_objects_inside_radius = minetest.get_objects_inside_radius
local minetest_get_modpath = minetest.get_modpath
local minetest_registered_nodes = minetest.registered_nodes
local minetest_get_node = minetest.get_node
local minetest_get_item_group = minetest.get_item_group
local minetest_registered_entities = minetest.registered_entities
local minetest_line_of_sight = minetest.line_of_sight
local minetest_after = minetest.after
local minetest_sound_play = minetest.sound_play
local minetest_add_particlespawner = minetest.add_particlespawner
local minetest_registered_items = minetest.registered_items
local minetest_set_node = minetest.set_node
local minetest_add_item = minetest.add_item
local minetest_get_craft_result = minetest.get_craft_result
local minetest_find_path = minetest.find_path
local minetest_is_protected = minetest.is_protected
local minetest_is_creative_enabled = minetest.is_creative_enabled
local minetest_find_node_near = minetest.find_node_near
local minetest_find_nodes_in_area_under_air = minetest.find_nodes_in_area_under_air
local minetest_raycast = minetest.raycast
local minetest_get_us_time = minetest.get_us_time
local minetest_add_entity = minetest.add_entity
local minetest_get_natural_light = minetest.get_natural_light
local minetest_get_node_or_nil = minetest.get_node_or_nil
-- localize math functions
local math_pi = math.pi
local math_sin = math.sin
local math_cos = math.cos
local math_abs = math.abs
local math_min = math.min
local math_max = math.max
local math_atan = math.atan
local math_random = math.random
local math_floor = math.floor
-- localize vector functions
local vector_new = vector.new
local vector_add = vector.add
local vector_length = vector.length
local vector_direction = vector.direction
local vector_normalize = vector.normalize
local vector_multiply = vector.multiply
local vector_divide = vector.divide
-- mob constants
local BREED_TIME = 30
local BREED_TIME_AGAIN = 300
local CHILD_GROW_TIME = 60*20
local DEATH_DELAY = 0.5
local DEFAULT_FALL_SPEED = -10
local FLOP_HEIGHT = 5.0
local FLOP_HOR_SPEED = 1.5
local GRAVITY = minetest_settings:get("movement_gravity")-- + 9.81
local MOB_CAP = {}
MOB_CAP.hostile = 70
MOB_CAP.passive = 10
MOB_CAP.ambient = 15
MOB_CAP.water = 15
-- Load main settings
local damage_enabled = minetest_settings:get_bool("enable_damage")
local disable_blood = minetest_settings:get_bool("mobs_disable_blood")
local mobs_drop_items = minetest_settings:get_bool("mobs_drop_items") ~= false
local mobs_griefing = minetest_settings:get_bool("mobs_griefing") ~= false
local spawn_protected = minetest_settings:get_bool("mobs_spawn_protected") ~= false
local remove_far = true
local difficulty = tonumber(minetest_settings:get("mob_difficulty")) or 1.0
local show_health = false
local max_per_block = tonumber(minetest_settings:get("max_objects_per_block") or 64)
local mobs_spawn_chance = tonumber(minetest_settings:get("mobs_spawn_chance") or 2.5)
-- pathfinding settings
local enable_pathfinding = true
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
-- default nodes
local node_ice = "mcl_core:ice"
local node_snowblock = "mcl_core:snowblock"
local node_snow = "mcl_core:snow"
mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core:dirt"
local mod_weather = minetest_get_modpath("mcl_weather") ~= nil
local mod_explosions = minetest_get_modpath("mcl_explosions") ~= nil
local mod_mobspawners = minetest_get_modpath("mcl_mobspawners") ~= nil
local mod_hunger = minetest_get_modpath("mcl_hunger") ~= nil
local mod_worlds = minetest_get_modpath("mcl_worlds") ~= nil
local mod_armor = minetest_get_modpath("mcl_armor") ~= nil
local mod_experience = minetest_get_modpath("mcl_experience") ~= nil
-- random locals I found
local los_switcher = false
local height_switcher = false
-- Get translator
local S = minetest.get_translator("mcl_mobs")
-- CMI support check
local use_cmi = minetest.global_exists("cmi")
-- Invisibility mod check
mobs.invis = {}
if minetest.global_exists("invisibility") then
mobs.invis = invisibility
end
-- creative check
function mobs.is_creative(name)
return minetest_is_creative_enabled(name)
end
local atan = function(x)
if not x or x ~= x then
return 0
else
return math_atan(x)
end
end
-- Shows helpful debug info above each mob
local mobs_debug = minetest_settings:get_bool("mobs_debug", false)
-- Peaceful mode message so players will know there are no monsters
if minetest_settings:get_bool("only_peaceful_mobs", false) then
minetest.register_on_joinplayer(function(player)
minetest.chat_send_player(player:get_player_name(),
S("Peaceful mode active! No monsters will spawn."))
end)
end
local api_path = minetest.get_modpath(minetest.get_current_modname()).."/api/mob_functions/"
--ignite all parts of the api
dofile(api_path .. "ai.lua")
dofile(api_path .. "animation.lua")
dofile(api_path .. "collision.lua")
dofile(api_path .. "environment.lua")
dofile(api_path .. "interaction.lua")
dofile(api_path .. "movement.lua")
dofile(api_path .. "set_up.lua")
dofile(api_path .. "attack_type_instructions.lua")
dofile(api_path .. "sound_handling.lua")
dofile(api_path .. "death_logic.lua")
dofile(api_path .. "mob_effects.lua")
dofile(api_path .. "projectile_handling.lua")
dofile(api_path .. "breeding.lua")
dofile(api_path .. "head_logic.lua")
mobs.spawning_mobs = {}
-- register mob entity
function mobs:register_mob(name, def)
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
mobs.spawning_mobs[name] = true
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
minetest.register_entity(name, {
description = def.description,
use_texture_alpha = def.use_texture_alpha,
stepheight = def.stepheight or 0.6,
stepheight_backup = def.stepheight or 0.6,
name = name,
type = def.type,
attack_type = def.attack_type,
fly = def.fly,
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,
jump_height = def.jump_height or 4, -- was 6
rotate = def.rotate or 0, -- 0=front, 90=side, 180=back, 270=side2
hp_min = scale_difficulty(def.hp_min, 5, 1),
hp_max = scale_difficulty(def.hp_max, 10, 1),
xp_min = def.xp_min or 1,
xp_max = def.xp_max or 5,
breath_max = def.breath_max or 6,
breathes_in_water = def.breathes_in_water or false,
physical = true,
collisionbox = collisionbox,
collide_with_objects = def.collide_with_objects or false,
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 = mobs.create_mob_on_rightclick(def.on_rightclick),
arrow = def.arrow,
shoot_interval = def.shoot_interval,
sounds = def.sounds or {},
animation = def.animation,
jump = def.jump ~= false,
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,
state_timer = 0,
env_damage_timer = 0,
tamed = false,
pause_timer = 0,
gotten = false,
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,
specific_attack = def.specific_attack,
runaway_from = def.runaway_from,
owner_loyal = def.owner_loyal,
facing_fence = false,
_cmi_is_mob = true,
pushable = def.pushable or true,
--j4i stuff
yaw = 0,
automatic_face_movement_dir = def.rotate or 0, -- 0=front, 90=side, 180=back, 270=side2
automatic_face_movement_max_rotation_per_sec = 360, --degrees
backface_culling = true,
walk_timer = 0,
stand_timer = 0,
current_animation = "",
gravity = GRAVITY,
swim = def.swim,
swim_in = def.swim_in or {mobs_mc.items.water_source, "mcl_core:water_flowing", mobs_mc.items.river_water_source},
pitch_switch = "static",
jump_only = def.jump_only,
hostile = def.hostile,
neutral = def.neutral,
attacking = nil,
visual_size_origin = def.visual_size or {x = 1, y = 1, z = 1},
punch_timer_cooloff = def.punch_timer_cooloff or 0.5,
death_animation_timer = 0,
hostile_cooldown = def.hostile_cooldown or 15,
tilt_fly = def.tilt_fly,
tilt_swim = def.tilt_swim,
fall_slow = def.fall_slow,
projectile_cooldown_min = def.projectile_cooldown_min or 2,
projectile_cooldown_max = def.projectile_cooldown_max or 6,
skittish = def.skittish,
minimum_follow_distance = def.minimum_follow_distance or 0.5, --make mobs not freak out when underneath
memory = 0, -- memory timer if chasing/following
fly_random_while_attack = def.fly_random_while_attack,
--for spiders
always_climb = def.always_climb,
--despawn mechanic variables
lifetimer_reset = 30, --30 seconds
lifetimer = 30, --30 seconds
--breeding stuff
breed_timer = 0,
breed_lookout_timer = 0,
breed_distance = def.breed_distance or 1.5, --how far away mobs have to be to begin actual breeding
breed_lookout_timer_goal = 30, --30 seconds (this timer is for how long the mob looks for a mate)
breed_timer_cooloff = 5*60, -- 5 minutes (this timer is for how long the mob has to wait before being bred again)
bred = false,
follow = def.follow, --this item is also used for the breeding mechanism
follow_distance = def.follow_distance or 2,
baby_size = def.baby_size or 0.5,
baby = false,
grow_up_timer = 0,
grow_up_goal = 20*60, --in 20 minutes the mob grows up
special_breed_timer = 0, --this is used for the AHEM AHEM part of breeding
backup_visual_size = def.visual_size,
backup_collisionbox = collisionbox,
backup_selectionbox = def.selectionbox or def.collisionbox,
--fire timer
burn_timer = 0,
ignores_cobwebs = def.ignores_cobwebs,
breath = def.breath_max or 6,
random_sound_timer_min = 3,
random_sound_timer_max = 10,
--head code variables
--defaults are for the cow's default
--because I don't know what else to set them
--to :P
has_head = def.has_head or false,
head_bone = def.head_bone,
--you must use these to adjust the mob's head positions
--has_head is used as a logic gate (quick easy check)
has_head = def.has_head or false,
--head_bone is the actual bone in the model which the head
--is attached to for animation
head_bone = def.head_bone or "head",
--this part controls the base position of the head calculations
--localized to the mob's visual yaw when gotten (self.object:get_yaw())
--you can enable the debug in /mob_functions/head_logic.lua by uncommenting the
--particle spawner code
head_height_offset = def.head_height_offset or 1.0525,
head_direction_offset = def.head_direction_offset or 0.5,
--this part controls the visual of the head
head_bone_pos_y = def.head_bone_pos_y or 3.6,
head_bone_pos_z = def.head_bone_pos_z or -0.6,
head_pitch_modifier = def.head_pitch_modifier or 0,
--these variables are switches in case the model
--moves the wrong way
swap_y_with_x = def.swap_y_with_x or false,
reverse_head_yaw = def.reverse_head_yaw or false,
--END HEAD CODE VARIABLES
--end j4i stuff
-- MCL2 extensions
teleport = mobs.teleport,
do_teleport = def.do_teleport,
spawn_class = def.spawn_class,
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,
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,
eye_height = def.eye_height or 1.5,
defuse_reach = def.defuse_reach or 4,
-- End of MCL2 extensions
on_spawn = def.on_spawn,
--on_blast = def.on_blast or do_tnt,
on_step = mobs.mob_step,
--do_punch = def.do_punch,
on_punch = mobs.mob_punch,
--on_breed = def.on_breed,
--on_grown = def.on_grown,
--on_detach_child = mob_detach_child,
on_activate = function(self, staticdata, dtime)
self.object:set_acceleration(vector_new(0,-GRAVITY, 0))
return mobs.mob_activate(self, staticdata, def, dtime)
end,
get_staticdata = function(self)
return mobs.mob_staticdata(self)
end,
--harmed_by_heal = def.harmed_by_heal,
})
if minetest_get_modpath("doc_identifier") ~= nil then
doc.sub.identifier.register_object(name, "basics", "mobs")
end
end -- END mobs:register_mob function
-- register arrow for shoot attack
function mobs:register_arrow(name, def)
-- errorcheck
if not name or not def then
print("failed to register arrow entity")
return
end
minetest.register_entity(name.."_entity", {
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,
speed = def.speed or nil,
on_step = function(self)
local vel = self.object:get_velocity()
local pos = self.object:get_pos()
if self.timer > 150
or not mobs.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
--do this to prevent clipping through main entity sprite
local pos_adjustment = vector_multiply(vector_normalize(vel), -1)
local divider = def.tail_distance_divider or 1
pos_adjustment = vector_divide(pos_adjustment, divider)
local new_pos = vector_add(pos, pos_adjustment)
minetest.add_particle({
pos = new_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 = minetest_get_node(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
if self.hit_player then
self.hit_player(self, player)
else
mobs.arrow_hit(self, player)
end
self.object:remove();
return
end
--[[
local entity = player:get_luaentity()
if entity
and self.hit_mob
and entity._cmi_is_mob == true
and tostring(player) ~= self.owner_id
and entity.name ~= self.object:get_luaentity().name
and (self._shooter and entity.name ~= self._shooter:get_luaentity().name) then
--self.hit_mob(self, player)
self.object:remove();
return
end
]]--
--[[
if entity
and self.hit_object
and (not entity._cmi_is_mob)
and tostring(player) ~= self.owner_id
and entity.name ~= self.object:get_luaentity().name
and (self._shooter and entity.name ~= self._shooter: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 mobs:register_egg(mob, desc, background, 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 = background
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 mod_mobspawners and 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 mobs.is_creative(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
local mob = minetest_add_entity(pos, mob)
minetest.log("action", "Mob spawned: "..name.." 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 mobs.is_creative(placer:get_player_name()) then
itemstack:take_item()
end
end
return itemstack
end,
})
end

@ -0,0 +1,158 @@
--[[
function mcl_mobs.register_arrow(name, def)
minetest.register_entity(name.."_entity", {
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,
speed = def.speed or nil,
on_step = function(self)
local vel = self.object:get_velocity()
local pos = self.object:get_pos()
if self.timer > 150
or not mobs.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
--do this to prevent clipping through main entity sprite
local pos_adjustment = vector.multiply(vector.normalize(vel), -1)
local divider = def.tail_distance_divider or 1
pos_adjustment = vector.divide(pos_adjustment, divider)
local new_pos = vector.add(pos, pos_adjustment)
minetest.add_particle({
pos = new_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 = minetest.get_node(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
if self.hit_player then
self.hit_player(self, player)
else
mobs.arrow_hit(self, player)
end
self.object:remove();
return
end
--[[
local entity = player:get_luaentity()
if entity
and self.hit_mob
and entity._cmi_is_mob == true
and tostring(player) ~= self.owner_id
and entity.name ~= self.object:get_luaentity().name
and (self._shooter and entity.name ~= self._shooter:get_luaentity().name) then
--self.hit_mob(self, player)
self.object:remove();
return
end
] ]--
--[[
if entity
and self.hit_object
and (not entity._cmi_is_mob)
and tostring(player) ~= self.owner_id
and entity.name ~= self.object:get_luaentity().name
and (self._shooter and entity.name ~= self._shooter:get_luaentity().name) then
--self.hit_object(self, player)
self.object:remove();
return
end
] ]--
end
end
self.lastpos = pos
end
})
end
--this is used for arrow collisions
mobs.arrow_hit = function(self, player)
player:punch(self.object, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = self._damage}
}, nil)
--knockback
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = player:get_pos()
pos2.y = 0
local dir = vector.direction(pos1,pos2)
dir = vector.multiply(dir,3)
if player:get_velocity().y <= 1 then
dir.y = 5
end
player:add_velocity(dir)
end
]]--

@ -0,0 +1,26 @@
function mcl_mobs.mob:baby_step()
if not self:do_timer("grow_up", true) then
self:baby_grow_up()
end
end
function mcl_mobs.mob:baby_grow_up()
self:debug("growing up")
self.data.baby = nil
if self.def.on_grow_up then
self.def.on_grow_up(self)
end
self:update_textures()
self:update_visual_size()
self:update_eye_height()
self:update_collisionbox()
end
function mcl_mobs.mob:boost()
self:debug("grow up boost")
self.data.grow_up_timer = self.data.grow_up_timer - self.data.grow_up_timer * mcl_mobs.const.grow_up_boost
-- ToDo: check whether the Minecraft wiki terminology is right about 10% or whether they actually mean 10 percent points
-- (10 percent would be 0.1 * self.data.grow_up_timer, 10 percent points would be 0.1 * self.def.grow_up_goal)
end

@ -0,0 +1,27 @@
function mcl_mobs.mob:start_breed_giveup_timer()
self.breed_giveup_timer = mcl_mobs.const.breed_giveup_timer
end
function mcl_mobs.mob:breeding_on_activate()
if self.data.breeding then
self:start_breed_giveup_timer()
end
end
function mcl_mobs.mob:init_breeding()
self:debug("initializing breeding")
self.data.bred = true
self.data.breeding = true
self:start_breed_giveup_timer()
end
-- looking for hot singles in the area
function mcl_mobs.mob:find_mate()
return self:get_near_object(self.def.view_range, function(self, obj)
local luaentity = obj:get_luaentity()
return luaentity -- dont fook with hoomans
and luaentity.name == self.name -- this is MineClone, not Animal Crossing
and not luaentity.data.bred -- no polygamy pls
and not luaentity.data.baby -- no pedophila pls
end)
end

@ -0,0 +1,115 @@
function mcl_mobs.mob:debug(msg)
if mcl_mobs.const.debug then
minetest.log("[mcl_mobs] " .. tostring(self.object) .. "[" .. self.name .. "]: " .. msg)
end
end
function mcl_mobs.mob:do_timer(name, persistent)
local k = name .. "_timer"
local t = persistent and self.data or self
local v = t[k]
if not v then
return
end
local r = true
v = v - self.dtime
if v <= 0 then
self:debug(k .. " elapsed")
v = nil
r = false
end
t[k] = v
return r
end
function mcl_mobs.mob:same_dimension_as(obj)
return mcl_worlds.pos_to_dimension(obj:get_pos()) == mcl_worlds.pos_to_dimension(self.object:get_pos())
end
function mcl_mobs.mob:can_see(obj)
return vector.distance(obj:get_pos(), self.object:get_pos()) <= self.def.view_range
end
function mcl_mobs.mob:get_player_in_sight()
return self:get_near_player(self.def.view_range)
end
function mcl_mobs.mob:is_player_near(radius)
for _, player in pairs(minetest.get_connected_players()) do
if vector.distance(pos, player:get_pos()) < radius then
return true
end
end
return false
end
function mcl_mobs.mob:get_near_player(radius, condition)
local pos = self.object:get_pos()
local eye_pos = vector.new(pos.x, pos.y + self.eye_height, pos.z)
local nearest_player
local nearest_distance = radius -- this is very big brain right there, I feel genious
for _, player in pairs(minetest.get_connected_players()) do
if player:get_hp() > 0 then
local player_pos = obj:get_pos()
if vector.distance(pos, player_pos) < nearest_distance and (not condition or condition(self, player)) and minetest.line_of_sight(eye_pos, vector.new(player_pos.x, player_pos.y + player:get_properties().eye_height, player_pos.z)) then
nearest_player = player
nearest_distance = distance
end
end
end
return nearest_player
end
-- I know this repeats some things from the get_near_player function but things need to be optimized so these 2 functions actually differ (believe me, even tho it looks ugly, it makes sense)
function mcl_mobs.mob:get_near_object(radius, condition)
local eye_pos = self.object:get_pos()
eye_pos.y = eye_pos.y + self.eye_height
for _, obj in ipairs(minetest.get_objects_inside_radius(pos, radius)) do
if obj ~= self.object and mcl_util.get_hp(obj) > 0 and (not condition or condition(self, obj)) then
local obj_eye_pos = obj:get_pos()
obj_eye_pos.y = obj_eye_pos.y + mcl_mobs.util.get_eye_height(obj)
if minetest.line_of_sight(eye_pos, obj_eye_pos) then
return obj
end
end
end
end
-- this function gets a definition field DYNAMICALLY (if the field is a function, call it and return the result, else return the field directly)
function mcl_mobs.mob:evaluate(key, ...)
local value = self.def[key]
if value then
if type(value) == "function" then
value = value(self, ...)
end
return value
end
end
--[[
--a teleport functoin
mobs.teleport = function(self, target)
if self.do_teleport then
if self.do_teleport(self, target) == false then
return
end
end
end
--a simple helper function for mobs following
mobs.get_2d_distance = function(pos1,pos2)
pos1.y = 0
pos2.y = 0
return(vector.distance(pos1, pos2))
end
]]--

@ -0,0 +1,80 @@
function mcl_mobs.mob:deal_damage(damage, reason)
if self.dead or self.data.invulnerable then
return 0
end
if reason.flags.is_fire and self.def.fire_damage_resistant then
return 0
end
damage = mcl_damage.run_modifiers(self.object, damage, reason)
if damage > 0 then
mcl_damage.run_damage_callbacks(self.object, damage, reason)
self.data.health = self.data.health - damage
self.stun_timer = mcl_mobs.const.stun_timer
self:update_movement()
self.object:set_texture_mod("^[colorize:red:120")
if self.data.health < 0 then
self:die(reason)
else
self:play_sound("damage")
end
end
return damage
end
function mcl_mobs.mob:on_punch(puncher, time_from_last_punch, tool_capabilities, direction, damage)
if damage < 0 then
return
end
local reason = {}
mcl_damage.from_punch(reason, puncher)
mcl_damage.finish_reason(reason)
if self.def.on_punch then
local args = {puncher = puncher, time_from_last_punch = time_from_last_punch, tool_capabilities = tool_capabilities, direction = direction}
if self.def.on_punch(self, damage, reason, args) == false then
return true
end
end
self:get_angry(reason.source)
-- PANIC AND RUN
if self.def.skittish then
self.state = "run"
self.run_timer = mcl_mobs.const.run_timer
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = reason.source:get_pos()
pos2.y = 0
local dir = vector.direction(pos2, pos1)
self.yaw = minetest.dir_to_yaw(direction)
end
if reason.type == "player" then
mcl_hunger.exhaust(puncher:get_player_name(), mcl_hunger.EXHAUST_ATTACK)
end
damage = self:deal_damage(damage, reason)
if damage > 0 then
self:play_sound_specific("default_punch")
self:knockback(reason.source)
end
return true
end
function mcl_mobs.mob:update_armor_groups()
self.object:set_armor_groups(self.def.armor_groups)
end

@ -0,0 +1,57 @@
function mcl_mobs.mob:get_staticdata()
if self.dead then
self.object:remove()
return
end
self:anger_on_staticdata()
if self.def.on_staticdata then
if self.def.on_staticdata(self) == false then
self.object:remove()
return
end
end
return minetest.serialize(self.data)
end
function mcl_mobs.mob:on_activate(staticdata, def, dtime)
self.is_mob = true
self.def = mcl_mobs.registered_mobs[self.name] -- just access the mob def instead of spamming the luaentity itself with a copy of every single definition field that is never mutated
self.description = def.description -- external mods might want to access this
self.data = minetest.deserialize(staticdata) or {}
self.data.health = self.data.health or math.random(self.def.health_min, self.def.health_max)
self.data.breath = self.data.breath or self.def.breath_max
self.data.yaw = self.data.yaw or 0
self:reload_properties()
self:backup_movement()
self:anger_on_activate()
self:despawn_on_activate()
self:breeding_on_activate()
self:set_animation("stand")
self:update_collisionbox()
self:update_eye_height()
self:update_mesh()
self:update_nametag()
self:update_roll()
self:update_textures()
self:update_visual_size()
if self.def.on_spawn and not self.data.on_spawn_run then
self.def.on_spawn(self)
self.data.on_spawn_run = true
end
if self.def.on_activate then
if self.def.on_activate(self, staticdata, def, dtime) == false then
self.object:remove()
return
end
end
end

@ -0,0 +1,49 @@
function mcl_mobs.mob:die(reason)
self.dead = true
self.death_timer = mcl_mobs.const.death_timer
for _, obj in pairs(self.object:get_children()) do
mcl_mount.throw_off(obj)
end
if minetest.settings:get_bool("doMobDrops", true) then
self:drop_loot(reason)
end
self:play_sound("death")
self:set_animation("death")
self:set_properties({pointable = false})
self:update_acceleration()
if self.def.on_death then
self.def.on_death(self, reason)
end
end
function mcl_mobs.mob:death_step()
if self:do_timer("death") then
self:update_roll()
else
local pos = self.object:get_pos()
minetest.add_particlespawner({
amount = 50,
time = 0.0001,
minpos = vector.add(pos, self.collisionbox.min),
maxpos = vector.add(pos, self.collisionbox.max),
minvel = vector.new(-0.5, 0.5, -0.5),
maxvel = vector.new(0.5, 1.0, 0.5),
minexptime = 1.1,
maxexptime = 1.5,
minsize = 1,
maxsize = 2,
collisiondetection = false,
vertical = false,
texture = "mcl_particles_mob_death.png",
})
self:play_sound_specific("mcl_sounds_poof")
self.object:remove() -- RIP
end
end

@ -0,0 +1,24 @@
function mcl_mobs.mob:despawn_on_activate()
self.data.can_despawn = self.data.can_despawn ~= false and self.def.can_despawn
if self.data.can_despawn then
self.life_timer = life_timer -- how much time is left until next despawn check
end
end
function mcl_mobs.mob:despawn_step()
if not self:do_timer("life") then
self.life_timer = life_timer
return not self:check_despawn()
end
return true
end
function mcl_mobs.mob:check_despawn()
self:debug("checking for nearby players")
if not self:is_player_near(despawn_radius) then
self:debug("despawning")
self.object:remove()
return true
end
return false
end

@ -0,0 +1,34 @@
function mcl_mobs.mob:update_easteregg()
local old_easteregg = self.easteregg or {}
local eastereggs = table.key_value_swap(mcl_mobs.eastereggs)
local easteregg_name = eastereggs[self.data.nametag]
local easteregg = old_easteregg
if old_easteregg.name ~= easteregg_name then
easteregg = {
name = easteregg_name,
[easteregg_name] = true,
}
end
if easteregg.rainbow ~= old_easteregg.rainbow then
if easteregg.rainbow then
easteregg.hue = 0
end
elseif easteregg.upside_down ~= old_easteregg.upside_down then
self:update_roll()
self:update_collisionbox()
end
self.easteregg = easteregg
end
function mcl_mobs.mob:easteregg_step()
if self.easteregg.rainbow then
self.easteregg.hue = self.easteregg.hue + 60 * self.dtime
self:update_textures()
elseif self.easteregg.spin then
self.data.yaw = self.data.yaw + 180 * dtime
end
end

@ -0,0 +1,110 @@
-- 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 mobs:register_egg(mob, desc, background, 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 = background
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 mod_mobspawners and 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 mobs.is_creative(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
local mob = minetest.add_entity(pos, mob)
minetest.log("action", "mcl_mobs.mob spawned: "..name.." 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 mobs.is_creative(placer:get_player_name()) then
itemstack:take_item()
end
end
return itemstack
end,
})
end
]]--

@ -0,0 +1,22 @@
function mcl_mobs.mob:breath_step()
local pos = self.object:get_pos()
pos.y = pos.y + self.eye_height
local node = minetest.get_node(pos).name
if minetest.get_item_group(node, "water") ~= 0 then
self.data.breath = self.data.breath - self.dtime
if self.data.breath <= 0 then
self:deal_damage(4, {type = "drowning"})
self.data.breath = 1
end
elseif self.data.breath < self.def.breath_max then
self.data.breath = self.data.breath + self.dtime
if self.data.breath > self.def.breath_max then
self.data.breath = self.def.breath_max
end
end
end

@ -0,0 +1,65 @@
function mcl_mobs.mob:collision_step()
local own_box, own_pos, own_boundary = mcl_mobs.util.get_collision_data(self.object)
local radius = math.max(own_boundary, own_box[5])
local max_cramming = tonumber(minetest.settings:get("mclMaxEntityCramming")) or mcl_mobs.const.max_entity_cramming
local parent = self.object:get_attach()
for _, obj in pairs(minetest.get_objects_inside_radius(own_pos, radius * 1.25)) do
if obj ~= self.object and obj ~= parent and obj:get_attach() ~= self.object then
local luaentity = obj:get_luaentity()
if not luaentity and obj:get_hp() > 0 or luaentity and luaentity.is_mob and not luaentity.dead then
max_cramming = max_cramming - 1
if max_cramming <= 0 then
local target, source = self.object, obj
-- hurt adults before babies
if self.data.baby and luaentity then
target, source = source, target -- how the turntables...
end
mcl_util.deal_damage(target, mcl_util.get_hp(target), {type = "cramming", source = source})
return
end
local obj_box, obj_pos, obj_boundary = mcl_mobs.util.get_collision_data(obj)
-- this is checking the difference of the object collided with's possision
-- if positive top of other object is inside (y axis) of current object
local y_base_diff = obj_pos.y + obj_box[5] - own_pos.y
local y_top_diff = own_pos.y + own_box[5] - obj_pos.y
local distance = vector.distance(
vector.new(own_pos.x, 0, own_pos.z),
vector.new(obj_pos.x, 0, obj_pos.z)
)
local combined_boundary = own_boundary + obj_boundary
if distance <= combined_boundary and y_base_diff >= 0 and y_top_diff >= 0 then
local dir = vector.direction(own_pos, obj_pos)
dir.y = 0
-- eliminate mob being stuck in corners
if dir.x == 0 and dir.z == 0 then
-- slightly adjust mob position to prevent equal length
-- corner/wall sticking
dir.x = dir.x + math.random() / 10 * (math.round(math.random()) * 2 - 1)
dir.z = dir.z + math.random() / 10 * (math.round(math.random()) * 2 - 1)
end
local obj_vel = vector.multiply(dir, 0.5 * (1 - distance / combined_boundary) * 1.5)
local own_vel = vector.multiply(obj_vel, -10)
if not luaentity then
obj_vel = vector.multiply(obj_vel, 2.5)
end
obj:add_velocity(obj_vel)
self.object:add_velocity(own_vel)
end
end
end
end
end

23
mods/ENTITIES/mcl_mobs/api/env/env.lua vendored Normal file

@ -0,0 +1,23 @@
function mcl_mobs.mob:env_step()
self:fall_damage_step()
if not self.def.breathes_in_water then
self:breath_step()
end
mcl_burning.tick(self.object, dtime, self.data)
if self.dead then
return false
end
if self.def.ignited_by_sunlight then
self:sunlight_step()
end
if not self.def.unpushable then
self:collision_step()
end
return true
end

@ -0,0 +1,8 @@
function mcl_mobs.mob:fall_damage_step()
-- ToDo: fall damage based on distance, not velocity
local velocity = self.object:get_velocity()
if self.last_velocity.y < -7 and velocity.y == 0 then
self:deal_damage(math.abs(self.last_velocity.y + 7) * 2, {type = "fall"})
end
end

@ -0,0 +1,15 @@
function mcl_mobs.mob:sunlight_step()
if self.data.burn_time then
return
end
local pos = self.object:get_pos()
pos.y = pos.y + 0.1
if mcl_worlds.pos_to_dimension(pos) == "overworld" then
local ok, light = pcall(minetest.get_natural_light or minetest.get_node_light, pos, minetest.get_timeofday())
if ok and light >= minetest.LIGHT_MAX then
mcl_burning.set_on_fire(self.object, math.huge)
end
end
end

@ -0,0 +1,64 @@
-- set defined animation
function mcl_mobs.mob: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 (not self.animation[anim .. "_start"] or not self.animation[anim .. "_end"]) then
return
end
--animations break if they are constantly set
--so we put this return gate to check if it is
--already at the animation we are trying to implement
if self.current_animation == anim then
return
end
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
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)
self.current_animation = anim
end
--this is a helper function for mobs explosion animation
function mcl_mobs.mob:handle_explosion_animation()
--secondary catch-all
if not self.explosion_animation then
self.explosion_animation = 0
end
--the timer works from 0 for sense of a 0 based counting
--but this just bumps it up so it's usable in here
local explosion_timer_adjust = self.explosion_animation + 1
local visual_size_modified = table.copy(self.visual_size_origin)
visual_size_modified.x = visual_size_modified.x * (explosion_timer_adjust ^ 3)
visual_size_modified.y = visual_size_modified.y * explosion_timer_adjust
self.object:set_properties({visual_size = visual_size_modified})
end

@ -0,0 +1,21 @@
function mcl_mobs.mob:update_collisionbox()
local box = self.def.collisionbox
if self.baby and self.def.baby_size then
box = mcl_mobs.util.scale_size(box, self.def.baby_size)
end
if self.easteregg.upside_down then
box[2], box[5] = -box[5], -box[2]
end
self.collisionbox = {
min = vector.new(box[1], box[2], box[3]),
max = vector.new(box[4], box[5], box[6]),
}
self:set_properties({collisionbox = box})
self.collisionbox_cache = nil
mcl_mount.update_children_visual_size(self.object)
end

@ -0,0 +1,9 @@
function mcl_mobs.mob:update_eye_height()
local eye_height = self.def.eye_height
if self.data.baby and self.def.baby_size then
eye_height = eye_height * self.def.baby_size
end
self.eye_height = eye_height
end

@ -1,4 +1,6 @@
local vector_new = vector.new
--[[
local vector.new = vector.new
--converts yaw to degrees --converts yaw to degrees
@ -43,7 +45,7 @@ mobs.do_head_logic = function(self,dtime)
}) })
local bone_pos = vector_new(0,0,0) local bone_pos = vector.new(0,0,0)
--(horizontal) --(horizontal)
@ -60,7 +62,7 @@ mobs.do_head_logic = function(self,dtime)
--bone_rot.z = bone_rot.z + (dtime * 10) --bone_rot.z = bone_rot.z + (dtime * 10)
local head_yaw local head_yaw
head_yaw = minetest.dir_to_yaw(vector.direction(pos,look_at)) - body_yaw head_yaw = minetest.dir_to_yaw(vector.direction(pos,look_at)) - body_yaw
if self.reverse_head_yaw then if self.reverse_head_yaw then
@ -89,9 +91,9 @@ mobs.do_head_logic = function(self,dtime)
local head_pitch = 0 local head_pitch = 0
--DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG --DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG
--head_yaw = 0 --head_yaw = 0
--DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG --DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG
if not check_failed then if not check_failed then
head_pitch = minetest.dir_to_yaw(vector.new(vector.distance(vector.new(pos.x,0,pos.z),vector.new(look_at.x,0,look_at.z)),0,pos.y-look_at.y))+(math.pi/2) head_pitch = minetest.dir_to_yaw(vector.new(vector.distance(vector.new(pos.x,0,pos.z),vector.new(look_at.x,0,look_at.z)),0,pos.y-look_at.y))+(math.pi/2)
@ -102,11 +104,12 @@ mobs.do_head_logic = function(self,dtime)
end end
if self.swap_y_with_x then if self.swap_y_with_x then
self.object:set_bone_position(self.head_bone, bone_pos, vector_new(degrees(head_pitch),degrees(head_yaw),0)) self.object:set_bone_position(self.head_bone, bone_pos, vector.new(degrees(head_pitch),degrees(head_yaw),0))
else else
self.object:set_bone_position(self.head_bone, bone_pos, vector_new(degrees(head_pitch),0,degrees(head_yaw))) self.object:set_bone_position(self.head_bone, bone_pos, vector.new(degrees(head_pitch),0,degrees(head_yaw)))
end end
--set_bone_position([bone, position, rotation]) --set_bone_position([bone, position, rotation])
end end
--]]

@ -0,0 +1,6 @@
function mcl_mobs.mob:update_mesh()
self:set_properties({
visual = "mesh",
mesh = self.def.model,
})
end

@ -0,0 +1,7 @@
function mcl_mobs.mob:update_nametag()
self:update_easteregg()
self:set_properties({
nametag = self.data.nametag,
})
end

@ -0,0 +1,110 @@
-- this is used when a mob is following player and for when mobs breed
function mcl_mobs.mob:look_at(obj)
self:lock_yaw()
-- turn positions into pseudo 2d vectors
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = obj:get_pos()
pos2.y = 0
local new_direction = vector.direction(pos1, pos2)
local new_yaw = minetest.dir_to_yaw(new_direction)
self.object:set_yaw(new_yaw)
self.yaw = new_yaw
end
-- this allows auto facedir rotation while making it so mobs
-- don't look like wet noodles flopping around
function mcl_mobs.mob:movement_rotation_lock()
local current_engine_yaw = self.object:get_yaw()
local current_lua_yaw = self.yaw
if current_engine_yaw > math.pi * 2 then
current_engine_yaw = current_engine_yaw - math.pi * 2
end
local diff = math.abs(current_engine_yaw - current_lua_yaw)
if diff <= 0.05 then
self:lock_yaw()
elseif diff > 0.05 then
self:unlock_yaw()
end
end
-- this is used to unlock a mob's yaw after attacking
function mcl_mobs.mob:unlock_yaw()
if not self.properties.automatic_face_movement_dir then
self:set_properties({automatic_face_movement_dir = self.def.rotate})
end
end
-- this is used to lock a mob's yaw when they're standing
function mcl_mobs.mob:lock_yaw()
if self.properties.automatic_face_movement_dir then
self:set_properties({automatic_face_movement_dir = false})
end
end
function mcl_mobs.mob:calculate_pitch(self)
local pos = self.object:get_pos()
local pos2 = self.old_pos
if pos == nil or pos2 == nil then
return false
end
return minetest.dir_to_yaw(vector.new(vector.distance(vector.new(pos.x, 0, pos.z), vector.new(pos2.x, 0, pos2.z)), 0, pos.y - pos2.y)) + math.pi / 2
end
--this is a helper function used to make mobs pitch rotation dynamically flow when flying/swimming
function mcl_mobs.mob:set_dynamic_pitch()
local pitch = self:calculate_pitch()
if not pitch then
return
end
local rotation = self.object:get_rotation()
rotation.x = pitch
self.object:set_rotation(rotation)
self.dynamic_pitch = true
end
--this is a helper function used to make mobs pitch rotation reset when flying/swimming
function mcl_mobs.mob:set_static_pitch()
if not self.dynamic_pitch then
return
end
local current_rotation = self.object:get_rotation()
current_rotation.x = 0
self.object:set_rotation(current_rotation)
self.dynamic_pitch = nil
end
function mcl_mobs.mob:quick_rotate()
self.yaw = self.yaw + math.pi * 2 * 0.03125
if self.yaw > math.pi * 2 then
self.yaw = self.yaw - math.pi * 2
end
end
function mcl_mobs.mob:update_roll()
local roll = 0
if self.dead then
roll = math.pi * math.min(0.5, 1 - self.death_timer / mcl_mobs.const.death_timer)
elseif self.easteregg.upside_down then
roll = math.pi
end
local rotation = self.object:get_rotation()
rotation.z = roll
self.object:set_rotation(rotation)
end

@ -0,0 +1,17 @@
function mcl_mobs.mob:get_special_textures()
if self.baby then
return self:evaluate("baby_textures")
elseif self.gotten then
return self:evaluate("gotten_textures")
elseif self.easteregg.rainbow then
return self:evaluate("rainbow_textures", mcl_mobs.util.color_from_hue(self.easteregg.hue))
end
end
function mcl_mobs.mob:get_textures()
return self:get_special_textures() or self:calculate_textures(self.def.textures)
end
function mcl_mobs.mob:update_textures()
self:set_properties({textures = self:get_textures()})
end

@ -0,0 +1,21 @@
function mcl_mobs.mob:update_visual_size()
local size = self.def.visual_size
if self.data.size then
mcl_mobs.util.scale_size(size, self.data.size)
end
if self.data.baby and self.def.baby_size then
mcl_mobs.util.scale_size(size, self.def.baby_size)
end
local parent = self.object:get_attach()
if parent then
size = vector.divide(size, parent:get_properties().visual_size)
end
self:set_properties({visual_size = size})
mcl_mount.update_children_visual_size(self.object)
end

@ -0,0 +1,59 @@
function mcl_mobs.mob:on_rightclick(clicker)
local itemstack = clicker:get_wielded_item()
if self:on_rightclick_handler(clicker, itemstack) then
clicker:set_wielded_item(itemstack)
end
end
function mcl_mobs.mob:feed(clicker, itemname)
if self.data.heal_with[itemname] and self.data.health < self.def.health_max then
self:heal()
elseif self.def.boost_with[itemname] and self.data.baby then
self:boost()
elseif self.def.breed_with[itemname] and not self.data.bred then
self:init_breeding()
elseif self.data.tame_with[itemname] and not self.def.tamed then
self:tame(clicker)
else
return false
end
return true
end
function mcl_mobs.mob:on_rightclick_handler(clicker, itemstack)
if self.dead then
return false
end
if self.def.on_rightclick then
if self.def.on_rightclick(clicker, itemstack) then
return true
end
end
local itemname = itemstack:get_name()
if not self.def.ignores_nametag and itemname == "mcl_mobitems:nametag" then
local tag = item:get_meta():get_string("name")
if tag ~= "" then
self.data.nametag = tag
self:update_nametag()
return mcl_mobs.util.take_item(clicker, itemstack)
end
end
if self:feed(clicker, itemstack) then
mcl_mobs.util.take_item(clicker, itemname)
self:play_sound_specific("mobs_mc_animal_eat_generic")
return true
end
if not self.data.gotten and self.def.get_with[itemname] and self.def.get(self, clicker, itemstack) then
self.data.gotten = true
self.data.gotten_timer = self:evaluate("gotten_cooldown")
return true
end
end

@ -0,0 +1,35 @@
function mcl_mobs.mob:knockback(hitter)
if self.def.knockback_multiplier == 0 then
return
end
if hitter:get_attach() == self.object then
return
end
local velocity = self.object:get_velocity()
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = hitter:get_pos()
pos2.y = 0
local dir = vector.direction(pos2, pos1)
local up = mcl_mobs.const.knockback_up
if velocity.y ~= 0 then
up = 0
end
local multiplier = mcl_mobs.const.knockback
local knockback_level = mcl_enchanting.get_enchantment(mcl_util.get_wield_item(hitter), "knockback")
if knockback_level > 0 then
multiplier = multiplier + knockback_level * 3
end
dir = vector.multiply(dir, multiplier * self.def.knockback_multiplier)
dir.y = up * self.def.knockback_multiplier
self.object:add_velocity(dir)
end

@ -0,0 +1,62 @@
function mcl_mobs.mob:drop_loot(reason)
if self.data.baby and self.def.type ~= "monster" then
return
end
local enchantments = reason.source and mcl_enchanting.get_enchantments(mcl_util.get_wield_item(reason.source)) or {}
local cooked = self.data.burn_time or enchantments.fire_aspect
local looting = enchantments.looting or 0
mcl_experience.throw_experience(self.object:get_pos(), math.random(self.def.xp_min, self.def.xp_max))
local pos = self.object:get_pos()
for _, dropdef in pairs(self:evaluate("drops")) do
local chance = 1 / dropdef.chance
local looting_type = dropdef.looting
if looting > 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 count = 0
local do_common_looting = looting > 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 count > 0 then
local item = dropdef.name
if cooked and dropdef.cookable 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, count do
minetest.add_item(pos, ItemStack(item)):set_velocity({
x = math.random(-10, 10) / 9,
y = 6,
z = math.random(-10, 10) / 9,
})
end
end
end
end

@ -0,0 +1,60 @@
function mcl_mobs.mob:on_step(dtime, moveresult)
self.dtime = dtime
self:reload_properties()
local stunned = self.stun_timer and self:do_timer("stun")
-- can be true (currently stunned), nil (not stunned) or false (stopped being stunned in this tick, which is what we want to check for here)
if stunned == false then
self.object:set_texture_mod("")
end
self:update_node_type()
self:movement_step()
if self.dead then
self:death_step()
return
end
if self.def.hostile and not minetest.settings:get_bool("mclPeacefulMode") then
self:debug("peaceful mode active, removing")
self:deal_damage(self.data.health, {type = "out_of_world"})
end
if self.data.can_despawn then
if not self:despawn_step() then
return
end
end
if self.def.on_step then
if self.def.on_step(self, dtime, moveresult) == false then
return
end
end
if not self.data.silent then
self:sound_step()
end
self:easteregg_step()
if not self:env_step() then
return
end
if self.data.baby then
self:baby_step()
end
if self.data.gotten and not self:do_timer("gotten", true) then
self.data.gotten = nil
end
if not self.data.no_ai and not stunned then
self:ai_step()
end
self:backup_movement()
end

File diff suppressed because it is too large Load Diff

@ -1,259 +0,0 @@
local math_pi = math.pi
local math_floor = math.floor
local math_random = math.random
local HALF_PI = math_pi/2
local vector_direction = vector.direction
local vector_distance = vector.distance
local vector_new = vector.new
local minetest_dir_to_yaw = minetest.dir_to_yaw
-- set defined animation
mobs.set_mob_animation = function(self, 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 (not self.animation[anim .. "_start"] or not self.animation[anim .. "_end"]) then
return
end
--animations break if they are constantly set
--so we put this return gate to check if it is
--already at the animation we are trying to implement
if self.current_animation == anim then
return
end
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
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)
self.current_animation = anim
end
mobs.death_effect = function(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
--this allows auto facedir rotation while making it so mobs
--don't look like wet noodles flopping around
mobs.movement_rotation_lock = function(self)
local current_engine_yaw = self.object:get_yaw()
local current_lua_yaw = self.yaw
if current_engine_yaw > math.pi * 2 then
current_engine_yaw = current_engine_yaw - (math.pi * 2)
end
if math.abs(current_engine_yaw - current_lua_yaw) <= 0.05 and self.object:get_properties().automatic_face_movement_dir then
self.object:set_properties{automatic_face_movement_dir = false}
elseif math.abs(current_engine_yaw - current_lua_yaw) > 0.05 and self.object:get_properties().automatic_face_movement_dir == false then
self.object:set_properties{automatic_face_movement_dir = self.rotate}
end
end
--this is used when a mob is chasing a player
mobs.set_yaw_while_attacking = function(self)
if self.object:get_properties().automatic_face_movement_dir then
self.object:set_properties{automatic_face_movement_dir = false}
end
--turn positions into pseudo 2d vectors
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = self.attacking:get_pos()
pos2.y = 0
local new_direction = vector_direction(pos1,pos2)
local new_yaw = minetest_dir_to_yaw(new_direction)
self.object:set_yaw(new_yaw)
self.yaw = new_yaw
end
--this is used to unlock a mob's yaw after attacking
mobs.unlock_yaw = function(self)
if self.object:get_properties().automatic_face_movement_dir == false then
self.object:set_properties{automatic_face_movement_dir = self.rotate}
end
end
--this is used to lock a mob's yaw when they're standing
mobs.lock_yaw = function(self)
if self.object:get_properties().automatic_face_movement_dir then
self.object:set_properties{automatic_face_movement_dir = false}
end
end
local calculate_pitch = function(self)
local pos = self.object:get_pos()
local pos2 = self.old_pos
if pos == nil or pos2 == nil then
return false
end
return(minetest_dir_to_yaw(vector_new(vector_distance(vector_new(pos.x,0,pos.z),vector_new(pos2.x,0,pos2.z)),0,pos.y - pos2.y)) + HALF_PI)
end
--this is a helper function used to make mobs pitch rotation dynamically flow when flying/swimming
mobs.set_dynamic_pitch = function(self)
local pitch = calculate_pitch(self)
if not pitch then
return
end
local current_rotation = self.object:get_rotation()
current_rotation.x = pitch
self.object:set_rotation(current_rotation)
self.pitch_switch = "dynamic"
end
--this is a helper function used to make mobs pitch rotation reset when flying/swimming
mobs.set_static_pitch = function(self)
if self.pitch_switch == "static" then
return
end
local current_rotation = self.object:get_rotation()
current_rotation.x = 0
self.object:set_rotation(current_rotation)
self.pitch_switch = "static"
end
--this is a helper function for mobs explosion animation
mobs.handle_explosion_animation = function(self)
--secondary catch-all
if not self.explosion_animation then
self.explosion_animation = 0
end
--the timer works from 0 for sense of a 0 based counting
--but this just bumps it up so it's usable in here
local explosion_timer_adjust = self.explosion_animation + 1
local visual_size_modified = table.copy(self.visual_size_origin)
visual_size_modified.x = visual_size_modified.x * (explosion_timer_adjust ^ 3)
visual_size_modified.y = visual_size_modified.y * explosion_timer_adjust
self.object:set_properties({visual_size = visual_size_modified})
end
--this is used when a mob is following player
mobs.set_yaw_while_following = function(self)
if self.object:get_properties().automatic_face_movement_dir then
self.object:set_properties{automatic_face_movement_dir = false}
end
--turn positions into pseudo 2d vectors
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = self.following_person:get_pos()
pos2.y = 0
local new_direction = vector_direction(pos1,pos2)
local new_yaw = minetest_dir_to_yaw(new_direction)
self.object:set_yaw(new_yaw)
self.yaw = new_yaw
end
--this is used for when mobs breed
mobs.set_yaw_while_breeding = function(self, mate)
if self.object:get_properties().automatic_face_movement_dir then
self.object:set_properties{automatic_face_movement_dir = false}
end
--turn positions into pseudo 2d vectors
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = mate:get_pos()
pos2.y = 0
local new_direction = vector_direction(pos1,pos2)
local new_yaw = minetest_dir_to_yaw(new_direction)
self.object:set_yaw(new_yaw)
self.yaw = new_yaw
end

@ -1,351 +0,0 @@
local vector_direction = vector.direction
local minetest_dir_to_yaw = minetest.dir_to_yaw
local vector_distance = vector.distance
local vector_multiply = vector.multiply
local math_random = math.random
--[[
_ _ _ _
| | | | | | | |
| | | | __ _ _ __ __| | | |
| | | | / _` | '_ \ / _` | | |
|_| | |___| (_| | | | | (_| | |_|
(_) \_____/\__,_|_| |_|\__,_| (_)
]]--
--[[
_____ _ _
| ___| | | | |
| |____ ___ __ | | ___ __| | ___
| __\ \/ / '_ \| |/ _ \ / _` |/ _ \
| |___> <| |_) | | (_) | (_| | __/
\____/_/\_\ .__/|_|\___/ \__,_|\___|
| |
|_|
]]--
mobs.explode_attack_walk = function(self,dtime)
--this needs an exception
if self.attacking == nil or not self.attacking:is_player() then
self.attacking = nil
return
end
mobs.set_yaw_while_attacking(self)
local distance_from_attacking = vector_distance(self.object:get_pos(), self.attacking:get_pos())
--make mob walk up to player within 2 nodes distance then start exploding
if distance_from_attacking >= self.reach and
--don't allow explosion to cancel unless out of the reach boundary
not (self.explosion_animation ~= nil and self.explosion_animation > 0 and distance_from_attacking <= self.defuse_reach) then
mobs.set_velocity(self, self.run_velocity)
mobs.set_mob_animation(self,"run")
mobs.reverse_explosion_animation(self,dtime)
else
mobs.set_velocity(self,0)
--this is the only way I can reference this without dumping extra data on all mobs
if not self.explosion_animation then
self.explosion_animation = 0
end
--play ignite sound
if self.explosion_animation == 0 then
mobs.play_sound(self,"attack")
end
mobs.set_mob_animation(self,"stand")
mobs.handle_explosion_animation(self)
self.explosion_animation = self.explosion_animation + (dtime/2.5)
end
--make explosive mobs jump
--check for nodes to jump over
--explosive mobs will just ride against walls for now
local node_in_front_of = mobs.jump_check(self)
if node_in_front_of == 1 then
mobs.jump(self)
end
--do biggening explosion thing
if self.explosion_animation and self.explosion_animation > self.explosion_timer then
mcl_explosions.explode(self.object:get_pos(), self.explosion_strength,{ drop_chance = 1.0 })
self.object:remove()
end
end
--this is a small helper function to make working with explosion animations easier
mobs.reverse_explosion_animation = function(self,dtime)
--if explosion animation was greater than 0 then reverse it
if self.explosion_animation ~= nil and self.explosion_animation > 0 then
self.explosion_animation = self.explosion_animation - dtime
if self.explosion_animation < 0 then
self.explosion_animation = 0
end
end
mobs.handle_explosion_animation(self)
end
--[[
______ _
| ___ \ | |
| |_/ / _ _ __ ___| |__
| __/ | | | '_ \ / __| '_ \
| | | |_| | | | | (__| | | |
\_| \__,_|_| |_|\___|_| |_|
]]--
mobs.punch_attack_walk = function(self,dtime)
--this needs an exception
if self.attacking == nil or not self.attacking:is_player() then
self.attacking = nil
return
end
local distance_from_attacking = mobs.get_2d_distance(self.object:get_pos(), self.attacking:get_pos())
if distance_from_attacking >= self.minimum_follow_distance then
mobs.set_velocity(self, self.run_velocity)
mobs.set_mob_animation(self, "run")
else
mobs.set_velocity(self, 0)
mobs.set_mob_animation(self, "stand")
end
mobs.set_yaw_while_attacking(self)
--make punchy mobs jump
--check for nodes to jump over
--explosive mobs will just ride against walls for now
local node_in_front_of = mobs.jump_check(self)
if node_in_front_of == 1 then
mobs.jump(self)
end
--mobs that can climb over stuff
if self.always_climb and node_in_front_of > 0 then
mobs.climb(self)
end
--auto reset punch_timer
if not self.punch_timer then
self.punch_timer = 0
end
if self.punch_timer > 0 then
self.punch_timer = self.punch_timer - dtime
end
end
mobs.punch_attack = function(self)
self.attacking:punch(self.object, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = self.damage}
}, nil)
self.punch_timer = self.punch_timer_cooloff
--knockback
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = self.attacking:get_pos()
pos2.y = 0
local dir = vector_direction(pos1,pos2)
dir = vector_multiply(dir,3)
if self.attacking:get_velocity().y <= 1 then
dir.y = 5
end
self.attacking:add_velocity(dir)
end
--[[
______ _ _ _ _
| ___ \ (_) | | (_) |
| |_/ / __ ___ _ ___ ___| |_ _| | ___
| __/ '__/ _ \| |/ _ \/ __| __| | |/ _ \
| | | | | (_) | | __/ (__| |_| | | __/
\_| |_| \___/| |\___|\___|\__|_|_|\___|
_/ |
|__/
]]--
mobs.projectile_attack_walk = function(self,dtime)
--this needs an exception
if self.attacking == nil or not self.attacking:is_player() then
self.attacking = nil
return
end
mobs.set_yaw_while_attacking(self)
local distance_from_attacking = vector_distance(self.object:get_pos(), self.attacking:get_pos())
if distance_from_attacking >= self.reach then
mobs.set_velocity(self, self.run_velocity)
mobs.set_mob_animation(self,"run")
else
mobs.set_velocity(self,0)
mobs.set_mob_animation(self,"stand")
end
--do this to not load data into other mobs
if not self.projectile_timer then
self.projectile_timer = math_random(self.projectile_cooldown_min, self.projectile_cooldown_max)
end
--run projectile timer
if self.projectile_timer > 0 then
self.projectile_timer = self.projectile_timer - dtime
--shoot
if self.projectile_timer <= 0 then
--reset timer
self.projectile_timer = math_random(self.projectile_cooldown_min, self.projectile_cooldown_max)
mobs.shoot_projectile(self)
end
end
--make shooty mobs jump
--check for nodes to jump over
--explosive mobs will just ride against walls for now
local node_in_front_of = mobs.jump_check(self)
if node_in_front_of == 1 then
mobs.jump(self)
end
end
--[[
_ ______ _ _
| | | ___| | | |
| | | |_ | |_ _ | |
| | | _| | | | | | | |
|_| | | | | |_| | |_|
(_) \_| |_|\__, | (_)
__/ |
|___/
]]--
--[[
______ _ _ _ _
| ___ \ (_) | | (_) |
| |_/ / __ ___ _ ___ ___| |_ _| | ___
| __/ '__/ _ \| |/ _ \/ __| __| | |/ _ \
| | | | | (_) | | __/ (__| |_| | | __/
\_| |_| \___/| |\___|\___|\__|_|_|\___|
_/ |
|__/
]]--
local random_pitch_multiplier = {-1,1}
mobs.projectile_attack_fly = function(self, dtime)
--this needs an exception
if self.attacking == nil or not self.attacking:is_player() then
self.attacking = nil
return
end
--this is specifically for random ghast movement
if self.fly_random_while_attack then
--enable rotation locking
mobs.movement_rotation_lock(self)
self.walk_timer = self.walk_timer - dtime
--reset the walk timer
if self.walk_timer <= 0 then
--re-randomize the walk timer
self.walk_timer = math.random(1,6) + math.random()
--set the mob into a random direction
self.yaw = (math_random() * (math.pi * 2))
--create a truly random pitch, since there is no easy access to pitch math that I can find
self.pitch = math_random() * math.random(1,3) * random_pitch_multiplier[math_random(1,2)]
end
mobs.set_fly_velocity(self, self.run_velocity)
else
mobs.set_yaw_while_attacking(self)
local distance_from_attacking = vector_distance(self.object:get_pos(), self.attacking:get_pos())
if distance_from_attacking >= self.reach then
mobs.set_pitch_while_attacking(self)
mobs.set_fly_velocity(self, self.run_velocity)
mobs.set_mob_animation(self,"run")
else
mobs.set_pitch_while_attacking(self)
mobs.set_fly_velocity(self, 0)
mobs.set_mob_animation(self,"stand")
end
end
--do this to not load data into other mobs
if not self.projectile_timer then
self.projectile_timer = math_random(self.projectile_cooldown_min, self.projectile_cooldown_max)
end
--run projectile timer
if self.projectile_timer > 0 then
self.projectile_timer = self.projectile_timer - dtime
--shoot
if self.projectile_timer <= 0 then
if self.fly_random_while_attack then
mobs.set_yaw_while_attacking(self)
self.walk_timer = 0
end
--reset timer
self.projectile_timer = math_random(self.projectile_cooldown_min, self.projectile_cooldown_max)
mobs.shoot_projectile(self)
end
end
end

@ -1,184 +0,0 @@
local minetest_get_objects_inside_radius = minetest.get_objects_inside_radius
local vector_distance = vector.distance
--check to see if someone nearby has some tasty food
mobs.check_following = function(self) -- returns true or false
--ignore
if not self.follow then
self.following_person = nil
return(false)
end
--hey look, this thing works for passive mobs too!
local follower = mobs.detect_closest_player_within_radius(self,true,self.view_range,self.eye_height)
--check if the follower is a player incase they log out
if follower and follower:is_player() then
local stack = follower:get_wielded_item()
--safety check
if not stack then
self.following_person = nil
return(false)
end
local item_name = stack:get_name()
--all checks have passed, that guy has some good looking food
if item_name == self.follow then
self.following_person = follower
return(true)
end
end
--everything failed
self.following_person = nil
return(false)
end
--a function which attempts to make mobs enter
--the breeding state
mobs.enter_breed_state = function(self,clicker)
--do not breed if baby
if self.baby then
return(false)
end
--do not do anything if looking for mate or
--if cooling off from breeding
if self.breed_lookout_timer > 0 or self.breed_timer > 0 then
return(false)
end
--if this is caught, that means something has gone
--seriously wrong
if not clicker or not clicker:is_player() then
return(false)
end
local stack = clicker:get_wielded_item()
--safety check
if not stack then
return(false)
end
local item_name = stack:get_name()
--all checks have passed, that guy has some good looking food
if item_name == self.follow then
if not minetest.is_creative_enabled(clicker:get_player_name()) then
stack:take_item()
clicker:set_wielded_item(stack)
end
self.breed_lookout_timer = self.breed_lookout_timer_goal
self.bred = true
mobs.play_sound_specific(self,"mobs_mc_animal_eat_generic")
return(true)
end
--everything failed
return(false)
end
--find the closest mate in the area
mobs.look_for_mate = function(self)
local pos1 = self.object:get_pos()
pos1.y = pos1.y + self.eye_height
local mates_in_area = {}
local winner_mate = nil
local mates_detected = 0
local radius = self.view_range
--get mates in radius
for _,mate in pairs(minetest_get_objects_inside_radius(pos1, radius)) do
--look for a breeding mate
if mate and mate:get_luaentity()
and mate:get_luaentity()._cmi_is_mob
and mate:get_luaentity().name == self.name
and mate:get_luaentity().breed_lookout_timer > 0
and mate:get_luaentity() ~= self then
local pos2 = mate:get_pos()
local distance = vector_distance(pos1,pos2)
if distance <= radius then
if line_of_sight then
--must add eye height or stuff breaks randomly because of
--seethrough nodes being a blocker (like grass)
if minetest_line_of_sight(
vector_new(pos1.x, pos1.y, pos1.z),
vector_new(pos2.x, pos2.y + mate:get_properties().eye_height, pos2.z)
) then
mates_detected = mates_detected + 1
mates_in_area[mate] = distance
end
else
mates_detected = mates_detected + 1
mates_in_area[mate] = distance
end
end
end
end
--return if there's no one near by
if mates_detected <= 0 then --handle negative numbers for some crazy error that could possibly happen
return nil
end
--do a default radius max
local shortest_distance = radius + 1
--sort through mates and find the closest mate
for mate,distance in pairs(mates_in_area) do
if distance < shortest_distance then
shortest_distance = distance
winner_mate = mate
end
end
return(winner_mate)
end
--make the baby grow up
mobs.baby_grow_up = function(self)
self.baby = nil
self.visual_size = self.backup_visual_size
self.collisionbox = self.backup_collisionbox
self.selectionbox = self.backup_selectionbox
self.object:set_properties(self)
end
--makes the baby grow up faster with diminishing returns
mobs.make_baby_grow_faster = function(self,clicker)
if clicker and clicker:is_player() then
local stack = clicker:get_wielded_item()
--safety check
if not stack then
return(false)
end
local item_name = stack:get_name()
--all checks have passed, that guy has some good looking food
if item_name == self.follow then
self.grow_up_timer = self.grow_up_timer - (self.grow_up_timer * 0.10) --take 10 percent off - diminishing returns
if not minetest.is_creative_enabled(clicker:get_player_name()) then
stack:take_item()
clicker:set_wielded_item(stack)
end
mobs.play_sound_specific(self,"mobs_mc_animal_eat_generic")
return(true)
end
end
return(false)
end

@ -1,140 +0,0 @@
local minetest_get_objects_inside_radius = minetest.get_objects_inside_radius
local math_random = math.random
local vector_multiply = vector.multiply
local vector_direction = vector.direction
local integer_test = {-1,1}
mobs.collision = function(self)
local pos = self.object:get_pos()
if not self or not self.object or not self.object:get_luaentity() then
return
end
--do collision detection from the base of the mob
local collisionbox = self.object:get_properties().collisionbox
pos.y = pos.y + collisionbox[2]
local collision_boundary = collisionbox[4]
local radius = collision_boundary
if collisionbox[5] > collision_boundary then
radius = collisionbox[5]
end
local collision_count = 0
local check_for_attack = false
if self.attack_type == "punch" and self.hostile and self.attacking then
check_for_attack = true
end
for _,object in ipairs(minetest_get_objects_inside_radius(pos, radius*1.25)) do
if object and object ~= self.object and (object:is_player() or (object:get_luaentity() and object:get_luaentity()._cmi_is_mob == true and object:get_luaentity().health > 0)) and
--don't collide with rider, rider don't collide with thing
(not object:get_attach() or (object:get_attach() and object:get_attach() ~= self.object)) and
(not self.object:get_attach() or (self.object:get_attach() and self.object:get_attach() ~= object)) then
--stop infinite loop
collision_count = collision_count + 1
--mob cramming
if collision_count > 30 then
self.health = -20
break
end
local pos2 = object:get_pos()
local object_collisionbox = object:get_properties().collisionbox
pos2.y = pos2.y + object_collisionbox[2]
local object_collision_boundary = object_collisionbox[4]
--this is checking the difference of the object collided with's possision
--if positive top of other object is inside (y axis) of current object
local y_base_diff = (pos2.y + object_collisionbox[5]) - pos.y
local y_top_diff = (pos.y + collisionbox[5]) - pos2.y
local distance = vector.distance(vector.new(pos.x,0,pos.z),vector.new(pos2.x,0,pos2.z))
if distance <= collision_boundary + object_collision_boundary and y_base_diff >= 0 and y_top_diff >= 0 then
local dir = vector.direction(pos,pos2)
dir.y = 0
--eliminate mob being stuck in corners
if dir.x == 0 and dir.z == 0 then
--slightly adjust mob position to prevent equal length
--corner/wall sticking
dir.x = dir.x + ((math_random()/10)*integer_test[math.random(1,2)])
dir.z = dir.z + ((math_random()/10)*integer_test[math.random(1,2)])
end
local velocity = dir
--0.5 is the max force multiplier
local force = 0.5 - (0.5 * distance / (collision_boundary + object_collision_boundary))
local vel1 = vector.multiply(velocity, -1.5)
local vel2 = vector.multiply(velocity, 1.5)
vel1 = vector.multiply(vel1, force * 10)
vel2 = vector.multiply(vel2, force)
if object:is_player() then
vel2 = vector_multiply(vel2, 2.5)
--integrate mob punching into collision detection
if check_for_attack and self.punch_timer <= 0 then
if object == self.attacking then
mobs.punch_attack(self)
end
end
end
self.object:add_velocity(vel1)
object:add_velocity(vel2)
end
end
end
end
--this is used for arrow collisions
mobs.arrow_hit = function(self, player)
player:punch(self.object, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = self._damage}
}, nil)
--knockback
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = player:get_pos()
pos2.y = 0
local dir = vector_direction(pos1,pos2)
dir = vector_multiply(dir,3)
if player:get_velocity().y <= 1 then
dir.y = 5
end
player:add_velocity(dir)
end

@ -1,160 +0,0 @@
local minetest_add_item = minetest.add_item
local minetest_sound_play = minetest.sound_play
local math_pi = math.pi
local math_random = math.random
local math_floor = math.floor
local HALF_PI = math_pi / 2
local vector_new = vector.new
-- drop items
local item_drop = function(self, cooked, looting_level)
looting_level = looting_level or 0
-- no drops for child mobs (except monster)
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 {} -- nil check
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
-- cook items when true
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
-- add item if it exists
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
mobs.death_logic = function(self, dtime)
--stop crashing game when object is nil
if not self or not self.object or not self.object:get_luaentity() then
return
end
self.death_animation_timer = self.death_animation_timer + dtime
--get all attached entities and sort through them
local attached_entities = self.object:get_children()
if #attached_entities > 0 then
for _,entity in pairs(attached_entities) do
--kick the player off
if entity:is_player() then
mobs.detach(entity)
--kick mobs off
--if there is scaling issues, this needs an additional check
else
entity:set_detach()
end
end
end
--stop mob from getting in the way of other mobs you're fighting
if self.object:get_properties().pointable then
self.object:set_properties({pointable = false})
end
--the final POOF of a mob despawning
if self.death_animation_timer >= 1.25 then
item_drop(self,false,1)
mobs.death_effect(self)
mcl_experience.throw_experience(self.object:get_pos(), math_random(self.xp_min, self.xp_max))
self.object:remove()
return
end
--I'm sure there's a more efficient way to do this
--but this is the easiest, easier to work with 1 variable synced
--this is also not smooth
local death_animation_roll = self.death_animation_timer * 2 -- * 2 to make it faster
if death_animation_roll > 1 then
death_animation_roll = 1
end
local rot = self.object:get_rotation() --(no pun intended)
rot.z = death_animation_roll * HALF_PI
self.object:set_rotation(rot)
mobs.set_mob_animation(self,"stand", true)
--flying and swimming mobs just fall down
if self.fly or self.swim then
if self.object:get_acceleration().y ~= -self.gravity then
self.object:set_acceleration(vector_new(0,-self.gravity,0))
end
end
--when landing allow mob to slow down and just fall if in air
if self.pause_timer <= 0 then
mobs.set_velocity(self,0)
end
end

@ -1,260 +0,0 @@
local minetest_line_of_sight = minetest.line_of_sight
local minetest_dir_to_yaw = minetest.dir_to_yaw
local minetest_yaw_to_dir = minetest.yaw_to_dir
local minetest_get_node = minetest.get_node
local minetest_get_item_group = minetest.get_item_group
local minetest_get_objects_inside_radius = minetest.get_objects_inside_radius
local minetest_get_node_or_nil = minetest.get_node_or_nil
local minetest_registered_nodes = minetest.registered_nodes
local minetest_get_connected_players = minetest.get_connected_players
local vector_new = vector.new
local vector_add = vector.add
local vector_multiply = vector.multiply
local vector_distance = vector.distance
local table_copy = table.copy
local math_abs = math.abs
-- default function when mobs are blown up with TNT
local do_tnt = function(obj, damage)
obj.object:punch(obj.object, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = damage},
}, nil)
return false, true, {}
end
--a fast function to be able to detect only players without using objects_in_radius
mobs.detect_closest_player_within_radius = function(self, line_of_sight, radius, object_height_adder)
local pos1 = self.object:get_pos()
local players_in_area = {}
local winner_player = nil
local players_detected = 0
--get players in radius
for _,player in pairs(minetest.get_connected_players()) do
if player and player:get_hp() > 0 then
local pos2 = player:get_pos()
local distance = vector_distance(pos1,pos2)
if distance <= radius then
if line_of_sight then
--must add eye height or stuff breaks randomly because of
--seethrough nodes being a blocker (like grass)
if minetest_line_of_sight(
vector_new(pos1.x, pos1.y + object_height_adder, pos1.z),
vector_new(pos2.x, pos2.y + player:get_properties().eye_height, pos2.z)
) then
players_detected = players_detected + 1
players_in_area[player] = distance
end
else
players_detected = players_detected + 1
players_in_area[player] = distance
end
end
end
end
--return if there's no one near by
if players_detected <= 0 then --handle negative numbers for some crazy error that could possibly happen
return nil
end
--do a default radius max
local shortest_distance = radius + 1
--sort through players and find the closest player
for player,distance in pairs(players_in_area) do
if distance < shortest_distance then
shortest_distance = distance
winner_player = player
end
end
return(winner_player)
end
--check if a mob needs to jump
mobs.jump_check = function(self,dtime)
local pos = self.object:get_pos()
pos.y = pos.y + 0.1
local dir = minetest_yaw_to_dir(self.yaw)
local collisionbox = self.object:get_properties().collisionbox
local radius = collisionbox[4] + 0.5
vector_multiply(dir, radius)
--only jump if there's a node and a non-solid node above it
local test_dir = vector_add(pos,dir)
local green_flag_1 = minetest_get_item_group(minetest_get_node(test_dir).name, "solid") ~= 0
test_dir.y = test_dir.y + 1
local green_flag_2 = minetest_get_item_group(minetest_get_node(test_dir).name, "solid") == 0
if green_flag_1 and green_flag_2 then
--can jump over node
return(1)
elseif green_flag_1 and not green_flag_2 then
--wall in front of mob
return(2)
end
--nothing to jump over
return(0)
end
-- a helper function to quickly turn neutral passive mobs hostile
local turn_hostile = function(self,detected_mob)
--drop in variables for attacking (stops crash)
detected_mob.punch_timer = 0
--set to hostile
detected_mob.hostile = true
--hostile_cooldown timer is initialized here
detected_mob.hostile_cooldown_timer = detected_mob.hostile_cooldown
--set target to the same
detected_mob.attacking = self.attacking
end
--allow hostile mobs to signal to other mobs
--to switch from neutal passive to neutral hostile
mobs.group_attack_initialization = function(self)
--get basic data
local friends_list
if self.group_attack == true then
friends_list = {self.name}
else
friends_list = table_copy(self.group_attack)
end
local objects_in_area = minetest_get_objects_inside_radius(self.object:get_pos(), self.view_range)
--get the player's name
local name = self.attacking:get_player_name()
--re-use local variable
local detected_mob
--run through mobs in viewing distance
for _,object in pairs(objects_in_area) do
if object and object:get_luaentity() then
detected_mob = object:get_luaentity()
-- only alert members of same mob or friends
if detected_mob._cmi_is_mob and detected_mob.state ~= "attack" and detected_mob.owner ~= name then
if detected_mob.name == self.name then
turn_hostile(self,detected_mob)
else
for _,id in pairs(friends_list) do
if detected_mob.name == id then
turn_hostile(self,detected_mob)
break
end
end
end
end
--THIS NEEDS TO BE RE-IMPLEMENTED AS A GLOBAL HIT IN MOB_PUNCH!!
-- have owned mobs attack player threat
--if obj.owner == name and obj.owner_loyal then
-- do_attack(obj, self.object)
--end
end
end
end
-- check if within physical map limits (-30911 to 30927)
-- within_limits, wmin, wmax = nil, -30913, 30928
mobs.within_limits = function(pos, radius)
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
within_limits = function(pos, radius)
return pos
and (pos.x - radius) > wmin and (pos.x + radius) < wmax
and (pos.y - radius) > wmin and (pos.y + radius) < wmax
and (pos.z - radius) > wmin and (pos.z + radius) < wmax
end
end
end
return pos
and (pos.x - radius) > wmin and (pos.x + radius) < wmax
and (pos.y - radius) > wmin and (pos.y + radius) < wmax
and (pos.z - radius) > wmin and (pos.z + radius) < wmax
end
-- get node but use fallback for nil or unknown
mobs.node_ok = function(pos, fallback)
fallback = fallback or 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
--a teleport functoin
mobs.teleport = function(self, target)
if self.do_teleport then
if self.do_teleport(self, target) == false then
return
end
end
end
--a function used for despawning mobs
mobs.check_for_player_within_area = function(self, radius)
local pos1 = self.object:get_pos()
--get players in radius
for _,player in pairs(minetest_get_connected_players()) do
if player and player:get_hp() > 0 then
local pos2 = player:get_pos()
local distance = vector_distance(pos1,pos2)
if distance < radius then
--found a player
return(true)
end
end
end
--did not find a player
return(false)
end
--a simple helper function for mobs following
mobs.get_2d_distance = function(pos1,pos2)
pos1.y = 0
pos2.y = 0
return(vector_distance(pos1, pos2))
end
-- fall damage onto solid ground
mobs.calculate_fall_damage = function(self)
if self.old_velocity and self.old_velocity.y < -7 and self.object:get_velocity().y == 0 then
local vel = self.object:get_velocity()
if vel then
local damage = math_abs(self.old_velocity.y + 7) * 2
self.pause_timer = 0.4
self.health = self.health - damage
end
end
end

@ -1,291 +0,0 @@
local minetest_after = minetest.after
local minetest_sound_play = minetest.sound_play
local minetest_dir_to_yaw = minetest.dir_to_yaw
local math_floor = math.floor
local math_min = math.min
local math_random = math.random
local vector_direction = vector.direction
local vector_multiply = vector.multiply
local MAX_MOB_NAME_LENGTH = 30
mobs.feed_tame = function(self)
return nil
end
-- Code to execute before custom on_rightclick handling
local on_rightclick_prefix = function(self, clicker)
local item = clicker:get_wielded_item()
-- 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
mobs.update_tag(self)
if not mobs.is_creative(clicker:get_player_name()) then
item:take_item()
clicker:set_wielded_item(item)
end
return true
end
end
return false
end
-- I have no idea what this does
mobs.create_mob_on_rightclick = function(on_rightclick)
return function(self, clicker)
--don't allow rightclicking dead mobs
if self.health <= 0 then
return
end
local stop = on_rightclick_prefix(self, clicker)
if (not stop) and (on_rightclick) then
on_rightclick(self, clicker)
end
end
end
-- deal damage and effects when mob punched
mobs.mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
--don't do anything if the mob is already dead
if self.health <= 0 then
return
end
--neutral passive mobs switch to neutral hostile
if self.neutral then
--drop in variables for attacking (stops crash)
self.attacking = hitter
self.punch_timer = 0
self.hostile = true
--hostile_cooldown timer is initialized here
self.hostile_cooldown_timer = self.hostile_cooldown
--initialize the group attack (check for other mobs in area, make them neutral hostile)
if self.group_attack then
mobs.group_attack_initialization(self)
end
end
--turn skittish mobs away and RUN
if self.skittish then
self.state = "run"
self.run_timer = 5 --arbitrary 5 seconds
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = hitter:get_pos()
pos2.y = 0
local dir = vector_direction(pos2,pos1)
local yaw = minetest_dir_to_yaw(dir)
self.yaw = yaw
end
-- 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
--don't do damage until pause timer resets
if self.pause_timer > 0 then
return
end
-- error checking when mod profiling is enabled
if not tool_capabilities then
minetest.log("warning", "[mobs_mc] Mod profiling enabled, damage not enabled")
return
end
local is_player = hitter:is_player()
-- punch interval
local weapon = hitter:get_wielded_item()
local punch_interval = 1.4
-- exhaust attacker
if mod_hunger and 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
--calculate damage groups
for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do
damage = damage + (tool_capabilities.damage_groups[group] or 0) * ((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)
--minetest_is_creative_enabled("") ~= true --removed for now
if 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
--if player is falling multiply damage by 1.5
--critical hit
if hitter:get_velocity().y < 0 then
damage = damage * 1.5
mobs.critical_effect(self)
end
-- 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
minetest_sound_play("default_punch", {
object = self.object,
max_hear_distance = 16
}, true)
-- do damage
self.health = self.health - damage
--0.4 seconds until you can hurt the mob again
self.pause_timer = 0.4
--don't do knockback from a rider
for _,obj in pairs(self.object:get_children()) do
if obj == hitter then
return
end
end
-- knock back effect
local velocity = self.object:get_velocity()
--2d direction
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = hitter:get_pos()
pos2.y = 0
local dir = vector.direction(pos2,pos1)
local up = 3
-- if already in air then dont go up anymore when hit
if velocity.y ~= 0 then
up = 0
end
--0.75 for perfect distance to not be too easy, and not be too hard
local multiplier = 0.75
-- check if tool already has specific knockback value
local knockback_enchant = mcl_enchanting.get_enchantment(hitter:get_wielded_item(), "knockback")
if knockback_enchant and knockback_enchant > 0 then
multiplier = knockback_enchant + 1 --(starts from 1, 1 would be no change)
end
--do this to sure you can punch a mob back when
--it's coming for you
if self.hostile then
multiplier = multiplier + 2
end
dir = vector_multiply(dir,multiplier)
dir.y = up
--add the velocity
self.object:add_velocity(dir)
end
end
--do internal per mob projectile calculations
mobs.shoot_projectile = function(self)
local pos1 = self.object:get_pos()
--add mob eye height
pos1.y = pos1.y + self.eye_height
local pos2 = self.attacking:get_pos()
--add player eye height
pos2.y = pos2.y + self.attacking:get_properties().eye_height
--get direction
local dir = vector_direction(pos1,pos2)
--call internal shoot_arrow function
self.shoot_arrow(self,pos1,dir)
end
mobs.update_tag = function(self)
self.object:set_properties({
nametag = self.nametag,
})
end

@ -1,152 +0,0 @@
local minetest_add_particlespawner = minetest.add_particlespawner
mobs.death_effect = function(self)
local pos = self.object:get_pos()
local yaw = self.object:get_yaw()
local collisionbox = self.object:get_properties().collisionbox
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]}
end
minetest_add_particlespawner({
amount = 50,
time = 0.0001,
minpos = vector.add(pos, min),
maxpos = vector.add(pos, max),
minvel = vector.new(-0.5,0.5,-0.5),
maxvel = vector.new(0.5,1,0.5),
minexptime = 1.1,
maxexptime = 1.5,
minsize = 1,
maxsize = 2,
collisiondetection = false,
vertical = false,
texture = "mcl_particles_mob_death.png", -- this particle looks strange
})
end
mobs.critical_effect = function(self)
local pos = self.object:get_pos()
local yaw = self.object:get_yaw()
local collisionbox = self.object:get_properties().collisionbox
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]}
end
minetest_add_particlespawner({
amount = 10,
time = 0.0001,
minpos = vector.add(pos, min),
maxpos = vector.add(pos, max),
minvel = vector.new(-1,1,-1),
maxvel = vector.new(1,3,1),
minexptime = 0.7,
maxexptime = 1,
minsize = 1,
maxsize = 2,
collisiondetection = false,
vertical = false,
texture = "heart.png^[colorize:black:255",
})
end
--when feeding a mob
mobs.feed_effect = function(self)
local pos = self.object:get_pos()
local yaw = self.object:get_yaw()
local collisionbox = self.object:get_properties().collisionbox
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]}
end
minetest_add_particlespawner({
amount = 10,
time = 0.0001,
minpos = vector.add(pos, min),
maxpos = vector.add(pos, max),
minvel = vector.new(-1,1,-1),
maxvel = vector.new(1,3,1),
minexptime = 0.7,
maxexptime = 1,
minsize = 1,
maxsize = 2,
collisiondetection = false,
vertical = false,
texture = "heart.png^[colorize:gray:255",
})
end
--hearts when tamed
mobs.tamed_effect = function(self)
local pos = self.object:get_pos()
local yaw = self.object:get_yaw()
local collisionbox = self.object:get_properties().collisionbox
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]}
end
minetest_add_particlespawner({
amount = 30,
time = 0.0001,
minpos = vector.add(pos, min),
maxpos = vector.add(pos, max),
minvel = vector.new(-1,1,-1),
maxvel = vector.new(1,3,1),
minexptime = 0.7,
maxexptime = 1,
minsize = 1,
maxsize = 2,
collisiondetection = false,
vertical = false,
texture = "heart.png",
})
end
--hearts when breeding
mobs.breeding_effect = function(self)
local pos = self.object:get_pos()
local yaw = self.object:get_yaw()
local collisionbox = self.object:get_properties().collisionbox
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]}
end
minetest_add_particlespawner({
amount = 2,
time = 0.0001,
minpos = vector.add(pos, min),
maxpos = vector.add(pos, max),
minvel = vector.new(-1,1,-1),
maxvel = vector.new(1,3,1),
minexptime = 0.7,
maxexptime = 1,
minsize = 1,
maxsize = 2,
collisiondetection = false,
vertical = false,
texture = "heart.png",
})
end

@ -1,391 +0,0 @@
local math_pi = math.pi
local math_sin = math.sin
local math_cos = math.cos
local math_random = math.random
local HALF_PI = math_pi / 2
local DOUBLE_PI = math_pi * 2
-- localize vector functions
local vector_new = vector.new
local vector_length = vector.length
local vector_multiply = vector.multiply
local vector_distance = vector.distance
local vector_normalize = vector.normalize
local minetest_yaw_to_dir = minetest.yaw_to_dir
local minetest_dir_to_yaw = minetest.dir_to_yaw
local DEFAULT_JUMP_HEIGHT = 5
local DEFAULT_FLOAT_SPEED = 4
local DEFAULT_CLIMB_SPEED = 3
mobs.stick_in_cobweb = function(self)
local current_velocity = self.object:get_velocity()
local goal_velocity = vector_multiply(vector_normalize(current_velocity), 0.4)
goal_velocity.y = -0.5
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
--smooths out mobs a bit
if vector_length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
--this is a generic float function
mobs.float = function(self)
if self.object:get_acceleration().y ~= 0 then
self.object:set_acceleration(vector_new(0,0,0))
end
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = 0,
y = DEFAULT_FLOAT_SPEED,
z = 0,
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
new_velocity_addition.x = 0
new_velocity_addition.z = 0
--smooths out mobs a bit
if vector_length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
--this is a generic climb function
mobs.climb = function(self)
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = 0,
y = DEFAULT_CLIMB_SPEED,
z = 0,
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
new_velocity_addition.x = 0
new_velocity_addition.z = 0
--smooths out mobs a bit
if vector_length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
--[[
_ _
| | | |
| | __ _ _ __ __| |
| | / _` | '_ \ / _` |
| |___| (_| | | | | (_| |
\_____/\__,_|_| |_|\__,_|
]]
-- move mob in facing direction
--this has been modified to be internal
--internal = lua (self.yaw)
--engine = c++ (self.object:get_yaw())
mobs.set_velocity = function(self, v)
local yaw = (self.yaw or 0)
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = (math_sin(yaw) * -v),
y = 0,
z = (math_cos(yaw) * v),
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
if vector_length(new_velocity_addition) > vector_length(goal_velocity) then
vector.multiply(new_velocity_addition, (vector_length(goal_velocity) / vector_length(new_velocity_addition)))
end
new_velocity_addition.y = 0
--smooths out mobs a bit
if vector_length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
-- calculate mob velocity
mobs.get_velocity = function(self)
local v = self.object:get_velocity()
v.y = 0
if v then
return vector_length(v)
end
return 0
end
--make mobs jump
mobs.jump = function(self, velocity)
if self.object:get_velocity().y ~= 0 or not self.old_velocity or (self.old_velocity and self.old_velocity.y > 0) then
return
end
--fallback velocity to allow modularity
velocity = velocity or DEFAULT_JUMP_HEIGHT
self.object:add_velocity(vector_new(0,velocity,0))
end
--make mobs fall slowly
mobs.mob_fall_slow = function(self)
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = 0,
y = -2,
z = 0,
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
new_velocity_addition.x = 0
new_velocity_addition.z = 0
if vector_length(new_velocity_addition) > vector_length(goal_velocity) then
vector.multiply(new_velocity_addition, (vector_length(goal_velocity) / vector_length(new_velocity_addition)))
end
new_velocity_addition.x = 0
new_velocity_addition.z = 0
--smooths out mobs a bit
if vector_length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
--[[
_____ _
/ ___| (_)
\ `--.__ ___ _ __ ___
`--. \ \ /\ / / | '_ ` _ \
/\__/ /\ V V /| | | | | | |
\____/ \_/\_/ |_|_| |_| |_|
]]--
--make mobs flop
mobs.flop = function(self, velocity)
if self.object:get_velocity().y ~= 0 or not self.old_velocity or (self.old_velocity and self.old_velocity.y > 0) then
return false
end
mobs.set_velocity(self, 0)
--fallback velocity to allow modularity
velocity = velocity or DEFAULT_JUMP_HEIGHT
--create a random direction (2d yaw)
local dir = DOUBLE_PI * math_random()
--create a random force value
local force = math_random(0,3) + math_random()
--convert the yaw to a direction vector then multiply it times the force
local final_additional_force = vector_multiply(minetest_yaw_to_dir(dir), force)
--place in the "flop" velocity to make the mob flop
final_additional_force.y = velocity
self.object:add_velocity(final_additional_force)
return true
end
-- move mob in facing direction
--this has been modified to be internal
--internal = lua (self.yaw)
--engine = c++ (self.object:get_yaw())
mobs.set_swim_velocity = function(self, v)
local yaw = (self.yaw or 0)
local pitch = (self.pitch or 0)
if v == 0 then
pitch = 0
end
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = (math_sin(yaw) * -v),
y = pitch,
z = (math_cos(yaw) * v),
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
if vector_length(new_velocity_addition) > vector_length(goal_velocity) then
vector.multiply(new_velocity_addition, (vector_length(goal_velocity) / vector_length(new_velocity_addition)))
end
--smooths out mobs a bit
if vector_length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
--[[
______ _
| ___| |
| |_ | |_ _
| _| | | | | |
| | | | |_| |
\_| |_|\__, |
__/ |
|___/
]]--
-- move mob in facing direction
--this has been modified to be internal
--internal = lua (self.yaw)
--engine = c++ (self.object:get_yaw())
mobs.set_fly_velocity = function(self, v)
local yaw = (self.yaw or 0)
local pitch = (self.pitch or 0)
if v == 0 then
pitch = 0
end
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = (math_sin(yaw) * -v),
y = pitch,
z = (math_cos(yaw) * v),
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
if vector_length(new_velocity_addition) > vector_length(goal_velocity) then
vector.multiply(new_velocity_addition, (vector_length(goal_velocity) / vector_length(new_velocity_addition)))
end
--smooths out mobs a bit
if vector_length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
--a quick and simple pitch calculation between two vector positions
mobs.calculate_pitch = function(pos1, pos2)
if pos1 == nil or pos2 == nil then
return false
end
return(minetest_dir_to_yaw(vector_new(vector_distance(vector_new(pos1.x,0,pos1.z),vector_new(pos2.x,0,pos2.z)),0,pos1.y - pos2.y)) + HALF_PI)
end
--make mobs fly up or down based on their y difference
mobs.set_pitch_while_attacking = function(self)
local pos1 = self.object:get_pos()
local pos2 = self.attacking:get_pos()
local pitch = mobs.calculate_pitch(pos2,pos1)
self.pitch = pitch
end
--[[
___
|_ |
| |_ _ _ __ ___ _ __
| | | | | '_ ` _ \| '_ \
/\__/ / |_| | | | | | | |_) |
\____/ \__,_|_| |_| |_| .__/
| |
|_|
]]--
--special mob jump movement
mobs.jump_move = function(self, velocity)
if self.object:get_velocity().y ~= 0 or not self.old_velocity or (self.old_velocity and self.old_velocity.y > 0) then
return
end
--make the mob stick for a split second
mobs.set_velocity(self,0)
--fallback velocity to allow modularity
jump_height = DEFAULT_JUMP_HEIGHT
local yaw = (self.yaw or 0)
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = (math_sin(yaw) * -velocity),
y = jump_height,
z = (math_cos(yaw) * velocity),
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
if vector_length(new_velocity_addition) > vector_length(goal_velocity) then
vector.multiply(new_velocity_addition, (vector_length(goal_velocity) / vector_length(new_velocity_addition)))
end
--smooths out mobs a bit
if vector_length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
--make it so mobs do not glitch out and freak out
--when moving around over nodes
mobs.swap_auto_step_height_adjust = function(self)
local y_vel = self.object:get_velocity().y
if y_vel == 0 and self.stepheight ~= self.stepheight_backup then
self.stepheight = self.stepheight_backup
elseif y_vel ~= 0 and self.stepheight ~= 0 then
self.stepheight = 0
end
end

@ -1,44 +0,0 @@
local GRAVITY = minetest.settings:get("movement_gravity")-- + 9.81
mobs.shoot_projectile_handling = function(arrow_item, pos, dir, yaw, shooter, power, damage, is_critical, bow_stack, collectable, gravity)
local obj = minetest.add_entity({x=pos.x,y=pos.y,z=pos.z}, arrow_item.."_entity")
if power == nil then
power = 19
end
if damage == nil then
damage = 3
end
gravity = gravity or -GRAVITY
local knockback
if bow_stack then
local enchantments = mcl_enchanting.get_enchantments(bow_stack)
if enchantments.power then
damage = damage + (enchantments.power + 1) / 4
end
if enchantments.punch then
knockback = enchantments.punch * 3
end
if enchantments.flame then
mcl_burning.set_on_fire(obj, math.huge)
end
end
obj:set_velocity({x=dir.x*power, y=dir.y*power, z=dir.z*power})
obj:set_acceleration({x=0, y=gravity, z=0})
obj:set_yaw(yaw-math.pi/2)
local le = obj:get_luaentity()
le._shooter = shooter
le._damage = damage
le._is_critical = is_critical
le._startpos = pos
le._knockback = knockback
le._collectable = collectable
--play custom shoot sound
if shooter ~= nil and shooter.shoot_sound then
minetest.sound_play(shooter.shoot_sound, {pos=pos, max_hear_distance=16}, true)
end
return obj
end

@ -1,226 +0,0 @@
local math_random = math.random
local minetest_settings = minetest.settings
-- get entity staticdata
mobs.mob_staticdata = function(self)
--despawn mechanism
--don't despawned tamed or bred mobs
if not self.tamed and not self.bred then
if not mobs.check_for_player_within_area(self, 64) then
--print("removing SERIALIZED!")
self.object:remove()
return
end
end
self.remove_ok = true
self.attack = nil
self.following = nil
if use_cmi then
self.serialized_cmi_components = cmi.serialize_components(self._cmi_components)
end
local tmp = {}
for _,stat in pairs(self) do
local t = type(stat)
if t ~= "function"
and t ~= "nil"
and t ~= "userdata"
and _ ~= "_cmi_components" then
tmp[_] = self[_]
end
end
return minetest.serialize(tmp)
end
-- activate mob and reload settings
mobs.mob_activate = function(self, staticdata, def, dtime)
-- remove monsters in peaceful mode
if self.type == "monster" and minetest_settings:get_bool("only_peaceful_mobs", false) then
mcl_burning.extinguish(self.object)
self.object:remove()
return
end
-- load entity variables
local tmp = minetest.deserialize(staticdata)
if tmp then
for _,stat in pairs(tmp) do
self[_] = stat
end
end
--set up wandering
if not self.wandering then
self.wandering = true
end
--clear animation
self.current_animation = nil
-- select random texture, set model and size
if not self.base_texture then
-- compatiblity with old simple mobs textures
if type(def.textures[1]) == "string" then
def.textures = {def.textures}
end
self.base_texture = def.textures[math_random(1, #def.textures)]
self.base_mesh = def.mesh
self.base_size = self.visual_size
self.base_colbox = self.collisionbox
self.base_selbox = self.selectionbox
end
-- for current mobs that dont have this set
if not self.base_selbox then
self.base_selbox = self.selectionbox or self.base_colbox
end
-- set texture, model and size
local textures = self.base_texture
local mesh = self.base_mesh
local vis_size = self.base_size
local colbox = self.base_colbox
local selbox = self.base_selbox
-- specific texture if gotten
if self.gotten == true
and def.gotten_texture then
textures = def.gotten_texture
end
-- specific mesh if gotten
if self.gotten == true
and def.gotten_mesh then
mesh = def.gotten_mesh
end
-- set baby mobs to half size
if self.baby == true then
vis_size = {
x = self.base_size.x * self.baby_size,
y = self.base_size.y * self.baby_size,
}
if def.child_texture then
textures = def.child_texture[1]
end
colbox = {
self.base_colbox[1] * self.baby_size,
self.base_colbox[2] * self.baby_size,
self.base_colbox[3] * self.baby_size,
self.base_colbox[4] * self.baby_size,
self.base_colbox[5] * self.baby_size,
self.base_colbox[6] * self.baby_size
}
selbox = {
self.base_selbox[1] * self.baby_size,
self.base_selbox[2] * self.baby_size,
self.base_selbox[3] * self.baby_size,
self.base_selbox[4] * self.baby_size,
self.base_selbox[5] * self.baby_size,
self.base_selbox[6] * self.baby_size
}
end
--stop mobs from reviving
if not self.dead and not self.health then
self.health = math_random (self.hp_min, self.hp_max)
end
if not self.random_sound_timer then
self.random_sound_timer = math_random(self.random_sound_timer_min,self.random_sound_timer_max)
end
if self.breath == nil then
self.breath = self.breath_max
end
-- pathfinding init
self.path = {}
self.path.way = {} -- path to follow, table of positions
self.path.lastpos = {x = 0, y = 0, z = 0}
self.path.stuck = false
self.path.following = false -- currently following path?
self.path.stuck_timer = 0 -- if stuck for too long search for path
-- Armor groups
-- immortal=1 because we use custom health
-- handling (using "health" property)
local armor
if type(self.armor) == "table" then
armor = table.copy(self.armor)
armor.immortal = 1
else
armor = {immortal=1, fleshy = self.armor}
end
self.object:set_armor_groups(armor)
self.old_y = self.object:get_pos().y
self.old_health = self.health
self.sounds.distance = self.sounds.distance or 10
self.textures = textures
self.mesh = mesh
self.collisionbox = colbox
self.selectionbox = selbox
self.visual_size = vis_size
self.standing_in = "ignore"
self.standing_on = "ignore"
self.jump_sound_cooloff = 0 -- used to prevent jump sound from being played too often in short time
self.opinion_sound_cooloff = 0 -- used to prevent sound spam of particular sound types
self.texture_mods = {}
self.v_start = false
self.timer = 0
self.blinktimer = 0
self.blinkstatus = false
--continue mob effect on server restart
if self.dead or self.health <= 0 then
self.object:set_texture_mod("^[colorize:red:120")
else
self.object:set_texture_mod("")
end
-- set anything changed above
self.object:set_properties(self)
--update_tag(self)
--mobs.set_animation(self, "stand")
-- run on_spawn function if found
if self.on_spawn and not self.on_spawn_run then
if self.on_spawn(self) then
self.on_spawn_run = true -- if true, set flag to run once only
end
end
-- run after_activate
if def.after_activate then
def.after_activate(self, staticdata, def, dtime)
end
if use_cmi then
self._cmi_components = cmi.activate_components(self.serialized_cmi_components)
cmi.notify_activate(self.object, dtime)
end
end

@ -1,59 +0,0 @@
local math_random = math.random
--generic call for sound handler for mobs (data access)
mobs.play_sound = function(self,sound)
local soundinfo = self.sounds
if not soundinfo then
return
end
local play_sound = soundinfo[sound]
if not play_sound then
return
end
mobs.play_sound_handler(self, play_sound)
end
--generic sound handler for mobs
mobs.play_sound_handler = function(self, sound)
local pitch = (100 + math_random(-15,15) + math_random()) / 100
local distance = self.sounds.distance or 16
minetest.sound_play(sound, {
object = self.object,
gain = 1.0,
max_hear_distance = distance,
pitch = pitch,
}, true)
end
--random sound timing handler
mobs.random_sound_handling = function(self,dtime)
self.random_sound_timer = self.random_sound_timer - dtime
--play sound and reset timer
if self.random_sound_timer <= 0 then
mobs.play_sound(self,"random")
self.random_sound_timer = math_random(self.random_sound_timer_min,self.random_sound_timer_max)
end
end
--used for playing a non-mob internal sound at random pitches
mobs.play_sound_specific = function(self,soundname)
local pitch = (100 + math_random(-15,15) + math_random()) / 100
local distance = self.sounds.distance or 16
minetest.sound_play(soundname, {
object = self.object,
gain = 1.0,
max_hear_distance = distance,
pitch = pitch,
}, true)
end

@ -1,18 +1,7 @@
-- lib_mount by Blert2112 (edited by TenPlus1) --[[local node_ok = function(pos, fallback)
local enable_crash = false fallback = fallback or "mcl_core:dirt"
local crash_threshold = 6.5 -- ignored if enable_crash=false
------------------------------------------------------------------------------
--
-- Helper functions
--
local node_ok = function(pos, fallback)
fallback = fallback or mobs.fallback_node
local node = minetest.get_node_or_nil(pos) local node = minetest.get_node_or_nil(pos)
@ -25,8 +14,7 @@ end
local function node_is(pos) local function node_is(pos)
local node = minetest.get_node(pos)
local node = node_ok(pos)
if node.name == "air" then if node.name == "air" then
return "air" return "air"
@ -40,182 +28,62 @@ local function node_is(pos)
return "liquid" return "liquid"
end end
if minetest.registered_nodes[node.name].walkable == true then local def = minetest.registered_nodes[node.name]
if not def or def.walkable then
return "walkable" return "walkable"
end end
return "other" return "other"
end end
--]]
function mcl_mobs.mob:mount(obj)
if mcl_mount.mount(obj) then
if self.def.on_mount(obj) == false then
return
end
local function get_sign(i) self.driver = obj
obj:set_attach(self.object, "", self.def.driver_offset)
i = i or 0 if obj:is_player() then
obj:set_eye_offset(self.def.driver_eye_offset, vector.new(0, 0, 0))
if i == 0 then end
return 0
else
return i / math.abs(i)
end end
end end
--[[
local function get_velocity(v, yaw, y) function mobs:drive(moving_anim, stand_anim, can_fly, dtime)
local x = -math.sin(yaw) * v
local z = math.cos(yaw) * v
return {x = x, y = y, z = z}
end
local function get_v(v)
return math.sqrt(v.x * v.x + v.z * v.z)
end
local function force_detach(player)
local attached_to = player:get_attach()
if not attached_to then
return
end
local entity = attached_to:get_luaentity()
if entity.driver
and entity.driver == player then
entity.driver = nil
end
player:set_detach()
mcl_player.player_attached[player:get_player_name()] = false
player:set_eye_offset({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0})
mcl_player.player_set_animation(player, "stand" , 30)
player:set_properties({visual_size = {x = 1, y = 1} })
end
-------------------------------------------------------------------------------
minetest.register_on_leaveplayer(function(player)
force_detach(player)
end)
minetest.register_on_shutdown(function()
local players = minetest.get_connected_players()
for i = 1, #players do
force_detach(players[i])
end
end)
minetest.register_on_dieplayer(function(player)
force_detach(player)
return true
end)
-------------------------------------------------------------------------------
function mobs.attach(entity, player)
local attach_at, eye_offset
entity.player_rotation = entity.player_rotation or {x = 0, y = 0, z = 0}
entity.driver_attach_at = entity.driver_attach_at or {x = 0, y = 0, z = 0}
entity.driver_eye_offset = entity.driver_eye_offset or {x = 0, y = 0, z = 0}
entity.driver_scale = entity.driver_scale or {x = 1, y = 1}
local rot_view = 0 local rot_view = 0
if entity.player_rotation.y == 90 then if self.player_rotation.y == 90 then
rot_view = math.pi/2
end
attach_at = entity.driver_attach_at
eye_offset = entity.driver_eye_offset
entity.driver = player
force_detach(player)
player:set_attach(entity.object, "", attach_at, entity.player_rotation)
mcl_player.player_attached[player:get_player_name()] = true
player:set_eye_offset(eye_offset, {x = 0, y = 0, z = 0})
player:set_properties({
visual_size = {
x = entity.driver_scale.x,
y = entity.driver_scale.y
}
})
minetest.after(0.2, function(name)
local player = minetest.get_player_by_name(name)
if player then
mcl_player.player_set_animation(player, "sit_mount" , 30)
end
end, player:get_player_name())
player:set_look_horizontal(entity.object:get_yaw() - rot_view)
end
function mobs.detach(player, offset)
force_detach(player)
mcl_player.player_set_animation(player, "stand" , 30)
--local pos = player:get_pos()
--pos = {x = pos.x + offset.x, y = pos.y + 0.2 + offset.y, z = pos.z + offset.z}
player:add_velocity(vector.new(math.random(-6,6),math.random(5,8),math.random(-6,6))) --throw the rider off
--[[
minetest.after(0.1, function(name, pos)
local player = minetest.get_player_by_name(name)
if player then
player:set_pos(pos)
end
end, player:get_player_name(), pos)
]]--
end
function mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
local rot_view = 0
if entity.player_rotation.y == 90 then
rot_view = math.pi/2 rot_view = math.pi/2
end end
local acce_y = 0 local acce_y = 0
local velo = entity.object:get_velocity() local velo = self.object:get_velocity()
entity.v = get_v(velo) * get_sign(entity.v) self.v = get_v(velo) * get_sign(self.v)
-- process controls -- process controls
if entity.driver then if self.driver then
local ctrl = entity.driver:get_player_control() local ctrl = self.driver:get_player_control()
-- move forwards -- move forwards
if ctrl.up then if ctrl.up then
mobs.set_velocity(entity, entity.run_velocity) self:set_velocity(self.run_velocity)
mobs.set_mob_animation(entity, moving_anim) self:set_mob_animation( moving_anim)
-- move backwards -- move backwards
elseif ctrl.down then elseif ctrl.down then
mobs.set_velocity(entity, -entity.run_velocity) self:set_velocity(-self.run_velocity)
mobs.set_mob_animation(entity, moving_anim) self:set_mob_animation(moving_anim)
--halt --halt
else else
@ -253,7 +121,7 @@ function mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
end end
else else
]]-- ] ]--
-- jump -- jump
if ctrl.jump then if ctrl.jump then
@ -381,7 +249,7 @@ function mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
end end
entity.v2 = v entity.v2 = v
]]-- ] ]--
end end
@ -449,3 +317,5 @@ function mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_anim)
mobs:set_mob_animation(entity, moving_anim) mobs:set_mob_animation(entity, moving_anim)
end end
end end
]]--

@ -0,0 +1,57 @@
function mcl_mobs.mob:update_node_type()
self.last_node_type = self.node_type or "ignore"
self.node_type = mcl_mobs.util.get_node_type(self.object:get_pos())
if self.def.ignore_cobweb and self.node_type == "cobweb" then
self.node_type = "air"
end
end
function mcl_mobs.mob:backup_movement()
self.last_pos = self.object:get_pos()
self.last_velocity = self.object:get_velocity()
end
-- vertical_acceleration, vertical_speed, horizontal_speed_factor
function mcl_mobs.mob:get_movement()
if self.node_type == "solid" or self.node_type == "ignore" then
return 0, 0, 0
elseif self.node_type == "air" then
return self.def.gravity, nil, nil
elseif self.node_type == "water" then
return 0, mcl_mobs.const.water_sink_speed, mcl_mobs.const.water_slowdown_factor
elseif self.node_type == "lava" then
return 0, mcl_mobs.const.lava_sink_speed, mcl_mobs.const.lava_slowdown_factor
elseif self.node_type == "cobweb" then
return 0, mcl_mobs.const.cobweb_sink_speed, mcl_mobs.const.cobweb_slowdown_factor
end
end
function mcl_mobs.mob:update_movement()
local vertical_acceleration, vertical_speed, horizontal_speed_factor = self:get_movement()
if vertical_acceleration then
self.object:set_acceleration(vector.new(0, vertical_acceleration, 0))
end
local velocity = self.object:get_velocity()
if vertical_speed then
velocity.y = vertical_speed
end
if horizontal_speed_factor then
velocity.x = velocity.x * horizontal_speed_factor
velocity.z = velocity.z * horizontal_speed_factor
end
self.horizontal_speed_factor = horizontal_speed_factor
self.object:set_velocity(velocity)
end
function mcl_mobs.mob:movement_step()
if self.last_node_type ~= self.node_type then
self:update_movement()
end
end

@ -0,0 +1,8 @@
function mcl_mobs.mob:set_properties(props)
self.object:set_properties(props)
self:reload_properties()
end
function mcl_mobs.mob:reload_properties()
self.properties = self.object:get_properties()
end

@ -0,0 +1,84 @@
function mcl_mobs.register_mob(name, def)
mcl_mobs.registered_mobs[name] = def
def = table.copy(def)
def.health_min = mcl_mobs.util.scale_difficulty(def.health_min, 5, 1)
def.health_max = mcl_mobs.util.scale_difficulty(def.health_max, 10, 1)
def.breath_max = def.breath_max or mcl_mobs.const.breath_max
def.damage = mcl_mobs.util.scale_difficulty(def.damage, 0, 0)
def.fear_height = def.fear_height or 0
def.view_range = def.view_range or mcl_mobs.const.VIEW_RANGE
def.armor_groups = def.armor_groups or {fleshy = 100}
def.knockback_multiplier = def.knockback_multiplier or 1
def.sounds = def.sounds or {}
def.gravity = def.gravity or mcl_mobs.const.gravity
if def.group_attack then
if def.group_attack == true then
def.group_attack = {[name] = true}
else
def.group_attack = mcl_mobs.util.list_to_set(def.group_attack)
end
end
def.get_with = mcl_mobs.util.list_to_set(def.feed_with)
local feed_with = mcl_mobs.util.list_to_set(def.feed_with)
def.boost_with = def.boostable and (def.boost_with and mcl_mobs.util.list_to_set(def.boost_with) or feed_with)
def.heal_with = def.healable and (def.heal_with and mcl_mobs.util.list_to_set(def.heal_with) or feed_with)
def.breed_with = def.breedable and (def.breed_with and mcl_mobs.util.list_to_set(def.breed_with) or feed_with)
def.tame_with = def.tameable and (def.tame_with and mcl_mobs.util.list_to_set(def.tame_with) or feed_with)
if type(def.visual_size) == "number" then
def.visual_size = {x = def.visual_size, y = def.visual_size}
end
def.driver_offset = def.driver_offset or vector.new(0, 0, 0)
def.driver_eye_offset = def.driver_eye_offset or vector.new(0, 0, 0)
def.stepheight = def.stepheight or mcl_mobs.const.stepheight
def.float_in_air = def.float_in_air == true and mcl_mobs.const.float_in_air or def.float_in_air
def.float_in_water = def.float_in_water == true and mcl_mobs.const.float_in_water or def.float_in_water
def.float_in_lava = def.float_in_lava == true and mcl_mobs.const.float_in_lava or def.float_in_lava
mcl_mobs.mob_defititions[name] = def
local entity_def = {
initial_properties = {
physical = true,
collide_with_objects = false, -- custom magnetic object collision handling
collisionbox = def.collisionbox, -- no default collisionbox, there are some basic traits the mob def **needs** to have, there is no such thing as a generic mob
pointable = true,
visual = "mesh", -- all mobs have models
mesh = def.model,
visual_size = def.visual_size or {x = 1, y = 1},
textures = def.textures, -- maybe introduce a better concept to automatically handle textures (to avoid problems with armor and wielditems once they are added)
use_texture_alpha = def.use_texture_alpha, -- for e.g. slimes
is_visible = true, -- always visible
makes_footstep_sound = def.makes_footstep_sound, -- play the footstep sounds of the nodes the mob is walking over (maybe this should be enabled by default?)
automatic_rotate = 0,
stepheight = def.stepheight, -- is this needed? mobs have custom movement functions, don't they? Or is this useful for client side prediction?
automatic_face_movement_dir = def.rotate or 0,
automatic_face_movement_max_rotation_per_sec = 360,
glow = def.glow, -- Some mobs glow. Could also be used for spectral arrows in the future
static_save = true, -- persistent mobs
damage_texture_modifier = "^blank.png", -- damage is handled customly, don't show the damage overlay client-side
show_on_minimap = def.show_on_minimap, -- nice to have feature for bosses
},
}
setmetatable(entity_def, {__index = mcl_mobs.mob})
minetest.register_entity(name, entity_def)
if minetest.get_modpath("doc_identifier") ~= nil then
doc.sub.identifier.register_object(name, "basics", "mobs")
end
end

@ -0,0 +1,32 @@
function mcl_mobs.mob:play_sound(sound_name)
local sound = self.def.sounds[sound_name]
if sound then
self:play_sound_specific(sound)
end
end
function mcl_mobs.mob:play_sound_specific(self, sound)
if not self.silent then
minetest.sound_play(sound, {
object = self.object,
gain = 1.0,
max_hear_distance = 16,
pitch = (100 + math.random(-15, 15) + math.random()) / 100,
}, true)
end
end
function mcl_mobs.mob:start_sound_timer()
self.random_sound_timer = math.random(self.def.random_sound_timer_min, self.def.random_sound_timer_max)
end
function mcl_mobs.mob:sound_step()
if not self.random_sound_timer then
self:start_sound_timer()
end
if self:do_timer("random_sound") then
self:play_sound("random")
self:start_sound_timer()
end
end

@ -1,23 +1,4 @@
--lua locals --[[
local get_node = minetest.get_node
local get_item_group = minetest.get_item_group
local get_node_light = minetest.get_node_light
local find_nodes_in_area_under_air = minetest.find_nodes_in_area_under_air
local get_biome_name = minetest.get_biome_name
local get_objects_inside_radius = minetest.get_objects_inside_radius
local math_random = math.random
local math_floor = math.floor
local max = math.max
local vector_distance = vector.distance
local vector_new = vector.new
local vector_floor = vector.floor
local table_copy = table.copy
local table_remove = table.remove
-- range for mob count -- range for mob count
local aoc_range = 48 local aoc_range = 48
@ -162,7 +143,7 @@ Overworld regular:
"MesaBryce", "MesaBryce",
"JungleEdge", "JungleEdge",
"SavannaM", "SavannaM",
]]-- ] ]--
@ -170,7 +151,7 @@ Overworld regular:
local count_mobs = function(pos) local count_mobs = function(pos)
local num = 0 local num = 0
for _,object in pairs(get_objects_inside_radius(pos, aoc_range)) do for _,object in pairs(get_objects_inside_radius(pos, aoc_range)) do
if object and object:get_luaentity() and object:get_luaentity()._cmi_is_mob then if object and object:get_luaentity() and object:get_luaentity().is_mob then
num = num + 1 num = num + 1
end end
end end
@ -209,7 +190,7 @@ biomes: tells the spawner to allow certain mobs to spawn in certain biomes
what is aoc??? objects in area what is aoc??? objects in area
WARNING: BIOME INTEGRATION NEEDED -> How to get biome through lua?? WARNING: BIOME INTEGRATION NEEDED -> How to get biome through lua??
]]-- ] ]--
--this is where all of the spawning information is kept --this is where all of the spawning information is kept
@ -249,13 +230,13 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh
-- is mob actually registered? -- is mob actually registered?
if not mobs.spawning_mobs[name] if not mobs.spawning_mobs[name]
or not minetest.registered_entities[name] then or not minetest.registered_entities[name] then
minetest.log("warning", "Mob spawn of "..name.." failed, unknown entity or mob is not registered for spawning!") minetest.log("warning", "mcl_mobs.mob spawn of "..name.." failed, unknown entity or mob is not registered for spawning!")
return return
end end
-- additional custom checks for spawning mob -- additional custom checks for spawning mob
if mobs:spawn_abm_check(pos, node, name) == true then if mobs:spawn_abm_check(pos, node, name) == true then
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, ABM check rejected!") minetest.log("info", "mcl_mobs.mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, ABM check rejected!")
return return
end end
@ -275,7 +256,7 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh
or (not in_class_cap) -- spawn class mob cap or (not in_class_cap) -- spawn class mob cap
or count_mobs(pos, name) >= aoc then -- per-mob mob cap or count_mobs(pos, name) >= aoc then -- per-mob mob cap
-- too many entities -- too many entities
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, too crowded!") minetest.log("info", "mcl_mobs.mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, too crowded!")
return return
end end
@ -288,14 +269,14 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh
-- daylight, but mob wants night -- daylight, but mob wants night
if day_toggle == false then if day_toggle == false then
-- mob needs night -- mob needs night
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, mob needs light!") minetest.log("info", "mcl_mobs.mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, mob needs light!")
return return
end end
else else
-- night time but mob wants day -- night time but mob wants day
if day_toggle == true then if day_toggle == true then
-- mob needs day -- mob needs day
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, mob needs daylight!") minetest.log("info", "mcl_mobs.mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, mob needs daylight!")
return return
end end
end end
@ -311,7 +292,7 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh
if objs[n]:is_player() then if objs[n]:is_player() then
-- player too close -- player too close
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, player too close!") minetest.log("info", "mcl_mobs.mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, player too close!")
return return
end end
end end
@ -319,14 +300,14 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh
-- mobs cannot spawn in protected areas when enabled -- mobs cannot spawn in protected areas when enabled
if not spawn_protected if not spawn_protected
and minetest.is_protected(pos, "") then and minetest.is_protected(pos, "") then
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, position is protected!") minetest.log("info", "mcl_mobs.mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, position is protected!")
return return
end end
-- are we spawning within height limits? -- are we spawning within height limits?
if pos.y > max_height if pos.y > max_height
or pos.y < min_height then or pos.y < min_height then
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, out of height limit!") minetest.log("info", "mcl_mobs.mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, out of height limit!")
return return
end end
@ -335,7 +316,7 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh
if not light if not light
or light > max_light or light > max_light
or light < min_light then or light < min_light then
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, bad light!") minetest.log("info", "mcl_mobs.mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, bad light!")
return return
end end
@ -369,7 +350,7 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh
local pos2 = {x = pos.x+x, y = pos.y+y, z = pos.z+z} local pos2 = {x = pos.x+x, y = pos.y+y, z = pos.z+z}
if minetest.registered_nodes[node_ok(pos2).name].walkable == true then if minetest.registered_nodes[node_ok(pos2).name].walkable == true then
-- inside block -- inside block
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, too little space!") minetest.log("info", "mcl_mobs.mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, too little space!")
if ent.spawn_small_alternative ~= nil and (not minetest.registered_nodes[node_ok(pos).name].walkable) then if ent.spawn_small_alternative ~= nil and (not minetest.registered_nodes[node_ok(pos).name].walkable) then
minetest.log("info", "Trying to spawn smaller alternative mob: "..ent.spawn_small_alternative) minetest.log("info", "Trying to spawn smaller alternative mob: "..ent.spawn_small_alternative)
spawn_action(orig_pos, node, active_object_count, active_object_count_wider, ent.spawn_small_alternative) spawn_action(orig_pos, node, active_object_count, active_object_count_wider, ent.spawn_small_alternative)
@ -390,7 +371,7 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh
pos.y = pos.y - 0.5 pos.y = pos.y - 0.5
local mob = minetest.add_entity(pos, name) local mob = minetest.add_entity(pos, name)
minetest.log("action", "Mob spawned: "..name.." at "..minetest.pos_to_string(pos)) minetest.log("action", "mcl_mobs.mob spawned: "..name.." at "..minetest.pos_to_string(pos))
if on_spawn then if on_spawn then
@ -403,7 +384,7 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh
local function spawn_abm_action(pos, node, active_object_count, active_object_count_wider) local function spawn_abm_action(pos, node, active_object_count, active_object_count_wider)
spawn_action(pos, node, active_object_count, active_object_count_wider, name) spawn_action(pos, node, active_object_count, active_object_count_wider, name)
end end
]]-- ] ]--
local entdef = minetest.registered_entities[name] local entdef = minetest.registered_entities[name]
local spawn_class local spawn_class
@ -441,7 +422,7 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh
catch_up = false, catch_up = false,
action = spawn_abm_action, action = spawn_abm_action,
}) })
]]-- ] ]--
end end
-- compatibility with older mob registration -- compatibility with older mob registration
@ -452,7 +433,7 @@ function mobs:register_spawn(name, nodes, max_light, min_light, chance, active_o
mobs:spawn_specific(name, nodes, {"air"}, min_light, max_light, 30, mobs:spawn_specific(name, nodes, {"air"}, min_light, max_light, 30,
chance, active_object_count, -31000, max_height, day_toggle) chance, active_object_count, -31000, max_height, day_toggle)
end end
]]-- ] ]--
--Don't disable this yet-j4i --Don't disable this yet-j4i
@ -476,7 +457,7 @@ function mobs:spawn(def)
mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, interval, mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, interval,
chance, active_object_count, min_height, max_height, day_toggle, on_spawn) chance, active_object_count, min_height, max_height, day_toggle, on_spawn)
]]-- ] ]--
end end
@ -488,18 +469,18 @@ local outer = 64
local int = {-1,1} local int = {-1,1}
local position_calculation = function(pos) local position_calculation = function(pos)
pos = vector_floor(pos) pos = vector.floor(pos)
--this is used to determine the axis buffer from the player --this is used to determine the axis buffer from the player
axis = math_random(0,1) axis = math.random(0,1)
--cast towards the direction --cast towards the direction
if axis == 0 then --x if axis == 0 then --x
pos.x = pos.x + math_random(inner,outer)*int[math_random(1,2)] pos.x = pos.x + math.random(inner,outer)*int[math.random(1,2)]
pos.z = pos.z + math_random(-outer,outer) pos.z = pos.z + math.random(-outer,outer)
else --z else --z
pos.z = pos.z + math_random(inner,outer)*int[math_random(1,2)] pos.z = pos.z + math.random(inner,outer)*int[math.random(1,2)]
pos.x = pos.x + math_random(-outer,outer) pos.x = pos.x + math.random(-outer,outer)
end end
return(pos) return(pos)
end end
@ -510,12 +491,12 @@ local decypher_limits_dictionary = {
["nether"] = {mcl_vars.mg_nether_min, mcl_vars.mg_nether_max}, ["nether"] = {mcl_vars.mg_nether_min, mcl_vars.mg_nether_max},
["end"] = {mcl_vars.mg_end_min, mcl_vars.mg_end_max} ["end"] = {mcl_vars.mg_end_min, mcl_vars.mg_end_max}
} }
]]-- ] ]--
local function decypher_limits(posy) local function decypher_limits(posy)
--local min_max_table = decypher_limits_dictionary[dimension] --local min_max_table = decypher_limits_dictionary[dimension]
--return min_max_table[1],min_max_table[2] --return min_max_table[1],min_max_table[2]
posy = math_floor(posy) posy = math.floor(posy)
return posy - 32, posy + 32 return posy - 32, posy + 32
end end
@ -556,27 +537,27 @@ if mobs_spawn then
local min,max = decypher_limits(player_pos.y) local min,max = decypher_limits(player_pos.y)
for i = 1,math_random(1,4) do for i = 1,math.random(1,4) do
-- after this line each "break" means "continue" -- after this line each "break" means "continue"
local do_mob_algorithm = true local do_mob_algorithm = true
repeat repeat
local goal_pos = position_calculation(player_pos) local goal_pos = position_calculation(player_pos)
local spawning_position_list = find_nodes_in_area_under_air(vector_new(goal_pos.x,min,goal_pos.z), vector_new(goal_pos.x,max,goal_pos.z), {"group:solid", "group:water", "group:lava"}) local spawning_position_list = find_nodes_in_area_under_air(vector.new(goal_pos.x,min,goal_pos.z), vector.new(goal_pos.x,max,goal_pos.z), {"group:solid", "group:water", "group:lava"})
--couldn't find node --couldn't find node
if #spawning_position_list <= 0 then if #spawning_position_list <= 0 then
break break
end end
local spawning_position = spawning_position_list[math_random(1,#spawning_position_list)] local spawning_position = spawning_position_list[math.random(1,#spawning_position_list)]
--Prevent strange behavior --- this is commented out: /too close to player --fixed with inner circle --Prevent strange behavior --- this is commented out: /too close to player --fixed with inner circle
if not spawning_position then -- or vector_distance(player_pos, spawning_position) < 15 if not spawning_position then -- or vector.distance(player_pos, spawning_position) < 15
break break
end end
--hard code mob limit in area to 5 for now --hard code mob limit in area to 5 for now
if count_mobs(spawning_position) >= 5 then if count_mobs(spawning_position) >= 5 then
break break
@ -606,7 +587,7 @@ if mobs_spawn then
local is_lava = get_item_group(gotten_node, "lava") ~= 0 local is_lava = get_item_group(gotten_node, "lava") ~= 0
local mob_def = nil local mob_def = nil
--create a disconnected clone of the spawn dictionary --create a disconnected clone of the spawn dictionary
--prevents memory leak --prevents memory leak
local mob_library_worker_table = table_copy(spawn_dictionary) local mob_library_worker_table = table_copy(spawn_dictionary)
@ -625,7 +606,7 @@ if mobs_spawn then
local skip = false local skip = false
--use this for removing table elements of mobs that do not match --use this for removing table elements of mobs that do not match
local temp_index = math_random(1,#mob_library_worker_table) local temp_index = math.random(1,#mob_library_worker_table)
local temp_def = mob_library_worker_table[temp_index] local temp_def = mob_library_worker_table[temp_index]
@ -705,3 +686,25 @@ if mobs_spawn then
end end
end) end)
end end
local function is_forbidden_node(pos, node)
node = node or minetest.get_node(pos)
return minetest.get_item_group(node.name, "stair") > 0 or minetest.get_item_group(node.name, "slab") > 0 or minetest.get_item_group(node.name, "carpet") > 0
end
function mobs:spawn_abm_check(pos, node, name)
-- Don't spawn monsters on mycelium
if (node.name == "mcl_core:mycelium" or node.name == "mcl_core:mycelium_snow") and minetest.registered_entities[name].type == "monster" then
return true
--Don't Spawn mobs on stairs, slabs, or carpets
elseif is_forbidden_node(pos, node) or is_forbidden_node(vector.add(pos, vector.new(0, 1, 0))) then
return true
-- Spawn on opaque or liquid nodes
elseif minetest.get_item_group(node.name, "opaque") ~= 0 or minetest.registered_nodes[node.name].liquidtype ~= "none" or node.name == "mcl_core:grass_path" then
return false
end
-- Reject everything else
return true
end
]]--

@ -1,3 +1,6 @@
-- ToDo
--[[
local pr = PseudoRandom(os.time()*5) local pr = PseudoRandom(os.time()*5)
local offsets = {} local offsets = {}
@ -12,7 +15,9 @@ the owner is too far away. To be used with do_custom. Note: Optimized for mobs s
Larger mobs might have space problems after teleportation. Larger mobs might have space problems after teleportation.
* dist: Minimum required distance from owner to teleport. Default: 12 * dist: Minimum required distance from owner to teleport. Default: 12
* teleport_check_interval: Optional. Interval in seconds to check the mob teleportation. Default: 4 ]] * teleport_check_interval: Optional. Interval in seconds to check the mob teleportation. Default: 4 ]]--
--[[
mobs_mc.make_owner_teleport_function = function(dist, teleport_check_interval) mobs_mc.make_owner_teleport_function = function(dist, teleport_check_interval)
return function(self, dtime) return function(self, dtime)
-- No teleportation if no owner or if sitting -- No teleportation if no owner or if sitting
@ -61,3 +66,5 @@ mobs_mc.make_owner_teleport_function = function(dist, teleport_check_interval)
end end
end end
end end
--]]

@ -0,0 +1,114 @@
function mcl_mobs.util.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
function mcl_mobs.util.scale_size(tbl, size)
for k, v in pairs(tbl) do
tbl[k] = v * size
end
end
function mcl_mobs.util.rgb_to_hex(rgb)
local hexadecimal = "#"
for key, value in pairs(rgb) do
local hex = ""
while value > 0 do
local index = math.fmod(value, 16) + 1
value = math.floor(value / 16)
hex = string.sub("0123456789ABCDEF", index, index) .. hex
end
local len = string.len(hex)
if len == 0 then
hex = "00"
elseif len == 1 then
hex = "0" .. hex
end
hexadecimal = hexadecimal .. hex
end
return hexadecimal
end
function mcl_mobs.util.color_from_hue(hue)
local h = hue / 60
local c = 255
local x = (1 - math.abs(h % 2 - 1)) * 255
local i = math.floor(h)
if i == 0 then
return mcl_mobs.util.rgb_to_hex({c, x, 0})
elseif i == 1 then
return mcl_mobs.util.rgb_to_hex({x, c, 0})
elseif i == 2 then
return mcl_mobs.util.rgb_to_hex({0, c, x})
elseif i == 3 then
return mcl_mobs.util.rgb_to_hex({0, x, c})
elseif i == 4 then
return mcl_mobs.util.rgb_to_hex({x, 0, c})
else
return mcl_mobs.util.rgb_to_hex({c, 0, x})
end
end
function mcl_mobs.util.take_item(player, itemstack)
if not minetest.is_creative_enabled(player:get_player_name()) then
itemstack:take_item()
return true
end
end
function mcl_mobs.util.get_eye_height(obj)
if obj:is_player() then
return obj:get_properties().eye_height
else
return obj:get_luaentity().eye_height or 0
end
end
function mcl_mobs.util.list_to_set(list)
local set = {}
if list then
for k, v in pairs(list) do
set[v] = true
end
end
return set
end
function mcl_mobs.util.within_map_limits(pos, radius)
return pos
and (pos.x - radius) > mcl_vars.mapgen_edge_min and (pos.x + radius) < mcl_vars.mapgen_edge_max
and (pos.y - radius) > mcl_vars.mapgen_edge_min and (pos.y + radius) < mcl_vars.mapgen_edge_max
and (pos.z - radius) > mcl_vars.mapgen_edge_min and (pos.z + radius) < mcl_vars.mapgen_edge_max
end
function mcl_mobs.util.get_collision_data(obj)
local collisionbox = obj:get_properties().collisionbox
local pos = obj:get_pos()
pos.y = pos.y + collisionbox[2]
return collisionbox, pos, collisionbox[4]
end
function mcl_mobs.util.get_node_type(pos)
local node = minetest.get_node(pos).name
return nil
or node == "air" and "air"
or (minetest.registered_nodes[node] or {walkable = true}).walkable and "solid"
or node == "ignore" and "ignore"
or node == "mcl_core:cobweb" and "cobweb"
or minetest.get_item_group(node, "water") > 0 and "water"
or minetest.get_item_group(node, "lava") > 0 and "lava"
or "air"
end

@ -0,0 +1,51 @@
api/properties.lua
api/taming.lua
api/main.lua
api/despawn.lua
api/damage.lua
api/arrow.lua
api/mount.lua
api/graphics/eye_height.lua
api/graphics/nametag.lua
api/graphics/rotation.lua
api/graphics/animation.lua
api/graphics/head.lua
api/graphics/visual_size.lua
api/graphics/textures.lua
api/graphics/collisionbox.lua
api/graphics/mesh.lua
api/ai/follow.lua
api/ai/land.lua
api/ai/fly.lua
api/ai/swim.lua
api/ai/jump.lua
api/ai/ai.lua
api/ai/attack/projectile_land.lua
api/ai/attack/projectile_fly.lua
api/ai/attack/projectile_common.lua
api/ai/attack/explode.lua
api/ai/attack/punch.lua
api/ai/climb.lua
api/ai/float.lua
api/ai/step_height.lua
api/common.lua
api/loot.lua
api/spawn.lua
api/register.lua
api/breeding.lua
api/env/breath.lua
api/env/env.lua
api/env/fall_damage.lua
api/env/collision.lua
api/env/sunlight.lua
api/movement.lua
api/death.lua
api/eggs.lua
api/interaction.lua
api/baby.lua
api/knockback.lua
api/anger.lua
api/util.lua
api/sound.lua
api/easteregg.lua
api/data.lua

@ -1,16 +0,0 @@
local S = minetest.get_translator("mcl_mobs")
-- name tag
minetest.register_craftitem("mcl_mobs:nametag", {
description = S("Name Tag"),
_tt_help = S("Give names to mobs").."\n"..S("Set name at anvil"),
_doc_items_longdesc = S("A name tag is an item to name a mob."),
_doc_items_usagehelp = S("Before you use the name tag, you need to set a name at an anvil. Then you can use the name tag to name a mob. This uses up the name tag."),
inventory_image = "mobs_nametag.png",
wield_image = "mobs_nametag.png",
stack_max = 64,
groups = { tool=1 },
})
minetest.register_alias("mobs:nametag", "mcl_mobs:nametag")

@ -1,16 +1,55 @@
mcl_mobs = {
registered_mobs = {},
mob_defititions = {},
mob = {},
util = {},
eastereggs = {
rainbow = "kay27",
upside_down = "Fleckenstein",
spin = "Wuzzy",
},
const = {
-- print debug messages
debug = minetest.settings:get_bool("mcl_mobs_debug"),
local path = minetest.get_modpath(minetest.get_current_modname()) -- misc
breath_max = 6, -- default maximum breath
grow_up_boost = 0.1, -- how much grow up boost a baby mob will recieve when feed
local api_path = path.."/api" -- limits
max_entity_cramming = 24, -- max amount of crammed entities before they take damage
despawn_radius = 64, -- radius outside of which mobs may despawn
-- Mob API -- timers
dofile(api_path .. "/api.lua") life_timer = 30, -- how long it takes before the next despawn check is done
calm_down_timer = 6, -- how long it takes for mobs to calm down again after being angered when they don't see the attack target anymore
stun_timer = 0.4, -- how long it a mob will be stunned after taking damage
death_timer = 1.25, -- duration of the death animation
breed_giveup_timer = 15, -- how long a mob will search for a mate before giving up
run_timer = 5, -- how long a skittish mob runs after being damage: arbitrary 5 seconds
-- Spawning Algorithm -- movement
dofile(api_path .. "/spawning.lua") gravity = 9.81, -- gravity: this is actually incorrect but MCL2 uses this value elsewhere too
water_sink_speed = -0.5, -- how fast mobs sink in water ToDo: research
water_slowdown_factor = 0.5, -- how much horizontal movement is slown down in water ToDo: research
lava_sink_speed = -0.2, -- how fast mobs sink in lava ToDo: research
lava_slowdown_factor = 0.2, -- how much horizontal movement is slown down in lava ToDo: research
cobweb_sink_speed = -0.1, -- how fast mobs sink in cobwebs ToDo: research
cobweb_slowdown_factor = 0.1, -- how much horizontal movement is slown down in cobwebs ToDo: research
float_in_air = -0.5, -- default float speed for mobs that float in air ToDo: research
float_in_water = 0.5, -- default float speed for mobs that float in water ToDo: research
float_in_lava = 0.5, -- default float speed for mobs that float in lava ToDo: research
knockback = 0.75, -- base knockback multiplier
knockback_up = 3, -- base vertical knockback
stepheight = 0.6, -- default step height
},
}
-- Rideable Mobs local path = minetest.get_modpath("mcl_mobs")
dofile(api_path .. "/mount.lua") local api_files = io.open(path .. "/api_files.txt", "r") -- update with: $ find api -name "*.lua" > api_files.txt
-- Mob Items for file in api_files:lines() do
dofile(path .. "/crafts.lua") dofile(path .. "/" .. file)
end
api_files:close()

@ -1,11 +1,69 @@
# textdomain: mcl_mobs # textdomain: mcl_mobs
Peaceful mode active! No monsters will spawn.=Friedlicher Modus aktiv! Es werden keine Monster auftauchen.
This allows you to place a single mob.=Damit kann man einen Mob platzieren. This allows you to place a single mob.=Damit kann man einen Mob platzieren.
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.=Platzieren Sie dies einfach dort, wo der Mob auftauchen soll. Tiere werden zahm erscheinen, außer, wenn Sie beim Platzieren die Schlichtaste drücken. Platzieren Sie dies auf einem Mobspawner, um den Mob im Mobspawner zu wechseln. 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.=Platzieren Sie dies einfach dort, wo der Mob auftauchen soll. Tiere werden zahm erscheinen, außer, wenn Sie beim Platzieren die Schlichtaste drücken. Platzieren Sie dies auf einem Mobspawner, um den Mob im Mobspawner zu wechseln.
You need the “maphack” privilege to change the mob spawner.=Sie brauchen das „maphack“-Privileg, um den Mobspawner ändern zu können. You need the “maphack” privilege to change the mob spawner.=Sie brauchen das „maphack“-Privileg, um den Mobspawner ändern zu können.
Name Tag=Namensschild
A name tag is an item to name a mob.=Ein Namensschild ist ein Gegenstand, um einen Mob zu benennen.
Before you use the name tag, you need to set a name at an anvil. Then you can use the name tag to name a mob. This uses up the name tag.=Bevor Sie ein Namensschild benutzen können, müssen Sie ihn an einem Amboss benennen. Dann können können Sie das Namensschild benutztn, um einen Mob zu benennen. Das wird das Namensschild verbrauchen.
Only peaceful mobs allowed!=Nur friedliche Mobs erlaubt! Only peaceful mobs allowed!=Nur friedliche Mobs erlaubt!
Give names to mobs=Benennt Mobs Agent=Akteur
Set name at anvil=Namen am Amboss setzen Bat=Fledermaus
Blaze=Lohe
Chicken=Huhn
Cow=Kuh
Mooshroom=Pilzkuh
Creeper=Creeper
Ender Dragon=Enderdrache
Enderman=Enderman
Endermite=Endermilbe
Ghast=Ghast
Elder Guardian=Großer Wächter
Guardian=Wächter
Horse=Pferd
Skeleton Horse=Skelettpferd
Zombie Horse=Zombiepferd
Donkey=Esel
Mule=Maultier
Iron Golem=Eisengolem
Llama=Lama
Ocelot=Ozelot
Parrot=Papagei
Pig=Schwein
Polar Bear=Eisbär
Rabbit=Kaninchen
Killer Bunny=Killerkaninchen
The Killer Bunny=Das Killerkaninchen
Sheep=Schaf
Shulker=Shulker
Silverfish=Silberfischchen
Skeleton=Skelett
Stray=Eiswanderer
Wither Skeleton=Witherskelett
Magma Cube=Magmakubus
Slime=Schleim
Snow Golem=Schneegolem
Spider=Spinne
Cave Spider=Höhlenspinne
Squid=Tintenfisch
Vex=Plagegeist
Evoker=Magier
Illusioner=Illusionist
Villager=Dorfbewohner
Vindicator=Diener
Zombie Villager=Dorfbewohnerzombie
Witch=Hexe
Wither=Wither
Wolf=Wolf
Husk=Wüstenzombie
Zombie=Zombie
Zombie Pigman=Schweinezombie
Farmer=Bauer
Fisherman=Fischer
Fletcher=Pfeilmacher
Shepherd=Schäfer
Librarian=Bibliothekar
Cartographer=Kartograph
Armorer=Rüstungsschmied
Leatherworker=Lederarbeiter
Butcher=Metzger
Weapon Smith=Waffenschmied
Tool Smith=Werkzeugschmied
Cleric=Priester
Nitwit=Dorftrottel

@ -1,9 +1,69 @@
# textdomain: mcl_mobs # textdomain: mcl_mobs
Peaceful mode active! No monsters will spawn.=¡Modo pacífico activo! No aparecerán monstruos.
This allows you to place a single mob.=Esto le permite colocar un solo animal. This allows you to place a single mob.=Esto le permite colocar un solo animal.
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.=Simplemente colóquelo donde desea que aparezcan los animales. Los animales aparecerán domesticados, a menos que mantenga presionada la tecla de sigilo mientras coloca. Si coloca esto en un engendrador de animales, cambia el animal que genera. 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.=Simplemente colóquelo donde desea que aparezcan los animales. Los animales aparecerán domesticados, a menos que mantenga presionada la tecla de sigilo mientras coloca. Si coloca esto en un engendrador de animales, cambia el animal que genera.
You need the “maphack” privilege to change the mob spawner.=Necesita el privilegio "maphack" para cambiar el generador de animales. You need the “maphack” privilege to change the mob spawner.=Necesita el privilegio "maphack" para cambiar el generador de animales.
Name Tag=Etiqueta
A name tag is an item to name a mob.=Una etiqueta es un elemento para nombrar una animal.
Before you use the name tag, you need to set a name at an anvil. Then you can use the name tag to name a mob. This uses up the name tag.=Antes de usar la etiqueta, debe establecer un nombre en un yunque. Luego puede usar la etiqueta para nombrar un animal. Esto usa la etiqueta.
Only peaceful mobs allowed!=¡Solo se permiten animales pacíficos! Only peaceful mobs allowed!=¡Solo se permiten animales pacíficos!
Agent=Agente
Bat=Murciélago
Blaze=Blaze
Chicken=Pollo
Cow=Vaca
Mooshroom=Champiñaca
Creeper=Creeper
Ender Dragon=Enderdragón
Enderman=Enderman
Endermite=Endermite
Ghast=Ghast
Elder Guardian=Gran guardián
Guardian=Guardián
Horse=Caballo
Skeleton Horse=Caballo esquelético
Zombie Horse=Caballo zombie
Donkey=Burro
Mule=Mula
Iron Golem=Golem de hierro
Llama=Llama
Ocelot=Ocelote
Parrot=Loro
Pig=Cerdo
Polar Bear=Oso polar
Rabbit=Conejo
Killer Bunny=Conejo asesino
The Killer Bunny=El Conejo asesino
Sheep=Oveja
Shulker=Shulker
Silverfish=Lepisma
Skeleton=Esqueleto
Stray=Esqueleto
Wither Skeleton=Esqueleto wither
Magma Cube=Cubo de Magma
Slime=Slime
Snow Golem=Golem de nieve
Spider=Araña
Cave Spider=Araña de las cuevas
Squid=Calamar
Vex=Ánima
Evoker=Invocador
Illusioner=Illusionista
Villager=Aldeano
Vindicator=Vindicador
Zombie Villager=Aldeano zombie
Witch=Bruja
Wither=Wither
Wolf=Lobo
Husk=Husk
Zombie=Zombie
Zombie Pigman=Cerdo Zombie
Farmer=Granjero
Fisherman=Pescador
Fletcher=Flechador
Shepherd=Sacerdote
Librarian=Bibliotecario
Cartographer=Cartógrafo
Armorer=Armero
Leatherworker=Peletero
Butcher=Carnicero
Weapon Smith=Herrero de Armas
Tool Smith=Herrero de Herramientas
Cleric=Sacerdote
Nitwit=Simple

@ -1,11 +1,69 @@
# textdomain: mcl_mobs # textdomain: mcl_mobs
Peaceful mode active! No monsters will spawn.=Mode paisible actif! Aucun monstre n'apparaîtra.
This allows you to place a single mob.=Cela vous permet de placer un seul mob. This allows you to place a single mob.=Cela vous permet de placer un seul mob.
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.=Placez-le là où vous voulez que le mob apparaisse. Les animaux apparaîtront apprivoisés, sauf si vous maintenez la touche furtive enfoncée pendant le placement. Si vous le placez sur un générateur de mob, vous changez le mob qu'il génère. 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.=Placez-le là où vous voulez que le mob apparaisse. Les animaux apparaîtront apprivoisés, sauf si vous maintenez la touche furtive enfoncée pendant le placement. Si vous le placez sur un générateur de mob, vous changez le mob qu'il génère.
You need the “maphack” privilege to change the mob spawner.=Vous avez besoin du privilège "maphack" pour changer le générateur de mob. You need the “maphack” privilege to change the mob spawner.=Vous avez besoin du privilège "maphack" pour changer le générateur de mob.
Name Tag=Étiquette de nom
A name tag is an item to name a mob.=Une étiquette de nom est un élément pour nommer un mob.
Before you use the name tag, you need to set a name at an anvil. Then you can use the name tag to name a mob. This uses up the name tag.=Avant d'utiliser l'étiquette de nom, vous devez définir un nom sur une enclume. Ensuite, vous pouvez utiliser l'étiquette de nom pour nommer un mob. Cela utilise l'étiquette de nom.
Only peaceful mobs allowed!=Seuls les mobs pacifiques sont autorisées! Only peaceful mobs allowed!=Seuls les mobs pacifiques sont autorisées!
Give names to mobs=Donne des noms aux mobs Agent=Agent
Set name at anvil=Définir le nom sur l'enclume Bat=Chauve-souris
Blaze=Blaze
Chicken=Poulet
Cow=Vache
Mooshroom=Champimeuh
Creeper=Creeper
Ender Dragon=Ender Dragon
Enderman=Enderman
Endermite=Endermite
Ghast=Ghast
Elder Guardian=Gardien de l'Elder
Guardian=Gardien
Horse=Cheval
Skeleton Horse=Cheval-squelette
Zombie Horse=Cheval-zombie
Donkey=Âne
Mule=Mule
Iron Golem=Golem de fer
Llama=Lama
Ocelot=Ocelot
Parrot=Perroquet
Pig=Cochon
Polar Bear=Ours blanc
Rabbit=Lapin
Killer Bunny=Lapin tueur
The Killer Bunny=Le Lapin tueur
Sheep=Mouton
Shulker=Shulker
Silverfish=Poisson d'argent
Skeleton=Squelette
Stray=Vagabond
Wither Skeleton=Wither squelette
Magma Cube=Cube de magma
Slime=Slime
Snow Golem=Golem de neige
Spider=Araignée
Cave Spider=Araignée venimeuse
Squid=Poulpe
Vex=Vex
Evoker=Invocateur
Illusioner=Illusionniste
Villager=Villageois
Vindicator=Vindicateur
Zombie Villager=Zombie Villageois
Witch=Sorcière
Wither=Wither
Wolf=Loup
Husk=Zombie Momifié
Zombie=Zombie
Zombie Pigman=Zombie Cochon
Farmer=Fermier
Fisherman=Pêcheur
Fletcher=Archer
Shepherd=Berger
Librarian=Bibliothécaire
Cartographer=Cartographe
Armorer=Armurier
Leatherworker=Tanneur
Butcher=Boucher
Weapon Smith=Fabriquant d'arme
Tool Smith=Fabriquant d'outil
Cleric=Clerc
Nitwit=Crétin

@ -1,11 +1,69 @@
# textdomain: mcl_mobs # textdomain: mcl_mobs
Peaceful mode active! No monsters will spawn.=Мирный режим включён! Монстры не будут появляться.
This allows you to place a single mob.=Позволяет вам разместить одного моба. This allows you to place a single mob.=Позволяет вам разместить одного моба.
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.=Просто поместите это туда, где хотите, чтобы появился моб. Животные будут появляться уже прирученные, если это не нужно, удерживайте клавишу [Красться] при размещении. Если поместить это на спаунер, появляющийся из него моб будет изменён. 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.=Просто поместите это туда, где хотите, чтобы появился моб. Животные будут появляться уже прирученные, если это не нужно, удерживайте клавишу [Красться] при размещении. Если поместить это на спаунер, появляющийся из него моб будет изменён.
You need the “maphack” privilege to change the mob spawner.=Вам нужно обладать привилегией “maphack”, чтобы изменить спаунер моба. You need the “maphack” privilege to change the mob spawner.=Вам нужно обладать привилегией “maphack”, чтобы изменить спаунер моба.
Name Tag=Именная бирка
A name tag is an item to name a mob.=Именная бирка это предмет, чтобы дать мобу имя.
Before you use the name tag, you need to set a name at an anvil. Then you can use the name tag to name a mob. This uses up the name tag.=Прежде чем использовать именную бирку, нужно задать имя на наковальне. Тогда вы сможете использовать бирку, чтобы дать имя мобу.
Only peaceful mobs allowed!=Разрешены только мирные мобы! Only peaceful mobs allowed!=Разрешены только мирные мобы!
Give names to mobs=Даёт имена мобам Agent=Агент
Set name at anvil=Задайте имя при помощи наковальни Bat=Летучая мышь
Blaze=Ифрит
Chicken=Курица
Cow=Корова
Mooshroom=Гриб
Creeper=Крипер
Ender Dragon=Дракон Предела
Enderman=Эндермен
Endermite=Эндермит
Ghast=Гаст
Elder Guardian=Древний страж
Guardian=Страж
Horse=Лошадь
Skeleton Horse=Скелет лошади
Zombie Horse=Зомби-лошадь
Donkey=Ослик
Mule=Мул
Iron Golem=Железный голем
Llama=Лама
Ocelot=Оцелот
Parrot=Попугай
Pig=Свинья
Polar Bear=Полярный медведь
Rabbit=Кролик
Killer Bunny=Кролик-убийца
The Killer Bunny=Кролик-убийца
Sheep=Овца
Shulker=Шалкер
Silverfish=Чешуйница
Skeleton=Скелет
Stray=Странник
Wither Skeleton=Скелет-иссушитель
Magma Cube=Лавовый куб
Slime=Слизняк
Snow Golem=Снежный голем
Spider=Паук
Cave Spider=Пещерный паук
Squid=Кальмар
Vex=Досаждатель
Evoker=Маг
Illusioner=Иллюзор
Villager=Житель
Vindicator=Поборник
Zombie Villager=Зомби-житель
Witch=Ведьма
Wither=Иссушитель
Wolf=Волк
Husk=Кадавр
Zombie=Зомби
Zombie Pigman=Зомби-свиночеловек
Farmer=Фермер
Fisherman=Рыбак
Fletcher=Лучник
Shepherd=Пастух
Librarian=Библиотекарь
Cartographer=Картограф
Armorer=Бронник
Leatherworker=Кожевник
Butcher=Мясник
Weapon Smith=Оружейник
Tool Smith=Инструментальщик
Cleric=Церковник
Nitwit=Нищий

@ -1,11 +1,68 @@
# textdomain: mcl_mobs # textdomain: mcl_mobs
Peaceful mode active! No monsters will spawn.=
This allows you to place a single mob.= This allows you to place a single mob.=
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.= 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.=
You need the “maphack” privilege to change the mob spawner.= You need the “maphack” privilege to change the mob spawner.=
Name Tag=
A name tag is an item to name a mob.=
Before you use the name tag, you need to set a name at an anvil. Then you can use the name tag to name a mob. This uses up the name tag.=
Only peaceful mobs allowed!= Only peaceful mobs allowed!=
Give names to mobs= Agent=
Set name at anvil= Bat=
Blaze=
Chicken=
Cow=
Mooshroom=
Creeper=
Ender Dragon=
Enderman=
Endermite=
Ghast=
Elder Guardian=
Guardian=
Horse=
Skeleton Horse=
Zombie Horse=
Donkey=
Mule=
Iron Golem=
Llama=
Ocelot=
Parrot=
Pig=
Polar Bear=
Rabbit=
Killer Bunny=
Sheep=
Shulker=
Silverfish=
Skeleton=
Stray=
Wither Skeleton=
Magma Cube=
Slime=
Snow Golem=
Spider=
Cave Spider=
Squid=
Vex=
Evoker=
Illusioner=
Villager=
Vindicator=
Zombie Villager=
Witch=
Wither=
Wolf=
Husk=
Zombie=
Zombie Pigman=
Farmer=
Fisherman=
Fletcher=
Shepherd=
Librarian=
Cartographer=
Armorer=
Leatherworker=
Butcher=
Weapon Smith=
Tool Smith=
Cleric=
Nitwit=

@ -1,3 +1,194 @@
mobs_mc.create_monster_egg_nodes = true
-- Tables for attracting, feeding and breeding mobs
mobs_mc.follow = {
sheep = { mobs_mc.items.wheat },
cow = { mobs_mc.items.wheat },
chicken = { "farming:seed_wheat", "farming:seed_cotton" }, -- seeds in general
parrot = { "farming:seed_wheat", "farming:seed_cotton" }, -- seeds in general
horse = { mobs_mc.items.apple, mobs_mc.items.sugar, mobs_mc.items.wheat, mobs_mc.items.hay_bale, mobs_mc.items.golden_apple, mobs_mc.items.golden_carrot },
llama = { mobs_mc.items.wheat, mobs_mc.items.hay_bale, },
pig = { mobs_mc.items.potato, mobs_mc.items.carrot, mobs_mc.items.carrot_on_a_stick,
mobs_mc.items.apple, -- Minetest Game extra
},
rabbit = { mobs_mc.items.dandelion, mobs_mc.items.carrot, mobs_mc.items.golden_carrot, "farming_plus:carrot_item", },
ocelot = { mobs_mc.items.fish_raw, mobs_mc.items.salmon_raw, mobs_mc.items.clownfish_raw, mobs_mc.items.pufferfish_raw,
mobs_mc.items.chicken_raw, -- Minetest Game extra
},
wolf = { mobs_mc.items.bone },
dog = { mobs_mc.items.rabbit_raw, mobs_mc.items.rabbit_cooked, mobs_mc.items.mutton_raw, mobs_mc.items.mutton_cooked, mobs_mc.items.beef_raw, mobs_mc.items.beef_cooked, mobs_mc.items.chicken_raw, mobs_mc.items.chicken_cooked, mobs_mc.items.rotten_flesh,
-- Mobs Redo items
"mobs:meat", "mobs:meat_raw" },
}
-- Contents for replace_what
mobs_mc.replace = {
-- Rabbits reduce carrot growth stage by 1
rabbit = {
-- Farming Redo carrots
{"farming:carrot_8", "farming:carrot_7", 0},
{"farming:carrot_7", "farming:carrot_6", 0},
{"farming:carrot_6", "farming:carrot_5", 0},
{"farming:carrot_5", "farming:carrot_4", 0},
{"farming:carrot_4", "farming:carrot_3", 0},
{"farming:carrot_3", "farming:carrot_2", 0},
{"farming:carrot_2", "farming:carrot_1", 0},
{"farming:carrot_1", "air", 0},
-- Farming Plus carrots
{"farming_plus:carrot", "farming_plus:carrot_7", 0},
{"farming_plus:carrot_6", "farming_plus:carrot_5", 0},
{"farming_plus:carrot_5", "farming_plus:carrot_4", 0},
{"farming_plus:carrot_4", "farming_plus:carrot_3", 0},
{"farming_plus:carrot_3", "farming_plus:carrot_2", 0},
{"farming_plus:carrot_2", "farming_plus:carrot_1", 0},
{"farming_plus:carrot_1", "air", 0},
},
-- Sheep eat grass
sheep = {
-- Grass Block
{ "default:dirt_with_grass", "default:dirt", -1 },
-- “Tall Grass”
{ "default:grass_5", "air", 0 },
{ "default:grass_4", "air", 0 },
{ "default:grass_3", "air", 0 },
{ "default:grass_2", "air", 0 },
{ "default:grass_1", "air", 0 },
},
-- Silverfish populate stone, etc. with monster eggs
silverfish = {
{"default:stone", "mobs_mc:monster_egg_stone", -1},
{"default:cobble", "mobs_mc:monster_egg_cobble", -1},
{"default:mossycobble", "mobs_mc:monster_egg_mossycobble", -1},
{"default:stonebrick", "mobs_mc:monster_egg_stonebrick", -1},
{"default:stone_block", "mobs_mc:monster_egg_stone_block", -1},
},
}
-- List of nodes which endermen can take
mobs_mc.enderman_takable = {
-- Generic handling, useful for entensions
"group:enderman_takable",
-- Generic nodes
"group:sand",
"group:flower",
-- Minetest Game
"default:dirt",
"default:dirt_with_grass",
"default:dirt_with_dry_grass",
"default:dirt_with_snow",
"default:dirt_with_rainforest_litter",
"default:dirt_with_grass_footsteps",
-- FIXME: For some reason, Minetest has a Lua error when an enderman tries to place a Minetest Game cactus.
-- Maybe this is because default:cactus has rotate_and_place?
-- "default:cactus", -- TODO: Re-enable cactus when it works again
"default:gravel",
"default:clay",
"flowers:mushroom_red",
"flowers:mushroom_brown",
"tnt:tnt",
-- Nether mod
"nether:rack",
}
--[[ Table of nodes to replace when an enderman takes it.
If the enderman takes an indexed node, it the enderman will get the item in the value.
Table indexes: Original node, taken by enderman.
Table values: The item which the enderman *actually* gets
Example:
mobs_mc.enderman_node_replace = {
["default:dirt_with_dry_grass"] = "default_dirt_with_grass",
}
-- This means, if the enderman takes a dirt with dry grass, he will get a dirt with grass
-- on his hand instead.
]]
mobs_mc.enderman_replace_on_take = {} -- no replacements by default
-- A table which can be used to override block textures of blocks carried by endermen.
-- Only works for cube-shaped nodes and nodeboxes.
-- Key: itemstrings of the blocks to replace
-- Value: A table with the texture overrides (6 textures)
mobs_mc.enderman_block_texture_overrides = {
}
-- List of nodes on which mobs can spawn
mobs_mc.spawn = {
solid = { "group:cracky", "group:crumbly", "group:shovely", "group:pickaxey" }, -- spawn on "solid" nodes (this is mostly just guessing)
grassland = { mobs_mc.items.grass_block, "ethereal:prairie_dirt" },
savanna = { "default:dirt_with_dry_grass" },
grassland_savanna = { mobs_mc.items.grass_block, "default:dirt_with_dry_grass" },
desert = { "default:desert_sand", "group:sand" },
jungle = { "default:dirt_with_rainforest_litter", "default:jungleleaves", "default:junglewood", "mcl_core:jungleleaves", "mcl_core:junglewood" },
snow = { "default:snow", "default:snowblock", "default:dirt_with_snow" },
end_city = { "default:sandstonebrick", "mcl_end:purpur_block", "mcl_end:end_stone" },
wolf = { mobs_mc.items.grass_block, "default:dirt_with_rainforest_litter", "default:dirt", "default:dirt_with_snow", "default:snow", "default:snowblock" },
village = { "mg_villages:road" },
-- These probably don't need overrides
mushroom_island = { mobs_mc.items.mycelium, "mcl_core:mycelium" },
nether_fortress = { mobs_mc.items.nether_brick_block, "mcl_nether:nether_brick", },
nether = { mobs_mc.items.netherrack, "mcl_nether:netherrack", },
nether_portal = { mobs_mc.items.nether_portal, "mcl_portals:portal" },
water = { mobs_mc.items.water_source, "mcl_core:water_source", "default:water_source" },
}
-- This table contains important spawn height references for the mob spawn height.
-- Please base your mob spawn height on these numbers to keep things clean.
mobs_mc.spawn_height = {
water = tonumber(minetest.settings:get("water_level")) or 0, -- Water level in the Overworld
-- Overworld boundaries (inclusive) --I adjusted this to be more reasonable
overworld_min = -64,-- -2999,
overworld_max = 31000,
-- Nether boundaries (inclusive)
nether_min = -29067,-- -3369,
nether_max = -28939,-- -3000,
-- End boundaries (inclusive)
end_min = -6200,
end_max = -6000,
}
mobs_mc.misc = {
shears_wear = 276, -- Wear to add per shears usage (238 uses)
totem_fail_nodes = {} -- List of nodes in which the totem of undying fails
}
-- Item name overrides from mobs_mc_gameconfig (if present)
if minetest.get_modpath("mobs_mc_gameconfig") and mobs_mc.override then
local tables = {"items", "follow", "replace", "spawn", "spawn_height", "misc"}
for t=1, #tables do
local tbl = tables[t]
if mobs_mc.override[tbl] then
for k, v in pairs(mobs_mc.override[tbl]) do
mobs_mc[tbl][k] = v
end
end
end
if mobs_mc.override.enderman_takable then
mobs_mc.enderman_takable = mobs_mc.override.enderman_takable
end
if mobs_mc.override.enderman_replace_on_take then
mobs_mc.enderman_replace_on_take = mobs_mc.override.enderman_replace_on_take
end
if mobs_mc.enderman_block_texture_overrides then
mobs_mc.enderman_block_texture_overrides = mobs_mc.override.enderman_block_texture_overrides
end
end
---
---
--- actual gameconfig now
---
---
mobs_mc = {} mobs_mc = {}
mobs_mc.override = {} mobs_mc.override = {}
@ -179,9 +370,6 @@ mobs_mc.override.enderman_takable = {
} }
mobs_mc.override.enderman_replace_on_take = { mobs_mc.override.enderman_replace_on_take = {
} }
mobs_mc.override.misc = {
totem_fail_nodes = { "mcl_core:void", "mcl_core:realm_barrier" },
}
-- Texuture overrides for enderman block. Required for cactus because it's original is a nodebox -- Texuture overrides for enderman block. Required for cactus because it's original is a nodebox
-- and the textures have tranparent pixels. -- and the textures have tranparent pixels.
@ -200,7 +388,7 @@ end
mobs_mc.override.enderman_block_texture_overrides = { mobs_mc.override.enderman_block_texture_overrides = {
["mcl_core:cactus"] = ctable, ["mcl_core:cactus"] = ctable,
-- FIXME: replace colorize colors with colors from palette -- FIXME: replace colorize colors with colors from palette
["mcl_core:dirt_with_grass"] = ["mcl_core:dirt_with_grass"] =
{ {
"mcl_core_grass_block_top.png^[colorize:green:90", "mcl_core_grass_block_top.png^[colorize:green:90",
"default_dirt.png", "default_dirt.png",

@ -195,7 +195,7 @@ local how_to_throw = "Hold it in your and and leftclick to throw."
-- egg throwing item -- egg throwing item
-- egg entity -- egg entity
if c("egg") then if c("egg") then
local egg_GRAVITY = 9 local egg_gravity = 9
local egg_VELOCITY = 19 local egg_VELOCITY = 19
mobs:register_arrow("mobs_mc:egg_entity", { mobs:register_arrow("mobs_mc:egg_entity", {
@ -289,7 +289,7 @@ if c("egg") then
obj:set_acceleration({ obj:set_acceleration({
x = dir.x * -3, x = dir.x * -3,
y = -egg_GRAVITY, y = -egg_gravity,
z = dir.z * -3 z = dir.z * -3
}) })
@ -315,7 +315,7 @@ end
-- Snowball -- Snowball
local snowball_GRAVITY = 9 local snowball_gravity = 9
local snowball_VELOCITY = 19 local snowball_VELOCITY = 19
mobs:register_arrow("mobs_mc:snowball_entity", { mobs:register_arrow("mobs_mc:snowball_entity", {
@ -373,7 +373,7 @@ if c("snowball") then
obj:set_acceleration({ obj:set_acceleration({
x = dir.x * -3, x = dir.x * -3,
y = -snowball_GRAVITY, y = -snowball_gravity,
z = dir.z * -3 z = dir.z * -3
}) })

@ -1,8 +1,7 @@
--License for code WTFPL and otherwise stated in readmes --License for code WTFPL and otherwise stated in readmes
local S = minetest.get_translator("mcl_mobs")
local S = minetest.get_translator("mobs_mc") mcl_mobs.register_mob("mcl_mobs:bat", {
mobs:register_mob("mobs_mc:bat", {
description = S("Bat"), description = S("Bat"),
type = "animal", type = "animal",
spawn_class = "ambient", spawn_class = "ambient",
@ -14,16 +13,14 @@ mobs:register_mob("mobs_mc:bat", {
hp_min = 6, hp_min = 6,
hp_max = 6, hp_max = 6,
collisionbox = {-0.25, -0.01, -0.25, 0.25, 0.89, 0.25}, collisionbox = {-0.25, -0.01, -0.25, 0.25, 0.89, 0.25},
visual = "mesh", model = "mcl_mobs_bat.b3d",
mesh = "mobs_mc_bat.b3d",
textures = { textures = {
{"mobs_mc_bat.png"}, {"mcl_mobs_bat.png"},
}, },
visual_size = {x=1, y=1},
sounds = { sounds = {
random = "mobs_mc_bat_idle", random = "mcl_mobs_bat_idle",
damage = "mobs_mc_bat_hurt", damage = "mcl_mobs_bat_hurt",
death = "mobs_mc_bat_death", death = "mcl_mobs_bat_death",
distance = 16, distance = 16,
}, },
walk_velocity = 4.5, walk_velocity = 4.5,
@ -47,7 +44,6 @@ mobs:register_mob("mobs_mc:bat", {
walk_chance = 100, walk_chance = 100,
fall_damage = 0, fall_damage = 0,
view_range = 16, view_range = 16,
fear_height = 0,
jump = false, jump = false,
makes_footstep_sound = false, makes_footstep_sound = false,
}) })
@ -66,8 +62,8 @@ else
end end
-- Spawn on solid blocks at or below Sea level and the selected light level -- Spawn on solid blocks at or below Sea level and the selected light level
mobs:spawn_specific( mcl_mobs.spawn_specific(
"mobs_mc:bat", "mcl_mobs:bat",
"overworld", "overworld",
"ground", "ground",
{ {
@ -139,9 +135,8 @@ maxlight,
20, 20,
5000, 5000,
2, 2,
mobs_mc.spawn_height.overworld_min, mcl_mobs.spawn_height.overworld_min,
mobs_mc.spawn_height.water-1) mcl_mobs.spawn_height.water-1)
-- spawn eggs -- spawn eggs
mobs:register_egg("mobs_mc:bat", S("Bat"), "mobs_mc_spawn_icon_bat.png", 0) mcl_mobs.register_egg("mcl_mobs:bat", "mcl_mobs_spawn_icon_bat.png", 0)

@ -1,16 +1,21 @@
-- daufinsyd -- daufinsyd
-- My work is under the LGPL terms -- My work is under the LGPL terms
-- Model and mobs_blaze.png see https://github.com/22i/minecraft-voxel-blender-models -hi 22i ~jordan4ibanez -- Model and mcl_mobs_blaze.png see https://github.com/22i/minecraft-voxel-blender-models -hi 22i ~jordan4ibanez
-- blaze.lua partial copy of mobs_mc/ghast.lua -- blaze.lua partial copy of ghast.lua
local S = minetest.get_translator("mcl_mobs")
local S = minetest.get_translator("mobs_mc") local smokedef = mcl_particles.get_smoke_def({
amount = 0.009,
maxexptime = 4.0,
minvel = {x = -0.1, y = 0.3, z = -0.1},
maxvel = {x = 0.1, y = 1.6, z = 0.1},
minsize = 4.0,
maxsize = 4.5,
minrelpos = {x = -0.7, y = -0.45, z = -0.7},
maxrelpos = {x = 0.7, y = 0.45, z = 0.7},
})
--################### mcl_mobs.register_mob("mcl_mobs:blaze", {
--################### BLAZE
--###################
mobs:register_mob("mobs_mc:blaze", {
description = S("Blaze"), description = S("Blaze"),
type = "monster", type = "monster",
spawn_class = "hostile", spawn_class = "hostile",
@ -23,17 +28,16 @@ mobs:register_mob("mobs_mc:blaze", {
rotate = 270, rotate = 270,
collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.79, 0.3}, collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.79, 0.3},
rotate = -180, rotate = -180,
visual = "mesh", model = "mcl_mobs_blaze.b3d",
mesh = "mobs_mc_blaze.b3d",
textures = { textures = {
{"mobs_mc_blaze.png"}, {"mcl_mobs_blaze.png"},
}, },
armor = { fleshy = 100, snowball_vulnerable = 100, water_vulnerable = 100 }, armor = {fleshy = 100, snowball_vulnerable = 100, water_vulnerable = 100},
visual_size = {x=3, y=3}, visual_size = {x = 3, y = 3},
sounds = { sounds = {
random = "mobs_mc_blaze_breath", random = "mcl_mobs_blaze_breath",
death = "mobs_mc_blaze_died", death = "mcl_mobs_blaze_died",
damage = "mobs_mc_blaze_hurt", damage = "mcl_mobs_blaze_hurt",
distance = 16, distance = 16,
}, },
walk_velocity = .8, walk_velocity = .8,
@ -42,17 +46,19 @@ mobs:register_mob("mobs_mc:blaze", {
reach = 4, -- don't want blaze getting too close reach = 4, -- don't want blaze getting too close
pathfinding = 1, pathfinding = 1,
drops = { drops = {
{name = mobs_mc.items.blaze_rod, {
chance = 1, name = "mcl_mobitems:blaze_rod",
min = 0, chance = 1,
max = 1, min = 0,
looting = "common",}, max = 1,
looting = "common",
},
}, },
animation = { animation = {
stand_speed = 25, stand_speed = 25,
stand_start = 0, stand_start = 0,
stand_end = 100, stand_end = 100,
walk_speed = 25, walk_speed = 25,
walk_start = 0, walk_start = 0,
walk_end = 100, walk_end = 100,
run_speed = 50, run_speed = 50,
@ -62,11 +68,9 @@ mobs:register_mob("mobs_mc:blaze", {
-- MC Wiki: takes 1 damage every half second while in water -- MC Wiki: takes 1 damage every half second while in water
water_damage = 2, water_damage = 2,
lava_damage = 0, lava_damage = 0,
fire_damage = 0,
fall_damage = 0, fall_damage = 0,
fall_speed = -2.25, fall_speed = -2.25,
light_damage = 0, view_range = 48,
view_range = 16,
attack_type = "projectile", attack_type = "projectile",
arrow = "mobs_mc:blaze_fireball", arrow = "mobs_mc:blaze_fireball",
shoot_interval = 3.5, shoot_interval = 3.5,
@ -76,17 +80,12 @@ mobs:register_mob("mobs_mc:blaze", {
jump_height = 4, jump_height = 4,
fly = true, fly = true,
makes_footstep_sound = false, makes_footstep_sound = false,
fear_height = 0,
glow = 14, glow = 14,
fire_resistant = true, fire_resistant = true,
eye_height = 0.75, eye_height = 0.75,
projectile_cooldown_min = 2, projectile_cooldown_min = 2,
projectile_cooldown_max = 3, projectile_cooldown_max = 3,
shoot_arrow = function(self, pos, dir) arrow = "mcl_mobs:blaze_fireball",
-- 2-4 damage per arrow
local dmg = math.random(2,4)
mobs.shoot_projectile_handling("mobs_mc:blaze_fireball", pos, dir, self.object:get_yaw(), self.object, 7, dmg,nil,nil,nil,-0.4)
end,
do_custom = function(self) do_custom = function(self)
if self.attacking and self.state == "attack" and vector.distance(self.object:get_pos(), self.attacking:get_pos()) < 1.2 then if self.attacking and self.state == "attack" and vector.distance(self.object:get_pos(), self.attacking:get_pos()) < 1.2 then
@ -155,6 +154,13 @@ mobs_mc.spawn_height.nether_min,
mobs_mc.spawn_height.nether_max) mobs_mc.spawn_height.nether_max)
-- Blaze fireball -- Blaze fireball
mcl_mobs.register_arrow("mobs_mc:blaze_fireball", {
type = "fireball",
deflectable = false,
explosion = false,
damage = 5,
})
mobs:register_arrow("mobs_mc:blaze_fireball", { mobs:register_arrow("mobs_mc:blaze_fireball", {
visual = "sprite", visual = "sprite",
visual_size = {x = 0.3, y = 0.3}, visual_size = {x = 0.3, y = 0.3},
@ -165,11 +171,9 @@ mobs:register_arrow("mobs_mc:blaze_fireball", {
tail_texture = "mobs_mc_spit.png^[colorize:black:255", --repurpose spit texture tail_texture = "mobs_mc_spit.png^[colorize:black:255", --repurpose spit texture
tail_size = 2, tail_size = 2,
tail_distance_divider = 3, tail_distance_divider = 3,
_is_fireball = true, is_fireball = true,
-- Direct hit, no fire... just plenty of pain
hit_player = function(self, player) hit_player = function(self, player)
mcl_burning.set_on_fire(player, 5)
player:punch(self.object, 1.0, { player:punch(self.object, 1.0, {
full_punch_interval = 1.0, full_punch_interval = 1.0,
damage_groups = {fleshy = self._damage}, damage_groups = {fleshy = self._damage},
@ -177,7 +181,6 @@ mobs:register_arrow("mobs_mc:blaze_fireball", {
end, end,
hit_mob = function(self, mob) hit_mob = function(self, mob)
mcl_burning.set_on_fire(mob, 5)
mob:punch(self.object, 1.0, { mob:punch(self.object, 1.0, {
full_punch_interval = 1.0, full_punch_interval = 1.0,
damage_groups = {fleshy = self._damage}, damage_groups = {fleshy = self._damage},
@ -214,4 +217,4 @@ mobs:register_arrow("mobs_mc:blaze_fireball", {
}) })
-- spawn eggs -- spawn eggs
mobs:register_egg("mobs_mc:blaze", S("Blaze"), "mobs_mc_spawn_icon_blaze.png", 0) mcl_mobs.register_egg("mcl_mobs:blaze", "mobs_mc_spawn_icon_blaze.png", 0)

@ -6,10 +6,7 @@ local S = minetest.get_translator("mobs_mc")
--################### CREEPER --################### CREEPER
--################### --###################
local creeper = {
mobs:register_mob("mobs_mc:creeper", {
type = "monster", type = "monster",
spawn_class = "hostile", spawn_class = "hostile",
hostile = true, hostile = true,
@ -24,7 +21,7 @@ mobs:register_mob("mobs_mc:creeper", {
mesh = "mobs_mc_creeper.b3d", mesh = "mobs_mc_creeper.b3d",
textures = { textures = {
{"mobs_mc_creeper.png", {"mobs_mc_creeper.png",
"mobs_mc_empty.png"}, "empty.png"},
}, },
visual_size = {x=3, y=3}, visual_size = {x=3, y=3},
sounds = { sounds = {
@ -99,15 +96,12 @@ mobs:register_mob("mobs_mc:creeper", {
end end
end end
end, end,
on_die = function(self, pos, cmi_cause) on_die = function(self, pos, reason)
-- Drop a random music disc when killed by skeleton or stray -- Drop a random music disc when killed by skeleton or stray
if cmi_cause and cmi_cause.type == "punch" then if reason.type == "arrow" then
local luaentity = cmi_cause.puncher and cmi_cause.puncher:get_luaentity() local shooter_luaentity = reason.source:get_luaentity()
if luaentity and luaentity.name:find("arrow") then if shooter_luaentity and (shooter_luaentity.name == "mobs_mc:skeleton" or shooter_luaentity.name == "mobs_mc:stray") then
local shooter_luaentity = luaentity._shooter and luaentity._shooter:get_luaentity() minetest.add_item({x=pos.x, y=pos.y+1, z=pos.z}, mobs_mc.items.music_discs[math.random(1, #mobs_mc.items.music_discs)])
if shooter_luaentity and (shooter_luaentity.name == "mobs_mc:skeleton" or shooter_luaentity.name == "mobs_mc:stray") then
minetest.add_item({x=pos.x, y=pos.y+1, z=pos.z}, mobs_mc.items.music_discs[math.random(1, #mobs_mc.items.music_discs)])
end
end end
end end
end, end,
@ -145,134 +139,15 @@ mobs:register_mob("mobs_mc:creeper", {
floats = 1, floats = 1,
fear_height = 4, fear_height = 4,
view_range = 16, view_range = 16,
}) }
mobs:register_mob("mobs_mc:creeper", creeper)
mobs:register_mob("mobs_mc:creeper_charged", { local creeper_charged = table.copy(creeper)
description = S("Charged Creeper"), creeper_charged.description = S("Charged Creeper")
type = "monster", creeper_charged.textures = {{"mobs_mc_creeper.png", "mobs_mc_creeper_charge.png"}}
spawn_class = "hostile", creeper_charged.explosion_strength = 6
hp_min = 20, creeper_charged.glow = 3
hp_max = 20, mobs:register_mob("mobs_mc:creeper_charged", creeper_charged)
xp_min = 5,
xp_max = 5,
collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.69, 0.3},
pathfinding = 1,
visual = "mesh",
mesh = "mobs_mc_creeper.b3d",
--BOOM
textures = {
{"mobs_mc_creeper.png",
"mobs_mc_creeper_charge.png"},
},
visual_size = {x=3, y=3},
rotate = 270,
sounds = {
attack = "tnt_ignite",
death = "mobs_mc_creeper_death",
damage = "mobs_mc_creeper_hurt",
fuse = "tnt_ignite",
explode = "tnt_explode",
distance = 16,
},
makes_footstep_sound = false,
walk_velocity = 1.05,
run_velocity = 2.1,
runaway_from = { "mobs_mc:ocelot", "mobs_mc:cat" },
attack_type = "explode",
explosion_strength = 6,
--explosion_radius = 3,
--explosion_damage_radius = 6,
--explosiontimer_reset_radius = 3,
reach = 1.5,
defuse_reach = 4,
explosion_timer = 0.3,
allow_fuse_reset = true,
stop_to_explode = true,
-- Force-ignite creeper with flint and steel and explode after 1.5 seconds.
-- TODO: Make creeper flash after doing this as well.
-- TODO: Test and debug this code.
on_rightclick = function(self, clicker)
if self._forced_explosion_countdown_timer ~= nil then
return
end
local item = clicker:get_wielded_item()
if item:get_name() == mobs_mc.items.flint_and_steel then
if not minetest.is_creative_enabled(clicker:get_player_name()) then
-- Wear tool
local wdef = item:get_definition()
item:add_wear(1000)
-- Tool break sound
if item:get_count() == 0 and wdef.sound and wdef.sound.breaks then
minetest.sound_play(wdef.sound.breaks, {pos = clicker:get_pos(), gain = 0.5}, true)
end
clicker:set_wielded_item(item)
end
self._forced_explosion_countdown_timer = self.explosion_timer
minetest.sound_play(self.sounds.attack, {pos = self.object:get_pos(), gain = 1, max_hear_distance = 16}, true)
end
end,
do_custom = function(self, dtime)
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
mobs:boom(self, mcl_util.get_object_center(self.object), self.explosion_strength)
end
end
end,
on_die = function(self, pos, cmi_cause)
-- Drop a random music disc when killed by skeleton or stray
if cmi_cause and cmi_cause.type == "punch" then
local luaentity = cmi_cause.puncher and cmi_cause.puncher:get_luaentity()
if luaentity and luaentity.name:find("arrow") then
local shooter_luaentity = luaentity._shooter and luaentity._shooter:get_luaentity()
if shooter_luaentity and (shooter_luaentity.name == "mobs_mc:skeleton" or shooter_luaentity.name == "mobs_mc:stray") then
minetest.add_item({x=pos.x, y=pos.y+1, z=pos.z}, mobs_mc.items.music_discs[math.random(1, #mobs_mc.items.music_discs)])
end
end
end
end,
maxdrops = 2,
drops = {
{name = mobs_mc.items.gunpowder,
chance = 1,
min = 0,
max = 2,
looting = "common",},
-- Head
-- TODO: Only drop if killed by charged creeper
{name = mobs_mc.items.head_creeper,
chance = 200, -- 0.5%
min = 1,
max = 1,},
},
animation = {
speed_normal = 24,
speed_run = 48,
stand_start = 0,
stand_end = 23,
walk_start = 24,
walk_end = 49,
run_start = 24,
run_end = 49,
hurt_start = 110,
hurt_end = 139,
death_start = 140,
death_end = 189,
look_start = 50,
look_end = 108,
},
floats = 1,
fear_height = 4,
view_range = 16,
--Having trouble when fire is placed with lightning
fire_resistant = true,
glow = 3,
})
mobs:spawn_specific( mobs:spawn_specific(
"mobs_mc:creeper", "mobs_mc:creeper",

Some files were not shown because too many files have changed in this diff Show More