Zerotorescue@17
|
1 local addon = select(2, ...);
|
Zerotorescue@1
|
2 local mod = addon:NewModule("Summary", "AceEvent-3.0", "AceTimer-3.0");
|
Zerotorescue@0
|
3
|
Zerotorescue@0
|
4 local AceGUI = LibStub("AceGUI-3.0");
|
Zerotorescue@0
|
5
|
Zerotorescue@23
|
6 local unknownItemName = "Unknown (#%d)";
|
Zerotorescue@23
|
7
|
Zerotorescue@23
|
8 local itemsCache = {};
|
Zerotorescue@23
|
9
|
Zerotorescue@23
|
10 local CACHE_ITEMS_TOTAL, CACHE_ITEMS_CURRENT = 0, 0;
|
Zerotorescue@23
|
11 local cacheStart;
|
Zerotorescue@23
|
12
|
Zerotorescue@0
|
13 function mod:OnEnable()
|
Zerotorescue@0
|
14 self:RegisterWidgets();
|
Zerotorescue@0
|
15
|
Zerotorescue@1
|
16 -- Register our own slash commands
|
Zerotorescue@1
|
17 addon:RegisterSlash(function()
|
Zerotorescue@1
|
18 mod:BuildMain();
|
Zerotorescue@1
|
19 mod:Build();
|
Zerotorescue@36
|
20 end, { "s", "sum", "summary" }, "|Hfunction:InventoriumCommandHandler:summary|h|cff00fff7/im summary|r|h (or /im s) - Show the summary window containing an overview of all items within the groups tracked at this current character.");
|
Zerotorescue@23
|
21 addon:RegisterSlash(function()
|
Zerotorescue@23
|
22 if mod.frame then
|
Zerotorescue@23
|
23 mod.frame:SetWidth(650);
|
Zerotorescue@23
|
24 mod.frame:SetHeight(600);
|
Zerotorescue@23
|
25
|
Zerotorescue@23
|
26 print("Resetting width and height of the summary frame.");
|
Zerotorescue@23
|
27 end
|
Zerotorescue@36
|
28 end, { "r", "reset" }, "|Hfunction:InventoriumCommandHandler:reset|h|cff00fff7/im reset|r|h (or /im r) - Reset the size of the summary frame.");
|
Zerotorescue@0
|
29 end
|
Zerotorescue@0
|
30
|
Zerotorescue@13
|
31 local function ShowTooltip(self)
|
Zerotorescue@13
|
32 -- If this function is called from a widget, self is the widget and self.frame the actual frame
|
Zerotorescue@13
|
33 local this = self.frame or self;
|
Zerotorescue@13
|
34
|
Zerotorescue@13
|
35 GameTooltip:SetOwner(this, "ANCHOR_NONE");
|
Zerotorescue@13
|
36 GameTooltip:SetPoint("BOTTOM", this, "TOP");
|
Zerotorescue@13
|
37 GameTooltip:SetText(this.tooltipTitle, 1, .82, 0, 1);
|
Zerotorescue@13
|
38
|
Zerotorescue@13
|
39 if type(this.tooltip) == "string" then
|
Zerotorescue@13
|
40 GameTooltip:AddLine(this.tooltip, 1, 1, 1, 1);
|
Zerotorescue@13
|
41 end
|
Zerotorescue@13
|
42
|
Zerotorescue@13
|
43 GameTooltip:Show();
|
Zerotorescue@13
|
44 end
|
Zerotorescue@13
|
45
|
Zerotorescue@13
|
46 local function HideTooltip()
|
Zerotorescue@13
|
47 GameTooltip:Hide();
|
Zerotorescue@13
|
48 end
|
Zerotorescue@13
|
49
|
Zerotorescue@0
|
50 function mod:RegisterWidgets()
|
Zerotorescue@0
|
51 -- Register InlineGroupWithButton-widget
|
Zerotorescue@0
|
52 -- This widget adds a button next to the header of an inline group
|
Zerotorescue@0
|
53 -- SetPoint doesn't seem usable within AceGUI.
|
Zerotorescue@0
|
54
|
Zerotorescue@0
|
55 local widgetType = "InlineGroupWithButton";
|
Zerotorescue@0
|
56 local widgetVersion = 1;
|
Zerotorescue@0
|
57
|
Zerotorescue@0
|
58 local function Constructor()
|
Zerotorescue@0
|
59 local widget = AceGUI:Create("InlineGroup");
|
Zerotorescue@0
|
60 widget.type = widgetType;
|
Zerotorescue@0
|
61
|
Zerotorescue@0
|
62 widget.MakeButton = function(self, buttonSettings)
|
Zerotorescue@0
|
63 if type(buttonSettings) == "table" then
|
Zerotorescue@14
|
64 if not self.btnQueue then
|
Zerotorescue@14
|
65 -- Because widgets are re-used, we don't want to recreate this button
|
Zerotorescue@14
|
66 self.btnQueue = CreateFrame("Button", nil, self.frame, "UIPanelButtonTemplate");
|
Zerotorescue@14
|
67 self.btnQueue:SetHeight(22);
|
Zerotorescue@14
|
68 self.btnQueue:SetWidth(120);
|
Zerotorescue@14
|
69 end
|
Zerotorescue@14
|
70 self.btnQueue:SetText(buttonSettings.name);
|
Zerotorescue@14
|
71 self.btnQueue:SetPoint("TOPRIGHT", self.frame, "TOPRIGHT", -10, 5);
|
Zerotorescue@14
|
72
|
Zerotorescue@14
|
73 -- Triggers
|
Zerotorescue@14
|
74 self.btnQueue:SetScript("OnClick", buttonSettings.exec);
|
Zerotorescue@14
|
75
|
Zerotorescue@14
|
76 -- Tooltip
|
Zerotorescue@14
|
77 self.btnQueue.tooltipTitle = buttonSettings.name;
|
Zerotorescue@14
|
78 self.btnQueue.tooltip = buttonSettings.desc or "";
|
Zerotorescue@14
|
79 self.btnQueue:SetScript("OnEnter", ShowTooltip);
|
Zerotorescue@14
|
80 self.btnQueue:SetScript("OnLeave", HideTooltip);
|
Zerotorescue@0
|
81 else
|
Zerotorescue@0
|
82 error("settings must be a table - usage: MakeButton(table);");
|
Zerotorescue@0
|
83 end
|
Zerotorescue@14
|
84 end;
|
Zerotorescue@0
|
85
|
Zerotorescue@0
|
86 return widget;
|
Zerotorescue@0
|
87 end
|
Zerotorescue@0
|
88
|
Zerotorescue@0
|
89 AceGUI:RegisterWidgetType(widgetType, Constructor, widgetVersion);
|
Zerotorescue@0
|
90 end
|
Zerotorescue@0
|
91
|
Zerotorescue@0
|
92 function mod:BuildMain()
|
Zerotorescue@11
|
93 LibStub("AceConfigDialog-3.0"):Close("InventoriumOptions");
|
Zerotorescue@0
|
94
|
Zerotorescue@10
|
95 self:CloseFrame();
|
Zerotorescue@10
|
96
|
Zerotorescue@1
|
97 -- Main Window
|
Zerotorescue@1
|
98 mod.frame = AceGUI:Create("Frame");
|
Zerotorescue@23
|
99 _G["InventoriumSummary"] = mod.frame; -- name the global frame so it can be put in the UISpecialFrames
|
Zerotorescue@13
|
100 mod.frame:SetTitle("Summary");
|
Zerotorescue@1
|
101 mod.frame:SetLayout("Fill");
|
Zerotorescue@1
|
102 mod.frame:SetCallback("OnClose", function(widget)
|
Zerotorescue@7
|
103 mod:CancelTimer(self.tmrUpdater, true);
|
Zerotorescue@9
|
104 mod:CloseFrame();
|
Zerotorescue@1
|
105 end);
|
Zerotorescue@13
|
106 mod.frame:SetWidth(addon.db.global.defaults.summary.width);
|
Zerotorescue@13
|
107 mod.frame:SetHeight(addon.db.global.defaults.summary.height);
|
Zerotorescue@13
|
108 mod.frame.OnWidthSet = function(_, width)
|
Zerotorescue@13
|
109 addon.db.global.defaults.summary.width = width;
|
Zerotorescue@13
|
110 end;
|
Zerotorescue@13
|
111 mod.frame.OnHeightSet = function(_, height)
|
Zerotorescue@13
|
112 addon.db.global.defaults.summary.height = height;
|
Zerotorescue@13
|
113 end;
|
Zerotorescue@0
|
114
|
Zerotorescue@23
|
115 -- Close on escape
|
Zerotorescue@23
|
116 tinsert(UISpecialFrames, "InventoriumSummary");
|
Zerotorescue@23
|
117
|
Zerotorescue@1
|
118 -- ScrollFrame child
|
Zerotorescue@1
|
119 mod.scrollFrame = AceGUI:Create("ScrollFrame");
|
Zerotorescue@1
|
120 mod.scrollFrame:SetLayout("Flow");
|
Zerotorescue@0
|
121
|
Zerotorescue@1
|
122 mod.frame:AddChild(mod.scrollFrame);
|
Zerotorescue@1
|
123
|
Zerotorescue@1
|
124 -- Reset items cache
|
Zerotorescue@1
|
125 table.wipe(itemsCache);
|
Zerotorescue@0
|
126 end
|
Zerotorescue@0
|
127
|
Zerotorescue@9
|
128 function mod:CloseFrame()
|
Zerotorescue@9
|
129 if mod.frame then
|
Zerotorescue@9
|
130 mod.frame:Release();
|
Zerotorescue@9
|
131 mod.frame = nil;
|
Zerotorescue@10
|
132
|
Zerotorescue@10
|
133 -- Stop caching
|
Zerotorescue@10
|
134
|
Zerotorescue@10
|
135 -- Stop timer
|
Zerotorescue@10
|
136 self:CancelTimer(self.tmrUpdater, true);
|
Zerotorescue@10
|
137
|
Zerotorescue@10
|
138 -- Reset trackers
|
Zerotorescue@10
|
139 CACHE_ITEMS_TOTAL = 0;
|
Zerotorescue@10
|
140 CACHE_ITEMS_CURRENT = 0;
|
Zerotorescue@9
|
141 end
|
Zerotorescue@9
|
142 end
|
Zerotorescue@9
|
143
|
Zerotorescue@0
|
144 local sortMethod = "item";
|
Zerotorescue@0
|
145 local sortDirectory = "ASC";
|
Zerotorescue@0
|
146 local function ReSort(subject)
|
Zerotorescue@0
|
147 if sortMethod == subject then
|
Zerotorescue@0
|
148 sortDirectory = (sortDirectory == "ASC" and "DESC") or "ASC";
|
Zerotorescue@0
|
149 else
|
Zerotorescue@0
|
150 sortDirectory = "ASC";
|
Zerotorescue@0
|
151 end
|
Zerotorescue@0
|
152 sortMethod = subject;
|
Zerotorescue@0
|
153
|
Zerotorescue@0
|
154 mod:Build();
|
Zerotorescue@0
|
155 end
|
Zerotorescue@10
|
156
|
Zerotorescue@10
|
157 -- From http://www.wowwiki.com/API_sort
|
Zerotorescue@10
|
158 local function pairsByKeys (t, f)
|
Zerotorescue@10
|
159 local a = {}
|
Zerotorescue@10
|
160 for n in pairs(t) do table.insert(a, n) end
|
Zerotorescue@10
|
161 table.sort(a, f)
|
Zerotorescue@10
|
162 local i = 0 -- iterator variable
|
Zerotorescue@10
|
163 local iter = function () -- iterator function
|
Zerotorescue@10
|
164 i = i + 1
|
Zerotorescue@10
|
165 if a[i] == nil then return nil
|
Zerotorescue@10
|
166 else return a[i], t[a[i]]
|
Zerotorescue@10
|
167 end
|
Zerotorescue@10
|
168 end
|
Zerotorescue@10
|
169 return iter
|
Zerotorescue@10
|
170 end
|
Zerotorescue@0
|
171
|
Zerotorescue@0
|
172 function mod:Build()
|
Zerotorescue@10
|
173 local buildStartTime, times = GetTime(), {};
|
Zerotorescue@0
|
174
|
Zerotorescue@1
|
175 -- We are going to add hunderds of widgets to this container, but don't want it to also cause hunderds of reflows, thus pause reflowing and just do it once when everything is prepared
|
Zerotorescue@1
|
176 -- This appears to be required for each container we wish to pause, so also do this for the contents
|
Zerotorescue@1
|
177 mod.scrollFrame:PauseLayout();
|
Zerotorescue@1
|
178
|
Zerotorescue@10
|
179 mod.scrollFrame:ReleaseChildren();
|
Zerotorescue@10
|
180
|
Zerotorescue@10
|
181 -- Refresh button
|
Zerotorescue@10
|
182 local btnRefresh = AceGUI:Create("Button");
|
Zerotorescue@10
|
183 btnRefresh:SetText("Refresh");
|
Zerotorescue@10
|
184 btnRefresh:SetRelativeWidth(.2);
|
Zerotorescue@10
|
185 btnRefresh:SetCallback("OnClick", function()
|
Zerotorescue@10
|
186 -- Reset items cache
|
Zerotorescue@10
|
187 table.wipe(itemsCache);
|
Zerotorescue@10
|
188
|
Zerotorescue@10
|
189 -- Rebuild itemlist and start caching
|
Zerotorescue@10
|
190 mod:Build();
|
Zerotorescue@10
|
191 end);
|
Zerotorescue@13
|
192 btnRefresh:SetCallback("OnEnter", ShowTooltip);
|
Zerotorescue@13
|
193 btnRefresh:SetCallback("OnLeave", HideTooltip);
|
Zerotorescue@13
|
194 btnRefresh.frame.tooltipTitle = "Refresh Cache";
|
Zerotorescue@13
|
195 btnRefresh.frame.tooltip = "Refresh the list and recache the item counts and auction values.";
|
Zerotorescue@10
|
196
|
Zerotorescue@10
|
197 mod.scrollFrame:AddChild(btnRefresh);
|
Zerotorescue@10
|
198
|
Zerotorescue@13
|
199 local lblSpacer = AceGUI:Create("Label");
|
Zerotorescue@13
|
200 lblSpacer:SetRelativeWidth(.03);
|
Zerotorescue@13
|
201
|
Zerotorescue@13
|
202 mod.scrollFrame:AddChild(lblSpacer);
|
Zerotorescue@13
|
203
|
Zerotorescue@10
|
204 -- Speed slider
|
Zerotorescue@13
|
205 local sdrSpeed = AceGUI:Create("Slider");
|
Zerotorescue@13
|
206 sdrSpeed:SetLabel("Processing speed");
|
Zerotorescue@14
|
207 sdrSpeed:SetSliderValues(0.01, 5, 0.05);
|
Zerotorescue@13
|
208 sdrSpeed:SetIsPercent(true);
|
Zerotorescue@13
|
209 sdrSpeed:SetRelativeWidth(.3);
|
Zerotorescue@13
|
210 sdrSpeed:SetCallback("OnMouseUp", function(self, event, value)
|
Zerotorescue@13
|
211 addon.db.global.defaults.summary.speed = ceil( value * 100 / 5 );
|
Zerotorescue@13
|
212
|
Zerotorescue@13
|
213 CACHE_ITEMS_PER_UPDATE = addon.db.global.defaults.summary.speed; -- max = 20, min = 1
|
Zerotorescue@10
|
214 end);
|
Zerotorescue@13
|
215 sdrSpeed:SetValue( addon.db.global.defaults.summary.speed * 5 / 100 );
|
Zerotorescue@13
|
216 sdrSpeed:SetCallback("OnEnter", ShowTooltip);
|
Zerotorescue@13
|
217 sdrSpeed:SetCallback("OnLeave", HideTooltip);
|
Zerotorescue@13
|
218 sdrSpeed.frame.tooltipTitle = "Caching Processing Speed";
|
Zerotorescue@13
|
219 sdrSpeed.frame.tooltip = "Change the speed at which item counts and auction values are being cached. Higher is faster but may drastically reduce your FPS while caching.\n\nAnything above 100% will probably become uncomfortable.";
|
Zerotorescue@10
|
220
|
Zerotorescue@13
|
221 mod.scrollFrame:AddChild(sdrSpeed);
|
Zerotorescue@13
|
222
|
Zerotorescue@13
|
223 local lblSpacer = AceGUI:Create("Label");
|
Zerotorescue@13
|
224 lblSpacer:SetRelativeWidth(.23);
|
Zerotorescue@13
|
225
|
Zerotorescue@13
|
226 mod.scrollFrame:AddChild(lblSpacer);
|
Zerotorescue@13
|
227
|
Zerotorescue@13
|
228 -- Config button
|
Zerotorescue@13
|
229 --[[local btnConfig = AceGUI:Create("Button");
|
Zerotorescue@13
|
230 btnConfig:SetText("Config");
|
Zerotorescue@13
|
231 btnConfig:SetRelativeWidth(.2);
|
Zerotorescue@13
|
232 btnConfig:SetCallback("OnClick", function()
|
Zerotorescue@13
|
233 --TODO: Tidy up
|
Zerotorescue@13
|
234 SlashCmdList["INVENTORIUM"]("config");
|
Zerotorescue@13
|
235 end);
|
Zerotorescue@13
|
236 btnConfig:SetCallback("OnEnter", ShowTooltip);
|
Zerotorescue@13
|
237 btnConfig:SetCallback("OnLeave", HideTooltip);
|
Zerotorescue@13
|
238 btnConfig.frame.tooltipTitle = "Config";
|
Zerotorescue@13
|
239 btnConfig.frame.tooltip = "Click to open the config window. This will close the current window.";
|
Zerotorescue@13
|
240
|
Zerotorescue@13
|
241 mod.scrollFrame:AddChild(btnConfig);]]
|
Zerotorescue@13
|
242
|
Zerotorescue@13
|
243 local lblSpacer = AceGUI:Create("Label");
|
Zerotorescue@13
|
244 lblSpacer:SetRelativeWidth(.03);
|
Zerotorescue@13
|
245
|
Zerotorescue@13
|
246 mod.scrollFrame:AddChild(lblSpacer);
|
Zerotorescue@13
|
247
|
Zerotorescue@13
|
248 -- Queue all button
|
Zerotorescue@13
|
249 local btnQueueAll = AceGUI:Create("Button");
|
Zerotorescue@13
|
250 btnQueueAll:SetText("Queue All");
|
Zerotorescue@13
|
251 btnQueueAll:SetRelativeWidth(.2);
|
Zerotorescue@13
|
252 btnQueueAll:SetCallback("OnClick", function()
|
Zerotorescue@14
|
253 self:SendMessage("IM_QUEUE_ALL");
|
Zerotorescue@13
|
254 end);
|
Zerotorescue@13
|
255 btnQueueAll:SetCallback("OnEnter", ShowTooltip);
|
Zerotorescue@13
|
256 btnQueueAll:SetCallback("OnLeave", HideTooltip);
|
Zerotorescue@13
|
257 btnQueueAll.frame.tooltipTitle = "Queue all";
|
Zerotorescue@13
|
258 btnQueueAll.frame.tooltip = "Queue everything that requires restocking within every single visible group.";
|
Zerotorescue@13
|
259
|
Zerotorescue@13
|
260 mod.scrollFrame:AddChild(btnQueueAll);
|
Zerotorescue@10
|
261
|
Zerotorescue@10
|
262 times.init = ceil( ( GetTime() - buildStartTime ) * 1000 );
|
Zerotorescue@10
|
263 addon:Debug("Time spent legend: (init / sorting / preparing / building / all).");
|
Zerotorescue@10
|
264
|
Zerotorescue@1
|
265 local playerName = UnitName("player");
|
Zerotorescue@1
|
266
|
Zerotorescue@1
|
267 -- Go through all our stored groups
|
Zerotorescue@10
|
268 for groupName, values in pairsByKeys(addon.db.global.groups, function(a, b) return a:lower() < b:lower(); end) do
|
Zerotorescue@10
|
269 local groupStartTime, groupTimes = GetTime(), {};
|
Zerotorescue@10
|
270
|
Zerotorescue@50
|
271 local trackAt = addon:GetOptionByKey(groupName, "trackAtCharacters");
|
Zerotorescue@50
|
272
|
Zerotorescue@1
|
273
|
Zerotorescue@1
|
274 -- Does this group have any items and do we want to track it at this char?
|
Zerotorescue@1
|
275 if values.items and trackAt[playerName] then
|
Zerotorescue@10
|
276
|
Zerotorescue@10
|
277
|
Zerotorescue@0
|
278 -- Get group settings
|
Zerotorescue@50
|
279 local minimumStock = addon:GetOptionByKey(groupName, "minimumStock");
|
Zerotorescue@50
|
280 local showWhenBelow = addon:GetOptionByKey(groupName, "summaryThresholdShow");
|
Zerotorescue@50
|
281 local priceThreshold = addon:GetOptionByKey(groupName, "priceThreshold");
|
Zerotorescue@50
|
282 local hideWhenBelowPriceThreshold = addon:GetOptionByKey(groupName, "summaryHidePriceThreshold");
|
Zerotorescue@50
|
283 local alwaysGetAuctionValue = addon:GetOptionByKey(groupName, "alwaysGetAuctionValue");
|
Zerotorescue@10
|
284
|
Zerotorescue@0
|
285 -- Make group container
|
Zerotorescue@0
|
286 local iGroup = AceGUI:Create("InlineGroupWithButton");
|
Zerotorescue@1
|
287 iGroup:PauseLayout();
|
Zerotorescue@0
|
288 iGroup:SetTitle(groupName);
|
Zerotorescue@0
|
289 iGroup:SetFullWidth(true);
|
Zerotorescue@0
|
290 iGroup:SetLayout("Flow");
|
Zerotorescue@0
|
291 iGroup:MakeButton({
|
Zerotorescue@0
|
292 name = "Queue",
|
Zerotorescue@0
|
293 desc = "Queue all items in this group.",
|
Zerotorescue@0
|
294 exec = function()
|
Zerotorescue@14
|
295 print("Queueing all items within " .. groupName .. " craftable by the currently open profession.");
|
Zerotorescue@14
|
296 self:SendMessage("IM_QUEUE_GROUP", groupName);
|
Zerotorescue@0
|
297 end,
|
Zerotorescue@0
|
298 });
|
Zerotorescue@0
|
299
|
Zerotorescue@10
|
300
|
Zerotorescue@10
|
301
|
Zerotorescue@0
|
302 -- Headers
|
Zerotorescue@1
|
303
|
Zerotorescue@0
|
304 -- Itemlink
|
Zerotorescue@0
|
305 local lblItem = AceGUI:Create("InteractiveLabel");
|
Zerotorescue@0
|
306 lblItem:SetText("|cfffed000Item|r");
|
Zerotorescue@0
|
307 lblItem:SetFontObject(GameFontHighlight);
|
Zerotorescue@50
|
308 lblItem:SetRelativeWidth(.6);
|
Zerotorescue@0
|
309 lblItem:SetCallback("OnClick", function() ReSort("item"); end);
|
Zerotorescue@13
|
310 lblItem:SetCallback("OnEnter", ShowTooltip);
|
Zerotorescue@13
|
311 lblItem:SetCallback("OnLeave", HideTooltip);
|
Zerotorescue@13
|
312 lblItem.frame.tooltipTitle = "Item";
|
Zerotorescue@13
|
313 lblItem.frame.tooltip = "Sort on the item quality, then name.";
|
Zerotorescue@0
|
314
|
Zerotorescue@0
|
315 iGroup:AddChild(lblItem);
|
Zerotorescue@0
|
316
|
Zerotorescue@50
|
317 -- Local quantity
|
Zerotorescue@50
|
318 local lblLocal = AceGUI:Create("InteractiveLabel");
|
Zerotorescue@50
|
319 lblLocal:SetText("|cfffed000Loc.|r");
|
Zerotorescue@50
|
320 lblLocal:SetFontObject(GameFontHighlight);
|
Zerotorescue@50
|
321 lblLocal:SetRelativeWidth(.099);
|
Zerotorescue@50
|
322 lblLocal:SetCallback("OnClick", function() ReSort("local"); end);
|
Zerotorescue@50
|
323 lblLocal:SetCallback("OnEnter", ShowTooltip);
|
Zerotorescue@50
|
324 lblLocal:SetCallback("OnLeave", HideTooltip);
|
Zerotorescue@50
|
325 lblLocal.frame.tooltipTitle = "Local stock";
|
Zerotorescue@50
|
326 lblLocal.frame.tooltip = "Sort on the amount of items currently in local stock.";
|
Zerotorescue@50
|
327
|
Zerotorescue@50
|
328 iGroup:AddChild(lblLocal);
|
Zerotorescue@50
|
329
|
Zerotorescue@0
|
330 -- Current quantity
|
Zerotorescue@0
|
331 local lblQuantity = AceGUI:Create("InteractiveLabel");
|
Zerotorescue@0
|
332 lblQuantity:SetText("|cfffed000Cur.|r");
|
Zerotorescue@0
|
333 lblQuantity:SetFontObject(GameFontHighlight);
|
Zerotorescue@10
|
334 lblQuantity:SetRelativeWidth(.099);
|
Zerotorescue@0
|
335 lblQuantity:SetCallback("OnClick", function() ReSort("current"); end);
|
Zerotorescue@13
|
336 lblQuantity:SetCallback("OnEnter", ShowTooltip);
|
Zerotorescue@13
|
337 lblQuantity:SetCallback("OnLeave", HideTooltip);
|
Zerotorescue@13
|
338 lblQuantity.frame.tooltipTitle = "Current stock";
|
Zerotorescue@13
|
339 lblQuantity.frame.tooltip = "Sort on the amount of items currently in stock.";
|
Zerotorescue@0
|
340
|
Zerotorescue@0
|
341 iGroup:AddChild(lblQuantity);
|
Zerotorescue@0
|
342
|
Zerotorescue@0
|
343 -- Required stock
|
Zerotorescue@9
|
344 local lblMinimumStock = AceGUI:Create("InteractiveLabel");
|
Zerotorescue@13
|
345 lblMinimumStock:SetText("|cfffed000Min.|r");
|
Zerotorescue@9
|
346 lblMinimumStock:SetFontObject(GameFontHighlight);
|
Zerotorescue@10
|
347 lblMinimumStock:SetRelativeWidth(.099);
|
Zerotorescue@9
|
348 lblMinimumStock:SetCallback("OnClick", function() ReSort("percentage"); end);
|
Zerotorescue@13
|
349 lblMinimumStock:SetCallback("OnEnter", ShowTooltip);
|
Zerotorescue@13
|
350 lblMinimumStock:SetCallback("OnLeave", HideTooltip);
|
Zerotorescue@13
|
351 lblMinimumStock.frame.tooltipTitle = "Minimum stock";
|
Zerotorescue@13
|
352 lblMinimumStock.frame.tooltip = "Sort on the minimum amount of items you wish to keep in stock.";
|
Zerotorescue@0
|
353
|
Zerotorescue@9
|
354 iGroup:AddChild(lblMinimumStock);
|
Zerotorescue@0
|
355
|
Zerotorescue@1
|
356 -- Lowest value
|
Zerotorescue@1
|
357 local lblValue = AceGUI:Create("InteractiveLabel");
|
Zerotorescue@1
|
358 lblValue:SetText("|cfffed000Value|r");
|
Zerotorescue@1
|
359 lblValue:SetFontObject(GameFontHighlight);
|
Zerotorescue@10
|
360 lblValue:SetRelativeWidth(.099);
|
Zerotorescue@1
|
361 lblValue:SetCallback("OnClick", function() ReSort("value"); end);
|
Zerotorescue@13
|
362 lblValue:SetCallback("OnEnter", ShowTooltip);
|
Zerotorescue@13
|
363 lblValue:SetCallback("OnLeave", HideTooltip);
|
Zerotorescue@13
|
364 lblValue.frame.tooltipTitle = "Value";
|
Zerotorescue@13
|
365 lblValue.frame.tooltip = "Sort on the item auction value.";
|
Zerotorescue@1
|
366
|
Zerotorescue@1
|
367 iGroup:AddChild(lblValue);
|
Zerotorescue@1
|
368
|
Zerotorescue@10
|
369
|
Zerotorescue@10
|
370 -- Retrieve items list
|
Zerotorescue@1
|
371 if not itemsCache[groupName] then
|
Zerotorescue@1
|
372 itemsCache[groupName] = {};
|
Zerotorescue@0
|
373
|
Zerotorescue@1
|
374 -- Sort item list
|
Zerotorescue@12
|
375 for itemId, _ in pairs(values.items) do
|
Zerotorescue@1
|
376 local itemName, itemLink, itemRarity = GetItemInfo(itemId);
|
Zerotorescue@1
|
377
|
Zerotorescue@1
|
378 table.insert(itemsCache[groupName], {
|
Zerotorescue@1
|
379 id = itemId,
|
Zerotorescue@12
|
380 name = itemName or unknownItemName:format(itemId),
|
Zerotorescue@23
|
381 link = itemLink,
|
Zerotorescue@23
|
382 value = ((priceThreshold == 0 and not alwaysGetAuctionValue) and -4) or -3,-- if (no price threshold is set for this item and you don't want to always get auction value), then don't look it up either --addon:GetAuctionValue(itemLink),
|
Zerotorescue@12
|
383 rarity = itemRarity or 1,
|
Zerotorescue@23
|
384 count = -3,--addon:GetItemCount(itemId, groupName),
|
Zerotorescue@50
|
385 localCount = -3,
|
Zerotorescue@1
|
386 set = {},
|
Zerotorescue@1
|
387 });
|
Zerotorescue@1
|
388 CACHE_ITEMS_TOTAL = CACHE_ITEMS_TOTAL + 1;
|
Zerotorescue@1
|
389 end
|
Zerotorescue@0
|
390 end
|
Zerotorescue@0
|
391
|
Zerotorescue@10
|
392 groupTimes.init = ceil( ( GetTime() - groupStartTime ) * 1000 );
|
Zerotorescue@10
|
393
|
Zerotorescue@10
|
394
|
Zerotorescue@10
|
395
|
Zerotorescue@10
|
396 -- Sort items
|
Zerotorescue@1
|
397 table.sort(itemsCache[groupName], function(a, b)
|
Zerotorescue@1
|
398 if sortMethod == "item" and a.rarity == b.rarity then
|
Zerotorescue@1
|
399 -- Do a name-compare for items of the same rarity
|
Zerotorescue@1
|
400 -- Otherwise epics first, then junk
|
Zerotorescue@0
|
401 if sortDirectory == "ASC" then
|
Zerotorescue@1
|
402 return a.name:upper() < b.name:upper();
|
Zerotorescue@0
|
403 else
|
Zerotorescue@1
|
404 return a.name:upper() > b.name:upper();
|
Zerotorescue@1
|
405 end
|
Zerotorescue@1
|
406 elseif sortMethod == "item" then
|
Zerotorescue@1
|
407 if sortDirectory == "ASC" then
|
Zerotorescue@1
|
408 return a.rarity > b.rarity; -- the comparers were reversed because we want epics first
|
Zerotorescue@1
|
409 else
|
Zerotorescue@1
|
410 return a.rarity < b.rarity; -- the comparers were reversed because we want epics first
|
Zerotorescue@0
|
411 end
|
Zerotorescue@0
|
412 elseif sortMethod == "current" then
|
Zerotorescue@0
|
413 if sortDirectory == "ASC" then
|
Zerotorescue@0
|
414 return a.count < b.count;
|
Zerotorescue@0
|
415 else
|
Zerotorescue@0
|
416 return a.count > b.count;
|
Zerotorescue@0
|
417 end
|
Zerotorescue@50
|
418 elseif sortMethod == "local" then
|
Zerotorescue@50
|
419 if sortDirectory == "ASC" then
|
Zerotorescue@50
|
420 return a.localCount < b.localCount;
|
Zerotorescue@50
|
421 else
|
Zerotorescue@50
|
422 return a.localCount > b.localCount;
|
Zerotorescue@50
|
423 end
|
Zerotorescue@0
|
424 elseif sortMethod == "percentage" then
|
Zerotorescue@0
|
425 if sortDirectory == "ASC" then
|
Zerotorescue@10
|
426 return ( a.count / minimumStock ) < ( b.count / minimumStock );
|
Zerotorescue@0
|
427 else
|
Zerotorescue@10
|
428 return ( a.count / minimumStock ) > ( b.count / minimumStock );
|
Zerotorescue@0
|
429 end
|
Zerotorescue@1
|
430 elseif sortMethod == "value" then
|
Zerotorescue@1
|
431 if sortDirectory == "ASC" then
|
Zerotorescue@1
|
432 return a.value < b.value;
|
Zerotorescue@1
|
433 else
|
Zerotorescue@1
|
434 return a.value > b.value;
|
Zerotorescue@1
|
435 end
|
Zerotorescue@0
|
436 end
|
Zerotorescue@0
|
437 end);
|
Zerotorescue@0
|
438
|
Zerotorescue@10
|
439 groupTimes.sorting = ceil( ( GetTime() - groupStartTime ) * 1000 );
|
Zerotorescue@10
|
440
|
Zerotorescue@10
|
441
|
Zerotorescue@10
|
442
|
Zerotorescue@10
|
443
|
Zerotorescue@10
|
444 -- Show itemslist
|
Zerotorescue@1
|
445 for i, item in pairs(itemsCache[groupName]) do
|
Zerotorescue@31
|
446 -- Go through all items for this group
|
Zerotorescue@31
|
447
|
Zerotorescue@50
|
448 if (( item.count / minimumStock ) < showWhenBelow or ( item.localCount / minimumStock ) < showWhenBelow) and (not hideWhenBelowPriceThreshold or priceThreshold == 0 or item.value < 0 or item.value >= priceThreshold) then
|
Zerotorescue@31
|
449 -- if the option "hide when below threshold" is disabled or no price threshold is set or the value is above the price threshold or the value could not be determined, proceed
|
Zerotorescue@50
|
450
|
Zerotorescue@0
|
451 local btnItemLink = AceGUI:Create("ItemLinkButton");
|
Zerotorescue@23
|
452 btnItemLink:SetUserData("exec", function(_, itemId, _, buttonName)
|
Zerotorescue@23
|
453 local itemName, itemLink = GetItemInfo(itemId);
|
Zerotorescue@23
|
454
|
Zerotorescue@23
|
455 if buttonName == "LeftButton" and IsShiftKeyDown() and itemLink then
|
Zerotorescue@23
|
456 ChatEdit_InsertLink(itemLink);
|
Zerotorescue@23
|
457 elseif buttonName == "LeftButton" and IsAltKeyDown() and itemName and AuctionFrame and AuctionFrame:IsShown() and AuctionFrameBrowse then
|
Zerotorescue@23
|
458 -- Start search at page 0
|
Zerotorescue@23
|
459 AuctionFrameBrowse.page = 0;
|
Zerotorescue@23
|
460
|
Zerotorescue@23
|
461 -- Set the search field (even though we could use "ChatEdit_InsertLink", this ensures the right field is updated)
|
Zerotorescue@23
|
462 BrowseName:SetText(itemName)
|
Zerotorescue@23
|
463
|
Zerotorescue@23
|
464 QueryAuctionItems(itemName, nil, nil, 0, 0, 0, 0, 0, 0);
|
Zerotorescue@23
|
465 end
|
Zerotorescue@23
|
466 end);
|
Zerotorescue@50
|
467 btnItemLink:SetRelativeWidth(.6);
|
Zerotorescue@1
|
468 btnItemLink:SetItemId(item.id);
|
Zerotorescue@0
|
469
|
Zerotorescue@0
|
470 iGroup:AddChild(btnItemLink);
|
Zerotorescue@0
|
471
|
Zerotorescue@50
|
472 -- Local quantity
|
Zerotorescue@50
|
473 local lblLocal = AceGUI:Create("Label");
|
Zerotorescue@50
|
474 lblLocal:SetText(self:DisplayItemCount(item.localCount, minimumStock));
|
Zerotorescue@50
|
475 lblLocal:SetRelativeWidth(.099);
|
Zerotorescue@50
|
476
|
Zerotorescue@50
|
477 iGroup:AddChild(lblLocal);
|
Zerotorescue@50
|
478
|
Zerotorescue@0
|
479 -- Current quantity
|
Zerotorescue@0
|
480 local lblQuantity = AceGUI:Create("Label");
|
Zerotorescue@9
|
481 lblQuantity:SetText(self:DisplayItemCount(item.count, minimumStock));
|
Zerotorescue@10
|
482 lblQuantity:SetRelativeWidth(.099);
|
Zerotorescue@0
|
483
|
Zerotorescue@0
|
484 iGroup:AddChild(lblQuantity);
|
Zerotorescue@0
|
485
|
Zerotorescue@0
|
486 -- Required stock
|
Zerotorescue@9
|
487 local lblMinimumStock = AceGUI:Create("Label");
|
Zerotorescue@9
|
488 lblMinimumStock:SetText(minimumStock);
|
Zerotorescue@10
|
489 lblMinimumStock:SetRelativeWidth(.099);
|
Zerotorescue@0
|
490
|
Zerotorescue@9
|
491 iGroup:AddChild(lblMinimumStock);
|
Zerotorescue@1
|
492
|
Zerotorescue@1
|
493 -- Value
|
Zerotorescue@1
|
494 local lblValue = AceGUI:Create("Label");
|
Zerotorescue@1
|
495 lblValue:SetText(self:DisplayMoney(item.value, priceThreshold));
|
Zerotorescue@10
|
496 lblValue:SetRelativeWidth(.099);
|
Zerotorescue@1
|
497
|
Zerotorescue@1
|
498 iGroup:AddChild(lblValue);
|
Zerotorescue@1
|
499
|
Zerotorescue@1
|
500 -- Remember references to the value and current fields so we can fill them later
|
Zerotorescue@1
|
501 if item.set then
|
Zerotorescue@10
|
502 -- -3 means the price is unknown, queue look up
|
Zerotorescue@10
|
503 if item.value == -3 then
|
Zerotorescue@10
|
504 item.set.value = lblValue;
|
Zerotorescue@10
|
505 end
|
Zerotorescue@10
|
506 if item.count == -3 then
|
Zerotorescue@10
|
507 item.set.current = lblQuantity;
|
Zerotorescue@10
|
508 end
|
Zerotorescue@50
|
509 if item.localCount == -3 then
|
Zerotorescue@50
|
510 item.set.localCount = lblLocal;
|
Zerotorescue@50
|
511 end
|
Zerotorescue@10
|
512
|
Zerotorescue@10
|
513 -- Don't queue if we already know everything we want to know
|
Zerotorescue@50
|
514 if item.value ~= -3 and item.count ~= -3 and item.localCount ~= -3 then
|
Zerotorescue@10
|
515 item.set = nil;
|
Zerotorescue@10
|
516 end
|
Zerotorescue@1
|
517 end
|
Zerotorescue@0
|
518 end
|
Zerotorescue@0
|
519 end
|
Zerotorescue@0
|
520
|
Zerotorescue@10
|
521 groupTimes.preparing = ceil( ( GetTime() - groupStartTime ) * 1000 );
|
Zerotorescue@10
|
522
|
Zerotorescue@1
|
523 iGroup:ResumeLayout();
|
Zerotorescue@1
|
524 mod.scrollFrame:AddChild(iGroup); -- this can take up to .5 seconds, might need to look into again at a later time
|
Zerotorescue@10
|
525
|
Zerotorescue@10
|
526 groupTimes.building = ceil( ( GetTime() - groupStartTime ) * 1000 );
|
Zerotorescue@0
|
527 end
|
Zerotorescue@10
|
528
|
Zerotorescue@13
|
529 if groupStartTime and groupTimes then
|
Zerotorescue@13
|
530 addon:Debug(("Building of %s took %d ms (%d / %d / %d / %d / %d)."):format(groupName, ceil( ( GetTime() - groupStartTime ) * 1000 ), groupTimes.init or 0, groupTimes.sorting or 0, groupTimes.preparing or 0, groupTimes.building or 0, ceil( ( GetTime() - buildStartTime ) * 1000 )));
|
Zerotorescue@13
|
531 end
|
Zerotorescue@0
|
532 end
|
Zerotorescue@1
|
533
|
Zerotorescue@1
|
534 mod.scrollFrame:ResumeLayout();
|
Zerotorescue@1
|
535 mod.scrollFrame:DoLayout();
|
Zerotorescue@1
|
536
|
Zerotorescue@10
|
537 addon:Debug(("Done building summary after %d ms."):format(ceil( ( GetTime() - buildStartTime ) * 1000 )));
|
Zerotorescue@10
|
538
|
Zerotorescue@1
|
539 if CACHE_ITEMS_TOTAL > 0 then
|
Zerotorescue@1
|
540 cacheStart = GetTime();
|
Zerotorescue@7
|
541 self:CancelTimer(self.tmrUpdater, true);
|
Zerotorescue@1
|
542 self.tmrUpdater = self:ScheduleRepeatingTimer("UpdateNextItem", .01); -- Once every 100 frames (or once every x frames if you have less than 100 FPS, basically, once every frame)
|
Zerotorescue@1
|
543 end
|
Zerotorescue@1
|
544 end
|
Zerotorescue@1
|
545
|
Zerotorescue@1
|
546 function mod:UpdateNextItem()
|
Zerotorescue@1
|
547 local i = 0;
|
Zerotorescue@1
|
548
|
Zerotorescue@1
|
549 for groupName, items in pairs(itemsCache) do
|
Zerotorescue@1
|
550 local minimumStock = addon:GetOptionByKey(groupName, "minimumStock");
|
Zerotorescue@1
|
551 local priceThreshold = addon:GetOptionByKey(groupName, "priceThreshold");
|
Zerotorescue@1
|
552
|
Zerotorescue@1
|
553 for _, item in pairs(items) do
|
Zerotorescue@1
|
554 if item.set then
|
Zerotorescue@10
|
555 if item.count == -3 then
|
Zerotorescue@10
|
556 -- Only if item count was queued, update it
|
Zerotorescue@23
|
557 item.count = addon:GetItemCount(item.id, groupName);
|
Zerotorescue@10
|
558 if item.set.current and item.set.current.SetText then
|
Zerotorescue@10
|
559 item.set.current:SetText(self:DisplayItemCount(item.count, minimumStock));
|
Zerotorescue@10
|
560 end
|
Zerotorescue@1
|
561 end
|
Zerotorescue@1
|
562
|
Zerotorescue@50
|
563 if item.localCount == -3 then
|
Zerotorescue@50
|
564 -- Only if item count was queued, update it
|
Zerotorescue@54
|
565 item.localCount = addon:GetLocalItemCount(item.id, groupName);
|
Zerotorescue@50
|
566 if item.set.localCount and item.set.localCount.SetText then
|
Zerotorescue@50
|
567 item.set.localCount:SetText(self:DisplayItemCount(item.localCount, minimumStock));
|
Zerotorescue@50
|
568 end
|
Zerotorescue@50
|
569 end
|
Zerotorescue@50
|
570
|
Zerotorescue@10
|
571 if item.value == -3 then
|
Zerotorescue@10
|
572 -- Only if item value was queued, update it
|
Zerotorescue@23
|
573
|
Zerotorescue@23
|
574 -- The itemlink might not have been loaded yet in which case we retry
|
Zerotorescue@23
|
575 item.link = item.link or select(2, GetItemInfo(item.id));
|
Zerotorescue@23
|
576
|
Zerotorescue@23
|
577 if item.link then
|
Zerotorescue@23
|
578 item.value = addon:GetAuctionValue(item.link, groupName);
|
Zerotorescue@23
|
579 if item.set.value and item.set.value.SetText then
|
Zerotorescue@23
|
580 item.set.value:SetText(self:DisplayMoney(item.value, priceThreshold));
|
Zerotorescue@23
|
581 end
|
Zerotorescue@10
|
582 end
|
Zerotorescue@1
|
583 end
|
Zerotorescue@1
|
584
|
Zerotorescue@1
|
585 item.set = nil;
|
Zerotorescue@1
|
586
|
Zerotorescue@1
|
587 i = i + 1;
|
Zerotorescue@1
|
588 CACHE_ITEMS_CURRENT = CACHE_ITEMS_CURRENT + 1;
|
Zerotorescue@1
|
589
|
Zerotorescue@9
|
590 if mod.frame then
|
Zerotorescue@9
|
591 mod.frame:SetStatusText(("Caching auction values and item-counts... %d%% has already been processed."):format(floor(CACHE_ITEMS_CURRENT / CACHE_ITEMS_TOTAL * 100)));
|
Zerotorescue@9
|
592 end
|
Zerotorescue@1
|
593
|
Zerotorescue@13
|
594 if i >= addon.db.global.defaults.summary.speed then
|
Zerotorescue@1
|
595 return;
|
Zerotorescue@1
|
596 end
|
Zerotorescue@1
|
597 end
|
Zerotorescue@1
|
598 end
|
Zerotorescue@1
|
599 end
|
Zerotorescue@1
|
600
|
Zerotorescue@1
|
601 -- Reset trackers
|
Zerotorescue@1
|
602 CACHE_ITEMS_TOTAL = 0;
|
Zerotorescue@1
|
603 CACHE_ITEMS_CURRENT = 0;
|
Zerotorescue@1
|
604
|
Zerotorescue@1
|
605 -- Stop timer
|
Zerotorescue@1
|
606 self:CancelTimer(self.tmrUpdater, true);
|
Zerotorescue@10
|
607
|
Zerotorescue@10
|
608 -- Rebuild list so hidden items due to too low prices get added
|
Zerotorescue@10
|
609 self:Build();
|
Zerotorescue@1
|
610
|
Zerotorescue@1
|
611 -- Announce
|
Zerotorescue@10
|
612 mod.frame:SetStatusText("All required prices and itemcounts have been cached. This process took " .. ceil(GetTime() - cacheStart) .. " seconds.");
|
Zerotorescue@1
|
613
|
Zerotorescue@1
|
614 -- Forget time
|
Zerotorescue@1
|
615 cacheStart = nil;
|
Zerotorescue@0
|
616 end
|
Zerotorescue@0
|
617
|
Zerotorescue@0
|
618 function mod:ColorCode(num, required)
|
Zerotorescue@0
|
619 local percentage = ( num / required );
|
Zerotorescue@0
|
620
|
Zerotorescue@0
|
621 if percentage >= addon.db.global.defaults.colors.green then
|
Zerotorescue@0
|
622 return ("|cff00ff00%d|r"):format(num);
|
Zerotorescue@0
|
623 elseif percentage >= addon.db.global.defaults.colors.yellow then
|
Zerotorescue@0
|
624 return ("|cffffff00%d|r"):format(num);
|
Zerotorescue@0
|
625 elseif percentage >= addon.db.global.defaults.colors.orange then
|
Zerotorescue@0
|
626 return ("|cffff9933%d|r"):format(num);
|
Zerotorescue@0
|
627 elseif percentage >= addon.db.global.defaults.colors.red then
|
Zerotorescue@0
|
628 return ("|cffff0000%d|r"):format(num);
|
Zerotorescue@0
|
629 end
|
Zerotorescue@0
|
630 end
|
Zerotorescue@0
|
631
|
Zerotorescue@1
|
632 function mod:DisplayMoney(value, priceThreshold)
|
Zerotorescue@7
|
633 if value == -1 then
|
Zerotorescue@7
|
634 return "|cff0000ffNone up|r";
|
Zerotorescue@7
|
635 elseif value == -2 then
|
Zerotorescue@7
|
636 return "|cff0000ffNo AH mod|r";
|
Zerotorescue@9
|
637 elseif value == -3 then
|
Zerotorescue@9
|
638 return "|cffffff00Unknown|r";
|
Zerotorescue@13
|
639 elseif value == -4 then
|
Zerotorescue@13
|
640 return "|cff00ff00-|r";
|
Zerotorescue@23
|
641 elseif value == -5 then
|
Zerotorescue@23
|
642 return "|cffff9933Error|r";
|
Zerotorescue@23
|
643 elseif priceThreshold and value < priceThreshold then
|
Zerotorescue@17
|
644 return ("|cffaaaaaa%s|r"):format(addon:ReadableMoney(value or 0, true):gsub("|([a-fA-F0-9]+)([gsc]+)|r", "%2"));
|
Zerotorescue@1
|
645 else
|
Zerotorescue@1
|
646 return addon:ReadableMoney(value or 0, true);
|
Zerotorescue@1
|
647 end
|
Zerotorescue@1
|
648 end
|
Zerotorescue@1
|
649
|
Zerotorescue@9
|
650 function mod:DisplayItemCount(value, minimumStock)
|
Zerotorescue@13
|
651 if value == -1 then
|
Zerotorescue@13
|
652 return "|cffffff00Unknown|r";
|
Zerotorescue@13
|
653 elseif value == -3 then
|
Zerotorescue@9
|
654 return "|cffffff00Unknown|r";
|
Zerotorescue@9
|
655 else
|
Zerotorescue@9
|
656 return self:ColorCode(value, minimumStock);
|
Zerotorescue@9
|
657 end
|
Zerotorescue@9
|
658 end
|
Zerotorescue@9
|
659
|
Zerotorescue@0
|
660 function mod:NumberFormat(num)
|
Zerotorescue@0
|
661 local formatted = string.gsub(num, "(%d)(%d%d%d)$", "%1,%2", 1);
|
Zerotorescue@0
|
662
|
Zerotorescue@0
|
663 while true do
|
Zerotorescue@0
|
664 formatted, matches = string.gsub(formatted, "(%d)(%d%d%d),", "%1,%2,", 1);
|
Zerotorescue@0
|
665
|
Zerotorescue@0
|
666 if matches == 0 then
|
Zerotorescue@0
|
667 break;
|
Zerotorescue@0
|
668 end
|
Zerotorescue@0
|
669 end
|
Zerotorescue@0
|
670
|
Zerotorescue@0
|
671 return formatted;
|
Zerotorescue@1
|
672 end
|