2017-03-04 00:55:34 +01:00
-- AWARDS
--
-- Copyright (C) 2013-2015 rubenwardy
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU Lesser General Public License as published by
-- the Free Software Foundation; either version 2.1 of the License, or
-- (at your option) any later version.
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Lesser General Public License for more details.
-- You should have received a copy of the GNU Lesser General Public License along
-- with this program; if not, write to the Free Software Foundation, Inc.,
-- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--
2021-05-29 16:12:33 +02:00
local modname = minetest.get_current_modname ( )
local modpath = minetest.get_modpath ( modname )
local S = minetest.get_translator ( modname )
2024-05-31 01:23:47 +02:00
-- Tunable parameters
local notif_delay = vl_tuning.get_server_setting ( " award_display_time " , S ( " Amount of time award notification are displayed " ) , 3 , " awards_notif_delay " , " number " , { min = 2 , max = 10 } )
2017-03-04 00:55:34 +01:00
-- The global award namespace
awards = {
2021-05-29 16:12:33 +02:00
show_mode = " hud " ,
2017-03-04 00:55:34 +01:00
}
2021-05-29 16:12:33 +02:00
dofile ( modpath .. " /api_helpers.lua " )
2017-03-04 00:55:34 +01:00
-- Table Save Load Functions
function awards . save ( )
local file = io.open ( minetest.get_worldpath ( ) .. " /awards.txt " , " w " )
if file then
file : write ( minetest.serialize ( awards.players ) )
file : close ( )
end
end
function awards . init ( )
awards.players = awards.load ( )
awards.def = { }
awards.trigger_types = { }
awards.on = { }
awards.on_unlock = { }
end
function awards . load ( )
local file = io.open ( minetest.get_worldpath ( ) .. " /awards.txt " , " r " )
if file then
local table = minetest.deserialize ( file : read ( " *all " ) )
if type ( table ) == " table " then
return table
end
end
return { }
end
function awards . register_trigger ( name , func )
awards.trigger_types [ name ] = func
awards.on [ name ] = { }
2021-05-29 16:12:33 +02:00
awards [ " register_on_ " .. name ] = function ( func )
2017-03-04 00:55:34 +01:00
table.insert ( awards.on [ name ] , func )
end
end
function awards . run_trigger_callbacks ( player , data , trigger , table_func )
for i = 1 , # awards.on [ trigger ] do
local res = nil
local entry = awards.on [ trigger ] [ i ]
if type ( entry ) == " function " then
res = entry ( player , data )
elseif type ( entry ) == " table " and entry.award then
res = table_func ( entry )
end
if res then
awards.unlock ( player : get_player_name ( ) , res )
end
end
end
function awards . increment_item_counter ( data , field , itemname , count )
local name_split = string.split ( itemname , " : " )
if # name_split ~= 2 then
return false
end
local mod = name_split [ 1 ]
local item = name_split [ 2 ]
if data and field and mod and item then
awards.assertPlayer ( data )
awards.tbv ( data , field )
awards.tbv ( data [ field ] , mod )
awards.tbv ( data [ field ] [ mod ] , item , 0 )
data [ field ] [ mod ] [ item ] = data [ field ] [ mod ] [ item ] + ( count or 1 )
return true
else
return false
end
end
function awards . get_item_count ( data , field , itemname )
local name_split = string.split ( itemname , " : " )
if # name_split ~= 2 then
return false
end
local mod = name_split [ 1 ]
local item = name_split [ 2 ]
if data and field and mod and item then
awards.assertPlayer ( data )
awards.tbv ( data , field )
awards.tbv ( data [ field ] , mod )
awards.tbv ( data [ field ] [ mod ] , item , 0 )
return data [ field ] [ mod ] [ item ]
end
end
function awards . get_total_item_count ( data , field )
local i = 0
if data and field then
awards.assertPlayer ( data )
awards.tbv ( data , field )
for mod , _ in pairs ( data [ field ] ) do
awards.tbv ( data [ field ] , mod )
for item , _ in pairs ( data [ field ] [ mod ] ) do
awards.tbv ( data [ field ] [ mod ] , item , 0 )
i = i + data [ field ] [ mod ] [ item ]
end
end
end
return i
end
function awards . register_on_unlock ( func )
table.insert ( awards.on_unlock , func )
end
-- API Functions
function awards . _additional_triggers ( name , def )
-- Depreciated!
end
function awards . register_achievement ( name , def )
def.name = name
-- Add Triggers
if def.trigger and def.trigger . type then
local func = awards.trigger_types [ def.trigger . type ]
if func then
func ( def )
else
awards._additional_triggers ( name , def )
end
end
-- Add Award
awards.def [ name ] = def
local tdef = awards.def [ name ]
if def.description == nil and tdef.getDefaultDescription then
def.description = tdef : getDefaultDescription ( )
end
end
function awards . enable ( name )
local data = awards.player ( name )
if data then
data.disabled = nil
end
end
function awards . disable ( name )
local data = awards.player ( name )
if data then
data.disabled = true
end
end
function awards . clear_player ( name )
awards.players [ name ] = { }
end
2020-03-30 21:02:13 +02:00
-- Returns true if award exists, false otherwise
function awards . exists ( award )
return awards.def [ award ] ~= nil
end
2017-03-04 00:55:34 +01:00
-- This function is called whenever a target condition is met.
-- It checks if a player already has that achievement, and if they do not,
-- it gives it to them
----------------------------------------------
--awards.unlock(name, award)
-- name - the name of the player
-- award - the name of the award to give
function awards . unlock ( name , award )
-- Access Player Data
local data = awards.players [ name ]
local awdef = awards.def [ award ]
-- Perform checks
if not data then
return
end
if not awdef then
return
end
if data.disabled then
return
end
awards.tbv ( data , " unlocked " )
-- Don't give the achievement if it has already been given
if data.unlocked [ award ] and data.unlocked [ award ] == award then
return
end
2017-03-06 01:47:07 +01:00
-- Get award
2021-02-25 23:58:53 +01:00
minetest.log ( " action " , name .. " has gotten award " .. award )
2023-11-28 02:37:45 +01:00
if minetest.settings : get_bool ( " mcl_showAdvancementMessages " , true ) then
minetest.chat_send_all ( S ( " @1 has made the advancement @2 " , name , minetest.colorize ( mcl_colors.GREEN , " [ " .. ( awdef.title or award ) .. " ] " ) ) )
end
2017-03-04 00:55:34 +01:00
data.unlocked [ award ] = award
awards.save ( )
-- Give Prizes
if awdef and awdef.prizes then
for i = 1 , # awdef.prizes do
local itemstack = ItemStack ( awdef.prizes [ i ] )
if not itemstack : is_empty ( ) then
local receiverref = minetest.get_player_by_name ( name )
if receiverref then
receiverref : get_inventory ( ) : add_item ( " main " , itemstack )
end
end
end
end
-- Run callbacks
if awdef.on_unlock and awdef.on_unlock ( name , awdef ) then
return
end
for _ , callback in pairs ( awards.on_unlock ) do
if callback ( name , awdef ) then
return
end
end
-- Get Notification Settings
local title = awdef.title or award
local desc = awdef.description or " "
local background = awdef.background or " awards_bg_default.png "
local icon = awdef.icon or " awards_unknown.png "
local sound = awdef.sound
if sound == nil then
-- Explicit check for nil because sound could be `false` to disable it
sound = { name = " awards_got_generic " , gain = 0.25 }
end
local custom_announce = awdef.custom_announce
if not custom_announce then
if awdef.secret then
2022-09-23 17:24:31 +02:00
custom_announce = S ( " Secret Advancement Made: " )
2022-10-04 00:55:26 +02:00
elseif awdef.type == " Goal " then
custom_announce = S ( " Goal Completed: " )
elseif awdef.type == " Challenge " then
custom_announce = S ( " Challenge Completed: " )
2017-03-04 00:55:34 +01:00
else
2022-09-23 17:24:31 +02:00
custom_announce = S ( " Advancement Made: " )
2017-03-04 00:55:34 +01:00
end
end
-- Do Notification
if sound then
-- Enforce sound delay to prevent sound spamming
local lastsound = awards.players [ name ] . lastsound
if lastsound == nil or os.difftime ( os.time ( ) , lastsound ) >= 1 then
2020-04-07 00:55:45 +02:00
minetest.sound_play ( sound , { to_player = name } , true )
2017-03-04 00:55:34 +01:00
awards.players [ name ] . lastsound = os.time ( )
end
end
if awards.show_mode == " formspec " then
-- use a formspec to send it
minetest.show_formspec ( name , " achievements:unlocked " , " size[4,2] " ..
" image_button_exit[0,0;4,2; " .. background .. " ;close1; ] " ..
" image_button_exit[0.2,0.8;1,1; " .. icon .. " ;close2; ] " ..
" label[1.1,1; " .. title .. " ] " ..
" label[0.3,0.1; " .. custom_announce .. " ] " )
elseif awards.show_mode == " chat " then
local chat_announce
if awdef.secret == true then
2022-09-23 17:24:31 +02:00
chat_announce = S ( " Secret Advancement Made: @1 " )
2022-10-04 00:55:26 +02:00
elseif awdef.type == " Goal " then
chat_announce = S ( " Goal Completed: @1 " )
elseif awdef.type == " Challenge " then
chat_announce = S ( " Challenge Completed: @1 " )
2017-03-04 00:55:34 +01:00
else
2022-09-23 17:24:31 +02:00
chat_announce = S ( " Advancement Made: @1 " )
2017-03-04 00:55:34 +01:00
end
-- use the chat console to send it
minetest.chat_send_player ( name , string.format ( chat_announce , title ) )
if desc ~= " " then
minetest.chat_send_player ( name , desc )
end
else
local player = minetest.get_player_by_name ( name )
local one = player : hud_add ( {
hud_elem_type = " image " ,
name = " award_bg " ,
2023-01-06 02:38:55 +01:00
scale = { x = 1.25 , y = 1 } ,
2017-03-04 00:55:34 +01:00
text = background ,
position = { x = 0.5 , y = 0 } ,
offset = { x = 0 , y = 138 } ,
2020-04-07 13:03:10 +02:00
alignment = { x = 0 , y = - 1 } ,
2020-04-17 21:27:45 +02:00
z_index = 101 ,
2017-03-04 00:55:34 +01:00
} )
local hud_announce
if awdef.secret == true then
2022-09-23 17:24:31 +02:00
hud_announce = S ( " Secret Advancement Made! " )
2022-10-04 00:55:26 +02:00
elseif awdef.type == " Goal " then
hud_announce = S ( " Goal Completed! " )
elseif awdef.type == " Challenge " then
hud_announce = S ( " Challenge Completed! " )
2017-03-04 00:55:34 +01:00
else
2022-09-23 17:24:31 +02:00
hud_announce = S ( " Advancement Made! " )
2017-03-04 00:55:34 +01:00
end
local two = player : hud_add ( {
hud_elem_type = " text " ,
name = " award_au " ,
2017-03-09 04:09:16 +01:00
number = 0xFFFF00 ,
2017-03-04 00:55:34 +01:00
scale = { x = 100 , y = 20 } ,
text = hud_announce ,
position = { x = 0.5 , y = 0 } ,
2023-01-06 02:38:55 +01:00
offset = { x = 30 , y = 40 } ,
2020-04-07 13:03:10 +02:00
alignment = { x = 0 , y = - 1 } ,
2020-04-17 21:27:45 +02:00
z_index = 102 ,
2017-03-04 00:55:34 +01:00
} )
local three = player : hud_add ( {
hud_elem_type = " text " ,
name = " award_title " ,
number = 0xFFFFFF ,
scale = { x = 100 , y = 20 } ,
text = title ,
position = { x = 0.5 , y = 0 } ,
2023-01-06 02:38:55 +01:00
offset = { x = 35 , y = 100 } ,
2020-04-07 13:03:10 +02:00
alignment = { x = 0 , y = - 1 } ,
2020-04-17 21:27:45 +02:00
z_index = 102 ,
2017-03-04 00:55:34 +01:00
} )
2017-03-04 00:56:56 +01:00
--[[ We use a statbar instead of image here because statbar allows us to scale the image
properly . Note that number is 2 , thus leading to a single full image .
Yes , it ' s a hack, but it works for all texture sizes and is needed because the image
type does NOT allow us a simple scaling . ] ]
2017-03-04 00:55:34 +01:00
local four = player : hud_add ( {
2017-03-04 00:56:56 +01:00
hud_elem_type = " statbar " ,
2017-03-04 00:55:34 +01:00
name = " award_icon " ,
2017-03-04 00:56:56 +01:00
size = { x = 64 , y = 64 } ,
number = 2 ,
2017-03-04 00:55:34 +01:00
text = icon ,
position = { x = 0.5 , y = 0 } ,
2023-01-06 02:38:55 +01:00
offset = { x = - 138 , y = 62 } ,
2017-03-04 00:56:56 +01:00
alignment = { x = 0 , y = 0 } ,
direction = 0 ,
2020-04-17 21:27:45 +02:00
z_index = 102 ,
2017-03-04 00:55:34 +01:00
} )
2024-05-31 01:32:16 +02:00
minetest.after ( notif_delay [ 1 ] , function ( name )
2018-06-03 16:44:37 +02:00
local player = minetest.get_player_by_name ( name )
if not player then
return
end
2017-03-04 00:55:34 +01:00
player : hud_remove ( one )
player : hud_remove ( two )
player : hud_remove ( three )
player : hud_remove ( four )
2018-06-03 16:44:37 +02:00
end , player : get_player_name ( ) )
2017-03-04 00:55:34 +01:00
end
end
-- Backwards compatibility
awards.give_achievement = awards.unlock
--[[minetest.register_chatcommand("gawd", {
params = " award name " ,
description = " gawd: give award to self " ,
func = function ( name , param )
awards.unlock ( name , param )
end
} ) ] ] --
function awards . getFormspec ( name , to , sid )
local formspec = " "
local listofawards = awards._order_awards ( name )
local playerdata = awards.players [ name ]
if # listofawards == 0 then
formspec = formspec .. " label[3.9,1.5; " .. minetest.formspec_escape ( S ( " Error: No awards available. " ) ) .. " ] "
formspec = formspec .. " button_exit[4.2,2.3;3,1;close; " .. minetest.formspec_escape ( S ( " OK " ) ) .. " ] "
return formspec
end
-- Sidebar
if sid then
local item = listofawards [ sid + 0 ]
local def = awards.def [ item.name ]
if def and def.secret and not item.got then
2022-09-23 17:24:31 +02:00
formspec = formspec .. " label[1,2.75; " .. minetest.formspec_escape ( S ( " (Secret Advancement) " ) ) .. " ] " ..
2017-03-04 00:55:34 +01:00
" image[1,0;3,3;awards_unknown.png] "
if def and def.description then
2022-09-23 17:24:31 +02:00
formspec = formspec .. " textarea[0.25,3.25;4.8,1.7;; " .. minetest.formspec_escape ( S ( " Make this advancement to find out what it is. " ) ) .. " ;] "
2017-03-04 00:55:34 +01:00
end
else
local title = item.name
if def and def.title then
title = def.title
end
2019-03-16 03:36:32 +01:00
local status
2017-03-04 00:55:34 +01:00
if item.got then
2019-03-16 03:36:32 +01:00
status = S ( " @1 (got) " , title )
else
status = title
2017-03-04 00:55:34 +01:00
end
formspec = formspec .. " label[1,2.75; " ..
2019-03-16 03:36:32 +01:00
minetest.formspec_escape ( status ) ..
2017-03-04 00:55:34 +01:00
" ] "
if def and def.icon then
formspec = formspec .. " image[1,0;3,3; " .. def.icon .. " ] "
end
local barwidth = 4.6
local perc = nil
local label = nil
if def.getProgress and playerdata then
local res = def : getProgress ( playerdata )
perc = res.perc
label = res.label
end
if perc then
if perc > 1 then
perc = 1
end
2020-03-10 05:01:49 +01:00
formspec = formspec .. " background[0,4.80; " .. barwidth .. " ,0.3;awards_progress_gray.png;false] "
if perc > 0 then
formspec = formspec .. " background[0,4.80; " .. ( barwidth * perc ) .. " ,0.3;awards_progress_green.png;false] "
end
2017-03-04 00:55:34 +01:00
if label then
formspec = formspec .. " label[1.75,4.63; " .. minetest.formspec_escape ( label ) .. " ] "
end
end
if def and def.description then
formspec = formspec .. " textarea[0.25,3.25;4.8,1.7;; " .. minetest.formspec_escape ( def.description ) .. " ;] "
end
end
end
-- Create list box
2020-03-10 05:01:49 +01:00
formspec = formspec ..
" textlist[4.75,0;6,5;awards; "
2017-03-04 00:55:34 +01:00
local first = true
for _ , award in pairs ( listofawards ) do
local def = awards.def [ award.name ]
if def then
if not first then
formspec = formspec .. " , "
end
first = false
if def.secret and not award.got then
2022-09-23 17:24:31 +02:00
formspec = formspec .. " #707070 " .. minetest.formspec_escape ( S ( " (Secret Advancement) " ) )
2017-03-04 00:55:34 +01:00
else
local title = award.name
if def and def.title then
title = def.title
end
if award.got then
formspec = formspec .. minetest.formspec_escape ( title )
else
2021-04-19 14:44:20 +02:00
formspec = formspec .. " #ACACAC " .. minetest.formspec_escape ( title )
2017-03-04 00:55:34 +01:00
end
end
end
end
return formspec .. " ; " .. sid .. " ] "
end
function awards . show_to ( name , to , sid , text )
if name == " " or name == nil then
name = to
end
if name == to and awards.player ( to ) . disabled then
2022-10-31 16:19:38 +01:00
minetest.chat_send_player ( name , S ( " You've disabled awards. Type /awards enable to reenable. " ) )
2017-03-04 00:55:34 +01:00
return
end
if text then
local listofawards = awards._order_awards ( name )
if # listofawards == 0 then
minetest.chat_send_player ( to , S ( " Error: No awards available. " ) )
return
elseif not awards.players [ name ] or not awards.players [ name ] . unlocked then
2017-03-06 01:47:07 +01:00
minetest.chat_send_player ( to , S ( " You have not gotten any awards. " ) )
2017-03-04 00:55:34 +01:00
return
end
2020-01-06 11:24:28 +01:00
minetest.chat_send_player ( to , S ( " @1’ s awards: " , name ) )
2017-03-04 00:55:34 +01:00
for _ , str in pairs ( awards.players [ name ] . unlocked ) do
local def = awards.def [ str ]
if def then
if def.title then
if def.description then
2019-03-16 03:36:32 +01:00
minetest.chat_send_player ( to , S ( " @1: @2 " , def.title , def.description ) )
2017-03-04 00:55:34 +01:00
else
minetest.chat_send_player ( to , def.title )
end
else
minetest.chat_send_player ( to , str )
end
end
end
else
if sid == nil or sid < 1 then
sid = 1
end
local d eco = " "
if minetest.global_exists ( " default " ) then
deco = default.gui_bg .. default.gui_bg_img
end
-- Show formspec to user
minetest.show_formspec ( to , " awards:awards " ,
" size[11,5] " .. deco ..
awards.getFormspec ( name , to , sid ) )
end
end
awards.showto = awards.show_to
minetest.register_on_player_receive_fields ( function ( player , formname , fields )
if formname ~= " awards:awards " then
return false
end
if fields.quit then
return true
end
local name = player : get_player_name ( )
if fields.awards then
local event = minetest.explode_textlist_event ( fields.awards )
if event.type == " CHG " then
awards.show_to ( name , name , event.index , false )
end
end
return true
end )
awards.init ( )
minetest.register_on_newplayer ( function ( player )
local playern = player : get_player_name ( )
awards.assertPlayer ( playern )
end )
minetest.register_on_shutdown ( function ( )
awards.save ( )
end )