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>
class AutoFishing : ChatBot
{
private Entity fishingRod;
private Double fishingHookThreshold = 0.2;
private Location LastPos = new Location();
private DateTime CaughtTime = DateTime.Now;
private int fishCount = 0;
private bool inventoryEnabled;
private bool isFishing = false;
private int useItemCounter = 0;
private int castTimeout = 12;
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()
{
if (!GetEntityHandlingEnabled())
{
LogToConsoleTranslated("extra.entity_required");
LogToConsoleTranslated("general.bot_unload");
UnloadBot();
state = FishingState.WaitJoinGame;
}
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()
{
if (useItemCounter > 0)
lock (stateLock)
{
useItemCounter--;
if (useItemCounter <= 0)
switch (state)
{
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)
{
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");
fishingRod = entity;
fishingBobber = entity;
LastPos = entity.Location;
isFishing = true;
castTimeout = 24;
counter = 0;
state = FishingState.WaitingFishToBite;
}
}
}
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;
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)
{
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;
Double Dx = LastPos.X - Pos.X;
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)
// prevent triggering multiple time
if ((DateTime.Now - CaughtTime).TotalSeconds > 1)
{
if (Math.Abs(Dy) > fishingHookThreshold)
{
// caught
// prevent triggering multiple time
if ((DateTime.Now - CaughtTime).TotalSeconds > 1)
{
OnCaughtFish();
CaughtTime = DateTime.Now;
}
}
isFishing = false;
CaughtTime = DateTime.Now;
OnCaughtFish();
}
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)
{
fishingRod = null;
LastPos = new Location();
StopFishing();
fishingBobber = null;
LastPos = Location.Zero;
CaughtTime = DateTime.Now;
isFishing = false;
useItemCounter = 0;
return base.OnDisconnect(reason, message);
}
@ -118,43 +308,124 @@ namespace MinecraftClient.ChatBots
/// </summary>
public void OnCaughtFish()
{
LogToConsole(GetTimestamp() + ": " + Translations.Get("bot.autoFish.caught"));
// retract fishing rod
UseItemInHand();
if (inventoryEnabled)
++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)
{
if (!hasFishingRod())
{
LogToConsole(GetTimestamp() + ": " + Translations.Get("bot.autoFish.no_rod"));
return;
}
UseFishRod();
counter = 0;
state = FishingState.StartMove;
}
// thread-safe
useItemCounter = 8; // 800ms
}
/// <summary>
/// Check whether the player has a fishing rod in inventory
/// </summary>
/// <returns>TRUE if the player has a fishing rod</returns>
public bool hasFishingRod()
private void UpdateLocation(double[,] locationList)
{
if (!inventoryEnabled)
return false;
int start = 36;
int end = 44;
Inventory.Container container = GetPlayerInventory();
foreach (KeyValuePair<int, Item> a in container.Items)
if (curLocationIdx >= locationList.GetLength(0))
{
if (a.Key < start || a.Key > end)
continue;
if (a.Value.Type == ItemType.FishingRod)
return true;
curLocationIdx = Math.Max(0, locationList.GetLength(0) - 2);
moveDir = -1;
}
else if (curLocationIdx < 0)
{
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);
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))

View file

@ -643,5 +643,25 @@ namespace MinecraftClient.Mapping
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));
}
/// <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>
/// Try to merge a slot
/// </summary>

View file

@ -602,7 +602,7 @@ namespace MinecraftClient.Protocol.Handlers
bool lastVerifyResult = player.IsMessageChainLegal();
verifyResult = player.VerifyMessageHead(ref precedingSignature, ref headerSignature, ref bodyDigest);
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;
Ping = ping;
DisplayName = displayName;
lastMessageVerified = false;
if (timeStamp != null && publicKey != null && signature != null)
{
DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds((long)timeStamp);
@ -53,13 +54,13 @@ namespace MinecraftClient.Protocol
try
{
PublicKey = new PublicKey(publicKey, signature);
lastMessageVerified = true;
}
catch (System.Security.Cryptography.CryptographicException)
{
PublicKey = null;
}
}
lastMessageVerified = true;
precedingSignature = null;
}
@ -121,9 +122,15 @@ namespace MinecraftClient.Protocol
if (this.lastMessageVerified == false)
return false;
if (PublicKey == null || IsKeyExpired() || (this.precedingSignature != null && precedingSignature == null))
{
this.lastMessageVerified = false;
return false;
}
if (this.precedingSignature != null && !this.precedingSignature.SequenceEqual(precedingSignature!))
{
this.lastMessageVerified = false;
return false;
}
DateTimeOffset timeOffset = DateTimeOffset.FromUnixTimeMilliseconds(timestamp);
@ -150,9 +157,15 @@ namespace MinecraftClient.Protocol
if (this.lastMessageVerified == false)
return false;
if (PublicKey == null || IsKeyExpired() || (this.precedingSignature != null && precedingSignature == null))
{
this.lastMessageVerified = false;
return false;
}
if (this.precedingSignature != null && !this.precedingSignature.SequenceEqual(precedingSignature!))
{
this.lastMessageVerified = false;
return false;
}
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
enabled=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]
# 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_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,9 +449,17 @@ bot.autoDrop.no_mode=Cannot read drop mode from config. Using include mode.
bot.autoDrop.no_inventory=Cannot find inventory {0}!
# AutoFish
bot.autoFish.throw=Threw a fishing rod
bot.autoFish.caught=Caught a fish!
bot.autoFish.no_rod=No Fishing Rod on hand. Maybe broken?
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=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
bot.autoRelog.launch=Launching with {0} reconnection attempts

View file

@ -1012,6 +1012,16 @@ namespace MinecraftClient
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>
/// Get a Y-M-D h:m:s timestamp representing the current system date and time
/// </summary>
@ -1113,6 +1123,15 @@ namespace MinecraftClient
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>
/// Return the list of currently online players
/// </summary>
@ -1253,6 +1272,15 @@ namespace MinecraftClient
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>
/// Check inventory handling enable status
/// </summary>

View file

@ -203,6 +203,17 @@ namespace MinecraftClient
//Auto Fishing
public static bool AutoFishing_Enabled = 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
public static bool AutoEat_Enabled = false;
@ -711,6 +722,17 @@ namespace MinecraftClient
{
case "enabled": AutoFishing_Enabled = 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;
@ -863,9 +885,24 @@ namespace MinecraftClient
/// <returns>Float number</returns>
public static float str2float(string str)
{
float f;
if (float.TryParse(str.Trim(), out f))
return f;
if (float.TryParse(str.Trim(), out float num))
return num;
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
{
ConsoleIO.WriteLogLine(Translations.Get("error.setting.str2int", str));
@ -886,6 +923,46 @@ namespace MinecraftClient
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)]
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)
[![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>
@ -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.
## Discord
We now have a Discord server, click [here](https://discord.gg/sfBv4TtpC9) to join.
## 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 :)