tyrant/init.lua
2016-05-28 22:51:00 +02:00

697 lines
25 KiB
Lua

--tyrant mod for minetest 0.4.13
--library to provide a shared api for area protection mods
-- Boilerplate to support localized strings if intllib mod is installed.
local S
if minetest.get_modpath("intllib") then
S = intllib.Getter()
else
-- If you use insertions, but not insertion escapes this will work:
S = function(s,a,...)a={a,...}return s:gsub("@(%d+)",function(n)return a[tonumber(n)]end)end
end
--circlewalktogetherbreak
tyrant={
integrations={}
}
--[[tyrant integration register:
functions preceeded with ! must exist, others can but don't have to.
tyrant.register_integration(name(preferably modname), {
! function get_all_area_ids() - should return:
true and an ipairable table with areaids as VALUEs or
false and a table with areaids as KEYs
! function get_is_area_at(areaid, pos)
should return true if this position is inside the area.
function get_area_priority(areaid)
should return a number determining the priority of this area over others.
areas having the same priority will co-exist.
default definition: return 0
! function check_permission(areaid, player_name, action, pos)
checks if <player> is allowed to do <action> in <areaid>
action can be one of:
"enter"(walk into area), "activate"(right-click nodes), "punch"(nodes), "inv"(change inventories), "build"(is_protected), "pvp"(hit other players)
should return one of these combinations:
true if the action is allowed
true, true if the action is allowed and no other area should prohibit the action.
false if the action is forbidden, a default error message will be shown.
false, message if the action is forbidden, message will be shown.
if more than one area with the same, highest priority is at the event's position, in case any of these areas is denying the action, it will be denied, except another area returned true, true
!Warning! player_name CAN BE NIL, in this case something non-playery such as TNT committed the action. Please handle this case!
! function get_area_intersects_with(areaid, pos1, pos2)
should return true if any point inside the area between pos1 and pos2 intersects with areaid
function is_hostile_mob_spawning_allowed(areaid)
should return true if hostile mobs should spawn inside the area and false if not.
default definition: return true
if more than one area with the same, highest priority is at the event's position, in case any of these areas is denying the action, it will be denied.
function on_area_info_requested(areaid, player_name)
called when a player clicks an area in the area selection menu recalled either by /areas_here or /all_areas
should show a formspec with either management options or information to the player
default definition: shows simple information formspec on what the player can do here.
function get_display_name(areaid)
get the display name of areaid.
default definition: return integration_name..":"..areaid
}
the following functions are available for all integrations and other mods to access things provided by tyrant
tyrant.check_action_allowed(pos, player_name, action* [, no_notification])
checks if the action specified is allowed at pos for player.
if no_notification is set, players will not be notified on violation.
* see check_permission() above
tyrant.check_hostile_mobs_allowed(pos)
checks for any area having the hostile_mob_spawning function returning false. If it retunrs false, should not spawn the hostile mob.
to be included in mob frameworks.
tyrant.get_area_priority_at(pos)
returns the highest priority any area has at this point
can be used to determine if a new area can be established here.
tyrant.get_area_priority_inside(pos1, pos2)
returns the highest priority any area has inside the area ranging from pos1 to pos2.
can be used to determine if a new area can be established here.
tyrant.get_areas_at(pos)
returns a table containing all areas at pos in the following format:
{
[integration1]={
[1]=areaid1,
[2]=areaid2...
}
[integration2]...
}
In most cases, this is one area (the one with the highest priority), but can be more.
tyrant.get_all_areas()
returns a table in the format like get_areas_at()
tyrant.show_player_areas_at(pos, player_name)
opens up an area selection formspec for all areas at the given position.
tyrant.show_player_all_areas(player_name)
opens up an area selection formspec for all areas
minetest.is_protected() wraps to check_action_allowed(..., "build")
a minetest.register_on_punchplayer wraps to check_action_allowed(..., "pvp")
)
]]
--tyrant.falsemessages
--at the same time source for denial messages and for isAction.
tyrant.falsemessages={
enter="You may not enter @1",
activate="You may not right-click nodes inside @1",
inv="You may not change inventories inside @1",
build="You may not build inside @1",
punch="You may not punch nodes inside @1",
pvp="PvP (Player vs. Player) is not allowed inside @1"
}
minetest.register_privilege("tyrant_bypass", {
description = S("Can bypass any restrictions set up by any areas integrated in tyrant."),
})
tyrant.check_action_allowed=function(pos, pname, action, no_notification)
if minetest.check_player_privs(pname, {tyrant_bypass=true}) or minetest.check_player_privs(pname, {protection_bypass=true}) then
return true
end
local intareas=tyrant.get_areas_at(pos)
if not tyrant.falsemessages[action] then
error("given invalid action >"..(action or "nil").."< to tyrant.check_action_allowed")
end
--print("inside actionallowed action",action)
local all_allow, all_error=true, ""
for intname,areaids in pairs(intareas) do
--print(" intname", intname)
for _,areaid in ipairs(areaids) do
--print(" areaid", areaid)
local permit, err_or_override=tyrant.integrations[intname].check_permission(areaid, pname, action, pos)
--print(" pe", permit, err_or_override)
if permit then
if err_or_override then
return true
end
else
all_allow=false
all_error=err_or_override or S(tyrant.falsemessages[action], tyrant.integrations[intname].get_display_name(areaid) or intname..":"..areaid)
end
end
end
if not no_notification and pname and not all_allow then
tyrant.fs_message(pname, all_error);
end
return all_allow, all_error
end
tyrant.get_all_areas=function()
local ialist={}
for intname,intdef in pairs(tyrant.integrations) do
local as_values, areaids=intdef.get_all_area_ids()
ialist[intname]={}
if as_values then
for _,areaid in ipairs(areaids) do
ialist[intname][#ialist[intname]+1]=areaid
end
else
for areaid,_ in pairs(areaids) do
ialist[intname][#ialist[intname]+1]=areaid
end
end
end
return ialist
end
tyrant.get_areas_at=function(pos)
local last_prior=-127
local ialist={}
for intname,intdef in pairs(tyrant.integrations) do
local as_values, areaids=intdef.get_all_area_ids()
if as_values then
for _,areaid in ipairs(areaids) do
if tyrant.integrations[intname].get_is_area_at(areaid, pos) then
local now_prior=tyrant.integrations[intname].get_area_priority(areaid)
if now_prior>last_prior then
ialist={}
last_prior=now_prior
end
if now_prior>=last_prior then
if not ialist[intname] then ialist[intname]={} end
ialist[intname][#ialist[intname]+1]=areaid
end
end
end
else
for areaid,_ in pairs(areaids) do
if tyrant.integrations[intname].get_is_area_at(areaid, pos) then
local now_prior=tyrant.integrations[intname].get_area_priority(areaid)
if now_prior>last_prior then
ialist={}
last_prior=now_prior
end
if now_prior==last_prior then
if not ialist[intname] then ialist[intname]={} end
ialist[intname][#ialist[intname]+1]=areaid
end
end
end
end
end
return ialist
end
--protection, nodebuild
tyrant.old_is_protected = minetest.is_protected
function minetest.is_protected(pos, name)
local t1=os.clock()
local allowed, err=tyrant.check_action_allowed(pos, name, "build")
--print("[tyrant][benchmark] "..math.floor((os.clock()-t1)*1000).."ms for is_protected("..name.." at "..minetest.pos_to_string(pos)..")")
if not allowed then
return true
end
return tyrant.old_is_protected(pos, name)
end
--position golbalstep
tyrant.position_recheck_and_hud_timer=1
minetest.register_globalstep(function(dtime)
if tyrant.position_recheck_and_hud_timer<=0 then
local t1=os.clock()
for name, object in pairs(minetest.get_connected_players()) do
tyrant.position_handler(object:get_player_name(), object:getpos(), object)
end
tyrant.update_hud()
--print("[tyrant][benchmark] "..math.floor((os.clock()-t1)*1000).."ms for movement check (all players)")
tyrant.position_recheck_and_hud_timer=1
else
tyrant.position_recheck_and_hud_timer=tyrant.position_recheck_and_hud_timer-dtime
end
end)
tyrant.last_valid_player_positions={}
tyrant.last_known_player_positions={}
tyrant.position_handler=function(pname, pos, player)
local rpos, lvpos=vector.round(pos), tyrant.last_known_player_positions[pname]
if lvpos and rpos.x==lvpos.x and rpos.y==lvpos.y and rpos.z==lvpos.z then
--no position change, no need to recheck!
return
end
tyrant.last_known_player_positions[pname]=vector.round(pos)
local allowed, err=tyrant.check_action_allowed(vector.round(pos), pname, "enter", true)
if not allowed then
tyrant.forbidden_entry_hdlr(pname, pos, player, err)
else
tyrant.last_valid_player_positions[pname]=vector.round(pos)
--print("lvp "..minetest.pos_to_string(tyrant.last_valid_player_positions[pname]))
end
end
tyrant.forbidden_entry_hdlr=function(pname, pos, player, err)
if not tyrant.last_valid_player_positions[pname] then
--ignore
--print("ignored forbidden state lastvalidpos nil")
return
end
local a, newerr=tyrant.check_action_allowed(tyrant.last_valid_player_positions[pname], pname, "enter", true)
if not a then
--print("ignored forbidden state lastvalidpos not safe, "..minetest.pos_to_string(tyrant.last_valid_player_positions[pname]).." tells "..newerr)
--ignore
return
else
tyrant.fs_message(pname, err)
player:setpos(tyrant.last_valid_player_positions[pname])
end
--player:set_hp(player:get_hp()-1)
end
--And now: PvP (only if on_punchplayer exists)
if minetest.setting_getbool("enable_pvp") then
if minetest.register_on_punchplayer then
minetest.register_on_punchplayer(
function(player, hitter_param, time_from_last_punch, tool_capabilities, dir, damage)
local t1=os.clock()
--to fix throwing entities (sadly not working...)
local hitter=hitter_param
if hitter:get_luaentity() and hitter:get_luaentity().name and string.match(hitter:get_luaentity().name, "^throwing") and hitter:get_luaentity().player then
hitter=hitter:get_luaentity().player
print("[tyrant] on_punchplayer detected a throwing arrow")
end
if not player or not hitter then
print("[tyrant] on_punchplayer called with nil objects.")
end
if not hitter:is_player() then
--no case of pvp!
return false
else
--PvP here. check areas
local allow, err=tyrant.check_action_allowed(pos, pname, "pvp")
if not allow then
hitter:set_hp(player:get_hp()-1)
end
return not allow--should disable normal damage...(do no dmg.)
end
--print("[tyrant][benchmark] "..math.floor((os.clock()-t1)*1000).."ms for pvp event ("..hitter:get_player_name().." hitting "..player:get_player_name().." at "..minetest.pos_to_string(pos)..")")
end)
else
print("[tyrant]Warning: PvP protection is not working because your version of Minetest is too old. Please upgrade to 0.4.13 to use this feature.")
print("[tyrant]You can disable PvP world-wide via the minetest.conf option. PvP settings of areas are ignored!")
end
else
print("[tyrant]PvP disabled world-wide via config, PvP settings of areas are ignored!")
end
tyrant.fs_message=function(pname, msg)
minetest.show_formspec(pname, "tyrantmessage", "size[10,1]label[0.2,0.2;"..msg.."]")
end
tyrant.sort_coords=function(c1, c2)
return
{x=math.min(c1.x, c2.x), y=math.min(c1.y, c2.y), z=math.min(c1.z, c2.z)},
{x=math.max(c1.x, c2.x), y=math.max(c1.y, c2.y), z=math.max(c1.z, c2.z)}
end
--nice_desc_of_area
tyrant.nice_desc_of_area=function(intname, areaid, pname)
local you=""
--print(intname)
if tyrant.hudaccess[pname] then
if tyrant.hudaccess[pname][intname] then
if tyrant.hudaccess[pname][intname][areaid] then
you=" -> "..tyrant.hudaccess[pname][intname][areaid]
end
end
end
return (tyrant.integrations[intname].get_display_name(areaid) or areaid)..you
end
tyrant.hudaccess={}
tyrant.hudactions={
[true]={
enter=S("E"),
activate=S("A"),
inv=S("I"),
build=S("B"),
punch="",
pvp="",
},
[false]={
enter="-",
activate="-",
inv="-",
build="-",
punch="",
pvp=""
}
}
tyrant.hudactions_order={
"enter", "activate", "inv", "build"
}
tyrant.update_hudaccess=function()
local intareas=tyrant.get_all_areas()
for _,player in ipairs(minetest.get_connected_players()) do
local pname=player:get_player_name()
for intname,areaids in pairs(intareas) do
for _,areaid in ipairs(areaids) do
local str=""
for _,action in ipairs(tyrant.hudactions_order) do
local permit, err_or_override=tyrant.integrations[intname].check_permission(areaid, pname, action, player:getpos())
str=str..tyrant.hudactions[permit and true or false][action]
end
if not tyrant.hudaccess[pname] then tyrant.hudaccess[pname]={} end
if not tyrant.hudaccess[pname][intname] then tyrant.hudaccess[pname][intname]={} end
tyrant.hudaccess[pname][intname][areaid]=str
end
end
end
end
tyrant.update_hudaccess_timer=10
--hudaccess golbalstep
minetest.register_globalstep(function(dtime)
tyrant.update_hudaccess_timer=tyrant.update_hudaccess_timer+dtime
if(tyrant.update_hudaccess_timer>5) then
tyrant.update_hudaccess_timer=0
tyrant.update_hudaccess()
end
end)
--
---stolen stuff from areas
tyrant.hud = {}
tyrant.update_hud=function(dtime)
for _, player in pairs(minetest.get_connected_players()) do
local name = player:get_player_name()
local pos = vector.round(player:getpos())
local areaStrings = {}
local intareas=tyrant.get_areas_at(pos)
for intname,areaids in pairs(intareas) do
for _,areaid in ipairs(areaids) do
table.insert(areaStrings, tyrant.nice_desc_of_area(intname, areaid, name))
end
end
---areas in front
--[[
local any=false
for _, areaid in pairs(tyrant.get_areas_at(pos)) do
if not any then
table.insert(areaStrings, "2 Blöcke voraus:")
any=true
end
table.insert(areaStrings, tyrant.nice_desc_of_area(areaid, name))
end
]]
local areaString
if #areaStrings > 0 then
areaString = S("Here is:").."\n"..
table.concat(areaStrings, "\n")
else
areaString = ""
end
local hud = tyrant.hud[name]
if not hud then
hud = {}
tyrant.hud[name] = hud
hud.areasId = player:hud_add({
hud_elem_type = "text",
name = "TYRANT",
number = 0xFFFFFF,
position = {x=0, y=1},
offset = {x=8, y=-8},
text = areaString,
scale = {x=200, y=60},
alignment = {x=1, y=-1},
})
hud.oldAreas = areaString
return
elseif hud.oldAreas ~= areaString then
player:hud_change(hud.areasId, "text", areaString)
hud.oldAreas = areaString
end
end
end
minetest.register_on_leaveplayer(function(player)
tyrant.hud[player:get_player_name()] = nil
end)
--block defs for use override (metadata put/take/move + onrightclick)
minetest.after(0, function()
for key, value in pairs(minetest.registered_nodes) do
getmetatable(value).__newindex = nil
if value.on_rightclick then --if an on_rightclick function exists
local t1=os.clock()
local old_on_rc=value.on_rightclick
value.on_rightclick=function(pos, node, player, itemstack, pointed_thing)
print("[tyrant][info]node at "..minetest.pos_to_string(pos)..": "..(player and player:get_player_name() or "UNKNOWN").." right-clicks "..(node and node.name or "an unknown node"))
if pos and player and player:is_player() then
local allowed, err=tyrant.check_action_allowed(pos, player:get_player_name(), "activate")
if not allowed then
print("[tyrant][info]rightclick blocked:"..err)
return false
end
end
return old_on_rc(pos, node, player, itemstack, pointed_thing)
end
--print("[tyrant][benchmark] "..math.floor((os.clock()-t1)*1000).."ms for rightclick event")
end
local old_allow_metadata_inventory_move=value.allow_metadata_inventory_move or function(_, _, _, _, _, count) return count end
minetest.registered_nodes[key].allow_metadata_inventory_move=function(pos, from_list, from_index,
to_list, to_index, count, player)
local t1=os.clock()
print("[denaid][info]meta inventory at "..minetest.pos_to_string(pos)..": "..(player and player:get_player_name() or "UNKNOWN").." moves "..count.." items from "..from_list..":"..from_index.." to "..to_list..":"..to_index)
if pos and player and player.is_player and player:is_player() then--player.is_player since pipeworks creates a fake player not including this function.
local allowed, err=tyrant.check_action_allowed(pos, player:get_player_name(), "inv")
if not allowed then
print("[tyrant][info]inventory transaction blocked:"..err)
return false
end
end
local allow= old_allow_metadata_inventory_move(pos, from_list, from_index,
to_list, to_index, count, player)
if allow==0 then
print("[denaid][info]Inventory transaction denied by block definition")
end
--print("[tyrant][benchmark] "..math.floor((os.clock()-t1)*1000).."ms for inventory move event")
return allow
end
local old_allow_metadata_inventory_put=value.allow_metadata_inventory_put or function(_, _, _, stack) return stack:get_count() end
minetest.registered_nodes[key].allow_metadata_inventory_put=function(pos, listname, index, stack, player)
local t1=os.clock()
print("[denaid][info]meta inventory at "..minetest.pos_to_string(pos)..": "..(player and player:get_player_name() or "UNKNOWN").." puts "..stack:get_count().."x "..stack:get_name().." into "..listname..":"..index)
if pos and player and player.is_player and player:is_player() then--player.is_player since pipeworks creates a fake player not including this function.
local allowed, err=tyrant.check_action_allowed(pos, player:get_player_name(), "inv")
if not allowed then
print("[tyrant][info]inventory transaction blocked:"..err)
return false
end
end
local allow= old_allow_metadata_inventory_put(pos, listname, index, stack, player)
if allow==0 then
print("[denaid][info]Inventory transaction denied by block definition")
end
--print("[tyrant][benchmark] "..math.floor((os.clock()-t1)*1000).."ms for inventory put event")
return allow
end
local old_allow_metadata_inventory_take=value.allow_metadata_inventory_take or function(_, _, _, stack) return stack:get_count() end
minetest.registered_nodes[key].allow_metadata_inventory_take=function(pos, listname, index, stack, player)
local t1=os.clock()
print("[denaid][info]meta inventory at "..minetest.pos_to_string(pos)..": "..(player and player:get_player_name() or "UNKNOWN").." takes "..stack:get_count().."x "..stack:get_name().." from "..listname..":"..index)
if pos and player and player.is_player and player:is_player() then--player.is_player since pipeworks creates a fake player not including this function.
local allowed, err=tyrant.check_action_allowed(pos, player:get_player_name(), "inv")
if not allowed then
print("[tyrant][info]inventory transaction blocked:"..err)
return false
end
end
local allow= old_allow_metadata_inventory_take(pos, listname, index, stack, player)
if allow==0 then
print("[denaid][info]Inventory transaction denied by block definition")
end
--print("[tyrant][benchmark] "..math.floor((os.clock()-t1)*1000).."ms for inventory take event")
return allow
end
end
end)
tyrant.areaselect={}
tyrant.show_area_selection_form=function(player, intareas, desc)
local ttbl={}
local fsstr=""
local first=true
for intname,areaids in pairs(intareas) do
for _,areaid in ipairs(areaids) do
local entry=tyrant.nice_desc_of_area(intname, areaid, player)
if first then
fsstr=entry
first=false
else
fsstr=fsstr..","..entry
end
table.insert(ttbl, intname..":"..areaid)
end
end
tyrant.areaselect[player]=ttbl
local trfa={}
trfa[true]="true"
trfa[false]="false"
local formtext="size[5,8]label[0,02;"..S("Choose area by double-clicking").."]label[0,1;"..desc.."]"..
"textlist[0,2;5,6;areas;"..fsstr..";0;false]"
minetest.show_formspec(player, "tyrantareaselect", formtext)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname=="tyrantareaselect" then
local pname=player:get_player_name()
if tyrant.areaselect[pname] then
--fields got send over
--do anything
if fields.areas then
local val=minetest.explode_textlist_event(fields.areas)
if val.type=="DCL" and tyrant.areaselect[pname][val.index] then
local integration, areaid=string.match(tyrant.areaselect[pname][val.index], "^([^:]+):(.+)")
if integration and areaid then
tyrant.integrations[integration].on_area_info_requested(areaid, pname)
end
end
end
end
end
end)
tyrant.show_player_all_areas=function(player_name)
tyrant.show_area_selection_form(player_name, tyrant.get_all_areas(), S("All Areas"))
end
tyrant.show_player_areas_at=function(pos, player_name)
tyrant.show_area_selection_form(player_name, tyrant.get_areas_at(vector.round(pos)), S("Areas at @1", minetest.pos_to_string(vector.round(pos))))
end
--chat commands
core.register_chatcommand("all_areas", {
params = "",
description = S("List all areas"),
privs = {},
func = function(name, param)
tyrant.show_player_all_areas(name)
end,
})
core.register_chatcommand("areas_here", {
params = "",
description = S("List areas at your position"),
privs = {},
func = function(name, param)
tyrant.show_player_areas_at(vector.round(minetest.get_player_by_name(name):getpos()), name)
end,
})
--integration registration
tyrant.register_integration=function(name, register)
if string.match(name, ":") then error("tyrant integration names may not contain ':'!") end
if not (register.get_all_area_ids or register.get_is_area_at or register.check_permission or register.get_area_intersects_with) then error("register tyrant integration: missing required function in "..name) end
local predef={
get_area_priority=function(areaid)
return 0
end,
is_hostile_mob_spawning_allowed=function(areaid)
return true
end,
on_area_info_requested=function(areaid, player_name)
tyrant.fs_message(player_name, tyrant.nice_desc_of_area(name, areaid, player_name))
end,
get_display_name=function(areaid)
return name..":"..areaid
end,
}
for k,v in pairs(register) do
predef[k]=v
end
tyrant.integrations[name]=predef
end
--still missing api
tyrant.check_hostile_mobs_allowed=function(pos)
local intareas=tyrant.get_areas_at(pos)
local all_allow=true
for intname,areaids in pairs(intareas) do
for _,areaid in ipairs(areaids) do
local permit=tyrant.integrations[intname].is_hostile_mob_spawning_allowed(areaid)
if not permit then
all_allow=false
end
end
end
return all_allow
end
tyrant.get_area_priority_at=function(pos)
local last_prior=-127
for intname,intdef in pairs(tyrant.integrations) do
local as_values, areaids=intdef.get_all_area_ids()
if as_values then
for _,areaid in ipairs(areaids) do
if tyrant.integrations[intname].get_is_area_at(areaid, pos) then
local now_prior=tyrant.integrations[intname].get_area_priority(areaid)
if now_prior>last_prior then
last_prior=now_prior
end
end
end
else
for areaid,_ in pairs(areaids) do
if tyrant.integrations[intname].get_is_area_at(areaid, pos) then
local now_prior=tyrant.integrations[intname].get_area_priority(areaid)
if now_prior>last_prior then
last_prior=now_prior
end
end
end
end
end
return last_prior
end
tyrant.get_area_priority_inside=function(pos1, pos2)
local last_prior=-127
for intname,intdef in pairs(tyrant.integrations) do
local as_values, areaids=intdef.get_all_area_ids()
if as_values then
for _,areaid in ipairs(areaids) do
if tyrant.integrations[intname].get_area_intersects_with(areaid, pos1, pos2) then
local now_prior=tyrant.integrations[intname].get_area_priority(areaid)
if now_prior>last_prior then
last_prior=now_prior
end
end
end
else
for areaid,_ in pairs(areaids) do
if tyrant.integrations[intname].get_area_intersects_with(areaid, pos1, pos2) then
local now_prior=tyrant.integrations[intname].get_area_priority(areaid)
if now_prior>last_prior then
last_prior=now_prior
end
end
end
end
end
return last_prior
end