commit 4e63572b7ca57f5751d104bc830d601bb86b27a9 Author: Zenon Seth Date: Thu Nov 17 21:05:51 2022 +0000 Treasure chest squashed commit of previous work diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..e864bca --- /dev/null +++ b/README.txt @@ -0,0 +1,85 @@ +== Treasure Chest mod for Minetest == + +01. What is it? +02. License Info +03. Current Version +04. Installation Instructions +05. Dependencies +06. Bugs/contact info + +01. What is it? +=============== +Treasure Chest is a small mod for the Minetest game that adds a kind of chest made for world designers. +The chest has no crafting recipe, so it has to be obtained by /giveme or other commands. + +The intended use and original idea, comes from trying to design challenges in a survival world, +and having some way to automatically reward players who complete the challenges. + +The rewards can be somewhat randomized, with a probability for each one,and can reset after +a specified time, on a per user basis, after being given out. + +When the chest is used by someone without the "give" privilege, the chest will attempt to give +a copy of the items inside it (with some chance) to the user. The chest then records the last time +this user has tried to get the items, and the user will then have to wait for a timeout period to +expire before he/she can have a chance of obtaining the items again. This timeout period is per-user. + +For someone with the "give" privilege, the chest will display a GUI that allows you to configure it. + + - 1st input: Refresh Time: An integer value + The number of minutes of gametime that must pass before the chest can give its items out again. + This is on a per-user basis, so two users can always obtain the reward if they use the chest, + but if the same user tries to use it before the refresh timeout, he will get nothing. + + - 2nd line: Six input: Integer values + Probabilities, ranging 0..100, of how likely a reward is to be given to a user. Randomly + determined each time the chest is used. Associated with the inventory slot below each one + + - 3rd line: Six inventory slots + The items to be given out, as associated by the probabilities above them. Each slot can hold + a regular item stack. Items stacks are given out as a whole, so the user will get either the + whole item stack, or nothing from that slot. Item stacks in these slots are not taken by + regular users using the chest, instead they get copied. + +02. License Info: +================= +License for Code +---------------- + +Copyright (C) 2017-2022 + +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. + +License for Textures, Models and Sounds +--------------------------------------- +CC-BY-SA 3.0 UNPORTED. Created by Zenon Seth + + +03. Current Version +=================== +v1.0 : Mod release + +04. Installation Instructions +============================= +Copy the entire folder containing this file into your minetest/mods folder. +When running the game through GUI, configure world, select and enable the mod. +When running a server through command line, edit world.mt and mark this mod as 'true' to be loaded + +05. Dependencies +================ +Minetest engine and Minetest game (see https://www.minetest.net) + +06. Bugs/contact info +===================== +Submit bugs on github: https://github.com/ZenonSeth/treasure_chest diff --git a/depends.txt b/depends.txt new file mode 100644 index 0000000..331d858 --- /dev/null +++ b/depends.txt @@ -0,0 +1 @@ +default \ No newline at end of file diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..c6b5859 --- /dev/null +++ b/init.lua @@ -0,0 +1,238 @@ + + +dofile(minetest.get_modpath("treasure_chest") .. "/utils.lua") + +local openedTreasureChestConfigs = {}; + +local metaStrType = "type"; +local metaExpectedType = "traesurechest"; +local metaStrOwner = "owner"; +local metaIntRefresh = "refresh"; +local metaInt0p = "0p"; +local metaInt1p = "1p"; +local metaInt2p = "2p"; +local metaInt3p = "3p"; +local metaInt4p = "4p"; +local metaInt5p = "5p"; + +local fieldRefresh = "refresh_interval"; +local fieldI0P = "i0p"; +local fieldI1P = "i1p"; +local fieldI2P = "i2p"; +local fieldI3P = "i3p"; +local fieldI4P = "i4p"; +local fieldI5P = "i5p"; +local buttonExit = "exit"; + +local strDescription = "A chest that gives semi-randomized rewards per player"; +local strOneTime = "This is a one-time use chest, and you already opened it!"; +local strTooSoon = "To get another reward come back in "; +local strFromRefreshLabel = "Refresh time, in minutes, integer. E.g.: 60 = 1 hour, 1440 = 1 day, 10080 = 1 week"; +local strProbabiltiesLabel = "Item probability of being given, integer, range 0..100: 0 = never, 100 = always"; + +minetest.register_node("treasure_chest:treasure_chest", { + description = strDescription, + + tiles = { + "treasurechest_u.png", + "treasurechest_d.png", + "treasurechest_r.png", + "treasurechest_l.png", + "treasurechest_b.png", + "treasurechest_f.png" + }, + + groups = {cracky = 3}, + drop = "", + paramtype2 = "facedir", + can_dig = function(pos, player) + local playerName = player:get_player_name(); + local meta = minetest.get_meta(pos); + local privs = minetest.get_player_privs(playerName); + local owner = meta:get_string(metaStrOwner); + + if player:get_player_name() == owner or privs.give then + return true; + else + return false; + end + end, + + after_place_node = + function(pos, placer, itemstack, pointed_thing) + local meta = minetest.get_meta(pos); + + meta:set_string(metaStrOwner, placer:get_player_name()); + meta:set_int(metaIntRefresh, 1); + meta:set_string(metaStrType, metaExpectedType); + meta:set_int(metaInt0p, 100); + meta:set_int(metaInt1p, 100); + meta:set_int(metaInt2p, 100); + meta:set_int(metaInt3p, 100); + meta:set_int(metaInt4p, 100); + meta:set_int(metaInt5p, 100); + + local inv = meta:get_inventory(); + inv:set_size("main", 6); + end, + + on_rightclick = + function(nodePos, node, player, itemstack, pointed_thing) + local playerName = player:get_player_name(); + local spos = nodePos.x..","..nodePos.y..","..nodePos.z; + local gameTime = minetest.get_gametime(); + local privs = minetest.get_player_privs(playerName); + + local meta = minetest.get_meta(nodePos); + local owner = meta:get_string(metaStrOwner); + local refresh = meta:get_int(metaIntRefresh); + local i0p = meta:get_int(metaInt0p); + local i1p = meta:get_int(metaInt1p); + local i2p = meta:get_int(metaInt2p); + local i3p = meta:get_int(metaInt3p); + local i4p = meta:get_int(metaInt4p); + local i5p = meta:get_int(metaInt5p); + + -- clean up some metadata + local tmp = meta:to_table() + local newMetaTable = tmp + if refresh > 0 then + for k,v in pairs(tmp["fields"]) do + if k ~= metaStrOwner + and k ~= metaStrType + and k ~= metaIntRefresh + and k ~= metaInt0p + and k ~= metaInt1p + and k ~= metaInt2p + and k ~= metaInt3p + and k ~= metaInt4p + and k ~= metaInt5p then + local tv = tonumber(v) + if tv then + diff = gameTime - tv + if diff > refresh * 60 then + newMetaTable["fields"] = table.removeKey(newMetaTable["fields"], k) + end + end + end + end + meta:from_table(newMetaTable) + end + -- end clean-up + + if privs.server or owner == playerName then + openedTreasureChestConfigs[playerName] = nodePos; + minetest.show_formspec(playerName, "treasure_chest:setup_inventory", + "size[8,8]" .. + + "field[0.2,0.2;7.0,0.9;"..fieldRefresh..";"..strFromRefreshLabel..";".. refresh .."]".. + + "label[0.2,0.6;"..strProbabiltiesLabel.."]".. + + "field[0.5,1.2;1,1;"..fieldI0P..";;"..i0p.."]".. + "field[1.5,1.2;1,1;"..fieldI1P..";;"..i1p.."]".. + "field[2.5,1.2;1,1;"..fieldI2P..";;"..i2p.."]".. + "field[3.5,1.2;1,1;"..fieldI3P..";;"..i3p.."]".. + "field[4.5,1.2;1,1;"..fieldI4P..";;"..i4p.."]".. + "field[5.5,1.2;1,1;"..fieldI5P..";;"..i5p.."]".. + + "list[nodemeta:"..spos..";main;0.2,1.8;6.0,1.0;]".. + "button_exit[1.0,2.8;3.0,1.0;"..buttonExit..";Save & Close]".. + + "list[current_player;main;0.0,4.0;8.0,4.0;]"); + + else + local lastTime = meta:get_int(playerName); + local diff; + if lastTime and lastTime > 0 then + diff = gameTime - lastTime; + else + diff = refresh*60 + 1; + end + + local singleUseUsed = (lastTime ~= 0) and (refresh < 0); + local notSingleUseButUsed = (refresh > 0) and (lastTime ~= 0) and (diff <= refresh*60); + + if singleUseUsed or notSingleUseButUsed then + local reason + if refresh < 0 then + reason = strOneTime + else + diff = (lastTime + refresh * 60) - gameTime + diff = math.floor(diff / 60 + 0.5) + local time = "" + if diff <= 1 then + time = "1 minute" + elseif diff < 60 then + time = diff .. " minutes" + elseif diff < 1440 then + time = math.floor(diff/60 + 0.5) .. " hours" + else + time = math.floor(diff/1440 + 0.5) .. " days" + end + reason = strTooSoon .. time + end + + minetest.chat_send_player(playerName, reason); + + else + local nodeInv = meta:get_inventory(); --minetest.get_inventory({type="node", pos=nodePos}); + local playerInv = player:get_inventory(); + local playerWieldedItem = player:get_wielded_item(); + -- bit of hard-coding, relying we only have 6 slots. Consider that the formspec is also hardcoded, it's not a huge deal + for index=0,5,1 do + local metaAccessString = index.."p"; + local probability = meta:get_int(metaAccessString); + print("wield list name = "..player:get_wield_list()); + if (randomCheck(probability)) then + local itemStackToAdd = nodeInv:get_stack("main", index+1); -- +1 for inventory indexing begins at 1 + itemStackToAdd = playerInv:add_item("main", itemStackToAdd); + if not itemStackToAdd:is_empty() then + minetest.item_drop(itemStackToAdd, player, player:get_pos()); + end + end + end + meta:set_int(playerName, gameTime); + return playerInv:get_stack(player:get_wield_list(), player:get_wield_index()); -- the itemstack we have as input may no longer be valid due to the add_item call above + end + end + end + }) + + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if formname == "treasure_chest:setup_inventory" then + local playerName = player:get_player_name(); + + if (not fields[fieldRefresh]) then + -- User cancelled, quit now + return true; + end + + local pos = openedTreasureChestConfigs[playerName]; + if pos == nil then + return; + end + openedTreasureChestConfigs[playerName] = nil; + + local meta = minetest.get_meta(pos); + if meta:get_string(metaStrType) ~= metaExpectedType then + return; + end + + meta:set_int(metaIntRefresh, clamp(toNum(fields[fieldRefresh], meta:get_int(metaIntRefresh)), -1, nil) ); + meta:set_int(metaInt0p, clamp(toNum(fields[fieldI0P], meta:get_int(metaInt0p)), 0, 100)); + meta:set_int(metaInt1p, clamp(toNum(fields[fieldI1P], meta:get_int(metaInt1p)), 0, 100)); + meta:set_int(metaInt2p, clamp(toNum(fields[fieldI2P], meta:get_int(metaInt2p)), 0, 100)); + meta:set_int(metaInt3p, clamp(toNum(fields[fieldI3P], meta:get_int(metaInt3p)), 0, 100)); + meta:set_int(metaInt4p, clamp(toNum(fields[fieldI4P], meta:get_int(metaInt4p)), 0, 100)); + meta:set_int(metaInt5p, clamp(toNum(fields[fieldI5P], meta:get_int(metaInt5p)), 0, 100)); + return true + end + return false +end) + +minetest.register_on_leaveplayer(function(player) + local playerName = player:get_player_name() + openedTreasureChestConfigs[playerName] = nil; +end) diff --git a/textures/treasurechest_b.png b/textures/treasurechest_b.png new file mode 100644 index 0000000..1f1149d Binary files /dev/null and b/textures/treasurechest_b.png differ diff --git a/textures/treasurechest_d.png b/textures/treasurechest_d.png new file mode 100644 index 0000000..7fd9ec0 Binary files /dev/null and b/textures/treasurechest_d.png differ diff --git a/textures/treasurechest_f.png b/textures/treasurechest_f.png new file mode 100644 index 0000000..82dd72d Binary files /dev/null and b/textures/treasurechest_f.png differ diff --git a/textures/treasurechest_l.png b/textures/treasurechest_l.png new file mode 100644 index 0000000..501f1e9 Binary files /dev/null and b/textures/treasurechest_l.png differ diff --git a/textures/treasurechest_r.png b/textures/treasurechest_r.png new file mode 100644 index 0000000..6de8f83 Binary files /dev/null and b/textures/treasurechest_r.png differ diff --git a/textures/treasurechest_u.png b/textures/treasurechest_u.png new file mode 100644 index 0000000..fed8c03 Binary files /dev/null and b/textures/treasurechest_u.png differ diff --git a/utils.lua b/utils.lua new file mode 100644 index 0000000..52ffda9 --- /dev/null +++ b/utils.lua @@ -0,0 +1,81 @@ +function table.removeKey(t, k) + local i = 0 + local keys, values = {},{} + for k,v in pairs(t) do + i = i + 1 + keys[i] = k + values[i] = v + end + + while i>0 do + if keys[i] == k then + table.remove(keys, i) + table.remove(values, i) + break + end + i = i - 1 + end + + local a = {} + for i = 1,#keys do + a[keys[i]] = values[i] + end + + return a +end + +function splitStringToTable(inputString, splitter) + local ret = {}; + local tmp; + + if inputString == nil then return nil; end + + if (splitter == nil) then + table.insert(ret, inputString); + return ret; + end + + -- print("inputString: " .. inputString .. ", splitter:" .. splitter); + local found = true; + while found do + local s,e = inputString:find(splitter); + if s == nil then + table.insert(ret, inputString); + found = false; + else + -- print("s/e=" .. s .. "/" .. e); + tmp = inputString:sub(0,s - 1); + table.insert(ret, tmp); + inputString = inputString:sub(e + 1); + end + end + -- for k,v in pairs(ret) do print(k,v) end + return ret; +end + + +function tableLength(table) + if (table == nil) then return 0; end + local count = 0 + for _ in pairs(table) do count = count + 1 end + return count +end + +function clamp(value, min, max) + if value == nil then return nil; end + if max == nil and min == nil then return value; end + if min == nil then return math.min(value, max); end + if max == nil then return math.max(value, min); end + return math.max(math.min(value, max), min); +end + +function toNum(number, default) + default = default or 0; + return tonumber(number) or default; +end + +function randomCheck(normalizedIntProb, minValue, maxValue) + minValue = toNum(minValue, 1); + maxValue = toNum(maxValue, 100); + return math.random(1,100) <= toNum(normalizedIntProb); +end