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
= minetest
.get_translator("awards")
34 function awards
.init()
35 awards
.players
= awards
.load()
37 awards
.trigger_types
= {}
42 function awards
.load()
43 local file
= io
.open(minetest
.get_worldpath().."/awards.txt", "r")
45 local table = minetest
.deserialize(file
:read("*all"))
46 if type(table) == "table" then
53 function awards
.register_trigger(name
, func
)
54 awards
.trigger_types
[name
] = func
56 awards
['register_on_'..name
] = function(func
)
57 table.insert(awards
.on
[name
], func
)
61 function awards
.run_trigger_callbacks(player
, data
, trigger
, table_func
)
62 for i
= 1, #awards
.on
[trigger
] do
64 local entry
= awards
.on
[trigger
][i
]
65 if type(entry
) == "function" then
66 res
= entry(player
, data
)
67 elseif type(entry
) == "table" and entry
.award
then
68 res
= table_func(entry
)
72 awards
.unlock(player
:get_player_name(), res
)
77 function awards
.increment_item_counter(data
, field
, itemname
, count
)
78 local name_split
= string.split(itemname
, ":")
79 if #name_split
~= 2 then
82 local mod = name_split
[1]
83 local item
= name_split
[2]
85 if data
and field
and mod and item
then
86 awards
.assertPlayer(data
)
87 awards
.tbv(data
, field
)
88 awards
.tbv(data
[field
], mod)
89 awards
.tbv(data
[field
][mod], item
, 0)
91 data
[field
][mod][item
] = data
[field
][mod][item
] + (count
or 1)
98 function awards
.get_item_count(data
, field
, itemname
)
99 local name_split
= string.split(itemname
, ":")
100 if #name_split
~= 2 then
103 local mod = name_split
[1]
104 local item
= name_split
[2]
106 if data
and field
and mod and item
then
107 awards
.assertPlayer(data
)
108 awards
.tbv(data
, field
)
109 awards
.tbv(data
[field
], mod)
110 awards
.tbv(data
[field
][mod], item
, 0)
111 return data
[field
][mod][item
]
115 function awards
.get_total_item_count(data
, field
)
117 if data
and field
then
118 awards
.assertPlayer(data
)
119 awards
.tbv(data
, field
)
120 for mod,_
in pairs(data
[field
]) do
121 awards
.tbv(data
[field
], mod)
122 for item
,_
in pairs(data
[field
][mod]) do
123 awards
.tbv(data
[field
][mod], item
, 0)
124 i
= i
+ data
[field
][mod][item
]
131 function awards
.register_on_unlock(func
)
132 table.insert(awards
.on_unlock
, func
)
136 function awards
._additional_triggers(name
, def
)
140 function awards
.register_achievement(name
, def
)
144 if def
.trigger
and def
.trigger
.type then
145 local func
= awards
.trigger_types
[def
.trigger
.type]
150 awards
._additional_triggers(name
, def
)
155 awards
.def
[name
] = def
157 local tdef
= awards
.def
[name
]
158 if def
.description
== nil and tdef
.getDefaultDescription
then
159 def
.description
= tdef
:getDefaultDescription()
163 function awards
.enable(name
)
164 local data
= awards
.player(name
)
170 function awards
.disable(name
)
171 local data
= awards
.player(name
)
177 function awards
.clear_player(name
)
178 awards
.players
[name
] = {}
181 -- Returns true if award exists, false otherwise
182 function awards
.exists(award
)
183 return awards
.def
[award
] ~= nil
186 -- This function is called whenever a target condition is met.
187 -- It checks if a player already has that achievement, and if they do not,
188 -- it gives it to them
189 ----------------------------------------------
190 --awards.unlock(name, award)
191 -- name - the name of the player
192 -- award - the name of the award to give
193 function awards
.unlock(name
, award
)
194 -- Access Player Data
195 local data
= awards
.players
[name
]
196 local awdef
= awards
.def
[award
]
205 if data
.disabled
then
208 awards
.tbv(data
,"unlocked")
210 -- Don't give the achievement if it has already been given
211 if data
.unlocked
[award
] and data
.unlocked
[award
] == award
then
216 minetest
.log("action", name
.." has gotten award "..name
)
217 data
.unlocked
[award
] = award
221 if awdef
and awdef
.prizes
then
222 for i
= 1, #awdef
.prizes
do
223 local itemstack
= ItemStack(awdef
.prizes
[i
])
224 if not itemstack
:is_empty() then
225 local receiverref
= minetest
.get_player_by_name(name
)
227 receiverref
:get_inventory():add_item("main", itemstack
)
234 if awdef
.on_unlock
and awdef
.on_unlock(name
, awdef
) then
237 for _
, callback
in pairs(awards
.on_unlock
) do
238 if callback(name
, awdef
) then
243 -- Get Notification Settings
244 local title
= awdef
.title
or award
245 local desc
= awdef
.description
or ""
246 local background
= awdef
.background
or "awards_bg_default.png"
247 local icon
= awdef
.icon
or "awards_unknown.png"
248 local sound
= awdef
.sound
250 -- Explicit check for nil because sound could be `false` to disable it
251 sound
= {name
="awards_got_generic", gain
=0.25}
253 local custom_announce
= awdef
.custom_announce
254 if not custom_announce
then
256 custom_announce
= S("Secret achievement gotten:")
258 custom_announce
= S("Achievement gotten:")
264 -- Enforce sound delay to prevent sound spamming
265 local lastsound
= awards
.players
[name
].lastsound
266 if lastsound
== nil or os
.difftime(os
.time(), lastsound
) >= 1 then
267 minetest
.sound_play(sound
, {to_player
=name
}, true)
268 awards
.players
[name
].lastsound
= os
.time()
272 if awards
.show_mode
== "formspec" then
273 -- use a formspec to send it
274 minetest
.show_formspec(name
, "achievements:unlocked", "size[4,2]"..
275 "image_button_exit[0,0;4,2;"..background
..";close1; ]"..
276 "image_button_exit[0.2,0.8;1,1;"..icon
..";close2; ]"..
277 "label[1.1,1;"..title
.."]"..
278 "label[0.3,0.1;"..custom_announce
.."]")
279 elseif awards
.show_mode
== "chat" then
281 if awdef
.secret
== true then
282 chat_announce
= S("Secret achievement gotten: @1")
284 chat_announce
= S("Achievement gotten: @1")
286 -- use the chat console to send it
287 minetest
.chat_send_player(name
, string.format(chat_announce
, title
))
289 minetest
.chat_send_player(name
, desc
)
292 local player
= minetest
.get_player_by_name(name
)
293 local one
= player
:hud_add({
294 hud_elem_type
= "image",
296 scale
= {x
= 1, y
= 1},
298 position
= {x
= 0.5, y
= 0},
299 offset
= {x
= 0, y
= 138},
300 alignment
= {x
= 0, y
= -1},
304 if awdef
.secret
== true then
305 hud_announce
= S("Secret achievement gotten!")
307 hud_announce
= S("Achievement gotten!")
309 local two
= player
:hud_add({
310 hud_elem_type
= "text",
313 scale
= {x
= 100, y
= 20},
315 position
= {x
= 0.5, y
= 0},
316 offset
= {x
= 0, y
= 40},
317 alignment
= {x
= 0, y
= -1},
320 local three
= player
:hud_add({
321 hud_elem_type
= "text",
322 name
= "award_title",
324 scale
= {x
= 100, y
= 20},
326 position
= {x
= 0.5, y
= 0},
327 offset
= {x
= 30, y
= 100},
328 alignment
= {x
= 0, y
= -1},
331 --[[ We use a statbar instead of image here because statbar allows us to scale the image
332 properly. Note that number is 2, thus leading to a single full image.
333 Yes, it's a hack, but it works for all texture sizes and is needed because the image
334 type does NOT allow us a simple scaling. ]]
335 local four
= player
:hud_add({
336 hud_elem_type
= "statbar",
338 size
= {x
=64, y
= 64},
341 position
= {x
= 0.5, y
= 0},
342 offset
= {x
= -110, y
= 62},
343 alignment
= {x
= 0, y
= 0},
347 minetest
.after(3, function(name
)
348 local player
= minetest
.get_player_by_name(name
)
352 player
:hud_remove(one
)
353 player
:hud_remove(two
)
354 player
:hud_remove(three
)
355 player
:hud_remove(four
)
356 end, player
:get_player_name())
360 -- Backwards compatibility
361 awards
.give_achievement
= awards
.unlock
363 --[[minetest.register_chatcommand("gawd", {
364 params = "award name",
365 description = "gawd: give award to self",
366 func = function(name, param)
367 awards.unlock(name,param)
371 function awards
.getFormspec(name
, to
, sid
)
373 local listofawards
= awards
._order_awards(name
)
374 local playerdata
= awards
.players
[name
]
376 if #listofawards
== 0 then
377 formspec
= formspec
.. "label[3.9,1.5;"..minetest
.formspec_escape(S("Error: No awards available.")).."]"
378 formspec
= formspec
.. "button_exit[4.2,2.3;3,1;close;"..minetest
.formspec_escape(S("OK")).."]"
384 local item
= listofawards
[sid
+0]
385 local def
= awards
.def
[item
.name
]
387 if def
and def
.secret
and not item
.got
then
388 formspec
= formspec
.. "label[1,2.75;"..minetest
.formspec_escape(S("(Secret achievement)")).."]"..
389 "image[1,0;3,3;awards_unknown.png]"
390 if def
and def
.description
then
391 formspec
= formspec
.. "textarea[0.25,3.25;4.8,1.7;;"..minetest
.formspec_escape(S("Get this achievement to find out what it is."))..";]"
394 local title
= item
.name
395 if def
and def
.title
then
400 status
= S("@1 (got)", title
)
404 formspec
= formspec
.. "label[1,2.75;" ..
405 minetest
.formspec_escape(status
) ..
407 if def
and def
.icon
then
408 formspec
= formspec
.. "image[1,0;3,3;" .. def
.icon
.. "]"
413 if def
.getProgress
and playerdata
then
414 local res
= def
:getProgress(playerdata
)
422 formspec
= formspec
.. "background[0,4.80;" .. barwidth
..",0.3;awards_progress_gray.png;false]"
424 formspec
= formspec
.. "background[0,4.80;" .. (barwidth
* perc
) ..",0.3;awards_progress_green.png;false]"
427 formspec
= formspec
.. "label[1.75,4.63;" .. minetest
.formspec_escape(label
) .. "]"
430 if def
and def
.description
then
431 formspec
= formspec
.. "textarea[0.25,3.25;4.8,1.7;;"..minetest
.formspec_escape(def
.description
)..";]"
437 formspec
= formspec
..
438 "textlist[4.75,0;6,5;awards;"
440 for _
,award
in pairs(listofawards
) do
441 local def
= awards
.def
[award
.name
]
444 formspec
= formspec
.. ","
448 if def
.secret
and not award
.got
then
449 formspec
= formspec
.. "#707070"..minetest
.formspec_escape(S("(Secret Award)"))
451 local title
= award
.name
452 if def
and def
.title
then
456 formspec
= formspec
.. minetest
.formspec_escape(title
)
458 formspec
= formspec
.. "#ACACAC".. minetest
.formspec_escape(title
)
463 return formspec
.. ";"..sid
.."]"
466 function awards
.show_to(name
, to
, sid
, text
)
467 if name
== "" or name
== nil then
470 if name
== to
and awards
.player(to
).disabled
then
471 minetest
.chat_send_player(S("You've disabled awards. Type /awards enable to reenable."))
475 local listofawards
= awards
._order_awards(name
)
476 if #listofawards
== 0 then
477 minetest
.chat_send_player(to
, S("Error: No awards available."))
479 elseif not awards
.players
[name
] or not awards
.players
[name
].unlocked
then
480 minetest
.chat_send_player(to
, S("You have not gotten any awards."))
483 minetest
.chat_send_player(to
, S("@1’s awards:", name
))
485 for _
, str
in pairs(awards
.players
[name
].unlocked
) do
486 local def
= awards
.def
[str
]
489 if def
.description
then
490 minetest
.chat_send_player(to
, S("@1: @2", def
.title
, def
.description
))
492 minetest
.chat_send_player(to
, def
.title
)
495 minetest
.chat_send_player(to
, str
)
500 if sid
== nil or sid
< 1 then
504 if minetest
.global_exists("default") then
505 deco
= default
.gui_bg
.. default
.gui_bg_img
507 -- Show formspec to user
508 minetest
.show_formspec(to
,"awards:awards",
509 "size[11,5]" .. deco
..
510 awards
.getFormspec(name
, to
, sid
))
513 awards
.showto
= awards
.show_to
515 minetest
.register_on_player_receive_fields(function(player
, formname
, fields
)
516 if formname
~= "awards:awards" then
522 local name
= player
:get_player_name()
523 if fields
.awards
then
524 local event
= minetest
.explode_textlist_event(fields
.awards
)
525 if event
.type == "CHG" then
526 awards
.show_to(name
, name
, event
.index
, false)
535 minetest
.register_on_newplayer(function(player
)
536 local playern
= player
:get_player_name()
537 awards
.assertPlayer(playern
)
540 minetest
.register_on_shutdown(function()