Advertisement
Karlan

High pressure script

Mar 28th, 2017
156
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 84.92 KB | None | 0 0
  1. --[[ ----------------------------------------------------------------------------------------------
  2.  File       : manager_trade.script (base on trade_manager)
  3.  Description: Trade handler
  4.  Copyright  : 2017 © Prosectors Project
  5.  Author     : Karlan
  6.  Version    : 1.6
  7. --]] ----------------------------------------------------------------------------------------------
  8. --// Karlan->ALL: при пересоздании торговца его таймер обнулится, можно конечно все пошить на сиды, но тогда обязуем каждому торговцу делать сид, что является издевательством над такими ленивыми как я, так что если чего, то сбросится таймер, и, затем, соответственно реинтится торговля, только не забывайте затирать старую торговлю при удалении непися ручными способами
  9. --// TODO: перевязать все с коллбек схемой, скорее всего сюда полностью интегрировать, а схему снести, у кого звуки в теме существуют те и будут базарить, остальные и так молчуны
  10. --// TODO: доделать фишку с артефактом
  11. --//-----------------------------------------------------------------
  12. --// Flags for enable output of the debug information
  13. --//-----------------------------------------------------------------
  14. _DEBUG_ = false --// file under debugging?
  15. _DEBUG_GSC_ = _DEBUG_ and false --// only for scripts by GSC
  16. --//-----------------------------------------------------------------
  17. --//-----------------------------------------------------------------------------------------------
  18. --// Variables
  19. --//-----------------------------------------------------------------------------------------------
  20. local loaded = false
  21. --//--------------------
  22. _OLD_TRADE_ = false --// для совместимости
  23. --// SYSTEM
  24. local ts = {}
  25. local bargain_link = nil
  26. local clsid_to_str = nil
  27. local clsid_invert = nil
  28. local msg_data = { --// TODO: написать тексты
  29.     ['escape_trader'] = {name = 'Сидорович:', text = 'Ассортимент обновлен.', icon = 'trader'},
  30.     ['esc_bridge_soldier5'] = {name = 'Кузнецов:', text = 'Ассортимент обновлен.', icon = 'kuznetsov'},
  31.     ['bar_dolg_petrenko'] = {name = 'Петренко:', text = 'Ассортимент обновлен.', icon = 'petrenko'},
  32.     ['bar_barman'] = {name = 'Бармен:', text = 'Ассортимент обновлен.', icon = 'barman'},
  33.     ['yantar_ecolog_general'] = {name = 'Сахаров:', text = 'Ассортимент обновлен.', icon = 'saharov'},
  34.     ['mil_freedom_member0021'] = {name = 'Скряга:', text = 'Ассортимент обновлен.', icon = 'miser'},
  35. }
  36. local trade_guid = '{729A6836-E6A8-93CE-ABF4-8E4A470A3BE2}'
  37. --// ARTS (движковая фишка, которая была скорее всего вырезана из-за изменения функционала заданий) --// TODO: пока только веселые зарисовки
  38. local need_art = nil
  39. --// GOODWILL
  40. local goodwill_excludes = {
  41.     ['trader'] = true,
  42.     ['ecolog'] = true,
  43. }
  44. local money_get, money_put, goodwill_coeff, art_count = nil, nil, nil, 0
  45. --// ECONOMICS
  46. local nominal_prices = {} --// стораж наращения
  47. local inflation_rate = {} --// стораж инфляции
  48. local entry_trade = false
  49. local deficit_buff = {}
  50. local deficit_factor = nil
  51. --// SNAKEFISH
  52. local snakefish_items = {} --// шкуроход
  53. local snakefish_reset = false
  54. local sf_ammo_id = trade_guid..'_'..'sf_ammo_id'
  55. local reason_msg = nil
  56. local reason_data = {
  57.     npc_sell_ammo_reject    = '$partner не может продать последние боеприпасы',
  58.     npc_buy_ammo_reject     = '$partner уже имеет достаточное количество боеприпасов данного типа',
  59.     npc_buy_ammoi_reject     = '$partner не интересуется боеприпасами данного типа',
  60.     npc_sell_gren_reject    = '$partner не может продать последние гранаты',
  61.     npc_buy_gren_reject     = '$partner уже имеет достаточное количество гранат данного типа',
  62.     npc_sell_eat_reject     = '$partner не может продать последнюю еду',
  63.     npc_buy_bottle_reject     = '$partner уже имеет достаточное количество напитков данного типа',
  64.     npc_sell_bottle_reject     = '$partner не может продать последние напитки',
  65.     npc_buy_eat_reject     = '$partner уже имеет достаточное количество еды данного типа',
  66.     npc_buy_eatc_reject     = '$partner не купит испорченную еду',
  67.     npc_sell_eatb_reject     = '$partner не продаст лучшую еду',
  68.     npc_sell_med_reject     = '$partner не может продать последние медикаменты',
  69.     npc_buy_med_reject     = '$partner уже имеет достаточное количество медикаментов данного типа',
  70.     npc_sell_best_med_reject     = '$partner не продаст лучшие медикаменты',
  71.     npc_buy_part_reject     = '$partner не купит испорченную часть',
  72.     npc_sell_outfit_reject     = '$partner не продаст вам лучший бронежилет',
  73.     npc_buy_outfit_reject     = '$partner не купит костюм хуже текущего',
  74.     npc_buy_weapon_reject     = '$partner не заинтересован в этом оружии',
  75.     npc_sell_weapon_reject     = '$partner не продаст вам необходимое оружие',
  76.     npc_sell_torch_reject     = '$partner не продаст вам единственный фонарь',
  77.     npc_sell_binocle_reject     = '$partner не продаст вам единственный бинокль',
  78. }
  79. --// CUSTOMIZABLE
  80. local MAX_NPC_SLOT_AMMO = 5 --// необходимый боезапас НПС для оружия в слотах
  81. local MAX_NPC_MUST_AMMO = 2 --// необходимый боезапас НПС для каждого ствола из списка необходимых
  82. local MAX_NPC_EAT = 2 --// необходимый запас еды/напитков одного типа (+1 (учитывается ниже), так как движок сжирает один предмет и не дает им торговать)
  83. local MAX_NPC_GRENADE = 2 --// необходимый запас гранат
  84. local MAX_NPC_MEDKIT = 2 --// необходимый запас аптечек одного типа, бинтов
  85. local MAX_NPC_ANTIRAD = 2 --// необходимый запас антирадов
  86. --//-----------------------------------------------------------------------------------------------
  87. --// Local functions
  88. --//-----------------------------------------------------------------------------------------------
  89. local function get_trader_goodwill(npc)
  90.     if goodwill_excludes[npc:character_community()] then
  91.         return get_npc_goodwill(npc, actor)/1000 --// Karlan->Team: делитель обязательно должен быть равен положительному(!) барьеру благосклонности, потом сделаю вычитку настройки
  92.     end
  93.     return (get_npc_goodwill(npc, actor)/1000+get_factions_community_goodwill(npc:character_community(), actor_id)/5000)
  94. end
  95. --//------------------------------------------------------------
  96. local function fill_snakefish_items(npc)
  97.     npc:iterate_inventory(function(_, item) --// Karlan->Karlan: foreach здесь использовать не надо, у нас и так прокручивается foreach каждый раз, здесь нам нужно только составить список для read режима
  98.         local section = item:section()
  99.         if not snakefish_items[section] then
  100.             snakefish_items[section] = 1
  101.         else
  102.             snakefish_items[section] = snakefish_items[section]+1
  103.         end
  104.     end)
  105.     snakefish_items[sf_ammo_id] = {}
  106.     snakefish_items.lock_wpn = {}
  107.     for k,v in pairs(npc:get_wm():get_best_by_types()) do
  108.         snakefish_items.lock_wpn[v.id] = true
  109.     end
  110. end
  111. local function inc_snakefish_items(section, count)
  112.     count = count or 1
  113.     if not snakefish_items[section] then
  114.         snakefish_items[section] = 0
  115.     end
  116.     snakefish_items[section] = snakefish_items[section] + count
  117. end
  118. local function dec_snakefish_items(section, count)
  119.     count = count or 1
  120.     if not snakefish_items[section] then return end
  121.     snakefish_items[section] = snakefish_items[section] - count
  122.     if snakefish_items[section] == 0 then snakefish_items[section] = nil end
  123. end
  124. local function get_snakefish_items(section)
  125.     local count = snakefish_items[section]
  126.     log('get_snakefish_items: count = %s', snakefish_items[section])
  127.     table.print(snakefish_items, 'snakefish_items')
  128.     if is_number(count) then return count end
  129.     return -1
  130. end
  131. local function clear_snakefish_items()
  132.     snakefish_items = {}
  133. end
  134. local function reset_snakefish_items(npc) --// Karlan: со стороны оптимизации эта функция проигрывает fill_snakefish_items, но поскольку релаьными итемами не оперируем, а затрата на итерацию одна и таже так что можно забить ,эта работает в режиме ресета, вышеуказанная в режиме изменения
  135.     if npc:id() == actor_id then return end
  136.     if not snakefish_reset then return end
  137.     if is_trader_mode(npc) then return end
  138.     log('call reset items for [%s]', npc)
  139.     clear_snakefish_items()
  140.     fill_snakefish_items(npc)
  141.     snakefish_reset = false
  142.     table.print(snakefish_items, 'snakefish_items')
  143. end
  144. --//----------------------------
  145. --// [вспомогательный функционал]:>
  146. local function get_bool_sell_buy(mode, allow_cfg, section, max_count, reason_buy, reason_sell, update)
  147.     local count = get_snakefish_items(section)
  148.     -- logc('/[T02] get_bool_sell_buy: [%s] = count<%s>, max_count<%s> {mode = %s}', section, count, max_count, mode)
  149.     if count < 0 then
  150.         if allow_cfg then
  151.             if mode == 3 and not update then inc_snakefish_items(section) end
  152.             if mode == 2 and not update then dec_snakefish_items(section) end
  153.             return true
  154.         end
  155.         return false
  156.     end
  157.     if count == max_count then
  158.         reason_msg = (mode == 3 and reason_buy) or (mode == 2 and reason_sell)
  159.         return false
  160.     elseif count > max_count then
  161.         if mode == 2 then
  162.             if not update then dec_snakefish_items(section) end
  163.             return true
  164.         elseif mode == 3 then
  165.             reason_msg = reason_buy
  166.             return false
  167.         end
  168.     elseif count < max_count then
  169.         if mode == 2 then
  170.             reason_msg = reason_sell
  171.             return false
  172.         elseif mode == 3 then
  173.             if not update then inc_snakefish_items(section) end
  174.             return true
  175.         end
  176.     end
  177. end
  178. local function calculate_sf_ammo(obj, slot, section, mode, allow_cfg, update, max_ammo)
  179.     max_ammo = max_ammo or MAX_NPC_SLOT_AMMO
  180.     local wpn = is_string(slot) and slot or obj:item_in_slot(slot)
  181.     if not wpn then return end
  182.     local st = snakefish_items[sf_ammo_id]
  183.     if not st[slot] then
  184.         local buff = parse_names(cfg_get_string(sini, is_string(wpn) and wpn or wpn:section(), 'ammo_class'))
  185.         st[slot] = {}
  186.         for _,v in ipairs(buff) do
  187.             st[slot][v] = true
  188.         end
  189.     end
  190.     if st[slot][section] then
  191.         return get_bool_sell_buy(mode, allow_cfg, section, max_ammo, reason_data.npc_buy_ammo_reject, reason_data.npc_sell_ammo_reject, update)
  192.     end
  193. end
  194. local function calculate_items_count(tab)
  195.     local count = 0
  196.     for i=1,#tab do
  197.         local rc = get_snakefish_items(tab[i])
  198.         if math.is_positive(rc) then
  199.             count = count+rc
  200.         end
  201.     end
  202.     log('calculate_items_count tab = %s, result = %s', tab, count)
  203.     return count
  204. end
  205. --// [вспомогательный функционал]:<
  206. local function set_snakefish(obj, item, allow_cfg, mode, update) --// (re)set/get
  207. --// @ mode == 1 --// добавляем в лист?
  208. --// @ mode == 2 --// разрешаем агенту продавать?
  209. --// @ mode == 3 --// разрешаем агенту покупать?
  210.     local section = item:section()
  211.     if mode == 1 then
  212.         if obj:id() ~= actor_id then --// возможность регулировки актора добавляем, но пока не будем использовать, не надо пока
  213.             --// добавляем в лист агенту? todo
  214.         end
  215.     end
  216.     if mode == 2 or mode == 3 then
  217.         if item:is_ammo() then
  218.             log('calculate ammo <%s>', item)
  219.             local result = nil
  220.             if obj:get_wm() and obj:get_wm():get_weapon() then
  221.                 result = calculate_sf_ammo(obj, obj:get_wm():get_weapon():section(), section, mode, allow_cfg, update)
  222.                 if is_boolean(result) then return result end --/>
  223.             end
  224.             result = calculate_sf_ammo(obj, PISTOL_SLOT, section, mode, allow_cfg, update)
  225.             if is_boolean(result) then return result end --/>
  226.             result = calculate_sf_ammo(obj, RIFLE_SLOT, section, mode, allow_cfg, update)
  227.             if is_boolean(result) then return result end --/>            
  228.             for _,v in pairs(obj:get_wm():get_best_by_types()) do
  229.                 result = calculate_sf_ammo(obj, v.sec, section, mode, allow_cfg, update, MAX_NPC_MUST_AMMO)
  230.                 if is_boolean(result) then return result end --/>
  231.             end
  232.             reason_msg = reason_data.npc_buy_ammoi_reject
  233.             return false --// не подходящими не торгуем вообще
  234.         end
  235.         if item:is_medkit() then
  236.             --// есть тема не продавать только оставшиеся лучшие аптечки, но этот вариант мне не очень сильно понравился, сделал оставление кажого типа
  237.             if mode == 2 and ((section:find('sci') and (get_snakefish_items('medkit') > MAX_NPC_MEDKIT or get_snakefish_items('medkit_army') > MAX_NPC_MEDKIT)) or (section:find('army') and get_snakefish_items('medkit') > MAX_NPC_MEDKIT)) then
  238.                 reason_msg = reason_data.npc_sell_best_med_reject
  239.                 return false
  240.             end
  241.             return get_bool_sell_buy(mode, allow_cfg, section, MAX_NPC_MEDKIT, reason_data.npc_buy_med_reject, reason_data.npc_sell_med_reject, update)
  242.         end
  243.         if item:is_antirad() then
  244.             return get_bool_sell_buy(mode, allow_cfg, section, MAX_NPC_ANTIRAD, reason_data.npc_buy_med_reject, reason_data.npc_sell_med_reject, update)
  245.         end
  246.         if item:is_grenade() then
  247.             return get_bool_sell_buy(mode, allow_cfg, section, MAX_NPC_GRENADE, reason_data.npc_buy_gren_reject, reason_data.npc_sell_gren_reject, update)
  248.         end
  249.         if item:is_eatable_item() then
  250.             local count = item:is_bottle_item() and calculate_items_count{'energy_drink', 'vodka'} or calculate_items_count{'bread', 'kolbasa', 'conserva'}
  251.             local reason_buy, reason_sell = reason_data['npc_buy_'..(item:is_bottle_item() and 'bottle' or 'eat')..'_reject'], reason_data['npc_sell_'..(item:is_bottle_item() and 'bottle' or 'eat')..'_reject']
  252.             if mode == 3 and item:condition() < 0.35 then
  253.                 reason_msg = reason_data.npc_buy_eatc_reject
  254.                 return false
  255.             end
  256.             if mode == 2 then
  257.                 if count < MAX_NPC_EAT+1 then
  258.                     return get_bool_sell_buy(mode, allow_cfg, section, MAX_NPC_EAT+1, reason_buy, reason_sell, update)
  259.                 end
  260.                 if section == 'conserva' and (math.is_positive(get_snakefish_items('bread')) or math.is_positive(get_snakefish_items('kolbasa'))) then
  261.                     reason_msg = reason_data.npc_sell_eatb_reject
  262.                     return false
  263.                 end
  264.                 if section == 'bread' or section == 'kolbasa' then
  265.                     if get_snakefish_items('conserva') > 0 then
  266.                         return allow_cfg
  267.                     end
  268.                 end
  269.             end
  270.             return get_bool_sell_buy(mode, allow_cfg, section, MAX_NPC_EAT+1, reason_buy, reason_sell, update)
  271.         end
  272.         if mode == 3 and section:find('^mutant_') and item:condition() < 0.5 and item:is_inventory_item() then --// Karlan: ага, и тут сказка об атачабельности
  273.             reason_msg = reason_data.npc_buy_part_reject
  274.             return false
  275.         end
  276.         if item:is_outfit() then
  277.             local visual = string.split(obj:get_visual_name(), '\\')
  278.             if manager_death.drop_outfit_base[visual[3]] then
  279.                 local cost = math.huge --// Karlan: затычка, потому что не все броники еще записали в массивы
  280.                 if sini:section_exist(manager_death.drop_outfit_base[visual[3]][1]) then
  281.                     cost = sini:r_float(manager_death.drop_outfit_base[visual[3]][1], 'cost')
  282.                 end
  283.                 if mode == 3 then
  284.                     if item:cost()*item:condition() > cost and item:condition() > 0.75 then
  285.                         return allow_cfg
  286.                     end
  287.                     reason_msg = reason_data.npc_buy_outfit_reject
  288.                     return false
  289.                 end
  290.                 if mode == 2 and (item:cost() > cost and get_snakefish_items(item:section()) < 2) then
  291.                     reason_msg = reason_data.npc_sell_outfit_reject --// Karlan->Karlan: возможно тут стоит запрещать только самый лучший к продаже, а не каждый лучше текущего?
  292.                     return false
  293.                 end
  294.             end
  295.         end
  296.         if IsWeapon(item) then
  297.             if mode == 3 and not obj:get_wm():can_take(item) then
  298.                 reason_msg = reason_data.npc_buy_weapon_reject
  299.                 return false
  300.             end
  301.             if mode == 2 and snakefish_items.lock_wpn[item:id()] then
  302.                 reason_msg = reason_data.npc_sell_weapon_reject
  303.                 return false
  304.             end
  305.         end
  306.         if section == 'device_torch' then
  307.             if mode == 3 and obj:object('device_torch') then
  308.                 return false
  309.             end
  310.             if mode == 2 and get_snakefish_items('device_torch') < 2 then
  311.                 reason_msg = reason_data.npc_sell_torch_reject
  312.                 return false
  313.             end
  314.         end        
  315.         if section == 'wpn_binoc' then
  316.             if mode == 3 and obj:object('wpn_binoc') then
  317.                 return false
  318.             end
  319.             if mode == 2 and get_snakefish_items('wpn_binoc') < 2 then
  320.                 reason_msg = reason_data.npc_sell_binocle_reject
  321.                 return false
  322.             end
  323.         end
  324.         -- if item:is_simple_detector() then
  325.             --// placeholder
  326.         -- end
  327.     end
  328.     return allow_cfg
  329. end
  330. --//------------------------------------------------------------
  331. local function send_new(name, deviation)
  332.     if _DEBUG_ then
  333.         assert(msg_data[name], 'msg_data[%s]', name)
  334.     else
  335.         if not msg_data[name] then return warning('msg_data[%s]', name) end
  336.     end
  337.     local data = msg_data[name]
  338.     local text = data.text
  339.     if is_table(text) then
  340.         text = text[math.random(#text)]
  341.     end
  342.     manager_news.send_new(data.name, text, data.icon, nil, deviation, 10, 'news')
  343. end
  344. --//------------------------------------------------------------
  345. local function get_clsid_buffer()
  346.     if not clsid_to_str and not clsid_invert then
  347.         clsid_to_str = {}
  348.         clsid_invert = table.invert(clsid_table)
  349.         for k,v in pairs(clsid_invert) do --// Karlan->Kalran: в этой реализации есть избытки, но они несущественны :)
  350.             if v:find('wpn') then       clsid_to_str[v] = 'weapon'      end
  351.             if v:find('grenade') then   clsid_to_str[v] = 'grenade'     end --// karlan: redefenition, dont'move!
  352.             if v:find('knife') then     clsid_to_str[v] = 'knife'       end --// karlan: redefenition, dont'move!
  353.             if v:find('ammo') then      clsid_to_str[v] = 'ammo'        end --// karlan: redefenition, dont'move!
  354.             if v:find('^art') then      clsid_to_str[v] = 'artefact'    end
  355.             if v:find('^dev') then      clsid_to_str[v] = 'device'      end
  356.             if v:find('document') then  clsid_to_str[v] = 'document'    end
  357.             if v:find('explosive') then clsid_to_str[v] = 'explosive'   end
  358.             if v:find('attach') then    clsid_to_str[v] = 'attachable'  end
  359.             if v:find('backpack') then  clsid_to_str[v] = 'backpack'    end
  360.             if v:find('^equ') or v:find('helmet') then                  clsid_to_str[v] = 'outfit'      end
  361.             if v:find('ban') or v:find('med') or v:find('antir') then   clsid_to_str[v] = 'medicine'    end
  362.             if v:find('food') or v:find('bottle') then                  clsid_to_str[v] = 'eat'         end
  363.         end
  364.     end
  365.     return clsid_to_str
  366. end
  367. local function get_item_trade_type(obj)
  368.     local str = get_clsid_buffer()
  369.     local reason = str[clsid_invert[obj:clsid()]]
  370.     log('trade type for [%s] = [%s]', obj:section(), reason)
  371.     assert(is_string(reason), 'obj is bad, trade module does not support [%s] class ', clsid_invert[obj:clsid()])
  372.     return reason
  373. end
  374. --//------------------------------------------------------------
  375. local function fill_deficit_data(obj)
  376.     if not is_trader_mode(obj) then return end
  377.     local st = ts[obj and obj:id()]
  378.     local ini, str = st.config, st.current_buy_supplies
  379.     local n,result,section,value = ini:line_count(str)
  380.     for i = 0, n-1 do
  381.         result,section,value = ini:r_line(str, i, "", "")
  382.         local count,prob = value:match("(.+),(.+)")
  383.         deficit_buff[section] = (tonumber(count) or 1)*(tonumber(prob) or 1)
  384.     end
  385. end
  386. local function create_buy_supplies(npc, ini, str)
  387. -- npc:buy_supplies(ini, str) -- default
  388. --// thx Charsi
  389.     local item_cnt = {}
  390.     local item_ids = {}
  391.     npc:inventory_for_each(function(item,npc)
  392.             local section = item:section()
  393.             if not item_ids[section] then
  394.                 item_ids[section] = {item:id()}
  395.                 item_cnt[section] = 1
  396.             else
  397.                 table.insert(item_ids[section],item:id())
  398.                 item_cnt[section] = item_cnt[section] + 1
  399.             end
  400.         end)
  401.     local new_items = {}
  402.     local n = ini:line_count(str)
  403.     for i = 0, n-1 do
  404.         local result, section, value = ini:r_line(str, i, "", "")
  405.         local count,prob = value:match("(.+),(.+)")
  406.         count,prob = tonumber(count),tonumber(prob)
  407.         for i=1,count do
  408.             if math.random()<prob then
  409.                 item_cnt[section] = (item_cnt[section] or 0) - 1
  410.             end
  411.         end
  412.     end
  413.     for sect, cnt in pairs(item_cnt) do
  414.         if cnt>0 then
  415.             -- удаляем лишнее
  416.             for i=1,cnt do
  417.                 local so = sim:object(item_ids[sect][i])
  418.                 if so then sim:release(so) end
  419.             end
  420.         elseif cnt<0 then
  421.             -- спавним недостающее
  422.             for i=1,-cnt do
  423.                 sim:create(sect, vector(), 0, 0, npc:id())
  424.             end
  425.         end
  426.     end
  427. end
  428. --//------------------------------------------------------------
  429. local function set_default_buy_condition(obj)
  430.     local st = ts[obj and obj:id()]
  431.     local str = cfg_get_string(st.config, "trader", "default_buy_condition")
  432.     if not is_string(str) then return end
  433.     str = xr_logic.pick_section_from_condlist(obj, parse_condlist(str))
  434.     assert(st.config:section_exist(str), 'Invalid current default_buy_condition section, check section name', str)
  435.     local value = st.config:r_string(str, 'value')
  436.     local f,e = value:match("(.+),(.+)")
  437.     local friend, enemy = tonumber(f) or 1, tonumber(e) or 1
  438.     obj:buy_condition(friend, enemy)
  439. end
  440. local function set_default_sell_condition(obj)
  441.     local st = ts[obj and obj:id()]
  442.     local str = cfg_get_string(st.config, "trader", "default_sell_condition")
  443.     if not is_string(str) then return end
  444.     str = xr_logic.pick_section_from_condlist(obj, parse_condlist(str))
  445.     assert(st.config:section_exist(str), 'Invalid current default_sell_condition section, check section name', str)
  446.     local value = st.config:r_string(str, 'value')
  447.     local f,e = value:match("(.+),(.+)")
  448.     local friend, enemy = tonumber(f) or 1, tonumber(e) or 1
  449.     obj:sell_condition(friend, enemy)
  450. end
  451. local function set_show_condition(obj, forced) --// Karlan: возможность есть, использования нет, покажем народу где раки...
  452.     local st, str = ts[obj and obj:id()]
  453.     if forced then
  454.         str = forced
  455.     else
  456.         str = cfg_get_string(st.config, "trader", "show_condition")
  457.         if str == nil then if st.reload_show then ts[obj:id()]['reload_show'] = nil end return end
  458.         str = xr_logic.pick_section_from_condlist(obj, parse_condlist(str))
  459.         assert(st.config:section_exist(str), 'st.config:section_exist(%s)', str)
  460.     end
  461.     if (st.current_show_condition ~= str) or forced or st.reload_show then
  462.         log("[show_condition] for [%s]:set section = [%s]:reload = [%s]", obj:name(), str, st.reload_show~=nil)
  463.         obj:show_condition(st.config, str)
  464.         st.current_show_condition = str
  465.         ts[obj:id()]['reload_show'] = nil
  466.     end
  467. end
  468. local function set_buy_condition(obj, forced) --// позволяет формировать конфиг из самой системы, а также напрямую передав динамический лтх
  469.     local st, str = ts[obj and obj:id()]
  470.     if forced then
  471.         str = forced
  472.     else
  473.         str = cfg_get_string(st.config, "trader", "buy_condition", obj, true, "")
  474.         assert(str~=nil, "Incorrect trader settings. Cannot find buy_condition. [%s]->[%s]", obj:name(), st.cfg_ltx) --// предполагается обязательным параметром
  475.         str = xr_logic.pick_section_from_condlist(obj, parse_condlist(str))
  476.         assert(st.config:section_exist(str), 'st.config:section_exist(%s)', str)
  477.     end
  478.     if (st.current_buy_condition ~= str) or forced or st.reload_buy then --// поскольку forced секция реплейсится, то ее имя всегда будет совпадать, и если мы перегружаем часть, то вот эту часть как раз надо обновить
  479.         log("[buy_condition] for [%s]:set section = [%s]:reload = [%s]", obj:name(), str, st.reload_buy~=nil)
  480.         obj:buy_condition(st.config, str)
  481.         st.current_buy_condition = str
  482.         ts[obj:id()]['reload_buy'] = nil
  483.     end
  484. end
  485. local function set_sell_condition(obj, forced)
  486.     local st, str = ts[obj and obj:id()]
  487.     if forced then
  488.         str = forced
  489.     else
  490.         str = cfg_get_string(st.config, "trader", "sell_condition", obj, true, "")
  491.         assert(str~=nil, "Incorrect trader settings. Cannot find sell_condition. [%s]->[%s]", obj:name(), st.cfg_ltx) --// предполагается обязательным параметром
  492.         str = xr_logic.pick_section_from_condlist(obj, parse_condlist(str))
  493.         assert(st.config:section_exist(str), 'st.config:section_exist(%s)', str)
  494.     end
  495.     if (st.current_sell_condition ~= str) or forced or st.reload_sell then
  496.         log("[sell_condition] for [%s]:set section = [%s]:reload = [%s]", obj:name(), str, st.reload_sell~=nil)
  497.         obj:sell_condition(st.config, str)
  498.         st.current_sell_condition = str
  499.         ts[obj:id()]['reload_sell'] = nil
  500.     end
  501. end
  502. local function set_buy_supplies(obj, forced)
  503.     local st, str = ts[obj and obj:id()]
  504.     if not st then return end --// на загрузке при вызове конструктора таймера, само поле в ts уже есть, но вот объекта еще разумеется нет, соответственно надо сматыватся
  505.     if forced then
  506.         if is_string(forced) then
  507.             str = forced
  508.         else --// для случаев обычного обновления текущего ассортимента для статического (оригинального) конфига
  509.             if st.buy_supplies==nil then return end --// фейкового ассортимента у него не предусмотрено, ну тогда и нечего его мурыжить
  510.             str = xr_logic.pick_section_from_condlist(obj, st.buy_supplies) --// может быть опасно при вызове повторяющихся экшнов
  511.             if st.script_trade then
  512.                 --// если у нас совсем бешено-рандомная скриптовая торговля, то тут самое время поменять весь лист
  513.             end
  514.         end
  515.     else
  516.         if not st.buy_supplies then --// разгружаем, если при срабатывании обновления у этого торговца конфиг не поменялся, то даже париться не будем, а просто обновим ассортимент
  517.             str = cfg_get_string(st.config, "trader", "buy_supplies", obj)
  518.             st.buy_supplies = str and parse_condlist(str) or nil --// предполагается необязательным параметром
  519.         end
  520.         if st.buy_supplies==nil then return end --// фейкового ассортимента у него не предусмотрено, ну тогда и нечего его мурыжить
  521.         str = xr_logic.pick_section_from_condlist(obj, st.buy_supplies)
  522.         assert(st.config:section_exist(str), 'st.config:section_exist(%s)', str)
  523.     end
  524.     if (st.current_buy_supplies ~= str) or forced then
  525.         log("[buy_supplies] for [%s]:set section = [%s]", obj:name(), str)
  526.         create_buy_supplies(obj, st.config, str)
  527.         log("[buy_supplies] created")
  528.         st.current_buy_supplies = str
  529.     end    
  530. end
  531. --//------------------------------
  532. local function set_discounts(obj, forced, discount_type, item_type)
  533.     local st = ts[obj and obj:id()]
  534.     if not st.discounts then
  535.         st.discounts = {}
  536.         st.discounts.buy = {}
  537.         st.discounts.sell = {}
  538.     end
  539.     if forced then
  540.         if is_number(forced) then --// change mode
  541.             assert(is_string(discount_type), 'is_string(discount_type)')
  542.             assert(is_string(item_type), 'is_string(item_type)')
  543.             st.discounts[discount_type][item_type] = forced
  544.         end
  545.         if is_table(forced) then --// (re)set mode
  546.             if is_string(discount_type) then
  547.                 st.discounts[discount_type] = forced
  548.             else
  549.                 st.discounts = forced
  550.             end
  551.         end
  552.     else
  553.         local str = cfg_get_string(st.config, "trader", "discounts")
  554.         if not str then return log('section [discounts] does not matched in st.config') end
  555.         local sect = xr_logic.pick_section_from_condlist(obj, parse_condlist(str))
  556.         for i=0, st.config:line_count(sect)-1 do
  557.             local res, key, value = st.config:r_line(str, i, "", "")
  558.             local buy, sell = value:match("(.+),(.+)")
  559.             buy, sell = tonumber(buy) or 1, tonumber(sell) or 1
  560.             st.discounts.buy[key] = buy
  561.             st.discounts.sell[key] = sell
  562.         end
  563.     end
  564.     table.print(st.discounts, 'discounts')
  565. end
  566. local function get_discounts(trader, item, discount_type) --// Karlan->Karlan: не лучший вариант, технически грамотнее возвратить сразу Fvector2
  567.     local st = ts[trader and trader:id()]
  568.     assert(st, 'trader not finded in trade storage')
  569.     assert(st.config, 'trader is not configurated')
  570.     if not is_table(st.discounts[discount_type]) then
  571.         log('trader has no discounts')
  572.         return 1
  573.     end
  574.     local type_items = get_item_trade_type(item)
  575.     local discount = st.discounts[discount_type][type_items]
  576.     if not is_number(discount) then
  577.         log('directed type does not finded')
  578.         discount = st.discounts[discount_type]['base']
  579.     end
  580.     if not is_number(discount) then
  581.         log('base type does not finded, return 1')
  582.         discount = 1
  583.     end
  584.     return discount
  585. end
  586. --//------------------------------
  587. --// Karlan->ALL: sell condition вырезал, так как игрок пусть сам глазами смотрит и головой думает
  588. local function set_conditions(obj, forced, condition_type, item_type)
  589.     local st = ts[obj and obj:id()]
  590.     if not st.conditions then
  591.         st.conditions = {}
  592.         st.conditions.buy = {}
  593.         -- st.conditions.sell = {}
  594.     end
  595.     if forced then
  596.         if is_number(forced) then --// change mode
  597.             assert(is_string(condition_type), 'is_string(condition_type)')
  598.             assert(is_string(item_type), 'is_string(item_type)')
  599.             st.conditions[condition_type][item_type] = forced
  600.         end
  601.         if is_table(forced) then --// (re)set mode
  602.             if is_string(condition_type) then
  603.                 st.conditions[condition_type] = forced
  604.             else
  605.                 st.conditions = forced
  606.             end
  607.         end
  608.     else
  609.         local str = cfg_get_string(st.config, "trader", "conditions")
  610.         if not str then return log('section [conditions] does not matched in st.config') end
  611.         local sect = xr_logic.pick_section_from_condlist(obj, parse_condlist(str))
  612.         for i=0, st.config:line_count(sect)-1 do
  613.             local res, key, value = st.config:r_line(str, i, "", "")
  614.             local buy, sell = value:match("(.+),(.+)")
  615.             buy, sell = tonumber(buy) or 1, tonumber(sell) or 1
  616.             st.conditions.buy[key] = buy
  617.             -- st.conditions.sell[key] = sell
  618.         end
  619.     end
  620.     table.print(st.conditions, 'conditions')
  621. end
  622. local function get_conditions(trader, item, condition_type) --// Karlan->Karlan: не лучший вариант, технически грамотнее возвратить сразу Fvector2
  623.     local st = ts[trader and trader:id()]
  624.     assert(st, 'trader not finded in trade storage')
  625.     assert(st.config, 'trader is not configurated')
  626.     if not is_table(st.conditions[condition_type]) then
  627.         log('trader has no conditions')
  628.         return 0
  629.     end
  630.     local type_items = get_item_trade_type(item)
  631.     local condition = st.conditions[condition_type][type_items]
  632.     if not is_number(condition) then
  633.         log('directed type does not finded')
  634.         condition = st.conditions[condition_type]['base']
  635.     end
  636.     if not is_number(condition) then
  637.         log('base type does not finded, return 0')
  638.         condition = 0
  639.     end
  640.     return condition
  641. end
  642. --//------------------------------
  643. local function set_barter(obj)
  644.     local st = ts[obj and obj:id()]
  645.     local barter_ini = cfg_get_string(st.config, "trader", "barter")
  646.     if not barter_ini then return log('NPC does not barter') end
  647.     local sect = xr_logic.pick_section_from_condlist(obj, parse_condlist(barter_ini))
  648.     assert(st.config:section_exist(sect), 'st.config:section_exist(%s)', sect)
  649.     obj:set_barter_mode(true)
  650.     st.barter = {}
  651.     local reference_item = cfg_get_string(st.config, sect, "reference_item")
  652.     st.barter.ref_price = false --// флаг того, что это свободный обмен основанный на отклонении цены корзины в чью либо пользу
  653.     if reference_item then
  654.         local price = cfg_get_number(sini, reference_item, "cost")
  655.         assert(is_number(price), 'Invalid price for item [%s]', reference_item)
  656.         st.barter.ref_price = price
  657.     end
  658.     if not is_number(st.barter.ref_price) then
  659.         string_table.replace_string('ui_st_pieces_regional', game.translate_string('null_string'))
  660.         log('set_barter string replaced [%s]', game.translate_string('ui_st_pieces_regional'))
  661.     end
  662.     local lock_sell = cfg_get_string(st.config, sect, "lock_sell")
  663.     if lock_sell then
  664.         --// итемы которые нпс не может продавать
  665.         st.barter.locked = {}
  666.         for i,v in ipairs(parse_names(lock_sell)) do
  667.             assert(is_string(v), 'is_string(%s)', v)
  668.             st.barter.locked[v] = true
  669.         end
  670.     end
  671.     local allow_buy = cfg_get_string(st.config, sect, "allow_buy")
  672.     if allow_buy then
  673.         --// итемы которые нпс может покупать
  674.         st.barter.allows = {}
  675.         for i,v in ipairs(parse_names(allow_buy)) do
  676.             assert(is_string(v), 'is_string(%s)', v)
  677.             st.barter.allows[v] = true
  678.         end
  679.     end
  680.     local bool_btn = cfg_get_bool(st.config, sect, "hide_button", false)
  681.     obj:show_trade_btn(true)
  682.     if bool_btn then
  683.         obj:show_trade_btn(false)
  684.     end
  685. end
  686. --//------------------------------
  687. local function set_need_artefact(obj)
  688.     if is_string(need_art) then return end
  689.     if not is_table(manager_task.get_random_task().current_parent_type_prior) then return end --// Karlan: прежде чем назначать арт игрок должен поинтересоваться что это за арт
  690.     local tasks = manager_task.get_random_task():get_task_info()
  691.     local parent = manager_task.get_random_task():get_parent(obj)
  692.     for _,v in pairs(tasks) do
  693.         if v.parent == parent and v.type == 'artefact' and manager_task.get_random_task():task_avail(_,_,_,_,v.init_phrase_id) then
  694.             need_art = v.target
  695.             return --/> цепочка, как правило, одна, так что валим
  696.         end
  697.     end
  698. end
  699. --//------------------------------------------------------------
  700. local function trade_init(obj, reinit) --// Karlan->Karlan: загрузку самих данных можно вызывать непосредственно при старте торговли, плюс только в красивостях, так что нафиг
  701.     assert(is_userdata(obj), 'is_userdata(obj)')
  702.     if blocked_trade(obj) then return log('trade has blocked for <%s>', obj) end --// Карлан: не следует возиться с теми, с кем мы сейчас не можем поторговать
  703.     local id = get_id(obj)
  704.     local st = db.storage[id]
  705.     assert(is_table(st), 'is_table(st)')
  706.     log('stype = [%s]:ini = [%s]:section_logic = [%s] ', st.stype, st.ini, st.section_logic)
  707.     log('original trade cfg = [%s] ', cfg_get_string(st.ini, st.section_logic, "trade", obj, false, "", "misc\\manager_trade\\trade_generic.ltx"))
  708.     if st.stype == modules.stype_stalker or st.stype == modules.stype_trader or obj:clsid() == clsid.script_trader then
  709.         local trade_ini = cfg_get_string(st.ini, st.section_logic, "trade", "misc\\manager_trade\\trade_generic.ltx")
  710.         local script = cfg_get_bool(st.ini, st.section_logic, "script_trade", false)
  711.         if script then
  712.             -- trade_ini = blablabla --// вот тут можно ставить лист формируемый скриптом
  713.         end
  714. if _OLD_TRADE_ then --// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  715.         trade_init_old(obj, trade_ini)
  716. else --// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  717.         local tt = nil
  718.         if is_table(ts[id]) and ts[id].cfg_ltx and not reinit then
  719.             tt = ts[id]
  720.         else
  721.             tt = {cfg_ltx = trade_ini, allow_trade = true, script_trade = script==true}
  722.             ts[id] = tt
  723.         end
  724.         tt.is_dltx = tt.cfg_ltx:match('^%[')
  725.         if tt.is_dltx then
  726.             log('dltx')
  727.             tt.config = create_ini_file(tt.cfg_ltx)
  728.         else
  729.             log('cltx')
  730.             tt.config = ini_file(tt.cfg_ltx)
  731.         end
  732.         if not tt.net_profit then
  733.             tt.net_profit = {}
  734.             local _min,_max = 5,10
  735.             if is_trader_mode(obj) then
  736.                 _min,_max = _min*2,_max*2
  737.             end
  738.             table.insert(tt.net_profit, math.random(_min,_max)/365)
  739.         end
  740.        
  741.         set_default_buy_condition(obj)
  742.         set_default_sell_condition(obj)
  743.         set_show_condition(obj)
  744.         set_buy_condition (obj)
  745.         set_sell_condition(obj)
  746.         set_discounts(obj)
  747.         set_conditions(obj)
  748.         set_barter(obj)
  749.         -- set_need_artefact(obj) --// Karlan: пока вырубим
  750.         if not is_trader_mode(obj) then --// Karlan: обновление ассортимента предполагается только для реальных барыг, обычных сталкеров на таймер ясное дело не сажаем
  751.             snakefish_reset = true
  752.             reset_snakefish_items(obj)
  753.             return
  754.         end
  755.         if not m_timers.timer_exists("karlan_trade_timer"..tostring(id)) then
  756.             log('create timer [%s] of object <%s>', "karlan_trade_timer"..tostring(id), obj)
  757.             trade_update_timer("karlan_trade_timer"..tostring(id), obj, id):cyclic():set_gdelay():start() --// самая первая стартовая инициализация
  758.             log('timer created')
  759.         end
  760.         local timer = m_timers.get_timer("karlan_trade_timer"..tostring(id))
  761.         assert(timer, 'timer')
  762.         timer:reset_object(obj, id)
  763.         if reinit then
  764.             timer:set_reinit(true)
  765.         end
  766.         timer:update_list() --// Karlan: если нам надо было перезагрузить лист, когда непись был к этому не способен, то сделаем это сейчас
  767.         fill_deficit_data(obj)
  768. end --// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  769.     end  
  770. end
  771. --//------------------------------------------------------------
  772. --// экономика
  773. local function get_inflation_rate(target_inflation) --// fixed factor
  774.     return inflation_rate[math.abs(target_inflation)+1+target_inflation]
  775. end
  776.  
  777. local function get_net_profit(obj, target_net_profit) --// fixed factor
  778.     if not obj or (obj:id() == actor_id) then
  779.         return 0 --// Karlan: актор - добрая душа :)
  780.     end
  781.     local st = ts[obj and obj:id()]
  782.     assert(is_table(st.net_profit), 'is_table(st.net_profit)')
  783.     local i = math.abs(target_net_profit)+1+target_net_profit
  784.     log('* net_profit of [%s] = [%s]', obj:name(), st.net_profit[i])
  785.     if not st.net_profit[i] then --// Karlan: профит у некоторых будет не успевать за инфляцией, поэтому его надо проставить по тому же алгоритму
  786.         local _min,_max = 5,10
  787.         if is_trader_mode(obj) then
  788.             _min,_max = _min*2,_max*2
  789.         end
  790.         st.net_profit[i] = math.random(_min,_max)/365
  791.     end
  792.     return st.net_profit[i]
  793. end
  794.  
  795. local function calculate_compounding(obj, index)
  796.     local r,a = get_net_profit(obj, index)/100, get_inflation_rate(index)/100
  797.     local result = (1 + r) * (1 + a) - 1
  798.     -- log('* r = %s, a = %s, res = %s',r,a,result)
  799.     return result*100
  800. end
  801.  
  802. local function calculate_capvalue(item, costs, obj)
  803.     local st = nominal_prices[item:section()]
  804.     if not st then
  805.         assert(is_table(nominal_prices['zaglushka']), "is_table(nominal_prices['zaglushka'])")
  806.         assert(nominal_prices['zaglushka'].inflation_recalculate<1, "nominal_prices['zaglushka'].inflation_recalculate<1 (%s)", nominal_prices['zaglushka'].inflation_recalculate)
  807.         log('section %s doesnt find in nominal_prices', item:section()) --// warning???
  808.         nominal_prices[item:section()] = {inflation_recalculate = nominal_prices['zaglushka'].inflation_recalculate}
  809.         st = nominal_prices[item:section()]
  810.     end
  811.     local gain = st.gain or 0
  812.     local i = 0 --// DEBUG !!!
  813.     while st.inflation_recalculate ~= 1 do
  814.         i = i+1 --// DEBUG !!!
  815.         local cost = costs.gross_price+gain
  816.         local p = calculate_compounding(obj, st.inflation_recalculate)
  817.         local fv = (cost/100)*p + cost
  818.         local pv = fv * 1/(1+p/100)^(-1/365)
  819.         gain=gain+(pv-cost)
  820.         log('* inflation = [%s], CC = [%s], R = [%s], FV = [%s], PV = [%s], gain = [%s]', inflation_rate[math.abs(st.inflation_recalculate)+1+st.inflation_recalculate], cost, p, fv, pv, gain)
  821.         st.inflation_recalculate = st.inflation_recalculate+1
  822.         log('~%s>[day:%s]', string.rep('~', 20), i or -1)
  823.     end
  824.     log('# net gain = [%s]', gain)
  825.     st.gain = gain
  826.     gain = gain*costs.condition_factor*costs.action_factor*costs.deficit_factor --// приращение нужно также размазать
  827.     gain = math.ceil(gain)
  828.     log('# result gain = [%s]', gain)
  829.     log('# result info = [%s]:[%s = %s+%s]', item:parent():name(), costs.net_price+gain,costs.net_price,gain)
  830.     local net_price = costs.net_price+gain
  831.     return net_price
  832. end
  833.  
  834. class "superviser" (m_timers.savable_timer)
  835. function superviser:__init(timer_id) super(timer_id)
  836.     self._class = script_name()..".superviser"
  837.     self:taction()
  838. end
  839.  
  840. function superviser:taction()
  841.     if _DEBUG_ and has_info('ui_trade') then return end
  842.     local inflation = math.random(60,80)--math.random(600,800) --// Karlan: а ну-ка нафиг нам такую инфляцию, рюкзаки по паре мультов во фриплее!
  843.     if math.random(100) < 10 then
  844.         log('inflation adds')
  845.         inflation=inflation+math.random(15,30)--math.random(50,100)
  846.     end
  847.     inflation = inflation/365
  848.     log('current inflation = [%s]', inflation)
  849.     table.insert(inflation_rate, inflation)
  850.     for k in pairs(ts) do
  851.         if is_table(ts[k].net_profit) then
  852.             local _min,_max = 5,10
  853.             if m_timers.timer_exists("karlan_trade_timer"..tostring(k)) then --// dirty hack
  854.                 log('profit calculate for [%s], HACKED!', get_name_by_id(k))
  855.                 _min,_max = _min*2,_max*2
  856.             end
  857.             table.insert(ts[k]['net_profit'], math.random(_min,_max)/365) --// переменяем все
  858.         end
  859.     end
  860.     for k in pairs(nominal_prices) do
  861.         if is_number(nominal_prices[k].inflation_recalculate) then
  862.             nominal_prices[k].inflation_recalculate = nominal_prices[k].inflation_recalculate-1
  863.         else
  864.             nominal_prices[k].inflation_recalculate = 0
  865.         end
  866.     end
  867. end
  868. --//------------------------------------------------------------
  869. class "trade_update_timer" (m_timers.savable_timer)
  870. function trade_update_timer:__init(timer_id, obj, id) super(timer_id)
  871.     self._class = script_name()..".trade_update_timer"
  872.     set_buy_supplies(obj)
  873.     self.trader_id = id --// Karlan->Karlan: может быть проблема, если какой-то придурок начнет переспавнивать торговца, тогда весь его таймер собьется к черту, еще так же проблема может быть когда удаляем в оффлайне, поэтому ,возможно, выгоднее сохранять его имя, надо прикинуть, погубят фраера два байта :)
  874.     self.object = obj
  875.     --//----------
  876.     self.reinit_flag = false
  877.     self.watch = false
  878.     self.tact = 0
  879.     log('timer constructed')
  880. end
  881. function trade_update_timer:reset_object(obj, id)
  882.     self.trader_id = id
  883.     self.object = obj
  884. end
  885. function trade_update_timer:taction()
  886.     log('start taction')
  887.     assert(self.trader_id, 'self.trader_id')
  888.     if has_info('ui_trade') and level.get_talker():id() == self.trader_id then
  889.         log('<name=[%s]:[%s]>:set watch', self.object and self.object:name(), self.trader_id)
  890.         self.watch = true
  891.         return
  892.     end
  893.     self.object = level.object_by_id(self.trader_id)
  894.     log('level.object_by_id(self.trader_id) = <%s>', self.object)
  895.     if not self.object then --// Karlan: для случаев, когда обновить уже надо, но мы в оффлайне
  896.         self.reinit_flag = true
  897.         log('object [%s]:<sid=%s> is offline, set reinit_trade', get_name_by_id(self.trader_id), sid_by_id(self.trader_id))
  898.         send_new(get_name_by_id(self.trader_id))
  899.         return
  900.     end
  901.     local st = ts[self.trader_id]
  902.     log('<name=[%s]:[%s]>:attempt to call set_buy_supplies(self.object, true) in trade_update_timer:taction()', self.object and self.object:name(), self.trader_id)
  903.     send_new(get_name_by_id(self.trader_id))
  904.     set_buy_supplies(self.object, true)
  905. end
  906. function trade_update_timer:update_list()
  907.     if self.reinit_flag then
  908.         log('<name=[%s]:[%s]>:attempt to call set_buy_supplies(self.object, true) in trade_update_timer:update_list()', self.object and self.object:name(), self.trader_id)
  909.         self:stop(true) --// Karlan: сбрасываем время цикла и начинаем отсчет сначала
  910.         set_buy_supplies(self.object, true)
  911.         self.reinit_flag = false
  912.     end
  913. end
  914. function trade_update_timer:set_reinit(bool)
  915.     self.reinit_flag = bool
  916. end
  917. function trade_update_timer:condition()
  918.     if self.watch and has_info('ui_trade_hide') then
  919.         self.watch = false
  920.         send_new(get_name_by_id(self.trader_id), math.random(5,10)) --// тут убогая несохраняемая движковая девиация, будем надеятся, что в этот период никто не будет перезагружатся
  921.         log('<name=[%s]:[%s]>:attempt to call set_buy_supplies(self.object, true) in trade_update_timer:condition()', self.object and self.object:name(), self.trader_id)
  922.         set_buy_supplies(self.object, true) --// это такой хак чтобы не создавать action, ибо лень, а раз лень, то нефиг :)
  923.         log('<name=[%s]:[%s]>:unset watch', self.object and self.object:name(), self.trader_id)
  924.         return true
  925.     end
  926.     return false
  927. end
  928. function trade_update_timer:set_period()
  929.     local period = 24*3600 + math.random(-6,6)*3600
  930.     if _DEBUG_ then period = 5*60 end --// for test
  931.     log('game hours to trade update [%s]:[%s]:[%s]', period/3600, self.object and self.object:name(), self.trader_id)
  932.     return period
  933. end
  934. function trade_update_timer:save(pk) --// +3 байта
  935.     pk:w_u16(self.trader_id)
  936.     pk:w_bool(self.reinit_flag)
  937. end
  938. function trade_update_timer:load(pk)
  939.     self.trader_id = pk:r_u16()
  940.     self.reinit_flag = pk:r_bool()
  941. end
  942. --//-----------------------------------------------------------------------------------------------
  943. --// Callbacks
  944. --//-----------------------------------------------------------------------------------------------
  945. local function start_dialog(e)
  946.     local obj = e.obj
  947.     log('relations for [%s:(%s)] = [p(%s)]:[c(%s)]', obj:name(), obj:character_community(), get_npc_goodwill(obj, actor), get_factions_community_goodwill(obj:character_community(), actor_id))
  948.     log('get_trader_goodwill = [%s]:(%s/%s)', get_trader_goodwill(obj), get_npc_goodwill(obj, actor)/1000, get_factions_community_goodwill(obj:character_community(), actor_id)/5000)
  949.     trade_init(obj, false)
  950. end
  951. local function npc_death(e)
  952.     local id = e.obj_id
  953.     if m_timers.timer_exists("karlan_trade_timer"..tostring(id)) then
  954.         local timer = m_timers.get_timer("karlan_trade_timer"..tostring(id))
  955.         timer:stop()
  956.     end
  957.     rawset(ts, id, nil)
  958. end
  959. local function npc_destroy(e)
  960.     local id = e.obj_id
  961.     local sobj = sim:object(id)
  962.     if sobj then return end
  963.     if m_timers.timer_exists("karlan_trade_timer"..tostring(id)) then
  964.         local timer = m_timers.get_timer("karlan_trade_timer"..tostring(id))
  965.         timer:stop()
  966.     end
  967.     rawset(ts, id, nil)
  968. end
  969. local function info_received(e)
  970.     if e.info == 'ui_trade_hide' then
  971.         string_table.replace_string('ui_st_pieces_regional', game.translate_string('ui_st_pieces_regional_safe'))
  972.         -- log('callback string replaced [%s]', game.translate_string('ui_st_pieces_regional'))
  973.         if bargain_link then --// Karlan: сталкер по каким-то причинам решил нас послать подальше с нашими распинаниями
  974.             level.start_stop_menu(bargain_link, false)
  975.         end
  976.         local obj = level.get_talker()
  977.         if entry_trade and is_userdata(obj) then --// Karlan: будем считать, если сталкер сам прервал торговлю, то обижатся он не будет
  978.             local changed = math.random(5)*-1
  979.             change_npc_goodwill(obj, actor, changed)
  980.             log('entry_trade = %s, changed [%s]:[%s]:[%s]', changed, get_npc_goodwill(obj, e.actor), get_weighted_goodwill(obj, e.actor), get_two_goodwill(obj, e.actor))
  981.             entry_trade = false
  982.             snakefish_reset = true
  983.             reset_snakefish_items(obj)
  984.             if is_trader_mode(obj) then
  985.                 obj:iterate_inventory(function(_, item)
  986.                     if item:condition_dec() > 0 and item:condition() < 0.9 then
  987.                         local sobj = sim:object(item:id())
  988.                         if sobj then sim:release(sobj) end
  989.                     end
  990.                 end)
  991.             end
  992.         end
  993.     end
  994.     if e.info == 'ui_trade' then
  995.         snakefish_reset = true --// Karlan->Karlan: оптимизировать, сейчас на старте дергается дважды, это нехорошо
  996.         reset_snakefish_items(level.get_talker())
  997.         entry_trade = true
  998.     end
  999. end
  1000. local function actor_spawn(e)
  1001.     if not m_timers.timer_exists("karlan_trade_superviser") then
  1002.         local d,m = 1,0
  1003.         if _DEBUG_ then d,m = 0,2 end
  1004.         superviser("karlan_trade_superviser"):cyclic():set_gdelayDHMS(d,0,m,0):start()
  1005.     end
  1006.     if table.size(nominal_prices) < 1 then
  1007.         local ini = ini_file("misc\\manager_trade\\_data_trade.ltx") --// пока так, потом нужно будет список доделать
  1008.         for i=0, ini:line_count('gain_data')-1 do
  1009.             local _, id   = ini:r_line('gain_data',i,'','')
  1010.             nominal_prices[id] = {inflation_recalculate = 0}
  1011.         end
  1012.         table.print(nominal_prices, 'nominal_prices')
  1013.     end
  1014. end
  1015. local function trade_perform(e)
  1016.     log('~%s>[trade_operation:%s]:>', string.rep('~', 41), 'start')
  1017.     log('$trade_perform called: get = [%s] put = [%s]', e.money_get, e.money_put) --// проданные актору товары (уплаченные деньги), проданные торговцу товары (полученные деньги)
  1018.     local obj = e.obj
  1019.     log('* before goodwill [%s]:[%s]:[%s]', get_npc_goodwill(obj, actor), get_weighted_goodwill(obj, actor), get_two_goodwill(obj, actor))
  1020.     local get_divider, put_divider = is_trader_mode(obj) and 500 or 50, is_trader_mode(obj) and 1000 or 100
  1021.     local buy_coeff, sell_coeff, bad_coeff = math.clamp(e.money_get/get_divider, 0, math.huge), math.clamp(e.money_put/put_divider, 0, math.huge), 0
  1022.     if is_trader_mode(obj) and (get_trader_goodwill(obj) > 0.65) and (e.money_get < 1000) then --// TODO: учесть что ничего не продали
  1023.         bad_coeff = (buy_coeff*2)*-1
  1024.     end
  1025.     goodwill_coeff = buy_coeff+sell_coeff+bad_coeff
  1026.     log('* results = bc [%f], sc [%f], bc[%f], rs [%f]', buy_coeff, sell_coeff, bad_coeff, goodwill_coeff)
  1027.     money_get, money_put = e.money_get, e.money_put
  1028. end
  1029. local function trade_action(e)
  1030.     log('%item = %s, sell_bye = %s, money = %s', e.item, e.sell_bye, e.money)
  1031.     if e.item:is_artefact() then
  1032.         local add = e.money/(e.sell_bye and money_put or money_get)
  1033.         add = add/2
  1034.         log('* add coeff [%f]', add)
  1035.         goodwill_coeff = goodwill_coeff+add
  1036.         art_count = art_count+1
  1037.     end
  1038. end
  1039. local function trade_terminate(e)
  1040.     local obj = e.obj
  1041.     if goodwill_coeff < art_count then --// Karlan: не будем доводить игрока до запускания клавиатуры в монитор :)
  1042.         goodwill_coeff = art_count
  1043.     end
  1044.     goodwill_coeff = (art_count>0) and math.ceil(goodwill_coeff) or math.floor(goodwill_coeff)
  1045.     log('* finish result = [%f]', goodwill_coeff)
  1046.     change_npc_goodwill(obj, actor, goodwill_coeff)
  1047.     if goodwill_coeff < 0 then
  1048.         local st = ts[obj and obj:id()]
  1049.         st.ui_set.buy = get_trader_goodwill(obj)
  1050.         st.ui_set.sell = 2 - st.ui_set.buy
  1051.     end
  1052.     entry_trade = false
  1053.     snakefish_reset = true
  1054.     money_get, money_put, goodwill_coeff, art_count = nil, nil, nil, 0
  1055.     log('* after goodwill [%s]:[%s]:[%s]', get_npc_goodwill(obj, actor), get_weighted_goodwill(obj, actor), get_two_goodwill(obj, actor))
  1056.     log('~%s>[trade_operation:%s]:<', string.rep('~', 41), 'end')
  1057. end
  1058. local function stop_dialog(e)
  1059.     clear_snakefish_items()
  1060.     deficit_factor = nil
  1061. end
  1062. --//-----------------------------------------------------------------------------------------------
  1063. --// Initialization module
  1064. --//-----------------------------------------------------------------------------------------------
  1065. local function presets()
  1066.     if not loaded then abort('not init!') end
  1067.    
  1068.     event("start_dialog"):register(start_dialog)
  1069.     event("npc_death"):register(npc_death)
  1070.     event("npc_destroy"):register(npc_destroy)
  1071.     event("trader_destroy"):register(npc_destroy)
  1072.     event("info_received"):register(info_received)
  1073.     event("actor_spawn"):register(actor_spawn)
  1074.     event("trade_perform"):register(trade_perform)
  1075.     event("trade_terminate"):register(trade_terminate)
  1076.     event("trade_action"):register(trade_action)
  1077.     event("stop_dialog"):register(stop_dialog)
  1078. end
  1079. --//-------------------------
  1080. function init()
  1081.     event("presets"):register(presets)
  1082.     loaded = true
  1083.     log('init:[>]')
  1084. end
  1085. --//-----------------------------------------------------------------------------------------------
  1086. --// Serialization module
  1087. --//-----------------------------------------------------------------------------------------------
  1088. function save(pk)
  1089.     table.print(ts, 'save ts')
  1090.     local copy_ts = table.copy(ts) --// постоянно делаем простую копию таблицы чтобы не летать по ходу игры
  1091.     for k,v in pairs(copy_ts) do
  1092.         copy_ts[k].config = nil
  1093.     end
  1094.     pk.ts = copy_ts
  1095.     pk.np = nominal_prices
  1096.     pk.ir = inflation_rate
  1097. end
  1098. function load(pk)
  1099.     ts = pk.ts
  1100.     for k in pairs(ts) do
  1101.         if ts[k].cfg_ltx:match('^%[') then
  1102.             log('load dltx')
  1103.             ts[k].config = create_ini_file(ts[k].cfg_ltx)
  1104.         else
  1105.             log('load cltx')
  1106.             ts[k].config = ini_file(ts[k].cfg_ltx)
  1107.         end
  1108.         if not ts[k].allow_trade then
  1109.             block_trade(obj, true)
  1110.         end
  1111.         ts[k].reload_buy = true
  1112.         ts[k].reload_sell = true
  1113.         ts[k].reload_show = true
  1114.     end
  1115.     table.print(ts, 'load ts')
  1116.     nominal_prices = pk.np
  1117.     inflation_rate = pk.ir
  1118. end
  1119. --//-----------------------------------------------------------------------------------------------
  1120. --// External functions
  1121. --//-----------------------------------------------------------------------------------------------
  1122. function disable_trade(obj)
  1123.     assert(is_userdata(obj), "bad argument #1 to 'disable_trade' (userdata excepted, got %s)", type(obj))
  1124.     local st = ts[obj and obj:id()]
  1125.     if st then
  1126.         block_trade(obj, true)
  1127.         st.allow_trade = not blocked_trade(obj)
  1128.         assert(st.allow_trade~=true, 'st.allow_trade~=true')
  1129.     end
  1130. end
  1131.  
  1132. function enable_trade(obj)
  1133.     assert(is_userdata(obj), "bad argument #1 to 'enable_trade' (userdata excepted, got %s)", type(obj))
  1134.     local st = ts[obj and obj:id()]
  1135.     if st then
  1136.         block_trade(obj, false)
  1137.         trade_init(obj, true)
  1138.         st.allow_trade = blocked_trade(obj)
  1139.         assert(st.allow_trade==true, 'st.allow_trade==true')
  1140.     end
  1141. end
  1142.  
  1143. function update_current_list(obj)
  1144.     assert(is_userdata(obj), "bad argument #1 to 'update_current_list' (userdata excepted, got %s)", type(obj))
  1145.     local st = ts[obj and obj:id()]
  1146.     if st then
  1147.         if m_timers.timer_exists("karlan_trade_timer"..tostring(id)) then
  1148.             local timer = m_timers.get_timer("karlan_trade_timer"..tostring(id))
  1149.             timer:set_reinit(true)
  1150.         end
  1151.     end
  1152. end
  1153.  
  1154. function trade_remove(npc)
  1155.     npc_death{obj_id = get_id(npc)}
  1156. end
  1157.  
  1158. function trade_remove_safe(npc) --// настоятельно рекомендую вызывать при ручном удалении любого объекта (сразу ПОСЛЕ удаления)
  1159.     npc_destroy{obj_id = get_id(npc)}
  1160. end
  1161. --//-----------------------------------------------------------------------------------------------
  1162. --// Global functions
  1163. --//-----------------------------------------------------------------------------------------------
  1164. --// engine hacks [start]
  1165. local hack_marker = -1 --// флаг для синхронизации экшна и статика
  1166. function to_our_bag(partner, item_object) --// обычная функция, нужна для поддержки
  1167.     -- log('to_our_bag called, partner = [%s], item = [%s]:[%s], snakefish_items = [%s]', partner:name(), item_object:section(),item_object:id(),snakefish_items[item_object:section()])
  1168.     dec_snakefish_items(item_object:section())
  1169.     hack_marker = 4
  1170. end
  1171. function to_others_bag(partner, item_object) --// обычная функция, нужна для поддержки
  1172.     -- log('to_others_bag called, partner = [%s], item = [%s]:[%s], snakefish_items = [%s]', partner:name(), item_object:section(),item_object:id(),snakefish_items[item_object:section()])
  1173.     inc_snakefish_items(item_object:section())
  1174.     hack_marker = 5
  1175. end
  1176. function allow_item_to_trade(owner, item, allow_cfg) --// добавлять ли в торговый список этот итем у этого овнера?
  1177.     --// Karlan->ALL: если итем не торгуемый по флагу, либо квестовый, то все равно не добавится, таковы правила и в этом изюминка
  1178.     log('allow_item_to_trade called, owner = [%s], item = [%s]:[%s], item allow in cfg = [%s]', owner:name(), item:section(),item:id(),allow_cfg)
  1179.     if not level.get_talker() and not has_info('ui_trade') then return true end --// Karlan: функция вызывается еще в работе inventory_for_each
  1180.     reset_snakefish_items(owner)
  1181.     hack_marker = 1
  1182.     if not allow_cfg then --// сохраним оригинальный функционал
  1183.         return false --/> в конфиге нет? ну и тут не будет
  1184.     end
  1185.     if owner:id() ~= actor_id and is_trader_mode(owner) then if item:condition() < 0.95 then return false end end
  1186. if not _OLD_TRADE_ then
  1187.     local st = ts[owner and owner:id()]
  1188.     if st and st.barter and st.barter.locked then
  1189.         if st.barter.locked[item:section()] then
  1190.             return false
  1191.         end
  1192.     end
  1193. end
  1194.     if not IsTrader(owner) and is_trader_mode(owner) then
  1195.         log('~item dont added in trade list for [%s]', owner)
  1196.         local weapon = owner:get_wm() and owner:get_wm():get_weapon()
  1197.         if weapon and item:id() == weapon:id() then
  1198.             log('~[T03] weapon got by wmgr ~> [%s]', item)
  1199.             return false
  1200.         end
  1201.         weapon = owner:item_in_slot(PISTOL_SLOT)
  1202.         if weapon and item:id() == weapon:id() then
  1203.             log('~[T03] weapon got by pistol ~> [%s]', item)
  1204.             return false
  1205.         end
  1206.         weapon = owner:item_in_slot(RIFLE_SLOT)
  1207.         if weapon and item:id() == weapon:id() then
  1208.             log('~[T03] weapon got by rifle ~> [%s]', item)
  1209.             return false
  1210.         end
  1211.     end
  1212.     if not is_trader_mode(owner) then
  1213.         return set_snakefish(owner, item, allow_cfg, hack_marker)
  1214.     end
  1215.     return true
  1216. end
  1217. function can_move_to_actor(partner, item, allow_cfg, update) --// актор может покупать итем из листа?
  1218.     log('can_move_to_actor called, partner = [%s], item = [%s]:[%s]', partner:name(), item, allow_cfg)
  1219.     -- get_item_trade_type(item) --// for test
  1220.     hack_marker = 2
  1221. if not _OLD_TRADE_ then
  1222.     local st = ts[partner and partner:id()] --// Karlan->Karlan: в движке проблема исправлена, этот блок здесь остается для сохранения фишки постепенного анлока ассортимента
  1223.     if st and st.barter and st.barter.locked then
  1224.         if st.barter.locked[item:section()] then
  1225.             return false
  1226.         end
  1227.     end
  1228. end
  1229.     if not is_trader_mode(partner) --[[and partner:id() ~= actor_id]] --[[and not update]] then
  1230.         return set_snakefish(partner, item, allow_cfg, hack_marker, update)
  1231.     end
  1232.     return true
  1233. end
  1234. function can_move_to_agent(partner, item, allow_cfg, update) --// нпс может покупать итем из листа?
  1235.     log('can_move_to_agent called, partner = [%s], item = [%s]:[%s]', partner:name(), item, allow_cfg)
  1236.     hack_marker = 3
  1237. if not _OLD_TRADE_ then
  1238.     local st = ts[partner and partner:id()]
  1239.     if st and st.barter and st.barter.allows then
  1240.         if not st.barter.allows[item:section()] then
  1241.             return false
  1242.         end
  1243.     end
  1244. end
  1245.     if is_trader_mode(partner) then
  1246.         local condition = get_conditions(partner, item, 'buy')
  1247.         if item:condition() < condition then
  1248.             reason_msg = partner:character_name()..' не купит предмет в таком состоянии'
  1249.             return false
  1250.         end
  1251.     end
  1252.     if not is_trader_mode(partner) --[[and partner:id() ~= actor_id]] --[[and not update]] then
  1253.         return set_snakefish(partner, item, allow_cfg, hack_marker, update)
  1254.     end
  1255.     return true
  1256. end
  1257. function set_static_text(partner, item, iin_npc_list) --// подсказка: почему итем не торгуется?
  1258.     -- log('set_static_text called, item = [%s]:[%s], item from npc list = [%s]', item:section(),item:id(), iin_npc_list)
  1259.     --// "прямая" конкатенация не работает, формируйте строку заранее, движку нужно скармливать готовую строку, в движке я поставил SetTextST
  1260.     -- local text = iin_npc_list and 'NPC не продает '..item:name() or 'NPC не интересуется '..item:name()
  1261.     -- if text then
  1262.         -- return text
  1263.     -- end
  1264. if not _OLD_TRADE_ then
  1265.     local st = ts[partner and partner:id()]
  1266.     if st and st.barter and st.barter.allows then
  1267.         if not st.barter.allows[item:section()] and not iin_npc_list then
  1268.             local reason = partner:character_name() .. ' не обменивается на предмет ' .. string.lower(get_string_name(item))
  1269.             return reason
  1270.         end
  1271.     end
  1272.     if reason_msg then --// кастомная строка имеет высший приоритет
  1273.         local buff = reason_msg:gsub('$partner', partner:character_name())
  1274.         reason_msg = nil
  1275.         return buff
  1276.     end
  1277.     if hack_marker == 2 and iin_npc_list then
  1278.         local reason = partner:character_name() .. ' не может продать вам эту вещь'
  1279.         return reason
  1280.     end
  1281.     if hack_marker == 3 and not iin_npc_list then
  1282.         local reason = partner:character_name() .. ' не интересуется такого типа предметами'
  1283.         return reason
  1284.     end
  1285. end
  1286.     return nil --/> LPCSTR, если нил - ничего не происходит, т.е. как в оригинале
  1287. end
  1288. function get_item_cost(partner, item_object, buying)
  1289.     -- log('tb,ts = %s,%s', test_buy, test_sell)
  1290.     local st = ts[partner and partner:id()]
  1291. if not _OLD_TRADE_ then
  1292.     if st and st.barter then
  1293.         if is_number(st.barter.ref_price) then
  1294.             return math.ceil((item_object:cost()/st.barter.ref_price))
  1295.         end
  1296.         local deviation = math.random(1, 20)/10
  1297.         return item_object:cost()*deviation --// для свободного обмена мы вычисляем цену корзины исходя из номинальной стоимости предмета + какая-то погрешность
  1298.     end
  1299. end
  1300.     local cost = item_object:section():match('^cash_*(%d+)$') --// 'cash%%_*(%d+)$' | '^cash\_*(%d+)$'
  1301.     if cost then return cost end
  1302.     log('~%s>[section:%s][id:%s]:>', string.rep('~', 41), item_object:section(), item_object:id())
  1303.     local discount = 1
  1304.     if not st.ui_set then
  1305.         st.ui_set = {}
  1306.         st.ui_set.buy = get_trader_goodwill(partner)
  1307.         st.ui_set.sell = 2 - st.ui_set.buy
  1308.     end
  1309.     log('~%s>[get_discounts]:>', string.rep('~', 20))
  1310.     local cfg_discounts = {buy = get_discounts(partner, item_object, 'buy'), sell = get_discounts(partner, item_object, 'sell')}
  1311.     table.print(cfg_discounts, 'cfg_discounts')
  1312.     log('~%s>[calculate_capvalue]:>', string.rep('~', 20))
  1313.     local capitalize_value = calculate_capvalue(item_object, karlan_costs, partner)
  1314.     log('~%s>[get_item_cost]:>', string.rep('~', 20))
  1315.     if buying then --// покупает НПС / актор продает
  1316.         discount = st.ui_set.buy+0.01
  1317.         if not is_trader_mode(partner) then --// Karlan: со всякими люмпенами нечего устраивать чикагскую биржу
  1318.             discount = 0.8+(discount/20)
  1319.             log('* is hartzIV')
  1320.         end
  1321.         discount = discount*cfg_discounts.buy --// Karlan->Karlan: или тут все-же сложение практичнее?
  1322.         log('# discount = %s, cfg_discounts.buy = %s, price = %s', discount, cfg_discounts.buy, capitalize_value)
  1323.     else --// покупает актор / продает НПС
  1324.         discount = st.ui_set.sell+0.01
  1325.         if not is_trader_mode(partner) then
  1326.             discount = 0.9+(discount/10)
  1327.             log('* is hartzIV')
  1328.         end
  1329.         discount = discount*cfg_discounts.sell
  1330.         if not deficit_factor then
  1331.             deficit_factor = 1
  1332.             local cur_cnt, stor_cnt = partner:item_section_count(item_object:section()), deficit_buff[item_object:section()]
  1333.             if is_number(stor_cnt) then deficit_factor = math.clamp(stor_cnt/cur_cnt, 0.9, 3) end
  1334.         end
  1335.         capitalize_value = capitalize_value*deficit_factor
  1336.         log('# discount = %s, cfg_discounts.sell = %s, price = %s', discount, cfg_discounts.sell, capitalize_value)
  1337.     end
  1338.     log('~%s>[section:%s]:<', string.rep('~', 41), item_object:section())
  1339.     local _ceil = math.ceil(capitalize_value*discount)
  1340.     return math.clamp(_ceil, 0, _ceil) --// Karlan->ALL: цена не должна быть меньше нуля
  1341. end
  1342. --// engine hacks [finish]
  1343. --//------------------------------------
  1344. --// Karlan->Karlan: raw вариант окошка, нужно переделать три переменные в одну, две равны, последняя в строгой зависимости от первой
  1345. class "bargain_wnd" (CUIScriptWnd)
  1346. function bargain_wnd:__init(partner) super()
  1347.     if not partner then return end
  1348.     local st = ts[partner and partner:id()]
  1349.     if not st.ui_set then
  1350.         st.ui_set = {}
  1351.         st.ui_set.buy = get_trader_goodwill(partner)
  1352.         st.ui_set.sell = 2 - st.ui_set.buy
  1353.     end
  1354.     self.buy_coeff = st.ui_set.buy
  1355.     self.buy_coeff = math.clamp(self.buy_coeff, 0, 1)
  1356.     self.sell_coeff = st.ui_set.sell
  1357.     self.sell_coeff = math.clamp(self.sell_coeff, 1, 2)
  1358.    
  1359.     self:Init(250,300,510,206)
  1360.    
  1361.     self.fon = CUIStatic()
  1362.     self.fon:Init(0,0,510,206)
  1363.     self.fon:InitTexture("ui_frame_error")
  1364.     self.fon:SetStretchTexture(true)
  1365.     self:AttachChild(self.fon)
  1366.    
  1367.     self.agent_frame = CUIFrameWindow()
  1368.     self.agent_frame:Init(30,50,110,115)
  1369.     self.agent_frame:InitTexture("ui_frame_01")
  1370.     self.fon:AttachChild(self.agent_frame)
  1371.    
  1372.     self.agent_data = CUIStatic()
  1373.     self.agent_data:Init(0,10,110,60)
  1374.     self.agent_data:SetTextComplexMode(true)
  1375.     self.agent_data:SetTextST(string.format(' %s\\n Уровень: %s\\n ОТНОШЕНИЯ\\n Общие: %s\\n Персональные: %s', partner:character_name(), partner:character_rank(), partner:general_goodwill(actor), partner:goodwill(actor)))
  1376.     self.agent_data:SetTextAlign(2)
  1377.     self.agent_data:SetTextX(-5)
  1378.     self.agent_frame:AttachChild(self.agent_data)
  1379.  
  1380.     self.track_bar = CUITrackBarScript() --// из-за кривой реализации оригинального пришлось писать новый класс который специально предназначен для скриптов
  1381.     self.track_bar:Init(200,90,245,21)
  1382.     self.track_bar:SetValue(self.buy_coeff) --// тут надо устанавливать сохраненное из стоража при повторном запуске
  1383.     log('track bar value: %s, btn width = %s', self.track_bar:GetValue(), self.track_bar:GetBtn():GetWidth())
  1384.     self:Register(self.track_bar, "track_bar")
  1385.     self.fon:AttachChild(self.track_bar)
  1386.    
  1387.     self.track_data = CUIStatic()
  1388.     self.track_data:Init(120,40,400,30)
  1389.     self.track_data:SetTextComplexMode(true)
  1390.     self:UpdateCostText()
  1391.     self.track_data:SetTextAlign(2)
  1392.     self.track_data:SetFont(GetFontGraffiti22Russian())
  1393.     self.track_data:SetTextColor(255,238,153,26)
  1394.     self.fon:AttachChild(self.track_data)
  1395.    
  1396.     self.reason = CUIStatic()
  1397.     self.reason:Init(130,115,400,20)
  1398.     self.reason:SetText("")
  1399.     self.reason:SetTextAlign(2)
  1400.     self.reason:SetFont(GetFontLetterica16Russian())
  1401.     self.reason:SetTextColor(200,255,255,255)
  1402.     self.fon:AttachChild(self.reason)
  1403.    
  1404.     self.btn_left = CUI3tButton ()
  1405.     self.btn_left:Init(155,88,40,29)
  1406.     self.btn_left:InitTexture('ui_button_arrow_left')
  1407.     self.btn_left:SetTextComplexMode(true)
  1408.     self:Register(self.btn_left, "btn_left")
  1409.     self:AddCallback("btn_left",    ui_events.BUTTON_CLICKED, function(self)
  1410.     self.buy_coeff = self.buy_coeff-0.01; self.buy_coeff = math.clamp(self.buy_coeff, 0, 1)
  1411.     self.sell_coeff = self.sell_coeff+0.01; self.sell_coeff = math.clamp(self.sell_coeff, 1, 2)
  1412.     self.track_bar:SetValue(self.buy_coeff)
  1413.     self:UpdateCostText()
  1414.     end,    self)
  1415.     self.fon:AttachChild(self.btn_left)
  1416.    
  1417.     self.btn_right = CUI3tButton ()
  1418.     self.btn_right:Init(445,88,40,29)
  1419.     self.btn_right:InitTexture('ui_button_arrow_right')
  1420.     self:Register(self.btn_right, "btn_right")
  1421.     self:AddCallback("btn_right",    ui_events.BUTTON_CLICKED, function(self)
  1422.     self.buy_coeff = self.buy_coeff+0.01; self.buy_coeff = math.clamp(self.buy_coeff, 0, 1)
  1423.     self.sell_coeff = self.sell_coeff-0.01; self.sell_coeff = math.clamp(self.sell_coeff, 1, 2)
  1424.     self.track_bar:SetValue(self.buy_coeff)
  1425.     self:UpdateCostText()
  1426.     end,    self)
  1427.     self.fon:AttachChild(self.btn_right)
  1428.    
  1429.     self.btn = CUI3tButton ()
  1430.     self.btn:Init("ui\\ui_btn_01", 250,140,113,26)
  1431.     self.btn:SetText("OK")
  1432.     self.btn:SetFont(GetFontLetterica16Russian())
  1433.     self.btn:SetTextColor(255,216,186,140)
  1434.     self.btn:SetTextY(2)
  1435.     self:Register(self.btn, "btn")
  1436.     self:AddCallback("btn",    ui_events.BUTTON_CLICKED, function(self)
  1437.     log('get_npc_goodwill = (%s/%s):<%s>', get_npc_goodwill(partner, actor)/1000, get_weighted_goodwill(partner, actor)/1000, self.buy_coeff)
  1438.     log('get_trader_goodwill = [%s]:(%s/%s)', get_trader_goodwill(partner), get_npc_goodwill(partner, actor)/1000, get_factions_community_goodwill(partner:character_community(), actor_id)/5000)
  1439.     if get_trader_goodwill(partner) < self.buy_coeff and self.buy_coeff > 0 then self:UpdateReason(string.format('%s не согласен на такие условия.', partner:character_name())) return end
  1440.     st.ui_set.buy, st.ui_set.sell = math.rounding(self.buy_coeff*100)/100, math.rounding(self.sell_coeff*100)/100
  1441.     self:GetHolder():start_stop_menu(self,true); bargain_link = nil
  1442.     end,    self)
  1443.     self.fon:AttachChild(self.btn)
  1444.    
  1445.     self.btn_quit = CUI3tButton ()
  1446.     self.btn_quit:Init("ui\\ui_btn_01", 370,140,113,26)
  1447.     self.btn_quit:SetText("Отмена")
  1448.     self.btn_quit:SetFont(GetFontLetterica16Russian())
  1449.     self.btn_quit:SetTextColor(255,216,186,140)
  1450.     self.btn_quit:SetTextY(2) --// default: 0
  1451.     self:Register(self.btn_quit, "btn_quit")
  1452.     self:AddCallback("btn_quit",    ui_events.BUTTON_CLICKED, function(self) self:GetHolder():start_stop_menu(self,true); bargain_link = nil end,    self)
  1453.     self.fon:AttachChild(self.btn_quit)
  1454. end
  1455. function bargain_wnd:UpdateCostText()
  1456.     self.track_data:SetText(string.exformat('Покупка предметов за %s% от рыночной цены\\nПродажа предметов за %s% от рыночной цены', math.rounding(self.buy_coeff*100), math.rounding(self.sell_coeff*100)))
  1457. end
  1458. function bargain_wnd:UpdateReason(text)
  1459.     self.reason:SetText(text)
  1460.     self.reason_timer = time_global() + 2000
  1461. end
  1462. function bargain_wnd:Update()
  1463.     CUIScriptWnd.Update(self)
  1464.     if self.track_bar:IsChanged() then
  1465.         log('changed value on (%s|%s)', self.track_bar:GetBackupValue(), self.track_bar:GetValue())
  1466.         local add = self.track_bar:GetValue()-self.track_bar:GetBackupValue()
  1467.         self.buy_coeff = self.buy_coeff+add
  1468.         self.sell_coeff = self.sell_coeff-add
  1469.         self.buy_coeff = math.clamp(self.buy_coeff, 0, 1)
  1470.         self.sell_coeff = math.clamp(self.sell_coeff, 1, 2)
  1471.         self:UpdateCostText()
  1472.         self.track_bar:SetValue(self.buy_coeff) --// необходимо осуществлять постоянный контроль позиции ползунка, иначе будет расхождение ползунка и реального значения
  1473.     end
  1474.     if self.reason_timer and self.reason_timer < time_global() then
  1475.         self.reason:SetText("")
  1476.     end
  1477. end
  1478.  
  1479. function show_bargain_wnd(partner)
  1480.     bargain_link = bargain_wnd(partner)
  1481.     level.start_stop_menu(bargain_link, false)
  1482. end
  1483. --//------------------------------------
  1484. --// функция для замены отдельного участка торговли
  1485. --// работает только для конфигов формируемых из скриптов (не важно динамические или нет)
  1486. --// имя секции обязательно должно совпадать с именем ключа конфига, ваш конфиг не нарушится, так как эта функция сама как нужно переименует секцию
  1487. function replace_list_part(obj, list) --// TODO: перепроверить
  1488.     local st = ts[obj:id()]
  1489.     if not st then return end
  1490.     local _type = list:match('^%b[]'):sub(2,-2)
  1491.     if _type:match('s$') and not st.buy_supplies then
  1492.         st.cfg_ltx = (st.cfg_ltx~=list) and st.cfg_ltx .. '\\n' .. list or abort()
  1493.         st.config = create_ini_file(st.cfg_ltx)
  1494.         this['set_'.._type](obj, _type)
  1495.         return
  1496.     end
  1497.     local str = cfg_get_string(st.config, "trader", _type, obj)
  1498.     --// active_section в supplies в любом случае будет существовать, так как st.buy_supplies ~= nil
  1499.     local active_section = xr_logic.pick_section_from_condlist(obj, parse_condlist(str))
  1500.     list = list:gsub(_type, '['..active_section..']')
  1501.     st.cfg_ltx = st.cfg_ltx:gsub('%['..active_section..'[^%[]+', list)
  1502.     st.config = create_ini_file(st.cfg_ltx)
  1503.     this['set_'.._type](obj, active_section)
  1504. end
  1505. --//-----------------------------------------------------------------------------------------------
  1506. --/////////////////////////////////////////////////////////////////////////////////////////////////
  1507. --/////////////////////////////////////////////////////////////////////////////////////////////////
  1508. --/////////////////////////////////////////////////////////////////////////////////////////////////
  1509. --/////////////////////////////////////////////////////////////////////////////////////////////////
  1510. --//-----------------------------------------------------------------------------------------------
  1511. --// Karlan->ALL: консервация, архиторговля, оставляю для тех, кого впирает оригинальная торговля
  1512. --//-------------------------
  1513. local trade_manager = {}
  1514. function trade_init_old(npc, cfg)
  1515.     trade_manager[npc:id()] = {}
  1516.     trade_manager[npc:id()].cfg_ltx = cfg
  1517.     trade_manager[npc:id()].config = ini_file(cfg)
  1518.     local str = cfg_get_string(trade_manager[npc:id()].config, "trader", "buy_condition", npc, true, "")
  1519.     if str == nil then abort("Incorrect trader settings. Cannot find buy_condition. [%s]->[%s]", npc:name(), cfg) end
  1520.     trade_manager[npc:id()].buy_condition = parse_condlist(str)
  1521.     str = cfg_get_string(trade_manager[npc:id()].config, "trader", "sell_condition", npc, true, "")
  1522.     if str == nil then abort("Incorrect trader settings. Cannot find sell_condition. [%s]->[%s]", npc:name(), cfg) end
  1523.     trade_manager[npc:id()].sell_condition = parse_condlist(str)
  1524.     str = cfg_get_string(trade_manager[npc:id()].config, "trader", "buy_supplies", npc, false, "")
  1525.     if str ~= nil then trade_manager[npc:id()].buy_supplies = parse_condlist(str) end
  1526. end
  1527. function update_old(npc)
  1528.     local tt = trade_manager[npc:id()]
  1529.     if tt == nil then return end
  1530.     if tt.update_time ~= nil and tt.update_time < time_global() then return end
  1531.     tt.update_time = time_global() + 3600000
  1532.     local str = xr_logic.pick_section_from_condlist(npc, tt.buy_condition)
  1533.     if(str=="" or str==nil) then abort("Wrong section in buy_condition condlist for npc [%s]!", npc:name()) end
  1534.     if tt.current_buy_condition ~= str then
  1535.         npc:buy_condition(tt.config, str)
  1536.         tt.current_buy_condition = str
  1537.     end
  1538.     str = xr_logic.pick_section_from_condlist(npc, tt.sell_condition)
  1539.     if(str=="" or str==nil) then abort("Wrong section in buy_condition condlist for npc [%s]!", npc:name()) end
  1540.     if tt.current_sell_condition ~= str then
  1541.         npc:sell_condition(tt.config, str)
  1542.         tt.current_sell_condition = str
  1543.     end
  1544.     if tt.buy_supplies == nil then return end
  1545.     str = xr_logic.pick_section_from_condlist(npc, tt.buy_supplies)
  1546.     if(str=="" or str==nil) then abort("Wrong section in buy_condition condlist for npc [%s]!", npc:name()) end
  1547.     if tt.current_buy_supplies ~= str then
  1548.         if tt.resuply_time ~= nil and tt.resuply_time < time_global() then return end
  1549.         npc:buy_supplies(tt.config, str)
  1550.         tt.current_buy_supplies = str
  1551.         tt.resuply_time = time_global() + 24*3600000
  1552.     end
  1553. end
  1554. function save_old(obj, packet)
  1555.     local tt = trade_manager[obj:id()]
  1556.     -- set_save_marker(packet, "save", false, "trade_manager")
  1557.     if tt == nil then
  1558.         packet:w_bool(false)
  1559.         return
  1560.     else
  1561.         packet:w_bool(true)
  1562.     end
  1563.     packet:w_stringZ(tt.cfg_ltx)
  1564.     if tt.current_buy_condition == nil then
  1565.         packet:w_stringZ("")
  1566.     else
  1567.         packet:w_stringZ(tt.current_buy_condition)
  1568.     end
  1569.     if tt.current_sell_condition == nil then
  1570.         packet:w_stringZ("")
  1571.     else
  1572.         packet:w_stringZ(tt.current_sell_condition)
  1573.     end
  1574.     if tt.current_buy_supplies == nil then
  1575.         packet:w_stringZ("")
  1576.     else
  1577.         packet:w_stringZ(tt.current_buy_supplies)
  1578.     end
  1579.     local cur_tm = time_global()
  1580.     if tt.update_time == nil then
  1581.         packet:w_s32(-1)
  1582.     else
  1583.          packet:w_s32(tt.update_time - cur_tm)
  1584.     end
  1585.     if tt.resuply_time == nil then
  1586.         packet:w_s32(-1)
  1587.     else
  1588.          packet:w_s32(tt.resuply_time - cur_tm)
  1589.     end
  1590.     -- set_save_marker(packet, "save", true, "trade_manager")
  1591. end
  1592. function load_old(obj, packet)
  1593.     -- set_save_marker(packet, "load", false, "trade_manager")
  1594.     local a = packet:r_bool()
  1595.     if a == false then return end
  1596.     trade_manager[obj:id()] = {}
  1597.     local tt = trade_manager[obj:id()]
  1598.     tt.cfg_ltx = packet:r_stringZ()
  1599.     tt.config = ini_file(tt.cfg_ltx)
  1600.     a = packet:r_stringZ()
  1601.     if a ~= "" then
  1602.         tt.current_buy_condition = a
  1603.         obj:buy_condition(tt.config, a)
  1604.     end
  1605.     a = packet:r_stringZ()
  1606.     if a ~= "" then
  1607.         tt.current_sell_condition = a
  1608.         obj:sell_condition(tt.config, a)
  1609.     end
  1610.     a = packet:r_stringZ()
  1611.     if a ~= "" then
  1612.         tt.current_buy_supplies = a
  1613.     end
  1614.     local cur_tm = time_global()
  1615.     a = packet:r_s32()
  1616.     if a ~= -1 then
  1617.         tt.update_time = cur_tm + a
  1618.     end
  1619.     a = packet:r_s32()
  1620.     if a ~= -1 then
  1621.         tt.resuply_time = cur_tm + a
  1622.     end
  1623.     -- set_save_marker(packet, "load", true, "trade_manager")
  1624. end
  1625. --//---------------------------------------------
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement