Initial commit: 68 Xabbo Scripter scripts

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.
This commit is contained in:
Administrator 2026-03-16 09:38:59 +01:00
commit 7bfc390ed6
72 changed files with 17656 additions and 0 deletions

View File

@ -0,0 +1,101 @@
/*
AUTO-FISHER V10 - SILENT HISTORY
- Zeigt NUR noch Fänge und wichtige Events an
- Kein "Angel ausgeworfen" Spam mehr
- Clean Log (ohne BBCode) & Rare Tracker aktiv
*/
using System;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
// --- EINSTELLUNGEN ---
int itemId = 2147419751; // ID deiner Angel/Tile
// Trigger
string triggerCatch = "Rhaz caught";
string triggerShark = "attacked by a shark";
string triggerStunOver = "stun effect has worn off";
Log("--- Auto-Fisher V10 (History Mode) gestartet ---");
string CleanMessage(string input)
{
return Regex.Replace(input, @"\[.*?\]", "");
}
void Click()
{
if (!Run) return;
Send(Out["ClickFurni"], itemId, 0);
// HIER HABEN WIR DEN LOG ENTFERNT
// Damit bleibt deine History sauber!
}
void HandleMessage(InterceptArgs e)
{
try
{
var p = e.Packet;
p.ReadInt();
string rawMsg = p.ReadString();
string lowerMsg = rawMsg.ToLower();
string cleanMsg = CleanMessage(rawMsg);
// 1. FANG
if (lowerMsg.Contains(triggerCatch.ToLower()))
{
// Rarity Tracker
string rarityLog = "";
if (lowerMsg.Contains("exotic")) rarityLog = "🟣 EXOTIC";
else if (lowerMsg.Contains("legendary")) rarityLog = "🟡 LEGENDARY";
else if (lowerMsg.Contains("epic")) rarityLog = "🔴 EPIC";
else if (lowerMsg.Contains("rare")) rarityLog = "🔵 RARE";
// Nur besondere Fische kriegen eine Zeitstempel-Hervorhebung
if (rarityLog != "")
Log($"💎 {rarityLog} ({DateTime.Now:HH:mm:ss})");
// Der normale Log-Eintrag für die History
Log($"✅ {cleanMsg}");
Task.Run(() =>
{
int ms = new Random().Next(500, 1500);
System.Threading.Thread.Sleep(ms);
if (Run) Click();
});
}
// 2. HAI
else if (lowerMsg.Contains(triggerShark.ToLower()))
{
Log($"⚠️ HAI-ANGRIFF! ({CleanMessage(rawMsg)})");
// Wir warten stillschweigend auf das Ende
}
// 3. STUN VORBEI
else if (lowerMsg.Contains(triggerStunOver.ToLower()))
{
Log($"🎉 Stun vorbei - weiter geht's!");
Task.Run(() =>
{
int reactionTime = new Random().Next(500, 1200);
System.Threading.Thread.Sleep(reactionTime);
if (Run) Click();
});
}
}
catch { }
}
OnIntercept(In["Chat"], e => HandleMessage(e));
OnIntercept(In["Shout"], e => HandleMessage(e));
OnIntercept(In["Whisper"], e => HandleMessage(e));
// Erster Klick
Click();
Log("(Angel ist aktiv - warte auf ersten Fisch...)");
while(Run) Delay(1000);

131
Auto Healer Medic Bot.csx Normal file
View File

@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Sulakore.Communication;
using Sulakore.Modules;
using Tangine;
namespace HabboHealBot
{
[Module("Auto Medic", "Automates the :offer heal process")]
[Author("User")]
public class HealBot : Extension
{
// Status-Variable, damit Prozesse nicht unterbrochen werden
private bool _isBusy = false;
// Speichert die Zuordnung von Index (Raum-ID) zu EntityID (Datenbank-ID)
// Das wird benötigt, weil der Chat den Index sendet, der Klick aber oft die ID braucht.
private Dictionary<int, int> _userMap = new Dictionary<int, int>();
public HealBot()
{
// Events registrieren
Triggers.In(In.Chat, OnChat); // Wenn jemand spricht
Triggers.In(In.Users, OnUsers); // Wenn User den Raum betreten
Triggers.In(In.RoomReady, OnRoomReady); // Wenn wir den Raum betreten (Reset)
}
// 1. Liste zurücksetzen wenn wir den Raum wechseln
private void OnRoomReady(DataInterceptedEventArgs e)
{
_userMap.Clear();
_isBusy = false;
}
// 2. User tracken (Index zu ID Zuordnung)
private void OnUsers(DataInterceptedEventArgs e)
{
var parser = e.Packet;
int count = parser.ReadInt();
for (int i = 0; i < count; i++)
{
int id = parser.ReadInt(); // Entity ID (Datenbank ID)
string name = parser.ReadString();
string motto = parser.ReadString();
string look = parser.ReadString();
int index = parser.ReadInt(); // Room Index
// Restliche Daten überspringen (Koordinaten etc.)
// Hinweis: Die Struktur kann je nach Server leicht variieren,
// aber ID und Index kommen meist zuerst.
if (!_userMap.ContainsKey(index))
{
_userMap.Add(index, id);
}
}
}
// 3. Auf "heal" im Chat reagieren
private void OnChat(DataInterceptedEventArgs e)
{
// Wenn wir gerade schon jemanden heilen -> Ignorieren
if (_isBusy) return;
int index = e.Packet.ReadInt();
string message = e.Packet.ReadString();
// Prüfen ob "heal" (groß/kleinschreibung egal) vorkommt
if (message.ToLower().Contains("heal"))
{
// Prüfen, ob wir die ID zu diesem User haben
if (_userMap.ContainsKey(index))
{
int targetId = _userMap[index];
// Prozess starten (Async damit der Main-Thread nicht blockiert)
Task.Run(() => PerformHealRoutine(targetId));
}
}
}
private async Task PerformHealRoutine(int targetUserId)
{
_isBusy = true; // Blockieren
Console.WriteLine($"[Bot] Healing User ID: {targetUserId}...");
try
{
// Schritt 1: :offer senden
await SendPacketAsync(Out.Shout, ":offer", 0);
await Task.Delay(600); // Warten bis Menü da ist (Evtl. anpassen je nach Lag)
// Schritt 2: 1 senden (Heal auswählen)
await SendPacketAsync(Out.Shout, "1", 0);
await Task.Delay(600); // Warten auf "Click the user" Prompt
// Schritt 3: User anklicken
// Auf den meisten Servern ist "Anklicken" das Paket "GetSelectedBadges"
// oder einfach das Abfragen der User-Info.
// Paket-Struktur: {Header} {Int: UserID}
await SendPacketAsync(Out.GetSelectedBadges, targetUserId);
// Falls "GetSelectedBadges" auf deinem Server nicht als Klick zählt,
// probiere stattdessen Out.RoomUserAction oder Out.LookTo
Console.WriteLine("[Bot] Heal sequence finished.");
}
catch (Exception ex)
{
Console.WriteLine("[Bot] Error: " + ex.Message);
}
finally
{
// Prozess freigeben
await Task.Delay(500); // Kurzer Cooldown
_isBusy = false;
}
}
// Helfer für asynchrones Senden
private async Task SendPacketAsync(ushort header, params object[] values)
{
await SendToServerAsync(header, values);
}
}
}

156
Autogate.csx Normal file
View File

@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
public struct Point : IEquatable<Point>
{
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})";
}
Log("started");
const string FURNI_NAME_CONTAINS_TEXT = "One Way Gate";
Regex mvRegex = new Regex(@"/mv (\d+),(\d+),([\d\.]+)/");
void CheckAndEnterGateGeneral(int userTileX, int userTileY)
{
if (FloorItems == null) return;
foreach (var item in FloorItems)
{
if (item == null || item.Location == null) continue;
string itemName = null;
try { itemName = item.GetName(); } catch { continue; }
if (itemName != null && itemName.Contains(FURNI_NAME_CONTAINS_TEXT))
{
int gateX = item.Location.X;
int gateY = item.Location.Y;
int gateDir = item.Direction;
long gateId = item.Id;
bool shouldEnter = false;
if (gateDir == 0) { if (userTileX == gateX && userTileY == gateY - 1) shouldEnter = true; }
else if (gateDir == 2) { if (userTileX == gateX + 1 && userTileY == gateY) shouldEnter = true; }
else if (gateDir == 4) { if (userTileX == gateX && userTileY == gateY + 1) shouldEnter = true; }
else if (gateDir == 6) { if (userTileX == gateX - 1 && userTileY == gateY) shouldEnter = true; }
if (shouldEnter)
{
Log($"User tile ({userTileX},{userTileY}) matches criteria for Gate (ID {gateId}, Name: '{itemName}') at ({gateX},{gateY} Dir:{gateDir}) via GeneralCheck. Sending packet.");
Send(Out.EnterOneWayDoor, gateId);
return;
}
}
}
}
void HandleUserUpdate(dynamic e)
{
if (Self == null) return;
var packet = e.Packet;
int numUpdates = packet.ReadInt();
for (int i = 0; i < numUpdates; i++)
{
int entityIndex = packet.ReadInt();
int currentX = packet.ReadInt();
int currentY = packet.ReadInt();
packet.ReadString();
packet.ReadInt();
packet.ReadInt();
string action = packet.ReadString();
if (entityIndex == Self.Index)
{
Point tileToConsiderForGate;
Match match = mvRegex.Match(action);
if (match.Success)
{
try
{
int targetX = int.Parse(match.Groups[1].Value);
int targetY = int.Parse(match.Groups[2].Value);
tileToConsiderForGate = new Point(targetX, targetY);
}
catch { tileToConsiderForGate = new Point(currentX, currentY); }
}
else { tileToConsiderForGate = new Point(currentX, currentY); }
CheckAndEnterGateGeneral(tileToConsiderForGate.X, tileToConsiderForGate.Y);
}
}
}
void HandleObjectUpdate(dynamic e)
{
if (Self == null || Self.Location == null) return;
var packet = e.Packet;
int furniIdFromPacket = packet.ReadInt();
packet.ReadInt();
int itemXFromPacket = packet.ReadInt();
int itemYFromPacket = packet.ReadInt();
int itemNewDirectionFromPacket = packet.ReadInt();
var itemInstance = FloorItems?.FirstOrDefault(f => f != null && f.Id == furniIdFromPacket);
if (itemInstance != null)
{
string itemName = null;
try { itemName = itemInstance.GetName(); } catch { return; }
if (itemName != null && itemName.Contains(FURNI_NAME_CONTAINS_TEXT))
{
bool shouldEnter = false;
int userX = Self.Location.X;
int userY = Self.Location.Y;
if (itemNewDirectionFromPacket == 0) { if (userX == itemXFromPacket && userY == itemYFromPacket - 1) shouldEnter = true; }
else if (itemNewDirectionFromPacket == 2) { if (userX == itemXFromPacket + 1 && userY == itemYFromPacket) shouldEnter = true; }
else if (itemNewDirectionFromPacket == 4) { if (userX == itemXFromPacket && userY == itemYFromPacket + 1) shouldEnter = true; }
else if (itemNewDirectionFromPacket == 6) { if (userX == itemXFromPacket - 1 && userY == itemYFromPacket) shouldEnter = true; }
if (shouldEnter)
{
Log($"User at ({userX},{userY}) matches criteria for Gate ID {furniIdFromPacket} ({itemXFromPacket},{itemYFromPacket}) with NewDir:{itemNewDirectionFromPacket} from ObjectUpdate. Sending packet.");
Send(Out.EnterOneWayDoor, furniIdFromPacket);
}
}
}
}
void InitializeStateAndCheckGates(dynamic eventArgs)
{
if (Self != null && Self.Location != null)
{
Point initialPosition = new Point(Self.Location.X, Self.Location.Y);
CheckAndEnterGateGeneral(initialPosition.X, initialPosition.Y);
}
}
OnIntercept(In["UserUpdate"], e => HandleUserUpdate(e));
OnIntercept(In["ObjectUpdate"], e => HandleObjectUpdate(e));
OnEnteredRoom(e => InitializeStateAndCheckGates(e));
if (Self != null && Self.Location != null) {
InitializeStateAndCheckGates(null);
}
while(Run)
{
try { }
catch (Exception ex) { Log($"MAIN LOOP ERROR: {ex.GetType().Name} - {ex.Message}"); }
if (!Run) break;
Delay(30);
}
Log("closed");

View File

@ -0,0 +1,56 @@
var furnitures = new Dictionary<int, (int x, int y)> {
{ 880616572, (25, 13) },
{ 880617415, (13, 13) },
{ 880617090, (13, 29) },
{ 880617208, (25, 29) }
};
var lastusedid = -1;
var nearfurni = false;
Log("Furniture proximity trigger started");
Log($"Monitoring {furnitures.Count} furniture items");
bool isnearfurniture(int x, int y, out int furniid) {
foreach (var furni in furnitures) {
var dx = Math.Abs(x - furni.Value.x);
var dy = Math.Abs(y - furni.Value.y);
if (dx <= 1 && dy <= 1) {
furniid = furni.Key;
return true;
}
}
furniid = -1;
return false;
}
while (Run) {
if (Self == null || Self.Location == null) {
Delay(100);
continue;
}
var x = Self.Location.X;
var y = Self.Location.Y;
if (isnearfurniture(x, y, out int furniid)) {
if (!nearfurni || furniid != lastusedid) {
Send(Out["UseFurniture"], furniid, 0);
Log($"Used furniture {furniid} from position ({x},{y})");
lastusedid = furniid;
nearfurni = true;
}
}
else {
if (nearfurni) {
Log("Left furniture area");
nearfurni = false;
lastusedid = -1;
}
}
Delay(100);
}
Log("Proximity trigger stopped");

47
Cabbage Placer Loop.csx Normal file
View File

@ -0,0 +1,47 @@
Log("=== Cabbage Placer Loop (Alle Tiles) ===");
// Raumgrenzen
int minX = 4;
int maxX = 11;
int minY = 1;
int maxY = 13;
EnsureInventory();
Delay(100);
while (Run) {
bool hasCabbage = true;
for (int x = minX; x <= maxX && Run && hasCabbage; x++) {
for (int y = maxY; y >= minY && Run && hasCabbage; y--) {
long cabbageId = -1;
string cabbageName = "";
foreach (var item in Inventory) {
string name = item.GetName().ToLower();
if (name.Contains("cabbage")) {
cabbageId = item.Id;
cabbageName = item.GetName();
break;
}
}
if (cabbageId != -1) {
Log($"Platziere: {cabbageName} auf ({x}, {y})...");
Send(Out["PlaceObject"], $"-{cabbageId} {x} {y} 0");
Delay(100);
EnsureInventory();
Delay(100);
} else {
Log("Kein Cabbage mehr im Inventar!");
hasCabbage = false;
}
}
}
Log("Durchlauf abgeschlossen. Starte neu...");
Delay(500);
EnsureInventory();
Delay(100);
}

60
Catalog Exporter.csx Normal file
View File

@ -0,0 +1,60 @@
/// @name Catalog Scraper
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Encodings.Web;
var catalog = GetCatalog();
var nodes = catalog.Where(x => x.Id > 0).ToArray();
var pages = new List<(ICatalogPageNode Node, ICatalogPage Page)>();
for (int i = 0; i < nodes.Length; i++) {
var node = nodes[i];
Status($"Loading page {i+1}/{nodes.Length}...");
pages.Add((node, GetCatalogPage(node)));
await Task.Delay(300);
}
var opts = new JsonSerializerOptions {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true
};
static bool IsFurni(IItem it) => it.Type == ItemType.Floor || it.Type == ItemType.Wall;
string json = JsonSerializer.Serialize(
pages.Select(x => new {
PageId = x.Node.Id,
x.Node.Name,
x.Node.Title,
x.Node.Icon,
x.Node.IsVisible,
x.Page.LayoutCode,
x.Page.Images,
x.Page.Texts,
x.Page.AcceptSeasonCurrencyAsCredits,
Offers = x.Page.Offers.Select(offer => new {
offer.Id,
offer.FurniLine,
offer.PriceInCredits,
offer.PriceInActivityPoints,
ActivityPointType = offer.ActivityPointType.ToString(),
offer.CanPurchaseAsGift,
offer.CanPurchaseMultiple,
offer.ClubLevel,
Products = offer.Products.Select(product => new {
Identifier = IsFurni(product) ? product.GetIdentifier() : null,
Name = IsFurni(product) ? product.GetIdentifier() : null,
Type = product.Type.ToString(),
product.Variant,
product.Count,
product.IsLimited
})
})
}),
opts
);
Directory.CreateDirectory("catalog");
File.WriteAllText($"catalog/{DateTime.Now:yyyyMMddHHmmssfff}.json", json);

View File

@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
const int SOURCE_MIN_X = 18;
const int SOURCE_MAX_X = 29;
const int SOURCE_MIN_Y = 25;
const int SOURCE_MAX_Y = 36;
const int FIELD_MIN_X = 18;
const int FIELD_MAX_X = 29;
const int FIELD_MIN_Y = 9;
const int FIELD_MAX_Y = 20;
int GetColorNumber(string name)
{
var match = Regex.Match(name, @"\d+$");
return match.Success ? int.Parse(match.Value) : -1;
}
bool InField(int x, int y) => x >= FIELD_MIN_X && x <= FIELD_MAX_X && y >= FIELD_MIN_Y && y <= FIELD_MAX_Y;
Log("=== WAITING FOR FIELD POSITION ===");
while (true)
{
int selfX = Self.Location.X;
int selfY = Self.Location.Y;
if (selfX == 14 && selfY >= 16 && selfY <= 35)
{
Log("Moving to (14, 15)...");
await SendAsync(Out["MoveAvatar"], 14, 15);
await Task.Delay(500);
continue;
}
if (InField(selfX, selfY))
{
Log($"In field at ({selfX}, {selfY}), starting!");
break;
}
await Task.Delay(100);
}
Log("=== AUTO DETECTING COLORS ===");
var selectors = new Dictionary<int, int>();
foreach (var item in FloorItems)
{
string name = item.GetName();
if (name.Contains("Cylinder Block") || name.Contains("Hemisphere Block"))
{
int colorNum = GetColorNumber(name);
if (colorNum > 0 && !selectors.ContainsKey(colorNum))
{
selectors[colorNum] = (int)item.Id;
Log($"Selector: {name} -> Color {colorNum}");
}
}
}
Log($"Total selectors: {selectors.Count}");
var sourcePixels = new Dictionary<(int x, int y), int>();
foreach (var item in FloorItems)
{
int x = item.Location.X;
int y = item.Location.Y;
double z = item.Location.Z;
if (x < SOURCE_MIN_X || x > SOURCE_MAX_X || y < SOURCE_MIN_Y || y > SOURCE_MAX_Y) continue;
if (z < 0.7 || z > 1.0) continue;
int colorNum = GetColorNumber(item.GetName());
if (colorNum > 0)
sourcePixels[(x, y)] = colorNum;
}
Log($"Source pixels: {sourcePixels.Count}");
int offsetY = SOURCE_MIN_Y - FIELD_MIN_Y;
int changed = 0;
for (int attempt = 1; attempt <= 3; attempt++)
{
var fieldTiles = new Dictionary<(int x, int y), (int id, int colorNum)>();
foreach (var item in FloorItems)
{
int x = item.Location.X;
int y = item.Location.Y;
double z = item.Location.Z;
if (x < FIELD_MIN_X || x > FIELD_MAX_X || y < FIELD_MIN_Y || y > FIELD_MAX_Y) continue;
int colorNum = GetColorNumber(item.GetName());
if (z > 0.5)
fieldTiles[(x, y)] = ((int)item.Id, colorNum);
else if (!fieldTiles.ContainsKey((x, y)))
fieldTiles[(x, y)] = ((int)item.Id, -1);
}
var toChange = new List<(int fieldId, int srcColor)>();
foreach (var src in sourcePixels)
{
int fieldX = src.Key.x;
int fieldY = src.Key.y - offsetY;
int srcColor = src.Value;
if (!fieldTiles.TryGetValue((fieldX, fieldY), out var field)) continue;
if (field.colorNum == srcColor) continue;
if (!selectors.ContainsKey(srcColor)) continue;
toChange.Add((field.id, srcColor));
}
if (toChange.Count == 0)
{
Log($"Attempt {attempt}: All correct!");
break;
}
Log($"Attempt {attempt}: {toChange.Count} to change");
var sorted = toChange.OrderBy(x => x.srcColor).ToList();
int lastColor = -1;
foreach (var item in sorted)
{
if (item.srcColor != lastColor)
{
await SendAsync(Out["ClickFurni"], selectors[item.srcColor], 0);
await Task.Delay(50);
lastColor = item.srcColor;
}
await SendAsync(Out["ClickFurni"], item.fieldId, 0);
await Task.Delay(50);
changed++;
}
await Task.Delay(500);
}
Log("=== SUBMITTING ===");
var badge = FloorItems.FirstOrDefault(f => f.GetName() == "Badge Display Case");
if (badge != null)
{
await SendAsync(Out["ClickFurni"], (int)badge.Id, 0);
Log($"Clicked Badge Display Case (ID: {badge.Id})");
}
else
{
Log("Badge Display Case not found!");
}
Log($"Done! Total changed: {changed}");

101
Color Matching Simple.csx Normal file
View File

@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
const int SOURCE_MIN_X = 18;
const int SOURCE_MAX_X = 29;
const int SOURCE_MIN_Y = 25;
const int SOURCE_MAX_Y = 36;
const int FIELD_MIN_X = 18;
const int FIELD_MAX_X = 29;
const int FIELD_MIN_Y = 9;
const int FIELD_MAX_Y = 20;
int GetColorNumber(string name)
{
var match = Regex.Match(name, @"\d+$");
return match.Success ? int.Parse(match.Value) : -1;
}
Log("=== AUTO DETECTING COLORS ===");
var selectors = new Dictionary<int, int>();
foreach (var item in FloorItems)
{
string name = item.GetName();
if (name.Contains("Cylinder Block") || name.Contains("Hemisphere Block"))
{
int colorNum = GetColorNumber(name);
if (colorNum > 0 && !selectors.ContainsKey(colorNum))
{
selectors[colorNum] = (int)item.Id;
Log($"Selector: {name} -> Color {colorNum}");
}
}
}
Log($"Total selectors: {selectors.Count}");
var sourcePixels = new Dictionary<(int x, int y), int>();
foreach (var item in FloorItems)
{
int x = item.Location.X;
int y = item.Location.Y;
double z = item.Location.Z;
if (x < SOURCE_MIN_X || x > SOURCE_MAX_X || y < SOURCE_MIN_Y || y > SOURCE_MAX_Y) continue;
if (z < 0.7 || z > 1.0) continue;
int colorNum = GetColorNumber(item.GetName());
if (colorNum > 0)
sourcePixels[(x, y)] = colorNum;
}
Log($"Source pixels: {sourcePixels.Count}");
var fieldTiles = new Dictionary<(int x, int y), int>();
foreach (var item in FloorItems)
{
int x = item.Location.X;
int y = item.Location.Y;
double z = item.Location.Z;
if (x < FIELD_MIN_X || x > FIELD_MAX_X || y < FIELD_MIN_Y || y > FIELD_MAX_Y) continue;
if (z > 0.5) continue;
fieldTiles[(x, y)] = (int)item.Id;
}
Log($"Field tiles: {fieldTiles.Count}");
int offsetY = SOURCE_MIN_Y - FIELD_MIN_Y;
var toChange = new List<(int fieldId, int srcColor)>();
foreach (var src in sourcePixels)
{
int fieldX = src.Key.x;
int fieldY = src.Key.y - offsetY;
int srcColor = src.Value;
if (!fieldTiles.TryGetValue((fieldX, fieldY), out int fieldId)) continue;
if (!selectors.ContainsKey(srcColor)) continue;
toChange.Add((fieldId, srcColor));
}
var sorted = toChange.OrderBy(x => x.srcColor).ToList();
Log($"To change: {sorted.Count}");
int lastColor = -1;
int changed = 0;
foreach (var item in sorted)
{
if (item.srcColor != lastColor)
{
await SendAsync(Out["ClickFurni"], selectors[item.srcColor], 0);
await Task.Delay(50);
lastColor = item.srcColor;
}
await SendAsync(Out["ClickFurni"], item.fieldId, 0);
await Task.Delay(50);
changed++;
}
Log($"Done! Changed: {changed}");

View File

@ -0,0 +1,635 @@
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}");

View File

@ -0,0 +1,518 @@
using System;
using System.Collections.Generic;
using System.Linq;
class Tile
{
public long Id;
public int Kind;
public string Name;
public int X;
public int Y;
public double Z;
public int State;
}
class Cluster
{
public int Kind;
public List<Tile> Tiles = new List<Tile>();
public double AvgZ;
public HashSet<int> StateSet = new HashSet<int>();
public double CenterX;
public double CenterY;
}
const int SPAWN_X = 13;
const int SPAWN_Y = 13;
const bool WAIT_FOR_SPAWN_START = true;
const int SPAWN_WAIT_TIMEOUT_MS = 180000;
int GetState(dynamic item)
{
try { return int.Parse(item.State?.ToString() ?? "0"); }
catch { return 0; }
}
int GetKind(dynamic item)
{
try { return (int)item.Kind; }
catch { return -1; }
}
string GetNameSafe(dynamic item)
{
try
{
string n = item.GetName();
return string.IsNullOrWhiteSpace(n) ? "<unknown>" : n;
}
catch { return "<unknown>"; }
}
List<Tile> ReadAllFloorTiles()
{
var list = new List<Tile>();
foreach (var item in FloorItems)
{
if (item == null) continue;
list.Add(new Tile {
Id = item.Id,
Kind = GetKind(item),
Name = GetNameSafe(item),
X = item.Location.X,
Y = item.Location.Y,
Z = item.Location.Z,
State = GetState(item)
});
}
return list;
}
string PosKey(int x, int y) => x + "," + y;
List<Cluster> BuildClusters(List<Tile> tilesOfKind)
{
var byPos = tilesOfKind.ToDictionary(t => PosKey(t.X, t.Y), t => t);
var visited = new HashSet<string>();
var clusters = new List<Cluster>();
int[] d = { -1, 0, 1 };
foreach (var t in tilesOfKind)
{
string start = PosKey(t.X, t.Y);
if (visited.Contains(start)) continue;
var q = new Queue<Tile>();
var c = new Cluster { Kind = t.Kind };
q.Enqueue(t);
visited.Add(start);
while (q.Count > 0)
{
var cur = q.Dequeue();
c.Tiles.Add(cur);
c.StateSet.Add(cur.State);
foreach (int dx in d)
foreach (int dy in d)
{
if (dx == 0 && dy == 0) continue;
string nk = PosKey(cur.X + dx, cur.Y + dy);
if (visited.Contains(nk)) continue;
if (!byPos.ContainsKey(nk)) continue;
visited.Add(nk);
q.Enqueue(byPos[nk]);
}
}
c.AvgZ = c.Tiles.Count == 0 ? 0.0 : c.Tiles.Average(x => x.Z);
c.CenterX = c.Tiles.Count == 0 ? 0.0 : c.Tiles.Average(x => x.X);
c.CenterY = c.Tiles.Count == 0 ? 0.0 : c.Tiles.Average(x => x.Y);
clusters.Add(c);
}
return clusters;
}
void LogRoomScan(List<Tile> all)
{
Log("=== Full Room Scan ===");
Log($"FloorItems total: {all.Count}");
var byKind = all.GroupBy(x => x.Kind)
.Select(g => new {
Kind = g.Key,
Count = g.Count(),
Names = g.Select(x => x.Name).Distinct().Take(3).ToArray(),
MinX = g.Min(x => x.X), MaxX = g.Max(x => x.X),
MinY = g.Min(x => x.Y), MaxY = g.Max(x => x.Y),
MinZ = g.Min(x => x.Z), MaxZ = g.Max(x => x.Z),
States = string.Join(",", g.Select(x => x.State).Distinct().OrderBy(x => x))
})
.OrderByDescending(x => x.Count)
.Take(30)
.ToList();
foreach (var k in byKind)
{
Log($"Kind {k.Kind} x{k.Count} | states [{k.States}] | bbox X[{k.MinX}-{k.MaxX}] Y[{k.MinY}-{k.MaxY}] Z[{k.MinZ:F2}-{k.MaxZ:F2}] | name {string.Join(" / ", k.Names)}");
}
}
double DistD(double x1, double y1, double x2, double y2)
{
double dx = x1 - x2;
double dy = y1 - y2;
return Math.Sqrt(dx * dx + dy * dy);
}
bool TryDetectBoards(List<Tile> all, int spawnX, int spawnY, out Cluster top, out Cluster bottom)
{
top = null;
bottom = null;
var byKind = all.GroupBy(x => x.Kind).ToList();
Cluster bestA = null, bestB = null;
int bestScore = -1;
foreach (var g in byKind)
{
var clusters = BuildClusters(g.ToList())
.Where(c => c.Tiles.Count >= 9)
.OrderByDescending(c => c.Tiles.Count)
.ToList();
if (clusters.Count < 2) continue;
for (int i = 0; i < clusters.Count; i++)
{
for (int j = i + 1; j < clusters.Count; j++)
{
var a = clusters[i];
var b = clusters[j];
int minCount = Math.Min(a.Tiles.Count, b.Tiles.Count);
int diff = Math.Abs(a.Tiles.Count - b.Tiles.Count);
int score = (minCount * 10) - diff;
if (a.StateSet.Count > 1) score += 5;
if (b.StateSet.Count > 1) score += 5;
if (score > bestScore)
{
bestScore = score;
bestA = a;
bestB = b;
}
}
}
}
if (bestA == null || bestB == null) return false;
// Prefer the board whose center is closer to your spawn as bottom/play board.
double da = DistD(bestA.CenterX, bestA.CenterY, spawnX, spawnY);
double db = DistD(bestB.CenterX, bestB.CenterY, spawnX, spawnY);
if (Math.Abs(da - db) >= 2.0)
{
if (da <= db) { bottom = bestA; top = bestB; }
else { bottom = bestB; top = bestA; }
}
else
{
// Fallback if both are similarly far away.
if (bestA.AvgZ >= bestB.AvgZ) { top = bestA; bottom = bestB; }
else { top = bestB; bottom = bestA; }
}
return true;
}
bool WaitForRoundSpawn(int spawnX, int spawnY, int timeoutMs)
{
Log($"Waiting for round start spawn at {spawnX}:{spawnY}...");
int elapsed = 0;
while (elapsed < timeoutMs)
{
if (Self != null && Self.Location != null)
{
if (Self.Location.X == spawnX && Self.Location.Y == spawnY)
{
Log("Spawn detected. Round started.");
Delay(350);
return true;
}
}
Delay(200);
elapsed += 200;
}
Log("Spawn wait timeout reached. Continuing anyway.");
return false;
}
Dictionary<string, Tile> BuildIndexedMap(Cluster c, out int w, out int h)
{
var xs = c.Tiles.Select(t => t.X).Distinct().OrderBy(x => x).ToList();
var ys = c.Tiles.Select(t => t.Y).Distinct().OrderBy(y => y).ToList();
w = xs.Count;
h = ys.Count;
var xToI = new Dictionary<int, int>();
var yToI = new Dictionary<int, int>();
for (int i = 0; i < xs.Count; i++) xToI[xs[i]] = i;
for (int i = 0; i < ys.Count; i++) yToI[ys[i]] = i;
var map = new Dictionary<string, Tile>();
foreach (var t in c.Tiles)
{
int xi = xToI[t.X];
int yi = yToI[t.Y];
map[PosKey(xi, yi)] = t;
}
return map;
}
bool TryBuildTargetMap(Cluster top, Cluster bottom, out Dictionary<long, int> targetByBottomId, out List<Tile> bottomTilesOrdered)
{
targetByBottomId = new Dictionary<long, int>();
bottomTilesOrdered = new List<Tile>();
int bw, bh, tw, th;
var bMap = BuildIndexedMap(bottom, out bw, out bh);
var tMap = BuildIndexedMap(top, out tw, out th);
if (bw != tw || bh != th)
{
Log($"WARN: Different board dimensions: top {tw}x{th}, bottom {bw}x{bh}. Trying overlap map.");
}
int w = Math.Min(bw, tw);
int h = Math.Min(bh, th);
if (w <= 0 || h <= 0) return false;
var transforms = new List<Func<int, int, (int x, int y)>>();
transforms.Add((x, y) => (x, y));
transforms.Add((x, y) => (w - 1 - x, y));
transforms.Add((x, y) => (x, h - 1 - y));
transforms.Add((x, y) => (w - 1 - x, h - 1 - y));
if (w == h)
{
transforms.Add((x, y) => (y, x));
transforms.Add((x, y) => (w - 1 - y, x));
transforms.Add((x, y) => (y, h - 1 - x));
transforms.Add((x, y) => (w - 1 - y, h - 1 - x));
}
int bestIdx = 0;
int bestMatches = -1;
for (int ti = 0; ti < transforms.Count; ti++)
{
int matches = 0;
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
var bKey = PosKey(x, y);
if (!bMap.ContainsKey(bKey)) continue;
var tr = transforms[ti](x, y);
var tKey = PosKey(tr.x, tr.y);
if (tMap.ContainsKey(tKey)) matches++;
}
}
if (matches > bestMatches)
{
bestMatches = matches;
bestIdx = ti;
}
}
var bestTf = transforms[bestIdx];
Log($"Mapping transform index: {bestIdx}, overlap: {bestMatches}");
foreach (var kv in bMap)
{
var p = kv.Key.Split(',');
int x = int.Parse(p[0]);
int y = int.Parse(p[1]);
if (x >= w || y >= h) continue;
var tr = bestTf(x, y);
var tKey = PosKey(tr.x, tr.y);
if (!tMap.ContainsKey(tKey)) continue;
var bTile = kv.Value;
var tTile = tMap[tKey];
targetByBottomId[bTile.Id] = tTile.State;
bottomTilesOrdered.Add(bTile);
}
return targetByBottomId.Count > 0;
}
Dictionary<long, int> ReadCurrentStatesById(HashSet<long> ids)
{
var map = new Dictionary<long, int>();
foreach (var item in FloorItems)
{
if (item == null) continue;
long id = item.Id;
if (!ids.Contains(id)) continue;
map[id] = GetState(item);
}
return map;
}
int CircularNeed(int current, int target, int mod)
{
int d = (target - current) % mod;
if (d < 0) d += mod;
return d;
}
int CalcObjective(Dictionary<long, int> current, Dictionary<long, int> target, int cycle)
{
int s = 0;
foreach (var kv in target)
{
if (!current.ContainsKey(kv.Key)) continue;
s += CircularNeed(current[kv.Key], kv.Value, cycle);
}
return s;
}
bool WaitUntilAt(int tx, int ty, int timeoutMs)
{
int elapsed = 0;
while (elapsed < timeoutMs)
{
if (Self != null && Self.Location != null && Self.Location.X == tx && Self.Location.Y == ty)
return true;
Delay(120);
elapsed += 120;
}
return false;
}
int Dist(int x1, int y1, int x2, int y2)
{
return Math.Abs(x1 - x2) + Math.Abs(y1 - y2);
}
Log("=== Color Pattern Walker Solver ===");
if (WAIT_FOR_SPAWN_START)
WaitForRoundSpawn(SPAWN_X, SPAWN_Y, SPAWN_WAIT_TIMEOUT_MS);
var allTiles = ReadAllFloorTiles();
if (allTiles.Count == 0)
{
Log("ERROR: No floor items found.");
return;
}
LogRoomScan(allTiles);
Cluster top, bottom;
if (!TryDetectBoards(allTiles, SPAWN_X, SPAWN_Y, out top, out bottom))
{
Log("ERROR: Could not auto-detect top template board + bottom play board.");
Log("Tip: run ColorPuzzleScanner first and tell me tile Kind/Name, then I hard-bind it.");
return;
}
Log($"Detected kind: {top.Kind}");
Log($"Top tiles: {top.Tiles.Count}, avgZ={top.AvgZ:F2}");
Log($"Bottom tiles: {bottom.Tiles.Count}, avgZ={bottom.AvgZ:F2}");
Dictionary<long, int> targetById;
List<Tile> bottomTiles;
if (!TryBuildTargetMap(top, bottom, out targetById, out bottomTiles))
{
Log("ERROR: Could not map top pattern to bottom board.");
return;
}
var targetIds = new HashSet<long>(targetById.Keys);
var current = ReadCurrentStatesById(targetIds);
if (current.Count == 0)
{
Log("ERROR: Could not read current bottom states.");
return;
}
int maxStateSeen = 0;
foreach (var v in targetById.Values) if (v > maxStateSeen) maxStateSeen = v;
foreach (var v in current.Values) if (v > maxStateSeen) maxStateSeen = v;
int cycle = maxStateSeen + 1;
if (cycle < 2 || cycle > 8) cycle = 4;
Log($"Mapped tiles: {targetById.Count}");
Log($"State cycle guessed: {cycle}");
int objective = CalcObjective(current, targetById, cycle);
Log($"Initial distance: {objective}");
if (objective == 0)
{
Log("Already solved.");
return;
}
if (Self == null || Self.Location == null)
{
Log("ERROR: Self position unavailable.");
return;
}
var rng = new Random();
int stagnation = 0;
const int MAX_MOVES = 1200;
for (int step = 1; step <= MAX_MOVES; step++)
{
var meX = Self?.Location?.X ?? -1;
var meY = Self?.Location?.Y ?? -1;
Tile chosen = null;
var needTiles = bottomTiles
.Where(t => current.ContainsKey(t.Id) && targetById.ContainsKey(t.Id))
.Select(t => new {
Tile = t,
Need = CircularNeed(current[t.Id], targetById[t.Id], cycle),
D = (meX >= 0 && meY >= 0) ? Dist(meX, meY, t.X, t.Y) : 9999
})
.Where(x => x.Need > 0)
.OrderByDescending(x => x.Need)
.ThenBy(x => x.D)
.ToList();
if (needTiles.Count == 0)
{
Log("Solved (need list empty).");
break;
}
if (stagnation >= 12)
{
chosen = needTiles[rng.Next(needTiles.Count)].Tile;
}
else
{
chosen = needTiles[0].Tile;
}
Log($"[{step}] Move to ({chosen.X},{chosen.Y}) id={chosen.Id} state={current[chosen.Id]} target={targetById[chosen.Id]}");
Move(chosen.X, chosen.Y);
bool arrived = WaitUntilAt(chosen.X, chosen.Y, 2800);
if (!arrived)
{
Move(chosen.X, chosen.Y);
WaitUntilAt(chosen.X, chosen.Y, 2000);
}
Delay(220);
current = ReadCurrentStatesById(targetIds);
int newObj = CalcObjective(current, targetById, cycle);
int delta = objective - newObj;
Log($" distance: {objective} -> {newObj} (delta {delta})");
if (newObj <= 0)
{
Log("=== Done: bottom now matches top pattern ===");
return;
}
if (newObj < objective) stagnation = 0;
else stagnation++;
objective = newObj;
Delay(120);
}
var finalStates = ReadCurrentStatesById(targetIds);
int finalObj = CalcObjective(finalStates, targetById, cycle);
if (finalObj == 0)
Log("=== Done: bottom now matches top pattern ===");
else
Log($"Stopped. Remaining distance: {finalObj}. Re-run script to continue.");

View File

@ -0,0 +1,482 @@
using System;
using System.Collections.Generic;
using System.Linq;
// Color Puzzle Solver v2
// - Auto calibration of arrow -> move mapping
// - Waits for real state change after every click
const int TILE_KIND = 3696;
const int ARROW_KIND = 17851;
const int GRID_X_MIN = 36;
const int GRID_X_MAX = 39;
const int GRID_Y_MIN = 27;
const int GRID_Y_MAX = 30;
const int CLICK_SETTLE_DELAY_MS = 250;
const int WAIT_CHANGE_TIMEOUT_MS = 6000;
const int WAIT_CHANGE_POLL_MS = 120;
const int MAX_STEPS = 140;
const int BFS_MAX_NODES = 4_000_000;
const int IDA_MAX_SEC = 12;
const bool AUTO_QUEUE_START = true;
const long TRANSPORTER_ID = 759030883;
const int QUEUE_CLICK_INTERVAL_MS = 5000;
const int WAIT_PUZZLE_POLL_MS = 250;
const int WAIT_PUZZLE_LOG_MS = 5000;
const bool REQUIRE_SELF_IN_PLAYZONE = true;
const int PLAY_X_MIN = 34;
const int PLAY_X_MAX = 41;
const int PLAY_Y_MIN = 26;
const int PLAY_Y_MAX = 31;
const bool REQUIRE_SELF_MIN_Z = true;
const double SELF_MIN_Z = 17.0;
int GetState(dynamic item)
{
try { return int.Parse(item.State?.ToString() ?? "0"); }
catch { return 0; }
}
int GetKind(dynamic item)
{
try { return (int)item.Kind; }
catch { return -1; }
}
uint EncodeGrid(int[,] g)
{
uint s = 0;
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
s |= ((uint)(g[r, c] & 3)) << (2 * (r * 4 + c));
return s;
}
bool TryReadGrid(out uint state, out string dump)
{
int[,] grid = new int[4, 4];
bool[,] found = new bool[4, 4];
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != TILE_KIND) continue;
int x = item.Location.X;
int y = item.Location.Y;
double z = item.Location.Z;
if (x < GRID_X_MIN || x > GRID_X_MAX) continue;
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
if (z < 18.4) continue;
int row = y - GRID_Y_MIN;
int col = x - GRID_X_MIN;
grid[row, col] = GetState(item);
found[row, col] = true;
}
int cnt = 0;
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
if (found[r, c]) cnt++;
if (cnt < 16)
{
state = 0;
dump = "";
return false;
}
state = EncodeGrid(grid);
dump = string.Join(" | ", Enumerable.Range(0, 4).Select(r =>
$"R{r}[{grid[r,0]},{grid[r,1]},{grid[r,2]},{grid[r,3]}]"));
return true;
}
uint RowLeft(uint s, int r)
{
int sh = r * 8;
uint row = (s >> sh) & 0xFFu;
uint rot = ((row >> 2) | (row << 6)) & 0xFFu;
return (s & ~(0xFFu << sh)) | (rot << sh);
}
uint RowRight(uint s, int r)
{
int sh = r * 8;
uint row = (s >> sh) & 0xFFu;
uint rot = ((row << 2) | (row >> 6)) & 0xFFu;
return (s & ~(0xFFu << sh)) | (rot << sh);
}
uint ColUp(uint s, int c)
{
int b = c * 2;
uint v0 = (s >> b) & 3u;
uint v1 = (s >> (b + 8)) & 3u;
uint v2 = (s >> (b + 16)) & 3u;
uint v3 = (s >> (b + 24)) & 3u;
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
return (s & mask) | (v1 << b) | (v2 << (b + 8)) | (v3 << (b + 16)) | (v0 << (b + 24));
}
uint ColDown(uint s, int c)
{
int b = c * 2;
uint v0 = (s >> b) & 3u;
uint v1 = (s >> (b + 8)) & 3u;
uint v2 = (s >> (b + 16)) & 3u;
uint v3 = (s >> (b + 24)) & 3u;
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
return (s & mask) | (v3 << b) | (v0 << (b + 8)) | (v1 << (b + 16)) | (v2 << (b + 24));
}
uint ApplyMove(uint s, int m)
{
if (m < 4) return RowLeft(s, m);
if (m < 8) return RowRight(s, m - 4);
if (m < 12) return ColUp(s, m - 8);
return ColDown(s, m - 12);
}
int InverseMove(int m)
{
if (m < 4) return m + 4;
if (m < 8) return m - 4;
if (m < 12) return m + 4;
return m - 4;
}
string MoveName(int m)
{
if (m < 4) return $"Row{m} LEFT";
if (m < 8) return $"Row{m - 4} RIGHT";
if (m < 12) return $"Col{m - 8} UP";
return $"Col{m - 12} DOWN";
}
int DetectMove(uint before, uint after)
{
int hit = -1;
for (int m = 0; m < 16; m++)
{
if (ApplyMove(before, m) != after) continue;
if (hit != -1) return -2;
hit = m;
}
return hit;
}
List<int> SolveBfs(uint start, uint goal)
{
if (start == goal) return new List<int>();
var visited = new Dictionary<uint, (uint parent, int move)>();
var queue = new Queue<uint>();
visited[start] = (start, -1);
queue.Enqueue(start);
int nodes = 0;
bool found = false;
while (queue.Count > 0 && nodes < BFS_MAX_NODES)
{
uint cur = queue.Dequeue();
nodes++;
for (int m = 0; m < 16; m++)
{
uint nxt = ApplyMove(cur, m);
if (visited.ContainsKey(nxt)) continue;
visited[nxt] = (cur, m);
if (nxt == goal)
{
found = true;
queue.Clear();
break;
}
queue.Enqueue(nxt);
}
}
if (!found) return null;
var sol = new List<int>();
uint s = goal;
while (s != start)
{
var p = visited[s];
sol.Add(p.move);
s = p.parent;
}
sol.Reverse();
return sol;
}
List<int> SolveIda(uint start, uint goal)
{
if (start == goal) return new List<int>();
var t0 = DateTime.Now;
int H(uint st)
{
int mis = 0;
for (int i = 0; i < 16; i++)
{
int a = (int)((st >> (i * 2)) & 3u);
int b = (int)((goal >> (i * 2)) & 3u);
if (a != b) mis++;
}
return (mis + 3) / 4;
}
List<int> best = null;
bool timeout = false;
bool Dfs(uint st, List<int> path, int maxDepth)
{
if (timeout) return false;
if ((DateTime.Now - t0).TotalSeconds > IDA_MAX_SEC)
{
timeout = true;
return false;
}
if (st == goal)
{
best = new List<int>(path);
return true;
}
int h = H(st);
if (path.Count + h > maxDepth) return false;
int block = path.Count > 0 ? InverseMove(path[path.Count - 1]) : -1;
for (int m = 0; m < 16; m++)
{
if (m == block) continue;
path.Add(m);
if (Dfs(ApplyMove(st, m), path, maxDepth)) return true;
path.RemoveAt(path.Count - 1);
if (timeout) return false;
}
return false;
}
int d0 = H(start);
for (int d = d0; d <= 22 && !timeout; d++)
{
if (Dfs(start, new List<int>(), d)) break;
}
return best;
}
List<int> Solve(uint start, uint goal)
{
var bfs = SolveBfs(start, goal);
if (bfs != null) return bfs;
return SolveIda(start, goal);
}
bool ClickAndWaitChange(long furniId, uint before, out uint after, out string dumpAfter)
{
Send(Out["ClickFurni"], (int)furniId, 0);
Delay(CLICK_SETTLE_DELAY_MS);
int waited = 0;
while (waited < WAIT_CHANGE_TIMEOUT_MS)
{
if (TryReadGrid(out after, out dumpAfter) && after != before)
return true;
Delay(WAIT_CHANGE_POLL_MS);
waited += WAIT_CHANGE_POLL_MS;
}
after = before;
dumpAfter = "";
return false;
}
Dictionary<string, long> ReadArrowIds()
{
var arrowIds = new Dictionary<string, long>();
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != ARROW_KIND) continue;
int x = item.Location.X;
int y = item.Location.Y;
if (y == GRID_Y_MIN - 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
arrowIds[$"up_{x - GRID_X_MIN}"] = item.Id;
else if (y == GRID_Y_MAX + 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
arrowIds[$"down_{x - GRID_X_MIN}"] = item.Id;
else if (x == GRID_X_MIN - 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
arrowIds[$"left_{y - GRID_Y_MIN}"] = item.Id;
else if (x == GRID_X_MAX + 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
arrowIds[$"right_{y - GRID_Y_MIN}"] = item.Id;
}
return arrowIds;
}
bool IsSelfInPlayZone()
{
try
{
int x = Self.Location.X;
int y = Self.Location.Y;
double z = Self.Location.Z;
bool inRect = x >= PLAY_X_MIN && x <= PLAY_X_MAX && y >= PLAY_Y_MIN && y <= PLAY_Y_MAX;
bool inZ = !REQUIRE_SELF_MIN_Z || z >= SELF_MIN_Z;
return inRect && inZ;
}
catch
{
return false;
}
}
Log("=== Color Puzzle Auto-Solver (AutoCalib + WaitChange) ===");
Dictionary<string, long> arrowIds = null;
uint current;
string dumpNow;
int sinceQueueClick = QUEUE_CLICK_INTERVAL_MS;
int sinceLog = WAIT_PUZZLE_LOG_MS;
while (true)
{
bool hasGrid = TryReadGrid(out current, out dumpNow);
var probeArrows = ReadArrowIds();
bool hasArrows = probeArrows.Count == 16;
bool inPlayZone = !REQUIRE_SELF_IN_PLAYZONE || IsSelfInPlayZone();
if (hasGrid && hasArrows && inPlayZone)
{
arrowIds = probeArrows;
break;
}
if (AUTO_QUEUE_START && sinceQueueClick >= QUEUE_CLICK_INTERVAL_MS)
{
Send(Out["ClickFurni"], (int)TRANSPORTER_ID, 0);
Log($"Queue: Klick Transporter {TRANSPORTER_ID}...");
sinceQueueClick = 0;
}
if (sinceLog >= WAIT_PUZZLE_LOG_MS)
{
string selfPos = "?";
try { selfPos = $"{Self.Location.X},{Self.Location.Y},{Self.Location.Z:F2}"; } catch { }
Log($"Warte auf Spielstart... Grid={(hasGrid ? "ok" : "no")}, Pfeile={probeArrows.Count}/16, InZone={(inPlayZone ? "yes" : "no")}, Self={selfPos}");
sinceLog = 0;
}
Delay(WAIT_PUZZLE_POLL_MS);
sinceQueueClick += WAIT_PUZZLE_POLL_MS;
sinceLog += WAIT_PUZZLE_POLL_MS;
}
Log("Puzzle erkannt. Starte Solver...");
Log($"Pfeile: {arrowIds.Count}/16");
int[] targetRows = new int[4];
bool targetFound = false;
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != TILE_KIND) continue;
if (item.Location.X != 41) continue;
int y = item.Location.Y;
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
targetRows[y - GRID_Y_MIN] = GetState(item);
targetFound = true;
}
if (!targetFound) targetRows = new[] { 1, 2, 3, 0 };
int[,] tgt = new int[4, 4];
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
tgt[r, c] = targetRows[r];
uint goal = EncodeGrid(tgt);
Log($"Ziel: R0={targetRows[0]}, R1={targetRows[1]}, R2={targetRows[2]}, R3={targetRows[3]}");
Log($"Start: {dumpNow}");
var moveToKey = new Dictionary<int, string>();
var keyToMove = new Dictionary<string, int>();
var allKeys = arrowIds.Keys.OrderBy(k => k).ToList();
for (int step = 1; step <= MAX_STEPS; step++)
{
if (current == goal)
{
Log("=== Geloest: alle 4 Reihen korrekt ===");
return;
}
var plan = Solve(current, goal);
if (plan == null || plan.Count == 0)
{
Log("ERROR: Kein Plan vom aktuellen Zustand.");
return;
}
int wanted = plan[0];
string key;
bool probing = false;
if (moveToKey.ContainsKey(wanted))
{
key = moveToKey[wanted];
}
else
{
key = allKeys.FirstOrDefault(k => !keyToMove.ContainsKey(k));
if (key == null)
{
key = allKeys[0];
}
probing = true;
}
long id = arrowIds[key];
Log($"[{step}] want {MoveName(wanted)} | click {key}" + (probing ? " (probe)" : ""));
if (!ClickAndWaitChange(id, current, out uint after, out string dumpAfter))
{
Log(" Kein Move erkannt (Timeout), gleicher Schritt nochmal.");
continue;
}
int actual = DetectMove(current, after);
if (actual >= 0)
{
moveToKey[actual] = key;
keyToMove[key] = actual;
if (actual != wanted)
Log($" AutoCalib: {key} == {MoveName(actual)} (nicht {MoveName(wanted)})");
}
else if (actual == -1)
{
Log($" Unbekannter Transition-Delta, weiter mit Re-Plan. State: {dumpAfter}");
}
else
{
Log($" Mehrdeutiger Delta, weiter mit Re-Plan. State: {dumpAfter}");
}
current = after;
if (step % 10 == 0)
Log($" Calib: {moveToKey.Count}/16 Moves gemappt");
}
Log("Nicht fertig in MAX_STEPS. Script einfach nochmal starten.");

View File

@ -0,0 +1,415 @@
using System;
using System.Collections.Generic;
using System.Linq;
// Color Puzzle Solver v2
// - Auto calibration of arrow -> move mapping
// - Waits for real state change after every click
const int TILE_KIND = 3696;
const int ARROW_KIND = 17851;
const int GRID_X_MIN = 36;
const int GRID_X_MAX = 39;
const int GRID_Y_MIN = 27;
const int GRID_Y_MAX = 30;
const int CLICK_SETTLE_DELAY_MS = 250;
const int WAIT_CHANGE_TIMEOUT_MS = 6000;
const int WAIT_CHANGE_POLL_MS = 120;
const int MAX_STEPS = 140;
const int BFS_MAX_NODES = 4_000_000;
const int IDA_MAX_SEC = 12;
int GetState(dynamic item)
{
try { return int.Parse(item.State?.ToString() ?? "0"); }
catch { return 0; }
}
int GetKind(dynamic item)
{
try { return (int)item.Kind; }
catch { return -1; }
}
uint EncodeGrid(int[,] g)
{
uint s = 0;
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
s |= ((uint)(g[r, c] & 3)) << (2 * (r * 4 + c));
return s;
}
bool TryReadGrid(out uint state, out string dump)
{
int[,] grid = new int[4, 4];
bool[,] found = new bool[4, 4];
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != TILE_KIND) continue;
int x = item.Location.X;
int y = item.Location.Y;
double z = item.Location.Z;
if (x < GRID_X_MIN || x > GRID_X_MAX) continue;
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
if (z < 18.4) continue;
int row = y - GRID_Y_MIN;
int col = x - GRID_X_MIN;
grid[row, col] = GetState(item);
found[row, col] = true;
}
int cnt = 0;
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
if (found[r, c]) cnt++;
if (cnt < 16)
{
state = 0;
dump = "";
return false;
}
state = EncodeGrid(grid);
dump = string.Join(" | ", Enumerable.Range(0, 4).Select(r =>
$"R{r}[{grid[r,0]},{grid[r,1]},{grid[r,2]},{grid[r,3]}]"));
return true;
}
uint RowLeft(uint s, int r)
{
int sh = r * 8;
uint row = (s >> sh) & 0xFFu;
uint rot = ((row >> 2) | (row << 6)) & 0xFFu;
return (s & ~(0xFFu << sh)) | (rot << sh);
}
uint RowRight(uint s, int r)
{
int sh = r * 8;
uint row = (s >> sh) & 0xFFu;
uint rot = ((row << 2) | (row >> 6)) & 0xFFu;
return (s & ~(0xFFu << sh)) | (rot << sh);
}
uint ColUp(uint s, int c)
{
int b = c * 2;
uint v0 = (s >> b) & 3u;
uint v1 = (s >> (b + 8)) & 3u;
uint v2 = (s >> (b + 16)) & 3u;
uint v3 = (s >> (b + 24)) & 3u;
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
return (s & mask) | (v1 << b) | (v2 << (b + 8)) | (v3 << (b + 16)) | (v0 << (b + 24));
}
uint ColDown(uint s, int c)
{
int b = c * 2;
uint v0 = (s >> b) & 3u;
uint v1 = (s >> (b + 8)) & 3u;
uint v2 = (s >> (b + 16)) & 3u;
uint v3 = (s >> (b + 24)) & 3u;
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
return (s & mask) | (v3 << b) | (v0 << (b + 8)) | (v1 << (b + 16)) | (v2 << (b + 24));
}
uint ApplyMove(uint s, int m)
{
if (m < 4) return RowLeft(s, m);
if (m < 8) return RowRight(s, m - 4);
if (m < 12) return ColUp(s, m - 8);
return ColDown(s, m - 12);
}
int InverseMove(int m)
{
if (m < 4) return m + 4;
if (m < 8) return m - 4;
if (m < 12) return m + 4;
return m - 4;
}
string MoveName(int m)
{
if (m < 4) return $"Row{m} LEFT";
if (m < 8) return $"Row{m - 4} RIGHT";
if (m < 12) return $"Col{m - 8} UP";
return $"Col{m - 12} DOWN";
}
int DetectMove(uint before, uint after)
{
int hit = -1;
for (int m = 0; m < 16; m++)
{
if (ApplyMove(before, m) != after) continue;
if (hit != -1) return -2;
hit = m;
}
return hit;
}
List<int> SolveBfs(uint start, uint goal)
{
if (start == goal) return new List<int>();
var visited = new Dictionary<uint, (uint parent, int move)>();
var queue = new Queue<uint>();
visited[start] = (start, -1);
queue.Enqueue(start);
int nodes = 0;
bool found = false;
while (queue.Count > 0 && nodes < BFS_MAX_NODES)
{
uint cur = queue.Dequeue();
nodes++;
for (int m = 0; m < 16; m++)
{
uint nxt = ApplyMove(cur, m);
if (visited.ContainsKey(nxt)) continue;
visited[nxt] = (cur, m);
if (nxt == goal)
{
found = true;
queue.Clear();
break;
}
queue.Enqueue(nxt);
}
}
if (!found) return null;
var sol = new List<int>();
uint s = goal;
while (s != start)
{
var p = visited[s];
sol.Add(p.move);
s = p.parent;
}
sol.Reverse();
return sol;
}
List<int> SolveIda(uint start, uint goal)
{
if (start == goal) return new List<int>();
var t0 = DateTime.Now;
int H(uint st)
{
int mis = 0;
for (int i = 0; i < 16; i++)
{
int a = (int)((st >> (i * 2)) & 3u);
int b = (int)((goal >> (i * 2)) & 3u);
if (a != b) mis++;
}
return (mis + 3) / 4;
}
List<int> best = null;
bool timeout = false;
bool Dfs(uint st, List<int> path, int maxDepth)
{
if (timeout) return false;
if ((DateTime.Now - t0).TotalSeconds > IDA_MAX_SEC)
{
timeout = true;
return false;
}
if (st == goal)
{
best = new List<int>(path);
return true;
}
int h = H(st);
if (path.Count + h > maxDepth) return false;
int block = path.Count > 0 ? InverseMove(path[path.Count - 1]) : -1;
for (int m = 0; m < 16; m++)
{
if (m == block) continue;
path.Add(m);
if (Dfs(ApplyMove(st, m), path, maxDepth)) return true;
path.RemoveAt(path.Count - 1);
if (timeout) return false;
}
return false;
}
int d0 = H(start);
for (int d = d0; d <= 22 && !timeout; d++)
{
if (Dfs(start, new List<int>(), d)) break;
}
return best;
}
List<int> Solve(uint start, uint goal)
{
var bfs = SolveBfs(start, goal);
if (bfs != null) return bfs;
return SolveIda(start, goal);
}
bool ClickAndWaitChange(long furniId, uint before, out uint after, out string dumpAfter)
{
Send(Out["ClickFurni"], (int)furniId, 0);
Delay(CLICK_SETTLE_DELAY_MS);
int waited = 0;
while (waited < WAIT_CHANGE_TIMEOUT_MS)
{
if (TryReadGrid(out after, out dumpAfter) && after != before)
return true;
Delay(WAIT_CHANGE_POLL_MS);
waited += WAIT_CHANGE_POLL_MS;
}
after = before;
dumpAfter = "";
return false;
}
Log("=== Color Puzzle Auto-Solver (AutoCalib + WaitChange) ===");
var arrowIds = new Dictionary<string, long>();
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != ARROW_KIND) continue;
int x = item.Location.X;
int y = item.Location.Y;
if (y == GRID_Y_MIN - 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
arrowIds[$"up_{x - GRID_X_MIN}"] = item.Id;
else if (y == GRID_Y_MAX + 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
arrowIds[$"down_{x - GRID_X_MIN}"] = item.Id;
else if (x == GRID_X_MIN - 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
arrowIds[$"left_{y - GRID_Y_MIN}"] = item.Id;
else if (x == GRID_X_MAX + 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
arrowIds[$"right_{y - GRID_Y_MIN}"] = item.Id;
}
Log($"Pfeile: {arrowIds.Count}/16");
if (arrowIds.Count < 16)
{
Log("ERROR: Nicht alle 16 Pfeile gefunden.");
return;
}
int[] targetRows = new int[4];
bool targetFound = false;
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != TILE_KIND) continue;
if (item.Location.X != 41) continue;
int y = item.Location.Y;
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
targetRows[y - GRID_Y_MIN] = GetState(item);
targetFound = true;
}
if (!targetFound) targetRows = new[] { 1, 2, 3, 0 };
int[,] tgt = new int[4, 4];
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
tgt[r, c] = targetRows[r];
uint goal = EncodeGrid(tgt);
Log($"Ziel: R0={targetRows[0]}, R1={targetRows[1]}, R2={targetRows[2]}, R3={targetRows[3]}");
if (!TryReadGrid(out uint current, out string dumpNow))
{
Log("ERROR: Grid nicht lesbar.");
return;
}
Log($"Start: {dumpNow}");
var moveToKey = new Dictionary<int, string>();
var keyToMove = new Dictionary<string, int>();
var allKeys = arrowIds.Keys.OrderBy(k => k).ToList();
for (int step = 1; step <= MAX_STEPS; step++)
{
if (current == goal)
{
Log("=== Geloest: alle 4 Reihen korrekt ===");
return;
}
var plan = Solve(current, goal);
if (plan == null || plan.Count == 0)
{
Log("ERROR: Kein Plan vom aktuellen Zustand.");
return;
}
int wanted = plan[0];
string key;
bool probing = false;
if (moveToKey.ContainsKey(wanted))
{
key = moveToKey[wanted];
}
else
{
key = allKeys.FirstOrDefault(k => !keyToMove.ContainsKey(k));
if (key == null)
{
key = allKeys[0];
}
probing = true;
}
long id = arrowIds[key];
Log($"[{step}] want {MoveName(wanted)} | click {key}" + (probing ? " (probe)" : ""));
if (!ClickAndWaitChange(id, current, out uint after, out string dumpAfter))
{
Log(" Kein Move erkannt (Timeout), gleicher Schritt nochmal.");
continue;
}
int actual = DetectMove(current, after);
if (actual >= 0)
{
moveToKey[actual] = key;
keyToMove[key] = actual;
if (actual != wanted)
Log($" AutoCalib: {key} == {MoveName(actual)} (nicht {MoveName(wanted)})");
}
else if (actual == -1)
{
Log($" Unbekannter Transition-Delta, weiter mit Re-Plan. State: {dumpAfter}");
}
else
{
Log($" Mehrdeutiger Delta, weiter mit Re-Plan. State: {dumpAfter}");
}
current = after;
if (step % 10 == 0)
Log($" Calib: {moveToKey.Count}/16 Moves gemappt");
}
Log("Nicht fertig in MAX_STEPS. Script einfach nochmal starten.");

View File

@ -0,0 +1,418 @@
using System;
using System.Collections.Generic;
using System.Linq;
// ============================================================
// COLOR PUZZLE AUTO-SOLVER (Loopover 4x4)
// Liest Grid + Ziel aus dem Raum, loest per BFS/IDA*,
// klickt die Pfeil-Buttons automatisch.
// ============================================================
const int TILE_KIND = 3696;
const int ARROW_KIND = 17851;
const int GRID_X_MIN = 36;
const int GRID_X_MAX = 39;
const int GRID_Y_MIN = 27;
const int GRID_Y_MAX = 30;
const int CLICK_DELAY = 700;
const int BFS_MAX_NODES = 8_000_000;
const int IDA_MAX_SEC = 15;
// Set true if arrows push tiles INTO the grid (opposite direction)
const bool REVERSE_ARROWS = false;
int GetState(dynamic item)
{
try { return int.Parse(item.State?.ToString() ?? "0"); }
catch { return 0; }
}
int GetKind(dynamic item)
{
try { return (int)item.Kind; }
catch { return -1; }
}
Log("=== Color Puzzle Auto-Solver ===");
// ── 1. Read puzzle grid from room ──────────────────────────
int[,] grid = new int[4, 4];
bool[,] gridFound = new bool[4, 4];
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != TILE_KIND) continue;
int x = item.Location.X, y = item.Location.Y;
double z = item.Location.Z;
if (x < GRID_X_MIN || x > GRID_X_MAX) continue;
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
if (z < 18.4) continue;
int col = x - GRID_X_MIN;
int row = y - GRID_Y_MIN;
grid[row, col] = GetState(item);
gridFound[row, col] = true;
}
int foundCount = 0;
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
if (gridFound[r, c]) foundCount++;
if (foundCount < 16)
{
Log($"ERROR: Nur {foundCount}/16 Grid-Tiles gefunden!");
Log("Bist du im richtigen Raum?");
return;
}
Log("Aktuelles Grid:");
for (int r = 0; r < 4; r++)
Log($" Row {r}: [{grid[r,0]}, {grid[r,1]}, {grid[r,2]}, {grid[r,3]}]");
// ── 2. Define *fixed* target pattern (ignore room indicators) ──────────
// Mapping from Scanner (Floor:3696):
// State 1 = grün, State 2 = rot, State 3 = blau, State 0 = bunt (3FarbenTile)
// Gewünschtes Endbild (von oben nach unten):
// Row 0: alles grün (1)
// Row 1: alles rot (2)
// Row 2: alles blau (3)
// Row 3: alles bunt (0)
int[,] target = new int[4, 4];
for (int c = 0; c < 4; c++)
{
target[0, c] = 1; // grün
target[1, c] = 2; // rot
target[2, c] = 3; // blau
target[3, c] = 0; // bunt
}
Log("Ziel-Grid (fest vorgegeben):");
for (int r = 0; r < 4; r++)
Log($" Row {r}: [{target[r,0]}, {target[r,1]}, {target[r,2]}, {target[r,3]}]");
// ── 3. Read arrow button IDs ──────────────────────────────
var arrowIds = new Dictionary<string, long>();
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != ARROW_KIND) continue;
int x = item.Location.X, y = item.Location.Y;
if (y == GRID_Y_MIN - 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
arrowIds[$"up_{x - GRID_X_MIN}"] = item.Id;
else if (y == GRID_Y_MAX + 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
arrowIds[$"down_{x - GRID_X_MIN}"] = item.Id;
else if (x == GRID_X_MIN - 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
arrowIds[$"left_{y - GRID_Y_MIN}"] = item.Id;
else if (x == GRID_X_MAX + 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
arrowIds[$"right_{y - GRID_Y_MIN}"] = item.Id;
}
Log($"Arrow-Buttons gefunden: {arrowIds.Count}/16");
if (arrowIds.Count < 16)
{
Log("WARN: Nicht alle 16 Pfeile gefunden!");
foreach (var kv in arrowIds) Log($" {kv.Key} = {kv.Value}");
}
// ── 4. State encoding (2 bits per cell, 32 bits total) ────
// Bits 0-1: grid[0,0], Bits 2-3: grid[0,1], ... Bits 30-31: grid[3,3]
// Row r = bits [r*8 .. r*8+7]
uint Encode(int[,] g)
{
uint s = 0;
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
s |= ((uint)(g[r, c] & 3)) << (2 * (r * 4 + c));
return s;
}
// ── 5. Bit-manipulation move functions ────────────────────
// RowLeft: [c0,c1,c2,c3] → [c1,c2,c3,c0] = rotate byte RIGHT by 2
uint RowLeft(uint s, int r)
{
int sh = r * 8;
uint row = (s >> sh) & 0xFFu;
uint rot = ((row >> 2) | (row << 6)) & 0xFFu;
return (s & ~(0xFFu << sh)) | (rot << sh);
}
// RowRight: [c0,c1,c2,c3] → [c3,c0,c1,c2] = rotate byte LEFT by 2
uint RowRight(uint s, int r)
{
int sh = r * 8;
uint row = (s >> sh) & 0xFFu;
uint rot = ((row << 2) | (row >> 6)) & 0xFFu;
return (s & ~(0xFFu << sh)) | (rot << sh);
}
// ColUp: [r0,r1,r2,r3] → [r1,r2,r3,r0]
uint ColUp(uint s, int c)
{
int b = c * 2;
uint v0 = (s >> b) & 3u;
uint v1 = (s >> (b + 8)) & 3u;
uint v2 = (s >> (b + 16)) & 3u;
uint v3 = (s >> (b + 24)) & 3u;
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
return (s & mask) | (v1 << b) | (v2 << (b + 8)) | (v3 << (b + 16)) | (v0 << (b + 24));
}
// ColDown: [r0,r1,r2,r3] → [r3,r0,r1,r2]
uint ColDown(uint s, int c)
{
int b = c * 2;
uint v0 = (s >> b) & 3u;
uint v1 = (s >> (b + 8)) & 3u;
uint v2 = (s >> (b + 16)) & 3u;
uint v3 = (s >> (b + 24)) & 3u;
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
return (s & mask) | (v3 << b) | (v0 << (b + 8)) | (v1 << (b + 16)) | (v2 << (b + 24));
}
// Move encoding: 0-3=RowLeft(0-3), 4-7=RowRight(0-3), 8-11=ColUp(0-3), 12-15=ColDown(0-3)
uint ApplyMove(uint s, int m)
{
if (m < 4) return RowLeft(s, m);
if (m < 8) return RowRight(s, m - 4);
if (m < 12) return ColUp(s, m - 8);
return ColDown(s, m - 12);
}
int InverseMove(int m)
{
if (m < 4) return m + 4;
if (m < 8) return m - 4;
if (m < 12) return m + 4;
return m - 4;
}
string MoveName(int m)
{
if (m < 4) return $"Row{m} LEFT";
if (m < 8) return $"Row{m-4} RIGHT";
if (m < 12) return $"Col{m-8} UP";
return $"Col{m-12} DOWN";
}
// ── 6. Solve ──────────────────────────────────────────────
uint startState = Encode(grid);
uint goalState = Encode(target);
if (startState == goalState)
{
Log("Puzzle ist bereits geloest!");
return;
}
List<int> solution = null;
// ── 6a. BFS ───────────────────────────────────────────────
Log($"Starte BFS (max {BFS_MAX_NODES:N0} Nodes)...");
var startBfs = DateTime.Now;
var visited = new Dictionary<uint, (uint parent, int move)>();
var queue = new Queue<uint>();
visited[startState] = (startState, -1);
queue.Enqueue(startState);
bool solved = false;
int nodesExplored = 0;
while (queue.Count > 0 && !solved && nodesExplored < BFS_MAX_NODES)
{
uint current = queue.Dequeue();
nodesExplored++;
if (nodesExplored % 2_000_000 == 0)
Log($" BFS: {nodesExplored:N0} States, Queue: {queue.Count:N0}");
for (int m = 0; m < 16; m++)
{
uint next = ApplyMove(current, m);
if (visited.ContainsKey(next)) continue;
visited[next] = (current, m);
if (next == goalState)
{
solved = true;
break;
}
queue.Enqueue(next);
}
}
if (solved)
{
solution = new List<int>();
uint s = goalState;
while (s != startState)
{
var (parent, move) = visited[s];
solution.Add(move);
s = parent;
}
solution.Reverse();
var bfsTime = (DateTime.Now - startBfs).TotalMilliseconds;
Log($"BFS Loesung: {solution.Count} Moves in {bfsTime:F0}ms ({nodesExplored:N0} States)");
}
else
{
Log($"BFS: Keine Loesung in {nodesExplored:N0} Nodes.");
visited.Clear();
visited = null;
queue.Clear();
queue = null;
// ── 6b. IDA* Fallback ─────────────────────────────────
Log($"Starte IDA* (max {IDA_MAX_SEC}s)...");
var startIda = DateTime.Now;
int Heuristic(uint st)
{
int mis = 0;
for (int i = 0; i < 16; i++)
{
int sv = (int)((st >> (i * 2)) & 3u);
int gv = (int)((goalState >> (i * 2)) & 3u);
if (sv != gv) mis++;
}
return (mis + 3) / 4;
}
List<int> bestSol = null;
int bestLen = 30;
bool timeout = false;
bool DFS(uint state, List<int> moves, int maxDepth)
{
if (timeout) return false;
if ((DateTime.Now - startIda).TotalSeconds > IDA_MAX_SEC)
{
timeout = true;
return false;
}
if (state == goalState)
{
if (moves.Count < bestLen)
{
bestLen = moves.Count;
bestSol = new List<int>(moves);
}
return true;
}
int h = Heuristic(state);
if (moves.Count + h > maxDepth) return false;
if (moves.Count >= bestLen - 1) return false;
int lastInv = moves.Count > 0 ? InverseMove(moves[moves.Count - 1]) : -1;
bool found = false;
for (int m = 0; m < 16; m++)
{
if (m == lastInv) continue;
uint next = ApplyMove(state, m);
moves.Add(m);
if (DFS(next, moves, maxDepth)) found = true;
moves.RemoveAt(moves.Count - 1);
if (timeout) break;
}
return found;
}
int startH = Heuristic(startState);
for (int depth = startH; depth <= 20 && !timeout; depth++)
{
Log($" IDA* Tiefe {depth}...");
DFS(startState, new List<int>(), depth);
if (bestSol != null) break;
}
if (bestSol != null)
{
solution = bestSol;
var idaTime = (DateTime.Now - startIda).TotalMilliseconds;
Log($"IDA* Loesung: {solution.Count} Moves in {idaTime:F0}ms");
}
else
{
Log("ERROR: Keine Loesung gefunden!");
Log("Moegliche Gruende:");
Log(" - Puzzle-State hat sich geaendert");
Log(" - Target-Zuordnung ist falsch");
return;
}
}
// ── 7. Show solution ──────────────────────────────────────
Log("Loesungs-Schritte:");
for (int i = 0; i < solution.Count; i++)
Log($" {i+1}. {MoveName(solution[i])}");
// ── 8. Execute moves via ClickFurni ──────────────────────
Log("Fuehre Moves aus...");
foreach (int m in solution)
{
string dir;
int idx;
if (REVERSE_ARROWS)
{
// Reversed: solver says LEFT → click RIGHT arrow (push from right)
if (m < 4) { dir = "right"; idx = m; }
else if (m < 8) { dir = "left"; idx = m - 4; }
else if (m < 12) { dir = "down"; idx = m - 8; }
else { dir = "up"; idx = m - 12; }
}
else
{
// Normal: solver says LEFT → click LEFT arrow
if (m < 4) { dir = "left"; idx = m; }
else if (m < 8) { dir = "right"; idx = m - 4; }
else if (m < 12) { dir = "up"; idx = m - 8; }
else { dir = "down"; idx = m - 12; }
}
string key = $"{dir}_{idx}";
if (!arrowIds.ContainsKey(key))
{
Log($"ERROR: Arrow '{key}' nicht gefunden!");
return;
}
long arrowId = arrowIds[key];
Log($" Click: {MoveName(m)} -> {key} (ID: {arrowId})");
Send(Out["ClickFurni"], (int)arrowId, 0);
Delay(CLICK_DELAY);
}
// ── 9. Verify final grid ────────────────────────────────────
int[,] finalGrid = new int[4, 4];
bool[,] finalFound = new bool[4, 4];
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != TILE_KIND) continue;
int x = item.Location.X, y = item.Location.Y;
double z = item.Location.Z;
if (x < GRID_X_MIN || x > GRID_X_MAX) continue;
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
if (z < 18.4) continue;
int col = x - GRID_X_MIN;
int row = y - GRID_Y_MIN;
finalGrid[row, col] = GetState(item);
finalFound[row, col] = true;
}
Log("Finales Grid nach Ausfuehrung:");
for (int r = 0; r < 4; r++)
Log($" Row {r}: [{finalGrid[r,0]}, {finalGrid[r,1]}, {finalGrid[r,2]}, {finalGrid[r,3]}]");
Log("=== Puzzle geloest (internes Ziel erreicht) ===");

View File

@ -0,0 +1,512 @@
using System;
using System.Collections.Generic;
using System.Linq;
// ============================================================
// COLOR PUZZLE AUTO-SOLVER (Loopover 4x4)
// Layer-by-layer Ansatz: loest ALLE 4 Reihen zuverlaessig.
// Behaelt Anti-Desync + Auto-Flip-Erkennung bei.
// ============================================================
const int TILE_KIND = 3696;
const int ARROW_KIND = 17851;
const int GRID_X_MIN = 36;
const int GRID_X_MAX = 39;
const int GRID_Y_MIN = 27;
const int GRID_Y_MAX = 30;
const int CLICK_DELAY_MS = 950;
int GetState(dynamic item)
{
try { return int.Parse(item.State?.ToString() ?? "0"); }
catch { return 0; }
}
int GetKind(dynamic item)
{
try { return (int)item.Kind; }
catch { return -1; }
}
Log("=== Color Puzzle Auto-Solver (Layer-by-Layer) ===");
// ── 1. Read grid as array ─────────────────────────────────
int[,] ReadGridFromRoom()
{
int[,] g = new int[4, 4];
bool[,] found = new bool[4, 4];
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != TILE_KIND) continue;
int x = item.Location.X, y = item.Location.Y;
double z = item.Location.Z;
if (x < GRID_X_MIN || x > GRID_X_MAX) continue;
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
if (z < 18.4) continue;
g[y - GRID_Y_MIN, x - GRID_X_MIN] = GetState(item);
found[y - GRID_Y_MIN, x - GRID_X_MIN] = true;
}
int cnt = 0;
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
if (found[r, c]) cnt++;
if (cnt < 16) return null;
return g;
}
string GridDump(int[,] g)
{
return string.Join(" | ", Enumerable.Range(0, 4).Select(r =>
$"R{r}[{g[r,0]},{g[r,1]},{g[r,2]},{g[r,3]}]"));
}
var grid = ReadGridFromRoom();
if (grid == null)
{
Log("ERROR: Konnte Grid nicht lesen (nicht alle 16 Tiles gefunden).");
return;
}
Log($"Start: {GridDump(grid)}");
// ── 2. Read arrows ────────────────────────────────────────
var arrowIds = new Dictionary<string, long>();
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != ARROW_KIND) continue;
int x = item.Location.X, y = item.Location.Y;
if (y == GRID_Y_MIN - 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
arrowIds[$"up_{x - GRID_X_MIN}"] = item.Id;
else if (y == GRID_Y_MAX + 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
arrowIds[$"down_{x - GRID_X_MIN}"] = item.Id;
else if (x == GRID_X_MIN - 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
arrowIds[$"left_{y - GRID_Y_MIN}"] = item.Id;
else if (x == GRID_X_MAX + 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
arrowIds[$"right_{y - GRID_Y_MIN}"] = item.Id;
}
Log($"Pfeile: {arrowIds.Count}/16");
if (arrowIds.Count < 16) { Log("ERROR: Nicht alle Pfeile gefunden!"); return; }
// ── 3. Read target ────────────────────────────────────────
int[] targetRows = new int[4];
bool targetFound = false;
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != TILE_KIND) continue;
if (item.Location.X != 41) continue;
int y = item.Location.Y;
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
targetRows[y - GRID_Y_MIN] = GetState(item);
targetFound = true;
}
if (!targetFound) targetRows = new[] { 1, 2, 3, 0 };
Log($"Ziel: R0={targetRows[0]}, R1={targetRows[1]}, R2={targetRows[2]}, R3={targetRows[3]}");
// ── 4. Layer-by-Layer Solver ──────────────────────────────
// Move encoding: 0-3=RowLeft(0-3), 4-7=RowRight(0-3),
// 8-11=ColUp(0-3), 12-15=ColDown(0-3)
string MoveName(int m)
{
if (m < 4) return $"Row{m} LEFT";
if (m < 8) return $"Row{m-4} RIGHT";
if (m < 12) return $"Col{m-8} UP";
return $"Col{m-12} DOWN";
}
// Simulate a single move on a grid copy
void SimMove(int[,] g, int m)
{
if (m < 4) { // RowLeft
int r = m;
int t = g[r,0]; g[r,0]=g[r,1]; g[r,1]=g[r,2]; g[r,2]=g[r,3]; g[r,3]=t;
} else if (m < 8) { // RowRight
int r = m-4;
int t = g[r,3]; g[r,3]=g[r,2]; g[r,2]=g[r,1]; g[r,1]=g[r,0]; g[r,0]=t;
} else if (m < 12) { // ColUp
int c = m-8;
int t = g[0,c]; g[0,c]=g[1,c]; g[1,c]=g[2,c]; g[2,c]=g[3,c]; g[3,c]=t;
} else { // ColDown
int c = m-12;
int t = g[3,c]; g[3,c]=g[2,c]; g[2,c]=g[1,c]; g[1,c]=g[0,c]; g[0,c]=t;
}
}
List<int> SolveLayerByLayer(int[,] srcGrid, int[] tgtRows)
{
// Work on a copy
int[,] g = new int[4,4];
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
g[r,c] = srcGrid[r,c];
var moves = new List<int>();
void Do(int m) { moves.Add(m); SimMove(g, m); }
void DoRowRight(int r, int times) {
times = ((times % 4) + 4) % 4;
if (times == 3) { Do(r); return; } // 1x RowLeft is cheaper
for (int i = 0; i < times; i++) Do(r + 4);
}
void DoRowLeft(int r, int times) {
times = ((times % 4) + 4) % 4;
if (times == 3) { Do(r + 4); return; }
for (int i = 0; i < times; i++) Do(r);
}
void DoColUp(int c, int times) {
times = ((times % 4) + 4) % 4;
if (times == 3) { Do(c + 12); return; } // 1x ColDown is cheaper
for (int i = 0; i < times; i++) Do(c + 8);
}
void DoColDown(int c, int times) {
times = ((times % 4) + 4) % 4;
if (times == 3) { Do(c + 8); return; }
for (int i = 0; i < times; i++) Do(c + 12);
}
// ── Phase 1: Solve Row 0 ─────────────────────────────
// Use free column rotations + row shifts on rows 1-3.
int C0 = tgtRows[0];
for (int c = 0; c < 4; c++)
{
if (g[0,c] == C0) continue;
// Look in same column
int foundRow = -1;
for (int r = 1; r <= 3; r++)
if (g[r,c] == C0) { foundRow = r; break; }
if (foundRow >= 0)
{
DoColUp(c, foundRow);
}
else
{
// Find C0 anywhere in rows 1-3
bool found = false;
for (int r = 1; r <= 3 && !found; r++)
for (int c2 = 0; c2 < 4 && !found; c2++)
{
if (c2 == c) continue;
if (g[r,c2] == C0)
{
DoRowRight(r, (c - c2 + 4) % 4);
DoColUp(c, r);
found = true;
}
}
if (!found) return null; // should never happen
}
}
// ── Phase 2: Solve Row 1 (protecting Row 0) ──────────
// Commutator [RowLeft(1,k1), ColUp(c,k2), RowRight(1,k1), ColDown(c,k2)]
// creates a 3-cycle in rows 1+ only. Row 0 stays intact.
int C1 = tgtRows[1];
for (int pass = 0; pass < 4; pass++)
{
int colW = -1;
for (int c = 0; c < 4; c++)
if (g[1,c] != C1) { colW = c; break; }
if (colW < 0) break;
int srcR = -1, srcC = -1;
for (int r = 2; r <= 3 && srcR < 0; r++)
for (int c = 0; c < 4; c++)
if (g[r,c] == C1) { srcR = r; srcC = c; break; }
if (srcR < 0) return null;
// Move C1 to (srcR, colW) via row shift (safe: rows 2-3 only)
if (srcC != colW)
DoRowRight(srcR, (colW - srcC + 4) % 4);
int k2 = srcR - 1; // 1 or 2
DoRowLeft(1, 1);
DoColUp(colW, k2);
DoRowRight(1, 1);
DoColDown(colW, k2);
}
// ── Phase 3: Solve Rows 2-3 (protecting Rows 0-1) ───
// Commutator with r1=2, k2=1 only touches rows 2-3.
int C2 = tgtRows[2];
for (int pass = 0; pass < 4; pass++)
{
int colW = -1;
for (int c = 0; c < 4; c++)
if (g[2,c] != C2) { colW = c; break; }
if (colW < 0) break;
int srcC = -1;
for (int c = 0; c < 4; c++)
if (g[3,c] == C2) { srcC = c; break; }
if (srcC < 0) return null;
if (srcC != colW)
DoRowRight(3, (colW - srcC + 4) % 4);
DoRowLeft(2, 1);
DoColUp(colW, 1);
DoRowRight(2, 1);
DoColDown(colW, 1);
}
// Verify
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
if (g[r,c] != tgtRows[r]) return null;
// Optimize: remove consecutive inverse pairs
bool changed = true;
while (changed)
{
changed = false;
for (int i = 0; i < moves.Count - 1; i++)
{
int a = moves[i], b = moves[i+1];
bool cancel = false;
if (a < 4 && b == a + 4) cancel = true;
if (a >= 4 && a < 8 && b == a - 4) cancel = true;
if (a >= 8 && a < 12 && b == a + 4) cancel = true;
if (a >= 12 && b == a - 4) cancel = true;
if (cancel)
{
moves.RemoveAt(i + 1);
moves.RemoveAt(i);
changed = true;
break;
}
}
}
return moves;
}
// ── 5. Check if already solved ────────────────────────────
bool IsGridSolved(int[,] g)
{
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
if (g[r,c] != targetRows[r]) return false;
return true;
}
if (IsGridSolved(grid))
{
Log("Puzzle ist bereits geloest!");
return;
}
// ── 6. Solve ──────────────────────────────────────────────
var solution = SolveLayerByLayer(grid, targetRows);
if (solution == null || solution.Count == 0)
{
Log("ERROR: Solver konnte keine Loesung finden!");
Log("Moegliche Gruende: Farb-Verteilung nicht 4x je Farbe, oder falsche Ziel-Zuordnung.");
return;
}
Log($"Loesung gefunden: {solution.Count} Moves");
for (int i = 0; i < solution.Count; i++)
Log($" {i+1}. {MoveName(solution[i])}");
// ── 7. Execute with verification ──────────────────────────
// Track arrow direction flips (auto-detect reversed arrows)
bool[] rowFlip = new bool[4];
bool[] colFlip = new bool[4];
string KeyForMove(int m)
{
if (m < 4) {
int r = m;
return rowFlip[r] ? $"right_{r}" : $"left_{r}";
}
if (m < 8) {
int r = m - 4;
return rowFlip[r] ? $"left_{r}" : $"right_{r}";
}
if (m < 12) {
int c = m - 8;
return colFlip[c] ? $"down_{c}" : $"up_{c}";
}
int cc = m - 12;
return colFlip[cc] ? $"up_{cc}" : $"down_{cc}";
}
// Encode grid as uint for quick comparison
uint EncodeGrid(int[,] g)
{
uint s = 0;
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
s |= ((uint)(g[r,c] & 3)) << (2 * (r * 4 + c));
return s;
}
// Compute expected state after a move (using bit ops for speed)
uint ApplyMoveBits(uint s, int m)
{
if (m < 4) { // RowLeft
int sh = m * 8;
uint row = (s >> sh) & 0xFFu;
uint rot = ((row >> 2) | (row << 6)) & 0xFFu;
return (s & ~(0xFFu << sh)) | (rot << sh);
}
if (m < 8) { // RowRight
int sh = (m-4) * 8;
uint row = (s >> sh) & 0xFFu;
uint rot = ((row << 2) | (row >> 6)) & 0xFFu;
return (s & ~(0xFFu << sh)) | (rot << sh);
}
if (m < 12) { // ColUp
int b = (m-8) * 2;
uint v0=(s>>b)&3u, v1=(s>>(b+8))&3u, v2=(s>>(b+16))&3u, v3=(s>>(b+24))&3u;
uint mask = ~(3u<<b | 3u<<(b+8) | 3u<<(b+16) | 3u<<(b+24));
return (s&mask) | (v1<<b) | (v2<<(b+8)) | (v3<<(b+16)) | (v0<<(b+24));
}
{ // ColDown
int b = (m-12) * 2;
uint v0=(s>>b)&3u, v1=(s>>(b+8))&3u, v2=(s>>(b+16))&3u, v3=(s>>(b+24))&3u;
uint mask = ~(3u<<b | 3u<<(b+8) | 3u<<(b+16) | 3u<<(b+24));
return (s&mask) | (v3<<b) | (v0<<(b+8)) | (v1<<(b+16)) | (v2<<(b+24));
}
}
int InverseMove(int m)
{
if (m < 4) return m + 4;
if (m < 8) return m - 4;
if (m < 12) return m + 4;
return m - 4;
}
Log("\nFuehre Moves aus...");
uint currentState = EncodeGrid(grid);
uint goalState = EncodeGrid(new int[4,4]); // temp
{
int[,] tgt = new int[4,4];
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
tgt[r,c] = targetRows[r];
goalState = EncodeGrid(tgt);
}
int moveIdx = 0;
int retries = 0;
const int MAX_RETRIES = 3;
while (moveIdx < solution.Count)
{
if (currentState == goalState)
{
Log("=== Puzzle geloest! Alle 4 Reihen korrekt! ===");
return;
}
int move = solution[moveIdx];
uint expected = ApplyMoveBits(currentState, move);
string key = KeyForMove(move);
if (!arrowIds.ContainsKey(key))
{
Log($"ERROR: Arrow '{key}' nicht gefunden!");
return;
}
long id = arrowIds[key];
Log($" [{moveIdx+1}/{solution.Count}] {MoveName(move)} via {key}");
Send(Out["ClickFurni"], (int)id, 0);
Delay(CLICK_DELAY_MS);
// Re-read grid to verify
var newGrid = ReadGridFromRoom();
if (newGrid == null)
{
Log("WARN: Grid-Read fehlgeschlagen, retry...");
Delay(400);
continue;
}
uint afterState = EncodeGrid(newGrid);
if (afterState == expected)
{
// Move worked as expected
currentState = afterState;
moveIdx++;
retries = 0;
continue;
}
// Check if arrow direction was reversed
uint invExpected = ApplyMoveBits(currentState, InverseMove(move));
if (afterState == invExpected)
{
if (move < 8) {
int r = move < 4 ? move : move - 4;
rowFlip[r] = !rowFlip[r];
Log($" Auto-Fix: Row {r} Richtung gespiegelt.");
} else {
int c = move < 12 ? move - 8 : move - 12;
colFlip[c] = !colFlip[c];
Log($" Auto-Fix: Col {c} Richtung gespiegelt.");
}
currentState = afterState;
// Don't advance moveIdx - the move did the opposite, re-plan
Log(" Re-plane von neuem Zustand...");
grid = newGrid;
solution = SolveLayerByLayer(grid, targetRows);
if (solution == null) { Log("ERROR: Re-Plan fehlgeschlagen!"); return; }
moveIdx = 0;
retries = 0;
Log($" Neuer Plan: {solution.Count} Moves");
continue;
}
if (afterState == currentState)
{
// Click had no effect
retries++;
if (retries >= MAX_RETRIES)
{
Log("WARN: Klick ohne Effekt nach 3 Versuchen, re-plane...");
grid = newGrid;
solution = SolveLayerByLayer(grid, targetRows);
if (solution == null) { Log("ERROR: Re-Plan fehlgeschlagen!"); return; }
moveIdx = 0;
retries = 0;
}
else
{
Log(" Klick ohne Effekt, retry...");
Delay(300);
}
continue;
}
// Desync: grid changed unexpectedly (maybe another player or lag)
Log($" Desync! Neuer Zustand: {GridDump(newGrid)}");
Log(" Re-plane von neuem Zustand...");
grid = newGrid;
currentState = afterState;
if (IsGridSolved(grid))
{
Log("=== Puzzle geloest! Alle 4 Reihen korrekt! ===");
return;
}
solution = SolveLayerByLayer(grid, targetRows);
if (solution == null) { Log("ERROR: Re-Plan fehlgeschlagen!"); return; }
moveIdx = 0;
retries = 0;
Log($" Neuer Plan: {solution.Count} Moves");
}
if (currentState == goalState)
Log("=== Puzzle geloest! Alle 4 Reihen korrekt! ===");
else
Log("Alle Moves ausgefuehrt. Grid pruefen ob geloest.");

View File

@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
Log("started");
const string FURNI_NAME_CONTAINS_TEXT = "One Way Gate";
Regex mvRegex = new Regex(@"/mv (\d+),(\d+),([\d\.]+)");
// Cache: trigger-tile -> (gateId, gateX, gateY, direction)
Dictionary<(int x, int y), (long id, int gx, int gy, int dir)> triggerMap = new();
(int dx, int dy) GetTriggerOffset(int dir) => dir switch
{
0 => (0, -1),
2 => (1, 0),
4 => (0, 1),
6 => (-1, 0),
_ => (0, 0)
};
void RebuildGateCache()
{
triggerMap.Clear();
if (FloorItems == null) return;
foreach (var item in FloorItems)
{
if (item?.Location == null) continue;
string name = null;
try { name = item.GetName(); } catch { continue; }
if (name == null || !name.Contains(FURNI_NAME_CONTAINS_TEXT)) continue;
var (dx, dy) = GetTriggerOffset(item.Direction);
var trigger = (item.Location.X + dx, item.Location.Y + dy);
triggerMap[trigger] = (item.Id, item.Location.X, item.Location.Y, item.Direction);
}
Log($"Gate cache built: {triggerMap.Count} gates indexed");
}
void TryEnterGate(int userX, int userY)
{
if (triggerMap.TryGetValue((userX, userY), out var gate))
{
Log($"Match at ({userX},{userY}) for Gate ID {gate.id} at ({gate.gx},{gate.gy} Dir:{gate.dir}). Sending packet.");
Send(Out.EnterOneWayDoor, gate.id);
}
}
void HandleUserUpdate(dynamic e)
{
if (Self == null) return;
var packet = e.Packet;
int numUpdates = packet.ReadInt();
for (int i = 0; i < numUpdates; i++)
{
int entityIndex = packet.ReadInt();
int currentX = packet.ReadInt();
int currentY = packet.ReadInt();
packet.ReadString();
packet.ReadInt();
packet.ReadInt();
string action = packet.ReadString();
if (entityIndex == Self.Index)
{
int checkX = currentX, checkY = currentY;
Match match = mvRegex.Match(action);
if (match.Success)
{
int.TryParse(match.Groups[1].Value, out checkX);
int.TryParse(match.Groups[2].Value, out checkY);
}
TryEnterGate(checkX, checkY);
}
}
}
void HandleObjectUpdate(dynamic e)
{
if (Self?.Location == null) return;
var packet = e.Packet;
int furniId = packet.ReadInt();
packet.ReadInt();
int itemX = packet.ReadInt();
int itemY = packet.ReadInt();
int newDir = packet.ReadInt();
// Update cache for this specific gate
var item = FloorItems?.FirstOrDefault(f => f != null && f.Id == furniId);
if (item != null)
{
string name = null;
try { name = item.GetName(); } catch { return; }
if (name != null && name.Contains(FURNI_NAME_CONTAINS_TEXT))
{
// Remove old trigger entry for this gate
var toRemove = triggerMap.Where(kv => kv.Value.id == furniId).Select(kv => kv.Key).ToList();
foreach (var key in toRemove) triggerMap.Remove(key);
// Add new trigger position
var (dx, dy) = GetTriggerOffset(newDir);
triggerMap[(itemX + dx, itemY + dy)] = (furniId, itemX, itemY, newDir);
// Check if user is on new trigger tile
TryEnterGate(Self.Location.X, Self.Location.Y);
}
}
}
void OnRoomReady(dynamic e)
{
RebuildGateCache();
if (Self?.Location != null)
TryEnterGate(Self.Location.X, Self.Location.Y);
}
OnIntercept(In["UserUpdate"], e => HandleUserUpdate(e));
OnIntercept(In["ObjectUpdate"], e => HandleObjectUpdate(e));
OnEnteredRoom(e => OnRoomReady(e));
// Sofort beim Start Cache bauen und prüfen (schon im Raum)
RebuildGateCache();
if (Self?.Location != null)
TryEnterGate(Self.Location.X, Self.Location.Y);
while (Run)
{
Delay(30);
}
Log("closed");

View File

@ -0,0 +1,577 @@
using System;
using System.Collections.Generic;
using System.Linq;
// Color Puzzle Solver (fix): solves all 4 rows reliably.
// Keeps desync handling + auto direction flip detection.
const int TILE_KIND = 3696;
const int ARROW_KIND = 17851;
const int GRID_X_MIN = 36;
const int GRID_X_MAX = 39;
const int GRID_Y_MIN = 27;
const int GRID_Y_MAX = 30;
const int CLICK_DELAY_MS = 950;
int GetState(dynamic item)
{
try { return int.Parse(item.State?.ToString() ?? "0"); }
catch { return 0; }
}
int GetKind(dynamic item)
{
try { return (int)item.Kind; }
catch { return -1; }
}
int[,] ReadGridFromRoom()
{
int[,] g = new int[4, 4];
bool[,] found = new bool[4, 4];
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != TILE_KIND) continue;
int x = item.Location.X;
int y = item.Location.Y;
double z = item.Location.Z;
if (x < GRID_X_MIN || x > GRID_X_MAX) continue;
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
if (z < 18.4) continue;
int r = y - GRID_Y_MIN;
int c = x - GRID_X_MIN;
g[r, c] = GetState(item);
found[r, c] = true;
}
int cnt = 0;
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
if (found[r, c]) cnt++;
return cnt == 16 ? g : null;
}
string GridDump(int[,] g)
{
return string.Join(" | ", Enumerable.Range(0, 4).Select(r =>
$"R{r}[{g[r,0]},{g[r,1]},{g[r,2]},{g[r,3]}]"));
}
void SimMove(int[,] g, int m)
{
if (m < 4)
{
int r = m;
int t = g[r, 0]; g[r, 0] = g[r, 1]; g[r, 1] = g[r, 2]; g[r, 2] = g[r, 3]; g[r, 3] = t;
}
else if (m < 8)
{
int r = m - 4;
int t = g[r, 3]; g[r, 3] = g[r, 2]; g[r, 2] = g[r, 1]; g[r, 1] = g[r, 0]; g[r, 0] = t;
}
else if (m < 12)
{
int c = m - 8;
int t = g[0, c]; g[0, c] = g[1, c]; g[1, c] = g[2, c]; g[2, c] = g[3, c]; g[3, c] = t;
}
else
{
int c = m - 12;
int t = g[3, c]; g[3, c] = g[2, c]; g[2, c] = g[1, c]; g[1, c] = g[0, c]; g[0, c] = t;
}
}
List<int> SolveLayerByLayer(int[,] srcGrid, int[] tgtRows)
{
int[,] g = new int[4, 4];
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
g[r, c] = srcGrid[r, c];
var moves = new List<int>();
void Do(int m) { moves.Add(m); SimMove(g, m); }
void DoRowRight(int r, int times)
{
times = ((times % 4) + 4) % 4;
if (times == 3) { Do(r); return; }
for (int i = 0; i < times; i++) Do(r + 4);
}
void DoRowLeft(int r, int times)
{
times = ((times % 4) + 4) % 4;
if (times == 3) { Do(r + 4); return; }
for (int i = 0; i < times; i++) Do(r);
}
void DoColUp(int c, int times)
{
times = ((times % 4) + 4) % 4;
if (times == 3) { Do(c + 12); return; }
for (int i = 0; i < times; i++) Do(c + 8);
}
void DoColDown(int c, int times)
{
times = ((times % 4) + 4) % 4;
if (times == 3) { Do(c + 8); return; }
for (int i = 0; i < times; i++) Do(c + 12);
}
int C0 = tgtRows[0];
for (int c = 0; c < 4; c++)
{
if (g[0, c] == C0) continue;
int foundRow = -1;
for (int r = 1; r <= 3; r++)
if (g[r, c] == C0) { foundRow = r; break; }
if (foundRow >= 0)
{
DoColUp(c, foundRow);
}
else
{
bool found = false;
for (int r = 1; r <= 3 && !found; r++)
for (int c2 = 0; c2 < 4 && !found; c2++)
if (c2 != c && g[r, c2] == C0)
{
DoRowRight(r, (c - c2 + 4) % 4);
DoColUp(c, r);
found = true;
}
if (!found) return null;
}
}
int C1 = tgtRows[1];
for (int pass = 0; pass < 8; pass++)
{
int colW = -1;
for (int c = 0; c < 4; c++) if (g[1, c] != C1) { colW = c; break; }
if (colW < 0) break;
int srcR = -1, srcC = -1;
for (int r = 2; r <= 3 && srcR < 0; r++)
for (int c = 0; c < 4; c++)
if (g[r, c] == C1) { srcR = r; srcC = c; break; }
if (srcR < 0) return null;
if (srcC != colW) DoRowRight(srcR, (colW - srcC + 4) % 4);
int k2 = srcR - 1;
DoRowLeft(1, 1);
DoColUp(colW, k2);
DoRowRight(1, 1);
DoColDown(colW, k2);
}
int C2 = tgtRows[2];
for (int pass = 0; pass < 8; pass++)
{
int colW = -1;
for (int c = 0; c < 4; c++) if (g[2, c] != C2) { colW = c; break; }
if (colW < 0) break;
int srcC = -1;
for (int c = 0; c < 4; c++) if (g[3, c] == C2) { srcC = c; break; }
if (srcC < 0) return null;
if (srcC != colW) DoRowRight(3, (colW - srcC + 4) % 4);
DoRowLeft(2, 1);
DoColUp(colW, 1);
DoRowRight(2, 1);
DoColDown(colW, 1);
}
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
if (g[r, c] != tgtRows[r]) return null;
bool changed = true;
while (changed)
{
changed = false;
for (int i = 0; i < moves.Count - 1; i++)
{
int a = moves[i], b = moves[i + 1];
bool cancel = false;
if (a < 4 && b == a + 4) cancel = true;
if (a >= 4 && a < 8 && b == a - 4) cancel = true;
if (a >= 8 && a < 12 && b == a + 4) cancel = true;
if (a >= 12 && b == a - 4) cancel = true;
if (cancel)
{
moves.RemoveAt(i + 1);
moves.RemoveAt(i);
changed = true;
break;
}
}
}
return moves;
}
bool IsSolvedForTarget(int[,] g, int[] targetRows)
{
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
if (g[r, c] != targetRows[r]) return false;
return true;
}
bool TryReadTargetRows(out int[] targetRows)
{
targetRows = null;
var byX = new Dictionary<int, (int[] states, bool[] found, int count)>();
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != TILE_KIND) continue;
int x = item.Location.X;
int y = item.Location.Y;
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
if (x >= GRID_X_MIN && x <= GRID_X_MAX) continue;
if (!byX.ContainsKey(x))
byX[x] = (new int[4], new bool[4], 0);
var entry = byX[x];
int r = y - GRID_Y_MIN;
if (!entry.found[r])
{
entry.states[r] = GetState(item);
entry.found[r] = true;
entry.count++;
byX[x] = entry;
}
}
if (byX.Count == 0) return false;
var best = byX
.Select(kvp => new
{
X = kvp.Key,
States = kvp.Value.states,
Count = kvp.Value.count,
Dist = kvp.Key < GRID_X_MIN ? (GRID_X_MIN - kvp.Key) : (kvp.Key - GRID_X_MAX)
})
.OrderByDescending(x => x.Count)
.ThenBy(x => x.Dist)
.First();
if (best.Count < 4) return false;
targetRows = new int[4];
for (int r = 0; r < 4; r++) targetRows[r] = best.States[r];
return true;
}
uint EncodeGrid(int[,] g)
{
uint s = 0;
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
s |= ((uint)(g[r, c] & 3)) << (2 * (r * 4 + c));
return s;
}
uint ApplyMoveBits(uint s, int m)
{
if (m < 4)
{
int sh = m * 8;
uint row = (s >> sh) & 0xFFu;
uint rot = ((row >> 2) | (row << 6)) & 0xFFu;
return (s & ~(0xFFu << sh)) | (rot << sh);
}
if (m < 8)
{
int sh = (m - 4) * 8;
uint row = (s >> sh) & 0xFFu;
uint rot = ((row << 2) | (row >> 6)) & 0xFFu;
return (s & ~(0xFFu << sh)) | (rot << sh);
}
if (m < 12)
{
int b = (m - 8) * 2;
uint v0 = (s >> b) & 3u, v1 = (s >> (b + 8)) & 3u, v2 = (s >> (b + 16)) & 3u, v3 = (s >> (b + 24)) & 3u;
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
return (s & mask) | (v1 << b) | (v2 << (b + 8)) | (v3 << (b + 16)) | (v0 << (b + 24));
}
{
int b = (m - 12) * 2;
uint v0 = (s >> b) & 3u, v1 = (s >> (b + 8)) & 3u, v2 = (s >> (b + 16)) & 3u, v3 = (s >> (b + 24)) & 3u;
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
return (s & mask) | (v3 << b) | (v0 << (b + 8)) | (v1 << (b + 16)) | (v2 << (b + 24));
}
}
int InverseMove(int m)
{
if (m < 4) return m + 4;
if (m < 8) return m - 4;
if (m < 12) return m + 4;
return m - 4;
}
string MoveName(int m)
{
if (m < 4) return $"Row{m} LEFT";
if (m < 8) return $"Row{m - 4} RIGHT";
if (m < 12) return $"Col{m - 8} UP";
return $"Col{m - 12} DOWN";
}
int[,] ApplyMovesToCopy(int[,] src, List<int> moves)
{
int[,] g = new int[4, 4];
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
g[r, c] = src[r, c];
foreach (int m in moves) SimMove(g, m);
return g;
}
Log("=== Color Puzzle Solver (fix all rows) ===");
var grid = ReadGridFromRoom();
if (grid == null)
{
Log("ERROR: Could not read full 4x4 grid.");
return;
}
Log($"Start: {GridDump(grid)}");
var arrowIds = new Dictionary<string, long>();
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != ARROW_KIND) continue;
int x = item.Location.X, y = item.Location.Y;
if (y == GRID_Y_MIN - 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
arrowIds[$"up_{x - GRID_X_MIN}"] = item.Id;
else if (y == GRID_Y_MAX + 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
arrowIds[$"down_{x - GRID_X_MIN}"] = item.Id;
else if (x == GRID_X_MIN - 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
arrowIds[$"left_{y - GRID_Y_MIN}"] = item.Id;
else if (x == GRID_X_MAX + 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
arrowIds[$"right_{y - GRID_Y_MIN}"] = item.Id;
}
if (arrowIds.Count < 16)
{
Log($"ERROR: Missing arrows ({arrowIds.Count}/16).");
return;
}
int[] detectedTarget;
if (!TryReadTargetRows(out detectedTarget))
{
detectedTarget = new[] { 1, 2, 3, 0 };
Log("WARN: Target tiles not fully detected, using fallback target rows 1,2,3,0.");
}
var candidateTargets = new List<int[]>();
void AddTargetCandidate(int[] t)
{
if (t == null || t.Length != 4) return;
if (!candidateTargets.Any(x => x[0] == t[0] && x[1] == t[1] && x[2] == t[2] && x[3] == t[3]))
candidateTargets.Add(new[] { t[0], t[1], t[2], t[3] });
}
AddTargetCandidate(detectedTarget);
AddTargetCandidate(new[] { detectedTarget[3], detectedTarget[2], detectedTarget[1], detectedTarget[0] });
AddTargetCandidate(new[] { 1, 2, 3, 0 });
AddTargetCandidate(new[] { 0, 3, 2, 1 });
List<int> solution = null;
int[] targetRows = null;
foreach (var candidate in candidateTargets)
{
var s = SolveLayerByLayer(grid, candidate);
if (s == null || s.Count == 0) continue;
var check = ApplyMovesToCopy(grid, s);
if (!IsSolvedForTarget(check, candidate)) continue;
if (solution == null || s.Count < solution.Count)
{
solution = s;
targetRows = candidate;
}
}
if (solution == null || targetRows == null)
{
Log("ERROR: Could not build a valid full 4-row plan.");
return;
}
Log($"Target rows chosen: R0={targetRows[0]}, R1={targetRows[1]}, R2={targetRows[2]}, R3={targetRows[3]}");
Log($"Plan length: {solution.Count} moves");
bool[] rowFlip = new bool[4];
bool[] colFlip = new bool[4];
string KeyForMove(int m)
{
if (m < 4) { int r = m; return rowFlip[r] ? $"right_{r}" : $"left_{r}"; }
if (m < 8) { int r = m - 4; return rowFlip[r] ? $"left_{r}" : $"right_{r}"; }
if (m < 12) { int c = m - 8; return colFlip[c] ? $"down_{c}" : $"up_{c}"; }
int cc = m - 12; return colFlip[cc] ? $"up_{cc}" : $"down_{cc}";
}
int[,] tgtGrid = new int[4, 4];
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
tgtGrid[r, c] = targetRows[r];
uint goalState = EncodeGrid(tgtGrid);
uint currentState = EncodeGrid(grid);
int moveIdx = 0;
int retries = 0;
const int MAX_RETRIES = 3;
while (moveIdx < solution.Count)
{
if (currentState == goalState)
{
Log("=== Solved: all 4 rows complete ===");
return;
}
int move = solution[moveIdx];
uint expected = ApplyMoveBits(currentState, move);
string key = KeyForMove(move);
if (!arrowIds.ContainsKey(key))
{
Log($"ERROR: Arrow '{key}' not found.");
return;
}
long id = arrowIds[key];
Log($"[{moveIdx + 1}/{solution.Count}] {MoveName(move)} via {key}");
Send(Out["ClickFurni"], (int)id, 0);
Delay(CLICK_DELAY_MS);
var newGrid = ReadGridFromRoom();
if (newGrid == null)
{
Log("WARN: Grid read failed, retry...");
Delay(400);
continue;
}
uint afterState = EncodeGrid(newGrid);
if (afterState == expected)
{
currentState = afterState;
moveIdx++;
retries = 0;
continue;
}
uint invExpected = ApplyMoveBits(currentState, InverseMove(move));
if (afterState == invExpected)
{
if (move < 8)
{
int r = move < 4 ? move : move - 4;
rowFlip[r] = !rowFlip[r];
Log($"Auto-fix: Row {r} direction flipped.");
}
else
{
int c = move < 12 ? move - 8 : move - 12;
colFlip[c] = !colFlip[c];
Log($"Auto-fix: Col {c} direction flipped.");
}
grid = newGrid;
currentState = afterState;
var replan = SolveLayerByLayer(grid, targetRows);
if (replan == null)
{
Log("ERROR: Replan failed after direction flip.");
return;
}
solution = replan;
moveIdx = 0;
retries = 0;
Log($"Replan: {solution.Count} moves");
continue;
}
if (afterState == currentState)
{
retries++;
if (retries >= MAX_RETRIES)
{
Log("WARN: Click had no effect multiple times, replan.");
grid = newGrid;
var replan = SolveLayerByLayer(grid, targetRows);
if (replan == null)
{
Log("ERROR: Replan failed after no-effect clicks.");
return;
}
solution = replan;
moveIdx = 0;
retries = 0;
}
else
{
Log("Click no effect, retrying...");
Delay(300);
}
continue;
}
Log($"Desync detected. New grid: {GridDump(newGrid)}");
grid = newGrid;
currentState = afterState;
if (IsSolvedForTarget(grid, targetRows))
{
Log("=== Solved: all 4 rows complete ===");
return;
}
var desyncReplan = SolveLayerByLayer(grid, targetRows);
if (desyncReplan == null)
{
Log("ERROR: Replan failed after desync.");
return;
}
solution = desyncReplan;
moveIdx = 0;
retries = 0;
Log($"Replan after desync: {solution.Count} moves");
}
if (currentState == goalState)
Log("=== Solved: all 4 rows complete ===");
else
Log("Moves done. Please check if room state changed externally.");

View File

@ -0,0 +1,477 @@
using System;
using System.Collections.Generic;
using System.Linq;
// Color Puzzle Solver v2
// - Auto calibration of arrow -> move mapping
// - Waits for real state change after every click
const int TILE_KIND = 3696;
const int ARROW_KIND = 17851;
const int GRID_X_MIN = 36;
const int GRID_X_MAX = 39;
const int GRID_Y_MIN = 27;
const int GRID_Y_MAX = 30;
const int CLICK_SETTLE_DELAY_MS = 250;
const int WAIT_CHANGE_TIMEOUT_MS = 6000;
const int WAIT_CHANGE_POLL_MS = 120;
const int MAX_STEPS = 140;
const int BFS_MAX_NODES = 4_000_000;
const int IDA_MAX_SEC = 12;
const bool AUTO_QUEUE_START = true;
const long TRANSPORTER_ID = 759030883;
const int QUEUE_CLICK_INTERVAL_MS = 5000;
const int WAIT_PUZZLE_POLL_MS = 250;
const int WAIT_PUZZLE_LOG_MS = 5000;
const bool REQUIRE_SELF_IN_PLAYZONE = true;
const int PLAY_X_MIN = 34;
const int PLAY_X_MAX = 41;
const int PLAY_Y_MIN = 26;
const int PLAY_Y_MAX = 31;
int GetState(dynamic item)
{
try { return int.Parse(item.State?.ToString() ?? "0"); }
catch { return 0; }
}
int GetKind(dynamic item)
{
try { return (int)item.Kind; }
catch { return -1; }
}
uint EncodeGrid(int[,] g)
{
uint s = 0;
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
s |= ((uint)(g[r, c] & 3)) << (2 * (r * 4 + c));
return s;
}
bool TryReadGrid(out uint state, out string dump)
{
int[,] grid = new int[4, 4];
bool[,] found = new bool[4, 4];
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != TILE_KIND) continue;
int x = item.Location.X;
int y = item.Location.Y;
double z = item.Location.Z;
if (x < GRID_X_MIN || x > GRID_X_MAX) continue;
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
if (z < 18.4) continue;
int row = y - GRID_Y_MIN;
int col = x - GRID_X_MIN;
grid[row, col] = GetState(item);
found[row, col] = true;
}
int cnt = 0;
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
if (found[r, c]) cnt++;
if (cnt < 16)
{
state = 0;
dump = "";
return false;
}
state = EncodeGrid(grid);
dump = string.Join(" | ", Enumerable.Range(0, 4).Select(r =>
$"R{r}[{grid[r,0]},{grid[r,1]},{grid[r,2]},{grid[r,3]}]"));
return true;
}
uint RowLeft(uint s, int r)
{
int sh = r * 8;
uint row = (s >> sh) & 0xFFu;
uint rot = ((row >> 2) | (row << 6)) & 0xFFu;
return (s & ~(0xFFu << sh)) | (rot << sh);
}
uint RowRight(uint s, int r)
{
int sh = r * 8;
uint row = (s >> sh) & 0xFFu;
uint rot = ((row << 2) | (row >> 6)) & 0xFFu;
return (s & ~(0xFFu << sh)) | (rot << sh);
}
uint ColUp(uint s, int c)
{
int b = c * 2;
uint v0 = (s >> b) & 3u;
uint v1 = (s >> (b + 8)) & 3u;
uint v2 = (s >> (b + 16)) & 3u;
uint v3 = (s >> (b + 24)) & 3u;
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
return (s & mask) | (v1 << b) | (v2 << (b + 8)) | (v3 << (b + 16)) | (v0 << (b + 24));
}
uint ColDown(uint s, int c)
{
int b = c * 2;
uint v0 = (s >> b) & 3u;
uint v1 = (s >> (b + 8)) & 3u;
uint v2 = (s >> (b + 16)) & 3u;
uint v3 = (s >> (b + 24)) & 3u;
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
return (s & mask) | (v3 << b) | (v0 << (b + 8)) | (v1 << (b + 16)) | (v2 << (b + 24));
}
uint ApplyMove(uint s, int m)
{
if (m < 4) return RowLeft(s, m);
if (m < 8) return RowRight(s, m - 4);
if (m < 12) return ColUp(s, m - 8);
return ColDown(s, m - 12);
}
int InverseMove(int m)
{
if (m < 4) return m + 4;
if (m < 8) return m - 4;
if (m < 12) return m + 4;
return m - 4;
}
string MoveName(int m)
{
if (m < 4) return $"Row{m} LEFT";
if (m < 8) return $"Row{m - 4} RIGHT";
if (m < 12) return $"Col{m - 8} UP";
return $"Col{m - 12} DOWN";
}
int DetectMove(uint before, uint after)
{
int hit = -1;
for (int m = 0; m < 16; m++)
{
if (ApplyMove(before, m) != after) continue;
if (hit != -1) return -2;
hit = m;
}
return hit;
}
List<int> SolveBfs(uint start, uint goal)
{
if (start == goal) return new List<int>();
var visited = new Dictionary<uint, (uint parent, int move)>();
var queue = new Queue<uint>();
visited[start] = (start, -1);
queue.Enqueue(start);
int nodes = 0;
bool found = false;
while (queue.Count > 0 && nodes < BFS_MAX_NODES)
{
uint cur = queue.Dequeue();
nodes++;
for (int m = 0; m < 16; m++)
{
uint nxt = ApplyMove(cur, m);
if (visited.ContainsKey(nxt)) continue;
visited[nxt] = (cur, m);
if (nxt == goal)
{
found = true;
queue.Clear();
break;
}
queue.Enqueue(nxt);
}
}
if (!found) return null;
var sol = new List<int>();
uint s = goal;
while (s != start)
{
var p = visited[s];
sol.Add(p.move);
s = p.parent;
}
sol.Reverse();
return sol;
}
List<int> SolveIda(uint start, uint goal)
{
if (start == goal) return new List<int>();
var t0 = DateTime.Now;
int H(uint st)
{
int mis = 0;
for (int i = 0; i < 16; i++)
{
int a = (int)((st >> (i * 2)) & 3u);
int b = (int)((goal >> (i * 2)) & 3u);
if (a != b) mis++;
}
return (mis + 3) / 4;
}
List<int> best = null;
bool timeout = false;
bool Dfs(uint st, List<int> path, int maxDepth)
{
if (timeout) return false;
if ((DateTime.Now - t0).TotalSeconds > IDA_MAX_SEC)
{
timeout = true;
return false;
}
if (st == goal)
{
best = new List<int>(path);
return true;
}
int h = H(st);
if (path.Count + h > maxDepth) return false;
int block = path.Count > 0 ? InverseMove(path[path.Count - 1]) : -1;
for (int m = 0; m < 16; m++)
{
if (m == block) continue;
path.Add(m);
if (Dfs(ApplyMove(st, m), path, maxDepth)) return true;
path.RemoveAt(path.Count - 1);
if (timeout) return false;
}
return false;
}
int d0 = H(start);
for (int d = d0; d <= 22 && !timeout; d++)
{
if (Dfs(start, new List<int>(), d)) break;
}
return best;
}
List<int> Solve(uint start, uint goal)
{
var bfs = SolveBfs(start, goal);
if (bfs != null) return bfs;
return SolveIda(start, goal);
}
bool ClickAndWaitChange(long furniId, uint before, out uint after, out string dumpAfter)
{
Send(Out["ClickFurni"], (int)furniId, 0);
Delay(CLICK_SETTLE_DELAY_MS);
int waited = 0;
while (waited < WAIT_CHANGE_TIMEOUT_MS)
{
if (TryReadGrid(out after, out dumpAfter) && after != before)
return true;
Delay(WAIT_CHANGE_POLL_MS);
waited += WAIT_CHANGE_POLL_MS;
}
after = before;
dumpAfter = "";
return false;
}
Dictionary<string, long> ReadArrowIds()
{
var arrowIds = new Dictionary<string, long>();
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != ARROW_KIND) continue;
int x = item.Location.X;
int y = item.Location.Y;
if (y == GRID_Y_MIN - 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
arrowIds[$"up_{x - GRID_X_MIN}"] = item.Id;
else if (y == GRID_Y_MAX + 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
arrowIds[$"down_{x - GRID_X_MIN}"] = item.Id;
else if (x == GRID_X_MIN - 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
arrowIds[$"left_{y - GRID_Y_MIN}"] = item.Id;
else if (x == GRID_X_MAX + 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
arrowIds[$"right_{y - GRID_Y_MIN}"] = item.Id;
}
return arrowIds;
}
bool IsSelfInPlayZone()
{
try
{
int x = Self.Location.X;
int y = Self.Location.Y;
return x >= PLAY_X_MIN && x <= PLAY_X_MAX && y >= PLAY_Y_MIN && y <= PLAY_Y_MAX;
}
catch
{
return false;
}
}
Log("=== Color Puzzle Auto-Solver (AutoCalib + WaitChange) ===");
Dictionary<string, long> arrowIds = null;
uint current;
string dumpNow;
int sinceQueueClick = QUEUE_CLICK_INTERVAL_MS;
int sinceLog = WAIT_PUZZLE_LOG_MS;
while (true)
{
bool hasGrid = TryReadGrid(out current, out dumpNow);
var probeArrows = ReadArrowIds();
bool hasArrows = probeArrows.Count == 16;
bool inPlayZone = !REQUIRE_SELF_IN_PLAYZONE || IsSelfInPlayZone();
if (hasGrid && hasArrows && inPlayZone)
{
arrowIds = probeArrows;
break;
}
if (AUTO_QUEUE_START && sinceQueueClick >= QUEUE_CLICK_INTERVAL_MS)
{
Send(Out["ClickFurni"], (int)TRANSPORTER_ID, 0);
Log($"Queue: Klick Transporter {TRANSPORTER_ID}...");
sinceQueueClick = 0;
}
if (sinceLog >= WAIT_PUZZLE_LOG_MS)
{
string selfPos = "?";
try { selfPos = $"{Self.Location.X},{Self.Location.Y}"; } catch { }
Log($"Warte auf Spielstart... Grid={(hasGrid ? "ok" : "no")}, Pfeile={probeArrows.Count}/16, InZone={(inPlayZone ? "yes" : "no")}, Self={selfPos}");
sinceLog = 0;
}
Delay(WAIT_PUZZLE_POLL_MS);
sinceQueueClick += WAIT_PUZZLE_POLL_MS;
sinceLog += WAIT_PUZZLE_POLL_MS;
}
Log("Puzzle erkannt. Starte Solver...");
Log($"Pfeile: {arrowIds.Count}/16");
int[] targetRows = new int[4];
bool targetFound = false;
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != TILE_KIND) continue;
if (item.Location.X != 41) continue;
int y = item.Location.Y;
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
targetRows[y - GRID_Y_MIN] = GetState(item);
targetFound = true;
}
if (!targetFound) targetRows = new[] { 1, 2, 3, 0 };
int[,] tgt = new int[4, 4];
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
tgt[r, c] = targetRows[r];
uint goal = EncodeGrid(tgt);
Log($"Ziel: R0={targetRows[0]}, R1={targetRows[1]}, R2={targetRows[2]}, R3={targetRows[3]}");
Log($"Start: {dumpNow}");
var moveToKey = new Dictionary<int, string>();
var keyToMove = new Dictionary<string, int>();
var allKeys = arrowIds.Keys.OrderBy(k => k).ToList();
for (int step = 1; step <= MAX_STEPS; step++)
{
if (current == goal)
{
Log("=== Geloest: alle 4 Reihen korrekt ===");
return;
}
var plan = Solve(current, goal);
if (plan == null || plan.Count == 0)
{
Log("ERROR: Kein Plan vom aktuellen Zustand.");
return;
}
int wanted = plan[0];
string key;
bool probing = false;
if (moveToKey.ContainsKey(wanted))
{
key = moveToKey[wanted];
}
else
{
key = allKeys.FirstOrDefault(k => !keyToMove.ContainsKey(k));
if (key == null)
{
key = allKeys[0];
}
probing = true;
}
long id = arrowIds[key];
Log($"[{step}] want {MoveName(wanted)} | click {key}" + (probing ? " (probe)" : ""));
if (!ClickAndWaitChange(id, current, out uint after, out string dumpAfter))
{
Log(" Kein Move erkannt (Timeout), gleicher Schritt nochmal.");
continue;
}
int actual = DetectMove(current, after);
if (actual >= 0)
{
moveToKey[actual] = key;
keyToMove[key] = actual;
if (actual != wanted)
Log($" AutoCalib: {key} == {MoveName(actual)} (nicht {MoveName(wanted)})");
}
else if (actual == -1)
{
Log($" Unbekannter Transition-Delta, weiter mit Re-Plan. State: {dumpAfter}");
}
else
{
Log($" Mehrdeutiger Delta, weiter mit Re-Plan. State: {dumpAfter}");
}
current = after;
if (step % 10 == 0)
Log($" Calib: {moveToKey.Count}/16 Moves gemappt");
}
Log("Nicht fertig in MAX_STEPS. Script einfach nochmal starten.");

View File

@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.Linq;
class ScanCell
{
public long Id;
public int X;
public int Y;
public double Z;
public int Kind;
public int State;
public string Name;
}
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;
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;
}
Log("=== Color State Logger ===");
var all = new List<dynamic>();
foreach (var it in FloorItems)
{
if (it == null) continue;
all.Add(it);
}
if (all.Count == 0)
{
Log("ERROR: No floor items.");
return;
}
var solutionRaw = new List<ScanCell>();
var playRaw = new List<ScanCell>();
foreach (var it in all)
{
int x = (int)it.Location.X;
int y = (int)it.Location.Y;
var cell = new ScanCell {
Id = (long)it.Id,
X = x,
Y = y,
Z = (double)it.Location.Z,
Kind = GetKind(it),
State = GetState(it),
Name = GetNameSafe(it)
};
if (InRect(x, y, SOL_MIN_X, SOL_MAX_X, SOL_MIN_Y, SOL_MAX_Y))
solutionRaw.Add(cell);
if (InRect(x, y, PLAY_MIN_X, PLAY_MAX_X, PLAY_MIN_Y, PLAY_MAX_Y))
playRaw.Add(cell);
}
if (solutionRaw.Count == 0 || playRaw.Count == 0)
{
Log($"ERROR: Missing board items. Solution={solutionRaw.Count}, Play={playRaw.Count}");
return;
}
var solKind = solutionRaw.GroupBy(x => x.Kind)
.Select(g => new { Kind = g.Key, CoordCount = g.Select(x => x.X + "," + x.Y).Distinct().Count(), Count = g.Count() })
.OrderByDescending(x => x.CoordCount)
.ThenByDescending(x => x.Count)
.First().Kind;
var playKind = playRaw.GroupBy(x => x.Kind)
.Select(g => new { Kind = g.Key, CoordCount = g.Select(x => x.X + "," + x.Y).Distinct().Count(), Count = g.Count() })
.OrderByDescending(x => x.CoordCount)
.ThenByDescending(x => x.Count)
.First().Kind;
var solCells = solutionRaw.Where(x => x.Kind == solKind)
.GroupBy(x => x.X + "," + x.Y)
.Select(g => g.OrderByDescending(x => x.Z).First())
.OrderBy(x => x.Y)
.ThenBy(x => x.X)
.ToList();
var playCells = playRaw.Where(x => x.Kind == playKind)
.GroupBy(x => x.X + "," + x.Y)
.Select(g => g.OrderByDescending(x => x.Z).First())
.OrderBy(x => x.Y)
.ThenBy(x => x.X)
.ToList();
Log($"Solution board kind={solKind} name={solCells.Select(x => x.Name).FirstOrDefault()} cells={solCells.Count}");
Log($"Play board kind={playKind} name={playCells.Select(x => x.Name).FirstOrDefault()} cells={playCells.Count}");
var solStates = solCells.GroupBy(x => x.State).OrderBy(x => x.Key).ToList();
var playStates = playCells.GroupBy(x => x.State).OrderBy(x => x.Key).ToList();
Log("\nStates in solution board:");
foreach (var s in solStates)
Log($" state {s.Key}: {s.Count()} tiles");
Log("\nStates in play board:");
foreach (var s in playStates)
Log($" state {s.Key}: {s.Count()} tiles");
Log("\nCoordinate -> state (solution board):");
foreach (var c in solCells)
Log($" ({c.X},{c.Y}) = {c.State}");
Log("\nCoordinate -> state (play board):");
foreach (var c in playCells)
Log($" ({c.X},{c.Y}) = {c.State}");
Log("\nHint: colors are client visuals; script logs exact numeric states.");
Log("Use this once, then map state->color manually by looking at one tile per state.");

536
Color Run Path Recorder.csx Normal file
View File

@ -0,0 +1,536 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
public struct Step
{
public int X;
public int Y;
public int DelayMs;
}
const int START_X = 13;
const int START_Y = 13;
const int FINISH_X = 14;
const int FINISH_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 bool RECORD_ONLY_PLAYFIELD = true;
const int MAX_RECORD_MS = 130000;
const int MIN_STEP_DELAY_MS = 40;
const int REPLAY_MOVE_INTERVAL_MS = 80;
const int FAST_PLAY_INTERVAL_MS = 70;
const bool AUTO_DUMP_ON_FINISH = true;
const bool LIVE_LOG_EACH_STEP = true;
Regex mvRegex = new Regex(@"/mv (\d+),(\d+),([\d\.]+)", RegexOptions.Compiled);
bool armed = true;
bool recording = false;
bool replaying = false;
int targetIndex = -1;
string targetName = "";
DateTime recordStart = DateTime.MinValue;
DateTime lastStepAt = DateTime.MinValue;
DateTime lastReplayMove = DateTime.MinValue;
string lastTrackedPos = "";
List<Step> steps = new List<Step>();
List<Step> lastCompletedSteps = new List<Step>();
string lastCompletedReason = "";
int lastCompletedDurationMs = 0;
Dictionary<string, List<Step>> savedPaths = new Dictionary<string, List<Step>>();
string pendingSymbol = "";
string currentRunSymbol = "";
DateTime lastStatusLog = DateTime.MinValue;
string MissingSymbol()
{
bool haveRose = HasSaved("rose");
bool haveHeart = HasSaved("heart");
if (haveRose && haveHeart) return "";
return haveRose ? "heart" : "rose";
}
bool HasSaved(string symbol)
{
string s = NormalizeSymbol(symbol);
return !string.IsNullOrEmpty(s) && savedPaths.ContainsKey(s) && savedPaths[s].Count > 0;
}
string P(int x, int y) => x + "," + y;
bool InPlay(int x, int y)
{
return x >= PLAY_MIN_X && x <= PLAY_MAX_X && y >= PLAY_MIN_Y && y <= PLAY_MAX_Y;
}
bool IsFinish(int x, int y)
{
return x == FINISH_X && y == FINISH_Y;
}
string NormalizeSymbol(string s)
{
if (string.IsNullOrWhiteSpace(s)) return "";
string t = s.Trim().ToLowerInvariant();
if (t.Contains("rose")) return "rose";
if (t.Contains("heart")) return "heart";
return "";
}
void ResetRecorder(bool keepArmed)
{
recording = false;
targetIndex = -1;
targetName = "";
recordStart = DateTime.MinValue;
lastStepAt = DateTime.MinValue;
lastTrackedPos = "";
if (!keepArmed) armed = false;
}
dynamic FindUserOnStartTile()
{
foreach (var u in Users)
{
if (u == null || u.Location == null) continue;
if (u.Location.X == START_X && u.Location.Y == START_Y)
return u;
}
return null;
}
void ArmIfNeeded()
{
if (!armed || recording || replaying) return;
string missing = MissingSymbol();
if (!string.IsNullOrEmpty(missing))
{
if (string.IsNullOrEmpty(pendingSymbol))
{
if ((DateTime.UtcNow - lastStatusLog).TotalSeconds >= 4)
{
Log($"Waiting for symbol call: {missing}.");
lastStatusLog = DateTime.UtcNow;
}
return;
}
if (pendingSymbol != missing)
{
if ((DateTime.UtcNow - lastStatusLog).TotalSeconds >= 4)
{
Log($"Ignoring round symbol '{pendingSymbol}', waiting for missing '{missing}'.");
lastStatusLog = DateTime.UtcNow;
}
return;
}
}
var u = FindUserOnStartTile();
if (u == null) return;
StartRecordingForUser(u);
}
void StartRecordingForUser(dynamic u)
{
if (u == null) return;
targetIndex = u.Index;
targetName = u.Name;
recording = true;
recordStart = DateTime.UtcNow;
lastStepAt = recordStart;
steps.Clear();
lastTrackedPos = P(START_X, START_Y);
currentRunSymbol = pendingSymbol;
Log($"REC START: {targetName} (index {targetIndex}) from {START_X}:{START_Y}");
if (!string.IsNullOrEmpty(currentRunSymbol))
Log($"REC symbol: {currentRunSymbol}");
}
void AddStep(int x, int y)
{
if (RECORD_ONLY_PLAYFIELD && !InPlay(x, y)) return;
int delay = (int)(DateTime.UtcNow - lastStepAt).TotalMilliseconds;
if (delay < MIN_STEP_DELAY_MS) delay = MIN_STEP_DELAY_MS;
if (steps.Count > 0)
{
var prev = steps[steps.Count - 1];
if (prev.X == x && prev.Y == y) return;
}
steps.Add(new Step { X = x, Y = y, DelayMs = delay });
lastStepAt = DateTime.UtcNow;
if (LIVE_LOG_EACH_STEP)
Log($"REC step {steps.Count}: ({x},{y}) +{delay}ms");
if (steps.Count % 10 == 0)
Log($"REC progress: {steps.Count} steps...");
}
void StopRecording(string reason)
{
if (!recording) return;
recording = false;
int dur = (int)(DateTime.UtcNow - recordStart).TotalMilliseconds;
lastCompletedSteps = new List<Step>(steps);
lastCompletedReason = reason;
lastCompletedDurationMs = dur;
Log($"REC STOP ({reason}) steps={steps.Count}, duration={dur}ms");
if (steps.Count > 0)
{
Log("Use .path replay to replay on your avatar.");
Log("Use .path dump to print recorded path.");
if (AUTO_DUMP_ON_FINISH && reason.StartsWith("finish@"))
{
Log("Auto dump on finish:");
DumpPath();
}
if (reason.StartsWith("finish@") && !string.IsNullOrEmpty(currentRunSymbol))
{
if (!savedPaths.ContainsKey(currentRunSymbol))
{
savedPaths[currentRunSymbol] = new List<Step>(steps);
Log($"Saved path for symbol '{currentRunSymbol}' ({steps.Count} steps).");
}
else
{
int oldCount = savedPaths[currentRunSymbol].Count;
if (steps.Count < oldCount)
{
savedPaths[currentRunSymbol] = new List<Step>(steps);
Log($"Updated '{currentRunSymbol}' path: {oldCount} -> {steps.Count} steps (better).");
}
else
{
Log($"Kept existing '{currentRunSymbol}' path ({oldCount} steps), new run had {steps.Count}.");
}
}
}
}
if (reason.StartsWith("finish@"))
{
bool haveRose = HasSaved("rose");
bool haveHeart = HasSaved("heart");
if (haveRose && haveHeart)
{
armed = false;
Log("Both symbols saved (rose + heart). Recorder auto-disarmed.");
}
else
{
armed = true;
string missing = !haveRose ? "rose" : "heart";
Log($"Saved run complete. Waiting for missing symbol: {missing}.");
}
}
currentRunSymbol = "";
pendingSymbol = "";
}
dynamic FindTargetByIndex(int idx)
{
foreach (var u in Users)
{
if (u == null) continue;
if (u.Index == idx) return u;
}
return null;
}
void DumpPath(string symbol = "")
{
var src = steps;
string which = "current";
string norm = NormalizeSymbol(symbol);
if (!string.IsNullOrEmpty(norm) && savedPaths.ContainsKey(norm))
{
src = savedPaths[norm];
which = norm;
}
else if (src.Count == 0 && lastCompletedSteps.Count > 0)
{
src = lastCompletedSteps;
which = "last";
}
if (src.Count == 0)
{
Log("No recorded steps.");
return;
}
if (which == "last")
Log($"PATH DUMP (last complete, {lastCompletedReason}, {lastCompletedDurationMs}ms) steps={src.Count}");
else if (which == "rose" || which == "heart")
Log($"PATH DUMP ({which}) steps={src.Count}");
else
Log($"PATH DUMP steps={src.Count}");
for (int i = 0; i < src.Count; i++)
{
var s = src[i];
Log($" {i + 1}. ({s.X},{s.Y}) after {s.DelayMs}ms");
}
}
void ReplayPath(string symbol = "", bool useRecordedDelays = true)
{
if (replaying) return;
var src = steps;
string norm = NormalizeSymbol(symbol);
if (!string.IsNullOrEmpty(norm) && savedPaths.ContainsKey(norm))
src = savedPaths[norm];
else if (src.Count == 0 && lastCompletedSteps.Count > 0)
src = lastCompletedSteps;
if (src.Count == 0)
{
Log("No recorded path to replay.");
return;
}
replaying = true;
Log($"REPLAY START: {src.Count} steps" + (string.IsNullOrEmpty(norm) ? "" : $" ({norm})") + (useRecordedDelays ? " [timed]" : " [fast]"));
for (int i = 0; i < src.Count; i++)
{
var s = src[i];
int wait = useRecordedDelays ? s.DelayMs : FAST_PLAY_INTERVAL_MS;
if (wait < REPLAY_MOVE_INTERVAL_MS) wait = REPLAY_MOVE_INTERVAL_MS;
Delay(wait);
Move(s.X, s.Y);
lastReplayMove = DateTime.UtcNow;
}
replaying = false;
Log("REPLAY DONE");
}
OnIntercept(In["UserUpdate"], e =>
{
if (!recording) return;
var packet = e.Packet;
int numUpdates = packet.ReadInt();
for (int i = 0; i < numUpdates; i++)
{
int entityIndex = packet.ReadInt();
packet.ReadInt();
packet.ReadInt();
packet.ReadString();
packet.ReadInt();
packet.ReadInt();
string action = packet.ReadString();
if (entityIndex != targetIndex) continue;
Match m = mvRegex.Match(action ?? "");
if (!m.Success) continue;
int tx = int.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture);
int ty = int.Parse(m.Groups[2].Value, CultureInfo.InvariantCulture);
AddStep(tx, ty);
}
});
OnChat(e =>
{
try
{
string msg = (e.Message ?? "").ToLowerInvariant();
string sym = NormalizeSymbol(msg);
if (!string.IsNullOrEmpty(sym) && msg.Contains("paint me"))
{
pendingSymbol = sym;
Log($"Detected round symbol: {pendingSymbol}");
if (recording && string.IsNullOrEmpty(currentRunSymbol))
{
currentRunSymbol = pendingSymbol;
Log($"Bound current run to symbol: {currentRunSymbol}");
string missing = MissingSymbol();
if (!string.IsNullOrEmpty(missing) && currentRunSymbol != missing)
{
StopRecording($"wrong_symbol_{currentRunSymbol}");
ResetRecorder(true);
Log($"Discarded run: needed '{missing}', got '{currentRunSymbol}'.");
}
}
}
}
catch { }
});
OnIntercept(Out.Chat, e =>
{
string msg = e.Packet.ReadString();
if (string.IsNullOrWhiteSpace(msg)) return;
string c = msg.Trim().ToLowerInvariant();
var parts = c.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (c == ".path arm")
{
e.Block();
armed = true;
if (recording) StopRecording("re-armed");
ResetRecorder(true);
Log("Recorder armed. Waiting for someone on 13:13.");
return;
}
if (parts.Length >= 3 && parts[0] == ".path" && parts[1] == "symbol")
{
e.Block();
string sym = NormalizeSymbol(parts[2]);
if (string.IsNullOrEmpty(sym))
Log("Unknown symbol. Use .path symbol rose|heart");
else
{
pendingSymbol = sym;
Log($"Manual symbol set: {pendingSymbol}");
}
return;
}
if (parts.Length >= 1 && parts[0] == "!play")
{
e.Block();
string sym = parts.Length >= 2 ? NormalizeSymbol(parts[1]) : "";
if (parts.Length >= 2 && string.IsNullOrEmpty(sym))
{
Log("Unknown play symbol. Use !play rose or !play heart");
return;
}
ReplayPath(sym, false);
return;
}
if (parts.Length >= 1 && parts[0] == "!playtimed")
{
e.Block();
string sym = parts.Length >= 2 ? NormalizeSymbol(parts[1]) : "";
if (parts.Length >= 2 && string.IsNullOrEmpty(sym))
{
Log("Unknown play symbol. Use !playtimed rose or !playtimed heart");
return;
}
ReplayPath(sym, true);
return;
}
if (c == ".path stop")
{
e.Block();
StopRecording("manual");
ResetRecorder(true);
return;
}
if (parts.Length >= 2 && parts[0] == ".path" && parts[1] == "dump")
{
e.Block();
string sym = parts.Length >= 3 ? parts[2] : "";
DumpPath(sym);
return;
}
if (parts.Length >= 2 && parts[0] == ".path" && parts[1] == "replay")
{
e.Block();
string sym = parts.Length >= 3 ? parts[2] : "";
ReplayPath(sym);
return;
}
if (c == ".path clear")
{
e.Block();
steps.Clear();
Log("Path cleared.");
return;
}
});
Log("=== Color Run Recorder (13:13) ===");
Log("Commands: .path arm | .path stop | .path dump [rose|heart] | .path replay [rose|heart] | .path clear");
Log("Optional: .path symbol rose|heart");
Log("Quick play: !play rose | !play heart");
Log("Timed play: !playtimed rose | !playtimed heart");
Log("Auto-start records when a user is on 13:13.");
Log($"Auto-stop when target reaches finish tile {FINISH_X}:{FINISH_Y}.");
while (Run)
{
try
{
ArmIfNeeded();
if (recording)
{
// If a different user newly starts on 13:13, switch immediately to new run.
var starter = FindUserOnStartTile();
if (starter != null && starter.Index != targetIndex)
{
StopRecording("replaced_by_new_start");
ResetRecorder(true);
StartRecordingForUser(starter);
}
var t = FindTargetByIndex(targetIndex);
if (t != null && t.Location != null)
{
int ux = t.Location.X;
int uy = t.Location.Y;
// Fallback tracker by live position (works even if /mv parse misses packets).
string pk = P(ux, uy);
if (pk != lastTrackedPos)
{
AddStep(ux, uy);
lastTrackedPos = pk;
}
if (IsFinish(ux, uy))
{
StopRecording($"finish@{FINISH_X}:{FINISH_Y}");
ResetRecorder(true);
Delay(200);
continue;
}
}
int ms = (int)(DateTime.UtcNow - recordStart).TotalMilliseconds;
if (ms > MAX_RECORD_MS)
{
StopRecording("timeout");
ResetRecorder(true);
}
}
}
catch { }
Delay(100);
}

13
Dance Switcher.csx Normal file
View File

@ -0,0 +1,13 @@
// Dance Switcher - Wechselt jede Sekunde die Tanzart
Log("=== DANCE SWITCHER ===");
int dance = 1;
while (Run)
{
Dance(dance);
dance++;
if (dance > 4) dance = 1;
Delay(100);
}

30
Dave 2.csx Normal file
View File

@ -0,0 +1,30 @@
/// @name Catalog Item Finder
var targetIdentifier = "clothing_r25_kittybag";
var catalog = GetCatalog();
var nodes = catalog.Where(x => x.Id > 0).ToArray();
for (int i = 0; i < nodes.Length; i++) {
var node = nodes[i];
Status($"Searching {i+1}/{nodes.Length}...");
var page = GetCatalogPage(node);
foreach (var offer in page.Offers) {
foreach (var product in offer.Products) {
if (product.Type != ItemType.Floor && product.Type != ItemType.Wall) continue;
if (product.GetIdentifier() != targetIdentifier) continue;
var pointsLabel = offer.ActivityPointType.ToString() == "Diamond"
? "PriceInDiamonds"
: "PriceInActivityPoints";
Log($"Found: {targetIdentifier} | id: {offer.Id} | pageId: {node.Id} | pageName: {node.Name} | furniLine: {offer.FurniLine} | priceInCredits: {offer.PriceInCredits} | {pointsLabel}: {offer.PriceInActivityPoints} | canPurchaseMultiple: {offer.CanPurchaseMultiple} | canPurchaseAsGift: {offer.CanPurchaseAsGift} | type: {product.Type} | isLimited: {product.IsLimited}");
return;
}
}
await Task.Delay(150);
}
Log("Item not found in catalog");

View File

@ -0,0 +1,491 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
public struct Point : IEquatable<Point>
{
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 class Duck
{
public long id { get; set; }
public Point pos { get; set; }
public Point lastpos { get; set; }
public Point vel { get; set; }
public DateTime lastseen { get; set; }
public Queue<Point> trail { get; set; } = new Queue<Point>(10);
public double spd { get; set; }
}
HashSet<Point> tiles = new HashSet<Point> {
(13,13),(14,13),(15,13),(16,13),(17,13),(18,13),(19,13),(20,13),(21,13),(22,13),(23,13),(24,13),(25,13),
(13,14),(17,14),(21,14),(25,14),
(13,15),(14,15),(15,15),(17,15),(18,15),(20,15),(21,15),(23,15),(24,15),(25,15),
(13,16),(15,16),(18,16),(20,16),(23,16),(25,16),
(13,17),(15,17),(16,17),(17,17),(18,17),(19,17),(20,17),(21,17),(22,17),(23,17),(25,17),
(13,18),(15,18),(19,18),(23,18),(25,18),
(13,19),(14,19),(15,19),(17,19),(18,19),(19,19),(20,19),(21,19),(23,19),(24,19),(25,19),
(13,20),(15,20),(16,20),(17,20),(21,20),(22,20),(23,20),(25,20),
(12,21),(13,21),(17,21),(18,21),(19,21),(20,21),(21,21),(25,21),(26,21),
(13,22),(15,22),(16,22),(17,22),(21,22),(22,22),(23,22),(25,22),
(13,23),(14,23),(15,23),(17,23),(18,23),(19,23),(20,23),(21,23),(23,23),(24,23),(25,23),
(13,24),(15,24),(19,24),(23,24),(25,24),
(13,25),(15,25),(16,25),(17,25),(18,25),(19,25),(20,25),(21,25),(22,25),(23,25),(25,25),
(13,26),(15,26),(18,26),(20,26),(23,26),(25,26),
(13,27),(14,27),(15,27),(17,27),(18,27),(20,27),(21,27),(23,27),(24,27),(25,27),
(13,28),(17,28),(21,28),(25,28),
(13,29),(14,29),(15,29),(16,29),(17,29),(18,29),(19,29),(20,29),(21,29),(22,29),(23,29),(24,29),(25,29)
};
Dictionary<Point, List<Point>> adj = new Dictionary<Point, List<Point>>();
Point[] dirs = { (0,1), (0,-1), (1,0), (-1,0), (1,1), (1,-1), (-1,1), (-1,-1) };
foreach(var t in tiles)
{
var n = new List<Point>();
foreach(var d in dirs)
{
Point p = new Point(t.X + d.X, t.Y + d.Y);
if(tiles.Contains(p)) n.Add(p);
}
adj[t] = n;
}
Dictionary<long, Duck> ducks = new Dictionary<long, Duck>();
Tile tgt = null;
Point lastcmd = default(Point);
DateTime cmdtime = DateTime.MinValue;
Point prev = default(Point);
Point curr = default(Point);
Queue<Point> hist = new Queue<Point>(5);
int stuck = 0;
HashSet<Point> danger = new HashSet<Point>();
Point dest = default(Point);
bool forcedest = false;
DateTime desttime = DateTime.MinValue;
Point getpos()
{
if (Self == null) return default(Point);
if (tgt != null) return tgt.XY;
if (!lastcmd.Equals(default(Point)) && (DateTime.UtcNow - cmdtime).TotalMilliseconds < 250)
return lastcmd;
if (Self.Location != null) return new Point(Self.Location.X, Self.Location.Y);
return default(Point);
}
void go(int x, int y)
{
Move(x, y);
lastcmd = new Point(x, y);
cmdtime = DateTime.UtcNow;
tgt = null;
}
HashSet<Point> predict(int frames)
{
var zones = new HashSet<Point>();
foreach(var d in ducks.Values)
{
zones.Add(d.pos);
if(!d.vel.Equals(default(Point)))
{
for(int i = 1; i <= frames; i++)
{
Point pred = new Point(
d.pos.X + d.vel.X * i,
d.pos.Y + d.vel.Y * i
);
if(tiles.Contains(pred))
zones.Add(pred);
}
}
if(frames >= 1 && adj.ContainsKey(d.pos))
{
foreach(var n in adj[d.pos])
zones.Add(n);
}
if(frames >= 2)
{
foreach(var d1 in dirs)
{
Point p1 = new Point(d.pos.X + d1.X, d.pos.Y + d1.Y);
if(tiles.Contains(p1))
{
zones.Add(p1);
foreach(var d2 in dirs)
{
Point p2 = new Point(p1.X + d2.X, p1.Y + d2.Y);
if(tiles.Contains(p2))
zones.Add(p2);
}
}
}
}
}
return zones;
}
Point findsafe(Point me, HashSet<Point> bad)
{
if(!bad.Any()) return tiles.First();
double best = double.MinValue;
Point spot = me;
foreach(var t in tiles)
{
if(bad.Contains(t)) continue;
double score = 0;
double mindist = double.MaxValue;
foreach(var b in bad)
{
double d = Math.Abs(t.X - b.X) + Math.Abs(t.Y - b.Y);
mindist = Math.Min(mindist, d);
score += d;
}
score += mindist * 100;
if(adj.ContainsKey(t))
{
int exits = adj[t].Count(n => !bad.Contains(n));
score += exits * 10;
}
double dist = Math.Abs(t.X - me.X) + Math.Abs(t.Y - me.Y);
score -= dist * 0.5;
if(score > best)
{
best = score;
spot = t;
}
}
return spot;
}
Point pathto(Point me, Point goal, HashSet<Point> d1, HashSet<Point> d2, HashSet<Point> d3)
{
if(!adj.ContainsKey(me)) return me;
if(hist.Count >= 3)
{
var last3 = hist.TakeLast(3).ToArray();
if(last3[0] == last3[2] && last3[0] != last3[1])
{
stuck++;
if(stuck > 2)
{
var esc = adj[me]
.Where(n => !d1.Contains(n))
.OrderBy(n => Guid.NewGuid())
.FirstOrDefault();
if(!esc.Equals(default(Point)))
{
stuck = 0;
return esc;
}
}
}
else stuck = 0;
}
double best = double.MinValue;
Point move = me;
foreach(var n in adj[me])
{
if(d1.Contains(n)) continue;
double score = 0;
if(d2.Contains(n)) score -= 800;
if(d3.Contains(n)) score -= 400;
double dist = Math.Abs(n.X - goal.X) + Math.Abs(n.Y - goal.Y);
score -= dist * 100;
foreach(var duck in ducks.Values)
{
double dd = Math.Abs(n.X - duck.pos.X) + Math.Abs(n.Y - duck.pos.Y);
score += dd * 10;
}
if(adj.ContainsKey(n))
{
int safe = adj[n].Count(x => !d1.Contains(x));
score += safe * 20;
if(safe == 0 && d2.Contains(n))
score -= 2000;
}
if(!prev.Equals(default(Point)) && n.Equals(prev))
score -= 50;
if(score > best)
{
best = score;
move = n;
}
}
return move;
}
Point getmove(Point me, Point goal, HashSet<Point> d1, HashSet<Point> d2, HashSet<Point> d3)
{
if(!adj.ContainsKey(me)) return me;
if(hist.Count >= 3)
{
var last3 = hist.TakeLast(3).ToArray();
if(last3[0] == last3[2] && last3[0] != last3[1])
{
stuck++;
if(stuck > 1)
{
var any = adj[me]
.Where(n => !d1.Contains(n))
.OrderBy(n => d2.Contains(n) ? 1 : 0)
.FirstOrDefault();
if(!any.Equals(default(Point)))
{
stuck = 0;
return any;
}
}
}
else stuck = 0;
}
double best = double.MinValue;
Point move = me;
foreach(var n in adj[me])
{
if(d1.Contains(n)) continue;
if(!prev.Equals(default(Point)) && n.Equals(prev))
continue;
double score = 0;
if(d2.Contains(n)) score -= 1000;
if(d3.Contains(n)) score -= 500;
double dist = Math.Abs(n.X - goal.X) + Math.Abs(n.Y - goal.Y);
score -= dist * 10;
foreach(var duck in ducks.Values)
{
double dd = Math.Abs(n.X - duck.pos.X) + Math.Abs(n.Y - duck.pos.Y);
score += dd * 20;
}
if(adj.ContainsKey(n))
{
int exits = adj[n].Count(x => !d1.Contains(x) && !d2.Contains(x));
score += exits * 50;
}
if(score > best)
{
best = score;
move = n;
}
}
return move;
}
OnIntercept(Out["MoveAvatar"], e => {
var pkt = e.Packet;
int x = pkt.ReadInt();
int y = pkt.ReadInt();
dest = new Point(x, y);
forcedest = true;
desttime = DateTime.UtcNow;
});
OnEnteredRoom(e => {
ducks.Clear();
hist.Clear();
prev = default(Point);
stuck = 0;
forcedest = false;
dest = default(Point);
});
OnIntercept(In["WiredMovements"], e => {
var pkt = e.Packet;
int cnt = pkt.ReadInt();
for(int i = 0; i < cnt; i++)
{
pkt.ReadInt();
int fx = pkt.ReadInt();
int fy = pkt.ReadInt();
int tx = pkt.ReadInt();
int ty = pkt.ReadInt();
pkt.ReadString();
pkt.ReadString();
int id = pkt.ReadInt();
pkt.ReadInt();
pkt.ReadInt();
long fid = id;
Point newp = new Point(tx, ty);
Point oldp = new Point(fx, fy);
if(!ducks.ContainsKey(fid))
{
ducks[fid] = new Duck { id = fid };
}
var d = ducks[fid];
d.lastpos = d.pos;
d.pos = newp;
d.vel = new Point(tx - fx, ty - fy);
d.lastseen = DateTime.UtcNow;
d.trail.Enqueue(newp);
if(d.trail.Count > 10) d.trail.Dequeue();
if((d.lastseen - DateTime.UtcNow).TotalSeconds < 1)
{
d.spd = Math.Sqrt(Math.Pow(d.vel.X, 2) + Math.Pow(d.vel.Y, 2));
}
}
});
OnIntercept(In["UserUpdate"], e => {
if(Self == null) return;
var pkt = e.Packet;
int num = pkt.ReadInt();
for(int i = 0; i < num; i++)
{
int idx = pkt.ReadInt();
int x = pkt.ReadInt();
int y = pkt.ReadInt();
string z = pkt.ReadString();
pkt.ReadInt();
pkt.ReadInt();
string act = pkt.ReadString();
if(idx == Self.Index)
{
prev = curr;
curr = new Point(x, y);
hist.Enqueue(curr);
if(hist.Count > 5) hist.Dequeue();
if(forcedest && curr.Equals(dest))
{
forcedest = false;
}
if(act.Contains("/mv"))
{
var parts = act.Split(new[] {' ', '/', ','}, StringSplitOptions.RemoveEmptyEntries);
if(parts.Length >= 4 && parts[0] == "mv")
{
if(int.TryParse(parts[1], out int mx) &&
int.TryParse(parts[2], out int my) &&
double.TryParse(parts[3], NumberStyles.Any, CultureInfo.InvariantCulture, out double mz))
{
tgt = new Tile(mx, my, mz);
lastcmd = default(Point);
}
}
}
else if(act.EndsWith("//") && !act.Contains("/mv"))
{
tgt = null;
}
}
}
});
while(Run)
{
try
{
Point me = getpos();
if(me.Equals(default(Point))) { Delay(20); continue; }
if(ducks.Any() || forcedest)
{
var d1 = predict(1);
var d2 = predict(2);
var d3 = predict(3);
Point goal;
Point next = me;
if(forcedest && tiles.Contains(dest))
{
if((DateTime.UtcNow - desttime).TotalSeconds > 30)
{
forcedest = false;
goal = findsafe(me, d1);
next = getmove(me, goal, d1, d2, d3);
}
else
{
goal = dest;
next = pathto(me, goal, d1, d2, d3);
}
}
else
{
goal = findsafe(me, d1);
next = getmove(me, goal, d1, d2, d3);
}
if(!next.Equals(me))
{
go(next.X, next.Y);
}
}
}
catch(Exception ex)
{
}
Delay(20);
}

59
Enter-One-Way-Door #1.csx Normal file
View File

@ -0,0 +1,59 @@
/// @group Bots
public class TileAction
{
private readonly Action MainAction;
private readonly Action ElseAction;
private readonly Func<bool> Condition;
public TileAction(Action mainAction) => MainAction = mainAction;
public TileAction(Action mainAction, Action elseAction, Func<bool> condition) : this(mainAction)
{
ElseAction = elseAction;
Condition = condition;
}
public void Execute()
{
if (Condition == null || Condition())
{
MainAction();
}
else
{
ElseAction();
}
}
}
public Point RealPos => (Self.CurrentUpdate.MovingTo ?? Self.Location).XY;
Dictionary<Point, TileAction> Actions = new()
{
// HIER DEINE ACTIONS
{ ( 24, 15), new (() => Send(Out["EnterOneWayDoor"],2147418205)) },
{ ( 24, 16), new (() => Send(Out["EnterOneWayDoor"],2147418204)) },
{ ( 25, 16), new (() => Send(Out["EnterOneWayDoor"],2147418203)) },
{ ( 25, 17), new (() => Send(Out["EnterOneWayDoor"],2147418547)) },
{ ( 26, 19), new (() => Send(Out["EnterOneWayDoor"],2147418490)) },
{ ( 25, 19), new (() => Send(Out["EnterOneWayDoor"],2147418211)) },
{ ( 25, 20), new (() => Send(Out["EnterOneWayDoor"],2147418212)) },
{ ( 24, 20), new (() => Send(Out["EnterOneWayDoor"],2147418551)) },
};
while (Run)
{
try
{
if (Actions.TryGetValue(RealPos, out var result))
{
result.Execute();
}
}
catch { }
Delay(100);
}

578
FURNIMATIC.csx Normal file
View File

@ -0,0 +1,578 @@
using System.Net;
using System.Text;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading.Tasks;
var port = 8230;
var queue = new List<int>();
var progress = 0;
var total = 0;
var delay = 850;
var lastrun = DateTime.Now;
HttpListener server = null;
try {
EnsureInventory();
var items = Inventory
.Where(x => x.IsRecyclable)
.GroupBy(x => x.GetDescriptor())
.Where(g => g.Count() >= 8)
.Select(g => new {
name = g.Key.GetName(),
id = g.Key.GetInfo().Identifier,
rev = g.Key.GetInfo().Revision,
count = g.Count(),
list = g.Select(i => (int)i.Id).ToList()
})
.OrderByDescending(x => x.count)
.ToList();
Log($"Found {items.Count} recyclable types (8+ items)");
items.ForEach(x => Log($" {x.name}: {x.count}x"));
var json = "[" + string.Join(",", items.Select((item, i) =>
$"{{\"i\":{i},\"n\":\"{item.name.Replace("\"", "\\\"")}\",\"id\":\"{item.id}\",\"r\":{item.rev},\"c\":{item.count}}}"
)) + "]";
var html = @"<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Recycler</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: linear-gradient(135deg, #0f0f0f 0%, #1a1a1a 100%);
color: #e0e0e0;
font: 14px -apple-system, system-ui, sans-serif;
min-height: 100vh;
padding: 20px;
}
.wrap {
max-width: 900px;
margin: 0 auto;
animation: fadein 0.5s;
}
@keyframes fadein {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
h1 {
font-size: 24px;
font-weight: 600;
margin-bottom: 25px;
text-align: center;
background: linear-gradient(90deg, #4ade80, #22d3ee);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.panel {
background: rgba(255,255,255,0.03);
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 12px;
padding: 16px;
margin-bottom: 16px;
}
.search-box {
width: 100%;
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.1);
color: #fff;
padding: 12px;
border-radius: 8px;
font-size: 14px;
margin-bottom: 12px;
transition: all 0.2s;
}
.search-box:focus {
outline: none;
border-color: #4ade80;
background: rgba(255,255,255,0.08);
}
.search-box::placeholder {
color: #666;
}
.controls {
display: flex;
gap: 12px;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.controls label {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: #a0a0a0;
}
button {
padding: 10px 20px;
background: linear-gradient(135deg, #4ade80, #22d3ee);
border: none;
color: #000;
font-weight: 600;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s;
font-size: 13px;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(74,222,128,0.3);
}
button:active {
transform: translateY(0);
}
button:disabled {
background: #333;
color: #666;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
button.stop {
background: linear-gradient(135deg, #ef4444, #f97316);
}
button.stop:hover {
box-shadow: 0 5px 15px rgba(239,68,68,0.3);
}
input[type=number] {
width: 70px;
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.1);
color: #fff;
padding: 8px;
border-radius: 6px;
font-size: 13px;
transition: all 0.2s;
}
input[type=number]:focus {
outline: none;
border-color: #4ade80;
background: rgba(255,255,255,0.08);
}
.bar {
height: 40px;
background: rgba(0,0,0,0.3);
border-radius: 20px;
position: relative;
overflow: hidden;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.2);
}
.fill {
height: 100%;
background: linear-gradient(90deg, #4ade80, #22d3ee);
border-radius: 20px;
transition: width 0.5s ease;
box-shadow: 0 0 20px rgba(74,222,128,0.5);
}
.bartext {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-weight: 600;
font-size: 14px;
color: #fff;
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
}
.status {
text-align: center;
font-size: 13px;
color: #888;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 12px;
max-height: 450px;
overflow-y: auto;
padding: 4px;
}
.grid::-webkit-scrollbar {
width: 8px;
}
.grid::-webkit-scrollbar-track {
background: rgba(255,255,255,0.02);
border-radius: 4px;
}
.grid::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.1);
border-radius: 4px;
}
.grid::-webkit-scrollbar-thumb:hover {
background: rgba(255,255,255,0.15);
}
.card {
background: rgba(255,255,255,0.04);
border: 2px solid rgba(255,255,255,0.08);
border-radius: 10px;
padding: 12px;
text-align: center;
cursor: pointer;
transition: all 0.2s;
position: relative;
}
.card:hover {
background: rgba(255,255,255,0.07);
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
.card.on {
border-color: #4ade80;
background: rgba(74,222,128,0.1);
}
.card.hidden {
display: none;
}
.card img {
width: 60px;
height: 60px;
margin-bottom: 8px;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
}
.card .name {
font-size: 12px;
margin-bottom: 6px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #ccc;
}
.card .count {
color: #4ade80;
font-weight: 600;
font-size: 14px;
margin-bottom: 8px;
}
.amt {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
margin-top: 8px;
}
.amt button {
width: 24px;
height: 24px;
padding: 0;
background: rgba(255,255,255,0.1);
border-radius: 4px;
font-size: 16px;
line-height: 1;
color: #fff;
}
.amt button:hover {
background: rgba(74,222,128,0.3);
transform: none;
box-shadow: none;
}
.amt span {
min-width: 40px;
font-size: 13px;
font-weight: 600;
color: #fff;
}
.empty {
grid-column: 1 / -1;
text-align: center;
padding: 60px 20px;
color: #666;
}
.result-count {
text-align: center;
font-size: 12px;
color: #666;
margin-bottom: 8px;
}
</style>
</head>
<body>
<div class='wrap'>
<h1>Recycler</h1>
<div class='panel'>
<input type='text' class='search-box' id='search' placeholder='Search items to recycle...' autofocus>
<div class='controls'>
<label>Delay <input type='number' id='delay' value='850' min='100' max='2000' step='50'>ms</label>
<button onclick='reset()'>Clear</button>
<button onclick='go()' id='btn'>Start</button>
<button onclick='stop()' class='stop'>Stop</button>
</div>
</div>
<div class='panel'>
<div class='bar'>
<div class='fill' id='bar' style='width:0%'></div>
<div class='bartext' id='txt'>Ready</div>
</div>
</div>
<div class='panel'>
<div class='status' id='info'>Select items to recycle</div>
<div class='result-count' id='results'></div>
<div class='grid' id='grid'></div>
</div>
</div>
<script>
const d = " + json + @";
let sel = [];
let vals = {};
let searchterm = '';
let wasrunning = false;
d.forEach(x => vals[x.i] = Math.min(80, Math.floor(x.c/8)*8));
function img(x) {
return 'https://images.habbo.com/dcr/hof_furni/' + x.r + '/' + x.id + '_icon.png';
}
function draw() {
const g = document.getElementById('grid');
if (!d.length) {
g.innerHTML = '<div class=""empty"">No recyclable items found<br><small>Need 8+ of the same type</small></div>';
return;
}
let visible = 0;
let html = '';
d.forEach(x => {
const show = !searchterm || x.n.toLowerCase().includes(searchterm.toLowerCase());
if (show) visible++;
html += '<div class=""card' + (show ? '' : ' hidden') + '"" id=""c' + x.i + '"" onclick=""pick(' + x.i + ')"">' +
'<img src=""' + img(x) + '"" onerror=""this.style.display=\'none\'"">' +
'<div class=""name"" title=""' + x.n + '"">' + x.n + '</div>' +
'<div class=""count"">' + x.c + 'x</div>' +
'<div class=""amt"" onclick=""event.stopPropagation()"">' +
'<button onclick=""adj(' + x.i + ',-8)"">-</button>' +
'<span id=""v' + x.i + '"">' + vals[x.i] + '</span>' +
'<button onclick=""adj(' + x.i + ',8)"">+</button>' +
'</div>' +
'</div>';
});
g.innerHTML = html;
// Restore selected state
sel.forEach(i => {
const el = document.getElementById('c' + i);
if (el) el.classList.add('on');
});
// Update result count
const rc = document.getElementById('results');
if (searchterm) {
rc.textContent = visible + ' items found';
} else {
rc.textContent = '';
}
if (visible === 0 && searchterm) {
g.innerHTML = '<div class=""empty"">No items match ""' + searchterm + '""<br><small>Try different search</small></div>';
}
}
function adj(i, n) {
const max = Math.floor(d.find(x => x.i === i).c / 8) * 8;
vals[i] = Math.max(8, Math.min(max, vals[i] + n));
document.getElementById('v' + i).textContent = vals[i];
update();
}
function pick(i) {
const e = document.getElementById('c' + i);
if (sel.includes(i)) {
sel = sel.filter(x => x !== i);
e.classList.remove('on');
} else {
sel.push(i);
e.classList.add('on');
}
update();
}
function reset() {
sel = [];
d.forEach(x => document.getElementById('c' + x.i)?.classList.remove('on'));
update();
}
function update() {
const t = sel.reduce((s, i) => s + vals[i], 0);
document.getElementById('info').textContent = sel.length ?
sel.length + ' types • ' + t + ' items' :
'Select items to recycle';
}
function go() {
if (!sel.length) {
alert('Select items first');
return;
}
const data = sel.map(i => ({i: i, a: vals[i]}));
document.getElementById('btn').disabled = true;
fetch('/recycle', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({items: data, delay: parseInt(document.getElementById('delay').value)})
});
}
function stop() {
fetch('/stop', {method: 'POST'});
document.getElementById('btn').disabled = false;
wasrunning = false;
}
// Search functionality
document.getElementById('search').addEventListener('input', (e) => {
searchterm = e.target.value.trim();
draw();
});
setInterval(() => {
fetch('/status')
.then(r => r.json())
.then(x => {
const p = x.total ? Math.round(x.done / x.total * 100) : 0;
document.getElementById('bar').style.width = p + '%';
document.getElementById('txt').textContent = x.total ? x.done + ' / ' + x.total : 'Ready';
// Track if we were running
if (x.total > 0) {
wasrunning = true;
}
// If we were running and now total is 0, we're done
if (wasrunning && x.total === 0) {
wasrunning = false;
document.getElementById('btn').disabled = false;
document.getElementById('bar').style.width = '100%';
document.getElementById('txt').textContent = 'Complete!';
setTimeout(() => {
document.getElementById('bar').style.width = '0%';
document.getElementById('txt').textContent = 'Ready';
}, 2000);
}
})
.catch(() => {});
}, 500);
draw();
</script>
</body>
</html>";
server = new HttpListener();
server.Prefixes.Add($"http://localhost:{port}/");
server.Start();
Process.Start(new ProcessStartInfo {
FileName = $"http://localhost:{port}/",
UseShellExecute = true
});
Log($"Server on port {port}");
Task.Run(async () => {
while (Run && server.IsListening) {
try {
var ctx = await server.GetContextAsync();
Task.Run(() => handle(ctx));
}
catch { break; }
}
});
while (Run) {
if (queue.Count >= 8 && (DateTime.Now - lastrun).TotalMilliseconds >= delay) {
var batch = queue.Take(8).ToList();
queue.RemoveRange(0, 8);
Send(Out["RecycleItems"], 8, batch[0], batch[1], batch[2], batch[3], batch[4], batch[5], batch[6], batch[7]);
progress += 8;
lastrun = DateTime.Now;
if (queue.Count < 8) {
Log($"Done - recycled {progress} items");
queue.Clear();
progress = 0;
total = 0;
}
}
Delay(10);
}
void handle(HttpListenerContext ctx) {
try {
var req = ctx.Request;
var res = ctx.Response;
var path = req.RawUrl;
if (path == "/") {
var b = Encoding.UTF8.GetBytes(html);
res.ContentType = "text/html";
res.ContentLength64 = b.Length;
res.OutputStream.Write(b, 0, b.Length);
}
else if (path == "/recycle" && req.HttpMethod == "POST") {
using (var r = new StreamReader(req.InputStream)) {
var data = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(r.ReadToEnd());
queue.Clear();
progress = 0;
if (data.ContainsKey("delay"))
delay = System.Text.Json.JsonSerializer.Deserialize<int>(data["delay"].ToString());
if (data.ContainsKey("items")) {
var selected = System.Text.Json.JsonSerializer.Deserialize<List<Dictionary<string, int>>>(data["items"].ToString());
foreach (var s in selected) {
if (s["i"] < items.Count) {
var item = items[s["i"]];
var amt = Math.Min(s["a"], item.count);
for (int i = 0; i < amt && i < item.list.Count; i++)
queue.Add(item.list[i]);
}
}
}
total = queue.Count;
Log($"Queued {total} items @ {delay}ms");
}
res.StatusCode = 200;
}
else if (path == "/stop") {
queue.Clear();
progress = total = 0;
Log("Stopped");
res.StatusCode = 200;
}
else if (path == "/status") {
var json = $"{{\"done\":{progress},\"total\":{total}}}";
var b = Encoding.UTF8.GetBytes(json);
res.ContentType = "application/json";
res.ContentLength64 = b.Length;
res.OutputStream.Write(b, 0, b.Length);
}
else {
res.StatusCode = 404;
}
res.Close();
}
catch { }
}
}
finally {
server?.Stop();
server?.Close();
Log("Shutdown");
}

21
Find Item by ID.csx Normal file
View File

@ -0,0 +1,21 @@
using System;
int searchId = 796960578;
Log("Suche Item...");
foreach (var item in FloorItems)
{
if (item == null) continue;
if (item.Id == searchId)
{
Log("=== ITEM GEFUNDEN ===");
Log("ID: " + item.Id);
Log("Position: X=" + item.Location.X + ", Y=" + item.Location.Y);
Log("Kind: " + item.Kind);
break;
}
}
Log("Suche beendet.");

View File

@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using System.Linq;
const int BIRD_X = 17;
const int BIRD_KIND = 9039;
const int PIPE_KIND = 5986;
const int CONTROL_ID = 630985637;
int GetKind(dynamic item) { try { return (int)item.Kind; } catch { return -1; } }
void Flap() { Send(Out["UseFurniture"], CONTROL_ID, 0); }
int GetBirdY()
{
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != BIRD_KIND) continue;
if (item.Location.X == BIRD_X) return item.Location.Y;
}
return -1;
}
(int gapMin, int gapMax, int pipeX) FindNextPipe()
{
var pipesByX = new Dictionary<int, List<int>>();
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != PIPE_KIND) continue;
int x = item.Location.X, y = item.Location.Y;
if (!pipesByX.ContainsKey(x)) pipesByX[x] = new List<int>();
pipesByX[x].Add(y);
}
var ahead = pipesByX.Keys.Where(x => x >= BIRD_X).OrderBy(x => x).ToList();
if (ahead.Count == 0) return (-1, -1, 99);
int closestX = ahead.First();
var wallYs = new HashSet<int>(pipesByX[closestX]);
int bestStart = -1, bestEnd = -1, bestSize = 0, gapStart = -1;
for (int y = 7; y <= 24; y++)
{
if (!wallYs.Contains(y)) { if (gapStart < 0) gapStart = y; }
else
{
if (gapStart >= 0)
{
int size = (y - 1) - gapStart + 1;
if (size > bestSize) { bestSize = size; bestStart = gapStart; bestEnd = y - 1; }
}
gapStart = -1;
}
}
if (gapStart >= 0)
{
int size = 24 - gapStart + 1;
if (size > bestSize) { bestStart = gapStart; bestEnd = 24; }
}
return (bestStart, bestEnd, closestX);
}
Log("FLAPPY BOT V19 - SILENT MODE"); // Output spam reduced
long lastFlap = 0;
int tick = 0;
int lastY = -1;
while (Run)
{
tick++;
int birdY = GetBirdY();
if (birdY < 0) { Delay(25); continue; }
var (gapMin, gapMax, pipeX) = FindNextPipe();
int dist = pipeX - BIRD_X;
long now = DateTimeOffset.Now.ToUnixTimeMilliseconds();
bool shouldFlap = false;
string reason = "";
// MINIMUM COOLDOWN = 120ms
int cooldown = 120;
if (gapMin > 0 && dist <= 15)
{
bool aboveGap = birdY < gapMin;
bool belowGap = birdY > gapMax;
int gapCenter = (gapMin + gapMax) / 2;
int afterFlap = birdY - 3;
bool wouldOvershoot = afterFlap <= gapMin;
if (belowGap)
{
shouldFlap = true;
reason = "BELOW";
}
else if (aboveGap)
{
shouldFlap = false;
reason = "ABOVE";
}
else
{
if (dist <= 4)
{
if (birdY >= gapMax && !wouldOvershoot)
{
shouldFlap = true;
reason = "CLOSE-SAVE";
}
else { reason = "CLOSE-OK"; }
}
else if (dist <= 10)
{
if (birdY > gapMax && !wouldOvershoot)
{
shouldFlap = true;
reason = "MED-LOW";
}
else if (birdY > gapCenter + 2 && !wouldOvershoot)
{
shouldFlap = true;
reason = "MED-ADJ";
}
else { reason = "MED-OK"; }
}
else
{
if (birdY > gapCenter + 3 && !wouldOvershoot)
{
shouldFlap = true;
reason = "FAR-LOW";
}
else { reason = "FAR-OK"; }
}
}
}
else
{
if (birdY > 16) { shouldFlap = true; reason = "HOVER"; }
}
if (birdY >= 22) { shouldFlap = true; reason = "EMERG"; }
if (birdY <= 8) shouldFlap = false;
int dy = lastY > 0 ? birdY - lastY : 0;
string gapStr = gapMin > 0 ? $"gap={gapMin}-{gapMax}" : "NO_GAP";
if (shouldFlap && (now - lastFlap) >= cooldown)
{
// Nur noch Loggen wenn er wirklich springt
Log($">>> FLAP! Y={birdY} | d={dist} {gapStr} | {reason}");
Flap();
lastFlap = now;
}
// Der Code-Block, der sonst hier war (else if dist <= 3...), wurde entfernt.
// Dadurch hört der Spam auf.
lastY = birdY;
Delay(25);
}

View File

@ -0,0 +1,244 @@
using System;
using System.Collections.Generic;
using System.Linq;
const int CLICK_DELAY = 2500;
const int GAME_MIN_X = 9;
const int GAME_MAX_X = 26;
const int GAME_MIN_Y = 12;
const int GAME_MAX_Y = 29;
const int GRID_W = 18;
const int GRID_H = 18;
const int TOTAL = 324;
const int KIND_TILE = 3666;
Dictionary<int, int> tileIdByColor = new Dictionary<int, int>();
int[,] grid = new int[GRID_W, GRID_H];
int GetState(dynamic item) { try { return int.Parse(item.State?.ToString() ?? "0"); } catch { return 0; } }
int GetId(dynamic item) { try { return (int)item.Id; } catch { return 0; } }
int GetKind(dynamic item) { try { return (int)item.Kind; } catch { return -1; } }
void ReadGrid()
{
Array.Clear(grid, 0, grid.Length);
tileIdByColor.Clear();
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != KIND_TILE) continue;
int x = item.Location.X, y = item.Location.Y;
if (x < GAME_MIN_X || x > GAME_MAX_X || y < GAME_MIN_Y || y > GAME_MAX_Y) continue;
int gx = x - GAME_MIN_X, gy = GAME_MAX_Y - y;
int state = GetState(item), id = GetId(item);
if (gx >= 0 && gx < GRID_W && gy >= 0 && gy < GRID_H)
{
grid[gx, gy] = state;
if (!tileIdByColor.ContainsKey(state)) tileIdByColor[state] = id;
}
}
}
HashSet<(int,int)> Flood(HashSet<(int,int)> area, int color)
{
var result = new HashSet<(int,int)>(area);
var q = new Queue<(int,int)>();
foreach (var p in area) q.Enqueue(p);
while (q.Count > 0)
{
var (x, y) = q.Dequeue();
foreach (var (nx, ny) in new[]{(x-1,y),(x+1,y),(x,y-1),(x,y+1)})
{
if (nx < 0 || nx >= GRID_W || ny < 0 || ny >= GRID_H) continue;
if (result.Contains((nx,ny))) continue;
if (grid[nx,ny] == color) { result.Add((nx,ny)); q.Enqueue((nx,ny)); }
}
}
return result;
}
HashSet<(int,int)> InitArea()
{
var area = new HashSet<(int,int)>{(0,0)};
return Flood(area, grid[0,0]);
}
HashSet<int> BorderColors(HashSet<(int,int)> area)
{
var c = new HashSet<int>();
foreach (var (x,y) in area)
{
if (x > 0 && !area.Contains((x-1,y))) c.Add(grid[x-1,y]);
if (x < GRID_W-1 && !area.Contains((x+1,y))) c.Add(grid[x+1,y]);
if (y > 0 && !area.Contains((x,y-1))) c.Add(grid[x,y-1]);
if (y < GRID_H-1 && !area.Contains((x,y+1))) c.Add(grid[x,y+1]);
}
return c;
}
HashSet<int> RemainingColors(HashSet<(int,int)> area)
{
var colors = new HashSet<int>();
for (int x = 0; x < GRID_W; x++)
for (int y = 0; y < GRID_H; y++)
if (!area.Contains((x,y))) colors.Add(grid[x,y]);
return colors;
}
int CountRegions(HashSet<(int,int)> area)
{
var visited = new bool[GRID_W, GRID_H];
foreach (var (x,y) in area) visited[x,y] = true;
int regions = 0;
for (int sx = 0; sx < GRID_W; sx++)
{
for (int sy = 0; sy < GRID_H; sy++)
{
if (visited[sx,sy]) continue;
regions++;
var q = new Queue<(int,int)>();
q.Enqueue((sx,sy));
visited[sx,sy] = true;
int c = grid[sx,sy];
while (q.Count > 0)
{
var (x,y) = q.Dequeue();
foreach (var (nx,ny) in new[]{(x-1,y),(x+1,y),(x,y-1),(x,y+1)})
{
if (nx < 0 || nx >= GRID_W || ny < 0 || ny >= GRID_H) continue;
if (visited[nx,ny]) continue;
if (grid[nx,ny] == c) { visited[nx,ny] = true; q.Enqueue((nx,ny)); }
}
}
}
}
return regions;
}
int Heuristic(HashSet<(int,int)> area)
{
var remaining = RemainingColors(area);
int regions = CountRegions(area);
return Math.Max(remaining.Count, (regions + 1) / 2);
}
bool EliminatesColor(HashSet<(int,int)> area, int color)
{
var newArea = Flood(area, color);
for (int x = 0; x < GRID_W; x++)
for (int y = 0; y < GRID_H; y++)
if (!newArea.Contains((x,y)) && grid[x,y] == color) return false;
return true;
}
int Eval(HashSet<(int,int)> area, int depth, int alpha)
{
if (area.Count == TOTAL) return 1000 - depth;
if (depth <= 0) return area.Count - Heuristic(area) * 10;
var borders = BorderColors(area);
int best = -999;
var moves = borders.Select(c => {
var next = Flood(area, c);
bool elim = EliminatesColor(area, c);
return (c, next.Count - area.Count, elim, next);
}).OrderByDescending(m => m.Item3 ? 1000 : 0)
.ThenByDescending(m => m.Item2).ToList();
foreach (var (c, gain, elim, next) in moves)
{
int score = Eval(next, depth - 1, best);
if (score > best) best = score;
if (best >= alpha) break;
}
return best;
}
(int color, int score) BestMove(HashSet<(int,int)> area, int depth)
{
var borders = BorderColors(area);
int bestC = 0, bestS = -9999;
var moves = borders.Select(c => {
var next = Flood(area, c);
bool elim = EliminatesColor(area, c);
return (c, next.Count - area.Count, elim, next);
}).OrderByDescending(m => m.Item3 ? 1000 : 0)
.ThenByDescending(m => m.Item2).ToList();
foreach (var (c, gain, elim, next) in moves)
{
int score;
if (next.Count == TOTAL) score = 10000;
else score = Eval(next, depth - 1, bestS) + (elim ? 50 : 0);
if (score > bestS) { bestS = score; bestC = c; }
}
return (bestC, bestS);
}
List<int> Solve()
{
var moves = new List<int>();
var area = InitArea();
while (area.Count < TOTAL && moves.Count < 32)
{
int remaining = TOTAL - area.Count;
int depth = remaining > 200 ? 3 : remaining > 100 ? 4 : remaining > 50 ? 5 : 6;
var (c, s) = BestMove(area, depth);
if (c == 0) break;
moves.Add(c);
area = Flood(area, c);
}
return moves;
}
void Click(int color)
{
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != KIND_TILE) continue;
int x = item.Location.X, y = item.Location.Y;
if (x < GAME_MIN_X || x > GAME_MAX_X || y < GAME_MIN_Y || y > GAME_MAX_Y) continue;
int state = GetState(item);
if (state == color)
{
Send(Out["ClickFurni"], (int)item.Id, 0);
return;
}
}
}
Log("═══════════════════════════════════════════");
Log(" FLOOD-IT BOT - RESEARCH BASED");
Log("═══════════════════════════════════════════");
while (Run)
{
ReadGrid();
var area = InitArea();
Log($"Start: {area.Count}/{TOTAL}");
if (area.Count == TOTAL) { Log("COMPLETE!"); Delay(2000); continue; }
Log("Solving...");
var sw = System.Diagnostics.Stopwatch.StartNew();
var solution = Solve();
sw.Stop();
Log($"Solution: {string.Join(",", solution)} ({solution.Count} moves) in {sw.ElapsedMilliseconds}ms");
foreach (var c in solution)
{
Click(c);
Log($"Click {c}");
Delay(CLICK_DELAY);
}
Delay(1000);
}

View File

@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
string Safe(string s)
{
return string.IsNullOrWhiteSpace(s) ? "" : s.Replace("\r", " ").Replace("\n", " ").Trim();
}
int GetKind(dynamic item)
{
try { return (int)item.Kind; }
catch { return -1; }
}
int GetState(dynamic item)
{
try { return int.Parse(item.State?.ToString() ?? "0", CultureInfo.InvariantCulture); }
catch { return 0; }
}
int GetDir(dynamic item)
{
try { return (int)item.Direction; }
catch { return 0; }
}
string GetNameSafe(dynamic item)
{
try { return Safe(item.GetName()); }
catch { return ""; }
}
string EscCsv(string s)
{
s = s ?? "";
if (s.Contains(",") || s.Contains("\"") || s.Contains("\n"))
return "\"" + s.Replace("\"", "\"\"") + "\"";
return s;
}
var rows = new List<string>();
rows.Add("id,kind,name,x,y,z,state,dir");
int count = 0;
foreach (var item in FloorItems)
{
if (item == null) continue;
count++;
long id = item.Id;
int kind = GetKind(item);
string name = GetNameSafe(item);
int x = item.Location.X;
int y = item.Location.Y;
double z = item.Location.Z;
int state = GetState(item);
int dir = GetDir(item);
rows.Add(string.Join(",", new[] {
id.ToString(CultureInfo.InvariantCulture),
kind.ToString(CultureInfo.InvariantCulture),
EscCsv(name),
x.ToString(CultureInfo.InvariantCulture),
y.ToString(CultureInfo.InvariantCulture),
z.ToString("0.###", CultureInfo.InvariantCulture),
state.ToString(CultureInfo.InvariantCulture),
dir.ToString(CultureInfo.InvariantCulture)
}));
}
if (count == 0)
{
Log("ERROR: No floor items found.");
return;
}
string roomName = "room";
try { roomName = Safe(Room?.Name ?? "room"); } catch { roomName = "room"; }
if (string.IsNullOrWhiteSpace(roomName)) roomName = "room";
var invalid = Path.GetInvalidFileNameChars();
foreach (char c in invalid) roomName = roomName.Replace(c, '_');
string stamp = DateTime.Now.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture);
string exportDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "HabboFloorExports");
Directory.CreateDirectory(exportDir);
string filePath = Path.Combine(exportDir, $"{roomName}_floor_{stamp}.csv");
File.WriteAllText(filePath, string.Join(Environment.NewLine, rows), Encoding.UTF8);
Log("=== Floor Export Complete ===");
Log($"Items exported: {count}");
Log($"File: {filePath}");

367
Furni-Matic Auto-Eco.csx Normal file
View File

@ -0,0 +1,367 @@
using System;
using System.Net;
using System.Text;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Text.Json;
using System.Text.Json.Serialization;
using Xabbo.Messages;
#nullable enable
public class ItemView {
[JsonPropertyName("h")] public string HabboId { get; set; } = "";
[JsonPropertyName("n")] public string Name { get; set; } = "";
[JsonPropertyName("r")] public int Revision { get; set; }
[JsonPropertyName("c")] public int Count { get; set; }
}
public class LiveState {
[JsonPropertyName("items")] public List<ItemView> Items { get; set; } = new();
[JsonPropertyName("status")] public StatusUpdate Status { get; set; } = new();
}
public class RecycleRequest {
[JsonPropertyName("items")] public List<RecycleItem> Items { get; set; } = new();
[JsonPropertyName("delay")] public int Delay { get; set; }
}
public class RecycleItem {
[JsonPropertyName("h")] public string HabboId { get; set; } = "";
[JsonPropertyName("a")] public int Amount { get; set; }
}
public class StatusUpdate {
[JsonPropertyName("done")] public int Done { get; set; }
[JsonPropertyName("total")] public int Total { get; set; }
}
var port = 8226;
var queue = new List<int>();
var progress = 0;
var total = 0;
var delay = 12000;
var lastRun = DateTime.Now;
HttpListener? server = null;
void TryRefreshInventory() {
try { Send(Out["RequestFurniInventory"]); return; } catch { }
}
OnIntercept(In["RecyclerFinished"], e => {
TryRefreshInventory();
});
LiveState GetCurrentState() {
EnsureInventory(10000);
var currentItems = Inventory.Where(x => x.IsRecyclable)
.GroupBy(x => x.GetDescriptor())
.Where(g => g.Count() >= 8)
.Select(g => new ItemView {
HabboId = g.Key.GetInfo().Identifier,
Name = g.Key.GetName(),
Revision = g.Key.GetInfo().Revision,
Count = g.Count()
})
.OrderByDescending(x => x.Count)
.ToList();
if (progress >= total && total > 0) {
progress = total = 0;
}
return new LiveState {
Items = currentItems,
Status = new StatusUpdate { Done = progress, Total = total }
};
}
try {
log("starting server...");
var html = @"<!DOCTYPE html>
<html lang=""en"">
<head>
<meta charset=""utf-8""><title>Recycler</title><meta name=""viewport"" content=""width=device-width, initial-scale=1"">
<link rel=""preconnect"" href=""https://fonts.googleapis.com""><link rel=""preconnect"" href=""https://fonts.gstatic.com"" crossorigin>
<link href=""https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap"" rel=""stylesheet"">
<style>
:root{--bg-deep:#111;--bg-med:#1C1C1C;--bg-light:#2C2C2C;--border:#333;--text-bright:#EAEAEA;--text-dim:#888;--accent:#00dd99;--accent-dark:#00b37b;--danger:#ff4757;}
*{margin:0;padding:0;box-sizing:border-box;}
body{background:var(--bg-deep);color:var(--text-bright);font-family:'Inter',sans-serif;padding:1.5rem;font-size:14px;}
.wrap{max-width:1200px;margin:0 auto;display:grid;grid-template-columns:1fr;gap:1rem;}
.panel{background:var(--bg-med);border:1px solid var(--border);border-radius:6px;padding:1rem;}
.header{text-align:center;font-size:1.5rem;font-weight:700;color:var(--text-bright);margin-bottom:1rem;}
.controls{display:flex;align-items:center;justify-content:center;gap:0.75rem;flex-wrap:wrap;}
.controls label{display:flex;align-items:center;gap:0.5rem;color:var(--text-dim);}
button{background:var(--accent);color:#000;border:none;padding:0.5rem 1rem;border-radius:4px;font-weight:600;cursor:pointer;transition:background-color 150ms ease;}
button:hover{background:var(--accent-dark);}
button:disabled{background:var(--bg-light);color:var(--text-dim);cursor:not-allowed;}
button.secondary{background:var(--bg-light);color:var(--text-bright);border:1px solid var(--border);}
button.secondary:hover{background-color:var(--border);}
button.danger{background:var(--danger);}
button.danger:hover{background:#d63031;}
input[type=number],input[type=text]{width:80px;background:var(--bg-light);border:1px solid var(--border);color:var(--text-bright);padding:0.5rem;border-radius:4px;text-align:center;}
input[type=text]{width:100%;text-align:left;}
input:focus{outline:none;border-color:var(--accent);}
.progress-bar{height:2rem;background:var(--bg-light);border-radius:4px;position:relative;overflow:hidden;}
.progress-fill{width:0;height:100%;background:var(--accent);transition:width 300ms ease-out;}
.progress-text{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-weight:600;text-shadow:0 1px 2px #000;}
.main-layout{display:grid;grid-template-columns:2fr 1fr;gap:1rem;}
.status-bar{display:flex;justify-content:space-between;align-items:center;padding:0.5rem 0;color:var(--text-dim);font-size:0.8rem;}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:0.75rem;max-height:60vh;overflow-y:auto;padding-right:0.5rem;}
.grid::-webkit-scrollbar{width:6px;} .grid::-webkit-scrollbar-track{background:transparent;} .grid::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px;}
.card{background:var(--bg-light);border-radius:4px;padding:0.75rem;text-align:center;cursor:pointer;border:2px solid transparent;transition:border-color 150ms ease, background-color 150ms ease;}
.card.selected{border-color:var(--accent);}
.card .name{font-size:0.8rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0.25rem 0;}
.card .count{font-size:0.75rem;color:var(--text-dim);}
h2{font-size:1rem;font-weight:600;margin-bottom:0.75rem;}
.queue-list{display:flex;flex-direction:column;gap:0.5rem;max-height:calc(60vh - 2rem);overflow-y:auto;}
.queue-item{display:flex;align-items:center;gap:0.5rem;background:var(--bg-light);padding:0.5rem;border-radius:4px;}
.queue-item .name{flex-grow:1;font-size:0.8rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.queue-item .remove-btn{background:transparent;border:none;color:var(--text-dim);font-size:1.25rem;padding:0 0.25rem;cursor:pointer;line-height:1;}
.queue-item .remove-btn:hover{color:var(--danger);}
.empty-state{text-align:center;color:var(--text-dim);font-size:0.85rem;padding:3rem 1rem;border:2px dashed var(--border);border-radius:4px;}
@media (max-width: 900px) {.main-layout{grid-template-columns:1fr;}}
</style>
</head>
<body>
<div class=""wrap"">
<div class=""header"">recycler</div>
<div class=""panel""><div class=""progress-bar""><div class=""progress-fill"" id=""progress-fill""></div><div class=""progress-text"" id=""progress-text"">idle</div></div></div>
<div class=""panel"">
<div class=""controls"">
<button id=""start-btn"">start</button><button id=""stop-btn"" class=""danger"">stop</button>
<label>delay <input type=""number"" id=""delay-input"" value=""12000"" min=""500"" step=""100""></label>
<button id=""clear-selection-btn"" class=""secondary"">clear selection</button><button id=""clear-queue-btn"" class=""secondary"">clear queue</button>
</div>
</div>
<div class=""main-layout"">
<div class=""panel grid-container"">
<input type=""text"" id=""search-input"" placeholder=""search items..."">
<div class=""status-bar""><span id=""info-text"">select items</span><button id=""add-selected-btn"" class=""secondary"" style=""display:none;"">add selected to queue</button></div>
<div class=""grid"" id=""grid""></div>
</div>
<div class=""panel queue-container"">
<h2>queue</h2>
<div class=""queue-list"" id=""queue-list""></div>
</div>
</div>
</div>
<script>
const state={items:[],selected:new Set(),queue:[],amounts:{},searchTerm:'',status:{done:0,total:0},wasRunning:false};
const dom={grid:document.getElementById('grid'),queueList:document.getElementById('queue-list'),infoText:document.getElementById('info-text'),addSelectedBtn:document.getElementById('add-selected-btn'),progressFill:document.getElementById('progress-fill'),progressText:document.getElementById('progress-text'),searchInput:document.getElementById('search-input'),delayInput:document.getElementById('delay-input'),startBtn:document.getElementById('start-btn')};
const api={
recycle:()=>fetch('/recycle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({items:state.queue.map(habboId=>({h:habboId,a:state.amounts[habboId]})) ,delay:parseInt(dom.delayInput.value)})}),
stop:()=>fetch('/stop',{method:'POST'}),
getState:()=>fetch('/state').then(r=>r.json())
};
const ui={
renderCard:item=>{
const card=document.createElement('div');
card.className=`card ${state.selected.has(item.h)?'selected':''}`;
card.dataset.id=item.h;
card.innerHTML=`<img src=""https://images.habbo.com/dcr/hof_furni/${item.r}/${item.h}_icon.png"" alt=""""><div class=""name"" title=""${item.n}"">${item.n}</div><div class=""count"">${item.c}x | ${state.amounts[item.h]}</div>`;
return card;
},
renderQueueItem:habboId=>{
const item=state.items.find(x=>x.h===habboId);
if(!item)return null;
const li=document.createElement('div');
li.className='queue-item';li.dataset.id=habboId;
li.innerHTML=`<img src=""https://images.habbo.com/dcr/hof_furni/${item.r}/${item.h}_icon.png"" alt=""""><span class=""name"">${item.n} (${state.amounts[habboId]})</span><button class=""remove-btn"">&times;</button>`;
return li;
},
updateGrid:()=>{
const fragment=document.createDocumentFragment();
const filtered=state.items.filter(item=>item.n.toLowerCase().includes(state.searchTerm));
if(filtered.length===0){dom.grid.innerHTML='<div class=""empty-state"">no items found</div>';return;}
filtered.forEach(item=>fragment.appendChild(ui.renderCard(item)));
dom.grid.innerHTML='';dom.grid.appendChild(fragment);
},
updateQueue:()=>{
state.queue=state.queue.filter(habboId=>state.items.some(item=>item.h===habboId));
if(state.queue.length===0){dom.queueList.innerHTML='<div class=""empty-state"">queue is empty</div>';return;}
const fragment=document.createDocumentFragment();
state.queue.forEach(habboId=>{
const itemElement = ui.renderQueueItem(habboId);
if(itemElement) fragment.appendChild(itemElement);
});
dom.queueList.innerHTML='';dom.queueList.appendChild(fragment);
},
updateInfo:()=>{
dom.infoText.textContent=`${state.selected.size} items selected`;
dom.addSelectedBtn.style.display=state.selected.size>0?'inline-block':'none';
},
updateStatus:()=>{
const currentStatus=state.status;
const isRunning=currentStatus.total>0;
const percent=isRunning?Math.round((currentStatus.done/currentStatus.total)*100):0;
dom.progressFill.style.width=`${percent}%`;
dom.startBtn.disabled=isRunning;
if(isRunning)dom.progressText.textContent=`${currentStatus.done}/${currentStatus.total}`;
else if(state.wasRunning)dom.progressText.textContent='complete';
else dom.progressText.textContent='idle';
if(state.wasRunning&&!isRunning)setTimeout(()=>dom.progressText.textContent='idle',2500);
state.wasRunning=isRunning;
},
renderAll:()=>{ui.updateGrid();ui.updateQueue();ui.updateInfo();ui.updateStatus();}
};
const handlers={
gridClick:e=>{
const card=e.target.closest('.card');if(!card)return;
const habboId=card.dataset.id;
if(e.detail===2){
const inQueue=state.queue.includes(habboId);
if(inQueue)state.queue=state.queue.filter(x=>x!==habboId);
else state.queue.push(habboId);
}else{
state.selected.has(habboId)?state.selected.delete(habboId):state.selected.add(habboId);
}
ui.renderAll();
},
queueClick:e=>{
if(!e.target.classList.contains('remove-btn'))return;
const habboId=e.target.closest('.queue-item').dataset.id;
state.queue=state.queue.filter(x=>x!==habboId);
ui.renderAll();
},
addSelected:()=>{
state.selected.forEach(habboId=>{if(!state.queue.includes(habboId))state.queue.push(habboId);});
state.selected.clear();
ui.renderAll();
},
updateAllAmounts:()=>{
state.items.forEach(item=>{if(!state.amounts[item.h])state.amounts[item.h]=Math.floor(item.c/8)*8;});
},
init:()=>{
document.getElementById('start-btn').onclick=()=>{if(state.queue.length===0){alert('add items to queue');return;}api.recycle();};
document.getElementById('stop-btn').onclick=api.stop;
document.getElementById('clear-selection-btn').onclick=()=>{state.selected.clear();ui.renderAll();};
document.getElementById('clear-queue-btn').onclick=()=>{state.queue=[];ui.renderAll();};
dom.addSelectedBtn.onclick=handlers.addSelected;
dom.grid.addEventListener('click',handlers.gridClick);
dom.queueList.addEventListener('click',handlers.queueClick);
dom.searchInput.addEventListener('input',e=>{state.searchTerm=e.target.value.toLowerCase();ui.updateGrid();});
setInterval(()=>{
api.getState().then(newState=>{
state.items=newState.items;
state.status=newState.status;
handlers.updateAllAmounts();
ui.renderAll();
});
},1500);
}
};
handlers.init();
</script>
</body>
</html>";
server = new HttpListener();
server.Prefixes.Add($"http://localhost:{port}/");
server.Start();
Process.Start(new ProcessStartInfo { FileName = $"http://localhost:{port}/", UseShellExecute = true });
log($"server listening on http://localhost:{port}");
_ = Task.Run(async () => {
while (Run && server.IsListening) {
try {
var ctx = await server.GetContextAsync();
_ = Task.Run(() => HandleContextAsync(ctx, html));
} catch { break; }
}
});
while (Run) {
if (queue.Count >= 8 && (DateTime.Now - lastRun).TotalMilliseconds >= delay) {
var batch = queue.Take(8).ToList();
queue.RemoveRange(0, 8);
Send(Out["RecycleItems"], 8, batch[0], batch[1], batch[2], batch[3], batch[4], batch[5], batch[6], batch[7]);
progress += 8;
lastRun = DateTime.Now;
}
await Task.Delay(10);
}
}
catch (TaskCanceledException) {
log("error: inventory load timed out. ensure you are fully in-game.");
}
catch (Exception ex) {
log($"unhandled exception: {ex.Message}");
}
finally {
server?.Stop();
server?.Close();
log("server shut down.");
}
async Task HandleContextAsync(HttpListenerContext ctx, string html) {
var req = ctx.Request;
var res = ctx.Response;
try {
var endpoint = (req.HttpMethod, req.Url?.AbsolutePath);
switch (endpoint) {
case ("GET", "/"):
await WriteResponseAsync(res, html, "text/html");
break;
case ("GET", "/state"):
var stateJson = JsonSerializer.Serialize(GetCurrentState());
await WriteResponseAsync(res, stateJson, "application/json");
break;
case ("POST", "/recycle"):
TryRefreshInventory();
EnsureInventory(10000);
using (var r = new StreamReader(req.InputStream)) {
var payload = JsonSerializer.Deserialize<RecycleRequest>(await r.ReadToEndAsync());
if (payload != null) {
queue.Clear();
delay = Math.Clamp(payload.Delay, 500, 60000);
foreach (var pItem in payload.Items) {
var itemInstances = Inventory
.Where(x => x.IsRecyclable && x.GetInfo().Identifier == pItem.HabboId)
.Select(i => -(int)i.Id)
.ToList();
int amount = Math.Min(pItem.Amount, itemInstances.Count);
queue.AddRange(itemInstances.Take(amount));
}
total = queue.Count;
progress = 0;
log($"queueing {total} items, {delay}ms delay.");
}
}
res.StatusCode = 200;
break;
case ("POST", "/stop"):
queue.Clear();
progress = total = 0;
log("recycling stopped.");
res.StatusCode = 200;
break;
default:
res.StatusCode = 404;
break;
}
}
catch (Exception ex) {
log($"request error: {ex.Message}");
if(!res.OutputStream.CanWrite) return;
res.StatusCode = 500;
}
finally {
res.Close();
}
}
async Task WriteResponseAsync(HttpListenerResponse res, string content, string type) {
var buffer = Encoding.UTF8.GetBytes(content);
res.ContentType = type;
res.ContentLength64 = buffer.Length;
await res.OutputStream.WriteAsync(buffer, 0, buffer.Length);
}
void log(string message) => Log(message);

20
Furni-Matic Unboxing.csx Normal file
View File

@ -0,0 +1,20 @@
Send(Out["RequestFurniInventory"]);
Delay(200);
EnsureInventory(5000);
var name = "Mystery Box";
var boxes = Inventory.Where(x => x.GetDescriptor().GetName() == name);
var freeTiles = Heightmap.Where(x => x.IsFree);
foreach (var box in boxes) {
Place(box, Rand(freeTiles).Location);
Delay(50);
}
while (Run) {
foreach (var box in FloorItems.Where(x => x.GetName() == name))
Send(Out["PresentOpen"], (int)box.Id);
Delay(100);
}
Wait();

366
Furni.csx Normal file
View File

@ -0,0 +1,366 @@
using System;
using System.Net;
using System.Text;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Text.Json;
using System.Text.Json.Serialization;
using Xabbo.Messages;
#nullable enable
public class ItemView {
[JsonPropertyName("h")] public string HabboId { get; set; } = "";
[JsonPropertyName("n")] public string Name { get; set; } = "";
[JsonPropertyName("r")] public int Revision { get; set; }
[JsonPropertyName("c")] public int Count { get; set; }
}
public class LiveState {
[JsonPropertyName("items")] public List<ItemView> Items { get; set; } = new();
[JsonPropertyName("status")] public StatusUpdate Status { get; set; } = new();
}
public class RecycleRequest {
[JsonPropertyName("items")] public List<RecycleItem> Items { get; set; } = new();
[JsonPropertyName("delay")] public int Delay { get; set; }
}
public class RecycleItem {
[JsonPropertyName("h")] public string HabboId { get; set; } = "";
[JsonPropertyName("a")] public int Amount { get; set; }
}
public class StatusUpdate {
[JsonPropertyName("done")] public int Done { get; set; }
[JsonPropertyName("total")] public int Total { get; set; }
}
var port = 8226;
var queue = new List<int>();
var progress = 0;
var total = 0;
var delay = 12000;
var lastRun = DateTime.Now;
HttpListener? server = null;
void TryRefreshInventory() {
try { Send(Out["RequestFurniInventory"]); return; } catch { }
}
OnIntercept(In["RecyclerFinished"], e => {
TryRefreshInventory();
});
LiveState GetCurrentState() {
EnsureInventory(10000);
var currentItems = Inventory.Where(x => x.IsRecyclable)
.GroupBy(x => x.GetDescriptor())
.Where(g => g.Count() >= 8)
.Select(g => new ItemView {
HabboId = g.Key.GetInfo().Identifier,
Name = g.Key.GetName(),
Revision = g.Key.GetInfo().Revision,
Count = g.Count()
})
.OrderByDescending(x => x.Count)
.ToList();
if (progress >= total && total > 0) {
progress = total = 0;
}
return new LiveState {
Items = currentItems,
Status = new StatusUpdate { Done = progress, Total = total }
};
}
try {
log("starting server...");
var html = @"<!DOCTYPE html>
<html lang=""en"">
<head>
<meta charset=""utf-8""><title>Recycler</title><meta name=""viewport"" content=""width=device-width, initial-scale=1"">
<link rel=""preconnect"" href=""https://fonts.googleapis.com""><link rel=""preconnect"" href=""https://fonts.gstatic.com"" crossorigin>
<link href=""https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap"" rel=""stylesheet"">
<style>
:root{--bg-deep:#111;--bg-med:#1C1C1C;--bg-light:#2C2C2C;--border:#333;--text-bright:#EAEAEA;--text-dim:#888;--accent:#00dd99;--accent-dark:#00b37b;--danger:#ff4757;}
*{margin:0;padding:0;box-sizing:border-box;}
body{background:var(--bg-deep);color:var(--text-bright);font-family:'Inter',sans-serif;padding:1.5rem;font-size:14px;}
.wrap{max-width:1200px;margin:0 auto;display:grid;grid-template-columns:1fr;gap:1rem;}
.panel{background:var(--bg-med);border:1px solid var(--border);border-radius:6px;padding:1rem;}
.header{text-align:center;font-size:1.5rem;font-weight:700;color:var(--text-bright);margin-bottom:1rem;}
.controls{display:flex;align-items:center;justify-content:center;gap:0.75rem;flex-wrap:wrap;}
.controls label{display:flex;align-items:center;gap:0.5rem;color:var(--text-dim);}
button{background:var(--accent);color:#000;border:none;padding:0.5rem 1rem;border-radius:4px;font-weight:600;cursor:pointer;transition:background-color 150ms ease;}
button:hover{background:var(--accent-dark);}
button:disabled{background:var(--bg-light);color:var(--text-dim);cursor:not-allowed;}
button.secondary{background:var(--bg-light);color:var(--text-bright);border:1px solid var(--border);}
button.secondary:hover{background-color:var(--border);}
button.danger{background:var(--danger);}
button.danger:hover{background:#d63031;}
input[type=number],input[type=text]{width:80px;background:var(--bg-light);border:1px solid var(--border);color:var(--text-bright);padding:0.5rem;border-radius:4px;text-align:center;}
input[type=text]{width:100%;text-align:left;}
input:focus{outline:none;border-color:var(--accent);}
.progress-bar{height:2rem;background:var(--bg-light);border-radius:4px;position:relative;overflow:hidden;}
.progress-fill{width:0;height:100%;background:var(--accent);transition:width 300ms ease-out;}
.progress-text{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-weight:600;text-shadow:0 1px 2px #000;}
.main-layout{display:grid;grid-template-columns:2fr 1fr;gap:1rem;}
.status-bar{display:flex;justify-content:space-between;align-items:center;padding:0.5rem 0;color:var(--text-dim);font-size:0.8rem;}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:0.75rem;max-height:60vh;overflow-y:auto;padding-right:0.5rem;}
.grid::-webkit-scrollbar{width:6px;} .grid::-webkit-scrollbar-track{background:transparent;} .grid::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px;}
.card{background:var(--bg-light);border-radius:4px;padding:0.75rem;text-align:center;cursor:pointer;border:2px solid transparent;transition:border-color 150ms ease, background-color 150ms ease;}
.card.selected{border-color:var(--accent);}
.card .name{font-size:0.8rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0.25rem 0;}
.card .count{font-size:0.75rem;color:var(--text-dim);}
h2{font-size:1rem;font-weight:600;margin-bottom:0.75rem;}
.queue-list{display:flex;flex-direction:column;gap:0.5rem;max-height:calc(60vh - 2rem);overflow-y:auto;}
.queue-item{display:flex;align-items:center;gap:0.5rem;background:var(--bg-light);padding:0.5rem;border-radius:4px;}
.queue-item .name{flex-grow:1;font-size:0.8rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.queue-item .remove-btn{background:transparent;border:none;color:var(--text-dim);font-size:1.25rem;padding:0 0.25rem;cursor:pointer;line-height:1;}
.queue-item .remove-btn:hover{color:var(--danger);}
.empty-state{text-align:center;color:var(--text-dim);font-size:0.85rem;padding:3rem 1rem;border:2px dashed var(--border);border-radius:4px;}
@media (max-width: 900px) {.main-layout{grid-template-columns:1fr;}}
</style>
</head>
<body>
<div class=""wrap"">
<div class=""header"">recycler</div>
<div class=""panel""><div class=""progress-bar""><div class=""progress-fill"" id=""progress-fill""></div><div class=""progress-text"" id=""progress-text"">idle</div></div></div>
<div class=""panel"">
<div class=""controls"">
<button id=""start-btn"">start</button><button id=""stop-btn"" class=""danger"">stop</button>
<label>delay <input type=""number"" id=""delay-input"" value=""12000"" min=""500"" step=""100""></label>
<button id=""clear-selection-btn"" class=""secondary"">clear selection</button><button id=""clear-queue-btn"" class=""secondary"">clear queue</button>
</div>
</div>
<div class=""main-layout"">
<div class=""panel grid-container"">
<input type=""text"" id=""search-input"" placeholder=""search items..."">
<div class=""status-bar""><span id=""info-text"">select items</span><button id=""add-selected-btn"" class=""secondary"" style=""display:none;"">add selected to queue</button></div>
<div class=""grid"" id=""grid""></div>
</div>
<div class=""panel queue-container"">
<h2>queue</h2>
<div class=""queue-list"" id=""queue-list""></div>
</div>
</div>
</div>
<script>
const state={items:[],selected:new Set(),queue:[],amounts:{},searchTerm:'',status:{done:0,total:0},wasRunning:false};
const dom={grid:document.getElementById('grid'),queueList:document.getElementById('queue-list'),infoText:document.getElementById('info-text'),addSelectedBtn:document.getElementById('add-selected-btn'),progressFill:document.getElementById('progress-fill'),progressText:document.getElementById('progress-text'),searchInput:document.getElementById('search-input'),delayInput:document.getElementById('delay-input'),startBtn:document.getElementById('start-btn')};
const api={
recycle:()=>fetch('/recycle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({items:state.queue.map(habboId=>({h:habboId,a:state.amounts[habboId]})) ,delay:parseInt(dom.delayInput.value)})}),
stop:()=>fetch('/stop',{method:'POST'}),
getState:()=>fetch('/state').then(r=>r.json())
};
const ui={
renderCard:item=>{
const card=document.createElement('div');
card.className=`card ${state.selected.has(item.h)?'selected':''}`;
card.dataset.id=item.h;
card.innerHTML=`<img src=""https://images.habbo.com/dcr/hof_furni/${item.r}/${item.h}_icon.png"" alt=""""><div class=""name"" title=""${item.n}"">${item.n}</div><div class=""count"">${item.c}x | ${state.amounts[item.h]}</div>`;
return card;
},
renderQueueItem:habboId=>{
const item=state.items.find(x=>x.h===habboId);
if(!item)return null;
const li=document.createElement('div');
li.className='queue-item';li.dataset.id=habboId;
li.innerHTML=`<img src=""https://images.habbo.com/dcr/hof_furni/${item.r}/${item.h}_icon.png"" alt=""""><span class=""name"">${item.n} (${state.amounts[habboId]})</span><button class=""remove-btn"">&times;</button>`;
return li;
},
updateGrid:()=>{
const fragment=document.createDocumentFragment();
const filtered=state.items.filter(item=>item.n.toLowerCase().includes(state.searchTerm));
if(filtered.length===0){dom.grid.innerHTML='<div class=""empty-state"">no items found</div>';return;}
filtered.forEach(item=>fragment.appendChild(ui.renderCard(item)));
dom.grid.innerHTML='';dom.grid.appendChild(fragment);
},
updateQueue:()=>{
state.queue=state.queue.filter(habboId=>state.items.some(item=>item.h===habboId));
if(state.queue.length===0){dom.queueList.innerHTML='<div class=""empty-state"">queue is empty</div>';return;}
const fragment=document.createDocumentFragment();
state.queue.forEach(habboId=>{
const itemElement = ui.renderQueueItem(habboId);
if(itemElement) fragment.appendChild(itemElement);
});
dom.queueList.innerHTML='';dom.queueList.appendChild(fragment);
},
updateInfo:()=>{
dom.infoText.textContent=`${state.selected.size} items selected`;
dom.addSelectedBtn.style.display=state.selected.size>0?'inline-block':'none';
},
updateStatus:()=>{
const currentStatus=state.status;
const isRunning=currentStatus.total>0;
const percent=isRunning?Math.round((currentStatus.done/currentStatus.total)*100):0;
dom.progressFill.style.width=`${percent}%`;
dom.startBtn.disabled=isRunning;
if(isRunning)dom.progressText.textContent=`${currentStatus.done}/${currentStatus.total}`;
else if(state.wasRunning)dom.progressText.textContent='complete';
else dom.progressText.textContent='idle';
if(state.wasRunning&&!isRunning)setTimeout(()=>dom.progressText.textContent='idle',2500);
state.wasRunning=isRunning;
},
renderAll:()=>{ui.updateGrid();ui.updateQueue();ui.updateInfo();ui.updateStatus();}
};
const handlers={
gridClick:e=>{
const card=e.target.closest('.card');if(!card)return;
const habboId=card.dataset.id;
if(e.detail===2){
const inQueue=state.queue.includes(habboId);
if(inQueue)state.queue=state.queue.filter(x=>x!==habboId);
else state.queue.push(habboId);
}else{
state.selected.has(habboId)?state.selected.delete(habboId):state.selected.add(habboId);
}
ui.renderAll();
},
queueClick:e=>{
if(!e.target.classList.contains('remove-btn'))return;
const habboId=e.target.closest('.queue-item').dataset.id;
state.queue=state.queue.filter(x=>x!==habboId);
ui.renderAll();
},
addSelected:()=>{
state.selected.forEach(habboId=>{if(!state.queue.includes(habboId))state.queue.push(habboId);});
state.selected.clear();
ui.renderAll();
},
updateAllAmounts:()=>{
state.items.forEach(item=>{if(!state.amounts[item.h])state.amounts[item.h]=Math.floor(item.c/8)*8;});
},
init:()=>{
document.getElementById('start-btn').onclick=()=>{if(state.queue.length===0){alert('add items to queue');return;}api.recycle();};
document.getElementById('stop-btn').onclick=api.stop;
document.getElementById('clear-selection-btn').onclick=()=>{state.selected.clear();ui.renderAll();};
document.getElementById('clear-queue-btn').onclick=()=>{state.queue=[];ui.renderAll();};
dom.addSelectedBtn.onclick=handlers.addSelected;
dom.grid.addEventListener('click',handlers.gridClick);
dom.queueList.addEventListener('click',handlers.queueClick);
dom.searchInput.addEventListener('input',e=>{state.searchTerm=e.target.value.toLowerCase();ui.updateGrid();});
setInterval(()=>{
api.getState().then(newState=>{
state.items=newState.items;
state.status=newState.status;
handlers.updateAllAmounts();
ui.renderAll();
});
},1500);
}
};
handlers.init();
</script>
</body>
</html>";
server = new HttpListener();
server.Prefixes.Add($"http://localhost:{port}/");
server.Start();
Process.Start(new ProcessStartInfo { FileName = $"http://localhost:{port}/", UseShellExecute = true });
log($"server listening on http://localhost:{port}");
_ = Task.Run(async () => {
while (Run && server.IsListening) {
try {
var ctx = await server.GetContextAsync();
_ = Task.Run(() => HandleContextAsync(ctx, html));
} catch { break; }
}
});
while (Run) {
if (queue.Count >= 8 && (DateTime.Now - lastRun).TotalMilliseconds >= delay) {
var batch = queue.Take(8).ToList();
queue.RemoveRange(0, 8);
Send(Out["RecycleItems"], 8, batch[0], batch[1], batch[2], batch[3], batch[4], batch[5], batch[6], batch[7]);
progress += 8;
lastRun = DateTime.Now;
}
await Task.Delay(10);
}
}
catch (TaskCanceledException) {
log("error: inventory load timed out. ensure you are fully in-game.");
}
catch (Exception ex) {
log($"unhandled exception: {ex.Message}");
}
finally {
server?.Stop();
server?.Close();
log("server shut down.");
}
async Task HandleContextAsync(HttpListenerContext ctx, string html) {
var req = ctx.Request;
var res = ctx.Response;
try {
var endpoint = (req.HttpMethod, req.Url?.AbsolutePath);
switch (endpoint) {
case ("GET", "/"):
await WriteResponseAsync(res, html, "text/html");
break;
case ("GET", "/state"):
var stateJson = JsonSerializer.Serialize(GetCurrentState());
await WriteResponseAsync(res, stateJson, "application/json");
break;
case ("POST", "/recycle"):
EnsureInventory(10000);
using (var r = new StreamReader(req.InputStream)) {
var payload = JsonSerializer.Deserialize<RecycleRequest>(await r.ReadToEndAsync());
if (payload != null) {
queue.Clear();
delay = Math.Clamp(payload.Delay, 500, 60000);
foreach (var pItem in payload.Items) {
var itemInstances = Inventory
.Where(x => x.IsRecyclable && x.GetInfo().Identifier == pItem.HabboId)
.Select(i => -(int)i.Id)
.ToList();
int amount = Math.Min(pItem.Amount, itemInstances.Count);
queue.AddRange(itemInstances.Take(amount));
}
total = queue.Count;
progress = 0;
log($"queueing {total} items, {delay}ms delay.");
}
}
res.StatusCode = 200;
break;
case ("POST", "/stop"):
queue.Clear();
progress = total = 0;
log("recycling stopped.");
res.StatusCode = 200;
break;
default:
res.StatusCode = 404;
break;
}
}
catch (Exception ex) {
log($"request error: {ex.Message}");
if(!res.OutputStream.CanWrite) return;
res.StatusCode = 500;
}
finally {
res.Close();
}
}
async Task WriteResponseAsync(HttpListenerResponse res, string content, string type) {
var buffer = Encoding.UTF8.GetBytes(content);
res.ContentType = type;
res.ContentLength64 = buffer.Length;
await res.OutputStream.WriteAsync(buffer, 0, buffer.Length);
}
void log(string message) => Log(message);

View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
// ═══════════════════════════════════════════════════════════════════════════════
// DEBUG - ZEIGT ALLE POSITIONEN
// ═══════════════════════════════════════════════════════════════════════════════
const int PILLOW_ID = 893412986;
const int GATE_ID = 2147418143;
HashSet<int> greenRollers = new HashSet<int> {
2147418115, 2147418119, 2147418133,
2147418134, 2147418135, 2147418136
};
int GetId(dynamic item) { try { return (int)item.Id; } catch { return 0; } }
Log("═══════════════════════════════════════");
Log(" DEBUG - POSITIONEN");
Log("═══════════════════════════════════════");
while (Run)
{
Delay(1000);
Log("--- SCAN ---");
// Meine Position
try
{
Log($"ICH: ({Self.Location.X}, {Self.Location.Y})");
}
catch { Log("ICH: nicht gefunden"); }
// Kissen
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetId(item) == PILLOW_ID)
{
Log($"KISSEN: ({item.Location.X}, {item.Location.Y})");
}
}
// Alle grünen Roller
foreach (var item in FloorItems)
{
if (item == null) continue;
int id = GetId(item);
if (greenRollers.Contains(id))
{
Log($"ROLLER {id}: ({item.Location.X}, {item.Location.Y})");
}
if (id == GATE_ID)
{
Log($"GATE: ({item.Location.X}, {item.Location.Y})");
}
}
}

97
Heal Bot.csx Normal file
View File

@ -0,0 +1,97 @@
using System.Collections.Generic;
using System.Threading.Tasks;
// Globale Variablen für den State
bool isBusy = false;
Dictionary<int, int> userMap = new Dictionary<int, int>();
Log("Heal-Script gestartet. Bitte einmal den Raum neu betreten!");
// 1. Reset beim Raumwechsel
OnIntercept(In.RoomReady, e => {
userMap.Clear();
isBusy = false;
Log("Raum geladen. Tracking aktiv.");
});
// 2. User-Liste aufbauen (Index zu ID Zuordnung)
OnIntercept(In.Users, e => {
try {
var p = e.Packet;
int count = p.ReadInt();
for (int i = 0; i < count; i++) {
// Standard Habbo User-Parsing
int id = p.ReadInt(); // Entity ID (Wichtig für den Klick)
string name = p.ReadString();
string motto = p.ReadString();
string look = p.ReadString();
int index = p.ReadInt(); // Index (Wichtig für den Chat)
// Restliche Daten überspringen (Position etc.)
p.ReadInt(); // x
p.ReadInt(); // y
p.ReadString(); // z
p.ReadInt(); // dir
int type = p.ReadInt(); // type
// In Map speichern
userMap[index] = id;
}
} catch {
// Parsing Fehler ignorieren (falls Server-Struktur abweicht)
}
});
// 3. Auf Chat reagieren
OnIntercept(In.Chat, async e => {
// Wenn wir gerade beschäftigt sind, ignorieren wir alles
if (isBusy) return;
var p = e.Packet;
int index = p.ReadInt();
string message = p.ReadString();
// Trigger-Wort prüfen
if (message.ToLower().Contains("heal")) {
// Prüfen ob wir die ID zum User kennen
if (userMap.ContainsKey(index)) {
int targetId = userMap[index];
// Ablauf starten (in eigenem Task, damit der Chat nicht laggt)
_ = RunHealRoutine(targetId);
} else {
Log($"[Bot] User Index {index} unbekannt. Bitte Raum neu laden!");
}
}
});
// 4. Die Logik zum Senden der Befehle
async Task RunHealRoutine(int targetId) {
if (isBusy) return;
isBusy = true; // Sperren
Log($"[Bot] Heile User ID: {targetId}");
try {
// Schritt 1: :offer senden
Send(Out.Chat, ":offer", 0, -1);
await Task.Delay(600); // Wartezeit für das Menü (anpassen bei Lag)
// Schritt 2: 1 senden
Send(Out.Chat, "1", 0, -1);
await Task.Delay(600); // Wartezeit für die Zielauswahl
// Schritt 3: User anklicken
// Header "GetSelectedBadges" ist der Standard-Klick auf einen User
Send(Out.GetSelectedBadges, targetId);
} catch (Exception ex) {
Log($"Fehler: {ex.Message}");
} finally {
// Sperre aufheben
await Task.Delay(200);
isBusy = false;
}
}

373
Heart.csx Normal file
View File

@ -0,0 +1,373 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
public struct Step
{
public int X;
public int Y;
public int DelayMs;
}
const int START_X = 13;
const int START_Y = 13;
string rawDump = @"
1. (13,14) after 971ms
2. (13,15) after 499ms
3. (13,14) after 61ms
4. (13,16) after 438ms
5. (13,15) after 40ms
6. (13,17) after 499ms
7. (13,16) after 47ms
8. (13,18) after 452ms
9. (13,17) after 89ms
10. (13,19) after 410ms
11. (13,18) after 40ms
12. (13,20) after 467ms
13. (13,19) after 75ms
14. (13,21) after 424ms
15. (13,20) after 40ms
16. (12,21) after 489ms
17. (13,21) after 61ms
18. (12,20) after 438ms
19. (12,21) after 104ms
20. (12,19) after 395ms
21. (12,20) after 47ms
22. (12,18) after 452ms
23. (12,19) after 91ms
24. (12,17) after 407ms
25. (12,18) after 40ms
26. (12,16) after 474ms
27. (12,17) after 71ms
28. (12,15) after 424ms
29. (12,16) after 40ms
30. (12,14) after 486ms
31. (12,15) after 60ms
32. (11,14) after 438ms
33. (12,14) after 108ms
34. (11,15) after 391ms
35. (11,14) after 46ms
36. (11,15) after 544ms
37. (11,16) after 408ms
38. (11,17) after 499ms
39. (11,16) after 80ms
40. (11,18) after 419ms
41. (11,17) after 40ms
42. (11,19) after 484ms
43. (11,18) after 58ms
44. (11,20) after 442ms
45. (11,19) after 110ms
46. (11,21) after 389ms
47. (11,20) after 47ms
48. (10,21) after 452ms
49. (11,21) after 91ms
50. (10,20) after 409ms
51. (10,21) after 40ms
52. (10,19) after 473ms
53. (10,20) after 79ms
54. (10,18) after 420ms
55. (10,19) after 40ms
56. (10,17) after 485ms
57. (10,18) after 62ms
58. (10,16) after 437ms
59. (10,17) after 109ms
60. (10,15) after 390ms
61. (10,16) after 44ms
62. (10,14) after 455ms
63. (10,15) after 93ms
64. (9,14) after 406ms
65. (10,14) after 40ms
66. (9,15) after 476ms
67. (9,14) after 68ms
68. (9,16) after 424ms
69. (9,15) after 40ms
70. (9,17) after 485ms
71. (9,16) after 62ms
72. (9,18) after 437ms
73. (9,17) after 94ms
74. (9,19) after 405ms
75. (9,18) after 40ms
76. (9,20) after 481ms
77. (9,19) after 62ms
78. (9,21) after 437ms
79. (9,20) after 40ms
80. (8,20) after 500ms
81. (9,21) after 44ms
82. (8,20) after 546ms
83. (8,21) after 1407ms
84. (8,20) after 500ms
85. (8,21) after 62ms
86. (8,19) after 437ms
87. (8,20) after 104ms
88. (8,18) after 394ms
89. (8,19) after 42ms
90. (8,17) after 457ms
91. (8,18) after 94ms
92. (8,16) after 406ms
93. (8,17) after 40ms
94. (8,15) after 469ms
95. (8,16) after 77ms
96. (8,14) after 422ms
97. (8,15) after 40ms
98. (9,14) after 492ms
99. (8,14) after 55ms
100. (9,14) after 441ms
101. (8,14) after 499ms
102. (8,15) after 1000ms
103. (8,16) after 499ms
104. (8,15) after 75ms
105. (8,17) after 424ms
106. (8,16) after 40ms
107. (8,18) after 484ms
108. (8,17) after 61ms
109. (8,19) after 438ms
110. (8,18) after 106ms
111. (8,19) after 440ms
112. (9,20) after 453ms
113. (8,21) after 500ms
114. (9,20) after 40ms
115. (8,21) after 548ms
116. (9,21) after 423ms
117. (10,21) after 499ms
118. (9,21) after 62ms
119. (10,20) after 438ms
120. (10,21) after 104ms
121. (9,19) after 394ms
122. (10,20) after 47ms
123. (9,18) after 452ms
124. (9,19) after 89ms
125. (9,17) after 410ms
126. (9,18) after 40ms
127. (9,16) after 472ms
128. (9,17) after 80ms
129. (9,15) after 419ms
130. (9,16) after 40ms
131. (10,14) after 487ms
132. (9,15) after 59ms
133. (10,14) after 546ms
134. (10,15) after 393ms
135. (10,16) after 499ms
136. (10,15) after 91ms
137. (10,17) after 408ms
138. (10,16) after 40ms
139. (10,18) after 467ms
140. (10,17) after 75ms
141. (10,19) after 423ms
142. (10,18) after 40ms
143. (11,20) after 486ms
144. (10,19) after 61ms
145. (11,21) after 437ms
146. (11,20) after 109ms
147. (11,21) after 436ms
148. (12,21) after 954ms
149. (13,21) after 499ms
150. (12,21) after 75ms
151. (12,20) after 424ms
152. (13,21) after 40ms
153. (13,20) after 485ms
154. (12,20) after 64ms
155. (13,19) after 436ms
156. (13,20) after 40ms
157. (12,19) after 497ms
158. (13,19) after 44ms
159. (11,19) after 455ms
160. (12,19) after 94ms
161. (11,18) after 405ms
162. (11,19) after 40ms
163. (12,18) after 471ms
164. (11,18) after 74ms
165. (13,18) after 425ms
166. (12,18) after 40ms
167. (13,17) after 484ms
168. (13,18) after 64ms
169. (13,17) after 543ms
170. (12,17) after 391ms
171. (11,17) after 499ms
172. (12,17) after 90ms
173. (11,17) after 443ms
174. (11,16) after 466ms
175. (11,15) after 499ms
176. (11,16) after 40ms
177. (11,14) after 485ms
178. (11,15) after 63ms
179. (12,14) after 436ms
180. (11,14) after 106ms
181. (12,14) after 438ms
182. (12,15) after 455ms
183. (12,16) after 499ms
184. (12,15) after 40ms
185. (13,16) after 487ms
186. (12,16) after 63ms
187. (13,15) after 436ms
188. (13,16) after 107ms
189. (13,14) after 393ms
190. (13,15) after 45ms
191. (13,14) after 545ms
192. (12,14) after 409ms
193. (11,14) after 500ms
194. (12,14) after 74ms
195. (12,15) after 424ms
196. (11,14) after 40ms
197. (13,16) after 488ms
198. (12,15) after 64ms
199. (13,15) after 435ms
200. (13,16) after 106ms
201. (13,14) after 394ms
202. (13,15) after 46ms
203. (13,14) after 548ms
204. (12,14) after 403ms
205. (11,14) after 499ms
206. (12,14) after 74ms
207. (11,14) after 437ms
208. (12,15) after 1487ms
209. (13,14) after 500ms
210. (12,15) after 95ms
211. (13,14) after 435ms
212. (13,15) after 468ms
213. (13,16) after 500ms
214. (13,15) after 40ms
215. (13,17) after 487ms
216. (13,16) after 60ms
217. (13,18) after 440ms
218. (13,17) after 105ms
219. (13,19) after 392ms
220. (13,18) after 48ms
221. (13,20) after 451ms
222. (13,19) after 92ms
223. (13,21) after 407ms
224. (13,20) after 40ms
225. (12,21) after 471ms
226. (13,21) after 80ms
227. (11,21) after 420ms
228. (12,21) after 40ms
229. (12,20) after 487ms
230. (11,21) after 61ms
231. (13,19) after 439ms
232. (12,20) after 108ms
233. (13,20) after 390ms
234. (13,19) after 47ms
235. (13,21) after 452ms
236. (13,20) after 95ms
237. (13,21) after 438ms
238. (12,21) after 2966ms
239. (12,20) after 999ms
240. (11,21) after 500ms
241. (12,20) after 40ms
242. (11,21) after 553ms
243. (10,21) after 934ms
244. (9,21) after 500ms
245. (10,21) after 92ms
246. (8,21) after 407ms
247. (9,21) after 40ms
248. (8,21) after 545ms
249. (9,20) after 1924ms
250. (8,21) after 999ms
251. (8,20) after 999ms
252. (8,19) after 500ms
253. (8,20) after 64ms
254. (8,18) after 435ms
255. (8,19) after 109ms
256. (8,18) after 435ms
257. (8,17) after 454ms
258. (8,18) after 999ms
259. (8,17) after 1000ms
260. (8,16) after 1999ms
261. (8,15) after 500ms
262. (8,16) after 78ms
263. (8,14) after 421ms
264. (8,15) after 40ms
265. (9,15) after 486ms
266. (8,14) after 63ms
267. (9,15) after 546ms
268. (8,14) after 891ms
269. (9,14) after 997ms
270. (10,14) after 500ms
271. (9,14) after 40ms
272. (10,15) after 484ms
273. (10,14) after 58ms
274. (11,15) after 440ms
275. (10,15) after 111ms
276. (12,16) after 387ms
277. (11,15) after 49ms
278. (12,16) after 546ms
279. (11,16) after 404ms
280. (10,16) after 500ms
281. (11,16) after 75ms
282. (9,16) after 424ms
283. (10,16) after 40ms
284. (9,17) after 483ms
285. (9,16) after 60ms
286. (10,17) after 439ms
287. (9,17) after 40ms
288. (11,17) after 498ms
289. (10,17) after 45ms
290. (12,17) after 453ms
291. (11,17) after 96ms
292. (12,18) after 403ms
293. (12,17) after 40ms
294. (11,18) after 467ms
295. (12,18) after 82ms
296. (10,18) after 417ms
297. (11,18) after 40ms
298. (9,18) after 482ms
299. (10,18) after 65ms
300. (9,19) after 435ms
301. (9,18) after 107ms
302. (10,19) after 392ms
303. (9,19) after 47ms
304. (11,19) after 452ms
305. (10,19) after 89ms
306. (12,19) after 409ms
307. (11,19) after 40ms
308. (11,20) after 471ms
309. (12,19) after 81ms
310. (10,20) after 418ms
311. (11,20) after 40ms
312. (10,20) after 545ms
";
List<Step> ParseSteps(string text)
{
var steps = new List<Step>();
var rx = new Regex(@"\((\d+),(\d+)\)\s+after\s+(\d+)ms", RegexOptions.Compiled);
foreach (Match m in rx.Matches(text))
{
steps.Add(new Step {
X = int.Parse(m.Groups[1].Value),
Y = int.Parse(m.Groups[2].Value),
DelayMs = int.Parse(m.Groups[3].Value)
});
}
return steps;
}
Log("=== Play Path From Dump ===");
var steps = ParseSteps(rawDump);
if (steps.Count == 0)
{
Log("ERROR: No steps parsed.");
return;
}
Log($"Parsed {steps.Count} steps.");
Log($"Waiting until you are on {START_X}:{START_Y}...");
while (Run)
{
if (Self != null && Self.Location != null && Self.Location.X == START_X && Self.Location.Y == START_Y)
break;
Delay(100);
}
if (!Run) return;
Log("Start reached. Replaying exact timed path...");
for (int i = 0; i < steps.Count; i++)
{
var s = steps[i];
if (s.DelayMs > 0) Delay(s.DelayMs);
Move(s.X, s.Y);
}
Log("Replay complete.");

512
One-Way Door Auto Enter.csx Normal file
View File

@ -0,0 +1,512 @@
using System;
using System.Collections.Generic;
using System.Linq;
// Color Puzzle Solver v2
// - Auto calibration of arrow -> move mapping
// - Waits for real state change after every click
const int TILE_KIND = 3696;
const int ARROW_KIND = 17851;
const int GRID_X_MIN = 36;
const int GRID_X_MAX = 39;
const int GRID_Y_MIN = 27;
const int GRID_Y_MAX = 30;
const int CLICK_SETTLE_DELAY_MS = 250;
const int WAIT_CHANGE_TIMEOUT_MS = 6000;
const int WAIT_CHANGE_POLL_MS = 120;
const int MAX_STEPS = 140;
const int BFS_MAX_NODES = 4_000_000;
const int IDA_MAX_SEC = 12;
const bool AUTO_QUEUE_START = true;
const long TRANSPORTER_ID = 759030883;
const int QUEUE_CLICK_INTERVAL_MS = 5000;
const int WAIT_PUZZLE_POLL_MS = 250;
const int WAIT_PUZZLE_LOG_MS = 5000;
const bool REQUIRE_SELF_IN_PLAYZONE = true;
const int PLAY_X_MIN = 34;
const int PLAY_X_MAX = 41;
const int PLAY_Y_MIN = 26;
const int PLAY_Y_MAX = 31;
const bool REQUIRE_SELF_MIN_Z = true;
const double SELF_MIN_Z = 17.0;
const bool REQUIRE_PLAYER_QUEUE_CLEAR = true;
const string WATCH_PLAYER_NAME = "gracie";
const int WATCH_QUEUE_X = 33;
const int WATCH_QUEUE_Y = 19;
const double WATCH_QUEUE_Z = 4.5;
const double WATCH_QUEUE_Z_TOL = 1.0;
int GetState(dynamic item)
{
try { return int.Parse(item.State?.ToString() ?? "0"); }
catch { return 0; }
}
int GetKind(dynamic item)
{
try { return (int)item.Kind; }
catch { return -1; }
}
uint EncodeGrid(int[,] g)
{
uint s = 0;
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
s |= ((uint)(g[r, c] & 3)) << (2 * (r * 4 + c));
return s;
}
bool TryReadGrid(out uint state, out string dump)
{
int[,] grid = new int[4, 4];
bool[,] found = new bool[4, 4];
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != TILE_KIND) continue;
int x = item.Location.X;
int y = item.Location.Y;
double z = item.Location.Z;
if (x < GRID_X_MIN || x > GRID_X_MAX) continue;
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
if (z < 18.4) continue;
int row = y - GRID_Y_MIN;
int col = x - GRID_X_MIN;
grid[row, col] = GetState(item);
found[row, col] = true;
}
int cnt = 0;
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
if (found[r, c]) cnt++;
if (cnt < 16)
{
state = 0;
dump = "";
return false;
}
state = EncodeGrid(grid);
dump = string.Join(" | ", Enumerable.Range(0, 4).Select(r =>
$"R{r}[{grid[r,0]},{grid[r,1]},{grid[r,2]},{grid[r,3]}]"));
return true;
}
uint RowLeft(uint s, int r)
{
int sh = r * 8;
uint row = (s >> sh) & 0xFFu;
uint rot = ((row >> 2) | (row << 6)) & 0xFFu;
return (s & ~(0xFFu << sh)) | (rot << sh);
}
uint RowRight(uint s, int r)
{
int sh = r * 8;
uint row = (s >> sh) & 0xFFu;
uint rot = ((row << 2) | (row >> 6)) & 0xFFu;
return (s & ~(0xFFu << sh)) | (rot << sh);
}
uint ColUp(uint s, int c)
{
int b = c * 2;
uint v0 = (s >> b) & 3u;
uint v1 = (s >> (b + 8)) & 3u;
uint v2 = (s >> (b + 16)) & 3u;
uint v3 = (s >> (b + 24)) & 3u;
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
return (s & mask) | (v1 << b) | (v2 << (b + 8)) | (v3 << (b + 16)) | (v0 << (b + 24));
}
uint ColDown(uint s, int c)
{
int b = c * 2;
uint v0 = (s >> b) & 3u;
uint v1 = (s >> (b + 8)) & 3u;
uint v2 = (s >> (b + 16)) & 3u;
uint v3 = (s >> (b + 24)) & 3u;
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
return (s & mask) | (v3 << b) | (v0 << (b + 8)) | (v1 << (b + 16)) | (v2 << (b + 24));
}
uint ApplyMove(uint s, int m)
{
if (m < 4) return RowLeft(s, m);
if (m < 8) return RowRight(s, m - 4);
if (m < 12) return ColUp(s, m - 8);
return ColDown(s, m - 12);
}
int InverseMove(int m)
{
if (m < 4) return m + 4;
if (m < 8) return m - 4;
if (m < 12) return m + 4;
return m - 4;
}
string MoveName(int m)
{
if (m < 4) return $"Row{m} LEFT";
if (m < 8) return $"Row{m - 4} RIGHT";
if (m < 12) return $"Col{m - 8} UP";
return $"Col{m - 12} DOWN";
}
int DetectMove(uint before, uint after)
{
int hit = -1;
for (int m = 0; m < 16; m++)
{
if (ApplyMove(before, m) != after) continue;
if (hit != -1) return -2;
hit = m;
}
return hit;
}
List<int> SolveBfs(uint start, uint goal)
{
if (start == goal) return new List<int>();
var visited = new Dictionary<uint, (uint parent, int move)>();
var queue = new Queue<uint>();
visited[start] = (start, -1);
queue.Enqueue(start);
int nodes = 0;
bool found = false;
while (queue.Count > 0 && nodes < BFS_MAX_NODES)
{
uint cur = queue.Dequeue();
nodes++;
for (int m = 0; m < 16; m++)
{
uint nxt = ApplyMove(cur, m);
if (visited.ContainsKey(nxt)) continue;
visited[nxt] = (cur, m);
if (nxt == goal)
{
found = true;
queue.Clear();
break;
}
queue.Enqueue(nxt);
}
}
if (!found) return null;
var sol = new List<int>();
uint s = goal;
while (s != start)
{
var p = visited[s];
sol.Add(p.move);
s = p.parent;
}
sol.Reverse();
return sol;
}
List<int> SolveIda(uint start, uint goal)
{
if (start == goal) return new List<int>();
var t0 = DateTime.Now;
int H(uint st)
{
int mis = 0;
for (int i = 0; i < 16; i++)
{
int a = (int)((st >> (i * 2)) & 3u);
int b = (int)((goal >> (i * 2)) & 3u);
if (a != b) mis++;
}
return (mis + 3) / 4;
}
List<int> best = null;
bool timeout = false;
bool Dfs(uint st, List<int> path, int maxDepth)
{
if (timeout) return false;
if ((DateTime.Now - t0).TotalSeconds > IDA_MAX_SEC)
{
timeout = true;
return false;
}
if (st == goal)
{
best = new List<int>(path);
return true;
}
int h = H(st);
if (path.Count + h > maxDepth) return false;
int block = path.Count > 0 ? InverseMove(path[path.Count - 1]) : -1;
for (int m = 0; m < 16; m++)
{
if (m == block) continue;
path.Add(m);
if (Dfs(ApplyMove(st, m), path, maxDepth)) return true;
path.RemoveAt(path.Count - 1);
if (timeout) return false;
}
return false;
}
int d0 = H(start);
for (int d = d0; d <= 22 && !timeout; d++)
{
if (Dfs(start, new List<int>(), d)) break;
}
return best;
}
List<int> Solve(uint start, uint goal)
{
var bfs = SolveBfs(start, goal);
if (bfs != null) return bfs;
return SolveIda(start, goal);
}
bool ClickAndWaitChange(long furniId, uint before, out uint after, out string dumpAfter)
{
Send(Out["ClickFurni"], (int)furniId, 0);
Delay(CLICK_SETTLE_DELAY_MS);
int waited = 0;
while (waited < WAIT_CHANGE_TIMEOUT_MS)
{
if (TryReadGrid(out after, out dumpAfter) && after != before)
return true;
Delay(WAIT_CHANGE_POLL_MS);
waited += WAIT_CHANGE_POLL_MS;
}
after = before;
dumpAfter = "";
return false;
}
Dictionary<string, long> ReadArrowIds()
{
var arrowIds = new Dictionary<string, long>();
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != ARROW_KIND) continue;
int x = item.Location.X;
int y = item.Location.Y;
if (y == GRID_Y_MIN - 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
arrowIds[$"up_{x - GRID_X_MIN}"] = item.Id;
else if (y == GRID_Y_MAX + 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
arrowIds[$"down_{x - GRID_X_MIN}"] = item.Id;
else if (x == GRID_X_MIN - 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
arrowIds[$"left_{y - GRID_Y_MIN}"] = item.Id;
else if (x == GRID_X_MAX + 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
arrowIds[$"right_{y - GRID_Y_MIN}"] = item.Id;
}
return arrowIds;
}
bool IsSelfInPlayZone()
{
try
{
int x = Self.Location.X;
int y = Self.Location.Y;
double z = Self.Location.Z;
bool inRect = x >= PLAY_X_MIN && x <= PLAY_X_MAX && y >= PLAY_Y_MIN && y <= PLAY_Y_MAX;
bool inZ = !REQUIRE_SELF_MIN_Z || z >= SELF_MIN_Z;
return inRect && inZ;
}
catch
{
return false;
}
}
bool IsWatchedPlayerAtQueueSpot()
{
try
{
var u = Users.FirstOrDefault(x =>
x != null &&
x.Name != null &&
x.Name.Equals(WATCH_PLAYER_NAME, StringComparison.OrdinalIgnoreCase));
if (u == null || u.Location == null) return false;
int x = u.Location.X;
int y = u.Location.Y;
double z = u.Location.Z;
return x == WATCH_QUEUE_X && y == WATCH_QUEUE_Y && Math.Abs(z - WATCH_QUEUE_Z) <= WATCH_QUEUE_Z_TOL;
}
catch
{
return false;
}
}
Log("=== Color Puzzle Auto-Solver (AutoCalib + WaitChange) ===");
Dictionary<string, long> arrowIds = null;
uint current;
string dumpNow;
int sinceQueueClick = QUEUE_CLICK_INTERVAL_MS;
int sinceLog = WAIT_PUZZLE_LOG_MS;
while (true)
{
bool hasGrid = TryReadGrid(out current, out dumpNow);
var probeArrows = ReadArrowIds();
bool hasArrows = probeArrows.Count == 16;
bool inPlayZone = !REQUIRE_SELF_IN_PLAYZONE || IsSelfInPlayZone();
bool queueClear = !REQUIRE_PLAYER_QUEUE_CLEAR || !IsWatchedPlayerAtQueueSpot();
if (hasGrid && hasArrows && inPlayZone && queueClear)
{
arrowIds = probeArrows;
break;
}
if (AUTO_QUEUE_START && sinceQueueClick >= QUEUE_CLICK_INTERVAL_MS)
{
Send(Out["ClickFurni"], (int)TRANSPORTER_ID, 0);
Log($"Queue: Klick Transporter {TRANSPORTER_ID}...");
sinceQueueClick = 0;
}
if (sinceLog >= WAIT_PUZZLE_LOG_MS)
{
string selfPos = "?";
try { selfPos = $"{Self.Location.X},{Self.Location.Y},{Self.Location.Z:F2}"; } catch { }
Log($"Warte auf Spielstart... Grid={(hasGrid ? "ok" : "no")}, Pfeile={probeArrows.Count}/16, InZone={(inPlayZone ? "yes" : "no")}, QueueClear={(queueClear ? "yes" : "no")}, Self={selfPos}");
sinceLog = 0;
}
Delay(WAIT_PUZZLE_POLL_MS);
sinceQueueClick += WAIT_PUZZLE_POLL_MS;
sinceLog += WAIT_PUZZLE_POLL_MS;
}
Log("Puzzle erkannt. Starte Solver...");
Log($"Pfeile: {arrowIds.Count}/16");
int[] targetRows = new int[4];
bool targetFound = false;
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetKind(item) != TILE_KIND) continue;
if (item.Location.X != 41) continue;
int y = item.Location.Y;
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
targetRows[y - GRID_Y_MIN] = GetState(item);
targetFound = true;
}
if (!targetFound) targetRows = new[] { 1, 2, 3, 0 };
int[,] tgt = new int[4, 4];
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
tgt[r, c] = targetRows[r];
uint goal = EncodeGrid(tgt);
Log($"Ziel: R0={targetRows[0]}, R1={targetRows[1]}, R2={targetRows[2]}, R3={targetRows[3]}");
Log($"Start: {dumpNow}");
var moveToKey = new Dictionary<int, string>();
var keyToMove = new Dictionary<string, int>();
var allKeys = arrowIds.Keys.OrderBy(k => k).ToList();
for (int step = 1; step <= MAX_STEPS; step++)
{
if (current == goal)
{
Log("=== Geloest: alle 4 Reihen korrekt ===");
return;
}
var plan = Solve(current, goal);
if (plan == null || plan.Count == 0)
{
Log("ERROR: Kein Plan vom aktuellen Zustand.");
return;
}
int wanted = plan[0];
string key;
bool probing = false;
if (moveToKey.ContainsKey(wanted))
{
key = moveToKey[wanted];
}
else
{
key = allKeys.FirstOrDefault(k => !keyToMove.ContainsKey(k));
if (key == null)
{
key = allKeys[0];
}
probing = true;
}
long id = arrowIds[key];
Log($"[{step}] want {MoveName(wanted)} | click {key}" + (probing ? " (probe)" : ""));
if (!ClickAndWaitChange(id, current, out uint after, out string dumpAfter))
{
Log(" Kein Move erkannt (Timeout), gleicher Schritt nochmal.");
continue;
}
int actual = DetectMove(current, after);
if (actual >= 0)
{
moveToKey[actual] = key;
keyToMove[key] = actual;
if (actual != wanted)
Log($" AutoCalib: {key} == {MoveName(actual)} (nicht {MoveName(wanted)})");
}
else if (actual == -1)
{
Log($" Unbekannter Transition-Delta, weiter mit Re-Plan. State: {dumpAfter}");
}
else
{
Log($" Mehrdeutiger Delta, weiter mit Re-Plan. State: {dumpAfter}");
}
current = after;
if (step % 10 == 0)
Log($" Calib: {moveToKey.Count}/16 Moves gemappt");
}
Log("Nicht fertig in MAX_STEPS. Script einfach nochmal starten.");

33
Pet Level Scanner.csx Normal file
View File

@ -0,0 +1,33 @@
string suchName = "Goki";
int tempLevel = 0;
bool gotPetInfo = false;
OnIntercept(In["PetInfo"], e => {
e.Packet.ReadInt();
e.Packet.ReadString();
tempLevel = e.Packet.ReadInt();
gotPetInfo = true;
});
Log("Suche: " + suchName);
int gefunden = 0;
foreach (var entity in Entities) {
if (entity.Name == suchName) {
gefunden++;
gotPetInfo = false;
Send(Out["GetPetInfo"], entity.Id);
int waited = 0;
while (!gotPetInfo && waited < 20 && Run) {
Delay(100);
waited++;
}
Log("[" + gefunden + "] Level: " + tempLevel);
}
}
Log("Ende");

2177
Pet Trainer Auto v84.csx Normal file

File diff suppressed because it is too large Load Diff

12
Random Walker Bot.csx Normal file
View File

@ -0,0 +1,12 @@
Random rnd = new Random();
while(Run)
{
if(Self?.Location == null) { Delay(100); continue; }
int x = Self.Location.X + rnd.Next(-1, 2);
int y = Self.Location.Y + rnd.Next(-1, 2);
Move(x, y);
Delay(100);
}

122
Roller Gate Bot.csx Normal file
View File

@ -0,0 +1,122 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
// ════════════════════════════════════════════════════
// FIXED ROLLER BOT (Ohne Schnickschnack)
// ════════════════════════════════════════════════════
// IDs
int pillowId = 893412986;
int gateId = 2147418143;
List<int> rollers = new List<int> {
2147418115, 2147418119, 2147418133,
2147418134, 2147418135, 2147418136
};
// Einstellungen
int delay = 130; // Wartezeit auf dem Roller (ms)
// Speicher für Koordinaten: Key=ItemID, Value=[x, y]
Dictionary<int, int[]> coords = new Dictionary<int, int[]>();
bool active = false;
Log("Bot gestartet. BITTE RAUM NEU LADEN!");
// 1. Alles scannen beim Raum betreten
OnIntercept(In.RoomReady, e => { coords.Clear(); active = false; });
OnIntercept(In.Users, e => { coords.Clear(); active = false; });
OnIntercept(In.FloorItems, e => {
var p = e.Packet;
int n = p.ReadInt();
for(int i=0; i<n; i++) {
int id = p.ReadInt();
int type = p.ReadInt();
int x = p.ReadInt();
int y = p.ReadInt();
p.ReadInt(); // rot
p.ReadString(); // z
p.ReadString(); p.ReadInt(); p.ReadInt(); p.ReadString(); // skip rest
coords[id] = new int[] { x, y };
}
if (coords.ContainsKey(pillowId)) {
active = true;
Log("Kissen gefunden. Bot bereit.");
}
});
// 2. Bewegung erkennen
OnIntercept(In.SlideObjectBundle, e => {
if (!active) return;
var p = e.Packet;
int oldX = p.ReadInt();
int oldY = p.ReadInt();
int newX = p.ReadInt(); // Ziel X
int newY = p.ReadInt(); // Ziel Y
int count = p.ReadInt();
bool pillowMoved = false;
for(int i=0; i<count; i++) {
int id = p.ReadInt();
p.ReadString(); // z1
p.ReadString(); // z2
// Position updaten
if (coords.ContainsKey(id)) {
coords[id][0] = newX;
coords[id][1] = newY;
}
if (id == pillowId) pillowMoved = true;
}
if (pillowMoved) {
// Logik in separaten Task auslagern damit Packet nicht blockiert
_ = RunLogic(newX, newY);
}
});
// 3. Die Logik
async Task RunLogic(int px, int py) {
// 4 Richtungen prüfen: Oben, Unten, Links, Rechts
int[][] checks = new int[][] {
new int[]{0, 1}, new int[]{0, -1}, new int[]{1, 0}, new int[]{-1, 0}
};
foreach(var offset in checks) {
int cx = px + offset[0];
int cy = py + offset[1];
// Prüfen ob an cx/cy ein erlaubter Roller oder das Gate liegt
int foundId = -1;
foreach(var kvp in coords) {
if (kvp.Value[0] == cx && kvp.Value[1] == cy) {
if (rollers.Contains(kvp.Key) || kvp.Key == gateId) {
foundId = kvp.Key;
break;
}
}
}
if (foundId != -1) {
if (foundId == gateId) {
Log("Gate gefunden! Benutze es.");
Send(Out.UseFurniture, gateId, 0);
return;
} else {
// Es ist ein Roller -> Hin und Zurück
Send(Out.MoveAvatar, cx, cy);
await Task.Delay(delay);
Send(Out.MoveAvatar, px, py);
return;
}
}
}
}

172
Roller Surf Game Bot.csx Normal file
View File

@ -0,0 +1,172 @@
using System;
using System.Collections.Generic;
using System.Linq;
// ═══════════════════════════════════════════════════════════════════════════════
// ROLLER SURF BOT
// ═══════════════════════════════════════════════════════════════════════════════
const int PILLOW_ID = 893412986;
const int GATE_ID = 2147418143;
const int MARBLE_TILE_ID = 2147418142;
const int TICK_DELAY = 50;
const int MOVE_COOLDOWN = 150;
HashSet<int> greenRollers = new HashSet<int> {
2147418115, 2147418119, 2147418133,
2147418134, 2147418135, 2147418136
};
bool gameStarted = false;
DateTime lastMoveCmd = DateTime.MinValue;
int GetId(dynamic item) { try { return (int)item.Id; } catch { return 0; } }
(int x, int y) GetPillowPos()
{
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetId(item) == PILLOW_ID)
return (item.Location.X, item.Location.Y);
}
return (-1, -1);
}
(int x, int y) GetMarbleTilePos()
{
foreach (var item in FloorItems)
{
if (item == null) continue;
if (GetId(item) == MARBLE_TILE_ID)
return (item.Location.X, item.Location.Y);
}
return (-1, -1);
}
(int x, int y) GetMyPos()
{
try { return (Self.Location.X, Self.Location.Y); }
catch { return (-1, -1); }
}
bool IsAdjacent(int x1, int y1, int x2, int y2)
{
int dx = Math.Abs(x1 - x2);
int dy = Math.Abs(y1 - y2);
return (dx == 1 && dy == 0) || (dx == 0 && dy == 1);
}
int GetTargetIdAt(int x, int y)
{
foreach (var item in FloorItems)
{
if (item == null) continue;
if (item.Location.X != x || item.Location.Y != y) continue;
int id = GetId(item);
if (id == GATE_ID || greenRollers.Contains(id))
{
return id;
}
}
return -1;
}
(int x, int y, int id) FindAdjacentTarget(int px, int py)
{
int[] dX = { 1, -1, 0, 0 };
int[] dY = { 0, 0, 1, -1 };
for (int i = 0; i < 4; i++)
{
int checkX = px + dX[i];
int checkY = py + dY[i];
int targetId = GetTargetIdAt(checkX, checkY);
if (targetId != -1)
{
return (checkX, checkY, targetId);
}
}
return (-1, -1, -1);
}
Log("═══════════════════════════════════════");
Log(" ROLLER SURF BOT");
Log("═══════════════════════════════════════");
while (Run)
{
Delay(TICK_DELAY);
var pillow = GetPillowPos();
var myPos = GetMyPos();
var marbleTile = GetMarbleTilePos();
if (pillow.x == -1 || myPos.x == -1) continue;
bool onPillow = (myPos.x == pillow.x && myPos.y == pillow.y);
bool onMarbleTile = (marbleTile.x != -1 && myPos.x == marbleTile.x && myPos.y == marbleTile.y);
if (!gameStarted)
{
if (onMarbleTile)
{
Log("Start!");
Send(Out["MoveAvatar"], pillow.x, pillow.y);
gameStarted = true;
lastMoveCmd = DateTime.Now;
continue;
}
if (onPillow)
{
gameStarted = true;
Log("GO!");
continue;
}
if (IsAdjacent(myPos.x, myPos.y, pillow.x, pillow.y))
{
Send(Out["MoveAvatar"], pillow.x, pillow.y);
gameStarted = true;
lastMoveCmd = DateTime.Now;
continue;
}
continue;
}
if (!onPillow)
{
if ((DateTime.Now - lastMoveCmd).TotalMilliseconds > MOVE_COOLDOWN)
{
Send(Out["MoveAvatar"], pillow.x, pillow.y);
lastMoveCmd = DateTime.Now;
}
continue;
}
var target = FindAdjacentTarget(pillow.x, pillow.y);
if (target.id == -1) continue;
if (target.id == GATE_ID)
{
Log("GATE!");
Send(Out["UseFurniture"], GATE_ID, 0);
gameStarted = false;
Delay(300);
continue;
}
Send(Out["MoveAvatar"], target.x, target.y);
Delay(TICK_DELAY);
var newPillow = GetPillowPos();
if (newPillow.x != -1)
{
Send(Out["MoveAvatar"], newPillow.x, newPillow.y);
lastMoveCmd = DateTime.Now;
}
}

31
Room Furni Scanner.csx Normal file
View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Xabbo.Core;
Log("=== Color Puzzle Scanner ===");
Log("Scanning all floor items in the room...");
var furniByName = new Dictionary<string, List<(long Id, int X, int Y, double Z, int State, int Dir)>>();
foreach (IFloorItem item in FloorItems)
{
if (item == null) continue;
string name = item.GetName();
if (!furniByName.ContainsKey(name))
furniByName[name] = new List<(long, int, int, double, int, int)>();
furniByName[name].Add((item.Id, item.Location.X, item.Location.Y, item.Location.Z, item.State, item.Direction));
}
Log($"\nFound {furniByName.Count} unique furni types:\n");
foreach (var kvp in furniByName.OrderByDescending(x => x.Value.Count))
{
Log($"--- {kvp.Key} (x{kvp.Value.Count}) ---");
foreach (var f in kvp.Value.OrderBy(x => x.Y).ThenBy(x => x.X))
{
Log($" ID={f.Id} Pos=({f.X},{f.Y}) Z={f.Z:F2} State={f.State} Dir={f.Dir}");
}
}
Log("\n=== Scan Complete ===");

View File

@ -0,0 +1,50 @@
// Room Queue Joiner - Versucht solange bis man in der Queue landet
int roomId = 62909254;
int delayMs = 500; // Wartezeit zwischen Versuchen
bool inQueue = false;
bool success = false;
int attempts = 0;
// Wenn wir in die Queue kommen oder den Raum betreten
OnIntercept(In["RoomQueueStatus"], e => {
Log(">>> IN DER QUEUE!");
inQueue = true;
success = true;
});
// Wenn wir den Raum betreten haben
OnIntercept(In["RoomReady"], e => {
Log(">>> IM RAUM!");
success = true;
});
// Error abfangen (Raum voll, Queue voll, etc.)
OnIntercept(In["CantConnect"], e => {
// Raum/Queue voll - weiter versuchen
});
Log("=== ROOM QUEUE JOINER ===");
Log("Room ID: " + roomId);
Log("Versuche in den Raum zu kommen...");
while (Run && !success)
{
attempts++;
Log("Versuch #" + attempts + "...");
// GetGuestRoom senden
Send(Out["GetGuestRoom"], roomId, 0, 1);
Delay(delayMs);
if (success)
break;
}
if (inQueue)
Log("ERFOLG! Du bist jetzt in der Room Queue.");
else if (success)
Log("ERFOLG! Du bist im Raum.");
else
Log("Script gestoppt.");

399
SOLVER.csx Normal file
View File

@ -0,0 +1,399 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
const int MAIN_ROOM = 80262160;
const int SOURCE_MIN_X = 18;
const int SOURCE_MAX_X = 29;
const int SOURCE_MIN_Y = 25;
const int SOURCE_MAX_Y = 36;
const int FIELD_MIN_X = 18;
const int FIELD_MAX_X = 29;
const int FIELD_MIN_Y = 9;
const int FIELD_MAX_Y = 20;
var gameRooms = new[] { 80262165, 80262159, 80262161, 80262162, 80262164, 80262166, 80262167, 80262163 };
var completed = new HashSet<int>();
int GetColorNumber(string name)
{
var match = Regex.Match(name, @"\d+$");
return match.Success ? int.Parse(match.Value) : -1;
}
bool InArea(int x, int y, int minX, int maxX, int minY, int maxY) => x >= minX && x <= maxX && y >= minY && y <= maxY;
bool InField(int x, int y) => InArea(x, y, FIELD_MIN_X, FIELD_MAX_X, FIELD_MIN_Y, FIELD_MAX_Y);
bool InWaitingLine(int x, int y) => x == 14 && y >= 16 && y <= 33;
bool AtChair(int x, int y) => x == 14 && y == 15;
int GetRoomId()
{
try { return (int)Room.Id; } catch { return 0; }
}
(int x, int y) GetSelfPos()
{
try { return (Self.Location.X, Self.Location.Y); } catch { return (-1, -1); }
}
void WalkToAndWait(int tx, int ty)
{
for (int retry = 0; retry < 10; retry++)
{
Send(Out["MoveAvatar"], tx, ty);
for (int i = 0; i < 30; i++)
{
Delay(100);
var pos = GetSelfPos();
if (pos.x == tx && pos.y == ty)
{
Delay(200);
return;
}
}
}
}
void WalkToAreaAndWait(int minX, int maxX, int minY, int maxY)
{
for (int retry = 0; retry < 10; retry++)
{
var pos = GetSelfPos();
if (pos.x >= 0 && InArea(pos.x, pos.y, minX, maxX, minY, maxY))
{
Delay(200);
return;
}
Send(Out["MoveAvatar"], minX, minY);
for (int i = 0; i < 30; i++)
{
Delay(100);
pos = GetSelfPos();
if (pos.x >= 0 && InArea(pos.x, pos.y, minX, maxX, minY, maxY))
{
Delay(200);
return;
}
}
}
}
void ClickUntilTeleport(int id, int targetX, int targetY)
{
for (int retry = 0; retry < 20; retry++)
{
Send(Out["ClickFurni"], id, 0);
for (int i = 0; i < 20; i++)
{
Delay(100);
var pos = GetSelfPos();
if (pos.x == targetX && pos.y == targetY)
{
Delay(200);
return;
}
}
}
}
void UseUntilRoom(int id, int roomId)
{
for (int retry = 0; retry < 20; retry++)
{
Send(Out["UseFurniture"], id, 0);
for (int i = 0; i < 30; i++)
{
Delay(100);
if (GetRoomId() == roomId)
{
Delay(2000);
return;
}
}
}
}
void WaitForMainRoom()
{
for (int i = 0; i < 200; i++)
{
Delay(100);
if (GetRoomId() == MAIN_ROOM)
{
Delay(2000);
return;
}
}
}
void WaitUntilInField()
{
Log("Waiting for field...");
while (true)
{
var pos = GetSelfPos();
if (pos.x < 0) { Delay(100); continue; }
if (InField(pos.x, pos.y))
{
Log($"In field at ({pos.x},{pos.y})");
Delay(300);
return;
}
if (AtChair(pos.x, pos.y))
{
Delay(100);
continue;
}
if (InWaitingLine(pos.x, pos.y))
{
Send(Out["MoveAvatar"], 14, 15);
Delay(200);
continue;
}
Send(Out["MoveAvatar"], 14, 20);
Delay(300);
}
}
void DoPixelArt()
{
WaitUntilInField();
var selectors = new Dictionary<int, int>();
try
{
foreach (var item in FloorItems)
{
if (item == null) continue;
string name = item.GetName();
if (name.Contains("Cylinder Block") || name.Contains("Hemisphere Block"))
{
int colorNum = GetColorNumber(name);
if (colorNum > 0 && !selectors.ContainsKey(colorNum))
selectors[colorNum] = (int)item.Id;
}
}
} catch { }
var sourcePixels = new Dictionary<(int x, int y), int>();
try
{
foreach (var item in FloorItems)
{
if (item == null) continue;
int x = item.Location.X;
int y = item.Location.Y;
double z = item.Location.Z;
if (x < SOURCE_MIN_X || x > SOURCE_MAX_X || y < SOURCE_MIN_Y || y > SOURCE_MAX_Y) continue;
if (z < 0.7 || z > 1.0) continue;
int colorNum = GetColorNumber(item.GetName());
if (colorNum > 0)
sourcePixels[(x, y)] = colorNum;
}
} catch { }
int offsetY = SOURCE_MIN_Y - FIELD_MIN_Y;
int changed = 0;
for (int attempt = 1; attempt <= 3; attempt++)
{
var fieldTiles = new Dictionary<(int x, int y), (int id, int colorNum)>();
try
{
foreach (var item in FloorItems)
{
if (item == null) continue;
int x = item.Location.X;
int y = item.Location.Y;
double z = item.Location.Z;
if (x < FIELD_MIN_X || x > FIELD_MAX_X || y < FIELD_MIN_Y || y > FIELD_MAX_Y) continue;
int colorNum = GetColorNumber(item.GetName());
if (z > 0.5)
fieldTiles[(x, y)] = ((int)item.Id, colorNum);
else if (!fieldTiles.ContainsKey((x, y)))
fieldTiles[(x, y)] = ((int)item.Id, -1);
}
} catch { }
var toChange = new List<(int fieldId, int srcColor)>();
foreach (var src in sourcePixels)
{
int fieldX = src.Key.x;
int fieldY = src.Key.y - offsetY;
int srcColor = src.Value;
if (!fieldTiles.TryGetValue((fieldX, fieldY), out var field)) continue;
if (field.colorNum == srcColor) continue;
if (!selectors.ContainsKey(srcColor)) continue;
toChange.Add((field.id, srcColor));
}
if (toChange.Count == 0) break;
var sorted = toChange.OrderBy(x => x.srcColor).ToList();
int lastColor = -1;
foreach (var item in sorted)
{
if (item.srcColor != lastColor)
{
Send(Out["ClickFurni"], selectors[item.srcColor], 0);
Delay(50);
lastColor = item.srcColor;
}
Send(Out["ClickFurni"], item.fieldId, 0);
Delay(50);
changed++;
}
Delay(500);
}
try
{
var badge = FloorItems.FirstOrDefault(f => f != null && f.GetName() == "Badge Display Case");
if (badge != null)
Send(Out["ClickFurni"], (int)badge.Id, 0);
} catch { }
Log($"Done: {changed}");
completed.Add(GetRoomId());
}
void GoToRoom1()
{
if (completed.Contains(80262165)) return;
Log("Room 1");
WalkToAreaAndWait(5, 6, 25, 27);
ClickUntilTeleport(902905683, 11, 27);
ClickUntilTeleport(902906203, 11, 24);
UseUntilRoom(902906203, 80262165);
DoPixelArt();
WaitForMainRoom();
}
void GoToRoom2()
{
if (completed.Contains(80262159)) return;
Log("Room 2");
WalkToAreaAndWait(16, 20, 34, 35);
ClickUntilTeleport(902906461, 20, 34);
ClickUntilTeleport(902906426, 20, 32);
UseUntilRoom(902906426, 80262159);
DoPixelArt();
WaitForMainRoom();
}
void GoToRoom3()
{
if (completed.Contains(80262161)) return;
Log("Room 3");
WalkToAreaAndWait(25, 25, 17, 21);
Send(Out["ClickFurni"], 902906152, 0);
Delay(500);
ClickUntilTeleport(902907470, 25, 19);
UseUntilRoom(902907470, 80262161);
DoPixelArt();
WaitForMainRoom();
}
void GoToRoom4()
{
if (completed.Contains(80262162)) return;
Log("Room 4");
WalkToAreaAndWait(30, 31, 15, 16);
Send(Out["ClickFurni"], 902907718, 0);
Delay(500);
ClickUntilTeleport(902907623, 31, 15);
UseUntilRoom(902907623, 80262162);
DoPixelArt();
WaitForMainRoom();
}
void GoToRoom5()
{
if (completed.Contains(80262164)) return;
Log("Room 5");
WalkToAreaAndWait(30, 31, 15, 16);
Send(Out["ClickFurni"], 902907718, 0);
Delay(500);
WalkToAndWait(36, 16);
ClickUntilTeleport(902905838, 36, 16);
UseUntilRoom(902905838, 80262164);
DoPixelArt();
WaitForMainRoom();
}
void GoToRoom6()
{
if (completed.Contains(80262166)) return;
Log("Room 6");
WalkToAreaAndWait(30, 31, 15, 16);
Send(Out["ClickFurni"], 902907718, 0);
Delay(500);
WalkToAndWait(44, 14);
ClickUntilTeleport(902907457, 44, 14);
UseUntilRoom(902907457, 80262166);
DoPixelArt();
WaitForMainRoom();
}
void GoToRoom7()
{
if (completed.Contains(80262167)) return;
Log("Room 7");
WalkToAreaAndWait(30, 31, 15, 16);
Send(Out["ClickFurni"], 902907718, 0);
Delay(500);
WalkToAndWait(50, 12);
ClickUntilTeleport(902906400, 50, 12);
UseUntilRoom(902906400, 80262167);
DoPixelArt();
WaitForMainRoom();
}
void GoToRoom8()
{
if (completed.Contains(80262163)) return;
Log("Room 8");
WalkToAreaAndWait(50, 51, 21, 21);
Send(Out["ClickFurni"], 902906802, 0);
Delay(500);
ClickUntilTeleport(902906273, 50, 21);
UseUntilRoom(902906273, 80262163);
DoPixelArt();
WaitForMainRoom();
}
Log("Starting");
int currentRoom = GetRoomId();
if (gameRooms.Contains(currentRoom))
{
Log($"In game room {currentRoom}");
DoPixelArt();
WaitForMainRoom();
}
GoToRoom1();
GoToRoom2();
GoToRoom3();
GoToRoom4();
GoToRoom5();
GoToRoom6();
GoToRoom7();
GoToRoom8();
Log($"All done: {completed.Count}/8");
WalkToAreaAndWait(41, 41, 32, 33);

3
Seed trade (simple).csx Normal file
View File

@ -0,0 +1,3 @@
EnsureInventory();
var items = Inventory.GetFloorItems().Named("Plant Seed").Where(seed => (seed.Data as MapData)?["rarity"] == "7").Take(20);
Send(Out.TradeAddItems, items.Select(x => (int)x.ItemId).ToList());

9
Seed trade (split).csx Normal file
View File

@ -0,0 +1,9 @@
EnsureInventory();
var items = Inventory.GetFloorItems()
.Named("Plant Seed")
.GroupBy(seed => (seed.Data as MapData)?["rarity"] as string) // Gruppiert nach rarity
.Where(group => int.TryParse(group.Key, out int r) && r >= 0 && r <= 11) // Prüft, ob rarity zwischen 0-11 liegt
.SelectMany(group => group.Take(150)) // Nimmt 10 aus jeder Gruppe
.ToList();
Send(Out.TradeAddItems, items.Select(x => (int)x.ItemId).ToList());

9
Sit Spam.csx Normal file
View File

@ -0,0 +1,9 @@
// Sit Spam - Setzt dich dauerhaft hin
Log("=== Sit Spam gestartet ===");
while (Run)
{
Sit();
Delay(100);
}

404
Snake Auto (AutoCalib).csx Normal file
View File

@ -0,0 +1,404 @@
/// @name Snake Auto (AutoCalib)
using System;
using System.Linq;
using System.Collections.Generic;
const int GAME_MIN_X = 9;
const int GAME_MAX_X = 28;
const int GAME_MIN_Y = 11;
const int GAME_MAX_Y = 30;
const int BLOCKED_MIN_X = 17;
const int BLOCKED_MAX_X = 20;
const int BLOCKED_MIN_Y = 19;
const int BLOCKED_MAX_Y = 22;
const int KIND_SNAKE = 7100;
const int KIND_FOOD = 5068;
// Fallback controls for this room layout
const int FALLBACK_UP = 769407748;
const int FALLBACK_RIGHT = 769407103;
const int FALLBACK_DOWN = 769407216;
const int FALLBACK_LEFT = 769407068;
int CTRL_UP = FALLBACK_UP;
int CTRL_RIGHT = FALLBACK_RIGHT;
int CTRL_DOWN = FALLBACK_DOWN;
int CTRL_LEFT = FALLBACK_LEFT;
List<(int x, int y)> body = new List<(int x, int y)>(256);
HashSet<(int x, int y)> bodySet = new HashSet<(int x, int y)>();
(int x, int y) food = (-1, -1);
string dir = "DOWN";
string lastSentDir = "";
DateTime lastCmdTime = DateTime.MinValue;
bool ate = false;
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
bool Wall(int x, int y) => x < GAME_MIN_X || x > GAME_MAX_X || y < GAME_MIN_Y || y > GAME_MAX_Y;
bool Blk(int x, int y) => x >= BLOCKED_MIN_X && x <= BLOCKED_MAX_X && y >= BLOCKED_MIN_Y && y <= BLOCKED_MAX_Y;
void Step(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;
bool Blocked(int x, int y, bool ignoreTail)
{
if (Wall(x, y) || Blk(x, y)) return true;
if (body.Count == 0) return false;
int checkLen = ignoreTail && !ate ? body.Count - 1 : body.Count;
for (int i = 0; i < checkLen; i++)
if (body[i].x == x && body[i].y == y) return true;
return false;
}
void SendDir(int dIdx)
{
string d = DIRS[dIdx];
if (d == lastSentDir && (DateTime.Now - lastCmdTime).TotalMilliseconds < 80) return;
int id = dIdx == 0 ? CTRL_UP : dIdx == 1 ? CTRL_DOWN : dIdx == 2 ? CTRL_LEFT : CTRL_RIGHT;
Send(Out["ClickFurni"], id, 0);
lastCmdTime = DateTime.Now;
lastSentDir = d;
dir = d;
}
int OpenSpace(int sx, int sy, int cap)
{
if (Blocked(sx, sy, false)) return 0;
var seen = new HashSet<(int, int)>();
var q = new Queue<(int, int)>();
q.Enqueue((sx, sy));
seen.Add((sx, sy));
int c = 0;
while (q.Count > 0 && c < cap)
{
var p = q.Dequeue();
c++;
for (int d = 0; d < 4; d++)
{
Step(p.Item1, p.Item2, d, out int nx, out int ny);
if (!Blocked(nx, ny, false) && seen.Add((nx, ny)))
q.Enqueue((nx, ny));
}
}
return c;
}
int DistTo(int sx, int sy, int tx, int ty, int cap = 180)
{
if (sx == tx && sy == ty) return 0;
var seen = new HashSet<(int, int)>();
var q = new Queue<((int x, int y) pos, int dist)>();
q.Enqueue(((sx, sy), 0));
seen.Add((sx, sy));
while (q.Count > 0)
{
var cur = q.Dequeue();
if (cur.dist >= cap) return 999;
for (int d = 0; d < 4; d++)
{
Step(cur.pos.x, cur.pos.y, d, out int nx, out int ny);
if (nx == tx && ny == ty) return cur.dist + 1;
if (!Blocked(nx, ny, false) && seen.Add((nx, ny)))
q.Enqueue(((nx, ny), cur.dist + 1));
}
}
return -1;
}
int BestMove(int hx, int hy, int fx, int fy, int curDir)
{
int best = curDir;
int bestScore = int.MinValue;
int minSpace = Math.Max(body.Count / 4, 8);
for (int d = 0; d < 4; d++)
{
if (d == Opp(curDir)) continue;
Step(hx, hy, d, out int nx, out int ny);
if (Blocked(nx, ny, true)) continue;
int space = OpenSpace(nx, ny, 180);
if (space < minSpace) continue;
int score = 0;
if (nx == fx && ny == fy) score += 10000;
int distFood = DistTo(nx, ny, fx, fy, 180);
if (distFood > 0 && distFood < 999) score += (220 - distFood) * 7;
else if (distFood == -1) score -= 150;
score += Math.Min(space, 100) * 4;
if (score > bestScore)
{
bestScore = score;
best = d;
}
}
return best;
}
int Emergency(int hx, int hy, int curDir)
{
int best = curDir;
int bestSpace = -1;
for (int d = 0; d < 4; d++)
{
if (d == Opp(curDir)) continue;
Step(hx, hy, d, out int nx, out int ny);
if (Blocked(nx, ny, true)) continue;
int space = OpenSpace(nx, ny, 80);
if (space > bestSpace)
{
bestSpace = space;
best = d;
}
}
return best;
}
List<List<(long id, int x, int y)>> GetComponents(List<(long id, int kind, int x, int y)> points)
{
var comps = new List<List<(long id, int x, int y)>>();
var used = new HashSet<long>();
foreach (var p in points)
{
if (!used.Add(p.id)) continue;
var comp = new List<(long id, int x, int y)>();
var q = new Queue<(long id, int x, int y)>();
q.Enqueue((p.id, p.x, p.y));
while (q.Count > 0)
{
var cur = q.Dequeue();
comp.Add(cur);
foreach (var n in points)
{
if (used.Contains(n.id)) continue;
if (Math.Abs(cur.x - n.x) <= 1 && Math.Abs(cur.y - n.y) <= 1)
{
used.Add(n.id);
q.Enqueue((n.id, n.x, n.y));
}
}
}
comps.Add(comp);
}
return comps;
}
bool TryAutoCalibrateControls()
{
if (Self == null) return false;
int sx = Self.Location.X;
int sy = Self.Location.Y;
var nearby = FloorItems
.Where(i => i != null)
.Where(i => Math.Abs(i.Location.X - sx) <= 14 && Math.Abs(i.Location.Y - sy) <= 14)
.Where(i => i.Kind != KIND_SNAKE && i.Kind != KIND_FOOD)
.Select(i => (id: (long)i.Id, kind: (int)i.Kind, x: i.Location.X, y: i.Location.Y))
.ToList();
if (nearby.Count == 0) return false;
List<(long id, int x, int y)> best = null;
double bestScore = double.MaxValue;
foreach (var g in nearby.GroupBy(p => p.kind))
{
if (g.Count() < 4) continue;
var components = GetComponents(g.ToList());
foreach (var comp in components)
{
if (comp.Count < 4) continue;
int minX = comp.Min(p => p.x);
int maxX = comp.Max(p => p.x);
int minY = comp.Min(p => p.y);
int maxY = comp.Max(p => p.y);
int w = maxX - minX;
int h = maxY - minY;
if (w > 6 || h > 6) continue;
double cx = comp.Average(p => p.x);
double cy = comp.Average(p => p.y);
double dist = Math.Abs(cx - sx) + Math.Abs(cy - sy);
double score = dist + Math.Abs(comp.Count - 4) * 0.5;
if (score < bestScore)
{
bestScore = score;
best = comp;
}
}
}
if (best == null || best.Count < 4) return false;
double centerX = best.Average(p => p.x);
double centerY = best.Average(p => p.y);
var used = new HashSet<long>();
long Pick(IEnumerable<(long id, int x, int y)> list)
{
foreach (var p in list)
{
if (used.Add(p.id)) return p.id;
}
return -1;
}
var upList = best.OrderBy(p => p.y).ThenBy(p => Math.Abs(p.x - centerX)).ToList();
var downList = best.OrderByDescending(p => p.y).ThenBy(p => Math.Abs(p.x - centerX)).ToList();
var leftList = best.OrderBy(p => p.x).ThenBy(p => Math.Abs(p.y - centerY)).ToList();
var rightList = best.OrderByDescending(p => p.x).ThenBy(p => Math.Abs(p.y - centerY)).ToList();
long up = Pick(upList);
long down = Pick(downList);
long left = Pick(leftList);
long right = Pick(rightList);
if (up <= 0 || down <= 0 || left <= 0 || right <= 0) return false;
CTRL_UP = (int)up;
CTRL_RIGHT = (int)right;
CTRL_DOWN = (int)down;
CTRL_LEFT = (int)left;
return true;
}
Log("Snake Auto (AutoCalib) started");
bool calibrated = TryAutoCalibrateControls();
if (calibrated)
Log($"Auto-calibrated controls: U={CTRL_UP} R={CTRL_RIGHT} D={CTRL_DOWN} L={CTRL_LEFT}");
else
Log($"Auto-calibration failed, using fallback: U={CTRL_UP} R={CTRL_RIGHT} D={CTRL_DOWN} L={CTRL_LEFT}");
Log("Waiting for snake + food spawn...");
while (Run)
{
Delay(15);
var curSnake = new HashSet<(int x, int y)>();
food = (-1, -1);
foreach (var item in FloorItems)
{
if (item == null) continue;
int k;
try { k = (int)item.Kind; } catch { continue; }
if (k == KIND_SNAKE && !Wall(item.Location.X, item.Location.Y))
curSnake.Add((item.Location.X, item.Location.Y));
else if (k == KIND_FOOD)
food = (item.Location.X, item.Location.Y);
}
int curLen = curSnake.Count;
if (curLen == 0)
{
body.Clear();
bodySet.Clear();
continue;
}
if (body.Count == 0)
{
var sorted = curSnake.OrderBy(p => p.y).ThenBy(p => p.x).ToList();
body = new List<(int x, int y)>(sorted);
bodySet = new HashSet<(int x, int y)>(sorted);
continue;
}
var head = body[0];
(int x, int y) newHead = (-1, -1);
for (int d = 0; d < 4; d++)
{
Step(head.x, head.y, d, out int nx, out int ny);
if (curSnake.Contains((nx, ny)) && !bodySet.Contains((nx, ny)))
{
newHead = (nx, ny);
if ((DateTime.Now - lastCmdTime).TotalMilliseconds > 400)
dir = DIRS[d];
break;
}
}
if (newHead.x != -1)
{
int prevLen = body.Count;
body.Insert(0, newHead);
bodySet.Add(newHead);
ate = curLen > prevLen;
while (body.Count > curLen)
{
var tail = body[body.Count - 1];
bodySet.Remove(tail);
body.RemoveAt(body.Count - 1);
}
}
if (body.Count == 0 || food.x == -1) continue;
head = body[0];
int curDir = DirToIdx(dir);
Step(head.x, head.y, curDir, out int fx, out int fy);
if (Blocked(fx, fy, true))
{
SendDir(Emergency(head.x, head.y, curDir));
continue;
}
for (int d = 0; d < 4; d++)
{
if (d == Opp(curDir)) continue;
Step(head.x, head.y, d, out int nx, out int ny);
if (nx == food.x && ny == food.y && !Blocked(nx, ny, true))
{
SendDir(d);
goto nextLoop;
}
}
SendDir(BestMove(head.x, head.y, food.x, food.y, curDir));
nextLoop:;
}

View File

@ -0,0 +1,116 @@
/// @name Snake Auto (Campaign) v1
using System;
using System.Linq;
// Fixed from your room
const long HEAD_ID = 769407982;
const int TARGET_KIND = 3666; // Color Tile kind
// Pad button IDs (as confirmed by you)
const int PAD_TOP_LEFT = 769407085;
const int PAD_TOP_RIGHT = 769406820;
const int PAD_BOTTOM_LEFT = 769407385;
const int PAD_BOTTOM_RIGHT = 769407116;
// Direction mapping (tuned for this room)
const int CTRL_UP = PAD_TOP_LEFT;
const int CTRL_RIGHT = PAD_TOP_RIGHT;
const int CTRL_DOWN = PAD_BOTTOM_LEFT;
const int CTRL_LEFT = PAD_BOTTOM_RIGHT;
DateTime lastCmd = DateTime.MinValue;
DateTime lastInfo = DateTime.MinValue;
int lastDir = -1; // 0=UP 1=DOWN 2=LEFT 3=RIGHT
bool TryGetButton(int dir, out int id, out int x, out int y)
{
id = dir == 0 ? CTRL_UP : dir == 1 ? CTRL_DOWN : dir == 2 ? CTRL_LEFT : CTRL_RIGHT;
var btn = GetFloorItem(id);
if (btn == null)
{
x = 0;
y = 0;
return false;
}
x = btn.Location.X;
y = btn.Location.Y;
return true;
}
void SendDir(int dir)
{
if ((DateTime.Now - lastCmd).TotalMilliseconds < 140 && dir == lastDir)
return;
if (TryGetButton(dir, out int id, out int x, out int y))
{
// This game mode reacts when user steps on the plate.
Move(x, y);
}
else
{
int fallbackId = dir == 0 ? CTRL_UP : dir == 1 ? CTRL_DOWN : dir == 2 ? CTRL_LEFT : CTRL_RIGHT;
Send(Out["ClickFurni"], fallbackId, 0);
}
lastCmd = DateTime.Now;
lastDir = dir;
}
int ToDir(int dx, int dy)
{
if (Math.Abs(dx) >= Math.Abs(dy))
return dx < 0 ? 2 : 3;
return dy < 0 ? 0 : 1;
}
Log("Snake Auto (Campaign) v3 started");
Log($"HeadId={HEAD_ID}, TargetKind={TARGET_KIND}");
Log($"Buttons: U={CTRL_UP} R={CTRL_RIGHT} D={CTRL_DOWN} L={CTRL_LEFT}");
while (Run)
{
Delay(40);
var head = GetFloorItem(HEAD_ID);
if (head == null)
{
if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500)
{
Log("waiting: head not visible");
lastInfo = DateTime.Now;
}
continue;
}
var targets = FloorItems
.Where(i => i != null && i.Kind == TARGET_KIND)
.Where(i => Math.Abs(i.Location.X - head.Location.X) + Math.Abs(i.Location.Y - head.Location.Y) <= 45)
.ToList();
if (targets.Count == 0)
{
if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500)
{
int allTargets = FloorItems.Count(i => i != null && i.Kind == TARGET_KIND);
Log($"waiting: no nearby target tiles (all kind {TARGET_KIND}: {allTargets})");
lastInfo = DateTime.Now;
}
continue;
}
var target = targets
.OrderBy(t => Math.Abs(t.Location.X - head.Location.X) + Math.Abs(t.Location.Y - head.Location.Y))
.ThenBy(t => t.State)
.First();
int dx = target.Location.X - head.Location.X;
int dy = target.Location.Y - head.Location.Y;
if (dx == 0 && dy == 0)
continue;
int dir = ToDir(dx, dy);
SendDir(dir);
}

View File

@ -0,0 +1,286 @@
/// @name Snake Auto (Campaign) v10
using System;
using System.Linq;
using System.Collections.Generic;
const long HEAD_ID = 769407982;
const int SNAKE_KIND = 5478;
static readonly int[] TARGET_KINDS = { 6559, 3666 };
const int GAME_MIN_X = 20;
const int GAME_MAX_X = 65;
const int GAME_MIN_Y = 20;
const int GAME_MAX_Y = 65;
// Direction -> plate ids (verified from logs)
const int CTRL_UP = 769406820;
const int CTRL_DOWN = 769407116;
const int CTRL_LEFT = 769407385;
const int CTRL_RIGHT = 769407085;
// Known static obstacle (blue block cluster)
const int BLOCK_MIN_X = 20;
const int BLOCK_MAX_X = 22;
const int BLOCK_MIN_Y = 20;
const int BLOCK_MAX_Y = 21;
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
DateTime lastCmd = DateTime.MinValue;
DateTime lastDbg = DateTime.MinValue;
int curDir = -1;
string lastSentDir = "";
(int x, int y) prevHead = (-1, -1);
int lastAttemptDir = -1;
long lastTargetId = 0;
DateTime lastAttemptAt = DateTime.MinValue;
Dictionary<(int x, int y), DateTime> tempBlocked = new Dictionary<(int, int), DateTime>();
Dictionary<long, DateTime> targetBlacklist = new Dictionary<long, DateTime>();
HashSet<(int x, int y)> fillVisited = new HashSet<(int, int)>();
Queue<(int x, int y)> fillQueue = new Queue<(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 IsStaticBlock(int x, int y)
=> x >= BLOCK_MIN_X && x <= BLOCK_MAX_X && y >= BLOCK_MIN_Y && y <= BLOCK_MAX_Y;
void Dbg(string m, bool force = false)
{
if (!force && (DateTime.Now - lastDbg).TotalMilliseconds < 220) return;
Log($"dbg: {m}");
lastDbg = DateTime.Now;
}
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 DirFromDelta(int dx, int dy)
{
if (Math.Abs(dx) >= Math.Abs(dy)) return dx < 0 ? 2 : 3;
return dy < 0 ? 0 : 1;
}
int BtnForDir(int d)
=> d == 0 ? CTRL_UP : d == 1 ? CTRL_DOWN : d == 2 ? CTRL_LEFT : CTRL_RIGHT;
bool TryGetBtnPos(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 CleanupMaps()
{
var now = DateTime.Now;
foreach (var k in tempBlocked.Where(kv => kv.Value <= now).Select(kv => kv.Key).ToList())
tempBlocked.Remove(k);
foreach (var k in targetBlacklist.Where(kv => kv.Value <= now).Select(kv => kv.Key).ToList())
targetBlacklist.Remove(k);
}
bool IsTempBlocked(int x, int y)
=> tempBlocked.TryGetValue((x, y), out var until) && until > DateTime.Now;
bool Bad(int x, int y, HashSet<(int x, int y)> body)
{
if (!InBoard(x, y)) return true;
if (IsStaticBlock(x, y)) return true;
if (IsTempBlocked(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 Emergency((int x, int y) head, HashSet<(int x, int y)> body)
{
int bestDir = curDir >= 0 ? curDir : 0;
int bestSpace = -1;
for (int d = 0; d < 4; d++)
{
if (curDir >= 0 && 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, 260);
if (space > bestSpace)
{
bestSpace = space;
bestDir = d;
}
}
return bestDir;
}
int PickDir((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body)
{
int desired = DirFromDelta(target.x - head.x, target.y - head.y);
var dirs = new List<int> { desired, 0, 1, 2, 3 };
int bestDir = curDir >= 0 ? curDir : desired;
int bestScore = int.MinValue;
foreach (int d in dirs.Distinct())
{
if (curDir >= 0 && d == Opp(curDir)) continue;
Next(head.x, head.y, d, out int nx, out int ny);
if (Bad(nx, ny, body)) continue;
int dist = Math.Abs(target.x - nx) + Math.Abs(target.y - ny);
int space = Fill(nx, ny, body, 260);
int score = -dist * 6 + space * 2;
if (d == desired) score += 25;
if (curDir >= 0 && d == curDir) score += 4;
if (score > bestScore)
{
bestScore = score;
bestDir = d;
}
}
return bestDir;
}
void Cmd(int d, string reason, (int x, int y) head, (int x, int y)? target, long targetId)
{
string nd = DIRS[d];
if (nd == lastSentDir && (DateTime.Now - lastCmd).TotalMilliseconds < 220)
return;
int id = BtnForDir(d);
if (TryGetBtnPos(id, out int bx, out int by)) Move(bx, by);
Send(Out["ClickFurni"], id, 0);
lastCmd = DateTime.Now;
lastSentDir = nd;
lastAttemptDir = d;
lastAttemptAt = DateTime.Now;
lastTargetId = targetId;
if (target.HasValue)
Dbg($"{reason} h=({head.x},{head.y}) t=({target.Value.x},{target.Value.y}) d={nd} b={id}", true);
else
Dbg($"{reason} h=({head.x},{head.y}) d={nd} b={id}", true);
}
Log("Snake Auto (Campaign) v10 started");
Log($"target kinds: {string.Join(",", TARGET_KINDS)}");
Log($"buttons U={CTRL_UP} D={CTRL_DOWN} L={CTRL_LEFT} R={CTRL_RIGHT}");
while (Run)
{
Delay(22);
CleanupMaps();
var headItem = GetFloorItem(HEAD_ID);
if (headItem == null)
{
Dbg("head missing");
continue;
}
var head = (headItem.Location.X, headItem.Location.Y);
if (!InBoard(head.Item1, head.Item2))
continue;
// infer direction from movement
if (prevHead.x != -1)
{
int hdx = head.Item1 - prevHead.x;
int hdy = head.Item2 - prevHead.y;
if (hdx != 0 || hdy != 0)
curDir = DirFromDelta(hdx, hdy);
}
// stuck detection: command sent but head did not move
if (prevHead.x == head.Item1 && prevHead.y == head.Item2 &&
lastAttemptDir >= 0 && (DateTime.Now - lastAttemptAt).TotalMilliseconds > 420)
{
Next(head.Item1, head.Item2, lastAttemptDir, out int bx, out int by);
tempBlocked[(bx, by)] = DateTime.Now.AddMilliseconds(1800);
if (lastTargetId != 0)
targetBlacklist[lastTargetId] = DateTime.Now.AddMilliseconds(3000);
Dbg($"stuck: block ({bx},{by}) and skip target {lastTargetId}", true);
lastAttemptDir = -1;
}
prevHead = head;
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 targetsAll = FloorItems
.Where(i => i != null && TARGET_KINDS.Contains((int)i.Kind))
.Where(i => InBoard(i.Location.X, i.Location.Y))
.Select(i => (x: i.Location.X, y: i.Location.Y, id: (long)i.Id, kind: (int)i.Kind))
.ToList();
if (targetsAll.Count == 0)
{
int safe = Emergency(head, body);
Cmd(safe, "no_target_safe", head, null, 0);
continue;
}
var targets = targetsAll
.Where(t => !targetBlacklist.TryGetValue(t.id, out var until) || until <= DateTime.Now)
.ToList();
if (targets.Count == 0)
targets = targetsAll;
var target = targets
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
.First();
int dsel = PickDir(head, (target.x, target.y), body);
Cmd(dsel, $"seek k={target.kind} id={target.id}", head, (target.x, target.y), target.id);
}

View File

@ -0,0 +1,293 @@
/// @name Snake Auto (Campaign) v11
using System;
using System.Linq;
using System.Collections.Generic;
const long HEAD_ID = 769407982;
const int SNAKE_KIND = 5478;
static readonly int[] TARGET_KINDS = { 3666 };
const int GAME_MIN_X = 20;
const int GAME_MAX_X = 65;
const int GAME_MIN_Y = 20;
const int GAME_MAX_Y = 65;
// Direction -> plate ids (verified from logs)
const int CTRL_UP = 769406820;
const int CTRL_DOWN = 769407116;
const int CTRL_LEFT = 769407385;
const int CTRL_RIGHT = 769407085;
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
DateTime lastCmd = DateTime.MinValue;
DateTime lastDbg = DateTime.MinValue;
int curDir = -1;
string lastSentDir = "";
(int x, int y) prevHead = (-1, -1);
int lastCmdDir = -1;
(int x, int y) lastCmdHead = (-999, -999);
int lastAttemptDir = -1;
(int x, int y) lastAttemptHead = (-999, -999);
DateTime lastAttemptAt = DateTime.MinValue;
long lastTargetId = 0;
Dictionary<(int x, int y), DateTime> tempBlocked = new Dictionary<(int, int), DateTime>();
Dictionary<long, DateTime> targetBlacklist = new Dictionary<long, DateTime>();
HashSet<(int x, int y)> fillVisited = new HashSet<(int, int)>();
Queue<(int x, int y)> fillQueue = new Queue<(int, int)>();
bool InBoard(int x, int y)
=> x >= GAME_MIN_X && x <= GAME_MAX_X && y >= GAME_MIN_Y && y <= GAME_MAX_Y;
void Dbg(string m, bool force = false)
{
if (!force && (DateTime.Now - lastDbg).TotalMilliseconds < 220) return;
Log($"dbg: {m}");
lastDbg = DateTime.Now;
}
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 DirFromDelta(int dx, int dy)
{
if (Math.Abs(dx) >= Math.Abs(dy)) return dx < 0 ? 2 : 3;
return dy < 0 ? 0 : 1;
}
int BtnForDir(int d)
=> d == 0 ? CTRL_UP : d == 1 ? CTRL_DOWN : d == 2 ? CTRL_LEFT : CTRL_RIGHT;
bool TryGetBtnPos(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 CleanupMaps()
{
var now = DateTime.Now;
foreach (var k in tempBlocked.Where(kv => kv.Value <= now).Select(kv => kv.Key).ToList())
tempBlocked.Remove(k);
foreach (var k in targetBlacklist.Where(kv => kv.Value <= now).Select(kv => kv.Key).ToList())
targetBlacklist.Remove(k);
}
bool IsTempBlocked(int x, int y)
=> tempBlocked.TryGetValue((x, y), out var until) && until > DateTime.Now;
bool Bad(int x, int y, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
{
if (!InBoard(x, y)) return true;
if (blocks.Contains((x, y))) return true;
if (IsTempBlocked(x, y)) return true;
return body.Contains((x, y));
}
int Fill(int sx, int sy, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks, int cap)
{
if (Bad(sx, sy, body, blocks)) 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, blocks) && fillVisited.Add((nx, ny)))
fillQueue.Enqueue((nx, ny));
}
}
return c;
}
int Emergency((int x, int y) head, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
{
int bestDir = curDir >= 0 ? curDir : 0;
int bestSpace = -1;
for (int d = 0; d < 4; d++)
{
if (curDir >= 0 && d == Opp(curDir)) continue;
Next(head.x, head.y, d, out int nx, out int ny);
if (Bad(nx, ny, body, blocks)) continue;
int space = Fill(nx, ny, body, blocks, 260);
if (space > bestSpace)
{
bestSpace = space;
bestDir = d;
}
}
return bestDir;
}
int PickDir((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
{
int desired = DirFromDelta(target.x - head.x, target.y - head.y);
var dirs = new List<int> { desired, 0, 1, 2, 3 };
int bestDir = curDir >= 0 ? curDir : desired;
int bestScore = int.MinValue;
foreach (int d in dirs.Distinct())
{
if (curDir >= 0 && d == Opp(curDir)) continue;
Next(head.x, head.y, d, out int nx, out int ny);
if (Bad(nx, ny, body, blocks)) continue;
int dist = Math.Abs(target.x - nx) + Math.Abs(target.y - ny);
int space = Fill(nx, ny, body, blocks, 260);
int score = -dist * 6 + space * 2;
if (d == desired) score += 25;
if (curDir >= 0 && d == curDir) score += 4;
if (score > bestScore)
{
bestScore = score;
bestDir = d;
}
}
return bestDir;
}
void Cmd(int d, string reason, (int x, int y) head, (int x, int y)? target, long targetId)
{
string nd = DIRS[d];
if (d == lastCmdDir && head == lastCmdHead && (DateTime.Now - lastCmd).TotalMilliseconds < 450)
return;
if (nd == lastSentDir && (DateTime.Now - lastCmd).TotalMilliseconds < 180)
return;
int id = BtnForDir(d);
if (TryGetBtnPos(id, out int bx, out int by)) Move(bx, by);
Send(Out["ClickFurni"], id, 0);
lastCmd = DateTime.Now;
lastSentDir = nd;
lastCmdDir = d;
lastCmdHead = head;
lastAttemptDir = d;
lastAttemptHead = head;
lastAttemptAt = DateTime.Now;
lastTargetId = targetId;
if (target.HasValue)
Dbg($"{reason} h=({head.x},{head.y}) t=({target.Value.x},{target.Value.y}) d={nd} b={id}", true);
else
Dbg($"{reason} h=({head.x},{head.y}) d={nd} b={id}", true);
}
Log("Snake Auto (Campaign) v11 started");
Log($"target kinds: {string.Join(",", TARGET_KINDS)}");
Log($"buttons U={CTRL_UP} D={CTRL_DOWN} L={CTRL_LEFT} R={CTRL_RIGHT}");
while (Run)
{
Delay(24);
CleanupMaps();
var headItem = GetFloorItem(HEAD_ID);
if (headItem == null)
{
Dbg("head missing");
continue;
}
var head = (headItem.Location.X, headItem.Location.Y);
if (!InBoard(head.Item1, head.Item2))
continue;
if (prevHead.x != -1)
{
int hdx = head.Item1 - prevHead.x;
int hdy = head.Item2 - prevHead.y;
if (hdx != 0 || hdy != 0)
curDir = DirFromDelta(hdx, hdy);
}
// stuck detection
if (head == lastAttemptHead && lastAttemptDir >= 0 && (DateTime.Now - lastAttemptAt).TotalMilliseconds > 520)
{
Next(head.Item1, head.Item2, lastAttemptDir, out int bx, out int by);
tempBlocked[(bx, by)] = DateTime.Now.AddMilliseconds(2200);
if (lastTargetId != 0)
targetBlacklist[lastTargetId] = DateTime.Now.AddMilliseconds(3200);
Dbg($"stuck: block ({bx},{by}) skip target {lastTargetId}", true);
lastAttemptDir = -1;
}
prevHead = head;
var blocks = FloorItems
.Where(i => i != null && i.Kind == 9109)
.Where(i => InBoard(i.Location.X, i.Location.Y))
.Select(i => (i.Location.X, i.Location.Y))
.ToHashSet();
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 targetsAll = FloorItems
.Where(i => i != null && TARGET_KINDS.Contains((int)i.Kind))
.Where(i => InBoard(i.Location.X, i.Location.Y))
.Where(i => !blocks.Contains((i.Location.X, i.Location.Y)))
.Select(i => (x: i.Location.X, y: i.Location.Y, id: (long)i.Id, kind: (int)i.Kind))
.ToList();
if (targetsAll.Count == 0)
{
int safe = Emergency(head, body, blocks);
Cmd(safe, "no_target_safe", head, null, 0);
continue;
}
var targets = targetsAll
.Where(t => !targetBlacklist.TryGetValue(t.id, out var until) || until <= DateTime.Now)
.ToList();
if (targets.Count == 0)
targets = targetsAll;
var target = targets
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
.First();
int dsel = PickDir(head, (target.x, target.y), body, blocks);
Cmd(dsel, $"seek k={target.kind} id={target.id}", head, (target.x, target.y), target.id);
}

View File

@ -0,0 +1,285 @@
/// @name Snake Auto (Campaign) v12
using System;
using System.Linq;
using System.Collections.Generic;
const long HEAD_ID = 769407982;
const int SNAKE_KIND = 5478;
static readonly int[] TARGET_KINDS = { 6559, 3666 };
const int GAME_MIN_X = 20;
const int GAME_MAX_X = 65;
const int GAME_MIN_Y = 20;
const int GAME_MAX_Y = 65;
// Direction -> plate ids
const int CTRL_UP = 769406820;
const int CTRL_DOWN = 769407116;
const int CTRL_LEFT = 769407385;
const int CTRL_RIGHT = 769407085;
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
DateTime lastCmd = DateTime.MinValue;
DateTime lastDbg = DateTime.MinValue;
string lastSentDir = "";
int curDir = -1;
(int x, int y) prevHead = (-1, -1);
int lastCmdDir = -1;
(int x, int y) lastCmdHead = (-999, -999);
int lastAttemptDir = -1;
(int x, int y) lastAttemptHead = (-999, -999);
DateTime lastAttemptAt = DateTime.MinValue;
long lastTargetId = 0;
Dictionary<(int x, int y), DateTime> tempBlocked = new Dictionary<(int, int), DateTime>();
Dictionary<long, DateTime> targetBlacklist = new Dictionary<long, DateTime>();
HashSet<(int x, int y)> fillVisited = new HashSet<(int, int)>();
Queue<(int x, int y)> fillQueue = new Queue<(int, int)>();
bool InBoard(int x, int y)
=> x >= GAME_MIN_X && x <= GAME_MAX_X && y >= GAME_MIN_Y && y <= GAME_MAX_Y;
void Dbg(string m, bool force = false)
{
if (!force && (DateTime.Now - lastDbg).TotalMilliseconds < 220) return;
Log($"dbg: {m}");
lastDbg = DateTime.Now;
}
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 DirFromDelta(int dx, int dy)
{
if (Math.Abs(dx) >= Math.Abs(dy)) return dx < 0 ? 2 : 3;
return dy < 0 ? 0 : 1;
}
int BtnForDir(int d)
=> d == 0 ? CTRL_UP : d == 1 ? CTRL_DOWN : d == 2 ? CTRL_LEFT : CTRL_RIGHT;
bool TryGetBtnPos(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 CleanupMaps()
{
var now = DateTime.Now;
foreach (var k in tempBlocked.Where(kv => kv.Value <= now).Select(kv => kv.Key).ToList())
tempBlocked.Remove(k);
foreach (var k in targetBlacklist.Where(kv => kv.Value <= now).Select(kv => kv.Key).ToList())
targetBlacklist.Remove(k);
}
bool IsTempBlocked(int x, int y)
=> tempBlocked.TryGetValue((x, y), out var until) && until > DateTime.Now;
bool Bad(int x, int y, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
{
if (!InBoard(x, y)) return true;
if (blocks.Contains((x, y))) return true;
if (IsTempBlocked(x, y)) return true;
return body.Contains((x, y));
}
int Fill(int sx, int sy, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks, int cap)
{
if (Bad(sx, sy, body, blocks)) 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, blocks) && fillVisited.Add((nx, ny)))
fillQueue.Enqueue((nx, ny));
}
}
return c;
}
int Emergency((int x, int y) head, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
{
int bestDir = curDir >= 0 ? curDir : 0;
int bestSpace = -1;
for (int d = 0; d < 4; d++)
{
if (curDir >= 0 && d == Opp(curDir)) continue;
Next(head.x, head.y, d, out int nx, out int ny);
if (Bad(nx, ny, body, blocks)) continue;
int space = Fill(nx, ny, body, blocks, 260);
if (space > bestSpace)
{
bestSpace = space;
bestDir = d;
}
}
return bestDir;
}
int PickDir((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
{
int desired = DirFromDelta(target.x - head.x, target.y - head.y);
var dirs = new List<int> { desired, 0, 1, 2, 3 };
int bestDir = curDir >= 0 ? curDir : desired;
int bestScore = int.MinValue;
foreach (int d in dirs.Distinct())
{
if (curDir >= 0 && d == Opp(curDir)) continue;
Next(head.x, head.y, d, out int nx, out int ny);
if (Bad(nx, ny, body, blocks)) continue;
int dist = Math.Abs(target.x - nx) + Math.Abs(target.y - ny);
int space = Fill(nx, ny, body, blocks, 260);
int score = -dist * 6 + space * 2;
if (d == desired) score += 25;
if (curDir >= 0 && d == curDir) score += 4;
if (score > bestScore)
{
bestScore = score;
bestDir = d;
}
}
return bestDir;
}
void Cmd(int d, string reason, (int x, int y) head, (int x, int y)? target, long targetId)
{
string nd = DIRS[d];
if (d == lastCmdDir && head == lastCmdHead && (DateTime.Now - lastCmd).TotalMilliseconds < 450)
return;
if (nd == lastSentDir && (DateTime.Now - lastCmd).TotalMilliseconds < 180)
return;
int id = BtnForDir(d);
if (TryGetBtnPos(id, out int bx, out int by)) Move(bx, by);
Send(Out["ClickFurni"], id, 0);
lastCmd = DateTime.Now;
lastSentDir = nd;
lastCmdDir = d;
lastCmdHead = head;
lastAttemptDir = d;
lastAttemptHead = head;
lastAttemptAt = DateTime.Now;
lastTargetId = targetId;
if (target.HasValue)
Dbg($"{reason} h=({head.x},{head.y}) t=({target.Value.x},{target.Value.y}) d={nd} b={id}", true);
else
Dbg($"{reason} h=({head.x},{head.y}) d={nd} b={id}", true);
}
Log("Snake Auto (Campaign) v12 started");
Log($"target kinds: {string.Join(",", TARGET_KINDS)}");
Log($"buttons U={CTRL_UP} D={CTRL_DOWN} L={CTRL_LEFT} R={CTRL_RIGHT}");
while (Run)
{
Delay(24);
CleanupMaps();
var headItem = GetFloorItem(HEAD_ID);
if (headItem == null)
{
Dbg("head missing");
continue;
}
var head = (headItem.Location.X, headItem.Location.Y);
if (!InBoard(head.Item1, head.Item2))
continue;
if (prevHead.x != -1)
{
int hdx = head.Item1 - prevHead.x;
int hdy = head.Item2 - prevHead.y;
if (hdx != 0 || hdy != 0)
curDir = DirFromDelta(hdx, hdy);
}
if (head == lastAttemptHead && lastAttemptDir >= 0 && (DateTime.Now - lastAttemptAt).TotalMilliseconds > 520)
{
Next(head.Item1, head.Item2, lastAttemptDir, out int bx, out int by);
tempBlocked[(bx, by)] = DateTime.Now.AddMilliseconds(2200);
if (lastTargetId != 0) targetBlacklist[lastTargetId] = DateTime.Now.AddMilliseconds(3200);
Dbg($"stuck: block ({bx},{by}) skip target {lastTargetId}", true);
lastAttemptDir = -1;
}
prevHead = head;
var blocks = FloorItems
.Where(i => i != null && i.Kind == 9109)
.Where(i => InBoard(i.Location.X, i.Location.Y))
.Select(i => (i.Location.X, i.Location.Y))
.ToHashSet();
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 targetsAll = FloorItems
.Where(i => i != null && TARGET_KINDS.Contains((int)i.Kind))
.Where(i => InBoard(i.Location.X, i.Location.Y))
.Where(i => !blocks.Contains((i.Location.X, i.Location.Y)))
.Select(i => (x: i.Location.X, y: i.Location.Y, id: (long)i.Id, kind: (int)i.Kind))
.ToList();
if (targetsAll.Count == 0)
{
int safe = Emergency(head, body, blocks);
Cmd(safe, "no_target_safe", head, null, 0);
continue;
}
var targets = targetsAll
.Where(t => !targetBlacklist.TryGetValue(t.id, out var until) || until <= DateTime.Now)
.ToList();
if (targets.Count == 0)
targets = targetsAll;
var target = targets
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
.First();
int dsel = PickDir(head, (target.x, target.y), body, blocks);
Cmd(dsel, $"seek k={target.kind} id={target.id}", head, (target.x, target.y), target.id);
}

View File

@ -0,0 +1,287 @@
/// @name Snake Auto (Campaign) v13
using System;
using System.Linq;
using System.Collections.Generic;
const long HEAD_ID = 769407982;
const int SNAKE_KIND = 5478;
static readonly int[] TARGET_KINDS = { 6559, 3666 };
const int GAME_MIN_X = 20;
const int GAME_MAX_X = 65;
const int GAME_MIN_Y = 20;
const int GAME_MAX_Y = 65;
const int CTRL_UP = 769406820;
const int CTRL_DOWN = 769407116;
const int CTRL_LEFT = 769407385;
const int CTRL_RIGHT = 769407085;
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
DateTime lastCmd = DateTime.MinValue;
DateTime lastDbg = DateTime.MinValue;
int curDir = -1;
string lastSentDir = "";
(int x, int y) prevHead = (-1, -1);
int lastCmdDir = -1;
(int x, int y) lastCmdHead = (-999, -999);
int lastAttemptDir = -1;
(int x, int y) lastAttemptHead = (-999, -999);
DateTime lastAttemptAt = DateTime.MinValue;
long lastTargetId = 0;
Dictionary<(int x, int y), DateTime> tempBlocked = new Dictionary<(int, int), DateTime>();
Dictionary<long, DateTime> targetBlacklist = new Dictionary<long, DateTime>();
HashSet<(int x, int y)> fillVisited = new HashSet<(int, int)>();
Queue<(int x, int y)> fillQueue = new Queue<(int, int)>();
bool InBoard(int x, int y)
=> x >= GAME_MIN_X && x <= GAME_MAX_X && y >= GAME_MIN_Y && y <= GAME_MAX_Y;
void Dbg(string m, bool force = false)
{
if (!force && (DateTime.Now - lastDbg).TotalMilliseconds < 220) return;
Log($"dbg: {m}");
lastDbg = DateTime.Now;
}
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 DirFromDelta(int dx, int dy)
{
if (Math.Abs(dx) >= Math.Abs(dy)) return dx < 0 ? 2 : 3;
return dy < 0 ? 0 : 1;
}
int BtnForDir(int d)
=> d == 0 ? CTRL_UP : d == 1 ? CTRL_DOWN : d == 2 ? CTRL_LEFT : CTRL_RIGHT;
bool TryGetBtnPos(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 CleanupMaps()
{
var now = DateTime.Now;
foreach (var k in tempBlocked.Where(kv => kv.Value <= now).Select(kv => kv.Key).ToList())
tempBlocked.Remove(k);
foreach (var k in targetBlacklist.Where(kv => kv.Value <= now).Select(kv => kv.Key).ToList())
targetBlacklist.Remove(k);
}
bool IsTempBlocked(int x, int y)
=> tempBlocked.TryGetValue((x, y), out var until) && until > DateTime.Now;
bool Bad(int x, int y, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
{
if (!InBoard(x, y)) return true;
if (blocks.Contains((x, y))) return true;
if (IsTempBlocked(x, y)) return true;
return body.Contains((x, y));
}
int Fill(int sx, int sy, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks, int cap)
{
if (Bad(sx, sy, body, blocks)) 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, blocks) && fillVisited.Add((nx, ny)))
fillQueue.Enqueue((nx, ny));
}
}
return c;
}
int Emergency((int x, int y) head, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
{
int bestDir = curDir >= 0 ? curDir : 0;
int bestSpace = -1;
for (int d = 0; d < 4; d++)
{
if (curDir >= 0 && d == Opp(curDir)) continue;
Next(head.x, head.y, d, out int nx, out int ny);
if (Bad(nx, ny, body, blocks)) continue;
int space = Fill(nx, ny, body, blocks, 260);
if (space > bestSpace)
{
bestSpace = space;
bestDir = d;
}
}
return bestDir;
}
int PickDir((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
{
int desired = DirFromDelta(target.x - head.x, target.y - head.y);
var dirs = new List<int> { desired, 0, 1, 2, 3 };
int bestDir = curDir >= 0 ? curDir : desired;
int bestScore = int.MinValue;
foreach (int d in dirs.Distinct())
{
if (curDir >= 0 && d == Opp(curDir)) continue;
Next(head.x, head.y, d, out int nx, out int ny);
if (Bad(nx, ny, body, blocks)) continue;
int dist = Math.Abs(target.x - nx) + Math.Abs(target.y - ny);
int space = Fill(nx, ny, body, blocks, 260);
int score = -dist * 6 + space * 2;
if (d == desired) score += 25;
if (curDir >= 0 && d == curDir) score += 4;
if (score > bestScore)
{
bestScore = score;
bestDir = d;
}
}
return bestDir;
}
void Cmd(int d, string reason, (int x, int y) head, (int x, int y)? target, long targetId)
{
string nd = DIRS[d];
if (d == lastCmdDir && head == lastCmdHead && (DateTime.Now - lastCmd).TotalMilliseconds < 500)
return;
if (nd == lastSentDir && (DateTime.Now - lastCmd).TotalMilliseconds < 200)
return;
int id = BtnForDir(d);
if (TryGetBtnPos(id, out int bx, out int by)) Move(bx, by);
Send(Out["ClickFurni"], id, 0);
lastCmd = DateTime.Now;
lastSentDir = nd;
lastCmdDir = d;
lastCmdHead = head;
// Do NOT reset attempt timer for repeated same head+dir.
if (lastAttemptDir != d || lastAttemptHead != head)
{
lastAttemptDir = d;
lastAttemptHead = head;
lastAttemptAt = DateTime.Now;
lastTargetId = targetId;
}
if (target.HasValue)
Dbg($"{reason} h=({head.x},{head.y}) t=({target.Value.x},{target.Value.y}) d={nd} b={id}", true);
else
Dbg($"{reason} h=({head.x},{head.y}) d={nd} b={id}", true);
}
Log("Snake Auto (Campaign) v13 started");
Log($"target kinds: {string.Join(",", TARGET_KINDS)}");
Log($"buttons U={CTRL_UP} D={CTRL_DOWN} L={CTRL_LEFT} R={CTRL_RIGHT}");
while (Run)
{
Delay(24);
CleanupMaps();
var headItem = GetFloorItem(HEAD_ID);
if (headItem == null)
{
Dbg("head missing");
continue;
}
var head = (headItem.Location.X, headItem.Location.Y);
if (!InBoard(head.Item1, head.Item2))
continue;
if (prevHead.x != -1)
{
int hdx = head.Item1 - prevHead.x;
int hdy = head.Item2 - prevHead.y;
if (hdx != 0 || hdy != 0)
curDir = DirFromDelta(hdx, hdy);
}
// Stuck: head did not move after attempted direction.
if (head == lastAttemptHead && lastAttemptDir >= 0 && (DateTime.Now - lastAttemptAt).TotalMilliseconds > 560)
{
Next(head.Item1, head.Item2, lastAttemptDir, out int bx, out int by);
tempBlocked[(bx, by)] = DateTime.Now.AddMilliseconds(2500);
if (lastTargetId != 0) targetBlacklist[lastTargetId] = DateTime.Now.AddMilliseconds(3000);
Dbg($"stuck: blocked ({bx},{by}), blacklisted target {lastTargetId}", true);
lastAttemptDir = -1;
}
prevHead = head;
var blocks = FloorItems
.Where(i => i != null && i.Kind == 9109)
.Where(i => InBoard(i.Location.X, i.Location.Y))
.Select(i => (i.Location.X, i.Location.Y))
.ToHashSet();
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 targetsAll = FloorItems
.Where(i => i != null && TARGET_KINDS.Contains((int)i.Kind))
.Where(i => InBoard(i.Location.X, i.Location.Y))
.Where(i => !blocks.Contains((i.Location.X, i.Location.Y)))
.Select(i => (x: i.Location.X, y: i.Location.Y, id: (long)i.Id, kind: (int)i.Kind))
.ToList();
if (targetsAll.Count == 0)
{
int safe = Emergency(head, body, blocks);
Cmd(safe, "no_target_safe", head, null, 0);
continue;
}
var targets = targetsAll
.Where(t => !targetBlacklist.TryGetValue(t.id, out var until) || until <= DateTime.Now)
.ToList();
if (targets.Count == 0) targets = targetsAll;
var target = targets
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
.First();
int dsel = PickDir(head, (target.x, target.y), body, blocks);
Cmd(dsel, $"seek k={target.kind} id={target.id}", head, (target.x, target.y), target.id);
}

View File

@ -0,0 +1,282 @@
/// @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);
}

View File

@ -0,0 +1,269 @@
/// @name Snake Auto (Campaign) v5
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 lastCmdTime = DateTime.MinValue;
DateTime lastInfo = DateTime.MinValue;
string lastSentDir = "";
int curDir = -1; // 0=UP 1=DOWN 2=LEFT 3=RIGHT
(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;
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 DirFromDelta(int dx, int dy)
{
if (Math.Abs(dx) >= Math.Abs(dy))
return dx < 0 ? 2 : 3;
return dy < 0 ? 0 : 1;
}
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 - lastCmdTime).TotalMilliseconds < 110)
return;
int id = IdForDir(d);
if (TryGetButtonPos(id, out int bx, out int by))
Move(bx, by);
Send(Out["ClickFurni"], id, 0);
lastCmdTime = DateTime.Now;
lastSentDir = 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 Emergency((int x, int y) head, HashSet<(int x, int y)> body)
{
int bestDir = curDir >= 0 ? curDir : 0;
int bestSpace = -1;
for (int d = 0; d < 4; d++)
{
if (curDir >= 0 && 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, 120);
if (space > bestSpace)
{
bestSpace = space;
bestDir = d;
}
}
return bestDir;
}
int Decide((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body)
{
int bestDir = curDir >= 0 ? curDir : 0;
int bestScore = int.MinValue;
for (int d = 0; d < 4; d++)
{
if (curDir >= 0 && 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, 200);
if (space < 10) continue;
int dist = Dist(nx, ny, target.x, target.y, body, 200);
int score = 0;
if (nx == target.x && ny == target.y) score += 15000;
if (dist > 0 && dist < 999) score += (240 - dist) * 8;
else if (dist == -1) score -= 300;
score += Math.Min(space, 120) * 4;
if (curDir >= 0 && d == curDir) score += 2;
if (score > bestScore)
{
bestScore = score;
bestDir = d;
}
}
return bestDir;
}
Log("Snake Auto (Campaign) v5 started");
Log($"Buttons: U={CTRL_UP} R={CTRL_RIGHT} D={CTRL_DOWN} L={CTRL_LEFT}");
while (Run)
{
Delay(18);
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;
if (prevHead.x != -1)
{
int dx = head.Item1 - prevHead.x;
int dy = head.Item2 - prevHead.y;
if (dx != 0 || dy != 0)
curDir = DirFromDelta(dx, dy);
}
prevHead = head;
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))
.ToList();
if (targets.Count == 0)
{
// No tile visible: stay safe, do not random reverse.
int safe = Emergency(head, body);
Cmd(safe);
continue;
}
var target = targets
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
.First();
if (curDir >= 0)
{
Next(head.Item1, head.Item2, curDir, out int fx, out int fy);
if (Bad(fx, fy, body))
{
int emergency = Emergency(head, body);
Cmd(emergency);
continue;
}
}
int best = Decide(head, target, body);
Cmd(best);
}

View File

@ -0,0 +1,287 @@
/// @name Snake Auto (Campaign) v6
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;
const bool DEBUG = true;
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
DateTime lastCmdTime = DateTime.MinValue;
DateTime lastInfo = DateTime.MinValue;
DateTime lastDebug = DateTime.MinValue;
string lastSentDir = "";
int curDir = -1; // 0=UP 1=DOWN 2=LEFT 3=RIGHT
(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;
void Dbg(string msg, bool force = false)
{
if (!DEBUG) return;
if (!force && (DateTime.Now - lastDebug).TotalMilliseconds < 120) return;
Log($"dbg: {msg}");
lastDebug = DateTime.Now;
}
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 DirFromDelta(int dx, int dy)
{
if (Math.Abs(dx) >= Math.Abs(dy))
return dx < 0 ? 2 : 3;
return dy < 0 ? 0 : 1;
}
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 reason, (int x, int y) head, (int x, int y)? target)
{
string nd = DIRS[d];
if (nd == lastSentDir && (DateTime.Now - lastCmdTime).TotalMilliseconds < 110)
return;
int id = IdForDir(d);
if (TryGetButtonPos(id, out int bx, out int by))
Move(bx, by);
Send(Out["ClickFurni"], id, 0);
lastCmdTime = DateTime.Now;
lastSentDir = nd;
if (target.HasValue)
Dbg($"reason={reason} head=({head.x},{head.y}) target=({target.Value.x},{target.Value.y}) dir={nd} btn={id}", true);
else
Dbg($"reason={reason} head=({head.x},{head.y}) dir={nd} btn={id}", true);
}
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 Emergency((int x, int y) head, HashSet<(int x, int y)> body)
{
int bestDir = curDir >= 0 ? curDir : 0;
int bestSpace = -1;
for (int d = 0; d < 4; d++)
{
if (curDir >= 0 && 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, 120);
if (space > bestSpace)
{
bestSpace = space;
bestDir = d;
}
}
return bestDir;
}
int Decide((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body)
{
int bestDir = curDir >= 0 ? curDir : 0;
int bestScore = int.MinValue;
for (int d = 0; d < 4; d++)
{
if (curDir >= 0 && 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, 200);
if (space < 10) continue;
int dist = Dist(nx, ny, target.x, target.y, body, 200);
int score = 0;
if (nx == target.x && ny == target.y) score += 15000;
if (dist > 0 && dist < 999) score += (240 - dist) * 8;
else if (dist == -1) score -= 300;
score += Math.Min(space, 120) * 4;
if (curDir >= 0 && d == curDir) score += 2;
if (score > bestScore)
{
bestScore = score;
bestDir = d;
}
}
return bestDir;
}
Log("Snake Auto (Campaign) v6 started");
Log($"Buttons: U={CTRL_UP} R={CTRL_RIGHT} D={CTRL_DOWN} L={CTRL_LEFT}");
while (Run)
{
Delay(18);
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;
if (prevHead.x != -1)
{
int dx = head.Item1 - prevHead.x;
int dy = head.Item2 - prevHead.y;
if (dx != 0 || dy != 0)
curDir = DirFromDelta(dx, dy);
}
prevHead = head;
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))
.ToList();
if (targets.Count == 0)
{
if ((DateTime.Now - lastInfo).TotalMilliseconds > 500)
{
Dbg($"reason=no_target_hold head=({head.Item1},{head.Item2})", true);
lastInfo = DateTime.Now;
}
continue;
}
var target = targets
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
.First();
if (curDir >= 0)
{
Next(head.Item1, head.Item2, curDir, out int fx, out int fy);
if (Bad(fx, fy, body))
{
int emergency = Emergency(head, body);
Cmd(emergency, "front_blocked", head, target);
continue;
}
}
int best = Decide(head, target, body);
Cmd(best, "seek_target", head, target);
}

View File

@ -0,0 +1,336 @@
/// @name Snake Auto (Campaign) v7
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;
// Initial guess; script auto-corrects while running.
const int BTN_TL = 769407085;
const int BTN_TR = 769406820;
const int BTN_BL = 769407385;
const int BTN_BR = 769407116;
// dirs: 0=UP 1=DOWN 2=LEFT 3=RIGHT
Dictionary<int, int> dirBtn = new Dictionary<int, int>
{
[0] = BTN_TR,
[1] = BTN_BR,
[2] = BTN_BL,
[3] = BTN_TL
};
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
DateTime lastCmdTime = DateTime.MinValue;
DateTime lastInfo = DateTime.MinValue;
DateTime lastDebug = DateTime.MinValue;
string lastSentDir = "";
int curDir = -1;
(int x, int y) prevHead = (-1, -1);
int pendingBtn = 0;
(int x, int y) pendingHead = (-1, -1);
DateTime pendingAt = DateTime.MinValue;
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;
void Dbg(string msg, bool force = false)
{
if (!force && (DateTime.Now - lastDebug).TotalMilliseconds < 120) return;
Log($"dbg: {msg}");
lastDebug = DateTime.Now;
}
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 DirFromDelta(int dx, int dy)
{
if (Math.Abs(dx) >= Math.Abs(dy))
return dx < 0 ? 2 : 3;
return dy < 0 ? 0 : 1;
}
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 LearnMappingFromMovement((int x, int y) head)
{
if (pendingBtn == 0) return;
if ((DateTime.Now - pendingAt).TotalMilliseconds > 1200)
{
pendingBtn = 0;
return;
}
int dx = head.x - pendingHead.x;
int dy = head.y - pendingHead.y;
if (dx == 0 && dy == 0) return;
int movedDir = DirFromDelta(dx, dy);
int oldBtn = dirBtn[movedDir];
if (oldBtn != pendingBtn)
{
dirBtn[movedDir] = pendingBtn;
// keep mapping one-to-one by swapping previous owner
foreach (var kv in dirBtn.ToList())
{
if (kv.Key != movedDir && kv.Value == pendingBtn)
{
dirBtn[kv.Key] = oldBtn;
break;
}
}
Dbg($"calib: {DIRS[movedDir]} now={pendingBtn} swapped_old={oldBtn}", true);
Dbg($"map: U={dirBtn[0]} D={dirBtn[1]} L={dirBtn[2]} R={dirBtn[3]}", true);
}
pendingBtn = 0;
}
void Cmd(int d, string reason, (int x, int y) head, (int x, int y)? target)
{
string nd = DIRS[d];
if (nd == lastSentDir && (DateTime.Now - lastCmdTime).TotalMilliseconds < 110)
return;
int id = dirBtn[d];
if (TryGetButtonPos(id, out int bx, out int by))
Move(bx, by);
Send(Out["ClickFurni"], id, 0);
pendingBtn = id;
pendingHead = head;
pendingAt = DateTime.Now;
lastCmdTime = DateTime.Now;
lastSentDir = nd;
if (target.HasValue)
Dbg($"reason={reason} head=({head.x},{head.y}) target=({target.Value.x},{target.Value.y}) dir={nd} btn={id}", true);
else
Dbg($"reason={reason} head=({head.x},{head.y}) dir={nd} btn={id}", true);
}
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 Emergency((int x, int y) head, HashSet<(int x, int y)> body)
{
int bestDir = curDir >= 0 ? curDir : 0;
int bestSpace = -1;
for (int d = 0; d < 4; d++)
{
if (curDir >= 0 && 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, 120);
if (space > bestSpace)
{
bestSpace = space;
bestDir = d;
}
}
return bestDir;
}
int Decide((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body)
{
int bestDir = curDir >= 0 ? curDir : 0;
int bestScore = int.MinValue;
for (int d = 0; d < 4; d++)
{
if (curDir >= 0 && 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, 200);
if (space < 10) continue;
int dist = Dist(nx, ny, target.x, target.y, body, 200);
int score = 0;
if (nx == target.x && ny == target.y) score += 15000;
if (dist > 0 && dist < 999) score += (240 - dist) * 8;
else if (dist == -1) score -= 300;
score += Math.Min(space, 120) * 4;
if (curDir >= 0 && d == curDir) score += 2;
if (score > bestScore)
{
bestScore = score;
bestDir = d;
}
}
return bestDir;
}
Log("Snake Auto (Campaign) v7 started");
Log($"init map: U={dirBtn[0]} D={dirBtn[1]} L={dirBtn[2]} R={dirBtn[3]}");
while (Run)
{
Delay(18);
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;
LearnMappingFromMovement(head);
if (prevHead.x != -1)
{
int dx = head.Item1 - prevHead.x;
int dy = head.Item2 - prevHead.y;
if (dx != 0 || dy != 0)
curDir = DirFromDelta(dx, dy);
}
prevHead = head;
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))
.ToList();
if (targets.Count == 0)
{
if ((DateTime.Now - lastInfo).TotalMilliseconds > 500)
{
Dbg($"reason=no_target_hold head=({head.Item1},{head.Item2})", true);
lastInfo = DateTime.Now;
}
continue;
}
var target = targets
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
.First();
if (curDir >= 0)
{
Next(head.Item1, head.Item2, curDir, out int fx, out int fy);
if (Bad(fx, fy, body))
{
int emergency = Emergency(head, body);
Cmd(emergency, "front_blocked", head, target);
continue;
}
}
int best = Decide(head, target, body);
Cmd(best, "seek_target", head, target);
}

View File

@ -0,0 +1,171 @@
/// @name Snake Auto (Campaign) v8
using System;
using System.Linq;
using System.Collections.Generic;
const long HEAD_ID = 769407982;
const int SNAKE_KIND = 5478;
static readonly int[] TARGET_KINDS = { 6559, 3666 };
const int GAME_MIN_X = 20;
const int GAME_MAX_X = 65;
const int GAME_MIN_Y = 20;
const int GAME_MAX_Y = 65;
// Direction -> plate ids (kept from your latest calibration)
const int CTRL_UP = 769406820;
const int CTRL_DOWN = 769407116;
const int CTRL_LEFT = 769407385;
const int CTRL_RIGHT = 769407085;
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
DateTime lastCmd = DateTime.MinValue;
DateTime lastDbg = DateTime.MinValue;
int curDir = -1;
(int x, int y) prevHead = (-1, -1);
bool InBoard(int x, int y)
=> x >= GAME_MIN_X && x <= GAME_MAX_X && y >= GAME_MIN_Y && y <= GAME_MAX_Y;
void Dbg(string m, bool force = false)
{
if (!force && (DateTime.Now - lastDbg).TotalMilliseconds < 220) return;
Log($"dbg: {m}");
lastDbg = DateTime.Now;
}
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 DirFromDelta(int dx, int dy)
{
if (Math.Abs(dx) >= Math.Abs(dy)) return dx < 0 ? 2 : 3;
return dy < 0 ? 0 : 1;
}
int BtnForDir(int d)
=> d == 0 ? CTRL_UP : d == 1 ? CTRL_DOWN : d == 2 ? CTRL_LEFT : CTRL_RIGHT;
bool TryGetBtnPos(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 reason, (int x, int y) head, (int x, int y)? target)
{
if ((DateTime.Now - lastCmd).TotalMilliseconds < 120) return;
int id = BtnForDir(d);
if (TryGetBtnPos(id, out int bx, out int by)) Move(bx, by);
Send(Out["ClickFurni"], id, 0);
lastCmd = DateTime.Now;
if (target.HasValue)
Dbg($"{reason} head=({head.x},{head.y}) target=({target.Value.x},{target.Value.y}) dir={DIRS[d]} btn={id}", true);
else
Dbg($"{reason} head=({head.x},{head.y}) dir={DIRS[d]} btn={id}", true);
}
bool Bad(int x, int y, HashSet<(int x, int y)> body)
{
if (!InBoard(x, y)) return true;
return body.Contains((x, y));
}
int PickDir((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body)
{
var order = new List<int>();
int dx = target.x - head.x;
int dy = target.y - head.y;
if (Math.Abs(dx) >= Math.Abs(dy))
{
order.Add(dx < 0 ? 2 : 3);
order.Add(dy < 0 ? 0 : 1);
}
else
{
order.Add(dy < 0 ? 0 : 1);
order.Add(dx < 0 ? 2 : 3);
}
order.AddRange(new[] { 0, 1, 2, 3 });
foreach (int d in order.Distinct())
{
if (curDir >= 0 && d == Opp(curDir)) continue;
Next(head.x, head.y, d, out int nx, out int ny);
if (!Bad(nx, ny, body)) return d;
}
return curDir >= 0 ? curDir : 0;
}
Log("Snake Auto (Campaign) v8 started");
Log($"target kinds: {string.Join(",", TARGET_KINDS)}");
Log($"buttons U={CTRL_UP} D={CTRL_DOWN} L={CTRL_LEFT} R={CTRL_RIGHT}");
while (Run)
{
Delay(20);
var headItem = GetFloorItem(HEAD_ID);
if (headItem == null)
{
Dbg("head missing");
continue;
}
var head = (headItem.Location.X, headItem.Location.Y);
if (!InBoard(head.Item1, head.Item2))
{
Dbg($"head out of board ({head.Item1},{head.Item2})");
continue;
}
if (prevHead.x != -1)
{
int hdx = head.Item1 - prevHead.x;
int hdy = head.Item2 - prevHead.y;
if (hdx != 0 || hdy != 0) curDir = DirFromDelta(hdx, hdy);
}
prevHead = head;
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 && TARGET_KINDS.Contains((int)i.Kind))
.Where(i => InBoard(i.Location.X, i.Location.Y))
.Select(i => (x: i.Location.X, y: i.Location.Y, kind: (int)i.Kind, id: (long)i.Id))
.ToList();
if (targets.Count == 0)
{
Dbg("no_target_hold", true);
continue;
}
var target = targets
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
.First();
int dsel = PickDir(head, (target.x, target.y), body);
Cmd(dsel, $"seek k={target.kind} id={target.id}", head, (target.x, target.y));
}

View File

@ -0,0 +1,233 @@
/// @name Snake Auto (Campaign) v9
using System;
using System.Linq;
using System.Collections.Generic;
const long HEAD_ID = 769407982;
const int SNAKE_KIND = 5478;
static readonly int[] TARGET_KINDS = { 6559, 3666 };
const int GAME_MIN_X = 20;
const int GAME_MAX_X = 65;
const int GAME_MIN_Y = 20;
const int GAME_MAX_Y = 65;
// Hard obstacle (blue block area)
const int BLOCK_MIN_X = 20;
const int BLOCK_MAX_X = 22;
const int BLOCK_MIN_Y = 20;
const int BLOCK_MAX_Y = 21;
// Direction -> plate ids
const int CTRL_UP = 769406820;
const int CTRL_DOWN = 769407116;
const int CTRL_LEFT = 769407385;
const int CTRL_RIGHT = 769407085;
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
DateTime lastCmd = DateTime.MinValue;
DateTime lastDbg = DateTime.MinValue;
int curDir = -1;
(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)>();
bool InBoard(int x, int y)
=> x >= GAME_MIN_X && x <= GAME_MAX_X && y >= GAME_MIN_Y && y <= GAME_MAX_Y;
bool IsBlock(int x, int y)
=> x >= BLOCK_MIN_X && x <= BLOCK_MAX_X && y >= BLOCK_MIN_Y && y <= BLOCK_MAX_Y;
void Dbg(string m, bool force = false)
{
if (!force && (DateTime.Now - lastDbg).TotalMilliseconds < 220) return;
Log($"dbg: {m}");
lastDbg = DateTime.Now;
}
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 DirFromDelta(int dx, int dy)
{
if (Math.Abs(dx) >= Math.Abs(dy)) return dx < 0 ? 2 : 3;
return dy < 0 ? 0 : 1;
}
int BtnForDir(int d)
=> d == 0 ? CTRL_UP : d == 1 ? CTRL_DOWN : d == 2 ? CTRL_LEFT : CTRL_RIGHT;
bool TryGetBtnPos(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 reason, (int x, int y) head, (int x, int y)? target)
{
if ((DateTime.Now - lastCmd).TotalMilliseconds < 120) return;
int id = BtnForDir(d);
if (TryGetBtnPos(id, out int bx, out int by)) Move(bx, by);
Send(Out["ClickFurni"], id, 0);
lastCmd = DateTime.Now;
if (target.HasValue)
Dbg($"{reason} h=({head.x},{head.y}) t=({target.Value.x},{target.Value.y}) d={DIRS[d]} b={id}", true);
else
Dbg($"{reason} h=({head.x},{head.y}) d={DIRS[d]} b={id}", true);
}
bool Bad(int x, int y, HashSet<(int x, int y)> body)
{
if (!InBoard(x, y)) return true;
if (IsBlock(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 Emergency((int x, int y) head, HashSet<(int x, int y)> body)
{
int bestDir = curDir >= 0 ? curDir : 0;
int bestSpace = -1;
for (int d = 0; d < 4; d++)
{
if (curDir >= 0 && 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, 260);
if (space > bestSpace)
{
bestSpace = space;
bestDir = d;
}
}
return bestDir;
}
int PickDir((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body)
{
int desired = DirFromDelta(target.x - head.x, target.y - head.y);
var dirs = new List<int> { desired, 0, 1, 2, 3 };
int bestDir = curDir >= 0 ? curDir : desired;
int bestScore = int.MinValue;
foreach (int d in dirs.Distinct())
{
if (curDir >= 0 && d == Opp(curDir)) continue;
Next(head.x, head.y, d, out int nx, out int ny);
if (Bad(nx, ny, body)) continue;
int dist = Math.Abs(target.x - nx) + Math.Abs(target.y - ny);
int space = Fill(nx, ny, body, 260);
int score = 0;
score += -dist * 6;
score += space * 2;
if (d == desired) score += 30;
if (curDir >= 0 && d == curDir) score += 5;
if (score > bestScore)
{
bestScore = score;
bestDir = d;
}
}
return bestDir;
}
Log("Snake Auto (Campaign) v9 started");
Log($"target kinds: {string.Join(",", TARGET_KINDS)}");
Log($"buttons U={CTRL_UP} D={CTRL_DOWN} L={CTRL_LEFT} R={CTRL_RIGHT}");
while (Run)
{
Delay(20);
var headItem = GetFloorItem(HEAD_ID);
if (headItem == null)
{
Dbg("head missing");
continue;
}
var head = (headItem.Location.X, headItem.Location.Y);
if (!InBoard(head.Item1, head.Item2))
continue;
if (prevHead.x != -1)
{
int hdx = head.Item1 - prevHead.x;
int hdy = head.Item2 - prevHead.y;
if (hdx != 0 || hdy != 0) curDir = DirFromDelta(hdx, hdy);
}
prevHead = head;
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 && TARGET_KINDS.Contains((int)i.Kind))
.Where(i => InBoard(i.Location.X, i.Location.Y))
.Select(i => (x: i.Location.X, y: i.Location.Y, kind: (int)i.Kind, id: (long)i.Id))
.ToList();
if (targets.Count == 0)
{
int safe = Emergency(head, body);
Cmd(safe, "no_target_safe", head, null);
continue;
}
var target = targets
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
.First();
int dir = PickDir(head, (target.x, target.y), body);
Cmd(dir, $"seek k={target.kind} id={target.id}", head, (target.x, target.y));
}

115
Snake Auto (Campaign).csx Normal file
View File

@ -0,0 +1,115 @@
/// @name Snake Auto (Campaign)
using System;
using System.Linq;
// Fixed from your room
const long HEAD_ID = 769407982;
const int TARGET_KIND = 3666; // Color Tile kind
// Pad button IDs (as confirmed by you)
const int PAD_TOP_LEFT = 769407085;
const int PAD_TOP_RIGHT = 769406820;
const int PAD_BOTTOM_LEFT = 769407385;
const int PAD_BOTTOM_RIGHT = 769407116;
// Direction mapping (tuned for this room)
const int CTRL_UP = PAD_BOTTOM_LEFT;
const int CTRL_RIGHT = PAD_TOP_RIGHT;
const int CTRL_DOWN = PAD_TOP_LEFT;
const int CTRL_LEFT = PAD_BOTTOM_RIGHT;
DateTime lastCmd = DateTime.MinValue;
DateTime lastInfo = DateTime.MinValue;
int lastDir = -1; // 0=UP 1=DOWN 2=LEFT 3=RIGHT
bool TryGetButton(int dir, out int id, out int x, out int y)
{
id = dir == 0 ? CTRL_UP : dir == 1 ? CTRL_DOWN : dir == 2 ? CTRL_LEFT : CTRL_RIGHT;
var btn = GetFloorItem(id);
if (btn == null)
{
x = 0;
y = 0;
return false;
}
x = btn.Location.X;
y = btn.Location.Y;
return true;
}
void SendDir(int dir)
{
if ((DateTime.Now - lastCmd).TotalMilliseconds < 140 && dir == lastDir)
return;
if (TryGetButton(dir, out int id, out int x, out int y))
{
// This game mode reacts when user steps on the plate.
Move(x, y);
}
else
{
int fallbackId = dir == 0 ? CTRL_UP : dir == 1 ? CTRL_DOWN : dir == 2 ? CTRL_LEFT : CTRL_RIGHT;
Send(Out["ClickFurni"], fallbackId, 0);
}
lastCmd = DateTime.Now;
lastDir = dir;
}
int ToDir(int dx, int dy)
{
if (Math.Abs(dx) >= Math.Abs(dy))
return dx < 0 ? 2 : 3;
return dy < 0 ? 0 : 1;
}
Log("Snake Auto (Campaign) v4 started");
Log($"HeadId={HEAD_ID}, TargetKind={TARGET_KIND}");
Log($"Buttons: U={CTRL_UP} R={CTRL_RIGHT} D={CTRL_DOWN} L={CTRL_LEFT}");
while (Run)
{
Delay(40);
var head = GetFloorItem(HEAD_ID);
if (head == null)
{
if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500)
{
Log("waiting: head not visible");
lastInfo = DateTime.Now;
}
continue;
}
var targets = FloorItems
.Where(i => i != null && i.Kind == TARGET_KIND)
.ToList();
if (targets.Count == 0)
{
if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500)
{
Log($"waiting: no target tiles kind {TARGET_KIND}");
lastInfo = DateTime.Now;
}
continue;
}
var target = targets
.OrderBy(t => Math.Abs(t.Location.X - head.Location.X) + Math.Abs(t.Location.Y - head.Location.Y))
.ThenBy(t => t.State)
.First();
int dx = target.Location.X - head.Location.X;
int dy = target.Location.Y - head.Location.Y;
if (dx == 0 && dy == 0)
continue;
int dir = ToDir(dx, dy);
SendDir(dir);
}

37
Snake Debug Watch.csx Normal file
View File

@ -0,0 +1,37 @@
/// @name Snake Debug Watch
long TARGET_ID = 769407982;
long[] BTN_IDS = { 769407085, 769407116, 769407385, 769406820 };
int tick = 0;
int lastX = int.MinValue, lastY = int.MinValue, lastState = int.MinValue;
Log("Snake Debug Watch started");
while (Run)
{
Delay(100);
tick++;
var target = GetFloorItem(TARGET_ID);
if (target == null)
{
if (tick % 10 == 0)
Log("target missing");
continue;
}
if (target.Location.X != lastX || target.Location.Y != lastY || target.State != lastState)
{
Log($"target: ({target.Location.X},{target.Location.Y}) state={target.State} kind={target.Kind}");
lastX = target.Location.X;
lastY = target.Location.Y;
lastState = target.State;
}
if (tick % 20 == 0)
{
int k7100 = FloorItems.Count(i => i != null && i.Kind == 7100);
int k5068 = FloorItems.Count(i => i != null && i.Kind == 5068);
Log($"k7100={k7100}, k5068={k5068}");
}
}

47
Snake Target Probe.csx Normal file
View File

@ -0,0 +1,47 @@
/// @name Snake Target Probe
using System;
using System.Linq;
using System.Collections.Generic;
const int MIN_X = 37, MAX_X = 59, MIN_Y = 29, MAX_Y = 51;
bool InBoard(dynamic i)
=> i != null && i.Location.X >= MIN_X && i.Location.X <= MAX_X && i.Location.Y >= MIN_Y && i.Location.Y <= MAX_Y;
var map = new Dictionary<long, (int kind, int x, int y, int state)>();
foreach (var i in FloorItems)
{
if (!InBoard(i)) continue;
map[i.Id] = ((int)i.Kind, i.Location.X, i.Location.Y, (int)i.State);
}
Log("Snake Target Probe started");
Log("Watching board item state/move changes...");
while (Run)
{
Delay(100);
foreach (var i in FloorItems)
{
if (!InBoard(i)) continue;
int kind = (int)i.Kind;
int x = i.Location.X;
int y = i.Location.Y;
int state = (int)i.State;
if (!map.TryGetValue(i.Id, out var prev))
{
map[i.Id] = (kind, x, y, state);
Log($"add id={i.Id} kind={kind} pos=({x},{y}) state={state}");
continue;
}
if (prev.x != x || prev.y != y || prev.state != state)
{
Log($"chg id={i.Id} kind={kind} ({prev.x},{prev.y})/{prev.state} -> ({x},{y})/{state}");
map[i.Id] = (kind, x, y, state);
}
}
}

343
Snake [28.12.25] V1.0.csx Normal file
View File

@ -0,0 +1,343 @@
using System;
using System.Collections.Generic;
using System.Linq;
// --- KONFIGURATION ---
const int GAME_MIN_X = 9;
const int GAME_MAX_X = 28;
const int GAME_MIN_Y = 11;
const int GAME_MAX_Y = 30;
const int BLOCKED_MIN_X = 17;
const int BLOCKED_MAX_X = 20;
const int BLOCKED_MIN_Y = 19;
const int BLOCKED_MAX_Y = 22;
const int KIND_SNAKE = 7100;
const int KIND_FOOD = 5068;
const int CTRL_UP = 2147418172;
const int CTRL_RIGHT = 2147418173;
const int CTRL_DOWN = 2147418174;
const int CTRL_LEFT = 2147418175;
// --- GLOBALE VARIABLEN ---
List<(int x, int y)> body = new List<(int x, int y)>(200);
HashSet<(int x, int y)> bodySet = new HashSet<(int x, int y)>(200);
(int x, int y) food = (-1, -1);
string dir = "DOWN";
string lastSentDir = "";
DateTime lastCmdTime = DateTime.MinValue;
int len = 0;
bool ate = false;
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
HashSet<(int, int)> fillVisited = new HashSet<(int, int)>(300);
Queue<(int, int)> fillQueue = new Queue<(int, int)>(300);
HashSet<(int, int)> pathVisited = new HashSet<(int, int)>(200);
Queue<((int x, int y) pos, int dist)> pathQueue = new Queue<((int, int), int)>(200);
// --- HILFSFUNKTIONEN ---
bool Wall(int x, int y) => x < GAME_MIN_X || x > GAME_MAX_X || y < GAME_MIN_Y || y > GAME_MAX_Y;
bool Blk(int x, int y) => x >= BLOCKED_MIN_X && x <= BLOCKED_MAX_X && y >= BLOCKED_MIN_Y && y <= BLOCKED_MAX_Y;
void Nxt(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 OppIdx(int d) => d ^ 1;
bool Bad(int x, int y, bool ignoreTail)
{
if (Wall(x, y) || Blk(x, y)) return true;
if (body.Count == 0) return false;
int checkLen = ignoreTail && !ate ? body.Count - 1 : body.Count;
for (int i = 0; i < checkLen; i++)
if (body[i].x == x && body[i].y == y) return true;
return false;
}
void Cmd(int dIdx)
{
string d = DIRS[dIdx];
if (d == lastSentDir && (DateTime.Now - lastCmdTime).TotalMilliseconds < 80) return;
int id = dIdx == 0 ? CTRL_UP : dIdx == 1 ? CTRL_DOWN : dIdx == 2 ? CTRL_LEFT : CTRL_RIGHT;
Send(Out["ClickFurni"], id, 0);
lastCmdTime = DateTime.Now;
lastSentDir = d;
dir = d;
}
int Fill(int sx, int sy, int limit)
{
if (Bad(sx, sy, false)) return 0;
fillVisited.Clear();
fillQueue.Clear();
fillQueue.Enqueue((sx, sy));
fillVisited.Add((sx, sy));
int c = 0;
while (fillQueue.Count > 0 && c < limit)
{
var (x, y) = fillQueue.Dequeue();
c++;
for (int d = 0; d < 4; d++)
{
Nxt(x, y, d, out int nx, out int ny);
if (!Bad(nx, ny, false) && fillVisited.Add((nx, ny)))
fillQueue.Enqueue((nx, ny));
}
}
return c;
}
// Pathfinding mit höherem Default-Limit
int GetPathDistance(int sx, int sy, int tx, int ty, int limit = 150)
{
if (sx == tx && sy == ty) return 0;
pathVisited.Clear();
pathQueue.Clear();
pathQueue.Enqueue(((sx, sy), 0));
pathVisited.Add((sx, sy));
while (pathQueue.Count > 0)
{
var curr = pathQueue.Dequeue();
if (curr.dist >= limit) return 999;
for (int d = 0; d < 4; d++)
{
Nxt(curr.pos.x, curr.pos.y, d, out int nx, out int ny);
if (nx == tx && ny == ty) return curr.dist + 1;
if (!Bad(nx, ny, false) && pathVisited.Add((nx, ny)))
pathQueue.Enqueue(((nx, ny), curr.dist + 1));
}
}
return -1;
}
int DecideMove(int hx, int hy, int fx, int fy, int curDirIdx)
{
int bestDir = curDirIdx;
int bestScore = int.MinValue;
// Höhere Limits für besseres Pathfinding
int fillLimit = len > 100 ? 100 : len > 50 ? 150 : 200;
int pathLimit = len > 100 ? 80 : len > 50 ? 120 : 150;
int minSpace = Math.Max(len / 4, 10); // Noch toleranter
var tail = body.Count > 0 ? body[body.Count - 1] : (-1, -1);
// Prüfe ob Food am Rand ist
bool foodAtEdge = fx <= GAME_MIN_X + 1 || fx >= GAME_MAX_X - 1 ||
fy <= GAME_MIN_Y + 1 || fy >= GAME_MAX_Y - 1;
for (int d = 0; d < 4; d++)
{
if (d == OppIdx(curDirIdx)) continue;
Nxt(hx, hy, d, out int nx, out int ny);
if (Bad(nx, ny, true)) continue;
int space = Fill(nx, ny, fillLimit);
if (space < minSpace) continue;
int score = 0;
// ESSEN DIREKT DANEBEN = HÖCHSTE PRIORITÄT!
if (nx == fx && ny == fy)
{
score += 10000;
}
else
{
int distFood = GetPathDistance(nx, ny, fx, fy, pathLimit);
if (distFood > 0 && distFood < 999)
{
// Stärkerer Food-Bonus - Essen ist wichtiger!
score += (200 - distFood) * 8;
}
else if (distFood == -1)
{
// Kein Weg zum Essen gefunden - leichter Malus aber nicht zu hart
score -= 100;
}
}
// Platz-Bonus (weniger gewichtet als vorher)
score += Math.Min(space, 100) * 5;
// Safety: Schwanz erreichbar?
if (tail.Item1 != -1 && space < fillLimit)
{
int distTail = GetPathDistance(nx, ny, tail.Item1, tail.Item2, 30);
if (distTail > 0) score += 150;
else if (space < 40) score -= 300;
}
// Wandnähe - NUR wenn Food NICHT am Rand ist!
// Wenn Food am Rand -> kein Penalty für Randnähe
if (!foodAtEdge && len > 50)
{
if (nx <= GAME_MIN_X + 1 || nx >= GAME_MAX_X - 1) score -= 20;
if (ny <= GAME_MIN_Y + 1 || ny >= GAME_MAX_Y - 1) score -= 20;
}
if (score > bestScore)
{
bestScore = score;
bestDir = d;
}
}
return bestDir;
}
int EmergencyMove(int hx, int hy, int curDirIdx)
{
int bestDir = curDirIdx;
int bestSpace = -1;
for (int d = 0; d < 4; d++)
{
if (d == OppIdx(curDirIdx)) continue;
Nxt(hx, hy, d, out int nx, out int ny);
if (Bad(nx, ny, true)) continue;
int space = Fill(nx, ny, 60);
if (space > bestSpace)
{
bestSpace = space;
bestDir = d;
}
}
return bestDir;
}
int DirToIdx(string d) => d == "UP" ? 0 : d == "DOWN" ? 1 : d == "LEFT" ? 2 : 3;
Log("SNAKE V16 - EDGE FOOD FIX");
DateTime lastFullCalc = DateTime.MinValue;
int cachedMove = -1;
while (Run)
{
Delay(15);
var curSnake = new HashSet<(int x, int y)>();
food = (-1, -1);
foreach (var item in FloorItems)
{
if (item == null) continue;
int k;
try { k = (int)item.Kind; } catch { continue; }
if (k == KIND_SNAKE && !Wall(item.Location.X, item.Location.Y))
curSnake.Add((item.Location.X, item.Location.Y));
else if (k == KIND_FOOD)
food = (item.Location.X, item.Location.Y);
}
int curLen = curSnake.Count;
if (curLen == 0) { body.Clear(); bodySet.Clear(); len = 0; continue; }
if (body.Count == 0)
{
var sorted = curSnake.OrderBy(p => p.y).ThenBy(p => p.x).ToList();
body = new List<(int x, int y)>(sorted);
bodySet = new HashSet<(int x, int y)>(sorted);
len = curLen;
continue;
}
var head = body[0];
(int x, int y) newHead = (-1, -1);
for (int d = 0; d < 4; d++)
{
Nxt(head.x, head.y, d, out int nx, out int ny);
if (curSnake.Contains((nx, ny)) && !bodySet.Contains((nx, ny)))
{
newHead = (nx, ny);
if ((DateTime.Now - lastCmdTime).TotalMilliseconds > 400) dir = DIRS[d];
break;
}
}
if (newHead.x != -1)
{
body.Insert(0, newHead);
bodySet.Add(newHead);
ate = curLen > len;
while (body.Count > curLen)
{
var tail = body[body.Count - 1];
bodySet.Remove(tail);
body.RemoveAt(body.Count - 1);
}
len = curLen;
cachedMove = -1;
}
if (body.Count == 0) continue;
head = body[0];
int curDirIdx = DirToIdx(dir);
// NOTFALL: Wand direkt vor uns?
Nxt(head.x, head.y, curDirIdx, out int frontX, out int frontY);
if (Bad(frontX, frontY, true))
{
int emergency = EmergencyMove(head.x, head.y, curDirIdx);
Cmd(emergency);
continue;
}
if (food.x == -1) continue;
// SOFORT ESSEN wenn direkt daneben!
for (int d = 0; d < 4; d++)
{
if (d == OppIdx(curDirIdx)) continue;
Nxt(head.x, head.y, d, out int nx, out int ny);
if (nx == food.x && ny == food.y && !Bad(nx, ny, true))
{
int spaceAfter = Fill(nx, ny, 80);
if (spaceAfter >= Math.Max(len / 4, 8))
{
Cmd(d);
goto nextLoop;
}
}
}
// Normale KI - öfter neu berechnen für bessere Reaktion
double msSinceCalc = (DateTime.Now - lastFullCalc).TotalMilliseconds;
int recalcInterval = len > 100 ? 100 : len > 50 ? 70 : 50;
if (cachedMove == -1 || msSinceCalc > recalcInterval)
{
cachedMove = DecideMove(head.x, head.y, food.x, food.y, curDirIdx);
lastFullCalc = DateTime.Now;
}
if (cachedMove != curDirIdx)
Cmd(cachedMove);
nextLoop:;
}

1115
Tetris [28.12.25] V1.0.csx Normal file

File diff suppressed because it is too large Load Diff

35
Trade Spam.csx Normal file
View File

@ -0,0 +1,35 @@
// Trade Spam - Klicke auf einen User um ihn zu spammen
int targetIndex = 0;
string targetName = "";
Log("=== Trade Spam Script ===");
Log("Klicke auf einen User um ihn zu spammen!");
// Erkennt wenn du auf einen User klickst
OnIntercept(Out["GetSelectedBadges"], e => {
var userId = e.Packet.ReadInt();
var user = Users.FirstOrDefault(u => u.Id == userId);
if (user != null)
{
targetIndex = user.Index;
targetName = user.Name;
Log($">>> Ziel gesetzt: {targetName} <<<");
}
});
while (Run)
{
if (targetIndex > 0)
{
try
{
Send(Out["OpenTrading"], targetIndex);
Delay(80);
Send(Out["CloseTrading"]);
Delay(80);
}
catch { }
}
Delay(50);
}

View File

@ -0,0 +1,95 @@
// Automatisch durch Räume gehen und User in TXT speichern (nur Namen)
using System.IO;
var minUsers = 1;
var delayBetweenRooms = 3000;
var desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
var filePath = Path.Combine(desktopPath, "HabboUserList_fi.txt");
var allUsers = new HashSet<string>();
// Existierende User laden
if (File.Exists(filePath))
{
foreach (var line in File.ReadAllLines(filePath))
{
if (!string.IsNullOrWhiteSpace(line))
{
allUsers.Add(line.Trim());
}
}
Log($"Bestehende Liste geladen: {allUsers.Count} User");
}
void SaveToFile()
{
File.WriteAllLines(filePath, allUsers.OrderBy(x => x));
Log($"[GESPEICHERT] {allUsers.Count} User");
}
var categories = new[] { "popular", "recommended" };
var allRooms = new Dictionary<long, dynamic>();
Log("=== Suche Räume... ===");
foreach (var category in categories)
{
try
{
var rooms = SearchNav(category).Where(x => x.Users >= minUsers);
foreach (var room in rooms)
{
if (!allRooms.ContainsKey(room.Id))
allRooms[room.Id] = room;
}
Delay(1000);
}
catch { }
}
Log($"Gefunden: {allRooms.Count} Räume");
int roomCount = 0;
foreach (var room in allRooms.Values)
{
if (!Run) break;
roomCount++;
Log($"[{roomCount}/{allRooms.Count}] {room.Name}");
try
{
if (EnsureEnterRoom(room.Id) == RoomEntryResult.Success)
{
Delay(1500);
int newUsers = 0;
foreach (var user in Users)
{
if (allUsers.Add(user.Name))
newUsers++;
}
if (newUsers > 0)
{
Log($" +{newUsers} neue User");
SaveToFile();
}
LeaveRoom();
}
}
catch { }
Delay(delayBetweenRooms);
}
SaveToFile();
Log($"");
Log($"=== FERTIG ===");
Log($"Gesamt: {allUsers.Count} User");
Log($"Datei: {filePath}");

View File

@ -0,0 +1,245 @@
/// @name User Collector + Auto Hopper
/// @desc Sammelt User-Namen und hoppt automatisch durch Raeume
/// @author OpenCode
/// @scripter 1.0.0-beta.136
// ============ EINSTELLUNGEN ============
int minUsers = 5; // Minimum User im Raum
int waitInRoom = 6000; // Wie lange in jedem Raum bleiben (ms)
int delayBetweenRooms = 2000; // Pause zwischen Raumwechsel (ms)
// ========================================
// Pfad zur Ausgabedatei auf dem Desktop
var desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
var outputFile = Path.Combine(desktopPath, "HabboUserList.txt");
// HashSet um Duplikate zu vermeiden
var collectedUsers = new HashSet<string>();
// Besuchte Raeume tracken
var visitedRooms = new HashSet<long>();
// Raeume Queue - sortiert nach User-Anzahl
var roomQueue = new List<(long Id, string Name, int UserCount)>();
// Lade bereits gesammelte User aus der Datei (falls vorhanden)
if (File.Exists(outputFile))
{
var lines = File.ReadAllLines(outputFile);
foreach (var line in lines)
{
if (!string.IsNullOrWhiteSpace(line))
collectedUsers.Add(line.Trim());
}
Log($"Loaded: {collectedUsers.Count} users from file");
}
Log("=================================");
Log("User Collector + Auto Hopper");
Log($"Min. users per room: {minUsers}");
Log("=================================");
Log("HOW TO USE:");
Log("1. Open Navigator in game");
Log("2. Click 'Rooms' -> 'Popular'");
Log("3. Script catches the rooms");
Log("4. Type :start to begin hopping");
Log("=================================");
Log("Commands: :start :stop :status :reset");
Log("=================================");
// Hopping aktiv?
bool isHopping = false;
// Event: Wenn jemand den Raum betritt
OnEntityAdded(e => {
if (e.Entity is IRoomUser user)
{
var userName = user.Name;
if (collectedUsers.Add(userName))
{
File.AppendAllText(outputFile, userName + "\n");
Log($"+ {userName}");
}
}
});
// Intercept Navigator Suchergebnisse (Navigator2)
OnIntercept(In.Navigator2SearchResultBlocks, e => {
try
{
var packet = e.Packet;
var searchCode = packet.ReadString();
var filterText = packet.ReadString();
var blockCount = packet.ReadInt();
int foundRooms = 0;
for (int b = 0; b < blockCount; b++)
{
var blockCode = packet.ReadString();
var blockText = packet.ReadString();
var actionAllowed = packet.ReadInt();
var forceClosed = packet.ReadBool();
var roomCount = packet.ReadInt();
for (int r = 0; r < roomCount; r++)
{
var roomId = packet.ReadLong();
var roomName = packet.ReadString();
var ownerId = packet.ReadLong();
var ownerName = packet.ReadString();
var doorMode = packet.ReadInt();
var userCount = packet.ReadInt();
var maxUsers = packet.ReadInt();
var description = packet.ReadString();
var tradeMode = packet.ReadInt();
var score = packet.ReadInt();
var ranking = packet.ReadInt();
var categoryId = packet.ReadInt();
// Tags
var tagCount = packet.ReadInt();
for (int t = 0; t < tagCount; t++)
packet.ReadString();
// Flags
var flags = packet.ReadInt();
if ((flags & 1) != 0) packet.ReadString();
if ((flags & 2) != 0) packet.ReadString();
if ((flags & 4) != 0) { packet.ReadString(); packet.ReadLong(); packet.ReadString(); }
// Raum hinzufuegen
if (userCount >= minUsers && !visitedRooms.Contains(roomId))
{
if (!roomQueue.Any(x => x.Id == roomId))
{
roomQueue.Add((roomId, roomName, userCount));
foundRooms++;
}
}
}
}
if (foundRooms > 0)
{
// Sort by user count (most first)
roomQueue = roomQueue.OrderByDescending(x => x.UserCount).ToList();
Log($"+ {foundRooms} new rooms found! (Total: {roomQueue.Count})");
Status($"Queue: {roomQueue.Count} rooms | Users: {collectedUsers.Count}");
}
}
catch (Exception ex)
{
Log($"Parse-Fehler: {ex.Message}");
}
});
// Chat-Befehl zum Starten
OnIntercept(Out.Chat, e => {
var msg = e.Packet.ReadString(0);
if (msg == ":start")
{
e.Block();
if (roomQueue.Count > 0)
{
isHopping = true;
Log($"Starting hopping through {roomQueue.Count} rooms...");
}
else
{
Log("No rooms in queue! Open Navigator -> Popular");
}
}
else if (msg == ":stop")
{
e.Block();
isHopping = false;
Log("Hopping stopped.");
}
else if (msg == ":status")
{
e.Block();
Log($"Queue: {roomQueue.Count} | Visited: {visitedRooms.Count} | Users: {collectedUsers.Count}");
}
else if (msg == ":reset")
{
e.Block();
visitedRooms.Clear();
roomQueue.Clear();
Log("Reset! Open Navigator again.");
}
});
// Hauptschleife
while (Run)
{
// User im aktuellen Raum sammeln
if (IsInRoom)
{
foreach (var user in Users)
{
if (collectedUsers.Add(user.Name))
{
File.AppendAllText(outputFile, user.Name + "\n");
Log($"+ {user.Name}");
}
}
}
// Hopping wenn aktiv
if (isHopping && roomQueue.Count > 0)
{
var room = roomQueue[0];
roomQueue.RemoveAt(0);
if (!visitedRooms.Contains(room.Id))
{
visitedRooms.Add(room.Id);
Log($">>> {room.Name} ({room.UserCount} User)");
Send(Out.GoToFlat, room.Id, "", -1);
Delay(3500);
if (IsInRoom)
{
// User sammeln
foreach (var user in Users)
{
if (collectedUsers.Add(user.Name))
{
File.AppendAllText(outputFile, user.Name + "\n");
Log($"+ {user.Name}");
}
}
Status($"Users: {collectedUsers.Count} | Rooms: {visitedRooms.Count} | Queue: {roomQueue.Count}");
// Warten
Delay(waitInRoom);
// Nochmal sammeln (fuer Joiner)
foreach (var user in Users)
{
if (collectedUsers.Add(user.Name))
{
File.AppendAllText(outputFile, user.Name + "\n");
Log($"+ {user.Name}");
}
}
}
Delay(delayBetweenRooms);
}
}
else if (isHopping && roomQueue.Count == 0)
{
isHopping = false;
Log($"=== DONE! ===");
Log($"Visited: {visitedRooms.Count} rooms");
Log($"Collected: {collectedUsers.Count} users");
Log("Open Navigator for more rooms, then :start");
}
Status($"Users: {collectedUsers.Count} | Queue: {roomQueue.Count}");
Delay(1000);
}

View File

@ -0,0 +1,95 @@
// Automatisch durch Räume gehen und User in TXT speichern (nur Namen)
using System.IO;
var minUsers = 1;
var delayBetweenRooms = 3000;
var desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
var filePath = Path.Combine(desktopPath, "HabboUserList_nl.txt");
var allUsers = new HashSet<string>();
// Existierende User laden
if (File.Exists(filePath))
{
foreach (var line in File.ReadAllLines(filePath))
{
if (!string.IsNullOrWhiteSpace(line))
{
allUsers.Add(line.Trim());
}
}
Log($"Bestehende Liste geladen: {allUsers.Count} User");
}
void SaveToFile()
{
File.WriteAllLines(filePath, allUsers.OrderBy(x => x));
Log($"[GESPEICHERT] {allUsers.Count} User");
}
var categories = new[] { "popular", "recommended" };
var allRooms = new Dictionary<long, dynamic>();
Log("=== Suche Räume... ===");
foreach (var category in categories)
{
try
{
var rooms = SearchNav(category).Where(x => x.Users >= minUsers);
foreach (var room in rooms)
{
if (!allRooms.ContainsKey(room.Id))
allRooms[room.Id] = room;
}
Delay(1000);
}
catch { }
}
Log($"Gefunden: {allRooms.Count} Räume");
int roomCount = 0;
foreach (var room in allRooms.Values)
{
if (!Run) break;
roomCount++;
Log($"[{roomCount}/{allRooms.Count}] {room.Name}");
try
{
if (EnsureEnterRoom(room.Id) == RoomEntryResult.Success)
{
Delay(1500);
int newUsers = 0;
foreach (var user in Users)
{
if (allUsers.Add(user.Name))
newUsers++;
}
if (newUsers > 0)
{
Log($" +{newUsers} neue User");
SaveToFile();
}
LeaveRoom();
}
}
catch { }
Delay(delayBetweenRooms);
}
SaveToFile();
Log($"");
Log($"=== FERTIG ===");
Log($"Gesamt: {allUsers.Count} User");
Log($"Datei: {filePath}");

28
Wired Message Blocker.csx Normal file
View File

@ -0,0 +1,28 @@
using Xabbo.Interceptor;
using Xabbo.Messages;
// Funktion zum Prüfen der Nachricht
void BlockWired(Intercept e)
{
try
{
e.Packet.Position = 0;
e.Packet.ReadInt(); // User Index
e.Packet.ReadString(); // Nachricht
e.Packet.ReadInt(); // Geste
// Die Bubble ID lesen
int bubbleStyle = e.Packet.ReadInt();
// 34 ist die Standard-Wired-Bubble.
// Falls dein Retro eine andere ID für Wired nutzt, ändere die 34.
if (bubbleStyle == 34)
{
e.Block();
}
}
catch { }
}
// Intercept nur auf Flüstern (Whisper), da Wireds meistens flüstern
Intercept(In.Whisper, BlockWired);

2
hello-once.csx Normal file
View File

@ -0,0 +1,2 @@
/// @name hello-once
Log("hello once");

12
room-scan-basic.csx Normal file
View File

@ -0,0 +1,12 @@
/// @name room-scan-basic
Log("Room scan basic start");
if (!IsInRoom || Room == null)
{
Log("Not in room.");
}
else
{
Log($"Room: {Room.Name} ({Room.Id})");
Log($"Users: {Users.Count()} | FloorItems: {FloorItems.Count()} | WallItems: {WallItems.Count()}");
}

50
room-scan-starter.csx Normal file
View File

@ -0,0 +1,50 @@
using System.Linq;
Log("=== Room Scan Starter ===");
if (!IsInRoom || Room == null)
{
Log("Not in a room. Enter a room and run again.");
}
else
{
Log($"Room: {Room.Name} (ID: {Room.Id})");
Log($"Owner: {Room.OwnerName} ({Room.OwnerId})");
Log($"Users: {Users.Count()} | FloorItems: {FloorItems.Count()} | WallItems: {WallItems.Count()}");
var topFloorKinds = FloorItems
.GroupBy(i => i.Kind)
.OrderByDescending(g => g.Count())
.ThenBy(g => g.Key)
.Take(5)
.ToList();
Log("Top 5 floor kinds:");
if (topFloorKinds.Count == 0)
{
Log("- none");
}
else
{
foreach (var g in topFloorKinds)
Log($"- kind {g.Key}: {g.Count()}x");
}
var topWallKinds = WallItems
.GroupBy(i => i.Kind)
.OrderByDescending(g => g.Count())
.ThenBy(g => g.Key)
.Take(5)
.ToList();
Log("Top 5 wall kinds:");
if (topWallKinds.Count == 0)
{
Log("- none");
}
else
{
foreach (var g in topWallKinds)
Log($"- kind {g.Key}: {g.Count()}x");
}
}

20
room-scan-topk.csx Normal file
View File

@ -0,0 +1,20 @@
/// @name room-scan-topk
using System.Linq;
Log("Top kinds test start");
if (!IsInRoom || Room == null)
{
Log("Not in room.");
}
else
{
var topFloorKinds = FloorItems
.GroupBy(i => i.Kind)
.OrderByDescending(g => g.Count())
.Take(5)
.ToList();
foreach (var g in topFloorKinds)
Log($"kind {g.Key}: {g.Count()}x");
}