local PATH_FIND_ALGORITHM PATH_FIND_ALGORITHM = "Dijkstra" -- PATH_FIND_ALGORITHM = "A*_noprefetch" -- PATH_FIND_ALGORITHM = "A*" PyuTest.ENTITY_BLOOD_AMOUNT = 6 PyuTest.HUMAN_LIKE_CBOX = {-0.25, -1, -0.25, 0.25, 1, 0.25} PyuTest.get_nearest_entity = function (entity, pos, range, only_player, dont_ignore_allies) local closet_distance = math.huge local nearest local function set_nearest_and_distance(n, d) nearest = n closet_distance = d end for obj in minetest.objects_inside_radius(pos, range) do local dist = vector.distance(pos, obj:get_pos()) if dist < closet_distance and obj ~= entity then if only_player then if obj:is_player() then set_nearest_and_distance(obj, dist) end else local e = obj:get_luaentity() -- Ignore items if e then if e.name ~= "__builtin:item" then if not dont_ignore_allies then local self = entity:get_luaentity() if self then if self.name ~= e.name then set_nearest_and_distance(obj, dist) end end end end else set_nearest_and_distance(obj, dist) end end end end return nearest end PyuTest.register_entity_spawn = function (name, entity, def) if def == nil then error("Table expected for options!") end minetest.register_node(name, { description = "Entity Spawner", groups = { not_in_creative_inventory = 1, }, drawtype = "airlike", walkable = false, pointable = false }) minetest.register_decoration({ sidelen = 80, decoration = name, deco_type = "simple", place_on = def.place_on, spawn_by = def.spawn_by, num_spawn_by = def.num_spawn_by, fill_ratio = def.fill_ratio or 0.0008, y_max = def.y_max or 31000, y_min = def.y_min or -31000, biomes = {} }) minetest.register_lbm({ name = name.."_spawn", run_at_every_load = true, nodenames = {name}, action = function (pos) minetest.remove_node(pos) local min = def.min or 1 local max = def.max or 1 if max == 1 then minetest.add_entity(pos, entity) else for _ = min, math.random(min, max) do minetest.add_entity(pos, entity) end end end }) end local class = {} function class:do_physics() local obj = self.object local state = self.state local cfg = self.options local moveresult = self.moveresult local pos = obj:get_pos() if cfg.gravity then if not moveresult.touching_ground then end end end function class:path_find_nearest_entity(follow_only_player) local obj = self.object local state = self.state local cfg = self.options local pos = obj:get_pos() if state.target.path == nil then state.target.object = PyuTest.get_nearest_entity(obj, pos, cfg.sight_range, follow_only_player) if state.target.object == nil then return end state.target.position = state.target.object:get_pos() state.target.path = minetest.find_path(pos, state.target.position, cfg.view_range, cfg.max_jump, cfg.max_drop, PATH_FIND_ALGORITHM) state.target.pathindex = 1 else if state.target.pathindex == #state.target.path then state.target.path = nil state.target.object = nil state.target.position = nil state.target.pathindex = nil else local p = state.target.path[state.target.pathindex] -- obj:set_velocity(p * dtime) obj:move_to(p, true) state.target.pathindex = state.target.pathindex + 1 end end end PyuTest.make_mob = function (name, properties, options) local default_options = { ai = "dummy", max_jump = 1, max_drop = 8, speed = 3, view_range = 3, sight_range = 10, gravity = true, gravity_multiplier = 1, health_regen = true, sounds = { hurt = "pyutest-entity-hurt" }, drops = {} } local cfg = {} for k, v in pairs(options) do cfg[k] = v end for k, v in pairs(default_options) do if cfg[k] == nil then cfg[k] = v end end local collisionbox = properties.collisionbox or PyuTest.HUMAN_LIKE_CBOX minetest.register_entity(name, setmetatable({ initial_properties = PyuTest.util.tableconcat(properties, { hp_max = properties.hp_max or 20, physical = true, collide_with_objects = true, stepheight = properties.stepheight or 1.1, collisionbox = collisionbox, selectionbox = properties.selectionbox or collisionbox, show_on_minimap = properties.show_on_minimap or true, infotext = "", }), options = cfg, state = { target = { object = nil, position = nil, path = nil, pathindex = nil } }, on_step = function (self, dtime, moveresult) self.dtime = dtime self.moveresult = moveresult local ai = self.options.ai local obj = self.object local p = obj:get_properties() local hp = obj:get_hp() if self.options.health_regen and not (hp >= p.hp_max) then obj:set_hp(hp + 0.5) end if obj:get_hp() > p.hp_max then obj:set_hp(p.hp_max) end p.infotext = string.format("Mob Health: %d/%d", obj:get_hp(), p.hp_max) obj:set_properties(p) if ai == nil or ai == "dummy" then return end self:do_physics() if ai == "follownearest" then self:path_find_nearest_entity(false) end end, on_punch = function (self) local pos = self.object:get_pos() minetest.sound_play(self.options.sounds.hurt, {pos = pos}) minetest.add_particlespawner({ amount = 8, time = 0.4, minexptime = 0.4, maxexptime = 0.8, minsize = 1.5, maxsize = 1.62, vertical = false, glow = minetest.LIGHT_MAX, collisiondetection = false, texture = "pyutest-blood.png", minpos = pos, maxpos = pos, minvel = vector.new(-1, -1, 1), maxvel = vector.new(1, 1, 1), }) end, on_death = function (self) local pos = self.object:get_pos() for _, v in pairs(self.options.drops) do minetest.add_item(pos, v) end end }, {__index = class})) end