write something there

This commit is contained in:
N-Nachtigal 2025-05-04 16:01:41 +02:00
commit b4b6c08f4f
8546 changed files with 309825 additions and 0 deletions

4
mods/researcher/.gitattributes vendored Normal file
View file

@ -0,0 +1,4 @@
*.xcf export-ignore
*.blend export-ignore
*.bbmodel export-ignore
*.json export-ignore

View file

@ -0,0 +1,13 @@
# Sounds
- `sounds/researcher_level_up.ogg` derived from ["Game Pickup" by INEBA](https://freesound.org/people/IENBA/sounds/698768/) at FreeSound.org, licensed [CC0](https://creativecommons.org/publicdomain/zero/1.0/)
- `sounds/researcher_research.ogg` derived from ["Supershort Ping, Or Short Notification.wav" by MATRIXXX_](https://freesound.org/people/MATRIXXX_/sounds/495650/) at FreeSound.org, licensed [CC0](https://creativecommons.org/publicdomain/zero/1.0/)
- `sounds/researcher_duplicate.ogg` derived from ["Rattle Egg" by qubodup](https://freesound.org/people/qubodup/sounds/171935/) at FreeSound.org, licensed [CC0](https://creativecommons.org/publicdomain/zero/1.0/)
# Textures
- `textures/researcher_gui_hb_bg.png` is a copy of [gui_hb_bg.png in Minetest Game](https://github.com/minetest/minetest_game/blob/master/mods/default/textures/gui_hb_bg.png) by paramat et. al., licensed [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/), [CC BY 3.0](http://creativecommons.org/licenses/by/3.0/), or [CC0](https://creativecommons.org/publicdomain/zero/1.0/); see [Minetest Game license.txt](https://github.com/minetest/minetest_game/blob/master/mods/default/license.txt) for more info
- `textures/researcher_research_table_surface.png` made by Mirtilo as part of the [Baunilha texture pack](https://content.minetest.net/packages/Mirtilo/baunilha/), licensed [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
- `textures/researcher_research_table_frame.png` made by JoeEnderman, dedicated to the public domain via [CC0](https://creativecommons.org/publicdomain/zero/1.0/)
- `textures/researcher_icon*` files derived from "Erlenmeyer icon" by Lorc and downloaded from <https://game-icons.net/1x1/lorc/erlenmeyer.html>, licensed [CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)
- The light bulb icon used in `screenshot.png` and `screenshot.xcf` is derivied from ["Light Bulb by Anamika Singh"](https://thenounproject.com/icon/light-bulb-6757220/), licensed [CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)

13
mods/researcher/LICENSE Normal file
View file

@ -0,0 +1,13 @@
MIT License
Copyright © 2024 EmptyStar <https://github.com/EmptyStar>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---
Additional license information and appropriate attributions can be found in `CREDITS.md`.

76
mods/researcher/README.md Normal file
View file

@ -0,0 +1,76 @@
Researcher
==========
Unlock the limitless creative potential of your world with Researcher! This mod adds a thematic "earned creative" mode to Minetest through which you can earn infinite duplication of the items in your world. When applied to a survival world, research becomes an enticing goal that rewards your gameplay efforts with endless command of the items you study the most.
Research
--------
Research is the mechanism by which items are permanently destroyed in order to gain the ability to duplicate them. You gain *research points* when you destroy an item, a score which is tracked per player and per item such that every player has their own research point total for each item in the game.
Reaching certain research point milestones known as *research levels* will grant you permanent bonuses towards future research of similar items. Reaching maximum research levels for an item will grant you the permanent ability to duplicate the item.
### How to Research
To research an item, open the Researcher menu in your inventory or via `/research`, place an item into the research box, then click the "Research" button. Researching an item will immediately destroy the researched item and grant you research points in return. The amount of research points gained per research is a constant amount plus any research bonuses you've gained.
### Duplication
Once you've reached the maximum research level for an item (level 10 by default), you are granted the permanent ability to duplicate the item as many times as you'd like at no cost. When duplication is unlocked for an item, the "Research" button in the research menu will change to "Duplicate" for the item, and clicking the button will add a full stack of the item to your main inventory.
Bonuses
-------
You can earn bonuses towards research by researching under certain conditions. These bonuses encourage players to specialize in a particular field of research.
### Group Research
Gaining research levels for a particular item will earn permanent bonuses on future items that match any of the item's groups. For example, cobblestone will have the `stone` group, and five research levels in cobblestone will add +5 research points for all `stone` items, including cobblestone itself.
### Focused Research
Focusing your research on a particular item will grant you cumulative bonuses to continuous research of the item and its groups. Whenever you research an item, the item becomes your current research focus.
You will gain additional research points for each successive research that matches the focus item or its groups, up to a certain maximum. More points are given for an exact match and fewer points are given for a group match. All focus bonuses are reset to zero if you research a non-matching item.
### Research Table
A research table is a special node that allows you to gain significant bonuses to your research. A research table has its own focus item that must be set and matched in order to use it for research.
To use a research table, craft it, place it, then right-click it in order to access its menu. Place an item into its "focus" slot to set its focus item. The research table can then be used to research items that match its focus item or the item's groups.
Placing items near the research table that match the groups of the research table's focus item will increase the research bonus it grants, up to a certain maximum (10 items by default). This can be done by placing matching nodes in the world near the research table or by placing a node with an inventory that contains matching items (e.g., a nearby chest full of swords to gain research bonuses to swords).
The crafing recipe for a research table requires wood (`[W]`) and stone (`[S]`) as illustrated below:
```
[S] [S] [S]
[W] [W] [W]
[W] [ ] [W]
```
Slash Commands
--------------
Researcher can be controlled via the `/research` slash command.
- `/research` - Opens the research interface, or prompts you to use your inventory for research if a supported inventory mod is being used (see next section for complete list)
- `/research reset` - Reset your entire research progress for all items
- `/research reset <item ID>` - Reset your research progress for the item specified by its item ID, e.g., `default:cobble`
Supported Games/Mods
--------------------
Researcher supports a number of games and mods. Popular games include:
- [Minetest Game](https://content.minetest.net/packages/Minetest/minetest_game/) and its derivatives, e.g., [MeseCraft](https://content.minetest.net/packages/MeseCraft/mesecraft/), [Asuna](https://content.minetest.net/packages/EmptyStar/asuna/), etc.
- [Mineclonia](https://content.minetest.net/packages/ryvnf/mineclonia/)
- [VoxeLibre](https://content.minetest.net/packages/Wuzzy/mineclone2/)
- Any Minetest game in theory, but those not listed above are untested
Supported mods include:
- [Awards](https://content.minetest.net/packages/rubenwardy/awards/) - Adds awards for certain research milestones
- [sfinv](https://content.minetest.net/packages/rubenwardy/sfinv/) (Minetest Game default) - Adds an integrated research tab where research can be performed
- [i3](https://content.minetest.net/packages/mt-mods/i3/) - Adds an integrated research tab
- [Unified Inventory](https://content.minetest.net/packages/RealBadAngel/unified_inventory/) - Adds an integrated research tab

120
mods/researcher/init.lua Normal file
View file

@ -0,0 +1,120 @@
-- Globals
researcher = {
settings = {
-- Research points and levels
points_per_level = tonumber(minetest.settings:get("researcher.points_per_level",600) or 600),
points_per_research = tonumber(minetest.settings:get("researcher.points_per_research",100) or 100),
level_max = tonumber(minetest.settings:get("researcher.level_max",10) or 10),
level_scale = tonumber(minetest.settings:get("researcher.level_scale",1.25) or 1.25),
-- Bonuses for group research
group_research_bonus = tonumber(minetest.settings:get("researcher.group_research_bonus",1) or 1),
group_research_bonus_max = tonumber(minetest.settings:get("researcher.group_research_bonus_max",150) or 150),
-- Bonuses for focused research
focused_research_bonus_exact = tonumber(minetest.settings:get("researcher.focused_research_bonus_exact",5) or 5),
focused_research_bonus_group = tonumber(minetest.settings:get("researcher.focused_research_bonus_group",1) or 1),
focused_research_bonus_max = tonumber(minetest.settings:get("researcher.focused_research_bonus_max",150) or 150),
-- Bonuses for using a research table
research_table_bonus_exact = tonumber(minetest.settings:get("researcher.research_table_bonus_exact",25) or 25),
research_table_bonus_group = tonumber(minetest.settings:get("researcher.research_table_bonus_group",5) or 5),
research_table_adjacency_bonus = tonumber(minetest.settings:get("researcher.research_table_adjacency_bonus",10) or 10),
research_table_adjacency_max = tonumber(minetest.settings:get("researcher.research_table_adjacency_max",10) or 10),
research_table_adjacency_radius = tonumber(minetest.settings:get("researcher.research_table_adjacency_radius",3) or 3),
research_table_player_radius = tonumber(minetest.settings:get("researcher.research_table_player_radius",2) or 2),
research_table_bonus_max = tonumber(minetest.settings:get("researcher.research_table_bonus_max",150) or 150),
-- Built-in item discount amounts
discount_stack_max = tonumber(minetest.settings:get("researcher.discount_stack_max",-250) or -250),
discount_mapgen = tonumber(minetest.settings:get("researcher.discount_mapgen",-400) or -400),
discount_not_craftable = tonumber(minetest.settings:get("researcher.discount_not_craftable",-250) or -250),
-- Use research awards
awards = minetest.settings:get_bool("researcher.awards",true) and minetest.get_modpath("awards") and true or false,
-- Groups that are excluded from group matching
excluded_groups = minetest.settings:get("researcher.excluded_groups") or table.concat({
"not_in_creative_inventory",
"attached_node",
"connect_to_raillike",
"dig_immediate",
"disable_jump",
"disable_descend",
"fall_damage_add_percent",
"falling_node",
"float",
"level",
"oddly_breakable_by_hand",
"immortal",
"disable_repair",
"creative_breakable",
"opaque",
"solid",
}," "),
},
-- Cached mod data
data = {
save = {},
},
-- Mod storage
storage = minetest.get_mod_storage(),
-- Registered data
registered_items = {},
registered_adjustments = {},
registered_bonuses = {},
registered_on_research = {},
-- Item groups indexed by group name
groups = {},
-- Dependency info
dependencies = (function(deps)
for _,mod in ipairs({
"default",
"mcl_sounds",
"mcl_inventory",
"sfinv",
"awards",
"unified_inventory",
"i3",
}) do
deps[mod] = minetest.get_modpath(mod)
end
return deps
end)({}),
}
-- Get excluded groups from settings
researcher.excluded_groups = (function()
local groups = researcher.settings.excluded_groups:split("[ \n\r\t]+",false,-1,true)
local exclude = {}
for _,group in ipairs(groups) do
exclude[group] = true
end
return exclude
end)()
-- Load secondary files if the Research content pack is enabled in Asuna settings
if asuna.content.research.enabled then
local mpath = minetest.get_modpath("researcher")
local function runfile(file)
dofile(mpath .. "/src/" .. file .. ".lua")
end
for _,file in ipairs({
"api",
"inventory",
"bonuses",
"research_table",
"scan",
"commands",
"gui",
"awards",
}) do
runfile(file)
end
end

5
mods/researcher/mod.conf Normal file
View file

@ -0,0 +1,5 @@
name = researcher
title = Researcher
description = Research items to unlock duplication
author = EmptyStar
optional_depends = sfinv, awards, default, mcl_sounds, mcl_inventory, unified_inventory, i3

View file

@ -0,0 +1,901 @@
v -0.25 0 -0.25
v -0.25 0 -0.40625
v -0.25 -0.25 -0.25
v -0.25 -0.25 -0.40625
v -0.40625 0 -0.40625
v -0.40625 0 -0.25
v -0.40625 -0.25 -0.40625
v -0.40625 -0.25 -0.25
vt 0.5625 1
vt 0.9375 1
vt 0.9375 0.625
vt 0.5625 0.625
vt 0 1
vt 0.375 1
vt 0.375 0.625
vt 0 0.625
vt 0.5625 1
vt 0.9375 1
vt 0.9375 0.625
vt 0.5625 0.625
vt 0 1
vt 0.375 1
vt 0.375 0.625
vt 0 0.625
vt 0.5625 1
vt 0.9375 1
vt 0.9375 0.625
vt 0.5625 0.625
vt 0.5625 1
vt 0.9375 1
vt 0.9375 0.625
vt 0.5625 0.625
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 2
f 4/4/1 7/3/1 5/2/1 2/1/1
f 3/8/2 4/7/2 2/6/2 1/5/2
f 8/12/3 3/11/3 1/10/3 6/9/3
f 7/16/4 8/15/4 6/14/4 5/13/4
f 6/20/5 1/19/5 2/18/5 5/17/5
f 7/24/6 4/23/6 3/22/6 8/21/6
v -0.23125 0.0625 -0.23125
v -0.23125 0.0625 -0.425
v -0.23125 0 -0.23125
v -0.23125 0 -0.425
v -0.425 0.0625 -0.425
v -0.425 0.0625 -0.23125
v -0.425 0 -0.425
v -0.425 0 -0.23125
vt 0.4375 0.375
vt 0.9375 0.375
vt 0.9375 0.25
vt 0.4375 0.25
vt 0.4375 0.375
vt 0.9375 0.375
vt 0.9375 0.25
vt 0.4375 0.25
vt 0.4375 0.375
vt 0.9375 0.375
vt 0.9375 0.25
vt 0.4375 0.25
vt 0.4375 0.375
vt 0.9375 0.375
vt 0.9375 0.25
vt 0.4375 0.25
vt 0.75 0.5
vt 0.9375 0.5
vt 0.9375 0.3125
vt 0.75 0.3125
vt 0.75 0.5
vt 0.9375 0.5
vt 0.9375 0.3125
vt 0.75 0.3125
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 2
f 12/28/7 15/27/7 13/26/7 10/25/7
f 11/32/8 12/31/8 10/30/8 9/29/8
f 16/36/9 11/35/9 9/34/9 14/33/9
f 15/40/10 16/39/10 14/38/10 13/37/10
f 14/44/11 9/43/11 10/42/11 13/41/11
f 15/48/12 12/47/12 11/46/12 16/45/12
v -0.21875 -0.25 -0.21875
v -0.21875 -0.25 -0.4375
v -0.21875 -0.375 -0.21875
v -0.21875 -0.375 -0.4375
v -0.4375 -0.25 -0.4375
v -0.4375 -0.25 -0.21875
v -0.4375 -0.375 -0.4375
v -0.4375 -0.375 -0.21875
vt 0.4375 0.5
vt 0.9375 0.5
vt 0.9375 0.25
vt 0.4375 0.25
vt 0 1
vt 0.5 1
vt 0.5 0.75
vt 0 0.75
vt 0.4375 0.5
vt 0.9375 0.5
vt 0.9375 0.25
vt 0.4375 0.25
vt 0 1
vt 0.5 1
vt 0.5 0.75
vt 0 0.75
vt 0.4375 1
vt 0.9375 1
vt 0.9375 0.8125
vt 0.4375 0.8125
vt 0.4375 1
vt 0.9375 1
vt 0.9375 0.8125
vt 0.4375 0.8125
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 2
f 20/52/13 23/51/13 21/50/13 18/49/13
f 19/56/14 20/55/14 18/54/14 17/53/14
f 24/60/15 19/59/15 17/58/15 22/57/15
f 23/64/16 24/63/16 22/62/16 21/61/16
f 22/68/17 17/67/17 18/66/17 21/65/17
f 23/72/18 20/71/18 19/70/18 24/69/18
v -0.1875 -0.375 -0.1875
v -0.1875 -0.375 -0.46875
v -0.1875 -0.5 -0.1875
v -0.1875 -0.5 -0.46875
v -0.46875 -0.375 -0.46875
v -0.46875 -0.375 -0.1875
v -0.46875 -0.5 -0.46875
v -0.46875 -0.5 -0.1875
vt 0.4375 1
vt 0.9375 1
vt 0.9375 0.75
vt 0.4375 0.75
vt 0 0.5
vt 0.5 0.5
vt 0.5 0.25
vt 0 0.25
vt 0.4375 1
vt 0.9375 1
vt 0.9375 0.75
vt 0.4375 0.75
vt 0 0.5
vt 0.5 0.5
vt 0.5 0.25
vt 0 0.25
vt 0.4375 1
vt 0.625 1
vt 0.625 0.8125
vt 0.4375 0.8125
vt 0.1875 0.9375
vt 0.75 0.9375
vt 0.75 0.375
vt 0.1875 0.375
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 2
f 28/76/19 31/75/19 29/74/19 26/73/19
f 27/80/20 28/79/20 26/78/20 25/77/20
f 32/84/21 27/83/21 25/82/21 30/81/21
f 31/88/22 32/87/22 30/86/22 29/85/22
f 30/92/23 25/91/23 26/90/23 29/89/23
f 31/96/24 28/95/24 27/94/24 32/93/24
v 0.40625 0 -0.25
v 0.40625 0 -0.40625
v 0.40625 -0.25 -0.25
v 0.40625 -0.25 -0.40625
v 0.25 0 -0.40625
v 0.25 0 -0.25
v 0.25 -0.25 -0.40625
v 0.25 -0.25 -0.25
vt 0.5625 1
vt 0.9375 1
vt 0.9375 0.625
vt 0.5625 0.625
vt 0 1
vt 0.375 1
vt 0.375 0.625
vt 0 0.625
vt 0.5625 1
vt 0.9375 1
vt 0.9375 0.625
vt 0.5625 0.625
vt 0 1
vt 0.375 1
vt 0.375 0.625
vt 0 0.625
vt 0.5625 1
vt 0.9375 1
vt 0.9375 0.625
vt 0.5625 0.625
vt 0.5625 1
vt 0.9375 1
vt 0.9375 0.625
vt 0.5625 0.625
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 2
f 36/100/25 39/99/25 37/98/25 34/97/25
f 35/104/26 36/103/26 34/102/26 33/101/26
f 40/108/27 35/107/27 33/106/27 38/105/27
f 39/112/28 40/111/28 38/110/28 37/109/28
f 38/116/29 33/115/29 34/114/29 37/113/29
f 39/120/30 36/119/30 35/118/30 40/117/30
v 0.42500000000000004 0.0625 -0.23125
v 0.42500000000000004 0.0625 -0.425
v 0.42500000000000004 0 -0.23125
v 0.42500000000000004 0 -0.425
v 0.23124999999999996 0.0625 -0.425
v 0.23124999999999996 0.0625 -0.23125
v 0.23124999999999996 0 -0.425
v 0.23124999999999996 0 -0.23125
vt 0.4375 0.375
vt 0.9375 0.375
vt 0.9375 0.25
vt 0.4375 0.25
vt 0.4375 0.375
vt 0.9375 0.375
vt 0.9375 0.25
vt 0.4375 0.25
vt 0.4375 0.375
vt 0.9375 0.375
vt 0.9375 0.25
vt 0.4375 0.25
vt 0.4375 0.375
vt 0.9375 0.375
vt 0.9375 0.25
vt 0.4375 0.25
vt 0.75 0.5
vt 0.9375 0.5
vt 0.9375 0.3125
vt 0.75 0.3125
vt 0.75 0.5
vt 0.9375 0.5
vt 0.9375 0.3125
vt 0.75 0.3125
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 2
f 44/124/31 47/123/31 45/122/31 42/121/31
f 43/128/32 44/127/32 42/126/32 41/125/32
f 48/132/33 43/131/33 41/130/33 46/129/33
f 47/136/34 48/135/34 46/134/34 45/133/34
f 46/140/35 41/139/35 42/138/35 45/137/35
f 47/144/36 44/143/36 43/142/36 48/141/36
v 0.4375 -0.25 -0.21875
v 0.4375 -0.25 -0.4375
v 0.4375 -0.375 -0.21875
v 0.4375 -0.375 -0.4375
v 0.21875 -0.25 -0.4375
v 0.21875 -0.25 -0.21875
v 0.21875 -0.375 -0.4375
v 0.21875 -0.375 -0.21875
vt 0.4375 0.5
vt 0.9375 0.5
vt 0.9375 0.25
vt 0.4375 0.25
vt 0 1
vt 0.5 1
vt 0.5 0.75
vt 0 0.75
vt 0.4375 0.5
vt 0.9375 0.5
vt 0.9375 0.25
vt 0.4375 0.25
vt 0 1
vt 0.5 1
vt 0.5 0.75
vt 0 0.75
vt 0.4375 1
vt 0.9375 1
vt 0.9375 0.8125
vt 0.4375 0.8125
vt 0.4375 1
vt 0.9375 1
vt 0.9375 0.8125
vt 0.4375 0.8125
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 2
f 52/148/37 55/147/37 53/146/37 50/145/37
f 51/152/38 52/151/38 50/150/38 49/149/38
f 56/156/39 51/155/39 49/154/39 54/153/39
f 55/160/40 56/159/40 54/158/40 53/157/40
f 54/164/41 49/163/41 50/162/41 53/161/41
f 55/168/42 52/167/42 51/166/42 56/165/42
v 0.46875 -0.375 -0.1875
v 0.46875 -0.375 -0.46875
v 0.46875 -0.5 -0.1875
v 0.46875 -0.5 -0.46875
v 0.1875 -0.375 -0.46875
v 0.1875 -0.375 -0.1875
v 0.1875 -0.5 -0.46875
v 0.1875 -0.5 -0.1875
vt 0.4375 1
vt 0.9375 1
vt 0.9375 0.75
vt 0.4375 0.75
vt 0 0.5
vt 0.5 0.5
vt 0.5 0.25
vt 0 0.25
vt 0.4375 1
vt 0.9375 1
vt 0.9375 0.75
vt 0.4375 0.75
vt 0 0.5
vt 0.5 0.5
vt 0.5 0.25
vt 0 0.25
vt 0.4375 1
vt 0.625 1
vt 0.625 0.8125
vt 0.4375 0.8125
vt 0.1875 0.9375
vt 0.75 0.9375
vt 0.75 0.375
vt 0.1875 0.375
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 2
f 60/172/43 63/171/43 61/170/43 58/169/43
f 59/176/44 60/175/44 58/174/44 57/173/44
f 64/180/45 59/179/45 57/178/45 62/177/45
f 63/184/46 64/183/46 62/182/46 61/181/46
f 62/188/47 57/187/47 58/186/47 61/185/47
f 63/192/48 60/191/48 59/190/48 64/189/48
v 0.40625 0 0.40625
v 0.40625 0 0.25
v 0.40625 -0.25 0.40625
v 0.40625 -0.25 0.25
v 0.25 0 0.25
v 0.25 0 0.40625
v 0.25 -0.25 0.25
v 0.25 -0.25 0.40625
vt 0.5625 1
vt 0.9375 1
vt 0.9375 0.625
vt 0.5625 0.625
vt 0 1
vt 0.375 1
vt 0.375 0.625
vt 0 0.625
vt 0.5625 1
vt 0.9375 1
vt 0.9375 0.625
vt 0.5625 0.625
vt 0 1
vt 0.375 1
vt 0.375 0.625
vt 0 0.625
vt 0.5625 1
vt 0.9375 1
vt 0.9375 0.625
vt 0.5625 0.625
vt 0.5625 1
vt 0.9375 1
vt 0.9375 0.625
vt 0.5625 0.625
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 2
f 68/196/49 71/195/49 69/194/49 66/193/49
f 67/200/50 68/199/50 66/198/50 65/197/50
f 72/204/51 67/203/51 65/202/51 70/201/51
f 71/208/52 72/207/52 70/206/52 69/205/52
f 70/212/53 65/211/53 66/210/53 69/209/53
f 71/216/54 68/215/54 67/214/54 72/213/54
v 0.42500000000000004 0.0625 0.42500000000000004
v 0.42500000000000004 0.0625 0.23124999999999996
v 0.42500000000000004 0 0.42500000000000004
v 0.42500000000000004 0 0.23124999999999996
v 0.23124999999999996 0.0625 0.23124999999999996
v 0.23124999999999996 0.0625 0.42500000000000004
v 0.23124999999999996 0 0.23124999999999996
v 0.23124999999999996 0 0.42500000000000004
vt 0.4375 0.375
vt 0.9375 0.375
vt 0.9375 0.25
vt 0.4375 0.25
vt 0.4375 0.375
vt 0.9375 0.375
vt 0.9375 0.25
vt 0.4375 0.25
vt 0.4375 0.375
vt 0.9375 0.375
vt 0.9375 0.25
vt 0.4375 0.25
vt 0.4375 0.375
vt 0.9375 0.375
vt 0.9375 0.25
vt 0.4375 0.25
vt 0.75 0.5
vt 0.9375 0.5
vt 0.9375 0.3125
vt 0.75 0.3125
vt 0.75 0.5
vt 0.9375 0.5
vt 0.9375 0.3125
vt 0.75 0.3125
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 2
f 76/220/55 79/219/55 77/218/55 74/217/55
f 75/224/56 76/223/56 74/222/56 73/221/56
f 80/228/57 75/227/57 73/226/57 78/225/57
f 79/232/58 80/231/58 78/230/58 77/229/58
f 78/236/59 73/235/59 74/234/59 77/233/59
f 79/240/60 76/239/60 75/238/60 80/237/60
v 0.4375 -0.25 0.4375
v 0.4375 -0.25 0.21875
v 0.4375 -0.375 0.4375
v 0.4375 -0.375 0.21875
v 0.21875 -0.25 0.21875
v 0.21875 -0.25 0.4375
v 0.21875 -0.375 0.21875
v 0.21875 -0.375 0.4375
vt 0.4375 0.5
vt 0.9375 0.5
vt 0.9375 0.25
vt 0.4375 0.25
vt 0 1
vt 0.5 1
vt 0.5 0.75
vt 0 0.75
vt 0.4375 0.5
vt 0.9375 0.5
vt 0.9375 0.25
vt 0.4375 0.25
vt 0 1
vt 0.5 1
vt 0.5 0.75
vt 0 0.75
vt 0.4375 1
vt 0.9375 1
vt 0.9375 0.8125
vt 0.4375 0.8125
vt 0.4375 1
vt 0.9375 1
vt 0.9375 0.8125
vt 0.4375 0.8125
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 2
f 84/244/61 87/243/61 85/242/61 82/241/61
f 83/248/62 84/247/62 82/246/62 81/245/62
f 88/252/63 83/251/63 81/250/63 86/249/63
f 87/256/64 88/255/64 86/254/64 85/253/64
f 86/260/65 81/259/65 82/258/65 85/257/65
f 87/264/66 84/263/66 83/262/66 88/261/66
v 0.46875 -0.375 0.46875
v 0.46875 -0.375 0.1875
v 0.46875 -0.5 0.46875
v 0.46875 -0.5 0.1875
v 0.1875 -0.375 0.1875
v 0.1875 -0.375 0.46875
v 0.1875 -0.5 0.1875
v 0.1875 -0.5 0.46875
vt 0.4375 1
vt 0.9375 1
vt 0.9375 0.75
vt 0.4375 0.75
vt 0 0.5
vt 0.5 0.5
vt 0.5 0.25
vt 0 0.25
vt 0.4375 1
vt 0.9375 1
vt 0.9375 0.75
vt 0.4375 0.75
vt 0 0.5
vt 0.5 0.5
vt 0.5 0.25
vt 0 0.25
vt 0.4375 1
vt 0.625 1
vt 0.625 0.8125
vt 0.4375 0.8125
vt 0.1875 0.9375
vt 0.75 0.9375
vt 0.75 0.375
vt 0.1875 0.375
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 2
f 92/268/67 95/267/67 93/266/67 90/265/67
f 91/272/68 92/271/68 90/270/68 89/269/68
f 96/276/69 91/275/69 89/274/69 94/273/69
f 95/280/70 96/279/70 94/278/70 93/277/70
f 94/284/71 89/283/71 90/282/71 93/281/71
f 95/288/72 92/287/72 91/286/72 96/285/72
v -0.25 0 0.40625
v -0.25 0 0.25
v -0.25 -0.25 0.40625
v -0.25 -0.25 0.25
v -0.40625 0 0.25
v -0.40625 0 0.40625
v -0.40625 -0.25 0.25
v -0.40625 -0.25 0.40625
vt 0.5625 1
vt 0.9375 1
vt 0.9375 0.625
vt 0.5625 0.625
vt 0 1
vt 0.375 1
vt 0.375 0.625
vt 0 0.625
vt 0.5625 1
vt 0.9375 1
vt 0.9375 0.625
vt 0.5625 0.625
vt 0 1
vt 0.375 1
vt 0.375 0.625
vt 0 0.625
vt 0.5625 1
vt 0.9375 1
vt 0.9375 0.625
vt 0.5625 0.625
vt 0.5625 1
vt 0.9375 1
vt 0.9375 0.625
vt 0.5625 0.625
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 2
f 100/292/73 103/291/73 101/290/73 98/289/73
f 99/296/74 100/295/74 98/294/74 97/293/74
f 104/300/75 99/299/75 97/298/75 102/297/75
f 103/304/76 104/303/76 102/302/76 101/301/76
f 102/308/77 97/307/77 98/306/77 101/305/77
f 103/312/78 100/311/78 99/310/78 104/309/78
v -0.23125 0.0625 0.42500000000000004
v -0.23125 0.0625 0.23124999999999996
v -0.23125 0 0.42500000000000004
v -0.23125 0 0.23124999999999996
v -0.425 0.0625 0.23124999999999996
v -0.425 0.0625 0.42500000000000004
v -0.425 0 0.23124999999999996
v -0.425 0 0.42500000000000004
vt 0.4375 0.375
vt 0.9375 0.375
vt 0.9375 0.25
vt 0.4375 0.25
vt 0.4375 0.375
vt 0.9375 0.375
vt 0.9375 0.25
vt 0.4375 0.25
vt 0.4375 0.375
vt 0.9375 0.375
vt 0.9375 0.25
vt 0.4375 0.25
vt 0.4375 0.375
vt 0.9375 0.375
vt 0.9375 0.25
vt 0.4375 0.25
vt 0.75 0.5
vt 0.9375 0.5
vt 0.9375 0.3125
vt 0.75 0.3125
vt 0.75 0.5
vt 0.9375 0.5
vt 0.9375 0.3125
vt 0.75 0.3125
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 2
f 108/316/79 111/315/79 109/314/79 106/313/79
f 107/320/80 108/319/80 106/318/80 105/317/80
f 112/324/81 107/323/81 105/322/81 110/321/81
f 111/328/82 112/327/82 110/326/82 109/325/82
f 110/332/83 105/331/83 106/330/83 109/329/83
f 111/336/84 108/335/84 107/334/84 112/333/84
v -0.21875 -0.25 0.4375
v -0.21875 -0.25 0.21875
v -0.21875 -0.375 0.4375
v -0.21875 -0.375 0.21875
v -0.4375 -0.25 0.21875
v -0.4375 -0.25 0.4375
v -0.4375 -0.375 0.21875
v -0.4375 -0.375 0.4375
vt 0.4375 0.5
vt 0.9375 0.5
vt 0.9375 0.25
vt 0.4375 0.25
vt 0 1
vt 0.5 1
vt 0.5 0.75
vt 0 0.75
vt 0.4375 0.5
vt 0.9375 0.5
vt 0.9375 0.25
vt 0.4375 0.25
vt 0 1
vt 0.5 1
vt 0.5 0.75
vt 0 0.75
vt 0.4375 1
vt 0.9375 1
vt 0.9375 0.8125
vt 0.4375 0.8125
vt 0.4375 1
vt 0.9375 1
vt 0.9375 0.8125
vt 0.4375 0.8125
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 2
f 116/340/85 119/339/85 117/338/85 114/337/85
f 115/344/86 116/343/86 114/342/86 113/341/86
f 120/348/87 115/347/87 113/346/87 118/345/87
f 119/352/88 120/351/88 118/350/88 117/349/88
f 118/356/89 113/355/89 114/354/89 117/353/89
f 119/360/90 116/359/90 115/358/90 120/357/90
v -0.1875 -0.375 0.46875
v -0.1875 -0.375 0.1875
v -0.1875 -0.5 0.46875
v -0.1875 -0.5 0.1875
v -0.46875 -0.375 0.1875
v -0.46875 -0.375 0.46875
v -0.46875 -0.5 0.1875
v -0.46875 -0.5 0.46875
vt 0.4375 1
vt 0.9375 1
vt 0.9375 0.75
vt 0.4375 0.75
vt 0 0.5
vt 0.5 0.5
vt 0.5 0.25
vt 0 0.25
vt 0.4375 1
vt 0.9375 1
vt 0.9375 0.75
vt 0.4375 0.75
vt 0 0.5
vt 0.5 0.5
vt 0.5 0.25
vt 0 0.25
vt 0.4375 1
vt 0.625 1
vt 0.625 0.8125
vt 0.4375 0.8125
vt 0.1875 0.9375
vt 0.75 0.9375
vt 0.75 0.375
vt 0.1875 0.375
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 2
f 124/364/91 127/363/91 125/362/91 122/361/91
f 123/368/92 124/367/92 122/366/92 121/365/92
f 128/372/93 123/371/93 121/370/93 126/369/93
f 127/376/94 128/375/94 126/374/94 125/373/94
f 126/380/95 121/379/95 122/378/95 125/377/95
f 127/384/96 124/383/96 123/382/96 128/381/96
v 0.34375 -0.28125 0.34375
v 0.34375 -0.28125 -0.34375
v 0.34375 -0.34375 0.34375
v 0.34375 -0.34375 -0.34375
v -0.34375 -0.28125 -0.34375
v -0.34375 -0.28125 0.34375
v -0.34375 -0.34375 -0.34375
v -0.34375 -0.34375 0.34375
vt 0.1875 0.4375
vt 1 0.4375
vt 1 0.375
vt 0.1875 0.375
vt 0.1875 0.4375
vt 1 0.4375
vt 1 0.375
vt 0.1875 0.375
vt 0.1875 0.4375
vt 1 0.4375
vt 1 0.375
vt 0.1875 0.375
vt 0.1875 0.4375
vt 1 0.4375
vt 1 0.375
vt 0.1875 0.375
vt 0.0625 1
vt 0.875 1
vt 0.875 0.1875
vt 0.0625 0.1875
vt 0.0625 1
vt 0.875 1
vt 0.875 0.1875
vt 0.0625 0.1875
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 1
f 132/388/97 135/387/97 133/386/97 130/385/97
f 131/392/98 132/391/98 130/390/98 129/389/98
f 136/396/99 131/395/99 129/394/99 134/393/99
f 135/400/100 136/399/100 134/398/100 133/397/100
f 134/404/101 129/403/101 130/402/101 133/401/101
f 135/408/102 132/407/102 131/406/102 136/405/102
v 0.4375 0.25 0.4375
v 0.4375 0.25 -0.4375
v 0.4375 0.0625 0.4375
v 0.4375 0.0625 -0.4375
v -0.4375 0.25 -0.4375
v -0.4375 0.25 0.4375
v -0.4375 0.0625 -0.4375
v -0.4375 0.0625 0.4375
vt 1 1
vt 0 1
vt 0 0.5
vt 1 0.5
vt 0 0.5
vt 1 0.5
vt 1 0
vt 0 0
vt 0 1
vt 1 1
vt 1 0.5
vt 0 0.5
vt 0 0.5
vt 1 0.5
vt 1 0
vt 0 0
vt 0 1
vt 1 1
vt 1 0
vt 0 0
vt 0 1
vt 1 1
vt 1 0
vt 0 0
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 2
f 140/412/103 143/411/103 141/410/103 138/409/103
f 139/416/104 140/415/104 138/414/104 137/413/104
f 144/420/105 139/419/105 137/418/105 142/417/105
f 143/424/106 144/423/106 142/422/106 141/421/106
f 142/428/107 137/427/107 138/426/107 141/425/107
f 143/432/108 140/431/108 139/430/108 144/429/108
v 0.5 0.5 0.5
v 0.5 0.5 -0.5
v 0.5 0.3125 0.5
v 0.5 0.3125 -0.5
v -0.5 0.5 -0.5
v -0.5 0.5 0.5
v -0.5 0.3125 -0.5
v -0.5 0.3125 0.5
vt 0 0.3125
vt 1 0.3125
vt 1 0.125
vt 0 0.125
vt 0 0.3125
vt 1 0.3125
vt 1 0.125
vt 0 0.125
vt 0 0.3125
vt 1 0.3125
vt 1 0.125
vt 0 0.125
vt 0 0.3125
vt 1 0.3125
vt 1 0.125
vt 0 0.125
vt 0 1
vt 1 1
vt 1 0
vt 0 0
vt 0 1
vt 1 1
vt 1 0
vt 0 0
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 1
f 148/436/109 151/435/109 149/434/109 146/433/109
f 147/440/110 148/439/110 146/438/110 145/437/110
f 152/444/111 147/443/111 145/442/111 150/441/111
f 151/448/112 152/447/112 150/446/112 149/445/112
f 150/452/113 145/451/113 146/450/113 149/449/113
f 151/456/114 148/455/114 147/454/114 152/453/114
v 0.46875 0.3125 0.46875
v 0.46875 0.3125 -0.46875
v 0.46875 0.25 0.46875
v 0.46875 0.25 -0.46875
v -0.46875 0.3125 -0.46875
v -0.46875 0.3125 0.46875
v -0.46875 0.25 -0.46875
v -0.46875 0.25 0.46875
vt 0 0.8125
vt 1 0.8125
vt 1 0.75
vt 0 0.75
vt 0 0.8125
vt 1 0.8125
vt 1 0.75
vt 0 0.75
vt 0 0.8125
vt 1 0.8125
vt 1 0.75
vt 0 0.75
vt 0 0.8125
vt 1 0.8125
vt 1 0.75
vt 0 0.75
vt 0 1
vt 0.9375 1
vt 0.9375 0.0625
vt 0 0.0625
vt 0 1
vt 0.9375 1
vt 0.9375 0.0625
vt 0 0.0625
vn 0 0 -1
vn 1 0 0
vn 0 0 1
vn -1 0 0
vn 0 1 0
vn 0 -1 0
g 1
f 156/460/115 159/459/115 157/458/115 154/457/115
f 155/464/116 156/463/116 154/462/116 153/461/116
f 160/468/117 155/467/117 153/466/117 158/465/117
f 159/472/118 160/471/118 158/470/118 157/469/118
f 158/476/119 153/475/119 154/474/119 157/473/119
f 159/480/120 156/479/120 155/478/120 160/477/120

View file

@ -0,0 +1,76 @@
[Research Points and Levels]
# How many points a player needs per item to advance to the next research level for a given item. More points required will require more research and vice-versa.
researcher.points_per_level (Points per research level) int 600 100 10000
# How many research points a successful research gives per item.
researcher.points_per_research (Points per successful research) int 100 1 10000
# The maximum research level that can be reached for an item. Duplication is unlocked for any item at max research level.
researcher.level_max (Max research level) int 10 1 10000
# The scale at which the points to the next research level increase. This is an exponential multiplier for each research level.
researcher.level_scale (Research level scale) float 1.25 1.0 2.0
[Group Research]
# The number of bonus research points granted for each research level of items that match the groups of a researched item.
researcher.group_research_bonus (Research group bonus) int 1 0 10000
# The maximum level group bonus that can be given.
researcher.group_research_bonus_max (Research group bonus max) int 100 0 10000
[Focused Research]
# How many bonus research points granted per combo that matches player focus item exactly. This value is cumulative for each successive research. This rewards players who continuously research the same item.
researcher.focused_research_bonus_exact (Focused research bonus, exact) int 5 0 10000
# How many bonus research points granted per combo that matches player focus item group. This value is cumulative for each successive research. This rewards players who continuously research items similar to their focus.
researcher.focused_research_bonus_group (Focused research bonus, grouped) int 1 0 10000
# The maximum bonus that focused research can contribute.
researcher.focused_research_bonus_max (Focused research max bonus) int 150 0 10000
[Research Table]
# How many bonus research points granted for research at a research table that matches its item exactly?
researcher.research_table_bonus_exact (Research table bonus, exact) int 25 0 10000
# How many bonus research points granted for research at a research table that matches one of its item groups?
researcher.research_table_bonus_group (Research table bonus, group) int 5 0 10000
# How many bonus research points are given for each matching item near a research table.
researcher.research_table_adjacency_bonus (Research table adjacency bonus, per item) int 10 0 100000
# The maximum number of items that can contribute to research table adjacency bonus.
researcher.research_table_adjacency_max (Max research table adjacency) int 10 0 50
# The node radius in which research tables will search for matching adjacency.
researcher.research_table_adjacency_radius (Research table adjacency radius) int 3 1 10
# The node radius in which players can gain benefits from a nearby research table.
researcher.research_table_player_radius (Research table player radius) int 2 1 3
# The maximum bonus that research tables can contribute.
researcher.research_table_bonus_max (Max research table bonus) int 150 0 10000
[Item Research Cost Discounts]
# The research point discount for items with a lower max stack value.
researcher.discount_stack_max (Discount for items with low max stacks) int -250 -10000 0
# The research point discount for items not found commonly in the world.
researcher.discount_mapgen (Discount for items not in mapgen) int -400 -10000 0
# The research point discount for items that cannot be crafted
researcher.discount_not_craftable (Discount for non-craftable items) int -250 -10000 0
[Awards]
# If this setting is enabled and the Awards mod is enabled, then Researcher awards will be registered.
researcher.awards (Enable awards?) bool true
[Advanced]
# Which groups to exclude from Researcher. This list should include system groups and other "non-useful" groups that don't make sense to players.
researcher.excluded_groups (Item groups to exclude) string not_in_creative_inventory attached_node connect_to_raillike dig_immediate disable_jump disable_descend fall_damage_add_percent falling_node float level oddly_breakable_by_hand immortal disable_repair creative_breakable opaque solid

Binary file not shown.

Binary file not shown.

Binary file not shown.

456
mods/researcher/src/api.lua Normal file
View file

@ -0,0 +1,456 @@
-- ----------------- --
-- GROUP FUNCTIONS --
-- ----------------- --
-- Add an item to a group
function researcher.add_item_to_group(item,group)
researcher.groups[group] = researcher.groups[group] or {}
researcher.groups[group][item] = true
return true
end
-- Iterate over items in a group
function researcher.for_item_in_group(group,fn)
for item,_ in pairs(researcher.groups[group]) do
if fn(item) then
return true
end
end
return true
end
-- Iterate over groups of an item
function researcher.for_group_in_item(item,fn)
item = researcher.registered_items[item]
if not item then
return false
end
for group,_ in pairs(item.groups) do
if fn(group) then
return true
end
end
return true
end
-- Determine if item is in a specific group
function researcher.is_item_in_group(item,group)
return researcher.groups[group] and researcher.groups[group][item] and true or false
end
-- Determine if two items have any groups in common
function researcher.do_items_share_groups(item1,item2)
item1 = researcher.registered_items[item1]
item2 = researcher.registered_items[item2]
if not item1 or not item2 then
return false
end
for group,_ in pairs(item1.groups) do
if item2.groups[group] then
return true
end
end
return false
end
-- ---------------- --
-- ITEM FUNCTIONS --
-- ---------------- --
-- Register an item with Researcher
function researcher.register_item(def)
-- Do not register duplicate items
if researcher.registered_items[def.name] then
return false
end
-- Create internal item definition
local item = {
name = def.name,
groups = {},
}
-- Determine base research points per level for this item
item.points_per_level = def.points_per_level
-- Determine research point adjustments that apply to this item
item.adjustments = {}
for _,adjustment in ipairs(researcher.registered_adjustments) do
local amount = adjustment.calculate(item.name)
if amount ~= 0 then
item.points_per_level = math.max(researcher.settings.points_per_research,item.points_per_level + amount)
table.insert(item.adjustments,{
name = adjustment.name,
amount = amount,
})
end
end
-- Determine groups for this item
local groups = def.groups or {}
for _,group in ipairs(groups) do
item.groups[group] = true
researcher.add_item_to_group(def.name,group)
end
-- Register the item
researcher.registered_items[def.name] = item
return true
end
-- ---------------------- --
-- ADJUSTMENT FUNCTIONS --
-- ---------------------- --
-- Register a adjustment function that will return a adjustment amount
function researcher.register_adjustment(def)
-- Do not register duplicate adjustments
if researcher.registered_adjustments[def.name] then
return false
end
-- Register the new adjustment
local adjustment = {
name = def.name,
reason = def.reason,
calculate = def.calculate,
}
researcher.registered_adjustments[def.name] = adjustment
table.insert(researcher.registered_adjustments,adjustment)
return true
end
function researcher.get_adjustments_for_item(item)
item = researcher.registered_items[item]
if not item then
return {}
end
return item.adjustments
end
-- ----------------- --
-- BONUS FUNCTIONS --
-- ----------------- --
-- Register a bonus
function researcher.register_bonus(def)
-- Do not register duplicate bonuses
if researcher.registered_bonuses[def.name] then
return false
end
-- Register the new bonus
local bonus = {
name = def.name,
reason = def.reason,
calculate = def.calculate,
initialize_player_data = def.initialize_player_data or function() end,
}
researcher.registered_bonuses[def.name] = bonus
table.insert(researcher.registered_bonuses,bonus)
return true
end
-- ---------------- --
-- DATA FUNCTIONS --
-- ---------------- --
-- Initialize player data
function researcher.initialize_player_data(player_name)
-- Create new player data
local player_data = {
-- The player's name
name = player_name,
-- All of the player's research progress indexed by item name
research = {},
-- The current subject of the player's research
subject = {
image = nil,
description = "",
groups = "(Research an item on the left to see info)",
research = nil,
},
}
-- Initialize bonus-specific data
for _,bonus in ipairs(researcher.registered_bonuses) do
bonus.initialize_player_data(player_data)
end
-- Save initialized player data to mod storage
researcher.save_player_data(player_name)
-- Return initialized data
return player_data
end
-- Get player data
function researcher.get_player_data(player_name)
-- Attempt to load player data from cache
local pstring = "player_" .. player_name
local player_data = researcher.data[pstring]
if not player_data then
-- Attempt to load player data from mod storage
player_data = researcher.storage:get(pstring)
-- Initialize new player data if not found or parse string if found
if not player_data then
player_data = researcher.initialize_player_data(player_name)
else
player_data = minetest.deserialize(player_data)
end
-- Link research subject to actual research
if player_data.subject.research then
player_data.subject.research = player_data.research[player_data.subject.item.name]
end
-- Cache player data
researcher.data[pstring] = player_data
end
-- Return player data
return player_data
end
-- Determine the number of points to the next level
-- FIXME should be (item,level)?
function researcher.get_points_to_next_level(player,item)
item = researcher.registered_items[item]
if item then
local research = (type(player) == "string" and researcher.get_player_data(player) or player).research[item.name]
if research then
local level = research.level
if research.level <= researcher.settings.level_max then
return math.round(item.points_per_level * math.pow(research.level,researcher.settings.level_scale) / 100) * 100
end
end
else
return 0
end
return item.points_per_level
end
-- Save player data; can be called many times during the current tick but will
-- only execute once on the next tick
function researcher.save_player_data(player_name)
if not researcher.data.save[player_name] then
researcher.data.save[player_name] = true
minetest.after(0,function()
researcher.storage:set_string("player_" .. player_name,minetest.serialize(researcher.get_player_data(player_name)))
researcher.data.save[player_name] = nil
end)
end
end
-- -------------------- --
-- RESEARCH FUNCTIONS --
-- -------------------- --
-- Research an item
function researcher.research_item(player,item)
-- Initialize result
local result = {
item = item,
base = researcher.settings.points_per_research,
bonuses = {},
total = researcher.settings.points_per_research,
success = true,
}
-- Get player data
local player_data = type(player) == "string" and researcher.get_player_data(player) or player
-- Cannot research beyond max research level
if player_data.research[item] and player_data.research[item].level > researcher.settings.level_max then
result = {
item = item,
base = 0,
bonuses = {},
total = 0,
success = false,
}
return result
end
-- Calculate bonuses
for _,bonus in ipairs(researcher.registered_bonuses) do
local amount = bonus.calculate(item,player_data)
if amount ~= 0 then
result.total = result.total + amount
table.insert(result.bonuses,{
name = bonus.name,
reason = bonus.reason,
points = amount,
})
end
end
-- Get research entry
local research = player_data.research[item] or {
level = 1,
points = 0,
}
player_data.research[item] = research
player_data.subject.research = research
-- Look up item
item = researcher.registered_items[item]
if not item then
return {
item = "???",
base = 0,
bonuses = {},
total = 0,
success = false,
}
end
-- Level up research
local points_tally = research.points + result.total
local points_level = researcher.get_points_to_next_level(player_data,item.name)
while points_tally >= points_level do
research.level = math.min(researcher.settings.level_max + 1,research.level + 1)
points_tally = points_tally - points_level
points_level = researcher.get_points_to_next_level(player_data,item.name)
end
research.points = points_tally
-- Unlock award for basic research
if researcher.settings.awards then
awards.unlock(player.name,"researcher:apprentice")
end
-- Set points to 0 at max level and unlock Prodigious award
if research.level > researcher.settings.level_max then
research.points = 0
if researcher.settings.awards then
awards.unlock(player.name,"researcher:prodigious")
end
end
-- Save data
researcher.save_player_data(player_data.name)
-- Return final research result
return result
end
-- Research an ItemStack
function researcher.research_itemstack(player,itemstack)
if itemstack:is_empty() then
return {
item = itemstack:get_name(),
success = false,
remainder = ItemStack(itemstack:get_name() .. " 0"),
}
end
-- Get player data for entire stack
local player_data = type(player) == "string" and researcher.get_player_data(player) or player
-- Research each item in the stack individually
local name = itemstack:get_name()
local results = {
name = name,
success = false,
remainder = ItemStack(name .. " 0"),
}
for i = 1, itemstack:get_count() do
local result = researcher.research_item(player_data,name)
results.success = results.success or result.success
if result.success then
table.insert(results,result)
elseif player_data.research[name].level > researcher.settings.level_max then
itemstack:set_count(itemstack:get_count() - i + 1)
results.remainder = itemstack
return results
else
return results
end
end
-- Return all results
return results
end
-- Research an entire inventory list
function researcher.research_inventory(player,inventory,list)
-- Get player data
local player_data = type(player) == "string" and researcher.get_player_data(player) or player
-- Research each ItemStack in the inventory
local results = {
success = false,
}
for i = 1, inventory:get_size(list) do
local itemstack = inventory:get_stack(list,i)
local result = researcher.research_itemstack(player_data,itemstack)
results.success = results.success or result.success
if result.success then
inventory:set_stack(list,i,result.remainder)
end
table.insert(results,result)
end
-- Return full inventory results
return results
end
-- ----------------------- --
-- DUPLICATION FUNCTIONS --
-- ----------------------- --
-- Duplicate the item in the player's research inventory
function researcher.duplicate_research(player)
local inventory = player:get_inventory()
local itemstack = inventory:get_stack("research",1)
if itemstack and not itemstack:is_empty() then
local item = itemstack:get_name()
local idef = minetest.registered_items[item]
if idef then
inventory:add_item("main",ItemStack(itemstack:get_name() .. " " .. itemstack:get_stack_max()))
end
end
end
-- --------------- --
-- GUI FUNCTIONS --
-- --------------- --
-- Return a formspec that implements research
function researcher.get_formspec_data(player_name)
local player_data = researcher.get_player_data(player_name)
local subject = player_data.subject
local data = {
subject = subject,
current_points = subject.research and subject.research.points or 0,
points_to_next_level = subject.research and researcher.get_points_to_next_level(player_name,subject.item.name) or 0,
is_max_level = subject.research and (subject.research.level > researcher.settings.level_max) and true or false,
is_inventory_empty = minetest.get_inventory({ type = "player", name = player_name, }):get_stack("research",1):is_empty(),
last_result = player_data.last_result,
}
player_data.last_result = nil -- last result is only available once immediately after research
return data
end
-- -------------------- --
-- CALLBACK FUNCTIONS --
-- -------------------- --
function researcher.register_on_research(fn)
table.insert(researcher.registered_on_research,fn)
end

View file

@ -0,0 +1,43 @@
if researcher.settings.awards then
-- Use the available registration function, if any
local register_award = (function()
if awards.register_award then
return function(...)
awards.register_award(...)
end
elseif awards.register_achievement then
return function(...)
awards.register_achievement(...)
end
else
minetest.log("warn","Researcher does not support the loaded awards mod.")
return function()
-- unsupported awards mod, noop
end
end
end)()
-- Apprentice: awarded for a player's first successful research
register_award("researcher:apprentice",{
title = "Apprentice",
description = "Research any item",
difficulty = 5,
icon = "researcher_icon_bronze.png",
})
-- Eureka!: awarded for a player's first gained research level
register_award("researcher:eureka",{
title = "Eureka!",
description = "Earn a research level for any item",
difficulty = 50,
icon = "researcher_icon_silver.png",
})
-- Prodigious: awarded for a player's first max research level
register_award("researcher:prodigious",{
title = "Prodigious",
description = "Earn max research level for any item",
difficulty = 150,
icon = "researcher_icon_gold.png",
})
end

View file

@ -0,0 +1,183 @@
-- Research level group bonus
if researcher.settings.group_research_bonus > 0 and researcher.settings.group_research_bonus_max > 0 then
researcher.register_bonus({
name = "researcher:research_group_research_bonus",
reason = "Group Research",
calculate = function(item,player_data)
local bonus = 0
for subject,research in pairs(player_data.research) do
if researcher.do_items_share_groups(item,subject) then
bonus = bonus + (research.level - 1) * researcher.settings.group_research_bonus
end
end
return bonus
end,
initialize_player_data = function()
-- all bonus calculation comes from research; nothing to initialize
end,
})
else
researcher.register_bonus({
name = "researcher:research_group_research_bonus",
reason = "Group Research",
calculate = function() return 0 end,
initialize_player_data = function()
-- bonus is calculated from research; nothing to initialize
end,
})
end
-- Focused research bonus
if researcher.settings.focused_research_bonus_max > 0 and (researcher.settings.focused_research_bonus_exact > 0 or researcher.settings.focused_research_bonus_group > 0) then
researcher.register_bonus({
name = "researcher:focused_research_bonus",
reason = "Focused Research",
calculate = function(item,player_data)
-- Calculate bonus value
if item == player_data.focused_research.item then
player_data.focused_research.bonus = player_data.focused_research.bonus + researcher.settings.focused_research_bonus_exact
elseif researcher.do_items_share_groups(item,player_data.focused_research.item) then
player_data.focused_research.bonus = player_data.focused_research.bonus + researcher.settings.focused_research_bonus_group
else
player_data.focused_research.bonus = 0
end
-- Set focused item
player_data.focused_research.item = item
-- Return capped bonus value
player_data.focused_research.bonus = math.min(player_data.focused_research.bonus,researcher.settings.focused_research_bonus_max)
return player_data.focused_research.bonus
end,
initialize_player_data = function(player_data)
player_data.focused_research = {
item = "",
bonus = 0,
}
end,
})
else
researcher.register_bonus({
name = "researcher:focused_research_bonus",
reason = "Focused Research",
calculate = function() return 0 end,
initialize_player_data = function(player_data)
player_data.focused_research = {
item = "",
bonus = 0,
}
end,
})
end
-- Research table bonus
if researcher.settings.research_table_bonus_exact > 0 or researcher.settings.research_table_bonus_group > 0 or (researcher.settings.research_table_adjacency_bonus > 0 and researcher.settings.research_table_adjacency_max > 0) then
researcher.register_bonus({
name = "researcher:research_table_bonus",
reason = "Research Table",
calculate = function(item,player_data)
-- Initialize bonus and max flag
local bonus = 0
local bonusmax = false
-- Track limits when tallying bonuses
local nadj = 0
local function rtbonus(bonus,increment,adjacency)
local result = bonus
if adjacency then
local adj_bounded = math.min(adjacency,researcher.settings.research_table_adjacency_max - nadj)
nadj = nadj + adj_bounded
result = result + researcher.settings.research_table_adjacency_bonus * adj_bounded
else
result = bonus + increment
end
return math.min(result,researcher.settings.research_table_bonus_max), (result >= researcher.settings.research_table_bonus_max or nadj >= researcher.settings.research_table_adjacency_max)
end
-- Scan radius around player for research tables
local player = minetest.get_player_by_name(player_data.name)
local research_table = nil
if player then
local pos = player:get_pos()
local radius = researcher.settings.research_table_player_radius
for _,rt in ipairs(minetest.find_nodes_in_area(pos:add(-radius),pos:add(radius),"researcher:research_table")) do
-- Get research table's focus
local meta = minetest.get_meta(rt)
local inventory = meta:get_inventory()
local itemstack = inventory:get_stack("focus",1)
local name = itemstack:get_name()
-- If the focus item matches the item in question, then add to the
-- calculated bonus accordingly
if item == name then
bonus, bonusmax = rtbonus(bonus,(research_table and (researcher.settings.research_table_bonus_exact - researcher.settings.research_table_bonus_group) or researcher.settings.research_table_bonus_exact))
if bonusmax then
return bonus
end
research_table = rt
break -- cannot do better than an exact match
elseif not research_table and researcher.do_items_share_groups(item,name) then
bonus, bonusmax = rtbonus(bonus,researcher.settings.research_table_bonus_group)
if bonusmax then
return bonus
end
research_table = rt
-- keep scanning for better matches
end
end
-- Calculate adjacency bonus for research table
if research_table then
local radius = researcher.settings.research_table_adjacency_radius
local pos1 = research_table:add(-radius)
local pos2 = research_table:add(radius)
-- Check nearby node groups
bonus, bonusmax = rtbonus(bonus,researcher.settings.research_table_adjacency_bonus,#minetest.find_nodes_in_area(pos1,pos2,(function()
local groups = {}
for group,_ in pairs(researcher.registered_items[item].groups) do
table.insert(groups,"group:" .. group)
end
return groups
end)()))
if bonusmax then
return bonus
end
-- Check nearby node inventories
for _,node in ipairs(minetest.find_nodes_with_meta(pos1,pos2)) do
if not node:equals(research_table) then
local nodemeta = minetest.get_meta(node)
local nodeinventory = nodemeta:get_inventory()
if nodeinventory then
for list,stacks in pairs(nodeinventory:get_lists() or {}) do
for _,itemstack in ipairs(stacks or {}) do
if not itemstack:is_empty() and researcher.do_items_share_groups(item,itemstack:get_name()) then
bonus, bonusmax = rtbonus(bonus,researcher.settings.research_table_adjacency_bonus,itemstack:get_count())
if bonusmax then
return bonus
end
end
end
end
end
end
end
else
return 0 -- no bonus if no research table was found
end
else
return 0 -- no bonus if player is mysteriously not found
end
-- Return partial bonus total
return bonus
end,
})
else
researcher.register_bonus({
name = "researcher:research_table_bonus",
reason = "Research Table",
calculate = function() return 0 end,
})
end

View file

@ -0,0 +1,51 @@
local show_formspec = (function()
if researcher.dependencies.sfinv and sfinv.enabled then
return function(name)
local player = minetest.get_player_by_name(name)
sfinv.set_page(player,"researcher:player_research")
return "Use the inventory (default key 'i') to perform research."
end
elseif researcher.dependencies.mcl_inventory then
return function(name)
return "Use the inventory (default key 'i') to perform research."
end
elseif researcher.dependencies.unified_inventory then
return function(name)
return "Use the inventory (default key 'i') to perform research."
end
elseif researcher.dependencies.i3 then
return function(name)
local player = minetest.get_player_by_name(name)
i3.set_tab(player,"research")
return "Use the inventory (default key 'i') to perform research."
end
else
return function(name)
minetest.show_formspec(name,"researcher:player_research",researcher.get_formspec(name))
end
end
end)()
minetest.register_chatcommand("research",{
params = "<action> <value>",
description = "interact with researcher",
func = function(name,params)
if params == "reset" then
researcher.data["player_" .. name] = researcher.initialize_player_data(name)
return true, "Research has been fully reset."
end
if params:find("^reset .+$") then
local item = params:split(" ")[2]
researcher.get_player_data(name).research[item] = nil
researcher.save_player_data(name)
return true, "Research has been reset for " .. item
end
if not params or params == "" or params == "show" then
return true, show_formspec(name)
end
return false, "Unknown or incorrect researcher command"
end
})

484
mods/researcher/src/gui.lua Normal file
View file

@ -0,0 +1,484 @@
-- ----------------- --
-- LOCAL FUNCTIONS --
-- ----------------- --
local function format_formspec(formspec,...)
return string.format(string.gsub(formspec,"%-%-[^\n]*",""),...)
end
local get_ui = (function()
if researcher.dependencies.sfinv and sfinv.enabled and not researcher.dependencies.i3 then
-- Get sfinv formspec
return function(player_name)
local data = researcher.get_formspec_data(player_name)
local status_string = "hypertext[0.3,2.3;4,4;;<global halign=center>(place item above to analyze)]"
if data.last_result then
status_string = "hypertext[0.3,2.3;4,4;;<global halign=center><i>Research successful!</i>\n\n" .. data.last_result .. "]"
end
local progress_bar = data.subject.image and string.format([[
box[4.475,3.675;%f,0.625;#00ff00]
image[4.45,3.65;3.65,0.8;researcher_research_points_border.png;7]
hypertext[4.3,3.65;4,0.9;;<global valign=middle halign=center><b>Level %s</b>%s]
]],
data.is_max_level and 2.85 or (2.85 * (data.current_points / data.points_to_next_level)),
data.is_max_level and "MAX" or (data.subject.research and data.subject.research.level or 1),
data.is_max_level and "" or ("\n" .. (data.subject.research and data.subject.research.points or 0) .. " / " .. researcher.get_points_to_next_level(player_name,data.subject.item.name)))
local decor = data.is_inventory_empty and "inactive" or "active"
return format_formspec([[
-- Background boxes; research/duplication on the left, info/progress on the right
box[0,0;3.8,4.9;#00000040]
box[4,0;3.8,4.9;#00000040]
-- Player's 1x1 research inventory with optional research/duplication button
-- at the bottom
image[1.5,0.8;1,1;researcher_gui_hb_bg.png]
image[1.1,0.375;2,2;researcher_research_inventory_decor_%s.png]
list[current_player;research;1.5,0.8;1,1;0]
listring[current_player;main]
listring[current_player;research]
%s
-- Current research item image, name, and groups
item_image[5,0.2;2.2,2.2;%s]
hypertext[4.3,2.3;4,0.5;;<global halign=center size=18><b>%s</b>]
box[4.3,2.6;3.2,0.001;#00000099]
hypertext[4.3,2.7;4,1.5;;<global halign=center>%s]
-- Research level/points progress bar
%s
]],
decor,
data.is_inventory_empty and status_string or string.format("button[0.4,3.3;3.15,1.6;%s;%s]",data.is_max_level and "duplicate" or "research",data.is_max_level and "Duplicate" or "Research"),
data.subject.image or "",
data.subject.description,
data.subject.research and data.subject.groups or "(research to learn item groups)",
progress_bar or "")
end
-- Get Mineclonia/VoxelLibre formspec
elseif researcher.dependencies.mcl_inventory then
return function(player_name)
local data = researcher.get_formspec_data(player_name)
local status_string = "hypertext[1,2.75;4.5,4;;<global halign=center>(place item above to analyze)]"
if data.last_result then
status_string = "hypertext[1,2.75;4.5,4;;<global halign=center><i>Research successful!</i>\n\n" .. data.last_result .. "]"
end
local progress_bar = data.subject.image and string.format([[
box[6.55,4.41;%f,0.73;#00ff00]
image[6.5,4.4;3.725,0.75;researcher_research_points_border.png;7]
hypertext[6.1,4.325;4.5,0.9;;<global valign=middle halign=center><b>Level %s</b>%s]
]],
data.is_max_level and 3.66 or (3.66 * (data.current_points / data.points_to_next_level)),
data.is_max_level and "MAX" or (data.subject.research and data.subject.research.level or 1),
data.is_max_level and "" or ("\n" .. (data.subject.research and data.subject.research.points or 0) .. " / " .. researcher.get_points_to_next_level(player_name,data.subject.item.name)))
local decor = data.is_inventory_empty and "inactive" or "active"
return format_formspec([[
-- Background boxes; research/duplication on the left, info/progress on the right
box[1,0.2;4.5,5.1;#00000040]
box[6.1,0.2;4.5,5.1;#00000040]
-- Player's 1x1 research inventory with optional research/duplication button
-- at the bottom
image[2.75,1;1,1;researcher_gui_hb_bg.png]
image[2.25,0.5;2,2;researcher_research_inventory_decor_%s.png]
list[current_player;research;2.75,1;1,1;0]
listring[current_player;main]
listring[current_player;research]
%s
-- Current research item image, name, and groups
item_image[7.325,0.4;2,2;%s]
hypertext[6.1,2.5;4.5,0.5;;<global halign=center size=18><b>%s</b>]
box[6.5,2.8;3.75,0.001;#00000099]
hypertext[6.1,2.9;4.5,1.5;;<global halign=center>%s]
-- Research level/points progress bar
%s
]],
decor,
data.is_inventory_empty and status_string or string.format("button[1.25,4.4;4,0.75;%s;%s]",data.is_max_level and "duplicate" or "research",data.is_max_level and "Duplicate" or "Research"),
data.subject.image or "",
data.subject.description,
data.subject.research and data.subject.groups or "(research to learn item groups)",
progress_bar or "")
end
-- Get Unified Inventory formspec
elseif researcher.dependencies.unified_inventory then
return function(player_name)
local data = researcher.get_formspec_data(player_name)
local status_string = "hypertext[0.5,2.75;4.5,4;;<global halign=center>(place item above to analyze)]"
if data.last_result then
status_string = "hypertext[0.5,2.75;4.5,4;;<global halign=center><i>Research successful!</i>\n\n" .. data.last_result .. "]"
end
local progress_bar = data.subject.image and string.format([[
box[6.05,4.61;%f,0.73;#00ff00]
image[6,4.6;3.725,0.75;researcher_research_points_border.png;7]
hypertext[5.6,4.525;4.5,0.9;;<global valign=middle halign=center><b>Level %s</b>%s]
]],
data.is_max_level and 3.66 or (3.66 * (data.current_points / data.points_to_next_level)),
data.is_max_level and "MAX" or (data.subject.research and data.subject.research.level or 1),
data.is_max_level and "" or ("\n" .. (data.subject.research and data.subject.research.points or 0) .. " / " .. researcher.get_points_to_next_level(player_name,data.subject.item.name)))
local decor = data.is_inventory_empty and "inactive" or "active"
return format_formspec([[
-- Background boxes; research/duplication on the left, info/progress on the right
box[0.5,0.2;4.5,5.4;#00000040]
box[5.6,0.2;4.5,5.4;#00000040]
-- Player's 1x1 research inventory with optional research/duplication button
-- at the bottom
image[2.25,1;1,1;researcher_gui_hb_bg.png]
image[1.75,0.5;2,2;researcher_research_inventory_decor_%s.png]
list[current_player;research;2.25,1;1,1;0]
listring[current_player;main]
listring[current_player;research]
%s
-- Current research item image, name, and groups
item_image[6.825,0.4;2,2;%s]
hypertext[5.6,2.5;4.5,0.5;;<global halign=center size=18><b>%s</b>]
box[6,2.8;3.75,0.001;#00000099]
hypertext[5.6,2.9;4.5,1.5;;<global halign=center>%s]
-- Research level/points progress bar
%s
]],
decor,
data.is_inventory_empty and status_string or string.format("button[0.75,4.6;4,0.75;%s;%s]",data.is_max_level and "duplicate" or "research",data.is_max_level and "Duplicate" or "Research"),
data.subject.image or "",
data.subject.description,
data.subject.research and data.subject.groups or "(research to learn item groups)",
progress_bar or "")
end
-- Get i3 formspec
elseif researcher.dependencies.i3 then
return function(player_name)
local data = researcher.get_formspec_data(player_name)
local status_string = "hypertext[0.5,3.05;4.5,4;;<global halign=center>(place item above to analyze)]"
if data.last_result then
status_string = "hypertext[0.5,3.05;4.5,4;;<global halign=center><i>Research successful!</i>\n\n" .. data.last_result .. "]"
end
local progress_bar = data.subject.image and string.format([[
box[5.75,4.925;%f,0.715;#00ff00]
image[5.7,4.9;3.725,0.75;researcher_research_points_border.png;7]
hypertext[5.3,4.825;4.5,0.9;;<global valign=middle halign=center><b>Level %s</b>%s]
]],
data.is_max_level and 3.64 or (3.64 * (data.current_points / data.points_to_next_level)),
data.is_max_level and "MAX" or (data.subject.research and data.subject.research.level or 1),
data.is_max_level and "" or ("\n" .. (data.subject.research and data.subject.research.points or 0) .. " / " .. researcher.get_points_to_next_level(player_name,data.subject.item.name)))
local decor = data.is_inventory_empty and "inactive" or "active"
return format_formspec([[
-- Background boxes; research/duplication on the left, info/progress on the right
box[0.5,0.5;4.5,5.4;#00000040]
box[5.3,0.5;4.5,5.4;#00000040]
-- Player's 1x1 research inventory with optional research/duplication button
-- at the bottom
image[2.25,1.3;1,1;researcher_gui_hb_bg.png]
image[1.75,0.8;2,2;researcher_research_inventory_decor_%s.png]
list[current_player;research;2.25,1.3;1,1;0]
listring[current_player;main]
listring[current_player;research]
%s
-- Current research item image, name, and groups
item_image[6.525,0.7;2,2;%s]
hypertext[5.3,2.8;4.5,0.5;;<global halign=center size=18><b>%s</b>]
box[5.7,3.1;3.75,0.001;#00000099]
hypertext[5.3,3.2;4.5,1.5;;<global halign=center>%s]
-- Research level/points progress bar
%s
]],
decor,
data.is_inventory_empty and status_string or string.format("button[0.75,4.9;4,0.75;%s;%s]",data.is_max_level and "duplicate" or "research",data.is_max_level and "Duplicate" or "Research"),
data.subject.image or "",
data.subject.description,
data.subject.research and data.subject.groups or "(research to learn item groups)",
progress_bar or "")
end
-- Get universal formspec
else
return function(player_name)
local data = researcher.get_formspec_data(player_name)
local status_string = "hypertext[0.3,2.3;4,4;;<global halign=center>(place item above to analyze)]"
if data.last_result then
status_string = "hypertext[0.3,2.3;4,4;;<global halign=center><i>Research successful!</i>\n\n" .. data.last_result .. "]"
end
local progress_bar = data.subject.image and string.format([[
box[4.475,3.675;%f,0.625;#00ff00]
image[4.45,3.65;3.65,0.8;researcher_research_points_border.png;7]
hypertext[4.3,3.65;4,0.9;;<global valign=middle halign=center><b>Level %s</b>%s]
]],
data.is_max_level and 2.85 or (2.85 * (data.current_points / data.points_to_next_level)),
data.is_max_level and "MAX" or (data.subject.research and data.subject.research.level or 1),
data.is_max_level and "" or ("\n" .. (data.subject.research and data.subject.research.points or 0) .. " / " .. researcher.get_points_to_next_level(player_name,data.subject.item.name)))
local decor = data.is_inventory_empty and "inactive" or "active"
return format_formspec([[
-- sfinv size + padding
size[8,9.1]
-- Background boxes; research/duplication on the left, info/progress on the right
box[0,0;3.8,4.9;#00000040]
box[4,0;3.8,4.9;#00000040]
-- Player's 1x1 research inventory with optional research/duplication button
-- at the bottom
image[1.5,0.8;1,1;researcher_gui_hb_bg.png]
image[1.1,0.375;2,2;researcher_research_inventory_decor_%s.png]
list[current_player;research;1.5,0.8;1,1;0]
listring[current_player;main]
listring[current_player;research]
%s
-- Current research item image, name, and groups
item_image[5,0.2;2.2,2.2;%s]
hypertext[4.3,2.3;4,0.5;;<global halign=center size=18><b>%s</b>]
box[4.3,2.6;3.2,0.001;#00000099]
hypertext[4.3,2.7;4,1.5;;<global halign=center>%s]
-- Research level/points progress bar
%s
-- Player's main inventory a la sfinv
image[0,5.2;1,1;researcher_gui_hb_bg.png]
image[1,5.2;1,1;researcher_gui_hb_bg.png]
image[2,5.2;1,1;researcher_gui_hb_bg.png]
image[3,5.2;1,1;researcher_gui_hb_bg.png]
image[4,5.2;1,1;researcher_gui_hb_bg.png]
image[5,5.2;1,1;researcher_gui_hb_bg.png]
image[6,5.2;1,1;researcher_gui_hb_bg.png]
image[7,5.2;1,1;researcher_gui_hb_bg.png]
list[current_player;main;0,5.2;8,1;]
list[current_player;main;0,6.35;8,3;8]
]],
decor,
data.is_inventory_empty and status_string or string.format("button[0.4,3.3;3.15,1.6;%s;%s]",data.is_max_level and "duplicate" or "research",data.is_max_level and "Duplicate" or "Research"),
data.subject.image or "",
data.subject.description,
data.subject.research and data.subject.groups or "(research to learn item groups)",
progress_bar or "")
end
end
end)()
local refresh_ui = (function()
-- Refresh sfinv formspec
if researcher.dependencies.sfinv and sfinv.enabled and not researcher.dependencies.i3 then
return function(player)
sfinv.set_player_inventory_formspec(player,sfinv.get_or_create_context(player))
end
-- Refresh Mineclone formspec
elseif researcher.dependencies.mcl_inventory then
return function(player)
mcl_inventory.update_inventory_formspec(player)
end
-- Refresh Unified Inventory formspec
elseif researcher.dependencies.unified_inventory then
return function(player)
unified_inventory.set_inventory_formspec(player,"Research")
end
-- Refresh i3 formspec
elseif researcher.dependencies.i3 then
return function(player)
i3.set_fs(player)
end
-- Refresh universal formspec
else
return function(player)
local name = player:get_player_name()
minetest.show_formspec(name,"researcher:player_research",get_ui(name))
end
end
end)()
local do_research = function(player,fields,refresh)
if fields.research then
local player_name = player:get_player_name()
local inventory = player:get_inventory()
local item = inventory:get_stack("research",1)
if not item:is_empty() then
-- Get research level to compare with after research
local player_data = researcher.get_player_data(player_name)
local research = player_data.research[item:get_name()]
local level_before = 1
if research then
level_before = research.level
end
-- Perform research
local results = researcher.research_inventory(player_name,inventory,"research")
-- Aggregate results
local totals = {
items = 0,
base = 0,
bonuses = {},
}
for _,itemstack in ipairs(results) do
for _,i in ipairs(itemstack) do
totals.items = totals.items + 1
totals.base = totals.base + i.base
for _,bonus in ipairs(i.bonuses) do
totals.bonuses[bonus.reason] = totals.bonuses[bonus.reason] or 0
totals.bonuses[bonus.reason] = totals.bonuses[bonus.reason] + bonus.points
end
end
end
player_data.last_result = "Items researched: <style color=#0099cc>" .. totals.items .. "</style>\nBase points: <style color=#0099cc>" .. totals.base .. "</style>\n"
for reason,points in pairs(totals.bonuses) do
player_data.last_result = player_data.last_result .. reason .. ": " .. (points > 0 and "<style color=#00cc00>+" or "<style color=#cc0000>-") .. points .. "</style>\n"
end
-- Refresh UI
if refresh ~= false then
refresh_ui(player)
end
-- Determine level up progress
research = player_data.research[item:get_name()]
if research and research.level > level_before then
-- Unlock eureka award
if researcher.settings.awards then
awards.unlock(player_name,"researcher:eureka")
end
-- Play research level up sound
minetest.sound_play({
name = "researcher_level_up",
gain = 0.5,
},{ to_player = player_name },true)
else
-- Play normal research sound
minetest.sound_play({
name = "researcher_research",
gain = 0.1,
pitch = 1 + (math.random(1,4) / 10),
},{ to_player = player_name },true)
end
end
elseif fields.duplicate then
researcher.duplicate_research(player)
minetest.sound_play({
name = "researcher_duplicate",
gain = 0.5,
pitch = 1.5,
},{ to_player = player_name },true)
end
end
-- ------------------ --
-- GUI INTEGRATIONS --
-- ------------------ --
-- Configure sfinv UI
if researcher.dependencies.sfinv and sfinv.enabled and not researcher.dependencies.i3 then
sfinv.register_page("researcher:player_research",{
title = "Research",
get = function(self,player,context)
return sfinv.make_formspec(player,context,get_ui(player:get_player_name()),true)
end,
is_in_nav = function()
return true
end,
on_player_receive_fields = function(self,player,context,fields)
do_research(player,fields)
end,
})
-- Configure Mineclonia/VoxelLibre UI
elseif researcher.dependencies.mcl_inventory then
mcl_inventory.register_survival_inventory_tab({
id = "research",
description = "Research",
item_icon = "researcher:research_table",
show_inventory = true,
build = function(player)
return get_ui(player:get_player_name())
end,
handle = function(player, fields)
do_research(player,fields)
end,
})
-- Configure Unified Inventory UI
elseif researcher.dependencies.unified_inventory then
unified_inventory.register_page("Research",{
get_formspec = function(player,formspec)
return { formspec = formspec.standard_inv_bg .. get_ui(player:get_player_name()) }
end,
})
unified_inventory.register_button("Research",{
type = "image",
image = "researcher_icon_black.png",
tooltip = "Research",
hide_lite = false,
})
minetest.register_on_player_receive_fields(function(player,formname,fields)
if formname == "" and (fields.research or fields.duplicate) then
do_research(player,fields)
return
end
end)
-- Configure i3 UI
elseif researcher.dependencies.i3 then
i3.new_tab("research",{
description = "Research",
slots = true,
formspec = function(player, data, fs)
fs(get_ui(player:get_player_name()))
end,
fields = function(player, data, fields)
if fields.research or fields.duplicate then
do_research(player,fields,false)
end
end,
})
-- Configure universal/standalone UI
else
minetest.register_on_player_receive_fields(function(player,formname,fields)
if formname == "researcher:player_research" then
do_research(player,fields)
return
end
end)
end
-- Refresh UI when research inventory changes
minetest.register_on_player_inventory_action(function(player, action, inventory, inventory_info)
if inventory_info.listname == "research" or inventory_info.to_list == "research" or inventory_info.from_list == "research" then
refresh_ui(player)
return true
end
return false
end)

View file

@ -0,0 +1,62 @@
-- Initialize player research inventory
minetest.register_on_joinplayer(function(player)
player:get_inventory():set_size("research",1)
end)
-- Set the subject of the player's research based on their research inventory
minetest.register_on_player_inventory_action(function(player, action, inventory, inventory_info)
if inventory_info.listname == "research" or inventory_info.to_list == "research" or inventory_info.from_list == "research" then
-- Get item info from research inventory
local item = inventory:get_stack("research",1)
-- Get player name and data
local player_name = player:get_player_name()
local player_data = researcher.get_player_data(player_name)
-- Cache inventory empty status
player_data.subject.is_empty = item:is_empty()
-- Set the player's research subject if research inventory has an item in it
local name = item:get_name()
local subject = player_data.subject
local description, groups
if not item:is_empty() then
subject.name = name
subject.item = researcher.registered_items[name]
subject.image = name
description = minetest.registered_items[name]
if description then
subject.description = description.description:split("\n",1)[1]
else
subject.description = "???"
end
if subject.item then
subject.groups = (function()
local str = ""
local grouplist = {}
for group,_ in pairs(subject.item.groups) do
table.insert(grouplist,group)
end
table.sort(grouplist)
return table.concat(grouplist,", ")
end)()
subject.research = player_data.research[name]
else
subject.groups = "(groups unknown)"
subject.research = nil
subject.item = {
name = "???",
groups = {},
adjustments = {},
}
end
end
-- Save player data
researcher.save_player_data(player_name)
return true
end
return false
end)

View file

@ -0,0 +1,178 @@
-- Function to get formspec string
local function get_formspec(item)
-- Set item info string
local iteminfo = [[
box[1,2.75;6,0.001;#00000099]
hypertext[2.3,3.15;4,1.5;;<global halign=center>(place item above to set focus)]
]]
if item then
local description = minetest.registered_items[item] and minetest.registered_items[item].description:split("\n")[1]
local groups = researcher.registered_items[item] and (function()
local str = ""
local grouplist = {}
for group,_ in pairs(researcher.registered_items[item].groups) do
table.insert(grouplist,group)
end
table.sort(grouplist)
return table.concat(grouplist,", ")
end)()
iteminfo = string.format([[
box[1,2.75;6,0.001;#00000099]
hypertext[2.3,3;4,0.5;;<global halign=center size=24><b>%s</b>]
hypertext[2.3,3.35;4,1.5;;<global halign=center size=18>%s]
]],
description or "???",
groups or "(groups unknown)")
end
return string.format([[
size[8,9.1]
box[0,0;7.8,5;#00000040]
image[3.1,0.575;2,2;researcher_research_inventory_decor_%s.png]
list[context;focus;3.5,1;1,1;0]
listring[current_player;main]
listring[context;focus]
%s
image[0,5.2;1,1;researcher_gui_hb_bg.png]
image[1,5.2;1,1;researcher_gui_hb_bg.png]
image[2,5.2;1,1;researcher_gui_hb_bg.png]
image[3,5.2;1,1;researcher_gui_hb_bg.png]
image[4,5.2;1,1;researcher_gui_hb_bg.png]
image[5,5.2;1,1;researcher_gui_hb_bg.png]
image[6,5.2;1,1;researcher_gui_hb_bg.png]
image[7,5.2;1,1;researcher_gui_hb_bg.png]
list[current_player;main;0,5.2;8,1;]
list[current_player;main;0,6.35;8,3;8]
]],
item and "active" or "inactive",
iteminfo)
end
-- Function for refreshing infotext when node meta inventory changes
local function update_metadata(pos)
local meta = minetest.get_meta(pos)
local item = meta:get_inventory():get_stack("focus",1)
if item and not item:is_empty() then
local iname = item:get_name()
item = minetest.registered_items[iname]
description = ""
if item and item.description then
meta:set_string("infotext","Research Table: " .. item.description:split("\n")[1])
end
meta:set_string("formspec",get_formspec(iname))
else
meta:set_string("formspec",get_formspec(nil))
meta:set_string("infotext","Research Table")
end
end
-- Register research table node
minetest.register_node("researcher:research_table",{
-- Node definition fields
description = "Research Table",
short_description = "Research Table",
drawtype = "mesh",
mesh = "research_table.obj",
tiles = {
{ name = "researcher_research_table_frame.png" },
{ name = "researcher_research_table_surface.png" },
},
paramtype2 = "4dir",
stack_max = 1,
sounds = (function()
if researcher.dependencies.default then
return default.node_sound_wood_defaults()
elseif researcher.dependencies.mcl_sounds then
return mcl_sounds.node_sound_wood_defaults()
else
return nil -- no specific sounds
end
end)(),
-- Set research table groups
groups = {
oddly_breakable_by_hand = 1,
},
-- Initialize research table data
on_construct = function(pos)
-- Set inventory size
local meta = minetest.get_meta(pos)
local inventory = meta:get_inventory()
inventory:set_size("focus", 1)
-- Set infotext
meta:set_string("infotext","Research Table")
-- Set meta formspec
meta:set_string("formspec",get_formspec(nil))
end,
-- Drop inventory contents when destroyed
on_destruct = function(pos)
local item = minetest.get_meta(pos):get_inventory():get_stack("focus",1)
if item and not item:is_empty() then
minetest.add_item(pos,item)
end
end,
-- Update infotext when inventory changes
on_metadata_inventory_move = update_metadata,
on_metadata_inventory_take = update_metadata,
on_metadata_inventory_put = update_metadata,
})
-- Register research table crafting recipe
minetest.register_craft({
output = "researcher:research_table",
recipe = {
{"group:stone", "group:stone", "group:stone"},
{"group:wood", "group:wood", "group:wood"},
{"group:wood", "", "group:wood"},
},
})
-- Register ABM for activation particles
minetest.register_abm({
label = "Researcher: Research Table Activation Particles",
nodenames = {"researcher:research_table"},
interval = 4,
chance = 1,
catch_up = false,
action = function(pos)
-- Do nothing if research table is empty
if minetest.get_meta(pos):get_inventory():get_stack("focus",1):is_empty() then
return
end
-- Show item particles to nearby players
local radius = researcher.settings.research_table_player_radius
for object in minetest.objects_in_area(pos:add(-2),pos:add(2)) do
if object:is_player() then
minetest.add_particlespawner({
playername = object:get_player_name(),
amount = 16,
time = 4,
pos = {
min = pos:add(vector.new(-0.6,0,-0.6)),
max = pos:add(vector.new(0.6,0,0.6)),
},
minsize = 1,
maxsize = 1.5,
minvel = { x = 0, y = 0.05, z = 0 },
maxvel = { x = 0, y = 0.1, z = 0 },
minacc = { x = 0, y = 0.1, z = 0 },
maxacc = { x = 0, y = 0.2, z = 0 },
minexptime = 4.5,
maxexptime = 3,
texture = "plus.png^[colorize:#ffff77^[opacity:180",
glow = 14,
collisiondetection = false,
})
end
end
end,
})

View file

@ -0,0 +1,195 @@
-- Scan all items for groups
minetest.register_on_mods_loaded(function()
-- Reduce research difficulty for items not present in mapgen
local mapgen_nodes = {}
if researcher.settings.discount_mapgen < 0 then
mapgen_nodes = {
map_drop = function(self,node)
local def = minetest.registered_nodes[node]
if not def then
return "<ndef>"
end
local drop = def.drop
if not drop or drop == "" then
self[node] = true
return node
elseif type(drop) == "string" then
self[drop] = true
return drop
elseif type(drop) == "table" then
for _,item in ipairs(drop.items or {}) do
if type(item) == "table" then
for _,i in ipairs(item.items) do
self[i] = true
end
end
end
end
end,
}
for _,def in pairs(minetest.registered_biomes) do
for _,node_type in ipairs({
"node_top",
"node_filler",
"node_stone",
"node_water_top",
"node_water",
"node_river_water",
"node_riverbed",
"node_cave_liquid",
"node_dungeon",
"node_dungeon_alt",
"node_dungeon_stair",
}) do
if def[node_type] then
local node = def[node_type]
if type(node) == "string" then
mapgen_nodes[node] = true
mapgen_nodes:map_drop(node)
elseif type(node) == "table" then
for _,n in ipairs(node) do
if type(n) == "string" then
mapgen_nodes[n] = true
mapgen_nodes:map_drop(n)
end
end
end
end
end
end
local read_schematics = {}
for _,def in pairs(minetest.registered_decorations) do
if (not def.deco_type or def.deco_type == "simple") and def.decoration then
if type(def.decoration) == "string" then
mapgen_nodes[def.decoration] = true
mapgen_nodes:map_drop(def.decoration)
elseif type(def.decoration) == "table" then
for _,node in ipairs(def.decoration) do
if type(node) == "string" then
mapgen_nodes[node] = true
local to = mapgen_nodes:map_drop(node)
end
end
end
elseif def.deco_type == "schematic" and def.schematic then
local schematic
if type(def.schematic) == "string" and not read_schematics[def.schematic] then
read_schematics[def.schematic] = true
schematic = minetest.read_schematic(def.schematic,{ write_yslice_prob = "none" })
elseif type(def.schematic) == "table" then
schematic = def.schematic
end
if schematic then
for _,node in ipairs(schematic.data) do
if type(node.name) == "string" and node.name ~= "air" then
mapgen_nodes[node.name] = true
mapgen_nodes:map_drop(node.name)
end
end
end
end
end
for _,def in pairs(minetest.registered_ores) do
if type(def.ore) == "string" then
mapgen_nodes[def.ore] = true
mapgen_nodes:map_drop(def.ore)
end
end
researcher.register_adjustment({
name = "researcher:discount_mapgen",
reason = "Item not abundant in the world",
calculate = function(item)
return (not mapgen_nodes[item]) and researcher.settings.discount_mapgen or 0
end,
})
else
researcher.register_adjustment({
name = "researcher:discount_mapgen",
reason = "Item not abundant in world",
calculate = function() return 0 end,
})
end
-- Register low stack discount
local stack_max = researcher.dependencies.mcl_inventory and 64 or tonumber(minetest.settings:get("default_stack_max",99) or 99)
local low_stack = {}
if researcher.settings.discount_stack_max < 0 then
researcher.register_adjustment({
name = "researcher:discount_stack_max",
reason = "Item has a stack max less than the default max",
calculate = function(item)
return low_stack[item] and researcher.settings.discount_stack_max or 0
end,
})
else
researcher.register_adjustment({
name = "researcher:discount_stack_max",
reason = "Item has a stack max less than the default max",
calculate = function() return 0 end,
})
end
-- Reduce research difficulty for items that are not craftable
local not_craftable = {}
if researcher.settings.discount_not_craftable < 0 then
researcher.register_adjustment({
name = "researcher:discount_not_craftable",
reason = "Item is not craftable",
calculate = function(item)
return (not mapgen_nodes[item] and not_craftable[item]) and researcher.settings.discount_not_craftable or 0
end,
})
else
researcher.register_adjustment({
name = "researcher:discount_not_craftable",
reason = "Item is not craftable",
calculate = function() return 0 end,
})
end
-- Register items with Researcher
for name,def in pairs(minetest.registered_items) do
-- Get low stack data
if def.tool_capabilities or ((def.stack_max or stack_max) < stack_max) then
low_stack[name] = def.stack_max or (def.tool_capabilities and 1) or stack_max
end
-- Filter out unwanted groups
local groups = def.groups
local keep_groups = {}
if groups then
for group,value in pairs(groups) do
if value > 0 and not researcher.excluded_groups[group] then
table.insert(keep_groups,group)
end
end
end
-- Check for valid recipes
local recipes = minetest.get_all_craft_recipes(name) or {}
for _,recipe in ipairs(recipes) do
if recipe.method == "normal" or recipe.method == "cooking" then
recipes = 1
break
end
end
if recipes ~= 1 then
not_craftable[name] = true
end
-- Register item
researcher.register_item({
name = name,
points_per_level = researcher.settings.points_per_level,
groups = keep_groups,
})
end
end)

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B