Added scripts from C:\Users\ploet\Desktop\Habbo\Xabbo Scripte: - 39 KI/Chatbot scripts (ChatGPT, Gemini, Grok, DeepSeek, Ollama) - Game solvers (Domino, Dodgeball, Obsidian Maze, IceBall) - Collision avoidance bots (5 versions) - Plant/breeding automation (12 scripts) - Trading tools, packet debuggers, room utilities - Navigation & teleport helpers Removed: 9 files (3 empty, 2 trivial, 4 cross-duplicates) Updated README with full categorized index of all 230+ scripts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
636 lines
16 KiB
C#
636 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
class Cell
|
|
{
|
|
public long Id;
|
|
public int X;
|
|
public int Y;
|
|
public int State;
|
|
public int Kind;
|
|
public double Z;
|
|
}
|
|
|
|
const int SOL_MIN_X = 4;
|
|
const int SOL_MAX_X = 9;
|
|
const int SOL_MIN_Y = 1;
|
|
const int SOL_MAX_Y = 8;
|
|
|
|
const int PLAY_MIN_X = 8;
|
|
const int PLAY_MAX_X = 13;
|
|
const int PLAY_MIN_Y = 14;
|
|
const int PLAY_MAX_Y = 21;
|
|
|
|
const int SPAWN_X = 13;
|
|
const int SPAWN_Y = 13;
|
|
const int SPAWN_WAIT_MS = 180000;
|
|
|
|
const int FORCED_CYCLE = 5;
|
|
const bool ASSUME_START_ALL_ZERO = true;
|
|
|
|
const int STEP_WAIT_MS = 420;
|
|
const int MOVE_COMMAND_INTERVAL_MS = 70;
|
|
const int MOVE_SETTLE_MS = 0;
|
|
const int PERIODIC_SYNC_EVERY_STEPS = 12;
|
|
const int PATH_BURST_MAX_STEPS = 10;
|
|
const int MAX_STEPS = 5000;
|
|
|
|
string K(int x, int y) => x + "," + y;
|
|
|
|
int GetKind(dynamic item)
|
|
{
|
|
try { return (int)item.Kind; }
|
|
catch { return -1; }
|
|
}
|
|
|
|
int GetState(dynamic item)
|
|
{
|
|
try { return int.Parse(item.State?.ToString() ?? "0"); }
|
|
catch { return 0; }
|
|
}
|
|
|
|
string GetNameSafe(dynamic item)
|
|
{
|
|
try
|
|
{
|
|
string n = item.GetName();
|
|
return string.IsNullOrWhiteSpace(n) ? "<unknown>" : n;
|
|
}
|
|
catch { return "<unknown>"; }
|
|
}
|
|
|
|
bool InRect(int x, int y, int minX, int maxX, int minY, int maxY)
|
|
{
|
|
return x >= minX && x <= maxX && y >= minY && y <= maxY;
|
|
}
|
|
|
|
bool InPlay(int x, int y)
|
|
{
|
|
return InRect(x, y, PLAY_MIN_X, PLAY_MAX_X, PLAY_MIN_Y, PLAY_MAX_Y);
|
|
}
|
|
|
|
void FullRoomScanLog()
|
|
{
|
|
var all = new List<dynamic>();
|
|
foreach (var it in FloorItems)
|
|
{
|
|
if (it == null) continue;
|
|
all.Add(it);
|
|
}
|
|
|
|
Log("=== Full Room Scan ===");
|
|
Log($"FloorItems total: {all.Count}");
|
|
|
|
var byKind = all.GroupBy(x => GetKind(x))
|
|
.Select(g => new {
|
|
Kind = g.Key,
|
|
Count = g.Count(),
|
|
States = string.Join(",", g.Select(x => GetState(x)).Distinct().OrderBy(x => x)),
|
|
Name = g.Select(x => GetNameSafe(x)).FirstOrDefault()
|
|
})
|
|
.OrderByDescending(x => x.Count)
|
|
.Take(25)
|
|
.ToList();
|
|
|
|
foreach (var k in byKind)
|
|
Log($"Kind {k.Kind} x{k.Count} states[{k.States}] name={k.Name}");
|
|
}
|
|
|
|
bool WaitForSpawn()
|
|
{
|
|
Log($"Waiting for round spawn on {SPAWN_X}:{SPAWN_Y}...");
|
|
int elapsed = 0;
|
|
while (elapsed < SPAWN_WAIT_MS)
|
|
{
|
|
if (Self != null && Self.Location != null && Self.Location.X == SPAWN_X && Self.Location.Y == SPAWN_Y)
|
|
{
|
|
Log("Spawn detected, starting solver.");
|
|
Delay(300);
|
|
return true;
|
|
}
|
|
Delay(200);
|
|
elapsed += 200;
|
|
}
|
|
Log("Spawn timeout. Starting anyway.");
|
|
return false;
|
|
}
|
|
|
|
List<Cell> CollectCellsInRect(int minX, int maxX, int minY, int maxY)
|
|
{
|
|
var raw = new List<Cell>();
|
|
foreach (var it in FloorItems)
|
|
{
|
|
if (it == null) continue;
|
|
int x = it.Location.X;
|
|
int y = it.Location.Y;
|
|
if (!InRect(x, y, minX, maxX, minY, maxY)) continue;
|
|
raw.Add(new Cell {
|
|
Id = it.Id,
|
|
X = x,
|
|
Y = y,
|
|
State = GetState(it),
|
|
Kind = GetKind(it),
|
|
Z = it.Location.Z
|
|
});
|
|
}
|
|
|
|
if (raw.Count == 0) return new List<Cell>();
|
|
|
|
int targetCount = (maxX - minX + 1) * (maxY - minY + 1);
|
|
|
|
var bestKind = raw.GroupBy(c => c.Kind)
|
|
.Select(g => new {
|
|
Kind = g.Key,
|
|
CoordCount = g.Select(c => K(c.X, c.Y)).Distinct().Count(),
|
|
Count = g.Count()
|
|
})
|
|
.OrderByDescending(x => x.CoordCount)
|
|
.ThenByDescending(x => x.Count)
|
|
.First();
|
|
|
|
var cellsOfKind = raw.Where(c => c.Kind == bestKind.Kind).ToList();
|
|
|
|
var bestPerCoord = new List<Cell>();
|
|
foreach (var g in cellsOfKind.GroupBy(c => K(c.X, c.Y)))
|
|
{
|
|
var top = g.OrderByDescending(c => c.Z).First();
|
|
bestPerCoord.Add(top);
|
|
}
|
|
|
|
Log($"Rect X[{minX}-{maxX}] Y[{minY}-{maxY}] -> kind {bestKind.Kind}, coords {bestPerCoord.Count}/{targetCount}");
|
|
return bestPerCoord;
|
|
}
|
|
|
|
Dictionary<string, Cell> IndexCells(List<Cell> cells)
|
|
{
|
|
var d = new Dictionary<string, Cell>();
|
|
foreach (var c in cells) d[K(c.X, c.Y)] = c;
|
|
return d;
|
|
}
|
|
|
|
Dictionary<long, int> ReadCurrentPlayStates(HashSet<long> ids)
|
|
{
|
|
var d = new Dictionary<long, int>();
|
|
foreach (var it in FloorItems)
|
|
{
|
|
if (it == null) continue;
|
|
long id = it.Id;
|
|
if (!ids.Contains(id)) continue;
|
|
d[id] = GetState(it);
|
|
}
|
|
return d;
|
|
}
|
|
|
|
int Need(int current, int target, int cycle)
|
|
{
|
|
int d = (target - current) % cycle;
|
|
if (d < 0) d += cycle;
|
|
return d;
|
|
}
|
|
|
|
int Objective(Dictionary<long, int> cur, Dictionary<long, int> target, int cycle)
|
|
{
|
|
int sum = 0;
|
|
foreach (var kv in target)
|
|
{
|
|
if (!cur.ContainsKey(kv.Key)) continue;
|
|
sum += Need(cur[kv.Key], kv.Value, cycle);
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
int HardObjectiveFromRoom(List<Cell> playCells, Dictionary<long, int> targetByPlayId, int cycle, int[] needs)
|
|
{
|
|
var ids = new HashSet<long>(targetByPlayId.Keys);
|
|
var cur = ReadCurrentPlayStates(ids);
|
|
for (int i = 0; i < playCells.Count; i++)
|
|
{
|
|
long id = playCells[i].Id;
|
|
int val = cur.ContainsKey(id) ? cur[id] : 0;
|
|
needs[i] = Need(val, targetByPlayId[id], cycle);
|
|
}
|
|
return needs.Sum();
|
|
}
|
|
|
|
int ApplyNeedStep(int[] needs, int idx, int cycle)
|
|
{
|
|
int d = needs[idx];
|
|
if (d > 0)
|
|
{
|
|
needs[idx] = d - 1;
|
|
return -1;
|
|
}
|
|
|
|
needs[idx] = cycle - 1;
|
|
return cycle - 1;
|
|
}
|
|
|
|
bool WaitUntilAt(int tx, int ty)
|
|
{
|
|
int elapsed = 0;
|
|
while (elapsed < STEP_WAIT_MS)
|
|
{
|
|
if (Self != null && Self.Location != null && Self.Location.X == tx && Self.Location.Y == ty)
|
|
return true;
|
|
Delay(40);
|
|
elapsed += 40;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
List<(int x, int y)> Neigh4(int x, int y)
|
|
{
|
|
var list = new List<(int, int)> {
|
|
(x + 1, y),
|
|
(x - 1, y),
|
|
(x, y + 1),
|
|
(x, y - 1),
|
|
(x + 1, y + 1),
|
|
(x + 1, y - 1),
|
|
(x - 1, y + 1),
|
|
(x - 1, y - 1)
|
|
};
|
|
return list.Where(p => InPlay(p.Item1, p.Item2)).ToList();
|
|
}
|
|
|
|
int Dist(int x1, int y1, int x2, int y2)
|
|
{
|
|
return Math.Abs(x1 - x2) + Math.Abs(y1 - y2);
|
|
}
|
|
|
|
int ReadSelfX(int fallback)
|
|
{
|
|
try { return Self.Location.X; }
|
|
catch { return fallback; }
|
|
}
|
|
|
|
int ReadSelfY(int fallback)
|
|
{
|
|
try { return Self.Location.Y; }
|
|
catch { return fallback; }
|
|
}
|
|
|
|
DateTime _lastMoveCmd = DateTime.MinValue;
|
|
void FastMove(int x, int y)
|
|
{
|
|
int since = (int)(DateTime.UtcNow - _lastMoveCmd).TotalMilliseconds;
|
|
if (since < MOVE_COMMAND_INTERVAL_MS)
|
|
Delay(MOVE_COMMAND_INTERVAL_MS - since);
|
|
Move(x, y);
|
|
_lastMoveCmd = DateTime.UtcNow;
|
|
}
|
|
|
|
(int nx, int ny)? NextStepToTarget(int sx, int sy, int tx, int ty)
|
|
{
|
|
(int x, int y) start = (sx, sy);
|
|
(int x, int y) goal = (tx, ty);
|
|
if (start == goal) return null;
|
|
|
|
var q = new Queue<(int x, int y)>();
|
|
var vis = new HashSet<string>();
|
|
var prev = new Dictionary<string, (int x, int y)>();
|
|
q.Enqueue(start);
|
|
vis.Add(K(start.x, start.y));
|
|
|
|
while (q.Count > 0)
|
|
{
|
|
var cur = q.Dequeue();
|
|
foreach (var n in Neigh4(cur.x, cur.y))
|
|
{
|
|
string nk = K(n.x, n.y);
|
|
if (vis.Contains(nk)) continue;
|
|
vis.Add(nk);
|
|
prev[nk] = cur;
|
|
if (n == goal)
|
|
{
|
|
var node = goal;
|
|
while (true)
|
|
{
|
|
var pk = K(node.x, node.y);
|
|
var pnode = prev[pk];
|
|
if (pnode == start) return node;
|
|
node = pnode;
|
|
}
|
|
}
|
|
q.Enqueue(n);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
List<(int x, int y)> BuildPathToTarget(int sx, int sy, int tx, int ty)
|
|
{
|
|
(int x, int y) start = (sx, sy);
|
|
(int x, int y) goal = (tx, ty);
|
|
var empty = new List<(int x, int y)>();
|
|
if (start == goal) return empty;
|
|
|
|
var q = new Queue<(int x, int y)>();
|
|
var vis = new HashSet<string>();
|
|
var prev = new Dictionary<string, (int x, int y)>();
|
|
q.Enqueue(start);
|
|
vis.Add(K(start.x, start.y));
|
|
|
|
while (q.Count > 0)
|
|
{
|
|
var cur = q.Dequeue();
|
|
foreach (var n in Neigh4(cur.x, cur.y))
|
|
{
|
|
string nk = K(n.x, n.y);
|
|
if (vis.Contains(nk)) continue;
|
|
vis.Add(nk);
|
|
prev[nk] = cur;
|
|
if (n == goal)
|
|
{
|
|
var rev = new List<(int x, int y)>();
|
|
var node = goal;
|
|
while (node != start)
|
|
{
|
|
rev.Add(node);
|
|
node = prev[K(node.x, node.y)];
|
|
}
|
|
rev.Reverse();
|
|
return rev;
|
|
}
|
|
q.Enqueue(n);
|
|
}
|
|
}
|
|
return empty;
|
|
}
|
|
|
|
Log("=== Color Pattern Walker Solver (fixed bounds) ===");
|
|
WaitForSpawn();
|
|
FullRoomScanLog();
|
|
|
|
var solCells = CollectCellsInRect(SOL_MIN_X, SOL_MAX_X, SOL_MIN_Y, SOL_MAX_Y);
|
|
var playCells = CollectCellsInRect(PLAY_MIN_X, PLAY_MAX_X, PLAY_MIN_Y, PLAY_MAX_Y);
|
|
|
|
int expectedSol = (SOL_MAX_X - SOL_MIN_X + 1) * (SOL_MAX_Y - SOL_MIN_Y + 1);
|
|
int expectedPlay = (PLAY_MAX_X - PLAY_MIN_X + 1) * (PLAY_MAX_Y - PLAY_MIN_Y + 1);
|
|
|
|
if (solCells.Count < expectedSol || playCells.Count < expectedPlay)
|
|
{
|
|
Log($"ERROR: Board incomplete. Solution {solCells.Count}/{expectedSol}, Play {playCells.Count}/{expectedPlay}");
|
|
return;
|
|
}
|
|
|
|
var solMap = IndexCells(solCells);
|
|
var playMap = IndexCells(playCells);
|
|
|
|
var targetByPlayId = new Dictionary<long, int>();
|
|
foreach (var p in playCells)
|
|
{
|
|
int sx = p.X - 4;
|
|
int sy = p.Y - 13;
|
|
string sk = K(sx, sy);
|
|
if (!solMap.ContainsKey(sk)) continue;
|
|
targetByPlayId[p.Id] = solMap[sk].State;
|
|
}
|
|
|
|
if (targetByPlayId.Count != expectedPlay)
|
|
{
|
|
Log($"ERROR: Could not map all play cells to solution cells ({targetByPlayId.Count}/{expectedPlay}).");
|
|
return;
|
|
}
|
|
|
|
var ids = new HashSet<long>(targetByPlayId.Keys);
|
|
var cur = new Dictionary<long, int>();
|
|
if (ASSUME_START_ALL_ZERO)
|
|
{
|
|
foreach (var id in ids) cur[id] = 0;
|
|
Log("Using round-start baseline: all play tiles = state 0.");
|
|
}
|
|
else
|
|
{
|
|
cur = ReadCurrentPlayStates(ids);
|
|
if (cur.Count != expectedPlay)
|
|
{
|
|
Log($"ERROR: Could not read all current play states ({cur.Count}/{expectedPlay}).");
|
|
return;
|
|
}
|
|
}
|
|
|
|
int cycle = FORCED_CYCLE;
|
|
if (cycle < 2) cycle = 5;
|
|
|
|
Log($"State cycle: {cycle}");
|
|
|
|
var coordToIdx = new Dictionary<string, int>();
|
|
var idToIdx = new Dictionary<long, int>();
|
|
for (int i = 0; i < playCells.Count; i++)
|
|
{
|
|
var c = playCells[i];
|
|
coordToIdx[K(c.X, c.Y)] = i;
|
|
idToIdx[c.Id] = i;
|
|
}
|
|
|
|
int[] needs = new int[playCells.Count];
|
|
for (int i = 0; i < playCells.Count; i++)
|
|
{
|
|
long id = playCells[i].Id;
|
|
needs[i] = Need(cur[id], targetByPlayId[id], cycle);
|
|
}
|
|
|
|
int obj = needs.Sum();
|
|
Log($"Initial objective: {obj}");
|
|
if (obj == 0) { Log("Already solved."); return; }
|
|
|
|
if (Self == null || Self.Location == null)
|
|
{
|
|
Log("ERROR: No self location.");
|
|
return;
|
|
}
|
|
|
|
int cx = Self.Location.X;
|
|
int cy = Self.Location.Y;
|
|
|
|
if (!InPlay(cx, cy))
|
|
{
|
|
var bestEntry = playCells
|
|
.OrderBy(c => Dist(cx, cy, c.X, c.Y))
|
|
.First();
|
|
FastMove(bestEntry.X, bestEntry.Y);
|
|
WaitUntilAt(bestEntry.X, bestEntry.Y);
|
|
cur = ReadCurrentPlayStates(ids);
|
|
for (int i = 0; i < playCells.Count; i++)
|
|
{
|
|
long id = playCells[i].Id;
|
|
needs[i] = Need(cur[id], targetByPlayId[id], cycle);
|
|
}
|
|
cx = ReadSelfX(bestEntry.X);
|
|
cy = ReadSelfY(bestEntry.Y);
|
|
obj = needs.Sum();
|
|
Log($"After entry objective: {obj}");
|
|
}
|
|
|
|
int stagnation = 0;
|
|
int prevX = -999;
|
|
int prevY = -999;
|
|
int sinceResync = 0;
|
|
var burstPath = new List<(int x, int y)>();
|
|
int burstIndex = 0;
|
|
|
|
for (int step = 1; step <= MAX_STEPS; step++)
|
|
{
|
|
if (!InPlay(cx, cy))
|
|
{
|
|
Log("WARN: Left play field unexpectedly, moving back.");
|
|
var back = playCells.OrderBy(c => Dist(cx, cy, c.X, c.Y)).First();
|
|
FastMove(back.X, back.Y);
|
|
WaitUntilAt(back.X, back.Y);
|
|
cx = ReadSelfX(back.X);
|
|
cy = ReadSelfY(back.Y);
|
|
cur = ReadCurrentPlayStates(ids);
|
|
for (int i = 0; i < playCells.Count; i++)
|
|
{
|
|
long id = playCells[i].Id;
|
|
needs[i] = Need(cur[id], targetByPlayId[id], cycle);
|
|
}
|
|
obj = needs.Sum();
|
|
sinceResync = 0;
|
|
burstPath.Clear();
|
|
burstIndex = 0;
|
|
continue;
|
|
}
|
|
|
|
if (obj == 0)
|
|
{
|
|
obj = HardObjectiveFromRoom(playCells, targetByPlayId, cycle, needs);
|
|
int standNeed = 999;
|
|
if (InPlay(cx, cy) && coordToIdx.ContainsKey(K(cx, cy)))
|
|
standNeed = needs[coordToIdx[K(cx, cy)]];
|
|
|
|
if (obj == 0 && standNeed == 0)
|
|
{
|
|
Log("=== Done: bottom matches solution (hard check OK) ===");
|
|
return;
|
|
}
|
|
}
|
|
|
|
var neighbors = Neigh4(cx, cy);
|
|
if (neighbors.Count == 0)
|
|
{
|
|
Log("ERROR: No neighbors on play field.");
|
|
return;
|
|
}
|
|
|
|
int unresolved = 0;
|
|
for (int i = 0; i < needs.Length; i++)
|
|
if (needs[i] > 0) unresolved++;
|
|
|
|
if (burstIndex >= burstPath.Count)
|
|
{
|
|
var needy = playCells
|
|
.Select(c => new {
|
|
Cell = c,
|
|
Need = needs[idToIdx[c.Id]],
|
|
D = Dist(cx, cy, c.X, c.Y)
|
|
})
|
|
.Where(x => x.Need > 0)
|
|
.OrderByDescending(x => x.Need)
|
|
.ThenBy(x => x.D)
|
|
.FirstOrDefault();
|
|
|
|
if (needy != null)
|
|
{
|
|
var path = BuildPathToTarget(cx, cy, needy.Cell.X, needy.Cell.Y);
|
|
if (path.Count > 0)
|
|
{
|
|
burstPath = path.Take(PATH_BURST_MAX_STEPS).ToList();
|
|
burstIndex = 0;
|
|
}
|
|
}
|
|
|
|
if (burstIndex >= burstPath.Count)
|
|
{
|
|
var fallback = neighbors
|
|
.Select(n => new {
|
|
N = n,
|
|
Need = needs[coordToIdx[K(n.x, n.y)]],
|
|
Back = (n.x == prevX && n.y == prevY) ? 1 : 0
|
|
})
|
|
.OrderByDescending(x => x.Need)
|
|
.ThenBy(x => x.Back)
|
|
.First();
|
|
burstPath = new List<(int x, int y)> { fallback.N };
|
|
burstIndex = 0;
|
|
}
|
|
}
|
|
|
|
(int x, int y) bestN = burstPath[burstIndex];
|
|
burstIndex++;
|
|
|
|
int idxChosen = coordToIdx[K(bestN.x, bestN.y)];
|
|
if (needs[idxChosen] == 0 && unresolved <= 8)
|
|
{
|
|
stagnation++;
|
|
}
|
|
else
|
|
{
|
|
stagnation = 0;
|
|
}
|
|
|
|
if (stagnation >= 12)
|
|
{
|
|
burstPath.Clear();
|
|
burstIndex = 0;
|
|
stagnation = 0;
|
|
}
|
|
|
|
prevX = cx;
|
|
prevY = cy;
|
|
int oldObj = obj;
|
|
|
|
FastMove(bestN.x, bestN.y);
|
|
if (MOVE_SETTLE_MS > 0) Delay(MOVE_SETTLE_MS);
|
|
|
|
// Predictive advance: keep running without stop-go per step.
|
|
cx = bestN.x;
|
|
cy = bestN.y;
|
|
|
|
if (InPlay(cx, cy) && coordToIdx.ContainsKey(K(cx, cy)))
|
|
{
|
|
int landedIdx = coordToIdx[K(cx, cy)];
|
|
obj += ApplyNeedStep(needs, landedIdx, cycle);
|
|
}
|
|
else
|
|
{
|
|
sinceResync = 20;
|
|
}
|
|
|
|
sinceResync++;
|
|
if (sinceResync >= PERIODIC_SYNC_EVERY_STEPS || stagnation >= 8)
|
|
{
|
|
Delay(120);
|
|
cx = ReadSelfX(cx);
|
|
cy = ReadSelfY(cy);
|
|
cur = ReadCurrentPlayStates(ids);
|
|
for (int i = 0; i < playCells.Count; i++)
|
|
{
|
|
long id = playCells[i].Id;
|
|
needs[i] = Need(cur[id], targetByPlayId[id], cycle);
|
|
}
|
|
obj = needs.Sum();
|
|
sinceResync = 0;
|
|
}
|
|
|
|
int newObj = obj;
|
|
Log($"[{step}] ({cx},{cy}) objective {oldObj} -> {newObj}");
|
|
}
|
|
|
|
obj = HardObjectiveFromRoom(playCells, targetByPlayId, cycle, needs);
|
|
int finalStandNeed = 999;
|
|
if (Self != null && Self.Location != null)
|
|
{
|
|
int fx = ReadSelfX(-9999);
|
|
int fy = ReadSelfY(-9999);
|
|
if (InPlay(fx, fy) && coordToIdx.ContainsKey(K(fx, fy)))
|
|
finalStandNeed = needs[coordToIdx[K(fx, fy)]];
|
|
}
|
|
|
|
if (obj == 0 && finalStandNeed == 0)
|
|
Log("=== Done: bottom matches solution (hard check OK) ===");
|
|
else
|
|
Log($"Stopped after max steps. Remaining objective: {obj}");
|