Guest User

Untitled

a guest
Feb 20th, 2017
69
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 35.20 KB | None | 0 0
  1. -- Parameters to the HUD that should not be visible to the regular user,
  2. -- but that the developer might want to change in the future
  3. HiddenConfig =
  4. {
  5. panelWidth = 200,
  6. timeTick = 1,
  7. yInset = 30,
  8. xDescWidth = 145,
  9. xTitleInset = 60,
  10. xHeaderInset = 80,
  11. xImageOffset = -25,
  12. yImageOffset = -5,
  13. yStandardHeight = 15,
  14. imageSize = 10,
  15. headerColor = {r=240, g=240, b=240},
  16. keyColor = {r=200, g=200, b=200},
  17. valColor = {r=150, g=150, b=150},
  18. goldChangeLimit = 300,
  19. stackableChangeLimit = 10,
  20. nonStackableChangeLimit = 3,
  21. WasteMod = 10000,
  22. }
  23.  
  24. Config = (function()
  25. local marketPrices = {}
  26.  
  27. local function getMarketPrice(item)
  28. local id = Item.GetItemIDFromDualInput(item)
  29. return marketPrices[id]
  30. end
  31.  
  32.  
  33. local function fileExists(name)
  34. local f = io.open(name)
  35. if not f then
  36. return false
  37. end
  38. f:close()
  39. return true
  40. end
  41.  
  42.  
  43. local configText = [[
  44. -- Accurate market prices are necessary for correct profits calculations.
  45. -- Some examples have been provided to demonstrate the format, but you should probably edit them to fit your world.
  46. MarketPrices = {
  47. ["sword ring"] = 500,
  48. ["great health potion"] = 150,
  49. ["life ring"] = 900,
  50. ["spider silk"] = 7000,
  51. }]]
  52. local configName = "../Configs/shAdOwHUD.ini"
  53.  
  54. local function loadConfig()
  55. -- If the config file does not exist, create it
  56. if not fileExists(configName) then
  57. local f = io.open(configName, "w+")
  58. if f then
  59. f:write(configText)
  60. f:close()
  61. local noConfigMsg = "This is the first time you're using this HUD. A config file has been created for you, use it to change market prices of items that you won't sell to npcs."
  62. print(noConfigMsg)
  63. end
  64. end
  65.  
  66. dofile(configName)
  67.  
  68. for name, price in pairs(MarketPrices) do
  69. marketPrices[Item.GetID(name)] = price
  70. end
  71. end
  72.  
  73. return
  74. {
  75. getMarketPrice = getMarketPrice,
  76. loadConfig = loadConfig,
  77. }
  78. end)()
  79.  
  80. Data = (function()
  81. local wornSoftID = 6530
  82. local activeSoftID = 3549
  83. local passiveSoftID = 6529
  84.  
  85. local durations =
  86. {
  87. [Item.GetID("Sword Ring")] = 30 * 60,
  88. [Item.GetID("Club Ring")] = 30 * 60,
  89. [Item.GetID("Axe Ring")] = 30 * 60,
  90. [Item.GetID("Energy Ring")] = 10 * 60,
  91. [Item.GetID("Stealth Ring")] = 10 * 60,
  92. [Item.GetID("Life Ring")] = 20 * 60,
  93. [Item.GetID("Ring of Healing")] = 7.5 * 60,
  94. [Item.GetID("Prismatic Ring")] = 60 * 60,
  95. [Item.GetID("Time Ring")] = 10 * 60,
  96. }
  97.  
  98. -- Translation map for getting the passive id from an active id
  99. local passiveIDs = {[activeSoftID] = passiveSoftID, [wornSoftID] = passiveSoftID}
  100. for passive, _ in pairs(durations) do
  101. passiveIDs[Item.GetRingActiveID(passive)] = passive
  102. end
  103.  
  104. -- Add the soft boots duration after doing the ring translations
  105. durations[passiveSoftID] = 4 * 60 * 60
  106.  
  107.  
  108. -- If we loot one of these items something fishy is going on, so invalidate the whole count.
  109. local invalidationIDs =
  110. {
  111. Item.GetID("shovel"),
  112. Item.GetID("light shovel"),
  113. Item.GetID("rope"),
  114. Item.GetID("elvenhair rope"),
  115. Item.GetID("fishing rod"),
  116. SoftID,
  117. WornSoftID,
  118. PassiveSoftID
  119. }
  120.  
  121. -- These are items whose count may decrease, and should then be counted as waste
  122. local supplies =
  123. {
  124. -- Arrows
  125. [Item.GetID("arrow")] = true,
  126. [Item.GetID("burst arrow")] = true,
  127. [Item.GetID("crystalline arrow")] = true,
  128. [Item.GetID("earth arrow")] = true,
  129. [Item.GetID("envenomed arrow")] = true,
  130. [Item.GetID("flaming arrow")] = true,
  131. [Item.GetID("flash arrow")] = true,
  132. [Item.GetID("onyx arrow")] = true,
  133. [Item.GetID("poison arrow")] = true,
  134. [Item.GetID("shiver arrow")] = true,
  135. [Item.GetID("simple arrow")] = true,
  136. [Item.GetID("sniper arrow")] = true,
  137. [Item.GetID("tarsal arrow")] = true,
  138.  
  139. -- Bolts
  140. [Item.GetID("bolt")] = true,
  141. [Item.GetID("drill bolt")] = true,
  142. [Item.GetID("infernal bolt")] = true,
  143. [Item.GetID("piercing bolt")] = true,
  144. [Item.GetID("power bolt")] = true,
  145. [Item.GetID("prismatic bolt")] = true,
  146. [Item.GetID("vortex bolt")] = true,
  147.  
  148. -- Throwing weapons
  149. [Item.GetID("enchanted spear")] = true,
  150. [Item.GetID("glooth spear")] = true,
  151. [Item.GetID("hunting spear")] = true,
  152. [Item.GetID("mean paladin spear")] = true,
  153. [Item.GetID("royal spear")] = true,
  154. [Item.GetID("spear")] = true,
  155. [Item.GetID("assassin star")] = true,
  156. [Item.GetID("snowball")] = true,
  157. [Item.GetID("small stone")] = true,
  158. [Item.GetID("throwing knife")] = true,
  159. [Item.GetID("throwing star")] = true,
  160. [Item.GetID("viper star")] = true,
  161.  
  162. -- Amulets
  163. [Item.GetID("gill necklace")] = true,
  164. [Item.GetID("prismatic necklace")] = true,
  165. [Item.GetID("protection amulet")] = true,
  166.  
  167. -- Misc
  168. [Item.GetID("scarab coin")] = true,
  169. }
  170.  
  171. local supplyPatterns =
  172. {
  173. "rune",
  174. "mana",
  175. "health",
  176. "spirit potion",
  177. }
  178.  
  179. local function isSupply(item)
  180. local name = Item.GetName(item):lower()
  181. local matchesSupplyPattern = false
  182. for _, pattern in ipairs(supplyPatterns) do
  183. matchesSupplyPattern = matchesSupplyPattern or name:match(pattern)
  184. end
  185.  
  186. local id = Item.GetItemIDFromDualInput(item)
  187. return matchesSupplyPattern or supplies[id]
  188. end
  189.  
  190.  
  191. function getCost(id)
  192. local shopCost = Item.GetCost(id)
  193. return id == passiveSoftID and 10000 or (shopCost == 0 and Config.getMarketPrice(id) or shopCost)
  194. end
  195.  
  196. function getValue(id)
  197. return Config.getMarketPrice(id) or (isSupply(id) and getCost(id)) or Item.GetValue(id)
  198. end
  199.  
  200.  
  201. return
  202. {
  203. wornSoftID = wornSoftID,
  204. activeSoftID = activeSoftID,
  205. passiveSoftID = passiveSoftID,
  206.  
  207. durations = durations,
  208. passiveIDs = passiveIDs,
  209. invalidationIDs = invalidationIDs,
  210.  
  211. isSupply = isSupply,
  212. getCost = getCost,
  213. getValue = getValue,
  214. }
  215. end)()
  216.  
  217. HUDData = (function()
  218. -- Initial data
  219. local startTime = os.time()
  220. local startExp = Self.Experience()
  221. local startStamina = Self.Stamina()
  222.  
  223. -- The data that will be visible to the rest of the HUD
  224. local data = { countsAreValid = true, general = { lastTick = os.time() } }
  225. -- The functions that update the data, in the order they will be called.
  226. local updateFunctions = {}
  227.  
  228. -- Helper functions and their accumulative data
  229. data.general.balance = 0
  230. NpcMessageProxy.OnReceive("Gets Bank Balance", function(proxy, npcName, message)
  231. local balance = string.match(message, "Your account balance is (.+) gold.")
  232. if balance and tonumber(balance) then
  233. data.general.balance = tonumber(balance)
  234. end
  235. end)
  236.  
  237. local function isEmptyFlask(id)
  238. return table.find({283, 284, 285}, id)
  239. end
  240.  
  241. local function ignoreItem(id)
  242. return id == 0 or isEmptyFlask(id) or Item.isContainer(id)
  243. end
  244.  
  245. local corpseNames = {"the", "demonic", "dead", "slain", "dissolved", "remains", "elemental", "split"}
  246. local function isCorpse(cont)
  247. if Item.isCorpse(cont:ID()) then return true end
  248. local name = cont:Name():lower()
  249. for _, CPartName in ipairs(corpseNames) do
  250. if name:find(CPartName) then
  251. return true
  252. end
  253. end
  254. return false
  255. end
  256.  
  257. local function countItems()
  258. newCounts = {}
  259.  
  260. veryUnsafeFunctionEnterCriticalMode()
  261.  
  262. -- Count equipped
  263. local slots = {Self.Head, Self.Armor, Self.Legs, Self.Amulet, Self.Feet, Self.Ring, Self.Weapon, Self.Shield, Self.Ammo}
  264. for i = 1, #slots do
  265. local slot = slots[i]()
  266.  
  267. if not ignoreItem(slot.id) then
  268. newCounts[slot.id] = (newCounts[slot.id] or 0) + math.max(slot.count, 1)
  269. end
  270. end
  271.  
  272. -- Count in backpacks
  273. for i = 0, 16 do
  274. local cont = Container(i)
  275. if cont:isOpen() and not isCorpse(cont) and cont:Name() ~= "Browse Field" then
  276. for spot = 0, cont:ItemCount() - 1 do
  277. local item = cont:GetItemData(spot)
  278. if not ignoreItem(item.id) then
  279. newCounts[item.id] = (newCounts[item.id] or 0) + item.count
  280. end
  281. end
  282. end
  283. end
  284.  
  285. veryUnsafeFunctionExitCriticalMode()
  286.  
  287. -- Handle changing ring and feet ids
  288. for activeID, passiveID in pairs(Data.passiveIDs) do
  289. newCounts[passiveID] = (newCounts[passiveID] or 0) + (newCounts[activeID] or 0)
  290. newCounts[activeID] = nil
  291. end
  292.  
  293. -- Handle worn soft boots
  294. newCounts[Data.passiveSoftID] = (newCounts[Data.passiveSoftID] or 0) + (newCounts[Data.wornSoftID] or 0)
  295.  
  296. return newCounts
  297. end
  298.  
  299. local function changeIsWithinLimits(id, change)
  300. if id == Item.GetID("gold coin") then
  301. return change < HiddenConfig.goldChangeLimit
  302. elseif Item.isStackable(id) then
  303. return change < HiddenConfig.stackableChangeLimit
  304. else
  305. return change < HiddenConfig.nonStackableChangeLimit
  306. end
  307. end
  308.  
  309. -- We only want to update the diff if it contains reasonable values
  310. local totalNewItemLimit = (Self.Level() < 100 and 5 or (Self.Level() < 200 and 7 or 9))
  311. local lastBpCount = 0
  312. local function validateDiff(diff, new)
  313. local totalNewCount = 0
  314. -- If we discovered too many of any one item we invalidate the diff
  315. for id, count in pairs(diff) do
  316. if (count > 0 and not changeIsWithinLimits(id, count)) or
  317. (count < 0 and Data.isSupply(id) and not changeIsWithinLimits(id, -1*count)) or
  318. (count < 0 and not Data.isSupply(id) and new[id] and not changeIsWithinLimits(id, new[id])) then
  319. return false
  320. end
  321. if count > 0 then
  322. totalNewCount = totalNewCount + (Item.isStackable(id) and 1 or count)
  323. elseif count < 0 and not Data.isSupply(id) then
  324. totalNewCount = totalNewCount + (Item.isStackable(id) and 1 or new[id] or 0)
  325. end
  326. end
  327. -- If we discovered too many new items in total we invalidate the diff
  328. if totalNewCount > totalNewItemLimit then
  329. return false
  330. end
  331. -- If we discovered a new tool we invalidate the diff
  332. for _, id in ipairs(Data.invalidationIDs) do
  333. local change = diff[id]
  334. if change and change ~= 0 then
  335. return false
  336. end
  337. end
  338.  
  339. local goldID = Item.GetID("gold coin")
  340. local platID = Item.GetID("platinum coin")
  341. -- If we're on an OT and lost gold or plats we invalidate the diff, because of gold changing
  342. if not XenoBot.IsInRealTibia() and
  343. ((diff[goldID] and diff[goldID] < 0) or
  344. (diff[platID] and diff[platID] < 0)) then
  345. return false
  346. end
  347.  
  348. -- If the number of open backpacks have changed we invalidate
  349. local bpCount = 0
  350. for i = 0, 16 do
  351. local cont = Container(i)
  352. if cont:isOpen() and not isCorpse(cont) and cont:Name() ~= "Browse Field" then
  353. bpCount = bpCount + 1
  354. end
  355. end
  356. if bpCount ~= lastBpCount then
  357. lastBpCount = bpCount
  358. return false
  359. end
  360.  
  361. -- Otherwise we're valid
  362. return true
  363. end
  364.  
  365. local function idOnScreen(ids)
  366. local myPos = Self.Position()
  367. for dx = -7, 7 do
  368. for dy = -5, 5 do
  369. local pos = {x = myPos.x + dx, y = myPos.y + dy, z = myPos.z}
  370. if table.find(ids, Map.GetTopUseItem(pos.x, pos.y, pos.z).id) then
  371. return true
  372. end
  373. end
  374. end
  375. return false
  376. end
  377.  
  378. local function trainerOnScreen()
  379. return idOnScreen({16201, 16198, 16202, 16199, 16200})
  380. end
  381.  
  382. local function depotOnScreen()
  383. return idOnScreen({3497, 3498, 3499, 3500})
  384. end
  385.  
  386. local function hasCloseNPC()
  387. for _, c in Creature.iNpcs() do
  388. if c:isOnScreen() then
  389. return true
  390. end
  391. end
  392. return false
  393. end
  394.  
  395. local lastTalkedToNPC = 0
  396. NpcMessageProxy:OnReceive("Spoke With NPC Detector", function(_)
  397. lastTalkedToNPC = os.time()
  398. end)
  399.  
  400. -- We don't want to update the diff while doing certain things
  401. -- that are normally done when refilling
  402. local function isRefilling()
  403. return (XenoBot.IsInRealTibia() and hasCloseNPC()) or
  404. os.difftime(os.time(), lastTalkedToNPC) < 10 or
  405. depotOnScreen() or
  406. trainerOnScreen()
  407. end
  408.  
  409. data.itemCounts = countItems()
  410. local function updateDiffs()
  411. local newCounts = countItems()
  412.  
  413. -- Calculate the diff
  414. local diff = {}
  415. for id, count in pairs(newCounts) do
  416. diff[id] = count
  417. end
  418.  
  419. for id, count in pairs(data.itemCounts) do
  420. diff[id] = (diff[id] or 0) - count
  421. end
  422.  
  423. for id, count in pairs(diff) do
  424. if diff[id] == 0 then
  425. diff[id] = nil
  426. end
  427. end
  428.  
  429. -- Store the new counts
  430. data.itemCounts = newCounts
  431.  
  432. -- Validate the diff
  433. data.countsAreValid = validateDiff(diff, data.itemCounts) and (not isRefilling())
  434.  
  435. -- Filter the diff into loot and waste
  436. local newLoot = {}
  437. local newWaste = {}
  438. for id, count in pairs(diff) do
  439. if count > 0 then
  440. newLoot[id] = count
  441. -- Do not accept count based waste of active items
  442. elseif Data.isSupply(id) then
  443. newWaste[id] = 0-count
  444. end
  445. end
  446.  
  447. -- Check for active items
  448. local slots = {Self.Head, Self.Armor, Self.Legs, Self.Feet, Self.Amulet, Self.Weapon, Self.Ring, Self.Shield, Self.Ammo}
  449. for i = 1, #slots do
  450. local slot = slots[i]()
  451. local passiveID = Data.passiveIDs[slot.id]
  452. if (passiveID and Data.durations[passiveID]) then
  453. -- The count for an active item is the time it was active for
  454. newWaste[passiveID] = os.time() - data.general.lastTick
  455. end
  456. end
  457. data.general.lastTick = os.time()
  458.  
  459. -- Store the filtered diffs
  460. data.newLoot = newLoot
  461. data.newWaste = newWaste
  462. end
  463. table.insert(updateFunctions, updateDiffs)
  464.  
  465. data.general.totalLoot = 0
  466. data.general.totalWaste = 0
  467. data.general.totalProfit = 0
  468. local function updateProfits()
  469. if data.countsAreValid then
  470. for id, num in pairs(data.newLoot) do
  471. data.general.totalLoot = data.general.totalLoot + Data.getValue(id) * num
  472. end
  473. for id, num in pairs(data.newWaste) do
  474. if Data.durations[id] then
  475. data.general.totalWaste = data.general.totalWaste + Data.getCost(id) * num / Data.durations[id]
  476. else
  477. data.general.totalWaste = data.general.totalWaste + Data.getCost(id) * num
  478. end
  479. end
  480. data.general.totalProfit = data.general.totalLoot - data.general.totalWaste
  481. end
  482. end
  483. table.insert(updateFunctions, updateProfits)
  484.  
  485. local function updateLevel()
  486. data.general.level = Self.Level()
  487. end
  488. table.insert(updateFunctions, updateLevel)
  489.  
  490. local function updateExp()
  491. data.general.gainedExp = Self.Experience() - startExp
  492. end
  493. table.insert(updateFunctions, updateExp)
  494.  
  495. local previously = os.time()
  496. data.general.usedSeconds = 1
  497. data.general.usedHours = 0
  498. local function updateTime()
  499. local now = os.time()
  500. local diff = now - previously
  501. -- If the time since the last tick was too long we've very likely been offline
  502. if diff < 30 then
  503. data.general.usedSeconds = data.general.usedSeconds + diff
  504. data.general.usedHours = data.general.usedSeconds / 3600
  505. end
  506. previously = now
  507. end
  508. table.insert(updateFunctions, updateTime)
  509.  
  510. local previousStamina = Self.Stamina()
  511. data.general.usedStaminaMinutes = 0
  512. data.general.usedStaminaHours = 0
  513. local function updateStamina()
  514. local current = Self.Stamina()
  515. data.general.stamina = current
  516. local diff = previousStamina - current
  517. -- Stamina cannot increase while we are online, and
  518. -- we dont want being offlane to corrupt the stats
  519. if diff >= 0 then
  520. data.general.usedStaminaMinutes = data.general.usedStaminaMinutes + diff
  521. data.general.usedStaminaHours = data.general.usedStaminaMinutes / 60
  522. end
  523. previousStamina = current
  524. end
  525. table.insert(updateFunctions, updateStamina)
  526.  
  527. local function updateRates()
  528. data.general.expS = data.general.gainedExp / data.general.usedSeconds
  529. data.general.expH = data.general.gainedExp / data.general.usedHours
  530. data.general.expSH = data.general.usedStaminaHours < 0.017 and data.general.expH or data.general.gainedExp / data.general.usedStaminaHours
  531. data.general.profitsH = data.general.totalProfit / data.general.usedHours
  532. data.general.profitsSH = data.general.usedStaminaHours < 0.017 and data.general.profitsH or data.general.totalProfit / data.general.usedStaminaHours
  533. end
  534. table.insert(updateFunctions, updateRates)
  535.  
  536. local function expForLevel(x)
  537. return 50/3*(x*x*x - 6*x*x + 17*x - 12)
  538. end
  539.  
  540. local function updateTimeToLevel()
  541. local nextLevelExp = expForLevel(Self.Level() + 1)
  542. local missingExp = nextLevelExp - Self.Experience()
  543. data.general.timeToLevel = data.general.expS == 0 and -1 or math.floor(missingExp / data.general.expS)
  544. end
  545. table.insert(updateFunctions, updateTimeToLevel)
  546.  
  547. data.general.ping = 0
  548. local function updatePing()
  549. data.general.ping = Self.Ping()
  550. end
  551. table.insert(updateFunctions, updatePing)
  552.  
  553. local function getStats()
  554. for _, f in ipairs(updateFunctions) do
  555. f()
  556. end
  557. return data
  558. end
  559.  
  560. return
  561. {
  562. getStats = getStats,
  563. }
  564. end)()
  565.  
  566. Events = (function()
  567.  
  568. local topics = {}
  569.  
  570. local function subscribe(topic, callback)
  571. local subs = topics[topic] or {}
  572. table.insert(subs, callback)
  573. topics[topic] = subs
  574. end
  575.  
  576. local function publish(topic, data)
  577. local subs = topics[topic] or {}
  578. for _, callback in ipairs(subs) do
  579. callback(data)
  580. end
  581. end
  582.  
  583. local function keyset(tab)
  584. local set = {}
  585. for key, _ in pairs(tab) do
  586. table.insert(set, key)
  587. end
  588. return set
  589. end
  590.  
  591. Module("Update HUD", function(module)
  592. local stats = HUDData.getStats()
  593.  
  594. -- Notify general subscribers
  595. publish("general", stats.general)
  596.  
  597. -- Only notify count based subscribers when the counts are valid
  598. if stats.countsAreValid then
  599. -- Notify about new possible new ids
  600. local newLootIDs = keyset(stats.newLoot)
  601. publish("newLootIDs", newLootIDs)
  602. local newWasteIDs = keyset(stats.newWaste)
  603. publish("newWasteIDs", newWasteIDs)
  604.  
  605. -- Notify about new counts
  606. for id, count in pairs(stats.newLoot) do
  607. publish(id, count)
  608. end
  609.  
  610. for id, count in pairs(stats.newWaste) do
  611. publish(id + HiddenConfig.WasteMod, count)
  612. end
  613. end
  614.  
  615. module:Delay(1000)
  616. end)
  617.  
  618. -- Let main scripts stop the HUD when they logout.
  619. Signal.OnReceive("Stop HUD", function(_, Msg)
  620. if Msg == "Stop" then
  621. Module.Stop("Update HUD")
  622. end
  623. end)
  624.  
  625. return
  626. {
  627. publish = publish,
  628. subscribe = subscribe,
  629. }
  630. end)()
  631.  
  632.  
  633. -- HUDContainer is an abstract super class
  634. HUDContainer = {}
  635. HUDContainer_mt = {__index = HUDContainer}
  636.  
  637. function HUDContainer:move(newx, newy)
  638. self.y = newy
  639. self.x = newx
  640. for inset, hud in pairs(self.huds) do
  641. hud:SetPosition(self.x + inset, self.y)
  642. end
  643. end
  644.  
  645. function HUDContainer:moveRelative(diffx, diffy)
  646. self:move(self.x + diffx, self.y + diffy)
  647. end
  648.  
  649. HeaderHUDContainer = {}
  650. HeaderHUDContainer_mt = {__index = HeaderHUDContainer}
  651.  
  652. function HeaderHUDContainer.new(title, parent, inset, signalkey)
  653. local c = {}
  654. setmetatable(c, HeaderHUDContainer_mt)
  655.  
  656. c.x = 0
  657. c.y = 0
  658. c.height = HiddenConfig.yStandardHeight
  659.  
  660. c.huds = {}
  661.  
  662. local inset = inset or HiddenConfig.xHeaderInset
  663.  
  664. c.headerHUD = HUD.New(c.x + inset, c.y, title, HiddenConfig.headerColor.r, HiddenConfig.headerColor.g, HiddenConfig.headerColor.b)
  665. c.huds[inset] = c.headerHUD
  666.  
  667. parent:addChild(c)
  668. if signalkey then
  669. Signal.OnReceive(signalkey, function(_, name)
  670. c.headerHUD:SetText(name)
  671. end)
  672. end
  673.  
  674. return c
  675. end
  676.  
  677. setmetatable(HeaderHUDContainer, { __index = HUDContainer, __call = function(_, ...) return HeaderHUDContainer.new(...) end })
  678.  
  679. ScriptNameHUDContainer = {}
  680. ScriptNameHUDContainer_mt = {__index = ScriptNameHUDContainer}
  681.  
  682. function ScriptNameHUDContainer.new(title, parent, inset, signalkey, transformer)
  683. local c = {}
  684. setmetatable(c, ScriptNameHUDContainer_mt)
  685.  
  686. c.id = -1 -- To be set by the parent
  687. c.x = 0
  688. c.y = 0
  689. c.height = 0
  690. c.parent = parent
  691.  
  692. c.huds = {}
  693.  
  694. local inset = inset or HiddenConfig.xHeaderInset
  695.  
  696. c.headerHUD = HUD.New(c.x + inset, c.y, title, HiddenConfig.keyColor.r, HiddenConfig.keyColor.g, HiddenConfig.keyColor.b)
  697. c.huds[inset] = c.headerHUD
  698.  
  699. parent:addChild(c)
  700. Signal.OnReceive(signalkey, function(self, name)
  701. local name = transformer and transformer(name) or name
  702. c.headerHUD:SetText(name)
  703. local size = 3 * (name:len() - 3) + 13
  704. local inset = 95-size
  705. c.headerHUD:SetPosition(c.x + inset, c.y)
  706. c:grow(HiddenConfig.yStandardHeight)
  707. self:Close()
  708. end)
  709.  
  710. return c
  711. end
  712.  
  713. function ScriptNameHUDContainer:grow(diffy)
  714. self.height = self.height + diffy
  715. if self.parent then
  716. for id = self.id + 1, self.parent.nextChildID - 1 do
  717. self.parent.children[id]:moveRelative(0, diffy)
  718. end
  719. self.parent:grow(diffy)
  720. end
  721. end
  722.  
  723.  
  724. setmetatable(ScriptNameHUDContainer, { __index = HUDContainer, __call = function(_, ...) return ScriptNameHUDContainer.new(...) end })
  725.  
  726. EmptyHUDContainer = {}
  727. EmptyHUDContainer_mt = {__index = EmptyHUDContainer}
  728.  
  729. function EmptyHUDContainer.new(parent, height)
  730. local c = {}
  731. setmetatable(c, EmptyHUDContainer_mt)
  732.  
  733. c.x = 0
  734. c.y = 0
  735. c.height = height or HiddenConfig.yStandardHeight
  736.  
  737. c.huds = {}
  738.  
  739. parent:addChild(c)
  740.  
  741. return c
  742. end
  743.  
  744. setmetatable(EmptyHUDContainer, { __index = HUDContainer, __call = function(_, ...) return EmptyHUDContainer.new(...) end })
  745.  
  746. KeyValHUDContainer = {}
  747. KeyValHUDContainer_mt = {__index = KeyValHUDContainer}
  748.  
  749. function KeyValHUDContainer.new(desc, parent)
  750. local c = {}
  751. setmetatable(c, KeyValHUDContainer_mt)
  752.  
  753. c.x = 0
  754. c.y = 0
  755. c.height = HiddenConfig.yStandardHeight
  756. c.accum = 0
  757.  
  758. c.huds = {}
  759.  
  760. c.keyHUD = HUD.New(c.x , c.y, desc, HiddenConfig.keyColor.r, HiddenConfig.keyColor.g, HiddenConfig.keyColor.b)
  761. c.huds[0] = c.keyHUD
  762. c.valHUD = HUD.New(c.x + HiddenConfig.xDescWidth, c.y, "", HiddenConfig.valColor.r, HiddenConfig.valColor.g, HiddenConfig.valColor.b)
  763. c.huds[HiddenConfig.xDescWidth] = c.valHUD
  764.  
  765. parent:addChild(c)
  766.  
  767. return c
  768. end
  769.  
  770. setmetatable(KeyValHUDContainer, {__index = HUDContainer, __call = function(_, ...) return KeyValHUDContainer.new(...) end})
  771.  
  772. function KeyValHUDContainer:setValText(newText)
  773. self.valHUD:SetText(newText)
  774. end
  775.  
  776. function KeyValHUDContainer:incAccum(val)
  777. self.accum = self.accum + val
  778. end
  779.  
  780. function KeyValHUDContainer:getAccum(newText)
  781. return self.accum
  782. end
  783.  
  784. ContainerContainer = {}
  785. ContainerContainer_mt = {__index = ContainerContainer}
  786.  
  787. function ContainerContainer.new(x, y, parent)
  788. local c = {}
  789. setmetatable(c, ContainerContainer_mt)
  790.  
  791. c.id = -1 -- To be set by the parent
  792. c.x = x
  793. c.y = y
  794. c.height = 0
  795. c.parent = parent
  796. -- Only used by those containers that contain item huds
  797. c.containedItemIDs = {}
  798.  
  799. c.children = {}
  800. c.nextChildID = 1
  801.  
  802. if parent then
  803. parent:addChild(c)
  804. end
  805.  
  806. return c
  807. end
  808. setmetatable(ContainerContainer, {__call = function(_, ...) return ContainerContainer.new(...) end})
  809.  
  810. function ContainerContainer:addChild(child)
  811. child.id = self.nextChildID
  812. self.nextChildID = self.nextChildID + 1
  813. table.insert(self.children, child)
  814. child:move(self.x, self.y + self.height)
  815. self:grow(child.height)
  816. end
  817.  
  818. function ContainerContainer:grow(diffy)
  819. self.height = self.height + diffy
  820. if self.parent then
  821. for id = self.id + 1, self.parent.nextChildID - 1 do
  822. self.parent.children[id]:moveRelative(0, diffy)
  823. end
  824. self.parent:grow(diffy)
  825. end
  826. end
  827.  
  828. function ContainerContainer:moveRelative(x, y)
  829. for _, child in ipairs(self.children) do
  830. child:moveRelative(x, y)
  831. end
  832. self.x = self.x + x
  833. self.y = self.y + y
  834. end
  835.  
  836. function ContainerContainer:move(x, y)
  837. local diffx = x - self.x
  838. local diffy = y - self.y
  839. self:moveRelative(diffx, diffy)
  840. end
  841.  
  842. ItemHUDContainer = {}
  843. ItemHUDContainer_mt = {__index = ItemHUDContainer}
  844.  
  845. function ItemHUDContainer.new(id, parent, formatFunc)
  846. local c = {}
  847. setmetatable(c, ItemHUDContainer_mt)
  848.  
  849. c.formatFunc = formatFunc
  850. c.x = 0
  851. c.y = 0
  852. c.itemID = id
  853. c.height = HiddenConfig.yStandardHeight
  854. c.accum = 0
  855.  
  856. c.imageHUD = HUD.New(c.x - HiddenConfig.xImageOffset, c.y + HiddenConfig.yImageOffset, id, 0, 0, 0)
  857. c.imageHUD:SetItemSize(HiddenConfig.imageSize)
  858. c.keyHUD = HUD.New(c.x , c.y, Item.GetName(id), HiddenConfig.keyColor.r, HiddenConfig.keyColor.g, HiddenConfig.keyColor.b)
  859. c.valHUD = HUD.New(c.x + HiddenConfig.xDescWidth, c.y, "", HiddenConfig.valColor.r, HiddenConfig.valColor.g, HiddenConfig.valColor.b)
  860. c:addCount(0)
  861. parent:addChild(c)
  862.  
  863. return c
  864. end
  865.  
  866. setmetatable(ItemHUDContainer, {__index = HUDContainer, __call = function(_, ...) return ItemHUDContainer.new(...) end})
  867.  
  868. function ItemHUDContainer:addCount(num)
  869. self.accum = self.accum + num
  870. self.valHUD:SetText(self.formatFunc(self.itemID, self.accum))
  871. end
  872.  
  873. function ItemHUDContainer:move(newx, newy)
  874. self.y = newy
  875. self.x = newx
  876. self.imageHUD:SetPosition(self.x + HiddenConfig.xImageOffset, self.y + HiddenConfig.yImageOffset)
  877. self.keyHUD:SetPosition(self.x, self.y)
  878. self.valHUD:SetPosition(self.x + HiddenConfig.xDescWidth, self.y)
  879. end
  880.  
  881. function ItemHUDContainer:moveRelative(diffx, diffy)
  882. self:move(self.x + diffx, self.y + diffy)
  883. end
  884.  
  885. local function lootFormatFunc(id, count)
  886. return string.format("%d (%d gp)", count, count * Data.getValue(id))
  887. end
  888.  
  889. local function wasteFormatFunc(id, count)
  890. return string.format("%d (%d gp)", count, count * Data.getCost(id))
  891. end
  892.  
  893. local function activeWasteFormatFunc(id, count)
  894. local hour = math.floor(count / 3600)
  895. local min = math.floor((count / 60) % 60)
  896. local sec = math.floor(count % 60)
  897. return string.format("%02.f:%02.f:%02.f (%d gp)", hour, min, sec, count * Data.getCost(id) / Data.durations[id])
  898. end
  899.  
  900. function LootItemHUDContainer(id, parent)
  901. local c = ItemHUDContainer(id, parent, lootFormatFunc)
  902. Events.subscribe(id, function(diff)
  903. c:addCount(diff)
  904. end)
  905. return c
  906. end
  907.  
  908. function WasteItemHUDContainer(id, parent)
  909. local c = ItemHUDContainer(id, parent, wasteFormatFunc)
  910. Events.subscribe(id + HiddenConfig.WasteMod, function(diff)
  911. c:addCount(diff)
  912. end)
  913. return c
  914. end
  915.  
  916. function ActiveWasteItemHUDContainer(id, parent)
  917. local c = ItemHUDContainer(id, parent, activeWasteFormatFunc)
  918. Events.subscribe(id + HiddenConfig.WasteMod, function(diff)
  919. c:addCount(diff)
  920. end)
  921. return c
  922. end
  923.  
  924. Config.loadConfig()
  925.  
  926. local function formatGain(total)
  927. if total > 1e+6 then
  928. return string.format("%.01fkk", total/1e+6)
  929. elseif total > 1e+3 then
  930. return string.format("%.01fk", total/1e+3)
  931. else
  932. return tostring(math.floor(total))
  933. end
  934. end
  935.  
  936. local function leftX(screen)
  937. return math.max(math.floor((screen.gamewindowx - HiddenConfig.panelWidth) / 2), 10)
  938. end
  939.  
  940. local function rightX(screen)
  941. local xRightBase = screen.gamewindowx + screen.gamewindoww
  942. return math.floor((screen.eqwindowx - HiddenConfig.panelWidth - xRightBase) / 2) + xRightBase
  943. end
  944.  
  945. local screen = HUD.GetMainWindowDimensions()
  946. local leftx = leftX(screen)
  947. local rightx = rightX(screen)
  948.  
  949. -- Create the left column of the HUD
  950. local leftColumn = ContainerContainer(leftx, HiddenConfig.yInset)
  951. local title = HeaderHUDContainer("shAdOwHUD", leftColumn, HiddenConfig.xTitleInset)
  952. local script = ScriptNameHUDContainer("", leftColumn, 0, "script name")
  953. local script = ScriptNameHUDContainer("", leftColumn, 0, "script author", function(author)
  954. return "by " .. author
  955. end)
  956.  
  957. -- General statistics
  958. local ping = KeyValHUDContainer("Ping:", leftColumn)
  959. Events.subscribe("general", function(general)
  960. ping:setValText(general.ping .. " ms")
  961. end)
  962.  
  963. local level = KeyValHUDContainer("Level:", leftColumn)
  964. Events.subscribe("general", function(general)
  965. level:setValText(tostring(general.level))
  966. end)
  967.  
  968. local stamina = KeyValHUDContainer("Stamina:", leftColumn)
  969. Events.subscribe("general", function(general)
  970. local totalStamina = general.stamina
  971. local staminaH = math.floor(totalStamina/60)
  972. local staminaM = totalStamina % 60
  973. stamina:setValText(string.format("%02.f:%02.f", staminaH, staminaM))
  974. end)
  975.  
  976. local session = KeyValHUDContainer("Session Length:", leftColumn)
  977. Events.subscribe("general", function(general)
  978. local totalSeconds = general.usedSeconds
  979. local seconds = math.floor(totalSeconds % 60)
  980. local minutes = math.floor(totalSeconds/60 % 60)
  981. local hours = math.floor(totalSeconds/3600)
  982. local formatted = string.format("%02.f:%02.f:%02.f", hours, minutes, seconds)
  983. session:setValText(formatted)
  984. end)
  985.  
  986. local bankBalance = KeyValHUDContainer("Balance:", leftColumn)
  987. Events.subscribe("general", function(general)
  988. bankBalance:setValText(string.format("%s gp", formatGain(general.balance)))
  989. end)
  990.  
  991. local timeToLevel = KeyValHUDContainer("Time to level:", leftColumn)
  992. Events.subscribe("general", function(general)
  993. local totalSeconds = general.timeToLevel
  994. local minutes = math.floor(totalSeconds/60 % 60)
  995. local hours = math.floor(totalSeconds/3600)
  996. local formatted = string.format("%02.f:%02.f", hours, minutes)
  997. timeToLevel:setValText(totalSeconds == -1 and "--" or formatted)
  998. end)
  999.  
  1000. -- Exp gain
  1001. EmptyHUDContainer(leftColumn)
  1002. local expPerH = KeyValHUDContainer("Exp:", leftColumn)
  1003. Events.subscribe("general", function(general)
  1004. local expHK = general.expH / 1000
  1005. expPerH:setValText(string.format("%.01f k/h", expHK))
  1006. end)
  1007. local expPerSH = KeyValHUDContainer("", leftColumn)
  1008. Events.subscribe("general", function(general)
  1009. local expSHK = general.expSH / 1000
  1010. expPerSH:setValText(string.format("%.01f k/sh", expSHK))
  1011. end)
  1012.  
  1013. -- Profit gain
  1014. EmptyHUDContainer(leftColumn)
  1015. local profitsPerH = KeyValHUDContainer("Profits:", leftColumn)
  1016. Events.subscribe("general", function(general)
  1017. local profitsHK = general.profitsH / 1000
  1018. profitsPerH:setValText(string.format("%.01f k/h", profitsHK))
  1019. end)
  1020. local profitsPerSH = KeyValHUDContainer("", leftColumn)
  1021. Events.subscribe("general", function(general)
  1022. local profitsSHK = general.profitsSH / 1000
  1023. profitsPerSH:setValText(string.format("%.01f k/sh", profitsSHK))
  1024. end)
  1025.  
  1026. -- Totals
  1027. EmptyHUDContainer(leftColumn)
  1028. local expTotal = KeyValHUDContainer("Totals:", leftColumn)
  1029. Events.subscribe("general", function(general)
  1030. expTotal:setValText(string.format("%s exp", formatGain(general.gainedExp)))
  1031. end)
  1032. local profitsTotal = KeyValHUDContainer("", leftColumn)
  1033. Events.subscribe("general", function(general)
  1034. profitsTotal:setValText(string.format("%s gp", formatGain(general.totalProfit)))
  1035. end)
  1036.  
  1037. -- Right Column
  1038. local rightColumn = ContainerContainer(rightx, HiddenConfig.yInset)
  1039. local loot = ContainerContainer(0, 0, rightColumn)
  1040. local waste = ContainerContainer(0, 0, rightColumn)
  1041.  
  1042. -- Waste
  1043. EmptyHUDContainer(waste)
  1044. local wasteHeader = HeaderHUDContainer("WASTE", waste)
  1045. EmptyHUDContainer(waste, 10)
  1046.  
  1047. local wasteParts = ContainerContainer(0, 0, waste)
  1048. Events.subscribe("newWasteIDs", function(newids)
  1049. for _, id in ipairs(newids) do
  1050. if not wasteParts.containedItemIDs[id] then
  1051. if Data.durations[id] then
  1052. ActiveWasteItemHUDContainer(id, wasteParts)
  1053. else
  1054. WasteItemHUDContainer(id, wasteParts)
  1055. end
  1056. wasteParts.containedItemIDs[id] = true
  1057. end
  1058. end
  1059. end)
  1060.  
  1061. local wasteSum = ContainerContainer(0, 0, waste)
  1062. EmptyHUDContainer(wasteSum)
  1063. local totalWaste = KeyValHUDContainer("Wasted:", wasteSum)
  1064. Events.subscribe("general", function(general)
  1065. totalWaste:setValText(string.format("%d gp", general.totalWaste))
  1066. end)
  1067.  
  1068. -- Loot
  1069. local lootHeader = HeaderHUDContainer("LOOT", loot)
  1070. EmptyHUDContainer(waste, 10)
  1071.  
  1072. local lootParts = ContainerContainer(0, 0, loot)
  1073. Events.subscribe("newLootIDs", function(newids)
  1074. for _, id in ipairs(newids) do
  1075. if not lootParts.containedItemIDs[id] then
  1076. LootItemHUDContainer(id, lootParts)
  1077. lootParts.containedItemIDs[id] = true
  1078. end
  1079. end
  1080. end)
  1081.  
  1082. local lootSum = ContainerContainer(0, 0, loot)
  1083. EmptyHUDContainer(lootSum)
  1084. local totalLoot = KeyValHUDContainer("Looted:", lootSum)
  1085. Events.subscribe("general", function(general)
  1086. totalLoot:setValText(string.format("%d gp", general.totalLoot))
  1087. end)
  1088.  
  1089.  
  1090. Module("Resizer", function(module)
  1091. local newScreen = HUD.GetMainWindowDimensions()
  1092.  
  1093. local newLeftx = leftX(newScreen)
  1094. if newLeftx ~= leftx then
  1095. leftx = newLeftx
  1096. leftColumn:move(leftx, HiddenConfig.yInset)
  1097. end
  1098.  
  1099. local newRightx = rightX(newScreen)
  1100. if newRightx ~= rightx then
  1101. rightx = newRightx
  1102. rightColumn:move(rightx, HiddenConfig.yInset)
  1103. end
  1104.  
  1105. module:Delay(1000)
  1106. end)
Add Comment
Please, Sign In to add comment