diff --git a/MinecraftClient/ChatBots/AutoFishing.cs b/MinecraftClient/ChatBots/AutoFishing.cs
index d92d07ab..69617b36 100644
--- a/MinecraftClient/ChatBots/AutoFishing.cs
+++ b/MinecraftClient/ChatBots/AutoFishing.cs
@@ -15,6 +15,7 @@ namespace MinecraftClient.ChatBots
{
private int fishCount = 0;
private bool inventoryEnabled;
+ private int castTimeout = 12;
private bool isFishing = false;
private Entity? fishingBobber;
@@ -25,7 +26,8 @@ namespace MinecraftClient.ChatBots
private readonly object stateLock = new();
private FishingState state = FishingState.WaitJoinGame;
- private int castTimeout = 12;
+ private int curLocationIdx = 0, moveDir = 1;
+ float nextYaw = 0, nextPitch = 0;
private enum FishingState
{
@@ -33,8 +35,9 @@ namespace MinecraftClient.ChatBots
WaitingToCast,
CastingRod,
WaitingFishingBobber,
- WaitingFishBite,
- Preparing,
+ WaitingFishToBite,
+ DurabilityCheck,
+ ChangeLocation,
Stopping,
}
@@ -47,19 +50,41 @@ namespace MinecraftClient.ChatBots
UnloadBot();
}
inventoryEnabled = GetInventoryEnabled();
+ if (!inventoryEnabled)
+ LogToConsoleTranslated("bot.autoFish.no_inv_handle");
}
- public override void AfterGameJoined()
+ private void StartFishing()
{
+ isFishing = false;
double delay = Settings.AutoFishing_FishingDelay;
LogToConsole(Translations.Get("bot.autoFish.start", delay));
lock (stateLock)
{
counter = (int)(delay * 10);
- state = FishingState.WaitingToCast;
+ state = FishingState.DurabilityCheck;
}
}
+ private void StopFishing()
+ {
+ isFishing = false;
+ lock (stateLock)
+ {
+ state = FishingState.Stopping;
+ }
+ }
+
+ public override void AfterGameJoined()
+ {
+ StartFishing();
+ }
+
+ public override void OnRespawn()
+ {
+ StartFishing();
+ }
+
public override void Update()
{
lock (stateLock)
@@ -69,7 +94,9 @@ namespace MinecraftClient.ChatBots
case FishingState.WaitJoinGame:
break;
case FishingState.WaitingToCast:
- if (--counter < 0)
+ if (AutoEat.Eating)
+ counter = (int)(Settings.AutoFishing_FishingCastDelay * 10);
+ else if (--counter < 0)
state = FishingState.CastingRod;
break;
case FishingState.CastingRod:
@@ -81,14 +108,14 @@ namespace MinecraftClient.ChatBots
if (++counter > castTimeout)
{
if (castTimeout < 6000)
- castTimeout *= 2;
+ castTimeout *= 2; // Exponential backoff
LogToConsole(GetTimestamp() + ": " + Translations.Get("bot.autoFish.cast_timeout", castTimeout / 10.0));
counter = (int)(Settings.AutoFishing_FishingCastDelay * 10);
state = FishingState.WaitingToCast;
}
break;
- case FishingState.WaitingFishBite:
+ case FishingState.WaitingFishToBite:
if (++counter > (int)(Settings.AutoFishing_FishingTimeout * 10))
{
LogToConsole(GetTimestamp() + ": " + Translations.Get("bot.autoFish.fishing_timeout"));
@@ -97,7 +124,21 @@ namespace MinecraftClient.ChatBots
state = FishingState.WaitingToCast;
}
break;
- case FishingState.Preparing:
+ case FishingState.DurabilityCheck:
+ if (--counter < 0)
+ {
+ DurabilityCheckAndMove();
+ }
+ break;
+ case FishingState.ChangeLocation:
+ if (!ClientIsMoving())
+ {
+ LookAtLocation(nextYaw, nextPitch);
+ LogToConsole(Translations.Get("bot.autoFish.update_lookat", nextYaw, nextPitch));
+
+ counter = (int)(Settings.AutoFishing_FishingCastDelay * 10);
+ state = FishingState.WaitingToCast;
+ }
break;
case FishingState.Stopping:
break;
@@ -118,7 +159,7 @@ namespace MinecraftClient.ChatBots
castTimeout = 24;
counter = 0;
- state = FishingState.WaitingFishBite;
+ state = FishingState.WaitingFishToBite;
}
}
}
@@ -161,6 +202,7 @@ namespace MinecraftClient.ChatBots
// prevent triggering multiple time
if ((DateTime.Now - CaughtTime).TotalSeconds > 1)
{
+ isFishing = false;
CaughtTime = DateTime.Now;
OnCaughtFish();
}
@@ -169,15 +211,14 @@ namespace MinecraftClient.ChatBots
}
}
+ public override void OnDeath()
+ {
+ StopFishing();
+ }
+
public override bool OnDisconnect(DisconnectReason reason, string message)
{
- lock (stateLock)
- {
- isFishing = false;
-
- counter = 0;
- state = FishingState.Stopping;
- }
+ StopFishing();
fishingBobber = null;
LastPos = Location.Zero;
@@ -194,34 +235,123 @@ namespace MinecraftClient.ChatBots
UseItemInLeftHand();
}
- ///
- /// Called when detected a fish is caught
- ///
- public void OnCaughtFish()
+ private void UpdateLocation(double[,] locationList)
{
- lock (stateLock)
+ if (curLocationIdx >= locationList.GetLength(0))
{
- state = FishingState.Preparing;
+ curLocationIdx = Math.Max(0, locationList.GetLength(0) - 2);
+ moveDir = -1;
+ }
+ else if (curLocationIdx < 0)
+ {
+ curLocationIdx = Math.Min(locationList.GetLength(0) - 1, 1);
+ moveDir = 1;
}
- UseFishRod();
+ int locationType = locationList.GetLength(1);
- ++fishCount;
- LogToConsole(GetTimestamp() + ": " + Translations.Get("bot.autoFish.caught", fishCount));
+ if (locationType == 2)
+ {
+ nextYaw = (float)locationList[curLocationIdx, 0];
+ nextPitch = (float)locationList[curLocationIdx, 1];
+ }
+ else if (locationType == 3)
+ {
+ nextYaw = GetYaw();
+ nextPitch = GetPitch();
+ }
+ else if (locationType == 5)
+ {
+ nextYaw = (float)locationList[curLocationIdx, 3];
+ nextPitch = (float)locationList[curLocationIdx, 4];
+ }
+ if (locationType == 3 || locationType == 5)
+ {
+ Location current = GetCurrentLocation();
+ Location goal = new(locationList[curLocationIdx, 0], locationList[curLocationIdx, 1], locationList[curLocationIdx, 2]);
+
+ bool isMoveSuccessed;
+ if (!Movement.CheckChunkLoading(GetWorld(), current, goal))
+ {
+ LogToConsole(Translations.Get("cmd.move.chunk_not_loaded", goal.X, goal.Y, goal.Z));
+ isMoveSuccessed = false;
+ }
+ else
+ {
+ isMoveSuccessed = MoveToLocation(goal, allowUnsafe: false, allowDirectTeleport: false);
+ }
+
+ if (!isMoveSuccessed)
+ {
+ nextYaw = GetYaw();
+ nextPitch = GetPitch();
+ LogToConsole(Translations.Get("cmd.move.fail", goal));
+ }
+ else
+ {
+ LogToConsole(Translations.Get("cmd.move.walk", goal, current));
+ }
+ }
+
+ curLocationIdx += moveDir;
+ }
+
+
+ private void DurabilityCheckAndMove()
+ {
if (inventoryEnabled)
{
if (!HasFishingRod())
{
LogToConsole(GetTimestamp() + ": " + Translations.Get("bot.autoFish.no_rod"));
+
+ lock (stateLock)
+ {
+ state = FishingState.Stopping;
+ }
+
return;
}
}
+ double[,]? locationList = Settings.AutoFishing_Location;
+ if (locationList != null)
+ {
+ UpdateLocation(locationList);
+ lock (stateLock)
+ {
+ state = FishingState.ChangeLocation;
+ }
+ }
+ else
+ {
+ lock (stateLock)
+ {
+ counter = (int)(Settings.AutoFishing_FishingCastDelay * 10);
+ state = FishingState.WaitingToCast;
+ }
+ }
+ }
+
+ ///
+ /// Called when detected a fish is caught
+ ///
+ public void OnCaughtFish()
+ {
+ ++fishCount;
+ if (Settings.AutoFishing_Location != null)
+ LogToConsole(GetTimestamp() + ": " + Translations.Get("bot.autoFish.caught_at",
+ fishingBobber!.Location.X, fishingBobber!.Location.Y, fishingBobber!.Location.Z, fishCount));
+ else
+ LogToConsole(GetTimestamp() + ": " + Translations.Get("bot.autoFish.caught", fishCount));
+
lock (stateLock)
{
- counter = (int)(Settings.AutoFishing_FishingCastDelay * 10);
- state = FishingState.WaitingToCast;
+ UseFishRod();
+
+ counter = 0;
+ state = FishingState.DurabilityCheck;
}
}
@@ -233,6 +363,7 @@ namespace MinecraftClient.ChatBots
{
if (!inventoryEnabled)
return false;
+
int start = 36;
int end = 44;
Container container = GetPlayerInventory();
diff --git a/MinecraftClient/Commands/Move.cs b/MinecraftClient/Commands/Move.cs
index 1b4f85ee..da58e76b 100644
--- a/MinecraftClient/Commands/Move.cs
+++ b/MinecraftClient/Commands/Move.cs
@@ -74,8 +74,7 @@ namespace MinecraftClient.Commands
Location goal = Movement.Move(handler.GetCurrentLocation(), direction);
- ChunkColumn? chunkColumn = handler.GetWorld().GetChunkColumn(goal);
- if (chunkColumn == null || chunkColumn.FullyLoaded == false)
+ if (!Movement.CheckChunkLoading(handler.GetWorld(), handler.GetCurrentLocation(), goal))
return Translations.Get("cmd.move.chunk_not_loaded", goal.X, goal.Y, goal.Z);
if (Movement.CanMove(handler.GetWorld(), handler.GetCurrentLocation(), direction))
@@ -98,8 +97,7 @@ namespace MinecraftClient.Commands
double z = args[2].StartsWith('~') ? current.Z + (args[2].Length > 1 ? double.Parse(args[2][1..]) : 0) : double.Parse(args[2]);
Location goal = new(x, y, z);
- ChunkColumn? chunkColumn = handler.GetWorld().GetChunkColumn(goal);
- if (chunkColumn == null || chunkColumn.FullyLoaded == false)
+ if (!Movement.CheckChunkLoading(handler.GetWorld(), current, goal))
return Translations.Get("cmd.move.chunk_not_loaded", x, y, z);
if (takeRisk || Movement.PlayerFitsHere(handler.GetWorld(), goal))
diff --git a/MinecraftClient/Mapping/Movement.cs b/MinecraftClient/Mapping/Movement.cs
index 8389cdd7..21811c8d 100644
--- a/MinecraftClient/Mapping/Movement.cs
+++ b/MinecraftClient/Mapping/Movement.cs
@@ -643,5 +643,25 @@ namespace MinecraftClient.Mapping
throw new ArgumentException("Unknown direction", "direction");
}
}
+
+ ///
+ /// Check that the chunks at both the start and destination locations have been loaded
+ ///
+ /// Current world
+ /// Start location
+ /// Destination location
+ /// Is loading complete
+ public static bool CheckChunkLoading(World world, Location start, Location dest)
+ {
+ ChunkColumn? chunkColumn = world.GetChunkColumn(dest);
+ if (chunkColumn == null || chunkColumn.FullyLoaded == false)
+ return false;
+
+ chunkColumn = world.GetChunkColumn(start);
+ if (chunkColumn == null || chunkColumn.FullyLoaded == false)
+ return false;
+
+ return true;
+ }
}
}
diff --git a/MinecraftClient/Resources/config/MinecraftClient.ini b/MinecraftClient/Resources/config/MinecraftClient.ini
index 2a9d7436..4632cd00 100644
--- a/MinecraftClient/Resources/config/MinecraftClient.ini
+++ b/MinecraftClient/Resources/config/MinecraftClient.ini
@@ -207,11 +207,16 @@ interaction=Attack # Possible values: Interact, Attack (default)
# /!\ Make sure server rules allow automated farming before using this bot
enabled=false
antidespawn=false
-main_hand=true # Use the main hand or the second hand to hold the rod
+main_hand=true # Use the main hand or the second hand to hold the rod.
fishing_delay=3.0 # How long after entering the game to start fishing (seconds).
-fishing_timeout=600.0 # Fishing timeout (seconds). Timeout will re-cast the rod
+fishing_timeout=300.0 # Fishing timeout (seconds). Timeout will trigger a re-cast.
fishing_hook_threshold=0.2 # Fish hooks moving on the Y-axis above this threshold will be considered to have caught a fish.
fishing_cast_delay=0.4 # How soon to re-cast after successful fishing.
+location= # Some plugins do not allow the player to fish in one place. This allows the player to change position/angle after each fish caught.
+ # Floating point numbers can be used for both coordinates and angles. Leave blank to disable this function.
+ # Change the angle only (recommended): location=yaw_1,pitch_1;yaw_2,pitch_2;...;yaw_n,pitch_n
+ # Change position only: location=x1,y1,z1;x2,y2,z2;...;xn,yn,zn
+ # Change both angle and position: location=x1,y1,z1,yaw_1,pitch_1;x2,y2,z2,yaw_2,pitch_2;...;xn,yn,zn,yaw_n,pitch_n
[AutoEat]
# Automatically eat food when your Hunger value is low
diff --git a/MinecraftClient/Resources/lang/en.ini b/MinecraftClient/Resources/lang/en.ini
index 4a30ee1c..15b6d2e9 100644
--- a/MinecraftClient/Resources/lang/en.ini
+++ b/MinecraftClient/Resources/lang/en.ini
@@ -88,6 +88,8 @@ error.connection_timeout=§8A timeout occured while attempting to connect to thi
error.forge=§8Forge Login Handshake did not complete successfully
error.forge_encrypt=§8Forge StartEncryption Handshake did not complete successfully
error.setting.str2int=Failed to convert '{0}' into an integer. Please check your settings.
+error.setting.str2locationList.convert_fail=Failed to convert '{0}' to a floating point number. Please check your settings.
+error.setting.str2locationList.format_err=Wrong format, can't parse '{0}' into position data.. Please check your settings.
error.setting.argument_syntax={0}: Invalid syntax, expecting --argname=value or --section.argname=value
error.setting.unknown_section={0}: Unknown setting section '{1}'
error.setting.unknown_or_invalid={0}: Unknown setting or invalid value
@@ -447,13 +449,16 @@ bot.autoDrop.no_mode=Cannot read drop mode from config. Using include mode.
bot.autoDrop.no_inventory=Cannot find inventory {0}!
# AutoFish
+bot.autoFish.no_inv_handle=Inventory handling is not enabled. Cannot check rod durability and switch rods.
bot.autoFish.start=Fishing will start in {0:0.0} second(s).
bot.autoFish.throw=Casting successfully.
bot.autoFish.caught=Caught a fish! (Count: {0})
+bot.autoFish.caught_at=Caught a fish at ({0:0.0},{1:0.0},{2:0.0})! (Count: {3})
bot.autoFish.no_rod=No Fishing Rod on hand. Maybe broken?
bot.autoFish.despawn=Fish floating despawn, will re-cast.
bot.autoFish.fishing_timeout=Fishing timeout, will soon re-cast.
bot.autoFish.cast_timeout=Casting timeout and will soon retry. (Timeout increased to {0:0.0} sec).
+bot.autoFish.update_lookat=Update yaw = {0:0.00}, pitch = {1:0.00}.
# AutoRelog
bot.autoRelog.launch=Launching with {0} reconnection attempts
diff --git a/MinecraftClient/Scripting/ChatBot.cs b/MinecraftClient/Scripting/ChatBot.cs
index 3e5515e9..4d4ac0ac 100644
--- a/MinecraftClient/Scripting/ChatBot.cs
+++ b/MinecraftClient/Scripting/ChatBot.cs
@@ -1012,6 +1012,16 @@ namespace MinecraftClient
Handler.UpdateLocation(Handler.GetCurrentLocation(), location);
}
+ ///
+ /// Look at the specified location
+ ///
+ /// Yaw to look at
+ /// Pitch to look at
+ protected void LookAtLocation(float yaw, float pitch)
+ {
+ Handler.UpdateLocation(Handler.GetCurrentLocation(), yaw, pitch);
+ }
+
///
/// Get a Y-M-D h:m:s timestamp representing the current system date and time
///
diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs
index 380f7154..30e46cc0 100644
--- a/MinecraftClient/Settings.cs
+++ b/MinecraftClient/Settings.cs
@@ -208,6 +208,7 @@ namespace MinecraftClient
public static double AutoFishing_FishingTimeout = 600.0;
public static double AutoFishing_FishingHookThreshold = 0.2;
public static double AutoFishing_FishingCastDelay = 0.4;
+ public static double[,]? AutoFishing_Location = null;
//Auto Eating
public static bool AutoEat_Enabled = false;
@@ -720,6 +721,7 @@ namespace MinecraftClient
case "fishing_timeout": AutoFishing_FishingTimeout = str2double(argValue); return true;
case "fishing_hook_threshold": AutoFishing_FishingHookThreshold = str2double(argValue); return true;
case "fishing_cast_delay": AutoFishing_FishingCastDelay = str2double(argValue); return true;
+ case "location": AutoFishing_Location = str2locationList(argValue); return true;
}
break;
@@ -872,8 +874,8 @@ namespace MinecraftClient
/// Float number
public static float str2float(string str)
{
- if (float.TryParse(str.Trim(), out float f))
- return f;
+ if (float.TryParse(str.Trim(), out float num))
+ return num;
else
{
ConsoleIO.WriteLogLine(Translations.Get("error.setting.str2int", str));
@@ -888,8 +890,8 @@ namespace MinecraftClient
/// Double number
public static double str2double(string str)
{
- if (double.TryParse(str.Trim(), out double f))
- return f;
+ if (double.TryParse(str.Trim(), out double num))
+ return num;
else
{
ConsoleIO.WriteLogLine(Translations.Get("error.setting.str2int", str));
@@ -910,6 +912,46 @@ namespace MinecraftClient
return str == "true" || str == "1";
}
+ ///
+ /// Convert the specified string to a list of location, returning null if invalid argument
+ ///
+ /// String to parse as a location list
+ /// Location list (null or double[*,5] or double[*,3] or double[*,2])
+ public static double[,]? str2locationList(string str)
+ {
+ string[] locationStrList = str.Split(';', StringSplitOptions.RemoveEmptyEntries);
+ double[,]? res = null;
+ int codLen = 0;
+ for (int i = 0; i < locationStrList.Length; ++i)
+ {
+ string[] coordinates_str_list = locationStrList[i].Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+ int curCodLen = coordinates_str_list.Length;
+ if ((curCodLen == 2 || curCodLen == 3 || curCodLen == 5) && (i == 0 || curCodLen == codLen))
+ {
+ if (i == 0)
+ {
+ res = new double[locationStrList.Length, curCodLen];
+ codLen = curCodLen;
+ }
+
+ for (int j = 0; j < curCodLen; ++j)
+ {
+ if (!double.TryParse(coordinates_str_list[j], out res![i, j]))
+ {
+ ConsoleIO.WriteLogLine(Translations.Get("error.setting.str2locationList.convert_fail", coordinates_str_list[j]));
+ return null;
+ }
+ }
+ }
+ else
+ {
+ ConsoleIO.WriteLogLine(Translations.Get("error.setting.str2locationList.format_err", locationStrList[i]));
+ return null;
+ }
+ }
+ return res;
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static string ToLowerIfNeed(string str)
{