2020-10-19 14:38:21 +02:00
local S = minetest.get_translator ( " mcl_experience " )
mcl_experience = { }
2020-10-23 22:18:53 +02:00
local pool = { }
local registered_nodes
2020-10-23 23:16:46 +02:00
local max_xp = 2 ^ 31 - 1
2020-10-25 22:06:35 +01:00
local max_orb_age = 300 -- seconds
2020-10-19 14:38:21 +02:00
2020-10-23 22:18:53 +02:00
local gravity = { x = 0 , y = - ( ( tonumber ( minetest.settings : get ( " movement_gravity " ) ) ) or 9.81 ) , z = 0 }
local size_min , size_max = 20 , 59 -- percents
local delta_size = size_max - size_min
local size_to_xp = {
{ - 32768 , 2 } , -- 1
{ 3 , 6 } , -- 2
{ 7 , 16 } , -- 3
{ 17 , 36 } , -- 4
{ 37 , 72 } , -- 5
{ 73 , 148 } , -- 6
{ 149 , 306 } , -- 7
{ 307 , 616 } , -- 8
{ 617 , 1236 } , -- 9
{ 1237 , 2476 } , --10
{ 2477 , 32767 } --11
}
local function xp_to_size ( xp )
local i , l = 1 , # size_to_xp
while ( xp > size_to_xp [ i ] [ 1 ] ) and ( i < l ) do
i = i + 1
end
return ( ( i - 1 ) / ( l - 1 ) * delta_size + size_min ) / 100
end
2020-10-19 14:38:21 +02:00
minetest.register_on_mods_loaded ( function ( )
registered_nodes = minetest.registered_nodes
end )
local load_data = function ( player )
2020-10-23 22:18:53 +02:00
local name = player : get_player_name ( )
2020-10-19 14:38:21 +02:00
pool [ name ] = { }
2020-10-23 22:18:53 +02:00
local temp_pool = pool [ name ]
local meta = player : get_meta ( )
temp_pool.xp = meta : get_int ( " xp " ) or 0
temp_pool.level = mcl_experience.xp_to_level ( temp_pool.xp )
2020-10-24 00:46:45 +02:00
temp_pool.bar , temp_pool.bar_step , temp_pool.xp_next_level = mcl_experience.xp_to_bar ( temp_pool.xp , temp_pool.level )
2020-10-23 22:18:53 +02:00
temp_pool.last_time = minetest.get_us_time ( ) / 1000000
2020-10-19 14:38:21 +02:00
end
-- saves data to be utilized on next login
2020-10-23 22:18:53 +02:00
local save_data = function ( player )
2020-10-24 11:42:24 +02:00
local name = player : get_player_name ( )
2020-10-23 22:18:53 +02:00
local temp_pool = pool [ name ]
local meta = player : get_meta ( )
meta : set_int ( " xp " , temp_pool.xp )
2020-10-19 14:38:21 +02:00
pool [ name ] = nil
end
local player_huds = { } -- the list of players hud lists (3d array)
hud_manager = { } -- hud manager class
-- terminate the player's list on leave
minetest.register_on_leaveplayer ( function ( player )
local name = player : get_player_name ( )
player_huds [ name ] = nil
end )
-- create instance of new hud
hud_manager.add_hud = function ( player , hud_name , def )
local name = player : get_player_name ( )
local local_hud = player : hud_add ( {
hud_elem_type = def.hud_elem_type ,
position = def.position ,
text = def.text ,
2020-10-23 23:30:40 +02:00
text2 = def.text2 ,
2020-10-19 14:38:21 +02:00
number = def.number ,
2020-10-23 23:30:40 +02:00
item = def.item ,
2020-10-19 14:38:21 +02:00
direction = def.direction ,
size = def.size ,
offset = def.offset ,
2020-10-23 22:50:53 +02:00
z_index = def.z_index ,
2020-12-21 17:02:39 +01:00
alignment = def.alignment ,
scale = def.scale ,
2020-10-19 14:38:21 +02:00
} )
-- create new 3d array here
-- depends.txt is not needed
-- with it here
if not player_huds [ name ] then
player_huds [ name ] = { }
end
player_huds [ name ] [ hud_name ] = local_hud
end
-- delete instance of hud
hud_manager.remove_hud = function ( player , hud_name )
local name = player : get_player_name ( )
if player_huds [ name ] and player_huds [ name ] [ hud_name ] then
player : hud_remove ( player_huds [ name ] [ hud_name ] )
player_huds [ name ] [ hud_name ] = nil
end
end
-- change element of hud
hud_manager.change_hud = function ( data )
local name = data.player : get_player_name ( )
if player_huds [ name ] and player_huds [ name ] [ data.hud_name ] then
data.player : hud_change ( player_huds [ name ] [ data.hud_name ] , data.element , data.data )
end
end
-- gets if hud exists
hud_manager.hud_exists = function ( player , hud_name )
local name = player : get_player_name ( )
if player_huds [ name ] and player_huds [ name ] [ hud_name ] then
return ( true )
else
return ( false )
end
end
-------------------
-- saves specific users data for when they relog
minetest.register_on_leaveplayer ( function ( player )
save_data ( player )
end )
-- is used for shutdowns to save all data
local save_all = function ( )
for name , _ in pairs ( pool ) do
2020-10-23 22:18:53 +02:00
local player = minetest.get_player_by_name ( name )
if player then
save_data ( player )
end
2020-10-19 14:38:21 +02:00
end
end
-- save all data to mod storage on shutdown
minetest.register_on_shutdown ( function ( )
save_all ( )
end )
function mcl_experience . get_player_xp_level ( player )
2020-10-24 00:46:45 +02:00
local name = player : get_player_name ( )
2020-10-23 22:18:53 +02:00
return ( pool [ name ] . level )
2020-10-19 14:38:21 +02:00
end
function mcl_experience . set_player_xp_level ( player , level )
2020-10-24 00:46:45 +02:00
local name = player : get_player_name ( )
if level == pool [ name ] . level then
return
end
2020-10-23 22:18:53 +02:00
pool [ name ] . level = level
2020-10-25 22:06:35 +01:00
pool [ name ] . xp , pool [ name ] . bar_step , pool [ name ] . next_level = mcl_experience.bar_to_xp ( pool [ name ] . bar , level )
2020-12-14 14:16:09 +01:00
hud_manager.change_hud ( { player = player , hud_name = " xp_level " , element = " text " , data = tostring ( level ) } )
2020-10-24 00:46:45 +02:00
-- we may don't update the bar
2020-10-19 14:38:21 +02:00
end
local name
local temp_pool
minetest.register_on_joinplayer ( function ( player )
load_data ( player )
name = player : get_player_name ( )
temp_pool = pool [ name ]
2020-10-23 22:18:53 +02:00
hud_manager.add_hud ( player , " experience_bar " ,
{
2020-12-21 17:02:39 +01:00
hud_elem_type = " image " ,
name = " experience bar " ,
text = " experience_bar_background.png^[lowpart: " .. math.floor ( temp_pool.bar / 36 * 100 ) .. " :experience_bar.png^[transformR270 " ,
position = { x = 0.5 , y = 1 } ,
2020-12-21 18:27:58 +01:00
offset = { x = ( - 9 * 28 ) - 3 , y = - ( 48 + 24 + 16 - 5 ) } ,
scale = { x = 2.8 , y = 3.0 } ,
2020-12-21 17:02:39 +01:00
alignment = { x = 1 , y = 1 } ,
z_index = 11 ,
2020-10-23 22:18:53 +02:00
} )
2020-10-19 14:38:21 +02:00
2020-10-23 22:50:53 +02:00
hud_manager.add_hud ( player , " xp_level " ,
2020-10-23 22:18:53 +02:00
{
hud_elem_type = " text " , position = { x = 0.5 , y = 1 } ,
2020-10-23 22:50:53 +02:00
name = " xp_level " , text = tostring ( temp_pool.level ) ,
number = 0xFFFFFF ,
2020-10-23 22:18:53 +02:00
offset = { x = 0 , y = - ( 48 + 24 + 24 ) } ,
2020-10-23 22:50:53 +02:00
z_index = 12 ,
2020-12-21 17:02:39 +01:00
} )
2020-10-19 14:38:21 +02:00
end )
2020-10-23 22:18:53 +02:00
function mcl_experience . xp_to_level ( xp )
local xp = xp or 0
local a , b , c , D
if xp > 1507 then
a , b , c = 4.5 , - 162.5 , 2220 - xp
elseif xp > 352 then
a , b , c = 2.5 , - 40.5 , 360 - xp
else
a , b , c = 1 , 6 , - xp
end
D = b * b - 4 * a * c
if D == 0 then
return math.floor ( - b / 2 / a )
elseif D > 0 then
local v1 , v2 = - b / 2 / a , math.sqrt ( D ) / 2 / a
return math.floor ( ( math.max ( v1 - v2 , v1 + v2 ) ) )
end
return 0
2020-10-19 14:38:21 +02:00
end
2020-10-23 22:18:53 +02:00
function mcl_experience . level_to_xp ( level )
if ( level >= 1 and level <= 16 ) then
return math.floor ( math.pow ( level , 2 ) + 6 * level )
elseif ( level >= 17 and level <= 31 ) then
return math.floor ( 2.5 * math.pow ( level , 2 ) - 40.5 * level + 360 )
elseif level >= 32 then
return math.floor ( 4.5 * math.pow ( level , 2 ) - 162.5 * level + 2220 ) ;
end
return 0
end
2020-10-19 14:38:21 +02:00
2020-10-23 22:18:53 +02:00
function mcl_experience . xp_to_bar ( xp , level )
local level = level or mcl_experience.xp_to_level ( xp )
local xp_this_level = mcl_experience.level_to_xp ( level )
local xp_next_level = mcl_experience.level_to_xp ( level + 1 )
2020-10-24 00:46:45 +02:00
local bar_step = 36 / ( xp_next_level - xp_this_level )
local bar = ( xp - xp_this_level ) * bar_step
return bar , bar_step , xp_next_level
end
function mcl_experience . bar_to_xp ( bar , level )
local xp_this_level = mcl_experience.level_to_xp ( level )
local xp_next_level = mcl_experience.level_to_xp ( level + 1 )
local bar_step = 36 / ( xp_next_level - xp_this_level )
local xp = xp_this_level + math.floor ( bar / 36 * ( xp_next_level - xp_this_level ) )
return xp , bar_step , xp_next_level
end
2020-10-23 22:18:53 +02:00
function mcl_experience . add_experience ( player , experience )
local name = player : get_player_name ( )
local temp_pool = pool [ name ]
local old_bar , old_xp , old_level = temp_pool.bar , temp_pool.xp , temp_pool.level
2020-10-24 11:48:39 +02:00
temp_pool.xp = math.min ( math.max ( temp_pool.xp + experience , 0 ) , max_xp )
2020-10-26 07:24:38 +01:00
if ( temp_pool.xp < temp_pool.xp_next_level ) and ( temp_pool.xp >= old_xp ) then
2020-12-14 20:04:52 +01:00
temp_pool.bar = mcl_experience.xp_to_bar ( temp_pool.xp , temp_pool.level )
2020-10-26 07:24:38 +01:00
else
2020-10-24 00:46:45 +02:00
temp_pool.level = mcl_experience.xp_to_level ( temp_pool.xp )
temp_pool.bar , temp_pool.bar_step , temp_pool.xp_next_level = mcl_experience.xp_to_bar ( temp_pool.xp , temp_pool.level )
2020-10-26 07:24:38 +01:00
end
if old_bar ~= temp_pool.bar then
2020-12-21 17:02:39 +01:00
hud_manager.change_hud ( { player = player , hud_name = " experience_bar " , element = " text " , data = " experience_bar_background.png^[lowpart: " .. math.floor ( temp_pool.bar / 36 * 100 ) .. " :experience_bar.png^[transformR270 " , } )
2020-10-26 07:24:38 +01:00
end
if experience > 0 and minetest.get_us_time ( ) / 1000000 - temp_pool.last_time > 0.01 then
2020-10-24 00:46:45 +02:00
if old_level ~= temp_pool.level then
2020-10-26 07:24:38 +01:00
minetest.sound_play ( " level_up " , { gain = 0.2 , to_player = name } )
temp_pool.last_time = minetest.get_us_time ( ) / 1000000 + 0.2
else
minetest.sound_play ( " experience " , { gain = 0.1 , to_player = name , pitch = math.random ( 75 , 99 ) / 100 } )
2020-10-19 14:38:21 +02:00
temp_pool.last_time = minetest.get_us_time ( ) / 1000000
2020-10-23 23:35:46 +02:00
end
2020-10-23 22:18:53 +02:00
end
2020-10-26 07:24:38 +01:00
if old_level ~= temp_pool.level then
hud_manager.change_hud ( { player = player , hud_name = " xp_level " , element = " text " , data = tostring ( temp_pool.level ) } )
2020-10-19 14:38:21 +02:00
end
end
--reset player level
local name
local temp_pool
local xp_amount
minetest.register_on_dieplayer ( function ( player )
2020-12-14 14:16:09 +01:00
if minetest.settings : get_bool ( " mcl_keepInventory " , false ) then
2020-10-23 22:18:53 +02:00
return
end
2020-10-19 14:38:21 +02:00
name = player : get_player_name ( )
temp_pool = pool [ name ]
2020-10-23 22:18:53 +02:00
xp_amount = temp_pool.xp
2020-10-19 14:38:21 +02:00
2020-10-23 22:18:53 +02:00
temp_pool.xp = 0
2020-11-10 08:25:35 +01:00
temp_pool.level = 0
temp_pool.bar , temp_pool.bar_step , temp_pool.xp_next_level = mcl_experience.xp_to_bar ( temp_pool.xp , temp_pool.level )
2020-10-19 14:38:21 +02:00
2020-10-23 22:50:53 +02:00
hud_manager.change_hud ( { player = player , hud_name = " xp_level " , element = " text " , data = tostring ( temp_pool.level ) } )
2020-12-21 17:02:39 +01:00
hud_manager.change_hud ( { player = player , hud_name = " experience_bar " , element = " text " , data = " experience_bar_background.png^[lowpart: " .. math.floor ( temp_pool.bar / 36 * 100 ) .. " :experience_bar.png^[transformR270 " , } )
2020-10-19 14:38:21 +02:00
2020-10-23 22:18:53 +02:00
mcl_experience.throw_experience ( player : get_pos ( ) , xp_amount )
2020-10-19 14:38:21 +02:00
end )
local name
local collector , pos , pos2
local direction , distance , player_velocity , goal
local currentvel , acceleration , multiplier , velocity
local node , vel , def
local is_moving , is_slippery , slippery , slip_factor
local size , data
local function xp_step ( self , dtime )
--if item set to be collected then only execute go to player
if self.collected == true then
if not self.collector then
self.collected = false
return
end
collector = minetest.get_player_by_name ( self.collector )
if collector and collector : get_hp ( ) > 0 and vector.distance ( self.object : get_pos ( ) , collector : get_pos ( ) ) < 5 then
self.object : set_acceleration ( vector.new ( 0 , 0 , 0 ) )
self.disable_physics ( self )
--get the variables
pos = self.object : get_pos ( )
pos2 = collector : get_pos ( )
player_velocity = collector : get_player_velocity ( )
pos2.y = pos2.y + 0.8
direction = vector.direction ( pos , pos2 )
distance = vector.distance ( pos2 , pos )
multiplier = distance
if multiplier < 1 then
multiplier = 1
end
goal = vector.multiply ( direction , multiplier )
currentvel = self.object : get_velocity ( )
if distance > 1 then
multiplier = 20 - distance
velocity = vector.multiply ( direction , multiplier )
goal = velocity
acceleration = vector.new ( goal.x - currentvel.x , goal.y - currentvel.y , goal.z - currentvel.z )
self.object : add_velocity ( vector.add ( acceleration , player_velocity ) )
elseif distance < 0.4 then
2020-12-21 15:12:24 +01:00
local xp = self._xp
local inv = collector : get_inventory ( )
local candidates = {
{ list = " main " , index = collector : get_wield_index ( ) } ,
{ list = " armor " , index = 2 } ,
{ list = " armor " , index = 3 } ,
{ list = " armor " , index = 4 } ,
{ list = " armor " , index = 5 } ,
}
local final_candidates = { }
for _ , can in ipairs ( candidates ) do
local stack = inv : get_stack ( can.list , can.index )
local wear = stack : get_wear ( )
if mcl_enchanting.has_enchantment ( stack , " mending " ) and wear > 0 then
can.stack = stack
can.wear = wear
table.insert ( final_candidates , can )
end
end
if # final_candidates > 0 then
local can = final_candidates [ math.random ( # final_candidates ) ]
local stack , list , index , wear = can.stack , can.list , can.index , can.wear
local unbreaking_level = mcl_enchanting.get_enchantment ( stack , " unbreaking " )
local uses
local armor_uses = minetest.get_item_group ( stack : get_name ( ) , " mcl_armor_uses " )
if armor_uses > 0 then
uses = armor_uses
if unbreaking_level > 0 then
uses = uses / ( 0.6 + 0.4 / ( unbreaking_level + 1 ) )
end
else
local def = stack : get_definition ( )
if def then
local fixed_uses = def._mcl_uses
if fixed_uses then
uses = fixed_uses
if unbreaking_level > 0 then
uses = uses * ( unbreaking_level + 1 )
end
end
end
if not uses then
local toolcaps = stack : get_tool_capabilities ( )
local groupcaps = toolcaps.groupcaps
for _ , v in pairs ( groupcaps ) do
uses = v.uses
break
end
end
end
uses = uses or 0
local repair = ( 65536 / uses ) * xp * 2
local new_wear = wear - repair
if new_wear < 0 then
xp = math.floor ( - new_wear / 2 )
new_wear = 0
else
xp = 0
end
stack : set_wear ( math.floor ( new_wear ) )
inv : set_stack ( list , index , stack )
if can.list == " armor " then
local armor_inv = minetest.get_inventory ( { type = " detached " , name = collector : get_player_name ( ) .. " _armor " } )
armor_inv : set_stack ( list , index , stack )
end
end
if xp > 0 then
mcl_experience.add_experience ( collector , xp )
end
2020-10-19 14:38:21 +02:00
self.object : remove ( )
end
return
else
self.collector = nil
self.enable_physics ( self )
end
end
self.age = self.age + dtime
2020-10-25 22:06:35 +01:00
if self.age > max_orb_age then
2020-10-19 14:38:21 +02:00
self.object : remove ( )
return
end
pos = self.object : get_pos ( )
if pos then
node = minetest.get_node_or_nil ( {
x = pos.x ,
y = pos.y - 0.25 ,
z = pos.z
} )
else
return
end
-- Remove nodes in 'ignore'
if node and node.name == " ignore " then
self.object : remove ( )
return
end
if not self.physical_state then
return -- Don't do anything
end
-- Slide on slippery nodes
vel = self.object : get_velocity ( )
def = node and registered_nodes [ node.name ]
is_moving = ( def and not def.walkable ) or
vel.x ~= 0 or vel.y ~= 0 or vel.z ~= 0
is_slippery = false
if def and def.walkable then
slippery = minetest.get_item_group ( node.name , " slippery " )
is_slippery = slippery ~= 0
if is_slippery and ( math.abs ( vel.x ) > 0.2 or math.abs ( vel.z ) > 0.2 ) then
-- Horizontal deceleration
slip_factor = 4.0 / ( slippery + 4 )
self.object : set_acceleration ( {
x = - vel.x * slip_factor ,
y = 0 ,
z = - vel.z * slip_factor
} )
elseif vel.y == 0 then
is_moving = false
end
end
if self.moving_state == is_moving and self.slippery_state == is_slippery then
-- Do not update anything until the moving state changes
return
end
self.moving_state = is_moving
self.slippery_state = is_slippery
if is_moving then
2020-10-23 22:18:53 +02:00
self.object : set_acceleration ( gravity )
2020-10-19 14:38:21 +02:00
else
self.object : set_acceleration ( { x = 0 , y = 0 , z = 0 } )
self.object : set_velocity ( { x = 0 , y = 0 , z = 0 } )
end
end
minetest.register_entity ( " mcl_experience:orb " , {
initial_properties = {
hp_max = 1 ,
physical = true ,
collide_with_objects = false ,
collisionbox = { - 0.2 , - 0.2 , - 0.2 , 0.2 , 0.2 , 0.2 } ,
visual = " sprite " ,
visual_size = { x = 0.4 , y = 0.4 } ,
textures = { name = " experience_orb.png " , animation = { type = " vertical_frames " , aspect_w = 16 , aspect_h = 16 , length = 2.0 } } ,
spritediv = { x = 1 , y = 14 } ,
initial_sprite_basepos = { x = 0 , y = 0 } ,
is_visible = true ,
pointable = false ,
static_save = false ,
} ,
moving_state = true ,
slippery_state = false ,
physical_state = true ,
-- Item expiry
age = 0 ,
-- Pushing item out of solid nodes
force_out = nil ,
force_out_start = nil ,
--Collection Variables
collectable = false ,
try_timer = 0 ,
collected = false ,
delete_timer = 0 ,
radius = 4 ,
on_activate = function ( self , staticdata , dtime_s )
self.object : set_velocity ( vector.new (
math.random ( - 2 , 2 ) * math.random ( ) ,
math.random ( 2 , 5 ) ,
math.random ( - 2 , 2 ) * math.random ( )
) )
self.object : set_armor_groups ( { immortal = 1 } )
self.object : set_velocity ( { x = 0 , y = 2 , z = 0 } )
2020-10-23 22:18:53 +02:00
self.object : set_acceleration ( gravity )
local xp = tonumber ( staticdata )
self._xp = xp
size = xp_to_size ( xp )
self.object : set_properties ( {
2020-10-19 14:38:21 +02:00
visual_size = { x = size , y = size } ,
glow = 14 ,
} )
self.object : set_sprite ( { x = 1 , y = math.random ( 1 , 14 ) } , 14 , 0.05 , false )
end ,
enable_physics = function ( self )
if not self.physical_state then
self.physical_state = true
self.object : set_properties ( { physical = true } )
self.object : set_velocity ( { x = 0 , y = 0 , z = 0 } )
2020-10-23 22:18:53 +02:00
self.object : set_acceleration ( gravity )
2020-10-19 14:38:21 +02:00
end
end ,
disable_physics = function ( self )
if self.physical_state then
self.physical_state = false
self.object : set_properties ( { physical = false } )
self.object : set_velocity ( { x = 0 , y = 0 , z = 0 } )
self.object : set_acceleration ( { x = 0 , y = 0 , z = 0 } )
end
end ,
on_step = function ( self , dtime )
xp_step ( self , dtime )
end ,
} )
minetest.register_chatcommand ( " xp " , {
2020-10-24 11:48:39 +02:00
params = S ( " [[<player>] <xp>] " ) ,
2020-10-23 22:58:13 +02:00
description = S ( " Gives a player some XP " ) ,
2020-10-19 14:38:21 +02:00
privs = { server = true } ,
func = function ( name , params )
local player , xp = nil , 1000
local P , i = { } , 0
for str in string.gmatch ( params , " ([^ ]+) " ) do
i = i + 1
P [ i ] = str
end
if i > 2 then
return false , S ( " Error: Too many parameters! " )
end
if i > 0 then
xp = tonumber ( P [ i ] )
end
if i < 2 then
player = minetest.get_player_by_name ( name )
end
if i == 2 then
player = minetest.get_player_by_name ( P [ 1 ] )
end
2020-10-23 22:18:53 +02:00
if not xp then
return false , S ( " Error: Incorrect value of XP " )
2020-10-19 14:38:21 +02:00
end
if not player then
return false , S ( " Error: Player not found " )
end
2020-10-23 22:18:53 +02:00
mcl_experience.add_experience ( player , xp )
2020-10-24 00:46:45 +02:00
local playername = player : get_player_name ( )
minetest.chat_send_player ( name , S ( " Added @1 XP to @2, total: @3, experience level: @4 " , tostring ( xp ) , playername , tostring ( pool [ playername ] . xp ) , tostring ( pool [ playername ] . level ) ) )
2020-10-19 14:38:21 +02:00
end ,
} )
function mcl_experience . throw_experience ( pos , amount )
2020-10-23 22:18:53 +02:00
local i , j = 0 , 0
local obj , xp
while i < amount and j < 100 do
xp = math.min ( math.random ( 1 , math.min ( 32767 , amount - math.floor ( i / 2 ) ) ) , amount - i )
obj = minetest.add_entity ( pos , " mcl_experience:orb " , tostring ( xp ) )
if not obj then
return false
end
obj : set_velocity ( {
x = math.random ( - 2 , 2 ) * math.random ( ) ,
y = math.random ( 2 , 5 ) ,
z = math.random ( - 2 , 2 ) * math.random ( )
} )
i = i + xp
j = j + 1
end
2020-10-19 14:38:21 +02:00
end