local PATH_FIND_ALGORITHM PATH_FIND_ALGORITHM = "Dijkstra" PyuTest.ENTITY_BLOOD_AMOUNT = 6 PyuTest.HUMAN_LIKE_CBOX = { -0.25, -1, -0.25, 0.25, 1, 0.25 } PyuTest.SMALL_ANIMAL_CBOX = {-0.25, -0.5, -0.25, 0.25, 0.15, 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 core.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 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_entity(target) 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 = target if state.target.object == nil then return end state.target.position = state.target.object:get_pos() state.target.path = core.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 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() self:path_find_entity(PyuTest.get_nearest_entity(obj, pos, cfg.view_range, follow_only_player, false)) end function class:on_punch() local pos = self.object:get_pos() core.sound_play({ name = self.options.sounds.hurt, pos = pos }) core.add_particlespawner({ amount = 8, time = 0.4, minexptime = 0.4, maxexptime = 0.8, minsize = 1.5, maxsize = 1.62, vertical = false, glow = core.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 function class:on_death() local pos = self.object:get_pos() for _, v in pairs(self.options.drops) do PyuTest.drop_item(pos, v) end end function class:on_step(dtime, moveresult) self.dtime = dtime self.moveresult = moveresult local obj = self.object local cfg = self.options 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) self:do_physics() if not self.state.action_timer then core.after(1, function () self.state.action_timer = true core.after(1, function () self.action_timer = false end) end) end -- follow_player if self.options.follow_player then self:path_find_nearest_entity(false) end -- follow_items for o in core.objects_inside_radius(obj:get_pos(), cfg.view_range) do if o ~= obj then for _, v in pairs(self.options.follow_items) do local i = o:get_wielded_item() if i:get_name() == v then self:path_find_entity(o) end end end end -- attack_entities if self.state.action_timer then for o in core.objects_inside_radius(obj:get_pos(), cfg.hit_range) do if o ~= obj then for _, v in pairs(cfg.attack_entities) do local lua_entity = o:get_luaentity() if (v == "player" and o:is_player()) or (lua_entity and v == lua_entity.name) then PyuTest.deal_damage(o, cfg.hit_damage * dtime, { type = "punch", object = obj }) end end end end end end PyuTest.make_mob = function(name, properties, options) local default_options = { max_jump = 1, max_drop = 8, speed = 3, hit_range = 3, hit_damage = 3, view_range = 10, gravity = true, gravity_multiplier = 1, health_regen = true, follow_player = false, follow_items = {}, attack_entities = {}, 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 core.register_entity(name, setmetatable({ initial_properties = PyuTest.util.tableconcat(properties, { hp_max = properties.hp_max or 20, physical = true, collide_with_objects = false, 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 = { action_timer = false, target = { object = nil, position = nil, path = nil, pathindex = nil } }, }, { __index = class })) end