---==================================================================================================================--- --- --- --- Original Author(s) : NLTP_ASHES --- --- Edited : N/A --- --- Date : 02/06/2023 --- --- License : Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) --- --- --- --- Script used to manage the Mercenary flea market system. --- --- --- --- You can add new sections to the item pool using a file : --- --- --- ---=== gamedata/configs/plugins/western_goods/flea_market/flea_.ltx ================--- --- --- --- [some_unique_name_of_your_choice] --- --- some_item_section --- --- some_other_item_section --- --- --- ---==================================================================================================================--- --- --- --- Or you can use the callback to add new items : --- --- --- ---=== gamedata/scripts/my_addon_s_script.script ====================================================================--- --- --- --- function on_game_start() --- --- RegisterScriptCallback("western_goods_flea_on_collect_sections", function(flags) --- --- local items_to_add = { --- --- "some_item_section", --- --- "some_other_item_section", --- --- } --- --- for k,v in pairs(items_to_add) do --- --- table.insert(flags.result,v) --- --- end --- --- end) --- --- end --- --- --- ---==================================================================================================================--- --- --- --- By default, if the npc has the "western_goods_flea_offers" dialog, it will be available to the player. --- --- If you want to have more control over this dialog, you can use the "western_goods_flea_on_dialog" callback. --- --- --- ---=== gamedata/scripts/my_addon_s_script.script ====================================================================--- --- --- --- function on_game_start() --- --- RegisterScriptCallback("western_goods_flea_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 --- --- --- ---==================================================================================================================--- --- --- ---==================================================================================================================--- -- --------------------------------------------------------------------------------------------------------------------- -- Constants, global variables, imported functions and new callbacks -- --------------------------------------------------------------------------------------------------------------------- -- New callbacks AddScriptCallback("western_goods_flea_on_dialog") AddScriptCallback("western_goods_flea_on_collect_sections") -- Imported functions local dbg_printf = western_goods_utils.dbg_printf -- Constants local ini_sections = ini_file([[plugins\western_goods\flea_market\importer.ltx]]) local offers_per_npc = 5 local offers_exp_freq = 3600000 local min_item_count = 2 local max_item_count = 10 local min_discount = 5 local max_discount = 50 -- Variables local current_offer = 1 local offers = {} -- --------------------------------------------------------------------------------------------------------------------- -- Core Functions -- --------------------------------------------------------------------------------------------------------------------- --- Function used to generate new offers --- @param count number --- @return table function generate_offers(count) local new_offers = {} local viable_sections = this.get_viable_sections() dbg_printf("[WG] Flea Market | Generating %s offers...", count) for i = 1, count do local price = 0 local sections = {} for y = 1, math.random(min_item_count,max_item_count) do local section = viable_sections[math.random(1,size_table(viable_sections))] price = price + western_goods_utils.get_section_price(section) table.insert(sections,section) end local sale = math.random(min_discount,max_discount) local cost = math.ceil(price * ((100 - sale) / 100)) table.insert(new_offers,{ sections=sections, sale=sale, cost=cost }) end dbg_printf("[WG] Flea Market | Generated %s offers :\n%s", count, utils_data.print_table(new_offers, false, true)) return new_offers end --- Function used to purchase an offer. Check if player has enough money before calling! --- @param offer table --- @param npc game_object --- @return nil function purchase_offer(offer, npc) for _,sec in pairs(offer.sections) do local uses = IsItem("multiuse", sec) itms_manager.relocate_item_to_actor(db.actor, npc, sec, uses or 1) end dialogs.relocate_money(npc, offer.cost, "out") dbg_printf("[WG] Flea Market | Purchased offer (qte:%s)(price:%s)(sale:%s)", size_table(offer.sections), offer.cost, offer.sale) table.remove(offers[npc:id()].set,current_offer) end -- --------------------------------------------------------------------------------------------------------------------- -- General Dialog Functions -- --------------------------------------------------------------------------------------------------------------------- --- Function called by dialogs, used to know if an NPC show display the western_goods_flea_offers dialog. --- Used mainly to have per NPC conditions, while all sharing the same line of dialog. --- @return nil function cond_offers_dialog(actor, npc, dialog_id) local flags = { result = true } SendScriptCallback("western_goods_flea_on_dialog",actor,npc,flags) return flags.result end --- Function called by dialogs, used to initialize offers for an NPC. --- @param actor game_object --- @param npc game_object --- @return nil function init_offers(actor, npc) dbg_printf("[WG] Flea Market | Initializing for %s(%s)...", npc:section(), npc:id()) if not offers[npc:id()] then offers[npc:id()] = { set = this.generate_offers(offers_per_npc), exp = time_global() + offers_exp_freq, } end if not offers[npc:id()].exp or offers[npc:id()].exp <= time_global() then offers[npc:id()] = { set = this.generate_offers(offers_per_npc), exp = time_global() + offers_exp_freq, } end end --- Function called by dialogs, used to initialize a purchase. --- @param actor game_object --- @param npc game_object --- @return nil function init_purchase(actor, npc) current_offer = 1 end --- Function called by dialogs, used to determine if the 'current_offer' pointer has an offer. --- @param actor game_object --- @param npc game_object --- @return boolean function exists_current_offer(actor, npc) return offers[npc:id()].set[current_offer] ~= nil end --- Function called by dialogs, used to determine if the current offer has expired or not. --- @param actor game_object --- @param npc game_object --- @return boolean function not_expired_current_offer(actor, npc) return offers[npc:id()].exp > time_global() end --- Function called by dialogs, used to determine if the player has enough money for the current offer. --- @param actor game_object --- @param npc game_object --- @return boolean function can_afford_offer(actor, npc) local offer = offers[npc:id()].set[current_offer] if offer then return db.actor:money() >= offer.cost end return false end --- Function called by dialogs, used to list all available offers for a given NPC. --- @param actor game_object --- @param npc game_object --- @return nil function list_offers(actor, npc) for _,offer in ipairs(offers[npc:id()].set) do local expiration = offers[npc:id()] and offers[npc:id()].exp local time_left = expiration and math.floor((expiration - time_global()) / 60000) or "unknown" local caption = western_goods_utils.get_translation("st_wg_flea_offer_caption", this.get_expiration_color(time_left), time_left) local text = western_goods_utils.get_translation("st_wg_flea_offer_text", this.get_items_text(offer.sections), size_table(offer.sections), this.get_discount_color(offer.sale), offer.sale, offer.cost) db.actor:give_talk_message2(caption, text, "ui_inGame2_Osobiy_zakaz", "iconed_answer_item") end end -- --------------------------------------------------------------------------------------------------------------------- -- Show Current Item Dialog Functions -- --------------------------------------------------------------------------------------------------------------------- --- Function called by dialogs, used to return the dialog text when showing an offer. --- @param actor game_object --- @param npc game_object --- @return string function st_show_current_item(npc, dialog_id) local offer = offers[npc:id()].set[current_offer] local exp = offers[npc:id()].exp if time_global() >= exp then return western_goods_utils.get_translation("st_wg_flea_expired") elseif offer then return western_goods_utils.get_translation("st_wg_flea_offer", offer.sale) .. " " .. western_goods_utils.get_translation("st_wg_flea_offer_end_"..tostring(math.random(0,4))) else return western_goods_utils.get_translation("st_wg_flea_no_offer") end end --- Function called by dialogs, used to show the current offer. --- @param npc game_object --- @param actor game_object --- @return nil function show_current_offer(npc, actor) local offer = offers[npc:id()].set[current_offer] local exp = offers[npc:id()].exp if offer and time_global() < exp then local expiration = offers[npc:id()] and offers[npc:id()].exp local time_left = expiration and math.floor((expiration - time_global()) / 60000 * level.get_time_factor()) local hours_left = time_left and math.floor(time_left / 60) local mins_left = time_left and math.ceil(time_left % 60) local caption = western_goods_utils.get_translation("st_wg_flea_offer_caption", this.get_expiration_color(time_left), hours_left, mins_left) local text = western_goods_utils.get_translation("st_wg_flea_offer_text", this.get_items_text(offer.sections), size_table(offer.sections), this.get_discount_color(offer.sale), offer.sale, offer.cost) dbg_printf("[WG] Flea Market | Showing offer (qte:%s)(sale:%s)(price:%s)", size_table(offer.sections), offer.sale, offer.cost) db.actor:give_talk_message2(caption, text, "ui_inGame2_Osobiy_zakaz", "iconed_answer_item") end end -- --------------------------------------------------------------------------------------------------------------------- -- Buy Current Item Dialog Functions -- --------------------------------------------------------------------------------------------------------------------- --- Function called by dialogs, used to return the dialog text when purchasing an offer. --- @param actor game_object --- @param npc game_object --- @return string function st_buy_current_item(npc, dialog_id) local offer = offers[npc:id()].set[current_offer] local exp = offers[npc:id()].exp if offer and time_global() < exp then return western_goods_utils.get_translation("st_wg_flea_offer_buy", offer.cost) .. " " .. western_goods_utils.get_translation("st_wg_flea_offer_buy_end_"..tostring(math.random(0,4))) end end --- Function called by dialogs, used to execute a purchase for the current offer. --- @param actor game_object --- @param npc game_object --- @return nil function buy_current_item(actor, npc) this.purchase_offer(offers[npc:id()].set[current_offer], npc) end -- --------------------------------------------------------------------------------------------------------------------- -- Next Item Dialog Functions -- --------------------------------------------------------------------------------------------------------------------- --- Function called by dialogs, used to return the dialog text when skipping to the next offer. --- @param actor game_object --- @param npc game_object --- @return string function st_next_item(npc, dialog_id) local offer = offers[npc:id()].set[current_offer] if offer and db.actor:money() >= offer.cost then return western_goods_utils.get_translation("st_wg_flea_next_offer") else return western_goods_utils.get_translation("st_wg_flea_poor_next_offer_"..tostring(math.random(0,2))) end end --- Function called by dialogs, used to skip to the next offer. --- @param actor game_object --- @param npc game_object --- @return nil function next_item(actor, npc) dbg_printf("[WG] Flea Market | Next offer...") current_offer = current_offer + 1 end -- --------------------------------------------------------------------------------------------------------------------- -- Callbacks registration -- --------------------------------------------------------------------------------------------------------------------- --- Function used to register callbacks. --- @return nil function on_game_start() RegisterScriptCallback("save_state", save_state) RegisterScriptCallback("load_state", load_state) end -- --------------------------------------------------------------------------------------------------------------------- -- Data persistence -- --------------------------------------------------------------------------------------------------------------------- --- Function used to store information in the save file. --- @param m_data table --- @return nil function save_state(m_data) -- Prepare save table local offers_save = {} -- Make copy of offers table copy_table(offers_save, offers) -- Pre-process offers table for _,npc_offers in pairs(offers_save) do npc_offers.exp = npc_offers.exp - time_global() end -- Save offers table m_data.wg_flea_offers = offers_save -- Debug prints dbg_printf("[WG] Flea Market | Saved offers...\n%s",utils_data.print_table(offers_save, false, true)) end --- Function used to load information stored in the save file. --- @param m_data table --- @return nil function load_state(m_data) -- Retrieve save table local offers_save = m_data.wg_flea_offers or {} -- Post-process offers table for npc_id,data in pairs(offers_save) do data.exp = data.exp + time_global() end -- Restore offers table copy_table(offers, offers_save) -- Debug prints dbg_printf("[WG] Flea Market | Loaded offers...\n%s",utils_data.print_table(offers, false, true)) end -- --------------------------------------------------------------------------------------------------------------------- -- Service Functions -- --------------------------------------------------------------------------------------------------------------------- --- Function used to a copy of the offers table from another script. --- You better not use the LUA Unlocalizer to sneak into my pipes ! --- @param npc_id number --- @return table function get_all_offers(npc_id) local result = {} if npc_id ~= nil then copy_table(result, offers) else copy_table(result, offers[npc_id]) end return result end --- Function used to get a table listing all possible sections that can spawn in the Flea Market system. --- @return table function get_viable_sections() local viable_sections = {} local file_count, script_count = 0, 0 -- Collect viable sections via files ini_sections:section_for_each(function(section) local sec_names = utils_data.collect_section(ini_sections, section, true) for sec_name,_ in pairs(sec_names) do file_count = file_count + 1 if ini_sys:section_exist(sec_name) then table.insert(viable_sections,sec_name) else printf("![WG] ERROR | Flea Market | Section %s (module : %s) cannot be collected, as it does not exist!", sec_name, section) end end end) -- Collect viable sections via script local flags = { result = { } } SendScriptCallback("western_goods_flea_on_collect_sections",flags) for _,sec_name in pairs(flags.result) do script_count = script_count + 1 if ini_sys:section_exist(sec_name) then table.insert(viable_sections,sec_name) else printf("![WG] ERROR | Flea Market | Section %s (from script) cannot be collected, as it does not exist!", sec_name) end end dbg_printf("[WG] Flea Market | Collected %s viable sections from files", file_count) dbg_printf("[WG] Flea Market | Collected %s viable sections from scripts", script_count) dbg_printf("[WG] Flea Market | Collected total of %s viable sections", file_count+script_count) return viable_sections end --- Function used to get color code for a given percentage. --- Color code : x>=35% : green / 35%>x>=15% : yellow / 15%>x : red --- @param percentage number --- @return string function get_discount_color(percentage) if percentage >= 35 then return "%c[green]" elseif percentage >= 15 then return "%c[yellow]" else return "%c[red]" end end --- Function used to get color code for a given expiration time. --- Color code : x>=10m : default / 10m>x>=5m : yellow / 5m>x : red --- @param time_left number --- @return string function get_expiration_color(time_left) if time_left >= 60 then return "%c[default]" elseif time_left >= 10 then return "%c[yellow]" else return "%c[red]" end end --- Function used to get a string list of the items contained in a given offer. --- @param sections table --- @return string function get_items_text(sections) local items_text = [[\n]] for _,sec in pairs(sections) do items_text = items_text .. [[ - ]] .. ui_item.get_sec_name(sec) .. [[\n]] end return items_text end