---==================================================================================================================--- --- --- --- Original Author(s) : NLTP_ASHES --- --- Edited : N/A --- --- Date : 19/02/2024 --- --- License : Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) --- --- --- --- Script used to manage the trade system, but with Euros/Dollars instead of Roubles. --- --- --- --- By default, if the npc has the "western_goods_trade_eur_usd" dialog, it will be available to the player. --- --- If you want more control over this dialog, you can use the "western_goods_trade_eur_usd_on_dialog" callback. --- --- --- ---=== gamedata/scripts/my_addon_s_script.script ====================================================================--- --- --- --- function on_game_start() --- --- RegisterScriptCallback("western_goods_trade_eur_usd_on_dialog", function(actor,npc,flags) --- --- if npc:section() == "my_npc_section" and some_condition() then --- --- -- The line of dialog will now be hidden --- --- flags.result = false --- --- end --- --- end) --- --- end --- --- --- ---==================================================================================================================--- --- --- --- By default, NPCs using the EUR/USD trade system are shielded (aka unaffected) from trader_autoinject.script. --- --- If you want change that, you can use the "western_goods_trade_eur_usd_on_trader_autoinject" callback. --- --- --- ---=== gamedata/scripts/my_addon_s_script.script ====================================================================--- --- --- --- function on_game_start() --- --- RegisterScriptCallback("western_goods_trade_eur_usd_on_trader_autoinject", function(npc,flags) --- --- if npc:section() == "my_npc_section" and some_condition() then --- --- -- The NPC will now receive items from trader_autoinject.script --- --- flags.result = true --- --- end --- --- end) --- --- end --- --- --- ---==================================================================================================================--- --- --- ---==================================================================================================================--- -- --------------------------------------------------------------------------------------------------------------------- -- Constants, global variables, imported functions and new callbacks -- --------------------------------------------------------------------------------------------------------------------- -- New callbacks AddScriptCallback("western_goods_trade_eur_usd_on_dialog") AddScriptCallback("western_goods_trade_eur_usd_on_trader_autoinject") -- Imported functions local dbg_printf = western_goods_utils.dbg_printf local level_object_by_id = western_goods_utils.level_object_by_id -- Constants local bags_to_tint = { actor_equ = true, actor_bag = true, actor_trade = true, actor_trade_bag = true } local eur_rouble_rate = 68 local usd_rouble_rate = 70 -- Variables local traders_list = {} local traders_names = {} -- --------------------------------------------------------------------------------------------------------------------- -- General Dialog Functions -- --------------------------------------------------------------------------------------------------------------------- --- Function called when the player uses the trade line of dialog. --- @param npc game_object --- @param actor game_object --- @return nil function start_trade(npc, actor) if not this.is_eur_usd_trader(npc) then return end if hide_hud_inventory() then npc:start_trade(actor) end end --- Function called to determine if the trade line of dialog should be shown. --- @param npc game_object --- @param actor game_object --- @param dialog_id string --- @return table function cond_trade_dialog(actor, npc, dialog_id) local flags = { result = true } SendScriptCallback("western_goods_trade_eur_usd_on_dialog",actor,npc,flags) return flags.result end -- --------------------------------------------------------------------------------------------------------------------- -- Trading actions/restrictions -- --------------------------------------------------------------------------------------------------------------------- --- Monkey-patching the monkey-patches so I can prevent the monkey-patches to be executed on the western goods trader. --- Got it? Me neither. In the end, it works. --- @param npc game_object --- @param force_refresh boolean --- @return nil function trade_manager_update(npc, force_refresh) local flags = { result = false } SendScriptCallback("western_goods_trade_eur_usd_on_trader_autoinject",npc,flags) if this.is_eur_usd_trader(npc) and not flags.result then trader_autoinject.TraderUpdate(npc, force_refresh) else western_goods_monkey_patches.trade_manager_update(npc, force_refresh) end end --- Function used to allow/disallow the player from moving items from one container to another when trading with a EUR/USD trader. --- @param flags table --- @param npc_id number --- @param obj game_object --- @param mode string --- @param bag string --- @return nil function check_move_item(flags, npc_id, obj, mode, bag) if mode ~= "trade" then return flags.ret_value end local npc = npc_id and level_object_by_id(npc_id) -- Always allow items in NPC inventory to be moved if utils_item.in_npc_inv(npc, obj) then return end -- If you are talking to a EUR/USD trader and the item isn't tradable, prevent the item from moving if this.is_eur_usd_trader(npc) and not this.is_tradable(obj:section()) then flags.ret_value = false end end --- Function used to color the untradable items in the player inventory. --- @param cell table --- @return fcolor function tint_functor(cell) local ui_instance = ui_inventory.GUI if not ui_instance or not cell then return end local partner = ui_instance:GetPartner() local obj = cell.ID and level_object_by_id(cell.ID) if (not ui_instance.mode == "trade") or (not bags_to_tint[cell.container.ID]) or (not obj) or (not partner) or (utils_item.in_npc_inv(partner, obj)) or (this.is_tradable(obj:section())) or (not this.is_eur_usd_trader(partner)) then return end -- If you're in the trade mode with a EUR/USD trader, and the item isn't tradable, tint it in red return GetARGB(240, 250, 0, 0) end --- Monkey-patching ui_inventory.UIInventory.UpdateCharacter to display custom currencies in the character wallet. --- @param self UIInventory --- @return nil function ui_inventory_UIInventory_UpdateCharacter(self) local partner = self:GetPartner() if (not self.mode == "trade") or (not partner) or (not this.is_eur_usd_trader(partner)) then return end local eur_money = 0 local usd_money = 0 western_goods_utils.inventory_iter(db.actor,function(owner, obj) if this.is_tradable(obj:section()) then local currency,value = this.get_money_value(obj:section()) if currency == "usd" then usd_money = usd_money + value end if currency == "eur" then eur_money = eur_money + value end end end) self.player_money:SetText(tostring(eur_money) .. " EUR | " .. tostring(usd_money) .. " USD") -- NPC self.npc_money:SetText("... EUR | ... USD") end --- Monkey-patching ui_inventory.UIInventory.UpdatePrice to display custom value for items in cart. --- @param self UIInventory --- @param ele_txt ? --- @param ele_btn ? --- @param bag string --- @return nil function ui_inventory_UIInventory_TMode_UpdatePrice(self, ele_txt, ele_btn, bag) local partner = self:GetPartner() if (not self.mode == "trade") or (not partner) or (not this.is_eur_usd_trader(partner)) then return end local cc = self.CC[bag] if (not cc) then return end local tot_cost = 0 for obj_id,idx in pairs(cc.indx_id) do local obj = level_object_by_id(obj_id) local currency,value = this.get_money_value(obj:section()) if currency == "eur" then tot_cost = tot_cost + value * eur_rouble_rate end if currency == "usd" then tot_cost = tot_cost + value * usd_rouble_rate end if currency == "unknown" then tot_cost = tot_cost + cc:GetCellCost(cc.cell[idx]) end end ele_txt:SetText(math.ceil(tot_cost/eur_rouble_rate) .. " EUR or " .. math.ceil(tot_cost/usd_rouble_rate) .. " USD") ele_btn:Enable(math.ceil(tot_cost) > 0) end --- Monkey-patching ui_inventory.UIInventory.TMode_Sell to change the trade logic to a sort of barter system with the trader. --- @param self UIInventory --- @return nil function ui_inventory_UIInventory_TMode_Sell(self) -- If we aren't talking to a EUR/USD trader, process normally local npc = self:GetPartner() if not npc or not this.is_eur_usd_trader(npc) then western_goods_monkey_patches.ui_inventory_t_mode_sell(self) return end make_exchange(self, npc) end --- Monkey-patching ui_inventory.UIInventory.TMode_Buy to change the trade logic to a sort of barter system with the trader. --- @param self UIInventory --- @return nil function ui_inventory_UIInventory_TMode_Buy(self) -- If we aren't talking to a EUR/USD trader, process normally local npc = self:GetPartner() if not npc or not this.is_eur_usd_trader(npc) then western_goods_monkey_patches.ui_inventory_t_mode_buy(self) return end make_exchange(self, npc) end --- Function called when the player clicks Sell/Buy in the trade UI. --- This function makes a few checks, and then proceeds to exchange the money for the items selected. --- @param self UIInventory --- @param npc game_object --- @return nil function make_exchange(self, npc) -- Get sell cells local cc_sell = self.CC["actor_trade"] if (not cc_sell) then return end if size_table(cc_sell.indx_id) <= 0 then self.message_box:InitMessageBox("message_box_ok") self.message_box:SetText(western_goods_utils.get_translation("st_wg_trader_empty_sell")) self.message_box:ShowDialog(true) return end -- Get buy sells local cc_buy = self.CC["npc_trade"] if (not cc_buy) then return end if size_table(cc_buy.indx_id) <= 0 then self.message_box:InitMessageBox("message_box_ok") self.message_box:SetText(western_goods_utils.get_translation("st_wg_trader_empty_buy")) self.message_box:ShowDialog(true) return end -- Calculate full sell price local tot_sell_cost = 0 for obj_id,idx in pairs(cc_sell.indx_id) do local obj = level_object_by_id(obj_id) local currency,value = this.get_money_value(obj:section()) if currency == "eur" then tot_sell_cost = tot_sell_cost + value * eur_rouble_rate end if currency == "usd" then tot_sell_cost = tot_sell_cost + value * usd_rouble_rate end if currency == "unknown" then tot_sell_cost = tot_sell_cost + cc_sell:GetCellCost(cc_sell.cell[idx]) end end -- Calculate full buy price local tot_buy_cost = 0 for obj_id,idx in pairs(cc_buy.indx_id) do local obj = level_object_by_id(obj_id) local currency,value = this.get_money_value(obj:section()) if currency == "eur" then tot_buy_cost = tot_buy_cost + value * eur_rouble_rate end if currency == "usd" then tot_buy_cost = tot_buy_cost + value * usd_rouble_rate end if currency == "unknown" then tot_buy_cost = tot_buy_cost + cc_buy:GetCellCost(cc_buy.cell[idx]) end end -- Round values up tot_sell_cost = math.ceil(tot_sell_cost) tot_buy_cost = math.ceil(tot_buy_cost) -- Don't processed the sell price < buy price if tot_sell_cost < tot_buy_cost then self.message_box:InitMessageBox("message_box_ok") self.message_box:SetText(western_goods_utils.get_translation("st_wg_trader_uneven_value")) self.message_box:ShowDialog(true) return end -- Make the trade dbg_printf("[WG] Western Goods Trader | Traded :") for id,_ in pairs(cc_sell.indx_id) do local obj = level_object_by_id(id) dbg_printf("[WG] - [%s] %s", obj:id(), obj:section()) self:On_Item_Exchange(db.actor, npc, obj) end dbg_printf("[WG] Western Goods Trader | In exchange for :") for id,_ in pairs(cc_buy.indx_id) do local obj = level_object_by_id(id) dbg_printf("[WG] - [%s] %s", obj:id(), obj:section()) self:On_Item_Exchange(npc, db.actor, obj) end -- Update UI self:UpdateInfo(true) -- Clean trader inventory CreateTimeEvent("delete_traded_items",npc:id(),0.01,function() dbg_printf("[WG] Western Goods Trader | Cleaning traded items :") western_goods_utils.inventory_iter(npc,function(owner, obj) if this.is_tradable(obj:section()) then dbg_printf("[WG] - [%s] %s", obj:id(), obj:section()) alife_release_id(obj:id()) end end) return true end) end -- --------------------------------------------------------------------------------------------------------------------- -- Barter actions/restrictions -- --------------------------------------------------------------------------------------------------------------------- --- Monkey-patching barter_ui.GUI_on_show to prevent the Barter button from showing if the NPC is a EUR/USD trader. --- @param name string --- @param path string --- @return nil function barter_ui_gui_on_show(name, path) local ui_instance = ui_inventory.GUI if name ~= "UIInventory" or (not ui_instance) then return end local partner = ui_instance:GetPartner() if partner and this.is_eur_usd_trader(partner) then return end western_goods_monkey_patches.barter_ui_gui_on_show(name, path) end -- --------------------------------------------------------------------------------------------------------------------- -- Callbacks registration -- --------------------------------------------------------------------------------------------------------------------- --- Function used to register callbacks. --- @return nil function on_game_start() rax_icon_tint.register("wg_tint_functor", tint_functor) RegisterScriptCallback("ActorMenu_on_item_before_move", check_move_item) RegisterScriptCallback("on_game_load", collect_traders_list) end -- --------------------------------------------------------------------------------------------------------------------- -- Service Functions -- --------------------------------------------------------------------------------------------------------------------- --- Function used to know if an NPC is a EUR/USD trader or not. --- @param section string --- @return boolean function is_eur_usd_trader(obj) -- Check cache if traders_names[obj:name()] ~= nil then return traders_names[obj:name()] end -- Query and cache character_id local character_id = western_goods_utils.get_character_id(obj) traders_names[obj:name()] = traders_list[character_id] or false return traders_names[obj:name()] end --- Function used to know if an object is tradable with the western goods trader. --- @param obj game_object --- @return boolean function is_tradable(sec) for section,data in pairs(western_goods_core.western_goods_items) do -- Is tradable if item is in actor inventory, and is of type money if sec == section and data.type == "money" then return true end end -- All other items are forbidden for trade return false end --- Function used to get the monetary value of a section, if this section is a WG money bill. --- @param sec string --- @return string,number function get_money_value(sec) if string.find(sec, "wg_euros_") then local value = string.sub(sec,10,#sec) or "0" return "eur", tonumber(value) end if string.find(sec, "wg_dollars_") then local value = string.sub(sec,12,#sec) or "0" return "usd", tonumber(value) end return "unknown", 0 end --- Function used to parse through all character description files. --- This function will find and save (in 'traders_list' table) all character IDs that have the EUR/USD trade dialog. --- @return nil function collect_traders_list() dbg_printf("[WG] Trade EUR/USD | Started trader collection...") local profiler = profile_timer() profiler:start() local file_list = str_explode(ini_sys:r_string("profiles", "specific_characters_files"),",") for _,file_name in pairs(file_list) do local xml = CScriptXmlInit() xml:ParseDirFile([[gameplay]],file_name..".xml") local char_count = xml:GetNodesNum("", 0, "specific_character") for i=0, char_count-1 do local char_id = xml:ReadAttribute("specific_character", i, "id") xml:NavigateToNode("specific_character", i) local diag_count = xml:GetNodesNum("specific_character", i, "actor_dialog") for y=0, diag_count-1 do if xml:ReadValue("actor_dialog", y) == "western_goods_trade_eur_usd" then traders_list[char_id] = true dbg_printf("[WG] Trade EUR/USD | Found EUR/USD dialog for '%s'", char_id) end end xml:NavigateToRoot() end xml = nil end profiler:stop() dbg_printf("[WG] Trade EUR/USD | Finished trader collection in %sms :\n%s", profiler:time()/1000, utils_data.print_table(traders_list, false, true)) end