diff --git a/api.lua b/api.lua new file mode 100644 index 0000000..2100245 --- /dev/null +++ b/api.lua @@ -0,0 +1,500 @@ + +local spawners = {} +spawner_count = 0 + +natspawner.on_mob_die = function(self, pos) + if self.spawn and self.spawn.pos then + local meta = minetest.get_meta(self.spawn.pos) + meta:set_int("entity_killed_count", meta:get_int("entity_killed_count") + 1) + if self.variation then + meta:set_int("spawned_variation_count", meta:get_int("spawned_variation_count") - 1) + end + end +end + +local function spawn(pos, force) + + local meta = minetest.get_meta(pos) + local spawner_name = meta:get_string("spawner_name") + + if spawner_name then + local spawner = spawners[spawner_name] + local replacement_node_name = meta:get_string("replaced_node_name") + + local timer = minetest.get_node_timer(pos) + local interval = math.random(spawner.min_spawn_interval, spawner.max_spawn_interval) + + if replacement_node_name and replacement_node_name ~= "" then + + -- Check if variations are forced + local force_variation = false + --minetest.log("Spawner data: "..dump(spawners[spawner_name].replacement_nodes[replacement_node_name])) + if spawners[spawner_name].replacement_nodes[replacement_node_name].variations_only == true then + force_variation = true + end + + -- Entity names for which we are going to search + local search_names = {} + local variation_names = {} + local raw_var_names = spawners[spawner_name].replacement_nodes[replacement_node_name].variations + if raw_var_names then + for name,_ in pairs(raw_var_names) do + variation_names[#variation_names + 1] = name + end + end + -- If variations are forced, only take the variation names + if force_variation == true then + search_names = variation_names + else + search_names = spawners[spawner_name].entities.all_entity_names + end + + -- Search for nearby players and entities + local objects = minetest.get_objects_inside_radius(pos, 10) + local entity_count = 0 + for _,object in pairs(objects) do + if object then + if object:is_player() and not force then + minetest.log("Player too close") + -- Re-schedule + timer:start(interval) + minetest.log("Next spawning scheduled in "..interval.." seconds") + return + elseif object:get_luaentity() + and object:get_luaentity().entity_name then + + -- Check if the found entity has the name of one the entities + -- that this spawner cares about + for i = 1, #search_names do + if object:get_luaentity().entity_name == search_names[i] then + entity_count = entity_count + 1 + break + end + end + + end + end + end + + -- Create a list of names we are to spawn + local spawn_names = {} + if force_variation then + spawn_names = variation_names + elseif #variation_names > 0 then + spawn_names[#spawn_names + 1] = spawners[spawner_name].entities.default + for i = 1, #variation_names do + spawn_names[#spawn_names + 1] = variation_names[i] + end + else + spawn_names[#spawn_names + 1] = spawners[spawner_name].entities.default + end + + -- Choose an entity name. + local is_variation = false + + if force_variation == true then + entity_name = spawn_names[math.random(1, #spawn_names)] + --minetest.log("New entity name: "..dump(entity_name)) + is_variation = true + minetest.log("Spawning variation "..entity_name.." (forced)") + else + local variation_chance = math.random(1, 10) + if variation_chance > 7 then + entity_name = spawn_names[math.random(1, #spawn_names)] + -- Increase variation count + meta:set_int("spawned_variation_count", meta:get_int("spawned_variation_count") + 1) + is_variation = true + minetest.log("Spawned variation count: "..meta:get_int("spawned_variation_count")) + minetest.log("Spawning variation "..entity_name.." (not forced)") + else + entity_name = spawners[spawner_name].entities.default + end + end + + -- Validation: enforce entity max spawn count + local max_mob_count = spawner.max_mob_count + + if force_variation then + --minetest.log("Entity name: "..dump(entity_name)) + --minetest.log("Mob data: "..dump(spawners[spawner_name] +-- .replacement_nodes[replacement_node_name] +-- .variations[entity_name])) + max_mob_count = spawners[spawner_name] + .replacement_nodes[replacement_node_name] + .variations[entity_name] + .max_count or spawner.max_mob_count + entity_count = meta:get_int("spawned_variation_count") + end + + minetest.log("Entity count: "..dump(entity_count)) + minetest.log("Max mob count: "..dump(max_mob_count)) + --minetest.log("Force: "..dump(force)) + + if force or (entity_count <= max_mob_count) then + + -- Spawn + local spawn_pos = {x=pos.x + math.random(0, spawner.spawn_radius), y=pos.y+3, z=pos.z + math.random(0, spawner.spawn_radius)} + -- Check spawn position - if not air, then spawn just above the spawner + local spawn_node = minetest.get_node_or_nil(spawn_pos) + if spawn_node and spawn_node.name ~= "air" then + spawn_pos = pos + end + + minetest.log("Spawning "..entity_name.." at pos "..minetest.pos_to_string(spawn_pos)) + local entity = minetest.add_entity(spawn_pos, entity_name) + if entity then + entity:get_luaentity().entity_name = entity_name + entity:get_luaentity().spawn = { + pos = pos + } + + if is_variation == true then + entity:get_luaentity().variation = true + end + end + minetest.log("Next spawning scheduled in "..interval.." seconds") + else + minetest.log("Max spawn limit reached") + -- Re-calulate interval using deactivation times + interval = math.random(spawner.min_deactivation_time, spawner.max_deactivation_time) + minetest.log("Deactivating spawner for "..interval.." seconds") + end + end + + -- Re-schedule + timer:start(interval) + end +end + +natspawner.register_spawner = function(spawner_name, def) + + spawners[spawner_name] = { + entities = def.entities, + replacement_nodes = def.node_replacement_map, + min_player_distance = def.min_player_distance or 10, + max_spawn_interval = def.max_spawn_interval or 300, --300 + min_spawn_interval = def.min_spawn_interval or 120, --120 + spawn_radius = def.spawn_radius or 15, + min_kill_count = def.min_kill_count or 20, + max_kill_count = def.max_kill_count or 35, + min_deactivation_time = def.min_deactivation_time or 60, + max_deactivation_time = def.max_deactivation_time or 120, + max_mob_count = def.max_mob_count or 15, + spawn_on_dig = def.spawn_on_dig + } + + -- Process all names + local all_names = {} + all_names[#all_names + 1] = def.entities.default + for i = 1, #def.entities.others do + all_names[#all_names + 1] = def.entities.others[i] + end + spawners[spawner_name].entities.all_entity_names = all_names + + spawner_count = spawner_count + 1 + + -- Register all spawners + for _,val in pairs(def.node_replacement_map) do + + minetest.register_node("natspawner:"..spawner_name..val.name, { + description = "Spawner", + drop = "natspawner:"..spawner_name.."_"..val.name, + tiles = val.tiles, + groups = {crumbly=2, soil = 2}, + sounds = default.node_sound_sand_defaults(), + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("spawner_name", spawner_name) + meta:set_int("entity_spawn_count", 0) + meta:set_int("entity_killed_count", 0) + meta:set_int("spawned_variation_count", 0) + meta:set_int("max_variation_count", + math.random(def.min_variation_count or 0, def.max_variation_count or 0)) + meta:set_int("next_deactivation_count", + math.random(def.min_kill_count, def.max_kill_count)) + meta:set_int("next_deactivation_time", + math.random(def.min_deactivation_time, def.max_deactivation_time)) + + local timer = minetest.get_node_timer(pos) + timer:start(def.min_spawn_interval) + end, + on_timer = function(pos) + spawn(pos) + end, + on_dig = function(pos, node, digger) + local meta = minetest.get_meta(pos) + local entity_killed_count = meta:get_int("entity_killed_count") + local next_deactivation_count = meta:get_int("next_deactivation_count") + if (entity_killed_count < next_deactivation_count) then + if def.spawn_on_dig then + spawn(pos, true) + end + minetest.chat_send_player(digger:get_player_name(), "You have killed "..entity_killed_count.." enemies!") + return false + else + minetest.node_dig(pos, node, digger) + end + end + }) + + end +end + +natspawner.register_spawner("zombie", { + entities = { + default = "natspawner:zombie", + others = { + "natspawner:giant_zombie", + "natspawner:ice_zombie", + "natspawner:sand_zombie", + "natspawner:sand_zombie_small" + }, + }, + node_replacement_map = { + ["default:stone"] = { + name = "stone_spawner", + tiles = {"default_stone.png^[cracko:1:2", "default_stone.png", {name = "default_stone.png"}} + }, + ["default:sand"] = { + name = "sand_spawner", + tiles = {"default_sand.png^[cracko:1:2", "default_sand.png", {name = "default_sand.png"}}, + variations = { + ["natspawner:sand_zombie"] = {}, + ["natspawner:sand_zombie_small"] = { + max_count = 5 + } + }, + variations_only = true + }, + ["default:desert_sand"] = { + name = "desert_sand_spawner", + tiles = {"default_desert_sand.png^[cracko:1:2", "default_desert_sand.png", {name = "default_desert_sand.png"}}, + variations = { + ["natspawner:sand_zombie"] = {}, + ["natspawner:sand_zombie_small"] = { + max_count = 5 + } + }, + variations_only = true + }, + ["default:ice"] = { + name = "ice_spawner", + tiles = {"default_ice.png^[cracko:1:2", "default_ice.png", {name = "default_ice.png"}}, + variations = { + ["natspawner:ice_zombie"] = { + max_count = 15, + min_count = 10 + } + }, + variations_only = true + }, + ["default:snowblock"] = { + name = "snowblock_spawner", + tiles = {"default_snow.png^[cracko:1:2", "default_snow.png", {name = "default_snow.png"}}, + variations = { + ["natspawner:ice_zombie"] = { + max_count = 15, + min_count = 10 + } + }, + variations_only = true + }, + ["default:dirt_with_grass"] = { + name = "dirt_with_grass_spawner", + tiles = {"default_grass.png^[cracko:1:2", "default_dirt.png", {name = "default_dirt.png^default_grass_side.png"}}}, + ["default:dirt_with_dry_grass"] = { + name = "dirt_with_dry_grass_spawner", + tiles = {"default_dry_grass.png^[cracko:1:2", "default_dirt.png", {name = "default_dirt.png^default_dry_grass_side.png"}}}, + ["default:dirt_with_snow"] = { + name = "dirt_with_snow_spawner", + tiles = {"default_snow.png^[cracko:1:2", "default_dirt.png", {name = "default_dirt.png^default_snow_side.png"}}, + variations = { + ["natspawner:ice_zombie"] = { + max_count = 15, + min_count = 10 + } + }, + variations_only = true + }, + ["default:dirt_with_rainforest_litter"] = { + name = "dirt_with_rainforest_litter_spawner", + tiles = {"default_rainforest_litter.png^[cracko:1:2", "default_dirt.png", {name = "default_dirt.png^default_rainforest_litter_side.png"}} + }, + ["default:dirt_with_coniferous_litter"] = { + name = "dirt_with_coniferous_litter_spawner", + tiles = {"default_coniferous_litter.png^[cracko:1:2", "default_dirt.png", {name = "default_dirt.png^default_coniferous_litter_side.png"}} + }, + ["default:permafrost"] = { + name = "permafrost_spawner", + tiles = {"default_permafrost.png^[cracko:1:2", "default_permafrost.png", {name = "default_permafrost.png"}}, + variations = { + ["natspawner:ice_zombie"] = {} + } + }, + ["default:permafrost_with_stones"] = { + name = "permafrost_with_stones_spawner", + tiles = {"default_permafrost.png^default_stones.png^[cracko:1:2", "default_permafrost.png", {name = "default_permafrost.png"}}, + variations = { + ["natspawner:ice_zombie"] = {} + } + }, + ["default:permafrost_with_moss"] = { + name = "permafrost_with_moss_spawner", + tiles = {"default_moss.png^[cracko:1:2", "default_permafrost.png", {name = "default_permafrost.png^default_moss_side.png"}}, + variations = { + ["natspawner:ice_zombie"] = {} + } + } + }, + min_player_distance = 5, --20 + max_mob_count = 5, --15 + max_spawn_interval = 10, --300 + min_spawn_interval = 5, --120 + spawn_radius = 5, + min_kill_count = 5, + max_kill_count = 10, + min_deactivation_time = 5, + max_deactivation_time = 5, + spawn_on_dig = true +}) + +local perl1 = {SEED1 = 9130, OCTA1 = 3, PERS1 = 0.5, SCAL1 = 250} -- Values should match minetest mapgen V6 desert noise. + +local function hlp_fnct(pos, name) + local n = minetest.get_node_or_nil(pos) + if n and n.name and n.name == name then + return true + else + return false + end +end +local function ground(pos, old) + local p2 = pos + while hlp_fnct(p2, "air") do + p2.y = p2.y -1 + end + if p2.y < old.y then + return p2 + else + return old + end +end + +local function is_air_or_buildable(node) + return node + and node.name + and (node.name == "air" + or minetest.registered_nodes[node.name].buildable_to == true) +end + +minetest.register_on_generated(function(minp, maxp, seed) + + -- No spawners registered + if spawner_count == 0 then return end + + if maxp.y < 0 then return end + math.randomseed(seed) + local cnt = 0 + + local perlin1 = minetest.env:get_perlin(perl1.SEED1, perl1.OCTA1, perl1.PERS1, perl1.SCAL1) + local noise1 = perlin1:get2d({x=minp.x,y=minp.y})--,z=minp.z}) + + if noise1 > 0.25 or noise1 < -0.26 then + local mpos = {x=math.random(minp.x,maxp.x), y=math.random(minp.y,maxp.y), z=math.random(minp.z,maxp.z)} + + local p2 = minetest.find_node_near(mpos, 25, { + "default:dirt_with_grass", + "default:dirt_with_snow", + "default:dirt_with_coniferous_litter", + "default:permafrost", + "default:permafrost_with_moss", + "default:permafrost_with_stones", + "default:sand", + "default:desert_sand" + }) + while p2 == nil and cnt < 10 do + cnt = cnt+1 + mpos = {x=math.random(minp.x,maxp.x), y=math.random(minp.y,maxp.y), z=math.random(minp.z,maxp.z)} + p2 = minetest.find_node_near(mpos, 25, { + "default:dirt_with_grass", + "default:dirt_with_snow", + "default:dirt_with_coniferous_litter", + "default:permafrost", + "default:permafrost_with_moss", + "default:permafrost_with_stones", + "default:sand", + "default:desert_sand" + }) + end + if p2 == nil then return end + if p2.y < 0 then return end + + local off = 0 + + -- Simpler finding routine - check if node immediately above is air, + -- and if node 16 blocks above is air + minetest.log("Checking pos to spawn: "..minetest.pos_to_string(p2)) + local next_node_above = minetest.get_node_or_nil({x=p2.x, y=p2.y+1, z=p2.z}) + minetest.log("Found next node above: "..dump(next_node_above)) + local next_mapblock_above = minetest.get_node_or_nil({x=p2.x, y=p2.y+16, z=p2.z}) + minetest.log("Found next node above mapblock: "..dump(next_mapblock_above)) + + if is_air_or_buildable(next_node_above) and is_air_or_buildable(next_mapblock_above) then + + -- Choose a spawner + for i = 1, spawner_count do + local spawner_name = next(spawners) + local chance = math.random(1, spawner_count) + + if chance == i then + -- Create spawner + minetest.after(0.8, function(spawner_name, pos) + local node_map = spawners[spawner_name].replacement_nodes + local node_to_replace = minetest.get_node(pos) + if node_map[node_to_replace.name] then + minetest.log("Replacing "..node_to_replace.name.." with "..spawner_name) + minetest.log("Creating advanced spawner at "..minetest.pos_to_string(pos)) + minetest.set_node(pos, {name="natspawner:"..spawner_name..node_map[node_to_replace.name].name}) + local meta = minetest.get_meta(pos) + meta:set_string("replaced_node_name", node_to_replace.name) + else + minetest.log("Unable to find replacement for node '"..dump(node_to_replace.name).."'") + end + end, spawner_name, p2) + end + end + end + + -- local opos1 = {x=p2.x+22,y=p2.y-1,z=p2.z+22} + -- local opos2 = {x=p2.x+22,y=p2.y-1,z=p2.z} + -- local opos3 = {x=p2.x,y=p2.y-1,z=p2.z+22} + -- local opos1_n = minetest.get_node_or_nil(opos1) + -- local opos2_n = minetest.get_node_or_nil(opos2) + -- local opos3_n = minetest.get_node_or_nil(opos3) + -- if opos1_n and opos1_n.name and opos1_n.name == "air" then + -- p2 = ground(opos1, p2) + -- end + -- if opos2_n and opos2_n.name and opos2_n.name == "air" then + -- p2 = ground(opos2, p2) + -- end + -- if opos3_n and opos3_n.name and opos3_n.name == "air" then + -- p2 = ground(opos3, p2) + -- end + -- p2.y = p2.y - 3 + -- if p2.y < 0 then p2.y = 0 end + --if minetest.find_node_near(p2, 25, {"default:water_source"}) ~= nil or minetest.find_node_near(p2, 22, {"default:dirt_with_grass"}) ~= nil or minetest.find_node_near(p2, 52, {"default:sandstonebrick"}) ~= nil then return end + + --minetest.after(0.8,make,p2) + end +end) + + + + +mobs:register_egg("natspawner:zombie", "Zombie", "zombie_head.png", 0) + +minetest.register_craftitem("natspawner:rotten_flesh", { + description = "Rotten Flesh", + inventory_image = "mobs_rotten_flesh.png", + on_use = minetest.item_eat(-5), +}) diff --git a/init.lua b/init.lua index 76cd2bf..3f096b7 100644 --- a/init.lua +++ b/init.lua @@ -1,523 +1,7 @@ natspawner = {} -local spawners = {} -spawner_count = 0 +local path = minetest.get_modpath("natspawner") -mobs:register_mob("natspawner:zombie", { - type = "monster", - passive = false, - attack_type = "dogfight", - damage = 4, - reach = 2, - hp_min = 12, - hp_max = 35, - armor = 150, - collisionbox = {-0.25, -1, -0.3, 0.25, 0.75, 0.3}, - visual = "mesh", - mesh = "creatures_mob.x", - textures = { - {"mobs_zombie.png"}, - }, - visual_size = {x=1, y=1}, - makes_footstep_sound = true, - sounds = { - random = "mobs_zombie.1", - damage = "mobs_zombie_hit", - attack = "mobs_zombie.3", - death = "mobs_zombie_death", - }, - walk_velocity = 0.5, - run_velocity = 1.75, - jump = true, - floats = 0, - view_range = 12, - drops = { - {name = "zombie:rotten_flesh", - chance = 2, min = 3, max = 5,}, - }, - water_damage = 0, - lava_damage = 1, - light_damage = 0, - animation = { - speed_normal = 10, speed_run = 15, - stand_start = 0, stand_end = 79, - walk_start = 168, walk_end = 188, - run_start = 168, run_end = 188, --- punch_start = 168, punch_end = 188, - }, - on_rightclick = function(self, clicker) - minetest.log(dump(self)) - end, - on_die = function(self, pos) - if self.spawn and self.spawn.pos then - local meta = minetest.get_meta(self.spawn.pos) - meta:set_int("entity_killed_count", meta:get_int("entity_killed_count") + 1) - if self.variation then - meta:set_int("spawned_variation_count", meta:get_int("spawned_variation_count") - 1) - end - end - end -}) - -local function spawn(pos, force) - - local meta = minetest.get_meta(pos) - local spawner_name = meta:get_string("spawner_name") - - if spawner_name then - local spawner = spawners[spawner_name] - local replacement_node_name = meta:get_string("replaced_node_name") - - local timer = minetest.get_node_timer(pos) - local interval = math.random(spawner.min_spawn_interval, spawner.max_spawn_interval) - - -- Check for players nearby - local objects = minetest.get_objects_inside_radius(pos, 25) - - --local entity_spawn_count = meta:get_int("entity_spawn_count") - - local entity_count = 0 - for _,object in pairs(objects) do - if object then - if object:is_player() and not force then - minetest.log("Player too close") - -- Re-schedule - timer:start(interval) - minetest.log("Next spawning scheduled in "..interval.." seconds") - return - elseif object:get_luaentity() - and object:get_luaentity().entity_name - and object:get_luaentity().entity_name == spawner.entity_name then - entity_count = entity_count + 1 - end - end - end - - if replacement_node_name and replacement_node_name ~= "" then - local max_mob_count = spawner.max_mob_count - -- Force variations only - local force_variation = false - minetest.log("Spawner data: "..dump(spawners[spawner_name].replacement_nodes[replacement_node_name])) - if spawners[spawner_name].replacement_nodes[replacement_node_name].variations_only == true then - force_variation = true - end - - if force_variation - and spawners[spawner_name].replacement_nodes[replacement_node_name].max_count then - max_mob_count = spawners[spawner_name].replacement_nodes[replacement_node_name].max_count - minetest.log("The max count: "..dump(max_mob_count)) - entity_count = meta:get_int("spawned_variation_count") - end - - minetest.log("Entity count: "..dump(entity_count)) - minetest.log("Max mob count: "..dump(max_mob_count)) - minetest.log("Force: "..dump(force)) - - -- Check for amount nearby - if force or (entity_count <= max_mob_count) then - -- Spawn - local spawn_pos = {x=pos.x + math.random(0, spawner.spawn_radius), y=pos.y+3, z=pos.z + math.random(0, spawner.spawn_radius)} - -- Check spawn position - if not air, then spawn just above the spawner - local spawn_node = minetest.get_node_or_nil(spawn_pos) - if spawn_node and spawn_node.name ~= "air" then - spawn_pos = pos - end - minetest.log("Spawning "..spawner.entity_name.." at pos "..minetest.pos_to_string(spawn_pos)) - local entity = minetest.add_entity(spawn_pos, spawner.entity_name) - if entity then - entity:get_luaentity().entity_name = spawner.entity_name - entity:get_luaentity().spawn = { - pos = pos - } - - local max_variation_count = meta:get_int("max_variation_count") - local spawned_variation_count = meta:get_int("spawned_variation_count") - - -- Spawn a variation with 30% chance - local variation_chance = math.random(1, 10) - local variations = spawners[spawner_name].replacement_nodes[replacement_node_name].variations - - minetest.log("") - - if (variations - and spawned_variation_count < max_variation_count - and variation_chance > 7) or force_variation == true then - - -- Choose a variation - local index = math.random(1, #variations) - if spawners[spawner_name].variations.data[variations[index]] ~= nil then - local registered_variation = spawners[spawner_name].variations.data[variations[index]] - - minetest.log("Spawning variation "..dump(variations[index])) - - entity:get_luaentity().variation = true - -- Velocity - if registered_variation.walk_velocity then - entity:get_luaentity().walk_velocity = - registered_variation.walk_velocity - end - - if registered_variation.run_velocity then - entity:get_luaentity().run_velocity = - registered_variation.run_velocity - end - - -- View range - if registered_variation.view_range then - entity:get_luaentity().view_range = - registered_variation.view_range - end - - -- HP - if registered_variation.hp_min and registered_variation.hp_max then - local hp = - math.random(registered_variation.hp_min, registered_variation.hp_max) - entity:get_luaentity().hp_max = hp - entity:get_luaentity().health = hp - entity:get_luaentity().old_health = hp - end - - -- Damage - if registered_variation.damage then - entity:get_luaentity().damage = registered_variation.damage - end - - if registered_variation.reach then - entity:get_luaentity().reach = registered_variation.reach - end - - -- Armor - if registered_variation.armor then - entity:set_armor_groups({immortal = 1, fleshy = registered_variation.armor}) - end - - if registered_variation.knock_back then - entity:get_luaentity().knock_back = registered_variation.knock_back - end - - if registered_variation.visual_size then - -- Size properties - entity:set_properties({ - visual_size = registered_variation.visual_size, - collisionbox = registered_variation.collisionbox, - stepheight = registered_variation.stepheight - }) - end - - -- Textures - if registered_variation.textures then - entity:set_properties({ - textures = registered_variation.textures - }) - end - - -- Increase variation count - meta:set_int("spawned_variation_count", meta:get_int("spawned_variation_count") + 1) - end - end - end - minetest.log("Spawned variation count: "..meta:get_int("spawned_variation_count")) - minetest.log("Next spawning scheduled in "..interval.." seconds") - - else - minetest.log("Max spawn limit reached") - -- Re-calulate interval using deactivation times - interval = math.random(spawner.min_deactivation_time, spawner.max_deactivation_time) - minetest.log("Deactivating spawner for "..interval.." seconds") - end - end - - -- Re-schedule - timer:start(interval) - end -end - -natspawner.register_spawner = function(spawner_name, def) - - spawners[spawner_name] = { - entity_name = def.entity_name, - variations = { - data = def.entity_variations - }, - replacement_nodes = def.node_replacement_map, - min_player_distance = def.min_player_distance or 10, - max_spawn_interval = def.max_spawn_interval or 300, --300 - min_spawn_interval = def.min_spawn_interval or 120, --120 - spawn_radius = def.spawn_radius or 15, - min_kill_count = def.min_kill_count or 20, - max_kill_count = def.max_kill_count or 35, - min_deactivation_time = def.min_deactivation_time or 60, - max_deactivation_time = def.max_deactivation_time or 120, - max_mob_count = def.max_mob_count or 15, - spawn_on_dig = def.spawn_on_dig - } - spawner_count = spawner_count + 1 - - -- Register all spawners - for _,val in pairs(def.node_replacement_map) do - - minetest.register_node("natspawner:"..spawner_name..val.name, { - description = "Spawner", - drop = "natspawner:"..spawner_name.."_"..val.name, - tiles = val.tiles, - groups = {crumbly=2, soil = 2}, - sounds = default.node_sound_sand_defaults(), - on_construct = function(pos) - local meta = minetest.get_meta(pos) - meta:set_string("spawner_name", spawner_name) - meta:set_int("entity_spawn_count", 0) - meta:set_int("entity_killed_count", 0) - meta:set_int("spawned_variation_count", 0) - meta:set_int("max_variation_count", - math.random(def.min_variation_count or 0, def.max_variation_count or 0)) - meta:set_int("next_deactivation_count", - math.random(def.min_kill_count, def.max_kill_count)) - meta:set_int("next_deactivation_time", - math.random(def.min_deactivation_time, def.max_deactivation_time)) - - local timer = minetest.get_node_timer(pos) - timer:start(def.min_spawn_interval) - end, - on_timer = function(pos) - spawn(pos) - end, - on_dig = function(pos, node, digger) - local meta = minetest.get_meta(pos) - local entity_killed_count = meta:get_int("entity_killed_count") - local next_deactivation_count = meta:get_int("next_deactivation_count") - if (entity_killed_count < next_deactivation_count) then - if def.spawn_on_dig then - spawn(pos, true) - end - minetest.chat_send_player(digger:get_player_name(), "You have killed "..entity_killed_count.." enemies!") - return false - else - minetest.node_dig(pos, node, digger) - end - end - }) - - end -end - -natspawner.register_spawner("zombie", { - entity_name = "natspawner:zombie", - entity_variations = { - ["giant_zombie"] = - { - visual_size = {x=2.75, y=2.5, z=2.75}, - collisionbox = {-0.63, -2.5, -0.75, 0.63, 1.88, 0.75}, - stepheight = 2, - walk_velocity = 1.5, - run_velocity = 3, - view_range = 20, - hp_min = 215, - hp_max = 315, - damage = 12, - reach = 4, - armor = 325, - knock_back = false, - }, - ["ice_zombie"] = { - textures = {"mobs_zombie_ice.png"} - }, - ["sand_zombie_small"] = { - textures = {"mobs_zombie_sand.png"}, - visual_size = {x=0.5, y=0.5, z=0.5}, - } - }, - node_replacement_map = { - ["default:stone"] = { - name = "stone_spawner", - tiles = {"default_stone.png^[cracko:1:2", "default_stone.png", {name = "default_stone.png"}} - }, - ["default:sand"] = { - name = "sand_spawner", - tiles = {"default_sand.png^[cracko:1:2", "default_sand.png", {name = "default_sand.png"}}, - variations = {"sand_zombie_small"} - }, - ["default:desert_sand"] = { - name = "desert_sand_spawner", - tiles = {"default_desert_sand.png^[cracko:1:2", "default_desert_sand.png", {name = "default_desert_sand.png"}} - }, - ["default:ice"] = { - name = "ice_spawner", - tiles = {"default_ice.png^[cracko:1:2", "default_ice.png", {name = "default_ice.png"}}, - variations = {"ice_zombie"}, - variations_only = true, - max_count = 15, - min_count = 10 - }, - ["default:snowblock"] = { - name = "snowblock_spawner", - tiles = {"default_snow.png^[cracko:1:2", "default_snow.png", {name = "default_snow.png"}}, - variations = {"ice_zombie"}, - variations_only = true, - max_count = 15, - min_count = 10 - }, - ["default:dirt_with_grass"] = {name = "dirt_with_grass_spawner", tiles = {"default_grass.png^[cracko:1:2", "default_dirt.png", {name = "default_dirt.png^default_grass_side.png"}}}, - ["default:dirt_with_dry_grass"] = {name = "dirt_with_dry_grass_spawner", tiles = {"default_dry_grass.png^[cracko:1:2", "default_dirt.png", {name = "default_dirt.png^default_dry_grass_side.png"}}}, - ["default:dirt_with_snow"] = { - name = "dirt_with_snow_spawner", - tiles = {"default_snow.png^[cracko:1:2", "default_dirt.png", {name = "default_dirt.png^default_snow_side.png"}}, - variations = {"ice_zombie"}, - variations_only = true, - max_count = 15, - min_count = 10 - }, - ["default:dirt_with_rainforest_litter"] = {name = "dirt_with_rainforest_litter_spawner", tiles = {"default_rainforest_litter.png^[cracko:1:2", "default_dirt.png", {name = "default_dirt.png^default_rainforest_litter_side.png"}}}, - ["default:dirt_with_coniferous_litter"] = {name = "dirt_with_coniferous_litter_spawner", tiles = {"default_coniferous_litter.png^[cracko:1:2", "default_dirt.png", {name = "default_dirt.png^default_coniferous_litter_side.png"}}}, - ["default:permafrost"] = {name = "permafrost_spawner", tiles = {"default_permafrost.png^[cracko:1:2", "default_permafrost.png", {name = "default_permafrost.png"}}}, - ["default:permafrost_with_stones"] = {name = "permafrost_with_stones_spawner", tiles = {"default_permafrost.png^default_stones.png^[cracko:1:2", "default_permafrost.png", {name = "default_permafrost.png"}}}, - ["default:permafrost_with_moss"] = {name = "permafrost_with_moss_spawner", tiles = {"default_moss.png^[cracko:1:2", "default_permafrost.png", {name = "default_permafrost.png^default_moss_side.png"}}} - }, - min_player_distance = 5, --20 - max_mob_count = 5, --15 - max_spawn_interval = 10, --300 - min_spawn_interval = 5, --120 - spawn_radius = 5, - min_kill_count = 5, - max_kill_count = 10, - min_deactivation_time = 5, - max_deactivation_time = 5, - spawn_on_dig = true -}) - -local perl1 = {SEED1 = 9130, OCTA1 = 3, PERS1 = 0.5, SCAL1 = 250} -- Values should match minetest mapgen V6 desert noise. - -local function hlp_fnct(pos, name) - local n = minetest.get_node_or_nil(pos) - if n and n.name and n.name == name then - return true - else - return false - end -end -local function ground(pos, old) - local p2 = pos - while hlp_fnct(p2, "air") do - p2.y = p2.y -1 - end - if p2.y < old.y then - return p2 - else - return old - end -end - -local function is_air_or_buildable(node) - return node - and node.name - and (node.name == "air" - or minetest.registered_nodes[node.name].buildable_to == true) -end - -minetest.register_on_generated(function(minp, maxp, seed) - - -- No spawners registered - if spawner_count == 0 then return end - - if maxp.y < 0 then return end - math.randomseed(seed) - local cnt = 0 - - local perlin1 = minetest.env:get_perlin(perl1.SEED1, perl1.OCTA1, perl1.PERS1, perl1.SCAL1) - local noise1 = perlin1:get2d({x=minp.x,y=minp.y})--,z=minp.z}) - - if noise1 > 0.25 or noise1 < -0.26 then - local mpos = {x=math.random(minp.x,maxp.x), y=math.random(minp.y,maxp.y), z=math.random(minp.z,maxp.z)} - - local p2 = minetest.find_node_near(mpos, 25, { - "default:dirt_with_grass", - "default:dirt_with_snow", - "default:dirt_with_coniferous_litter", - "default:permafrost", - "default:permafrost_with_moss", - "default:permafrost_with_stones", - "default:sand", - "default:desert_sand" - }) - while p2 == nil and cnt < 10 do - cnt = cnt+1 - mpos = {x=math.random(minp.x,maxp.x), y=math.random(minp.y,maxp.y), z=math.random(minp.z,maxp.z)} - p2 = minetest.find_node_near(mpos, 25, { - "default:dirt_with_grass", - "default:dirt_with_snow", - "default:dirt_with_coniferous_litter", - "default:permafrost", - "default:permafrost_with_moss", - "default:permafrost_with_stones", - "default:sand", - "default:desert_sand" - }) - end - if p2 == nil then return end - if p2.y < 0 then return end - - local off = 0 - - -- Simpler finding routine - check if node immediately above is air, - -- and if node 16 blocks above is air - minetest.log("Checking pos to spawn: "..minetest.pos_to_string(p2)) - local next_node_above = minetest.get_node_or_nil({x=p2.x, y=p2.y+1, z=p2.z}) - minetest.log("Found next node above: "..dump(next_node_above)) - local next_mapblock_above = minetest.get_node_or_nil({x=p2.x, y=p2.y+16, z=p2.z}) - minetest.log("Found next node above mapblock: "..dump(next_mapblock_above)) - - if is_air_or_buildable(next_node_above) and is_air_or_buildable(next_mapblock_above) then - - -- Choose a spawner - for i = 1, spawner_count do - local spawner_name = next(spawners) - local chance = math.random(1, spawner_count) - - if chance == i then - -- Create spawner - minetest.after(0.8, function(spawner_name, pos) - local node_map = spawners[spawner_name].replacement_nodes - minetest.log("Creating advanced spawner at "..minetest.pos_to_string(pos)) - local node_to_replace = minetest.get_node(pos) - minetest.log("Replacing "..node_to_replace.name.." with "..spawner_name) - minetest.set_node(pos, {name="natspawner:"..spawner_name..node_map[node_to_replace.name].name}) - local meta = minetest.get_meta(pos) - meta:set_string("replaced_node_name", node_to_replace.name) - end, spawner_name, p2) - end - end - end - - -- local opos1 = {x=p2.x+22,y=p2.y-1,z=p2.z+22} - -- local opos2 = {x=p2.x+22,y=p2.y-1,z=p2.z} - -- local opos3 = {x=p2.x,y=p2.y-1,z=p2.z+22} - -- local opos1_n = minetest.get_node_or_nil(opos1) - -- local opos2_n = minetest.get_node_or_nil(opos2) - -- local opos3_n = minetest.get_node_or_nil(opos3) - -- if opos1_n and opos1_n.name and opos1_n.name == "air" then - -- p2 = ground(opos1, p2) - -- end - -- if opos2_n and opos2_n.name and opos2_n.name == "air" then - -- p2 = ground(opos2, p2) - -- end - -- if opos3_n and opos3_n.name and opos3_n.name == "air" then - -- p2 = ground(opos3, p2) - -- end - -- p2.y = p2.y - 3 - -- if p2.y < 0 then p2.y = 0 end - --if minetest.find_node_near(p2, 25, {"default:water_source"}) ~= nil or minetest.find_node_near(p2, 22, {"default:dirt_with_grass"}) ~= nil or minetest.find_node_near(p2, 52, {"default:sandstonebrick"}) ~= nil then return end - - --minetest.after(0.8,make,p2) - end -end) - - - - -mobs:register_egg("natspawner:zombie", "Zombie", "zombie_head.png", 0) - -minetest.register_craftitem("natspawner:rotten_flesh", { - description = "Rotten Flesh", - inventory_image = "mobs_rotten_flesh.png", - on_use = minetest.item_eat(-5), -}) +dofile(path .. "/api.lua") +dofile(path .. "/mobs.lua") diff --git a/mobs.lua b/mobs.lua new file mode 100644 index 0000000..f0f51a9 --- /dev/null +++ b/mobs.lua @@ -0,0 +1,381 @@ +-- Entity registrations + +local function normal_attack(self, to_attack) + --set_animation(self, "punch") + +-- local p2 = to_attack +-- local s2 = self.object:get_pos() + +-- p2.y = p2.y + .5 +-- s2.y = s2.y + .5 + + --local line = minetest.line_of_sight(p2, s2) + --minetest.log(dump(line)) + +--r, pos = minetest.line_of_sight(p2, s2, stepsize) + --if line_of_sight(self, p2, s2) == true then + + -- play attack sound + --mob_sound(self, self.sounds.attack) + + -- punch player (or what player is attached to) + local attached = self.attack:get_attach() + if attached then + self.attack = attached + end + self.attack:punch(self.object, 1.0, { + full_punch_interval = 1.0, + damage_groups = {fleshy = self.damage} + }, nil) + --end + +end + +-- Default zombie +mobs:register_mob("natspawner:zombie", { + type = "monster", + passive = false, + attack_type = "dogfight", + damage = 4, + reach = 2, + hp_min = 12, + hp_max = 35, + armor = 150, + collisionbox = {-0.25, -1, -0.3, 0.25, 0.75, 0.3}, + visual = "mesh", + mesh = "creatures_mob.x", + textures = { + {"mobs_zombie.png"}, + }, + visual_size = {x=1, y=1}, + makes_footstep_sound = true, + sounds = { + random = "mobs_zombie.1", + damage = "mobs_zombie_hit", + attack = "mobs_zombie.3", + death = "mobs_zombie_death", + }, + walk_velocity = 0.5, + run_velocity = 1.75, + jump = true, + floats = 0, + view_range = 12, + drops = { + {name = "zombie:rotten_flesh", + chance = 2, min = 3, max = 5,}, + }, + water_damage = 0, + lava_damage = 1, + light_damage = 0, + animation = { + speed_normal = 10, speed_run = 15, + stand_start = 0, stand_end = 79, + walk_start = 168, walk_end = 188, + run_start = 168, run_end = 188, +-- punch_start = 168, punch_end = 188, + }, + on_rightclick = function(self, clicker) + minetest.log(dump(self)) + end, + on_die = natspawner.on_mob_die +}) + +mobs:register_mob("natspawner:giant_zombie", { + type = "monster", + passive = false, + attack_type = "dogfight", + damage = 4, + reach = 2, + hp_min = 12, + hp_max = 35, + armor = 150, + collisionbox = {-0.625, -2.5, -2.8, 0.625, 1.875, 2.8}, + visual = "mesh", + mesh = "creatures_mob.x", + textures = { + {"mobs_zombie.png"}, + }, + visual_size = {x=2.5, y=2.5, z=2.5}, + makes_footstep_sound = true, + sounds = { + random = "mobs_zombie.1", + damage = "mobs_zombie_hit", + attack = "mobs_zombie.3", + death = "mobs_zombie_death", + }, + walk_velocity = 0.5, + run_velocity = 1.75, + jump = true, + floats = 0, + view_range = 12, + drops = { + {name = "zombie:rotten_flesh", + chance = 2, min = 3, max = 5,}, + }, + water_damage = 0, + lava_damage = 1, + light_damage = 0, + animation = { + speed_normal = 10, speed_run = 15, + stand_start = 0, stand_end = 79, + walk_start = 168, walk_end = 188, + run_start = 168, run_end = 188, +-- punch_start = 168, punch_end = 188, + }, + on_rightclick = function(self, clicker) + minetest.log(dump(self)) + end, + on_die = natspawner.on_mob_die +}) + +mobs:register_mob("natspawner:ice_zombie", { + type = "monster", + passive = false, + attack_type = "dogfight", + damage = 4, + reach = 2, + hp_min = 12, + hp_max = 35, + armor = 150, + collisionbox = {-0.25, -1, -0.3, 0.25, 0.75, 0.3}, + visual = "mesh", + mesh = "creatures_mob.x", + textures = { + {"mobs_zombie_ice.png"}, + }, + visual_size = {x=1, y=1}, + makes_footstep_sound = true, + sounds = { + random = "mobs_zombie.1", + damage = "mobs_zombie_ice_hit", + attack = "mobs_zombie.3", + death = "mobs_zombie_death", + }, + walk_velocity = 0.5, + run_velocity = 1.75, + jump = true, + floats = 0, + view_range = 12, + drops = { + {name = "zombie:rotten_flesh", + chance = 2, min = 3, max = 5,}, + }, + water_damage = 0, + lava_damage = 1, + light_damage = 0, + animation = { + speed_normal = 10, speed_run = 15, + stand_start = 0, stand_end = 79, + walk_start = 168, walk_end = 188, + run_start = 168, run_end = 188, +-- punch_start = 168, punch_end = 188, + }, + blood_amount = 4, + blood_texture = "default_ice.png", + after_activate = function(self) + self.ice_attack_timer = 0 + end, + on_rightclick = function(self, clicker) + minetest.log(dump(self)) + end, + on_die = natspawner.on_mob_die, + custom_attack = function(self, to_attack) + + self.ice_attack_timer = self.ice_attack_timer + 1 + + if self.ice_attack_timer >= 5 then + self.ice_attack_timer = 0 + local chance = math.random(1, 5) + if chance > 3 then + -- TODO: Add particle effects + minetest.log("Ice") + minetest.set_node(to_attack, {name="natspawner:freezing_ice"}) + minetest.set_node({x=to_attack.x, y=to_attack.y+1, z=to_attack.z}, {name="natspawner:freezing_ice"}) + -- Remove node + minetest.after(math.random(1,2), function(pos) + minetest.sound_play("default_break_glass") + minetest.set_node(to_attack, {name="air"}) + minetest.set_node({x=to_attack.x, y=to_attack.y+1, z=to_attack.z}, {name="air"}) + end, to_attack) + end + end + + normal_attack(self, to_attack) + end +}) + +mobs:register_mob("natspawner:sand_zombie_small", { + type = "monster", + passive = false, + attack_type = "dogfight", + damage = 6, + reach = 2, + hp_min = 12, + hp_max = 35, + armor = 100, + collisionbox = {-0.1625, -0.65, -0.195, 0.1625, 0.4875, 0.195}, + visual = "mesh", + mesh = "creatures_mob.x", + textures = { + {"mobs_zombie_sand.png"}, + }, + visual_size = {x=0.675, y=0.65, z=0.675}, + makes_footstep_sound = true, + sounds = { + random = "mobs_zombie", + damage = "mobs_zombie_sand_hit", + attack = "mobs_zombie", + death = "mobs_zombie_death", + }, + walk_velocity = 1.5, + run_velocity = 3, + jump = true, + floats = 0, + view_range = 12, + drops = { + {name = "zombie:rotten_flesh", + chance = 2, min = 3, max = 5,}, + }, + water_damage = 0, + lava_damage = 1, + light_damage = 0, + animation = { + speed_normal = 10, speed_run = 15, + stand_start = 0, stand_end = 79, + walk_start = 168, walk_end = 188, + run_start = 168, run_end = 188, +-- punch_start = 168, punch_end = 188, + }, + blood_amount = 7, + blood_texture = "default_sand.png", + on_rightclick = function(self, clicker) + minetest.log(dump(self)) + end, + on_die = natspawner.on_mob_die +}) + +mobs:register_mob("natspawner:sand_zombie", { + type = "monster", + passive = false, + attack_type = "dogfight", + damage = 4, + reach = 2, + hp_min = 12, + hp_max = 35, + armor = 150, + collisionbox = {-0.25, -1, -0.3, 0.25, 0.75, 0.3}, + visual = "mesh", + mesh = "creatures_mob.x", + textures = { + {"mobs_zombie_sand.png"}, + }, + visual_size = {x=1, y=1}, + makes_footstep_sound = true, + sounds = { + random = "mobs_zombie", + damage = "mobs_zombie_sand_hit",--"mobs_zombie_hit", + attack = "mobs_zombie", + death = "mobs_zombie_death", + }, + walk_velocity = 0.5, + run_velocity = 1.75, + jump = true, + floats = 0, + view_range = 12, + drops = { + {name = "zombie:rotten_flesh", + chance = 2, min = 3, max = 5,}, + }, + water_damage = 0, + lava_damage = 1, + light_damage = 0, + animation = { + speed_normal = 10, speed_run = 15, + stand_start = 0, stand_end = 79, + walk_start = 168, walk_end = 188, + run_start = 168, run_end = 188, +-- punch_start = 168, punch_end = 188, + }, + blood_amount = 7, + blood_texture = "default_sand.png", + on_rightclick = function(self, clicker) + minetest.log(dump(self)) + end, + on_die = function(self, pos) + + -- TODO: Add particle effects + --minetest.add_particle_spawner() + local positions = {} + for x = -1, 1 do + for z = -1, 1 do + local chance = math.random(1, 2) + if (chance > 1) then + minetest.set_node({x=pos.x + x, y=pos.y-1, z=pos.z + z}, {name="natspawner:evil_sand"}) + positions[#positions + 1] = {x=pos.x + x, y = pos.y-1, z=pos.z + z} + end + end + end + + minetest.after(1, function(positions) + for i = 1, #positions do + local chance = math.random(1, 10) + if (chance > 5) then + minetest.add_entity({x=positions[i].x, y=positions[i].y+1, z=positions[i].z}, "natspawner:sand_zombie_small") + end + end + --minetest.add_entity({x=pos.x - 1, y=pos.y, z=pos.z}, "natspawner:sand_zombie_small") + end, positions) + + -- Spawner callback + natspawner.on_mob_die(self, pos) + end +}) + +-- Nodes +minetest.register_node("natspawner:freezing_ice", { + description = "Freezing Ice", + drawtype = "glasslike", + tiles = {"freezing_ice.png"}, + paramtype = "light", + walkable = false, + pointable = true, + diggable = true, + buildable_to = false, + drop = "", + drowning = 1, + liquid_renewable = false, + liquidtype = "source", + use_textrure_alpha = true, + liquid_range= 0, + liquid_alternative_flowing = "natspawner:freezing_ice", + liquid_alternative_source = "natspawner:freezing_ice", + liquid_viscosity = 30, + post_effect_color = {a = 103, r = 30, g = 60, b = 90}, + groups = {cracky = 3, cools_lava = 1, slippery = 3, flammable=2, liquid=1, not_in_creative_inventory=1}, +}) + +minetest.register_node("natspawner:evil_sand", { + description = "Evil Sand", + tiles = {"default_sand.png"}, + inventory_image = "default_snowball.png", + wield_image = "default_snowball.png", + paramtype = "light", + buildable_to = true, + floodable = true, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-0.5, -0.5, -0.5, 0.5, -0.25, 0.5}, + }, + }, + collision_box = { + type = "fixed", + fixed = { + {-0.5, -0.5, -0.5, 0.5, -7 / 16, 0.5}, + }, + }, + groups = {crumbly = 3, falling_node = 1, sand = 1}, + sounds = default.node_sound_sand_defaults(), + damage_per_second = 2 +}) diff --git a/sounds/mobs_zombie_ice_hit.1.ogg b/sounds/mobs_zombie_ice_hit.1.ogg new file mode 100644 index 0000000..191287a Binary files /dev/null and b/sounds/mobs_zombie_ice_hit.1.ogg differ diff --git a/sounds/mobs_zombie_ice_hit.2.ogg b/sounds/mobs_zombie_ice_hit.2.ogg new file mode 100644 index 0000000..b6cc9e8 Binary files /dev/null and b/sounds/mobs_zombie_ice_hit.2.ogg differ diff --git a/sounds/mobs_zombie_sand_hit.1.ogg b/sounds/mobs_zombie_sand_hit.1.ogg new file mode 100644 index 0000000..30217b1 Binary files /dev/null and b/sounds/mobs_zombie_sand_hit.1.ogg differ diff --git a/sounds/mobs_zombie_sand_hit.2.ogg b/sounds/mobs_zombie_sand_hit.2.ogg new file mode 100644 index 0000000..57f35f3 Binary files /dev/null and b/sounds/mobs_zombie_sand_hit.2.ogg differ diff --git a/sounds/mobs_zombie_sand_hit.3.ogg b/sounds/mobs_zombie_sand_hit.3.ogg new file mode 100644 index 0000000..65b68c7 Binary files /dev/null and b/sounds/mobs_zombie_sand_hit.3.ogg differ diff --git a/textures/freezing_ice.png b/textures/freezing_ice.png new file mode 100644 index 0000000..366a303 Binary files /dev/null and b/textures/freezing_ice.png differ