api.lua 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. -- AWARDS
  2. --
  3. -- Copyright (C) 2013-2015 rubenwardy
  4. -- This program is free software; you can redistribute it and/or modify
  5. -- it under the terms of the GNU Lesser General Public License as published by
  6. -- the Free Software Foundation; either version 2.1 of the License, or
  7. -- (at your option) any later version.
  8. -- This program is distributed in the hope that it will be useful,
  9. -- but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. -- GNU Lesser General Public License for more details.
  12. -- You should have received a copy of the GNU Lesser General Public License along
  13. -- with this program; if not, write to the Free Software Foundation, Inc.,
  14. -- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  15. --
  16. local S = lottachievements.gettext
  17. dofile(minetest.get_modpath("lottachievements").."/api_helpers.lua")
  18. -- Table Save Load Functions
  19. function lottachievements.save()
  20. local file = io.open(minetest.get_worldpath().."/lottachievements.txt", "w")
  21. if file then
  22. file:write(minetest.serialize(lottachievements.players))
  23. file:close()
  24. end
  25. end
  26. function lottachievements.init()
  27. lottachievements.players = lottachievements.load()
  28. lottachievements.def = {}
  29. lottachievements.trigger_types = {}
  30. lottachievements.on = {}
  31. lottachievements.on_unlock = {}
  32. end
  33. function lottachievements.load()
  34. local file = io.open(minetest.get_worldpath().."/lottachievements.txt", "r")
  35. if file then
  36. local table = minetest.deserialize(file:read("*all"))
  37. if type(table) == "table" then
  38. return table
  39. end
  40. end
  41. return {}
  42. end
  43. function lottachievements.register_trigger(name, func)
  44. lottachievements.trigger_types[name] = func
  45. lottachievements.on[name] = {}
  46. lottachievements['register_on_'..name] = function(func)
  47. table.insert(lottachievements.on[name], func)
  48. end
  49. end
  50. function lottachievements.run_trigger_callbacks(player, data, trigger, table_func)
  51. for i = 1, #lottachievements.on[trigger] do
  52. local res = nil
  53. local entry = lottachievements.on[trigger][i]
  54. if type(entry) == "function" then
  55. res = entry(player, data)
  56. elseif type(entry) == "table" and entry.award then
  57. res = table_func(entry)
  58. end
  59. if res then
  60. lottachievements.unlock(player:get_player_name(), res)
  61. end
  62. end
  63. end
  64. function lottachievements.increment_item_counter(data, field, itemname, count)
  65. local name_split = string.split(itemname, ":")
  66. if #name_split ~= 2 then
  67. return false
  68. end
  69. local mod = name_split[1]
  70. local item = name_split[2]
  71. if data and field and mod and item then
  72. lottachievements.assertPlayer(data)
  73. lottachievements.tbv(data, field)
  74. lottachievements.tbv(data[field], mod)
  75. lottachievements.tbv(data[field][mod], item, 0)
  76. if data[field][mod][item] + (count or 1) < 0 then
  77. count = 0
  78. end
  79. data[field][mod][item] = data[field][mod][item] + (count or 1)
  80. return true
  81. else
  82. return false
  83. end
  84. end
  85. function lottachievements.get_item_count(data, field, itemname)
  86. local name_split = string.split(itemname, ":")
  87. if #name_split ~= 2 then
  88. return false
  89. end
  90. local mod = name_split[1]
  91. local item = name_split[2]
  92. if data and field and mod and item then
  93. lottachievements.assertPlayer(data)
  94. lottachievements.tbv(data, field)
  95. lottachievements.tbv(data[field], mod)
  96. lottachievements.tbv(data[field][mod], item, 0)
  97. return data[field][mod][item]
  98. end
  99. end
  100. function lottachievements.get_total_item_count(data, field)
  101. local i = 0
  102. if data and field then
  103. lottachievements.assertPlayer(data)
  104. lottachievements.tbv(data, field)
  105. for mod,_ in pairs(data[field]) do
  106. lottachievements.tbv(data[field], mod)
  107. for item,_ in pairs(data[field][mod]) do
  108. lottachievements.tbv(data[field][mod], item, 0)
  109. i = i + data[field][mod][item]
  110. end
  111. end
  112. end
  113. return i
  114. end
  115. function lottachievements.register_on_unlock(func)
  116. table.insert(lottachievements.on_unlock, func)
  117. end
  118. -- API Functions
  119. function lottachievements._additional_triggers(name, def)
  120. -- Depreciated!
  121. end
  122. function lottachievements.register_achievement(name, def)
  123. def.name = name
  124. -- Add Triggers
  125. if def.trigger and def.trigger.type then
  126. local func = lottachievements.trigger_types[def.trigger.type]
  127. if func then
  128. func(def)
  129. else
  130. lottachievements._additional_triggers(name, def)
  131. end
  132. end
  133. -- Add Award
  134. lottachievements.def[name] = def
  135. local tdef = lottachievements.def[name]
  136. if def.description == nil and tdef.getDefaultDescription then
  137. def.description = tdef:getDefaultDescription()
  138. end
  139. end
  140. function lottachievements.enable(name)
  141. local data = lottachievements.player(name)
  142. if data then
  143. data.disabled = nil
  144. end
  145. end
  146. function lottachievements.disable(name)
  147. local data = lottachievements.player(name)
  148. if data then
  149. data.disabled = true
  150. end
  151. end
  152. function lottachievements.clear_player(name)
  153. lottachievements.players[name] = {}
  154. end
  155. -- This function is called whenever a target condition is met.
  156. -- It checks if a player already has that achievement, and if they do not,
  157. -- it gives it to them
  158. ----------------------------------------------
  159. --lottachievements.unlock(name, award)
  160. -- name - the name of the player
  161. -- award - the name of the award to give
  162. function lottachievements.unlock(name, award)
  163. -- Access Player Data
  164. local data = lottachievements.players[name]
  165. local awdef = lottachievements.def[award]
  166. -- Perform checks
  167. if not data then
  168. return
  169. end
  170. if not awdef then
  171. return
  172. end
  173. if data.disabled then
  174. return
  175. end
  176. lottachievements.tbv(data,"unlocked")
  177. -- Don't give the achievement if it has already been given
  178. if data.unlocked[award] and data.unlocked[award] == award then
  179. return
  180. end
  181. -- Unlock Award
  182. minetest.log("action", name.." has unlocked award "..name)
  183. data.unlocked[award] = award
  184. lottachievements.save()
  185. -- Give Prizes
  186. if awdef and awdef.prizes then
  187. for i = 1, #awdef.prizes do
  188. local itemstack = ItemStack(awdef.prizes[i])
  189. if not itemstack:is_empty() then
  190. local receiverref = minetest.get_player_by_name(name)
  191. if receiverref then
  192. receiverref:get_inventory():add_item("main", itemstack)
  193. end
  194. end
  195. end
  196. end
  197. -- Run callbacks
  198. if awdef.on_unlock and awdef.on_unlock(name, awdef) then
  199. return
  200. end
  201. for _, callback in pairs(lottachievements.on_unlock) do
  202. if callback(name, awdef) then
  203. return
  204. end
  205. end
  206. -- Get Notification Settings
  207. local title = awdef.title or award
  208. local desc = awdef.description or ""
  209. local background = awdef.background or "lottachievements_bg_default.png"
  210. local icon = awdef.icon or "lottachievements_unknown.png"
  211. local sound = awdef.sound
  212. if sound == nil then
  213. -- Explicit check for nil because sound could be `false` to disable it
  214. sound = {name="lottachievements_got_generic", gain=0.25}
  215. end
  216. local custom_announce = awdef.custom_announce
  217. if not custom_announce then
  218. if awdef.secret then
  219. custom_announce = S("Secret Achievement Unlocked:")
  220. else
  221. custom_announce = S("Achievement Unlocked:")
  222. end
  223. end
  224. -- Do Notification
  225. if sound then
  226. -- Enforce sound delay to prevent sound spamming
  227. local lastsound = lottachievements.players[name].lastsound
  228. if lastsound == nil or os.difftime(os.time(), lastsound) >= 1 then
  229. minetest.sound_play(sound, {to_player=name})
  230. lottachievements.players[name].lastsound = os.time()
  231. end
  232. end
  233. if lottachievements.show_mode == "formspec" then
  234. -- use a formspec to send it
  235. minetest.show_formspec(name, "achievements:unlocked", "size[6,2]"..
  236. "image_button_exit[0,0;6,2;"..background..";close1; ]"..
  237. "image_button_exit[0.2,0.8;1,1;"..icon..";close2; ]"..
  238. "label[1.1,1;"..title.."]"..
  239. "label[0.3,0.1;"..custom_announce.."]")
  240. elseif lottachievements.show_mode == "chat" then
  241. local chat_announce
  242. if awdef.secret == true then
  243. chat_announce = S("Secret Achievement Unlocked: %s")
  244. else
  245. chat_announce = S("Achievement Unlocked: %s")
  246. end
  247. -- use the chat console to send it
  248. minetest.chat_send_player(name, string.format(chat_announce, title))
  249. if desc~="" then
  250. minetest.chat_send_player(name, desc)
  251. end
  252. else
  253. local player = minetest.get_player_by_name(name)
  254. local one = player:hud_add({
  255. hud_elem_type = "image",
  256. name = "award_bg",
  257. scale = {x = 2, y = 1},
  258. text = background,
  259. position = {x = 0.5, y = 0},
  260. offset = {x = 0, y = 138},
  261. alignment = {x = 0, y = -1}
  262. })
  263. local hud_announce
  264. if awdef.secret == true then
  265. hud_announce = S("Secret Achievement Unlocked!")
  266. else
  267. hud_announce = S("Achievement Unlocked!")
  268. end
  269. local two = player:hud_add({
  270. hud_elem_type = "text",
  271. name = "award_au",
  272. number = 0xFFFFFF,
  273. scale = {x = 100, y = 20},
  274. text = hud_announce,
  275. position = {x = 0.5, y = 0},
  276. offset = {x = 0, y = 40},
  277. alignment = {x = 0, y = -1}
  278. })
  279. local three = player:hud_add({
  280. hud_elem_type = "text",
  281. name = "award_title",
  282. number = 0xFFFFFF,
  283. scale = {x = 100, y = 20},
  284. text = title,
  285. position = {x = 0.5, y = 0},
  286. offset = {x = 30, y = 100},
  287. alignment = {x = 0, y = -1}
  288. })
  289. local four = player:hud_add({
  290. hud_elem_type = "image",
  291. name = "award_icon",
  292. scale = {x = 4, y = 4},
  293. text = icon,
  294. position = {x = 0.4, y = 0},
  295. offset = {x = -81.5, y = 126},
  296. alignment = {x = 0, y = -1}
  297. })
  298. minetest.after(4, function()
  299. player:hud_remove(one)
  300. player:hud_remove(two)
  301. player:hud_remove(three)
  302. player:hud_remove(four)
  303. end)
  304. end
  305. end
  306. -- Backwards compatibility
  307. lottachievements.give_achievement = lottachievements.unlock
  308. --[[minetest.register_chatcommand("gawd", {
  309. params = "award name",
  310. description = "gawd: give award to self",
  311. func = function(name, param)
  312. lottachievements.unlock(name,param)
  313. end
  314. })]]--
  315. function lottachievements.getFormspec(name, to, sid)
  316. local formspec = ""
  317. local listoflottachievements = lottachievements._order_lottachievements(name)
  318. local completed = lottachievements.completed_achievements(name)
  319. local playerdata = lottachievements.players[name]
  320. if #listoflottachievements == 0 then
  321. formspec = formspec .. "label[3.9,1.5;"..minetest.formspec_escape(S("Error: No lottachievements available.")).."]"
  322. formspec = formspec .. "button_exit[4.2,2.3;3,1;close;"..minetest.formspec_escape(S("OK")).."]"
  323. return formspec
  324. end
  325. -- Sidebar
  326. if sid then
  327. local item = listoflottachievements[sid+0]
  328. local def = lottachievements.def[item.name]
  329. if def and def.secret and not item.got then
  330. formspec = formspec .. "label[0.64,2.75;"..
  331. minetest.formspec_escape(minetest.colorize("black", S("(Secret Achievement)"))).."]"..
  332. "image[1,0;3,3;lottachievements_unknown.png]"
  333. if def and def.description then
  334. local text = minetest.colorize("black",
  335. minetest.wrap_text(S("Unlock this achievement to" ..
  336. " find out what it is."), 25))
  337. formspec = formspec .. "label[0.15,3.25;"..
  338. minetest.formspec_escape(text).."]"
  339. end
  340. elseif def and def.requires and not item.got and not completed[def.requires] then
  341. formspec = formspec .. "label[0.56,2.75;"..
  342. minetest.formspec_escape(minetest.colorize("black", S("(Hidden Achievement)"))).."]"..
  343. "image[1,0;3,3;lottachievements_unknown.png]"
  344. if def and def.description and lottachievements.def[def.requires] then
  345. if lottachievements.def[def.requires].requires and
  346. not completed[lottachievements.def[def.requires].requires] then
  347. local text = minetest.colorize("black",
  348. minetest.wrap_text(S("To see this achievement you" ..
  349. "need to complete more achievements!"), 25))
  350. formspec = formspec .. "label[0.15,3.25;"..
  351. minetest.formspec_escape(text) .. "]"
  352. else
  353. local text = minetest.colorize("black",
  354. minetest.wrap_text(S("To see this achievement, complete \"")
  355. .. lottachievements.def[def.requires].title .. "\"", 25))
  356. formspec = formspec .. "label[0.15,3.25;" ..
  357. minetest.formspec_escape(text) .. "]"
  358. end
  359. end
  360. else
  361. local title = item.name
  362. if def and def.title then
  363. title = def.title
  364. end
  365. local status = ""
  366. if item.got then
  367. status = minetest.colorize("green", S("(completed)"))
  368. end
  369. formspec = formspec .. "label[0.15,2.7;" ..
  370. minetest.formspec_escape(minetest.colorize("black", title)) .. "\n" .. status .. "]"
  371. if def and def.icon then
  372. formspec = formspec .. "image[1,0;3,3;" .. def.icon .. "]"
  373. end
  374. local barwidth = 4.6
  375. local perc = nil
  376. local label = nil
  377. if def.getProgress and playerdata then
  378. local res = def:getProgress(playerdata)
  379. perc = res.perc
  380. label = res.label
  381. end
  382. if perc then
  383. if perc > 1 then
  384. perc = 1
  385. end
  386. formspec = formspec .. "background[0,5.675;" .. barwidth ..",0.425;lottachievements_progress_gray.png;false]"
  387. formspec = formspec .. "background[0,5.675;" .. (barwidth * perc) ..",0.425;lottachievements_progress_green.png;false]"
  388. if label then
  389. formspec = formspec .. "label[1.75,5.6;" .. minetest.formspec_escape(label) .. "]"
  390. end
  391. end
  392. if def and def.description then
  393. local text = minetest.colorize("black", minetest.wrap_text(def.description, 25))
  394. formspec = formspec .. "label[0.15,3.75;" .. text .."]"
  395. end
  396. end
  397. end
  398. -- Create list box
  399. formspec = formspec .. "textlist[4.75,0;6,6;lottachievements;"
  400. local first = true
  401. for _,award in pairs(listoflottachievements) do
  402. local def = lottachievements.def[award.name]
  403. if def then
  404. if not first then
  405. formspec = formspec .. ","
  406. end
  407. first = false
  408. if def.secret and not award.got then
  409. formspec = formspec .. "#808080" ..minetest.formspec_escape(S("(Secret Achievement)"))
  410. elseif def.requires and not award.got and not completed[def.requires] then
  411. formspec = formspec .. "#ACACAC" ..minetest.formspec_escape(S("(Hidden Achievement)"))
  412. else
  413. local title = award.name
  414. if def and def.title then
  415. title = def.title
  416. end
  417. if award.got then
  418. formspec = formspec .. "#00CC00" ..minetest.formspec_escape(title)
  419. else
  420. formspec = formspec .. minetest.formspec_escape(title)
  421. end
  422. end
  423. end
  424. end
  425. return formspec .. ";"..sid.."]"
  426. end
  427. function lottachievements.show_to(name, to, sid, text)
  428. if name == "" or name == nil then
  429. name = to
  430. end
  431. if name == to and lottachievements.player(to).disabled then
  432. minetest.chat_send_player(S("You've disabled lottachievements. Type /lottachievements enable to reenable."))
  433. return
  434. end
  435. if text then
  436. local listoflottachievements = lottachievements._order_lottachievements(name)
  437. if #listoflottachievements == 0 then
  438. minetest.chat_send_player(to, S("Error: No lottachievements available."))
  439. return
  440. elseif not lottachievements.players[name] or not lottachievements.players[name].unlocked then
  441. minetest.chat_send_player(to, S("You have not unlocked any lottachievements."))
  442. return
  443. end
  444. minetest.chat_send_player(to, string.format(S("%s’s lottachievements:"), name))
  445. for _, str in pairs(lottachievements.players[name].unlocked) do
  446. local def = lottachievements.def[str]
  447. if def then
  448. if def.title then
  449. if def.description then
  450. minetest.chat_send_player(to, string.format(S("%s: %s"), def.title, def.description))
  451. else
  452. minetest.chat_send_player(to, def.title)
  453. end
  454. else
  455. minetest.chat_send_player(to, str)
  456. end
  457. end
  458. end
  459. else
  460. if sid == nil or sid < 1 then
  461. sid = 1
  462. end
  463. -- Show formspec to user
  464. minetest.show_formspec(to,"lottachievements:lottachievements",
  465. "size[11,6]" ..
  466. "background[5,5;1,1;gui_formbg.png;true]" ..
  467. lottachievements.getFormspec(name, to, sid))
  468. end
  469. end
  470. lottachievements.showto = lottachievements.show_to
  471. minetest.register_on_player_receive_fields(function(player, formname, fields)
  472. if formname ~= "lottachievements:lottachievements" then
  473. return false
  474. end
  475. if fields.quit then
  476. return true
  477. end
  478. local name = player:get_player_name()
  479. if fields.lottachievements then
  480. local event = minetest.explode_textlist_event(fields.lottachievements)
  481. if event.type == "CHG" then
  482. lottachievements.show_to(name, name, event.index, false)
  483. end
  484. end
  485. return true
  486. end)
  487. lottachievements.init()
  488. minetest.register_on_newplayer(function(player)
  489. local playern = player:get_player_name()
  490. lottachievements.assertPlayer(playern)
  491. end)
  492. minetest.register_on_shutdown(function()
  493. lottachievements.save()
  494. end)