489 lines
15 KiB
Lua
489 lines
15 KiB
Lua
--
|
|
-- This file adds function to complete the mobs_redo api
|
|
--
|
|
|
|
local S = mobs.intllib
|
|
local modname = minetest.get_current_modname()
|
|
local modpath = minetest.get_modpath(modname)
|
|
|
|
mobs.mobs_bundle_settings = {}
|
|
mobs.mobs_bundle_settings.damages = {}
|
|
mobs.mobs_bundle_settings.damages.nopositional = tonumber(minetest.settings:get("mobs.damages.nopositional")) or false
|
|
|
|
|
|
local animal_attack_ignore_player = true
|
|
|
|
-- Settings for positional damage
|
|
local pdam = {}
|
|
-- Disable positional damages
|
|
pdam.disable = tonumber(minetest.settings:get("mobs.damages.nopositional")) or false
|
|
-- Min and max multiplier
|
|
pdam.max = tonumber(minetest.settings:get("mobs.damages.max_multiplier")) or 11
|
|
pdam.min = tonumber(minetest.settings:get("mobs.damages.min_multiplier")) or 1
|
|
-- Upper limit for distant where to multiply -- Uuuh not clear
|
|
pdam.max_radius = tonumber(minetest.settings:get("mobs.damages.max_radius")) or minetest.settings:get("mapgen_limit") or 310000
|
|
|
|
-- Mutilpication to allow different incerases on axis
|
|
pdam.y_mult = tonumber(minetest.settings:get("mobs.damages.y_multiplier")) or 1
|
|
pdam.x_mult = tonumber(minetest.settings:get("mobs.damages.x_multiplier")) or 1
|
|
pdam.z_mult = tonumber(minetest.settings:get("mobs.damages.z_multiplier")) or 1
|
|
|
|
pdam.change_walk_velocity = tonumber(minetest.settings:get("mobs.damages.change_walk_velocity")) or true
|
|
pdam.speed_factor = tonumber(minetest.settings:get("mobs.damages.speed_factor")) or 0.1
|
|
|
|
local passive_mob_radius = tonumber(minetest.settings:get("mobs.passive_mob_radius")) or 128
|
|
local no_monster_radius = tonumber(minetest.settings:get("mobs.no_monster_radius")) or 100
|
|
|
|
local hardness = (pdam.max_radius / pdam.max) / pdam.min
|
|
|
|
-- Add leather and saddles to chests because there is not other way to get it yet
|
|
if minetest.get_modpath('treasurer') then
|
|
treasurer.register_treasure("mobs:lether",0.01,5,{1,4},nil,"crafting_component")
|
|
treasurer.register_treasure("mobs:saddle",0.0002,7,1,nil,"tool")
|
|
end
|
|
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Custom check before spawning
|
|
-----------------------------------------------------------------------
|
|
|
|
function mobs:spawn_abm_check(pos, node, name)
|
|
-- global function to add additional spawn checks
|
|
-- return true to stop spawning mob
|
|
|
|
--- [ Allow spawning only outside or only inside a specific radius] ---
|
|
local ent = minetest.registered_entities[name]
|
|
|
|
--~ print('----'..name..' is spawning at '..minetest.pos_to_string(pos))
|
|
--~ print(dump(ent))
|
|
|
|
local apos = math.max(math.abs(pos.x), math.abs(pos.y), math.abs(pos.z))
|
|
|
|
if no_monster_radius and ( ent.type == "monster" ) and apos < no_monster_radius then
|
|
return true
|
|
end
|
|
|
|
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Positional promotion
|
|
-----------------------------------------------------------------------
|
|
function mobs:promote(self,pos)
|
|
|
|
if pdam.disable then return true end
|
|
|
|
if ( not self.initial_promotion ) and self.hp_max and self.object and self.health and self.damage then
|
|
|
|
if not pos then pos = self.object:get_pos() end
|
|
pos.x = pos.x * pdam.z_mult
|
|
pos.y = pos.y * pdam.y_mult
|
|
pos.z = pos.z * pdam.z_mult
|
|
local apos = math.max(math.abs(pos.x), math.abs(pos.y), math.abs(pos.z))
|
|
if apos > pdam.max_radius then apos = pdam.max_radius end
|
|
local factor = 1 + ( apos * pdam.min / hardness)
|
|
|
|
-- These all expect that a mob will never be promoted twice,
|
|
self.hp_max = math.floor(self.hp_max * factor)
|
|
self.damage = math.floor(self.damage * factor)
|
|
self.health = math.floor(self.health * factor)
|
|
if pdam.change_walk_velocity then
|
|
self.walk_velocity = self.walk_velocity * math.max(1, pdam.speed_factor * factor)
|
|
end
|
|
self.run_velocity = self.run_velocity * math.max(1, pdam.speed_factor * factor)
|
|
self.difficulty = factor
|
|
local name = self.object:get_entity_name() or ''
|
|
--~ self.old_on_die = mob.on_die
|
|
--~ self.on_die = extra_loot_on_die
|
|
self.object:set_hp(self.health)
|
|
self.initial_promotion = true
|
|
--~ check_for_death(mob)
|
|
|
|
print('Promoting '..name..': '..self.health..' health, '..self.damage..' damage (x'..(math.floor(factor * 100) / 100)..')')
|
|
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
mobs.custom_on_spawn = function (self,pos)
|
|
|
|
local prom = mobs:promote(self,pos)
|
|
|
|
local apos = math.max(math.abs(pos.x), math.abs(pos.y), math.abs(pos.z))
|
|
if passive_mob_radius and apos < passive_mob_radius then
|
|
if self.type == "monster" then self.type = "animal" end
|
|
self.passive = true
|
|
self.runaway = true
|
|
if not self.runaway_from then self.runaway_from = {} end
|
|
table.insert(self.runaway_from,'player')
|
|
end
|
|
|
|
return prom
|
|
end
|
|
-----------------------------------------------------------------------
|
|
-- Throw eggs
|
|
-----------------------------------------------------------------------
|
|
if not mobs.throw_egg then
|
|
|
|
-- egg throwing item
|
|
|
|
local egg_GRAVITY = 9
|
|
local egg_VELOCITY = 19
|
|
|
|
-- shoot egg
|
|
mobs.throw_egg = function(item, player, pointed_thing)
|
|
|
|
local playerpos = player:get_pos()
|
|
|
|
minetest.sound_play("default_place_node_hard", {
|
|
pos = playerpos,
|
|
gain = 1.0,
|
|
max_hear_distance = 5,
|
|
})
|
|
|
|
local obj = minetest.add_entity({
|
|
x = playerpos.x,
|
|
y = playerpos.y +1.5,
|
|
z = playerpos.z
|
|
}, modname..":egg_entity")
|
|
|
|
local ent = obj:get_luaentity()
|
|
local dir = player:get_look_dir()
|
|
|
|
ent.velocity = egg_VELOCITY -- needed for api internal timing
|
|
ent.switch = 1 -- needed so that egg doesn't despawn straight away
|
|
|
|
obj:setvelocity({
|
|
x = dir.x * egg_VELOCITY,
|
|
y = dir.y * egg_VELOCITY,
|
|
z = dir.z * egg_VELOCITY
|
|
})
|
|
|
|
obj:setacceleration({
|
|
x = dir.x * -3,
|
|
y = -egg_GRAVITY,
|
|
z = dir.z * -3
|
|
})
|
|
|
|
-- pass player name to egg for chick ownership
|
|
local ent2 = obj:get_luaentity()
|
|
ent2.playername = player:get_player_name()
|
|
|
|
item:take_item()
|
|
|
|
return item
|
|
end
|
|
end
|
|
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Attack
|
|
-----------------------------------------------------------------------
|
|
function mobs:animal_attack(self)
|
|
|
|
-- Return there is a reason to prevent agressive behavior
|
|
-- if self.type ~= "monster"
|
|
if not damage_enabled
|
|
or creative
|
|
or self.state == "attack"
|
|
or day_docile(self) then
|
|
return
|
|
end
|
|
|
|
-- Return if there is not target
|
|
if not ( type(self.specific_attack) == "table"
|
|
or self.attack_animal
|
|
or self.attacks_monsters ) then return end
|
|
local s = self.object:get_pos()
|
|
local p, sp, dist
|
|
local player, obj, min_player
|
|
local type, name = "", ""
|
|
local min_dist = self.view_range + 1
|
|
local objs = minetest.get_objects_inside_radius(s, self.view_range)
|
|
|
|
for n = 1, #objs do
|
|
if objs[n]:is_player() then
|
|
if animal_attack_ignore_player or mobs.invis[ objs[n]:get_player_name() ] then
|
|
type = ""
|
|
else
|
|
player = objs[n]
|
|
type = "player"
|
|
name = "player"
|
|
end
|
|
else
|
|
obj = objs[n]:get_luaentity()
|
|
if obj then
|
|
player = obj.object
|
|
type = obj.type
|
|
name = obj.name or ""
|
|
end
|
|
end
|
|
|
|
-- Find a specific target
|
|
local has_prey
|
|
local targets = self.specific_attack
|
|
if type(targets) == "table" then
|
|
for no = 1, #targets do
|
|
if targets[no] == name then
|
|
has_prey = true
|
|
end
|
|
end
|
|
end
|
|
|
|
local target_is_monster = (type == "monster")
|
|
local target_is_not_monster = (type == "player" or type == "npc" or type == "animal")
|
|
local can_attack_not_monster = (self.attack_animals == true)
|
|
-- find specific mob to attack, failing that attack player/npc/animal
|
|
if has_prey and (target_is_monster or (target_is_not_monster and can_attack_not_monster )) then
|
|
|
|
s = self.object:get_pos()
|
|
p = player:get_pos()
|
|
sp = s
|
|
|
|
-- aim higher to make looking up hills more realistic
|
|
p.y = p.y + 1
|
|
sp.y = sp.y + 1
|
|
|
|
dist = get_distance(p, s)
|
|
|
|
if dist < self.view_range then
|
|
-- field of view check goes here
|
|
|
|
-- choose closest target to attack
|
|
if line_of_sight(self, sp, p, 2) == true
|
|
and dist < min_dist then
|
|
min_dist = dist
|
|
min_player = player
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- attack player
|
|
if min_player then
|
|
self.attack = min_player
|
|
self.state = "attack"
|
|
|
|
if random(0, 100) < 90 then
|
|
local sound = self.sounds.war_cry
|
|
if sound then
|
|
minetest.sound_play(sound, {
|
|
object = self.object,
|
|
gain = 1.0,
|
|
max_hear_distance = self.sounds.distance
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function mobs:doc_identifier_compat(mob_id,longdesc,usagehelp)
|
|
minetest.override_item(mob_id, {
|
|
_doc_items_longdesc = longdesc,
|
|
_doc_items_usagehelp = usagehelp,
|
|
})
|
|
if minetest.get_modpath("doc_identifier") and doc then
|
|
doc.sub.identifier.register_object(mob_id, "craftitems", mob_id)
|
|
end
|
|
end
|
|
|
|
function mobs:custom_captured_name(mob_id,desc)
|
|
minetest.override_item(mob_id..'_set', {
|
|
description = desc,
|
|
})
|
|
end
|
|
|
|
|
|
function get_preciousness(dpth,min_abs,max_abs)
|
|
local abs_dpth = math.abs(dpth)
|
|
|
|
-- Get depth range
|
|
if not min_abs then min_abs = 0 end
|
|
if not max_abs then max_abs = 31000 end
|
|
min_abs = math.abs(min_abs)
|
|
max_abs = math.abs(max_abs)
|
|
|
|
local h_max, h_min
|
|
if min_abs > max_abs then h_max = min_abs ; h_min = max_abs
|
|
else h_max = max_abs ; h_min = min_abs end
|
|
|
|
local l = (h_max - h_min) / 12
|
|
|
|
-- Preciousness step up the deeper (or higher) we go
|
|
local v = { minp = 1, maxp = 3 }
|
|
if dpth > 8*l then v = { minp = 8, maxp = 10 }
|
|
elseif dpth > 5*l then v = { minp = 7, maxp = 9 }
|
|
elseif dpth > 3*l then v = { minp = 6, maxp = 8 }
|
|
elseif dpth > 2*l then v = { minp = 5, maxp = 6 }
|
|
elseif dpth > 1*l then v = { minp = 3, maxp = 5 }
|
|
end
|
|
|
|
return v
|
|
end
|
|
|
|
function mobs:drop_something_from_treasurer(self, pos, min_abs, max_abs)
|
|
-- Might drop something from treasurer
|
|
-- Unless treasurer is not present
|
|
if not minetest.get_modpath('treasurer') then return end
|
|
-- And only after a fight
|
|
if self.state ~= "attack" then return end
|
|
local drops = {}
|
|
local n = 1 -- Only one treasure
|
|
local tresure_types = {
|
|
"ranged_weapon", "melee_weapon", "minetool",
|
|
"food", "crafting_component", "default",
|
|
}
|
|
local pre = get_preciousness(pos.y, min_abs, max_abs)
|
|
local treasure = treasurer.select_random_treasures(n,pre[minp],pre[maxp],tresure_types)
|
|
|
|
for _,itemstack in ipairs(treasure) do
|
|
drops[#drops+1] = itemstack
|
|
end
|
|
|
|
-- if chance == 1 and minetest.get_modpath('cursedstone') and opt.spawner_use_cursedstone then
|
|
|
|
-- Drop something from the drops table
|
|
if #drops > 0 then
|
|
local i = math.random(1,#drops)
|
|
minetest.add_item(pos, drops[i])
|
|
end
|
|
end
|
|
|
|
|
|
function mobs:register_special_spawner(def)
|
|
if not ( def and def.id and def.description ) then return end
|
|
local peaceful = minetest.settings:get_bool("only_peaceful_mobs")
|
|
local drop_use_default = true
|
|
local drop_use_treasurer = true
|
|
local spawnerdef = {}
|
|
spawnerdef.id = def.id
|
|
spawnerdef.name = modname..":spawner_"..spawnerdef.id
|
|
spawnerdef.description = def.description
|
|
spawnerdef.longdesc = def.longdesc
|
|
-- abm definition
|
|
spawnerdef.neighbors = def.neighbors
|
|
spawnerdef.interval = def.interval or 2.0
|
|
spawnerdef.chance = def.chance or 20
|
|
spawnerdef.min_light = def.min_light or -1
|
|
spawnerdef.max_light = def.max_light or 15
|
|
spawnerdef.min_height = def.min_height or -31000
|
|
spawnerdef.max_height = def.max_height or 31000
|
|
spawnerdef.day_toggle = def.day_toggle or true
|
|
spawnerdef.on_spawn = def.on_spawn
|
|
-- Range of spawner
|
|
spawnerdef.range = def.range or 17
|
|
-- base texture for the spawner
|
|
spawnerdef.texture_face = def.texture_face or "sides"
|
|
spawnerdef.base_texture = def.base_texture or "default_sandstone_block.png"
|
|
-- Image drawn on the spawner block
|
|
spawnerdef.glyph = def.glyph or modname.."_spawner_face.png"
|
|
-- Number of mobs spawned when spawner is destructed
|
|
spawnerdef.last_spawn = def.last_spawn or 3
|
|
-- Chances that spawner drop nothing
|
|
spawnerdef.empty_drop_chance = def.empty_drop_chance or 2
|
|
-- Mob Spawned by spawner
|
|
spawnerdef.mob = def.mob
|
|
spawnerdef.tiles = def.tiles
|
|
spawnerdef.drawtype = def.drawtype or "normal"
|
|
spawnerdef.node_box = def.node_box or nil
|
|
|
|
spawnerdef.after_dig_node = def.after_dig_node or function (pos, oldnode)
|
|
|
|
-- -- Might spawn a mob -- --
|
|
if spawnerdef.mob and spawnerdef.last_spawn
|
|
and spawnerdef.last_spawn > 0 and not peaceful then
|
|
local n = spawnerdef.last_spawn or 1
|
|
for i=1,n do
|
|
if ( math.random(1,3) == 1 ) then
|
|
local sp = {
|
|
x = pos.x + math.random(-2,2),
|
|
y = pos.y + 1,
|
|
z = pos.x + math.random(-2,2),
|
|
}
|
|
minetest.add_entity(sp, spawnerdef.mob)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- -- Drops things (or not) -- --
|
|
local drops = {}
|
|
|
|
-- Might drop nothing
|
|
if spawnerdef.last_spawn and spawnerdef.last_spawn > 0 then
|
|
local n = spawnerdef.last_spawn
|
|
for i=1,n do
|
|
table.insert(drops, "")
|
|
end
|
|
end
|
|
|
|
-- Might drop diamond or other small rare things
|
|
if minetest.get_modpath('default') and drop_use_default then
|
|
table.insert(drops, "default:diamond")
|
|
table.insert(drops, "default:obsidian_shard")
|
|
table.insert(drops, "default:mese_crystal_fragment ")
|
|
end
|
|
|
|
-- Might drop something from treasurer
|
|
if minetest.get_modpath('treasurer') and drop_use_treasurer then
|
|
local n = 2
|
|
local treasure = treasurer.select_random_treasures(n,1,5,spawner_treasures_types)
|
|
for _,itemstack in ipairs(treasure) do
|
|
table.insert(drops, itemstack)
|
|
end
|
|
end
|
|
|
|
-- if chance == 1 and minetest.get_modpath('cursedstone') and opt.spawner_use_cursedstone then
|
|
|
|
-- Drop something from the drops table
|
|
if drops ~= "" then
|
|
local i = math.random(1,#drops)
|
|
minetest.add_item(pos, drops[i])
|
|
end
|
|
end
|
|
|
|
|
|
local ttr = spawnerdef.base_texture
|
|
local tls = {ttr,ttr,ttr..'^' .. spawnerdef.glyph } --sides
|
|
if spawnerdef.tiles then
|
|
tls = spawnerdef.tiles
|
|
elseif spawnerdef.texture_face == "all" then
|
|
tls = {ttr..'^' .. spawnerdef.glyph }
|
|
end
|
|
local drw = spawnerdef.drawtype
|
|
local nbdx = spawnerdef.node_box
|
|
|
|
minetest.register_node(spawnerdef.name, {
|
|
description = spawnerdef.description,
|
|
_doc_items_longdesc = spawnerdef.longdesc,
|
|
paramtype = "light",
|
|
tiles = tls,
|
|
is_ground_content = false,
|
|
drawtype = drw,
|
|
node_box = nbdx,
|
|
groups = {cracky = 3, stone = 2},
|
|
drop = "",
|
|
after_dig_node = spawnerdef.after_dig_node
|
|
})
|
|
|
|
minetest.register_alias("mobs:spawner_"..spawnerdef.id, spawnerdef.name)
|
|
|
|
if spawnerdef.mob and not peaceful then
|
|
mobs:spawn_specific(
|
|
spawnerdef.mob,
|
|
{spawnerdef.name},
|
|
spawnerdef.neighbors,
|
|
spawnerdef.min_light,
|
|
spawnerdef.max_light,
|
|
spawnerdef.interval,
|
|
spawnerdef.chance,
|
|
spawnerdef.range,
|
|
spawnerdef.min_height,
|
|
spawnerdef.max_height,
|
|
spawnerdef.day_toggle,
|
|
spawnerdef.on_spawn
|
|
)
|
|
end
|
|
|
|
end
|