Update 20161106
@ -1,37 +0,0 @@
|
||||
-- ANTI CHEAT by rnd
|
||||
-- Copyright 2016 rnd
|
||||
-- includes spectator mod by jp, modified/bugfixed by rnd
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
features:
|
||||
|
||||
0. what it does:
|
||||
- succesffuly detect noclip/fly. Its just a matter of time when someone noclipping/flying is detected.
|
||||
- players cant know when they are being watch since intervals are randomized
|
||||
- lag resistant (see CHECK_AGAIN in settings)
|
||||
|
||||
1. moderators can:
|
||||
-see full reports with coordinates of location as cheats occur
|
||||
-use /cstats to see latest detected cheater
|
||||
-use /cdebug to see even suspected cheats to be verified later
|
||||
-use /watch NAME to spectate suspect/detected cheater, /unwatch to return to normal
|
||||
|
||||
managing moderators:
|
||||
-edit names inside anticheatsettings.moderators in settings.lua
|
||||
-Any player with kick privileges is moderator and is additionaly ignored by cheat checks. Use this for admin only - cheaters can then see who moderators are.
|
||||
|
||||
2. this mod works well with basic_vote mod. After cheater has been positively detected anyone can use /vote to kick, remove interact or kill cheater. Vote in this case is cast anonymously, under the name #anticheat.
|
@ -1,2 +0,0 @@
|
||||
default
|
||||
boneworld?
|
@ -1,556 +0,0 @@
|
||||
-- ANTI CHEAT by rnd
|
||||
-- Copyright 2016 rnd
|
||||
-- includes modified/bugfixed spectator mod by jp
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-------------------------------------------------------------------------
|
||||
local cheat = {};
|
||||
local version = "10/27/2016";
|
||||
|
||||
anticheatsettings = {};
|
||||
dofile(minetest.get_modpath("anticheat").."/settings.lua")
|
||||
|
||||
local CHEAT_TIMESTEP = anticheatsettings.CHEAT_TIMESTEP;
|
||||
local CHECK_AGAIN = anticheatsettings.CHECK_AGAIN;
|
||||
cheat.moderators = anticheatsettings.moderators;
|
||||
|
||||
|
||||
anticheatdb = {}; -- data about detected cheaters
|
||||
|
||||
cheat.suspect = "";
|
||||
cheat.players = {}; -- temporary cheat detection db
|
||||
cheat.message = "";
|
||||
cheat.debuglist = {}; -- [name]=true -- who gets to see debug msgs
|
||||
|
||||
cheat.scan_timer = 0; -- global scan of players
|
||||
cheat.stat_timer = 0; -- used to collect stats
|
||||
|
||||
cheat.nodelist = {};
|
||||
cheat.watcher = {}; -- list of watchers
|
||||
|
||||
cheat.timestep = CHEAT_TIMESTEP;
|
||||
-- list of forbidden nodes
|
||||
cheat.nodelist = {["default:stone"] = false, ["default:cobble"]= false, ["default:dirt"] = false, ["default:sand"]=false,["default:tree"]= false};
|
||||
|
||||
|
||||
local punish_cheat = function(name)
|
||||
|
||||
local player = minetest.get_player_by_name(name);
|
||||
local ip = tostring(minetest.get_player_ip(name));
|
||||
|
||||
if not player then return end
|
||||
local text=""; local logtext = "";
|
||||
|
||||
if cheat.players[name].cheattype == 1 then
|
||||
text = "#anticheat: ".. name .. " was caught walking inside wall";
|
||||
logtext = os.date("%H:%M.%S").." #anticheat: ".. name .. " was caught walking inside wall at " .. minetest.pos_to_string(cheat.players[name].cheatpos);
|
||||
player:set_hp(0);
|
||||
elseif cheat.players[name].cheattype == 2 then
|
||||
logtext= os.date("%H:%M.%S").." #anticheat: ".. name .. " was caught flying at " .. minetest.pos_to_string(cheat.players[name].cheatpos);
|
||||
if cheat.players[name].cheatpos.y>5 then -- only above height 5 it directly damages flyer
|
||||
text = "#anticheat: ".. name .. " was caught flying";
|
||||
--player:set_hp(0);
|
||||
end
|
||||
end
|
||||
|
||||
if text~="" then
|
||||
minetest.chat_send_all(text);
|
||||
end
|
||||
|
||||
if logtext~="" then
|
||||
minetest.log("action", logtext);
|
||||
cheat.message = logtext;
|
||||
|
||||
anticheatdb[ip] = {name = name, msg = logtext};
|
||||
|
||||
cheat.players[name].count=0; -- reset counter
|
||||
cheat.players[name].cheattype = 0;
|
||||
|
||||
for name,_ in pairs(cheat.moderators) do -- display full message to moderators
|
||||
minetest.chat_send_player(name,logtext);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- check position more closely
|
||||
|
||||
-- uncomment when obfuscating:
|
||||
--dofile(minetest.get_modpath("anticheat").."/anticheat_source.lua")
|
||||
|
||||
|
||||
local ie = minetest.request_insecure_environment() or _G;
|
||||
|
||||
local anticheat_routines = ie.loadfile(minetest.get_modpath("anticheat").."/anticheat_routines.bin")
|
||||
check_noclip, check_fly, check_player = anticheat_routines(minetest,cheat,CHECK_AGAIN,punish_cheat);
|
||||
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
|
||||
cheat.scan_timer = cheat.scan_timer + dtime
|
||||
|
||||
|
||||
-- GENERAL SCAN OF ALL PLAYERS
|
||||
if cheat.scan_timer>cheat.timestep then
|
||||
|
||||
|
||||
cheat.stat_timer = cheat.stat_timer + cheat.timestep;
|
||||
-- dig xp stats every 2 minutes
|
||||
if boneworld and cheat.stat_timer>120 then
|
||||
cheat.stat_timer = 0;
|
||||
local players = minetest.get_connected_players();
|
||||
for _,player in pairs(players) do
|
||||
local pname = player:get_player_name();
|
||||
if cheat.players[pname].stats.state == 1 then -- only if dig xp loaded to prevent anomalous stats
|
||||
if boneworld.digxp[pname] then
|
||||
local deltadig = cheat.players[pname].stats.digxp;
|
||||
cheat.players[pname].stats.digxp = boneworld.digxp[pname];
|
||||
deltadig = boneworld.digxp[pname]-deltadig;
|
||||
cheat.players[pname].stats.deltadig = deltadig;
|
||||
|
||||
if deltadig>cheat.players[pname].stats.maxdeltadig then
|
||||
cheat.players[pname].stats.maxdeltadig = deltadig;
|
||||
end
|
||||
|
||||
if deltadig>2 then -- unnaturally high deltadig
|
||||
local ip = tostring(minetest.get_player_ip(pname));
|
||||
local logtext = os.date("%H:%M.%S") .. " #anticheat: " .. pname .. " (ip " .. ip .. ") is mining resources too fast, deltadig " .. deltadig;
|
||||
anticheatdb[ip] = {name = pname, msg = logtext};
|
||||
minetest.log("action", logtext);
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
cheat.timestep = CHEAT_TIMESTEP + (2*math.random()-1)*2; -- randomize step so its unpredictable
|
||||
cheat.scan_timer=0;
|
||||
--local t = minetest.get_gametime();
|
||||
local players = minetest.get_connected_players();
|
||||
|
||||
for _,player in pairs(players) do
|
||||
check_player(player);
|
||||
end
|
||||
|
||||
for name,_ in pairs(cheat.debuglist) do -- show suspects in debug
|
||||
for _,player in pairs(players) do
|
||||
local pname = player:get_player_name();
|
||||
if cheat.players[pname].count>0 then
|
||||
minetest.chat_send_player(name, "name " .. pname .. ", cheat pos " .. minetest.pos_to_string(cheat.players[pname].cheatpos) .. " last clear pos " .. minetest.pos_to_string(cheat.players[pname].clearpos) .. " cheat type " .. cheat.players[pname].cheattype .. " cheatcount " .. cheat.players[pname].count );
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end)
|
||||
|
||||
-- long range dig check
|
||||
|
||||
local check_can_dig = function(pos, digger)
|
||||
|
||||
local p = digger:getpos();
|
||||
if p.y<0 then p.y=p.y+2 else p.y=p.y+1 end -- head position
|
||||
local dist = math.max(math.abs(p.x-pos.x),math.abs(p.y-pos.y),math.abs(p.z-pos.z));
|
||||
|
||||
|
||||
if dist>6 then -- here 5
|
||||
dist = math.floor(dist*100)/100;
|
||||
local pname = digger:get_player_name();
|
||||
local logtext = os.date("%H:%M.%S") .. "#anticheat: long range dig " .. pname ..", distance " .. dist .. ", pos " .. minetest.pos_to_string(pos);
|
||||
for name,_ in pairs(cheat.debuglist) do -- show to all watchers
|
||||
minetest.chat_send_player(name,logtext)
|
||||
|
||||
end
|
||||
local ip = tostring(minetest.get_player_ip(pname));
|
||||
anticheatdb[ip] = {name = pname, msg = logtext};
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local set_check_can_dig = function(name)
|
||||
local tabl = minetest.registered_nodes[name];
|
||||
if not tabl then return end
|
||||
tabl.can_dig = check_can_dig;
|
||||
minetest.override_item(name, {can_dig = check_can_dig})
|
||||
--minetest.register_node(":"..name, tabl);
|
||||
end
|
||||
|
||||
minetest.after(0,
|
||||
function()
|
||||
set_check_can_dig("default:stone");
|
||||
set_check_can_dig("default:stone_with_iron");
|
||||
set_check_can_dig("default:stone_with_copper");
|
||||
set_check_can_dig("default:stone_with_coal");
|
||||
set_check_can_dig("default:stone_with_gold");
|
||||
set_check_can_dig("default:stone_with_mese");
|
||||
set_check_can_dig("default:stone_with_diamond");
|
||||
end
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
-- DISABLED: lot of false positives
|
||||
-- collects misc stats on players
|
||||
|
||||
-- minetest.register_on_cheat(
|
||||
-- function(player, c)
|
||||
-- local name = player:get_player_name(); if name == nil then return end
|
||||
-- local stats = cheat.players[name].stats;
|
||||
-- if not stats[c.type] then stats[c.type] = 0 end
|
||||
-- stats[c.type]=stats[c.type]+1;
|
||||
-- end
|
||||
-- )
|
||||
|
||||
|
||||
|
||||
local watchers = {}; -- for each player a list of watchers
|
||||
minetest.register_on_joinplayer(function(player) -- init stuff on player join
|
||||
local name = player:get_player_name(); if name == nil then return end
|
||||
local pos = player:getpos();
|
||||
|
||||
if cheat.players[name] == nil then
|
||||
cheat.players[name]={count=0,cheatpos = pos, clearpos = pos, lastpos = pos, cheattype = 0}; -- type 0: none, 1 noclip, 2 fly
|
||||
end
|
||||
|
||||
if cheat.players[name] and cheat.players[name].stats == nil then
|
||||
cheat.players[name].stats = {maxdeltadig=0,deltadig = 0,digxp = 0, state = 0}; -- various statistics about player: max dig xp increase in 2 minutes, current dig xp increase
|
||||
|
||||
minetest.after(5, -- load digxp after boneworld loads it
|
||||
function()
|
||||
if boneworld and boneworld.xp then
|
||||
cheat.players[name].stats.digxp = boneworld.digxp[name] or 0;
|
||||
cheat.players[name].stats.state = 1;
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
end
|
||||
--state 0 = stats not loaded yet
|
||||
|
||||
|
||||
watchers[name] = {}; -- for spectator mod
|
||||
|
||||
local ip = tostring(minetest.get_player_ip(name));
|
||||
local msg = "";
|
||||
|
||||
-- check anticheat db
|
||||
--check ip
|
||||
if anticheatdb[ip] then
|
||||
msg = "#anticheat: welcome back detected cheater, ip = " .. ip .. ", name " .. anticheatdb[ip].name .. ", new name = " .. name;
|
||||
end;
|
||||
--check names
|
||||
for ip,v in pairs(anticheatdb) do
|
||||
if v.name == name then
|
||||
msg = "#anticheat: welcome back detected cheater, ip = " .. ip .. ", name = newname = " .. v.name;
|
||||
break;
|
||||
end
|
||||
end
|
||||
|
||||
if msg~="" then
|
||||
for name,_ in pairs(cheat.moderators) do
|
||||
minetest.chat_send_player(name,msg);
|
||||
end
|
||||
end
|
||||
|
||||
end)
|
||||
|
||||
minetest.register_chatcommand("cchk", { -- see cheat report
|
||||
privs = {
|
||||
interact = true
|
||||
},
|
||||
func = function(name, param)
|
||||
local privs = minetest.get_player_privs(name).privs;
|
||||
if not cheat.moderators[name] and not privs then return end
|
||||
|
||||
local player = minetest.get_player_by_name(param);
|
||||
if not player then return end
|
||||
check_player(player);
|
||||
|
||||
|
||||
local players = minetest.get_connected_players();
|
||||
for name,_ in pairs(cheat.debuglist) do -- show suspects in debug
|
||||
for _,player in pairs(players) do
|
||||
local pname = player:get_player_name();
|
||||
if cheat.players[pname].count>0 then
|
||||
minetest.chat_send_player(name, "name " .. pname .. ", cheat pos " .. minetest.pos_to_string(cheat.players[pname].cheatpos) .. " last clear pos " .. minetest.pos_to_string(cheat.players[pname].clearpos) .. " cheat type " .. cheat.players[pname].cheattype .. " cheatcount " .. cheat.players[pname].count );
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
})
|
||||
|
||||
|
||||
minetest.register_chatcommand("crep", { -- see cheat report
|
||||
privs = {
|
||||
interact = true
|
||||
},
|
||||
func = function(name, param)
|
||||
local privs = minetest.get_player_privs(name).privs;
|
||||
if not cheat.moderators[name] and not privs then return end
|
||||
|
||||
if param == "" then
|
||||
minetest.chat_send_player(name,"use: crep type, types: 0(default) cheat report, 1 connected player stats (".. version ..")");
|
||||
end
|
||||
|
||||
param = tonumber(param) or 0;
|
||||
|
||||
if param == 0 then -- show cheat report
|
||||
local text = "";
|
||||
for ip,v in pairs(anticheatdb) do
|
||||
if v and v.name and v.msg then
|
||||
text = text .. "ip " .. ip .. " ".. v.msg .. "\n";
|
||||
end
|
||||
end
|
||||
if text ~= "" then
|
||||
local form = "size [6,7] textarea[0,0;6.5,8.5;creport;CHEAT REPORT;".. text.."]"
|
||||
minetest.show_formspec(name, "anticheatreport", form)
|
||||
end
|
||||
elseif param == 1 then -- show cheat stats
|
||||
local text = "";
|
||||
local players = minetest.get_connected_players();
|
||||
for _,player in pairs(players) do
|
||||
local pname = player:get_player_name();
|
||||
local ip = tostring(minetest.get_player_ip(pname));
|
||||
|
||||
|
||||
text = text .. "\nname " .. pname .. ", digxp " .. math.floor(1000*cheat.players[pname].stats.digxp)/1000 ..
|
||||
", deltadigxp(2min) " .. math.floor(1000*cheat.players[pname].stats.deltadig)/1000 .. ", maxdeltadigxp " .. math.floor(1000*cheat.players[pname].stats.maxdeltadig)/1000; -- .. " ".. string.gsub(dump(cheat.players[pname].stats), "\n", " ");
|
||||
if anticheatdb[ip] then text = text .. " (DETECTED) ip ".. ip .. ", name " .. anticheatdb[ip].name end
|
||||
end
|
||||
if text ~= "" then
|
||||
local form = "size [10,8] textarea[0,0;10.5,9.;creport;CHEAT STATISTICS;".. text.."]"
|
||||
minetest.show_formspec(name, "anticheatreport", form)
|
||||
end
|
||||
end
|
||||
-- suspects info
|
||||
local players = minetest.get_connected_players();
|
||||
for _,player in pairs(players) do
|
||||
local pname = player:get_player_name();
|
||||
if cheat.players[pname].count>0 then
|
||||
minetest.chat_send_player(name, "name " .. pname .. ", cheat pos " .. minetest.pos_to_string(cheat.players[pname].cheatpos) .. " last clear pos " .. minetest.pos_to_string(cheat.players[pname].lastpos) .. " cheat type " .. cheat.players[pname].cheattype .. " cheatcount " .. cheat.players[pname].count );
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("cdebug", { -- toggle cdebug= display of stats on/off for this player
|
||||
privs = {
|
||||
interact = true
|
||||
},
|
||||
func = function(name, param)
|
||||
local privs = minetest.get_player_privs(name).privs;
|
||||
if not cheat.moderators[name] and not privs then return end
|
||||
|
||||
if cheat.debuglist[name] == nil then cheat.debuglist[name] = true else cheat.debuglist[name] = nil end;
|
||||
|
||||
minetest.chat_send_player(name,"#anticheat: " .. version);
|
||||
if cheat.debuglist[name]==true then
|
||||
minetest.chat_send_player(name,"#anticheat: display of debug messages is ON");
|
||||
else
|
||||
minetest.chat_send_player(name,"#anticheat: display of debug messages is OFF");
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
------------------------------------------------------
|
||||
-- [Mod] Spectator Mode [git] [spectator_mode]
|
||||
-- https://github.com/minetest-mods/spectator_mode
|
||||
-- by jp » Tue Dec 08, 2015 15:34
|
||||
-- modified/bugfixes by rnd
|
||||
------------------------------------------------------
|
||||
|
||||
|
||||
local original_pos = {}
|
||||
|
||||
local function unwatching(name)
|
||||
local watcher = minetest.get_player_by_name(name)
|
||||
local privs = minetest.get_player_privs(name)
|
||||
|
||||
if watcher and default.player_attached[name] == true then
|
||||
watcher:set_detach()
|
||||
|
||||
|
||||
local pos = original_pos[watcher]
|
||||
if pos then
|
||||
-- setpos seems to be very unreliable
|
||||
-- this workaround helps though
|
||||
minetest.after(0.1, function()
|
||||
watcher:setpos(pos)
|
||||
end)
|
||||
original_pos[watcher] = nil
|
||||
end
|
||||
cheat.watcher[name]=nil;
|
||||
|
||||
minetest.after(5,
|
||||
function()
|
||||
default.player_attached[name] = false
|
||||
watcher:set_eye_offset({x=0, y=0, z=0}, {x=0, y=0, z=0})
|
||||
watcher:set_nametag_attributes({color = {a=255, r=255, g=255, b=255}})
|
||||
|
||||
watcher:hud_set_flags({
|
||||
healthbar = true,
|
||||
minimap = true,
|
||||
breathbar = true,
|
||||
hotbar = true,
|
||||
wielditem = true,
|
||||
crosshair = true
|
||||
})
|
||||
|
||||
watcher:set_properties({
|
||||
visual_size = {x=1, y=1},
|
||||
makes_footstep_sound = true,
|
||||
collisionbox = {-0.3, -1, -0.3, 0.3, 1, 0.3}
|
||||
})
|
||||
end
|
||||
)
|
||||
-- if not privs.interact and cheat.moderators[name] == true then
|
||||
-- privs.interact = true
|
||||
-- minetest.set_player_privs(name, privs)
|
||||
-- end
|
||||
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_chatcommand("watch", {
|
||||
params = "<to_name>",
|
||||
description = "",
|
||||
privs = {interact=true},
|
||||
func = function(name, param)
|
||||
|
||||
local privs = minetest.get_player_privs(name)
|
||||
if not cheat.moderators[name] and not privs.kick then return end
|
||||
local watcher = minetest.get_player_by_name(name)
|
||||
|
||||
|
||||
if param == "" then -- no name given - select a suspect automatically
|
||||
local players = minetest.get_connected_players();
|
||||
for _,player in pairs(players) do
|
||||
local pname = player:get_player_name();
|
||||
if cheat.players[pname].count>0 then
|
||||
param = pname;
|
||||
cheat.suspect = param;
|
||||
break;
|
||||
end
|
||||
end
|
||||
if param == "" and cheat.suspect~="" then param = cheat.suspect end -- if none found watch last suspect
|
||||
end
|
||||
|
||||
local target = minetest.get_player_by_name(param);
|
||||
|
||||
if not target then return end
|
||||
if not cheat.players[param] then return end
|
||||
|
||||
|
||||
local canwatch = false;
|
||||
for ip,v in pairs(anticheatdb) do
|
||||
if v.name == param then
|
||||
canwatch = true;
|
||||
break;
|
||||
end
|
||||
end
|
||||
|
||||
local ip = tostring(minetest.get_player_ip(param));
|
||||
if anticheatdb[ip] then canwatch = true end -- can watch since this ip was detected before
|
||||
|
||||
|
||||
if canwatch or cheat.players[param].count>0 or param == cheat.suspect or privs.kick then
|
||||
else
|
||||
minetest.chat_send_player(name, "ordinary watchers can only watch cheat suspects of detected cheaters");
|
||||
return
|
||||
end
|
||||
|
||||
if target and watcher ~= target then
|
||||
if default.player_attached[name] == true then
|
||||
unwatching(param)
|
||||
else
|
||||
original_pos[watcher] = watcher:getpos()
|
||||
end
|
||||
|
||||
-- show inventory
|
||||
local tinv = target:get_inventory():get_list("main");
|
||||
for i,v in pairs(tinv) do tinv[i] = v:to_string(); end
|
||||
tinv = dump(tinv);
|
||||
local form = "size [6,7] textarea[0,0;6.5,8.5;creport;INVENTORY LIST;".. tinv.."]"
|
||||
minetest.show_formspec(name, "watch_inventory", form)
|
||||
|
||||
|
||||
default.player_attached[name] = true
|
||||
watcher:set_attach(target, "", {x=0, y=-5, z=-20}, {x=0, y=0, z=0})
|
||||
watcher:set_eye_offset({x=0, y=-5, z=-20}, {x=0, y=0, z=0})
|
||||
watcher:set_nametag_attributes({color = {a=0}})
|
||||
|
||||
watcher:hud_set_flags({
|
||||
healthbar = false,
|
||||
minimap = false,
|
||||
breathbar = false,
|
||||
hotbar = false,
|
||||
wielditem = false,
|
||||
crosshair = false
|
||||
})
|
||||
|
||||
watcher:set_properties({
|
||||
visual_size = {x=0, y=0},
|
||||
makes_footstep_sound = false,
|
||||
collisionbox = {0}
|
||||
})
|
||||
|
||||
-- privs.interact = nil
|
||||
-- minetest.set_player_privs(name, privs)
|
||||
|
||||
cheat.watcher[name]=true;
|
||||
watchers[param][name] = true; -- register name as watcher of param
|
||||
|
||||
|
||||
return true, "Watching '"..param.."' at "..minetest.pos_to_string(vector.round(target:getpos()))
|
||||
end
|
||||
|
||||
return false, "Invalid parameter ('"..param.."')."
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("unwatch", {
|
||||
description = "",
|
||||
privs = {interact=true},
|
||||
func = function(name, param)
|
||||
unwatching(name)
|
||||
-- unregister name as watcher
|
||||
for pname,val in pairs (watchers) do
|
||||
if val[name] then watchers[pname][name] = nil; end
|
||||
end
|
||||
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
local name = player:get_player_name()
|
||||
for pname,_ in pairs (watchers[name]) do
|
||||
unwatching(pname); -- all watchers do /unwatch
|
||||
end
|
||||
watchers[name] = nil;
|
||||
end)
|
@ -1,36 +0,0 @@
|
||||
-- ANTI CHEAT by rnd
|
||||
-- Copyright 2016 rnd
|
||||
-- includes modified/bugfixed spectator mod by jp
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
|
||||
-- SETTINGS --------------------------------------------------------------
|
||||
anticheatsettings.CHEAT_TIMESTEP = 15; -- check all players
|
||||
anticheatsettings.CHECK_AGAIN = 15; -- after player found in bad position check again after this to make sure its not lag, this should be larger than expected lag in seconds
|
||||
|
||||
-- moderators list, those players can use cheat debug and will see full cheat message
|
||||
anticheatsettings.moderators = {
|
||||
["rnd"]=true,
|
||||
["sasha2"]=true,
|
||||
["maikerumine"]=true,
|
||||
["sorcerykid"]=true,
|
||||
["Zorg"]=true,
|
||||
["AspireMint"]=true,
|
||||
["843jdc"]=true,
|
||||
["Fixer"] = true,
|
||||
}
|
||||
-- END OF SETTINGS --------------------------------------------------------
|
@ -1 +0,0 @@
|
||||
bones
|
@ -1,377 +0,0 @@
|
||||
-- boneworld by rnd, 2016
|
||||
|
||||
-- for more interesting bone gameplay:
|
||||
|
||||
-- you no longer get extra bones if you pick bones from player with same ip address (no suicide bone farming)
|
||||
-- each player has experience points (xp)
|
||||
-- when you die you loose 20% of your xp, half of that is stored in bones
|
||||
-- (when you kill other player you get 10% of his xp)
|
||||
-- if you pick up bones you get xp stored in bones
|
||||
-- if you pick up other player bones you get 20% of average of your and bone owner xp award in extra bones (for example if you have 10 xp and you pick noob bone will get 2 bones instead of normally 1)
|
||||
|
||||
local version = "10/22/16"
|
||||
|
||||
local worldpath = minetest.get_worldpath();
|
||||
--os.execute( "mkdir "..worldpath.. "\\boneworld")
|
||||
minetest.mkdir(worldpath .. "\\boneworld") -- directory used to save xp data
|
||||
|
||||
boneworld = {};
|
||||
boneworld.xp = {}; -- [name] = bonexp, bone collect xp
|
||||
boneworld.digxp = {}; -- [name] = xp, mining xp
|
||||
|
||||
boneworld.protect = {}; -- [name] = {timer, position}: time of last dig in unprotected area, position
|
||||
|
||||
-- those players get special dig xp when they join
|
||||
boneworld.vipdig = {["abba"]=1000000}
|
||||
|
||||
|
||||
--boneworld.killxp = {}; -- xp obtained through kills
|
||||
|
||||
boneworld.wastedxp = 0; -- xp thats stored in bones and not yet reclaimed
|
||||
|
||||
|
||||
local share_bones_time = tonumber(minetest.setting_get("share_bones_time")) or 1200
|
||||
--local share_bones_time = tonumber(minetest.setting_get("share_bones_time")) or 20;
|
||||
local share_bones_time_early = tonumber(minetest.setting_get("share_bones_time_early")) or share_bones_time / 4
|
||||
|
||||
|
||||
local function is_owner(pos, name)
|
||||
local owner = minetest.get_meta(pos):get_string("owner")
|
||||
if owner == "" or owner == name or minetest.check_player_privs(name, "protection_bypass") then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
local on_timer = function(pos, elapsed)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local time = meta:get_int("time")+elapsed;
|
||||
if time >= share_bones_time then
|
||||
|
||||
meta:set_string("infotext", meta:get_string("owner").."'s old bones (died ".. meta:get_string("date") .."), bone xp " ..math.floor(meta:get_float("xp")*100)/100);
|
||||
meta:set_string("owner", "")
|
||||
|
||||
else
|
||||
|
||||
if meta:get_int("active") == 0 then -- store data in bones, 1x
|
||||
meta:set_int("active",1);
|
||||
local owner = meta:get_string("owner");
|
||||
meta:set_string("date",os.date("%x"));
|
||||
meta:set_string("owner_orig",owner);
|
||||
meta:set_string("ip", tostring(minetest.get_player_ip(owner)));
|
||||
if not minetest.get_player_by_name(owner) then -- mob bones
|
||||
boneworld.xp[owner] = 0.1 -- 0.1th of noob player xp in mobs bone
|
||||
time=0.9*share_bones_time; -- 10x shorter old bone time
|
||||
else
|
||||
boneworld.xp[owner] = boneworld.xp[owner] or 1;
|
||||
time = 0;
|
||||
end
|
||||
|
||||
if boneworld.xp[owner]<1 then
|
||||
meta:set_float("xp", 0.01) -- mobs or bones with 0 xp
|
||||
else
|
||||
meta:set_float("xp", 0.1); -- player bones give 0.1 xp, same as 10 mob bones
|
||||
end
|
||||
|
||||
boneworld.wastedxp = boneworld.wastedxp + meta:get_float("xp");
|
||||
meta:set_string("infotext"," Here lies " .. owner .. ", bone xp " .. math.floor(meta:get_float("xp")*100)/100);
|
||||
end
|
||||
|
||||
|
||||
meta:set_int("time", time)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local on_punch = function(pos, node, player)
|
||||
if(not is_owner(pos, player:get_player_name())) then
|
||||
return
|
||||
end
|
||||
|
||||
if(minetest.get_meta(pos):get_string("infotext") == "") then
|
||||
return
|
||||
end
|
||||
|
||||
local inv = minetest.get_meta(pos):get_inventory()
|
||||
local player_inv = player:get_inventory()
|
||||
local has_space = true
|
||||
|
||||
for i=1,inv:get_size("main") do
|
||||
local stk = inv:get_stack("main", i)
|
||||
if player_inv:room_for_item("main", stk) then
|
||||
inv:set_stack("main", i, nil)
|
||||
player_inv:add_item("main", stk)
|
||||
else
|
||||
has_space = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- remove bones if player emptied them
|
||||
if has_space then
|
||||
local meta = minetest.get_meta(pos);
|
||||
local active = meta:get_int("active") == 1;
|
||||
local puncher = player:get_player_name();
|
||||
|
||||
-- award extra bones/xp if you collect bones from different ip player
|
||||
--debug
|
||||
if active and meta:get_string("ip")~= tostring(minetest.get_player_ip(puncher)) then
|
||||
local xp = meta:get_float("xp");if xp==0 then xp = 0.01 end
|
||||
-- average of owners xp (at time of death) and puncher xp will be awarded as extra bones
|
||||
-- with every 10 more xp one bone
|
||||
|
||||
local count;
|
||||
if boneworld.xp[puncher]>100 then -- dont give more bones when bone xp exceeds 100
|
||||
count = 1 + 0.1*100;
|
||||
else
|
||||
count = 1+0.1*boneworld.xp[puncher];
|
||||
end
|
||||
|
||||
count = math.floor(count);
|
||||
minetest.chat_send_player(puncher, "you find " .. count .. " bones in the corpse.");
|
||||
|
||||
if player_inv:room_for_item("main", ItemStack("bones:bones "..count)) then
|
||||
player_inv:add_item("main", ItemStack("bones:bones "..count))
|
||||
else
|
||||
minetest.add_item(pos,ItemStack("bones:bones "..count))
|
||||
end
|
||||
|
||||
-- add xp from bones to player who retrieved bones;
|
||||
|
||||
boneworld.xp[puncher] = boneworld.xp[puncher] + meta:get_float("xp");
|
||||
boneworld.wastedxp = boneworld.wastedxp - meta:get_float("xp");
|
||||
end
|
||||
|
||||
|
||||
minetest.remove_node(pos)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- load xp
|
||||
minetest.register_on_joinplayer(
|
||||
function(player)
|
||||
local name = player:get_player_name();
|
||||
if not boneworld.xp[name] then -- load xp
|
||||
local filename = worldpath .. "\\boneworld\\" .. name..".xp";
|
||||
local f = io.open(filename, "r");
|
||||
if not f then -- file does not yet exist
|
||||
boneworld.xp[name] = 1;
|
||||
boneworld.digxp[name] = 0;
|
||||
else
|
||||
local str = f:read("*a") or 1;
|
||||
local words = {};
|
||||
for w in str:gmatch("%S+") do
|
||||
words[#words+1]=w
|
||||
end
|
||||
boneworld.xp[name] = tonumber(words[1] or 1);
|
||||
boneworld.digxp[name] = tonumber(words[2] or 0);
|
||||
f:close();
|
||||
end
|
||||
end
|
||||
|
||||
if boneworld.vipdig[name] then
|
||||
if boneworld.digxp[name]<boneworld.vipdig[name] then
|
||||
boneworld.digxp[name] = boneworld.vipdig[name];
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
)
|
||||
|
||||
-- save xp
|
||||
minetest.register_on_leaveplayer(
|
||||
function(player)
|
||||
local name = player:get_player_name();
|
||||
local bonexp = boneworld.xp[name] or 1;
|
||||
local digxp = boneworld.digxp[name] or 0;
|
||||
--debug
|
||||
if bonexp > 2 or digxp>1 then -- save xp for serious players only
|
||||
local filename = worldpath .. "\\boneworld\\" .. name..".xp";
|
||||
|
||||
local f = io.open(filename, "w");
|
||||
if not f then return end
|
||||
f:write(bonexp .. " " .. digxp);
|
||||
f:close();
|
||||
else
|
||||
-- dont save, player didnt do anything
|
||||
end
|
||||
end
|
||||
|
||||
)
|
||||
|
||||
minetest.register_on_dieplayer( -- -1 bone xp each time you die; otherwise no motivation to be careful
|
||||
function(player)
|
||||
local name = player:get_player_name();
|
||||
local xp = boneworld.xp[name] or 1;
|
||||
if xp>2 then
|
||||
xp=xp-1
|
||||
else
|
||||
xp = 1;
|
||||
end
|
||||
boneworld.xp[name]=xp;
|
||||
end
|
||||
)
|
||||
|
||||
|
||||
local tweak_bones = function()
|
||||
local name = "bones:bones";
|
||||
local table = minetest.registered_nodes[name];
|
||||
--table.on_construct = on_construct;
|
||||
table.on_punch = on_punch;
|
||||
table.on_timer = on_timer;
|
||||
minetest.register_node(":"..name, table);
|
||||
end
|
||||
|
||||
minetest.after(0,tweak_bones);
|
||||
|
||||
minetest.register_chatcommand("xp", {
|
||||
description = "xp name - show bone collecting experience of target player " .. version,
|
||||
privs = {
|
||||
interact = true
|
||||
},
|
||||
func = function(name, param)
|
||||
|
||||
local msg;
|
||||
if param == "" then
|
||||
local xp = math.floor((boneworld.xp[name])*100)/100;
|
||||
local digxp = math.floor((boneworld.digxp[name])*100)/100;
|
||||
--local killxp = math.floor((boneworld.killxp[name])*100)/100;
|
||||
msg = "xp name - show experience of target player"
|
||||
.."\n# "..name .. " has " .. xp .. " bone collecting experience, ".. digxp .. " digging experience"
|
||||
.. " (can dig to ".. math.floor(200+50*math.sqrt(digxp)) .. ")"
|
||||
.. "\nTotal xp stored in bones in world is " .. math.floor(boneworld.wastedxp*100)/100;
|
||||
else
|
||||
local xp = math.floor((boneworld.xp[param] or 1)*100)/100;
|
||||
local digxp = math.floor((boneworld.digxp[param] or 0)*100)/100;
|
||||
--local killxp = math.floor((boneworld.killxp[name])*100)/100;
|
||||
msg = "xp name - show experience of target player (10.04.16)"
|
||||
.."\n# "..param .. " has " .. xp .. " bone collecting experience, ".. digxp .. " digging experience";
|
||||
|
||||
end
|
||||
minetest.chat_send_player(name, msg);
|
||||
end
|
||||
})
|
||||
|
||||
|
||||
-- limit digging to above -(200+xp*5)
|
||||
local old_is_protected = minetest.is_protected
|
||||
function minetest.is_protected(pos, name)
|
||||
|
||||
local is_protected_new = old_is_protected(pos, name);
|
||||
if pos.y>-200 or name == "" then
|
||||
|
||||
else
|
||||
--to do : digxp here!!
|
||||
local digxp = boneworld.digxp[name] or 0;
|
||||
|
||||
local maxdepth = 200+50*math.sqrt(digxp);
|
||||
if pos.y<-maxdepth then
|
||||
minetest.chat_send_player(name, "You can only dig above -"..math.floor(maxdepth) .. ". Get more dig experience to dig deeper");
|
||||
local player = minetest.get_player_by_name(name); if not player then return true end
|
||||
if pos.y<-maxdepth-5 then player:setpos({x=0,y=1,z=0}) end
|
||||
is_protected_new = true
|
||||
end
|
||||
end
|
||||
|
||||
if not is_protected_new then -- remember time, pos of last dig in unprotected area
|
||||
local t1 = minetest.get_gametime();
|
||||
local t0;
|
||||
local protect_data = boneworld.protect[name];
|
||||
if not protect_data then
|
||||
boneworld.protect[name] = {t=t1, pos=pos};
|
||||
t0 = t1;
|
||||
else
|
||||
t0 = boneworld.protect[name].t;
|
||||
end
|
||||
|
||||
if t1-t0>10 then -- "time" to remember new time, pos
|
||||
boneworld.protect[name].t = t1;
|
||||
boneworld.protect[name].pos = pos;
|
||||
end
|
||||
else -- tried to dig in protected area, teleport to last good position
|
||||
|
||||
local player = minetest.get_player_by_name(name); if not player then return true end
|
||||
local protect_data = boneworld.protect[name];
|
||||
local tpos;
|
||||
if not protect_data then -- safety check
|
||||
boneworld.protect[name] = {t=minetest.get_gametime(), pos=pos};
|
||||
tpos = pos
|
||||
else
|
||||
tpos = boneworld.protect[name].pos;
|
||||
end
|
||||
|
||||
|
||||
player:setpos({x=tpos.x,y=tpos.y+1,z=tpos.z});
|
||||
end
|
||||
|
||||
return is_protected_new;
|
||||
end
|
||||
|
||||
-- mining xp
|
||||
|
||||
-- how much mining xp digging minerals yields
|
||||
boneworld.mineralxp = {
|
||||
["default:stone"] = 0.01,
|
||||
["default:stone_with_coal"] = 0.03,
|
||||
["default:stone_with_iron"] = 0.1,
|
||||
["default:stone_with_copper"] = 0.1,
|
||||
["default:stone_with_gold"] = 0.2,
|
||||
["default:stone_with_mese"] = 0.5,
|
||||
["default:stone_with_diamond"] = 1,
|
||||
}
|
||||
|
||||
|
||||
local after_dig_node = function(pos, oldnode, oldmetadata, digger)
|
||||
local nodename = oldnode.name;
|
||||
local name = digger:get_player_name();
|
||||
local digxp = boneworld.mineralxp[nodename] or 0; digxp = digxp*0.1; -- bonus xp
|
||||
local xp = boneworld.digxp[name] or 0;
|
||||
xp = xp + digxp;
|
||||
boneworld.digxp[name] = xp;
|
||||
|
||||
-- extra reward with small probability
|
||||
if xp<100 or nodename == "default:stone" or digxp == 0 then return end
|
||||
|
||||
local P;
|
||||
if xp>10000 then
|
||||
P=0.5
|
||||
else
|
||||
P = (xp/10000+0.0001)*0.5;
|
||||
end
|
||||
|
||||
if math.random(1/P) == 1 then
|
||||
P=1;
|
||||
end
|
||||
|
||||
if P==1 then
|
||||
|
||||
local player_inv = digger:get_inventory()
|
||||
local stk = ItemStack(nodename);
|
||||
if player_inv:room_for_item("main", stk) then
|
||||
--minetest.chat_send_player(name, "Congratulations! You found extra " .. nodename)
|
||||
player_inv:add_item("main", stk)
|
||||
end
|
||||
end
|
||||
|
||||
--minetest.chat_send_all(name .. " digged " .. nodename .. " for " .. digxp .. " mining xp ")
|
||||
end
|
||||
|
||||
local set_after_dig_node = function(name)
|
||||
local tabl = minetest.registered_nodes[name];
|
||||
if not tabl then return end
|
||||
minetest.override_item(name, {after_dig_node = after_dig_node})
|
||||
end
|
||||
|
||||
minetest.after(0,
|
||||
function()
|
||||
set_after_dig_node("default:stone");
|
||||
set_after_dig_node("default:stone_with_iron");
|
||||
set_after_dig_node("default:stone_with_copper");
|
||||
set_after_dig_node("default:stone_with_coal");
|
||||
set_after_dig_node("default:stone_with_gold");
|
||||
set_after_dig_node("default:stone_with_mese");
|
||||
set_after_dig_node("default:stone_with_diamond");
|
||||
end
|
||||
)
|
@ -1,20 +0,0 @@
|
||||
Copyright (c) 2015-2016 BlockMen <blockmen2015@gmail.com>
|
||||
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty. In no
|
||||
event will the authors be held liable for any damages arising from the use of
|
||||
this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose, including
|
||||
commercial applications, and to alter it and redistribute it freely, subject to the
|
||||
following restrictions:
|
||||
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software in a
|
||||
product, an acknowledgment in the product documentation is required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not
|
||||
be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
@ -1,26 +0,0 @@
|
||||
Creatures MOB-Engine
|
||||
====================
|
||||
Copyright (c) 2015-2016 BlockMen <blockmen2015@gmail.com>
|
||||
|
||||
Version: 2.0.2
|
||||
|
||||
|
||||
This mod provides an engine, that handles the base function for MOB in Minetest.
|
||||
It offers an easy way to register MOB and allows to custom handling for the needs
|
||||
of each mob. This engine aims to be a solid base, that has a good balance between
|
||||
performance and functionality.
|
||||
See API.txt for more informations on how to use this engine for mobs.
|
||||
|
||||
|
||||
License:
|
||||
~~~~~~~~
|
||||
Code:
|
||||
(c) Copyright 2015-2016 BlockMen; modified zlib-License
|
||||
see "LICENSE.txt" for details.
|
||||
|
||||
Media(textures and other media):
|
||||
(c) Copyright (2014-2016) BlockMen; CC-BY-SA 3.0
|
||||
|
||||
Github:
|
||||
~~~~~~~
|
||||
https://github.com/BlockMen/cme/creatures
|
@ -1,148 +0,0 @@
|
||||
--= Creatures MOB-Engine (cme) =--
|
||||
-- Copyright (c) 2015-2016 BlockMen <blockmen2015@gmail.com>
|
||||
--
|
||||
-- common.lua
|
||||
--
|
||||
-- This software is provided 'as-is', without any express or implied warranty. In no
|
||||
-- event will the authors be held liable for any damages arising from the use of
|
||||
-- this software.
|
||||
--
|
||||
-- Permission is granted to anyone to use this software for any purpose, including
|
||||
-- commercial applications, and to alter it and redistribute it freely, subject to the
|
||||
-- following restrictions:
|
||||
--
|
||||
-- 1. The origin of this software must not be misrepresented; you must not
|
||||
-- claim that you wrote the original software. If you use this software in a
|
||||
-- product, an acknowledgment in the product documentation is required.
|
||||
-- 2. Altered source versions must be plainly marked as such, and must not
|
||||
-- be misrepresented as being the original software.
|
||||
-- 3. This notice may not be removed or altered from any source distribution.
|
||||
--
|
||||
|
||||
|
||||
-- constants
|
||||
nullVec = {x = 0, y = 0, z = 0}
|
||||
DEGTORAD = math.pi / 180.0
|
||||
|
||||
-- common functions
|
||||
function creatures.rnd(table, errval)
|
||||
if not errval then
|
||||
errval = false
|
||||
end
|
||||
|
||||
local res = 1000000000
|
||||
local rn = math.random(0, res - 1)
|
||||
local retval = nil
|
||||
|
||||
local psum = 0
|
||||
for s,w in pairs(table) do
|
||||
psum = psum + ((tonumber(w) or w.chance or 0) * res)
|
||||
if psum > rn then
|
||||
retval = s
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return retval
|
||||
end
|
||||
|
||||
function throw_error(msg)
|
||||
core.log("error", "#Creatures: ERROR: " .. msg)
|
||||
end
|
||||
|
||||
function creatures.compare_pos(pos1, pos2)
|
||||
if not pos1 or not pos2 then
|
||||
return
|
||||
end
|
||||
if pos1.x == pos2.x and pos1.y == pos2.y and pos1.z == pos2.z then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function creatures.findTarget(search_obj, pos, radius, search_type, ignore_mob, xray, no_count)
|
||||
local player_near = false
|
||||
local mobs = {}
|
||||
for _,obj in ipairs(core.get_objects_inside_radius(pos, radius)) do
|
||||
if obj ~= search_obj then
|
||||
if xray or core.line_of_sight(pos, obj:getpos()) == true then
|
||||
local is_player = obj:is_player()
|
||||
if is_player then
|
||||
player_near = true
|
||||
if no_count == true then
|
||||
return {}, true
|
||||
end
|
||||
end
|
||||
local entity = obj:get_luaentity()
|
||||
local isItem = (entity and entity.name == "__builtin:item") or false
|
||||
local ignore = (entity and entity.mob_name == ignore_mob and search_type ~= "mates") or false
|
||||
|
||||
if search_type == "all" then
|
||||
if not isItem and not ignore then
|
||||
table.insert(mobs, obj)
|
||||
end
|
||||
elseif search_type == "hostile" then
|
||||
if not ignore and (entity and entity.hostile == true) or is_player then
|
||||
table.insert(mobs, obj)
|
||||
end
|
||||
elseif search_type == "nonhostile" then
|
||||
if entity and not entity.hostile and not isItem and not ignore then
|
||||
table.insert(mobs, obj)
|
||||
end
|
||||
elseif search_type == "player" then
|
||||
if is_player then
|
||||
table.insert(mobs, obj)
|
||||
end
|
||||
elseif search_type == "mate" then
|
||||
if not isItem and (entity and entity.mob_name == ignore_mob) then
|
||||
table.insert(mobs, obj)
|
||||
end
|
||||
end
|
||||
end
|
||||
end --for
|
||||
end
|
||||
|
||||
return mobs,player_near
|
||||
end
|
||||
|
||||
function creatures.dropItems(pos, drops)
|
||||
if not pos or not drops then
|
||||
return
|
||||
end
|
||||
|
||||
-- convert drops table
|
||||
local tab = {}
|
||||
for _,elem in pairs(drops) do
|
||||
local name = tostring(elem[1])
|
||||
local v = elem[2]
|
||||
local chance = elem.chance
|
||||
local amount = ""
|
||||
-- check if drops depending on defined chance
|
||||
if name and chance then
|
||||
local ct = {}
|
||||
ct[name] = chance
|
||||
ct["_fake"] = 1 - chance
|
||||
local res = creatures.rnd(ct)
|
||||
if res == "_fake" then
|
||||
name = nil
|
||||
end
|
||||
end
|
||||
-- get amount
|
||||
if name and v then
|
||||
if type(v) == "table" then
|
||||
amount = math.random(v.min or 1, v.max or 1) or 1
|
||||
elseif type(v) == "number" then
|
||||
amount = v
|
||||
end
|
||||
if amount > 0 then
|
||||
amount = " " .. amount
|
||||
end
|
||||
end
|
||||
if name then
|
||||
local obj = core.add_item(pos, name .. amount)
|
||||
if not obj then
|
||||
throw_error("Could not drop item '" .. name .. amount .. "'")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,2 +0,0 @@
|
||||
default
|
||||
wool
|
@ -1 +0,0 @@
|
||||
A Mod(pack) for Minetest that provides a MOB-Engine and adds several creatures to the game.
|
@ -1,674 +0,0 @@
|
||||
--= Creatures MOB-Engine (cme) =--
|
||||
-- Copyright (c) 2015-2016 BlockMen <blockmen2015@gmail.com>
|
||||
--
|
||||
-- functions.lua
|
||||
--
|
||||
-- This software is provided 'as-is', without any express or implied warranty. In no
|
||||
-- event will the authors be held liable for any damages arising from the use of
|
||||
-- this software.
|
||||
--
|
||||
-- Permission is granted to anyone to use this software for any purpose, including
|
||||
-- commercial applications, and to alter it and redistribute it freely, subject to the
|
||||
-- following restrictions:
|
||||
--
|
||||
-- 1. The origin of this software must not be misrepresented; you must not
|
||||
-- claim that you wrote the original software. If you use this software in a
|
||||
-- product, an acknowledgment in the product documentation is required.
|
||||
-- 2. Altered source versions must be plainly marked as such, and must not
|
||||
-- be misrepresented as being the original software.
|
||||
-- 3. This notice may not be removed or altered from any source distribution.
|
||||
--
|
||||
|
||||
|
||||
-- Localizations
|
||||
local rnd = math.random
|
||||
|
||||
|
||||
local function knockback(selfOrObject, dir, old_dir, strengh)
|
||||
local object = selfOrObject
|
||||
if selfOrObject.mob_name then
|
||||
object = selfOrObject.object
|
||||
end
|
||||
local current_fmd = object:get_properties().automatic_face_movement_dir or 0
|
||||
object:set_properties({automatic_face_movement_dir = false})
|
||||
object:setvelocity(vector.add(old_dir, {x = dir.x * strengh, y = 3.5, z = dir.z * strengh}))
|
||||
old_dir.y = 0
|
||||
core.after(0.4, function()
|
||||
object:set_properties({automatic_face_movement_dir = current_fmd})
|
||||
object:setvelocity(old_dir)
|
||||
selfOrObject.falltimer = nil
|
||||
if selfOrObject.stunned == true then
|
||||
selfOrObject.stunned = false
|
||||
if selfOrObject.can_panic == true then
|
||||
selfOrObject.target = nil
|
||||
selfOrObject.mode = "_run"
|
||||
selfOrObject.modetimer = 0
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function on_hit(me)
|
||||
core.after(0.1, function()
|
||||
me:settexturemod("^[colorize:#c4000099")
|
||||
end)
|
||||
core.after(0.5, function()
|
||||
me:settexturemod("")
|
||||
end)
|
||||
end
|
||||
|
||||
local hasMoved = creatures.compare_pos
|
||||
|
||||
local function getDir(pos1, pos2)
|
||||
local retval
|
||||
if pos1 and pos2 then
|
||||
retval = {x = pos2.x - pos1.x, y = pos2.y - pos1.y, z = pos2.z - pos1.z}
|
||||
end
|
||||
return retval
|
||||
end
|
||||
|
||||
local function getDistance(vec, fly_offset)
|
||||
if not vec then
|
||||
return -1
|
||||
end
|
||||
if fly_offset then
|
||||
vec.y = vec.y + fly_offset
|
||||
end
|
||||
return math.sqrt((vec.x)^2 + (vec.y)^2 + (vec.z)^2)
|
||||
end
|
||||
|
||||
local findTarget = creatures.findTarget
|
||||
|
||||
local function update_animation(obj_ref, mode, anim_def)
|
||||
if anim_def and obj_ref then
|
||||
obj_ref:set_animation({x = anim_def.start, y = anim_def.stop}, anim_def.speed, 0, anim_def.loop)
|
||||
end
|
||||
end
|
||||
|
||||
local function update_velocity(obj_ref, dir, speed, add)
|
||||
local velo = obj_ref:getvelocity()
|
||||
if not dir.y then
|
||||
dir.y = velo.y/speed
|
||||
end
|
||||
local new_velo = {x = dir.x * speed, y = dir.y * speed or velo.y , z = dir.z * speed}
|
||||
if add then
|
||||
new_velo = vector.add(velo, new_velo)
|
||||
end
|
||||
obj_ref:setvelocity(new_velo)
|
||||
end
|
||||
|
||||
local function getYaw(dirOrYaw)
|
||||
local yaw = 360 * rnd()
|
||||
if dirOrYaw and type(dirOrYaw) == "table" then
|
||||
yaw = math.atan(dirOrYaw.z / dirOrYaw.x) + math.pi^2 - 2
|
||||
if dirOrYaw.x > 0 then
|
||||
yaw = yaw + math.pi
|
||||
end
|
||||
elseif dirOrYaw and type(dirOrYaw) == "number" then
|
||||
-- here could be a value based on given yaw
|
||||
end
|
||||
|
||||
return yaw
|
||||
end
|
||||
|
||||
local dropItems = creatures.dropItems
|
||||
|
||||
local function killMob(me, def)
|
||||
if not def then
|
||||
if me then
|
||||
me:remove()
|
||||
end
|
||||
end
|
||||
local pos = me:getpos()
|
||||
me:setvelocity(nullVec)
|
||||
me:set_properties({collisionbox = nullVec})
|
||||
me:set_hp(0)
|
||||
|
||||
if def.sounds and def.sounds.on_death then
|
||||
local death_snd = def.sounds.on_death
|
||||
core.sound_play(death_snd.name, {pos = pos, max_hear_distance = death_snd.distance or 5, gain = death_snd.gain or 1})
|
||||
end
|
||||
|
||||
if def.model.animations.death then
|
||||
local dur = def.model.animations.death.duration or 0.5
|
||||
update_animation(me, "death", def.model.animations["death"])
|
||||
core.after(dur, function()
|
||||
me:remove()
|
||||
end)
|
||||
else
|
||||
me:remove()
|
||||
end
|
||||
if def.drops then
|
||||
if type(def.drops) == "function" then
|
||||
def.drops(me:get_luaentity())
|
||||
else
|
||||
dropItems(pos, def.drops)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function limit(value, min, max)
|
||||
if value < min then
|
||||
return min
|
||||
end
|
||||
if value > max then
|
||||
return max
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
local function calcPunchDamage(obj, actual_interval, tool_caps)
|
||||
local damage = 0
|
||||
if not tool_caps or not actual_interval then
|
||||
return 0
|
||||
end
|
||||
local my_armor = obj:get_armor_groups() or {}
|
||||
for group,_ in pairs(tool_caps.damage_groups) do
|
||||
damage = damage + (tool_caps.damage_groups[group] or 0) * limit(actual_interval / tool_caps.full_punch_interval, 0.0, 1.0) * ((my_armor[group] or 0) / 100.0)
|
||||
end
|
||||
return damage or 0
|
||||
end
|
||||
|
||||
local function onDamage(self, hp)
|
||||
local me = self.object
|
||||
local def = core.registered_entities[self.mob_name]
|
||||
hp = hp or me:get_hp()
|
||||
|
||||
if hp <= 0 then
|
||||
self.stunned = true
|
||||
killMob(me, def)
|
||||
else
|
||||
on_hit(me) -- red flashing
|
||||
if def.sounds and def.sounds.on_damage then
|
||||
local dmg_snd = def.sounds.on_damage
|
||||
core.sound_play(dmg_snd.name, {pos = me:getpos(), max_hear_distance = dmg_snd.distance or 5, gain = dmg_snd.gain or 1})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function changeHP(self, value)
|
||||
local me = self.object
|
||||
local hp = me:get_hp()
|
||||
hp = hp + math.floor(value)
|
||||
me:set_hp(hp)
|
||||
if value < 0 then
|
||||
onDamage(self, hp)
|
||||
end
|
||||
end
|
||||
|
||||
local function checkWielded(wielded, itemList)
|
||||
for s,w in pairs(itemList) do
|
||||
if w == wielded then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local tool_uses = {0, 30, 110, 150, 280, 300, 500, 1000}
|
||||
local function addWearout(player, tool_def)
|
||||
if not core.setting_getbool("creative_mode") then
|
||||
local item = player:get_wielded_item()
|
||||
if tool_def and tool_def.damage_groups and tool_def.damage_groups.fleshy then
|
||||
local uses = tool_uses[tool_def.damage_groups.fleshy] or 0
|
||||
if uses > 0 then
|
||||
local wear = 65535/uses
|
||||
item:add_wear(wear)
|
||||
player:set_wielded_item(item)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function spawnParticles(...)
|
||||
end
|
||||
if core.setting_getbool("creatures_enable_particles") == true then
|
||||
spawnParticles = function(pos, velocity, texture_str)
|
||||
local vel = vector.multiply(velocity, 0.5)
|
||||
vel.y = 0
|
||||
core.add_particlespawner({
|
||||
amount = 8,
|
||||
time = 1,
|
||||
minpos = vector.add(pos, -0.7),
|
||||
maxpos = vector.add(pos, 0.7),
|
||||
minvel = vector.add(vel, {x = -0.1, y = -0.01, z = -0.1}),
|
||||
maxvel = vector.add(vel, {x = 0.1, y = 0, z = 0.1}),
|
||||
minacc = vector.new(),
|
||||
maxacc = vector.new(),
|
||||
minexptime = 0.8,
|
||||
maxexptime = 1,
|
||||
minsize = 1,
|
||||
maxsize = 2.5,
|
||||
texture = texture_str,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- --
|
||||
-- Default entity functions
|
||||
-- --
|
||||
|
||||
creatures.on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
|
||||
if self.stunned == true then
|
||||
return
|
||||
end
|
||||
|
||||
local me = self.object
|
||||
local mypos = me:getpos()
|
||||
|
||||
changeHP(self, calcPunchDamage(me, time_from_last_punch, tool_capabilities) * -1)
|
||||
if puncher then
|
||||
if self.hostile then
|
||||
self.mode = "attack"
|
||||
self.target = puncher
|
||||
end
|
||||
if time_from_last_punch >= 0.45 and self.stunned == false then
|
||||
if self.has_kockback == true then
|
||||
local v = me:getvelocity()
|
||||
v.y = 0
|
||||
if not self.can_fly then
|
||||
me:setacceleration({x = 0, y = -15, z = 0})
|
||||
end
|
||||
knockback(self, dir, v, 5)
|
||||
self.stunned = true
|
||||
end
|
||||
|
||||
-- add wearout to weapons/tools
|
||||
addWearout(puncher, tool_capabilities)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
creatures.on_rightclick = function(self, clicker)
|
||||
end
|
||||
|
||||
creatures.on_step = function(self, dtime)
|
||||
-- first get the relevant specs; exit if we don't know anything (1-3ms)
|
||||
local def = core.registered_entities[self.mob_name]
|
||||
if not def then
|
||||
throw_error("Can't load creature-definition")
|
||||
return
|
||||
end
|
||||
|
||||
-- timer updates
|
||||
self.lifetimer = self.lifetimer + dtime
|
||||
self.modetimer = self.modetimer + dtime
|
||||
self.soundtimer = self.soundtimer + dtime
|
||||
self.yawtimer = self.yawtimer + dtime
|
||||
self.nodetimer = self.nodetimer + dtime
|
||||
self.followtimer = self.followtimer + dtime
|
||||
if self.envtimer then
|
||||
self.envtimer = self.envtimer + dtime
|
||||
end
|
||||
if self.falltimer then
|
||||
self.falltimer = self.falltimer + dtime
|
||||
end
|
||||
if self.searchtimer then
|
||||
self.searchtimer = self.searchtimer + dtime
|
||||
end
|
||||
if self.attacktimer then
|
||||
self.attacktimer = self.attacktimer + dtime
|
||||
end
|
||||
if self.swimtimer then
|
||||
self.swimtimer = self.swimtimer + dtime
|
||||
end
|
||||
|
||||
-- main
|
||||
if self.stunned == true then
|
||||
return
|
||||
end
|
||||
|
||||
if self.lifetimer > def.stats.lifetime and not (self.mode == "attack" and self.target) then
|
||||
self.lifetimer = 0
|
||||
if not self.tamed or (self.tamed and def.stats.dies_when_tamed) then
|
||||
killMob(self.object, def)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- localize some things
|
||||
local modes = def.modes
|
||||
local current_mode = self.mode
|
||||
local me = self.object
|
||||
local current_pos = me:getpos()
|
||||
current_pos.y = current_pos.y + 0.5
|
||||
local moved = hasMoved(current_pos, self.last_pos) or false
|
||||
local fallen = false
|
||||
|
||||
-- Update pos and current node if necessary
|
||||
if moved == true or not self.last_pos then
|
||||
-- for falldamage
|
||||
if self.has_falldamage and self.last_pos and not self.in_water then
|
||||
local dist = math.abs(current_pos.y - self.last_pos.y)
|
||||
if dist > 0 then
|
||||
self.fall_dist = self.fall_dist - dist
|
||||
if not self.falltimer then
|
||||
self.falltimer = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.last_pos = current_pos
|
||||
if self.nodetimer > 0.2 then
|
||||
self.nodetimer = 0
|
||||
local current_node = core.get_node_or_nil(current_pos)
|
||||
self.last_node = current_node
|
||||
if def.stats.light then
|
||||
local wtime = core.get_timeofday()
|
||||
local llvl = core.get_node_light({x = current_pos.x, y = current_pos.y + 0.5, z = current_pos.z}) or 0
|
||||
self.last_llvl = llvl
|
||||
end
|
||||
end
|
||||
else
|
||||
if (modes[current_mode].moving_speed or 0) > 0 then
|
||||
update_velocity(me, nullVec, 0)
|
||||
if modes["idle"] and not (current_mode == "attack" or current_mode == "follow") then
|
||||
current_mode = "idle"
|
||||
self.modetimer = 0
|
||||
end
|
||||
end
|
||||
if self.fall_dist < 0 then
|
||||
fallen = true
|
||||
end
|
||||
end
|
||||
|
||||
if fallen then
|
||||
local falltime = tonumber(self.falltimer) or 0
|
||||
local dist = math.abs(self.fall_dist) or 0
|
||||
self.falltimer = 0
|
||||
self.fall_dist = 0
|
||||
fallen = false
|
||||
|
||||
local damage = 0
|
||||
if dist > 3 and not self.in_water and falltime/dist < 0.2 then
|
||||
damage = dist - 3
|
||||
end
|
||||
|
||||
-- damage by calced value
|
||||
if damage > 0 then
|
||||
changeHP(self, damage * -1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- special mode handling
|
||||
-- check distance to target
|
||||
if self.target and self.followtimer > 0.6 then
|
||||
self.followtimer = 0
|
||||
local p2 = self.target:getpos()
|
||||
local dir = getDir(current_pos, p2)
|
||||
local offset
|
||||
if self.can_fly then
|
||||
offset = modes["fly"].target_offset
|
||||
end
|
||||
local dist = getDistance(dir, offset)
|
||||
local radius
|
||||
if self.hostile and def.combat then
|
||||
radius = def.combat.search_radius
|
||||
elseif modes["follow"] then
|
||||
radius = modes["follow"].radius
|
||||
end
|
||||
if dist == -1 or dist > (radius or 5) then
|
||||
self.target = nil
|
||||
current_mode = ""
|
||||
elseif dist > -1 and self.hostile and dist < def.combat.attack_radius then
|
||||
-- attack
|
||||
if self.attacktimer > def.combat.attack_speed then
|
||||
self.attacktimer = 0
|
||||
if core.line_of_sight(current_pos, p2) == true then
|
||||
self.target:punch(me, 1.0, {
|
||||
full_punch_interval = def.combat.attack_speed,
|
||||
damage_groups = {fleshy = def.combat.attack_damage}
|
||||
})
|
||||
end
|
||||
update_velocity(me, self.dir, 0)
|
||||
end
|
||||
else
|
||||
if current_mode == "attack" or current_mode == "follow" then
|
||||
self.dir = vector.normalize(dir)
|
||||
me:setyaw(getYaw(dir))
|
||||
if self.in_water then
|
||||
self.dir.y = me:getvelocity().y
|
||||
end
|
||||
update_velocity(me, self.dir, modes[current_mode].moving_speed or 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- search a target (1-2ms)
|
||||
if not self.target and ((self.hostile and def.combat.search_enemy) or modes["follow"]) and current_mode ~= "_run" then
|
||||
local timer
|
||||
if self.hostile then
|
||||
timer = def.combat.search_timer or 2
|
||||
elseif modes["follow"] then
|
||||
timer = modes["follow"].timer
|
||||
end
|
||||
if self.searchtimer > (timer or 4) then
|
||||
self.searchtimer = 0
|
||||
local targets = {}
|
||||
if self.hostile then
|
||||
targets = findTarget(me, current_pos, def.combat.search_radius, def.combat.search_type, def.combat.search_xray)
|
||||
else
|
||||
targets = findTarget(me, current_pos, modes["follow"].radius or 5, "player")
|
||||
end
|
||||
if #targets > 1 then
|
||||
self.target = targets[rnd(1, #targets)]
|
||||
elseif #targets == 1 then
|
||||
self.target = targets[1]
|
||||
end
|
||||
if self.target then
|
||||
if self.hostile and modes["attack"] then
|
||||
current_mode = "attack"
|
||||
else
|
||||
local name = self.target:get_wielded_item():get_name()
|
||||
if name and checkWielded(name, modes["follow"].items) == true then
|
||||
current_mode = "follow"
|
||||
self.modetimer = 0
|
||||
else
|
||||
self.target = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if current_mode == "eat" and not self.eat_node then
|
||||
local nodes = modes[current_mode].nodes
|
||||
local p = {x = current_pos.x, y = current_pos.y - 1, z = current_pos.z}
|
||||
local sn = core.get_node_or_nil(p)
|
||||
local eat_node
|
||||
for _,name in pairs(nodes) do
|
||||
if name == self.last_node.name then
|
||||
eat_node = current_pos
|
||||
break
|
||||
elseif sn and sn.name == name then
|
||||
eat_node = p
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not eat_node then
|
||||
current_mode = "idle"
|
||||
else
|
||||
self.eat_node = eat_node
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- further mode handling
|
||||
-- update mode
|
||||
if current_mode ~= "attack" and
|
||||
(current_mode == "" or self.modetimer > (modes[current_mode].duration or 4)) then
|
||||
self.modetimer = 0
|
||||
|
||||
local new_mode = creatures.rnd(modes) or "idle"
|
||||
if new_mode == "eat" and self.in_water == true then
|
||||
new_mode = "idle"
|
||||
end
|
||||
if current_mode == "follow" and rnd(1, 10) < 3 then
|
||||
new_mode = current_mode
|
||||
elseif current_mode == "follow" then
|
||||
-- "lock" searching a little bit
|
||||
self.searchtimer = rnd(5, 8) * -1
|
||||
self.target = nil
|
||||
end
|
||||
current_mode = new_mode
|
||||
|
||||
-- change eaten node when mode changes
|
||||
if self.eat_node then
|
||||
local n = core.get_node_or_nil(self.eat_node)
|
||||
local nnn = n.name
|
||||
local def = core.registered_nodes[n.name]
|
||||
local sounds
|
||||
if def then
|
||||
if def.drop and type(def.drop) == "string" then
|
||||
nnn = def.drop
|
||||
elseif not def.walkable then
|
||||
nnn = "air"
|
||||
end
|
||||
end
|
||||
if nnn and nnn ~= n.name and core.registered_nodes[nnn] then
|
||||
core.set_node(self.eat_node, {name = nnn})
|
||||
if not sounds then
|
||||
sounds = def.sounds
|
||||
end
|
||||
if sounds and sounds.dug then
|
||||
core.sound_play(sounds.dug, {pos = self.eat_node, max_hear_distance = 5, gain = 1})
|
||||
end
|
||||
end
|
||||
self.eat_node = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- mode has changes, do things
|
||||
if current_mode ~= self.last_mode then
|
||||
self.last_mode = current_mode
|
||||
|
||||
local moving_speed = modes[current_mode].moving_speed or 0
|
||||
if moving_speed > 0 then
|
||||
local yaw = (getYaw(me:getyaw()) + 90.0) * DEGTORAD
|
||||
me:setyaw(yaw + 4.73)
|
||||
self.dir = {x = math.cos(yaw), y = 0, z = math.sin(yaw)}
|
||||
if self.can_fly then
|
||||
if current_pos.y >= (modes["fly"].max_height or 50) and not self.target then
|
||||
self.dir.y = -0.5
|
||||
else
|
||||
self.dir.y = (rnd() - 0.5)
|
||||
end
|
||||
end
|
||||
|
||||
-- reduce speed in water
|
||||
if self.in_water == true then
|
||||
moving_speed = moving_speed * 0.7
|
||||
end
|
||||
else
|
||||
self.dir = nullVec
|
||||
end
|
||||
|
||||
update_velocity(me, self.dir, moving_speed)
|
||||
local anim_def = def.model.animations[current_mode]
|
||||
if self.in_water and def.model.animations["swim"] then
|
||||
anim_def = def.model.animations["swim"]
|
||||
end
|
||||
update_animation(me, current_mode, anim_def)
|
||||
end
|
||||
|
||||
-- update yaw
|
||||
if current_mode ~= "attack" and current_mode ~= "follow" and
|
||||
(modes[current_mode].update_yaw or 0) > 0 and
|
||||
self.yawtimer > (modes[current_mode].update_yaw or 4) then
|
||||
self.yawtimer = 0
|
||||
local mod = nil
|
||||
if current_mode == "_run" then
|
||||
mod = me:getyaw()
|
||||
end
|
||||
local yaw = (getYaw(mod) + 90.0) * DEGTORAD
|
||||
me:setyaw(yaw + 4.73)
|
||||
local moving_speed = modes[current_mode].moving_speed or 0
|
||||
if moving_speed > 0 then
|
||||
self.dir = {x = math.cos(yaw), y = nil, z = math.sin(yaw)}
|
||||
update_velocity(me, self.dir, moving_speed)
|
||||
end
|
||||
end
|
||||
|
||||
--swim
|
||||
if self.can_swim and self.swimtimer > 0.8 and self.last_node then
|
||||
self.swimtimer = 0
|
||||
local name = self.last_node.name
|
||||
if name then
|
||||
if name == "default:water_source" then
|
||||
self.air_cnt = 0
|
||||
local vel = me:getvelocity()
|
||||
update_velocity(me, {x = vel.x, y = 0.9, z = vel.z}, 1)
|
||||
me:setacceleration({x = 0, y = -1.2, z = 0})
|
||||
self.in_water = true
|
||||
-- play swimming sounds
|
||||
if def.sounds and def.sounds.swim then
|
||||
local swim_snd = def.sounds.swim
|
||||
core.sound_play(swim_snd.name, {pos = current_pos, gain = swim_snd.gain or 1, max_hear_distance = swim_snd.distance or 10})
|
||||
end
|
||||
spawnParticles(current_pos, vel, "bubble.png")
|
||||
else
|
||||
self.air_cnt = self.air_cnt + 1
|
||||
if self.in_water == true and self.air_cnt > 5 then
|
||||
self.in_water = false
|
||||
if not self.can_fly then
|
||||
me:setacceleration({x = 0, y = -15, z = 0})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Add damage when drowning or in lava
|
||||
if self.env_damage and self.envtimer > 1 and self.last_node then
|
||||
self.envtimer = 0
|
||||
local name = self.last_node.name
|
||||
if not self.can_swim and name == "default:water_source" then
|
||||
changeHP(self, -1)
|
||||
elseif self.can_burn then
|
||||
if name == "fire:basic_flame" or name == "default:lava_source" then
|
||||
changeHP(self, -4)
|
||||
end
|
||||
end
|
||||
|
||||
-- add damage when light is too bright or too dark
|
||||
local tod = core.get_timeofday() * 24000
|
||||
if self.last_llvl and self.can_burn and self.last_llvl > (def.stats.light.max or 15) and tod < 18000 then
|
||||
changeHP(self, -1)
|
||||
elseif self.last_llvl and self.last_llvl < (def.stats.light.min or 0) then
|
||||
changeHP(self, -2)
|
||||
end
|
||||
end
|
||||
|
||||
-- Random sounds
|
||||
if def.sounds and def.sounds.random[current_mode] then
|
||||
local rnd_sound = def.sounds.random[current_mode]
|
||||
if not self.snd_rnd_time then
|
||||
self.snd_rnd_time = rnd((rnd_sound.time_min or 5), (rnd_sound.time_max or 35))
|
||||
end
|
||||
if rnd_sound and self.soundtimer > self.snd_rnd_time + rnd() then
|
||||
self.soundtimer = 0
|
||||
self.snd_rnd_time = nil
|
||||
core.sound_play(rnd_sound.name, {pos = current_pos, gain = rnd_sound.gain or 1, max_hear_distance = rnd_sound.distance or 30})
|
||||
end
|
||||
end
|
||||
|
||||
self.mode = current_mode
|
||||
end
|
||||
|
||||
|
||||
creatures.get_staticdata = function(self)
|
||||
return {
|
||||
hp = self.object:get_hp(),
|
||||
mode = self.mode,
|
||||
tamed = self.tamed,
|
||||
modetimer = self.modetimer,
|
||||
lifetimer = self.lifetimer,
|
||||
soundtimer = self.soundtimer,
|
||||
fall_dist = self.fall_dist,
|
||||
in_water = self.in_water,
|
||||
}
|
||||
end
|
@ -1,33 +0,0 @@
|
||||
--= Creatures MOB-Engine (cme) =--
|
||||
-- Copyright (c) 2015-2016 BlockMen <blockmen2015@gmail.com>
|
||||
--
|
||||
-- init.lua
|
||||
--
|
||||
-- This software is provided 'as-is', without any express or implied warranty. In no
|
||||
-- event will the authors be held liable for any damages arising from the use of
|
||||
-- this software.
|
||||
--
|
||||
-- Permission is granted to anyone to use this software for any purpose, including
|
||||
-- commercial applications, and to alter it and redistribute it freely, subject to the
|
||||
-- following restrictions:
|
||||
--
|
||||
-- 1. The origin of this software must not be misrepresented; you must not
|
||||
-- claim that you wrote the original software. If you use this software in a
|
||||
-- product, an acknowledgment in the product documentation is required.
|
||||
-- 2. Altered source versions must be plainly marked as such, and must not
|
||||
-- be misrepresented as being the original software.
|
||||
-- 3. This notice may not be removed or altered from any source distribution.
|
||||
--
|
||||
|
||||
|
||||
creatures = {}
|
||||
|
||||
local modpath = core.get_modpath("creatures")
|
||||
|
||||
-- API and common functions
|
||||
dofile(modpath .."/common.lua")
|
||||
dofile(modpath .."/functions.lua")
|
||||
dofile(modpath .."/register.lua")
|
||||
|
||||
-- Common items
|
||||
--dofile(modpath .."/items.lua")
|
@ -1,39 +0,0 @@
|
||||
--= Creatures MOB-Engine (cme) =--
|
||||
-- Copyright (c) 2015-2016 BlockMen <blockmen2015@gmail.com>
|
||||
--
|
||||
-- items.lua
|
||||
--
|
||||
-- This software is provided 'as-is', without any express or implied warranty. In no
|
||||
-- event will the authors be held liable for any damages arising from the use of
|
||||
-- this software.
|
||||
--
|
||||
-- Permission is granted to anyone to use this software for any purpose, including
|
||||
-- commercial applications, and to alter it and redistribute it freely, subject to the
|
||||
-- following restrictions:
|
||||
--
|
||||
-- 1. The origin of this software must not be misrepresented; you must not
|
||||
-- claim that you wrote the original software. If you use this software in a
|
||||
-- product, an acknowledgment in the product documentation is required.
|
||||
-- 2. Altered source versions must be plainly marked as such, and must not
|
||||
-- be misrepresented as being the original software.
|
||||
-- 3. This notice may not be removed or altered from any source distribution.
|
||||
--
|
||||
|
||||
|
||||
core.register_craftitem("creatures:flesh", {
|
||||
description = "Flesh",
|
||||
inventory_image = "creatures_flesh.png",
|
||||
on_use = core.item_eat(2),
|
||||
})
|
||||
|
||||
core.register_craftitem("creatures:meat", {
|
||||
description = "Cooked Meat",
|
||||
inventory_image = "creatures_meat.png",
|
||||
on_use = core.item_eat(4),
|
||||
})
|
||||
|
||||
core.register_craft({
|
||||
type = "cooking",
|
||||
output = "creatures:meat",
|
||||
recipe = "creatures:flesh",
|
||||
})
|
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 3.1 KiB |
@ -1,580 +0,0 @@
|
||||
--= Creatures MOB-Engine (cme) =--
|
||||
-- Copyright (c) 2015-2016 BlockMen <blockmen2015@gmail.com>
|
||||
--
|
||||
-- register.lua
|
||||
--
|
||||
-- This software is provided 'as-is', without any express or implied warranty. In no
|
||||
-- event will the authors be held liable for any damages arising from the use of
|
||||
-- this software.
|
||||
--
|
||||
-- Permission is granted to anyone to use this software for any purpose, including
|
||||
-- commercial applications, and to alter it and redistribute it freely, subject to the
|
||||
-- following restrictions:
|
||||
--
|
||||
-- 1. The origin of this software must not be misrepresented; you must not
|
||||
-- claim that you wrote the original software. If you use this software in a
|
||||
-- product, an acknowledgment in the product documentation is required.
|
||||
-- 2. Altered source versions must be plainly marked as such, and must not
|
||||
-- be misrepresented as being the original software.
|
||||
-- 3. This notice may not be removed or altered from any source distribution.
|
||||
--
|
||||
|
||||
|
||||
local allow_hostile = core.setting_getbool("only_peaceful_mobs") ~= true
|
||||
|
||||
local function translate_def(def)
|
||||
local new_def = {
|
||||
physical = true,
|
||||
visual = "mesh",
|
||||
stepheight = 0.6, -- ensure we get over slabs/stairs
|
||||
automatic_face_movement_dir = def.model.rotation or 0.0,
|
||||
|
||||
mesh = def.model.mesh,
|
||||
textures = def.model.textures,
|
||||
collisionbox = def.model.collisionbox or {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
|
||||
visual_size = def.model.scale or {x = 1, y = 1},
|
||||
backface_culling = def.model.backface_culling or false,
|
||||
collide_with_objects = def.model.collide_with_objects or true,
|
||||
makes_footstep_sound = true,
|
||||
|
||||
stats = def.stats,
|
||||
model = def.model,
|
||||
sounds = def.sounds,
|
||||
combat = def.combat,
|
||||
modes = {},
|
||||
drops = def.drops,
|
||||
}
|
||||
|
||||
-- Tanslate modes to better accessable format
|
||||
for mn,def in pairs(def.modes) do
|
||||
local name = tostring(mn)
|
||||
if name ~= "update_time" then
|
||||
new_def.modes[name] = def
|
||||
--if name == "attack" then new_def.modes[name].chance = 0 end
|
||||
end
|
||||
end
|
||||
-- insert special mode "_run" which is used when in panic
|
||||
if def.stats.can_panic then
|
||||
if def.modes.walk then
|
||||
local new = table.copy(new_def.modes["walk"])
|
||||
new.chance = 0
|
||||
new.duration = 3
|
||||
new.moving_speed = new.moving_speed * 2
|
||||
if def.modes.panic and def.modes.panic.moving_speed then
|
||||
new.moving_speed = def.modes.panic.moving_speed
|
||||
end
|
||||
new.update_yaw = 0.7
|
||||
new_def.modes["_run"] = new
|
||||
local new_anim = def.model.animations.panic
|
||||
if not new_anim then
|
||||
new_anim = table.copy(def.model.animations.walk)
|
||||
new_anim.speed = new_anim.speed * 2
|
||||
end
|
||||
new_def.model.animations._run = new_anim
|
||||
end
|
||||
end
|
||||
|
||||
if def.stats.can_jump and type(def.stats.can_jump) == "number" then
|
||||
if def.stats.can_jump > 0 then
|
||||
new_def.stepheight = def.stats.can_jump + 0.1
|
||||
end
|
||||
end
|
||||
|
||||
if def.stats.sneaky or def.stats.can_fly then
|
||||
new_def.makes_footstep_sound = false
|
||||
end
|
||||
|
||||
|
||||
new_def.get_staticdata = function(self)
|
||||
local main_tab = creatures.get_staticdata(self)
|
||||
-- is own staticdata function defined? If so, merge results
|
||||
if def.get_staticdata then
|
||||
local data = def.get_staticdata(self)
|
||||
if data and type(data) == "table" then
|
||||
for s,w in pairs(data) do
|
||||
main_tab[s] = w
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- return data serialized
|
||||
return core.serialize(main_tab)
|
||||
end
|
||||
|
||||
new_def.on_activate = function(self, staticdata)
|
||||
|
||||
-- Add everything we need as basis for the engine
|
||||
self.mob_name = def.name
|
||||
self.hp = def.stats.hp
|
||||
self.hostile = def.stats.hostile
|
||||
self.mode = ""
|
||||
self.stunned = false -- if knocked back or hit do nothing else
|
||||
|
||||
self.has_kockback = def.stats.has_kockback
|
||||
self.has_falldamage = def.stats.has_falldamage
|
||||
self.can_swim = def.stats.can_swim
|
||||
self.can_fly = def.stats.can_fly
|
||||
self.can_burn = def.stats.can_burn
|
||||
self.can_panic = def.stats.can_panic == true and def.modes.walk ~= nil
|
||||
--self.is_tamed = nil
|
||||
--self.target = nil
|
||||
self.dir = {x = 0, z = 0}
|
||||
|
||||
--self.last_pos = nil (was nullVec)
|
||||
--self.last_node = nil
|
||||
--self.last_llvl = 0
|
||||
self.fall_dist = 0
|
||||
self.air_cnt = 0
|
||||
|
||||
|
||||
-- Timers
|
||||
self.lifetimer = 0
|
||||
self.modetimer = math.random()--0
|
||||
self.soundtimer = math.random()
|
||||
self.nodetimer = 2 -- ensure we get the first step
|
||||
self.yawtimer = math.random() * 2--0
|
||||
self.followtimer = 0
|
||||
if self.can_swim then
|
||||
self.swimtimer = 2 -- ensure we get the first step
|
||||
-- self.in_water = nil
|
||||
end
|
||||
if self.hostile then
|
||||
self.attacktimer = 0
|
||||
end
|
||||
if self.hostile or def.modes.follow then
|
||||
self.searchtimer = 0
|
||||
end
|
||||
if self.can_burn or not def.stats.can_swim or self.has_falldamage then
|
||||
self.env_damage = true
|
||||
self.envtimer = 0
|
||||
end
|
||||
|
||||
-- Other things
|
||||
|
||||
|
||||
if staticdata then
|
||||
local tab = core.deserialize(staticdata)
|
||||
if tab and type(tab) == "table" then
|
||||
for s,w in pairs(tab) do
|
||||
self[tostring(s)] = w
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- check we got a valid mode
|
||||
if not new_def.modes[self.mode] or (new_def.modes[self.mode].chance or 0) <= 0 then
|
||||
self.mode = "idle"
|
||||
end
|
||||
|
||||
if not self.can_fly then
|
||||
if not self.in_water then
|
||||
self.object:setacceleration({x = 0, y = -15, z = 0})
|
||||
end
|
||||
end
|
||||
|
||||
-- check if falling and set velocity only 0 when not falling
|
||||
if self.fall_dist == 0 then
|
||||
self.object:setvelocity(nullVec)
|
||||
end
|
||||
|
||||
self.object:set_hp(self.hp)
|
||||
|
||||
if not core.setting_getbool("enable_damage") then
|
||||
self.hostile = false
|
||||
end
|
||||
|
||||
-- immortal is needed to disable clientside smokepuff shit
|
||||
self.object:set_armor_groups({fleshy = 100, immortal = 1})
|
||||
|
||||
-- call custom on_activate if defined
|
||||
if def.on_activate then
|
||||
def.on_activate(self, staticdata)
|
||||
end
|
||||
end
|
||||
|
||||
new_def.on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
|
||||
if def.on_punch and def.on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir) == true then
|
||||
return
|
||||
end
|
||||
|
||||
creatures.on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir)
|
||||
end
|
||||
|
||||
new_def.on_rightclick = function(self, clicker)
|
||||
if def.on_rightclick and def.on_rightclick(self, clicker) == true then
|
||||
return
|
||||
end
|
||||
|
||||
creatures.on_rightclick(self, clicker)
|
||||
end
|
||||
|
||||
new_def.on_step = function(self, dtime)
|
||||
if def.on_step and def.on_step(self, dtime) == true then
|
||||
return
|
||||
end
|
||||
|
||||
creatures.on_step(self, dtime)
|
||||
end
|
||||
|
||||
return new_def
|
||||
end
|
||||
|
||||
function creatures.register_mob(def) -- returns true if sucessfull
|
||||
if not def or not def.name then
|
||||
throw_error("Can't register mob. No name or Definition given.")
|
||||
return false
|
||||
end
|
||||
|
||||
local mob_def = translate_def(def)
|
||||
|
||||
core.register_entity(":" .. def.name, mob_def)
|
||||
|
||||
-- register spawn
|
||||
if def.spawning and not (def.stats.hostile and not allow_hostile) then
|
||||
local spawn_def = def.spawning
|
||||
spawn_def.mob_name = def.name
|
||||
spawn_def.mob_size = def.model.collisionbox
|
||||
if creatures.register_spawn(spawn_def) ~= true then
|
||||
throw_error("Couldn't register spawning for '" .. def.name .. "'")
|
||||
end
|
||||
|
||||
if spawn_def.spawn_egg then
|
||||
local egg_def = def.spawning.spawn_egg
|
||||
egg_def.mob_name = def.name
|
||||
egg_def.box = def.model.collisionbox
|
||||
creatures.register_egg(egg_def)
|
||||
end
|
||||
|
||||
if spawn_def.spawner then
|
||||
local spawner_def = def.spawning.spawner
|
||||
spawner_def.mob_name = def.name
|
||||
spawner_def.range = spawner_def.range or 4
|
||||
spawner_def.number = spawner_def.number or 6
|
||||
spawner_def.model = def.model
|
||||
creatures.register_spawner(spawner_def)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
local function inRange(min_max, value)
|
||||
if not value or not min_max or not min_max.min or not min_max.max then
|
||||
return false
|
||||
end
|
||||
if (value >= min_max.min and value <= min_max.max) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function checkSpace(pos, height)
|
||||
for i = 0, height do
|
||||
local n = core.get_node_or_nil({x = pos.x, y = pos.y + i, z = pos.z})
|
||||
if not n or n.name ~= "air" then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local time_taker = 0
|
||||
local function step(tick)
|
||||
core.after(tick, step, tick)
|
||||
time_taker = time_taker + tick
|
||||
end
|
||||
step(0.5)
|
||||
|
||||
local function stopABMFlood()
|
||||
if time_taker == 0 then
|
||||
return true
|
||||
end
|
||||
time_taker = 0
|
||||
end
|
||||
|
||||
local function groupSpawn(pos, mob, group, nodes, range, max_loops)
|
||||
local cnt = 0
|
||||
local cnt2 = 0
|
||||
|
||||
local nodes = core.find_nodes_in_area({x = pos.x - range, y = pos.y - range, z = pos.z - range},
|
||||
{x = pos.x + range, y = pos.y, z = pos.z + range}, nodes)
|
||||
local number = #nodes - 1
|
||||
if max_loops and type(max_loops) == "number" then
|
||||
number = max_loops
|
||||
end
|
||||
while cnt < group and cnt2 < number do
|
||||
cnt2 = cnt2 + 1
|
||||
local p = nodes[math.random(1, number)]
|
||||
p.y = p.y + 1
|
||||
if checkSpace(p, mob.size) == true then
|
||||
cnt = cnt + 1
|
||||
core.add_entity(p, mob.name)
|
||||
end
|
||||
end
|
||||
if cnt < group then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function creatures.register_spawn(spawn_def)
|
||||
if not spawn_def or not spawn_def.abm_nodes then
|
||||
throw_error("No valid definition for given.")
|
||||
return false
|
||||
end
|
||||
|
||||
if not spawn_def.abm_nodes.neighbors then
|
||||
spawn_def.abm_nodes.neighbors = {}
|
||||
end
|
||||
table.insert(spawn_def.abm_nodes.neighbors, "air")
|
||||
|
||||
core.register_abm({
|
||||
nodenames = spawn_def.abm_nodes.spawn_on,
|
||||
neighbors = spawn_def.abm_nodes.neighbors,
|
||||
interval = spawn_def.abm_interval or 44,
|
||||
chance = spawn_def.abm_chance or 7000,
|
||||
catch_up = false,
|
||||
action = function(pos, node, active_object_count, active_object_count_wider)
|
||||
-- prevent abm-"feature"
|
||||
if stopABMFlood() == true then
|
||||
return
|
||||
end
|
||||
|
||||
-- time check
|
||||
local tod = core.get_timeofday() * 24000
|
||||
if spawn_def.time_range then
|
||||
local wanted_res = false
|
||||
local range = table.copy(spawn_def.time_range)
|
||||
if range.min > range.max and range.min <= tod then
|
||||
wanted_res = true
|
||||
end
|
||||
if inRange(range, tod) == wanted_res then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- position check
|
||||
if spawn_def.height_limit and not inRange(spawn_def.height_limit, pos.y) then
|
||||
return
|
||||
end
|
||||
|
||||
-- light check
|
||||
pos.y = pos.y + 1
|
||||
local llvl = core.get_node_light(pos)
|
||||
if spawn_def.light and not inRange(spawn_def.light, llvl) then
|
||||
return
|
||||
end
|
||||
-- creature count check
|
||||
local max
|
||||
if active_object_count_wider > (spawn_def.max_number or 1) then
|
||||
local mates_num = #creatures.findTarget(nil, pos, 16, "mate", spawn_def.mob_name, true)
|
||||
if (mates_num or 0) >= spawn_def.max_number then
|
||||
return
|
||||
else
|
||||
max = spawn_def.max_number - mates_num
|
||||
end
|
||||
end
|
||||
|
||||
-- ok everything seems fine, spawn creature
|
||||
local height_min = (spawn_def.mob_size[5] or 2) - (spawn_def.mob_size[2] or 0)
|
||||
height_min = math.ceil(height_min)
|
||||
|
||||
local number = 0
|
||||
if type(spawn_def.number) == "table" then
|
||||
number = math.random(spawn_def.number.min, spawn_def.number.max)
|
||||
else
|
||||
number = spawn_def.number or 1
|
||||
end
|
||||
|
||||
if max and number > max then
|
||||
number = max
|
||||
end
|
||||
|
||||
if number > 1 then
|
||||
groupSpawn(pos, {name = spawn_def.mob_name, size = height_min}, number, spawn_def.abm_nodes.spawn_on, 5)
|
||||
else
|
||||
-- space check
|
||||
if not checkSpace(pos, height_min) then
|
||||
return
|
||||
end
|
||||
core.add_entity(pos, spawn_def.mob_name)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function eggSpawn(itemstack, placer, pointed_thing, egg_def)
|
||||
if pointed_thing.type == "node" then
|
||||
local pos = pointed_thing.above
|
||||
pos.y = pos.y + 0.5
|
||||
local height = (egg_def.box[5] or 2) - (egg_def.box[2] or 0)
|
||||
if checkSpace(pos, height) == true then
|
||||
core.add_entity(pos, egg_def.mob_name)
|
||||
if core.setting_getbool("creative_mode") ~= true then
|
||||
itemstack:take_item()
|
||||
end
|
||||
end
|
||||
return itemstack
|
||||
end
|
||||
end
|
||||
|
||||
function creatures.register_egg(egg_def)
|
||||
if not egg_def or not egg_def.mob_name or not egg_def.box then
|
||||
throw_error("Can't register Spawn-Egg. Not enough parameters given.")
|
||||
return false
|
||||
end
|
||||
|
||||
core.register_craftitem(":" .. egg_def.mob_name .. "_spawn_egg", {
|
||||
description = egg_def.description or egg_def.mob_name .. " spawn egg",
|
||||
inventory_image = egg_def.texture or "creatures_spawn_egg.png",
|
||||
liquids_pointable = false,
|
||||
on_place = function(itemstack, placer, pointed_thing)
|
||||
return eggSpawn(itemstack, placer, pointed_thing, egg_def)
|
||||
end,
|
||||
})
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
local function makeSpawnerEntiy(mob_name, model)
|
||||
core.register_entity(":" .. mob_name .. "_spawner_dummy", {
|
||||
hp_max = 1,
|
||||
physical = false,
|
||||
collide_with_objects = false,
|
||||
collisionbox = nullVec,
|
||||
visual = "mesh",
|
||||
visual_size = {x = 0.42, y = 0.42},
|
||||
mesh = model.mesh,
|
||||
textures = model.textures,
|
||||
makes_footstep_sound = false,
|
||||
automatic_rotate = math.pi * -2.9,
|
||||
mob_name = "_" .. mob_name .. "_dummy",
|
||||
|
||||
on_activate = function(self)
|
||||
self.timer = 0
|
||||
self.object:setvelocity(nullVec)
|
||||
self.object:setacceleration(nullVec)
|
||||
self.object:set_armor_groups({immortal = 1})
|
||||
--self.object:set_bone_position("Root", nullVec, {x=45,y=0,z=0})
|
||||
end,
|
||||
|
||||
on_step = function(self, dtime)
|
||||
self.timer = self.timer + dtime
|
||||
if self.timer > 30 then
|
||||
self.timer = 0
|
||||
local n = core.get_node_or_nil(self.object:getpos())
|
||||
if n and n.name and n.name ~= mob_name .. "_spawner" then
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
local function spawnerSpawn(pos, spawner_def)
|
||||
local mates = creatures.findTarget(nil, pos, spawner_def.range, "mate", spawner_def.mob_name, true) or {}
|
||||
if #mates >= spawner_def.number then
|
||||
return false
|
||||
end
|
||||
local number_max = spawner_def.number - #mates
|
||||
|
||||
local rh = math.floor(spawner_def.range/2)
|
||||
local area = {
|
||||
min = {x = pos.x - rh, y=pos.y - rh, z = pos.z - rh},
|
||||
max = {x = pos.x + rh, y=pos.y + rh - spawner_def.height - 1, z = pos.z + rh}
|
||||
}
|
||||
|
||||
local height = area.max.y - area.min.y
|
||||
local cnt = 0
|
||||
for i = 0, height do
|
||||
if cnt >= number_max then
|
||||
break
|
||||
end
|
||||
local p = {x = math.random(area.min.x, area.max.x), y = area.min.y + i, z = math.random(area.min.z, area.max.z)}
|
||||
local n = core.get_node_or_nil(p)
|
||||
if n and n.name then
|
||||
local walkable = core.registered_nodes[n.name].walkable or false
|
||||
p.y = p.y + 1
|
||||
if walkable and checkSpace(p, spawner_def.height) == true then
|
||||
local llvl = core.get_node_light(p)
|
||||
if not spawner_def.light or (spawner_def.light and inRange(spawner_def.light, llvl)) then
|
||||
cnt = cnt + 1
|
||||
core.add_entity(p, spawner_def.mob_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local spawner_timers = {}
|
||||
function creatures.register_spawner(spawner_def)
|
||||
if not spawner_def or not spawner_def.mob_name or not spawner_def.model then
|
||||
throw_error("Can't register Spawn-Egg. Not enough parameters given.")
|
||||
return false
|
||||
end
|
||||
|
||||
makeSpawnerEntiy(spawner_def.mob_name, spawner_def.model)
|
||||
|
||||
core.register_node(":" .. spawner_def.mob_name .. "_spawner", {
|
||||
description = spawner_def.description or spawner_def.mob_name .. " spawner",
|
||||
paramtype = "light",
|
||||
tiles = {"creatures_spawner.png"},
|
||||
is_ground_content = true,
|
||||
drawtype = "glasslike",
|
||||
groups = {cracky = 1, level = 1},
|
||||
drop = "",
|
||||
on_construct = function(pos)
|
||||
pos.y = pos.y - 0.3
|
||||
core.add_entity(pos, spawner_def.mob_name .. "_spawner_dummy")
|
||||
end,
|
||||
on_destruct = function(pos)
|
||||
for _,obj in ipairs(core.get_objects_inside_radius(pos, 1)) do
|
||||
local entity = obj:get_luaentity()
|
||||
if obj and entity and entity.mob_name == "_" .. spawner_def.mob_name .. "_dummy" then
|
||||
obj:remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
local box = spawner_def.model.collisionbox
|
||||
local height = (box[5] or 2) - (box[2] or 0)
|
||||
spawner_def.height = height
|
||||
|
||||
if spawner_def.player_range and type(spawner_def.player_range) == "number" then
|
||||
core.register_abm({
|
||||
nodenames = {spawner_def.mob_name .. "_spawner"},
|
||||
interval = 2,
|
||||
chance = 1,
|
||||
catch_up = false,
|
||||
action = function(pos)
|
||||
local id = core.pos_to_string(pos)
|
||||
if not spawner_timers[id] then
|
||||
spawner_timers[id] = os.time()
|
||||
end
|
||||
local time_from_last_call = os.time() - spawner_timers[id]
|
||||
local mobs,player_near = creatures.findTarget(nil, pos, spawner_def.player_range, "player", nil, true, true)
|
||||
if player_near == true and time_from_last_call > 10 and (math.random(1, 5) == 1 or (time_from_last_call ) > 27) then
|
||||
spawner_timers[id] = os.time()
|
||||
|
||||
spawnerSpawn(pos, spawner_def)
|
||||
end
|
||||
end
|
||||
})
|
||||
else
|
||||
core.register_abm({
|
||||
nodenames = {spawner_def.mob_name .. "_spawner"},
|
||||
interval = 10,
|
||||
chance = 3,
|
||||
action = function(pos)
|
||||
|
||||
spawnerSpawn(pos, spawner_def)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
@ -1,61 +0,0 @@
|
||||
-------------------------------------------------------------------------
|
||||
-- Wasteland
|
||||
-- Copyright (C) 2015 BlockMen <blockmen2015@gmail.de>
|
||||
--
|
||||
-- This file is part of Wasteland
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
|
||||
local max_mobs_sum = creatures.zombie_max
|
||||
-- hostile mobs
|
||||
if not minetest.setting_getbool("only_peaceful_mobs") then
|
||||
-- zombie
|
||||
minetest.register_abm({
|
||||
nodenames = creatures.z_spawn_nodes,
|
||||
neighbors = {"air"},
|
||||
interval = 40.0,
|
||||
chance = 7600,
|
||||
action = function(pos, node, active_object_count, active_object_count_wider)
|
||||
-- check per mapblock max (max per creature is done by .spawn())
|
||||
if active_object_count_wider > max_mobs_sum then
|
||||
return
|
||||
end
|
||||
local n = minetest.get_node_or_nil(pos)
|
||||
--if n and n.name and n.name ~= "default:stone" and math.random(1,4)>3 then return end
|
||||
pos.y = pos.y + 1
|
||||
local ll = minetest.get_node_light(pos)
|
||||
if not ll then
|
||||
return
|
||||
end
|
||||
if ll >= creatures.z_ll then
|
||||
return
|
||||
end
|
||||
if ll < -1 then
|
||||
return
|
||||
end
|
||||
if minetest.get_node(pos).name ~= "air" then
|
||||
return
|
||||
end
|
||||
pos.y = pos.y + 1
|
||||
if minetest.get_node(pos).name ~= "air" then
|
||||
return
|
||||
end
|
||||
|
||||
pos.y = pos.y - 1
|
||||
|
||||
creatures.spawn(pos, math.random(1, 3), "creatures:zombie", 2, 20)
|
||||
end})
|
||||
end
|
@ -1,115 +0,0 @@
|
||||
-------------------------------------------------------------------------
|
||||
-- Wasteland
|
||||
-- Copyright (C) 2015 BlockMen <blockmen2015@gmail.de>
|
||||
--
|
||||
-- This file is part of Wasteland
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
|
||||
function creatures.register_spawner(mob,size,offset,mesh,texture,range,max,max_ll,min_ll,day_only)
|
||||
local DUMMY = {
|
||||
hp_max = 1,
|
||||
physical = true,
|
||||
collisionbox = {0,0,0,0,0,0},
|
||||
visual = "mesh",
|
||||
visual_size = size,
|
||||
mesh = mesh,
|
||||
textures = texture,
|
||||
makes_footstep_sound = false,
|
||||
timer = 0,
|
||||
automatic_rotate = math.pi * -2.9,
|
||||
m_name = "dummy"
|
||||
}
|
||||
|
||||
DUMMY.on_activate = function(self)
|
||||
self.object:setvelocity({x=0, y=0, z=0})
|
||||
self.object:setacceleration({x=0, y=0, z=0})
|
||||
self.object:set_armor_groups({immortal=1})
|
||||
end
|
||||
|
||||
DUMMY.on_step = function(self, dtime)
|
||||
self.timer = self.timer + 0.01
|
||||
local n = minetest.get_node_or_nil(self.object:getpos())
|
||||
if self.timer > 1 then
|
||||
if n and n.name and n.name ~= "creatures:"..mob.."_spawner" then
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
DUMMY.on_punch = function(self, hitter)
|
||||
end
|
||||
|
||||
minetest.register_entity("creatures:dummy_"..mob, DUMMY)
|
||||
|
||||
-- node
|
||||
minetest.register_node("creatures:"..mob.."_spawner", {
|
||||
description = mob.." spawner",
|
||||
paramtype = "light",
|
||||
tiles = {"creatures_spawner.png"},
|
||||
is_ground_content = true,
|
||||
drawtype = "allfaces",
|
||||
groups = {cracky=1,level=1},
|
||||
drop = "",
|
||||
on_construct = function(pos)
|
||||
pos.y = pos.y + offset
|
||||
minetest.env:add_entity(pos,"creatures:dummy_"..mob)
|
||||
end,
|
||||
on_destruct = function(pos)
|
||||
for _,obj in ipairs(minetest.env:get_objects_inside_radius(pos, 1)) do
|
||||
if not obj:is_player() then
|
||||
if obj ~= nil and obj:get_luaentity().m_name == "dummy" then
|
||||
obj:remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
stack_max = 40,
|
||||
})
|
||||
--abm
|
||||
minetest.register_abm({
|
||||
nodenames = {"creatures:"..mob.."_spawner"},
|
||||
interval = 2.0,
|
||||
chance = 20,
|
||||
action = function(pos, node, active_object_count, active_object_count_wider)
|
||||
local res,player_near = false
|
||||
local mobs = 0
|
||||
res,mobs,player_near = creatures.find_mates(pos, mob, range)
|
||||
if player_near then
|
||||
if mobs < max then
|
||||
pos.x = pos.x+1
|
||||
local p = minetest.find_node_near(pos, 5, {"air"})
|
||||
local ll = minetest.env:get_node_light(p)
|
||||
local wtime = minetest.env:get_timeofday()
|
||||
if not ll then return end
|
||||
if ll > max_ll then return end
|
||||
if ll < min_ll then return end
|
||||
if minetest.env:get_node(p).name ~= "air" then return end
|
||||
p.y = p.y+1
|
||||
if minetest.env:get_node(p).name ~= "air" then return end
|
||||
if not day_only then
|
||||
if (wtime > 0.2 and wtime < 0.805) and pos.y > 0 then return end
|
||||
end
|
||||
|
||||
p.y = p.y-1
|
||||
creatures.spawn(p, 1, "creatures:"..mob,range,max)
|
||||
end
|
||||
end
|
||||
end })
|
||||
end
|
||||
|
||||
-- spawner
|
||||
creatures.register_spawner("zombie",{x=0.42,y=0.42},0.08,"creatures_mob.x",{"creatures_zombie.png"},17,6,7,-1,false)
|
Before Width: | Height: | Size: 714 B |
Before Width: | Height: | Size: 441 B |
Before Width: | Height: | Size: 478 B |
Before Width: | Height: | Size: 433 B |
Before Width: | Height: | Size: 686 B |
Before Width: | Height: | Size: 453 B |
@ -1,364 +0,0 @@
|
||||
-------------------------------------------------------------------------
|
||||
-- Wasteland
|
||||
-- Copyright (C) 2015 BlockMen <blockmen2015@gmail.de>
|
||||
--
|
||||
-- This file is part of Wasteland
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
|
||||
local z_chillaxin_speed = 3
|
||||
local z_animation_speed = 15
|
||||
local z_mesh = "creatures_mob.x"
|
||||
local z_texture = {"creatures_zombie.png"}
|
||||
local z_hp = 20
|
||||
local z_drop = "jt_mods:griefer_soul"
|
||||
local z_life_max = 80 --~5min
|
||||
|
||||
local z_player_radius = 24
|
||||
local z_hit_radius = 1.4
|
||||
creatures.z_ll = 7
|
||||
|
||||
local z_sound_normal = "creatures_zombie"
|
||||
local z_sound_hit = "creatures_zombie_hit"
|
||||
local z_sound_dead = "creatures_zombie_death"
|
||||
|
||||
creatures.z_spawn_nodes = {"default:dry_dirt","default:dirt","default:mossycobble", "default:stone","default:dirt","default:desert_sand"}
|
||||
creatures.z_spawner_range = 17
|
||||
creatures.z_spawner_max_mobs = 6
|
||||
|
||||
local function z_get_animations()
|
||||
return {
|
||||
stand_START = 0,
|
||||
stand_END = 79,
|
||||
lay_START = 162,
|
||||
lay_END = 166,
|
||||
walk_START = 168,
|
||||
walk_END = 188,
|
||||
-- not used
|
||||
sit_START = 81,
|
||||
sit_END = 160
|
||||
}
|
||||
end
|
||||
|
||||
function z_hit(self)
|
||||
local sound = z_sound_hit
|
||||
if self.object:get_hp() < 1 then sound = z_sound_dead end
|
||||
minetest.sound_play(sound, {pos = self.object:getpos(), max_hear_distance = 18, loop = false, gain = 0.4})
|
||||
self.object:settexturemod("^[colorize:#c4000099")
|
||||
self.can_punch = false
|
||||
self.object:set_animation({x = self.anim.walk_START, y = self.anim.walk_END}, 35, 0)
|
||||
self.npc_anim = creatures.ANIM_WALK
|
||||
minetest.after(0.4, function()
|
||||
self.can_punch = true
|
||||
self.object:settexturemod("")
|
||||
self.npc_anim = ""
|
||||
end)
|
||||
end
|
||||
|
||||
function z_init_visuals(self)
|
||||
local prop = {
|
||||
mesh = z_mesh,
|
||||
textures = z_texture,
|
||||
}
|
||||
self.object:set_properties(prop)
|
||||
end
|
||||
|
||||
ZOMBIE_DEF = {
|
||||
physical = true,
|
||||
collisionbox = {-0.25, -1, -0.3, 0.25, 0.75, 0.3},
|
||||
visual = "mesh",
|
||||
visual_size = {x=1, y=1},
|
||||
mesh = z_mesh,
|
||||
textures = z_texture,
|
||||
makes_footstep_sound = true,
|
||||
npc_anim = 0,
|
||||
lifetime = 0,
|
||||
timer = 0,
|
||||
turn_timer = 0,
|
||||
vec = 0,
|
||||
yaw = 0,
|
||||
yawwer = 0,
|
||||
state = 1,
|
||||
can_punch = true,
|
||||
dead = false,
|
||||
jump_timer = 0,
|
||||
last_pos = {x=0,y=0,z=0},
|
||||
punch_timer = 0,
|
||||
sound_timer = 0,
|
||||
attacker = "",
|
||||
attacking_timer = 0,
|
||||
mob_name = "zombie"
|
||||
}
|
||||
|
||||
ZOMBIE_DEF.get_staticdata = function(self)
|
||||
return minetest.serialize({
|
||||
itemstring = self.itemstring,
|
||||
timer = self.timer,
|
||||
lifetime = self.lifetime,
|
||||
})
|
||||
end
|
||||
|
||||
ZOMBIE_DEF.on_activate = function(self, staticdata, dtime_s)
|
||||
z_init_visuals(self)
|
||||
self.anim = z_get_animations()
|
||||
self.object:set_animation({x=self.anim.stand_START,y=self.anim.stand_END}, z_animation_speed, 0)
|
||||
self.npc_anim = creatures.ANIM_STAND
|
||||
self.object:setacceleration({x=0,y=-20,z=0})
|
||||
self.state = 1
|
||||
self.object:set_hp(z_hp)
|
||||
self.object:set_armor_groups({fleshy=130})
|
||||
self.last_pos = {x=0,y=0,z=0}
|
||||
self.can_punch = true
|
||||
self.dead = false
|
||||
self.lifetime = 0
|
||||
if staticdata then
|
||||
local tmp = minetest.deserialize(staticdata)
|
||||
if tmp and tmp.timer then
|
||||
self.timer = tmp.timer
|
||||
end
|
||||
if tmp and tmp.lifetime ~= nil then
|
||||
self.lifetime = tmp.lifetime
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ZOMBIE_DEF.on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
|
||||
if not self.can_punch then return end
|
||||
|
||||
local my_pos = self.object:getpos()
|
||||
if puncher ~= nil then
|
||||
self.attacker = puncher
|
||||
if time_from_last_punch >= 0.45 then
|
||||
z_hit(self)
|
||||
local v = self.object:getvelocity()
|
||||
self.direction = {x=v.x, y=v.y, z=v.z}
|
||||
self.punch_timer = 0
|
||||
self.object:setvelocity({x=dir.x*z_chillaxin_speed,y=5,z=dir.z*z_chillaxin_speed})
|
||||
if self.state == 1 then
|
||||
self.state = 8
|
||||
elseif self.state >= 2 then
|
||||
self.state = 9
|
||||
end
|
||||
-- add wear to sword/tool
|
||||
creatures.add_wear(puncher, tool_capabilities)
|
||||
end
|
||||
end
|
||||
|
||||
if self.object:get_hp() < 1 then
|
||||
creatures.drop(my_pos, {{name=z_drop, count=math.random(0,2)}}, dir)
|
||||
end
|
||||
end
|
||||
|
||||
ZOMBIE_DEF.on_step = function(self, dtime)
|
||||
if self.dead then return end
|
||||
self.timer = self.timer + 0.01
|
||||
self.lifetime = self.lifetime + 0.01
|
||||
self.turn_timer = self.turn_timer + 0.01
|
||||
self.jump_timer = self.jump_timer + 0.01
|
||||
self.punch_timer = self.punch_timer + 0.01
|
||||
self.attacking_timer = self.attacking_timer + 0.01
|
||||
self.sound_timer = self.sound_timer + 0.01
|
||||
|
||||
local current_pos = self.object:getpos()
|
||||
local current_node = minetest.env:get_node_or_nil(current_pos)
|
||||
if self.time_passed == nil then
|
||||
self.time_passed = 0
|
||||
end
|
||||
|
||||
-- death
|
||||
if self.object:get_hp() < 1 then
|
||||
self.object:setvelocity({x=0,y=-20,z=0})
|
||||
self.object:set_hp(0)
|
||||
self.attacker = ""
|
||||
self.state = 0
|
||||
self.dead = true
|
||||
minetest.sound_play(z_sound_dead, {pos = current_pos, max_hear_distance = 10, gain = 0.9})
|
||||
self.object:set_animation({x=self.anim.lay_START,y=self.anim.lay_END}, z_animation_speed, 0)
|
||||
minetest.after(1, function()
|
||||
self.object:remove()
|
||||
if self.object:get_hp() < 1 and creatures.drop_on_death then
|
||||
creatures.drop(current_pos, {{name=z_drop, count=math.random(0,2)}})
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- die if old
|
||||
if self.lifetime > z_life_max then
|
||||
self.object:set_hp(0)
|
||||
self.state = 0
|
||||
self.dead = true
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
|
||||
-- die when in water, lava or sunlight
|
||||
local wtime = minetest.env:get_timeofday()
|
||||
local ll = minetest.env:get_node_light({x=current_pos.x,y=current_pos.y+1,z=current_pos.z}) or 0
|
||||
local nn = nil
|
||||
if current_node ~= nil then nn = current_node.name end
|
||||
if nn ~= nil and nn == "default:water_source" or
|
||||
nn == "default:water_flowing" or
|
||||
nn == "default:lava_source" or
|
||||
nn == "default:lava_flowing" or
|
||||
(wtime > 0.2 and wtime < 0.805 and current_pos.y > 0 and ll > 11) then
|
||||
self.sound_timer = self.sound_timer + dtime
|
||||
if self.sound_timer >= 0.8 then
|
||||
local damage = 5
|
||||
if ll > 11 then damage = 2 end
|
||||
self.sound_timer = 0
|
||||
self.object:set_hp(self.object:get_hp()-damage)
|
||||
z_hit(self)
|
||||
end
|
||||
else
|
||||
self.time_passed = 0
|
||||
end
|
||||
|
||||
-- update moving state every 0.5 or 1 second
|
||||
if self.state < 3 then
|
||||
if self.timer > 0.2 then
|
||||
if self.attacker == "" then
|
||||
self.state = math.random(1,2)
|
||||
else self.state = 5 end
|
||||
self.timer = 0
|
||||
end
|
||||
end
|
||||
|
||||
-- play random sound
|
||||
if self.sound_timer > math.random(5,35) then
|
||||
minetest.sound_play(z_sound_normal, {pos = current_pos, max_hear_distance = 18, gain = 0.7})
|
||||
self.sound_timer = 0
|
||||
end
|
||||
|
||||
-- after knocked back
|
||||
if self.state >= 8 then
|
||||
if self.punch_timer > 0.15 then
|
||||
if self.state == 9 then
|
||||
self.object:setvelocity({x=self.direction.x*z_chillaxin_speed,y=-20,z=self.direction.z*z_chillaxin_speed})
|
||||
self.state = 2
|
||||
elseif self.state == 8 then
|
||||
self.object:setvelocity({x=0,y=-20,z=0})
|
||||
self.state = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--STANDING
|
||||
if self.state == 1 then
|
||||
self.yawwer = true
|
||||
self.attacker = ""
|
||||
-- seach for players
|
||||
for _,object in ipairs(minetest.env:get_objects_inside_radius(current_pos, z_player_radius)) do
|
||||
if object:is_player() then
|
||||
self.yawwer = false
|
||||
NPC = current_pos
|
||||
PLAYER = object:getpos()
|
||||
self.vec = {x=PLAYER.x-NPC.x, y=PLAYER.y-NPC.y, z=PLAYER.z-NPC.z}
|
||||
self.yaw = math.atan(self.vec.z/self.vec.x)+math.pi^2
|
||||
if PLAYER.x > NPC.x then
|
||||
self.yaw = self.yaw + math.pi
|
||||
end
|
||||
self.yaw = self.yaw - 2
|
||||
self.object:setyaw(self.yaw)
|
||||
self.attacker = object
|
||||
end
|
||||
end
|
||||
|
||||
if self.attacker == "" and self.turn_timer > math.random(1,4) then
|
||||
self.yaw = 360 * math.random()
|
||||
self.object:setyaw(self.yaw)
|
||||
self.turn_timer = 0
|
||||
self.direction = {x = math.sin(self.yaw)*-1, y = -20, z = math.cos(self.yaw)}
|
||||
end
|
||||
self.object:setvelocity({x=0,y=self.object:getvelocity().y,z=0})
|
||||
if self.npc_anim ~= creatures.ANIM_STAND then
|
||||
self.anim = z_get_animations()
|
||||
self.object:set_animation({x=self.anim.stand_START,y=self.anim.stand_END}, z_animation_speed, 0)
|
||||
self.npc_anim = creatures.ANIM_STAND
|
||||
end
|
||||
if self.attacker ~= "" then
|
||||
self.direction = {x = math.sin(self.yaw)*-1, y = -20, z = math.cos(self.yaw)}
|
||||
self.state = 2
|
||||
end
|
||||
end
|
||||
|
||||
--UPDATE DIR
|
||||
if self.state == 5 then
|
||||
self.yawwer = true
|
||||
self.attacker = ""
|
||||
-- seach for players
|
||||
for _,object in ipairs(minetest.env:get_objects_inside_radius(current_pos, z_player_radius)) do
|
||||
if object:is_player() then
|
||||
self.yawwer = false
|
||||
NPC = current_pos
|
||||
PLAYER = object:getpos()
|
||||
self.vec = {x=PLAYER.x-NPC.x, y=PLAYER.y-NPC.y, z=PLAYER.z-NPC.z}
|
||||
self.yaw = math.atan(self.vec.z/self.vec.x)+math.pi^2
|
||||
if PLAYER.x > NPC.x then
|
||||
self.yaw = self.yaw + math.pi
|
||||
end
|
||||
self.yaw = self.yaw - 2
|
||||
self.object:setyaw(self.yaw)
|
||||
self.attacker = object
|
||||
end
|
||||
end
|
||||
|
||||
if self.attacker ~= "" then
|
||||
self.direction = {x = math.sin(self.yaw)*-1, y = -20, z = math.cos(self.yaw)}
|
||||
self.state = 2
|
||||
else
|
||||
self.state =1
|
||||
end
|
||||
end
|
||||
|
||||
-- WALKING
|
||||
if self.state == 2 then
|
||||
|
||||
if self.attacker ~= "" then
|
||||
self.direction = {x=math.sin(self.yaw)*-1, y=-20, z=math.cos(self.yaw)}
|
||||
end
|
||||
if self.direction ~= nil then
|
||||
self.object:setvelocity({x=self.direction.x*z_chillaxin_speed,y=self.object:getvelocity().y,z=self.direction.z*z_chillaxin_speed})
|
||||
end
|
||||
if self.turn_timer > math.random(1,4) and not self.attacker then
|
||||
self.yaw = 360 * math.random()
|
||||
self.object:setyaw(self.yaw)
|
||||
self.turn_timer = 0
|
||||
self.direction = {x=math.sin(self.yaw)*-1, y=-20, z=math.cos(self.yaw)}
|
||||
end
|
||||
if self.npc_anim ~= creatures.ANIM_WALK then
|
||||
self.npc_anim = creatures.ANIM_WALK
|
||||
self.object:set_animation({x=self.anim.walk_START,y=self.anim.walk_END}, z_animation_speed, 0)
|
||||
end
|
||||
|
||||
--jump
|
||||
local p = current_pos
|
||||
p.y = p.y-0.5
|
||||
creatures.jump(self, p, 7.4, 0.25)
|
||||
|
||||
if self.attacker ~= "" and minetest.setting_getbool("enable_damage") then
|
||||
local s = current_pos
|
||||
local attacker_pos = self.attacker:getpos() or nil
|
||||
if attacker_pos == nil then return end
|
||||
local p = attacker_pos
|
||||
if (s ~= nil and p ~= nil) then
|
||||
local dist = ((p.x-s.x)^2 + (p.y-s.y)^2 + (p.z-s.z)^2)^0.5
|
||||
creatures.attack(self, current_pos, attacker_pos, dist, z_hit_radius)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_entity("creatures:zombie", ZOMBIE_DEF)
|
@ -53,7 +53,7 @@ function default.register_ores()
|
||||
ore_type = "blob",
|
||||
ore = "default:clay",
|
||||
wherein = {"default:sand"},
|
||||
clust_scarcity = 16 * 16 * 16,
|
||||
clust_scarcity = 26 * 26 * 26,
|
||||
clust_size = 5,
|
||||
y_min = -15,
|
||||
y_max = 0,
|
||||
@ -75,7 +75,7 @@ function default.register_ores()
|
||||
ore = "default:sand",
|
||||
wherein = {"default:stone", "default:sandstone",
|
||||
"default:desert_stone"},
|
||||
clust_scarcity = 16 * 16 * 16,
|
||||
clust_scarcity = 26 * 26 * 26,
|
||||
clust_size = 5,
|
||||
y_min = -31,
|
||||
y_max = 0,
|
||||
@ -96,7 +96,7 @@ function default.register_ores()
|
||||
ore_type = "blob",
|
||||
ore = "default:dirt",
|
||||
wherein = {"default:stone"},
|
||||
clust_scarcity = 16 * 16 * 16,
|
||||
clust_scarcity = 36 * 36 * 36,
|
||||
clust_size = 5,
|
||||
y_min = -31,
|
||||
y_max = 31000,
|
||||
@ -117,7 +117,7 @@ function default.register_ores()
|
||||
ore_type = "blob",
|
||||
ore = "default:gravel",
|
||||
wherein = {"default:stone"},
|
||||
clust_scarcity = 16 * 16 * 16,
|
||||
clust_scarcity = 36 * 36 * 36,
|
||||
clust_size = 5,
|
||||
y_min = -31000,
|
||||
y_max = 31000,
|
||||
@ -178,7 +178,7 @@ function default.register_ores()
|
||||
clust_scarcity = 9 * 9 * 9,
|
||||
clust_num_ores = 12,
|
||||
clust_size = 3,
|
||||
y_min = 1025,
|
||||
y_min = 125,
|
||||
y_max = 31000,
|
||||
})
|
||||
|
||||
@ -213,7 +213,7 @@ function default.register_ores()
|
||||
clust_scarcity = 9 * 9 * 9,
|
||||
clust_num_ores = 5,
|
||||
clust_size = 3,
|
||||
y_min = 1025,
|
||||
y_min = 125,
|
||||
y_max = 31000,
|
||||
})
|
||||
|
||||
@ -283,7 +283,7 @@ function default.register_ores()
|
||||
clust_scarcity = 14 * 14 * 14,
|
||||
clust_num_ores = 5,
|
||||
clust_size = 3,
|
||||
y_min = 1025,
|
||||
y_min = 125,
|
||||
y_max = 31000,
|
||||
})
|
||||
|
||||
@ -318,7 +318,7 @@ function default.register_ores()
|
||||
clust_scarcity = 15 * 15 * 15,
|
||||
clust_num_ores = 4,
|
||||
clust_size = 3,
|
||||
y_min = 1025,
|
||||
y_min = 155,
|
||||
y_max = 31000,
|
||||
})
|
||||
|
||||
@ -353,7 +353,7 @@ function default.register_ores()
|
||||
clust_scarcity = 36 * 36 * 36,
|
||||
clust_num_ores = 3,
|
||||
clust_size = 2,
|
||||
y_min = 1025,
|
||||
y_min = 125,
|
||||
y_max = 31000,
|
||||
})
|
||||
|
||||
|
BIN
mods/default/textures-410.zip
Normal file
BIN
mods/default/textures-414.zip
Normal file
Before Width: | Height: | Size: 459 B After Width: | Height: | Size: 273 B |
Before Width: | Height: | Size: 348 B After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 251 B After Width: | Height: | Size: 247 B |
Before Width: | Height: | Size: 201 B After Width: | Height: | Size: 210 B |
Before Width: | Height: | Size: 471 B After Width: | Height: | Size: 511 B |
Before Width: | Height: | Size: 351 B After Width: | Height: | Size: 480 B |
Before Width: | Height: | Size: 356 B After Width: | Height: | Size: 562 B |
Before Width: | Height: | Size: 224 B After Width: | Height: | Size: 257 B |
Before Width: | Height: | Size: 314 B After Width: | Height: | Size: 649 B |
Before Width: | Height: | Size: 267 B After Width: | Height: | Size: 607 B |
Before Width: | Height: | Size: 423 B After Width: | Height: | Size: 761 B |
Before Width: | Height: | Size: 469 B After Width: | Height: | Size: 864 B |
Before Width: | Height: | Size: 375 B After Width: | Height: | Size: 709 B |
Before Width: | Height: | Size: 422 B After Width: | Height: | Size: 627 B |
Before Width: | Height: | Size: 272 B After Width: | Height: | Size: 496 B |
Before Width: | Height: | Size: 158 B After Width: | Height: | Size: 217 B |
Before Width: | Height: | Size: 167 B After Width: | Height: | Size: 337 B |
Before Width: | Height: | Size: 83 B After Width: | Height: | Size: 113 B |
Before Width: | Height: | Size: 240 B After Width: | Height: | Size: 418 B |
Before Width: | Height: | Size: 157 B After Width: | Height: | Size: 251 B |
Before Width: | Height: | Size: 268 B After Width: | Height: | Size: 585 B |
Before Width: | Height: | Size: 359 B After Width: | Height: | Size: 599 B |
Before Width: | Height: | Size: 225 B After Width: | Height: | Size: 264 B |
Before Width: | Height: | Size: 177 B After Width: | Height: | Size: 279 B |
Before Width: | Height: | Size: 350 B After Width: | Height: | Size: 670 B |
Before Width: | Height: | Size: 280 B After Width: | Height: | Size: 367 B |
Before Width: | Height: | Size: 552 B After Width: | Height: | Size: 483 B |
Before Width: | Height: | Size: 313 B After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 576 B |
Before Width: | Height: | Size: 277 B After Width: | Height: | Size: 913 B |
Before Width: | Height: | Size: 148 B After Width: | Height: | Size: 292 B |
BIN
mods/default/textures/default_fence.png
Normal file
After Width: | Height: | Size: 482 B |
Before Width: | Height: | Size: 274 B After Width: | Height: | Size: 604 B |
Before Width: | Height: | Size: 129 B After Width: | Height: | Size: 282 B |
Before Width: | Height: | Size: 558 B After Width: | Height: | Size: 803 B |
Before Width: | Height: | Size: 307 B After Width: | Height: | Size: 628 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 826 B |
Before Width: | Height: | Size: 296 B After Width: | Height: | Size: 604 B |
Before Width: | Height: | Size: 274 B After Width: | Height: | Size: 604 B |
Before Width: | Height: | Size: 483 B After Width: | Height: | Size: 906 B |
Before Width: | Height: | Size: 225 B After Width: | Height: | Size: 262 B |
Before Width: | Height: | Size: 165 B After Width: | Height: | Size: 252 B |
Before Width: | Height: | Size: 377 B After Width: | Height: | Size: 794 B |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 206 B |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 243 B |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 270 B |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 292 B |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 313 B |
BIN
mods/default/textures/default_grass_footsteps.png
Normal file
After Width: | Height: | Size: 771 B |
Before Width: | Height: | Size: 779 B After Width: | Height: | Size: 556 B |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 278 B |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 490 B |
Before Width: | Height: | Size: 160 B After Width: | Height: | Size: 236 B |
Before Width: | Height: | Size: 197 B After Width: | Height: | Size: 340 B |
Before Width: | Height: | Size: 184 B After Width: | Height: | Size: 284 B |
Before Width: | Height: | Size: 417 B After Width: | Height: | Size: 430 B |
Before Width: | Height: | Size: 190 B After Width: | Height: | Size: 253 B |
Before Width: | Height: | Size: 736 B After Width: | Height: | Size: 758 B |
Before Width: | Height: | Size: 823 B After Width: | Height: | Size: 834 B |
Before Width: | Height: | Size: 260 B After Width: | Height: | Size: 254 B |
BIN
mods/default/textures/default_ladder.png
Normal file
After Width: | Height: | Size: 467 B |