From 4e63572b7ca57f5751d104bc830d601bb86b27a9 Mon Sep 17 00:00:00 2001 From: Zenon Seth Date: Thu, 17 Nov 2022 21:05:51 +0000 Subject: [PATCH] Treasure chest squashed commit of previous work --- README.txt | 85 +++++++++++++ depends.txt | 1 + init.lua | 238 +++++++++++++++++++++++++++++++++++ textures/treasurechest_b.png | Bin 0 -> 655 bytes textures/treasurechest_d.png | Bin 0 -> 603 bytes textures/treasurechest_f.png | Bin 0 -> 687 bytes textures/treasurechest_l.png | Bin 0 -> 669 bytes textures/treasurechest_r.png | Bin 0 -> 663 bytes textures/treasurechest_u.png | Bin 0 -> 642 bytes utils.lua | 81 ++++++++++++ 10 files changed, 405 insertions(+) create mode 100644 README.txt create mode 100644 depends.txt create mode 100644 init.lua create mode 100644 textures/treasurechest_b.png create mode 100644 textures/treasurechest_d.png create mode 100644 textures/treasurechest_f.png create mode 100644 textures/treasurechest_l.png create mode 100644 textures/treasurechest_r.png create mode 100644 textures/treasurechest_u.png create mode 100644 utils.lua 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 0000000000000000000000000000000000000000..1f1149dee0ce50bd85f93a9eb6170295d5c3316c GIT binary patch literal 655 zcmV;A0&x9_P)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00H+&L_t(I%SDsRjuS}`MbFK8 z+0CO3Xf(5dpJBsiz+bXq!G^z>kw5|@dZq_=ciFD0%(yJFuuDosC?f8SSA~B5^UeS? ziSsw*Do;z5*V}3v+Dpv-2-8p6Gw{b(#``^+O(N&YIc#^;_x&3H<6)xj#Qh<7{>MvD zNBf)Y^_vfjbI>JN!YGMtD@dXh6vJFh1hff0zQ2aFp{5_dus;@-Y;0(o#L$S8fE8TA z+?|5M5}dMO_ZMJVO2DI6NT0as6MsJLYF7!uW(kpGrp1ieO?JM?nHflwvxG?I6OyrXlwKaUQ)5NgzYW*y{tZND>HgG3$&;?b8dvDMt-nNu-Mo zCaPcxUqHj|qL;M-;2bPORF%GgN1bW~;Tl!qLXg(yP4$wDZ(?Q=PEODGo*VG~Dch>w+2~5wks|FkHr(i0e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00F~EL_t(I%XN}XZd5@Gguin4 z%S|*Jt)8p2mr`imb-;H8=I4$PP@0T-N4*nvxn^jhh@b7wO34W&Ey3h?y2Mh zv<>gxf8>5i3`o$^goL!##xgBMOY`;H2l#nIB~D=e#bHUB;cN?X0TLIe_21(GO&n1Z{-T+OD0pc%YS0p`Ok1O!G$x|tAy8Zb$e zBBZ2S%q^h`+zCpsIo*SgnHt_Dk|RisF%QI4MN8Jt_4#~D1XU(I67lffN6;9|yFCIp z0`xo5X40BN9MwphW@9I|C^9GYu~83zo(q}KF$@u01 pxPZA)HuXh-J;3jIz2Dzn^A~6M<`ee zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00I|DL_t(I%WadfZWBQehQFD; z^Vx}$6eLL0c?7zYJOD3%5J;4H0p5a+E(I+GJrukEv=ImqDIg*fix?-t*yr1wq1bgo z5Zjzqv-90Q|Gz8KFHf9**R12I9q{Vgl5uO?ntbBu?lTzH5cK-o_!M0=aQYP{GZ>FZ z(<2^?9<(5}i4;5u_;q38)BLO<;SyBH;uCbEl_5>qZxaekIg{ zii<_O@-YLtGlD`B6jeb16#;}=gk?06gW&n^$Q2-`(A*(8tn!pq!2xj)!OJ$T&NUTu zX9dvxz}*o6DR9F{Kr+YObCZK0*p>dx0?;NGiUbjGa-{hS;({57cpe}bcq#ytN^=tW zmkM_<&kaE!1fdr+O9_gl8A>v+IS3?o%&*OG0VAtX&Atc_5J-krn^gI3E6e1ZFCvQ5 zs|9x<2?U2W8cD$2>5^kbL2S=UjM@<0Ih!Y@ZK5CK+Ii1LS{bhiN?is|=E#3v<7&0> z=Dw+JmX8mq(#c0cy~XceKUwPsE@qM8dWDKYy#sNIS%;euLd9r&$?fevq22&d{s1kY V3`DdJh(!PZ002ovPDHLkV1lrh7PtTa literal 0 HcmV?d00001 diff --git a/textures/treasurechest_l.png b/textures/treasurechest_l.png new file mode 100644 index 0000000000000000000000000000000000000000..501f1e91be74d57869555be98298bc5cb421d270 GIT binary patch literal 669 zcmV;O0%HA%P)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00IR`L_t(I%WacAZd5@Kgum{2 z`_^9DEEa+YaR5>Rge(FQ$O#BZI06Bo6G*rNmmo#>2#|0A7J=}}HoMk(*Y9V#2;N)# zBRZQ=b#+xukIlZk5Vv-?UPtW?`={>#csG8{&9uA<$N|TP*zEf&bsaO>)o+S&n}y%u zP5Qv6!v=uqWWw=-eW-qt24fiChG7Bo6$QD4qhIVjJz&|00IVFbcktoLz$ec>lA;9+ z2R?s&34qfIie0P+4wEtVif_;FGM!AAPA1&ne?)0PmQjZ|InMw{qM%3+6-_HPlCe8V zl!c*o&f~Wak%4J_>3TQ&k zMk3dz>kvEnBRRDoLKp%BYZ4&2kz5Q?7fRZ>cR}FR?SS{5WndV1Cp`hKE~CMEY3fBs zkQYDL9yo@`0U04d2$F;M1Tv$_5Hi-*|0zH)TL#Guvu?|BKr+MqVx>cnqxV&AU;0ef ztACx#B{$6M;$ps{V0s9{8~^2?I=OUk>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00I9=L_t(I%WadpZWJ*ThQDKb zE|68OP*Ea8kr#l5f(nU-XMiW5qKzn!=y(~LKt&meo(hSGYeGVnWV16nwttG5>~2s_ zbtL=zHy_){muKRNI!hHV#%}<4z4n}uZ!TXrI)&U~bAa)b;~$?{UGW^9dtTmr&%0k8 z00;Z~e82OQNt4-J3wSfwx($E65+q@VKY8$ImzYK24J&K5-(Dts_+*bZ3h!XE^6Y-R z3+4zZi2xi<5)#yfQOu_7J-)@k&IbUtckW}($PR=!Yg3PEp5PI&9aDP48LZcyDj1$I zH(uStoF~x0^#BsnE^|DMkTOUFGvjoUki@8TY}DYK0ifJKf*cH!a0YFJB!ig2RRn;I z&2^HX2o9MeVHkiwHmEfNL=t#+k&N{K;4pes!7damfdFP;PBb4Zc%;yB5U|zJUEJ4094mjkwn`;GfQ*n0{md2^hUT~HbIO~j+Q=CT8g$wi+-ix%4$0p|0bkw xpJe zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00HVrL_t(I%YBo*Zqz^+MbG_a zyc=g%*$7gkgOZX0@dOm)6_D}@H1HUC2RfhuS_+;3K|_V0O%o;jY+$pywr4(yapDa~ zjAd!;(VRKDzShrAkDLUMAsGK1%M++>K&&8TXikaD>hiET0>E{cyTi_{ehw1obW8-` z+X)X3-jN)TgCNQn3o?-0>2QP!*WvB^LsC2M0iI~Hvud54+Ndj|GBGxz&6ywTOqU!p zXj^Awa@L*J>)ZpjZX!f5XF4**l~EatOw@{4NVG|qiB;#EFOwKYwx_cm05mBRO#~C( zzcUFLY(-%T7!DB@b`{K!RC0(xNFqCQ4%s26AxNogO8p5rEqXw+AWBR}MlcZN&g$bv zxctgYmxf_77&IxD8aSBBKJ)Sg1eoi}G+3E0FMbVxOi`A~q5&=l+H6Cysd_*?*&P6; z5}pI0 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