Habbo Hotel automation scripts including: - Game solvers (Snake, Color Puzzle, Tetris, Flappy Bird, Flood-IT) - Room utilities (Autogate, One-Way Door, Furni Scanner) - Bot tools (Heal Bot, Pet Trainer, User Collector) - Trading & economy (Furni-Matic, Seed Trade, Trade Spam) Cleaned up: removed 5 duplicates and 2 broken scripts, renamed 37 gibberish filenames to descriptive names.
282 lines
7.1 KiB
C#
282 lines
7.1 KiB
C#
/// @name Snake Auto (Campaign) v4
|
|
using System;
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
|
|
const long HEAD_ID = 769407982;
|
|
const int TARGET_KIND = 3666;
|
|
const int SNAKE_KIND = 5478;
|
|
|
|
const int GAME_MIN_X = 37;
|
|
const int GAME_MAX_X = 59;
|
|
const int GAME_MIN_Y = 29;
|
|
const int GAME_MAX_Y = 51;
|
|
|
|
// Confirmed controls
|
|
const int CTRL_UP = 769407385;
|
|
const int CTRL_RIGHT = 769406820;
|
|
const int CTRL_DOWN = 769407085;
|
|
const int CTRL_LEFT = 769407116;
|
|
|
|
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
|
|
|
|
DateTime lastCmd = DateTime.MinValue;
|
|
DateTime lastInfo = DateTime.MinValue;
|
|
string dir = "UP";
|
|
string lastSentDir = "";
|
|
(int x, int y) prevHead = (-1, -1);
|
|
|
|
HashSet<(int x, int y)> fillVisited = new HashSet<(int, int)>();
|
|
Queue<(int x, int y)> fillQueue = new Queue<(int, int)>();
|
|
HashSet<(int x, int y)> pathVisited = new HashSet<(int, int)>();
|
|
Queue<((int x, int y) pos, int dist)> pathQueue = new Queue<((int, int), int)>();
|
|
|
|
bool InBoard(int x, int y)
|
|
=> x >= GAME_MIN_X && x <= GAME_MAX_X && y >= GAME_MIN_Y && y <= GAME_MAX_Y;
|
|
|
|
bool IsInQueueArea()
|
|
{
|
|
if (Self == null) return false;
|
|
return Self.Location.X == 59 && Self.Location.Y >= 29 && Self.Location.Y <= 51;
|
|
}
|
|
|
|
void Next(int x, int y, int d, out int nx, out int ny)
|
|
{
|
|
nx = x + (d == 3 ? 1 : d == 2 ? -1 : 0);
|
|
ny = y + (d == 1 ? 1 : d == 0 ? -1 : 0);
|
|
}
|
|
|
|
int Opp(int d) => d ^ 1;
|
|
int DirToIdx(string d) => d == "UP" ? 0 : d == "DOWN" ? 1 : d == "LEFT" ? 2 : 3;
|
|
|
|
int IdForDir(int d)
|
|
=> d == 0 ? CTRL_UP : d == 1 ? CTRL_DOWN : d == 2 ? CTRL_LEFT : CTRL_RIGHT;
|
|
|
|
bool TryGetButtonPos(int id, out int x, out int y)
|
|
{
|
|
var b = GetFloorItem(id);
|
|
if (b == null)
|
|
{
|
|
x = 0;
|
|
y = 0;
|
|
return false;
|
|
}
|
|
x = b.Location.X;
|
|
y = b.Location.Y;
|
|
return true;
|
|
}
|
|
|
|
void Cmd(int d)
|
|
{
|
|
string nd = DIRS[d];
|
|
if (nd == lastSentDir && (DateTime.Now - lastCmd).TotalMilliseconds < 120) return;
|
|
|
|
int id = IdForDir(d);
|
|
if (TryGetButtonPos(id, out int bx, out int by))
|
|
Move(bx, by);
|
|
|
|
Send(Out["ClickFurni"], id, 0);
|
|
|
|
lastCmd = DateTime.Now;
|
|
lastSentDir = nd;
|
|
dir = nd;
|
|
}
|
|
|
|
bool Bad(int x, int y, HashSet<(int x, int y)> body)
|
|
{
|
|
if (!InBoard(x, y)) return true;
|
|
return body.Contains((x, y));
|
|
}
|
|
|
|
int Fill(int sx, int sy, HashSet<(int x, int y)> body, int cap)
|
|
{
|
|
if (Bad(sx, sy, body)) return 0;
|
|
|
|
fillVisited.Clear();
|
|
fillQueue.Clear();
|
|
|
|
fillVisited.Add((sx, sy));
|
|
fillQueue.Enqueue((sx, sy));
|
|
int c = 0;
|
|
|
|
while (fillQueue.Count > 0 && c < cap)
|
|
{
|
|
var p = fillQueue.Dequeue();
|
|
c++;
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
Next(p.x, p.y, d, out int nx, out int ny);
|
|
if (!Bad(nx, ny, body) && fillVisited.Add((nx, ny)))
|
|
fillQueue.Enqueue((nx, ny));
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
|
|
int Dist(int sx, int sy, int tx, int ty, HashSet<(int x, int y)> body, int cap)
|
|
{
|
|
if (sx == tx && sy == ty) return 0;
|
|
|
|
pathVisited.Clear();
|
|
pathQueue.Clear();
|
|
|
|
pathVisited.Add((sx, sy));
|
|
pathQueue.Enqueue(((sx, sy), 0));
|
|
|
|
while (pathQueue.Count > 0)
|
|
{
|
|
var cur = pathQueue.Dequeue();
|
|
if (cur.dist >= cap) return 999;
|
|
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
Next(cur.pos.x, cur.pos.y, d, out int nx, out int ny);
|
|
if (nx == tx && ny == ty) return cur.dist + 1;
|
|
if (!Bad(nx, ny, body) && pathVisited.Add((nx, ny)))
|
|
pathQueue.Enqueue(((nx, ny), cur.dist + 1));
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int Decide((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body, int curDir)
|
|
{
|
|
int bestDir = curDir;
|
|
int bestScore = int.MinValue;
|
|
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
if (d == Opp(curDir)) continue;
|
|
|
|
Next(head.x, head.y, d, out int nx, out int ny);
|
|
if (Bad(nx, ny, body)) continue;
|
|
|
|
int space = Fill(nx, ny, body, 180);
|
|
if (space < 8) continue;
|
|
|
|
int dist = Dist(nx, ny, target.x, target.y, body, 180);
|
|
|
|
int score = 0;
|
|
if (nx == target.x && ny == target.y) score += 10000;
|
|
if (dist > 0 && dist < 999) score += (220 - dist) * 7;
|
|
else if (dist == -1) score -= 200;
|
|
|
|
score += Math.Min(space, 100) * 4;
|
|
|
|
if (score > bestScore)
|
|
{
|
|
bestScore = score;
|
|
bestDir = d;
|
|
}
|
|
}
|
|
|
|
return bestDir;
|
|
}
|
|
|
|
int Emergency((int x, int y) head, HashSet<(int x, int y)> body, int curDir)
|
|
{
|
|
int bestDir = curDir;
|
|
int bestSpace = -1;
|
|
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
if (d == Opp(curDir)) continue;
|
|
Next(head.x, head.y, d, out int nx, out int ny);
|
|
if (Bad(nx, ny, body)) continue;
|
|
|
|
int space = Fill(nx, ny, body, 100);
|
|
if (space > bestSpace)
|
|
{
|
|
bestSpace = space;
|
|
bestDir = d;
|
|
}
|
|
}
|
|
return bestDir;
|
|
}
|
|
|
|
Log("Snake Auto (Campaign) v4 started");
|
|
Log($"Buttons: U={CTRL_UP} R={CTRL_RIGHT} D={CTRL_DOWN} L={CTRL_LEFT}");
|
|
|
|
while (Run)
|
|
{
|
|
Delay(20);
|
|
|
|
if (IsInQueueArea())
|
|
{
|
|
if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500)
|
|
{
|
|
Log("waiting: in queue");
|
|
lastInfo = DateTime.Now;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
var headItem = GetFloorItem(HEAD_ID);
|
|
if (headItem == null)
|
|
{
|
|
if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500)
|
|
{
|
|
Log("waiting: head not visible");
|
|
lastInfo = DateTime.Now;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
var head = (headItem.Location.X, headItem.Location.Y);
|
|
if (!InBoard(head.Item1, head.Item2)) continue;
|
|
|
|
// Infer current movement direction from head delta.
|
|
if (prevHead.x != -1)
|
|
{
|
|
int dx = head.Item1 - prevHead.x;
|
|
int dy = head.Item2 - prevHead.y;
|
|
if (dx != 0 || dy != 0)
|
|
dir = Math.Abs(dx) >= Math.Abs(dy)
|
|
? (dx < 0 ? "LEFT" : "RIGHT")
|
|
: (dy < 0 ? "UP" : "DOWN");
|
|
}
|
|
prevHead = head;
|
|
|
|
// Body = all snake tiles in board except current head position.
|
|
var body = FloorItems
|
|
.Where(i => i != null && i.Kind == SNAKE_KIND)
|
|
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
|
.Select(i => (i.Location.X, i.Location.Y))
|
|
.Where(p => !(p.Item1 == head.Item1 && p.Item2 == head.Item2))
|
|
.ToHashSet();
|
|
|
|
var targets = FloorItems
|
|
.Where(i => i != null && i.Kind == TARGET_KIND)
|
|
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
|
.Select(i => (x: i.Location.X, y: i.Location.Y, state: (int)i.State))
|
|
.ToList();
|
|
|
|
if (targets.Count == 0)
|
|
{
|
|
if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500)
|
|
{
|
|
Log("waiting: no color tile in board");
|
|
lastInfo = DateTime.Now;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
var target = targets
|
|
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
|
|
.ThenBy(t => t.state)
|
|
.First();
|
|
|
|
int curDir = DirToIdx(dir);
|
|
|
|
// Never force reverse into body.
|
|
Next(head.Item1, head.Item2, curDir, out int fx, out int fy);
|
|
if (Bad(fx, fy, body))
|
|
{
|
|
int e = Emergency(head, body, curDir);
|
|
Cmd(e);
|
|
continue;
|
|
}
|
|
|
|
int best = Decide(head, (target.x, target.y), body, curDir);
|
|
Cmd(best);
|
|
} |