· 6 years ago · Mar 12, 2019, 09:28 PM
1; Path of Exile ItemInfo
2;
3; Script is currently maintained by various people and kept up to date by aRTy42 / IGN: Erinyen
4; Forum thread: https://www.pathofexile.com/forum/view-thread/1678678
5; GitHub: https://github.com/aRTy42/POE-ItemInfo
6
7#SingleInstance force
8#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
9#Persistent ; Stay open in background
10SetWorkingDir, %A_ScriptDir%
11SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
12
13;Define window criteria for the regular and steam version, for later use at the very end of the script. This needs to be done early, in the "auto-execute section".
14GroupAdd, PoEWindowGrp, Path of Exile ahk_class POEWindowClass ahk_exe PathOfExile.exe
15GroupAdd, PoEWindowGrp, Path of Exile ahk_class POEWindowClass ahk_exe PathOfExileSteam.exe
16GroupAdd, PoEWindowGrp, Path of Exile ahk_class POEWindowClass ahk_exe PathOfExile_x64.exe
17GroupAdd, PoEWindowGrp, Path of Exile ahk_class POEWindowClass ahk_exe PathOfExile_x64Steam.exe
18
19#Include, %A_ScriptDir%\resources\Version.txt
20#Include, %A_ScriptDir%\lib\JSON.ahk
21#Include, %A_ScriptDir%\lib\EasyIni.ahk
22#Include, %A_ScriptDir%\lib\DebugPrintArray.ahk
23#Include, %A_ScriptDir%\lib\ConvertKeyToKeyCode.ahk
24#Include, %A_ScriptDir%\lib\Class_GdipTooltip.ahk
25#Include, %A_ScriptDir%\lib\Class_ColorPicker.ahk
26#Include, %A_ScriptDir%\lib\Class_SplashUI.ahk
27#Include, %A_ScriptDir%\lib\AdvancedHotkey.ahk
28IfNotExist, %A_ScriptDir%\temp
29FileCreateDir, %A_ScriptDir%\temp
30
31; Instead of polluting the default namespace with Globals, create our own Globals "namespace".
32class Globals {
33
34 Set(name, value) {
35 Globals[name] := value
36 }
37
38 Get(name, value_default="") {
39 result := Globals[name]
40 If (result == "") {
41 result := value_default
42 }
43 return result
44 }
45}
46
47Globals.Set("AHKVersionRequired", AHKVersionRequired)
48Globals.Set("ReleaseVersion", ReleaseVersion)
49Globals.Set("DataDir", A_ScriptDir . "\data")
50Globals.Set("SettingsUIWidth", 963)
51Globals.Set("SettingsUIHeight", 665)
52Globals.Set("AboutWindowHeight", 340)
53Globals.Set("AboutWindowWidth", 435)
54Globals.Set("SettingsUITitle", "PoE ItemInfo Settings")
55Globals.Set("GithubRepo", "POE-ItemInfo")
56Globals.Set("GithubUser", "aRTy42")
57Globals.Set("ScriptList", [A_ScriptDir "\POE-ItemInfo"])
58Globals.Set("UpdateNoteFileList", [[A_ScriptDir "\resources\updates.txt","ItemInfo"]])
59Globals.Set("SettingsScriptList", ["ItemInfo", "Additional Macros", "Lutbot"])
60Globals.Set("ScanCodes", GetScanCodes())
61Globals.Set("AssignedHotkeys", GetObjPropertyCount(Globals.Get("AssignedHotkeys")) ? Globals.Get("AssignedHotkeys") : {}) ; initializes the object only if it hasn't any properties already
62argumentProjectName = %1%
63argumentUserDirectory = %2%
64argumentIsDevVersion = %3%
65argumentOverwrittenFiles = %4%
66Globals.Set("ProjectName", argumentProjectName)
67; make sure not to overwrite these variables if set from another script
68global userDirectory := userDirectory ? userDirectory : argumentUserDirectory
69global isDevVersion := isDevVersion ? isDevVersion : argumentIsDevVersion
70global overwrittenUserFiles := overwrittenUserFiles ? overwrittenUserFiles : argumentOverwrittenFiles
71
72global SuspendPOEItemScript = 0
73
74/*
75 Import item bases
76*/
77ItemBaseList := {}
78FileRead, JSONFile, %A_ScriptDir%\data\item_bases.json
79parsedJSON := JSON.Load(JSONFile)
80ItemBaseList.general := parsedJSON.item_bases
81
82FileRead, JSONFile, %A_ScriptDir%\data\item_bases_weapon.json
83parsedJSON := JSON.Load(JSONFile)
84ItemBaseList.weapons := parsedJSON.item_bases_weapon
85
86FileRead, JSONFile, %A_ScriptDir%\data\item_bases_armour.json
87parsedJSON := JSON.Load(JSONFile)
88ItemBaseList.armours := parsedJSON.item_bases_armour
89
90Globals.Set("ItemBaseList", ItemBaseList)
91Globals.Set("ItemFilterObj", [])
92Globals.Set("CurrentItemFilter", "")
93/*
94*/
95
96class UserOptions {
97 ScanUI()
98 {
99 For key, val in this {
100 _get := GuiGet(key, "", Error)
101 this[key] := not Error ? _get : this[key]
102 }
103 }
104}
105
106class ItemInfoOptions extends UserOptions {
107 ; Hotkey to invoke ItemInfo. Default: Ctrl+C.
108 ParseItemHotKey := "^c"
109
110 ; When checked (1) the script only activates while you are ingame (technically while the game window is the frontmost). This is handy for have the script parse
111 ; textual item representations appearing somewhere Else, like in the forums or text files.
112 OnlyActiveIfPOEIsFront := 1
113
114 ; Put result text on clipboard (overwriting the textual representation the game put there to begin with)
115 PutResultsOnClipboard := 0
116
117 ShowUpdateNotifications := 1
118 UpdateSkipSelection := 0
119 UpdateSkipBackup := 0
120
121 ; Enable/disable the entire AdditionalMacros. The individual settings are in the AdditionalMacros.ini
122 ;EnableAdditionalMacros := 1
123 ; Enable/disable the entire MapModWarnings functionality. The individual settings are in the MapModWarnings.ini
124 EnableMapModWarnings := 1
125
126 ; Include a header above the affix overview: TierRange ilvl Total ilvl Tier
127 ShowHeaderForAffixOverview := 1
128
129 ; Explain abbreviations and special notation symbols at the end of the tooltip when they are used
130 ShowExplanationForUsedNotation := 1
131
132 ; If the mirrored affix text is longer than the field length the affix line will be cut off and
133 ; this text will be appended at the end to indicate that the line was truncated.
134 ; Usually this is set to the ASCII or Unicode value of the three dot ellipsis (alt code: 0133).
135 ; Note that the correct display of text characters outside the ASCII standard depend on the file encoding and
136 ; the AHK version used. For best results, save this file as ANSI encoding which can be read and
137 ; displayed correctly by either ANSI based AutoHotkey or Unicode based AutoHotkey.
138 ; Example: Assume the affix line to be mirrored is '50% increased Spell Damage'.
139 ; The field width is hardcoded (assume 20), this text would be shown as '50% increased Spell…'
140 AffixTextEllipsis := "…"
141
142 ; Separator for affix overview columns. This is put in at three places. Example with \\ instead of spaces:
143 ; 50% increased Spell…\\50-59 (46)\\75-79 (84)\\T4 P
144 ; Default: 2 spaces
145 AffixColumnSeparator := " "
146
147 ; Select separator for double ranges from 'added damage' mods: a-b to c-d is displayed as a-b|c-d
148 DoubleRangeSeparator := "|"
149
150 ; Shorten double ranges: a-b to c-d becomes a-d
151 UseCompactDoubleRanges := 0
152
153 ; Only use compact double ranges for the second range column in the affix overview (with the header 'total')
154 OnlyCompactForTotalColumn := 0
155
156 ; Separator for a multi tier roll range with uncertainty, such as:
157 ; 83% increased Light… 73-85…83-95 102-109 (84) T1-4 P + T1-6 S
158 ; There--^
159 MultiTierRangeSeparator := "…"
160
161 ; Font size for the tooltip.
162 FontSize := 9
163
164 ; Hide tooltip when the mouse cursor moved x pixels away from the initial position.
165 ; Effectively permanent tooltip when using a value larger than the monitor diameter.
166 MouseMoveThreshold := 40
167
168 ; Set this to 1 if you want to have the tooltip disappear after the time frame set below.
169 ; Otherwise you will have to move the mouse by x pixels for the tip to disappear.
170 UseTooltipTimeout := 0
171
172 ;How many seconds to wait before removing tooltip.
173 ToolTipTimeoutSeconds := 15
174
175 ; Displays the tooltip in virtual screen space at fixed coordinates.
176 ; Virtual screen space means the complete desktop frame, including any secondary monitors.
177 DisplayToolTipAtFixedCoords := 0
178
179 ; Coordinates relative to top left corner, increasing by going down and to the right.
180 ; Only used if DisplayToolTipAtFixedCoords is 1.
181 ScreenOffsetX := 0
182 ScreenOffsetY := 0
183
184 ; Set this to 1 to enable GDI+ rendering
185 UseGDI := 0
186
187 ; Format: RRGGBB
188 GDIWindowColor := "000000"
189 GDIBorderColor := "654025"
190 GDITextColor := "FEFEFE"
191 GDIWindowOpacity := 90
192 GDIBorderOpacity := 90
193 GDITextOpacity := 100
194 GDIRenderingFix := 1
195 GDIConditionalColors := 0
196}
197Opts := new ItemInfoOptions()
198
199class Fonts {
200 __New(FontSizeFixed, FontSizeUI = 9)
201 {
202 this.FontSizeFixed := FontSizeFixed
203 this.FontSizeUI := FontSizeUI
204 this.FixedFont := this.CreateFixedFont(this.FontSizeFixed)
205 this.UIFont := this.CreateUIFont(this.FontSizeUI)
206 ;debugprintarray(this)
207 }
208
209 CreateFixedFont(FontSize_, Options = "")
210 {
211 ; Q5 = Windows XP and later: If set, text is rendered (when possible) using ClearType antialiasing method.
212 Options .= " q5 "
213 If (!(FontSize_ == ""))
214 {
215 Options .= "s" FontSize_
216 }
217 Gui Font, %Options%, Courier New
218 Gui Font, %Options%, Consolas
219 Gui Add, Text, HwndHidden h0 w0 x0 y0,
220 SendMessage, 0x31,,,, ahk_id %Hidden%
221 return ErrorLevel
222 }
223
224 CreateUIFont(FontSize_, Options = "")
225 {
226 ; Q5 = Windows XP and later: If set, text is rendered (when possible) using ClearType antialiasing method.
227 Options .= " q5 "
228 If (!(FontSize_ == ""))
229 {
230 Options .= "s" FontSize_
231 }
232 Gui, SettingsUI:Font, %Options%, Arial
233 Gui, SettingsUI:Font, %Options%, Tahoma
234 Gui, SettingsUI: Font, %Options%, Segoe UI
235 Gui, SettingsUI: Font, %Options%, Verdana
236 Gui, SettingsUI: Add, Text, HwndHidden h0 w0 x0 y0,
237 SendMessage, 0x31,,,, ahk_id %Hidden%
238 return ErrorLevel
239 }
240
241 Set(NewFont)
242 {
243 AhkExe := GetAhkExeFilename()
244 SendMessage, 0x30, NewFont, 1,, ahk_class tooltips_class32 ahk_exe %AhkExe%
245 ; Development versions of AHK
246 SendMessage, 0x30, NewFont, 1,, ahk_class tooltips_class32 ahk_exe AutoHotkeyA32.exe
247 SendMessage, 0x30, NewFont, 1,, ahk_class tooltips_class32 ahk_exe AutoHotkeyU32.exe
248 SendMessage, 0x30, NewFont, 1,, ahk_class tooltips_class32 ahk_exe AutoHotkeyU64.exe
249 }
250
251 SetFixedFont(FontSize_=-1, Options = "")
252 {
253 If (FontSize_ != -1) {
254 FontSize_ := this.FontSizeFixed
255 } Else {
256 this.FontSizeFixed := FontSize_
257 }
258 FixedFont := this.CreateFixedFont(FontSize_, Options)
259
260 If (FixedFont) {
261 this.Set(this.FixedFont)
262 this.FontSizeFixed := FixedFont
263 }
264 }
265
266 SetUIFont(FontSize_=-1, Options = "")
267 {
268 If (FontSize_ == -1) {
269 FontSize_ := this.FontSizeUI
270 } Else {
271 this.FontSizeUI := FontSize_
272 }
273 UIFont := this.CreateUIFont(FontSize_, Options)
274
275 If (UIFont and (this.UIFont != UIFont)) {
276 this.Set(this.UIFont)
277 this.UIFont := UIFont
278 }
279 }
280
281 GetFixedFont()
282 {
283 return this.FixedFont
284 }
285
286 GetUIFont()
287 {
288 return this.UIFont
289 }
290}
291
292class ItemData_ {
293 Init()
294 {
295 This.Links := ""
296 This.Sockets := ""
297 This.Stats := ""
298 This.NamePlate := ""
299 This.Affixes := ""
300 This.AffixTextLines := []
301 This.UncertainAffixes := {}
302 This.UncAffTmpAffixLines := []
303 This.LastAffixLineNumber := 0
304 This.HasMultipleCrafted := 0
305 This.SpecialCaseNotation := ""
306 This.FullText := ""
307 This.IndexAffixes := -1
308 This.IndexLast := -1
309 This.PartsLast := ""
310 This.Rarity := ""
311 This.Parts := []
312 }
313}
314Global ItemData := new ItemData_
315ItemData.Init()
316
317class Item_ {
318 ; Initialize all the Item object attributes to default values
319 Init()
320 {
321 This.Name := ""
322 This.BaseName := ""
323 This.Quality := ""
324 This.BaseLevel := ""
325 This.RarityLevel := ""
326 This.BaseType := ""
327 This.GripType := ""
328 This.Level := ""
329 This.Experience := ""
330 This.ExperienceFlat := ""
331 This.MapLevel := ""
332 This.MapTier := ""
333 This.MaxSockets := ""
334 This.Sockets := ""
335 This.AbyssalSockets := ""
336 This.SocketGroups := []
337 This.SocketString := ""
338 This.Links := ""
339 This.SubType := ""
340 This.DifficultyRestriction := ""
341 This.Implicit := []
342 This.Enchantment := []
343 This.Charges := []
344 This.AreaMonsterLevelReq := []
345 This.BeastData := {}
346 This.GemColor := ""
347 This.veiledPrefixCount := ""
348 This.veiledSuffixCount := ""
349
350 This.HasImplicit := False
351 This.HasEnchantment := False
352 This.HasEffect := False
353 This.IsWeapon := False
354 This.IsArmour := False
355 This.IsHybridBase := False
356 This.IsQuiver := False
357 This.IsFlask := False
358 This.IsGem := False
359 This.IsCurrency := False
360 This.IsUnidentified := False
361 This.IsBelt := False
362 This.IsRing := False
363 This.IsUnsetRing := False
364 This.IsBow := False
365 This.IsAmulet := False
366 This.IsSingleSocket := False
367 This.IsFourSocket := False
368 This.IsThreeSocket := False
369 This.IsMap := False
370 This.IsTalisman := False
371 This.IsJewel := False
372 This.IsLeaguestone := False
373 This.IsScarab := False
374 This.IsDivinationCard := False
375 This.IsProphecy := False
376 This.IsUnique := False
377 This.IsRare := False
378 This.IsCorrupted := False
379 This.IsMirrored := False
380 This.IsMapFragment := False
381 This.IsEssence := False
382 This.IsRelic := False
383 This.IsElderBase := False
384 This.IsShaperBase := False
385 This.IsSynthesisedBase:= False
386 This.IsFracturedBase:= False
387 This.IsAbyssJewel := False
388 This.IsBeast := False
389 This.IsHideoutObject:= False
390 This.IsFossil := False
391 }
392}
393Global Item := new Item_
394Item.Init()
395
396class AffixTotals_ {
397
398 NumPrefixes := 0
399 NumSuffixes := 0
400 NumPrefixesMax := 0
401 NumSuffixesMax := 0
402 NumTotal := 0
403 NumTotalMax := 0
404
405 Reset()
406 {
407 this.NumPrefixes := 0
408 this.NumSuffixes := 0
409 this.NumPrefixesMax := 0
410 this.NumSuffixesMax := 0
411 this.NumTotal := 0
412 this.NumTotalMax := 0
413 }
414
415 FormatAll()
416 {
417 this.NumPrefixes := NumFormatPointFiveOrInt(this.NumPrefixes)
418 this.NumSuffixes := NumFormatPointFiveOrInt(this.NumSuffixes)
419 this.NumPrefixesMax := NumFormatPointFiveOrInt(this.NumPrefixesMax)
420 this.NumSuffixesMax := NumFormatPointFiveOrInt(this.NumSuffixesMax)
421 this.NumTotal := NumFormatPointFiveOrInt(this.NumTotal)
422 this.NumTotalMax := NumFormatPointFiveOrInt(this.NumTotalMax)
423 }
424}
425AffixTotals := new AffixTotals_()
426
427class AffixLines_ {
428
429 __New()
430 {
431 this.Length := 0
432 }
433
434 ; Sets fields to empty string
435 Clear(Index)
436 {
437 this[Index] := ""
438 }
439
440 ClearAll()
441 {
442 Loop, % this.MaxIndex()
443 {
444 this.Clear(A_Index)
445 }
446 }
447
448 ; Actually removes fields
449 Reset()
450 {
451 Loop, % this.MaxIndex()
452 {
453 this.Remove(this.MaxIndex())
454 }
455 this.Length := 0
456 }
457
458 Set(Index, Contents)
459 {
460 this[Index] := Contents
461 this.Length := this.MaxIndex()
462 }
463}
464AffixLines := new AffixLines_()
465
466IfNotExist, %userDirectory%\config.ini
467{
468 CopyDefaultConfig()
469}
470
471; Windows system tray icon
472; possible values: poe.ico, poe-bw.ico, poe-web.ico, info.ico
473; set before creating the settings UI so it gets used for the settings dialog as well
474Menu, Tray, Icon, %A_ScriptDir%\resources\images\poe-bw.ico
475
476ReadConfig()
477Sleep, 100
478
479; Use some variables to skip the update check or enable/disable update check feedback.
480; The first call on script start shouldn't have any feedback and including ItemInfo in other scripts should call the update once from that other script.
481; Under no circumstance set the variable "SkipItemInfoUpdateCall" in this script.
482; This code block should only be called when ItemInfo runs by itself, not when it's included in other scripts like PoE-TradeMacro.
483; "SkipItemInfoUpdateCall" should be set outside by other scripts.
484global firstUpdateCheck := true
485If (!SkipItemInfoUpdateCall) {
486 global SplashUI := new SplashUI("on", "PoE-ItemInfo", "Initializing PoE-ItemInfo...", "", ReleaseVersion, A_ScriptDir "\resources\images\greydot.png")
487 GoSub, CheckForUpdates
488}
489firstUpdateCheck := false
490
491CreateSettingsUI()
492If (StrLen(overwrittenUserFiles)) {
493 ShowChangedUserFiles()
494}
495GoSub, AM_AssignHotkeys
496SplashUI.SetSubMessage("Fetching currency data from poe.ninja for all leagues...")
497GoSub, FetchCurrencyData
498GoSub, InitGDITooltip
499
500
501/*
502 Item data translation, won't be used for now.
503 Todo: remove test/debug code
504*/
505If (false) {
506 global currentLocale := ""
507 _Debug := true
508 SplashUI.SetSubMessage("Downloading language files...")
509 global translationData := PoEScripts_DownloadLanguageFiles(currentLocale, false, "PoE-ItemInfo", "Updating and parsing language files...", _Debug)
510}
511
512Menu, TextFiles, Add, Additional Macros Settings, EditAdditionalMacrosSettings
513Menu, TextFiles, Add, Map Mod Warnings, EditMapModWarningsConfig
514Menu, TextFiles, Add, Custom Macros Example, EditCustomMacrosExample
515Menu, PreviewTextFiles, Add, Additional Macros, PreviewAdditionalMacros
516
517; Menu tooltip
518RelVer := Globals.Get("ReleaseVersion")
519Menu, Tray, Tip, Path of Exile Item Info %RelVer%
520
521Menu, Tray, NoStandard
522Menu, Tray, Add, Reload Script (Use only this), ReloadScript
523Menu, Tray, Add ; Separator
524Menu, Tray, Add, About..., MenuTray_About
525/*
526 ;Item data Translation, won't be used for now.
527 Menu, Tray, Add, Translate Item, ShowTranslationUI
528*/
529Menu, Tray, Add, Show all assigned Hotkeys, ShowAssignedHotkeys
530Menu, Tray, Add, % Globals.Get("SettingsUITitle", "PoE ItemInfo Settings"), ShowSettingsUI
531Menu, Tray, Add, Check for updates, CheckForUpdates
532Menu, Tray, Add, Show Update Notes, ShowUpdateNotes
533Menu, Tray, Add ; Separator
534Menu, Tray, Add, Edit Files, :TextFiles
535Menu, Tray, Add, Preview Files, :PreviewTextFiles
536Menu, Tray, Add, Open User Folder, EditOpenUserSettings
537Menu, Tray, Add ; Separator
538Menu, Tray, Standard
539Menu, Tray, Default, % Globals.Get("SettingsUITitle", "PoE ItemInfo Settings")
540
541
542#Include %A_ScriptDir%\data\MapList.txt
543#Include %A_ScriptDir%\data\DivinationCardList.txt
544#Include %A_ScriptDir%\data\GemQualityList.txt
545
546Fonts := new Fonts(Opts.FontSize, 9)
547
548If (Opts.Lutbot_CheckScript) {
549 SetTimer, StartLutbot, 2000
550}
551
552SplashUI.DestroyUI() ; init finished
553
554; ----------------------------------------------------------- Functions and Labels ----------------------------------------------------------------
555
556GetAhkExeFilename(Default_="AutoHotkey.exe")
557{
558 AhkExeFilename := Default_
559 If (A_AhkPath)
560 {
561 StringSplit, AhkPathParts, A_AhkPath, \
562 Loop, % AhkPathParts0
563 {
564 IfInString, AhkPathParts%A_Index%, .exe
565 {
566 AhkExeFilename := AhkPathParts%A_Index%
567 Break
568 }
569 }
570 }
571 return AhkExeFilename
572}
573
574OpenCreateDataTextFile(Filename)
575{
576 Filepath := A_ScriptDir . "\data\" . Filename
577 IfExist, % Filepath
578 {
579 Run, % Filepath
580 }
581 Else
582 {
583 File := FileOpen(Filepath, "w")
584 IF !IsObject(File)
585 {
586 MsgBox, 16, Error, File not found and can't write new file.
587 return
588 }
589 File.Close()
590 Run, % Filepath
591 }
592 return
593
594}
595
596OpenTextFileReadOnly(FilePath)
597{
598 ExecuteString := FilePath
599 if (FileExist(FilePath)) {
600 openWith := AssociatedProgram("txt")
601 if (openWith) {
602 if (InStr(openWith, "system32\NOTEPAD.exe")) {
603 if (InStr(openWith, "SystemRoot")) {
604 ; because `Run` cannot expand environment variable for some reason
605 EnvGet, SystemRoot, SystemRoot
606 StringReplace, openWith, openWith, `%SystemRoot`%, %SystemRoot%
607 }
608 }
609 if (InStr(openWith, " %1")) {
610 ; trim `%1`
611 StringTrimRight, openWith, openWith, 2
612 }
613 ExecuteString := openWith " " FilePath
614 }
615 FileSetAttrib, +R, %FilePath%
616 RunWait, %ExecuteString%
617 FileSetAttrib, -R, %FilePath%
618 }
619 else {
620 MsgBox, 16, Error, File not found.
621 }
622 return
623}
624
625OpenUserDirFile(Filename)
626{
627 Filepath := userDirectory . "\" . Filename
628 IfExist, % Filepath
629 {
630 Run, % Filepath
631 }
632 Else
633 {
634 MsgBox, 16, Error, File not found.
635 return
636 }
637 return
638
639}
640
641OpenUserSettingsFolder(ProjectName, Dir = "")
642{
643 If (!StrLen(Dir)) {
644 Dir := userDirectory
645 }
646
647 If (!InStr(FileExist(Dir), "D")) {
648 FileCreateDir, %Dir%
649 }
650 Run, Explorer %Dir%
651 return
652}
653
654; Function that checks item type name against entries
655; from ItemList.txt to get the item's base level
656; Added by kongyuyu, changed by hazydoc, vdorie
657CheckBaseLevel(ItemBaseName)
658{
659 ItemListArray = 0
660 Loop, Read, %A_ScriptDir%\data\ItemList.txt
661 {
662 ; This loop retrieves each line from the file, one at a time.
663 ItemListArray += 1 ; Keep track of how many items are in the array.
664 StringSplit, NameLevel, A_LoopReadLine, |,
665 Array%ItemListArray%1 := NameLevel1 ; Store this line in the next array element.
666 Array%ItemListArray%2 := NameLevel2
667 }
668
669 ResultLength := 0
670 ResultIndex := 0
671
672 Loop %ItemListArray% {
673 element := Array%A_Index%1
674
675 IF (InStr(ItemBaseName, element) != 0 && StrLen(element) > ResultLength)
676 {
677 ResultIndex := A_Index
678 ResultLength := StrLen(element)
679 }
680 }
681
682 BaseLevel := ""
683 IF (ResultIndex > 0) {
684 BaseLevel := Array%ResultIndex%2
685 }
686 return BaseLevel
687}
688
689CheckRarityLevel(RarityString)
690{
691 IfInString, RarityString, Normal
692 return 1
693 IfInString, RarityString, Magic
694 return 2
695 IfInString, RarityString, Rare
696 return 3
697 IfInString, RarityString, Unique
698 return 4
699 return 0 ; unknown rarity. shouldn't happen!
700}
701
702ParseItemType(ItemDataStats, ItemDataNamePlate, ByRef BaseType, ByRef SubType, ByRef GripType, RarityLevel)
703{
704 ; Grip type only matters for weapons at this point. For all others it will be 'None'.
705 ; Note that shields are armour and not weapons, they are not 1H.
706 GripType = None
707
708 ; Check stats section first as weapons usually have their sub type as first line
709 Loop, Parse, ItemDataStats, `n, `r
710 {
711 If (RegExMatch(A_LoopField, "i)\b((One Handed|Two Handed) (Axe|Sword|Mace)|Sceptre|Staff|Dagger|Claw|Bow|Wand)\b", match))
712 {
713 BaseType := "Weapon"
714 If (RegExMatch(match1, "i)(Sword|Axe|Mace)", subMatch)) {
715 SubType := subMatch1
716 } Else {
717 SubType := match1
718 }
719 GripType := (RegExMatch(match1, "i)\b(Two Handed|Staff|Bow)\b")) ? "2H" : "1H"
720 return
721 }
722 }
723
724 ; Check name plate section
725 Loop, Parse, ItemDataNamePlate, `n, `r
726 {
727 ; Get third line in case of rare or unique item and retrieve the base item name
728 LoopField := RegExReplace(A_LoopField, "<<.*>>", "")
729 If (RarityLevel > 2)
730 {
731 Loop, Parse, ItemDataNamePlate, `n, `r
732 {
733 If (A_Index = 3) {
734 LoopField := Trim(A_LoopField) ? Trim(A_LoopField) : LoopField
735 }
736 }
737 }
738
739 ; Belts, Amulets, Rings, Quivers, Flasks
740 If (RegExMatch(LoopField, "i)\b(Belt|Stygian Vise|Rustic Sash)\b"))
741 {
742 BaseType = Item
743 SubType = Belt
744 return
745 }
746 If (RegExMatch(LoopField, "i)\b(Amulet|Talisman)\b")) and not (RegExMatch(LoopField, "i)\bLeaguestone\b"))
747 {
748 BaseType = Item
749 SubType = Amulet
750 return
751 }
752 If (RegExMatch(LoopField, "\b(Ring|Quiver|Flask)\b", match))
753 {
754 BaseType := "Item"
755 SubType := match1
756 return
757 }
758 If (RegExMatch(LoopField, "i)\b(Map)\b"))
759 {
760 Global mapMatchList
761 BaseType = Map
762 Loop % mapMatchList.MaxIndex()
763 {
764 mapMatch := mapMatchList[A_Index]
765 IfInString, LoopField, %mapMatch%
766 {
767 If (RegExMatch(LoopField, "\bShaped " . mapMatch))
768 {
769 SubType = Shaped %mapMatch%
770 }
771 Else
772 {
773 SubType = %mapMatch%
774 }
775 return
776 }
777 }
778
779 SubType = Unknown%A_Space%Map
780 return
781 }
782
783 ; Jewels
784 If (RegExMatch(LoopField, "i)(Cobalt|Crimson|Viridian|Prismatic) Jewel", match)) {
785 BaseType = Jewel
786 SubType := match1 " Jewel"
787 return
788 }
789 ; Abyss Jewels
790 If (RegExMatch(LoopField, "i)(Murderous|Hypnotic|Searching|Ghastly) Eye Jewel", match)) {
791 BaseType = Jewel
792 SubType := match1 " Eye Jewel"
793 return
794 }
795
796 ; Leaguestones and Scarabs
797 If (RegExMatch(Loopfield, "i)\b(Leaguestone|Scarab)\b"))
798 {
799 RegexMatch(LoopField, "i)(.*)(Leaguestone|Scarab)", typeMatch)
800 RegexMatch(Trim(typeMatch1), "i)\b(\w+)\W*$", match) ; match last word
801 BaseType := Trim(typeMatch2)
802 SubType := Trim(match1) " " Trim(typeMatch2)
803 return
804 }
805
806
807 ; Matching armour types with regular expressions for compact code
808
809 ; Shields
810 If (RegExMatch(LoopField, "\b(Buckler|Bundle|Shield)\b"))
811 {
812 BaseType = Armour
813 SubType = Shield
814 return
815 }
816
817 ; Gloves
818 If (RegExMatch(LoopField, "\b(Gauntlets|Gloves|Mitts)\b"))
819 {
820 BaseType = Armour
821 SubType = Gloves
822 return
823 }
824
825 ; Boots
826 If (RegExMatch(LoopField, "\b(Boots|Greaves|Slippers)\b"))
827 {
828 BaseType = Armour
829 SubType = Boots
830 return
831 }
832
833 ; Helmets
834 If (RegExMatch(LoopField, "\b(Bascinet|Burgonet|Cage|Circlet|Crown|Hood|Helm|Helmet|Mask|Sallet|Tricorne)\b"))
835 {
836 BaseType = Armour
837 SubType = Helmet
838 return
839 }
840
841 ; Note: Body armours can have "Pelt" in their randomly assigned name,
842 ; explicitly matching the three pelt base items to be safe.
843
844 If (RegExMatch(LoopField, "\b(Iron Hat|Leather Cap|Rusted Coif|Wolf Pelt|Ursine Pelt|Lion Pelt)\b"))
845 {
846 BaseType = Armour
847 SubType = Helmet
848 return
849 }
850
851 ; BodyArmour
852 ; Note: Not using "$" means "Leather" could match "Leather Belt", therefore we first check that the item is not a belt. (belts are currently checked earlier so this is redundant, but the order might change)
853 If (!RegExMatch(LoopField, "\b(Belt)\b"))
854 {
855 If (RegExMatch(LoopField, "\b(Armour|Brigandine|Chainmail|Coat|Doublet|Garb|Hauberk|Jacket|Lamellar|Leather|Plate|Raiment|Regalia|Ringmail|Robe|Tunic|Vest|Vestment)\b"))
856 {
857 BaseType = Armour
858 SubType = BodyArmour
859 return
860 }
861 }
862
863 If (RegExMatch(LoopField, "\b(Chestplate|Full Dragonscale|Full Wyrmscale|Necromancer Silks|Shabby Jerkin|Silken Wrap)\b"))
864 {
865 BaseType = Armour
866 SubType = BodyArmour
867 return
868 }
869 }
870}
871
872GetClipboardContents(DropNewlines=False)
873{
874 Result =
875 Note =
876 If Not DropNewlines
877 {
878 Loop, Parse, Clipboard, `n, `r
879 {
880 IfInString, A_LoopField, note:
881
882 ; new code added by Bahnzo - The ability to add prices to items causes issues.
883 ; Building the code sent from the clipboard differently, and ommiting the line with "Note:" on it partially fixes this.
884 ; We also have to omit the \newline \return that gets added at the end.
885 ; Not adding the note to ClipboardContents but its own variable should solve all problems.
886 {
887 Note := A_LoopField
888 ; We drop the "note:", but the "--------" has already been added and we don't want it, so we delete the last 8 chars.
889 Result := SubStr(Result, 1, -8)
890 break
891 }
892 IfInString, A_LoopField, Map drop
893 {
894 ; We drop the "Map drop", but the "--------" has already been added and we don't want it, so we delete the last 8 chars.
895 Result := SubStr(Result, 1, -8)
896 break
897 }
898 If A_Index = 1 ; so we start with just adding the first line w/o either a `n or `r
899 {
900 Result := Result . A_LoopField
901 }
902 Else
903 {
904 Result := Result . "`r`n" . A_LoopField ; and then adding those before adding lines. This makes sure there are no trailing `n or `r.
905 ;Result := Result . A_LoopField . "`r`n" ; the original line, left in for clarity.
906 }
907 }
908 }
909 Else
910 {
911 Loop, Parse, Clipboard, `n, `r
912 {
913 IfInString, A_LoopField, note:
914 {
915 Note := A_LoopField
916 Result := SubStr(Result, 1, -8)
917 break
918 }
919 Result := Result . A_LoopField
920 }
921 }
922
923 RegExMatch(Trim(Note), "i)^Note: (.*)", match)
924 Globals.Set("ItemNote", match1)
925
926 return Result
927}
928
929SetClipboardContents(String)
930{
931 Clipboard := String
932 ; Temp, I used this for debugging and considering adding it to UserOptions
933 ; append the result for easier comparison and debugging
934 ; Clipboard = %Clipboard%`n*******************************************`n`n%String%
935}
936
937/* See ArrayFromDatafile()
938*/
939ArrayFromDataobject(Obj)
940{
941 If (Obj = False)
942 {
943 return False
944 }
945
946 ModDataArray := []
947
948 For Idx, Line in Obj
949 {
950 min := ""
951 max := ""
952 minLo := ""
953 minHi := ""
954 maxLo := ""
955 maxHi := ""
956
957 StringSplit, AffixDataParts, Line, |,
958 RangeItemLevel := AffixDataParts1
959 RangeValues := AffixDataParts2
960
961 IfInString, RangeValues, `,
962 {
963 ; Example lines from txt file database for double range lookups:
964 ; 3|1,14-15
965 ; 13|1-3,35-37
966 StringSplit, DoubleRangeParts, RangeValues, `,
967 LB := DoubleRangeParts1
968 UB := DoubleRangeParts2
969
970 IfInString, LB, -
971 {
972 ; Lower bound is a range: #-#
973 SplitRange(LB, minLo, minHi)
974 }
975 Else
976 {
977 ; Lower bound is a single value. Gets assigned to both min.
978 minLo := LB
979 minHi := LB
980 }
981 IfInString, UB, -
982 {
983 ; Upper bound is a range: #-#
984 SplitRange(UB, maxLo, maxHi)
985 }
986 Else
987 {
988 ; Upper bound is a single value. Gets assigned to both min.
989 maxLo := UB
990 maxHi := UB
991 }
992 }
993 Else
994 {
995 ; The whole bracket in RangeValues is in the #-# format (it is no double range). This is the case for most mods.
996 SplitRange(RangeValues, min, max)
997 }
998
999 element := {"ilvl":RangeItemLevel, "values":RangeValues, "min":min, "max":max, "minLo":minLo, "minHi":minHi, "maxLo":maxLo, "maxHi":maxHi}
1000 ModDataArray.InsertAt(1, element)
1001 }
1002
1003 return ModDataArray
1004}
1005
1006/*
1007Puts the data from a file into an array in inverted order, so that tier1 is at array position 1.
1008Each array element is an object with 8 keys:
1009
1010 Always assinged:
1011 ilvl: Itemlevel of the bracket/tier
1012 values: The complete value line from the file
1013
1014 In case of the simple range "1-2" format, otherwise empty string:
1015 min: min value (1)
1016 max: min value (2)
1017
1018 In case of the double range "1-2,3-4" format, otherwise empty string:
1019 minLo: lower min value (1)
1020 minHi: upper min value (2)
1021 maxLo: lower max value (3)
1022 maxHi: upper max value (4)
1023
1024*/
1025ArrayFromDatafile(Filename, AffixMode="Native")
1026{
1027 If (Filename = False)
1028 {
1029 return False
1030 }
1031
1032 ReadType := "Native"
1033
1034 ModDataArray_Native := []
1035 ModDataArray_Essence := []
1036
1037 Loop, Read, %A_ScriptDir%\%Filename%
1038 {
1039 min := ""
1040 max := ""
1041 minLo := ""
1042 minHi := ""
1043 maxLo := ""
1044 maxHi := ""
1045
1046 If (A_LoopReadLine ~= "--Essence--")
1047 {
1048 ReadType := "Essence"
1049 Continue
1050 }
1051
1052 StringSplit, AffixDataParts, A_LoopReadLine, |,
1053 RangeItemLevel := AffixDataParts1
1054 RangeValues := AffixDataParts2
1055
1056 IfInString, RangeValues, `,
1057 {
1058 ; Example lines from txt file database for double range lookups:
1059 ; 3|1,14-15
1060 ; 13|1-3,35-37
1061 StringSplit, DoubleRangeParts, RangeValues, `,
1062 LB := DoubleRangeParts1
1063 UB := DoubleRangeParts2
1064
1065 IfInString, LB, -
1066 {
1067 ; Lower bound is a range: #-#
1068 SplitRange(LB, minLo, minHi)
1069 }
1070 Else
1071 {
1072 ; Lower bound is a single value. Gets assigned to both min.
1073 minLo := LB
1074 minHi := LB
1075 }
1076 IfInString, UB, -
1077 {
1078 ; Upper bound is a range: #-#
1079 SplitRange(UB, maxLo, maxHi)
1080 }
1081 Else
1082 {
1083 ; Upper bound is a single value. Gets assigned to both min.
1084 maxLo := UB
1085 maxHi := UB
1086 }
1087 }
1088 Else
1089 {
1090 ; The whole bracket in RangeValues is in the #-# format (it is no double range). This is the case for most mods.
1091 SplitRange(RangeValues, min, max)
1092 }
1093
1094 element := {"ilvl":RangeItemLevel, "values":RangeValues, "min":min, "max":max, "minLo":minLo, "minHi":minHi, "maxLo":maxLo, "maxHi":maxHi}
1095 ModDataArray_%ReadType%.InsertAt(1, element)
1096 }
1097
1098 If (AffixMode = "essence" or AffixMode = "ess")
1099 {
1100 return ModDataArray_Essence
1101 }
1102 Else{
1103 return ModDataArray_Native
1104 }
1105}
1106
1107/*
1108Parameters:
11091) Finds all matching tiers for Value (either number or value range).
11102) Uses the ModDataArray provided by ArrayFromDatafile().
11113) ItemLevel optional but usually needed for the function to make sense. Tiers that require a higher itemlevel than provided are skipped.
1112
1113Returns an object with 3 keys:
1114 tier: The matching tier found for the provided "Value". Empty string if no match or more than one matching tier is found.
1115
1116 If more than one matching tier is found, otherwise empty strings:
1117 Top: The "best" tier that matches, so the numerically lowest (!) tier.
1118 Btm: The "worst" tier that matches, so the numerically highest (!) tier.
1119*/
1120LookupTierByValue(Value, ModDataArray, ItemLevel=100)
1121{
1122 If (ModDataArray = False)
1123 {
1124 return False
1125 }
1126
1127 tier := ""
1128 tierTop := ""
1129 tierBtm := ""
1130
1131 Loop
1132 {
1133 If ( A_Index > ModDataArray.Length() )
1134 {
1135 Break
1136 }
1137
1138 CheckTier := A_Index
1139
1140 If ( ModDataArray[CheckTier].ilvl > ItemLevel)
1141 {
1142 ; Skip line if the ItemLevel is too low for the tier
1143 Continue
1144 }
1145 Else
1146 {
1147 IfInString, Value, -
1148 {
1149 ; Value is a range (due to a double range mod)
1150 SplitRange(Value, ValueLo, ValueHi)
1151
1152 If ( (ModDataArray[CheckTier].minLo <= ValueLo) and (ValueLo <= ModDataArray[CheckTier].minHi) and (ModDataArray[CheckTier].maxLo <= ValueHi) and (ValueHi <= ModDataArray[CheckTier].maxHi) )
1153 {
1154 ; Both values fit in the brackets
1155 If (tier="")
1156 {
1157 ; tier not assigned yet, so fill it. This is put into tierTop later if more than one tier fits.
1158 tier := CheckTier
1159 }
1160 Else
1161 {
1162 ; otherwise fill tierBtm (and have it potentially overwritten if an even lower match is found later).
1163 tierBtm := CheckTier
1164 }
1165 }
1166 Continue
1167 }
1168 Else
1169 {
1170 ; Value is a number, not a range
1171 If ( (ModDataArray[CheckTier].min <= Value) and (Value <= ModDataArray[CheckTier].max) )
1172 {
1173 ; Value fits in the bracket
1174 If (tier="")
1175 {
1176 ; tier not assigned yet, so fill it. This is put into tierTop later if more than one tier fits.
1177 tier := CheckTier
1178 }
1179 Else
1180 {
1181 ; otherwise fill tierBtm (and have it potentially overwritten if an even lower match is found later).
1182 tierBtm := CheckTier
1183 }
1184 }
1185 Continue
1186 }
1187 }
1188 }
1189 If (tierBtm)
1190 {
1191 ; tierBtm was actually used, so more than one tier fits. Thus putting tier into tierTop instead.
1192 tierTop := tier
1193 tier := ""
1194 }
1195
1196 return {"Tier":tier,"Top":tierTop,"Btm":tierBtm}
1197}
1198
1199LookupImplicitValue(ItemBaseName)
1200{
1201 Global Opts
1202 FileRead, FileImplicits, data\Implicits.json
1203 Implicits := JSON.Load(FileImplicits)
1204 ImplicitText := Implicits[ItemBaseName]["Implicit"]
1205
1206 IfInString, ImplicitText, `,
1207 {
1208 StringSplit, Part, ImplicitText, `,
1209 return [GetActualValue(Part1), GetActualValue(Part2)]
1210 }
1211 Else If (RegExMatch(ImplicitText, "Adds \((\d+\-\d+)\) to \((\d+\-\d+)\)", match))
1212 {
1213 return [match1 Opts.DoubleRangeSeparator match2]
1214 }
1215 Else If (RegExMatch(ImplicitText, "Adds (\d+\) to (\d+\)", match))
1216 {
1217 return [match1 Opts.DoubleRangeSeparator match2]
1218 }
1219 Else If (RegExMatch(ImplicitText, "\((.*?)\)", match))
1220 {
1221 return [match1]
1222 }
1223 Else{
1224 return [GetActualValue(ImplicitText)]
1225 }
1226}
1227
1228LookupAffixData(DataSource, ItemLevel, Value, ByRef Tier="")
1229{
1230 If (IsObject(DataSource)){
1231 ModDataArray := ArrayFromDataobject(DataSource)
1232 }
1233 Else{
1234 ModDataArray := ArrayFromDatafile(DataSource)
1235 }
1236
1237 ModTiers := LookupTierByValue(Value, ModDataArray, ItemLevel)
1238
1239 If (ModTiers.Tier)
1240 {
1241 Tier := ModTiers.Tier
1242 }
1243 Else If (ModTiers.Top and ModTiers.Btm)
1244 {
1245 Tier := [ModTiers.Top, ModTiers.Btm]
1246 }
1247 Else If (Value contains "-")
1248 {
1249 SplitRange(Value, Lo, Hi)
1250 If (Lo > ModDataArray[1].maxLo and Hi > ModDataArray[1].maxHi)
1251 {
1252 Tier := 0
1253 }
1254 Else
1255 {
1256 Tier := "?"
1257 }
1258 }
1259 Else
1260 {
1261 If (Value > ModDataArray[1].max)
1262 {
1263 Tier := 0
1264 }
1265 Else
1266 {
1267 Tier := "?"
1268 }
1269 }
1270
1271 return FormatValueRangesAndIlvl(ModDataArray, Tier)
1272}
1273
1274ParseRarity(ItemData_NamePlate)
1275{
1276 Loop, Parse, ItemData_NamePlate, `n, `r
1277 {
1278 IfInString, A_LoopField, Rarity:
1279 {
1280 StringReplace, RarityReplace, A_LoopField, :%A_Space%, :, All
1281 StringSplit, RarityParts, RarityReplace, :
1282 Break
1283 }
1284 }
1285
1286 return RarityParts%RarityParts%2
1287}
1288
1289Assert(expr, msg)
1290{
1291 If (Not (expr))
1292 {
1293 MsgBox, 4112, Assertion Failure, %msg%
1294 ExitApp
1295 }
1296}
1297
1298GetItemDataChunk(ItemDataText, MatchWord)
1299{
1300 Assert(StrLen(MatchWord) > 0, "GetItemDataChunk: parameter 'MatchWord' can't be empty")
1301
1302 StringReplace, TempResult, ItemDataText, --------`r`n, ``, All
1303 StringSplit, ItemDataChunks, TempResult, ``
1304 Loop, %ItemDataChunks0%
1305 {
1306 IfInString, ItemDataChunks%A_Index%, %MatchWord%
1307 {
1308 return ItemDataChunks%A_Index%
1309 }
1310 }
1311}
1312
1313ParseQuality(ItemDataNamePlate)
1314{
1315 ItemQuality := 0
1316 Loop, Parse, ItemDataNamePlate, `n, `r
1317 {
1318 If (StrLen(A_LoopField) = 0)
1319 {
1320 Break
1321 }
1322 IfInString, A_LoopField, Unidentified
1323 {
1324 Break
1325 }
1326 IfInString, A_LoopField, Quality:
1327 {
1328 ItemQuality := RegExReplace(A_LoopField, "Quality: \+(\d+)% .*", "$1")
1329 Break
1330 }
1331 }
1332 return ItemQuality
1333}
1334
1335ParseAugmentations(ItemDataChunk, ByRef AffixCSVList)
1336{
1337 CurAugment := ItemDataChunk
1338 Loop, Parse, ItemDataChunk, `n, `r
1339 {
1340 CurAugment := A_LoopField
1341 Globals.Set("CurAugment", A_LoopField)
1342 IfInString, A_LoopField, Requirements:
1343 {
1344 ; too far - Requirements: is already the next chunk
1345 Break
1346 }
1347 IfInString, A_LoopField, (augmented)
1348 {
1349 StringSplit, LineParts, A_LoopField, :
1350 AffixCSVList := AffixCSVList . "'" . LineParts%LineParts%1 . "'"
1351 AffixCSVList := AffixCSVList . ", "
1352 }
1353 }
1354 AffixCSVList := SubStr(AffixCSVList, 1, -2)
1355}
1356
1357ParseRequirements(ItemDataChunk, ByRef Level, ByRef Attributes, ByRef Values="")
1358{
1359 IfNotInString, ItemDataChunk, Requirements
1360 {
1361 return
1362 }
1363 Attr =
1364 AttrValues =
1365 Delim := ","
1366 DelimLen := StrLen(Delim)
1367 Loop, Parse, ItemDataChunk, `n, `r
1368 {
1369 If StrLen(A_LoopField) = 0
1370 {
1371 Break ; Not interested in blank lines
1372 }
1373 IfInString, A_LoopField, Str
1374 {
1375 Attr := Attr . "Str" . Delim
1376 AttrValues := AttrValues . GetColonValue(A_LoopField) . Delim
1377 }
1378 IfInString, A_LoopField, Dex
1379 {
1380 Attr := Attr . "Dex" . Delim
1381 AttrValues := AttrValues . GetColonValue(A_LoopField) . Delim
1382 }
1383 IfInString, A_LoopField, Int
1384 {
1385 Attr := Attr . "Int" . Delim
1386 AttrValues := AttrValues . GetColonValue(A_LoopField) . Delim
1387 }
1388 IfInString, A_LoopField, Level
1389 {
1390 Level := GetColonValue(A_LoopField)
1391 }
1392 }
1393 ; Chop off last Delim
1394 If (SubStr(Attr, -(DelimLen-1)) == Delim)
1395 {
1396 Attr := SubStr(Attr, 1, -(DelimLen))
1397 }
1398 If (SubStr(AttrValues, -(DelimLen-1)) == Delim)
1399 {
1400 AttrValues := SubStr(AttrValues, 1, -(DelimLen))
1401 }
1402 Attributes := Attr
1403 Values := AttrValues
1404}
1405
1406; Parses #low-#high and sets Hi to #high and Lo to #low
1407; if RangeChunk is just a single value (#) it will set both
1408; Hi and Lo to this single value (effectively making the range 1-1 if # was 1)
1409SplitRange(RangeChunk, ByRef Lo, ByRef Hi)
1410{
1411 IfInString, RangeChunk, -
1412 {
1413 StringSplit, RangeParts, RangeChunk, -
1414 Lo := RegExReplace(RangeParts1, "(\d+?)", "$1")
1415 Hi := RegExReplace(RangeParts2, "(\d+?)", "$1")
1416 }
1417 Else
1418 {
1419 Lo := RangeChunk
1420 Hi := RangeChunk
1421 }
1422}
1423
1424FormatDoubleRanges(BtmMin, BtmMax, TopMin, TopMax, StyleOverwrite="")
1425{
1426 Global Opts
1427
1428 If (StyleOverwrite = "compact" or (Opts.UseCompactDoubleRanges and StyleOverwrite = "") )
1429 {
1430 ValueRange := BtmMin "-" TopMax
1431 }
1432 Else
1433 {
1434 ; Other Variant: Simplify ranges like 1-1 to 1
1435 ; LowerRange := (BtmMin = BtmMax) ? BtmMin : BtmMin "-" BtmMax
1436 ; UpperRange := (TopMin = TopMax) ? TopMax : TopMin "-" TopMax
1437 ; ValueRange := LowerRange "|" UpperRange
1438
1439 ValueRange := BtmMin "-" BtmMax Opts.DoubleRangeSeparator TopMin "-" TopMax
1440 }
1441
1442 return ValueRange
1443}
1444
1445FormatMultiTierRange(BtmMin, BtmMax, TopMin, TopMax)
1446{
1447 Global Opts
1448
1449 If (BtmMin = TopMin and BtmMax = TopMax)
1450 {
1451 return BtmMin "-" TopMax
1452 }
1453 Else
1454 {
1455 return BtmMin "-" BtmMax Opts.MultiTierRangeSeparator TopMin "-" TopMax
1456 }
1457}
1458
1459ParseItemLevel(ItemDataText)
1460{
1461 ; XXX
1462 ; Add support for The Awakening Closed Beta
1463 ; Once TA is released we won't need to support both occurences of
1464 ; the word "Item level" any more...
1465 ItemDataChunk := GetItemDataChunk(ItemDataText, "Itemlevel:")
1466 If (StrLen(ItemDataChunk) <= 0)
1467 {
1468 ItemDataChunk := GetItemDataChunk(ItemDataText, "Item Level:")
1469 }
1470
1471 Assert(StrLen(ItemDataChunk) > 0, "ParseItemLevel: couldn't parse item data chunk")
1472
1473 Loop, Parse, ItemDataChunk, `n, `r
1474 {
1475 IfInString, A_LoopField, Itemlevel:
1476 {
1477 StringSplit, ItemLevelParts, A_LoopField, %A_Space%
1478 Result := StrTrimWhitespace(ItemLevelParts2)
1479 return Result
1480 }
1481 IfInString, A_LoopField, Item Level:
1482 {
1483 StringSplit, ItemLevelParts, A_LoopField, %A_Space%
1484 Result := StrTrimWhitespace(ItemLevelParts3)
1485 return Result
1486 }
1487 }
1488}
1489
1490;;hixxie fixed. Shows MapLevel for any map base.
1491ParseMapTier(ItemDataText)
1492{
1493 ItemDataChunk := GetItemDataChunk(ItemDataText, "MapTier:")
1494 If (StrLen(ItemDataChunk) <= 0)
1495 {
1496 ItemDataChunk := GetItemDataChunk(ItemDataText, "Map Tier:")
1497 }
1498
1499 Assert(StrLen(ItemDataChunk) > 0, "ParseMapTier: couldn't parse item data chunk")
1500
1501 Loop, Parse, ItemDataChunk, `n, `r
1502 {
1503 IfInString, A_LoopField, MapTier:
1504 {
1505 StringSplit, MapLevelParts, A_LoopField, %A_Space%
1506 Result := StrTrimWhitespace(MapLevelParts2)
1507 return Result
1508 }
1509 IfInString, A_LoopField, Map Tier:
1510 {
1511 StringSplit, MapLevelParts, A_LoopField, %A_Space%
1512 Result := StrTrimWhitespace(MapLevelParts3)
1513 return Result
1514 }
1515 }
1516}
1517
1518ParseGemLevel(ItemDataText, PartialString="Level:")
1519{
1520 ItemDataChunk := GetItemDataChunk(ItemDataText, PartialString)
1521 Loop, Parse, ItemDataChunk, `n, `r
1522 {
1523 IfInString, A_LoopField, %PartialString%
1524 {
1525 StringSplit, ItemLevelParts, A_LoopField, %A_Space%
1526 Result := StrTrimWhitespace(ItemLevelParts2)
1527 return Result
1528 }
1529 }
1530}
1531
1532ParseGemColor(ItemDataText)
1533{
1534 RegExMatch(ItemDataText, "ims)Requirements.*?(Str\s?:\s?(\d+))", str)
1535 RegExMatch(ItemDataText, "ims)Requirements.*?(Dex\s?:\s?(\d+))", dex)
1536 RegExMatch(ItemDataText, "ims)Requirements.*?(Int\s?:\s?(\d+))", int)
1537
1538 highestRequirement := ""
1539 If (not str2 and not dex2 and not int2) {
1540 Return "WHITE"
1541 }
1542 Else If (str2 > dex2 and str2 > int2) {
1543 Return "RED"
1544 }
1545 Else If (dex2 > str2 and dex2 > int2) {
1546 Return "GREEN"
1547 }
1548 Else If (int2 > dex2 and int2 > str2) {
1549 Return "BLUE"
1550 }
1551}
1552
1553ParseGemXP(ItemDataText, PartialString="Experience:", ByRef Flat = "")
1554{
1555 ItemDataChunk := GetItemDataChunk(ItemDataText, PartialString)
1556 Loop, Parse, ItemDataChunk, `n, `r
1557 {
1558 IfInString, A_LoopField, %PartialString%
1559 {
1560 StringSplit, ItemLevelParts, A_LoopField, %A_Space%
1561 _Flat := StrTrimWhitespace(ItemLevelParts2)
1562 XP := RegExReplace(_Flat, "\.")
1563 }
1564 }
1565 If (XP) {
1566 RegExMatch(XP, "i)([0-9.,]+)\/([0-9.,]+)", xpPart)
1567 If (StrLen(xpPart1) and StrLen(xpPart2)) {
1568 Percent := Round((xpPart1 / xpPart2) * 100)
1569 Flat := _Flat
1570 Return Percent
1571 }
1572 }
1573}
1574
1575; For Debug purposes. Can be used to unravel an object into a printable format.
1576ExploreObj(Obj, NewRow="`n", Equal=" = ", Indent="`t", Depth=12, CurIndent="")
1577{
1578 for k,v in Obj
1579 ToReturn .= CurIndent . k . (IsObject(v) && depth>1 ? NewRow . ExploreObj(v, NewRow, Equal, Indent, Depth-1, CurIndent . Indent) : Equal . v) . NewRow
1580
1581 return RTrim(ToReturn, NewRow)
1582}
1583
1584
1585DebugMode := False
1586
1587DebugFile(Content, LineEnd="`n", StartNewFile=False)
1588{
1589 Global DebugMode
1590
1591 If ( not isDevVersion){
1592 DebugMode := False
1593 }
1594
1595 If (DebugMode = False)
1596 return
1597
1598 If (StartNewFile){
1599 FileDelete, DebugFile.txt
1600 }
1601
1602 If (IsObject(Content))
1603 {
1604 Print := "`n>>`n" ExploreObj(Content) "`n<<`n"
1605 }
1606 Else If (StrLen(Content) > 100)
1607 {
1608 Print := "`n" Content "`n`n"
1609 }
1610 Else
1611 {
1612 Print := Content . LineEnd
1613 }
1614
1615 FileAppend, %Print%, DebugFile.txt, UTF-8
1616}
1617
1618StrMult(Char, Times)
1619{
1620 Result =
1621 Loop, %Times%
1622 {
1623 Result := Result . Char
1624 }
1625 return Result
1626}
1627
1628StrTrimSpaceLeft(String)
1629{
1630 return RegExReplace(String, " *(.+?)", "$1")
1631}
1632
1633StrTrimSpaceRight(String)
1634{
1635 return RegExReplace(String, "(.+?) *$", "$1")
1636}
1637
1638StrTrimSpace(String)
1639{
1640 return RegExReplace(String, " *(.+?) *", "$1")
1641}
1642
1643StrTrimWhitespace(String)
1644{
1645 return RegExReplace(String, "[ \r\n\t]*(.+?)[ \r\n\t]*", "$1")
1646}
1647
1648; Pads a string with a multiple of PadChar to become a wanted total length.
1649; Note that Side is the side that is padded not the anchored side.
1650; Meaning, if you pad right side, the text will move left. If Side was an
1651; anchor instead, the text would move right if anchored right.
1652StrPad(String, Length, Side="right", PadChar=" ")
1653{
1654 StringLen, Len, String
1655 AddLen := Length-Len
1656 If (AddLen <= 0)
1657 {
1658 return String
1659 }
1660 Pad := StrMult(PadChar, AddLen)
1661 If (Side == "right")
1662 {
1663 Result := String . Pad
1664 }
1665 Else
1666 {
1667 Result := Pad . String
1668 }
1669 return Result
1670}
1671
1672; Prefix a string s with another string prefix.
1673; Does nothing if s is already prefixed.
1674StrPrefix(s, prefix) {
1675 If (s == "") {
1676 return ""
1677 } Else {
1678 If (SubStr(s, 1, StrLen(prefix)) == prefix) {
1679 return s ; Nothing to do
1680 } Else {
1681 return prefix . s
1682 }
1683 }
1684}
1685
1686; Formats a number with SetFormat (leaving A_FormatFloat unchanged)
1687; Returns formatted Num as string.
1688NumFormat(Num, Format)
1689{
1690 oldFormat := A_FormatFloat
1691 newNum := Num
1692 SetFormat, FloatFast, %Format%
1693 newNum += 0.0 ; convert to float, which applies SetFormat
1694 newNum := newNum . "" ; convert to string so the next SetFormat doesn't apply
1695 SetFormat, FloatFast, %oldFormat%
1696 return newNum
1697}
1698
1699; Formats floating values such as 2.50000 or 3.00000 into 2.5 and 3
1700NumFormatPointFiveOrInt(Value){
1701 If ( not Mod(Value, 1) )
1702 {
1703 return Round(Value)
1704 }
1705 Else
1706 {
1707 return NumFormat(Value, 0.1)
1708 }
1709}
1710
1711; Pads a number with prefixed 0s and optionally rounds or appends to specified decimal places width.
1712NumPad(Num, TotalWidth, DecimalPlaces=0)
1713{
1714 myFormat = 0%TotalWidth%.%DecimalPlaces%
1715 newNum := NumFormat(Num, myFormat)
1716 return newNum
1717}
1718
1719AffixTypeShort(AffixType)
1720{
1721 result := RegExReplace(AffixType, "Hybrid Defence Prefix", "HDP")
1722 result := RegExReplace(result, "Crafted ", "Cr") ; not fully supported yet.
1723 result := RegExReplace(result, "Hybrid ", "Hyb")
1724 result := RegExReplace(result, "Prefix", "P")
1725 result := RegExReplace(result, "Suffix", "S")
1726
1727 return result
1728}
1729
1730MakeAffixDetailLine(AffixLine, AffixType, ValueRange, Tier, CountAffixTotals=True)
1731{
1732 Global ItemData, AffixTotals
1733
1734 If (CountAffixTotals)
1735 {
1736 If (AffixType = "Hybrid Prefix" or AffixType = "Hybrid Defence Prefix"){
1737 AffixTotals.NumPrefixes += 0.5
1738 }
1739 Else If (AffixType = "Hybrid Suffix"){
1740 AffixTotals.NumSuffixes += 0.5
1741 }
1742 Else If (AffixType ~= "Prefix"){ ; using ~= to match all that contains "Prefix", such as "Crafted Prefix".
1743 AffixTotals.NumPrefixes += 1
1744 }
1745 Else If (AffixType ~= "Suffix"){
1746 AffixTotals.NumSuffixes += 1
1747 }
1748 }
1749
1750 If (Item.IsJewel and not Item.IsAbyssJewel)
1751 {
1752 TierAndType := AffixTypeShort(AffixType) ; Discard tier since it's always T1
1753
1754 return [AffixLine, ValueRange, TierAndType]
1755 }
1756
1757 If (IsObject(AffixType))
1758 {
1759 ; Multiple mods in one line
1760 TierAndType := ""
1761
1762 For n, AfTy in AffixType
1763 {
1764 If (IsObject(Tier[A_Index]))
1765 {
1766 ; Tier has a range
1767 If (Tier[A_Index][1] = Tier[A_Index][2])
1768 {
1769 Ti := Tier[A_Index][1]
1770 }
1771 Else
1772 {
1773 Ti := Tier[A_Index][1] "-" Tier[A_Index][2]
1774 }
1775 }
1776 Else
1777 {
1778 Ti := Tier[A_Index]
1779 }
1780
1781 TierAndType .= "T" Ti " " AffixTypeShort(AfTy) " + "
1782 }
1783
1784 TierAndType := SubStr(TierAndType, 1, -3) ; Remove trailing " + " at line end
1785 }
1786 Else If (IsObject(Tier))
1787 {
1788 ; Just one mod in the line, but Tier has a range
1789 If (Tier[1] = Tier[2])
1790 {
1791 Ti := Tier[1]
1792 }
1793 Else
1794 {
1795 Ti := Tier[1] "-" Tier[2]
1796 }
1797
1798 TierAndType := "T" Ti " " AffixTypeShort(AffixType)
1799 }
1800 Else
1801 {
1802 If (IsNum(Tier) or Tier = "?")
1803 {
1804 ; Just one mod and a single numeric tier
1805 TierAndType := "T" Tier " " AffixTypeShort(AffixType)
1806 }
1807 Else
1808 {
1809 ; Some special mods like meta crafts provide no Tier, don't use the "T" in that case.
1810 TierAndType := AffixTypeShort(AffixType)
1811 }
1812 }
1813
1814 return [AffixLine, ValueRange, TierAndType]
1815}
1816
1817MakeMapAffixLine(AffixLine, MapAffixCount)
1818{
1819 Line := [AffixLine, MapAffixCount]
1820 return Line
1821}
1822
1823AppendAffixInfo(Line, AffixPos)
1824{
1825 Global AffixLines
1826 AffixLines.Set(AffixPos, Line)
1827}
1828
1829AssembleAffixDetails()
1830{
1831 Global Opts, AffixLines, Itemdata
1832
1833 Result := ""
1834 NumAffixLines := AffixLines.MaxIndex() ; ( Itemdata.AffixTextLines.MaxIndex() > AffixLines.MaxIndex() ) ? Itemdata.AffixTextLines.MaxIndex() : AffixLines.MaxIndex()
1835
1836 TextLineWidth := 23
1837 TextLineWidthUnique := TextLineWidth + 10
1838 TextLineWidthJewel := TextLineWidth + 10
1839
1840 ValueRange1Width := 4
1841 ValueRange2Width := 5
1842
1843 Separator := Opts.AffixColumnSeparator
1844 Ellipsis := Opts.AffixTextEllipsis
1845
1846 If (Item.IsUnique)
1847 {
1848 Loop, %NumAffixLines%
1849 {
1850 CurLine := AffixLines[A_Index]
1851 AffixText := CurLine[1]
1852 ValueRange := CurLine[2]
1853
1854 If (StrLen(AffixText) > TextLineWidthUnique)
1855 {
1856 AffixText := SubStr(AffixText, 1, TextLineWidthUnique - StrLen(Ellipsis)) . Ellipsis
1857 }
1858 Else
1859 {
1860 AffixText := StrPad(AffixText, TextLineWidthUnique)
1861 }
1862
1863 ProcessedLine := AffixText . Separator . ValueRange
1864
1865 Result .= "`n" ProcessedLine
1866 }
1867
1868 return Result
1869 }
1870 Else
1871 {
1872 Loop, %NumAffixLines%
1873 {
1874 CurLine := AffixLines[A_Index]
1875
1876 ValueRange := CurLine[2]
1877 If ( ! IsObject(ValueRange) )
1878 {
1879 ; Text as ValueRange
1880 continue
1881 }
1882
1883 If ( StrLen(ValueRange[1]) > ValueRange1Width )
1884 {
1885 If (ValueRange[2])
1886 {
1887 ValueRange1Width := StrLen(ValueRange[1])
1888 }
1889 Else
1890 { ; TierRange has no ilvl entry, can expand a bit.
1891 ; Moving more the longer the range text is. Until 9 chars: +2, 10-11 chars: +3, 12-13 chars: +4, then: +5.
1892 ; This keeps the "…" of a multi tier range aligned with the "-" of most normal ranges,
1893 ; but also keeps slightly larger normal ranges aligned as usual, like so:
1894 /* 28-32 (44)
1895 48-62…58-72
1896 104-117
1897 30-35 (49)
1898 84-97…94-108
1899 119-261…201-361
1900 40-44 (35)
1901 */
1902 extra := StrLen(ValueRange[1]) <= 7 ? 0 : (StrLen(ValueRange[1]) <= 9 ? 2 : ( StrLen(ValueRange[1]) <= 11 ? 3 : ( StrLen(ValueRange[1]) <= 13 ? 4 : 5)))
1903
1904 If ( StrLen(ValueRange[1]) > ValueRange1Width + extra )
1905 {
1906 ValueRange1Width := StrLen(ValueRange[1]) - extra
1907 }
1908 }
1909 }
1910
1911 If ( StrLen(ValueRange[3]) > ValueRange2Width )
1912 {
1913 ValueRange2Width := StrLen(ValueRange[3])
1914 }
1915 }
1916
1917 If ( not ((Item.IsJewel and not Item.IsAbyssJewel) or Item.IsFlask) and Opts.ShowHeaderForAffixOverview)
1918 {
1919 ; Add a header line above the affix infos.
1920 ProcessedLine := "`n"
1921 ProcessedLine .= StrPad("TierRange", TextLineWidth + ValueRange1Width + StrLen(Separator), "left")
1922 ProcessedLine .= " ilvl" Separator
1923 ProcessedLine .= StrPad("Total", ValueRange2Width, "left")
1924 ProcessedLine .= " ilvl" Separator
1925 ProcessedLine .= "Tier"
1926
1927 Result .= ProcessedLine
1928 }
1929
1930 Loop, %NumAffixLines%
1931 {
1932 CurLine := AffixLines[A_Index]
1933 ; Any empty line is considered as an Unprocessed Mod
1934 If (IsObject(CurLine))
1935 {
1936 AffixText := CurLine[1]
1937 ValueRange := CurLine[2]
1938 TierAndType := CurLine[3]
1939
1940 If (AffixText = "or")
1941 {
1942 AffixText := "--or--"
1943 AffixText := StrPad(AffixText, round( (TextLineWidth + StrLen(AffixText))/2 ), "left") ; align mid
1944 }
1945
1946 If ((Item.IsJewel and not Item.IsAbyssJewel) or Item.IsFlask)
1947 {
1948 If (StrLen(AffixText) > TextLineWidthJewel)
1949 {
1950 ProcessedLine := SubStr(AffixText, 1, TextLineWidthJewel - StrLen(Ellipsis)) Ellipsis
1951 }
1952 Else
1953 {
1954 ProcessedLine := StrPad(AffixText, TextLineWidthJewel)
1955 }
1956
1957 ; Jewel mods don't have tiers. Display only the ValueRange and the AffixType. TierAndType already holds only the Type here, due to a check in MakeAffixDetailLine().
1958 ProcessedLine .= Separator " " StrPad(ValueRange[1], ValueRange1Width, "left")
1959 ProcessedLine .= Separator TierAndType
1960 }
1961 Else
1962 {
1963 If (StrLen(AffixText) > TextLineWidth)
1964 {
1965 ProcessedLine := SubStr(AffixText, 1, TextLineWidth - StrLen(Ellipsis)) Ellipsis
1966 }
1967 Else
1968 {
1969 ProcessedLine := StrPad(AffixText, TextLineWidth)
1970 }
1971
1972 If ( ! IsObject(ValueRange) )
1973 {
1974 ; Text as ValueRange. Right-aligned to tier range column and with separator if it fits.
1975 If (StrLen(ValueRange) > ValueRange1Width + 5)
1976 {
1977 ; wider than the TierRange column (with ilvl space), content is allowed to also move in the second column but we can't put the Separator in.
1978 ProcessedLine .= Separator StrPad(StrPad(ValueRange, ValueRange1Width + 5, "left"), ValueRange1Width + 5 + StrLen(Separator) + ValueRange2Width + 5, "right")
1979 }
1980 Else
1981 {
1982 ; Fits into TierRange column (with ilvl space), so we set the Separator and an empty column two.
1983 ProcessedLine .= Separator StrPad(StrPad(ValueRange, ValueRange1Width + 5, "left") . Separator, ValueRange1Width + 5 + StrLen(Separator) + ValueRange2Width + 5, "right")
1984 }
1985
1986 If (RegExMatch(TierAndType, "^T\d.*"))
1987 {
1988 ProcessedLine .= Separator TierAndType
1989 }
1990 Else
1991 {
1992 ; If TierAndType does not start with T and a number, then there is just the affix type (or other text) stored. Add 3 spaces to align affix type with the others.
1993 ProcessedLine .= Separator " " TierAndType
1994 }
1995 }
1996 Else
1997 {
1998 If (IsNum(ValueRange[2]))
1999 { ; Has ilvl entry for tier range
2000 ProcessedLine .= Separator StrPad(ValueRange[1], ValueRange1Width, "left") " " StrPad("(" ValueRange[2] ")", 4, "left")
2001 ProcessedLine .= Separator StrPad(ValueRange[3], ValueRange2Width, "left") " " StrPad("(" ValueRange[4] ")", 4, "left")
2002 ProcessedLine .= Separator TierAndType
2003 }
2004 Else If (ValueRange[2] != "")
2005 { ; Has some kind of ilvl entry that is not a number, likely a space. Format as above but without brackets.
2006 ProcessedLine .= Separator StrPad(ValueRange[1], ValueRange1Width, "left") " " StrPad(ValueRange[2] , 4, "left")
2007 ProcessedLine .= Separator StrPad(ValueRange[3], ValueRange2Width, "left") " " StrPad(ValueRange[4] , 4, "left")
2008 ProcessedLine .= Separator TierAndType
2009 }
2010 Else
2011 { ; Has no ilvl entry for tier range, can expand a bit.
2012 ; The "extra" calculation and reasoning has an explanation about 100 code lines above.
2013 extra := StrLen(ValueRange[1]) <= 7 ? 0 : (StrLen(ValueRange[1]) <= 9 ? 2 : ( StrLen(ValueRange[1]) <= 11 ? 3 : ( StrLen(ValueRange[1]) <= 13 ? 4 : 5)))
2014
2015 ProcessedLine .= Separator StrPad(ValueRange[1] . StrMult(" ", 5 - extra), ValueRange1Width + 5, "left")
2016 ProcessedLine .= Separator StrPad(ValueRange[3], ValueRange2Width, "left") " " StrPad("(" ValueRange[4] ")", 4, "left")
2017 ProcessedLine .= Separator TierAndType
2018 }
2019 }
2020 }
2021 }
2022 Else
2023 {
2024 ProcessedLine := " Essence Mod, unknown Mod or unsolved case"
2025 }
2026
2027 Result .= "`n" ProcessedLine
2028 }
2029 }
2030
2031 return Result
2032}
2033
2034AssembleMapAffixes()
2035{
2036 Global Opts, AffixLines
2037
2038 AffixLine =
2039 NumAffixLines := AffixLines.MaxIndex()
2040 AffixLineParts := 0
2041 Loop, %NumAffixLines%
2042 {
2043 CurLine := AffixLines[A_Index]
2044 ; Any empty line is considered as an Unprocessed Mod
2045 If (IsObject(CurLine))
2046 {
2047 AffixLine := CurLine[1]
2048 MapAffixCount := CurLine[2]
2049
2050 ProcessedLine := Format("{1: 2s}) {2:s}", MapAffixCount, AffixLine)
2051 }
2052 Else
2053 {
2054 ProcessedLine := " Unknown Mod"
2055 }
2056
2057 Result := Result . "`n" . ProcessedLine
2058 }
2059 return Result
2060}
2061
2062; Checks ActualValue against ValueRange, returning 1 if
2063; ActualValue is within bounds of ValueRange, 0 otherwise.
2064WithinBounds(ValueRange, ActualValue)
2065{
2066 VHi := 0
2067 VLo := 0
2068 SplitRange(ValueRange, VLo, VHi)
2069 Result := 1
2070 IfInString, ActualValue, -
2071 {
2072 AVHi := 0
2073 AVLo := 0
2074 SplitRange(ActualValue, AVLo, AVHi)
2075 If ((AVLo < VLo) or (AVHi > VHi))
2076 {
2077 Result := 0
2078 }
2079 }
2080 Else
2081 {
2082 If ((ActualValue < VLo) or (ActualValue > VHi))
2083 {
2084 Result := 0
2085 }
2086 }
2087 return Result
2088}
2089
2090; Get actual value from a line of the ingame tooltip as a number
2091; that can be used in calculations.
2092GetActualValue(ActualValueLine)
2093{
2094 ; Leaves "-" in for negative values, example: "Ventor's Gamble"
2095 Result := RegExReplace(ActualValueLine, ".*?\+?(-?\d+(?: to -?\d+|\.\d+)?).*", "$1")
2096 ; Formats "1 to 2" as "1-2"
2097 StringReplace, Result, Result, %A_SPACE%to%A_SPACE%, -
2098 return Result
2099}
2100
2101; Get value from a colon line, e.g. given the line "Level: 57", returns the number 57
2102GetColonValue(Line)
2103{
2104 IfInString, Line, :
2105 {
2106 StringSplit, LineParts, Line, :
2107 Result := StrTrimSpace(LineParts%LineParts%2)
2108 return Result
2109 }
2110}
2111
2112AddRange(Range1, Range2)
2113{
2114 R1Hi := 0
2115 R1Lo := 0
2116 R2Hi := 0
2117 R2Lo := 0
2118 SplitRange(Range1, R1Lo, R1Hi)
2119 SplitRange(Range2, R2Lo, R2Hi)
2120 FinalHi := R1Hi + R2Hi
2121 FinalLo := R1Lo + R2Lo
2122 FinalRange = %FinalLo%-%FinalHi%
2123 return FinalRange
2124}
2125
2126/*
2127ParseProphecy(ItemData, ByRef Difficulty = "", ByRef SealingCost = "")
2128{
2129 ; Will have to be reworked for 3.0
2130 For key, part in ItemData.Parts {
2131 RegExMatch(part, "i)(Normal)|(Cruel)|(Merciless) Difficulty", match)
2132 If (match) {
2133 Difficulty := match1 . match2 . match3
2134 }
2135 }
2136}
2137*/
2138
2139
2140ParseFlaskAffixes(ItemDataAffixes)
2141{
2142 IfInString, ItemDataChunk, Unidentified
2143 {
2144 return ; Not interested in unidentified items
2145 }
2146
2147 Loop, Parse, ItemDataAffixes, `n, `r
2148 {
2149 If StrLen(A_LoopField) = 0
2150 {
2151 Continue ; Not interested in blank lines
2152 }
2153
2154
2155 IfInString, A_LoopField, `% increased Charge Recovery
2156 {
2157 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Prefix", ["20-40"], ""), A_Index)
2158 Continue
2159 }
2160 IfInString, A_LoopField, Maximum Charges
2161 {
2162 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Prefix", ["10-20"], ""), A_Index)
2163 Continue
2164 }
2165 IfInString, A_LoopField, `% reduced Charges used
2166 {
2167 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Prefix", ["20-25"], ""), A_Index)
2168 Continue
2169 }
2170 IfInString, A_LoopField, 50`% increased Amount Recovered
2171 {
2172 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Hybrid Prefix", ["50"], ""), A_Index)
2173 Continue
2174 }
2175 IfInString, A_LoopField, 33`% reduced Recovery Rate
2176 {
2177 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Hybrid Prefix", ["33"], ""), A_Index)
2178 Continue
2179 }
2180 IfInString, A_LoopField, 100`% increased Recovery when on Low Life
2181 {
2182 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Prefix", ["100"], ""), A_Index)
2183 Continue
2184 }
2185 IfInString, A_LoopField, 40`% increased Life Recovered
2186 {
2187 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Hybrid Prefix", ["40"], ""), A_Index)
2188 Continue
2189 }
2190 IfInString, A_LoopField, Removes 10`% of Life Recovered from Mana when used
2191 {
2192 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Hybrid Prefix", ["10"], ""), A_Index)
2193 Continue
2194 }
2195 IfInString, A_LoopField, 60`% increased Mana Recovered
2196 {
2197 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Hybrid Prefix", ["60"], ""), A_Index)
2198 Continue
2199 }
2200 IfInString, A_LoopField, Removes 15`% of Mana Recovered from Life when used
2201 {
2202 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Hybrid Prefix", ["15"], ""), A_Index)
2203 Continue
2204 }
2205 IfInString, A_LoopField, 25`% reduced Amount Recovered
2206 {
2207 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Hybrid Prefix", ["25"], ""), A_Index)
2208 Continue
2209 }
2210 IfInString, A_LoopField, Instant Recovery when on Low Life
2211 {
2212 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Hybrid Prefix", [""], ""), A_Index)
2213 Continue
2214 }
2215 IfInString, A_LoopField, 66`% reduced Amount Recovered
2216 {
2217 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Hybrid Prefix", ["66"], ""), A_Index)
2218 Continue
2219 }
2220 IfInString, A_LoopField, Instant Recovery
2221 {
2222 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Hybrid Prefix", [""], ""), A_Index)
2223 Continue
2224 }
2225 IfInString, A_LoopField, 50`% reduced Amount Recovered
2226 {
2227 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Hybrid Prefix", ["50"], ""), A_Index)
2228 Continue
2229 }
2230 IfInString, A_LoopField, 135`% increased Recovery Rate
2231 {
2232 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Hybrid Prefix", ["135"], ""), A_Index)
2233 Continue
2234 }
2235 IfInString, A_LoopField, 50`% of Recovery applied Instantly
2236 {
2237 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Hybrid Prefix", ["50"], "", False), A_Index)
2238 Continue
2239 }
2240 IfInString, A_LoopField, 50`% increased Recovery Rate
2241 {
2242 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Prefix", ["50"], ""), A_Index)
2243 Continue
2244 }
2245 IfInString, A_LoopField, `% increased Duration
2246 {
2247 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Prefix", ["30-40"], ""), A_Index)
2248 Continue
2249 }
2250 IfInString, A_LoopField, 25`% increased effect
2251 {
2252 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Hybrid Prefix", ["25"], ""), A_Index)
2253 Continue
2254 }
2255 IfInString, A_LoopField, 33`% reduced Duration
2256 {
2257 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Hybrid Prefix", ["33"], ""), A_Index)
2258 Continue
2259 }
2260 IfInString, A_LoopField, 20`% chance to gain a Flask Charge when you deal a Critical Strike
2261 {
2262 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Prefix", ["20"], ""), A_Index)
2263 Continue
2264 }
2265 IfInString, A_LoopField, Recharges 1 Charge when you deal a Critical Strike
2266 {
2267 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Prefix", ["Legacy"], ""), A_Index)
2268 Continue
2269 }
2270 IfInString, A_LoopField, Recharges 3 Charges when you take a Critical Strike
2271 {
2272 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Prefix", ["3"], ""), A_Index)
2273 Continue
2274 }
2275
2276
2277 IfInString, A_LoopField, Adds Knockback to Melee Attacks during Flask effect
2278 {
2279 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Suffix", [""], ""), A_Index)
2280 Continue
2281 }
2282 IfInString, A_LoopField, `% increased Armour during Flask effect
2283 {
2284 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Suffix", ["60-100"], ""), A_Index)
2285 Continue
2286 }
2287 IfInString, A_LoopField, `% increased Evasion Rating during Flask effect
2288 {
2289 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Suffix", ["60-100"], ""), A_Index)
2290 Continue
2291 }
2292 IfInString, A_LoopField, 0.4`% of Physical Attack Damage Leeched as Life during Flask effect
2293 {
2294 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Suffix", ["0.4"], ""), A_Index)
2295 Continue
2296 }
2297 IfInString, A_LoopField, 0.4`% of Physical Attack Damage Leeched as Mana during Flask effect
2298 {
2299 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Suffix", ["0.4"], ""), A_Index)
2300 Continue
2301 }
2302 IfInString, A_LoopField, `% of Life Recovery to Minions
2303 {
2304 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Suffix", ["40-60"], ""), A_Index)
2305 Continue
2306 }
2307 IfInString, A_LoopField, `% increased Movement Speed during Flask effect
2308 {
2309 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Suffix", ["20-30"], ""), A_Index)
2310 Continue
2311 }
2312 IfInString, A_LoopField, `% additional Elemental Resistances during Flask effect
2313 {
2314 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Suffix", ["20-30"], ""), A_Index)
2315 Continue
2316 }
2317 IfInString, A_LoopField, `% increased Block and Stun Recovery during Flask effect
2318 {
2319 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Suffix", ["40-60"], ""), A_Index)
2320 Continue
2321 }
2322 IfInString, A_LoopField, Immun
2323 {
2324 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Hybrid Suffix", [""], ""), A_Index)
2325 Continue
2326 }
2327 IfInString, A_LoopField, Removes
2328 {
2329 AppendAffixInfo(MakeAffixDetailLine(A_LoopField, "Hybrid Suffix", [""], ""), A_Index)
2330 Continue
2331 }
2332 }
2333}
2334
2335SetMapInfoLine(AffixType, ByRef MapAffixCount, EnumLabel="")
2336{
2337 Global AffixTotals
2338
2339 If (AffixType =="Prefix")
2340 {
2341 AffixTotals.NumPrefixes += 1
2342 }
2343 Else If (AffixType =="Suffix")
2344 {
2345 AffixTotals.NumSuffixes += 1
2346 }
2347
2348 MapAffixCount += 1
2349 AppendAffixInfo(MakeMapAffixLine(A_LoopField, MapAffixCount . EnumLabel), A_Index)
2350}
2351
2352ParseMapAffixes(ItemDataAffixes)
2353{
2354 Global Globals, Opts, AffixTotals, AffixLines
2355
2356 MapModWarn := class_EasyIni(userDirectory "\MapModWarnings.ini").Affixes
2357 ; FileRead, File_MapModWarn, %userDirectory%\MapModWarnings.txt
2358 ; MapModWarn := JSON.Load(File_MapModWarn)
2359
2360 ItemDataChunk := ItemDataAffixes
2361
2362 ItemBaseType := Item.BaseType
2363 ItemSubType := Item.SubType
2364
2365
2366 ; Reset the AffixLines "array" and other vars
2367 ResetAffixDetailVars()
2368
2369 IfInString, ItemDataChunk, Unidentified
2370 {
2371 return ; Not interested in unidentified items
2372 }
2373
2374 MapAffixCount := 0
2375 TempAffixCount := 0
2376
2377 Index_RareMonst :=
2378 Index_MonstSlowedTaunted :=
2379 Index_BossDamageAttackCastSpeed :=
2380 Index_BossLifeAoE :=
2381 Index_MonstChaosEleRes :=
2382 Index_MonstStunLife :=
2383 Index_MagicMonst :=
2384 Index_MonstCritChanceMult :=
2385 Index_PlayerDodgeMonstAccu :=
2386 Index_PlayerBlockArmour :=
2387 Index_CannotLeech :=
2388 Index_MonstMoveAttCastSpeed :=
2389
2390 Count_DmgMod := 0
2391 String_DmgMod := ""
2392
2393 Flag_TwoAdditionalProj := 0
2394 Flag_SkillsChain := 0
2395
2396 MapModWarnings := ""
2397
2398 Loop, Parse, ItemDataAffixes, `n, `r
2399 {
2400 If StrLen(A_LoopField) = 0
2401 {
2402 Continue ; Not interested in blank lines
2403 }
2404
2405 ; --- ONE LINE AFFIXES ---
2406
2407 If (RegExMatch(A_LoopField, "Area is inhabited by 2 additional Rogue Exiles|Area has increased monster variety"))
2408 {
2409 SetMapInfoLine("Prefix", MapAffixCount)
2410 Continue
2411 }
2412 If (RegExMatch(A_LoopField, "Area is inhabited by .*"))
2413 {
2414 SetMapInfoLine("Prefix", MapAffixCount)
2415 Continue
2416 }
2417 If (RegExMatch(A_LoopField, "Monsters deal \d+% extra Damage as (Fire|Cold|Lightning)"))
2418 {
2419 MapModWarnings .= MapModWarn.MonstExtraEleDmg ? "`nExtra Ele Damage" : ""
2420 SetMapInfoLine("Prefix", MapAffixCount)
2421 Count_DmgMod += 1
2422 String_DmgMod := String_DmgMod . ", Extra Ele"
2423 Continue
2424 }
2425 If (RegExMatch(A_LoopField, "Monsters reflect \d+% of Elemental Damage"))
2426 {
2427 MapModWarnings .= MapModWarn.EleReflect ? "`nEle reflect" : ""
2428 SetMapInfoLine("Prefix", MapAffixCount)
2429 Continue
2430 }
2431 If (RegExMatch(A_LoopField, "Monsters reflect \d+% of Physical Damage"))
2432 {
2433 MapModWarnings .= MapModWarn.PhysReflect ? "`nPhys reflect" : ""
2434 SetMapInfoLine("Prefix", MapAffixCount)
2435 Continue
2436 }
2437 If (RegExMatch(A_LoopField, "\+\d+% Monster Physical Damage Reduction"))
2438 {
2439 MapModWarnings .= MapModWarn.MonstPhysDmgReduction ? "`nPhys Damage Reduction" : ""
2440 SetMapInfoLine("Prefix", MapAffixCount)
2441 Continue
2442 }
2443 If (RegExMatch(A_LoopField, "\d+% less effect of Curses on Monsters"))
2444 {
2445 MapModWarnings .= MapModWarn.MonstLessCurse ? "`nLess Curse Effect" : ""
2446 SetMapInfoLine("Prefix", MapAffixCount)
2447 Continue
2448 }
2449 If (RegExMatch(A_LoopField, "Monsters have a \d+% chance to avoid Poison, Blind, and Bleed"))
2450 {
2451 MapModWarnings .= MapModWarn.MonstAvoidPoisonBlindBleed ? "`nAvoid Poison/Blind/Bleed" : ""
2452 SetMapInfoLine("Prefix", MapAffixCount)
2453 Continue
2454 }
2455 If (RegExMatch(A_LoopField, "Monsters have a \d+% chance to cause Elemental Ailments on Hit"))
2456 {
2457 MapModWarnings .= MapModWarn.MonstCauseElementalAilments ? "`nCause Elemental Ailments" : ""
2458 SetMapInfoLine("Prefix", MapAffixCount)
2459 Continue
2460 }
2461 If (RegExMatch(A_LoopField, "\d+% increased Monster Damage"))
2462 {
2463 MapModWarnings .= MapModWarn.MonstIncrDmg ? "`nIncreased Damage" : ""
2464 SetMapInfoLine("Prefix", MapAffixCount)
2465 Continue
2466 }
2467 If (RegExMatch(A_LoopField, "Area contains many Totems"))
2468 {
2469 MapModWarnings .= MapModWarn.ManyTotems ? "`nTotems" : ""
2470 SetMapInfoLine("Prefix", MapAffixCount)
2471 Continue
2472 }
2473 If (RegExMatch(A_LoopField, "Monsters' skills Chain 2 additional times"))
2474 {
2475 MapModWarnings .= MapModWarn.MonstSkillsChain ? "`nSkills Chain" : ""
2476 SetMapInfoLine("Prefix", MapAffixCount)
2477 Flag_SkillsChain := 1
2478 Continue
2479 }
2480 If (RegExMatch(A_LoopField, "All Monster Damage from Hits always Ignites"))
2481 {
2482 MapModWarnings .= MapModWarn.MonstHitsIgnite ? "`nHits Ignite" : ""
2483 SetMapInfoLine("Prefix", MapAffixCount)
2484 Continue
2485 }
2486 If (RegExMatch(A_LoopField, "Slaying Enemies close together can attract monsters from Beyond"))
2487 {
2488 MapModWarnings .= MapModWarn.Beyond ? "`nBeyond" : ""
2489 SetMapInfoLine("Prefix", MapAffixCount)
2490 Continue
2491 }
2492 If (RegExMatch(A_LoopField, "Area contains two Unique Bosses"))
2493 {
2494 MapModWarnings .= MapModWarn.BossTwinned ? "`nTwinned Boss" : ""
2495 SetMapInfoLine("Prefix", MapAffixCount)
2496 Continue
2497 }
2498 If (RegExMatch(A_LoopField, "Monsters are Hexproof"))
2499 {
2500 MapModWarnings .= MapModWarn.MonstHexproof ? "`nHexproof" : ""
2501 SetMapInfoLine("Prefix", MapAffixCount)
2502 Continue
2503 }
2504 If (RegExMatch(A_LoopField, "Monsters fire 2 additional Projectiles"))
2505 {
2506 MapModWarnings .= MapModWarn.MonstTwoAdditionalProj ? "`nAdditional Projectiles" : ""
2507 SetMapInfoLine("Prefix", MapAffixCount)
2508 Flag_TwoAdditionalProj := 1
2509 Continue
2510 }
2511
2512
2513 If (RegExMatch(A_LoopField, "Players are Cursed with Elemental Weakness"))
2514 {
2515 MapModWarnings .= MapModWarn.EleWeakness ? "`nEle Weakness" : ""
2516 SetMapInfoLine("Suffix", MapAffixCount)
2517 Continue
2518 }
2519 If (RegExMatch(A_LoopField, "Players are Cursed with Enfeeble"))
2520 {
2521 MapModWarnings .= MapModWarn.Enfeeble ? "`nEnfeeble" : ""
2522 SetMapInfoLine("Suffix", MapAffixCount)
2523 Continue
2524 }
2525 If (RegExMatch(A_LoopField, "Players are Cursed with Temporal Chains"))
2526 {
2527 MapModWarnings .= MapModWarn.TempChains ? "`nTemp Chains" : ""
2528 SetMapInfoLine("Suffix", MapAffixCount)
2529 Continue
2530 }
2531 If (RegExMatch(A_LoopField, "Players are Cursed with Vulnerability"))
2532 {
2533 MapModWarnings .= MapModWarn.Vulnerability ? "`nVulnerability" : ""
2534 SetMapInfoLine("Suffix", MapAffixCount)
2535
2536 Count_DmgMod += 0.5
2537 String_DmgMod := String_DmgMod . ", Vuln"
2538 Continue
2539 }
2540 If (RegExMatch(A_LoopField, "Area has patches of burning ground"))
2541 {
2542 MapModWarnings .= MapModWarn.BurningGround ? "`nBurning ground" : ""
2543 SetMapInfoLine("Suffix", MapAffixCount)
2544 Continue
2545 }
2546 If (RegExMatch(A_LoopField, "Area has patches of chilled ground"))
2547 {
2548 MapModWarnings .= MapModWarn.ChilledGround ? "`nChilled ground" : ""
2549 SetMapInfoLine("Suffix", MapAffixCount)
2550 Continue
2551 }
2552 If (RegExMatch(A_LoopField, "Area has patches of shocking ground"))
2553 {
2554 MapModWarnings .= MapModWarn.ShockingGround ? "`nShocking ground" : ""
2555 SetMapInfoLine("Suffix", MapAffixCount)
2556
2557 Count_DmgMod += 0.5
2558 String_DmgMod := String_DmgMod . ", Shocking"
2559 Continue
2560 }
2561 If (RegExMatch(A_LoopField, "Area has patches of desecrated ground"))
2562 {
2563 MapModWarnings .= MapModWarn.DesecratedGround ? "`nDesecrated ground" : ""
2564 SetMapInfoLine("Suffix", MapAffixCount)
2565 Continue
2566 }
2567 If (RegExMatch(A_LoopField, "Players gain \d+% reduced Flask Charges"))
2568 {
2569 MapModWarnings .= MapModWarn.PlayerReducedFlaskCharge ? "`nReduced Flask Charges" : ""
2570 SetMapInfoLine("Suffix", MapAffixCount)
2571 Continue
2572 }
2573 If (RegExMatch(A_LoopField, "Monsters have \d+% increased Area of Effect"))
2574 {
2575 MapModWarnings .= MapModWarn.MonstIncrAoE ? "`nIncreased Monster AoE" : ""
2576 SetMapInfoLine("Suffix", MapAffixCount)
2577 Continue
2578 }
2579 If (RegExMatch(A_LoopField, "Players have \d+% less Area of Effect"))
2580 {
2581 MapModWarnings .= MapModWarn.PlayerLessAoE ? "`nLess Player AoE" : ""
2582 SetMapInfoLine("Suffix", MapAffixCount)
2583 Continue
2584 }
2585 If (RegExMatch(A_LoopField, "Monsters have \d+% chance to Avoid Elemental Ailments"))
2586 {
2587 MapModWarnings .= MapModWarn.MonstAvoidElementalAilments ? "`nMonsters Avoid Elemental Ailments" : ""
2588 SetMapInfoLine("Suffix", MapAffixCount)
2589 Continue
2590 }
2591 If (RegExMatch(A_LoopField, "Players have \d+% less Recovery Rate of Life and Energy Shield"))
2592 {
2593 MapModWarnings .= MapModWarn.LessRecovery ? "`nLess Recovery" : ""
2594 SetMapInfoLine("Suffix", MapAffixCount)
2595 Continue
2596 }
2597 If (RegExMatch(A_LoopField, "Monsters take \d+% reduced Extra Damage from Critical Strikes"))
2598 {
2599 MapModWarnings .= MapModWarn.MonstTakeReducedCritDmg ? "`nReduced Crit Damage" : ""
2600 SetMapInfoLine("Suffix", MapAffixCount)
2601 Continue
2602 }
2603 If (RegExMatch(A_LoopField, "-\d+% maximum Player Resistances"))
2604 {
2605 MapModWarnings .= MapModWarn.PlayerReducedMaxRes ? "`n-Max Res" : ""
2606 SetMapInfoLine("Suffix", MapAffixCount)
2607
2608 Count_DmgMod += 0.5
2609 String_DmgMod := String_DmgMod . ", -Max Res"
2610 Continue
2611 }
2612 If (RegExMatch(A_LoopField, "Players have Elemental Equilibrium"))
2613 {
2614 MapModWarnings .= MapModWarn.PlayerEleEquilibrium ? "`nEle Equilibrium" : ""
2615 SetMapInfoLine("Suffix", MapAffixCount)
2616 Continue
2617 }
2618 If (RegExMatch(A_LoopField, "Players have Point Blank"))
2619 {
2620 MapModWarnings .= MapModWarn.PlayerPointBlank ? "`nPoint Blank" : ""
2621 SetMapInfoLine("Suffix", MapAffixCount)
2622 Continue
2623 }
2624 If (RegExMatch(A_LoopField, "Monsters Poison on Hit"))
2625 {
2626 MapModWarnings .= MapModWarn.MonstHitsPoison ? "`nHits Poison" : ""
2627 SetMapInfoLine("Suffix", MapAffixCount)
2628 Continue
2629 }
2630 If (RegExMatch(A_LoopField, "Players cannot Regenerate Life, Mana or Energy Shield"))
2631 {
2632 MapModWarnings .= MapModWarn.NoRegen ? "`nNo Regen" : ""
2633 SetMapInfoLine("Suffix", MapAffixCount)
2634 Continue
2635 }
2636 If (RegExMatch(A_LoopField, "Monsters gain (an Endurance|a Frenzy|a Power) Charge on Hit"))
2637 {
2638 SetMapInfoLine("Suffix", MapAffixCount)
2639 Continue
2640 }
2641
2642
2643 ; --- SIMPLE TWO LINE AFFIXES ---
2644
2645
2646 If (RegExMatch(A_LoopField, "Rare Monsters each have a Nemesis Mod|\d+% more Rare Monsters"))
2647 {
2648 If (Not Index_RareMonst)
2649 {
2650 MapModWarnings .= MapModWarn.MonstRareNemesis ? "`nNemesis" : ""
2651 SetMapInfoLine("Prefix", MapAffixCount, "a")
2652 Index_RareMonst := MapAffixCount
2653 Continue
2654 }
2655 Else
2656 {
2657 AppendAffixInfo(MakeMapAffixLine(A_LoopField, Index_RareMonst . "b"), A_Index)
2658 Continue
2659 }
2660 }
2661 If (RegExMatch(A_LoopField, "Monsters cannot be slowed below base speed|Monsters cannot be Taunted"))
2662 {
2663 If (Not Index_MonstSlowedTaunted)
2664 {
2665 MapModWarnings .= MapModWarn.MonstNotSlowedTaunted ? "`nNot Slowed/Taunted" : ""
2666 SetMapInfoLine("Prefix", MapAffixCount, "a")
2667 Index_MonstSlowedTaunted := MapAffixCount
2668 Continue
2669 }
2670 Else
2671 {
2672 AppendAffixInfo(MakeMapAffixLine(A_LoopField, Index_MonstSlowedTaunted . "b"), A_Index)
2673 Continue
2674 }
2675 }
2676 If (RegExMatch(A_LoopField, "Unique Boss deals \d+% increased Damage|Unique Boss has \d+% increased Attack and Cast Speed"))
2677 {
2678 If (Not Index_BossDamageAttackCastSpeed)
2679 {
2680 MapModWarnings .= MapModWarn.BossDmgAtkCastSpeed ? "`nBoss Damage & Attack/Cast Speed" : ""
2681 SetMapInfoLine("Prefix", MapAffixCount, "a")
2682 Index_BossDamageAttackCastSpeed := MapAffixCount
2683 Continue
2684 }
2685 Else
2686 {
2687 AppendAffixInfo(MakeMapAffixLine(A_LoopField, Index_BossDamageAttackCastSpeed . "b"), A_Index)
2688 Continue
2689 }
2690 }
2691 If (RegExMatch(A_LoopField, "Unique Boss has \d+% increased Life|Unique Boss has \d+% increased Area of Effect"))
2692 {
2693 If (Not Index_BossLifeAoE)
2694 {
2695 MapModWarnings .= MapModWarn.BossLifeAoE ? "`nBoss Life & AoE" : ""
2696 SetMapInfoLine("Prefix", MapAffixCount, "a")
2697 Index_BossLifeAoE := MapAffixCount
2698 Continue
2699 }
2700 Else
2701 {
2702 AppendAffixInfo(MakeMapAffixLine(A_LoopField, Index_BossLifeAoE . "b"), A_Index)
2703 Continue
2704 }
2705 }
2706 If (RegExMatch(A_LoopField, "\+\d+% Monster Chaos Resistance|\+\d+% Monster Elemental Resistance"))
2707 {
2708 If (Not Index_MonstChaosEleRes)
2709 {
2710 MapModWarnings .= MapModWarn.MonstChaosEleRes ? "`nChaos/Ele Res" : ""
2711 SetMapInfoLine("Prefix", MapAffixCount, "a")
2712 Index_MonstChaosEleRes := MapAffixCount
2713 Continue
2714 }
2715 Else
2716 {
2717 AppendAffixInfo(MakeMapAffixLine(A_LoopField, Index_MonstChaosEleRes . "b"), A_Index)
2718 Continue
2719 }
2720 }
2721 If (RegExMatch(A_LoopField, "\d+% more Magic Monsters|Magic Monster Packs each have a Bloodline Mod"))
2722 {
2723 If (Not Index_MagicMonst)
2724 {
2725 MapModWarnings .= MapModWarn.MonstMagicBloodlines ? "`nBloodlines" : ""
2726 SetMapInfoLine("Suffix", MapAffixCount, "a")
2727 Index_MagicMonst := MapAffixCount
2728 Continue
2729 }
2730 Else
2731 {
2732 AppendAffixInfo(MakeMapAffixLine(A_LoopField, Index_MagicMonst . "b"), A_Index)
2733 Continue
2734 }
2735 }
2736 If (RegExMatch(A_LoopField, "Monsters have \d+% increased Critical Strike Chance|\+\d+% to Monster Critical Strike Multiplier"))
2737 {
2738 If (Not Index_MonstCritChanceMult)
2739 {
2740 MapModWarnings .= MapModWarn.MonstCritChanceMult ? "`nCrit Chance & Multiplier" : ""
2741 SetMapInfoLine("Suffix", MapAffixCount, "a")
2742
2743 Count_DmgMod += 1
2744 String_DmgMod := String_DmgMod . ", Crit"
2745 Index_MonstCritChanceMult := MapAffixCount
2746 Continue
2747 }
2748 Else
2749 {
2750 AppendAffixInfo(MakeMapAffixLine(A_LoopField, Index_MonstCritChanceMult . "b"), A_Index)
2751 Continue
2752 }
2753 }
2754 If (RegExMatch(A_LoopField, "Player Dodge chance is Unlucky|Monsters have \d+% increased Accuracy Rating"))
2755 {
2756 If (Not Index_PlayerDodgeMonstAccu)
2757 {
2758 MapModWarnings .= MapModWarn.PlayerDodgeMonstAccu ? "`nDodge unlucky / Monster Accuracy" : ""
2759 SetMapInfoLine("Suffix", MapAffixCount, "a")
2760 Index_PlayerDodgeMonstAccu := MapAffixCount
2761 Continue
2762 }
2763 Else
2764 {
2765 AppendAffixInfo(MakeMapAffixLine(A_LoopField, Index_PlayerDodgeMonstAccu . "b"), A_Index)
2766 Continue
2767 }
2768 }
2769 If (RegExMatch(A_LoopField, "Players have \d+% reduced Block Chance|Players have \d+% less Armour"))
2770 {
2771 If (Not Index_PlayerBlockArmour)
2772 {
2773 MapModWarnings .= MapModWarn.PlayerReducedBlockLessArmour ? "`nReduced Block / Less Armour" : ""
2774 SetMapInfoLine("Suffix", MapAffixCount, "a")
2775 Index_PlayerBlockArmour := MapAffixCount
2776 Continue
2777 }
2778 Else
2779 {
2780 AppendAffixInfo(MakeMapAffixLine(A_LoopField, Index_PlayerBlockArmour . "b"), A_Index)
2781 Continue
2782 }
2783 }
2784 If (RegExMatch(A_LoopField, "Cannot Leech Life from Monsters|Cannot Leech Mana from Monsters"))
2785 {
2786 If (Not Index_CannotLeech)
2787 {
2788 MapModWarnings .= MapModWarn.NoLeech ? "`nNo Leech" : ""
2789 SetMapInfoLine("Suffix", MapAffixCount, "a")
2790 Index_CannotLeech := MapAffixCount
2791 Continue
2792 }
2793 Else
2794 {
2795 AppendAffixInfo(MakeMapAffixLine(A_LoopField, Index_CannotLeech . "b"), A_Index)
2796 Continue
2797 }
2798 }
2799 ; Second part of this affix is further below at complex affixes
2800 If (RegExMatch(A_LoopField, "Monsters cannot be Stunned"))
2801 {
2802 MapModWarnings .= MapModWarn.MonstNotStunned ? "`nNot Stunned" : ""
2803
2804 If (Not Index_MonstStunLife)
2805 {
2806 SetMapInfoLine("Prefix", MapAffixCount, "a")
2807 Index_MonstStunLife := MapAffixCount
2808 Continue
2809 }
2810 Else
2811 {
2812 AppendAffixInfo(MakeMapAffixLine(A_LoopField, Index_MonstStunLife . "b"), A_Index)
2813 Continue
2814 }
2815 }
2816
2817 ; --- SIMPLE THREE LINE AFFIXES ---
2818
2819
2820 If (RegExMatch(A_LoopField, "\d+% increased Monster Movement Speed|\d+% increased Monster Attack Speed|\d+% increased Monster Cast Speed"))
2821 {
2822 If (Not Index_MonstMoveAttCastSpeed)
2823 {
2824 MapModWarnings .= MapModWarn.MonstMoveAtkCastSpeed ? "`nMove/Attack/Cast Speed" : ""
2825
2826 Count_DmgMod += 0.5
2827 String_DmgMod := String_DmgMod . ", Move/Attack/Cast"
2828
2829 MapAffixCount += 1
2830 Index_MonstMoveAttCastSpeed := MapAffixCount . "a"
2831 AffixTotals.NumPrefixes += 1
2832 AppendAffixInfo(MakeMapAffixLine(A_LoopField, Index_MonstMoveAttCastSpeed), A_Index)
2833 Continue
2834 }
2835 Else If InStr(Index_MonstMoveAttCastSpeed, "a")
2836 {
2837 Index_MonstMoveAttCastSpeed := StrReplace(Index_MonstMoveAttCastSpeed, "a", "b")
2838 AppendAffixInfo(MakeMapAffixLine(A_LoopField, Index_MonstMoveAttCastSpeed), A_Index)
2839 Continue
2840 }
2841 Else If InStr(Index_MonstMoveAttCastSpeed, "b")
2842 {
2843 Index_MonstMoveAttCastSpeed := StrReplace(Index_MonstMoveAttCastSpeed, "b", "c")
2844 AppendAffixInfo(MakeMapAffixLine(A_LoopField, Index_MonstMoveAttCastSpeed), A_Index)
2845 Continue
2846 }
2847 }
2848
2849
2850 ; --- COMPLEX AFFIXES ---
2851
2852 ; Pure life: (20-29)/(30-39)/(40-49)% more Monster Life
2853 ; Hybrid mod: (15-19)/(20-24)/(25-30)% more Monster Life, Monsters cannot be Stunned
2854
2855 If (RegExMatch(A_LoopField, "(\d+)% more Monster Life", match))
2856 {
2857 RegExMonsterLife := match1
2858 MapModWarnings .= MapModWarn.MonstMoreLife ? "`nMore Life" : ""
2859
2860 RegExMatch(ItemData.FullText, "Map Tier: (\d+)", RegExMapTier)
2861
2862 ; only hybrid mod
2863 If ((RegExMapTier1 >= 11 and RegExMonsterLife <= 30) or (RegExMapTier1 >= 6 and RegExMonsterLife <= 24) or RegExMonsterLife <= 19)
2864 {
2865 If (Not Index_MonstStunLife)
2866 {
2867 MapAffixCount += 1
2868 Index_MonstStunLife := MapAffixCount
2869 AffixTotals.NumPrefixes += 1
2870 AppendAffixInfo(MakeMapAffixLine(A_LoopField, MapAffixCount . "a"), A_Index)
2871 Continue
2872 }
2873 Else
2874 {
2875 AppendAffixInfo(MakeMapAffixLine(A_LoopField, Index_MonstStunLife . "b"), A_Index)
2876 Continue
2877 }
2878 }
2879
2880 ; pure life mod
2881 Else If ((RegExMapTier1 >= 11 and RegExMonsterLife <= 49) or (RegExMapTier1 >= 6 and RegExMonsterLife <= 39) or RegExMonsterLife <= 29)
2882 {
2883 MapAffixCount += 1
2884 AffixTotals.NumPrefixes += 1
2885 AppendAffixInfo(MakeMapAffixLine(A_LoopField, MapAffixCount), A_Index)
2886 Continue
2887 }
2888
2889 ; both mods
2890 Else
2891 {
2892 If (Not Index_MonstStunLife)
2893 {
2894 TempAffixCount := MapAffixCount + 1
2895 MapAffixCount += 2
2896 Index_MonstStunLife := TempAffixCount
2897 AffixTotals.NumPrefixes += 2
2898 AppendAffixInfo(MakeMapAffixLine(A_LoopField, TempAffixCount . "a+" . MapAffixCount), A_Index)
2899 Continue
2900 }
2901 Else
2902 {
2903 MapAffixCount += 1
2904 AffixTotals.NumPrefixes += 1
2905 AppendAffixInfo(MakeMapAffixLine(A_LoopField, Index_MonstStunLife . "b+" . MapAffixCount), A_Index)
2906 Continue
2907 }
2908 }
2909 }
2910 }
2911
2912 If (Flag_TwoAdditionalProj and Flag_SkillsChain)
2913 {
2914 MapModWarnings := MapModWarnings . "`nAdditional Projectiles & Skills Chain"
2915 }
2916
2917 If (Count_DmgMod >= 1.5)
2918 {
2919 If (MapModWarn.MultiDmgWarning)
2920 {
2921 String_DmgMod := SubStr(String_DmgMod, 3)
2922 MapModWarnings := MapModWarnings . "`nMulti Damage: " . String_DmgMod
2923 }
2924 }
2925
2926 If (Not Opts.EnableMapModWarnings)
2927 {
2928 MapModWarnings := " disabled"
2929 }
2930
2931 return MapModWarnings
2932}
2933
2934ParseLeagueStoneAffixes(ItemDataAffixes, Item) {
2935 ; Placeholder
2936}
2937
2938LookupAffixAndSetInfoLine(DataSource, AffixType, ItemLevel, Value, AffixLineText:="", AffixLineNum:="")
2939{
2940 If ( ! AffixLineText){
2941 AffixLineText := A_LoopField
2942 }
2943 If ( ! AffixLineNum){
2944 AffixLineNum := A_Index
2945 }
2946
2947 AffixMode := "Native"
2948 If (AffixType ~= ".*Craft.*")
2949 {
2950 AffixMode := "Crafted"
2951 }
2952 Else If (AffixType ~= ".*Essence.*")
2953 {
2954 AffixMode := "Essence"
2955 }
2956
2957 ValueRanges := LookupAffixData(DataSource, ItemLevel, Value, CurrTier)
2958
2959 If (RegexMatch(AffixLineText, "Adds (\d+) to (\d+) (.+)", match) and ValueRanges[5])
2960 {
2961 NormalRow := MakeAffixDetailLine(AffixLineText, AffixType, ValueRanges, CurrTier)
2962
2963 avgDmg := NumFormatPointFiveOrInt((match1 + match2)/2)
2964 AvgLineText := " Average: " StrPad(avgDmg, 4, "left")
2965
2966 AppendAffixInfo([NormalRow, [AvgLineText, [ValueRanges[5], " ", ValueRanges[7], " "], ""]], AffixLineNum)
2967 }
2968 Else
2969 {
2970 AppendAffixInfo(MakeAffixDetailLine(AffixLineText, AffixType, ValueRanges, CurrTier), AffixLineNum)
2971 }
2972}
2973
2974/*
2975Finds possible tier combinations for a single value (thus from a single affix line) assuming that the value is a combination of two non-hybrid mods (so with no further clues).
2976*/
2977SolveTiers_Mod1Mod2(Value, Mod1DataArray, Mod2DataArray, ItemLevel)
2978{
2979 If ((Mod1DataArray = False) or (Mod2DataArray = False))
2980 {
2981 return False
2982 }
2983
2984 Mod1MinVal := Mod1DataArray[Mod1DataArray.MaxIndex()].min
2985 Mod2MinVal := Mod2DataArray[Mod2DataArray.MaxIndex()].min
2986
2987 If (Mod1MinVal + Mod2MinVal > Value)
2988 {
2989 ; Value is smaller than smallest possible sum, so it can't be composite
2990 return
2991 }
2992
2993 Mod1MinIlvl := Mod1DataArray[Mod1DataArray.MaxIndex()].ilvl
2994 Mod2MinIlvl := Mod2DataArray[Mod2DataArray.MaxIndex()].ilvl
2995
2996 If ( (Mod1MinIlvl > ItemLevel) or (Mod2MinIlvl > ItemLevel) )
2997 {
2998 ; The ItemLevel is too low to roll both affixes
2999 return
3000 }
3001
3002 ; Remove the minimal Mod2 value from Value and try to fit the remainder into Mod1 tiers.
3003 TmpValue := Value - Mod2MinVal
3004 Mod1Tiers := LookupTierByValue(TmpValue, Mod1DataArray, ItemLevel)
3005
3006 If (Mod1Tiers.Tier)
3007 {
3008 ; Tier exists, so we already found a working combination
3009 Mod1TopTier := Mod1Tiers.Tier
3010 Mod2BtmTier := Mod2DataArray.MaxIndex()
3011 }
3012 Else
3013 {
3014 ; Assuming the min portion for Mod2 was not enough, so look up the highest tier for Mod1, limited by ItemLevel
3015 Loop
3016 {
3017 If ( Mod1DataArray[A_Index].ilvl <= ItemLevel )
3018 {
3019 Mod1TopTier := A_Index
3020 Break
3021 }
3022 }
3023
3024 ; Remove the maximal Mod1 value from Value and try to fit the remainder into Mod2 tiers (giving us the bottom tier for Mod2)
3025 TmpValue := Value - Mod1DataArray[Mod1TopTier].max
3026 Mod2Tiers := LookupTierByValue(TmpValue, Mod2DataArray, ItemLevel)
3027
3028 If (Mod2Tiers.Tier)
3029 {
3030 ; Tier exists, we found a working combination
3031 Mod2BtmTier := Mod2Tiers.Tier
3032 }
3033 Else
3034 {
3035 ; Can't find a fitting tier. This should only happen when the sum of the max values for both mods is not enough to reach Value (legacy/essence cases)
3036 return "Legacy?"
3037 }
3038 }
3039
3040 ; Repeat the same the other way around.
3041
3042 TmpValue := Value - Mod1MinVal
3043 Mod2Tiers := LookupTierByValue(TmpValue, Mod2DataArray, ItemLevel)
3044
3045 If (Mod2Tiers.Tier)
3046 {
3047 Mod2TopTier := Mod2Tiers.Tier
3048 Mod1BtmTier := Mod1DataArray.MaxIndex()
3049 }
3050 Else
3051 {
3052 Loop
3053 {
3054 If ( Mod2DataArray[A_Index].ilvl <= ItemLevel )
3055 {
3056 Mod2TopTier := A_Index
3057 Break
3058 }
3059 }
3060
3061 TmpValue := Value - Mod2DataArray[Mod2TopTier].max
3062 Mod1Tiers := LookupTierByValue(TmpValue, Mod1DataArray, ItemLevel)
3063
3064 If (Mod1Tiers.Tier)
3065 {
3066 Mod1BtmTier := Mod1Tiers.Tier
3067 }
3068 Else
3069 {
3070 return "Legacy?"
3071 }
3072 }
3073
3074 return {"Mod1Top":Mod1TopTier,"Mod1Btm":Mod1BtmTier,"Mod2Top":Mod2TopTier,"Mod2Btm":Mod2BtmTier}
3075}
3076
3077/*
3078Finds possible tier combinations for the following case/paramenters:
3079 ModHybValue: There is one value which is a combination of a normal mod and a hybrid mod.
3080 HybOnlyValue: The other part of the hybrid mod is plain, so there is no second normal mod combining with the other hybrid part.
3081
3082 ModDataArray: DataArray for the normal mod.
3083 HybWithModDataArray: DataArray for the part of the hybrid mod that is combined with the normal mod.
3084 HybOnlyDataArray: DataArray for the plain part of the hybrid mod.
3085*/
3086SolveTiers_ModHyb(ModHybValue, HybOnlyValue, ModDataArray, HybWithModDataArray, HybOnlyDataArray, ItemLevel)
3087{
3088 If ((ModDataArray = False) or (HybWithModDataArray = False) or (HybOnlyDataArray = False))
3089 {
3090 return False
3091 }
3092
3093 HybTiers := LookupTierByValue(HybOnlyValue, HybOnlyDataArray, ItemLevel)
3094
3095 If (not(HybTiers.Tier))
3096 {
3097 ; HybOnlyValue can't be found as a bare hybrid mod.
3098 return
3099 }
3100
3101 ; Remove hybrid portion from ModHybValue
3102 RemainLo := ModHybValue - HybWithModDataArray[HybTiers.Tier].max
3103 RemainHi := ModHybValue - HybWithModDataArray[HybTiers.Tier].min
3104
3105 RemainHiTiers := LookupTierByValue(RemainHi, ModDataArray, ItemLevel)
3106 RemainLoTiers := LookupTierByValue(RemainLo, ModDataArray, ItemLevel)
3107
3108 If ( RemainHiTiers.Tier and RemainLoTiers.Tier )
3109 {
3110 ; Both RemainLo/Hi result in a possible tier
3111 ModTopTier := RemainHiTiers.Tier
3112 ModBtmTier := RemainLoTiers.Tier
3113 }
3114 Else If (RemainHiTiers.Tier)
3115 {
3116 ; Only RemainHi gives a possible tier, assign that tier to both Top/Btm output results
3117 ModTopTier := RemainHiTiers.Tier
3118 ModBtmTier := RemainHiTiers.Tier
3119 }
3120 Else If (RemainLoTiers.Tier)
3121 {
3122 ; Only RemainLo gives a possible tier, assign that tier to both Top/Btm output results
3123 ModTopTier := RemainLoTiers.Tier
3124 ModBtmTier := RemainLoTiers.Tier
3125 }
3126 Else If (RemainHi > ModDataArray[1].max)
3127 {
3128 ; Legacy cases
3129 ModTopTier := 0
3130 ModBtmTier := 0
3131 }
3132 Else
3133 {
3134 ; No matching tier found for both RemainLo/Hi values.
3135 return
3136 }
3137
3138 return {"ModTop":ModTopTier,"ModBtm":ModBtmTier,"Hyb":HybTiers.Tier}
3139}
3140
3141/*
3142Finds possible tier combinations for the following case/paramenters:
3143 There are three mods: two normal ones in different affix lines and a hybrid mod that combines with both.
3144
3145 Value1/Value2: Values.
3146 Mod1DataArray: DataArray for the normal mod part of Value1.
3147 Mod2DataArray: DataArray for the normal mod part of Value2.
3148 Hyb1DataArray: DataArray for the hybrid mod part of Value1.
3149 Hyb2DataArray: DataArray for the hybrid mod part of Value2.
3150*/
3151SolveTiers_Mod1Mod2Hyb(Value1, Value2, Mod1DataArray, Mod2DataArray, Hyb1DataArray, Hyb2DataArray, ItemLevel, TierCombinationArray=False)
3152{
3153 If ((Mod1DataArray = False) or (Mod2DataArray = False) or (Hyb1DataArray = False) or (Hyb2DataArray = False))
3154 {
3155 return False
3156 }
3157
3158 Mod1HybTiers := SolveTiers_Mod1Mod2(Value1, Mod1DataArray, Hyb1DataArray, ItemLevel)
3159 Mod2HybTiers := SolveTiers_Mod1Mod2(Value2, Mod2DataArray, Hyb2DataArray, ItemLevel)
3160
3161 If (not( IsObject(Mod1HybTiers) and IsObject(Mod2HybTiers) ))
3162 {
3163 ; Checking that both results are objects and thus contain tiers
3164 return
3165 }
3166
3167 ; Assign non-hybrid tiers into local variables because they might need to be corrected/overwritten.
3168 ; It is always a ".Mod1" key that contains the tier here due to the order of SolveTiers_Mod1Mod2() from above.
3169 Mod1TopTier := Mod1HybTiers.Mod1Top
3170 Mod1BtmTier := Mod1HybTiers.Mod1Btm
3171 Mod2TopTier := Mod2HybTiers.Mod1Top
3172 Mod2BtmTier := Mod2HybTiers.Mod1Btm
3173
3174 ; Get the overlap of both theoretical hybrid tier ranges, because the actual hybrid tier(s) must be valid for both.
3175 ; It is always a ".Mod2" key which contains the hybrid tier here due to the order of SolveTiers_Mod1Mod2() from above.
3176 ; Picking the worse (numerically greater) "top" and the better (numerically lesser) "btm".
3177 HybTopTier := (Mod1HybTiers.Mod2Top > Mod2HybTiers.Mod2Top) ? Mod1HybTiers.Mod2Top : Mod2HybTiers.Mod2Top
3178 HybBtmTier := (Mod1HybTiers.Mod2Btm < Mod2HybTiers.Mod2Btm) ? Mod1HybTiers.Mod2Btm : Mod2HybTiers.Mod2Btm
3179
3180 If (HybTopTier > HybBtmTier)
3181 {
3182 ; Check that HybTopTier is not worse (numerically higher) than HybBtmTier.
3183 return
3184 }
3185
3186 ; Check if any hybrid tier was actually changed and re-calculate the corresponding non-hybrid tier.
3187 If (Mod1HybTiers.Mod2Top != HybTopTier)
3188 {
3189 TmpValue := Value1 - Hyb1DataArray[HybTopTier].max
3190 Mod1BtmTier := LookupTierByValue(TmpValue, Mod1DataArray, ItemLevel).Tier
3191 }
3192 Else If (Mod2HybTiers.Mod2Top != HybTopTier)
3193 {
3194 TmpValue := Value2 - Hyb2DataArray[HybTopTier].max
3195 Mod2BtmTier := LookupTierByValue(TmpValue, Mod2DataArray, ItemLevel).Tier
3196 }
3197
3198 If (Mod1HybTiers.Mod2Btm != HybBtmTier)
3199 {
3200 TmpValue := Value1 - Hyb1DataArray[HybBtmTier].min
3201 Mod1TopTier := LookupTierByValue(TmpValue, Mod1DataArray, ItemLevel).Tier
3202 }
3203 Else If (Mod2HybTiers.Mod2Btm != HybBtmTier)
3204 {
3205 TmpValue := Value2 - Hyb2DataArray[HybBtmTier].min
3206 Mod2TopTier := LookupTierByValue(TmpValue, Mod2DataArray, ItemLevel).Tier
3207 }
3208
3209 If (TierCombinationArray = True)
3210 {
3211 TierArray := []
3212 Mod1Tier := Mod1TopTier
3213 While Mod1Tier <= Mod1BtmTier
3214 {
3215 Mod2Tier := Mod2TopTier
3216 While Mod2Tier <= Mod2BtmTier
3217 {
3218 HybTier := HybTopTier
3219 While HybTier <= HybBtmTier
3220 {
3221 If ( Mod1DataArray[Mod1Tier].min + Hyb1DataArray[HybTier].min < Value1
3222 and Mod1DataArray[Mod1Tier].max + Hyb1DataArray[HybTier].max > Value1
3223 and Mod2DataArray[Mod2Tier].min + Hyb2DataArray[HybTier].min < Value2
3224 and Mod2DataArray[Mod2Tier].max + Hyb2DataArray[HybTier].max > Value2)
3225 {
3226 TierArray.push([Mod1Tier, Mod2Tier, HybTier])
3227 }
3228 ++HybTier
3229 }
3230 ++Mod2Tier
3231 }
3232 ++Mod1Tier
3233 }
3234 }
3235
3236 return {"Mod1Top":Mod1TopTier,"Mod1Btm":Mod1BtmTier,"Mod2Top":Mod2TopTier,"Mod2Btm":Mod2BtmTier,"HybTop":HybTopTier,"HybBtm":HybBtmTier, "TierCombinationArray":TierArray}
3237}
3238
3239SolveTiers_Hyb1Hyb2(HybOverlapValue, Hyb1OnlyValue, Hyb2OnlyValue, Hyb1OverlapDataArray, Hyb2OverlapDataArray, Hyb1OnlyDataArray, Hyb2OnlyDataArray, ItemLevel)
3240{
3241 If ((Hyb1OverlapDataArray = False) or (Hyb2OverlapDataArray = False) or (Hyb1OnlyDataArray = False) or (Hyb2OnlyDataArray = False))
3242 {
3243 return False
3244 }
3245
3246 Hyb1Tiers := LookupTierByValue(Hyb1OnlyValue, Hyb1OnlyDataArray, ItemLevel)
3247 Hyb2Tiers := LookupTierByValue(Hyb2OnlyValue, Hyb2OnlyDataArray, ItemLevel)
3248
3249 If (not(Hyb1Tiers.Tier) or not(Hyb2Tiers.Tier))
3250 {
3251 ; Value can't be found as a bare hybrid mod.
3252 return
3253 }
3254
3255 OverlapValueMin := Hyb1OverlapDataArray[Hyb1Tiers.Tier].min + Hyb2OverlapDataArray[Hyb2Tiers.Tier].min
3256 OverlapValueMax := Hyb1OverlapDataArray[Hyb1Tiers.Tier].max + Hyb2OverlapDataArray[Hyb2Tiers.Tier].max
3257
3258 If (not( (OverlapValueMin < HybOverlapValue) and (HybOverlapValue < OverlapValueMax) ))
3259 {
3260 ; Combined Value can't be explained.
3261 return
3262 }
3263
3264 return {"Hyb1":Hyb1Tiers.Tier,"Hyb2":Hyb2Tiers.Tier}
3265}
3266
3267ReviseTierCombinationArray(TierCombinationArray, ReviseValue, ReviseIndex)
3268{
3269 RevisedTierCombinationArray := []
3270 Loop % TierCombinationArray.MaxIndex()
3271 {
3272 If (TierCombinationArray[A_Index][ReviseIndex] = ReviseValue)
3273 {
3274 RevisedTierCombinationArray.push(TierCombinationArray[A_Index])
3275 }
3276 }
3277 If (not RevisedTierCombinationArray[1][1]){
3278 return False
3279 }
3280 return RevisedTierCombinationArray
3281}
3282
3283GetTierRangesFromTierCombinationArray(TierCombinationArray)
3284{
3285 If (not IsObject(TierCombinationArray)){
3286 return False
3287 }
3288
3289 ResultArray := []
3290
3291 TierIdxToCheck := 1
3292 ; We loop over all first entries of all combinations, then over all second and so forth.
3293 ; Consequently the outer loop is the amount of entries per combination and the inner loop the amount of combinations.
3294 ; Check how many tier entries there are per tier combination by checking the first array.
3295 Loop % TierCombinationArray[1].MaxIndex()
3296 {
3297 TopTier := 100
3298 BtmTier := 0
3299 Loop % TierCombinationArray.MaxIndex()
3300 {
3301 If (TierCombinationArray[A_Index][TierIdxToCheck] < TopTier)
3302 {
3303 TopTier := TierCombinationArray[A_Index][TierIdxToCheck]
3304 }
3305 If (TierCombinationArray[A_Index][TierIdxToCheck] > BtmTier)
3306 {
3307 BtmTier := TierCombinationArray[A_Index][TierIdxToCheck]
3308 }
3309 }
3310 ResultArray.push([TopTier,BtmTier])
3311
3312 ++TierIdxToCheck
3313 }
3314
3315 return ResultArray
3316}
3317
3318FormatValueRangesAndIlvl(Mod1DataArray, Mod1Tiers, Mod2DataArray="", Mod2Tier="")
3319{
3320 Global Opts, Itemdata
3321
3322 result := []
3323
3324 If (IsObject(Mod2DataArray) and Mod2Tier)
3325 {
3326 If (IsObject(Mod1Tiers))
3327 {
3328 BtmMin := Mod1DataArray[Mod1Tiers[2]].min + Mod2DataArray[Mod2Tier].min
3329 BtmMax := Mod1DataArray[Mod1Tiers[2]].max + Mod2DataArray[Mod2Tier].max
3330 TopMin := Mod1DataArray[Mod1Tiers[1]].min + Mod2DataArray[Mod2Tier].min
3331 TopMax := Mod1DataArray[Mod1Tiers[1]].max + Mod2DataArray[Mod2Tier].max
3332
3333 result[1] := FormatMultiTierRange(BtmMin, BtmMax, TopMin, TopMax)
3334 result[2] := ""
3335 }
3336 Else
3337 {
3338 result[1] := Mod1DataArray[Mod1Tiers].min + Mod2DataArray[Mod2Tier].min "-" Mod1DataArray[Mod1Tiers].max + Mod2DataArray[Mod2Tier].max
3339 result[2] := Mod1DataArray[Mod1Tiers].ilvl
3340 }
3341
3342 result[3] := Mod1DataArray[Mod1DataArray.MaxIndex()].min + Mod2DataArray[Mod2DataArray.MaxIndex()].min "-" Mod1DataArray[1].max + Mod2DataArray[1].max
3343 result[4] := (Mod1DataArray[1].ilvl > Mod2DataArray[1].ilvl) ? Mod1DataArray[1].ilvl : Mod2DataArray[1].ilvl
3344 }
3345 Else
3346 {
3347 If (Mod1DataArray[1].maxHi) ; arbitrary pick to check whether mod has double ranges
3348 {
3349 If (IsObject(Mod1Tiers))
3350 {
3351 WorstBtmMin := Mod1DataArray[Mod1Tiers[2]].minLo
3352 WorstBtmMax := Mod1DataArray[Mod1Tiers[2]].minHi
3353 WorstTopMin := Mod1DataArray[Mod1Tiers[2]].maxLo
3354 WorstTopMax := Mod1DataArray[Mod1Tiers[2]].maxHi
3355 BestBtmMin := Mod1DataArray[Mod1Tiers[1]].minLo
3356 BestBtmMax := Mod1DataArray[Mod1Tiers[1]].minHi
3357 BestTopMin := Mod1DataArray[Mod1Tiers[1]].maxLo
3358 BestTopMax := Mod1DataArray[Mod1Tiers[1]].maxHi
3359
3360 TmpFullrange := WorstBtmMin "-" WorstBtmMax " to " WorstTopMin "-" WorstTopMax " " Opts.MultiTierRangeSeparator " " BestBtmMin "-" BestBtmMax " to " BestTopMin "-" BestTopMax
3361 Itemdata.SpecialCaseNotation .= "`nWe have a rare case of a double range mod with multi tier uncertainty here.`n The entire TierRange is: " TmpFullrange
3362
3363 result[1] := WorstBtmMin Opts.DoubleRangeSeparator WorstTopMin Opts.MultiTierRangeSeparator BestBtmMax Opts.DoubleRangeSeparator BestTopMax
3364 result[2] := " "
3365 }
3366 Else If (IsNum(Mod1Tiers))
3367 {
3368 BtmMin := Mod1DataArray[Mod1Tiers].minLo
3369 BtmMax := Mod1DataArray[Mod1Tiers].minHi
3370 TopMin := Mod1DataArray[Mod1Tiers].maxLo
3371 TopMax := Mod1DataArray[Mod1Tiers].maxHi
3372
3373 result[1] := FormatDoubleRanges(BtmMin, BtmMax, TopMin, TopMax)
3374 result[2] := Mod1DataArray[Mod1Tiers].ilvl
3375 result[5] := NumFormatPointFiveOrInt((BtmMin + TopMin)/2) "-" StrPad(NumFormatPointFiveOrInt((BtmMax + TopMax)/2), StrLen(TopMin) + 1 + StrLen(TopMax) )
3376 }
3377 Else
3378 {
3379 result[1] := "n/a"
3380 result[2] := ""
3381 }
3382
3383 BtmMin := Mod1DataArray[Mod1DataArray.MaxIndex()].minLo
3384 BtmMax := Mod1DataArray[1].minHi
3385 TopMin := Mod1DataArray[Mod1DataArray.MaxIndex()].maxLo
3386 TopMax := Mod1DataArray[1].maxHi
3387
3388 If (Opts.OnlyCompactForTotalColumn and not Opts.UseCompactDoubleRanges){
3389 result[3] := FormatDoubleRanges(BtmMin, BtmMax, TopMin, TopMax, "compact")
3390 }
3391 Else{
3392 result[3] := FormatDoubleRanges(BtmMin, BtmMax, TopMin, TopMax)
3393 }
3394
3395 result[4] := Mod1DataArray[1].ilvl
3396 result[7] := NumFormatPointFiveOrInt((BtmMin + TopMin)/2) "-" StrPad(NumFormatPointFiveOrInt((BtmMax + TopMax)/2), StrLen(TopMin) + 1 + StrLen(TopMax) )
3397 }
3398 Else
3399 {
3400 If (IsObject(Mod1Tiers))
3401 {
3402 BtmMin := Mod1DataArray[Mod1Tiers[2]].min
3403 BtmMax := Mod1DataArray[Mod1Tiers[2]].max
3404 TopMin := Mod1DataArray[Mod1Tiers[1]].min
3405 TopMax := Mod1DataArray[Mod1Tiers[1]].max
3406
3407 result[1] := FormatMultiTierRange(BtmMin, BtmMax, TopMin, TopMax)
3408 result[2] := ""
3409 }
3410 Else If (IsNum(Mod1Tiers))
3411 {
3412 result[1] := Mod1DataArray[Mod1Tiers].values
3413 result[2] := Mod1DataArray[Mod1Tiers].ilvl
3414 }
3415 Else
3416 {
3417 result[1] := "n/a"
3418 result[2] := ""
3419 }
3420
3421 result[3] := Mod1DataArray[Mod1DataArray.MaxIndex()].min "-" Mod1DataArray[1].max
3422 result[4] := Mod1DataArray[1].ilvl
3423 }
3424 }
3425
3426 If (Mod1Tiers = 0 or Mod1Tiers[1] = 0 or Mod1Tiers[2] = 0 or Mod2Tier = 0){
3427 result[1] := "Legacy?"
3428 }
3429 return result
3430}
3431
3432
3433FormatValueRangesAndIlvl_MultiTiers(Value, Mod1DataArray, Mod2DataArray, Mod1TopTier, Mod1BtmTier, Mod2TopTier, Mod2BtmTier)
3434{
3435 If ( (Mod1TopTier = Mod1BtmTier) and (Mod2TopTier = Mod2BtmTier) )
3436 {
3437 result := []
3438 result[1] := (Mod1DataArray[Mod1TopTier].min + Mod2DataArray[Mod2BtmTier].min) "-" (Mod1DataArray[Mod1TopTier].max + Mod2DataArray[Mod2BtmTier].max)
3439 result[2] := ""
3440 result[3] := Mod1DataArray[Mod1DataArray.MaxIndex()].min + Mod2DataArray[Mod2DataArray.MaxIndex()].min "-" Mod1DataArray[1].max + Mod2DataArray[1].max
3441 result[4] := (Mod1DataArray[1].ilvl > Mod2DataArray[1].ilvl) ? Mod1DataArray[1].ilvl : Mod2DataArray[1].ilvl
3442
3443 return result
3444 }
3445
3446 ; Concept:
3447 ; Inner loop t: Start with Mod1TopTier and get worse (numerically higher).
3448 ; Outer loop b: Start with Mod2BtmTier and get better (numerically lower).
3449 ; Record lowest/highest range found where value still fits. The outer values take precedence here,
3450 ; so RangeTop is primarily defined by RangeTopMax (e.g. 100-109 is "higher" than 105-108) and RangeBtm by RangeBtmMin (e.g. 50-59 is "lower" than 51-54).
3451
3452 RangeBtmMin := Mod1DataArray[Mod1TopTier].min + Mod2DataArray[Mod2BtmTier].min
3453 RangeBtmMax := Mod1DataArray[Mod1TopTier].max + Mod2DataArray[Mod2BtmTier].max
3454 RangeTopMin := RangeBtmMin
3455 RangeTopMax := RangeBtmMax
3456
3457 ; Start at t+1 because we assigned the t/b combination as the initial range values already.
3458 t := Mod1TopTier + 1
3459 b := Mod2BtmTier
3460
3461 ; We don't need to check t starting from Mod1TopTier each loop, we can use the best/first t from the previous b loop.
3462 t_RestartIndex := Mod1TopTier
3463
3464 While(b >= Mod2TopTier)
3465 {
3466 While(t <= Mod1BtmTier)
3467 {
3468 TmpMin := Mod1DataArray[t].min + Mod2DataArray[b].min
3469 TmpMax := Mod1DataArray[t].max + Mod2DataArray[b].max
3470 ; Increment t here because we might break/continue the loop just below.
3471 ++t
3472
3473 If (not( (TmpMin <= Value) and (Value <= TmpMax) ))
3474 {
3475 If (t_RestartIndex)
3476 {
3477 ; Value not within Tmp-Range, but we have a t_RestartIndex, so we had matching Tmp-Ranges for this b value
3478 ; but the Tmp-Ranges are getting too low for "Value" now. Break t loop to check next b, start t at t_RestartIndex and set t_RestartIndex to 0 (see loop end).
3479 break
3480 }
3481 ; Value not within Tmp-Range and we have no t_RestartIndex, so the Tmp-Range is still too high for "Value". Restart t loop with continue.
3482 Else continue
3483 }
3484
3485 If (not(t_RestartIndex))
3486 {
3487 ; Value is within Tmp-Range (because section above was passed) and we have no t_RestartIndex yet.
3488 ; This means this is the first matching range found for this b. Record this t (and remove the increment from the loop start).
3489 t_RestartIndex := (t-1)
3490 }
3491
3492 If (TmpMin <= RangeBtmMin)
3493 {
3494 If (TmpMin < RangeBtmMin)
3495 {
3496 RangeBtmMin := TmpMin
3497 RangeBtmMax := TmpMax
3498 }
3499 Else If (TmpMax < RangeBtmMax)
3500 {
3501 RangeBtmMax := TmpMax
3502 }
3503 }
3504
3505 If (TmpMax >= RangeTopMax)
3506 {
3507 If (TmpMax > RangeTopMax)
3508 {
3509 RangeTopMax := TmpMax
3510 RangeTopMin := TmpMin
3511 }
3512 Else If (TmpMin > RangeTopMin)
3513 {
3514 RangeTopMin := TmpMin
3515 }
3516 }
3517 }
3518
3519 --b
3520 t := t_RestartIndex
3521 t_RestartIndex := 0
3522 }
3523
3524 result := []
3525 result[1] := FormatMultiTierRange(RangeBtmMin, RangeBtmMax, RangeTopMin, RangeTopMax)
3526 result[2] := ""
3527 result[3] := Mod1DataArray[Mod1DataArray.MaxIndex()].min + Mod2DataArray[Mod2DataArray.MaxIndex()].min "-" Mod1DataArray[1].max + Mod2DataArray[1].max
3528 result[4] := (Mod1DataArray[1].ilvl > Mod2DataArray[1].ilvl) ? Mod1DataArray[1].ilvl : Mod2DataArray[1].ilvl
3529
3530 return result
3531}
3532
3533SolveAffixes_HybBase_FlatDefLife(Keyname, LineNumDef1, LineNumDef2, LineNumLife, ValueDef1, ValueDef2, ValueLife, Filename_HybDualDef_Def1, Filename_HybDualDef_Def2, Filename_Life, Filename_HybDefLife_Def1, Filename_HybDefLife_Def2, Filename_HybDefLife_Life, ItemLevel)
3534{
3535 Global Itemdata
3536 Itemdata.UncertainAffixes[Keyname] := {}
3537
3538 DualDef_D1_DataArray := ArrayFromDatafile(Filename_HybDualDef_Def1)
3539 DualDef_D2_DataArray := ArrayFromDatafile(Filename_HybDualDef_Def2)
3540 DefLife_D1_DataArray := ArrayFromDatafile(Filename_HybDefLife_Def1)
3541 DefLife_D2_DataArray := ArrayFromDatafile(Filename_HybDefLife_Def2)
3542 Life_DataArray := ArrayFromDatafile(Filename_Life)
3543 DefLife_Li_DataArray := ArrayFromDatafile(Filename_HybDefLife_Life)
3544
3545 DualDef_D1_Tiers := LookupTierByValue(ValueDef1, DualDef_D1_DataArray, ItemLevel)
3546 DualDef_D2_Tiers := LookupTierByValue(ValueDef2, DualDef_D2_DataArray, ItemLevel)
3547 DefLife_D1_Tiers := LookupTierByValue(ValueDef1, DefLife_D1_DataArray, ItemLevel)
3548 DefLife_D2_Tiers := LookupTierByValue(ValueDef2, DefLife_D2_DataArray, ItemLevel)
3549 LifeTiers := LookupTierByValue(ValueLife, Life_DataArray, ItemLevel)
3550 DefLife_Li_Tiers := LookupTierByValue(ValueLife, DefLife_Li_DataArray, ItemLevel)
3551
3552 Def1LifeTiers := SolveTiers_Hyb1Hyb2(ValueDef1, ValueDef2, ValueLife, DualDef_D1_DataArray, DefLife_D1_DataArray, DualDef_D2_DataArray, DefLife_Li_DataArray, ItemLevel)
3553 Def2LifeTiers := SolveTiers_Hyb1Hyb2(ValueDef2, ValueDef1, ValueLife, DualDef_D2_DataArray, DefLife_D2_DataArray, DualDef_D1_DataArray, DefLife_Li_DataArray, ItemLevel)
3554
3555
3556 /* --------- Overlap1Case --------- --------- Overlap2Case ---------
3557 ValueDef1 = DefLife_D1 + DualDef_D1 (DualDef_D1)
3558 ValueDef2 = (DualDef_D2) DefLife_D2 + DualDef_D2
3559 ValueLife = Life + DefLife_Li Life + DefLife_Li
3560 */
3561 Overlap1Tiers := SolveTiers_Mod1Mod2Hyb(ValueDef1, ValueLife, DualDef_D1_DataArray, Life_DataArray, DefLife_D1_DataArray, DefLife_Li_DataArray, ItemLevel, True)
3562 Overlap2Tiers := SolveTiers_Mod1Mod2Hyb(ValueDef2, ValueLife, DualDef_D2_DataArray, Life_DataArray, DefLife_D2_DataArray, DefLife_Li_DataArray, ItemLevel, True)
3563
3564
3565 If (IsObject(Overlap1Tiers))
3566 {
3567 Overlap1TierCombinationArray := ReviseTierCombinationArray(Overlap1Tiers.TierCombinationArray, DualDef_D2_Tiers.Tier, 1)
3568
3569 If (Overlap1TierCombinationArray = False){
3570 Overlap1Tiers := False
3571 }
3572 Else
3573 {
3574 NewOverlap1Tiers := GetTierRangesFromTierCombinationArray(Overlap1TierCombinationArray)
3575
3576 Overlap1Tiers := {}
3577 Overlap1Tiers.Mod1Top := NewOverlap1Tiers[1][1]
3578 Overlap1Tiers.Mod1Btm := NewOverlap1Tiers[1][2]
3579 Overlap1Tiers.Mod2Top := NewOverlap1Tiers[2][1]
3580 Overlap1Tiers.Mod2Btm := NewOverlap1Tiers[2][2]
3581 Overlap1Tiers.HybTop := NewOverlap1Tiers[3][1]
3582 Overlap1Tiers.HybBtm := NewOverlap1Tiers[3][2]
3583 }
3584 }
3585
3586 If (IsObject(Overlap2Tiers))
3587 {
3588 Overlap2TierCombinationArray := ReviseTierCombinationArray(Overlap2Tiers.TierCombinationArray, DualDef_D1_Tiers.Tier, 1)
3589
3590 If (Overlap2TierCombinationArray = False){
3591 Overlap2Tiers := False
3592 }
3593 Else
3594 {
3595 NewOverlap2Tiers := GetTierRangesFromTierCombinationArray(Overlap2TierCombinationArray)
3596
3597 Overlap2Tiers := {}
3598 Overlap2Tiers.Mod1Top := NewOverlap2Tiers[1][1]
3599 Overlap2Tiers.Mod1Btm := NewOverlap2Tiers[1][2]
3600 Overlap2Tiers.Mod2Top := NewOverlap2Tiers[2][1]
3601 Overlap2Tiers.Mod2Btm := NewOverlap2Tiers[2][2]
3602 Overlap2Tiers.HybTop := NewOverlap2Tiers[3][1]
3603 Overlap2Tiers.HybBtm := NewOverlap2Tiers[3][2]
3604 }
3605 }
3606
3607
3608 If (DualDef_D1_Tiers.Tier and DualDef_D2_Tiers.Tier and (DualDef_D1_Tiers.Tier = DualDef_D2_Tiers.Tier) and LifeTiers.Tier)
3609 {
3610 ValueRange1 := FormatValueRangesAndIlvl(DualDef_D1_DataArray, DualDef_D1_Tiers.Tier)
3611 LineTxt1 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNumDef1].Text, "Hybrid Defence Prefix", ValueRange1, DualDef_D1_Tiers.Tier, False)
3612
3613 ValueRange2 := FormatValueRangesAndIlvl(DualDef_D2_DataArray, DualDef_D2_Tiers.Tier)
3614 LineTxt2 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNumDef2].Text, "Hybrid Defence Prefix", ValueRange2, DualDef_D2_Tiers.Tier, False)
3615
3616 ValueRange3 := FormatValueRangesAndIlvl(Life_DataArray, LifeTiers.Tier)
3617 LineTxt3 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNumLife].Text, "Prefix", ValueRange3, LifeTiers.Tier, False)
3618
3619 Itemdata.UncertainAffixes[Keyname]["1_ModHyb"] := [2, 0, LineNumDef1, LineTxt1, LineNumDef2, LineTxt2, LineNumLife, LineTxt3]
3620 }
3621
3622 If (IsObject(Def1LifeTiers) or IsObject(Def2LifeTiers))
3623 {
3624 If (IsObject(Def1LifeTiers))
3625 {
3626 ValueRange1 := FormatValueRangesAndIlvl(DualDef_D1_DataArray, Def1LifeTiers.Hyb1, DefLife_D1_DataArray, Def1LifeTiers.Hyb2)
3627 LineTxt1 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNumDef1].Text, ["Hybrid Defence Prefix", "Hybrid Prefix"], ValueRange1, [Def1LifeTiers.Hyb1, Def1LifeTiers.Hyb2], False)
3628
3629 ValueRange2 := FormatValueRangesAndIlvl(DualDef_D2_DataArray, Def1LifeTiers.Hyb1)
3630 LineTxt2 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNumDef2].Text, "Hybrid Defence Prefix", ValueRange2, Def1LifeTiers.Hyb1, False)
3631
3632 ValueRange3 := FormatValueRangesAndIlvl(DefLife_Li_DataArray, Def1LifeTiers.Hyb2)
3633 LineTxt3 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNumLife].Text, "Hybrid Prefix", ValueRange3, Def1LifeTiers.Hyb2, False)
3634 }
3635
3636 If (IsObject(Def2LifeTiers))
3637 {
3638 ValueRange4 := FormatValueRangesAndIlvl(DualDef_D1_DataArray, Def2LifeTiers.Hyb1)
3639 LineTxt4 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNumDef2].Text, "Hybrid Defence Prefix", ValueRange4, Def2LifeTiers.Hyb1, False)
3640
3641 ValueRange5 := FormatValueRangesAndIlvl(DualDef_D2_DataArray, Def2LifeTiers.Hyb1, DefLife_D2_DataArray, Def2LifeTiers.Hyb2)
3642 LineTxt5 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNumDef1].Text, ["Hybrid Defence Prefix", "Hybrid Prefix"], ValueRange5, [Def2LifeTiers.Hyb1, Def2LifeTiers.Hyb2], False)
3643
3644 ValueRange6 := FormatValueRangesAndIlvl(DefLife_Li_DataArray, Def2LifeTiers.Hyb2)
3645 LineTxt6 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNumLife].Text, "Hybrid Prefix", ValueRange6, Def2LifeTiers.Hyb2, False)
3646 }
3647
3648 If (IsObject(Def1LifeTiers) and IsObject(Def2LifeTiers))
3649 {
3650 Itemdata.UncertainAffixes[Keyname]["2_Hyb1Hyb2"] := [2, 0, LineNumDef1, LineTxt1, LineNumDef2, LineTxt2, LineNumLife, LineTxt3, LineNumDef1, LineTxt4, LineNumDef2, LineTxt5, LineNumLife, LineTxt6]
3651 }
3652 Else If (IsObject(Def1LifeTiers))
3653 {
3654 Itemdata.UncertainAffixes[Keyname]["2_Hyb1Hyb2"] := [2, 0, LineNumDef1, LineTxt1, LineNumDef2, LineTxt2, LineNumLife, LineTxt3]
3655 }
3656 Else If (IsObject(Def2LifeTiers))
3657 {
3658 Itemdata.UncertainAffixes[Keyname]["2_Hyb1Hyb2"] := [2, 0, LineNumDef1, LineTxt4, LineNumDef2, LineTxt5, LineNumLife, LineTxt6]
3659 }
3660 }
3661
3662 If (IsObject(Overlap1Tiers) or IsObject(Overlap2Tiers))
3663 {
3664 If (IsObject(Overlap1Tiers))
3665 {
3666 ValueRange1 := FormatValueRangesAndIlvl_MultiTiers(ValueDef1, DualDef_D1_DataArray, DefLife_D1_DataArray, Overlap1Tiers.Mod1Top, Overlap1Tiers.Mod1Btm, Overlap1Tiers.HybTop, Overlap1Tiers.HybBtm)
3667 LineTxt1 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNumDef1].Text, ["Hybrid Defence Prefix", "Hybrid Prefix"], ValueRange1, [[Overlap1Tiers.Mod1Top, Overlap1Tiers.Mod1Btm], [Overlap1Tiers.HybTop, Overlap1Tiers.HybBtm]], False)
3668
3669 ValueRange2 := FormatValueRangesAndIlvl(DualDef_D2_DataArray, DualDef_D2_Tiers.Tier)
3670 LineTxt2 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNumDef2].Text, "Hybrid Defence Prefix", ValueRange2, DualDef_D2_Tiers.Tier, False)
3671
3672 ValueRange3 := FormatValueRangesAndIlvl_MultiTiers(ValueLife, Life_DataArray, DefLife_Li_DataArray, Overlap1Tiers.Mod2Top, Overlap1Tiers.Mod2Btm, Overlap1Tiers.HybTop, Overlap1Tiers.HybBtm)
3673 LineTxt3 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNumLife].Text, ["Prefix", "Hybrid Prefix"], ValueRange3, [[Overlap1Tiers.Mod2Top, Overlap1Tiers.Mod2Btm], [Overlap1Tiers.HybTop, Overlap1Tiers.HybBtm]], False)
3674 }
3675
3676 If (IsObject(Overlap2Tiers))
3677 {
3678 ValueRange4 := FormatValueRangesAndIlvl(DualDef_D1_DataArray, DualDef_D1_Tiers.Tier)
3679 LineTxt4 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNumDef1].Text, "Hybrid Defence Prefix", ValueRange4, DualDef_D1_Tiers.Tier, False)
3680
3681 ValueRange5 := FormatValueRangesAndIlvl_MultiTiers(ValueDef2, DualDef_D2_DataArray, DefLife_D2_DataArray, Overlap2Tiers.Mod1Top, Overlap2Tiers.Mod1Btm, Overlap2Tiers.HybTop, Overlap2Tiers.HybBtm)
3682 LineTxt5 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNumDef1].Text, ["Hybrid Defence Prefix", "Hybrid Prefix"], ValueRange5, [[Overlap2Tiers.Mod1Top, Overlap2Tiers.Mod1Btm], [Overlap2Tiers.HybTop, Overlap2Tiers.HybBtm]], False)
3683
3684 ValueRange6 := FormatValueRangesAndIlvl_MultiTiers(ValueLife, Life_DataArray, DefLife_Li_DataArray, Overlap2Tiers.Mod2Top, Overlap2Tiers.Mod2Btm, Overlap2Tiers.HybTop, Overlap2Tiers.HybBtm)
3685 LineTxt6 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNumLife].Text, ["Prefix", "Hybrid Prefix"], ValueRange6, [[Overlap2Tiers.Mod2Top, Overlap2Tiers.Mod2Btm], [Overlap2Tiers.HybTop, Overlap2Tiers.HybBtm]], False)
3686 }
3687
3688 If (IsObject(Overlap1Tiers) and IsObject(Overlap2Tiers))
3689 {
3690 Itemdata.UncertainAffixes[Keyname]["3_ModHyb1Hyb2"] := [3, 0, LineNumDef1, LineTxt1, LineNumDef2, LineTxt2, LineNumLife, LineTxt3, LineNumDef1, LineTxt4, LineNumDef2, LineTxt5, LineNumLife, LineTxt6]
3691 }
3692 Else If (IsObject(Overlap1Tiers))
3693 {
3694 Itemdata.UncertainAffixes[Keyname]["3_ModHyb1Hyb2"] := [3, 0, LineNumDef1, LineTxt1, LineNumDef2, LineTxt2, LineNumLife, LineTxt3]
3695 }
3696 Else If (IsObject(Overlap2Tiers))
3697 {
3698 Itemdata.UncertainAffixes[Keyname]["3_ModHyb1Hyb2"] := [3, 0, LineNumDef1, LineTxt4, LineNumDef2, LineTxt5, LineNumLife, LineTxt6]
3699 }
3700 }
3701}
3702
3703SolveAffixes_Mod1Mod2Hyb(Keyname, LineNum1, LineNum2, Value1, Value2, Mod1Type, Mod2Type, HybType, Filename1, Filename2, FilenameHyb1, FilenameHyb2, ItemLevel)
3704{
3705 Global Itemdata
3706 Itemdata.UncertainAffixes[Keyname] := {}
3707
3708 Mod1DataArray := ArrayFromDatafile(Filename1)
3709 Mod2DataArray := ArrayFromDatafile(Filename2)
3710 Hyb1DataArray := ArrayFromDatafile(FilenameHyb1)
3711 Hyb2DataArray := ArrayFromDatafile(FilenameHyb2)
3712
3713 Mod1Tiers := LookupTierByValue(Value1, Mod1DataArray, ItemLevel)
3714 Mod2Tiers := LookupTierByValue(Value2, Mod2DataArray, ItemLevel)
3715 Hyb1Tiers := LookupTierByValue(Value1, Hyb1DataArray, ItemLevel)
3716 Hyb2Tiers := LookupTierByValue(Value2, Hyb2DataArray, ItemLevel)
3717
3718 Mod1HybTiers := SolveTiers_ModHyb(Value1, Value2, Mod1DataArray, Hyb1DataArray, Hyb2DataArray, ItemLevel)
3719 Mod2HybTiers := SolveTiers_ModHyb(Value2, Value1, Mod2DataArray, Hyb2DataArray, Hyb1DataArray, ItemLevel)
3720
3721 Mod1Mod2HybTiers := SolveTiers_Mod1Mod2Hyb(Value1, Value2, Mod1DataArray, Mod2DataArray, Hyb1DataArray, Hyb2DataArray, ItemLevel)
3722
3723
3724 If (Mod1Tiers.Tier and Mod2Tiers.Tier)
3725 {
3726 PrefixCount := 0
3727 SuffixCount := 0
3728 PrefixCount += (Mod1Type ~= "Prefix") ? 1 : 0
3729 PrefixCount += (Mod2Type ~= "Prefix") ? 1 : 0
3730 SuffixCount += (Mod1Type ~= "Suffix") ? 1 : 0
3731 SuffixCount += (Mod2Type ~= "Suffix") ? 1 : 0
3732
3733 ValueRange1 := FormatValueRangesAndIlvl(Mod1DataArray, Mod1Tiers.Tier)
3734 LineTxt1 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNum1].Text, Mod1Type, ValueRange1, Mod1Tiers.Tier, False)
3735
3736 ValueRange2 := FormatValueRangesAndIlvl(Mod2DataArray, Mod2Tiers.Tier)
3737 LineTxt2 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNum2].Text, Mod2Type, ValueRange2, Mod2Tiers.Tier, False)
3738
3739 Itemdata.UncertainAffixes[Keyname]["1_Mod1Mod2"] := [PrefixCount, SuffixCount, LineNum1, LineTxt1, LineNum2, LineTxt2]
3740 }
3741
3742 If (Hyb1Tiers.Tier and Hyb2Tiers.Tier and (Hyb1Tiers.Tier = Hyb2Tiers.Tier))
3743 {
3744 PrefixCount := 0
3745 SuffixCount := 0
3746 PrefixCount += (HybType ~= "Hybrid Prefix") ? 1 : 0
3747 SuffixCount += (HybType ~= "Hybrid Suffix") ? 1 : 0
3748
3749 ValueRange1 := FormatValueRangesAndIlvl(Hyb1DataArray, Hyb1Tiers.Tier)
3750 LineTxt1 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNum1].Text, HybType, ValueRange1, Hyb1Tiers.Tier, False)
3751
3752 ValueRange2 := FormatValueRangesAndIlvl(Hyb2DataArray, Hyb2Tiers.Tier)
3753 LineTxt2 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNum2].Text, HybType, ValueRange2, Hyb2Tiers.Tier, False)
3754
3755 Itemdata.UncertainAffixes[Keyname]["2_OnlyHyb"] := [PrefixCount, SuffixCount, LineNum1, LineTxt1, LineNum2, LineTxt2]
3756 }
3757
3758 If (IsObject(Mod1HybTiers))
3759 {
3760 PrefixCount := 0
3761 SuffixCount := 0
3762 PrefixCount += (Mod1Type ~= "Prefix") ? 1 : 0
3763 PrefixCount += (HybType ~= "Hybrid Prefix") ? 1 : 0
3764 SuffixCount += (Mod1Type ~= "Suffix") ? 1 : 0
3765 SuffixCount += (HybType ~= "Hybrid Suffix") ? 1 : 0
3766
3767 ValueRange1 := FormatValueRangesAndIlvl(Mod1DataArray, [Mod1HybTiers.ModTop, Mod1HybTiers.ModBtm], Hyb1DataArray, Mod1HybTiers.Hyb)
3768 LineTxt1 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNum1].Text, [Mod1Type, HybType], ValueRange1, [[Mod1HybTiers.ModTop, Mod1HybTiers.ModBtm], Mod1HybTiers.Hyb], False)
3769
3770 ValueRange2 := FormatValueRangesAndIlvl(Hyb2DataArray, Hyb2Tiers.Tier)
3771 LineTxt2 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNum2].Text, HybType, ValueRange2, Mod1HybTiers.Hyb, False)
3772
3773 Itemdata.UncertainAffixes[Keyname]["3_Mod1Hyb"] := [PrefixCount, SuffixCount, LineNum1, LineTxt1, LineNum2, LineTxt2]
3774 }
3775
3776 If (IsObject(Mod2HybTiers))
3777 {
3778 PrefixCount := 0
3779 SuffixCount := 0
3780 PrefixCount += (Mod2Type ~= "Prefix") ? 1 : 0
3781 PrefixCount += (HybType ~= "Hybrid Prefix") ? 1 : 0
3782 SuffixCount += (Mod2Type ~= "Suffix") ? 1 : 0
3783 SuffixCount += (HybType ~= "Hybrid Suffix") ? 1 : 0
3784
3785 ValueRange1 := FormatValueRangesAndIlvl(Hyb1DataArray, Hyb1Tiers.Tier)
3786 LineTxt1 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNum1].Text, HybType, ValueRange1, Mod2HybTiers.Hyb, False)
3787
3788 ValueRange2 := FormatValueRangesAndIlvl(Mod2DataArray, [Mod2HybTiers.ModTop, Mod2HybTiers.ModBtm], Hyb2DataArray, Mod2HybTiers.Hyb)
3789 LineTxt2 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNum2].Text, [Mod2Type, HybType], ValueRange2, [[Mod2HybTiers.ModTop, Mod2HybTiers.ModBtm], Mod2HybTiers.Hyb], False)
3790
3791 Itemdata.UncertainAffixes[Keyname]["4_Mod2Hyb"] := [PrefixCount, SuffixCount, LineNum1, LineTxt1, LineNum2, LineTxt2]
3792 }
3793
3794 If (IsObject(Mod1Mod2HybTiers))
3795 {
3796 PrefixCount := 0
3797 SuffixCount := 0
3798 PrefixCount += (Mod1Type ~= "Prefix") ? 1 : 0
3799 PrefixCount += (Mod2Type ~= "Prefix") ? 1 : 0
3800 PrefixCount += (HybType ~= "Hybrid Prefix") ? 1 : 0
3801 SuffixCount += (Mod1Type ~= "Suffix") ? 1 : 0
3802 SuffixCount += (Mod2Type ~= "Suffix") ? 1 : 0
3803 SuffixCount += (HybType ~= "Hybrid Suffix") ? 1 : 0
3804
3805 ValueRange1 := FormatValueRangesAndIlvl_MultiTiers(Value1, Mod1DataArray, Hyb1DataArray, Mod1Mod2HybTiers.Mod1Top, Mod1Mod2HybTiers.Mod1Btm, Mod1Mod2HybTiers.HybTop, Mod1Mod2HybTiers.HybBtm)
3806 LineTxt1 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNum1].Text, [Mod1Type, HybType], ValueRange1, [[Mod1Mod2HybTiers.Mod1Top, Mod1Mod2HybTiers.Mod1Btm], [Mod1Mod2HybTiers.HybTop, Mod1Mod2HybTiers.HybBtm]], False)
3807
3808 ValueRange2 := FormatValueRangesAndIlvl_MultiTiers(Value2, Mod2DataArray, Hyb2DataArray, Mod1Mod2HybTiers.Mod2Top, Mod1Mod2HybTiers.Mod2Btm, Mod1Mod2HybTiers.HybTop, Mod1Mod2HybTiers.HybBtm)
3809 LineTxt2 := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNum2].Text, [Mod2Type, HybType], ValueRange2, [[Mod1Mod2HybTiers.Mod2Top, Mod1Mod2HybTiers.Mod2Btm], [Mod1Mod2HybTiers.HybTop, Mod1Mod2HybTiers.HybBtm]], False)
3810
3811 Itemdata.UncertainAffixes[Keyname]["5_Mod1Mod2Hyb"] := [PrefixCount, SuffixCount, LineNum1, LineTxt1, LineNum2, LineTxt2]
3812 }
3813}
3814
3815SolveAffixes_PreSuf(Keyname, LineNum, Value, Filename1, Filename2, ItemLevel)
3816{
3817 Global Itemdata
3818 Itemdata.UncertainAffixes[Keyname] := {}
3819
3820 Mod1DataArray := ArrayFromDatafile(Filename1)
3821 Mod2DataArray := ArrayFromDatafile(Filename2)
3822
3823 Mod1Tiers := LookupTierByValue(Value, Mod1DataArray, ItemLevel)
3824 Mod2Tiers := LookupTierByValue(Value, Mod2DataArray, ItemLevel)
3825 Mod1Mod2Tiers := SolveTiers_Mod1Mod2(Value, Mod1DataArray, Mod2DataArray, ItemLevel)
3826
3827 If (Mod1Tiers.Tier)
3828 {
3829 ValueRange := FormatValueRangesAndIlvl(Mod1DataArray, Mod1Tiers.Tier)
3830 LineTxt := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNum].Text, "Prefix", ValueRange, Mod1Tiers.Tier, False)
3831 Itemdata.UncertainAffixes[Keyname]["1_Pre"] := [1, 0, LineNum, LineTxt]
3832 }
3833
3834 If (Mod2Tiers.Tier)
3835 {
3836 ValueRange := FormatValueRangesAndIlvl(Mod2DataArray, Mod2Tiers.Tier)
3837 LineTxt := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNum].Text, "Suffix", ValueRange, Mod2Tiers.Tier, False)
3838 Itemdata.UncertainAffixes[Keyname]["2_Suf"] := [0, 1, LineNum, LineTxt]
3839 }
3840
3841 If (IsObject(Mod1Mod2Tiers))
3842 {
3843 ValueRange := FormatValueRangesAndIlvl_MultiTiers(Value, Mod1DataArray, Mod2DataArray, Mod1Mod2Tiers.Mod1Top, Mod1Mod2Tiers.Mod1Btm, Mod1Mod2Tiers.Mod2Top, Mod1Mod2Tiers.Mod2Btm)
3844 LineTxt := MakeAffixDetailLine(Itemdata.AffixTextLines[LineNum].Text, ["Prefix", "Suffix"], ValueRange, [[Mod1Mod2Tiers.Mod1Top, Mod1Mod2Tiers.Mod1Btm] , [Mod1Mod2Tiers.Mod2Top, Mod1Mod2Tiers.Mod2Btm]], False)
3845 Itemdata.UncertainAffixes[Keyname]["3_PreSuf"] := [1, 1, LineNum, LineTxt]
3846 }
3847}
3848
3849GetVeiledModCount(ItemDataAffixes, AffixType) {
3850 vCount := 0
3851
3852 IfInString, ItemDataAffixes, Unidentified
3853 {
3854 Return ; Not interested in unidentified items
3855 }
3856
3857 Loop, Parse, ItemDataAffixes, `n, `r
3858 {
3859 If (RegExMatch(A_LoopField, "i)Veiled (Prefix|Suffix)", match)) {
3860 If (match1 = AffixType) {
3861 vCount := vCount + 1
3862 }
3863 }
3864 }
3865
3866 Return vCount
3867}
3868
3869ParseAffixes(ItemDataAffixes, Item)
3870{
3871 Global Globals, Opts, AffixTotals, AffixLines, Itemdata
3872
3873 ItemDataChunk := ItemDataAffixes
3874
3875 IfInString, ItemDataChunk, Unidentified
3876 {
3877 Return ; Not interested in unidentified items
3878 }
3879
3880
3881 ItemBaseType := Item.BaseType
3882 ItemSubType := Item.SubType
3883 ItemGripType := Item.GripType
3884 ItemLevel := Item.Level
3885 ItemQuality := Item.Quality
3886 ItemIsHybridBase := Item.IsHybridBase
3887 ItemNamePlate := Itemdata.NamePlate
3888
3889 ; Reset the AffixLines "array" and other vars
3890 ResetAffixDetailVars()
3891
3892 ; Composition flags
3893 ;
3894 ; The pre-pass loop sets line numbers or markers for potentially ambiguous affixes,
3895 ; so that the composition of these affixes can be checked later.
3896
3897 If ( ! Item.IsJewel)
3898 {
3899 HasToArmour := 0
3900 HasToEvasion := 0
3901 HasToMaxES := 0
3902 HasToMaxLife := 0
3903 HasToArmourCraft := 0
3904 HasToEvasionCraft := 0
3905 HasToMaxESCraft := 0
3906 HasToMaxLifeCraft := 0
3907
3908 HasIncrDefences := 0
3909 HasIncrDefencesType := ""
3910 HasIncrDefencesCraft := 0
3911 HasIncrDefencesCraftType := ""
3912 HasStunBlockRecovery := 0
3913 HasChanceToBlockStrShield := 0
3914 ; pure str shields ("tower shields") can have a hybrid prefix "#% increased Armour / +#% Chance to Block"
3915 ; This means those fuckers can have 5 mods that combine:
3916 ; Prefix:
3917 ; #% increased Armour
3918 ; #% increased Armour / #% increased Stun and Block Recovery
3919 ; #% increased Armour / +#% Chance to Block
3920 ; Suffix:
3921 ; #% increased Stun and Block Recovery
3922 ; +#% Chance to Block
3923
3924 HasToAccuracyRating := 0
3925 HasIncrPhysDmg := 0
3926 HasToAccuracyRatingCraft := 0
3927 HasIncrPhysDmgCraft := 0
3928
3929 HasIncrRarity := 0
3930 HasIncrRarityCraft := 0
3931
3932 HasMaxMana := 0
3933 HasMaxManaCraft := 0
3934
3935 HasIncrSpellDamage := 0
3936 HasIncrFireDamage := 0
3937 HasIncrColdDamage := 0
3938 HasIncrLightningDamage := 0
3939 HasIncrSpellDamageCraft := 0
3940 HasIncrFireDamageCraft := 0
3941 HasIncrColdDamageCraft := 0
3942 HasIncrLightningDamageCraft := 0
3943
3944 HasIncrSpellDamagePrefix := 0
3945 HasIncrSpellOrElePrefix := 0
3946
3947 HasMultipleCrafted := 0
3948 HasLastLineNumber := 0
3949
3950 ; these two for jewels
3951 HasIncreasedAccuracyRating := 0
3952 HasIncreasedGlobalCritChance := 0
3953
3954
3955 ; --- PRE-PASS ---
3956 Loop, Parse, ItemDataChunk, `n, `r
3957 {
3958 LoopField := RegExReplace(Trim(A_LoopField), "i) \(fractured|crafted\)$")
3959 If StrLen(LoopField) = 0
3960 {
3961 Continue ; Not interested in blank lines
3962 }
3963
3964 Itemdata.AffixTextLines.Push( {"Text":LoopField, "Value":GetActualValue(LoopField)} )
3965 ; AffixTextLines[1].Text stores the full text of the first line (yes, with index 1 and not 0)
3966 ; AffixTextLines[1].Value stores just the extracted value
3967
3968 ++HasLastLineNumber ; Counts the affix text lines so that the last line can be checked for being a craft
3969
3970 IfInString, LoopField, to Armour
3971 {
3972 If (HasToArmour){
3973 HasToArmourCraft := A_Index
3974 }Else{
3975 HasToArmour := A_Index
3976 }
3977 Continue
3978 }
3979 IfInString, LoopField, to Evasion Rating
3980 {
3981 If (HasToEvasion){
3982 HasToEvasionCraft := A_Index
3983 }Else{
3984 HasToEvasion := A_Index
3985 }
3986 Continue
3987 }
3988 IfInString, LoopField, to maximum Energy Shield
3989 {
3990 If (HasToMaxES){
3991 HasToMaxESCraft := A_Index
3992 }Else{
3993 HasToMaxES := A_Index
3994 }
3995 Continue
3996 }
3997 IfInString, LoopField, to maximum Life
3998 {
3999 If (HasToMaxLife){
4000 HasToMaxLifeCraft := A_Index
4001 }Else{
4002 HasToMaxLife := A_Index
4003 }
4004 Continue
4005 }
4006 IfInString, LoopField, increased Armour and Evasion ; it's indeed "Evasion" and not "Evasion Rating" here
4007 {
4008 If (HasIncrDefences){
4009 HasIncrDefencesCraftType := "Defences_HybridBase"
4010 HasIncrDefencesCraft := A_Index
4011 }Else{
4012 HasIncrDefencesType := "Defences_HybridBase"
4013 HasIncrDefences := A_Index
4014 }
4015 Continue
4016 }
4017 IfInString, LoopField, increased Armour and Energy Shield
4018 {
4019 If (HasIncrDefences){
4020 HasIncrDefencesCraftType := "Defences_HybridBase"
4021 HasIncrDefencesCraft := A_Index
4022 }Else{
4023 HasIncrDefencesType := "Defences_HybridBase"
4024 HasIncrDefences := A_Index
4025 }
4026 Continue
4027 }
4028 IfInString, LoopField, increased Evasion and Energy Shield ; again "Evasion" and not "Evasion Rating"
4029 {
4030 If (HasIncrDefences){
4031 HasIncrDefencesCraftType := "Defences_HybridBase"
4032 HasIncrDefencesCraft := A_Index
4033 }Else{
4034 HasIncrDefencesType := "Defences_HybridBase"
4035 HasIncrDefences := A_Index
4036 }
4037 Continue
4038 }
4039 IfInString, LoopField, increased Armour
4040 {
4041 If (HasIncrDefences){
4042 HasIncrDefencesCraftType := "Armour"
4043 HasIncrDefencesCraft := A_Index
4044 }Else{
4045 HasIncrDefencesType := "Armour"
4046 HasIncrDefences := A_Index
4047 }
4048 Continue
4049 }
4050 IfInString, LoopField, increased Evasion Rating
4051 {
4052 If (HasIncrDefences){
4053 HasIncrDefencesCraftType := "Evasion"
4054 HasIncrDefencesCraft := A_Index
4055 }Else{
4056 HasIncrDefencesType := "Evasion"
4057 HasIncrDefences := A_Index
4058 }
4059 Continue
4060 }
4061 IfInString, LoopField, increased Energy Shield
4062 {
4063 If (HasIncrDefences){
4064 HasIncrDefencesCraftType := "EnergyShield"
4065 HasIncrDefencesCraft := A_Index
4066 }Else{
4067 HasIncrDefencesType := "EnergyShield"
4068 HasIncrDefences := A_Index
4069 }
4070 Continue
4071 }
4072 IfInString, LoopField, increased Stun and Block Recovery
4073 {
4074 HasStunBlockRecovery := A_Index
4075 Continue
4076 }
4077 IfInString, LoopField, Chance to Block
4078 {
4079 IfInString, ItemNamePlate, Tower Shield
4080 {
4081 HasChanceToBlockStrShield := A_Index
4082 }
4083 Continue
4084 }
4085 IfInString, LoopField, to Accuracy Rating
4086 {
4087 If (HasToAccuracyRating){
4088 HasToAccuracyRatingCraft := A_Index
4089 }Else{
4090 HasToAccuracyRating := A_Index
4091 }
4092 Continue
4093 }
4094 IfInString, LoopField, increased Physical Damage
4095 {
4096 If (HasIncrPhysDmg){
4097 HasIncrPhysDmgCraft := A_Index
4098 }Else{
4099 HasIncrPhysDmg := A_Index
4100 }
4101 Continue
4102 }
4103 IfInString, LoopField, increased Rarity of Items found
4104 {
4105 If (HasIncrRarity){
4106 HasIncrRarityCraft := A_Index
4107 }Else{
4108 HasIncrRarity := A_Index
4109 }
4110 Continue
4111 }
4112 IfInString, LoopField, to maximum Mana
4113 {
4114 If (HasMaxMana){
4115 HasMaxManaCraft := A_Index
4116 }Else{
4117 HasMaxMana := A_Index
4118 }
4119 Continue
4120 }
4121 IfInString, LoopField, increased Light Radius
4122 {
4123 HasIncrLightRadius := A_Index
4124 Continue
4125 }
4126 IfInString, LoopField, increased Spell Damage
4127 {
4128 If (HasIncrSpellDamage){
4129 HasIncrSpellDamageCraft := A_Index
4130 HasIncrSpellDamagePrefix := A_Index
4131 HasIncrSpellOrElePrefix := A_Index
4132 }Else{
4133 HasIncrSpellDamage := A_Index
4134 }
4135
4136 If ((ItemGripType = "1H") or (ItemSubType = "Shield")){
4137 Found := LookupTierByValue(GetActualValue(LoopField), ArrayFromDatafile("data\SpellDamage_MaxMana_1H.txt"), ItemLevel).Tier
4138 }Else{
4139 Found := LookupTierByValue(GetActualValue(LoopField), ArrayFromDatafile("data\SpellDamage_MaxMana_Staff.txt"), ItemLevel).Tier
4140 }
4141 If ( ! Found){
4142 HasIncrSpellDamagePrefix := A_Index
4143 HasIncrSpellOrElePrefix := A_Index
4144 }
4145 Continue
4146 }
4147 IfInString, LoopField, increased Fire Damage
4148 {
4149 If (HasIncrFireDamage){
4150 HasIncrFireDamageCraft := A_Index
4151 HasIncrFireDamagePrefix := HasIncrFireDamage
4152 HasIncrSpellOrElePrefix := HasIncrFireDamagePrefix
4153 }Else{
4154 HasIncrFireDamage := A_Index
4155 }
4156
4157 If ( ! LookupTierByValue(GetActualValue(LoopField), ArrayFromDatafile("data\IncrFireDamage_Suffix_Weapon.txt"), ItemLevel).Tier )
4158 {
4159 HasIncrFireDamagePrefix := A_Index
4160 HasIncrSpellOrElePrefix := A_Index
4161 }
4162 Continue
4163 }
4164 IfInString, LoopField, increased Cold Damage
4165 {
4166 If (HasIncrColdDamage){
4167 HasIncrColdDamageCraft := A_Index
4168 HasIncrColdDamagePrefix := HasIncrColdDamage
4169 HasIncrSpellOrElePrefix := HasIncrColdDamagePrefix
4170 }Else{
4171 HasIncrColdDamage := A_Index
4172 }
4173
4174 If ( ! LookupTierByValue(GetActualValue(LoopField), ArrayFromDatafile("data\IncrColdDamage_Suffix_Weapon.txt"), ItemLevel).Tier )
4175 {
4176 HasIncrColdDamagePrefix := A_Index
4177 HasIncrSpellOrElePrefix := A_Index
4178 }
4179 Continue
4180 }
4181 IfInString, LoopField, increased Lightning Damage
4182 {
4183 If (HasIncrLightningDamage){
4184 HasIncrLightningDamageCraft := A_Index
4185 HasIncrLightningDamagePrefix := HasIncrLightningDamage
4186 HasIncrSpellOrElePrefix := HasIncrLightningDamagePrefix
4187 }Else{
4188 HasIncrLightningDamage := A_Index
4189 }
4190
4191 If ( ! LookupTierByValue(GetActualValue(LoopField), ArrayFromDatafile("data\IncrLightningDamage_Suffix_Weapon.txt"), ItemLevel).Tier )
4192 {
4193 HasIncrLightningDamagePrefix := A_Index
4194 HasIncrSpellOrElePrefix := A_Index
4195 }
4196 Continue
4197 }
4198 IfInString, LoopField, Can have multiple Crafted Mods
4199 {
4200 HasMultipleCrafted := A_Index
4201 Itemdata.HasMultipleCrafted := A_Index
4202 Continue
4203 }
4204 }
4205 }
4206 Else
4207 {
4208 ; Jewels get their own Pre-Pass
4209
4210 ; --- PRE-PASS ---
4211 Loop, Parse, ItemDataChunk, `n, `r
4212 {
4213 LoopField := RegExReplace(Trim(A_LoopField), "i) \(fractured|crafted\)$")
4214 If StrLen(LoopField) = 0
4215 {
4216 Continue ; Not interested in blank lines
4217 }
4218
4219 Itemdata.AffixTextLines.Push( {"Text":LoopField, "Value":GetActualValue(LoopField)} )
4220 ++HasLastLineNumber
4221
4222 IfInString, LoopField, increased Accuracy Rating
4223 {
4224 If (Item.SubType = "Viridian Jewel" or Item.SubType = "Prismatic Jewel")
4225 {
4226 HasIncreasedAccuracyRating := A_Index
4227 Continue
4228 }
4229 }
4230
4231 IfInString, LoopField, increased Global Critical Strike Chance
4232 {
4233 If (Item.SubType = "Viridian Jewel" or Item.SubType = "Prismatic Jewel" )
4234 {
4235 HasIncreasedGlobalCritChance := A_Index
4236 Continue
4237 }
4238 }
4239 }
4240 }
4241
4242 Itemdata.LastAffixLineNumber := HasLastLineNumber
4243
4244 ; Prepare AffixLines. If a line isn't matched, it will later be recognized as unmatched because the entry is empty instead of undefined.
4245 Loop, %HasLastLineNumber%
4246 {
4247 AffixLines.Set(A_Index, "")
4248 }
4249
4250
4251 ; --- SIMPLE AFFIXES ---
4252
4253 Loop, Parse, ItemDataChunk, `n, `r
4254 {
4255 LoopField := RegExReplace(Trim(A_LoopField), "i) \(fractured|crafted\)$")
4256 If StrLen(LoopField) = 0
4257 {
4258 Continue ; Not interested in blank lines
4259 }
4260 IfInString, ItemDataChunk, Unidentified
4261 {
4262 Break ; Not interested in unidentified items
4263 }
4264
4265 CurrValue := GetActualValue(LoopField)
4266 CurrTier := 0
4267
4268
4269 ; --- SIMPLE JEWEL AFFIXES ---
4270
4271 If (Item.IsJewel)
4272 {
4273 If (Item.IsAbyssJewel)
4274 {
4275 If RegExMatch(LoopField, "Adds \d+? to \d+? (Physical|Fire|Cold|Lightning|Chaos) Damage")
4276 {
4277 If RegExMatch(LoopField, "Adds \d+? to \d+? (Physical|Fire|Cold|Lightning|Chaos) Damage to \w+ Attacks", match)
4278 {
4279 LookupAffixAndSetInfoLine("data\abyss_jewel\Adds" match1 "DamageToWeaponTypeAttacks.txt", "Prefix", ItemLevel, CurrValue)
4280 Continue
4281 }
4282 If RegExMatch(LoopField, "Adds \d+? to \d+? (Physical|Fire|Cold|Lightning|Chaos) Damage to Attacks", match)
4283 {
4284 LookupAffixAndSetInfoLine("data\abyss_jewel\Adds" match1 "DamageToAttacks.txt", "Suffix", ItemLevel, CurrValue)
4285 Continue
4286 }
4287 If RegExMatch(LoopField, "Adds \d+? to \d+? (Physical|Fire|Cold|Lightning|Chaos) Damage to Spells while", match)
4288 {
4289 LookupAffixAndSetInfoLine("data\abyss_jewel\Adds" match1 "DamageToSpellsWhile.txt", "Prefix", ItemLevel, CurrValue)
4290 Continue
4291 }
4292 If RegExMatch(LoopField, "Adds \d+? to \d+? (Physical|Fire|Cold|Lightning|Chaos) Damage to Spells", match)
4293 {
4294 LookupAffixAndSetInfoLine("data\abyss_jewel\Adds" match1 "DamageToSpells.txt", "Suffix", ItemLevel, CurrValue)
4295 Continue
4296 }
4297 }
4298
4299 IfInString, LoopField, to maximum Life
4300 {
4301 LookupAffixAndSetInfoLine("data\abyss_jewel\MaxLife.txt", "Prefix", ItemLevel, CurrValue)
4302 Continue
4303 }
4304 IfInString, LoopField, to maximum Mana
4305 {
4306 LookupAffixAndSetInfoLine("data\abyss_jewel\MaxMana.txt", "Prefix", ItemLevel, CurrValue)
4307 Continue
4308 }
4309 IfInString, LoopField, to Armour
4310 {
4311 LookupAffixAndSetInfoLine("data\abyss_jewel\ToArmour.txt", "Prefix", ItemLevel, CurrValue)
4312 Continue
4313 }
4314 IfInString, LoopField, to Evasion Rating
4315 {
4316 LookupAffixAndSetInfoLine("data\abyss_jewel\ToEvasionRating.txt", "Prefix", ItemLevel, CurrValue)
4317 Continue
4318 }
4319 IfInString, LoopField, to maximum Energy Shield
4320 {
4321 LookupAffixAndSetInfoLine("data\abyss_jewel\ToMaximumEnergyShield.txt", "Prefix", ItemLevel, CurrValue)
4322 Continue
4323 }
4324 IfInString, LoopField, Energy Shield Regenerated per second
4325 {
4326 LookupAffixAndSetInfoLine("data\abyss_jewel\EnergyShieldRegenerated.txt", "Prefix", ItemLevel, CurrValue)
4327 Continue
4328 }
4329 If RegExMatch(LoopField, "^[\d\.]+ Life Regenerated per second$")
4330 {
4331 LookupAffixAndSetInfoLine("data\abyss_jewel\LifeRegenerated.txt", "Prefix", ItemLevel, CurrValue)
4332 Continue
4333 }
4334 IfInString, LoopField, Mana Regenerated per second
4335 {
4336 LookupAffixAndSetInfoLine("data\abyss_jewel\ManaRegenerated.txt", "Prefix", ItemLevel, CurrValue)
4337 Continue
4338 }
4339 IfInString, LoopField, increased Damage over Time while
4340 {
4341 LookupAffixAndSetInfoLine(["1|10-14"], "Prefix", ItemLevel, CurrValue)
4342 Continue
4343 }
4344
4345 IfInString, LoopField, Minion
4346 {
4347 If RegExMatch(LoopField, "Minions deal \d+? to \d+? additional (Physical|Fire|Cold|Lightning|Chaos) Damage", match)
4348 {
4349 LookupAffixAndSetInfoLine("data\abyss_jewel\MinionsDealAdditional" match1 "Damage.txt", "Prefix", ItemLevel, CurrValue)
4350 Continue
4351 }
4352 If RegExMatch(LoopField, "Minions Regenerate \d+? Life per second")
4353 {
4354 LookupAffixAndSetInfoLine("data\abyss_jewel\MinionsRegenerateLife.txt", "Prefix", ItemLevel, CurrValue)
4355 Continue
4356 }
4357 If RegExMatch(LoopField, "Minions have \d+% chance to Blind on Hit with Attacks")
4358 {
4359 LookupAffixAndSetInfoLine(["32|3-4","65|5-6"], "Suffix", ItemLevel, CurrValue)
4360 Continue
4361 }
4362 If RegExMatch(LoopField, "Minions have \d+% chance to Taunt on Hit with Attacks")
4363 {
4364 LookupAffixAndSetInfoLine(["32|3-5","65|6-8"], "Suffix", ItemLevel, CurrValue)
4365 Continue
4366 }
4367 If RegExMatch(LoopField, "Minions have \d+% chance to Hinder Enemies on Hit with Spells, with 30% reduced Movement Speed")
4368 {
4369 LookupAffixAndSetInfoLine(["32|3-5","65|6-8"], "Suffix", ItemLevel, CurrValue)
4370 Continue
4371 }
4372 If RegExMatch(LoopField, "Minions deal \d+% increased Damage against Abyssal Monsters")
4373 {
4374 LookupAffixAndSetInfoLine(["1|30-40"], "Suffix", ItemLevel, CurrValue)
4375 Continue
4376 }
4377 If RegExMatch(LoopField, "Minions have \d+% increased (Attack|Cast) Speed")
4378 {
4379 LookupAffixAndSetInfoLine(["1|4-6"], "Hybrid Suffix", ItemLevel, CurrValue)
4380 Continue
4381 }
4382 If RegExMatch(LoopField, "Minions Regenerate \d+% Life per second")
4383 {
4384 LookupAffixAndSetInfoLine(["1|0.4-0.8"], "Suffix", ItemLevel, CurrValue)
4385 Continue
4386 }
4387 If RegExMatch(LoopField, "Minions Leech [\d\.]+% of Damage as Life")
4388 {
4389 LookupAffixAndSetInfoLine(["1|0.3-0.5"], "Suffix", ItemLevel, CurrValue)
4390 Continue
4391 }
4392 If RegExMatch(LoopField, "Minions have \d+% increased Movement Speed")
4393 {
4394 LookupAffixAndSetInfoLine(["1|6-10"], "Suffix", ItemLevel, CurrValue)
4395 Continue
4396 }
4397 If RegExMatch(LoopField, "Minions have \d+% increased maximum Life")
4398 {
4399 LookupAffixAndSetInfoLine(["1|8-12"], "Suffix", ItemLevel, CurrValue)
4400 Continue
4401 }
4402 If RegExMatch(LoopField, "Minions have +\d+% to all Elemental Resistances")
4403 {
4404 LookupAffixAndSetInfoLine(["1|6-10"], "Suffix", ItemLevel, CurrValue)
4405 Continue
4406 }
4407 If RegExMatch(LoopField, "Minions have +\d+% to Chaos Resistance")
4408 {
4409 LookupAffixAndSetInfoLine(["1|7-12"], "Suffix", ItemLevel, CurrValue)
4410 Continue
4411 }
4412 If RegExMatch(LoopField, "Minions have \d+% increased Attack and Cast Speed if you or your Minions have Killed Recently")
4413 {
4414 LookupAffixAndSetInfoLine(["1|6-8"], "Suffix", ItemLevel, CurrValue)
4415 Continue
4416 }
4417 If RegExMatch(LoopField, "increased Minion Damage if you've used a Minion Skill Recently")
4418 {
4419 LookupAffixAndSetInfoLine(["1|15-20"], "Suffix", ItemLevel, CurrValue)
4420 Continue
4421 }
4422 }
4423
4424 IfInString, LoopField, to Accuracy Rating
4425 {
4426 LookupAffixAndSetInfoLine("data\abyss_jewel\AccuracyRating.txt", "Suffix", ItemLevel, CurrValue)
4427 Continue
4428 }
4429 IfInString, LoopField, chance to Blind Enemies on Hit with Attacks
4430 {
4431 LookupAffixAndSetInfoLine(["32|3-4","65|5-6"], "Suffix", ItemLevel, CurrValue)
4432 Continue
4433 }
4434 IfInString, LoopField, chance to Taunt Enemies on Hit with Attacks
4435 {
4436 LookupAffixAndSetInfoLine(["32|3-5","65|6-8"], "Suffix", ItemLevel, CurrValue)
4437 Continue
4438 }
4439 If RegExMatch(LoopField, "chance to Hinder Enemies on Hit with Spells, with 30% reduced Movement Speed")
4440 {
4441 LookupAffixAndSetInfoLine(["32|3-5","65|6-8"], "Suffix", ItemLevel, CurrValue)
4442 Continue
4443 }
4444 If RegExMatch(LoopField, "chance to Avoid being (Ignited|Shocked)")
4445 {
4446 LookupAffixAndSetInfoLine(["1|6-8","30|9-10"], "Suffix", ItemLevel, CurrValue)
4447 Continue
4448 }
4449 If RegExMatch(LoopField, "chance to Avoid being (Chilled|Frozen)")
4450 {
4451 LookupAffixAndSetInfoLine(["1|6-8","30|9-10"], "Hybrid Suffix", ItemLevel, CurrValue)
4452 Continue
4453 }
4454 If RegExMatch(LoopField, "chance to [Aa]void (being Poisoned|Bleeding)")
4455 {
4456 LookupAffixAndSetInfoLine(["20|6-8","50|9-10"], "Suffix", ItemLevel, CurrValue)
4457 Continue
4458 }
4459 IfInString, LoopField, chance to Avoid being Stunned
4460 {
4461 LookupAffixAndSetInfoLine(["1|6-8","20|9-10"], "Suffix", ItemLevel, CurrValue)
4462 Continue
4463 }
4464 IfInString, LoopField, increased Damage against Abyssal Monsters
4465 {
4466 LookupAffixAndSetInfoLine(["1|30-40"], "Suffix", ItemLevel, CurrValue)
4467 Continue
4468 }
4469 IfInString, LoopField, additional Physical Damage Reduction against Abyssal Monsters
4470 {
4471 LookupAffixAndSetInfoLine(["1|4-6"], "Suffix", ItemLevel, CurrValue)
4472 Continue
4473 }
4474 If RegExMatch(LoopField, "increased Effect of (Chill|Shock)")
4475 {
4476 LookupAffixAndSetInfoLine(["30|6-10"], "Suffix", ItemLevel, CurrValue)
4477 Continue
4478 }
4479 IfInString, LoopField, chance to Block Spells if you were Damaged by a Hit Recently
4480 {
4481 LookupAffixAndSetInfoLine(["1|2"], "Suffix", ItemLevel, CurrValue)
4482 Continue
4483 }
4484 IfInString, LoopField, additional Physical Damage Reduction if you weren't Damaged by a Hit Recently
4485 {
4486 LookupAffixAndSetInfoLine(["1|2"], "Suffix", ItemLevel, CurrValue)
4487 Continue
4488 }
4489 IfInString, LoopField, increased Movement Speed if you haven't taken Damage Recently
4490 {
4491 LookupAffixAndSetInfoLine(["1|3-4"], "Suffix", ItemLevel, CurrValue)
4492 Continue
4493 }
4494 IfInString, LoopField, increased Damage if you've Killed Recently
4495 {
4496 LookupAffixAndSetInfoLine(["1|10-20"], "Suffix", ItemLevel, CurrValue)
4497 Continue
4498 }
4499 IfInString, LoopField, to Critical Strike Multiplier if you've Killed Recently
4500 {
4501 LookupAffixAndSetInfoLine(["25|8-14"], "Suffix", ItemLevel, CurrValue)
4502 Continue
4503 }
4504 IfInString, LoopField, increased Armour if you haven't Killed Recently
4505 {
4506 LookupAffixAndSetInfoLine(["1|20-30"], "Suffix", ItemLevel, CurrValue)
4507 Continue
4508 }
4509 IfInString, LoopField, increased Accuracy Rating if you haven't Killed Recently
4510 {
4511 LookupAffixAndSetInfoLine(["1|20-30"], "Suffix", ItemLevel, CurrValue)
4512 Continue
4513 }
4514 If RegExMatch(LoopField, "Damage Penetrates \d+% Elemental Resistance if you haven't Killed Recently")
4515 {
4516 LookupAffixAndSetInfoLine(["1|2"], "Suffix", ItemLevel, CurrValue)
4517 Continue
4518 }
4519 IfInString, LoopField, increased Evasion Rating while moving
4520 {
4521 LookupAffixAndSetInfoLine(["1|25-35"], "Suffix", ItemLevel, CurrValue)
4522 Continue
4523 }
4524 IfInString, LoopField, increased Mana Regeneration Rate while moving
4525 {
4526 LookupAffixAndSetInfoLine(["1|20-25"], "Suffix", ItemLevel, CurrValue)
4527 Continue
4528 }
4529 IfInString, LoopField, of Life Regenerated per second while moving
4530 {
4531 LookupAffixAndSetInfoLine(["1|0.5-1"], "Suffix", ItemLevel, CurrValue)
4532 Continue
4533 }
4534 If RegExMatch(LoopField, "Gain \d+% of Physical Damage as Extra Fire Damage if you've dealt a Critical Strike Recently")
4535 {
4536 LookupAffixAndSetInfoLine(["40|2-4"], "Suffix", ItemLevel, CurrValue)
4537 Continue
4538 }
4539 IfInString, LoopField, increased Attack Speed if you've dealt a Critical Strike Recently
4540 {
4541 LookupAffixAndSetInfoLine(["25|6-8"], "Suffix", ItemLevel, CurrValue)
4542 Continue
4543 }
4544 IfInString, LoopField, increased Cast Speed if you've dealt a Critical Strike Recently
4545 {
4546 LookupAffixAndSetInfoLine(["25|5-7"], "Suffix", ItemLevel, CurrValue)
4547 Continue
4548 }
4549 IfInString, LoopField, increased Critical Strike Chance if you haven't dealt a Critical Strike Recently
4550 {
4551 LookupAffixAndSetInfoLine(["1|20-30"], "Suffix", ItemLevel, CurrValue)
4552 Continue
4553 }
4554 IfInString, LoopField, chance to Dodge Attacks and Spells if you've been Hit Recently
4555 {
4556 LookupAffixAndSetInfoLine(["1|2"], "Suffix", ItemLevel, CurrValue)
4557 Continue
4558 }
4559 IfInString, LoopField, increased Movement Speed if you've Killed Recently
4560 {
4561 LookupAffixAndSetInfoLine(["1|2-4"], "Suffix", ItemLevel, CurrValue)
4562 Continue
4563 }
4564 IfInString, LoopField, additional Block Chance if you were Damaged by a Hit Recently
4565 {
4566 LookupAffixAndSetInfoLine(["1|2"], "Suffix", ItemLevel, CurrValue)
4567 Continue
4568 }
4569 IfInString, LoopField, chance to gain Onslaught for 4 seconds on Kill
4570 {
4571 LookupAffixAndSetInfoLine(["10|3-5","50|6-8"], "Suffix", ItemLevel, CurrValue)
4572 Continue
4573 }
4574 IfInString, LoopField, chance to gain Phasing for 4 seconds on Kill
4575 {
4576 LookupAffixAndSetInfoLine(["10|3-5","50|6-8"], "Suffix", ItemLevel, CurrValue)
4577 Continue
4578 }
4579 IfInString, LoopField, chance to gain Unholy Might for 4 seconds on Melee Kill
4580 {
4581 LookupAffixAndSetInfoLine(["40|2-3","80|4-5"], "Suffix", ItemLevel, CurrValue)
4582 Continue
4583 }
4584 }
4585
4586 IfInString, LoopField, increased Area Damage
4587 {
4588 LookupAffixAndSetInfoLine("data\jewel\AreaDamage.txt", "Suffix", ItemLevel, CurrValue)
4589 Continue
4590 }
4591 IfInString, LoopField, increased Attack and Cast Speed
4592 {
4593 LookupAffixAndSetInfoLine("data\jewel\AttackAndCastSpeed.txt", "Suffix", ItemLevel, CurrValue)
4594 Continue
4595 }
4596 If RegExMatch(LoopField, ".*increased Attack Speed with (One|Two) Handed Melee Weapons")
4597 {
4598 LookupAffixAndSetInfoLine("data\jewel\AttackSpeedWith1H2HMelee.txt", "Prefix", ItemLevel, CurrValue)
4599 Continue
4600 }
4601 IfInString, LoopField, increased Attack Speed while holding a Shield
4602 {
4603 LookupAffixAndSetInfoLine("data\jewel\AttackSpeedWhileHoldingShield.txt", "Prefix", ItemLevel, CurrValue)
4604 Continue
4605 }
4606 IfInString, LoopField, increased Attack Speed while Dual Wielding
4607 {
4608 LookupAffixAndSetInfoLine("data\jewel\AttackSpeedWhileDualWielding.txt", "Prefix", ItemLevel, CurrValue)
4609 Continue
4610 }
4611 If RegExMatch(LoopField, ".*increased Attack Speed with (Axes|Bows|Claws|Daggers|Maces|Staves|Swords|Wands)")
4612 {
4613 LookupAffixAndSetInfoLine("data\jewel\AttackSpeedWithWeapontype.txt", "Prefix", ItemLevel, CurrValue)
4614 Continue
4615 }
4616
4617 ; Pure Attack Speed must be checked last if RegEx line end isn't used
4618 If RegExMatch(LoopField, ".*increased Attack Speed$")
4619 {
4620 LookupAffixAndSetInfoLine("data\jewel\AttackSpeed_Jewels.txt", "Suffix", ItemLevel, CurrValue)
4621 Continue
4622 }
4623
4624 IfInString, LoopField, increased Accuracy Rating
4625 {
4626 If (Item.SubType = "Cobalt Jewel" or Item.SubType = "Crimson Jewel")
4627 {
4628 ; Cobalt and Crimson jewels can't get the combined increased accuracy/crit chance affix
4629 LookupAffixAndSetInfoLine("data\jewel\IncrAccuracyRating_Jewels.txt", "Suffix", ItemLevel, CurrValue)
4630 Continue
4631 }
4632 Else
4633 {
4634 ; increased Accuracy Rating on Viridian and Prismatic jewels is a complex affix and handled later
4635 Continue
4636 }
4637 }
4638
4639 IfInString, LoopField, to all Attributes
4640 {
4641 LookupAffixAndSetInfoLine("data\jewel\ToAllAttributes_Jewels.txt", "Suffix", ItemLevel, CurrValue)
4642 Continue
4643 }
4644
4645 If RegExMatch(LoopField, ".*to (Strength|Dexterity|Intelligence) and (Strength|Dexterity|Intelligence)")
4646 {
4647 LookupAffixAndSetInfoLine("data\jewel\To2Attributes_Jewels.txt", "Suffix", ItemLevel, CurrValue)
4648 Continue
4649 }
4650 If RegExMatch(LoopField, ".*to (Strength|Dexterity|Intelligence)")
4651 {
4652 LookupAffixAndSetInfoLine("data\jewel\To1Attribute_Jewels.txt", "Suffix", ItemLevel, CurrValue)
4653 Continue
4654 }
4655 If RegExMatch(LoopField, ".*increased Cast Speed (with|while) .*")
4656 {
4657 LookupAffixAndSetInfoLine("data\jewel\CastSpeedWithWhile.txt", "Prefix", ItemLevel, CurrValue)
4658 Continue
4659 }
4660
4661 ; pure Cast Speed must be checked last if RegEx line end isn't used
4662 If RegExMatch(LoopField, ".*increased Cast Speed$")
4663 {
4664 LookupAffixAndSetInfoLine("data\jewel\CastSpeed_Jewels.txt", "Suffix", ItemLevel, CurrValue)
4665 Continue
4666 }
4667 IfInString, LoopField, increased Critical Strike Chance for Spells
4668 {
4669 LookupAffixAndSetInfoLine("data\jewel\CritChanceSpells_Jewels.txt", "Suffix", ItemLevel, CurrValue)
4670 Continue
4671 }
4672 IfInString, LoopField, increased Melee Critical Strike Chance
4673 {
4674 LookupAffixAndSetInfoLine("data\jewel\MeleeCritChance.txt", "Suffix", ItemLevel, CurrValue)
4675 Continue
4676 }
4677 IfInString, LoopField, increased Critical Strike Chance with Elemental Skills
4678 {
4679 LookupAffixAndSetInfoLine("data\jewel\CritChanceElementalSkills.txt", "Suffix", ItemLevel, CurrValue)
4680 Continue
4681 }
4682 If RegExMatch(LoopField, ".*increased Critical Strike Chance with (Fire|Cold|Lightning) Skills")
4683 {
4684 LookupAffixAndSetInfoLine("data\jewel\CritChanceFireColdLightningSkills.txt", "Prefix", ItemLevel, CurrValue)
4685 Continue
4686 }
4687 If RegExMatch(LoopField, ".*increased Critical Strike Chance with (One|Two) Handed Melee Weapons")
4688 {
4689 LookupAffixAndSetInfoLine("data\jewel\CritChanceWith1H2HMelee.txt", "Prefix", ItemLevel, CurrValue)
4690 Continue
4691 }
4692 IfInString, LoopField, increased Weapon Critical Strike Chance while Dual Wielding
4693 {
4694 LookupAffixAndSetInfoLine("data\jewel\WeaponCritChanceDualWielding.txt", "Prefix", ItemLevel, CurrValue)
4695 Continue
4696 }
4697 IfInString, LoopField, increased Global Critical Strike Chance
4698 {
4699 If not (Item.SubType = "Viridian Jewel" or Item.SubType = "Prismatic Jewel")
4700 {
4701 ; Only Viridian and Prismatic jewels can get the combined increased accuracy/crit chance affix
4702 LookupAffixAndSetInfoLine("data\jewel\CritChanceGlobal_Jewels.txt", "Suffix", ItemLevel, CurrValue)
4703 Continue
4704 }
4705 Else
4706 {
4707 ; Crit chance on Viridian and Prismatic Jewels is a complex affix that is handled later
4708 Continue
4709 }
4710 }
4711 IfInString, LoopField, to Melee Critical Strike Multiplier
4712 {
4713 LookupAffixAndSetInfoLine("data\jewel\CritMeleeMultiplier.txt", "Suffix", ItemLevel, CurrValue)
4714 Continue
4715 }
4716 IfInString, LoopField, to Critical Strike Multiplier for Spells
4717 {
4718 LookupAffixAndSetInfoLine("data\jewel\CritMultiplierSpells.txt", "Suffix", ItemLevel, CurrValue)
4719 Continue
4720 }
4721 IfInString, LoopField, to Critical Strike Multiplier with Elemental Skills
4722 {
4723 LookupAffixAndSetInfoLine("data\jewel\CritMultiplierElementalSkills.txt", "Suffix", ItemLevel, CurrValue)
4724 Continue
4725 }
4726 If RegExMatch(LoopField, ".*to Critical Strike Multiplier with (Fire|Cold|Lightning) Skills")
4727 {
4728 LookupAffixAndSetInfoLine("data\jewel\CritMultiplierFireColdLightningSkills.txt", "Prefix", ItemLevel, CurrValue)
4729 Continue
4730 }
4731 If RegExMatch(LoopField, ".*to Critical Strike Multiplier with (One|Two) Handed Melee Weapons")
4732 {
4733 LookupAffixAndSetInfoLine("data\jewel\CritMultiplierWith1H2HMelee.txt", "Prefix", ItemLevel, CurrValue)
4734 Continue
4735 }
4736 IfInString, LoopField, to Critical Strike Multiplier while Dual Wielding
4737 {
4738 LookupAffixAndSetInfoLine("data\jewel\CritMultiplierWhileDualWielding.txt", "Prefix", ItemLevel, CurrValue)
4739 Continue
4740 }
4741 IfInString, LoopField, Critical Strike Multiplier
4742 {
4743 LookupAffixAndSetInfoLine("data\jewel\CritMultiplierGlobal_Jewels.txt", "Suffix", ItemLevel, CurrValue)
4744 Continue
4745 }
4746 IfInString, LoopField, chance to Ignite
4747 {
4748 LookupAffixAndSetInfoLine("data\jewel\ChanceToIgnite.txt", "Hybrid Suffix", ItemLevel, CurrValue)
4749 Continue
4750 }
4751 IfInString, LoopField, increased Ignite Duration on Enemies
4752 {
4753 LookupAffixAndSetInfoLine("data\jewel\IgniteDurationOnEnemies.txt", "Hybrid Suffix", ItemLevel, CurrValue)
4754 Continue
4755 }
4756 IfInString, LoopField, chance to Freeze
4757 {
4758 LookupAffixAndSetInfoLine("data\jewel\ChanceToFreeze.txt", "Hybrid Suffix", ItemLevel, CurrValue)
4759 Continue
4760 }
4761 IfInString, LoopField, increased Freeze Duration on Enemies
4762 {
4763 LookupAffixAndSetInfoLine("data\jewel\FreezeDurationOnEnemies.txt", "Hybrid Suffix", ItemLevel, CurrValue)
4764 Continue
4765 }
4766 IfInString, LoopField, chance to Shock
4767 {
4768 LookupAffixAndSetInfoLine("data\jewel\ChanceToShock.txt", "Hybrid Suffix", ItemLevel, CurrValue)
4769 Continue
4770 }
4771 IfInString, LoopField, increased Shock Duration on Enemies
4772 {
4773 LookupAffixAndSetInfoLine("data\jewel\ShockDurationOnEnemies.txt", "Hybrid Suffix", ItemLevel, CurrValue)
4774 Continue
4775 }
4776 IfInString, LoopField, chance to Poison
4777 {
4778 LookupAffixAndSetInfoLine("data\jewel\ChanceToPoison.txt", "Hybrid Suffix", ItemLevel, CurrValue)
4779 Continue
4780 }
4781 IfInString, LoopField, increased Poison Duration on Enemies
4782 {
4783 LookupAffixAndSetInfoLine("data\jewel\PoisonDuration.txt", "Hybrid Suffix", ItemLevel, CurrValue)
4784 Continue
4785 }
4786 IfInString, LoopField, chance to cause Bleeding
4787 {
4788 LookupAffixAndSetInfoLine("data\jewel\ChanceToBleed.txt", "Hybrid Suffix", ItemLevel, CurrValue)
4789 Continue
4790 }
4791 IfInString, LoopField, increased Bleed duration
4792 {
4793 LookupAffixAndSetInfoLine("data\jewel\BleedingDurationOnEnemies.txt", "Hybrid Suffix", ItemLevel, CurrValue)
4794 Continue
4795 }
4796 IfInString, LoopField, increased Burning Damage
4797 {
4798 LookupAffixAndSetInfoLine("data\jewel\IncrBurningDamage.txt", "Suffix", ItemLevel, CurrValue)
4799 Continue
4800 }
4801 IfInString, LoopField, increased Damage with Bleeding
4802 {
4803 LookupAffixAndSetInfoLine("data\jewel\IncrBleedingDamage.txt", "Suffix", ItemLevel, CurrValue)
4804 Continue
4805 }
4806 IfInString, LoopField, increased Damage with Poison
4807 {
4808 LookupAffixAndSetInfoLine("data\jewel\IncrPoisonDamage.txt", "Suffix", ItemLevel, CurrValue)
4809 Continue
4810 }
4811 If RegExMatch(LoopField, ".*increased (Fire|Cold|Lightning) Damage")
4812 {
4813 LookupAffixAndSetInfoLine("data\jewel\IncrFireColdLightningDamage_Jewels.txt", "Prefix", ItemLevel, CurrValue)
4814 Continue
4815 }
4816 If RegExMatch(LoopField, "Minions have .* Chance to Block")
4817 {
4818 LookupAffixAndSetInfoLine("data\jewel\MinionBlockChance.txt", "Suffix", ItemLevel, CurrValue)
4819 Continue
4820 }
4821 If RegExMatch(LoopField, ".*(Chance to Block|Block Chance).*")
4822 {
4823 LookupAffixAndSetInfoLine("data\jewel\BlockChance_ChanceToBlock_Jewels.txt", "Prefix", ItemLevel, CurrValue)
4824 Continue
4825 }
4826 IfInString, LoopField, increased Damage over Time
4827 {
4828 LookupAffixAndSetInfoLine("data\jewel\DamageOverTime.txt", "Suffix", ItemLevel, CurrValue)
4829 Continue
4830 }
4831 If RegExMatch(LoopField, "Minions deal .* increased Damage")
4832 {
4833 LookupAffixAndSetInfoLine("data\jewel\MinionsDealIncrDamage.txt", "Prefix", ItemLevel, CurrValue)
4834 Continue
4835 }
4836 If RegExMatch(LoopField, ".*increased Damage$")
4837 {
4838 LookupAffixAndSetInfoLine("data\jewel\IncrDamage.txt", "Suffix", ItemLevel, CurrValue)
4839 Continue
4840 }
4841 IfInString, LoopField, chance to Knock Enemies Back on hit
4842 {
4843 LookupAffixAndSetInfoLine("data\jewel\KnockBackOnHit.txt", "Suffix", ItemLevel, CurrValue)
4844 Continue
4845 }
4846 IfInString, LoopField, Life gained for each Enemy hit by your Attacks
4847 {
4848 LookupAffixAndSetInfoLine("data\jewel\LifeOnHit_Jewels.txt", "Suffix", ItemLevel, CurrValue)
4849 Continue
4850 }
4851 IfInString, LoopField, Energy Shield gained for each Enemy hit by your Attacks
4852 {
4853 LookupAffixAndSetInfoLine("data\jewel\ESOnHit.txt", "Suffix", ItemLevel, CurrValue)
4854 Continue
4855 }
4856 IfInString, LoopField, Mana gained for each Enemy hit by your Attacks
4857 {
4858 LookupAffixAndSetInfoLine("data\jewel\ManaOnHit.txt", "Suffix", ItemLevel, CurrValue)
4859 Continue
4860 }
4861 IfInString, LoopField, reduced Mana Cost of Skills
4862 {
4863 LookupAffixAndSetInfoLine("data\jewel\ReducedManaCost.txt", "Suffix", ItemLevel, CurrValue)
4864 Continue
4865 }
4866 IfInString, LoopField, increased Mana Regeneration Rate
4867 {
4868 LookupAffixAndSetInfoLine("data\jewel\ManaRegen_Jewels.txt", "Prefix", ItemLevel, CurrValue)
4869 Continue
4870 }
4871 IfInString, LoopField, increased Melee Damage
4872 {
4873 LookupAffixAndSetInfoLine("data\jewel\MeleeDamage.txt", "Suffix", ItemLevel, CurrValue)
4874 Continue
4875 }
4876 IfInString, LoopField, increased Projectile Damage
4877 {
4878 LookupAffixAndSetInfoLine("data\jewel\ProjectileDamage.txt", "Suffix", ItemLevel, CurrValue)
4879 Continue
4880 }
4881 IfInString, LoopField, increased Projectile Speed
4882 {
4883 LookupAffixAndSetInfoLine("data\jewel\ProjectileSpeed_Jewels.txt", "Suffix", ItemLevel, CurrValue)
4884 Continue
4885 }
4886 IfInString, LoopField, to all Elemental Resistances
4887 {
4888 ; "to all Elemental Resistances" matches multiple affixes
4889 If InStr(LoopField, "Minions have"){
4890 File := "data\jewel\ToAllResist_Jewels_Minions.txt"
4891 }
4892 Else If InStr(LoopField, "Totems gain"){
4893 File := "data\jewel\ToAllResist_Jewels_Totems.txt"
4894 }
4895 Else{
4896 File := "data\jewel\ToAllResist_Jewels.txt"
4897 }
4898 LookupAffixAndSetInfoLine(File, "Suffix", ItemLevel, CurrValue)
4899 Continue
4900 }
4901 If RegExMatch(LoopField, ".*to (Fire|Cold|Lightning) and (Fire|Cold|Lightning) Resistances")
4902 {
4903 LookupAffixAndSetInfoLine("data\jewel\To2Resist_Jewels.txt", "Suffix", ItemLevel, CurrValue)
4904 Continue
4905 }
4906 If RegExMatch(LoopField, ".*to (Fire|Cold|Lightning) Resistance")
4907 {
4908 LookupAffixAndSetInfoLine("data\jewel\To1Resist_Jewels.txt", "Suffix", ItemLevel, CurrValue)
4909 Continue
4910 }
4911 IfInString, LoopField, to Chaos Resistance
4912 {
4913 LookupAffixAndSetInfoLine("data\jewel\ToChaosResist_Jewels.txt", "Suffix", ItemLevel, CurrValue)
4914 Continue
4915 }
4916 IfInString, LoopField, increased Stun Duration on Enemies
4917 {
4918 LookupAffixAndSetInfoLine("data\jewel\StunDuration_Jewels.txt", "Suffix", ItemLevel, CurrValue)
4919 Continue
4920 }
4921 If RegExMatch(LoopField, ".*increased Physical Damage with (Axes|Bows|Claws|Daggers|Maces|Staves|Swords|Wands)")
4922 {
4923 LookupAffixAndSetInfoLine("data\jewel\IncrPhysDamageWithWeapontype.txt", "Prefix", ItemLevel, CurrValue)
4924 Continue
4925 }
4926 IfInString, LoopField, increased Melee Physical Damage while holding a Shield
4927 {
4928 LookupAffixAndSetInfoLine("data\jewel\IncrMeleePhysDamageWhileHoldingShield.txt", "Prefix", ItemLevel, CurrValue)
4929 Continue
4930 }
4931 IfInString, LoopField, increased Physical Weapon Damage while Dual Wielding
4932 {
4933 LookupAffixAndSetInfoLine("data\jewel\IncrPhysWeaponDamageDualWielding.txt", "Prefix", ItemLevel, CurrValue)
4934 Continue
4935 }
4936 If RegExMatch(LoopField, ".*increased Physical Damage with (One|Two) Handed Melee Weapons")
4937 {
4938 LookupAffixAndSetInfoLine("data\jewel\IncrPhysDamageWith1H2HMelee.txt", "Prefix", ItemLevel, CurrValue)
4939 Continue
4940 }
4941 IfInString, LoopField, increased Global Physical Damage
4942 {
4943 LookupAffixAndSetInfoLine("data\jewel\IncrPhysDamage_Jewels.txt", "Prefix", ItemLevel, CurrValue)
4944 Continue
4945 }
4946 IfInString, LoopField, increased Totem Damage
4947 {
4948 LookupAffixAndSetInfoLine("data\jewel\IncrTotemDamage.txt", "Prefix", ItemLevel, CurrValue)
4949 Continue
4950 }
4951 IfInString, LoopField, increased Totem Life
4952 {
4953 LookupAffixAndSetInfoLine("data\jewel\IncrTotemLife.txt", "Prefix", ItemLevel, CurrValue)
4954 Continue
4955 }
4956 IfInString, LoopField, increased Trap Throwing Speed
4957 {
4958 LookupAffixAndSetInfoLine("data\jewel\IncrTrapThrowingSpeed.txt", "Prefix", ItemLevel, CurrValue)
4959 Continue
4960 }
4961 IfInString, LoopField, increased Trap Damage
4962 {
4963 LookupAffixAndSetInfoLine("data\jewel\IncrTrapDamage.txt", "Prefix", ItemLevel, CurrValue)
4964 Continue
4965 }
4966 IfInString, LoopField, increased Mine Laying Speed
4967 {
4968 LookupAffixAndSetInfoLine("data\jewel\IncrMineLayingSpeed.txt", "Prefix", ItemLevel, CurrValue)
4969 Continue
4970 }
4971 IfInString, LoopField, increased Mine Damage
4972 {
4973 LookupAffixAndSetInfoLine("data\jewel\IncrMineDamage.txt", "Prefix", ItemLevel, CurrValue)
4974 Continue
4975 }
4976 IfInString, LoopField, increased Chaos Damage
4977 {
4978 LookupAffixAndSetInfoLine("data\jewel\IncrChaosDamage.txt", "Prefix", ItemLevel, CurrValue)
4979 Continue
4980 }
4981 If ( InStr(LoopField,"increased maximum Life"))
4982 {
4983 If InStr(LoopField,"Minions have")
4984 {
4985 FilePath := "data\jewel\MinionIncrMaximumLife.txt"
4986 }
4987 Else
4988 {
4989 FilePath := "data\jewel\IncrMaximumLife.txt"
4990 }
4991 LookupAffixAndSetInfoLine(FilePath, "Prefix", ItemLevel, CurrValue)
4992 Continue
4993 }
4994 IfInString, LoopField, increased Armour
4995 {
4996 LookupAffixAndSetInfoLine("data\jewel\IncrArmour_Jewels.txt", "Prefix", ItemLevel, CurrValue)
4997 Continue
4998 }
4999 IfInString, LoopField, increased Evasion Rating
5000 {
5001 LookupAffixAndSetInfoLine("data\jewel\IncrEvasion_Jewels.txt", "Prefix", ItemLevel, CurrValue)
5002 Continue
5003 }
5004 IfInString, LoopField, increased Energy Shield Recharge Rate
5005 {
5006 LookupAffixAndSetInfoLine("data\jewel\EnergyShieldRechargeRate.txt", "Prefix", ItemLevel, CurrValue)
5007 Continue
5008 }
5009 IfInString, LoopField, faster start of Energy Shield Recharge
5010 {
5011 LookupAffixAndSetInfoLine("data\jewel\FasterStartOfEnergyShieldRecharge.txt", "Prefix", ItemLevel, CurrValue)
5012 Continue
5013 }
5014 IfInString, LoopField, increased maximum Energy Shield
5015 {
5016 LookupAffixAndSetInfoLine("data\jewel\IncrMaxEnergyShield_Jewels.txt", "Prefix", ItemLevel, CurrValue)
5017 Continue
5018 }
5019 IfInString, LoopField, Physical Attack Damage Leeched as
5020 {
5021 LookupAffixAndSetInfoLine("data\jewel\PhysicalAttackDamageLeeched_Jewels.txt", "Prefix", ItemLevel, CurrValue)
5022 Continue
5023 }
5024 IfInString, LoopField, increased Spell Damage while Dual Wielding
5025 {
5026 LookupAffixAndSetInfoLine("data\jewel\SpellDamageDualWielding_Jewels.txt", "Prefix", ItemLevel, CurrValue)
5027 Continue
5028 }
5029 IfInString, LoopField, increased Spell Damage while holding a Shield
5030 {
5031 LookupAffixAndSetInfoLine("data\jewel\SpellDamageHoldingShield_Jewels.txt", "Prefix", ItemLevel, CurrValue)
5032 Continue
5033 }
5034 IfInString, LoopField, increased Spell Damage while wielding a Staff
5035 {
5036 LookupAffixAndSetInfoLine("data\jewel\SpellDamageWieldingStaff_Jewels.txt", "Prefix", ItemLevel, CurrValue)
5037 Continue
5038 }
5039 IfInString, LoopField, increased Spell Damage
5040 {
5041 LookupAffixAndSetInfoLine("data\jewel\SpellDamage_Jewels.txt", "Suffix", ItemLevel, CurrValue)
5042 Continue
5043 }
5044 IfInString, LoopField, increased maximum Mana
5045 {
5046 LookupAffixAndSetInfoLine("data\jewel\IncrMaximumMana_Jewel.txt", "Prefix", ItemLevel, CurrValue)
5047 Continue
5048 }
5049 IfInString, LoopField, increased Stun and Block Recovery
5050 {
5051 LookupAffixAndSetInfoLine("data\jewel\StunRecovery_Suffix_Jewels.txt", "Suffix", ItemLevel, CurrValue)
5052 Continue
5053 }
5054 IfInString, LoopField, increased Rarity
5055 {
5056 LookupAffixAndSetInfoLine("data\jewel\IIR_Suffix_Jewels.txt", "Suffix", ItemLevel, CurrValue)
5057 Continue
5058 }
5059 } ; End of Jewel Affixes
5060
5061
5062
5063
5064 ; Suffixes
5065
5066 IfInString, LoopField, increased Attack Speed
5067 {
5068 If (ItemSubType = "Wand" or ItemSubType = "Bow"){
5069 File := "data\AttackSpeed_BowsAndWands.txt"
5070 }
5071 Else If (ItemBaseType = "Weapon"){
5072 File := "data\AttackSpeed_Weapons.txt"
5073 }
5074 Else If (ItemSubType = "Shield"){
5075 File := "data\AttackSpeed_Shield.txt"
5076 }
5077 Else{
5078 File := "data\AttackSpeed_ArmourAndItems.txt"
5079 }
5080 LookupAffixAndSetInfoLine(File, "Suffix", ItemLevel, CurrValue)
5081 Continue
5082 }
5083 IfInString, LoopField, to all Attributes
5084 {
5085 If (ItemSubType = "Amulet"){
5086 LookupAffixAndSetInfoLine("data\ToAllAttributes_Amulet.txt", "Suffix", ItemLevel, CurrValue)
5087 Continue
5088 }
5089 Else{
5090 LookupAffixAndSetInfoLine("data\ToAllAttributes.txt", "Suffix", ItemLevel, CurrValue)
5091 Continue
5092 }
5093 }
5094 If RegExMatch(LoopField, ".*to (Strength|Dexterity|Intelligence)", match)
5095 {
5096 If ((match1 = "Strength" and ItemSubType = "Belt") or (match1 = "Dexterity" and (ItemSubType = "Gloves" or ItemSubType = "Quiver")) or (match1 = "Intelligence" and ItemSubType = "Helmet"))
5097 {
5098 LookupAffixAndSetInfoLine("data\To1Attribute_ilvl85.txt", "Suffix", ItemLevel, CurrValue)
5099 Continue
5100 }
5101 Else
5102 {
5103 LookupAffixAndSetInfoLine("data\To1Attribute.txt", "Suffix", ItemLevel, CurrValue)
5104 Continue
5105 }
5106 }
5107 IfInString, LoopField, increased Cast Speed
5108 {
5109 If (ItemGripType = "1H"){
5110 ; wands and scepters
5111 File := "data\CastSpeed_1H.txt"
5112 }
5113 Else If (ItemGripType = "2H"){
5114 ; staves
5115 File := "data\CastSpeed_2H.txt"
5116 }
5117 Else If (Item.IsAmulet){
5118 File := "data\CastSpeedAmulet.txt"
5119 }
5120 Else If (Item.IsRing){
5121 File := "data\CastSpeedRing.txt"
5122 }
5123 Else If (ItemSubType = "Shield"){
5124 ; The native mod only appears on bases with ES
5125 File := "data\CastSpeedShield.txt"
5126 }
5127 Else {
5128 ; All shields can receive a cast speed master mod.
5129 ; Leaving this as non shield specific if the master mod ever becomes applicable on something else
5130 File := "data\CastSpeedCraft.txt"
5131 }
5132 LookupAffixAndSetInfoLine(File, "Suffix", ItemLevel, CurrValue)
5133 Continue
5134 }
5135
5136 IfInString, LoopField, increased Critical Strike Chance for Spells
5137 {
5138 LookupAffixAndSetInfoLine("data\CritChanceSpells.txt", "Suffix", ItemLevel, CurrValue)
5139 Continue
5140 }
5141
5142 ; Pure Critical Strike Chance must be checked last
5143 IfInString, LoopField, Critical Strike Chance
5144 {
5145 If (ItemBaseType = "Weapon"){
5146 File := "data\CritChance_Weapon.txt"
5147 }
5148 Else If (ItemSubType = "Quiver"){
5149 File := "data\CritChance_Quiver.txt"
5150 }
5151 Else{
5152 File := "data\CritChance_Amulet.txt"
5153 }
5154 LookupAffixAndSetInfoLine(File, "Suffix", ItemLevel, CurrValue)
5155 Continue
5156 }
5157
5158 IfInString, LoopField, Critical Strike Multiplier
5159 {
5160 If (ItemBaseType = "Weapon"){
5161 File := "data\CritMultiplierLocal.txt"
5162 }
5163 Else{
5164 File := "data\CritMultiplierGlobal.txt"
5165 }
5166 LookupAffixAndSetInfoLine(File, "Suffix", ItemLevel, CurrValue)
5167 Continue
5168 }
5169 IfInString, LoopField, increased Light Radius
5170 {
5171 ; T1 comes with "#% increased Accuracy Rating", T2-3 with "+# to Accuracy Rating"
5172 ; This part can always be assigned now. The Accuracy will be solved later in case it's T2-3 and it forms a complex affix.
5173
5174 ; Taking the complicated function call here to use MakeAffixDetailLine with "CountAffixTotals=False".
5175 ; This way the mod is already written in case it's T1 and gets overwritten in case it's T2-3.
5176 ; We don't want to overcount the affixes by 0.5 when overwriting though,
5177 ; so we don't count them here (see also "increased Accuracy" right below).
5178 ValueRanges := LookupAffixData("data\LightRadius_AccuracyRating.txt", ItemLevel, CurrValue, CurrTier)
5179 AppendAffixInfo(MakeAffixDetailLine(LoopField, "Hybrid Suffix", ValueRanges, CurrTier, False), A_Index)
5180 Continue
5181 }
5182 IfInString, LoopField, increased Accuracy Rating
5183 {
5184 ; This variant comes always with Light Radius, see part right above.
5185 HasIncrLightRadius := False ; Second part is accounted for, no need to involve "+# to Accuracy Rating" or complex affixes.
5186 LookupAffixAndSetInfoLine("data\IncrAccuracyRating_LightRadius.txt", "Hybrid Suffix", ItemLevel, CurrValue)
5187 ; Now that we know that it's certainly T1 for "Light Radius" and complex affixes won't be involved,
5188 ; we count the 0.5 that we skipped at "Light Radius".
5189 AffixTotals.NumSuffixes += 0.5
5190 Continue
5191 }
5192 IfInString, LoopField, Chance to Block
5193 {
5194 If (not HasChanceToBlockStrShield){
5195 LookupAffixAndSetInfoLine("data\BlockChance.txt", "Suffix", ItemLevel, CurrValue)
5196 }
5197 Continue
5198 }
5199 ; Flask affixes (on belts)
5200 IfInString, LoopField, reduced Flask Charges used
5201 {
5202 LookupAffixAndSetInfoLine("data\FlaskChargesUsed.txt", "Suffix", ItemLevel, CurrValue)
5203 Continue
5204 }
5205 IfInString, LoopField, increased Flask Charges gained
5206 {
5207 LookupAffixAndSetInfoLine("data\FlaskChargesGained.txt", "Suffix", ItemLevel, CurrValue)
5208 Continue
5209 }
5210 IfInString, LoopField, increased Flask effect duration
5211 {
5212 LookupAffixAndSetInfoLine("data\FlaskDuration.txt", "Suffix", ItemLevel, CurrValue)
5213 Continue
5214 }
5215
5216 IfInString, LoopField, increased Quantity of Items found
5217 {
5218 If (Item.IsShaperBase)
5219 {
5220 File := ["75|4-7","85|8-10"]
5221 }
5222 Else
5223 {
5224 File := "data\IncrQuantity.txt"
5225 }
5226 LookupAffixAndSetInfoLine(File, "Suffix", ItemLevel, CurrValue)
5227 Continue
5228 }
5229 IfInString, LoopField, Life gained on Kill
5230 {
5231 LookupAffixAndSetInfoLine("data\LifeOnKill.txt", "Suffix", ItemLevel, CurrValue)
5232 Continue
5233 }
5234 IfInString, LoopField, Life gained for each Enemy hit ; Cuts off the rest to accommodate both "by Attacks" and "by your Attacks"
5235 {
5236 If (ItemBaseType = "Weapon") {
5237 File := "data\LifeOnHit_Weapon.txt"
5238 }
5239 Else If (ItemSubType = "Amulet"){
5240 File := "data\LifeOnHit_Amulet.txt"
5241 }
5242 Else {
5243 File := "data\LifeOnHit_GlovesRing.txt"
5244 }
5245 LookupAffixAndSetInfoLine(File, "Suffix", ItemLevel, CurrValue)
5246 Continue
5247 }
5248 IfInString, LoopField, of Life Regenerated per second
5249 {
5250 LookupAffixAndSetInfoLine("data\LifeRegenPercent.txt", "Suffix", ItemLevel, CurrValue)
5251 Continue
5252 }
5253 IfInString, LoopField, Life Regenerated per second
5254 {
5255 If (ItemSubType = "BodyArmour"){
5256 LookupAffixAndSetInfoLine("data\LifeRegen_BodyArmour.txt", "Suffix", ItemLevel, CurrValue)
5257 Continue
5258 }
5259 Else{
5260 LookupAffixAndSetInfoLine("data\LifeRegen.txt", "Suffix", ItemLevel, CurrValue)
5261 Continue
5262 }
5263 }
5264 IfInString, LoopField, Mana Gained on Kill
5265 {
5266 LookupAffixAndSetInfoLine("data\ManaOnKill.txt", "Suffix", ItemLevel, CurrValue)
5267 Continue
5268 }
5269 IfInString, LoopField, increased Mana Regeneration Rate
5270 {
5271 LookupAffixAndSetInfoLine("data\ManaRegen.txt", "Suffix", ItemLevel, CurrValue)
5272 Continue
5273 }
5274 IfInString, LoopField, increased Projectile Speed
5275 {
5276 LookupAffixAndSetInfoLine("data\ProjectileSpeed.txt", "Suffix", ItemLevel, CurrValue)
5277 Continue
5278 }
5279 IfInString, LoopField, reduced Attribute Requirements
5280 {
5281 LookupAffixAndSetInfoLine("data\ReducedAttrReqs.txt", "Suffix", ItemLevel, CurrValue)
5282 Continue
5283 }
5284 IfInString, LoopField, to all Elemental Resistances
5285 {
5286 If (ItemSubType = "Amulet"){
5287 LookupAffixAndSetInfoLine("data\ToAllResist_Amulet.txt", "Suffix", ItemLevel, CurrValue)
5288 Continue
5289 }
5290 Else{
5291 LookupAffixAndSetInfoLine("data\ToAllResist.txt", "Suffix", ItemLevel, CurrValue)
5292 Continue
5293 }
5294 }
5295 IfInString, LoopField, to Fire Resistance
5296 {
5297 LookupAffixAndSetInfoLine("data\ToFireResist.txt", "Suffix", ItemLevel, CurrValue)
5298 Continue
5299 }
5300 IfInString, LoopField, to Cold Resistance
5301 {
5302 LookupAffixAndSetInfoLine("data\ToColdResist.txt", "Suffix", ItemLevel, CurrValue)
5303 Continue
5304 }
5305 IfInString, LoopField, to Lightning Resistance
5306 {
5307 LookupAffixAndSetInfoLine("data\ToLightningResist.txt", "Suffix", ItemLevel, CurrValue)
5308 Continue
5309 }
5310 IfInString, LoopField, to Chaos Resistance
5311 {
5312 LookupAffixAndSetInfoLine("data\ToChaosResist.txt", "Suffix", ItemLevel, CurrValue)
5313 Continue
5314 }
5315 IfInString, LoopField, increased Stun Duration on Enemies
5316 {
5317 LookupAffixAndSetInfoLine("data\StunDuration.txt", "Suffix", ItemLevel, CurrValue)
5318 Continue
5319 }
5320 IfInString, LoopField, reduced Enemy Stun Threshold
5321 {
5322 LookupAffixAndSetInfoLine("data\StunThreshold.txt", "Suffix", ItemLevel, CurrValue)
5323 Continue
5324 }
5325 IfInString, LoopField, additional Physical Damage Reduction
5326 {
5327 LookupAffixAndSetInfoLine("data\AdditionalPhysicalDamageReduction.txt", "Suffix", ItemLevel, CurrValue)
5328 Continue
5329 }
5330 IfInString, LoopField, chance to Dodge Attacks
5331 {
5332 If (ItemSubType = "BodyArmour")
5333 {
5334 LookupAffixAndSetInfoLine("data\ChanceToDodgeAttacks_BodyArmour.txt", "Suffix", ItemLevel, CurrValue)
5335 Continue
5336 }
5337 Else If (ItemSubType = "Shield")
5338 {
5339 LookupAffixAndSetInfoLine("data\ChanceToDodgeAttacks_Shield.txt", "Suffix", ItemLevel, CurrValue)
5340 Continue
5341 }
5342 }
5343 IfInString, LoopField, of Energy Shield Regenerated per second
5344 {
5345 LookupAffixAndSetInfoLine("data\EnergyShieldRegeneratedPerSecond.txt", "Suffix", ItemLevel, CurrValue)
5346 Continue
5347 }
5348 IfInString, LoopField, additional Block Chance against Projectiles
5349 {
5350 LookupAffixAndSetInfoLine("data\AdditionalBlockChanceAgainstProjectiles.txt", "Suffix", ItemLevel, CurrValue)
5351 Continue
5352 }
5353 IfInString, LoopField, chance to Avoid being Stunned
5354 {
5355 LookupAffixAndSetInfoLine("data\ChanceToAvoidBeingStunned.txt", "Suffix", ItemLevel, CurrValue)
5356 Continue
5357 }
5358 IfInString, LoopField, chance to Avoid Elemental Ailments
5359 {
5360 LookupAffixAndSetInfoLine("data\ChanceToAvoidElementalAilments.txt", "Suffix", ItemLevel, CurrValue)
5361 Continue
5362 }
5363 IfInString, LoopField, chance to Dodge Spell Damage
5364 {
5365 LookupAffixAndSetInfoLine("data\ChanceToDodgeSpellDamage.txt", "Suffix", ItemLevel, CurrValue)
5366 Continue
5367 }
5368 IfInString, LoopField, Chance to Block Spells
5369 {
5370 LookupAffixAndSetInfoLine("data\ChanceToBlockSpells.txt", "Suffix", ItemLevel, CurrValue)
5371 Continue
5372 }
5373 IfInString, LoopField, Life gained when you Block
5374 {
5375 LookupAffixAndSetInfoLine("data\LifeOnBlock.txt", "Suffix", ItemLevel, CurrValue)
5376 Continue
5377 }
5378 IfInString, LoopField, Mana gained when you Block
5379 {
5380 LookupAffixAndSetInfoLine("data\ManaOnBlock.txt", "Suffix", ItemLevel, CurrValue)
5381 Continue
5382 }
5383 IfInString, LoopField, increased Attack and Cast Speed
5384 {
5385 LookupAffixAndSetInfoLine("data\AttackAndCastSpeed_Shield.txt", "Suffix", ItemLevel, CurrValue)
5386 Continue
5387 }
5388 If (ItemBaseType = "Weapon")
5389 {
5390 IfInString, LoopField, Chance to Ignite
5391 {
5392 LookupAffixAndSetInfoLine("data\ChanceToIgnite.txt", "Suffix", ItemLevel, CurrValue)
5393 Continue
5394 }
5395 IfInString, LoopField, Chance to Freeze
5396 {
5397 LookupAffixAndSetInfoLine("data\ChanceToFreeze.txt", "Suffix", ItemLevel, CurrValue)
5398 Continue
5399 }
5400 IfInString, LoopField, Chance to Shock
5401 {
5402 LookupAffixAndSetInfoLine("data\ChanceToShock.txt", "Suffix", ItemLevel, CurrValue)
5403 Continue
5404 }
5405 IfInString, LoopField, chance to cause Bleeding on Hit
5406 {
5407 If (CurrValue = 25)
5408 {
5409 ; Vagan/Tora prefix
5410 AppendAffixInfo(MakeAffixDetailLine(LoopField, "Prefix", "Vagan 7 or Buy:Tora 4", ""), A_Index)
5411 Continue
5412 }
5413
5414 LookupAffixAndSetInfoLine("data\ChanceToBleed.txt", "Suffix", ItemLevel, CurrValue)
5415 Continue
5416 }
5417 IfInString, LoopField, chance to Poison on Hit
5418 {
5419 LookupAffixAndSetInfoLine("data\ChanceToPoison.txt", "Suffix", ItemLevel, CurrValue)
5420 Continue
5421 }
5422 IfInString, LoopField, increased Burning Damage
5423 {
5424 LookupAffixAndSetInfoLine("data\IncrBurningDamage.txt", "Suffix", ItemLevel, CurrValue)
5425 Continue
5426 }
5427 IfInString, LoopField, increased Damage with Poison
5428 {
5429 LookupAffixAndSetInfoLine("data\IncrPoisonDamage.txt", "Suffix", ItemLevel, CurrValue)
5430 Continue
5431 }
5432 IfInString, LoopField, increased Damage with Bleeding
5433 {
5434 LookupAffixAndSetInfoLine("data\IncrBleedingDamage.txt", "Suffix", ItemLevel, CurrValue)
5435 Continue
5436 }
5437 IfInString, LoopField, increased Poison Duration
5438 {
5439 LookupAffixAndSetInfoLine("data\PoisonDuration.txt", "Suffix", ItemLevel, CurrValue)
5440 Continue
5441 }
5442 IfInString, LoopField, increased Bleed duration
5443 {
5444 LookupAffixAndSetInfoLine("data\BleedDuration.txt", "Suffix", ItemLevel, CurrValue)
5445 Continue
5446 }
5447 }
5448
5449
5450 ; Prefixes
5451
5452 If RegExMatch(LoopField, "Adds \d+? to \d+? Physical Damage")
5453 {
5454 If (ItemBaseType = "Weapon")
5455 {
5456 If (ItemGripType = "1H"){
5457 File := "data\AddsPhysDamage_1H.txt"
5458 }
5459 Else{
5460 File := "data\AddsPhysDamage_2H.txt"
5461 }
5462 }
5463 Else If (ItemSubType = "Amulet"){
5464 File := "data\AddsPhysDamage_Amulet.txt"
5465 }
5466 Else If (ItemSubType = "Quiver"){
5467 File := "data\AddsPhysDamage_Quivers.txt"
5468 }
5469 Else If (ItemSubType = "Ring"){
5470 File := "data\AddsPhysDamage_Ring.txt"
5471 }
5472 Else If (ItemSubType = "Gloves"){
5473 File := "data\AddsPhysDamage_Gloves.txt"
5474 }
5475 Else{
5476 ; There is no Else for rare items. Just lookup in 1H for now...
5477 File := "data\AddsPhysDamage_1H.txt"
5478 }
5479 LookupAffixAndSetInfoLine(File, "Prefix", ItemLevel, CurrValue)
5480 Continue
5481 }
5482
5483 If RegExMatch(LoopField, "Adds \d+? to \d+? Cold Damage")
5484 {
5485 If RegExMatch(LoopField, "Adds \d+? to \d+? Cold Damage to Spells")
5486 {
5487 If (ItemGripType = "1H"){
5488 File := "data\SpellAddsCold_1H.txt"
5489 }
5490 Else{
5491 File := "data\SpellAddsCold_2H.txt"
5492 }
5493 }
5494 Else If (ItemSubType = "Amulet" or ItemSubType = "Ring"){
5495 File := "data\AddsColdDamage_AmuletRing.txt"
5496 }
5497 Else If (ItemSubType = "Gloves"){
5498 File := "data\AddsColdDamage_Gloves.txt"
5499 }
5500 Else If (ItemSubType = "Quiver"){
5501 File := "data\AddsColdDamage_Quivers.txt"
5502 }
5503 Else If ((ItemGripType = "1H") or (ItemSubType = "Bow")){
5504 ; Added damage for bows follows 1H tiers
5505 File := "data\AddsColdDamage_1H.txt"
5506 }
5507 Else{
5508 File := "data\AddsColdDamage_2H.txt"
5509 }
5510 LookupAffixAndSetInfoLine(File, "Prefix", ItemLevel, CurrValue)
5511 Continue
5512 }
5513
5514 If RegExMatch(LoopField, "Adds \d+? to \d+? Fire Damage")
5515 {
5516 If RegExMatch(LoopField, "Adds \d+? to \d+? Fire Damage to Spells")
5517 {
5518 If (ItemGripType = "1H"){
5519 File := "data\SpellAddsFire_1H.txt"
5520 }
5521 Else{
5522 File := "data\SpellAddsFire_2H.txt"
5523 }
5524 }
5525 Else If (ItemSubType = "Amulet" or ItemSubType = "Ring"){
5526 File := "data\AddsFireDamage_AmuletRing.txt"
5527 }
5528 Else If (ItemSubType = "Gloves"){
5529 File := "data\AddsFireDamage_Gloves.txt"
5530 }
5531 Else If (ItemSubType = "Quiver"){
5532 File := "data\AddsFireDamage_Quivers.txt"
5533 }
5534 Else If ((ItemGripType = "1H") or (ItemSubType = "Bow")){
5535 ; Added damage for bows follows 1H tiers
5536 File := "data\AddsFireDamage_1H.txt"
5537 }
5538 Else{
5539 File := "data\AddsFireDamage_2H.txt"
5540 }
5541 LookupAffixAndSetInfoLine(File, "Prefix", ItemLevel, CurrValue)
5542 Continue
5543 }
5544
5545 If RegExMatch(LoopField, "Adds \d+? to \d+? Lightning Damage")
5546 {
5547 If RegExMatch(LoopField, "Adds \d+? to \d+? Lightning Damage to Spells")
5548 {
5549 If (ItemGripType = "1H"){
5550 File := "data\SpellAddsLightning_1H.txt"
5551 }
5552 Else{
5553 File := "data\SpellAddsLightning_2H.txt"
5554 }
5555 }
5556 Else If (ItemSubType = "Amulet" or ItemSubType = "Ring"){
5557 File := "data\AddsLightningDamage_AmuletRing.txt"
5558 }
5559 Else If (ItemSubType = "Gloves"){
5560 File := "data\AddsLightningDamage_Gloves.txt"
5561 }
5562 Else If (ItemSubType = "Quiver"){
5563 File := "data\AddsLightningDamage_Quivers.txt"
5564 }
5565 Else If ((ItemGripType = "1H") or (ItemSubType = "Bow")){
5566 ; Added damage for bows follows 1H tiers
5567 File := "data\AddsLightningDamage_1H.txt"
5568 }
5569
5570 Else{
5571 File := "data\AddsLightningDamage_2H.txt"
5572 }
5573 LookupAffixAndSetInfoLine(File, "Prefix", ItemLevel, CurrValue)
5574 Continue
5575 }
5576
5577 If RegExMatch(LoopField, "Adds \d+? to \d+? Chaos Damage")
5578 {
5579 If ((ItemGripType = "1H") or (ItemSubType = "Bow")){
5580 ; Added damage for bows follows 1H tiers
5581 File := "data\AddsChaosDamage_1H.txt"
5582 }
5583 Else If (ItemGripType = "2H"){
5584 File := "data\AddsChaosDamage_2H.txt"
5585 }
5586 Else If (ItemSubType = "Amulet" or ItemSubType = "Ring"){
5587 ; Master modded prefix
5588 File := "data\AddsChaosDamage_AmuletRing.txt"
5589 }
5590 LookupAffixAndSetInfoLine(File, "Prefix", ItemLevel, CurrValue)
5591 Continue
5592 }
5593
5594 IfInString, LoopField, increased maximum Energy Shield
5595 {
5596 ; Contrary to %Armour and %Evasion this one has a unique wording due to "maximum" and is clearly from Amulets (or Legacy Rings)
5597 LookupAffixAndSetInfoLine("data\IncrMaxEnergyShield_Amulet.txt", "Prefix", ItemLevel, CurrValue)
5598 Continue
5599 }
5600
5601 IfInString, LoopField, Physical Damage to Melee Attackers
5602 {
5603 LookupAffixAndSetInfoLine("data\PhysDamagereturn.txt", "Prefix", ItemLevel, CurrValue)
5604 Continue
5605 }
5606
5607 IfInString, LoopField, to Level of Socketed
5608 {
5609 If RegExMatch(LoopField, "(Fire|Cold|Lightning)"){
5610 File := "data\GemLevel_Elemental.txt"
5611 }
5612 Else If (InStr(LoopField, "Chaos")){
5613 File := "data\GemLevel_Chaos.txt"
5614 }
5615 Else If (InStr(LoopField, "Bow")){
5616 File := "data\GemLevel_Bow.txt"
5617 }
5618 Else If (InStr(LoopField, "Melee")){
5619 File := "data\GemLevel_Melee.txt"
5620 }
5621 Else If (InStr(LoopField, "Minion")){
5622 File := "data\GemLevel_Minion.txt"
5623 }
5624 ; Catarina prefix
5625 Else If (InStr(LoopField, "Support")){
5626 File := "data\GemLevel_Support.txt"
5627 }
5628 Else If (InStr(LoopField, "Socketed Gems"))
5629 {
5630 If (ItemSubType = "Ring"){
5631 File := "data\GemLevel_UnsetRing.txt"
5632 }
5633 Else{
5634 File := "data\GemLevel.txt"
5635 }
5636 }
5637 LookupAffixAndSetInfoLine(File, "Prefix", ItemLevel, CurrValue)
5638 Continue
5639 }
5640 IfInString, LoopField, Physical Attack Damage Leeched as
5641 {
5642 LookupAffixAndSetInfoLine("data\PhysicalAttackDamageLeeched.txt", "Prefix", ItemLevel, CurrValue)
5643 Continue
5644 }
5645 IfInString, LoopField, Movement Speed
5646 {
5647 If (ItemSubType = "Boots")
5648 {
5649 LookupAffixAndSetInfoLine("data\MovementSpeed_Boots.txt", "Prefix", ItemLevel, CurrValue)
5650 Continue
5651 }
5652 Else If (ItemSubType = "Belt")
5653 {
5654 LookupAffixAndSetInfoLine("data\MovementSpeed_Belt.txt", "Prefix", ItemLevel, CurrValue)
5655 Continue
5656 }
5657 }
5658 IfInString, LoopField, increased Elemental Damage with Attack Skills
5659 {
5660 If (ItemBaseType = "Weapon"){
5661 ; Because GGG apparently thought having the exact same iLvls and tiers except for one single percentage point is necessary. T1-2: 31-37|38-42 vs. 31-36|37-42
5662 LookupAffixAndSetInfoLine("data\IncrElementalDamageWithAttackSkills_Weapon.txt", "Prefix", ItemLevel, CurrValue)
5663 Continue
5664 }
5665 Else If (ItemSubType = "Ring"){
5666 LookupAffixAndSetInfoLine("data\IncrElementalDamageWithAttackSkills_Ring.txt", "Prefix", ItemLevel, CurrValue)
5667 Continue
5668 }
5669 Else{
5670 ; Amulet, Belt, Quiver
5671 LookupAffixAndSetInfoLine("data\IncrElementalDamageWithAttackSkills.txt", "Prefix", ItemLevel, CurrValue)
5672 Continue
5673 }
5674 }
5675 ; Flask effects (on belts)
5676 IfInString, LoopField, increased Flask Mana Recovery rate
5677 {
5678 LookupAffixAndSetInfoLine("data\FlaskManaRecoveryRate.txt", "Prefix", ItemLevel, CurrValue)
5679 Continue
5680 }
5681 IfInString, LoopField, increased Flask Life Recovery rate
5682 {
5683 LookupAffixAndSetInfoLine("data\FlaskLifeRecoveryRate.txt", "Prefix", ItemLevel, CurrValue)
5684 Continue
5685 }
5686 If (ItemSubType = "Shield"){
5687 IfInString, LoopField, increased Global Physical Damage
5688 {
5689 HasIncrPhysDmg := False ; No worries about hybrid here.
5690 LookupAffixAndSetInfoLine("data\IncrPhysDamage_Shield.txt", "Prefix", ItemLevel, CurrValue)
5691 Continue
5692 }
5693 IfInString, LoopField, increased Elemental Damage
5694 {
5695 LookupAffixAndSetInfoLine("data\IncrEleDamage_Shield.txt", "Prefix", ItemLevel, CurrValue)
5696 Continue
5697 }
5698 IfInString, LoopField, increased Attack Damage
5699 {
5700 LookupAffixAndSetInfoLine("data\IncrAttackDamage_Shield.txt", "Prefix", ItemLevel, CurrValue)
5701 Continue
5702 }
5703 }
5704
5705 ; --- MASTER CRAFT/BUY ONLY AFFIXES ---
5706
5707 ; Can be either Leo prefix or jewel suffix. Jewels are checked already, so it's Leo.
5708 If RegExMatch(LoopField, ".*increased Damage$")
5709 {
5710 LookupAffixAndSetInfoLine("data\IncrDamageLeo.txt", "Prefix", ItemLevel, CurrValue)
5711 Continue
5712 }
5713 ; Haku prefix
5714 IfInString, LoopField, to Quality of Socketed Support Gems
5715 {
5716 LookupAffixAndSetInfoLine("data\GemQuality_Support.txt", "Prefix", ItemLevel, CurrValue)
5717 Continue
5718 }
5719 ; Elreon prefix
5720 IfInString, LoopField, to Mana Cost of Skills
5721 {
5722 CurrValue := Abs(CurrValue) ; Turn potentially negative number into positive.
5723 LookupAffixAndSetInfoLine("data\ManaCostOfSkills.txt", "Prefix", ItemLevel, CurrValue)
5724 Continue
5725 }
5726 ; Vorici prefix
5727 IfInString, LoopField, increased Life Leeched per Second
5728 {
5729 LookupAffixAndSetInfoLine("data\LifeLeechedPerSecond.txt", "Prefix", ItemLevel, CurrValue)
5730 Continue
5731 }
5732 ; Tora dual suffixes
5733 IfInString, LoopField, increased Trap Throwing Speed
5734 {
5735 LookupAffixAndSetInfoLine("data\IncrTrapThrowingMineLayingSpeed.txt", "Hybrid Suffix", ItemLevel, CurrValue)
5736 Continue
5737 }
5738 IfInString, LoopField, increased Mine Laying Speed
5739 {
5740 LookupAffixAndSetInfoLine("data\IncrTrapThrowingMineLayingSpeed.txt", "Hybrid Suffix", ItemLevel, CurrValue)
5741 Continue
5742 }
5743 IfInString, LoopField, increased Trap Damage
5744 {
5745 LookupAffixAndSetInfoLine("data\IncrTrapMineDamage.txt", "Hybrid Suffix", ItemLevel, CurrValue)
5746 Continue
5747 }
5748 IfInString, LoopField, increased Mine Damage
5749 {
5750 LookupAffixAndSetInfoLine("data\IncrTrapMineDamage.txt", "Hybrid Suffix", ItemLevel, CurrValue)
5751 Continue
5752 }
5753 ; Vagan suffix
5754 IfInString, LoopField, to Weapon range
5755 {
5756 LookupAffixAndSetInfoLine("data\ToWeaponRange.txt", "Suffix", ItemLevel, CurrValue)
5757 }
5758
5759
5760
5761 IfInString, LoopField, Gems in this item are Supported by Lvl 1 Blood Magic
5762 {
5763 AppendAffixInfo(MakeAffixDetailLine(LoopField, "Prefix", "", ""), A_Index)
5764 Continue
5765 }
5766 IfInString, LoopField, Hits can't be Evaded
5767 {
5768 AppendAffixInfo(MakeAffixDetailLine(LoopField, "Prefix", "", ""), A_Index)
5769 Continue
5770 }
5771
5772
5773 ; Meta Craft Mods
5774
5775 IfInString, LoopField, Can have multiple Crafted Mods
5776 {
5777 AppendAffixInfo(MakeAffixDetailLine(LoopField, "Suffix", "", ""), A_Index)
5778 Continue
5779 }
5780 IfInString, LoopField, Prefixes Cannot Be Changed
5781 {
5782 AppendAffixInfo(MakeAffixDetailLine(LoopField, "Suffix", "", ""), A_Index)
5783 Continue
5784 }
5785 IfInString, LoopField, Suffixes Cannot Be Changed
5786 {
5787 AppendAffixInfo(MakeAffixDetailLine(LoopField, "Prefix", "", ""), A_Index)
5788 Continue
5789 }
5790 IfInString, LoopField, Cannot roll Attack Mods
5791 {
5792 AppendAffixInfo(MakeAffixDetailLine(LoopField, "Suffix", "", ""), A_Index)
5793 Continue
5794 }
5795 IfInString, LoopField, Cannot roll Caster Mods
5796 {
5797 AppendAffixInfo(MakeAffixDetailLine(LoopField, "Suffix", "", ""), A_Index)
5798 Continue
5799 }
5800 IfInString, LoopField, Cannot roll Mods with Required Lvl above Lvl 28
5801 {
5802 AppendAffixInfo(MakeAffixDetailLine(LoopField, "Suffix", "", ""), A_Index)
5803 Continue
5804 }
5805 }
5806
5807
5808 ; --- COMPLEX AFFIXES ---
5809
5810 If (HasIncreasedAccuracyRating or HasIncreasedGlobalCritChance and (Item.SubType = "Viridian Jewel" or Item.SubType = "Prismatic Jewel") )
5811 {
5812 FileAccu := "data\jewel\IncrAccuracyRating_Jewels.txt"
5813 FileCrit := "data\jewel\CritChanceGlobal_Jewels.txt"
5814 FileAccuHyb := "data\jewel\CritChanceGlobal_IncrAcc_Jewels.txt"
5815 FileCritHyb := "data\jewel\IncrAccuracyRating_CritChance_Jewels.txt"
5816
5817 If (HasIncreasedAccuracyRating and HasIncreasedGlobalCritChance)
5818 {
5819 LineNum1 := HasIncreasedAccuracyRating
5820 Value1 := Itemdata.AffixTextLines[LineNum1].Value
5821 LineNum2 := HasIncreasedGlobalCritChance
5822 Value2 := Itemdata.AffixTextLines[LineNum2].Value
5823 SolveAffixes_Mod1Mod2Hyb("AccuCritJewel", LineNum1, LineNum2, Value1, Value2, "Suffix", "Suffix", "Hybrid Suffix", FileAccu, FileCrit, FileAccuHyb, FileCritHyb, ItemLevel)
5824 }
5825 Else If (HasIncreasedAccuracyRating)
5826 {
5827 LineNum := HasIncreasedAccuracyRating
5828 LineTxt := Itemdata.AffixTextLines[LineNum].Text
5829 Value := Itemdata.AffixTextLines[LineNum].Value
5830 LookupAffixAndSetInfoLine(FileAccu, "Suffix", ItemLevel, Value, LineTxt, LineNum)
5831 }
5832 Else If (HasIncreasedGlobalCritChance)
5833 {
5834 LineNum := HasIncreasedGlobalCritChance
5835 LineTxt := Itemdata.AffixTextLines[LineNum].Text
5836 Value := Itemdata.AffixTextLines[LineNum].Value
5837 LookupAffixAndSetInfoLine(FileCrit, "Suffix", ItemLevel, Value, LineTxt, LineNum)
5838 }
5839 }
5840
5841
5842 If (HasIncrRarity)
5843 {
5844 If (ItemSubType = "Amulet" or ItemSubType = "Ring")
5845 {
5846 FilePrefix := "data\IncrRarity_Prefix_AmuletRing.txt"
5847 FileSuffix := "data\IncrRarity_Suffix_AmuletRingHelmet.txt"
5848
5849 If (HasIncrRarityCraft)
5850 {
5851 FileSuffix := "data\IncrRarity_Suffix_Craft.txt"
5852
5853 LineNum := HasIncrRarityCraft
5854 LineTxt := Itemdata.AffixTextLines[LineNum].Text
5855 Value := Itemdata.AffixTextLines[LineNum].Value
5856 LookupAffixAndSetInfoLine(FileSuffix, "Suffix", ItemLevel, Value, LineTxt, LineNum)
5857
5858 LineNum := HasIncrRarity
5859 LineTxt := Itemdata.AffixTextLines[LineNum].Text
5860 Value := Itemdata.AffixTextLines[LineNum].Value
5861 LookupAffixAndSetInfoLine(FilePrefix, "Prefix", ItemLevel, Value, LineTxt, LineNum)
5862 }
5863
5864 Else
5865 {
5866 LineNum := HasIncrRarity
5867 LineTxt := Itemdata.AffixTextLines[LineNum].Text
5868 Value := Itemdata.AffixTextLines[LineNum].Value
5869
5870 SolveAffixes_PreSuf("Rarity", LineNum, Value, FilePrefix, FileSuffix, ItemLevel)
5871 }
5872 }
5873
5874 Else If (ItemSubType = "Helmet")
5875 {
5876 FilePrefix := "data\IncrRarity_Prefix_Helmet.txt"
5877 FileSuffix := "data\IncrRarity_Suffix_AmuletRingHelmet.txt"
5878
5879 LineNum := HasIncrRarity
5880 LineTxt := Itemdata.AffixTextLines[LineNum].Text
5881 Value := Itemdata.AffixTextLines[LineNum].Value
5882
5883 SolveAffixes_PreSuf("Rarity", LineNum, Value, FilePrefix, FileSuffix, ItemLevel)
5884 }
5885
5886 Else If (ItemSubType = "Gloves" or ItemSubType = "Boots")
5887 {
5888 FilePrefix := "data\IncrRarity_Prefix_GlovesBoots.txt"
5889 FileSuffix := "data\IncrRarity_Suffix_GlovesBoots.txt"
5890
5891 LineNum := HasIncrRarity
5892 LineTxt := Itemdata.AffixTextLines[LineNum].Text
5893 Value := Itemdata.AffixTextLines[LineNum].Value
5894
5895 SolveAffixes_PreSuf("Rarity", LineNum, Value, FilePrefix, FileSuffix, ItemLevel)
5896 }
5897 }
5898
5899
5900 If (HasToMaxLife or (HasToArmour or HasToEvasion or HasToMaxES) )
5901 {
5902 If (ItemSubType = "BodyArmour" or ItemSubType = "Shield")
5903 {
5904 FileToArmour := "data\ToArmour_BodyArmourShield.txt"
5905 FileToArmourHyb := "data\ToArmour_BodyArmourShield_HybridBase.txt"
5906 FileToEvasion := "data\ToEvasion_BodyArmourShield.txt"
5907 FileToEvasionHyb := "data\ToEvasion_BodyArmourShield_HybridBase.txt"
5908 FileToMaxES := (ItemSubType = "BodyArmour") ? "data\ToMaxES_BodyArmour.txt" : "data\ToMaxES_Shield.txt"
5909 FileToMaxESHyb := "data\ToMaxES_BodyArmourShield_HybridBase.txt"
5910 }
5911 Else If (ItemSubType = "Helmet")
5912 {
5913 FileToArmour := "data\ToArmour_Helmet.txt"
5914 FileToArmourHyb := "data\ToArmour_Helmet_HybridBase.txt"
5915 FileToEvasion := "data\ToEvasion_Helmet.txt"
5916 FileToEvasionHyb := "data\ToEvasion_Helmet_HybridBase.txt"
5917 FileToMaxES := "data\ToMaxES_Helmet.txt"
5918 FileToMaxESHyb := "data\ToMaxES_Helmet_HybridBase.txt"
5919 }
5920 Else If (ItemSubType = "Gloves" or ItemSubType = "Boots")
5921 {
5922 FileToArmour := "data\ToArmour_GlovesBoots.txt"
5923 FileToArmourHyb := "data\ToArmour_GlovesBoots_HybridBase.txt"
5924 FileToEvasion := "data\ToEvasion_GlovesBoots.txt"
5925 FileToEvasionHyb := "data\ToEvasion_GlovesBoots_HybridBase.txt"
5926 FileToMaxES := "data\ToMaxES_GlovesBoots.txt"
5927 FileToMaxESHyb := "data\ToMaxES_GlovesBoots_HybridBase.txt"
5928 }
5929 Else
5930 {
5931 FileToArmour := "data\ToArmour_Belt.txt"
5932 FileToEvasion := "data\ToEvasion_Ring.txt"
5933 FileToMaxES := (ItemSubType = "Ring") ? "data\ToMaxES_Ring.txt" : "data\ToMaxES_AmuletBelt.txt"
5934 }
5935
5936 If (ItemSubType = "BodyArmour")
5937 {
5938 FileToArmourMaxLife := "data\ToArmour_MaxLife_BodyArmour.txt"
5939 FileToEvasionMaxLife := "data\ToEvasion_MaxLife_BodyArmour.txt"
5940 FileToMaxESMaxLife := "data\ToMaxES_MaxLife_BodyArmour.txt"
5941 FileMaxLifeToDef := "data\MaxLife_ToDef_BodyArmour.txt"
5942 }
5943 Else If (ItemSubType = "Shield" or ItemSubType = "Helmet")
5944 {
5945 FileToArmourMaxLife := "data\ToArmour_MaxLife_ShieldHelmet.txt"
5946 FileToEvasionMaxLife := "data\ToEvasion_MaxLife_ShieldHelmet.txt"
5947 FileToMaxESMaxLife := "data\ToMaxES_MaxLife_ShieldHelmet.txt"
5948 FileMaxLifeToDef := "data\MaxLife_ToDef_ShieldHelmet.txt"
5949 }
5950 Else If (ItemSubType = "Gloves" or ItemSubType = "Boots")
5951 {
5952 FileToArmourMaxLife := "data\ToArmour_MaxLife_GlovesBoots.txt"
5953 FileToEvasionMaxLife := "data\ToEvasion_MaxLife_GlovesBoots.txt"
5954 FileToMaxESMaxLife := "data\ToMaxES_MaxLife_GlovesBoots.txt"
5955 FileMaxLifeToDef := "data\MaxLife_ToDef_GlovesBoots.txt"
5956 }
5957
5958 If (ItemSubType = "Amulet" or ItemSubType = "Boots" or ItemSubType = "Gloves"){
5959 FileMaxLife := "data\MaxLife_AmuletBootsGloves.txt"
5960 }
5961 Else If (ItemSubType = "Belt" or ItemSubType = "Helmet" or ItemSubType = "Quiver"){
5962 FileMaxLife := "data\MaxLife_BeltHelmetQuiver.txt"
5963 }
5964 Else If (ItemSubType = "BodyArmour"){
5965 FileMaxLife := "data\MaxLife_BodyArmour.txt"
5966 }
5967 Else If (ItemSubType = "Shield"){
5968 FileMaxLife := "data\MaxLife_Shield.txt"
5969 }
5970 Else If (ItemSubType = "Ring"){
5971 FileMaxLife := "data\MaxLife_Ring.txt"
5972 }
5973 Else{
5974 FileMaxLife := "data\MaxLife.txt"
5975 }
5976
5977 If (HasToMaxLife and (HasToArmour or HasToEvasion or HasToMaxES) and (ItemBaseType = "Armour"))
5978 {
5979 If (HasToMaxLifeCraft)
5980 {
5981 LineNum := HasToMaxLifeCraft
5982 LineTxt := Itemdata.AffixTextLines[LineNum].Text
5983 Value := Itemdata.AffixTextLines[LineNum].Value
5984 LookupAffixAndSetInfoLine(FileMaxLife, "Crafted Prefix", ItemLevel, Value, LineTxt, LineNum)
5985
5986 FileMaxLife := False ; indirectly invalidating the pure life mod for other calculations.
5987 }
5988 If (HasToArmourCraft)
5989 {
5990 LineNum := HasToArmourCraft
5991 LineTxt := Itemdata.AffixTextLines[LineNum].Text
5992 Value := Itemdata.AffixTextLines[LineNum].Value
5993 LookupAffixAndSetInfoLine(FileToArmour, "Crafted Prefix", ItemLevel, Value, LineTxt, LineNum)
5994
5995 FileToArmour := False ; indirectly invalidating the pure Armour mod for other calculations.
5996 }
5997 If (HasToEvasionCraft)
5998 {
5999 LineNum := HasToEvasionCraft
6000 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6001 Value := Itemdata.AffixTextLines[LineNum].Value
6002 LookupAffixAndSetInfoLine(FileToEvasion, "Crafted Prefix", ItemLevel, Value, LineTxt, LineNum)
6003
6004 FileToEvasion := False ; indirectly invalidating the pure Evasion mod for other calculations.
6005 }
6006 If (HasToMaxESCraft)
6007 {
6008 LineNum := HasToMaxESCraft
6009 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6010 Value := Itemdata.AffixTextLines[LineNum].Value
6011 LookupAffixAndSetInfoLine(FileToMaxES, "Crafted Prefix", ItemLevel, Value, LineTxt, LineNum)
6012
6013 FileToMaxES := False ; indirectly invalidating the pure MaxES mod for other calculations.
6014 }
6015
6016
6017 If (HasToArmour and HasToEvasion and HasToMaxES)
6018 {
6019
6020 }
6021 Else If (HasToArmour and HasToEvasion)
6022 {
6023 LineNum1 := HasToArmour
6024 LineNum2 := HasToEvasion
6025 LineNum3 := HasToMaxLife
6026 Value1 := Itemdata.AffixTextLines[LineNum1].Value
6027 Value2 := Itemdata.AffixTextLines[LineNum2].Value
6028 Value3 := Itemdata.AffixTextLines[LineNum3].Value
6029 SolveAffixes_HybBase_FlatDefLife("FlatDefMaxLife", LineNum1, LineNum2, LineNum3, Value1, Value2, Value3, FileToArmourHyb, FileToEvasionHyb, FileMaxLife, FileToArmourMaxLife, FileToEvasionMaxLife, FileMaxLifeToDef, ItemLevel)
6030 }
6031 Else If (HasToArmour and HasToMaxES)
6032 {
6033 LineNum1 := HasToArmour
6034 LineNum2 := HasToMaxES
6035 LineNum3 := HasToMaxLife
6036 Value1 := Itemdata.AffixTextLines[LineNum1].Value
6037 Value2 := Itemdata.AffixTextLines[LineNum2].Value
6038 Value3 := Itemdata.AffixTextLines[LineNum3].Value
6039 SolveAffixes_HybBase_FlatDefLife("FlatDefMaxLife", LineNum1, LineNum2, LineNum3, Value1, Value2, Value3, FileToArmourHyb, FileToMaxESHyb, FileMaxLife, FileToArmourMaxLife, FileToMaxESMaxLife, FileMaxLifeToDef, ItemLevel)
6040 }
6041 Else If (HasToEvasion and HasToMaxES)
6042 {
6043 LineNum1 := HasToEvasion
6044 LineNum2 := HasToMaxES
6045 LineNum3 := HasToMaxLife
6046 Value1 := Itemdata.AffixTextLines[LineNum1].Value
6047 Value2 := Itemdata.AffixTextLines[LineNum2].Value
6048 Value3 := Itemdata.AffixTextLines[LineNum3].Value
6049 SolveAffixes_HybBase_FlatDefLife("FlatDefMaxLife", LineNum1, LineNum2, LineNum3, Value1, Value2, Value3, FileToEvasionHyb, FileToMaxESHyb, FileMaxLife, FileToEvasionMaxLife, FileToMaxESMaxLife, FileMaxLifeToDef, ItemLevel)
6050 }
6051 Else If (HasToArmour)
6052 {
6053 LineNum1 := HasToArmour
6054 LineNum2 := HasToMaxLife
6055 Value1 := Itemdata.AffixTextLines[LineNum1].Value
6056 Value2 := Itemdata.AffixTextLines[LineNum2].Value
6057 SolveAffixes_Mod1Mod2Hyb("FlatDefMaxLife", LineNum1, LineNum2, Value1, Value2, "Prefix", "Prefix", "Hybrid Prefix", FileToArmour, FileMaxLife, FileToArmourMaxLife, FileMaxLifeToDef, ItemLevel)
6058 }
6059 Else If (HasToEvasion)
6060 {
6061 LineNum1 := HasToEvasion
6062 LineNum2 := HasToMaxLife
6063 Value1 := Itemdata.AffixTextLines[LineNum1].Value
6064 Value2 := Itemdata.AffixTextLines[LineNum2].Value
6065 SolveAffixes_Mod1Mod2Hyb("FlatDefMaxLife", LineNum1, LineNum2, Value1, Value2, "Prefix", "Prefix", "Hybrid Prefix", FileToEvasion, FileMaxLife, FileToEvasionMaxLife, FileMaxLifeToDef, ItemLevel)
6066 }
6067 Else If (HasToMaxES)
6068 {
6069 LineNum1 := HasToMaxES
6070 LineNum2 := HasToMaxLife
6071 Value1 := Itemdata.AffixTextLines[LineNum1].Value
6072 Value2 := Itemdata.AffixTextLines[LineNum2].Value
6073 SolveAffixes_Mod1Mod2Hyb("FlatDefMaxLife", LineNum1, LineNum2, Value1, Value2, "Prefix", "Prefix", "Hybrid Prefix", FileToMaxES, FileMaxLife, FileToMaxESMaxLife, FileMaxLifeToDef, ItemLevel)
6074 }
6075 }
6076 Else
6077 {
6078 If (HasToMaxLife)
6079 {
6080 LineNum := HasToMaxLife
6081 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6082 Value := Itemdata.AffixTextLines[LineNum].Value
6083 LookupAffixAndSetInfoLine(FileMaxLife, "Prefix", ItemLevel, Value, LineTxt, LineNum)
6084 }
6085
6086 If ( (HasToArmour or HasToEvasion or HasToMaxES) and (ItemBaseType = "Armour") )
6087 {
6088 If (HasToArmour and HasToEvasion)
6089 {
6090 LineNum := HasToArmour
6091 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6092 Value := Itemdata.AffixTextLines[LineNum].Value
6093 LookupAffixAndSetInfoLine(FileToArmourHyb, "Hybrid Defence Prefix", ItemLevel, Value, LineTxt, LineNum)
6094
6095 LineNum := HasToEvasion
6096 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6097 Value := Itemdata.AffixTextLines[LineNum].Value
6098 LookupAffixAndSetInfoLine(FileToEvasionHyb, "Hybrid Defence Prefix", ItemLevel, Value, LineTxt, LineNum)
6099 }
6100 Else If (HasToArmour and HasToMaxES)
6101 {
6102 LineNum := HasToArmour
6103 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6104 Value := Itemdata.AffixTextLines[LineNum].Value
6105 LookupAffixAndSetInfoLine(FileToArmourHyb, "Hybrid Defence Prefix", ItemLevel, Value, LineTxt, LineNum)
6106
6107 LineNum := HasToMaxES
6108 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6109 Value := Itemdata.AffixTextLines[LineNum].Value
6110 LookupAffixAndSetInfoLine(FileToMaxESHyb, "Hybrid Defence Prefix", ItemLevel, Value, LineTxt, LineNum)
6111 }
6112 Else If (HasToEvasion and HasToMaxES)
6113 {
6114 LineNum := HasToEvasion
6115 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6116 Value := Itemdata.AffixTextLines[LineNum].Value
6117 LookupAffixAndSetInfoLine(FileToEvasionHyb, "Hybrid Defence Prefix", ItemLevel, Value, LineTxt, LineNum)
6118
6119 LineNum := HasToMaxES
6120 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6121 Value := Itemdata.AffixTextLines[LineNum].Value
6122 LookupAffixAndSetInfoLine(FileToMaxESHyb, "Hybrid Defence Prefix", ItemLevel, Value, LineTxt, LineNum)
6123 }
6124 Else If (HasToArmour)
6125 {
6126 LineNum := HasToArmour
6127 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6128 Value := Itemdata.AffixTextLines[LineNum].Value
6129 LookupAffixAndSetInfoLine(FileToArmour, "Prefix", ItemLevel, Value, LineTxt, LineNum)
6130 }
6131 Else If (HasToEvasion)
6132 {
6133 LineNum := HasToEvasion
6134 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6135 Value := Itemdata.AffixTextLines[LineNum].Value
6136 LookupAffixAndSetInfoLine(FileToEvasion, "Prefix", ItemLevel, Value, LineTxt, LineNum)
6137 }
6138 Else If (HasToMaxES)
6139 {
6140 LineNum := HasToMaxES
6141 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6142 Value := Itemdata.AffixTextLines[LineNum].Value
6143 LookupAffixAndSetInfoLine(FileToMaxES, "Prefix", ItemLevel, Value, LineTxt, LineNum)
6144 }
6145 }
6146 Else If (HasToArmour or HasToEvasion or HasToMaxES) ; Not an Armour, case for Belt/Ring/Amulet. Belts can have multiple single flat mods while Armours can't.
6147 {
6148 If (HasToArmour)
6149 {
6150 LineNum := HasToArmour
6151 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6152 Value := Itemdata.AffixTextLines[LineNum].Value
6153 LookupAffixAndSetInfoLine(FileToArmour, "Prefix", ItemLevel, Value, LineTxt, LineNum)
6154 }
6155
6156 If (HasToEvasion)
6157 {
6158 LineNum := HasToEvasion
6159 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6160 Value := Itemdata.AffixTextLines[LineNum].Value
6161 LookupAffixAndSetInfoLine(FileToEvasion, "Prefix", ItemLevel, Value, LineTxt, LineNum)
6162 }
6163
6164 If (HasToMaxES)
6165 {
6166 LineNum := HasToMaxES
6167 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6168 Value := Itemdata.AffixTextLines[LineNum].Value
6169 LookupAffixAndSetInfoLine(FileToMaxES, "Prefix", ItemLevel, Value, LineTxt, LineNum)
6170 }
6171 }
6172 }
6173 }
6174
6175
6176 If (HasStunBlockRecovery or HasIncrDefences)
6177 {
6178 If (ItemSubType = "BodyArmour" or ItemSubType = "Shield"){
6179 BodyArmourShieldOrNot := "_BodyArmourShield"
6180 }
6181 Else{
6182 BodyArmourShieldOrNot := ""
6183 }
6184
6185 If (HasStunBlockRecovery and HasIncrDefences and (ItemBaseType = "Armour") )
6186 {
6187 If (HasChanceToBlockStrShield)
6188 {
6189 ; TODO: UNHANDLED CASE. Special case: 5 mods can combine into 3 lines here. Implementing this later, because it is so rare.
6190 }
6191 Else If (HasIncrDefencesCraft)
6192 {
6193 ; If there are two separate %def mod lines visible, then the first pre-pass match has to be the part from the hybrid mod
6194 ; and the second match has to be the pure mod in crafted form.
6195
6196 If (HasIncrDefencesType = "Armour" or HasIncrDefencesType = "Evasion" or HasIncrDefencesType = "EnergyShield"){
6197 FileHybDef := "data\Incr" . HasIncrDefencesType . "_StunRecovery.txt"
6198 FileHybStun := "data\StunBlockRecovery_" . HasIncrDefencesType . ".txt"
6199 }
6200 Else{
6201 FileHybDef := "data\IncrDefences_HybridBase_StunRecovery.txt"
6202 FileHybStun := "data\StunBlockRecovery_HybridBase.txt"
6203 }
6204
6205 FileCraft := "data\Incr" . HasIncrDefencesCraftType . BodyArmourShieldOrNot . ".txt"
6206
6207 LineNum := HasIncrDefencesCraft
6208 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6209 Value := Itemdata.AffixTextLines[LineNum].Value
6210 LookupAffixAndSetInfoLine(FileCraft, "Crafted Prefix", ItemLevel, Value, LineTxt, LineNum)
6211
6212
6213 LineNum1 := HasIncrDefences
6214 LineNum2 := HasStunBlockRecovery
6215 Value1 := Itemdata.AffixTextLines[LineNum1].Value
6216 Value2 := Itemdata.AffixTextLines[LineNum2].Value
6217 SolveAffixes_Mod1Mod2Hyb("IncrDefStunBlock", LineNum1, LineNum2, Value1, Value2, "Prefix", "Suffix", "Hybrid Prefix", False, False, FileHybDef, FileHybStun, ItemLevel)
6218 }
6219 Else
6220 {
6221 If (HasIncrDefencesType = "Armour" or HasIncrDefencesType = "Evasion" or HasIncrDefencesType = "EnergyShield"){
6222 FileHybDef := "data\Incr" . HasIncrDefencesType . "_StunRecovery.txt"
6223 FileHybStun := "data\StunBlockRecovery_" . HasIncrDefencesType . ".txt"
6224 }
6225 Else{
6226 FileHybDef := "data\IncrDefences_HybridBase_StunRecovery.txt"
6227 FileHybStun := "data\StunBlockRecovery_HybridBase.txt"
6228 }
6229
6230 FileDef := "data\Incr" . HasIncrDefencesType . BodyArmourShieldOrNot . ".txt"
6231 FileStun := "data\StunBlockRecovery_Suffix.txt"
6232
6233 LineNum1 := HasIncrDefences
6234 LineNum2 := HasStunBlockRecovery
6235 Value1 := Itemdata.AffixTextLines[LineNum1].Value
6236 Value2 := Itemdata.AffixTextLines[LineNum2].Value
6237 SolveAffixes_Mod1Mod2Hyb("IncrDefStunBlock", LineNum1, LineNum2, Value1, Value2, "Prefix", "Suffix", "Hybrid Prefix", FileDef, FileStun, FileHybDef, FileHybStun, ItemLevel)
6238 }
6239
6240 }
6241 Else
6242 {
6243 If (HasStunBlockRecovery)
6244 {
6245 FileStun := "data\StunBlockRecovery_Suffix.txt"
6246 LineNum := HasStunBlockRecovery
6247 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6248 Value := Itemdata.AffixTextLines[LineNum].Value
6249 LookupAffixAndSetInfoLine(FileStun, "Suffix", ItemLevel, Value, LineTxt, LineNum)
6250 }
6251
6252 If (HasIncrDefences)
6253 {
6254 If (ItemSubType = "Amulet")
6255 {
6256 File := "data\Incr" . HasIncrDefencesType . "_Amulet.txt" ; "Armour" or "Evasion". ES has a "maximum" in the Amulet wording and was already checked in simple affixes.
6257 }
6258 Else
6259 {
6260 File := "data\Incr" . HasIncrDefencesType . BodyArmourShieldOrNot . ".txt"
6261 }
6262
6263 LineNum := HasIncrDefences
6264 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6265 Value := Itemdata.AffixTextLines[LineNum].Value
6266 LookupAffixAndSetInfoLine(File, "Prefix", ItemLevel, Value, LineTxt, LineNum)
6267 }
6268 }
6269 }
6270
6271 ; Note: The "HasIncrPhysDmg" can either come from a shield or weapon. The shield case has already been dealt with in the simple affix section though and
6272 ; "HasIncrPhysDmg" gets disabled in that case. Consequently this flag is certainly from a weapon.
6273
6274 If ((HasIncrPhysDmg or HasIncrLightRadius) or HasToAccuracyRating)
6275 {
6276 FilePhys := "data\IncrPhysDamage.txt"
6277
6278 If (ItemSubType = "Bow" or ItemSubType = "Wand"){
6279 FileAccu := "data\AccuracyRating_BowWand.txt"
6280 }
6281 Else If (ItemBaseType = "Weapon"){
6282 FileAccu := "data\AccuracyRating_Weapon.txt"
6283 }
6284 Else If (ItemSubType = "Helmet" or ItemSubType = "Gloves"){
6285 FileAccu := "data\AccuracyRating_HelmetGloves.txt"
6286 }
6287 Else{
6288 FileAccu := "data\AccuracyRating_Global.txt"
6289 }
6290
6291 FileHybPhys := "data\IncrPhysDamage_AccuracyRating.txt"
6292 FileHybAccuPhys := "data\AccuracyRating_IncrPhysDamage.txt"
6293
6294 FileHybLight := "data\LightRadius_AccuracyRating.txt"
6295 FileHybAccuLight := "data\AccuracyRating_LightRadius.txt"
6296
6297
6298 If ((HasIncrPhysDmg or HasIncrLightRadius) and HasToAccuracyRating)
6299 {
6300 If (HasIncrPhysDmg and HasIncrLightRadius and HasToAccuracyRating)
6301 {
6302 ; TODO: UNHANDLED CASE
6303 }
6304
6305 Else If (HasIncrPhysDmg and HasToAccuracyRating)
6306 {
6307 LineNum1 := HasIncrPhysDmg
6308 LineNum2 := HasToAccuracyRating
6309 Value1 := Itemdata.AffixTextLines[LineNum1].Value
6310 Value2 := Itemdata.AffixTextLines[LineNum2].Value
6311
6312 SolveAffixes_Mod1Mod2Hyb("IncrPhysToAcc", LineNum1, LineNum2, Value1, Value2, "Prefix", "Suffix", "Hybrid Prefix", FilePhys, FileAccu, FileHybPhys, FileHybAccuPhys, ItemLevel)
6313 }
6314
6315 Else If (HasIncrLightRadius and HasToAccuracyRating)
6316 {
6317 LineNum1 := HasIncrLightRadius
6318 LineNum2 := HasToAccuracyRating
6319 Value1 := Itemdata.AffixTextLines[LineNum1].Value
6320 Value2 := Itemdata.AffixTextLines[LineNum2].Value
6321
6322 ; there is no "pure" Light Radius mod, hence the False
6323
6324 SolveAffixes_Mod1Mod2Hyb("LightRadiusToAcc", LineNum1, LineNum2, Value1, Value2, "Prefix", "Suffix", "Hybrid Suffix", False, FileAccu, FileHybLight, FileHybAccuLight, ItemLevel)
6325 }
6326 }
6327
6328 Else If (HasIncrPhysDmg)
6329 {
6330 LineNum := HasIncrPhysDmg
6331 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6332 Value := Itemdata.AffixTextLines[LineNum].Value
6333 LookupAffixAndSetInfoLine(FilePhys, "Prefix", ItemLevel, Value, LineTxt, LineNum)
6334 }
6335
6336 Else If (HasToAccuracyRating)
6337 {
6338 LineNum := HasToAccuracyRating
6339 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6340 Value := Itemdata.AffixTextLines[LineNum].Value
6341 LookupAffixAndSetInfoLine(FileAccu, "Suffix", ItemLevel, Value, LineTxt, LineNum)
6342 }
6343 }
6344
6345
6346 If (HasIncrSpellDamage or HasMaxMana)
6347 {
6348 If (ItemGripType = "1H" or ItemSubType = "Shield"){
6349 FileSpell := "data\SpellDamage_1H.txt"
6350 FileHybSpell := "data\SpellDamage_MaxMana_1H.txt"
6351 }
6352 Else If (ItemSubType = "Amulet"){
6353 FileSpell := "data\SpellDamage_Amulet.txt"
6354 }
6355 Else{
6356 FileSpell := "data\SpellDamage_Staff.txt"
6357 FileHybSpell := "data\SpellDamage_MaxMana_Staff.txt"
6358 }
6359
6360 If (ItemSubType = "Amulet" or ItemSubType = "Ring"){
6361 FileMana := "data\MaxMana_AmuletRing.txt"
6362 }
6363 Else{
6364 FileMana := "data\MaxMana.txt"
6365 }
6366
6367 FileHybMana := "data\MaxMana_SpellDamage.txt"
6368
6369
6370 ; Shields and Amulets can't have the hybrid mod.
6371 If (HasIncrSpellDamage and HasMaxMana and not((ItemSubType = "Shield") or (ItemSubType = "Amulet")) )
6372 {
6373 If (HasIncrSpellOrElePrefix and (HasIncrSpellOrElePrefix != HasIncrSpellDamage))
6374 {
6375 FileSpell := False ; There is an increased Fire, Cold or Lightning Prefix, so SpellDamage can't have the pure mod.
6376 }
6377
6378 LineNum1 := HasIncrSpellDamage
6379 LineNum2 := HasMaxMana
6380 Value1 := Itemdata.AffixTextLines[LineNum1].Value
6381 Value2 := Itemdata.AffixTextLines[LineNum2].Value
6382
6383 SolveAffixes_Mod1Mod2Hyb("SpellMana", LineNum1, LineNum2, Value1, Value2, "Prefix", "Prefix", "Hybrid Prefix", FileSpell, FileMana, FileHybSpell, FileHybMana, ItemLevel)
6384 }
6385 Else
6386 {
6387 ; Checking these in separate ifs and not an else-if chain to cover the case where a shield or amulet has both mods (which are certainly simple mods).
6388 If (HasIncrSpellDamage)
6389 {
6390 LineNum := HasIncrSpellDamage
6391 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6392 Value := Itemdata.AffixTextLines[LineNum].Value
6393 LookupAffixAndSetInfoLine(FileSpell, "Prefix", ItemLevel, Value, LineTxt, LineNum)
6394 }
6395
6396 If (HasMaxMana)
6397 {
6398 LineNum := HasMaxMana
6399 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6400 Value := Itemdata.AffixTextLines[LineNum].Value
6401 LookupAffixAndSetInfoLine(FileMana, "Prefix", ItemLevel, Value, LineTxt, LineNum)
6402 }
6403 }
6404 }
6405
6406 If (HasIncrFireDamage or HasIncrColdDamage or HasIncrLightningDamage)
6407 {
6408 If (ItemSubType = "Staff"){
6409 FilePrefix := "data\IncrEleTypeDamage_Prefix_Staff.txt"
6410 FileSuffixEnd := "_Weapon.txt"
6411 }
6412 Else If (ItemSubType = "Wand" or ItemSubType = "Sceptre"){
6413 FilePrefix := "data\IncrEleTypeDamage_Prefix_WandSceptreFocus.txt"
6414 FileSuffixEnd := "_Weapon.txt"
6415 }
6416 Else If (ItemSubType = "Amulet"){
6417 FilePrefix := False
6418 FileSuffixEnd := "_Amulet.txt"
6419 }
6420 Else If (ItemSubType = "Ring"){
6421 FilePrefix := False
6422 FileSuffixEnd := "_Ring.txt"
6423 }
6424
6425 IfInString, ItemNamePlate, Spirit Shield
6426 {
6427 FilePrefix := "data\IncrEleTypeDamage_Prefix_WandSceptreFocus.txt"
6428 FileSuffixEnd := False
6429 }
6430
6431
6432 If (HasIncrFireDamage)
6433 {
6434 FileSuffix := "data\IncrFireDamage_Suffix" . FileSuffixEnd
6435 LineNum := HasIncrFireDamage
6436 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6437 Value := Itemdata.AffixTextLines[LineNum].Value
6438
6439 If (HasIncrSpellOrElePrefix and (HasIncrSpellOrElePrefix != HasIncrFireDamage))
6440 {
6441 LookupAffixAndSetInfoLine(FileSuffix, "Suffix", ItemLevel, Value, LineTxt, LineNum)
6442 }
6443 Else
6444 {
6445 SolveAffixes_PreSuf("IncrFire", LineNum, Value, FilePrefix, FileSuffix, ItemLevel)
6446 }
6447 }
6448 If (HasIncrColdDamage)
6449 {
6450 FileSuffix := "data\IncrColdDamage_Suffix" . FileSuffixEnd
6451 LineNum := HasIncrColdDamage
6452 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6453 Value := Itemdata.AffixTextLines[LineNum].Value
6454
6455 If (HasIncrSpellOrElePrefix and (HasIncrSpellOrElePrefix != HasIncrColdDamage))
6456 {
6457 LookupAffixAndSetInfoLine(FileSuffix, "Suffix", ItemLevel, Value, LineTxt, LineNum)
6458 }
6459 Else
6460 {
6461 SolveAffixes_PreSuf("IncrCold", LineNum, Value, FilePrefix, FileSuffix, ItemLevel)
6462 }
6463 }
6464 If (HasIncrLightningDamage)
6465 {
6466 FileSuffix := "data\IncrLightningDamage_Suffix" . FileSuffixEnd
6467 LineNum := HasIncrLightningDamage
6468 LineTxt := Itemdata.AffixTextLines[LineNum].Text
6469 Value := Itemdata.AffixTextLines[LineNum].Value
6470
6471 If (HasIncrSpellOrElePrefix and (HasIncrSpellOrElePrefix != HasIncrLightningDamage))
6472 {
6473 LookupAffixAndSetInfoLine(FileSuffix, "Suffix", ItemLevel, Value, LineTxt, LineNum)
6474 }
6475 Else
6476 {
6477 SolveAffixes_PreSuf("IncrLightning", LineNum, Value, FilePrefix, FileSuffix, ItemLevel)
6478 }
6479 }
6480 }
6481
6482
6483 i := AffixLines.MaxIndex()
6484
6485 ; Just using := is not enough for a full copy, so we do all entries per loop.
6486 Loop, %i%
6487 {
6488 Itemdata.UncAffTmpAffixLines[A_Index] := AffixLines[A_Index]
6489 }
6490 If (Itemdata.Rarity = "Magic"){
6491 PrefixLimit := 1
6492 SuffixLimit := 1
6493 } Else {
6494 PrefixLimit := 3
6495 SuffixLimit := 3
6496 }
6497
6498 ; Set max estimated number to current number. From now on we possibly deal with affix count ranges.
6499 AffixTotals.NumPrefixesMax := AffixTotals.NumPrefixes
6500 AffixTotals.NumSuffixesMax := AffixTotals.NumSuffixes
6501 AffixTotals.NumTotal := AffixTotals.NumPrefixes + AffixTotals.NumSuffixes
6502 AffixTotals.NumTotalMax := AffixTotals.NumTotal
6503
6504 ; THIS FUNCTION IS QUITE COMPLICATED. IT INVOLVES FOUR LOOPS THAT FULFILL DIFFERENT JOBS AT DIFFERENT TIMES.
6505 ; CONSEQUENTLY THE CODE CAN'T BE SIMPLY READ FROM TOP TO BOTTOM. IT IS HEAVILY COMMENTED THOUGH.
6506 ; READ THE REST OF THE INTRODUCTION HERE. IN THE FUNCTION ITSELF SKIP ALL "PHASE2" UNTIL YOU'VE READ ALL "PHASE1"
6507 ;
6508 ; Phase 1:
6509 ; Check each possible mod if it alone breaks the affix count limit. Discard these from Itemdata.UncertainAffixes.
6510 ; Check if discarding possibilities leaves a mod group with a single choice. Finalize these now certain outcomes.
6511 ; Loop through these checks until nothing changes for one complete pass.
6512 ; Phase 2:
6513 ; Check whether a group will certainly bring an affix type but is not finalized yet. We can use that info to
6514 ; potentially discard mods from other groups.
6515 ReloopAll := True
6516 ConsiderAllRemainingAffixes := False
6517 CheckAgainForGoodMeasure := False
6518 While ReloopAll
6519 {
6520 ; No infinite looping. We re-enable ReloopAll below when it is warranted.
6521 ReloopAll := False
6522
6523 If (ConsiderAllRemainingAffixes = False)
6524 {
6525 ; Phase 1:
6526 ; Store each grp's affix min count for a later check.
6527 ; When the outer loop would not repeat due to ReloopAll False,
6528 ; we have completed a full loopthrough without changes and these numbers are current.
6529 ;
6530 ; Phase 2:
6531 ; ConsiderAllRemainingAffixes is now activated and we start using these values,
6532 ; so we don't want to reset them once ConsiderAllRemainingAffixes is True.
6533
6534 GrpPrefixMinCount := {"Total":0}
6535 GrpSuffixMinCount := {"Total":0}
6536 GrpTotalMinCount := {"Total":0}
6537 }
6538
6539 For key_grp, grp in Itemdata.UncertainAffixes
6540 {
6541 PrefixMinCount := 10 ; Start arbitrary high enough and then lower them with comparisons.
6542 SuffixMinCount := 10
6543 TotalMinCount := 10
6544
6545 ; We enable ReloopGrp here because when we are at this point, we want to enter the loop. The only time we don't want to
6546 ; loop here is when we went through the whole "For key_entry..." loop (below) without any events/changes.
6547 ReloopGrp := True
6548
6549 While ReloopGrp
6550 {
6551 ; Again, no infinite looping.
6552 ReloopGrp := False
6553
6554 ; Counting the entries in grp for checks further below. Since the keys are named .MaxIndex() won't work.
6555 grp_len := 0
6556
6557 For key_entry, entry in grp
6558 {
6559 ++grp_len
6560
6561 ; Phase 1:
6562 ; Figure out whether all entries of a group have at least a certain prefix or suffix amount.
6563 ; If no entry lowers the respective min to 0, we have that many prefixes or suffixes regardless which entry is correct.
6564 ; Phase 2:
6565 ; Even though we might not be able to finalize the group itself yet, we can now use
6566 ; that information to make decisions about the entries of other groups.
6567
6568 If (entry[1] < PrefixMinCount){
6569 PrefixMinCount := entry[1]
6570 }
6571 If (entry[2] < SuffixMinCount){
6572 SuffixMinCount := entry[2]
6573 }
6574 If (entry[1] + entry[2] < TotalMinCount){
6575 TotalMinCount := entry[1] + entry[2]
6576 }
6577
6578 If (ConsiderAllRemainingAffixes = False)
6579 {
6580 ; Phase 1:
6581 ; No fancy affix assumptions yet, just the certain count due to what we have added to AffixLines already.
6582 AssumePrefixCount := AffixTotals.NumPrefixes
6583 AssumeSuffixCount := AffixTotals.NumSuffixes
6584 AssumeTotalCount := 0 ; not used in this phase.
6585 }
6586 Else
6587 {
6588 ; Phase 2:
6589 ; Now we add the summed up mins of all groups into the comparison.
6590 ; Since a group's entry is not supposed to be discarded because of the groups own min portion (that is in the total), we subtract the respective group's share.
6591 AssumePrefixCount := AffixTotals.NumPrefixes + GrpPrefixMinCount["total"] - GrpPrefixMinCount[key_grp]
6592 AssumeSuffixCount := AffixTotals.NumSuffixes + GrpSuffixMinCount["total"] - GrpSuffixMinCount[key_grp]
6593 AssumeTotalCount := AffixTotals.NumPrefixes + AffixTotals.NumSuffixes + GrpTotalMinCount["total"] - GrpTotalMinCount[key_grp]
6594 }
6595
6596 If ( (AssumePrefixCount + entry[1] > PrefixLimit) or (AssumeSuffixCount + entry[2] > SuffixLimit) or (AssumeTotalCount + entry[1] + entry[2] > PrefixLimit + SuffixLimit) )
6597 {
6598 ; Mod does not work because of affix number limit
6599 ; Remove mod entry from "grp"
6600 grp.Delete(key_entry)
6601
6602 ; Use ReloopGrp and break to restart the "For key_entry..." loop, because the indexes changed with the deletion.
6603 ReloopGrp := True
6604 break
6605 }
6606 }
6607 }
6608
6609 If (ConsiderAllRemainingAffixes = False)
6610 {
6611 ; Phase 1:
6612 ; We've finished the whole "For key_entry..." loop for a grp, so the Prefix/Suffix/TotalMinCount actually represents that grp.
6613 ; Record that value (by "key_grp") and also add it to a total.
6614 ; Phase 2:
6615 ; While ConsiderAllRemainingAffixes is True and we are consequently actively using these values, we don't touch them.
6616 ; Otherwise we would add a group's portion a second time to the total, since the total was not reset to 0 at the start.
6617 GrpPrefixMinCount[key_grp] := PrefixMinCount
6618 GrpSuffixMinCount[key_grp] := SuffixMinCount
6619 GrpTotalMinCount[key_grp] := TotalMinCount
6620 GrpPrefixMinCount["total"] += PrefixMinCount
6621 GrpSuffixMinCount["total"] += SuffixMinCount
6622 GrpTotalMinCount["total"] += TotalMinCount
6623 }
6624
6625
6626 If (grp_len=1)
6627 {
6628 ; Only one mod in this grp, so there is no ambiguity. Put the mod in and remove grp.
6629 FinalizeUncertainAffixGroup(grp)
6630 Itemdata.UncertainAffixes.Delete(key_grp)
6631
6632 ; Phase 2:
6633 ; By finalizing a group their affixes are now included in AffixTotals.NumPrefixes/Suffixes, which adds into AssumePrefix/Suffix/TotalCount.
6634 ; Consequently we don't want that min value to remain in the total, because it would effectively get added twice.
6635 GrpPrefixMinCount["total"] -= GrpPrefixMinCount[key_grp]
6636 GrpSuffixMinCount["total"] -= GrpSuffixMinCount[key_grp]
6637 GrpTotalMinCount["total"] -= GrpTotalMinCount[key_grp]
6638
6639 ; Restart at outer "for" loop because grp is gone and outer indexes are shifted due to deletion.
6640 ReloopAll := True
6641 break
6642 }
6643 Else If (grp_len=0)
6644 {
6645 Itemdata.UncertainAffixes.Delete(key_grp)
6646
6647 ; Restart at outer "for" loop because grp is gone and outer indexes are shifted due to deletion.
6648 ReloopAll := True
6649 break
6650 }
6651 }
6652
6653
6654 If (ReloopAll = False)
6655 {
6656 ; Phase 1:
6657 ; Basic checks are done. Now we can check if a group is not solved yet but is guaranteed to bring a certain affix type
6658 ; which we can count against the limit and then rule out entries from other groups.
6659
6660 If (ConsiderAllRemainingAffixes = False)
6661 {
6662 ; We enable Phase 2 of affix count comparison here.
6663
6664 ConsiderAllRemainingAffixes = True
6665 ReloopAll = True
6666 ; ...and ReloopAll obviously. (Continue reading Phase 2 from the top again)
6667 }
6668 Else If (CheckAgainForGoodMeasure = False)
6669 {
6670 ; Phase 2:
6671 ; If we arrive here we've checked everything with the Phase 2 comparison.
6672 ; Reset flags to run through a whole Phase1+Phase2 iteration again, just to be sure that we
6673 ; can't figure anything more out. When that is done we finally get out of the loops because:
6674 ; ConsiderAllRemainingAffixes is True and
6675 ; CheckAgainForGoodMeasure is True
6676
6677 CheckAgainForGoodMeasure = True
6678 ConsiderAllRemainingAffixes = False
6679 ReloopAll = True
6680 }
6681 }
6682 }
6683
6684 ; Now also accept whatever is still remaining.
6685 For idx1, grp in Itemdata.UncertainAffixes
6686 {
6687 FinalizeUncertainAffixGroup(grp)
6688 }
6689
6690 ; Remove lines with identical entries (coming from different mod-combinations)
6691 ; For example if line1 can be either "T1 HybP" or "T6 P + T1 HybP" but the corresponding
6692 ; line2 is "T1 HybP" in both cases, we don't want that double entry in line2.
6693 For key1, line in Itemdata.UncAffTmpAffixLines{
6694 For key2, subline in line{
6695 If (IsObject(subline)){
6696 ; Check line possibilities starting from the last index (safer when removing entries)
6697 i := line.MaxIndex()
6698 While(i > key2)
6699 {
6700 ; Check all entries after our current entry at line[key2] if they are duplicates of it
6701 ; (by comparing "TypeAndTier" and line index 3). Remove if identical.
6702 If (line[i][3] = line[key2][3])
6703 {
6704 line.RemoveAt(i)
6705 }
6706 --i
6707 }
6708 }Else{
6709 break
6710 }
6711 }
6712 }
6713
6714 AffixLines.Reset()
6715
6716 ; Go through Itemdata.UncAffTmpAffixLines and write lines that have multiple possibilities stored in an array
6717 ; as several single lines into AffixLines. So for example:
6718 ; [
6719 ; [Line1Data],
6720 ; [ [Line2Data_#1], [Line2Data_#2] ],
6721 ; [Line3Data]
6722 ; ]
6723 ; becomes
6724 ; [
6725 ; [Line1Data],
6726 ; [Line2Data_#1],
6727 ; [Line2Data_#2],
6728 ; [Line3Data]
6729 ; ]
6730
6731 i := 1
6732 For key1, line in Itemdata.UncAffTmpAffixLines{
6733 If (IsObject(line)){
6734 For key2, subline in line{
6735 If (IsObject(subline)){
6736 AffixLines.Set(i, subline)
6737 ++i
6738 }Else{
6739 AffixLines.Set(i, line)
6740 ++i
6741 break
6742 }
6743 }
6744 }
6745 Else{
6746 AffixLines.Set(i, line)
6747 ++i
6748 }
6749 }
6750 return
6751}
6752
6753FinalizeUncertainAffixGroup(grp)
6754{
6755 Global Itemdata, AffixTotals
6756
6757 PrefixMin := 10
6758 PrefixMax := 0
6759 SuffixMin := 10
6760 SuffixMax := 0
6761 TotalMin := 10
6762 TotalMax := 0
6763
6764 For key_entry, entry in grp
6765 {
6766 ; entry[1] is PrefixCount and entry[2] is SuffixCount for that entry.
6767
6768 If (entry[1] < PrefixMin){
6769 PrefixMin := entry[1]
6770 }
6771 If (entry[1] > PrefixMax){
6772 PrefixMax := entry[1]
6773 }
6774 If (entry[2] < SuffixMin){
6775 SuffixMin := entry[2]
6776 }
6777 If (entry[2] > SuffixMax){
6778 SuffixMax := entry[2]
6779 }
6780 If (entry[1] + entry[2] < TotalMin){
6781 TotalMin := entry[1] + entry[2]
6782 }
6783 If (entry[1] + entry[2] > TotalMax){
6784 TotalMax := entry[1] + entry[2]
6785 }
6786
6787 For junk, val in [3,5,7,9,11,13] ; these are the potential line number entries, the +1's are the line texts.
6788 {
6789 If (entry[val])
6790 {
6791 If (IsObject(Itemdata.UncAffTmpAffixLines[entry[val]]))
6792 {
6793 ; There already is a mod for that line. Append this alternative to the array of the line.
6794 ; Overwrite the line text with "or"
6795 entry[val+1][1] := "or"
6796 Itemdata.UncAffTmpAffixLines[entry[val]].Push(entry[val+1])
6797 }
6798 Else
6799 {
6800 ; First entry for that line, start array and put the whole line as entry 1.
6801 Itemdata.UncAffTmpAffixLines[entry[val]] := [entry[val+1]]
6802 }
6803 }
6804 }
6805 }
6806
6807 AffixTotals.NumPrefixes += PrefixMin
6808 AffixTotals.NumPrefixesMax += PrefixMax
6809 AffixTotals.NumSuffixes += SuffixMin
6810 AffixTotals.NumSuffixesMax += SuffixMax
6811 AffixTotals.NumTotal += TotalMin
6812 AffixTotals.NumTotalMax += TotalMax
6813}
6814
6815ResetAffixDetailVars()
6816{
6817 Global AffixLines, AffixTotals, Globals
6818 AffixLines.Reset()
6819 AffixTotals.Reset()
6820}
6821
6822IsEmptyString(String)
6823{
6824 If (StrLen(String) == 0)
6825 {
6826 return True
6827 }
6828 Else
6829 {
6830 String := RegExReplace(String, "[\r\n ]", "")
6831 If (StrLen(String) < 1)
6832 {
6833 return True
6834 }
6835 }
6836 return False
6837}
6838
6839IsNum(Var){
6840 If (++Var)
6841 return true
6842 return false
6843}
6844
6845PreProcessContents(CBContents)
6846{
6847; --- Place fixes for data inconsistencies here ---
6848
6849; Remove the line that indicates an item cannot be used due to missing character stats
6850 ; Matches "Rarity: ..." + anything until "--------"\r\n
6851 If (RegExMatch(CBContents, "s)^(.+?:.+?\r\n)(.+?-{8}\r\n)(.*)", match)) {
6852 ; Matches any ".", looking for the 2 sentences saying "You cannot use this item. Its stats will be ignored."
6853 ; Could be improved, should suffice though because the alternative would be the item name/type, which can't have any dots.
6854 ; This should work regardless of the selected language.
6855 If (RegExMatch(match2, "\.")) {
6856 CBContents := match1 . match3
6857 }
6858 }
6859
6860 Needle := "--------`r`n--------`r`n"
6861 StringReplace, CBContents, CBContents, %Needle%, --------`r`n, All
6862
6863 return CBContents
6864}
6865
6866PostProcessData(ParsedData)
6867{
6868 Global Opts
6869
6870 Result := ParsedData
6871
6872 StringReplace, TempResult, ParsedData, --------`n, ``, All
6873 StringSplit, ParsedDataChunks, TempResult, ``
6874
6875 Result =
6876 Loop, %ParsedDataChunks0%
6877 {
6878 CurrChunk := ParsedDataChunks%A_Index%
6879 If IsEmptyString(CurrChunk)
6880 {
6881 Continue
6882 }
6883 If (A_Index < ParsedDataChunks0)
6884 {
6885 Result := Result . CurrChunk . "--------`r`n"
6886 }
6887 Else
6888 {
6889 Result := Result . CurrChunk
6890 }
6891 }
6892
6893 return Result
6894}
6895
6896ParseClipBoardChanges(debug = false)
6897{
6898 Global Opts, Globals, Item
6899
6900 CBContents := GetClipboardContents()
6901 CBContents := PreProcessContents(CBContents)
6902 /*
6903 ;Item Data Translation, won't be used for now.
6904 CBContents := PoEScripts_TranslateItemData(CBContents, translationData, currentLocale, retObj, retCode)
6905 */
6906
6907 Globals.Set("ItemText", CBContents)
6908
6909 ParsedData := ParseItemData(CBContents)
6910 ParsedData := PostProcessData(ParsedData)
6911
6912 If (Opts.PutResultsOnClipboard && ParsedData)
6913 {
6914 SetClipboardContents(ParsedData)
6915 }
6916
6917 If (StrLen(ParsedData) and !Opts.OnlyActiveIfPOEIsFront and debug) {
6918 AddLogEntry(ParsedData, CBContents)
6919 }
6920
6921 ShowToolTip(ParsedData, false, Opts.GDIConditionalColors)
6922}
6923
6924AddLogEntry(ParsedData, RawData) {
6925 logFileRaw := userDirectory "\parsingLogRaw.txt"
6926 logFileParsed := userDirectory "\parsingLog.txt"
6927
6928 line := "----------------------------------------------------------"
6929 timeStamp := ""
6930 ID := MD5(RawData)
6931 UTCTimestamp := GetTimestampUTC()
6932 UTCFormatStr := "yyyy-MM-dd'T'HH:mm'Z'"
6933 FormatTime, TimeStr, %UTCTimestamp%, %UTCFormatStr%
6934
6935 entry := line "`n" TimeStr " - ID: " ID "`n" line "`n`n"
6936 entryRaw := entry . RawData "`n`n"
6937 entryParsed := entry . ParsedData "`n`n"
6938
6939 FileAppend, %entryRaw%, %logFileRaw%
6940 FileAppend, %entryParsed%, %logFileParsed%
6941}
6942
6943MD5(string, case := False) ; by SKAN | rewritten by jNizM
6944{
6945 static MD5_DIGEST_LENGTH := 16
6946 hModule := DllCall("LoadLibrary", "Str", "advapi32.dll", "Ptr")
6947, VarSetCapacity(MD5_CTX, 104, 0), DllCall("advapi32\MD5Init", "Ptr", &MD5_CTX)
6948, DllCall("advapi32\MD5Update", "Ptr", &MD5_CTX, "AStr", string, "UInt", StrLen(string))
6949, DllCall("advapi32\MD5Final", "Ptr", &MD5_CTX)
6950 loop % MD5_DIGEST_LENGTH
6951 o .= Format("{:02" (case ? "X" : "x") "}", NumGet(MD5_CTX, 87 + A_Index, "UChar"))
6952 return o, DllCall("FreeLibrary", "Ptr", hModule)
6953}
6954
6955GetTimestampUTC() { ; http://msdn.microsoft.com/en-us/library/ms724390
6956 VarSetCapacity(ST, 16, 0) ; SYSTEMTIME structure
6957 DllCall("Kernel32.dll\GetSystemTime", "Ptr", &ST)
6958 Return NumGet(ST, 0, "UShort") ; year : 4 digits until 10000
6959 . SubStr("0" . NumGet(ST, 2, "UShort"), -1) ; month : 2 digits forced
6960 . SubStr("0" . NumGet(ST, 6, "UShort"), -1) ; day : 2 digits forced
6961 . SubStr("0" . NumGet(ST, 8, "UShort"), -1) ; hour : 2 digits forced
6962 . SubStr("0" . NumGet(ST, 10, "UShort"), -1) ; minute : 2 digits forced
6963 . SubStr("0" . NumGet(ST, 12, "UShort"), -1) ; second : 2 digits forced
6964}
6965
6966WriteToLogFile(data, file, project) {
6967 logFile := A_ScriptDir "\temp\" file
6968 If (not FileExist(logFile)) {
6969 FileAppend, Starting up %project%....`n`n, %logFile%
6970 }
6971
6972 line := "----------------------------------------------------------"
6973 timeStamp := ""
6974 UTCTimestamp := GetTimestampUTC()
6975 UTCFormatStr := "yyyy-MM-dd'T'HH:mm'Z'"
6976 FormatTime, TimeStr, %UTCTimestamp%, %UTCFormatStr%
6977
6978 entry := line "`n" TimeStr "`n" line "`n`n"
6979 entry := entry . data "`n`n"
6980
6981 FileAppend, %entry%, %logFile%
6982}
6983
6984ParseAddedDamage(String, DmgType, ByRef DmgLo, ByRef DmgHi)
6985{
6986 If (RegExMatch(String, "Adds (\d+) to (\d+) " DmgType " Damage", Match))
6987 {
6988 ;StringSplit, Arr, Match, %A_Space%
6989 ;StringSplit, Arr, Arr2, -
6990 DmgLo := Match1
6991 DmgHi := Match2
6992 }
6993}
6994
6995AssembleDamageDetails(FullItemData)
6996{
6997 Quality := 0
6998 AttacksPerSecond := 0
6999 AttackSpeedIncr := 0
7000 PhysIncr := 0
7001 PhysLo := 0
7002 PhysHi := 0
7003 FireLo := 0
7004 FireHi := 0
7005 ColdLo := 0
7006 ColdHi := 0
7007 LighLo := 0
7008 LighHi := 0
7009 ChaoLo := 0
7010 ChaoHi := 0
7011
7012 MainHFireLo := 0
7013 MainHFireHi := 0
7014 MainHColdLo := 0
7015 MainHColdHi := 0
7016 MainHLighLo := 0
7017 MainHLighHi := 0
7018 MainHChaoLo := 0
7019 MainHChaoHi := 0
7020
7021 OffHFireLo := 0
7022 OffHFireHi := 0
7023 OffHColdLo := 0
7024 OffHColdHi := 0
7025 OffHLighLo := 0
7026 OffHLighHi := 0
7027 OffHChaoLo := 0
7028 OffHChaoHi := 0
7029
7030
7031 Loop, Parse, FullItemData, `n, `r
7032 {
7033 ; Get quality
7034 IfInString, A_LoopField, Quality:
7035 {
7036 StringSplit, Arr, A_LoopField, %A_Space%, +`%
7037 Quality := Arr2
7038 Continue
7039 }
7040
7041 ; Get total physical damage
7042 IfInString, A_LoopField, Physical Damage:
7043 {
7044 StringSplit, Arr, A_LoopField, %A_Space%
7045 StringSplit, Arr, Arr3, -
7046 PhysLo := Arr1
7047 PhysHi := Arr2
7048 Continue
7049 }
7050
7051 ; Get attack speed
7052 IfInString, A_LoopField, Attacks per Second:
7053 {
7054 StringSplit, Arr, A_LoopField, %A_Space%
7055 AttacksPerSecond := Arr4
7056 Continue
7057 }
7058
7059 ; Get percentage attack speed increase
7060 IfInString, A_LoopField, increased Attack Speed
7061 {
7062 StringSplit, Arr, A_LoopField, %A_Space%, `%
7063 AttackSpeedIncr += Arr1 ; There are a few weapons with an AS implicit, so we ADD all relevant lines here
7064 Continue
7065 }
7066
7067 ; Get percentage physical damage increase
7068 IfInString, A_LoopField, increased Physical Damage
7069 {
7070 StringSplit, Arr, A_LoopField, %A_Space%, `%
7071 PhysIncr := Arr1
7072 Continue
7073 }
7074
7075 ; Skip "ele/chaos damage to spells" being counted as "added damage" (implying to attacks)
7076 IfNotInString, A_LoopField, Damage to Spells
7077 {
7078 ; Parse added damage
7079 ; Differentiate general mods from main hand and off hand only
7080 ; Examples for main/off: Dyadus, Wings of Entropy
7081
7082 IfInString, A_LoopField, in Main Hand
7083 {
7084 ParseAddedDamage(A_LoopField, "Fire", MainHFireLo, MainHFireHi)
7085 ParseAddedDamage(A_LoopField, "Cold", MainHColdLo, MainHColdHi)
7086 ParseAddedDamage(A_LoopField, "Lightning", MainHLighLo, MainHLighHi)
7087 ParseAddedDamage(A_LoopField, "Chaos", MainHChaoLo, MainHChaoHi)
7088 }
7089 Else IfInString, A_LoopField, in Off Hand
7090 {
7091 ParseAddedDamage(A_LoopField, "Fire", OffHFireLo, OffHFireHi)
7092 ParseAddedDamage(A_LoopField, "Cold", OffHColdLo, OffHColdHi)
7093 ParseAddedDamage(A_LoopField, "Lightning", OffHLighLo, OffHLighHi)
7094 ParseAddedDamage(A_LoopField, "Chaos", OffHChaoLo, OffHChaoHi)
7095 }
7096 Else
7097 {
7098 ParseAddedDamage(A_LoopField, "Fire", FireLo, FireHi)
7099 ParseAddedDamage(A_LoopField, "Cold", ColdLo, ColdHi)
7100 ParseAddedDamage(A_LoopField, "Lightning", LighLo, LighHi)
7101 ParseAddedDamage(A_LoopField, "Chaos", ChaoLo, ChaoHi)
7102 }
7103 }
7104
7105 }
7106
7107 Result =
7108
7109 If ( AttackSpeedIncr > 0 )
7110 {
7111 BaseAttackSpeed := AttacksPerSecond / (AttackSpeedIncr / 100 + 1)
7112 ; The BaseAttackSpeed's second decimal place is always 0 or 5, so for example 1.24 should actually be 1.25
7113 ; We check how far off it is
7114 ModVal := Mod(BaseAttackSpeed, 0.05)
7115 ; And effectively round to the nearest 0.05
7116 BaseAttackSpeed += (ModVal > 0.025) ? (0.05 - ModVal) : (- ModVal)
7117 ; Now we put the AttacksPerSecond back together
7118 AttacksPerSecond := BaseAttackSpeed * (AttackSpeedIncr / 100 + 1)
7119 }
7120
7121
7122 SetFormat, FloatFast, 5.1
7123 PhysDps := ((PhysLo + PhysHi) / 2) * AttacksPerSecond
7124 EleDps := ((FireLo + FireHi + ColdLo + ColdHi + LighLo + LighHi) / 2) * AttacksPerSecond
7125 MainHEleDps := ((MainHFireLo + MainHFireHi + MainHColdLo + MainHColdHi + MainHLighLo + MainHLighHi) / 2) * AttacksPerSecond
7126 OffHEleDps := ((OffHFireLo + OffHFireHi + OffHColdLo + OffHColdHi + OffHLighLo + OffHLighHi) / 2) * AttacksPerSecond
7127 ChaosDps := ((ChaoLo + ChaoHi) / 2) * AttacksPerSecond
7128 MainHChaosDps := ((MainHChaoLo + MainHChaoHi) / 2) * AttacksPerSecond
7129 OffHChaosDps := ((OffHChaoLo + OffHChaoHi) / 2) * AttacksPerSecond
7130 TotalDps := PhysDps + EleDps + ChaosDps
7131
7132 If (Quality < 20) {
7133 Q20Dps := Q20PhysDps := PhysDps * (PhysIncr + 120) / (PhysIncr + Quality + 100)
7134 Q20Dps := Q20Dps + EleDps + ChaosDps
7135 }
7136
7137 If ( MainHEleDps > 0 or OffHEleDps > 0 or MainHChaosDps > 0 or OffHChaosDps > 0 ) {
7138 MainH_OffH_Display := true
7139 TotalMainHEleDps := MainHEleDps + EleDps
7140 TotalOffHEleDps := OffHEleDps + EleDps
7141 TotalMainHChaosDps := MainHChaosDps + ChaosDps
7142 TotalOffHChaosDps := OffHChaosDps + ChaosDps
7143 TotalMainHDps := PhysDps + TotalMainHEleDps + TotalMainHChaosDps
7144 TotalOffHDps := PhysDps + TotalOffHEleDps + TotalOffHChaosDps
7145 Q20MainHDps := Q20Dps + TotalMainHEleDps + TotalMainHChaosDps
7146 Q20OffHDps := Q20Dps + TotalOffHEleDps + TotalOffHChaosDps
7147
7148 Result = %Result%`nPhys DPS: %PhysDps%
7149
7150 If (Quality < 20)
7151 {
7152 Result = %Result%`nQ20 Phys: %Q20PhysDps%
7153 }
7154
7155 If ( MainHEleDps > 0 or OffHEleDps > 0 )
7156 {
7157 Result = %Result%`nEle DPS: %TotalMainHEleDps% MainH | %TotalOffHEleDps% OffH
7158 }
7159 Else Result = %Result%`nEle DPS: %EleDps%
7160
7161 If ( MainHChaosDps > 0 or OffHChaosDps > 0 )
7162 {
7163 Result = %Result%`nChaos DPS: %TotalMainHChaosDps% MainH | %TotalOffHChaosDps% OffH
7164 }
7165 Else Result = %Result%`nChaos DPS: %ChaosDps%
7166
7167 Result = %Result%`nTotal DPS: %TotalMainHDps% MainH | %TotalOffHDps% OffH
7168
7169 If (Quality < 20)
7170 {
7171 Result = %Result%`nQ20 Total: %Q20MainHDps% MainH | %Q20OffHDps% OffH
7172 }
7173 }
7174 Else
7175 {
7176 Result = %Result%`nEle DPS: %EleDps% Chaos DPS: %ChaosDps%
7177
7178 ; Only show Q20 values if item is not Q20
7179 Result = %Result%`nPhys DPS: %PhysDps%
7180 If (Quality < 20)
7181 {
7182 Result = %Result% Q20 Phys: %Q20PhysDps%
7183 }
7184
7185 Result = %Result%`nTotal DPS: %TotalDps%
7186
7187 If (Quality < 20)
7188 {
7189 Result = %Result% Q20 Total: %Q20Dps%
7190 }
7191 }
7192
7193
7194 Item.DamageDetails := {}
7195 Item.DamageDetails.MainHEleDps := MainHEleDps
7196 Item.DamageDetails.OffHEleDps := OffHEleDps
7197 Item.DamageDetails.MainHChaosDps := MainHChaosDps
7198 Item.DamageDetails.OffHChaosDps := OffHChaosDps
7199 Item.DamageDetails.TotalMainHDps := TotalMainHDps
7200 Item.DamageDetails.TotalOffHDps := TotalOffHDps
7201 Item.DamageDetails.TotalMainHEleDps := TotalMainHEleDps
7202 Item.DamageDetails.TotalOffHEleDps := TotalOffHEleDps
7203 Item.DamageDetails.TotalMainHChaosDps := TotalMainHChaosDps
7204 Item.DamageDetails.TotalOffHChaosDps := TotalOffHChaosDps
7205 Item.DamageDetails.Q20MainHDps := Q20MainHDps
7206 Item.DamageDetails.Q20OffHDps := Q20OffHDps
7207
7208 Item.DamageDetails.Quality := Quality
7209 Item.DamageDetails.PhysDps := PhysDps
7210 Item.DamageDetails.EleDps := EleDps
7211 Item.DamageDetails.ChaosDps := ChaosDps
7212 Item.DamageDetails.TotalDps := TotalDps
7213 Item.DamageDetails.Q20PhysDps := Q20PhysDps
7214 Item.DamageDetails.Q20Dps := Q20Dps
7215
7216 return Result
7217}
7218
7219AssembleProphecyDetails(name) {
7220 parsedJSON := {}
7221 prophecy := {}
7222
7223 If (not Globals.Get("ProphecyData")) {
7224 Try {
7225 FileRead, JSONFile, %A_ScriptDir%\data_trade\prophecy_details.json
7226 parsedJSON := JSON.Load(JSONFile)
7227 prophecy := parsedJSON.prophecy_details[name]
7228
7229 If (not prophecy.text) {
7230 Return
7231 }
7232 } Catch error {
7233 Return
7234 }
7235
7236 Globals.Set("ProphecyData", parsedJSON.prophecy_details)
7237 } Else {
7238 prophecy := Globals.Get("ProphecyData")[name]
7239 }
7240
7241 TT := ""
7242 If (prophecy.objective) {
7243 TT .= "`n" "Objective:" "`n" prophecy.objective "`n"
7244 }
7245 If (prophecy.reward) {
7246 TT .= "`n" "Reward:" "`n" prophecy.reward "`n"
7247 }
7248 If (StrLen(prophecy["seal cost"])) {
7249 TT .= "`n" "Seal Cost:" " " prophecy["seal cost"] "`n"
7250 }
7251
7252 Return TT
7253}
7254
7255; ParseItemName fixed by user: uldo_. Thanks!
7256ParseItemName(ItemDataChunk, ByRef ItemName, ByRef ItemBaseName, AffixCount = "", ItemData = "")
7257{
7258 isVaalGem := false
7259 If (RegExMatch(Trim(ItemData.Parts[1]), "i)^Rarity: Gem") and RegExMatch(Trim(ItemData.Parts[2]), "i)Vaal")) {
7260 isVaalGem := true
7261 }
7262
7263 If (RegExMatch(ItemData.NamePlate, "i)Rarity\s?+:\s?+(Currency|Divination Card|Gem)", match)) {
7264 If (RegExMatch(match1, "i)Gem")) {
7265 ItemBaseName := Trim(RegExReplace(ItemName, "i) Support"))
7266 } Else {
7267 ItemBaseName := Trim(ItemName)
7268 }
7269 }
7270
7271 Loop, Parse, ItemDataChunk, `n, `r
7272 {
7273 If (A_Index == 1)
7274 {
7275 IfNotInString, A_LoopField, Rarity:
7276 {
7277 return
7278 }
7279 Else
7280 {
7281 Continue
7282 }
7283 }
7284
7285 If (StrLen(A_LoopField) == 0 or A_LoopField == "--------" or A_Index > 3)
7286 {
7287 return
7288 }
7289
7290 If (A_Index = 2)
7291 {
7292 If InStr(A_LoopField, ">>")
7293 {
7294 StringGetPos, pos, A_LoopField, >>, R
7295 ItemName := SubStr(A_LoopField, pos+3)
7296 }
7297 Else
7298 {
7299 ItemName := A_LoopField
7300 If (isVaalGem and not RegExMatch(ItemName, "i)^Vaal ")) {
7301 ; examples of name differences
7302 ; summon skeleton - vaal summon skeletons
7303 ; Purity of Lightning - Vaal Impurity of Lightning
7304 ItemName := ItemData.Parts[6] ; this may be unsafe, the parts index may change in the future
7305
7306 For k, part in ItemData.Parts {
7307 If (RegExMatch(part, "im)(^Vaal .*?" ItemName ".*)", vaalName)) { ; TODO: make sure this is safer
7308 ItemName := vaalName1
7309 Break
7310 }
7311 }
7312 }
7313 }
7314
7315 ; Normal items don't have a third line and the item name equals the BaseName if we sanitize it ("superior").
7316 ; Also unidentified items.
7317 If (RegExMatch(ItemDataChunk, "i)Rarity.*?:.*?Normal") or RegExMatch(ItemData.PartsLast, "i)Unidentified"))
7318 {
7319 ItemBaseName := Trim(RegExReplace(ItemName, "i)Superior", ""))
7320 Return
7321 }
7322 ; Magic items don't have a third line.
7323 ; Sanitizing the item name is a bit more complicated but should work with the following assumptions:
7324 ; 1. The suffix always begins with " of".
7325 ; 2. The prefix consists of only 1 word, never more.
7326 ; We need to know the AffixCount for this though.
7327 Else If (AffixCount > 0) {
7328 If (RegExMatch(ItemDataChunk, "i)Rarity.*?:.*?Magic"))
7329 {
7330 ItemBaseName := Trim(RegExReplace(ItemName, "i) of .*", "", matchCount))
7331 If ((matchCount and AffixCount > 1) or (not matchCount and AffixCount = 1))
7332 {
7333 ; We replaced the suffix and have 2 affixes, therefore we must also have a prefix that we can replace.
7334 ; OR we didn't replace the suffix but have 1 mod, therefore we must have a prefix that we can replace.
7335 ItemBaseName := Trim(RegExReplace(ItemBaseName, "iU)^.* ", ""))
7336 Return
7337 }
7338 }
7339 }
7340 }
7341 If (A_Index = 3)
7342 {
7343 ItemBaseName := A_LoopField
7344 }
7345 }
7346}
7347
7348UniqueHasFatedVariant(ItemName)
7349{
7350 Loop, Read, %A_ScriptDir%\data\UniqueHasFatedVariant.txt
7351 {
7352 Line := StripLineCommentRight(A_LoopReadLine)
7353 If (SkipLine(Line))
7354 {
7355 Continue
7356 }
7357 If (ItemName == Line)
7358 {
7359 return True
7360 }
7361 }
7362 return False
7363}
7364
7365ParseLinks(ItemDataText)
7366{
7367 HighestLink := 0
7368 Loop, Parse, ItemDataText, `n, `r
7369 {
7370 IfInString, A_LoopField, Sockets
7371 {
7372 LinksString := GetColonValue(A_LoopField)
7373 If (RegExMatch(LinksString, ".-.-.-.-.-."))
7374 {
7375 HighestLink := 6
7376 Break
7377 }
7378 If (RegExMatch(LinksString, ".-.-.-.-."))
7379 {
7380 HighestLink := 5
7381 Break
7382 }
7383 If (RegExMatch(LinksString, ".-.-.-."))
7384 {
7385 HighestLink := 4
7386 Break
7387 }
7388 If (RegExMatch(LinksString, ".-.-."))
7389 {
7390 HighestLink := 3
7391 Break
7392 }
7393 If (RegExMatch(LinksString, ".-."))
7394 {
7395 HighestLink := 2
7396 Break
7397 }
7398 }
7399 }
7400 return HighestLink
7401}
7402
7403ParseSockets(ItemDataText, ByRef AbyssalSockets)
7404{
7405 SocketsCount := 0
7406
7407 Loop, Parse, ItemDataText, `n, `r
7408 {
7409 If (RegExMatch(A_LoopField, "i)^Sockets\s?+:"))
7410 {
7411 LinksString := GetColonValue(A_LoopField)
7412 RegExReplace(LinksString, "i)[RGBWDA]", "", SocketsCount) ; "D" is being used for Resonator sockets, "A" for Abyssal Sockets
7413 RegExReplace(LinksString, "i)[A]", "", AbyssalSockets) ; "A" for Abyssal Sockets
7414 Break
7415 }
7416 }
7417 return SocketsCount
7418}
7419
7420ParseSocketGroups(ItemDataText, ByRef RawSocketString = "")
7421{
7422 groups := []
7423 Loop, Parse, ItemDataText, `n, `r
7424 {
7425 IfInString, A_LoopField, Sockets
7426 {
7427 RegExMatch(A_LoopField, "i)Sockets:\s?(.*)", socketString)
7428
7429 sockets := socketString1 " " ; add a space at the end for easier regex
7430 If (StrLen(socketString1)) {
7431 RawSocketString := socketString1
7432 }
7433 If (StrLen(sockets)) {
7434 Pos := 0
7435 While Pos := RegExMatch(sockets, "i)(.*?)\s+", value, Pos + (StrLen(value) ? StrLen(value) : 1)) {
7436 s := Trim(value1)
7437 s := RegExReplace(s, "i)-")
7438 If (StrLen(Trim(s))) {
7439 groups.push(s)
7440 }
7441 }
7442 }
7443 }
7444 }
7445
7446 return groups
7447}
7448
7449ParseCharges(stats)
7450{
7451 charges := {}
7452 Loop, Parse, stats, `n, `r
7453 {
7454 LoopField := RegExReplace(A_Loopfield, "i)\s\(augmented\)", "")
7455 ; Flasks
7456 RegExMatch(LoopField, "i)Consumes (\d+) of (\d+) Charges on use.*", max)
7457 If (max) {
7458 charges.usage := max1
7459 charges.max := max2
7460 }
7461 RegExMatch(LoopField, "i)Currently has (\d+) Charge.*", current)
7462 If (current) {
7463 charges.current:= current1
7464 }
7465
7466 ; Leaguestones
7467 RegExMatch(LoopField, "i)Currently has (\d+) of (\d+) Charge.*", max)
7468 If (max) {
7469 charges.usage := 1
7470 charges.max := max2
7471 charges.current:= max1
7472 }
7473 }
7474
7475 return charges
7476}
7477
7478ParseBeastData(data) {
7479 a := {}
7480
7481 legendaryBeastsList := ["Farric Wolf Alpha", "Fenumal Scorpion", "Fenumal Plaqued Arachnid", "Farric Frost Hellion Alpha", "Farric Lynx ALpha", "Saqawine Vulture", "Craicic Chimeral", "Saqawine Cobra", "Craicic Maw", "Farric Ape", "Farric Magma Hound", "Craicic Vassal", "Farric Pit Hound", "Craicic Squid"
7482 , "Farric Taurus", "Fenumal Scrabbler", "Farric Goliath", "Fenumal Queen", "Saqawine Blood Viper", "Fenumal Devourer", "Farric Ursa", "Fenumal Widow", "Farric Gargantuan", "Farric Chieftain", "Farric Ape", "Farrci Flame Hellion Alpha", "Farrci Goatman", "Craicic Watcher", "Saqawine Retch"
7483 , "Saqawine Chimeral", "Craicic Shield Crab", "Craicic Sand Spitter", "Craicic Savage Crab", "Saqawine Rhoa"]
7484
7485 portalBeastsList := ["Farric Tiger Alpha", "Craicic Spider Crab", "Fenumal Hybrid Arachnid", "Saqawine Rhex"]
7486 aspectBeastsList := ["Farrul, First of the Plains", "Craiceann, First of the Deep", "Fenumus, First of the Night", "Saqawal, First of the Sky"]
7487
7488 nameplate := data.nameplate
7489 Loop, Parse, nameplate, `n, `r
7490 {
7491 If (A_Index = 2 and IsInArray(Trim(A_LoopField), aspectBeastsList)) {
7492 a.IsAspectBeast := true
7493 a.BeastName := Trim(A_LoopField)
7494 }
7495
7496 If (A_Index = 3) {
7497 a.BeastBase := Trim(A_LoopField)
7498 If (IsInArray(Trim(A_LoopField), portalBeastsList)) {
7499 a.IsPortalbeast := true
7500 }
7501 Else If (IsInArray(Trim(A_LoopField), legendaryBeastsList)) {
7502 a.IsLegendaryBeast := true
7503 }
7504
7505 }
7506 }
7507
7508 parts := data.parts[2]
7509 Loop, Parse, parts, `n, `r
7510 {
7511 RegExMatch(A_LoopField, "i)(Genus|Family|Group):\s+?(.*)", match)
7512 a[match1] := Trim(match2)
7513 }
7514
7515 parts := data.parts[4]
7516 a["Mods"] := []
7517 Loop, Parse, parts, `n, `r
7518 {
7519 If (RegExMatch(A_LoopField, "i)(Aspect of the Hellion|Blood Geyser|Churning Claws|Craicic Presence|Crimson Flock|Crushing Claws|Deep One's Presence|Erupting Winds|Farric Presence|Fenumal Presence|Fertile Presence|Hadal Dive|Incendiary Mite|Infested Earth|Putrid Flight|Raven Caller|Saqawine Presence|Satyr Storm|Spectral Stampede|Spectral Swipe|Tiger Prey|Unstable Swarm|Vile Hatchery|Winter Bloom)", match))
7520 {
7521 a["Mods"].Push(match1)
7522 }
7523 }
7524
7525 return a
7526}
7527
7528ParseAreaMonsterLevelRequirement(stats)
7529{
7530 requirements := {}
7531 Loop, Parse, stats, `n, `r
7532 {
7533 RegExMatch(A_LoopField, "i)Can only be used in Areas with Monster Level(.*)", req)
7534 RegExMatch(req1, "i)(\d+).*", lvl)
7535 RegExMatch(req1, "i)below|above|higher|lower", logicalOperator)
7536
7537 If (lvl) {
7538 requirements.lvl := Trim(lvl1)
7539 }
7540 If (logicalOperator) {
7541 requirements.logicalOperator := Trim(logicalOperator)
7542 }
7543 }
7544 return requirements
7545}
7546
7547; Converts a currency stack to Chaos by looking up the
7548; conversion ratio from CurrencyRates.txt or downloaded ratios from poe.ninja
7549ConvertCurrency(ItemName, ItemStats, ByRef dataSource)
7550{
7551 If (InStr(ItemName, "Shard"))
7552 {
7553 IsShard := True
7554 ItemName := "Orb of " . SubStr(ItemName, 1, -StrLen(" Shard"))
7555 }
7556 If (InStr(ItemName, "Fragment"))
7557 {
7558 IsFragment:= True
7559 ItemName := "Scroll of Wisdom"
7560 }
7561 StackSize := SubStr(ItemStats, StrLen("Stack Size: "))
7562 StringSplit, StackSizeParts, StackSize, /
7563 If (IsShard or IsFragment)
7564 {
7565 SetFormat, FloatFast, 5.3
7566 StackSize := StackSizeParts1 / StackSizeParts2
7567 }
7568 Else
7569 {
7570 SetFormat, FloatFast, 5.2
7571 StackSize := RegExReplace(StackSizeParts1, "i)[^0-9a-z]")
7572 }
7573
7574 ; Update currency rates from poe.ninja
7575 last := Globals.Get("LastCurrencyUpdate")
7576 diff := A_NowUTC
7577 EnvSub, diff, %last%, Minutes
7578 If (diff > 180 or !last) {
7579 ; no data or older than 3 hours
7580 GoSub, FetchCurrencyData
7581 }
7582
7583 ; Use downloaded currency rates if they exist, otherwise use hardcoded fallback
7584 fallback := A_ScriptDir . "\data\CurrencyRates.txt"
7585 result := []
7586
7587 CurrencyDataRates := Globals.Get("CurrencyDataRates")
7588 For idx, league in ["Synthesis", "tmphardcore", "Standard", "Hardcore", "eventstandard", "eventhardcore"] {
7589 ninjaRates := CurrencyDataRates[league]
7590 ChaosRatio := ninjaRates[ItemName].OwnQuantity ":" ninjaRates[ItemName].ChaosQuantity
7591 ChaosMult := ninjaRates[ItemName].ChaosQuantity / ninjaRates[ItemName].OwnQuantity
7592 ValueInChaos := (ChaosMult * StackSize)
7593
7594 If (league == "Synthesis" or league == "tmphardcore" ) {
7595 leagueName := InStr(league, "Synthesis") ? "Challenge Standard" : "Challenge Hardcore"
7596 }
7597 Else If (league = "eventstandard" or league = "eventhardcore") {
7598 leagueName := InStr(league, "standard") ? "Event Standard " : "Event Hardcore "
7599 }
7600 Else {
7601 leagueName := "Permanent " . league
7602 }
7603
7604 If (ValueInChaos) {
7605 tmp := [leagueName ": ", ValueInChaos, ChaosRatio]
7606 result.push(tmp)
7607 }
7608 }
7609
7610 ; fallback - condition : no results found so far
7611 If (!result.Length()) {
7612 ValueInChaos := 0
7613 dataSource := "Fallback <\data\CurrencyRates.txt>`n`n"
7614 leagueName := "Hardcoded rates: "
7615
7616 Loop, Read, %fallback%
7617 {
7618 Line := StripLineCommentRight(A_LoopReadLine)
7619 If (SkipLine(Line))
7620 {
7621 Continue
7622 }
7623 IfInString, Line, %ItemName%
7624 {
7625 StringSplit, LineParts, Line, |
7626 ChaosRatio := LineParts2
7627 StringSplit, ChaosRatioParts,ChaosRatio, :
7628 ChaosMult := ChaosRatioParts2 / ChaosRatioParts1
7629 ValueInChaos := (ChaosMult * StackSize)
7630 }
7631 }
7632
7633 If (ValueInChaos) {
7634 tmp := [leagueName, ValueInChaos, ChaosRatio]
7635 result.push(tmp)
7636 }
7637 }
7638
7639 return result
7640}
7641
7642FindUnique(ItemName)
7643{
7644 Loop, Read, %A_ScriptDir%\data\Uniques.txt
7645 {
7646 Line := StripLineCommentRight(A_LoopReadLine)
7647 If (SkipLine(Line))
7648 {
7649 Continue
7650 }
7651 IfInString, Line, %ItemName%
7652 {
7653 return True
7654 }
7655 }
7656 return False
7657}
7658
7659; Strip comments at line end, e.g. "Bla bla bla ; comment" -> "Bla bla bla"
7660StripLineCommentRight(Line)
7661{
7662 IfNotInString, Line, `;
7663 {
7664 return Line
7665 }
7666 ProcessedLine := RegExReplace(Line, "(.+?)([ \t]*;.+)", "$1")
7667 If IsEmptyString(ProcessedLine)
7668 {
7669 return Line
7670 }
7671 return ProcessedLine
7672}
7673
7674; Return True if line begins with comment character (;)
7675; or if it is blank (that is, it only has 2 characters
7676; at most (newline and carriage return)
7677SkipLine(Line)
7678{
7679 IfInString, Line, `;
7680 {
7681 ; Comment
7682 return True
7683 }
7684 If (StrLen(Line) <= 2)
7685 {
7686 ; Blank line (at most \r\n)
7687 return True
7688 }
7689 return False
7690}
7691
7692; Parse unique affixes from text file database.
7693; Has wanted side effect of populating AffixLines "array" vars.
7694; return True if the unique was found the database
7695ParseUnique(ItemName)
7696{
7697 Global Opts, AffixLines
7698
7699 ResetAffixDetailVars()
7700 UniqueFound := False
7701 Loop, Read, %A_ScriptDir%\data\Uniques.txt
7702 {
7703 ALine := StripLineCommentRight(A_LoopReadLine)
7704 If (SkipLine(ALine))
7705 {
7706 Continue
7707 }
7708 If (RegExMatch(ALine, "^" ItemName "\|"))
7709 {
7710 StringSplit, LineParts, ALine, |
7711 NumLineParts := LineParts0
7712 UniqueFound := True
7713 AppendImplicitSep := False
7714 Idx := 1
7715 Loop, % (NumLineParts)
7716 {
7717 If (A_Index > 1)
7718 {
7719 ProcessedLine =
7720 CurLinePart := LineParts%A_Index%
7721 IfInString, CurLinePart, :
7722 {
7723 StringSplit, CurLineParts, CurLinePart, :
7724 AffixLine := CurLineParts2
7725 ValueRange := CurLineParts1
7726 IfInString, ValueRange, @
7727 {
7728 AppendImplicitSep := True
7729 StringReplace, ValueRange, ValueRange, @
7730 }
7731
7732 IfInString, ValueRange, `,
7733 {
7734 ; Double range
7735 StringSplit, VRParts, ValueRange, `,
7736 LowerRange := VRParts1
7737 UpperRange := VRParts2
7738 StringSplit, LowerBoundParts, LowerRange, -
7739 StringSplit, UpperBoundParts, UpperRange, -
7740 BtmMin := LowerBoundParts1
7741 BtmMax := LowerBoundParts2
7742 TopMin := UpperBoundParts1
7743 TopMax := UpperBoundParts2
7744 ValueRange := FormatDoubleRanges(BtmMin, BtmMax, TopMin, TopMax)
7745 }
7746
7747 ValueRange := StrPad(ValueRange, 7, "left")
7748
7749 If (AppendImplicitSep)
7750 {
7751 ValueRange .= "`n--------"
7752 AppendImplicitSep := False
7753 }
7754 ProcessedLine := [AffixLine, ValueRange]
7755 }
7756 Else{
7757 ProcessedLine := [CurLinePart, ""]
7758 }
7759 AffixLines.Set(Idx, ProcessedLine)
7760 Idx += 1
7761 }
7762 }
7763 return UniqueFound
7764 }
7765 }
7766 return UniqueFound
7767}
7768
7769ItemIsMirrored(ItemDataText)
7770{
7771 Loop, Parse, ItemDataText, `n, `r
7772 {
7773 RegExMatch(Trim(A_LoopField), "i)^Mirrored$", match)
7774 If (match) {
7775 return True
7776 }
7777 }
7778 return False
7779}
7780
7781ItemIsHybridBase(ItemDataText)
7782{
7783 DefenceStatCount := 0
7784 Loop, Parse, ItemDataText, `n, `r
7785 {
7786 If RegExMatch(Trim(A_LoopField), "^(Armour|Evasion Rating|Energy Shield): \d+( \(augmented\))?$")
7787 {
7788 DefenceStatCount += 1
7789 }
7790 }
7791 return (DefenceStatCount > 1) ? True : False
7792}
7793
7794
7795/*
7796########### MAIN PARSE FUNCTION ##############
7797
7798Invocation stack (simplified) for full item parse:
7799
7800(timer watches clipboard contents)
7801(on clipboard changed) ->
7802
7803ParseClipBoardChanges()
7804 PreProcessContents()
7805 ParseItemData()
7806 (get item details by calling many other Parse... functions)
7807 ParseAffixes()
7808 (on affix match found) ->
7809 Simple affixes:
7810 LookupAffixAndSetInfoLine()
7811 AppendAffixInfo(MakeAffixDetailLine()) ; appends to global AffixLines table
7812 Complex affixes:
7813 Use functions depending on Pre-Pass flags, set by ParseAffixes()
7814 Put results into Itemdata.UncertainAffixes
7815 Decide which ones are possible regarding prefix/suffix limit and append them to global AffixLines table
7816 PostProcessData()
7817 ShowToolTip()
7818*/
7819ParseItemData(ItemDataText, ByRef RarityLevel="")
7820{
7821 Global Opts, AffixTotals, mapList, mapMatchList, uniqueMapList, uniqueMapNameFromBase, divinationCardList, gemQualityList
7822
7823 ItemDataPartsIndexLast =
7824 ItemDataPartsIndexAffixes =
7825 ItemDataPartsLast =
7826 ItemDataNamePlate =
7827 ItemDataStats =
7828 ItemDataAffixes =
7829 ItemDataRequirements =
7830 ItemDataRarity =
7831 ItemDataLinks =
7832 ItemName =
7833 ItemBaseName =
7834 ItemQuality =
7835 ItemLevel =
7836 ItemMaxSockets =
7837 ItemBaseType =
7838 ItemSubType =
7839 ItemGripType =
7840 BaseLevel =
7841 RarityLevel =
7842 TempResult =
7843 Variation =
7844
7845 Item.Init()
7846 ItemData.Init()
7847
7848 ResetAffixDetailVars()
7849
7850 ItemData.FullText := ItemDataText
7851
7852 Loop, Parse, ItemDataText, `n, `r
7853 {
7854 RegExMatch(Trim(A_LoopField), "i)^Corrupted$", corrMatch)
7855 If (corrMatch) {
7856 Item.IsCorrupted := True
7857 }
7858 RegExMatch(Trim(A_LoopField), "i)^(Elder|Shaper|Synthesised|Fractured) Item$", match)
7859 If (match) {
7860 Item["Is" match1 "Base"] := True
7861 }
7862 }
7863
7864 ; AHK only allows splitting on single chars, so first
7865 ; replace the split string (\r\n--------\r\n) with AHK's escape char (`)
7866 ; then do the actual string splitting...
7867 StringReplace, TempResult, ItemDataText, `r`n--------`r`n, ``, All
7868 StringSplit, ItemDataParts, TempResult, ``,
7869
7870 ItemData.NamePlate := ItemDataParts1
7871 ItemData.Stats := ItemDataParts2
7872
7873 ItemDataIndexLast := ItemDataParts0
7874 ItemDataPartsLast := ItemDataParts%ItemDataIndexLast%
7875
7876 Loop, %ItemDataParts0%
7877 {
7878 ItemData.Parts[A_Index] := ItemDataParts%A_Index%
7879 }
7880 ItemData.PartsLast := ItemDataPartsLast
7881 ItemData.IndexLast := ItemDataIndexLast
7882
7883 ; ItemData.Requirements := GetItemDataChunk(ItemDataText, "Requirements:")
7884 ; ParseRequirements(ItemData.Requirements, RequiredLevel, RequiredAttributes, RequiredAttributeValues)
7885
7886 ParseItemName(ItemData.NamePlate, ItemName, ItemBaseName, "", ItemData)
7887 If (Not ItemName)
7888 {
7889 return
7890 }
7891
7892 Item.Name := ItemName
7893 Item.BaseName := ItemBaseName
7894
7895 IfInString, ItemDataText, Unidentified
7896 {
7897 If (Item.Name != "Scroll of Wisdom")
7898 {
7899 Item.IsUnidentified := True
7900 }
7901 }
7902
7903 Item.Quality := ParseQuality(ItemData.Stats)
7904
7905 ; This function should return the second part of the "Rarity: ..." line
7906 ; in the case of "Rarity: Unique" it should return "Unique"
7907 ItemData.Rarity := ParseRarity(ItemData.NamePlate)
7908
7909 ItemData.Links := ParseLinks(ItemDataText)
7910 Item.Links := ItemData.Links
7911 ItemData.Sockets := ParseSockets(ItemDataText, ItemAbyssalSockets)
7912 Item.Sockets := ItemData.Sockets
7913 Item.AbyssalSockets := ItemAbyssalSockets
7914 Item.SocketGroups := ParseSocketGroups(ItemDataText, ItemSocketString)
7915 Item.SocketString := ItemSocketString
7916
7917 Item.Charges := ParseCharges(ItemData.Stats)
7918
7919 Item.IsUnique := False
7920 If (InStr(ItemData.Rarity, "Unique"))
7921 {
7922 Item.IsUnique := True
7923 }
7924
7925 If (InStr(ItemData.Rarity, "Rare"))
7926 {
7927 Item.IsRare := True
7928 }
7929
7930 ; Divination Card detection = Normal rarity with stack size (100% valid??)
7931 ; Cards like "The Void" don't have a stack size
7932 If (InStr(ItemData.Rarity, "Divination Card"))
7933 {
7934 Item.IsDivinationCard := True
7935 Item.BaseType := "Divination Card"
7936 }
7937
7938 ; Prophecy Orb detection
7939 If (InStr(ItemData.PartsLast, "to add this prophecy to"))
7940 {
7941 Item.IsProphecy := True
7942 Item.BaseType := "Prophecy"
7943 ; ParseProphecy(ItemData, Difficulty)
7944 ; Item.DifficultyRestriction := Difficulty
7945 }
7946
7947 ; Hideout doodad detection
7948 If (InStr(ItemData.PartsLast, "Creates an object in your hideout"))
7949 {
7950 Item.IsHideoutObject := True
7951 }
7952
7953 ; Beast detection
7954 If (RegExMatch(ItemData.Parts[2], "i)Genus|Family"))
7955 {
7956 Item.IsBeast := True
7957 Item.BeastData := ParseBeastData(ItemData)
7958 Item.BaseType := "Beast"
7959 }
7960
7961 Item.IsGem := (InStr(ItemData.Rarity, "Gem"))
7962 Item.IsCurrency:= (InStr(ItemData.Rarity, "Currency"))
7963 Item.IsFossil := (RegExMatch(ItemData.NamePlate, "i)Fossil$")) ? true : false
7964 Item.IsScarab := (RegExMatch(ItemData.NamePlate, "i)Scarab$")) ? true : false
7965
7966 regex := ["^Sacrifice At", "^Fragment of", "^Mortal ", "^Offering to ", "'s Key$", "Ancient Reliquary Key", "Timeworn Reliquary Key", "Breachstone", "Divine Vessel"]
7967 For key, val in regex {
7968 If (RegExMatch(Item.Name, "i)" val "")) {
7969 Item.IsMapFragment := True
7970 Item.SubType := "Map Fragment"
7971 Break
7972 }
7973 }
7974
7975 If (Not (InStr(ItemDataText, "Itemlevel:") or InStr(ItemDataText, "Item Level:")) and not Item.IsGem and not Item.IsCurrency and not Item.IsDivinationCard and not Item.IsProphecy and not Item.IsScarab)
7976 {
7977 return Item.Name
7978 }
7979
7980 If (Item.IsGem)
7981 {
7982 RarityLevel := 0
7983 Item.Level := ParseGemLevel(ItemDataText, "Level:")
7984 Item.GemColor := ParseGemColor(ItemDataText)
7985 ItemExperienceFlat := ""
7986 Item.Experience:= ParseGemXP(ItemDataText, "Experience:", ItemExperienceFlat)
7987 Item.ExperienceFlat := ItemExperienceFlat
7988 ItemLevelWord := "Gem Level:"
7989 ItemXPWord := "Experience:"
7990 Item.BaseType := "Gem"
7991 }
7992 Else
7993 {
7994 If (Item.IsCurrency)
7995 {
7996 Item.BaseType := "Currency"
7997
7998 dataSource := ""
7999 ValueInChaos := ConvertCurrency(Item.Name, ItemData.Stats, dataSource)
8000 If (ValueInChaos.Length() and not Item.Name == "Chaos Orb")
8001 {
8002 CurrencyDetails := "`n" . dataSource
8003 CurrencyValueLength := 0
8004 CurrencyRatioLength := 0
8005 Loop, % ValueInChaos.Length()
8006 {
8007 CurrencyValueLength := CurrencyValueLength < StrLen(ValueInChaos[A_Index][2]) ? StrLen(ValueInChaos[A_Index][2]) : CurrencyValueLength
8008 CurrencyRatioLength := CurrencyRatioLength < StrLen(ValueInChaos[A_Index][3]) ? StrLen(ValueInChaos[A_Index][3]) : CurrencyRatioLength
8009 }
8010 Loop, % ValueInChaos.Length()
8011 {
8012 CurrencyDetails .= ValueInChaos[A_Index][1]
8013 CurrencyDetails .= "" . StrPad(ValueInChaos[A_Index][2], CurrencyValueLength, "left") . " Chaos "
8014 CurrencyDetails .= StrPad("(" . ValueInChaos[A_Index][3], CurrencyRatioLength + 1, "left") . "c)`n"
8015 }
8016 }
8017 }
8018
8019 ; Don't do this on Divination Cards or this script crashes on trying to do the ParseItemLevel
8020 Else If (Not Item.IsCurrency and Not Item.IsDivinationCard and Not Item.IsProphecy)
8021 {
8022 RarityLevel := CheckRarityLevel(ItemData.Rarity)
8023 If (not Item.IsScarab) {
8024 Item.Level := ParseItemLevel(ItemDataText)
8025 ItemLevelWord := "Item Level:"
8026 }
8027 If (Not Item.IsBeast) {
8028 ParseItemType(ItemData.Stats, ItemData.NamePlate, ItemBaseType, ItemSubType, ItemGripType, RarityLevel)
8029 Item.BaseType := ItemBaseType
8030 Item.SubType := ItemSubType
8031 Item.GripType := ItemGripType
8032 }
8033 }
8034 }
8035
8036 Item.RarityLevel := RarityLevel
8037
8038 Item.IsBow := (Item.SubType == "Bow")
8039 Item.IsFlask := (Item.SubType == "Flask")
8040 Item.IsBelt := (Item.SubType == "Belt")
8041 Item.IsRing := (Item.SubType == "Ring")
8042 Item.IsUnsetRing := (Item.IsRing and InStr(ItemData.NamePlate, "Unset Ring"))
8043 Item.IsAmulet := (Item.SubType == "Amulet")
8044 Item.IsTalisman := (Item.IsAmulet and InStr(ItemData.NamePlate, "Talisman") and !InStr(ItemData.NamePlate, "Amulet"))
8045 Item.IsSingleSocket := (IsUnsetRing)
8046 Item.IsFourSocket := (Item.SubType == "Gloves" or Item.SubType == "Boots" or Item.SubType == "Helmet")
8047 Item.IsThreeSocket := (Item.GripType == "1H" or Item.SubType == "Shield")
8048 Item.IsQuiver := (Item.SubType == "Quiver")
8049 Item.IsWeapon := (Item.BaseType == "Weapon")
8050 Item.IsArmour := (Item.BaseType == "Armour")
8051 Item.IsHybridBase := (ItemIsHybridBase(ItemDataText))
8052 Item.IsMap := (Item.BaseType == "Map")
8053 Item.IsLeaguestone := (Item.BaseType == "Leaguestone")
8054 Item.IsJewel := (Item.BaseType == "Jewel")
8055 Item.IsAbyssJewel := (Item.IsJewel and RegExMatch(Item.SubType, "i)(Murderous|Hypnotic|Searching|Ghastly) Eye"))
8056 Item.IsMirrored := (ItemIsMirrored(ItemDataText) and Not Item.IsCurrency)
8057 Item.IsEssence := Item.IsCurrency and RegExMatch(Item.Name, "i)Essence of |Remnant of Corruption")
8058 Item.Note := Globals.Get("ItemNote")
8059
8060 If (Item.IsLeaguestone) {
8061 Item.AreaMonsterLevelReq := ParseAreaMonsterLevelRequirement(ItemData.Stats)
8062 }
8063
8064 TempStr := ItemData.PartsLast
8065 Loop, Parse, TempStr, `n, `r
8066 {
8067 RegExMatch(Trim(A_LoopField), "i)^Has ", match)
8068 If (match) {
8069 Item.HasEffect := True
8070 }
8071 ; parse item variations like relics (variation of it's unique counterpart)
8072 If (RegExMatch(Trim(A_LoopField), "i)Relic Unique", match)) {
8073 Item.IsRelic := true
8074 }
8075 }
8076
8077 If Item.IsTalisman {
8078 Loop, Read, %A_ScriptDir%\data\TalismanTiers.txt
8079 {
8080 ; This loop retrieves each line from the file, one at a time.
8081 StringSplit, TalismanData, A_LoopReadLine, |,
8082 If InStr(ItemData.NamePlate, TalismanData1) {
8083 Item.TalismanTier := TalismanData2
8084 }
8085 }
8086 }
8087
8088 ItemDataIndexAffixes := ItemData.IndexLast - GetNegativeAffixOffset(Item)
8089 If (ItemDataIndexAffixes <= 0)
8090 {
8091 ; ItemDataParts doesn't have the parts/text we need. Bail.
8092 ; This might be because the clipboard is completely empty.
8093 return
8094 }
8095
8096 If (Item.IsLeagueStone or Item.IsScarab) {
8097 ItemDataIndexAffixes := ItemDataIndexAffixes - 1
8098 }
8099 If (Item.IsBeast) {
8100 ItemDataIndexAffixes := ItemDataIndexAffixes - 1
8101 }
8102 ItemData.Affixes := RegExReplace(ItemDataParts%ItemDataIndexAffixes%, "[\r\n]+([a-z])", " $1")
8103 ItemData.IndexAffixes := ItemDataIndexAffixes
8104
8105 ; Retrieve items implicit mod if it has one
8106 If (Item.IsWeapon or Item.IsQuiver or Item.IsArmour or Item.IsRing or Item.IsBelt or Item.IsAmulet or Item.IsJewel) {
8107 ; Magic and higher rarity
8108 If (RarityLevel > 1) {
8109 ItemDataIndexImplicit := ItemData.IndexLast - GetNegativeAffixOffset(Item) - 1
8110 }
8111 ; Normal rarity
8112 Else {
8113 ItemDataIndexImplicit := ItemData.IndexLast - GetNegativeAffixOffset(Item)
8114 }
8115
8116 ; Check that there is no ":" in the retrieved text = can only be an implicit mod
8117 _implicitFound := !InStr(ItemDataParts%ItemDataIndexImplicit%, ":")
8118 If (_implicitFound) {
8119 tempImplicit := ItemDataParts%ItemDataIndexImplicit%
8120 Loop, Parse, tempImplicit, `n, `r
8121 {
8122 Item.Implicit.push(A_LoopField)
8123 }
8124 Item.hasImplicit := True
8125 }
8126
8127 ; Check if there is a second "possible implicit" which means the first one is actually an enchantmet
8128 _ItemDataIndexImplicit := ItemDataIndexImplicit - 1
8129 If (_implicitFound and !InStr(ItemDataParts%_ItemDataIndexImplicit%, ":")) {
8130 tempImplicit := ItemDataParts%_ItemDataIndexImplicit%
8131 Loop, Parse, tempImplicit, `n, `r
8132 {
8133 Item.Enchantment.push(A_LoopField)
8134 }
8135 Item.hasImplicit := True
8136 Item.hasEnchantment := True
8137 }
8138 }
8139
8140 ItemData.Stats := ItemDataParts2
8141
8142 If (Item.IsFlask)
8143 {
8144 ParseFlaskAffixes(ItemData.Affixes)
8145 }
8146 Else If (Item.IsBeast)
8147 {
8148 ; already parsed
8149 }
8150 Else If (RarityLevel > 1 and RarityLevel < 4 and Item.IsMap = False and not (Item.IsLeaguestone or Item.IsScarab)) ; Code added by Bahnzo to avoid maps showing affixes
8151 {
8152 ParseAffixes(ItemData.Affixes, Item)
8153 }
8154 Else If (RarityLevel > 1 and RarityLevel < 4 and Item.IsMap = True)
8155 {
8156 MapModWarnings := ParseMapAffixes(ItemData.Affixes)
8157 }
8158 Else If (RarityLevel > 1 and RarityLevel < 4 and (Item.IsLeaguestone or Item.IsScarab))
8159 {
8160 ParseLeagueStoneAffixes(ItemData.Affixes, Item)
8161 }
8162
8163 If (RarityLevel > 1 and Item.IsMap = False) {
8164 Item.veiledPrefixCount := GetVeiledModCount(ItemData.Affixes, "Prefix")
8165 Item.veiledSuffixCount := GetVeiledModCount(ItemData.Affixes, "Suffix")
8166 }
8167
8168 AffixTotals.FormatAll()
8169
8170 NumPrefixes := AffixTotals.NumPrefixes
8171 NumPrefixesMax := AffixTotals.NumPrefixesMax
8172 NumSuffixes := AffixTotals.NumSuffixes
8173 NumSuffixesMax := AffixTotals.NumSuffixesMax
8174 NumTotalAffixes := NumFormatPointFiveOrInt( (AffixTotals.NumTotal > NumPrefixes + NumSuffixes) ? AffixTotals.NumTotal : NumPrefixes + NumSuffixes )
8175 AffixTotals.NumTotal := NumTotalAffixes
8176 NumTotalAffixesMax := NumFormatPointFiveOrInt( (AffixTotals.NumTotalMax > AffixTotals.NumTotal) ? AffixTotals.NumTotalMax : AffixTotals.NumTotal)
8177 AffixTotals.NumTotalMax := NumTotalAffixesMax
8178 ; We need to call this function a second time because now we know the AffixCount.
8179 ParseItemName(ItemData.NamePlate, ItemName, ItemBaseName, NumTotalAffixes, ItemData)
8180 Item.BaseName := ItemBaseName
8181
8182 pseudoMods := PreparePseudoModCreation(ItemData.Affixes, Item.Implicit, RarityLevel, Item.isMap)
8183
8184 ; Start assembling the text for the tooltip
8185 TT := Item.Name
8186
8187 If (Item.BaseName && (Item.BaseName != Item.Name))
8188 {
8189 TT := TT . "`n" . Item.BaseName
8190 }
8191
8192 If (Item.IsGem) {
8193 If (Item.GemColor) {
8194 TT := TT . "`nColor: " . Item.GemColor
8195 }
8196 }
8197
8198 If (Item.IsCurrency)
8199 {
8200 TT := TT . "`n" . CurrencyDetails
8201 return TT ; Skip everything else.
8202 }
8203
8204 If (not (Item.IsMap or Item.IsCurrency or Item.IsDivinationCard))
8205 {
8206 TT := TT . "`n"
8207 TT := TT . ItemLevelWord . " " . StrPad(Item.Level, 3, Side="left")
8208
8209 If (Item.IsGem) {
8210 TT := TT . "`n"
8211 TT := TT . ItemXPWord . " " . StrPad(Item.Experience "%", 3, Side="left")
8212 }
8213
8214 If Item.IsTalisman {
8215 TT := TT . "`nTalisman Tier: " . StrPad(Item.TalismanTier, 2, Side="left")
8216 }
8217 If (Not Item.IsFlask)
8218 {
8219 ;;Item.BaseLevel := CheckBaseLevel(Item.BaseName)
8220
8221 ;;Hixxie: fixed! Shows base level for any item rarity, rings/jewelry, etc
8222 If (Item.RarityLevel < 3)
8223 {
8224 Item.BaseLevel := CheckBaseLevel(Item.Name)
8225 }
8226 Else If (Item.IsUnidentified)
8227 {
8228 Item.BaseLevel := CheckBaseLevel(Item.Name)
8229 }
8230 Else
8231 {
8232 Item.BaseLevel := CheckBaseLevel(Item.BaseName)
8233 }
8234
8235 If (Item.BaseLevel)
8236 {
8237 TT := TT . " Base Level: " . StrPad(Item.BaseLevel, 3, Side="left")
8238 }
8239 }
8240 }
8241
8242 If (Item.IsWeapon or Item.IsArmour)
8243 {
8244 If (Item.Level >= 50){
8245 IlvlSocket := 6
8246 }
8247 Else If (Item.Level >= 35){
8248 IlvlSocket := 5
8249 }
8250 Else If (Item.Level >= 25){
8251 IlvlSocket := 4
8252 }
8253 Else If (Item.Level >= 2){
8254 IlvlSocket := 3
8255 }
8256 Else{
8257 IlvlSocket := 2
8258 }
8259
8260 If (Item.IsFourSocket){
8261 Item.MaxSockets := 4
8262 }
8263 Else If (Item.IsThreeSocket){
8264 Item.MaxSockets := 3
8265 }
8266 Else If (Item.IsSingleSocket){
8267 Item.MaxSockets := 1
8268 }
8269 Else{
8270 Item.MaxSockets := 6
8271 }
8272
8273
8274 If (Not Item.IsRing or Item.IsUnsetRing)
8275 {
8276 TT := TT . "`n"
8277 TT := TT . "Max Sockets: "
8278
8279 If (IlvlSocket < Item.MaxSockets)
8280 {
8281 Item.MaxSockets := IlvlSocket
8282 TT := TT . Item.MaxSockets . " (ilvl impacts max!)"
8283 }
8284 Else
8285 {
8286 TT := TT . Item.MaxSockets
8287 }
8288 }
8289
8290 If (Item.SocketString) {
8291 TT := TT . "`n"
8292 TT := TT . "Sockets: " . Item.SocketString
8293 }
8294 }
8295
8296 If (Item.IsWeapon)
8297 {
8298 TT := TT . AssembleDamageDetails(ItemDataText)
8299 }
8300
8301 If (Item.IsDivinationCard)
8302 {
8303 If (divinationCardList[Item.Name] != "")
8304 {
8305 CardDescription := divinationCardList[Item.Name]
8306 }
8307 Else
8308 {
8309 CardDescription := divinationCardList["Unknown Card"]
8310 }
8311
8312 TT := TT . "`n--------`n" . CardDescription
8313 }
8314
8315 If (Item.IsProphecy)
8316 {
8317 /*
8318 Restriction := StrLen(Item.DifficultyRestriction) > 0 ? Item.DifficultyRestriction : "None"
8319 TT := TT . "`n--------`nDifficulty Restriction: " Restriction
8320 */
8321 TT .= AssembleProphecyDetails(Item.Name)
8322 }
8323
8324 If (Item.IsMap)
8325 {
8326 Item.MapTier := ParseMapTier(ItemDataText)
8327 Item.MapLevel := Item.MapTier + 67
8328
8329 MapDescription := " (Tier: " Item.MapTier ", Level: " Item.MapLevel ")`n`n"
8330
8331 If (Item.IsUnique)
8332 {
8333 MapDescription .= uniqueMapList[uniqueMapNameFromBase[Item.SubType]]
8334 }
8335 Else
8336 {
8337 If (RegExMatch(Item.SubType, "Shaped (.+ Map)", match))
8338 {
8339 MapDescription .= "Infos from non-shaped version:`n" mapList[match1]
8340 }
8341 Else
8342 {
8343 MapDescription .= mapList[Item.SubType]
8344 }
8345 }
8346 If (MapDescription)
8347 {
8348 TT .= MapDescription
8349 }
8350
8351 If (RarityLevel > 1 and RarityLevel < 4 and Not Item.IsUnidentified)
8352 {
8353 AffixDetails := AssembleMapAffixes()
8354 MapAffixCount := AffixTotals.NumPrefixes + AffixTotals.NumSuffixes
8355 TT = %TT%`n`nMods (%MapAffixCount%):%AffixDetails%
8356
8357 If (MapModWarnings)
8358 {
8359 TT = %TT%`n`nMod warnings:%MapModWarnings%
8360 }
8361 Else
8362 {
8363 TT = %TT%`n`nMod warnings:`nnone
8364 }
8365 }
8366 }
8367
8368 If (Item.IsGem)
8369 {
8370 If (gemQualityList[Item.Name] != "")
8371 {
8372 GemQualityDescription := gemQualityList[Item.Name]
8373 }
8374 Else
8375 {
8376 GemQualityDescription := gemQualityList["Unknown Gem"]
8377 }
8378
8379 TT := TT . "`nQuality 20%:`n" . GemQualityDescription
8380 }
8381
8382 If (Item.IsBeast)
8383 {
8384 return TT
8385 }
8386
8387 If (RarityLevel > 1 and RarityLevel < 4)
8388 {
8389 ; Append affix info if rarity is greater than normal (white)
8390 ; Affix total statistic
8391 If (Itemdata.Rarity = "Magic"){
8392 PrefixLimit := 1
8393 SuffixLimit := 1
8394 } Else {
8395 PrefixLimit := 3
8396 SuffixLimit := 3
8397 }
8398
8399 WordPrefixes := NumPrefixesMax > PrefixLimit ? "?! " : " " ; Turns "2-4 Prefixes" into "2-4?! Prefixes"
8400 WordSuffixes := NumSuffixesMax > SuffixLimit ? "?! " : " "
8401
8402 WordPrefixes .= NumPrefixesMax = 1 ? "Prefix" : "Prefixes"
8403 WordSuffixes .= NumSuffixesMax = 1 ? "Suffix" : "Suffixes"
8404
8405 If (NumPrefixesMax = 0){
8406 PrefixText := ""
8407 PreSufComma := "" ; If there are no prefixes, we also don't want the comma.
8408 }
8409 Else If (NumPrefixes = NumPrefixesMax){
8410 PrefixText := NumPrefixes . WordPrefixes
8411 PreSufComma := ", "
8412 }
8413 Else{
8414 PrefixText := NumPrefixes "-" NumPrefixesMax . WordPrefixes
8415 PreSufComma := ", "
8416 }
8417
8418 If (NumSuffixesMax = 0){
8419 SuffixText := ""
8420 }
8421 Else If (NumSuffixes = NumSuffixesMax){
8422 SuffixText := PreSufComma . NumSuffixes . WordSuffixes
8423 }
8424 Else{
8425 SuffixText := PreSufComma . NumSuffixes "-" NumSuffixesMax . WordSuffixes
8426 }
8427
8428 TotalsText := NumTotalAffixes = NumTotalAffixesMax ? NumTotalAffixes : NumTotalAffixes "-" NumTotalAffixesMax
8429
8430 If (NumTotalAffixes > 0 and Not Item.IsUnidentified and Not Item.IsMap)
8431 {
8432 TT = %TT%`n--------`nAffixes (%TotalsText%): %PrefixText%%SuffixText%
8433 }
8434 }
8435
8436 If (Item.hasImplicit and not Item.IsUnique) {
8437 ImplicitTooltip := ""
8438 ImplicitValueArray := LookupImplicitValue(Item.BaseName)
8439
8440 maxIndex := Item.Implicit.MaxIndex()
8441 TextLineWidth := ImplicitValueArray.MaxIndex() and StrLen(ImplicitValueArray[1]) ? 20 : 50
8442 Ellipsis := Opts.AffixTextEllipsis
8443
8444 Loop, % maxIndex {
8445 ImplicitText := Item.Implicit[A_Index]
8446 If (StrLen(ImplicitText) > TextLineWidth)
8447 {
8448 ImplicitText := SubStr(ImplicitText, 1, TextLineWidth - StrLen(Ellipsis)) Ellipsis
8449 }
8450 Else
8451 {
8452 ImplicitText := StrPad(ImplicitText, TextLineWidth)
8453 }
8454 ImplicitTooltip .= "`n" ImplicitText " " StrPad(ImplicitValueArray[A_Index], 5, "left")
8455 }
8456 TT = %TT%`n--------%ImplicitTooltip%
8457 }
8458
8459 If (RarityLevel > 1 and RarityLevel < 4)
8460 {
8461 If (Not (Item.IsUnidentified or Item.IsMap))
8462 {
8463 AffixDetails := AssembleAffixDetails()
8464
8465 TT = %TT%`n--------%AffixDetails%
8466 }
8467 }
8468
8469 Else If (ItemData.Rarity == "Unique")
8470 {
8471 If (FindUnique(Item.Name) == False and Not Item.IsUnidentified)
8472 {
8473 TT = %TT%`n--------`nUnique item currently not supported
8474 }
8475 Else If (Not Item.IsUnidentified)
8476 {
8477 ParseUnique(Item.Name)
8478 AffixDetails := AssembleAffixDetails()
8479 TT = %TT%`n--------%AffixDetails%
8480 }
8481 }
8482
8483 If (pseudoMods.Length())
8484 {
8485 TT = %TT%`n--------
8486 For key, val in pseudoMods
8487 {
8488 pseudoMod := "(pseudo) " val.name_orig
8489 TT = %TT%`n%pseudoMod%
8490 }
8491 }
8492
8493 If (Item.IsUnidentified and (Item.Name != "Scroll of Wisdom") and Not Item.IsMap)
8494 {
8495 TT = %TT%`n--------`nUnidentified
8496 }
8497
8498 If (UniqueHasFatedVariant(Item.Name))
8499 {
8500 TT = %TT%`n--------`nHas Fated Variant
8501 }
8502
8503 If (Item.IsMirrored)
8504 {
8505 TT = %TT%`n--------`nMirrored
8506 }
8507
8508 If (Opts.ShowExplanationForUsedNotation)
8509 {
8510 Notation := ""
8511
8512 If (RegExMatch(AffixDetails, "(HybP|HybS)")){
8513 Notation .= "`n Hyb: Hybrid. One mod with two parts in two lines."
8514 }
8515 If (RegExMatch(AffixDetails, "HDP")){
8516 Notation .= "`n HDP: Hybrid Defence Prefix. Flat Def on Hybrid Base Armour."
8517 }
8518 If (RegExMatch(AffixDetails, "(CrP|CrS)")){
8519 Notation .= "`n Cr: Craft. Master tiers not in yet, treated as normal mod."
8520 }
8521 matchpattern := "\d\" Opts.DoubleRangeSeparator "\d"
8522 If (RegExMatch(AffixDetails, matchpattern)){
8523 Notation .= "`n a-b" Opts.DoubleRangeSeparator "c-d: For added damage mods. (a-b) to (c-d)"
8524 }
8525 matchpattern := "\d\" Opts.MultiTierRangeSeparator "\d"
8526 If (RegExMatch(AffixDetails, matchpattern)){
8527 Notation .= "`n a-b" Opts.MultiTierRangeSeparator "c-d: Multi tier uncertainty. WorstCaseRange" Opts.MultiTierRangeSeparator "BestCaseRange"
8528 }
8529
8530 If (Itemdata.SpecialCaseNotation != "")
8531 {
8532 Notation .= "`n" Itemdata.SpecialCaseNotation
8533 }
8534
8535 If (Notation)
8536 {
8537 TT .= "`n--------`nNotation:" Notation
8538 }
8539 }
8540
8541 return TT
8542}
8543
8544GetNegativeAffixOffset(Item)
8545{
8546 ; Certain item types have descriptive text lines at the end,
8547 ; so decrement item index to get to the affix lines.
8548 NegativeAffixOffset := 0
8549 If (Item.IsFlask)
8550 {
8551 NegativeAffixOffset += 1
8552 }
8553 If (Item.IsUnique)
8554 {
8555 NegativeAffixOffset += 1
8556 }
8557 If (Item.IsTalisman)
8558 {
8559 NegativeAffixOffset += 1
8560 }
8561 If (Item.IsMap)
8562 {
8563 NegativeAffixOffset += 1
8564 }
8565 If (Item.IsJewel)
8566 {
8567 NegativeAffixOffset += 1
8568 }
8569 If (Item.HasEffect)
8570 {
8571 NegativeAffixOffset += 1
8572 }
8573 If (Item.IsCorrupted)
8574 {
8575 NegativeAffixOffset += 1
8576 }
8577 If (Item.IsElderBase or Item.IsShaperBase or Item.IsSynthesisedBase or Item.IsFracturedBase)
8578 {
8579 NegativeAffixOffset += 1
8580 }
8581 If (Item.IsMirrored)
8582 {
8583 NegativeAffixOffset += 1
8584 }
8585 If (RegExMatch(Item.Name, "i)Tabula Rasa")) ; no mods, no flavour text
8586 {
8587 NegativeAffixOffset -= 2
8588 }
8589 return NegativeAffixOffset
8590}
8591
8592; ### TODO: Fix issue for white items, currently receiving duplicate infos (affixes and implicit containing the same line)
8593; Prepare item affixes to create pseudo mods, taken from PoE-TradeMacro
8594; Moved from TradeMacro to ItemInfo to avoid duplicate code, please be careful with any changes
8595PreparePseudoModCreation(Affixes, Implicit, Rarity, isMap = false) {
8596 ; ### TODO: remove blank lines ( rare cases, maybe from crafted mods )
8597
8598 mods := []
8599 ; ### Append Implicits if any
8600 modStrings := Implicit
8601 For i, modString in modStrings {
8602 tempMods := ModStringToObject(modString, true)
8603 For i, tempMod in tempMods {
8604 If (tempMod.name) {
8605 mods.push(tempMod)
8606 }
8607 }
8608 }
8609
8610 ; ### Convert affix lines to mod objects
8611 If (Rarity > 1) {
8612 modStrings := StrSplit(Affixes, "`n")
8613 For i, modString in modStrings {
8614 tempMods := ModStringToObject(modString, false)
8615 For i, tempMod in tempMods {
8616 If (tempMod.name) {
8617 mods.push(tempMod)
8618 }
8619 }
8620 }
8621 }
8622
8623 ; return only pseudoMods, this is changed from PoE-TradeMacro where all mods are returned.
8624 mods := CreatePseudoMods(mods)
8625
8626 Return mods
8627}
8628
8629; Convert mod strings to objects while separating combined mods like "+#% to Fire and Lightning Resitances"
8630; Moved from TradeMacro to ItemInfo to avoid duplicate code, please be careful with any changes
8631ModStringToObject(string, isImplicit) {
8632 StringReplace, val, string, `r,, All
8633 StringReplace, val, val, `n,, All
8634 values := []
8635
8636 RegExMatch(val, "i) \((fractured)\)$", sType)
8637 spawnType := sType1
8638
8639 val := RegExReplace(val, "i) \((fractured|crafted)\)$")
8640
8641 ; Collect all numeric values in the mod-string
8642 Pos := 0
8643 While Pos := RegExMatch(val, "i)(-?[.0-9]+)", value, Pos + (StrLen(value) ? StrLen(value) : 1)) {
8644 values.push(value)
8645 }
8646
8647 ; Collect all resists/attributes that are combined in one mod
8648 Matches := []
8649
8650 If (RegexMatch(val, "i)to (Strength|Dexterity|Intelligence) and (Strength|Dexterity|Intelligence)$", attribute)) {
8651 IF ( attribute1 AND attribute2 ) {
8652 Matches.push(attribute1)
8653 Matches.push(attribute2)
8654 }
8655 }
8656
8657 type := ""
8658 ; Matching "x% fire and cold resistance" or "x% to cold resist", excluding "to maximum cold resistance" and "damage penetrates x% cold resistance" and minion/totem related mods
8659 If (RegExMatch(val, "i)to ((cold|fire|lightning)( and (cold|fire|lightning))?) resistance") and not RegExMatch(val, "i)Minion|Totem")) {
8660 type := "Resistance"
8661 If (RegExMatch(val, "i)fire")) {
8662 Matches.push("Fire")
8663 }
8664 If (RegExMatch(val, "i)cold")) {
8665 Matches.push("Cold")
8666 }
8667 If (RegExMatch(val, "i)lightning")) {
8668 Matches.push("Lightning")
8669 }
8670 }
8671 ; Matching "x% fire/cold/lgitning and chaos resistance"
8672 If (RegExMatch(val, "i)to (cold|fire|lightning) and (chaos) resistance") and not RegExMatch(val, "i)Minion|Totem")) {
8673 type := "Resistance"
8674 If (RegExMatch(val, "i)fire")) {
8675 Matches.push("Fire")
8676 }
8677 If (RegExMatch(val, "i)cold")) {
8678 Matches.push("Cold")
8679 }
8680 If (RegExMatch(val, "i)lightning")) {
8681 Matches.push("Lightning")
8682 }
8683 If (RegExMatch(val, "i)chaos")) {
8684 Matches.push("Chaos")
8685 }
8686 }
8687
8688 ; Vanguard Belt implicit for example (flat AR + EV)
8689 If (RegExMatch(val, "i)([.0-9]+) to (Armour|Evasion Rating|Energy Shield) and (Armour|Evasion Rating|Energy Shield)")) {
8690 type := "Defence"
8691 If (RegExMatch(val, "i)Armour")) {
8692 Matches.push("Armour")
8693 }
8694 If (RegExMatch(val, "i)Evasion Rating")) {
8695 Matches.push("Evasion Rating")
8696 }
8697 If (RegExMatch(val, "i)Energy Shield")) {
8698 Matches.push("Energy Shield")
8699 }
8700 }
8701
8702 ; Create single mod from every collected resist/attribute
8703 Loop % Matches.Length() {
8704 RegExMatch(val, "i)(Resistance)", match)
8705 ; differentiate between negative and positive values; flat and increased attributes
8706 sign := "+"
8707 type := RegExMatch(val, "i)increased", inc) ? "% increased " : " to "
8708 If (inc) {
8709 sign := ""
8710 }
8711 If (RegExMatch(val, "i)^-")) {
8712 sign := "-"
8713 }
8714 Matches[A_Index] := match1 ? sign . "#% to " . Matches[A_Index] . " " . match1 : sign . "#" . type . "" . Matches[A_Index]
8715 }
8716
8717 If (RegExMatch(val, "i)to all attributes|to all elemental (Resistances)", match) and not RegExMatch(val, "i)Minion|Totem")) {
8718 resist := match1 ? true : false
8719 Matches[1] := resist ? "+#% to Fire Resistance" : "+# to Strength"
8720 Matches[2] := resist ? "+#% to Lightning Resistance" : "+# to Intelligence"
8721 Matches[3] := resist ? "+#% to Cold Resistance" : "+# to Dexterity"
8722 }
8723
8724 ; Use original mod-string if no combination is found
8725 Matches[1] := Matches.Length() > 0 ? Matches[1] : val
8726
8727 ;
8728 arr := []
8729 Loop % (Matches.Length() ? Matches.Length() : 1) {
8730 temp := {}
8731 temp.name_orig := Matches[A_Index]
8732 Loop {
8733 temp.name_orig := RegExReplace(temp.name_orig, "-?#", values[A_Index], Count, 1)
8734 If (!Count) {
8735 break
8736 }
8737 }
8738
8739 temp.values := values
8740 ; mods with negative values inputted in the value fields are not supported on poe.trade, so searching for "-1 maximum charges/frenzy charges" is not possible
8741 ; unless there is a mod "-# maximum charges"
8742
8743 ; some values shouldn't be replaced because they are fixed, for example "#% chance to gain Onslaught for 4 seconds on Kill"
8744 ; regex, | delimited
8745 exceptionsList := "recovered every 3 seconds|inflicted with this Weapon to deal 100% more Damage|with 30% reduced Movement Speed|chance to Recover 10% of Maximum Mana|"
8746 exceptionsList .= "for 3 seconds|for 4 seconds|for 8 seconds|for 10 seconds|over 4 seconds|"
8747 exceptionsList .= "per (10|12|15|16|50) (Strength|Dexterity|Intelligence)|"
8748 exceptionsList .= "per 200 Accuracy Rating|if you have at least 500 Strength|per 1% Chance to Block Attack Damage|are at least 5 nearby Enemies|a total of 200 Mana"
8749
8750 RegExMatch(Matches[A_Index], "i)(" exceptionsList ")", exception)
8751
8752 s := RegExReplace(Matches[A_Index], "i)(-?)[.0-9]+", "$1#")
8753
8754 ; restore certain mod line parts if there are exceptions
8755 If (exception) {
8756 replacer_reg := RegExReplace(exception, "i)(-?)[.0-9]+", "(#)")
8757 s := RegExReplace(s, "i)" replacer_reg "", exception)
8758 }
8759
8760 temp.name := RegExReplace(s, "i)# ?to ? #", "#", isRange)
8761 temp.isVariable:= false
8762 If (StrLen(spawnType)) {
8763 temp.spawnType := spawnType
8764 }
8765 temp.type := (isImplicit and Matches.Length() <= 1) ? "implicit" : "explicit"
8766 arr.push(temp)
8767 }
8768
8769 Return arr
8770}
8771
8772; Moved from TradeMacro to ItemInfo to avoid duplicate code, please be careful with any changes
8773CreatePseudoMods(mods, returnAllMods := False) {
8774 tempMods := []
8775 lifeFlat := 0
8776 manaFlat := 0
8777 energyShieldFlat := 0
8778 energyShieldPercent := 0
8779 energyShieldPercentGlobal := 0
8780 evasionRatingPercentGlobal := 0
8781
8782 rarityItemsFoundPercent := 0
8783
8784 accuracyRatingFlat := 0
8785 globalCritChancePercent := 0
8786 globalCritMultiplierPercent := 0
8787 critChanceForSpellsPercent := 0
8788
8789 spellDmg_Percent := 0
8790 attackDmg_Percent := 0
8791
8792 ; Attributes
8793 strengthFlat := 0
8794 dexterityFlat := 0
8795 intelligenceFlat := 0
8796 allAttributesFlat := 0
8797 strengthPercent := 0
8798 dexterityPercent := 0
8799 intelligencePercent := 0
8800 allAttributesPercent := 0
8801
8802 ; Resistances
8803 coldResist := 0
8804 fireResist := 0
8805 lightningResist := 0
8806 chaosResist := 0
8807 toAllElementalResist := 0
8808
8809 ; Damages
8810 meleePhysDmgGlobal_Percent := 0
8811
8812 dmgTypes := ["elemental", "fire", "cold", "lightning"]
8813 For key, type in dmgTypes {
8814 %type%Dmg_Percent := 0
8815 %type%Dmg_AttacksPercent := 0
8816 %type%Dmg_SpellsPercent := 0
8817 %type%Dmg_AttacksFlatLow := 0
8818 %type%Dmg_AttacksFlatHi := 0
8819 %type%Dmg_SpellsFlatLow := 0
8820 %type%Dmg_SpellsFlatHi := 0
8821 %type%Dmg_FlatLow := 0
8822 %type%Dmg_FlatHi := 0
8823 }
8824
8825 /* BREAKPOINT
8826 ; ########################################################################
8827 ; ### Combine values from mods of same types
8828 ; ### - also assign simplifiedName to the found mod for easier comparison later without duplicating precious regex
8829 ; ########################################################################
8830 */
8831
8832 ; Note that at this point combined mods/attributes have already been separated into two mods
8833 ; like '+ x % to fire and lightning resist' would be '+ x % to fire resist' AND '+ x % to lightning resist' as 2 different mods
8834 For key, mod in mods {
8835 RegExMatch(mod.name, "i) \((fractured)\)$", spawnType)
8836 If (StrLen(spawnType1)) {
8837 mod.spawnType := spawnType1
8838 }
8839
8840 mod.name := RegExReplace(mod.name, "i) \((fractured|crafted)\)$")
8841
8842 ; ### Base stats
8843 ; life and mana
8844 If (RegExMatch(mod.name, "i)to maximum (Life|Mana)$", stat)) {
8845 %stat1%Flat := %stat1%Flat + mod.values[1]
8846 mod.simplifiedName := "xToMaximum" stat1
8847 }
8848 ; flat energy shield
8849 Else If (RegExMatch(mod.name, "i)to maximum Energy Shield$")) {
8850 energyShieldFlat := energyShieldFlat + mod.values[1]
8851 mod.simplifiedName := "xToMaximumEnergyShield"
8852 }
8853 ; percent energy shield
8854 Else If (RegExMatch(mod.name, "i)increased maximum Energy Shield$")) {
8855 energyShieldPercent := energyShieldPercent + mod.values[1]
8856 mod.simplifiedName := "xIncreasedMaximumEnergyShield"
8857 }
8858
8859 ; ### Items found
8860 ; rarity
8861 Else If (RegExMatch(mod.name, "i)increased Rarity of items found$")) {
8862 rarityItemsFoundPercent := rarityItemsFoundPercent + mod.values[1]
8863 mod.simplifiedName := "xIncreasedRarityOfItemsFound"
8864 }
8865
8866 ; ### crits
8867 Else If (RegExMatch(mod.name, "i)increased Global Critical Strike Chance$")) {
8868 globalCritChancePercent := globalCritChancePercent + mod.values[1]
8869 mod.simplifiedName := "xIncreasedGlobalCriticalChance"
8870 }
8871 Else If (RegExMatch(mod.name, "i)to Global Critical Strike Multiplier$")) {
8872 globalCritMultiplierPercent := globalCritMultiplierPercent + mod.values[1]
8873 mod.simplifiedName := "xIncreasedGlobalCriticalMultiplier"
8874 }
8875 Else If (RegExMatch(mod.name, "i)increased Critical Strike Chance for Spells$")) {
8876 critChanceForSpellsPercent := critChanceForSpellsPercent + mod.values[1]
8877 mod.simplifiedName := "xIncreasedCriticalSpells"
8878 }
8879
8880 ; ### Attributes
8881 ; all flat attributes
8882 Else If (RegExMatch(mod.name, "i)to All Attributes$")) {
8883 allAttributesFlat := allAttributesFlat + mod.values[1]
8884 mod.simplifiedName := "xToAllAttributes"
8885 }
8886 ; single flat attributes
8887 Else If (RegExMatch(mod.name, "i)to (Intelligence|Dexterity|Strength)$", attribute)) {
8888 %attribute1%Flat := %attribute1%Flat + mod.values[1]
8889 mod.simplifiedName := "xTo" . attribute1
8890 }
8891 ; % increased attributes
8892 Else If (RegExMatch(mod.name, "i)increased (Intelligence|Dexterity|Strength)$", attribute)) {
8893 %attribute1%Percent := %attribute1%Percent + mod.values[1]
8894 mod.simplifiedName := "xIncreased" . attribute1 . "Percentage"
8895 }
8896
8897 ; ### Resistances
8898 ; % to all resistances ( careful about 'max all resistances' )
8899 Else If (RegExMatch(mod.name, "i)to all Elemental Resistances$") and not RegExMatch(mod.name, "i)Minion|Totem")) {
8900 toAllElementalResist := toAllElementalResist + mod.values[1]
8901 mod.simplifiedName := "xToAllElementalResistances"
8902 }
8903 ; % to base resistances
8904 Else If (RegExMatch(mod.name, "i)to (Cold|Fire|Lightning|Chaos) Resistance$", resistType) and not RegExMatch(mod.name, "i)Minion|Totem")) {
8905 %resistType1%Resist := %resistType1%Resist + mod.values[1]
8906 mod.simplifiedName := "xTo" resistType1 "Resistance"
8907 }
8908 Else If (RegExMatch(mod.name, "i)to (Cold|Fire|Lightning) and (Chaos) Resistances$") and not RegExMatch(mod.name, "i)Minion|Totem")) {
8909 %resistType1%Resist := %resistType1%Resist + mod.values[1]
8910 mod.simplifiedName := "xTo" resistType1 "Resistance"
8911
8912 chaosResist := chaosResist + mod.values[1]
8913 mod.simplifiedName := "xToChaosResistance"
8914 }
8915
8916 ; ### Percent damages
8917 ; % increased elemental damage
8918 Else If (RegExMatch(mod.name, "i)increased (Cold|Fire|Lightning|Elemental) damage$", element)) {
8919 %element1%Dmg_Percent := %element1%Dmg_Percent + mod.values[1]
8920 mod.simplifiedName := "xIncreased" element1 "Damage"
8921 }
8922 ; % elemental damage with weapons
8923 Else If (RegExMatch(mod.name, "i)(Cold|Fire|Lightning|Elemental) damage with attack skills", element)) {
8924 %element1%Dmg_AttacksPercent := %element1%Dmg_AttacksPercent + mod.values[1]
8925 mod.simplifiedName := "xIncreased" element1 "DamageAttacks"
8926 }
8927
8928 ; ### Flat Damages
8929 ; flat 'element' damage; source: weapons
8930 Else If (RegExMatch(mod.name, "i)adds .* (Cold|Fire|Lightning|Elemental) damage$", element)) {
8931 element := element1
8932 %element%Dmg_FlatLow := %element%Dmg_FlatLow + mod.values[1]
8933 %element%Dmg_FlatHi := %element%Dmg_FlatHi + mod.values[2]
8934 mod.simplifiedName := "xFlat" element "Damage"
8935 }
8936 ; flat 'element' damage; source: various (wands/rings/amulets etc)
8937 Else If (RegExMatch(mod.name, "i)adds .* (Cold|Fire|Lightning|Elemental) damage to (Attacks|Spells)$", element)) {
8938 %element1%Dmg_%element2%FlatLow := %element1%Dmg_%element2%FlatLow + mod.values[1]
8939 %element1%Dmg_%element2%FlatHi := %element1%Dmg_%element2%FlatHi + mod.values[2]
8940 ElementalDmg_%element2%FlatLow += %element1%Dmg_%element2%FlatLow
8941 ElementalDmg_%element2%FlatHi += %element1%Dmg_%element2%FlatHi
8942 mod.simplifiedName := "xFlat" element1 "Damage" element2
8943 }
8944 ; this would catch any * Spell * Damage * ( we might need to be more precise here )
8945 Else If (RegExMatch(mod.name, "i)spell") and RegExMatch(mod.name, "i)damage") and not RegExMatch(mod.name, "i)chance|multiplier")) {
8946 spellDmg_Percent := spellDmg_Percent + mod.values[1]
8947 mod.simplifiedName := "xIncreasedSpellDamage"
8948 }
8949
8950 ; ### remaining mods that can be derived from attributes (str|dex|int)
8951 ; flat accuracy rating
8952 Else If (RegExMatch(mod.name, "i)to accuracy rating$")) {
8953 accuracyRatingFlat := accuracyRatingFlat + mod.values[1]
8954 }
8955 }
8956
8957 /* BREAKPOINT
8958 ; ########################################################################
8959 ; ### Spread global values to their sub element
8960 ; ### - like % all Elemental to the base elementals
8961 ; ########################################################################
8962 */
8963
8964 ; ### Attributes
8965 ; flat attributes
8966 If (allAttributesFlat) {
8967 strengthFlat := strengthFlat + allAttributesFlat
8968 dexterityFlat := dexterityFlat + allAttributesFlat
8969 intelligenceFlat := intelligenceFlat + allAttributesFlat
8970 }
8971
8972 ; spread attributes to their corresponding stats they give
8973 If (strengthFlat) {
8974 lifeFlat := lifeFlat + Floor(strengthFlat/2)
8975 meleePhysDmgGlobal_Percent := meleePhysDmgGlobal_Percent + Floor(strengthFlat/5)
8976 }
8977 If (intelligenceFlat) {
8978 manaFlat := manaFlat + Floor(intelligenceFlat/2)
8979 energyShieldPercentGlobal := Floor(intelligenceFlat/5)
8980 }
8981 If (dexterityFlat) {
8982 accuracyRatingFlat := accuracyRatingFlat + Floor(dexterityFlat*2)
8983 evasionRatingPercentGlobal := Floor(dexterityFlat/5)
8984 }
8985
8986 ; ### Elemental Damage - % increased
8987 fireDmg_Percent := fireDmg_Percent + elementalDmg_Percent
8988 coldDmg_Percent := coldDmg_Percent + elementalDmg_Percent
8989 lightningDmg_Percent:= lightningDmg_Percent + elementalDmg_Percent
8990
8991 ; ### Elemental damage - attack skills % increased
8992 ; ### - spreads Elemental damage with attack skills to each 'element' damage with attack skills and adds related % increased 'element' damage
8993 fireDmg_AttacksPercent := fireDmg_AttacksPercent + elementalDmg_AttacksPercent + fireDmg_Percent
8994 coldDmg_AttacksPercent := coldDmg_AttacksPercent + elementalDmg_AttacksPercent + coldDmg_Percent
8995 lightningDmg_AttacksPercent := lightningDmg_AttacksPercent + elementalDmg_AttacksPercent + lightningDmg_Percent
8996
8997 ; ### Elemental damage - Spells % increased
8998 ; ### - spreads % spell damage to each % 'element' spell damage and adds related % increased 'element' damage
8999 fireDmg_SpellsPercent := fireDmg_SpellsPercent + spellDmg_Percent + fireDmg_Percent
9000 coldDmg_SpellsPercent := coldDmg_SpellsPercent + spellDmg_Percent + coldDmg_Percent
9001 lightningDmg_SpellsPercent := lightningDmg_SpellsPercent + spellDmg_Percent + lightningDmg_Percent
9002
9003 ; ### Elemental Resistances
9004 ; ### - spreads % to all Elemental Resistances to the base resist
9005 ; ### - also calculates the totalElementalResistance and totalResistance
9006 totalElementalResistance := 0
9007 For i, element in ["Fire", "Cold", "Lightning"] {
9008 %element%Resist := %element%Resist + toAllElementalResist
9009 totalElementalResistance := totalElementalResistance + %element%Resist
9010 }
9011 totalResistance := totalElementalResistance + chaosResist
9012
9013 /* BREAKPOINT
9014 ; ########################################################################
9015 ; ### Generate ALL the pseudo mods from the non 0 values combined above
9016 ; ### - just remember the spreading logic above when assigning the temp mods inherited values references in possibleParentSimplifiedNames
9017 ; ########################################################################
9018 */
9019
9020 ; ### Generate Basic Stats pseudos
9021 If (lifeFlat > 0) {
9022 temp := {}
9023 temp.values := [lifeFlat]
9024 temp.name_orig := "+" . lifeFlat . " to maximum Life"
9025 temp.name := "+# to maximum Life"
9026 temp.simplifiedName := "xToMaximumLife"
9027 temp.exception := true
9028 temp.possibleParentSimplifiedNames := ["xToMaximumLife"]
9029 tempMods.push(temp)
9030 }
9031 If (manaFlat > 0) {
9032 temp := {}
9033 temp.values := [manaFlat]
9034 temp.name_orig := "+" . manaFlat . " to maximum Mana"
9035 temp.name := "+# to maximum Mana"
9036 temp.simplifiedName := "xToMaximumMana"
9037 temp.possibleParentSimplifiedNames := ["xToMaximumMana"]
9038 tempMods.push(temp)
9039 }
9040 If (energyShieldFlat > 0) {
9041 temp := {}
9042 temp.values := [energyShieldFlat]
9043 temp.name_orig := "+" . energyShieldFlat . " to maximum Energy Shield"
9044 temp.name := "+# to maximum Energy Shield"
9045 temp.simplifiedName := "xToMaximumEnergyShield"
9046 temp.possibleParentSimplifiedNames := ["xToMaximumEnergyShield"]
9047 tempMods.push(temp)
9048 }
9049 If (energyShieldPercent > 0) {
9050 temp := {}
9051 temp.values := [energyShieldPercent]
9052 temp.name_orig := energyShieldPercent . "% increased maximum Energy Shield"
9053 temp.name := "#% increased maximum Energy Shield"
9054 temp.simplifiedName := "xIncreasedMaximumEnergyShield"
9055 temp.possibleParentSimplifiedNames := ["xIncreasedMaximumEnergyShield"]
9056 tempMods.push(temp)
9057 }
9058 ; ### Generate rarity item found pseudo
9059 If (rarityItemsFoundPercent > 0) {
9060 temp := {}
9061 temp.values := [rarityItemsFoundPercent]
9062 temp.name_orig := rarityItemsFoundPercent . "% increased Rarity of items found"
9063 temp.name := "#% increased Rarity of items found"
9064 temp.simplifiedName := "xIncreasedRarityOfItemsFound"
9065 temp.possibleParentSimplifiedNames := ["xIncreasedRarityOfItemsFound"]
9066 tempMods.push(temp)
9067 }
9068 ; ### Generate crit pseudos
9069 If (globalCritChancePercent > 0) {
9070 temp := {}
9071 temp.values := [globalCritChancePercent]
9072 temp.name_orig := globalCritChancePercent . "% increased Global Critical Strike Chance"
9073 temp.name := "#% increased Global Critical Strike Chance"
9074 temp.simplifiedName := "xIncreasedGlobalCriticalChance"
9075 temp.possibleParentSimplifiedNames := ["xIncreasedGlobalCriticalChance"]
9076 tempMods.push(temp)
9077 }
9078 If (globalCritMultiplierPercent > 0) {
9079 temp := {}
9080 temp.values := [globalCritMultiplierPercent]
9081 temp.name_orig := "+" . globalCritMultiplierPercent . "% to Global Critical Strike Multiplier"
9082 temp.name := "+#% to Global Critical Strike Multiplier"
9083 temp.simplifiedName := "xIncreasedGlobalCriticalMultiplier"
9084 temp.possibleParentSimplifiedNames := ["xIncreasedGlobalCriticalMultiplier"]
9085 tempMods.push(temp)
9086 }
9087 If (critChanceForSpellsPercent > 0) {
9088 temp := {}
9089 temp.values := [critChanceForSpellsPercent]
9090 temp.name_orig := critChanceForSpellsPercent . "% increased Critical Strike Chance for Spells"
9091 temp.name := "#% increased Critical Strike Chance for Spells"
9092 temp.simplifiedName := "xIncreasedCriticalSpells"
9093 temp.possibleParentSimplifiedNames := ["xIncreasedCriticalSpells"]
9094 tempMods.push(temp)
9095 }
9096 ; ### Generate Attributes pseudos
9097 For i, attribute in ["Strength", "Dexterity", "Intelligence"] {
9098 If ( %attribute%Flat > 0 ) {
9099 temp := {}
9100 temp.values := [%attribute%Flat]
9101 temp.name_orig := "+" . %attribute%Flat . " to " . attribute
9102 temp.name := "+# to " . attribute
9103 temp.simplifiedName := "xTo" attribute
9104 temp.possibleParentSimplifiedNames := ["xTo" attribute, "xToAllAttributes"]
9105 tempMods.push(temp)
9106 }
9107 }
9108 ; cumulative all attributes mods
9109 If (allAttributesFlat > 0) {
9110 temp := {}
9111 temp.values := [allAttributesFlat]
9112 temp.name_orig := "+" . allAttributesFlat . " to all Attributes"
9113 temp.name := "+#% to all Attributes"
9114 temp.simplifiedName := "xToAllAttributes"
9115 temp.possibleParentSimplifiedNames := ["xToAllAttributes"]
9116 tempMods.push(temp)
9117 }
9118
9119 ; ### Generate Resists pseudos
9120 For i, element in ["Fire", "Cold", "Lightning"] {
9121 If ( %element%Resist > 0) {
9122 temp := {}
9123 temp.values := [%element%Resist]
9124 temp.name_orig := "+" %element%Resist "% to " element " Resistance"
9125 temp.name := "+#% to " element " Resistance"
9126 temp.simplifiedName := "xTo" element "Resistance"
9127 temp.possibleParentSimplifiedNames := ["xTo" element "Resistance", "xToAllElementalResistances"]
9128 temp.hideForTradeMacro := true
9129 tempMods.push(temp)
9130 }
9131 }
9132 If (toAllElementalResist > 0) {
9133 temp := {}
9134 temp.values := [toAllElementalResist]
9135 temp.name_orig := "+" . toAllElementalResist . "% to all Elemental Resistances"
9136 temp.name := "+#% to all Elemental Resistances"
9137 temp.simplifiedName := "xToAllElementalResistances"
9138 temp.exception := true
9139 temp.possibleParentSimplifiedNames := ["xToAllElementalResistances"]
9140 temp.hideForTradeMacro := true
9141 tempMods.push(temp)
9142 }
9143 ; Note that total resistances are calculated values with no possible child mods, so they have no simplifiedName
9144 If (totalElementalResistance > 0) {
9145 temp := {}
9146 temp.values := [totalElementalResistance]
9147 temp.name_orig := "+" . totalElementalResistance . "% total Elemental Resistance"
9148 temp.name := "+#% total Elemental Resistance"
9149 temp.exception := true
9150 temp.possibleParentSimplifiedNames := ["xToFireResistance", "xToColdResistance", "xToLightningResistance", "xToAllElementalResistances"]
9151 tempMods.push(temp)
9152 }
9153 ; without chaos resist, this would have the same value as totalElementalResistance
9154 If ((totalResistance > 0) AND (chaosResist > 0)) {
9155 temp := {}
9156 temp.values := [totalResistance]
9157 temp.name_orig := "+" . totalResistance . "% total Resistance"
9158 temp.name := "+#% total Resistance"
9159 temp.exception := true
9160 temp.possibleParentSimplifiedNames := ["xToFireResistance", "xToColdResistance", "xToLightningResistance", "xToAllElementalResistances", "xToChaosResistance"]
9161 tempMods.push(temp)
9162 }
9163
9164 ; ### Generate remaining pseudos derived from attributes
9165 If (meleePhysDmgGlobal_Percent > 0) {
9166 temp := {}
9167 temp.values := [meleePhysDmgGlobal_Percent]
9168 temp.name_orig := meleePhysDmgGlobal_Percent . "% increased Melee Physical Damage"
9169 temp.name := "#% increased Melee Physical Damage"
9170 temp.simplifiedName := "xIncreasedMeleePhysicalDamage"
9171 temp.possibleParentSimplifiedNames := ["xIncreasedMeleePhysicalDamage"]
9172 temp.hideForTradeMacro := true
9173 tempMods.push(temp)
9174 }
9175 If (energyShieldPercentGlobal > 0) {
9176 temp := {}
9177 temp.values := [energyShieldPercentGlobal]
9178 temp.name_orig := energyShieldPercentGlobal . "% increased Energy Shield (Global)"
9179 temp.name := "#% increased Energy Shield (Global)"
9180 temp.simplifiedName := "xIncreasedEnergyShieldPercentGlobal"
9181 temp.possibleParentSimplifiedNames := ["xIncreasedEnergyShieldPercentGlobal"]
9182 temp.hideForTradeMacro := true
9183 tempMods.push(temp)
9184 }
9185 If (evasionRatingPercentGlobal > 0) {
9186 temp := {}
9187 temp.values := [evasionRatingPercentGlobal]
9188 temp.name_orig := evasionRatingPercentGlobal . "% increased Evasion (Global)"
9189 temp.name := "#% increased Evasion (Global)"
9190 temp.simplifiedName := "xIncreasedEvasionRatingPercentGlobal"
9191 temp.possibleParentSimplifiedNames := ["xIncreasedEvasionRatingPercentGlobal"]
9192 temp.hideForTradeMacro := true
9193 tempMods.push(temp)
9194 }
9195 If (accuracyRatingFlat > 0) {
9196 temp := {}
9197 temp.values := [accuracyRatingFlat]
9198 temp.name_orig := "+" . accuracyRatingFlat . " to Accuracy Rating"
9199 temp.name := "+# to Accuracy Rating"
9200 temp.simplifiedName := "xToAccuracyRating"
9201 temp.possibleParentSimplifiedNames := ["xToAccuracyRating"]
9202 tempMods.push(temp)
9203 }
9204
9205 ; ### Generate Damages pseudos
9206 ; spell damage global
9207 If (spellDmg_Percent > 0) {
9208 temp := {}
9209 temp.values := [spellDmg_Percent]
9210 temp.name_orig := spellDmg_Percent . "% increased Spell Damage"
9211 temp.name := "#% increased Spell Damage"
9212 temp.simplifiedName := "xIncreasedSpellDamage"
9213 temp.possibleParentSimplifiedNames := ["xIncreasedSpellDamage"]
9214 tempMods.push(temp)
9215 }
9216
9217 ; other damages
9218 percentDamageModSuffixes := [" Damage", " Damage with Attack Skills", " Spell Damage"]
9219 flatDamageModSuffixes := ["", " to Attacks", " to Spells"]
9220
9221 For i, element in dmgTypes {
9222 StringUpper, element, element, T
9223
9224 For j, dmgType in ["", "Attacks", "Spells"] {
9225 ; ### Percentage damages
9226 If (%element%Dmg_%dmgType%Percent > 0) {
9227 modSuffix := percentDamageModSuffixes[j]
9228 temp := {}
9229 temp.values := [%element%Dmg_%dmgType%Percent]
9230 temp.name_orig := %element%Dmg_%dmgType%Percent "% increased " element . modSuffix
9231 temp.name := "#% increased " element . modSuffix
9232 temp.simplifiedName := "xIncreased" element "Damage" dmgType
9233 temp.possibleParentSimplifiedNames := ["xIncreased" element "Damage" dmgType, "xIncreased" element "Damage"]
9234 ( element != "Elemental" ) ? temp.possibleParentSimplifiedNames.push("xIncreasedElementalDamage" dmgType) : False
9235 ( dmgType == "Spells" ) ? temp.possibleParentSimplifiedNames.push("xIncreasedSpellDamage") : False
9236 tempMods.push(temp)
9237 }
9238 ; ### Flat damages
9239 If (%element%Dmg_%dmgType%FlatLow > 0 or %element%Dmg_%dmgType%FlatHi > 0) {
9240 modSuffix := flatDamageModSuffixes[j]
9241 temp := {}
9242 temp.values := [%element%Dmg_%dmgType%FlatLow, %element%Dmg_%dmgType%FlatHi]
9243 temp.name_orig := "Adds " %element%Dmg_%dmgType%FlatLow " to " %element%Dmg_%dmgType%FlatHi " " element " Damage" modSuffix
9244 temp.name := "Adds # " element " Damage" modSuffix
9245 temp.simplifiedName := "xFlat" element "Damage" dmgType
9246 temp.possibleParentSimplifiedNames := ["xFlat" element "Damage" dmgType]
9247 If (element != "Elemental") {
9248 temp.possibleParentSimplifiedNames.push("xFlatElementalDamage" dmgType)
9249 } Else {
9250 temp.possibleParentSimplifiedNames := []
9251 For e, el in dmgTypes {
9252 StringUpper, upperEl, el, T
9253 temp.possibleParentSimplifiedNames.push("xFlat" upperEl "Damage" dmgType)
9254 }
9255 }
9256 tempMods.push(temp)
9257 }
9258 }
9259 }
9260
9261 /* BREAKPOINT
9262 ; ########################################################################
9263 ; ### Filter/Remove unwanted pseudos
9264 ; ### Only keep pseudos with values higher than other related mods
9265 ; ### TODO: Improve/Simplify this part, so far I just copy/pasted code and doing ALMOST the same thing in each loop
9266 ; ### We could exit inner loop as soon as higher is set to false, I'll check the docs later
9267 ; ########################################################################
9268 */
9269
9270 ; This 1st pass is for TradeMacro
9271 ; remove pseudos that are shadowed by an original mod ONLY if they have the same name
9272 ; inherited values not taken into account for this 1st pass
9273 ; ex ( '25% increased Cold Spell Damage' is shadowed by '%25 increased Spell Damage' ) BUT don't have same name
9274
9275 allPseudoMods := []
9276 For i, tempMod in tempMods {
9277 higher := true
9278 For j, mod in mods {
9279 ; check for mods with same name
9280 ; Eruyome: Is there any reason to use simplifiedName here? This can fail for pseudo mods like total ele resists
9281 ; Eruyome: It's possible to match empty simplified names and compare the values of different mods with each other that way
9282
9283 ;If ( tempMod.simplifiedName == mod.simplifiedName ) {
9284 If ( tempMod.name == mod.name ) {
9285 ; check if it's a flat damage mod
9286 If (mod.values[2]) {
9287 mv := Round((mod.values[1] + mod.values[2]) / 2, 3)
9288 tv := Round((tempMod.values[1] + tempMod.values[2]) / 2, 3)
9289 If (tv <= mv) {
9290 higher := false
9291 }
9292 }
9293 Else {
9294 If (tempMod.values[1] <= mod.values[1]) {
9295 higher := false
9296 }
9297 }
9298 }
9299 }
9300 ; add the tempMod to pseudos if it has greater values, or no parent
9301 If (higher or (tempMod.exception and returnAllMods)) {
9302 tempMod.isVariable:= false
9303 tempMod.type := "pseudo"
9304 allPseudoMods.push(tempMod)
9305 }
9306 }
9307
9308 ; 2nd pass
9309 ; now we remove pseudos that are shadowed by an original mod they inherited from
9310 ; ex ( '25% increased Cold Spell Damage' is shadowed by '%25 increased Spell Damage' )
9311 tempPseudoMods := []
9312 For i, tempMod in allPseudoMods {
9313 higher := true
9314 For j, mod in mods {
9315 ; check if it's a parent mod
9316 isParentMod := false
9317 For k, simplifiedName in tempMod.possibleParentSimplifiedNames {
9318 If (mod.simplifiedName == simplifiedName) {
9319 isParentMod := true
9320 ; TODO: match found we could exit loop here
9321 }
9322 }
9323 If ( isParentMod ) {
9324 ; check if it's a flat damage mod
9325 If (mod.values[2]) {
9326 mv := Round((mod.values[1] + mod.values[2]) / 2, 3)
9327 tv := Round((tempMod.values[1] + tempMod.values[2]) / 2, 3)
9328 If (tv <= mv) {
9329 higher := false
9330 }
9331 }
9332 Else {
9333 If (tempMod.values[1] <= mod.values[1]) {
9334 higher := false
9335 }
9336 }
9337 }
9338 }
9339 ; add the tempMod to pseudos if it has greater values, or no parent
9340 If (higher or (tempMod.exception and returnAllMods)) {
9341 tempMod.isVariable:= false
9342 tempMod.type := "pseudo"
9343 tempPseudoMods.push(tempMod)
9344 }
9345 }
9346
9347 ; 3rd Pass
9348 ; same logic as above but compare pseudo with other pseudos
9349 ; remove pseudos that are shadowed by another pseudo
9350 ; ex ( '25% increased Cold Spell Damage' is shadowed by '%25 increased Spell Damage' )
9351 ; must also avoid removing itself
9352
9353 pseudoMods := []
9354 For i, tempPseudoA in tempPseudoMods {
9355 higher := true
9356 For j, tempPseudoB in tempPseudoMods {
9357 ; skip if its the same object
9358 If ( i != j ) {
9359 ; check if it's a parent mod
9360 isParentMod := false
9361 For k, simplifiedName in tempPseudoA.possibleParentSimplifiedNames {
9362 if (tempPseudoB.simplifiedName == simplifiedName) {
9363 isParentMod := true
9364 ; TODO: match found we could exit loop here
9365 }
9366 }
9367 If ( isParentMod ) {
9368 ; check if it's a flat damage mod
9369 If (tempPseudoB.values[2]) {
9370 mv := Round((tempPseudoB.values[1] + tempPseudoB.values[2]) / 2, 3)
9371 tv := Round((tempPseudoA.values[1] + tempPseudoA.values[2]) / 2, 3)
9372 If (tv <= mv) {
9373 higher := false
9374 }
9375 }
9376 Else {
9377 If (tempPseudoA.values[1] <= tempPseudoB.values[1]) {
9378 higher := false
9379 }
9380 }
9381 }
9382 }
9383 }
9384 ; add the tempMod to pseudos if it has greater values, or no parent
9385 If (higher) {
9386 tempPseudoA.isVariable:= false
9387 tempPseudoA.type := "pseudo"
9388 pseudoMods.push(tempPseudoA)
9389 }
9390 }
9391
9392 ; ### This is mostly for TradeMacro
9393 ; returns all original mods and the pseudo mods if requested
9394 If (returnAllMods) {
9395 returnedMods := mods
9396 For i, mod in pseudoMods {
9397 returnedMods.push(mod)
9398 }
9399 Return returnedMods
9400 }
9401
9402 Return pseudoMods
9403}
9404
9405ChangeTooltipColorByItem(conditionalColors = false) {
9406 Global Opts, Item
9407
9408 _rarity := Item.RarityLevel
9409 _type := Item.BaseType
9410
9411 If (conditionalColors) {
9412 If (_rarity = 4) {
9413 _bColor := "af5f1c"
9414 _bOpacity := 90
9415 } Else If (_rarity = 3) {
9416 _bColor := "b3931e"
9417 _bOpacity := 90
9418 } Else If (_rarity = 2) {
9419 _bColor := "8787fe"
9420 _bOpacity := 90
9421 } Else If (_rarity = 1) {
9422 _bColor := "9c9285"
9423 _bOpacity := 90
9424 } Else If (_type = "Gem") {
9425 _bColor := "608376"
9426 _bOpacity := 90
9427 } Else If (_type = "Prophecy") {
9428 _bColor := "b547fe"
9429 _bOpacity := 90
9430 } Else If (Item.IsCurrency or Item.IsEssence) {
9431 _bColor := "867951"
9432 _bOpacity := 90
9433 }
9434 }
9435
9436 If (not StrLen(_bColor) or not conditionalColors) {
9437 gdipTooltip.UpdateColors(Opts.GDIWindowColor, Opts.GDIWindowOpacity, Opts.GDIBorderColor, Opts.GDIBorderOpacity, Opts.GDITextColor, Opts.GDITextOpacity, 10, 16)
9438 } Else {
9439 gdipTooltip.UpdateColors(Opts.GDIWindowColor, Opts.GDIWindowOpacity, _bColor, _bOpacity, Opts.GDITextColor, Opts.GDITextOpacity, 10, 16)
9440 }
9441}
9442
9443; Show tooltip, with fixed width font
9444ShowToolTip(String, Centered = false, conditionalColors = false)
9445{
9446 Global X, Y, ToolTipTimeout, Opts, gdipTooltip, Item
9447
9448 ; Get position of mouse cursor
9449 MouseGetPos, X, Y
9450 WinGet, PoEWindowHwnd, ID, ahk_group PoEWindowGrp
9451 RelativeToActiveWindow := true ; default tooltip behaviour
9452
9453 If (not RelativeToActiveWindow) {
9454 OldCoordMode := A_CoordModeToolTip
9455 CoordMode, Tooltip, Screen
9456 }
9457
9458 If (Not Opts.DisplayToolTipAtFixedCoords)
9459 {
9460 If (Centered)
9461 {
9462 ScreenOffsetY := A_ScreenHeight / 2
9463 ScreenOffsetX := A_ScreenWidth / 2
9464
9465 XCoord := 0 + ScreenOffsetX
9466 YCoord := 0 + ScreenOffsetY
9467
9468 If (Opts.UseGDI)
9469 {
9470 ChangeTooltipColorByItem(conditionalColors)
9471 gdipTooltip.ShowGdiTooltip(Opts.FontSize, String, XCoord, YCoord, RelativeToActiveWindow, PoEWindowHwnd)
9472 }
9473 Else
9474 {
9475 ToolTip, %String%, XCoord, YCoord
9476 Fonts.SetFixedFont()
9477 Sleep, 10
9478 ToolTip, %String%, XCoord, YCoord
9479 }
9480 }
9481 Else
9482 {
9483 XCoord := (X - 135 >= 0) ? X - 135 : 0
9484 YCoord := (Y + 35 >= 0) ? Y + 35 : 0
9485
9486 If (Opts.UseGDI)
9487 {
9488 ChangeTooltipColorByItem(conditionalColors)
9489 gdipTooltip.ShowGdiTooltip(Opts.FontSize, String, XCoord, YCoord, RelativeToActiveWindow, PoEWindowHwnd)
9490 }
9491 Else
9492 {
9493 ToolTip, %String%, XCoord, YCoord
9494 Fonts.SetFixedFont()
9495 Sleep, 10
9496 ToolTip, %String%, XCoord, YCoord
9497 }
9498 }
9499 }
9500 Else
9501 {
9502 CoordMode, ToolTip, Screen
9503 ScreenOffsetY := Opts.ScreenOffsetY
9504 ScreenOffsetX := Opts.ScreenOffsetX
9505
9506 XCoord := 0 + ScreenOffsetX
9507 YCoord := 0 + ScreenOffsetY
9508
9509 If (Opts.UseGDI)
9510 {
9511 ChangeTooltipColorByItem(conditionalColors)
9512 gdipTooltip.ShowGdiTooltip(Opts.FontSize, String, XCoord, YCoord, RelativeToActiveWindow, PoEWindowHwnd, true)
9513 }
9514 Else
9515 {
9516 ToolTip, %String%, XCoord, YCoord
9517 Fonts.SetFixedFont()
9518 Sleep, 10
9519 ToolTip, %String%, XCoord, YCoord
9520 }
9521 }
9522 ;Fonts.SetFixedFont()
9523
9524 ; Set up count variable and start timer for tooltip timeout
9525 ToolTipTimeout := 0
9526 SetTimer, ToolTipTimer, 100
9527
9528 If (OldCoordMode) {
9529 CoordMode, Tooltip, % OldCoordMode
9530 }
9531}
9532
9533; ############ GUI #############
9534
9535GuiSet(ControlID, Param3="", SubCmd="")
9536{
9537 If (!(SubCmd == "")) {
9538 GuiControl, %SubCmd%, %ControlID%, %Param3%
9539 } Else {
9540 GuiControl,, %ControlID%, %Param3%
9541 }
9542}
9543
9544GuiGet(ControlID, DefaultValue="", ByRef Error = false)
9545{
9546 curVal =
9547 ErrorLevel := 0
9548 GuiControlGet, curVal,, %ControlID%, %DefaultValue%
9549 Error := ErrorLevel
9550 return curVal
9551}
9552
9553GuiAdd(ControlType, Contents, PositionInfo, AssocVar="", AssocHwnd="", AssocLabel="", Param4="", GuiName="")
9554{
9555 Global
9556 Local av, ah, al
9557 av := StrPrefix(AssocVar, "v")
9558 al := StrPrefix(AssocLabel, "g")
9559 ah := StrPrefix(AssocHwnd, "hwnd")
9560
9561 If (ControlType = "GroupBox") {
9562 Options := Param4 " cDA4F49 "
9563 }
9564 Else If (ControlType = "ListView") {
9565 Options := Param4
9566 }
9567 Else {
9568 Options := Param4 . " BackgroundTrans "
9569 }
9570
9571 GuiName := (StrLen(GuiName) > 0) ? Trim(GuiName) . ":Add" : "Add"
9572 Gui, %GuiName%, %ControlType%, %PositionInfo% %av% %al% %ah% %Options%, %Contents%
9573}
9574
9575GuiAddPicture(Contents, PositionInfo, AssocVar="", AssocHwnd="", AssocLabel="", Options="", GuiName="")
9576{
9577 GuiAdd("Picture", Contents, PositionInfo, AssocVar, AssocHwnd, AssocLabel, Options, GuiName)
9578}
9579
9580GuiAddListView(ColumnHeaders, PositionInfo, AssocVar="", AssocHwnd="", AssocLabel="", Options="", GuiName="")
9581{
9582 GuiAdd("ListView", ColumnHeaders, PositionInfo, AssocVar, AssocHwnd, AssocLabel, Options, GuiName)
9583}
9584
9585GuiAddButton(Contents, PositionInfo, AssocLabel="", AssocVar="", AssocHwnd="", Options="", GuiName="")
9586{
9587 GuiAdd("Button", Contents, PositionInfo, AssocVar, AssocHwnd, AssocLabel, Options, GuiName)
9588}
9589
9590GuiAddGroupBox(Contents, PositionInfo, AssocVar="", AssocHwnd="", AssocLabel="", Options="", GuiName="")
9591{
9592 GuiAdd("GroupBox", Contents, PositionInfo, AssocVar, AssocHwnd, AssocLabel, Options, GuiName)
9593}
9594
9595GuiAddCheckbox(Contents, PositionInfo, CheckedState=0, AssocVar="", AssocHwnd="", AssocLabel="", Options="", GuiName="")
9596{
9597 GuiAdd("Checkbox", Contents, PositionInfo, AssocVar, AssocHwnd, AssocLabel, "Checked" . CheckedState . " " . Options, GuiName)
9598}
9599
9600GuiAddText(Contents, PositionInfo, AssocVar="", AssocHwnd="", AssocLabel="", Options="", GuiName="")
9601{
9602 ; static controls like Text need "0x0100" added to their options for the tooltip to work
9603 ; either add it always here or don't forget to add it manually when using this function
9604 GuiAdd("Text", Contents, PositionInfo, AssocVar, AssocHwnd, AssocLabel, Options, GuiName)
9605}
9606
9607GuiAddEdit(Contents, PositionInfo, AssocVar="", AssocHwnd="", AssocLabel="", Options="", GuiName="")
9608{
9609 GuiAdd("Edit", Contents, PositionInfo, AssocVar, AssocHwnd, AssocLabel, Options, GuiName)
9610}
9611
9612GuiAddHotkey(Contents, PositionInfo, AssocVar="", AssocHwnd="", AssocLabel="", Options="", GuiName="")
9613{
9614 GuiAdd("Hotkey", Contents, PositionInfo, AssocVar, AssocHwnd, AssocLabel, Options, GuiName)
9615}
9616
9617GuiAddDropDownList(Contents, PositionInfo, Selected="", AssocVar="", AssocHwnd="", AssocLabel="", Options="", GuiName="")
9618{
9619 ; usage : add list items as a | delimited string, for example = "item1|item2|item3"
9620 ListItems := StrSplit(Contents, "|")
9621 Contents := ""
9622 Loop % ListItems.MaxIndex() {
9623 Contents .= Trim(ListItems[A_Index]) . "|"
9624 ; add second | to mark pre-select list item
9625 If (Trim(ListItems[A_Index]) == Selected) {
9626 Contents .= "|"
9627 }
9628 }
9629 GuiAdd("DropDownList", Contents, PositionInfo, AssocVar, AssocHwnd, AssocLabel, Options, GuiName)
9630}
9631
9632GuiUpdateDropdownList(Contents="", Selected="", AssocVar="", Options="", GuiName="") {
9633 GuiName := (StrLen(GuiName) > 0) ? Trim(GuiName) . ":" . AssocVar : "" . AssocVar
9634
9635 If (StrLen(Contents) > 0) {
9636 ; usage : add list items as a | delimited string, for example = "item1|item2|item3"
9637 ListItems := StrSplit(Contents, "|")
9638 ; prepend the list with a pipe to re-create the list instead of appending it
9639 Contents := "|"
9640 Loop % ListItems.MaxIndex() {
9641 Contents .= Trim(ListItems[A_Index]) . "|"
9642 ; add second | to mark pre-select list item
9643 If (Trim(ListItems[A_Index]) == Selected) {
9644 Contents .= "|"
9645 }
9646 }
9647 GuiControl, , %GuiName%, %Contents%
9648 }
9649
9650 If (StrLen(Selected)) > 0 {
9651 ; falls back to "ChooseString" if param3 is not an integer
9652 GuiControl, Choose, %GuiName% , %Selected%
9653 }
9654}
9655
9656AddToolTip(con, text, Modify=0) {
9657 Static TThwnd, GuiHwnd
9658 TInfo =
9659 UInt := "UInt"
9660 Ptr := (A_PtrSize ? "Ptr" : UInt)
9661 PtrSize := (A_PtrSize ? A_PtrSize : 4)
9662 Str := "Str"
9663 ; defines from Windows MFC commctrl.h
9664 WM_USER := 0x400
9665 TTM_ADDTOOL := (A_IsUnicode ? WM_USER+50 : WM_USER+4) ; used to add a tool, and assign it to a control
9666 TTM_UPDATETIPTEXT := (A_IsUnicode ? WM_USER+57 : WM_USER+12) ; used to adjust the text of a tip
9667 TTM_SETMAXTIPWIDTH := WM_USER+24 ; allows the use of multiline tooltips
9668 TTF_IDISHWND := 1
9669 TTF_CENTERTIP := 2
9670 TTF_RTLREADING := 4
9671 TTF_SUBCLASS := 16
9672 TTF_TRACK := 0x0020
9673 TTF_ABSOLUTE := 0x0080
9674 TTF_TRANSPARENT := 0x0100
9675 TTF_PARSELINKS := 0x1000
9676 If (!TThwnd) {
9677 Gui, +LastFound
9678 GuiHwnd := WinExist()
9679 TThwnd := DllCall("CreateWindowEx"
9680 ,UInt,0
9681 ,Str,"tooltips_class32"
9682 ,UInt,0
9683 ,UInt,2147483648
9684 ,UInt,-2147483648
9685 ,UInt,-2147483648
9686 ,UInt,-2147483648
9687 ,UInt,-2147483648
9688 ,UInt,GuiHwnd
9689 ,UInt,0
9690 ,UInt,0
9691 ,UInt,0)
9692 }
9693 ; TOOLINFO structure
9694 cbSize := 6*4+6*PtrSize
9695 uFlags := TTF_IDISHWND|TTF_SUBCLASS|TTF_PARSELINKS
9696 VarSetCapacity(TInfo, cbSize, 0)
9697 NumPut(cbSize, TInfo)
9698 NumPut(uFlags, TInfo, 4)
9699 NumPut(GuiHwnd, TInfo, 8)
9700 NumPut(con, TInfo, 8+PtrSize)
9701 NumPut(&text, TInfo, 6*4+3*PtrSize)
9702 NumPut(0,TInfo, 6*4+6*PtrSize)
9703 DetectHiddenWindows, On
9704 If (!Modify) {
9705 DllCall("SendMessage"
9706 ,Ptr,TThwnd
9707 ,UInt,TTM_ADDTOOL
9708 ,Ptr,0
9709 ,Ptr,&TInfo
9710 ,Ptr)
9711 DllCall("SendMessage"
9712 ,Ptr,TThwnd
9713 ,UInt,TTM_SETMAXTIPWIDTH
9714 ,Ptr,0
9715 ,Ptr,A_ScreenWidth)
9716 }
9717 DllCall("SendMessage"
9718 ,Ptr,TThwnd
9719 ,UInt,TTM_UPDATETIPTEXT
9720 ,Ptr,0
9721 ,Ptr,&TInfo
9722 ,Ptr)
9723
9724}
9725
9726; ######### SETTINGS ############
9727
9728; (Internal: RegExr x-forms)
9729; GroupBox
9730; Gui, Add, GroupBox, (.+?) , (.+) -> ; $2 \n\n GuiAddGroupBox("$2", "$1")
9731; Checkbox (with label)
9732; Gui, Add, (.+?), (.+?) hwnd(.+?) v(.+?) g(.+?) Checked%(.+)%, (.+) -> GuiAdd$1("$7", "$2", Opts.$6, "$4", "$3", "$5")
9733; Checkbox /w/o label)
9734; Gui, Add, (.+?), (.+?) hwnd(.+?) v(.+?) Checked%(.+)%, (.+) -> GuiAdd$1("$6", "$2", Opts.$5, "$4", "$3")
9735; Edit
9736; Gui, Add, Edit, (.+?) hwnd(.+?) v(.+?), %(.+)% -> GuiAddEdit(Opts.$4, "$1", "$3", "", "$2")
9737; Text
9738; Gui, Add, Text, (.+?) hwnd(.+?) v(.+?), (.+) -> GuiAddText("$4", "$1", "$3", "", "$2")
9739; Button
9740; Gui, Add, Button, (.+?) g(.+?), (.+) -> GuiAddButton("$3", "$1", "", "$2", "")
9741
9742CreateSettingsUI()
9743{
9744 Global
9745
9746 Gui, SettingsUI:Color, ffffff, ffffff
9747 Gui, SettingsUI:Default
9748
9749 ; ItemInfo is not included in other scripts
9750 If (not SkipItemInfoUpdateCall) {
9751 Fonts.SetUIFont(8)
9752 Scripts := Globals.Get("SettingsScriptList")
9753 TabNames := ""
9754 Loop, % Scripts.Length() {
9755 name := Scripts[A_Index]
9756 TabNames .= name "|"
9757 }
9758
9759 StringTrimRight, TabNames, TabNames, 1
9760 Gui, SettingsUI:Add, Tab3, Choose1 h660 x0, %TabNames%
9761 }
9762
9763 ; Note: window handles (hwnd) are only needed if a UI tooltip should be attached.
9764
9765 generalHeight := SkipItemInfoUpdateCall ? "150" : "240" ; "180" : "270" with ParseItemHotKey
9766 topGroupBoxYPos := SkipItemInfoUpdateCall ? "y51" : "y30"
9767
9768 ; General
9769 GuiAddGroupBox("General", "x7 " topGroupBoxYPos " w310 h" generalHeight " Section", "", "", "", "", "SettingsUI")
9770 GuiAddCheckbox("Only show tooltip if PoE is frontmost", "xs10 yp+20 w250 h30", Opts.OnlyActiveIfPOEIsFront, "OnlyActiveIfPOEIsFront", "OnlyActiveIfPOEIsFrontH", "", "", "SettingsUI")
9771 AddToolTip(OnlyActiveIfPOEIsFrontH, "When checked the script only activates while you are ingame`n(technically while the game window is the frontmost)")
9772
9773 ;GuiAddHotkey(Opts.ParseItemHotKey, "xs75 yp+37 w120 h20", "ParseItemHotKey")
9774 ;GuiAddText("Hotkey:", "xs27 yp+2 w50 h20 0x0100", "LblParseItemHotKey")
9775 ; Change next from yp+30 to yp+25 when this is implemented.
9776
9777 GuiAddCheckbox("Put tooltip results on clipboard", "xs10 yp+30 w250 h30", Opts.PutResultsOnClipboard, "PutResultsOnClipboard", "PutResultsOnClipboardH", "", "", "SettingsUI")
9778 AddToolTip(PutResultsOnClipboardH, "Put tooltip result text into the system clipboard`n(overwriting the raw text PoE itself put there to begin with)")
9779
9780 GuiAddCheckbox("Enable Map Mod Warnings", "xs10 yp+30 w250 h30", Opts.EnableMapModWarnings, "EnableMapModWarnings", "EnableMapModWarningsH", "", "", "SettingsUI")
9781 AddToolTip(EnableMapModWarningsH, "Enables or disables the entire Map Mod Warnings function.")
9782
9783 If (!SkipItemInfoUpdateCall) {
9784 GuiAddCheckbox("Update: Show Notifications", "xs10 yp+30 w250 h30", Opts.ShowUpdateNotification, "ShowUpdateNotification", "ShowUpdateNotificationH", "", "", "SettingsUI")
9785 AddToolTip(ShowUpdateNotificationH, "Notifies you when there's a new release available.")
9786
9787 GuiAddCheckbox("Update: Skip folder selection", "xs10 yp+30 w250 h30", Opts.UpdateSkipSelection, "UpdateSkipSelection", "UpdateSkipSelectionH", "", "", "SettingsUI")
9788 AddToolTip(UpdateSkipSelectionH, "Skips selecting an update location.`nThe current script directory will be used as default.")
9789
9790 GuiAddCheckbox("Update: Skip backup", "xs10 yp+30 w250 h30", Opts.UpdateSkipBackup, "UpdateSkipBackup", "UpdateSkipBackupH", "", "", "SettingsUI")
9791 AddToolTip(UpdateSkipBackupH, "Skips making a backup of the install location/folder.")
9792 }
9793
9794 ; GDI+
9795 GDIShift := SkipItemInfoUpdateCall ? 210 : 300
9796 GuiAddGroupBox("GDI+", "x7 ym+" GDIShift " w310 h320 Section", "", "", "", "", "SettingsUI")
9797
9798 GuiAddCheckBox("Enable GDI+", "xs10 yp+20 w115", Opts.UseGDI, "UseGDI", "UseGDIH", "SettingsUI_ChkUseGDI", "", "SettingsUI")
9799 AddToolTip(UseGDIH, "Enables rendering of tooltips using Windows gdip.dll`n(allowing limited styling options).")
9800 GuiAddCheckBox("Rendering Fix", "xs10 yp+30 w115", Opts.GDIRenderingFix, "GDIRenderingFix", "GDIRenderingFixH", "", "", "SettingsUI")
9801 AddToolTip(GDIRenderingFixH, "In the case that rendered graphics (window, border and text) are`nunsharp/blurry this should fix the issue.")
9802 GuiAddText("(Restart script after disabling GDI+. Enabling might cause general FPS drops.)", "xs120 ys+20 w185 cRed", "", "", "", "", "SettingsUI")
9803
9804 GuiAddButton("Edit Window", "xs9 ys80 w80 h23", "SettingsUI_BtnGDIWindowColor", "BtnGDIWindowColor", "", "", "SettingsUI")
9805 GuiAddText("Color (hex RGB):", "xs100 ys85 w200", "LblGDIWindowColor", "", "", "", "SettingsUI")
9806 GuiAddEdit(Opts.GDIWindowColor, "xs240 ys82 w60", "GDIWindowColor", "GDIWindowColorH", "", "", "SettingsUI")
9807 GuiAddText("Opactiy (0-100):", "xs100 ys115 w200", "LblGDIWindowOpacity", "", "", "", "SettingsUI")
9808 GuiAddEdit(Opts.GDIWindowOpacity, "xs240 ys112 w60", "GDIWindowOpacity", "GDIWindowOpacityH", "", "", "SettingsUI")
9809 GuiAddButton("Edit Border", "xs9 ys140 w80 h23", "SettingsUI_BtnGDIBorderColor", "BtnGDIBorderColor", "", "", "SettingsUI")
9810 GuiAddText("Color (hex RGB):", "xs100 ys145 w200", "LblGDIBorderColor", "", "", "", "SettingsUI")
9811 GuiAddEdit(Opts.GDIBorderColor, "xs240 ys142 w60", "GDIBorderColor", "GDIBorderColorH", "", "", "SettingsUI")
9812 GuiAddText("Opacity (0-100):", "xs100 ys175 w200", "LblGDIBorderOpacity", "", "", "", "SettingsUI")
9813 GuiAddEdit(Opts.GDIBorderOpacity, "xs240 ys172 w60", "GDIBorderOpacity", "GDIBorderOpacityH", "", "", "SettingsUI")
9814 GuiAddButton("Edit Text", "xs9 ys200 w80 h23", "SettingsUI_BtnGDITextColor", "BtnGDITextColor", "", "", "SettingsUI")
9815 GuiAddText("Color (hex RGB):", "xs100 ys205 w200", "LblGDITextColor", "", "", "", "SettingsUI")
9816 GuiAddEdit(Opts.GDITextColor, "xs240 ys202 w60", "GDITextColor", "GDITextColorH", "", "", "SettingsUI")
9817 GuiAddText("Opacity (0-100):", "xs100 ys235 w200", "LblGDITextOpacity", "", "", "", "SettingsUI")
9818 GuiAddEdit(Opts.GDITextOpacity, "xs240 ys232 w60", "GDITextOpacity", "GDITextOpacityH", "", "", "SettingsUI")
9819 GuiAddCheckBox("Style border depending on checked item.", "xs10 ys260 w260", Opts.GDIConditionalColors, "GDIConditionalColors", "GDIConditionalColorsH", "", "", "SettingsUI")
9820
9821 GuiAddButton("GDI Defaults", "xs9 ys290 w100 h23", "SettingsUI_BtnGDIDefaults", "BtnGDIDefaults", "BtnGDIDefaultsH", "", "SettingsUI")
9822 GuiAddButton("Preview", "xs210 ys290 w80 h23", "SettingsUI_BtnGDIPreviewTooltip", "BtnGDIPreviewTooltip", "BtnGDIPreviewTooltipH", "", "SettingsUI")
9823
9824 ; Tooltip
9825 GuiAddGroupBox("Tooltip", "x327 " topGroupBoxYPos " w310 h140 Section", "", "", "", "", "SettingsUI")
9826
9827 GuiAddEdit(Opts.MouseMoveThreshold, "xs250 yp+22 w50 h20 Number", "MouseMoveThreshold", "MouseMoveThresholdH", "", "", "SettingsUI")
9828 GuiAddText("Mouse move threshold (px):", "xs27 yp+3 w200 h20 0x0100", "LblMouseMoveThreshold", "LblMouseMoveThresholdH", "", "", "SettingsUI")
9829 AddToolTip(LblMouseMoveThresholdH, "Hide tooltip when the mouse cursor moved x pixel away from the initial position.`nEffectively permanent tooltip when using a value larger than the monitor diameter.")
9830
9831 GuiAddEdit(Opts.ToolTipTimeoutSeconds, "xs250 yp+27 w50 Number", "ToolTipTimeoutSeconds", "", "", "", "SettingsUI")
9832 GuiAddCheckBox("Use tooltip timeout (seconds)", "xs10 yp+3 w200", Opts.UseTooltipTimeout, "UseTooltipTimeout", "UseTooltipTimeoutH", "SettingsUI_ChkUseTooltipTimeout", "", "SettingsUI")
9833 AddToolTip(UseTooltipTimeoutH, "Hide tooltip automatically after defined time.")
9834
9835 GuiAddCheckbox("Display at fixed coordinates", "xs10 yp+30 w280", Opts.DisplayToolTipAtFixedCoords, "DisplayToolTipAtFixedCoords", "DisplayToolTipAtFixedCoordsH", "SettingsUI_ChkDisplayToolTipAtFixedCoords", "", "SettingsUI")
9836 AddToolTip(DisplayToolTipAtFixedCoordsH, "Show tooltip in virtual screen space at the fixed`ncoordinates given below. Virtual screen space means`nthe full desktop frame, including any secondary`nmonitors. Coords are relative to the top left edge`nand increase going down and to the right.")
9837 GuiAddEdit(Opts.ScreenOffsetX, "xs50 yp+22 w50", "ScreenOffsetX", "", "", "", "SettingsUI")
9838 GuiAddEdit(Opts.ScreenOffsetY, "xs130 yp+0 w50", "ScreenOffsetY", "", "", "", "SettingsUI")
9839 GuiAddText("X", "xs35 yp+3 w15", "LblScreenOffsetX", "", "", "", "SettingsUI")
9840 GuiAddText("Y", "xs115 yp+0 w15", "LblScreenOffsetY", "", "", "", "SettingsUI")
9841
9842
9843 ; Display
9844 GuiAddGroupBox("Display", "x327 ym+" 200 " w310 h295 Section", "", "", "", "", "SettingsUI")
9845
9846 GuiAddCheckbox("Show header for affix overview", "xs10 yp+20 w260 h30", Opts.ShowHeaderForAffixOverview, "ShowHeaderForAffixOverview", "ShowHeaderForAffixOverviewH", "", "", "SettingsUI")
9847 AddToolTip(ShowHeaderForAffixOverviewH, "Include a header above the affix overview:`n TierRange ilvl Total ilvl Tier")
9848
9849 GuiAddCheckbox("Show explanation for used notation", "xs10 yp+30 w260 h30", Opts.ShowExplanationForUsedNotation, "ShowExplanationForUsedNotation", "ShowExplanationForUsedNotationH", "", "", "SettingsUI")
9850 AddToolTip(ShowExplanationForUsedNotationH, "Explain abbreviations and special notation symbols at`nthe end of the tooltip when they are used")
9851
9852 GuiAddEdit(Opts.AffixTextEllipsis, "xs260 y+5 w40 h20", "AffixTextEllipsis", "", "", "", "SettingsUI")
9853 GuiAddText("Affix text ellipsis:", "xs10 yp+3 w170 h20 0x0100", "LblAffixTextEllipsis", "AffixTextEllipsisH", "", "", "SettingsUI")
9854 AddToolTip(AffixTextEllipsisH, "Symbol used when affix text is shortened, such as:`n50% increased Spell…")
9855
9856 GuiAddEdit(Opts.AffixColumnSeparator, "xs260 y+7 w40 h20", "AffixColumnSeparator", "", "", "", "SettingsUI")
9857 GuiAddText("Affix column separator:", "xs10 yp+3 w170 h20 0x0100", "LblAffixColumnSeparator", "AffixColumnSeparatorH", "", "", "SettingsUI")
9858 AddToolTip(AffixColumnSeparatorH, "Select separator (default: 2 spaces) for the \\ spots:`n50% increased Spell�\\50-59 (46)\\75-79 (84)\\T4 P")
9859
9860 GuiAddEdit(Opts.DoubleRangeSeparator, "xs260 y+7 w40 h20", "DoubleRangeSeparator", "", "", "", "SettingsUI")
9861 GuiAddText("Double range separator:", "xs10 yp+3 w170 h20 0x0100", "LblDoubleRangeSeparator", "DoubleRangeSeparatorH", "", "", "SettingsUI")
9862 AddToolTip(DoubleRangeSeparatorH, "Select separator (default: | ) for double ranges from 'added damage' mods:`na-b to c-d is displayed as a-b|c-d")
9863
9864 GuiAddCheckbox("Use compact double ranges", "xs10 y+3 w210 h30", Opts.UseCompactDoubleRanges, "UseCompactDoubleRanges", "UseCompactDoubleRangesH", "SettingsUI_ChkUseCompactDoubleRanges", "", "SettingsUI")
9865 AddToolTip(UseCompactDoubleRangesH, "Show double ranges from 'added damage' mods as one range,`ne.g. a-b to c-d becomes a-d")
9866
9867 GuiAddCheckbox("Only compact for 'Total' column", "xs30 yp+30 w210 h30", Opts.OnlyCompactForTotalColumn, "OnlyCompactForTotalColumn", "OnlyCompactForTotalColumnH", "", "", "SettingsUI")
9868 AddToolTip(OnlyCompactForTotalColumnH, "Only use compact double ranges for the second range column`nin the affix overview (with the header 'total')")
9869
9870 GuiAddEdit(Opts.MultiTierRangeSeparator, "xs260 y+6 w40 h20", "MultiTierRangeSeparator", "", "", "", "SettingsUI")
9871 GuiAddText("Multi tier range separator:", "xs10 yp+3 w170 h20 0x0100", "LblMultiTierRangeSeparator", "MultiTierRangeSeparatorH", "", "", "SettingsUI")
9872 AddToolTip(MultiTierRangeSeparatorH, "Select separator (default: � ) for a multi tier roll range with uncertainty:`n83% increased Light� 73-85�83-95 102-109 (84) T1-4 P + T1-6 S`n There--^")
9873
9874 GuiAddEdit(Opts.FontSize, "xs260 y+6 w40 h20 Number", "FontSize", "", "", "", "SettingsUI")
9875 GuiAddText("Font Size:", "xs10 yp+3 w180 h20 0x0100", "", "", "", "", "SettingsUI")
9876
9877 ; Buttons
9878 ButtonsShiftX := "x659 "
9879 GuiAddText("Mouse over settings or see the GitHub Wiki page for comments on what these settings do exactly.", ButtonsShiftX " y63 w290 h30 0x0100", "", "", "", "", "SettingsUI")
9880
9881 GuiAddButton("Defaults", ButtonsShiftX "y+8 w90 h23", "SettingsUI_BtnDefaults", "", "", "", "SettingsUI")
9882 GuiAddButton("OK", "Default x+5 yp+0 w90 h23", "SettingsUI_BtnOK", "", "", "", "SettingsUI")
9883 GuiAddButton("Cancel", "x+5 yp+0 w90 h23", "SettingsUI_BtnCancel", "", "", "", "SettingsUI")
9884
9885 If (SkipItemInfoUpdateCall) {
9886 GuiAddText("Use these buttons to change ItemInfo and AdditionalMacros settings (TradeMacro has it's own buttons).", ButtonsShiftX "y+10 w250 h50 cRed", "", "", "", "", "SettingsUI")
9887 GuiAddText("", "x10 y10 w250 h10", "", "", "", "", "SettingsUI")
9888 }
9889
9890 ; Begin Additional Macros Tab
9891 If (SkipItemInfoUpdateCall) {
9892 Gui, SettingsUI:Tab, 3
9893 } Else {
9894 Gui, SettingsUI:Tab, 2
9895 }
9896
9897 ; AM Hotkeys
9898 GuiAddGroupBox("[AdditionalMacros] Hotkeys", "x7 " topGroupBoxYPos " w630 h625", "", "", "", "", "SettingsUI")
9899
9900 If (not AM_Config) {
9901 GoSub, AM_Init
9902 }
9903
9904 chkBoxWidth := 160
9905 chkBoxShiftY := 28
9906 LVWidth := 185
9907
9908 _AM_sections := StrSplit(AM_Config.GetSections("|", "C"), "|")
9909 For sectionIndex, sectionName in _AM_sections { ; this enables section sorting
9910 If (sectionName != "General") {
9911 ; hotkey checkboxes (enable/disable)
9912 HKCheckBoxID := "AM_" sectionName "_State"
9913 GuiAddCheckbox(sectionName ":", "x17 yp+" chkBoxShiftY " w" chkBoxWidth " h20 0x0100", AM_Config[sectionName].State, HKCheckBoxID, HKCheckBoxID "H", "", "", "SettingsUI")
9914 AddToolTip(%HKCheckBoxID%H, RegExReplace(AM_ConfigDefault[sectionName].Description, "i)(\(Default = .*\))|\\n", "`n$1")) ; read description from default config
9915
9916 For keyIndex, keyValue in StrSplit(AM_Config[sectionName].Hotkeys, ", ") {
9917 HotKeyID := "AM_" sectionName "_HotKeys_" keyIndex
9918 LV_shiftY := keyIndex > 1 ? 1 : 0
9919 GuiAddListView("1|2", "x+10 yp+" LV_shiftY " h20 w" LVWidth, HotKeyID, HotKeyID "H", "", "r1 -Hdr -LV0x20 r1 C454444 Backgroundf0f0f0", "SettingsUI")
9920 LV_ModifyCol(1, 0)
9921 LV_ModifyCol(2, LVWidth - 5)
9922 LV_Delete(1)
9923 LV_Add("","", keyValue)
9924
9925 GuiAddButton("Edit", "xp+" LVWidth " yp-1 w30 h22 v" HotKeyID "_Trigger", "LV_HotkeyEdit", "", "", "", "SettingsUI")
9926 }
9927
9928 For keyIndex, keyValue in AM_Config[sectionName] {
9929 If (not RegExMatch(keyIndex, "i)State|Hotkeys|Description")) {
9930 If (RegExMatch(sectionName, "i)HighlightItems|HighlightItemsAlt")) {
9931 If (keyIndex = "Arg2") {
9932 CheckBoxID := "AM_" sectionName "_Arg2"
9933 GuiAddCheckbox("Leave search field.", "x" 17 + chkBoxWidth + 10 " yp+" chkBoxShiftY, keyValue, CheckBoxID, CheckBoxID "H", "", "", "SettingsUI")
9934 }
9935 If (keyIndex = "Arg3") {
9936 CheckBoxID := "AM_" sectionName "_Arg3"
9937 GuiAddCheckbox("Enable hideout stash search.", "x+10 yp+0", keyValue, CheckBoxID, CheckBoxID "H", "", "", "SettingsUI")
9938 }
9939 If (keyIndex = "Arg4") {
9940 EditID := "AM_" sectionName "_" keyIndex
9941 GuiAddText("Decoration stash search field coordinates: ", "x" 17 + chkBoxWidth + 10 " yp+" chkBoxShiftY " w260 h20 0x0100", "LblHighlighting", "LblHighlightingH", "", "", "SettingsUI")
9942 AddToolTip(LblHighlightingH, "Refers to the decoration stash on the right side`nof the screen, not the master vendor window.`n`nCoordinates are relative to the PoE game window and`nare neccessary to click into/focus the search field.")
9943 GuiAddPicture(A_ScriptDir "\resources\images\info-blue.png", "x+-15 yp+0 w15 h-1 0x0100", "HighlightInfo", "HighlightH", "", "", "SettingsUI")
9944 GuiAddText("x= ", "x+5 yp+0 w20 h20 0x0100", "", "", "", "", "SettingsUI")
9945 GuiAddEdit(keyValue, "x+0 yp-2 w40 h20", EditID, "", "", "", "SettingsUI")
9946 }
9947 If (keyIndex = "Arg5") {
9948 EditID := "AM_" sectionName "_" keyIndex
9949 GuiAddText("y=", "x+5 yp+2 w20 h20 0x0100", "", "", "", "", "SettingsUI")
9950 GuiAddEdit(keyValue, "x+0 yp-2 w40 h20", EditID, "", "", "", "SettingsUI")
9951 }
9952 }
9953 Else If (RegExMatch(sectionName, "i)JoinChannel|KickYourself")) {
9954 EditID := "AM_" sectionName "_" keyIndex
9955 GuiAddText(keyIndex ":", "x+10 yp+4 w85 h20 0x0100", "", "", "", "", "SettingsUI")
9956 GuiAddEdit(keyValue, "x+0 yp-2 w99 h20", EditID, "", "", "", "SettingsUI")
9957 }
9958 Else {
9959 EditID := "AM_" sectionName "_" keyIndex
9960 GuiAddText(keyIndex ":", "x" 17 + chkBoxWidth + 10 " yp+" chkBoxShiftY " w85 h20 0x0100", "", "", "", "", "SettingsUI")
9961 GuiAddEdit(keyValue, "x+0 yp-2 w99 h20", EditID, "", "", "", "SettingsUI")
9962 }
9963 }
9964 }
9965 }
9966 }
9967
9968 ; AM General
9969
9970 GuiAddGroupBox("[AdditionalMacros] General", "x647 " topGroupBoxYPos " w310 h60", "", "", "", "", "SettingsUI")
9971
9972 _i := 0
9973 For keyIndex, keyValue in AM_Config.General {
9974 If (not RegExMatch(keyIndex, ".*_Description$")) {
9975 elementYPos := _i > 0 ? 20 : 30
9976
9977 If (RegExMatch(keyIndex, ".*State$") and not (InStr(keyIndex, "KeyToSC", 0))) {
9978 RegExMatch(AM_ConfigDefault.General[keyIndex "_Description"], ".*Short\$(.*)Long\$(.*)""", _description) ; read description from default config
9979 ControlID := "AM_General_" keyIndex
9980 GuiAddCheckbox(Trim(_description1), "x657 yp+" elementYPos " w250 h30", AM_Config.General[keyIndex], ControlID, ControlID "H", "", "", "SettingsUI")
9981 AddToolTip(%ControlID%H, Trim(_description2))
9982 }
9983 _i++
9984 }
9985 }
9986
9987 ; AM Buttons
9988
9989 GuiAddText("Mouse over settings or see the GitHub Wiki page for comments on what these settings do exactly.", ButtonsShiftX "yp+60 w290 h30 0x0100", "", "", "", "", "SettingsUI")
9990 GuiAddButton("Defaults", "xp-5 y+8 w90 h23", "SettingsUI_AM_BtnDefaults", "", "", "", "SettingsUI")
9991 GuiAddButton("OK", "Default x+5 yp+0 w90 h23", "SettingsUI_BtnOK", "", "", "", "SettingsUI")
9992 GuiAddButton("Cancel", "x+5 yp+0 w90 h23", "SettingsUI_BtnCancel", "", "", "", "SettingsUI")
9993 GuiAddText("Any change here currently requires a script restart!", ButtonsShiftX "y+10 w280 h50 cGreen", "", "", "", "", "SettingsUI")
9994
9995 If (SkipItemInfoUpdateCall) {
9996 GuiAddText("Use these buttons to change ItemInfo and AdditionalMacros settings (TradeMacro has it's own buttons).", ButtonsShiftX "y+5 w280 h50 cRed", "", "", "", "", "SettingsUI")
9997 }
9998
9999 GuiAddText("Experimental Feature!", ButtonsShiftX "y+35 w280 h200 cRed", "", "", "", "", "SettingsUI")
10000 experimentalNotice := "This new feature to assign hotkeys may cause issues for users with non-latin keyboard layouts."
10001 experimentalNotice .= "`n`n" . "AHKs default UI element for selecting hotkeys doesn't support any special keys and mouse buttons."
10002 experimentalNotice .= "`n`n" . "Please report any issues that you are experiencing."
10003 experimentalNotice .= " You can still assign your settings directly using the AdditionalMacros.ini like before."
10004 experimentalNotice .= " (Right-click system tray icon -> Edit Files)."
10005 GuiAddText(experimentalNotice, ButtonsShiftX "yp+25 w290", "", "", "", "", "SettingsUI")
10006
10007 ; Begin Lutbot Tab
10008 If (SkipItemInfoUpdateCall) {
10009 Gui, SettingsUI:Tab, 4
10010 } Else {
10011 Gui, SettingsUI:Tab, 3
10012 }
10013
10014 GuiAddGroupBox("[Lutbot Logout]", "x7 " topGroupBoxYPos " w630 h625", "", "", "", "", "SettingsUI")
10015
10016 lb_desc := "Lutbot's macro is a collection of features like TCP disconnect logout, whisper replies, ladder tracker and more.`n"
10017 lb_desc .= "The included logout macro is the most advanced logout feature currently out there."
10018 GuiAddText(lb_desc, "x17 yp+28 w600 h40 0x0100", "", "", "", "", "SettingsUI")
10019
10020 lb_desc := "Since running the main version of this script alongside " Globals.Get("Projectname") " can cause some issues`n"
10021 lb_desc .= "and hotkey conflicts, Lutbot also released a lite version that only contains the logout features."
10022 GuiAddText(lb_desc, "x17 y+10 w600 h35 0x0100", "", "", "", "", "SettingsUI")
10023
10024 Gui, SettingsUI:Add, Link, x17 y+5 cBlue, <a href="http://lutbot.com/#/ahk">Website and download</a>
10025
10026 lb_desc := Globals.Get("Projectname") " can manage running this lite version for you, keeping it an independant script."
10027 GuiAddText(lb_desc, "x17 y+20 w600 h20 0x0100", "", "", "", "", "SettingsUI")
10028
10029 GuiAddCheckbox("Run lutbot on script start if the lutbot macro exists (requires you to have run it once before).", "x17 yp+20 w600 h30", Opts.Lutbot_CheckScript, "Lutbot_CheckScript", "Lutbot_CheckScriptH", "", "", "SettingsUI")
10030
10031 GuiAddCheckbox("Warn in case of hotkey conflicts", "x17 yp+30 w290 h30", Opts.Lutbot_WarnConflicts, "Lutbot_WarnConflicts", "Lutbot_WarnConflictsH", "", "", "SettingsUI")
10032 AddToolTip(Lutbot_CheckScriptH, "Check if the lutbot macro exists and run it.")
10033
10034 GuiAddButton("Open Lutbot folder", "Default x17 y+10 w130 h23", "OpenLutbotDocumentsFolder", "", "", "", "SettingsUI")
10035
10036 lb_desc := "If you have any issues related to"
10037 GuiAddText(lb_desc, "x17 y+40 w600 h20 0x0100", "", "", "", "", "SettingsUI")
10038 lb_desc := "- " Globals.Get("Projectname") " starting the lutbot script or checking for conflicts report here:"
10039 GuiAddText(lb_desc, "x17 y+0 w600 h20 0x0100", "", "", "", "", "SettingsUI")
10040 Gui, SettingsUI:Add, Link, x35 y+5 cBlue h20, - <a href="https://github.com/PoE-TradeMacro/POE-TradeMacro/issues">Github</a>
10041 Gui, SettingsUI:Add, Link, x35 y+0 cBlue h20, - <a href="https://discord.gg/taKZqWw">Discord</a>
10042 Gui, SettingsUI:Add, Link, x35 y+0 cBlue h20, - <a href="https://www.pathofexile.com/forum/view-thread/1757730">Forum</a>
10043
10044 lb_desc := "- Lutbots script not working correctly in any way report here:"
10045 GuiAddText(lb_desc, "x17 y+5 w600 h20 0x0100", "", "", "", "", "SettingsUI")
10046 Gui, SettingsUI:Add, Link, x35 y+5 cBlue h20, - <a href="https://discord.gg/nttekWT">Discord</a>
10047
10048 ; Lutbot Buttons
10049
10050 GuiAddText("Mouse over settings to see what these settings do exactly.", ButtonsShiftX "y60 w290 h30 0x0100", "", "", "", "", "SettingsUI")
10051 GuiAddButton("OK", "Default xp-5 y+8 w90 h23", "SettingsUI_BtnOK", "", "", "", "SettingsUI")
10052 GuiAddButton("Cancel", "x+5 yp+0 w90 h23", "SettingsUI_BtnCancel", "", "", "", "SettingsUI")
10053
10054 ; close tabs
10055 Gui, SettingsUI:Tab
10056
10057 GoSub, SettingsUI_ChkUseCompactDoubleRanges
10058 GoSub, SettingsUI_ChkDisplayToolTipAtFixedCoords
10059 GoSub, SettingsUI_ChkUseTooltipTimeout
10060 GoSub, SettingsUI_ChkUseGDI
10061}
10062
10063UpdateSettingsUI()
10064{
10065 Global Opts
10066
10067 ; General
10068 ;GuiControl,, ParseItemHotKey, % Opts.ParseItemHotKey
10069 GuiControl,, OnlyActiveIfPOEIsFront, % Opts.OnlyActiveIfPOEIsFront
10070 GuiControl,, PutResultsOnClipboard, % Opts.PutResultsOnClipboard
10071 ;GuiControl,, EnableAdditionalMacros, % Opts.EnableAdditionalMacros
10072 GuiControl,, EnableMapModWarnings, % Opts.EnableMapModWarnings
10073 If (!SkipItemInfoUpdateCall) {
10074 GuiControl,, ShowUpdateNotifications, % Opts.ShowUpdateNotifications
10075 GuiControl,, UpdateSkipSelection, % Opts.UpdateSkipSelection
10076 GuiControl,, UpdateSkipBackup, % Opts.UpdateSkipBackup
10077 }
10078
10079 ; Tooltip
10080 GuiControl,, MouseMoveThreshold, % Opts.MouseMoveThreshold
10081 GuiControl,, UseTooltipTimeout, % Opts.UseTooltipTimeout
10082 If (Opts.UseTooltipTimeout == False){
10083 GuiControl, Disable, ToolTipTimeoutSeconds
10084 }
10085 Else{
10086 GuiControl, Enable, ToolTipTimeoutSeconds
10087 }
10088 GuiControl,, ToolTipTimeoutSeconds, % Opts.ToolTipTimeoutSeconds
10089
10090 GuiControl,, DisplayToolTipAtFixedCoords, % Opts.DisplayToolTipAtFixedCoords
10091 If (Opts.DisplayToolTipAtFixedCoords == False)
10092 {
10093 GuiControl, Disable, LblScreenOffsetX
10094 GuiControl, Disable, ScreenOffsetX
10095 GuiControl, Disable, LblScreenOffsetY
10096 GuiControl, Disable, ScreenOffsetY
10097 }
10098 Else
10099 {
10100 GuiControl, Enable, LblScreenOffsetX
10101 GuiControl, Enable, ScreenOffsetX
10102 GuiControl, Enable, LblScreenOffsetY
10103 GuiControl, Enable, ScreenOffsetY
10104 }
10105
10106 ; Display
10107 GuiControl,, ShowHeaderForAffixOverview, % Opts.ShowHeaderForAffixOverview
10108 GuiControl,, ShowExplanationForUsedNotation, % Opts.ShowExplanationForUsedNotation
10109 GuiControl,, AffixTextEllipsis, % Opts.AffixTextEllipsis
10110 GuiControl,, AffixColumnSeparator, % Opts.AffixColumnSeparator
10111 GuiControl,, DoubleRangeSeparator, % Opts.DoubleRangeSeparator
10112 GuiControl,, UseCompactDoubleRanges, % Opts.UseCompactDoubleRanges
10113 If (Opts.UseCompactDoubleRanges == False) {
10114 GuiControl, Enable, OnlyCompactForTotalColumn
10115 }
10116 Else {
10117 GuiControl, Disable, OnlyCompactForTotalColumn
10118 }
10119 GuiControl,, OnlyCompactForTotalColumn, % Opts.OnlyCompactForTotalColumn
10120 GuiControl,, MultiTierRangeSeparator, % Opts.MultiTierRangeSeparator
10121 GuiControl,, FontSize, % Opts.FontSize
10122
10123
10124 ; GDI+
10125 GuiControl,, UseGDI, % Opts.UseGDI
10126 GuiControl,, GDIRenderingFix, % Opts.GDIRenderingFix
10127 gdipTooltip.SetRenderingFix(Opts.GDIRenderingFix)
10128
10129 ; If the gdipTooltip is not yet initialised use the color value without validation, it will be validated and updated on enabling GDI
10130 GuiControl,, GDIWindowColor , % not IsObject(gdipTooltip.window) ? gdipTooltip.ValidateRGBColor(Opts.GDIWindowColor, Opts.GDIWindowColorDefault) : Opts.GDIWindowColor
10131 GuiControl,, GDIWindowOpacity , % not IsObject(gdipTooltip.window) ? gdipTooltip.ValidateOpacity(Opts.GDIWindowOpacity, Opts.GDIWindowOpacityDefault, "10", "10") : Opts.GDIWindowOpacity
10132 GuiControl,, GDIBorderColor , % not IsObject(gdipTooltip.window) ? gdipTooltip.ValidateRGBColor(Opts.GDIBorderColor, Opts.GDIBorderColorDefault) : Opts.GDIBorderColor
10133 GuiControl,, GDIBorderOpacity , % not IsObject(gdipTooltip.window) ? gdipTooltip.ValidateOpacity(Opts.GDIBorderOpacity, Opts.GDIBorderOpacityDefault, "10", "10") : Opts.GDIBorderOpacity
10134 GuiControl,, GDITextColor , % not IsObject(gdipTooltip.window) ? gdipTooltip.ValidateRGBColor(Opts.GDITextColor, Opts.GDITextColorDefault) : Opts.GDITextColor
10135 GuiControl,, GDITextOpacity , % not IsObject(gdipTooltip.window) ? gdipTooltip.ValidateOpacity(Opts.GDITextOpacity, Opts.GDITextOpacityDefault, "10", "10") : Opts.GDITextOpacity
10136 gdipTooltip.UpdateColors(Opts.GDIWindowColor, Opts.GDIWindowOpacity, Opts.GDIBorderColor, Opts.GDIBorderOpacity, Opts.GDITextColor, Opts.GDITextOpacity, 10, 16)
10137
10138 If (Opts.UseGDI == False)
10139 {
10140 GuiControl, Disable, GDIWindowColor
10141 GuiControl, Disable, GDIWindowOpacity
10142 GuiControl, Disable, GDIBorderColor
10143 GuiControl, Disable, GDIBorderOpacity
10144 GuiControl, Disable, GDITextColor
10145 GuiControl, Disable, GDITextOpacity
10146
10147 GuiControl, Disable, BtnGDIWindowColor
10148 GuiControl, Disable, BtnGDIBorderColor
10149 GuiControl, Disable, BtnGDITextColor
10150
10151 GuiControl, Disable, BtnGDIDefaults
10152 GuiControl, Disable, BtnGDIPreviewTooltip
10153 GuiControl, Disable, GDIRenderingFix
10154 GuiControl, Disable, GDIConditionalColors
10155 }
10156 Else
10157 {
10158 GuiControl, Enable, GDIWindowColor
10159 GuiControl, Enable, GDIWindowOpacity
10160 GuiControl, Enable, GDIBorderColor
10161 GuiControl, Enable, GDIBorderOpacity
10162 GuiControl, Enable, GDITextColor
10163 GuiControl, Enable, GDITextOpacity
10164
10165 GuiControl, Enable, BtnGDIWindowColor
10166 GuiControl, Enable, BtnGDIBorderColor
10167 GuiControl, Enable, BtnGDITextColor
10168
10169 GuiControl, Enable, BtnGDIDefaults
10170 GuiControl, Enable, BtnGDIPreviewTooltip
10171 GuiControl, Enable, GDIRenderingFix
10172 GuiControl, Enable, GDIConditionalColors
10173 }
10174
10175 ; AdditionalMacros
10176 AM_UpdateSettingsUI()
10177}
10178
10179ShowSettingsUI()
10180{
10181 ; remove POE-Item-Info tooltip if still visible
10182 SetTimer, ToolTipTimer, Off
10183 ToolTip
10184 Fonts.SetUIFont(9)
10185 SettingsUIWidth := Globals.Get("SettingsUIWidth", 545)
10186 ; Adjust user option window height depending on whether ItemInfo is used as a Standalone or included in the TradeMacro.
10187 ; The TradeMacro needs much more space for all the options.
10188 SettingsUIHeight := Globals.Get("SettingsUIHeight", 615)
10189 SettingsUITitle := Globals.Get("SettingsUITitle", "PoE ItemInfo Settings")
10190 Gui, SettingsUI:Show, w%SettingsUIWidth% h%SettingsUIHeight%, %SettingsUITitle%
10191}
10192
10193ShowUpdateNotes()
10194{
10195 ; remove POE-Item-Info tooltip if still visible
10196 SetTimer, ToolTipTimer, Off
10197
10198 If (gdipTooltip.GetVisibility()) {
10199 gdipTooltip.HideGdiTooltip()
10200 }
10201 ToolTip
10202 Gui, UpdateNotes:Destroy
10203 Gui, UpdateNotes:Color, ffffff, ffffff
10204 Fonts.SetUIFont(9)
10205 Gui, UpdateNotes:Font, , Verdana
10206
10207 Files := Globals.Get("UpdateNoteFileList")
10208
10209 TabNames := ""
10210 Loop, % Files.Length() {
10211 name := Files[A_Index][2]
10212 TabNames .= name "|"
10213 }
10214
10215 StringTrimRight, TabNames, TabNames, 1
10216 PreSelect := Files.Length()
10217 Gui, UpdateNotes:Add, Tab3, Choose%PreSelect%, %TabNames%
10218
10219 Loop, % Files.Length() {
10220 file := Files[A_Index][1]
10221 FileRead, notes, %file%
10222 Gui, UpdateNotes:Add, Edit, r50 ReadOnly w900 BackgroundTrans, %notes%
10223 NextTab := A_Index + 1
10224 Gui, UpdateNotes:Tab, %NextTab%
10225 }
10226 Gui, UpdateNotes:Tab
10227
10228 SettingsUIWidth := 945
10229 SettingsUIHeight := 710
10230 SettingsUITitle := "Update Notes"
10231 Gui, UpdateNotes:Show, w%SettingsUIWidth% h%SettingsUIHeight%, %SettingsUITitle%
10232}
10233
10234ShowChangedUserFiles()
10235{
10236 Gui, ChangedUserFiles:Destroy
10237 Gui, ChangedUserFiles:Color, ffffff, ffffff
10238
10239 Gui, ChangedUserFiles:Add, Text, , Following user files were changed in the last update and `nwere overwritten (old files were backed up):
10240
10241 Loop, Parse, overwrittenUserFiles, `n
10242 {
10243 If (StrLen(A_Loopfield) > 0) {
10244 Gui, ChangedUserFiles:Add, Text, y+5, %A_LoopField%
10245 }
10246 }
10247 Gui, ChangedUserFiles:Add, Button, y+10 gChangedUserFilesWindow_Cancel, Close
10248 Gui, ChangedUserFiles:Add, Button, x+10 yp+0 gChangedUserFilesWindow_OpenFolder, Open user folder
10249 Gui, ChangedUserFiles:Show, w300, Changed User Files
10250 ControlFocus, Close, Changed User Files
10251}
10252
10253IniRead(SectionName, KeyName, DefaultVal, ConfigObj)
10254{
10255 If (ConfigObj[SectionName].HasKey(KeyName)) {
10256 ; return value and replace potential leading or trailing
10257 return RegExReplace(ConfigObj[SectionName, KeyName], "^""'|'""$")
10258 }
10259 Else {
10260 return DefaultVal
10261 }
10262}
10263
10264IniWrite(Val, SectionName, KeyName, ByRef ConfigObj)
10265{
10266 If (RegExMatch(Val, "^\s|\s$")) {
10267 Val := """'" Val "'"""
10268 }
10269
10270 ConfigObj.SetKeyVal(SectionName, KeyName, Val)
10271}
10272
10273ReadConfig(ConfigDir = "", ConfigFile = "config.ini")
10274{
10275 Global Opts, ItemInfoConfigObj
10276
10277 If (StrLen(ConfigDir) < 1) {
10278 ConfigDir := userDirectory
10279 }
10280 ConfigPath := StrLen(ConfigDir) > 0 ? ConfigDir . "\" . ConfigFile : ConfigFile
10281
10282 ItemInfoConfigObj := class_EasyIni(ConfigPath)
10283
10284 IfExist, %ConfigPath%
10285 {
10286 ; General
10287 ;Opts.ParseItemHotKey := IniRead("General", "ParseItemHotKey", Opts.ParseItemHotKey)
10288 Opts.OnlyActiveIfPOEIsFront := IniRead("General", "OnlyActiveIfPOEIsFront", Opts.OnlyActiveIfPOEIsFront, ItemInfoConfigObj)
10289 Opts.PutResultsOnClipboard := IniRead("General", "PutResultsOnClipboard", Opts.PutResultsOnClipboard, ItemInfoConfigObj)
10290 ;Opts.EnableAdditionalMacros := IniRead("General", "EnableAdditionalMacros", Opts.EnableAdditionalMacros, ItemInfoConfigObj)
10291 Opts.EnableMapModWarnings := IniRead("General", "EnableMapModWarnings", Opts.EnableMapModWarnings, ItemInfoConfigObj)
10292 Opts.ShowUpdateNotifications := IniRead("General", "ShowUpdateNotifications", Opts.ShowUpdateNotifications, ItemInfoConfigObj)
10293 Opts.UpdateSkipSelection := IniRead("General", "UpdateSkipSelection", Opts.UpdateSkipSelection, ItemInfoConfigObj)
10294 Opts.UpdateSkipBackup := IniRead("General", "UpdateSkipBackup", Opts.UpdateSkipBackup, ItemInfoConfigObj)
10295
10296 ; Tooltip
10297 Opts.MouseMoveThreshold := IniRead("Tooltip", "MouseMoveThreshold", Opts.MouseMoveThreshold, ItemInfoConfigObj)
10298 Opts.UseTooltipTimeout := IniRead("Tooltip", "UseTooltipTimeout", Opts.UseTooltipTimeout, ItemInfoConfigObj)
10299 Opts.ToolTipTimeoutSeconds := IniRead("Tooltip", "ToolTipTimeoutSeconds", Opts.ToolTipTimeoutSeconds, ItemInfoConfigObj)
10300 Opts.DisplayToolTipAtFixedCoords := IniRead("Tooltip", "DisplayToolTipAtFixedCoords", Opts.DisplayToolTipAtFixedCoords, ItemInfoConfigObj)
10301 Opts.ScreenOffsetX := IniRead("Tooltip", "ScreenOffsetX", Opts.ScreenOffsetX, ItemInfoConfigObj)
10302 Opts.ScreenOffsetY := IniRead("Tooltip", "ScreenOffsetY", Opts.ScreenOffsetY, ItemInfoConfigObj)
10303
10304 ; Display
10305 Opts.ShowHeaderForAffixOverview := IniRead("Display", "ShowHeaderForAffixOverview", Opts.ShowHeaderForAffixOverview, ItemInfoConfigObj)
10306 Opts.ShowExplanationForUsedNotation := IniRead("Display", "ShowExplanationForUsedNotation", Opts.ShowExplanationForUsedNotation, ItemInfoConfigObj)
10307 Opts.AffixTextEllipsis := IniRead("Display", "AffixTextEllipsis", Opts.AffixTextEllipsis, ItemInfoConfigObj)
10308 Opts.AffixColumnSeparator := IniRead("Display", "AffixColumnSeparator", Opts.AffixColumnSeparator, ItemInfoConfigObj)
10309 Opts.DoubleRangeSeparator := IniRead("Display", "DoubleRangeSeparator", Opts.DoubleRangeSeparator, ItemInfoConfigObj)
10310 Opts.UseCompactDoubleRanges := IniRead("Display", "UseCompactDoubleRanges", Opts.UseCompactDoubleRanges, ItemInfoConfigObj)
10311 Opts.OnlyCompactForTotalColumn := IniRead("Display", "OnlyCompactForTotalColumn", Opts.OnlyCompactForTotalColumn, ItemInfoConfigObj)
10312 Opts.MultiTierRangeSeparator := IniRead("Display", "MultiTierRangeSeparator", Opts.MultiTierRangeSeparator, ItemInfoConfigObj)
10313 Opts.FontSize := IniRead("Display", "FontSize", Opts.FontSize, ItemInfoConfigObj)
10314
10315 ; GDI+
10316 Opts.UseGDI := IniRead("GDI", "Enabled", Opts.UseGDI, ItemInfoConfigObj)
10317 Opts.GDIRenderingFix := IniRead("GDI", "RenderingFix", Opts.GDIRenderingFix, ItemInfoConfigObj)
10318 Opts.GDIConditionalColors := IniRead("GDI", "ConditionalColors", Opts.GDIConditionalColors, ItemInfoConfigObj)
10319 Opts.GDIWindowColor := IniRead("GDI", "WindowColor", Opts.GDIWindowColor, ItemInfoConfigObj)
10320 Opts.GDIWindowColorDefault := IniRead("GDI", "WindowColorDefault", Opts.GDIWindowColorDefault, ItemInfoConfigObj)
10321 Opts.GDIWindowOpacity := IniRead("GDI", "WindowOpacity", Opts.GDIWindowOpacity, ItemInfoConfigObj)
10322 Opts.GDIWindowOpacityDefault := IniRead("GDI", "WindowOpacityDefault", Opts.GDIWindowOpacityDefault, ItemInfoConfigObj)
10323 Opts.GDIBorderColor := IniRead("GDI", "BorderColor", Opts.GDIBorderColor, ItemInfoConfigObj)
10324 Opts.GDIBorderColorDefault := IniRead("GDI", "BorderColorDefault", Opts.GDIBorderColorDefault, ItemInfoConfigObj)
10325 Opts.GDIBorderOpacity := IniRead("GDI", "BorderOpacity", Opts.GDIBorderOpacity, ItemInfoConfigObj)
10326 Opts.GDIBorderOpacityDefault := IniRead("GDI", "BorderOpacityDefault", Opts.GDIBorderOpacityDefault, ItemInfoConfigObj)
10327 Opts.GDITextColor := IniRead("GDI", "TextColor", Opts.GDITextColor, ItemInfoConfigObj)
10328 Opts.GDITextColorDefault := IniRead("GDI", "TextColorDefault", Opts.GDITextColorDefault, ItemInfoConfigObj)
10329 Opts.GDITextOpacity := IniRead("GDI", "TextOpacity", Opts.GDITextOpacity, ItemInfoConfigObj)
10330 Opts.GDITextOpacityDefault := IniRead("GDI", "TextOpacityDefault", Opts.GDITextOpacityDefault, ItemInfoConfigObj)
10331 gdipTooltip.UpdateColors(Opts.GDIWindowColor, Opts.GDIWindowOpacity, Opts.GDIBorderColor, Opts.GDIBorderOpacity, Opts.GDITextColor, Opts.GDITextOpacity, "10", "16")
10332
10333 ; Lutbot
10334 Opts.Lutbot_CheckScript := IniRead("Lutbot", "Lutbot_CheckScript", Opts.Lutbot_CheckScript, ItemInfoConfigObj)
10335 Opts.Lutbot_WarnConflicts := IniRead("Lutbot", "Lutbot_WarnConflicts", Opts.Lutbot_WarnConflicts, ItemInfoConfigObj)
10336 }
10337}
10338
10339WriteConfig(ConfigDir = "", ConfigFile = "config.ini")
10340{
10341 Global Opts, ItemInfoConfigObj
10342
10343 If (StrLen(ConfigDir) < 1) {
10344 ConfigDir := userDirectory
10345 }
10346 ConfigPath := StrLen(ConfigDir) > 0 ? ConfigDir . "\" . ConfigFile : ConfigFile
10347
10348 ItemInfoConfigObj := class_EasyIni(ConfigPath)
10349
10350 Opts.ScanUI()
10351
10352 ; General
10353 ;IniWrite(Opts.ParseItemHotKey, "General", "ParseItemHotKey")
10354 IniWrite(Opts.OnlyActiveIfPOEIsFront, "General", "OnlyActiveIfPOEIsFront", ItemInfoConfigObj)
10355 IniWrite(Opts.PutResultsOnClipboard, "General", "PutResultsOnClipboard", ItemInfoConfigObj)
10356 ;IniWrite(Opts.EnableAdditionalMacros, "General", "EnableAdditionalMacros", ItemInfoConfigObj)
10357 IniWrite(Opts.EnableMapModWarnings, "General", "EnableMapModWarnings", ItemInfoConfigObj)
10358 IniWrite(Opts.ShowUpdateNotifications, "General", "ShowUpdateNotifications", ItemInfoConfigObj)
10359 IniWrite(Opts.UpdateSkipSelection, "General", "UpdateSkipSelection", ItemInfoConfigObj)
10360 IniWrite(Opts.UpdateSkipBackup, "General", "UpdateSkipBackup", ItemInfoConfigObj)
10361
10362 ; Display
10363 IniWrite(Opts.ShowHeaderForAffixOverview, "Display", "ShowHeaderForAffixOverview", ItemInfoConfigObj)
10364 IniWrite(Opts.ShowExplanationForUsedNotation, "Display", "ShowExplanationForUsedNotation", ItemInfoConfigObj)
10365 IniWrite("" . Opts.AffixTextEllipsis . "", "Display", "AffixTextEllipsis", ItemInfoConfigObj)
10366 IniWrite("" . Opts.AffixColumnSeparator . "", "Display", "AffixColumnSeparator", ItemInfoConfigObj)
10367 IniWrite("" . Opts.DoubleRangeSeparator . "", "Display", "DoubleRangeSeparator", ItemInfoConfigObj)
10368 IniWrite(Opts.UseCompactDoubleRanges, "Display", "UseCompactDoubleRanges", ItemInfoConfigObj)
10369 IniWrite(Opts.OnlyCompactForTotalColumn, "Display", "OnlyCompactForTotalColumn", ItemInfoConfigObj)
10370 IniWrite("" . Opts.MultiTierRangeSeparator . "", "Display", "MultiTierRangeSeparator", ItemInfoConfigObj)
10371 IniWrite(Opts.FontSize, "Display", "FontSize", ItemInfoConfigObj)
10372
10373 ; Tooltip
10374 IniWrite(Opts.MouseMoveThreshold, "Tooltip", "MouseMoveThreshold", ItemInfoConfigObj)
10375 IniWrite(Opts.UseTooltipTimeout, "Tooltip", "UseTooltipTimeout", ItemInfoConfigObj)
10376 IniWrite(Opts.ToolTipTimeoutSeconds, "Tooltip", "ToolTipTimeoutSeconds", ItemInfoConfigObj)
10377 IniWrite(Opts.DisplayToolTipAtFixedCoords, "Tooltip", "DisplayToolTipAtFixedCoords", ItemInfoConfigObj)
10378 IniWrite(Opts.ScreenOffsetX, "Tooltip", "ScreenOffsetX", ItemInfoConfigObj)
10379 IniWrite(Opts.ScreenOffsetY, "Tooltip", "ScreenOffsetY", ItemInfoConfigObj)
10380
10381 ; GDI+
10382 IniWrite(Opts.UseGDI, "GDI", "Enabled", ItemInfoConfigObj)
10383 IniWrite(Opts.GDIRenderingFix, "GDI", "RenderingFix", ItemInfoConfigObj)
10384 IniWrite(Opts.GDIConditionalColors, "GDI", "ConditionalColors", ItemInfoConfigObj)
10385 IniWrite(Opts.GDIWindowColor, "GDI", "WindowColor", ItemInfoConfigObj)
10386 IniWrite(Opts.GDIWindowOpacity, "GDI", "WindowOpacity", ItemInfoConfigObj)
10387 IniWrite(Opts.GDIBorderColor, "GDI", "BorderColor", ItemInfoConfigObj)
10388 IniWrite(Opts.GDIBorderOpacity, "GDI", "BorderOpacity", ItemInfoConfigObj)
10389 IniWrite(Opts.GDITextColor, "GDI", "TextColor", ItemInfoConfigObj)
10390 IniWrite(Opts.GDITextOpacity, "GDI", "TextOpacity", ItemInfoConfigObj)
10391
10392 ; Lutbot
10393 IniWrite(Opts.Lutbot_CheckScript, "Lutbot", "Lutbot_CheckScript", ItemInfoConfigObj)
10394 IniWrite(Opts.Lutbot_WarnConflicts, "Lutbot", "Lutbot_WarnConflicts", ItemInfoConfigObj)
10395
10396 ItemInfoConfigObj.Save(ConfigPath)
10397}
10398
10399CopyDefaultConfig(config = "config.ini")
10400{
10401 FileCopy, %A_ScriptDir%\resources\default_UserFiles\%config%, %userDirectory%\%config%
10402}
10403
10404RemoveConfig(config = "config.ini")
10405{
10406 FileDelete, %userDirectory%\%config%
10407}
10408
10409StdOutStream(sCmd, Callback = "") {
10410 /*
10411 Runs commands in a hidden cmdlet window and returns the output.
10412 */
10413 ; Modified : Eruyome 18-June-2017
10414 Static StrGet := "StrGet" ; Modified : SKAN 31-Aug-2013 http://goo.gl/j8XJXY
10415 ; Thanks to : HotKeyIt http://goo.gl/IsH1zs
10416 ; Original : Sean 20-Feb-2007 http://goo.gl/mxCdn
10417 64Bit := A_PtrSize=8
10418
10419 DllCall( "CreatePipe", UIntP,hPipeRead, UIntP,hPipeWrite, UInt,0, UInt,0 )
10420 DllCall( "SetHandleInformation", UInt,hPipeWrite, UInt,1, UInt,1 )
10421
10422 If 64Bit {
10423 VarSetCapacity( STARTUPINFO, 104, 0 ) ; STARTUPINFO ; http://goo.gl/fZf24
10424 NumPut( 68, STARTUPINFO, 0 ) ; cbSize
10425 NumPut( 0x100, STARTUPINFO, 60 ) ; dwFlags => STARTF_USESTDHANDLES = 0x100
10426 NumPut( hPipeWrite, STARTUPINFO, 88 ) ; hStdOutput
10427 NumPut( hPipeWrite, STARTUPINFO, 96 ) ; hStdError
10428
10429 VarSetCapacity( PROCESS_INFORMATION, 32 ) ; PROCESS_INFORMATION ; http://goo.gl/b9BaI
10430 } Else {
10431 VarSetCapacity( STARTUPINFO, 68, 0 ) ; STARTUPINFO ; http://goo.gl/fZf24
10432 NumPut( 68, STARTUPINFO, 0 ) ; cbSize
10433 NumPut( 0x100, STARTUPINFO, 44 ) ; dwFlags => STARTF_USESTDHANDLES = 0x100
10434 NumPut( hPipeWrite, STARTUPINFO, 60 ) ; hStdOutput
10435 NumPut( hPipeWrite, STARTUPINFO, 64 ) ; hStdError
10436
10437 VarSetCapacity( PROCESS_INFORMATION, 32 ) ; PROCESS_INFORMATION ; http://goo.gl/b9BaI
10438 }
10439
10440 If ! DllCall( "CreateProcess", UInt,0, UInt,&sCmd, UInt,0, UInt,0 ; http://goo.gl/USC5a
10441 , UInt,1, UInt,0x08000000, UInt,0, UInt,0
10442 , UInt,&STARTUPINFO, UInt,&PROCESS_INFORMATION )
10443 Return ""
10444 , DllCall( "CloseHandle", UInt,hPipeWrite )
10445 , DllCall( "CloseHandle", UInt,hPipeRead )
10446 , DllCall( "SetLastError", Int,-1 )
10447
10448 hProcess := NumGet( PROCESS_INFORMATION, 0 )
10449 If 64Bit {
10450 hThread := NumGet( PROCESS_INFORMATION, 8 )
10451 } Else {
10452 hThread := NumGet( PROCESS_INFORMATION, 4 )
10453 }
10454
10455 DllCall( "CloseHandle", UInt,hPipeWrite )
10456
10457 AIC := ( SubStr( A_AhkVersion, 1, 3 ) = "1.0" ) ; A_IsClassic
10458 VarSetCapacity( Buffer, 4096, 0 ), nSz := 0
10459
10460 While DllCall( "ReadFile", UInt,hPipeRead, UInt,&Buffer, UInt,4094, UIntP,nSz, Int,0 ) {
10461 tOutput := ( AIC && NumPut( 0, Buffer, nSz, "Char" ) && VarSetCapacity( Buffer,-1 ) )
10462 ? Buffer : %StrGet%( &Buffer, nSz, "CP850" )
10463
10464 Isfunc( Callback ) ? %Callback%( tOutput, A_Index ) : sOutput .= tOutput
10465 }
10466
10467 DllCall( "GetExitCodeProcess", UInt,hProcess, UIntP,ExitCode )
10468 DllCall( "CloseHandle", UInt,hProcess )
10469 DllCall( "CloseHandle", UInt,hThread )
10470 DllCall( "CloseHandle", UInt,hPipeRead )
10471 DllCall( "SetLastError", UInt,ExitCode )
10472
10473 Return Isfunc( Callback ) ? %Callback%( "", 0 ) : sOutput
10474}
10475
10476ReadConsoleOutputFromFile(command, fileName, ByRef error = "") {
10477 file := "temp\" fileName
10478 RunWait %comspec% /c "chcp 1251 /f >nul 2>&1 & %command% > %file%", , Hide
10479 FileRead, io, %file%
10480
10481 If (FileExist(file) and not StrLen(io)) {
10482 error := "Output file is empty."
10483 }
10484 Else If (not FileExist(file)) {
10485 error := "Output file does not exist."
10486 }
10487
10488 Return io
10489}
10490
10491StrPutVar(Str, ByRef Var, Enc = "") {
10492 Len := StrPut(Str, Enc) * (Enc = "UTF-16" || Enc = "CP1200" ? 2 : 1)
10493 VarSetCapacity(Var, Len, 0)
10494 Return, StrPut(Str, &Var, Enc)
10495}
10496
10497UriEncode(Uri, Enc = "UTF-8") {
10498 StrPutVar(Uri, Var, Enc)
10499 f := A_FormatInteger
10500 SetFormat, IntegerFast, H
10501 Loop
10502 {
10503 Code := NumGet(Var, A_Index - 1, "UChar")
10504 If (!Code)
10505 Break
10506 If (Code >= 0x30 && Code <= 0x39 ; 0-9
10507 || Code >= 0x41 && Code <= 0x5A ; A-Z
10508 || Code >= 0x61 && Code <= 0x7A) ; a-z
10509 Res .= Chr(Code)
10510 Else
10511 Res .= "%" . SubStr(Code + 0x100, -1)
10512 }
10513 SetFormat, IntegerFast, %f%
10514 Return, Res
10515}
10516
10517ScriptInfo(Command) {
10518 ; https://autohotkey.com/boards/viewtopic.php?t=9656
10519 ; Command must be "ListLines", "ListVars", "ListHotkeys" or "KeyHistory".
10520 static hEdit := 0, pfn, bkp
10521 if !hEdit {
10522 hEdit := DllCall("GetWindow", "ptr", A_ScriptHwnd, "uint", 5, "ptr")
10523 user32 := DllCall("GetModuleHandle", "str", "user32.dll", "ptr")
10524 pfn := [], bkp := []
10525 for i, fn in ["SetForegroundWindow", "ShowWindow"] {
10526 pfn[i] := DllCall("GetProcAddress", "ptr", user32, "astr", fn, "ptr")
10527 DllCall("VirtualProtect", "ptr", pfn[i], "ptr", 8, "uint", 0x40, "uint*", 0)
10528 bkp[i] := NumGet(pfn[i], 0, "int64")
10529 }
10530 }
10531
10532 if (A_PtrSize=8) { ; Disable SetForegroundWindow and ShowWindow.
10533 NumPut(0x0000C300000001B8, pfn[1], 0, "int64") ; return TRUE
10534 NumPut(0x0000C300000001B8, pfn[2], 0, "int64") ; return TRUE
10535 } else {
10536 NumPut(0x0004C200000001B8, pfn[1], 0, "int64") ; return TRUE
10537 NumPut(0x0008C200000001B8, pfn[2], 0, "int64") ; return TRUE
10538 }
10539
10540 static cmds := {ListLines:65406, ListVars:65407, ListHotkeys:65408, KeyHistory:65409}
10541 cmds[Command] ? DllCall("SendMessage", "ptr", A_ScriptHwnd, "uint", 0x111, "ptr", cmds[Command], "ptr", 0) : 0
10542
10543 NumPut(bkp[1], pfn[1], 0, "int64") ; Enable SetForegroundWindow.
10544 NumPut(bkp[2], pfn[2], 0, "int64") ; Enable ShowWindow.
10545
10546 ControlGetText, text,, ahk_id %hEdit%
10547 return text
10548}
10549
10550GetContributors(AuthorsPerLine=0)
10551{
10552 IfNotExist, %A_ScriptDir%\resources\AUTHORS.txt
10553 {
10554 return "`r`n AUTHORS.txt missing `r`n"
10555 }
10556 Authors := "`r`n"
10557 i := 0
10558 Loop, Read, %A_ScriptDir%\resources\AUTHORS.txt, `r, `n
10559 {
10560 Authors := Authors . A_LoopReadLine . " "
10561 i += 1
10562 IF (AuthorsPerLine != 0 and mod(i, AuthorsPerLine) == 0) ; every four authors
10563 {
10564 Authors := Authors . "`r`n"
10565 }
10566 }
10567 return Authors
10568}
10569
10570ShowAssignedHotkeys(returnList = false) {
10571 scriptInfo := ScriptInfo("ListHotkeys")
10572 hotkeys := []
10573
10574 Loop, Parse, scriptInfo, `n`r,
10575 {
10576 line := RegExReplace(A_Loopfield, "[\t]", "|")
10577 line := RegExReplace(line, "\|(?!\s)", "| ") . "|"
10578 fields := []
10579
10580 If (StrLen(line)) {
10581 Pos := 0
10582 While Pos := RegExMatch(line, "i)(.*?\|+)", value, Pos + (StrLen(value) ? StrLen(value) : 1)) {
10583 fields.push(Trim(RegExReplace(value1, "\|")))
10584 }
10585 If (StrLen(fields[1]) and not InStr(fields[1], "--------")) {
10586 hotkeys.push(fields)
10587 }
10588 }
10589 }
10590
10591 ; supposed that array length wouldn't change, otherwise it's better to switch to associative array
10592 For key, val in hotkeys {
10593 If (key = 1) {
10594 val.Push("NameENG")
10595 }
10596 Else {
10597 val.Push(KeyCodeToKeyName(val[5]))
10598 }
10599 }
10600
10601 If (returnList) {
10602 Return hotkeys
10603 }
10604
10605 Gui, ShowHotkeys:Color, ffffff, ffffff
10606 Gui, ShowHotkeys:Add, Text, , List of this scripts assigned hotkeys.
10607 Gui, ShowHotkeys:Default
10608 Gui, ShowHotkeys:Font, , Courier New
10609 Gui, ShowHotkeys:Font, , Consolas
10610 Gui, ShowHotkeys:Add, ListView, r25 w800 NoSortHdr Grid ReadOnly, Type | Enabled | Level | Running | Key combination (Code) | Key combination (ENG name)
10611 For key, val in hotkeys {
10612 If (key != 1) {
10613 LV_Add("", val*)
10614 LV_ModifyCol()
10615 }
10616 }
10617
10618 i := 0
10619 Loop % LV_GetCount("Column")
10620 {
10621 i++
10622 LV_ModifyCol(a_index,"AutoHdr")
10623 }
10624
10625 text := "reg: The hotkey is implemented via the operating system's RegisterHotkey() function." . "`n"
10626 text .= "reg(no): Same as above except that this hotkey is inactive (due to being unsupported, disabled, or suspended)." . "`n"
10627 text .= "k-hook: The hotkey is implemented via the keyboard hook." . "`n"
10628 text .= "m-hook: The hotkey is implemented via the mouse hook." . "`n"
10629 text .= "2-hooks: The hotkey requires both the hooks mentioned above." . "`n"
10630 text .= "joypoll: The hotkey is implemented by polling the joystick at regular intervals." . "`n"
10631 text .= "`n"
10632 text .= "Enabled: Hotkey is assigned but enabled/disabled [on/off] via the Hotkey command." . "`n"
10633
10634 Gui, ShowHotkeys:Add, Text, , % text
10635
10636 Gui, ShowHotkeys:Show, w820 xCenter yCenter, Assigned Hotkeys
10637 Gui, SettingsUI:Default
10638 Gui, Font
10639}
10640
10641CloseScripts() {
10642 ; Close all active scripts listed in Globals.Get("ScriptList").
10643 ; Can be used with scripts extending/including ItemInfo (TradeMacro for example) by adding to/altering this list.
10644 ; Shortcut is placed in AdditionalMacros.txt
10645
10646 scripts := Globals.Get("ScriptList")
10647 currentScript := A_ScriptDir . "\" . A_ScriptName
10648 SplitPath, currentScript, , , ext, currentscript_name_no_ext
10649 currentScript := A_ScriptDir . "\" . currentscript_name_no_ext
10650
10651 DetectHiddenWindows, On
10652
10653 Loop, % scripts.Length() {
10654 scriptPath := scripts[A_Index]
10655
10656 ; close current script last (with ExitApp)
10657 If (currentScript != scriptPath) {
10658 WinClose, %scriptPath% ahk_class AutoHotkey
10659 }
10660 }
10661 ExitApp
10662}
10663
10664ColorBlindSupport() {
10665 IfWinActive, ahk_group PoEWindowGrp
10666 {
10667 Global Item, Opts, Globals, ItemData
10668
10669 ClipBoardTemp := ClipboardAll
10670 SuspendPOEItemScript = 1 ; This allows us to handle the clipboard change event
10671
10672 scancode_c := Globals.Get("ScanCodes").c
10673
10674 ; Parse the clipboard contents twice.
10675 ; If the clipboard contains valid item data before we send ctrl + c to try and parse an item via ctrl + f then don't restore that clipboard data later on.
10676 ; This prevents the highlighting function to fill search fields with data from previous item parsings/manual data copying since
10677 ; that clipboard data would always be restored again.
10678 Loop, 2 {
10679 If (A_Index = 2) {
10680 Clipboard :=
10681 Send ^{%scancode_c%} ; ^{c}
10682 Sleep 100
10683 }
10684 CBContents := GetClipboardContents()
10685 CBContents := PreProcessContents(CBContents)
10686 Globals.Set("ItemText", CBContents)
10687 ParsedData := ParseItemData(CBContents)
10688 If (A_Index = 1 and Item.Name) {
10689 dontRestoreClipboard := true
10690 }
10691 }
10692
10693 If (Item.Name) {
10694 Sleep, 100
10695 If (!dontRestoreClipboard) {
10696 Clipboard := ClipBoardTemp
10697 }
10698
10699 If (Item.IsGem) {
10700 ShowToolTip("Gem color: " Item.GemColor)
10701 }
10702 Else If (Item.Sockets > 0) {
10703 sockets := Item.Sockets
10704
10705 groups := StrSplit(Trim(Item.SocketString), "")
10706 groups[2] := groups[2] = "-" ? "--" : " "
10707 groups[6] := groups[6] = "-" ? "--" : " "
10708 groups[10] := groups[10] = "-" ? "--" : " "
10709
10710 str := " `n"
10711 If (sockets <= 2) {
10712 If (sockets = 1) {
10713 str .= " " groups[1] " "
10714 } Else {
10715 str .= " " groups[1] " " groups[2] " " groups[3] " "
10716 }
10717 ShowToolTip(str "`n ")
10718 }
10719 Else If (sockets <= 4) {
10720 groups[7] := StrLen(groups[7]) ? groups[7] : " "
10721 groups[6] := StrLen(groups[6]) ? groups[6] : " "
10722
10723 str .= " " groups[1] " " groups[2] " " groups[3] " `n"
10724 str .= (groups[4] = "-") ? " |" : " "
10725 str .= "`n " groups[7] " " groups[6] " " groups[5] " "
10726
10727 ShowToolTip(str "`n ")
10728 }
10729 Else {
10730 groups[11] := StrLen(groups[11]) ? groups[11] : " "
10731 groups[9] := StrLen(groups[9]) ? groups[9] : " "
10732
10733 str .= " " groups[1] " " groups[2] " " groups[3] " `n"
10734 str .= (groups[4] = "-") ? " |" : " "
10735 str .= "`n " groups[7] " " groups[6] " " groups[5] " `n"
10736 str .= (groups[8] = "-") ? " | " : " "
10737 str .= "`n " groups[9] " " groups[10] " " groups[11] " "
10738
10739 ShowToolTip(str "`n ")
10740
10741 }
10742 }
10743 }
10744
10745 SuspendPOEItemScript = 0 ; Allow Item info to handle clipboard change event
10746 }
10747}
10748
10749HighlightItems(broadTerms = false, leaveSearchField = true, focusHideoutFilter = false, hideoutFieldX = 0, hideoutFieldY = 0) {
10750 ; Highlights items via stash search (also in vendor and hideout search)
10751 IfWinActive, ahk_group PoEWindowGrp
10752 {
10753 Global Item, Opts, Globals, ItemData
10754
10755 ClipBoardTemp := ClipboardAll
10756 SuspendPOEItemScript = 1 ; This allows us to handle the clipboard change event
10757
10758 scancode_c := Globals.Get("ScanCodes").c
10759 scancode_v := Globals.Get("ScanCodes").v
10760 scancode_a := Globals.Get("ScanCodes").a
10761 scancode_f := Globals.Get("ScanCodes").f
10762 scancode_enter := Globals.Get("ScanCodes").enter
10763
10764 ; Parse the clipboard contents twice.
10765 ; If the clipboard contains valid item data before we send ctrl + c to try and parse an item via ctrl + f then don't restore that clipboard data later on.
10766 ; This prevents the highlighting function to fill search fields with data from previous item parsings/manual data copying since
10767 ; that clipboard data would always be restored again.
10768 Loop, 2 {
10769 If (A_Index = 2) {
10770 Clipboard :=
10771 Send ^{%scancode_c%} ; ^{c}
10772 Sleep 100
10773 }
10774 CBContents := GetClipboardContents()
10775 CBContents := PreProcessContents(CBContents)
10776 Globals.Set("ItemText", CBContents)
10777 ParsedData := ParseItemData(CBContents)
10778 If (A_Index = 1 and Item.Name) {
10779 dontRestoreClipboard := true
10780 }
10781 }
10782
10783 If (Item.Name) {
10784 rarity := ""
10785 If (Item.RarityLevel = 2) {
10786 rarity := "magic"
10787 } Else If (Item.RarityLevel = 3) {
10788 rarity := "rare"
10789 } Else If (Item.RarityLevel = 4) {
10790 rarity := "unique"
10791 }
10792
10793 terms := []
10794 ; uniques / gems / div cards
10795 If (Item.IsUnique or Item.IsGem or Item.IsDivinationCard) {
10796 If (broadTerms) {
10797 If (Item.IsUnique) {
10798 terms.push("Rarity: Unique")
10799 } Else {
10800 terms.push("Rarity: " Item.BaseType)
10801 }
10802 } Else {
10803 If (Item.IsUnique) {
10804 terms.push("Rarity: Unique")
10805 } Else {
10806 terms.push("Rarity: " Item.BaseType)
10807 }
10808 terms.push(Item.Name)
10809 }
10810 }
10811 ; prophecies
10812 Else If (Item.IsProphecy) {
10813 If (broadTerms) {
10814 terms.push("this prophecy")
10815 } Else {
10816 terms.push("this prophecy")
10817 terms.push(Item.Name)
10818 }
10819 }
10820 ; essences
10821 Else If (Item.IsEssence) {
10822 If (broadTerms) {
10823 terms.push("Rarity: Currency")
10824 terms.push("Essence")
10825 } Else {
10826 terms.push(Item.Name)
10827 }
10828 }
10829 ; currency
10830 Else If (Item.IsCurrency) {
10831 If (broadTerms) {
10832 terms.push("Currency")
10833 } Else {
10834 terms.push(Item.Name)
10835 }
10836 }
10837 ; maps
10838 Else If (Item.IsMap) {
10839 If (broadTerms) {
10840 terms.push(" Map")
10841 } Else {
10842 terms.push(Item.SubType)
10843 terms.push("tier:" Item.MapTier)
10844 }
10845 }
10846 ; flasks
10847 Else If (Item.IsFlask) {
10848 If (broadTerms) {
10849 terms.push("Consumes")
10850 terms.push(Item.SubType)
10851 } Else {
10852 terms.push(Item.BaseName)
10853 }
10854 }
10855 ; leaguestones and Scarabs
10856 Else If (Item.IsLeaguestone or Item.IsScarab) {
10857 If (broadTerms) {
10858 terms.push(Item.BaseType)
10859 } Else {
10860 terms.push(Item.SubType)
10861 }
10862 }
10863 ; jewels
10864 Else If (Item.IsJewel) {
10865 If (broadTerms) {
10866 terms.push(Item.BaseType)
10867 } Else {
10868 If (Item.BaseName) {
10869 terms.push(Item.BaseName)
10870 } Else {
10871 terms.push("Jewel")
10872 }
10873 terms.push(rarity)
10874 }
10875 }
10876 ; offerings / sacrifice and mortal fragments / guardian fragments / council keys / breachstones / reliquary keys
10877 Else If (RegExMatch(Item.Name, "i)Sacrifice At") or RegExMatch(Item.Name, "i)Fragment of") or RegExMatch(Item.Name, "i)Mortal ") or RegExMatch(Item.Name, "i)Offering to ") or RegExMatch(Item.Name, "i)'s Key") or RegExMatch(Item.Name, "i)Breachstone") or RegExMatch(Item.Name, "i)Reliquary Key")) {
10878 If (broadTerms) {
10879 tmpName := RegExReplace(Item.Name, "i)(Sacrifice At).*|(Fragment of).*|(Mortal).*|.*('s Key)|.*(Breachstone)|(Reliquary Key)", "$1$2$3$4$5$6")
10880 terms.push(tmpName)
10881 } Else {
10882 terms.push(Item.Name)
10883 }
10884 }
10885 ; other items (weapons, armour pieces, jewelry etc)
10886 Else {
10887 If (broadTerms) {
10888 If (Item.IsWeapon or Item.IsAmulet or Item.IsRing or Item.IsBelt or InStr(Item.SubType, "Shield")) {
10889 ; add the term "Chance to Block" to remove items with "Energy Shield" from "Shield" searches
10890 If (InStr(Item.SubType, "Shield")) {
10891 terms.push("Chance to Block")
10892 }
10893
10894 ; add grip type to differentiate 1 and 2 handed weapons
10895 If (Item.GripType == "1H" and RegExMatch(Item.Subtype, "i)Sword|Mace|Axe")) {
10896 prefix := "One Handed"
10897 } Else If (Item.GripType == "2H") {
10898 prefix := "Two Handed"
10899 }
10900
10901 ; Handle Talismans, they have SubType "Amulet" but this won't be found ingame.
10902 If (Item.IsTalisman) {
10903 term := "Talisman Tier:"
10904 } Else {
10905 ; add a space since all these terms have a preceding one, this reduces the chance of accidental matches
10906 ; for example "Ring" found in "Voidbringers" or "during Flask effect"
10907 term := " " Item.SubType
10908 }
10909
10910 terms.push(prefix . term)
10911 }
10912 ; armour pieces are a bit special, the ingame information doesn't include "armour/body armour" or something alike.
10913 ; we can use the item defences though to match armour pieces with the same defence types (can't differentiate between "Body Armour" and "Helmet").
10914 Else If (InStr(Item.BaseType, "Armour")) {
10915 For key, val in ItemData.Parts {
10916 If (RegExMatch(val, "i)(Energy Shield:)|(Armour:)|(Evasion Rating:)", match)) {
10917 Loop, 3 {
10918 If (StrLen(match%A_Index%)) {
10919 terms.push(match%A_Index%)
10920 }
10921 }
10922 }
10923 }
10924 }
10925 } Else {
10926 If (Item.BaseName) {
10927 terms.push(Item.BaseName)
10928 } Else {
10929 terms.push(Trim(RegExReplace(Item.Name, "Superior")))
10930 }
10931 }
10932 }
10933 }
10934
10935 If (terms.length() > 0) {
10936 focusHideoutFilter := true
10937 If (Item.IsHideoutObject and focusHideoutFilter) {
10938 CoordMode, Mouse, Relative
10939 MouseGetPos, currentX, currentY
10940 MouseMove, %hideoutFieldX%, %hideoutFieldY%, 0
10941 Sleep, 10
10942 MouseClick
10943 Sleep, 50
10944 MouseMove, %currentX%, %currentY%, 0
10945 Sleep, 10
10946 SendInput ^{%scancode_a%}
10947 } Else {
10948 SendInput ^{%scancode_f%} ; sc021 = f
10949 }
10950
10951 searchText =
10952 For key, val in terms {
10953 If (not Item.IsHideoutObject) {
10954 searchText = %searchText% "%val%"
10955 } Else {
10956 ; hideout objects shouldn't use quotation marks
10957 searchText = %searchText% %val%
10958 }
10959 }
10960
10961 ; search fields have character limits
10962 ; stash search field := 50 chars , we have to close the last term with a quotation mark
10963 ; hideout mtx search field := 23 chars
10964 charLimit := Item.IsHideoutObject ? 23 : 50
10965
10966 If (StrLen(searchText) > charLimit) {
10967 newString := SubStr(searchText, 1, charLimit)
10968
10969 temp := RegExReplace(newString, "i)""", Replacement = "", QuotationMarks)
10970 ; make sure we have an equal amount of quotation marks (all terms properly enclosed)
10971 If (QuotationMarks&1) {
10972 searchText := RegExReplace(newString, "i).$", """")
10973 } Else {
10974 searchText := newString
10975 }
10976 }
10977
10978 Clipboard := searchText
10979
10980 Sleep 10
10981 SendEvent ^{%scancode_v%} ; ctrl + v
10982
10983 If (not (Item.IsHideoutObject and focusHideoutFilter)) {
10984 If (leaveSearchField) {
10985 SendInput {%scancode_enter%} ; enter
10986 } Else {
10987 SendInput ^{%scancode_a%} ; ctrl + a
10988 }
10989 }
10990 } Else {
10991 SendInput ^{%scancode_f%} ; send ctrl + f in case we don't have information to input
10992 }
10993
10994 Sleep, 500
10995 If (!dontRestoreClipboard) {
10996 Clipboard := ClipBoardTemp
10997 }
10998 SuspendPOEItemScript = 0 ; Allow Item info to handle clipboard change event
10999 }
11000}
11001
11002AdvancedItemInfoExt() {
11003 IfWinActive, ahk_group PoEWindowGrp
11004 {
11005 Global Item, Opts, Globals, ItemData
11006
11007 ClipBoardTemp := ClipboardAll
11008 SuspendPOEItemScript = 1 ; This allows us to handle the clipboard change event
11009
11010 Clipboard :=
11011 scancode_c := Globals.Get("Scancodes").c
11012 Send ^{%scancode_c%} ; ^{c}
11013 Sleep 100
11014
11015 CBContents := GetClipboardContents()
11016 CBContents := PreProcessContents(CBContents)
11017 Globals.Set("ItemText", CBContents)
11018 ParsedData := ParseItemData(CBContents)
11019
11020 If (Item.Name) {
11021 url := "http://pathof.info/?item=" StringToBase64UriEncoded(CBContents)
11022 openWith := AssociatedProgram("html")
11023 OpenWebPageWith(openWith, Url)
11024 }
11025 SuspendPOEItemScript = 0
11026 }
11027}
11028
11029OpenItemOnPoEAntiquary() {
11030 IfWinActive, ahk_group PoEWindowGrp
11031 {
11032 Global Item, Opts, Globals, ItemData
11033
11034 ClipBoardTemp := ClipboardAll
11035 SuspendPOEItemScript = 1 ; This allows us to handle the clipboard change event
11036
11037 Clipboard :=
11038 scancode_c := Globals.Get("Scancodes").c
11039 Send ^{%scancode_c%} ; ^{c}
11040 Sleep 100
11041
11042 CBContents := GetClipboardContents()
11043 CBContents := PreProcessContents(CBContents)
11044 Globals.Set("ItemText", CBContents)
11045 ParsedData := ParseItemData(CBContents)
11046
11047 If (Item.Name) {
11048 global AntiquaryData := []
11049 global AntiquaryType := AntiquaryGetType(Item)
11050
11051 If (AntiquaryType) {
11052 If (AntiquaryType = "Map") {
11053 name := Item.BaseName
11054 } Else {
11055 name := Item.Name
11056 }
11057
11058 url := "http://poe-antiquary.xyz/api/macro/" UriEncode(AntiquaryType) "/" UriEncode(Item.Name)
11059
11060 postData := ""
11061 options := "RequestType: GET"
11062 options .= "`n" "TimeOut: 15"
11063 reqHeaders := []
11064
11065 reqHeaders.push("Connection: keep-alive")
11066 reqHeaders.push("Cache-Control: max-age=0")
11067 reqHeaders.push("Upgrade-Insecure-Requests: 1")
11068 reqHeaders.push("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")
11069
11070 data := PoEScripts_Download(url, postData, reqHeaders, "", true)
11071
11072 Try {
11073 AntiquaryData := JSON.Load(data)
11074 } Catch error {
11075 errorMsg := error.Message
11076 Msgbox, %errorMsg%
11077 }
11078
11079 name := AntiquaryData["name"]
11080 lastLeague := AntiquaryData["league"]
11081 itemType := AntiquaryData["itemType"]
11082 items := AntiquaryData.items
11083 length := items.Length()
11084
11085 If (length == 0) {
11086 ShowToolTip("Item not available on http://poe-antiquary.xyz.")
11087 }
11088 Else If (length == 1) {
11089 id := items[1].id
11090 AntiquaryOpenInBrowser(itemType, name, id, lastLeague)
11091 }
11092 Else If (length > 1) {
11093 AntiquaryOpenInBrowser(itemType, name, id, lastLeague, length)
11094 }
11095 }
11096 }
11097 Else {
11098 ShowToolTip("Item parsing failed, no name recognized.")
11099 }
11100 SuspendPOEItemScript = 0
11101 }
11102}
11103
11104AntiquaryOpenInBrowser(type, name, id, lastLeague, multiItems = false) {
11105 league := TradeGlobals.Get("LeagueName")
11106 If (RegExMatch(league, "Hardcore.*")) {
11107 league := lastLeague " HC"
11108 } Else {
11109 league := lastLeague
11110 }
11111
11112 league := UriEncode(league)
11113 type := UriEncode(type)
11114 name := UriEncode(name)
11115 id := UriEncode(id)
11116 utm := UriEncode("trade macro")
11117
11118 If (multiItems) {
11119 url := "http://poe-antiquary.xyz/" league "/" type "?name=" name ;"?utm_source=" utm "&utm_medium=" utm "&utm_campaign=" utm
11120 }
11121 Else {
11122 url := "http://poe-antiquary.xyz/" league "/" type "/" name "/" id ;"?utm_source=" utm "&utm_medium=" utm "&utm_campaign=" utm
11123 }
11124 openWith := AssociatedProgram("html")
11125 OpenWebPageWith(openWith, url)
11126}
11127
11128AntiquaryGetType(Item) {
11129 If (Item.IsUnique) {
11130 If (Item.IsWeapon) {
11131 return "Weapon"
11132 }
11133 If (Item.IsArmour) {
11134 return "Armour"
11135 }
11136 If (Item.IsFlask) {
11137 return "Flask"
11138 }
11139 If (Item.IsJewel) {
11140 return "Jewel"
11141 }
11142 If (Item.IsBelt or Item.IsRing or Item.IsAmulet) {
11143 return "Accessory"
11144 }
11145 }
11146 If (Item.IsEssence) {
11147 return "Essence"
11148 }
11149 If (Item.IsDivinationCard) {
11150 return "Divination"
11151 }
11152 If (Item.IsProphecy) {
11153 return "Prophecy"
11154 }
11155 If (Item.IsMapFragment) {
11156 return "Fragment"
11157 }
11158 If (Item.IsMap) {
11159 If (Item.IsUnique) {
11160 return "Unique Map"
11161 } Else {
11162 return "Map"
11163 }
11164 }
11165 If (RegExMatch(Item.Name, "(Sacrifice|Mortal|Fragment).*|Offering to the Goddess|Divine Vesse|.*(Breachstone|s Key)")) {
11166 return "Fragment"
11167 }
11168 If (Item.IsCurrency) {
11169 return "Currency"
11170 }
11171}
11172
11173
11174StringToBase64UriEncoded(stringIn, noUriEncode = false, ByRef errorMessage = "") {
11175 FileDelete, %A_ScriptDir%\temp\itemText.txt
11176 FileDelete, %A_ScriptDir%\temp\base64Itemtext.txt
11177 FileDelete, %A_ScriptDir%\temp\encodeToBase64.txt
11178
11179 encodeError1 := ""
11180 encodeError2 := ""
11181 stringBase64 := b64Encode(stringIn, encodeError1)
11182
11183 If (not StrLen(stringBase64)) {
11184 FileAppend, %stringIn%, %A_ScriptDir%\temp\itemText.txt, utf-8
11185 command := "certutil -encode -f ""%cd%\temp\itemText.txt"" ""%cd%\temp\base64ItemText.txt"" & type ""%cd%\temp\base64ItemText.txt"""
11186 stringBase64 := ReadConsoleOutputFromFile(command, "encodeToBase64.txt", encodeError2)
11187 stringBase64 := Trim(RegExReplace(stringBase64, "i)-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----|77u/", ""))
11188 }
11189
11190 If (not StrLen(stringBase64)) {
11191 errorMessage := ""
11192 If (StrLen(encodeError1)) {
11193 errorMessage .= encodeError1 " "
11194 }
11195 If (StrLen(encodeError2)) {
11196 errorMessage .= "Encoding via certutil returned: " encodeError2
11197 }
11198 }
11199
11200 If (not noUriEncode) {
11201 stringBase64 := UriEncode(stringBase64)
11202 stringBase64 := RegExReplace(stringBase64, "i)^(%0D)?(%0A)?|((%0D)?(%0A)?)+$", "")
11203 } Else {
11204 stringBase64 := RegExReplace(stringBase64, "i)\r|\n", "")
11205 }
11206
11207 Return stringBase64
11208}
11209
11210/*
11211 Base64 Encode / Decode a string (binary-to-text encoding)
11212 https://github.com/jNizM/AHK_Scripts/blob/master/src/encoding_decoding/base64.ahk
11213
11214 Alternative: https://github.com/cocobelgica/AutoHotkey-Util/blob/master/Base64.ahk
11215*/
11216b64Encode(string, ByRef error = "") {
11217 VarSetCapacity(bin, StrPut(string, "UTF-8")) && len := StrPut(string, &bin, "UTF-8") - 1
11218 If !(DllCall("crypt32\CryptBinaryToString", "ptr", &bin, "uint", len, "uint", 0x1, "ptr", 0, "uint*", size)) {
11219 ;throw Exception("CryptBinaryToString failed", -1)
11220 error := "Exception (1) while encoding string to base64."
11221 }
11222 VarSetCapacity(buf, size << 1, 0)
11223 If !(DllCall("crypt32\CryptBinaryToString", "ptr", &bin, "uint", len, "uint", 0x1, "ptr", &buf, "uint*", size)) {
11224 ;throw Exception("CryptBinaryToString failed", -1)
11225 error := "Exception (2) while encoding string to base64."
11226 }
11227
11228 If (not StrLen(Error)) {
11229 Return StrGet(&buf)
11230 } Else {
11231 Return ""
11232 }
11233}
11234
11235b64Decode(string)
11236{
11237 If !(DllCall("crypt32\CryptStringToBinary", "ptr", &string, "uint", 0, "uint", 0x1, "ptr", 0, "uint*", size, "ptr", 0, "ptr", 0))
11238 throw Exception("CryptStringToBinary failed", -1)
11239 VarSetCapacity(buf, size, 0)
11240 If !(DllCall("crypt32\CryptStringToBinary", "ptr", &string, "uint", 0, "uint", 0x1, "ptr", &buf, "uint*", size, "ptr", 0, "ptr", 0))
11241 throw Exception("CryptStringToBinary failed", -1)
11242 return StrGet(&buf, size, "UTF-8")
11243}
11244
11245OpenWebPageWith(application, url) {
11246 If (InStr(application, "iexplore")) {
11247 ie := ComObjCreate("InternetExplorer.Application")
11248 ie.Visible:=True
11249 ie.Navigate(url)
11250 } Else If (InStr(application, "launchwinapp")) {
11251 ; Microsoft Edge
11252 Run, %comspec% /c "chcp 1251 & start microsoft-edge:%Url%", , Hide
11253 } Else {
11254 args := ""
11255 If (StrLen(application)) {
11256 args := "-new-tab"
11257 Try {
11258 Run, "%application%" %args% "%Url%"
11259 } Catch e {
11260 Run, "%application%" "%Url%"
11261 }
11262 } Else {
11263 Run %Url%
11264 }
11265 }
11266 Return
11267}
11268
11269LookUpAffixes() {
11270 /*
11271 Opens item base on poeaffix.net
11272 */
11273 IfWinActive, ahk_group PoEWindowGrp
11274 {
11275 Global Item, Opts, Globals, ItemData
11276
11277 ClipBoardTemp := ClipboardAll
11278 SuspendPOEItemScript = 1 ; This allows us to handle the clipboard change event
11279
11280 Clipboard :=
11281 scancode_c := Globals.Get("Scancodes").c
11282 Send ^{%scancode_c%} ; ^{c}
11283 Sleep 100
11284
11285 CBContents := GetClipboardContents()
11286 CBContents := PreProcessContents(CBContents)
11287 Globals.Set("ItemText", CBContents)
11288 ParsedData := ParseItemData(CBContents)
11289 If (Item.Name) {
11290 dontRestoreClipboard := true
11291 }
11292
11293 If (Item.Name) {
11294 url := "http://poeaffix.net/"
11295 If (RegExMatch(Item.BaseName, "i)Sacrificial Garb")) {
11296 url .= "ch-garb" ; ".html"
11297 } Else {
11298 ev := RegExMatch(ItemData.Stats, "i)Evasion Rating") ? "ev" : ""
11299 ar := RegExMatch(ItemData.Stats, "i)Armour") ? "ar" : ""
11300 es := RegExMatch(ItemData.Stats, "i)Energy Shield") ? "es" : ""
11301 RegExMatch(Item.SubType, "i)Axe|Sword|Mace|Sceptre|Bow|Staff|Wand|Fish|Dagger", weapon)
11302 RegExMatch(Item.Subtype, "i)Amulet|Ring|Belt|Quiver|Flask", accessory)
11303 RegExMatch(Item.Subtype, "i)Cobalt|Viridian|Crimson", jewel)
11304
11305 suffix := ar . ev . es . weapon . accessory . jewel
11306 StringLower, suffix, suffix
11307
11308 boots := RegExMatch(Item.Subtype, "i)Boots") ? "bt" : ""
11309 chest := RegExMatch(Item.Subtype, "i)BodyArmour") ? "ch" : ""
11310 gloves := RegExMatch(Item.Subtype, "i)Gloves") ? "gl" : ""
11311 helmet := RegExMatch(Item.Subtype, "i)Helmet") ? "hm" : ""
11312 shield := RegExMatch(Item.Subtype, "i)Shield") ? "sh" : ""
11313 ac := StrLen(accessory) ? "ac" : ""
11314 jw := StrLen(jewel) ? "jw" : ""
11315 gripType := Item.GripType != "None" ? Item.GripType : ""
11316
11317 prefix := boots . chest . gloves . helmet . shield . gripType . ac . jw
11318 StringLower, prefix, prefix
11319
11320 url .= prefix "-" suffix ; ".html"
11321 }
11322 openWith := AssociatedProgram("html")
11323 OpenWebPageWith(openWith, Url)
11324 }
11325
11326 Sleep, 10
11327 If (!dontRestoreClipboard) {
11328 Clipboard := ClipBoardTemp
11329 }
11330 SuspendPOEItemScript = 0 ; Allow Item info to handle clipboard change event
11331 }
11332}
11333
11334; ########### TIMERS ############
11335
11336; Tick every 100 ms
11337; Remove tooltip if mouse is moved or x seconds pass
11338ToolTipTimer:
11339 Global Opts, ToolTipTimeout, gdipTooltip
11340 ToolTipTimeout += 1
11341 MouseGetPos, CurrX, CurrY
11342 MouseMoved := (CurrX - X) ** 2 + (CurrY - Y) ** 2 > Opts.MouseMoveThreshold ** 2
11343 If (MouseMoved or ((UseTooltipTimeout == 1) and (ToolTipTimeout >= Opts.ToolTipTimeoutSeconds*10)))
11344 {
11345 SetTimer, ToolTipTimer, Off
11346 If (Opts.UseGDI or gdipTooltip.GetVisibility())
11347 {
11348 gdipTooltip.HideGdiTooltip(true)
11349 }
11350 Else
11351 {
11352 ToolTip
11353 }
11354
11355 ; close item filter nameplate
11356 fullScriptPath := A_ScriptDir "\lib\PoEScripts_ItemFilterNamePlate.ahk"
11357 DetectHiddenWindows, On
11358 WinClose, %fullScriptPath% ahk_class AutoHotkey
11359 WinKill, %fullScriptPath% ahk_class AutoHotkey
11360 }
11361 return
11362
11363OnClipBoardChange:
11364 Global Opts
11365 IF SuspendPOEItemScript = 0
11366 {
11367 If (Opts.OnlyActiveIfPOEIsFront)
11368 {
11369 ; do nothing if Path of Exile isn't the foremost window
11370 IfWinActive, ahk_group PoEWindowGrp
11371 {
11372 ParseClipBoardChanges()
11373 }
11374 }
11375 Else
11376 {
11377 ; if running tests parse clipboard regardless if PoE is foremost
11378 ; so we can check individual cases from test case text files
11379 ParseClipBoardChanges()
11380 }
11381 }
11382 return
11383
11384ShowUpdateNotes:
11385 ShowUpdateNotes()
11386 return
11387
11388ShowTranslationUI:
11389 ShowTranslationUI()
11390 return
11391
11392TranslationUI_BtnTranslate:
11393 Gui, Translate:Submit, NoHide
11394 GuiControlGet, cbTransData, , TranslationEditInput
11395 CBContents := PreProcessContents(cbTransData)
11396 CBContents := PoEScripts_TranslateItemData(CBContents, translationData, currentLocale, retObj, retCode)
11397 GuiControl, Translate:, TranslationEditOutput, % CBContents
11398 GuiControl, Translate:, TranslationEditOutputDebug, % DebugPrintarray(retObj, 0)
11399 CBContents :=
11400 return
11401
11402TranslationUI_BtnCancel:
11403 Gui, Translate:Destroy
11404 return
11405
11406TranslationUI_BtnCopyToClipboard:
11407 Gui, Translate:Submit, NoHide
11408 SuspendPOEItemScript = 1
11409 GuiControlGet, cbTransData, , TranslationEditOutput
11410 Clipboard := cbTransData
11411 SuspendPOEItemScript = 0
11412 return
11413
11414ShowTranslationUI() {
11415 Global
11416
11417 Gui, Translate:Destroy
11418 Gui, Translate:Color, ffffff, ffffff
11419
11420 Gui, Translate:Margin, 10 , 10
11421 Gui, Translate:Add, Text, , Add your copied item information to translate it to english. The rightmost column shows some debug information.
11422
11423 TransGuiWidth := 1300
11424 TransGuiHeight := 750
11425 TransEditWidth := 375
11426 TransEditDebugWidth := 500
11427 TransEditHeight := (TransGuiHeight - 115)
11428 TransGuiSecondColumnPosX := TransEditWidth + 30
11429 TransGuiCopyButtonPosX := TransGuiWidth - 130 - 500
11430 TransGuiTransButtonPosX := TransEditWidth + 10 - 100
11431 TransGuiCloseButtonPosX := TransGuiWidth - 110
11432
11433 Gui, Translate:Font, bold, Tahoma
11434 Gui, Translate:Add, Text, y+20, Add item text (copied ingame via ctrl + c)
11435 Gui, Translate:Font
11436 Gui, Translate:Add, Button, yp-5 w100 x%TransGuiTransButtonPosX% gTranslationUI_BtnTranslate, Translate
11437 Gui, Translate:Font, bold, Tahoma
11438 Gui, Translate:Add, Text, yp+5 x%TransGuiSecondColumnPosX%, Translated text
11439 Gui, Translate:Font
11440 Gui, Translate:Add, Button, yp-5 w100 x%TransGuiCopyButtonPosX% gTranslationUI_BtnCopyToClipboard, Copy (Clipboard)
11441 Gui, Translate:Font, , Consolas
11442 Gui, Translate:Add, Edit, w%TransEditWidth% h%TransEditHeight% y+5 x10 HScroll vTranslationEditInput hwndTransateEditHwnd,
11443 Gui, Translate:Add, Edit, w%TransEditWidth% h%TransEditHeight% yp+0 x+20 HScroll vTranslationEditOutput ReadOnly,
11444 Gui, Translate:Add, Edit, w%TransEditDebugWidth% h%TransEditHeight% yp+0 x+10 HScroll vTranslationEditOutputDebug ReadOnly,
11445 Gui, Translate:Font
11446
11447 Gui, Translate:Add, Button, x%TransGuiCloseButtonPosX% y+15 w100 gTranslationUI_BtnCancel, Close
11448
11449 ControlFocus, , ahk_id %TransateEditHwnd%
11450 Gui, Translate:Show, w%TransGuiWidth% h%TransGuiHeight%, Translate Item Data
11451}
11452
11453ChangedUserFilesWindow_Cancel:
11454 Gui, ChangedUserFiles:Cancel
11455 return
11456
11457ChangedUserFilesWindow_OpenFolder:
11458 Gui, ChangedUserFiles:Cancel
11459 GoSub, EditOpenUserSettings
11460 return
11461
11462LV_HotkeyEdit:
11463 If (not RegexMatch(A_GuiControlEvent, "Normal|DoubleClick")) {
11464 Return
11465 }
11466 If (RegExMatch(A_GuiControl, "i).*_Trigger$")) {
11467 _LVID := RegExReplace(A_GuiControl, "i)(.*)_Trigger$", "$1")
11468 } Else {
11469 _LVID := A_GuiControl
11470 }
11471
11472 ; check keyboard layout = eng_US and switch to if not, quick and dirty workaround
11473 ; to support non-latin layouts (russian etc)
11474 ; TODO: remove later
11475 _ENG_US := 0x4090409
11476 _Defaultlayout := GetCurrentLayout()
11477 If (GetKeySC("d") = 0 and _Defaultlayout != _ENG_US) {
11478 ;If (_Defaultlayout != _ENG_US) {
11479 console.log("change layout")
11480 SwitchLayoutStart := A_TickCount
11481 SwitchLayoutElapsed := 0
11482 While (GetCurrentLayout() != _ENG_US and SwitchLayoutElapsed < 120) {
11483 SwitchLayout(_ENG_US)
11484 Sleep, 50
11485 SwitchLayoutElapsed := (A_TickCount - SwitchLayoutStart) / 1000
11486 }
11487 _switched := (GetCurrentLayout() != _Defaultlayout) ? true : false
11488 }
11489
11490 Gui, ListView, %_LVID%
11491 LV_GetText(_oV, 1, 2)
11492
11493 _prompt := "Please hold down the keys or mouse buttons you want to turn into a hotkey:"
11494 _note := "The majority of common keys from latin keyboard layouts should work."
11495 If (_switched) {
11496 _note := "`n`n" . "Forcibly switched to en_US layout because your layout seems to be unsupported."
11497 }
11498 _nV := Hotkey("+Tooltips", _prompt, _note, "Choose a Hotkey")
11499
11500 ; TODO: remove later
11501 If (_switched) {
11502 SwitchLayout(_Defaultlayout)
11503 console.log("changed layout back")
11504 }
11505
11506 LV_Delete(1)
11507 If (not StrLen(_nV)) {
11508 _nV := _oV
11509 }
11510 LV_Add("","",_nV)
11511Return
11512
11513ShowSettingsUI:
11514 ReadConfig()
11515 Sleep, 50
11516 UpdateSettingsUI()
11517 Sleep, 50
11518 ShowSettingsUI()
11519 return
11520
11521SettingsUI_BtnOK:
11522 Global Opts
11523 Gui, SettingsUI:Submit
11524 Sleep, 50
11525 WriteConfig()
11526 AM_WriteConfig()
11527 UpdateSettingsUI()
11528 Fonts.SetFixedFont(GuiGet("FontSize", Opts.FontSize))
11529 return
11530
11531SettingsUI_BtnCancel:
11532 Gui, SettingsUI:Cancel
11533 return
11534
11535SettingsUI_BtnDefaults:
11536 Gui, SettingsUI:Cancel
11537 RemoveConfig()
11538 Sleep, 75
11539 CopyDefaultConfig()
11540 Sleep, 75
11541 ReadConfig()
11542 Sleep, 75
11543 UpdateSettingsUI()
11544 ShowSettingsUI()
11545 return
11546
11547SettingsUI_AM_BtnDefaults:
11548 Gui, SettingsUI:Cancel
11549 RemoveConfig("AdditionalMacros.ini")
11550 Sleep, 75
11551 CopyDefaultConfig("AdditionalMacros.ini")
11552 Sleep, 75
11553 AM_ReadConfig(AM_Config)
11554 Sleep, 75
11555 UpdateSettingsUI()
11556 ShowSettingsUI()
11557 AM_SetHotkeys()
11558 Return
11559
11560InitGDITooltip:
11561 Global Opts
11562 ; some users experience FPS drops when gdi tooltip is initialized.
11563 If (Opts.UseGDI) {
11564 global gdipTooltip = new GdipTooltip(2, 8,,,[Opts.GDIWindowOpacity, Opts.GDIWindowColor, 10],[Opts.GDIBorderOpacity, Opts.GDIBorderColor, 10],[Opts.GDITextOpacity, Opts.GDITextColor, 10],true, Opts.RenderingFix, -0.3)
11565 }
11566 return
11567
11568
11569OpenGDIColorPicker(type, rgb, opacity, title, image) {
11570 ; GDI+
11571 global
11572 _defaultColor := Opts["GDI" type "Color"]
11573 _defaultOpacity := Opts["GDI" type "Opacity"]
11574 _rgb := gdipTooltip.ValidateRGBColor(rgb, _defaultColor)
11575 _opacity := gdipTooltip.ValidateOpacity(opacity, _defaultOpacity, 10, 10)
11576 _ColorHandle := GDI%_type%ColorH
11577 _OpacityHandle := GDI%_type%OpacityH
11578
11579 ColorPickerResults := new ColorPicker(_rgb, _opacity, title, image)
11580 If (StrLen(ColorPickerResults[2])) {
11581 GuiControl, , % _ColorHandle, % ColorPickerResults[2]
11582 GuiControl, , % _OpacityHandle, % ColorPickerResults[3]
11583 }
11584}
11585
11586SettingsUI_BtnGDIWindowColor:
11587 _image := A_ScriptDir "\resources\images\colorPickerPreviewBg.png"
11588 _type := "Window"
11589 GuiControlGet, _cGDIColor , , % GDIWindowColorH
11590 GuiControlGet, _cGDIOpacity, , % GDIWindowOpacityH
11591 OpenGDIColorPicker(_type, _cGDIColor, _cGDIOpacity, "GDI+ Tooltip " _type " Color Picker", _image)
11592 return
11593
11594SettingsUI_BtnGDIBorderColor:
11595 _image := A_ScriptDir "\resources\images\colorPickerPreviewBg.png"
11596 _type := "Border"
11597 GuiControlGet, _cGDIColor , , % GDIBorderColorH
11598 GuiControlGet, _cGDIOpacity, , % GDIBorderOpacityH
11599 OpenGDIColorPicker(_type, _cGDIColor, _cGDIOpacity, "GDI+ Tooltip " _type " Color Picker", _image)
11600 return
11601
11602SettingsUI_BtnGDITextColor:
11603 _image := A_ScriptDir "\resources\images\colorPickerPreviewBg.png"
11604 _type := "Text"
11605 GuiControlGet, _cGDIColor , , % GDITextColorH
11606 GuiControlGet, _cGDIOpacity, , % GDITextOpacityH
11607 OpenGDIColorPicker(_type, _cGDIColor, _cGDIOpacity, "GDI+ Tooltip " _type " Color Picker", _image)
11608 return
11609
11610SettingsUI_BtnGDIPreviewTooltip:
11611 ; temporarily save GDI state as true
11612 _tempGDIState := Opts.UseGDI
11613 _tempGDIRenderingFixState := Opts.GDIRenderingFix
11614 GuiControlGet, _tempUseGDI, , % UseGDIH
11615 Opts.UseGDI := _tempUseGDI
11616
11617 GuiControlGet, _tempGDIWindowColor , , % GDIWindowColorH
11618 GuiControlGet, _tempGDIWindowOpacity , , % GDIWindowOpacityH
11619 GuiControlGet, _tempGDIBorderColor , , % GDIBorderColorH
11620 GuiControlGet, _tempGDIBorderOpacity , , % GDIBorderOpacityH
11621 GuiControlGet, _tempGDITextColor , , % GDITextColorH
11622 GuiControlGet, _tempGDITextOpacity , , % GDITextOpacityH
11623 GuiControlGet, _tempGDIRenderingFix , , % GDIRenderingFixH
11624 gdipTooltip.SetRenderingFix(_tempGDIRenderingFix)
11625 gdipTooltip.UpdateColors(_tempGDIWindowColor, _tempGDIWindowOpacity, _tempGDIBorderColor, _tempGDIBorderOpacity, _tempGDITextColor, _tempGDITextOpacity, "10", "16")
11626 _testString =
11627 (
11628 TOOLIP Preview Window
11629
11630 Voidbringer
11631 Conjurer Gloves
11632 Item Level: 70
11633 Base Level: 55
11634 Max Sockets: 4
11635 --------
11636 +1 to Level of Socketed Elem�
11637 Increased Critical Strike Ch� 125-150
11638 Increased Energy Shield 180-250
11639 Increased Mana Cost of Skill� 80-40
11640 Energy Shield gained on Kill 15-20
11641 )
11642
11643 If (not IsObject(gdipTooltip.window)) {
11644 GoSub, InitGDITooltip
11645 }
11646
11647 ShowToolTip(_testString)
11648 ; reset options
11649 Opts.UseGDI := _tempGDIState
11650 Opts.GDIRenderingFix := _tempGDIRenderingFixState
11651 gdipTooltip.SetRenderingFix(Opts.GDIRenderingFix)
11652 gdipTooltip.UpdateColors(Opts.GDIWindowColor, Opts.GDIWindowOpacity, Opts.GDIBorderColor, Opts.GDIBorderOpacity, Opts.GDITextColor, Opts.GDITextOpacity, "10", "16")
11653 return
11654
11655SettingsUI_BtnGDIDefaults:
11656 GuiControl, , % GDIWindowColorH , % Opts.GDIWindowColorDefault
11657 GuiControl, , % GDIWindowOpacityH, % Opts.GDIWindowOpacityDefault
11658 GuiControl, , % GDIBorderColorH , % Opts.GDIBorderColorDefault
11659 GuiControl, , % GDIBorderOpacityH, % Opts.GDIBorderOpacityDefault
11660 GuiControl, , % GDITextColorH , % Opts.GDITextColorDefault
11661 GuiControl, , % GDITextOpacityH , % Opts.GDITextOpacityDefault
11662 return
11663
11664SettingsUI_ChkUseGDI:
11665 ; GDI+
11666 GuiControlGet, IsChecked,, UseGDI
11667 If (Not IsChecked)
11668 {
11669 GuiControl, Disable, GDIWindowColor
11670 GuiControl, Disable, GDIWindowOpacity
11671 GuiControl, Disable, GDIBorderColor
11672 GuiControl, Disable, GDIBorderOpacity
11673 GuiControl, Disable, GDITextColor
11674 GuiControl, Disable, GDITextOpacity
11675
11676 GuiControl, Disable, BtnGDIWindowColor
11677 GuiControl, Disable, BtnGDIBorderColor
11678 GuiControl, Disable, BtnGDITextColor
11679
11680 GuiControl, Disable, BtnGDIDefaults
11681 GuiControl, Disable, BtnGDIPreviewTooltip
11682 GuiControl, Disable, GDIRenderingFix
11683 GuiControl, Disable, GDIConditionalColors
11684 }
11685 Else
11686 {
11687 If (not IsObject(gdipTooltip.window)) {
11688 _tempUseGDI := Opts.UseGDI
11689 Opts.UseGDI := 1
11690 GoSub, InitGDITooltip
11691 Opts.UseGDI := _tempUseGDI
11692
11693 ;update color and validate values and set rendering fix after initialising GDI
11694 gdipTooltip.SetRenderingFix(Opts.GDIRenderingFix)
11695
11696 GuiControl,, GDIWindowColor , % gdipTooltip.ValidateRGBColor(Opts.GDIWindowColor, Opts.GDIWindowColorDefault)
11697 GuiControl,, GDIWindowOpacity , % gdipTooltip.ValidateOpacity(Opts.GDIWindowOpacity, Opts.GDIWindowOpacityDefault, "10", "10")
11698 GuiControl,, GDIBorderColor , % gdipTooltip.ValidateRGBColor(Opts.GDIBorderColor, Opts.GDIBorderColorDefault)
11699 GuiControl,, GDIBorderOpacity , % gdipTooltip.ValidateOpacity(Opts.GDIBorderOpacity, Opts.GDIBorderOpacityDefault, "10", "10")
11700 GuiControl,, GDITextColor , % gdipTooltip.ValidateRGBColor(Opts.GDITextColor, Opts.GDITextColorDefault)
11701 GuiControl,, GDITextOpacity , % gdipTooltip.ValidateOpacity(Opts.GDITextOpacity, Opts.GDITextOpacityDefault, "10", "10")
11702 gdipTooltip.UpdateColors(Opts.GDIWindowColor, Opts.GDIWindowOpacity, Opts.GDIBorderColor, Opts.GDIBorderOpacity, Opts.GDITextColor, Opts.GDITextOpacity, 10, 16)
11703 }
11704
11705 GuiControl, Enable, GDIWindowColor
11706 GuiControl, Enable, GDIWindowOpacity
11707 GuiControl, Enable, GDIBorderColor
11708 GuiControl, Enable, GDIBorderOpacity
11709 GuiControl, Enable, GDITextColor
11710 GuiControl, Enable, GDITextOpacity
11711
11712 GuiControl, Enable, BtnGDIWindowColor
11713 GuiControl, Enable, BtnGDIBorderColor
11714 GuiControl, Enable, BtnGDITextColor
11715
11716 GuiControl, Enable, BtnGDIDefaults
11717 GuiControl, Enable, BtnGDIPreviewTooltip
11718 GuiControl, Enable, GDIRenderingFix
11719 GuiControl, Enable, GDIConditionalColors
11720 }
11721 return
11722
11723SettingsUI_ChkUseTooltipTimeout:
11724 GuiControlGet, IsChecked,, UseTooltipTimeout
11725 If (Not IsChecked) {
11726 GuiControl, Disable, ToolTipTimeoutSeconds
11727 }
11728 Else {
11729 GuiControl, Enable, ToolTipTimeoutSeconds
11730 }
11731 return
11732
11733SettingsUI_ChkUseCompactDoubleRanges:
11734 GuiControlGet, IsChecked,, UseCompactDoubleRanges
11735 If (Not IsChecked) {
11736 GuiControl, Enable, OnlyCompactForTotalColumn
11737 }
11738 Else {
11739 GuiControl, Disable, OnlyCompactForTotalColumn
11740 }
11741 return
11742
11743SettingsUI_ChkDisplayToolTipAtFixedCoords:
11744 GuiControlGet, IsChecked,, DisplayToolTipAtFixedCoords
11745 If (Not IsChecked)
11746 {
11747 GuiControl, Disable, LblScreenOffsetX
11748 GuiControl, Disable, ScreenOffsetX
11749 GuiControl, Disable, LblScreenOffsetY
11750 GuiControl, Disable, ScreenOffsetY
11751 }
11752 Else
11753 {
11754 GuiControl, Enable, LblScreenOffsetX
11755 GuiControl, Enable, ScreenOffsetX
11756 GuiControl, Enable, LblScreenOffsetY
11757 GuiControl, Enable, ScreenOffsetY
11758 }
11759 return
11760
11761MenuTray_About:
11762 IfNotEqual, FirstTimeA, No
11763 {
11764 Authors := GetContributors(0)
11765 RelVer := Globals.get("ReleaseVersion")
11766 Gui, About:+owner1 -Caption +Border
11767 Gui, About:Color, ffffff, ffffff
11768 Gui, About:Font, S10 CA03410,verdana
11769 Gui, About:Add, Text, x260 y27 w170 h20 Center, Release %RelVer%
11770 Gui, About:Add, Button, 0x8000 x316 y300 w70 h21, Close
11771 Gui, About:Add, Picture, 0x1000 x17 y16 w230 h180, %A_ScriptDir%\resources\images\splash.png
11772 Gui, About:Font, Underline C3571AC,verdana
11773 Gui, About:Add, Text, x260 y57 w170 h20 gVisitForumsThread Center, PoE forums thread
11774 Gui, About:Add, Text, x260 y87 w170 h20 gAboutDlg_AhkHome Center, AutoHotkey homepage
11775 Gui, About:Add, Text, x260 y117 w170 h20 gAboutDlg_GitHub Center, PoE-ItemInfo GitHub
11776 Gui, About:Font, S7 CDefault normal, Verdana
11777 Gui, About:Add, Text, x16 y207 w410 h80,
11778 (LTrim
11779 Shows affix breakdowns and other useful infos for any item or item link.
11780
11781 Usage: Set PoE to Windowed Fullscreen mode and hover over any item or item link. Press Ctrl+C to show a tooltip.
11782
11783 (c) %A_YYYY% Hazydoc, Nipper4369 and contributors:
11784 )
11785 Gui, About:Add, Text, x16 y277 w270 h80, %Authors%
11786
11787 FirstTimeA = No
11788 }
11789
11790 height := Globals.Get("AboutWindowHeight", 340)
11791 width := Globals.Get("AboutWindowWidth", 435)
11792 Gui, About:Show, h%height% w%width%, About..
11793
11794 ; Release counter animation
11795 tmpH = 0
11796 Loop, 20
11797 {
11798 tmpH += 1
11799 ControlMove, Static1,,,, %tmpH%, About..
11800 Sleep, 100
11801 }
11802 return
11803
11804AboutDlg_AhkHome:
11805 Run, https://autohotkey.com/
11806 return
11807
11808AboutDlg_GitHub:
11809 Run, https://github.com/aRTy42/POE-ItemInfo
11810 return
11811
11812VisitForumsThread:
11813 Run, https://www.pathofexile.com/forum/view-thread/1678678
11814 return
11815
11816AboutButtonClose:
11817AboutGuiClose:
11818 WinGet, AbtWndID, ID, About..
11819 DllCall("AnimateWindow", "Int", AbtWndID, "Int", 500, "Int", 0x00090010)
11820 WinActivate, ahk_id %MainWndID%
11821 return
11822
11823EditOpenUserSettings:
11824 OpenUserSettingsFolder(Globals.Get("ProjectName"))
11825 return
11826
11827EditAdditionalMacrosSettings:
11828 OpenUserDirFile("AdditionalMacros.ini")
11829 return
11830
11831PreviewAdditionalMacros:
11832 OpenTextFileReadOnly(A_ScriptDir "\resources\ahk\AdditionalMacros.ahk")
11833 return
11834
11835EditMapModWarningsConfig:
11836 OpenUserDirFile("MapModWarnings.ini")
11837 return
11838
11839EditCustomMacrosExample:
11840 OpenUserDirFile("CustomMacros\customMacros_example.txt")
11841 return
11842
11843EditCurrencyRates:
11844 OpenCreateDataTextFile("CurrencyRates.txt")
11845 return
11846
11847ReloadScript:
11848 scriptName := RegExReplace(Globals.Get("ProjectName"), "i)poe-", "Run_") . ".ahk"
11849 Run, "%A_AhkPath%" "%A_ScriptDir%\%scriptName%"
11850 return
11851
11852ShowAssignedHotkeys:
11853 ShowAssignedHotkeys()
11854 return
11855
118563GuiClose:
11857 Gui, 3:Cancel
11858 return
11859
11860SettingsUIGuiEscape:
11861 ; settings
11862 Gui, SettingsUI:Cancel
11863Return
11864
11865AboutGuiEscape:
11866 Gui, About:Cancel
11867Return
11868
11869ShowHotkeysGuiEscape:
11870 Gui, ShowHotkeys:Cancel
11871Return
11872
11873TranslateGuiEscape:
11874 Gui, Translate:Cancel
11875Return
11876
11877UpdateNotesGuiEscape:
11878 Gui, UpdateNotes:Cancel
11879Return
11880
11881HotkeyConflictGuiEscape:
11882 Gui, HotkeyConflict:Cancel
11883Return
11884
11885CloseHotkeyConflictGui:
11886 Gui, HotkeyConflict:Destroy
11887Return
11888
11889CheckForUpdates:
11890 If (not globalUpdateInfo.repo) {
11891 global globalUpdateInfo := {}
11892 }
11893 If (not SkipItemInfoUpdateCall) {
11894 globalUpdateInfo.repo := Globals.Get("GithubRepo")
11895 globalUpdateInfo.user := Globals.Get("GithubUser")
11896 globalUpdateInfo.releaseVersion := Globals.Get("ReleaseVersion")
11897 globalUpdateInfo.skipSelection := Opts.UpdateSkipSelection
11898 globalUpdateInfo.skipBackup := Opts.UpdateSkipBackup
11899 globalUpdateInfo.skipUpdateCheck := Opts.ShowUpdateNotifications
11900 SplashScreenTitle := "PoE-ItemInfo"
11901 }
11902
11903 hasUpdate := PoEScripts_Update(globalUpdateInfo.user, globalUpdateInfo.repo, globalUpdateInfo.releaseVersion, globalUpdateInfo.skipUpdateCheck, userDirectory, isDevVersion, globalUpdateInfo.skipSelection, globalUpdateInfo.skipBackup)
11904 If (hasUpdate = "no update" and not firstUpdateCheck) {
11905 SplashUI.SetSubMessage("No update available")
11906 Sleep 2000
11907 SplashUI.DestroyUI()
11908 }
11909 return
11910
11911CurrencyDataDownloadURLtoJSON(url, sampleValue, critical = false, isFallbackRequest = false, league = "", project = "", tmpFileName = "", fallbackDir = "", ByRef usedFallback = false, ByRef loggedCurrencyRequestAtStartup = false, ByRef loggedTempLeagueCurrencyRequest = false, CurlTimeout = 35) {
11912 errorMsg := "Parsing the currency data (json) from poe.ninja failed.`n"
11913 errorMsg .= "This should only happen when the servers are down / unavailable."
11914 errorMsg .= "`n`n"
11915 errorMsg .= "This can fix itself when the servers are up again and the data gets updated automatically or if you restart the script at such a time."
11916 errorMsg .= "`n`n"
11917 errorMsg .= "You can find a log file with some debug information:"
11918 errorMsg .= "`n" """" A_ScriptDir "\temp\StartupLog.txt"""
11919 errorMsg .= "`n`n"
11920
11921 errors := 0
11922 parsingError := false
11923 Try {
11924 options := ""
11925 options .= "`n" "TimeOut: " CurlTimeout
11926
11927 reqHeaders.push("Connection: keep-alive")
11928 reqHeaders.push("Cache-Control: max-age=0")
11929 reqHeaders.push("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")
11930 reqHeaders.push("User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36")
11931 currencyData := PoEScripts_Download(url, postData, reqHeaders, options, true, true, false, "", reqHeadersCurl)
11932
11933 deleteError := PoEScripts_SaveWriteTextFile(A_ScriptDir "\temp\currencyHistory_" league ".txt", currencyData, "utf-8", true, true)
11934
11935 Try {
11936 parsedJSON := JSON.Load(currencyData)
11937 } Catch e {
11938 parsingError := true
11939 }
11940
11941 isTempLeague := RegExMatch(league, "^(Standard|Hardcore)")
11942 If (not loggedCurrencyRequestAtStartup or (not loggedTempLeagueCurrencyRequest and not isTempLeague)) {
11943 logMsg := "Requesting currency data from poe.ninja...`n`n" "cURL command:`n" reqHeadersCurl "`n`nAnswer: " reqHeaders
11944 WriteToLogFile(logMsg, "StartupLog.txt", project)
11945
11946 loggedCurrencyRequestAtStartup := true
11947 If (not loggedTempLeagueCurrencyRequest and not isTempLeague) {
11948 loggedTempLeagueCurrencyRequest := true
11949 }
11950 If (deleteError) {
11951 WriteToLogFile("Failed to delete " A_ScriptDir "\temp\currencyHistory_" league ".txt before writing data to it. `n", "StartupLog.txt", project)
11952 }
11953 }
11954 ; first currency data parsing (script start)
11955 If ((critical and (not sampleValue or isFallbackRequest)) or not parsedJSON.lines.length()) {
11956 errors++
11957 }
11958 } Catch error {
11959 ; first currency data parsing (script start)
11960 If (critical and (not sampleValue or isFallbackRequest)) {
11961 errors++
11962 }
11963 }
11964
11965 If ((errors and critical and (not sampleValue or isFallbackRequest)) or parsingError) {
11966 FileRead, JSONFile, %fallbackDir%\currencyData_Fallback_%league%.json
11967 Try {
11968 parsedJSON := JSON.Load(JSONFile)
11969 If (isFallbackRequest) {
11970 errorMsg .= "This is a fallback request trying to get data for the """ league """ league since getting data for the currently selected league failed."
11971 errorMsg .= "`n`n"
11972 }
11973 errorMsg .= "The script is now using archived data from a fallback file instead. League: """ league """"
11974 errorMsg .= "`n`n"
11975 } Catch e {
11976 errorMsg .= "Using archived fallback data failed (JSON parse error)."
11977 errorMsg .= "`n`n"
11978 }
11979
11980 MsgBox, 16, %project% - Error, %errorMsg%
11981 usedFallback := true
11982 }
11983
11984 Return parsedJSON
11985}
11986
11987FetchCurrencyData:
11988 _CurrencyDataJSON := {}
11989 currencyLeagues := ["Standard", "Hardcore", "Synthesis", "tmphardcore", "eventstandard", "eventhardcore"]
11990 sampleValue := "ff"
11991 loggedCurrencyRequestAtStartup := loggedCurrencyRequestAtStartup ? loggedCurrencyRequestAtStartup : false
11992 loggedTempLeagueCurrencyRequest := loggedTempLeagueCurrencyRequest ? loggedTempLeagueCurrencyRequest : false
11993
11994 Loop, % currencyLeagues.Length() {
11995 currencyLeague := currencyLeagues[A_Index]
11996 url := "https://poe.ninja/api/Data/GetCurrencyOverview?league=" . currencyLeague
11997 file := A_ScriptDir . "\temp\currencyData_" . currencyLeague . ".json"
11998
11999 url := "https://poe.ninja/api/Data/GetCurrencyOverview?league=" . currencyLeague
12000 critical := StrLen(Globals.Get("LastCurrencyUpdate")) ? false : true
12001 parsedJSON := CurrencyDataDownloadURLtoJSON(url, sampleValue, critical, false, currencyLeague, "PoE-ItemInfo", file, A_ScriptDir "\data", usedFallback, loggedCurrencyRequestAtStartup, loggedTempLeagueCurrencyRequest)
12002
12003 Try {
12004 If (parsedJSON) {
12005 _CurrencyDataJSON[currencyLeague] := parsedJSON.lines
12006 If (not usedFallback) {
12007 ParsedAtLeastOneLeague := True
12008 }
12009 }
12010 Else {
12011 _CurrencyDataJSON[currencyLeague] := null
12012 }
12013 } Catch error {
12014 errorMsg := "Parsing the currency data (json) from poe.ninja failed for league:"
12015 errorMsg .= "`n" currencyLeague
12016 ;MsgBox, 16, PoE-ItemInfo - Error, %errorMsg%
12017 }
12018 parsedJSON :=
12019 }
12020
12021 If (ParsedAtLeastOneLeague) {
12022 Globals.Set("LastCurrencyUpdate", A_NowUTC)
12023 }
12024
12025 ; parse JSON and write files to disk (like \data\CurrencyRates.txt)
12026 _CurrencyDataRates := {}
12027 For league, data in _CurrencyDataJSON {
12028 _CurrencyDataRates[league] := {}
12029
12030 For currency, cData in data {
12031 cName := cData.currencyTypeName
12032 cChaosEquiv := cData.chaosEquivalent
12033
12034 If (cChaosEquiv >= 1) {
12035 cChaosQuantity := ZeroTrim(Round(cChaosEquiv, 2))
12036 cOwnQuantity := 1
12037 }
12038 Else {
12039 cChaosQuantity := 1
12040 cOwnQuantity := ZeroTrim(Round(1 / cChaosEquiv, 2))
12041 }
12042
12043 _CurrencyDataRates[league][cName] := {}
12044 _CurrencyDataRates[league][cName].ChaosEquiv := cChaosEquiv
12045 _CurrencyDataRates[league][cName].ChaosQuantity := cChaosQuantity
12046 _CurrencyDataRates[league][cName].OwnQuantity := cOwnQuantity
12047 }
12048 }
12049
12050 Globals.Set("CurrencyDataRates", _CurrencyDataRates)
12051 _CurrencyDataJSON :=
12052 return
12053
12054ZeroTrim(number) {
12055 ; Trim trailing zeros from numbers
12056
12057 RegExMatch(number, "(\d+)\.?(.+)?", match)
12058 If (StrLen(match2) < 1) {
12059 Return number
12060 } Else {
12061 trail := RegExReplace(match2, "0+$", "")
12062 number := (StrLen(trail) > 0) ? match1 "." trail : match1
12063 Return number
12064 }
12065}
12066
12067IsInArray(el, array) {
12068 For i, element in array {
12069 If (el = "") {
12070 Return false
12071 }
12072 If (element = el) {
12073 Return true
12074 }
12075 }
12076 Return false
12077}
12078
12079GetObjPropertyCount(obj) {
12080 Return NumGet(&obj + 4*A_PtrSize)
12081}
12082
12083TogglePOEItemScript()
12084{
12085 IF SuspendPOEItemScript = 0
12086 {
12087 SuspendPOEItemScript = 1
12088 ShowToolTip("Item parsing PAUSED")
12089 }
12090 Else
12091 {
12092 SuspendPOEItemScript = 0
12093 ShowToolTip("Item parsing ENABLED")
12094 }
12095}
12096
12097GetScanCodes() {
12098 f := A_FormatInteger
12099 SetFormat, Integer, H
12100 WinGet, WinID,, A
12101 ThreadID:=DllCall("GetWindowThreadProcessId", "UInt", WinID, "UInt", 0)
12102 InputLocaleID:=DllCall("GetKeyboardLayout", "UInt", ThreadID, "UInt")
12103 SetFormat, Integer, %f%
12104
12105 ; example results: 0xF0020809/0xF01B0809/0xF01A0809
12106 ; 0809 is for "English United Kingdom"
12107 ; 0xF002 = "Dvorak"
12108 ; 0xF01B = "Dvorak right handed"
12109 ; 0xF01A = "Dvorak left handed"
12110 ; 0xF01C0809 = some other Dvorak layout
12111
12112 If (RegExMatch(InputLocaleID, "i)^(0xF002|0xF01B|0xF01A|0xF01C0809|0xF01C0409).*")) {
12113 ; dvorak
12114 sc := {"c" : "sc017", "v" : "sc034", "f" : "sc015", "a" : "sc01E", "enter" : "sc01C"}
12115 project := Globals.Set("ProjectName")
12116 msg := "Using Dvorak keyboard layout mode!`n`nMsgBox closes after 15s."
12117 MsgBox, 0, %project%, %msg%, 15
12118 Return sc
12119 } Else {
12120 ; default
12121 sc := {"c" : "sc02E", "v" : "sc02f", "f" : "sc021", "a" : "sc01E", "enter" : "sc01C"}
12122 Return sc
12123 }
12124}
12125
12126GetCurrentItemFilterPath(ByRef parsingNeeded = true) {
12127 currentFilter := Globals.Get("CurrentItemFilter")
12128 iniPath := A_MyDocuments . "\My Games\Path of Exile\"
12129 configs := []
12130 productionIni := iniPath . "production_Config.ini"
12131 betaIni := iniPath . "beta_Config.ini"
12132
12133 configs.push(productionIni)
12134 configs.push(betaIni)
12135 If (not FileExist(productionIni) and not FileExist(betaIni)) {
12136 Loop %iniPath%\*.ini
12137 {
12138 configs.push(iniPath . A_LoopFileName)
12139 }
12140 }
12141
12142 readFile := ""
12143 For key, val in configs {
12144 IniRead, filter, %val%, UI, item_filter_loaded_successfully
12145 If (filter != "ERROR" and FileExist(iniPath . filter)) {
12146 Break
12147 }
12148 }
12149
12150 filter := iniPath . filter
12151
12152 If (currentFilter != filter) {
12153 parsingNeeded := true
12154 Globals.Set("CurrentItemFilter", filter)
12155 Return filter
12156 } Else {
12157 parsingNeeded := false
12158 Return currentFilter
12159 }
12160}
12161
12162ShowAdvancedItemFilterFormatting() {
12163 Global Item
12164
12165 SuspendPOEItemScript = 1 ; This allows us to handle the clipboard change event
12166
12167 scancode_c := Globals.Get("Scancodes").c
12168 Send ^{%scancode_c%}
12169 Sleep 150
12170 CBContents := GetClipboardContents()
12171 CBContents := PreProcessContents(CBContents)
12172 Globals.Set("ItemText", CBContents)
12173 ParsedData := ParseItemData(CBContents)
12174 ShowItemFilterFormatting(Item, true)
12175
12176 SuspendPOEItemScript = 0 ; Allow ItemInfo to handle clipboard change event
12177}
12178
12179ShowItemFilterFormatting(Item, advanced = false) {
12180 If (not Item.Name) {
12181 Return
12182 }
12183
12184 parsingNeeded := true
12185 filterFile := GetCurrentItemFilterPath(parsingNeeded)
12186 If (RegExMatch(filterFile, "i).*\\(error)$")) {
12187 If (advanced) {
12188 ShowToolTip("No custom loot filter loaded successfully.`nMake sure you have one selected in your UI options.")
12189 }
12190 Return
12191 }
12192
12193 ItemBaseList := Globals.Get("ItemBaseList")
12194
12195 search := {}
12196 search.LinkedSockets := Item.Links
12197 search.ShaperItem := Item.IsShaperBase
12198 search.ElderItem := Item.IsElderBase
12199 search.FracturedItem := Item.IsFracturedBase
12200 search.SynthesisedItem := Item.IsSynthesisedBase
12201 search.ItemLevel := Item.Level
12202 search.BaseType := [Item.BaseName]
12203 search.HasExplicitMod := ; HasExplicitMod "of Crafting" "of Spellcraft" "of Weaponcraft"
12204 search.Identified := Item.IsUnidentified ? 0 : 1
12205 search.Corrupted := Item.IsCorrupted
12206 search.Quality := Item.Quality
12207 search.Sockets := Item.Sockets
12208 search.Width :=
12209 search.Height :=
12210 search.name := Item.Name
12211
12212 ; rarity
12213 If (Item.RarityLevel = 1) {
12214 search.Rarity := "Normal"
12215 search.RarityLevel := 1
12216 } Else If (Item.RarityLevel = 2) {
12217 search.Rarity := "Magic"
12218 search.RarityLevel := 2
12219 } Else If (Item.RarityLevel = 3) {
12220 search.Rarity := "Rare"
12221 search.RarityLevel := 3
12222 } Else If (Item.RarityLevel = 4) {
12223 search.Rarity := "Unique"
12224 search.RarityLevel := 4
12225 }
12226
12227 ; classes
12228 class := (StrLen(Item.SubType)) ? Item.SubType : Item.BaseType
12229 search.Class := []
12230 If (RegExMatch(class, "i)BodyArmour")) {
12231 search.Class.push("Body Armour")
12232 search.Class.push("Body Armours")
12233 }
12234 If (RegExMatch(class, "i)Sword|Mace|Axe")) {
12235 If (Item.GripType = "2H") {
12236 search.Class.push(class)
12237 search.Class.push("Two Hand " class)
12238 search.Class.push("Two Hand " class "s")
12239 search.Class.push("Two Hand")
12240 } Else {
12241 search.Class.push(class)
12242 search.Class.push("One Hand " class)
12243 search.Class.push("One Hand " class "s")
12244 search.Class.push("One Hand")
12245 }
12246 }
12247 If (RegExMatch(class, "i)Flask")) {
12248 If (RegExMatch(Item.BaseName, "i) (Life|Mana) ", match)) {
12249 search.Class.push(match1 " Flasks")
12250 search.Class.push(match1 " Flask")
12251 search.Class.push("Flask")
12252 } Else {
12253 search.Class.push("Utility Flasks")
12254 search.Class.push("Utility Flask")
12255 search.Class.push("Flask")
12256 }
12257 }
12258 If (RegExMatch(class, "i)Jewel")) {
12259 class := "Jewel"
12260 search.Class.push(class)
12261 search.Class.push(class "s")
12262
12263 If (RegExMatch(Item.SubType, "i)Murderous Eye|Hypnotic Eye|Searching Eye")) {
12264 class := "Abyss Jewel"
12265 search.Class.push(class)
12266 search.Class.push(class "s")
12267 }
12268 }
12269 If (RegExMatch(class, "i)Currency") and RegExMatch(Item.BaseName, "i)Resonator")) {
12270 search.Class.push("Delve Socketable Currency")
12271 search.Class.push("Currency")
12272 }
12273 ; Quest Items
12274 If (RegExMatch(Item.BaseName, "i)(Elder's Orb|Shaper's Orb)", match)) {
12275 search.Class.push("Quest")
12276 Item.IsQuestItem := true
12277 }
12278
12279 If (not search.Class.MaxIndex() and StrLen(class)) {
12280 search.Class.push(class)
12281 search.Class.push(class "s")
12282 }
12283
12284 For key, val in ItemBaseList {
12285 For k, v in val {
12286 If (k = Item.BaseName) {
12287 search.DropLevel := v["Drop Level"]
12288 search.Width := v["Width"]
12289 search.Height := v["Height"]
12290 Break
12291 }
12292 }
12293 }
12294 If (Item.IsMap) {
12295 search.DropLevel := Item.MapTier + 67
12296 }
12297
12298 ; SocketGroups, RGB for example
12299 search.SocketGroup := []
12300 For key, val in Item.SocketGroups {
12301 sGroup := {}
12302 _r := RegExReplace(val, "i)r" , "", rCount)
12303 _g := RegExReplace(val, "i)g" , "", gCount)
12304 _b := RegExReplace(val, "i)b" , "", bCount)
12305 _w := RegExReplace(val, "i)w" , "", wCount)
12306 _w := RegExReplace(val, "i)d" , "", dCount)
12307 _w := RegExReplace(val, "i)a" , "", aCount)
12308 sGroup.r := rCount
12309 sGroup.g := gCount
12310 sGroup.b := bCount
12311 sGroup.w := wCount
12312 sGroup.d := dCount
12313 sGroup.a := aCount
12314 If (sGroup.r or sGroup.b or sGroup.g or sGroup.w or sGroup.d or sGroup.a) {
12315 search.SocketGroup.push(sGroup)
12316 }
12317 }
12318
12319 search.HasExplicitMod := []
12320 ; works only for magic items
12321 If (Item.RarityLevel = 2) {
12322 RegExMatch(Item.Name, "i)(.*)?" Item.BaseName "(.*)?", nameParts)
12323 If (StrLen(nameParts1)) {
12324 search.HasExplicitMod.push(Trim(nameParts1))
12325 }
12326 If (StrLen(nameParts2)) {
12327 search.HasExplicitMod.push(Trim(nameParts2))
12328 }
12329 }
12330
12331 search.SetBackGroundColor := GetItemDefaultColor(Item, "BackGround")
12332 search.SetBorderColor := GetItemDefaultColor(Item, "Border")
12333 search.SetTextColor := GetItemDefaultColor(Item, "Text")
12334
12335 search.LabelLines := []
12336 _line := (Item.Quality > 0) ? "Superior " RegExReplace(Item.Name, "i)Superior (.*)", "$1") : Item.Name
12337 _line := (not Item.IsGem and not Item.IsUnidentified and not Item.RarityLevel = 1) ? RegExReplace(Item.Name, "i)Superior (.*)", "$1") : _line
12338 _line .= (Item.IsGem and Item.Level > 1) ? " (Level " Item.Level ")" : ""
12339 search.LabelLines.push(_line)
12340
12341 ; Unidentified rare/unique items have the same baseName as their name
12342 If (Item.RarityLevel >= 3 and (RegExReplace(Item.Name, "i)Superior (.*)", "$1") != Item.BaseName)) {
12343 _line := Item.BaseName
12344 search.LabelLines.push(_line)
12345 }
12346
12347 ParseItemLootFilter(filterFile, search, parsingNeeded, advanced)
12348}
12349
12350GetItemDefaultColor(item, cType) {
12351 If (cType = "Border") {
12352 ; labyrinth map item or map fragment
12353 If (item.IsMapFragment) {
12354 return "200 200 200 1" ; // white
12355 }
12356
12357 ; Quest Item / labyrinth item
12358 If (item.IsQuestItem or item.IsLabyrinthItem) { ; these variables don't exist yet
12359 return "74 230 58 1" ; // green
12360 }
12361
12362 ; map rarity
12363 Else If (item.IsMap) {
12364 If (item.RarityLevel = 1) {
12365 return "200 200 200 1"
12366 } Else If (item.RarityLevel = 2) {
12367 return "136 136 255 1"
12368 } Else If (item.RarityLevel = 3) {
12369 return "255 255 119 1"
12370 } Else If (item.RarityLevel = 4) {
12371 return " 175 96 37 1"
12372 } Else {
12373 return "255 255 255 0"
12374 }
12375 }
12376
12377 ; default border color: none
12378 Else {
12379 return s:= "0 0 0 0"
12380 }
12381 }
12382
12383 ; background is always black
12384 Else If (cType = "BackGround") {
12385 return "0 0 0 255"
12386 }
12387
12388 Else If (cType = "Text") {
12389 ; create text color based gem class
12390 If (item.IsGem)
12391 {
12392 return "27 162 155 1"
12393 }
12394
12395 ; create text color based on currency class
12396 Else If (item.IsCurrency)
12397 {
12398 return "170 158 130 1"
12399 }
12400
12401 ; create text color based on map fragments classes
12402 Else If (RegExMatch(item.Name, "i)Offering of the Goddess") or item.IsMap)
12403 {
12404 return "200 200 200 1"
12405 }
12406
12407 ; quest / lab item
12408 Else If (item.IsQuestItem or item.IsLabyrinthItem) ; IsLabyrinthItem doesn't exist yet
12409 {
12410 return "74 230 58 1"
12411 }
12412
12413 ; div card
12414 Else If (item.IsDivinationCard)
12415 {
12416 return "14 186 255 1"
12417 }
12418
12419 ; create text color based on rarity
12420 Else If (item.RarityLevel)
12421 {
12422 If (item.RarityLevel = 1) {
12423 return "200 200 200 1"
12424 } Else If (item.RarityLevel = 2) {
12425 return "136 136 255 1"
12426 } Else If (item.RarityLevel = 3) {
12427 return "255 255 119 1"
12428 } Else If (item.RarityLevel = 4) {
12429 return " 175 96 37 1"
12430 } Else {
12431 return "255 255 255 0"
12432 }
12433 }
12434
12435 ; creating default text color (white)
12436 Else {
12437 return "255 255 255 1"
12438 }
12439 }
12440
12441 Return
12442}
12443
12444ParseItemLootFilter(filter, item, parsingNeeded, advanced = false) {
12445 ; https://pathofexile.gamepedia.com/Item_filter
12446 rules := []
12447 matchedRule := {}
12448
12449 ; Use already parsed filter data if the item filter is still the same
12450 If (not parsingNeeded) {
12451 rules := Globals.Get("ItemFilterObj")
12452 }
12453 ; Parse the item filter if it wasn't used the last time or fall back to parsing it if using the already parsed data fails
12454 If (parsingNeeded or rules.MaxIndex() > 1) {
12455 /*
12456 Parse filter rules to object
12457 */
12458 Loop, Read, %filter%
12459 {
12460 If (RegExMatch(A_LoopReadLine, "i)^#") or not StrLen(A_LoopReadLine)) {
12461 continue
12462 }
12463
12464 If (RegExMatch(Trim(A_LoopReadLine), "i)^(Show|Hide)(\s|#)?", match)) {
12465 rule := {}
12466 rule.Display := match1
12467 rule.Conditions := []
12468 rule.Comments := []
12469 If (RegExMatch(Trim(A_LoopReadLine), "i)#(.*)?", comment)) { ; only comments after filter code
12470 If (StrLen(comment1)) {
12471 rule.Comments.push(comment1)
12472 }
12473 }
12474 rules.push(rule)
12475 } Else {
12476 RegExMatch(Trim(A_LoopReadLine), "i)#(.*)?", comment) ; only comments after filter code
12477 If (StrLen(comment1)) {
12478 rules[rules.MaxIndex()].Comments.push(comment1)
12479 }
12480
12481 line := RegExReplace(Trim(A_LoopReadLine), "i)#.*")
12482
12483 /*
12484 Styles (last line is valid)
12485 */
12486 If (RegExMatch(line, "i)^.*?Color\s")) {
12487 RegExMatch(line, "i)(.*?)\s(.*)", match)
12488 rules[rules.MaxIndex()][Trim(match1)] := Trim(match2)
12489 }
12490
12491 Else If (RegExMatch(line, "i)^.*?(PlayAlertSound|MinimapIcon|PlayEffect)\s")) {
12492 RegExMatch(line, "i)(.*?)\s(.*)", match)
12493 params := StrSplit(Trim(match2), " ")
12494 rules[rules.MaxIndex()][Trim(match1)] := params
12495 }
12496
12497 /*
12498 Conditions (every condition must match, lines don't overwrite each other)
12499 */
12500 Else If (RegExMatch(line, "i)^.*?(Class|BaseType|HasExplicitMod|SocketGroup)\s")) {
12501 RegExMatch(line, "i)(.*?)\s(.*)", match)
12502
12503 ;temp := RegExReplace(match2, "i)(""\s+"")", """,""")
12504 temp := RegExReplace(match2, "i)(\s)\s+", "\s")
12505 temp := RegExReplace(temp, "i)(\s+)|""(.*?)""", "$1,$2")
12506 temp := RegExReplace(temp, "i)(,,+)", ",")
12507 temp := RegExReplace(temp, "i)(\s,)", ",")
12508 temp := RegExReplace(temp, "i)(^,)|(, $)")
12509
12510 arr := StrSplit(temp, ",")
12511
12512 condition := {}
12513 condition.name := match1
12514 condition.values := arr
12515 rules[rules.MaxIndex()].conditions.push(condition)
12516 }
12517
12518 Else If (RegExMatch(line, "i)^.*?(DropLevel|ItemLevel|Rarity|LinkedSockets|Sockets|Quality|Height|Width|StackSize|GemLevel|MapTier)\s")) {
12519 RegExMatch(line, "i)(.*?)\s(.*)", match)
12520 paramsTemp := StrSplit(Trim(match2), " ")
12521
12522 condition := {}
12523 condition.name := match1
12524 condition.operator := ParamsTemp.MaxIndex() = 2 ? paramsTemp[1] : "="
12525 condition.value := ParamsTemp.MaxIndex() = 2 ? paramsTemp[2] : paramsTemp[1]
12526
12527 ; rarity
12528 If (condition.value = "Normal") {
12529 condition.value := 1
12530 } Else If (condition.value = "Magic") {
12531 condition.value := 2
12532 } Else If (condition.value = "Rare") {
12533 condition.value := 3
12534 } Else If (condition.value = "Unique") {
12535 condition.value := 4
12536 }
12537
12538 rules[rules.MaxIndex()].conditions.push(condition)
12539 }
12540
12541 Else If (RegExMatch(line, "i)^.*?(Identified|Corrupted|ElderItem|SynthesisedItem|FracturedItem|ShaperItem|ShapedMap|ElderMap)\s")) {
12542 RegExMatch(line, "i)(.*?)\s(.*)", match)
12543
12544 condition := {}
12545 condition.name := Trim(match1)
12546 condition.value := Trim(match2) = "True" ? true : false
12547 rules[rules.MaxIndex()].conditions.push(condition)
12548 }
12549
12550 /*
12551 the rest
12552 */
12553 Else {
12554 RegExMatch(line, "i)(.*?)\s(.*)", match)
12555 rules[rules.MaxIndex()][Trim(match1)] := Trim(match2)
12556 }
12557 }
12558 }
12559 Globals.Set("ItemFilterObj", rules)
12560 }
12561
12562 json := JSON.Dump(rules)
12563 FileDelete, %A_ScriptDir%\temp\itemFilterParsed.json
12564 FileAppend, %json%, %A_ScriptDir%\temp\itemFilterParsed.json
12565
12566 /*
12567 Match item againt rules
12568 */
12569 match := ""
12570 match1 := ""
12571 match2 := ""
12572 For k, rule in rules {
12573 totalConditions := rule.conditions.MaxIndex()
12574 matchingConditions := 0
12575 matching_rules := []
12576
12577 For i, condition in rule.conditions {
12578
12579 If (RegExMatch(condition.name, "i)(LinkedSockets|DropLevel|ItemLevel|Rarity|Sockets|Quality|Height|Width|StackSize|GemLevel|MapTier)", match1)) {
12580 If (match1 = "Rarity") {
12581 If (CompareNumValues(item["RarityLevel"], condition.value, condition.operator)) {
12582 matchingConditions++
12583 matching_rules.push(condition.name)
12584 }
12585 } Else {
12586 If (CompareNumValues(item[match1], condition.value, condition.operator)) {
12587 matchingConditions++
12588 matching_rules.push(condition.name)
12589 }
12590 }
12591 }
12592 Else If (RegExMatch(condition.name, "i)(Identified|Corrupted|ElderItem|SynthesisedItem|FracturedItem|ShaperItem|ShapedMap)", match1)) {
12593 If (item[match1] == condition.value) {
12594 matchingConditions++
12595 matching_rules.push(condition.name)
12596 }
12597 }
12598 Else If (RegExMatch(condition.name, "i)(Class|BaseType|HasExplicitMod)", match1)) {
12599 For j, value in condition.values {
12600 foundMatch := 0
12601
12602 For l, v in item[match1] {
12603 If (RegExMatch(v, "i)" value "")) {
12604 matchingConditions++
12605 matching_rules.push(condition.name)
12606 foundMatch := 1
12607 Break
12608 }
12609 }
12610 If (foundMatch) {
12611 Break
12612 }
12613 }
12614 }
12615 Else If (RegExMatch(condition.name, "i)(SocketGroup)", match1)) {
12616 For j, value in condition.values {
12617 foundMatch := 0
12618
12619 For l, v in item[match1] {
12620 _r := RegExReplace(value, "i)r" , "", rCount)
12621 _g := RegExReplace(value, "i)g" , "", gCount)
12622 _b := RegExReplace(value, "i)b" , "", bCount)
12623 _w := RegExReplace(value, "i)w" , "", wCount)
12624 _w := RegExReplace(value, "i)d" , "", dCount)
12625 _w := RegExReplace(value, "i)a" , "", aCount)
12626
12627 If (v.r = rCount and v.g = gCount and v.b = bCount and v.w = wCount and v.d = dCount and v.a = aCount) {
12628 matchingConditions++
12629 matching_rules.push(condition.name)
12630 foundMatch := 1
12631 Break
12632 }
12633 }
12634 If (foundMatch) {
12635 Break
12636 }
12637 }
12638 }
12639 }
12640
12641 If (totalConditions = matchingConditions) {
12642 matchedRule := rule
12643 matchedRule["matching_rules"] := matching_rules
12644 Break
12645 }
12646 }
12647 ;debugprintarray([matchedRule, item])
12648
12649 If (not StrLen(matchedRule.SetBackgroundColor)) {
12650 matchedRule.SetBackgroundColor := item.SetBackgroundColor
12651 }
12652 If (not StrLen(matchedRule.SetBorderColor)) {
12653 matchedRule.SetBorderColor := item.SetBorderColor
12654 }
12655 If (not StrLen(matchedRule.SetTextColor)) {
12656 matchedRule.SetTextColor := item.SetTextColor
12657 }
12658 If (not StrLen(matchedRule.SetFontSize)) {
12659 matchedRule.SetFontSize := 32
12660 }
12661
12662 itemName := item.LabelLines[1]
12663 itemBase := item.LabelLines[2]
12664 bgColor := matchedRule.SetBackgroundColor
12665 borderColor := matchedRule.SetBorderColor
12666 fontColor := matchedRule.SetTextColor
12667 fontSize := matchedRule.SetFontSize
12668
12669 If (advanced) {
12670 filterName := RegExReplace(Globals.Get("CurrentItemFilter"), "i).*\\(.*)(\.filter)","$1")
12671 commentsJSON := DebugPrintArray(matchedRule, false)
12672
12673 comments := ""
12674 For key, val in matchedRule.Comments {
12675 comments .= val "`n"
12676 }
12677
12678 conditions := ""
12679 rarities := ["Normal", "Magic", "Rare", "Unique"]
12680 For key, val in matchedRule.Conditions {
12681 If (val.operator) {
12682 cLine := val.name
12683 If (val.name = "Rarity") {
12684 cLine .= " " val.operator " " rarities[val.value]
12685 } Else {
12686 cLine .= " " val.operator " " val.value
12687 }
12688 }
12689 Else {
12690 cLine := val.name ": "
12691 indent := StrLen(cLine)
12692 count := 0
12693 For k, v in val.values {
12694 cLine .= """" v """"
12695
12696 count++
12697 If (count = 5) {
12698 cLine .= "`n" StrPad("", indent)
12699 count := 0
12700 } Else {
12701 cLine .= ", "
12702 }
12703 }
12704 }
12705
12706 conditions .= cLine "`n"
12707 }
12708 conditions := RegExReplace(Trim(conditions), "i),(\n|\r|\s)+?$")
12709
12710 line := "--------------------------------------------"
12711 tt := "Loaded Item Filter: """ filterName """`n`n"
12712 tt .= "Inline comments:" "`n" line "`n"
12713 tt .= comments "`n"
12714 tt .= "Matching conditions:" "`n" line "`n"
12715 tt .= conditions "`n`n"
12716 tt .= " Disclaimer: Matching explicit mods is only possible for magic items. In rare" "`n"
12717 tt .= " cases this can cause a wrong match, depending on the used filter."
12718
12719 ShowToolTip(tt)
12720 }
12721
12722 MouseGetPos, CurrX, CurrY
12723 If (advanced) {
12724 Run "%A_AhkPath%" "%A_ScriptDir%\lib\PoEScripts_ItemFilterNamePlate.ahk" "%itemName%" "%itemBase%" "%bgColor%" "%borderColor%" "%fontColor%" "%fontSize%" "%CurrX%" "%CurrY%" "1"
12725 } Else {
12726 Run "%A_AhkPath%" "%A_ScriptDir%\lib\PoEScripts_ItemFilterNamePlate.ahk" "%itemName%" "%itemBase%" "%bgColor%" "%borderColor%" "%fontColor%" "%fontSize%" "%CurrX%" "%CurrY%"
12727 }
12728
12729}
12730
12731CompareNumValues(num1, num2, operator = "=") {
12732 res := 0
12733 If (operator = "=") {
12734 res := num1 = num2
12735 } Else If (operator = "==") {
12736 res := num1 == num2
12737 } Else If (operator = ">=") {
12738 res := num1 >= num2
12739 } Else If (operator = ">") {
12740 res := num1 > num2
12741 } Else If (operator = "<=") {
12742 res := num1 <= num2
12743 } Else If (operator = "<") {
12744 res := num1 < num2
12745 }
12746 Return res
12747}
12748
12749StartLutbot:
12750 global LutBotSettings := class_EasyIni(A_MyDocuments "\AutoHotKey\LutTools\settings.ini")
12751
12752 If (not FileExist(A_MyDocuments "\AutoHotKey\LutTools\lite.ahk")) {
12753 _project := Globals.Get("ProjectName")
12754 MsgBox, 0x14, %_project% - Lutbot lite.ahk missing, The Lutbot lite macro cannot be executed since its script file is missing,`nopen download website? ("http://lutbot.com/#/ahk")
12755 IfMsgBox Yes
12756 {
12757 OpenWebPageWith(AssociatedProgram("html"), "http://lutbot.com/#/ahk")
12758 }
12759 } Else {
12760 Run "%A_AhkPath%" "%A_MyDocuments%\AutoHotKey\LutTools\lite.ahk"
12761 }
12762
12763 If (Opts.Lutbot_WarnConflicts) {
12764 CheckForLutBotHotkeyConflicts(ShowAssignedHotkeys(true), LutBotSettings)
12765 }
12766
12767 SetTimer, StartLutbot, Off
12768Return
12769
12770OpenLutbotDocumentsFolder:
12771 ;OpenUserSettingsFolder("Lutbot", A_MyDocuments "\AutoHotKey\LutTools")
12772 OpenUserSettingsFolder("Lutbot", LutBotSettings.variables.launcherPath)
12773Return
12774
12775CheckForLutBotHotkeyConflicts(hotkeys, config) {
12776 conflicts := []
12777
12778 For key, val in config.hotkeys {
12779 If (RegExMatch(key, "i)superLogout|logout|options")) {
12780 conflict := {}
12781 VKey := KeyNameToKeyCode(val, 0)
12782 assignedLabel := GetAssignedHotkeysLabel(key, val, vkey, "on")
12783
12784 s1 := RegExReplace(val, "([-+^*$?\|&()])", "\$1")
12785 foundConflict := false
12786 For k, v in hotkeys {
12787 s2 := RegExReplace(v[6], "([-+^*$?\|&()])", "\$1")
12788 If (RegExmatch(Trim(val), "i)^" Trim(s2) "$")) {
12789 foundConflict := true
12790 Break
12791 }
12792 }
12793
12794 If (StrLen(assignedLabel) or foundConflict) {
12795 conflict.name := key
12796 conflict.hkey := val
12797 conflict.vkey := vkey
12798 conflict.assignedLabel := assignedLabel
12799 conflicts.push(conflict)
12800 }
12801 }
12802 }
12803
12804 If (conflicts.MaxIndex()) {
12805 project := Globals.Get("ProjectName")
12806 msg := project " detected a hotkey conflict with the Lutbot lite macro, "
12807 msg .= "`n" "which should be resolved before playing the game."
12808 msg .= "`n`n" "Conflicting hotkey(s) from Lutbot:"
12809 For key, val in conflicts {
12810 msg .= "`n" "- Lutbots """ val.name """ (" val.hkey ") conflicts with """ val.assignedLabel """"
12811 }
12812
12813 MsgBox, 16, Lutbot lite - %project% conflict, %msg%
12814 }
12815}
12816
12817SaveAssignedHotkey(label, key, vkey, state) {
12818 hk := {}
12819 hk.key := key
12820 hk.vkey := vkey
12821 hk.state := state
12822
12823 obj := Globals.Get("AssignedHotkeys")
12824 obj[label] := hk
12825 Globals.Set("AssignedHotkeys", obj)
12826}
12827
12828RemoveAssignedHotkey(label) {
12829 haystack := Globals.Get("AssignedHotkeys")
12830
12831 For k, v in haystack {
12832 If (k = label) {
12833 v.vkey := ""
12834 v.key := ""
12835 Globals.Set("AssignedHotkeys", haystack)
12836 Return
12837 }
12838 }
12839}
12840
12841GetAssignedHotkeysLabel(label, key, vkey, ByRef state) {
12842 haystack := Globals.Get("AssignedHotkeys")
12843
12844 For k, v in haystack {
12845 If (v.vkey = vkey) {
12846 state := v.state
12847 Return k
12848 }
12849 }
12850}
12851
12852GetAssignedHotkeysEnglishKey(vkey) {
12853 haystack := ShowAssignedHotkeys(true)
12854
12855 For k, v in haystack {
12856 If (v[5] = vkey) {
12857 Return haystack[k]
12858 }
12859 }
12860}
12861
12862AssignHotKey(Label, key, vkey, enabledState = "on") {
12863 assignedState := ""
12864 assignedLabel := GetAssignedHotkeysLabel(Label, key, vkey, assignedState)
12865
12866 If (assignedLabel = Label and enabledState = assignedState) {
12867 ; new hotkey is already assigned to the target label
12868
12869 Return
12870 } Else If (assignedLabel = Label and enabledState != assignedState) {
12871 ; new hotkey is already assigned but has a different state (enabled/disabled)
12872 Hotkey, %VKey%, %Label%, UseErrorLevel %enabledState%
12873 SaveAssignedHotkey(Label, key, vkey, enabledState)
12874 } Else If (StrLen(assignedLabel)) {
12875 ; new hotkey is already assigned to a different label
12876 ; the old label will be unassigned unless prevented
12877 Hotkey, %VKey%, %Label%, UseErrorLevel %stateValue%
12878 If (not ErrorLevel) {
12879 SaveAssignedHotkey(Label, key, vkey, enabledState)
12880 RemoveAssignedHotkey(assignedLabel)
12881 ShowHotKeyConflictUI(GetAssignedHotkeysEnglishKey(VKey), VKey, Label, assignedLabel, false)
12882 }
12883 } Else {
12884 ; new hotkey is not assigned to any label yet
12885 If (enabledState != "off") {
12886 ; only assign it when it's enabled
12887 Hotkey, %VKey%, %Label%, UseErrorLevel %stateValue%
12888 SaveAssignedHotkey(Label, key, vkey, enabledState)
12889 }
12890 }
12891
12892 If (ErrorLevel) {
12893 If (errorlevel = 1)
12894 str := str . "`nASCII " . VKey . " - 1) The Label parameter specifies a nonexistent label name."
12895 Else If (errorlevel = 2)
12896 str := str . "`nASCII " . VKey . " - 2) The KeyName parameter specifies one or more keys that are either not recognized or not supported by the current keyboard layout/language. Switching to the english layout should solve this for now."
12897 Else If (errorlevel = 3)
12898 str := str . "`nASCII " . VKey . " - 3) Unsupported prefix key. For example, using the mouse wheel as a prefix in a hotkey such as WheelDown & Enter is not supported."
12899 Else If (errorlevel = 4)
12900 str := str . "`nASCII " . VKey . " - 4) The KeyName parameter is not suitable for use with the AltTab or ShiftAltTab actions. A combination of two keys is required. For example: RControl & RShift::AltTab."
12901 Else If (errorlevel = 5)
12902 str := str . "`nASCII " . VKey . " - 5) The command attempted to modify a nonexistent hotkey."
12903 Else If (errorlevel = 6)
12904 str := str . "`nASCII " . VKey . " - 6) The command attempted to modify a nonexistent variant of an existing hotkey. To solve this, use Hotkey IfWin to set the criteria to match those of the hotkey to be modified."
12905 Else If (errorlevel = 50)
12906 str := str . "`nASCII " . VKey . " - 50) Windows 95/98/Me: The command completed successfully but the operating system refused to activate the hotkey. This is usually caused by the hotkey being "" ASCII " . int . " - in use"" by some other script or application (or the OS itself). This occurs only on Windows 95/98/Me because on other operating systems, the program will resort to the keyboard hook to override the refusal."
12907 Else If (errorlevel = 51)
12908 str := str . "`nASCII " . VKey . " - 51) Windows 95/98/Me: The command completed successfully but the hotkey is not supported on Windows 95/98/Me. For example, mouse hotkeys and prefix hotkeys such as a & b are not supported."
12909 Else If (errorlevel = 98)
12910 str := str . "`nASCII " . VKey . " - 98) Creating this hotkey would exceed the 1000-hotkey-per-script limit (however, each hotkey can have an unlimited number of variants, and there is no limit to the number of hotstrings)."
12911 Else If (errorlevel = 99)
12912 str := str . "`nASCII " . VKey . " - 99) Out of memory. This is very rare and usually happens only when the operating system has become unstable."
12913
12914 MsgBox, %str%
12915 }
12916}
12917
12918ShowHotKeyConflictUI(hkeyObj, hkey, hkeyLabel, oldLabel = "", preventedAssignment = false) {
12919 SplashUI.DestroyUI()
12920
12921 Gui, HotkeyConflict:Destroy
12922 Gui, HotkeyConflict:Font,, Consolas
12923
12924 Gui, HotkeyConflict:Add, Edit, w0 h0
12925
12926 Gui, HotkeyConflict:Add, Text, x17 w150 h20, Label
12927 Gui, HotkeyConflict:Add, Text, x+10 yp+0 w150 h20, Pretty hotkey
12928 Gui, HotkeyConflict:Add, Text, x+10 yp+0 w150 h20, Hotkey
12929 Gui, HotkeyConflict:Add, Text, x+10 yp+0 w150 h20, Virtual Key
12930 line := ""
12931 Loop, 130 {
12932 line .= "-"
12933 }
12934 Gui, HotkeyConflict:Add, Text, x17 y+-5 w630 h20, % line
12935
12936 Gui, HotkeyConflict:Add, Text, x17 y+0 w150 h20, % hkeyLabel
12937 Gui, HotkeyConflict:Add, Hotkey, x+10 yp-3 w150 h20, % hkeyObj[5]
12938 Gui, HotkeyConflict:Add, Text, x+10 yp+3 w150 h20, % hkeyObj[6]
12939 Gui, HotkeyConflict:Add, Text, x+10 yp+0 w150 h20, % hkey
12940
12941 If (StrLen(oldLabel)) {
12942 Gui, HotkeyConflict:Font, bold
12943 Gui, HotkeyConflict:Add, Text, x17 y+15 w400 h20, % "Old Label: " oldLabel
12944 Gui, HotkeyConflict:Font, norm
12945 }
12946
12947 Gui, HotkeyConflict:Font,, Verdana
12948 msg := "The hotkey for the label/function/feature """ hkeyLabel """ was previously used for "
12949 msg .= (StrLen(oldLabel)) ? "the label """ oldLabel """." : "a different one."
12950 If (not preventedAssignment) {
12951 msg .= "`nThe previously created one got overwritten and is now unassigned, please resolve this conflict`nin the settings menu."
12952 } Else {
12953 msg .= "`nThis current hotkey was not assigned, keeping it's previous value. Please resolve this conflict`nin the settings menu if you want to set the hotkey to this function."
12954 }
12955 msg .= "`n`nYou may have to restart the script afterwards."
12956
12957 Gui, HotkeyConflict:Add, Text, x17 y+15 w630 h80, % msg
12958
12959 Gui, HotkeyConflict:Add, Button, w60 x590 gCloseHotkeyConflictGui, Close
12960 Gui, HotkeyConflict:Show, xCenter yCenter w660, Hotkey conflict
12961
12962 WinWaitClose, Hotkey conflict
12963 sleep 5000
12964}
12965
12966
12967; ############ (user) macros #############
12968; macros are being appended here by merge script