mobs_creatures/mobs_api.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