This commit is contained in:
BruceChen 2022-09-15 12:48:50 +08:00
commit 206a5f1e72
11 changed files with 530 additions and 85 deletions

View file

@ -13,61 +13,237 @@ namespace MinecraftClient.ChatBots
/// </summary> /// </summary>
class AutoFishing : ChatBot class AutoFishing : ChatBot
{ {
private Entity fishingRod; private int fishCount = 0;
private Double fishingHookThreshold = 0.2;
private Location LastPos = new Location();
private DateTime CaughtTime = DateTime.Now;
private bool inventoryEnabled; private bool inventoryEnabled;
private bool isFishing = false; private int castTimeout = 12;
private int useItemCounter = 0;
private bool isFishing = false, isWaitingRod = false;
private Entity? fishingBobber;
private Location LastPos = Location.Zero;
private DateTime CaughtTime = DateTime.Now;
private int counter = 0;
private readonly object stateLock = new();
private FishingState state = FishingState.WaitJoinGame;
private int curLocationIdx = 0, moveDir = 1;
float nextYaw = 0, nextPitch = 0;
private enum FishingState
{
WaitJoinGame,
WaitingToCast,
CastingRod,
WaitingFishingBobber,
WaitingFishToBite,
StartMove,
WaitingMovement,
DurabilityCheck,
Stopping,
}
public override void Initialize() public override void Initialize()
{ {
if (!GetEntityHandlingEnabled()) if (!GetEntityHandlingEnabled())
{ {
LogToConsoleTranslated("extra.entity_required"); LogToConsoleTranslated("extra.entity_required");
LogToConsoleTranslated("general.bot_unload"); state = FishingState.WaitJoinGame;
UnloadBot();
} }
inventoryEnabled = GetInventoryEnabled(); inventoryEnabled = GetInventoryEnabled();
if (!inventoryEnabled)
LogToConsoleTranslated("bot.autoFish.no_inv_handle");
}
/// <summary>
/// Update settings when reloaded
/// </summary>
public /* override */ void OnSettingsReload()
{
if (Settings.AutoFishing_Enabled)
{
if (!GetEntityHandlingEnabled())
{
LogToConsoleTranslated("extra.entity_required");
state = FishingState.WaitJoinGame;
}
inventoryEnabled = GetInventoryEnabled();
if (!inventoryEnabled)
LogToConsoleTranslated("bot.autoFish.no_inv_handle");
}
else
{
UnloadBot();
return;
}
}
private void StartFishing()
{
isFishing = false;
if (Settings.AutoFishing_AutoStart)
{
double delay = Settings.AutoFishing_FishingDelay;
LogToConsole(Translations.Get("bot.autoFish.start", delay));
lock (stateLock)
{
counter = (int)(delay * 10);
state = FishingState.StartMove;
}
}
else
{
lock (stateLock)
{
state = FishingState.WaitJoinGame;
}
}
}
private void StopFishing()
{
isFishing = false;
lock (stateLock)
{
state = FishingState.Stopping;
}
}
private void UseFishRod()
{
if (Settings.AutoFishing_Mainhand)
UseItemInHand();
else
UseItemInLeftHand();
} }
public override void Update() public override void Update()
{ {
if (useItemCounter > 0) lock (stateLock)
{ {
useItemCounter--; switch (state)
if (useItemCounter <= 0)
{ {
UseItemInHand(); case FishingState.WaitJoinGame:
break;
case FishingState.WaitingToCast:
if (AutoEat.Eating)
counter = (int)(Settings.AutoFishing_CastDelay * 10);
else if (--counter < 0)
state = FishingState.CastingRod;
break;
case FishingState.CastingRod:
UseFishRod();
counter = 0;
state = FishingState.WaitingFishingBobber;
break;
case FishingState.WaitingFishingBobber:
if (++counter > castTimeout)
{
if (castTimeout < 6000)
castTimeout *= 2; // Exponential backoff
LogToConsole(GetTimestamp() + ": " + Translations.Get("bot.autoFish.cast_timeout", castTimeout / 10.0));
counter = (int)(Settings.AutoFishing_CastDelay * 10);
state = FishingState.WaitingToCast;
}
break;
case FishingState.WaitingFishToBite:
if (++counter > (int)(Settings.AutoFishing_FishingTimeout * 10))
{
LogToConsole(GetTimestamp() + ": " + Translations.Get("bot.autoFish.fishing_timeout"));
counter = (int)(Settings.AutoFishing_CastDelay * 10);
state = FishingState.WaitingToCast;
}
break;
case FishingState.StartMove:
if (--counter < 0)
{
double[,]? locationList = Settings.AutoFishing_Location;
if (locationList != null)
{
if (GetTerrainEnabled())
{
UpdateLocation(locationList);
state = FishingState.WaitingMovement;
}
else
{
LogToConsole(Translations.Get("extra.terrainandmovement_required"));
state = FishingState.WaitJoinGame;
}
}
else
{
counter = (int)(Settings.AutoFishing_CastDelay * 10);
state = FishingState.DurabilityCheck;
goto case FishingState.DurabilityCheck;
}
}
break;
case FishingState.WaitingMovement:
if (!ClientIsMoving())
{
LookAtLocation(nextYaw, nextPitch);
LogToConsole(Translations.Get("bot.autoFish.update_lookat", nextYaw, nextPitch));
state = FishingState.DurabilityCheck;
goto case FishingState.DurabilityCheck;
}
break;
case FishingState.DurabilityCheck:
if (DurabilityCheck())
{
counter = (int)(Settings.AutoFishing_CastDelay * 10);
state = FishingState.WaitingToCast;
}
break;
case FishingState.Stopping:
break;
} }
} }
} }
public override void OnEntitySpawn(Entity entity) public override void OnEntitySpawn(Entity entity)
{ {
if (entity.Type == EntityType.FishingBobber) if (entity.Type == EntityType.FishingBobber && entity.ObjectData == GetPlayerEntityID())
{ {
if (GetCurrentLocation().Distance(entity.Location) < 2 && !isFishing) if (Settings.AutoFishing_LogFishingBobber)
LogToConsole(string.Format("FishingBobber spawn at {0}, distance = {1:0.00}", entity.Location, GetCurrentLocation().Distance(entity.Location)));
LogToConsole(GetTimestamp() + ": " + Translations.Get("bot.autoFish.throw"));
lock (stateLock)
{ {
LogToConsoleTranslated("bot.autoFish.throw"); fishingBobber = entity;
fishingRod = entity;
LastPos = entity.Location; LastPos = entity.Location;
isFishing = true; isFishing = true;
castTimeout = 24;
counter = 0;
state = FishingState.WaitingFishToBite;
} }
} }
} }
public override void OnEntityDespawn(Entity entity) public override void OnEntityDespawn(Entity entity)
{ {
if (entity.Type == EntityType.FishingBobber) if (entity.Type == EntityType.FishingBobber && entity.ID == fishingBobber!.ID)
{ {
if(entity.ID == fishingRod.ID) if (Settings.AutoFishing_LogFishingBobber)
LogToConsole(string.Format("FishingBobber despawn at {0}", entity.Location));
if (isFishing)
{ {
isFishing = false; isFishing = false;
if (Settings.AutoFishing_Antidespawn) if (Settings.AutoFishing_Antidespawn)
{ {
useItemCounter = 5; // 500ms LogToConsoleTranslated("bot.autoFish.despawn");
lock (stateLock)
{
counter = (int)(Settings.AutoFishing_CastDelay * 10);
state = FishingState.WaitingToCast;
}
} }
} }
} }
@ -75,41 +251,55 @@ namespace MinecraftClient.ChatBots
public override void OnEntityMove(Entity entity) public override void OnEntityMove(Entity entity)
{ {
if (isFishing) if (isFishing && fishingBobber!.ID == entity.ID)
{ {
if (fishingRod.ID == entity.ID) Location Pos = entity.Location;
double Dx = LastPos.X - Pos.X;
double Dy = LastPos.Y - Pos.Y;
double Dz = LastPos.Z - Pos.Z;
LastPos = Pos;
if (Settings.AutoFishing_LogFishingBobber)
LogToConsole(string.Format("FishingBobber {0} Dx={1:0.000000} Dy={2:0.000000} Dz={3:0.000000}", Pos, Dx, Math.Abs(Dy), Dz));
if (Math.Abs(Dx) < Math.Abs(Settings.AutoFishing_StationaryThreshold) &&
Math.Abs(Dz) < Math.Abs(Settings.AutoFishing_StationaryThreshold) &&
Math.Abs(Dy) > Math.Abs(Settings.AutoFishing_HookThreshold))
{ {
Location Pos = entity.Location; // prevent triggering multiple time
Double Dx = LastPos.X - Pos.X; if ((DateTime.Now - CaughtTime).TotalSeconds > 1)
Double Dy = LastPos.Y - Pos.Y;
Double Dz = LastPos.Z - Pos.Z;
LastPos = Pos;
// check if fishing hook is stationary
if (Dx == 0 && Dz == 0)
{ {
if (Math.Abs(Dy) > fishingHookThreshold) isFishing = false;
{ CaughtTime = DateTime.Now;
// caught OnCaughtFish();
// prevent triggering multiple time
if ((DateTime.Now - CaughtTime).TotalSeconds > 1)
{
OnCaughtFish();
CaughtTime = DateTime.Now;
}
}
} }
fishingRod = entity;
} }
} }
} }
public override void AfterGameJoined()
{
StartFishing();
}
public override void OnRespawn()
{
StartFishing();
}
public override void OnDeath()
{
StopFishing();
}
public override bool OnDisconnect(DisconnectReason reason, string message) public override bool OnDisconnect(DisconnectReason reason, string message)
{ {
fishingRod = null; StopFishing();
LastPos = new Location();
fishingBobber = null;
LastPos = Location.Zero;
CaughtTime = DateTime.Now; CaughtTime = DateTime.Now;
isFishing = false;
useItemCounter = 0;
return base.OnDisconnect(reason, message); return base.OnDisconnect(reason, message);
} }
@ -118,43 +308,124 @@ namespace MinecraftClient.ChatBots
/// </summary> /// </summary>
public void OnCaughtFish() public void OnCaughtFish()
{ {
LogToConsole(GetTimestamp() + ": " + Translations.Get("bot.autoFish.caught")); ++fishCount;
// retract fishing rod if (Settings.AutoFishing_Location != null)
UseItemInHand(); LogToConsole(GetTimestamp() + ": " + Translations.Get("bot.autoFish.caught_at",
if (inventoryEnabled) fishingBobber!.Location.X, fishingBobber!.Location.Y, fishingBobber!.Location.Z, fishCount));
else
LogToConsole(GetTimestamp() + ": " + Translations.Get("bot.autoFish.caught", fishCount));
lock (stateLock)
{ {
if (!hasFishingRod()) UseFishRod();
{
LogToConsole(GetTimestamp() + ": " + Translations.Get("bot.autoFish.no_rod")); counter = 0;
return; state = FishingState.StartMove;
}
} }
// thread-safe
useItemCounter = 8; // 800ms
} }
/// <summary> private void UpdateLocation(double[,] locationList)
/// Check whether the player has a fishing rod in inventory
/// </summary>
/// <returns>TRUE if the player has a fishing rod</returns>
public bool hasFishingRod()
{ {
if (!inventoryEnabled) if (curLocationIdx >= locationList.GetLength(0))
return false;
int start = 36;
int end = 44;
Inventory.Container container = GetPlayerInventory();
foreach (KeyValuePair<int, Item> a in container.Items)
{ {
if (a.Key < start || a.Key > end) curLocationIdx = Math.Max(0, locationList.GetLength(0) - 2);
continue; moveDir = -1;
}
if (a.Value.Type == ItemType.FishingRod) else if (curLocationIdx < 0)
return true; {
curLocationIdx = Math.Min(locationList.GetLength(0) - 1, 1);
moveDir = 1;
} }
return false; int locationType = locationList.GetLength(1);
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 bool DurabilityCheck()
{
if (!inventoryEnabled)
return true;
bool useMainHand = Settings.AutoFishing_Mainhand;
Container container = GetPlayerInventory();
int itemSolt = useMainHand ? GetCurrentSlot() + 36 : 45;
if (container.Items.TryGetValue(itemSolt, out Item? handItem) &&
handItem.Type == ItemType.FishingRod && (64 - handItem.Damage) >= Settings.AutoFishing_DurabilityLimit)
{
isWaitingRod = false;
return true;
}
else
{
if (!isWaitingRod)
LogToConsole(GetTimestamp() + ": " + Translations.Get("bot.autoFish.no_rod"));
if (Settings.AutoFishing_AutoRodSwitch)
{
foreach ((int slot, Item item) in container.Items)
{
if (item.Type == ItemType.FishingRod && (64 - item.Damage) >= Settings.AutoFishing_DurabilityLimit)
{
WindowAction(0, slot, WindowActionType.LeftClick);
WindowAction(0, itemSolt, WindowActionType.LeftClick);
WindowAction(0, slot, WindowActionType.LeftClick);
LogToConsole(GetTimestamp() + ": " + Translations.Get("bot.autoFish.switch", slot, (64 - item.Damage)));
isWaitingRod = false;
return true;
}
}
}
isWaitingRod = true;
return false;
}
} }
} }
} }

View file

@ -74,8 +74,7 @@ namespace MinecraftClient.Commands
Location goal = Movement.Move(handler.GetCurrentLocation(), direction); Location goal = Movement.Move(handler.GetCurrentLocation(), direction);
ChunkColumn? chunkColumn = handler.GetWorld().GetChunkColumn(goal); if (!Movement.CheckChunkLoading(handler.GetWorld(), handler.GetCurrentLocation(), goal))
if (chunkColumn == null || chunkColumn.FullyLoaded == false)
return Translations.Get("cmd.move.chunk_not_loaded", goal.X, goal.Y, goal.Z); return Translations.Get("cmd.move.chunk_not_loaded", goal.X, goal.Y, goal.Z);
if (Movement.CanMove(handler.GetWorld(), handler.GetCurrentLocation(), direction)) 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]); 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); Location goal = new(x, y, z);
ChunkColumn? chunkColumn = handler.GetWorld().GetChunkColumn(goal); if (!Movement.CheckChunkLoading(handler.GetWorld(), current, goal))
if (chunkColumn == null || chunkColumn.FullyLoaded == false)
return Translations.Get("cmd.move.chunk_not_loaded", x, y, z); return Translations.Get("cmd.move.chunk_not_loaded", x, y, z);
if (takeRisk || Movement.PlayerFitsHere(handler.GetWorld(), goal)) if (takeRisk || Movement.PlayerFitsHere(handler.GetWorld(), goal))

View file

@ -643,5 +643,25 @@ namespace MinecraftClient.Mapping
throw new ArgumentException("Unknown direction", "direction"); throw new ArgumentException("Unknown direction", "direction");
} }
} }
/// <summary>
/// Check that the chunks at both the start and destination locations have been loaded
/// </summary>
/// <param name="world">Current world</param>
/// <param name="start">Start location</param>
/// <param name="dest">Destination location</param>
/// <returns>Is loading complete</returns>
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;
}
} }
} }

View file

@ -1277,6 +1277,15 @@ namespace MinecraftClient
return InvokeOnMainThread(() => handler.SendUseItem(0, this.sequenceId)); return InvokeOnMainThread(() => handler.SendUseItem(0, this.sequenceId));
} }
/// <summary>
/// Use the item currently in the player's left hand
/// </summary>
/// <returns>TRUE if the item was successfully used</returns>
public bool UseItemOnLeftHand()
{
return InvokeOnMainThread(() => handler.SendUseItem(1, this.sequenceId));
}
/// <summary> /// <summary>
/// Try to merge a slot /// Try to merge a slot
/// </summary> /// </summary>

View file

@ -602,7 +602,7 @@ namespace MinecraftClient.Protocol.Handlers
bool lastVerifyResult = player.IsMessageChainLegal(); bool lastVerifyResult = player.IsMessageChainLegal();
verifyResult = player.VerifyMessageHead(ref precedingSignature, ref headerSignature, ref bodyDigest); verifyResult = player.VerifyMessageHead(ref precedingSignature, ref headerSignature, ref bodyDigest);
if (lastVerifyResult && !verifyResult) if (lastVerifyResult && !verifyResult)
log.Warn("Player " + player.Name + "'s message chain is broken!"); log.Warn(Translations.Get("chat.message_chain_broken", player.Name));
} }
} }
} }

View file

@ -46,6 +46,7 @@ namespace MinecraftClient.Protocol
Gamemode = gamemode; Gamemode = gamemode;
Ping = ping; Ping = ping;
DisplayName = displayName; DisplayName = displayName;
lastMessageVerified = false;
if (timeStamp != null && publicKey != null && signature != null) if (timeStamp != null && publicKey != null && signature != null)
{ {
DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds((long)timeStamp); DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds((long)timeStamp);
@ -53,13 +54,13 @@ namespace MinecraftClient.Protocol
try try
{ {
PublicKey = new PublicKey(publicKey, signature); PublicKey = new PublicKey(publicKey, signature);
lastMessageVerified = true;
} }
catch (System.Security.Cryptography.CryptographicException) catch (System.Security.Cryptography.CryptographicException)
{ {
PublicKey = null; PublicKey = null;
} }
} }
lastMessageVerified = true;
precedingSignature = null; precedingSignature = null;
} }
@ -121,9 +122,15 @@ namespace MinecraftClient.Protocol
if (this.lastMessageVerified == false) if (this.lastMessageVerified == false)
return false; return false;
if (PublicKey == null || IsKeyExpired() || (this.precedingSignature != null && precedingSignature == null)) if (PublicKey == null || IsKeyExpired() || (this.precedingSignature != null && precedingSignature == null))
{
this.lastMessageVerified = false;
return false; return false;
}
if (this.precedingSignature != null && !this.precedingSignature.SequenceEqual(precedingSignature!)) if (this.precedingSignature != null && !this.precedingSignature.SequenceEqual(precedingSignature!))
{
this.lastMessageVerified = false;
return false; return false;
}
DateTimeOffset timeOffset = DateTimeOffset.FromUnixTimeMilliseconds(timestamp); DateTimeOffset timeOffset = DateTimeOffset.FromUnixTimeMilliseconds(timestamp);
@ -150,9 +157,15 @@ namespace MinecraftClient.Protocol
if (this.lastMessageVerified == false) if (this.lastMessageVerified == false)
return false; return false;
if (PublicKey == null || IsKeyExpired() || (this.precedingSignature != null && precedingSignature == null)) if (PublicKey == null || IsKeyExpired() || (this.precedingSignature != null && precedingSignature == null))
{
this.lastMessageVerified = false;
return false; return false;
}
if (this.precedingSignature != null && !this.precedingSignature.SequenceEqual(precedingSignature!)) if (this.precedingSignature != null && !this.precedingSignature.SequenceEqual(precedingSignature!))
{
this.lastMessageVerified = false;
return false; return false;
}
bool res = PublicKey.VerifyHeader(Uuid, ref bodyDigest, ref headerSignature, ref precedingSignature); bool res = PublicKey.VerifyHeader(Uuid, ref bodyDigest, ref headerSignature, ref precedingSignature);

View file

@ -207,6 +207,21 @@ interaction=Attack # Possible values: Interact, Attack (default)
# /!\ Make sure server rules allow automated farming before using this bot # /!\ Make sure server rules allow automated farming before using this bot
enabled=false enabled=false
antidespawn=false antidespawn=false
main_hand=true # Use the main hand or the second hand to hold the rod.
auto_start=true # Whether to start fishing automatically after entering a world.
cast_delay=0.4 # How soon to re-cast after successful fishing.
fishing_delay=3.0 # How long after entering the game to start fishing (seconds).
fishing_timeout=300.0 # Fishing timeout (seconds). Timeout will trigger a re-cast.
durability_limit=2 # Will not use rods with less durability than this (full durability is 64). Set to zero to disable this feature.
auto_rod_switch=true # Switch to a new rod from inventory after the current rod is unavailable.
stationary_threshold=0.001 # Hooks moving in the X and Z axes below this threshold will be considered stationary.
hook_threshold=0.2 # A stationary hook moving on the Y-axis above this threshold will be considered to have caught a fish.
log_fishing_bobber=false # For debugging purposes, you can use this log to adjust the two thresholds mentioned above.
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] [AutoEat]
# Automatically eat food when your Hunger value is low # Automatically eat food when your Hunger value is low

View file

@ -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=§8Forge Login Handshake did not complete successfully
error.forge_encrypt=§8Forge StartEncryption 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.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.argument_syntax={0}: Invalid syntax, expecting --argname=value or --section.argname=value
error.setting.unknown_section={0}: Unknown setting section '{1}' error.setting.unknown_section={0}: Unknown setting section '{1}'
error.setting.unknown_or_invalid={0}: Unknown setting or invalid value error.setting.unknown_or_invalid={0}: Unknown setting or invalid value
@ -447,9 +449,17 @@ bot.autoDrop.no_mode=Cannot read drop mode from config. Using include mode.
bot.autoDrop.no_inventory=Cannot find inventory {0}! bot.autoDrop.no_inventory=Cannot find inventory {0}!
# AutoFish # AutoFish
bot.autoFish.throw=Threw a fishing rod bot.autoFish.no_inv_handle=Inventory handling is not enabled. Cannot check rod durability and switch rods.
bot.autoFish.caught=Caught a fish! bot.autoFish.start=Fishing will start in {0:0.0} second(s).
bot.autoFish.no_rod=No Fishing Rod on hand. Maybe broken? 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=Current fishing rod is not available. Maybe broken or low durability?
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}.
bot.autoFish.switch=Switch to the rod in slot {0}, durability {1}/64.
# AutoRelog # AutoRelog
bot.autoRelog.launch=Launching with {0} reconnection attempts bot.autoRelog.launch=Launching with {0} reconnection attempts

View file

@ -1012,6 +1012,16 @@ namespace MinecraftClient
Handler.UpdateLocation(Handler.GetCurrentLocation(), location); Handler.UpdateLocation(Handler.GetCurrentLocation(), location);
} }
/// <summary>
/// Look at the specified location
/// </summary>
/// <param name="yaw">Yaw to look at</param>
/// <param name="pitch">Pitch to look at</param>
protected void LookAtLocation(float yaw, float pitch)
{
Handler.UpdateLocation(Handler.GetCurrentLocation(), yaw, pitch);
}
/// <summary> /// <summary>
/// Get a Y-M-D h:m:s timestamp representing the current system date and time /// Get a Y-M-D h:m:s timestamp representing the current system date and time
/// </summary> /// </summary>
@ -1113,6 +1123,15 @@ namespace MinecraftClient
return Handler.GetUserUuidStr(); return Handler.GetUserUuidStr();
} }
/// <summary>
/// Return the EntityID of the current player
/// </summary>
/// <returns>EntityID of the current player</returns>
protected int GetPlayerEntityID()
{
return Handler.GetPlayerEntityID();
}
/// <summary> /// <summary>
/// Return the list of currently online players /// Return the list of currently online players
/// </summary> /// </summary>
@ -1253,6 +1272,15 @@ namespace MinecraftClient
return Handler.UseItemOnHand(); return Handler.UseItemOnHand();
} }
/// <summary>
/// Use item currently in the player's hand (active inventory bar slot)
/// </summary>
/// <returns>TRUE if successful</returns>
protected bool UseItemInLeftHand()
{
return Handler.UseItemOnLeftHand();
}
/// <summary> /// <summary>
/// Check inventory handling enable status /// Check inventory handling enable status
/// </summary> /// </summary>

View file

@ -203,6 +203,17 @@ namespace MinecraftClient
//Auto Fishing //Auto Fishing
public static bool AutoFishing_Enabled = false; public static bool AutoFishing_Enabled = false;
public static bool AutoFishing_Antidespawn = false; public static bool AutoFishing_Antidespawn = false;
public static bool AutoFishing_Mainhand = true;
public static bool AutoFishing_AutoStart = true;
public static double AutoFishing_CastDelay = 0.4;
public static double AutoFishing_FishingDelay = 3.0;
public static double AutoFishing_FishingTimeout = 300.0;
public static double AutoFishing_DurabilityLimit = 2;
public static bool AutoFishing_AutoRodSwitch = true;
public static double AutoFishing_StationaryThreshold = 0.001;
public static double AutoFishing_HookThreshold = 0.2;
public static bool AutoFishing_LogFishingBobber = false;
public static double[,]? AutoFishing_Location = null;
//Auto Eating //Auto Eating
public static bool AutoEat_Enabled = false; public static bool AutoEat_Enabled = false;
@ -711,6 +722,17 @@ namespace MinecraftClient
{ {
case "enabled": AutoFishing_Enabled = str2bool(argValue); return true; case "enabled": AutoFishing_Enabled = str2bool(argValue); return true;
case "antidespawn": AutoFishing_Antidespawn = str2bool(argValue); return true; case "antidespawn": AutoFishing_Antidespawn = str2bool(argValue); return true;
case "main_hand": AutoFishing_Mainhand = str2bool(argValue); return true;
case "auto_start": AutoFishing_AutoStart = str2bool(argValue); return true;
case "cast_delay": AutoFishing_CastDelay = str2double(argValue); return true;
case "fishing_delay": AutoFishing_FishingDelay = str2double(argValue); return true;
case "fishing_timeout": AutoFishing_FishingTimeout = str2double(argValue); return true;
case "durability_limit": AutoFishing_DurabilityLimit = str2int(argValue); return true;
case "auto_rod_switch": AutoFishing_AutoRodSwitch = str2bool(argValue); return true;
case "stationary_threshold": AutoFishing_StationaryThreshold = str2double(argValue); return true;
case "hook_threshold": AutoFishing_HookThreshold = str2double(argValue); return true;
case "log_fishing_bobber": AutoFishing_LogFishingBobber = str2bool(argValue); return true;
case "location": AutoFishing_Location = str2locationList(argValue); return true;
} }
break; break;
@ -863,9 +885,24 @@ namespace MinecraftClient
/// <returns>Float number</returns> /// <returns>Float number</returns>
public static float str2float(string str) public static float str2float(string str)
{ {
float f; if (float.TryParse(str.Trim(), out float num))
if (float.TryParse(str.Trim(), out f)) return num;
return f; else
{
ConsoleIO.WriteLogLine(Translations.Get("error.setting.str2int", str));
return 0;
}
}
/// <summary>
/// Convert the specified string to a double number, defaulting to zero if invalid argument
/// </summary>
/// <param name="str">String to parse as a float number</param>
/// <returns>Double number</returns>
public static double str2double(string str)
{
if (double.TryParse(str.Trim(), out double num))
return num;
else else
{ {
ConsoleIO.WriteLogLine(Translations.Get("error.setting.str2int", str)); ConsoleIO.WriteLogLine(Translations.Get("error.setting.str2int", str));
@ -886,6 +923,46 @@ namespace MinecraftClient
return str == "true" || str == "1"; return str == "true" || str == "1";
} }
/// <summary>
/// Convert the specified string to a list of location, returning null if invalid argument
/// </summary>
/// <param name="str">String to parse as a location list</param>
/// <returns>Location list (null or double[*,5] or double[*,3] or double[*,2])</returns>
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)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static string ToLowerIfNeed(string str) public static string ToLowerIfNeed(string str)
{ {

View file

@ -6,7 +6,7 @@
[Documentation](https://mccteam.github.io/docs/) | [Download](#download) | [Installation](https://mccteam.github.io/docs/guide/installation.html) | [Configuration](https://mccteam.github.io/docs/guide/configuration.html) | [Usage](https://mccteam.github.io/docs/guide/usage.html) [Documentation](https://mccteam.github.io/docs/) | [Download](#download) | [Installation](https://mccteam.github.io/docs/guide/installation.html) | [Configuration](https://mccteam.github.io/docs/guide/configuration.html) | [Usage](https://mccteam.github.io/docs/guide/usage.html)
[![GitHub Actions build status](https://github.com/MCCTeam/Minecraft-Console-Client/actions/workflows/build-and-release.yml/badge.svg)](https://github.com/MCCTeam/Minecraft-Console-Client/releases/latest) [![GitHub Actions build status](https://github.com/MCCTeam/Minecraft-Console-Client/actions/workflows/build-and-release.yml/badge.svg)](https://github.com/MCCTeam/Minecraft-Console-Client/releases/latest) <a href="https://discord.gg/sfBv4TtpC9"><img src="https://img.shields.io/discord/1018553894831403028?color=5865F2&logo=discord&logoColor=white" alt="Discord server" /></a>
</div> </div>
@ -31,6 +31,10 @@ Get development builds from the [Releases section](https://github.com/MCCTeam/Mi
Check out the [Website](https://mccteam.github.io/), [README](https://github.com/MCCTeam/Minecraft-Console-Client/tree/master/MinecraftClient/config#minecraft-console-client-user-manual) and existing [Discussions](https://github.com/MCCTeam/Minecraft-Console-Client/discussions): Maybe your question is answered there. If not, please open a [New Discussion](https://github.com/MCCTeam/Minecraft-Console-Client/discussions/new) and ask your question. If you find a bug, please report it in the [Issues](https://github.com/MCCTeam/Minecraft-Console-Client/issues) section. Check out the [Website](https://mccteam.github.io/), [README](https://github.com/MCCTeam/Minecraft-Console-Client/tree/master/MinecraftClient/config#minecraft-console-client-user-manual) and existing [Discussions](https://github.com/MCCTeam/Minecraft-Console-Client/discussions): Maybe your question is answered there. If not, please open a [New Discussion](https://github.com/MCCTeam/Minecraft-Console-Client/discussions/new) and ask your question. If you find a bug, please report it in the [Issues](https://github.com/MCCTeam/Minecraft-Console-Client/issues) section.
## Discord
We now have a Discord server, click [here](https://discord.gg/sfBv4TtpC9) to join.
## Helping Us ❤️ ## Helping Us ❤️
We are a small community so we need help to implement upgrades for new Minecraft versions, fixing bugs and expanding the project. We are always looking for motivated people to contribute. If you feel like it could be you, please have a look at the [issues](https://github.com/MCCTeam/Minecraft-Console-Client/issues?q=is%3Aissue+is%3Aopen+label%3Awaiting-for%3Acontributor) section :) We are a small community so we need help to implement upgrades for new Minecraft versions, fixing bugs and expanding the project. We are always looking for motivated people to contribute. If you feel like it could be you, please have a look at the [issues](https://github.com/MCCTeam/Minecraft-Console-Client/issues?q=is%3Aissue+is%3Aopen+label%3Awaiting-for%3Acontributor) section :)