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.
17 -- The global award namespace
21 dofile(minetest
.get_modpath("awards").."/api_helpers.lua")
23 -- Table Save Load Functions
24 function awards
.save()
25 local file
= io
.open(minetest
.get_worldpath().."/awards.txt", "w")
27 file
:write(minetest
.serialize(awards
.players
))
32 local S
= function(s
) return s
end
33 function awards
.set_intllib(locale
)
37 function awards
.init()
38 awards
.players
= awards
.load()
40 awards
.trigger_types
= {}
45 function awards
.load()
46 local file
= io
.open(minetest
.get_worldpath().."/awards.txt", "r")
48 local table = minetest
.deserialize(file
:read("*all"))
49 if type(table) == "table" then
56 function awards
.register_trigger(name
, func
)
57 awards
.trigger_types
[name
] = func
59 awards
['register_on_'..name
] = function(func
)
60 table.insert(awards
.on
[name
], func
)
64 function awards
.run_trigger_callbacks(player
, data
, trigger
, table_func
)
65 for i
= 1, #awards
.on
[trigger
] do
67 local entry
= awards
.on
[trigger
][i
]
68 if type(entry
) == "function" then
69 res
= entry(player
, data
)
70 elseif type(entry
) == "table" and entry
.award
then
71 res
= table_func(entry
)
75 awards
.unlock(player
:get_player_name(), res
)
80 function awards
.increment_item_counter(data
, field
, itemname
, count
)
81 local name_split
= string.split(itemname
, ":")
82 if #name_split
~= 2 then
85 local mod = name_split
[1]
86 local item
= name_split
[2]
88 if data
and field
and mod and item
then
89 awards
.assertPlayer(data
)
90 awards
.tbv(data
, field
)
91 awards
.tbv(data
[field
], mod)
92 awards
.tbv(data
[field
][mod], item
, 0)
94 data
[field
][mod][item
] = data
[field
][mod][item
] + (count
or 1)
101 function awards
.get_item_count(data
, field
, itemname
)
102 local name_split
= string.split(itemname
, ":")
103 if #name_split
~= 2 then
106 local mod = name_split
[1]
107 local item
= name_split
[2]
109 if data
and field
and mod and item
then
110 awards
.assertPlayer(data
)
111 awards
.tbv(data
, field
)
112 awards
.tbv(data
[field
], mod)
113 awards
.tbv(data
[field
][mod], item
, 0)
114 return data
[field
][mod][item
]
118 function awards
.get_total_item_count(data
, field
)
120 if data
and field
then
121 awards
.assertPlayer(data
)
122 awards
.tbv(data
, field
)
123 for mod,_
in pairs(data
[field
]) do
124 awards
.tbv(data
[field
], mod)
125 for item
,_
in pairs(data
[field
][mod]) do
126 awards
.tbv(data
[field
][mod], item
, 0)
127 i
= i
+ data
[field
][mod][item
]
134 function awards
.register_on_unlock(func
)
135 table.insert(awards
.on_unlock
, func
)
139 function awards
._additional_triggers(name
, def
)
143 function awards
.register_achievement(name
, def
)
147 if def
.trigger
and def
.trigger
.type then
148 local func
= awards
.trigger_types
[def
.trigger
.type]
153 awards
._additional_triggers(name
, def
)
158 awards
.def
[name
] = def
160 local tdef
= awards
.def
[name
]
161 if def
.description
== nil and tdef
.getDefaultDescription
then
162 def
.description
= tdef
:getDefaultDescription()
166 function awards
.enable(name
)
167 local data
= awards
.player(name
)
173 function awards
.disable(name
)
174 local data
= awards
.player(name
)
180 function awards
.clear_player(name
)
181 awards
.players
[name
] = {}
184 -- This function is called whenever a target condition is met.
185 -- It checks if a player already has that achievement, and if they do not,
186 -- it gives it to them
187 ----------------------------------------------
188 --awards.unlock(name, award)
189 -- name - the name of the player
190 -- award - the name of the award to give
191 function awards
.unlock(name
, award
)
192 -- Access Player Data
193 local data
= awards
.players
[name
]
194 local awdef
= awards
.def
[award
]
203 if data
.disabled
then
206 awards
.tbv(data
,"unlocked")
208 -- Don't give the achievement if it has already been given
209 if data
.unlocked
[award
] and data
.unlocked
[award
] == award
then
214 minetest
.log("action", name
.." has gotten award "..name
)
215 data
.unlocked
[award
] = award
219 if awdef
and awdef
.prizes
then
220 for i
= 1, #awdef
.prizes
do
221 local itemstack
= ItemStack(awdef
.prizes
[i
])
222 if not itemstack
:is_empty() then
223 local receiverref
= minetest
.get_player_by_name(name
)
225 receiverref
:get_inventory():add_item("main", itemstack
)
232 if awdef
.on_unlock
and awdef
.on_unlock(name
, awdef
) then
235 for _
, callback
in pairs(awards
.on_unlock
) do
236 if callback(name
, awdef
) then
241 -- Get Notification Settings
242 local title
= awdef
.title
or award
243 local desc
= awdef
.description
or ""
244 local background
= awdef
.background
or "awards_bg_default.png"
245 local icon
= awdef
.icon
or "awards_unknown.png"
246 local sound
= awdef
.sound
248 -- Explicit check for nil because sound could be `false` to disable it
249 sound
= {name
="awards_got_generic", gain
=0.25}
251 local custom_announce
= awdef
.custom_announce
252 if not custom_announce
then
254 custom_announce
= S("Secret achievement gotten:")
256 custom_announce
= S("Achievement gotten:")
262 -- Enforce sound delay to prevent sound spamming
263 local lastsound
= awards
.players
[name
].lastsound
264 if lastsound
== nil or os
.difftime(os
.time(), lastsound
) >= 1 then
265 minetest
.sound_play(sound
, {to_player
=name
})
266 awards
.players
[name
].lastsound
= os
.time()
270 if awards
.show_mode
== "formspec" then
271 -- use a formspec to send it
272 minetest
.show_formspec(name
, "achievements:unlocked", "size[4,2]"..
273 "image_button_exit[0,0;4,2;"..background
..";close1; ]"..
274 "image_button_exit[0.2,0.8;1,1;"..icon
..";close2; ]"..
275 "label[1.1,1;"..title
.."]"..
276 "label[0.3,0.1;"..custom_announce
.."]")
277 elseif awards
.show_mode
== "chat" then
279 if awdef
.secret
== true then
280 chat_announce
= S("Secret achievement gotten: %s")
282 chat_announce
= S("Achievement gotten: %s")
284 -- use the chat console to send it
285 minetest
.chat_send_player(name
, string.format(chat_announce
, title
))
287 minetest
.chat_send_player(name
, desc
)
290 local player
= minetest
.get_player_by_name(name
)
291 local one
= player
:hud_add({
292 hud_elem_type
= "image",
294 scale
= {x
= 1, y
= 1},
296 position
= {x
= 0.5, y
= 0},
297 offset
= {x
= 0, y
= 138},
298 alignment
= {x
= 0, y
= -1}
301 if awdef
.secret
== true then
302 hud_announce
= S("Secret achievement gotten!")
304 hud_announce
= S("Achievement gotten!")
306 local two
= player
:hud_add({
307 hud_elem_type
= "text",
310 scale
= {x
= 100, y
= 20},
312 position
= {x
= 0.5, y
= 0},
313 offset
= {x
= 0, y
= 40},
314 alignment
= {x
= 0, y
= -1}
316 local three
= player
:hud_add({
317 hud_elem_type
= "text",
318 name
= "award_title",
320 scale
= {x
= 100, y
= 20},
322 position
= {x
= 0.5, y
= 0},
323 offset
= {x
= 30, y
= 100},
324 alignment
= {x
= 0, y
= -1}
326 --[[ We use a statbar instead of image here because statbar allows us to scale the image
327 properly. Note that number is 2, thus leading to a single full image.
328 Yes, it's a hack, but it works for all texture sizes and is needed because the image
329 type does NOT allow us a simple scaling. ]]
330 local four
= player
:hud_add({
331 hud_elem_type
= "statbar",
333 size
= {x
=64, y
= 64},
336 position
= {x
= 0.5, y
= 0},
337 offset
= {x
= -110, y
= 62},
338 alignment
= {x
= 0, y
= 0},
341 minetest
.after(3, function(name
)
342 local player
= minetest
.get_player_by_name(name
)
346 player
:hud_remove(one
)
347 player
:hud_remove(two
)
348 player
:hud_remove(three
)
349 player
:hud_remove(four
)
350 end, player
:get_player_name())
354 -- Backwards compatibility
355 awards
.give_achievement
= awards
.unlock
357 --[[minetest.register_chatcommand("gawd", {
358 params = "award name",
359 description = "gawd: give award to self",
360 func = function(name, param)
361 awards.unlock(name,param)
365 function awards
.getFormspec(name
, to
, sid
)
367 local listofawards
= awards
._order_awards(name
)
368 local playerdata
= awards
.players
[name
]
370 if #listofawards
== 0 then
371 formspec
= formspec
.. "label[3.9,1.5;"..minetest
.formspec_escape(S("Error: No awards available.")).."]"
372 formspec
= formspec
.. "button_exit[4.2,2.3;3,1;close;"..minetest
.formspec_escape(S("OK")).."]"
378 local item
= listofawards
[sid
+0]
379 local def
= awards
.def
[item
.name
]
381 if def
and def
.secret
and not item
.got
then
382 formspec
= formspec
.. "label[1,2.75;"..minetest
.formspec_escape(S("(Secret achievement)")).."]"..
383 "image[1,0;3,3;awards_unknown.png]"
384 if def
and def
.description
then
385 formspec
= formspec
.. "textarea[0.25,3.25;4.8,1.7;;"..minetest
.formspec_escape(S("Get this achievement to find out what it is."))..";]"
388 local title
= item
.name
389 if def
and def
.title
then
394 status
= S("%s (got)")
396 formspec
= formspec
.. "label[1,2.75;" ..
397 string.format(status
, minetest
.formspec_escape(title
)) ..
399 if def
and def
.icon
then
400 formspec
= formspec
.. "image[1,0;3,3;" .. def
.icon
.. "]"
405 if def
.getProgress
and playerdata
then
406 local res
= def
:getProgress(playerdata
)
414 formspec
= formspec
.. "background[0,4.80;" .. barwidth
..",0.25;awards_progress_gray.png;false]"
415 formspec
= formspec
.. "background[0,4.80;" .. (barwidth
* perc
) ..",0.25;awards_progress_green.png;false]"
417 formspec
= formspec
.. "label[1.75,4.63;" .. minetest
.formspec_escape(label
) .. "]"
420 if def
and def
.description
then
421 formspec
= formspec
.. "textarea[0.25,3.25;4.8,1.7;;"..minetest
.formspec_escape(def
.description
)..";]"
427 formspec
= formspec
.. "textlist[4.75,0;6,5;awards;"
429 for _
,award
in pairs(listofawards
) do
430 local def
= awards
.def
[award
.name
]
433 formspec
= formspec
.. ","
437 if def
.secret
and not award
.got
then
438 formspec
= formspec
.. "#707070"..minetest
.formspec_escape(S("(Secret Award)"))
440 local title
= award
.name
441 if def
and def
.title
then
445 formspec
= formspec
.. minetest
.formspec_escape(title
)
447 formspec
= formspec
.. "#ACACAC".. minetest
.formspec_escape(title
)
452 return formspec
.. ";"..sid
.."]"
455 function awards
.show_to(name
, to
, sid
, text
)
456 if name
== "" or name
== nil then
459 if name
== to
and awards
.player(to
).disabled
then
460 minetest
.chat_send_player(S("You've disabled awards. Type /awards enable to reenable."))
464 local listofawards
= awards
._order_awards(name
)
465 if #listofawards
== 0 then
466 minetest
.chat_send_player(to
, S("Error: No awards available."))
468 elseif not awards
.players
[name
] or not awards
.players
[name
].unlocked
then
469 minetest
.chat_send_player(to
, S("You have not gotten any awards."))
472 minetest
.chat_send_player(to
, string.format(S("%s’s awards:"), name
))
474 for _
, str
in pairs(awards
.players
[name
].unlocked
) do
475 local def
= awards
.def
[str
]
478 if def
.description
then
479 minetest
.chat_send_player(to
, string.format(S("%s: %s"), def
.title
, def
.description
))
481 minetest
.chat_send_player(to
, def
.title
)
484 minetest
.chat_send_player(to
, str
)
489 if sid
== nil or sid
< 1 then
493 if minetest
.global_exists("default") then
494 deco
= default
.gui_bg
.. default
.gui_bg_img
496 -- Show formspec to user
497 minetest
.show_formspec(to
,"awards:awards",
498 "size[11,5]" .. deco
..
499 awards
.getFormspec(name
, to
, sid
))
502 awards
.showto
= awards
.show_to
504 minetest
.register_on_player_receive_fields(function(player
, formname
, fields
)
505 if formname
~= "awards:awards" then
511 local name
= player
:get_player_name()
512 if fields
.awards
then
513 local event
= minetest
.explode_textlist_event(fields
.awards
)
514 if event
.type == "CHG" then
515 awards
.show_to(name
, name
, event
.index
, false)
524 minetest
.register_on_newplayer(function(player
)
525 local playern
= player
:get_player_name()
526 awards
.assertPlayer(playern
)
529 minetest
.register_on_shutdown(function()