interactions.lua 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. -- INTERACTIONS.LUA (for Minetest)
  2. -- CONTEXTUAL STUFF
  3. -- ================
  4. -- First, set up contexts to enable passing across formspecs
  5. -- https://rubenwardy.com/minetest_modding_book/en/players/formspecs.html#contexts
  6. local _contexts = {}
  7. local function get_context(name)
  8. local context = _contexts[name] or {}
  9. _contexts[name] = context
  10. return context
  11. end
  12. minetest.register_on_leaveplayer(function(player)
  13. _contexts[player:get_player_name()] = nil
  14. end)
  15. -- table of formspec field responses
  16. local formspec_fields = {}
  17. -- UTILITIES
  18. -- =========
  19. -- Function: formspec_list
  20. -- for use generating option lists in formspecs from tables
  21. -- input: table of strings
  22. -- output: a formspec-ready list of the strings
  23. local function formspec_list(array)
  24. local escaped = {}
  25. for i = 1, #array do
  26. escaped[i] = minetest.formspec_escape(array[i])
  27. end
  28. return table.concat(escaped,",")
  29. end
  30. -- DASHBOARDS
  31. -- ==========
  32. -- Function: modpol.interactions.dashboard(user)
  33. -- Params: user (string)
  34. -- Q: Should this return a menu of commands relevant to the specific user?
  35. -- Output: Displays a menu of commands to the user
  36. -- TODO currently a manually curated list---needs major improvement
  37. function modpol.interactions.dashboard(user)
  38. -- prepare data
  39. -- to add: nested orgs map
  40. local all_orgs = modpol.orgs.list_all()
  41. local user_orgs = modpol.orgs.user_orgs(user)
  42. local all_users = modpol.list_users()
  43. -- set up formspec
  44. local formspec = {
  45. "formspec_version[4]",
  46. "size[10,8]",
  47. "label[0.5,0.5;MODULAR POLITICS]",
  48. "label[0.5,2;All orgs:]",
  49. "dropdown[2,1.5;5,0.8;all_orgs;"..formspec_list(all_orgs)..";;]",
  50. "label[0.5,3;Your orgs:]",
  51. "dropdown[2,2.5;5,0.8;user_orgs;"..formspec_list(user_orgs)..";;]",
  52. "label[0.5,4;All users:]",
  53. "dropdown[2,3.5;5,0.8;all_users;"..formspec_list(all_users)..";;]",
  54. "label[0.5,5;Processes:]",
  55. "dropdown[2,4.5;5,0.8;processes;TBA;;]",
  56. "button[0.5,7;1,0.8;test_poll;Test poll]",
  57. "button[2,7;1,0.8;add_org;Add org]",
  58. "button[3.5,7;1.5,0.8;remove_org;Remove org]",
  59. "button_exit[8.5,7;1,0.8;close;Close]",
  60. }
  61. local formspec_string = table.concat(formspec, "")
  62. -- present to player
  63. minetest.show_formspec(user, "modpol:dashboard", formspec_string)
  64. end
  65. -- receive input
  66. minetest.register_on_player_receive_fields(function (player, formname, fields)
  67. if formname == "modpol:dashboard" then
  68. local pname = player:get_player_name()
  69. if fields.test_poll then
  70. modpol.interactions.binary_poll_org(pname, 1, "Poll question (yes/no):")
  71. elseif fields.add_org then
  72. modpol.interactions.add_org(pname, 1)
  73. elseif fields.remove_org then
  74. modpol.interactions.remove_org(pname)
  75. end
  76. end
  77. end)
  78. -- Function: modpol.interactions.org_dashboard
  79. -- Params: user (string), org_name (string)
  80. -- Output: Displays a menu of org-specific commands to the user
  81. function modpol.interactions.org_dashboard(user, org_name)
  82. -- prepare data
  83. local org = modpol.orgs.get_org(org_name)
  84. if not org then return nil end
  85. -- set player context
  86. _contexts[user][current_org] = org
  87. -- set up formspec
  88. local formspec = {
  89. "formspec_version[4]",
  90. "size[10,8]",
  91. "label[0.5,0.5;Org: "..
  92. minetest.formspec_escape(org_name).."]",
  93. "label[0.5,2;Members:]",
  94. "dropdown[2,1.5;5,0.8;user_orgs;"..formspec_list(org.members)..";;]",
  95. "label[0.5,3;Children:]",
  96. "dropdown[2,2.5;5,0.8;children;"..formspec_list(org.children)..";;]",
  97. "label[0.5,4;Policies:]",
  98. "dropdown[2,3.5;5,0.8;policies;"..formspec_list(org.policies)..";;]",
  99. "label[0.5,5;Processes:]",
  100. "dropdown[2,4.5;5,0.8;processes;"..formspec_list(org.processes)..";;]",
  101. "button[0.5,7;1,0.8;test_poll;Test poll]",
  102. "button[2,7;1,0.8;add_org;Add child]",
  103. "button[3.5,7;1.5,0.8;remove_org;Remove org]",
  104. "button[8.5,7;1,0.8;back;Back]",
  105. }
  106. local formspec_string = table.concat(formspec, "")
  107. -- present to player
  108. minetest.show_formspec(user, "modpol:org_dashboard", formspec_string)
  109. end
  110. -- receive input
  111. minetest.register_on_player_receive_fields(function (player, formname, fields)
  112. if formname == "modpol:org_dashboard" then
  113. local pname = player:get_player_name()
  114. if fields.test_poll then
  115. modpol.interactions.binary_poll_org(pname, _contexts.pname.current_org.id, "Poll question (yes/no):")
  116. elseif fields.add_org then
  117. modpol.interactions.add_org(pname, _contexts.pname.current_org.id)
  118. elseif fields.remove_org then
  119. modpol.interactions.remove_org(pname)
  120. elseif fields.back then
  121. modpol.interactions.dashboard(pname)
  122. end
  123. end
  124. end)
  125. -- BASIC INTERACTION FUNCTIONS
  126. -- ===========================
  127. -- Function: modpol.interactions.message
  128. -- input: message (string)
  129. -- output
  130. modpol.interactions.message = function(user, message)
  131. minetest.chat_send_player(user, message)
  132. end
  133. -- Function: modpol.interactions.text_query
  134. -- Overrides function at modpol/interactions.lua
  135. -- input: Query (string), User (string)
  136. -- output: User response (string)
  137. -- TODO Need to switch "user" to index not name
  138. function modpol.interactions.text_query(user, query)
  139. -- set up formspec
  140. local formspec = {
  141. "formspec_version[4]",
  142. "size[10,4]",
  143. "label[0.5,1;", minetest.formspec_escape(query), "]",
  144. "field[0.5,1.25;9,0.8;input;;]",
  145. "button[0.5,2.5;1,0.8;yes;OK]",
  146. }
  147. local formspec_string = table.concat(formspec, "")
  148. -- present to players
  149. minetest.show_formspec(user, "modpol:text_query", formspec_string)
  150. end
  151. -- receive fields
  152. minetest.register_on_player_receive_fields(function (player, formname, fields)
  153. if formname == "modpol:text_query" then
  154. local pname = player:get_player_name()
  155. if _contexts[pname] then
  156. _contexts[pname](fields.input)
  157. end
  158. minetest.close_formspec(pname, formname)
  159. end
  160. end)
  161. -- Function: dropdown_query
  162. -- input: user (string), label (string), options (table of strings)
  163. function modpol.interactions.dropdown_query(user, label, options)
  164. -- set up formspec
  165. local formspec = {
  166. "formspec_version[4]",
  167. "size[10,4]",
  168. "label[0.5,1;"..minetest.formspec_escape(label).."]",
  169. "dropdown[0.5,1.25;9,0.8;input;"..formspec_list(options)..";;]",
  170. "button[0.5,2.5;1,0.8;yes;OK]",
  171. }
  172. local formspec_string = table.concat(formspec, "")
  173. -- present to players
  174. minetest.show_formspec(user, "modpol:dropdown_query", formspec_string)
  175. end
  176. -- receive fields
  177. minetest.register_on_player_receive_fields(function (player, formname, fields)
  178. if formname == "modpol:dropdown_query" then
  179. local pname = player:get_player_name()
  180. if _contexts[pname] then
  181. _contexts[pname](fields.input)
  182. end
  183. minetest.close_formspec(pname, formname)
  184. end
  185. end)
  186. -- SECONDARY INTERACTIONS
  187. -- ======================
  188. -- Function: modpol.binary_poll_user(user, question)
  189. -- Overrides function at modpol/interactions.lua
  190. -- presents a yes/no poll to a user, returns answer
  191. --
  192. function modpol.interactions.binary_poll_user(user, question)
  193. -- set up formspec
  194. local text = "Poll: " .. question
  195. local formspec = {
  196. "formspec_version[4]",
  197. "size[5,3]",
  198. "label[0.375,0.5;", minetest.formspec_escape(text), "]",
  199. "button[1,1.5;1,0.8;yes;Yes]",
  200. "button[2,1.5;1,0.8;no;No]",
  201. --TKTK can we enable text wrapping?
  202. --TKTK we could use scroll boxes to contain the text
  203. }
  204. local formspec_string = table.concat(formspec, "")
  205. -- present to player
  206. minetest.show_formspec(user, "modpol:binary_poll_user", formspec_string)
  207. end
  208. minetest.register_on_player_receive_fields(function (player, formname, fields)
  209. local pname = player:get_player_name()
  210. -- modpol:binary_poll
  211. if formname == "modpol:binary_poll_user" then
  212. local vote = nil
  213. if fields.yes then vote = fields.yes
  214. elseif fields.no then vote = fields.no
  215. elseif fields.abstain then vote = fields.abstain
  216. end
  217. if vote then
  218. modpol.interactions.message(pname, "Vote recorded")
  219. minetest.chat_send_all(pname .. " voted " .. vote)
  220. --TODO : we should send the message to all in that org, not to all players
  221. end
  222. minetest.close_formspec(pname, formname)
  223. return vote
  224. end
  225. end)
  226. -- COMPLEX INTERACTIONS
  227. -- ====================
  228. -- Function: modpol.interactions.binary_poll_org
  229. -- input: initator (user string), org (number)
  230. -- output: interaction begins
  231. function modpol.interactions.binary_poll_org(initiator, org)
  232. -- start formspec
  233. modpol.interactions.text_query(initiator, "Poll question (yes/no):")
  234. -- set user's context to followup function
  235. _contexts[initiator] =
  236. function(input)
  237. local users = modpol.list_users()
  238. for k,v in ipairs(users) do
  239. modpol.interactions.binary_poll_user(v, input)
  240. end
  241. _contexts[initiator] = nil
  242. end
  243. end
  244. -- Function: modpol.interactions.add_org
  245. -- input: initator (user string), base_org_id (ID)
  246. -- output: interaction begins
  247. function modpol.interactions.add_org(initiator, base_org_id)
  248. -- start formspec
  249. modpol.interactions.text_query(initiator, "Org name:")
  250. -- set user's context to followup function
  251. _contexts[initiator] = function(input)
  252. if input then
  253. local base_org = modpol.orgs.get_org(base_org_id)
  254. local result = base_org:add_org(input)
  255. if result then
  256. local message = input .. " created"
  257. modpol.interactions.message(initiator, message)
  258. end
  259. end
  260. _contexts[initiator] = nil
  261. modpol.interactions.dashboard(initiator)
  262. end
  263. end
  264. -- Function: modpol.interactions.remove_org
  265. -- input: initator (user string)
  266. -- output: interaction begins
  267. function modpol.interactions.remove_org(initiator)
  268. -- start formspec
  269. local orgs_list = modpol.orgs.list_all()
  270. local label = "Choose an org to remove:"
  271. modpol.interactions.dropdown_query(initiator, label, orgs_list)
  272. -- set user's context to followup function
  273. _contexts[initiator] = function(input)
  274. if input then
  275. local target_org = modpol.orgs.get_org(input)
  276. local result = target_org:delete()
  277. if result then
  278. local message = input .. " deleted"
  279. modpol.interactions.message(initiator, message)
  280. end
  281. end
  282. _contexts[initiator] = nil
  283. modpol.interactions.dashboard(initiator)
  284. end
  285. end