Mods/basic robot/Programs: Difference between revisions
>Voxel No edit summary |
>Voxel No edit summary |
(No difference)
|
Latest revision as of 21:27, 17 April 2021
Programs for support/maintainance/admins.
They might need admin- or puzzle-privileges.
Administration
Mailbox
Shows a form with a field for textinput and a "SEND"-button to the player,
stores the text in a book.
For mailing feedback to the admin.
-- feedback-form, based on rnd's mailrobot
if not pn then pn="Mailbox" -- hajo 2017-03-07
s=0
daynr=os.date("%w") -- %w : weekday [0-6 = Sunday-Saturday]
booknr=tonumber(daynr); -- say(daynr.."/"..booknr)
msgsize = 2500;
_,text = book.read(booknr); text = text or "";
write_msg = function(sender,msg)
local newsize = string.len(text)+string.len(msg);
if newsize>msgsize then return "messages space exceeded" end
text = text .. "\n"..os.date("%Y-%m-%d %H:%M") .. " " .. sender .. ": " .. msg;
book.write(booknr,"messages #"..booknr, text)
end
end
--textarea[X,Y;W,H;name;label;default]
--button[X,Y;W,H;name;label]
if s == 0 then
players = find_player(4);
if players and players[1] then
s=1
local form = "size[8,4.5]" ..
"textarea[0,0;9,4.5;msg;YOUR FEEDBACK-MESSAGE;]"..
"button_exit[-0.5,4.15;2,1;send;send]"
self.show_form(players[1],form)
end
elseif s==1 then
sender,fields = self.read_form();
if sender then
if fields.send then
msg = fields.msg;
if msg and msg~="" then
write_msg(sender,msg); -- activate.up(1)
--_G.minetest.chat_send_player(sender,"#mailbot: your message has been stored")
say("#mailbot: your message has been stored")
end
end
self.remove()
end
end
--
This variant uses separate books for each day of the week.
Without access to _G, the program can also be used by regular users
as their mailbox.
To read the messages, open the books in the robot's library.
Poll/Vote
Shows a form with text and several buttons to choose from to the player,
stores which button was choosen in a book.
This is much like the mailbox above, with buttons instead of a field for text-input.
--todo
Spawn-Quiz
This is the spawn-quiz from the old robots-server.
On the old robot-server, the spawn-building was like a cage,
and to get out, players had to solve this quiz.
The program shows a dialog with a math-quiz, like "2 * 3 + 4 = ?",
with 5 multiple-choice-buttons in a formspec.
When solved, the player is teleported out of the spawn-building.
-- rnd - Spawn-Quiz @ -7 -3 5, with dialog/formspec.
-- When solved, gives apple, and teleports the player out of the spawn-building.
if not s then
s=0
t=0
option = {"A","B","C","D","E"}
generate_question = function()
local a = math.random(10)+0;
local b = math.random(10)+0;
local c = math.random(20)-10;
local d = a*b+c;
msg = "To get out solve the math problem\n\n";
msg = msg .. a.."*"..b.."+"..c .. " = ?\n\n"
problem = a.."*"..b.."+"..c .. " = ?";
correct = math.random(5);
local frm = "";
for i =1,5 do
local offset = 0;
if i~=correct then offset = math.random(10)-5; if offset == 0 then offset = -1 end end
frm = frm .. "button_exit[".. -0.25+(i-1)*1.25 ..",1.75;1.25,1;" .. i .. ";".. d + offset .. "]"
end
local form = "size[6,2.25]" .. "textarea[0.05,-0.3;6.5,2.25;message;;".. msg.."]"..frm;
return form, correct
end
selection = 1;
question = "";
problem = "";
end
if t%10 == 0 then -- new quiz every 10s
t = 0; form,selection = generate_question();
local players = find_player(4);
if players then
for _,pname in ipairs(players) do
self.show_form(pname,form)
end
end
end
t=t+1;
sender,fields = self.read_form()
if sender then
player = _G.minetest.get_player_by_name(sender);
if player then
answer = 0;
for i = 1,5 do if fields[_G.tostring(i)] then answer = i end end
if answer == correct then
player:setpos({x=-4,y=2,z=0})
inv = player:get_inventory(); inv:add_item("main", "default:apple")
_G.minetest.chat_send_player(sender,"<MATH ROBOT> congratulations, here is an apple.")
elseif answer ~= 0 then
player:setpos({x=0,y=-6,z=-1})
say(sender .. " failed to solve the problem " .. problem)
end
end
end
Commands like player:setpos(), player:get_inventory() and access to _G require admin-privileges.
Education
Math-Quiz
See Math_quiz1 and Math_quiz2.
They give rewards for solving questions like "WHAT IS 12*13 ?", via chat.
General-Quiz
From the forum, by rnd.
Show a quiz with text-questions, and buttons with multiple-choice answers.
The order of the answer-buttons is randomized on each run.
if not quiz then
-- QUIZ QUESTIONS:
-- FORMAT: question, answer1, answer2,..., index of correct answer
quiz = {
{
"QUESTION 1: what is the correct answer ?",
"correct is a.",
"correct is b.",
"correct is c.",
3 -- correct is 3rd answer - c.
},
{
"QUESTION 2: what is the correct answer ?",
"correct is a.",
"correct is b.",
"correct is c.",
1 -- correct is a.
},
{
"QUESTION 3: what is the correct answer ?",
"correct is a.",
"correct is b.",
"correct is c.",
2
}
}
local permute = function(n,seed) -- return permuted array {1,..,n}
_G.math.randomseed(seed);
local permute = {}
for i = 1, n do permute[i] = i end --input:sub(i, i)
for i = n,2,-1 do
local j = math.random(i-1);
local tmp = permute[j];
permute[j] = permute[i]; permute[i] = tmp;
end
return permute
end
get_form = function(k)
local n = #quiz[k]-2;
if n < 1 then error("quiz: error in question " .. k) end
local frm = "label[0,0;" .. quiz[k][1] .. "] "
order = permute(n,os.time())
for i = 1,n do
frm = frm .. "button_exit[0,".. (i)*1 ..";8,1;" .. order[i] .. ";".. quiz[k][1+order[i]].. "] "
end
local form = "size[8," .. (n+1) .. "]" .. frm;
return form, quiz[k][#quiz[k]]
end
points = 0; -- how many correct answers so far
question = 1; -- current question
correct = 1; -- current correct answer
state = 1; -- 1: display question, 2: wait for answer
local players = find_player(4);
if not players then say("quiz: no players") self.remove() end
pname = players[1];
end
if state == 1 then
if question>#quiz then
say("Congratulations! You answered all question. Your result: " .. points .. "/" .. #quiz)
self.remove();
else
local form;
form, correct = get_form(question)
self.show_form(pname,form)
state = 2 -- wait for answer
end
elseif state == 2 then
sender,fields = self.read_form()
if sender then -- form event
local pl = _G.minetest.get_player_by_name(pname);
if pl then
if fields.quit then -- button was pressed
local selected = 0;
-- what is the answer?
for k,_ in pairs(fields) do if k~="quit" then selected = tonumber(k) break end end
if selected>0 then
if selected == correct then
say("correct!")
points=points+1
--correct: do something with player
else
say("incorrect!")
-- incorrect: do something else with player
end
state = 1
question = question + 1
else
say("quiz aborted")
self.remove()
-- no question was selected in form?
end
end
end
end
end
Service
Char-table
Make a table of all the keyboard-blocks, above the robot.
-- Char-table, by Kurik
local pos = self.pos()
pos.y = pos.y + 5
local p = {x=0,y=pos.y,z=0}
local i = 1
for z = -8,8 --[[ do ]] do
for x = -8,7 --[[ do ]] do
p.x = pos.x + x
p.z = pos.z + z
--local n = puzzle.get_node(p)
--if n.name == 'air' then puzzle.set_node(p,node) end
--i=0 -- air, for removing the table
keyboard.set(p,i)
i = i + 1
end
end
self.remove()
Char-table2
A variant of the above, can limit the output to selected ranges, and set markers.
-- Char-table - 2017-10-14+
local pos = self.pos()
pos.y = pos.y + 5
local p = {x=0,y=pos.y,z=0}
local i = 1
for z = -8,8 --[[ do ]] do
for x = -8,7 --[[ do ]] do
p.x = pos.x + x
p.z = pos.z + z
keyboard.set(p,0) -- air
r=0
if i<= 6 then r=1 end -- colors
if i>= 7 and i<= 16 then r=2 end -- white numbers
if i>= 65 and i<= 74 then r=3 end -- black numbers
if i>= 82 and i<=107 then r=4 end -- uppercase chars
if i>=114 and i<=139 then r=5 end -- lowercase chars
if i== 17 then r=-1 end -- set marker
if i== 272 then r=-2 end
if r>0 then keyboard.set(p,i) end -- !! adjust condition here !!
if r==-1 then keyboard.set(p,4) end -- green
if r==-2 then keyboard.set(p,3) end -- red marker
i = i + 1
end
--say(i)
end
self.remove()
For example, change "if r>0" to "if r==1" to only show the colored keyboard-blocks.
Door-Opener
Place robot (with a keypad above it) next to a door,
Opens the locked doors inside a building owned by another player for guests,
eg. 'Hotels'.
-- simple door-opener - 2017-11
-- activated by keypad with password
if not i then i=0 end
i=i+1
n=read_node.forward(); --say(i..n)
n=string.sub(n,1,4)
if n=="door" then activate.forward(1); i=9 end
if i>=4 then self.remove() end
turn.left()
--turn.right()
The robot turns around until it finds a door, then activates it = opens/closes it.
Teleporter
Move player to new location.
-- teleporter, by Kurik
puzzle.get_player('hajo'):move_to({x=100,y=9,z=100})
self.remove()
See also the mover from the mod basic_machines.
Shop
For selling stuff - see Simple_Shop.
It checks a chest above the robot for payment,
then exchanges the item(s) from the chest
for one (or more) item(s) from the robot's inventory.
As this uses item-names, it cannot sell unique stuff, eg. keys or books-with-text.
Shop2
A variant of the above shop.
This shop offers to sell several different items, with their own prices,
selected by pressing the different keypads/buttons that are placed near the robot.
if not pn then pn="Mike's Snack-Shop v0.6c"; -- 2017-03-29
self.spam(1)
pickup(3) --collect empty cups
t=""
steel1="default:steel_ingot"
gold1 ="default:gold_ingot"; gold2 ="default:gold_ingot 2"
n=read_node.down(); -- say(n)
if n=="basic_robot:spawner" then
t=read_text.down() -- text can be set with keypad
end
slot=0
-- row 1,2,3,4 --> slot 1,9,17,25
if t=="snack1" then slot= 1; price=gold1 end -- donut
if t=="snack2" then slot= 9; price=gold2 end -- apple-donut
if t=="drink1" then slot=17; price=gold1 end -- cold coffee
if t=="drink2" then slot=25; price=gold2 end -- hot coffee
if slot<1 then say("config error:"..t) self.remove(); end
item = check_inventory.self("","main",slot)
if item=="" then
say(pn.."\n".."out of stock: slot"..slot); self.remove();
end
--say(slot.."->"..item.."!")
if not item then say("?")
else
--say("<"..item..">")
cItem=1
p=string.find(item," ")
if p then -- say("pos="..p)
cItem = string.sub(item,p+1) --!! tools also have a wear-count
--say(p..":"..cItem)
item1 = string.sub(item,1,p-1)
end
say(item.."--> "..item1.." itemCount:"..cItem) --
msg=pn.."\n".."selling "..item1.." for "..price
-- remove "default" and "farming"
msg = string.gsub(msg, "default:", "")
msg = string.gsub(msg, "farming:", "")
say(msg)
end
m=check_inventory.up(price,inv)
if m=="" then msg=msg.."\n no payment in chest !"
say(msg); self.remove();
end
ok2 = check_inventory.up( price,"main") -- look at chest above robot
if not ok2 then
price = string.gsub(price, "default:", "")
say("No payment in chest ("..price..")"); self.remove();
end
--say("Selling "..m1.." for "..m2)
take.up(price)
insert.up(item1)
say("Thanks for your purchase !");
take.up("farming:coffee_cup") -- take back empty cups
self.remove()
end
--
This shop sells snacks: cold and hot coffee, and two kinds of food.
Each row of the robot's inventory is used to store one of the different kinds of goods.
( but 1-2 spots somewhere need to be empty for the payments :)
By specifying the goods to sell by slotnumber, this robot can also sell unique items,
such as keys set to open specific doors, or books with different texts.
Setup:
Keypad1 | _ | Keypad3 |
Keypad2 | Chest | Keypad4 |
Sign | _ | Sign with prices |
(Floor) | Spawner | (Floor) |
Each keypad has to be set to point at the spawner, and write a unique text onto it, eg. "snack1".
The robot reads that text to determine which good to sell.
Admin-Shop
Remote-shop by rnd, from the server ROBOTS-SKYBLOCK.
It supplements the regular admin-shop of about 50 shops at spawn.
If the shop-bot is running, it can be contacted via chat-command "#shop".
It shows a list of items and prices, and directly accesses the players inventory
for payment and delivery.
if not s then
s=0;item = 1; price =""; buyer = ""
_G.minetest.forceload_block(self.pos(),true)
_G.basic_robot.data[self.name()].obj:set_properties({nametag = ""})
self.listen(1);self.spam(1)
shoplist = {};
--scan shops:
pos = self.pos(); pos.y=pos.y-5; pos.x=pos.x-6
pos1 = {x=pos.x-8,y=pos.y-2,z=pos.z-8};pos2 = {x=pos.x+8,y=pos.y+2,z=pos.z+8};
local shoppos = _G.minetest.find_nodes_in_area(pos1, pos2, "shop:shop");
--say("scanning... i found " .. #shoppos .. " shops ");
count = 0
for _,p in pairs(shoppos) do
local inv = _G.minetest.get_meta(p):get_inventory()
local s = inv:get_list("sell");local b = inv:get_list("buy")
local k = s[1]:to_string();
local v = b[1]:to_string();
if (k and k~="") and (v and v~="") then count = count +1 shoplist[count] = {k,v} end
end
local f_sort = function(a,b) return a[1]<b[1] end;
table.sort(shoplist,f_sort)
itemlist = ""; count = 0;
shopinventory = {};
for k,v in pairs(shoplist) do
if v[1] then
count = count +1
itemlist = itemlist .. string.format("%-5s%-40s%-32s",k,v[1],v[2]) .. ","
end
end
t = 0; ct = 0; ci = 0
say("scanning shops ... completed. Added " .. count .. " items for sale ");
player1="";player2 ="";
end
ct=ct+1
if ct%5 == 0 then
ct=0
ctext = "say #shop to buy";
if ci>0 and ci<=count then
if shoplist[ci] then
iname = shoplist[ci][1];local p=string.find(iname,":"); if p then iname = string.sub(iname,p+1) end
sname = shoplist[ci][2];p=string.find(sname,":"); if p then sname = string.sub(sname,p+1) end
ctext = "#SHOP ".. ci .."\n" ..iname .. "\nPRICE\n" .. sname
end
ci=ci+1
elseif ci == count+1 then ci=0
elseif ci == 0 then ci=1
end
text = "SHOP ROBOT"..os.date("%x") .." " .. os.date("%H:%M:%S").. "\n\n"..ctext
self.display_text(text,10,3);
end
speaker,msg = self.listen_msg()
if msg then
if s == 0 then
if string.sub(msg,1,5)=="#shop" then
if msg == "#shop" then
--say("say #shop command, where command is list OR armor OR item number. Example: #shop list or #shop 1")
local list = "1,2,3";
local form = "size[8,8.5]" ..
"label[0.,0.5;ID]"..
"label[0.4,0.5;BUY]"..
"label[3.,0.5;SELL]" ..
"field[7.2,0.25;1.,1;count;count;".. 1 .."]"..
"button[5.,-0.05;2.,1;ARMOR;ARMOR]"..
"textlist[0,1;7.75,7.5;list;" .. itemlist .. "]";
self.show_form(speaker,form)
end
elseif string.sub(msg,1,3) == "#tp" then
local tname = string.sub(msg,5);
player1 = speaker; player2 = tname; s = 1
_G.minetest.chat_send_player(player2,"#SHOP: say tp to allow teleport of " .. player1)
end
elseif s == 1 then -- tp
s=0;
if string.sub(msg,1,2)=="tp" and speaker == player2 then
local player = _G.minetest.get_player_by_name(player1);
local tplayer = _G.minetest.get_player_by_name(player2);
if player and tplayer then
local p1 = player:getpos(); local p2 = tplayer:getpos();
local price = math.floor(math.sqrt((p1.x-p2.x)^2+(p1.y-p2.y)^2+(p1.z-p2.z)^2)/100)+1;
local inv = player:get_inventory();
local stack = _G.ItemStack("default:gold_ingot " .. price);
if price>0 and inv:contains_item("main",stack) then
player:setpos(tplayer:getpos()); inv:remove_item("main",stack)
elseif price>0 then
_G.minetest.chat_send_player(player1,"You need " .. price .. " gold to teleport ")
end
end
else
say("tp aborted.")
end
end
end
sender,fields = self.read_form()
if sender then
local sel = fields.list; --"CHG:3"
--say( string.gsub(_G.dump(fields),"\n",""))
if sel and string.sub(sel,1,3) == "DCL" then
local quantity = tonumber(fields.count) or 1;
local select = tonumber(string.sub(sel,5) or "") or 1;
local item, price
if shoplist[select] then
item,price = shoplist[select][1],shoplist[select][2];
end
local player = _G.minetest.get_player_by_name(sender);
if player and item and price then
local inv = player:get_inventory();
if quantity > 99 then quantity = 99 end
if quantity > 1 then
local k = 1;
local i = string.find(price," ");
if i then
k = tonumber(string.sub(price,i+1)) or 1
price = string.sub(price,1,i-1).. " " .. k*quantity
else
price = price.. " " .. quantity
end
k=1;i = string.find(item," ");
if i then
k = tonumber(string.sub(item,i+1)) or 1
item = string.sub(item,1,i-1).. " " .. k*quantity
else
item = item .. " " .. quantity
end
end
if inv:contains_item("main", price ) then
inv:remove_item("main",price)
inv:add_item("main",item)
_G.minetest.chat_send_player(sender,"#SHOP ROBOT: " .. item .. " sold to " .. sender .. " for " .. price)
else
_G.minetest.chat_send_player(sender,"#SHOP ROBOT: you dont have " .. price .. " in inventory ")
end
end
elseif fields.ARMOR then
local player = _G.minetest.get_player_by_name(sender);
if player then
local inv = player:get_inventory();
if inv:contains_item("main",_G.ItemStack("default:diamond 30")) then
player:set_armor_groups({fleshy = 50})
_G.minetest.chat_send_player(sender,"#SHOP ROBOT: you bought 50% damage reduction.")
else
_G.minetest.chat_send_player(sender,"#SHOP ROBOT: you need 30 diamonds to get armor effect")
end
end
end
end
The robot sits under the roof above the admin-shop,
and scans the shops below to get the list of goods and prices.
The armor-item is special, the server has no mod for armor / 3d-armor.
But if a player has 30 diamonds, the shop can give him a 50% damage-reduction.
Replicator
Make copies of a block from the chest above the robot.
-- Replicator - single, solid, placeable blocks only !
-- No stone, desert-stone, basalt, slate, gneis, ORS. or cobble !
-- Also, no doors, or protectors ...
-- but trapdoors work
if not pn then pn="Replicator v1b"; --say(pn) --hajo, 2017-11-11
m="default:glass"
i=0
max=4
it=check_inventory.up("","main",1); -- say(it)
if it=="" then
say(pn.." running on robot version="..robot_version() )
self.remove()
end
if it=="default:stone" then
say("Cannot process stone !" )
take.up(it)
self.remove()
end
m=it
--
p=string.find(m," ")
if p then
say("Need single item as master !" )
self.remove()
-- m=string.sub(m,1,p-1)
end
say(pn.." working on "..m.."...")
turn.right()
move.forward()
p0=self.pos()
self.reset()
turn.right()
end
ok=puzzle.set_node(p0,{name = m}) -- needs puzzle-priv !
--todo (there is no returncode yet):
--if ok==nil then say("+") else say("Error !"); self.remove() end
dig.forward()
craft("default:clay") -- when dug, they crumble to lumps
craft("darkage:silt")
i=i+1
self.label(i)
insert.up(m)
if i>=max then
say(pn .. " done.")
self.remove()
end
--
To keep the game balanced, it only makes 4 copies on each activation.
It uses digging, so only placeable blocks can be copied (eg. gold-blocks, but no ingots).
That means, players still have to do some work before they can benefit from this machine.
Outfitter
Give several items, much like give-initial-stuff.
-- 2017-11-11+
if not pn then pn="Outfittery"; -- say(pn)
i=0
n="hajo"; -- i=31
p = find_player(2); --say(serialize(p))
if p then n=p[1] end
inv = puzzle.get_player_inv(n) -- say(serialize(inv))
if inv then say("Hello "..n)
else say("Error"); self.remove() end
if n=="hajo" then i=31 end
end
i=i+1
--
m=check_inventory.self("","main",i);
say(i.." ="..m)
local itm = puzzle.ItemStack(m)
ok = inv:add_item('main', itm)
--say(i.." ="..m.." > "..serialize(ok)) -- userdata
--
if i>=9 then say("YW!"); self.remove() end
This outfits a player:
the first 9 items from the robot's inventory are copied to the player's inventory.
Special handling for player 'hajo'; he gets the item from slot 32 instead.