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:
@@ -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);
|
||||
@@ -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
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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}");
|
||||
@@ -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}");
|
||||
@@ -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}");
|
||||
@@ -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.");
|
||||
@@ -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.");
|
||||
@@ -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.");
|
||||
@@ -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 (3‑Farben‑Tile)
|
||||
// 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) ===");
|
||||
@@ -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.");
|
||||
@@ -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");
|
||||
@@ -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.");
|
||||
@@ -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.");
|
||||
@@ -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.");
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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");
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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");
|
||||
}
|
||||
@@ -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.");
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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}");
|
||||
@@ -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"">×</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);
|
||||
@@ -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();
|
||||
@@ -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"">×</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);
|
||||
@@ -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})");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
@@ -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.");
|
||||
@@ -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");
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 ===");
|
||||
@@ -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
@@ -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);
|
||||
@@ -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());
|
||||
@@ -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());
|
||||
@@ -0,0 +1,9 @@
|
||||
// Sit Spam - Setzt dich dauerhaft hin
|
||||
|
||||
Log("=== Sit Spam gestartet ===");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Sit();
|
||||
Delay(100);
|
||||
}
|
||||
@@ -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:;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
@@ -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}");
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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}");
|
||||
@@ -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);
|
||||
@@ -0,0 +1,2 @@
|
||||
/// @name hello-once
|
||||
Log("hello once");
|
||||
@@ -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()}");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
Reference in New Issue
Block a user