Added Slab handling

Added Slab handling
This commit is contained in:
Anon 2023-05-20 13:29:58 +02:00 committed by GitHub
commit a4fb677566
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 683 additions and 239 deletions

View file

@ -6,10 +6,9 @@ namespace MinecraftClient.Commands
{
public class Sneak : Command
{
private bool sneaking = false;
public override string CmdName { get { return "sneak"; } }
public override string CmdUsage { get { return "sneak"; } }
public override string CmdDesc { get { return Translations.cmd_sneak_desc; } }
public override string CmdName => "sneak";
public override string CmdUsage => "sneak";
public override string CmdDesc => Translations.cmd_sneak_desc;
public override void RegisterCommand(CommandDispatcher<CmdResult> dispatcher)
{
@ -39,27 +38,22 @@ namespace MinecraftClient.Commands
private int DoSneak(CmdResult r)
{
McClient handler = CmdResult.currentHandler!;
if (sneaking)
var handler = CmdResult.currentHandler!;
if (handler.IsSneaking)
{
var result = handler.SendEntityAction(Protocol.EntityActionType.StopSneaking);
if (result)
sneaking = false;
if (result)
if (!handler.SendEntityAction(Protocol.EntityActionType.StopSneaking))
return r.SetAndReturn(CmdResult.Status.Fail);
handler.IsSneaking = false;
return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_sneak_off);
else
return r.SetAndReturn(CmdResult.Status.Fail);
}
else
{
var result = handler.SendEntityAction(Protocol.EntityActionType.StartSneaking);
if (result)
sneaking = true;
if (result)
if (!handler.SendEntityAction(Protocol.EntityActionType.StartSneaking))
return r.SetAndReturn(CmdResult.Status.Fail);
handler.IsSneaking = true;
return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_sneak_on);
else
return r.SetAndReturn(CmdResult.Status.Fail);
}
}
}
}

View file

@ -0,0 +1,382 @@
using MinecraftClient.Protocol.Handlers;
namespace MinecraftClient.Mapping;
public static class BlockExtension
{
public static bool IsTopSlab(this Block block, int protocolVersion)
{
if (protocolVersion >= Protocol18Handler.MC_1_19_4_Version)
{
switch (block.BlockId)
{
case 11018: // OakSlab
case 11024: // SpruceSlab
case 11030: // BirchSlab
case 11036: // JungleSlab
case 11042: // AcaciaSlab
case 11054: // DarkOakSlab
case 11060: // MangroveSlab
case 18510: // CrimsonSlab
case 18516: // WarpedSlab
case 11078: // StoneSlab
case 11108: // CobblestoneSlab
case 13948: // MossyCobblestoneSlab
case 11084: // SmoothStoneSlab
case 11120: // StoneBrickSlab
case 13936: // MossyStoneBrickSlab
case 13972: // GraniteSlab
case 13924: // PolishedGraniteSlab
case 13996: // DioriteSlab
case 13942: // PolishedDioriteSlab
case 13978: // AndesiteSlab
case 13990: // PolishedAndesiteSlab
case 22132: // CobbledDeepslateSlab
case 22543: // PolishedDeepslateSlab
case 23365: // DeepslateBrickSlab
case 22954: // DeepslateTileSlab
case 11114: // BrickSlab
case 11126: // MudBrickSlab
case 11090: // SandstoneSlab
case 13960: // SmoothSandstoneSlab
case 11096: // CutSandstoneSlab
case 11144: // RedSandstoneSlab
case 13930: // SmoothRedSandstoneSlab
case 11150: // CutRedSandstoneSlab
case 10562: // PrismarineSlab
case 10568: // PrismarineBrickSlab
case 10574: // DarkPrismarineSlab
case 11132: // NetherBrickSlab
case 13984: // RedNetherBrickSlab
case 19707: // BlackstoneSlab
case 20208: // PolishedBlackstoneSlab
case 19717: // PolishedBlackstoneBrickSlab
case 13954: // EndStoneBrickSlab
case 11156: // PurpurSlab
case 11138: // QuartzSlab
case 13966: // SmoothQuartzSlab
case 21510: // CutCopperSlab
case 21504: // ExposedCutCopperSlab
case 21498: // WeatheredCutCopperSlab
case 21492: // OxidizedCutCopperSlab
case 21862: // WaxedCutCopperSlab
case 21856: // WaxedExposedCutCopperSlab
case 21850: // WaxedWeatheredCutCopperSlab
case 21844: // WaxedOxidizedCutCopperSlab
return true;
}
}
else if (protocolVersion == Protocol18Handler.MC_1_19_3_Version)
{
switch (block.BlockId)
{
case 10686: // OakSlab
case 10692: // SpruceSlab
case 10698: // BirchSlab
case 10704: // JungleSlab
case 10710: // AcaciaSlab
case 10716: // DarkOakSlab
case 10722: // MangroveSlab
case 18041: // CrimsonSlab
case 18047: // WarpedSlab
case 10740: // StoneSlab
case 10770: // CobblestoneSlab
case 13479: // MossyCobblestoneSlab
case 10746: // SmoothStoneSlab
case 10782: // StoneBrickSlab
case 13467: // MossyStoneBrickSlab
case 13503: // GraniteSlab
case 13455: // PolishedGraniteSlab
case 13527: // DioriteSlab
case 13473: // PolishedDioriteSlab
case 13509: // AndesiteSlab
case 13521: // PolishedAndesiteSlab
case 21647: // CobbledDeepslateSlab
case 22058: // PolishedDeepslateSlab
case 22880: // DeepslateBrickSlab
case 22469: // DeepslateTileSlab
case 10776: // BrickSlab
case 10788: // MudBrickSlab
case 10752: // SandstoneSlab
case 13491: // SmoothSandstoneSlab
case 10758: // CutSandstoneSlab
case 10806: // RedSandstoneSlab
case 13461: // SmoothRedSandstoneSlab
case 10812: // CutRedSandstoneSlab
case 10230: // PrismarineSlab
case 10236: // PrismarineBrickSlab
case 10242: // DarkPrismarineSlab
case 10794: // NetherBrickSlab
case 13515: // RedNetherBrickSlab
case 19238: // BlackstoneSlab
case 19739: // PolishedBlackstoneSlab
case 19248: // PolishedBlackstoneBrickSlab
case 13485: // EndStoneBrickSlab
case 10818: // PurpurSlab
case 10800: // QuartzSlab
case 13497: // SmoothQuartzSlab
case 21041: // CutCopperSlab
case 21035: // ExposedCutCopperSlab
case 21029: // WeatheredCutCopperSlab
case 21023: // OxidizedCutCopperSlab
case 21393: // WaxedCutCopperSlab
case 21387: // WaxedExposedCutCopperSlab
case 21381: // WaxedWeatheredCutCopperSlab
case 21375: // WaxedOxidizedCutCopperSlab
return true;
}
}
else if (protocolVersion >= Protocol18Handler.MC_1_19_Version)
{
switch (block.BlockId)
{
case 19257: // CutCopperSlab
case 19251: // ExposedCutCopperSlab
case 19245: // WeatheredCutCopperSlab
case 19239: // OxidizedCutCopperSlab
case 19609: // WaxedCutCopperSlab
case 19603: // WaxedExposedCutCopperSlab
case 19597: // WaxedWeatheredCutCopperSlab
case 19591: // WaxedOxidizedCutCopperSlab
case 9042: // OakSlab
case 9048: // SpruceSlab
case 9054: // BirchSlab
case 9060: // JungleSlab
case 9066: // AcaciaSlab
case 9072: // DarkOakSlab
case 9078: // MangroveSlab
case 16257: // CrimsonSlab
case 16263: // WarpedSlab
case 9084: // StoneSlab
case 9090: // SmoothStoneSlab
case 9096: // SandstoneSlab
case 9102: // CutSandstoneSlab
case 9108: // PetrifiedOakSlab
case 9114: // CobblestoneSlab
case 9120: // BrickSlab
case 9126: // StoneBrickSlab
case 9132: // MudBrickSlab
case 9138: // NetherBrickSlab
case 9144: // QuartzSlab
case 9150: // RedSandstoneSlab
case 9156: // CutRedSandstoneSlab
case 9162: // PurpurSlab
case 8586: // PrismarineSlab
case 8592: // PrismarineBrickSlab
case 8598: // DarkPrismarineSlab
case 11671: // PolishedGraniteSlab
case 11677: // SmoothRedSandstoneSlab
case 11683: // MossyStoneBrickSlab
case 11689: // PolishedDioriteSlab
case 11695: // MossyCobblestoneSlab
case 11701: // EndStoneBrickSlab
case 11707: // SmoothSandstoneSlab
case 11713: // SmoothQuartzSlab
case 11719: // GraniteSlab
case 11725: // AndesiteSlab
case 11731: // RedNetherBrickSlab
case 11737: // PolishedAndesiteSlab
case 11743: // DioriteSlab
case 19863: // CobbledDeepslateSlab
case 20274: // PolishedDeepslateSlab
case 21096: // DeepslateBrickSlab
case 20685: // DeepslateTileSlab
case 17454: // BlackstoneSlab
case 17955: // PolishedBlackstoneSlab
case 17464: // PolishedBlackstoneBrickSlab
return true;
}
}
else if (protocolVersion >= Protocol18Handler.MC_1_17_Version)
{
switch (block.BlockId)
{
case 18163: // CutCopperSlab
case 18157: // ExposedCutCopperSlab
case 18151: // WeatheredCutCopperSlab
case 18145: // OxidizedCutCopperSlab
case 18515: // WaxedCutCopperSlab
case 18509: // WaxedExposedCutCopperSlab
case 18503: // WaxedWeatheredCutCopperSlab
case 18497: // WaxedOxidizedCutCopperSlab
case 8551: // OakSlab
case 8557: // SpruceSlab
case 8563: // BirchSlab
case 8569: // JungleSlab
case 8575: // AcaciaSlab
case 8581: // DarkOakSlab
case 15302: // CrimsonSlab
case 15308: // WarpedSlab
case 8587: // StoneSlab
case 8593: // SmoothStoneSlab
case 8599: // SandstoneSlab
case 8605: // CutSandstoneSlab
case 8611: // PetrifiedOakSlab
case 8617: // CobblestoneSlab
case 8623: // BrickSlab
case 8629: // StoneBrickSlab
case 8635: // NetherBrickSlab
case 8641: // QuartzSlab
case 8647: // RedSandstoneSlab
case 8653: // CutRedSandstoneSlab
case 8659: // PurpurSlab
case 8095: // PrismarineSlab
case 8101: // PrismarineBrickSlab
case 8107: // DarkPrismarineSlab
case 11040: // PolishedGraniteSlab
case 11046: // SmoothRedSandstoneSlab
case 11052: // MossyStoneBrickSlab
case 11058: // PolishedDioriteSlab
case 11064: // MossyCobblestoneSlab
case 11070: // EndStoneBrickSlab
case 11076: // SmoothSandstoneSlab
case 11082: // SmoothQuartzSlab
case 11088: // GraniteSlab
case 11094: // AndesiteSlab
case 11100: // RedNetherBrickSlab
case 11106: // PolishedAndesiteSlab
case 11112: // DioriteSlab
case 18768: // CobbledDeepslateSlab
case 19179: // PolishedDeepslateSlab
case 20001: // DeepslateBrickSlab
case 19590: // DeepslateTileSlab
case 16499: // BlackstoneSlab
case 17000: // PolishedBlackstoneSlab
case 16509: // PolishedBlackstoneBrickSlab
return true;
}
}
else if (protocolVersion >= Protocol18Handler.MC_1_16_Version)
{
switch (block.BlockId)
{
case 8305: // OakSlab
case 8311: // SpruceSlab
case 8317: // BirchSlab
case 8323: // JungleSlab
case 8329: // AcaciaSlab
case 8335: // DarkOakSlab
case 15056: // CrimsonSlab
case 15062: // WarpedSlab
case 8341: // StoneSlab
case 8347: // SmoothStoneSlab
case 8353: // SandstoneSlab
case 8359: // CutSandstoneSlab
case 8365: // PetrifiedOakSlab
case 8371: // CobblestoneSlab
case 8377: // BrickSlab
case 8383: // StoneBrickSlab
case 8389: // NetherBrickSlab
case 8395: // QuartzSlab
case 8401: // RedSandstoneSlab
case 8407: // CutRedSandstoneSlab
case 8413: // PurpurSlab
case 7849: // PrismarineSlab
case 7855: // PrismarineBrickSlab
case 7861: // DarkPrismarineSlab
case 10794: // PolishedGraniteSlab
case 10800: // SmoothRedSandstoneSlab
case 10806: // MossyStoneBrickSlab
case 10812: // PolishedDioriteSlab
case 10818: // MossyCobblestoneSlab
case 10824: // EndStoneBrickSlab
case 10830: // SmoothSandstoneSlab
case 10836: // SmoothQuartzSlab
case 10842: // GraniteSlab
case 10848: // AndesiteSlab
case 10854: // RedNetherBrickSlab
case 10860: // PolishedAndesiteSlab
case 10866: // DioriteSlab
case 16253: // BlackstoneSlab
case 16754: // PolishedBlackstoneSlab
case 16263: // PolishedBlackstoneBrickSlab
return true;
}
}
else if (protocolVersion >= Protocol18Handler.MC_1_15_Version)
{
switch (block.BlockId)
{
case 7765: // OakSlab
case 7771: // SpruceSlab
case 7777: // BirchSlab
case 7783: // JungleSlab
case 7789: // AcaciaSlab
case 7795: // DarkOakSlab
case 7801: // StoneSlab
case 7807: // SmoothStoneSlab
case 7813: // SandstoneSlab
case 7819: // CutSandstoneSlab
case 7825: // PetrifiedOakSlab
case 7831: // CobblestoneSlab
case 7837: // BrickSlab
case 7843: // StoneBrickSlab
case 7849: // NetherBrickSlab
case 7855: // QuartzSlab
case 7861: // RedSandstoneSlab
case 7867: // CutRedSandstoneSlab
case 7873: // PurpurSlab
case 7309: // PrismarineSlab
case 7321: // DarkPrismarineSlab
case 10254: // PolishedGraniteSlab
case 10260: // SmoothRedSandstoneSlab
case 10266: // MossyStoneBrickSlab
case 10272: // PolishedDioriteSlab
case 10278: // MossyCobblestoneSlab
case 10284: // EndStoneBrickSlab
case 10290: // SmoothSandstoneSlab
case 10296: // SmoothQuartzSlab
case 10302: // GraniteSlab
case 10308: // AndesiteSlab
case 10314: // RedNetherBrickSlab
case 10320: // PolishedAndesiteSlab
case 10326: // DioriteSlab
return true;
}
}
else if (protocolVersion >= Protocol18Handler.MC_1_14_Version)
{
switch (block.BlockId)
{
case 7765: // OakSlab
case 7771: // SpruceSlab
case 7777: // BirchSlab
case 7783: // JungleSlab
case 7789: // AcaciaSlab
case 7795: // DarkOakSlab
case 7801: // StoneSlab
case 7807: // SmoothStoneSlab
case 7813: // SandstoneSlab
case 7819: // CutSandstoneSlab
case 7825: // PetrifiedOakSlab
case 7831: // CobblestoneSlab
case 7837: // BrickSlab
case 7843: // StoneBrickSlab
case 7849: // NetherBrickSlab
case 7855: // QuartzSlab
case 7861: // RedSandstoneSlab
case 7867: // CutRedSandstoneSlab
case 7873: // PurpurSlab
case 7309: // PrismarineSlab
case 7315: // PrismarineBrickSlab
case 7321: // DarkPrismarineSlab
case 10254: // PolishedGraniteSlab
case 10260: // SmoothRedSandstoneSlab
case 10266: // MossyStoneBrickSlab
case 10272: // PolishedDioriteSlab
case 10278: // MossyCobblestoneSlab
case 10284: // EndStoneBrickSlab
case 10290: // SmoothSandstoneSlab
case 10296: // SmoothQuartzSlab
case 10302: // GraniteSlab
case 10308: // AndesiteSlab
case 10314: // RedNetherBrickSlab
case 10320: // PolishedAndesiteSlab
case 10326: // DioriteSlab
return true;
}
}
return false;
}
}

View file

@ -728,6 +728,7 @@
return false;
}
}
/// <summary>
/// Check if contact with the provided material can harm players
/// </summary>
@ -817,5 +818,74 @@
return false;
}
}
/// <summary>
/// Check if the provided material is a slab
/// </summary>
/// <param name="m">Material to test</param>
/// <returns>True if the material is a slab</returns>
public static bool IsSlab(this Material m)
{
switch (m)
{
case Material.AcaciaSlab:
case Material.AndesiteSlab:
case Material.BirchSlab:
case Material.BlackstoneSlab:
case Material.BrickSlab:
case Material.CobbledDeepslateSlab:
case Material.CobblestoneSlab:
case Material.CrimsonSlab:
case Material.CutCopperSlab:
case Material.CutRedSandstoneSlab:
case Material.CutSandstoneSlab:
case Material.DarkOakSlab:
case Material.DarkPrismarineSlab:
case Material.DeepslateBrickSlab:
case Material.DeepslateTileSlab:
case Material.DioriteSlab:
case Material.EndStoneBrickSlab:
case Material.ExposedCutCopperSlab:
case Material.GraniteSlab:
case Material.JungleSlab:
case Material.MangroveSlab:
case Material.MossyCobblestoneSlab:
case Material.MossyStoneBrickSlab:
case Material.MudBrickSlab:
case Material.NetherBrickSlab:
case Material.OakSlab:
case Material.OxidizedCutCopperSlab:
case Material.PetrifiedOakSlab:
case Material.PolishedAndesiteSlab:
case Material.PolishedBlackstoneBrickSlab:
case Material.PolishedBlackstoneSlab:
case Material.PolishedDeepslateSlab:
case Material.PolishedDioriteSlab:
case Material.PolishedGraniteSlab:
case Material.PrismarineBrickSlab:
case Material.PrismarineSlab:
case Material.PurpurSlab:
case Material.QuartzSlab:
case Material.RedNetherBrickSlab:
case Material.RedSandstoneSlab:
case Material.SandstoneSlab:
case Material.SmoothQuartzSlab:
case Material.SmoothRedSandstoneSlab:
case Material.SmoothSandstoneSlab:
case Material.SmoothStoneSlab:
case Material.SpruceSlab:
case Material.StoneBrickSlab:
case Material.StoneSlab:
case Material.WarpedSlab:
case Material.WaxedCutCopperSlab:
case Material.WaxedExposedCutCopperSlab:
case Material.WaxedOxidizedCutCopperSlab:
case Material.WaxedWeatheredCutCopperSlab:
case Material.WeatheredCutCopperSlab:
return true;
default:
return false;
}
}
}
}

View file

@ -30,15 +30,17 @@ namespace MinecraftClient.Mapping
belowFoots = location;
belowFoots.Y = Math.Truncate(location.Y);
}
if (!IsOnGround(world, location) && !IsSwimming(world, location))
{
while (!IsOnGround(world, belowFoots) && belowFoots.Y >= 1 + World.GetDimension().minY)
belowFoots = Move(belowFoots, Direction.Down);
location = Move2Steps(location, belowFoots, ref motionY, true).Dequeue();
}
else if (!(world.GetBlock(onFoots).Type.IsSolid()))
else if (!world.GetBlock(onFoots).Type.IsSolid())
location = Move2Steps(location, onFoots, ref motionY, true).Dequeue();
}
return location;
}
@ -46,10 +48,11 @@ namespace MinecraftClient.Mapping
/// Return a list of possible moves for the player
/// </summary>
/// <param name="world">World the player is currently located in</param>
/// <param name="location">Location the player is currently at</param>
/// <param name="originLocation">Location the player is currently at</param>
/// <param name="allowUnsafe">Allow possible but unsafe locations</param>
/// <returns>A list of new locations the player can move to</returns>
public static IEnumerable<Location> GetAvailableMoves(World world, Location originLocation, bool allowUnsafe = false)
public static IEnumerable<Location> GetAvailableMoves(World world, Location originLocation,
bool allowUnsafe = false)
{
Location location = originLocation.ToCenter();
List<Location> availableMoves = new();
@ -65,10 +68,12 @@ namespace MinecraftClient.Mapping
else
{
foreach (Direction dir in new[] { Direction.East, Direction.West, Direction.North, Direction.South })
if (CanMove(world, location, dir) && IsOnGround(world, Move(location, dir)) && (allowUnsafe || IsSafe(world, Move(location, dir))))
if (CanMove(world, location, dir) && IsOnGround(world, Move(location, dir)) &&
(allowUnsafe || IsSafe(world, Move(location, dir))))
availableMoves.Add(Move(location, dir));
availableMoves.Add(Move(location, Direction.Down));
}
return availableMoves;
}
@ -85,7 +90,8 @@ namespace MinecraftClient.Mapping
/// <param name="falling">Specify if performing falling steps</param>
/// <param name="stepsByBlock">Amount of steps by block</param>
/// <returns>A list of locations corresponding to the requested steps</returns>
public static Queue<Location> Move2Steps(Location start, Location goal, ref double motionY, bool falling = false, int stepsByBlock = 8)
public static Queue<Location> Move2Steps(Location start, Location goal, ref double motionY,
bool falling = false, int stepsByBlock = 8)
{
if (stepsByBlock <= 0)
stepsByBlock = 1;
@ -93,17 +99,17 @@ namespace MinecraftClient.Mapping
if (falling)
{
//Use MC-Like falling algorithm
double Y = start.Y;
double y = start.Y;
Queue<Location> fallSteps = new();
fallSteps.Enqueue(start);
double motionPrev = motionY;
motionY -= 0.08D;
motionY *= 0.9800000190734863D;
Y += motionY;
if (Y < goal.Y)
y += motionY;
if (y < goal.Y)
return new Queue<Location>(new[] { goal });
else
return new Queue<Location>(new[] { new Location(start.X, Y, start.Z) });
return new Queue<Location>(new[] { new Location(start.X, y, start.Z) });
}
else
{
@ -132,6 +138,7 @@ namespace MinecraftClient.Mapping
/// Based on the A* pathfinding algorithm described on Wikipedia
/// </remarks>
/// <see href="https://en.wikipedia.org/wiki/A*_search_algorithm#Pseudocode"/>
/// <param name="world">World</param>
/// <param name="start">Start location</param>
/// <param name="goal">Destination location</param>
/// <param name="allowUnsafe">Allow possible but unsafe locations</param>
@ -140,16 +147,19 @@ namespace MinecraftClient.Mapping
/// <param name="timeout">How long to wait before stopping computation</param>
/// <remarks>When location is unreachable, computation will reach timeout, then optionally fallback to a close location within maxOffset</remarks>
/// <returns>A list of locations, or null if calculation failed</returns>
public static Queue<Location>? CalculatePath(World world, Location start, Location goal, bool allowUnsafe, int maxOffset, int minOffset, TimeSpan timeout)
public static Queue<Location>? CalculatePath(World world, Location start, Location goal, bool allowUnsafe,
int maxOffset, int minOffset, TimeSpan timeout)
{
CancellationTokenSource cts = new();
Task<Queue<Location>?> pathfindingTask = Task.Factory.StartNew(() => Movement.CalculatePath(world, start, goal, allowUnsafe, maxOffset, minOffset, cts.Token));
Task<Queue<Location>?> pathfindingTask = Task.Factory.StartNew(() =>
CalculatePath(world, start, goal, allowUnsafe, maxOffset, minOffset, cts.Token));
pathfindingTask.Wait(timeout);
if (!pathfindingTask.IsCompleted)
{
cts.Cancel();
pathfindingTask.Wait();
}
return pathfindingTask.Result;
}
@ -160,6 +170,7 @@ namespace MinecraftClient.Mapping
/// Based on the A* pathfinding algorithm described on Wikipedia
/// </remarks>
/// <see href="https://en.wikipedia.org/wiki/A*_search_algorithm#Pseudocode"/>
/// <param name="world">World</param>
/// <param name="start">Start location</param>
/// <param name="goal">Destination location</param>
/// <param name="allowUnsafe">Allow possible but unsafe locations</param>
@ -167,7 +178,8 @@ namespace MinecraftClient.Mapping
/// <param name="minOffset">Do not get closer of destination than specified distance</param>
/// <param name="ct">Token for stopping computation after a certain time</param>
/// <returns>A list of locations, or null if calculation failed</returns>
public static Queue<Location>? CalculatePath(World world, Location start, Location goal, bool allowUnsafe, int maxOffset, int minOffset, CancellationToken ct)
public static Queue<Location>? CalculatePath(World world, Location start, Location goal, bool allowUnsafe,
int maxOffset, int minOffset, CancellationToken ct)
{
// This is a bad configuration
if (minOffset > maxOffset)
@ -181,12 +193,10 @@ namespace MinecraftClient.Mapping
minOffset *= minOffset;
maxOffset *= maxOffset;
///---///
// Prepare variables and datastructures for A*
///---///
// Dictionary that contains the relation between all coordinates and resolves the final path
Dictionary<Location, Location> CameFrom = new();
Dictionary<Location, Location> cameFrom = new();
// Create a Binary Heap for all open positions => Allows fast access to Nodes with lowest scores
BinaryHeap openSet = new();
// Dictionary to keep track of the G-Score of every location
@ -197,9 +207,7 @@ namespace MinecraftClient.Mapping
gScoreDict[startLower] = 0;
BinaryHeap.Node? current = null;
///---///
// Start of A*
///---///
// Execute while we have nodes to process and we are not cancelled
while (openSet.Count() > 0 && !ct.IsCancellationRequested)
@ -209,10 +217,9 @@ namespace MinecraftClient.Mapping
current = openSet.GetRootLocation();
// Return if goal found and no maxOffset was given OR current node is between minOffset and maxOffset
if ((current.Location == goalLower && maxOffset <= 0) || (maxOffset > 0 && current.H_score >= minOffset && current.H_score <= maxOffset))
{
return ReconstructPath(CameFrom, current.Location, start, goal);
}
if ((current.Location == goalLower && maxOffset <= 0) ||
(maxOffset > 0 && current.HScore >= minOffset && current.HScore <= maxOffset))
return ReconstructPath(cameFrom, current.Location, start, goal);
// Discover neighbored blocks
foreach (Location neighbor in GetAvailableMoves(world, current.Location, allowUnsafe))
@ -221,14 +228,15 @@ namespace MinecraftClient.Mapping
if (ct.IsCancellationRequested)
break;
// tentative_gScore is the distance from start to the neighbor through current
int tentativeGScore = current.G_score + (int)current.Location.DistanceSquared(neighbor);
// tentative_GScore is the distance from start to the neighbor through current
int tentativeGScore = current.GScore + (int)current.Location.DistanceSquared(neighbor);
// If the neighbor is not in the gScoreDict OR its current tentativeGScore is lower than the previously saved one:
if (!gScoreDict.ContainsKey(neighbor) || (gScoreDict.ContainsKey(neighbor) && tentativeGScore < gScoreDict[neighbor]))
// If the neighbor is not in the GScoreDict OR its current tentativeGScore is lower than the previously saved one:
if (!gScoreDict.ContainsKey(neighbor) ||
(gScoreDict.ContainsKey(neighbor) && tentativeGScore < gScoreDict[neighbor]))
{
// Save the new relation between the neighbored block and the current one
CameFrom[neighbor] = current.Location;
cameFrom[neighbor] = current.Location;
gScoreDict[neighbor] = tentativeGScore;
// If this location is not already included in the Binary Heap: save it
@ -238,47 +246,51 @@ namespace MinecraftClient.Mapping
}
}
//// Goal could not be reached. Set the path to the closest location if close enough
if (current != null && openSet.MinH_ScoreNode != null && (maxOffset == int.MaxValue || openSet.MinH_ScoreNode.H_score <= maxOffset))
return ReconstructPath(CameFrom, openSet.MinH_ScoreNode.Location, start, goal);
else
// Goal could not be reached. Set the path to the closest location if close enough
if (current != null && openSet.MinHScoreNode != null &&
(maxOffset == int.MaxValue || openSet.MinHScoreNode.HScore <= maxOffset))
return ReconstructPath(cameFrom, openSet.MinHScoreNode.Location, start, goal);
return null;
}
/// <summary>
/// Helper function for CalculatePath(). Backtrack from goal to start to reconstruct a step-by-step path.
/// </summary>
/// <param name="Came_From">The collection of Locations that leads back to the start</param>
/// <param name="cameFrom">The collection of Locations that leads back to the start</param>
/// <param name="current">Endpoint of our later walk</param>
/// <param name="start">Start location</param>
/// <param name="end">End location</param>
/// <returns>the path that leads to current from the start position</returns>
private static Queue<Location> ReconstructPath(Dictionary<Location, Location> Came_From, Location current, Location start, Location end)
private static Queue<Location> ReconstructPath(Dictionary<Location, Location> cameFrom, Location current,
Location start, Location end)
{
int midPathCnt = 0;
List<Location> total_path = new();
List<Location> totalPath = new();
// Move from the center of the block to the final position
if (current != end && current == end.ToFloor())
total_path.Add(end);
totalPath.Add(end);
// Generate intermediate paths
total_path.Add(current.ToCenter());
while (Came_From.ContainsKey(current))
totalPath.Add(current.ToCenter());
while (cameFrom.ContainsKey(current))
{
++midPathCnt;
current = Came_From[current];
total_path.Add(current.ToCenter());
current = cameFrom[current];
totalPath.Add(current.ToCenter());
}
if (midPathCnt <= 2 && start.DistanceSquared(end) < 2.0)
return new Queue<Location>(new Location[] { end });
return new Queue<Location>(new[] { end });
else
{
// Move to the center of the block first
if (current != start && current == start.ToFloor())
total_path.Add(start.ToCenter());
totalPath.Add(start.ToCenter());
total_path.Reverse();
return new Queue<Location>(total_path);
totalPath.Reverse();
return new Queue<Location>(totalPath);
}
}
@ -297,62 +309,70 @@ namespace MinecraftClient.Mapping
public class Node
{
// Distance to start
public int G_score;
public int GScore;
// Distance to Goal
public int H_score;
public int F_score { get { return H_score + G_score; } }
public int HScore;
public int FScore
{
get { return HScore + GScore; }
}
public Location Location;
public Node(int g_score, int h_score, Location loc)
public Node(int gScore, int hScore, Location loc)
{
G_score = g_score;
H_score = h_score;
this.GScore = gScore;
this.HScore = hScore;
Location = loc;
}
}
// List which contains all nodes in form of a Binary Heap
private readonly List<Node> heapList;
// Hashset for quick checks of locations included in the heap
private readonly HashSet<Location> locationList;
public Node? MinH_ScoreNode;
public Node? MinHScoreNode;
public BinaryHeap()
{
heapList = new List<Node>();
locationList = new HashSet<Location>();
MinH_ScoreNode = null;
MinHScoreNode = null;
}
/// <summary>
/// Insert a new location in the heap
/// </summary>
/// <param name="newG_Score">G-Score of the location</param>
/// <param name="newH_Score">H-Score of the location</param>
/// <param name="newGScore">G-Score of the location</param>
/// <param name="newHScore">H-Score of the location</param>
/// <param name="loc">The location</param>
public void Insert(int newG_Score, int newH_Score, Location loc)
public void Insert(int newGScore, int newHScore, Location loc)
{
// Begin at the end of the list
int i = heapList.Count;
// Temporarily save the node created with the parameters to allow comparisons
Node newNode = new(newG_Score, newH_Score, loc);
Node newNode = new(newGScore, newHScore, loc);
// Add new note to the end of the list
heapList.Add(newNode);
locationList.Add(loc);
// Save node with the smallest H-Score => Distance to goal
if (MinH_ScoreNode == null || newNode.H_score < MinH_ScoreNode.H_score)
MinH_ScoreNode = newNode;
if (MinHScoreNode == null || newNode.HScore < MinHScoreNode.HScore)
MinHScoreNode = newNode;
if (i == 0)
return;
// There is no need of sorting for one node.
if (i > 0)
{
/// Go up the heap from child to parent and move parent down...
// Go up the heap from child to parent and move parent down...
// while we are not looking at the root node AND the new node has better attributes than the parent node ((i - 1) / 2)
while (i > 0 && FirstNodeBetter(newNode /* Current Child */, heapList[(i - 1) / 2] /* Coresponding Parent */))
while (i > 0 && FirstNodeBetter(newNode /* Current Child */,
heapList[(i - 1) / 2] /* Corresponding Parent */))
{
// Move parent down and replace current child -> New free space is created
heapList[i] = heapList[(i - 1) / 2];
@ -360,11 +380,10 @@ namespace MinecraftClient.Mapping
i = (i - 1) / 2;
}
/// Nodes were moved down at position I there is now a free space at the correct position for our new node:
// Nodes were moved down at position I there is now a free space at the correct position for our new node:
// Insert new node in position
heapList[i] = newNode;
}
}
/// <summary>
/// Obtain the root which represents the node the the best attributes currently
@ -375,16 +394,14 @@ namespace MinecraftClient.Mapping
{
// The heap is empty. There is nothing to return.
if (heapList.Count == 0)
{
throw new InvalidOperationException("The heap is empty.");
}
// Save the root node
Node rootNode = heapList[0];
var rootNode = heapList[0];
locationList.Remove(rootNode.Location);
// Temporarirly store the last item's value.
Node lastNode = heapList[^1];
var lastNode = heapList[^1];
// Remove the last value.
heapList.RemoveAt(heapList.Count - 1);
@ -392,17 +409,18 @@ namespace MinecraftClient.Mapping
if (heapList.Count > 0)
{
// Start at the first index.
int currentParentPos = 0;
var currentParentPos = 0;
/// Go through the heap from root to bottom...
// Go through the heap from root to bottom...
// Continue until the halfway point of the heap.
while (currentParentPos < heapList.Count / 2)
{
// Select the left child of the current parent
int currentChildPos = (2 * currentParentPos) + 1;
var currentChildPos = (2 * currentParentPos) + 1;
// If the currently selected child is not the last entry of the list AND right child has better attributes
if ((currentChildPos < heapList.Count - 1) && FirstNodeBetter(heapList[currentChildPos + 1], heapList[currentChildPos]))
if ((currentChildPos < heapList.Count - 1) && FirstNodeBetter(heapList[currentChildPos + 1],
heapList[currentChildPos]))
{
// Select the right child
currentChildPos++;
@ -411,15 +429,14 @@ namespace MinecraftClient.Mapping
// If the last item is smaller than both siblings at the
// current height, break.
if (FirstNodeBetter(lastNode, heapList[currentChildPos]))
{
break;
}
// Move the item at index j up one level.
heapList[currentParentPos] = heapList[currentChildPos];
// Move index i to the appropriate branch.
currentParentPos = currentChildPos;
}
// Insert the last node into the currently free position
heapList[currentParentPos] = lastNode;
}
@ -432,13 +449,13 @@ namespace MinecraftClient.Mapping
/// </summary>
/// <param name="firstNode">First node to compare</param>
/// <param name="secondNode">Second node to compare</param>
/// <returns>True if the first node has a more promissing position to the goal than the second</returns>
/// <returns>True if the first node has a more promising position to the goal than the second</returns>
private static bool FirstNodeBetter(Node firstNode, Node secondNode)
{
// Is the F_score smaller?
return (firstNode.F_score < secondNode.F_score) ||
// If F_score is equal, evaluate the h-score
(firstNode.F_score == secondNode.F_score && firstNode.H_score < secondNode.H_score);
// Is the FScore smaller?
return (firstNode.FScore < secondNode.FScore) ||
// If FScore is equal, evaluate the h-score
(firstNode.FScore == secondNode.FScore && firstNode.HScore < secondNode.HScore);
}
/// <summary>
@ -478,120 +495,63 @@ namespace MinecraftClient.Mapping
return true; // avoid moving downward in a not loaded chunk
Location down = Move(location, Direction.Down);
Material currentMaterial = world.GetBlock(down).Type;
bool result = currentMaterial.IsSolid()
var result = currentMaterial.IsSolid()
|| currentMaterial == Material.TwistingVines || currentMaterial == Material.TwistingVinesPlant
|| currentMaterial == Material.WeepingVines || currentMaterial == Material.WeepingVinesPlant
|| currentMaterial == Material.Vine;
bool northCheck = 1 + Math.Floor(down.Z) - down.Z > 0.7;
bool eastCheck = down.X - Math.Floor(down.X) > 0.7;
bool southCheck = down.Z - Math.Floor(down.Z) > 0.7;
bool westCheck = 1 + Math.Floor(down.X) - down.X > 0.7;
var northCheck = 1 + Math.Floor(down.Z) - down.Z > 0.7;
var eastCheck = down.X - Math.Floor(down.X) > 0.7;
var southCheck = down.Z - Math.Floor(down.Z) > 0.7;
var westCheck = 1 + Math.Floor(down.X) - down.X > 0.7;
if (!result && northCheck)
{
Location locationDownNorth = Move(down, Direction.North);
result |= world.GetBlock(locationDownNorth).Type.IsSolid()
|| world.GetBlock(locationDownNorth).Type == Material.TwistingVines
|| world.GetBlock(locationDownNorth).Type == Material.TwistingVinesPlant
|| world.GetBlock(locationDownNorth).Type == Material.WeepingVines
|| world.GetBlock(locationDownNorth).Type == Material.WeepingVinesPlant
|| world.GetBlock(locationDownNorth).Type == Material.Vine;
}
result |= IsSolidOrVine(world, Move(down, Direction.North));
if (!result && northCheck && eastCheck)
{
Location locationDownNorthEast = Move(down, Direction.NorthEast);
result |= world.GetBlock(locationDownNorthEast).Type.IsSolid()
|| world.GetBlock(locationDownNorthEast).Type == Material.TwistingVines
|| world.GetBlock(locationDownNorthEast).Type == Material.TwistingVinesPlant
|| world.GetBlock(locationDownNorthEast).Type == Material.WeepingVines
|| world.GetBlock(locationDownNorthEast).Type == Material.WeepingVinesPlant
|| world.GetBlock(locationDownNorthEast).Type == Material.Vine;
}
result |= IsSolidOrVine(world, Move(down, Direction.NorthEast));
if (!result && eastCheck)
{
Location locationDownEast = Move(down, Direction.East);
result |= world.GetBlock(locationDownEast).Type.IsSolid()
|| world.GetBlock(locationDownEast).Type == Material.TwistingVines
|| world.GetBlock(locationDownEast).Type == Material.TwistingVinesPlant
|| world.GetBlock(locationDownEast).Type == Material.WeepingVines
|| world.GetBlock(locationDownEast).Type == Material.WeepingVinesPlant
|| world.GetBlock(locationDownEast).Type == Material.Vine;
}
result |= IsSolidOrVine(world, Move(down, Direction.East));
if (!result && eastCheck && southCheck)
{
Location locationDownSouthEast = Move(down, Direction.SouthEast);
result |= world.GetBlock(locationDownSouthEast).Type.IsSolid()
|| world.GetBlock(locationDownSouthEast).Type == Material.TwistingVines
|| world.GetBlock(locationDownSouthEast).Type == Material.TwistingVinesPlant
|| world.GetBlock(locationDownSouthEast).Type == Material.WeepingVines
|| world.GetBlock(locationDownSouthEast).Type == Material.WeepingVinesPlant
|| world.GetBlock(locationDownSouthEast).Type == Material.Vine;
}
result |= IsSolidOrVine(world, Move(down, Direction.SouthEast));
if (!result && southCheck)
{
Location locationDownSouth = Move(down, Direction.South);
result |= world.GetBlock(locationDownSouth).Type.IsSolid()
|| world.GetBlock(locationDownSouth).Type == Material.TwistingVines
|| world.GetBlock(locationDownSouth).Type == Material.TwistingVinesPlant
|| world.GetBlock(locationDownSouth).Type == Material.WeepingVines
|| world.GetBlock(locationDownSouth).Type == Material.WeepingVinesPlant
|| world.GetBlock(locationDownSouth).Type == Material.Vine;
}
result |= IsSolidOrVine(world, Move(down, Direction.South));
if (!result && southCheck && westCheck)
{
Location locationDownSouthWest = Move(down, Direction.SouthWest);
result |= world.GetBlock(locationDownSouthWest).Type.IsSolid()
|| world.GetBlock(locationDownSouthWest).Type == Material.TwistingVines
|| world.GetBlock(locationDownSouthWest).Type == Material.TwistingVinesPlant
|| world.GetBlock(locationDownSouthWest).Type == Material.WeepingVines
|| world.GetBlock(locationDownSouthWest).Type == Material.WeepingVinesPlant
|| world.GetBlock(locationDownSouthWest).Type == Material.Vine;
}
result |= IsSolidOrVine(world, Move(down, Direction.SouthWest));
if (!result && westCheck)
{
Location locationDownWest = Move(down, Direction.West);
result |= world.GetBlock(locationDownWest).Type.IsSolid()
|| world.GetBlock(locationDownWest).Type == Material.TwistingVines
|| world.GetBlock(locationDownWest).Type == Material.TwistingVinesPlant
|| world.GetBlock(locationDownWest).Type == Material.WeepingVines
|| world.GetBlock(locationDownWest).Type == Material.WeepingVinesPlant
|| world.GetBlock(locationDownWest).Type == Material.Vine;
}
result |= IsSolidOrVine(world, Move(down, Direction.West));
if (!result && westCheck && northCheck)
{
Location locationDownNorthWest = Move(down, Direction.NorthWest);
result |= world.GetBlock(locationDownNorthWest).Type.IsSolid()
|| world.GetBlock(locationDownNorthWest).Type == Material.TwistingVines
|| world.GetBlock(locationDownNorthWest).Type == Material.TwistingVinesPlant
|| world.GetBlock(locationDownNorthWest).Type == Material.WeepingVines
|| world.GetBlock(locationDownNorthWest).Type == Material.WeepingVinesPlant
|| world.GetBlock(locationDownNorthWest).Type == Material.Vine;
}
result |= IsSolidOrVine(world, Move(down, Direction.NorthWest));
return result && (location.Y <= Math.Truncate(location.Y) + 0.0001);
}
private static bool IsSolidOrVine(World world, Location location)
{
var block = world.GetBlock(location);
return block.Type.IsSolid()
|| block.Type == Material.TwistingVines
|| block.Type == Material.TwistingVinesPlant
|| block.Type == Material.WeepingVines
|| block.Type == Material.WeepingVinesPlant
|| block.Type == Material.Vine;
}
/// <summary>
/// Check if the specified location implies swimming
/// </summary>
/// <param name="world">World for performing check</param>
/// <param name="location">Location to check</param>
/// <returns>True if the specified location implies swimming</returns>
public static bool IsSwimming(World world, Location location)
private static bool IsSwimming(World world, Location location)
{
return world.GetBlock(location).Type.IsLiquid();
}
@ -602,7 +562,7 @@ namespace MinecraftClient.Mapping
/// <param name="world">World for performing check</param>
/// <param name="location">Location to check</param>
/// <returns>True if the specified location can be climbed on</returns>
public static bool IsClimbing(World world, Location location)
private static bool IsClimbing(World world, Location location)
{
return world.GetBlock(location).Type.CanBeClimbedOn();
}
@ -613,7 +573,7 @@ namespace MinecraftClient.Mapping
/// <param name="world">World for performing check</param>
/// <param name="location">Location to check</param>
/// <returns>True if the destination location won't directly harm the player</returns>
public static bool IsSafe(World world, Location location)
private static bool IsSafe(World world, Location location)
{
return
//No block that can harm the player
@ -622,9 +582,12 @@ namespace MinecraftClient.Mapping
&& !world.GetBlock(Move(location, Direction.Down)).Type.CanHarmPlayers()
//No fall from a too high place
&& (world.GetBlock(Move(location, Direction.Down)).Type.IsSolid() || IsClimbing(world, Move(location, Direction.Down))
|| world.GetBlock(Move(location, Direction.Down, 2)).Type.IsSolid() || IsClimbing(world, Move(location, Direction.Down, 2))
|| world.GetBlock(Move(location, Direction.Down, 3)).Type.IsSolid() || IsClimbing(world, Move(location, Direction.Down, 3)))
&& (world.GetBlock(Move(location, Direction.Down)).Type.IsSolid() ||
IsClimbing(world, Move(location, Direction.Down))
|| world.GetBlock(Move(location, Direction.Down, 2)).Type.IsSolid() ||
IsClimbing(world, Move(location, Direction.Down, 2))
|| world.GetBlock(Move(location, Direction.Down, 3)).Type.IsSolid() ||
IsClimbing(world, Move(location, Direction.Down, 3)))
//Not an underwater location
&& !(world.GetBlock(Move(location, Direction.Up)).Type.IsLiquid());
@ -647,11 +610,12 @@ namespace MinecraftClient.Mapping
case Direction.Down:
return IsClimbing(world, Move(location, Direction.Down)) || !IsOnGround(world, location);
case Direction.Up:
bool nextTwoBlocks = !world.GetBlock(Move(Move(location, Direction.Up), Direction.Up)).Type.IsSolid();
bool nextTwoBlocks =
!world.GetBlock(Move(Move(location, Direction.Up), Direction.Up)).Type.IsSolid();
// Check if the current block can be climbed on
if (IsClimbing(world, location))
// Check if next block after the next one can be climbed uppon
// Check if next block after the next one can be climbed upon
return IsClimbing(world, Move(location, Direction.Up)) || nextTwoBlocks;
return (IsOnGround(world, location) || IsSwimming(world, location)) && nextTwoBlocks;
@ -665,13 +629,21 @@ namespace MinecraftClient.Mapping
// Move diagonal
case Direction.NorthEast:
return PlayerFitsHere(world, Move(location, Direction.North)) && PlayerFitsHere(world, Move(location, Direction.East)) && PlayerFitsHere(world, Move(location, direction));
return PlayerFitsHere(world, Move(location, Direction.North)) &&
PlayerFitsHere(world, Move(location, Direction.East)) &&
PlayerFitsHere(world, Move(location, direction));
case Direction.SouthEast:
return PlayerFitsHere(world, Move(location, Direction.South)) && PlayerFitsHere(world, Move(location, Direction.East)) && PlayerFitsHere(world, Move(location, direction));
return PlayerFitsHere(world, Move(location, Direction.South)) &&
PlayerFitsHere(world, Move(location, Direction.East)) &&
PlayerFitsHere(world, Move(location, direction));
case Direction.SouthWest:
return PlayerFitsHere(world, Move(location, Direction.South)) && PlayerFitsHere(world, Move(location, Direction.West)) && PlayerFitsHere(world, Move(location, direction));
return PlayerFitsHere(world, Move(location, Direction.South)) &&
PlayerFitsHere(world, Move(location, Direction.West)) &&
PlayerFitsHere(world, Move(location, direction));
case Direction.NorthWest:
return PlayerFitsHere(world, Move(location, Direction.North)) && PlayerFitsHere(world, Move(location, Direction.West)) && PlayerFitsHere(world, Move(location, direction));
return PlayerFitsHere(world, Move(location, Direction.North)) &&
PlayerFitsHere(world, Move(location, Direction.West)) &&
PlayerFitsHere(world, Move(location, direction));
default:
throw new ArgumentException("Unknown direction", nameof(direction));
@ -686,8 +658,16 @@ namespace MinecraftClient.Mapping
/// <returns>True if a player is able to stand in this location</returns>
public static bool PlayerFitsHere(World world, Location location)
{
return (IsClimbing(world, location) && IsClimbing(world, Move(location, Direction.Up)))
|| !world.GetBlock(location).Type.IsSolid() && !world.GetBlock(Move(location, Direction.Up)).Type.IsSolid();
var canClimb = IsClimbing(world, location) && IsClimbing(world, Move(location, Direction.Up));
var isNotSolid = !world.GetBlock(location).Type.IsSolid() &&
!world.GetBlock(Move(location, Direction.Up)).Type.IsSolid();
// Handle slabs
if (!isNotSolid && world.GetBlock(Move(location, Direction.Up))
.IsTopSlab(McClient.Instance!.GetProtocolVersion()))
isNotSolid = true;
return canClimb || isNotSolid;
}
/// <summary>
@ -707,39 +687,28 @@ namespace MinecraftClient.Mapping
/// </summary>
/// <param name="direction">Direction to move to</param>
/// <returns>A location delta for moving in that direction</returns>
public static Location Move(Direction direction)
private static Location Move(Direction direction)
{
switch (direction)
return direction switch
{
// Move vertical
case Direction.Down:
return new Location(0, -1, 0);
case Direction.Up:
return new Location(0, 1, 0);
Direction.Down => new Location(0, -1, 0),
Direction.Up => new Location(0, 1, 0),
// Move horizontal straight
case Direction.East:
return new Location(1, 0, 0);
case Direction.West:
return new Location(-1, 0, 0);
case Direction.South:
return new Location(0, 0, 1);
case Direction.North:
return new Location(0, 0, -1);
Direction.East => new Location(1, 0, 0),
Direction.West => new Location(-1, 0, 0),
Direction.South => new Location(0, 0, 1),
Direction.North => new Location(0, 0, -1),
// Move horizontal diagonal
case Direction.NorthEast:
return Move(Direction.North) + Move(Direction.East);
case Direction.SouthEast:
return Move(Direction.South) + Move(Direction.East);
case Direction.SouthWest:
return Move(Direction.South) + Move(Direction.West);
case Direction.NorthWest:
return Move(Direction.North) + Move(Direction.West);
Direction.NorthEast => Move(Direction.North) + Move(Direction.East),
Direction.SouthEast => Move(Direction.South) + Move(Direction.East),
Direction.SouthWest => Move(Direction.South) + Move(Direction.West),
Direction.NorthWest => Move(Direction.North) + Move(Direction.West),
default:
throw new ArgumentException("Unknown direction", nameof(direction));
}
_ => throw new ArgumentException("Unknown direction", nameof(direction))
};
}
/// <summary>
@ -751,7 +720,7 @@ namespace MinecraftClient.Mapping
/// <returns>Is loading complete</returns>
public static bool CheckChunkLoading(World world, Location start, Location dest)
{
ChunkColumn? chunkColumn = world.GetChunkColumn(dest);
var chunkColumn = world.GetChunkColumn(dest);
if (chunkColumn == null || chunkColumn.FullyLoaded == false)
return false;

View file

@ -9,6 +9,7 @@ using Brigadier.NET.Exceptions;
using MinecraftClient.ChatBots;
using MinecraftClient.CommandHandler;
using MinecraftClient.CommandHandler.Patch;
using MinecraftClient.Commands;
using MinecraftClient.Inventory;
using MinecraftClient.Logger;
using MinecraftClient.Mapping;
@ -98,6 +99,11 @@ namespace MinecraftClient
private int playerTotalExperience;
private byte CurrentSlot = 0;
// Sneaking
public bool IsSneaking { get; set; } = false;
private bool isUnderSlab = false;
private DateTime nextSneakingUpdate = DateTime.Now;
// Entity handling
private readonly Dictionary<int, Entity> entities = new();
@ -145,6 +151,8 @@ namespace MinecraftClient
public ILogger Log;
private static IMinecraftComHandler? instance;
public static IMinecraftComHandler? Instance => instance;
/// <summary>
/// Starts the main chat client, wich will login to the server using the MinecraftCom class.
/// </summary>
@ -157,6 +165,8 @@ namespace MinecraftClient
public McClient(SessionToken session, PlayerKeyPair? playerKeyPair, string server_ip, ushort port, int protocolversion, ForgeInfo? forgeInfo)
{
CmdResult.currentHandler = this;
instance = this;
terrainAndMovementsEnabled = Config.Main.Advanced.TerrainAndMovements;
inventoryHandlingEnabled = Config.Main.Advanced.InventoryHandling;
entityHandlingEnabled = Config.Main.Advanced.EntityHandling;
@ -328,6 +338,25 @@ namespace MinecraftClient
}
}
if (nextSneakingUpdate < DateTime.Now)
{
if (world.GetBlock(new Location(location.X, location.Y + 1, location.Z)).IsTopSlab(protocolversion) && !IsSneaking)
{
isUnderSlab = true;
SendEntityAction(EntityActionType.StartSneaking);
}
else
{
if (isUnderSlab && !IsSneaking)
{
isUnderSlab = false;
SendEntityAction(EntityActionType.StopSneaking);
}
}
nextSneakingUpdate = DateTime.Now.AddMilliseconds(300);
}
lock (chatQueue)
{
TrySendMessageToServer();