diff --git a/README.md b/README.md index 867df4e..640d943 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,9 @@ For background information, documentation, and the project roadmap, see [the wik To use this in Minetest, simply install it in your `mods/` or `worldmods/` folder. Minetest will load `init.lua`. -In the game, open the Modular Politics interface with the command `/modpol`. +In the game, open the Modular Politics dashboard with the command `/mp`. + +For testing purposes, players with the `privs` privilege (generally admins) can use the `/mp` command, which resets all the orgs and opens a dashboard. ## Standalone Version on the Command Line diff --git a/modpol_core/api.lua b/modpol_core/api.lua index 45029c1..4f9bca6 100644 --- a/modpol_core/api.lua +++ b/modpol_core/api.lua @@ -11,9 +11,12 @@ dofile (localdir .. "/interactions/interactions.lua") --modules --TODO make this automatic and directory-based -dofile (localdir .. "/modules/add_child_org.lua") +dofile (localdir .. "/modules/add_child_org_consent.lua") dofile (localdir .. "/modules/consent.lua") dofile (localdir .. "/modules/join_org_consent.lua") dofile (localdir .. "/modules/leave_org.lua") +dofile (localdir .. "/modules/remove_child_consent.lua") +dofile (localdir .. "/modules/remove_member_consent.lua") dofile (localdir .. "/modules/remove_org_consent.lua") dofile (localdir .. "/modules/remove_org.lua") +dofile (localdir .. "/modules/rename_org_consent.lua") diff --git a/modpol_core/interactions/interactions.lua b/modpol_core/interactions/interactions.lua index 81de00a..664841b 100644 --- a/modpol_core/interactions/interactions.lua +++ b/modpol_core/interactions/interactions.lua @@ -48,10 +48,10 @@ end -- Function: modpol.interactions.org_dashboard --- Params: user (string), org_name (string) +-- Params: user (string), org_string (string or id) -- Output: Displays a menu of org-specific commands to the user -function modpol.interactions.org_dashboard(user, org_name) - local org = modpol.orgs.get_org(org_name) +function modpol.interactions.org_dashboard(user, org_string) + local org = modpol.orgs.get_org(org_string) if not org then return nil end -- identify parent @@ -72,11 +72,13 @@ function modpol.interactions.org_dashboard(user, org_name) -- list available modules local org_modules = {} for k,v in pairs(org.modules) do - table.insert(org_modules, v.slug) + if not v.hide then + table.insert(org_modules, v.slug) + end end - -- list pending actions - local process_msg = #org.processes .. " total actions" + -- list pending + local process_msg = #org.processes .. " total processes" if org.pending[user] then process_msg = process_msg .. " (" .. #org.pending[user] .. " pending)" else @@ -84,14 +86,14 @@ function modpol.interactions.org_dashboard(user, org_name) end -- set up output - print("Org: " .. org_name) + print("Org: " .. org.name) print("Parent: " .. parent) print("Members: " .. table.concat(org.members, ", ")) print("Children: " .. table.concat(children, ", ")) print("Modules: " .. table.concat(org_modules, ", ")) - print("Actions: " .. process_msg) + print("Pending: " .. process_msg) print() - print("Commands: (M)odules, (A)ctions") + print("Commands: (M)odules, (P)ending") local sel = io.read() print() @@ -114,7 +116,7 @@ function modpol.interactions.org_dashboard(user, org_name) elseif sel == 'a' or sel == 'A' then local processes = {} - print("All processes: (* indicates pending action)") + print("All processes: (* indicates pending)") for k,v in ipairs(org.processes) do local active = '' if org.pending[user] then @@ -135,7 +137,7 @@ function modpol.interactions.org_dashboard(user, org_name) end else print("Command not found") - modpol.interactions.org_dashboard(user, org_name) + modpol.interactions.org_dashboard(user, org.name) end end diff --git a/modpol_core/modules/add_child_org.lua b/modpol_core/modules/add_child_org_consent.lua similarity index 54% rename from modpol_core/modules/add_child_org.lua rename to modpol_core/modules/add_child_org_consent.lua index 933b087..e323e19 100644 --- a/modpol_core/modules/add_child_org.lua +++ b/modpol_core/modules/add_child_org_consent.lua @@ -1,20 +1,21 @@ ---- @module add_child_org +--- @module add_child_org_consent -- Adds a child org -- Depends on `consent` -local add_child_org = { - name = "Add child org", - slug = "add_child_org", - desc = "Create a child org within the current one with consent" +local add_child_org_consent = { + name = "Add child org (consent)", + slug = "add_child_org_consent", + desc = "Create a child org in the current one with member consent" } -add_child_org.data = { - child_name = "" +add_child_org_consent.data = { + child_name = "", + result = nil } -add_child_org.config = { +add_child_org_consent.config = { } -- @function initiate -function add_child_org:initiate(config, result) +function add_child_org_consent:initiate(result) modpol.interactions.text_query( self.initiator,"Child org name: ", function(input) @@ -22,8 +23,21 @@ function add_child_org:initiate(config, result) modpol.interactions.message( self.initiator, "No name entered for child org") + modpol.interactions.org_dashboard( + self.initiator, self.org.name) + self.org:delete_process(self.id) + if result then result() end + return + elseif modpol.orgs.get_org(input) then + modpol.interactions.message( + self.initiator, + "Org name already in use") + modpol.interactions.org_dashboard( + self.initiator, self.org.name) self.org:delete_process(self.id) - return end + if result then result() end + return + end self.data.child_name = input modpol.interactions.message( self.initiator, @@ -47,14 +61,15 @@ function add_child_org:initiate(config, result) ) end -function add_child_org:create_child_org() +function add_child_org_consent:create_child_org() self.org:add_org(self.data.child_name, self.initiator) modpol.interactions.message_org( self.initiator, self.org.name, "Child org created: "..self.data.child_name) + if self.data.result then self.data.result() end self.org:delete_process(self.id) end --- (Required) Add to module table -modpol.modules.add_child_org = add_child_org +modpol.modules.add_child_org_consent = add_child_org_consent diff --git a/modpol_core/modules/consent.lua b/modpol_core/modules/consent.lua index e936b39..78deca1 100644 --- a/modpol_core/modules/consent.lua +++ b/modpol_core/modules/consent.lua @@ -2,9 +2,10 @@ -- A utility module for checking consent local consent = { - name = "Consent", + name = "Consent process utility", slug = "consent", - desc = "Other modules can use to implement consent based decision making", + desc = "A module other modules use for consent decisions", + hide = true } consent.data = { @@ -46,6 +47,8 @@ function consent:callback(member) self.org:wipe_pending_actions(self.id) self.org:delete_process(self.id) end + modpol.interactions.org_dashboard( + member, self.org.name) end ) end diff --git a/modpol_core/modules/join_org_consent.lua b/modpol_core/modules/join_org_consent.lua index e40e023..9bfa437 100644 --- a/modpol_core/modules/join_org_consent.lua +++ b/modpol_core/modules/join_org_consent.lua @@ -3,21 +3,30 @@ -- Depends on the Consent module. local join_org_consent = { - name = "Join this org", + name = "Join this org (consent)", slug = "join_org_consent", - desc = "Adds member with consent of all members." + desc = "Adds member with consent of all members" } join_org_consent.data = { + result = nil } join_org_consent.config = { } -function join_org_consent:initiate(result) - self.org:call_module( - "consent", - self.initiator, +function join_org_consent:initiate(result) + if self.org:has_member(self.initiator) then + modpol.interactions.message( + self.initiator, + "You are already a member of this org") + if result then result() end + self.org:delete_process(self.id) + else + self.data.result = result + self.org:call_module( + "consent", + self.initiator, { prompt = "Allow " .. self.initiator .. " to join?", votes_required = #self.org.members @@ -26,12 +35,17 @@ function join_org_consent:initiate(result) self:complete() end ) - if result then result() end + end end function join_org_consent:complete() - self.org:add_member(self.initiator) - print("Added " .. self.initiator .. " to the org.") + self.org:add_member(self.initiator) + modpol.interactions.message_org( + self.initiator,self.org.name, + "Consent reached: " .. self.initiator .. + " joined org " .. self.org.name) + if self.data.result then self.data.result() end + self.org:delete_process(self.id) end modpol.modules.join_org_consent = join_org_consent diff --git a/modpol_core/modules/leave_org.lua b/modpol_core/modules/leave_org.lua index bbc0c42..d8e685d 100644 --- a/modpol_core/modules/leave_org.lua +++ b/modpol_core/modules/leave_org.lua @@ -4,7 +4,7 @@ local leave_org = { name = "Leave org", slug = "leave_org", - desc = "Leave this org" + desc = "Remove yourself from the current org" } leave_org.data = { @@ -19,9 +19,9 @@ leave_org.config = { -- @function initiate function leave_org:initiate(result) if self.org == modpol.instance then - modpol.interactions.message( - self.initiator, - "You cannot leave the root org") + modpol.interactions.message( + self.initiator, + "You cannot leave the root org") else self.org:remove_member(self.initiator) modpol.interactions.message_org( @@ -32,6 +32,7 @@ function leave_org:initiate(result) "You have left org " .. self.org.name) end if result then result() end + self.org:delete_process(self.id) end --- (Required) Add to module table diff --git a/modpol_core/modules/remove_child_consent.lua b/modpol_core/modules/remove_child_consent.lua new file mode 100644 index 0000000..3968b8b --- /dev/null +++ b/modpol_core/modules/remove_child_consent.lua @@ -0,0 +1,71 @@ +--- Remove child (consent) +-- A simple module that calls a consent process on an org to remove its child +-- Depends on the Consent module. + +local remove_child_consent = { + name = "Remove child (consent)", + slug = "remove_child_consent", + desc = "Removes a child org if all members of this org consent." +} + +remove_child_consent.data = { + result = nil, + child_to_remove = nil +} + +remove_child_consent.config = { +} + +function remove_child_consent:initiate(result) + local children = {} + for i,v in ipairs(self.org.children) do + local child = modpol.orgs.get_org(v) + if child then table.insert(children, child.name) end + end + -- Abort if no child orgs + if #children == 0 then + modpol.interactions.message( + self.initiator, + "Org has no children") + if result then result() end + self.org:delete_process(self.id) + else + self.data.result = result + modpol.interactions.dropdown_query( + self.initiator, + "Which child of org "..self.org.name.. + " do you want to remove?", + children, + function(input) + self.data.child_to_remove = modpol.orgs.get_org(input) + self.org:call_module( + "consent", + self.initiator, + { + prompt = "Remove child org "..input.."?", + votes_required = #self.org.members + }, + function() + self:complete() + end) + modpol.interactions.org_dashboard( + self.initiator, self.org.name) + end) + end +end + +function remove_child_consent:complete() + modpol.interactions.message_org( + self.initiator, self.data.child_to_remove.id, + "Removing org " .. self.data.child_to_remove.name .. + " by parent org consent") + modpol.interactions.message_org( + self.initiator, self.org.id, + "Consent reached: removing org " .. + self.data.child_to_remove.name) + self.data.child_to_remove:delete() + if self.data.result then self.data.result() end + self.org:delete_process(self.id) +end + +modpol.modules.remove_child_consent = remove_child_consent diff --git a/modpol_core/modules/remove_member_consent.lua b/modpol_core/modules/remove_member_consent.lua new file mode 100644 index 0000000..fc118e1 --- /dev/null +++ b/modpol_core/modules/remove_member_consent.lua @@ -0,0 +1,64 @@ +--- remove_member_consent +-- @module remove_member_consent + +local remove_member_consent = { + name = "Remove a member (consent)", + slug = "remove_member_consent", + desc = "Removes org member with consent of other members" +} + +remove_member_consent.data = { + member_to_remove = "", + result = nil +} + +remove_member_consent.config = { +} + +function remove_member_consent:initiate(result) + -- Abort if in root org + if self.org == modpol.instance then + modpol.interactions.message( + self.initiator, + "Members cannot be removed from the root org") + if result then result() end + self.org:delete_process(self.id) + else -- proceed if not root + self.data.result = result + modpol.interactions.dropdown_query( + self.initiator, + "Which member of org "..self.org.name.. + " do you want to remove?", + self.org.members, + function(input) + self.data.member_to_remove = input + self.org:call_module( + "consent", + self.initiator, + { + prompt = "Remove "..input.. + " from org "..self.org.name.."?", + votes_required = #self.org.members - 1 + }, + function() + self:complete() + end) + modpol.interactions.org_dashboard( + self.initiator, self.org.name) + end) + end +end + +function remove_member_consent:complete() + modpol.interactions.message_org( + self.initiator, self.org.id, + "Consent reached: removing ".. + self.data.member_to_remove.. + " from org "..self.org.name) + self.org:remove_member(self.data.member_to_remove) + self.org:delete_process(self.id) + if self.data.result then self.data.result() end +end + +--- (Required) Add to module table +modpol.modules.remove_member_consent = remove_member_consent diff --git a/modpol_core/modules/remove_org.lua b/modpol_core/modules/remove_org.lua index 23fc07e..b2118d1 100644 --- a/modpol_core/modules/remove_org.lua +++ b/modpol_core/modules/remove_org.lua @@ -18,7 +18,7 @@ function remove_org:initiate(result) if self.org == modpol.instance then modpol.interactions.message( self.initiator, - "You cannot remove the root org") + "Cannot remove the root org") else modpol.interactions.message_org( self.initiator,self.org.id, diff --git a/modpol_core/modules/remove_org_consent.lua b/modpol_core/modules/remove_org_consent.lua index d5f5973..f9e8513 100644 --- a/modpol_core/modules/remove_org_consent.lua +++ b/modpol_core/modules/remove_org_consent.lua @@ -3,19 +3,28 @@ -- Depends on the Consent module. local remove_org_consent = { - name = "Remove this org", + name = "Remove this org (consent)", slug = "remove_org_consent", desc = "Removes an org if all members consent." } remove_org_consent.data = { + result = nil } remove_org_consent.config = { } -function remove_org_consent:initiate(result) - self.org:call_module( +function remove_org_consent:initiate(result) + if self.org == modpol.instance then + modpol.interactions.message( + self.initiator, + "Cannot remove root org") + if result then result() end + self.org:delete_process(self.id) + else + self.data.result = result + self.org:call_module( "consent", self.initiator, { @@ -25,17 +34,19 @@ function remove_org_consent:initiate(result) function () self:complete() end - ) - modpol.interactions.org_dashboard( - self.initiator, self.org.name) - if result then result() end + ) + modpol.interactions.org_dashboard( + self.initiator, self.org.name) + end end function remove_org_consent:complete() modpol.interactions.message_org( self.initiator, self.org.id, - "Removing org: " .. self.org.name) - self.org:delete() + "Consent reached: removing org " .. self.org.name) + if self.data.result then self.data.result() end + self.org:delete_process(self.id) + self.org:delete() end modpol.modules.remove_org_consent = remove_org_consent diff --git a/modpol_core/modules/rename_org_consent.lua b/modpol_core/modules/rename_org_consent.lua new file mode 100644 index 0000000..1b9aa47 --- /dev/null +++ b/modpol_core/modules/rename_org_consent.lua @@ -0,0 +1,77 @@ +--- Rename org (consent) +-- A simple module that calls a consent process on an org to rename it. +-- Depends on the Consent module. + +local rename_org_consent = { + name = "Rename this org (consent)", + slug = "rename_org_consent", + desc = "Renames an org if all members consent." +} + +rename_org_consent.data = { + result = nil, + new_name = nil +} + +rename_org_consent.config = { +} + +function rename_org_consent:initiate(result) + modpol.interactions.text_query( + self.initiator,"New org name: ", + function(input) + if input == "" then + modpol.interactions.message( + self.initiator, + "No name entered for child org") + modpol.interactions.org_dashboard( + self.initiator, self.org.name) + self.org:delete_process(self.id) + if result then result() end + return + elseif modpol.orgs.get_org(input) then + modpol.interactions.message( + self.initiator, + "Org name already in use") + modpol.interactions.org_dashboard( + self.initiator, self.org.name) + self.org:delete_process(self.id) + if result then result() end + return + end + self.data.new_name = input + modpol.interactions.message( + self.initiator, + "Proposed to change name of org " .. + self.org.name .. " to " .. input) + -- initiate consent process + self.org:call_module( + "consent", + self.initiator, + { + prompt = "Change name of org " .. + self.org.name .. " to " .. input .. "?", + votes_required = #self.org.members + }, + function() + self:complete() + end + ) + modpol.interactions.org_dashboard( + self.initiator, self.org.name) + end + ) +end + +function rename_org_consent:complete() + modpol.interactions.message_org( + self.initiator, + self.org.name, + "Changing name of org " .. self.org.name .. + " to " .. self.data.new_name) + self.org.name = self.data.new_name + if self.data.result then self.data.result() end + self.org:delete_process(self.id) +end + +modpol.modules.rename_org_consent = rename_org_consent diff --git a/modpol_core/modules/template.lua b/modpol_core/modules/template.lua index 1232e52..ec20f7e 100644 --- a/modpol_core/modules/template.lua +++ b/modpol_core/modules/template.lua @@ -1,20 +1,22 @@ ---- module_template --- @module module_template +--- change_modules +-- @module change_modules --- (Required): data table containing name and description of the module -- @field name "Human-readable name" -- @field slug "Same as module class name" -- @field desc "Description of the module" -local module_template = { +-- @field hide "Whether this is a hidden utility module" +local change_modules = { name = "Module Human-Readable Name", slug = "template", - desc = "Description of the module" + desc = "Description of the module", + hide = false; } --- (Required) Data for module -- Variables that module uses during the course of a process -- Can be blank -module_template.data = { +change_modules.data = { } --- (Required): config for module @@ -25,7 +27,7 @@ module_template.data = { -- Default values set in config can be overridden -- @field field_1 ex: votes_required, default = 5 -- @field field_2 ex: voting_type, default = "majority" -module_template.config = { +change_modules.config = { field_1 = 5 field_2 = "majority" } @@ -37,12 +39,16 @@ module_template.config = { --
  • self.id (the process id of the module instance)
  • -- @param result (optional) Callback if this module is embedded in other modules -- @function initiate -function module_template:initiate(result) - -- call interaction functions here! +function change_modules:initiate(result) + -- call interaction functions here! - -- call result function - if result then result() end + -- concluding functions: + -- call these wherever process might end; + -- may need to put result in self.data.result + -- if process ends in another function + if result then result() end + self.org:delete_process(self.id) end --- (Required) Add to module table -modpol.modules.module_template = module_template +modpol.modules.change_modules = change_modules diff --git a/modpol_minetest/chatcommands.lua b/modpol_minetest/chatcommands.lua index 33c0ca6..103de31 100644 --- a/modpol_minetest/chatcommands.lua +++ b/modpol_minetest/chatcommands.lua @@ -15,10 +15,10 @@ regchat = function(name, command_table) end -- =================================================================== --- /modpol +-- /mp -- Presents a menu of options to users regchat( - "modpol", { + "mp", { privs = {}, func = function(user) modpol.interactions.dashboard(user) @@ -26,14 +26,17 @@ regchat( }) -- =================================================================== --- /reset --- For testing only +-- /mptest +-- For testing only, accessible to admin users -- Clears the system and recreates instance with all players +-- opens dashboard too for fun. regchat( - "reset", { - privs = {}, + "mptest", { + privs = {privs=true}, func = function(user) - modpol.orgs.reset(); + modpol.orgs.reset() + modpol.instance:add_member(user) + modpol.interactions.dashboard(user) return true, "Reset orgs" end, }) @@ -71,38 +74,3 @@ regchat( end }) --- =================================================================== --- /listplayers -regchat( - "listplayers", { - privs = {}, - func = function(user) - local result = table.concat(modpol.list_users(),", ") - return true, "All players: " .. result - end, -}) - --- =================================================================== --- /joinorg -regchat( - "joinorg", { - privs = {}, - func = function(user, param) - local org = modpol.orgs.get_org(param) - local success, result = org:add_member(user) - return true, result - end, -}) - - --- =================================================================== --- /pollself [question] --- asks the user a question specified in param -regchat( - "pollself", { - privs = {}, - func = function(user, param) - modpol.interactions.binary_poll_user(user, param) - return true, result - end, -}) diff --git a/modpol_minetest/overrides/interactions.lua b/modpol_minetest/overrides/interactions.lua index a4a5d2a..3000f15 100644 --- a/modpol_minetest/overrides/interactions.lua +++ b/modpol_minetest/overrides/interactions.lua @@ -51,15 +51,15 @@ function modpol.interactions.dashboard(user) -- set up formspec local formspec = { "formspec_version[4]", - "size[10,8]", - "label[0.5,0.5;MODPOL DASHBOARD]", + "size[10,6]", + "label[0.5,0.5;M O D U L A R P O L I T I C S]", "label[0.5,2;All orgs:]", "dropdown[2,1.5;5,0.8;all_orgs;"..formspec_list(all_orgs)..";;]", "label[0.5,3;Your orgs:]", "dropdown[2,2.5;5,0.8;user_orgs;"..formspec_list(user_orgs)..";;]", "label[0.5,4;All users:]", "dropdown[2,3.5;5,0.8;all_users;"..formspec_list(all_users)..";;]", - "button_exit[8.5,7;1,0.8;close;Close]", + "button_exit[8.5,5;1,0.8;close;Close]", } local formspec_string = table.concat(formspec, "") -- present to player @@ -85,11 +85,11 @@ end) -- Function: modpol.interactions.org_dashboard --- Params: user (string), org_name (string) +-- Params: user (string), org_string (string or num) -- Output: Displays a menu of org-specific commands to the user -function modpol.interactions.org_dashboard(user, org_name) +function modpol.interactions.org_dashboard(user, org_string) -- prepare data - local org = modpol.orgs.get_org(org_name) + local org = modpol.orgs.get_org(org_string) if not org then return nil end local function membership_toggle(org_display) @@ -98,9 +98,8 @@ function modpol.interactions.org_dashboard(user, org_name) if current_org:has_member(user) then return " (member)" end - else - return "" end + return "" end -- identify parent @@ -119,32 +118,36 @@ function modpol.interactions.org_dashboard(user, org_name) local modules = {"View..."} if org.modules then for k,v in pairs(org.modules) do - table.insert(modules, v.slug) + if not v.hide then -- hide utility modules + local module_entry = v.name.. + " ["..v.slug.."]" + table.insert(modules, module_entry) + end end end - -- prepare actions menu - local actions = {"View..."} - local num_actions = 0 + -- prepare pending menu + local pending = {"View..."} + local num_pending = 0 if org.pending[user] then for k,v in pairs(org.pending[user]) do - local action_string = "[" .. k .. "] " .. - org.processes[k].name - table.insert(actions, action_string) - num_actions = num_actions + 1 + local pending_string = org.processes[k].name + .." ["..k.."]" + table.insert(pending, pending_string) + num_pending = num_pending + 1 end end -- set player context local user_context = {} - user_context["current_org"] = org_name + user_context["current_org"] = org.name _contexts[user] = user_context -- set up formspec local formspec = { "formspec_version[4]", "size[10,8]", "label[0.5,0.5;Org: ".. - minetest.formspec_escape(org_name)..membership_toggle(org_name).."]", + minetest.formspec_escape(org.name)..membership_toggle(org.name).."]", "label[0.5,1;Parent: "..parent..membership_toggle(parent).."]", "label[0.5,2;Members:]", "dropdown[2,1.5;5,0.8;user_orgs;"..formspec_list(org.members)..";;]", @@ -152,8 +155,8 @@ function modpol.interactions.org_dashboard(user, org_name) "dropdown[2,2.5;5,0.8;children;"..formspec_list(children)..";;]", "label[0.5,4;Modules:]", "dropdown[2,3.5;5,0.8;modules;"..formspec_list(modules)..";;]", - "label[0.5,5;Actions ("..num_actions.."):]", - "dropdown[2,4.5;5,0.8;actions;"..formspec_list(actions)..";;]", + "label[0.5,5;Pending ("..num_pending.."):]", + "dropdown[2,4.5;5,0.8;pending;"..formspec_list(pending)..";;]", "button[8.5,7;1,0.8;back;Back]", } local formspec_string = table.concat(formspec, "") @@ -179,7 +182,8 @@ minetest.register_on_player_receive_fields(function (player, formname, fields) -- Receiving modules elseif fields.modules and fields.modules ~= "View..." then - local module = fields.modules + local module = string.match( + fields.modules,"%[(.*)%]") modpol.interactions.binary_poll_user( pname, modpol.modules[module].name.."\n".. @@ -191,12 +195,12 @@ minetest.register_on_player_receive_fields(function (player, formname, fields) end end) - -- Receiving actions - elseif fields.actions - and fields.actions ~= "View..." then - local action = string.match( - fields.actions,"%[(%d)%]") - local process = org.processes[tonumber(action)] + -- Receiving pending + elseif fields.pending + and fields.pending ~= "View..." then + local pending = string.match( + fields.pending,"%[(%d)%]") + local process = org.processes[tonumber(pending)] if process then org:interact(process.id, pname) end