Interaction with formspec: part I

That is: read formspec, limited to lists only for now, and get a stack
This commit is contained in:
Novatux 2015-02-12 12:11:02 +01:00
parent 09bbcc1487
commit aaed258432
6 changed files with 299 additions and 12 deletions

266
api.lua
View File

@ -12,7 +12,7 @@ local function pointable(stack, node)
return nodedef and def and (nodedef.pointable or (nodedef.liquidtype ~= "none" and def.liquid_pointable))
end
local function create_turtle_player(turtle_id, dir)
local function create_turtle_player(turtle_id, dir, only_player)
local info = turtles.get_turtle_info(turtle_id)
local inv = turtles.get_turtle_inventory(turtle_id)
local pitch
@ -68,6 +68,7 @@ local function create_turtle_player(turtle_id, dir)
set_detach = delay(),
set_bone_position = delay(),
}
if only_player then return player end
local above, under = nil, nil
local wieldstack = player:get_wielded_item()
local pos = vector.add(info.spos, dir)
@ -101,6 +102,7 @@ function tl.select(turtle, cptr, slot)
end
local function tl_move(turtle, cptr, dir)
tl.close_form(turtle)
local info = turtles.get_turtle_info(turtle)
if info.energy < MOVE_COST then
cptr.X = 0
@ -139,6 +141,7 @@ function tl.down(turtle, cptr)
end
function tl.turnleft(turtle, cptr)
tl.close_form(turtle)
local info = turtles.get_turtle_info(turtle)
info.ndir = (info.dir + 3) % 4
info.rotate = math.pi / 2
@ -147,6 +150,7 @@ function tl.turnleft(turtle, cptr)
end
function tl.turnright(turtle, cptr)
tl.close_form(turtle)
local info = turtles.get_turtle_info(turtle)
info.ndir = (info.dir + 1) % 4
info.rotate = - math.pi / 2
@ -182,6 +186,7 @@ function tl.detectdown(turtle, cptr)
end
local function turtle_dig(turtle, cptr, dir)
tl.close_form(turtle)
local player, pointed_thing = create_turtle_player(turtle, dir)
if pointed_thing == nil then return end
local info = turtles.get_turtle_info(turtle)
@ -218,8 +223,17 @@ function tl.digdown(turtle, cptr)
end
local function turtle_place(turtle, cptr, dir)
tl.close_form(turtle)
local player, pointed_thing = create_turtle_player(turtle, dir)
if pointed_thing == nil then return end
local formspec = minetest.get_meta(pointed_thing.under):get_string("formspec")
if formspec ~= nil then
local info = turtles.get_turtle_info(turtle)
info.open_formspec = tl.read_formspec(formspec)
info.formspec_type = {type = "node", pos = pointed_thing.under}
info.formspec_fields = {}
return
end
local wieldstack = player:get_wielded_item()
local on_place = (minetest.registered_items[wieldstack:get_name()] or {on_place = minetest.item_place}).on_place
player:set_wielded_item(on_place(wieldstack, player, pointed_thing) or wieldstack)
@ -282,3 +296,253 @@ function tl.get_energy(turtle, cptr)
cptr.Y = u16(info.energy)
cptr.X = u16(math.floor(info.energy / 0x10000))
end
--------------
-- Formspec --
--------------
minetest.register_on_player_receive_fields(function(player, formname, fields)
if player:get_player_name():sub(1, 7) == "turtle:" and formname == "turtle:inventory" then
return true
end
end)
local function send_fields(turtle)
local info = turtles.get_turtle_info(turtle)
local fields = info.formspec_fields
info.formspec_fields = {}
if info.formspec_type.type == "show" then
local dir = minetest.facedir_to_dir(info.dir)
local player = create_turtle_player(turtle, dir, true)
for _, func in ipairs(minetest.registered_on_receive_fields) do
if func(player, info.formspec_type.formname, fields) then
return
end
end
else
local pos = info.formspec_type.pos
local nodedef = minetest.registered_nodes[minetest.get_node(pos).name]
if nodedef and nodedef.on_receive_fields then
local dir = vector.normalize(vector.sub(pos, info.spos))
local player = create_turtle_player(turtle, dir, true)
nodedef.on_receive_fields(vector.new(pos), "", fields, player)
end
end
end
function tl.close_form(turtle)
local info = turtles.get_turtle_info(turtle)
if info.formspec_fields then
info.formspec_fields["quit"] = "true"
send_fields(turtle)
info.open_formspec = nil
info.formspec_type = nil
info.formspec_fields = nil
end
end
local function split_str(str, delim)
local parsed = {}
local i = 1
local s = ""
while i <= string.len(str) do
if str:sub(i, i) == "\\" then
s = s .. str:sub(i, i + 1)
i = i + 2
elseif str:sub(i, i) == delim then
parsed[#parsed + 1] = s
s = ""
i = i + 1
else
s = s .. str:sub(i, i)
i = i + 1
end
end
parsed[#parsed + 1] = s
return parsed
end
local function parse_list(lstdef)
local parsed = split_str(lstdef, ";")
local psize = split_str(parsed[4], ",")
if parsed[1] == "current_name" then parsed[1] = "context" end
return {location = parsed[1], listname = parsed[2],
size = tonumber(psize[1]) * tonumber(psize[2]),
start_index = ((parsed[5] and tonumber(parsed[5])) or 0) + 1}
end
local function merge_adjacent_lists(lsts)
local function merge_at(t, ind)
if t.starts[ind] and t.ends[ind] then
t.starts[t.ends[ind]] = t.starts[ind]
t.ends[t.starts[ind]] = t.ends[ind]
t.starts[ind] = nil
t.ends[ind] = nil
end
end
local locs = {}
for _, lst in ipairs(lsts) do
local loc = lst.location .. ";" .. lst.listname
if locs[loc] == nil then
locs[loc] = {location = lst.location, listname = lst.listname, starts = {}, ends = {}}
end
local starti, endi = lst.start_index, lst.start_index + lst.size
locs[loc].ends[endi] = starti
locs[loc].starts[starti] = endi
merge_at(locs[loc], endi)
merge_at(locs[loc], starti)
end
local new = {}
for _, lst in pairs(locs) do
for starti, endi in pairs(lst.starts) do
new[#new + 1] = {location = lst.location, listname = lst.listname, size = endi - starti, start_index = starti}
end
end
return new
end
function tl.read_formspec(formspec)
local parsed = split_str(formspec, "]")
local lsts = {}
for _, item in ipairs(parsed) do
if item:sub(1, 5) == "list[" then
lsts[#lsts + 1] = parse_list(item:sub(6, -1))
end
end
return {lists = merge_adjacent_lists(lsts)}
end
local old_show_formspec = minetest.show_formspec
function minetest.show_formspec(playername, formname, formspec)
if playername:sub(1, 7) == "turtle:" then
local id = tonumber(playername:sub(8, -1))
local info = turtles.get_turtle_info(id)
info.open_formspec = tl.read_formspec(formspec)
info.formspec_type = {type = "show", formname = formname}
info.formspec_fields = {}
return
end
old_show_formspec(playername, formname, formspec)
end
function tl.open_inv(turtle, cptr)
tl.close_form(turtle)
local info = turtles.get_turtle_info(turtle)
info.open_formspec = tl.read_formspec(
"list[current_player;main;0,4.25;8,4;]"..
"list[current_player;craft;1.75,0.5;3,3;]"..
"list[current_player;craftpreview;5.75,1.5;1,1;]")
info.formspec_type = {type = "show", formname = "turtle:inventory"}
end
-- Formspec memory layout
-- +-----+-----+-----+-----+----
-- | | Pointer | |
-- | Tag | to next | ID | Data
-- | | element | |
-- +-----+-----+-----+-----+----
--
-- For last element (TAG_END), only tag is present
local function push(cptr, addr, value)
cptr[addr] = bit32.band(value, 0xff)
cptr[u16(addr + 1)] = bit32.band(math.floor(value/256), 0xff)
return u16(addr + 2)
end
local function pushC(cptr, addr, value)
cptr[addr] = bit32.band(value, 0xff)
return u16(addr + 1)
end
local function push_string(cptr, addr, str)
for i = 1, string.len(str) do
cptr[u16(addr - 1 + i)] = string.byte(str, i)
end
return u16(addr + string.len(str))
end
local function push_string_counted(cptr, addr, str)
-- String length (2 bytes)
-- String contents
return push_string(cptr, push(cptr, addr, string.len(str)), str)
end
local function push_stack(cptr, addr, stack)
-- Count (2 bytes)
-- Wear (2 bytes)
-- Item name
return push_string_counted(cptr,
push(cptr,
push(cptr, addr,
stack:get_count()),
stack:get_wear()),
stack:get_name())
end
local TAG_END = 0
local TAG_LIST = 1
function tl.get_formspec(turtle, cptr, addr)
local info = turtles.get_turtle_info(turtle)
if not info.open_formspec then
pushC(cptr, addr, TAG_END)
return
end
local i = 0
for _, lst in ipairs(info.open_formspec.lists) do
addr = pushC(cptr, addr, TAG_LIST)
local old_addr = addr
addr = u16(addr + 2)
addr = pushC(cptr, addr, i)
i = i + 1
addr = push_string_counted(cptr, addr, lst.location)
addr = push_string_counted(cptr, addr, lst.listname)
addr = push(cptr, addr, lst.size)
addr = push(cptr, addr, lst.start_index)
push(cptr, old_addr, addr) -- Pointer to next element
end
pushC(cptr, addr, TAG_END)
end
local function get_element_by_id(formspec, elem_id)
return formspec.lists[elem_id + 1]
end
local function get_inventory_from_location(turtle, location)
if location == "current_player" then
return turtles.get_turtle_inventory(turtle)
elseif location == "context" then
local info = turtles.get_turtle_info(turtle)
local formspec = info.formspec_type
if formspec and formspec.type == "node" then
return minetest.get_meta(formspec.pos):get_inventory()
end
print("WARNING: tried to access context without open node formspec")
elseif location:sub(1, 8) == "nodemeta" then
local p = split_str(location, ":")
local spos = split_str(p[2], ",")
local pos = {x = tonumber(spos[1]), y = tonumber(spos[2]), z = tonumber(spos[3])}
if pos.x and pos.y and pos.z then
return minetest.get_meta(pos):get_inventory()
end
print("WARNING: incorrect nodemeta element: " .. location)
else
print("WARNING: unimplemented location type: " .. location)
end
end
function tl.get_stack(turtle, cptr, elem_id, slot, addr)
local info = turtles.get_turtle_info(turtle)
local stack = ItemStack("")
if info.open_formspec then
local formspec = info.open_formspec
local elem = get_element_by_id(formspec, elem_id)
if elem and elem.location and
elem.start_index <= slot and slot <= elem.start_index + elem.size then
local inv = get_inventory_from_location(turtle, elem.location)
if inv then
stack = inv:get_stack(elem.listname, slot)
end
end
end
push_stack(cptr, addr, stack)
end

View File

@ -86,7 +86,7 @@ end
local function write(cptr, addr, value)
cptr[addr] = bit32.band(value, 0xff)
cptr[addr+1] = bit32.band(math.floor(value/256), 0xff)
cptr[u16(addr + 1)] = bit32.band(math.floor(value/256), 0xff)
end
local function push(cptr, value)
@ -308,6 +308,10 @@ ITABLE_RAW = {
[0x80] = "tl.refuel(turtle, cptr, cptr.X, cptr.Y)",
[0x81] = "tl.select(turtle, cptr, cptr.X)",
[0x82] = "tl.get_energy(turtle, cptr)",
[0x88] = "tl.open_inv(turtle, cptr)",
[0x89] = "tl.get_formspec(turtle, cptr, cptr.X)",
[0x8a] = "tl.get_stack(turtle, cptr, cptr.X, cptr.Y, cptr.Z)",
}
ITABLE = {}

View File

@ -105,6 +105,10 @@ ASSEMBLER
: SELECT PLX 0x81 NXT ;
: GET-ENERGY 0x82 PHY PHX NXT ;
: OPEN-INV 0x88 NXT ;
: GET-FORMSPEC PLX 0x89 NXT ;
: GET-STACK PLZ PLY PLX 0x8a NXT ;
ENVIRONMENT
256 CONSTANT /COUNTED-STRING
34 CONSTANT /HOLD
@ -286,10 +290,24 @@ FORTH
: D. DUP >R DABS <# BL HOLD #S R> SIGN #> TYPE ;
: SM/REM OVER >R 2DUP XOR >R ABS >R DABS R> UM/MOD R> 0< IF NEGATE THEN SWAP R> 0< IF NEGATE THEN SWAP ;
\ : KEY BEGIN RAWKEY DUP 31 > OVER 127 < AND IF EXIT THEN DROP AGAIN ;
: ON SET-CHANNEL S" on" SEND ;
: OFF SET-CHANNEL S" off" SEND ;
: IO@ RECEIVE 3 < ;
: LOADPKG 0 0x11e C! PARSE-WORD 0x11a 2! BEGIN 0x11a 2@ SET-CHANNEL 0x11e 1 SEND 0x11e C@ 1+ 0x11e C! 0x11a 2@ 16 RECEIVE-AT 16 C@ WHILE 16 SWAP EVALUATE REPEAT DROP ;
0xf000 CONSTANT FORMSPEC-BUFFER
0 CONSTANT TAG-END
1 CONSTANT TAG-LIST
\ location location-length listname listname-length formspec-addr
: IS-ADDR-LIST DUP >R @ 3 PICK = IF 3 PICK R@ 2 + R@ @ STR= IF R@ @ R> + 2 + DUP >R @ OVER = IF OVER R@ 2 + R@ @ STR= ELSE 0 THEN ELSE 0 THEN ELSE 0 THEN R> DROP ;
: GET-LIST-ADDR >R BEGIN R@ C@ WHILE R@ R@ 1+ @ R@ C@ SWAP >R TAG-LIST = IF 4 + IS-ADDR-LIST IF R> DROP DROP DROP DROP DROP R> -1 EXIT THEN THEN R> R> DROP >R REPEAT R> DROP DROP DROP DROP DROP 0 ;
: GET-ELEM-ID 3 + C@ ;
: GET-LIST-ID GET-LIST-ADDR IF GET-ELEM-ID ELSE -1 THEN ;
: GET-INV-ID >R S" current_player" S" main" R> GET-LIST-ID ;
: GET-CRAFT-ID >R S" current_player" S" craft" R> GET-LIST-ID ;
: GET-CRAFT-OUTPUT-ID >R S" current_player" S" craftpreview" R> GET-LIST-ID ;
: GET-CURRENT CW @ ;
: SET-CURRENT LATEST @ CW @ ! DUP CW ! @ LATEST ! ;
: WORDLIST NEW-WORDLIST @ DUP CELL+ NEW-WORDLIST ! ;

File diff suppressed because one or more lines are too long

View File

@ -36,7 +36,7 @@ local function escape(text)
end
function screen.new()
return "\n\n\n\n\n\n\n\n\n\n"
return "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
end
function screen.add_text(text, toadd)

17
t2.lua
View File

@ -39,12 +39,12 @@ end
function turtles.update_formspec(turtle_id)
local info = turtles.get_turtle_info(turtle_id)
local pos = info.spos
local formspec = "size[9,10]"..
local formspec = "size[13,9]"..
screen.create_text_formspec(info.screen, 0, 0)..
"list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";main;4.8,0;4,4;]"..
"image_button[1,4.6;2.5,1;turtle_execute.png;reboot;]"..
"list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";floppy;0,4.6;1,1;]"..
"list[current_player;main;0.5,6;8,4;]"
"list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";main;4.8,0;8,4;]"..
"image_button[1,7.6;2.5,1;turtle_execute.png;reboot;]"..
"list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";floppy;0,7.6;1,1;]"..
"list[current_player;main;4.8,4.6;8,4;]"
if info.formspec ~= formspec then
info.formspec = formspec
info.formspec_changed = true
@ -184,17 +184,18 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
turtle_receptor_send(turtle_id, "screen", fields.f)
turtles.update_formspec(turtle_id)
return
return true
end
if fields.reboot then
local info = turtles.get_turtle_info(turtle_id)
info.cptr = create_cptr()
return
return true
end
if fields.quit then
local info = turtles.get_turtle_info(turtle_id)
info.playernames[player:get_player_name()] = nil
end
return true
end)
minetest.register_node("turtle:turtle", {
@ -255,7 +256,7 @@ end
minetest.register_entity("turtle:turtle", {
physical = true,
collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
collisionbox = {-0.4999, -0.4999, -0.4999, 0.4999, 0.4999, 0.4999}, -- Not 0.5 to avoid the turtle being stuck due to rounding errors
collides_with_objects = false,
visual = "wielditem", -- TODO: change that to a mesh, and add animations
visual_size = {x = 2/3, y = 2/3},