├── .gitignore ├── Batch ├── Account_CharSettings.bat └── Account_SortaKinda.bat ├── LICENSE ├── Macros └── Return ├── Notes └── pcall.md ├── README.md └── SND ├── ACT.lua ├── AutoED.lua ├── ContextMenu.lua ├── DiaLoop.lua ├── FC Chest ├── FC Withdraw.lua ├── FCCrystals.lua └── FCGil.lua ├── FC_Buffs.lua ├── FishingRaid.lua ├── GoldSaucer.lua ├── HuntBoard.lua ├── LoV.lua ├── MarketBotty ├── MarketBotty.lua └── README.md ├── MoveNear.lua └── Old retainer sales script.lua /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/settings.json 2 | SND/Experiments/ -------------------------------------------------------------------------------- /Batch/Account_CharSettings.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM First, log in to alts to create their config folders. 4 | REM Make a new folder, named the same as TARGET below. 5 | REM Copy in the files that you want to be account wide. 6 | REM Recommended list: 7 | REM ADDON.DAT COMMON.DAT CONTROL0.DAT CONTROL1.DAT HOTBAR.DAT KEYBIND.DAT LOGFLTR.DAT MACRO.DAT 8 | REM Save this script anywhere and run as administrator. 9 | REM Files in the TARGET folder will be symlinked into all character folders. 10 | REM Script has to be run again after adding another character. 11 | 12 | SET FF_FOLDER=%USERPROFILE%\Documents\My games\FINAL FANTASY XIV - A Realm Reborn 13 | SET TARGET=%FF_FOLDER%\SYMLINK_TARGET 14 | 15 | net session >nul 2>&1 16 | IF %ERRORLEVEL% NEQ 0 ( 17 | ECHO This script must be run as administrator 18 | PAUSE 19 | EXIT /B 20 | ) 21 | 22 | IF EXIST "%TARGET%\" ( 23 | ECHO some useful message 24 | ) ELSE ( 25 | ECHO %TARGET% does not exist! 26 | PAUSE 27 | EXIT /B 28 | ) 29 | 30 | FOR /D %%Q IN ("%FF_FOLDER%"\FFXIV_CHR*) DO ( 31 | FOR %%T IN ("%TARGET%"\*.DAT) DO ( 32 | DEL "%%Q\%%~nxT" 33 | MKLINK "%%Q\%%~nxT" "%%T" 34 | ) 35 | ) 36 | 37 | PAUSE 38 | EXIT /B -------------------------------------------------------------------------------- /Batch/Account_SortaKinda.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM First, log in to alts to create their config folders. 4 | REM Make a copy of your main SortaKinda config, named the same as TARGET below. 5 | REM Save this script anywhere and run as administrator. 6 | REM All individual character configs for SortaKinda will be replaced with directory junctions. 7 | REM Script has to be run again after adding another character. 8 | 9 | SET CONFIG=%APPDATA%\XIVLauncher\pluginConfigs\SortaKinda 10 | SET TARGET=%CONFIG%\SORTA_TARGET 11 | 12 | net session >nul 2>&1 13 | IF %ERRORLEVEL% NEQ 0 ( 14 | ECHO This script must be run as administrator 15 | PAUSE 16 | EXIT /B 17 | ) 18 | 19 | IF EXIST "%TARGET%\" ( 20 | ECHO some useful message 21 | ) ELSE ( 22 | ECHO %TARGET% does not exist! 23 | ECHO If you continue, your SortaKinda config will be lost! 24 | ECHO Directory junctions should still be created. 25 | PAUSE 26 | MKDIR %TARGET% 27 | ) 28 | 29 | FOR /D %%C IN ("%CONFIG%"\*) DO ( 30 | IF "%%~nC" LSS "999" ( 31 | RMDIR /S /Q "%%C" 32 | MKLINK /J "%%C" %TARGET% 33 | ) 34 | ) 35 | 36 | PAUSE 37 | EXIT /B -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Macros/Return: -------------------------------------------------------------------------------- 1 | /return 2 | /pcall SelectYesno true 0 3 | //tp ul'dah 4 | -------------------------------------------------------------------------------- /Notes/pcall.md: -------------------------------------------------------------------------------- 1 | `/pcall AddonName(string) UpdateVisibility(bool) Values` You can find the values with `/tweaks Debug` (case sensitive). 2 | Be very careful though. There are no safety rails here, and you can absolutely send invalid data to the server by clicking buttons that the UI wouldn't let you click. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FFXIV-scripts-and-macros 2 | Scripts and macros for FFXIV, mostly SND Lua 3 | 4 | SND stuff is using the *good* SND fork 5 | 6 |
7 | 8 | Such big plans, but so little time. 9 | -------------------------------------------------------------------------------- /SND/ACT.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Nothing to see here, move along. 3 | Just some Lua to start ACT for no reason at all. 4 | ]] 5 | 6 | os.execute("start \"ACT\" /D \""..os.getenv("ProgramFiles(x86)").."\\Advanced Combat Tracker\\\" \"Advanced Combat Tracker.exe\"") 7 | 8 | --[[ 9 | Yes I know it can be a LOT simpler, and probably just as reliable. 10 | I don't know anyone who moves their program files, and frankly I don't want to. 11 | Does winblows even care about starting directory anymore? Pretty sure that's a holdover from DOS. 12 | Either way, shut up. It's fine. 13 | ]] 14 | -------------------------------------------------------------------------------- /SND/AutoED.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | /waitaddon DON'T_FORGET_TO_PRESS_LUA 3 | /waitaddon DON'T_FORGET_TO_PRESS_LUA 4 | /waitaddon DON'T_FORGET_TO_PRESS_LUA 5 | Automagic grand company expert delivery and purchase script for SomethingNeedDoing 6 | Written by plottingCreeper, with help from Thee 7 | 8 | Requires: 9 | SomethingNeedDoing 10 | Pandora v69 11 | Advanced requirements: 12 | SomethingNeedDoing (Expanded Edition) 13 | Island Sanctuary Automation 14 | --]] 15 | 16 | -- CONFIGURE THESE BEFORE USE 17 | GrandCompany = "auto" -- "Storm", "Flame", "Serpent", "auto" (auto requires SND expanded) 18 | WhatToBuy = "Ventures" -- "Ventures", "Paper", "Sap", "Coke", "MC3", "MC4" 19 | NumberToBuy = "max" -- Can be a number or "max" 20 | UseSealBuff = "yes" -- Whether to use Priority Seal Allowance 21 | VenturesUntil = 10000 22 | AfterVentures = "Paper" --"Paper", "Sap", "Coke", "MC3", "MC4" 23 | TurninArmoury = "yes" -- Might be dodgy if this doesn't align with your game setting. 24 | CharacterSpecificSettings = true 25 | UseGCTicket = true 26 | ReturnTo = "none" -- "FC", "Inn", "none" 27 | Multi = false 28 | FinalPurchase = false 29 | CompletionMessage = true 30 | EnableAR = false 31 | Verbose = true 32 | Debug = true 33 | 34 | -- Advanced configuration. Will probably break things and/or get you banned. 35 | ExpertDeliveryThrottle = "0" -- Probably fine at 0, since I have to wait anyway. 36 | PurchaseThrottle = "2" 37 | TargetThrottle = "1" 38 | 39 | -- There's a better way to handle this, and I kinda know what it is, but I don't know how to do it. One day! 40 | MultiCharacters = { 41 | 'Character Name@Server', 42 | 'Character Name@Server', 43 | 'Character Name@Server', 44 | 'Character Name@Server', 45 | 'Character Name@Server', 46 | 'Character Name@Server', 47 | 'Character Name@Server', 48 | 'Character Name@Server', 49 | } 50 | 51 | -- Super experimental character specific settings. 52 | Characters = { 53 | CharacterName = { WhatToBuy = "Ventures", NumberToBuy = "max", UseSealBuff = "yes", VenturesUntil = 65000, AfterVentures = "Sap", TurninArmoury = yes }, 54 | p = { VenturesUntil = 65000, AfterVentures = "MC4" }, 55 | q = { UseSealBuff = "no", ReturnTo = "FC" }, 56 | Name = { VenturesUntil = 65000 }, 57 | } 58 | 59 | function CharacterSpecific() 60 | CurrentChar = GetCharacterName() 61 | if Verbose then yield("/echo Current character: "..CurrentChar) end 62 | for CharTest, _ in pairs(Characters) do 63 | if string.find(string.gsub(CurrentChar,"%W",""), string.gsub(CharTest,"%W","")) then 64 | CharName = CharTest 65 | CharSpecific = Characters[CharTest] 66 | UsingCharSpecific = true 67 | yield("/echo Found "..CharName) 68 | end 69 | end 70 | if UsingCharSpecific then 71 | if CharSpecific.WhatToBuy then WhatToBuy = CharSpecific.WhatToBuy end 72 | if CharSpecific.NumberToBuy then NumberToBuy = CharSpecific.NumberToBuy end 73 | if CharSpecific.UseSealBuff then UseSealBuff = CharSpecific.UseSealBuff end 74 | if CharSpecific.VenturesUntil then VenturesUntil = CharSpecific.VenturesUntil end 75 | if CharSpecific.AfterVentures then AfterVentures = CharSpecific.AfterVentures end 76 | if CharSpecific.TurninArmoury then TurninArmoury = CharSpecific.TurninArmoury end 77 | if Debug then 78 | if CharSpecific.WhatToBuy then yield("/echo "..CharName.." specific setting: WhatToBuy = "..WhatToBuy) end 79 | if CharSpecific.NumberToBuy then yield("/echo "..CharName.." specific setting: NumberToBuy = "..NumberToBuy) end 80 | if CharSpecific.UseSealBuff then yield("/echo "..CharName.." specific setting: UseSealBuff = "..UseSealBuff) end 81 | if CharSpecific.VenturesUntil then yield("/echo "..CharName.." specific setting: VenturesUntil = "..VenturesUntil) end 82 | if CharSpecific.AfterVentures then yield("/echo "..CharName.." specific setting: AfterVentures = "..AfterVentures) end 83 | if CharSpecific.TurninArmoury then yield("/echo "..CharName.." specific setting: TurninArmoury = "..TurninArmoury) end 84 | yield("/wait 3") 85 | end 86 | else 87 | if Verbose then yield("/echo Using general settings") end 88 | end 89 | end 90 | 91 | function OpenPurchase() 92 | if Verbose then yield("/echo Running OpenPurchase") end 93 | yield("/target "..GC.." Quartermaster ") 94 | yield("/pint ") 95 | yield("/pint ") 96 | yield("/waitaddon GrandCompanyExchange ") 97 | step = "Purchase" 98 | end 99 | 100 | ItemsTable = { 101 | Ventures = { Cost = 200, Page = 0, Tab = 1, Position = 0 }, 102 | Paper = { Cost = 600, Page = 2, Tab = 1, Position = 17 }, 103 | Sap = { Cost = 200, Page = 2, Tab = 4, Position = 30 }, 104 | Coke = { Cost = 200, Page = 2, Tab = 4, Position = 31 }, 105 | MC3 = { Cost = 20000, Page = 2, Tab = 1, Position = 38 }, 106 | MC4 = { Cost = 20000, Page = 2, Tab = 1, Position = 39 }, 107 | } 108 | 109 | function Purchase() 110 | if Verbose then yield("/echo Running purchase") end 111 | CheckSeals() 112 | if WhatToBuy=="Ventures" then 113 | if CheckVentures() >= VenturesUntil then 114 | WhatToBuy = AfterVentures 115 | end 116 | end 117 | Item = ItemsTable[WhatToBuy] 118 | if NumberToBuy=="max" then 119 | Amount = CurrentSeals // Item.Cost 120 | else 121 | Amount = tonumber(NumberToBuy) 122 | end 123 | if WhatToBuy=="Ventures" then 124 | SecondAmount = 0 125 | if (CurrentVentures+Amount)>65000 then 126 | Amount=(65000-CurrentVentures) 127 | end 128 | else 129 | if Amount > 99 then 130 | SecondAmount = Amount - 99 131 | Amount = 99 132 | else 133 | SecondAmount = 0 134 | end 135 | end 136 | if Verbose then yield("/echo "..NumberToBuy.." "..WhatToBuy) end 137 | yield("/pcall GrandCompanyExchange true 1 " .. Item.Page) 138 | yield("/pcall GrandCompanyExchange true 2 " .. Item.Tab) 139 | yield("/pcall GrandCompanyExchange false 0 " .. Item.Position .. " " .. Amount .." 0 True False 0 0 0") 140 | if SecondAmount > 0 then 141 | yield("/wait 1") 142 | yield("/pcall GrandCompanyExchange false 0 " .. Item.Position .. " " .. SecondAmount .." 0 True False 0 0 0") 143 | end 144 | yield("/wait 0.3") 145 | if IsAddonVisible("SelectYesno") then yield("/pcall SelectYesno true 0") end 146 | yield("/wait "..PurchaseThrottle) 147 | QuitPurchase() 148 | step = "OpenDeliver" 149 | end 150 | 151 | function QuitPurchase() 152 | if Verbose then yield("/echo Running QuitPurchase") end 153 | yield("/pcall GrandCompanyExchange true -1") 154 | end 155 | 156 | function OpenDeliver() 157 | if Verbose then yield("/echo Running OpenDeliver") end 158 | yield("/wait "..TargetThrottle) 159 | yield("/target "..GC.." Personnel Officer ") 160 | yield("/pint ") 161 | yield("/pint ") 162 | yield("/waitaddon SelectString") 163 | yield("/click select_string1") 164 | yield("/waitaddon GrandCompanySupplyList ") 165 | step = "Deliver" 166 | end 167 | 168 | function Deliver() 169 | if Verbose then yield("/echo Running Deliver") end 170 | ed = 1 171 | while (ed == 1) do 172 | if TurninArmoury=="yes" then 173 | yield("/pcall GrandCompanySupplyList true 5 1 0") 174 | if Debug then yield("/echo Armoury=yes") end 175 | else 176 | yield("/pcall GrandCompanySupplyList true 5 2 0") 177 | if Debug then yield("/echo Armoury=yes") end 178 | end 179 | yield("/wait 0.1") 180 | if GetNodeText("GrandCompanySupplyList", 5, 2, 4)=="" then 181 | yield("/echo No more items!") 182 | ed = 0 183 | step = "finish" 184 | break 185 | end 186 | CheckSeals() 187 | if Debug then 188 | yield("/echo Current : "..CurrentSeals) 189 | yield("/echo Next Item : "..NextSealValue) 190 | yield("/echo Current+Next : "..(CurrentSeals + NextSealValue)) 191 | yield("/echo Seals Max : "..MaxSeals) 192 | end 193 | if ((CurrentSeals + NextSealValue) > MaxSeals) then 194 | yield("/echo Current+Next:"..(CurrentSeals + NextSealValue)) 195 | ed = 0 196 | step = "OpenPurchase" 197 | break 198 | end 199 | yield("/pcall GrandCompanySupplyList true 1 0 0") 200 | yield("/wait 0.1") 201 | if IsAddonVisible("GrandCompanySupplyReward") then yield("/pcall GrandCompanySupplyReward true 0") end 202 | if IsAddonVisible("Request") then 203 | yield("/echo Request window bug: probably no more items!") 204 | yield("/pcall Request true 1") 205 | ed = 0 206 | step = "finish" 207 | end 208 | if IsAddonVisible("SelectYesno") then 209 | yield("/pcall SelectYesno true 0") 210 | end 211 | yield("/waitaddon GrandCompanySupplyList ") 212 | end 213 | QuitDeliver() 214 | end 215 | 216 | function QuitDeliver() 217 | if Verbose then yield("/echo Running QuitDeliver") end 218 | yield("/pcall GrandCompanySupplyList true -1") 219 | yield("/waitaddon SelectString") 220 | yield("/pcall SelectString true -1 ") 221 | end 222 | 223 | function SealBuff() 224 | if HasStatus("Priority Seal Allowance")==false then 225 | if IsAddonVisible("GrandCompanySupplyList") then 226 | QuitDeliver() 227 | ed = 0 228 | end 229 | yield("/wait 1") 230 | yield("/item Priority Seal Allowance") 231 | step = "OpenDeliver" 232 | yield("/target "..GC.." Personnel Officer ") 233 | yield("/target "..GC.." Personnel Officer ") 234 | yield("/target "..GC.." Personnel Officer ") 235 | yield("/target "..GC.." Personnel Officer ") 236 | end 237 | end 238 | 239 | function CheckSeals(input) 240 | if IsAddonVisible("GrandCompanySupplyList") then 241 | NextSealValue = string.gsub(GetNodeText("GrandCompanySupplyList", 5, 2, 4),",","") 242 | NextSealValue = tonumber(NextSealValue) 243 | if HasStatus("Priority Seal Allowance") then 244 | NextSealValue = math.floor(NextSealValue * 1.15) + 1 245 | else if HasStatus("Seal Sweetener") then 246 | NextSealValue = math.floor(NextSealValue * 1.10) + 1 247 | end 248 | end 249 | RawSeals = string.gsub(GetNodeText("GrandCompanySupplyList", 23),",","") 250 | CurrentSeals = tonumber(string.sub(RawSeals,1,-7)) 251 | MaxSeals = tonumber(string.sub(RawSeals,-5,-1)) 252 | end 253 | if IsAddonVisible("GrandCompanyExchange") then 254 | CurrentSeals = string.gsub(GetNodeText("GrandCompanyExchange", 52),",","") 255 | CurrentSeals = tonumber(CurrentSeals) 256 | end 257 | output = CurrentSeals 258 | if input == "max" then 259 | output = MaxSeals 260 | end 261 | return output 262 | end 263 | 264 | function CheckVentures() 265 | yield("/pcall GrandCompanyExchange true 1 0") 266 | yield("/pcall GrandCompanyExchange true 2 1") 267 | yield("/wait 0.1") 268 | CurrentVentures = string.gsub(GetNodeText("GrandCompanyExchange", 2, 1, 3),",","") 269 | CurrentVentures = tonumber(CurrentVentures) 270 | return CurrentVentures 271 | end 272 | 273 | function GetCloser() 274 | if IsAddonVisible("_TargetInfoMainTarget") then 275 | yield("/lockon on") 276 | yield("/automove on") 277 | end 278 | end 279 | 280 | function LeaveInn() 281 | if IsInZone(177) or IsInZone(178) or IsInZone(179) then 282 | yield("/target HeavyOaken Door") 283 | yield("/lockon on") 284 | yield("/automove on ") --TODO check coordinates instead of wait? 285 | yield("/pint") 286 | yield("/waitaddon Nowloading ") 287 | yield("/waitaddon NamePlate ") 288 | end 289 | end 290 | 291 | function InnToGC() 292 | if IsInZone(128) then 293 | yield("/visland moveto 12 40 12") 294 | yield("/visland moveto 1.6 40 19.35") 295 | yield("/visland moveto 2.4 40 73.2") 296 | yield("/visland moveto 28 40 73.8") 297 | yield("/visland moveto 94 40 75.2") 298 | end 299 | if IsInZone(130) then 300 | yield("/visland moveto ") 301 | end 302 | if IsInZone(132) then 303 | end 304 | end 305 | 306 | function GCToInn() 307 | if IsInZone(128) then 308 | yield("/visland moveto 81.5 40 74.1") 309 | yield("/visland moveto 1 40 71.1") 310 | yield("/visland moveto 1.2 40 18.2") 311 | yield("/visland moveto 13.2 40 12.6") 312 | end 313 | if IsInZone(130) then 314 | end 315 | if IsInZone(132) then 316 | end 317 | end 318 | 319 | function EnterInn() 320 | if IsInZone(128) then 321 | GC="Storm" 322 | end 323 | if IsInZone(130) then 324 | GC="Flame" 325 | yield("/visland moveto ") 326 | end 327 | if IsInZone(132) then 328 | yield("/target Antoinaut") 329 | yield("/pint") 330 | end 331 | end 332 | 333 | c = 1 334 | 335 | :: Start :: 336 | yield("/waitaddon NamePlate ") 337 | yield("/echo AutoED is starting...") 338 | step = "Startup" 339 | if IsAddonVisible("GrandCompanyExchange") then 340 | step = "Purchase" 341 | else 342 | if IsAddonVisible("GrandCompanySupplyList") then 343 | step = "Deliver" 344 | end 345 | end 346 | 347 | if CharacterSpecificSettings then CharacterSpecific() end 348 | 349 | if IsInZone(177) or IsInZone(178) or IsInZone(179) then 350 | LeaveInn() 351 | InnToGC() 352 | end 353 | if not (IsInZone(128) or IsInZone(130) or IsInZone(132)) then 354 | if UseGCTicket then 355 | pcall(yield("/item Maelstrom Aetheryte Ticket")) 356 | pcall(yield("/item Twin Adder Aetheryte Ticket")) 357 | pcall(yield("/item Immortal Flames Aetheryte Ticket")) 358 | else 359 | yield("/return") 360 | end 361 | end 362 | 363 | if GrandCompany=="auto" then 364 | yield("/echo Autodetecting GC") 365 | if IsInZone(128) then GC="Storm" end 366 | if IsInZone(130) then GC="Flame" end 367 | if IsInZone(132) then GC="Serpent" end 368 | if ( GC=="Storm" or GC=="Flame" or GC=="Serpent" )==false then 369 | yield("/echo GC = "..GC) 370 | yield("/echo ERROR: Auto is set but you are not in a GC zone!") 371 | step = "finish" 372 | end 373 | else 374 | GC = GrandCompany 375 | end 376 | 377 | if UseSealBuff=="yes" then SealBuff() end 378 | 379 | if Verbose then yield("/echo Entering main loop.") end 380 | 381 | while (step~="finish") do 382 | if step=="OpenDeliver" then OpenDeliver() end 383 | if step=="Deliver" then Deliver() end 384 | if step=="QuitDeliver" then QuitDeliver() end 385 | if step=="OpenPurchase" then OpenPurchase() end 386 | if step=="Purchase" then Purchase() end 387 | if step=="QuitPurchase" then QuitPurchase() end 388 | if step=="Startup" then step = "OpenDeliver" end 389 | if Debug then yield("/echo DEBUG: step = "..step) end 390 | yield ("/wait 1") 391 | end 392 | 393 | if FinalPurchase then 394 | OpenPurchase() 395 | Purchase() 396 | end 397 | 398 | if ReturnTo == "Inn" then 399 | GCToInn() 400 | EnterInn() 401 | end 402 | if ReturnTo == "FC" then 403 | yield("/tp Estate Hall") 404 | yield("/wait 10") 405 | yield("/waitaddon NamePlate ") 406 | yield("/automove on ") 407 | yield("/target Entrance ") 408 | yield("/lockon on ") 409 | end 410 | 411 | if Multi then 412 | if string.find(string.gsub(GetCharacterName(),"%W",""), string.gsub(MultiCharacters[c],"%W","")) then 413 | c = c + 1 414 | end 415 | if MultiCharacters[c] then 416 | yield("/ays relog "..MultiCharacters[c]) 417 | goto Start 418 | end 419 | end 420 | 421 | if CompletionMessage then 422 | yield("/echo --------------------") 423 | yield("/echo AutoED has finished!") 424 | yield("/echo --------------------") 425 | end 426 | 427 | if EnableAR then yield("/ays multi") end 428 | 429 | yield("/pcraft stop") -------------------------------------------------------------------------------- /SND/ContextMenu.lua: -------------------------------------------------------------------------------- 1 | 2 | function ContextMenu(input) 3 | string = input 4 | menu = "ContextMenu" 5 | if not IsAddonVisible("ContextMenu") then yield("/wait 0.1") end 6 | ::Retry:: 7 | if IsAddonVisible(menu) and string then 8 | for i=1, 21 do 9 | entry = GetNodeText(menu, 2, i, 6) 10 | if entry==6 then break end 11 | if entry==string then 12 | click = i-1 13 | break 14 | end 15 | end 16 | if click then 17 | yield("/pcall "..menu.." true 0 "..click) 18 | if string == "Second Tier" then 19 | string = input 20 | click = nil 21 | menu = "AddonContextSub" 22 | yield("/wait 0.1") 23 | goto Retry 24 | end 25 | elseif string~="Second Tier" then 26 | string = "Second Tier" 27 | goto Retry 28 | end 29 | end 30 | if click then return true else return false end 31 | end 32 | -------------------------------------------------------------------------------- /SND/DiaLoop.lua: -------------------------------------------------------------------------------- 1 | routename = "paste route name here" 2 | 3 | ::Wait:: 4 | while not IsInZone(886) do 5 | yield("/wait 10") 6 | end 7 | yield("/visland stop") 8 | yield("/wait 3") 9 | 10 | ::Enter:: 11 | if IsInZone(886) then 12 | if NeedsRepair(99) then 13 | while not IsAddonVisible("Repair") do 14 | yield("/generalaction repair") 15 | yield("/wait 0.5") 16 | end 17 | yield("/pcall Repair true 0") 18 | yield("/wait 0.1") 19 | if IsAddonVisible("SelectYesno") then 20 | yield("/pcall SelectYesno true 0") 21 | yield("/wait 0.1") 22 | end 23 | while GetCharacterCondition(39) do yield("/wait 1") end 24 | yield("/wait 1") 25 | yield("/pcall Repair true -1") 26 | end 27 | while GetCharacterCondition(34, false) and GetCharacterCondition(45, false) do 28 | if IsAddonVisible("ContentsFinderConfirm") then 29 | yield("/pcall ContentsFinderConfirm true 8") 30 | elseif GetTargetName()=="" then 31 | yield("/target Aurvael") 32 | elseif GetCharacterCondition(32, false) then 33 | yield("/pinteract") 34 | elseif IsAddonVisible("Talk") then 35 | yield("/click talk") 36 | elseif IsAddonVisible("SelectString") then 37 | yield("/pcall SelectString true 0") 38 | elseif IsAddonVisible("SelectYesno") then 39 | yield("/pcall SelectYesno true 0") 40 | end 41 | yield("/wait 0.5") 42 | end 43 | while GetCharacterCondition(35, false) do yield("/wait 1") end 44 | while GetCharacterCondition(35) do yield("/wait 1") end 45 | yield("/wait 3") 46 | end 47 | 48 | ::Move:: 49 | if IsInZone(939) then 50 | while GetCharacterCondition(77, false) do 51 | if GetCharacterCondition(4, false) then 52 | yield("/mount \"Company Chocobo\"") 53 | yield("/wait 3") 54 | else 55 | yield("/generalaction jump") 56 | yield("/wait 0.5") 57 | end 58 | end 59 | yield("/visland movedir 0 20 0") 60 | yield("/wait 1") 61 | yield("/visland movedir 50 0 -50") 62 | yield("/wait 1") 63 | yield("/visland moveto -235 30 -435") 64 | yield("/wait 1") 65 | while IsMoving() do yield("/wait 1") end 66 | yield("/visland exec "..routename) 67 | end 68 | 69 | goto Wait 70 | -------------------------------------------------------------------------------- /SND/FC Chest/FC Withdraw.lua: -------------------------------------------------------------------------------- 1 | chest_tabs_to_empty = { 2, 3, 4 } 2 | 3 | for _, tab in ipairs(chest_tabs_to_empty) do 4 | yield("/pcall FreeCompanyChest true 0 " .. tab -1) 5 | yield("/wait 1") 6 | for slot = 1, 50 do 7 | if GetNodeText("FreeCompanyChest", 5)=="0/50" then break end 8 | yield("/pcall FreeCompanyChest true 4 " .. slot -1) 9 | for tick = 1, 10 do 10 | if IsAddonVisible("ContextMenu") then 11 | yield("/pcall ContextMenu true 0 0 0 0 0") 12 | yield("/wait 1.1") 13 | break 14 | else 15 | yield("/wait 0.01") 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /SND/FC Chest/FCCrystals.lua: -------------------------------------------------------------------------------- 1 | main = "" 2 | keep_gil_in_fc_chest = 100000000 3 | keep_gil_on_alts = 0 4 | 5 | file_characters = os.getenv("appdata").."\\XIVLauncher\\pluginConfigs\\SomethingNeedDoing\\my_characters.txt" 6 | if io.open(file_characters,"r")~=nil then 7 | file_characters = io.input(file_characters) 8 | main = file_characters:read("l") 9 | file_characters:close() 10 | end 11 | 12 | yield("/echo Main character set to: "..main) 13 | if string.find(main, GetCharacterName()) then 14 | yield("/echo On main character!") 15 | on_main = true 16 | end 17 | 18 | if IsAddonVisible("FreeCompanyChest") then 19 | if on_main then 20 | yield("/pcall FreeCompanyChest true 1") 21 | for i=1, 18 do 22 | if tonumber(GetNodeText("FreeCompanyChest", 27-i, 1)) > 1 then 23 | yield("/pcall FreeCompanyChest true 4 "..i-1) 24 | yield("/wait 0.1") 25 | if IsAddonVisible("ContextMenu") then 26 | yield("/pcall ContextMenu true 0 0 0 0 0") 27 | yield("/wait 1") 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /SND/FC Chest/FCGil.lua: -------------------------------------------------------------------------------- 1 | main = "" 2 | keep_gil_in_fc_chest = 100000000 3 | keep_gil_on_alts = 0 4 | 5 | file_characters = os.getenv("appdata").."\\XIVLauncher\\pluginConfigs\\SomethingNeedDoing\\my_characters.txt" 6 | if io.open(file_characters,"r")~=nil then 7 | file_characters = io.input(file_characters) 8 | main = file_characters:read("l") 9 | file_characters:close() 10 | end 11 | 12 | yield("/echo Main character set to: "..main) 13 | if string.find(main, GetCharacterName()) then 14 | yield("/echo On main character!") 15 | on_main = true 16 | end 17 | 18 | if IsAddonVisible("FreeCompanyChest") then 19 | yield("/pcall FreeCompanyChest true 2") 20 | yield("/waitaddon Bank") 21 | if on_main then 22 | gil = string.gsub(GetNodeText("Bank",20),"%D","") - keep_gil_in_fc_chest 23 | yield("/pcall Bank true 2") 24 | yield("/wait 0.1") 25 | else 26 | gil = string.gsub(GetNodeText("Bank",15),"%D","") - keep_gil_on_alts 27 | end 28 | if tonumber(gil) > 0 then 29 | yield("/pcall Bank true 3 "..gil) 30 | yield("/wait 0.1") 31 | yield("/pcall Bank true 0") 32 | else 33 | yield("/pcall Bank true 1") 34 | end 35 | end 36 | 37 | -------------------------------------------------------------------------------- /SND/FC_Buffs.lua: -------------------------------------------------------------------------------- 1 | is_use_level_2_buffs = true 2 | actions_to_buy = { 3 | "heat of battle", 4 | "reduced rates", 5 | } 6 | 7 | ---------------------------------------------------------- 8 | 9 | actions_list = { 10 | "heat battle", 11 | "earth water", 12 | "helping hand", 13 | "best friend", 14 | "mark up", 15 | "seal sweetener", 16 | "jack pot", 17 | "brave world", 18 | "live land", 19 | "what you", 20 | "eat hand", 21 | "control control", 22 | "which binds", 23 | "meat mead", 24 | "proper care", 25 | "fleet foot", 26 | "reduced rates", 27 | } 28 | 29 | selected_action = 0 30 | 31 | function NextBuff() 32 | action_click = nil 33 | if selected_action < #actions_to_buy then 34 | selected_action = selected_action + 1 35 | else 36 | selected_action = 1 37 | end 38 | for number, name in pairs(actions_list) do 39 | if string.find(actions_to_buy[selected_action],string.gsub(name, ".+ ", "")) or string.find(actions_to_buy[selected_action],string.gsub(name, " .+", "")) then 40 | if is_use_level_2_buffs then 41 | action_click = number + 16 42 | else 43 | action_click = number - 1 44 | end 45 | break 46 | end 47 | end 48 | if not action_click then yield("/pcall stop") end 49 | end 50 | 51 | for i, _ in pairs(actions_to_buy) do 52 | actions_to_buy[i] = string.gsub(string.lower(actions_to_buy[i]),"%A","") 53 | end 54 | 55 | while not IsAddonVisible("FreeCompanyExchange") do 56 | if GetTargetName()~="OIC Quartermaster" then 57 | yield("/target OIC Quartermaster") 58 | elseif GetCharacterCondition(32, false) then 59 | yield("/pinteract") 60 | elseif IsAddonVisible("Talk") then 61 | yield("/click talk") 62 | elseif IsAddonVisible("SelectString") then 63 | yield("/pcall SelectString true 0") 64 | end 65 | yield("/wait 0.5") 66 | end 67 | 68 | while not IsAddonReady("FreeCompanyExchange") do yield("/wait 0.1") end 69 | 70 | while IsAddonVisible("FreeCompanyExchange") do 71 | action_inventory = GetNodeText("FreeCompanyExchange", 6) 72 | action_max = string.gsub(action_inventory, "%d+/", "") 73 | action_current = string.gsub(action_inventory, "/%d+", "") 74 | buy_amount = tonumber(action_max) - tonumber(action_current) 75 | if not (buy_amount>0) then 76 | yield("/pcall FreeCompanyExchange true -1") 77 | break 78 | end 79 | if IsAddonVisible("SelectYesno") then 80 | yield("/pcall SelectYesno true 0") 81 | elseif buy_amount>0 then 82 | NextBuff() 83 | yield("/pcall FreeCompanyExchange true 2 "..action_click.."u") 84 | end 85 | yield("/wait 0.5") 86 | end 87 | -------------------------------------------------------------------------------- /SND/FishingRaid.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Automatic ocean fishing script. Options for AutoRetainer and returning to inn room between trips. 4 | 5 | Script runs using: 6 | SomethingNeedDoing (Expanded Edition): https://puni.sh/api/repository/croizat 7 | Required plugins: 8 | Autohook: https://love.puni.sh/ment.json 9 | Pandora: https://love.puni.sh/ment.json 10 | Visland: https://puni.sh/api/repository/veyn 11 | Required for major features: 12 | Teleporter: main repository 13 | Simple Tweaks: main repository 14 | Optional plugins: 15 | AutoRetainer: https://love.puni.sh/ment.json 16 | Discard Helper: https://plugins.carvel.li/ 17 | YesAlready: https://love.puni.sh/ment.json 18 | ]] 19 | 20 | -- Before getting on the boat 21 | do_repair = "npc" --"npc", "self". Add a number to set threshhold; "npc 10" to only repair if under 10% 22 | buy_baits = 100 --Minimum number of baits you want. Will buy 99 at a time. 23 | boat_route = "indigo" --"indigo", "ruby", "random" 24 | is_equip_recommended_gear = true --Run /equiprecommended 25 | 26 | -- Just got on the boat 27 | food_to_eat = false --Name of the food you want to use, in quotes. DOES NOT CHECK ITEM COUNT YET 28 | is_wait_to_move = true --Wait for the barrier to drop before moving to the side of the boat. 29 | is_adjust_z = true --true might cause stuttery movement, false might cause infinite movement. Good luck. 30 | 31 | -- Fishing 32 | bait_and_switch = false --Uses /bait command from SimpleTweaks 33 | force_autohook_presets = true 34 | is_recast_on_spectral = true --Cancels cast when spectral current starts 35 | is_leveling = "auto" --false, "auto" 36 | is_spam_next_zone_bait = false --Spam chat with the bait for the next zone. Might not work? 37 | 38 | -- Getting off the boat 39 | score_screen_delay = 3 --How long in seconds to wait once the final score is displayed. 40 | is_discard = false --Requires Discard Helper. Can set to "spam" to run during cutscenes. 41 | is_desynth = true --Will enable Extended Desynthesis Window in Simple Tweaks. Optionally runs faster with YesAlready. 42 | 43 | -- Waiting for next boat 44 | wait_location = false --false, "inn", "fc", "private", "apartment", "shared", "Shared Estate (Plot [#], [#]nd ward)" 45 | is_ar_while_waiting = false --AutoRetainer multimode enabled in between fishing trips. 46 | 47 | -- Other script settings 48 | is_verbose = true --General status messages 49 | is_debug = false --Spammy status messages 50 | fishing_character = "auto" --"First Last@Server", "auto" 51 | movement_method = "visland" --"visland" (navmesh coming soon) 52 | is_last_minute_entry = false --Waits until 5 minutes before the boat leaves 53 | is_single_run = false --Only go on 1 fishing trip, then stop. 54 | 55 | -- Spend white gatherer scrips 56 | spend_scrips_when_above = false 57 | scrip_category = 1 58 | scrip_subcategory = 1 59 | scrip_item_to_buy = "Hi-Cordial" 60 | 61 | -- What to do when bags are full. 62 | bags_full = { 63 | "/echo Bags full!", 64 | "/leaveduty", 65 | "/pcraft stop", 66 | } 67 | 68 | ------------------------------------------------------------------------ 69 | 70 | function AutoHookPresets() 71 | if force_autohook_presets then 72 | if is_leveling then 73 | if GetLevel()>=60 then 74 | UseAutoHookAnonymousPreset("AH4_H4sIAAAAAAAACu1aTXPaMBD9K4zPZAZMME0OnUkJ+ZjSlAHaHJgcVCywBmG5spyEZvjv1SJLRMYGckon1Q1Wb98+adfarMmLd5EJ1kWpSLuzuXf+4vVi9IviC0q9c8EzXPdgsU9ivF0M9dKAE8aJWHnnzbp3m/aepzQLcbg1A2ytKL4xNo2AY/PBh0/XyTjiOI0YlYzNRsMi3M9YLaRxUEk3ypYlm7HUdIK2xapdGaV4KsB7u+gfjsh4SBDde4KnOfaKpFFvhdOdA2q3rQMK9LmiBR5FZCa+ILIRBoZUG0YCTReSS+7GJntNdZZTDZAgOJ6WZdp2DuxU+dqfkz+4i4TKs+XiF7Lbyl3GEaIELdIr9Mg4eFkGrb5Vt+1DPGWPWOKbcCb7pLasqMEaSudZcJQXOxzRmI2eUHIbi4wIwuJrRGJYupXeJ7II79BSHojnSc8SdJ+logI9kCJwObt34lWsK77N+jbaKJE1xxHtZpzjWBylsOBzpM7SSN4EapLE8yEi4UNNYwpbKA0oN2Ii5CFtsj5+xFR+q30OGpKwT1LxfQa7kFmfqPwAXkv3zzrNjlH/E/NUlizFtX7GMUS6Y3yJ6A1ji80jmtfzPUab72AHvatEep82O1D3GjMSnMXzElSj9QrVx3Mch4ivqoA/UnzJMlmOSoOqSGUpqDBVO0M0lSCLzQ9Am/LbUXa0Z7VahRpzkrxRV6ftt4znG5VZvnu05biRYMnFTGDeRdk8ki1oCfcb3MTmadGJHuLfGeE4lPeFyOC2Cz5tc+ay/7GyD3BQXbj2wQwUakPwdCubklkB1nuAp1dZjbIKj1fKt04Q0xTlHl0GsyvwkHsBVSr5EMcuUNOUXlsW9+XN+KZAW5Eik3h3BX/UK3hdL2/Np6Y1D9H8SfZi15NdT3Y9+eP/ReZ6suvJbiz6J3ty2/Tkr5zIt3puSnZTspuSXUd2U7Kbkt2LyvfoyIHpyAOaLZPavRuU3ctr9/L6v/jpwg3KblB2g/L7DsoP+nfm/F9SJsagOvXkYf0XukpR2xcjAAA=") 75 | else 76 | UseAutoHookAnonymousPreset("AH4_H4sIAAAAAAAACu1aTXPaMBD9K4zPZAZMME2ml5SQjylNGaDNgclBxQJrEJYry0lohv9eLbJEZGwgp3RS3WD19u2Tdq3Nmrx4F5lgXZSKtDube+cvXi9Gvyi+oNQ7FzzDdQ8W+yTG28VQLw04YZyIlXferHu3ae95SrMQh1szwNaK4htj0wg4Nh98+HSdjCOO04hRydhsNCzC/YzVQhoHlXSjbFmyGUtNJ2hbrNqVUYqnAry3i/7hiIyHBNG9J3iaY69IGvVWON05oHbbOqBAnyta4FFEZuILIhthYEi1YSTQdCG55G5sstdUZznVAAmC42lZpm3nwE6Vr/05+YO7SKg8Wy5+Ibut3GUcIUrQIr1Cj4yDl2XQ6lt12z7EU/aIJb4JZ7JPasuKGqyhdJ4FR3mxwxGN2egJJbexyIggLL5GJIalW+l9IovwDi3lgXie9CxB91kqKtADKQKXs3snXsW64tusb6ONEllzHNFuxjmOxVEKCz5H6iyN5E2gJkk8HyISPtQ0prCF0oByIyZCHtIm6+NHTOW32uegIQn7JBXfZ7ALmfWJyg/gtXT/rNPsGPU/MU9lyVJc62ccQ6Q7xpeI3jC22DyieT3fY7T5DnbQu0qk92mzA3WvMSPBWTwvQTVar1B9PMdxiPiqCvgjxZcsk+WoNKiKVJaCClO1M0RTCbLY/AC0Kb8dZUd7VqtVqDEnyRt1ddp+y3i+UZnlu0dbjhsJllzMBOZdlM0j2YKWcL/BTWyeFp3oIf6dEY5DeV+IDG674NM2Zy77Hyv7AAfVhWsfzEChNgRPt7IpmRVgvQd4epXVKKvweKV86wQxTVHu0WUwuwIPuRdQpZIPcewCNU3ptWVxX96Mbwq0FSkyiXdX8Ee9gtf18tZ8alrzEM2fZC92Pdn1ZNeTP/5fZK4nu57sxqJ/sie3TU/+yol8q+emZDcluynZdWQ3Jbsp2b2ofI+OHJiOPKDZMqndu0HZvbx2L6//i58u3KDsBmU3KL/voPygf2fO/yVlYgyqU08e1n8B1bpk/BcjAAA=") 77 | end 78 | elseif OceanFishingIsSpectralActive() then 79 | if GetMaxGp()>700 then 80 | UseAutoHookAnonymousPreset("AH4_H4sIAAAAAAAACu1aTXPaMBD9KxmdyQyYAA23lHxOacoAbQ5MDioWtgZhubKclDL892otS8bEhvTSSVrdYPV292l3pWWVbNBFKvkAJzIZLALU36CrCH9n5IIx1JciJQ0Ei0MaEVi8CyIuyGfO5yHqLzBL1LpW8A18JCgXVK5Rv9VAd8nVzzlLfeIXYoBttdnc0AZlH7zCvbV2E09DQZKQMyVqNZsl+4cd1PNqHiU2CNMVsDno3mA5Y2QuAV4sesddcOFTzCr2XMDPcuw1TcKrNUleUOp0SpS6Jq54SSYhXciPmGbEQJAYwUTi+VLZ6uyFt7Nr6jw3NcKSkmieZb+M7paj4RkFQX+RAZY6sSUVby+A7VxlGmJG8TK5xk9cgFZJYOi2G2X5mMz5E1H4FgThUN20S167WyiOn1LgUsVbVQjRlE+ecXwXyZRKyqMbTKOs/BXoVFXdPV6pgCC0rUQPeSJr0CPFiVRbR6eoZl3by9YLb5NY1ZzAbJAKQSL5KoZ7Oq/kWekJzaAmaRSMMfUfT+65WKlabiBQ0MqVzkyB11rP91pp3fLKiZZBxpAiMaSJ/LKAfauymW2yCADWbNY777V6dr/fiEhUkTNyMkwFAS/a3y3ny+xQ5yfggeDsO8iB/jpW2metHpwUg5lIwaOgAtVs76CGJCCRj8W6DnjJU1WRew5tmeb3bknR655bvRoSu5B6Bhr1NSFTQWNGdBB02rTkj1j1Oh7sR2vW8SqBDjDLcRPJ44uFJGKA0yBUrWkFdxzcxvbEmNSNyY+UCuKrK0SmcON1PxRZcPl86/kEONDbu6BBDCY0cziBWqb51IANWThhWmqZ1WjsMC+UwKctswO8LOYlwWPqe6hKysdsvAQaM5UnuyB3eTu9rbBp7VWs1+TPlo67Q9/PHbptVHfLM9stxzh4Vu3RtUnXJt3PnrdwZF2bdG3SjRpvo012bJv8JKh6QXOzpJsl3duAa5JulnSz5H/3HlfXJLu2SY5YuopPHtw4+Y6z7F5d/61T68ZJN066cfIvd8pH8zfL/B8iZlagm+fscfsbA95oG6khAAA=") 81 | elseif GetMaxGp()>400 then 82 | UseAutoHookAnonymousPreset("AH4_H4sIAAAAAAAACu1aS3PaMBD+K4zOZIZHgIZbSp5TmjJAmwOTg4qFrUFYriwnoQz/PVrLkjGxIb10klY3WH27+2m12mWVbNB5IvkAxzIeLHzU36DLEP9k5Jwx1JciIXUEi0MaEli89UMuyFfO5wHqLzCL1bpW8Ax8JCgXVK5Rv1lHt/Hl85wlHvFyMcC22mxmaIPSD63cvbV2HU0DQeKAMyVqNhoF+4cdVPNqHCU2CJIVsDno3mA5Y2QuAZ4vto674MKjmJXsOYefZtgrGgeXaxK/otTpFCh1TVzxkkwCupCfMU2JgSA2gonE86Wy1dkLb2fX1FlmaoQlJeE8Pf0iuluMRssoCPqbDLDUB1tQae0FsJ2pTAPMKF7GV/iRC9AqCAzddr0oH5M5fyQK34QgHMqbdsFrdwvJ8SwFLmS8VYUQTfnkCUe3oUyopDy8xjRM01+BTlTW3eGVCghC21L0kMeyAj1SnEi5dXSCKta1vXQ99zaJVM4JzAaJECSUb2K4p/NGnqWeUjawqpGllk02V5rKNjaD9KahP8bUe6jdcbHCrHZSG/Kn2vUoZ5PRK6KNxRxfR0May28L2LbKmtkmDQAomb22znrNnt3uDyJileOM1IaJIOBOM7jhfJne6ewC3BOcfgc5bGgdKe3TZg8uisFMpOChX4JqtHdQQ+KT0MNiXQX8HpMLnqic1Bx0HLVkj4VN3awWF6y1usBN61Uw24VU09KoqaDRHxLodVptq1lFoQA6QCLDTSSPzheSiAFO/EB1phWUOCjG9sKYoxuTXwkVxFMVRCZQ8Lqf8lNw5/nezxPgQG+vPoMYTGjmcAO1TPOpABuycMO01DKr0NhhniuBT5tmB3hZzGuCx9T3UKWUj9l4DTRmSktLTu7iZnpTYtPaK1mvOD+bOq6Gfpwauq2Xd8tT2y3H2H9S7dG1Sdcm3c+e93BlXZt0bdKNGu+jTXZsm/wiqHpAc7OkmyXd24Brkm6WdLPkf/ceV9Uku7ZJjliyimr3bpz8wKfsXl3/rVvrxkk3Trpx8i93ygfzN8vs/yFmVqCb5+xh+wJpSeU1qCEAAA==") 83 | else 84 | is_leveling = true 85 | end 86 | autohook_preset_loaded = "spectral" 87 | else 88 | if GetMaxGp()>700 then 89 | UseAutoHookAnonymousPreset("AH4_H4sIAAAAAAAACu1aTXPaMBD9KxmfkxlwwDTcKPmc0pQB2hyYHBQssAZjubKchDL892otS0bGQJjphUY3Rtr39Fa72rWUrJxOymkXJTzpTmdOe+XcROglxJ0wdNqcpfjcgckeiXAx6aupPiOUEb502vVz5yG5eZ+EqY/9YhjM1pLiO6WTADiyHy78uotHAcNJQEPBWK/VDML9jLuF1A4q6QbposIZQ03LaxqsCkrDEE84oItJ9/CKlPkEhSaskU/ekiS4WeJka0eaTWNHPLWRaI6HAZnyr4hkSmAgUQNDjiZzwSXkm2SbVFc5VR9xgqNJFlrT2jOD4SoAI39wF3EZSQPiluJ3mUNGAQoJmie36JUyQBkDSi6EzZgY4Al9xQJQh13YF6pmaWHPmO+8CBqnPUVhgteQOO+cISPVNSts34gO31D8EPGUcEKjO0QisHwQRhciIx/RQpA5zrrSukcTvsO6L+TganbnwtkxL/my+WK1YSwSkKGwmzKGI/4hhSXMHp1gLbVUrqNOWyG20swZQ1KTaDZAxH8+UzYlPytVCW/1duW6TLJHyhYZVY8k/McUnBT5M15l7oKl8sy9atVb2rlfmCUi20N81ksZhjUk0T2l8+xc5kfhCaN5kRswC3sh1Crn86HRMha0jXoLzpICDzmj0ewYeO1yA97DMxz5iC2PZrimqTBW2nMLnd5Z8peArnelcYXsY5GG4gqrESPxkbpaTfdSI49UZmD3aMvthpzGnSnHrIvSWSCa3AIKKtR6fQRVegzw75Qw7It6xVMor96XIm4l/w6kxocz4JQC/TPBctflhslMPdXogzmoLvUHGAYK6RDEV45JmTuMlQ8QPzmqle1AbCgvQLCmTso9urTNtsBD8JJVpeRDHNuGiqYyQQzu6/vRfYl2R4h04G3htoV78+iuz6s/Axr6M2CAZm+i7x/u/5+4lp9q2bZN2zZt27RPsmnbavufdNqm7rTfGBFviLbP2j776Y+4vRzby7Hts/ZW8w/7rKf7bD9MF/HZ04cutfZR296P7aO2fdS2j9ondj+2hfvzFu71s/pbd/4vNGM9IL8Kxs/rvzoxdEnHIwAA") 90 | elseif GetMaxGp()>400 then 91 | UseAutoHookAnonymousPreset("AH4_H4sIAAAAAAAACu1aXW/aMBT9KyjPVIIUwsobo58a6xCw9QH1wSWGWIQ4c5x2rOK/zzeODQ4JUKlSpc5v1D7n+Nj32hebvjq9lNM+SnjSny+c7qtzFaGnEPfC0OlyluK6A50DEuFtp6+6hoxQRvja6Tbrzl1y9WcWpj72t80A20iJ75TOAtDIPrjw6SaeBAwnAQ2FYrPRMAQPK1YbaRx10g/SVclkDDctr22oKioNQzzjwN52usdHpMwnKDRprbzzmiTB1RoneyvSbhsr4qmFREs8Dsicf0UkcwINiWoYczRbCi1h3xTblbrIpYaIExzNstCaaM8MhqsIjPzFfcRlJA2KW4jfeU6ZBCgkaJlco2fKgGU0KLsQNqNjhGf0GQtCE1bhUKiKA3tGf+9JyDjdOQoTvIHE+cMZMlJdq8LyTej4BcV3EU8JJzS6QSQC5J0AnYmMvEcrIeY4m1L0gCa8Aj0UdnC5unPmVPRLvax/O9o4FgnIUNhPGcMRP8lhgXPAJ6Cll9Jx1G7bmi2FOVNIahItRoj4jzWFqZ3VBvSldjMsTLjUnpi2XrfcoKl6T9nK1ByQhP+Yw7RFRk1fswUAipqre9FpdvR0f2GWiPwPcW2QMgyDScVbSpfZTs03xwNG2d/QDpbXsWC3mh3YRAoz5oxGixJU43wHNcALHPmIrauAlzQVCVkYUGdplsN1k+h6F5q3Z+JkZrUxiZowEr/RV6ftnmvmG50Z3APectyY07g355j1UboIRK1awbkIR7beSSqmI/w7JQz74tjhKZyS3pf6RwT6Z4LlyktjclvZ6L9v9AEOMS0c89AMEjLcEF/ZJkNbAVZxh/jJVu2sgrHjfEuCMXVSHvClMfsGj9ELqFLLxzT2gUqmNHEN7cvbyW1BtiJEetvb0/YTnLabennBbemCO0KLF1FhbaW1ldZW2k/1PctWWltp7b3moyttW1fab4yI9zt7o7U3WnujtXXW3mjtjda+H75fnfV0nR2G6SquPZx0qZXbMDcAr967ryfHnxtPpVe/Q75Jwb5F2pdo+zuEvR/bl2h7cP/HPyFuHtWvyvm/r0x1g/xWMH3c/AMAagpYQyMAAA==") 92 | else 93 | is_leveling = true 94 | end 95 | autohook_preset_loaded = "normal" 96 | end 97 | end 98 | end 99 | 100 | baits_list = { 101 | unset = { name = "unset", id = 0}, 102 | versatile = { name = "Versatile Lure", id = 29717 }, 103 | ragworm = { name = "Ragworm", id = 29714 }, 104 | krill = { name = "Krill", id = 29715 }, 105 | plumpworm = { name = "Plump Worm", id = 29716 }, 106 | } 107 | 108 | ocean_zones = { 109 | [1] = {id = 237, name = "Galadion Bay", normal_bait = baits_list.krill, daytime = baits_list.ragworm, sunset = baits_list.plumpworm, nighttime = baits_list.krill}, 110 | [2] = {id = 239, name = "Southern Merlthor", normal_bait = baits_list.krill, daytime = baits_list.krill, sunset = baits_list.ragworm, nighttime = baits_list.plumpworm}, 111 | [3] = {id = 243, name = "Northern Merlthor", normal_bait = baits_list.ragworm, daytime = baits_list.plumpworm, sunset = baits_list.ragworm, nighttime = baits_list.krill}, 112 | [4] = {id = 241, name = "Rhotano Sea", normal_bait = baits_list.plumpworm, daytime = baits_list.plumpworm, sunset = baits_list.ragworm, nighttime = baits_list.krill}, 113 | [5] = {id = 246, name = "The Ciedalaes", normal_bait = baits_list.ragworm, daytime = baits_list.krill, sunset = baits_list.plumpworm, nighttime = baits_list.krill}, 114 | [6] = {id = 248, name = "Bloodbrine Sea", normal_bait = baits_list.krill, daytime = baits_list.ragworm, sunset = baits_list.plumpworm, nighttime = baits_list.krill}, 115 | [7] = {id = 250, name = "Rothlyt Sound", normal_bait = baits_list.plumpworm, daytime = baits_list.krill, sunset = baits_list.krill, nighttime = baits_list.krill}, 116 | [8] = {id = 286, name = "Sirensong Sea", normal_bait = baits_list.plumpworm, daytime = baits_list.krill, sunset = baits_list.krill, nighttime = baits_list.krill}, 117 | [9] = {id = 288, name = "Kugane Coast", normal_bait = baits_list.ragworm, daytime = baits_list.krill, sunset = baits_list.ragworm, nighttime = baits_list.plumpworm}, 118 | [10] = {id = 290, name = "Ruby Sea", normal_bait = baits_list.krill, daytime = baits_list.ragworm, sunset = baits_list.plumpworm, nighttime = baits_list.krill}, 119 | [11] = {id = 292, name = "Lower One River", normal_bait = baits_list.krill, daytime = baits_list.ragworm, sunset = baits_list.krill, nighttime = baits_list.krill}, 120 | } 121 | 122 | routes = { --Lua indexes from 1, so make sure to add 1 to the zone returned by SND. 123 | [1] = {[1] = 2, [2] = 1, [3] = 3}, 124 | [2] = {[1] = 2, [2] = 1, [3] = 3}, 125 | [3] = {[1] = 2, [2] = 1, [3] = 3}, 126 | [4] = {[1] = 1, [2] = 2, [3] = 4}, 127 | [5] = {[1] = 1, [2] = 2, [3] = 4}, 128 | [6] = {[1] = 1, [2] = 2, [3] = 4}, 129 | [7] = {[1] = 5, [2] = 3, [3] = 6}, 130 | [8] = {[1] = 5, [2] = 3, [3] = 6}, 131 | [9] = {[1] = 5, [2] = 3, [3] = 6}, 132 | [10] = {[1] = 5, [2] = 4, [3] = 7}, 133 | [11] = {[1] = 5, [2] = 4, [3] = 7}, 134 | [12] = {[1] = 5, [2] = 4, [3] = 7}, 135 | [13] = {[1] = 8, [2] = 9, [3] = 11}, 136 | [14] = {[1] = 8, [2] = 9, [3] = 11}, 137 | [15] = {[1] = 8, [2] = 9, [3] = 11}, 138 | [16] = {[1] = 8, [2] = 9, [3] = 10}, 139 | [17] = {[1] = 8, [2] = 9, [3] = 10}, 140 | [18] = {[1] = 8, [2] = 9, [3] = 10}, 141 | } 142 | 143 | function WaitReady(delay, is_not_ready, status, target_zone) 144 | if is_not_ready then loading_tick = -1 145 | else loading_tick = 0 end 146 | if not delay then delay = 3 end 147 | wait = 0.1 148 | if type(status)=="number" then wait = wait + (status / 10000) end 149 | while loading_tick=3 then 250 | yield("/visland stop") 251 | break 252 | elseif IsMoving() then 253 | bugfix_tick = 0 254 | else 255 | bugfix_tick = bugfix_tick + 1 256 | end 257 | yield("/wait 1.035") 258 | end 259 | yield("/visland stop") 260 | end 261 | 262 | function IsNeedBait() 263 | if type(buy_baits)~="number" then 264 | return false 265 | else 266 | if GetItemCount(29714)=11 then 352 | time_state = "queue" 353 | elseif os.date("!*t").min>=10 then 354 | time_state = "movewait" 355 | end 356 | end 357 | elseif os.date("!*t").hour%2==0 then 358 | if is_last_minute_entry and os.date("!*t").min>10 then 359 | time_state = "queue" 360 | elseif os.date("!*t").min<15 then 361 | time_state = "queue" 362 | end 363 | elseif os.date("!*t").hour%2==1 then 364 | if os.date("!*t").min>=55 then 365 | time_state = "movewait" 366 | elseif context and os.date("!*t").min>=45 then 367 | time_state = "early" 368 | end 369 | end 370 | return time_state 371 | end 372 | 373 | function EatFood() 374 | if type(food_to_eat)=="string" then 375 | eat_food_tick = 0 376 | while HasStatus("Well Fed")==false and eat_food_tick<8 do 377 | verbose("Eating "..food_to_eat) 378 | yield("/item "..food_to_eat) 379 | yield("/wait 1") 380 | eat_food_tick = eat_food_tick + 1 381 | end 382 | if eat_food_tick>=8 then food_to_eat = false end 383 | end 384 | end 385 | 386 | function SafeCallback(...) -- Could be safer, but this is a good start, right? 387 | local callback_table = table.pack(...) 388 | local addon = nil 389 | local update = nil 390 | if type(callback_table[1])=="string" then 391 | addon = callback_table[1] 392 | table.remove(callback_table, 1) 393 | end 394 | if type(callback_table[1])=="boolean" then 395 | update = tostring(callback_table[1]) 396 | table.remove(callback_table, 1) 397 | elseif type(callback_table[1])=="string" then 398 | if string.find(callback_table[1], "t") then 399 | update = "true" 400 | elseif string.find(callback_table[1], "f") then 401 | update = "false" 402 | end 403 | table.remove(callback_table, 1) 404 | end 405 | 406 | local call_command = "/pcall " .. addon .. " " .. update 407 | for _, value in pairs(callback_table) do 408 | if type(value)=="number" then 409 | call_command = call_command .. " " .. tostring(value) 410 | end 411 | end 412 | if IsAddonReady(addon) and IsAddonVisible(addon) then 413 | yield(call_command) 414 | end 415 | end 416 | 417 | correct_bait = baits_list.unset 418 | normal_bait = baits_list.unset 419 | spectral_bait = baits_list.unset 420 | current_bait = baits_list.unset 421 | if type(movement_method)~="string" then 422 | movement_method = "" 423 | elseif string.find(string.lower(movement_method),"visland") then 424 | movement_method = "visland" 425 | elseif string.find(string.lower(movement_method),"navmesh") then 426 | movement_method = "navmesh" 427 | else 428 | verbose("Invalid movement_method") 429 | yield("/pcraft stop") 430 | end 431 | if type(is_leveling)=="string" then 432 | if GetLevel()>=100 then 433 | is_leveling = false 434 | else 435 | is_leveling = true 436 | end 437 | end 438 | if type(score_screen_delay)~="number" then score_screen_delay = 3 end 439 | if score_screen_delay<0 or score_screen_delay>500 then score_screen_delay = 3 end 440 | points_earned = 0 441 | 442 | ::Start:: 443 | DeleteAllAutoHookAnonymousPresets() 444 | if IsAddonVisible("IKDResult") then 445 | goto FishingResults 446 | elseif IsInZone(900) or IsInZone(1163) then 447 | verbose("We're on the boat!") 448 | goto OnBoat 449 | elseif TimeCheck("start") then 450 | verbose("Starting at or near fishing time.") 451 | if IsInZone(129) and GetDistanceToPoint(-410,4,76)<6.9 then 452 | verbose("Near the ocean fishing NPC.") 453 | if GetCharacterCondition(91) then 454 | verbose("Already in queue.") 455 | goto Enter 456 | elseif IsNeedBait() then 457 | goto BuyBait 458 | elseif TimeCheck()=="queue" then 459 | goto PreQueue 460 | else 461 | goto WaitForBoat 462 | end 463 | else 464 | verbose("Not near the ocean fishing NPC.") 465 | goto ReturnFromWait 466 | end 467 | elseif IsInZone(129) and GetDistanceToPoint(-411,4,72)<20 then 468 | if GetCharacterCondition(91) then 469 | goto Enter 470 | else 471 | goto DoneFishing 472 | end 473 | elseif IsInZone(129) or IsInZone(128) then 474 | goto WaitLocation 475 | elseif fishing_character~="auto" and fishing_character~=GetCharacterName(true) then 476 | goto MainWait 477 | else 478 | if os.date("!*t").hour%2==1 then 479 | time_remaining = 55 - os.date("!*t").min .." minutes." 480 | elseif os.date("!*t").min<=55 then 481 | time_remaining = "1 hour and ".. 55 - os.date("!*t").min .." minutes" 482 | else 483 | time_remaining = 115 - os.date("!*t").min .." minutes" 484 | end 485 | verbose(time_remaining .." until the next boat.") 486 | goto StartAR 487 | end 488 | 489 | ::MainWait:: 490 | while not TimeCheck(false) do 491 | if os.date("!*t").hour%2==1 then 492 | time_remaining = 55 - os.date("!*t").min .." minutes." 493 | elseif os.date("!*t").min<=55 then 494 | time_remaining = "1 hour and ".. 55 - os.date("!*t").min .." minutes." 495 | else 496 | time_remaining = 115 - os.date("!*t").min .." minutes." 497 | end 498 | if is_ar_while_waiting then 499 | verbose("Still running! AutoRetainer for the next ".. time_remaining, true) 500 | else 501 | verbose("Still running! Waiting for the next ".. time_remaining, true) 502 | end 503 | yield("/wait 1.001") 504 | end 505 | 506 | 507 | yield("/vnavmesh stop") 508 | yield("/visland stop") 509 | yield("/ays multi d") 510 | while GetCharacterCondition(50) do 511 | if IsAddonVisible("RetainerList") then 512 | verbose("Closing retainer list.") 513 | yield("/callback RetainerList true -1") 514 | end 515 | yield("/wait 1.004") 516 | end 517 | while GetCharacterName(true)~=fishing_character do 518 | if IsAddonVisible("TitleConnect") or IsAddonVisible("NowLoading") or IsAddonVisible("CharaSelect") or GetCharacterCondition(53) then 519 | yield("/wait 1.002") 520 | elseif GetCharacterCondition(50,false) then 521 | verbose("Relogging to "..fishing_character) 522 | yield("/ays relog " .. fishing_character) 523 | WaitReady(3, true) 524 | else 525 | verbose("Waiting for AutoRetainer to finish!") 526 | end 527 | yield("/wait 1.003") 528 | end 529 | 530 | ::ReturnFromWait:: 531 | if GetCharacterCondition(45) then 532 | WaitReady(1) 533 | goto Start 534 | elseif IsAddonVisible("NowLoading") or GetCharacterCondition(35) then 535 | WaitReady(1) 536 | end 537 | ::TeleportToLimsa:: 538 | while not ( IsInZone(177) or IsInZone(128) or IsInZone(129) ) do 539 | if GetCharacterCondition(27, false) and not IsPlayerOccupied() then 540 | yield("/tp Limsa") 541 | else 542 | WaitReady(2) 543 | end 544 | yield("/wait 0.3") 545 | end 546 | if IsInZone(129) and GetDistanceToPoint(-84,19,0)<20 then 547 | verbose("Limsa aetheryte plaza. Aethernet to arcanists guild.") 548 | while GetDistanceToPoint(-84,19,0)<20 do 549 | if IsAddonVisible("TelepotTown") then 550 | yield("/callback TelepotTown true 11 3u") 551 | elseif GetTargetName()~="aetheryte" then 552 | yield("/target aetheryte") 553 | elseif IsAddonVisible("SelectString") then 554 | yield("/callback SelectString true 0") 555 | elseif GetDistanceToTarget()<8 then 556 | yield("/interact") 557 | else 558 | if IsMoving() then yield("/generalaction Jump") end 559 | yield("/lockon on") 560 | yield("/automove on") 561 | end 562 | yield("/wait 0.501") 563 | end 564 | WaitReady(3) 565 | end 566 | ::ExitInn:: 567 | if IsInZone(177) then 568 | verbose("In inn. Leaving.") 569 | while IsInZone(177) do 570 | if GetTargetName()~="Heavy Oaken Door" then 571 | yield("/target Heavy Oaken Door") 572 | elseif IsAddonVisible("SelectYesno") then 573 | yield("/callback SelectYesno true 0") 574 | else 575 | yield("/lockon on") 576 | yield("/automove on") 577 | yield("/interact") 578 | end 579 | yield("/wait 0.502") 580 | end 581 | WaitReady(3) 582 | end 583 | ::MoveToAftcastle:: 584 | if IsInZone(128) and GetDistanceToPoint(13,40,13)<20 then 585 | verbose("Near inn. Moving to aftcastle.") 586 | if movement_method=="visland" then 587 | VislandRoute("H4sIAAAAAAAACuWTyWrDMBCGXyXM2QiNFkvyrXQBH9KNQrrQg2hUIqilYistxeTdqzgKCfQNGp3mnxlGvz40I1zbzkEDbQizFGfWpZXrg0tQwcL+fEYf0gDNywi3cfDJxwDNCI/QcEKlqaVUFTxlZYhEJYWo4BkarIlWRqDaZBmDay+goRXc26Vf52GMZDGPX65zIU2VNiTX27e08Gl1U7qPc8Vj9jSs4ve+ks3kae/2Y3CH9skhVnDZxbS/uE2uK+HZ1FHE3doNqcTbwQvr02HiVl3F/jyGZXk43SUffOfmuY9uqj9YKBFSG6WYPuYiJywMCUdTc3Z6WJAILSUKNlERlCCnivMJi6L5K2ltTo+KIJIaLcoOZSp0e/SOCiesVvoEVwg50UxLdqCyA4IEFar6vwN53fwCXs5zv5QFAAA=") 588 | elseif movement_method=="navmesh" then 589 | end 590 | end 591 | ::AethernetToArcanist:: 592 | if IsInZone(128) and GetDistanceToPoint(14,40,71)<9 then 593 | verbose("At aftcastle. Aethernet to arcanists guild.") 594 | while IsInZone(128) do 595 | if IsAddonVisible("TelepotTown") then 596 | yield("/callback TelepotTown true 11 3u") 597 | elseif GetTargetName()~="Aethernet shard" then 598 | yield("/target Aethernet shard") 599 | elseif GetDistanceToTarget()<4 then 600 | yield("/interact") 601 | else 602 | yield("/lockon on") 603 | yield("/automove on") 604 | end 605 | yield("/wait 0.503") 606 | end 607 | WaitReady(3) 608 | end 609 | 610 | JobCheck() 611 | 612 | ::MoveToOcean:: 613 | if IsInZone(129) and GetDistanceToPoint(-335,12,53)<9 then 614 | if IsNeedRepair()=="npc" or IsNeedBait() then 615 | verbose("At arcanists guild. Moving to Merchant & Mender.") 616 | if movement_method=="visland" then 617 | VislandRoute("H4sIAAAAAAAACuWTyWrDMBCGXyXMWRWyFmu5hS6QQ7pRcNPSg0hUIqilYCstJfjdKy8hUPoEjU7zj35+Rh+jA9za2oGBebO2wbdpRmeN21nfAILKfu+iD6kF83qA+9j65GMAc4BnMBeME8yJIAzBCkxRYNWrEsELGC6xZqwQossyBre4yg6qETzajd/nPIoJgmX8dLULCUwWi5BcY9ep8ml71/t/9aY581jtNn4db/I8Oe3dfrTuZB+GLBBc1zG5Y1Ry9VTOB8ckHvauTVPdB1fWp1Nir25icxnDZno7GZtPvnbL7CMd+oOMUljKkg9gBNb5cCVGMBpzwUquzhOMZpjpfkcyGD6A0XrgUgosqCJnui9aY1GoEQsbsdDxI8n8rQg923WRWDEp6ASG9GDEuDBSYUappP8fzFv3A6BUZs+lBQAA") 618 | else yield("/pcraft stop") 619 | end 620 | else 621 | verbose("At arcanists guild. Moving to ocean fishing.") 622 | if movement_method=="visland" then 623 | VislandRoute("H4sIAAAAAAAACuWSy2oDMQxFfyVoPTX22J6xvAt9QBbpi0L6oAszcRpDxy4Zp6WE/Hsdz4R0kS9ItNKVxLV80AZuTWtBw3jVGO+6OIphFBpr/GjhuqXzH1DAzPx+BedjB/ptA/ehc9EFD3oDz6AvOFekRiYLeAHNGFGilCgKeAUtFEEmsNomFbydXIGmBTyauVsnL0aSmIZv21ofc2fio12ZJs5cXN4N0/9rw65ppW4ZfvadtEtyW5jPzh7G84KsgOs2xP3Dk2jbIR3niUE8rG0Xh3xnPDMuHhx36iasLoOfD/+mffHJtXaa5ui2OEKlkkRgyUXGoghNIaueSk0oq7hSx7GUJ41FYcKCss5YJMEUXPVYkDDGKTtLLFgSKcsBCs080gExUZV4hjgErYmiFfZAeL4SxP5MakFqhVKeOpb37R9ZYl91nAUAAA==") 624 | else yield("/pcraft stop") 625 | end 626 | end 627 | end 628 | 629 | ::RepairNPC:: 630 | if IsNeedRepair()=="npc" then 631 | if IsInZone(129) and GetDistanceToPoint(-397,3,80)>5 then MoveNear(-398, 3, 78, 2, 5) end 632 | while not IsAddonVisible("Repair") do 633 | if GetTargetName()~="Merchant & Mender" then 634 | yield("/target Merchant & Mender") 635 | elseif IsAddonVisible("SelectIconString") then 636 | yield("/callback SelectIconString true 1") 637 | elseif GetCharacterCondition(32, false) then 638 | yield("/lockon on") 639 | yield("/interact") 640 | end 641 | yield("/wait 0.592") 642 | end 643 | while IsAddonVisible("Repair") do 644 | if string.gsub(GetNodeText("Repair",2),"%D","")~="0" then 645 | if IsAddonVisible("SelectYesno") then 646 | yield("/callback SelectYesno true 0") 647 | else 648 | yield("/callback Repair true 0") 649 | end 650 | else 651 | yield("/callback Repair true -1") 652 | yield("/lockon off") 653 | end 654 | yield("/wait 0.305") 655 | end 656 | end 657 | 658 | ::BuyBait:: 659 | if IsNeedBait() then 660 | verbose("Buying more bait.") 661 | if IsInZone(129) and GetDistanceToPoint(-397,3,80)>5 then MoveNear(-398, 3, 78, 2, 5) end 662 | while not IsAddonVisible("Shop") do 663 | if GetTargetName()~="Merchant & Mender" then 664 | yield("/target Merchant & Mender") 665 | elseif IsAddonVisible("SelectIconString") then 666 | yield("/callback SelectIconString true 0") 667 | elseif GetCharacterCondition(32, false) then 668 | yield("/lockon on") 669 | yield("/interact") 670 | end 671 | yield("/wait 0.591") 672 | end 673 | if is_purchase_ragworm then 674 | yield("/callback Shop true 0 0 99") 675 | is_purchase_ragworm = false 676 | yield("/wait 0.5") 677 | if IsAddonVisible("SelectYesno") then yield("/callback SelectYesno true 0") end 678 | yield("/wait 0.5") 679 | end 680 | if is_purchase_krill then 681 | yield("/callback Shop true 0 1 99") 682 | is_purchase_krill = false 683 | yield("/wait 0.5") 684 | if IsAddonVisible("SelectYesno") then yield("/callback SelectYesno true 0") end 685 | yield("/wait 0.5") 686 | end 687 | if is_purchase_plump then 688 | yield("/callback Shop true 0 2 99") 689 | is_purchase_plump = false 690 | yield("/wait 0.5") 691 | if IsAddonVisible("SelectYesno") then yield("/callback SelectYesno true 0") end 692 | yield("/wait 0.5") 693 | end 694 | goto BuyBait 695 | elseif IsAddonVisible("Shop") then 696 | yield("/callback Shop true -1") 697 | yield("/lockon off") 698 | goto BuyBait 699 | end 700 | 701 | ::BackToOcean:: 702 | WaitReady(0.3) 703 | if GetDistanceToPoint(-410,4,76)>6.9 then 704 | verbose("At Merchant & Mender. Moving to Ocean fishing.") 705 | if movement_method=="visland" then 706 | VislandRoute("H4sIAAAAAAAACuVQXUvDQBD8K2Wfz3CJqWnurVSFPtSPIsQqPhztSg+825LbKhLy393UK0XxH/g2MzsMs9PBjfUIBpa4s64dMY1ojTaAgsZ+7sgFjmCeO7ij6NhRANPBI5izUp9nuihLBSswZaYVPIGpRMt1PemFUcD5JZi8qBUs7cbtJacYfAt6R4+BwQiZB8bWrrlxvL0d/L+01E7qxC19HC/SQ9Je7VvEk/1QLldw5YnxGMXoE5weHInc7zFywkNwYx2fEgd2Te2Mwib9rL/FB+dxIT7dq78WGWcXdVGNf04iYJJX/2CSl/4LNp/3pk0CAAA=") 707 | else yield("/pcraft stop") 708 | end 709 | end 710 | 711 | ::RepairSelf:: 712 | if IsNeedRepair()=="self" then 713 | while not IsAddonVisible("Repair") do 714 | yield("/generalaction repair") 715 | yield("/wait 0.5") 716 | end 717 | yield("/callback Repair true 0") 718 | yield("/wait 0.1") 719 | if IsAddonVisible("SelectYesno") then 720 | yield("/callback SelectYesno true 0") 721 | yield("/wait 0.1") 722 | end 723 | while GetCharacterCondition(39) do yield("/wait 1") end 724 | yield("/wait 1") 725 | yield("/callback Repair true -1") 726 | end 727 | 728 | ::WaitForBoat:: 729 | if TimeCheck()=="queue" then goto PreQueue end 730 | while TimeCheck()~="queue" do 731 | verbose("Still running! ".. 60 - os.date("!*t").min .." minutes until the next boat.", true) 732 | yield("/wait 1.005") 733 | end 734 | 735 | ::BotPause:: 736 | notabot = math.random(2,8) 737 | verbose("Randomly waiting "..notabot.." seconds. Soooooooo human.") 738 | yield("/wait "..notabot) 739 | 740 | ::PreQueue:: 741 | boat_route = string.lower(boat_route) 742 | if string.find(boat_route,"random") then 743 | q = math.random(0,1) 744 | elseif string.find(boat_route,"ruby") or string.find(boat_route,"river") or string.find(boat_route,"kugane") then 745 | q = 1 746 | else 747 | q = 0 748 | end 749 | 750 | JobCheck() 751 | 752 | ::Queue:: 753 | if IsInZone(129) and GetDistanceToPoint(-410,4,76)<6.9 then 754 | verbose("Queueing up!") 755 | if q==1 then 756 | verbose("Ruby route") 757 | else 758 | verbose("Indigo route") 759 | end 760 | while GetCharacterCondition(91, false) do 761 | if GetTargetName()~="Dryskthota" then 762 | yield("/target Dryskthota") 763 | elseif GetCharacterCondition(32, false) then 764 | yield("/lockon on") 765 | yield("/interact") 766 | elseif IsAddonVisible("Talk") then 767 | yield("/click Talk Click") 768 | elseif IsAddonReady("SelectString") then 769 | if GetSelectStringText(0)=="Register to board." then 770 | yield("/callback SelectString true 0") 771 | else 772 | yield("/callback SelectString true "..q) 773 | end 774 | elseif IsAddonVisible("SelectYesno") then 775 | yield("/callback SelectYesno true 0") 776 | end 777 | yield("/wait 0.511") 778 | end 779 | else 780 | verbose("Zone: "..GetZoneID()) 781 | if IsInZone(129) then verbose("Distance from Dryskthota: "..GetDistanceToPoint(-410,4,76)) end 782 | verbose("That's not gonna work, chief.") 783 | yield("/pcraft stop") 784 | end 785 | yield("/lockon off") 786 | 787 | ::Enter:: 788 | while GetCharacterCondition(91) do 789 | verbose("Waiting for queue to pop.", true) 790 | if IsAddonVisible("ContentsFinderConfirm") then 791 | JobCheck() 792 | yield("/callback ContentsFinderConfirm true 8") 793 | end 794 | yield("/wait 1.007") 795 | end 796 | WaitReady(3) 797 | if not ( IsInZone(900) or IsInZone(1163) ) then 798 | verbose("Landed in zone "..GetZoneID()..", which isn't ocean fishing!") 799 | yield("/pcraft stop") 800 | end 801 | 802 | ::PrepareRandom:: 803 | need_to_move_to_rail = true 804 | math.randomseed(os.time()) 805 | move_y = math.random(-11000,5000)/1000 806 | move_z = 6.750 807 | if GetPlayerRawXPos()>0 then move_x = 7.5 else move_x = -7.5 end 808 | if move_x==7.5 and move_y<-2 and move_y>-4 then goto PrepareRandom end 809 | verbose("move_x: "..move_x) 810 | verbose("move_y: "..move_y) 811 | 812 | ::OnBoat:: 813 | start_fishing_attempts = 0 814 | is_changed_zone = true 815 | while ( IsInZone(900) or IsInZone(1163) ) and IsAddonVisible("IKDResult")==false do 816 | ::AlwaysDo:: 817 | AutoHookPresets() 818 | current_route = routes[GetCurrentOceanFishingRoute()] 819 | current_zone = current_route[GetCurrentOceanFishingZone()+1] 820 | normal_bait = ocean_zones[current_zone].normal_bait 821 | if GetCurrentOceanFishingTimeOfDay()==1 then spectral_bait = ocean_zones[current_zone].daytime end 822 | if GetCurrentOceanFishingTimeOfDay()==2 then spectral_bait = ocean_zones[current_zone].sunset end 823 | if GetCurrentOceanFishingTimeOfDay()==3 then spectral_bait = ocean_zones[current_zone].nighttime end 824 | if OceanFishingIsSpectralActive() then 825 | if spectral_bait then correct_bait = spectral_bait end 826 | if is_recast_on_spectral and not is_already_recast then 827 | is_already_recast = true 828 | yield("/ac hook") 829 | end 830 | else 831 | if normal_bait then correct_bait = normal_bait end 832 | is_already_recast = false 833 | end 834 | for _, bait in pairs(baits_list) do 835 | if GetCurrentBait()==bait.id then current_bait = bait end 836 | end 837 | verbose("FishingRoute: "..tostring(GetCurrentOceanFishingRoute()), true) 838 | verbose("FishingZone: "..tostring(GetCurrentOceanFishingZone()), true) 839 | verbose("FishingTime: "..tostring(GetCurrentOceanFishingTimeOfDay()), true) 840 | verbose("Zone name: "..ocean_zones[current_zone].name, true) 841 | verbose("Normal bait: "..normal_bait.name, true) 842 | verbose("Spectral bait: "..spectral_bait.name, true) 843 | verbose("Should now be using: "..correct_bait.name, true) 844 | verbose("Script thinks we're using: "..current_bait.name, true) 845 | verbose("time: "..string.format("%.1f", GetCurrentOceanFishingZoneTimeLeft()), true) 846 | EatFood() 847 | 848 | ::Ifs:: 849 | ::Loading:: 850 | if IsAddonVisible("NowLoading") or GetCharacterCondition(35) then 851 | DeleteAllAutoHookAnonymousPresets() 852 | is_changed_zone = true 853 | WaitReady(2) 854 | 855 | ::ShouldntNeed:: 856 | elseif IsAddonVisible("IKDResult") then 857 | break 858 | 859 | elseif wasabi_mode and is_changed_zone then 860 | if math.floor(GetPlayerRawXPos())~=7 and math.floor(GetPlayerRawXPos())~=-7 then 861 | need_to_move_to_rail = true 862 | wasabi_move = true 863 | end 864 | 865 | ::Movement:: 866 | elseif need_to_move_to_rail then 867 | while is_wait_to_move and ( GetCurrentOceanFishingZoneTimeLeft()>420 or GetCurrentOceanFishingZoneTimeLeft()<0 ) do 868 | yield("/wait 0.244") 869 | end 870 | if GetPlayerRawXPos()>0 then move_x = 7.5 else move_x = -7.5 end 871 | if wasabi_move then 872 | move_y = math.floor(GetPlayerRawZPos()*1000)/1000 873 | verbose("Resetting move_y to: "..move_y) 874 | end 875 | yield("/visland moveto "..move_x.." "..move_z.." "..move_y) 876 | yield("/wait 0.512") 877 | move_tick = 0 878 | while IsMoving() and move_tick <= 5 do 879 | if GetCurrentOceanFishingZoneTimeLeft()<420 and GetCurrentOceanFishingZoneTimeLeft()>0 then 880 | move_tick = move_tick + 0.1 881 | end 882 | if is_adjust_z then 883 | move_z = math.floor(GetPlayerRawYPos()*1000)/1000 884 | yield("/visland moveto "..move_x.." "..move_z.." "..move_y) 885 | end 886 | yield("/wait 0.119") 887 | end 888 | yield("/visland moveto "..move_x*2 .." "..move_z.." "..move_y) 889 | yield("/wait 0.200") 890 | yield("/visland stop") 891 | need_to_move_to_rail = false 892 | 893 | ::DoNothing:: 894 | elseif GetCurrentOceanFishingZoneTimeLeft()<30 then 895 | if current_zone<2 and is_spam_next_zone_bait then 896 | verbose("Next zone bait: "..ocean_zones[current_zone+1].normal_bait.name) 897 | end 898 | 899 | elseif GetCurrentOceanFishingZoneTimeLeft()>420 then 900 | yield("/wait 0.05") 901 | 902 | ::BagCheck:: 903 | elseif GetInventoryFreeSlotCount()<=2 then 904 | for _, command in pairs(bags_full) do 905 | if command=="/leaveduty" then LeaveDuty() end 906 | verbose("Running: "..command) 907 | yield(command) 908 | end 909 | 910 | ::BaitSwitch:: 911 | elseif bait_and_switch and ( ( current_bait.id~=correct_bait.id and GetItemCount(correct_bait.id)>1 ) or ( current_bait.id==baits_list.versatile and GetItemCount(correct_bait.id)<1 ) ) then 912 | if GetItemCount(correct_bait.id)>1 and current_bait.id~=correct_bait.id then 913 | yield("/tweaks e baitcommand") 914 | verbose("Switching bait to: "..correct_bait.name) 915 | bait_switch_failsafe = 0 916 | while GetCurrentBait()~=correct_bait.id and GetCurrentOceanFishingZoneTimeLeft()>30 and GetCurrentOceanFishingZoneTimeLeft()<420 do 917 | yield("/bait "..correct_bait.name) 918 | yield("/wait 0.1014") 919 | if bait_switch_failsafe > 13 then 920 | for i=1,20 do 921 | verbose("Bait switch didn't work! Please report this.") 922 | end 923 | break 924 | elseif is_fishing_animation_noticed then 925 | if GetCharacterCondition(42, false) then 926 | is_fishing_animation_noticed = false 927 | bait_switch_failsafe = bait_switch_failsafe + 1 928 | end 929 | elseif GetCharacterCondition(42) then 930 | is_fishing_animation_noticed = true 931 | end 932 | end 933 | else 934 | verbose("Out of "..correct_bait.name) 935 | yield("/bait Versatile Lure") 936 | end 937 | current_bait = correct_bait 938 | is_changed_bait = true 939 | SetAutoHookState(true) 940 | 941 | ::StartFishing:: 942 | elseif --[[ GetCurrentOceanFishingZoneTimeLeft()>30 and ]] GetCharacterCondition(43, false) then 943 | if not is_changed_bait and not is_changed_zone then 944 | not_fishing_tick = 0 945 | while GetCharacterCondition(43, false) and not_fishing_tick<1.5 do 946 | yield("/wait 0.108") 947 | not_fishing_tick = not_fishing_tick + 0.1 948 | end 949 | end 950 | is_changed_bait = false 951 | is_changed_zone = false 952 | if start_fishing_attempts>9 then 953 | yield("/echo [FishingRaid] Something has gone horribly wrong!") 954 | LeaveDuty() 955 | WaitReady(1,true) 956 | yield("/ays multi e") 957 | yield("/pcraft stop") 958 | elseif start_fishing_attempts>6 then 959 | if math.floor(GetPlayerRawXPos())~=7 and math.floor(GetPlayerRawXPos())~=-7 then 960 | verbose("Not standing at the side of the boat? Lets fix that.") 961 | need_to_move_to_rail = true 962 | wasabi_move = true 963 | end 964 | elseif start_fishing_attempts>1 then 965 | current_bait = "" 966 | elseif GetCharacterCondition(43, false) then 967 | start_fishing_attempts = start_fishing_attempts + 1 968 | verbose("Starting fishing from: X: ".. math.floor(GetPlayerRawXPos()*1000)/1000 .." Y or Z, depending on which plugin you ask: ".. math.floor(GetPlayerRawZPos()*1000)/1000 ) 969 | verbose("Should now be using: "..correct_bait.name) 970 | verbose("Script thinks we're using: "..current_bait.name) 971 | yield("/ac Cast") 972 | SetAutoHookState(true) 973 | end 974 | 975 | else 976 | SetAutoHookState(true) 977 | start_fishing_attempts = 0 978 | end 979 | yield("/wait 1.010") 980 | end 981 | 982 | ::FishingResults:: 983 | score_screen_wait = 500-(((score_screen_delay//60)*100)+(score_screen_delay%60)+40) 984 | if IsAddonVisible("IKDResult") then 985 | result_timer = 501 986 | while IsAddonVisible("IKDResult") do 987 | result_raw = string.gsub(GetNodeText("IKDResult",4),"%D","") 988 | result_timer = tonumber(result_raw) 989 | if type(result_timer)~="number" then result_timer = 501 end 990 | if result_timer<=(score_screen_wait) then 991 | points_earned_string = "" 992 | for i=9, 1, -1 do 993 | if type(GetNodeText("IKDResult",27,i))=="string" then 994 | points_earned_string = GetNodeText("IKDResult",27,i)..points_earned_string 995 | end 996 | end 997 | points_earned = tonumber(points_earned_string) 998 | yield("/callback IKDResult true 0") 999 | yield("/wait 1") 1000 | end 1001 | yield("/wait 0.266") 1002 | end 1003 | verbose("Points earned: "..points_earned) 1004 | end 1005 | 1006 | ::DoneFishing:: 1007 | DeleteAllAutoHookAnonymousPresets() 1008 | RunDiscard(1) 1009 | WaitReady(3, false, 72) 1010 | if IsInZone(129) then 1011 | yield("/echo Landed at: X:"..math.floor(GetPlayerRawXPos()*1000)/1000 .." Z:"..math.floor(GetPlayerRawYPos()*1000)/1000 .." Y:"..math.floor(GetPlayerRawZPos()*1000)/1000) 1012 | else 1013 | goto AtWaitLocation 1014 | end 1015 | 1016 | ::SpendScrips:: 1017 | if type(spend_scrips_when_above)=="number" then 1018 | if GetItemCount(25200)>spend_scrips_when_above then 1019 | verbose("Spending scrips on "..scrip_item_to_buy) 1020 | while IsInZone(129) and GetDistanceToPoint(-407,3.1,67.5)>6.9 do 1021 | if IsMoving() then while IsMoving() do yield("/wait 0.1") end 1022 | elseif GetDistanceToPoint(-410,4,76)<6.9 then --ocean 1023 | yield("/visland moveto -407 4 71") 1024 | elseif GetDistanceToPoint(-408.5,3.1,56)<6.9 or GetDistanceToPoint(-396,4.3,69)<6.9 or GetDistanceToPoint(-398,3.1,75.5)<6.9 then 1025 | yield("/visland moveto -404 4 71") --boardwalk 1026 | end 1027 | yield("/wait 0.1") 1028 | end 1029 | yield("/visland stop") 1030 | while IsAddonReady("InclusionShop")==false do 1031 | if GetTargetName()~="Scrip Exchange" then 1032 | yield("/target Scrip Exchange") 1033 | elseif IsAddonVisible("SelectIconString") then 1034 | yield("/callback SelectIconString true 0") 1035 | yield("/visland stop") 1036 | else 1037 | yield("/lockon on") 1038 | yield("/facetarget") 1039 | yield("/interact") 1040 | end 1041 | yield("/wait 0.521") 1042 | end 1043 | yield("/lockon off") 1044 | yield("/callback InclusionShop true 12 "..scrip_category) 1045 | yield("/wait 0.522") 1046 | yield("/callback InclusionShop true 13 "..scrip_subcategory) 1047 | yield("/wait 1.021") 1048 | scrips_raw = string.gsub(GetNodeText("InclusionShop", 21),"%D","") 1049 | scrips_owned = tonumber(scrips_raw) 1050 | for item=21, 36 do 1051 | scrip_shop_item_name = string.gsub(GetNodeText("InclusionShop", 5, item, 12),"%G","") 1052 | if scrip_shop_item_name==string.gsub(scrip_item_to_buy,"%G","") then 1053 | price_raw = string.gsub(GetNodeText("InclusionShop", 5, item, 5, 1),"%D","") 1054 | scrip_shop_item_price = tonumber(price_raw) 1055 | scrip_number_to_buy = scrips_owned//scrip_shop_item_price 1056 | yield("/callback InclusionShop true 14 "..item-21 .." "..scrip_number_to_buy) 1057 | yield("/wait 1.022") 1058 | if IsAddonVisible("ShopExchangeItemDialog") then 1059 | yield("/callback ShopExchangeItemDialog true 0") 1060 | yield("/wait 1.023") 1061 | end 1062 | break 1063 | end 1064 | end 1065 | yield("/callback InclusionShop true -1") 1066 | end 1067 | end 1068 | 1069 | ::WaitLocation:: 1070 | if type(wait_location)=="string" then 1071 | if string.find(string.lower(wait_location),"inn") then 1072 | verbose("Returning to inn.") 1073 | RunDiscard(2) 1074 | ::MoveToArcanist:: 1075 | if IsInZone(129) and GetDistanceToPoint(-408,4,75)<20 then 1076 | verbose("Near ocean fishing. Moving to arcanists guild.") 1077 | if movement_method=="visland" then 1078 | VislandRoute("H4sIAAAAAAAACuWUy0pDMRCGX6XM+hhyv5ydeIEualWEesFFaFMb8CTSkypS+u4m8ZR24RPYrDLz/0wmH8Ns4cZ2DlqYzp0No6XvVz68jVIc2fXcBt8naGBmvz+iD6mH9mULt7H3yccA7RYeoT1jhiAtjWrgCVqBcAPP0EqBBKeC7nIUgxtfQpuFe7vwm1yFFtckfrrOhVSVcUhubedp5tNqOriPc0OXuZl+Fb/2Su4iV1va994d7LU10sBVF9P+4XFy3XA9r44huNu4Pg33UnhmfTpULNF1XF/EsBh+jH+TD75zk+zDu+YPHtogJhkbeJh8KFeVCtdZ4YbzU8QiDCJYY1m5aITL4XssQhqhT5IKQ4zxMh+ZiqrTolmlkhVsuNIniYXjvFOo5BULIYULF79YKFKYSaKOsBBqTgUMU4hLqo7BkLJrChmOtJGG/H8wr7sfPGs0+LgGAAA=") 1079 | else yield("/pcraft stop") 1080 | end 1081 | end 1082 | ::AethernetToAftcastle:: 1083 | if IsInZone(129) and GetDistanceToPoint(-335,12,53)<9 then 1084 | verbose("At arcanists guild. Aethernet to aftcastle.") 1085 | while IsInZone(129) do 1086 | if IsAddonVisible("TelepotTown") then 1087 | yield("/callback TelepotTown true 11 1u") 1088 | elseif GetTargetName()~="Aethernet shard" then 1089 | yield("/target Aethernet shard") 1090 | elseif GetDistanceToTarget()<4 then 1091 | yield("/interact") 1092 | else 1093 | yield("/lockon on") 1094 | yield("/automove on") 1095 | end 1096 | yield("/wait 0.531") 1097 | end 1098 | WaitReady(3) 1099 | end 1100 | RunDiscard(3) 1101 | ::MoveToInn:: 1102 | if IsInZone(128) and GetDistanceToPoint(14,40,71)<9 then 1103 | verbose("Near aftcastle. Moving to inn.") 1104 | if movement_method=="visland" then 1105 | VislandRoute("H4sIAAAAAAAACuWT22rDMAyGX6XoOjNyYseHu7ID9KI7Mei6sYuwetSw2CNxN0bou09JU1rYnmDVlX5JyNKH3MF1VTuwMHVp7Zrg0iTFiQ8BMlhU3x/Rh9SCfe7gNrY++RjAdvAIliNTyCWKDJZgBTLsjdQTWCWYQNRiSyoGN7sAixncVyu/oV45IzGPn652IQ2ZWUiuqV7Twqf1zVh9HBtHpJHadfzaZ2gW6vZWvbfuUD4MyDO4rGPaPzxLrh7d6VAxiruNa9Po940XlU+Hjr26is15DKtxb9wFH3zt5lSH2+w3FcKgi7LM/6LCmVJSmdOjcoaMK11oLsqBS2GY6a0cuIiCFWgwl6cHhnYzXJpc77FIThcyUOGKqOTmFKnwgnGt6RsdH4uWOyw505Jj+e9/0cv2ByD0KqubBQAA") 1106 | else yield("/pcraft stop") 1107 | end 1108 | end 1109 | ::EnterInn:: 1110 | while IsInZone(128) and GetDistanceToPoint(13,40,13)<4 do 1111 | verbose("Near inn. Entering.") 1112 | if GetDistanceToPoint(13,40,13)<4 then 1113 | if GetTargetName()~="Mytesyn" then 1114 | yield("/target Mytesyn") 1115 | elseif GetCharacterCondition(32, false) then 1116 | yield("/lockon on") 1117 | yield("/interact") 1118 | elseif IsAddonVisible("Talk") then 1119 | yield("/click Talk Click") 1120 | elseif IsAddonVisible("SelectString") then 1121 | yield("/callback SelectString true 0") 1122 | elseif IsAddonVisible("SelectYesno") then 1123 | yield("/callback SelectYesno true 0") 1124 | end 1125 | end 1126 | yield("/wait 0.532") 1127 | end 1128 | WaitReady(3, false, 32, 177) 1129 | elseif string.find(string.lower(wait_location),"fc") 1130 | or string.find(string.lower(wait_location),"private") 1131 | or string.find(string.lower(wait_location),"personal") 1132 | or string.find(string.lower(wait_location),"apartment") 1133 | or string.find(string.lower(wait_location),"shared") 1134 | then 1135 | if string.find(string.lower(wait_location),"fc") then 1136 | verbose_string = "FC house" 1137 | tp_location = "Estate Hall (Free Company)" 1138 | elseif string.find(string.lower(wait_location),"private") or string.find(string.lower(wait_location),"personal") then 1139 | verbose_string = "private house" 1140 | tp_location = "Estate Hall (Private)" 1141 | elseif string.find(string.lower(wait_location),"apartment") then 1142 | verbose_string = "apartment" 1143 | tp_location = "Apartment" 1144 | elseif string.find(string.lower(wait_location),"shared") then 1145 | verbose_string = "shared estate" 1146 | if string.find(string.lower(wait_location), "Shared Estate (Plot ") then 1147 | tp_location = wait_location 1148 | else 1149 | tp_location = "Shared Estate " 1150 | end 1151 | end 1152 | verbose("Returning to "..verbose_string..".") 1153 | while not ( IsInZone(339) or IsInZone(340) or IsInZone(341) or IsInZone(641) or IsInZone(979) ) do 1154 | if GetCharacterCondition(27, false) and not IsPlayerOccupied() then 1155 | yield("/tp "..tp_location) 1156 | else 1157 | WaitReady() 1158 | end 1159 | yield("/wait 0.31") 1160 | RunDiscard(2) 1161 | end 1162 | verbose("Arrived at "..verbose_string..". Entering.") 1163 | yield("/automove on") 1164 | yield("/wait 1.033") 1165 | yield("/automove off") 1166 | yield("/ays het") 1167 | WaitReady(3, false) 1168 | verbose("Inside "..verbose_string.." (hopefully)") 1169 | end 1170 | end 1171 | 1172 | WaitReady() 1173 | 1174 | ::AtWaitLocation:: 1175 | ::Desynth:: 1176 | if is_desynth then 1177 | yield("/tweaks e UiAdjustments@ExtendedDesynthesisWindow") 1178 | verbose("Running desynthesis.") 1179 | verbose("Do not touch the desynth window!") 1180 | is_doing_desynth = true 1181 | failed_click_tick = 0 1182 | open_desynth_attempts = 0 1183 | desynth_last_item = nil 1184 | desynth_prev_item = nil 1185 | is_clicked_desynth = false 1186 | item_name = nil 1187 | yield("/wait 0.1") 1188 | while is_doing_desynth do 1189 | verbose("Desynth is running...", true) 1190 | verbose("Do not touch the desynth window!", true) 1191 | if not IsAddonVisible("SalvageItemSelector") then 1192 | verbose("Opening desynth window") 1193 | yield("/generalaction desynthesis") 1194 | open_desynth_attempts = open_desynth_attempts + 1 1195 | if open_desynth_attempts>3 then 1196 | is_doing_desynth = false 1197 | is_clicked_desynth = false 1198 | is_desynth = false 1199 | desynth_last_item = nil 1200 | desynth_prev_item = nil 1201 | item_name = nil 1202 | verbose("Tried too many times to open desynth, and it hasn't worked. Giving up and moving on.") 1203 | end 1204 | elseif desynth_prev_item~=nil and item_name and desynth_last_item==item_name and desynth_prev_item==item_name then 1205 | verbose("Repeat item bug?") 1206 | verbose("Closing desynth window") 1207 | SafeCallback("SalvageItemSelector", true, -1) 1208 | yield("/wait 1") 1209 | elseif not IsAddonReady("SalvageItemSelector") then 1210 | yield("/wait 0.541") 1211 | elseif IsAddonVisible("SalvageDialog") then 1212 | while not IsAddonReady("SalvageDialog") do yield("/wait 0.1") end 1213 | --if GetNodeText("SalvageDialog",21)==item_name then 1214 | if string.gsub(GetNodeText("SalvageDialog",19),"%D","")~="000" then 1215 | SafeCallback("SalvageDialog", true, 13, true) 1216 | SafeCallback("SalvageDialog", true, 0) 1217 | is_clicked_desynth = false 1218 | else 1219 | verbose("Empty SalvageDialogue window!") 1220 | verbose("Ending desynth!") 1221 | is_doing_desynth = false 1222 | SafeCallback("SalvageDialog", true, -1) 1223 | SafeCallback("SalvageItemSelector", true, -1) 1224 | end 1225 | elseif IsAddonVisible("SalvageResult") then 1226 | SafeCallback("SalvageResult", true, 1) 1227 | elseif IsAddonVisible("SalvageAutoDialog") then 1228 | is_clicked_desynth = false 1229 | if string.sub(GetNodeText("SalvageAutoDialog", 27),1,1)=="0" then SafeCallback("SalvageAutoDialog", true, -1) end 1230 | elseif GetCharacterCondition(39) then 1231 | is_clicked_desynth = false 1232 | elseif is_clicked_desynth then 1233 | failed_click_tick = failed_click_tick + 1 1234 | if failed_click_tick>4 then 1235 | is_doing_desynth = false 1236 | verbose("Desynth probably finished!") 1237 | verbose("Closing desynth window") 1238 | SafeCallback("SalvageItemSelector", true, -1) 1239 | yield("/wait 2") 1240 | failed_click_tick = 0 1241 | end 1242 | elseif IsNodeVisible("SalvageItemSelector",1,13) or IsNodeVisible("SalvageItemSelector",1,12,2) then 1243 | verbose("Desynth finished!") 1244 | is_doing_desynth = false 1245 | SafeCallback("SalvageItemSelector", true, -1) 1246 | else 1247 | for i=1,20 do 1248 | if string.gsub(GetNodeText("SalvageItemSelector", 3, 2, 8),"%W","")~="" then 1249 | break 1250 | else 1251 | yield("/wait 0.09") 1252 | end 1253 | end 1254 | for list=2, 16 do 1255 | --item_name_raw = string.gsub(GetNodeText("SalvageItemSelector", 3, list, 8),"%W","") 1256 | desynth_prev_item = desynth_last_item 1257 | desynth_last_item = item_name 1258 | --item_name = string.sub(item_name_raw, 3,-3) 1259 | item_name = GetNodeText("SalvageItemSelector", 3, list, 8) 1260 | if string.sub(GetNodeText("SalvageItemSelector", 3, 2, 2),-1,-1)==")" then 1261 | item_level_raw = string.sub(GetNodeText("SalvageItemSelector", 3, list, 2),1,3) 1262 | else 1263 | item_level_raw = string.sub(GetNodeText("SalvageItemSelector", 3, list, 2),-3,-1) 1264 | item_level_raw = string.gsub(item_level_raw,"%d+/","") 1265 | end 1266 | item_level = string.gsub(item_level_raw,"%D","") 1267 | item_type = GetNodeText("SalvageItemSelector", 3, list, 5) 1268 | if item_level=="1" and item_type=="Culinarian" then 1269 | verbose("Desynthing: "..item_name) 1270 | verbose("item_level: "..item_level, true) 1271 | verbose("item_type: "..item_type, true) 1272 | SafeCallback("SalvageItemSelector", true, 12, list-2) 1273 | is_clicked_desynth = true 1274 | break 1275 | elseif list==16 then 1276 | is_doing_desynth = false 1277 | verbose("Desynth finished!") 1278 | break 1279 | end 1280 | end 1281 | end 1282 | yield("/wait 0.540") 1283 | end 1284 | SafeCallback("SalvageItemSelector", true, -1) 1285 | end 1286 | 1287 | ::WrapUp:: 1288 | RunDiscard() 1289 | verbose("You did a good job today!") 1290 | verbose("Points earned: "..points_earned) 1291 | 1292 | ::StartAR:: 1293 | if not is_single_run then 1294 | if fishing_character=="auto" then fishing_character = GetCharacterName(true) end 1295 | if is_ar_while_waiting then 1296 | verbose("Enabling AutoRetainer while waiting.") 1297 | if ARRetainersWaitingToBeProcessed() then 1298 | target_tick = 1 1299 | while GetCharacterCondition(50, false) do 1300 | if target_tick > 9 then 1301 | break 1302 | elseif string.lower(GetTargetName())~="summoning bell" then 1303 | verbose("Finding summoning bell...") 1304 | yield("/target Summoning Bell") 1305 | target_tick = target_tick + 1 1306 | elseif GetDistanceToTarget()>5 then 1307 | yield("/lockon on") 1308 | yield("/automove on") 1309 | else 1310 | yield("/automove off") 1311 | yield("/interact") 1312 | end 1313 | yield("/lockon on") 1314 | yield("/wait 0.511") 1315 | end 1316 | if GetCharacterCondition(50) then 1317 | yield("/lockon off") 1318 | yield("/ays e") 1319 | while not IsAddonVisible("RetainerList") do yield("/wait 0.100") end 1320 | yield("/wait 0.4") 1321 | end 1322 | end 1323 | yield("/ays multi e") 1324 | else 1325 | verbose("Waiting for the next boat.") 1326 | end 1327 | goto MainWait 1328 | end 1329 | -------------------------------------------------------------------------------- /SND/GoldSaucer.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Jumbo Cactpot claim and purchase script. 3 | Maybe some extra bits too. Plan is to automate my whole weekly GS visit. 4 | ]] 5 | 6 | 7 | function Talk() 8 | yield("/wait 0.1") 9 | while IsAddonVisible("Talk") do 10 | yield("/click Talk") 11 | yield("/wait 0.1") 12 | end 13 | end 14 | 15 | function Yesno() 16 | yield("/wait 0.2") 17 | if IsAddonVisible("SelectYesno") then 18 | yield("/pcall SelectYesno true 0") 19 | yield("/wait 0.1") 20 | end 21 | end 22 | 23 | function Select(i) 24 | yield("/wait 0.1") 25 | if IsAddonVisible("SelectString") then 26 | yield("/click select_string"..i) 27 | yield("/wait 0.1") 28 | end 29 | end 30 | 31 | function MoveNear(near_x, near_z, near_y, radius, timeout) 32 | if not radius then radius = 3 end 33 | if not timeout then timeout = 60 end 34 | move_x = math.random((near_x-radius)*1000, (near_x+radius)*1000)/1000 35 | if near_z then 36 | move_z = near_z 37 | else 38 | move_z = math.floor(GetPlayerRawYPos()*1000)/1000 39 | end 40 | move_y = math.random((near_y-radius)*1000, (near_y+radius)*1000)/1000 41 | yield("/visland moveto "..move_x.." "..move_z.." "..move_y) 42 | yield("/wait 0.5") 43 | move_tick = 0 44 | while IsMoving() and move_tick <= timeout do 45 | move_tick = move_tick + 0.1 46 | if near_z == false then 47 | move_z = math.floor(GetPlayerRawYPos()*1000)/1000 48 | yield("/visland moveto "..move_x.." "..move_z.." "..move_y) 49 | end 50 | yield("/wait 0.1") 51 | end 52 | yield("/visland stop") 53 | if is_debug then 54 | yield("/echo Aimed for: X:"..move_x.." Z:"..move_z.." Y:"..move_y) 55 | yield("/echo Landed at: X:"..math.floor(GetPlayerRawXPos()*1000)/1000 .." Z:"..math.floor(GetPlayerRawYPos()*1000)/1000 .." Y:"..math.floor(GetPlayerRawZPos()*1000)/1000) 56 | if move_tick < timeout then 57 | reason = "arrived" 58 | else 59 | reason = "timeout" 60 | end 61 | yield("/echo Reason: "..reason) 62 | end 63 | return "X:"..move_x.." Z:"..move_z.." Y:"..move_y 64 | end 65 | 66 | ---------------------------------------------------------- 67 | 68 | ::Start:: 69 | gs = 0 70 | 71 | if IsAddonVisible("LotteryWeeklyRewardList") then 72 | ::NextClaim:: 73 | Yesno() 74 | Talk() 75 | if IsAddonVisible("LotteryWeeklyRewardList") then 76 | yield("/pcall LotteryWeeklyRewardList true -1") 77 | yield("/wait 1") 78 | Talk() 79 | goto NextClaim 80 | end 81 | is_tickets_claimed = true 82 | end 83 | 84 | if is_tickets_claimed then 85 | is_tickets_claimed = false 86 | MoveNear(121, false, -11, 2, 1.5) 87 | yield("/target Jumbo Cactpot Broker") 88 | yield("/wait 0.1") 89 | yield("/pinteract") 90 | Talk() 91 | Select(1) 92 | yield("/wait 2") 93 | end 94 | 95 | if IsAddonVisible("LotteryWeeklyInput") then 96 | four_digit = 9876 97 | ::BuyTicket:: 98 | if IsAddonVisible("LotteryWeeklyInput") then 99 | tick = 0 100 | math.randomseed(os.time()..four_digit) 101 | random=string.sub(math.random(),3,-1) 102 | four_digit = string.sub(random,6,9) 103 | yield("/echo "..random) 104 | yield("/echo "..four_digit) 105 | yield("/waitaddon LotteryWeeklyInput") 106 | yield("/wait 0.1") 107 | yield("/pcall LotteryWeeklyInput true "..four_digit) 108 | Yesno() 109 | Yesno() 110 | yield("/wait 0.3") 111 | goto BuyTicket 112 | elseif tick < 10 then 113 | Talk() 114 | yield("/wait 0.1") 115 | tick = tick + 1 116 | goto BuyTicket 117 | end 118 | end 119 | 120 | if IsAddonVisible("SelectString") then 121 | yield("/wait 0.1") 122 | for i=0, 5 do 123 | yield("/wait 0.01") 124 | string = GetSelectStringText(i) 125 | if string=="Present yourself for judging." or string=="Purchase a Jumbo Cactpot ticket." then 126 | Select(i+1) 127 | Yesno() 128 | break 129 | end 130 | end 131 | end 132 | 133 | if IsAddonVisible("Talk") then Talk() end 134 | 135 | if IsAddonVisible("FashionCheck") then 136 | yield("/pcall FashionCheck true -1") 137 | end 138 | 139 | if HasStatus("Gold Saucer VIP Card")==false then 140 | if IsAddonVisible("GrandCompanySupplyList") then 141 | QuitDeliver() 142 | ed = 0 143 | end 144 | if GetItemCount(14947) > 0 then 145 | yield("/wait 1") 146 | yield("/item Gold Saucer VIP Card") 147 | end 148 | end 149 | 150 | ::Loop:: 151 | yield("/wait 1.00"..gs) 152 | gs = gs + 1 153 | 154 | if IsInZone(144) and gs < 99 then 155 | goto Start 156 | else 157 | goto Loop 158 | end 159 | -------------------------------------------------------------------------------- /SND/HuntBoard.lua: -------------------------------------------------------------------------------- 1 | target = GetNodeText("_TargetInfoMainTarget", 8) 2 | count = -1 3 | function Board() 4 | if IsAddonVisible("Mobhunt") then board = "Mobhunt" end 5 | if IsAddonVisible("Mobhunt2") then board = "Mobhunt2" end 6 | if IsAddonVisible("Mobhunt3") then board = "Mobhunt3" end 7 | if IsAddonVisible("Mobhunt4") then board = "Mobhunt4" end 8 | if IsAddonVisible("Mobhunt5") then board = "Mobhunt5" end 9 | end 10 | if string.find(target, "Board") and IsAddonVisible("SelectString") then 11 | for i=0, 5 do 12 | if GetSelectStringText(i)~=i then 13 | yield("/echo "..GetSelectStringText(i)) 14 | count = count + 1 15 | yield("/echo "..count) 16 | end 17 | end 18 | for j=1, count do 19 | yield("/click select_string"..j) 20 | yield("/wait 0.1") 21 | if not board then Board() end 22 | yield("/pcall "..board.." true 0") 23 | yield("/wait 0.1") 24 | if IsAddonVisible("SelectYesno") then 25 | yield("/pcall SelectYesno true 0") 26 | end 27 | if j < count then 28 | yield("/wait 1") 29 | yield("/target "..target) 30 | yield("/wait 0.1") 31 | yield("/pinteract") 32 | yield("/wait 0.7") 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /SND/LoV.lua: -------------------------------------------------------------------------------- 1 | characters_to_run = { 2 | 5, 6, 4 3 | } 4 | my_characters = {'Character Name@Server', 'Alt Name@Server',} 5 | what_to_play = "Master Battle (Extreme)" 6 | games_to_lose = 5 7 | file_characters = os.getenv("appdata").."\\XIVLauncher\\pluginConfigs\\SomethingNeedDoing\\my_characters.txt" 8 | 9 | if io.open(file_characters,"r")~=nil then 10 | file_characters = io.input(file_characters) 11 | main = file_characters:read("l") 12 | my_characters = {} 13 | next_line = main 14 | i = 0 15 | while next_line do 16 | i = i + 1 17 | my_characters[i] = next_line 18 | yield("/echo Character "..i.." from file: "..next_line) 19 | next_line = file_characters:read("l") 20 | end 21 | file_characters:close() 22 | yield("/echo Characters loaded from file: "..i) 23 | else 24 | yield("/echo "..file_characters.." not found!") 25 | end 26 | 27 | tick = 0 28 | completed_characters = 0 29 | next_character = my_characters[characters_to_run[completed_characters+1]] 30 | 31 | ::Relog:: 32 | games_played = 0 33 | if not string.find(next_character, GetCharacterName()) then 34 | yield("/ays relog " .. next_character) 35 | yield("/wait 10") 36 | yield("/waitaddon NamePlate ") 37 | end 38 | 39 | if HasStatus("Squadron Enlistment Manual")==false and GetItemCount(14945)~=0 then 40 | yield("/wait 1") 41 | yield("/item Squadron Enlistment Manual") 42 | yield("/wait 3") 43 | end 44 | 45 | ::Queue:: 46 | if IsAddonVisible("JournalDetail")==false then yield("/dutyfinder") end 47 | yield("/waitaddon JournalDetail") 48 | yield("/pcall ContentsFinder true 1 9") 49 | yield("/pcall ContentsFinder true 12 1") 50 | while string.gsub(GetNodeText("ContentsFinder", 25, 6, 14),"%W","")=="" do 51 | yield("/wait 0.1") 52 | tick = tick + 1 53 | if tick > 30 then yield("/pcraft stop") end 54 | end 55 | yield("/wait 0.1") 56 | for i=3, 12 do 57 | entry = GetNodeText("ContentsFinder", 25, i, 14) 58 | if entry==what_to_play then 59 | click = i-2 60 | break 61 | end 62 | end 63 | if not click then yield("/pcraft stop") end 64 | yield("/pcall ContentsFinder true 3 "..click) 65 | yield("/pcall ContentsFinder true 12 0 ") 66 | if IsAddonVisible("ContentsFinderConfirm") then yield("/click duty_commence") end 67 | 68 | ::Return:: 69 | yield("/waitaddon LovmResult ") 70 | games_played = games_played + 1 71 | yield("/pcall LovmResult false -2") 72 | yield("/pcall LovmResult true -1") 73 | yield("/waitaddon NamePlate ") 74 | 75 | if HasStatus("Squadron Enlistment Manual")==false and GetItemCount(14945) > 0 then 76 | yield("/wait 1") 77 | yield("/item Squadron Enlistment Manual") 78 | yield("/wait 3") 79 | end 80 | 81 | if games_played < games_to_lose then goto Queue end 82 | completed_characters = completed_characters + 1 83 | 84 | next_character = my_characters[characters_to_run[completed_characters+1]] 85 | if next_character then goto Relog end 86 | 87 | 88 | 89 | ::Finish:: 90 | yield("/ays relog " .. main) 91 | yield("/wait 10") 92 | yield("/ays multi") 93 | 94 | 95 | -------------------------------------------------------------------------------- /SND/MarketBotty/MarketBotty.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | MarketBotty! Fuck it, I'm going there. Don't @ me. 3 | https://github.com/plottingCreeper/FFXIV-scripts-and-macros/tree/main/SND/MarketBotty 4 | ]] 5 | 6 | my_characters = { --Characters to switch to in multimode 7 | 'Character Name@Server', 8 | 'Character Name@Server', 9 | } 10 | my_retainers = { --Retainers to avoid undercutting 11 | 'Dont-undercut-this-retainer', 12 | 'Or-this-one', 13 | } 14 | blacklist_retainers = { --Do not run script on these retainers 15 | 'Dont-run-this-retainer', 16 | 'Or-this-one', 17 | } 18 | item_overrides = { --Item names with no spaces or symbols 19 | StuffedAlpha = { maximum = 450 }, 20 | StuffedBomBoko = { minimum = 450 }, 21 | Coke = { minimum = 450, maximum = 5000 }, 22 | RamieTabard = { default = 25000 }, 23 | } 24 | 25 | undercut = 1 --There's no reason to change this. 1 gil undercut is life. 26 | is_dont_undercut_my_retainers = true --Working! 27 | is_price_sanity_checking = true --Ignores market results below half the trimmed mean of historical prices. 28 | is_using_blacklist = true --Whether or not to use the blacklist_retainers list. 29 | history_trim_amount = 5 --Trims this many from highest and lowest in history list 30 | history_multiplier = "round" --if no active sales then get average historical price and multiply 31 | is_using_overrides = true --item_overrides table. 32 | is_check_for_hq = false --Not working yet :( 33 | 34 | is_override_report = true 35 | is_postrun_one_gil_report = true --Requires is_verbose 36 | is_postrun_sanity_report = true --Requires is_verbose 37 | 38 | is_verbose = true --Basic info in chat about what's going on. 39 | is_debug = true --Absolutely flood your chat with all sorts of shit you don't need to know. 40 | name_rechecks = 10 --Latency sensitive tunable. Probably sets wrong price if below 5 41 | 42 | is_read_from_files = true --Override arrays with lists in files. Missing files are ignored. 43 | is_write_to_files = true --Adds characters and retainers to characters_file and retainers_file 44 | is_echo_during_read = false --Echo each character and retainer name as they're read, to see how you screwed up. 45 | config_folder = os.getenv("appdata").."\\XIVLauncher\\pluginConfigs\\SomethingNeedDoing\\" 46 | marketbotty_settings = "marketbotty_settings.lua" --loaded first 47 | characters_file = "my_characters.txt" 48 | retainers_file = "my_retainers.txt" 49 | blacklist_file = "blacklist_retainers.txt" 50 | overrides_file = "item_overrides.lua" 51 | 52 | is_multimode = false --It worked once, which means it's perfect now. Please send any complaints to /dev/null 53 | start_wait = false --For when starting script during AR operation. 54 | after_multi = false --"logout", "wait 10", "wait logout", number. See readme. 55 | is_autoretainer_while_waiting = false 56 | multimode_ending_command = "/ays multi e" 57 | is_use_ar_to_enter_house = true --Breaks if you have subs ready. 58 | is_autoretainer_compatibility = false --Not implemented. Last on the to-do list. 59 | 60 | ------------------------------------------------------------------------------------------------------ 61 | 62 | function file_exists(name) 63 | local f=io.open(name,"r") 64 | if f~=nil then io.close(f) return true else return false end 65 | end 66 | 67 | function CountRetainers() 68 | if not IsAddonVisible("RetainerList") then SomethingBroke("RetainerList", "CountRetainers()") end 69 | while string.gsub(GetNodeText("RetainerList", 2, 1, 13),"%d","")=="" do 70 | yield("/wait 0.1") 71 | end 72 | yield("/wait 0.1") 73 | total_retainers = 0 74 | retainers_to_run = {} 75 | yield("/wait 0.1") 76 | for i= 1, 10 do 77 | yield("/wait 0.01") 78 | include_retainer = true 79 | retainer_name = GetNodeText("RetainerList", 2, i, 13) 80 | if retainer_name~="" and retainer_name~=13 then 81 | if GetNodeText("RetainerList", 2, i, 5)~="None" then 82 | if is_using_blacklist then 83 | for _, blacklist_test in pairs(blacklist_retainers) do 84 | if retainer_name==blacklist_test then 85 | include_retainer = false 86 | break 87 | end 88 | end 89 | end 90 | else 91 | include_retainer = false 92 | end 93 | if include_retainer then 94 | total_retainers = total_retainers + 1 95 | retainers_to_run[total_retainers] = i 96 | end 97 | if is_write_to_files and type(file_retainers)=="userdata" then 98 | is_add_to_file = true 99 | for _, known_retainer in pairs(my_retainers) do 100 | if retainer_name==known_retainer then 101 | is_add_to_file = false 102 | break 103 | end 104 | end 105 | if is_add_to_file then 106 | file_retainers = io.open(config_folder..retainers_file,"a") 107 | file_retainers:write("\n"..retainer_name) 108 | io.close(file_retainers) 109 | end 110 | end 111 | end 112 | end 113 | debug("Retainers to run on this character: " .. total_retainers) 114 | return total_retainers 115 | end 116 | 117 | function OpenRetainer(r) 118 | r = r - 1 119 | if not IsAddonVisible("RetainerList") then SomethingBroke("RetainerList", "OpenRetainer("..r..")") end 120 | yield("/wait 0.3") 121 | --yield("/click RetainerList Retainers["..r.."].Select") 122 | SafeCallback("RetainerList", true, 2, r) 123 | yield("/wait 0.5") 124 | while IsAddonVisible("SelectString")==false do 125 | if IsAddonVisible("Talk") and IsAddonReady("Talk") then 126 | --yield("/click Talk Click") 127 | SafeCallback("Talk", true) 128 | end 129 | yield("/wait 0.1") 130 | end 131 | if not IsAddonVisible("SelectString") then SomethingBroke("SelectString", "OpenRetainer("..r..")") end 132 | yield("/wait 0.3") 133 | --yield("/click SelectString Entries[3].Select") 134 | SafeCallback("SelectString", true, 3) 135 | if not IsAddonVisible("RetainerSellList") then SomethingBroke("RetainerSellList", "OpenRetainer("..r..")") end 136 | end 137 | 138 | function CloseRetainer() 139 | while not IsAddonVisible("RetainerList") do 140 | SafeCallback("RetainerSellList", true, -1) 141 | SafeCallback("SelectString", true, -1) 142 | if IsAddonVisible("Talk") and IsAddonReady("Talk") then 143 | --yield("/click Talk Click") 144 | SafeCallback("Talk", true) 145 | end 146 | yield("/wait 0.1") 147 | end 148 | end 149 | 150 | function CountItems() 151 | while IsAddonReady("RetainerSellList")==false do yield("/wait 0.1") end 152 | while string.gsub(GetNodeText("RetainerSellList", 3),"%d","")=="" do 153 | yield("/wait 0.1") 154 | end 155 | count_wait_tick = 0 156 | while GetNodeText("RetainerSellList", 3)==raw_item_count and count_wait_tick < 5 do 157 | count_wait_tick = count_wait_tick + 1 158 | yield("/wait 0.1") 159 | end 160 | yield("/wait 0.1") 161 | raw_item_count = GetNodeText("RetainerSellList", 3) 162 | item_count_trimmed = string.sub(raw_item_count,1,2) 163 | item_count = string.gsub(item_count_trimmed,"%D","") 164 | debug("Items for sale on this retainer: "..item_count) 165 | return tonumber(item_count) 166 | end 167 | 168 | function ClickItem(item) 169 | CloseSales() 170 | while IsAddonVisible("RetainerSell")==false do 171 | if IsAddonVisible("ContextMenu") then 172 | SafeCallback("ContextMenu", true, 0, 0) 173 | yield("/wait 0.2") 174 | elseif IsAddonVisible("RetainerSellList") then 175 | SafeCallback("RetainerSellList", true, 0, item - 1, 1) 176 | else 177 | SomethingBroke("RetainerSellList", "ClickItem()") 178 | end 179 | yield("/wait 0.05") 180 | end 181 | end 182 | 183 | function ReadOpenItem() 184 | last_item = open_item 185 | open_item = "" 186 | item_name_checks = 0 187 | while item_name_checks < name_rechecks and ( open_item == last_item or open_item == "" ) do 188 | item_name_checks = item_name_checks + 1 189 | yield("/wait 0.1") 190 | --open_item = string.sub(string.gsub(GetNodeText("RetainerSell",18),"%W",""),3,-3) 191 | open_item = string.gsub(GetNodeText("RetainerSell",18),"%W","") 192 | end 193 | debug("Last item: "..last_item) 194 | debug("Open item: "..open_item) 195 | end 196 | 197 | function SearchResults() 198 | if IsAddonVisible("ItemSearchResult")==false then 199 | yield("/wait 0.1") 200 | if IsAddonVisible("ItemSearchResult")==false then 201 | SafeCallback("RetainerSell", true, 4) 202 | end 203 | end 204 | yield("/waitaddon ItemSearchResult") 205 | if IsAddonVisible("ItemHistory")==false then 206 | yield("/wait 0.1") 207 | if IsAddonVisible("ItemHistory")==false then 208 | SafeCallback("ItemSearchResult", true, 0) 209 | end 210 | end 211 | yield("/wait 0.1") 212 | ready = false 213 | search_hits = "" 214 | search_wait_tick = 10 215 | while ready==false do 216 | search_hits = GetNodeText("ItemSearchResult", 2) 217 | first_price = string.gsub(GetNodeText("ItemSearchResult", 5, 1, 10),"%D","") 218 | if search_wait_tick > 20 and string.find(GetNodeText("ItemSearchResult", 26), "No items found.") then 219 | ready = true 220 | debug("No items found.") 221 | end 222 | if (string.find(search_hits, "hit") and first_price~="") and (old_first_price~=first_price or search_wait_tick>20) then 223 | ready = true 224 | debug("Ready!") 225 | else 226 | search_wait_tick = search_wait_tick + 1 227 | if (search_wait_tick > 50) or (string.find(GetNodeText("ItemSearchResult", 26), "Please wait") and search_wait_tick > 10) then 228 | SafeCallback("RetainerSell", true, 4) 229 | yield("/wait 0.1") 230 | if IsAddonVisible("ItemHistory")==false then 231 | SafeCallback("ItemSearchResult", true, 0) 232 | end 233 | yield("/wait 0.1") 234 | search_wait_tick = 0 235 | end 236 | end 237 | yield("/wait 0.1") 238 | end 239 | old_first_price = first_price 240 | search_results = string.gsub(GetNodeText("ItemSearchResult", 2),"%D","") 241 | debug("Search results: "..search_results) 242 | return search_results 243 | end 244 | 245 | function SearchPrices() 246 | yield("/waitaddon ItemSearchResult") 247 | prices_list = {} 248 | prices_list_length = 0 249 | for i= 1, 10 do 250 | raw_price = GetNodeText("ItemSearchResult", 5, i, 10) 251 | if raw_price~="" and raw_price~=10 then 252 | trimmed_price = string.gsub(raw_price,"%D","") 253 | prices_list[i] = tonumber(trimmed_price) 254 | end 255 | end 256 | debug(open_item.." Prices") 257 | for price_number, _ in pairs(prices_list) do 258 | debug(prices_list[price_number]) 259 | prices_list_length = prices_list_length + 1 260 | end 261 | end 262 | 263 | function SearchRetainers() 264 | search_retainers = {} 265 | for i= 1, 10 do 266 | market_search_retainer = GetNodeText("ItemSearchResult", 5, i, 5) 267 | if market_search_retainer~="" and market_search_retainer~=5 then 268 | search_retainers[i] = market_search_retainer 269 | end 270 | end 271 | if is_debug then 272 | debug(open_item.." Retainers") 273 | for i = 1,10 do 274 | if search_retainers[i] then 275 | debug(search_retainers[i]) 276 | end 277 | end 278 | end 279 | end 280 | 281 | function HistoryAverage() 282 | while IsAddonVisible("ItemHistory")==false do 283 | SafeCallback("ItemSearchResult", true, 0) 284 | yield("/wait 0.3") 285 | end 286 | yield("/waitaddon ItemHistory") 287 | history_tm_count = 0 288 | history_tm_running = 0 289 | history_list = {} 290 | first_history = string.gsub(GetNodeText("ItemHistory", 3, 2, 6),"%d","") 291 | while first_history=="" do 292 | yield("/wait 0.1") 293 | first_history = string.gsub(GetNodeText("ItemHistory", 3, 2, 6),"%d","") 294 | end 295 | yield("/wait 0.1") 296 | for i= 2, 21 do 297 | raw_history_price = GetNodeText("ItemHistory", 3, i, 6) 298 | if raw_history_price ~= 6 and raw_history_price ~= "" then 299 | trimmed_history_price = string.gsub(raw_history_price,"%D","") 300 | history_list[i-1] = tonumber(trimmed_history_price) 301 | history_tm_count = history_tm_count + 1 302 | end 303 | end 304 | debug("History items: "..history_tm_count) 305 | table.sort(history_list) 306 | for i=1, history_trim_amount do 307 | if history_tm_count > 2 then 308 | table.remove(history_list, history_tm_count) 309 | table.remove(history_list, 1) 310 | history_tm_count = history_tm_count - 2 311 | else 312 | break 313 | end 314 | end 315 | for history_tm_count, history_tm_price in pairs(history_list) do 316 | history_tm_running = history_tm_running + history_tm_price 317 | end 318 | history_trimmed_mean = history_tm_running // history_tm_count 319 | debug("History trimmed mean:" .. history_trimmed_mean) 320 | return history_trimmed_mean 321 | end 322 | 323 | function ItemOverride(mode) 324 | if is_using_overrides then 325 | itemor = nil 326 | is_price_overridden = false 327 | for item_test, _ in pairs(item_overrides) do 328 | if open_item == string.gsub(item_test,"%W","") then 329 | itemor = item_overrides[item_test] 330 | break 331 | end 332 | end 333 | if not itemor then return false end 334 | if itemor.default and mode == "default" then 335 | price = tonumber(itemor.default) 336 | is_price_overridden = true 337 | debug(open_item.." default price: "..itemor.default.." applied!") 338 | end 339 | if itemor.minimum then 340 | if price < itemor.minimum then 341 | price = tonumber(itemor.minimum) 342 | is_price_overridden = true 343 | debug(open_item.." minimum price: "..itemor.minimum.." applied!") 344 | end 345 | end 346 | if itemor.maximum then 347 | if price > itemor.maximum then 348 | price = tonumber(itemor.maximum) 349 | is_price_overridden = true 350 | debug(open_item.." maximum price: "..itemor.maximum.." applied!") 351 | end 352 | end 353 | end 354 | end 355 | 356 | function SetPrice(price) 357 | debug("Setting price to: "..price) 358 | CloseSearch() 359 | SafeCallback("RetainerSell", true, 2, price) 360 | SafeCallback("RetainerSell", true, 0) 361 | CloseSales() 362 | end 363 | 364 | function CloseSearch() 365 | while IsAddonVisible("ItemSearchResult") or IsAddonVisible("ItemHistory") do 366 | yield("/wait 0.1") 367 | if IsAddonVisible("ItemSearchResult") then SafeCallback("ItemSearchResult", true, -1) end 368 | if IsAddonVisible("ItemHistory") then SafeCallback("ItemHistory", true, -1) end 369 | end 370 | end 371 | 372 | function CloseSales() 373 | CloseSearch() 374 | while IsAddonVisible("RetainerSell") do 375 | yield("/wait 0.1") 376 | if IsAddonVisible("RetainerSell") then SafeCallback("RetainerSell", true, -1) end 377 | end 378 | end 379 | 380 | function SomethingBroke(what_should_be_visible, extra_info) 381 | for broken_rechecks=1, 20 do 382 | if IsAddonVisible(what_should_be_visible) then 383 | still_broken = false 384 | break 385 | else 386 | yield("/wait 0.1") 387 | end 388 | end 389 | if still_broken then 390 | yield("/echo It looks like something has gone wrong.") 391 | if what_should_be_visible then yield("/echo "..what_should_be_visible.." should be visible, but it isn't.") end 392 | yield("/echo Attempting to fix this, please wait.") 393 | if extra_info then yield("/echo "..extra_info) end 394 | --yield("") 395 | yield("/echo On second thought, I haven't finished this yet.") 396 | yield("/echo Oops!") 397 | yield("/pcraft stop") 398 | end 399 | end 400 | 401 | function NextCharacter() 402 | current_character = GetCharacterName(true) 403 | next_character = nil 404 | debug("Current character: "..current_character) 405 | for character_number, character_name in pairs(my_characters) do 406 | if character_name == current_character then 407 | next_character = my_characters[character_number+1] 408 | break 409 | end 410 | end 411 | return next_character 412 | end 413 | 414 | function Relog(relog_character) 415 | echo(relog_character) 416 | yield("/ays relog " .. relog_character) 417 | while GetCharacterCondition(1) do 418 | yield("/wait 1.01") 419 | end 420 | while GetCharacterCondition(1, false) do 421 | yield("/wait 1.02") 422 | end 423 | while GetCharacterCondition(45) or GetCharacterCondition(35) do 424 | yield("/wait 1.03") 425 | end 426 | yield("/wait 0.5") 427 | while GetCharacterCondition(35) do 428 | yield("/wait 1.04") 429 | end 430 | yield("/wait 2") 431 | end 432 | 433 | function EnterHouse() 434 | if IsInZone(339) or IsInZone(340) or IsInZone(341) or IsInZone(641) or IsInZone(979) or IsInZone(136) then 435 | debug("Entering house") 436 | if is_use_ar_to_enter_house then 437 | yield("/ays het") 438 | else 439 | yield("/target Entrance") 440 | yield("/target Apartment Building Entrance") 441 | end 442 | yield("/wait 1") 443 | if string.find(string.lower(GetTargetName()), "entrance") then 444 | while IsInZone(339) or IsInZone(340) or IsInZone(341) or IsInZone(641) or IsInZone(979) or IsInZone(136) do 445 | if not is_use_ar_to_enter_house then 446 | yield("/lockon on") 447 | yield("/automove on") 448 | end 449 | yield("/wait 1.2") 450 | end 451 | het_tick = 0 452 | while het_tick < 3 do 453 | if IsPlayerOccupied() then het_tick = 0 454 | elseif IsMoving() then het_tick = 0 455 | else het_tick = het_tick + 0.2 456 | end 457 | yield("/wait 0.200") 458 | end 459 | else 460 | debug("Not entering house?") 461 | end 462 | end 463 | end 464 | 465 | function OpenBell() 466 | EnterHouse() 467 | target_tick = 1 468 | while GetCharacterCondition(50, false) do 469 | if target_tick > 99 then 470 | break 471 | elseif string.lower(GetTargetName())~="summoning bell" then 472 | debug("Finding summoning bell...") 473 | yield("/target Summoning Bell") 474 | target_tick = target_tick + 1 475 | elseif GetDistanceToTarget()<20 then 476 | yield("/lockon on") 477 | yield("/automove on") 478 | yield("/pinteract") 479 | else 480 | yield("/automove off") 481 | yield("/pinteract") 482 | end 483 | yield("/lockon on") 484 | yield("/wait 0.511") 485 | end 486 | if GetCharacterCondition(50) then 487 | yield("/lockon off") 488 | while not IsAddonVisible("RetainerList") do yield("/wait 0.100") end 489 | yield("/wait 0.4") 490 | return true 491 | else 492 | return false 493 | end 494 | end 495 | 496 | function WaitARFinish(ar_time) 497 | title_wait = 0 498 | if not ar_time then ar_time = 10 end 499 | while IsAddonVisible("_TitleMenu")==false do 500 | yield("/wait 5.01") 501 | end 502 | while true do 503 | if IsAddonVisible("_TitleMenu") and IsAddonVisible("NowLoading")==false then 504 | title_wait = title_wait + 1 505 | else 506 | title_wait = 0 507 | end 508 | if title_wait > ar_time then 509 | break 510 | end 511 | yield("/wait 1.0"..ar_time - title_wait) 512 | end 513 | end 514 | 515 | function echo(input) 516 | if is_verbose then 517 | yield("/echo [MarketBotty] "..input) 518 | else 519 | yield("/wait 0.01") 520 | end 521 | end 522 | 523 | function debug(debug_input) 524 | if is_debug then 525 | yield("/echo [MarketBotty][DEBUG] "..debug_input) 526 | else 527 | yield("/wait 0.01") 528 | end 529 | end 530 | 531 | function SafeCallback(...) -- Could be safer, but this is a good start, right? 532 | local callback_table = table.pack(...) 533 | local addon = nil 534 | local update = nil 535 | if type(callback_table[1])=="string" then 536 | addon = callback_table[1] 537 | table.remove(callback_table, 1) 538 | end 539 | if type(callback_table[1])=="boolean" then 540 | update = tostring(callback_table[1]) 541 | table.remove(callback_table, 1) 542 | elseif type(callback_table[1])=="string" then 543 | if string.find(callback_table[1], "t") then 544 | update = "true" 545 | elseif string.find(callback_table[1], "f") then 546 | update = "false" 547 | end 548 | table.remove(callback_table, 1) 549 | end 550 | 551 | local call_command = "/pcall " .. addon .. " " .. update 552 | for _, value in pairs(callback_table) do 553 | if type(value)=="number" then 554 | call_command = call_command .. " " .. tostring(value) 555 | end 556 | end 557 | if IsAddonReady(addon) and IsAddonVisible(addon) then 558 | yield(call_command) 559 | end 560 | end 561 | 562 | function Clear() 563 | next_retainer = 0 564 | prices_list = {} 565 | item_list = {} 566 | item_count = 0 567 | search_retainers = {} 568 | last_item = "" 569 | open_item = "" 570 | is_single_retainer_mode = false 571 | undercut = 1 572 | target_sale_slot = 1 573 | end 574 | 575 | ------------------------------------------------------------------------------------------------------ 576 | 577 | -- Tried to do this as functions, but it was too hard. Oh well. 578 | if is_read_from_files then 579 | if file_exists(config_folder..marketbotty_settings) then 580 | chunk = loadfile(config_folder..marketbotty_settings) 581 | chunk() 582 | end 583 | file_characters = config_folder..characters_file 584 | if file_exists(file_characters) and is_multimode then 585 | my_characters = {} 586 | file_characters = io.input(file_characters) 587 | next_line = file_characters:read("l") 588 | i = 0 589 | while next_line do 590 | i = i + 1 591 | my_characters[i] = next_line 592 | if is_echo_during_read then debug("Character "..i.." from file: "..next_line) end 593 | next_line = file_characters:read("l") 594 | end 595 | file_characters:close() 596 | echo("Characters loaded from file: "..i) 597 | if i <= 1 then 598 | is_multimode = false 599 | end 600 | else 601 | echo(file_characters.." not found!") 602 | end 603 | file_retainers = config_folder..retainers_file 604 | if file_exists(file_retainers) and is_dont_undercut_my_retainers then 605 | my_retainers = {} 606 | file_retainers = io.input(file_retainers) 607 | next_line = file_retainers:read("l") 608 | i = 0 609 | while next_line do 610 | i = i + 1 611 | my_retainers[i] = next_line 612 | if is_echo_during_read then debug("Retainer "..i.." from file: "..next_line) end 613 | next_line = file_retainers:read("l") 614 | end 615 | file_retainers:close() 616 | echo("Retainers loaded from file: "..i) 617 | else 618 | echo(file_retainers.." not found!") 619 | end 620 | file_blacklist = config_folder..blacklist_file 621 | if file_exists(file_blacklist) and is_using_blacklist then 622 | blacklist_retainers = {} 623 | file_blacklist = io.input(file_blacklist) 624 | next_line = file_blacklist:read("l") 625 | i = 0 626 | while next_line do 627 | i = i + 1 628 | blacklist_retainers[i] = next_line 629 | if is_echo_during_read then debug("Blacklist "..i.." from file: "..next_line) end 630 | next_line = file_blacklist:read("l") 631 | end 632 | file_blacklist:close() 633 | echo("Blacklist loaded from file: "..i) 634 | else 635 | echo(file_blacklist.." not found!") 636 | end 637 | file_overrides = config_folder..overrides_file 638 | if file_exists(file_overrides) and is_using_overrides then 639 | chunk = nil 640 | item_overrides = {} 641 | chunk = loadfile(file_overrides) 642 | chunk() 643 | or_count = 0 644 | for _, i in pairs(item_overrides) do or_count = or_count + 1 end 645 | echo("Overrides loaded from file: "..or_count) 646 | else 647 | echo(file_overrides.." not found!") 648 | end 649 | end 650 | uc=1 651 | au=1 652 | if is_override_report then 653 | override_items_count = 0 654 | override_report = {} 655 | end 656 | if is_postrun_one_gil_report then 657 | one_gil_items_count = 0 658 | one_gil_report = {} 659 | end 660 | if is_postrun_sanity_report then 661 | sanity_items_count = 0 662 | sanity_report = {} 663 | end 664 | 665 | if IsAddonVisible("RetainerList") then is_multimode = false end 666 | 667 | ::MultiWait:: 668 | if start_wait and is_autoretainer_while_waiting then 669 | WaitARFinish() 670 | yield("/ays multi d") 671 | end 672 | after_multi = tostring(after_multi) 673 | if string.find(after_multi, "wait logout") then 674 | elseif string.find(after_multi, "wait") then 675 | multi_wait = string.gsub(after_multi,"%D","") * 60 676 | wait_until = os.time() + multi_wait 677 | end 678 | 679 | if is_write_to_files then 680 | is_add_to_file = true 681 | current_character = GetCharacterName(true) 682 | for _, character_name in pairs(my_characters) do 683 | if character_name == current_character then 684 | is_add_to_file = false 685 | break 686 | end 687 | end 688 | if is_add_to_file and current_character~="null" then 689 | file_characters = io.open(config_folder..characters_file,"a") 690 | file_characters:write("\n"..current_character) 691 | io.close(file_characters) 692 | end 693 | end 694 | 695 | ::Startup:: 696 | Clear() 697 | if GetCharacterCondition(1, false) then 698 | echo("Not logged in?") 699 | yield("/wait 1") 700 | Relog(my_characters[1]) 701 | goto Startup 702 | elseif GetCharacterCondition(50, false) then 703 | echo("Not at a summoning bell.") 704 | OpenBell() 705 | goto Startup 706 | elseif IsAddonVisible("RecommendList") then 707 | helper_mode = true 708 | while IsAddonVisible("RecommendList") do 709 | SafeCallback("RecommendList", true, -1) 710 | yield("/wait 0.1") 711 | end 712 | echo("Starting in helper mode!") 713 | goto Helper 714 | elseif IsAddonVisible("RetainerList") then 715 | CountRetainers() 716 | goto NextRetainer 717 | elseif IsAddonVisible("RetainerSell") then 718 | echo("Starting in single item mode!") 719 | is_single_item_mode = true 720 | goto RepeatItem 721 | elseif IsAddonVisible("SelectString") then 722 | echo("Starting in single retainer mode!") 723 | --yield("/click SelectString Entries[2].Select") 724 | SafeCallback("SelectString", true, 2) 725 | yield("/waitaddon RetainerSellList") 726 | is_single_retainer_mode = true 727 | goto Sales 728 | elseif IsAddonVisible("RetainerSellList") then 729 | echo("Starting in single retainer mode!") 730 | is_single_retainer_mode = true 731 | goto Sales 732 | else 733 | echo("Unexpected starting conditions!") 734 | echo("You broke it. It's your fault.") 735 | echo("Do not message me asking for help.") 736 | yield("/pcraft stop") 737 | end 738 | 739 | ------------------------------------------------------------------------------------------------------ 740 | 741 | ::NextRetainer:: 742 | if next_retainer < total_retainers then 743 | next_retainer = next_retainer + 1 744 | else 745 | goto MultiMode 746 | end 747 | yield("/wait 0.1") 748 | target_sale_slot = 1 749 | OpenRetainer(retainers_to_run[next_retainer]) 750 | 751 | ::Sales:: 752 | if CountItems() == 0 then goto Loop end 753 | 754 | ::NextItem:: 755 | ClickItem(target_sale_slot) 756 | 757 | ::Helper:: 758 | au = uc 759 | while IsAddonVisible("RetainerSell")==false do 760 | yield("/wait 0.5") 761 | if GetCharacterCondition(50, false) or IsAddonVisible("RecommendList") then 762 | goto EndOfScript 763 | end 764 | end 765 | 766 | ::RepeatItem:: 767 | ReadOpenItem() 768 | if last_item~="" then 769 | if open_item == last_item then 770 | debug("Repeat: "..open_item.." set to "..price) 771 | goto Apply 772 | end 773 | end 774 | 775 | ::ReadPrices:: 776 | SearchResults() 777 | current_price = string.gsub(GetNodeText("RetainerSell",6),"%D","") 778 | if (string.find(GetNodeText("ItemSearchResult", 26), "No items found.")) then 779 | if type(history_multiplier)=="number" then 780 | price = HistoryAverage() * history_multiplier 781 | price_length = string.len(tostring(price)) 782 | if price_length >= 5 then 783 | exp = 10 ^ math.ceil(price_length * 0.6) 784 | price = math.tointeger(math.floor(price // exp) * exp) 785 | end 786 | else 787 | price_length = string.len(tostring(HistoryAverage())) 788 | price = math.tointeger(10 ^ price_length) 789 | end 790 | CloseSearch() 791 | ItemOverride("default") 792 | goto Apply 793 | end 794 | target_price = 1 795 | if is_blind then 796 | raw_price = GetNodeText("ItemSearchResult", 5, i, 10) 797 | if raw_price~="" and raw_price~=10 then 798 | trimmed_price = string.gsub(raw_price,"%D","") 799 | price = trimmed_price - uc 800 | goto Apply 801 | else 802 | echo("Price not found") 803 | yield("/pcraft stop") 804 | end 805 | else 806 | SearchPrices() 807 | SearchRetainers() 808 | HistoryAverage() 809 | CloseSearch() 810 | end 811 | 812 | if is_check_for_hq then 813 | hq = GetNodeText("RetainerSell",18) 814 | hq = string.gsub(hq,"%g","") 815 | hq = string.gsub(hq,"%s","") 816 | if string.len(hq)==3 then 817 | is_hq = true 818 | debug("High quality!") 819 | else 820 | is_hq = false 821 | debug("Normal quality.") 822 | end 823 | end 824 | 825 | ::PricingLogic:: 826 | if is_price_sanity_checking and target_price < prices_list_length then 827 | if prices_list[target_price] == 1 then 828 | target_price = target_price + 1 829 | goto PricingLogic 830 | end 831 | if prices_list[target_price] <= (history_trimmed_mean // 2) then 832 | target_price = target_price + 1 833 | goto PricingLogic 834 | end 835 | debug("Price sanity checking results:") 836 | debug("target_price "..target_price) 837 | debug("prices_list[target_price] "..prices_list[target_price]) 838 | end 839 | if is_check_for_hq and is_hq and target_price < prices_list_length then 840 | debug("Checking listing "..target_price.." for HQ...") 841 | if target_price==1 then 842 | node_hq = 4 843 | else 844 | node_hq = target_price + 40999 845 | end 846 | --if not IsNodeVisible("ItemSearchResult", 5, target_price, 13) then 847 | if not IsNodeVisible("ItemSearchResult", 1, 26, node_hq, 2, 3) then 848 | debug(target_price.." not HQ") 849 | target_price = target_price + 1 850 | goto PricingLogic 851 | end 852 | end 853 | if is_dont_undercut_my_retainers then 854 | for _, retainer_test in pairs(my_retainers) do 855 | if retainer_test == search_retainers[target_price] then 856 | au = 0 857 | debug("Matching price with own retainer: "..retainer_test) 858 | break 859 | end 860 | end 861 | end 862 | price = prices_list[target_price] - au 863 | ItemOverride() 864 | if is_override_report and is_price_overridden then 865 | override_items_count = override_items_count + 1 866 | if is_multimode then 867 | override_report[override_items_count] = open_item.." on "..GetCharacterName().." set: "..price..". Low: "..prices_list[1] 868 | else 869 | override_report[override_items_count] = open_item.." set: "..price..". Low: "..prices_list[1] 870 | end 871 | elseif price <= 1 then 872 | echo("Should probably vendor this crap instead of setting it to 1. Since this script isn't *that* good yet, I'm just going to set it to...69. That's a nice number. You can deal with it yourself.") 873 | price = 69 874 | if is_postrun_one_gil_report then 875 | one_gil_items_count = one_gil_items_count + 1 876 | if is_multimode then 877 | one_gil_report[one_gil_items_count] = open_item.." on "..GetCharacterName() 878 | else 879 | one_gil_report[one_gil_items_count] = open_item 880 | end 881 | end 882 | elseif is_postrun_sanity_report and target_price ~= 1 then 883 | sanity_items_count = sanity_items_count + 1 884 | if is_multimode then 885 | sanity_report[sanity_items_count] = open_item.." on "..GetCharacterName().." set: "..price..". Low: "..prices_list[1] 886 | else 887 | sanity_report[sanity_items_count] = open_item.." set: "..price..". Low: "..prices_list[1] 888 | end 889 | end 890 | 891 | ::Apply:: 892 | if price ~= tonumber(string.gsub(GetNodeText("RetainerSell",6),"%D","")) then 893 | SetPrice(price) 894 | end 895 | CloseSales() 896 | 897 | ::Loop:: 898 | if helper_mode then 899 | yield("/wait 1") 900 | goto Helper 901 | elseif is_single_item_mode then 902 | yield("/pcraft stop") 903 | elseif not (tonumber(item_count) <= target_sale_slot) then 904 | target_sale_slot = target_sale_slot + 1 905 | goto NextItem 906 | elseif is_single_retainer_mode then 907 | goto EndOfScript 908 | elseif is_single_retainer_mode==false then 909 | CloseRetainer() 910 | goto NextRetainer 911 | end 912 | 913 | ::MultiMode:: 914 | if is_multimode then 915 | while IsAddonVisible("RetainerList") do 916 | SafeCallback("RetainerList", true, -1) 917 | yield("/wait 1") 918 | end 919 | NextCharacter() 920 | if not next_character then goto AfterMulti end 921 | Relog(next_character) 922 | if OpenBell()==false then goto MultiMode end 923 | goto Startup 924 | else 925 | goto EndOfScript 926 | end 927 | 928 | ::AfterMulti:: 929 | yield("/wait 3") 930 | if string.find(after_multi, "logout") then 931 | yield("/logout") 932 | yield("/waitaddon SelectYesno") 933 | yield("/wait 0.5") 934 | SafeCallback("SelectYesno", true, 0) 935 | while GetCharacterCondition(1) do 936 | yield("/wait 1.1") 937 | end 938 | elseif wait_until then 939 | if is_autoretainer_while_waiting then 940 | yield("/ays multi e") 941 | while GetCharacterCondition(1, false) do 942 | yield("/wait 10.1") 943 | end 944 | end 945 | while os.time() < wait_until do 946 | yield("/wait 12") 947 | end 948 | if is_autoretainer_while_waiting then 949 | WaitARFinish() 950 | yield("/ays multi d") 951 | end 952 | goto MultiWait 953 | elseif type(after_multi) == "number" then 954 | Relog(my_characters[after_multi]) 955 | end 956 | 957 | if string.find(after_multi, "wait logout") then 958 | if is_autoretainer_while_waiting then 959 | yield("/ays multi e") 960 | while GetCharacterCondition(1, false) do 961 | yield("/wait 10.2") 962 | end 963 | end 964 | WaitARFinish() 965 | if is_autoretainer_while_waiting then yield("/ays multi d") end 966 | goto MultiWait 967 | end 968 | 969 | if GetCharacterCondition(50, false) and multimode_ending_command then 970 | yield("/wait 3") 971 | yield(multimode_ending_command) 972 | end 973 | 974 | ::EndOfScript:: 975 | while IsAddonVisible("RecommendList") do 976 | SafeCallback("RecommendList", true, -1) 977 | yield("/wait 0.1") 978 | end 979 | echo("---------------------") 980 | echo("MarketBotty finished!") 981 | echo("---------------------") 982 | if is_override_report and override_items_count ~= 0 then 983 | echo("Items that triggered override: "..override_items_count) 984 | for i = 1, override_items_count do 985 | echo(override_report[i]) 986 | end 987 | echo("---------------------") 988 | end 989 | if is_postrun_one_gil_report and one_gil_items_count ~= 0 then 990 | echo("Items that triggered 1 gil check: "..one_gil_items_count) 991 | for i = 1, one_gil_items_count do 992 | echo(one_gil_report[i]) 993 | end 994 | echo("---------------------") 995 | end 996 | if is_postrun_sanity_report and sanity_items_count ~= 0 then 997 | echo("Items that triggered sanity check: "..sanity_items_count) 998 | for i = 1, sanity_items_count do 999 | echo(sanity_report[i]) 1000 | end 1001 | echo("---------------------") 1002 | end 1003 | yield("/pcraft stop") 1004 | yield("/pcraft stop") 1005 | yield("/pcraft stop") 1006 | -------------------------------------------------------------------------------- /SND/MarketBotty/README.md: -------------------------------------------------------------------------------- 1 | https://github.com/plottingCreeper/FFXIV-scripts-and-macros/blob/main/SND/MarketBotty/MarketBotty.lua 2 | # WORKING! 3 |
4 | 5 | I tried to write some documentation at some point, but it was never finished. Here it is I guess, maybe it'll help someone. 6 | PRs welcome. 7 |
8 | 9 | Look, this is dodgy as hell. A lot of FFXIV plugin communities will have opinions on shit like this. I don't agree with those opinions, but regardless, I want to respect them, and especially respect the rules in those communities. DO NOT discuss this in places that ban discussion of market automation. Check the damn rules before saying anything. 10 | 11 |
12 | 13 | Known bug: Items with no sale history cause MarketBotty to stop. Should be fixed Soon™️. 14 | 15 |
16 | 17 | # MarketBotty! 18 | Automatic sale undercutting script. Requires `Something Need Doing (Expanded Edition)`. Multimode currently requires `AutoRetainer`. 19 | Requires Pandora for /pcall, but that's being moved to SND at some point(?) so good luck I guess. 20 |
21 | 22 | ## Starting conditions 23 | If script is started while not at a summoning bell, it will try to find a summoning bell and interact with it. Don't expect too much of this. It's only intended for multimode, for characters that are logged out near a summoning bell or house. 24 | If started while on the retainer list, MarketBotty will run in normal mode. Retainers that have items for sale and aren't on the blacklist will be opened. All items on opened retainers will be repriced according to configured pricing logic. 25 | If started while a retainer is open, MarketBotty will run in single retainer mode. All items on that retainer will be repriced, then the script will stop. 26 | If started while an item price window is open, MarketBotty will run in single item mode. That item will be repriced, then the script will stop. 27 | If started while at a summoning bell, and the `Recommendations` window is open, MarketBotty will start in helper mode. It will wait for an item sale window to appear, then it will reprice the item and go back to waiting. Helper mode will end when the summoning bell is closed. 28 | 29 | ## Repricing logic 30 | MarketBotty will read the first 10 prices from search, and up to 20 prices from history. 31 | A trimmed mean is taken from history prices by culling the highest and lowest `5` (configurable) then averaging what's left. 32 | The lowest search result is found which: 33 | - is higher than 1 gil 34 | - is higher than half the trimmed mean from history 35 | 36 | If none of the first 9 search results qualify, then the 10th search result is chosen. 37 | If the 10th search result is lower than the item `minimum` set in `item_overrides` then item minimum is used instead. 38 | Unless `minimum` is used, the target listings retainer name will be checked. If it's on the `my_retainers` list, the price will be matched rather than undercut. 39 | If after all this the price is somehow only 1, it will be set to 69 instead. That's an old bit of code, but it seemed nice enough, so it's staying in. 40 | 41 | If there are no search results, the price is set to history trimmed mean multiplied by `10` (configurable). 42 | 43 |
44 | 45 | ## Arrays 46 | 47 | ### `'` 48 | If your characters or retainers have a `'` in their name, it needs to be escaped with `\`. 49 | For example, `R'etainer` would be entered into the array as `R\'etainer`. 50 | This does not apply to the files, only the arrays in the script. 51 | 52 | 53 | ### `my_characters` 54 | Used for multimode. First in the list should be main character, which will be switched back to before running `multimode_ending_command` 55 | Will probably cause an infinite wait loop if your character isn't known to AutoRetainer. Rework planned eventually to remove dependency, but it's still going to require you to spell your own characters name correctly. 56 | ``` 57 | my_characters = { --Characters to switch to in multimode 58 | 'Character Name@Server', 59 | 'Character Name@Server', 60 | } 61 | ``` 62 | 63 |
64 |
65 | 66 | ### `my_retainers` 67 | Exact string match. Price sanity checking takes priority, but if the target price is of a retainer on this list, it will be matched rather than undercut. 68 | ``` 69 | my_retainers = { --Retainers to avoid undercutting 70 | 'Dont-undercut-this-retainer', 71 | 'Or-this-one', 72 | } 73 | ``` 74 | 75 |
76 |
77 | 78 | ### `blacklist_retainers` 79 | Exact string match. Retainers on this list will be skipped when opening the next retainer. MarketBotty will still run in single retainer mode if started when a blacklisted retainer is already open. 80 | ``` 81 | blacklist_retainers = { --Do not run script on these retainers 82 | 'Dont-run-this-retainer', 83 | 'Or-this-one', 84 | } 85 | ``` 86 | 87 |
88 | 89 | 90 | `my_characters`, `my_retainers`, and `blacklist_retainers` arrays are **replaced** be the contents of `file_characters`, `file_retainers`, and `file_blacklist`, if found. 91 | Files should be one entry per line, with no additional quotes, escape characters, or formatting of any kind. 92 | Default file location is `%appdata%\XIVLauncher\pluginConfigs\SomethingNeedDoing\` 93 | Default file names are: 94 | `characters_file` = "my_characters.txt" 95 | `retainers_file` = "my_retainers.txt" 96 | `blacklist_file` = "blacklist_retainers.txt" 97 | 98 |
99 | 100 | 101 | ### `item_overrides` 102 | Item names have all non-word characters stripped out of them. Currently only supports `minimum` and `maximum` for setting prices. Hope to add some kind of `autolist` if I can figure out how to click on specific items from inventory. 103 | 104 | 105 |
106 | 107 | ## Script Options 108 | 109 | ### `is_blind` 110 | Undercut the lowest price with no additional logic. Overrides most other options. 111 | 112 | ### `is_dont_undercut_my_retainers` 113 | 114 | 115 | ### `is_price_sanity_checking` 116 | Ignores market results below half the trimmed mean of historical prices. 117 | 118 | ### `is_using_blacklist` 119 | Whether or not to use the blacklist_retainers list. 120 | 121 | ### `undercut` 122 | There's no reason to change this. 1 gil undercut is life. 123 | 124 | ### `history_multiplier` 125 | if no active sales then get average historical price and multiply 126 | 127 | ### `is_using_overrides` 128 | item_overrides table. Currently just minimum price, but expansion are coming soon:tm:! 129 | 130 | ### `is_postrun_one_gil_report` 131 | Requires is_verbose 132 | 133 | ### `is_postrun_sanity_report` 134 | Requires is_verbose 135 | 136 | ### `history_trim_amount` 137 | Trims this many from highest and lowest in history list 138 | 139 | ### `is_verbose` 140 | Basic info in chat about what's going on. 141 | 142 | ### `is_debug` 143 | Absolutely flood your chat with all sorts of shit you don't need to know. 144 | 145 | ### `name_rechecks` 146 | Latency sensitive tunable. Probably sets wrong price if below 5 147 | 148 | 149 | ### `after_multi` 150 | Intent of this is to stop and wait for AutoRetainer to run. It mostly works, but this isn't the super graceful end result I had in mind. 151 | `"logout"` to logout 152 | `"wait 10"` to wait `10` minutes (configurable) 153 | `1` to relog to first character in `my_characters` list. 154 | `wait logout` logs out and waits for a character to be logged in, then waits to be sitting back at the title screen for a while. Crappy AutoRetainer compatibility attempt. 155 | -------------------------------------------------------------------------------- /SND/MoveNear.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | vnavmesh random movement function 3 | Usage: 4 | MoveNear(20, 5, 30, walk, 5) to walk to somewhere random within 5 yalms of x20 y5 z30 5 | MoveNear(20, 5, 30, fly, 5) same as above but flying instead 6 | MoveNear(20, 5, 30, fly, 5, 60) same as above but stop if it takes more than 60 seconds 7 | Note 1: 8 | MoveNear(20, false, 30, walk, 5) probably won't work with navmesh? It's the old visland way of ignoring y 9 | hc_workaround: 10 | MoveNear(20, 5, 30, fly, 5, 60, "OEM_8") hc_workaround is for hybrid camera users. Since hybrid camera sets movement type to standard and vnavmesh requires legacy, this is to be set to an otherwise unused key that is configured in hybrid camera as a movement key. 11 | ]] 12 | 13 | function MoveNear(near_x, near_y, near_z, walkfly, radius, timeout, hc_workaround) 14 | if walkfly~="fly" then 15 | movement = "moveto" 16 | else 17 | movement = "flyto" 18 | end 19 | if type(radius)~="number" then radius = 3 end 20 | if type(timeout)~="number" then timeout = 999 end 21 | if type(near_y)=="number" then 22 | move_y = near_y 23 | else 24 | move_y = string.format("%.3f",GetPlayerRawYPos()) 25 | end 26 | move_x = math.random( (near_x-radius)*1000, (near_x+radius)*1000) /1000 27 | move_z = math.random( (near_z-radius)*1000, (near_z+radius)*1000) /1000 28 | if hc_workaround then yield("/hold "..hc_workaround) end 29 | yield("/vnavmesh "..movement.." "..move_x.." "..move_y.." "..move_z) 30 | start_moving_tick = 0 31 | while IsMoving()==false do 32 | if start_moving_tick>=3 then 33 | yield("/echo Failed to start moving!") 34 | yield("/vnavmesh stop") 35 | if hc_workaround then yield("/release "..hc_workaround) end 36 | yield("/pcraft stop") 37 | else 38 | yield("/wait 0.1") 39 | start_moving_tick = start_moving_tick + 0.1 40 | end 41 | end 42 | move_tick = 0 43 | while IsMoving() and move_tick <= timeout do 44 | move_tick = move_tick + 0.1 45 | if near_y == false then 46 | move_y = math.floor(GetPlayerRawYPos()*1000)/1000 47 | yield("/vnavmesh "..movement.." "..move_x.." "..move_y.." "..move_z) 48 | end 49 | yield("/wait 0.1") 50 | end 51 | yield("/vnavmesh stop") 52 | if hc_workaround then yield("/release "..hc_workaround) end 53 | if is_debug then 54 | yield("/echo Aimed for: X: "..move_x.." Y: "..move_y.." Z: "..move_z) 55 | yield("/echo Landed at: "..string.format("X: %.3f Y: %.3f Z: %.3f", GetPlayerRawXPos(), GetPlayerRawYPos(), GetPlayerRawZPos())) 56 | if move_tick < timeout then 57 | yield("/echo Reason: arrived") 58 | else 59 | yield("/echo Reason: timeout") 60 | end 61 | end 62 | return "X:"..move_x.." Y:"..move_y.." Z:"..move_z 63 | end 64 | -------------------------------------------------------------------------------- /SND/Old retainer sales script.lua: -------------------------------------------------------------------------------- 1 | for retainers = 1, 9 do -- First value is retainer number to start at. Second value is retainer number to stop at. 2 | yield("/waitaddon RetainerList") -- You must have the retainer list open before it will start. 3 | yield("/click select_retainer"..retainers.." ") 4 | -- What do you mean you don't have YesAlready and/or TextAdvance? Ok, fine, we'll work around that. 5 | if IsAddonVisible("SelectString")==false then yield("/click talk ") end -- You should install YesAlready 6 | if IsAddonVisible("SelectString")==false then yield("/click talk ") end -- You should install TextAdvance 7 | yield("/waitaddon SelectString") -- If it stops here, you probably don't have Pandora >= 1.4.0.6 8 | yield("/click select_string3") -- 3 to sell items from player inventory, 4 to sell from retainer inventory 9 | 10 | for list = 0, 19 do 11 | yield("/waitaddon RetainerSellList") 12 | yield("/pcall RetainerSellList true 0 "..list.." 1 ") 13 | if IsAddonVisible("ContextMenu") then yield("/pcall ContextMenu true 0 0 ") else break end 14 | a = 0 15 | while(a == 0)do -- Wait for user to click a price to undercut! Expected but not necessary to use MarketBuddy. 16 | if IsAddonVisible("RetainerSell") then yield("/wait 1") else a = 1 end 17 | end -- No, it's not stuck! You're expecting a bot, but what you have is not. 18 | end 19 | 20 | yield("/pcall RetainerSellList true -2") 21 | yield("/pcall RetainerSellList true -1") 22 | yield("/waitaddon SelectString") -- If it gets stuck here, back out to the retainer list and click resume in SND 23 | yield("/pcall SelectString true -1 ") 24 | yield("/click talk") 25 | end 26 | --------------------------------------------------------------------------------