{$UNDEF SCRIPT_ID}{$DEFINE SCRIPT_ID := 'd4b8e2a7-9c31-4f6b-8a15-7e2c9d04f6b8'} {$UNDEF SCRIPT_REVISION}{$DEFINE SCRIPT_REVISION := '1'} {$IFDEF WINDOWS}{$DEFINE SCRIPT_GUI}{$ENDIF} {$I SRL-B/osr.simba} {$I BashLib/osr.simba} {$I BashLib/optional/handlers/discord.simba} {$I BashLib/optional/handlers/bashcommon.simba} {$SCOPEDENUMS OFF} {$IFDEF SCRIPT_GUI} {$I BashLib/optional/handlers/bashgui.simba} {$ENDIF} { ---------------------------------------------------------------------------------- ~~~ Need Support? Contact Me ~~~ Website: https://bashscripts.shop/ Discord Server: https://discord.gg/qsmKs5uKfR Email: thebigaussie@proton.me Github: https://github.com/BigAussie/BASH ---------------------------------------------------------------------------------- } var SENDHOURLYREPORTMSG: Boolean = True; type EWCMode = ( WC_MODE_BANK, WC_MODE_POWERCUT, WC_MODE_FLETCH, WC_MODE_FLETCH_DROP, WC_MODE_FLETCH_BANK ); EState = ( STATE_LEVEL_UP, STATE_CHOP, STATE_WAIT_CHOPPING, STATE_PROCESS_INVENTORY ); RedwoodCutter = record(TBaseBankScript) CurrentState: EState; RunningTime, ActiveTimer, ActivityTimer: TStopwatch; WorldList: TIntegerArray; Mode: EWCMode; FletchItem: String; KnifeName: String; FletchMakeIndex: Int32; FletchMakeCount: Int32; LogItem: TRSItem; BankChest: TRSObjectV2; Ladder: TRSObjectV2; PluginBox: TBox; HasPluginBox: Boolean; ChopOCRState: Int32; HasSeenChopOverlay: Boolean; LastTreeClickTime: UInt64; ChopIdle: TCountDown; ChopUnknownSince: UInt64; ChopUnknownWaitMs: Int32; LogsCut, LogsPerHour: Int32; SessionLogs: Int32; LastReportTime: UInt64; LastHourlyReportTime: UInt64; StartXP, PrevXP: Int64; PetReceived: Boolean; LastSystemUpdateCheck: UInt64; KeepItems: TRSItemArray; DropItems: TRSItemArray; SpecWait: TCountDown; SpecArmed: Boolean; CheckedTreeIcons: Boolean; end; const BA_SETTINGS_FILE = 'Configs/BASettings.ini'; BA_RW_SETTINGS = ' Redwood Cutter Settings'; BA_RW_ANTIBAN = ' Redwood Cutter Antiban Manager'; RW_PLUGIN_MIN_W = 40; RW_PLUGIN_MIN_H = 48; RW_PLUGIN_MAX_W = 200; RW_PLUGIN_MAX_H = 140; RW_PLUGIN_GRACE_MS = 10000; RW_PLUGIN_COLOR_CHOP = 65280; RW_PLUGIN_COLOR_IDLE = 255; RW_PLUGIN_COLOR_STATS = 16777215; RW_PLUGIN_FALLBACK_BOX: TBox = [4, 5, 170, 142]; RW_OCR_UNKNOWN = -1; RW_OCR_IDLE = 0; RW_OCR_CHOPPING = 1; RW_OCR_UNKNOWN_MIN_MS = 5000; RW_OCR_UNKNOWN_MAX_MS = 6000; RW_BANK_CLICK_PX = 40; RW_IDLE_MS = 28000; RW_LADDER_TILE: TPoint = [2202, 36498]; RW_CLIMB_MS = 15000; RW_LADDER_UPTEXT: TStringArray = ['Climb-up Rope ladder']; RW_LADDER_DOWNTEXT: TStringArray = ['Climb-down Rope ladder', 'Climb-down']; RW_CUT_UPTEXT: TStringArray = ['Cut redwood tree', 'Cut Redwood tree', 'redwood tree']; RW_BANK_TILE: TPoint = [2272, 36526]; RW_BANK_SIZE: Vector3 = [1, 1, 2]; RW_BANK_UPTEXT: TStringArray = ['ank', 'ches']; RW_STOP_AT_LEVEL_SKILLS: array of ERSSkill := [ ERSSkill.WOODCUTTING, ERSSkill.FLETCHING ]; RW_MODE_NAMES: TStringArray = ['Powercut', 'Bank', 'Fletch and drop', 'Fletch and bank']; RW_FLETCH_PRODUCTS: TStringArray = ['Arrow shaft', 'Redwood shield', 'Redwood hiking staff']; RW_KNIVES: TRSItemArray = ['Fletching knife', 'Knife']; RW_AXES: TRSItemArray = [ 'Crystal axe', 'Crystal felling axe', 'Dragon axe', 'Dragon axe (or)', 'Dragon felling axe', 'Infernal axe', 'Rune felling axe', 'Adamant felling axe', 'Mithril felling axe', 'Black felling axe', 'Steel felling axe', 'Rune axe', 'Adamant axe', 'Mithril axe', 'Black axe', 'Steel axe', 'Iron axe', 'Bronze axe' ]; RW_BASKETS: TRSItemArray = ['Log basket', 'Open log basket', 'Forestry basket', 'Open forestry basket']; var Script: RedwoodCutter; {$IFDEF SCRIPT_GUI} type TConfig = record(TBASHPremiumGUI) ModeOpt, FletchOpt: TLabeledComboBox; SavedMode, SavedFletch: String; end; TScriptGUI = TConfig; var Config: TConfig; {$ENDIF} function GetBAUsername(): String; begin if (Login.PlayerIndex < 0) or (Login.PlayerIndex > High(Login.Players)) then Result := 'NoUserNameSelected' else Result := Login.Players[Login.PlayerIndex].User; end; function LoginIfNot(): Boolean; begin if not RSClient.IsLoggedIn() then begin if not Login.LoginPlayer() then TerminateScript('Could not log in player') else Result := True; end; end; function ComboIndexOf(items: TStringArray; value: String): Int32; var i: Int32; begin for i := 0 to High(items) do if SameText(items[i], value) then Exit(i); Result := 0; end; function ModeToString(mode: EWCMode): String; begin case mode of WC_MODE_POWERCUT: Result := 'Powercut'; WC_MODE_BANK: Result := 'Bank'; WC_MODE_FLETCH: Result := 'Fletch'; WC_MODE_FLETCH_DROP: Result := 'Fletch and drop'; WC_MODE_FLETCH_BANK: Result := 'Fletch and bank'; else Result := 'Bank'; end; end; function ModeFromString(text: String): EWCMode; begin if SameText(text, 'Powercut') then Exit(WC_MODE_POWERCUT); if SameText(text, 'Fletch and drop') or SameText(text, 'Fletch') then Exit(WC_MODE_FLETCH_DROP); if SameText(text, 'Fletch and bank') then Exit(WC_MODE_FLETCH_BANK); Result := WC_MODE_BANK; end; function IsFletchMode(mode: EWCMode): Boolean; begin Result := (mode = WC_MODE_FLETCH) or (mode = WC_MODE_FLETCH_DROP) or (mode = WC_MODE_FLETCH_BANK); end; function FletchProductIsStackable(name: String): Boolean; begin Result := LowerCase(name).Contains('arrow shaft'); end; function FletchHintMatches(hint, fletchItem: String): Boolean; begin Result := False; if (hint = '') or (fletchItem = '') then Exit; if LowerCase(fletchItem).Contains('arrow shaft') and LowerCase(hint).Contains('arrow shaft') then Exit(True); if SameText(hint, fletchItem) then Exit(True); if LowerCase(hint).Contains(LowerCase(fletchItem)) then Exit(True); end; function ResolveFletchItem(name: String): String; begin Result := name.Replace(' (u)', ''); if SameText(Result, '(none)') then Result := ''; end; function PluginBorderColor(): TCTS2Color; begin Result := CTS2(2766142, 2, 1.19, 3.47); end; function ChopOCRFilter(color: Int32): TOCRColorFilter; begin Result := TOCRColorFilter.Create([color], [1]); end; function RedwoodLadderColor(): TCTS2Color; begin Result := CTS2(5533303, 15, 0.03, 0.12); end; function RedwoodIconColor(): TCTS2Color; begin Result := CTS2(1267073, 3, 0.08, 1.08); end; function RedwoodIconFinder(): TRSObjectFinder; begin Result := []; Result.Colors := [RedwoodIconColor()]; Result.ClusterDistance := 3; end; function RedwoodBankFinder(): TCTS2Color; begin Result := CTS2(2578283, 10, 0.05, 0.78); end; function UpTextContains(upText: String; needles: TStringArray): Boolean; var i: Int32; text: String; begin Result := False; text := LowerCase(upText); if text = '' then Exit; for i := 0 to High(needles) do if text.Contains(LowerCase(needles[i])) then Exit(True); end; function PluginStatNumber(box: TBox): Int32; var text: String; numbers: TExtendedArray; begin Result := -1; if (box.Width() < 4) or (box.Height() < 4) then Exit; text := OCR.Recognize(box, TOCRColorFilter.Create([RW_PLUGIN_COLOR_STATS], [1]), RS_FONT_PLAIN_12); numbers := text.ExtractNumbers(); if Length(numbers) > 0 then Result := Round(numbers[High(numbers)]); end; function PluginBoxSizeOK(b: TBox): Boolean; begin Result := InRange(b.Width(), RW_PLUGIN_MIN_W, RW_PLUGIN_MAX_W) and InRange(b.Height(), RW_PLUGIN_MIN_H, RW_PLUGIN_MAX_H); end; function ReadChopOverlay(out chopAt, notAt: TBox; out locateChop, locateNot: Single): Int32; var chopFilter, idleFilter: TOCRColorFilter; begin Result := RW_OCR_UNKNOWN; chopAt := [0, 0, -1, -1]; notAt := [0, 0, -1, -1]; chopFilter := ChopOCRFilter(RW_PLUGIN_COLOR_CHOP); idleFilter := ChopOCRFilter(RW_PLUGIN_COLOR_IDLE); locateNot := OCR.LocateText(MainScreen.Bounds, 'NOT woodcutting', RS_FONT_PLAIN_12, idleFilter, notAt); locateChop := OCR.LocateText(MainScreen.Bounds, 'Woodcutting', RS_FONT_PLAIN_12, chopFilter, chopAt); if locateNot > 0 then Result := RW_OCR_IDLE else if locateChop > 0 then Result := RW_OCR_CHOPPING; end; function OverlayTitleHint(chopAt, notAt: TBox; locateChop, locateNot: Single): TBox; begin Result := [0, 0, -1, -1]; if locateNot > 0 then Result := notAt else if locateChop > 0 then Result := chopAt; end; function PickPluginBox(boxes: TBoxArray; hint: TBox): TBox; var i, best: Int32; hintOK: Boolean; begin Result := boxes[0]; best := 0; hintOK := hint.X2 >= hint.X1; for i := 0 to High(boxes) do begin if hintOK and boxes[i].Contains(hint.Center()) then Exit(boxes[i]); if (boxes[i].Y1 < boxes[best].Y1) or ((boxes[i].Y1 = boxes[best].Y1) and (boxes[i].X1 < boxes[best].X1)) then best := i; end; Result := boxes[best]; end; function RedwoodCutter.FindPluginBox(): Boolean; var tpa: TPointArray; atpa: T2DPointArray; boxes: TBoxArray; merged, hint, chopAt, notAt: TBox; locateChop, locateNot: Single; borderPx: Int32; begin Result := False; Self.HasPluginBox := False; Self.PluginBox := [0, 0, -1, -1]; ReadChopOverlay(chopAt, notAt, locateChop, locateNot); hint := OverlayTitleHint(chopAt, notAt, locateChop, locateNot); borderPx := SRL.FindColors(tpa, PluginBorderColor(), MainScreen.Bounds); if borderPx > 0 then begin merged := tpa.Bounds(); atpa := tpa.Cluster(16); atpa.FilterDimensions(RW_PLUGIN_MIN_W, RW_PLUGIN_MIN_H, RW_PLUGIN_MAX_W, RW_PLUGIN_MAX_H); boxes := atpa.ToTBA(); if Length(boxes) > 0 then Self.PluginBox := PickPluginBox(boxes, hint) else if PluginBoxSizeOK(merged) then Self.PluginBox := merged; if PluginBoxSizeOK(Self.PluginBox) then begin Self.HasPluginBox := True; Exit(True); end; end; Self.PluginBox := RW_PLUGIN_FALLBACK_BOX; Self.PluginBox.LimitTo(MainScreen.Bounds); Self.HasPluginBox := True; Exit(True); end; function RedwoodCutter.PluginSearchBox(): TBox; begin Result := Self.PluginBox.Expand(60, 12); Result.LimitTo(MainScreen.Bounds); end; procedure RedwoodCutter.HandleUnknownOverlay(); begin if not Self.HasSeenChopOverlay then Exit; if Self.ChopOCRState <> RW_OCR_UNKNOWN then Exit; if Self.ChopUnknownSince = 0 then Exit; if (GetTickCount() - Self.ChopUnknownSince) < Self.ChopUnknownWaitMs then Exit; Antiban.RandomRotate(); Self.ChopUnknownSince := GetTickCount(); Self.ChopUnknownWaitMs := Random(RW_OCR_UNKNOWN_MIN_MS, RW_OCR_UNKNOWN_MAX_MS); WL.Activity.Restart(); end; function RedwoodCutter.IsChopping(): Boolean; var screenChopAt, screenNotAt: TBox; screenChop, screenNot: Single; begin Result := False; Self.ChopOCRState := ReadChopOverlay(screenChopAt, screenNotAt, screenChop, screenNot); if (Self.ChopOCRState = RW_OCR_IDLE) or (Self.ChopOCRState = RW_OCR_CHOPPING) then Self.HasSeenChopOverlay := True else if Self.HasSeenChopOverlay then Self.HasSeenChopOverlay := False; Result := Self.ChopOCRState = RW_OCR_CHOPPING; Self.FindPluginBox(); if not Self.HasPluginBox then begin Self.HandleUnknownOverlay(); Exit; end; if Self.HasSeenChopOverlay and (Self.ChopOCRState = RW_OCR_UNKNOWN) then begin if Self.ChopUnknownSince = 0 then begin Self.ChopUnknownSince := GetTickCount(); Self.ChopUnknownWaitMs := Random(RW_OCR_UNKNOWN_MIN_MS, RW_OCR_UNKNOWN_MAX_MS); end; end else Self.ChopUnknownSince := 0; Self.HandleUnknownOverlay(); end; procedure RedwoodCutter.ReadPluginStats(); var foundBox, row, search: TBox; value, lineH: Int32; begin Self.FindPluginBox(); if not Self.HasPluginBox then Exit; search := Self.PluginSearchBox(); if OCR.LocateText(search, 'Logs/hr', RS_FONT_PLAIN_12, TOCRColorFilter.Create([RW_PLUGIN_COLOR_STATS], [1]), foundBox) > 0 then begin row := Box(search.X1, foundBox.Y1 - 2, search.X2, foundBox.Y2 + 2); value := PluginStatNumber(row); if value >= 0 then Self.LogsPerHour := value; end; if OCR.LocateText(search, 'cut:', RS_FONT_PLAIN_12, TOCRColorFilter.Create([RW_PLUGIN_COLOR_STATS], [1]), foundBox) > 0 then begin row := Box(search.X1, foundBox.Y1 - 2, search.X2, foundBox.Y2 + 2); value := PluginStatNumber(row); if value >= 0 then Self.LogsCut := value; end else if OCR.LocateText(search, 'Logs/hr', RS_FONT_PLAIN_12, TOCRColorFilter.Create([RW_PLUGIN_COLOR_STATS], [1]), foundBox) > 0 then begin lineH := Max(12, foundBox.Height()); row := Box(search.X1, foundBox.Y1 - lineH - 4, search.X2, foundBox.Y1 - 1); if row.Y1 < search.Y1 then row.Y1 := search.Y1; value := PluginStatNumber(row); if value >= 0 then Self.LogsCut := value; end; end; procedure RedwoodCutter.SetupMap(); begin Map.SetupChunks([Chunk(Box(23, 55, 26, 53), [0, 1])], 8); end; procedure RedwoodCutter.ApplyWalkerSettings(); begin Objects.Setup(Map.Objects(), @Map.Walker); NPCs.Setup(Map.NPCs, @Map.Walker); Banks.MapObjects := []; Banks.ObjectsCache := []; Banks.NPCsCache := []; Map.Walker._DoorHandler.Enabled := True; Map.Walker.ScreenWalk := False; Map.Walker.AdaptiveWalk := False; end; procedure RedwoodCutter.SetupObjects(); begin Self.BankChest := []; Self.BankChest.Walker := @Map.Walker; Self.BankChest.SetupEx(RW_BANK_SIZE, [RW_BANK_TILE]); Self.BankChest.SetupUpText(RW_BANK_UPTEXT); Self.BankChest.Finder.Colors := [RedwoodBankFinder()]; Self.BankChest.Finder.Grow := 8; Self.BankChest.TrackTarget := True; SetLength(Self.BankChest.Rotations, Length(Self.BankChest.Coordinates)); Self.Ladder := []; Self.Ladder.Walker := @Map.Walker; Self.Ladder.SetupEx([1, 1, 3], [RW_LADDER_TILE]); Self.Ladder.SetupUpText(RW_LADDER_UPTEXT); Self.Ladder.Finder.Colors := [RedwoodLadderColor()]; Self.Ladder.Finder.Grow := 6; Self.Ladder.TrackTarget := True; SetLength(Self.Ladder.Rotations, Length(Self.Ladder.Coordinates)); end; function RedwoodCutter.FindRedwoodIconBoxes(): TBoxArray; var atpa: T2DPointArray; boxes: TBoxArray; i: Int32; b: TBox; begin Result := []; atpa := MainScreen.FindObject(RedwoodIconFinder()); if Length(atpa) = 0 then Exit; atpa := atpa.SortFrom(MainScreen.GetPlayerBox().Center()); boxes := atpa.ToTBA(); for i := 0 to High(boxes) do begin b := boxes[i]; if Self.HasPluginBox and Self.PluginBox.Contains(b.Center()) then Continue; Result += b; end; end; function RedwoodCutter.HasRedwoodIcons(): Boolean; begin Result := Length(Self.FindRedwoodIconBoxes()) > 0; end; function RedwoodCutter.OnUpperFloor(): Boolean; begin Result := Map.FullPosition().Plane = 1; end; procedure RedwoodCutter.RequireRedwoodIcons(); const MSG = 'Ensure you are using the BASH profile or woodcutting plugin is enabled'; begin if Self.CheckedTreeIcons then Exit; if not Self.OnUpperFloor() then Exit; if Self.IsChopping() or Self.HasRedwoodIcons() then begin Self.CheckedTreeIcons := True; Exit; end; if WaitUntil(Self.HasRedwoodIcons() or Self.IsChopping(), 200, 5000) then begin Self.CheckedTreeIcons := True; Exit; end; ShowMessage(MSG); TerminateScript(MSG); end; function RedwoodCutter.OnRedwoodPlatform(): Boolean; begin Result := Self.OnUpperFloor() or Self.HasRedwoodIcons(); end; function RedwoodCutter.WalkToRedwoodLadder(): Boolean; begin Result := False; Map.Walker.TargetUpText := []; Map.Walker.RedClicked := False; Map.Walker.ScreenWalk := False; if Map.Position().InRange(RW_LADDER_TILE, 16) then Exit(True); Result := Map.Walker.WebWalk(RW_LADDER_TILE, 6); Minimap.WaitMoving(); end; function RedwoodCutter.ClickRedwoodLadder(goingUp: Boolean): Boolean; begin Result := False; Self.Ladder.Filter.UpText := True; Self.Ladder.Filter.Finder := True; if goingUp then begin Self.Ladder.UpText := RW_LADDER_UPTEXT; Self.Ladder.Filter.Walker := True; Result := Self.Ladder.WalkClick(True, 4); end else begin Self.Ladder.UpText := RW_LADDER_DOWNTEXT; Self.Ladder.Filter.Walker := False; Result := Self.Ladder.Click(True, 4); end; end; function RedwoodCutter.EnsureRedwoodPlatform(): Boolean; begin Result := True; if Self.OnRedwoodPlatform() then Exit; Self.WalkToRedwoodLadder(); if not Self.ClickRedwoodLadder(True) then begin WriteLn('[REDWOOD] Could not climb-up the rope ladder'); Exit(False); end; Minimap.WaitMoving(); Result := WaitUntil(Self.OnUpperFloor() or Self.HasRedwoodIcons(), 200, RW_CLIMB_MS); if Result then begin WriteLn('[REDWOOD] Climbed up to the redwood floor'); Wait(900, 1200); end else WriteLn('[REDWOOD] Climb-up timed out'); end; function RedwoodCutter.ClimbRedwoodDown(): Boolean; begin Result := True; if not Self.OnRedwoodPlatform() then Exit; if not Self.ClickRedwoodLadder(False) then begin WriteLn('[REDWOOD] Could not climb-down the rope ladder'); Exit(False); end; Minimap.WaitMoving(); Result := WaitUntil(not Self.OnUpperFloor(), 200, RW_CLIMB_MS); if Result then WriteLn('[REDWOOD] Climbed down to the guild floor') else WriteLn('[REDWOOD] Climb-down timed out'); end; function RedwoodCutter.ClickRedwoodTree(): Boolean; var boxes: TBoxArray; i: Int32; b: TBox; upText: String; begin Result := False; boxes := Self.FindRedwoodIconBoxes(); for i := 0 to High(boxes) do begin if i > 7 then Break; b := boxes[i]; Mouse.Move(SRL.RandomPoint(b)); WaitUntil(MainScreen.GetUpText() <> '', 40, 250); upText := MainScreen.GetUpText(); if UpTextContains(upText, RW_CUT_UPTEXT) then begin Mouse.Click(MOUSE_LEFT); Minimap.WaitMoving(); Exit(True); end; end; end; procedure RedwoodCutter.BuildItemLists(); var i: Int32; begin Self.LogItem := 'Redwood logs'; Self.KeepItems := RW_AXES + RW_KNIVES + RW_BASKETS + [ 'Rune pouch', 'Divine rune pouch', 'Forester''s ration', 'Skills necklace', 'Xeric''s talisman', 'Rada''s blessing 3', 'Rada''s blessing 4', 'Clue scroll (beginner)', 'Clue scroll (easy)', 'Clue scroll (medium)', 'Clue scroll (hard)', 'Clue scroll (elite)' ]; Self.DropItems := [Self.LogItem, 'Arrow shaft']; for i := 0 to High(RW_FLETCH_PRODUCTS) do Self.DropItems += RW_FLETCH_PRODUCTS[i]; if (Self.Mode = WC_MODE_FLETCH) or FletchProductIsStackable(Self.FletchItem) then begin if Self.FletchItem <> '' then Self.KeepItems += Self.FletchItem; for i := 0 to High(RW_FLETCH_PRODUCTS) do if FletchProductIsStackable(RW_FLETCH_PRODUCTS[i]) then Self.KeepItems += RW_FLETCH_PRODUCTS[i]; end; end; function RedwoodCutter.WaitForBankOpen(): Boolean; begin WaitUntil(Bank.IsOpen() or BankPin.IsOpen(), 65, 5000); if BankPin.IsOpen() then WaitUntil(Bank.IsOpen(), 65, 5000); Result := Bank.IsOpen(); end; function RedwoodCutter.ClickBankChest(): Boolean; begin Result := False; Self.BankChest.Filter.UpText := True; if not (Self.BankChest.Hover() and MainScreen.IsUpText(Self.BankChest.UpText)) then Exit; if not Self.BankChest.Click(True, 0) then Exit; Result := Self.WaitForBankOpen(); end; function RedwoodCutter.OpenBank(): Boolean; begin Result := False; if Bank.IsOpen() then Exit(True); if Self.OnRedwoodPlatform() then if not Self.ClimbRedwoodDown() then Exit; Map.Walker.TargetUpText := []; Map.Walker.RedClicked := False; if Self.BankChest.IsVisible() or Map.Position().InRange(RW_BANK_TILE, RW_BANK_CLICK_PX) then begin if Self.ClickBankChest() then Exit(True); end; Map.Walker.WebWalk(RW_BANK_TILE, 6); Minimap.WaitMoving(); if Self.ClickBankChest() then Exit(True); Result := Banks.WalkOpen(); Result := Self.WaitForBankOpen(); end; procedure RedwoodCutter.DropLogs(); begin Inventory.ShiftDrop(Self.DropItems, Inventory.RandomPattern()); WL.Activity.Restart(); end; procedure RedwoodCutter.BankLogs(); begin if not Self.OpenBank() then begin WriteLn('[BANK] Could not open bank'); Exit; end; Bank.DepositRandomItems(Self.KeepItems); WaitUntil(Inventory.CountEmptySlots() > 0, 65, 4000); if Bank.IsOpen() then Bank.Close(); WaitUntil(not Bank.IsOpen(), 65, 4000); WL.Activity.Restart(); end; procedure RedwoodCutter.FletchLogs(); var knife, hint: String; buttons: TRSButtonArray; tpa: TPointArray; i: Int32; picked: Boolean; begin if not Inventory.ContainsItem(Self.LogItem) then Exit; if Inventory.ContainsItem('Fletching knife') then knife := 'Fletching knife' else if Inventory.ContainsItem('Knife') then knife := 'Knife' else knife := Self.KnifeName; if knife = '' then Exit; Inventory.Use(knife, Self.LogItem); WaitUntil(Make.IsOpen(), 115, 5000); if not Make.IsOpen() then Exit; buttons := Make.GetItemButtons(); picked := False; if (Self.FletchMakeIndex >= 0) and (Self.FletchMakeIndex <= High(buttons)) and (Length(buttons) = Self.FletchMakeCount) then picked := Make.Select(Self.FletchMakeIndex, Make.QUANTITY_ALL, True); if not picked then begin Self.FletchMakeIndex := -1; Self.FletchMakeCount := 0; for i := 0 to High(buttons) do begin Mouse.Move(buttons[i].Bounds); if not WaitUntil((tpa := Make.FindHint()) <> [], 80, 2000) then Continue; hint := OCR.Recognize(tpa.Bounds(), TOCRColorFilter.Create([0]), RS_FONT_PLAIN_12); if not FletchHintMatches(hint, Self.FletchItem) then Continue; picked := Make.Select(i, Make.QUANTITY_ALL, True); if picked then begin Self.FletchMakeIndex := i; Self.FletchMakeCount := Length(buttons); Break; end; end; end; if not picked then begin WriteLn('[FLETCH] Make.Select failed for "', Self.FletchItem, '"'); Exit; end; WaitUntil(not Make.IsOpen(), 115, 5000); WaitUntil(Inventory.CountItem(Self.LogItem) <= 0, 115, 60000); WL.Activity.Restart(); end; procedure RedwoodCutter.ProcessInventory(); begin Self.SessionLogs += Inventory.CountItem(Self.LogItem); case Self.Mode of WC_MODE_POWERCUT: Self.DropLogs(); WC_MODE_BANK: Self.BankLogs(); WC_MODE_FLETCH: Self.FletchLogs(); WC_MODE_FLETCH_DROP: begin Self.FletchLogs(); if not FletchProductIsStackable(Self.FletchItem) then Self.DropLogs(); end; WC_MODE_FLETCH_BANK: begin Self.FletchLogs(); if not FletchProductIsStackable(Self.FletchItem) then Self.BankLogs(); end; end; end; function RedwoodCutter.WaitForWoodcuttingOverlay(): Boolean; begin Result := False; Self.LastTreeClickTime := GetTickCount(); if WaitUntil(Self.IsChopping(), 80, RW_PLUGIN_GRACE_MS) then begin Self.ChopIdle.Restart(); WL.Activity.Restart(); Exit(True); end; end; function RedwoodCutter.SpecWaitMs(): Int32; var roll: Int32; begin roll := Random(100); if roll < 55 then Result := Random(3000, 20000) else if roll < 85 then Result := Random(20000, 60000) else Result := Random(60000, 120000); end; function RedwoodCutter.MaybeUseSpec(): Boolean; var waitMs: Int32; begin Result := False; if not Minimap.IsSpecWeapon() then Exit; if Minimap.GetSpecLevel() < 100 then begin Self.SpecArmed := False; Exit; end; if not Self.SpecArmed then begin waitMs := Self.SpecWaitMs(); Self.SpecWait.Setup(waitMs); Self.SpecArmed := True; Exit; end; if not Self.SpecWait.IsFinished() then Exit; Minimap.EnableSpec(100); WaitUntil(Minimap.GetSpecLevel() < 100, 50, 1500); Wait(250, 450); Self.SpecArmed := False; WL.Activity.Restart(); Result := True; end; function RedwoodCutter.ChopRedwood(): Boolean; begin Result := False; if Bank.IsOpen() then Bank.Close(); if Self.IsChopping() then Exit(True); if Self.HasSeenChopOverlay and (Self.ChopOCRState = RW_OCR_UNKNOWN) then Exit; if not Self.EnsureRedwoodPlatform() then Exit; Self.RequireRedwoodIcons(); Self.MaybeUseSpec(); if Self.IsChopping() then Exit(True); Result := Self.ClickRedwoodTree(); if Result then Result := Self.WaitForWoodcuttingOverlay(); end; procedure RedwoodCutter.WaitChopping(); var why: String; chopping: Boolean; begin why := 'loop ended'; while not Inventory.IsFull() do begin chopping := Self.IsChopping(); if chopping then begin if Self.ChopIdle.IsFinished() then Self.ChopIdle.Restart(); WL.Activity.Restart(); end else if Self.ChopOCRState = RW_OCR_UNKNOWN then begin if not Self.HasSeenChopOverlay then begin why := 'overlay gone'; Break; end; WL.Activity.Restart(); end else if Self.ChopOCRState = RW_OCR_IDLE then begin why := 'overlay idle'; Break; end else if Self.ChopIdle.IsFinished() then begin why := 'overlay idle'; Break; end; if not RSClient.IsLoggedIn() then Exit; Self.HandlePet(); if Self.MaybeUseSpec() then begin why := 'spec used'; Break; end; Self.DoAntiban(); Self.ReadPluginStats(); WL.Activity.Restart(); end; if why = 'loop ended' then begin if Inventory.IsFull() then why := 'inventory full' else why := 'overlay idle'; end; end; function TRSChat.CheckSystemUpdate(minuteTreshold: Integer): Boolean; var b: TBox; s: String; numbers: TExtendedArray; begin b := Chat.Bounds; b.X1 += 4; b.Y1 -= 16; b.Y2 := Chat.Bounds.Y1 - 1; b.X2 := b.X1 + 140; s := OCR.Recognize(b, TOCRColorFilter.Create([65535]), RS_FONT_PLAIN_12); if s.Contains('System update') then begin s := s.After(': '); numbers := s.ExtractNumbers(); if (Length(numbers) > 0) and (numbers[0] <= minuteTreshold) then Result := True; end; end; procedure RedwoodCutter.CheckSystemUpdate(); var currentTime: UInt64; begin currentTime := GetTickCount(); if (currentTime - Self.LastSystemUpdateCheck) < 30000 then Exit; Self.LastSystemUpdateCheck := currentTime; if Chat.CheckSystemUpdate(15) then begin WriteLn('SYSTEM UPDATE DETECTED - logging out'); Logout.ClickLogout(); TerminateScript('System update - safe logout completed'); end; end; function FormatRoundedNumber(Number: Int64): String; begin if Number >= 1000000 then Result := FormatFloat('0.0M', Number / 1000000) else if Number >= 1000 then Result := FormatFloat('0K', Number / 1000) else Result := SRL.FormatNumber(Number); end; function RedwoodCutter.SafeReadXPBar(): Int64; begin try Result := XPBar.Read(); except Result := Self.PrevXP; end; end; procedure RedwoodCutter.TakeScreenshot(Name: String); var screenshotPath: String; fileCount: Integer; begin try if Name = '' then Name := 'Unknown'; try CreateDirectory('Screenshots/'); except Exit; end; try fileCount := Length(GetFiles('Screenshots/', 'png')); except fileCount := 0; end; screenshotPath := 'Screenshots/RedwoodCutter' + Name + '_' + IntToStr(fileCount) + '.png'; SaveScreenshot(screenshotPath); WriteLn('[Screenshot] Saved: ', screenshotPath); except end; end; function RedwoodCutter.GainedPet(): Boolean; begin Result := Chat.FindMessage('been followed', [CHAT_COLOR_RED]) or Chat.FindMessage('sneaking into your backpack', [CHAT_COLOR_RED]); end; procedure RedwoodCutter.SendPetNotification(); var embedIdx: Int32; myExp: Int64; begin if not ENABLEWEBHOOKS then Exit; try myExp := Max(0, Self.PrevXP - Self.StartXP); Discord.Webhook.Content := '**:tada: CONGRATULATIONS! YOU GOT A PET! :tada:**'; Discord.Webhook.ClearEmbeds(); embedIdx := Discord.Webhook.AddEmbed(); Discord.Webhook.Embeds[embedIdx].Title := ':chipmunk: Beaver Pet Obtained! :chipmunk:'; Discord.Webhook.Embeds[embedIdx].Color := $FFD700; Discord.Webhook.Embeds[embedIdx].Description := 'You have been followed by a Beaver!' + LineEnding + 'Location: Woodcutting Guild / Redwood' + LineEnding + 'Logs Cut: ' + FormatRoundedNumber(Self.LogsCut) + LineEnding + 'XP Gained This Session: ' + FormatRoundedNumber(myExp) + LineEnding + 'Active Runtime: ' + SRL.MsToTime(Self.ActiveTimer.ElapsedTime(), Time_Short) + LineEnding + 'Total Runtime: ' + SRL.MsToTime(Self.RunningTime.ElapsedTime(), Time_Short); if Discord.SendScreenshot(False) then WriteLn('[Discord] Pet notification sent!') else WriteLn('[Discord] Failed to send pet notification: ' + Discord.LastError); except WriteLn('[Discord] Error sending pet notification: ' + GetExceptionMessage()); end; end; procedure RedwoodCutter.HandlePet(); begin if Self.PetReceived then Exit; if not Self.GainedPet() then Exit; Self.PetReceived := True; WriteLn('CONGRATULATIONS! You have obtained the Beaver pet!'); Self.SendPetNotification(); Self.TakeScreenshot('PetDrop'); end; procedure RedwoodCutter.SendHourlyReport(); var embedIdx: Int32; runTime: Int64; myExp: Int64; hours: Double; begin if not SENDHOURLYREPORTMSG or not ENABLEWEBHOOKS then Exit; runTime := Self.RunningTime.ElapsedTime(); hours := Max(runTime / 3600000, 0.0001); myExp := Max(0, Self.SafeReadXPBar() - Self.StartXP); try Discord.Webhook.Content := '**Hourly Progress Report** :chart_with_upwards_trend:'; Discord.Webhook.ClearEmbeds(); embedIdx := Discord.Webhook.AddEmbed(); Discord.Webhook.Embeds[embedIdx].Title := 'B.A.S.H Redwood Cutter - Hourly Report'; Discord.Webhook.Embeds[embedIdx].Color := $228B22; Discord.Webhook.Embeds[embedIdx].Description := 'Runtime: ' + SRL.MsToTime(runTime, Time_Short) + LineEnding + 'Location: Woodcutting Guild / Redwood' + LineEnding + 'Logs Cut: ' + FormatRoundedNumber(Self.LogsCut) + LineEnding + 'Logs/Hr: ' + FormatRoundedNumber(Round(Self.LogsCut / hours)) + LineEnding + 'XP Gained: ' + FormatRoundedNumber(myExp) + LineEnding + 'XP/Hour: ' + FormatRoundedNumber(Round(myExp / hours)) + LineEnding + Antiban.BreakScheduleText() + LineEnding + Antiban.SleepScheduleText(); Discord.SendScreenshot(False); WriteLn('[Discord] Hourly report sent!'); except WriteLn('[Discord] Error sending hourly report: ' + GetExceptionMessage()); end; end; procedure RedwoodCutter.CheckHourlyReport(); begin if (GetTickCount() - Self.LastHourlyReportTime) < 3600000 then Exit; Self.SendHourlyReport(); Self.LastHourlyReportTime := GetTickCount(); end; procedure RedwoodCutter.Report(); var runTime: Int64; myExp: Int64; hours: Double; logs: Int64; begin if (GetTickCount() - Self.LastReportTime) < 10000 then Exit; Self.LastReportTime := GetTickCount(); Self.ReadPluginStats(); runTime := Self.RunningTime.ElapsedTime(); myExp := Self.SafeReadXPBar(); if myExp >= Self.PrevXP then Self.PrevXP := myExp; myExp := Max(0, myExp - Self.StartXP); hours := Max(runTime / 3600000, 0.0001); logs := Self.LogsCut; if logs <= 0 then logs := Self.SessionLogs; WriteLn('========================================'); WriteLn(' B.A.S.H Redwood Cutter '); WriteLn('========================================'); WriteLn(' Runtime: ', SRL.MsToTime(runTime, Time_Short)); WriteLn(' Location: Woodcutting Guild'); WriteLn(' Tree: Redwood Mode: ', ModeToString(Self.Mode)); if IsFletchMode(Self.Mode) and (Self.FletchItem <> '') then WriteLn(' Fletch: ', Self.FletchItem); WriteLn(' Logs Cut: ', FormatRoundedNumber(logs)); WriteLn(' Logs/Hr: ', FormatRoundedNumber(Round(logs / hours))); WriteLn(' XP Gained: ', FormatRoundedNumber(myExp)); WriteLn(' XP/Hour: ', FormatRoundedNumber(Round(myExp / hours))); if Self.PetReceived then WriteLn(' PET OBTAINED: Beaver!'); WriteLn(' ', Antiban.BreakScheduleText()); WriteLn(' ', Antiban.SleepScheduleText()); WriteLn('========================================'); WriteLn(' Revision: ' + {$MACRO SCRIPT_REVISION}); WriteLn('========================================'); StopAtLevel.WriteReport(); end; procedure OnBreakStart(Task: PBreakTask); var T: PBreakTask; begin T := Task; Script.ActivityTimer.Pause(); Script.ActiveTimer.Pause(); end; procedure OnBreakFinish(Task: PBreakTask); var T: PBreakTask; begin T := Task; Script.ActivityTimer.Resume(); Script.ActiveTimer.Resume(); end; procedure OnSleepStart(Task: PSleepTask); var T: PSleepTask; begin T := Task; Script.ActivityTimer.Pause(); Script.ActiveTimer.Pause(); end; procedure OnSleepFinish(Task: PSleepTask); var T: PSleepTask; begin T := Task; Script.ActivityTimer.Resume(); Script.ActiveTimer.Resume(); end; procedure TAntiban.TakeBreak(var task: TBreakTask); override; var countdown: TCountDown; i: Int32; activeTasks: PAntibanTaskArray; begin activeTasks := Self.GetActiveTasks(); for i := 0 to High(activeTasks) do activeTasks[i]^.countdown.Pause(); countdown.Init(Abs(Round(SRL.GaussRand(task.Length, task.Length * task.StdVar)))); WriteLn('Taking a break for ' + SRL.MsToTime(countdown.TimeRemaining(), TIME_FORMAL)); if (@Self.OnStartBreak <> nil) then Self.OnStartBreak(@task); if Random() < task.LogoutChance then begin WriteLn('Logging out'); Logout.ClickLogout(); end; if Random() < 0.50 then Self.LoseFocus(); i := 0; while not countdown.IsFinished() do begin if (Inc(i) mod 12 = 0) then WriteLn('Break time remaining: ' + ToString(countdown.TimeRemaining() div 60000) + ' minutes'); if (@Self.OnBreaking <> nil) then Self.OnBreaking(@task, countdown); Wait(Min(countdown.TimeRemaining(), 5 * ONE_SECOND)); end; WriteLn('Break finished'); if (@Self.OnFinishBreak <> nil) then Self.OnFinishBreak(@task); for i := 0 to High(Self.Breaks) do Self.Breaks[i].NextAtTime += GetTickCount() - (countdown.Timeout - countdown.Length); for i := 0 to High(activeTasks) do activeTasks[i]^.countdown.Resume(); task.NextAtTime := GetTimeRunning() + Abs(SRL.GaussRand(task.Interval, task.Interval * task.StdVar)); end; procedure TAntiban.Setup(); override; begin Self.Skills := [ERSSkill.WOODCUTTING, ERSSkill.FLETCHING, ERSSkill.TOTAL]; Self.MinZoom := 10; Self.MaxZoom := 25; Self.OnStartBreak := @OnBreakStart; Self.OnFinishBreak := @OnBreakFinish; Self.OnStartSleep := @OnSleepStart; Self.OnFinishSleep := @OnSleepFinish; inherited; RandomEvents.EnableGenie(); if OVERRIDEBREAKS then begin WriteLn('[ANTIBAN] Custom break override enabled:'); WriteLn(' - Break Interval: ', CUSTOMBREAKINTERVAL, ' minutes'); WriteLn(' - Break Duration: ', CUSTOMBREAKDURATION, ' minutes'); Self.Breaks := []; Self.AddBreak(CUSTOMBREAKINTERVAL * ONE_MINUTE, CUSTOMBREAKDURATION * ONE_MINUTE, 0.33, 0.15); end; end; function TBaseScript.DoAntiban(checkBreaks: Boolean = True; checkSleeps: Boolean = True): Boolean; override; begin Script.ActivityTimer.Pause(); Script.ActiveTimer.Pause(); Antiban.DismissRandom(); Self.TimeRunning.Pause(); Self.OnAntiban := True; checkBreaks := checkBreaks and (WLSettings.GetObject('antiban').getBoolean('breaks') or OVERRIDEBREAKS); checkSleeps := checkSleeps and WLSettings.GetObject('antiban').getJSONObject('sleep').getBoolean('enabled'); if WLSettings.GetObject('antiban').getJSONObject('tasks').getBoolean('enabled') or checkBreaks or checkSleeps then Result := Antiban.DoAntiban(checkBreaks, checkSleeps); Self.TimeRunning.Resume(); Self.OnAntiban := False; if not RSClient.IsLoggedIn() then begin if Length(Script.WorldList) > 0 then Login.SwitchToWorld(Script.WorldList[Random(0, High(Script.WorldList))]); Login.LoginPlayer(); end; Script.ActivityTimer.Resume(); Script.ActiveTimer.Resume(); end; procedure RedwoodCutter.Init(MaxActions: UInt32; MaxTime: UInt64); override; begin StopAtLevel.SetAvailableSkills(RW_STOP_AT_LEVEL_SKILLS); inherited; ClearDebug(); if RSClient.Mode <> ERSClientMode.FIXED then TerminateScript('FIXED CLASSIC mode is required'); if Length(Login.GetPlayer().Worlds) = 0 then TerminateScript('Worlds list is empty'); Self.WorldList := Login.GetPlayer.Worlds; if Length(Self.WorldList) = 0 then TerminateScript('Worlds list is empty'); if not RSClient.IsLoggedIn() then Login.LoginPlayer(); Self.LastSystemUpdateCheck := 0; Self.BuildItemLists(); Self.ChopIdle.Setup(RW_IDLE_MS); Self.ChopUnknownSince := 0; Self.ChopUnknownWaitMs := RW_OCR_UNKNOWN_MIN_MS; Self.HasSeenChopOverlay := False; Self.SpecArmed := False; Self.CheckedTreeIcons := False; Self.FletchMakeIndex := -1; Self.FletchMakeCount := 0; if Inventory.ContainsItem('Fletching knife') then Self.KnifeName := 'Fletching knife' else if Inventory.ContainsItem('Knife') then Self.KnifeName := 'Knife' else Self.KnifeName := 'Knife'; if IsFletchMode(Self.Mode) and (not Inventory.ContainsAny(RW_KNIVES)) then TerminateScript('Fletching requires a knife or fletching knife in the inventory.'); Antiban.Setup(); Self.ActiveTimer.Start(); Self.RunningTime.Start(); Self.ActivityTimer.Start(); Self.SetupMap(); Self.ApplyWalkerSettings(); Self.SetupObjects(); Self.FindPluginBox(); XPBar.Setup(); Self.StartXP := Self.SafeReadXPBar(); Self.PrevXP := Self.StartXP; Self.PetReceived := False; Self.LastHourlyReportTime := GetTickCount(); BASHEnsureRunZoom(0, 25); end; function RedwoodCutter.GetState(): EState; begin if WL.Activity.IsFinished() then TerminateScript('No activity detected in 5 minutes'); if InRange(Chat.GetScrollPosition, 1, 99) then Chat.SetScrollPosition(100); if Chat.LeveledUp() then Exit(STATE_LEVEL_UP); if Inventory.IsFull() then Exit(STATE_PROCESS_INVENTORY); if Self.IsChopping() then Result := STATE_WAIT_CHOPPING else if Self.HasSeenChopOverlay and (Self.ChopOCRState = RW_OCR_UNKNOWN) then Result := STATE_WAIT_CHOPPING else Result := STATE_CHOP; end; procedure RedwoodCutter.Run(MaxActions: UInt32; MaxTime: UInt64); begin Self.Init(MaxActions, MaxTime); repeat LoginIfNot(); Self.HandlePet(); Self.CheckHourlyReport(); Self.Report(); Self.CurrentState := Self.GetState(); case Self.CurrentState of STATE_LEVEL_UP: Chat.HandleLevelUp(); STATE_CHOP: Self.ChopRedwood(); STATE_WAIT_CHOPPING: Self.WaitChopping(); STATE_PROCESS_INVENTORY: Self.ProcessInventory(); end; Self.TotalActions := Self.SessionLogs; until Self.ShouldStop(); Self.LastReportTime := 0; Self.Report(); end; {$IFDEF SCRIPT_GUI} procedure TConfig.SetComboItems(var box: TLabeledComboBox; items: TStringArray; prefer: String); var idx: Int32; begin if not Assigned(box) then Exit; if Length(items) = 0 then items := ['(none)']; box.Clear(); box.AddItemArray(items); idx := ComboIndexOf(items, prefer); if (idx < 0) or (idx > High(items)) then idx := 0; box.SetItemIndex(idx); end; procedure TConfig.RefreshFletchOptions(); var mode: String; showFletch: Boolean; begin if not Assigned(Self.FletchOpt) then Exit; mode := Self.GetSelectedString(Self.ModeOpt); showFletch := SameText(mode, 'Fletch and drop') or SameText(mode, 'Fletch and bank'); Self.FletchOpt.SetVisible(showFletch); Self.SetComboItems(Self.FletchOpt, RW_FLETCH_PRODUCTS, ResolveFletchItem(Self.GetSelectedString(Self.FletchOpt))); end; procedure TConfig.ModeOptChanged({$H-}sender: TObject); {$H+} begin Self.RefreshFletchOptions(); end; procedure TConfig.InitGUI(); override; begin Self.BrandTitle := 'Redwood Cutter'; Self.WindowTitle := 'BigAussies Redwood Cutter'; Self.WebhookTestPrefix := 'Test message from BigAussies Redwood Cutter'; Self.HasFarm := False; Self.HasAIChat := False; Self.HasWorldHopping := False; Self.HasMaxActions := True; Self.HasStopAtLevel := True; Self.StopAtLevelSkills := RW_STOP_AT_LEVEL_SKILLS; Self.HasRandomEvents := True; Self.LegacyAntibanSection := BA_RW_ANTIBAN; end; procedure TConfig.LoadActionSettings(); override; var powerCutLegacy: Boolean; begin Self.SavedMode := ReadINI(Self.Username + BA_RW_SETTINGS, 'Mode', BA_SETTINGS_FILE); if SameText(Self.SavedMode, 'Fletch') then Self.SavedMode := 'Fletch and drop'; if Self.SavedMode = '' then begin powerCutLegacy := StrToBoolDef(ReadINI(Self.Username + BA_RW_SETTINGS, 'PowerCut', BA_SETTINGS_FILE), False); if powerCutLegacy then Self.SavedMode := 'Powercut' else Self.SavedMode := 'Bank'; end; Self.SavedFletch := ReadINI(Self.Username + BA_RW_SETTINGS, 'FletchItem', BA_SETTINGS_FILE); end; procedure TConfig.BuildActionSection(); override; begin Self.AddHeader(Self.ActionSection, 'Action', 'Chop redwood trees at the Woodcutting Guild.'); Self.AddCombo(Self.ActionSection, Self.ModeOpt, 'lcb_rw_mode', 'Mode', 24, 110, RW_MODE_NAMES, ComboIndexOf(RW_MODE_NAMES, Self.SavedMode)); Self.ModeOpt.SetTooltip('Powercut drops logs. Bank deposits them. Fletch modes need a knife in the inventory.'); Self.AddCombo(Self.ActionSection, Self.FletchOpt, 'lcb_rw_fletch', 'Fletch into', BASH_GUI_COL2, 110, RW_FLETCH_PRODUCTS, ComboIndexOf(RW_FLETCH_PRODUCTS, Self.SavedFletch)); Self.FletchOpt.SetTooltip('What to fletch redwood logs into.'); Self.ModeOpt.ComboBox.SetOnChange(@Self.ModeOptChanged); Self.RefreshFletchOptions(); if Self.SavedFletch <> '' then Self.SetComboItems(Self.FletchOpt, RW_FLETCH_PRODUCTS, ResolveFletchItem(Self.SavedFletch)); end; procedure TConfig.ApplyActionSettings(); override; var modeName, fletchName: String; begin modeName := Self.GetSelectedString(Self.ModeOpt); fletchName := Self.GetSelectedString(Self.FletchOpt); Script.Mode := ModeFromString(modeName); Script.FletchItem := ResolveFletchItem(fletchName); WriteINI(Self.Username + BA_RW_SETTINGS, 'Mode', modeName, BA_SETTINGS_FILE); WriteINI(Self.Username + BA_RW_SETTINGS, 'FletchItem', fletchName, BA_SETTINGS_FILE); WriteLn('Settings saved:'); WriteLn('Mode: ', modeName); if IsFletchMode(Script.Mode) then WriteLn('Fletch: ', Script.FletchItem); end; procedure TConfig.StartScript(Sender: TObject); override; var modeName, fletchName: String; begin if Sender = nil then; modeName := Self.GetSelectedString(Self.ModeOpt); fletchName := Self.GetSelectedString(Self.FletchOpt); if IsFletchMode(ModeFromString(modeName)) and ((fletchName = '') or SameText(fletchName, '(none)')) then begin ShowMessage('Choose what to fletch, or pick Powercut / Bank.'); Exit; end; try Self._WarmBuildCache(); except end; inherited; end; {$I BashLib/optional/handlers/bashgui_host_scriptgui.simba} {$ENDIF} begin SettingsHandler.CheckBashProfileRequired(); Discord.Setup(); {$IFDEF SCRIPT_GUI} Sync(@Config.Run); {$ENDIF} Script.Run(WLSettings.MaxActions, WLSettings.MaxTime); end.