write something there
This commit is contained in:
commit
b4b6c08f4f
8546 changed files with 309825 additions and 0 deletions
456
mods/researcher/src/api.lua
Normal file
456
mods/researcher/src/api.lua
Normal 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
|
43
mods/researcher/src/awards.lua
Normal file
43
mods/researcher/src/awards.lua
Normal 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
|
183
mods/researcher/src/bonuses.lua
Normal file
183
mods/researcher/src/bonuses.lua
Normal 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
|
51
mods/researcher/src/commands.lua
Normal file
51
mods/researcher/src/commands.lua
Normal 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
484
mods/researcher/src/gui.lua
Normal 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)
|
62
mods/researcher/src/inventory.lua
Normal file
62
mods/researcher/src/inventory.lua
Normal 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)
|
178
mods/researcher/src/research_table.lua
Normal file
178
mods/researcher/src/research_table.lua
Normal 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,
|
||||
})
|
195
mods/researcher/src/scan.lua
Normal file
195
mods/researcher/src/scan.lua
Normal 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)
|
Loading…
Add table
Add a link
Reference in a new issue