xabbo-scripts/Scripts/IceBallRunner.csx
Administrator 7a548130a3 Move all scripts into Scripts/ subfolder
Keeps the repo root clean - only README.md visible on landing page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 09:49:37 +01:00

389 lines
9.8 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
public struct Point : IEquatable<Point>
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) { X = x; Y = 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})";
}
int ballHandItemId = 2147418815;
string[] targetTileNameContains = { "ice skating patch", "snow-covered rocks" };
string passMessage = ":pass";
int passIntervalMs = 300;
int loopDelayMs = 80;
int moveIntervalMs = 200;
int rescanTilesMs = 3000;
int approachDistance = 1;
int escapeDurationMs = 15000;
Point escapeTileOverride = new Point(-1, -1);
bool enabled = true;
bool hasBall = false;
bool escapeMode = false;
DateTime escapeStart = DateTime.MinValue;
Point escapeTarget = new Point(-1, -1);
DateTime lastPass = DateTime.MinValue;
DateTime lastMove = DateTime.MinValue;
DateTime lastScan = DateTime.MinValue;
HashSet<Point> targetTiles = new HashSet<Point>();
HashSet<Point> walkableTiles = new HashSet<Point>();
Dictionary<Point, List<Point>> adj = new Dictionary<Point, List<Point>>();
Point[] dirs = { new Point(0, 1), new Point(0, -1), new Point(1, 0), new Point(-1, 0), new Point(1, 1), new Point(1, -1), new Point(-1, 1), new Point(-1, -1) };
bool floorPlanParsed = false;
void ParseFloorPlan()
{
if (floorPlanParsed) return;
try
{
dynamic floorPlan = FloorPlan;
if (floorPlan == null) return;
int width = floorPlan.Width;
int length = floorPlan.Length;
walkableTiles.Clear();
IReadOnlyList<int> tilesData = null;
string heightmapString = null;
try { tilesData = floorPlan.Tiles; } catch { }
try { heightmapString = floorPlan.Heightmap; } catch { }
if (heightmapString != null)
{
heightmapString = heightmapString.Replace("\r", "").Replace("\n", "");
for (int y = 0; y < length; y++)
{
for (int x = 0; x < width; x++)
{
if (heightmapString[y * width + x] != 'x')
walkableTiles.Add(new Point(x, y));
}
}
}
else if (tilesData != null)
{
for (int y = 0; y < length; y++)
{
for (int x = 0; x < width; x++)
{
int tileIndex = y * width + x;
if (tileIndex < tilesData.Count && tilesData[tileIndex] >= 0 && tilesData[tileIndex] < 250)
walkableTiles.Add(new Point(x, y));
}
}
}
BuildAdjacencyMap();
floorPlanParsed = true;
}
catch { }
}
void BuildAdjacencyMap()
{
adj.Clear();
foreach (var t in walkableTiles)
{
var n = new List<Point>();
foreach (var d in dirs)
{
var p = new Point(t.X + d.X, t.Y + d.Y);
if (walkableTiles.Contains(p)) n.Add(p);
}
adj[t] = n;
}
}
void ScanTargetTiles()
{
targetTiles.Clear();
if (FloorItems == null) return;
foreach (var item in FloorItems)
{
if (item == null || item.Location == null) continue;
string name = null;
try { name = item.GetName(); } catch { continue; }
if (string.IsNullOrEmpty(name)) continue;
string lower = name.ToLowerInvariant();
if (targetTileNameContains.Any(n => lower.Contains(n)))
targetTiles.Add(new Point(item.Location.X, item.Location.Y));
}
lastScan = DateTime.UtcNow;
}
Point GetMyPosition()
{
if (Self == null || Self.Location == null) return new Point(-1, -1);
return new Point(Self.Location.X, Self.Location.Y);
}
int Dist(Point a, Point b) => Math.Abs(a.X - b.X) + Math.Abs(a.Y - b.Y);
IEntity FindNearestTargetUser(Point me)
{
IEntity best = null;
int bestDist = int.MaxValue;
foreach (var user in Users)
{
if (user == null || user.Id == Self.Id || user.Location == null) continue;
var p = new Point(user.Location.X, user.Location.Y);
if (!targetTiles.Contains(p)) continue;
int d = Dist(me, p);
if (d < bestDist)
{
bestDist = d;
best = user;
}
}
return best;
}
Point FindClosestWalkable(Point target)
{
if (walkableTiles.Contains(target)) return target;
Point best = new Point(-1, -1);
int bestDist = int.MaxValue;
foreach (var t in walkableTiles)
{
int d = Dist(t, target);
if (d < bestDist)
{
bestDist = d;
best = t;
}
}
return bestDist == int.MaxValue ? target : best;
}
List<Point> FindPath(Point start, Point goal)
{
if (!walkableTiles.Contains(start) || !walkableTiles.Contains(goal))
return new List<Point>();
var queue = new Queue<Point>();
var visited = new HashSet<Point>();
var cameFrom = new Dictionary<Point, Point>();
queue.Enqueue(start);
visited.Add(start);
while (queue.Count > 0)
{
var cur = queue.Dequeue();
if (cur == goal) break;
if (!adj.TryGetValue(cur, out var neighbors)) continue;
foreach (var n in neighbors)
{
if (visited.Contains(n)) continue;
visited.Add(n);
cameFrom[n] = cur;
queue.Enqueue(n);
}
}
if (!visited.Contains(goal)) return new List<Point>();
var path = new List<Point>();
var node = goal;
while (node != start)
{
path.Add(node);
if (!cameFrom.TryGetValue(node, out var prev)) break;
node = prev;
}
path.Add(start);
path.Reverse();
return path;
}
void StepToward(Point goal)
{
if ((DateTime.UtcNow - lastMove).TotalMilliseconds < moveIntervalMs) return;
var me = GetMyPosition();
if (me.X < 0) return;
var realGoal = FindClosestWalkable(goal);
var path = FindPath(me, realGoal);
if (path.Count >= 2)
{
var next = path[1];
Move(next.X, next.Y);
lastMove = DateTime.UtcNow;
}
else if (walkableTiles.Contains(realGoal))
{
Move(realGoal.X, realGoal.Y);
lastMove = DateTime.UtcNow;
}
}
Point FindEscapeTile(Point me)
{
if (escapeTileOverride.X >= 0 && escapeTileOverride.Y >= 0 && walkableTiles.Contains(escapeTileOverride))
return escapeTileOverride;
var others = Users.Where(u => u != null && u.Id != Self.Id && u.Location != null)
.Select(u => new Point(u.Location.X, u.Location.Y))
.ToList();
if (others.Count == 0) return me;
Point best = me;
int bestScore = int.MinValue;
foreach (var t in walkableTiles)
{
int minDist = others.Min(o => Dist(t, o));
if (minDist > bestScore)
{
bestScore = minDist;
best = t;
}
}
return best;
}
void TryPass(IEntity target)
{
if (target == null) return;
if ((DateTime.UtcNow - lastPass).TotalMilliseconds < passIntervalMs) return;
var msg = passMessage.Replace("{name}", target.Name);
Talk(msg);
lastPass = DateTime.UtcNow;
}
void SetBallState(bool active)
{
if (active == hasBall) return;
hasBall = active;
if (hasBall)
{
escapeMode = false;
escapeTarget = new Point(-1, -1);
}
else
{
escapeMode = true;
escapeStart = DateTime.UtcNow;
escapeTarget = FindEscapeTile(GetMyPosition());
}
}
OnIntercept(In["CarryObject"], e =>
{
int userIndex = e.Packet.ReadInt();
int carrying = e.Packet.ReadInt();
if (Self != null && userIndex == Self.Index)
{
SetBallState(carrying == ballHandItemId);
}
});
OnEnteredRoom(e =>
{
floorPlanParsed = false;
targetTiles.Clear();
ScanTargetTiles();
});
OnIntercept(Out.Chat, e =>
{
string message = e.Packet.ReadString();
if (message.Equals(".icebot on", StringComparison.OrdinalIgnoreCase))
{
enabled = true;
e.Block();
Log("Ice bot enabled");
}
else if (message.Equals(".icebot off", StringComparison.OrdinalIgnoreCase))
{
enabled = false;
e.Block();
Log("Ice bot disabled");
}
else if (message.Equals(".icebot scan", StringComparison.OrdinalIgnoreCase))
{
e.Block();
ScanTargetTiles();
Log($"Target tiles: {targetTiles.Count}");
}
});
while (Run)
{
try
{
if (!enabled) { Delay(200); continue; }
if (Self == null || Self.Location == null) { Delay(200); continue; }
if (!floorPlanParsed) ParseFloorPlan();
if (!floorPlanParsed) { Delay(100); continue; }
if ((DateTime.UtcNow - lastScan).TotalMilliseconds > rescanTilesMs)
ScanTargetTiles();
var me = GetMyPosition();
if (me.X < 0) { Delay(loopDelayMs); continue; }
if (hasBall)
{
var target = FindNearestTargetUser(me);
if (target != null && target.Location != null)
{
var tp = new Point(target.Location.X, target.Location.Y);
if (Dist(me, tp) <= approachDistance)
TryPass(target);
else
StepToward(tp);
}
}
else if (escapeMode)
{
if ((DateTime.UtcNow - escapeStart).TotalMilliseconds > escapeDurationMs)
{
escapeMode = false;
}
else if (escapeTarget.X >= 0)
{
if (me != escapeTarget)
StepToward(escapeTarget);
}
}
}
catch { }
Delay(loopDelayMs);
}
Wait();