{$DEFINE SCRIPT_ID := 'degrime-002'} {$DEFINE SCRIPT_REVISION := '1'} {$IFDEF WINDOWS}{$DEFINE SCRIPT_GUI}{$ENDIF} {$I SRL-T/osr.simba} {$I WaspLib/osr.simba} {------------------------------------------------------------------------- Constants for timing and wait values used throughout the script. -------------------------------------------------------------------------} const // General wait constants cInterfaceCloseDelay = 300; // Delay after closing interfaces cWaitUntilDelay = 65; // Poll delay for WaitUntil calls cRandomAdjustment = 1000; // Maximum random adjustment // Magic interface retry and castable wait times cMagicInterfaceRetryDelay = 500; // Delay to retry opening magic interface cCastableWaitMin = 400; // Minimum wait time for checking castability cCastableWaitExpected = 500; // Expected wait time for checking castability cCastableWaitMax = 600; // Maximum wait time for checking castability cSkewParam = 3; // Skew parameter for all srl.SkewedRand calls // Wait times in the cast animation (WaitCast procedure) cUnfocusedWaitMin = 5000; // Minimum wait time when unfocused cUnfocusedWaitExpected = 7000; // Expected wait time when unfocused cUnfocusedWaitMax = 15000; // Maximum wait time when unfocused cFocusedWaitMin = 2400; // Minimum wait time when focused cFocusedWaitExpected = 2600; // Expected wait time when focused cFocusedWaitMax = 2700; // Maximum wait time when focused // Wait times for bank deposit and withdrawal actions cDepositWaitMin = 450; // Minimum wait time for deposit cDepositWaitExpected = 550; // Expected wait time for deposit cDepositWaitMax = 650; // Maximum wait time for deposit cWithdrawWaitMin = 300; // Minimum wait time for withdrawal cWithdrawWaitExpected = 400; // Expected wait time for withdrawal cWithdrawWaitMax = 550; // Maximum wait time for withdrawal cBankOpenWaitMin = 300; // Minimum wait time for bank open cBankOpenWaitExpected = 400; // Expected wait time for bank open cBankOpenWaitMax = 550; // Maximum wait time for bank open cPostBankCloseDelay = 500; // Delay after closing bank // Delay for main loop iterations in Run procedure cLoopDelayMin = 300; // Minimum loop delay cLoopDelayExpected = 400; // Expected loop delay cLoopDelayMax = 500; // Maximum loop delay // Focus duration constants (for focus-loss simulation) cFocusSkewMin = 2; // Minimum skew for focus duration calculation cFocusSkewMax = 4; // Maximum skew for focus duration calculation cFocusSwitchChance = 1; // Early switch chance threshold (in 0..cFocusRandomRange scale) cFocusRandomRange = 1000; // Random range used for early focus switch check cFocusedDurationExpected = 15 * 60 * 1000; // Expected focused duration (15 minutes in ms) cFocusedDurationMin = 0; // Minimum focused duration for srl.SkewedRand call cFocusedDurationMax = 20 * 60 * 1000; // Maximum focused duration (20 minutes in ms) cUnfocusedDurationExpected = 4 * 60 * 1000; // Expected unfocused duration (4 minutes in ms) cUnfocusedDurationMin = 0; // Minimum unfocused duration for srl.SkewedRand call cUnfocusedDurationMax = 8 * 60 * 1000; // Maximum unfocused duration (8 minutes in ms) // Time conversion constant cMsPerHour = 3600000; // Milliseconds per hour {------------------------------------------------------------------------- End of constants -------------------------------------------------------------------------} {------------------------------------------------------------------------- Shared Helper Functions -------------------------------------------------------------------------} { Returns a wait time using the skewed random function with given expected, min, and max values. } function GetWaitTime(expected, minVal, maxVal: Integer): Integer; begin Result := srl.SkewedRand(expected, minVal, maxVal, cSkewParam); end; { Waits until the bank is open. If the bank is not open, it walks to the bank and waits until open. } procedure WaitForBankOpen(); begin if not Bank.IsOpen() then begin Bank.WalkOpen(); WaitUntil(Bank.IsOpen(), cWaitUntilDelay, GetWaitTime(cBankOpenWaitExpected, cBankOpenWaitMin, cBankOpenWaitMax)); end; end; { Deposits a bank item and waits until the specified item is no longer in the inventory. } procedure DepositItemWithWait(bankItem: TRSBankItem; itemName: string); begin Bank.DepositItem(bankItem, True); WaitUntil(not Inventory.ContainsItem(itemName), cWaitUntilDelay, GetWaitTime(cDepositWaitExpected, cDepositWaitMin, cDepositWaitMax)); end; { Withdraws a bank item and waits until the specified item is present in the inventory. } procedure WithdrawItemWithWait(bankItem: TRSBankItem; itemName: string); begin Bank.WithdrawItem(bankItem, True); WaitUntil(Inventory.ContainsItem(itemName), cWaitUntilDelay, GetWaitTime(cWithdrawWaitExpected, cWithdrawWaitMin, cWithdrawWaitMax)); end; { Checks if the required runes (or rune pouches) are available.} function HasRequiredRunes(): Boolean; begin Result := ((Inventory.CountItem('Earth rune') >= 4) and (Inventory.CountItem('Nature rune') >= 2)) or Inventory.ContainsItem('Rune pouch') or Inventory.ContainsItem('Divine rune pouch'); end; {------------------------------------------------------------------------- End of shared helpers -------------------------------------------------------------------------} type // State machine reflecting in-game flow. TDegrimeState = ( DS_INVENTORY_CHECK, DS_CHECK_CASTABLE, DS_CAST_WAIT, DS_BANK, DS_TERMINATE ); // Enumeration for the herbs. THerbs = ( herbHuasca, herbTarromin, herbHarralander, herbMarrentill, herbGuamLeaf, herbIritLeaf, herbAvantoe, herbDwarfWeed, herbLantadyme, herbToadflax, herbCadantine, herbKwuarm, herbTorstol, herbRanarrWeed, herbSnapdragon ); const // Mapping from enum to the full "Grimy ..." herb name. HerbNames: array[THerbs] of string = [ 'Grimy Huasca', 'Grimy Tarromin', 'Grimy Harralander', 'Grimy Marrentill', 'Grimy Guam Leaf', 'Grimy Irit Leaf', 'Grimy Avantoe', 'Grimy Dwarf Weed', 'Grimy Lantadyme', 'Grimy Toadflax', 'Grimy Cadantine', 'Grimy Kwuarm', 'Grimy Torstol', 'Grimy Ranarr Weed', 'Grimy Snapdragon' ]; // Mapping from enum to the clean herb name. CleanHerbNames: array[THerbs] of string = [ 'Huasca', 'Tarromin', 'Harralander', 'Marrentill', 'Guam Leaf', 'Irit Leaf', 'Avantoe', 'Dwarf Weed', 'Lantadyme', 'Toadflax', 'Cadantine', 'Kwuarm', 'Torstol', 'Ranarr Weed', 'Snapdragon' ]; var GlobalCurrentHerb: THerbs = herbIritLeaf; SimulateFocusLoss: Boolean = False; // Global flag to enable focus-loss simulation type // Type for focus state. TFocusState = (fsFocused, fsUnfocused); // Main script record – inherits from TBaseBankScript. TDegrimeScript = record(TBaseBankScript) State: TDegrimeState; CastCount: UInt32; CastRetries: Integer; CurrentHerb: THerbs; CleanHerbBank: TRSBankItem; GrimyHerbBank: TRSBankItem; StartXP: Integer; FocusState: TFocusState; FocusTimer: Int64; end; {------------------------------------------------------------------------- Setup: Configure antiban settings. -------------------------------------------------------------------------} procedure TAntiban.Setup(); override; begin Antiban.Skills := [ERSSkill.TOTAL, ERSSKILL.MAGIC]; Antiban.MinZoom := 63; Antiban.MaxZoom := 83; inherited; end; {------------------------------------------------------------------------- Helper Methods for Script Flow -------------------------------------------------------------------------} { DS_INIT: Initialization – close interfaces, set bank region, and initialize focus timer. } procedure TDegrimeScript.Init(maxActions: UInt32; maxTime: UInt64); override; begin inherited; RSInterface.Close(); Wait(cInterfaceCloseDelay); Self.RSW.SetupNamedRegion(); // Initialize herb selection and bank items. Self.CurrentHerb := GlobalCurrentHerb; Self.CleanHerbBank := TRSBankItem.Setup(CleanHerbNames[Self.CurrentHerb], Bank.QUANTITY_ALL, True); Self.GrimyHerbBank := TRSBankItem.Setup(HerbNames[Self.CurrentHerb], Bank.QUANTITY_ALL, False); Self.CastCount := 0; Self.CastRetries := 0; // Initialize XP reporting and focus simulation if enabled. Self.StartXP := XPBar.Read(); if SimulateFocusLoss then begin Self.FocusState := fsFocused; Self.FocusTimer := GetTimeRunning() + GetWaitTime(cFocusedDurationExpected, cFocusedDurationMin, cFocusedDurationMax) + Random(-cRandomAdjustment, cRandomAdjustment); end else begin Self.FocusState := fsFocused; Self.FocusTimer := 0; end; Self.State := DS_INVENTORY_CHECK; end; { DS_INVENTORY_CHECK: Verify that the inventory contains the grimy herb and required runes. } procedure TDegrimeScript.InventoryCheck(); begin Self.SetAction('Checking inventory for ' + HerbNames[Self.CurrentHerb] + ' and required runes.'); if Inventory.Count() = 0 then begin Self.SetAction('Inventory is empty. Terminating.'); Self.State := DS_TERMINATE; Exit; end; if not Inventory.ContainsItem(HerbNames[Self.CurrentHerb]) then begin Self.SetAction('No grimy herb in inventory. Switching to bank.'); Self.State := DS_BANK; Exit; end; if not HasRequiredRunes() then begin Self.SetAction('Missing required runes/pouch in inventory. Switching to bank.'); Self.State := DS_BANK; end else Self.State := DS_CHECK_CASTABLE; end; { DS_CHECK_CASTABLE: Open magic interface and verify the Degrime spell is castable. } procedure TDegrimeScript.CheckCastable(); var waitTime: Integer; begin if not Magic.Open() then begin Self.SetAction('Magic interface failed to open. Retrying...'); Wait(cMagicInterfaceRetryDelay); if not Magic.Open() then begin Self.SetAction('Unable to open magic interface. Terminating.'); Self.State := DS_TERMINATE; Exit; end; end; waitTime := GetWaitTime(cCastableWaitExpected, cCastableWaitMin, cCastableWaitMax); Wait(waitTime); if Magic.CanActivate(ERSSpell.Degrime) then begin Self.SetAction('Spell is castable.'); Self.State := DS_CAST_WAIT; end else begin Self.SetAction('Spell unavailable. Terminating.'); Self.State := DS_TERMINATE; end; end; { DS_CAST_WAIT: Attempts to cast the spell and waits the appropriate amount of time to take the next action} procedure TDegrimeScript.CastAndWait(); var waitTime: Integer; begin Self.CheckCastable(); if Self.State = DS_TERMINATE then Exit; if Magic.CanActivate(ERSSpell.Degrime) then begin Self.SetAction('Casting Degrime spell.'); Inc(Self.CastCount); Magic.CastSpell(ERSSpell.Degrime); end else begin Self.State := DS_TERMINATE; Exit; end; if SimulateFocusLoss then Self.SwitchFocusState(); if SimulateFocusLoss and (Self.FocusState = fsUnfocused) then begin waitTime := GetWaitTime(cUnfocusedWaitExpected, cUnfocusedWaitMin, cUnfocusedWaitMax) + Random(-cRandomAdjustment, cRandomAdjustment); Self.SetAction('Unfocused: waiting for cast animation (' + ToStr(waitTime) + ' ms).'); end else begin waitTime := GetWaitTime(cFocusedWaitExpected, cFocusedWaitMin, cFocusedWaitMax); Self.SetAction('Waiting for cast animation (' + ToStr(waitTime) + ' ms).'); end; Bank.Hover(); Wait(waitTime); Self.Report(); Self.State := DS_BANK; end; { Deposits clean herbs using the shared helper function. } procedure TDegrimeScript.DepositCleanHerbs(); begin Self.SetAction('Depositing clean herbs.'); DepositItemWithWait(Self.CleanHerbBank, CleanHerbNames[Self.CurrentHerb]); end; { Withdraws grimy herbs using the shared helper function. } procedure TDegrimeScript.WithdrawGrimyHerbs(); begin if Inventory.ContainsItem(CleanHerbNames[Self.CurrentHerb]) then begin Self.SetAction('Deposit might have failed, retry'); DepositCleanHerbs(); end; if Bank.ContainsItem(HerbNames[Self.CurrentHerb]) then begin Self.SetAction('Withdrawing grimy herbs.'); WithdrawItemWithWait(Self.GrimyHerbBank, HerbNames[Self.CurrentHerb]); end else begin if (not Inventory.ContainsItem(CleanHerbNames[Self.CurrentHerb])) then begin Self.SetAction('No grimy herbs left in bank. Terminating.'); Self.State := DS_TERMINATE; end else begin Self.SetAction('Deposit might have failed, retry'); DepositCleanHerbs(); end end; end; { DS_BANK: Handle bank interactions (deposit/withdraw). } procedure TDegrimeScript.BankInteraction(); var runesOk: Boolean; begin Self.SetAction('Performing bank interaction.'); WaitForBankOpen(); runesOk := ((Inventory.CountItem('Earth rune') >= 4) and (Inventory.CountItem('Nature rune') >= 2)) or Inventory.ContainsItem('Rune pouch') or Inventory.ContainsItem('Divine rune pouch'); { Case 1: Inventory contains grimy herbs. } if Inventory.ContainsItem(HerbNames[Self.CurrentHerb]) then begin if runesOk then begin Self.SetAction('Grimy herb present with required runes. Closing bank.'); Bank.Close(); Wait(cPostBankCloseDelay); Self.State := DS_CHECK_CASTABLE; end else begin Self.SetAction('Grimy herb present but missing required runes. Terminating.'); Self.State := DS_TERMINATE; end; Exit; end { Case 2: Inventory contains clean herbs. } else if Inventory.ContainsItem(CleanHerbNames[Self.CurrentHerb]) then begin DepositCleanHerbs(); WithdrawGrimyHerbs(); if Self.State <> DS_TERMINATE then Self.State := DS_CHECK_CASTABLE; Exit; end { Case 3: Inventory is empty. } else if Inventory.Count() = 0 then begin Self.SetAction('Inventory is empty. Terminating.'); Self.State := DS_TERMINATE; Exit; end { Case 4: Fallback unexpected state. } else begin if runesOk and Bank.ContainsItem(HerbNames[Self.CurrentHerb]) then begin WithdrawGrimyHerbs(); if Self.State <> DS_TERMINATE then Self.State := DS_CHECK_CASTABLE; end else begin Self.SetAction('Missing required runes/pouch or herbs. Terminating.'); Self.State := DS_TERMINATE; end; end; end; {------------------------------------------------------------------------- Focus-State Functionality -------------------------------------------------------------------------} { SwitchFocusState: Determines when to switch between focused and unfocused modes using predefined durations and a chance for an early switch. (inspired by BigAussie AIO Lunar Spells) } procedure TDegrimeScript.SwitchFocusState(); var skewness: Integer; focusedDuration, unfocusedDuration: Integer; begin skewness := Random(cFocusSkewMin, cFocusSkewMax); focusedDuration := GetWaitTime(cFocusedDurationExpected, cFocusedDurationMin, cFocusedDurationMax) + Random(-cRandomAdjustment, cRandomAdjustment); unfocusedDuration := GetWaitTime(cUnfocusedDurationExpected, cUnfocusedDurationMin, cUnfocusedDurationMax) + Random(-cRandomAdjustment, cRandomAdjustment); if GetTimeRunning() >= Self.FocusTimer then begin if Self.FocusState = fsFocused then begin Self.FocusState := fsUnfocused; Self.SetAction('Switched to Unfocused mode for ' + ToStr(unfocusedDuration) + ' ms.'); Self.FocusTimer := GetTimeRunning() + unfocusedDuration; end else begin Self.FocusState := fsFocused; Self.SetAction('Switched to Focused mode for ' + ToStr(focusedDuration) + ' ms.'); Self.FocusTimer := GetTimeRunning() + focusedDuration; end; end else begin if Random(cFocusRandomRange) < cFocusSwitchChance then begin if Self.FocusState = fsFocused then begin Self.FocusState := fsUnfocused; Self.SetAction('Early switch to Unfocused mode.'); Self.FocusTimer := GetTimeRunning() + unfocusedDuration; end else begin Self.FocusState := fsFocused; Self.SetAction('Early switch to Focused mode.'); Self.FocusTimer := GetTimeRunning() + focusedDuration; end; end; end; end; {------------------------------------------------------------------------- Reporting Functionality -------------------------------------------------------------------------} { Report: Logs total casts, runtime, and XP splits for magic and herblore. } procedure TDegrimeScript.Report(); const ReportWidth = 60; var currentXP, totalGainedXP, magicExp, herbloreExp: Integer; castsPerHour, magicExpPerHour, herbloreExpPerHour: Integer; runtimeStr, focusStateStr, header, titleLine: string; timeInHours, leftPadding: Double; begin currentXP := XPBar.Read(); totalGainedXP := currentXP - Self.StartXP; magicExp := 83 * Self.CastCount; herbloreExp := totalGainedXP - magicExp; timeInHours := GetTimeRunning() / cMsPerHour; castsPerHour := Round(Self.CastCount / timeInHours); magicExpPerHour := Round(magicExp / timeInHours); herbloreExpPerHour := Round(herbloreExp / timeInHours); runtimeStr := SRL.MsToTime(GetTimeRunning, Time_Short); if Self.FocusState = fsFocused then focusStateStr := 'Focused' else focusStateStr := 'Unfocused'; header := StringOfChar('=', ReportWidth); titleLine := 'Degrime Cast Report'; leftPadding := (ReportWidth - Length(titleLine)) / 2; WriteLn(header); WriteLn(StringOfChar(' ', Trunc(leftPadding)) + titleLine); WriteLn(header); WriteLn(Format('%-16s %10s %-18s %10s', ['Casts:', ToStr(Self.CastCount), 'Casts/Hour:', ToStr(castsPerHour)])); WriteLn(Format('%-16s %10s %-18s %10s', ['Magic Exp:', ToStr(magicExp), 'Magic Exp/Hour:', ToStr(magicExpPerHour)])); WriteLn(Format('%-16s %10s %-18s %10s', ['Herblore Exp:', ToStr(herbloreExp), 'Herblore Exp/Hour:', ToStr(herbloreExpPerHour)])); WriteLn(Format('%-16s %10s %-18s %10s', ['Runtime:', runtimeStr, 'Focus State:', focusStateStr])); WriteLn(header); end; {Override the standard PrintReport as I won't be using it} procedure TDegrimeScript.PrintReport(); override; begin Self.PreviousAction := Self.Action; end; {------------------------------------------------------------------------- Main Run Loop -------------------------------------------------------------------------} procedure TDegrimeScript.Run(maxActions: UInt32; maxTime: UInt64); var loopDelay: Integer; begin Self.Init(maxActions, maxTime); while not Self.ShouldStop() do begin case Self.State of DS_INVENTORY_CHECK: Self.InventoryCheck(); DS_CHECK_CASTABLE: Self.CheckCastable(); DS_CAST_WAIT: Self.CastAndWait(); DS_BANK: Self.BankInteraction(); DS_TERMINATE: Break; end; Self.DoAntiban(); loopDelay := GetWaitTime(cLoopDelayExpected, cLoopDelayMin, cLoopDelayMax); Wait(loopDelay); end; Self.SetAction('Script terminated. Total casts: ' + ToStr(Self.CastCount)); end; {$IFDEF SCRIPT_GUI} type TDegrimeConfig = record(TScriptForm) HerbSelector: TLabeledCombobox; BankCheckbox: TLabeledCheckbox; FocusLossCheckbox: TLabeledCheckbox; end; procedure TDegrimeConfig.StartScript(sender: TObject); override; begin case Self.HerbSelector.GetItemIndex() of 0: GlobalCurrentHerb := herbHuasca; 1: GlobalCurrentHerb := herbTarromin; 2: GlobalCurrentHerb := herbHarralander; 3: GlobalCurrentHerb := herbMarrentill; 4: GlobalCurrentHerb := herbGuamLeaf; 5: GlobalCurrentHerb := herbIritLeaf; 6: GlobalCurrentHerb := herbAvantoe; 7: GlobalCurrentHerb := herbDwarfWeed; 8: GlobalCurrentHerb := herbLantadyme; 9: GlobalCurrentHerb := herbToadflax; 10: GlobalCurrentHerb := herbCadantine; 11: GlobalCurrentHerb := herbKwuarm; 12: GlobalCurrentHerb := herbTorstol; 13: GlobalCurrentHerb := herbRanarrWeed; 14: GlobalCurrentHerb := herbSnapdragon; else GlobalCurrentHerb := herbMarrentill; end; SimulateFocusLoss := Self.FocusLossCheckbox.IsChecked(); inherited; end; procedure TDegrimeConfig.Run(); override; var tab: TTabSheet; begin Self.Setup('Degrime Script Config'); Self.Start.SetOnClick(@Self.StartScript); Self.AddTab('Script Settings'); tab := Self.Tabs[High(Self.Tabs)]; with Self.HerbSelector do begin Create(tab); SetCaption('Select Herb:'); SetLeft(20); SetTop(50); Combobox.SetStyle(csDropDownList); Combobox.GetItems.Add('Grimy Huasca'); Combobox.GetItems.Add('Grimy Tarromin'); Combobox.GetItems.Add('Grimy Harralander'); Combobox.GetItems.Add('Grimy Marrentill'); Combobox.GetItems.Add('Grimy Guam Leaf'); Combobox.GetItems.Add('Grimy Irit Leaf'); Combobox.GetItems.Add('Grimy Avantoe'); Combobox.GetItems.Add('Grimy Dwarf Weed'); Combobox.GetItems.Add('Grimy Lantadyme'); Combobox.GetItems.Add('Grimy Toadflax'); Combobox.GetItems.Add('Grimy Cadantine'); Combobox.GetItems.Add('Grimy Kwuarm'); Combobox.GetItems.Add('Grimy Torstol'); Combobox.GetItems.Add('Grimy Ranarr Weed'); Combobox.GetItems.Add('Grimy Snapdragon'); Combobox.SetItemIndex(5); // Default herb selection. end; with Self.FocusLossCheckbox do begin Create(tab); SetCaption('Simulate Losing Focus'); SetLeft(20); SetTop(Self.HerbSelector.GetBottom() + 20); SetHint('Enable to occasionally switch focus state and cast with slower timers.'); end; Self.CreateBankSettings(); Self.CreateAntibanManager(); Self.CreateWaspLibSettings(); Self.CreateAPISettings(); inherited; end; var DegrimeConfig: TDegrimeConfig; {$ENDIF} var DegrimeScript: TDegrimeScript; {$IFNDEF SCRIPT_CHAIN} begin {$IFDEF SCRIPT_GUI} DegrimeConfig.Run(); {$ENDIF} DegrimeScript.Run(WLSettings.MaxActions, WLSettings.MaxTime); end. {$ENDIF}