diff --git a/functions.lua b/functions.lua index cb5e433..585787a 100644 --- a/functions.lua +++ b/functions.lua @@ -24,6 +24,8 @@ yl_speak_up.npc_was_changed = {} -- format: yl_speak_up.npc_owner[ npc_id ] = owner_name yl_speak_up.npc_owner = {} +-- store the current trade between player and npc +yl_speak_up.trade = {} function yl_speak_up.init_mob_table() return false @@ -2772,7 +2774,7 @@ yl_speak_up.input_talk = function(player, formname, fields) if(edit_mode and fields.show_inventory) then local dialog = yl_speak_up.speak_to[pname].dialog -- the inventory is just an inventory with a back button; come back to this dialog here - minetest.show_formspec(pname, "yl_speak_up:inventory", yl_speak_up.get_fs_inventory(player, n_id, dialog.n_npc)) + minetest.show_formspec(pname, "yl_speak_up:inventory", yl_speak_up.get_fs_inventory(player)) return end diff --git a/init.lua b/init.lua index 815eee0..ae6c950 100644 --- a/init.lua +++ b/init.lua @@ -19,8 +19,13 @@ dofile(modpath .. "config.lua") dofile(modpath .. "privs.lua") -- inventory management, trading and handling of quest items: dofile(modpath .. "inventory.lua") +-- trade one item(stack) against one other item(stack) +dofile(modpath .. "trade_simple.lua") +-- the main functionality of the mod dofile(modpath .. "functions.lua") +-- the staffs (requires npc_master priv) dofile(modpath .. "tools.lua") +-- the actual mobs, using mobs_redo dofile(modpath .. "mobs.lua") --dofile(modpath .. "debug.lua") diff --git a/inventory.lua b/inventory.lua index 4300793..c1e1fd6 100644 --- a/inventory.lua +++ b/inventory.lua @@ -10,14 +10,28 @@ end -- access the inventory of the NPC (only possible for players with the right priv) -yl_speak_up.get_fs_inventory = function(player, n_id, npc_name) +yl_speak_up.get_fs_inventory = function(player) + if(not(player)) then + return "" + end + local pname = player:get_player_name() + -- which NPC is the player talking to? + local n_id = yl_speak_up.speak_to[pname].n_id + local dialog = yl_speak_up.speak_to[pname].dialog + -- do we have all the necessary data? + if(not(n_id) or not(dialog.n_npc)) then + return "size[6,2]".. + "label[0.2,0.5;Ups! This NPC lacks ID or name.]".. + "button_exit[2,1.5;1,0.9;exit;Exit]" + + end return "size[12,11]" .. - "label[2,-0.2;Inventory of "..minetest.formspec_escape(npc_name).. + "label[2,-0.2;Inventory of "..minetest.formspec_escape(dialog.n_npc).. " (ID: "..tostring(n_id).."):]".. - "list[detached:yl_npc_"..tostring(n_id)..";npc_main;0,0.3;12,6;]" .. + "list[detached:yl_speak_up_npc_"..tostring(n_id)..";npc_main;0,0.3;12,6;]" .. "list[current_player;main;2,6.85;8,1;]" .. "list[current_player;main;2,8.08;8,3;8]" .. - "listring[detached:yl_npc_"..tostring(n_id)..";npc_main]" .. + "listring[detached:yl_speak_up_npc_"..tostring(n_id)..";npc_main]" .. "listring[current_player;main]" .. "button[10.0,10.2;2,0.9;back_from_inventory;Back]" end @@ -50,6 +64,7 @@ end -- create and load the detached inventory in yl_speak_up.after_activate; -- direct access to this inventory is only possible for players with the right privs +-- (this is an inventory for the *NPC*, which is stored to disk sometimes) yl_speak_up.load_npc_inventory = function(n_id) if(not(n_id)) then return @@ -59,7 +74,7 @@ yl_speak_up.load_npc_inventory = function(n_id) return end -- create the detached inventory (it is empty for now) - local npc_inv = minetest.create_detached_inventory("yl_npc_"..tostring(n_id), { + local npc_inv = minetest.create_detached_inventory("yl_speak_up_npc_"..tostring(n_id), { allow_move = function(inv, from_list, from_index, to_list, to_index, count, player) if(not(yl_speak_up.may_edit_npc(player, n_id))) then return 0 diff --git a/trade_simple.lua b/trade_simple.lua new file mode 100644 index 0000000..84343d3 --- /dev/null +++ b/trade_simple.lua @@ -0,0 +1,275 @@ +-- spimple trading: one item(stack) for another item(stack) + +-- fallback message if something went wrong +yl_speak_up.trade_fail_fs = "size[6,2]".. + "label[0.2,0.5;Ups! The trade is not possible.\nPlease notify an admin.]".. + "button_exit[2,1.5;1,0.9;exit;Exit]" + +-- TODO: when closing the formspec: give the items in the pay slot back + +-- can this trade be made? called in allow_take +yl_speak_up.can_trade_simple = function(player, count) + if(not(player)) then + return 0 + end + local pname = player:get_player_name() + -- which trade are we talking about? + local trade = yl_speak_up.trade[pname] + -- do we have all the necessary data? + if(not(trade) or trade.trade_type ~= "trade_simple") then + return 0 + end + + -- the player tries to take *less* items than what his payment is; + -- avoid this confusion! + if(ItemStack(trade.npc_gives):get_count() ~= count) then + return 0 + end + -- buy, sell and config items need to be placed somewhere + local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) + -- the players' inventory + local player_inv = player:get_inventory() + -- the NPCs' inventory + local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(trade.n_id)}) + + -- is the payment in the payment slot? + if( not(trade_inv:contains_item("pay", trade.player_gives)) + -- is the item to be sold in the buy slot? + or not(trade_inv:contains_item("buy", trade.npc_gives)) + -- has the NPC room for the payment? + or not(npc_inv:room_for_item("npc_main", trade.player_gives)) + -- has the player room for the sold item? + or not(player_inv:room_for_item("main", trade.npc_gives))) then + -- trade not possible + return 0 + end + -- all ok; all items that are to be sold can be taken + return ItemStack(trade.npc_gives):get_count() +end + + +-- actually execute the trade +yl_speak_up.do_trade_simple = function(player, count) + -- can the trade be made? + if(not(yl_speak_up.can_trade_simple(player, count))) then + return + end + + local pname = player:get_player_name() + -- which trade are we talking about? + local trade = yl_speak_up.trade[pname] + + -- buy, sell and config items need to be placed somewhere + local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) + -- the NPCs' inventory + local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(trade.n_id)}) + + -- the NPC sells these items right now, and the player is moving it to his inventory + npc_inv:remove_item("npc_main", trade.npc_gives) + + -- move price items to the NPC + local stack = trade_inv:remove_item("pay", trade.player_gives) + npc_inv:add_item("npc_main", stack) + -- save the inventory of the npc so that the payment does not get lost + yl_speak_up.save_npc_inventory( trade.n_id ) + + -- store for statistics how many times the player has executed this trade + trade.trade_done = trade.trade_done + 1 +end + + +-- simple trade: one item(stack) for another +-- handles configuration of new trades and showing the formspec for trades; +-- checks if payment and buying is possible +yl_speak_up.get_fs_trade_simple = function(player, player_gives, npc_gives) + if(not(player)) then + return yl_speak_up.trade_fail_fs + end + local pname = player:get_player_name() + -- which trade are we talking about? + local trade = yl_speak_up.trade[pname] + -- do we have all the necessary data? + if(not(trade) or trade.trade_type ~= "trade_simple") then + return yl_speak_up.trade_fail_fs + end + -- the common formspec, shared by actual trade and configuration + -- no listring here as that would make things more complicated + local formspec = "size[8.5,8]".. + "label[4.35,0.8;"..minetest.formspec_escape(trade.npc_name).." sells:]".. + "list[current_player;main;0.2,3.85;8,1;]".. + "list[current_player;main;0.2,5.08;8,3;8]" + + -- configuration of a new trade happens here + if(not(trade.player_gives) or not(trade.npc_gives)) then + -- is this player allowed to edit the NPC and his trades? If not abort. + if(not(yl_speak_up.may_edit_npc(player, trade.n_id))) then + return formspec.. + "label[2.0,1.8;Ups! Something went wrong.]".. + "button[6.2,1.6;2.0,0.9;abort_trade_simple;Back]" + end + return formspec.. + -- show the second slot of the setup inventory in the detached player's inv + "list[detached:yl_speak_up_player_"..pname..";setup;2,1.5;1,1;]".. + -- show the second slot of said inventory + "list[detached:yl_speak_up_player_"..pname..";setup;5,1.5;1,1;1]".. + "label[2.5,0.0;Configure trade with "..minetest.formspec_escape(trade.npc_name)..":]".. + "label[1.5,0.8;The customer pays:]".. + "label[1.5,2.8;Put items in the two slots and click on \"Store trade\".]".. + "label[1.5,3.2;You will get your items back when storing the trade.]".. + "button[0.2,1.6;1.0,0.9;abort_trade_simple;Abort]".. + "button[6.2,1.6;2.0,0.9;store_trade_simple;Store trade]".. + "tooltip[store_trade_simple;Click here to store this as a new trade. Your ".. + "items will be returned to you and the trade will be shown the way ".. + "the customer can see it.]".. + "tooltip[abort_trade_simple;Abort setting up this new trade.]" + end + + -- view for the customer when actually trading + + -- buy, sell and config items need to be placed somewhere + local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) + -- the players' inventory + local player_inv = player:get_inventory() + -- the NPCs' inventory + local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(trade.n_id)}) + + -- show edit button for the owner + if(yl_speak_up.may_edit_npc(player, trade.n_id)) then + formspec = formspec.. + "button[0.2,1.6;0.8,0.9;edit_trade_simple;Edit]".. + "tooltip[edit_trade_simple;Edit this trade. You can do so only ".. + "if you can edit the NPC as such (i.e. own it).]" + end + + local trade_possible_msg = "Status of trade: Unknown." + -- has the NPC room for the payment? + if(not(npc_inv:room_for_item("npc_main", trade.player_gives))) then + trade_possible_msg = "Sorry. "..minetest.formspec_escape(trade.npc_name).. + " ran out of inventory space.\nThere is no room to store your payment!]" + -- can the player pay? + elseif(not(player_inv:contains_item("main", trade.player_gives))) then + -- both slots will remain empty + trade_possible_msg = "You cannot pay the price." + -- is the slot for the payment empty? + elseif not(trade_inv:is_empty("pay")) then + -- both slots will remain empty + -- (the slot may already contain the right things; we'll find that out later on) + trade_possible_msg = "Please insert the right payment manually." + -- all good so far; move the price stack to the pay slot + else + -- move price item to the price slot + local stack = player_inv:remove_item("main", trade.player_gives) + trade_inv:add_item("pay", stack) + end + + -- make sure the sale slot is empty (we will fill it if the trade is possible) + trade_inv:set_stack("buy", 1, "") + -- after all this: does the payment slot contain the right things? + if(trade_inv:contains_item("pay", trade.player_gives)) then + trade_possible_msg = "Take the offered item(s) in order to buy them." + + -- can the NPC provide his part? + if(not(npc_inv:contains_item("npc_main", trade.npc_gives))) then + trade_possible_msg = "Sorry. "..minetest.formspec_escape(trade.npc_name).. + " ran out of stock.\nPlease come back later." + else + -- put a *copy* of the item(stack) that is to be sold in the sale slot + trade_inv:add_item("buy", trade.npc_gives) + end + end + + if(not(player_inv:room_for_item("main", trade.npc_gives))) then + -- the player has no room for the sold item; give a warning + trade_possible_msg = "Careful! You do not seem to have enough\n".. + "free inventory space to store your purchase.]" + end + + return formspec.. + "label[2.5,0.0;Trading with "..minetest.formspec_escape(trade.npc_name).."]".. + "label[1.5,0.8;You pay:]".. + -- show images of price and what is sold so that the player knows what + -- it costs and what he will get even if the trade is not possible at + -- that moment + "item_image[1,1.5;1,1;"..tostring(trade.player_gives).."]".. + "item_image[4,1.5;1,1;"..tostring(trade.npc_gives).."]".. + -- show the pay slot from the detached player's trade inventory + "list[detached:yl_speak_up_player_"..pname..";pay;2,1.5;1,1;]" .. + -- show the buy slot from the same inventory + "list[detached:yl_speak_up_player_"..pname..";buy;5,1.5;1,1;]" .. + "label[1.5,2.8;"..trade_possible_msg.."]".. + "button[6.2,1.6;2.0,0.9;finished_trading;Finish trading]".. + "tooltip[finished_trading;Click here once you've traded enough with this ".. + "NPC and want to get back to talking.]" +end + + +-- create a detached inventory for the *player* for trading with the npcs +minetest.register_on_joinplayer(function(player, last_login) + local pname = player:get_player_name() + + -- create the detached inventory; + -- the functions for monitoring changes will be important later on + -- only the the player owning this detached inventory may access it + local trade_inv = minetest.create_detached_inventory("yl_speak_up_player_"..tostring(pname), { + -- moving of items between diffrent lists is not allowed + allow_move = function(inv, from_list, from_index, to_list, to_index, count, player) + if(not(player) or player:get_player_name() ~= pname) then + return 0 + end + if(from_list ~= to_list) then + return 0 + end + return count + end, + + -- these all require calling special functions, depending on context + allow_put = function(inv, listname, index, stack, player) + if(not(player) or player:get_player_name() ~= pname) then + return 0 + end + -- the "buy" slot is managed by the NPC; the player only takes from it + if(listname == "buy") then + return 0 + end + return stack:get_count() + end, + allow_take = function(inv, listname, index, stack, player) + if(not(player) or player:get_player_name() ~= pname) then + return 0 + end + -- can the trade be made? + if(listname == "buy") then + return yl_speak_up.can_trade_simple(player, stack:get_count()) + end + return stack:get_count() + end, + on_move = function(inv, from_list, from_index, to_list, to_index, count, player) + end, + on_put = function(inv, listname, index, stack, player) + if(listname == "pay") then + local pname = player:get_player_name() + -- show formspec with updated information (perhaps sale is now possible) + minetest.show_formspec(pname, "yl_speak_up:talk", yl_speak_up.get_fs_trade_simple(player)) + end + end, + on_take = function(inv, listname, index, stack, player) + -- the player may have put something wrong in the payment slot + -- -> show updated formspec + if(listname == "pay") then + local pname = player:get_player_name() + -- show formspec with updated information (perhaps sale is now possible) + minetest.show_formspec(pname, "yl_speak_up:talk", yl_speak_up.get_fs_trade_simple(player)) + elseif(listname == "buy") then + yl_speak_up.do_trade_simple(player, stack:get_count()) + -- information may require an update (NPC might now be out of stock), or + -- the player can do the trade a second time + minetest.show_formspec(pname, "yl_speak_up:talk", yl_speak_up.get_fs_trade_simple(player)) + end + end, + }) + -- prepare the actual inventories + trade_inv:set_size("pay", 1) + trade_inv:set_size("buy", 1) + -- for setting up new simple trades + trade_inv:set_size("setup", 2*1) +end)