{$UNDEF SCRIPT_ID}{$DEFINE SCRIPT_ID := 'c4a8f1e2-7b3d-4a9e-8c6f-1d2e3f4a5b6c'} {$UNDEF SCRIPT_REVISION}{$DEFINE SCRIPT_REVISION := '5'} {$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/aichat.simba} {$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 SENDSESSIONSUMMARYMSG: Boolean = True; SENDHOURLYREPORTMSG: Boolean = True; PINGONTERMINATED: Boolean = True; STOPATCOUNT: Integer = 0; ENABLEPLAYERDETECTION: Boolean = False; PLAYERDETECTIONDELAY: Integer = 0; ENABLEPREHOVER: Boolean = True; ENABLESLOWMODE: Boolean = False; USESOUTHSIDE: Boolean = False; USENORTHSIDE: Boolean = False; const ASH_PILE_COUNT = 14; ASH_RESPAWN_AVOID_MS = 26000; ASH_XP_IDLE_MS = 2800; ASH_FLAG_GONE_MS = 5000; ASH_HOVER_ATTEMPTS = 3; ASH_PREHOVER_CHANCE = 60; ASH_PREHOVER_MAX_DIST = 70; ASH_DEPLETED_CONFIRM = 2; ASH_SOUTH_LO = 0; ASH_SOUTH_HI = 6; ASH_SLOW_LO = 0; ASH_SLOW_HI = 3; ASH_PILE_PRIMARY: TCTS2Color = [9803424, 19, 0.12, 0.15]; ASH_PILE_SECONDARY: TCTS2Color = [10461096, 19, 0.12, 0.19]; ASH_PILE_CLUSTER_DIST = 15; ASH_PILE_COORDS: array[0..13] of TPoint = [ [11028, 35334], [11060, 35354], [11080, 35338], [11104, 35362], [11144,35342], [11188, 35314], [11176,35266], [11184,35214], [11152,35166], [11120,35134], [11080,35166], [11048,35198], [11044,35146], [11176,35274] ]; type EAshState = ( VA_LOGIN, VA_DROP_SODA, VA_MINE, VA_HOP, VA_END ); TAshPile = record Coord: TPoint; LastMinedAt: UInt64; Obj: TRSObjectV2; end; TVolcanicAshMiner = record(TBaseScript) State: EAshState; Piles: array[0..13] of TAshPile; CurrentIndex: Int32; StartAshCount: Int32; AshGained: Int32; LastReportTime: UInt64; LastHourlyReportTime: Int64; NextWorldHopTime: UInt64; WorldHopsCompleted: Int32; LastWorldHopTime: Int64; PlayerDetectedTime: UInt64; PlayerDetectionHops: Int32; PlayerNearby: Boolean; ActiveRunTime: TStopwatch; end; var VolcanicAshMiner: TVolcanicAshMiner; procedure TAntiban.Setup(); override; begin Self.Skills := [ERSSkill.MINING, ERSSkill.TOTAL]; Self.MinZoom := 10; Self.MaxZoom := 20; inherited; if OVERRIDEBREAKS then begin Antiban.Breaks := []; Antiban.AddBreak( CUSTOMBREAKINTERVAL * ONE_MINUTE, CUSTOMBREAKDURATION * ONE_MINUTE, 0.15, 1.0 ); end; end; function TBaseScript.DoAntiban(checkBreaks: Boolean = True; checkSleeps: Boolean = True): Boolean; override; begin 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 Login.LoginPlayer(); 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 TVolcanicAshMiner.GetAshGained(): Int32; var Current: Int32; begin Result := Self.AshGained; if not Inventory.Open() then Exit; Current := Inventory.Items.CountStack('Volcanic ash'); if Current < 0 then Exit; Result := Max(0, Current - Self.StartAshCount); Self.AshGained := Result; end; function TVolcanicAshMiner.ReachedStopCount(): Boolean; begin Result := (STOPATCOUNT > 0) and (Self.GetAshGained() >= STOPATCOUNT); end; procedure TVolcanicAshMiner.SetupChunks(); begin Map.SetupChunksEx([[56, 61, 60, 57]], [0]); WriteLn('[INIT] Loaded Fossil Island chunks Box'); end; procedure TVolcanicAshMiner.SetupPiles(); var I: Int32; begin for I := 0 to High(Self.Piles) do begin Self.Piles[I].Coord := ASH_PILE_COORDS[I]; Self.Piles[I].LastMinedAt := 0; with Self.Piles[I].Obj do begin Walker := @Map.Walker; SetupEx([1, 1, 4], [[Self.Piles[I].Coord.X, Self.Piles[I].Coord.Y]]); SetupUpText(['Mine A', 'e Ash', 'Empty']); Finder.Colors := []; Finder.ColorClusters += [ ASH_PILE_PRIMARY, ASH_PILE_SECONDARY, ASH_PILE_CLUSTER_DIST ]; Finder.ClusterDistance := 4; Finder.Erode := 1; Finder.Grow := 2; Finder.MinShortSide := 10; Finder.MinLongSide := 10; end; end; end; function TVolcanicAshMiner.PileConfigured(Index: Int32): Boolean; begin Result := (Index >= 0) and (Index <= High(Self.Piles)) and (Self.Piles[Index].Coord.X <> 0) and (Self.Piles[Index].Coord.Y <> 0); end; function TVolcanicAshMiner.PileOnCooldown(Index: Int32): Boolean; begin if Self.Piles[Index].LastMinedAt = 0 then Exit(False); Result := (GetTickCount() - Self.Piles[Index].LastMinedAt) < ASH_RESPAWN_AVOID_MS; end; function TVolcanicAshMiner.IsMineableUpText(): Boolean; begin if MainScreen.IsUpText('Empty') then Exit(False); Result := MainScreen.IsUpText('Mine A') or MainScreen.IsUpText('e Ash') or MainScreen.IsUpText('Ash pile'); end; function TVolcanicAshMiner.IsEmptyUpText(): Boolean; begin Result := MainScreen.IsUpText('Empty'); end; function TVolcanicAshMiner.PileInSelectedSide(Index: Int32): Boolean; var SideFilter: Boolean; begin if ENABLESLOWMODE then Exit(InRange(Index, ASH_SLOW_LO, ASH_SLOW_HI)); SideFilter := USESOUTHSIDE or USENORTHSIDE; if not SideFilter then Exit(True); Result := False; if USESOUTHSIDE and InRange(Index, ASH_SOUTH_LO, ASH_SOUTH_HI) then Result := True; if USENORTHSIDE and (Index > ASH_SOUTH_HI) and (Index <= High(Self.Piles)) then Result := True; end; function TVolcanicAshMiner.PickNextPileIndex(ExcludeIndex: Int32 = -1): Int32; var Me: TPoint; I: Int32; Dist, BestDist: Double; Candidates: TIntegerArray; UseRandom: Boolean; begin Result := -1; BestDist := 999999.0; Candidates := []; Me := Map.Walker.Position(); UseRandom := (not ENABLESLOWMODE) and (not USESOUTHSIDE) and (not USENORTHSIDE); for I := 0 to High(Self.Piles) do begin if I = ExcludeIndex then Continue; if not Self.PileConfigured(I) then Continue; if not Self.PileInSelectedSide(I) then Continue; if Self.PileOnCooldown(I) then Continue; if UseRandom then Candidates += I else begin Dist := Me.DistanceTo(Self.Piles[I].Coord); if Dist < BestDist then begin BestDist := Dist; Result := I; end; end; end; if UseRandom and (Length(Candidates) > 0) then Result := Candidates[Random(Length(Candidates))]; end; procedure TVolcanicAshMiner.WaitUntilStationary(); begin if Minimap.HasFlag() then Minimap.WaitFlag(); if Minimap.IsPlayerMoving() then Minimap.WaitPlayerMoving(); end; function TVolcanicAshMiner.HoverPileOnce(Index: Int32): Boolean; var ATPA: T2DPointArray; begin Result := False; if Minimap.HasFlag() then Exit; if not Self.Piles[Index].Obj.Find(ATPA) or (Length(ATPA) = 0) or (Length(ATPA[0]) = 0) then Exit; Mouse.Move(ATPA[0].RandomValue()); Wait(40, 80); Result := True; end; function TVolcanicAshMiner.WalkToPile(Index: Int32): Boolean; begin Result := False; if not Self.PileConfigured(Index) then Exit; if Map.Walker.Position().DistanceTo(Self.Piles[Index].Coord) <= 50 then begin Self.WaitUntilStationary(); Exit(True); end; Result := Map.Walker.WebWalk(Self.Piles[Index].Coord, 30, 0.15); Self.WaitUntilStationary(); end; procedure TVolcanicAshMiner.HoverNextPile(ExcludeIndex: Int32); var NextIdx, I: Int32; Me: TPoint; Dist, BestDist: Double; ATPA: T2DPointArray; begin if not ENABLEPREHOVER then Exit; if Minimap.HasFlag() then Exit; if Random(100) >= ASH_PREHOVER_CHANCE then Exit; NextIdx := -1; BestDist := 999999.0; Me := Map.Walker.Position(); for I := 0 to High(Self.Piles) do begin if I = ExcludeIndex then Continue; if not Self.PileConfigured(I) then Continue; if not Self.PileInSelectedSide(I) then Continue; if Self.PileOnCooldown(I) then Continue; Dist := Me.DistanceTo(Self.Piles[I].Coord); if Dist > ASH_PREHOVER_MAX_DIST then Continue; if Dist < BestDist then begin BestDist := Dist; NextIdx := I; end; end; if NextIdx < 0 then Exit; if not Self.Piles[NextIdx].Obj.Find(ATPA) or (Length(ATPA) = 0) or (Length(ATPA[0]) = 0) then begin Exit; end; Mouse.Move(ATPA[0].RandomValue()); if Self.IsEmptyUpText() then begin Self.Piles[NextIdx].LastMinedAt := GetTickCount(); end; end; function TVolcanicAshMiner.PileHasAshColor(Index: Int32): Boolean; var ATPA: T2DPointArray; begin Result := False; if not Self.PileConfigured(Index) then Exit; Result := Self.Piles[Index].Obj.Find(ATPA); end; function TVolcanicAshMiner.WaitMiningXP(CurrentIndex: Int32): Boolean; var Idle, WaitXP: TCountDown; Started, HoveredNext: Boolean; ColorMissStreak: Int32; begin Result := False; Started := False; HoveredNext := False; ColorMissStreak := 0; XPBar.EarnedXP(); if Minimap.HasFlag() or Minimap.IsPlayerMoving() then begin Self.WaitUntilStationary(); XPBar.EarnedXP(); end; WaitXP.Init(ASH_FLAG_GONE_MS); Idle.Init(ASH_XP_IDLE_MS); repeat if Inventory.IsFull() then Exit(True); if Self.ReachedStopCount() then Exit(True); if Minimap.HasFlag() or Minimap.IsPlayerMoving() then begin Self.WaitUntilStationary(); XPBar.EarnedXP(); WaitXP.Restart(); Continue; end; if XPBar.EarnedXP() then begin Started := True; Result := True; Idle.Restart(); WL.Activity.Restart(); ColorMissStreak := 0; if (not HoveredNext) and (not Minimap.HasFlag()) and (not Minimap.IsPlayerMoving()) then begin Self.HoverNextPile(CurrentIndex); HoveredNext := True; end; end; if not Started then begin if WaitXP.IsFinished() then Exit(False); end else begin if not ENABLESLOWMODE then begin if not Self.PileHasAshColor(CurrentIndex) then begin Inc(ColorMissStreak); if ColorMissStreak >= ASH_DEPLETED_CONFIRM then Exit(True); end else ColorMissStreak := 0; end; if Idle.IsFinished() then Exit(True); end; Wait(50, 100); until False; end; function TVolcanicAshMiner.MinePile(Index: Int32): Boolean; var Attempts: Int32; begin Result := False; if not Self.PileConfigured(Index) then Exit; Inventory.Open(); Self.CurrentIndex := Index; WriteLn('[MINE] Targeting pile ' + IntToStr(Index) + ' @ ' + ToStr(Self.Piles[Index].Coord)); for Attempts := 1 to ASH_HOVER_ATTEMPTS do begin Self.WaitUntilStationary(); if not Self.HoverPileOnce(Index) then begin if Self.IsEmptyUpText() then begin WriteLn('[MINE] Empty ash pile — skipping'); Self.Piles[Index].LastMinedAt := GetTickCount(); Exit(False); end; Self.WalkToPile(Index); Self.WaitUntilStationary(); if not Self.HoverPileOnce(Index) then begin WriteLn('[MINE] Failed to hover pile ' + IntToStr(Index) + ' (' + IntToStr(Attempts) + '/' + IntToStr(ASH_HOVER_ATTEMPTS) + ')'); if Attempts >= ASH_HOVER_ATTEMPTS then begin WriteLn('[MINE] Giving up on pile ' + IntToStr(Index) + ' — moving on'); Self.Piles[Index].LastMinedAt := GetTickCount(); Exit(False); end; Continue; end; end; if Self.IsEmptyUpText() then begin WriteLn('[MINE] Empty ash pile — skipping'); Self.Piles[Index].LastMinedAt := GetTickCount(); Exit(False); end; if not Self.IsMineableUpText() then begin if Attempts >= ASH_HOVER_ATTEMPTS then begin WriteLn('[MINE] Giving up on pile ' + IntToStr(Index) + ' — moving on'); Self.Piles[Index].LastMinedAt := GetTickCount(); Exit(False); end; Continue; end; Mouse.Click(MOUSE_LEFT); if Self.WaitMiningXP(Index) then begin Result := True; Self.Piles[Index].LastMinedAt := GetTickCount(); Self.CurrentIndex := Index; Self.GetAshGained(); Exit; end; end; WriteLn('[MINE] Giving up on pile ' + IntToStr(Index) + ' — moving on'); Self.Piles[Index].LastMinedAt := GetTickCount(); end; procedure TVolcanicAshMiner.DropSodaAsh(); begin WriteLn('[INV] Dropping Soda ash'); Inventory.ShiftDrop(['Soda ash'], DROP_PATTERN_SNAKE); end; procedure TVolcanicAshMiner.TerminateFullInventory(); begin Logout.ClickLogout(); TerminateScript('Inventory full (no Soda ash to drop)'); end; procedure TVolcanicAshMiner.TerminateStopCount(); begin WriteLn('[STOP] Reached Volcanic ash target: ' + IntToStr(STOPATCOUNT)); Logout.ClickLogout(); TerminateScript('Reached volcanic ash stop count'); end; function TVolcanicAshMiner.GetRandomWorldHopTime(): UInt64; var BaseInterval: UInt64; begin BaseInterval := WORLDHOPINTERVAL * 60000; Result := BaseInterval + Round(BaseInterval * (Random(-10, 10) / 100.0)); end; procedure TVolcanicAshMiner.SetupWorldHopping(); begin if not ENABLEWORLDHOPPING then Exit; if Length(Login.GetPlayer().Worlds) < 2 then begin WriteLn('[ERROR] Need at least 2 worlds configured for world hopping!'); Logout.ClickLogout(); TerminateScript('Need 2+ worlds for hopping'); end; Self.NextWorldHopTime := GetTickCount() + Self.GetRandomWorldHopTime(); end; function TVolcanicAshMiner.ShouldHopWorld(): Boolean; begin if not ENABLEWORLDHOPPING then Exit(False); if GetTickCount() < Self.NextWorldHopTime then Exit(False); Result := True; end; procedure TVolcanicAshMiner.DoWorldHop(); var CurrentWorld, TargetWorld, I: Int32; PlayerWorlds, AvailableWorlds: TIntegerArray; begin if not ENABLEWORLDHOPPING then Exit; WriteLn('[WORLDHOP] Time to hop worlds!'); PlayerWorlds := Login.GetPlayer().Worlds; if not Logout.Open() then begin Self.NextWorldHopTime := GetTickCount() + 120000; Exit; end; if not Logout.IsWorldSwitcherOpen() then Logout.GetButton(ERSLogoutButton.WORLD_SWITCHER).Click(MOUSE_LEFT); WaitUntil((CurrentWorld := WorldHopper.GetCurrentWorld()) <> 0, 65, 20000); AvailableWorlds := []; for I := 0 to High(PlayerWorlds) do if PlayerWorlds[I] <> CurrentWorld then AvailableWorlds += PlayerWorlds[I]; if Length(AvailableWorlds) = 0 then begin Exit; end; TargetWorld := AvailableWorlds[Random(Length(AvailableWorlds))]; if WorldHopper.Hop([TargetWorld]) then begin Inc(Self.WorldHopsCompleted); Self.LastWorldHopTime := GetTickCount(); Self.NextWorldHopTime := GetTickCount() + Self.GetRandomWorldHopTime(); end else Self.NextWorldHopTime := GetTickCount() + 120000; end; function TVolcanicAshMiner.IsPlayerNearby(): Boolean; var PlayerDots: TPointArray; begin PlayerDots := Minimap.GetDots(ERSMinimapDot.PLAYER); Result := Length(PlayerDots) > 0; end; function TVolcanicAshMiner.ShouldHopForPlayer(): Boolean; var NowTime: UInt64; begin Result := False; if not ENABLEPLAYERDETECTION then Exit; NowTime := GetTickCount(); if Self.IsPlayerNearby() then begin if not Self.PlayerNearby then begin Self.PlayerNearby := True; Self.PlayerDetectedTime := NowTime; WriteLn('[PLAYER_DETECT] Player spotted on minimap!'); end; if PLAYERDETECTIONDELAY = 0 then Result := True else if (NowTime - Self.PlayerDetectedTime) >= UInt64(PLAYERDETECTIONDELAY * 1000) then Result := True; if Result then WriteLn('[PLAYER_DETECT] Timer met (' + IntToStr(PLAYERDETECTIONDELAY) + 's), hopping worlds!'); end else begin if Self.PlayerNearby then begin Self.PlayerNearby := False; Self.PlayerDetectedTime := 0; WriteLn('[PLAYER_DETECT] Player left, timer reset.'); end; end; end; procedure TVolcanicAshMiner.DoPlayerDetectionHop(); var CurrentWorld, TargetWorld, I: Int32; AvailableWorlds, OriginalWorlds: TIntegerArray; begin WriteLn('[PLAYER_DETECT] Logging out and hopping worlds...'); OriginalWorlds := Login.GetPlayer().Worlds; if Length(OriginalWorlds) < 2 then begin WriteLn('[PLAYER_DETECT] Need at least 2 worlds configured! Disabling player detection.'); ENABLEPLAYERDETECTION := False; Exit; end; if not Logout.Open() then begin WriteLn('[PLAYER_DETECT] Failed to open logout menu'); Exit; end; if not Logout.IsWorldSwitcherOpen() then Logout.GetButton(ERSLogoutButton.WORLD_SWITCHER).Click(MOUSE_LEFT); WaitUntil((CurrentWorld := WorldHopper.GetCurrentWorld()) <> 0, 65, 20000); AvailableWorlds := []; for I := 0 to High(OriginalWorlds) do if OriginalWorlds[I] <> CurrentWorld then AvailableWorlds += OriginalWorlds[I]; if Length(AvailableWorlds) = 0 then begin WriteLn('[PLAYER_DETECT] No other worlds available!'); Exit; end; TargetWorld := AvailableWorlds[Random(Length(AvailableWorlds))]; WriteLn('[PLAYER_DETECT] Target world: ' + ToStr(TargetWorld)); Logout.ClickLogout(); WaitUntil(not RSClient.IsLoggedIn(), 300, 10000); Login.Players[Login.PlayerIndex].Worlds := [TargetWorld]; Login.LoginPlayer(); Login.Players[Login.PlayerIndex].Worlds := OriginalWorlds; if not RSClient.IsLoggedIn() then WaitUntil(RSClient.IsLoggedIn(), 500, 30000); if RSClient.IsLoggedIn() then begin WriteLn('[PLAYER_DETECT] Logged back in on world ' + ToStr(TargetWorld)); Inc(Self.PlayerDetectionHops); Self.PlayerNearby := False; Self.PlayerDetectedTime := 0; end; end; procedure TVolcanicAshMiner.Report(); var Runtime: UInt64; AshPerHour: Int32; begin Runtime := GetTimeRunning(); Self.GetAshGained(); if Runtime > 0 then AshPerHour := Round((Self.AshGained * 3600000) / Runtime) else AshPerHour := 0; WriteLn('||===============================||'); WriteLn('|| B.A.S.H. Volcanic Ash Miner ||'); WriteLn('||===============================||'); WriteLn(PadR('|| Runtime: ' + SRL.MsToTime(Runtime, Time_Short), 33, ' ') + '||'); WriteLn(PadR('|| Volcanic Ash: ' + IntToStr(Self.AshGained), 33, ' ') + '||'); WriteLn(PadR('|| Ash/Hr: ' + IntToStr(AshPerHour), 33, ' ') + '||'); if STOPATCOUNT > 0 then WriteLn(PadR('|| Stop at: ' + IntToStr(STOPATCOUNT), 33, ' ') + '||'); if ENABLEWORLDHOPPING then WriteLn(PadR('|| World hops: ' + IntToStr(Self.WorldHopsCompleted), 33, ' ') + '||'); if ENABLEPLAYERDETECTION then WriteLn(PadR('|| Detect hops: ' + IntToStr(Self.PlayerDetectionHops), 33, ' ') + '||'); WriteLn(' ', Antiban.BreakScheduleText()); WriteLn(' ', Antiban.SleepScheduleText()); WriteLn('||===============================||'); end; procedure TVolcanicAshMiner.SendHourlyReport(); var EmbedIdx: Int32; Runtime: UInt64; AshPerHour: Int32; Description: String; begin if not (SENDHOURLYREPORTMSG and ENABLEWEBHOOKS) then Exit; Runtime := GetTimeRunning(); Self.GetAshGained(); if Runtime > 0 then AshPerHour := Round((Self.AshGained * 3600000) / Runtime) else AshPerHour := 0; try Discord.Webhook.Content := '**Hourly Progress Report** :chart_with_upwards_trend:'; EmbedIdx := Discord.Webhook.AddEmbed(); Discord.Webhook.Embeds[EmbedIdx].Title := 'BASH Scripts Volcanic Ash Miner - Hourly Report'; Discord.Webhook.Embeds[EmbedIdx].Color := $808080; Description := 'Runtime: ' + SRL.MsToTime(Runtime, Time_Short) + LineEnding + 'Volcanic Ash: ' + FormatRoundedNumber(Self.AshGained) + LineEnding + 'Ash/Hr: ' + FormatRoundedNumber(AshPerHour); if ENABLEPLAYERDETECTION then Description += LineEnding + 'Detection Hops: ' + IntToStr(Self.PlayerDetectionHops); if ENABLEWORLDHOPPING then Description += LineEnding + 'World Hops: ' + IntToStr(Self.WorldHopsCompleted); Discord.Webhook.Embeds[EmbedIdx].Description := Description; if Discord.SendScreenshot(False) then WriteLn('[Discord] Hourly report sent!') else WriteLn('[Discord] Failed to send hourly report: ' + Discord.LastError); except WriteLn('[Discord] Error sending hourly report: ' + GetExceptionMessage); end; end; procedure TVolcanicAshMiner.SendSessionSummary(); var EmbedIdx: Int32; Runtime: UInt64; AshPerHour: Int32; Description: String; begin if not (SENDSESSIONSUMMARYMSG and ENABLEWEBHOOKS) then Exit; Runtime := GetTimeRunning(); Self.GetAshGained(); if Runtime > 0 then AshPerHour := Round((Self.AshGained * 3600000) / Runtime) else AshPerHour := 0; try Discord.Webhook.Content := '**Session Complete!** :checkered_flag:'; EmbedIdx := Discord.Webhook.AddEmbed(); Discord.Webhook.Embeds[EmbedIdx].Title := 'BASH Scripts Volcanic Ash Miner - Session Summary'; Discord.Webhook.Embeds[EmbedIdx].Color := $0000FF; Description := 'Runtime: ' + SRL.MsToTime(Runtime, Time_Short) + LineEnding + 'Volcanic Ash: ' + FormatRoundedNumber(Self.AshGained) + LineEnding + 'Ash/Hr: ' + FormatRoundedNumber(AshPerHour); if ENABLEWORLDHOPPING then Description += LineEnding + 'World Hops: ' + IntToStr(Self.WorldHopsCompleted); if ENABLEPLAYERDETECTION then Description += LineEnding + 'Detection Hops: ' + IntToStr(Self.PlayerDetectionHops); Discord.Webhook.Embeds[EmbedIdx].Description := Description; if Discord.SendScreenshot(False) then WriteLn('[Discord] Session summary sent!') else WriteLn('[Discord] Failed to send session summary: ' + Discord.LastError); except WriteLn('[Discord] Error sending session summary: ' + GetExceptionMessage); end; end; procedure TVolcanicAshMiner.SendTerminationNotification(); begin WriteLn('Script has terminated, sending session summary'); Self.SendSessionSummary(); end; procedure TVolcanicAshMiner.CheckHourlyReport(); var CurrentTime: Int64; begin CurrentTime := GetTimeRunning(); if (CurrentTime - Self.LastHourlyReportTime) >= 3600000 then begin Self.SendHourlyReport(); Self.LastHourlyReportTime := CurrentTime; end; end; procedure TVolcanicAshMiner.Init(MaxActions: UInt32; MaxTime: UInt64); override; var Configured: Int32; I: Int32; begin inherited; if AIChatbot.Enabled then begin WriteLn('[AI Chatbot] Enabled - initializing'); AIChatbot.Init(); end else WriteLn('[AI Chatbot] Disabled'); if not RSClient.IsLoggedIn() then Login.LoginPlayer(); Self.SetupChunks(); Objects.Setup(Map.Objects(), @Map.Walker); Self.SetupPiles(); Configured := 0; for I := 0 to High(Self.Piles) do if Self.PileConfigured(I) then Inc(Configured); Inventory.Open(); Self.StartAshCount := Inventory.Items.CountStack('Volcanic ash'); if Self.StartAshCount < 0 then Self.StartAshCount := 0; Self.AshGained := 0; Self.CurrentIndex := 0; Self.LastReportTime := 0; Self.LastHourlyReportTime := 0; Self.WorldHopsCompleted := 0; Self.PlayerDetectionHops := 0; Self.PlayerNearby := False; Self.PlayerDetectedTime := 0; Self.ActiveRunTime.Start(); ItemFinder.Similarity := 0.9999999; Self.SetupWorldHopping(); if ENABLESLOWMODE then WriteLn('[INIT] Slow mode: piles 0-3, leave on XP idle (no despawn check).'); if ENABLEPLAYERDETECTION then WriteLn('[PLAYER_DETECT] Enabled with ' + IntToStr(PLAYERDETECTIONDELAY) + 's delay.') else WriteLn('[PLAYER_DETECT] Player detection is disabled.'); if SENDSESSIONSUMMARYMSG and ENABLEWEBHOOKS then AddOnTerminate(@Self.SendTerminationNotification); WL.Activity.Init(5 * ONE_MINUTE); WL.Activity.Restart(); end; function TVolcanicAshMiner.GetState(): EAshState; begin if WL.Activity.IsFinished() then begin WriteLn('[ACTIVITY] No activity detected in 5 minutes! Shutting down.'); Logout.ClickLogout(); TerminateScript('No activity detected in 5 minutes'); end; if not RSClient.IsLoggedIn() then Exit(EAshState.VA_LOGIN); if Self.ShouldStop() then Exit(EAshState.VA_END); if Self.ReachedStopCount() then begin Self.TerminateStopCount(); Exit(EAshState.VA_END); end; if Inventory.IsFull() then begin if Inventory.ContainsItem('Soda ash') then Exit(EAshState.VA_DROP_SODA); Self.TerminateFullInventory(); Exit(EAshState.VA_END); end; if Self.ShouldHopForPlayer() then Exit(EAshState.VA_HOP); Exit(EAshState.VA_MINE); end; procedure TVolcanicAshMiner.DoMine(); var Idx: Int32; begin Idx := Self.PickNextPileIndex(); if Idx < 0 then begin WriteLn('[MINE] No eligible piles'); Wait(400, 700); Exit; end; Self.MinePile(Idx); end; procedure TVolcanicAshMiner.Run(MaxActions: UInt32; MaxTime: UInt64); begin Self.Init(MaxActions, MaxTime); repeat Self.CheckHourlyReport(); Self.DoAntiban(True, True); if Self.ShouldHopWorld() then begin Self.DoWorldHop(); end; Self.State := Self.GetState(); case Self.State of EAshState.VA_LOGIN: if not Login.LoginPlayer() then Break; EAshState.VA_DROP_SODA: begin Self.DropSodaAsh(); if Inventory.IsFull() and (not Inventory.ContainsItem('Soda ash')) then Self.TerminateFullInventory(); end; EAshState.VA_HOP: begin Self.DoPlayerDetectionHop(); end; EAshState.VA_MINE: Self.DoMine(); EAshState.VA_END: Break; end; if (GetTickCount() - Self.LastReportTime) >= 10000 then begin Self.Report(); Self.LastReportTime := GetTickCount(); end; until Self.ShouldStop(); Self.Report(); end; {$IFDEF SCRIPT_GUI} type TConfig = record(TBASHPremiumGUI) StopAtCountInput: TLabeledEdit; SouthSideCheckBox, NorthSideCheckBox, SlowModeCheckBox: TLabeledCheckBox; EnablePlayerDetectionCheckBox, EnablePreHoverCheckBox: TLabeledCheckBox; PlayerDetectionDelayCombo: TLabeledCombobox; PingOnTerminatedCheckBox: TLabeledCheckBox; SavedStopAtCount, SavedPlayerDetectionDelayIdx: Integer; SavedUseSouthSide, SavedUseNorthSide, SavedEnableSlowMode: Boolean; SavedEnablePlayerDetection, SavedEnablePreHover, SavedPingOnTerminated: Boolean; end; TScriptGUI = TConfig; procedure TConfig.InitGUI(); override; begin Self.BrandTitle := 'Volcanic Ash'; Self.WindowTitle := 'BASH Scripts Volcanic Ash Miner'; Self.WebhookTestPrefix := 'Test message from BASH Scripts Volcanic Ash Miner'; Self.HasFarm := False; Self.HasAIChat := True; Self.HasWorldHopping := True; Self.HasMaxActions := True; Self.HasStopAtLevel := False; Self.LegacyAntibanSection := ' Volcanic Ash Miner Antiban'; end; procedure TConfig.SetupAIChat(); override; begin inherited; AIChatbot.SetScriptDefaults('mining volcanic ash'); end; procedure TConfig.MigrateLegacyWebhook(); var url, en: String; begin url := ReadINI(Self.Username + BASH_GUI_WEBHOOK_SECTION, 'WebhookURL', BASH_GUI_SETTINGS_FILE); if url = '' then begin url := ReadINI(Self.Username + ' Webhook Settings', 'WebhookURL', BASH_GUI_SETTINGS_FILE); if url <> '' then WriteINI(Self.Username + BASH_GUI_WEBHOOK_SECTION, 'WebhookURL', url, BASH_GUI_SETTINGS_FILE); end; en := ReadINI(Self.Username + BASH_GUI_WEBHOOK_SECTION, 'EnableWebhooks', BASH_GUI_SETTINGS_FILE); if en = '' then begin en := ReadINI(Self.Username + ' Webhook Settings', 'EnableWebhooks', BASH_GUI_SETTINGS_FILE); if en <> '' then WriteINI(Self.Username + BASH_GUI_WEBHOOK_SECTION, 'EnableWebhooks', en, BASH_GUI_SETTINGS_FILE); end; end; procedure TConfig.LoadActionSettings(); override; var hop, hopInt: String; delay: Integer; begin Self.MigrateLegacyWebhook(); Self.SavedStopAtCount := StrToIntDef(ReadINI(Self.Username + ' Volcanic Ash Miner Settings', 'StopAtCount', BASH_GUI_SETTINGS_FILE), 0); Self.SavedUseSouthSide := StrToBoolDef(ReadINI(Self.Username + ' Volcanic Ash Miner Settings', 'UseSouthSide', BASH_GUI_SETTINGS_FILE), False); Self.SavedUseNorthSide := StrToBoolDef(ReadINI(Self.Username + ' Volcanic Ash Miner Settings', 'UseNorthSide', BASH_GUI_SETTINGS_FILE), False); Self.SavedEnableSlowMode := StrToBoolDef(ReadINI(Self.Username + ' Volcanic Ash Miner Settings', 'EnableSlowMode', BASH_GUI_SETTINGS_FILE), False); Self.SavedEnablePlayerDetection := StrToBoolDef(ReadINI(Self.Username + ' Volcanic Ash Miner Settings', 'EnablePlayerDetection', BASH_GUI_SETTINGS_FILE), False); delay := StrToIntDef(ReadINI(Self.Username + ' Volcanic Ash Miner Settings', 'PlayerDetectionDelay', BASH_GUI_SETTINGS_FILE), 0); case delay of 10: Self.SavedPlayerDetectionDelayIdx := 1; 20: Self.SavedPlayerDetectionDelayIdx := 2; 30: Self.SavedPlayerDetectionDelayIdx := 3; else Self.SavedPlayerDetectionDelayIdx := 0; end; Self.SavedEnablePreHover := StrToBoolDef(ReadINI(Self.Username + ' Volcanic Ash Miner Settings', 'EnablePreHover', BASH_GUI_SETTINGS_FILE), True); Self.SavedPingOnTerminated := StrToBoolDef(ReadINI(Self.Username + BASH_GUI_WEBHOOK_SECTION, 'PingOnTerminated', BASH_GUI_SETTINGS_FILE), True); if ReadINI(Self.Username + BASH_GUI_WEBHOOK_SECTION, 'PingOnTerminated', BASH_GUI_SETTINGS_FILE) = '' then Self.SavedPingOnTerminated := StrToBoolDef(ReadINI(Self.Username + ' Webhook Settings', 'PingOnTerminated', BASH_GUI_SETTINGS_FILE), True); hop := ReadINI(Self.Username + ' Volcanic Ash Miner Settings', 'EnableWorldHopping', BASH_GUI_SETTINGS_FILE); hopInt := ReadINI(Self.Username + ' Volcanic Ash Miner Settings', 'WorldHopInterval', BASH_GUI_SETTINGS_FILE); if (Self.ReadAntibanINI('EnableWorldHopping', '') = '') and (hop <> '') then Self.WriteAntibanINI('EnableWorldHopping', hop); if (Self.ReadAntibanINI('WorldHopInterval', '') = '') and (hopInt <> '') then Self.WriteAntibanINI('WorldHopInterval', hopInt); end; procedure TConfig.PlayerDetectionChanged({$H-}sender: TObject){$H+}; begin if Assigned(Self.PlayerDetectionDelayCombo) then Self.PlayerDetectionDelayCombo.SetVisible(Self.EnablePlayerDetectionCheckBox.IsChecked()); end; procedure TConfig.BuildActionSection(); override; begin Self.AddHeader(Self.ActionSection, 'Action', 'Volcanic ash stop target, mining area, and player detection.'); Self.AddEdit(Self.ActionSection, Self.StopAtCountInput, 'le_va_stop_count', 'Stop at Volcanic ash (0 = never)', IntToStr(Self.SavedStopAtCount), 24, 110, BASH_GUI_EDIT_W); Self.StopAtCountInput.Edit.SetOnKeyPress(@Self.StopAtCountInput.Edit.NumberField); Self.StopAtCountInput.SetHint('Logout and stop once this many Volcanic ash have been gained this session. 0 = unlimited.'); Self.MakeLabel(Self.ActionSection, 'Mining area', 24, 168, 11, BASH_GUI_MUTED, True); Self.AddCheck(Self.ActionSection, Self.SouthSideCheckBox, 'cb_va_south', 'South side (Reccomended)', 24, 190, Self.SavedUseSouthSide); Self.SouthSideCheckBox.SetHint('Only mine the south-side ash piles.'); Self.AddCheck(Self.ActionSection, Self.NorthSideCheckBox, 'cb_va_north', 'North side', BASH_GUI_COL2, 190, Self.SavedUseNorthSide); Self.NorthSideCheckBox.SetHint('Only mine the north-side ash piles. Uncheck both to use all piles at random.'); Self.AddCheck(Self.ActionSection, Self.SlowModeCheckBox, 'cb_va_slow', 'Slow mode', 24, 218, Self.SavedEnableSlowMode); Self.SlowModeCheckBox.SetHint('Only mine piles 0-3. Leave a pile when XP stops instead of watching it despawn.'); Self.MakeLabel(Self.ActionSection, 'Player detection', 24, 276, 11, BASH_GUI_MUTED, True); Self.AddCheck(Self.ActionSection, Self.EnablePlayerDetectionCheckBox, 'cb_va_player_detection', 'Hop on player detection', 24, 299, Self.SavedEnablePlayerDetection); Self.EnablePlayerDetectionCheckBox.SetHint('Hop worlds when another player is detected nearby on the minimap.'); Self.EnablePlayerDetectionCheckBox.CheckBox.SetOnChange(@Self.PlayerDetectionChanged); with Self.PlayerDetectionDelayCombo do begin Create(Self.ActionSection); SetCaption('Detection delay'); SetLeft(TControl.AdjustToDPI(BASH_GUI_COL2)); SetTop(TControl.AdjustToDPI(294)); SetWidth(TControl.AdjustToDPI(BASH_GUI_EDIT_W)); Clear(); AddItem('Instant'); AddItem('10 seconds'); AddItem('20 seconds'); AddItem('30 seconds'); SetItemIndex(Self.SavedPlayerDetectionDelayIdx); SetStyle(csDropDownList); end; Self.PlayerDetectionChanged(nil); end; procedure TConfig.BuildAntibanExtras(var nextY: Int32); override; begin Self.AddCheck(Self.AntibanSection, Self.EnablePreHoverCheckBox, 'cb_va_prehover', 'Pre-Hover next pile', 24, nextY, Self.SavedEnablePreHover); Self.EnablePreHoverCheckBox.SetHint('When enabled, sometimes pre-hovers the next visible ash pile while mining. Off = never pre-hover.'); nextY += 28; end; procedure TConfig.WebhooksCheckboxChanged({$H-}sender: TObject); {$H+} override; var shown: Boolean; begin inherited; shown := Self.EnableWebhooksCheckBox.IsChecked(); if Assigned(Self.PingOnTerminatedCheckBox) then Self.PingOnTerminatedCheckBox.SetVisible(shown); end; procedure TConfig.BuildAccountsExtras(); override; begin Self.AddCheck(Self.AccountsSection, Self.PingOnTerminatedCheckBox, 'cb_va_ping_term', 'Ping on script termination', BASH_GUI_COL2, 350, Self.SavedPingOnTerminated); Self.EnableWebhooksCheckBox.CheckBox.SetOnChange(@Self.WebhooksCheckboxChanged); Self.WebhooksCheckboxChanged(nil); end; procedure TConfig.ApplyActionSettings(); override; begin STOPATCOUNT := StrToIntDef(Self.StopAtCountInput.GetText(), 0); USESOUTHSIDE := Self.SouthSideCheckBox.IsChecked(); USENORTHSIDE := Self.NorthSideCheckBox.IsChecked(); ENABLESLOWMODE := Self.SlowModeCheckBox.IsChecked(); ENABLEPLAYERDETECTION := Self.EnablePlayerDetectionCheckBox.IsChecked(); ENABLEPREHOVER := Assigned(Self.EnablePreHoverCheckBox) and Self.EnablePreHoverCheckBox.IsChecked(); SENDHOURLYREPORTMSG := True; SENDSESSIONSUMMARYMSG := True; if Assigned(Self.PingOnTerminatedCheckBox) then PINGONTERMINATED := Self.PingOnTerminatedCheckBox.IsChecked(); if Assigned(Self.PlayerDetectionDelayCombo) then begin case Self.PlayerDetectionDelayCombo.GetItemIndex() of 1: PLAYERDETECTIONDELAY := 10; 2: PLAYERDETECTIONDELAY := 20; 3: PLAYERDETECTIONDELAY := 30; else PLAYERDETECTIONDELAY := 0; end; end; WriteINI(Self.Username + ' Volcanic Ash Miner Settings', 'StopAtCount', ToStr(STOPATCOUNT), BASH_GUI_SETTINGS_FILE); WriteINI(Self.Username + ' Volcanic Ash Miner Settings', 'UseSouthSide', BoolToStr(USESOUTHSIDE, 'true', 'false'), BASH_GUI_SETTINGS_FILE); WriteINI(Self.Username + ' Volcanic Ash Miner Settings', 'UseNorthSide', BoolToStr(USENORTHSIDE, 'true', 'false'), BASH_GUI_SETTINGS_FILE); WriteINI(Self.Username + ' Volcanic Ash Miner Settings', 'EnableSlowMode', BoolToStr(ENABLESLOWMODE, 'true', 'false'), BASH_GUI_SETTINGS_FILE); WriteINI(Self.Username + ' Volcanic Ash Miner Settings', 'EnableWorldHopping', BoolToStr(ENABLEWORLDHOPPING, 'true', 'false'), BASH_GUI_SETTINGS_FILE); WriteINI(Self.Username + ' Volcanic Ash Miner Settings', 'WorldHopInterval', ToStr(WORLDHOPINTERVAL), BASH_GUI_SETTINGS_FILE); WriteINI(Self.Username + ' Volcanic Ash Miner Settings', 'EnablePlayerDetection', BoolToStr(ENABLEPLAYERDETECTION, 'true', 'false'), BASH_GUI_SETTINGS_FILE); WriteINI(Self.Username + ' Volcanic Ash Miner Settings', 'PlayerDetectionDelay', IntToStr(PLAYERDETECTIONDELAY), BASH_GUI_SETTINGS_FILE); WriteINI(Self.Username + ' Volcanic Ash Miner Settings', 'EnablePreHover', BoolToStr(ENABLEPREHOVER, 'true', 'false'), BASH_GUI_SETTINGS_FILE); WriteINI(Self.Username + BASH_GUI_WEBHOOK_SECTION, 'PingOnTerminated', BoolToStr(PINGONTERMINATED, 'true', 'false'), BASH_GUI_SETTINGS_FILE); end; procedure TConfig.StartScript(Sender: TObject); override; begin if Sender = nil then; try Self._WarmBuildCache(); except end; inherited; end; {$IFDEF SCRIPT_GUI} {$I BashLib/optional/handlers/bashgui_host_scriptgui.simba} {$ENDIF} var Config: TConfig; {$ENDIF} begin Discord.Setup(); {$IFDEF SCRIPT_GUI} Sync(@Config.Run); {$ENDIF} VolcanicAshMiner.Run(WLSettings.MaxActions, WLSettings.MaxTime); end.