using System; using System.Collections.Generic; using System.Linq; using System.Globalization; using System.Threading; using System.Text.RegularExpressions; public struct ExcludedArea { public Point TopLeft { get; } public Point BottomRight { get; } public ExcludedArea(Point p1, Point p2) { TopLeft = new Point(Math.Min(p1.X, p2.X), Math.Min(p1.Y, p2.Y)); BottomRight = new Point(Math.Max(p1.X, p2.X), Math.Max(p1.Y, p2.Y)); } public bool Contains(Point p) => p.X >= TopLeft.X && p.X <= BottomRight.X && p.Y >= TopLeft.Y && p.Y <= BottomRight.Y; public override string ToString() => $"[({TopLeft.X},{TopLeft.Y}) to ({BottomRight.X},{BottomRight.Y})]"; } List excludedSpecificXYPositions = new List { // new Point(3, 5), }; List excludedAreas = new List { // new ExcludedArea(new Point(4, 1), new Point(6, 3)), }; public struct Point : IEquatable { public int X { get; } public int Y { get; } public Point(int x, int y) { X = x; Y = y; } public static implicit operator Point((int x, int y) tuple) => new Point(tuple.x, tuple.y); public bool Equals(Point other) => X == other.X && Y == other.Y; public override bool Equals(object obj) => obj is Point other && Equals(other); public override int GetHashCode() => HashCode.Combine(X, Y); public static bool operator ==(Point left, Point right) => left.Equals(right); public static bool operator !=(Point left, Point right) => !(left == right); public override string ToString() => $"({X},{Y})"; } public class Tile { public int X { get; set; } public int Y { get; set; } public double Z { get; set; } public Point XY => new Point(X, Y); public Tile(int x, int y, double z = 0.0) { X = x; Y = y; Z = z; } public Tile(Point p, double z = 0.0) : this(p.X, p.Y, z) { } } public class TrackedFurni { public long Id { get; set; } public string Name { get; set; } public Tile Location { get; set; } } public class WiredMovement { public int FromX { get; set; } public int FromY { get; set; } public int ToX { get; set; } public int ToY { get; set; } public string FromHeight { get; set; } public string ToHeight { get; set; } public int Id { get; set; } } public class MoveCandidate { public Point Point { get; set; } public double MinSafety { get; set; } public double ThreatPressure { get; set; } public double ProgressScore { get; set; } public double CoherenceScore { get; set; } } Log("started"); List dangerousFurniIdsToAvoid = new List { 877618098, 31212, 324234234 }; List dangerousFurniNamesToAvoid = new List { "Color Tile", "Rare Black Elephant Statue","Esfera Flutuante" }; HashSet walkableTiles = null; int roomWidth = 0; int roomLength = 0; bool floorPlanParsedSuccessfully = false; DateTime lastFloorPlanParseAttempt = DateTime.MinValue; Dictionary> AllTrackedFurnisGlobal = new Dictionary>(); Tile _myAvatarActualTargetTile = null; DateTime _lastMoveCommandSentTime = DateTime.MinValue; Point _lastMoveCommandSentToXY = default(Point); Point _lastMoveDirection = default(Point); TimeSpan _clientSideAnticipationWindow = TimeSpan.FromMilliseconds(250); Point _lastKnownActualPosition = default(Point); Point CurrentAnticipatedBotPosition { get { if (Self == null) return default(Point); if (_myAvatarActualTargetTile != null) return _myAvatarActualTargetTile.XY; if (!_lastMoveCommandSentToXY.Equals(default(Point)) && (DateTime.UtcNow - _lastMoveCommandSentTime) < _clientSideAnticipationWindow) return _lastMoveCommandSentToXY; if (Self.Location != null) return new Point(Self.Location.X, Self.Location.Y); return default(Point); } } void ExecuteMove(int x, int y) { Point currentPos = CurrentAnticipatedBotPosition; if(!currentPos.Equals(default(Point))) { _lastMoveDirection = new Point(x - currentPos.X, y - currentPos.Y); } Move(x,y); _lastMoveCommandSentToXY = new Point(x,y); _lastMoveCommandSentTime = DateTime.UtcNow; _myAvatarActualTargetTile = null; } void TryParseFloorPlan() { if ((DateTime.UtcNow - lastFloorPlanParseAttempt).TotalSeconds < 10 && floorPlanParsedSuccessfully) return; lastFloorPlanParseAttempt = DateTime.UtcNow; bool currentParseSuccess = false; dynamic currentFloorPlan = null; int tempRoomWidth = 0; int tempRoomLength = 0; HashSet tempWalkableTiles = null; try { currentFloorPlan = FloorPlan; } catch (Exception ex) { Log($"Error accessing FloorPlan: {ex.Message}"); floorPlanParsedSuccessfully = false; return; } if (currentFloorPlan == null) { Log("FloorPlan is null."); floorPlanParsedSuccessfully = false; return; } try { tempRoomWidth = currentFloorPlan.Width; tempRoomLength = currentFloorPlan.Length; if (tempRoomWidth <= 0 || tempRoomLength <= 0) { Log($"Invalid dimensions: W={tempRoomWidth}, L={tempRoomLength}"); floorPlanParsedSuccessfully = false; return; } tempWalkableTiles = new HashSet(); IReadOnlyList tilesData = null; string heightmapString = null; object tilesProperty = null; try { tilesProperty = currentFloorPlan.Tiles; } catch { } object heightmapProperty = null; try { heightmapProperty = currentFloorPlan.Heightmap; } catch { } if (tilesProperty is IReadOnlyList intTiles) tilesData = intTiles; else if (heightmapProperty is string hmString) { heightmapString = hmString.Replace("\r", "").Replace("\n", ""); if (heightmapString.Length != tempRoomWidth * tempRoomLength) { Log("Heightmap length mismatch."); floorPlanParsedSuccessfully = false; return; } } else { Log("No recognizable Tiles/Heightmap."); floorPlanParsedSuccessfully = false; return; } for (int y = 0; y < tempRoomLength; y++) { for (int x = 0; x < tempRoomWidth; x++) { bool isTileWalkable = false; if (tilesData != null) { int tileIndex = y * tempRoomWidth + x; if (tileIndex < tilesData.Count) isTileWalkable = tilesData[tileIndex] >= 0 && tilesData[tileIndex] < 250; } else if (heightmapString != null) { isTileWalkable = heightmapString[y * tempRoomWidth + x] != 'x'; } if(isTileWalkable) { tempWalkableTiles.Add(new Point(x,y)); } } } if (tempWalkableTiles != null && tempWalkableTiles.Any()) { int specificExclusionsCount = 0; foreach (var excludedPos in excludedSpecificXYPositions) { if (tempWalkableTiles.Remove(excludedPos)) specificExclusionsCount++; } if (specificExclusionsCount > 0) Log($"Excluded {specificExclusionsCount} specific tiles based on 'excludedSpecificXYPositions'."); int areaExclusionsCount = 0; foreach (var area in excludedAreas) { for (int ex = area.TopLeft.X; ex <= area.BottomRight.X; ex++) { for (int ey = area.TopLeft.Y; ey <= area.BottomRight.Y; ey++) { Point pointInArea = new Point(ex, ey); if (tempWalkableTiles.Remove(pointInArea)) areaExclusionsCount++; } } } if (areaExclusionsCount > 0) Log($"Excluded {areaExclusionsCount} tiles based on 'excludedAreas'."); } currentParseSuccess = true; } catch (Exception ex) { Log($"Error parsing FloorPlan: {ex.Message}"); currentParseSuccess = false; } if(currentParseSuccess) { walkableTiles = tempWalkableTiles; roomWidth = tempRoomWidth; roomLength = tempRoomLength; floorPlanParsedSuccessfully = true; Log($"FloorPlan parsed: {walkableTiles.Count} walkable tiles (after exclusions) in a {roomWidth}x{roomLength} area."); } else { walkableTiles = null; floorPlanParsedSuccessfully = false; } } void OnBotEnteredNewRoom() { Log("Entered new room. Wiping memory."); _myAvatarActualTargetTile = null; _lastMoveCommandSentToXY = default(Point); _lastMoveDirection = default(Point); _lastKnownActualPosition = default(Point); long currentRoomId = RoomId; if (!AllTrackedFurnisGlobal.ContainsKey(currentRoomId)) AllTrackedFurnisGlobal[currentRoomId] = new Dictionary(); AllTrackedFurnisGlobal[currentRoomId].Clear(); if (FloorItems != null) { foreach (var item in FloorItems) { if (item == null || item.Location == null) continue; try { if (!AllTrackedFurnisGlobal[currentRoomId].ContainsKey(item.Id)) { AllTrackedFurnisGlobal[currentRoomId].Add(item.Id, new TrackedFurni { Id = item.Id, Name = item.GetName(), Location = new Tile(item.Location.X, item.Location.Y, item.Location.Z) }); } } catch {} } } TryParseFloorPlan(); } void InterceptWiredMovements(dynamic e) { long currentRoomId = RoomId; if (!AllTrackedFurnisGlobal.ContainsKey(currentRoomId)) AllTrackedFurnisGlobal[currentRoomId] = new Dictionary(); var packet = e.Packet; int count = packet.ReadInt(); for (int i = 0; i < count; i++) { packet.ReadInt(); var movement = new WiredMovement { FromX = packet.ReadInt(), FromY = packet.ReadInt(), ToX = packet.ReadInt(), ToY = packet.ReadInt(), FromHeight = packet.ReadString(), ToHeight = packet.ReadString(), Id = packet.ReadInt() }; packet.ReadInt(); packet.ReadInt(); long furniLongId = movement.Id; if (double.TryParse(movement.ToHeight, NumberStyles.Any, CultureInfo.InvariantCulture, out double z)) { var newLocation = new Tile(movement.ToX, movement.ToY, z); if (AllTrackedFurnisGlobal[currentRoomId].TryGetValue(furniLongId, out TrackedFurni trackedFurni)) { trackedFurni.Location = newLocation; } else { AllTrackedFurnisGlobal[currentRoomId][furniLongId] = new TrackedFurni { Id = furniLongId, Location = newLocation }; } } } } Regex mvRegex = new Regex(@"/mv (\d+),(\d+),([\d\.]+)/"); void InterceptUserUpdate(dynamic e) { if(Self == null) return; var packet = e.Packet; int numUpdates = packet.ReadInt(); for(int i=0; i dangerZone) { if (!floorPlanParsedSuccessfully || walkableTiles == null) return default(Point); if (!dangerZone.Any()) { try { return walkableTiles.OrderBy(t => t.X).ThenBy(t=> t.Y).Skip(walkableTiles.Count/2).First(); } catch { return default(Point); } } var distanceMap = new Dictionary(); var queue = new Queue(dangerZone.Count); foreach (var dangerPos in dangerZone) { if (IsTileEffectivelyWalkable(dangerPos.X, dangerPos.Y)) { if (!distanceMap.ContainsKey(dangerPos)) { distanceMap[dangerPos] = 0; queue.Enqueue(dangerPos); } } } Point[] dirs = { (0, 1), (0, -1), (1, 0), (-1, 0), (1, 1), (1, -1), (-1, 1), (-1, -1) }; while(queue.Count > 0) { Point p = queue.Dequeue(); int currentDist = distanceMap[p]; foreach(var dir in dirs) { Point next = new Point(p.X + dir.X, p.Y + dir.Y); if (IsTileEffectivelyWalkable(next.X, next.Y) && !distanceMap.ContainsKey(next)) { distanceMap[next] = currentDist + 1; queue.Enqueue(next); } } } Point bestTile = default(Point); int maxDist = -1; var safestUnreachableTile = walkableTiles.FirstOrDefault(t => !distanceMap.ContainsKey(t)); if (!safestUnreachableTile.Equals(default(Point))) { return safestUnreachableTile; } foreach(var tile in walkableTiles) { if(distanceMap.TryGetValue(tile, out int dist)) { if(dist > maxDist) { maxDist = dist; bestTile = tile; } } } return bestTile; } Point FindBestImmediateStep(Point currentPos, Point lastPos, ICollection dangerZone, Point ultimateGoal) { var candidates = new List(); double initialDistToGoalSq = ultimateGoal.Equals(default(Point)) ? 0 : CalculateDistanceSq(currentPos, ultimateGoal); for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { Point candidatePoint = new Point(currentPos.X + dx, currentPos.Y + dy); if (!lastPos.Equals(default(Point)) && candidatePoint.Equals(lastPos)) continue; if (!IsTileEffectivelyWalkable(candidatePoint.X, candidatePoint.Y)) continue; double minSafetyDistSq = double.MaxValue; double threatPressure = 0; if (dangerZone.Any()) { foreach(var danger in dangerZone) { double distSq = CalculateDistanceSq(danger, candidatePoint); if (distSq < minSafetyDistSq) minSafetyDistSq = distSq; threatPressure += 1.0 / (distSq + 0.1); } } double progressScore = ultimateGoal.Equals(default(Point)) ? 0 : initialDistToGoalSq - CalculateDistanceSq(candidatePoint, ultimateGoal); double coherenceScore = (dx == _lastMoveDirection.X && dy == _lastMoveDirection.Y) ? 1.0 : 0.0; if(dx == 0 && dy == 0) coherenceScore = -99; candidates.Add(new MoveCandidate { Point = candidatePoint, MinSafety = minSafetyDistSq, ThreatPressure = threatPressure, ProgressScore = progressScore, CoherenceScore = coherenceScore }); } } if (!candidates.Any()) return currentPos; var bestMove = candidates .OrderByDescending(c => Math.Round(c.MinSafety)) .ThenBy(c => c.ThreatPressure) .ThenByDescending(c => c.ProgressScore) .ThenByDescending(c => c.CoherenceScore) .First(); return bestMove.Point; } OnEnteredRoom(e => OnBotEnteredNewRoom()); OnIntercept(In["WiredMovements"], e => InterceptWiredMovements(e)); OnIntercept(In["UserUpdate"], e => InterceptUserUpdate(e)); Point[] threatMoveDirs = { (0, 1), (0, -1), (1, 0), (-1, 0) }; while(Run) { try { if (!Run) break; Point currentSelfLocationXY = CurrentAnticipatedBotPosition; if (currentSelfLocationXY.Equals(default(Point))) { Delay(30); continue; } if (!floorPlanParsedSuccessfully) TryParseFloorPlan(); if (!floorPlanParsedSuccessfully) { Delay(50); continue; } var currentThreats = new HashSet(); long currentRoomId = RoomId; if (AllTrackedFurnisGlobal.TryGetValue(currentRoomId, out var currentRoomTrackedItems)) { foreach(var item in currentRoomTrackedItems.Values) { if (item?.Location != null && (dangerousFurniIdsToAvoid.Contains(item.Id) || (item.Name != null && dangerousFurniNamesToAvoid.Contains(item.Name)))) currentThreats.Add(item.Location.XY); } } foreach(string dangerousName in dangerousFurniNamesToAvoid) { try { var itemsByName = FloorItems.Named(dangerousName); if (itemsByName != null) { foreach(var item in itemsByName) { if (item?.Location != null) currentThreats.Add(new Point(item.Location.X, item.Location.Y)); } } } catch {} } Point lastPositionThisTick = _lastKnownActualPosition; _lastKnownActualPosition = currentSelfLocationXY; if (currentThreats.Any()) { var predictiveDangerZone = new HashSet(currentThreats); foreach (var threatPos in currentThreats) { foreach (var dir in threatMoveDirs) { predictiveDangerZone.Add(new Point(threatPos.X + dir.X, threatPos.Y + dir.Y)); } } Point ultimateGoal = FindSafestUltimateDestination(predictiveDangerZone); Point nextStep = FindBestImmediateStep(currentSelfLocationXY, lastPositionThisTick, predictiveDangerZone, ultimateGoal); if (!nextStep.Equals(currentSelfLocationXY)) { Log($"Target: {ultimateGoal}. Best step: {nextStep}"); ExecuteMove(nextStep.X, nextStep.Y); } else { _lastMoveDirection = default(Point); } } else { _lastMoveDirection = default(Point); } } catch (Exception ex) { Log($"LOOP ERROR: {ex.GetType().Name} - {ex.Message}"); } if (!Run) break; Delay(30); } Log("closed");