diff --git a/init.lua b/init.lua index f741748..c9b473a 100644 --- a/init.lua +++ b/init.lua @@ -147,6 +147,59 @@ local function zombie_brain(self) end end +local function ghost_brain(self) + -- vitals should be checked every step + if mobkit.timer(self,1) then lava_dmg(self,6) end + + if self.hp <= 0 then + mobkit.clear_queue_high(self) -- cease all activity + mobkit.hq_die(self) -- kick the bucket + + -- workaround for models bottom y being -1. Makes them blink white sometimes, why? + local props = self.object:get_properties() + props.collisionbox[2] = props.collisionbox[1] + self.object:set_properties({collisionbox=props.collisionbox}) + return + end + + if mobkit.timer(self,1) then -- decision making needn't happen every engine step + local prty = mobkit.get_queue_priority(self) + + if prty < 50 and self.isinliquid then + mobkit.hq_liquid_recovery(self,50) + return + end + + local pos=self.object:get_pos() + + if prty < 20 then + local plyr=mobkit.get_nearby_player(self) + if plyr then + local pos2 = plyr:get_pos() + if prty < 5 then -- ghost not alert + if vector.distance(pos,pos2) < self.view_range/2 and + (not mobkit.is_there_yet2d(pos,minetest.yaw_to_dir(self.object:get_yaw()),pos2) or + vector.length(plyr:get_player_velocity()) > 3) then + mobkit.make_sound(self,'misc') + mobkit.hq_hunt(self,20,plyr) + if random()<=0.5 then alert(pos) end + end + else + if vector.distance(pos,pos2) < self.view_range then + mobkit.make_sound(self,'misc') + mobkit.hq_hunt(self,20,plyr) + if random()<=0.5 then alert(pos) end + end + end + end + end + + if mobkit.is_queue_empty_high(self) then + zombiestrd.hq_roam(self,0) + end + end +end + local function shark_brain(self) if mobkit.timer(self,1) then lava_dmg(self,6) end mobkit.vitals(self) @@ -210,12 +263,19 @@ minetest.register_entity("zombiestrd:target", { end, }) -local function check_is_inside_area(pos) +local function locate( table, value ) + for i = 1, #table do + if table[i] == value then return true end + end + return false +end + +local function check_is_inside_area(pos, table) if areas then local areasAtPos = areas:getAreasAtPos(pos) for id, area in pairs(areasAtPos) do --minetest.chat_send_all(dump(area.name)) - if area.name == "cemetery" or area.name == "Cemetery" or area.name == "monsters" or area.name == "zbd" then + if locate( table, area.name ) then return true end end @@ -223,6 +283,72 @@ local function check_is_inside_area(pos) return false end +local function spawn_monsters(pos, yaw, chance, distance_multiplier, monster_name, areas) + -- is the player inside the area? + if check_is_inside_area(pos, areas) then + + local dir = vector.multiply(minetest.yaw_to_dir(yaw),distance_multiplier) + local pos2 = vector.add(pos,dir) + --minetest.add_entity(pos2, "zombiestrd:target") --debug target + pos2.y=pos2.y-5 + local height, liquidflag = mobkit.get_terrain_height(pos2,32) + if height == nil then height = 0 end + local position_at_terrain_height = {x=pos2.x,y=height-0.01,z=pos2.z} + + --force the spawn area be inside the area + local is_zombie_spawn_area = check_is_inside_area(position_at_terrain_height, areas) + if is_zombie_spawn_area then + --if height and height >= 0 and + if mobkit.nodeatpos(position_at_terrain_height).is_ground_content then + + local objs = minetest.get_objects_inside_radius(pos,abr*distance_multiplier+5) + local wcnt=0 + local dcnt=0 + local mobname = monster_name --'zombiestrd:zombie' --myTable[ math.random( #myTable ) ] + if liquidflag then -- sharks + --[[ + local spnode = mobkit.nodeatpos({x=pos2.x,y=height+0.01,z=pos2.z}) + local spnode2 = mobkit.nodeatpos({x=pos2.x,y=height+1.01,z=pos2.z}) -- node above to make sure won't spawn in shallows + nodename_water = nodename_water or minetest.registered_aliases.mapgen_water_source + if spnode and spnode2 and spnode.name == nodename_water and spnode2.name == nodename_water then + for _,obj in ipairs(objs) do + if not obj:is_player() then + local entity = obj:get_luaentity() + if entity and entity.name=='zombiestrd:shark' then return end + end + end + mobname = 'zombiestrd:shark' + else + return false + end + ]]-- + return false + elseif height >= 0 then --zombies + for _,obj in ipairs(objs) do -- count mobs in abrange + if not obj:is_player() then + local entity = obj:get_luaentity() + if entity and entity.name:find('zombiestrd:') then + chance=chance + (1-chance)*spawn_reduction -- chance reduced for every mob in range + end + end + end + end + if chance < random() then + pos2.y = height+1.01 + objs = minetest.get_objects_inside_radius(pos2,abr*distance_multiplier-2) + --[[for _,obj in ipairs(objs) do -- do not spawn if another player around + if obj:is_player() then return end + end]]-- + local obj=minetest.add_entity(pos2,mobname) -- ok spawn it already damnit + return true + end + end + end --monsters_spwan_area + end + return false +end + + -- spawning is too specific to be included in the api, this is an example. -- a modder will want to refer to specific names according to games/mods they're using -- in order for mobs not to spawn on treetops, certain biomes etc. @@ -245,66 +371,15 @@ local function spawnstep(dtime) local distance_multiplier = 10 --16 --local dir = vector.multiply(minetest.yaw_to_dir(yaw),abr*distance_multiplier) if areas then - -- is player inside the spawn area? - if check_is_inside_area(pos) then + local zb_area = {"cemetery","Cemetery","monsters","zbd"} + local spawned = spawn_monsters(pos, yaw, chance, distance_multiplier, 'zombiestrd:zombie', zb_area) - local dir = vector.multiply(minetest.yaw_to_dir(yaw),distance_multiplier) - local pos2 = vector.add(pos,dir) - --minetest.add_entity(pos2, "zombiestrd:target") --debug target - pos2.y=pos2.y-5 - local height, liquidflag = mobkit.get_terrain_height(pos2,32) - if height == nil then height = 0 end - local position_at_terrain_height = {x=pos2.x,y=height-0.01,z=pos2.z} + if spawned == false then + chance = spawn_rate * 1.4 + local ghost_area = {"cemetery","Cemetery","monsters","gtd"} + spawned = spawn_monsters(pos, yaw, chance, distance_multiplier, 'zombiestrd:ghost', ghost_area) + end - --force the spawn area be inside the area - local is_monsters_spawn_area = check_is_inside_area(position_at_terrain_height) - if is_monsters_spawn_area then - --if height and height >= 0 and - if mobkit.nodeatpos(position_at_terrain_height).is_ground_content then - - local objs = minetest.get_objects_inside_radius(pos,abr*distance_multiplier+5) - local wcnt=0 - local dcnt=0 - local mobname = 'zombiestrd:zombie' - if liquidflag then -- sharks - --[[ - local spnode = mobkit.nodeatpos({x=pos2.x,y=height+0.01,z=pos2.z}) - local spnode2 = mobkit.nodeatpos({x=pos2.x,y=height+1.01,z=pos2.z}) -- node above to make sure won't spawn in shallows - nodename_water = nodename_water or minetest.registered_aliases.mapgen_water_source - if spnode and spnode2 and spnode.name == nodename_water and spnode2.name == nodename_water then - for _,obj in ipairs(objs) do - if not obj:is_player() then - local entity = obj:get_luaentity() - if entity and entity.name=='zombiestrd:shark' then return end - end - end - mobname = 'zombiestrd:shark' - else - return - end - ]]-- - return - elseif height >= 0 then --zombies - for _,obj in ipairs(objs) do -- count mobs in abrange - if not obj:is_player() then - local entity = obj:get_luaentity() - if entity and entity.name:find('zombiestrd:') then - chance=chance + (1-chance)*spawn_reduction -- chance reduced for every mob in range - end - end - end - end - if chance < random() then - pos2.y = height+1.01 - objs = minetest.get_objects_inside_radius(pos2,abr*distance_multiplier-2) - --[[for _,obj in ipairs(objs) do -- do not spawn if another player around - if obj:is_player() then return end - end]]-- - local obj=minetest.add_entity(pos2,mobname) -- ok spawn it already damnit - end - end - end --monsters_spwan_area - end -- player area end -- end areas end end @@ -388,11 +463,11 @@ minetest.register_entity("zombiestrd:zombie",{ --PONCTUATION if zombie_score[name] then zombie_score[name] = zombie_score[name] + 1 - check_prizes(puncher) + check_prizes(puncher, zombie_score[name], "zombie") else zombie_score[name] = 1 end - zb_savelist() + savelist() --END PONCTUATION else mobkit.make_sound(self,'bodyhit') @@ -416,6 +491,111 @@ minetest.register_entity("zombiestrd:zombie",{ }) +minetest.register_entity("zombiestrd:ghost",{ + -- common props + physical = true, + stepheight = 0.1, --EVIL! + collide_with_objects = false, + collisionbox = {-0.25, -1, -0.25, 0.25, 0.75, 0.25}, + visual = "mesh", + mesh = "zombie_normal.b3d", + textures = {"mobs_npc_ghost.png","mobs_npc_ghost2.png"}, + visual_size = {x = 1, y = 1}, + static_save = true, + makes_footstep_sound = false, + on_step = mobkit.stepfunc, -- required + on_activate = mobkit.actfunc, -- required + get_staticdata = mobkit.statfunc, + -- api props + springiness=0, + buoyancy = 0.75, -- portion of hitbox submerged + max_speed = 4, + jump_height = 3, + view_range = 30, + lung_capacity = 200, -- seconds + max_hp = 50, + attack={range=0.5,damage_groups={fleshy=7}}, + animation = { + walk={range={x=41,y=101},speed=40,loop=true}, + stand={range={x=0,y=40},speed=1,loop=true}, + }, + + sounds = { + misc='ghost', + attack='ghost_laugh', + warn = 'angrydog', + headhit = 'splash_hit', + bodyhit = 'ghost_hit', + charge = 'ghost_charge', + }, + armor_groups={immortal=100}, + brainfunc = zombie_brain, + + on_punch=function(self, puncher, time_from_last_punch, tool_caps, dir) + if mobkit.is_alive(self) then + + -- head seeking + if type(puncher)=='userdata' and puncher:is_player() then + local name = puncher:get_player_name() + local pp = puncher:get_pos() + pp.y = pp.y + puncher:get_properties().eye_height -- pp is now camera pos + local pm, radius = get_head(self) + local look_dir = puncher:get_look_dir() + local head_dir = vector.subtract(pm,pp) + local dot = dot(look_dir,head_dir) + local p2 = {x=pp.x+look_dir.x*dot, y=pp.y+look_dir.y*dot, z=pp.z+look_dir.z*dot} + if vector.distance(pp,pm) <=2 then -- a way to decrease punch range without dependences + if mobkit.isnear3d(pm,p2,radius*0.8) and + time_from_last_punch >= tool_caps.full_punch_interval-0.01 and + tool_caps.damage_groups.fleshy > 3 then -- valid headshot + mobkit.make_sound(self,'headhit') +-- self.object:set_hp(99) + self.hp=0 + --PONCTUATION + if ghost_score[name] then + ghost_score[name] = ghost_score[name] + 1 + check_prizes(puncher, ghost_score[name], "ghost") + else + ghost_score[name] = 1 + end + savelist() + --END PONCTUATION + else + mobkit.make_sound(self,'bodyhit') + if mobkit.is_alive(self) then + mobkit.hurt(self,tool_caps.damage_groups.fleshy or 1) + if random()<=0.3 then alert(pp) end + if mobkit.get_queue_priority(self) < 10 then + mobkit.make_sound(self,'misc') + mobkit.hq_hunt(self,10,puncher) + end + if self.hp<=0 then + --PONCTUATION + if ghost_score[name] then + ghost_score[name] = ghost_score[name] + 1 + check_prizes(puncher, ghost_score[name], "ghost") + else + ghost_score[name] = 1 + end + savelist() + --END PONCTUATION + end + end + end + -- kickback + local hvel = vector.multiply(look_dir,4) + self.object:set_velocity({x=hvel.x,y=max(hvel.y,1),z=hvel.z}) + end + else + local hvel = vector.multiply(vector.normalize({x=dir.x,y=0,z=dir.z}),4) + self.object:set_velocity({x=hvel.x,y=2,z=hvel.z}) + end + + end + end +}) + + --[[minetest.register_entity("zombiestrd:shark",{ -- common props physical = true, diff --git a/scoreboard.lua b/scoreboard.lua index 7a89b72..5179d56 100644 --- a/scoreboard.lua +++ b/scoreboard.lua @@ -1,5 +1,7 @@ local storage = minetest.get_mod_storage() +score = {} zombie_score = {} +ghost_score = {} local prizes = { {250, "binoculars:binoculars", 1, "binoculars"}, @@ -16,21 +18,68 @@ local prizes = { local function openlist() local load = storage:to_table() - zombie_score = load.fields - - for count in pairs(zombie_score) do - zombie_score[count] = tonumber(zombie_score[count]) - end - + score = load.fields + + --minetest.debug("score monsters: " .. dump(score)) + if score["zombie_score"] ~= nil then + --minetest.debug("score zombie: " .. dump(score["zombie_score"])) + zombie_score = loadstring("return " .. score["zombie_score"])() + for count in pairs(zombie_score) do + zombie_score[count] = tonumber(zombie_score[count]) + end + else + zombie_score = {} + end + if score["ghost_score"] ~= nil then + ghost_score = loadstring("return " .. score["ghost_score"])() + for count in pairs(ghost_score) do + ghost_score[count] = tonumber(ghost_score[count]) + end + else + ghost_score = {} + end + end -- save scoreboard -function zb_savelist() +function savelist() - storage:from_table({fields=zombie_score}) - + score["zombie_score"] = serializeTable(zombie_score) + score["ghost_score"] = serializeTable(ghost_score) + storage:from_table({fields=score}) + --minetest.chat_send_all(dump(score)) end -- poi.save() +function serializeTable(val, name, skipnewlines, depth) + skipnewlines = skipnewlines or false + depth = depth or 0 + + local tmp = string.rep(" ", depth) + + if name then tmp = tmp .. name .. " = " end + + if type(val) == "table" then + tmp = tmp .. "{" .. (not skipnewlines and "\n" or "") + + for k, v in pairs(val) do + tmp = tmp .. serializeTable(v, k, skipnewlines, depth + 1) .. "," .. (not skipnewlines and "\n" or "") + end + + tmp = tmp .. string.rep(" ", depth) .. "}" + elseif type(val) == "number" then + tmp = tmp .. tostring(val) + elseif type(val) == "string" then + tmp = tmp .. string.format("%q", val) + elseif type(val) == "boolean" then + tmp = tmp .. (val and "true" or "false") + else + tmp = tmp .. "\"[inserializeable datatype:" .. type(val) .. "]\"" + end + + return tmp +end + + function spairs(t, order) -- collect the keys local keys = {} @@ -55,11 +104,11 @@ function spairs(t, order) end -local function sortscore() +local function sortscore(score_table) local fname = "size[5,6]" local count = 1 - for k,v in spairs(zombie_score, function(t,a,b) return t[b] < t[a] end) do + for k,v in spairs(score_table, function(t,a,b) return t[b] < t[a] end) do --minetest.chat_send_all(count.." >>> "..k.." , "..v) fname = fname.."label[1,"..(count*0.3)..";"..count.." >>> "..k.." , "..v.."]" count = count + 1 @@ -70,20 +119,21 @@ local function sortscore() return fname end -function check_prizes(user) - local name = user:get_player_name() - local inv = user:get_inventory() - for i in ipairs(prizes) do - local goal = prizes[i][1] - local nodename = prizes[i][2] - local howmuch = prizes[i][3] - local sayit = prizes[i][4] - - if zombie_score[name] == goal then - minetest.chat_send_player(name, core.colorize("#FF6700", "Congratulation: You killed your "..goal.."s zombies !! Keep up the good work. "..howmuch.." "..sayit.." have been added to your inv")) +function check_prizes(user, points, monster_name) + --check_prizes(puncher, zombie_score[name], "zombie") + local name = user:get_player_name() + local inv = user:get_inventory() + for i in ipairs(prizes) do + local goal = prizes[i][1] + local nodename = prizes[i][2] + local howmuch = prizes[i][3] + local sayit = prizes[i][4] + + if points == goal then + minetest.chat_send_player(name, core.colorize("#FF6700", "Congratulation: You killed your "..goal.."s "..monster_name.."s !! Keep up the good work. "..howmuch.." "..sayit.." have been added to your inv")) inv:add_item("main", {name=nodename, count=howmuch}) end - end + end end minetest.register_chatcommand("zombie_score", { @@ -92,7 +142,22 @@ minetest.register_chatcommand("zombie_score", { privs = {interact = true}, func = function(name) - local fname = sortscore() + local fname = sortscore(zombie_score) + if fname then + --minetest.chat_send_player(name, ">>> Highscore is :"..score[highscore].." by "..highscore) + minetest.show_formspec(name, "zombiestrd:the_killers", fname) + end + + end, +}) + +minetest.register_chatcommand("ghost_score", { + params = "", + description = "Shows the best ghost killer", + privs = {interact = true}, + func = function(name) + + local fname = sortscore(ghost_score) if fname then --minetest.chat_send_player(name, ">>> Highscore is :"..score[highscore].." by "..highscore) minetest.show_formspec(name, "zombiestrd:the_killers", fname) diff --git a/sounds/ghost.1.ogg b/sounds/ghost.1.ogg new file mode 100644 index 0000000..92e0f09 Binary files /dev/null and b/sounds/ghost.1.ogg differ diff --git a/sounds/ghost.2.ogg b/sounds/ghost.2.ogg new file mode 100644 index 0000000..618fa30 Binary files /dev/null and b/sounds/ghost.2.ogg differ diff --git a/sounds/ghost.3.ogg b/sounds/ghost.3.ogg new file mode 100644 index 0000000..343aacd Binary files /dev/null and b/sounds/ghost.3.ogg differ diff --git a/sounds/ghost.4.ogg b/sounds/ghost.4.ogg new file mode 100644 index 0000000..f0881c5 Binary files /dev/null and b/sounds/ghost.4.ogg differ diff --git a/sounds/ghost_charge.ogg b/sounds/ghost_charge.ogg new file mode 100644 index 0000000..eac4277 Binary files /dev/null and b/sounds/ghost_charge.ogg differ diff --git a/sounds/ghost_hit.ogg b/sounds/ghost_hit.ogg new file mode 100644 index 0000000..45bbd14 Binary files /dev/null and b/sounds/ghost_hit.ogg differ diff --git a/sounds/ghost_laugh.ogg b/sounds/ghost_laugh.ogg new file mode 100644 index 0000000..881dbde Binary files /dev/null and b/sounds/ghost_laugh.ogg differ diff --git a/sounds/media.txt b/sounds/media.txt new file mode 100644 index 0000000..f60db74 --- /dev/null +++ b/sounds/media.txt @@ -0,0 +1,2 @@ +ghost.*.ogg and gohst_charge.ogg are Creative Commons 3.0 by Videvo https://www.videvo.net/royalty-free-sound-effects/ghost/ +Additional sound effects (ghost_laugh.ogg) from https://www.zapsplat.com diff --git a/textures/mobs_npc_ghost.png b/textures/mobs_npc_ghost.png new file mode 100755 index 0000000..0532b30 Binary files /dev/null and b/textures/mobs_npc_ghost.png differ diff --git a/textures/mobs_npc_ghost2.png b/textures/mobs_npc_ghost2.png new file mode 100755 index 0000000..ed1d224 Binary files /dev/null and b/textures/mobs_npc_ghost2.png differ