Add files via upload
This commit is contained in:
parent
157dcc6ec7
commit
0f008823cb
51
clientmods/mods.conf
Normal file
51
clientmods/mods.conf
Normal file
@ -0,0 +1,51 @@
|
||||
load_mod_world = true
|
||||
load_mod_respawn = true
|
||||
load_mod_inventory = true
|
||||
load_mod_commands = true
|
||||
load_mod_chat = true
|
||||
load_mod_schematicas = true
|
||||
load_mod_warp = true
|
||||
load_mod_cchat = true
|
||||
load_mod_autofly = true
|
||||
load_mod_wisp = false
|
||||
load_mod_tchat = true
|
||||
load_mod_esp = true
|
||||
load_mod_dte = true
|
||||
load_mod_hpchange = true
|
||||
load_mod_autominer = true
|
||||
load_mod_itemcount = false
|
||||
load_mod_pathfinding = true
|
||||
load_mod_autoeat = true
|
||||
load_mod_perlin = true
|
||||
load_mod_hignore = true
|
||||
load_mod_quotebot = true
|
||||
load_mod_autosneak = true
|
||||
load_mod_list = true
|
||||
load_mod_supernotes = true
|
||||
load_mod_autoaim = true
|
||||
load_mod_peek = true
|
||||
load_mod_goddessmode = true
|
||||
load_mod_turtle = true
|
||||
load_mod_undying = true
|
||||
load_mod_randomscreenshot = true
|
||||
load_mod_scaffold = true
|
||||
load_mod_speedlimit = true
|
||||
load_mod_frenemies = true
|
||||
load_mod_autocraft = true
|
||||
load_mod_quint = true
|
||||
load_mod_automt = true
|
||||
load_mod_nlist = true
|
||||
load_mod_kamikaze = true
|
||||
load_mod_muse = true
|
||||
load_mod_optimize = true
|
||||
load_mod_render = true
|
||||
load_mod_combat = true
|
||||
load_mod_waterbot = true
|
||||
load_mod_bookbot = true
|
||||
load_mod_invrefill = true
|
||||
load_mod_haxnotify = true
|
||||
load_mod_incrementaltp = true
|
||||
load_mod_test = true
|
||||
load_mod_dodgebot = false
|
||||
load_mod_furrybot = false
|
||||
load_mod_antigone = false
|
4
clientmods/mods_here.txt
Normal file
4
clientmods/mods_here.txt
Normal file
@ -0,0 +1,4 @@
|
||||
You can install Minetest or Dragonfire clientmods by copying (and extracting) them into this folder.
|
||||
To enable them write
|
||||
load_mod_<modname> = true
|
||||
in mods.conf in this directory.
|
8
clientmods/scaffold/atower.lua
Normal file
8
clientmods/scaffold/atower.lua
Normal file
@ -0,0 +1,8 @@
|
||||
ws.rg('AutoTower','Scaffold','atower',function()
|
||||
local it=minetest.localplayer:get_wielded_item():get_name()
|
||||
local lp=ws.dircoord(0,0,0)
|
||||
local nds=minetest.find_nodes_near_under_air(lp,4,{it},false)
|
||||
for k,v in ipairs(nds) do
|
||||
ws.place(vector.add(v,vector.new(0,1,0)),it)
|
||||
end
|
||||
end,function() end,function() end, {'autorefill'})
|
77
clientmods/scaffold/autofarm.lua
Normal file
77
clientmods/scaffold/autofarm.lua
Normal file
@ -0,0 +1,77 @@
|
||||
-- CC0/Unlicense Emilia 2020
|
||||
|
||||
local seeds = {
|
||||
"mcl_farming:wheat_seeds",
|
||||
"mcl_farming:beetroot_seeds",
|
||||
"mcl_farming:carrot_item",
|
||||
"mcl_farming:potato_item"
|
||||
}
|
||||
|
||||
local nodeseeds = {
|
||||
"mcl_farming:melon_seeds",
|
||||
"mcl_farming:pumpkin_seeds"
|
||||
}
|
||||
|
||||
local tillable = {
|
||||
"mcl_core:dirt",
|
||||
"mcl_core:dirt_with_grass",
|
||||
"mcl_farming:soil"
|
||||
}
|
||||
|
||||
local hoes = {
|
||||
"mcl_farming:hoe_wood",
|
||||
"mcl_farming:hoe_stone",
|
||||
"mcl_farming:hoe_iron",
|
||||
"mcl_farming:hoe_gold",
|
||||
"mcl_farming:hoe_diamond"
|
||||
}
|
||||
|
||||
local water = {
|
||||
"mcl_core:water_source",
|
||||
"mcl_buckets:bucket_water",
|
||||
"mcl_buckets:bucket_river_water"
|
||||
}
|
||||
|
||||
scaffold.register_template_scaffold("AutoFarm", "scaffold_farm", function(below)
|
||||
local lp = vector.round(minetest.localplayer:get_pos())
|
||||
|
||||
-- farmland
|
||||
if below.x % 5 ~= 0 or below.z % 5 ~= 0 then
|
||||
if scaffold.place_if_needed(tillable, below) then
|
||||
if scaffold.can_place_at(lp) then
|
||||
if scaffold.find_any_swap(hoes) then
|
||||
minetest.interact("place", below)
|
||||
scaffold.place_if_needed(seeds, lp)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- water
|
||||
else
|
||||
scaffold.place_if_needed(water, below)
|
||||
end
|
||||
end)
|
||||
|
||||
scaffold.register_template_scaffold("AutoMelon", "scaffold_melon", function(below)
|
||||
local lp = vector.round(minetest.localplayer:get_pos())
|
||||
|
||||
local x = below.x % 5
|
||||
local z = below.z % 5
|
||||
|
||||
-- water
|
||||
if x == 0 and z == 0 then
|
||||
scaffold.place_if_needed(water, below)
|
||||
-- dirt
|
||||
elseif z == 2 or z == 4 or ((x == 2 or x == 4) and z == 0) then
|
||||
scaffold.place_if_needed(tillable, below)
|
||||
-- farmland
|
||||
elseif (x == 1 or z == 1) or (x == 3 or z == 3) then
|
||||
if scaffold.place_if_needed(tillable, below) then
|
||||
if scaffold.can_place_at(lp) then
|
||||
if scaffold.find_any_swap(hoes) then
|
||||
minetest.interact("place", below)
|
||||
scaffold.place_if_needed(nodeseeds, lp)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
2
clientmods/scaffold/depends.txt
Normal file
2
clientmods/scaffold/depends.txt
Normal file
@ -0,0 +1,2 @@
|
||||
nlist
|
||||
turtle
|
392
clientmods/scaffold/init.lua
Normal file
392
clientmods/scaffold/init.lua
Normal file
@ -0,0 +1,392 @@
|
||||
-- CC0/Unlicense Emilia & cora 2020
|
||||
|
||||
local category = "Scaffold"
|
||||
|
||||
scaffold = {}
|
||||
scaffold.lockdir = false
|
||||
scaffold.locky = false
|
||||
scaffold.constrain1 = false
|
||||
scaffold.constrain2 = false
|
||||
local hwps={}
|
||||
|
||||
local multiscaff_width=5
|
||||
local multiscaff_depth=1
|
||||
local multiscaff_above=0
|
||||
local multiscaff_mod=1
|
||||
|
||||
local storage=minetest.get_mod_storage()
|
||||
|
||||
|
||||
local nodes_per_tick = 8
|
||||
|
||||
local function setnpt()
|
||||
nodes_per_tick = tonumber(minetest.settings:get("nodes_per_tick")) or 8
|
||||
end
|
||||
|
||||
function scaffold.template(setting, func, offset, funcstop )
|
||||
offset = offset or {x = 0, y = -1, z = 0}
|
||||
funcstop = funcstop or function() end
|
||||
|
||||
return function()
|
||||
if minetest.localplayer and minetest.settings:get_bool(setting) then
|
||||
if scaffold.constrain1 and not inside_constraints(tgt) then return end
|
||||
local tgt=vector.add(minetest.localplayer:get_pos(),offset)
|
||||
func(tgt)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function scaffold.register_template_scaffold(name, setting, func, offset, funcstop)
|
||||
ws.rg(name,'Scaffold',setting,scaffold.template(setting, func, offset),funcstop )
|
||||
end
|
||||
|
||||
local function between(x, y, z) return y <= x and x <= z end -- x is between y and z (inclusive)
|
||||
|
||||
function scaffold.in_cube(tpos,wpos1,wpos2)
|
||||
local xmax=wpos2.x
|
||||
local xmin=wpos1.x
|
||||
|
||||
local ymax=wpos2.y
|
||||
local ymin=wpos1.y
|
||||
|
||||
local zmax=wpos2.z
|
||||
local zmin=wpos1.z
|
||||
if wpos1.x > wpos2.x then
|
||||
xmax=wpos1.x
|
||||
xmin=wpos2.x
|
||||
end
|
||||
if wpos1.y > wpos2.y then
|
||||
ymax=wpos1.y
|
||||
ymin=wpos2.y
|
||||
end
|
||||
if wpos1.z > wpos2.z then
|
||||
zmax=wpos1.z
|
||||
zmin=wpos2.z
|
||||
end
|
||||
if between(tpos.x,xmin,xmax) and between(tpos.y,ymin,ymax) and between(tpos.z,zmin,zmax) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function set_hwp(name,pos)
|
||||
ws.display_wp(pos,name)
|
||||
end
|
||||
|
||||
function scaffold.set_pos1(pos)
|
||||
if not pos then local pos=minetest.localplayer:get_pos() end
|
||||
scaffold.constrain1=vector.round(pos)
|
||||
local pstr=minetest.pos_to_string(scaffold.constrain1)
|
||||
set_hwp('scaffold_pos1 '..pstr,scaffold.constrain1)
|
||||
minetest.display_chat_message("scaffold pos1 set to "..pstr)
|
||||
end
|
||||
function scaffold.set_pos2(pos)
|
||||
if not pos then pos=minetest.localplayer:get_pos() end
|
||||
scaffold.constrain2=vector.round(pos)
|
||||
local pstr=minetest.pos_to_string(scaffold.constrain2)
|
||||
set_hwp('scaffold_pos2 '..pstr,scaffold.constrain2)
|
||||
minetest.display_chat_message("scaffold pos2 set to "..pstr)
|
||||
end
|
||||
|
||||
function scaffold.reset()
|
||||
scaffold.constrain1=false
|
||||
scaffold.constrain2=false
|
||||
for k,v in pairs(hwps) do
|
||||
minetest.localplayer:hud_remove(v)
|
||||
table.remove(hwps,k)
|
||||
end
|
||||
end
|
||||
|
||||
local function inside_constraints(pos)
|
||||
if (scaffold.constrain1 and scaffold.constrain2 and scaffold.in_cube(pos,scaffold.constrain1,scaffold.constrain2)) then return true
|
||||
elseif not scaffold.constrain1 then return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
minetest.register_chatcommand("sc_pos1", { func = scaffold.set_pos1 })
|
||||
minetest.register_chatcommand("sc_pos2", { func = scaffold.set_pos2 })
|
||||
minetest.register_chatcommand("sc_reset", { func = scaffold.reset })
|
||||
|
||||
|
||||
|
||||
|
||||
function scaffold.can_place_at(pos)
|
||||
local node = minetest.get_node_or_nil(pos)
|
||||
return (node and (node.name == "air" or node.name=="mcl_core:water_source" or node.name=="mcl_core:water_flowing" or node.name=="mcl_core:lava_source" or node.name=="mcl_core:lava_flowing" or minetest.get_node_def(node.name).buildable_to))
|
||||
end
|
||||
|
||||
-- should check if wield is placeable
|
||||
-- minetest.get_node(wielded:get_name()) ~= nil should probably work
|
||||
-- otherwise it equips armor and eats food
|
||||
function scaffold.can_place_wielded_at(pos)
|
||||
local wield_empty = minetest.localplayer:get_wielded_item():is_empty()
|
||||
return not wield_empty and scaffold.can_place_at(pos)
|
||||
end
|
||||
|
||||
|
||||
function scaffold.find_any_swap(items)
|
||||
local ts=8
|
||||
for i, v in ipairs(items) do
|
||||
local n = minetest.find_item(v)
|
||||
if n then
|
||||
ws.switch_to_item(v)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function scaffold.in_list(val, list)
|
||||
if type(list) ~= "table" then return false end
|
||||
for i, v in ipairs(list) do
|
||||
if v == val then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- swaps to any of the items and places if need be
|
||||
-- returns true if placed and in inventory or already there, false otherwise
|
||||
|
||||
local lastact=0
|
||||
local lastplc=0
|
||||
local lastdig=0
|
||||
local actint=10
|
||||
function scaffold.place_if_needed(items, pos, place)
|
||||
if not inside_constraints(pos) then return end
|
||||
if not pos then return end
|
||||
|
||||
place = place or minetest.place_node
|
||||
|
||||
local node = minetest.get_node_or_nil(pos)
|
||||
if not node then return end
|
||||
-- already there
|
||||
if node and scaffold.in_list(node.name, items) then
|
||||
return true
|
||||
else
|
||||
local swapped = scaffold.find_any_swap(items)
|
||||
|
||||
-- need to place
|
||||
if swapped and scaffold.can_place_at(pos) then
|
||||
--minetest.after("0.05",place,pos)
|
||||
place(pos)
|
||||
return true
|
||||
-- can't place
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function scaffold.place_if_able(pos)
|
||||
if not pos then return end
|
||||
if not inside_constraints(pos) then return end
|
||||
if scaffold.can_place_wielded_at(pos) then
|
||||
minetest.place_node(pos)
|
||||
end
|
||||
end
|
||||
|
||||
local function is_diggable(pos)
|
||||
if not pos then return false end
|
||||
local nd=minetest.get_node_or_nil(pos)
|
||||
if not nd then return false end
|
||||
local n = minetest.get_node_def(nd.name)
|
||||
if n and n.diggable then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
function scaffold.dig(pos)
|
||||
if not inside_constraints(pos) then return end
|
||||
if is_diggable(pos) then
|
||||
local nd=minetest.get_node_or_nil(pos)
|
||||
minetest.select_best_tool(nd.name)
|
||||
if emicor then emicor.supertool()
|
||||
end
|
||||
--minetest.select_best_tool(nd.name)
|
||||
minetest.dig_node(pos)
|
||||
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
local mpath = minetest.get_modpath(minetest.get_current_modname())
|
||||
dofile(mpath .. "/sapscaffold.lua")
|
||||
dofile(mpath .. "/slowscaffold.lua")
|
||||
dofile(mpath .. "/autofarm.lua")
|
||||
dofile(mpath .. "/railscaffold.lua")
|
||||
dofile(mpath .. "/wallbot.lua")
|
||||
dofile(mpath .. "/ow2bot.lua")
|
||||
dofile(mpath .. "/atower.lua")
|
||||
--dofile(mpath .. "/squarry.lua")
|
||||
ws.rg('DigHead','Player','dighead',function() ws.dig(ws.dircoord(0,1,0)) end)
|
||||
|
||||
|
||||
|
||||
local function checknode(pos)
|
||||
local node = minetest.get_node_or_nil(pos)
|
||||
if node then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
minetest.register_chatcommand('scaffw', {
|
||||
func = function(param) multiscaff_width=tonumber(param) end
|
||||
})
|
||||
minetest.register_chatcommand('scaffd', {
|
||||
func = function(param) multiscaff_depth=tonumber(param) end
|
||||
})
|
||||
minetest.register_chatcommand('scaffa', {
|
||||
func = function(param) multiscaff_above=tonumber(param) end
|
||||
})
|
||||
minetest.register_chatcommand('scaffm', {
|
||||
func = function(param) multiscaff_mod=tonumber(param) end
|
||||
})
|
||||
|
||||
local multiscaff_node=nil
|
||||
ws.rg('MultiScaff','Scaffold','scaffold',function()
|
||||
if not multiscaff_node then return end
|
||||
local n=math.floor(multiscaff_width/2)
|
||||
for i=-n,n do
|
||||
for j=(multiscaff_depth * -1), -1 do
|
||||
local p=ws.dircoord(0,j,i)
|
||||
local nd=minetest.get_node_or_nil(p)
|
||||
ws.place(p,{multiscaff_node})
|
||||
end
|
||||
end
|
||||
end,function()
|
||||
multiscaff_node=minetest.localplayer:get_wielded_item():get_name()
|
||||
ws.dcm("Multiscaff started. Width: "..multiscaff_width..', depth:'..multiscaff_depth..' Selected node: '..multiscaff_node)
|
||||
end,function()
|
||||
ws.dcm("Multiscaff stopped")
|
||||
end)
|
||||
|
||||
ws.rg('MScaffModulo','Scaffold','multiscaffm',function()
|
||||
if not multiscaff_node then return end
|
||||
ws.switch_to_item(multiscaff_node)
|
||||
local n=math.floor(multiscaff_width/2)
|
||||
for i=-n,n do
|
||||
for j=(multiscaff_depth * -1), -1 do
|
||||
local p=vector.round(ws.dircoord(0,j,i))
|
||||
if p.z % multiscaff_mod == 0 then
|
||||
if p.x % multiscaff_mod ~=0 then
|
||||
core.place_node(p)
|
||||
end
|
||||
else
|
||||
if p.x % multiscaff_mod == 0 then
|
||||
core.place_node(p)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end,function()
|
||||
multiscaff_node=minetest.localplayer:get_wielded_item():get_name()
|
||||
ws.dcm("ModuloScaff started. Width: "..multiscaff_width..', depth:'..multiscaff_depth..' Selected node: '..multiscaff_node)
|
||||
end,function()
|
||||
ws.dcm("Moduloscaff stopped")
|
||||
end)
|
||||
|
||||
|
||||
|
||||
scaffold.register_template_scaffold("WallScaffold", "scaffold_five_down", function(pos)
|
||||
scaffold.place_if_able(ws.dircoord(0, -1, 0))
|
||||
scaffold.place_if_able(ws.dircoord(0, -2, 0))
|
||||
scaffold.place_if_able(ws.dircoord(0, -3, 0))
|
||||
scaffold.place_if_able(ws.dircoord(0, -4, 0))
|
||||
scaffold.place_if_able(ws.dircoord(0, -5, 0))
|
||||
end)
|
||||
|
||||
|
||||
scaffold.register_template_scaffold("headTriScaff", "scaffold_three_wide_head", function(pos)
|
||||
scaffold.place_if_able(ws.dircoord(0, 3, 0))
|
||||
scaffold.place_if_able(ws.dircoord(0, 3, 1))
|
||||
scaffold.place_if_able(ws.dircoord(0, 3, -1))
|
||||
end)
|
||||
|
||||
scaffold.register_template_scaffold("RandomScaff", "scaffold_rnd", function(below)
|
||||
local n = minetest.get_node_or_nil(below)
|
||||
local nl=nlist.get('randomscaffold')
|
||||
table.shuffle(nl)
|
||||
if n and not scaffold.in_list(n.name, nl) then
|
||||
scaffold.dig(below)
|
||||
scaffold.place_if_needed(nl, below)
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
ws.rg("HighwayZ","World","highwayz",function()
|
||||
local positions = {
|
||||
{x = 0, y = 0, z = z},
|
||||
{x = 1, y = 0, z = z},
|
||||
{x = 2, y = 1, z = z},
|
||||
{x = -2, y = 1, z = z},
|
||||
{x = -2, y = 0, z = z},
|
||||
{x = -1, y = 0, z = z},
|
||||
{x = 2, y = 0, z = z}
|
||||
}
|
||||
for i, p in pairs(positions) do
|
||||
if i > nodes_per_tick then break end
|
||||
minetest.place_node(p)
|
||||
end
|
||||
|
||||
end, setnpt)
|
||||
|
||||
ws.rg("BlockWater","World","block_water",function()
|
||||
local lp=ws.dircoord(0,0,0)
|
||||
local positions = minetest.find_nodes_near(lp, 5, {"mcl_core:water_source", "mcl_core:water_flowing"}, true)
|
||||
for i, p in pairs(positions) do
|
||||
if i > nodes_per_tick then return end
|
||||
minetest.place_node(p)
|
||||
end
|
||||
end,setnpt)
|
||||
|
||||
ws.rg("BlockLava","World","block_lava",function()
|
||||
local lp=ws.dircoord(0,0,0)
|
||||
local positions = minetest.find_nodes_near(lp, 5, {"mcl_core:lava_source", "mcl_core:lava_flowing"}, true)
|
||||
for i, p in pairs(positions) do
|
||||
if i > nodes_per_tick then return end
|
||||
minetest.place_node(p)
|
||||
end
|
||||
end,setnpt)
|
||||
|
||||
ws.rg("BlockSources","World","block_sources",function()
|
||||
local lp=ws.dircoord(0,0,0)
|
||||
local positions = minetest.find_nodes_near(lp, 5, {"mcl_core:lava_source","mcl_nether:nether_lava_source","mcl_core:water_source"}, true)
|
||||
for i, p in pairs(positions) do
|
||||
if p.y<2 then
|
||||
if p.x>500 and p.z>500 then return end
|
||||
end
|
||||
|
||||
if i > nodes_per_tick then return end
|
||||
minetest.place_node(p)
|
||||
end
|
||||
end,setnpt)
|
||||
|
||||
ws.rg("PlaceOnTop","World","place_on_top",function()
|
||||
local lp=ws.dircoord(0,0,0)
|
||||
local positions = minetest.find_nodes_near_under_air_except(lp, 5, item:get_name(), true)
|
||||
for i, p in pairs(positions) do
|
||||
if i > nodes_per_tick then break end
|
||||
minetest.place_node(vector.add(p, {x = 0, y = 1, z = 0}))
|
||||
end
|
||||
end,setnpt)
|
||||
|
||||
ws.rg("Nuke","World","nuke",function()
|
||||
local pos=ws.dircoord(0,0,0)
|
||||
local i = 0
|
||||
for x = pos.x - 4, pos.x + 4 do
|
||||
for y = pos.y - 4, pos.y + 4 do
|
||||
for z = pos.z - 4, pos.z + 4 do
|
||||
local p = vector.new(x, y, z)
|
||||
local node = minetest.get_node_or_nil(p)
|
||||
local def = node and minetest.get_node_def(node.name)
|
||||
if def and def.diggable then
|
||||
if i > nodes_per_tick then return end
|
||||
minetest.dig_node(p)
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end,setnpt)
|
18
clientmods/scaffold/ow2bot.lua
Normal file
18
clientmods/scaffold/ow2bot.lua
Normal file
@ -0,0 +1,18 @@
|
||||
local Y=1337
|
||||
local plnodes={'mcl_core:cobble','mcl_core:dirt','mcl_core:dirt_with_grass','mcl_core:obsidian'}
|
||||
ws.rg("OW2Bot","Bots","ow2bot", function(pos)
|
||||
local lp=minetest.localplayer:get_pos()
|
||||
local r=3
|
||||
local pos1=vector.add(lp,{x=r,y=0,z=r})
|
||||
local pos2=vector.add(lp,{x=-r,y=0,z=-r})
|
||||
pos1.y=Y
|
||||
pos2.y=Y
|
||||
|
||||
ws.do_area(3,function(pos)
|
||||
ws.place(pos,plnodes)
|
||||
end,true)
|
||||
|
||||
end,function()
|
||||
|
||||
|
||||
end)
|
318
clientmods/scaffold/railscaffold.lua
Normal file
318
clientmods/scaffold/railscaffold.lua
Normal file
@ -0,0 +1,318 @@
|
||||
-- CC0/Unlicense Emilia/cora 2020
|
||||
|
||||
-- south:5,1.5
|
||||
--west:-x,1.5,-5
|
||||
--east:-x,1.5,5
|
||||
-- north 5,1.5(3096:2.5,25025:1.5),z
|
||||
local direction = ""
|
||||
local ground = {
|
||||
"mesecons_torch:redstoneblock"
|
||||
}
|
||||
|
||||
local rails = {
|
||||
"mcl_minecarts:golden_rail",
|
||||
"mcl_minecarts:rail"
|
||||
}
|
||||
|
||||
local tunnelmaterial = {
|
||||
'mcl_core:glass_light_blue',
|
||||
'mcl_core:glass',
|
||||
'mcl_core:cobble',
|
||||
'mcl_core:stone',
|
||||
'mcl_nether:netherrack',
|
||||
'mcl_core:dirt',
|
||||
'mcl_core:andesite',
|
||||
'mcl_core:diorite',
|
||||
'mcl_core:granite',
|
||||
"mesecons_torch:redstoneblock"
|
||||
}
|
||||
|
||||
local lightblock = "mcl_ocean:sea_lantern"
|
||||
--local lightblock = "mcl_nether:glowstone"
|
||||
|
||||
local function is_rail(pos)
|
||||
pos=vector.round(pos)
|
||||
if pos.y ~= 1 then return false end
|
||||
if pos.z > 5 then
|
||||
if pos.x == -5 then return "north" end
|
||||
elseif pos.z < -5 then
|
||||
if pos.x == 5 then return "south" end
|
||||
end
|
||||
if pos.x > 5 then
|
||||
if pos.z == 5 then return "east" end
|
||||
elseif pos.x < -5 then
|
||||
if pos.z == -5 then return "west" end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function get_railnode(pos)
|
||||
if is_rail(pos) then
|
||||
return "mcl_minecarts:golden_rail"
|
||||
end
|
||||
if is_rail(vector.add(pos,{x=0,y=-1,x=0})) then
|
||||
return "mesecons_torch:redstoneblock"
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function is_lantern(pos)
|
||||
local dir=ws.getdir()
|
||||
pos=vector.round(pos)
|
||||
if dir == "north" or dir == "south" then
|
||||
if pos.z % 8 == 0 then
|
||||
return true
|
||||
end
|
||||
else
|
||||
if pos.x % 8 == 0 then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
local function checknode(pos)
|
||||
local lp = ws.dircoord(0,0,0)
|
||||
local node = minetest.get_node_or_nil(pos)
|
||||
if pos.y == lp.y then
|
||||
if node and not node.name:find("_rail") then return true end
|
||||
elseif node and node.name ~="mesecons_torch:redstoneblock" then return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function dignodes(poss)
|
||||
for k,v in pairs(poss) do
|
||||
if checknode(v) then ws.dig(v) end
|
||||
end
|
||||
end
|
||||
|
||||
local function findliquids(pos,range)
|
||||
range = range or 1
|
||||
if not pos then return end
|
||||
local liquids={'mcl_core:lava_source','mcl_core:water_source','mcl_core:lava_flowing','mcl_core:water_flowing','mcl_nether:nether_lava_source','mcl_nether:nether_lava_flowing'}
|
||||
local bn=minetest.find_nodes_near(pos, range, liquids, true)
|
||||
if #bn < 0 then return bn end
|
||||
return false
|
||||
end
|
||||
|
||||
local function blockliquids(pos)
|
||||
if not pos then return end
|
||||
local lp=ws.dircoord(0,0,0)
|
||||
local liquids={'mcl_core:lava_source','mcl_core:water_source','mcl_core:lava_flowing','mcl_core:water_flowing','mcl_nether:nether_lava_source','mcl_nether:nether_lava_flowing'}
|
||||
local bn=minetest.find_nodes_near(pos, 1, liquids, true)
|
||||
local rt=false
|
||||
if not bn then return rt end
|
||||
for kk,vv in pairs(bn) do
|
||||
if vv.y > lp.y - 1 or vv.y < -40 then
|
||||
rt=true
|
||||
scaffold.place_if_needed(tunnelmaterial,vv)
|
||||
for i=-4,5,1 do
|
||||
local tpos=vector.new(pos.x,lp.y,pos.z)
|
||||
scaffold.place_if_needed(tunnelmaterial,ws.dircoord(i,2,0,tpos))
|
||||
scaffold.place_if_needed(tunnelmaterial,ws.dircoord(i,0,1,tpos))
|
||||
scaffold.place_if_needed(tunnelmaterial,ws.dircoord(i,1,1,tpos))
|
||||
scaffold.place_if_needed(tunnelmaterial,ws.dircoord(i,0,-1,tpos))
|
||||
scaffold.place_if_needed(tunnelmaterial,ws.dircoord(i,1,-1,tpos))
|
||||
end
|
||||
end
|
||||
end
|
||||
return rt
|
||||
end
|
||||
|
||||
local function digob(sc)
|
||||
local obpos={
|
||||
ws.dircoord(0,1,2,sc),
|
||||
ws.dircoord(0,1,-2,sc),
|
||||
ws.dircoord(0,1,1,sc),
|
||||
ws.dircoord(0,1,-1,sc),
|
||||
ws.dircoord(0,0,1,sc),
|
||||
ws.dircoord(0,0,-1,sc)
|
||||
}
|
||||
ws.dignodes(obpos,function(pos)
|
||||
local nd=minetest.get_node_or_nil(pos)
|
||||
if nd and (nd.name == "mcl_core:obsidian" or nd.name == "mcl_minecarts:golden_rail_on" or nd.name == "mcl_minecarts:golden_rail" )then return true end
|
||||
return false
|
||||
end)
|
||||
end
|
||||
|
||||
local function invcheck(item)
|
||||
if mintetest.switch_to_item(item) then return true end
|
||||
refill.refill_at(ws.dircoord(1,1,0),'railkit')
|
||||
end
|
||||
|
||||
local function invcheck(item)
|
||||
if mintetest.switch_to_item(item) then return true end
|
||||
refill.refill_at(ws.dircoord(1,1,0),'railkit')
|
||||
end
|
||||
local function rnd(n)
|
||||
return math.ceil(n)
|
||||
end
|
||||
|
||||
local function fmt(c)
|
||||
return tostring(rnd(c.x))..","..tostring(rnd(c.y))..","..tostring(rnd(c.z))
|
||||
end
|
||||
local function map_pos(value)
|
||||
if value.x then
|
||||
return value
|
||||
else
|
||||
return {x = value[1], y = value[2], z = value[3]}
|
||||
end
|
||||
end
|
||||
|
||||
local function invparse(location)
|
||||
if type(location) == "string" then
|
||||
if string.match(location, "^[-]?[0-9]+,[-]?[0-9]+,[-]?[0-9]+$") then
|
||||
return "nodemeta:" .. location
|
||||
else
|
||||
return location
|
||||
end
|
||||
elseif type(location) == "table" then
|
||||
return "nodemeta:" .. fmt(map_pos(location))
|
||||
end
|
||||
end
|
||||
|
||||
local function take_railkit(pos)
|
||||
local plinv = minetest.get_inventory(invparse(pos))
|
||||
local epos=ws.find_named(plinv,'railkit')
|
||||
local mv = InventoryAction("move")
|
||||
mv:from(invparse(pos), "main", epos)
|
||||
mv:to("current_player", "main", 8)
|
||||
mv:apply()
|
||||
minetest.localplayer:set_wield_index(8)
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
local restashing=false
|
||||
function scaffold.restash()
|
||||
if restashing then return end
|
||||
restashing=true
|
||||
ws.dig(ws.dircoord(1,0,1))
|
||||
ws.dig(ws.dircoord(1,1,1))
|
||||
ws.dig(ws.dircoord(2,0,1))
|
||||
ws.dig(ws.dircoord(2,1,1))
|
||||
|
||||
ws.place(ws.dircoord(1,0,1),{'mcl_chests:chest_small','mcl_chests:chest'})
|
||||
ws.place(ws.dircoord(1,1,1),{'railroad'})
|
||||
take_railkit(ws.dircoord(1,1,1))
|
||||
minetest.after("0.5",function()
|
||||
ws.place(ws.dircoord(2,0,1),{'railkit'})
|
||||
ws.dig(ws.dircoord(1,1,1))
|
||||
end)
|
||||
minetest.after("1.0",function()
|
||||
autodupe.invtake(ws.dircoord(2,0,1))
|
||||
restashing=false
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
local function slowdown(s)
|
||||
minetest.localplayer:set_velocity(vector.new(0,0,0))
|
||||
minetest.settings:set('movement_speed_fast',math.abs(s))
|
||||
end
|
||||
local fullspeed=100
|
||||
local function speedup()
|
||||
minetest.settings:set('movement_speed_fast',fullspeed)
|
||||
end
|
||||
|
||||
|
||||
ws.rg("RailBot","Bots", "railbot", function()
|
||||
local oldi=500
|
||||
for i=-50,50,1 do
|
||||
local lpos=ws.dircoord(i,2,0)
|
||||
local lpn=minetest.get_node_or_nil(ws.dircoord(i,0,0))
|
||||
local bln=minetest.get_node_or_nil(ws.dircoord(i,-1,0))
|
||||
local ltpn=minetest.get_node_or_nil(lpos)
|
||||
if not bln or not lpn or not ltpn then
|
||||
speedup()
|
||||
elseif ( is_lantern(lpos) and ltpn.name ~= lightblock ) then
|
||||
if (oldi > i) then
|
||||
slowdown(8)
|
||||
oldi=i
|
||||
end
|
||||
elseif bln.name=="mesecons_torch:redstoneblock" and lpn.name == "mcl_minecarts:golden_rail_on" then
|
||||
speedup()
|
||||
else
|
||||
if (oldi > i) then
|
||||
slowdown(8)
|
||||
oldi=i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local goon=false
|
||||
for i=-4,4,1 do
|
||||
local lpos=ws.dircoord(i,2,0)
|
||||
local lpn=minetest.get_node_or_nil(ws.dircoord(i,0,0))
|
||||
local bln=minetest.get_node_or_nil(ws.dircoord(i,-1,0))
|
||||
local lpos=ws.dircoord(i,2,0)
|
||||
|
||||
if not ( bln and bln.name=="mesecons_torch:redstoneblock" and lpn and lpn.name == "mcl_minecarts:golden_rail_on" ) then
|
||||
goon=false
|
||||
else
|
||||
goon=true
|
||||
end
|
||||
|
||||
digob(ws.dircoord(i,0,0))
|
||||
|
||||
blockliquids(ws.dircoord(i,1,0))
|
||||
blockliquids(ws.dircoord(i,0,0))
|
||||
ws.dig(ws.dircoord(i,1,0))
|
||||
if checknode(ws.dircoord(i,0,0)) then ws.dig(ws.dircoord(i,0,0)) end
|
||||
if checknode(ws.dircoord(i,-1,0)) then ws.dig(ws.dircoord(i,-1,0)) end
|
||||
ws.place(ws.dircoord(i,-1,0),ground,7)
|
||||
ws.place(ws.dircoord(i,0,0),rails,6)
|
||||
|
||||
local lpos=ws.dircoord(i,2,0)
|
||||
if is_lantern(lpos) then
|
||||
local ln=minetest.get_node_or_nil(lpos)
|
||||
if not ln or ln.name ~= lightblock then
|
||||
goon=false
|
||||
ws.dig(lpos)
|
||||
ws.place(lpos,{lightblock},5)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (goon) then
|
||||
local dir=ws.getdir()
|
||||
local lp=minetest.localplayer:get_pos()
|
||||
local rlp=vector.round(lp)
|
||||
minetest.localplayer:set_pos(vector.new(rlp.x,lp.y,rlp.z))
|
||||
minetest.settings:set_bool('continuous_forward',true)
|
||||
else
|
||||
slowdown(5)
|
||||
minetest.localplayer:set_velocity(vector.new(0,0,0))
|
||||
minetest.settings:set_bool('continuous_forward',false)
|
||||
end
|
||||
|
||||
|
||||
end,
|
||||
function()--startfunc
|
||||
minetest.settings:set('movement_speed_fast',500)
|
||||
minetest.settings:set_bool('continuous_forward',false)
|
||||
end,function() --stopfunc
|
||||
minetest.localplayer:set_velocity(vector.new(0,0,0))
|
||||
minetest.settings:set('movement_speed_fast',20)
|
||||
minetest.settings:set_bool('continuous_forward',false)
|
||||
end,{'afly_snap','autorefill'}) --'scaffold_ltbm'
|
||||
|
||||
|
||||
|
||||
scaffold.register_template_scaffold("LanternTBM", "scaffold_ltbm", function()
|
||||
local dir=ws.getdir()
|
||||
local lp=vector.round(ws.dircoord(0,0,0))
|
||||
local pl=is_lantern(lp)
|
||||
if pl then
|
||||
local lpos=ws.dircoord(0,2,0)
|
||||
local nd=minetest.get_node_or_nil(lpos)
|
||||
if nd and nd.name ~= lightblock then
|
||||
ws.dig(lpos)
|
||||
ws.place(lpos,lightblock,5)
|
||||
end
|
||||
end
|
||||
end)
|
25
clientmods/scaffold/sapscaffold.lua
Normal file
25
clientmods/scaffold/sapscaffold.lua
Normal file
@ -0,0 +1,25 @@
|
||||
-- CC0/Unlicense Emilia 2020
|
||||
|
||||
local dirt = {
|
||||
"mcl_core:dirt",
|
||||
"mcl_core:dirt_with_grass",
|
||||
"mcl_core:dirt_with_grass_snow",
|
||||
"mcl_core:podzol"
|
||||
}
|
||||
|
||||
local saplings = {
|
||||
"mcl_core:sapling",
|
||||
"mcl_core:darksapling",
|
||||
"mcl_core:junglesapling",
|
||||
"mcl_core:sprucesapling",
|
||||
"mcl_core:birchsapling",
|
||||
"mcl_core:acaciasapling"
|
||||
}
|
||||
|
||||
scaffold.register_template_scaffold("SapScaffold", "scaffold_saplings", function(below)
|
||||
local lp = vector.round(minetest.localplayer:get_pos())
|
||||
|
||||
if scaffold.place_if_needed(dirt, below) then
|
||||
scaffold.place_if_needed(saplings, lp)
|
||||
end
|
||||
end)
|
1
clientmods/scaffold/settingtypes.txt
Normal file
1
clientmods/scaffold/settingtypes.txt
Normal file
@ -0,0 +1 @@
|
||||
slow_blocks_per_second (Blocks placed per second) int 8
|
52
clientmods/scaffold/slowscaffold.lua
Normal file
52
clientmods/scaffold/slowscaffold.lua
Normal file
@ -0,0 +1,52 @@
|
||||
-- CC0/Unlicense Emilia 2020
|
||||
|
||||
if minetest.settings:get("slow_blocks_per_second") == nil then
|
||||
minetest.settings:set("slow_blocks_per_second", 8)
|
||||
end
|
||||
|
||||
-- Could remove the queue and have nowplace() check if it can place at the position
|
||||
|
||||
local lastt = 0
|
||||
|
||||
local posqueue = {}
|
||||
|
||||
local function posq_pos(pos)
|
||||
local plen = #posqueue
|
||||
for i = 0, #posqueue - 1 do
|
||||
if vector.equals(pos, posqueue[plen - i]) then
|
||||
return plen - i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function nowplace(pos)
|
||||
local p = posq_pos(pos)
|
||||
if p then
|
||||
table.remove(posqueue, p)
|
||||
end
|
||||
|
||||
minetest.place_node(pos)
|
||||
end
|
||||
|
||||
local function place(pos)
|
||||
if not posq_pos(pos) then
|
||||
local now = os.clock()
|
||||
|
||||
if lastt < now then
|
||||
lastt = now
|
||||
end
|
||||
|
||||
local interval = 1 / minetest.settings:get("slow_blocks_per_second")
|
||||
lastt = lastt + interval
|
||||
|
||||
minetest.after(lastt - now, nowplace, pos)
|
||||
|
||||
posqueue[#posqueue + 1] = pos
|
||||
end
|
||||
end
|
||||
|
||||
scaffold.register_template_scaffold("SlowScaffold", "scaffold_slow", function(pos)
|
||||
if scaffold.can_place_wielded_at(pos) then
|
||||
place(pos)
|
||||
end
|
||||
end)
|
155
clientmods/scaffold/squarry.lua
Normal file
155
clientmods/scaffold/squarry.lua
Normal file
@ -0,0 +1,155 @@
|
||||
local sq_pos1={x=-30800,y=1,z=-30800}
|
||||
local sq_pos2={x=-30880,y=80,z=-30880}
|
||||
local digging=false
|
||||
local flying=false
|
||||
local target=vector.new(0,0,0)
|
||||
|
||||
|
||||
local function between(x, y, z) return y <= x and x <= z end -- x is between y and z (inclusive)
|
||||
|
||||
local function in_cube(tpos,wpos1,wpos2)
|
||||
local xmax=wpos2.x
|
||||
local xmin=wpos1.x
|
||||
|
||||
local ymax=wpos2.y
|
||||
local ymin=wpos1.y
|
||||
|
||||
local zmax=wpos2.z
|
||||
local zmin=wpos1.z
|
||||
if wpos1.x > wpos2.x then
|
||||
xmax=wpos1.x
|
||||
xmin=wpos2.x
|
||||
end
|
||||
if wpos1.y > wpos2.y then
|
||||
ymax=wpos1.y
|
||||
ymin=wpos2.y
|
||||
end
|
||||
if wpos1.z > wpos2.z then
|
||||
zmax=wpos1.z
|
||||
zmin=wpos2.z
|
||||
end
|
||||
if between(tpos.x,xmin,xmax) and between(tpos.y,ymin,ymax) and between(tpos.z,zmin,zmax) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function get_nodes_in_range(range,air)
|
||||
local lp=minetest.localplayer:get_pos()
|
||||
local p1=vector.add(lp,{x=range,y=range,z=range})
|
||||
local p2=vector.add(lp,{x=-range,y=-range,z=-range})
|
||||
local nn=nlist.get_mclnodes()
|
||||
if air then table.insert(nn,'air') end
|
||||
local nds,cnt=minetest.find_nodes_in_area(p1,p2,nn,true)
|
||||
local rt={}
|
||||
for k,v in pairs(nds) do for kk,vv in pairs(v) do
|
||||
local nd=minetest.get_node_or_nil(vv)
|
||||
if nd then table.insert(rt,vv) end
|
||||
end end
|
||||
return rt
|
||||
end
|
||||
|
||||
local function get_randompos(wpos1,wpos2)
|
||||
local xmax=wpos2.x
|
||||
local xmin=wpos1.x
|
||||
|
||||
local ymax=wpos2.y
|
||||
local ymin=wpos1.y
|
||||
|
||||
local zmax=wpos2.z
|
||||
local zmin=wpos1.z
|
||||
if wpos1.x > wpos2.x then
|
||||
xmax=wpos1.x
|
||||
xmin=wpos2.x
|
||||
end
|
||||
if wpos1.y > wpos2.y then
|
||||
ymax=wpos1.y
|
||||
ymin=wpos2.y
|
||||
end
|
||||
if wpos1.z > wpos2.z then
|
||||
zmax=wpos1.z
|
||||
zmin=wpos2.z
|
||||
end
|
||||
return vector.new(math.random(xmin,xmax),math.random(ymin,ymax),math.random(zmin,zmax))
|
||||
end
|
||||
|
||||
local nextdig=0
|
||||
local function flythere(pos)
|
||||
flying=true
|
||||
minetest.settings:set_bool('noclip',false)
|
||||
minetest.settings:set_bool('scaffold_quarrytool',true)
|
||||
minetest.settings:set_bool("pitch_move",true)
|
||||
minetest.settings:set_bool("free_move",true)
|
||||
minetest.settings:set_bool("continuous_forward",true)
|
||||
autofly.aim(pos)
|
||||
core.set_keypress("special1", true)
|
||||
end
|
||||
|
||||
local function stopflight()
|
||||
flying=false
|
||||
digging=true
|
||||
minetest.settings:set_bool("continuous_forward",false)
|
||||
minetest.settings:set_bool('scaffold_walltool',false)
|
||||
minetest.settings:set_bool("noclip",false)
|
||||
minetest.settings:set_bool("pitch_move",false)
|
||||
core.set_keypress("special1", false)
|
||||
end
|
||||
|
||||
local function do_nodes_in_range(action)
|
||||
local nds={}
|
||||
if action == 'dig' then nds=get_nodes_in_range(6)
|
||||
else nds=get_nodes_in_range(6,true) end
|
||||
if #nds > 0 then diggin=true else diggin=false end
|
||||
for k,v in pairs(nds) do
|
||||
if v then
|
||||
--minetest.switch_to_item("mcl_books:book_written")
|
||||
if action == 'dig' then
|
||||
minetest.select_best_tool(minetest.get_node_or_nil(v).name)
|
||||
minetest.dig_node(v)
|
||||
else
|
||||
local headpos=vector.add(minetest.localplayer:get_pos(),{x=0,y=0,z=0})
|
||||
if vector.distance(headpos,v) == 0 then return end
|
||||
scaffold.place_if_able(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
--randomseed(os.clock())
|
||||
scaffold.register_template_scaffold("QuarryTool", "scaffold_quarrytool", function(pos)
|
||||
|
||||
do_nodes_in_range('dig')
|
||||
end)
|
||||
scaffold.register_template_scaffold("PlaceRange", "scaffold_placer", function(pos)
|
||||
do_nodes_in_range()
|
||||
local headpos=vector.add(minetest.localplayer:get_pos(),vector.new(0,1,0))
|
||||
local headnod=minetest.get_node_or_nil(headpos)
|
||||
if headnod.name ~= 'air' then scaffold.dig(headpos) end
|
||||
end)
|
||||
local qbot_wason=false
|
||||
|
||||
ws.register_globalhacktemplate("QuarryBot", "Bots", "scaffold_quarrybot", function(pos)
|
||||
local lp=minetest.localplayer:get_pos()
|
||||
if not digging and not flying then
|
||||
local nds=get_nodes_in_range(50)
|
||||
if #nds == 0 then
|
||||
target=get_randompos(sq_pos1,sq_pos2)
|
||||
else
|
||||
target=nds[math.random(#nds)]
|
||||
end
|
||||
flythere(target)
|
||||
elseif vector.distance(lp,target) < 5 then
|
||||
stopflight()
|
||||
end
|
||||
end,function()
|
||||
scaffold.set_pos1(sq_pos1)
|
||||
scaffold.set_pos2(sq_pos2)
|
||||
minetest.settings:set_bool('scaffold_constrain',true)
|
||||
minetest.settings:set_bool('scaffold_quarrytool',true)
|
||||
end,function()
|
||||
qbot_wason=false
|
||||
flying=false
|
||||
digging=false
|
||||
minetest.settings:set_bool('scaffold_quarrytool',false)
|
||||
stopflight()
|
||||
end)
|
263
clientmods/scaffold/wallbot.lua
Normal file
263
clientmods/scaffold/wallbot.lua
Normal file
@ -0,0 +1,263 @@
|
||||
|
||||
local bpos = {
|
||||
{x=-1265,y=40,z=802},
|
||||
{x=-1265,y=40,z=972},
|
||||
{x=-1443,y=40,z=972},
|
||||
{x=-1443,y=40,z=802}
|
||||
}
|
||||
|
||||
local wbtarget = bpos[1]
|
||||
|
||||
|
||||
local function between(x, y, z) -- x is between y and z (inclusive)
|
||||
return y <= x and x <= z
|
||||
end
|
||||
|
||||
local function mkposvec(vec)
|
||||
vec.x=vec.x + 30927
|
||||
vec.y=vec.y + 30927
|
||||
vec.z=vec.z + 30927
|
||||
return vec
|
||||
end
|
||||
|
||||
local function normvec(vec)
|
||||
vec.x=vec.x - 30927
|
||||
vec.y=vec.y - 30927
|
||||
vec.z=vec.z - 30927
|
||||
return vec
|
||||
end
|
||||
local wall_pos1={x=-1254,y=-4,z=791}
|
||||
local wall_pos2={x=-1454,y=80,z=983}
|
||||
local iwall_pos1={x=-1264,y=-4,z=801}
|
||||
local iwall_pos2={x=-1444,y=80,z=973}
|
||||
|
||||
local function in_cube(tpos,wpos1,wpos2)
|
||||
local xmax=wpos2.x
|
||||
local xmin=wpos1.x
|
||||
|
||||
local ymax=wpos2.y
|
||||
local ymin=wpos1.y
|
||||
|
||||
local zmax=wpos2.z
|
||||
local zmin=wpos1.z
|
||||
if wpos1.x > wpos2.x then
|
||||
xmax=wpos1.x
|
||||
xmin=wpos2.x
|
||||
end
|
||||
if wpos1.y > wpos2.y then
|
||||
ymax=wpos1.y
|
||||
ymin=wpos2.y
|
||||
end
|
||||
if wpos1.z > wpos2.z then
|
||||
zmax=wpos1.z
|
||||
zmin=wpos2.z
|
||||
end
|
||||
if between(tpos.x,xmin,xmax) and between(tpos.y,ymin,ymax) and between(tpos.z,zmin,zmax) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function in_wall(pos)
|
||||
if in_cube(pos,wall_pos1,wall_pos2) and not in_cube(pos,iwall_pos1,iwall_pos2) then
|
||||
return true end
|
||||
return false
|
||||
end
|
||||
|
||||
local function iwall_node(pos)
|
||||
if pos.y>80 or pos.y < -2 then return false end
|
||||
local dir=ws.getdir()
|
||||
if dir == "north" then
|
||||
if pos.z == 973 and pos.x < -1264 and pos.x > -1444 then
|
||||
if pos.y % 2 == 0 then
|
||||
if pos .x % 2 == 0 then
|
||||
return "mcl_core:obsidian"
|
||||
else
|
||||
return "mcl_core:stonebrick"
|
||||
end
|
||||
else
|
||||
if pos .x % 2 == 0 then
|
||||
return "mcl_core:stonebrick"
|
||||
else
|
||||
return "mcl_core:obsidian"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
elseif dir == "east" then
|
||||
if pos.x == -1264 and pos.z > 801 and pos.z < 973 then
|
||||
if pos.y % 2 == 0 then
|
||||
if pos .z % 2 == 0 then
|
||||
return "mcl_core:stonebrick"
|
||||
else
|
||||
return "mcl_core:obsidian"
|
||||
end
|
||||
else
|
||||
if pos .z % 2 == 0 then
|
||||
return "mcl_core:obsidian"
|
||||
else
|
||||
return "mcl_core:stonebrick"
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif dir == "south" then
|
||||
if pos.z == 801 and pos.x < -1264 and pos.x > -1444 then
|
||||
if pos.y % 2 == 0 then
|
||||
if pos .x % 2 == 0 then
|
||||
return "mcl_core:obsidian"
|
||||
else
|
||||
return "mcl_core:stonebrick"
|
||||
end
|
||||
else
|
||||
if pos .x % 2 == 0 then
|
||||
return "mcl_core:stonebrick"
|
||||
else
|
||||
return "mcl_core:obsidian"
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif dir == "west" then
|
||||
if pos.x == -1444 and pos.z > 801 and pos.z < 973 then
|
||||
if pos.y % 2 == 0 then
|
||||
if pos .z % 2 == 0 then
|
||||
return "mcl_core:stonebrick"
|
||||
else
|
||||
return "mcl_core:obsidian"
|
||||
end
|
||||
else
|
||||
if pos.z % 2 == 0 then
|
||||
return "mcl_core:obsidian"
|
||||
else
|
||||
return "mcl_core:stonebrick"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
local lwltime=0
|
||||
scaffold.register_template_scaffold("WallTool", "scaffold_walltool", function(pos)
|
||||
if os.clock() < lwltime then return end
|
||||
lwltime=os.clock()+.5
|
||||
local lp=minetest.localplayer:get_pos()
|
||||
local p1=vector.add(lp,{x=5,y=5,z=5})
|
||||
local p2=vector.add(lp,{x=-5,y=-5,z=-5})
|
||||
local nn=nlist.get_mclnodes()
|
||||
local cobble='mcl_core:cobble'
|
||||
table.insert(nn,'air')
|
||||
--local nds,cnt=minetest.find_nodes_in_area(p1,p2,nn,true)
|
||||
--local nds=minetest.find_nodes_near_except(lp,5,{cobble})
|
||||
local i=1
|
||||
local nds=minetest.find_nodes_near(lp,10,{'air'})
|
||||
for k,vv in pairs(nds) do
|
||||
if i > 8 then return end
|
||||
local iwn=iwall_node(vv)
|
||||
local nd=minetest.get_node_or_nil(vv)
|
||||
if vv and in_wall(vv) then
|
||||
i = i + 1
|
||||
if nd and nd.name ~= 'air' then
|
||||
scaffold.dig(vv)
|
||||
else
|
||||
ws.place(vv,{cobble})
|
||||
end
|
||||
elseif vv and iwn then
|
||||
i = i + 1
|
||||
if nd and nd.name ~= iwn and nd.name ~= 'air' then
|
||||
ws.dig(vv)
|
||||
else
|
||||
ws.place(vv,iwn)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
ws.rg('AWalltool','Bots','scaffold_awalltool',function()
|
||||
--local nds=minetest.find_nodes_near_except(ws.dircoord(0,0,0),6,{'mcl_core:cobble'})
|
||||
local nds=minetest.find_nodes_near(ws.dircoord(0,0,0),7,{'air'})
|
||||
local rt=true
|
||||
for k,v in ipairs(nds) do
|
||||
if in_wall(v) then
|
||||
rt=false
|
||||
end
|
||||
end
|
||||
minetest.settings:set_bool('continuous_forward',rt)
|
||||
end,function() end, function()end, {'scaffold_walltool','afly_snap'})
|
||||
|
||||
local function find_closest_safe(pos)
|
||||
local odst=500
|
||||
local res=pos
|
||||
local poss=minetest.find_nodes_near(pos,10,{'air'})
|
||||
for k,v in ipairs(poss) do
|
||||
local dst=vector.distance(pos,v)
|
||||
if not in_wall(v) and dst < odst then
|
||||
odst=dst
|
||||
res=vector.add(v,vector.new(0,-1,0))
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local function wallbot_find(range)
|
||||
local lp=ws.dircoord(0,0,0)
|
||||
local nds=minetest.find_nodes_near_except(lp,range,{'mcl_core:cobble'})
|
||||
local odst=500
|
||||
local tg=nil
|
||||
res=nil
|
||||
for k,v in ipairs(nds) do
|
||||
if in_wall(v) then
|
||||
local dst=vector.distance(lp,v)
|
||||
if odst > dst then odst=dst res=v end
|
||||
end
|
||||
end
|
||||
if res then find_closest_safe(res)
|
||||
else return false end
|
||||
end
|
||||
|
||||
local function random_iwall()
|
||||
math.randomseed(os.clock())
|
||||
local x=math.random(0,90)
|
||||
local y=math.random(10,70)
|
||||
local z=math.random(0,90)
|
||||
local rpos={x=-1254 - x,y=y,z=791 + z}
|
||||
end
|
||||
|
||||
|
||||
local wallbot_state=0
|
||||
local wallbot_target=nil
|
||||
ws.rg('WallBot','Bots','scaffold_wallbot',function()
|
||||
local nds=nil
|
||||
if not wallbot_target then wallbot_state=0 end
|
||||
if wallbot_state == 0 then --searching
|
||||
wallbot_target=wallbot_find(79)
|
||||
if wallbot_target then
|
||||
wallbot_state=2
|
||||
else
|
||||
wallbot_target=random_iwall()
|
||||
wallbot_state=1
|
||||
end
|
||||
elseif wallbot_state == 1 then --flying - searching
|
||||
if incremental_tp.tpactive then return end
|
||||
if vector.distance(ws.dircoord(0,0,0),wallbot_target) < 10 then
|
||||
minetest.after(5,function() wallbot_state=0 end)
|
||||
return
|
||||
end
|
||||
incremental_tp.tp(wallbot_target,1,1)
|
||||
elseif wallbot_state == 2 then --flying - target
|
||||
if incremental_tp.tpactive then return end
|
||||
if vector.distance(ws.dircoord(0,0,0),wallbot_target) < 10 then
|
||||
wallbot_state=3
|
||||
return
|
||||
end
|
||||
incremental_tp.tp(wallbot_target,1,1)
|
||||
elseif wallbot_state == 3 then --filling
|
||||
if not wallbot_find(10) then
|
||||
wallbot_state=0
|
||||
return
|
||||
end
|
||||
else
|
||||
wallbot_state=0
|
||||
end
|
||||
|
||||
end,function() wallbot_state=0 end,function() end,{'scaffold_walltool'})
|
12
clientmods/speedlimit/init.lua
Normal file
12
clientmods/speedlimit/init.lua
Normal file
@ -0,0 +1,12 @@
|
||||
local wason=false;
|
||||
minetest.register_globalstep(function()
|
||||
if minetest.localplayer and minetest.settings:get_bool("movement_ignore_server_speed") then
|
||||
minetest.localplayer:set_speeds_from_local_settings()
|
||||
wason=true
|
||||
elseif wason then
|
||||
wason=false
|
||||
minetest.localplayer:set_speeds_from_server_settings()
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_cheat("IgnSrvSpd", "Movement", "movement_ignore_server_speed")
|
177
clientmods/supernotes/init.lua
Normal file
177
clientmods/supernotes/init.lua
Normal file
@ -0,0 +1,177 @@
|
||||
-- CC0/Unlicense system32 2020
|
||||
|
||||
-- WIKI FORMATTING
|
||||
-- [[link]] -> link to 'link'
|
||||
-- [[link|Wow]] -> link to 'link' with the display value of 'Wow'
|
||||
-- {{coords|X Y Z}} -> coordinates for X Y Z that adds an autofly waypoint at that coordinate when clicked
|
||||
|
||||
--[[
|
||||
TODO/wishlist:
|
||||
|
||||
<nowiki></nowiki> maybe extract text between these and put them somewhere, replacing them with a temp marker which is replaced with the original text once formatting is done
|
||||
|
||||
headers
|
||||
|
||||
maybe make the page list a wiki page?
|
||||
|
||||
page history
|
||||
--]]
|
||||
|
||||
local storage = minetest.get_mod_storage()
|
||||
|
||||
local start_page = "Welcome to supernotes! Click Edit/Save to edit or save a page, Pages to list all pages, and type in that Page bar to go to a page."
|
||||
|
||||
local sidebar_page = "Sidebar"
|
||||
|
||||
local example_json = '{"sidebar": "' .. sidebar_page .. '", "start": "' .. start_page .. '"}'
|
||||
|
||||
local pages = minetest.parse_json(storage:get("supernotes_pages") or example_json)
|
||||
|
||||
local function save()
|
||||
storage:set_string("supernotes_pages", minetest.write_json(pages))
|
||||
end
|
||||
|
||||
|
||||
local formspec_base = [[
|
||||
size[10,9]
|
||||
|
||||
field[0.3,0.5;6.5,1;page;Page/search;NOTE_TITLE]
|
||||
button[6.6,0.2;1.5,1;go;Go]
|
||||
button[8.2,0.2;1.5,1;pages;Pages]
|
||||
|
||||
hypertext[0.3,1.7;2,7;sidebar;SIDEBAR_TEXT]
|
||||
ARTICLE_AREA
|
||||
|
||||
button_exit[0,8.2;2,1;quit;Quit]
|
||||
button[7.9,8.2;2,1;edit;Edit/Save]
|
||||
|
||||
field_close_on_enter[page;false]
|
||||
]]
|
||||
|
||||
local formspec_article = formspec_base:gsub("ARTICLE_AREA", "hypertext[2.5,1.7;7,7;article;ARTICLE_TEXT]")
|
||||
|
||||
local formspec_edit = formspec_base:gsub("ARTICLE_AREA", "textarea[2.5,1.7;7,7;article;;ARTICLE_TEXT]")
|
||||
|
||||
local formspec_pagelist = formspec_base:gsub("ARTICLE_AREA", "textlist[2.5,1.7;7,6.3;article;ARTICLES]")
|
||||
|
||||
local function format_coords(text)
|
||||
local num = "([-]?%d+.?%d*)"
|
||||
local space = "%s+"
|
||||
text = text:gsub("{{coords|" .. num .. space .. num .. space .. num .. "}}", "<action name=coords_%1_%2_%3>%1 %2 %3</action>")
|
||||
return text
|
||||
end
|
||||
|
||||
local function format_wikilinks(text)
|
||||
local tmatch = "[^%[%|%]]"
|
||||
text = text:gsub("%[%[(" .. tmatch .. "-)|(" .. tmatch .. "-)%]%]", "<action name=link_%1>%2</action>")
|
||||
text = text:gsub("%[%[(" .. tmatch .. "-)%]%]", "<action name=link_%1>%1</action>")
|
||||
return text
|
||||
end
|
||||
|
||||
local function wikiformat(text)
|
||||
text = format_coords(text)
|
||||
text = format_wikilinks(text)
|
||||
return text
|
||||
end
|
||||
|
||||
|
||||
local function getkeys(t)
|
||||
local out = {}
|
||||
for k, v in pairs(t) do
|
||||
out[#out + 1] = k
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function startswith(s1, s2)
|
||||
return string.sub(s1, 1, string.len(s2)) == s2
|
||||
end
|
||||
|
||||
local editing = false
|
||||
local current_page = nil
|
||||
|
||||
local function show_page(page, mode)
|
||||
page = page or ""
|
||||
current_page = page
|
||||
|
||||
local fs = ""
|
||||
if mode == nil then
|
||||
fs = formspec_article
|
||||
fs = fs:gsub("ARTICLE_TEXT", wikiformat(pages[page] or "Page empty. Click Edit to create."))
|
||||
elseif mode == "list" then
|
||||
fs = formspec_pagelist
|
||||
fs = fs:gsub("ARTICLES", table.concat(getkeys(pages), ","))
|
||||
elseif mode == "edit" then
|
||||
editing = true
|
||||
fs = formspec_edit
|
||||
fs = fs:gsub("ARTICLE_TEXT", minetest.formspec_escape(pages[page] or ""))
|
||||
end
|
||||
|
||||
fs = fs:gsub("NOTE_TITLE", page)
|
||||
fs = fs:gsub("SIDEBAR_TEXT", wikiformat(pages.sidebar or ""))
|
||||
|
||||
minetest.show_formspec("supernotes", fs)
|
||||
end
|
||||
|
||||
local function linkfield(field)
|
||||
if startswith(field or "", "action:") then
|
||||
local action = field:match("action:(.*)")
|
||||
local wikilink = action:match("link_(.*)")
|
||||
local x, y, z = action:match("coords_([^_]+)_([^_]+)_([^_]+)")
|
||||
|
||||
if wikilink then
|
||||
show_page(wikilink)
|
||||
return true
|
||||
elseif x and y and z then
|
||||
if autofly then
|
||||
-- maybe use autofly.set_hud_wp instead?
|
||||
autofly.set_waypoint(x .. "," .. y .. "," .. z, "Supernotes: " .. x .. "," .. y .. "," .. z)
|
||||
autofly.display_formspec()
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
minetest.register_on_formspec_input(function(formspec, fields)
|
||||
if formspec == "supernotes" then
|
||||
-- go to page
|
||||
if fields.page ~= "" and (fields.go or fields.page ~= current_page) then
|
||||
show_page(fields.page)
|
||||
-- enter edit mode
|
||||
elseif fields.edit and not editing then
|
||||
show_page(fields.page, "edit")
|
||||
-- exit edit mode
|
||||
elseif fields.edit and editing then
|
||||
editing = false
|
||||
pages[fields.page] = fields.article
|
||||
save()
|
||||
show_page(fields.page)
|
||||
-- list pages
|
||||
elseif fields.pages then
|
||||
show_page(fields.page, "list")
|
||||
-- make sure to exit editing mode if exiting
|
||||
elseif fields.quit then
|
||||
editing = false
|
||||
-- process links for article and sidebar
|
||||
elseif linkfield(fields.article) then
|
||||
elseif linkfield(fields.sidebar) then
|
||||
-- list link clicking
|
||||
elseif startswith(fields.article, "CHG") then
|
||||
local target = fields.article:match("CHG:(.*)")
|
||||
show_page(getkeys(pages)[tonumber(target)])
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_on_shutdown(save)
|
||||
|
||||
minetest.register_chatcommand("notes", {
|
||||
params = "<page/empty>",
|
||||
description = "Open Supernotes at the specified page or last opened page.",
|
||||
func = function(params)
|
||||
local page = string.split(params, " ")[1]
|
||||
show_page(page or current_page or "start")
|
||||
end
|
||||
})
|
2
clientmods/supernotes/mod.conf
Normal file
2
clientmods/supernotes/mod.conf
Normal file
@ -0,0 +1,2 @@
|
||||
name = supernotes
|
||||
description = Adds a wiki-like local notetaking system. Integrates with cora's autofly.
|
643
clientmods/tchat/init.lua
Normal file
643
clientmods/tchat/init.lua
Normal file
@ -0,0 +1,643 @@
|
||||
---
|
||||
-- coras teamchat .. indev v0.5
|
||||
--
|
||||
-- adds a team chat for you and a couple friends, also prevents accidental sending of coordinates
|
||||
-- to say something in teamchat either activate teammode in the dragonfire menu or use .t message
|
||||
--
|
||||
-- supports the Wisp encrypted whisper mod
|
||||
--
|
||||
-- .t to say something in team chat (or regular chat if team mode is on)
|
||||
-- .tadd to add a team member
|
||||
-- .tdel to remove
|
||||
-- .tlist to list team
|
||||
--
|
||||
-- .coords to send a message containing coordinates
|
||||
-- .mcoord to send a player your current coordinates
|
||||
|
||||
|
||||
--[[
|
||||
Public methods
|
||||
|
||||
tchat.contains_coords(message) - returns true if the message contains coordinates (2d or 3d)
|
||||
|
||||
tchat.send(message) - send a message to teamchat, returns true if sent, nil if not
|
||||
tchat.send_conditional(message, inverse?) - send a message to teamchat or regular chat, returns true if sent to teamchat, false if main chat, nil if not sent
|
||||
tchat.send_coords(message) - send a message containing coordinates, true if sent, nil if not
|
||||
|
||||
tchat.whisper_coords(player) - DM current coords to a player
|
||||
|
||||
tchat.chat_clear() - clear chat widget
|
||||
tchat.chat_set([]) - set chat widget
|
||||
tchat.chat_append([] or message) - append to chat widget
|
||||
|
||||
tchat.team_add_player(player) - add player to team list
|
||||
tchat.team_remove_player(player) - remove player from team list
|
||||
tchat.team_clear() - clear team list
|
||||
tchat.team_set([]) - set team list
|
||||
|
||||
|
||||
Public properties
|
||||
|
||||
tchat.chat: last few chat messages
|
||||
tchat.team: team list
|
||||
tchat.team_online: online team list
|
||||
tchat.players: currently online players
|
||||
|
||||
|
||||
Settings
|
||||
|
||||
bool tchat_view_chat - if the team chat is shown
|
||||
bool tchat_view_team_list - if the team list is shown
|
||||
bool tchat_view_player_list - if the player list is shown
|
||||
bool tchat_team_mode - if team mode is on
|
||||
|
||||
bool tchat_colorize_team - if true, team list will show all team members colored for who is online
|
||||
bool tchat_use_wisp - if true, encrypt all messages using Wisp
|
||||
|
||||
str tchat_prefix_message - prefix for teamchat messages
|
||||
str tchat_prefix_receive - prefix for received messages
|
||||
str tchat_prefix_self - prefix for self sent messages
|
||||
str tchat_prefix_send - prefix for sent messages
|
||||
|
||||
str tchat_blacklist - comma separated list of accounts that cannot send team chat messages (useful for secret alts)
|
||||
|
||||
num tchat_chat_length - chat length (messages, not lines)
|
||||
num tchat_chat_width - chat width (columns)
|
||||
--]]
|
||||
|
||||
|
||||
---
|
||||
-- settings
|
||||
|
||||
local function init_settings(setting_table)
|
||||
for k, v in pairs(setting_table) do
|
||||
if minetest.settings:get(k) == nil then
|
||||
if type(v) == "boolean" then
|
||||
minetest.settings:set_bool(k, v)
|
||||
else
|
||||
minetest.settings:set(k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
init_settings({
|
||||
tchat_view_chat = false,
|
||||
tchat_view_team_list = true,
|
||||
tchat_view_player_list = true,
|
||||
tchat_team_mode = false,
|
||||
|
||||
tchat_colorize_team = false,
|
||||
|
||||
tchat_prefix_message = "TCHAT",
|
||||
tchat_prefix_receive = "From",
|
||||
tchat_prefix_self = "To Yourself",
|
||||
tchat_prefix_send = "To",
|
||||
|
||||
tchat_use_wisp = false,
|
||||
|
||||
tchat_hide_sent = true,
|
||||
tchat_blacklist = "",
|
||||
|
||||
tchat_chat_length = 6,
|
||||
tchat_chat_width = 80
|
||||
})
|
||||
|
||||
|
||||
---
|
||||
-- globals
|
||||
|
||||
tchat = {}
|
||||
|
||||
tchat.team = {}
|
||||
tchat.team_online = {}
|
||||
tchat.chat = {}
|
||||
tchat.players = {}
|
||||
|
||||
-- used for logs
|
||||
local server_info = minetest.get_server_info()
|
||||
local server_id = server_info.address .. ':' .. server_info.port
|
||||
|
||||
local max_total_chat_length = 1024
|
||||
|
||||
local player_list_epoch = 0
|
||||
|
||||
local message_prefix = minetest.settings:get("tchat_prefix_message")
|
||||
local message_receive = minetest.settings:get("tchat_prefix_receive")
|
||||
local message_receive_self = minetest.settings:get("tchat_prefix_self")
|
||||
local message_to = minetest.settings:get("tchat_prefix_send")
|
||||
|
||||
local team_mode = minetest.settings:get_bool("tchat_team_mode")
|
||||
|
||||
local use_wisp = minetest.settings:get_bool("tchat_use_wisp")
|
||||
|
||||
local hide_sent = minetest.settings:get_bool("tchat_hide_sent")
|
||||
local blacklist = string.split(minetest.settings:get("tchat_blacklist"))
|
||||
|
||||
local chat_length = tonumber(minetest.settings:get("tchat_chat_length"))
|
||||
local chat_width = tonumber(minetest.settings:get("tchat_chat_width"))
|
||||
|
||||
local storage = minetest.get_mod_storage()
|
||||
|
||||
if storage:get("tchat_team") == nil or storage:get("tchat_team") == "null" then
|
||||
storage:set_string("tchat_team", "[]")
|
||||
end
|
||||
|
||||
tchat.team = minetest.parse_json(storage:get_string("tchat_team"))
|
||||
|
||||
-- overrides contains_coords() the next time it runs
|
||||
local message_confirmed_safe = false
|
||||
|
||||
-- coordinate matching
|
||||
local pattern = "[-]?%d[.%d]*"
|
||||
local space = "[,%s]+"
|
||||
local pattern_three = pattern .. space .. pattern .. space .. pattern
|
||||
local pattern_two = pattern .. space .. pattern
|
||||
|
||||
local chat_idx
|
||||
local player_list_idx
|
||||
local team_list_idx
|
||||
local chat_str = ""
|
||||
|
||||
|
||||
---
|
||||
-- private stuff
|
||||
|
||||
local function apply(list, func, filter)
|
||||
local out = {}
|
||||
for k, v in ipairs(list) do
|
||||
if filter(v) then
|
||||
out[#out + 1] = func(v)
|
||||
else
|
||||
out[#out + 1] = v
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function uniq(list)
|
||||
local last
|
||||
local out = {}
|
||||
for k, v in ipairs(list) do
|
||||
if last ~= v then
|
||||
out[#out + 1] = v
|
||||
end
|
||||
last = v
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
-- limit a list to the last size elements
|
||||
local function limit_list(list, size)
|
||||
local out = {}
|
||||
for i = math.max(1, #list - size), #list do
|
||||
out[#out + 1] = list[i]
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function in_list(list, value)
|
||||
for k, v in ipairs(list) do
|
||||
if v == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
local function get_team_str()
|
||||
if minetest.settings:get_bool("tchat_colorize_team") then
|
||||
return table.concat(apply(tchat.team,
|
||||
function(value)
|
||||
return minetest.colorize("#00FFFF", value)
|
||||
end,
|
||||
function(value)
|
||||
return in_list(tchat.team_online, value)
|
||||
end), "\n")
|
||||
else
|
||||
return table.concat(tchat.team_online, "\n")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function display_chat()
|
||||
return minetest.localplayer:hud_add({
|
||||
hud_elem_type = 'text',
|
||||
name = "Teamchat",
|
||||
text = "Team Chat\n\n" .. chat_str,
|
||||
number = 0xEEFFEE,
|
||||
direction = 0,
|
||||
position = {x=0.01, y=0.45},
|
||||
scale = {x=0.9, y=0.9},
|
||||
alignment = {x=1, y=1},
|
||||
offset = {x=0, y=0}
|
||||
})
|
||||
end
|
||||
|
||||
local function display_player_list()
|
||||
return minetest.localplayer:hud_add({
|
||||
hud_elem_type = 'text',
|
||||
name = "Online Players",
|
||||
text = "Players\n\n" .. table.concat(tchat.players, "\n"),
|
||||
number = 0xDDFFDD,
|
||||
direction = 0,
|
||||
position = {x=0.9, y=0.01},
|
||||
alignment = {x=1, y=1},
|
||||
offset = {x=0, y=0}
|
||||
})
|
||||
end
|
||||
|
||||
-- should prob have all team members with online ones colored
|
||||
local function display_team_list()
|
||||
return minetest.localplayer:hud_add({
|
||||
hud_elem_type = 'text',
|
||||
name = "Team",
|
||||
text = "Team\n\n" .. get_team_str(),
|
||||
number = 0x00FF00,
|
||||
direction = 0,
|
||||
position = {x=0.8, y=0.01},
|
||||
alignment = {x=1, y=1},
|
||||
offset = {x=0, y=0}
|
||||
})
|
||||
end
|
||||
|
||||
local function auto_display(idx, setting, func)
|
||||
if minetest.settings:get_bool(setting) then
|
||||
if not idx then
|
||||
return func()
|
||||
end
|
||||
else
|
||||
if idx then
|
||||
minetest.localplayer:hud_remove(idx)
|
||||
return nil
|
||||
end
|
||||
end
|
||||
return idx
|
||||
end
|
||||
|
||||
local function auto_update(idx, text)
|
||||
if idx ~= nil then
|
||||
minetest.localplayer:hud_change(idx, "text", text)
|
||||
end
|
||||
end
|
||||
|
||||
local function update_team_online()
|
||||
tchat.team_online = {}
|
||||
for k, v in ipairs(tchat.players) do
|
||||
if in_list(tchat.team, v) then
|
||||
tchat.team_online[#tchat.team_online + 1] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function update_chat_str()
|
||||
chat_str = ""
|
||||
for k, v in ipairs(limit_list(tchat.chat, chat_length - 1)) do
|
||||
chat_str = chat_str .. "\n" .. minetest.wrap_text(v, chat_width)
|
||||
end
|
||||
chat_str = table.concat(limit_list(string.split(chat_str, "\n"), chat_length - 1), "\n")
|
||||
|
||||
-- update chat (do it here so external mods can add to the chat)
|
||||
auto_update(chat_idx, "Team Chat\n\n" .. chat_str)
|
||||
end
|
||||
|
||||
local function team_add_self()
|
||||
tchat.team_add_player(minetest.localplayer:get_name())
|
||||
end
|
||||
|
||||
|
||||
---
|
||||
-- public interface
|
||||
|
||||
|
||||
function tchat.contains_coords(message)
|
||||
if (not message_confirmed_safe and (message:find(pattern_three) or message:find(pattern_two))) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
local function dm(player, message)
|
||||
if wisp == nil or not use_wisp then
|
||||
minetest.send_chat_message("/msg " .. player .." " .. message)
|
||||
else
|
||||
wisp.send(player, message, true)
|
||||
end
|
||||
end
|
||||
|
||||
-- send
|
||||
function tchat.send(message, force_coords, force_commands)
|
||||
if (tchat.contains_coords(message) and not force_coords) or in_list(blacklist, minetest.localplayer:get_name()) then
|
||||
return
|
||||
end
|
||||
|
||||
if message:sub(1,1) == "/" and not force_commands then
|
||||
minetest.display_chat_message("A /command was scheduled to be sent to team chat but wasn't sent.")
|
||||
return
|
||||
end
|
||||
|
||||
local me = minetest.localplayer:get_name()
|
||||
|
||||
if not in_list(tchat.team, minetest.localplayer:get_name()) then
|
||||
team_add_self()
|
||||
end
|
||||
|
||||
update_team_online()
|
||||
|
||||
local prepend = ""
|
||||
if use_wisp then
|
||||
prepend = "E "
|
||||
end
|
||||
|
||||
tchat.chat_append(prepend .. me .. ": " .. message)
|
||||
|
||||
for k, p in ipairs(tchat.team_online) do
|
||||
if p ~= me then
|
||||
dm(p, message_prefix .. " " .. message)
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function tchat.send_conditional(message, inverse, force_coords)
|
||||
if tchat.contains_coords(message) and not force_coords then
|
||||
return
|
||||
end
|
||||
|
||||
team_mode = minetest.settings:get_bool("tchat_team_mode")
|
||||
|
||||
local tm = team_mode
|
||||
if inverse then
|
||||
tm = not team_mode
|
||||
end
|
||||
|
||||
if tm then
|
||||
tchat.send(message)
|
||||
return true
|
||||
else
|
||||
minetest.send_chat_message(message)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function tchat.send_coords(message)
|
||||
message_confirmed_safe = true
|
||||
local ret = tchat.send_conditional(message)
|
||||
message_confirmed_safe = false
|
||||
return ret
|
||||
end
|
||||
|
||||
|
||||
function tchat.whisper_coords(player)
|
||||
if player == "" then
|
||||
return
|
||||
end
|
||||
local coords = minetest.pos_to_string(vector.round(minetest.localplayer:get_pos()))
|
||||
minetest.run_server_chatcommand("w", param .. " " .. coords)
|
||||
end
|
||||
|
||||
|
||||
-- chat
|
||||
local function autoclear_chat()
|
||||
if #tchat.chat > max_total_chat_length then
|
||||
tchat = limit_list(tchat.chat, max_chat_total_length)
|
||||
end
|
||||
end
|
||||
|
||||
function tchat.chat_clear()
|
||||
tchat.chat = {}
|
||||
update_chat_str()
|
||||
end
|
||||
|
||||
function tchat.chat_set(message_list)
|
||||
chat = message_list
|
||||
autoclear_chat()
|
||||
update_chat_str()
|
||||
end
|
||||
|
||||
function tchat.chat_append(message)
|
||||
tchat.chat[#tchat.chat + 1] = message
|
||||
autoclear_chat()
|
||||
|
||||
minetest.log("action", "[tchat] " .. minetest.localplayer:get_name() .. "@" .. server_id .. " " .. message)
|
||||
|
||||
update_chat_str()
|
||||
|
||||
-- popup chat if its closed
|
||||
minetest.settings:set_bool("tchat_view_chat", true)
|
||||
chat_idx = auto_display(chat_idx, "tchat_view_chat", display_chat)
|
||||
end
|
||||
|
||||
|
||||
local function team_save()
|
||||
storage:set_string("tchat_team" , minetest.write_json(tchat.team))
|
||||
end
|
||||
|
||||
-- team
|
||||
function tchat.team_add_player(player)
|
||||
if not in_list(tchat.team, player) then
|
||||
tchat.team[#tchat.team + 1] = player
|
||||
update_team_online()
|
||||
team_save()
|
||||
end
|
||||
end
|
||||
|
||||
function tchat.team_remove_player(player)
|
||||
local out = {}
|
||||
for k, v in ipairs(tchat.team) do
|
||||
if v ~= player then
|
||||
out[#out + 1] = v
|
||||
end
|
||||
end
|
||||
tchat.team = out
|
||||
team_save()
|
||||
end
|
||||
|
||||
function tchat.team_clear()
|
||||
tchat.team = {}
|
||||
team_save()
|
||||
end
|
||||
|
||||
function tchat.team_set(player_list)
|
||||
tchat.team = player_list
|
||||
team_save()
|
||||
end
|
||||
|
||||
|
||||
---
|
||||
-- callbacks
|
||||
|
||||
minetest.register_on_sending_chat_message(function(message)
|
||||
if tchat.contains_coords(message) then
|
||||
minetest.display_chat_message("Message contained coordinates, be careful.")
|
||||
return true
|
||||
end
|
||||
|
||||
team_mode = minetest.settings:get_bool("tchat_team_mode")
|
||||
|
||||
if not team_mode then
|
||||
return
|
||||
end
|
||||
|
||||
tchat.send(message)
|
||||
return true
|
||||
end)
|
||||
|
||||
|
||||
local function message_sent(message)
|
||||
return message == "Message sent."
|
||||
end
|
||||
|
||||
local function clean_message(message)
|
||||
-- dirty, strips out legitimate uses of the prefix
|
||||
message = message:gsub(message_prefix, "")
|
||||
message = message:gsub("^" .. message_receive, "")
|
||||
message = message:gsub("^" .. message_receive_self, minetest.localplayer:get_name())
|
||||
|
||||
message = message:gsub(": ", ": ")
|
||||
message = message:match("^%s*(.-)%s*$")
|
||||
|
||||
return message
|
||||
end
|
||||
|
||||
-- greedily be the first in the receiving list (prob doesnt always work)
|
||||
table.insert(minetest.registered_on_receiving_chat_message, 1, function(message)
|
||||
if hide_sent and message_sent(message) then
|
||||
return true
|
||||
end
|
||||
|
||||
-- bit dirty, doesnt check the prefix position
|
||||
if not message:find(message_prefix) then
|
||||
return
|
||||
end
|
||||
|
||||
local player = message:match(message_receive .. " (.+): " .. message_prefix)
|
||||
|
||||
local from_self = message:sub(1, message_receive_self:len()) == message_receive_self
|
||||
local received = message:sub(1, message_receive:len()) == message_receive
|
||||
local sent = message:sub(1, message_to:len()) == message_to
|
||||
|
||||
if sent and not from_self then
|
||||
return true
|
||||
end
|
||||
|
||||
if not from_self and not in_list(tchat.team_online, player) then
|
||||
return
|
||||
end
|
||||
|
||||
-- add to chat list
|
||||
if from_self or received then
|
||||
tchat.chat_append(clean_message(message))
|
||||
return true
|
||||
end
|
||||
end)
|
||||
|
||||
if wisp ~= nil then
|
||||
wisp.register_on_receive_split(function(player, message)
|
||||
if message:find(message_prefix) then
|
||||
tchat.chat_append("E " .. player .. ": " .. clean_message(message))
|
||||
return true
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
minetest.register_globalstep(function()
|
||||
-- update data
|
||||
if player_list_epoch < os.time() + 2 then
|
||||
-- update players, remove duplicates
|
||||
tchat.players = minetest.get_player_names()
|
||||
table.sort(tchat.players)
|
||||
tchat.players = uniq(tchat.players)
|
||||
|
||||
update_team_online()
|
||||
|
||||
-- update HUD
|
||||
auto_update(player_list_idx, "Players\n\n" .. table.concat(tchat.players, "\n"))
|
||||
auto_update(team_list_idx, "Team\n\n" .. get_team_str())
|
||||
|
||||
player_list_epoch = os.time()
|
||||
end
|
||||
|
||||
-- display (if we need to)
|
||||
if minetest.localplayer then
|
||||
chat_idx = auto_display(chat_idx, "tchat_view_chat", display_chat)
|
||||
player_list_idx = auto_display(player_list_idx, "tchat_view_player_list", display_player_list)
|
||||
team_list_idx = auto_display(team_list_idx, "tchat_view_team_list", display_team_list)
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
---
|
||||
-- command/cheat interface
|
||||
|
||||
minetest.register_chatcommand("t", {
|
||||
params = "<message>",
|
||||
description = "Send a message to your team chat, or regular chat if team mode is on.",
|
||||
func = function(message)
|
||||
if tchat.contains_coords(message) then
|
||||
minetest.display_chat_message("Message contained coordinates, be careful.")
|
||||
return
|
||||
end
|
||||
tchat.send_conditional(message, true)
|
||||
end
|
||||
})
|
||||
minetest.register_chatcommand("tcoords", {
|
||||
params = "<message>",
|
||||
description = "Send a message containing coordinates to teamchat.",
|
||||
func = function(message)
|
||||
tchat.send(message, true)
|
||||
end
|
||||
})
|
||||
minetest.register_chatcommand("tlist", {
|
||||
description = "List your team.",
|
||||
func = function(param)
|
||||
minetest.display_chat_message(table.concat(tchat.team, ", "))
|
||||
end
|
||||
})
|
||||
minetest.register_chatcommand("tadd", {
|
||||
params = "<player>",
|
||||
description = "Add player to your team.",
|
||||
func = tchat.team_add_player
|
||||
})
|
||||
minetest.register_chatcommand("tdel", {
|
||||
params = "<player>",
|
||||
description = "Remove player from your team.",
|
||||
func = tchat.team_remove_player
|
||||
})
|
||||
minetest.register_chatcommand("tclear", {
|
||||
description = "Clear team list.",
|
||||
func = tchat.team_clear
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("tchat_clear", {
|
||||
description = "Clear team chat widget.",
|
||||
func = tchat.chat_clear
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("coords", {
|
||||
params = "<message>",
|
||||
description = "Send message containing coordinates.",
|
||||
func = tchat.send_coords
|
||||
})
|
||||
minetest.register_chatcommand("mcoord", {
|
||||
params = "<player>",
|
||||
description = "Whisper current coordinates to player.",
|
||||
func = tchat.whisper_coords
|
||||
})
|
||||
|
||||
|
||||
-- this fallbacks to showing everything if the cheat menu is unavailable
|
||||
-- use advanced settings instead :]
|
||||
if (_G["minetest"]["register_cheat"] == nil) then
|
||||
minetest.settings:set_bool('tchat_team_mode', true)
|
||||
minetest.settings:set_bool('tchat_view_team_list', true)
|
||||
minetest.settings:set_bool('tchat_view_player_list', true)
|
||||
minetest.settings:set_bool('tchat_view_chat', true)
|
||||
else
|
||||
minetest.register_cheat("Teamchat Mode", "Chat", "tchat_team_mode")
|
||||
minetest.register_cheat("Show Team List", "Chat", "tchat_view_team_list")
|
||||
minetest.register_cheat("Show Player List", "Chat", "tchat_view_player_list")
|
||||
minetest.register_cheat("Show Teamchat", "Chat", "tchat_view_chat")
|
||||
end
|
3
clientmods/tchat/mod.conf
Normal file
3
clientmods/tchat/mod.conf
Normal file
@ -0,0 +1,3 @@
|
||||
name = tchat
|
||||
author = cora, system32
|
||||
description = Adds a team chat and prevents accidental sending of coordinates. Supports Wisp for encrypting messages.
|
18
clientmods/tchat/settingtypes.txt
Normal file
18
clientmods/tchat/settingtypes.txt
Normal file
@ -0,0 +1,18 @@
|
||||
tchat_view_chat (Show team chat) bool false
|
||||
tchat_view_team_list (Show team list) bool true
|
||||
tchat_view_player_list (Show player list) bool true
|
||||
tchat_team_mode (Team mode) bool false
|
||||
|
||||
tchat_colorize_team (Show colorized team list) bool false
|
||||
tchat_use_wisp (Use Wisp to encrypt outgoing messages) bool false
|
||||
|
||||
tchat_prefix_message (Message Prefix) string TCHAT
|
||||
tchat_prefix_receive (Received PM starting string) string From
|
||||
tchat_prefix_self (Received PM starting string to yourself) string To Yourself
|
||||
tchat_prefix_send (Outgoing PM starting string) string To
|
||||
|
||||
tchat_blacklist (Names that can't use team chat) string
|
||||
tchat_hide_sent (Hide "Message sent." server messages) bool true
|
||||
|
||||
tchat_chat_length (Maximum team chat messages) int 6
|
||||
tchat_chat_width (Team chat width in columns) int 80
|
4
clientmods/test/init.lua
Normal file
4
clientmods/test/init.lua
Normal file
@ -0,0 +1,4 @@
|
||||
if minetest.settings:get_bool("test_chain") then
|
||||
local prefix = minetest.get_modpath(minetest.get_current_modname())
|
||||
dofile(prefix .. "/chain.lua")
|
||||
end
|
1
clientmods/test/settingtypes.txt
Normal file
1
clientmods/test/settingtypes.txt
Normal file
@ -0,0 +1 @@
|
||||
test_chain (Load the chain file) bool false
|
518
clientmods/turtle/init.lua
Normal file
518
clientmods/turtle/init.lua
Normal file
@ -0,0 +1,518 @@
|
||||
-- CC0/Unlicense Emilia 2020
|
||||
|
||||
turtle = {}
|
||||
|
||||
local mod_prefix = minetest.get_modpath(minetest.get_current_modname())
|
||||
tlang = dofile(mod_prefix .. "/tlang.lua")
|
||||
|
||||
function turtle.coord(x, y, z)
|
||||
return {x = x, y = y, z = z}
|
||||
end
|
||||
|
||||
turtle.pos1 = turtle.coord(0, 0, 0)
|
||||
turtle.pos2 = turtle.coord(0, 0, 0)
|
||||
|
||||
local function format_coord(c)
|
||||
return tostring(c.x) .. " " .. tostring(c.y) .. " " .. tostring(c.z)
|
||||
end
|
||||
|
||||
local function parse_coord(c)
|
||||
end
|
||||
|
||||
-- can include ~ + - along with num and ,
|
||||
local function parse_relative_coord(c)
|
||||
end
|
||||
|
||||
function turtle.ordercoord(c)
|
||||
if c.x == nil then
|
||||
return {x = c[1], y = c[2], z = c[3]}
|
||||
else
|
||||
return c
|
||||
end
|
||||
end
|
||||
|
||||
-- x or {x,y,z} or {x=x,y=y,z=z}
|
||||
function turtle.optcoord(x, y, z)
|
||||
if y and z then
|
||||
return turtle.coord(x, y, z)
|
||||
else
|
||||
return turtle.ordercoord(x)
|
||||
end
|
||||
end
|
||||
|
||||
-- swap x and y if x > y
|
||||
local function swapg(x, y)
|
||||
if x > y then
|
||||
return y, x
|
||||
else
|
||||
return x, y
|
||||
end
|
||||
end
|
||||
|
||||
-- swaps coordinates around such that (matching ords of) c1 < c2 and the overall cuboid is the same shape
|
||||
function turtle.rectify(c1, c2)
|
||||
c1.x, c2.x = swapg(c1.x, c2.x)
|
||||
c1.y, c2.y = swapg(c1.y, c2.y)
|
||||
c1.z, c2.z = swapg(c1.z, c2.z)
|
||||
return c1, c2
|
||||
end
|
||||
|
||||
-- converts a coordinate to a system where 0,0 is the southwestern corner of the world
|
||||
function turtle.zeroidx(c)
|
||||
local side = 30912
|
||||
return turtle.coord(c.x + side, c.y + side, c.z + side)
|
||||
end
|
||||
|
||||
-- swaps coords and subtracts such that c1 == {0, 0, 0} and c2 is the distance from c1
|
||||
-- returns rectified c1/c2 and the relativized version
|
||||
function turtle.relativize(c1, c2)
|
||||
c1, c2 = turtle.rectify(c1, c2)
|
||||
|
||||
local c1z = turtle.zeroidx(c1)
|
||||
local c2z = turtle.zeroidx(c2)
|
||||
|
||||
local rel = turtle.coord(c2z.x - c1z.x, c2z.y - c1z.y, c2z.z - c1z.z)
|
||||
|
||||
return c1, rel
|
||||
end
|
||||
|
||||
|
||||
-- get the inventory index of the best tool to mine x, y, z
|
||||
-- returns a wield index, which starts at 0
|
||||
function turtle.get_best_tool_index(x, y, z)
|
||||
local node = minetest.get_node_or_nil(turtle.optcoord(x, y, z))
|
||||
if not node then
|
||||
return
|
||||
end
|
||||
|
||||
local nodecaps = minetest.get_node_def(node.name).groups
|
||||
|
||||
local idx = minetest.localplayer:get_wield_index()
|
||||
local best = math.huge
|
||||
|
||||
for i, v in ipairs(minetest.get_inventory("current_player").main) do
|
||||
for gk, gv in pairs(v:get_tool_capabilities().groupcaps) do
|
||||
local level = nodecaps[gk]
|
||||
if level and gv.times[level] < best then
|
||||
idx = i
|
||||
best = gv.times[level]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return idx
|
||||
end
|
||||
|
||||
-- switch to the fastest tool to mine x, y, z
|
||||
function turtle.switch_best(x, y, z)
|
||||
local prev = minetest.localplayer:get_wield_index()
|
||||
|
||||
local index = turtle.get_best_tool_index(x, y, z)
|
||||
|
||||
if prev ~= index then
|
||||
minetest.localplayer:set_wield_index(index)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function turtle.mine(x, y, z)
|
||||
turtle.switch_best(x, y, z)
|
||||
minetest.dig_node(turtle.optcoord(x, y, z))
|
||||
end
|
||||
|
||||
function turtle.place(x, y, z)
|
||||
minetest.place_node(turtle.optcoord(x, y, z))
|
||||
end
|
||||
|
||||
function turtle.cadd(c1, c2)
|
||||
return turtle.coord(c1.x + c2.x, c1.y + c2.y, c1.z + c2.z)
|
||||
end
|
||||
|
||||
function turtle.relcoord(x, y, z)
|
||||
local pos = minetest.localplayer:get_pos()
|
||||
if pos.y > -5000 then pos.y=pos.y-1 end
|
||||
return turtle.cadd(pos, turtle.optcoord(x, y, z))
|
||||
end
|
||||
|
||||
local function between(x, y, z) -- x is between y and z (inclusive)
|
||||
return y <= x and x <= z
|
||||
end
|
||||
|
||||
function turtle.getdir() --
|
||||
local rot = minetest.localplayer:get_yaw() % 360
|
||||
if between(rot, 315, 360) or between(rot, 0, 45) then
|
||||
return "north"
|
||||
elseif between(rot, 135, 225) then
|
||||
return "south"
|
||||
elseif between(rot, 225, 315) then
|
||||
return "east"
|
||||
elseif between(rot, 45, 135) then
|
||||
return "west"
|
||||
end
|
||||
end
|
||||
function turtle.setdir(dir) --
|
||||
if dir == "north" then
|
||||
minetest.localplayer:set_yaw(0)
|
||||
elseif dir == "south" then
|
||||
minetest.localplayer:set_yaw(180)
|
||||
elseif dir == "east" then
|
||||
minetest.localplayer:set_yaw(270)
|
||||
elseif dir == "west" then
|
||||
minetest.localplayer:set_yaw(90)
|
||||
end
|
||||
end
|
||||
|
||||
function turtle.dircoord(f, y, r)
|
||||
local dir=turtle.getdir()
|
||||
local coord = turtle.optcoord(f, y, r)
|
||||
local f = coord.x
|
||||
local y = coord.y
|
||||
local r = coord.z
|
||||
local lp=minetest.localplayer:get_pos()
|
||||
if dir == "north" then
|
||||
return turtle.relcoord(r, y, f)
|
||||
elseif dir == "south" then
|
||||
return turtle.relcoord(-r, y, -f)
|
||||
elseif dir == "east" then
|
||||
return turtle.relcoord(f, y, -r)
|
||||
elseif dir== "west" then
|
||||
return turtle.relcoord(-f, y, r)
|
||||
end
|
||||
|
||||
return turtle.relcoord(0, 0, 0)
|
||||
end
|
||||
|
||||
function turtle.move(x, y, z)
|
||||
minetest.localplayer:set_pos(turtle.optcoord(x, y, z))
|
||||
end
|
||||
|
||||
function turtle.advance(amount)
|
||||
amount = amount or 1
|
||||
turtle.move(turtle.dircoord(amount, 0, 0))
|
||||
end
|
||||
|
||||
function turtle.descend(amount)
|
||||
amount = amount or 1
|
||||
turtle.move(turtle.relcoord(0, -amount, 0))
|
||||
end
|
||||
|
||||
function turtle.rotate_abs(deg)
|
||||
minetest.localplayer:set_yaw(deg)
|
||||
end
|
||||
|
||||
function turtle.rotate(deg)
|
||||
local prev = minetest.localplayer:get_yaw()
|
||||
minetest.localplayer:set_yaw((prev + deg) % 360)
|
||||
end
|
||||
|
||||
function turtle.rotate_right(deg)
|
||||
deg = deg or 90
|
||||
turtle.rotate(-deg)
|
||||
end
|
||||
|
||||
function turtle.rotate_left(deg)
|
||||
deg = deg or 90
|
||||
turtle.rotate(deg)
|
||||
end
|
||||
|
||||
function turtle.isblock(block, x, y, z)
|
||||
local node = minetest.get_node_or_nil(turtle.optcoord(x, y, z))
|
||||
return node ~= nil and block == node.name
|
||||
end
|
||||
|
||||
function turtle.checkmine(x, y, z)
|
||||
while true do
|
||||
turtle.mine(x, y, z)
|
||||
busysleep(0.125)
|
||||
-- i hate lua
|
||||
minetest.log(tostring(turtle.isblock("air", x, y, z)))
|
||||
if turtle.isblock("air", x, y, z) then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function turtle.tp(coords)
|
||||
minetest.localplayer:set_pos(coords)
|
||||
end
|
||||
|
||||
function turtle.moveto(x, y, z)
|
||||
turtle.tp(turtle.optcoord(x, y, z))
|
||||
end
|
||||
|
||||
function turtle.linemine(distance, func)
|
||||
for i = 1, distance do
|
||||
turtle.checkmine(turtle.dircoord(1, 1, 0))
|
||||
turtle.checkmine(turtle.dircoord(1, 0, 0))
|
||||
turtle.advance()
|
||||
|
||||
if func then
|
||||
func()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function left_or_right(left)
|
||||
if left then
|
||||
turtle.rotate_left()
|
||||
else
|
||||
turtle.rotate_right()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function quarry_clear_liquids()
|
||||
-- puts blocks in front, both sides, and two below where they are liquid
|
||||
-- it does all this one step ahead so no spillage may occur
|
||||
end
|
||||
|
||||
|
||||
-- needs to check for liquids (would need to be done in linemine)
|
||||
function turtle.quarry(cstart, cend)
|
||||
-- get a nice cuboid
|
||||
cstart, cend = turtle.rectify(cstart, cend)
|
||||
local start, relend = turtle.relativize(cstart, cend)
|
||||
|
||||
-- makes it start at the top rather than the bottom
|
||||
cend.y, cstart.y = swapg(cend.y, cstart.y)
|
||||
|
||||
-- go to the start
|
||||
turtle.moveto(turtle.cadd(cstart, turtle.coord(0, 1, 0)))
|
||||
turtle.rotate_abs(0)
|
||||
|
||||
-- main loop (zig zag pattern)
|
||||
for height = 0, math.floor(relend.y / 2) do
|
||||
-- go down two blocks
|
||||
turtle.mine(turtle.relcoord(0, -1, 0))
|
||||
turtle.mine(turtle.relcoord(0, -2, 0))
|
||||
turtle.descend(2)
|
||||
|
||||
for width = 0, relend.x do
|
||||
-- swaps left/right rotations each layer and zig zag
|
||||
local leftiness = ((height + width + 1) % 2) == 0
|
||||
|
||||
-- actually mine
|
||||
turtle.linemine(relend.z) -- maybe relend.z to make the end inclusive?
|
||||
left_or_right(leftiness)
|
||||
-- dont rotate at the end of the layer
|
||||
if width ~= relend.x then
|
||||
turtle.linemine(1)
|
||||
left_or_right(leftiness)
|
||||
end
|
||||
end
|
||||
|
||||
-- flip around to start again on the next layer
|
||||
turtle.rotate(180)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
minetest.register_chatcommand("quarry", {
|
||||
func = function()
|
||||
turtle.quarry({x = -60, y = 1, z = -60}, {x = -40, y = -5, z = -40})
|
||||
end
|
||||
})
|
||||
|
||||
turtle.builtins = {}
|
||||
|
||||
function turtle.builtins.mine(state)
|
||||
|
||||
end
|
||||
|
||||
function turtle.builtins.advance(state)
|
||||
|
||||
end
|
||||
|
||||
function turtle.builtins.descend(state)
|
||||
|
||||
end
|
||||
|
||||
function turtle.builtins.v3add(state)
|
||||
|
||||
end
|
||||
|
||||
function turtle.builtins.rotate(state)
|
||||
|
||||
end
|
||||
|
||||
function turtle.builtins.relativize(state)
|
||||
|
||||
end
|
||||
|
||||
function turtle.builtins.swapg(state)
|
||||
|
||||
end
|
||||
|
||||
function turtle.builtins.rectify(state)
|
||||
|
||||
end
|
||||
|
||||
local quarry_tlang = [[
|
||||
# turtle.builtins: mine advance v3add descend rotate relativize swapg rectify
|
||||
# tlang operators: //
|
||||
|
||||
################################
|
||||
# Mine ahead length nodes (including head and feet)
|
||||
{ 0 `length args
|
||||
####
|
||||
{
|
||||
i length == {break} if
|
||||
|
||||
[1 1 0] dircoord mine
|
||||
[1 0 0] dircoord mine
|
||||
1 wait
|
||||
advance
|
||||
} `i forever
|
||||
} `linemine =
|
||||
|
||||
|
||||
################################
|
||||
# Mine the cuboid defined by start and end
|
||||
{ 0 `start `end args
|
||||
####
|
||||
rectify `start = `end =
|
||||
start end relativize
|
||||
|
||||
start end swapg_y `relstart = `relend =
|
||||
|
||||
start [0 1 0] v3add moveto
|
||||
0 rotate_abs
|
||||
|
||||
relend.y 2 // `yend =
|
||||
|
||||
{
|
||||
height yend > {break} if
|
||||
|
||||
[0 -1 0] dircoord mine
|
||||
[0 -2 0] dircoord mine
|
||||
2 descend
|
||||
|
||||
{
|
||||
width relend.x > {break} if
|
||||
|
||||
height width + 1 + 2 % 0 == `leftiness =
|
||||
|
||||
relend.z linemine
|
||||
leftiness left_or_right
|
||||
|
||||
width relend.x != {
|
||||
1 linemine
|
||||
leftiness left_or_right
|
||||
} if
|
||||
} `width forever
|
||||
|
||||
180 rotate
|
||||
} `height forever
|
||||
} `quarry =
|
||||
]]
|
||||
|
||||
|
||||
turtle.states = {}
|
||||
turtle.states_available = false
|
||||
|
||||
function turtle.schedule(name, state)
|
||||
if type(name) == "table" then
|
||||
error("turtle.schedule: first parameter should be the task's name")
|
||||
return
|
||||
end
|
||||
|
||||
turtle.states[#turtle.states + 1] = {name = name, state = state}
|
||||
turtle.states_available = true
|
||||
end
|
||||
|
||||
function turtle.get_symbolic(name)
|
||||
local dead = {}
|
||||
|
||||
for i, v in ipairs(turtle.states) do
|
||||
if i == name or v.name == name then
|
||||
table.insert(dead, 1, i)
|
||||
end
|
||||
end
|
||||
|
||||
return dead
|
||||
end
|
||||
|
||||
function turtle.kill_symbolic(name)
|
||||
local dead = turtle.get_symbolic(name)
|
||||
|
||||
for i, v in ipairs(dead) do
|
||||
table.remove(turtle.states, v)
|
||||
end
|
||||
end
|
||||
|
||||
function turtle.pause_symbolic(name)
|
||||
local dead = turtle.get_symbolic(name)
|
||||
|
||||
for i, v in ipairs(dead) do
|
||||
turtle.states[v].state.paused = true
|
||||
end
|
||||
end
|
||||
|
||||
function turtle.resume_symbolic(name)
|
||||
local dead = turtle.get_symbolic(name)
|
||||
|
||||
for i, v in ipairs(dead) do
|
||||
turtle.states[v].state.paused = nil
|
||||
end
|
||||
end
|
||||
|
||||
function turtle.run_states(dtime)
|
||||
if turtle.states_available then
|
||||
local dead = {}
|
||||
|
||||
for i, v in ipairs(turtle.states) do
|
||||
local ret = tlang.step(v.state)
|
||||
if ret ~= true and ret ~= nil then
|
||||
if type(ret) == "string" then
|
||||
minetest.display_chat_message("Turtle/tlang ERROR in " .. v.name .. ": " .. ret)
|
||||
end
|
||||
table.insert(dead, 1, i)
|
||||
end
|
||||
end
|
||||
|
||||
for i, v in ipairs(dead) do
|
||||
table.remove(turtle.states, v)
|
||||
end
|
||||
|
||||
turtle.states_available = #turtle.states ~= 0
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_globalstep(turtle.run_states)
|
||||
|
||||
minetest.register_chatcommand("tlang", {
|
||||
description = "Run a tlang program.",
|
||||
params = "<code>",
|
||||
func = function(params)
|
||||
local state = tlang.get_state(params)
|
||||
turtle.schedule("chat_script", state)
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("tl_list", {
|
||||
description = "List running tlang states.",
|
||||
func = function()
|
||||
for i, v in ipairs(turtle.states) do
|
||||
minetest.display_chat_message(tostring(i) .. " " .. v.name)
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("tl_kill", {
|
||||
description = "Kill a tlang state.",
|
||||
params = "<task>",
|
||||
func = turtle.kill_symbolic
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("tl_pause", {
|
||||
description = "Pause a tlang state.",
|
||||
params = "<task>",
|
||||
func = turtle.pause_symbolic
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("tl_resume", {
|
||||
description = "Resume a tlang state.",
|
||||
params = "<task>",
|
||||
func = turtle.resume_symbolic
|
||||
})
|
3
clientmods/turtle/mod.conf
Normal file
3
clientmods/turtle/mod.conf
Normal file
@ -0,0 +1,3 @@
|
||||
name = turtle
|
||||
author = system32
|
||||
description = Adds a system to control the player like a graphics turtle.
|
263
clientmods/turtle/tlang.lua
Normal file
263
clientmods/turtle/tlang.lua
Normal file
@ -0,0 +1,263 @@
|
||||
-- CC0/Unlicense Emilia 2020
|
||||
|
||||
local tlang = {}
|
||||
|
||||
local prefix = ""
|
||||
if minetest ~= nil then
|
||||
prefix = minetest.get_modpath(minetest.get_current_modname()) .. "/"
|
||||
end
|
||||
|
||||
local function merge_tables(l1, l2)
|
||||
local out = {}
|
||||
|
||||
for k, v in pairs(l1) do
|
||||
out[k] = v
|
||||
end
|
||||
|
||||
for k, v in pairs(l2) do
|
||||
out[k] = v
|
||||
end
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
local function load_api_file(file)
|
||||
loadfile(prefix .. file)(tlang)
|
||||
end
|
||||
|
||||
load_api_file("tlang_lex.lua")
|
||||
load_api_file("tlang_parse.lua")
|
||||
load_api_file("tlang_vm.lua")
|
||||
|
||||
|
||||
function tlang.combine_builtins(b1, b2)
|
||||
return merge_tables(b1, b2)
|
||||
end
|
||||
|
||||
function tlang.construct_builtins(builtins)
|
||||
return merge_tables(tlang.builtins, builtins)
|
||||
end
|
||||
|
||||
-- TODO
|
||||
--[[
|
||||
lexer should include line/character number in symbols
|
||||
error messages
|
||||
maps should be able to have out of order number indexes (like [1 2 3 10:"Out of order"])
|
||||
map.key accessing syntax
|
||||
parse as identifier, include . as identifier character, split on . and thats the indexing tree
|
||||
--]]
|
||||
|
||||
function tlang.run(state)
|
||||
while true do
|
||||
local more = tlang.step(state)
|
||||
if more == true or more == nil then
|
||||
-- continue along
|
||||
elseif type(more) == "string" then
|
||||
print(more) -- error
|
||||
elseif more == false then
|
||||
return -- done
|
||||
else
|
||||
print("Unknown error, tlang.step returned: " .. tostring(more))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function tlang.get_state(code)
|
||||
local lexed = tlang.lex(code)
|
||||
local parsed = tlang.parse(lexed)
|
||||
|
||||
return {
|
||||
locals = {{
|
||||
pc = {sg = 1, pos = {"__ast__"}, elem = 1},
|
||||
vars = {
|
||||
__src__ = tlang.value_to_tlang(code),
|
||||
__lex__ = tlang.value_to_tlang(lexed),
|
||||
__ast__ = {type = "code", value = parsed}
|
||||
}
|
||||
}},
|
||||
stack = {},
|
||||
code_stack = {},
|
||||
builtins = tlang.builtins
|
||||
}
|
||||
end
|
||||
|
||||
function tlang.exec(code)
|
||||
local state = tlang.get_state(code)
|
||||
tlang.run(state)
|
||||
end
|
||||
|
||||
function tlang.pretty_pc(pc)
|
||||
return tostring(pc.sg) .. ";" .. table.concat(pc.pos, ".") .. ";" .. tostring(pc.elem)
|
||||
end
|
||||
|
||||
function tlang.format_table(t, depth, maxdepth)
|
||||
depth = depth or 0
|
||||
maxdepth = maxdepth or -1
|
||||
|
||||
if depth == maxdepth then
|
||||
return "{...}"
|
||||
end
|
||||
|
||||
local out = {}
|
||||
out[1] = "{\n"
|
||||
|
||||
for k, v in pairs(t) do
|
||||
local idx = k
|
||||
if type(k) == "string" then
|
||||
idx = '"' .. k .. '"'
|
||||
elseif type(k) == "table" then
|
||||
idx = "{...}"
|
||||
end
|
||||
|
||||
out[#out + 1] = string.rep("\t", depth + 1) .. "[" .. idx .. "] = "
|
||||
|
||||
if type(v) == "table" then
|
||||
out[#out + 1] = tlang.format_table(v, depth + 1, maxdepth)
|
||||
elseif type(v) == "string" then
|
||||
out[#out + 1] = '"' .. v .. '"'
|
||||
else
|
||||
out[#out + 1] = tostring(v)
|
||||
end
|
||||
|
||||
out[#out + 1] = ",\n"
|
||||
end
|
||||
|
||||
out[#out + 1] = string.rep("\t", depth) .. "}"
|
||||
return table.concat(out)
|
||||
end
|
||||
|
||||
function tlang.print_table(t, maxdepth)
|
||||
print(tlang.format_table(t, nil, maxdepth))
|
||||
end
|
||||
|
||||
local function test()
|
||||
local complex = [[{dup *} `square =
|
||||
-5.42 square
|
||||
"Hello, world!" print
|
||||
[1 2 3 str:"String"]
|
||||
]]
|
||||
|
||||
local number = "-4.2123"
|
||||
|
||||
local simple = "{dup *}"
|
||||
|
||||
local map = "[this:2 that:3]"
|
||||
|
||||
local square = [[{dup *} `square =
|
||||
5 square print]]
|
||||
|
||||
local square_run = "5 {dup *} run print"
|
||||
|
||||
local comment_test = "'asd' print # 'aft' print"
|
||||
|
||||
local forever_test = [[
|
||||
5 # iteration count
|
||||
{
|
||||
dup # duplicate iter count
|
||||
print # print countdown
|
||||
-- # decrement
|
||||
dup 0 == # check if TOS is 0
|
||||
{break} if # break if TOS == 0
|
||||
}
|
||||
forever # run loop
|
||||
]]
|
||||
|
||||
local local_test = [[
|
||||
'outside' `var =
|
||||
{
|
||||
var print # should be 'outside'
|
||||
'inside' `var =
|
||||
var print # should be 'inside'
|
||||
} run
|
||||
var print # should be 'inside'
|
||||
]]
|
||||
|
||||
local while_test = [[
|
||||
5 `cur =
|
||||
{
|
||||
`cur --
|
||||
cur
|
||||
} {
|
||||
"four times" print
|
||||
} while
|
||||
]]
|
||||
|
||||
local repeat_test = [[
|
||||
{
|
||||
"four times" print
|
||||
} 4 repeat
|
||||
{
|
||||
i print
|
||||
} 5 `i repeat
|
||||
]]
|
||||
|
||||
local stack_test = "5 5 == print"
|
||||
|
||||
local args_test = [[
|
||||
{ 0 `first `second args
|
||||
first print
|
||||
second print
|
||||
} `test =
|
||||
1 2 test
|
||||
]]
|
||||
|
||||
local ifelse_test = [[
|
||||
{
|
||||
{
|
||||
'if' print
|
||||
} {
|
||||
'else' print
|
||||
} if
|
||||
} `ifprint =
|
||||
|
||||
1 ifprint
|
||||
0 ifprint
|
||||
]]
|
||||
|
||||
local nest_run = [[
|
||||
{
|
||||
{
|
||||
'innermost' print
|
||||
} run
|
||||
} run
|
||||
'work' print
|
||||
]]
|
||||
|
||||
local mapid_test = "this.that.2.here .81..wao.88912"
|
||||
|
||||
local paren_test = "('works' print) 'out' print"
|
||||
|
||||
local mapdot_test = [[
|
||||
[1 a:5 b:[a:2 b:3] 3] `a =
|
||||
4 `a.a =
|
||||
a.1 print
|
||||
a.2 print
|
||||
a.a print
|
||||
a.b.b print
|
||||
]]
|
||||
|
||||
local stackdot_test = [[
|
||||
[a:1 b:2]
|
||||
.b print
|
||||
6 `.a =
|
||||
.a print
|
||||
]]
|
||||
|
||||
local funcfunc_test = [[
|
||||
{dup *} `square =
|
||||
{dup square *} `cube =
|
||||
5 cube print
|
||||
]]
|
||||
|
||||
local test = funcfunc_test
|
||||
|
||||
--tlang.print_table(tlang.lex(test))
|
||||
--tlang.print_table(tlang.parse(tlang.lex(test)))
|
||||
tlang.exec(test)
|
||||
end
|
||||
|
||||
if minetest == nil then
|
||||
test()
|
||||
end
|
||||
|
||||
return tlang
|
345
clientmods/turtle/tlang_lex.lua
Normal file
345
clientmods/turtle/tlang_lex.lua
Normal file
@ -0,0 +1,345 @@
|
||||
-- CC0/Unlicense Emilia 2020
|
||||
|
||||
local tlang = ...
|
||||
|
||||
local function in_list(value, list)
|
||||
for k, v in ipairs(list) do
|
||||
if v == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
-- lex state
|
||||
--[[
|
||||
{
|
||||
code = "",
|
||||
position = int
|
||||
}
|
||||
--]]
|
||||
|
||||
-- lex types
|
||||
--[[
|
||||
literal
|
||||
number
|
||||
quote
|
||||
identifier
|
||||
mapid -- TEMP
|
||||
string
|
||||
symbol
|
||||
code_open
|
||||
code_close
|
||||
code_e_open
|
||||
code_e_close
|
||||
map_open
|
||||
map_close
|
||||
map_relation
|
||||
--]]
|
||||
|
||||
|
||||
-- yeah yeah regex im lazy in this time consuming way shush
|
||||
local whitespace = {" ", "\t", "\n", "\r", "\v"}
|
||||
local identifier_start = {
|
||||
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
|
||||
"_", "."
|
||||
}
|
||||
local identifier_internal = {
|
||||
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
|
||||
"_",
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
|
||||
}
|
||||
local symbol_start = {"!", "-", "+", "=", "&", "*", "/", "^", "%", ">", "<", "?", "~"}
|
||||
local symbol_values = {
|
||||
"!", "-", "+", "=", "&", "*", "/", "^", "%", ">", "<", "?", "~"
|
||||
}
|
||||
local string_start = {"\"", "'"}
|
||||
local number_start = {"-", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
|
||||
local number_values = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
|
||||
local escape_values = {n = "\n", r = "\r", v = "\v", t = "\t", ['"'] = '"', ["'"] = "'"}
|
||||
local symbols = {
|
||||
"!", "-", "+", "=", "&", "*", "/", "^", "%", ">", "<", "?", "~",
|
||||
"&&", "||", "==", "!=", ">=", "<=", "--", "++"
|
||||
}
|
||||
|
||||
local function lex_peek(state)
|
||||
local out = state.code:sub(state.position, state.position)
|
||||
if out == "" then
|
||||
return nil
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function lex_next(state)
|
||||
local value = lex_peek(state)
|
||||
state.position = state.position + 1
|
||||
return value
|
||||
end
|
||||
|
||||
local function lex_expect(state, chars)
|
||||
if type(chars) == "string" then
|
||||
chars = {chars}
|
||||
end
|
||||
|
||||
local n = lex_next(state)
|
||||
if in_list(n, chars) then
|
||||
return n
|
||||
else
|
||||
return nil -- ERROR!
|
||||
end
|
||||
end
|
||||
|
||||
local function lex_whitespace(state)
|
||||
while true do
|
||||
local n = lex_peek(state)
|
||||
if not in_list(n, whitespace) then
|
||||
return
|
||||
end
|
||||
lex_next(state)
|
||||
end
|
||||
end
|
||||
|
||||
local function lex_identifier_raw(state, top)
|
||||
local identifier = {}
|
||||
local n = 1
|
||||
|
||||
while true do
|
||||
local cur = lex_peek(state)
|
||||
if in_list(cur, identifier_internal) then
|
||||
identifier[n] = lex_next(state)
|
||||
n = n + 1
|
||||
elseif cur == "." then
|
||||
lex_next(state)
|
||||
local subs = lex_identifier_raw(state)
|
||||
|
||||
if type(subs) == "string" then
|
||||
subs = {subs}
|
||||
end
|
||||
|
||||
if n > 1 then
|
||||
table.insert(subs, 1, table.concat(identifier))
|
||||
elseif top then -- TOS .key.key syntax
|
||||
table.insert(subs, 1, '')
|
||||
end
|
||||
|
||||
return subs
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return {table.concat(identifier)}
|
||||
end
|
||||
|
||||
local function lex_identifier(state)
|
||||
local id = lex_identifier_raw(state, true)
|
||||
return {type = "literal", subtype = "identifier", value = id}
|
||||
end
|
||||
|
||||
-- `identifier
|
||||
local function lex_quote(state)
|
||||
lex_next(state)
|
||||
local val = lex_identifier(state)
|
||||
val.subtype = "quote"
|
||||
return val
|
||||
end
|
||||
|
||||
local function lex_single_char(state, t, char)
|
||||
lex_next(state)
|
||||
return {type = t, value = char}
|
||||
end
|
||||
|
||||
local function lex_code_open(state)
|
||||
return lex_single_char(state, "code_open", "{")
|
||||
end
|
||||
|
||||
local function lex_code_close(state)
|
||||
return lex_single_char(state, "code_close", "}")
|
||||
end
|
||||
|
||||
local function lex_code_e_open(state)
|
||||
return lex_single_char(state, "code_e_open", "(")
|
||||
end
|
||||
|
||||
local function lex_code_e_close(state)
|
||||
return lex_single_char(state, "code_e_close", ")")
|
||||
end
|
||||
|
||||
local function lex_map_open(state)
|
||||
return lex_single_char(state, "map_open", "[")
|
||||
end
|
||||
|
||||
local function lex_map_relation(state)
|
||||
return lex_single_char(state, "map_relation", ":")
|
||||
end
|
||||
|
||||
local function lex_map_close(state)
|
||||
return lex_single_char(state, "map_close", "]")
|
||||
end
|
||||
|
||||
local function lex_string_escape(state)
|
||||
local n = lex_next(state)
|
||||
return escape_values[n]
|
||||
end
|
||||
|
||||
local function lex_string(state)
|
||||
local bchar = lex_next(state)
|
||||
|
||||
local escaped = false
|
||||
local string = {}
|
||||
local stringi = 1
|
||||
|
||||
while true do
|
||||
local n = lex_next(state)
|
||||
|
||||
if n == bchar then
|
||||
return {type = "literal", subtype = "string", value = table.concat(string)}
|
||||
elseif n == "\\" then
|
||||
n = lex_string_escape(state)
|
||||
end
|
||||
|
||||
if n == nil then
|
||||
return nil -- ERROR
|
||||
end
|
||||
|
||||
string[stringi] = n
|
||||
stringi = stringi + 1
|
||||
end
|
||||
end
|
||||
|
||||
local function lex_number(state)
|
||||
local used_sep = false
|
||||
local num = {}
|
||||
local numi = 1
|
||||
|
||||
local n = lex_peek(state)
|
||||
if in_list(n, number_start) then
|
||||
num[numi] = lex_next(state)
|
||||
numi = numi + 1
|
||||
|
||||
while true do
|
||||
n = lex_peek(state)
|
||||
|
||||
if n == "." and not used_sep then
|
||||
used_sep = true
|
||||
elseif not in_list(n, number_values) then
|
||||
return {type = "literal", subtype = "number", value = table.concat(num)}
|
||||
end
|
||||
|
||||
num[numi] = lex_next(state)
|
||||
numi = numi + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function lex_symbol(state)
|
||||
local sym = {}
|
||||
local symi = 1
|
||||
|
||||
while true do
|
||||
local n = lex_peek(state)
|
||||
if not in_list(n, symbol_values) then
|
||||
local symbol = table.concat(sym)
|
||||
if in_list(symbol, symbols) then
|
||||
return {type = "symbol", value = symbol}
|
||||
else
|
||||
return nil -- ERROR
|
||||
end
|
||||
elseif n == nil then
|
||||
return nil -- ERROR
|
||||
else
|
||||
sym[symi] = lex_next(state)
|
||||
symi = symi + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function lex_number_or_symbol(state)
|
||||
local nextpeek = state.code:sub(state.position + 1, state.position + 1)
|
||||
if in_list(nextpeek, number_values) then
|
||||
return lex_number(state)
|
||||
else
|
||||
return lex_symbol(state)
|
||||
end
|
||||
end
|
||||
|
||||
local function lex_comment(state)
|
||||
while true do
|
||||
local n = lex_next(state)
|
||||
if n == nil or n == "\n" then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function lex_step(state)
|
||||
local cur = lex_peek(state)
|
||||
|
||||
if cur == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
if in_list(cur, whitespace) then
|
||||
lex_whitespace(state)
|
||||
end
|
||||
|
||||
cur = lex_peek(state)
|
||||
|
||||
if cur == "`" then
|
||||
return lex_quote(state)
|
||||
elseif cur == "-" then -- special case for negative numbers and the minus
|
||||
return lex_number_or_symbol(state)
|
||||
elseif in_list(cur, symbol_start) then
|
||||
return lex_symbol(state)
|
||||
elseif cur == "{" then
|
||||
return lex_code_open(state)
|
||||
elseif cur == "}" then
|
||||
return lex_code_close(state)
|
||||
elseif cur == "(" then
|
||||
return lex_code_e_open(state)
|
||||
elseif cur == ")" then
|
||||
return lex_code_e_close(state)
|
||||
elseif cur == "[" then
|
||||
return lex_map_open(state)
|
||||
elseif cur == "]" then
|
||||
return lex_map_close(state)
|
||||
elseif cur == ":" then
|
||||
return lex_map_relation(state)
|
||||
elseif in_list(cur, identifier_start) then
|
||||
return lex_identifier(state)
|
||||
elseif in_list(cur, string_start) then
|
||||
return lex_string(state)
|
||||
elseif in_list(cur, number_start) then
|
||||
return lex_number(state)
|
||||
elseif cur == "#" then
|
||||
return lex_comment(state)
|
||||
end
|
||||
end
|
||||
|
||||
-- lex
|
||||
function tlang.lex(code)
|
||||
local state = {code = code, position = 1}
|
||||
local lexed = {}
|
||||
local lexi = 1
|
||||
|
||||
while true do
|
||||
local n = lex_step(state)
|
||||
|
||||
if n == nil then
|
||||
if state.position <= #state.code then
|
||||
return nil
|
||||
else
|
||||
return lexed
|
||||
end
|
||||
end
|
||||
|
||||
-- comment lexer returns false
|
||||
if n ~= false then
|
||||
lexed[lexi] = n
|
||||
lexi = lexi + 1
|
||||
end
|
||||
end
|
||||
end
|
208
clientmods/turtle/tlang_parse.lua
Normal file
208
clientmods/turtle/tlang_parse.lua
Normal file
@ -0,0 +1,208 @@
|
||||
-- CC0/Unlicense Emilia 2020
|
||||
|
||||
-- parse types
|
||||
--[[
|
||||
quote
|
||||
identifier
|
||||
code
|
||||
map
|
||||
string
|
||||
number
|
||||
symbol
|
||||
--]]
|
||||
|
||||
local tlang = ...
|
||||
|
||||
local internal = {}
|
||||
|
||||
local function sublist(list, istart, iend, inclusive)
|
||||
local o = {}
|
||||
local oi = 1
|
||||
|
||||
inclusive = inclusive or false
|
||||
|
||||
for i, v in ipairs(list) do
|
||||
iend = iend or 0 -- idk how but iend can become nil
|
||||
|
||||
local uninc = i > istart and i < iend
|
||||
local incl = i >= istart and i <= iend
|
||||
|
||||
if (inclusive and incl) or (not inclusive and uninc) then
|
||||
o[oi] = v
|
||||
oi = oi + 1
|
||||
end
|
||||
end
|
||||
|
||||
return o
|
||||
end
|
||||
|
||||
|
||||
local function parse_peek(state)
|
||||
return state.lexed[state.position]
|
||||
end
|
||||
|
||||
local function parse_next(state)
|
||||
local n = parse_peek(state)
|
||||
state.position = state.position + 1
|
||||
return n
|
||||
end
|
||||
|
||||
local function parse_identifier(state)
|
||||
local lexid = parse_next(state).value
|
||||
|
||||
for i, v in ipairs(lexid) do
|
||||
if v:match("^[0-9]+$") then
|
||||
lexid[i] = tonumber(v)
|
||||
end
|
||||
end
|
||||
|
||||
return {type = "identifier", value = lexid}
|
||||
end
|
||||
|
||||
local function parse_map(state)
|
||||
local map = {}
|
||||
local mapi = 1
|
||||
|
||||
if parse_next(state).type ~= "map_open" then
|
||||
return nil -- ERROR
|
||||
end
|
||||
|
||||
while true do
|
||||
local n = parse_next(state)
|
||||
local skip = false -- lua has no continue, 5.1 has no goto
|
||||
|
||||
if n == nil then
|
||||
return nil -- ERROR
|
||||
end
|
||||
|
||||
if n.type == "map_close" then
|
||||
break
|
||||
elseif n.type == "literal" and (n.subtype == "identifier" or n.subtype == "string") then
|
||||
local key = n.value
|
||||
local mr = parse_peek(state)
|
||||
|
||||
if type(key) == "table" then
|
||||
key = key[1]
|
||||
end
|
||||
|
||||
if mr.type == "map_relation" then
|
||||
parse_next(state)
|
||||
local nval = internal.parse_step(state)
|
||||
|
||||
if nval == nil then
|
||||
return nil -- ERROR
|
||||
end
|
||||
|
||||
map[key] = nval
|
||||
skip = true
|
||||
end
|
||||
end
|
||||
|
||||
if not skip then
|
||||
local nval = tlang.parse({n})
|
||||
|
||||
if nval == nil then
|
||||
return nil -- ERROR
|
||||
end
|
||||
|
||||
map[mapi] = nval[1]
|
||||
mapi = mapi + 1
|
||||
end
|
||||
end
|
||||
|
||||
return {type = "map", value = map}
|
||||
end
|
||||
|
||||
local function parse_find_matching(state, open, close)
|
||||
local level = 1
|
||||
|
||||
parse_next(state) -- skip beginning
|
||||
|
||||
while level ~= 0 do
|
||||
local n = parse_next(state)
|
||||
if n == nil then
|
||||
return nil -- ERROR
|
||||
elseif n.type == open then
|
||||
level = level + 1
|
||||
elseif n.type == close then
|
||||
level = level - 1
|
||||
end
|
||||
end
|
||||
|
||||
return state.position - 1
|
||||
end
|
||||
|
||||
local function parse_code(state, open, close)
|
||||
local istart = state.position
|
||||
local iend = parse_find_matching(state, open, close)
|
||||
|
||||
return {
|
||||
type = "code",
|
||||
value = tlang.parse(sublist(state.lexed, istart, iend))
|
||||
}
|
||||
end
|
||||
|
||||
function internal.parse_step(state)
|
||||
local n = parse_peek(state)
|
||||
|
||||
if n == nil then
|
||||
return nil
|
||||
elseif n.type == "code_open" then
|
||||
return parse_code(state, "code_open", "code_close")
|
||||
elseif n.type == "code_e_open" then
|
||||
return {
|
||||
parse_code(state, "code_e_open", "code_e_close"),
|
||||
{type = "identifier", value = "run"}
|
||||
}
|
||||
-- also return run
|
||||
elseif n.type == "map_open" then
|
||||
local istart = state.position
|
||||
local iend = parse_find_matching(state, "map_open", "map_close")
|
||||
return parse_map({lexed = sublist(state.lexed, istart, iend, true), position = 1})
|
||||
elseif n.type == "literal" then
|
||||
if n.subtype == "number" then
|
||||
parse_next(state)
|
||||
return {type = "number", value = tonumber(n.value)}
|
||||
elseif n.subtype == "string" then
|
||||
parse_next(state)
|
||||
return {type = "string", value = n.value}
|
||||
elseif n.subtype == "identifier" then
|
||||
return parse_identifier(state)
|
||||
elseif n.subtype == "quote" then
|
||||
parse_next(state)
|
||||
return {type = "quote", value = n.value}
|
||||
end
|
||||
elseif n.type == "symbol" then
|
||||
parse_next(state)
|
||||
return {type = "symbol", value = n.value}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- parse
|
||||
function tlang.parse(lexed)
|
||||
local state = {lexed = lexed, position = 1}
|
||||
local tree = {}
|
||||
local treei = 1
|
||||
|
||||
while true do
|
||||
local n = internal.parse_step(state)
|
||||
|
||||
if n == nil then
|
||||
if state.position <= #state.lexed then
|
||||
return nil
|
||||
else
|
||||
return tree
|
||||
end
|
||||
end
|
||||
|
||||
if n.type == nil then -- () = {} run
|
||||
tree[treei] = n[1]
|
||||
tree[treei + 1] = n[2]
|
||||
treei = treei + 2
|
||||
else
|
||||
tree[treei] = n
|
||||
treei = treei + 1
|
||||
end
|
||||
end
|
||||
end
|
741
clientmods/turtle/tlang_vm.lua
Normal file
741
clientmods/turtle/tlang_vm.lua
Normal file
@ -0,0 +1,741 @@
|
||||
-- CC0/Unlicense Emilia 2020
|
||||
|
||||
local tlang = ...
|
||||
|
||||
local function in_list(value, list)
|
||||
for k, v in ipairs(list) do
|
||||
if v == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function in_keys(value, list)
|
||||
return list[value] ~= nil
|
||||
end
|
||||
|
||||
-- state
|
||||
--[[
|
||||
{
|
||||
locals = {},
|
||||
stack = {},
|
||||
builtins = {},
|
||||
code_stack = {},
|
||||
wait_target = float,
|
||||
paused = f/t,
|
||||
nextpop = f/t
|
||||
}
|
||||
--]]
|
||||
|
||||
-- program counter
|
||||
--[[
|
||||
sg = 0/1,
|
||||
pos = int/string,
|
||||
elem = int
|
||||
--]]
|
||||
|
||||
function tlang.boolean_to_number(b)
|
||||
if b then
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
function tlang.number_to_boolean(n)
|
||||
if n ~= 0 then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- convert a lua value into a tlang literal
|
||||
function tlang.value_to_tlang(value)
|
||||
local t = type(value)
|
||||
if t == "string" then
|
||||
return {type = "string", value = value}
|
||||
elseif t == "number" then
|
||||
return {type = "number", value = value}
|
||||
elseif t == "boolean" then
|
||||
return {type = "number", value = tlang.boolean_to_number(value)}
|
||||
elseif t == "table" then
|
||||
local map = {}
|
||||
|
||||
for k, v in pairs(value) do
|
||||
map[k] = tlang.value_to_tlang(v)
|
||||
end
|
||||
|
||||
return {type = "map", value = map}
|
||||
end
|
||||
end
|
||||
|
||||
-- convert a tlang literal to a lua value
|
||||
function tlang.tlang_to_value(tl)
|
||||
if type(tl) ~= "table" then
|
||||
return
|
||||
end
|
||||
|
||||
if tl.type == "map" then
|
||||
local o = {}
|
||||
|
||||
for k, v in pairs(tl.value) do
|
||||
o[k] = tlang.tlang_to_value(v)
|
||||
end
|
||||
|
||||
return o
|
||||
else
|
||||
return tl.value
|
||||
end
|
||||
end
|
||||
|
||||
local literals = {
|
||||
"quote",
|
||||
"code",
|
||||
"map",
|
||||
"string",
|
||||
"number"
|
||||
}
|
||||
|
||||
|
||||
function tlang.call(state, target)
|
||||
if target.sg == 0 then
|
||||
state.code_stack[#state.code_stack + 1] = state.stack[target.pos]
|
||||
table.remove(state.stack, target.pos)
|
||||
target.pos = #state.code_stack
|
||||
end
|
||||
|
||||
state.locals[#state.locals + 1] = {vars = {}, pc = target}
|
||||
end
|
||||
|
||||
function tlang.call_tos(state)
|
||||
tlang.call(state, {sg = 0, pos = #state.stack, elem = 1})
|
||||
end
|
||||
|
||||
function tlang.call_var(state, name)
|
||||
if type(name) ~= "table" then
|
||||
name = {name}
|
||||
end
|
||||
|
||||
tlang.call(state, {sg = 1, pos = name, elem = 1})
|
||||
end
|
||||
|
||||
function tlang.call_builtin(state, name)
|
||||
local f = state.builtins[name]
|
||||
f(state)
|
||||
end
|
||||
|
||||
function tlang.call_var_or_builtin(state, name)
|
||||
if in_keys(name, state.builtins) then
|
||||
tlang.call_builtin(state, name)
|
||||
else
|
||||
tlang.call_var(state, name)
|
||||
end
|
||||
end
|
||||
|
||||
function tlang.push_values(state, vals)
|
||||
for i, v in ipairs(vals) do
|
||||
tlang.push(state, v)
|
||||
end
|
||||
end
|
||||
|
||||
function tlang.lua_call_tos(state, ...)
|
||||
tlang.push_values(state, {...})
|
||||
tlang.call_tos(state)
|
||||
end
|
||||
|
||||
function tlang.lua_call_var(state, name, ...)
|
||||
tlang.push_values(state, {...})
|
||||
tlang.call_var(state, name)
|
||||
end
|
||||
|
||||
local function find_var_pos(state, name)
|
||||
local slen = #state.locals
|
||||
|
||||
for i = 1, slen do
|
||||
local v = state.locals[slen + 1 - i]
|
||||
if in_keys(name, v.vars) then
|
||||
return slen + 1 - i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function tlang.map_access_assign(state, index, start, assign)
|
||||
local container
|
||||
local curtab
|
||||
|
||||
if start then
|
||||
container = start
|
||||
elseif index[1] == "" and #index > 1 then
|
||||
curtab = state.stack[#state.stack].value
|
||||
else
|
||||
local pos = find_var_pos(state, index[1])
|
||||
-- assignments can go at the current scope
|
||||
if assign then
|
||||
pos = pos or #state.locals
|
||||
elseif not pos then
|
||||
return nil -- ERROR, variable undefined
|
||||
end
|
||||
|
||||
container = state.locals[pos].vars
|
||||
end
|
||||
|
||||
if not container and not curtab then
|
||||
return
|
||||
end
|
||||
|
||||
if #index == 1 then
|
||||
if assign then
|
||||
container[index[1]] = assign
|
||||
return
|
||||
else
|
||||
return container[index[1]]
|
||||
end
|
||||
end
|
||||
|
||||
curtab = curtab or container[index[1]].value
|
||||
|
||||
for idx = 2, #index - 1 do
|
||||
curtab = curtab[index[idx]]
|
||||
|
||||
if not curtab then
|
||||
return nil
|
||||
end
|
||||
|
||||
curtab = curtab.value
|
||||
end
|
||||
|
||||
if assign then
|
||||
curtab[index[#index]] = assign
|
||||
else
|
||||
return curtab[index[#index]]
|
||||
end
|
||||
end
|
||||
|
||||
function tlang.near_access(state, index)
|
||||
return tlang.map_access_assign(state, index)
|
||||
end
|
||||
|
||||
function tlang.near_assign(state, index, value)
|
||||
tlang.map_access_assign(state, index, nil, value)
|
||||
end
|
||||
|
||||
function tlang.global_access(state, index)
|
||||
tlang.map_access_assign(state, index, state.locals[1].vars)
|
||||
end
|
||||
|
||||
function tlang.global_assign(state, index, value)
|
||||
tlang.map_access_assign(state, index, state.locals[1].vars, value)
|
||||
end
|
||||
|
||||
function tlang.local_access(state, index)
|
||||
tlang.map_access_assign(state, index, state.locals[#state.locals].vars)
|
||||
end
|
||||
|
||||
function tlang.local_assign(state, index, value)
|
||||
tlang.map_access_assign(state, index, state.locals[#state.locals].vars, value)
|
||||
end
|
||||
|
||||
function tlang.get_pc(state)
|
||||
return state.locals[#state.locals].pc
|
||||
end
|
||||
|
||||
local function accesspc(state, pc)
|
||||
local code
|
||||
if pc.sg == 0 then -- stack
|
||||
code = state.code_stack[pc.pos]
|
||||
elseif pc.sg == 1 then -- global
|
||||
code = tlang.near_access(state, pc.pos)
|
||||
end
|
||||
|
||||
if code then
|
||||
return code.value[pc.elem]
|
||||
end
|
||||
end
|
||||
|
||||
function tlang.increment_pc(state, pc)
|
||||
local next_pc = {sg = pc.sg, pos = pc.pos, elem = pc.elem + 1}
|
||||
|
||||
if accesspc(state, next_pc) then
|
||||
return next_pc
|
||||
end
|
||||
end
|
||||
|
||||
local function getnext(state)
|
||||
if state.locals[#state.locals].nextpop then
|
||||
local pc = tlang.get_pc(state)
|
||||
|
||||
-- allows for finished states to be used in calls
|
||||
if #state.locals == 1 then
|
||||
return nil
|
||||
end
|
||||
|
||||
state.locals[#state.locals] = nil
|
||||
|
||||
-- pop code stack
|
||||
if pc.sg == 0 then
|
||||
state.code_stack[pc.pos] = nil
|
||||
end
|
||||
|
||||
return getnext(state)
|
||||
end
|
||||
|
||||
local current
|
||||
if not state.locals[#state.locals].nextpop then
|
||||
state.current_pc = tlang.get_pc(state)
|
||||
current = accesspc(state, state.current_pc)
|
||||
end
|
||||
|
||||
local incd = tlang.increment_pc(state, tlang.get_pc(state))
|
||||
if not incd then
|
||||
state.locals[#state.locals].nextpop = true
|
||||
else
|
||||
state.locals[#state.locals].pc = incd
|
||||
end
|
||||
|
||||
return current
|
||||
end
|
||||
|
||||
-- doesn't support jumping out of scope yet
|
||||
function tlang.set_next_pc(state, pc)
|
||||
-- this probably causes issues when jumping outside scope
|
||||
state.locals[#state.locals].nextpop = nil
|
||||
|
||||
state.locals[#state.locals].pc = pc
|
||||
end
|
||||
|
||||
function tlang.peek_raw(state)
|
||||
return state.stack[#state.stack]
|
||||
end
|
||||
|
||||
function tlang.pop_raw(state)
|
||||
local tos = tlang.peek_raw(state)
|
||||
state.stack[#state.stack] = nil
|
||||
return tos
|
||||
end
|
||||
|
||||
function tlang.push_raw(state, value)
|
||||
state.stack[#state.stack + 1] = value
|
||||
end
|
||||
|
||||
function tlang.peek(state)
|
||||
return tlang.tlang_to_value(tlang.peek_raw(state))
|
||||
end
|
||||
|
||||
function tlang.pop(state)
|
||||
return tlang.tlang_to_value(tlang.pop_raw(state))
|
||||
end
|
||||
|
||||
function tlang.push(state, value)
|
||||
tlang.push_raw(state, tlang.value_to_tlang(value))
|
||||
end
|
||||
|
||||
local function statepeek_type(state, t)
|
||||
local tos = tlang.peek_raw(state)
|
||||
|
||||
if tos.type == t then
|
||||
return tos
|
||||
else
|
||||
return nil -- ERROR
|
||||
end
|
||||
end
|
||||
|
||||
local function statepop_type(state, t)
|
||||
local tos = tlang.peek_raw(state)
|
||||
|
||||
if tos.type == t then
|
||||
return tlang.pop_raw(state)
|
||||
else
|
||||
return nil -- ERROR
|
||||
end
|
||||
end
|
||||
|
||||
local function statepop_num(state)
|
||||
return statepop_type(state, "number")
|
||||
end
|
||||
|
||||
local function statepush_num(state, number)
|
||||
tlang.push_raw(state, {type = "number", value = number})
|
||||
end
|
||||
|
||||
|
||||
|
||||
tlang.builtins = {}
|
||||
|
||||
function tlang.builtins.run(state)
|
||||
tlang.call_tos(state)
|
||||
end
|
||||
|
||||
tlang.builtins["="] = function(state)
|
||||
local name = statepop_type(state, "quote")
|
||||
local value = tlang.pop_raw(state)
|
||||
|
||||
tlang.near_assign(state, name.value, value)
|
||||
end
|
||||
|
||||
function tlang.unary(func)
|
||||
return function(state)
|
||||
local tos = tlang.pop_raw(state)
|
||||
if tos.type == "number" then
|
||||
statepush_num(state, func(tos.value))
|
||||
elseif tos.type == "quote" then
|
||||
local n = tlang.near_access(state, tos.value)
|
||||
tlang.near_assign(state, tos.value, {type = "number", value = func(n.value)})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function tlang.binary(func)
|
||||
return function(state)
|
||||
local tos = statepop_num(state)
|
||||
local tos1 = statepop_num(state)
|
||||
|
||||
statepush_num(state, func(tos1.value, tos.value))
|
||||
end
|
||||
end
|
||||
|
||||
tlang.builtins["--"] = tlang.unary(function(v)
|
||||
return v - 1
|
||||
end)
|
||||
|
||||
tlang.builtins["++"] = tlang.unary(function(v)
|
||||
return v + 1
|
||||
end)
|
||||
|
||||
tlang.builtins["!"] = tlang.unary(function(v)
|
||||
return tlang.boolean_to_number(not tlang.number_to_boolean(v))
|
||||
end)
|
||||
|
||||
tlang.builtins["+"] = tlang.binary(function(v1, v2)
|
||||
return v1 + v2
|
||||
end)
|
||||
|
||||
tlang.builtins["-"] = tlang.binary(function(v1, v2)
|
||||
return v1 - v2
|
||||
end)
|
||||
|
||||
tlang.builtins["*"] = tlang.binary(function(v1, v2)
|
||||
return v1 * v2
|
||||
end)
|
||||
|
||||
tlang.builtins["/"] = tlang.binary(function(v1, v2)
|
||||
return v1 / v2
|
||||
end)
|
||||
|
||||
tlang.builtins["%"] = tlang.binary(function(v1, v2)
|
||||
return v1 % v2
|
||||
end)
|
||||
|
||||
tlang.builtins["=="] = tlang.binary(function(v1, v2)
|
||||
return tlang.boolean_to_number(v1 == v2)
|
||||
end)
|
||||
|
||||
tlang.builtins["!="] = tlang.binary(function(v1, v2)
|
||||
return tlang.boolean_to_number(v1 ~= v2)
|
||||
end)
|
||||
|
||||
tlang.builtins[">="] = tlang.binary(function(v1, v2)
|
||||
return tlang.boolean_to_number(v1 >= v2)
|
||||
end)
|
||||
|
||||
tlang.builtins["<="] = tlang.binary(function(v1, v2)
|
||||
return tlang.boolean_to_number(v1 <= v2)
|
||||
end)
|
||||
|
||||
tlang.builtins[">"] = tlang.binary(function(v1, v2)
|
||||
return tlang.boolean_to_number(v1 > v2)
|
||||
end)
|
||||
|
||||
tlang.builtins["<"] = tlang.binary(function(v1, v2)
|
||||
return tlang.boolean_to_number(v1 < v2)
|
||||
end)
|
||||
|
||||
tlang.builtins["&&"] = tlang.binary(function(v1, v2)
|
||||
return tlang.boolean_to_number(
|
||||
tlang.number_to_boolean(v1) and tlang.number_to_boolean(v2))
|
||||
end)
|
||||
|
||||
tlang.builtins["||"] = tlang.binary(function(v1, v2)
|
||||
return tlang.boolean_to_number(
|
||||
tlang.number_to_boolean(v1) or tlang.number_to_boolean(v2))
|
||||
end)
|
||||
|
||||
tlang.builtins["if"] = function(state)
|
||||
local tos = statepop_type(state, "code")
|
||||
local tos1 = tlang.pop_raw(state)
|
||||
|
||||
if tos1.type == "number" then
|
||||
if tos1.value ~= 0 then
|
||||
tlang.push_raw(state, tos)
|
||||
tlang.call_tos(state)
|
||||
end
|
||||
elseif tos1.type == "code" then
|
||||
local tos2 = statepop_num(state)
|
||||
if tos2.value ~= 0 then
|
||||
tlang.push_raw(state, tos1)
|
||||
tlang.call_tos(state)
|
||||
else
|
||||
tlang.push_raw(state, tos)
|
||||
tlang.call_tos(state)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function tlang.builtins.print(state)
|
||||
local value = tlang.pop_raw(state)
|
||||
|
||||
if minetest then
|
||||
local message = "[tlang] " .. tostring(value.value)
|
||||
minetest.display_chat_message(message)
|
||||
minetest.log("info", message)
|
||||
else
|
||||
print(value.value)
|
||||
end
|
||||
end
|
||||
|
||||
function tlang.builtins.dup(state)
|
||||
tlang.push_raw(state, tlang.peek_raw(state))
|
||||
end
|
||||
|
||||
function tlang.builtins.popoff(state)
|
||||
state.stack[#state.stack] = nil
|
||||
end
|
||||
|
||||
function tlang.builtins.wait(state)
|
||||
local tos = statepop_type(state, "number")
|
||||
state.wait_target = os.clock() + tos.value
|
||||
end
|
||||
|
||||
tlang.builtins["forever"] = function(state)
|
||||
local slen = #state.locals
|
||||
|
||||
if state.locals[slen].broke == true then
|
||||
state.locals[slen].broke = nil
|
||||
state.locals[slen].loop_code = nil
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if state.locals[slen].loop_code == nil then
|
||||
local tos = tlang.pop_raw(state)
|
||||
|
||||
if tos.type == "code" then
|
||||
state.locals[slen].loop_code = tos
|
||||
elseif tos.type == "quote" then
|
||||
state.locals[slen].loop_code = statepop_type(state, "code")
|
||||
state.locals[slen].repeat_n = 0
|
||||
state.locals[slen].loop_var = tos.value
|
||||
end
|
||||
end
|
||||
|
||||
if state.locals[slen].loop_var then
|
||||
tlang.local_assign(state,
|
||||
state.locals[slen].loop_var,
|
||||
{type = "number", value = state.locals[slen].repeat_n})
|
||||
state.locals[slen].repeat_n = state.locals[slen].repeat_n + 1
|
||||
end
|
||||
|
||||
tlang.push_raw(state, state.locals[slen].loop_code)
|
||||
|
||||
tlang.set_next_pc(state, state.current_pc)
|
||||
|
||||
tlang.call_tos(state)
|
||||
end
|
||||
|
||||
tlang.builtins["while"] = function(state)
|
||||
local slen = #state.locals
|
||||
|
||||
if state.locals[slen].broke == true then
|
||||
state.locals[slen].broke = nil
|
||||
state.locals[slen].loop_code = nil
|
||||
state.locals[slen].test_code = nil
|
||||
state.locals[slen].loop_stage = nil
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if state.locals[slen].loop_code == nil then
|
||||
local while_block = statepop_type(state, "code")
|
||||
local test_block = statepop_type(state, "code")
|
||||
|
||||
state.locals[slen].test_code = test_block
|
||||
state.locals[slen].loop_code = while_block
|
||||
state.locals[slen].loop_stage = 0
|
||||
end
|
||||
|
||||
-- stage 0, run test
|
||||
if state.locals[slen].loop_stage == 0 then
|
||||
tlang.push_raw(state, state.locals[slen].test_code)
|
||||
tlang.set_next_pc(state, state.current_pc)
|
||||
tlang.call_tos(state)
|
||||
|
||||
state.locals[slen].loop_stage = 1
|
||||
-- stage 1, run while
|
||||
elseif state.locals[slen].loop_stage == 1 then
|
||||
local tos = tlang.pop_raw(state)
|
||||
if tos and tos.value ~= 0 then
|
||||
tlang.push_raw(state, state.locals[slen].loop_code)
|
||||
tlang.set_next_pc(state, state.current_pc)
|
||||
tlang.call_tos(state)
|
||||
else
|
||||
tlang.set_next_pc(state, state.current_pc)
|
||||
state.locals[slen].broke = true
|
||||
end
|
||||
|
||||
state.locals[slen].loop_stage = 0
|
||||
end
|
||||
end
|
||||
|
||||
tlang.builtins["repeat"] = function(state)
|
||||
local slen = #state.locals
|
||||
|
||||
if state.locals[slen].broke == true then
|
||||
state.locals[slen].broke = nil
|
||||
state.locals[slen].loop_code = nil
|
||||
state.locals[slen].repeat_count = nil
|
||||
state.locals[slen].repeat_n = nil
|
||||
state.locals[slen].loop_var = nil
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if state.locals[slen].loop_code == nil then
|
||||
local num_var = tlang.pop_raw(state)
|
||||
local count
|
||||
local block
|
||||
|
||||
if num_var.type == "quote" then
|
||||
count = statepop_num(state)
|
||||
state.locals[slen].loop_var = num_var.value
|
||||
else
|
||||
count = num_var
|
||||
end
|
||||
|
||||
block = statepop_type(state, "code")
|
||||
|
||||
state.locals[slen].loop_code = block
|
||||
state.locals[slen].repeat_count = count.value
|
||||
state.locals[slen].repeat_n = 0
|
||||
end
|
||||
|
||||
if state.locals[slen].repeat_n ~= state.locals[slen].repeat_count then
|
||||
if state.locals[slen].loop_var then
|
||||
tlang.local_assign(state,
|
||||
state.locals[slen].loop_var,
|
||||
{type = "number", value = state.locals[slen].repeat_n})
|
||||
end
|
||||
|
||||
tlang.push_raw(state, state.locals[slen].loop_code)
|
||||
|
||||
tlang.set_next_pc(state, state.current_pc)
|
||||
|
||||
tlang.call_tos(state)
|
||||
|
||||
state.locals[slen].repeat_n = state.locals[slen].repeat_n + 1
|
||||
else
|
||||
tlang.set_next_pc(state, state.current_pc)
|
||||
state.locals[slen].broke = true
|
||||
end
|
||||
end
|
||||
|
||||
tlang.builtins["break"] = function(state)
|
||||
local slen = #state.locals
|
||||
local pos = 0
|
||||
local found = false
|
||||
|
||||
-- find highest loop_code
|
||||
-- slen - i to perform basically bitwise inverse
|
||||
-- it allows it to count down the list effectively
|
||||
for i = 1, slen do
|
||||
if state.locals[slen + 1 - i].loop_code then
|
||||
pos = slen + 1 - i
|
||||
found = true
|
||||
end
|
||||
end
|
||||
|
||||
if found then
|
||||
-- pop the top layers
|
||||
for i = pos + 1, #state.locals do
|
||||
state.locals[i] = nil
|
||||
end
|
||||
|
||||
-- break in the lower layer
|
||||
state.locals[#state.locals].broke = true
|
||||
end
|
||||
end
|
||||
|
||||
tlang.builtins["return"] = function(state)
|
||||
state.locals[#state.locals] = nil
|
||||
end
|
||||
|
||||
tlang.builtins["args"] = function(state)
|
||||
local vars = {}
|
||||
local vari = 1
|
||||
|
||||
while true do
|
||||
local n = tlang.pop_raw(state)
|
||||
if n.type == "quote" then
|
||||
vars[vari] = n.value
|
||||
vari = vari + 1
|
||||
elseif n.type == "number" and n.value == 0 then
|
||||
break
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
for i, v in ipairs(vars) do
|
||||
tlang.local_assign(state, v, tlang.pop_raw(state))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- returns:
|
||||
-- true - more to do
|
||||
-- nil - more to do but waiting
|
||||
-- false - finished
|
||||
-- string - error
|
||||
function tlang.step(state)
|
||||
if state.paused or (state.wait_target and os.clock() < state.wait_target) then
|
||||
return nil
|
||||
end
|
||||
|
||||
local cur = getnext(state)
|
||||
|
||||
if cur == nil then
|
||||
if state.locals[1].nextpop then
|
||||
state.finished = true
|
||||
return false
|
||||
else
|
||||
return "Error: code exited early"
|
||||
end
|
||||
else
|
||||
state.finished = false
|
||||
end
|
||||
|
||||
if in_list(cur.type, literals) then
|
||||
state.stack[#state.stack + 1] = cur
|
||||
elseif cur.type == "identifier" or cur.type == "symbol" then
|
||||
local strname = cur.value
|
||||
if type(cur.value) == "table" then
|
||||
strname = cur.value[1]
|
||||
end
|
||||
|
||||
if in_keys(strname, state.builtins) then
|
||||
local f = state.builtins[strname]
|
||||
f(state)
|
||||
else
|
||||
local var = tlang.near_access(state, cur.value)
|
||||
if var == nil then
|
||||
return "Undefined identifier: " .. table.concat(cur.value, ".")
|
||||
elseif var.type == "code" then
|
||||
tlang.call_var(state, cur.value)
|
||||
else
|
||||
state.stack[#state.stack + 1] = var
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
54
clientmods/undying/init.lua
Normal file
54
clientmods/undying/init.lua
Normal file
@ -0,0 +1,54 @@
|
||||
--
|
||||
-- undying
|
||||
|
||||
|
||||
local sh=false
|
||||
|
||||
local function findbones()
|
||||
return minetest.find_node_near(minetest.localplayer:get_pos(), 6, {"bones:bones"},true)
|
||||
end
|
||||
|
||||
local function digbones()
|
||||
local bn=findbones()
|
||||
if not bn then return false end
|
||||
minetest.dig_node(bn)
|
||||
if findbones() then minetest.after("0.1",digbones) end
|
||||
end
|
||||
|
||||
minetest.register_on_death(function()
|
||||
if not minetest.settings:get_bool("undying") then return end
|
||||
sh=false
|
||||
minetest.after("0.1",function() minetest.send_chat_message("/home") end)
|
||||
minetest.after("0.2",function()
|
||||
digbones()
|
||||
for k, v in ipairs(minetest.localplayer.get_nearby_objects(10)) do
|
||||
if (v:is_player() and v:get_name() ~= minetest.localplayer:get_name()) then
|
||||
local pos = v:get_pos()
|
||||
pos.y = pos.y - 1
|
||||
autofly.aim(pos)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
minetest.register_on_damage_taken(function(hp)
|
||||
if not sh and minetest.settings:get_bool("undying") then
|
||||
local hhp=minetest.localplayer:get_hp()
|
||||
--if (hhp==0 ) then return end
|
||||
if (hhp < 2 ) then
|
||||
sh=true
|
||||
minetest.settings:set_bool("autorespawn",true)
|
||||
minetest.send_chat_message("/sethome") end
|
||||
end
|
||||
end
|
||||
)
|
||||
minetest.register_on_receiving_chat_message(function(msg)
|
||||
if (msg:find('Teleported to home!') or msg:find('Home set!')) then return true end
|
||||
end)
|
||||
|
||||
-- REG cheats on DF
|
||||
if (_G["minetest"]["register_cheat"] ~= nil) then
|
||||
minetest.register_cheat("Undying", "Combat", "undying")
|
||||
else
|
||||
minetest.settings:set_bool('undying',true)
|
||||
end
|
3
clientmods/undying/mod.conf
Normal file
3
clientmods/undying/mod.conf
Normal file
@ -0,0 +1,3 @@
|
||||
name = undying
|
||||
author = cora
|
||||
description = revert death on servers with /sethome / bones
|
1
clientmods/undying/settingtypes.txt
Normal file
1
clientmods/undying/settingtypes.txt
Normal file
@ -0,0 +1 @@
|
||||
undying (Undying) bool false
|
131
clientmods/waterbot/init.lua
Normal file
131
clientmods/waterbot/init.lua
Normal file
@ -0,0 +1,131 @@
|
||||
-- CC0/Unlicense Emilia 2020
|
||||
waterbot = {}
|
||||
|
||||
-- TODO: FreeRefills tries to pick up too much at once
|
||||
-- quint time :]
|
||||
|
||||
-- Lua doesnt have enums and tables look gross
|
||||
-- should still be a table tho
|
||||
local WATER_USABLE = 0 -- water source
|
||||
local WATER_STABLE = 1 -- water source used for refreshing other sources
|
||||
local WATER_USED = 2 -- water source that can be bucketed
|
||||
local AIR = 3 -- something that water can flow into and renew
|
||||
local SOLID = 4 -- something that water cannot flow into and renew
|
||||
|
||||
local function get_offset(pos, radius)
|
||||
return vector.round({
|
||||
x = pos.x - radius - 1,
|
||||
y = pos.y - radius - 1,
|
||||
z = pos.z - radius - 1
|
||||
})
|
||||
end
|
||||
|
||||
-- returns {{{n n n} {n n n} ...} {...} ...}
|
||||
local function get_intarr(pos, radius)
|
||||
local offset = get_offset(pos, radius)
|
||||
local out = {}
|
||||
local diameter = radius * 2 + 1
|
||||
|
||||
for z = 1, diameter do
|
||||
table.insert(out, {})
|
||||
for y = 1, diameter do
|
||||
table.insert(out[#out], {})
|
||||
for x = 1, diameter do
|
||||
local npos = {x = x, y = y, z = z}
|
||||
local node = minetest.get_node_or_nil(vector.add(offset, npos))
|
||||
local v = SOLID
|
||||
if node then
|
||||
if node.name == "mcl_core:water_source" then
|
||||
v = WATER_USABLE
|
||||
elseif node.name == "air" then
|
||||
v = AIR
|
||||
end
|
||||
end
|
||||
table.insert(out[#out][#out[#out]], v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
local function coord_valid(coord, width, height)
|
||||
return ((coord[1] > 0) and (coord[2] > 0)) and ((coord[1] <= width) and (coord[2] <= height))
|
||||
end
|
||||
|
||||
-- returns modified list and safe sources
|
||||
-- table is [z][y][x] accessed
|
||||
-- safe sources is a coordinate list
|
||||
-- this is like cellular automata but the state is mogrified in place
|
||||
local function mogrify_stable(t, offset)
|
||||
local safe = {}
|
||||
|
||||
-- indented like this because this is necessary and full indent would be ugly
|
||||
for zi, zv in ipairs(t) do
|
||||
for yi, yv in ipairs(zv) do
|
||||
for xi, xv in ipairs(yv) do
|
||||
if xv == WATER_USABLE then
|
||||
local nhood = {
|
||||
{xi - 1, zi},
|
||||
{xi, zi - 1},
|
||||
{xi + 1, zi},
|
||||
{xi, zi + 1}
|
||||
}
|
||||
|
||||
local last
|
||||
local applied = false
|
||||
|
||||
for i, v in ipairs(nhood) do
|
||||
if not applied and coord_valid(v, #yv, #t) then
|
||||
local check = t[v[2]][yi][v[1]]
|
||||
if check == WATER_USABLE or check == WATER_STABLE then
|
||||
if not last then
|
||||
last = v
|
||||
else
|
||||
t[ v[2]][yi][ v[1]] = WATER_STABLE
|
||||
t[last[2]][yi][last[1]] = WATER_STABLE
|
||||
yv[xi] = WATER_USED
|
||||
table.insert(safe,
|
||||
vector.add(offset,
|
||||
{x = xi, y = yi, z = zi}))
|
||||
applied = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return t, safe
|
||||
end
|
||||
|
||||
function waterbot.find_renewable_water_near(pos, radius)
|
||||
local int = get_intarr(pos, radius)
|
||||
local offset = get_offset(pos, radius)
|
||||
local mint, safe = mogrify_stable(int, offset)
|
||||
return safe
|
||||
end
|
||||
|
||||
|
||||
local epoch = os.clock()
|
||||
|
||||
minetest.register_globalstep(function()
|
||||
if minetest.settings:get_bool("waterbot_refill") and os.clock() >= epoch + 2 then
|
||||
local pos = minetest.localplayer:get_pos()
|
||||
local sources = waterbot.find_renewable_water_near(pos, 6)
|
||||
|
||||
for i, v in ipairs(sources) do
|
||||
if minetest.switch_to_item("mcl_buckets:bucket_empty") then
|
||||
minetest.interact("place", v)
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
epoch = os.clock()
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_cheat("FreeRefills", "Inventory", "waterbot_refill")
|
463
clientmods/wisp/init.lua
Normal file
463
clientmods/wisp/init.lua
Normal file
@ -0,0 +1,463 @@
|
||||
-- Wisp by system32
|
||||
-- CC0/Unlicense 2020
|
||||
-- version 1.0
|
||||
--
|
||||
-- a clientmod for minetest that lets people send 1 on 1 encrypted messages
|
||||
-- also has a public interface for other mods
|
||||
--
|
||||
-- check out cora's tchat mod, which supports using wisp as a backend
|
||||
|
||||
-- uses the lua-openssl library by George Zhao: https://github.com/zhaozg/lua-openssl
|
||||
|
||||
-- public interface
|
||||
--
|
||||
-- Methods
|
||||
-- send(player, message) - send a message
|
||||
-- register_on_receive(function(message)) - register a receiving callback (includes To: messages), if it returns true the message will not be shown to the player
|
||||
-- register_on_receive_split(function(player, message)) - register_on_receive but player and message are pre split
|
||||
-- register_on_send_split(function(player, message)) - register a sending callback, if it returns true the message will not be sent
|
||||
--
|
||||
-- Properties
|
||||
-- players - list of online players (updated every 2 seconds , when someone may have left, and when a message is queued)
|
||||
|
||||
-- minetest mod security doesn't work so require() is still disabled while modsec is off
|
||||
-- so this doesnt work without patches (it should tho :])
|
||||
|
||||
-- PATCHING MINETEST
|
||||
--
|
||||
-- in src/script/lua_api/l_util.cpp add the following to ModApiUtil:InitializeClient() below API_FCT(decompress);
|
||||
--[[
|
||||
API_FCT(request_insecure_environment);
|
||||
--]]
|
||||
--
|
||||
-- in src/script/cpp_api/s_security.cpp add the following below int thread = getThread(L); in ScriptApiSecurity:initializeSecurityClient()
|
||||
--[[
|
||||
// Backup globals to the registry
|
||||
lua_getglobal(L, "_G");
|
||||
lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP);
|
||||
--]]
|
||||
--
|
||||
-- Recompile Minetest (just using make -j$(nproc) is fine)
|
||||
|
||||
-- INSTALLING OPENSSL
|
||||
--
|
||||
-- Git clone, make, make install (git repo is https://github.com/zhaozg/lua-openssl)
|
||||
-- # mkdir /usr/lib/lua/5.1
|
||||
-- # mv /usr/lib/lua/openssl.so /usr/lib/lua/5.1
|
||||
|
||||
-- ADDING TO TRUSTED
|
||||
--
|
||||
-- add wisp to the trusted mods setting in Minetest
|
||||
|
||||
--[[ protocol:
|
||||
on joining a game, generate a keypair for ECDH
|
||||
|
||||
medium is minetest private messages for all conversation
|
||||
|
||||
alice and bob dont know each other
|
||||
alice introduces herself, giving her ECDH public component to bob (using PEM)
|
||||
bob generates the secret and gives alice his public component
|
||||
alice generates the same secret
|
||||
|
||||
then at any point alice or bob can talk to the other (for eg, alice talks)
|
||||
alice generates a 256 bit nonce and encrypts her message using AES 256 CBC with the nonce as the initialization vector, sending the nonce and message to bob (both base64 encoded and separated by a space character)
|
||||
bob decrypts her message using AES 256 CBC with the nonce as the initialization vector
|
||||
you can swap alice with bob and vice versa to get what will happen if bob messages alice
|
||||
|
||||
the key exchanging step is performed whenever alice or bob don't have the other's key
|
||||
the encryption step is performed every time a private encrypted message is sent
|
||||
|
||||
if a player leaves all players with their public key and other data will forget them, it is important to do this since the keys for a player are not persistent across joining/leaving servers
|
||||
if this was not done alice may use a stale key for bob or vice versa, giving an incorrect shared secret
|
||||
this is not damaging to security, it just wouldn't let them talk
|
||||
--]]
|
||||
|
||||
|
||||
if minetest.request_insecure_environment == nil then
|
||||
error("Wisp: Minetest scripting patches were not applied, please apply them and recompile Minetest.")
|
||||
end
|
||||
|
||||
local env = minetest.request_insecure_environment()
|
||||
if env == nil then
|
||||
error("Wisp: not in trusted mods (secure.trusted_mods), please go into the advanced settings and add wisp (all lowercase).")
|
||||
end
|
||||
|
||||
local openssl = env.require("openssl")
|
||||
|
||||
|
||||
-- private stuff
|
||||
|
||||
local function init_settings(setting_table)
|
||||
for k, v in pairs(setting_table) do
|
||||
if minetest.settings:get(k) == nil then
|
||||
if type(v) == "boolean" then
|
||||
minetest.settings:set_bool(k, v)
|
||||
else
|
||||
minetest.settings:set(k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
init_settings({
|
||||
wisp_prefix = "&**&",
|
||||
wisp_curve = "prime256v1",
|
||||
wisp_cipher = "aes256",
|
||||
wisp_digest = "sha256",
|
||||
wisp_iv_size = 8,
|
||||
wisp_whisper = "msg",
|
||||
wisp_hide_sent = true,
|
||||
wisp_timeout = 10
|
||||
})
|
||||
|
||||
-- players must agree on these
|
||||
local prefix = minetest.settings:get("wisp_prefix")
|
||||
local curve = minetest.settings:get("wisp_curve")
|
||||
local cipher = minetest.settings:get("wisp_cipher")
|
||||
local digest = minetest.settings:get("wisp_digest")
|
||||
|
||||
local iv_size = minetest.settings:get("wisp_iv_size")
|
||||
local whisper = minetest.settings:get("wisp_whisper")
|
||||
local hide_sent = minetest.settings:get_bool("wisp_hide_sent")
|
||||
|
||||
local timeout = tonumber(minetest.settings:get("wisp_timeout"))
|
||||
|
||||
local my_key = openssl.pkey.new("ec", curve)
|
||||
local my_ec = my_key:parse().ec
|
||||
local my_export = my_key:get_public():export()
|
||||
|
||||
local pem_begin = "-----BEGIN PUBLIC KEY-----\n"
|
||||
local pem_end = "\n-----END PUBLIC KEY-----\n"
|
||||
|
||||
my_export = my_export:sub(pem_begin:len() + 1, -pem_end:len() - 1):gsub("\n", "~")
|
||||
|
||||
local friends = {}
|
||||
|
||||
|
||||
-- convenience aliases
|
||||
local function qsplit(message)
|
||||
return string.split(message, " ")
|
||||
end
|
||||
|
||||
local function b64_decode(message)
|
||||
return minetest.decode_base64(message)
|
||||
end
|
||||
|
||||
local function b64_encode(message)
|
||||
return minetest.encode_base64(message)
|
||||
end
|
||||
|
||||
local function in_list(list, value)
|
||||
for k, v in ipairs(list) do
|
||||
if v == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function append(list, item)
|
||||
list[#list + 1] = item
|
||||
end
|
||||
|
||||
local function popfirst(t)
|
||||
local out = {}
|
||||
|
||||
for i = 2, #t do
|
||||
out[#out + 1] = t[i]
|
||||
end
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
local function unpack(t, i)
|
||||
if type(t) ~= "table" then
|
||||
return t
|
||||
end
|
||||
|
||||
i = i or 1
|
||||
if t[i] ~= nil then
|
||||
return t[i], unpack(t, i + 1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- key trading
|
||||
|
||||
local function dm(player, message)
|
||||
minetest.send_chat_message("/" .. whisper .. " " .. player .. " " .. message)
|
||||
end
|
||||
|
||||
-- initialize
|
||||
local function establish(player)
|
||||
dm(player, prefix .. "I " .. my_export)
|
||||
end
|
||||
|
||||
-- receiving
|
||||
local function establish_receive(player, message, sendout)
|
||||
friends[player] = {}
|
||||
local friend = friends[player]
|
||||
|
||||
local key = pem_begin .. message:gsub("~", "\n") .. pem_end
|
||||
|
||||
friend.pubkey = openssl.pkey.read(key)
|
||||
|
||||
friend.secret = my_ec:compute_key(friend.pubkey:parse().ec)
|
||||
friend.key = openssl.digest.digest(digest, friend.secret, true)
|
||||
|
||||
if sendout == true then
|
||||
dm(player, prefix .. "R " .. my_export)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- encryption
|
||||
|
||||
local function run_callbacks(list, params)
|
||||
for k, v in ipairs(list) do
|
||||
if v(unpack(params)) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- encrypt and send
|
||||
local function message_send(player, message, hide_to, force_send)
|
||||
local me = minetest.localplayer:get_name()
|
||||
|
||||
if run_callbacks(wisp.send_split_callbacks, {player, message}) then
|
||||
return
|
||||
end
|
||||
|
||||
-- for displaying the To: stuff
|
||||
if not hide_to then
|
||||
local target = player
|
||||
if target == me then
|
||||
target = "Yourself"
|
||||
end
|
||||
local display_message = "To " .. target .. ": " .. message
|
||||
|
||||
local callback_value = run_callbacks(wisp.receive_callbacks, display_message)
|
||||
callback_value = callback_value or run_callbacks(wisp.receive_split_callbacks, {player, message})
|
||||
|
||||
if not callback_value then
|
||||
minetest.display_chat_message(display_message)
|
||||
end
|
||||
end
|
||||
|
||||
-- actual encryption
|
||||
local friend = friends[player]
|
||||
if friend == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local nonce = openssl.random(iv_size, true)
|
||||
local enc_message = openssl.cipher.encrypt(cipher, message, friend.key, nonce)
|
||||
local final_message = b64_encode(nonce) .. " " .. b64_encode(enc_message)
|
||||
|
||||
if player ~= me or force_send then
|
||||
dm(player, prefix .. "E " .. final_message)
|
||||
end
|
||||
end
|
||||
|
||||
-- decrypt and show
|
||||
local function message_receive(player, message)
|
||||
local friend = friends[player]
|
||||
if friend == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local nonce = b64_decode(qsplit(message)[1])
|
||||
local enc_message = b64_decode(qsplit(message)[2])
|
||||
local dec_message = openssl.cipher.decrypt(cipher, enc_message, friend.key, nonce)
|
||||
final_message = "From " .. player .. ": " .. dec_message
|
||||
|
||||
local callback_value = run_callbacks(wisp.receive_callbacks, final_message)
|
||||
callback_value = callback_value or run_callbacks(wisp.receive_split_callbacks, {player, dec_message})
|
||||
|
||||
if not callback_value then
|
||||
minetest.display_chat_message(final_message)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- check if a player actually left
|
||||
local function player_left(message)
|
||||
for player in message:gmatch("[^ ]* (.+) left the game.") do
|
||||
wisp.players = minetest.get_player_names()
|
||||
for k, v in ipairs(wisp.players) do
|
||||
if v == player then
|
||||
return player
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- check if a message is a PM
|
||||
local function pm(message)
|
||||
for player, message in message:gmatch(".*rom (.+): (.*)") do
|
||||
return player, message
|
||||
end
|
||||
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
-- check if a message is encrypted
|
||||
local function encrypted(message)
|
||||
local split = string.split(message, " ")
|
||||
|
||||
if split[1] == prefix then
|
||||
return string.sub(message, string.len(prefix) + 2)
|
||||
end
|
||||
end
|
||||
|
||||
-- check if a message is 'Message sent.' or similar
|
||||
local function message_sent(message)
|
||||
return message == "Message sent."
|
||||
end
|
||||
|
||||
|
||||
|
||||
wisp = {}
|
||||
wisp.receive_callbacks = {}
|
||||
wisp.receive_split_callbacks = {}
|
||||
wisp.send_split_callbacks = {}
|
||||
wisp.players = {}
|
||||
|
||||
|
||||
local player_check_epoch = 0
|
||||
|
||||
-- message queue, accounts for establishing taking non-zero time
|
||||
-- messages are enqueued and dequeued once they can be sent
|
||||
local queue = {}
|
||||
|
||||
local function enqueue(player, message, hide_to, force_send)
|
||||
append(queue, {
|
||||
player = player,
|
||||
message = message,
|
||||
hide_to = hide_to,
|
||||
force_send = force_send,
|
||||
time = os.time()
|
||||
})
|
||||
wisp.players = minetest.get_player_names()
|
||||
end
|
||||
|
||||
local function dequeue()
|
||||
local new_queue = {}
|
||||
local out = queue[1]
|
||||
for k, v in ipairs(queue) do
|
||||
if k ~= 1 then
|
||||
append(new_queue, v)
|
||||
end
|
||||
end
|
||||
queue = new_queue
|
||||
return out
|
||||
end
|
||||
|
||||
local function peek()
|
||||
return queue[1]
|
||||
end
|
||||
|
||||
|
||||
function wisp.send(player, message, hide_to, force_send)
|
||||
if (player ~= minetest.localplayer:get_name() or force_send) and friends[player] == nil then
|
||||
establish(player)
|
||||
end
|
||||
enqueue(player, message, hide_to, force_send)
|
||||
end
|
||||
|
||||
function wisp.register_on_receive(func)
|
||||
append(wisp.receive_callbacks, func)
|
||||
end
|
||||
|
||||
function wisp.register_on_receive_split(func)
|
||||
append(wisp.receive_split_callbacks, func)
|
||||
end
|
||||
|
||||
function wisp.register_on_send_split(func)
|
||||
append(wisp.send_split_callbacks, func)
|
||||
end
|
||||
|
||||
|
||||
-- glue
|
||||
|
||||
minetest.register_on_receiving_chat_message(
|
||||
function(message)
|
||||
-- hide Message sent.
|
||||
if hide_sent and message_sent(message) then
|
||||
return true
|
||||
end
|
||||
|
||||
-- if its a PM
|
||||
local player, msg = pm(message)
|
||||
if player and msg then
|
||||
|
||||
local split = qsplit(msg)
|
||||
local plain = table.concat(popfirst(split), " ")
|
||||
|
||||
-- initial key trade
|
||||
if split[1] == prefix .. "I" then
|
||||
establish_receive(player, plain, true)
|
||||
return true
|
||||
-- key trade response
|
||||
elseif split[1] == prefix .. "R" then
|
||||
establish_receive(player, plain)
|
||||
return true
|
||||
-- encrypted message receive
|
||||
elseif split[1] == prefix .. "E" then -- encrypt
|
||||
message_receive(player, plain)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- remove friends if they leave
|
||||
local player = player_left(message)
|
||||
if player then
|
||||
friends[player] = nil
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
|
||||
minetest.register_globalstep(
|
||||
function()
|
||||
if os.time() > player_check_epoch + 2 then
|
||||
wisp.players = minetest.get_player_names()
|
||||
end
|
||||
|
||||
local p = peek()
|
||||
if p then
|
||||
if not in_list(wisp.players, peek().player) then
|
||||
minetest.display_chat_message("Player " .. p.player .. " is not online. If they are please resend the message.")
|
||||
dequeue()
|
||||
return
|
||||
end
|
||||
|
||||
if os.time() > p.time + timeout then
|
||||
minetest.display_chat_message("Player " .. p.player .. " is not responsive.")
|
||||
dequeue()
|
||||
return
|
||||
end
|
||||
|
||||
if (p.player == minetest.localplayer:get_name() and not p.force_send) or friends[p.player] then
|
||||
local v = dequeue()
|
||||
message_send(v.player, v.message, v.hide_to, v.force_send)
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
|
||||
minetest.register_chatcommand("e", {
|
||||
params = "<player>",
|
||||
description = "Send encrypted whisper to player",
|
||||
func = function(param)
|
||||
local player = qsplit(param)[1]
|
||||
local message = table.concat(popfirst(qsplit(param)), " ")
|
||||
if player == nil then
|
||||
minetest.display_chat_message("Player not specified.")
|
||||
return
|
||||
end
|
||||
wisp.send(player, message)
|
||||
end
|
||||
})
|
3
clientmods/wisp/mod.conf
Normal file
3
clientmods/wisp/mod.conf
Normal file
@ -0,0 +1,3 @@
|
||||
name = wisp
|
||||
author = system32
|
||||
description = Encrypted whisper/msg (.e). Keys are handled automatically in the background. Also has public methods for other mods.
|
7
clientmods/wisp/settingtypes.txt
Normal file
7
clientmods/wisp/settingtypes.txt
Normal file
@ -0,0 +1,7 @@
|
||||
wisp_prefix (Prefix for encrypted messages) string &**&
|
||||
wisp_curve (Curve for ECDH) string prime256v1
|
||||
wisp_cipher (Cipher for messages) string aes256
|
||||
wisp_digest (Digest for shared secrets) string sha256
|
||||
wisp_iv_size (Size in bytes for initialization vector nonce) int 8
|
||||
wisp_whisper (Whisper command) string msg
|
||||
wisp_hide_sent (Hide "Message sent." server messages) bool true
|
Loading…
x
Reference in New Issue
Block a user