make drops more frequent.
[minetest-xpmod:xp.git] / init.lua
1 -- Copyright 2012 Andrew Engelbrecht.
2 -- This file is licensed under the WTFPL.
3
4 ----------------------------------
5
6 -- Some variables you can change:
7 ----------------------------------
8
9 -- How often (in seconds) xplevels file saves
10 xp_save_delta = 10
11 -- Max level a player can become; 0 means max level of 0.
12 xp_max_level = 32
13
14 ----------------------------------
15
16 -- some mod variables:
17 ----------------------------------
18
19 xp_modver = "0.0.3"
20 xp_ffver = 1
21 xp_ffversupport = {1}
22 xp_file_name = "xplevels"
23
24 xp_xplevels_file = minetest.get_worldpath() .. "/" .. xp_file_name
25 xp_xplevels = {}
26 xp_playert = {}
27 -- dynamically updated to note changes since last save to file:
28 xp_changed = false
29 -- automatically populated with list of xp needed to gain next level
30 xp_lptable = nil
31 -- create a global random seed
32 xp_prand = PseudoRandom(os.time())
33
34 ----------------------------------
35
36 -- include files:
37 ----------------------------------
38
39 dofile(minetest.get_modpath('xp').."/filehandling.lua")
40
41 ----------------------------------
42
43 -- function definitions:
44 ----------------------------------
45
46 -- returns true if player has ever logged into the server
47 -- it's different than minetest.is_player() since it accepts the name as a string,
48 -- and because you don't need to run a function that returns a player obj only for *online* players.
49 local function xp_is_player(name)
50     local file
51
52     if xp_playert[name] == nil then
53         file = io.open(minetest.get_worldpath().."/players/"..name, "r")
54         if file ~= nil then
55             xp_playert[name] = true
56             io.close(file)
57         else
58             xp_playert[name] = false
59         end
60     end
61
62     return xp_playert[name]
63 end
64
65 -- returns the number of xp required to gain the next level.
66 -- pass it the player's current level.
67 local function xp_lastpoint(level)
68     local i
69
70     if xp_lptable == nil then
71         xp_lptable = {}
72         for i = 0,xp_max_level - 1 do
73             xp_lptable[i] = (i + 1)^2 * 10
74         end
75     end
76     return xp_lptable[level]
77 end
78
79 -- initializes a player's experience points and level for a given skill
80 local function xp_initpxp(name, skill)
81     if xp_xplevels[name] == nil then
82         xp_xplevels[name] = {}
83     end
84     if xp_xplevels[name][skill] == nil then
85         xp_xplevels[name][skill] = {xp = 0, level = 0}
86     end
87 end
88
89 -- gives npoints additional xp points for the player in the specified skillset
90 local function xp_gainxp(name, skill, npoints)
91     local stat, appnd, gainedlevel
92
93     if npoints < 0 then
94         return "negative"
95     end
96
97     if xp_is_player(name) == false then
98         return "non-player"
99     end
100
101     xp_initpxp(name, skill)
102     stat = xp_xplevels[name][skill]
103
104     if stat.level >= xp_max_level then
105         return "level-max"
106     end
107
108     -- the file needs to be updated in the future:
109     xp_changed = true
110
111     gainedlevel = false
112     while npoints > 0 do
113
114         if stat.xp + npoints < xp_lastpoint(stat.level) then
115             stat.xp = stat.xp + npoints
116             if gainedlevel == true then
117                 break
118             else
119                 return "gained-xp"
120             end
121         else
122             npoints = npoints - (xp_lastpoint(stat.level) - stat.xp)
123
124             stat.level = stat.level + 1
125             stat.xp = 0
126
127             if stat.level >= xp_max_level then
128                 appnd = " (max)"
129                 break
130             else
131                 appnd = ""
132             end
133
134             gainedlevel = true
135         end
136     end
137     minetest.chat_send_player(name, "Welcome to level "..stat.level.." in "..skill.."!"..appnd)
138     minetest.log("action", "xp mod: "..name.." gained level "..stat.level.." in "..skill..appnd..".")
139
140     return "gained-level"
141 end
142
143 -- prints the level and xp for a player in the specified skillset.
144 -- if no skillset is selected, then stats for every skillset are printed.
145 -- output goes to the player's chat console.
146 local function xp_printxp(name, skill)
147     local i,v,stat,appnd
148
149     if xp_xplevels[name] == nil then
150         minetest.chat_send_player(name, "You have no experience in anything.")
151         return
152     end
153
154     if skill == "" then
155         for i,v in pairs(xp_xplevels[name]) do
156             if i ~= "" then
157                 xp_printxp(name, i)
158             end
159         end
160     else
161         stat = xp_xplevels[name][skill]
162         if stat == nil then
163             minetest.chat_send_player(name, "You don't have experience in '"..skill.."'. (check your spelling.)")
164         else
165             xp_initpxp(name, skill)
166
167             if stat.level >= xp_max_level then
168                 appnd = ". (max)"
169             else
170                 appnd = ", plus "..stat.xp.."/"..xp_lastpoint(stat.level).." experience points."
171             end
172             minetest.chat_send_player(name, "You are level "..stat.level.." in "..skill..appnd)
173         end
174     end
175 end
176
177 -- returns the probability of getting a drop, based on a player's level
178 -- if level_max is set to the default of 32, then:
179 -- at level 0, the prob. is about 1/100,000.
180 -- at level_max or above, it's about 1/1,000.
181 local function xp_gotdrop(level)
182     local prob, randn
183
184     if level > xp_max_level then
185         level = xp_max_level
186     end
187
188     prob = (level + 3)^2 / (xp_max_level + 3)^2 / 300
189     randn = xp_prand:next(0, 32767) / 32767
190
191     if randn < prob then
192         return true
193     else
194         return false
195     end
196 end
197
198 -- gives a mese block to player named 'name'.
199 -- if thier inventory is full, then it falls on the ground.
200 local function xp_givemese(name)
201     local player, pos
202
203     player = minetest.env:get_player_by_name(name)
204     inv = player:get_inventory()
205
206     if inv:room_for_item('main', 'default:mese') then
207         inv:add_item('main', 'default:mese')
208     else
209         pos = player:getpos()
210         pos.y = pos.y + 1.5
211         minetest.spawn_item(pos, 'default:mese')
212     end
213 end
214
215
216 ----------------------------------
217
218 -- priviledge registration:
219 ----------------------------------
220
221 minetest.register_privilege("givexp", {"Can give xp to others with /givexp", give_to_singleplayer = false})
222
223 ----------------------------------
224
225 -- callback registration:
226 ----------------------------------
227
228 -- it raises the xp of a player by the specified number of points.
229 minetest.register_chatcommand("givexp", {
230     params = "<username> <skill> <num-xp>",
231     description = "Give a player experience points",
232     privs = {givexp=true},
233     func = function(caller, param)
234         local ign, name, skill, numxp, result
235
236         -- matches two words: the name and the skill
237         ign,ign,name,skill,numxp = string.find(param, "^([^%s]+)%s+([^%s]+)%s+(%d+)%s*$")
238
239         if numxp == nil then
240             minetest.chat_send_player(caller, "Incorrect usage. See: /help givexp")
241         else
242             result = xp_gainxp(name,skill,tonumber(numxp))
243             if result == "non-player" then
244                 minetest.chat_send_player(caller, name.." isn't a player.")
245             elseif result == "level-max" then
246                 minetest.chat_send_player(caller, name.."'s "..skill.." level is already maxed-out.")
247             elseif result == "gained-xp" or result == "gained-level" then
248                 minetest.chat_send_player(caller, name.." is now level "..xp_xplevels[name][skill].level.." plus "..xp_xplevels[name][skill].xp.." xp in "..skill..".")
249                 minetest.log("action", "xp mod: ("..caller.." gave "..name.." "..numxp.." xp in "..skill..".)")
250             end
251         end
252     end,
253 })
254
255 -- shows the xp for the calling player, for a given skill, or if none is listed, then all of them.
256 minetest.register_chatcommand("showxp", {
257     params = "<skill (opt)>",
258     description = "Show your experience points",
259     privs = {},
260     func = xp_printxp,
261 })
262
263 -- every time a player places an object, they get one extra xp for this skill
264 minetest.register_on_placenode(function(pos, newnode, placer, oldnode)
265     local name = placer:get_player_name()
266     xp_gainxp(name, "construction", 1)
267 end)
268
269 -- every time a player digs an object, they get one extra xp for this skill
270 -- also, for now, the player gets mese somewhat randomly, based on level.
271 minetest.register_on_dignode(function(pos, node, player)
272     local name = player:get_player_name()
273     xp_gainxp(name, "digging", 1)
274     if xp_gotdrop(xp_xplevels[name].digging.level) then
275         xp_givemese(name)
276     end
277 end)
278
279 -- every time a player joins the game, add their name to the list of existing players.
280 -- if their player file wasn't readable, this works around the issue. also, if this is a new player,
281 -- then they will be added to the table even if they were previously marked as non-existent.
282 minetest.register_on_joinplayer(function(player)
283     local name = player:get_player_name()
284     xp_playert[name] = true
285 end)
286
287 ----------------------------------
288
289 -- execute at the start of the game:
290 ----------------------------------
291
292 -- before this file is done being read, this function must be called
293 -- to load player xp and levels for each skill
294 xp_loadxp()
295