mikeyy

AIShopUtilities.lua

Jun 19th, 2011
151
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. -- [MOD] Increased item comparison for selling to fixed amount to prevent sell loops.
  2. -- [MOD] Announce Citadel Upgrades
  3. -- [MOD] TE All WaitTicks set to 10 = 1 second
  4.  
  5. local ValidateAbility = import('/lua/common/ValidateAbility.lua')
  6. local ValidateInventory = import('/lua/common/ValidateInventory.lua')
  7. local ValidateShop = import('/lua/common/ValidateShop.lua')
  8. local CanPickItem = import('/lua/common/ValidateShop.lua').CanPickItem
  9. local AIChatGlobals = import('/lua/sim/AI/AIChatGlobals.lua').AIChat
  10. local Buff = import('/lua/sim/Buff.lua')
  11. local Common = import('/lua/common/CommonUtils.lua')
  12. local ValidateUpgrade = import('/lua/common/ValidateUpgrade.lua')
  13. local Upgrades = import('/lua/common/CitadelUpgrades.lua').Upgrades
  14.  
  15. local AIUtils = import('/lua/sim/ai/aiutilities.lua')
  16.  
  17. local AIGlobals = import('/lua/sim/ai/AIGlobals.lua')
  18.  
  19. local ITEM_RESELL_MULTIPLIER = import('/lua/game.lua').GameData.SellMult
  20. local ShopDistance = import('/lua/game.lua').GameData.ShopDistance
  21.  
  22.  
  23. --===== Shop functions ===== --
  24. function ShopCleanup(unit, action)
  25.     if unit.Sync.ShopId then
  26.         --Leave the shop
  27.         commandData = {
  28.             TaskName = 'EndShopTask',
  29.         }
  30.         IssueScript( {unit}, commandData )
  31.  
  32.         WaitTicks(6)
  33.         if unit:IsDead() then
  34.             return false
  35.         end
  36.     end
  37.  
  38.     return true
  39. end
  40.  
  41. function MoveToShop( unit, shop, aiBrain )
  42.     local path = AIUtils.GetSafePathBetweenPoints(aiBrain, unit.Position, shop.Position)
  43.     local cmd = false
  44.     if not path then
  45.         return IssueMove( {unit}, shop.Position )
  46.     end
  47.  
  48.     for k,v in path do
  49.         cmd = IssueMove( {unit}, v )
  50.     end
  51.     return cmd
  52. end
  53.  
  54. --Set up the sell routine
  55. function SellItemAction( unit, action )
  56.     local actionBp = HeroAIActionTemplates[action.ActionName]
  57.     local item, itemPri = FindLowestInventoryItem( unit, action, actionBp.InventoryType )
  58.  
  59.     if not item then
  60.         return false
  61.     end
  62.  
  63.     local itemType = item.Sync.Name
  64.  
  65.     local shopPos = false
  66.     if unit.ShopInformation.BuyItem['Purchase Base Item - Use Sell Refund'] and unit.ShopInformation.BuyItem['Purchase Base Item - Use Sell Refund'].PurchaseShopPosition then
  67.         shopPos = unit.ShopInformation.BuyItem['Purchase Base Item - Use Sell Refund'].PurchaseShopPosition
  68.     else
  69.         shopPos = unit.Position
  70.     end
  71.     local shop = unit:GetAIBrain().GoalPlanner:GetClosestFriendlyShop(shopPos)
  72.  
  73.     if SellItem( unit, itemType, shop ) == 'Stuck' then
  74.         action:LockAction( 2 )
  75.     end
  76.  
  77.     --if unit:GetArmy() == 1 then
  78.     --   WARN('*AI SHOP DEBUG: Brain= ' .. unit:GetArmy() .. ' - Selling item= ' .. itemType .. ' - Priority= ' .. itemPri)
  79.     --end
  80.  
  81.     ShopCleanup(unit, action)
  82. end
  83.  
  84. --If we have an item in the slot, we can sell it
  85. function SellItemStatus( unit, action )
  86.     local aiBrain = unit:GetAIBrain()
  87.  
  88.     if not unit.ShopInformation.SellItem[action.ActionName] then
  89.         unit.ShopInformation.SellItem[action.ActionName] = {}
  90.     end
  91.     local actionInformation = unit.ShopInformation.SellItem[action.ActionName]
  92.     actionInformation.SellItem = false
  93.     actionInformation.SellItemPriority = false
  94.     actionInformation.SellItemRefund = false
  95.     actionInformation.SellItemInventoryType = false
  96.  
  97.     local actionBp = HeroAIActionTemplates[action.ActionName]
  98.     local item,priority,shopType = FindLowestInventoryItem( unit, action, actionBp.InventoryType )
  99.     if not item or not priority or not shopType then
  100.         return false
  101.     end
  102.  
  103.     local nearby = aiBrain.GoalPlanner:GetFriendlyShop(shopType, unit:GetPosition())
  104.     if not nearby then
  105.         return false
  106.     end
  107.  
  108.     actionInformation.SellItemRefund = math.floor(item.Sync.PurchasePrice * ITEM_RESELL_MULTIPLIER )
  109.     actionInformation.SellItem = item.Sync.Name
  110.     actionInformation.SellItemPriority = priority
  111.     actionInformation.SellItemInventoryType = actionBp.InventoryType
  112.  
  113.     --if (unit:GetArmy() == 1) then
  114.     --   LOG('*AI SHOP DEBUG: SellItemStatus - Item= ' .. item.Sync.Name .. ' - ItemPriority = ' .. priority)
  115.     --end
  116.  
  117.     return true
  118. end
  119.  
  120. --Get weight of selling this item
  121. function SellItemWeights(action, aiBrain, agent, initialAgent)
  122.     if not agent.WorldStateData.CanMove then
  123.         return false
  124.     end
  125.  
  126.     local actionInformation = initialAgent.ShopInformation.SellItem[action.ActionName]
  127.     if not actionInformation or not actionInformation.SellItem then
  128.         return false
  129.     end
  130.  
  131.     agent.WorldStateData.ItemSold = true
  132.     agent.WorldStateData.ItemSoldPriority = actionInformation.SellItemPriority
  133.     agent.WorldStateConsistent = false
  134.  
  135.     agent.Gold = agent.Gold + actionInformation.SellItemRefund
  136.     agent.InventoryData[actionInformation.SellItemInventoryType] = agent.InventoryData[actionInformation.SellItemInventoryType] + 1
  137.  
  138.     return { PurchaseItems = ( actionInformation.SellItemPriority ), }, 0
  139. end
  140.  
  141. --Moves to shop, sells item, closes shop
  142. function SellItem( unit, item, shop )
  143.     if unit.Sync.ShopId and not ShopCleanup(unit) then
  144.         return false
  145.     end
  146.  
  147.     local aiBrain = unit:GetAIBrain()
  148.     if VDist3XZSq( unit.Position, shop.Position ) > 200 then
  149.         local cmd = MoveToShop( unit, shop, aiBrain )
  150.         while VDist3XZSq( unit.Position, shop.Position ) > 200 do
  151.             WaitTicks(6)
  152.  
  153.             if unit:IsDead() or shop:IsDead() then
  154.                 return false
  155.             end
  156.         end
  157.     end
  158.  
  159.     --Go to the shop
  160.     local commandData = {
  161.         TaskName = 'BeginShopTask',
  162.         TargetId = shop:GetEntityId(),
  163.     }
  164.     local cmd = IssueScript( {unit}, commandData )
  165.  
  166.     local stuckCount = 0
  167.     local oldPos = table.copy(unit.Position)
  168.     if unit:IsDead() or shop:IsDead() then
  169.         return
  170.     end
  171.  
  172.     while shop and not shop:IsDead() and shop.CheckShopper and not shop:CheckShopper(unit) do
  173.         WaitTicks(6)
  174.  
  175.         if unit:IsDead() or shop:IsDead() then
  176.             return
  177.         end
  178.  
  179.         local newPos = unit:GetPosition()
  180.         if newPos[1] == oldPos[1] and newPos[2] == oldPos[2] and newPos[3] == oldPos[3] then
  181.             stuckCount = stuckCount + 1
  182.         else
  183.             stuckCount = 0
  184.         end
  185.  
  186.         if stuckCount >= 10 then
  187.             LOG('*AI DEBUG: Unit Stuck getting to shop - Sell Item')
  188.             return 'Stuck'
  189.         end
  190.  
  191.         oldPos = table.copy(newPos)
  192.     end
  193.  
  194.     if unit:IsDead() or shop:IsDead() or not shop.CheckShopper or not shop:CheckShopper(unit) then
  195.         return false
  196.     end
  197.  
  198.     local itemEntity = unit.Inventory[ Items[item].InventoryType ]:FindItem( item )
  199.  
  200.     if not itemEntity then
  201.         return false
  202.     end
  203.  
  204.     --Sell the item
  205.     CODE_SellItem(unit, itemEntity:GetEntityId())
  206.     WaitTicks(6)
  207.     --WARN( LOC(unit:GetAIBrain().Nickname) .. ' Sold: ' .. LOC(Items[item].DisplayName))
  208.  
  209.  
  210.     return true
  211. end
  212.  
  213.  
  214. --==== Item purchasing functions ==== --
  215.  
  216. --== Action CalculateWeights Function == --
  217. function PurchaseItemCalculateWeights( action, aiBrain, agent, initialAgent )
  218.  
  219.  
  220.  
  221.  
  222.     if not agent.WorldStateData.CanMove then
  223.         return false
  224.     end
  225.  
  226.     local actionInformation = initialAgent.ShopInformation.BuyItem[action.ActionName]
  227.  
  228.     if agent.InventoryData[actionInformation.PurchaseItemInventoryType] <= 0 then
  229.         return false
  230.     end
  231.  
  232.     local actionBp = HeroAIActionTemplates[action.ActionName]
  233.     if actionBp.UseSellRefund and not agent.WorldStateData.ItemSold then
  234.         return false
  235.     end
  236.  
  237.     local actionBp = HeroAIActionTemplates[action.ActionName]
  238.     if not actionBp.UseSellRefund and agent.WorldStateData.ItemSold then
  239.         return false
  240.     end
  241.  
  242.     if not actionInformation or not actionInformation.PurchaseItem then
  243.         return false
  244.     end
  245.  
  246.     if actionInformation.PurchaseItemCost > agent.Gold then
  247.         return false
  248.     end
  249.  
  250.     --if actionBp.UseSellRefund then
  251.     --   if agent.WorldStateData.ItemSoldPriority >= actionInformation.PurchaseItemPriority then
  252.     --       return false
  253.     --   end
  254.     --end
  255.  
  256.     local shopPos = actionInformation.PurchaseShopPosition
  257.     if agent.Gold - actionInformation.PurchaseItemCost < 0 then
  258.         return false
  259.     end
  260.     agent.Gold = agent.Gold - actionInformation.PurchaseItemCost
  261.  
  262.     agent.InventoryData[actionInformation.PurchaseItemInventoryType] = agent.InventoryData[actionInformation.PurchaseItemInventoryType] - 1
  263.  
  264.     if not agent.AgentHasMoved then
  265.         distance = initialAgent.GOAP.BrainAsset:GetDistance( actionInformation.PurchaseShopType, 'Ally' )
  266.     else
  267.         distance = VDist3XZ( agent.Position, shopPos )
  268.     end
  269.  
  270.     if not distance then
  271.         return false
  272.     end
  273.  
  274.     agent:SetPosition( shopPos )
  275.  
  276.     return { PurchaseItems = ( actionInformation.PurchaseItemPriority * -1 ), }, math.max( distance / agent.Speed, 2 )
  277. end
  278.  
  279. --== StatusTrigger for buying items == --
  280. function PurchaseItemStatus(unit, action)
  281.     local aiBrain = unit:GetAIBrain()
  282.     local actionBp = HeroAIActionTemplates[action.ActionName]
  283.     unit.ShopInformation.CanBuyItem[action.ActionName] = false
  284.  
  285.     if not unit.ShopInformation.BuyItem[action.ActionName] then
  286.         unit.ShopInformation.BuyItem[action.ActionName] = false
  287.     end
  288.     local actionInformation = {}
  289.  
  290.     actionInformation.PurchaseItem = false
  291.     actionInformation.PurchaseItemPriority = false
  292.     actionInformation.PurchaseShopPosition = false
  293.     actionInformation.PurchaseItemCost = false
  294.     actionInformation.PurchaseItemInventoryType = false
  295.     actionInformation.PurchaseItemBaseShopType = false
  296.  
  297.     unit.ShopInformation.BuyItem[action.ActionName] = actionInformation
  298.  
  299.     local sellItemData = {}
  300.     if actionBp.UseSellRefund then
  301.         for k,v in unit.ShopInformation.SellItem do
  302.             if not v.SellItem then
  303.                 continue
  304.             end
  305.  
  306.             sellItemData[v.SellItemInventoryType] = v
  307.         end
  308.     end
  309.  
  310.  
  311.  
  312.     if actionBp.UseSellRefund then
  313.         if table.empty(sellItemData) then
  314.             return false
  315.         end
  316.     end
  317.  
  318.     local bestItem = FindBestBaseItem( unit, aiBrain, action, sellItemData )
  319.     if not bestItem then
  320.         return false
  321.     end
  322.     --[MOD] TE Citadel Check
  323.  
  324.     local bestCitadel = FindBestCitadelUpgrade( unit, aiBrain, action)
  325.     if bestCitadel and bestCitadel.ItemPriority > bestItem.ItemPriority then
  326.         return false
  327.     end
  328.  
  329.     local nearby = bestItem.Shop
  330.     if not nearby then
  331.         return false
  332.     end
  333.  
  334.  
  335.  
  336.  
  337.     --TODO: VALIDATE IF WE CAN BUY MORE OF A STACKED ITEM HERE
  338.     if ValidateInventory.NumFreeSlots( unit.Inventory[bestItem.InventoryType] ) <= 0 and not actionBp.UseSellRefund then
  339.         return false
  340.     end
  341.  
  342.     actionInformation.PurchaseItem = bestItem.ItemName
  343.     actionInformation.PurchaseItemPriority = bestItem.ItemPriority
  344.     actionInformation.PurchaseShopType = bestItem.ShopType
  345.     actionInformation.PurchaseItemBaseShopType = bestItem.BaseShopType
  346.     actionInformation.PurchaseShopPosition = bestItem.Shop:GetPosition()
  347.     actionInformation.PurchaseItemCost = bestItem.ItemCost
  348.     actionInformation.PurchaseItemInventoryType = bestItem.InventoryType
  349.  
  350.     unit.ShopInformation.BuyItem[action.ActionName] = actionInformation
  351.     unit.ShopInformation.CanBuyItem[action.ActionName] = true
  352.  
  353.     --if (unit:GetArmy() == 1) then
  354.     --   LOG('*AI SHOP DEBUG: ' .. action.ActionName .. 'Status - Item= ' .. bestItem.ItemName
  355.     --       .. ' - ItemPriority = ' .. bestItem.ItemPriority)
  356.     --end
  357.  
  358.  
  359.  
  360.     return true
  361. end
  362.  
  363. --== Action ActionFunction == --
  364. function PurchaseItemAction( unit, action )
  365.     local aiBrain = unit:GetAIBrain()
  366.  
  367.     local bestItem = FindBestBaseItem( unit, aiBrain, action )
  368.  
  369.     if not bestItem then
  370.         return
  371.     end
  372.  
  373.     --if (unit:GetArmy() == 1) then
  374.     --   WARN('*AI SHOP DEBUG: Army= ' .. unit:GetArmy() .. ' - Purchasing item= ' .. bestItem.ItemName
  375.     --       .. ' - Priority= ' .. bestItem.ItemPriority
  376.     --       .. ' - Quantity= ' .. bestItem.NumPurchase .. '\n\n' )
  377.     --end
  378.  
  379.     --WARN(aiBrain.Nickname.. ' Buying ' .. bestItem.ItemName)
  380.     if PurchaseItem( unit, bestItem.ItemName, bestItem.Shop, bestItem.NumPurchase, bestItem.BaseShopType ) == 'Stuck' then
  381.         action:LockAction( 2 )
  382.     end
  383.  
  384.     ShopCleanup(unit, action)
  385.  
  386.     unit.GOAP:ForcePurchaseUpdate()
  387. end
  388.  
  389. --Purchase item - moves unit to shop, buys item, closes shop
  390. function PurchaseItem( unit, itemName, shop, quantity, baseShopType )
  391.     local shopItemId = false
  392.     local aiBrain = unit:GetAIBrain()
  393.     local shopBp = false
  394.     if baseShopType then
  395.         shopBp = GetUnitBlueprintByName(baseShopType)
  396.         shopItemId = FindShopItemId( itemName, nil, shopBp )
  397.     else
  398.         shopBp = shop:GetBlueprint()
  399.         shopItemId = FindShopItemId( itemName, shop )
  400.     end
  401.  
  402.     quantity = quantity or 1
  403.  
  404.     if not shopItemId then
  405.         return false
  406.     end
  407.  
  408.  
  409.         --[MOD] TE Check idol priority against current idol
  410.     local sellIdol = false
  411.     local syncData = Common.GetSyncData(unit)
  412.     local itemEntity
  413.     local sellItem
  414.     if syncData.Inventory.Generals then
  415.         for slot, invData in syncData.Inventory.Generals.Slots do
  416.             local invItemData = EntityData[invData[1]].Data  --[MOD] TE Refrence .Data so item def can get blueprint.
  417.             local invItemDef = Items[invItemData.BlueprintId]
  418.             if invItemDef then
  419.                 local idolInSlot = string.sub(invItemDef.Name, 1, -5)
  420.                 local idolInShop = string.sub(shopItemId, 1, -5)
  421.                 if idolInSlot == idolInShop then
  422.                     sellIdol = true
  423.                     sellItem = invItemDef
  424.                     itemEntity = unit.Inventory[ Items[invItemDef.Name].InventoryType ]:FindItem( invItemDef.Name )
  425.                     --WARN(aiBrain.Nickname .. ' Sell Idol ' .. invItemDef.Name.. ' Type ' .. Items[invItemDef.Name].InventoryType .. ' , Entity - '.. tostring(itemEntity) )
  426.                 end
  427.             end
  428.         end
  429.     end
  430.  
  431.  
  432.     if not CanPickItem( unit, shopBp, shopItemId ) then
  433.         return false
  434.     end
  435.  
  436.     if unit.Sync.ShopId and not ShopCleanup(unit) then
  437.         return false
  438.     end
  439.  
  440.  
  441.     if VDist3XZSq( unit.Position, shop.Position ) > 200 then
  442.         local cmd = MoveToShop( unit, shop, aiBrain )
  443.         while VDist3XZSq( unit.Position, shop.Position ) > 200 do
  444.             WaitTicks(6)
  445.  
  446.             if unit:IsDead() or shop:IsDead() then
  447.                 return false
  448.             end
  449.         end
  450.     end
  451.  
  452.     --Go to the shop
  453.     local commandData = {
  454.         TaskName = 'BeginShopTask',
  455.         TargetId = shop:GetEntityId(),
  456.     }
  457.     local cmd = IssueScript( {unit}, commandData )
  458.  
  459.     local oldPos = table.copy( unit:GetPosition() )
  460.     local stuckCount = 0
  461.  
  462.     --Wait until we are in the shop
  463.     while not shop:CheckShopper(unit) do
  464.         WaitTicks(6)
  465.  
  466.         if unit:IsDead() or shop:IsDead() then
  467.             return
  468.         end
  469.  
  470.         local newPos = unit:GetPosition()
  471.         if newPos[1] == oldPos[1] and newPos[2] == oldPos[2] and newPos[3] == oldPos[3] then
  472.             stuckCount = stuckCount + 1
  473.         else
  474.             stuckCount = 0
  475.         end
  476.  
  477.         if stuckCount >= 10 then
  478.             LOG('*AI DEBUG: Unit Stuck getting to shop - Sell Item')
  479.             return 'Stuck'
  480.         end
  481.  
  482.         oldPos = table.copy(newPos)
  483.     end
  484.  
  485.  
  486.     if sellIdol then
  487.         --WARN('Entity - ' .. tostring(itemEntity) )
  488.         --WARN( LOC(unit:GetAIBrain().Nickname) .. ' Idol Sold: ' .. LOC(sellItem.DisplayName) )
  489.         CODE_SellItem(unit, itemEntity:GetEntityId())
  490.         WaitTicks(6)
  491.     end
  492.  
  493.     for i=1,quantity do
  494.         --Purchase the item
  495.         CODE_PurchaseItem(unit, shopItemId, shopBp.BlueprintId)
  496.  
  497.         WaitTicks(6)
  498.     end
  499.  
  500.  
  501.     if shopBp.BlueprintId == 'ugbshop05' then
  502.         local def = shopBp.Shop.Tree[shopItemId]
  503.         if Items[def.ItemBP].DisplayName then
  504.             WaitTicks(6)
  505.             local announcement = "Artifact Purchased: "..LOC(Items[def.ItemBP].DisplayName)
  506.             AIUtils.AIChat(unit, announcement)
  507.  
  508.         end
  509.     end
  510.  
  511.     if shopBp.BlueprintId == 'ugbshop09' then
  512.         local def = shopBp.Shop.Tree[shopItemId]
  513.         if Items[def.ItemBP].DisplayName then
  514.             WaitTicks(6)
  515.             ---- local announcement = "Idol Purchased: "..LOC(Items[def.ItemBP].DisplayName)
  516.             ---- AIUtils.AIChat(unit, announcement)
  517.             --WARN( LOC(aiBrain.Nickname) .. ' Idol Purchased: ' .. LOC(Items[def.ItemBP].DisplayName))
  518.         end
  519.     end
  520.  
  521.     return true
  522. end
  523.  
  524.  
  525.  
  526. --==== Helper functions ==== --
  527.  
  528. --iterates through the shop's items and sees if there is an item found
  529. function FindShopItemId( itemName, shop, shopBp )
  530.     if shop then
  531.         shopBp = shop:GetBlueprint()
  532.     end
  533.  
  534.     if not shopBp then
  535.         WARN('AI ERROR: Could not find a shop bp')
  536.         return false
  537.     end
  538.  
  539.     for shopItemId,item in shopBp.Shop.Tree do
  540.         if item.ItemBP == itemName then
  541.             return shopItemId
  542.         end
  543.     end
  544.  
  545.     return false
  546. end
  547.  
  548. function GetItemCount(unit, itemName)
  549.     local numItems = 0
  550.     for _,inv in unit.Inventory do
  551.         local temp = inv:GetCount( itemName )
  552.         if table.empty(temp) then
  553.             continue
  554.         end
  555.  
  556.         for k,v in temp do
  557.             numItems = numItems + v.Count
  558.         end
  559.     end
  560.  
  561.     return numItems
  562. end
  563.  
  564. --Check if the unit already has the item or not
  565. function UnitHasItem( unit, itemName, baseShopType )
  566.     local shopBp = GetUnitBlueprintByName(baseShopType)
  567.  
  568.     local itemTree = Common.GetShopTreeByBlueprint(shopBp)
  569.  
  570.     local shopItemId = FindShopItemId( itemName, nil, shopBp )
  571.  
  572.     local itemData = itemTree[shopItemId]
  573.  
  574.     if not itemData then
  575.         WARN('*AI ERROR: No item data found for item - ' .. itemName )
  576.         return false
  577.     end
  578.  
  579.     local syncData = Common.GetSyncData(unit)
  580.     local counts = ValidateInventory.GetCount(syncData.Inventory[ Items[itemName].InventoryType ], itemData.ItemBP)
  581.  
  582.     --We have none return out
  583.     if table.empty(counts) then
  584.         return false, 0
  585.     end
  586.  
  587.     --Find out how many we have
  588.     local numHeld = 0
  589.     for k,v in counts do
  590.         numHeld = numHeld + v.Count
  591.     end
  592.  
  593.     if numHeld > 0 then
  594.         return true, numHeld
  595.     end
  596.  
  597.     return false, 0
  598. end
  599.  
  600.  
  601. --Returns the best item that can be purchased by the agent
  602. --  Returned Table = { ItemName, Shop, ItemCost, ItemPriority }
  603. function FindBestBaseItem( unit, aiBrain, action, sellItemData )
  604.     local bestValue = 0
  605.     local bestItems = {}
  606.     local shops = {}
  607.  
  608.     local asset = action.StrategicAsset
  609.     if not asset or not asset.ItemPriorities then
  610.         return false
  611.     end
  612.  
  613.     local inventoryOpen = {}
  614.     for invName,inv in unit.Inventory do
  615.         inventoryOpen[invName] = ValidateInventory.NumFreeSlots( inv )
  616.     end
  617.  
  618.     local highestPriority = false
  619.  
  620.     local maxCost = false
  621.  
  622.     --Mithy: New save-for logic that takes into account item cost mods
  623.     local savingForUpgrade, savingForCost = AIGlobals.SaveForUpgrade(unit, aiBrain)
  624.     local gold = aiBrain.mGold - (savingForCost or 0 * (unit.Sync.ItemCostMod or 1))
  625.  
  626.     local syncData = Common.GetSyncData(unit)
  627.  
  628.     for _,itemData in asset.ItemPriorities do
  629.  
  630.         --this number can increase for stackable items
  631.         local purchaseQuantity = 1
  632.         local currentPriority = 0
  633.  
  634.         if itemData.Priority <= 0 then
  635.             continue
  636.         end
  637.  
  638.  
  639.  
  640.         --If we have a highest priority and this next item won't be higher; break the loop
  641.         if highestPriority and itemData.Priority < highestPriority then
  642.             continue
  643.         end
  644.  
  645.         --Store out each shop only once; this way we don't have to find shops more like crazy
  646.         if shops[itemData.ItemTable.ShopType] == nil then
  647.             local shop = aiBrain.GoalPlanner:GetFriendlyShop(itemData.ItemTable.ShopType, unit:GetPosition())
  648.             if shop then
  649.                 shops[itemData.ItemTable.ShopType] = shop
  650.             else
  651.                 shops[itemData.ItemTable.ShopType] = false
  652.             end
  653.  
  654.             --all shops of this time seem dead, continue to next item
  655.             if not shop then
  656.                 continue
  657.             end
  658.         end
  659.  
  660.  
  661.  
  662.         local nearby = shops[itemData.ItemTable.ShopType]
  663.         if not nearby then
  664.             continue
  665.         end
  666.  
  667.         local hasItem, numHeld = UnitHasItem( unit, itemData.ItemTable.ItemId, itemData.ItemTable.BaseShopType )
  668.         --Figure out if we can carry this item; We have two checks - one for non-stacked items and one for stacks
  669.         if itemData.ItemTable.StacksPerSlot > 1 then
  670.             if hasItem and numHeld >= itemData.ItemTable.StacksPerSlot then
  671.                 continue
  672.             end
  673.  
  674.             local maxPurchasable = itemData.ItemTable.StacksPerSlot - numHeld
  675.             --Already at max; this item is bankrupt
  676.             if maxPurchasable <= 0 then
  677.                 continue
  678.             end
  679.  
  680.             maxPurchasable = math.min( maxPurchasable, AIGlobals.ItemWeights[itemData.ItemTable.ItemId].MaxPurchase or 1 )
  681.  
  682.             --Mithy: Take into account item cost mult, and make sure we can actually afford one
  683.             local maxAffordable = math.floor( gold / (itemData.ItemTable.ItemCost * (unit.Sync.ItemCostMod or 1)) )
  684.             if maxAffordable < 1 then --was <= 0
  685.                 continue
  686.             end
  687.  
  688.             currentPriority = itemData.Priority
  689.  
  690.             --Make sure the new item is better than the old item
  691.             if sellItemData[itemData.ItemTable.InventoryType] then
  692.                 --[MOD] Fixed amount added to sell priority to ensure new item is greater than 5 priority better.  Prevents buy/sell loops
  693.                 if sellItemData[itemData.ItemTable.InventoryType].SellItemPriority + 5 > itemData.Priority then
  694.                     continue
  695.                 end
  696.             end
  697.  
  698.         else
  699.             --Handle non-stacked items here
  700.             if hasItem then
  701.                 continue
  702.             end
  703.  
  704. --0.26.40 - removed the code that placed a limitation on what items could be purchased at the start of the game
  705. --[[            --[MOD] TE   Buy more cheaper items at start
  706.             if GetGameTimeSeconds() < 30 and gold < 5000 and itemData.ItemTable.ItemCost >= 1750 then
  707.                 continue
  708.             elseif  GetGameTimeSeconds() < 30 and gold < 35000 and itemData.ItemTable.ItemCost > 10000 then
  709.                 continue
  710.             end
  711. --]]
  712.  
  713.             --Get our sell refund and make sure the new item is better than the old item
  714.             local addAmount = 0
  715.             if sellItemData[itemData.ItemTable.InventoryType] then
  716.                 addAmount = sellItemData[itemData.ItemTable.InventoryType].SellItemRefund
  717.  
  718.  
  719.                 --[MOD] Fixed amount added to sell priority to ensure new item is greater than 5 priority better.   Prevents buy/sell loops
  720.                 if sellItemData[itemData.ItemTable.InventoryType].SellItemPriority + 5 > itemData.Priority then
  721.                     continue
  722.                 end
  723.             end
  724.  
  725.             --Mithy: Factor item cost mult
  726.             if gold + (addAmount * (unit.Sync.ItemCostMod or 1)) < (itemData.ItemTable.ItemCost * (unit.Sync.ItemCostMod or 1)) then
  727.                 continue
  728.             end
  729.  
  730.             --[MOD] TE Check idol priority against current idol
  731.             if syncData.Inventory.Generals then
  732.                 local buyItem = true
  733.                 for slot, invData in syncData.Inventory.Generals.Slots do
  734.                     local invItemData = EntityData[invData[1]].Data  --[MOD] TE Refrence .Data so item def can get blueprint.
  735.                     local invItemDef = Items[invItemData.BlueprintId]
  736.                     if invItemDef then
  737.                         local idolInSlot = string.sub(invItemDef.Name, 1, -5)
  738.                         local idolInShop = string.sub(itemData.ItemTable.ItemId, 1, -5)
  739.                         if idolInSlot == idolInShop then
  740.                             local invPriority = 0
  741.                             for k,v in asset.ItemPriorities do
  742.                                 if v.ItemTable.ItemId == invItemDef.Name then
  743.                                     invPriority = v.Priority
  744.                                     break
  745.                                 end
  746.  
  747.                             end
  748.  
  749.  
  750.                             if invPriority + 5 > itemData.Priority then
  751.                                 --WARN( LOC(aiBrain.Nickname) ..' Current: '..invItemDef.Name ..' - ' .. invPriority + 5 .. ' | vs | ' .. itemData.ItemTable.ItemId .. ' - ' .. itemData.Priority )
  752.                                 buyItem = false
  753.                             else
  754.                                 buyItem = true
  755.                                 --WARN( LOC(aiBrain.Nickname) ..' Sell: '..invItemDef.Name ..' - ' .. invPriority .. ' | Buy: ' .. itemData.ItemTable.ItemId .. ' - ' .. itemData.Priority )
  756.                             end
  757.                         end
  758.                     end
  759.                 end
  760.                 if not buyItem then
  761.                     continue
  762.                 end
  763.             end
  764.  
  765.  
  766.             currentPriority = itemData.Priority
  767.         end
  768.  
  769.         --Make sure we'll have room for the item
  770.         if inventoryOpen[itemData.ItemTable.InventoryType] <= 0 and not sellItemData[itemData.ItemTable.InventoryType] then
  771.             continue
  772.         end
  773.  
  774.         --WARN('Items - ' ..  itemData.ItemTable.ItemId .. ' - Inventory Type - ' ..  inventoryOpen[itemData.ItemTable.InventoryType] )
  775.  
  776.  
  777.         --Do NOT allow rebuying of the same item
  778.         if sellItemData[itemData.ItemTable.InventoryType].SellItem == itemData.ItemTable.ItemId then
  779.             continue
  780.         end
  781.  
  782.         highestPriority = currentPriority
  783.  
  784.         --[[if (unit:GetArmy() == 1) then
  785.             WARN('*AI SHOP DEBUG: Find Best Item Army= ' .. unit:GetArmy() .. ' - Purchasing item= ' .. itemData.ItemTable.ItemId
  786.                 .. ' - Priority= ' .. highestPriority
  787.                 .. ' - Quantity= ' .. purchaseQuantity  )
  788.         end--]]
  789.  
  790.         table.insert( bestItems, { ItemName = itemData.ItemTable.ItemId, ShopType = itemData.ItemTable.ShopType,
  791.             Shop = nearby, ItemCost = itemData.ItemTable.ItemCost, ItemPriority = highestPriority, NumPurchase = purchaseQuantity,
  792.             InventoryType = itemData.ItemTable.InventoryType, BaseShopType = itemData.ItemTable.BaseShopType } )
  793.     end
  794.  
  795.     if table.getn( bestItems ) > 0 then
  796.         return bestItems[Random( 1, table.getn(bestItems) )]
  797.     end
  798.  
  799.     return false
  800. end
  801.  
  802. function FindLowestInventoryItem( unit, action, inventoryType )
  803.     local lowestPriority = false
  804.     local lowestItem = false
  805.     local shopType = false
  806.     local aiBrain = unit:GetAIBrain()
  807.  
  808.     local asset = action.StrategicAsset
  809.  
  810.     if not asset.ItemPriorities then
  811.         return false
  812.     end
  813.  
  814.     --[MOD] Block selling Generals item (sale of old is handled on idol purchase)
  815.     if inventoryType == 'Generals' then
  816.         --WARN('Generals Item')
  817.         return false
  818.     end
  819.  
  820.     if ValidateInventory.NumFreeSlots(unit.Inventory[inventoryType]) > 0 then
  821.         return false
  822.     end
  823.  
  824.     local inv = unit.Inventory[inventoryType]
  825.     for i=1,8 do
  826.         local itemId = inv:GetItemFromSlot( i )
  827.         if not itemId then
  828.             continue
  829.         end
  830.  
  831.         local item = GetEntityById( itemId )
  832.         if not item then
  833.             continue
  834.         end
  835.  
  836.  
  837.  
  838.  
  839.  
  840.  
  841.         --[MOD] TE  Don't Sell Portals and capture locks
  842.         if item.Sync.Name == 'Item_Consumable_010' or item.Sync.Name == 'Item_Consumable_030'  then
  843.             --WARN( LOC(aiBrain.Nickname) .. 'Skipped TP/Capture Lock ' .. item.Sync.Name )
  844.             continue
  845.         end
  846.  
  847.         local numItems = 0
  848.         for k,v in inv:GetCount( item.Sync.Name ) do
  849.             numItems = numItems + v.Count
  850.         end
  851.  
  852.         --Do not sell a stack of items
  853.         if numItems > 1 then
  854.             continue
  855.         end
  856.  
  857.  
  858.         local itemType = item.Sync.Name
  859.         local tempPriority, tempShop
  860.         for k,v in asset.ItemPriorities do
  861.             if v.ItemTable.ItemId == itemType then
  862.                 if v.SellPriority > 0 then
  863.                     tempPriority = v.SellPriority
  864.                     tempShop = v.ItemTable.ShopType
  865.                     LOG('*AI DEBUG: Using SellPriority for item - ' .. v.ItemTable.ItemId)
  866.                 elseif v.Priority > 0 then
  867.                     tempPriority = v.Priority
  868.                     tempShop = v.ItemTable.ShopType
  869.                 end
  870.                 break
  871.             end
  872.         end
  873.  
  874.  
  875.  
  876.         --WARN('Inventory Priority - ' .. tostring(item.Blueprint.SubInventoryType) )
  877.  
  878.         if tempPriority and ( not lowestPriority or tempPriority < lowestPriority ) then        --[MOD] TE Don't sell idols
  879.             lowestPriority = tempPriority
  880.             lowestItem = item
  881.             shopType = tempShop
  882.         end
  883.     end
  884.  
  885.     ---- local myString = ' '
  886.     ---- for kk, vv in lowestItem.Blueprint do
  887.         ---- myString = myString .. 'column: ' .. kk .. ' value: ' .. tostring(vv) .. ' || '
  888.     ---- end
  889.     ---- WARN('Inventory Priority - ' .. myString )
  890.     return lowestItem, lowestPriority, shopType
  891. end
  892.  
  893. function GetItemDesires(itemId)
  894.     if not ScenarioInfo.AIItemsList then
  895.         return false
  896.     end
  897.  
  898.     for k,itemTable in ScenarioInfo.AIItemsList do
  899.         if itemTable.ItemId != itemId then
  900.             continue
  901.         end
  902.  
  903.         return {
  904.             HealthDesire = itemTable.ItemWeights.HealthDesire,
  905.             ManaDesire = itemTable.ItemWeights.ManaDesire,
  906.             PrimaryWeaponDesire = itemTable.ItemWeights.PrimaryWeaponDesire,
  907.             MinionDesire = itemTable.ItemWeights.MinionDesire,
  908.             SpeedDesire = itemTable.ItemWeights.SpeedDesire,
  909.         }
  910.     end
  911. end
  912.  
  913. function GetPriorityFromCost(cost)
  914.     if cost < 1250 then
  915.         return 5
  916.     elseif cost < 2000 then
  917.         return 15
  918.     elseif cost < 4000 then
  919.         return 30
  920.     elseif cost < 7500 then
  921.         return 50
  922.     elseif cost < 11000 then
  923.         return 100
  924.     elseif cost < 17500 then
  925.         return 150
  926.     else
  927.         return 200
  928.     end
  929. end
  930.  
  931. function GetItemsList()
  932.     --We've already built the list once; return the list - it should NOT change mid game
  933.     if ScenarioInfo.AIItemsList then
  934.         return ScenarioInfo.AIItemsList
  935.     end
  936.  
  937.     ScenarioInfo.AIItemsList = {}
  938.  
  939.     --Get all the shops
  940.     local shops = {}
  941.     for _,brain in ArmyBrains do
  942.         shops = table.append( shops, brain:GetListOfUnits( categories.SHOP, false, false ) )
  943.     end
  944.     local shopIds = {
  945.         Boots           = 'ugbshop10',  --[MOD] TE Fixed to correct boot shop ugbshop01 -> ugbshop10
  946.         Breastplates    = 'ugbshop02',
  947.         Gloves          = 'ugbshop03',
  948.         Helms           = 'ugbshop04',
  949.         Artifacts       = 'ugbshop05',
  950.         Rings           = 'ugbshop06',
  951.         Consumables     = 'ugbshop07',
  952.         Generals        = 'ugbshop09',
  953.     }
  954.  
  955.     --Iterate through the shops
  956.     for shopType,shopId in shopIds do
  957.  
  958.         local shopBp = __blueprints[shopId]
  959.  
  960.         --Get the shop tree which has the costs and ItemBPs for all the items
  961.         local shopTree = Common.GetShopTreeByBlueprint(shopBp)
  962.  
  963.         --Go through all the items in the shop tree
  964.         for shopItemId,itemData in shopTree do
  965.             if not Items[itemData.ItemBP] then
  966.                 continue
  967.             end
  968.  
  969.             --Blank template used by all items
  970.             local itemTable = {
  971.                 ItemWeights = {
  972.                     HealthDesire = 0,
  973.                     ManaDesire = 0,
  974.                     PrimaryWeaponDesire = 0,
  975.                     MinionDesire = 0,
  976.                     SpeedDesire = 0,
  977.  
  978.                     Priority = 0,
  979.                     SellPriority = -1,
  980.                 },
  981.             }
  982.             if not AIGlobals.ItemWeights[itemData.ItemBP].Priority and not AIGlobals.ItemWeights[itemData.ItemBP].PriorityFunction then
  983.                 WARN('*AI DEBUG: No priority set for item = ' .. itemData.ItemBP)
  984.                 WARN('*AI DEBUG: Item Cost = ' .. itemData.Cost)
  985.                 WARN('*AI DEBUG: ItemData = ' .. repr(Items[itemData.ItemBP]) )
  986.                 LOG('*AI DEBUG: ======================')
  987.             end
  988.  
  989.             if AIGlobals.ItemWeights[itemData.ItemBP].Priority == -1 then
  990.                 itemTable.ItemWeights.Priority = GetPriorityFromCost(itemData.Cost)
  991.             end
  992.  
  993.             if AIGlobals.ItemWeights[itemData.ItemBp].SellPriority then
  994.                 itemTable.ItemWeights.SellPriority = AIGlobals.ItemWeights[itemData.ItemBp].SellPriority
  995.             end
  996.  
  997.             --Store out some basic info we'll use when trying to shop
  998.             itemTable.ShopItemId = shopItemId
  999.  
  1000.             if (shopId == 'ugbshop05') then
  1001.                 itemTable.ShopType = shopId
  1002.             else
  1003.                 itemTable.ShopType = 'ugbshop01'
  1004.             end
  1005.             itemTable.BaseShopType = shopId
  1006.             itemTable.ItemCost = itemData.Cost
  1007.             itemTable.ItemId = itemData.ItemBP
  1008.  
  1009.             --Figure out the weight for each item sold at this shop
  1010.             local itemBp = Items[itemData.ItemBP]
  1011.             itemTable.SlotLimit = itemBp.SlotLimit
  1012.             itemTable.StacksPerSlot = itemBp.StacksPerSlot or 1
  1013.             itemTable.InventoryType = itemBp.InventoryType
  1014.             itemTable.GeneralItem = AIGlobals.ItemWeights[itemTable.ItemId]['GeneralItem'] or false
  1015.             itemTable.AssassinItem = AIGlobals.ItemWeights[itemTable.ItemId]['AssassinItem'] or false
  1016.  
  1017.  
  1018.             --Go through all the abilities on the item and figure out what the ability does for demigods
  1019.             for _,abilityName in itemBp.Abilities do
  1020.                 if not Ability[abilityName] then
  1021.                     ERROR('*AI ERROR: Could not find ability information for Ability - ' .. abilityName)
  1022.                 end
  1023.  
  1024.                 --make sure we are taking into account the item even if the ability is a clicky
  1025.                 --if Ability[abilityName].AbilityType != 'Quiet' and not AIGlobals.ItemWeights[itemTable.ItemId] then
  1026.                     --LOG('*AI DEBUG: Click Ability = ' .. abilityName .. ' - Data = ' .. repr(Ability[abilityName]) )
  1027.                 --end
  1028.  
  1029.                 --TODO: Intelligently balance desire for procs & crits
  1030.                 if Ability[abilityName].AbilityType == 'WeaponProc' then
  1031.                     itemTable.ItemWeights.PrimaryWeaponDesire = itemTable.ItemWeights.PrimaryWeaponDesire + 0.1
  1032.                 elseif Ability[abilityName].AbilityType == 'ArmorProc' then
  1033.                     itemTable.ItemWeights.HealthDesire = itemTable.ItemWeights.HealthDesire + 0.1
  1034.                 elseif Ability[abilityName].AbilityType == 'WeaponCrit' then
  1035.                     itemTable.ItemWeights.PrimaryWeaponDesire = itemTable.ItemWeights.PrimaryWeaponDesire + 0.1
  1036.                 end
  1037.  
  1038.                 if Ability[abilityName].Buffs then
  1039.                     for _,buffName in Ability[abilityName].Buffs do
  1040.                         local buffWeights = GetBuffWeights( buffName )
  1041.  
  1042.                         for k,v in buffWeights do
  1043.                             itemTable.ItemWeights[k] = v + itemTable.ItemWeights[k]
  1044.                         end
  1045.                     end
  1046.                 end
  1047.             end
  1048.  
  1049.  
  1050.             --Get item default weighting
  1051.             if AIGlobals.ItemWeights[itemTable.ItemId] then
  1052.                 for weightName,weightValue in AIGlobals.ItemWeights[itemTable.ItemId] do
  1053.                     if weightName == 'MaxPurchase' or weightName == 'GeneralItem' or weightName == 'AssassinItem' then
  1054.                         continue
  1055.                     end
  1056.  
  1057.                     if (weightName == 'Priority' and weightValue == -1) or weightName == 'PriorityFunction' or weightName == 'SellPriority' then
  1058.                         continue
  1059.                     end
  1060.  
  1061.                     itemTable.ItemWeights[weightName] = itemTable.ItemWeights[weightName] + weightValue
  1062.                 end
  1063.             end
  1064.  
  1065.             --Uncomment to see the default weights for items
  1066.             --LOG('*AI DEBUG: Item = ' .. itemTable.ItemId .. ' - WeightsTable = ' .. repr(itemTable.ItemWeights) )
  1067.  
  1068.             --Store the cost and weight for this item in the global items list
  1069.             table.insert( ScenarioInfo.AIItemsList, itemTable )
  1070.         end
  1071.     end
  1072.  
  1073.     --return the global items list
  1074.     return ScenarioInfo.AIItemsList
  1075. end
  1076.  
  1077. function GetBuffWeights(buffName)
  1078.     local buffData = Buffs[buffName]
  1079.  
  1080.     --We currently are NOT dealing with items that have variable durations
  1081.     if buffData.Duration > -1 then
  1082.         return {}
  1083.     end
  1084.  
  1085.     local returnTable = {
  1086.         HealthDesire = 0,
  1087.         ManaDesire = 0,
  1088.         PrimaryWeaponDesire = 0,
  1089.         MinionDesire = 0,
  1090.         SpeedDesire = 0,
  1091.     }
  1092.  
  1093.     if not buffData.Affects then
  1094.         WARN('*AI WARNING: Item with buff - ' .. buffName .. ' - Does not have an Affects block')
  1095.         return returnTable
  1096.     end
  1097.  
  1098.     for aName,aData in buffData.Affects do
  1099.         --LOG('*AI DEBUG: BuffAffects = ' .. repr(buffData.Affects))
  1100.  
  1101.         --Damage Rating
  1102.         if aName == 'DamageRating' then
  1103.             local addAmount = aData.Add / 100
  1104.             returnTable.PrimaryWeaponDesire = returnTable.PrimaryWeaponDesire + addAmount
  1105.  
  1106.         --Health
  1107.         elseif aName == 'Regen' then
  1108.             local addAmount = ((aData.Add or 0) / 75 ) + ((aData.Mult or 0) / 5)
  1109.             returnTable.HealthDesire = returnTable.HealthDesire + addAmount
  1110.  
  1111.         elseif aName == 'MaxHealth' then
  1112.             local addAmount = aData.Add / 4000
  1113.             returnTable.HealthDesire = returnTable.HealthDesire + addAmount
  1114.  
  1115.         --Energy
  1116.         elseif aName == 'MaxEnergy' then
  1117.             local addAmount = ( aData.Add / 6000 )
  1118.             returnTable.ManaDesire = returnTable.ManaDesire + addAmount
  1119.  
  1120.         elseif aName == 'EnergyRegen' then
  1121.             local addAmount = ( (aData.Add or 0) / 25 ) + ((aData.Mult or 0) )
  1122.             returnTable.ManaDesire = returnTable.ManaDesire + addAmount
  1123.  
  1124.         --Armor
  1125.         elseif aName == 'Armor' then
  1126.             local addAmount = ( aData.Add / 4000 )
  1127.             returnTable.HealthDesire = returnTable.HealthDesire + addAmount
  1128.  
  1129.         --Move Speed
  1130.         elseif aName == 'MoveMult' then
  1131.             local addAmount = ( (aData.Mult or 0) )
  1132.             returnTable.SpeedDesire = returnTable.SpeedDesire + addAmount
  1133.  
  1134.         --Lifesteal
  1135.         elseif aName == 'LifeSteal' then
  1136.             local addAmount = aData.Add
  1137.             returnTable.PrimaryWeaponDesire = returnTable.PrimaryWeaponDesire + addAmount
  1138.             returnTable.HealthDesire = returnTable.HealthDesire + ( addAmount / 2 )
  1139.  
  1140.         --Damage return
  1141.         elseif aName == 'DamageReturn' then
  1142.             local addAmount = ( aData.Add / 100 )
  1143.             returnTable.HealthDesire = returnTable.HealthDesire + addAmount
  1144.  
  1145.         --Rate of Fire
  1146.         elseif aName == 'RateOfFire' then
  1147.             local addAmount = ((aData.Mult or 0) * 2)
  1148.             returnTable.PrimaryWeaponDesire = returnTable.PrimaryWeaponDesire + addAmount
  1149.  
  1150.         --Cooldown
  1151.         elseif aName == 'Cooldown' then
  1152.             local addAmount = aData.Mult * -1
  1153.             returnTable.ManaDesire = returnTable.ManaDesire + addAmount
  1154.  
  1155.         --Evasion
  1156.         elseif aName == 'Evasion' then
  1157.             local addAmount = ( aData.Add / 50 )
  1158.             returnTable.HealthDesire = returnTable.HealthDesire + addAmount
  1159.  
  1160.         --Damage Taken
  1161.         elseif aName == 'DamageTakenMult' then
  1162.             local addAmount = ( aData.Add * -1.5 )
  1163.             returnTable.HealthDesire = returnTable.HealthDesire + addAmount
  1164.  
  1165.         --Item cost reduction
  1166.         elseif aName == 'ItemCost' then
  1167.             local addAmount = ( aData.Add * -2 )
  1168.             returnTable.SpeedDesire = returnTable.SpeedDesire + addAmount
  1169.  
  1170.         --Item cost reduction
  1171.         elseif aName == 'Experience' then
  1172.             local addAmount = aData.Add
  1173.             returnTable.SpeedDesire = returnTable.SpeedDesire + addAmount
  1174.  
  1175.         elseif aName == 'Dummy' then
  1176.             --Valid name for dummy buffs used for persistent effects on actors
  1177.         else
  1178.             --Find any we don't handle and report
  1179.             LOG('*AI DEBUG: Unhandled buff data = ' .. aName .. ' - ' .. repr(buffData))
  1180.         end
  1181.     end
  1182.  
  1183.     return returnTable
  1184. end
  1185.  
  1186. --------------------------------------------------------------------------------
  1187. --Purchase Achievement Items
  1188. --------------------------------------------------------------------------------
  1189. function PurchaseAchievementItem(unit)
  1190.     local result = false
  1191.     local bestItem = FindBestAchievementItem(unit)
  1192.  
  1193.     if(bestItem) then
  1194.         local itemName = bestItem.ItemName
  1195.         local baseShopType = bestItem.BaseShopType
  1196.         local shopItemId = false
  1197.  
  1198.         local shopBp = false
  1199.         if(baseShopType) then
  1200.             shopBp = GetUnitBlueprintByName(baseShopType)
  1201.             shopItemId = FindShopItemId(itemName, nil, shopBp)
  1202.         end
  1203.  
  1204.         if(shopItemId and CanPickItem(unit, shopBp, shopItemId, true)) then
  1205.             --Purchase the item
  1206.             CODE_PurchaseItem(unit, shopItemId, shopBp.BlueprintId)
  1207.             result = true
  1208.             --LOG('*AI DEBUG: Army ' .. unit.Army .. ' buying achievement item ' .. shopItemId)
  1209.         end
  1210.     end
  1211.  
  1212.     return result
  1213. end
  1214.  
  1215. --Returns the best item that can be purchased
  1216. function FindBestAchievementItem(unit)
  1217.     local result = false
  1218.     local bestItem = {}
  1219.  
  1220.     if(ValidateInventory.NumFreeSlots(unit.Inventory.Achievement) > 0) then
  1221.         -- local difficulty = ScenarioInfo.ArmySetup[unit:GetAIBrain().Name].Difficulty
  1222.         -- local maxPoints = 1125 --GameData.DifficultyHandicaps[difficulty].MaxPoints
  1223.         -- local minPoints = 1125 --GameData.DifficultyHandicaps[difficulty].MinPoints
  1224.  
  1225.         local isGeneral = EntityCategoryContains( categories.GENERAL, unit )
  1226.         local isAssassin = EntityCategoryContains( categories.ASSASSIN, unit )
  1227.  
  1228.         local itemList = GetAchievementItemsList()
  1229.         local achievementItems = {}
  1230.  
  1231.  
  1232.  
  1233.          --[MOD]  Favor items added as list of items ot select from in each demigod build
  1234.          local unitId = unit:GetUnitId()--..'peppe'
  1235.  
  1236.          if(UnitAITemplates[unitId].SkillBuilds) then
  1237.             local brain = unit:GetAIBrain()
  1238.             if(not brain.UseSkillWeights) then
  1239.                 local build = nil
  1240.                 build = UnitAITemplates[unitId].SkillBuilds[brain.SkillBuild]
  1241.  
  1242.                 if(build.FavorItems) then
  1243.                     local myFavorItems = {}
  1244.                     for k, v in build.FavorItems do
  1245.                         table.insert(myFavorItems, k)
  1246.                     end
  1247.                     local num = math.random(table.getn(myFavorItems))
  1248.  
  1249.                     for k, v in itemList do
  1250.  
  1251.                         if(v.BaseShopType == 'ugbshop08_general' and not isGeneral) then
  1252.                             continue
  1253.                         elseif(v.BaseShopType == 'ugbshop08_assassin' and not isAssassin) then
  1254.                             continue
  1255.                         end
  1256.  
  1257.                         if v.ItemId == build.FavorItems[num] then
  1258.                             table.insert(achievementItems, {ItemTable = v})
  1259.                         end
  1260.                     end
  1261.  
  1262.                 end
  1263.             end
  1264.         end
  1265.  
  1266.         local numItems = table.getn(achievementItems)
  1267.         if(numItems == 0) then
  1268.             local myFavorItems = {'AchievementHealth','AchievementRunSpeed'} --'AchievementTeleport',
  1269.             local num = math.random(table.getn(myFavorItems))
  1270.             for k, v in itemList do
  1271.                 if(v.BaseShopType == 'ugbshop08_general' and not isGeneral) then
  1272.                     continue
  1273.                 elseif(v.BaseShopType == 'ugbshop08_assassin' and not isAssassin) then
  1274.                     continue
  1275.                 end
  1276.  
  1277.                 if v.ItemId == myFavorItems[num] then
  1278.                     table.insert(achievementItems, {ItemTable = v})
  1279.                 end
  1280.             end
  1281.             numItems = table.getn(achievementItems)
  1282.         end
  1283.         if(numItems > 0) then
  1284.             result = achievementItems[Random(1, numItems)]
  1285.             table.insert(bestItem, {ItemName = result.ItemTable.ItemId, ItemCost = result.ItemTable.ItemCost, BaseShopType = result.ItemTable.BaseShopType})
  1286.         end
  1287.     end
  1288.  
  1289.     if(result) then
  1290.         return bestItem[1]
  1291.     else
  1292.         return result
  1293.     end
  1294. end
  1295.  
  1296. function GetAchievementItemsList()
  1297.     --We've already built the list once; return the list - it should NOT change mid game
  1298.     if ScenarioInfo.AIAchievementItemsList then
  1299.         return ScenarioInfo.AIAchievementItemsList
  1300.     end
  1301.  
  1302.     ScenarioInfo.AIAchievementItemsList = {}
  1303.  
  1304.     local shopIds = {
  1305.         Generic         = 'ugbshop08',
  1306.         General         = 'ugbshop08_general',
  1307.         Assassin        = 'ugbshop08_assassin',
  1308.     }
  1309.  
  1310.     for shopType,shopId in shopIds do
  1311.         local shopBp = __blueprints[shopId]
  1312.  
  1313.  
  1314.         --Get the shop tree which has the costs and ItemBPs for all the items
  1315.         local shopTree = Common.GetShopTreeByBlueprint(shopBp)
  1316.  
  1317.         --Go through all the items in the shop tree
  1318.         for shopItemId,itemData in shopTree do
  1319.             if not Items[itemData.ItemBP] then
  1320.                 continue
  1321.             end
  1322.  
  1323.             local itemTable = {}
  1324.             itemTable.BaseShopType = shopId
  1325.             itemTable.ItemCost = itemData.Cost
  1326.             itemTable.ItemId = itemData.ItemBP
  1327.  
  1328.             table.insert(ScenarioInfo.AIAchievementItemsList, itemTable)
  1329.         end
  1330.     end
  1331.  
  1332.     --return the global items list
  1333.     return ScenarioInfo.AIAchievementItemsList
  1334. end
  1335.  
  1336. --------------------------------------------------------------------------------
  1337. --Purchase Citadel Upgrades
  1338. --------------------------------------------------------------------------------
  1339. function GetUpgradesList()
  1340.     --We've already built the list once; return the list - it should NOT change mid game
  1341.     if ScenarioInfo.AIUpgradesList then
  1342.         return ScenarioInfo.AIUpgradesList
  1343.     end
  1344.  
  1345.     ScenarioInfo.AIUpgradesList = {}
  1346.  
  1347.     --Get the shop tree which has the costs and BPs for all the upgrades
  1348.     local shopTree = Upgrades.Tree
  1349.  
  1350.     --Go through all the items in the shop tree
  1351.     for upgradeId, upgradeData in shopTree do
  1352.  
  1353.  
  1354.  
  1355.         --[MOD] TE  Ignore buff constraint to allow all upgrades to be purchased.
  1356.         ---- if not Buffs[upgradeId] then
  1357.             ---- continue
  1358.  
  1359.         ---- end
  1360.  
  1361.         --Blank template used by all upgrades
  1362.         local itemTable = {
  1363.             ItemWeights = {
  1364.                 HealthDesire = 0,
  1365.                 ManaDesire = 0,
  1366.                 PrimaryWeaponDesire = 0,
  1367.                 MinionDesire = 0,
  1368.                 SpeedDesire = 0,
  1369.  
  1370.                 Priority = 0,
  1371.             },
  1372.         }
  1373.  
  1374.  
  1375.  
  1376.         --Store out some basic info we'll use when trying to shop
  1377.         itemTable.ShopItemId = upgradeId
  1378.  
  1379.         itemTable.ShopType = 'ugbshop01'
  1380.         itemTable.BaseShopType = 'stronghold01'
  1381.         itemTable.ItemCost = upgradeData.Cost
  1382.         itemTable.ItemId = upgradeId
  1383.         itemTable.WarRank = upgradeData.Level
  1384.  
  1385.         --Get item default weighting
  1386.  
  1387.         -- [MOD] Set priority if it is available otherwise leave it to FriendlyAsset to process a PriorityFunction.
  1388.         if(AIGlobals.CitadelUpgradeWeights[upgradeId] and AIGlobals.CitadelUpgradeWeights[upgradeId].Priority != -1 and AIGlobals.CitadelUpgradeWeights[upgradeId].Priority != nil) then
  1389.            itemTable.ItemWeights.Priority = AIGlobals.CitadelUpgradeWeights[upgradeId].Priority
  1390.         end
  1391.  
  1392.         -- [MOD] If prioty is set to -1 (auto) convert it to a priority.
  1393.         if (AIGlobals.CitadelUpgradeWeights[upgradeId].Priority == -1) then
  1394.             itemTable.ItemWeights.Priority = GetPriorityFromCost(itemTable.ItemCost)
  1395.         end
  1396.  
  1397.         --Uncomment to see the default weights for items
  1398.         --LOG('*AI DEBUG: Item = ' .. itemTable.ItemId .. ' - WeightsTable = ' .. repr(itemTable.ItemWeights) )
  1399.  
  1400.         --Store the cost and weight for this upgrade in the global upgrades list
  1401.         table.insert( ScenarioInfo.AIUpgradesList, itemTable )
  1402.     end
  1403.  
  1404.  
  1405.     --return the global upgrades list
  1406.     return ScenarioInfo.AIUpgradesList
  1407. end
  1408.  
  1409. function PurchaseCitadelUpgradeCalculateWeights( action, aiBrain, agent, initialAgent )
  1410.     if not agent.WorldStateData.CanMove then
  1411.         return false
  1412.     end
  1413.  
  1414.  
  1415.  
  1416.  
  1417.  
  1418.     local actionInformation = initialAgent.ShopInformation.BuyItem[action.ActionName]
  1419.     if not actionInformation or not actionInformation.PurchaseItem then
  1420.         return false
  1421.     end
  1422.  
  1423.     if actionInformation.PurchaseItemCost > agent.Gold then
  1424.         return false
  1425.     end
  1426.  
  1427.     local shopPos = actionInformation.PurchaseShopPosition
  1428.     if agent.Gold - actionInformation.PurchaseItemCost < 0 then
  1429.         return false
  1430.     end
  1431.     agent.Gold = agent.Gold - actionInformation.PurchaseItemCost
  1432.  
  1433.     if not agent.AgentHasMoved then
  1434.         distance = initialAgent.GOAP.BrainAsset:GetDistance( 'STRONGHOLD', 'Ally' )
  1435.     else
  1436.         distance = VDist3XZ( agent.Position, shopPos )
  1437.     end
  1438.  
  1439.     if not distance then
  1440.         return false
  1441.     end
  1442.  
  1443.     agent:SetPosition( shopPos )
  1444.  
  1445.     return { PurchaseItems = ( actionInformation.PurchaseItemPriority * -1 ), }, math.max( distance / agent.Speed, 2 )
  1446. end
  1447.  
  1448. function PurchaseCitadelUpgradeStatus(unit, action)
  1449.     local aiBrain = unit:GetAIBrain()
  1450.     local actionBp = HeroAIActionTemplates[action.ActionName]
  1451.  
  1452.     unit.ShopInformation.CanBuyItem[action.ActionName] = false
  1453.  
  1454.     if(not unit.ShopInformation.BuyItem[action.ActionName]) then
  1455.         unit.ShopInformation.BuyItem[action.ActionName] = false
  1456.     end
  1457.  
  1458.     local actionInformation = {}
  1459.     actionInformation.PurchaseItem = false
  1460.     actionInformation.PurchaseItemPriority = false
  1461.     actionInformation.PurchaseShopPosition = false
  1462.     actionInformation.PurchaseItemCost = false
  1463.  
  1464.     unit.ShopInformation.BuyItem[action.ActionName] = actionInformation
  1465.  
  1466.     local bestItem = FindBestCitadelUpgrade(unit, aiBrain, action)
  1467.     if not bestItem then
  1468.         return false
  1469.     end
  1470.  
  1471.     actionInformation.PurchaseItem = bestItem.ItemName
  1472.     actionInformation.PurchaseItemPriority = bestItem.ItemPriority
  1473.     actionInformation.PurchaseShopType = bestItem.ShopType
  1474.     actionInformation.PurchaseShopPosition = bestItem.Shop:GetPosition()
  1475.     actionInformation.PurchaseItemCost = bestItem.ItemCost
  1476.  
  1477.     unit.ShopInformation.BuyItem[action.ActionName] = actionInformation
  1478.     unit.ShopInformation.CanBuyItem[action.ActionName] = true
  1479.  
  1480.     --if (unit:GetArmy() == 1) then
  1481.     --   LOG('*AI SHOP DEBUG: ' .. action.ActionName .. 'Status - Upgrade = ' .. bestItem.ItemName
  1482.     --       .. ' - ItemPriority = ' .. bestItem.ItemPriority)
  1483.     --end
  1484.  
  1485.     return true
  1486. end
  1487.  
  1488. function PurchaseCitadelUpgradeAction(unit, action)
  1489.     local aiBrain = unit:GetAIBrain()
  1490.  
  1491.     local bestItem = FindBestCitadelUpgrade( unit, aiBrain, action )
  1492.     if not bestItem then
  1493.         return
  1494.     end
  1495.  
  1496.     --if (unit:GetArmy() == 1) then
  1497.     --   WARN('*AI SHOP DEBUG: Army= ' .. unit:GetArmy() .. ' - Purchasing item= ' .. bestItem.ItemName
  1498.     --       .. ' - Priority= ' .. bestItem.ItemPriority
  1499.     --       .. ' - Quantity= ' .. bestItem.NumPurchase .. '\n\n' )
  1500.     --end
  1501.  
  1502.     if(PurchaseCitadelUpgrade(unit, bestItem.ItemName) == 'Stuck') then
  1503.         action:LockAction(2)
  1504.     end
  1505.  
  1506.     ShopCleanup(unit, action)
  1507.  
  1508.     unit.GOAP:ForcePurchaseUpdate()
  1509. end
  1510.  
  1511. function FindBestCitadelUpgrade(unit, aiBrain, action)
  1512.     local bestValue = 0
  1513.     local bestItems = {}
  1514.     local shop = aiBrain:GetStronghold()
  1515.  
  1516.     local asset = action.StrategicAsset
  1517.     if not asset or not asset.CitadelUpgradePriorities or not shop then
  1518.         return false
  1519.     end
  1520.  
  1521.     local highestPriority = false
  1522.  
  1523.     --Mithy: New save-for-upgrade method that only runs once per shopping check
  1524.     local savingForUpgrade, savingForCost = AIGlobals.SaveForUpgrade(unit, aiBrain)
  1525.  
  1526.     for _,itemData in asset.CitadelUpgradePriorities do
  1527.         local purchaseQuantity = 1
  1528.         local currentPriority = 0
  1529.  
  1530.         if itemData.Priority <= 0 then
  1531.             continue
  1532.         end
  1533.  
  1534.         --If we have a highest priority and this next item won't be higher; break the loop
  1535.         if highestPriority and (itemData.Priority) < highestPriority then
  1536.             break
  1537.         end
  1538.  
  1539.         if ValidateUpgrade.CanPickUpgrade(Upgrades.Tree, unit, aiBrain.Score.WarRank, itemData.ItemTable.ItemId) != true then
  1540.             continue
  1541.         end
  1542.  
  1543.         --Mithy: New save-for logic that runs after CanPickUpgrade and takes into account item cost mods
  1544.         --Are we saving, and is this what we're saving for?
  1545.         if savingForUpgrade and savingForUpgrade ~= itemData.ItemTable.ItemId then
  1546.             --If saving, do we have enough excess gold to purchase this?
  1547.             if aiBrain.mGold - (savingForCost * (unit.Sync.ItemCostMod or 1)) < (itemData.ItemTable.ItemCost * (unit.Sync.ItemCostMod or 1)) then
  1548.                 continue
  1549.             end
  1550.         end
  1551.  
  1552.         highestPriority = itemData.Priority
  1553.  
  1554.         table.insert( bestItems, { ItemName = itemData.ItemTable.ItemId, ShopType = itemData.ItemTable.ShopType,
  1555.             Shop = shop, ItemCost = itemData.ItemTable.ItemCost, ItemPriority = highestPriority } )
  1556.     end
  1557.  
  1558.     if table.getn( bestItems ) > 0 then
  1559.         return bestItems[ Random( 1, table.getn(bestItems) ) ]
  1560.     end
  1561.  
  1562.     return false
  1563. end
  1564.  
  1565. function PurchaseCitadelUpgrade(unit, upgradeName)
  1566.     if not ValidateUpgrade.CanPickUpgrade(Upgrades.Tree, unit, unit:GetAIBrain().Score.WarRank, upgradeName) then
  1567.         return false
  1568.     end
  1569.  
  1570.     if unit.Sync.ShopId and not ShopCleanup(unit) then
  1571.         return false
  1572.     end
  1573.  
  1574.     local aiBrain = unit:GetAIBrain()
  1575.     local shop = aiBrain:GetStronghold()
  1576.     if VDist3XZSq( unit.Position, shop.Position ) > 200 then
  1577.         local cmd = MoveToShop( unit, shop, aiBrain )
  1578.         while VDist3XZSq( unit.Position, shop.Position ) > 200 do
  1579.         WaitTicks(6)
  1580.  
  1581.             if unit:IsDead() or shop:IsDead() then
  1582.                 return false
  1583.             end
  1584.         end
  1585.     end
  1586.  
  1587.     --Go to the shop
  1588.     local commandData = {
  1589.         TaskName = 'BeginShopTask',
  1590.         TargetId = shop:GetEntityId(),
  1591.     }
  1592.     local cmd = IssueScript( {unit}, commandData )
  1593.  
  1594.     local oldPos = table.copy( unit:GetPosition() )
  1595.     local stuckCount = 0
  1596.  
  1597.     --Wait until we are in the shop
  1598.     while not shop:CheckShopper(unit) do
  1599.         WaitTicks(6)
  1600.  
  1601.         if unit:IsDead() or shop:IsDead() then
  1602.             return
  1603.         end
  1604.  
  1605.         local newPos = unit:GetPosition()
  1606.         if newPos[1] == oldPos[1] and newPos[2] == oldPos[2] and newPos[3] == oldPos[3] then
  1607.             stuckCount = stuckCount + 1
  1608.         else
  1609.             stuckCount = 0
  1610.         end
  1611.  
  1612.         if stuckCount >= 10 then
  1613.             LOG('*AI DEBUG: Unit Stuck getting to shop - PurchaseCitadelUpgrade')
  1614.             return 'Stuck'
  1615.         end
  1616.  
  1617.         oldPos = table.copy(newPos)
  1618.     end
  1619.  
  1620.     --Purchase the upgrade
  1621.  
  1622.     local result = import('/lua/sim/upgradesys.lua').HandleUpgrade(unit, upgradeName)
  1623.     WaitTicks(6)
  1624.  
  1625.  
  1626.     if(result) then
  1627.         local announcement = LOCF(AIChatGlobals.Named.PurchasingUpgrade, ArmyBonuses[upgradeName].DisplayName)
  1628.         AIUtils.AIChat(unit, announcement)
  1629.     end
  1630.  
  1631.     return result
  1632. end
RAW Paste Data