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.mobs
parent
1601437510
commit
0b27b6bec3
|
@ -13,7 +13,7 @@ mcl_damage = {
|
|||
starve = {bypasses_armor = true, bypasses_magic = true},
|
||||
cactus = {},
|
||||
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},
|
||||
generic = {bypasses_armor = true},
|
||||
magic = {is_magic = true, bypasses_armor = true},
|
||||
|
@ -28,7 +28,7 @@ mcl_damage = {
|
|||
fireball = {is_projectile = true, is_fire = true},
|
||||
thorns = {is_magic = true},
|
||||
explosion = {is_explosion = true},
|
||||
cramming = {bypasses_armor = true}, -- unused
|
||||
cramming = {bypasses_armor = true},
|
||||
fireworks = {is_explosion = true}, -- unused
|
||||
}
|
||||
}
|
||||
|
@ -74,11 +74,11 @@ function mcl_damage.from_punch(mcl_reason, object)
|
|||
mcl_reason.direct = object
|
||||
local luaentity = mcl_reason.direct:get_luaentity()
|
||||
if luaentity then
|
||||
if luaentity._is_arrow then
|
||||
if luaentity.is_arrow then
|
||||
mcl_reason.type = "arrow"
|
||||
elseif luaentity._is_fireball then
|
||||
elseif luaentity.is_fireball then
|
||||
mcl_reason.type = "fireball"
|
||||
elseif luaentity._cmi_is_mob then
|
||||
elseif luaentity.is_mob then
|
||||
mcl_reason.type = "mob"
|
||||
end
|
||||
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 colorized variants to imitate minecraft's
|
||||
local smoke_pdef_cached = {}
|
||||
|
||||
function mcl_particles.spawn_smoke(pos, name, smoke_pdef_base)
|
||||
local min = math.min
|
||||
local new_minpos = vector.add(pos, smoke_pdef_base.minrelpos)
|
||||
local new_maxpos = vector.add(pos, smoke_pdef_base.maxrelpos)
|
||||
function mcl_particles.get_smoke_def(def_base)
|
||||
local defs = {}
|
||||
|
||||
-- populate the cache
|
||||
if smoke_pdef_cached[name] then
|
||||
for i, smoke_pdef in ipairs(smoke_pdef_cached[name]) do
|
||||
smoke_pdef.minpos = new_minpos
|
||||
smoke_pdef.maxpos = new_maxpos
|
||||
mcl_particles.add_node_particlespawner(pos, smoke_pdef, "high")
|
||||
end
|
||||
-- cache already populated
|
||||
else
|
||||
smoke_pdef_cached[name] = {}
|
||||
local def = table.copy(def_base)
|
||||
def.amount = def.amount / 9
|
||||
def.time = 0
|
||||
def.animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 8,
|
||||
aspect_h = 8,
|
||||
-- length = 3 exptime variants
|
||||
}
|
||||
def.collisiondetection = true
|
||||
|
||||
local smoke_pdef = table.copy(smoke_pdef_base)
|
||||
smoke_pdef.amount = smoke_pdef_base.amount / 9
|
||||
smoke_pdef.time = 0
|
||||
smoke_pdef.animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 8,
|
||||
aspect_h = 8,
|
||||
-- length = 3 exptime variants
|
||||
}
|
||||
smoke_pdef.collisiondetection = true
|
||||
smoke_pdef.minpos = new_minpos
|
||||
smoke_pdef.maxpos = new_maxpos
|
||||
-- the last frame plays for 1/8 * N seconds, so we can take advantage of it
|
||||
-- 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
|
||||
def.maxexptime = exptime * def_base.maxexptime
|
||||
def.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
|
||||
def.minexptime = math.min(exptime, (7.0 / 8.0 * (exptime + 0.1) + 0.1))
|
||||
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
|
||||
-- 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
|
||||
table.insert(defs, table.copy(def))
|
||||
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
|
||||
|
|
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
|
||||
|
||||
mcl_sounds_poof.ogg by Planman (CC 0), https://freesound.org/people/Planman/sounds/208111/
|
||||
|
||||
Mito551 (sounds) (CC BY-SA 3.0):
|
||||
default_dig_choppy.ogg
|
||||
default_dig_cracky.ogg
|
||||
|
|
|
@ -477,32 +477,22 @@ end
|
|||
function mcl_util.deal_damage(target, damage, mcl_reason)
|
||||
local luaentity = target:get_luaentity()
|
||||
|
||||
if luaentity then
|
||||
if luaentity.deal_damage then
|
||||
luaentity:deal_damage(damage, mcl_reason or {type = "generic"})
|
||||
return
|
||||
elseif luaentity._cmi_is_mob then
|
||||
-- local puncher = mcl_reason and mcl_reason.direct or target
|
||||
-- target:punch(puncher, 1.0, {full_punch_interval = 1.0, damage_groups = {fleshy = damage}}, vector.direction(puncher:get_pos(), target:get_pos()), damage)
|
||||
if luaentity.health > 0 then
|
||||
luaentity.health = luaentity.health - damage
|
||||
end
|
||||
return
|
||||
if luaentity and luaentity.deal_damage then
|
||||
luaentity:deal_damage(damage, mcl_reason or {type = "generic"})
|
||||
else
|
||||
local hp = target:get_hp()
|
||||
|
||||
if hp > 0 then
|
||||
target:set_hp(hp - damage, {_mcl_reason = mcl_reason})
|
||||
end
|
||||
end
|
||||
|
||||
local hp = target:get_hp()
|
||||
|
||||
if hp > 0 then
|
||||
target:set_hp(hp - damage, {_mcl_reason = mcl_reason})
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_util.get_hp(obj)
|
||||
local luaentity = obj:get_luaentity()
|
||||
|
||||
if luaentity and luaentity._cmi_is_mob then
|
||||
return luaentity.health
|
||||
if luaentity and luaentity.is_mob then
|
||||
return luaentity.data.health
|
||||
else
|
||||
return obj:get_hp()
|
||||
end
|
||||
|
|
|
@ -12,51 +12,40 @@ local function is_group(pos, group)
|
|||
return minetest.get_item_group(nn, group) ~= 0
|
||||
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 x = -math.sin(yaw) * v
|
||||
local z = math.cos(yaw) * v
|
||||
return {x = x, y = y, z = z}
|
||||
return vector.add(vector.new(0, y, 0), vector.multiply(minetest.yaw_to_dir(yaw), v))
|
||||
end
|
||||
|
||||
local function get_v(v)
|
||||
return math.sqrt(v.x ^ 2 + v.z ^ 2)
|
||||
end
|
||||
|
||||
local function check_object(obj)
|
||||
return obj and (obj:is_player() or obj:get_luaentity()) and obj
|
||||
function get_sign(i)
|
||||
if not i or i == 0 then
|
||||
return 0
|
||||
else
|
||||
return i / math.abs(i)
|
||||
end
|
||||
end
|
||||
|
||||
local function get_visual_size(obj)
|
||||
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 is_water = flowlib.is_water
|
||||
|
||||
local function is_ice(pos)
|
||||
return is_group(pos, "ice")
|
||||
end
|
||||
|
||||
local function set_attach(boat)
|
||||
boat._driver:set_attach(boat.object, "",
|
||||
{x = 0, y = 0.42, z = -1}, {x = 0, y = 0, z = 0})
|
||||
boat._driver:set_attach(boat.object, "", vector.new(0, 0.42, -1), vector.new(0, 0, 0))
|
||||
end
|
||||
|
||||
local function set_double_attach(boat)
|
||||
boat._driver:set_attach(boat.object, "",
|
||||
{x = 0, y = 0.42, z = 0.8}, {x = 0, y = 0, z = 0})
|
||||
boat._passenger:set_attach(boat.object, "",
|
||||
{x = 0, y = 0.42, z = -2.2}, {x = 0, y = 0, z = 0})
|
||||
boat._driver:set_attach(boat.object, "", vector.new(0, 0.42, 0.8), vector.new(0, 0, 0))
|
||||
boat._passenger:set_attach(boat.object, "", vector.new(0, 0.42, -2.2), vector.new(0, 0, 0))
|
||||
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:is_player() then
|
||||
self._passenger = obj
|
||||
|
@ -69,39 +58,6 @@ local function attach_object(self, obj)
|
|||
self._driver = obj
|
||||
set_attach(self)
|
||||
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
|
||||
|
||||
--
|
||||
|
@ -131,16 +87,13 @@ local boat = {
|
|||
_damage_anim = 0,
|
||||
}
|
||||
|
||||
minetest.register_on_respawnplayer(detach_object)
|
||||
|
||||
function boat.on_rightclick(self, clicker)
|
||||
if self._passenger or not clicker or clicker:get_attach() then
|
||||
return
|
||||
end
|
||||
attach_object(self, clicker)
|
||||
enter_boat(self, clicker)
|
||||
end
|
||||
|
||||
|
||||
function boat.on_activate(self, staticdata, dtime_s)
|
||||
self.object:set_armor_groups({fleshy = 100})
|
||||
local data = minetest.deserialize(staticdata)
|
||||
|
@ -172,10 +125,10 @@ function boat.on_death(self, killer)
|
|||
minetest.add_item(self.object:get_pos(), self._itemstring)
|
||||
end
|
||||
if self._driver then
|
||||
detach_object(self._driver)
|
||||
mcl_mount.throw_off(self._driver)
|
||||
end
|
||||
if self._passenger then
|
||||
detach_object(self._passenger)
|
||||
mcl_mount.throw_off(self._passenger)
|
||||
end
|
||||
self._driver = nil
|
||||
self._passenger = nil
|
||||
|
@ -235,19 +188,13 @@ function boat.on_step(self, dtime, moveresult)
|
|||
|
||||
local had_passenger = self._passenger
|
||||
|
||||
self._driver = check_object(self._driver)
|
||||
self._passenger = check_object(self._passenger)
|
||||
self._driver = self._driver and self._driver:get_attach() == self.object and self._driver
|
||||
self._passenger = self._passenger and self._passenger:get_attach() == self.object and self._passenger
|
||||
|
||||
if self._passenger then
|
||||
if not self._driver then
|
||||
self._driver = self._passenger
|
||||
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
|
||||
|
||||
|
@ -256,13 +203,8 @@ function boat.on_step(self, dtime, moveresult)
|
|||
set_attach(self)
|
||||
end
|
||||
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()
|
||||
if ctrl.up then
|
||||
if ctrl and ctrl.up then
|
||||
-- Forwards
|
||||
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._animation = 1
|
||||
end
|
||||
elseif ctrl.down then
|
||||
elseif ctrl and ctrl.down then
|
||||
-- Backwards
|
||||
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
|
||||
local entity = obj:get_luaentity()
|
||||
if entity and entity._cmi_is_mob then
|
||||
attach_object(self, obj)
|
||||
if entity and entity.is_mob then
|
||||
enter_boat(self, obj)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,17 @@
|
|||
local S = minetest.get_translator("mcl_burning")
|
||||
|
||||
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
|
||||
|
||||
function mcl_burning.is_burning(obj)
|
||||
|
@ -82,21 +92,8 @@ function mcl_burning.set_on_fire(obj, burn_time)
|
|||
storage.fire_damage_timer = 0
|
||||
|
||||
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()
|
||||
fire_luaentity:update_visual_size(obj, storage)
|
||||
fire_luaentity:update_frame(obj, storage)
|
||||
|
||||
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
|
||||
storage.fire_damage_timer = 0
|
||||
|
||||
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
|
||||
mcl_util.deal_damage(obj, 1, {type = "on_fire"})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -35,6 +35,21 @@ minetest.register_globalstep(function(dtime)
|
|||
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)
|
||||
mcl_burning.extinguish(player)
|
||||
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)
|
||||
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
|
||||
description = Burning Objects for MineClone2
|
||||
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
|
||||
|
||||
|
@ -69,7 +69,7 @@ Origin of those models:
|
|||
* `mobs_mc_chicken.png`
|
||||
* `mobs_mc_wither.png`
|
||||
* `mobs_mc_wither_skeleton.png`
|
||||
* `mobs_mc_TEMP_wither_projectile.png`
|
||||
* `mobs_mc_TEMP_wither_projectile.png`
|
||||
* Gerold55
|
||||
* `mobs_mc_mooshroom_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
|
||||
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
|
||||
|
@ -641,7 +643,7 @@ mobs:register_mob("mob_horse:horse", {
|
|||
visual_size = {x = 1.20, y = 1.20},
|
||||
mesh = "mobs_horse.x",
|
||||
collisionbox = {-0.4, -0.01, -0.4, 0.4, 1.25, 0.4},
|
||||
animation = {
|
||||
animation = {
|
||||
speed_normal = 15,
|
||||
speed_run = 30,
|
||||
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
|
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -60,7 +62,7 @@ mobs.do_head_logic = function(self,dtime)
|
|||
--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
|
||||
|
||||
if self.reverse_head_yaw then
|
||||
|
@ -89,9 +91,9 @@ mobs.do_head_logic = function(self,dtime)
|
|||
|
||||
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
|
||||
--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
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
--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
|
||||
local crash_threshold = 6.5 -- ignored if enable_crash=false
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Helper functions
|
||||
--
|
||||
|
||||
local node_ok = function(pos, fallback)
|
||||
|
||||
fallback = fallback or mobs.fallback_node
|
||||
fallback = fallback or "mcl_core:dirt"
|
||||
|
||||
local node = minetest.get_node_or_nil(pos)
|
||||
|
||||
|
@ -25,8 +14,7 @@ end
|
|||
|
||||
|
||||
local function node_is(pos)
|
||||
|
||||
local node = node_ok(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
|
||||
if node.name == "air" then
|
||||
return "air"
|
||||
|
@ -40,182 +28,62 @@ local function node_is(pos)
|
|||
return "liquid"
|
||||
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"
|
||||
end
|
||||
|
||||
return "other"
|
||||
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)
|
||||
|
||||
i = i or 0
|
||||
|
||||
if i == 0 then
|
||||
return 0
|
||||
else
|
||||
return i / math.abs(i)
|
||||
self.driver = obj
|
||||
obj:set_attach(self.object, "", self.def.driver_offset)
|
||||
if obj:is_player() then
|
||||
obj:set_eye_offset(self.def.driver_eye_offset, vector.new(0, 0, 0))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_velocity(v, yaw, y)
|
||||
|
||||
local x = -math.sin(yaw) * v
|
||||
local z = math.cos(yaw) * v
|
||||
|
||||
return {x = x, y = y, z = z}
|
||||
end
|
||||
|
||||
|
||||
local function get_v(v)
|
||||
return math.sqrt(v.x * v.x + v.z * v.z)
|
||||
end
|
||||
|
||||
|
||||
local function force_detach(player)
|
||||
|
||||
local 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}
|
||||
--[[
|
||||
function mobs:drive(moving_anim, stand_anim, can_fly, dtime)
|
||||
|
||||
local rot_view = 0
|
||||
|
||||
if entity.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
|
||||
if self.player_rotation.y == 90 then
|
||||
rot_view = math.pi/2
|
||||
end
|
||||
|
||||
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
|
||||
if entity.driver then
|
||||
if self.driver then
|
||||
|
||||
local ctrl = entity.driver:get_player_control()
|
||||
local ctrl = self.driver:get_player_control()
|
||||
|
||||
-- move forwards
|
||||
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
|
||||
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
|
||||
else
|
||||
|
@ -253,7 +121,7 @@ function mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
|
|||
end
|
||||
|
||||
else
|
||||
]]--
|
||||
] ]--
|
||||
|
||||
-- jump
|
||||
if ctrl.jump then
|
||||
|
@ -381,7 +249,7 @@ function mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
|
|||
end
|
||||
|
||||
entity.v2 = v
|
||||
]]--
|
||||
] ]--
|
||||
end
|
||||
|
||||
|
||||
|
@ -449,3 +317,5 @@ function mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_anim)
|
|||
mobs:set_mob_animation(entity, moving_anim)
|
||||
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
|
||||
local aoc_range = 48
|
||||
|
@ -162,7 +143,7 @@ Overworld regular:
|
|||
"MesaBryce",
|
||||
"JungleEdge",
|
||||
"SavannaM",
|
||||
]]--
|
||||
] ]--
|
||||
|
||||
|
||||
|
||||
|
@ -170,7 +151,7 @@ Overworld regular:
|
|||
local count_mobs = function(pos)
|
||||
local num = 0
|
||||
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
|
||||
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
|
||||
|
||||
WARNING: BIOME INTEGRATION NEEDED -> How to get biome through lua??
|
||||
]]--
|
||||
] ]--
|
||||
|
||||
|
||||
--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?
|
||||
if not mobs.spawning_mobs[name]
|
||||
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
|
||||
end
|
||||
|
||||
-- additional custom checks for spawning mob
|
||||
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
|
||||
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 count_mobs(pos, name) >= aoc then -- per-mob mob cap
|
||||
-- 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
|
||||
end
|
||||
|
||||
|
@ -288,14 +269,14 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh
|
|||
-- daylight, but mob wants night
|
||||
if day_toggle == false then
|
||||
-- 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
|
||||
end
|
||||
else
|
||||
-- night time but mob wants day
|
||||
if day_toggle == true then
|
||||
-- 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
|
||||
end
|
||||
end
|
||||
|
@ -311,7 +292,7 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh
|
|||
|
||||
if objs[n]:is_player() then
|
||||
-- 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
|
||||
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
|
||||
if not spawn_protected
|
||||
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
|
||||
end
|
||||
|
||||
-- are we spawning within height limits?
|
||||
if pos.y > max_height
|
||||
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
|
||||
end
|
||||
|
||||
|
@ -335,7 +316,7 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh
|
|||
if not light
|
||||
or light > max_light
|
||||
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
|
||||
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}
|
||||
if minetest.registered_nodes[node_ok(pos2).name].walkable == true then
|
||||
-- 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
|
||||
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)
|
||||
|
@ -390,7 +371,7 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh
|
|||
pos.y = pos.y - 0.5
|
||||
|
||||
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
|
||||
|
||||
|
@ -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)
|
||||
spawn_action(pos, node, active_object_count, active_object_count_wider, name)
|
||||
end
|
||||
]]--
|
||||
] ]--
|
||||
|
||||
local entdef = minetest.registered_entities[name]
|
||||
local spawn_class
|
||||
|
@ -441,7 +422,7 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh
|
|||
catch_up = false,
|
||||
action = spawn_abm_action,
|
||||
})
|
||||
]]--
|
||||
] ]--
|
||||
end
|
||||
|
||||
-- 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,
|
||||
chance, active_object_count, -31000, max_height, day_toggle)
|
||||
end
|
||||
]]--
|
||||
] ]--
|
||||
|
||||
|
||||
--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,
|
||||
chance, active_object_count, min_height, max_height, day_toggle, on_spawn)
|
||||
]]--
|
||||
] ]--
|
||||
end
|
||||
|
||||
|
||||
|
@ -488,18 +469,18 @@ local outer = 64
|
|||
local int = {-1,1}
|
||||
local position_calculation = function(pos)
|
||||
|
||||
pos = vector_floor(pos)
|
||||
pos = vector.floor(pos)
|
||||
|
||||
--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
|
||||
if axis == 0 then --x
|
||||
pos.x = pos.x + math_random(inner,outer)*int[math_random(1,2)]
|
||||
pos.z = pos.z + math_random(-outer,outer)
|
||||
pos.x = pos.x + math.random(inner,outer)*int[math.random(1,2)]
|
||||
pos.z = pos.z + math.random(-outer,outer)
|
||||
else --z
|
||||
pos.z = pos.z + math_random(inner,outer)*int[math_random(1,2)]
|
||||
pos.x = pos.x + math_random(-outer,outer)
|
||||
pos.z = pos.z + math.random(inner,outer)*int[math.random(1,2)]
|
||||
pos.x = pos.x + math.random(-outer,outer)
|
||||
end
|
||||
return(pos)
|
||||
end
|
||||
|
@ -510,12 +491,12 @@ local decypher_limits_dictionary = {
|
|||
["nether"] = {mcl_vars.mg_nether_min, mcl_vars.mg_nether_max},
|
||||
["end"] = {mcl_vars.mg_end_min, mcl_vars.mg_end_max}
|
||||
}
|
||||
]]--
|
||||
] ]--
|
||||
|
||||
local function decypher_limits(posy)
|
||||
--local min_max_table = decypher_limits_dictionary[dimension]
|
||||
--return min_max_table[1],min_max_table[2]
|
||||
posy = math_floor(posy)
|
||||
posy = math.floor(posy)
|
||||
return posy - 32, posy + 32
|
||||
end
|
||||
|
||||
|
@ -556,27 +537,27 @@ if mobs_spawn then
|
|||
|
||||
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"
|
||||
local do_mob_algorithm = true
|
||||
repeat
|
||||
|
||||
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
|
||||
if #spawning_position_list <= 0 then
|
||||
break
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
|
||||
--hard code mob limit in area to 5 for now
|
||||
if count_mobs(spawning_position) >= 5 then
|
||||
break
|
||||
|
@ -606,7 +587,7 @@ if mobs_spawn then
|
|||
local is_lava = get_item_group(gotten_node, "lava") ~= 0
|
||||
|
||||
local mob_def = nil
|
||||
|
||||
|
||||
--create a disconnected clone of the spawn dictionary
|
||||
--prevents memory leak
|
||||
local mob_library_worker_table = table_copy(spawn_dictionary)
|
||||
|
@ -625,7 +606,7 @@ if mobs_spawn then
|
|||
local skip = false
|
||||
|
||||
--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]
|
||||
|
||||
|
@ -705,3 +686,25 @@ if mobs_spawn then
|
|||
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 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.
|
||||
|
||||
* 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)
|
||||
return function(self, dtime)
|
||||
-- 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
|
||||
|
||||
--]]
|
|
@ -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
|
||||
dofile(api_path .. "/api.lua")
|
||||
-- timers
|
||||
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
|
||||
dofile(api_path .. "/spawning.lua")
|
||||
-- movement
|
||||
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
|
||||
dofile(api_path .. "/mount.lua")
|
||||
local path = minetest.get_modpath("mcl_mobs")
|
||||
local api_files = io.open(path .. "/api_files.txt", "r") -- update with: $ find api -name "*.lua" > api_files.txt
|
||||
|
||||
-- Mob Items
|
||||
dofile(path .. "/crafts.lua")
|
||||
for file in api_files:lines() do
|
||||
dofile(path .. "/" .. file)
|
||||
end
|
||||
|
||||
api_files:close()
|
||||
|
|
|
@ -1,11 +1,69 @@
|
|||
# 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.
|
||||
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.
|
||||
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!
|
||||
Give names to mobs=Benennt Mobs
|
||||
Set name at anvil=Namen am Amboss setzen
|
||||
Agent=Akteur
|
||||
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
|
||||
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.
|
||||
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.
|
||||
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!
|
||||
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
|
||||
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.
|
||||
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.
|
||||
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!
|
||||
Give names to mobs=Donne des noms aux mobs
|
||||
Set name at anvil=Définir le nom sur l'enclume
|
||||
Agent=Agent
|
||||
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
|
||||
Peaceful mode active! No monsters will spawn.=Мирный режим включён! Монстры не будут появляться.
|
||||
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.=Просто поместите это туда, где хотите, чтобы появился моб. Животные будут появляться уже прирученные, если это не нужно, удерживайте клавишу [Красться] при размещении. Если поместить это на спаунер, появляющийся из него моб будет изменён.
|
||||
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!=Разрешены только мирные мобы!
|
||||
Give names to mobs=Даёт имена мобам
|
||||
Set name at anvil=Задайте имя при помощи наковальни
|
||||
Agent=Агент
|
||||
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
|
||||
Peaceful mode active! No monsters will spawn.=
|
||||
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.=
|
||||
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!=
|
||||
Give names to mobs=
|
||||
Set name at anvil=
|
||||
Agent=
|
||||
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.override = {}
|
||||
|
@ -179,9 +370,6 @@ mobs_mc.override.enderman_takable = {
|
|||
}
|
||||
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
|
||||
-- and the textures have tranparent pixels.
|
||||
|
@ -200,7 +388,7 @@ end
|
|||
mobs_mc.override.enderman_block_texture_overrides = {
|
||||
["mcl_core:cactus"] = ctable,
|
||||
-- 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",
|
||||
"default_dirt.png",
|
|
@ -195,7 +195,7 @@ local how_to_throw = "Hold it in your and and leftclick to throw."
|
|||
-- egg throwing item
|
||||
-- egg entity
|
||||
if c("egg") then
|
||||
local egg_GRAVITY = 9
|
||||
local egg_gravity = 9
|
||||
local egg_VELOCITY = 19
|
||||
|
||||
mobs:register_arrow("mobs_mc:egg_entity", {
|
||||
|
@ -289,7 +289,7 @@ if c("egg") then
|
|||
|
||||
obj:set_acceleration({
|
||||
x = dir.x * -3,
|
||||
y = -egg_GRAVITY,
|
||||
y = -egg_gravity,
|
||||
z = dir.z * -3
|
||||
})
|
||||
|
||||
|
@ -315,7 +315,7 @@ end
|
|||
|
||||
-- Snowball
|
||||
|
||||
local snowball_GRAVITY = 9
|
||||
local snowball_gravity = 9
|
||||
local snowball_VELOCITY = 19
|
||||
|
||||
mobs:register_arrow("mobs_mc:snowball_entity", {
|
||||
|
@ -373,7 +373,7 @@ if c("snowball") then
|
|||
|
||||
obj:set_acceleration({
|
||||
x = dir.x * -3,
|
||||
y = -snowball_GRAVITY,
|
||||
y = -snowball_gravity,
|
||||
z = dir.z * -3
|
||||
})
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
--License for code WTFPL and otherwise stated in readmes
|
||||
local S = minetest.get_translator("mcl_mobs")
|
||||
|
||||
local S = minetest.get_translator("mobs_mc")
|
||||
|
||||
mobs:register_mob("mobs_mc:bat", {
|
||||
mcl_mobs.register_mob("mcl_mobs:bat", {
|
||||
description = S("Bat"),
|
||||
type = "animal",
|
||||
spawn_class = "ambient",
|
||||
|
@ -14,16 +13,14 @@ mobs:register_mob("mobs_mc:bat", {
|
|||
hp_min = 6,
|
||||
hp_max = 6,
|
||||
collisionbox = {-0.25, -0.01, -0.25, 0.25, 0.89, 0.25},
|
||||
visual = "mesh",
|
||||
mesh = "mobs_mc_bat.b3d",
|
||||
model = "mcl_mobs_bat.b3d",
|
||||
textures = {
|
||||
{"mobs_mc_bat.png"},
|
||||
{"mcl_mobs_bat.png"},
|
||||
},
|
||||
visual_size = {x=1, y=1},
|
||||
sounds = {
|
||||
random = "mobs_mc_bat_idle",
|
||||
damage = "mobs_mc_bat_hurt",
|
||||
death = "mobs_mc_bat_death",
|
||||
random = "mcl_mobs_bat_idle",
|
||||
damage = "mcl_mobs_bat_hurt",
|
||||
death = "mcl_mobs_bat_death",
|
||||
distance = 16,
|
||||
},
|
||||
walk_velocity = 4.5,
|
||||
|
@ -47,7 +44,6 @@ mobs:register_mob("mobs_mc:bat", {
|
|||
walk_chance = 100,
|
||||
fall_damage = 0,
|
||||
view_range = 16,
|
||||
fear_height = 0,
|
||||
jump = false,
|
||||
makes_footstep_sound = false,
|
||||
})
|
||||
|
@ -66,8 +62,8 @@ else
|
|||
end
|
||||
|
||||
-- Spawn on solid blocks at or below Sea level and the selected light level
|
||||
mobs:spawn_specific(
|
||||
"mobs_mc:bat",
|
||||
mcl_mobs.spawn_specific(
|
||||
"mcl_mobs:bat",
|
||||
"overworld",
|
||||
"ground",
|
||||
{
|
||||
|
@ -139,9 +135,8 @@ maxlight,
|
|||
20,
|
||||
5000,
|
||||
2,
|
||||
mobs_mc.spawn_height.overworld_min,
|
||||
mobs_mc.spawn_height.water-1)
|
||||
|
||||
mcl_mobs.spawn_height.overworld_min,
|
||||
mcl_mobs.spawn_height.water-1)
|
||||
|
||||
-- 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
|
||||
-- My work is under the LGPL terms
|
||||
-- Model and mobs_blaze.png see https://github.com/22i/minecraft-voxel-blender-models -hi 22i ~jordan4ibanez
|
||||
-- blaze.lua partial copy of mobs_mc/ghast.lua
|
||||
-- Model and mcl_mobs_blaze.png see https://github.com/22i/minecraft-voxel-blender-models -hi 22i ~jordan4ibanez
|
||||
-- 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},
|
||||
})
|
||||
|
||||
--###################
|
||||
--################### BLAZE
|
||||
--###################
|
||||
|
||||
|
||||
mobs:register_mob("mobs_mc:blaze", {
|
||||
mcl_mobs.register_mob("mcl_mobs:blaze", {
|
||||
description = S("Blaze"),
|
||||
type = "monster",
|
||||
spawn_class = "hostile",
|
||||
|
@ -23,17 +28,16 @@ mobs:register_mob("mobs_mc:blaze", {
|
|||
rotate = 270,
|
||||
collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.79, 0.3},
|
||||
rotate = -180,
|
||||
visual = "mesh",
|
||||
mesh = "mobs_mc_blaze.b3d",
|
||||
model = "mcl_mobs_blaze.b3d",
|
||||
textures = {
|
||||
{"mobs_mc_blaze.png"},
|
||||
{"mcl_mobs_blaze.png"},
|
||||
},
|
||||
armor = { fleshy = 100, snowball_vulnerable = 100, water_vulnerable = 100 },
|
||||
visual_size = {x=3, y=3},
|
||||
armor = {fleshy = 100, snowball_vulnerable = 100, water_vulnerable = 100},
|
||||
visual_size = {x = 3, y = 3},
|
||||
sounds = {
|
||||
random = "mobs_mc_blaze_breath",
|
||||
death = "mobs_mc_blaze_died",
|
||||
damage = "mobs_mc_blaze_hurt",
|
||||
random = "mcl_mobs_blaze_breath",
|
||||
death = "mcl_mobs_blaze_died",
|
||||
damage = "mcl_mobs_blaze_hurt",
|
||||
distance = 16,
|
||||
},
|
||||
walk_velocity = .8,
|
||||
|
@ -42,17 +46,19 @@ mobs:register_mob("mobs_mc:blaze", {
|
|||
reach = 4, -- don't want blaze getting too close
|
||||
pathfinding = 1,
|
||||
drops = {
|
||||
{name = mobs_mc.items.blaze_rod,
|
||||
chance = 1,
|
||||
min = 0,
|
||||
max = 1,
|
||||
looting = "common",},
|
||||
{
|
||||
name = "mcl_mobitems:blaze_rod",
|
||||
chance = 1,
|
||||
min = 0,
|
||||
max = 1,
|
||||
looting = "common",
|
||||
},
|
||||
},
|
||||
animation = {
|
||||
stand_speed = 25,
|
||||
stand_start = 0,
|
||||
stand_end = 100,
|
||||
walk_speed = 25,
|
||||
stand_end = 100,
|
||||
walk_speed = 25,
|
||||
walk_start = 0,
|
||||
walk_end = 100,
|
||||
run_speed = 50,
|
||||
|
@ -62,11 +68,9 @@ mobs:register_mob("mobs_mc:blaze", {
|
|||
-- MC Wiki: takes 1 damage every half second while in water
|
||||
water_damage = 2,
|
||||
lava_damage = 0,
|
||||
fire_damage = 0,
|
||||
fall_damage = 0,
|
||||
fall_speed = -2.25,
|
||||
light_damage = 0,
|
||||
view_range = 16,
|
||||
view_range = 48,
|
||||
attack_type = "projectile",
|
||||
arrow = "mobs_mc:blaze_fireball",
|
||||
shoot_interval = 3.5,
|
||||
|
@ -76,17 +80,12 @@ mobs:register_mob("mobs_mc:blaze", {
|
|||
jump_height = 4,
|
||||
fly = true,
|
||||
makes_footstep_sound = false,
|
||||
fear_height = 0,
|
||||
glow = 14,
|
||||
fire_resistant = true,
|
||||
eye_height = 0.75,
|
||||
projectile_cooldown_min = 2,
|
||||
projectile_cooldown_max = 3,
|
||||
shoot_arrow = function(self, pos, dir)
|
||||
-- 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,
|
||||
arrow = "mcl_mobs:blaze_fireball",
|
||||
|
||||
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
|
||||
|
@ -155,6 +154,13 @@ mobs_mc.spawn_height.nether_min,
|
|||
mobs_mc.spawn_height.nether_max)
|
||||
|
||||
-- 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", {
|
||||
visual = "sprite",
|
||||
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_size = 2,
|
||||
tail_distance_divider = 3,
|
||||
_is_fireball = true,
|
||||
is_fireball = true,
|
||||
|
||||
-- Direct hit, no fire... just plenty of pain
|
||||
hit_player = function(self, player)
|
||||
mcl_burning.set_on_fire(player, 5)
|
||||
player:punch(self.object, 1.0, {
|
||||
full_punch_interval = 1.0,
|
||||
damage_groups = {fleshy = self._damage},
|
||||
|
@ -177,7 +181,6 @@ mobs:register_arrow("mobs_mc:blaze_fireball", {
|
|||
end,
|
||||
|
||||
hit_mob = function(self, mob)
|
||||
mcl_burning.set_on_fire(mob, 5)
|
||||
mob:punch(self.object, 1.0, {
|
||||
full_punch_interval = 1.0,
|
||||
damage_groups = {fleshy = self._damage},
|
||||
|
@ -214,4 +217,4 @@ mobs:register_arrow("mobs_mc:blaze_fireball", {
|
|||
})
|
||||
|
||||
-- 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
|
||||
--###################
|
||||
|
||||
|
||||
|
||||
|
||||
mobs:register_mob("mobs_mc:creeper", {
|
||||
local creeper = {
|
||||
type = "monster",
|
||||
spawn_class = "hostile",
|
||||
hostile = true,
|
||||
|
@ -24,7 +21,7 @@ mobs:register_mob("mobs_mc:creeper", {
|
|||
mesh = "mobs_mc_creeper.b3d",
|
||||
textures = {
|
||||
{"mobs_mc_creeper.png",
|
||||
"mobs_mc_empty.png"},
|
||||
"empty.png"},
|
||||
},
|
||||
visual_size = {x=3, y=3},
|
||||
sounds = {
|
||||
|
@ -99,15 +96,12 @@ mobs:register_mob("mobs_mc:creeper", {
|
|||
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
|
||||
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
|
||||
if reason.type == "arrow" then
|
||||
local shooter_luaentity = reason.source: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,
|
||||
|
@ -145,134 +139,15 @@ mobs:register_mob("mobs_mc:creeper", {
|
|||
floats = 1,
|
||||
fear_height = 4,
|
||||
view_range = 16,
|
||||
})
|
||||
}
|
||||
mobs:register_mob("mobs_mc:creeper", creeper)
|
||||
|
||||
mobs:register_mob("mobs_mc:creeper_charged", {
|
||||
description = S("Charged Creeper"),
|
||||
type = "monster",
|
||||
spawn_class = "hostile",
|
||||
hp_min = 20,
|
||||
hp_max = 20,
|
||||
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,
|
||||
})
|
||||
local creeper_charged = table.copy(creeper)
|
||||
creeper_charged.description = S("Charged Creeper")
|
||||
creeper_charged.textures = {{"mobs_mc_creeper.png", "mobs_mc_creeper_charge.png"}}
|
||||
creeper_charged.explosion_strength = 6
|
||||
creeper_charged.glow = 3
|
||||
mobs:register_mob("mobs_mc:creeper_charged", creeper_charged)
|
||||
|
||||
mobs:spawn_specific(
|
||||
"mobs_mc:creeper",
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue