removing API half, leaving only the market definitions

This commit is contained in:
FaceDeer 2020-01-18 17:43:23 -07:00
parent a00904abe4
commit 1761d9e844
22 changed files with 573 additions and 2843 deletions

View File

@ -1,528 +0,0 @@
local default_modpath = minetest.get_modpath("default")
if not default_modpath then return end
-- internationalization boilerplate
local MP = minetest.get_modpath(minetest.get_current_modname())
local S, NS = dofile(MP.."/intllib.lua")
-- Only register gold coins once, if required
local gold_coins_registered = false
local register_gold_coins = function()
if not gold_coins_registered then
minetest.register_craftitem("commoditymarket:gold_coins", {
description = S("Gold Coins"),
_doc_items_longdesc = S("A gold ingot is far too valuable to use as a basic unit of value, so it has become common practice to divide the standard gold bar into one thousand small disks to make trade easier."),
_doc_items_usagehelp = S("Gold coins can be deposited and withdrawn from markets that accept them as currency. These markets can make change if you have a thousand coins and would like them back in ingot form again."),
inventory_image = "commoditymarket_gold_coins.png",
stack_max = 1000,
})
gold_coins_registered = true
end
end
local default_items = {"default:axe_bronze","default:axe_diamond","default:axe_mese","default:axe_steel","default:axe_steel","default:axe_stone","default:axe_wood","default:pick_bronze","default:pick_diamond","default:pick_mese","default:pick_steel","default:pick_stone","default:pick_wood","default:shovel_bronze","default:shovel_diamond","default:shovel_mese","default:shovel_steel","default:shovel_stone","default:shovel_wood","default:sword_bronze","default:sword_diamond","default:sword_mese","default:sword_steel","default:sword_stone","default:sword_wood", "default:blueberries", "default:book", "default:bronze_ingot", "default:clay_brick", "default:clay_lump", "default:coal_lump", "default:copper_ingot", "default:copper_lump", "default:diamond", "default:flint", "default:gold_ingot", "default:gold_lump", "default:iron_lump", "default:mese_crystal", "default:mese_crystal_fragment", "default:obsidian_shard", "default:paper", "default:steel_ingot", "default:stick", "default:tin_ingot", "default:tin_lump", "default:acacia_tree", "default:acacia_wood", "default:apple", "default:aspen_tree", "default:aspen_wood", "default:blueberry_bush_sapling", "default:bookshelf", "default:brick", "default:bronzeblock", "default:bush_sapling", "default:cactus", "default:clay", "default:coalblock", "default:cobble", "default:copperblock", "default:desert_cobble", "default:desert_sand", "default:desert_sandstone", "default:desert_sandstone_block", "default:desert_sandstone_brick", "default:desert_stone", "default:desert_stone_block", "default:desert_stonebrick", "default:diamondblock", "default:dirt", "default:glass", "default:goldblock", "default:gravel", "default:ice", "default:junglegrass", "default:junglesapling", "default:jungletree", "default:junglewood", "default:ladder_steel", "default:ladder_wood", "default:large_cactus_seedling", "default:mese", "default:mese_post_light", "default:meselamp", "default:mossycobble", "default:obsidian", "default:obsidian_block", "default:obsidian_glass", "default:obsidianbrick", "default:papyrus", "default:pine_sapling", "default:pine_tree", "default:pine_wood", "default:sand", "default:sandstone", "default:sandstone_block", "default:sandstonebrick", "default:sapling", "default:silver_sand", "default:silver_sandstone", "default:silver_sandstone_block", "default:silver_sandstone_brick", "default:snow", "default:snowblock", "default:steelblock", "default:stone", "default:stone_block", "default:stonebrick", "default:tinblock", "default:tree", "default:wood",}
local usage_help = S("Right-click on this to open the market interface.")
------------------------------------------------------------------------------
-- King's Market
if minetest.settings:get_bool("commoditymarket_enable_kings_market") then
local kings_def = {
description = S("King's Market"),
long_description = S("The largest and most accessible market for the common man, the King's Market uses gold coins as its medium of exchange (or the equivalent in gold ingots - 1000 coins to the ingot). However, as a respectable institution of the surface world, the King's Market operates only during the hours of daylight. The purchase and sale of swords and explosives is prohibited in the King's Market. Gold coins are represented by a '☼' symbol."),
currency = {
["default:gold_ingot"] = 1000,
["commoditymarket:gold_coins"] = 1
},
currency_symbol = "", -- "\u{263C}" Alchemical symbol for gold
allow_item = function(item)
if item:sub(1,13) == "default:sword" or item:sub(1,4) == "tnt:" then
return false
end
return true
end,
inventory_limit = 100000,
--sell_limit =, -- no sell limit for the King's Market
initial_items = default_items,
}
register_gold_coins()
commoditymarket.register_market("kings", kings_def)
local kings_protect = minetest.settings:get_bool("commoditymarket_protect_kings_market", true)
local on_blast
if kings_protect then
on_blast = function() end
end
minetest.register_node("commoditymarket:kings_market", {
description = kings_def.description,
_doc_items_longdesc = kings_def.long_description,
_doc_items_usagehelp = usage_help,
tiles = {"default_chest_top.png","default_chest_top.png",
"default_chest_side.png","default_chest_side.png",
"commoditymarket_empty_shelf.png","default_chest_side.png^commoditymarket_crown.png",},
paramtype2 = "facedir",
is_ground_content = false,
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
sounds = default.node_sound_wood_defaults(),
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
local timeofday = minetest.get_timeofday()
if timeofday > 0.2 and timeofday < 0.8 then
commoditymarket.show_market("kings", clicker:get_player_name())
else
minetest.chat_send_player(clicker:get_player_name(), S("At this time of day the King's Market is closed."))
minetest.sound_play({name = "commoditymarket_error", gain = 0.1}, {to_player=clicker:get_player_name()})
end
end,
can_dig = function(pos, player)
return not kings_protect or minetest.check_player_privs(player, "protection_bypass")
end,
on_blast = on_blast,
})
end
-------------------------------------------------------------------------------
-- Night Market
if minetest.settings:get_bool("commoditymarket_enable_night_market") then
local night_def = {
description = S("Night Market"),
long_description = "When the sun sets and the stalls of the King's Market close, other vendors are just waking up to share their wares. The Night Market is not as voluminous as the King's Market but accepts a wider range of wares. It accepts the same gold coinage of the realm, one thousand coins to the gold ingot.",
currency = {
["default:gold_ingot"] = 1000,
["commoditymarket:gold_coins"] = 1
},
currency_symbol = "", --"\u{263C}"
inventory_limit = 10000,
--sell_limit =, -- no sell limit for the Night Market
initial_items = default_items,
anonymous = true,
}
register_gold_coins()
commoditymarket.register_market("night", night_def)
local night_protect = minetest.settings:get_bool("commoditymarket_protect_night_market", true)
local on_blast
if night_protect then
on_blast = function() end
end
minetest.register_node("commoditymarket:night_market", {
description = night_def.description,
_doc_items_longdesc = night_def.long_description,
_doc_items_usagehelp = usage_help,
tiles = {"default_chest_top.png","default_chest_top.png",
"default_chest_side.png","default_chest_side.png",
"commoditymarket_empty_shelf.png","default_chest_side.png^commoditymarket_moon.png",},
paramtype2 = "facedir",
is_ground_content = false,
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
sounds = default.node_sound_wood_defaults(),
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
local timeofday = minetest.get_timeofday()
if timeofday < 0.2 or timeofday > 0.8 then
commoditymarket.show_market("night", clicker:get_player_name())
else
minetest.chat_send_player(clicker:get_player_name(), S("At this time of day the Night Market is closed."))
minetest.sound_play({name = "commoditymarket_error", gain = 0.1}, {to_player=clicker:get_player_name()})
end
end,
can_dig = function(pos, player)
return not night_protect or minetest.check_player_privs(player, "protection_bypass")
end,
on_blast = on_blast,
})
end
-------------------------------------------------------------------------------
if minetest.settings:get_bool("commoditymarket_enable_caravan_market", true) then
-- "Trader's Caravan" - small-capacity market that players can summon
local time_until_caravan = 120 -- caravan arrives in two minutes
local dwell_time = 600 -- caravan leaves ten minutes after last usage
local caravan_def = {
description = S("Trader's Caravan"),
long_description = S("Unlike most markets that have well-known fixed locations that travelers congregate to, the network of Trader's Caravans is fluid and dynamic in their locations. A Trader's Caravan can show up anywhere, make modest trades, and then be gone the next time you visit them. These caravans accept gold and gold coins as a currency (one gold ingot to one thousand gold coins exchange rate). Any reasonably-wealthy person can create a signpost marking a location where Trader's Caravans will make a stop."),
currency = {
["default:gold_ingot"] = 1000,
["commoditymarket:gold_coins"] = 1
},
currency_symbol = "", --"\u{263C}"
inventory_limit = 1000,
sell_limit = 1000,
initial_items = default_items,
}
register_gold_coins()
minetest.register_craft({
output = "commoditymarket:caravan_post",
recipe = {
{'group:wood', 'group:wood', ''},
{'group:wood', "default:gold_ingot", ''},
{'group:wood', "default:chest_locked", ''},
}
})
commoditymarket.register_market("caravan", caravan_def)
local create_caravan_def = function(override_table)
local def = {
description = caravan_def.description,
_doc_items_longdesc = caravan_def.long_description,
_doc_items_usagehelp = usage_help,
drawtype = "mesh",
mesh = "commoditymarket_wagon.obj",
tiles = {
{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
{ name = "default_wood.png", backface_culling = true }, -- base wood
{ name = "default_fence_rail_wood.png", backface_culling = true }, -- wheel sides
{ name = "default_coal_block.png", backface_culling = true }, -- wheel tyre
{ name = "commoditymarket_shingles_wood.png", backface_culling = true }, -- roof
{ name = "default_junglewood.png", backface_culling = true }, -- corner wood
},
collision_box = {
type = "fixed",
fixed = {
{-0.75, -0.5, -1.25, 0.75, 1.5, 1.25},
},
},
selection_box = {
type = "fixed",
fixed = {
{-0.75, -0.5, -1.25, 0.75, 1.5, 1.25},
},
},
paramtype2 = "facedir",
drop = "",
groups = {choppy = 2, oddly_breakable_by_hand = 1, not_in_creative_inventory = 1},
sounds = default.node_sound_wood_defaults(),
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
commoditymarket.show_market("caravan", clicker:get_player_name())
local timer = minetest.get_node_timer(pos)
timer:start(dwell_time)
end,
after_destruct = function(pos, oldnode)
local facedir = oldnode.param2
local dir = minetest.facedir_to_dir(facedir)
local target = vector.add(pos, vector.multiply(dir,-3))
local target_node = minetest.get_node(target)
if target_node.name == "commoditymarket:caravan_post" then
local meta = minetest.get_meta(target)
meta:set_string("infotext", S("Right-click to summon a trader's caravan"))
end
end,
on_timer = function(pos, elapsed)
minetest.set_node(pos, {name="air"})
minetest.sound_play("commoditymarket_register_closed", {
pos = pos,
gain = 1.0, -- default
max_hear_distance = 32, -- default, uses an euclidean metric
})
end,
}
if override_table then
for k, v in pairs(override_table) do
def[k] = v
end
end
return def
end
-- Create five caravans with different textures, randomly pick which one shows up.
minetest.register_node("commoditymarket:caravan_market_1", create_caravan_def())
minetest.register_node("commoditymarket:caravan_market_2", create_caravan_def({
tiles = {
{ name = "commoditymarket_door_wood.png^[multiply:#CCCCFF", backface_culling = true }, -- door
{ name = "default_acacia_wood.png", backface_culling = true }, -- base wood
{ name = "default_fence_rail_wood.png", backface_culling = true }, -- wheel sides
{ name = "default_copper_block.png", backface_culling = true }, -- wheel tyre
{ name = "commoditymarket_shingles_wood.png^[multiply:#CC8888", backface_culling = true }, -- roof
{ name = "default_wood.png", backface_culling = true }, -- corner wood
}
}))
minetest.register_node("commoditymarket:caravan_market_3", create_caravan_def({
tiles = {
{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
{ name = "default_aspen_wood.png", backface_culling = true }, -- base wood
{ name = "default_fence_aspen_wood.png", backface_culling = true }, -- wheel sides
{ name = "default_cobble.png", backface_culling = true }, -- wheel tyre
{ name = "default_stone_brick.png", backface_culling = true }, -- roof
{ name = "default_pine_tree.png", backface_culling = true }, -- corner wood
}
}))
minetest.register_node("commoditymarket:caravan_market_4", create_caravan_def({
tiles = {
{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
{ name = "default_junglewood.png", backface_culling = true }, -- base wood
{ name = "default_fence_rail_junglewood.png", backface_culling = true }, -- wheel sides
{ name = "default_obsidian.png", backface_culling = true }, -- wheel tyre
{ name = "commoditymarket_shingles_wood.png^[multiply:#88FF88", backface_culling = true }, -- roof
{ name = "default_tree.png", backface_culling = true }, -- corner wood
}
}))
minetest.register_node("commoditymarket:caravan_market_5", create_caravan_def({
tiles = {
{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
{ name = "default_pine_wood.png", backface_culling = true }, -- base wood
{ name = "default_chest_lock.png", backface_culling = true }, -- wheel sides
{ name = "default_chest_top.png", backface_culling = true }, -- wheel tyre
{ name = "default_furnace_top.png", backface_culling = true }, -- roof
{ name = "default_wood.png", backface_culling = true }, -- corner wood
}
}))
local caravan_protect = minetest.settings:get_bool("commoditymarket_protect_caravan_market", true)
local on_blast
if caravan_protect then
on_blast = function() end
end
-- This one doesn't delete itself, server admins can place a permanent instance of it that way. Maybe inside towns next to bigger stationary markets.
minetest.register_node("commoditymarket:caravan_market_permanent", {
description = caravan_def.description,
_doc_items_longdesc = caravan_def.long_description,
_doc_items_usagehelp = usage_help,
drawtype = "mesh",
mesh = "commoditymarket_wagon.obj",
tiles = {
{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
{ name = "default_wood.png", backface_culling = true }, -- base wood
{ name = "default_fence_rail_wood.png", backface_culling = true }, -- wheel sides
{ name = "default_coal_block.png", backface_culling = true }, -- wheel tyre
{ name = "commoditymarket_shingles_wood.png", backface_culling = true }, -- roof
{ name = "default_junglewood.png", backface_culling = true }, -- corner wood
},
collision_box = {
type = "fixed",
fixed = {
{-0.75, -0.5, -1.25, 0.75, 1.5, 1.25},
},
},
selection_box = {
type = "fixed",
fixed = {
{-0.75, -0.5, -1.25, 0.75, 1.5, 1.25},
},
},
paramtype2 = "facedir",
is_ground_content = false,
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
sounds = default.node_sound_wood_defaults(),
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
commoditymarket.show_market("caravan", clicker:get_player_name())
end,
can_dig = function(pos, player)
return not caravan_protect or minetest.check_player_privs(player, "protection_bypass")
end,
on_blast = on_blast,
})
-- is a 5x3 area centered around pos clear of obstruction and has usable ground?
local is_suitable_caravan_space = function(pos, facedir)
local x_dim = 2
local z_dim = 2
local dir = minetest.facedir_to_dir(facedir)
if dir.x ~= 0 then
z_dim = 1
elseif dir.z ~= 0 then
x_dim = 1
end
-- walkable ground?
for x = pos.x - x_dim, pos.x + x_dim, 1 do
for z = pos.z - z_dim, pos.z + z_dim, 1 do
local node = minetest.get_node({x=x, y=pos.y-1, z=z})
local node_def = minetest.registered_nodes[node.name]
if node_def == nil or node_def.walkable ~= true then return false end
end
end
-- buildable_to in the rest?
for y = pos.y, pos.y+2, 1 do
for x = pos.x - x_dim, pos.x + x_dim, 1 do
for z = pos.z - z_dim, pos.z + z_dim, 1 do
local node = minetest.get_node({x=x, y=y, z=z})
local node_def = minetest.registered_nodes[node.name]
if node_def == nil or node_def.buildable_to ~= true then return false end
end
end
end
return true
end
minetest.register_node("commoditymarket:caravan_post", {
description = S("Trading Post"),
_long_items_longdesc = S("This post signals passing caravan traders that customers can be found here, and signals to customers that caravan traders can be found here. If no caravan is present, right-click to summon one."),
_doc_items_usagehelp = S("The trader's caravan requires a suitable open space next to the trading post for it to arrive, and takes some time to arrive after being summoned. The post gives a countdown to the caravan's arrival when moused over."),
tiles = {"commoditymarket_sign.png^[transformR90", "commoditymarket_sign.png^[transformR270",
"commoditymarket_sign.png^commoditymarket_caravan_sign.png", "commoditymarket_sign.png^commoditymarket_caravan_sign.png^[transformFX",
"commoditymarket_sign_post.png", "commoditymarket_sign_post.png"},
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
sounds = default.node_sound_wood_defaults(),
inventory_image = "commoditymarket_caravan_sign_inventory.png",
paramtype= "light",
paramtype2 = "facedir",
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-0.125,-0.5,-0.5,0.125,2.0625,-0.25},
{-0.0625,1.4375,-0.25,0.0625,2.0,0.5},
},
},
on_construct = function(pos)
local timer = minetest.get_node_timer(pos)
timer:start(1.0)
end,
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
local timer = minetest.get_node_timer(pos)
timer:start(1.0)
end,
on_timer = function(pos, elapsed)
local node = minetest.get_node(pos)
local meta = minetest.get_meta(pos)
if node.name ~= "commoditymarket:caravan_post" then
return -- the node was removed
end
local facedir = node.param2
local dir = minetest.facedir_to_dir(facedir)
local target = vector.add(pos, vector.multiply(dir,3))
local target_node = minetest.get_node(target)
if target_node.name:sub(1,string.len("commoditymarket:caravan_market")) == "commoditymarket:caravan_market" then
-- It's already here somehow, shut down timer.
meta:set_string("infotext", "")
meta:set_float("wait_time", 0)
return
end
local is_suitable_space = is_suitable_caravan_space(target, facedir)
if not is_suitable_space then
meta:set_string("infotext", S("Indicated parking area isn't suitable.\nA 5x3 open space with solid ground\nis required for a caravan."))
meta:set_float("wait_time", 0)
local timer = minetest.get_node_timer(pos)
timer:start(1.0)
return
end
local wait_time = (meta:get_float("wait_time") or 0) + elapsed
meta:set_float("wait_time", wait_time)
if wait_time < time_until_caravan then
meta:set_string("infotext", S("Caravan summoned\nETA: @1 seconds.", math.floor(time_until_caravan - wait_time)))
local timer = minetest.get_node_timer(pos)
timer:start(1.0)
return
end
-- spawn the caravan. We've already established that the target pos is clear.
minetest.set_node(target, {name="commoditymarket:caravan_market_"..math.random(1,5), param2=facedir})
minetest.sound_play("commoditymarket_register_opened", {
pos = target,
gain = 1.0, -- default
max_hear_distance = 32, -- default, uses an euclidean metric
})
local timer = minetest.get_node_timer(target)
timer:start(dwell_time)
meta:set_string("infotext", "")
meta:set_float("wait_time", 0)
end,
})
end
-------------------------------------------------------------------------------
-- "Goblin Exchange"
if minetest.settings:get_bool("commoditymarket_enable_goblin_market") then
local goblin_def = {
description = S("Goblin Exchange"),
long_description = S("One does not usually associate Goblins with the sort of sophistication that running a market requires. Usually one just associates Goblins with savagery and violence. But they understand the principle of tit-for-tat exchange, and if approached correctly they actually respect the concepts of ownership and debt. However, for some peculiar reason they understand this concept in the context of coal lumps. Goblins deal in the standard coal lump as their form of currency, conceptually divided into 100 coal centilumps (though Goblin brokers prefer to \"keep the change\" when giving back actual coal lumps)."),
currency = {
["default:coal_lump"] = 100
},
currency_symbol = "¢", --"\u{00A2}" cent symbol
inventory_limit = 1000,
--sell_limit =, -- no sell limit
}
commoditymarket.register_market("goblin", goblin_def)
local goblin_protect = minetest.settings:get_bool("commoditymarket_protect_goblin_market", true)
local on_blast
if goblin_protect then
on_blast = function() end
end
minetest.register_node("commoditymarket:goblin_market", {
description = goblin_def.description,
_doc_items_longdesc = goblin_def.long_description,
_doc_items_usagehelp = usage_help,
tiles = {"default_chest_top.png^(default_coal_block.png^[opacity:128)","default_chest_top.png^(default_coal_block.png^[opacity:128)",
"default_chest_side.png^(default_coal_block.png^[opacity:128)","default_chest_side.png^(default_coal_block.png^[opacity:128)",
"commoditymarket_empty_shelf.png^(default_coal_block.png^[opacity:128)","default_chest_side.png^(default_coal_block.png^[opacity:128)^commoditymarket_goblin.png",},
paramtype2 = "facedir",
is_ground_content = false,
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
sounds = default.node_sound_wood_defaults(),
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
commoditymarket.show_market("goblin", clicker:get_player_name())
end,
can_dig = function(pos, player)
return not goblin_protect or minetest.check_player_privs(player, "protection_bypass")
end,
on_blast = on_blast,
})
end
--------------------------------------------------------------------------------
if minetest.settings:get_bool("commoditymarket_enable_under_market") then
local undermarket_def = {
description = S("Undermarket"),
long_description = S("Deep in the bowels of the world, below even the goblin-infested warrens and ancient delvings of the dwarves, dark and mysterious beings once dwelled. A few still linger to this day, and facilitate barter for those brave souls willing to travel in their lost realms. The Undermarket uses Mese chips ('₥') as a currency - twenty chips to the Mese fragment. Though traders are loathe to physically break Mese crystals up into units that small, as it renders it useless for other purposes."),
currency = {
["default:mese"] = 9*9*20,
["default:mese_crystal"] = 9*20,
["default:mese_crystal_fragment"] = 20
},
currency_symbol = "", --"\u{20A5}" mill sign
inventory_limit = 10000,
--sell_limit =, -- no sell limit
}
commoditymarket.register_market("under", undermarket_def)
local under_protect = minetest.settings:get_bool("commoditymarket_protect_under_market", true)
local on_blast
if under_protect then
on_blast = function() end
end
minetest.register_node("commoditymarket:under_market", {
description = undermarket_def.description,
_doc_items_longdesc = undermarket_def.long_description,
_doc_items_usagehelp = usage_help,
tiles = {"commoditymarket_under_top.png","commoditymarket_under_top.png",
"commoditymarket_under.png","commoditymarket_under.png","commoditymarket_under.png","commoditymarket_under.png"},
paramtype2 = "facedir",
is_ground_content = false,
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
sounds = default.node_sound_stone_defaults(),
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
commoditymarket.show_market("under", clicker:get_player_name())
end,
can_dig = function(pos, player)
return not under_protect or minetest.check_player_privs(player, "protection_bypass")
end,
on_blast = on_blast,
})
end
------------------------------------------------------------------

41
doc.lua
View File

@ -1,41 +0,0 @@
if not minetest.get_modpath("doc") then
return
end
-- internationalization boilerplate
local MP = minetest.get_modpath(minetest.get_current_modname())
local S, NS = dofile(MP.."/intllib.lua")
doc.add_category("commoditymarket",
{
name = S("Commodity Markets"),
description = S("Game-wide marketplaces where goods can be bought and sold at prices of your choice."),
build_formspec = doc.entry_builders.text_and_gallery,
})
doc.add_entry("commoditymarket", "ui_inventory", {
name = S("User Interface: Inventory"),
data = { text =
S("Each player's account has an inventory that serves as a holding area for items that are destined to be sold or that have been bought by the player but not yet retrieved. This inventory is a bit different from the standard Minetest inventory in that it doesn't hold item \"stacks\", it just tracks the total number of that item present. Some markets allow for extremely large quantities of an item to be stored here for sale."
.."\n\n"..
"To add an item to your market inventory for eventual sale either shift-click on the item in your player inventory or drag the item stack to the inventory slot below the main market inventory list. Some markets may have restrictions on what items can be bought and sold, if an item is not valid for that market it won't go into the market's inventory. Some items are considered \"currency\" and will add to your account's currency balance instead of being listed in your market inventory."
.."\n\n"..
"Tools cannot be added to the market inventory if they have any wear on them. The market also can't handle items with attached metadata such as books that have had text added to them."
.."\n\n"..
"To remove an item from your market inventory, double-click in it in the market inventory list. As much of the item as can fit into your player inventory will be transferred to you, with any remainder staying behind in the market inventory. To withdraw currency from your market balance type the amount you'd like to withdraw in the field next to the \"Withdraw\" button. The currency will be converted into items and added to your player inventory, with whatever cannot be converted remaining behind in your market balance.")
}})
doc.add_entry("commoditymarket", "ui_orders", {
name = S("User Interface: Orders"),
data = { text =
S(
"At the core of how a market operates are \"buy\" and \"sell\" orders. A buy order is an announcement to the world that you are interested in purchasing a certain quantity of item and are willing to pay a certain amount of currency in exchange for each unit of that item. Conversely, a sell order is an announcement to the world that you are interested in selling a certain quantity of item and will accept a certain amount of currency in exchange for each unit of that item."
.."\n\n"..
"The market price of an item is determined by where the existing buy and sell orders for that item intersect. When you offer to buy an item for a price that someone is offering to sell it at, the item is transferred to you and currency is transferred from your account to theirs to cover the cost. The market will keep track of the most recent price that an item was successfully sold for, but note that this information is for historical interest only - there's no guarantee that anyone is currently willing to match the historical price."
.."\n\n"..
"When an item is selected in the upper list, the currently existing buy and sell orders for that item will be displayed in the lower list. Sell orders are listed first in descending price, followed by buy orders in ascending price. The current market price will be somewhere in between the lowest sell order and the highest buy order. If you wish to cancel a buy or sell order that you've placed for an item, double-click on the order and the item or currency that you put into that order will be returned to your inventory."
.."\n\n"..
"If you place a buy order and there are already sell orders for the item that meet or are below your price, some or all of your buy order might be immediately fulfilled. Your purchases will be made at the price that the sell orders have been set to - if you were willing to pay 15 units of currency per item but someone was already offering to sell for 2 units of currency per item, you only pay 2 units for each of that offer's items. If there aren't enough compatible sell orders to fulfill your buy order, the remainder will be placed into the market and made available for future sellers to see and fulfill if they agree to your price. Your buy order will immediately deduct the currency required for it from your account's balance, but if you cancel your order you will get that currency back - it's not gone until the order is actually fulfilled."
.."\n\n"..
"If you place a sell order and there are already buy orders that meet or exceed your price, some or all of your sell order may be immediately fulfilled. You'll be paid the price that the buyers are offering rather than the amount you're demanding. If any of your sell offer is left unfulfilled, the sell order will be added to the market for future buyers to see. The items for this offer will be immediately taken from your market inventory but if you cancel your order you will get those items back.")
}})

View File

@ -1,790 +0,0 @@
-- internationalization boilerplate
local MP = minetest.get_modpath(minetest.get_current_modname())
local S, NS = dofile(MP.."/intllib.lua")
local truncate_item_names_to = 30
-- Large textures can screw with the formspecs.
-- See https://github.com/minetest/minetest/issues/9300 for a feature request that would simplify and improve icon generation, if supported.
-- In the meantime, here's some methods for overriding item icons to manually work around this:
local override_item_icon = {}
commoditymarket.override_item_icon = function(item_name, new_icon_texture)
override_item_icon[item_name] = new_icon_texture
end
local override_image_icon = {}
commoditymarket.override_image_icon = function(old_icon_texture, new_icon_texture)
override_image_icon[old_icon_texture] = new_icon_texture
end
-- And a setting for disabling icons entirely:
local global_enable_item_icons = minetest.settings:get_bool("commoditymarket_enable_item_icons", true)
--[inventorycube{<top>{<left>{<right>
--Escaping does not apply here and `^` is replaced by `&` in texture names instead.
--Example:
-- [inventorycube{grass.png{dirt.png&grass_side.png{dirt.png&grass_side.png
--Creates an inventorycube with `grass.png`, `dirt.png^grass_side.png` and `dirt.png^grass_side.png` textures
local process_inventory_cube = function(texture_string)
if not texture_string:sub(1,14) == "[inventorycube" then
return texture_string
end
local split = texture_string:split("{")
local left = split[3] -- the "front" of the cube we're seeing in the inventory list
if left == nil then -- in case something weird happens, don't crash.
return texture_string
end
left = left:gsub("&", "^")
return left
end
local get_icon = function(item)
local def = minetest.registered_items[item]
local returnstring = "unknown_item.png"
if def == nil then
return returnstring
end
local override = override_item_icon[item]
if override then
return override
end
local inventory_image = def.inventory_image
if inventory_image and inventory_image ~= "" then
returnstring = inventory_image
else
local tiles = def.tiles
if tiles then
local tilecount = #tiles
-- Textures of node; +Y, -Y, +X, -X, +Z, -Z
local selected_tile = tiles[math.min(5,tilecount)]
if type(selected_tile) == "string" then
returnstring = selected_tile
else
local tile_name = selected_tile.name
if tile_name then
returnstring = tile_name
end
end
end
end
returnstring = process_inventory_cube(returnstring)
-- Formspec tables can't handle image compositing and modifiers
local found_caret = returnstring:find("%^")
if found_caret then
returnstring = returnstring:sub(1, found_caret-1)
end
override = override_image_icon[returnstring]
if override then
return override
end
return minetest.formspec_escape(returnstring)
end
-- Exposed so that the purge_unknowns command can use it.
commoditymarket.get_icon = get_icon
local truncate_string = function(target, length)
if target:len() > length then
return target:sub(1,length-2).."..."
end
return target
end
local get_item_description = function(item)
local def = minetest.registered_items[item]
if def then
local description = def.description
if description then
return minetest.formspec_escape(description:gsub("\n", " "))
end
end
return S("Unknown Item")
end
-- Inventory formspec
-------------------------------------------------------------------------------------
local inventory_item_comp = function(invitem1, invitem2) return invitem1.item < invitem2.item end
local inventory_desc_comp = function(invitem1, invitem2) return invitem1.description < invitem2.description end
local get_account_formspec = function(market, account)
local show_itemnames = account.show_itemnames == "true"
local show_icons = global_enable_item_icons and ((account.show_icons or "true") == "true")
local market_def = market.def
local inventory = {}
local inventory_count = 0
for item, quantity in pairs(account.inventory) do
local icon
if show_icons then
icon = get_icon(item)
end
table.insert(inventory, {item=item, quantity=quantity, icon=icon, description=get_item_description(item)})
inventory_count = inventory_count + quantity
end
if show_itemnames then
table.sort(inventory, inventory_item_comp)
else
table.sort(inventory, inventory_desc_comp)
end
local formspec = {
"size[10,10]"
.."tabheader[0,0;tabs;"..market_def.description..","..S("Your Inventory")..","..S("Market Orders")..";2;false;true]"
}
formspec[#formspec+1] = "tablecolumns["
if show_icons then
formspec[#formspec+1] = "image"
for i=1, #inventory, 2 do
formspec[#formspec+1] = ","..i.."="..inventory[i].icon
end
formspec[#formspec+1] = ";"
end
if show_itemnames then
formspec[#formspec+1] = "text;"
end
formspec[#formspec+1] = "text;text,align=center"
if show_icons then
formspec[#formspec+1] = ";image"
for i=2, #inventory, 2 do
formspec[#formspec+1] = ","..i.."="..inventory[i].icon
end
end
if show_itemnames then
formspec[#formspec+1] = ";text"
end
formspec[#formspec+1] = ";text;text,align=center]"
.."tooltip[inventory;"..S("All the items you've transfered to the market to sell and the items you've\npurchased with buy orders. Double-click on an item to bring it back into your\npersonal inventory.").."]"
.."table[0,0;9.75,4;inventory;"
if show_icons then
formspec[#formspec+1] = "0,"
end
if show_itemnames then
formspec[#formspec+1] = S("Item")..","
end
formspec[#formspec+1] = S("Description")..","..S("Quantity")
if show_icons then
formspec[#formspec+1] = ",0"
end
if show_itemnames then
formspec[#formspec+1] = ","..S("Item")
end
formspec[#formspec+1] = ","..S("Description")..","..S("Quantity")
for i, entry in ipairs(inventory) do
if show_icons then
formspec[#formspec+1] = "," .. i
end
if show_itemnames then
formspec[#formspec+1] = "," .. truncate_string(entry.item, truncate_item_names_to)
end
-- no need to formspec_escape description here, it gets done when it's initially added to the inventory table
formspec[#formspec+1] = "," .. entry.description .. "," .. entry.quantity
end
formspec[#formspec+1] = "]container[1,4.5]list[detached:commoditymarket:" .. market.name .. ";add;0,0;1,1;]"
.."label[1,0;"..S("Drop items here to\nadd to your account").."]"
.."listring[current_player;main]listring[detached:commoditymarket:" .. market.name .. ";add]"
if market_def.inventory_limit then
formspec[#formspec+1] = "label[3,0;"..S("Inventory limit:").."\n" .. inventory_count.."/" .. market_def.inventory_limit .. "]"
.. "tooltip[3,0;1.5,1;"..S("You can still receive purchased items if you've exceeded your inventory limit,\nbut you won't be able to transfer items from your personal inventory into\nthe market until you've emptied it back down below the limit again.").."]"
end
formspec[#formspec+1] = "label[4.9,0;Balance:\n" .. market_def.currency_symbol .. account.balance .. "]"
.."tooltip[4.9,0;3.5,1;"..S("Enter the amount of currency you'd like to withdraw then click the 'Withdraw'\nbutton to convert it into items and transfer it to your personal inventory.").."]"
.."field[6.1,0.325;1,1;withdrawamount;;]"
.."field_close_on_enter[withdrawamount;false]"
.."button[6.7,0;1.2,1;withdraw;"..S("Withdraw").."]"
.."container_end[]"
.."container[1,5.75]list[current_player;main;0,0;8,1;]"
.."list[current_player;main;0,1.25;8,3;8]container_end[]"
return table.concat(formspec)
end
-- Market formspec
--------------------------------------------------------------------------------------------------------
local compare_market_item = function(mkt1, mkt2)
return mkt1.item < mkt2.item
end
local compare_market_desc = function(mkt1, mkt2)
return get_item_description(mkt1.item) < get_item_description(mkt2.item)
end
local compare_buy_volume = function(mkt1, mkt2)
return mkt1.buy_volume > mkt2.buy_volume
end
local compare_buy_max = function(mkt1, mkt2)
return ((mkt1.buy_orders[#mkt1.buy_orders] or {}).price or -2^30) > ((mkt2.buy_orders[#mkt2.buy_orders] or {}).price or -2^30)
end
local compare_sell_volume = function(mkt1, mkt2)
return mkt1.sell_volume > mkt2.sell_volume
end
local compare_sell_min = function(mkt1, mkt2)
return ((mkt1.sell_orders[#mkt1.sell_orders] or {}).price or 2^31) < ((mkt2.sell_orders[#mkt2.sell_orders] or {}).price or 2^31)
end
local compare_last_price = function(mkt1, mkt2)
return (mkt1.last_price or 2^31) < (mkt2.last_price or 2^31)
end
local sort_marketlist = function(item_list, account)
-- I think tonumber is now redundant here, leaving it in in case upgrading a world that has text recorded in this field for an existing player account
local sort_by = tonumber(account.sort_markets_by_column)
if sort_by == nil then return end
local show_itemnames = account.show_itemnames == "true"
local show_icons = global_enable_item_icons and ((account.show_icons or "true") == "true")
local icon_displace = 0
if show_icons then
icon_displace = 1
end
local itemname_displace = 0
if show_itemnames then
itemname_displace = 1
end
-- "Icon,Item,Description,#00FF00,Buy Vol,Buy Max,#FF0000,Sell Vol,Sell Min,Last Price"
if sort_by == 1 + icon_displace and show_itemnames then
table.sort(item_list, compare_market_item)
elseif sort_by == 1 + icon_displace + itemname_displace then
table.sort(item_list, compare_market_desc)
elseif sort_by == 3 + icon_displace + itemname_displace then
table.sort(item_list, compare_buy_volume)
elseif sort_by == 4 + icon_displace + itemname_displace then
table.sort(item_list, compare_buy_max)
elseif sort_by == 6 + icon_displace + itemname_displace then
table.sort(item_list, compare_sell_volume)
elseif sort_by == 7 + icon_displace + itemname_displace then
table.sort(item_list, compare_sell_min)
elseif sort_by == 8 + icon_displace + itemname_displace then
table.sort(item_list, compare_last_price)
elseif sort_by == 9 + icon_displace + itemname_displace then
table.sort(item_list, function(mkt1, mkt2)
-- Define locally so that account is available
return (account.inventory[mkt1.item] or 0) > (account.inventory[mkt2.item] or 0)
end)
end
end
local make_marketlist = function(market, account)
local market_list = {}
local search_filter = account.search or ""
for item, row in pairs(market.orders_for_items) do
if (search_filter == "" or string.find(item, search_filter)) then
if account.filter_participating == "true" then
local found = false
for _, order in ipairs(row.buy_orders) do
if account == order.account then
found = true
break
end
end
if not found then
for _, order in ipairs(row.sell_orders) do
if account == order.account then
found = true
break
end
end
end
if found then
table.insert(market_list, row)
end
else
table.insert(market_list, row)
end
end
end
sort_marketlist(market_list, account)
return market_list
end
local get_account_name = function(target_account, this_account, anonymous)
if anonymous and target_account ~= this_account then
return ""
end
return target_account.name
end
local get_market_formspec = function(market, account)
local market_def = market.def
local selected = account.selected
local market_list = make_marketlist(market, account)
local show_itemnames = account.show_itemnames == "true"
local show_icons = global_enable_item_icons and ((account.show_icons or "true") == "true")
local anonymous = market_def.anonymous
local formspec = {
"size[10,10]"
.."tabheader[0,0;tabs;"..market_def.description..","..S("Your Inventory")..","..S("Market Orders")..";3;false;true]"
}
-- column definitions
formspec[#formspec+1] = "tablecolumns["
if show_icons then
formspec[#formspec+1] = "image" -- icon
for i, row in ipairs(market_list) do
formspec[#formspec+1] = "," .. i .. "=" .. get_icon(row.item)
end
formspec[#formspec+1] = ";"
end if show_itemnames then
formspec[#formspec+1] = "text;" -- itemname
end
formspec[#formspec+1] = "text;" -- description
.."color,span=2;"
.."text,align=right,tooltip="..S("Number of items there's demand for in the market.")..";"
.."text,align=right,tooltip="..S("Maximum price being offered to buy one of these.")..";"
.."color,span=2;"
.."text,align=right,tooltip="..S("Number of items available for sale in the market.")..";"
.."text,align=right,tooltip="..S("Minimum price being demanded to sell one of these.")..";"
.."text,align=right,tooltip="..S("Price paid for one of these the last time one was sold.")..";"
.."text,align=right,tooltip="..S("Quantity of this item that you have in your inventory ready to sell.").."]"
.."table[0,0;9.75,5;summary;"
if show_icons then
formspec[#formspec+1] = "0,"-- icon
end
-- header row
if show_itemnames then
formspec[#formspec+1] = "Item," -- itemname
end
formspec[#formspec+1] = S("Description")..",#00FF00,"..S("Buy Vol")..","..S("Buy Max")
..",#FF0000,"..S("Sell Vol")..","..S("Sell Min")..","..S("Last Price")..","..S("Inventory")
local selected_idx
local selected_row
-- Show list of item market summaries
for i, row in ipairs(market_list) do
if show_icons then
formspec[#formspec+1] = ","..i -- icon
end
if show_itemnames then
formspec[#formspec+1] = "," .. truncate_string(row.item, truncate_item_names_to)
end
formspec[#formspec+1] = "," .. get_item_description(row.item)
.. ",#00FF00,"
.. row.buy_volume
.. "," .. ((row.buy_orders[#row.buy_orders] or {}).price or "-")
.. ",#FF0000,"
.. row.sell_volume
.. "," .. ((row.sell_orders[#row.sell_orders] or {}).price or "-")
.. "," .. (row.last_price or "-")
.. "," .. (account.inventory[row.item] or "-")
-- we happen to be processing the row that matches the item this player has selected. Record that.
if selected == row.item then
selected_row = row
selected_idx = i + 1
end
end
-- a row that's visible is marked as the selected item, so make it selected in the formspec
if selected_row then
formspec[#formspec+1] = ";"..selected_idx
end
formspec[#formspec+1] = "]"
-- search field
formspec[#formspec+1] = "container[2.5,5]field_close_on_enter[search_filter;false]"
.."field[0,0.85;2.5,1;search_filter;;"..minetest.formspec_escape(account.search or "").."]"
.."image_button[2.05,0.65;0.8,0.8;commoditymarket_search.png;apply_search;]"
.."image_button[2.7,0.65;0.8,0.8;commoditymarket_clear.png;clear_search;]"
.."checkbox[1.77,0;filter_participating;"..S("My orders")..";".. account.filter_participating .."]"
.."tooltip[filter_participating;"..S("Select this to show only the markets where you have either a buy or a sell order pending.").."]"
.."tooltip[search_filter;"..S("Enter substring to search item identifiers for.").."]"
.."tooltip[apply_search;"..S("Apply search to outputs.").."]"
.."tooltip[clear_search;"..S("Clear search.").."]"
.."container_end[]"
-- if a visible item market is selected, show the orders for it in detail
if selected_row then
local current_time = minetest.get_gametime()
local desc_display
if show_itemnames then
desc_display = selected
else
local def = minetest.registered_items[selected_row.item] or {description=S("Unknown Item")}
desc_display = minetest.formspec_escape(def.description:gsub("\n", " "))
end
-- player inventory for this item and for currency
formspec[#formspec+1] = "label[0.1,5.1;"..desc_display.."\n"..S("In inventory:").." "
.. tostring(account.inventory[selected] or 0) .."\n"..S("Balance:").." "..market_def.currency_symbol..account.balance .."]"
-- buy/sell controls
.. "container[6.1,5]"
local sell_limit = market_def.sell_limit
if sell_limit then
local total_sell = 0
for item, orders in pairs(market.orders_for_items) do
for _, order in ipairs(orders.sell_orders) do
if order.account == account then
total_sell = total_sell + order.quantity
end
end
end
formspec[#formspec+1] = "label[0,0;"..S("Sell limit:").." ".. total_sell .. "/" .. sell_limit .."]"
.."tooltip[0,0;2,0.25;"..S("This market limits the total number of items a given seller can have for sale at a time.\nYou have @1 items remaining. Cancel old sell orders to free up space.", sell_limit-total_sell).."]"
end
-- Buy, sell, quantity and price button
formspec[#formspec+1] = "tooltip[0,0.25;3.75,1;"..S("Use these fields to enter buy and sell orders for the selected item.").."]"
.."button[0,0.55;1,1;buy;"..S("Buy").."]field[1.2,0.85;1,1;quantity;"..S("Quantity")..";]"
.."field[2.1,0.85;1,1;price;"..S("Price per")..";]button[2.7,0.55;1,1;sell;Sell]"
.."field_close_on_enter[quantity;false]field_close_on_enter[price;false]"
.."container_end[]"
-- table of buy and sell orders
.."tablecolumns[color;text;"
.."text,align=right,tooltip="..S("The price per item in this order.")..";"
.."text,align=right,tooltip="..S("The total amount of items in this particular order.")..";"
.."text,align=right,tooltip="..S("The total amount of items available at this price accounting for the other orders also currently being offered.")..";"
.."text,tooltip="..S("The name of the player who placed this order.\nDouble-click your own orders to cancel them.")..";"
.."text,align=right,tooltip="..S("How many days ago this order was placed.").."]"
.."table[0,6.5;9.75,3.5;orders;#FFFFFF,"..S("Order")..","..S("Price")..","..S("Quantity")..","..S("Total Volume")..","..S("Player")..","..S("Days Old")
local sell_volume = selected_row.sell_volume
for i, sell in ipairs(selected_row.sell_orders) do
formspec[#formspec+1] = ",#FF0000,"..S("Sell")..","
..sell.price..","
..sell.quantity..","
..sell_volume..","
..get_account_name(sell.account, account, anonymous)..","
..math.floor((current_time-sell.timestamp)/86400)
sell_volume = sell_volume - sell.quantity
end
local buy_volume = 0
local buy_orders = selected_row.buy_orders
local buy_count = #buy_orders
-- Show buy orders in reverse order
for i = buy_count, 1, -1 do
local buy = buy_orders[i]
buy_volume = buy_volume + buy.quantity
formspec[#formspec+1] = ",#00FF00,"..S("Buy")..","
..buy.price..","
..buy.quantity..","
..buy_volume..","
..get_account_name(buy.account, account, anonymous)..","
..math.floor((current_time-buy.timestamp)/86400)
end
formspec[#formspec+1] = "]"
else
formspec[#formspec+1] = "label[0.1,5.1;"..S("Select an item to view or place orders.").."]"
end
return table.concat(formspec)
end
-------------------------------------------------------------------------------------
-- Information formspec
--{item=item, quantity=quantity, price=price, purchaser=purchaser, seller=seller, timestamp = minetest.get_gametime()}
local log_to_string = function(market, log_entry, account)
local anonymous = market.def.anonymous
local purchaser = log_entry.purchaser
local seller = log_entry.seller
local purchaser_name
if purchaser == seller then
purchaser_name = S("yourself")
elseif anonymous and purchaser ~= account then
purchaser_name = S("someone")
elseif purchaser == account then
purchaser_name = S("you")
else
purchaser_name = purchaser.name
end
local seller_name
if anonymous and seller ~= account then
seller_name = S("someone")
elseif seller == account then
seller_name = S("you")
else
seller_name = seller.name
end
local colour
local new
local last_acknowledged = account.last_acknowledged or 0
if log_entry.timestamp > last_acknowledged then
colour = "#FFFF00"
new = true
else
colour = "#FFFFFF"
new = false
end
local show_itemnames = account.show_itemnames == "true"
local itemname = log_entry.item
if not show_itemnames then
local item_def = minetest.registered_items[log_entry.item]
if item_def then
itemname = minetest.formspec_escape(item_def.description:gsub("\n", " "))
end
end
return colour .. S("On day @1 @2 sold @3 @4 to @5 at @6@7 each for a total of @6@8.",
math.ceil(log_entry.timestamp/86400), seller_name, log_entry.quantity, itemname,
purchaser_name, market.def.currency_symbol, log_entry.price, log_entry.quantity*log_entry.price), new
end
local get_info_formspec = function(market, account)
local formspec = {
"size[10,10]"
.."tabheader[0,0;tabs;"..market.def.description..","..S("Your Inventory")..","..S("Market Orders")..";1;false;true]"
.."textarea[0.75,0.5;9.25,1.5;;"..S("Description:")..";"..market.def.long_description.."]"
.."label[0.5,2.2;"..S("Your Recent Purchases and Sales:").."]"
.."textlist[0.5,2.6;8.75,4;log_entries;"
}
if next(account.log) then
local new = false
for _, log_entry in ipairs(account.log) do
local log_string, new_log = log_to_string(market, log_entry, account)
new = new or new_log
formspec[#formspec+1] = log_string
formspec[#formspec+1] = ","
end
formspec[#formspec] = "]" -- Note: there's no +1 here deliberately, that way the "]" overwrites the last comma added by the loop above.
if new then
formspec[#formspec+1] = "button[7.1,6.9;2,0.5;acknowledge_log;"..S("Mark logs as read").."]" ..
"tooltip[acknowledge_log;"..S("Log entries in yellow are new since last time you marked your log as read.").."]"
end
else
formspec[#formspec+1] = "#CCCCCC"..S("No logged activites in this market yet.").."]"
end
local show_itemnames = account.show_itemnames or "false"
formspec[#formspec+1] = "]container[0.5, 7.5]label[0,0;Settings:]checkbox[0,0.25;show_itemnames;"..S("Show Itemnames")..";"
..show_itemnames.."]"
if global_enable_item_icons then
local show_icons = account.show_icons or "true"
formspec[#formspec+1] = "checkbox[2,0.25;show_icons;"..S("Show Icons")..";"..show_icons.."]"
end
formspec[#formspec+1] = "container_end[]"
return table.concat(formspec)
end
---------------------------------------------------------------------------------------
commoditymarket.get_formspec = function(market, account)
local tab = account.tab
if tab == 1 then
return get_info_formspec(market, account)
elseif tab == 2 then
return get_account_formspec(market, account)
else
return get_market_formspec(market, account)
end
end
------------------------------------------------------------------------------------
-- Handling recieve_fields
local add_to_player_inventory = function(name, item, amount)
local playerinv = minetest.get_inventory({type="player", name=name})
local not_full = true
while amount > 0 and not_full do
local stack = ItemStack(item .. " " .. amount)
amount = amount - stack:get_count()
local leftover = playerinv:add_item("main", stack)
if leftover:get_count() > 0 then
amount = amount + leftover:get_count()
return amount
end
end
return amount
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
local formname_split = formname:split(":")
if formname_split[1] ~= "commoditymarket" then
return false
end
local market = commoditymarket.registered_markets[formname_split[2]]
if not market then
return false
end
local name = formname_split[3]
if name ~= player:get_player_name() then
return false
end
local account = market:get_account(name)
local show_icons = global_enable_item_icons and ((account.show_icons or "true") == "true")
local something_changed = false
if fields.tabs then
account.tab = tonumber(fields.tabs)
something_changed = true
end
-- player clicked on an item in the market summary table
if fields.summary then
local summaryevent = minetest.explode_table_event(fields.summary)
if summaryevent.type == "DCL" or summaryevent.type == "CHG" then
if summaryevent.row == 1 then
-- header clicked, sort by column
local column = tonumber(summaryevent.column)
if not (column == 1 and show_icons) then -- ignore clicks on the icon column header
account.sort_markets_by_column = column
end
else
-- item clicked, recreate the list to find out which one
local marketlist = make_marketlist(market, account)
local selected = marketlist[summaryevent.row-1]
if selected then
account.selected = selected.item
end
end
elseif summaryevent.type == "INV" then
account.selected = nil
end
something_changed = true
end
if fields.orders then
local ordersevent = minetest.explode_table_event(fields.orders)
if ordersevent.type == "DCL" and ordersevent.column > 0 then
local selected_idx = ordersevent.row - 1 -- account for header
local selected_row = market.orders_for_items[account.selected] -- sell orders come first
local sell_orders = selected_row.sell_orders
local sell_order_count = #sell_orders
local selected_order
if selected_idx <= sell_order_count then -- if the index is within the range of sell orders,
selected_order = sell_orders[selected_idx]
if selected_order and selected_order.account == account then -- and the order belongs to the current player,
market:cancel_sell(account.selected, selected_order) -- cancel it
something_changed = true
end
else
-- otherwise we're in the buy group, shift the index up by sell_order_count and reverse index order
local buy_orders = selected_row.buy_orders
local buy_orders_count = #buy_orders
selected_order = buy_orders[buy_orders_count - (selected_idx - sell_order_count - 1)]
if selected_order and selected_order.account == account then
market:cancel_buy(account.selected, selected_order)
something_changed = true
end
end
end
end
if fields.buy then
local quantity = tonumber(fields.quantity)
local price = tonumber(fields.price)
if price ~= nil and quantity ~= nil then
market:buy(name, account.selected, quantity, price)
something_changed = true
end
end
if fields.sell then
local quantity = tonumber(fields.quantity)
local price = tonumber(fields.price)
if price ~= nil and quantity ~= nil then
market:sell(name, account.selected, quantity, price)
something_changed = true
end
end
-- player clicked in their inventory table, may need to give him his stuff back
if fields.inventory then
local invevent = minetest.explode_table_event(fields.inventory)
if invevent.type == "DCL" and invevent.column > 0 then
local col_count = 8
local show_itemnames = account.show_itemnames == "true"
if not show_itemnames then
col_count = col_count - 2
end
if not show_icons then
col_count = col_count - 2
end
local index = math.floor(((invevent.row-1)*col_count + invevent.column - 1)/(col_count/2)) - 1
local account = market:get_account(name)
-- build a local copy of the inventory that would be displayed in the formspec so we can
-- figure out what item the index we were given is pointing to
local inventory = {}
for item, quantity in pairs(account.inventory) do
table.insert(inventory, {item=item, quantity=quantity, description=get_item_description(item)})
end
if show_itemnames then
table.sort(inventory, inventory_item_comp)
else
table.sort(inventory, inventory_desc_comp)
end
if inventory[index] then
local item = inventory[index].item
local amount = account.inventory[item]
local remaining = add_to_player_inventory(name, item, amount)
if remaining == 0 then
account.inventory[item] = nil
else
account.inventory[item] = remaining
end
if remaining ~= amount then
something_changed = true
end
end
end
end
if fields.withdraw or fields.key_enter_field == "withdrawamount" then
local withdrawvalue = tonumber(fields.withdrawamount)
if withdrawvalue then
local account = market:get_account(name)
withdrawvalue = math.min(withdrawvalue, account.balance)
for _, currency in ipairs(market.def.currency_ordered) do
this_unit_amount = math.floor(withdrawvalue/currency.amount)
if this_unit_amount > 0 then
local remaining = add_to_player_inventory(name, currency.item, this_unit_amount)
local value_given = (this_unit_amount - remaining) * currency.amount
account.balance = account.balance - value_given
withdrawvalue = withdrawvalue - value_given
something_changed = true
end
end
end
end
if fields.search_filter then
local value = string.lower(fields.search_filter)
if account.search ~= value then
account.search = value
end
end
local process_checkbox = function(property_name, fields, account)
if (fields[property_name] == "true" and account[property_name] ~= "true") or
(fields[property_name] == "false" and account[property_name] ~= "false") then
account[property_name] = fields[property_name]
return true
end
return false
end
if process_checkbox("filter_participating", fields, account) then something_changed = true end
if process_checkbox("show_itemnames", fields, account) then something_changed = true end
if process_checkbox("show_icons", fields, account) then something_changed = true end
if fields.acknowledge_log then
account.last_acknowledged = minetest.get_gametime()
something_changed = true
end
if fields.apply_search or fields.key_enter_field == "search_filter" then
something_changed = true
end
if fields.clear_search then
account.search = ""
something_changed = true
end
if something_changed then
minetest.show_formspec(name, formname, market:get_formspec(account))
end
end)

545
init.lua
View File

@ -1,10 +1,541 @@
commoditymarket = {} local modpath = minetest.get_modpath(minetest.get_current_modname())
local MP = minetest.get_modpath(minetest.get_current_modname()) minetest.register_alias("commoditymarket:kings_market", "commoditymarket_fantasy:kings_market")
dofile(MP.."/formspecs.lua") minetest.register_alias("commoditymarket:gold_coins", "commoditymarket_fantasy:gold_coins")
dofile(MP.."/market.lua") minetest.register_alias("commoditymarket:night_market", "commoditymarket_fantasy:night_market")
dofile(MP.."/doc.lua") minetest.register_alias("commoditymarket:goblin_market", "commoditymarket_fantasy:goblin_market")
minetest.register_alias("commoditymarket:under_market", "commoditymarket_fantasy:under_market")
minetest.register_alias("commoditymarket:caravan_post", "commoditymarket_fantasy:caravan_post")
minetest.register_alias("commoditymarket:caravan_market_1", "commoditymarket_fantasy:caravan_market_1")
minetest.register_alias("commoditymarket:caravan_market_2", "commoditymarket_fantasy:caravan_market_2")
minetest.register_alias("commoditymarket:caravan_market_3", "commoditymarket_fantasy:caravan_market_3")
minetest.register_alias("commoditymarket:caravan_market_4", "commoditymarket_fantasy:caravan_market_4")
minetest.register_alias("commoditymarket:caravan_market_5", "commoditymarket_fantasy:caravan_market_5")
minetest.register_alias("commoditymarket:caravan_market_permanent", "commoditymarket_fantasy:caravan_market_permanent")
dofile(MP.."/default_markets.lua") -- internationalization boilerplate
dofile(MP.."/mapgen_dungeon_markets.lua") local S, NS = dofile(modpath.."/intllib.lua")
dofile(modpath.."/mapgen_dungeon_markets.lua")
-- Only register gold coins once, if required
local gold_coins_registered = false
local register_gold_coins = function()
if not gold_coins_registered then
minetest.register_craftitem("commoditymarket_fantasy:gold_coins", {
description = S("Gold Coins"),
_doc_items_longdesc = S("A gold ingot is far too valuable to use as a basic unit of value, so it has become common practice to divide the standard gold bar into one thousand small disks to make trade easier."),
_doc_items_usagehelp = S("Gold coins can be deposited and withdrawn from markets that accept them as currency. These markets can make change if you have a thousand coins and would like them back in ingot form again."),
inventory_image = "commoditymarket_gold_coins.png",
stack_max = 1000,
})
gold_coins_registered = true
end
end
local default_items = {"default:axe_bronze","default:axe_diamond","default:axe_mese","default:axe_steel","default:axe_steel","default:axe_stone","default:axe_wood","default:pick_bronze","default:pick_diamond","default:pick_mese","default:pick_steel","default:pick_stone","default:pick_wood","default:shovel_bronze","default:shovel_diamond","default:shovel_mese","default:shovel_steel","default:shovel_stone","default:shovel_wood","default:sword_bronze","default:sword_diamond","default:sword_mese","default:sword_steel","default:sword_stone","default:sword_wood", "default:blueberries", "default:book", "default:bronze_ingot", "default:clay_brick", "default:clay_lump", "default:coal_lump", "default:copper_ingot", "default:copper_lump", "default:diamond", "default:flint", "default:gold_ingot", "default:gold_lump", "default:iron_lump", "default:mese_crystal", "default:mese_crystal_fragment", "default:obsidian_shard", "default:paper", "default:steel_ingot", "default:stick", "default:tin_ingot", "default:tin_lump", "default:acacia_tree", "default:acacia_wood", "default:apple", "default:aspen_tree", "default:aspen_wood", "default:blueberry_bush_sapling", "default:bookshelf", "default:brick", "default:bronzeblock", "default:bush_sapling", "default:cactus", "default:clay", "default:coalblock", "default:cobble", "default:copperblock", "default:desert_cobble", "default:desert_sand", "default:desert_sandstone", "default:desert_sandstone_block", "default:desert_sandstone_brick", "default:desert_stone", "default:desert_stone_block", "default:desert_stonebrick", "default:diamondblock", "default:dirt", "default:glass", "default:goldblock", "default:gravel", "default:ice", "default:junglegrass", "default:junglesapling", "default:jungletree", "default:junglewood", "default:ladder_steel", "default:ladder_wood", "default:large_cactus_seedling", "default:mese", "default:mese_post_light", "default:meselamp", "default:mossycobble", "default:obsidian", "default:obsidian_block", "default:obsidian_glass", "default:obsidianbrick", "default:papyrus", "default:pine_sapling", "default:pine_tree", "default:pine_wood", "default:sand", "default:sandstone", "default:sandstone_block", "default:sandstonebrick", "default:sapling", "default:silver_sand", "default:silver_sandstone", "default:silver_sandstone_block", "default:silver_sandstone_brick", "default:snow", "default:snowblock", "default:steelblock", "default:stone", "default:stone_block", "default:stonebrick", "default:tinblock", "default:tree", "default:wood",}
local usage_help = S("Right-click on this to open the market interface.")
------------------------------------------------------------------------------
-- King's Market
if minetest.settings:get_bool("commoditymarket_enable_kings_market") then
local kings_def = {
description = S("King's Market"),
long_description = S("The largest and most accessible market for the common man, the King's Market uses gold coins as its medium of exchange (or the equivalent in gold ingots - 1000 coins to the ingot). However, as a respectable institution of the surface world, the King's Market operates only during the hours of daylight. The purchase and sale of swords and explosives is prohibited in the King's Market. Gold coins are represented by a '☼' symbol."),
currency = {
["default:gold_ingot"] = 1000,
["commoditymarket_fantasy:gold_coins"] = 1
},
currency_symbol = "", -- "\u{263C}" Alchemical symbol for gold
allow_item = function(item)
if item:sub(1,13) == "default:sword" or item:sub(1,4) == "tnt:" then
return false
end
return true
end,
inventory_limit = 100000,
--sell_limit =, -- no sell limit for the King's Market
initial_items = default_items,
}
register_gold_coins()
commoditymarket.register_market("kings", kings_def)
local kings_protect = minetest.settings:get_bool("commoditymarket_protect_kings_market", true)
local on_blast
if kings_protect then
on_blast = function() end
end
minetest.register_node("commoditymarket_fantasy:kings_market", {
description = kings_def.description,
_doc_items_longdesc = kings_def.long_description,
_doc_items_usagehelp = usage_help,
tiles = {"default_chest_top.png","default_chest_top.png",
"default_chest_side.png","default_chest_side.png",
"commoditymarket_empty_shelf.png","default_chest_side.png^commoditymarket_crown.png",},
paramtype2 = "facedir",
is_ground_content = false,
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
sounds = default.node_sound_wood_defaults(),
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
local timeofday = minetest.get_timeofday()
if timeofday > 0.2 and timeofday < 0.8 then
commoditymarket.show_market("kings", clicker:get_player_name())
else
minetest.chat_send_player(clicker:get_player_name(), S("At this time of day the King's Market is closed."))
minetest.sound_play({name = "commoditymarket_error", gain = 0.1}, {to_player=clicker:get_player_name()})
end
end,
can_dig = function(pos, player)
return not kings_protect or minetest.check_player_privs(player, "protection_bypass")
end,
on_blast = on_blast,
})
end
-------------------------------------------------------------------------------
-- Night Market
if minetest.settings:get_bool("commoditymarket_enable_night_market") then
local night_def = {
description = S("Night Market"),
long_description = "When the sun sets and the stalls of the King's Market close, other vendors are just waking up to share their wares. The Night Market is not as voluminous as the King's Market but accepts a wider range of wares. It accepts the same gold coinage of the realm, one thousand coins to the gold ingot.",
currency = {
["default:gold_ingot"] = 1000,
["commoditymarket_fantasy:gold_coins"] = 1
},
currency_symbol = "", --"\u{263C}"
inventory_limit = 10000,
--sell_limit =, -- no sell limit for the Night Market
initial_items = default_items,
anonymous = true,
}
register_gold_coins()
commoditymarket.register_market("night", night_def)
local night_protect = minetest.settings:get_bool("commoditymarket_protect_night_market", true)
local on_blast
if night_protect then
on_blast = function() end
end
minetest.register_node("commoditymarket_fantasy:night_market", {
description = night_def.description,
_doc_items_longdesc = night_def.long_description,
_doc_items_usagehelp = usage_help,
tiles = {"default_chest_top.png","default_chest_top.png",
"default_chest_side.png","default_chest_side.png",
"commoditymarket_empty_shelf.png","default_chest_side.png^commoditymarket_moon.png",},
paramtype2 = "facedir",
is_ground_content = false,
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
sounds = default.node_sound_wood_defaults(),
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
local timeofday = minetest.get_timeofday()
if timeofday < 0.2 or timeofday > 0.8 then
commoditymarket.show_market("night", clicker:get_player_name())
else
minetest.chat_send_player(clicker:get_player_name(), S("At this time of day the Night Market is closed."))
minetest.sound_play({name = "commoditymarket_error", gain = 0.1}, {to_player=clicker:get_player_name()})
end
end,
can_dig = function(pos, player)
return not night_protect or minetest.check_player_privs(player, "protection_bypass")
end,
on_blast = on_blast,
})
end
-------------------------------------------------------------------------------
if minetest.settings:get_bool("commoditymarket_enable_caravan_market", true) then
-- "Trader's Caravan" - small-capacity market that players can summon
local time_until_caravan = 120 -- caravan arrives in two minutes
local dwell_time = 600 -- caravan leaves ten minutes after last usage
local caravan_def = {
description = S("Trader's Caravan"),
long_description = S("Unlike most markets that have well-known fixed locations that travelers congregate to, the network of Trader's Caravans is fluid and dynamic in their locations. A Trader's Caravan can show up anywhere, make modest trades, and then be gone the next time you visit them. These caravans accept gold and gold coins as a currency (one gold ingot to one thousand gold coins exchange rate). Any reasonably-wealthy person can create a signpost marking a location where Trader's Caravans will make a stop."),
currency = {
["default:gold_ingot"] = 1000,
["commoditymarket_fantasy:gold_coins"] = 1
},
currency_symbol = "", --"\u{263C}"
inventory_limit = 1000,
sell_limit = 1000,
initial_items = default_items,
}
register_gold_coins()
minetest.register_craft({
output = "commoditymarket_fantasy:caravan_post",
recipe = {
{'group:wood', 'group:wood', ''},
{'group:wood', "default:gold_ingot", ''},
{'group:wood', "default:chest_locked", ''},
}
})
commoditymarket.register_market("caravan", caravan_def)
local create_caravan_def = function(override_table)
local def = {
description = caravan_def.description,
_doc_items_longdesc = caravan_def.long_description,
_doc_items_usagehelp = usage_help,
drawtype = "mesh",
mesh = "commoditymarket_wagon.obj",
tiles = {
{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
{ name = "default_wood.png", backface_culling = true }, -- base wood
{ name = "default_fence_rail_wood.png", backface_culling = true }, -- wheel sides
{ name = "default_coal_block.png", backface_culling = true }, -- wheel tyre
{ name = "commoditymarket_shingles_wood.png", backface_culling = true }, -- roof
{ name = "default_junglewood.png", backface_culling = true }, -- corner wood
},
collision_box = {
type = "fixed",
fixed = {
{-0.75, -0.5, -1.25, 0.75, 1.5, 1.25},
},
},
selection_box = {
type = "fixed",
fixed = {
{-0.75, -0.5, -1.25, 0.75, 1.5, 1.25},
},
},
paramtype2 = "facedir",
drop = "",
groups = {choppy = 2, oddly_breakable_by_hand = 1, not_in_creative_inventory = 1},
sounds = default.node_sound_wood_defaults(),
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
commoditymarket.show_market("caravan", clicker:get_player_name())
local timer = minetest.get_node_timer(pos)
timer:start(dwell_time)
end,
after_destruct = function(pos, oldnode)
local facedir = oldnode.param2
local dir = minetest.facedir_to_dir(facedir)
local target = vector.add(pos, vector.multiply(dir,-3))
local target_node = minetest.get_node(target)
if target_node.name == "commoditymarket_fantasy:caravan_post" then
local meta = minetest.get_meta(target)
meta:set_string("infotext", S("Right-click to summon a trader's caravan"))
end
end,
on_timer = function(pos, elapsed)
minetest.set_node(pos, {name="air"})
minetest.sound_play("commoditymarket_register_closed", {
pos = pos,
gain = 1.0, -- default
max_hear_distance = 32, -- default, uses an euclidean metric
})
end,
}
if override_table then
for k, v in pairs(override_table) do
def[k] = v
end
end
return def
end
-- Create five caravans with different textures, randomly pick which one shows up.
minetest.register_node("commoditymarket_fantasy:caravan_market_1", create_caravan_def())
minetest.register_node("commoditymarket_fantasy:caravan_market_2", create_caravan_def({
tiles = {
{ name = "commoditymarket_door_wood.png^[multiply:#CCCCFF", backface_culling = true }, -- door
{ name = "default_acacia_wood.png", backface_culling = true }, -- base wood
{ name = "default_fence_rail_wood.png", backface_culling = true }, -- wheel sides
{ name = "default_copper_block.png", backface_culling = true }, -- wheel tyre
{ name = "commoditymarket_shingles_wood.png^[multiply:#CC8888", backface_culling = true }, -- roof
{ name = "default_wood.png", backface_culling = true }, -- corner wood
}
}))
minetest.register_node("commoditymarket_fantasy:caravan_market_3", create_caravan_def({
tiles = {
{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
{ name = "default_aspen_wood.png", backface_culling = true }, -- base wood
{ name = "default_fence_aspen_wood.png", backface_culling = true }, -- wheel sides
{ name = "default_cobble.png", backface_culling = true }, -- wheel tyre
{ name = "default_stone_brick.png", backface_culling = true }, -- roof
{ name = "default_pine_tree.png", backface_culling = true }, -- corner wood
}
}))
minetest.register_node("commoditymarket_fantasy:caravan_market_4", create_caravan_def({
tiles = {
{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
{ name = "default_junglewood.png", backface_culling = true }, -- base wood
{ name = "default_fence_rail_junglewood.png", backface_culling = true }, -- wheel sides
{ name = "default_obsidian.png", backface_culling = true }, -- wheel tyre
{ name = "commoditymarket_shingles_wood.png^[multiply:#88FF88", backface_culling = true }, -- roof
{ name = "default_tree.png", backface_culling = true }, -- corner wood
}
}))
minetest.register_node("commoditymarket_fantasy:caravan_market_5", create_caravan_def({
tiles = {
{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
{ name = "default_pine_wood.png", backface_culling = true }, -- base wood
{ name = "default_chest_lock.png", backface_culling = true }, -- wheel sides
{ name = "default_chest_top.png", backface_culling = true }, -- wheel tyre
{ name = "default_furnace_top.png", backface_culling = true }, -- roof
{ name = "default_wood.png", backface_culling = true }, -- corner wood
}
}))
local caravan_protect = minetest.settings:get_bool("commoditymarket_protect_caravan_market", true)
local on_blast
if caravan_protect then
on_blast = function() end
end
-- This one doesn't delete itself, server admins can place a permanent instance of it that way. Maybe inside towns next to bigger stationary markets.
minetest.register_node("commoditymarket_fantasy:caravan_market_permanent", {
description = caravan_def.description,
_doc_items_longdesc = caravan_def.long_description,
_doc_items_usagehelp = usage_help,
drawtype = "mesh",
mesh = "commoditymarket_wagon.obj",
tiles = {
{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
{ name = "default_wood.png", backface_culling = true }, -- base wood
{ name = "default_fence_rail_wood.png", backface_culling = true }, -- wheel sides
{ name = "default_coal_block.png", backface_culling = true }, -- wheel tyre
{ name = "commoditymarket_shingles_wood.png", backface_culling = true }, -- roof
{ name = "default_junglewood.png", backface_culling = true }, -- corner wood
},
collision_box = {
type = "fixed",
fixed = {
{-0.75, -0.5, -1.25, 0.75, 1.5, 1.25},
},
},
selection_box = {
type = "fixed",
fixed = {
{-0.75, -0.5, -1.25, 0.75, 1.5, 1.25},
},
},
paramtype2 = "facedir",
is_ground_content = false,
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
sounds = default.node_sound_wood_defaults(),
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
commoditymarket.show_market("caravan", clicker:get_player_name())
end,
can_dig = function(pos, player)
return not caravan_protect or minetest.check_player_privs(player, "protection_bypass")
end,
on_blast = on_blast,
})
-- is a 5x3 area centered around pos clear of obstruction and has usable ground?
local is_suitable_caravan_space = function(pos, facedir)
local x_dim = 2
local z_dim = 2
local dir = minetest.facedir_to_dir(facedir)
if dir.x ~= 0 then
z_dim = 1
elseif dir.z ~= 0 then
x_dim = 1
end
-- walkable ground?
for x = pos.x - x_dim, pos.x + x_dim, 1 do
for z = pos.z - z_dim, pos.z + z_dim, 1 do
local node = minetest.get_node({x=x, y=pos.y-1, z=z})
local node_def = minetest.registered_nodes[node.name]
if node_def == nil or node_def.walkable ~= true then return false end
end
end
-- buildable_to in the rest?
for y = pos.y, pos.y+2, 1 do
for x = pos.x - x_dim, pos.x + x_dim, 1 do
for z = pos.z - z_dim, pos.z + z_dim, 1 do
local node = minetest.get_node({x=x, y=y, z=z})
local node_def = minetest.registered_nodes[node.name]
if node_def == nil or node_def.buildable_to ~= true then return false end
end
end
end
return true
end
minetest.register_node("commoditymarket_fantasy:caravan_post", {
description = S("Trading Post"),
_long_items_longdesc = S("This post signals passing caravan traders that customers can be found here, and signals to customers that caravan traders can be found here. If no caravan is present, right-click to summon one."),
_doc_items_usagehelp = S("The trader's caravan requires a suitable open space next to the trading post for it to arrive, and takes some time to arrive after being summoned. The post gives a countdown to the caravan's arrival when moused over."),
tiles = {"commoditymarket_sign.png^[transformR90", "commoditymarket_sign.png^[transformR270",
"commoditymarket_sign.png^commoditymarket_caravan_sign.png", "commoditymarket_sign.png^commoditymarket_caravan_sign.png^[transformFX",
"commoditymarket_sign_post.png", "commoditymarket_sign_post.png"},
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
sounds = default.node_sound_wood_defaults(),
inventory_image = "commoditymarket_caravan_sign_inventory.png",
paramtype= "light",
paramtype2 = "facedir",
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-0.125,-0.5,-0.5,0.125,2.0625,-0.25},
{-0.0625,1.4375,-0.25,0.0625,2.0,0.5},
},
},
on_construct = function(pos)
local timer = minetest.get_node_timer(pos)
timer:start(1.0)
end,
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
local timer = minetest.get_node_timer(pos)
timer:start(1.0)
end,
on_timer = function(pos, elapsed)
local node = minetest.get_node(pos)
local meta = minetest.get_meta(pos)
if node.name ~= "commoditymarket_fantasy:caravan_post" then
return -- the node was removed
end
local facedir = node.param2
local dir = minetest.facedir_to_dir(facedir)
local target = vector.add(pos, vector.multiply(dir,3))
local target_node = minetest.get_node(target)
if target_node.name:sub(1,string.len("commoditymarket_fantasy:caravan_market")) == "commoditymarket_fantasy:caravan_market" then
-- It's already here somehow, shut down timer.
meta:set_string("infotext", "")
meta:set_float("wait_time", 0)
return
end
local is_suitable_space = is_suitable_caravan_space(target, facedir)
if not is_suitable_space then
meta:set_string("infotext", S("Indicated parking area isn't suitable.\nA 5x3 open space with solid ground\nis required for a caravan."))
meta:set_float("wait_time", 0)
local timer = minetest.get_node_timer(pos)
timer:start(1.0)
return
end
local wait_time = (meta:get_float("wait_time") or 0) + elapsed
meta:set_float("wait_time", wait_time)
if wait_time < time_until_caravan then
meta:set_string("infotext", S("Caravan summoned\nETA: @1 seconds.", math.floor(time_until_caravan - wait_time)))
local timer = minetest.get_node_timer(pos)
timer:start(1.0)
return
end
-- spawn the caravan. We've already established that the target pos is clear.
minetest.set_node(target, {name="commoditymarket_fantasy:caravan_market_"..math.random(1,5), param2=facedir})
minetest.sound_play("commoditymarket_register_opened", {
pos = target,
gain = 1.0, -- default
max_hear_distance = 32, -- default, uses an euclidean metric
})
local timer = minetest.get_node_timer(target)
timer:start(dwell_time)
meta:set_string("infotext", "")
meta:set_float("wait_time", 0)
end,
})
end
-------------------------------------------------------------------------------
-- "Goblin Exchange"
if minetest.settings:get_bool("commoditymarket_enable_goblin_market") then
local goblin_def = {
description = S("Goblin Exchange"),
long_description = S("One does not usually associate Goblins with the sort of sophistication that running a market requires. Usually one just associates Goblins with savagery and violence. But they understand the principle of tit-for-tat exchange, and if approached correctly they actually respect the concepts of ownership and debt. However, for some peculiar reason they understand this concept in the context of coal lumps. Goblins deal in the standard coal lump as their form of currency, conceptually divided into 100 coal centilumps (though Goblin brokers prefer to \"keep the change\" when giving back actual coal lumps)."),
currency = {
["default:coal_lump"] = 100
},
currency_symbol = "¢", --"\u{00A2}" cent symbol
inventory_limit = 1000,
--sell_limit =, -- no sell limit
}
commoditymarket.register_market("goblin", goblin_def)
local goblin_protect = minetest.settings:get_bool("commoditymarket_protect_goblin_market", true)
local on_blast
if goblin_protect then
on_blast = function() end
end
minetest.register_node("commoditymarket_fantasy:goblin_market", {
description = goblin_def.description,
_doc_items_longdesc = goblin_def.long_description,
_doc_items_usagehelp = usage_help,
tiles = {"default_chest_top.png^(default_coal_block.png^[opacity:128)","default_chest_top.png^(default_coal_block.png^[opacity:128)",
"default_chest_side.png^(default_coal_block.png^[opacity:128)","default_chest_side.png^(default_coal_block.png^[opacity:128)",
"commoditymarket_empty_shelf.png^(default_coal_block.png^[opacity:128)","default_chest_side.png^(default_coal_block.png^[opacity:128)^commoditymarket_goblin.png",},
paramtype2 = "facedir",
is_ground_content = false,
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
sounds = default.node_sound_wood_defaults(),
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
commoditymarket.show_market("goblin", clicker:get_player_name())
end,
can_dig = function(pos, player)
return not goblin_protect or minetest.check_player_privs(player, "protection_bypass")
end,
on_blast = on_blast,
})
end
--------------------------------------------------------------------------------
if minetest.settings:get_bool("commoditymarket_enable_under_market") then
local undermarket_def = {
description = S("Undermarket"),
long_description = S("Deep in the bowels of the world, below even the goblin-infested warrens and ancient delvings of the dwarves, dark and mysterious beings once dwelled. A few still linger to this day, and facilitate barter for those brave souls willing to travel in their lost realms. The Undermarket uses Mese chips ('₥') as a currency - twenty chips to the Mese fragment. Though traders are loathe to physically break Mese crystals up into units that small, as it renders it useless for other purposes."),
currency = {
["default:mese"] = 9*9*20,
["default:mese_crystal"] = 9*20,
["default:mese_crystal_fragment"] = 20
},
currency_symbol = "", --"\u{20A5}" mill sign
inventory_limit = 10000,
--sell_limit =, -- no sell limit
}
commoditymarket.register_market("under", undermarket_def)
local under_protect = minetest.settings:get_bool("commoditymarket_protect_under_market", true)
local on_blast
if under_protect then
on_blast = function() end
end
minetest.register_node("commoditymarket_fantasy:under_market", {
description = undermarket_def.description,
_doc_items_longdesc = undermarket_def.long_description,
_doc_items_usagehelp = usage_help,
tiles = {"commoditymarket_under_top.png","commoditymarket_under_top.png",
"commoditymarket_under.png","commoditymarket_under.png","commoditymarket_under.png","commoditymarket_under.png"},
paramtype2 = "facedir",
is_ground_content = false,
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
sounds = default.node_sound_stone_defaults(),
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
commoditymarket.show_market("under", clicker:get_player_name())
end,
can_dig = function(pos, player)
return not under_protect or minetest.check_player_privs(player, "protection_bypass")
end,
on_blast = on_blast,
})
end
------------------------------------------------------------------

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-31 14:12-0700\n" "POT-Creation-Date: 2020-01-18 16:58-0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,33 +17,33 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: commoditymarket\default_markets.lua:13 #: commoditymarket_fantasy\init.lua:26
msgid "Gold Coins" msgid "Gold Coins"
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:14 #: commoditymarket_fantasy\init.lua:27
msgid "" msgid ""
"A gold ingot is far too valuable to use as a basic unit of value, so it has " "A gold ingot is far too valuable to use as a basic unit of value, so it has "
"become common practice to divide the standard gold bar into one thousand " "become common practice to divide the standard gold bar into one thousand "
"small disks to make trade easier." "small disks to make trade easier."
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:15 #: commoditymarket_fantasy\init.lua:28
msgid "" msgid ""
"Gold coins can be deposited and withdrawn from markets that accept them as " "Gold coins can be deposited and withdrawn from markets that accept them as "
"currency. These markets can make change if you have a thousand coins and " "currency. These markets can make change if you have a thousand coins and "
"would like them back in ingot form again." "would like them back in ingot form again."
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:25 #: commoditymarket_fantasy\init.lua:38
msgid "Right-click on this to open the market interface." msgid "Right-click on this to open the market interface."
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:33 #: commoditymarket_fantasy\init.lua:46
msgid "King's Market" msgid "King's Market"
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:34 #: commoditymarket_fantasy\init.lua:47
msgid "" msgid ""
"The largest and most accessible market for the common man, the King's Market " "The largest and most accessible market for the common man, the King's Market "
"uses gold coins as its medium of exchange (or the equivalent in gold ingots " "uses gold coins as its medium of exchange (or the equivalent in gold ingots "
@ -53,23 +53,23 @@ msgid ""
"Market. Gold coins are represented by a '☼' symbol." "Market. Gold coins are represented by a '☼' symbol."
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:77 #: commoditymarket_fantasy\init.lua:90
msgid "At this time of day the King's Market is closed." msgid "At this time of day the King's Market is closed."
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:92 #: commoditymarket_fantasy\init.lua:105
msgid "Night Market" msgid "Night Market"
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:131 #: commoditymarket_fantasy\init.lua:144
msgid "At this time of day the Night Market is closed." msgid "At this time of day the Night Market is closed."
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:150 #: commoditymarket_fantasy\init.lua:163
msgid "Trader's Caravan" msgid "Trader's Caravan"
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:151 #: commoditymarket_fantasy\init.lua:164
msgid "" msgid ""
"Unlike most markets that have well-known fixed locations that travelers " "Unlike most markets that have well-known fixed locations that travelers "
"congregate to, the network of Trader's Caravans is fluid and dynamic in " "congregate to, the network of Trader's Caravans is fluid and dynamic in "
@ -80,46 +80,46 @@ msgid ""
"a location where Trader's Caravans will make a stop." "a location where Trader's Caravans will make a stop."
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:219 #: commoditymarket_fantasy\init.lua:232
msgid "Right-click to summon a trader's caravan" msgid "Right-click to summon a trader's caravan"
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:362 #: commoditymarket_fantasy\init.lua:375
msgid "Trading Post" msgid "Trading Post"
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:363 #: commoditymarket_fantasy\init.lua:376
msgid "" msgid ""
"This post signals passing caravan traders that customers can be found here, " "This post signals passing caravan traders that customers can be found here, "
"and signals to customers that caravan traders can be found here. If no " "and signals to customers that caravan traders can be found here. If no "
"caravan is present, right-click to summon one." "caravan is present, right-click to summon one."
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:364 #: commoditymarket_fantasy\init.lua:377
msgid "" msgid ""
"The trader's caravan requires a suitable open space next to the trading post " "The trader's caravan requires a suitable open space next to the trading post "
"for it to arrive, and takes some time to arrive after being summoned. The " "for it to arrive, and takes some time to arrive after being summoned. The "
"post gives a countdown to the caravan's arrival when moused over." "post gives a countdown to the caravan's arrival when moused over."
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:411 #: commoditymarket_fantasy\init.lua:424
msgid "" msgid ""
"Indicated parking area isn't suitable.\n" "Indicated parking area isn't suitable.\n"
"A 5x3 open space with solid ground\n" "A 5x3 open space with solid ground\n"
"is required for a caravan." "is required for a caravan."
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:421 #: commoditymarket_fantasy\init.lua:434
msgid "" msgid ""
"Caravan summoned\n" "Caravan summoned\n"
"ETA: @1 seconds." "ETA: @1 seconds."
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:447 #: commoditymarket_fantasy\init.lua:460
msgid "Goblin Exchange" msgid "Goblin Exchange"
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:448 #: commoditymarket_fantasy\init.lua:461
msgid "" msgid ""
"One does not usually associate Goblins with the sort of sophistication that " "One does not usually associate Goblins with the sort of sophistication that "
"running a market requires. Usually one just associates Goblins with savagery " "running a market requires. Usually one just associates Goblins with savagery "
@ -131,11 +131,11 @@ msgid ""
"brokers prefer to \"keep the change\" when giving back actual coal lumps)." "brokers prefer to \"keep the change\" when giving back actual coal lumps)."
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:489 #: commoditymarket_fantasy\init.lua:502
msgid "Undermarket" msgid "Undermarket"
msgstr "" msgstr ""
#: commoditymarket\default_markets.lua:490 #: commoditymarket_fantasy\init.lua:503
msgid "" msgid ""
"Deep in the bowels of the world, below even the goblin-infested warrens and " "Deep in the bowels of the world, below even the goblin-infested warrens and "
"ancient delvings of the dwarves, dark and mysterious beings once dwelled. A " "ancient delvings of the dwarves, dark and mysterious beings once dwelled. A "
@ -145,435 +145,3 @@ msgid ""
"loathe to physically break Mese crystals up into units that small, as it " "loathe to physically break Mese crystals up into units that small, as it "
"renders it useless for other purposes." "renders it useless for other purposes."
msgstr "" msgstr ""
#: commoditymarket\doc.lua:11
msgid "Commodity Markets"
msgstr ""
#: commoditymarket\doc.lua:12
msgid ""
"Game-wide marketplaces where goods can be bought and sold at prices of your "
"choice."
msgstr ""
#: commoditymarket\doc.lua:17
msgid "User Interface: Inventory"
msgstr ""
#: commoditymarket\doc.lua:19
msgid ""
"Each player's account has an inventory that serves as a holding area for "
"items that are destined to be sold or that have been bought by the player "
"but not yet retrieved. This inventory is a bit different from the standard "
"Minetest inventory in that it doesn't hold item \"stacks\", it just tracks "
"the total number of that item present. Some markets allow for extremely "
"large quantities of an item to be stored here for sale.\n"
"\n"
"To add an item to your market inventory for eventual sale either shift-click "
"on the item in your player inventory or drag the item stack to the inventory "
"slot below the main market inventory list. Some markets may have "
"restrictions on what items can be bought and sold, if an item is not valid "
"for that market it won't go into the market's inventory. Some items are "
"considered \"currency\" and will add to your account's currency balance "
"instead of being listed in your market inventory.\n"
"\n"
"Tools cannot be added to the market inventory if they have any wear on them. "
"The market also can't handle items with attached metadata such as books that "
"have had text added to them.\n"
"\n"
"To remove an item from your market inventory, double-click in it in the "
"market inventory list. As much of the item as can fit into your player "
"inventory will be transferred to you, with any remainder staying behind in "
"the market inventory. To withdraw currency from your market balance type the "
"amount you'd like to withdraw in the field next to the \"Withdraw\" button. "
"The currency will be converted into items and added to your player "
"inventory, with whatever cannot be converted remaining behind in your market "
"balance."
msgstr ""
#: commoditymarket\doc.lua:29
msgid "User Interface: Orders"
msgstr ""
#: commoditymarket\doc.lua:32
msgid ""
"At the core of how a market operates are \"buy\" and \"sell\" orders. A buy "
"order is an announcement to the world that you are interested in purchasing "
"a certain quantity of item and are willing to pay a certain amount of "
"currency in exchange for each unit of that item. Conversely, a sell order is "
"an announcement to the world that you are interested in selling a certain "
"quantity of item and will accept a certain amount of currency in exchange "
"for each unit of that item.\n"
"\n"
"The market price of an item is determined by where the existing buy and sell "
"orders for that item intersect. When you offer to buy an item for a price "
"that someone is offering to sell it at, the item is transferred to you and "
"currency is transferred from your account to theirs to cover the cost. The "
"market will keep track of the most recent price that an item was "
"successfully sold for, but note that this information is for historical "
"interest only - there's no guarantee that anyone is currently willing to "
"match the historical price.\n"
"\n"
"When an item is selected in the upper list, the currently existing buy and "
"sell orders for that item will be displayed in the lower list. Sell orders "
"are listed first in descending price, followed by buy orders in ascending "
"price. The current market price will be somewhere in between the lowest sell "
"order and the highest buy order. If you wish to cancel a buy or sell order "
"that you've placed for an item, double-click on the order and the item or "
"currency that you put into that order will be returned to your inventory.\n"
"\n"
"If you place a buy order and there are already sell orders for the item that "
"meet or are below your price, some or all of your buy order might be "
"immediately fulfilled. Your purchases will be made at the price that the "
"sell orders have been set to - if you were willing to pay 15 units of "
"currency per item but someone was already offering to sell for 2 units of "
"currency per item, you only pay 2 units for each of that offer's items. If "
"there aren't enough compatible sell orders to fulfill your buy order, the "
"remainder will be placed into the market and made available for future "
"sellers to see and fulfill if they agree to your price. Your buy order will "
"immediately deduct the currency required for it from your account's balance, "
"but if you cancel your order you will get that currency back - it's not gone "
"until the order is actually fulfilled.\n"
"\n"
"If you place a sell order and there are already buy orders that meet or "
"exceed your price, some or all of your sell order may be immediately "
"fulfilled. You'll be paid the price that the buyers are offering rather than "
"the amount you're demanding. If any of your sell offer is left unfulfilled, "
"the sell order will be added to the market for future buyers to see. The "
"items for this offer will be immediately taken from your market inventory "
"but if you cancel your order you will get those items back."
msgstr ""
#: commoditymarket\formspecs.lua:43
#: commoditymarket\formspecs.lua:137
#: commoditymarket\formspecs.lua:275
#: commoditymarket\formspecs.lua:323
msgid "Unknown Item"
msgstr ""
#: commoditymarket\formspecs.lua:71
#: commoditymarket\formspecs.lua:225
#: commoditymarket\formspecs.lua:448
msgid "Your Inventory"
msgstr ""
#: commoditymarket\formspecs.lua:71
#: commoditymarket\formspecs.lua:225
#: commoditymarket\formspecs.lua:448
msgid "Market Orders"
msgstr ""
#: commoditymarket\formspecs.lua:88
msgid ""
"All the items you've transfered to the market to sell and the items you've\n"
"purchased with buy orders. Double-click on an item to bring it back into "
"your\n"
"personal inventory."
msgstr ""
#: commoditymarket\formspecs.lua:91
#: commoditymarket\formspecs.lua:95
msgid "Item"
msgstr ""
#: commoditymarket\formspecs.lua:93
#: commoditymarket\formspecs.lua:97
#: commoditymarket\formspecs.lua:253
msgid "Description"
msgstr ""
#: commoditymarket\formspecs.lua:93
#: commoditymarket\formspecs.lua:97
#: commoditymarket\formspecs.lua:347
#: commoditymarket\formspecs.lua:359
msgid "Quantity"
msgstr ""
#: commoditymarket\formspecs.lua:109
msgid ""
"Drop items here to\n"
"add to your account"
msgstr ""
#: commoditymarket\formspecs.lua:113
msgid "Inventory limit:"
msgstr ""
#: commoditymarket\formspecs.lua:114
msgid ""
"You can still receive purchased items if you've exceeded your inventory "
"limit,\n"
"but you won't be able to transfer items from your personal inventory into\n"
"the market until you've emptied it back down below the limit again."
msgstr ""
#: commoditymarket\formspecs.lua:119
msgid "Withdraw"
msgstr ""
#: commoditymarket\formspecs.lua:120
msgid ""
"Enter the amount of currency you'd like to withdraw then click the "
"'Withdraw'\n"
"button to convert it into items and transfer it to your personal inventory."
msgstr ""
#: commoditymarket\formspecs.lua:239
msgid "Number of items there's demand for in the market."
msgstr ""
#: commoditymarket\formspecs.lua:240
msgid "Maximum price being offered to buy one of these."
msgstr ""
#: commoditymarket\formspecs.lua:242
msgid "Number of items available for sale in the market."
msgstr ""
#: commoditymarket\formspecs.lua:243
msgid "Minimum price being demanded to sell one of these."
msgstr ""
#: commoditymarket\formspecs.lua:244
msgid "Price paid for one of these the last time one was sold."
msgstr ""
#: commoditymarket\formspecs.lua:245
msgid "Quantity of this item that you have in your inventory ready to sell."
msgstr ""
#: commoditymarket\formspecs.lua:253
msgid "Buy Vol"
msgstr ""
#: commoditymarket\formspecs.lua:253
msgid "Buy Max"
msgstr ""
#: commoditymarket\formspecs.lua:254
msgid "Sell Vol"
msgstr ""
#: commoditymarket\formspecs.lua:254
msgid "Sell Min"
msgstr ""
#: commoditymarket\formspecs.lua:254
msgid "Last Price"
msgstr ""
#: commoditymarket\formspecs.lua:254
msgid "Inventory"
msgstr ""
#: commoditymarket\formspecs.lua:308
msgid "My orders"
msgstr ""
#: commoditymarket\formspecs.lua:309
msgid ""
"Select this to show only the markets where you have either a buy or a sell "
"order pending."
msgstr ""
#: commoditymarket\formspecs.lua:310
msgid "Enter substring to search item identifiers for."
msgstr ""
#: commoditymarket\formspecs.lua:311
msgid "Apply search to outputs."
msgstr ""
#: commoditymarket\formspecs.lua:312
msgid "Clear search."
msgstr ""
#: commoditymarket\formspecs.lua:328
msgid "In inventory:"
msgstr ""
#: commoditymarket\formspecs.lua:329
msgid "Balance:"
msgstr ""
#: commoditymarket\formspecs.lua:342
msgid "Sell limit:"
msgstr ""
#: commoditymarket\formspecs.lua:343
msgid ""
"This market limits the total number of items a given seller can have for "
"sale at a time.\n"
"You have @1 items remaining. Cancel old sell orders to free up space."
msgstr ""
#: commoditymarket\formspecs.lua:346
msgid "Use these fields to enter buy and sell orders for the selected item."
msgstr ""
#: commoditymarket\formspecs.lua:347
#: commoditymarket\formspecs.lua:378
msgid "Buy"
msgstr ""
#: commoditymarket\formspecs.lua:348
msgid "Price per"
msgstr ""
#: commoditymarket\formspecs.lua:353
msgid "The price per item in this order."
msgstr ""
#: commoditymarket\formspecs.lua:354
msgid "The total amount of items in this particular order."
msgstr ""
#: commoditymarket\formspecs.lua:355
msgid ""
"The total amount of items available at this price accounting for the other "
"orders also currently being offered."
msgstr ""
#: commoditymarket\formspecs.lua:356
msgid ""
"The name of the player who placed this order.\n"
"Double-click your own orders to cancel them."
msgstr ""
#: commoditymarket\formspecs.lua:357
msgid "How many days ago this order was placed."
msgstr ""
#: commoditymarket\formspecs.lua:359
msgid "Order"
msgstr ""
#: commoditymarket\formspecs.lua:359
msgid "Price"
msgstr ""
#: commoditymarket\formspecs.lua:359
msgid "Total Volume"
msgstr ""
#: commoditymarket\formspecs.lua:359
msgid "Player"
msgstr ""
#: commoditymarket\formspecs.lua:359
msgid "Days Old"
msgstr ""
#: commoditymarket\formspecs.lua:363
msgid "Sell"
msgstr ""
#: commoditymarket\formspecs.lua:387
msgid "Select an item to view or place orders."
msgstr ""
#: commoditymarket\formspecs.lua:402
msgid "yourself"
msgstr ""
#: commoditymarket\formspecs.lua:404
#: commoditymarket\formspecs.lua:412
msgid "someone"
msgstr ""
#: commoditymarket\formspecs.lua:406
#: commoditymarket\formspecs.lua:414
msgid "you"
msgstr ""
#: commoditymarket\formspecs.lua:439
msgid "On day @1 @2 sold @3 @4 to @5 at @6@7 each for a total of @6@8."
msgstr ""
#: commoditymarket\formspecs.lua:449
msgid "Description:"
msgstr ""
#: commoditymarket\formspecs.lua:450
msgid "Your Recent Purchases and Sales:"
msgstr ""
#: commoditymarket\formspecs.lua:463
msgid "Mark logs as read"
msgstr ""
#: commoditymarket\formspecs.lua:464
msgid ""
"Log entries in yellow are new since last time you marked your log as read."
msgstr ""
#: commoditymarket\formspecs.lua:467
msgid "No logged activites in this market yet."
msgstr ""
#: commoditymarket\formspecs.lua:472
msgid "Show Itemnames"
msgstr ""
#: commoditymarket\market.lua:198
msgid ""
"You have too many items listed for sale in this market, please cancel some "
"sell orders to make room for new ones."
msgstr ""
#: commoditymarket\market.lua:200
msgid "You can't sell items for a negative price."
msgstr ""
#: commoditymarket\market.lua:202
msgid "You can't sell fewer than one item."
msgstr ""
#: commoditymarket\market.lua:204
msgid ""
"You don't have enough of that item in your inventory to post this sell order."
msgstr ""
#: commoditymarket\market.lua:295
msgid "You can't pay less than nothing for an item."
msgstr ""
#: commoditymarket\market.lua:297
msgid "You have to buy at least one item."
msgstr ""
#: commoditymarket\market.lua:299
msgid "You can't afford that many of this item."
msgstr ""
#: commoditymarket\market.lua:474
msgid "1 @1 = @2@3"
msgstr ""
#: commoditymarket\market.lua:478
msgid "Market inventory is limited to @1 items."
msgstr ""
#: commoditymarket\market.lua:480
msgid "Market has unlimited inventory space."
msgstr ""
#: commoditymarket\market.lua:485
msgid "Total pending sell orders are limited to @1 items."
msgstr ""
#: commoditymarket\market.lua:487
msgid "Market supports unlimited pending sell orders."
msgstr ""
#: commoditymarket\market.lua:494
msgid "Currency item values:"
msgstr ""
#: commoditymarket\market.lua:509
msgid "Market"
msgstr ""
#: commoditymarket\market.lua:510
msgid "A market where orders to buy or sell items can be placed and fulfilled."
msgstr ""

View File

@ -1,720 +0,0 @@
-- internationalization boilerplate
local MP = minetest.get_modpath(minetest.get_current_modname())
local S, NS = dofile(MP.."/intllib.lua")
commoditymarket.registered_markets = {}
local log_length_limit = 30
-- from http://lua-users.org/wiki/BinaryInsert
--[[
table.bininsert( table, value [, comp] )
Inserts a given value through BinaryInsert into the table sorted by [, comp].
If 'comp' is given, then it must be a function that receives
two table elements, and returns true when the first is less
than the second, e.g. comp = function(a, b) return a > b end,
will give a sorted table, with the biggest value on position 1.
[, comp] behaves as in table.sort(table, value [, comp])
returns the index where 'value' was inserted
]]--
local comp_default = function(a, b) return a < b end
function table.bininsert(t, value, comp)
-- Initialise compare function
local comp = comp or comp_default
-- Initialise numbers
local iStart, iEnd, iMid, iState = 1, #t, 1, 0
-- Get insert position
while iStart <= iEnd do
-- calculate middle
iMid = math.floor( (iStart+iEnd)/2 )
-- compare
if comp(value, t[iMid]) then
iEnd, iState = iMid - 1, 0
else
iStart, iState = iMid + 1, 1
end
end
local target = iMid+iState
table.insert(t, target, value)
return target
end
-- lowest price first
local buy_comp = function(order1, order2)
local price1 = order1.price
local price2 = order2.price
if price1 < price2 then
return true
elseif price1 == price2 and order1.timestamp < order2.timestamp then
return true
end
return false
end
-- highest price first
local sell_comp = function(order1, order2)
local price1 = order1.price
local price2 = order2.price
if price1 > price2 then
return true
elseif price1 == price2 and order1.timestamp < order2.timestamp then
return true
end
return false
end
---------------------------------
local get_account = function(market, player_name)
local account = market.player_accounts[player_name]
if account then
return account
end
account = {}
account.search = ""
account.name = player_name
account.balance = 0 -- currency
account.inventory = {} -- items stored in the market inventory that aren't part of sell orders yet. stored as "[item] = count"
account.filter_participating = "false"
account.log = {} -- might want to use a more sophisticated queue, but this isn't going to be a big list so that's more trouble than it's worth right now.
market.player_accounts[player_name] = account
return account
end
-- Caution: the data structures produced by sale logging caused me to discover
-- issue https://github.com/minetest/minetest/issues/8719 with minetest.serialize()
-- I'm working around it by using the code in persistence.lua instead
local log_sale = function(item, quantity, price, purchaser, seller)
local log_entry = {item=item, quantity=quantity, price=price, purchaser=purchaser, seller=seller, timestamp = minetest.get_gametime()}
local purchaser_log = purchaser.log
local seller_log = seller.log
table.insert(purchaser_log, log_entry)
if #purchaser_log > log_length_limit then
table.remove(purchaser_log, 1)
end
if (purchaser ~= seller) then
table.insert(seller_log, log_entry)
if #seller_log > log_length_limit then
table.remove(seller_log, 1)
end
end
end
local remove_orders_by_account = function(orders, account)
if not orders then return end
local i = 1
while i < #orders do
local order = orders[i]
if order.account == account then
table.remove(orders, i)
else
i = i + 1
end
end
end
local remove_account = function(player_name)
local account = player_accounts[player_name]
if account == nil then
return
end
player_accounts[player_name] = nil
for item, lists in pairs(market) do
remove_orders_by_account(lists.buy_orders, account)
remove_orders_by_account(lists.sell_orders, account)
end
end
------------------------------------------------------------------------------------------
local add_inventory_to_account = function(market, account, item, quantity)
if quantity < 1 then
return false
end
if market.def.currency[item] then
account.balance = account.balance + market.def.currency[item] * quantity
else
account.inventory[item] = (account.inventory[item] or 0) + quantity
end
return true
end
local remove_inventory_from_account = function(account, item, quantity)
if quantity < 1 then
return false
end
local inventory = account.inventory
local current_quantity = inventory[item] or 0
if current_quantity < quantity then
return false
end
local new_quantity = current_quantity - quantity
if new_quantity == 0 then
inventory[item] = nil
else
inventory[item] = new_quantity
end
return true
end
local remove_order = function(order, array)
for i, market_order in ipairs(array) do
if order == market_order then
table.remove(array, i)
return true
end
end
return false
end
-----------------------------------------------------------------------------------------------------------
local add_sell = function(market, account, item, price, quantity)
price = tonumber(price)
quantity = tonumber(quantity)
local sell_limit = market.def.sell_limit
local sell_limit_exceeded
if sell_limit then
local total_sell = 0
for item, orders in pairs(market.orders_for_items) do
for _, order in ipairs(orders.sell_orders) do
if order.account == account then
total_sell = total_sell + order.quantity
end
end
end
sell_limit_exceeded = total_sell + quantity > sell_limit
end
-- validate that this sell order is possible
if sell_limit_exceeded or price < 0 or quantity < 1 or not remove_inventory_from_account(account, item, quantity) then
minetest.sound_play({name = "commoditymarket_error", gain = 0.1}, {to_player=account.name})
if sell_limit_exceeded then
minetest.chat_send_player(account.name, S("You have too many items listed for sale in this market, please cancel some sell orders to make room for new ones."))
elseif price < 0 then
minetest.chat_send_player(account.name, S("You can't sell items for a negative price."))
elseif quantity < 1 then
minetest.chat_send_player(account.name, S("You can't sell fewer than one item."))
else
minetest.chat_send_player(account.name, S("You don't have enough of that item in your inventory to post this sell order."))
end
return false
end
local buy_market = market.orders_for_items[item].buy_orders
local buy_order = buy_market[#buy_market]
local current_buy_volume = market.orders_for_items[item].buy_volume
-- go through existing buy orders that are more expensive than or equal to the price
-- we're demanding, selling them at the order's price until we run out of
-- buy orders or run out of demand
while quantity > 0 and buy_order and buy_order.price >= price do
local quantity_to_sell = math.min(buy_order.quantity, quantity)
quantity = quantity - quantity_to_sell
local earned = quantity_to_sell*buy_order.price
account.balance = account.balance + earned
add_inventory_to_account(market, buy_order.account, item, quantity_to_sell)
buy_order.quantity = buy_order.quantity - quantity_to_sell
current_buy_volume = current_buy_volume - quantity_to_sell
if buy_order.account ~= account then
-- don't update the last price if a player is just buying and selling from themselves
market.orders_for_items[item].last_price = buy_order.price
end
log_sale(item, quantity_to_sell, buy_order.price, buy_order.account, account)
if buy_order.quantity == 0 then
table.remove(buy_market, #buy_market)
end
buy_order = buy_market[#buy_market]
end
market.orders_for_items[item].buy_volume = current_buy_volume
if quantity > 0 then
local sell_market = market.orders_for_items[item].sell_orders
-- create the order and insert it into order arrays
local order = {account=account, price=price, quantity=quantity, timestamp=minetest.get_gametime()}
table.bininsert(sell_market, order, sell_comp)
market.orders_for_items[item].sell_volume = market.orders_for_items[item].sell_volume + quantity
end
minetest.sound_play({name = "commoditymarket_register_opened", gain = 0.1}, {to_player=account.name})
return true
end
local cancel_sell = function(market, item, order)
local account = order.account
local quantity = order.quantity
local sell_market = market.orders_for_items[item].sell_orders
remove_order(order, sell_market)
market.orders_for_items[item].sell_volume = market.orders_for_items[item].sell_volume - quantity
add_inventory_to_account(market, account, item, quantity)
minetest.sound_play({name = "commoditymarket_register_closed", gain = 0.1}, {to_player=account.name})
end
-----------------------------------------------------------------------------------------------------------
local test_buy = function(market, balance, item, price, quantity)
local sell_market = market.orders_for_items[item].sell_orders
local test_quantity = quantity
local test_balance = balance
local i = 0
local sell_order = sell_market[#sell_market]
while test_quantity > 0 and sell_order and sell_order.price <= price do
local quantity_to_buy = math.min(sell_order.quantity, test_quantity)
test_quantity = test_quantity - quantity_to_buy
test_balance = test_balance - quantity_to_buy*sell_order.price
i = i + 1
sell_order = sell_market[#sell_market-i]
end
local spent = balance - test_balance
test_balance = test_balance - test_quantity*price
if test_balance < 0 then
return false, spent, test_quantity
end
return true, spent, test_quantity
end
local add_buy = function(market, account, item, price, quantity)
price = tonumber(price)
quantity = tonumber(quantity)
if price < 0 or quantity < 1 or not test_buy(market, account.balance, item, price, quantity) then
minetest.sound_play({name = "commoditymarket_error", gain = 0.1}, {to_player=account.name})
if price < 0 then
minetest.chat_send_player(account.name, S("You can't pay less than nothing for an item."))
elseif quantity < 1 then
minetest.chat_send_player(account.name, S("You have to buy at least one item."))
else
minetest.chat_send_player(account.name, S("You can't afford that many of this item."))
end
return false
end
local sell_market = market.orders_for_items[item].sell_orders
local sell_order = sell_market[#sell_market]
local current_sell_volume = market.orders_for_items[item].sell_volume
-- go through existing sell orders that are cheaper than or equal to the price
-- we're wanting to offer, buying them up at the offered price until we run out of
-- sell orders or run out of supply
while quantity > 0 and sell_order and sell_order.price <= price do
local quantity_to_buy = math.min(sell_order.quantity, quantity)
quantity = quantity - quantity_to_buy
local spent = quantity_to_buy*sell_order.price
account.balance = account.balance - spent
sell_order.account.balance = sell_order.account.balance + spent
sell_order.quantity = sell_order.quantity - quantity_to_buy
current_sell_volume = current_sell_volume - quantity_to_buy
add_inventory_to_account(market, account, item, quantity_to_buy)
if sell_order.account ~= account then
-- don't update the last price if a player is just buying and selling from themselves
market.orders_for_items[item].last_price = sell_order.price
end
log_sale(item, quantity_to_buy, sell_order.price, account, sell_order.account)
-- Sell order completely used up, remove it
if sell_order.quantity == 0 then
table.remove(sell_market, #sell_market)
end
-- get the next sell order
sell_order = sell_market[#sell_market]
end
market.orders_for_items[item].sell_volume = current_sell_volume
if quantity > 0 then
local buy_market = market.orders_for_items[item].buy_orders
-- create the order for the remainder and insert it into order arrays
local order = {account=account, price=price, quantity=quantity, timestamp=minetest.get_gametime()}
account.balance = account.balance - quantity*price -- buy orders are pre-paid
table.bininsert(buy_market, order, buy_comp)
market.orders_for_items[item].buy_volume = market.orders_for_items[item].buy_volume + quantity
end
minetest.sound_play({name = "commoditymarket_register_opened", gain = 0.1}, {to_player=account.name})
return true
end
local cancel_buy = function(market, item, order)
local account = order.account
local quantity = order.quantity
local price = order.price
local buy_market = market.orders_for_items[item].buy_orders
market.orders_for_items[item].buy_volume = market.orders_for_items[item].buy_volume - quantity
remove_order(order, buy_market)
account.balance = account.balance + price*quantity
minetest.sound_play({name = "commoditymarket_register_closed", gain = 0.1}, {to_player=account.name})
end
local initialize_market_item = function(orders_for_items, item)
if orders_for_items[item] == nil then
local lists = {}
lists.buy_orders = {}
lists.sell_orders = {}
lists.buy_volume = 0
lists.sell_volume = 0
lists.item = item
-- leave last_price nil to indicate it's never been sold before
orders_for_items[item] = lists
end
end
-----------------------------------------------------------------------------------------------------------
-- Chat commands
minetest.register_chatcommand("market.show", {
params = "marketname",
privs = {server=true},
description = S("show market formspec"),
func = function(name, param)
local market = commoditymarket.registered_markets[param]
if market == nil then return end
local formspec = market:get_formspec(market:get_account(name))
minetest.show_formspec(name, "commoditymarket:"..param..":"..name, formspec)
end,
})
minetest.register_chatcommand("market.list", {
params = "",
privs = {server=true},
description = S("list all registered markets"),
func = function(name, param)
local list = {}
for marketname, def in pairs(commoditymarket.registered_markets) do
table.insert(list, marketname)
end
table.sort(list)
minetest.chat_send_player(name, "Registered markets: " .. table.concat(list, ", "))
end,
})
local remove_market_item = function(market, item)
local marketitem = market.orders_for_items[item]
if marketitem then
local buy_orders = marketitem.buy_orders
while #buy_orders > 0 do
market:cancel_buy(item, buy_orders[#buy_orders])
end
local sell_orders = marketitem.sell_orders
while #sell_orders > 0 do
market:cancel_sell(item, sell_orders[#sell_orders])
end
market.orders_for_items[item] = nil
end
end
minetest.register_chatcommand("market.removeitem", {
params = "marketname item",
privs = {server=true},
description = S("remove item from market. All existing buys and sells will be canceled."),
func = function(name, param)
local params = param:split(" ")
if #params ~= 2 then
minetest.chat_send_player(name, "Incorrect parameter count")
return
end
local market = commoditymarket.registered_markets[params[1]]
if market == nil then
minetest.chat_send_player(name, "No such market: " .. params[1])
return
end
remove_market_item(market, params[2])
end,
})
minetest.register_chatcommand("market.purge_unknowns", {
params = "",
privs = {server=true},
description = S("removes all unknown items from all markets. All existing buys and sells for those items will be canceled."),
func = function(name, param)
for market_name, market in pairs(commoditymarket.registered_markets) do
local items_to_remove = {}
local items_to_move = {}
for item, orders in pairs(market.orders_for_items) do
local icon = commoditymarket.get_icon(item)
if icon == "unknown_item.png" then
table.insert(items_to_remove, item)
end
end
for _, item in ipairs(items_to_remove) do
minetest.chat_send_player(name, S("Purging item: @1 from market: @2", tostring(item), market_name))
minetest.log("warning", "[commoditymarket] Purging unknown item: " .. tostring(item) .. " from market: " .. market_name)
remove_market_item(market, item)
end
end
end,
})
-- Used during development and debugging to find items that break the market formspecs when added
local debugging_commands = false
if debugging_commands then
minetest.register_chatcommand("market.addeverything", {
params = "marketname",
privs = {server=true},
description = S("Add all registered items to the provided market"),
func = function(name, param)
local params = param:split(" ")
if #params ~= 1 then
minetest.chat_send_player(name, "Incorrect parameter count")
return
end
local market = commoditymarket.registered_markets[params[1]]
if market == nil then
minetest.chat_send_player(name, "No such market: " .. params[1])
return
end
for item_name, def in pairs(minetest.registered_items) do
initialize_market_item(market.orders_for_items, item_name)
end
end,
})
end
-----------------------------------------------------------------------------------------------------------
-- API exposed to the outside world
local add_inventory = function(self, player_name, item, quantity)
return add_inventory_to_account(self, get_account(self, player_name), item, quantity)
end
local remove_inventory = function(self, player_name, item, quantity)
return remove_inventory_from_account(get_account(self, player_name), item, quantity)
end
local sell = function(self, player_name, item, quantity, price)
return add_sell(self, get_account(self, player_name), item, price, quantity)
end
local buy = function(self, player_name, item, quantity, price)
return add_buy(self, get_account(self, player_name), item, price, quantity)
end
-- Using this instead of minetest.serialize because of https://github.com/minetest/minetest/issues/8719
local MP = minetest.get_modpath(minetest.get_current_modname())
local persistence_store, persistence_load = dofile(MP.."/persistence.lua")
local worldpath = minetest.get_worldpath()
local load_market_data = function(marketname)
local filename = worldpath .. "/market_"..marketname..".lua"
return persistence_load(filename)
end
local save_market_data = function(market)
local filename = worldpath .. "/market_"..market.name..".lua"
local data = {}
data.player_accounts = market.player_accounts
data.orders_for_items = market.orders_for_items
persistence_store(filename, data)
return true
end
local make_doc_entry = function() return end
if minetest.get_modpath("doc") then
make_doc_entry = function(market_name, market_def)
local currencies = {}
for _, currency_item in ipairs(market_def.currency_ordered) do
local item_def = minetest.registered_items[currency_item.item]
table.insert(currencies, S("1 @1 = @2@3", item_def.description, market_def.currency_symbol, currency_item.amount))
end
local inventory_limit
if market_def.inventory_limit then
inventory_limit = S("Market inventory is limited to @1 items.", market_def.inventory_limit)
else
inventory_limit = S("Market has unlimited inventory space.")
end
local sell_limit
if market_def.sell_limit then
sell_limit = S("Total pending sell orders are limited to @1 items.", market_def.inventory_limit)
else
sell_limit = S("Market supports unlimited pending sell orders.")
end
doc.add_entry("commoditymarket", "market_"..market_name, {
name = market_def.description,
data = { text = market_def.long_description
.."\n\n"
..S("Currency item values:") .. "\n " .. table.concat(currencies, "\n ")
.."\n\n"
..inventory_limit
.."\n"
..sell_limit
}
})
end
end
commoditymarket.register_market = function(market_name, market_def)
assert(not commoditymarket.registered_markets[market_name])
assert(market_def.currency)
market_def.currency_symbol = market_def.currency_symbol or "¤" -- \u{00A4} -- defaults to the generic currency symbol ("scarab")
market_def.description = market_def.description or S("Market")
market_def.long_description = market_def.long_description or S("A market where orders to buy or sell items can be placed and fulfilled.")
-- Reprocess currency table into a form easier for the withdraw code to work with
market_def.currency_ordered = {}
for item, amount in pairs(market_def.currency) do
table.insert(market_def.currency_ordered, {item=item, amount=amount})
end
table.sort(market_def.currency_ordered, function(currency1, currency2) return currency1.amount > currency2.amount end)
make_doc_entry(market_name, market_def) -- market_def has now been normalized, make documentation for it if doc is installed.
-- Just in case a developer supplied strings that don't work well in formspecs, escape them now so we don't have to do it
-- wherever they're used.
market_def.currency_symbol = minetest.formspec_escape(market_def.currency_symbol)
market_def.description = minetest.formspec_escape(market_def.description)
market_def.long_description = minetest.formspec_escape(market_def.long_description)
local new_market = {}
new_market.def = market_def
commoditymarket.registered_markets[market_name] = new_market
local loaded_data = load_market_data(market_name)
if loaded_data then
new_market.player_accounts = loaded_data.player_accounts
new_market.orders_for_items = loaded_data.orders_for_items
else
new_market.player_accounts = {}
new_market.orders_for_items = {}
end
-- If there's a list of initial items in the market def, initialize them. allow_item can trump this.
local initial_items = market_def.initial_items
if initial_items then
-- defer until after to ensure that all initial items have been registered, so we can guard against invalid items
minetest.after(0,
function()
for _, item in ipairs(initial_items) do
if minetest.registered_items[item] and
((not market_def.allow_item) or market_def.allow_item(item)) and
not market_def.currency[item] then
initialize_market_item(new_market.orders_for_items, item)
end
end
end)
end
market_def.initial_items = nil -- don't need this any more
new_market.name = market_name
new_market.add_inventory = add_inventory
new_market.remove_inventory = remove_inventory
new_market.sell = sell
new_market.buy = buy
new_market.cancel_sell = cancel_sell
new_market.cancel_buy = cancel_buy
new_market.get_formspec = commoditymarket.get_formspec
new_market.get_account = get_account
new_market.save = save_market_data
-- save markets on shutdown
minetest.register_on_shutdown(function() new_market:save() end)
-- and also every ten minutes, to be on the safe side in case Minetest crashes
-- TODO: a more sophisticated approach that checks whether the market data is "dirty" before actually saving
local until_next_save = 600
minetest.register_globalstep(function(dtime)
until_next_save = until_next_save - dtime
if until_next_save < 0 then
new_market:save()
until_next_save = 600
end
end)
----------------------------------------------------------------------
-- Detached inventory for adding items into the market
local inv = minetest.create_detached_inventory("commoditymarket:"..market_name, {
allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
return 0
end,
allow_put = function(inv, listname, index, stack, player)
local item = stack:get_name()
-- reject unknown items
if minetest.registered_items[item] == nil then
return 0
end
-- Currency items are always allowed
if new_market.def.currency[item] then
return stack:get_count()
end
-- only new tools, no used tools
if stack:get_wear() ~= 0 then
return 0
end
--nothing with metadata permitted
local meta = stack:get_meta():to_table()
local fields = meta.fields
local inventory = meta.inventory
if (fields and next(fields)) or (inventory and next(inventory)) then
return 0
end
-- If there's no allow_item function defined, allow everything. Otherwise check if the item is allowed
if (not market_def.allow_item) or market_def.allow_item(item) then
local allowed_count = stack:get_count()
if market_def.inventory_limit then
-- limit additions to the inventory_limit, if there is one
local current_count = 0
for _, inventory_quantity in pairs(new_market:get_account(player:get_player_name()).inventory) do
current_count = current_count + inventory_quantity
end
allowed_count = math.min(allowed_count, allowed_count + market_def.inventory_limit - (current_count+allowed_count))
if allowed_count <= 0 then return 0 end
end
--ensures the item is in the market listing if it wasn't before
initialize_market_item(new_market.orders_for_items, item)
return allowed_count
end
return 0
end,
allow_take = function(inv, listname, index, stack, player)
return 0
end,
on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
end,
on_take = function(inv, listname, index, stack, player)
end,
on_put = function(inv, listname, index, stack, player)
if listname == "add" then
local item = stack:get_name()
local count = stack:get_count()
new_market:add_inventory(player:get_player_name(), item, count)
inv:set_list("add", {})
local name = player:get_player_name()
local formspec = new_market:get_formspec(new_market:get_account(name))
minetest.show_formspec(name, "commoditymarket:"..market_name..":"..name, formspec)
end
end
})
inv:set_size("add", 1)
end
commoditymarket.show_market = function(market_name, player_name)
local market = commoditymarket.registered_markets[market_name]
if market == nil then return end
local formspec = market:get_formspec(market:get_account(player_name))
minetest.show_formspec(player_name, "commoditymarket:"..market_name..":"..player_name, formspec)
end

View File

@ -1,3 +1,4 @@
name = commoditymarket name = commoditymarket_fantasy
description = Provides support for various in-world commodity markets description = Adds a number of fantasy-themed marketplaces
optional_depends = default, doc depends = commoditymarket, default
optional_depends = doc

View File

@ -1,200 +0,0 @@
-- Internal persistence library
--[[ Provides ]]
-- persistence.store(path, ...): Stores arbitrary items to the file at the given path
-- persistence.load(path): Loads files that were previously stored with store and returns them
--[[ Limitations ]]
-- Does not export userdata, threads or most function values
-- Function export is not portable
--[[ License: MIT (see bottom) ]]
-- Private methods
local write, writeIndent, writers, refCount;
-- write thing (dispatcher)
write = function (file, item, level, objRefNames)
writers[type(item)](file, item, level, objRefNames);
end;
-- write indent
writeIndent = function (file, level)
for i = 1, level do
file:write("\t");
end;
end;
-- recursively count references
refCount = function (objRefCount, item)
-- only count reference types (tables)
if type(item) == "table" then
-- Increase ref count
if objRefCount[item] then
objRefCount[item] = objRefCount[item] + 1;
else
objRefCount[item] = 1;
-- If first encounter, traverse
for k, v in pairs(item) do
refCount(objRefCount, k);
refCount(objRefCount, v);
end;
end;
end;
end;
-- Format items for the purpose of restoring
writers = {
["nil"] = function (file, item)
file:write("nil");
end;
["number"] = function (file, item)
file:write(tostring(item));
end;
["string"] = function (file, item)
file:write(string.format("%q", item));
end;
["boolean"] = function (file, item)
if item then
file:write("true");
else
file:write("false");
end
end;
["table"] = function (file, item, level, objRefNames)
local refIdx = objRefNames[item];
if refIdx then
-- Table with multiple references
file:write("multiRefObjects["..refIdx.."]");
else
-- Single use table
file:write("{\n");
for k, v in pairs(item) do
writeIndent(file, level+1);
file:write("[");
write(file, k, level+1, objRefNames);
file:write("] = ");
write(file, v, level+1, objRefNames);
file:write(";\n");
end
writeIndent(file, level);
file:write("}");
end;
end;
["function"] = function (file, item)
-- Does only work for "normal" functions, not those
-- with upvalues or c functions
local dInfo = debug.getinfo(item, "uS");
if dInfo.nups > 0 then
file:write("nil --[[functions with upvalue not supported]]");
elseif dInfo.what ~= "Lua" then
file:write("nil --[[non-lua function not supported]]");
else
local r, s = pcall(string.dump,item);
if r then
file:write(string.format("loadstring(%q)", s));
else
file:write("nil --[[function could not be dumped]]");
end
end
end;
["thread"] = function (file, item)
file:write("nil --[[thread]]\n");
end;
["userdata"] = function (file, item)
file:write("nil --[[userdata]]\n");
end;
}
return function (path, ...)
local file, e;
if type(path) == "string" then
-- Path, open a file
file, e = io.open(path, "w");
if not file then
return error(e);
end
else
-- Just treat it as file
file = path;
end
local n = select("#", ...);
-- Count references
local objRefCount = {}; -- Stores reference that will be exported
for i = 1, n do
refCount(objRefCount, (select(i,...)));
end;
-- Export Objects with more than one ref and assign name
-- First, create empty tables for each
local objRefNames = {};
local objRefIdx = 0;
file:write("-- Persistent Data\n");
file:write("local multiRefObjects = {\n");
for obj, count in pairs(objRefCount) do
if count > 1 then
objRefIdx = objRefIdx + 1;
objRefNames[obj] = objRefIdx;
file:write("{};"); -- table objRefIdx
end;
end;
file:write("\n} -- multiRefObjects\n");
-- Then fill them (this requires all empty multiRefObjects to exist)
for obj, idx in pairs(objRefNames) do
for k, v in pairs(obj) do
file:write("multiRefObjects["..idx.."][");
write(file, k, 0, objRefNames);
file:write("] = ");
write(file, v, 0, objRefNames);
file:write(";\n");
end;
end;
-- Create the remaining objects
for i = 1, n do
file:write("local ".."obj"..i.." = ");
write(file, (select(i,...)), 0, objRefNames);
file:write("\n");
end
-- Return them
if n > 0 then
file:write("return obj1");
for i = 2, n do
file:write(" ,obj"..i);
end;
file:write("\n");
else
file:write("return\n");
end;
file:close();
end, function (path)
local f, e = loadfile(path);
if f then
return f();
else
return nil, e;
end;
end
--[[
Copyright (c) 2010 Gerhard Roethlin
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.
]]

View File

@ -1,65 +1,4 @@
This mod implements marketplaces where players can post buy and sell offers for various items, allowing for organic market forces to determine the relative values of the resources in a world. This mod implements a number of fantasy-themed marketplaces where players can post buy and sell offers for various items, allowing for organic market forces to determine the relative values of the resources in a world.
The basic market interface is the same across all markets and market types, but this mod allows for a variety of different ways that markets can be configured to support different playstyles. Markets can have restrictions on what they will allow to be bought and sold, different types of "currency", and can share a common inventory across multiple locations or can be localized to just one spot at the discretion of the server owner.
![](screenshot.png)
## Currency
Each market has one or more "currency" items defined that are treated differently from the other items that can be bought and sold there. Currency items are translated into a player's currency balance rather than being bought and sold directly.
For example, the default market offered by this mod has this currency definition:
{
["default:gold_ingot"] = 1000,
["commoditymarket:gold_coin"] = 1
}
When a gold ingot is added to the player's market account it turns into 1000 units of currency. When a gold coin is added it turns into 1 unit of currency. You can't buy and sell gold directly in this market, it is instead the "standard" by which the value of other items is measured.
There's no reason that all markets in a given world have to use the same currency. Having variety in currency types adds flavour to the world and also introduces opportunities for enterprising traders to make a profit by moneychanging between different marketplaces.
## Account Inventory
In addition to tracking a player's currency balance, each player's account has an inventory that serves as a holding area for items that are destined to be sold or that have been bought by the player but not yet retrieved. This inventory is a bit different from the standard Minetest inventory in that it doesn't hold individual item "stacks", allowing for larger quantities of items to be accumulated than would otherwise be practical. If a player needs to buy 20,000 stone bricks for a major construction project then their account's inventory will hold that.
To prevent abuse of the market inventory as a free storage space, or just to add some unique flavor to a particular market, a limit on the inventory's size can be added. This limit only affects transfers from a player's personal inventory into the market inventory; the limit can be exceeded by incoming items being sold to the player.
Note that tools cannot be added to the market inventory if they have any wear on them, nor can the market handle items with attached metadata (such as books that have had text added to them).
## Placing a "Buy" Order
A buy order is an offer to give a certain amount of currency in exchange for a particular type of item. To place a buy order go to the "Market Orders" tab of the market's interface and select the item from the list of items on the market. If the item isn't listed it may be that the market is simply "unaware" of the item's existence; try placing an example of the item into your personal inventory and if the item is permitted on the market a new entry will be added to Market Orders.
Enter the quantity and price you desire and then click the "buy" button to place a buy order.
If there are already "sell" orders for the item when you place a buy order, some or all of your buy order might be immediately fulfilled provided you are offering a sufficient price. Your purchases will be made at the price that the sell orders have been set to - if you were willing to pay 15 units of currency per item but someone was already offering to sell for 2 units of currency per item, you only pay 2 units for each of that offer's items.
If there aren't enough compatible sell orders to fulfill your buy order, the remainder will be placed into the market and made available for future sellers to see and fulfill if they agree to your price. Your buy order will immediately deduct the currency required for it from your account's balance, but if you cancel your order you will get that currency back - it's not gone until the order is actually fulfilled.
Double-click on your order in the orders list to cancel it.
## Placing a "Sell" Order
Sell orders are an offer of a certain amount of an item and a price you're willing to accept in exchange for them. They're placed in a similar manner to buy orders, except by clicking the "sell" button instead of the "buy" button.
If there are already buyers with buy orders that meet or exceed your price, some or all of your sell order may be immediately fulfilled. You'll be paid the price that the buyers are offering rather than the amount you're demanding.
If any of your sell offer is left unfulfilled, the sell order will be added to the market for future buyers to see. The items for this offer will be immediately taken from your market inventory but if you cancel your order you will get those items back.
Double-click on your order in the orders list to cancel it.
## Commands
This mod has several commands that a server administrator can use:
* market.removeitem marketname item -- cancels all existing buy and sell orders for an item and removes its entry from the market tab. This is useful if you've changed what items are permitted in a particular market and need to clear out items that are no longer allowed.
* market.show marketname -- opens the market's formspec
* market.list -- lists the marketnames of all registered markets
## Registering a market
The file "default_markets.lua" contains a number of pre-defined markets that provide examples of what's possible with this mod. They can be enabled as-is with game settings and include:
* King's Market - a basic sort of "commoner's marketplace", only open during the day * King's Market - a basic sort of "commoner's marketplace", only open during the day
* Night Market - the shadier side of commerce, only open during the night * Night Market - the shadier side of commerce, only open during the night
@ -67,27 +6,10 @@ The file "default_markets.lua" contains a number of pre-defined markets that pro
* Goblin Exchange - a strange marketplace that uses coal as a currency * Goblin Exchange - a strange marketplace that uses coal as a currency
* Undermarket - where dark powers make their trades, using Mese as a currency * Undermarket - where dark powers make their trades, using Mese as a currency
All of these except for the Trader's Caravan are intended to be placed in specific locations by server administrators, they don't have crafting recipes. Modifying these markets or creating your own from scratch should hopefully be a fairly straightforward task. ![](screenshot.jpg)
### Market definition API All of these except for the Trader's Caravan are intended to be placed in specific locations by server administrators, they don't have crafting recipes.
``` An option exists to allow Goblin markets and Undermarkets to be automatically placed inside dungeons by world gen.
local market_def = {
description = "Night Market", -- A short name for this market, appears as the text of the "info" tab of the market's UI
long_description = "When the sun sets and the stalls of the King's Market close, other vendors are just waking up to share their wares. The Night Market is not as voluminous as the King's Market but accepts a wider range of wares. It accepts the same gold coinage of the realm, one thousand coins to the gold ingot.", -- A longer description with flavor text and other information to present to the user, shown in the info tab. Optional.
currency = {
["default:gold_ingot"] = 1000,
["commoditymarket:gold_coins"] = 1
}, -- List all items that get translated into "currency" here, along with their conversion rates. Take care to ensure there's no way for a player to multiply their money when crafting currency items into each other (eg, if there was some way to get more than 1000 coin items out of a gold ingot, in this case)
currency_symbol = "☼", -- Used in various places in the UI. If not defined, defaults to "¤" (the generic currency symbol)
inventory_limit = 10000, -- Optional, when set this prevents the player from adding items to their market inventory when it's over this limit
sell_limit = 10000, -- Optional, when set this prevents sell orders from being added if the player already has this many items for sale
initial_items = {"default:cobble", "default:wood"}, -- Optional, a list of items that the market will be initialized with on startup. Players can add other items during play.
allow_item = function(item) return true end, -- Optional, this function is used to determine whether the market permits a player to add a particular item to its inventory.
anonymous = true, -- If set to true then the player won't be able to see the names associated with other player's orders, only their own.
}
commoditymarket.register_market("market_name", market_def) The [settlements](https://github.com/FaceDeer/settlements) mod scatters small settlements around on the surface of the world that can contain King's Markets and Night Markets.
```
Once a market is defined, use `commoditymarket.show_market(market_name, player_name)` to show the market interface to a player.

BIN
screenshot.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

View File

@ -1,18 +1,9 @@
#Some item images are very large and break the market formspec.
#See https://github.com/minetest/minetest/issues/9300
#Alternately, use:
#commoditymarket.override_item_icon(item_name, new_icon_texture)
#or
#commoditymarket.override_image_icon(old_icon_texture, new_icon_texture)
#to override a troublesome image directly.
commoditymarket_enable_item_icons (Enable item icon images in market formspecs) bool true
[Enable default markets] [Enable default markets]
commoditymarket_enable_kings_market (Enable King's Market) bool false commoditymarket_enable_kings_market (Enable King's Market) bool true
commoditymarket_enable_night_market (Enable Night Market) bool false commoditymarket_enable_night_market (Enable Night Market) bool true
commoditymarket_enable_caravan_market (Enable Trader's Caravan) bool true commoditymarket_enable_caravan_market (Enable Trader's Caravan) bool true
commoditymarket_enable_goblin_market (Enable Goblin Exchange) bool false commoditymarket_enable_goblin_market (Enable Goblin Exchange) bool true
commoditymarket_enable_under_market (Enable Undermarket) bool false commoditymarket_enable_under_market (Enable Undermarket) bool true
[Market node protection] [Market node protection]
commoditymarket_protect_kings_market (Protect King's Market node) bool true commoditymarket_protect_kings_market (Protect King's Market node) bool true

Binary file not shown.

View File

@ -1,3 +0,0 @@
commoditymarket_register_closed.ogg - from https://freesound.org/people/bspiller5/sounds/180252/ by bspiller5 under the CC-BY 3.0 license
commoditymarket_register_opened.ogg - from https://freesound.org/people/kiddpark/sounds/201159/ by kiddpark under the CC-BY 3.0 license
commoditymarket_error.ogg - from https://freesound.org/people/LorenzoTheGreat/sounds/417794/ by LorenzoTheGreat under the CC-BY-SA 3.0 license

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 446 B

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 562 B

After

Width:  |  Height:  |  Size: 723 B

View File

@ -3,7 +3,6 @@ commoditymarket_crown.png - from https://commons.wikimedia.org/wiki/File:Farm-Fr
commoditymarket_moon.png - from https://commons.wikimedia.org/wiki/File:Luneta08.svg by Arturo D. Castillo —Zoram.hakaan— under the CC-BY 3.0 Unported license commoditymarket_moon.png - from https://commons.wikimedia.org/wiki/File:Luneta08.svg by Arturo D. Castillo —Zoram.hakaan— under the CC-BY 3.0 Unported license
commoditymarket_goblin.png - cropped from the "goblins" mod, Copyright 2015 by Francisco "FreeLikeGNU" Athens Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0) commoditymarket_goblin.png - cropped from the "goblins" mod, Copyright 2015 by Francisco "FreeLikeGNU" Athens Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
commoditymarket_empty_shelf.png - from the moreblocks mod's "moreblocks_empty_shelf", under the zlib license by Hugo Locurcio and contributors commoditymarket_empty_shelf.png - from the moreblocks mod's "moreblocks_empty_shelf", under the zlib license by Hugo Locurcio and contributors
commoditymarket_search.png, commoditymarket_clear.png - Copyright © Diego Martínez (kaeza): CC BY-SA 3.0
commoditymarket_shingles_wood.png is cottages_homedecor_shingles_wood.png from the cottages mod by VanessaE (CC-by-SA 3.0) commoditymarket_shingles_wood.png is cottages_homedecor_shingles_wood.png from the cottages mod by VanessaE (CC-by-SA 3.0)
commoditymarket_trade.png, commoditymarket_trade_flag.png, commoditymarket_under.png, and commoditymarket_under_top were created by FaceDeer and released under the CC0 public domain license commoditymarket_trade.png, commoditymarket_trade_flag.png, commoditymarket_under.png, and commoditymarket_under_top were created by FaceDeer and released under the CC0 public domain license