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:
commit
7bfc390ed6
101
Auto Fishing [28.12.25] (MidMan).csx
Normal file
101
Auto Fishing [28.12.25] (MidMan).csx
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
AUTO-FISHER V10 - SILENT HISTORY
|
||||
- Zeigt NUR noch Fänge und wichtige Events an
|
||||
- Kein "Angel ausgeworfen" Spam mehr
|
||||
- Clean Log (ohne BBCode) & Rare Tracker aktiv
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
// --- EINSTELLUNGEN ---
|
||||
int itemId = 2147419751; // ID deiner Angel/Tile
|
||||
|
||||
// Trigger
|
||||
string triggerCatch = "Rhaz caught";
|
||||
string triggerShark = "attacked by a shark";
|
||||
string triggerStunOver = "stun effect has worn off";
|
||||
|
||||
Log("--- Auto-Fisher V10 (History Mode) gestartet ---");
|
||||
|
||||
string CleanMessage(string input)
|
||||
{
|
||||
return Regex.Replace(input, @"\[.*?\]", "");
|
||||
}
|
||||
|
||||
void Click()
|
||||
{
|
||||
if (!Run) return;
|
||||
Send(Out["ClickFurni"], itemId, 0);
|
||||
// HIER HABEN WIR DEN LOG ENTFERNT
|
||||
// Damit bleibt deine History sauber!
|
||||
}
|
||||
|
||||
void HandleMessage(InterceptArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var p = e.Packet;
|
||||
p.ReadInt();
|
||||
string rawMsg = p.ReadString();
|
||||
string lowerMsg = rawMsg.ToLower();
|
||||
string cleanMsg = CleanMessage(rawMsg);
|
||||
|
||||
// 1. FANG
|
||||
if (lowerMsg.Contains(triggerCatch.ToLower()))
|
||||
{
|
||||
// Rarity Tracker
|
||||
string rarityLog = "";
|
||||
if (lowerMsg.Contains("exotic")) rarityLog = "🟣 EXOTIC";
|
||||
else if (lowerMsg.Contains("legendary")) rarityLog = "🟡 LEGENDARY";
|
||||
else if (lowerMsg.Contains("epic")) rarityLog = "🔴 EPIC";
|
||||
else if (lowerMsg.Contains("rare")) rarityLog = "🔵 RARE";
|
||||
|
||||
// Nur besondere Fische kriegen eine Zeitstempel-Hervorhebung
|
||||
if (rarityLog != "")
|
||||
Log($"💎 {rarityLog} ({DateTime.Now:HH:mm:ss})");
|
||||
|
||||
// Der normale Log-Eintrag für die History
|
||||
Log($"✅ {cleanMsg}");
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
int ms = new Random().Next(500, 1500);
|
||||
System.Threading.Thread.Sleep(ms);
|
||||
if (Run) Click();
|
||||
});
|
||||
}
|
||||
|
||||
// 2. HAI
|
||||
else if (lowerMsg.Contains(triggerShark.ToLower()))
|
||||
{
|
||||
Log($"⚠️ HAI-ANGRIFF! ({CleanMessage(rawMsg)})");
|
||||
// Wir warten stillschweigend auf das Ende
|
||||
}
|
||||
|
||||
// 3. STUN VORBEI
|
||||
else if (lowerMsg.Contains(triggerStunOver.ToLower()))
|
||||
{
|
||||
Log($"🎉 Stun vorbei - weiter geht's!");
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
int reactionTime = new Random().Next(500, 1200);
|
||||
System.Threading.Thread.Sleep(reactionTime);
|
||||
if (Run) Click();
|
||||
});
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
OnIntercept(In["Chat"], e => HandleMessage(e));
|
||||
OnIntercept(In["Shout"], e => HandleMessage(e));
|
||||
OnIntercept(In["Whisper"], e => HandleMessage(e));
|
||||
|
||||
// Erster Klick
|
||||
Click();
|
||||
Log("(Angel ist aktiv - warte auf ersten Fisch...)");
|
||||
|
||||
while(Run) Delay(1000);
|
||||
131
Auto Healer Medic Bot.csx
Normal file
131
Auto Healer Medic Bot.csx
Normal file
@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Sulakore.Communication;
|
||||
using Sulakore.Modules;
|
||||
using Tangine;
|
||||
|
||||
namespace HabboHealBot
|
||||
{
|
||||
[Module("Auto Medic", "Automates the :offer heal process")]
|
||||
[Author("User")]
|
||||
public class HealBot : Extension
|
||||
{
|
||||
// Status-Variable, damit Prozesse nicht unterbrochen werden
|
||||
private bool _isBusy = false;
|
||||
|
||||
// Speichert die Zuordnung von Index (Raum-ID) zu EntityID (Datenbank-ID)
|
||||
// Das wird benötigt, weil der Chat den Index sendet, der Klick aber oft die ID braucht.
|
||||
private Dictionary<int, int> _userMap = new Dictionary<int, int>();
|
||||
|
||||
public HealBot()
|
||||
{
|
||||
// Events registrieren
|
||||
Triggers.In(In.Chat, OnChat); // Wenn jemand spricht
|
||||
Triggers.In(In.Users, OnUsers); // Wenn User den Raum betreten
|
||||
Triggers.In(In.RoomReady, OnRoomReady); // Wenn wir den Raum betreten (Reset)
|
||||
}
|
||||
|
||||
// 1. Liste zurücksetzen wenn wir den Raum wechseln
|
||||
private void OnRoomReady(DataInterceptedEventArgs e)
|
||||
{
|
||||
_userMap.Clear();
|
||||
_isBusy = false;
|
||||
}
|
||||
|
||||
// 2. User tracken (Index zu ID Zuordnung)
|
||||
private void OnUsers(DataInterceptedEventArgs e)
|
||||
{
|
||||
var parser = e.Packet;
|
||||
int count = parser.ReadInt();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int id = parser.ReadInt(); // Entity ID (Datenbank ID)
|
||||
string name = parser.ReadString();
|
||||
string motto = parser.ReadString();
|
||||
string look = parser.ReadString();
|
||||
int index = parser.ReadInt(); // Room Index
|
||||
|
||||
// Restliche Daten überspringen (Koordinaten etc.)
|
||||
// Hinweis: Die Struktur kann je nach Server leicht variieren,
|
||||
// aber ID und Index kommen meist zuerst.
|
||||
|
||||
if (!_userMap.ContainsKey(index))
|
||||
{
|
||||
_userMap.Add(index, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Auf "heal" im Chat reagieren
|
||||
private void OnChat(DataInterceptedEventArgs e)
|
||||
{
|
||||
// Wenn wir gerade schon jemanden heilen -> Ignorieren
|
||||
if (_isBusy) return;
|
||||
|
||||
int index = e.Packet.ReadInt();
|
||||
string message = e.Packet.ReadString();
|
||||
|
||||
// Prüfen ob "heal" (groß/kleinschreibung egal) vorkommt
|
||||
if (message.ToLower().Contains("heal"))
|
||||
{
|
||||
// Prüfen, ob wir die ID zu diesem User haben
|
||||
if (_userMap.ContainsKey(index))
|
||||
{
|
||||
int targetId = _userMap[index];
|
||||
|
||||
// Prozess starten (Async damit der Main-Thread nicht blockiert)
|
||||
Task.Run(() => PerformHealRoutine(targetId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PerformHealRoutine(int targetUserId)
|
||||
{
|
||||
_isBusy = true; // Blockieren
|
||||
Console.WriteLine($"[Bot] Healing User ID: {targetUserId}...");
|
||||
|
||||
try
|
||||
{
|
||||
// Schritt 1: :offer senden
|
||||
await SendPacketAsync(Out.Shout, ":offer", 0);
|
||||
await Task.Delay(600); // Warten bis Menü da ist (Evtl. anpassen je nach Lag)
|
||||
|
||||
// Schritt 2: 1 senden (Heal auswählen)
|
||||
await SendPacketAsync(Out.Shout, "1", 0);
|
||||
await Task.Delay(600); // Warten auf "Click the user" Prompt
|
||||
|
||||
// Schritt 3: User anklicken
|
||||
// Auf den meisten Servern ist "Anklicken" das Paket "GetSelectedBadges"
|
||||
// oder einfach das Abfragen der User-Info.
|
||||
// Paket-Struktur: {Header} {Int: UserID}
|
||||
await SendPacketAsync(Out.GetSelectedBadges, targetUserId);
|
||||
|
||||
// Falls "GetSelectedBadges" auf deinem Server nicht als Klick zählt,
|
||||
// probiere stattdessen Out.RoomUserAction oder Out.LookTo
|
||||
|
||||
Console.WriteLine("[Bot] Heal sequence finished.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("[Bot] Error: " + ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Prozess freigeben
|
||||
await Task.Delay(500); // Kurzer Cooldown
|
||||
_isBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Helfer für asynchrones Senden
|
||||
private async Task SendPacketAsync(ushort header, params object[] values)
|
||||
{
|
||||
await SendToServerAsync(header, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
156
Autogate.csx
Normal file
156
Autogate.csx
Normal file
@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
public struct Point : IEquatable<Point>
|
||||
{
|
||||
public int X { get; }
|
||||
public int Y { get; }
|
||||
public Point(int x, int y) { X = x; Y = y; }
|
||||
public static implicit operator Point((int x, int y) tuple) => new Point(tuple.x, tuple.y);
|
||||
public bool Equals(Point other) => X == other.X && Y == other.Y;
|
||||
public override bool Equals(object obj) => obj is Point other && Equals(other);
|
||||
public override int GetHashCode() => HashCode.Combine(X, Y);
|
||||
public static bool operator ==(Point left, Point right) => left.Equals(right);
|
||||
public static bool operator !=(Point left, Point right) => !(left == right);
|
||||
public override string ToString() => $"({X},{Y})";
|
||||
}
|
||||
|
||||
Log("started");
|
||||
|
||||
const string FURNI_NAME_CONTAINS_TEXT = "One Way Gate";
|
||||
Regex mvRegex = new Regex(@"/mv (\d+),(\d+),([\d\.]+)/");
|
||||
|
||||
void CheckAndEnterGateGeneral(int userTileX, int userTileY)
|
||||
{
|
||||
if (FloorItems == null) return;
|
||||
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null || item.Location == null) continue;
|
||||
|
||||
string itemName = null;
|
||||
try { itemName = item.GetName(); } catch { continue; }
|
||||
|
||||
if (itemName != null && itemName.Contains(FURNI_NAME_CONTAINS_TEXT))
|
||||
{
|
||||
int gateX = item.Location.X;
|
||||
int gateY = item.Location.Y;
|
||||
int gateDir = item.Direction;
|
||||
long gateId = item.Id;
|
||||
bool shouldEnter = false;
|
||||
|
||||
if (gateDir == 0) { if (userTileX == gateX && userTileY == gateY - 1) shouldEnter = true; }
|
||||
else if (gateDir == 2) { if (userTileX == gateX + 1 && userTileY == gateY) shouldEnter = true; }
|
||||
else if (gateDir == 4) { if (userTileX == gateX && userTileY == gateY + 1) shouldEnter = true; }
|
||||
else if (gateDir == 6) { if (userTileX == gateX - 1 && userTileY == gateY) shouldEnter = true; }
|
||||
|
||||
if (shouldEnter)
|
||||
{
|
||||
Log($"User tile ({userTileX},{userTileY}) matches criteria for Gate (ID {gateId}, Name: '{itemName}') at ({gateX},{gateY} Dir:{gateDir}) via GeneralCheck. Sending packet.");
|
||||
Send(Out.EnterOneWayDoor, gateId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleUserUpdate(dynamic e)
|
||||
{
|
||||
if (Self == null) return;
|
||||
var packet = e.Packet;
|
||||
int numUpdates = packet.ReadInt();
|
||||
for (int i = 0; i < numUpdates; i++)
|
||||
{
|
||||
int entityIndex = packet.ReadInt();
|
||||
int currentX = packet.ReadInt();
|
||||
int currentY = packet.ReadInt();
|
||||
packet.ReadString();
|
||||
packet.ReadInt();
|
||||
packet.ReadInt();
|
||||
string action = packet.ReadString();
|
||||
|
||||
if (entityIndex == Self.Index)
|
||||
{
|
||||
Point tileToConsiderForGate;
|
||||
Match match = mvRegex.Match(action);
|
||||
if (match.Success)
|
||||
{
|
||||
try
|
||||
{
|
||||
int targetX = int.Parse(match.Groups[1].Value);
|
||||
int targetY = int.Parse(match.Groups[2].Value);
|
||||
tileToConsiderForGate = new Point(targetX, targetY);
|
||||
}
|
||||
catch { tileToConsiderForGate = new Point(currentX, currentY); }
|
||||
}
|
||||
else { tileToConsiderForGate = new Point(currentX, currentY); }
|
||||
CheckAndEnterGateGeneral(tileToConsiderForGate.X, tileToConsiderForGate.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleObjectUpdate(dynamic e)
|
||||
{
|
||||
if (Self == null || Self.Location == null) return;
|
||||
|
||||
var packet = e.Packet;
|
||||
|
||||
int furniIdFromPacket = packet.ReadInt();
|
||||
packet.ReadInt();
|
||||
int itemXFromPacket = packet.ReadInt();
|
||||
int itemYFromPacket = packet.ReadInt();
|
||||
int itemNewDirectionFromPacket = packet.ReadInt();
|
||||
|
||||
var itemInstance = FloorItems?.FirstOrDefault(f => f != null && f.Id == furniIdFromPacket);
|
||||
if (itemInstance != null)
|
||||
{
|
||||
string itemName = null;
|
||||
try { itemName = itemInstance.GetName(); } catch { return; }
|
||||
|
||||
if (itemName != null && itemName.Contains(FURNI_NAME_CONTAINS_TEXT))
|
||||
{
|
||||
bool shouldEnter = false;
|
||||
int userX = Self.Location.X;
|
||||
int userY = Self.Location.Y;
|
||||
|
||||
if (itemNewDirectionFromPacket == 0) { if (userX == itemXFromPacket && userY == itemYFromPacket - 1) shouldEnter = true; }
|
||||
else if (itemNewDirectionFromPacket == 2) { if (userX == itemXFromPacket + 1 && userY == itemYFromPacket) shouldEnter = true; }
|
||||
else if (itemNewDirectionFromPacket == 4) { if (userX == itemXFromPacket && userY == itemYFromPacket + 1) shouldEnter = true; }
|
||||
else if (itemNewDirectionFromPacket == 6) { if (userX == itemXFromPacket - 1 && userY == itemYFromPacket) shouldEnter = true; }
|
||||
|
||||
if (shouldEnter)
|
||||
{
|
||||
Log($"User at ({userX},{userY}) matches criteria for Gate ID {furniIdFromPacket} ({itemXFromPacket},{itemYFromPacket}) with NewDir:{itemNewDirectionFromPacket} from ObjectUpdate. Sending packet.");
|
||||
Send(Out.EnterOneWayDoor, furniIdFromPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeStateAndCheckGates(dynamic eventArgs)
|
||||
{
|
||||
if (Self != null && Self.Location != null)
|
||||
{
|
||||
Point initialPosition = new Point(Self.Location.X, Self.Location.Y);
|
||||
CheckAndEnterGateGeneral(initialPosition.X, initialPosition.Y);
|
||||
}
|
||||
}
|
||||
|
||||
OnIntercept(In["UserUpdate"], e => HandleUserUpdate(e));
|
||||
OnIntercept(In["ObjectUpdate"], e => HandleObjectUpdate(e));
|
||||
OnEnteredRoom(e => InitializeStateAndCheckGates(e));
|
||||
|
||||
if (Self != null && Self.Location != null) {
|
||||
InitializeStateAndCheckGates(null);
|
||||
}
|
||||
|
||||
while(Run)
|
||||
{
|
||||
try { }
|
||||
catch (Exception ex) { Log($"MAIN LOOP ERROR: {ex.GetType().Name} - {ex.Message}"); }
|
||||
if (!Run) break;
|
||||
Delay(30);
|
||||
}
|
||||
Log("closed");
|
||||
56
BC Items legen (boost).csx
Normal file
56
BC Items legen (boost).csx
Normal file
@ -0,0 +1,56 @@
|
||||
var furnitures = new Dictionary<int, (int x, int y)> {
|
||||
{ 880616572, (25, 13) },
|
||||
{ 880617415, (13, 13) },
|
||||
{ 880617090, (13, 29) },
|
||||
{ 880617208, (25, 29) }
|
||||
};
|
||||
|
||||
var lastusedid = -1;
|
||||
var nearfurni = false;
|
||||
|
||||
Log("Furniture proximity trigger started");
|
||||
Log($"Monitoring {furnitures.Count} furniture items");
|
||||
|
||||
bool isnearfurniture(int x, int y, out int furniid) {
|
||||
foreach (var furni in furnitures) {
|
||||
var dx = Math.Abs(x - furni.Value.x);
|
||||
var dy = Math.Abs(y - furni.Value.y);
|
||||
|
||||
if (dx <= 1 && dy <= 1) {
|
||||
furniid = furni.Key;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
furniid = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
while (Run) {
|
||||
if (Self == null || Self.Location == null) {
|
||||
Delay(100);
|
||||
continue;
|
||||
}
|
||||
|
||||
var x = Self.Location.X;
|
||||
var y = Self.Location.Y;
|
||||
|
||||
if (isnearfurniture(x, y, out int furniid)) {
|
||||
if (!nearfurni || furniid != lastusedid) {
|
||||
Send(Out["UseFurniture"], furniid, 0);
|
||||
Log($"Used furniture {furniid} from position ({x},{y})");
|
||||
lastusedid = furniid;
|
||||
nearfurni = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (nearfurni) {
|
||||
Log("Left furniture area");
|
||||
nearfurni = false;
|
||||
lastusedid = -1;
|
||||
}
|
||||
}
|
||||
|
||||
Delay(100);
|
||||
}
|
||||
|
||||
Log("Proximity trigger stopped");
|
||||
47
Cabbage Placer Loop.csx
Normal file
47
Cabbage Placer Loop.csx
Normal file
@ -0,0 +1,47 @@
|
||||
Log("=== Cabbage Placer Loop (Alle Tiles) ===");
|
||||
|
||||
// Raumgrenzen
|
||||
int minX = 4;
|
||||
int maxX = 11;
|
||||
int minY = 1;
|
||||
int maxY = 13;
|
||||
|
||||
EnsureInventory();
|
||||
Delay(100);
|
||||
|
||||
while (Run) {
|
||||
bool hasCabbage = true;
|
||||
|
||||
for (int x = minX; x <= maxX && Run && hasCabbage; x++) {
|
||||
for (int y = maxY; y >= minY && Run && hasCabbage; y--) {
|
||||
|
||||
long cabbageId = -1;
|
||||
string cabbageName = "";
|
||||
|
||||
foreach (var item in Inventory) {
|
||||
string name = item.GetName().ToLower();
|
||||
if (name.Contains("cabbage")) {
|
||||
cabbageId = item.Id;
|
||||
cabbageName = item.GetName();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cabbageId != -1) {
|
||||
Log($"Platziere: {cabbageName} auf ({x}, {y})...");
|
||||
Send(Out["PlaceObject"], $"-{cabbageId} {x} {y} 0");
|
||||
Delay(100);
|
||||
EnsureInventory();
|
||||
Delay(100);
|
||||
} else {
|
||||
Log("Kein Cabbage mehr im Inventar!");
|
||||
hasCabbage = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log("Durchlauf abgeschlossen. Starte neu...");
|
||||
Delay(500);
|
||||
EnsureInventory();
|
||||
Delay(100);
|
||||
}
|
||||
60
Catalog Exporter.csx
Normal file
60
Catalog Exporter.csx
Normal file
@ -0,0 +1,60 @@
|
||||
/// @name Catalog Scraper
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
var catalog = GetCatalog();
|
||||
var nodes = catalog.Where(x => x.Id > 0).ToArray();
|
||||
var pages = new List<(ICatalogPageNode Node, ICatalogPage Page)>();
|
||||
for (int i = 0; i < nodes.Length; i++) {
|
||||
var node = nodes[i];
|
||||
Status($"Loading page {i+1}/{nodes.Length}...");
|
||||
pages.Add((node, GetCatalogPage(node)));
|
||||
await Task.Delay(300);
|
||||
}
|
||||
|
||||
var opts = new JsonSerializerOptions {
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
static bool IsFurni(IItem it) => it.Type == ItemType.Floor || it.Type == ItemType.Wall;
|
||||
|
||||
string json = JsonSerializer.Serialize(
|
||||
pages.Select(x => new {
|
||||
PageId = x.Node.Id,
|
||||
x.Node.Name,
|
||||
x.Node.Title,
|
||||
x.Node.Icon,
|
||||
x.Node.IsVisible,
|
||||
x.Page.LayoutCode,
|
||||
x.Page.Images,
|
||||
x.Page.Texts,
|
||||
x.Page.AcceptSeasonCurrencyAsCredits,
|
||||
Offers = x.Page.Offers.Select(offer => new {
|
||||
offer.Id,
|
||||
offer.FurniLine,
|
||||
offer.PriceInCredits,
|
||||
offer.PriceInActivityPoints,
|
||||
ActivityPointType = offer.ActivityPointType.ToString(),
|
||||
offer.CanPurchaseAsGift,
|
||||
offer.CanPurchaseMultiple,
|
||||
offer.ClubLevel,
|
||||
Products = offer.Products.Select(product => new {
|
||||
Identifier = IsFurni(product) ? product.GetIdentifier() : null,
|
||||
Name = IsFurni(product) ? product.GetIdentifier() : null,
|
||||
Type = product.Type.ToString(),
|
||||
product.Variant,
|
||||
product.Count,
|
||||
product.IsLimited
|
||||
})
|
||||
})
|
||||
}),
|
||||
opts
|
||||
);
|
||||
|
||||
Directory.CreateDirectory("catalog");
|
||||
File.WriteAllText($"catalog/{DateTime.Now:yyyyMMddHHmmssfff}.json", json);
|
||||
|
||||
155
Color Matching Puzzle Auto.csx
Normal file
155
Color Matching Puzzle Auto.csx
Normal file
@ -0,0 +1,155 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
const int SOURCE_MIN_X = 18;
|
||||
const int SOURCE_MAX_X = 29;
|
||||
const int SOURCE_MIN_Y = 25;
|
||||
const int SOURCE_MAX_Y = 36;
|
||||
|
||||
const int FIELD_MIN_X = 18;
|
||||
const int FIELD_MAX_X = 29;
|
||||
const int FIELD_MIN_Y = 9;
|
||||
const int FIELD_MAX_Y = 20;
|
||||
|
||||
int GetColorNumber(string name)
|
||||
{
|
||||
var match = Regex.Match(name, @"\d+$");
|
||||
return match.Success ? int.Parse(match.Value) : -1;
|
||||
}
|
||||
|
||||
bool InField(int x, int y) => x >= FIELD_MIN_X && x <= FIELD_MAX_X && y >= FIELD_MIN_Y && y <= FIELD_MAX_Y;
|
||||
|
||||
Log("=== WAITING FOR FIELD POSITION ===");
|
||||
|
||||
while (true)
|
||||
{
|
||||
int selfX = Self.Location.X;
|
||||
int selfY = Self.Location.Y;
|
||||
|
||||
if (selfX == 14 && selfY >= 16 && selfY <= 35)
|
||||
{
|
||||
Log("Moving to (14, 15)...");
|
||||
await SendAsync(Out["MoveAvatar"], 14, 15);
|
||||
await Task.Delay(500);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (InField(selfX, selfY))
|
||||
{
|
||||
Log($"In field at ({selfX}, {selfY}), starting!");
|
||||
break;
|
||||
}
|
||||
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
Log("=== AUTO DETECTING COLORS ===");
|
||||
|
||||
var selectors = new Dictionary<int, int>();
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
string name = item.GetName();
|
||||
if (name.Contains("Cylinder Block") || name.Contains("Hemisphere Block"))
|
||||
{
|
||||
int colorNum = GetColorNumber(name);
|
||||
if (colorNum > 0 && !selectors.ContainsKey(colorNum))
|
||||
{
|
||||
selectors[colorNum] = (int)item.Id;
|
||||
Log($"Selector: {name} -> Color {colorNum}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Log($"Total selectors: {selectors.Count}");
|
||||
|
||||
var sourcePixels = new Dictionary<(int x, int y), int>();
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
int x = item.Location.X;
|
||||
int y = item.Location.Y;
|
||||
double z = item.Location.Z;
|
||||
if (x < SOURCE_MIN_X || x > SOURCE_MAX_X || y < SOURCE_MIN_Y || y > SOURCE_MAX_Y) continue;
|
||||
if (z < 0.7 || z > 1.0) continue;
|
||||
int colorNum = GetColorNumber(item.GetName());
|
||||
if (colorNum > 0)
|
||||
sourcePixels[(x, y)] = colorNum;
|
||||
}
|
||||
Log($"Source pixels: {sourcePixels.Count}");
|
||||
|
||||
int offsetY = SOURCE_MIN_Y - FIELD_MIN_Y;
|
||||
int changed = 0;
|
||||
|
||||
for (int attempt = 1; attempt <= 3; attempt++)
|
||||
{
|
||||
var fieldTiles = new Dictionary<(int x, int y), (int id, int colorNum)>();
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
int x = item.Location.X;
|
||||
int y = item.Location.Y;
|
||||
double z = item.Location.Z;
|
||||
if (x < FIELD_MIN_X || x > FIELD_MAX_X || y < FIELD_MIN_Y || y > FIELD_MAX_Y) continue;
|
||||
|
||||
int colorNum = GetColorNumber(item.GetName());
|
||||
|
||||
if (z > 0.5)
|
||||
fieldTiles[(x, y)] = ((int)item.Id, colorNum);
|
||||
else if (!fieldTiles.ContainsKey((x, y)))
|
||||
fieldTiles[(x, y)] = ((int)item.Id, -1);
|
||||
}
|
||||
|
||||
var toChange = new List<(int fieldId, int srcColor)>();
|
||||
foreach (var src in sourcePixels)
|
||||
{
|
||||
int fieldX = src.Key.x;
|
||||
int fieldY = src.Key.y - offsetY;
|
||||
int srcColor = src.Value;
|
||||
|
||||
if (!fieldTiles.TryGetValue((fieldX, fieldY), out var field)) continue;
|
||||
if (field.colorNum == srcColor) continue;
|
||||
if (!selectors.ContainsKey(srcColor)) continue;
|
||||
|
||||
toChange.Add((field.id, srcColor));
|
||||
}
|
||||
|
||||
if (toChange.Count == 0)
|
||||
{
|
||||
Log($"Attempt {attempt}: All correct!");
|
||||
break;
|
||||
}
|
||||
|
||||
Log($"Attempt {attempt}: {toChange.Count} to change");
|
||||
|
||||
var sorted = toChange.OrderBy(x => x.srcColor).ToList();
|
||||
int lastColor = -1;
|
||||
|
||||
foreach (var item in sorted)
|
||||
{
|
||||
if (item.srcColor != lastColor)
|
||||
{
|
||||
await SendAsync(Out["ClickFurni"], selectors[item.srcColor], 0);
|
||||
await Task.Delay(50);
|
||||
lastColor = item.srcColor;
|
||||
}
|
||||
|
||||
await SendAsync(Out["ClickFurni"], item.fieldId, 0);
|
||||
await Task.Delay(50);
|
||||
changed++;
|
||||
}
|
||||
|
||||
await Task.Delay(500);
|
||||
}
|
||||
|
||||
Log("=== SUBMITTING ===");
|
||||
var badge = FloorItems.FirstOrDefault(f => f.GetName() == "Badge Display Case");
|
||||
if (badge != null)
|
||||
{
|
||||
await SendAsync(Out["ClickFurni"], (int)badge.Id, 0);
|
||||
Log($"Clicked Badge Display Case (ID: {badge.Id})");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("Badge Display Case not found!");
|
||||
}
|
||||
|
||||
Log($"Done! Total changed: {changed}");
|
||||
101
Color Matching Simple.csx
Normal file
101
Color Matching Simple.csx
Normal file
@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
const int SOURCE_MIN_X = 18;
|
||||
const int SOURCE_MAX_X = 29;
|
||||
const int SOURCE_MIN_Y = 25;
|
||||
const int SOURCE_MAX_Y = 36;
|
||||
|
||||
const int FIELD_MIN_X = 18;
|
||||
const int FIELD_MAX_X = 29;
|
||||
const int FIELD_MIN_Y = 9;
|
||||
const int FIELD_MAX_Y = 20;
|
||||
|
||||
int GetColorNumber(string name)
|
||||
{
|
||||
var match = Regex.Match(name, @"\d+$");
|
||||
return match.Success ? int.Parse(match.Value) : -1;
|
||||
}
|
||||
|
||||
Log("=== AUTO DETECTING COLORS ===");
|
||||
|
||||
var selectors = new Dictionary<int, int>();
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
string name = item.GetName();
|
||||
if (name.Contains("Cylinder Block") || name.Contains("Hemisphere Block"))
|
||||
{
|
||||
int colorNum = GetColorNumber(name);
|
||||
if (colorNum > 0 && !selectors.ContainsKey(colorNum))
|
||||
{
|
||||
selectors[colorNum] = (int)item.Id;
|
||||
Log($"Selector: {name} -> Color {colorNum}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Log($"Total selectors: {selectors.Count}");
|
||||
|
||||
var sourcePixels = new Dictionary<(int x, int y), int>();
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
int x = item.Location.X;
|
||||
int y = item.Location.Y;
|
||||
double z = item.Location.Z;
|
||||
if (x < SOURCE_MIN_X || x > SOURCE_MAX_X || y < SOURCE_MIN_Y || y > SOURCE_MAX_Y) continue;
|
||||
if (z < 0.7 || z > 1.0) continue;
|
||||
int colorNum = GetColorNumber(item.GetName());
|
||||
if (colorNum > 0)
|
||||
sourcePixels[(x, y)] = colorNum;
|
||||
}
|
||||
Log($"Source pixels: {sourcePixels.Count}");
|
||||
|
||||
var fieldTiles = new Dictionary<(int x, int y), int>();
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
int x = item.Location.X;
|
||||
int y = item.Location.Y;
|
||||
double z = item.Location.Z;
|
||||
if (x < FIELD_MIN_X || x > FIELD_MAX_X || y < FIELD_MIN_Y || y > FIELD_MAX_Y) continue;
|
||||
if (z > 0.5) continue;
|
||||
fieldTiles[(x, y)] = (int)item.Id;
|
||||
}
|
||||
Log($"Field tiles: {fieldTiles.Count}");
|
||||
|
||||
int offsetY = SOURCE_MIN_Y - FIELD_MIN_Y;
|
||||
|
||||
var toChange = new List<(int fieldId, int srcColor)>();
|
||||
foreach (var src in sourcePixels)
|
||||
{
|
||||
int fieldX = src.Key.x;
|
||||
int fieldY = src.Key.y - offsetY;
|
||||
int srcColor = src.Value;
|
||||
|
||||
if (!fieldTiles.TryGetValue((fieldX, fieldY), out int fieldId)) continue;
|
||||
if (!selectors.ContainsKey(srcColor)) continue;
|
||||
|
||||
toChange.Add((fieldId, srcColor));
|
||||
}
|
||||
|
||||
var sorted = toChange.OrderBy(x => x.srcColor).ToList();
|
||||
Log($"To change: {sorted.Count}");
|
||||
|
||||
int lastColor = -1;
|
||||
int changed = 0;
|
||||
|
||||
foreach (var item in sorted)
|
||||
{
|
||||
if (item.srcColor != lastColor)
|
||||
{
|
||||
await SendAsync(Out["ClickFurni"], selectors[item.srcColor], 0);
|
||||
await Task.Delay(50);
|
||||
lastColor = item.srcColor;
|
||||
}
|
||||
|
||||
await SendAsync(Out["ClickFurni"], item.fieldId, 0);
|
||||
await Task.Delay(50);
|
||||
changed++;
|
||||
}
|
||||
|
||||
Log($"Done! Changed: {changed}");
|
||||
635
Color Pattern Walker Fixed.csx
Normal file
635
Color Pattern Walker Fixed.csx
Normal file
@ -0,0 +1,635 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
class Cell
|
||||
{
|
||||
public long Id;
|
||||
public int X;
|
||||
public int Y;
|
||||
public int State;
|
||||
public int Kind;
|
||||
public double Z;
|
||||
}
|
||||
|
||||
const int SOL_MIN_X = 4;
|
||||
const int SOL_MAX_X = 9;
|
||||
const int SOL_MIN_Y = 1;
|
||||
const int SOL_MAX_Y = 8;
|
||||
|
||||
const int PLAY_MIN_X = 8;
|
||||
const int PLAY_MAX_X = 13;
|
||||
const int PLAY_MIN_Y = 14;
|
||||
const int PLAY_MAX_Y = 21;
|
||||
|
||||
const int SPAWN_X = 13;
|
||||
const int SPAWN_Y = 13;
|
||||
const int SPAWN_WAIT_MS = 180000;
|
||||
|
||||
const int FORCED_CYCLE = 5;
|
||||
const bool ASSUME_START_ALL_ZERO = true;
|
||||
|
||||
const int STEP_WAIT_MS = 420;
|
||||
const int MOVE_COMMAND_INTERVAL_MS = 70;
|
||||
const int MOVE_SETTLE_MS = 0;
|
||||
const int PERIODIC_SYNC_EVERY_STEPS = 12;
|
||||
const int PATH_BURST_MAX_STEPS = 10;
|
||||
const int MAX_STEPS = 5000;
|
||||
|
||||
string K(int x, int y) => x + "," + y;
|
||||
|
||||
int GetKind(dynamic item)
|
||||
{
|
||||
try { return (int)item.Kind; }
|
||||
catch { return -1; }
|
||||
}
|
||||
|
||||
int GetState(dynamic item)
|
||||
{
|
||||
try { return int.Parse(item.State?.ToString() ?? "0"); }
|
||||
catch { return 0; }
|
||||
}
|
||||
|
||||
string GetNameSafe(dynamic item)
|
||||
{
|
||||
try
|
||||
{
|
||||
string n = item.GetName();
|
||||
return string.IsNullOrWhiteSpace(n) ? "<unknown>" : n;
|
||||
}
|
||||
catch { return "<unknown>"; }
|
||||
}
|
||||
|
||||
bool InRect(int x, int y, int minX, int maxX, int minY, int maxY)
|
||||
{
|
||||
return x >= minX && x <= maxX && y >= minY && y <= maxY;
|
||||
}
|
||||
|
||||
bool InPlay(int x, int y)
|
||||
{
|
||||
return InRect(x, y, PLAY_MIN_X, PLAY_MAX_X, PLAY_MIN_Y, PLAY_MAX_Y);
|
||||
}
|
||||
|
||||
void FullRoomScanLog()
|
||||
{
|
||||
var all = new List<dynamic>();
|
||||
foreach (var it in FloorItems)
|
||||
{
|
||||
if (it == null) continue;
|
||||
all.Add(it);
|
||||
}
|
||||
|
||||
Log("=== Full Room Scan ===");
|
||||
Log($"FloorItems total: {all.Count}");
|
||||
|
||||
var byKind = all.GroupBy(x => GetKind(x))
|
||||
.Select(g => new {
|
||||
Kind = g.Key,
|
||||
Count = g.Count(),
|
||||
States = string.Join(",", g.Select(x => GetState(x)).Distinct().OrderBy(x => x)),
|
||||
Name = g.Select(x => GetNameSafe(x)).FirstOrDefault()
|
||||
})
|
||||
.OrderByDescending(x => x.Count)
|
||||
.Take(25)
|
||||
.ToList();
|
||||
|
||||
foreach (var k in byKind)
|
||||
Log($"Kind {k.Kind} x{k.Count} states[{k.States}] name={k.Name}");
|
||||
}
|
||||
|
||||
bool WaitForSpawn()
|
||||
{
|
||||
Log($"Waiting for round spawn on {SPAWN_X}:{SPAWN_Y}...");
|
||||
int elapsed = 0;
|
||||
while (elapsed < SPAWN_WAIT_MS)
|
||||
{
|
||||
if (Self != null && Self.Location != null && Self.Location.X == SPAWN_X && Self.Location.Y == SPAWN_Y)
|
||||
{
|
||||
Log("Spawn detected, starting solver.");
|
||||
Delay(300);
|
||||
return true;
|
||||
}
|
||||
Delay(200);
|
||||
elapsed += 200;
|
||||
}
|
||||
Log("Spawn timeout. Starting anyway.");
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Cell> CollectCellsInRect(int minX, int maxX, int minY, int maxY)
|
||||
{
|
||||
var raw = new List<Cell>();
|
||||
foreach (var it in FloorItems)
|
||||
{
|
||||
if (it == null) continue;
|
||||
int x = it.Location.X;
|
||||
int y = it.Location.Y;
|
||||
if (!InRect(x, y, minX, maxX, minY, maxY)) continue;
|
||||
raw.Add(new Cell {
|
||||
Id = it.Id,
|
||||
X = x,
|
||||
Y = y,
|
||||
State = GetState(it),
|
||||
Kind = GetKind(it),
|
||||
Z = it.Location.Z
|
||||
});
|
||||
}
|
||||
|
||||
if (raw.Count == 0) return new List<Cell>();
|
||||
|
||||
int targetCount = (maxX - minX + 1) * (maxY - minY + 1);
|
||||
|
||||
var bestKind = raw.GroupBy(c => c.Kind)
|
||||
.Select(g => new {
|
||||
Kind = g.Key,
|
||||
CoordCount = g.Select(c => K(c.X, c.Y)).Distinct().Count(),
|
||||
Count = g.Count()
|
||||
})
|
||||
.OrderByDescending(x => x.CoordCount)
|
||||
.ThenByDescending(x => x.Count)
|
||||
.First();
|
||||
|
||||
var cellsOfKind = raw.Where(c => c.Kind == bestKind.Kind).ToList();
|
||||
|
||||
var bestPerCoord = new List<Cell>();
|
||||
foreach (var g in cellsOfKind.GroupBy(c => K(c.X, c.Y)))
|
||||
{
|
||||
var top = g.OrderByDescending(c => c.Z).First();
|
||||
bestPerCoord.Add(top);
|
||||
}
|
||||
|
||||
Log($"Rect X[{minX}-{maxX}] Y[{minY}-{maxY}] -> kind {bestKind.Kind}, coords {bestPerCoord.Count}/{targetCount}");
|
||||
return bestPerCoord;
|
||||
}
|
||||
|
||||
Dictionary<string, Cell> IndexCells(List<Cell> cells)
|
||||
{
|
||||
var d = new Dictionary<string, Cell>();
|
||||
foreach (var c in cells) d[K(c.X, c.Y)] = c;
|
||||
return d;
|
||||
}
|
||||
|
||||
Dictionary<long, int> ReadCurrentPlayStates(HashSet<long> ids)
|
||||
{
|
||||
var d = new Dictionary<long, int>();
|
||||
foreach (var it in FloorItems)
|
||||
{
|
||||
if (it == null) continue;
|
||||
long id = it.Id;
|
||||
if (!ids.Contains(id)) continue;
|
||||
d[id] = GetState(it);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
int Need(int current, int target, int cycle)
|
||||
{
|
||||
int d = (target - current) % cycle;
|
||||
if (d < 0) d += cycle;
|
||||
return d;
|
||||
}
|
||||
|
||||
int Objective(Dictionary<long, int> cur, Dictionary<long, int> target, int cycle)
|
||||
{
|
||||
int sum = 0;
|
||||
foreach (var kv in target)
|
||||
{
|
||||
if (!cur.ContainsKey(kv.Key)) continue;
|
||||
sum += Need(cur[kv.Key], kv.Value, cycle);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
int HardObjectiveFromRoom(List<Cell> playCells, Dictionary<long, int> targetByPlayId, int cycle, int[] needs)
|
||||
{
|
||||
var ids = new HashSet<long>(targetByPlayId.Keys);
|
||||
var cur = ReadCurrentPlayStates(ids);
|
||||
for (int i = 0; i < playCells.Count; i++)
|
||||
{
|
||||
long id = playCells[i].Id;
|
||||
int val = cur.ContainsKey(id) ? cur[id] : 0;
|
||||
needs[i] = Need(val, targetByPlayId[id], cycle);
|
||||
}
|
||||
return needs.Sum();
|
||||
}
|
||||
|
||||
int ApplyNeedStep(int[] needs, int idx, int cycle)
|
||||
{
|
||||
int d = needs[idx];
|
||||
if (d > 0)
|
||||
{
|
||||
needs[idx] = d - 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
needs[idx] = cycle - 1;
|
||||
return cycle - 1;
|
||||
}
|
||||
|
||||
bool WaitUntilAt(int tx, int ty)
|
||||
{
|
||||
int elapsed = 0;
|
||||
while (elapsed < STEP_WAIT_MS)
|
||||
{
|
||||
if (Self != null && Self.Location != null && Self.Location.X == tx && Self.Location.Y == ty)
|
||||
return true;
|
||||
Delay(40);
|
||||
elapsed += 40;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
List<(int x, int y)> Neigh4(int x, int y)
|
||||
{
|
||||
var list = new List<(int, int)> {
|
||||
(x + 1, y),
|
||||
(x - 1, y),
|
||||
(x, y + 1),
|
||||
(x, y - 1),
|
||||
(x + 1, y + 1),
|
||||
(x + 1, y - 1),
|
||||
(x - 1, y + 1),
|
||||
(x - 1, y - 1)
|
||||
};
|
||||
return list.Where(p => InPlay(p.Item1, p.Item2)).ToList();
|
||||
}
|
||||
|
||||
int Dist(int x1, int y1, int x2, int y2)
|
||||
{
|
||||
return Math.Abs(x1 - x2) + Math.Abs(y1 - y2);
|
||||
}
|
||||
|
||||
int ReadSelfX(int fallback)
|
||||
{
|
||||
try { return Self.Location.X; }
|
||||
catch { return fallback; }
|
||||
}
|
||||
|
||||
int ReadSelfY(int fallback)
|
||||
{
|
||||
try { return Self.Location.Y; }
|
||||
catch { return fallback; }
|
||||
}
|
||||
|
||||
DateTime _lastMoveCmd = DateTime.MinValue;
|
||||
void FastMove(int x, int y)
|
||||
{
|
||||
int since = (int)(DateTime.UtcNow - _lastMoveCmd).TotalMilliseconds;
|
||||
if (since < MOVE_COMMAND_INTERVAL_MS)
|
||||
Delay(MOVE_COMMAND_INTERVAL_MS - since);
|
||||
Move(x, y);
|
||||
_lastMoveCmd = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
(int nx, int ny)? NextStepToTarget(int sx, int sy, int tx, int ty)
|
||||
{
|
||||
(int x, int y) start = (sx, sy);
|
||||
(int x, int y) goal = (tx, ty);
|
||||
if (start == goal) return null;
|
||||
|
||||
var q = new Queue<(int x, int y)>();
|
||||
var vis = new HashSet<string>();
|
||||
var prev = new Dictionary<string, (int x, int y)>();
|
||||
q.Enqueue(start);
|
||||
vis.Add(K(start.x, start.y));
|
||||
|
||||
while (q.Count > 0)
|
||||
{
|
||||
var cur = q.Dequeue();
|
||||
foreach (var n in Neigh4(cur.x, cur.y))
|
||||
{
|
||||
string nk = K(n.x, n.y);
|
||||
if (vis.Contains(nk)) continue;
|
||||
vis.Add(nk);
|
||||
prev[nk] = cur;
|
||||
if (n == goal)
|
||||
{
|
||||
var node = goal;
|
||||
while (true)
|
||||
{
|
||||
var pk = K(node.x, node.y);
|
||||
var pnode = prev[pk];
|
||||
if (pnode == start) return node;
|
||||
node = pnode;
|
||||
}
|
||||
}
|
||||
q.Enqueue(n);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
List<(int x, int y)> BuildPathToTarget(int sx, int sy, int tx, int ty)
|
||||
{
|
||||
(int x, int y) start = (sx, sy);
|
||||
(int x, int y) goal = (tx, ty);
|
||||
var empty = new List<(int x, int y)>();
|
||||
if (start == goal) return empty;
|
||||
|
||||
var q = new Queue<(int x, int y)>();
|
||||
var vis = new HashSet<string>();
|
||||
var prev = new Dictionary<string, (int x, int y)>();
|
||||
q.Enqueue(start);
|
||||
vis.Add(K(start.x, start.y));
|
||||
|
||||
while (q.Count > 0)
|
||||
{
|
||||
var cur = q.Dequeue();
|
||||
foreach (var n in Neigh4(cur.x, cur.y))
|
||||
{
|
||||
string nk = K(n.x, n.y);
|
||||
if (vis.Contains(nk)) continue;
|
||||
vis.Add(nk);
|
||||
prev[nk] = cur;
|
||||
if (n == goal)
|
||||
{
|
||||
var rev = new List<(int x, int y)>();
|
||||
var node = goal;
|
||||
while (node != start)
|
||||
{
|
||||
rev.Add(node);
|
||||
node = prev[K(node.x, node.y)];
|
||||
}
|
||||
rev.Reverse();
|
||||
return rev;
|
||||
}
|
||||
q.Enqueue(n);
|
||||
}
|
||||
}
|
||||
return empty;
|
||||
}
|
||||
|
||||
Log("=== Color Pattern Walker Solver (fixed bounds) ===");
|
||||
WaitForSpawn();
|
||||
FullRoomScanLog();
|
||||
|
||||
var solCells = CollectCellsInRect(SOL_MIN_X, SOL_MAX_X, SOL_MIN_Y, SOL_MAX_Y);
|
||||
var playCells = CollectCellsInRect(PLAY_MIN_X, PLAY_MAX_X, PLAY_MIN_Y, PLAY_MAX_Y);
|
||||
|
||||
int expectedSol = (SOL_MAX_X - SOL_MIN_X + 1) * (SOL_MAX_Y - SOL_MIN_Y + 1);
|
||||
int expectedPlay = (PLAY_MAX_X - PLAY_MIN_X + 1) * (PLAY_MAX_Y - PLAY_MIN_Y + 1);
|
||||
|
||||
if (solCells.Count < expectedSol || playCells.Count < expectedPlay)
|
||||
{
|
||||
Log($"ERROR: Board incomplete. Solution {solCells.Count}/{expectedSol}, Play {playCells.Count}/{expectedPlay}");
|
||||
return;
|
||||
}
|
||||
|
||||
var solMap = IndexCells(solCells);
|
||||
var playMap = IndexCells(playCells);
|
||||
|
||||
var targetByPlayId = new Dictionary<long, int>();
|
||||
foreach (var p in playCells)
|
||||
{
|
||||
int sx = p.X - 4;
|
||||
int sy = p.Y - 13;
|
||||
string sk = K(sx, sy);
|
||||
if (!solMap.ContainsKey(sk)) continue;
|
||||
targetByPlayId[p.Id] = solMap[sk].State;
|
||||
}
|
||||
|
||||
if (targetByPlayId.Count != expectedPlay)
|
||||
{
|
||||
Log($"ERROR: Could not map all play cells to solution cells ({targetByPlayId.Count}/{expectedPlay}).");
|
||||
return;
|
||||
}
|
||||
|
||||
var ids = new HashSet<long>(targetByPlayId.Keys);
|
||||
var cur = new Dictionary<long, int>();
|
||||
if (ASSUME_START_ALL_ZERO)
|
||||
{
|
||||
foreach (var id in ids) cur[id] = 0;
|
||||
Log("Using round-start baseline: all play tiles = state 0.");
|
||||
}
|
||||
else
|
||||
{
|
||||
cur = ReadCurrentPlayStates(ids);
|
||||
if (cur.Count != expectedPlay)
|
||||
{
|
||||
Log($"ERROR: Could not read all current play states ({cur.Count}/{expectedPlay}).");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int cycle = FORCED_CYCLE;
|
||||
if (cycle < 2) cycle = 5;
|
||||
|
||||
Log($"State cycle: {cycle}");
|
||||
|
||||
var coordToIdx = new Dictionary<string, int>();
|
||||
var idToIdx = new Dictionary<long, int>();
|
||||
for (int i = 0; i < playCells.Count; i++)
|
||||
{
|
||||
var c = playCells[i];
|
||||
coordToIdx[K(c.X, c.Y)] = i;
|
||||
idToIdx[c.Id] = i;
|
||||
}
|
||||
|
||||
int[] needs = new int[playCells.Count];
|
||||
for (int i = 0; i < playCells.Count; i++)
|
||||
{
|
||||
long id = playCells[i].Id;
|
||||
needs[i] = Need(cur[id], targetByPlayId[id], cycle);
|
||||
}
|
||||
|
||||
int obj = needs.Sum();
|
||||
Log($"Initial objective: {obj}");
|
||||
if (obj == 0) { Log("Already solved."); return; }
|
||||
|
||||
if (Self == null || Self.Location == null)
|
||||
{
|
||||
Log("ERROR: No self location.");
|
||||
return;
|
||||
}
|
||||
|
||||
int cx = Self.Location.X;
|
||||
int cy = Self.Location.Y;
|
||||
|
||||
if (!InPlay(cx, cy))
|
||||
{
|
||||
var bestEntry = playCells
|
||||
.OrderBy(c => Dist(cx, cy, c.X, c.Y))
|
||||
.First();
|
||||
FastMove(bestEntry.X, bestEntry.Y);
|
||||
WaitUntilAt(bestEntry.X, bestEntry.Y);
|
||||
cur = ReadCurrentPlayStates(ids);
|
||||
for (int i = 0; i < playCells.Count; i++)
|
||||
{
|
||||
long id = playCells[i].Id;
|
||||
needs[i] = Need(cur[id], targetByPlayId[id], cycle);
|
||||
}
|
||||
cx = ReadSelfX(bestEntry.X);
|
||||
cy = ReadSelfY(bestEntry.Y);
|
||||
obj = needs.Sum();
|
||||
Log($"After entry objective: {obj}");
|
||||
}
|
||||
|
||||
int stagnation = 0;
|
||||
int prevX = -999;
|
||||
int prevY = -999;
|
||||
int sinceResync = 0;
|
||||
var burstPath = new List<(int x, int y)>();
|
||||
int burstIndex = 0;
|
||||
|
||||
for (int step = 1; step <= MAX_STEPS; step++)
|
||||
{
|
||||
if (!InPlay(cx, cy))
|
||||
{
|
||||
Log("WARN: Left play field unexpectedly, moving back.");
|
||||
var back = playCells.OrderBy(c => Dist(cx, cy, c.X, c.Y)).First();
|
||||
FastMove(back.X, back.Y);
|
||||
WaitUntilAt(back.X, back.Y);
|
||||
cx = ReadSelfX(back.X);
|
||||
cy = ReadSelfY(back.Y);
|
||||
cur = ReadCurrentPlayStates(ids);
|
||||
for (int i = 0; i < playCells.Count; i++)
|
||||
{
|
||||
long id = playCells[i].Id;
|
||||
needs[i] = Need(cur[id], targetByPlayId[id], cycle);
|
||||
}
|
||||
obj = needs.Sum();
|
||||
sinceResync = 0;
|
||||
burstPath.Clear();
|
||||
burstIndex = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (obj == 0)
|
||||
{
|
||||
obj = HardObjectiveFromRoom(playCells, targetByPlayId, cycle, needs);
|
||||
int standNeed = 999;
|
||||
if (InPlay(cx, cy) && coordToIdx.ContainsKey(K(cx, cy)))
|
||||
standNeed = needs[coordToIdx[K(cx, cy)]];
|
||||
|
||||
if (obj == 0 && standNeed == 0)
|
||||
{
|
||||
Log("=== Done: bottom matches solution (hard check OK) ===");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var neighbors = Neigh4(cx, cy);
|
||||
if (neighbors.Count == 0)
|
||||
{
|
||||
Log("ERROR: No neighbors on play field.");
|
||||
return;
|
||||
}
|
||||
|
||||
int unresolved = 0;
|
||||
for (int i = 0; i < needs.Length; i++)
|
||||
if (needs[i] > 0) unresolved++;
|
||||
|
||||
if (burstIndex >= burstPath.Count)
|
||||
{
|
||||
var needy = playCells
|
||||
.Select(c => new {
|
||||
Cell = c,
|
||||
Need = needs[idToIdx[c.Id]],
|
||||
D = Dist(cx, cy, c.X, c.Y)
|
||||
})
|
||||
.Where(x => x.Need > 0)
|
||||
.OrderByDescending(x => x.Need)
|
||||
.ThenBy(x => x.D)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (needy != null)
|
||||
{
|
||||
var path = BuildPathToTarget(cx, cy, needy.Cell.X, needy.Cell.Y);
|
||||
if (path.Count > 0)
|
||||
{
|
||||
burstPath = path.Take(PATH_BURST_MAX_STEPS).ToList();
|
||||
burstIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (burstIndex >= burstPath.Count)
|
||||
{
|
||||
var fallback = neighbors
|
||||
.Select(n => new {
|
||||
N = n,
|
||||
Need = needs[coordToIdx[K(n.x, n.y)]],
|
||||
Back = (n.x == prevX && n.y == prevY) ? 1 : 0
|
||||
})
|
||||
.OrderByDescending(x => x.Need)
|
||||
.ThenBy(x => x.Back)
|
||||
.First();
|
||||
burstPath = new List<(int x, int y)> { fallback.N };
|
||||
burstIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
(int x, int y) bestN = burstPath[burstIndex];
|
||||
burstIndex++;
|
||||
|
||||
int idxChosen = coordToIdx[K(bestN.x, bestN.y)];
|
||||
if (needs[idxChosen] == 0 && unresolved <= 8)
|
||||
{
|
||||
stagnation++;
|
||||
}
|
||||
else
|
||||
{
|
||||
stagnation = 0;
|
||||
}
|
||||
|
||||
if (stagnation >= 12)
|
||||
{
|
||||
burstPath.Clear();
|
||||
burstIndex = 0;
|
||||
stagnation = 0;
|
||||
}
|
||||
|
||||
prevX = cx;
|
||||
prevY = cy;
|
||||
int oldObj = obj;
|
||||
|
||||
FastMove(bestN.x, bestN.y);
|
||||
if (MOVE_SETTLE_MS > 0) Delay(MOVE_SETTLE_MS);
|
||||
|
||||
// Predictive advance: keep running without stop-go per step.
|
||||
cx = bestN.x;
|
||||
cy = bestN.y;
|
||||
|
||||
if (InPlay(cx, cy) && coordToIdx.ContainsKey(K(cx, cy)))
|
||||
{
|
||||
int landedIdx = coordToIdx[K(cx, cy)];
|
||||
obj += ApplyNeedStep(needs, landedIdx, cycle);
|
||||
}
|
||||
else
|
||||
{
|
||||
sinceResync = 20;
|
||||
}
|
||||
|
||||
sinceResync++;
|
||||
if (sinceResync >= PERIODIC_SYNC_EVERY_STEPS || stagnation >= 8)
|
||||
{
|
||||
Delay(120);
|
||||
cx = ReadSelfX(cx);
|
||||
cy = ReadSelfY(cy);
|
||||
cur = ReadCurrentPlayStates(ids);
|
||||
for (int i = 0; i < playCells.Count; i++)
|
||||
{
|
||||
long id = playCells[i].Id;
|
||||
needs[i] = Need(cur[id], targetByPlayId[id], cycle);
|
||||
}
|
||||
obj = needs.Sum();
|
||||
sinceResync = 0;
|
||||
}
|
||||
|
||||
int newObj = obj;
|
||||
Log($"[{step}] ({cx},{cy}) objective {oldObj} -> {newObj}");
|
||||
}
|
||||
|
||||
obj = HardObjectiveFromRoom(playCells, targetByPlayId, cycle, needs);
|
||||
int finalStandNeed = 999;
|
||||
if (Self != null && Self.Location != null)
|
||||
{
|
||||
int fx = ReadSelfX(-9999);
|
||||
int fy = ReadSelfY(-9999);
|
||||
if (InPlay(fx, fy) && coordToIdx.ContainsKey(K(fx, fy)))
|
||||
finalStandNeed = needs[coordToIdx[K(fx, fy)]];
|
||||
}
|
||||
|
||||
if (obj == 0 && finalStandNeed == 0)
|
||||
Log("=== Done: bottom matches solution (hard check OK) ===");
|
||||
else
|
||||
Log($"Stopped after max steps. Remaining objective: {obj}");
|
||||
518
Color Pattern Walker Solver.csx
Normal file
518
Color Pattern Walker Solver.csx
Normal file
@ -0,0 +1,518 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
class Tile
|
||||
{
|
||||
public long Id;
|
||||
public int Kind;
|
||||
public string Name;
|
||||
public int X;
|
||||
public int Y;
|
||||
public double Z;
|
||||
public int State;
|
||||
}
|
||||
|
||||
class Cluster
|
||||
{
|
||||
public int Kind;
|
||||
public List<Tile> Tiles = new List<Tile>();
|
||||
public double AvgZ;
|
||||
public HashSet<int> StateSet = new HashSet<int>();
|
||||
public double CenterX;
|
||||
public double CenterY;
|
||||
}
|
||||
|
||||
const int SPAWN_X = 13;
|
||||
const int SPAWN_Y = 13;
|
||||
const bool WAIT_FOR_SPAWN_START = true;
|
||||
const int SPAWN_WAIT_TIMEOUT_MS = 180000;
|
||||
|
||||
int GetState(dynamic item)
|
||||
{
|
||||
try { return int.Parse(item.State?.ToString() ?? "0"); }
|
||||
catch { return 0; }
|
||||
}
|
||||
|
||||
int GetKind(dynamic item)
|
||||
{
|
||||
try { return (int)item.Kind; }
|
||||
catch { return -1; }
|
||||
}
|
||||
|
||||
string GetNameSafe(dynamic item)
|
||||
{
|
||||
try
|
||||
{
|
||||
string n = item.GetName();
|
||||
return string.IsNullOrWhiteSpace(n) ? "<unknown>" : n;
|
||||
}
|
||||
catch { return "<unknown>"; }
|
||||
}
|
||||
|
||||
List<Tile> ReadAllFloorTiles()
|
||||
{
|
||||
var list = new List<Tile>();
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
list.Add(new Tile {
|
||||
Id = item.Id,
|
||||
Kind = GetKind(item),
|
||||
Name = GetNameSafe(item),
|
||||
X = item.Location.X,
|
||||
Y = item.Location.Y,
|
||||
Z = item.Location.Z,
|
||||
State = GetState(item)
|
||||
});
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
string PosKey(int x, int y) => x + "," + y;
|
||||
|
||||
List<Cluster> BuildClusters(List<Tile> tilesOfKind)
|
||||
{
|
||||
var byPos = tilesOfKind.ToDictionary(t => PosKey(t.X, t.Y), t => t);
|
||||
var visited = new HashSet<string>();
|
||||
var clusters = new List<Cluster>();
|
||||
|
||||
int[] d = { -1, 0, 1 };
|
||||
foreach (var t in tilesOfKind)
|
||||
{
|
||||
string start = PosKey(t.X, t.Y);
|
||||
if (visited.Contains(start)) continue;
|
||||
|
||||
var q = new Queue<Tile>();
|
||||
var c = new Cluster { Kind = t.Kind };
|
||||
q.Enqueue(t);
|
||||
visited.Add(start);
|
||||
|
||||
while (q.Count > 0)
|
||||
{
|
||||
var cur = q.Dequeue();
|
||||
c.Tiles.Add(cur);
|
||||
c.StateSet.Add(cur.State);
|
||||
|
||||
foreach (int dx in d)
|
||||
foreach (int dy in d)
|
||||
{
|
||||
if (dx == 0 && dy == 0) continue;
|
||||
string nk = PosKey(cur.X + dx, cur.Y + dy);
|
||||
if (visited.Contains(nk)) continue;
|
||||
if (!byPos.ContainsKey(nk)) continue;
|
||||
visited.Add(nk);
|
||||
q.Enqueue(byPos[nk]);
|
||||
}
|
||||
}
|
||||
|
||||
c.AvgZ = c.Tiles.Count == 0 ? 0.0 : c.Tiles.Average(x => x.Z);
|
||||
c.CenterX = c.Tiles.Count == 0 ? 0.0 : c.Tiles.Average(x => x.X);
|
||||
c.CenterY = c.Tiles.Count == 0 ? 0.0 : c.Tiles.Average(x => x.Y);
|
||||
clusters.Add(c);
|
||||
}
|
||||
|
||||
return clusters;
|
||||
}
|
||||
|
||||
void LogRoomScan(List<Tile> all)
|
||||
{
|
||||
Log("=== Full Room Scan ===");
|
||||
Log($"FloorItems total: {all.Count}");
|
||||
|
||||
var byKind = all.GroupBy(x => x.Kind)
|
||||
.Select(g => new {
|
||||
Kind = g.Key,
|
||||
Count = g.Count(),
|
||||
Names = g.Select(x => x.Name).Distinct().Take(3).ToArray(),
|
||||
MinX = g.Min(x => x.X), MaxX = g.Max(x => x.X),
|
||||
MinY = g.Min(x => x.Y), MaxY = g.Max(x => x.Y),
|
||||
MinZ = g.Min(x => x.Z), MaxZ = g.Max(x => x.Z),
|
||||
States = string.Join(",", g.Select(x => x.State).Distinct().OrderBy(x => x))
|
||||
})
|
||||
.OrderByDescending(x => x.Count)
|
||||
.Take(30)
|
||||
.ToList();
|
||||
|
||||
foreach (var k in byKind)
|
||||
{
|
||||
Log($"Kind {k.Kind} x{k.Count} | states [{k.States}] | bbox X[{k.MinX}-{k.MaxX}] Y[{k.MinY}-{k.MaxY}] Z[{k.MinZ:F2}-{k.MaxZ:F2}] | name {string.Join(" / ", k.Names)}");
|
||||
}
|
||||
}
|
||||
|
||||
double DistD(double x1, double y1, double x2, double y2)
|
||||
{
|
||||
double dx = x1 - x2;
|
||||
double dy = y1 - y2;
|
||||
return Math.Sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
bool TryDetectBoards(List<Tile> all, int spawnX, int spawnY, out Cluster top, out Cluster bottom)
|
||||
{
|
||||
top = null;
|
||||
bottom = null;
|
||||
|
||||
var byKind = all.GroupBy(x => x.Kind).ToList();
|
||||
Cluster bestA = null, bestB = null;
|
||||
int bestScore = -1;
|
||||
|
||||
foreach (var g in byKind)
|
||||
{
|
||||
var clusters = BuildClusters(g.ToList())
|
||||
.Where(c => c.Tiles.Count >= 9)
|
||||
.OrderByDescending(c => c.Tiles.Count)
|
||||
.ToList();
|
||||
if (clusters.Count < 2) continue;
|
||||
|
||||
for (int i = 0; i < clusters.Count; i++)
|
||||
{
|
||||
for (int j = i + 1; j < clusters.Count; j++)
|
||||
{
|
||||
var a = clusters[i];
|
||||
var b = clusters[j];
|
||||
int minCount = Math.Min(a.Tiles.Count, b.Tiles.Count);
|
||||
int diff = Math.Abs(a.Tiles.Count - b.Tiles.Count);
|
||||
int score = (minCount * 10) - diff;
|
||||
if (a.StateSet.Count > 1) score += 5;
|
||||
if (b.StateSet.Count > 1) score += 5;
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestA = a;
|
||||
bestB = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestA == null || bestB == null) return false;
|
||||
|
||||
// Prefer the board whose center is closer to your spawn as bottom/play board.
|
||||
double da = DistD(bestA.CenterX, bestA.CenterY, spawnX, spawnY);
|
||||
double db = DistD(bestB.CenterX, bestB.CenterY, spawnX, spawnY);
|
||||
if (Math.Abs(da - db) >= 2.0)
|
||||
{
|
||||
if (da <= db) { bottom = bestA; top = bestB; }
|
||||
else { bottom = bestB; top = bestA; }
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback if both are similarly far away.
|
||||
if (bestA.AvgZ >= bestB.AvgZ) { top = bestA; bottom = bestB; }
|
||||
else { top = bestB; bottom = bestA; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WaitForRoundSpawn(int spawnX, int spawnY, int timeoutMs)
|
||||
{
|
||||
Log($"Waiting for round start spawn at {spawnX}:{spawnY}...");
|
||||
int elapsed = 0;
|
||||
while (elapsed < timeoutMs)
|
||||
{
|
||||
if (Self != null && Self.Location != null)
|
||||
{
|
||||
if (Self.Location.X == spawnX && Self.Location.Y == spawnY)
|
||||
{
|
||||
Log("Spawn detected. Round started.");
|
||||
Delay(350);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Delay(200);
|
||||
elapsed += 200;
|
||||
}
|
||||
|
||||
Log("Spawn wait timeout reached. Continuing anyway.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Dictionary<string, Tile> BuildIndexedMap(Cluster c, out int w, out int h)
|
||||
{
|
||||
var xs = c.Tiles.Select(t => t.X).Distinct().OrderBy(x => x).ToList();
|
||||
var ys = c.Tiles.Select(t => t.Y).Distinct().OrderBy(y => y).ToList();
|
||||
w = xs.Count;
|
||||
h = ys.Count;
|
||||
|
||||
var xToI = new Dictionary<int, int>();
|
||||
var yToI = new Dictionary<int, int>();
|
||||
for (int i = 0; i < xs.Count; i++) xToI[xs[i]] = i;
|
||||
for (int i = 0; i < ys.Count; i++) yToI[ys[i]] = i;
|
||||
|
||||
var map = new Dictionary<string, Tile>();
|
||||
foreach (var t in c.Tiles)
|
||||
{
|
||||
int xi = xToI[t.X];
|
||||
int yi = yToI[t.Y];
|
||||
map[PosKey(xi, yi)] = t;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
bool TryBuildTargetMap(Cluster top, Cluster bottom, out Dictionary<long, int> targetByBottomId, out List<Tile> bottomTilesOrdered)
|
||||
{
|
||||
targetByBottomId = new Dictionary<long, int>();
|
||||
bottomTilesOrdered = new List<Tile>();
|
||||
|
||||
int bw, bh, tw, th;
|
||||
var bMap = BuildIndexedMap(bottom, out bw, out bh);
|
||||
var tMap = BuildIndexedMap(top, out tw, out th);
|
||||
|
||||
if (bw != tw || bh != th)
|
||||
{
|
||||
Log($"WARN: Different board dimensions: top {tw}x{th}, bottom {bw}x{bh}. Trying overlap map.");
|
||||
}
|
||||
|
||||
int w = Math.Min(bw, tw);
|
||||
int h = Math.Min(bh, th);
|
||||
if (w <= 0 || h <= 0) return false;
|
||||
|
||||
var transforms = new List<Func<int, int, (int x, int y)>>();
|
||||
transforms.Add((x, y) => (x, y));
|
||||
transforms.Add((x, y) => (w - 1 - x, y));
|
||||
transforms.Add((x, y) => (x, h - 1 - y));
|
||||
transforms.Add((x, y) => (w - 1 - x, h - 1 - y));
|
||||
if (w == h)
|
||||
{
|
||||
transforms.Add((x, y) => (y, x));
|
||||
transforms.Add((x, y) => (w - 1 - y, x));
|
||||
transforms.Add((x, y) => (y, h - 1 - x));
|
||||
transforms.Add((x, y) => (w - 1 - y, h - 1 - x));
|
||||
}
|
||||
|
||||
int bestIdx = 0;
|
||||
int bestMatches = -1;
|
||||
|
||||
for (int ti = 0; ti < transforms.Count; ti++)
|
||||
{
|
||||
int matches = 0;
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
var bKey = PosKey(x, y);
|
||||
if (!bMap.ContainsKey(bKey)) continue;
|
||||
var tr = transforms[ti](x, y);
|
||||
var tKey = PosKey(tr.x, tr.y);
|
||||
if (tMap.ContainsKey(tKey)) matches++;
|
||||
}
|
||||
}
|
||||
if (matches > bestMatches)
|
||||
{
|
||||
bestMatches = matches;
|
||||
bestIdx = ti;
|
||||
}
|
||||
}
|
||||
|
||||
var bestTf = transforms[bestIdx];
|
||||
Log($"Mapping transform index: {bestIdx}, overlap: {bestMatches}");
|
||||
|
||||
foreach (var kv in bMap)
|
||||
{
|
||||
var p = kv.Key.Split(',');
|
||||
int x = int.Parse(p[0]);
|
||||
int y = int.Parse(p[1]);
|
||||
if (x >= w || y >= h) continue;
|
||||
|
||||
var tr = bestTf(x, y);
|
||||
var tKey = PosKey(tr.x, tr.y);
|
||||
if (!tMap.ContainsKey(tKey)) continue;
|
||||
|
||||
var bTile = kv.Value;
|
||||
var tTile = tMap[tKey];
|
||||
targetByBottomId[bTile.Id] = tTile.State;
|
||||
bottomTilesOrdered.Add(bTile);
|
||||
}
|
||||
|
||||
return targetByBottomId.Count > 0;
|
||||
}
|
||||
|
||||
Dictionary<long, int> ReadCurrentStatesById(HashSet<long> ids)
|
||||
{
|
||||
var map = new Dictionary<long, int>();
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
long id = item.Id;
|
||||
if (!ids.Contains(id)) continue;
|
||||
map[id] = GetState(item);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
int CircularNeed(int current, int target, int mod)
|
||||
{
|
||||
int d = (target - current) % mod;
|
||||
if (d < 0) d += mod;
|
||||
return d;
|
||||
}
|
||||
|
||||
int CalcObjective(Dictionary<long, int> current, Dictionary<long, int> target, int cycle)
|
||||
{
|
||||
int s = 0;
|
||||
foreach (var kv in target)
|
||||
{
|
||||
if (!current.ContainsKey(kv.Key)) continue;
|
||||
s += CircularNeed(current[kv.Key], kv.Value, cycle);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
bool WaitUntilAt(int tx, int ty, int timeoutMs)
|
||||
{
|
||||
int elapsed = 0;
|
||||
while (elapsed < timeoutMs)
|
||||
{
|
||||
if (Self != null && Self.Location != null && Self.Location.X == tx && Self.Location.Y == ty)
|
||||
return true;
|
||||
Delay(120);
|
||||
elapsed += 120;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int Dist(int x1, int y1, int x2, int y2)
|
||||
{
|
||||
return Math.Abs(x1 - x2) + Math.Abs(y1 - y2);
|
||||
}
|
||||
|
||||
Log("=== Color Pattern Walker Solver ===");
|
||||
|
||||
if (WAIT_FOR_SPAWN_START)
|
||||
WaitForRoundSpawn(SPAWN_X, SPAWN_Y, SPAWN_WAIT_TIMEOUT_MS);
|
||||
|
||||
var allTiles = ReadAllFloorTiles();
|
||||
if (allTiles.Count == 0)
|
||||
{
|
||||
Log("ERROR: No floor items found.");
|
||||
return;
|
||||
}
|
||||
|
||||
LogRoomScan(allTiles);
|
||||
|
||||
Cluster top, bottom;
|
||||
if (!TryDetectBoards(allTiles, SPAWN_X, SPAWN_Y, out top, out bottom))
|
||||
{
|
||||
Log("ERROR: Could not auto-detect top template board + bottom play board.");
|
||||
Log("Tip: run ColorPuzzleScanner first and tell me tile Kind/Name, then I hard-bind it.");
|
||||
return;
|
||||
}
|
||||
|
||||
Log($"Detected kind: {top.Kind}");
|
||||
Log($"Top tiles: {top.Tiles.Count}, avgZ={top.AvgZ:F2}");
|
||||
Log($"Bottom tiles: {bottom.Tiles.Count}, avgZ={bottom.AvgZ:F2}");
|
||||
|
||||
Dictionary<long, int> targetById;
|
||||
List<Tile> bottomTiles;
|
||||
if (!TryBuildTargetMap(top, bottom, out targetById, out bottomTiles))
|
||||
{
|
||||
Log("ERROR: Could not map top pattern to bottom board.");
|
||||
return;
|
||||
}
|
||||
|
||||
var targetIds = new HashSet<long>(targetById.Keys);
|
||||
var current = ReadCurrentStatesById(targetIds);
|
||||
if (current.Count == 0)
|
||||
{
|
||||
Log("ERROR: Could not read current bottom states.");
|
||||
return;
|
||||
}
|
||||
|
||||
int maxStateSeen = 0;
|
||||
foreach (var v in targetById.Values) if (v > maxStateSeen) maxStateSeen = v;
|
||||
foreach (var v in current.Values) if (v > maxStateSeen) maxStateSeen = v;
|
||||
int cycle = maxStateSeen + 1;
|
||||
if (cycle < 2 || cycle > 8) cycle = 4;
|
||||
|
||||
Log($"Mapped tiles: {targetById.Count}");
|
||||
Log($"State cycle guessed: {cycle}");
|
||||
|
||||
int objective = CalcObjective(current, targetById, cycle);
|
||||
Log($"Initial distance: {objective}");
|
||||
if (objective == 0)
|
||||
{
|
||||
Log("Already solved.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Self == null || Self.Location == null)
|
||||
{
|
||||
Log("ERROR: Self position unavailable.");
|
||||
return;
|
||||
}
|
||||
|
||||
var rng = new Random();
|
||||
int stagnation = 0;
|
||||
const int MAX_MOVES = 1200;
|
||||
|
||||
for (int step = 1; step <= MAX_MOVES; step++)
|
||||
{
|
||||
var meX = Self?.Location?.X ?? -1;
|
||||
var meY = Self?.Location?.Y ?? -1;
|
||||
|
||||
Tile chosen = null;
|
||||
|
||||
var needTiles = bottomTiles
|
||||
.Where(t => current.ContainsKey(t.Id) && targetById.ContainsKey(t.Id))
|
||||
.Select(t => new {
|
||||
Tile = t,
|
||||
Need = CircularNeed(current[t.Id], targetById[t.Id], cycle),
|
||||
D = (meX >= 0 && meY >= 0) ? Dist(meX, meY, t.X, t.Y) : 9999
|
||||
})
|
||||
.Where(x => x.Need > 0)
|
||||
.OrderByDescending(x => x.Need)
|
||||
.ThenBy(x => x.D)
|
||||
.ToList();
|
||||
|
||||
if (needTiles.Count == 0)
|
||||
{
|
||||
Log("Solved (need list empty).");
|
||||
break;
|
||||
}
|
||||
|
||||
if (stagnation >= 12)
|
||||
{
|
||||
chosen = needTiles[rng.Next(needTiles.Count)].Tile;
|
||||
}
|
||||
else
|
||||
{
|
||||
chosen = needTiles[0].Tile;
|
||||
}
|
||||
|
||||
Log($"[{step}] Move to ({chosen.X},{chosen.Y}) id={chosen.Id} state={current[chosen.Id]} target={targetById[chosen.Id]}");
|
||||
Move(chosen.X, chosen.Y);
|
||||
|
||||
bool arrived = WaitUntilAt(chosen.X, chosen.Y, 2800);
|
||||
if (!arrived)
|
||||
{
|
||||
Move(chosen.X, chosen.Y);
|
||||
WaitUntilAt(chosen.X, chosen.Y, 2000);
|
||||
}
|
||||
|
||||
Delay(220);
|
||||
current = ReadCurrentStatesById(targetIds);
|
||||
int newObj = CalcObjective(current, targetById, cycle);
|
||||
int delta = objective - newObj;
|
||||
Log($" distance: {objective} -> {newObj} (delta {delta})");
|
||||
|
||||
if (newObj <= 0)
|
||||
{
|
||||
Log("=== Done: bottom now matches top pattern ===");
|
||||
return;
|
||||
}
|
||||
|
||||
if (newObj < objective) stagnation = 0;
|
||||
else stagnation++;
|
||||
|
||||
objective = newObj;
|
||||
Delay(120);
|
||||
}
|
||||
|
||||
var finalStates = ReadCurrentStatesById(targetIds);
|
||||
int finalObj = CalcObjective(finalStates, targetById, cycle);
|
||||
if (finalObj == 0)
|
||||
Log("=== Done: bottom now matches top pattern ===");
|
||||
else
|
||||
Log($"Stopped. Remaining distance: {finalObj}. Re-run script to continue.");
|
||||
482
Color Puzzle AutoCalib Queue.csx
Normal file
482
Color Puzzle AutoCalib Queue.csx
Normal file
@ -0,0 +1,482 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
// Color Puzzle Solver v2
|
||||
// - Auto calibration of arrow -> move mapping
|
||||
// - Waits for real state change after every click
|
||||
|
||||
const int TILE_KIND = 3696;
|
||||
const int ARROW_KIND = 17851;
|
||||
const int GRID_X_MIN = 36;
|
||||
const int GRID_X_MAX = 39;
|
||||
const int GRID_Y_MIN = 27;
|
||||
const int GRID_Y_MAX = 30;
|
||||
|
||||
const int CLICK_SETTLE_DELAY_MS = 250;
|
||||
const int WAIT_CHANGE_TIMEOUT_MS = 6000;
|
||||
const int WAIT_CHANGE_POLL_MS = 120;
|
||||
const int MAX_STEPS = 140;
|
||||
const int BFS_MAX_NODES = 4_000_000;
|
||||
const int IDA_MAX_SEC = 12;
|
||||
|
||||
const bool AUTO_QUEUE_START = true;
|
||||
const long TRANSPORTER_ID = 759030883;
|
||||
const int QUEUE_CLICK_INTERVAL_MS = 5000;
|
||||
const int WAIT_PUZZLE_POLL_MS = 250;
|
||||
const int WAIT_PUZZLE_LOG_MS = 5000;
|
||||
const bool REQUIRE_SELF_IN_PLAYZONE = true;
|
||||
const int PLAY_X_MIN = 34;
|
||||
const int PLAY_X_MAX = 41;
|
||||
const int PLAY_Y_MIN = 26;
|
||||
const int PLAY_Y_MAX = 31;
|
||||
const bool REQUIRE_SELF_MIN_Z = true;
|
||||
const double SELF_MIN_Z = 17.0;
|
||||
|
||||
int GetState(dynamic item)
|
||||
{
|
||||
try { return int.Parse(item.State?.ToString() ?? "0"); }
|
||||
catch { return 0; }
|
||||
}
|
||||
|
||||
int GetKind(dynamic item)
|
||||
{
|
||||
try { return (int)item.Kind; }
|
||||
catch { return -1; }
|
||||
}
|
||||
|
||||
uint EncodeGrid(int[,] g)
|
||||
{
|
||||
uint s = 0;
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
s |= ((uint)(g[r, c] & 3)) << (2 * (r * 4 + c));
|
||||
return s;
|
||||
}
|
||||
|
||||
bool TryReadGrid(out uint state, out string dump)
|
||||
{
|
||||
int[,] grid = new int[4, 4];
|
||||
bool[,] found = new bool[4, 4];
|
||||
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != TILE_KIND) continue;
|
||||
|
||||
int x = item.Location.X;
|
||||
int y = item.Location.Y;
|
||||
double z = item.Location.Z;
|
||||
|
||||
if (x < GRID_X_MIN || x > GRID_X_MAX) continue;
|
||||
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
|
||||
if (z < 18.4) continue;
|
||||
|
||||
int row = y - GRID_Y_MIN;
|
||||
int col = x - GRID_X_MIN;
|
||||
grid[row, col] = GetState(item);
|
||||
found[row, col] = true;
|
||||
}
|
||||
|
||||
int cnt = 0;
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
if (found[r, c]) cnt++;
|
||||
|
||||
if (cnt < 16)
|
||||
{
|
||||
state = 0;
|
||||
dump = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
state = EncodeGrid(grid);
|
||||
dump = string.Join(" | ", Enumerable.Range(0, 4).Select(r =>
|
||||
$"R{r}[{grid[r,0]},{grid[r,1]},{grid[r,2]},{grid[r,3]}]"));
|
||||
return true;
|
||||
}
|
||||
|
||||
uint RowLeft(uint s, int r)
|
||||
{
|
||||
int sh = r * 8;
|
||||
uint row = (s >> sh) & 0xFFu;
|
||||
uint rot = ((row >> 2) | (row << 6)) & 0xFFu;
|
||||
return (s & ~(0xFFu << sh)) | (rot << sh);
|
||||
}
|
||||
|
||||
uint RowRight(uint s, int r)
|
||||
{
|
||||
int sh = r * 8;
|
||||
uint row = (s >> sh) & 0xFFu;
|
||||
uint rot = ((row << 2) | (row >> 6)) & 0xFFu;
|
||||
return (s & ~(0xFFu << sh)) | (rot << sh);
|
||||
}
|
||||
|
||||
uint ColUp(uint s, int c)
|
||||
{
|
||||
int b = c * 2;
|
||||
uint v0 = (s >> b) & 3u;
|
||||
uint v1 = (s >> (b + 8)) & 3u;
|
||||
uint v2 = (s >> (b + 16)) & 3u;
|
||||
uint v3 = (s >> (b + 24)) & 3u;
|
||||
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
|
||||
return (s & mask) | (v1 << b) | (v2 << (b + 8)) | (v3 << (b + 16)) | (v0 << (b + 24));
|
||||
}
|
||||
|
||||
uint ColDown(uint s, int c)
|
||||
{
|
||||
int b = c * 2;
|
||||
uint v0 = (s >> b) & 3u;
|
||||
uint v1 = (s >> (b + 8)) & 3u;
|
||||
uint v2 = (s >> (b + 16)) & 3u;
|
||||
uint v3 = (s >> (b + 24)) & 3u;
|
||||
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
|
||||
return (s & mask) | (v3 << b) | (v0 << (b + 8)) | (v1 << (b + 16)) | (v2 << (b + 24));
|
||||
}
|
||||
|
||||
uint ApplyMove(uint s, int m)
|
||||
{
|
||||
if (m < 4) return RowLeft(s, m);
|
||||
if (m < 8) return RowRight(s, m - 4);
|
||||
if (m < 12) return ColUp(s, m - 8);
|
||||
return ColDown(s, m - 12);
|
||||
}
|
||||
|
||||
int InverseMove(int m)
|
||||
{
|
||||
if (m < 4) return m + 4;
|
||||
if (m < 8) return m - 4;
|
||||
if (m < 12) return m + 4;
|
||||
return m - 4;
|
||||
}
|
||||
|
||||
string MoveName(int m)
|
||||
{
|
||||
if (m < 4) return $"Row{m} LEFT";
|
||||
if (m < 8) return $"Row{m - 4} RIGHT";
|
||||
if (m < 12) return $"Col{m - 8} UP";
|
||||
return $"Col{m - 12} DOWN";
|
||||
}
|
||||
|
||||
int DetectMove(uint before, uint after)
|
||||
{
|
||||
int hit = -1;
|
||||
for (int m = 0; m < 16; m++)
|
||||
{
|
||||
if (ApplyMove(before, m) != after) continue;
|
||||
if (hit != -1) return -2;
|
||||
hit = m;
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
List<int> SolveBfs(uint start, uint goal)
|
||||
{
|
||||
if (start == goal) return new List<int>();
|
||||
|
||||
var visited = new Dictionary<uint, (uint parent, int move)>();
|
||||
var queue = new Queue<uint>();
|
||||
visited[start] = (start, -1);
|
||||
queue.Enqueue(start);
|
||||
int nodes = 0;
|
||||
bool found = false;
|
||||
|
||||
while (queue.Count > 0 && nodes < BFS_MAX_NODES)
|
||||
{
|
||||
uint cur = queue.Dequeue();
|
||||
nodes++;
|
||||
|
||||
for (int m = 0; m < 16; m++)
|
||||
{
|
||||
uint nxt = ApplyMove(cur, m);
|
||||
if (visited.ContainsKey(nxt)) continue;
|
||||
visited[nxt] = (cur, m);
|
||||
if (nxt == goal)
|
||||
{
|
||||
found = true;
|
||||
queue.Clear();
|
||||
break;
|
||||
}
|
||||
queue.Enqueue(nxt);
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) return null;
|
||||
|
||||
var sol = new List<int>();
|
||||
uint s = goal;
|
||||
while (s != start)
|
||||
{
|
||||
var p = visited[s];
|
||||
sol.Add(p.move);
|
||||
s = p.parent;
|
||||
}
|
||||
sol.Reverse();
|
||||
return sol;
|
||||
}
|
||||
|
||||
List<int> SolveIda(uint start, uint goal)
|
||||
{
|
||||
if (start == goal) return new List<int>();
|
||||
var t0 = DateTime.Now;
|
||||
|
||||
int H(uint st)
|
||||
{
|
||||
int mis = 0;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
int a = (int)((st >> (i * 2)) & 3u);
|
||||
int b = (int)((goal >> (i * 2)) & 3u);
|
||||
if (a != b) mis++;
|
||||
}
|
||||
return (mis + 3) / 4;
|
||||
}
|
||||
|
||||
List<int> best = null;
|
||||
bool timeout = false;
|
||||
|
||||
bool Dfs(uint st, List<int> path, int maxDepth)
|
||||
{
|
||||
if (timeout) return false;
|
||||
if ((DateTime.Now - t0).TotalSeconds > IDA_MAX_SEC)
|
||||
{
|
||||
timeout = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (st == goal)
|
||||
{
|
||||
best = new List<int>(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
int h = H(st);
|
||||
if (path.Count + h > maxDepth) return false;
|
||||
|
||||
int block = path.Count > 0 ? InverseMove(path[path.Count - 1]) : -1;
|
||||
for (int m = 0; m < 16; m++)
|
||||
{
|
||||
if (m == block) continue;
|
||||
path.Add(m);
|
||||
if (Dfs(ApplyMove(st, m), path, maxDepth)) return true;
|
||||
path.RemoveAt(path.Count - 1);
|
||||
if (timeout) return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int d0 = H(start);
|
||||
for (int d = d0; d <= 22 && !timeout; d++)
|
||||
{
|
||||
if (Dfs(start, new List<int>(), d)) break;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
List<int> Solve(uint start, uint goal)
|
||||
{
|
||||
var bfs = SolveBfs(start, goal);
|
||||
if (bfs != null) return bfs;
|
||||
return SolveIda(start, goal);
|
||||
}
|
||||
|
||||
bool ClickAndWaitChange(long furniId, uint before, out uint after, out string dumpAfter)
|
||||
{
|
||||
Send(Out["ClickFurni"], (int)furniId, 0);
|
||||
Delay(CLICK_SETTLE_DELAY_MS);
|
||||
|
||||
int waited = 0;
|
||||
while (waited < WAIT_CHANGE_TIMEOUT_MS)
|
||||
{
|
||||
if (TryReadGrid(out after, out dumpAfter) && after != before)
|
||||
return true;
|
||||
Delay(WAIT_CHANGE_POLL_MS);
|
||||
waited += WAIT_CHANGE_POLL_MS;
|
||||
}
|
||||
|
||||
after = before;
|
||||
dumpAfter = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
Dictionary<string, long> ReadArrowIds()
|
||||
{
|
||||
var arrowIds = new Dictionary<string, long>();
|
||||
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != ARROW_KIND) continue;
|
||||
int x = item.Location.X;
|
||||
int y = item.Location.Y;
|
||||
|
||||
if (y == GRID_Y_MIN - 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
|
||||
arrowIds[$"up_{x - GRID_X_MIN}"] = item.Id;
|
||||
else if (y == GRID_Y_MAX + 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
|
||||
arrowIds[$"down_{x - GRID_X_MIN}"] = item.Id;
|
||||
else if (x == GRID_X_MIN - 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
|
||||
arrowIds[$"left_{y - GRID_Y_MIN}"] = item.Id;
|
||||
else if (x == GRID_X_MAX + 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
|
||||
arrowIds[$"right_{y - GRID_Y_MIN}"] = item.Id;
|
||||
}
|
||||
|
||||
return arrowIds;
|
||||
}
|
||||
|
||||
bool IsSelfInPlayZone()
|
||||
{
|
||||
try
|
||||
{
|
||||
int x = Self.Location.X;
|
||||
int y = Self.Location.Y;
|
||||
double z = Self.Location.Z;
|
||||
bool inRect = x >= PLAY_X_MIN && x <= PLAY_X_MAX && y >= PLAY_Y_MIN && y <= PLAY_Y_MAX;
|
||||
bool inZ = !REQUIRE_SELF_MIN_Z || z >= SELF_MIN_Z;
|
||||
return inRect && inZ;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Log("=== Color Puzzle Auto-Solver (AutoCalib + WaitChange) ===");
|
||||
|
||||
Dictionary<string, long> arrowIds = null;
|
||||
uint current;
|
||||
string dumpNow;
|
||||
|
||||
int sinceQueueClick = QUEUE_CLICK_INTERVAL_MS;
|
||||
int sinceLog = WAIT_PUZZLE_LOG_MS;
|
||||
|
||||
while (true)
|
||||
{
|
||||
bool hasGrid = TryReadGrid(out current, out dumpNow);
|
||||
var probeArrows = ReadArrowIds();
|
||||
bool hasArrows = probeArrows.Count == 16;
|
||||
bool inPlayZone = !REQUIRE_SELF_IN_PLAYZONE || IsSelfInPlayZone();
|
||||
|
||||
if (hasGrid && hasArrows && inPlayZone)
|
||||
{
|
||||
arrowIds = probeArrows;
|
||||
break;
|
||||
}
|
||||
|
||||
if (AUTO_QUEUE_START && sinceQueueClick >= QUEUE_CLICK_INTERVAL_MS)
|
||||
{
|
||||
Send(Out["ClickFurni"], (int)TRANSPORTER_ID, 0);
|
||||
Log($"Queue: Klick Transporter {TRANSPORTER_ID}...");
|
||||
sinceQueueClick = 0;
|
||||
}
|
||||
|
||||
if (sinceLog >= WAIT_PUZZLE_LOG_MS)
|
||||
{
|
||||
string selfPos = "?";
|
||||
try { selfPos = $"{Self.Location.X},{Self.Location.Y},{Self.Location.Z:F2}"; } catch { }
|
||||
Log($"Warte auf Spielstart... Grid={(hasGrid ? "ok" : "no")}, Pfeile={probeArrows.Count}/16, InZone={(inPlayZone ? "yes" : "no")}, Self={selfPos}");
|
||||
sinceLog = 0;
|
||||
}
|
||||
|
||||
Delay(WAIT_PUZZLE_POLL_MS);
|
||||
sinceQueueClick += WAIT_PUZZLE_POLL_MS;
|
||||
sinceLog += WAIT_PUZZLE_POLL_MS;
|
||||
}
|
||||
|
||||
Log("Puzzle erkannt. Starte Solver...");
|
||||
Log($"Pfeile: {arrowIds.Count}/16");
|
||||
|
||||
int[] targetRows = new int[4];
|
||||
bool targetFound = false;
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != TILE_KIND) continue;
|
||||
if (item.Location.X != 41) continue;
|
||||
int y = item.Location.Y;
|
||||
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
|
||||
|
||||
targetRows[y - GRID_Y_MIN] = GetState(item);
|
||||
targetFound = true;
|
||||
}
|
||||
if (!targetFound) targetRows = new[] { 1, 2, 3, 0 };
|
||||
|
||||
int[,] tgt = new int[4, 4];
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
tgt[r, c] = targetRows[r];
|
||||
|
||||
uint goal = EncodeGrid(tgt);
|
||||
Log($"Ziel: R0={targetRows[0]}, R1={targetRows[1]}, R2={targetRows[2]}, R3={targetRows[3]}");
|
||||
|
||||
Log($"Start: {dumpNow}");
|
||||
|
||||
var moveToKey = new Dictionary<int, string>();
|
||||
var keyToMove = new Dictionary<string, int>();
|
||||
var allKeys = arrowIds.Keys.OrderBy(k => k).ToList();
|
||||
|
||||
for (int step = 1; step <= MAX_STEPS; step++)
|
||||
{
|
||||
if (current == goal)
|
||||
{
|
||||
Log("=== Geloest: alle 4 Reihen korrekt ===");
|
||||
return;
|
||||
}
|
||||
|
||||
var plan = Solve(current, goal);
|
||||
if (plan == null || plan.Count == 0)
|
||||
{
|
||||
Log("ERROR: Kein Plan vom aktuellen Zustand.");
|
||||
return;
|
||||
}
|
||||
|
||||
int wanted = plan[0];
|
||||
string key;
|
||||
bool probing = false;
|
||||
|
||||
if (moveToKey.ContainsKey(wanted))
|
||||
{
|
||||
key = moveToKey[wanted];
|
||||
}
|
||||
else
|
||||
{
|
||||
key = allKeys.FirstOrDefault(k => !keyToMove.ContainsKey(k));
|
||||
if (key == null)
|
||||
{
|
||||
key = allKeys[0];
|
||||
}
|
||||
probing = true;
|
||||
}
|
||||
|
||||
long id = arrowIds[key];
|
||||
Log($"[{step}] want {MoveName(wanted)} | click {key}" + (probing ? " (probe)" : ""));
|
||||
|
||||
if (!ClickAndWaitChange(id, current, out uint after, out string dumpAfter))
|
||||
{
|
||||
Log(" Kein Move erkannt (Timeout), gleicher Schritt nochmal.");
|
||||
continue;
|
||||
}
|
||||
|
||||
int actual = DetectMove(current, after);
|
||||
if (actual >= 0)
|
||||
{
|
||||
moveToKey[actual] = key;
|
||||
keyToMove[key] = actual;
|
||||
if (actual != wanted)
|
||||
Log($" AutoCalib: {key} == {MoveName(actual)} (nicht {MoveName(wanted)})");
|
||||
}
|
||||
else if (actual == -1)
|
||||
{
|
||||
Log($" Unbekannter Transition-Delta, weiter mit Re-Plan. State: {dumpAfter}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($" Mehrdeutiger Delta, weiter mit Re-Plan. State: {dumpAfter}");
|
||||
}
|
||||
|
||||
current = after;
|
||||
|
||||
if (step % 10 == 0)
|
||||
Log($" Calib: {moveToKey.Count}/16 Moves gemappt");
|
||||
}
|
||||
|
||||
Log("Nicht fertig in MAX_STEPS. Script einfach nochmal starten.");
|
||||
415
Color Puzzle AutoCalib Solver.csx
Normal file
415
Color Puzzle AutoCalib Solver.csx
Normal file
@ -0,0 +1,415 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
// Color Puzzle Solver v2
|
||||
// - Auto calibration of arrow -> move mapping
|
||||
// - Waits for real state change after every click
|
||||
|
||||
const int TILE_KIND = 3696;
|
||||
const int ARROW_KIND = 17851;
|
||||
const int GRID_X_MIN = 36;
|
||||
const int GRID_X_MAX = 39;
|
||||
const int GRID_Y_MIN = 27;
|
||||
const int GRID_Y_MAX = 30;
|
||||
|
||||
const int CLICK_SETTLE_DELAY_MS = 250;
|
||||
const int WAIT_CHANGE_TIMEOUT_MS = 6000;
|
||||
const int WAIT_CHANGE_POLL_MS = 120;
|
||||
const int MAX_STEPS = 140;
|
||||
const int BFS_MAX_NODES = 4_000_000;
|
||||
const int IDA_MAX_SEC = 12;
|
||||
|
||||
int GetState(dynamic item)
|
||||
{
|
||||
try { return int.Parse(item.State?.ToString() ?? "0"); }
|
||||
catch { return 0; }
|
||||
}
|
||||
|
||||
int GetKind(dynamic item)
|
||||
{
|
||||
try { return (int)item.Kind; }
|
||||
catch { return -1; }
|
||||
}
|
||||
|
||||
uint EncodeGrid(int[,] g)
|
||||
{
|
||||
uint s = 0;
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
s |= ((uint)(g[r, c] & 3)) << (2 * (r * 4 + c));
|
||||
return s;
|
||||
}
|
||||
|
||||
bool TryReadGrid(out uint state, out string dump)
|
||||
{
|
||||
int[,] grid = new int[4, 4];
|
||||
bool[,] found = new bool[4, 4];
|
||||
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != TILE_KIND) continue;
|
||||
|
||||
int x = item.Location.X;
|
||||
int y = item.Location.Y;
|
||||
double z = item.Location.Z;
|
||||
|
||||
if (x < GRID_X_MIN || x > GRID_X_MAX) continue;
|
||||
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
|
||||
if (z < 18.4) continue;
|
||||
|
||||
int row = y - GRID_Y_MIN;
|
||||
int col = x - GRID_X_MIN;
|
||||
grid[row, col] = GetState(item);
|
||||
found[row, col] = true;
|
||||
}
|
||||
|
||||
int cnt = 0;
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
if (found[r, c]) cnt++;
|
||||
|
||||
if (cnt < 16)
|
||||
{
|
||||
state = 0;
|
||||
dump = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
state = EncodeGrid(grid);
|
||||
dump = string.Join(" | ", Enumerable.Range(0, 4).Select(r =>
|
||||
$"R{r}[{grid[r,0]},{grid[r,1]},{grid[r,2]},{grid[r,3]}]"));
|
||||
return true;
|
||||
}
|
||||
|
||||
uint RowLeft(uint s, int r)
|
||||
{
|
||||
int sh = r * 8;
|
||||
uint row = (s >> sh) & 0xFFu;
|
||||
uint rot = ((row >> 2) | (row << 6)) & 0xFFu;
|
||||
return (s & ~(0xFFu << sh)) | (rot << sh);
|
||||
}
|
||||
|
||||
uint RowRight(uint s, int r)
|
||||
{
|
||||
int sh = r * 8;
|
||||
uint row = (s >> sh) & 0xFFu;
|
||||
uint rot = ((row << 2) | (row >> 6)) & 0xFFu;
|
||||
return (s & ~(0xFFu << sh)) | (rot << sh);
|
||||
}
|
||||
|
||||
uint ColUp(uint s, int c)
|
||||
{
|
||||
int b = c * 2;
|
||||
uint v0 = (s >> b) & 3u;
|
||||
uint v1 = (s >> (b + 8)) & 3u;
|
||||
uint v2 = (s >> (b + 16)) & 3u;
|
||||
uint v3 = (s >> (b + 24)) & 3u;
|
||||
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
|
||||
return (s & mask) | (v1 << b) | (v2 << (b + 8)) | (v3 << (b + 16)) | (v0 << (b + 24));
|
||||
}
|
||||
|
||||
uint ColDown(uint s, int c)
|
||||
{
|
||||
int b = c * 2;
|
||||
uint v0 = (s >> b) & 3u;
|
||||
uint v1 = (s >> (b + 8)) & 3u;
|
||||
uint v2 = (s >> (b + 16)) & 3u;
|
||||
uint v3 = (s >> (b + 24)) & 3u;
|
||||
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
|
||||
return (s & mask) | (v3 << b) | (v0 << (b + 8)) | (v1 << (b + 16)) | (v2 << (b + 24));
|
||||
}
|
||||
|
||||
uint ApplyMove(uint s, int m)
|
||||
{
|
||||
if (m < 4) return RowLeft(s, m);
|
||||
if (m < 8) return RowRight(s, m - 4);
|
||||
if (m < 12) return ColUp(s, m - 8);
|
||||
return ColDown(s, m - 12);
|
||||
}
|
||||
|
||||
int InverseMove(int m)
|
||||
{
|
||||
if (m < 4) return m + 4;
|
||||
if (m < 8) return m - 4;
|
||||
if (m < 12) return m + 4;
|
||||
return m - 4;
|
||||
}
|
||||
|
||||
string MoveName(int m)
|
||||
{
|
||||
if (m < 4) return $"Row{m} LEFT";
|
||||
if (m < 8) return $"Row{m - 4} RIGHT";
|
||||
if (m < 12) return $"Col{m - 8} UP";
|
||||
return $"Col{m - 12} DOWN";
|
||||
}
|
||||
|
||||
int DetectMove(uint before, uint after)
|
||||
{
|
||||
int hit = -1;
|
||||
for (int m = 0; m < 16; m++)
|
||||
{
|
||||
if (ApplyMove(before, m) != after) continue;
|
||||
if (hit != -1) return -2;
|
||||
hit = m;
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
List<int> SolveBfs(uint start, uint goal)
|
||||
{
|
||||
if (start == goal) return new List<int>();
|
||||
|
||||
var visited = new Dictionary<uint, (uint parent, int move)>();
|
||||
var queue = new Queue<uint>();
|
||||
visited[start] = (start, -1);
|
||||
queue.Enqueue(start);
|
||||
int nodes = 0;
|
||||
bool found = false;
|
||||
|
||||
while (queue.Count > 0 && nodes < BFS_MAX_NODES)
|
||||
{
|
||||
uint cur = queue.Dequeue();
|
||||
nodes++;
|
||||
|
||||
for (int m = 0; m < 16; m++)
|
||||
{
|
||||
uint nxt = ApplyMove(cur, m);
|
||||
if (visited.ContainsKey(nxt)) continue;
|
||||
visited[nxt] = (cur, m);
|
||||
if (nxt == goal)
|
||||
{
|
||||
found = true;
|
||||
queue.Clear();
|
||||
break;
|
||||
}
|
||||
queue.Enqueue(nxt);
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) return null;
|
||||
|
||||
var sol = new List<int>();
|
||||
uint s = goal;
|
||||
while (s != start)
|
||||
{
|
||||
var p = visited[s];
|
||||
sol.Add(p.move);
|
||||
s = p.parent;
|
||||
}
|
||||
sol.Reverse();
|
||||
return sol;
|
||||
}
|
||||
|
||||
List<int> SolveIda(uint start, uint goal)
|
||||
{
|
||||
if (start == goal) return new List<int>();
|
||||
var t0 = DateTime.Now;
|
||||
|
||||
int H(uint st)
|
||||
{
|
||||
int mis = 0;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
int a = (int)((st >> (i * 2)) & 3u);
|
||||
int b = (int)((goal >> (i * 2)) & 3u);
|
||||
if (a != b) mis++;
|
||||
}
|
||||
return (mis + 3) / 4;
|
||||
}
|
||||
|
||||
List<int> best = null;
|
||||
bool timeout = false;
|
||||
|
||||
bool Dfs(uint st, List<int> path, int maxDepth)
|
||||
{
|
||||
if (timeout) return false;
|
||||
if ((DateTime.Now - t0).TotalSeconds > IDA_MAX_SEC)
|
||||
{
|
||||
timeout = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (st == goal)
|
||||
{
|
||||
best = new List<int>(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
int h = H(st);
|
||||
if (path.Count + h > maxDepth) return false;
|
||||
|
||||
int block = path.Count > 0 ? InverseMove(path[path.Count - 1]) : -1;
|
||||
for (int m = 0; m < 16; m++)
|
||||
{
|
||||
if (m == block) continue;
|
||||
path.Add(m);
|
||||
if (Dfs(ApplyMove(st, m), path, maxDepth)) return true;
|
||||
path.RemoveAt(path.Count - 1);
|
||||
if (timeout) return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int d0 = H(start);
|
||||
for (int d = d0; d <= 22 && !timeout; d++)
|
||||
{
|
||||
if (Dfs(start, new List<int>(), d)) break;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
List<int> Solve(uint start, uint goal)
|
||||
{
|
||||
var bfs = SolveBfs(start, goal);
|
||||
if (bfs != null) return bfs;
|
||||
return SolveIda(start, goal);
|
||||
}
|
||||
|
||||
bool ClickAndWaitChange(long furniId, uint before, out uint after, out string dumpAfter)
|
||||
{
|
||||
Send(Out["ClickFurni"], (int)furniId, 0);
|
||||
Delay(CLICK_SETTLE_DELAY_MS);
|
||||
|
||||
int waited = 0;
|
||||
while (waited < WAIT_CHANGE_TIMEOUT_MS)
|
||||
{
|
||||
if (TryReadGrid(out after, out dumpAfter) && after != before)
|
||||
return true;
|
||||
Delay(WAIT_CHANGE_POLL_MS);
|
||||
waited += WAIT_CHANGE_POLL_MS;
|
||||
}
|
||||
|
||||
after = before;
|
||||
dumpAfter = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
Log("=== Color Puzzle Auto-Solver (AutoCalib + WaitChange) ===");
|
||||
|
||||
var arrowIds = new Dictionary<string, long>();
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != ARROW_KIND) continue;
|
||||
int x = item.Location.X;
|
||||
int y = item.Location.Y;
|
||||
|
||||
if (y == GRID_Y_MIN - 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
|
||||
arrowIds[$"up_{x - GRID_X_MIN}"] = item.Id;
|
||||
else if (y == GRID_Y_MAX + 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
|
||||
arrowIds[$"down_{x - GRID_X_MIN}"] = item.Id;
|
||||
else if (x == GRID_X_MIN - 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
|
||||
arrowIds[$"left_{y - GRID_Y_MIN}"] = item.Id;
|
||||
else if (x == GRID_X_MAX + 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
|
||||
arrowIds[$"right_{y - GRID_Y_MIN}"] = item.Id;
|
||||
}
|
||||
|
||||
Log($"Pfeile: {arrowIds.Count}/16");
|
||||
if (arrowIds.Count < 16)
|
||||
{
|
||||
Log("ERROR: Nicht alle 16 Pfeile gefunden.");
|
||||
return;
|
||||
}
|
||||
|
||||
int[] targetRows = new int[4];
|
||||
bool targetFound = false;
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != TILE_KIND) continue;
|
||||
if (item.Location.X != 41) continue;
|
||||
int y = item.Location.Y;
|
||||
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
|
||||
|
||||
targetRows[y - GRID_Y_MIN] = GetState(item);
|
||||
targetFound = true;
|
||||
}
|
||||
if (!targetFound) targetRows = new[] { 1, 2, 3, 0 };
|
||||
|
||||
int[,] tgt = new int[4, 4];
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
tgt[r, c] = targetRows[r];
|
||||
|
||||
uint goal = EncodeGrid(tgt);
|
||||
Log($"Ziel: R0={targetRows[0]}, R1={targetRows[1]}, R2={targetRows[2]}, R3={targetRows[3]}");
|
||||
|
||||
if (!TryReadGrid(out uint current, out string dumpNow))
|
||||
{
|
||||
Log("ERROR: Grid nicht lesbar.");
|
||||
return;
|
||||
}
|
||||
Log($"Start: {dumpNow}");
|
||||
|
||||
var moveToKey = new Dictionary<int, string>();
|
||||
var keyToMove = new Dictionary<string, int>();
|
||||
var allKeys = arrowIds.Keys.OrderBy(k => k).ToList();
|
||||
|
||||
for (int step = 1; step <= MAX_STEPS; step++)
|
||||
{
|
||||
if (current == goal)
|
||||
{
|
||||
Log("=== Geloest: alle 4 Reihen korrekt ===");
|
||||
return;
|
||||
}
|
||||
|
||||
var plan = Solve(current, goal);
|
||||
if (plan == null || plan.Count == 0)
|
||||
{
|
||||
Log("ERROR: Kein Plan vom aktuellen Zustand.");
|
||||
return;
|
||||
}
|
||||
|
||||
int wanted = plan[0];
|
||||
string key;
|
||||
bool probing = false;
|
||||
|
||||
if (moveToKey.ContainsKey(wanted))
|
||||
{
|
||||
key = moveToKey[wanted];
|
||||
}
|
||||
else
|
||||
{
|
||||
key = allKeys.FirstOrDefault(k => !keyToMove.ContainsKey(k));
|
||||
if (key == null)
|
||||
{
|
||||
key = allKeys[0];
|
||||
}
|
||||
probing = true;
|
||||
}
|
||||
|
||||
long id = arrowIds[key];
|
||||
Log($"[{step}] want {MoveName(wanted)} | click {key}" + (probing ? " (probe)" : ""));
|
||||
|
||||
if (!ClickAndWaitChange(id, current, out uint after, out string dumpAfter))
|
||||
{
|
||||
Log(" Kein Move erkannt (Timeout), gleicher Schritt nochmal.");
|
||||
continue;
|
||||
}
|
||||
|
||||
int actual = DetectMove(current, after);
|
||||
if (actual >= 0)
|
||||
{
|
||||
moveToKey[actual] = key;
|
||||
keyToMove[key] = actual;
|
||||
if (actual != wanted)
|
||||
Log($" AutoCalib: {key} == {MoveName(actual)} (nicht {MoveName(wanted)})");
|
||||
}
|
||||
else if (actual == -1)
|
||||
{
|
||||
Log($" Unbekannter Transition-Delta, weiter mit Re-Plan. State: {dumpAfter}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($" Mehrdeutiger Delta, weiter mit Re-Plan. State: {dumpAfter}");
|
||||
}
|
||||
|
||||
current = after;
|
||||
|
||||
if (step % 10 == 0)
|
||||
Log($" Calib: {moveToKey.Count}/16 Moves gemappt");
|
||||
}
|
||||
|
||||
Log("Nicht fertig in MAX_STEPS. Script einfach nochmal starten.");
|
||||
418
Color Puzzle BFS-IDA Solver.csx
Normal file
418
Color Puzzle BFS-IDA Solver.csx
Normal file
@ -0,0 +1,418 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
// ============================================================
|
||||
// COLOR PUZZLE AUTO-SOLVER (Loopover 4x4)
|
||||
// Liest Grid + Ziel aus dem Raum, loest per BFS/IDA*,
|
||||
// klickt die Pfeil-Buttons automatisch.
|
||||
// ============================================================
|
||||
|
||||
const int TILE_KIND = 3696;
|
||||
const int ARROW_KIND = 17851;
|
||||
const int GRID_X_MIN = 36;
|
||||
const int GRID_X_MAX = 39;
|
||||
const int GRID_Y_MIN = 27;
|
||||
const int GRID_Y_MAX = 30;
|
||||
const int CLICK_DELAY = 700;
|
||||
const int BFS_MAX_NODES = 8_000_000;
|
||||
const int IDA_MAX_SEC = 15;
|
||||
|
||||
// Set true if arrows push tiles INTO the grid (opposite direction)
|
||||
const bool REVERSE_ARROWS = false;
|
||||
|
||||
int GetState(dynamic item)
|
||||
{
|
||||
try { return int.Parse(item.State?.ToString() ?? "0"); }
|
||||
catch { return 0; }
|
||||
}
|
||||
|
||||
int GetKind(dynamic item)
|
||||
{
|
||||
try { return (int)item.Kind; }
|
||||
catch { return -1; }
|
||||
}
|
||||
|
||||
Log("=== Color Puzzle Auto-Solver ===");
|
||||
|
||||
// ── 1. Read puzzle grid from room ──────────────────────────
|
||||
int[,] grid = new int[4, 4];
|
||||
bool[,] gridFound = new bool[4, 4];
|
||||
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != TILE_KIND) continue;
|
||||
int x = item.Location.X, y = item.Location.Y;
|
||||
double z = item.Location.Z;
|
||||
if (x < GRID_X_MIN || x > GRID_X_MAX) continue;
|
||||
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
|
||||
if (z < 18.4) continue;
|
||||
int col = x - GRID_X_MIN;
|
||||
int row = y - GRID_Y_MIN;
|
||||
grid[row, col] = GetState(item);
|
||||
gridFound[row, col] = true;
|
||||
}
|
||||
|
||||
int foundCount = 0;
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
if (gridFound[r, c]) foundCount++;
|
||||
|
||||
if (foundCount < 16)
|
||||
{
|
||||
Log($"ERROR: Nur {foundCount}/16 Grid-Tiles gefunden!");
|
||||
Log("Bist du im richtigen Raum?");
|
||||
return;
|
||||
}
|
||||
|
||||
Log("Aktuelles Grid:");
|
||||
for (int r = 0; r < 4; r++)
|
||||
Log($" Row {r}: [{grid[r,0]}, {grid[r,1]}, {grid[r,2]}, {grid[r,3]}]");
|
||||
|
||||
// ── 2. Define *fixed* target pattern (ignore room indicators) ──────────
|
||||
// Mapping from Scanner (Floor:3696):
|
||||
// State 1 = grün, State 2 = rot, State 3 = blau, State 0 = bunt (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) ===");
|
||||
512
Color Puzzle Layer Solver.csx
Normal file
512
Color Puzzle Layer Solver.csx
Normal file
@ -0,0 +1,512 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
// ============================================================
|
||||
// COLOR PUZZLE AUTO-SOLVER (Loopover 4x4)
|
||||
// Layer-by-layer Ansatz: loest ALLE 4 Reihen zuverlaessig.
|
||||
// Behaelt Anti-Desync + Auto-Flip-Erkennung bei.
|
||||
// ============================================================
|
||||
|
||||
const int TILE_KIND = 3696;
|
||||
const int ARROW_KIND = 17851;
|
||||
const int GRID_X_MIN = 36;
|
||||
const int GRID_X_MAX = 39;
|
||||
const int GRID_Y_MIN = 27;
|
||||
const int GRID_Y_MAX = 30;
|
||||
const int CLICK_DELAY_MS = 950;
|
||||
|
||||
int GetState(dynamic item)
|
||||
{
|
||||
try { return int.Parse(item.State?.ToString() ?? "0"); }
|
||||
catch { return 0; }
|
||||
}
|
||||
|
||||
int GetKind(dynamic item)
|
||||
{
|
||||
try { return (int)item.Kind; }
|
||||
catch { return -1; }
|
||||
}
|
||||
|
||||
Log("=== Color Puzzle Auto-Solver (Layer-by-Layer) ===");
|
||||
|
||||
// ── 1. Read grid as array ─────────────────────────────────
|
||||
int[,] ReadGridFromRoom()
|
||||
{
|
||||
int[,] g = new int[4, 4];
|
||||
bool[,] found = new bool[4, 4];
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != TILE_KIND) continue;
|
||||
int x = item.Location.X, y = item.Location.Y;
|
||||
double z = item.Location.Z;
|
||||
if (x < GRID_X_MIN || x > GRID_X_MAX) continue;
|
||||
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
|
||||
if (z < 18.4) continue;
|
||||
g[y - GRID_Y_MIN, x - GRID_X_MIN] = GetState(item);
|
||||
found[y - GRID_Y_MIN, x - GRID_X_MIN] = true;
|
||||
}
|
||||
int cnt = 0;
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
if (found[r, c]) cnt++;
|
||||
if (cnt < 16) return null;
|
||||
return g;
|
||||
}
|
||||
|
||||
string GridDump(int[,] g)
|
||||
{
|
||||
return string.Join(" | ", Enumerable.Range(0, 4).Select(r =>
|
||||
$"R{r}[{g[r,0]},{g[r,1]},{g[r,2]},{g[r,3]}]"));
|
||||
}
|
||||
|
||||
var grid = ReadGridFromRoom();
|
||||
if (grid == null)
|
||||
{
|
||||
Log("ERROR: Konnte Grid nicht lesen (nicht alle 16 Tiles gefunden).");
|
||||
return;
|
||||
}
|
||||
Log($"Start: {GridDump(grid)}");
|
||||
|
||||
// ── 2. Read arrows ────────────────────────────────────────
|
||||
var arrowIds = new Dictionary<string, long>();
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != ARROW_KIND) continue;
|
||||
int x = item.Location.X, y = item.Location.Y;
|
||||
if (y == GRID_Y_MIN - 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
|
||||
arrowIds[$"up_{x - GRID_X_MIN}"] = item.Id;
|
||||
else if (y == GRID_Y_MAX + 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
|
||||
arrowIds[$"down_{x - GRID_X_MIN}"] = item.Id;
|
||||
else if (x == GRID_X_MIN - 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
|
||||
arrowIds[$"left_{y - GRID_Y_MIN}"] = item.Id;
|
||||
else if (x == GRID_X_MAX + 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
|
||||
arrowIds[$"right_{y - GRID_Y_MIN}"] = item.Id;
|
||||
}
|
||||
Log($"Pfeile: {arrowIds.Count}/16");
|
||||
if (arrowIds.Count < 16) { Log("ERROR: Nicht alle Pfeile gefunden!"); return; }
|
||||
|
||||
// ── 3. Read target ────────────────────────────────────────
|
||||
int[] targetRows = new int[4];
|
||||
bool targetFound = false;
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != TILE_KIND) continue;
|
||||
if (item.Location.X != 41) continue;
|
||||
int y = item.Location.Y;
|
||||
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
|
||||
targetRows[y - GRID_Y_MIN] = GetState(item);
|
||||
targetFound = true;
|
||||
}
|
||||
if (!targetFound) targetRows = new[] { 1, 2, 3, 0 };
|
||||
Log($"Ziel: R0={targetRows[0]}, R1={targetRows[1]}, R2={targetRows[2]}, R3={targetRows[3]}");
|
||||
|
||||
// ── 4. Layer-by-Layer Solver ──────────────────────────────
|
||||
// Move encoding: 0-3=RowLeft(0-3), 4-7=RowRight(0-3),
|
||||
// 8-11=ColUp(0-3), 12-15=ColDown(0-3)
|
||||
|
||||
string MoveName(int m)
|
||||
{
|
||||
if (m < 4) return $"Row{m} LEFT";
|
||||
if (m < 8) return $"Row{m-4} RIGHT";
|
||||
if (m < 12) return $"Col{m-8} UP";
|
||||
return $"Col{m-12} DOWN";
|
||||
}
|
||||
|
||||
// Simulate a single move on a grid copy
|
||||
void SimMove(int[,] g, int m)
|
||||
{
|
||||
if (m < 4) { // RowLeft
|
||||
int r = m;
|
||||
int t = g[r,0]; g[r,0]=g[r,1]; g[r,1]=g[r,2]; g[r,2]=g[r,3]; g[r,3]=t;
|
||||
} else if (m < 8) { // RowRight
|
||||
int r = m-4;
|
||||
int t = g[r,3]; g[r,3]=g[r,2]; g[r,2]=g[r,1]; g[r,1]=g[r,0]; g[r,0]=t;
|
||||
} else if (m < 12) { // ColUp
|
||||
int c = m-8;
|
||||
int t = g[0,c]; g[0,c]=g[1,c]; g[1,c]=g[2,c]; g[2,c]=g[3,c]; g[3,c]=t;
|
||||
} else { // ColDown
|
||||
int c = m-12;
|
||||
int t = g[3,c]; g[3,c]=g[2,c]; g[2,c]=g[1,c]; g[1,c]=g[0,c]; g[0,c]=t;
|
||||
}
|
||||
}
|
||||
|
||||
List<int> SolveLayerByLayer(int[,] srcGrid, int[] tgtRows)
|
||||
{
|
||||
// Work on a copy
|
||||
int[,] g = new int[4,4];
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
g[r,c] = srcGrid[r,c];
|
||||
|
||||
var moves = new List<int>();
|
||||
|
||||
void Do(int m) { moves.Add(m); SimMove(g, m); }
|
||||
|
||||
void DoRowRight(int r, int times) {
|
||||
times = ((times % 4) + 4) % 4;
|
||||
if (times == 3) { Do(r); return; } // 1x RowLeft is cheaper
|
||||
for (int i = 0; i < times; i++) Do(r + 4);
|
||||
}
|
||||
void DoRowLeft(int r, int times) {
|
||||
times = ((times % 4) + 4) % 4;
|
||||
if (times == 3) { Do(r + 4); return; }
|
||||
for (int i = 0; i < times; i++) Do(r);
|
||||
}
|
||||
void DoColUp(int c, int times) {
|
||||
times = ((times % 4) + 4) % 4;
|
||||
if (times == 3) { Do(c + 12); return; } // 1x ColDown is cheaper
|
||||
for (int i = 0; i < times; i++) Do(c + 8);
|
||||
}
|
||||
void DoColDown(int c, int times) {
|
||||
times = ((times % 4) + 4) % 4;
|
||||
if (times == 3) { Do(c + 8); return; }
|
||||
for (int i = 0; i < times; i++) Do(c + 12);
|
||||
}
|
||||
|
||||
// ── Phase 1: Solve Row 0 ─────────────────────────────
|
||||
// Use free column rotations + row shifts on rows 1-3.
|
||||
int C0 = tgtRows[0];
|
||||
for (int c = 0; c < 4; c++)
|
||||
{
|
||||
if (g[0,c] == C0) continue;
|
||||
|
||||
// Look in same column
|
||||
int foundRow = -1;
|
||||
for (int r = 1; r <= 3; r++)
|
||||
if (g[r,c] == C0) { foundRow = r; break; }
|
||||
|
||||
if (foundRow >= 0)
|
||||
{
|
||||
DoColUp(c, foundRow);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find C0 anywhere in rows 1-3
|
||||
bool found = false;
|
||||
for (int r = 1; r <= 3 && !found; r++)
|
||||
for (int c2 = 0; c2 < 4 && !found; c2++)
|
||||
{
|
||||
if (c2 == c) continue;
|
||||
if (g[r,c2] == C0)
|
||||
{
|
||||
DoRowRight(r, (c - c2 + 4) % 4);
|
||||
DoColUp(c, r);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) return null; // should never happen
|
||||
}
|
||||
}
|
||||
|
||||
// ── Phase 2: Solve Row 1 (protecting Row 0) ──────────
|
||||
// Commutator [RowLeft(1,k1), ColUp(c,k2), RowRight(1,k1), ColDown(c,k2)]
|
||||
// creates a 3-cycle in rows 1+ only. Row 0 stays intact.
|
||||
int C1 = tgtRows[1];
|
||||
for (int pass = 0; pass < 4; pass++)
|
||||
{
|
||||
int colW = -1;
|
||||
for (int c = 0; c < 4; c++)
|
||||
if (g[1,c] != C1) { colW = c; break; }
|
||||
if (colW < 0) break;
|
||||
|
||||
int srcR = -1, srcC = -1;
|
||||
for (int r = 2; r <= 3 && srcR < 0; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
if (g[r,c] == C1) { srcR = r; srcC = c; break; }
|
||||
if (srcR < 0) return null;
|
||||
|
||||
// Move C1 to (srcR, colW) via row shift (safe: rows 2-3 only)
|
||||
if (srcC != colW)
|
||||
DoRowRight(srcR, (colW - srcC + 4) % 4);
|
||||
|
||||
int k2 = srcR - 1; // 1 or 2
|
||||
DoRowLeft(1, 1);
|
||||
DoColUp(colW, k2);
|
||||
DoRowRight(1, 1);
|
||||
DoColDown(colW, k2);
|
||||
}
|
||||
|
||||
// ── Phase 3: Solve Rows 2-3 (protecting Rows 0-1) ───
|
||||
// Commutator with r1=2, k2=1 only touches rows 2-3.
|
||||
int C2 = tgtRows[2];
|
||||
for (int pass = 0; pass < 4; pass++)
|
||||
{
|
||||
int colW = -1;
|
||||
for (int c = 0; c < 4; c++)
|
||||
if (g[2,c] != C2) { colW = c; break; }
|
||||
if (colW < 0) break;
|
||||
|
||||
int srcC = -1;
|
||||
for (int c = 0; c < 4; c++)
|
||||
if (g[3,c] == C2) { srcC = c; break; }
|
||||
if (srcC < 0) return null;
|
||||
|
||||
if (srcC != colW)
|
||||
DoRowRight(3, (colW - srcC + 4) % 4);
|
||||
|
||||
DoRowLeft(2, 1);
|
||||
DoColUp(colW, 1);
|
||||
DoRowRight(2, 1);
|
||||
DoColDown(colW, 1);
|
||||
}
|
||||
|
||||
// Verify
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
if (g[r,c] != tgtRows[r]) return null;
|
||||
|
||||
// Optimize: remove consecutive inverse pairs
|
||||
bool changed = true;
|
||||
while (changed)
|
||||
{
|
||||
changed = false;
|
||||
for (int i = 0; i < moves.Count - 1; i++)
|
||||
{
|
||||
int a = moves[i], b = moves[i+1];
|
||||
bool cancel = false;
|
||||
if (a < 4 && b == a + 4) cancel = true;
|
||||
if (a >= 4 && a < 8 && b == a - 4) cancel = true;
|
||||
if (a >= 8 && a < 12 && b == a + 4) cancel = true;
|
||||
if (a >= 12 && b == a - 4) cancel = true;
|
||||
if (cancel)
|
||||
{
|
||||
moves.RemoveAt(i + 1);
|
||||
moves.RemoveAt(i);
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return moves;
|
||||
}
|
||||
|
||||
// ── 5. Check if already solved ────────────────────────────
|
||||
bool IsGridSolved(int[,] g)
|
||||
{
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
if (g[r,c] != targetRows[r]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsGridSolved(grid))
|
||||
{
|
||||
Log("Puzzle ist bereits geloest!");
|
||||
return;
|
||||
}
|
||||
|
||||
// ── 6. Solve ──────────────────────────────────────────────
|
||||
var solution = SolveLayerByLayer(grid, targetRows);
|
||||
if (solution == null || solution.Count == 0)
|
||||
{
|
||||
Log("ERROR: Solver konnte keine Loesung finden!");
|
||||
Log("Moegliche Gruende: Farb-Verteilung nicht 4x je Farbe, oder falsche Ziel-Zuordnung.");
|
||||
return;
|
||||
}
|
||||
|
||||
Log($"Loesung gefunden: {solution.Count} Moves");
|
||||
for (int i = 0; i < solution.Count; i++)
|
||||
Log($" {i+1}. {MoveName(solution[i])}");
|
||||
|
||||
// ── 7. Execute with verification ──────────────────────────
|
||||
// Track arrow direction flips (auto-detect reversed arrows)
|
||||
bool[] rowFlip = new bool[4];
|
||||
bool[] colFlip = new bool[4];
|
||||
|
||||
string KeyForMove(int m)
|
||||
{
|
||||
if (m < 4) {
|
||||
int r = m;
|
||||
return rowFlip[r] ? $"right_{r}" : $"left_{r}";
|
||||
}
|
||||
if (m < 8) {
|
||||
int r = m - 4;
|
||||
return rowFlip[r] ? $"left_{r}" : $"right_{r}";
|
||||
}
|
||||
if (m < 12) {
|
||||
int c = m - 8;
|
||||
return colFlip[c] ? $"down_{c}" : $"up_{c}";
|
||||
}
|
||||
int cc = m - 12;
|
||||
return colFlip[cc] ? $"up_{cc}" : $"down_{cc}";
|
||||
}
|
||||
|
||||
// Encode grid as uint for quick comparison
|
||||
uint EncodeGrid(int[,] g)
|
||||
{
|
||||
uint s = 0;
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
s |= ((uint)(g[r,c] & 3)) << (2 * (r * 4 + c));
|
||||
return s;
|
||||
}
|
||||
|
||||
// Compute expected state after a move (using bit ops for speed)
|
||||
uint ApplyMoveBits(uint s, int m)
|
||||
{
|
||||
if (m < 4) { // RowLeft
|
||||
int sh = m * 8;
|
||||
uint row = (s >> sh) & 0xFFu;
|
||||
uint rot = ((row >> 2) | (row << 6)) & 0xFFu;
|
||||
return (s & ~(0xFFu << sh)) | (rot << sh);
|
||||
}
|
||||
if (m < 8) { // RowRight
|
||||
int sh = (m-4) * 8;
|
||||
uint row = (s >> sh) & 0xFFu;
|
||||
uint rot = ((row << 2) | (row >> 6)) & 0xFFu;
|
||||
return (s & ~(0xFFu << sh)) | (rot << sh);
|
||||
}
|
||||
if (m < 12) { // ColUp
|
||||
int b = (m-8) * 2;
|
||||
uint v0=(s>>b)&3u, v1=(s>>(b+8))&3u, v2=(s>>(b+16))&3u, v3=(s>>(b+24))&3u;
|
||||
uint mask = ~(3u<<b | 3u<<(b+8) | 3u<<(b+16) | 3u<<(b+24));
|
||||
return (s&mask) | (v1<<b) | (v2<<(b+8)) | (v3<<(b+16)) | (v0<<(b+24));
|
||||
}
|
||||
{ // ColDown
|
||||
int b = (m-12) * 2;
|
||||
uint v0=(s>>b)&3u, v1=(s>>(b+8))&3u, v2=(s>>(b+16))&3u, v3=(s>>(b+24))&3u;
|
||||
uint mask = ~(3u<<b | 3u<<(b+8) | 3u<<(b+16) | 3u<<(b+24));
|
||||
return (s&mask) | (v3<<b) | (v0<<(b+8)) | (v1<<(b+16)) | (v2<<(b+24));
|
||||
}
|
||||
}
|
||||
|
||||
int InverseMove(int m)
|
||||
{
|
||||
if (m < 4) return m + 4;
|
||||
if (m < 8) return m - 4;
|
||||
if (m < 12) return m + 4;
|
||||
return m - 4;
|
||||
}
|
||||
|
||||
Log("\nFuehre Moves aus...");
|
||||
uint currentState = EncodeGrid(grid);
|
||||
uint goalState = EncodeGrid(new int[4,4]); // temp
|
||||
{
|
||||
int[,] tgt = new int[4,4];
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
tgt[r,c] = targetRows[r];
|
||||
goalState = EncodeGrid(tgt);
|
||||
}
|
||||
|
||||
int moveIdx = 0;
|
||||
int retries = 0;
|
||||
const int MAX_RETRIES = 3;
|
||||
|
||||
while (moveIdx < solution.Count)
|
||||
{
|
||||
if (currentState == goalState)
|
||||
{
|
||||
Log("=== Puzzle geloest! Alle 4 Reihen korrekt! ===");
|
||||
return;
|
||||
}
|
||||
|
||||
int move = solution[moveIdx];
|
||||
uint expected = ApplyMoveBits(currentState, move);
|
||||
string key = KeyForMove(move);
|
||||
|
||||
if (!arrowIds.ContainsKey(key))
|
||||
{
|
||||
Log($"ERROR: Arrow '{key}' nicht gefunden!");
|
||||
return;
|
||||
}
|
||||
|
||||
long id = arrowIds[key];
|
||||
Log($" [{moveIdx+1}/{solution.Count}] {MoveName(move)} via {key}");
|
||||
Send(Out["ClickFurni"], (int)id, 0);
|
||||
Delay(CLICK_DELAY_MS);
|
||||
|
||||
// Re-read grid to verify
|
||||
var newGrid = ReadGridFromRoom();
|
||||
if (newGrid == null)
|
||||
{
|
||||
Log("WARN: Grid-Read fehlgeschlagen, retry...");
|
||||
Delay(400);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint afterState = EncodeGrid(newGrid);
|
||||
|
||||
if (afterState == expected)
|
||||
{
|
||||
// Move worked as expected
|
||||
currentState = afterState;
|
||||
moveIdx++;
|
||||
retries = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if arrow direction was reversed
|
||||
uint invExpected = ApplyMoveBits(currentState, InverseMove(move));
|
||||
if (afterState == invExpected)
|
||||
{
|
||||
if (move < 8) {
|
||||
int r = move < 4 ? move : move - 4;
|
||||
rowFlip[r] = !rowFlip[r];
|
||||
Log($" Auto-Fix: Row {r} Richtung gespiegelt.");
|
||||
} else {
|
||||
int c = move < 12 ? move - 8 : move - 12;
|
||||
colFlip[c] = !colFlip[c];
|
||||
Log($" Auto-Fix: Col {c} Richtung gespiegelt.");
|
||||
}
|
||||
currentState = afterState;
|
||||
// Don't advance moveIdx - the move did the opposite, re-plan
|
||||
Log(" Re-plane von neuem Zustand...");
|
||||
grid = newGrid;
|
||||
solution = SolveLayerByLayer(grid, targetRows);
|
||||
if (solution == null) { Log("ERROR: Re-Plan fehlgeschlagen!"); return; }
|
||||
moveIdx = 0;
|
||||
retries = 0;
|
||||
Log($" Neuer Plan: {solution.Count} Moves");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (afterState == currentState)
|
||||
{
|
||||
// Click had no effect
|
||||
retries++;
|
||||
if (retries >= MAX_RETRIES)
|
||||
{
|
||||
Log("WARN: Klick ohne Effekt nach 3 Versuchen, re-plane...");
|
||||
grid = newGrid;
|
||||
solution = SolveLayerByLayer(grid, targetRows);
|
||||
if (solution == null) { Log("ERROR: Re-Plan fehlgeschlagen!"); return; }
|
||||
moveIdx = 0;
|
||||
retries = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(" Klick ohne Effekt, retry...");
|
||||
Delay(300);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Desync: grid changed unexpectedly (maybe another player or lag)
|
||||
Log($" Desync! Neuer Zustand: {GridDump(newGrid)}");
|
||||
Log(" Re-plane von neuem Zustand...");
|
||||
grid = newGrid;
|
||||
currentState = afterState;
|
||||
|
||||
if (IsGridSolved(grid))
|
||||
{
|
||||
Log("=== Puzzle geloest! Alle 4 Reihen korrekt! ===");
|
||||
return;
|
||||
}
|
||||
|
||||
solution = SolveLayerByLayer(grid, targetRows);
|
||||
if (solution == null) { Log("ERROR: Re-Plan fehlgeschlagen!"); return; }
|
||||
moveIdx = 0;
|
||||
retries = 0;
|
||||
Log($" Neuer Plan: {solution.Count} Moves");
|
||||
}
|
||||
|
||||
if (currentState == goalState)
|
||||
Log("=== Puzzle geloest! Alle 4 Reihen korrekt! ===");
|
||||
else
|
||||
Log("Alle Moves ausgefuehrt. Grid pruefen ob geloest.");
|
||||
133
Color Puzzle Queue Watcher.csx
Normal file
133
Color Puzzle Queue Watcher.csx
Normal file
@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
Log("started");
|
||||
|
||||
const string FURNI_NAME_CONTAINS_TEXT = "One Way Gate";
|
||||
Regex mvRegex = new Regex(@"/mv (\d+),(\d+),([\d\.]+)");
|
||||
|
||||
// Cache: trigger-tile -> (gateId, gateX, gateY, direction)
|
||||
Dictionary<(int x, int y), (long id, int gx, int gy, int dir)> triggerMap = new();
|
||||
|
||||
(int dx, int dy) GetTriggerOffset(int dir) => dir switch
|
||||
{
|
||||
0 => (0, -1),
|
||||
2 => (1, 0),
|
||||
4 => (0, 1),
|
||||
6 => (-1, 0),
|
||||
_ => (0, 0)
|
||||
};
|
||||
|
||||
void RebuildGateCache()
|
||||
{
|
||||
triggerMap.Clear();
|
||||
if (FloorItems == null) return;
|
||||
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item?.Location == null) continue;
|
||||
string name = null;
|
||||
try { name = item.GetName(); } catch { continue; }
|
||||
if (name == null || !name.Contains(FURNI_NAME_CONTAINS_TEXT)) continue;
|
||||
|
||||
var (dx, dy) = GetTriggerOffset(item.Direction);
|
||||
var trigger = (item.Location.X + dx, item.Location.Y + dy);
|
||||
triggerMap[trigger] = (item.Id, item.Location.X, item.Location.Y, item.Direction);
|
||||
}
|
||||
Log($"Gate cache built: {triggerMap.Count} gates indexed");
|
||||
}
|
||||
|
||||
void TryEnterGate(int userX, int userY)
|
||||
{
|
||||
if (triggerMap.TryGetValue((userX, userY), out var gate))
|
||||
{
|
||||
Log($"Match at ({userX},{userY}) for Gate ID {gate.id} at ({gate.gx},{gate.gy} Dir:{gate.dir}). Sending packet.");
|
||||
Send(Out.EnterOneWayDoor, gate.id);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleUserUpdate(dynamic e)
|
||||
{
|
||||
if (Self == null) return;
|
||||
var packet = e.Packet;
|
||||
int numUpdates = packet.ReadInt();
|
||||
for (int i = 0; i < numUpdates; i++)
|
||||
{
|
||||
int entityIndex = packet.ReadInt();
|
||||
int currentX = packet.ReadInt();
|
||||
int currentY = packet.ReadInt();
|
||||
packet.ReadString();
|
||||
packet.ReadInt();
|
||||
packet.ReadInt();
|
||||
string action = packet.ReadString();
|
||||
|
||||
if (entityIndex == Self.Index)
|
||||
{
|
||||
int checkX = currentX, checkY = currentY;
|
||||
Match match = mvRegex.Match(action);
|
||||
if (match.Success)
|
||||
{
|
||||
int.TryParse(match.Groups[1].Value, out checkX);
|
||||
int.TryParse(match.Groups[2].Value, out checkY);
|
||||
}
|
||||
TryEnterGate(checkX, checkY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleObjectUpdate(dynamic e)
|
||||
{
|
||||
if (Self?.Location == null) return;
|
||||
|
||||
var packet = e.Packet;
|
||||
int furniId = packet.ReadInt();
|
||||
packet.ReadInt();
|
||||
int itemX = packet.ReadInt();
|
||||
int itemY = packet.ReadInt();
|
||||
int newDir = packet.ReadInt();
|
||||
|
||||
// Update cache for this specific gate
|
||||
var item = FloorItems?.FirstOrDefault(f => f != null && f.Id == furniId);
|
||||
if (item != null)
|
||||
{
|
||||
string name = null;
|
||||
try { name = item.GetName(); } catch { return; }
|
||||
if (name != null && name.Contains(FURNI_NAME_CONTAINS_TEXT))
|
||||
{
|
||||
// Remove old trigger entry for this gate
|
||||
var toRemove = triggerMap.Where(kv => kv.Value.id == furniId).Select(kv => kv.Key).ToList();
|
||||
foreach (var key in toRemove) triggerMap.Remove(key);
|
||||
|
||||
// Add new trigger position
|
||||
var (dx, dy) = GetTriggerOffset(newDir);
|
||||
triggerMap[(itemX + dx, itemY + dy)] = (furniId, itemX, itemY, newDir);
|
||||
|
||||
// Check if user is on new trigger tile
|
||||
TryEnterGate(Self.Location.X, Self.Location.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnRoomReady(dynamic e)
|
||||
{
|
||||
RebuildGateCache();
|
||||
if (Self?.Location != null)
|
||||
TryEnterGate(Self.Location.X, Self.Location.Y);
|
||||
}
|
||||
|
||||
OnIntercept(In["UserUpdate"], e => HandleUserUpdate(e));
|
||||
OnIntercept(In["ObjectUpdate"], e => HandleObjectUpdate(e));
|
||||
OnEnteredRoom(e => OnRoomReady(e));
|
||||
|
||||
// Sofort beim Start Cache bauen und prüfen (schon im Raum)
|
||||
RebuildGateCache();
|
||||
if (Self?.Location != null)
|
||||
TryEnterGate(Self.Location.X, Self.Location.Y);
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(30);
|
||||
}
|
||||
Log("closed");
|
||||
577
Color Puzzle Solver 4-Row Fix.csx
Normal file
577
Color Puzzle Solver 4-Row Fix.csx
Normal file
@ -0,0 +1,577 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
// Color Puzzle Solver (fix): solves all 4 rows reliably.
|
||||
// Keeps desync handling + auto direction flip detection.
|
||||
|
||||
const int TILE_KIND = 3696;
|
||||
const int ARROW_KIND = 17851;
|
||||
const int GRID_X_MIN = 36;
|
||||
const int GRID_X_MAX = 39;
|
||||
const int GRID_Y_MIN = 27;
|
||||
const int GRID_Y_MAX = 30;
|
||||
const int CLICK_DELAY_MS = 950;
|
||||
|
||||
int GetState(dynamic item)
|
||||
{
|
||||
try { return int.Parse(item.State?.ToString() ?? "0"); }
|
||||
catch { return 0; }
|
||||
}
|
||||
|
||||
int GetKind(dynamic item)
|
||||
{
|
||||
try { return (int)item.Kind; }
|
||||
catch { return -1; }
|
||||
}
|
||||
|
||||
int[,] ReadGridFromRoom()
|
||||
{
|
||||
int[,] g = new int[4, 4];
|
||||
bool[,] found = new bool[4, 4];
|
||||
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != TILE_KIND) continue;
|
||||
|
||||
int x = item.Location.X;
|
||||
int y = item.Location.Y;
|
||||
double z = item.Location.Z;
|
||||
|
||||
if (x < GRID_X_MIN || x > GRID_X_MAX) continue;
|
||||
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
|
||||
if (z < 18.4) continue;
|
||||
|
||||
int r = y - GRID_Y_MIN;
|
||||
int c = x - GRID_X_MIN;
|
||||
g[r, c] = GetState(item);
|
||||
found[r, c] = true;
|
||||
}
|
||||
|
||||
int cnt = 0;
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
if (found[r, c]) cnt++;
|
||||
|
||||
return cnt == 16 ? g : null;
|
||||
}
|
||||
|
||||
string GridDump(int[,] g)
|
||||
{
|
||||
return string.Join(" | ", Enumerable.Range(0, 4).Select(r =>
|
||||
$"R{r}[{g[r,0]},{g[r,1]},{g[r,2]},{g[r,3]}]"));
|
||||
}
|
||||
|
||||
void SimMove(int[,] g, int m)
|
||||
{
|
||||
if (m < 4)
|
||||
{
|
||||
int r = m;
|
||||
int t = g[r, 0]; g[r, 0] = g[r, 1]; g[r, 1] = g[r, 2]; g[r, 2] = g[r, 3]; g[r, 3] = t;
|
||||
}
|
||||
else if (m < 8)
|
||||
{
|
||||
int r = m - 4;
|
||||
int t = g[r, 3]; g[r, 3] = g[r, 2]; g[r, 2] = g[r, 1]; g[r, 1] = g[r, 0]; g[r, 0] = t;
|
||||
}
|
||||
else if (m < 12)
|
||||
{
|
||||
int c = m - 8;
|
||||
int t = g[0, c]; g[0, c] = g[1, c]; g[1, c] = g[2, c]; g[2, c] = g[3, c]; g[3, c] = t;
|
||||
}
|
||||
else
|
||||
{
|
||||
int c = m - 12;
|
||||
int t = g[3, c]; g[3, c] = g[2, c]; g[2, c] = g[1, c]; g[1, c] = g[0, c]; g[0, c] = t;
|
||||
}
|
||||
}
|
||||
|
||||
List<int> SolveLayerByLayer(int[,] srcGrid, int[] tgtRows)
|
||||
{
|
||||
int[,] g = new int[4, 4];
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
g[r, c] = srcGrid[r, c];
|
||||
|
||||
var moves = new List<int>();
|
||||
|
||||
void Do(int m) { moves.Add(m); SimMove(g, m); }
|
||||
|
||||
void DoRowRight(int r, int times)
|
||||
{
|
||||
times = ((times % 4) + 4) % 4;
|
||||
if (times == 3) { Do(r); return; }
|
||||
for (int i = 0; i < times; i++) Do(r + 4);
|
||||
}
|
||||
void DoRowLeft(int r, int times)
|
||||
{
|
||||
times = ((times % 4) + 4) % 4;
|
||||
if (times == 3) { Do(r + 4); return; }
|
||||
for (int i = 0; i < times; i++) Do(r);
|
||||
}
|
||||
void DoColUp(int c, int times)
|
||||
{
|
||||
times = ((times % 4) + 4) % 4;
|
||||
if (times == 3) { Do(c + 12); return; }
|
||||
for (int i = 0; i < times; i++) Do(c + 8);
|
||||
}
|
||||
void DoColDown(int c, int times)
|
||||
{
|
||||
times = ((times % 4) + 4) % 4;
|
||||
if (times == 3) { Do(c + 8); return; }
|
||||
for (int i = 0; i < times; i++) Do(c + 12);
|
||||
}
|
||||
|
||||
int C0 = tgtRows[0];
|
||||
for (int c = 0; c < 4; c++)
|
||||
{
|
||||
if (g[0, c] == C0) continue;
|
||||
|
||||
int foundRow = -1;
|
||||
for (int r = 1; r <= 3; r++)
|
||||
if (g[r, c] == C0) { foundRow = r; break; }
|
||||
|
||||
if (foundRow >= 0)
|
||||
{
|
||||
DoColUp(c, foundRow);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool found = false;
|
||||
for (int r = 1; r <= 3 && !found; r++)
|
||||
for (int c2 = 0; c2 < 4 && !found; c2++)
|
||||
if (c2 != c && g[r, c2] == C0)
|
||||
{
|
||||
DoRowRight(r, (c - c2 + 4) % 4);
|
||||
DoColUp(c, r);
|
||||
found = true;
|
||||
}
|
||||
if (!found) return null;
|
||||
}
|
||||
}
|
||||
|
||||
int C1 = tgtRows[1];
|
||||
for (int pass = 0; pass < 8; pass++)
|
||||
{
|
||||
int colW = -1;
|
||||
for (int c = 0; c < 4; c++) if (g[1, c] != C1) { colW = c; break; }
|
||||
if (colW < 0) break;
|
||||
|
||||
int srcR = -1, srcC = -1;
|
||||
for (int r = 2; r <= 3 && srcR < 0; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
if (g[r, c] == C1) { srcR = r; srcC = c; break; }
|
||||
if (srcR < 0) return null;
|
||||
|
||||
if (srcC != colW) DoRowRight(srcR, (colW - srcC + 4) % 4);
|
||||
|
||||
int k2 = srcR - 1;
|
||||
DoRowLeft(1, 1);
|
||||
DoColUp(colW, k2);
|
||||
DoRowRight(1, 1);
|
||||
DoColDown(colW, k2);
|
||||
}
|
||||
|
||||
int C2 = tgtRows[2];
|
||||
for (int pass = 0; pass < 8; pass++)
|
||||
{
|
||||
int colW = -1;
|
||||
for (int c = 0; c < 4; c++) if (g[2, c] != C2) { colW = c; break; }
|
||||
if (colW < 0) break;
|
||||
|
||||
int srcC = -1;
|
||||
for (int c = 0; c < 4; c++) if (g[3, c] == C2) { srcC = c; break; }
|
||||
if (srcC < 0) return null;
|
||||
|
||||
if (srcC != colW) DoRowRight(3, (colW - srcC + 4) % 4);
|
||||
|
||||
DoRowLeft(2, 1);
|
||||
DoColUp(colW, 1);
|
||||
DoRowRight(2, 1);
|
||||
DoColDown(colW, 1);
|
||||
}
|
||||
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
if (g[r, c] != tgtRows[r]) return null;
|
||||
|
||||
bool changed = true;
|
||||
while (changed)
|
||||
{
|
||||
changed = false;
|
||||
for (int i = 0; i < moves.Count - 1; i++)
|
||||
{
|
||||
int a = moves[i], b = moves[i + 1];
|
||||
bool cancel = false;
|
||||
if (a < 4 && b == a + 4) cancel = true;
|
||||
if (a >= 4 && a < 8 && b == a - 4) cancel = true;
|
||||
if (a >= 8 && a < 12 && b == a + 4) cancel = true;
|
||||
if (a >= 12 && b == a - 4) cancel = true;
|
||||
if (cancel)
|
||||
{
|
||||
moves.RemoveAt(i + 1);
|
||||
moves.RemoveAt(i);
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return moves;
|
||||
}
|
||||
|
||||
bool IsSolvedForTarget(int[,] g, int[] targetRows)
|
||||
{
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
if (g[r, c] != targetRows[r]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryReadTargetRows(out int[] targetRows)
|
||||
{
|
||||
targetRows = null;
|
||||
|
||||
var byX = new Dictionary<int, (int[] states, bool[] found, int count)>();
|
||||
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != TILE_KIND) continue;
|
||||
|
||||
int x = item.Location.X;
|
||||
int y = item.Location.Y;
|
||||
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
|
||||
if (x >= GRID_X_MIN && x <= GRID_X_MAX) continue;
|
||||
|
||||
if (!byX.ContainsKey(x))
|
||||
byX[x] = (new int[4], new bool[4], 0);
|
||||
|
||||
var entry = byX[x];
|
||||
int r = y - GRID_Y_MIN;
|
||||
if (!entry.found[r])
|
||||
{
|
||||
entry.states[r] = GetState(item);
|
||||
entry.found[r] = true;
|
||||
entry.count++;
|
||||
byX[x] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
if (byX.Count == 0) return false;
|
||||
|
||||
var best = byX
|
||||
.Select(kvp => new
|
||||
{
|
||||
X = kvp.Key,
|
||||
States = kvp.Value.states,
|
||||
Count = kvp.Value.count,
|
||||
Dist = kvp.Key < GRID_X_MIN ? (GRID_X_MIN - kvp.Key) : (kvp.Key - GRID_X_MAX)
|
||||
})
|
||||
.OrderByDescending(x => x.Count)
|
||||
.ThenBy(x => x.Dist)
|
||||
.First();
|
||||
|
||||
if (best.Count < 4) return false;
|
||||
|
||||
targetRows = new int[4];
|
||||
for (int r = 0; r < 4; r++) targetRows[r] = best.States[r];
|
||||
return true;
|
||||
}
|
||||
|
||||
uint EncodeGrid(int[,] g)
|
||||
{
|
||||
uint s = 0;
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
s |= ((uint)(g[r, c] & 3)) << (2 * (r * 4 + c));
|
||||
return s;
|
||||
}
|
||||
|
||||
uint ApplyMoveBits(uint s, int m)
|
||||
{
|
||||
if (m < 4)
|
||||
{
|
||||
int sh = m * 8;
|
||||
uint row = (s >> sh) & 0xFFu;
|
||||
uint rot = ((row >> 2) | (row << 6)) & 0xFFu;
|
||||
return (s & ~(0xFFu << sh)) | (rot << sh);
|
||||
}
|
||||
if (m < 8)
|
||||
{
|
||||
int sh = (m - 4) * 8;
|
||||
uint row = (s >> sh) & 0xFFu;
|
||||
uint rot = ((row << 2) | (row >> 6)) & 0xFFu;
|
||||
return (s & ~(0xFFu << sh)) | (rot << sh);
|
||||
}
|
||||
if (m < 12)
|
||||
{
|
||||
int b = (m - 8) * 2;
|
||||
uint v0 = (s >> b) & 3u, v1 = (s >> (b + 8)) & 3u, v2 = (s >> (b + 16)) & 3u, v3 = (s >> (b + 24)) & 3u;
|
||||
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
|
||||
return (s & mask) | (v1 << b) | (v2 << (b + 8)) | (v3 << (b + 16)) | (v0 << (b + 24));
|
||||
}
|
||||
{
|
||||
int b = (m - 12) * 2;
|
||||
uint v0 = (s >> b) & 3u, v1 = (s >> (b + 8)) & 3u, v2 = (s >> (b + 16)) & 3u, v3 = (s >> (b + 24)) & 3u;
|
||||
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
|
||||
return (s & mask) | (v3 << b) | (v0 << (b + 8)) | (v1 << (b + 16)) | (v2 << (b + 24));
|
||||
}
|
||||
}
|
||||
|
||||
int InverseMove(int m)
|
||||
{
|
||||
if (m < 4) return m + 4;
|
||||
if (m < 8) return m - 4;
|
||||
if (m < 12) return m + 4;
|
||||
return m - 4;
|
||||
}
|
||||
|
||||
string MoveName(int m)
|
||||
{
|
||||
if (m < 4) return $"Row{m} LEFT";
|
||||
if (m < 8) return $"Row{m - 4} RIGHT";
|
||||
if (m < 12) return $"Col{m - 8} UP";
|
||||
return $"Col{m - 12} DOWN";
|
||||
}
|
||||
|
||||
int[,] ApplyMovesToCopy(int[,] src, List<int> moves)
|
||||
{
|
||||
int[,] g = new int[4, 4];
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
g[r, c] = src[r, c];
|
||||
foreach (int m in moves) SimMove(g, m);
|
||||
return g;
|
||||
}
|
||||
|
||||
Log("=== Color Puzzle Solver (fix all rows) ===");
|
||||
|
||||
var grid = ReadGridFromRoom();
|
||||
if (grid == null)
|
||||
{
|
||||
Log("ERROR: Could not read full 4x4 grid.");
|
||||
return;
|
||||
}
|
||||
Log($"Start: {GridDump(grid)}");
|
||||
|
||||
var arrowIds = new Dictionary<string, long>();
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != ARROW_KIND) continue;
|
||||
|
||||
int x = item.Location.X, y = item.Location.Y;
|
||||
if (y == GRID_Y_MIN - 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
|
||||
arrowIds[$"up_{x - GRID_X_MIN}"] = item.Id;
|
||||
else if (y == GRID_Y_MAX + 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
|
||||
arrowIds[$"down_{x - GRID_X_MIN}"] = item.Id;
|
||||
else if (x == GRID_X_MIN - 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
|
||||
arrowIds[$"left_{y - GRID_Y_MIN}"] = item.Id;
|
||||
else if (x == GRID_X_MAX + 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
|
||||
arrowIds[$"right_{y - GRID_Y_MIN}"] = item.Id;
|
||||
}
|
||||
|
||||
if (arrowIds.Count < 16)
|
||||
{
|
||||
Log($"ERROR: Missing arrows ({arrowIds.Count}/16).");
|
||||
return;
|
||||
}
|
||||
|
||||
int[] detectedTarget;
|
||||
if (!TryReadTargetRows(out detectedTarget))
|
||||
{
|
||||
detectedTarget = new[] { 1, 2, 3, 0 };
|
||||
Log("WARN: Target tiles not fully detected, using fallback target rows 1,2,3,0.");
|
||||
}
|
||||
|
||||
var candidateTargets = new List<int[]>();
|
||||
void AddTargetCandidate(int[] t)
|
||||
{
|
||||
if (t == null || t.Length != 4) return;
|
||||
if (!candidateTargets.Any(x => x[0] == t[0] && x[1] == t[1] && x[2] == t[2] && x[3] == t[3]))
|
||||
candidateTargets.Add(new[] { t[0], t[1], t[2], t[3] });
|
||||
}
|
||||
|
||||
AddTargetCandidate(detectedTarget);
|
||||
AddTargetCandidate(new[] { detectedTarget[3], detectedTarget[2], detectedTarget[1], detectedTarget[0] });
|
||||
AddTargetCandidate(new[] { 1, 2, 3, 0 });
|
||||
AddTargetCandidate(new[] { 0, 3, 2, 1 });
|
||||
|
||||
List<int> solution = null;
|
||||
int[] targetRows = null;
|
||||
|
||||
foreach (var candidate in candidateTargets)
|
||||
{
|
||||
var s = SolveLayerByLayer(grid, candidate);
|
||||
if (s == null || s.Count == 0) continue;
|
||||
|
||||
var check = ApplyMovesToCopy(grid, s);
|
||||
if (!IsSolvedForTarget(check, candidate)) continue;
|
||||
|
||||
if (solution == null || s.Count < solution.Count)
|
||||
{
|
||||
solution = s;
|
||||
targetRows = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (solution == null || targetRows == null)
|
||||
{
|
||||
Log("ERROR: Could not build a valid full 4-row plan.");
|
||||
return;
|
||||
}
|
||||
|
||||
Log($"Target rows chosen: R0={targetRows[0]}, R1={targetRows[1]}, R2={targetRows[2]}, R3={targetRows[3]}");
|
||||
Log($"Plan length: {solution.Count} moves");
|
||||
|
||||
bool[] rowFlip = new bool[4];
|
||||
bool[] colFlip = new bool[4];
|
||||
|
||||
string KeyForMove(int m)
|
||||
{
|
||||
if (m < 4) { int r = m; return rowFlip[r] ? $"right_{r}" : $"left_{r}"; }
|
||||
if (m < 8) { int r = m - 4; return rowFlip[r] ? $"left_{r}" : $"right_{r}"; }
|
||||
if (m < 12) { int c = m - 8; return colFlip[c] ? $"down_{c}" : $"up_{c}"; }
|
||||
int cc = m - 12; return colFlip[cc] ? $"up_{cc}" : $"down_{cc}";
|
||||
}
|
||||
|
||||
int[,] tgtGrid = new int[4, 4];
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
tgtGrid[r, c] = targetRows[r];
|
||||
|
||||
uint goalState = EncodeGrid(tgtGrid);
|
||||
uint currentState = EncodeGrid(grid);
|
||||
|
||||
int moveIdx = 0;
|
||||
int retries = 0;
|
||||
const int MAX_RETRIES = 3;
|
||||
|
||||
while (moveIdx < solution.Count)
|
||||
{
|
||||
if (currentState == goalState)
|
||||
{
|
||||
Log("=== Solved: all 4 rows complete ===");
|
||||
return;
|
||||
}
|
||||
|
||||
int move = solution[moveIdx];
|
||||
uint expected = ApplyMoveBits(currentState, move);
|
||||
string key = KeyForMove(move);
|
||||
|
||||
if (!arrowIds.ContainsKey(key))
|
||||
{
|
||||
Log($"ERROR: Arrow '{key}' not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
long id = arrowIds[key];
|
||||
Log($"[{moveIdx + 1}/{solution.Count}] {MoveName(move)} via {key}");
|
||||
Send(Out["ClickFurni"], (int)id, 0);
|
||||
Delay(CLICK_DELAY_MS);
|
||||
|
||||
var newGrid = ReadGridFromRoom();
|
||||
if (newGrid == null)
|
||||
{
|
||||
Log("WARN: Grid read failed, retry...");
|
||||
Delay(400);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint afterState = EncodeGrid(newGrid);
|
||||
|
||||
if (afterState == expected)
|
||||
{
|
||||
currentState = afterState;
|
||||
moveIdx++;
|
||||
retries = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint invExpected = ApplyMoveBits(currentState, InverseMove(move));
|
||||
if (afterState == invExpected)
|
||||
{
|
||||
if (move < 8)
|
||||
{
|
||||
int r = move < 4 ? move : move - 4;
|
||||
rowFlip[r] = !rowFlip[r];
|
||||
Log($"Auto-fix: Row {r} direction flipped.");
|
||||
}
|
||||
else
|
||||
{
|
||||
int c = move < 12 ? move - 8 : move - 12;
|
||||
colFlip[c] = !colFlip[c];
|
||||
Log($"Auto-fix: Col {c} direction flipped.");
|
||||
}
|
||||
|
||||
grid = newGrid;
|
||||
currentState = afterState;
|
||||
|
||||
var replan = SolveLayerByLayer(grid, targetRows);
|
||||
if (replan == null)
|
||||
{
|
||||
Log("ERROR: Replan failed after direction flip.");
|
||||
return;
|
||||
}
|
||||
|
||||
solution = replan;
|
||||
moveIdx = 0;
|
||||
retries = 0;
|
||||
Log($"Replan: {solution.Count} moves");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (afterState == currentState)
|
||||
{
|
||||
retries++;
|
||||
if (retries >= MAX_RETRIES)
|
||||
{
|
||||
Log("WARN: Click had no effect multiple times, replan.");
|
||||
grid = newGrid;
|
||||
var replan = SolveLayerByLayer(grid, targetRows);
|
||||
if (replan == null)
|
||||
{
|
||||
Log("ERROR: Replan failed after no-effect clicks.");
|
||||
return;
|
||||
}
|
||||
solution = replan;
|
||||
moveIdx = 0;
|
||||
retries = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("Click no effect, retrying...");
|
||||
Delay(300);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Log($"Desync detected. New grid: {GridDump(newGrid)}");
|
||||
grid = newGrid;
|
||||
currentState = afterState;
|
||||
|
||||
if (IsSolvedForTarget(grid, targetRows))
|
||||
{
|
||||
Log("=== Solved: all 4 rows complete ===");
|
||||
return;
|
||||
}
|
||||
|
||||
var desyncReplan = SolveLayerByLayer(grid, targetRows);
|
||||
if (desyncReplan == null)
|
||||
{
|
||||
Log("ERROR: Replan failed after desync.");
|
||||
return;
|
||||
}
|
||||
|
||||
solution = desyncReplan;
|
||||
moveIdx = 0;
|
||||
retries = 0;
|
||||
Log($"Replan after desync: {solution.Count} moves");
|
||||
}
|
||||
|
||||
if (currentState == goalState)
|
||||
Log("=== Solved: all 4 rows complete ===");
|
||||
else
|
||||
Log("Moves done. Please check if room state changed externally.");
|
||||
477
Color Puzzle Solver Simple.csx
Normal file
477
Color Puzzle Solver Simple.csx
Normal file
@ -0,0 +1,477 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
// Color Puzzle Solver v2
|
||||
// - Auto calibration of arrow -> move mapping
|
||||
// - Waits for real state change after every click
|
||||
|
||||
const int TILE_KIND = 3696;
|
||||
const int ARROW_KIND = 17851;
|
||||
const int GRID_X_MIN = 36;
|
||||
const int GRID_X_MAX = 39;
|
||||
const int GRID_Y_MIN = 27;
|
||||
const int GRID_Y_MAX = 30;
|
||||
|
||||
const int CLICK_SETTLE_DELAY_MS = 250;
|
||||
const int WAIT_CHANGE_TIMEOUT_MS = 6000;
|
||||
const int WAIT_CHANGE_POLL_MS = 120;
|
||||
const int MAX_STEPS = 140;
|
||||
const int BFS_MAX_NODES = 4_000_000;
|
||||
const int IDA_MAX_SEC = 12;
|
||||
|
||||
const bool AUTO_QUEUE_START = true;
|
||||
const long TRANSPORTER_ID = 759030883;
|
||||
const int QUEUE_CLICK_INTERVAL_MS = 5000;
|
||||
const int WAIT_PUZZLE_POLL_MS = 250;
|
||||
const int WAIT_PUZZLE_LOG_MS = 5000;
|
||||
const bool REQUIRE_SELF_IN_PLAYZONE = true;
|
||||
const int PLAY_X_MIN = 34;
|
||||
const int PLAY_X_MAX = 41;
|
||||
const int PLAY_Y_MIN = 26;
|
||||
const int PLAY_Y_MAX = 31;
|
||||
|
||||
int GetState(dynamic item)
|
||||
{
|
||||
try { return int.Parse(item.State?.ToString() ?? "0"); }
|
||||
catch { return 0; }
|
||||
}
|
||||
|
||||
int GetKind(dynamic item)
|
||||
{
|
||||
try { return (int)item.Kind; }
|
||||
catch { return -1; }
|
||||
}
|
||||
|
||||
uint EncodeGrid(int[,] g)
|
||||
{
|
||||
uint s = 0;
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
s |= ((uint)(g[r, c] & 3)) << (2 * (r * 4 + c));
|
||||
return s;
|
||||
}
|
||||
|
||||
bool TryReadGrid(out uint state, out string dump)
|
||||
{
|
||||
int[,] grid = new int[4, 4];
|
||||
bool[,] found = new bool[4, 4];
|
||||
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != TILE_KIND) continue;
|
||||
|
||||
int x = item.Location.X;
|
||||
int y = item.Location.Y;
|
||||
double z = item.Location.Z;
|
||||
|
||||
if (x < GRID_X_MIN || x > GRID_X_MAX) continue;
|
||||
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
|
||||
if (z < 18.4) continue;
|
||||
|
||||
int row = y - GRID_Y_MIN;
|
||||
int col = x - GRID_X_MIN;
|
||||
grid[row, col] = GetState(item);
|
||||
found[row, col] = true;
|
||||
}
|
||||
|
||||
int cnt = 0;
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
if (found[r, c]) cnt++;
|
||||
|
||||
if (cnt < 16)
|
||||
{
|
||||
state = 0;
|
||||
dump = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
state = EncodeGrid(grid);
|
||||
dump = string.Join(" | ", Enumerable.Range(0, 4).Select(r =>
|
||||
$"R{r}[{grid[r,0]},{grid[r,1]},{grid[r,2]},{grid[r,3]}]"));
|
||||
return true;
|
||||
}
|
||||
|
||||
uint RowLeft(uint s, int r)
|
||||
{
|
||||
int sh = r * 8;
|
||||
uint row = (s >> sh) & 0xFFu;
|
||||
uint rot = ((row >> 2) | (row << 6)) & 0xFFu;
|
||||
return (s & ~(0xFFu << sh)) | (rot << sh);
|
||||
}
|
||||
|
||||
uint RowRight(uint s, int r)
|
||||
{
|
||||
int sh = r * 8;
|
||||
uint row = (s >> sh) & 0xFFu;
|
||||
uint rot = ((row << 2) | (row >> 6)) & 0xFFu;
|
||||
return (s & ~(0xFFu << sh)) | (rot << sh);
|
||||
}
|
||||
|
||||
uint ColUp(uint s, int c)
|
||||
{
|
||||
int b = c * 2;
|
||||
uint v0 = (s >> b) & 3u;
|
||||
uint v1 = (s >> (b + 8)) & 3u;
|
||||
uint v2 = (s >> (b + 16)) & 3u;
|
||||
uint v3 = (s >> (b + 24)) & 3u;
|
||||
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
|
||||
return (s & mask) | (v1 << b) | (v2 << (b + 8)) | (v3 << (b + 16)) | (v0 << (b + 24));
|
||||
}
|
||||
|
||||
uint ColDown(uint s, int c)
|
||||
{
|
||||
int b = c * 2;
|
||||
uint v0 = (s >> b) & 3u;
|
||||
uint v1 = (s >> (b + 8)) & 3u;
|
||||
uint v2 = (s >> (b + 16)) & 3u;
|
||||
uint v3 = (s >> (b + 24)) & 3u;
|
||||
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
|
||||
return (s & mask) | (v3 << b) | (v0 << (b + 8)) | (v1 << (b + 16)) | (v2 << (b + 24));
|
||||
}
|
||||
|
||||
uint ApplyMove(uint s, int m)
|
||||
{
|
||||
if (m < 4) return RowLeft(s, m);
|
||||
if (m < 8) return RowRight(s, m - 4);
|
||||
if (m < 12) return ColUp(s, m - 8);
|
||||
return ColDown(s, m - 12);
|
||||
}
|
||||
|
||||
int InverseMove(int m)
|
||||
{
|
||||
if (m < 4) return m + 4;
|
||||
if (m < 8) return m - 4;
|
||||
if (m < 12) return m + 4;
|
||||
return m - 4;
|
||||
}
|
||||
|
||||
string MoveName(int m)
|
||||
{
|
||||
if (m < 4) return $"Row{m} LEFT";
|
||||
if (m < 8) return $"Row{m - 4} RIGHT";
|
||||
if (m < 12) return $"Col{m - 8} UP";
|
||||
return $"Col{m - 12} DOWN";
|
||||
}
|
||||
|
||||
int DetectMove(uint before, uint after)
|
||||
{
|
||||
int hit = -1;
|
||||
for (int m = 0; m < 16; m++)
|
||||
{
|
||||
if (ApplyMove(before, m) != after) continue;
|
||||
if (hit != -1) return -2;
|
||||
hit = m;
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
List<int> SolveBfs(uint start, uint goal)
|
||||
{
|
||||
if (start == goal) return new List<int>();
|
||||
|
||||
var visited = new Dictionary<uint, (uint parent, int move)>();
|
||||
var queue = new Queue<uint>();
|
||||
visited[start] = (start, -1);
|
||||
queue.Enqueue(start);
|
||||
int nodes = 0;
|
||||
bool found = false;
|
||||
|
||||
while (queue.Count > 0 && nodes < BFS_MAX_NODES)
|
||||
{
|
||||
uint cur = queue.Dequeue();
|
||||
nodes++;
|
||||
|
||||
for (int m = 0; m < 16; m++)
|
||||
{
|
||||
uint nxt = ApplyMove(cur, m);
|
||||
if (visited.ContainsKey(nxt)) continue;
|
||||
visited[nxt] = (cur, m);
|
||||
if (nxt == goal)
|
||||
{
|
||||
found = true;
|
||||
queue.Clear();
|
||||
break;
|
||||
}
|
||||
queue.Enqueue(nxt);
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) return null;
|
||||
|
||||
var sol = new List<int>();
|
||||
uint s = goal;
|
||||
while (s != start)
|
||||
{
|
||||
var p = visited[s];
|
||||
sol.Add(p.move);
|
||||
s = p.parent;
|
||||
}
|
||||
sol.Reverse();
|
||||
return sol;
|
||||
}
|
||||
|
||||
List<int> SolveIda(uint start, uint goal)
|
||||
{
|
||||
if (start == goal) return new List<int>();
|
||||
var t0 = DateTime.Now;
|
||||
|
||||
int H(uint st)
|
||||
{
|
||||
int mis = 0;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
int a = (int)((st >> (i * 2)) & 3u);
|
||||
int b = (int)((goal >> (i * 2)) & 3u);
|
||||
if (a != b) mis++;
|
||||
}
|
||||
return (mis + 3) / 4;
|
||||
}
|
||||
|
||||
List<int> best = null;
|
||||
bool timeout = false;
|
||||
|
||||
bool Dfs(uint st, List<int> path, int maxDepth)
|
||||
{
|
||||
if (timeout) return false;
|
||||
if ((DateTime.Now - t0).TotalSeconds > IDA_MAX_SEC)
|
||||
{
|
||||
timeout = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (st == goal)
|
||||
{
|
||||
best = new List<int>(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
int h = H(st);
|
||||
if (path.Count + h > maxDepth) return false;
|
||||
|
||||
int block = path.Count > 0 ? InverseMove(path[path.Count - 1]) : -1;
|
||||
for (int m = 0; m < 16; m++)
|
||||
{
|
||||
if (m == block) continue;
|
||||
path.Add(m);
|
||||
if (Dfs(ApplyMove(st, m), path, maxDepth)) return true;
|
||||
path.RemoveAt(path.Count - 1);
|
||||
if (timeout) return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int d0 = H(start);
|
||||
for (int d = d0; d <= 22 && !timeout; d++)
|
||||
{
|
||||
if (Dfs(start, new List<int>(), d)) break;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
List<int> Solve(uint start, uint goal)
|
||||
{
|
||||
var bfs = SolveBfs(start, goal);
|
||||
if (bfs != null) return bfs;
|
||||
return SolveIda(start, goal);
|
||||
}
|
||||
|
||||
bool ClickAndWaitChange(long furniId, uint before, out uint after, out string dumpAfter)
|
||||
{
|
||||
Send(Out["ClickFurni"], (int)furniId, 0);
|
||||
Delay(CLICK_SETTLE_DELAY_MS);
|
||||
|
||||
int waited = 0;
|
||||
while (waited < WAIT_CHANGE_TIMEOUT_MS)
|
||||
{
|
||||
if (TryReadGrid(out after, out dumpAfter) && after != before)
|
||||
return true;
|
||||
Delay(WAIT_CHANGE_POLL_MS);
|
||||
waited += WAIT_CHANGE_POLL_MS;
|
||||
}
|
||||
|
||||
after = before;
|
||||
dumpAfter = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
Dictionary<string, long> ReadArrowIds()
|
||||
{
|
||||
var arrowIds = new Dictionary<string, long>();
|
||||
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != ARROW_KIND) continue;
|
||||
int x = item.Location.X;
|
||||
int y = item.Location.Y;
|
||||
|
||||
if (y == GRID_Y_MIN - 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
|
||||
arrowIds[$"up_{x - GRID_X_MIN}"] = item.Id;
|
||||
else if (y == GRID_Y_MAX + 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
|
||||
arrowIds[$"down_{x - GRID_X_MIN}"] = item.Id;
|
||||
else if (x == GRID_X_MIN - 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
|
||||
arrowIds[$"left_{y - GRID_Y_MIN}"] = item.Id;
|
||||
else if (x == GRID_X_MAX + 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
|
||||
arrowIds[$"right_{y - GRID_Y_MIN}"] = item.Id;
|
||||
}
|
||||
|
||||
return arrowIds;
|
||||
}
|
||||
|
||||
bool IsSelfInPlayZone()
|
||||
{
|
||||
try
|
||||
{
|
||||
int x = Self.Location.X;
|
||||
int y = Self.Location.Y;
|
||||
return x >= PLAY_X_MIN && x <= PLAY_X_MAX && y >= PLAY_Y_MIN && y <= PLAY_Y_MAX;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Log("=== Color Puzzle Auto-Solver (AutoCalib + WaitChange) ===");
|
||||
|
||||
Dictionary<string, long> arrowIds = null;
|
||||
uint current;
|
||||
string dumpNow;
|
||||
|
||||
int sinceQueueClick = QUEUE_CLICK_INTERVAL_MS;
|
||||
int sinceLog = WAIT_PUZZLE_LOG_MS;
|
||||
|
||||
while (true)
|
||||
{
|
||||
bool hasGrid = TryReadGrid(out current, out dumpNow);
|
||||
var probeArrows = ReadArrowIds();
|
||||
bool hasArrows = probeArrows.Count == 16;
|
||||
bool inPlayZone = !REQUIRE_SELF_IN_PLAYZONE || IsSelfInPlayZone();
|
||||
|
||||
if (hasGrid && hasArrows && inPlayZone)
|
||||
{
|
||||
arrowIds = probeArrows;
|
||||
break;
|
||||
}
|
||||
|
||||
if (AUTO_QUEUE_START && sinceQueueClick >= QUEUE_CLICK_INTERVAL_MS)
|
||||
{
|
||||
Send(Out["ClickFurni"], (int)TRANSPORTER_ID, 0);
|
||||
Log($"Queue: Klick Transporter {TRANSPORTER_ID}...");
|
||||
sinceQueueClick = 0;
|
||||
}
|
||||
|
||||
if (sinceLog >= WAIT_PUZZLE_LOG_MS)
|
||||
{
|
||||
string selfPos = "?";
|
||||
try { selfPos = $"{Self.Location.X},{Self.Location.Y}"; } catch { }
|
||||
Log($"Warte auf Spielstart... Grid={(hasGrid ? "ok" : "no")}, Pfeile={probeArrows.Count}/16, InZone={(inPlayZone ? "yes" : "no")}, Self={selfPos}");
|
||||
sinceLog = 0;
|
||||
}
|
||||
|
||||
Delay(WAIT_PUZZLE_POLL_MS);
|
||||
sinceQueueClick += WAIT_PUZZLE_POLL_MS;
|
||||
sinceLog += WAIT_PUZZLE_POLL_MS;
|
||||
}
|
||||
|
||||
Log("Puzzle erkannt. Starte Solver...");
|
||||
Log($"Pfeile: {arrowIds.Count}/16");
|
||||
|
||||
int[] targetRows = new int[4];
|
||||
bool targetFound = false;
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != TILE_KIND) continue;
|
||||
if (item.Location.X != 41) continue;
|
||||
int y = item.Location.Y;
|
||||
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
|
||||
|
||||
targetRows[y - GRID_Y_MIN] = GetState(item);
|
||||
targetFound = true;
|
||||
}
|
||||
if (!targetFound) targetRows = new[] { 1, 2, 3, 0 };
|
||||
|
||||
int[,] tgt = new int[4, 4];
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
tgt[r, c] = targetRows[r];
|
||||
|
||||
uint goal = EncodeGrid(tgt);
|
||||
Log($"Ziel: R0={targetRows[0]}, R1={targetRows[1]}, R2={targetRows[2]}, R3={targetRows[3]}");
|
||||
|
||||
Log($"Start: {dumpNow}");
|
||||
|
||||
var moveToKey = new Dictionary<int, string>();
|
||||
var keyToMove = new Dictionary<string, int>();
|
||||
var allKeys = arrowIds.Keys.OrderBy(k => k).ToList();
|
||||
|
||||
for (int step = 1; step <= MAX_STEPS; step++)
|
||||
{
|
||||
if (current == goal)
|
||||
{
|
||||
Log("=== Geloest: alle 4 Reihen korrekt ===");
|
||||
return;
|
||||
}
|
||||
|
||||
var plan = Solve(current, goal);
|
||||
if (plan == null || plan.Count == 0)
|
||||
{
|
||||
Log("ERROR: Kein Plan vom aktuellen Zustand.");
|
||||
return;
|
||||
}
|
||||
|
||||
int wanted = plan[0];
|
||||
string key;
|
||||
bool probing = false;
|
||||
|
||||
if (moveToKey.ContainsKey(wanted))
|
||||
{
|
||||
key = moveToKey[wanted];
|
||||
}
|
||||
else
|
||||
{
|
||||
key = allKeys.FirstOrDefault(k => !keyToMove.ContainsKey(k));
|
||||
if (key == null)
|
||||
{
|
||||
key = allKeys[0];
|
||||
}
|
||||
probing = true;
|
||||
}
|
||||
|
||||
long id = arrowIds[key];
|
||||
Log($"[{step}] want {MoveName(wanted)} | click {key}" + (probing ? " (probe)" : ""));
|
||||
|
||||
if (!ClickAndWaitChange(id, current, out uint after, out string dumpAfter))
|
||||
{
|
||||
Log(" Kein Move erkannt (Timeout), gleicher Schritt nochmal.");
|
||||
continue;
|
||||
}
|
||||
|
||||
int actual = DetectMove(current, after);
|
||||
if (actual >= 0)
|
||||
{
|
||||
moveToKey[actual] = key;
|
||||
keyToMove[key] = actual;
|
||||
if (actual != wanted)
|
||||
Log($" AutoCalib: {key} == {MoveName(actual)} (nicht {MoveName(wanted)})");
|
||||
}
|
||||
else if (actual == -1)
|
||||
{
|
||||
Log($" Unbekannter Transition-Delta, weiter mit Re-Plan. State: {dumpAfter}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($" Mehrdeutiger Delta, weiter mit Re-Plan. State: {dumpAfter}");
|
||||
}
|
||||
|
||||
current = after;
|
||||
|
||||
if (step % 10 == 0)
|
||||
Log($" Calib: {moveToKey.Count}/16 Moves gemappt");
|
||||
}
|
||||
|
||||
Log("Nicht fertig in MAX_STEPS. Script einfach nochmal starten.");
|
||||
148
Color Puzzle State Logger.csx
Normal file
148
Color Puzzle State Logger.csx
Normal file
@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
class ScanCell
|
||||
{
|
||||
public long Id;
|
||||
public int X;
|
||||
public int Y;
|
||||
public double Z;
|
||||
public int Kind;
|
||||
public int State;
|
||||
public string Name;
|
||||
}
|
||||
|
||||
const int SOL_MIN_X = 4;
|
||||
const int SOL_MAX_X = 9;
|
||||
const int SOL_MIN_Y = 1;
|
||||
const int SOL_MAX_Y = 8;
|
||||
|
||||
const int PLAY_MIN_X = 8;
|
||||
const int PLAY_MAX_X = 13;
|
||||
const int PLAY_MIN_Y = 14;
|
||||
const int PLAY_MAX_Y = 21;
|
||||
|
||||
int GetKind(dynamic item)
|
||||
{
|
||||
try { return (int)item.Kind; }
|
||||
catch { return -1; }
|
||||
}
|
||||
|
||||
int GetState(dynamic item)
|
||||
{
|
||||
try { return int.Parse(item.State?.ToString() ?? "0"); }
|
||||
catch { return 0; }
|
||||
}
|
||||
|
||||
string GetNameSafe(dynamic item)
|
||||
{
|
||||
try
|
||||
{
|
||||
string n = item.GetName();
|
||||
return string.IsNullOrWhiteSpace(n) ? "<unknown>" : n;
|
||||
}
|
||||
catch { return "<unknown>"; }
|
||||
}
|
||||
|
||||
bool InRect(int x, int y, int minX, int maxX, int minY, int maxY)
|
||||
{
|
||||
return x >= minX && x <= maxX && y >= minY && y <= maxY;
|
||||
}
|
||||
|
||||
Log("=== Color State Logger ===");
|
||||
|
||||
var all = new List<dynamic>();
|
||||
foreach (var it in FloorItems)
|
||||
{
|
||||
if (it == null) continue;
|
||||
all.Add(it);
|
||||
}
|
||||
|
||||
if (all.Count == 0)
|
||||
{
|
||||
Log("ERROR: No floor items.");
|
||||
return;
|
||||
}
|
||||
|
||||
var solutionRaw = new List<ScanCell>();
|
||||
var playRaw = new List<ScanCell>();
|
||||
|
||||
foreach (var it in all)
|
||||
{
|
||||
int x = (int)it.Location.X;
|
||||
int y = (int)it.Location.Y;
|
||||
|
||||
var cell = new ScanCell {
|
||||
Id = (long)it.Id,
|
||||
X = x,
|
||||
Y = y,
|
||||
Z = (double)it.Location.Z,
|
||||
Kind = GetKind(it),
|
||||
State = GetState(it),
|
||||
Name = GetNameSafe(it)
|
||||
};
|
||||
|
||||
if (InRect(x, y, SOL_MIN_X, SOL_MAX_X, SOL_MIN_Y, SOL_MAX_Y))
|
||||
solutionRaw.Add(cell);
|
||||
|
||||
if (InRect(x, y, PLAY_MIN_X, PLAY_MAX_X, PLAY_MIN_Y, PLAY_MAX_Y))
|
||||
playRaw.Add(cell);
|
||||
}
|
||||
|
||||
if (solutionRaw.Count == 0 || playRaw.Count == 0)
|
||||
{
|
||||
Log($"ERROR: Missing board items. Solution={solutionRaw.Count}, Play={playRaw.Count}");
|
||||
return;
|
||||
}
|
||||
|
||||
var solKind = solutionRaw.GroupBy(x => x.Kind)
|
||||
.Select(g => new { Kind = g.Key, CoordCount = g.Select(x => x.X + "," + x.Y).Distinct().Count(), Count = g.Count() })
|
||||
.OrderByDescending(x => x.CoordCount)
|
||||
.ThenByDescending(x => x.Count)
|
||||
.First().Kind;
|
||||
|
||||
var playKind = playRaw.GroupBy(x => x.Kind)
|
||||
.Select(g => new { Kind = g.Key, CoordCount = g.Select(x => x.X + "," + x.Y).Distinct().Count(), Count = g.Count() })
|
||||
.OrderByDescending(x => x.CoordCount)
|
||||
.ThenByDescending(x => x.Count)
|
||||
.First().Kind;
|
||||
|
||||
var solCells = solutionRaw.Where(x => x.Kind == solKind)
|
||||
.GroupBy(x => x.X + "," + x.Y)
|
||||
.Select(g => g.OrderByDescending(x => x.Z).First())
|
||||
.OrderBy(x => x.Y)
|
||||
.ThenBy(x => x.X)
|
||||
.ToList();
|
||||
|
||||
var playCells = playRaw.Where(x => x.Kind == playKind)
|
||||
.GroupBy(x => x.X + "," + x.Y)
|
||||
.Select(g => g.OrderByDescending(x => x.Z).First())
|
||||
.OrderBy(x => x.Y)
|
||||
.ThenBy(x => x.X)
|
||||
.ToList();
|
||||
|
||||
Log($"Solution board kind={solKind} name={solCells.Select(x => x.Name).FirstOrDefault()} cells={solCells.Count}");
|
||||
Log($"Play board kind={playKind} name={playCells.Select(x => x.Name).FirstOrDefault()} cells={playCells.Count}");
|
||||
|
||||
var solStates = solCells.GroupBy(x => x.State).OrderBy(x => x.Key).ToList();
|
||||
var playStates = playCells.GroupBy(x => x.State).OrderBy(x => x.Key).ToList();
|
||||
|
||||
Log("\nStates in solution board:");
|
||||
foreach (var s in solStates)
|
||||
Log($" state {s.Key}: {s.Count()} tiles");
|
||||
|
||||
Log("\nStates in play board:");
|
||||
foreach (var s in playStates)
|
||||
Log($" state {s.Key}: {s.Count()} tiles");
|
||||
|
||||
Log("\nCoordinate -> state (solution board):");
|
||||
foreach (var c in solCells)
|
||||
Log($" ({c.X},{c.Y}) = {c.State}");
|
||||
|
||||
Log("\nCoordinate -> state (play board):");
|
||||
foreach (var c in playCells)
|
||||
Log($" ({c.X},{c.Y}) = {c.State}");
|
||||
|
||||
Log("\nHint: colors are client visuals; script logs exact numeric states.");
|
||||
Log("Use this once, then map state->color manually by looking at one tile per state.");
|
||||
536
Color Run Path Recorder.csx
Normal file
536
Color Run Path Recorder.csx
Normal file
@ -0,0 +1,536 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
public struct Step
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
public int DelayMs;
|
||||
}
|
||||
|
||||
const int START_X = 13;
|
||||
const int START_Y = 13;
|
||||
const int FINISH_X = 14;
|
||||
const int FINISH_Y = 8;
|
||||
|
||||
const int PLAY_MIN_X = 8;
|
||||
const int PLAY_MAX_X = 13;
|
||||
const int PLAY_MIN_Y = 14;
|
||||
const int PLAY_MAX_Y = 21;
|
||||
const bool RECORD_ONLY_PLAYFIELD = true;
|
||||
|
||||
const int MAX_RECORD_MS = 130000;
|
||||
const int MIN_STEP_DELAY_MS = 40;
|
||||
const int REPLAY_MOVE_INTERVAL_MS = 80;
|
||||
const int FAST_PLAY_INTERVAL_MS = 70;
|
||||
const bool AUTO_DUMP_ON_FINISH = true;
|
||||
const bool LIVE_LOG_EACH_STEP = true;
|
||||
|
||||
Regex mvRegex = new Regex(@"/mv (\d+),(\d+),([\d\.]+)", RegexOptions.Compiled);
|
||||
|
||||
bool armed = true;
|
||||
bool recording = false;
|
||||
bool replaying = false;
|
||||
|
||||
int targetIndex = -1;
|
||||
string targetName = "";
|
||||
DateTime recordStart = DateTime.MinValue;
|
||||
DateTime lastStepAt = DateTime.MinValue;
|
||||
DateTime lastReplayMove = DateTime.MinValue;
|
||||
string lastTrackedPos = "";
|
||||
|
||||
List<Step> steps = new List<Step>();
|
||||
List<Step> lastCompletedSteps = new List<Step>();
|
||||
string lastCompletedReason = "";
|
||||
int lastCompletedDurationMs = 0;
|
||||
Dictionary<string, List<Step>> savedPaths = new Dictionary<string, List<Step>>();
|
||||
string pendingSymbol = "";
|
||||
string currentRunSymbol = "";
|
||||
DateTime lastStatusLog = DateTime.MinValue;
|
||||
|
||||
string MissingSymbol()
|
||||
{
|
||||
bool haveRose = HasSaved("rose");
|
||||
bool haveHeart = HasSaved("heart");
|
||||
if (haveRose && haveHeart) return "";
|
||||
return haveRose ? "heart" : "rose";
|
||||
}
|
||||
|
||||
bool HasSaved(string symbol)
|
||||
{
|
||||
string s = NormalizeSymbol(symbol);
|
||||
return !string.IsNullOrEmpty(s) && savedPaths.ContainsKey(s) && savedPaths[s].Count > 0;
|
||||
}
|
||||
|
||||
string P(int x, int y) => x + "," + y;
|
||||
|
||||
bool InPlay(int x, int y)
|
||||
{
|
||||
return x >= PLAY_MIN_X && x <= PLAY_MAX_X && y >= PLAY_MIN_Y && y <= PLAY_MAX_Y;
|
||||
}
|
||||
|
||||
bool IsFinish(int x, int y)
|
||||
{
|
||||
return x == FINISH_X && y == FINISH_Y;
|
||||
}
|
||||
|
||||
string NormalizeSymbol(string s)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(s)) return "";
|
||||
string t = s.Trim().ToLowerInvariant();
|
||||
if (t.Contains("rose")) return "rose";
|
||||
if (t.Contains("heart")) return "heart";
|
||||
return "";
|
||||
}
|
||||
|
||||
void ResetRecorder(bool keepArmed)
|
||||
{
|
||||
recording = false;
|
||||
targetIndex = -1;
|
||||
targetName = "";
|
||||
recordStart = DateTime.MinValue;
|
||||
lastStepAt = DateTime.MinValue;
|
||||
lastTrackedPos = "";
|
||||
if (!keepArmed) armed = false;
|
||||
}
|
||||
|
||||
dynamic FindUserOnStartTile()
|
||||
{
|
||||
foreach (var u in Users)
|
||||
{
|
||||
if (u == null || u.Location == null) continue;
|
||||
if (u.Location.X == START_X && u.Location.Y == START_Y)
|
||||
return u;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void ArmIfNeeded()
|
||||
{
|
||||
if (!armed || recording || replaying) return;
|
||||
|
||||
string missing = MissingSymbol();
|
||||
if (!string.IsNullOrEmpty(missing))
|
||||
{
|
||||
if (string.IsNullOrEmpty(pendingSymbol))
|
||||
{
|
||||
if ((DateTime.UtcNow - lastStatusLog).TotalSeconds >= 4)
|
||||
{
|
||||
Log($"Waiting for symbol call: {missing}.");
|
||||
lastStatusLog = DateTime.UtcNow;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingSymbol != missing)
|
||||
{
|
||||
if ((DateTime.UtcNow - lastStatusLog).TotalSeconds >= 4)
|
||||
{
|
||||
Log($"Ignoring round symbol '{pendingSymbol}', waiting for missing '{missing}'.");
|
||||
lastStatusLog = DateTime.UtcNow;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var u = FindUserOnStartTile();
|
||||
if (u == null) return;
|
||||
|
||||
StartRecordingForUser(u);
|
||||
}
|
||||
|
||||
void StartRecordingForUser(dynamic u)
|
||||
{
|
||||
if (u == null) return;
|
||||
|
||||
targetIndex = u.Index;
|
||||
targetName = u.Name;
|
||||
recording = true;
|
||||
recordStart = DateTime.UtcNow;
|
||||
lastStepAt = recordStart;
|
||||
steps.Clear();
|
||||
lastTrackedPos = P(START_X, START_Y);
|
||||
currentRunSymbol = pendingSymbol;
|
||||
Log($"REC START: {targetName} (index {targetIndex}) from {START_X}:{START_Y}");
|
||||
if (!string.IsNullOrEmpty(currentRunSymbol))
|
||||
Log($"REC symbol: {currentRunSymbol}");
|
||||
}
|
||||
|
||||
void AddStep(int x, int y)
|
||||
{
|
||||
if (RECORD_ONLY_PLAYFIELD && !InPlay(x, y)) return;
|
||||
|
||||
int delay = (int)(DateTime.UtcNow - lastStepAt).TotalMilliseconds;
|
||||
if (delay < MIN_STEP_DELAY_MS) delay = MIN_STEP_DELAY_MS;
|
||||
|
||||
if (steps.Count > 0)
|
||||
{
|
||||
var prev = steps[steps.Count - 1];
|
||||
if (prev.X == x && prev.Y == y) return;
|
||||
}
|
||||
|
||||
steps.Add(new Step { X = x, Y = y, DelayMs = delay });
|
||||
lastStepAt = DateTime.UtcNow;
|
||||
|
||||
if (LIVE_LOG_EACH_STEP)
|
||||
Log($"REC step {steps.Count}: ({x},{y}) +{delay}ms");
|
||||
|
||||
if (steps.Count % 10 == 0)
|
||||
Log($"REC progress: {steps.Count} steps...");
|
||||
}
|
||||
|
||||
void StopRecording(string reason)
|
||||
{
|
||||
if (!recording) return;
|
||||
recording = false;
|
||||
int dur = (int)(DateTime.UtcNow - recordStart).TotalMilliseconds;
|
||||
lastCompletedSteps = new List<Step>(steps);
|
||||
lastCompletedReason = reason;
|
||||
lastCompletedDurationMs = dur;
|
||||
|
||||
Log($"REC STOP ({reason}) steps={steps.Count}, duration={dur}ms");
|
||||
if (steps.Count > 0)
|
||||
{
|
||||
Log("Use .path replay to replay on your avatar.");
|
||||
Log("Use .path dump to print recorded path.");
|
||||
|
||||
if (AUTO_DUMP_ON_FINISH && reason.StartsWith("finish@"))
|
||||
{
|
||||
Log("Auto dump on finish:");
|
||||
DumpPath();
|
||||
}
|
||||
|
||||
if (reason.StartsWith("finish@") && !string.IsNullOrEmpty(currentRunSymbol))
|
||||
{
|
||||
if (!savedPaths.ContainsKey(currentRunSymbol))
|
||||
{
|
||||
savedPaths[currentRunSymbol] = new List<Step>(steps);
|
||||
Log($"Saved path for symbol '{currentRunSymbol}' ({steps.Count} steps).");
|
||||
}
|
||||
else
|
||||
{
|
||||
int oldCount = savedPaths[currentRunSymbol].Count;
|
||||
if (steps.Count < oldCount)
|
||||
{
|
||||
savedPaths[currentRunSymbol] = new List<Step>(steps);
|
||||
Log($"Updated '{currentRunSymbol}' path: {oldCount} -> {steps.Count} steps (better).");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"Kept existing '{currentRunSymbol}' path ({oldCount} steps), new run had {steps.Count}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reason.StartsWith("finish@"))
|
||||
{
|
||||
bool haveRose = HasSaved("rose");
|
||||
bool haveHeart = HasSaved("heart");
|
||||
|
||||
if (haveRose && haveHeart)
|
||||
{
|
||||
armed = false;
|
||||
Log("Both symbols saved (rose + heart). Recorder auto-disarmed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
armed = true;
|
||||
string missing = !haveRose ? "rose" : "heart";
|
||||
Log($"Saved run complete. Waiting for missing symbol: {missing}.");
|
||||
}
|
||||
}
|
||||
|
||||
currentRunSymbol = "";
|
||||
pendingSymbol = "";
|
||||
}
|
||||
|
||||
dynamic FindTargetByIndex(int idx)
|
||||
{
|
||||
foreach (var u in Users)
|
||||
{
|
||||
if (u == null) continue;
|
||||
if (u.Index == idx) return u;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void DumpPath(string symbol = "")
|
||||
{
|
||||
var src = steps;
|
||||
string which = "current";
|
||||
|
||||
string norm = NormalizeSymbol(symbol);
|
||||
if (!string.IsNullOrEmpty(norm) && savedPaths.ContainsKey(norm))
|
||||
{
|
||||
src = savedPaths[norm];
|
||||
which = norm;
|
||||
}
|
||||
else if (src.Count == 0 && lastCompletedSteps.Count > 0)
|
||||
{
|
||||
src = lastCompletedSteps;
|
||||
which = "last";
|
||||
}
|
||||
|
||||
if (src.Count == 0)
|
||||
{
|
||||
Log("No recorded steps.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (which == "last")
|
||||
Log($"PATH DUMP (last complete, {lastCompletedReason}, {lastCompletedDurationMs}ms) steps={src.Count}");
|
||||
else if (which == "rose" || which == "heart")
|
||||
Log($"PATH DUMP ({which}) steps={src.Count}");
|
||||
else
|
||||
Log($"PATH DUMP steps={src.Count}");
|
||||
|
||||
for (int i = 0; i < src.Count; i++)
|
||||
{
|
||||
var s = src[i];
|
||||
Log($" {i + 1}. ({s.X},{s.Y}) after {s.DelayMs}ms");
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayPath(string symbol = "", bool useRecordedDelays = true)
|
||||
{
|
||||
if (replaying) return;
|
||||
|
||||
var src = steps;
|
||||
string norm = NormalizeSymbol(symbol);
|
||||
if (!string.IsNullOrEmpty(norm) && savedPaths.ContainsKey(norm))
|
||||
src = savedPaths[norm];
|
||||
else if (src.Count == 0 && lastCompletedSteps.Count > 0)
|
||||
src = lastCompletedSteps;
|
||||
|
||||
if (src.Count == 0)
|
||||
{
|
||||
Log("No recorded path to replay.");
|
||||
return;
|
||||
}
|
||||
|
||||
replaying = true;
|
||||
Log($"REPLAY START: {src.Count} steps" + (string.IsNullOrEmpty(norm) ? "" : $" ({norm})") + (useRecordedDelays ? " [timed]" : " [fast]"));
|
||||
|
||||
for (int i = 0; i < src.Count; i++)
|
||||
{
|
||||
var s = src[i];
|
||||
int wait = useRecordedDelays ? s.DelayMs : FAST_PLAY_INTERVAL_MS;
|
||||
if (wait < REPLAY_MOVE_INTERVAL_MS) wait = REPLAY_MOVE_INTERVAL_MS;
|
||||
Delay(wait);
|
||||
Move(s.X, s.Y);
|
||||
lastReplayMove = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
replaying = false;
|
||||
Log("REPLAY DONE");
|
||||
}
|
||||
|
||||
OnIntercept(In["UserUpdate"], e =>
|
||||
{
|
||||
if (!recording) return;
|
||||
|
||||
var packet = e.Packet;
|
||||
int numUpdates = packet.ReadInt();
|
||||
for (int i = 0; i < numUpdates; i++)
|
||||
{
|
||||
int entityIndex = packet.ReadInt();
|
||||
packet.ReadInt();
|
||||
packet.ReadInt();
|
||||
packet.ReadString();
|
||||
packet.ReadInt();
|
||||
packet.ReadInt();
|
||||
string action = packet.ReadString();
|
||||
|
||||
if (entityIndex != targetIndex) continue;
|
||||
Match m = mvRegex.Match(action ?? "");
|
||||
if (!m.Success) continue;
|
||||
|
||||
int tx = int.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture);
|
||||
int ty = int.Parse(m.Groups[2].Value, CultureInfo.InvariantCulture);
|
||||
AddStep(tx, ty);
|
||||
}
|
||||
});
|
||||
|
||||
OnChat(e =>
|
||||
{
|
||||
try
|
||||
{
|
||||
string msg = (e.Message ?? "").ToLowerInvariant();
|
||||
string sym = NormalizeSymbol(msg);
|
||||
if (!string.IsNullOrEmpty(sym) && msg.Contains("paint me"))
|
||||
{
|
||||
pendingSymbol = sym;
|
||||
Log($"Detected round symbol: {pendingSymbol}");
|
||||
|
||||
if (recording && string.IsNullOrEmpty(currentRunSymbol))
|
||||
{
|
||||
currentRunSymbol = pendingSymbol;
|
||||
Log($"Bound current run to symbol: {currentRunSymbol}");
|
||||
|
||||
string missing = MissingSymbol();
|
||||
if (!string.IsNullOrEmpty(missing) && currentRunSymbol != missing)
|
||||
{
|
||||
StopRecording($"wrong_symbol_{currentRunSymbol}");
|
||||
ResetRecorder(true);
|
||||
Log($"Discarded run: needed '{missing}', got '{currentRunSymbol}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
|
||||
OnIntercept(Out.Chat, e =>
|
||||
{
|
||||
string msg = e.Packet.ReadString();
|
||||
if (string.IsNullOrWhiteSpace(msg)) return;
|
||||
string c = msg.Trim().ToLowerInvariant();
|
||||
var parts = c.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (c == ".path arm")
|
||||
{
|
||||
e.Block();
|
||||
armed = true;
|
||||
if (recording) StopRecording("re-armed");
|
||||
ResetRecorder(true);
|
||||
Log("Recorder armed. Waiting for someone on 13:13.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (parts.Length >= 3 && parts[0] == ".path" && parts[1] == "symbol")
|
||||
{
|
||||
e.Block();
|
||||
string sym = NormalizeSymbol(parts[2]);
|
||||
if (string.IsNullOrEmpty(sym))
|
||||
Log("Unknown symbol. Use .path symbol rose|heart");
|
||||
else
|
||||
{
|
||||
pendingSymbol = sym;
|
||||
Log($"Manual symbol set: {pendingSymbol}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (parts.Length >= 1 && parts[0] == "!play")
|
||||
{
|
||||
e.Block();
|
||||
string sym = parts.Length >= 2 ? NormalizeSymbol(parts[1]) : "";
|
||||
if (parts.Length >= 2 && string.IsNullOrEmpty(sym))
|
||||
{
|
||||
Log("Unknown play symbol. Use !play rose or !play heart");
|
||||
return;
|
||||
}
|
||||
ReplayPath(sym, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parts.Length >= 1 && parts[0] == "!playtimed")
|
||||
{
|
||||
e.Block();
|
||||
string sym = parts.Length >= 2 ? NormalizeSymbol(parts[1]) : "";
|
||||
if (parts.Length >= 2 && string.IsNullOrEmpty(sym))
|
||||
{
|
||||
Log("Unknown play symbol. Use !playtimed rose or !playtimed heart");
|
||||
return;
|
||||
}
|
||||
ReplayPath(sym, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == ".path stop")
|
||||
{
|
||||
e.Block();
|
||||
StopRecording("manual");
|
||||
ResetRecorder(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parts.Length >= 2 && parts[0] == ".path" && parts[1] == "dump")
|
||||
{
|
||||
e.Block();
|
||||
string sym = parts.Length >= 3 ? parts[2] : "";
|
||||
DumpPath(sym);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parts.Length >= 2 && parts[0] == ".path" && parts[1] == "replay")
|
||||
{
|
||||
e.Block();
|
||||
string sym = parts.Length >= 3 ? parts[2] : "";
|
||||
ReplayPath(sym);
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == ".path clear")
|
||||
{
|
||||
e.Block();
|
||||
steps.Clear();
|
||||
Log("Path cleared.");
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
Log("=== Color Run Recorder (13:13) ===");
|
||||
Log("Commands: .path arm | .path stop | .path dump [rose|heart] | .path replay [rose|heart] | .path clear");
|
||||
Log("Optional: .path symbol rose|heart");
|
||||
Log("Quick play: !play rose | !play heart");
|
||||
Log("Timed play: !playtimed rose | !playtimed heart");
|
||||
Log("Auto-start records when a user is on 13:13.");
|
||||
Log($"Auto-stop when target reaches finish tile {FINISH_X}:{FINISH_Y}.");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
try
|
||||
{
|
||||
ArmIfNeeded();
|
||||
|
||||
if (recording)
|
||||
{
|
||||
// If a different user newly starts on 13:13, switch immediately to new run.
|
||||
var starter = FindUserOnStartTile();
|
||||
if (starter != null && starter.Index != targetIndex)
|
||||
{
|
||||
StopRecording("replaced_by_new_start");
|
||||
ResetRecorder(true);
|
||||
StartRecordingForUser(starter);
|
||||
}
|
||||
|
||||
var t = FindTargetByIndex(targetIndex);
|
||||
if (t != null && t.Location != null)
|
||||
{
|
||||
int ux = t.Location.X;
|
||||
int uy = t.Location.Y;
|
||||
|
||||
// Fallback tracker by live position (works even if /mv parse misses packets).
|
||||
string pk = P(ux, uy);
|
||||
if (pk != lastTrackedPos)
|
||||
{
|
||||
AddStep(ux, uy);
|
||||
lastTrackedPos = pk;
|
||||
}
|
||||
|
||||
if (IsFinish(ux, uy))
|
||||
{
|
||||
StopRecording($"finish@{FINISH_X}:{FINISH_Y}");
|
||||
ResetRecorder(true);
|
||||
Delay(200);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int ms = (int)(DateTime.UtcNow - recordStart).TotalMilliseconds;
|
||||
if (ms > MAX_RECORD_MS)
|
||||
{
|
||||
StopRecording("timeout");
|
||||
ResetRecorder(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
Delay(100);
|
||||
}
|
||||
13
Dance Switcher.csx
Normal file
13
Dance Switcher.csx
Normal file
@ -0,0 +1,13 @@
|
||||
// Dance Switcher - Wechselt jede Sekunde die Tanzart
|
||||
|
||||
Log("=== DANCE SWITCHER ===");
|
||||
|
||||
int dance = 1;
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Dance(dance);
|
||||
dance++;
|
||||
if (dance > 4) dance = 1;
|
||||
Delay(100);
|
||||
}
|
||||
30
Dave 2.csx
Normal file
30
Dave 2.csx
Normal file
@ -0,0 +1,30 @@
|
||||
/// @name Catalog Item Finder
|
||||
|
||||
var targetIdentifier = "clothing_r25_kittybag";
|
||||
var catalog = GetCatalog();
|
||||
var nodes = catalog.Where(x => x.Id > 0).ToArray();
|
||||
|
||||
for (int i = 0; i < nodes.Length; i++) {
|
||||
var node = nodes[i];
|
||||
Status($"Searching {i+1}/{nodes.Length}...");
|
||||
|
||||
var page = GetCatalogPage(node);
|
||||
|
||||
foreach (var offer in page.Offers) {
|
||||
foreach (var product in offer.Products) {
|
||||
if (product.Type != ItemType.Floor && product.Type != ItemType.Wall) continue;
|
||||
if (product.GetIdentifier() != targetIdentifier) continue;
|
||||
|
||||
var pointsLabel = offer.ActivityPointType.ToString() == "Diamond"
|
||||
? "PriceInDiamonds"
|
||||
: "PriceInActivityPoints";
|
||||
|
||||
Log($"Found: {targetIdentifier} | id: {offer.Id} | pageId: {node.Id} | pageName: {node.Name} | furniLine: {offer.FurniLine} | priceInCredits: {offer.PriceInCredits} | {pointsLabel}: {offer.PriceInActivityPoints} | canPurchaseMultiple: {offer.CanPurchaseMultiple} | canPurchaseAsGift: {offer.CanPurchaseAsGift} | type: {product.Type} | isLimited: {product.IsLimited}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(150);
|
||||
}
|
||||
|
||||
Log("Item not found in catalog");
|
||||
491
Duck Dodging Pathfinder AI.csx
Normal file
491
Duck Dodging Pathfinder AI.csx
Normal file
@ -0,0 +1,491 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
|
||||
public struct Point : IEquatable<Point>
|
||||
{
|
||||
public int X { get; }
|
||||
public int Y { get; }
|
||||
public Point(int x, int y) { X = x; Y = y; }
|
||||
public static implicit operator Point((int x, int y) tuple) => new Point(tuple.x, tuple.y);
|
||||
public bool Equals(Point other) => X == other.X && Y == other.Y;
|
||||
public override bool Equals(object obj) => obj is Point other && Equals(other);
|
||||
public override int GetHashCode() => HashCode.Combine(X, Y);
|
||||
public static bool operator ==(Point left, Point right) => left.Equals(right);
|
||||
public static bool operator !=(Point left, Point right) => !(left == right);
|
||||
public override string ToString() => $"({X},{Y})";
|
||||
}
|
||||
|
||||
public class Tile
|
||||
{
|
||||
public int X { get; set; }
|
||||
public int Y { get; set; }
|
||||
public double Z { get; set; }
|
||||
public Point XY => new Point(X, Y);
|
||||
public Tile(int x, int y, double z = 0.0) { X = x; Y = y; Z = z; }
|
||||
}
|
||||
|
||||
public class Duck
|
||||
{
|
||||
public long id { get; set; }
|
||||
public Point pos { get; set; }
|
||||
public Point lastpos { get; set; }
|
||||
public Point vel { get; set; }
|
||||
public DateTime lastseen { get; set; }
|
||||
public Queue<Point> trail { get; set; } = new Queue<Point>(10);
|
||||
public double spd { get; set; }
|
||||
}
|
||||
|
||||
HashSet<Point> tiles = new HashSet<Point> {
|
||||
(13,13),(14,13),(15,13),(16,13),(17,13),(18,13),(19,13),(20,13),(21,13),(22,13),(23,13),(24,13),(25,13),
|
||||
(13,14),(17,14),(21,14),(25,14),
|
||||
(13,15),(14,15),(15,15),(17,15),(18,15),(20,15),(21,15),(23,15),(24,15),(25,15),
|
||||
(13,16),(15,16),(18,16),(20,16),(23,16),(25,16),
|
||||
(13,17),(15,17),(16,17),(17,17),(18,17),(19,17),(20,17),(21,17),(22,17),(23,17),(25,17),
|
||||
(13,18),(15,18),(19,18),(23,18),(25,18),
|
||||
(13,19),(14,19),(15,19),(17,19),(18,19),(19,19),(20,19),(21,19),(23,19),(24,19),(25,19),
|
||||
(13,20),(15,20),(16,20),(17,20),(21,20),(22,20),(23,20),(25,20),
|
||||
(12,21),(13,21),(17,21),(18,21),(19,21),(20,21),(21,21),(25,21),(26,21),
|
||||
(13,22),(15,22),(16,22),(17,22),(21,22),(22,22),(23,22),(25,22),
|
||||
(13,23),(14,23),(15,23),(17,23),(18,23),(19,23),(20,23),(21,23),(23,23),(24,23),(25,23),
|
||||
(13,24),(15,24),(19,24),(23,24),(25,24),
|
||||
(13,25),(15,25),(16,25),(17,25),(18,25),(19,25),(20,25),(21,25),(22,25),(23,25),(25,25),
|
||||
(13,26),(15,26),(18,26),(20,26),(23,26),(25,26),
|
||||
(13,27),(14,27),(15,27),(17,27),(18,27),(20,27),(21,27),(23,27),(24,27),(25,27),
|
||||
(13,28),(17,28),(21,28),(25,28),
|
||||
(13,29),(14,29),(15,29),(16,29),(17,29),(18,29),(19,29),(20,29),(21,29),(22,29),(23,29),(24,29),(25,29)
|
||||
};
|
||||
|
||||
Dictionary<Point, List<Point>> adj = new Dictionary<Point, List<Point>>();
|
||||
Point[] dirs = { (0,1), (0,-1), (1,0), (-1,0), (1,1), (1,-1), (-1,1), (-1,-1) };
|
||||
|
||||
foreach(var t in tiles)
|
||||
{
|
||||
var n = new List<Point>();
|
||||
foreach(var d in dirs)
|
||||
{
|
||||
Point p = new Point(t.X + d.X, t.Y + d.Y);
|
||||
if(tiles.Contains(p)) n.Add(p);
|
||||
}
|
||||
adj[t] = n;
|
||||
}
|
||||
|
||||
Dictionary<long, Duck> ducks = new Dictionary<long, Duck>();
|
||||
Tile tgt = null;
|
||||
Point lastcmd = default(Point);
|
||||
DateTime cmdtime = DateTime.MinValue;
|
||||
Point prev = default(Point);
|
||||
Point curr = default(Point);
|
||||
Queue<Point> hist = new Queue<Point>(5);
|
||||
int stuck = 0;
|
||||
HashSet<Point> danger = new HashSet<Point>();
|
||||
|
||||
Point dest = default(Point);
|
||||
bool forcedest = false;
|
||||
DateTime desttime = DateTime.MinValue;
|
||||
|
||||
Point getpos()
|
||||
{
|
||||
if (Self == null) return default(Point);
|
||||
if (tgt != null) return tgt.XY;
|
||||
if (!lastcmd.Equals(default(Point)) && (DateTime.UtcNow - cmdtime).TotalMilliseconds < 250)
|
||||
return lastcmd;
|
||||
if (Self.Location != null) return new Point(Self.Location.X, Self.Location.Y);
|
||||
return default(Point);
|
||||
}
|
||||
|
||||
void go(int x, int y)
|
||||
{
|
||||
Move(x, y);
|
||||
lastcmd = new Point(x, y);
|
||||
cmdtime = DateTime.UtcNow;
|
||||
tgt = null;
|
||||
}
|
||||
|
||||
HashSet<Point> predict(int frames)
|
||||
{
|
||||
var zones = new HashSet<Point>();
|
||||
|
||||
foreach(var d in ducks.Values)
|
||||
{
|
||||
zones.Add(d.pos);
|
||||
|
||||
if(!d.vel.Equals(default(Point)))
|
||||
{
|
||||
for(int i = 1; i <= frames; i++)
|
||||
{
|
||||
Point pred = new Point(
|
||||
d.pos.X + d.vel.X * i,
|
||||
d.pos.Y + d.vel.Y * i
|
||||
);
|
||||
if(tiles.Contains(pred))
|
||||
zones.Add(pred);
|
||||
}
|
||||
}
|
||||
|
||||
if(frames >= 1 && adj.ContainsKey(d.pos))
|
||||
{
|
||||
foreach(var n in adj[d.pos])
|
||||
zones.Add(n);
|
||||
}
|
||||
|
||||
if(frames >= 2)
|
||||
{
|
||||
foreach(var d1 in dirs)
|
||||
{
|
||||
Point p1 = new Point(d.pos.X + d1.X, d.pos.Y + d1.Y);
|
||||
if(tiles.Contains(p1))
|
||||
{
|
||||
zones.Add(p1);
|
||||
foreach(var d2 in dirs)
|
||||
{
|
||||
Point p2 = new Point(p1.X + d2.X, p1.Y + d2.Y);
|
||||
if(tiles.Contains(p2))
|
||||
zones.Add(p2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return zones;
|
||||
}
|
||||
|
||||
Point findsafe(Point me, HashSet<Point> bad)
|
||||
{
|
||||
if(!bad.Any()) return tiles.First();
|
||||
|
||||
double best = double.MinValue;
|
||||
Point spot = me;
|
||||
|
||||
foreach(var t in tiles)
|
||||
{
|
||||
if(bad.Contains(t)) continue;
|
||||
|
||||
double score = 0;
|
||||
|
||||
double mindist = double.MaxValue;
|
||||
foreach(var b in bad)
|
||||
{
|
||||
double d = Math.Abs(t.X - b.X) + Math.Abs(t.Y - b.Y);
|
||||
mindist = Math.Min(mindist, d);
|
||||
score += d;
|
||||
}
|
||||
|
||||
score += mindist * 100;
|
||||
|
||||
if(adj.ContainsKey(t))
|
||||
{
|
||||
int exits = adj[t].Count(n => !bad.Contains(n));
|
||||
score += exits * 10;
|
||||
}
|
||||
|
||||
double dist = Math.Abs(t.X - me.X) + Math.Abs(t.Y - me.Y);
|
||||
score -= dist * 0.5;
|
||||
|
||||
if(score > best)
|
||||
{
|
||||
best = score;
|
||||
spot = t;
|
||||
}
|
||||
}
|
||||
|
||||
return spot;
|
||||
}
|
||||
|
||||
Point pathto(Point me, Point goal, HashSet<Point> d1, HashSet<Point> d2, HashSet<Point> d3)
|
||||
{
|
||||
if(!adj.ContainsKey(me)) return me;
|
||||
|
||||
if(hist.Count >= 3)
|
||||
{
|
||||
var last3 = hist.TakeLast(3).ToArray();
|
||||
if(last3[0] == last3[2] && last3[0] != last3[1])
|
||||
{
|
||||
stuck++;
|
||||
if(stuck > 2)
|
||||
{
|
||||
var esc = adj[me]
|
||||
.Where(n => !d1.Contains(n))
|
||||
.OrderBy(n => Guid.NewGuid())
|
||||
.FirstOrDefault();
|
||||
if(!esc.Equals(default(Point)))
|
||||
{
|
||||
stuck = 0;
|
||||
return esc;
|
||||
}
|
||||
}
|
||||
}
|
||||
else stuck = 0;
|
||||
}
|
||||
|
||||
double best = double.MinValue;
|
||||
Point move = me;
|
||||
|
||||
foreach(var n in adj[me])
|
||||
{
|
||||
if(d1.Contains(n)) continue;
|
||||
|
||||
double score = 0;
|
||||
|
||||
if(d2.Contains(n)) score -= 800;
|
||||
if(d3.Contains(n)) score -= 400;
|
||||
|
||||
double dist = Math.Abs(n.X - goal.X) + Math.Abs(n.Y - goal.Y);
|
||||
score -= dist * 100;
|
||||
|
||||
foreach(var duck in ducks.Values)
|
||||
{
|
||||
double dd = Math.Abs(n.X - duck.pos.X) + Math.Abs(n.Y - duck.pos.Y);
|
||||
score += dd * 10;
|
||||
}
|
||||
|
||||
if(adj.ContainsKey(n))
|
||||
{
|
||||
int safe = adj[n].Count(x => !d1.Contains(x));
|
||||
score += safe * 20;
|
||||
|
||||
if(safe == 0 && d2.Contains(n))
|
||||
score -= 2000;
|
||||
}
|
||||
|
||||
if(!prev.Equals(default(Point)) && n.Equals(prev))
|
||||
score -= 50;
|
||||
|
||||
if(score > best)
|
||||
{
|
||||
best = score;
|
||||
move = n;
|
||||
}
|
||||
}
|
||||
|
||||
return move;
|
||||
}
|
||||
|
||||
Point getmove(Point me, Point goal, HashSet<Point> d1, HashSet<Point> d2, HashSet<Point> d3)
|
||||
{
|
||||
if(!adj.ContainsKey(me)) return me;
|
||||
|
||||
if(hist.Count >= 3)
|
||||
{
|
||||
var last3 = hist.TakeLast(3).ToArray();
|
||||
if(last3[0] == last3[2] && last3[0] != last3[1])
|
||||
{
|
||||
stuck++;
|
||||
if(stuck > 1)
|
||||
{
|
||||
var any = adj[me]
|
||||
.Where(n => !d1.Contains(n))
|
||||
.OrderBy(n => d2.Contains(n) ? 1 : 0)
|
||||
.FirstOrDefault();
|
||||
if(!any.Equals(default(Point)))
|
||||
{
|
||||
stuck = 0;
|
||||
return any;
|
||||
}
|
||||
}
|
||||
}
|
||||
else stuck = 0;
|
||||
}
|
||||
|
||||
double best = double.MinValue;
|
||||
Point move = me;
|
||||
|
||||
foreach(var n in adj[me])
|
||||
{
|
||||
if(d1.Contains(n)) continue;
|
||||
|
||||
if(!prev.Equals(default(Point)) && n.Equals(prev))
|
||||
continue;
|
||||
|
||||
double score = 0;
|
||||
|
||||
if(d2.Contains(n)) score -= 1000;
|
||||
if(d3.Contains(n)) score -= 500;
|
||||
|
||||
double dist = Math.Abs(n.X - goal.X) + Math.Abs(n.Y - goal.Y);
|
||||
score -= dist * 10;
|
||||
|
||||
foreach(var duck in ducks.Values)
|
||||
{
|
||||
double dd = Math.Abs(n.X - duck.pos.X) + Math.Abs(n.Y - duck.pos.Y);
|
||||
score += dd * 20;
|
||||
}
|
||||
|
||||
if(adj.ContainsKey(n))
|
||||
{
|
||||
int exits = adj[n].Count(x => !d1.Contains(x) && !d2.Contains(x));
|
||||
score += exits * 50;
|
||||
}
|
||||
|
||||
if(score > best)
|
||||
{
|
||||
best = score;
|
||||
move = n;
|
||||
}
|
||||
}
|
||||
|
||||
return move;
|
||||
}
|
||||
|
||||
OnIntercept(Out["MoveAvatar"], e => {
|
||||
var pkt = e.Packet;
|
||||
int x = pkt.ReadInt();
|
||||
int y = pkt.ReadInt();
|
||||
|
||||
dest = new Point(x, y);
|
||||
forcedest = true;
|
||||
desttime = DateTime.UtcNow;
|
||||
});
|
||||
|
||||
OnEnteredRoom(e => {
|
||||
ducks.Clear();
|
||||
hist.Clear();
|
||||
prev = default(Point);
|
||||
stuck = 0;
|
||||
forcedest = false;
|
||||
dest = default(Point);
|
||||
});
|
||||
|
||||
OnIntercept(In["WiredMovements"], e => {
|
||||
var pkt = e.Packet;
|
||||
int cnt = pkt.ReadInt();
|
||||
|
||||
for(int i = 0; i < cnt; i++)
|
||||
{
|
||||
pkt.ReadInt();
|
||||
int fx = pkt.ReadInt();
|
||||
int fy = pkt.ReadInt();
|
||||
int tx = pkt.ReadInt();
|
||||
int ty = pkt.ReadInt();
|
||||
pkt.ReadString();
|
||||
pkt.ReadString();
|
||||
int id = pkt.ReadInt();
|
||||
pkt.ReadInt();
|
||||
pkt.ReadInt();
|
||||
|
||||
long fid = id;
|
||||
Point newp = new Point(tx, ty);
|
||||
Point oldp = new Point(fx, fy);
|
||||
|
||||
if(!ducks.ContainsKey(fid))
|
||||
{
|
||||
ducks[fid] = new Duck { id = fid };
|
||||
}
|
||||
|
||||
var d = ducks[fid];
|
||||
d.lastpos = d.pos;
|
||||
d.pos = newp;
|
||||
d.vel = new Point(tx - fx, ty - fy);
|
||||
d.lastseen = DateTime.UtcNow;
|
||||
|
||||
d.trail.Enqueue(newp);
|
||||
if(d.trail.Count > 10) d.trail.Dequeue();
|
||||
|
||||
if((d.lastseen - DateTime.UtcNow).TotalSeconds < 1)
|
||||
{
|
||||
d.spd = Math.Sqrt(Math.Pow(d.vel.X, 2) + Math.Pow(d.vel.Y, 2));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
OnIntercept(In["UserUpdate"], e => {
|
||||
if(Self == null) return;
|
||||
|
||||
var pkt = e.Packet;
|
||||
int num = pkt.ReadInt();
|
||||
|
||||
for(int i = 0; i < num; i++)
|
||||
{
|
||||
int idx = pkt.ReadInt();
|
||||
int x = pkt.ReadInt();
|
||||
int y = pkt.ReadInt();
|
||||
string z = pkt.ReadString();
|
||||
pkt.ReadInt();
|
||||
pkt.ReadInt();
|
||||
string act = pkt.ReadString();
|
||||
|
||||
if(idx == Self.Index)
|
||||
{
|
||||
prev = curr;
|
||||
curr = new Point(x, y);
|
||||
|
||||
hist.Enqueue(curr);
|
||||
if(hist.Count > 5) hist.Dequeue();
|
||||
|
||||
if(forcedest && curr.Equals(dest))
|
||||
{
|
||||
forcedest = false;
|
||||
}
|
||||
|
||||
if(act.Contains("/mv"))
|
||||
{
|
||||
var parts = act.Split(new[] {' ', '/', ','}, StringSplitOptions.RemoveEmptyEntries);
|
||||
if(parts.Length >= 4 && parts[0] == "mv")
|
||||
{
|
||||
if(int.TryParse(parts[1], out int mx) &&
|
||||
int.TryParse(parts[2], out int my) &&
|
||||
double.TryParse(parts[3], NumberStyles.Any, CultureInfo.InvariantCulture, out double mz))
|
||||
{
|
||||
tgt = new Tile(mx, my, mz);
|
||||
lastcmd = default(Point);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(act.EndsWith("//") && !act.Contains("/mv"))
|
||||
{
|
||||
tgt = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
while(Run)
|
||||
{
|
||||
try
|
||||
{
|
||||
Point me = getpos();
|
||||
if(me.Equals(default(Point))) { Delay(20); continue; }
|
||||
|
||||
if(ducks.Any() || forcedest)
|
||||
{
|
||||
var d1 = predict(1);
|
||||
var d2 = predict(2);
|
||||
var d3 = predict(3);
|
||||
|
||||
Point goal;
|
||||
Point next = me;
|
||||
|
||||
if(forcedest && tiles.Contains(dest))
|
||||
{
|
||||
if((DateTime.UtcNow - desttime).TotalSeconds > 30)
|
||||
{
|
||||
forcedest = false;
|
||||
goal = findsafe(me, d1);
|
||||
next = getmove(me, goal, d1, d2, d3);
|
||||
}
|
||||
else
|
||||
{
|
||||
goal = dest;
|
||||
next = pathto(me, goal, d1, d2, d3);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
goal = findsafe(me, d1);
|
||||
next = getmove(me, goal, d1, d2, d3);
|
||||
}
|
||||
|
||||
if(!next.Equals(me))
|
||||
{
|
||||
go(next.X, next.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
}
|
||||
|
||||
Delay(20);
|
||||
}
|
||||
59
Enter-One-Way-Door #1.csx
Normal file
59
Enter-One-Way-Door #1.csx
Normal file
@ -0,0 +1,59 @@
|
||||
/// @group Bots
|
||||
|
||||
public class TileAction
|
||||
{
|
||||
private readonly Action MainAction;
|
||||
private readonly Action ElseAction;
|
||||
private readonly Func<bool> Condition;
|
||||
|
||||
public TileAction(Action mainAction) => MainAction = mainAction;
|
||||
|
||||
public TileAction(Action mainAction, Action elseAction, Func<bool> condition) : this(mainAction)
|
||||
{
|
||||
ElseAction = elseAction;
|
||||
Condition = condition;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
if (Condition == null || Condition())
|
||||
{
|
||||
MainAction();
|
||||
}
|
||||
else
|
||||
{
|
||||
ElseAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Point RealPos => (Self.CurrentUpdate.MovingTo ?? Self.Location).XY;
|
||||
|
||||
Dictionary<Point, TileAction> Actions = new()
|
||||
{
|
||||
// HIER DEINE ACTIONS
|
||||
{ ( 24, 15), new (() => Send(Out["EnterOneWayDoor"],2147418205)) },
|
||||
{ ( 24, 16), new (() => Send(Out["EnterOneWayDoor"],2147418204)) },
|
||||
{ ( 25, 16), new (() => Send(Out["EnterOneWayDoor"],2147418203)) },
|
||||
{ ( 25, 17), new (() => Send(Out["EnterOneWayDoor"],2147418547)) },
|
||||
|
||||
{ ( 26, 19), new (() => Send(Out["EnterOneWayDoor"],2147418490)) },
|
||||
{ ( 25, 19), new (() => Send(Out["EnterOneWayDoor"],2147418211)) },
|
||||
{ ( 25, 20), new (() => Send(Out["EnterOneWayDoor"],2147418212)) },
|
||||
{ ( 24, 20), new (() => Send(Out["EnterOneWayDoor"],2147418551)) },
|
||||
|
||||
|
||||
};
|
||||
|
||||
while (Run)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Actions.TryGetValue(RealPos, out var result))
|
||||
{
|
||||
result.Execute();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
Delay(100);
|
||||
}
|
||||
578
FURNIMATIC.csx
Normal file
578
FURNIMATIC.csx
Normal file
@ -0,0 +1,578 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
var port = 8230;
|
||||
var queue = new List<int>();
|
||||
var progress = 0;
|
||||
var total = 0;
|
||||
var delay = 850;
|
||||
var lastrun = DateTime.Now;
|
||||
HttpListener server = null;
|
||||
|
||||
try {
|
||||
EnsureInventory();
|
||||
|
||||
var items = Inventory
|
||||
.Where(x => x.IsRecyclable)
|
||||
.GroupBy(x => x.GetDescriptor())
|
||||
.Where(g => g.Count() >= 8)
|
||||
.Select(g => new {
|
||||
name = g.Key.GetName(),
|
||||
id = g.Key.GetInfo().Identifier,
|
||||
rev = g.Key.GetInfo().Revision,
|
||||
count = g.Count(),
|
||||
list = g.Select(i => (int)i.Id).ToList()
|
||||
})
|
||||
.OrderByDescending(x => x.count)
|
||||
.ToList();
|
||||
|
||||
Log($"Found {items.Count} recyclable types (8+ items)");
|
||||
items.ForEach(x => Log($" {x.name}: {x.count}x"));
|
||||
|
||||
var json = "[" + string.Join(",", items.Select((item, i) =>
|
||||
$"{{\"i\":{i},\"n\":\"{item.name.Replace("\"", "\\\"")}\",\"id\":\"{item.id}\",\"r\":{item.rev},\"c\":{item.count}}}"
|
||||
)) + "]";
|
||||
|
||||
var html = @"<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<title>Recycler</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #0f0f0f 0%, #1a1a1a 100%);
|
||||
color: #e0e0e0;
|
||||
font: 14px -apple-system, system-ui, sans-serif;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
.wrap {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
animation: fadein 0.5s;
|
||||
}
|
||||
@keyframes fadein {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 25px;
|
||||
text-align: center;
|
||||
background: linear-gradient(90deg, #4ade80, #22d3ee);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
.panel {
|
||||
background: rgba(255,255,255,0.03);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.search-box {
|
||||
width: 100%;
|
||||
background: rgba(255,255,255,0.05);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
color: #fff;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 12px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.search-box:focus {
|
||||
outline: none;
|
||||
border-color: #4ade80;
|
||||
background: rgba(255,255,255,0.08);
|
||||
}
|
||||
.search-box::placeholder {
|
||||
color: #666;
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.controls label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: #a0a0a0;
|
||||
}
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
background: linear-gradient(135deg, #4ade80, #22d3ee);
|
||||
border: none;
|
||||
color: #000;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s;
|
||||
font-size: 13px;
|
||||
}
|
||||
button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(74,222,128,0.3);
|
||||
}
|
||||
button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
button:disabled {
|
||||
background: #333;
|
||||
color: #666;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
button.stop {
|
||||
background: linear-gradient(135deg, #ef4444, #f97316);
|
||||
}
|
||||
button.stop:hover {
|
||||
box-shadow: 0 5px 15px rgba(239,68,68,0.3);
|
||||
}
|
||||
input[type=number] {
|
||||
width: 70px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
color: #fff;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
input[type=number]:focus {
|
||||
outline: none;
|
||||
border-color: #4ade80;
|
||||
background: rgba(255,255,255,0.08);
|
||||
}
|
||||
.bar {
|
||||
height: 40px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 2px 4px rgba(0,0,0,0.2);
|
||||
}
|
||||
.fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #4ade80, #22d3ee);
|
||||
border-radius: 20px;
|
||||
transition: width 0.5s ease;
|
||||
box-shadow: 0 0 20px rgba(74,222,128,0.5);
|
||||
}
|
||||
.bartext {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
|
||||
}
|
||||
.status {
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #888;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
gap: 12px;
|
||||
max-height: 450px;
|
||||
overflow-y: auto;
|
||||
padding: 4px;
|
||||
}
|
||||
.grid::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
.grid::-webkit-scrollbar-track {
|
||||
background: rgba(255,255,255,0.02);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.grid::-webkit-scrollbar-thumb {
|
||||
background: rgba(255,255,255,0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.grid::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255,255,255,0.15);
|
||||
}
|
||||
.card {
|
||||
background: rgba(255,255,255,0.04);
|
||||
border: 2px solid rgba(255,255,255,0.08);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
position: relative;
|
||||
}
|
||||
.card:hover {
|
||||
background: rgba(255,255,255,0.07);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
||||
}
|
||||
.card.on {
|
||||
border-color: #4ade80;
|
||||
background: rgba(74,222,128,0.1);
|
||||
}
|
||||
.card.hidden {
|
||||
display: none;
|
||||
}
|
||||
.card img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-bottom: 8px;
|
||||
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
|
||||
}
|
||||
.card .name {
|
||||
font-size: 12px;
|
||||
margin-bottom: 6px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: #ccc;
|
||||
}
|
||||
.card .count {
|
||||
color: #4ade80;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.amt {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.amt button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
color: #fff;
|
||||
}
|
||||
.amt button:hover {
|
||||
background: rgba(74,222,128,0.3);
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.amt span {
|
||||
min-width: 40px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
.empty {
|
||||
grid-column: 1 / -1;
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
}
|
||||
.result-count {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='wrap'>
|
||||
<h1>Recycler</h1>
|
||||
<div class='panel'>
|
||||
<input type='text' class='search-box' id='search' placeholder='Search items to recycle...' autofocus>
|
||||
<div class='controls'>
|
||||
<label>Delay <input type='number' id='delay' value='850' min='100' max='2000' step='50'>ms</label>
|
||||
<button onclick='reset()'>Clear</button>
|
||||
<button onclick='go()' id='btn'>Start</button>
|
||||
<button onclick='stop()' class='stop'>Stop</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class='panel'>
|
||||
<div class='bar'>
|
||||
<div class='fill' id='bar' style='width:0%'></div>
|
||||
<div class='bartext' id='txt'>Ready</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='panel'>
|
||||
<div class='status' id='info'>Select items to recycle</div>
|
||||
<div class='result-count' id='results'></div>
|
||||
<div class='grid' id='grid'></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const d = " + json + @";
|
||||
let sel = [];
|
||||
let vals = {};
|
||||
let searchterm = '';
|
||||
let wasrunning = false;
|
||||
|
||||
d.forEach(x => vals[x.i] = Math.min(80, Math.floor(x.c/8)*8));
|
||||
|
||||
function img(x) {
|
||||
return 'https://images.habbo.com/dcr/hof_furni/' + x.r + '/' + x.id + '_icon.png';
|
||||
}
|
||||
|
||||
function draw() {
|
||||
const g = document.getElementById('grid');
|
||||
if (!d.length) {
|
||||
g.innerHTML = '<div class=""empty"">No recyclable items found<br><small>Need 8+ of the same type</small></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let visible = 0;
|
||||
let html = '';
|
||||
|
||||
d.forEach(x => {
|
||||
const show = !searchterm || x.n.toLowerCase().includes(searchterm.toLowerCase());
|
||||
if (show) visible++;
|
||||
|
||||
html += '<div class=""card' + (show ? '' : ' hidden') + '"" id=""c' + x.i + '"" onclick=""pick(' + x.i + ')"">' +
|
||||
'<img src=""' + img(x) + '"" onerror=""this.style.display=\'none\'"">' +
|
||||
'<div class=""name"" title=""' + x.n + '"">' + x.n + '</div>' +
|
||||
'<div class=""count"">' + x.c + 'x</div>' +
|
||||
'<div class=""amt"" onclick=""event.stopPropagation()"">' +
|
||||
'<button onclick=""adj(' + x.i + ',-8)"">-</button>' +
|
||||
'<span id=""v' + x.i + '"">' + vals[x.i] + '</span>' +
|
||||
'<button onclick=""adj(' + x.i + ',8)"">+</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
});
|
||||
|
||||
g.innerHTML = html;
|
||||
|
||||
// Restore selected state
|
||||
sel.forEach(i => {
|
||||
const el = document.getElementById('c' + i);
|
||||
if (el) el.classList.add('on');
|
||||
});
|
||||
|
||||
// Update result count
|
||||
const rc = document.getElementById('results');
|
||||
if (searchterm) {
|
||||
rc.textContent = visible + ' items found';
|
||||
} else {
|
||||
rc.textContent = '';
|
||||
}
|
||||
|
||||
if (visible === 0 && searchterm) {
|
||||
g.innerHTML = '<div class=""empty"">No items match ""' + searchterm + '""<br><small>Try different search</small></div>';
|
||||
}
|
||||
}
|
||||
|
||||
function adj(i, n) {
|
||||
const max = Math.floor(d.find(x => x.i === i).c / 8) * 8;
|
||||
vals[i] = Math.max(8, Math.min(max, vals[i] + n));
|
||||
document.getElementById('v' + i).textContent = vals[i];
|
||||
update();
|
||||
}
|
||||
|
||||
function pick(i) {
|
||||
const e = document.getElementById('c' + i);
|
||||
if (sel.includes(i)) {
|
||||
sel = sel.filter(x => x !== i);
|
||||
e.classList.remove('on');
|
||||
} else {
|
||||
sel.push(i);
|
||||
e.classList.add('on');
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
function reset() {
|
||||
sel = [];
|
||||
d.forEach(x => document.getElementById('c' + x.i)?.classList.remove('on'));
|
||||
update();
|
||||
}
|
||||
|
||||
function update() {
|
||||
const t = sel.reduce((s, i) => s + vals[i], 0);
|
||||
document.getElementById('info').textContent = sel.length ?
|
||||
sel.length + ' types • ' + t + ' items' :
|
||||
'Select items to recycle';
|
||||
}
|
||||
|
||||
function go() {
|
||||
if (!sel.length) {
|
||||
alert('Select items first');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = sel.map(i => ({i: i, a: vals[i]}));
|
||||
|
||||
document.getElementById('btn').disabled = true;
|
||||
fetch('/recycle', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({items: data, delay: parseInt(document.getElementById('delay').value)})
|
||||
});
|
||||
}
|
||||
|
||||
function stop() {
|
||||
fetch('/stop', {method: 'POST'});
|
||||
document.getElementById('btn').disabled = false;
|
||||
wasrunning = false;
|
||||
}
|
||||
|
||||
// Search functionality
|
||||
document.getElementById('search').addEventListener('input', (e) => {
|
||||
searchterm = e.target.value.trim();
|
||||
draw();
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
fetch('/status')
|
||||
.then(r => r.json())
|
||||
.then(x => {
|
||||
const p = x.total ? Math.round(x.done / x.total * 100) : 0;
|
||||
document.getElementById('bar').style.width = p + '%';
|
||||
document.getElementById('txt').textContent = x.total ? x.done + ' / ' + x.total : 'Ready';
|
||||
|
||||
// Track if we were running
|
||||
if (x.total > 0) {
|
||||
wasrunning = true;
|
||||
}
|
||||
|
||||
// If we were running and now total is 0, we're done
|
||||
if (wasrunning && x.total === 0) {
|
||||
wasrunning = false;
|
||||
document.getElementById('btn').disabled = false;
|
||||
document.getElementById('bar').style.width = '100%';
|
||||
document.getElementById('txt').textContent = 'Complete!';
|
||||
setTimeout(() => {
|
||||
document.getElementById('bar').style.width = '0%';
|
||||
document.getElementById('txt').textContent = 'Ready';
|
||||
}, 2000);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}, 500);
|
||||
|
||||
draw();
|
||||
</script>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
server = new HttpListener();
|
||||
server.Prefixes.Add($"http://localhost:{port}/");
|
||||
server.Start();
|
||||
|
||||
Process.Start(new ProcessStartInfo {
|
||||
FileName = $"http://localhost:{port}/",
|
||||
UseShellExecute = true
|
||||
});
|
||||
|
||||
Log($"Server on port {port}");
|
||||
|
||||
Task.Run(async () => {
|
||||
while (Run && server.IsListening) {
|
||||
try {
|
||||
var ctx = await server.GetContextAsync();
|
||||
Task.Run(() => handle(ctx));
|
||||
}
|
||||
catch { break; }
|
||||
}
|
||||
});
|
||||
|
||||
while (Run) {
|
||||
if (queue.Count >= 8 && (DateTime.Now - lastrun).TotalMilliseconds >= delay) {
|
||||
var batch = queue.Take(8).ToList();
|
||||
queue.RemoveRange(0, 8);
|
||||
|
||||
Send(Out["RecycleItems"], 8, batch[0], batch[1], batch[2], batch[3], batch[4], batch[5], batch[6], batch[7]);
|
||||
|
||||
progress += 8;
|
||||
lastrun = DateTime.Now;
|
||||
|
||||
if (queue.Count < 8) {
|
||||
Log($"Done - recycled {progress} items");
|
||||
queue.Clear();
|
||||
progress = 0;
|
||||
total = 0;
|
||||
}
|
||||
}
|
||||
Delay(10);
|
||||
}
|
||||
|
||||
void handle(HttpListenerContext ctx) {
|
||||
try {
|
||||
var req = ctx.Request;
|
||||
var res = ctx.Response;
|
||||
var path = req.RawUrl;
|
||||
|
||||
if (path == "/") {
|
||||
var b = Encoding.UTF8.GetBytes(html);
|
||||
res.ContentType = "text/html";
|
||||
res.ContentLength64 = b.Length;
|
||||
res.OutputStream.Write(b, 0, b.Length);
|
||||
}
|
||||
else if (path == "/recycle" && req.HttpMethod == "POST") {
|
||||
using (var r = new StreamReader(req.InputStream)) {
|
||||
var data = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(r.ReadToEnd());
|
||||
|
||||
queue.Clear();
|
||||
progress = 0;
|
||||
|
||||
if (data.ContainsKey("delay"))
|
||||
delay = System.Text.Json.JsonSerializer.Deserialize<int>(data["delay"].ToString());
|
||||
|
||||
if (data.ContainsKey("items")) {
|
||||
var selected = System.Text.Json.JsonSerializer.Deserialize<List<Dictionary<string, int>>>(data["items"].ToString());
|
||||
foreach (var s in selected) {
|
||||
if (s["i"] < items.Count) {
|
||||
var item = items[s["i"]];
|
||||
var amt = Math.Min(s["a"], item.count);
|
||||
for (int i = 0; i < amt && i < item.list.Count; i++)
|
||||
queue.Add(item.list[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
total = queue.Count;
|
||||
Log($"Queued {total} items @ {delay}ms");
|
||||
}
|
||||
res.StatusCode = 200;
|
||||
}
|
||||
else if (path == "/stop") {
|
||||
queue.Clear();
|
||||
progress = total = 0;
|
||||
Log("Stopped");
|
||||
res.StatusCode = 200;
|
||||
}
|
||||
else if (path == "/status") {
|
||||
var json = $"{{\"done\":{progress},\"total\":{total}}}";
|
||||
var b = Encoding.UTF8.GetBytes(json);
|
||||
res.ContentType = "application/json";
|
||||
res.ContentLength64 = b.Length;
|
||||
res.OutputStream.Write(b, 0, b.Length);
|
||||
}
|
||||
else {
|
||||
res.StatusCode = 404;
|
||||
}
|
||||
res.Close();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
finally {
|
||||
server?.Stop();
|
||||
server?.Close();
|
||||
Log("Shutdown");
|
||||
}
|
||||
21
Find Item by ID.csx
Normal file
21
Find Item by ID.csx
Normal file
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
int searchId = 796960578;
|
||||
|
||||
Log("Suche Item...");
|
||||
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
|
||||
if (item.Id == searchId)
|
||||
{
|
||||
Log("=== ITEM GEFUNDEN ===");
|
||||
Log("ID: " + item.Id);
|
||||
Log("Position: X=" + item.Location.X + ", Y=" + item.Location.Y);
|
||||
Log("Kind: " + item.Kind);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Log("Suche beendet.");
|
||||
165
Flappy Bird [28.12.25] V1.0.csx
Normal file
165
Flappy Bird [28.12.25] V1.0.csx
Normal file
@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
const int BIRD_X = 17;
|
||||
const int BIRD_KIND = 9039;
|
||||
const int PIPE_KIND = 5986;
|
||||
const int CONTROL_ID = 630985637;
|
||||
|
||||
int GetKind(dynamic item) { try { return (int)item.Kind; } catch { return -1; } }
|
||||
void Flap() { Send(Out["UseFurniture"], CONTROL_ID, 0); }
|
||||
|
||||
int GetBirdY()
|
||||
{
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != BIRD_KIND) continue;
|
||||
if (item.Location.X == BIRD_X) return item.Location.Y;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
(int gapMin, int gapMax, int pipeX) FindNextPipe()
|
||||
{
|
||||
var pipesByX = new Dictionary<int, List<int>>();
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != PIPE_KIND) continue;
|
||||
int x = item.Location.X, y = item.Location.Y;
|
||||
if (!pipesByX.ContainsKey(x)) pipesByX[x] = new List<int>();
|
||||
pipesByX[x].Add(y);
|
||||
}
|
||||
|
||||
var ahead = pipesByX.Keys.Where(x => x >= BIRD_X).OrderBy(x => x).ToList();
|
||||
if (ahead.Count == 0) return (-1, -1, 99);
|
||||
|
||||
int closestX = ahead.First();
|
||||
var wallYs = new HashSet<int>(pipesByX[closestX]);
|
||||
|
||||
int bestStart = -1, bestEnd = -1, bestSize = 0, gapStart = -1;
|
||||
for (int y = 7; y <= 24; y++)
|
||||
{
|
||||
if (!wallYs.Contains(y)) { if (gapStart < 0) gapStart = y; }
|
||||
else
|
||||
{
|
||||
if (gapStart >= 0)
|
||||
{
|
||||
int size = (y - 1) - gapStart + 1;
|
||||
if (size > bestSize) { bestSize = size; bestStart = gapStart; bestEnd = y - 1; }
|
||||
}
|
||||
gapStart = -1;
|
||||
}
|
||||
}
|
||||
if (gapStart >= 0)
|
||||
{
|
||||
int size = 24 - gapStart + 1;
|
||||
if (size > bestSize) { bestStart = gapStart; bestEnd = 24; }
|
||||
}
|
||||
return (bestStart, bestEnd, closestX);
|
||||
}
|
||||
|
||||
Log("FLAPPY BOT V19 - SILENT MODE"); // Output spam reduced
|
||||
|
||||
long lastFlap = 0;
|
||||
int tick = 0;
|
||||
int lastY = -1;
|
||||
|
||||
while (Run)
|
||||
{
|
||||
tick++;
|
||||
int birdY = GetBirdY();
|
||||
if (birdY < 0) { Delay(25); continue; }
|
||||
|
||||
var (gapMin, gapMax, pipeX) = FindNextPipe();
|
||||
int dist = pipeX - BIRD_X;
|
||||
|
||||
long now = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
||||
|
||||
bool shouldFlap = false;
|
||||
string reason = "";
|
||||
|
||||
// MINIMUM COOLDOWN = 120ms
|
||||
int cooldown = 120;
|
||||
|
||||
if (gapMin > 0 && dist <= 15)
|
||||
{
|
||||
bool aboveGap = birdY < gapMin;
|
||||
bool belowGap = birdY > gapMax;
|
||||
int gapCenter = (gapMin + gapMax) / 2;
|
||||
int afterFlap = birdY - 3;
|
||||
bool wouldOvershoot = afterFlap <= gapMin;
|
||||
|
||||
if (belowGap)
|
||||
{
|
||||
shouldFlap = true;
|
||||
reason = "BELOW";
|
||||
}
|
||||
else if (aboveGap)
|
||||
{
|
||||
shouldFlap = false;
|
||||
reason = "ABOVE";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dist <= 4)
|
||||
{
|
||||
if (birdY >= gapMax && !wouldOvershoot)
|
||||
{
|
||||
shouldFlap = true;
|
||||
reason = "CLOSE-SAVE";
|
||||
}
|
||||
else { reason = "CLOSE-OK"; }
|
||||
}
|
||||
else if (dist <= 10)
|
||||
{
|
||||
if (birdY > gapMax && !wouldOvershoot)
|
||||
{
|
||||
shouldFlap = true;
|
||||
reason = "MED-LOW";
|
||||
}
|
||||
else if (birdY > gapCenter + 2 && !wouldOvershoot)
|
||||
{
|
||||
shouldFlap = true;
|
||||
reason = "MED-ADJ";
|
||||
}
|
||||
else { reason = "MED-OK"; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (birdY > gapCenter + 3 && !wouldOvershoot)
|
||||
{
|
||||
shouldFlap = true;
|
||||
reason = "FAR-LOW";
|
||||
}
|
||||
else { reason = "FAR-OK"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (birdY > 16) { shouldFlap = true; reason = "HOVER"; }
|
||||
}
|
||||
|
||||
if (birdY >= 22) { shouldFlap = true; reason = "EMERG"; }
|
||||
if (birdY <= 8) shouldFlap = false;
|
||||
|
||||
int dy = lastY > 0 ? birdY - lastY : 0;
|
||||
string gapStr = gapMin > 0 ? $"gap={gapMin}-{gapMax}" : "NO_GAP";
|
||||
|
||||
if (shouldFlap && (now - lastFlap) >= cooldown)
|
||||
{
|
||||
// Nur noch Loggen wenn er wirklich springt
|
||||
Log($">>> FLAP! Y={birdY} | d={dist} {gapStr} | {reason}");
|
||||
Flap();
|
||||
lastFlap = now;
|
||||
}
|
||||
|
||||
// Der Code-Block, der sonst hier war (else if dist <= 3...), wurde entfernt.
|
||||
// Dadurch hört der Spam auf.
|
||||
|
||||
lastY = birdY;
|
||||
Delay(25);
|
||||
}
|
||||
244
Flood-IT [28.12.25] V1.0.csx
Normal file
244
Flood-IT [28.12.25] V1.0.csx
Normal file
@ -0,0 +1,244 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
const int CLICK_DELAY = 2500;
|
||||
const int GAME_MIN_X = 9;
|
||||
const int GAME_MAX_X = 26;
|
||||
const int GAME_MIN_Y = 12;
|
||||
const int GAME_MAX_Y = 29;
|
||||
const int GRID_W = 18;
|
||||
const int GRID_H = 18;
|
||||
const int TOTAL = 324;
|
||||
const int KIND_TILE = 3666;
|
||||
|
||||
Dictionary<int, int> tileIdByColor = new Dictionary<int, int>();
|
||||
int[,] grid = new int[GRID_W, GRID_H];
|
||||
|
||||
int GetState(dynamic item) { try { return int.Parse(item.State?.ToString() ?? "0"); } catch { return 0; } }
|
||||
int GetId(dynamic item) { try { return (int)item.Id; } catch { return 0; } }
|
||||
int GetKind(dynamic item) { try { return (int)item.Kind; } catch { return -1; } }
|
||||
|
||||
void ReadGrid()
|
||||
{
|
||||
Array.Clear(grid, 0, grid.Length);
|
||||
tileIdByColor.Clear();
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != KIND_TILE) continue;
|
||||
int x = item.Location.X, y = item.Location.Y;
|
||||
if (x < GAME_MIN_X || x > GAME_MAX_X || y < GAME_MIN_Y || y > GAME_MAX_Y) continue;
|
||||
int gx = x - GAME_MIN_X, gy = GAME_MAX_Y - y;
|
||||
int state = GetState(item), id = GetId(item);
|
||||
if (gx >= 0 && gx < GRID_W && gy >= 0 && gy < GRID_H)
|
||||
{
|
||||
grid[gx, gy] = state;
|
||||
if (!tileIdByColor.ContainsKey(state)) tileIdByColor[state] = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<(int,int)> Flood(HashSet<(int,int)> area, int color)
|
||||
{
|
||||
var result = new HashSet<(int,int)>(area);
|
||||
var q = new Queue<(int,int)>();
|
||||
foreach (var p in area) q.Enqueue(p);
|
||||
while (q.Count > 0)
|
||||
{
|
||||
var (x, y) = q.Dequeue();
|
||||
foreach (var (nx, ny) in new[]{(x-1,y),(x+1,y),(x,y-1),(x,y+1)})
|
||||
{
|
||||
if (nx < 0 || nx >= GRID_W || ny < 0 || ny >= GRID_H) continue;
|
||||
if (result.Contains((nx,ny))) continue;
|
||||
if (grid[nx,ny] == color) { result.Add((nx,ny)); q.Enqueue((nx,ny)); }
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
HashSet<(int,int)> InitArea()
|
||||
{
|
||||
var area = new HashSet<(int,int)>{(0,0)};
|
||||
return Flood(area, grid[0,0]);
|
||||
}
|
||||
|
||||
HashSet<int> BorderColors(HashSet<(int,int)> area)
|
||||
{
|
||||
var c = new HashSet<int>();
|
||||
foreach (var (x,y) in area)
|
||||
{
|
||||
if (x > 0 && !area.Contains((x-1,y))) c.Add(grid[x-1,y]);
|
||||
if (x < GRID_W-1 && !area.Contains((x+1,y))) c.Add(grid[x+1,y]);
|
||||
if (y > 0 && !area.Contains((x,y-1))) c.Add(grid[x,y-1]);
|
||||
if (y < GRID_H-1 && !area.Contains((x,y+1))) c.Add(grid[x,y+1]);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
HashSet<int> RemainingColors(HashSet<(int,int)> area)
|
||||
{
|
||||
var colors = new HashSet<int>();
|
||||
for (int x = 0; x < GRID_W; x++)
|
||||
for (int y = 0; y < GRID_H; y++)
|
||||
if (!area.Contains((x,y))) colors.Add(grid[x,y]);
|
||||
return colors;
|
||||
}
|
||||
|
||||
int CountRegions(HashSet<(int,int)> area)
|
||||
{
|
||||
var visited = new bool[GRID_W, GRID_H];
|
||||
foreach (var (x,y) in area) visited[x,y] = true;
|
||||
int regions = 0;
|
||||
|
||||
for (int sx = 0; sx < GRID_W; sx++)
|
||||
{
|
||||
for (int sy = 0; sy < GRID_H; sy++)
|
||||
{
|
||||
if (visited[sx,sy]) continue;
|
||||
regions++;
|
||||
var q = new Queue<(int,int)>();
|
||||
q.Enqueue((sx,sy));
|
||||
visited[sx,sy] = true;
|
||||
int c = grid[sx,sy];
|
||||
while (q.Count > 0)
|
||||
{
|
||||
var (x,y) = q.Dequeue();
|
||||
foreach (var (nx,ny) in new[]{(x-1,y),(x+1,y),(x,y-1),(x,y+1)})
|
||||
{
|
||||
if (nx < 0 || nx >= GRID_W || ny < 0 || ny >= GRID_H) continue;
|
||||
if (visited[nx,ny]) continue;
|
||||
if (grid[nx,ny] == c) { visited[nx,ny] = true; q.Enqueue((nx,ny)); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return regions;
|
||||
}
|
||||
|
||||
int Heuristic(HashSet<(int,int)> area)
|
||||
{
|
||||
var remaining = RemainingColors(area);
|
||||
int regions = CountRegions(area);
|
||||
return Math.Max(remaining.Count, (regions + 1) / 2);
|
||||
}
|
||||
|
||||
bool EliminatesColor(HashSet<(int,int)> area, int color)
|
||||
{
|
||||
var newArea = Flood(area, color);
|
||||
for (int x = 0; x < GRID_W; x++)
|
||||
for (int y = 0; y < GRID_H; y++)
|
||||
if (!newArea.Contains((x,y)) && grid[x,y] == color) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
int Eval(HashSet<(int,int)> area, int depth, int alpha)
|
||||
{
|
||||
if (area.Count == TOTAL) return 1000 - depth;
|
||||
if (depth <= 0) return area.Count - Heuristic(area) * 10;
|
||||
|
||||
var borders = BorderColors(area);
|
||||
int best = -999;
|
||||
|
||||
var moves = borders.Select(c => {
|
||||
var next = Flood(area, c);
|
||||
bool elim = EliminatesColor(area, c);
|
||||
return (c, next.Count - area.Count, elim, next);
|
||||
}).OrderByDescending(m => m.Item3 ? 1000 : 0)
|
||||
.ThenByDescending(m => m.Item2).ToList();
|
||||
|
||||
foreach (var (c, gain, elim, next) in moves)
|
||||
{
|
||||
int score = Eval(next, depth - 1, best);
|
||||
if (score > best) best = score;
|
||||
if (best >= alpha) break;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
(int color, int score) BestMove(HashSet<(int,int)> area, int depth)
|
||||
{
|
||||
var borders = BorderColors(area);
|
||||
int bestC = 0, bestS = -9999;
|
||||
|
||||
var moves = borders.Select(c => {
|
||||
var next = Flood(area, c);
|
||||
bool elim = EliminatesColor(area, c);
|
||||
return (c, next.Count - area.Count, elim, next);
|
||||
}).OrderByDescending(m => m.Item3 ? 1000 : 0)
|
||||
.ThenByDescending(m => m.Item2).ToList();
|
||||
|
||||
foreach (var (c, gain, elim, next) in moves)
|
||||
{
|
||||
int score;
|
||||
if (next.Count == TOTAL) score = 10000;
|
||||
else score = Eval(next, depth - 1, bestS) + (elim ? 50 : 0);
|
||||
|
||||
if (score > bestS) { bestS = score; bestC = c; }
|
||||
}
|
||||
return (bestC, bestS);
|
||||
}
|
||||
|
||||
List<int> Solve()
|
||||
{
|
||||
var moves = new List<int>();
|
||||
var area = InitArea();
|
||||
|
||||
while (area.Count < TOTAL && moves.Count < 32)
|
||||
{
|
||||
int remaining = TOTAL - area.Count;
|
||||
int depth = remaining > 200 ? 3 : remaining > 100 ? 4 : remaining > 50 ? 5 : 6;
|
||||
|
||||
var (c, s) = BestMove(area, depth);
|
||||
if (c == 0) break;
|
||||
|
||||
moves.Add(c);
|
||||
area = Flood(area, c);
|
||||
}
|
||||
return moves;
|
||||
}
|
||||
|
||||
void Click(int color)
|
||||
{
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != KIND_TILE) continue;
|
||||
int x = item.Location.X, y = item.Location.Y;
|
||||
if (x < GAME_MIN_X || x > GAME_MAX_X || y < GAME_MIN_Y || y > GAME_MAX_Y) continue;
|
||||
int state = GetState(item);
|
||||
if (state == color)
|
||||
{
|
||||
Send(Out["ClickFurni"], (int)item.Id, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log("═══════════════════════════════════════════");
|
||||
Log(" FLOOD-IT BOT - RESEARCH BASED");
|
||||
Log("═══════════════════════════════════════════");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
ReadGrid();
|
||||
var area = InitArea();
|
||||
Log($"Start: {area.Count}/{TOTAL}");
|
||||
|
||||
if (area.Count == TOTAL) { Log("COMPLETE!"); Delay(2000); continue; }
|
||||
|
||||
Log("Solving...");
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
var solution = Solve();
|
||||
sw.Stop();
|
||||
Log($"Solution: {string.Join(",", solution)} ({solution.Count} moves) in {sw.ElapsedMilliseconds}ms");
|
||||
|
||||
foreach (var c in solution)
|
||||
{
|
||||
Click(c);
|
||||
Log($"Click {c}");
|
||||
Delay(CLICK_DELAY);
|
||||
}
|
||||
|
||||
Delay(1000);
|
||||
}
|
||||
97
Floor Items CSV Exporter.csx
Normal file
97
Floor Items CSV Exporter.csx
Normal file
@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
string Safe(string s)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(s) ? "" : s.Replace("\r", " ").Replace("\n", " ").Trim();
|
||||
}
|
||||
|
||||
int GetKind(dynamic item)
|
||||
{
|
||||
try { return (int)item.Kind; }
|
||||
catch { return -1; }
|
||||
}
|
||||
|
||||
int GetState(dynamic item)
|
||||
{
|
||||
try { return int.Parse(item.State?.ToString() ?? "0", CultureInfo.InvariantCulture); }
|
||||
catch { return 0; }
|
||||
}
|
||||
|
||||
int GetDir(dynamic item)
|
||||
{
|
||||
try { return (int)item.Direction; }
|
||||
catch { return 0; }
|
||||
}
|
||||
|
||||
string GetNameSafe(dynamic item)
|
||||
{
|
||||
try { return Safe(item.GetName()); }
|
||||
catch { return ""; }
|
||||
}
|
||||
|
||||
string EscCsv(string s)
|
||||
{
|
||||
s = s ?? "";
|
||||
if (s.Contains(",") || s.Contains("\"") || s.Contains("\n"))
|
||||
return "\"" + s.Replace("\"", "\"\"") + "\"";
|
||||
return s;
|
||||
}
|
||||
|
||||
var rows = new List<string>();
|
||||
rows.Add("id,kind,name,x,y,z,state,dir");
|
||||
|
||||
int count = 0;
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
count++;
|
||||
|
||||
long id = item.Id;
|
||||
int kind = GetKind(item);
|
||||
string name = GetNameSafe(item);
|
||||
int x = item.Location.X;
|
||||
int y = item.Location.Y;
|
||||
double z = item.Location.Z;
|
||||
int state = GetState(item);
|
||||
int dir = GetDir(item);
|
||||
|
||||
rows.Add(string.Join(",", new[] {
|
||||
id.ToString(CultureInfo.InvariantCulture),
|
||||
kind.ToString(CultureInfo.InvariantCulture),
|
||||
EscCsv(name),
|
||||
x.ToString(CultureInfo.InvariantCulture),
|
||||
y.ToString(CultureInfo.InvariantCulture),
|
||||
z.ToString("0.###", CultureInfo.InvariantCulture),
|
||||
state.ToString(CultureInfo.InvariantCulture),
|
||||
dir.ToString(CultureInfo.InvariantCulture)
|
||||
}));
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
Log("ERROR: No floor items found.");
|
||||
return;
|
||||
}
|
||||
|
||||
string roomName = "room";
|
||||
try { roomName = Safe(Room?.Name ?? "room"); } catch { roomName = "room"; }
|
||||
if (string.IsNullOrWhiteSpace(roomName)) roomName = "room";
|
||||
|
||||
var invalid = Path.GetInvalidFileNameChars();
|
||||
foreach (char c in invalid) roomName = roomName.Replace(c, '_');
|
||||
|
||||
string stamp = DateTime.Now.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture);
|
||||
string exportDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "HabboFloorExports");
|
||||
Directory.CreateDirectory(exportDir);
|
||||
|
||||
string filePath = Path.Combine(exportDir, $"{roomName}_floor_{stamp}.csv");
|
||||
File.WriteAllText(filePath, string.Join(Environment.NewLine, rows), Encoding.UTF8);
|
||||
|
||||
Log("=== Floor Export Complete ===");
|
||||
Log($"Items exported: {count}");
|
||||
Log($"File: {filePath}");
|
||||
367
Furni-Matic Auto-Eco.csx
Normal file
367
Furni-Matic Auto-Eco.csx
Normal file
@ -0,0 +1,367 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Xabbo.Messages;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class ItemView {
|
||||
[JsonPropertyName("h")] public string HabboId { get; set; } = "";
|
||||
[JsonPropertyName("n")] public string Name { get; set; } = "";
|
||||
[JsonPropertyName("r")] public int Revision { get; set; }
|
||||
[JsonPropertyName("c")] public int Count { get; set; }
|
||||
}
|
||||
|
||||
public class LiveState {
|
||||
[JsonPropertyName("items")] public List<ItemView> Items { get; set; } = new();
|
||||
[JsonPropertyName("status")] public StatusUpdate Status { get; set; } = new();
|
||||
}
|
||||
|
||||
public class RecycleRequest {
|
||||
[JsonPropertyName("items")] public List<RecycleItem> Items { get; set; } = new();
|
||||
[JsonPropertyName("delay")] public int Delay { get; set; }
|
||||
}
|
||||
|
||||
public class RecycleItem {
|
||||
[JsonPropertyName("h")] public string HabboId { get; set; } = "";
|
||||
[JsonPropertyName("a")] public int Amount { get; set; }
|
||||
}
|
||||
|
||||
public class StatusUpdate {
|
||||
[JsonPropertyName("done")] public int Done { get; set; }
|
||||
[JsonPropertyName("total")] public int Total { get; set; }
|
||||
}
|
||||
|
||||
var port = 8226;
|
||||
var queue = new List<int>();
|
||||
var progress = 0;
|
||||
var total = 0;
|
||||
var delay = 12000;
|
||||
var lastRun = DateTime.Now;
|
||||
HttpListener? server = null;
|
||||
|
||||
void TryRefreshInventory() {
|
||||
try { Send(Out["RequestFurniInventory"]); return; } catch { }
|
||||
}
|
||||
|
||||
OnIntercept(In["RecyclerFinished"], e => {
|
||||
TryRefreshInventory();
|
||||
});
|
||||
|
||||
LiveState GetCurrentState() {
|
||||
EnsureInventory(10000);
|
||||
var currentItems = Inventory.Where(x => x.IsRecyclable)
|
||||
.GroupBy(x => x.GetDescriptor())
|
||||
.Where(g => g.Count() >= 8)
|
||||
.Select(g => new ItemView {
|
||||
HabboId = g.Key.GetInfo().Identifier,
|
||||
Name = g.Key.GetName(),
|
||||
Revision = g.Key.GetInfo().Revision,
|
||||
Count = g.Count()
|
||||
})
|
||||
.OrderByDescending(x => x.Count)
|
||||
.ToList();
|
||||
|
||||
if (progress >= total && total > 0) {
|
||||
progress = total = 0;
|
||||
}
|
||||
|
||||
return new LiveState {
|
||||
Items = currentItems,
|
||||
Status = new StatusUpdate { Done = progress, Total = total }
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
log("starting server...");
|
||||
var html = @"<!DOCTYPE html>
|
||||
<html lang=""en"">
|
||||
<head>
|
||||
<meta charset=""utf-8""><title>Recycler</title><meta name=""viewport"" content=""width=device-width, initial-scale=1"">
|
||||
<link rel=""preconnect"" href=""https://fonts.googleapis.com""><link rel=""preconnect"" href=""https://fonts.gstatic.com"" crossorigin>
|
||||
<link href=""https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap"" rel=""stylesheet"">
|
||||
<style>
|
||||
:root{--bg-deep:#111;--bg-med:#1C1C1C;--bg-light:#2C2C2C;--border:#333;--text-bright:#EAEAEA;--text-dim:#888;--accent:#00dd99;--accent-dark:#00b37b;--danger:#ff4757;}
|
||||
*{margin:0;padding:0;box-sizing:border-box;}
|
||||
body{background:var(--bg-deep);color:var(--text-bright);font-family:'Inter',sans-serif;padding:1.5rem;font-size:14px;}
|
||||
.wrap{max-width:1200px;margin:0 auto;display:grid;grid-template-columns:1fr;gap:1rem;}
|
||||
.panel{background:var(--bg-med);border:1px solid var(--border);border-radius:6px;padding:1rem;}
|
||||
.header{text-align:center;font-size:1.5rem;font-weight:700;color:var(--text-bright);margin-bottom:1rem;}
|
||||
.controls{display:flex;align-items:center;justify-content:center;gap:0.75rem;flex-wrap:wrap;}
|
||||
.controls label{display:flex;align-items:center;gap:0.5rem;color:var(--text-dim);}
|
||||
button{background:var(--accent);color:#000;border:none;padding:0.5rem 1rem;border-radius:4px;font-weight:600;cursor:pointer;transition:background-color 150ms ease;}
|
||||
button:hover{background:var(--accent-dark);}
|
||||
button:disabled{background:var(--bg-light);color:var(--text-dim);cursor:not-allowed;}
|
||||
button.secondary{background:var(--bg-light);color:var(--text-bright);border:1px solid var(--border);}
|
||||
button.secondary:hover{background-color:var(--border);}
|
||||
button.danger{background:var(--danger);}
|
||||
button.danger:hover{background:#d63031;}
|
||||
input[type=number],input[type=text]{width:80px;background:var(--bg-light);border:1px solid var(--border);color:var(--text-bright);padding:0.5rem;border-radius:4px;text-align:center;}
|
||||
input[type=text]{width:100%;text-align:left;}
|
||||
input:focus{outline:none;border-color:var(--accent);}
|
||||
.progress-bar{height:2rem;background:var(--bg-light);border-radius:4px;position:relative;overflow:hidden;}
|
||||
.progress-fill{width:0;height:100%;background:var(--accent);transition:width 300ms ease-out;}
|
||||
.progress-text{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-weight:600;text-shadow:0 1px 2px #000;}
|
||||
.main-layout{display:grid;grid-template-columns:2fr 1fr;gap:1rem;}
|
||||
.status-bar{display:flex;justify-content:space-between;align-items:center;padding:0.5rem 0;color:var(--text-dim);font-size:0.8rem;}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:0.75rem;max-height:60vh;overflow-y:auto;padding-right:0.5rem;}
|
||||
.grid::-webkit-scrollbar{width:6px;} .grid::-webkit-scrollbar-track{background:transparent;} .grid::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px;}
|
||||
.card{background:var(--bg-light);border-radius:4px;padding:0.75rem;text-align:center;cursor:pointer;border:2px solid transparent;transition:border-color 150ms ease, background-color 150ms ease;}
|
||||
.card.selected{border-color:var(--accent);}
|
||||
.card .name{font-size:0.8rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0.25rem 0;}
|
||||
.card .count{font-size:0.75rem;color:var(--text-dim);}
|
||||
h2{font-size:1rem;font-weight:600;margin-bottom:0.75rem;}
|
||||
.queue-list{display:flex;flex-direction:column;gap:0.5rem;max-height:calc(60vh - 2rem);overflow-y:auto;}
|
||||
.queue-item{display:flex;align-items:center;gap:0.5rem;background:var(--bg-light);padding:0.5rem;border-radius:4px;}
|
||||
.queue-item .name{flex-grow:1;font-size:0.8rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||
.queue-item .remove-btn{background:transparent;border:none;color:var(--text-dim);font-size:1.25rem;padding:0 0.25rem;cursor:pointer;line-height:1;}
|
||||
.queue-item .remove-btn:hover{color:var(--danger);}
|
||||
.empty-state{text-align:center;color:var(--text-dim);font-size:0.85rem;padding:3rem 1rem;border:2px dashed var(--border);border-radius:4px;}
|
||||
@media (max-width: 900px) {.main-layout{grid-template-columns:1fr;}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class=""wrap"">
|
||||
<div class=""header"">recycler</div>
|
||||
<div class=""panel""><div class=""progress-bar""><div class=""progress-fill"" id=""progress-fill""></div><div class=""progress-text"" id=""progress-text"">idle</div></div></div>
|
||||
<div class=""panel"">
|
||||
<div class=""controls"">
|
||||
<button id=""start-btn"">start</button><button id=""stop-btn"" class=""danger"">stop</button>
|
||||
<label>delay <input type=""number"" id=""delay-input"" value=""12000"" min=""500"" step=""100""></label>
|
||||
<button id=""clear-selection-btn"" class=""secondary"">clear selection</button><button id=""clear-queue-btn"" class=""secondary"">clear queue</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class=""main-layout"">
|
||||
<div class=""panel grid-container"">
|
||||
<input type=""text"" id=""search-input"" placeholder=""search items..."">
|
||||
<div class=""status-bar""><span id=""info-text"">select items</span><button id=""add-selected-btn"" class=""secondary"" style=""display:none;"">add selected to queue</button></div>
|
||||
<div class=""grid"" id=""grid""></div>
|
||||
</div>
|
||||
<div class=""panel queue-container"">
|
||||
<h2>queue</h2>
|
||||
<div class=""queue-list"" id=""queue-list""></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const state={items:[],selected:new Set(),queue:[],amounts:{},searchTerm:'',status:{done:0,total:0},wasRunning:false};
|
||||
const dom={grid:document.getElementById('grid'),queueList:document.getElementById('queue-list'),infoText:document.getElementById('info-text'),addSelectedBtn:document.getElementById('add-selected-btn'),progressFill:document.getElementById('progress-fill'),progressText:document.getElementById('progress-text'),searchInput:document.getElementById('search-input'),delayInput:document.getElementById('delay-input'),startBtn:document.getElementById('start-btn')};
|
||||
const api={
|
||||
recycle:()=>fetch('/recycle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({items:state.queue.map(habboId=>({h:habboId,a:state.amounts[habboId]})) ,delay:parseInt(dom.delayInput.value)})}),
|
||||
stop:()=>fetch('/stop',{method:'POST'}),
|
||||
getState:()=>fetch('/state').then(r=>r.json())
|
||||
};
|
||||
const ui={
|
||||
renderCard:item=>{
|
||||
const card=document.createElement('div');
|
||||
card.className=`card ${state.selected.has(item.h)?'selected':''}`;
|
||||
card.dataset.id=item.h;
|
||||
card.innerHTML=`<img src=""https://images.habbo.com/dcr/hof_furni/${item.r}/${item.h}_icon.png"" alt=""""><div class=""name"" title=""${item.n}"">${item.n}</div><div class=""count"">${item.c}x | ${state.amounts[item.h]}</div>`;
|
||||
return card;
|
||||
},
|
||||
renderQueueItem:habboId=>{
|
||||
const item=state.items.find(x=>x.h===habboId);
|
||||
if(!item)return null;
|
||||
const li=document.createElement('div');
|
||||
li.className='queue-item';li.dataset.id=habboId;
|
||||
li.innerHTML=`<img src=""https://images.habbo.com/dcr/hof_furni/${item.r}/${item.h}_icon.png"" alt=""""><span class=""name"">${item.n} (${state.amounts[habboId]})</span><button class=""remove-btn"">×</button>`;
|
||||
return li;
|
||||
},
|
||||
updateGrid:()=>{
|
||||
const fragment=document.createDocumentFragment();
|
||||
const filtered=state.items.filter(item=>item.n.toLowerCase().includes(state.searchTerm));
|
||||
if(filtered.length===0){dom.grid.innerHTML='<div class=""empty-state"">no items found</div>';return;}
|
||||
filtered.forEach(item=>fragment.appendChild(ui.renderCard(item)));
|
||||
dom.grid.innerHTML='';dom.grid.appendChild(fragment);
|
||||
},
|
||||
updateQueue:()=>{
|
||||
state.queue=state.queue.filter(habboId=>state.items.some(item=>item.h===habboId));
|
||||
if(state.queue.length===0){dom.queueList.innerHTML='<div class=""empty-state"">queue is empty</div>';return;}
|
||||
const fragment=document.createDocumentFragment();
|
||||
state.queue.forEach(habboId=>{
|
||||
const itemElement = ui.renderQueueItem(habboId);
|
||||
if(itemElement) fragment.appendChild(itemElement);
|
||||
});
|
||||
dom.queueList.innerHTML='';dom.queueList.appendChild(fragment);
|
||||
},
|
||||
updateInfo:()=>{
|
||||
dom.infoText.textContent=`${state.selected.size} items selected`;
|
||||
dom.addSelectedBtn.style.display=state.selected.size>0?'inline-block':'none';
|
||||
},
|
||||
updateStatus:()=>{
|
||||
const currentStatus=state.status;
|
||||
const isRunning=currentStatus.total>0;
|
||||
const percent=isRunning?Math.round((currentStatus.done/currentStatus.total)*100):0;
|
||||
dom.progressFill.style.width=`${percent}%`;
|
||||
dom.startBtn.disabled=isRunning;
|
||||
if(isRunning)dom.progressText.textContent=`${currentStatus.done}/${currentStatus.total}`;
|
||||
else if(state.wasRunning)dom.progressText.textContent='complete';
|
||||
else dom.progressText.textContent='idle';
|
||||
if(state.wasRunning&&!isRunning)setTimeout(()=>dom.progressText.textContent='idle',2500);
|
||||
state.wasRunning=isRunning;
|
||||
},
|
||||
renderAll:()=>{ui.updateGrid();ui.updateQueue();ui.updateInfo();ui.updateStatus();}
|
||||
};
|
||||
const handlers={
|
||||
gridClick:e=>{
|
||||
const card=e.target.closest('.card');if(!card)return;
|
||||
const habboId=card.dataset.id;
|
||||
if(e.detail===2){
|
||||
const inQueue=state.queue.includes(habboId);
|
||||
if(inQueue)state.queue=state.queue.filter(x=>x!==habboId);
|
||||
else state.queue.push(habboId);
|
||||
}else{
|
||||
state.selected.has(habboId)?state.selected.delete(habboId):state.selected.add(habboId);
|
||||
}
|
||||
ui.renderAll();
|
||||
},
|
||||
queueClick:e=>{
|
||||
if(!e.target.classList.contains('remove-btn'))return;
|
||||
const habboId=e.target.closest('.queue-item').dataset.id;
|
||||
state.queue=state.queue.filter(x=>x!==habboId);
|
||||
ui.renderAll();
|
||||
},
|
||||
addSelected:()=>{
|
||||
state.selected.forEach(habboId=>{if(!state.queue.includes(habboId))state.queue.push(habboId);});
|
||||
state.selected.clear();
|
||||
ui.renderAll();
|
||||
},
|
||||
updateAllAmounts:()=>{
|
||||
state.items.forEach(item=>{if(!state.amounts[item.h])state.amounts[item.h]=Math.floor(item.c/8)*8;});
|
||||
},
|
||||
init:()=>{
|
||||
document.getElementById('start-btn').onclick=()=>{if(state.queue.length===0){alert('add items to queue');return;}api.recycle();};
|
||||
document.getElementById('stop-btn').onclick=api.stop;
|
||||
document.getElementById('clear-selection-btn').onclick=()=>{state.selected.clear();ui.renderAll();};
|
||||
document.getElementById('clear-queue-btn').onclick=()=>{state.queue=[];ui.renderAll();};
|
||||
dom.addSelectedBtn.onclick=handlers.addSelected;
|
||||
dom.grid.addEventListener('click',handlers.gridClick);
|
||||
dom.queueList.addEventListener('click',handlers.queueClick);
|
||||
dom.searchInput.addEventListener('input',e=>{state.searchTerm=e.target.value.toLowerCase();ui.updateGrid();});
|
||||
setInterval(()=>{
|
||||
api.getState().then(newState=>{
|
||||
state.items=newState.items;
|
||||
state.status=newState.status;
|
||||
handlers.updateAllAmounts();
|
||||
ui.renderAll();
|
||||
});
|
||||
},1500);
|
||||
}
|
||||
};
|
||||
handlers.init();
|
||||
</script>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
server = new HttpListener();
|
||||
server.Prefixes.Add($"http://localhost:{port}/");
|
||||
server.Start();
|
||||
|
||||
Process.Start(new ProcessStartInfo { FileName = $"http://localhost:{port}/", UseShellExecute = true });
|
||||
log($"server listening on http://localhost:{port}");
|
||||
|
||||
_ = Task.Run(async () => {
|
||||
while (Run && server.IsListening) {
|
||||
try {
|
||||
var ctx = await server.GetContextAsync();
|
||||
_ = Task.Run(() => HandleContextAsync(ctx, html));
|
||||
} catch { break; }
|
||||
}
|
||||
});
|
||||
|
||||
while (Run) {
|
||||
if (queue.Count >= 8 && (DateTime.Now - lastRun).TotalMilliseconds >= delay) {
|
||||
var batch = queue.Take(8).ToList();
|
||||
queue.RemoveRange(0, 8);
|
||||
Send(Out["RecycleItems"], 8, batch[0], batch[1], batch[2], batch[3], batch[4], batch[5], batch[6], batch[7]);
|
||||
progress += 8;
|
||||
lastRun = DateTime.Now;
|
||||
}
|
||||
await Task.Delay(10);
|
||||
}
|
||||
}
|
||||
catch (TaskCanceledException) {
|
||||
log("error: inventory load timed out. ensure you are fully in-game.");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
log($"unhandled exception: {ex.Message}");
|
||||
}
|
||||
finally {
|
||||
server?.Stop();
|
||||
server?.Close();
|
||||
log("server shut down.");
|
||||
}
|
||||
|
||||
async Task HandleContextAsync(HttpListenerContext ctx, string html) {
|
||||
var req = ctx.Request;
|
||||
var res = ctx.Response;
|
||||
try {
|
||||
var endpoint = (req.HttpMethod, req.Url?.AbsolutePath);
|
||||
switch (endpoint) {
|
||||
case ("GET", "/"):
|
||||
await WriteResponseAsync(res, html, "text/html");
|
||||
break;
|
||||
case ("GET", "/state"):
|
||||
var stateJson = JsonSerializer.Serialize(GetCurrentState());
|
||||
await WriteResponseAsync(res, stateJson, "application/json");
|
||||
break;
|
||||
case ("POST", "/recycle"):
|
||||
TryRefreshInventory();
|
||||
EnsureInventory(10000);
|
||||
using (var r = new StreamReader(req.InputStream)) {
|
||||
var payload = JsonSerializer.Deserialize<RecycleRequest>(await r.ReadToEndAsync());
|
||||
if (payload != null) {
|
||||
queue.Clear();
|
||||
delay = Math.Clamp(payload.Delay, 500, 60000);
|
||||
foreach (var pItem in payload.Items) {
|
||||
var itemInstances = Inventory
|
||||
.Where(x => x.IsRecyclable && x.GetInfo().Identifier == pItem.HabboId)
|
||||
.Select(i => -(int)i.Id)
|
||||
.ToList();
|
||||
int amount = Math.Min(pItem.Amount, itemInstances.Count);
|
||||
queue.AddRange(itemInstances.Take(amount));
|
||||
}
|
||||
total = queue.Count;
|
||||
progress = 0;
|
||||
log($"queueing {total} items, {delay}ms delay.");
|
||||
}
|
||||
}
|
||||
res.StatusCode = 200;
|
||||
break;
|
||||
case ("POST", "/stop"):
|
||||
queue.Clear();
|
||||
progress = total = 0;
|
||||
log("recycling stopped.");
|
||||
res.StatusCode = 200;
|
||||
break;
|
||||
default:
|
||||
res.StatusCode = 404;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
log($"request error: {ex.Message}");
|
||||
if(!res.OutputStream.CanWrite) return;
|
||||
res.StatusCode = 500;
|
||||
}
|
||||
finally {
|
||||
res.Close();
|
||||
}
|
||||
}
|
||||
|
||||
async Task WriteResponseAsync(HttpListenerResponse res, string content, string type) {
|
||||
var buffer = Encoding.UTF8.GetBytes(content);
|
||||
res.ContentType = type;
|
||||
res.ContentLength64 = buffer.Length;
|
||||
await res.OutputStream.WriteAsync(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
void log(string message) => Log(message);
|
||||
20
Furni-Matic Unboxing.csx
Normal file
20
Furni-Matic Unboxing.csx
Normal file
@ -0,0 +1,20 @@
|
||||
Send(Out["RequestFurniInventory"]);
|
||||
Delay(200);
|
||||
EnsureInventory(5000);
|
||||
|
||||
var name = "Mystery Box";
|
||||
var boxes = Inventory.Where(x => x.GetDescriptor().GetName() == name);
|
||||
var freeTiles = Heightmap.Where(x => x.IsFree);
|
||||
|
||||
foreach (var box in boxes) {
|
||||
Place(box, Rand(freeTiles).Location);
|
||||
Delay(50);
|
||||
}
|
||||
|
||||
while (Run) {
|
||||
foreach (var box in FloorItems.Where(x => x.GetName() == name))
|
||||
Send(Out["PresentOpen"], (int)box.Id);
|
||||
Delay(100);
|
||||
}
|
||||
|
||||
Wait();
|
||||
366
Furni.csx
Normal file
366
Furni.csx
Normal file
@ -0,0 +1,366 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Xabbo.Messages;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class ItemView {
|
||||
[JsonPropertyName("h")] public string HabboId { get; set; } = "";
|
||||
[JsonPropertyName("n")] public string Name { get; set; } = "";
|
||||
[JsonPropertyName("r")] public int Revision { get; set; }
|
||||
[JsonPropertyName("c")] public int Count { get; set; }
|
||||
}
|
||||
|
||||
public class LiveState {
|
||||
[JsonPropertyName("items")] public List<ItemView> Items { get; set; } = new();
|
||||
[JsonPropertyName("status")] public StatusUpdate Status { get; set; } = new();
|
||||
}
|
||||
|
||||
public class RecycleRequest {
|
||||
[JsonPropertyName("items")] public List<RecycleItem> Items { get; set; } = new();
|
||||
[JsonPropertyName("delay")] public int Delay { get; set; }
|
||||
}
|
||||
|
||||
public class RecycleItem {
|
||||
[JsonPropertyName("h")] public string HabboId { get; set; } = "";
|
||||
[JsonPropertyName("a")] public int Amount { get; set; }
|
||||
}
|
||||
|
||||
public class StatusUpdate {
|
||||
[JsonPropertyName("done")] public int Done { get; set; }
|
||||
[JsonPropertyName("total")] public int Total { get; set; }
|
||||
}
|
||||
|
||||
var port = 8226;
|
||||
var queue = new List<int>();
|
||||
var progress = 0;
|
||||
var total = 0;
|
||||
var delay = 12000;
|
||||
var lastRun = DateTime.Now;
|
||||
HttpListener? server = null;
|
||||
|
||||
void TryRefreshInventory() {
|
||||
try { Send(Out["RequestFurniInventory"]); return; } catch { }
|
||||
}
|
||||
|
||||
OnIntercept(In["RecyclerFinished"], e => {
|
||||
TryRefreshInventory();
|
||||
});
|
||||
|
||||
LiveState GetCurrentState() {
|
||||
EnsureInventory(10000);
|
||||
var currentItems = Inventory.Where(x => x.IsRecyclable)
|
||||
.GroupBy(x => x.GetDescriptor())
|
||||
.Where(g => g.Count() >= 8)
|
||||
.Select(g => new ItemView {
|
||||
HabboId = g.Key.GetInfo().Identifier,
|
||||
Name = g.Key.GetName(),
|
||||
Revision = g.Key.GetInfo().Revision,
|
||||
Count = g.Count()
|
||||
})
|
||||
.OrderByDescending(x => x.Count)
|
||||
.ToList();
|
||||
|
||||
if (progress >= total && total > 0) {
|
||||
progress = total = 0;
|
||||
}
|
||||
|
||||
return new LiveState {
|
||||
Items = currentItems,
|
||||
Status = new StatusUpdate { Done = progress, Total = total }
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
log("starting server...");
|
||||
var html = @"<!DOCTYPE html>
|
||||
<html lang=""en"">
|
||||
<head>
|
||||
<meta charset=""utf-8""><title>Recycler</title><meta name=""viewport"" content=""width=device-width, initial-scale=1"">
|
||||
<link rel=""preconnect"" href=""https://fonts.googleapis.com""><link rel=""preconnect"" href=""https://fonts.gstatic.com"" crossorigin>
|
||||
<link href=""https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap"" rel=""stylesheet"">
|
||||
<style>
|
||||
:root{--bg-deep:#111;--bg-med:#1C1C1C;--bg-light:#2C2C2C;--border:#333;--text-bright:#EAEAEA;--text-dim:#888;--accent:#00dd99;--accent-dark:#00b37b;--danger:#ff4757;}
|
||||
*{margin:0;padding:0;box-sizing:border-box;}
|
||||
body{background:var(--bg-deep);color:var(--text-bright);font-family:'Inter',sans-serif;padding:1.5rem;font-size:14px;}
|
||||
.wrap{max-width:1200px;margin:0 auto;display:grid;grid-template-columns:1fr;gap:1rem;}
|
||||
.panel{background:var(--bg-med);border:1px solid var(--border);border-radius:6px;padding:1rem;}
|
||||
.header{text-align:center;font-size:1.5rem;font-weight:700;color:var(--text-bright);margin-bottom:1rem;}
|
||||
.controls{display:flex;align-items:center;justify-content:center;gap:0.75rem;flex-wrap:wrap;}
|
||||
.controls label{display:flex;align-items:center;gap:0.5rem;color:var(--text-dim);}
|
||||
button{background:var(--accent);color:#000;border:none;padding:0.5rem 1rem;border-radius:4px;font-weight:600;cursor:pointer;transition:background-color 150ms ease;}
|
||||
button:hover{background:var(--accent-dark);}
|
||||
button:disabled{background:var(--bg-light);color:var(--text-dim);cursor:not-allowed;}
|
||||
button.secondary{background:var(--bg-light);color:var(--text-bright);border:1px solid var(--border);}
|
||||
button.secondary:hover{background-color:var(--border);}
|
||||
button.danger{background:var(--danger);}
|
||||
button.danger:hover{background:#d63031;}
|
||||
input[type=number],input[type=text]{width:80px;background:var(--bg-light);border:1px solid var(--border);color:var(--text-bright);padding:0.5rem;border-radius:4px;text-align:center;}
|
||||
input[type=text]{width:100%;text-align:left;}
|
||||
input:focus{outline:none;border-color:var(--accent);}
|
||||
.progress-bar{height:2rem;background:var(--bg-light);border-radius:4px;position:relative;overflow:hidden;}
|
||||
.progress-fill{width:0;height:100%;background:var(--accent);transition:width 300ms ease-out;}
|
||||
.progress-text{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-weight:600;text-shadow:0 1px 2px #000;}
|
||||
.main-layout{display:grid;grid-template-columns:2fr 1fr;gap:1rem;}
|
||||
.status-bar{display:flex;justify-content:space-between;align-items:center;padding:0.5rem 0;color:var(--text-dim);font-size:0.8rem;}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:0.75rem;max-height:60vh;overflow-y:auto;padding-right:0.5rem;}
|
||||
.grid::-webkit-scrollbar{width:6px;} .grid::-webkit-scrollbar-track{background:transparent;} .grid::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px;}
|
||||
.card{background:var(--bg-light);border-radius:4px;padding:0.75rem;text-align:center;cursor:pointer;border:2px solid transparent;transition:border-color 150ms ease, background-color 150ms ease;}
|
||||
.card.selected{border-color:var(--accent);}
|
||||
.card .name{font-size:0.8rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0.25rem 0;}
|
||||
.card .count{font-size:0.75rem;color:var(--text-dim);}
|
||||
h2{font-size:1rem;font-weight:600;margin-bottom:0.75rem;}
|
||||
.queue-list{display:flex;flex-direction:column;gap:0.5rem;max-height:calc(60vh - 2rem);overflow-y:auto;}
|
||||
.queue-item{display:flex;align-items:center;gap:0.5rem;background:var(--bg-light);padding:0.5rem;border-radius:4px;}
|
||||
.queue-item .name{flex-grow:1;font-size:0.8rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||
.queue-item .remove-btn{background:transparent;border:none;color:var(--text-dim);font-size:1.25rem;padding:0 0.25rem;cursor:pointer;line-height:1;}
|
||||
.queue-item .remove-btn:hover{color:var(--danger);}
|
||||
.empty-state{text-align:center;color:var(--text-dim);font-size:0.85rem;padding:3rem 1rem;border:2px dashed var(--border);border-radius:4px;}
|
||||
@media (max-width: 900px) {.main-layout{grid-template-columns:1fr;}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class=""wrap"">
|
||||
<div class=""header"">recycler</div>
|
||||
<div class=""panel""><div class=""progress-bar""><div class=""progress-fill"" id=""progress-fill""></div><div class=""progress-text"" id=""progress-text"">idle</div></div></div>
|
||||
<div class=""panel"">
|
||||
<div class=""controls"">
|
||||
<button id=""start-btn"">start</button><button id=""stop-btn"" class=""danger"">stop</button>
|
||||
<label>delay <input type=""number"" id=""delay-input"" value=""12000"" min=""500"" step=""100""></label>
|
||||
<button id=""clear-selection-btn"" class=""secondary"">clear selection</button><button id=""clear-queue-btn"" class=""secondary"">clear queue</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class=""main-layout"">
|
||||
<div class=""panel grid-container"">
|
||||
<input type=""text"" id=""search-input"" placeholder=""search items..."">
|
||||
<div class=""status-bar""><span id=""info-text"">select items</span><button id=""add-selected-btn"" class=""secondary"" style=""display:none;"">add selected to queue</button></div>
|
||||
<div class=""grid"" id=""grid""></div>
|
||||
</div>
|
||||
<div class=""panel queue-container"">
|
||||
<h2>queue</h2>
|
||||
<div class=""queue-list"" id=""queue-list""></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const state={items:[],selected:new Set(),queue:[],amounts:{},searchTerm:'',status:{done:0,total:0},wasRunning:false};
|
||||
const dom={grid:document.getElementById('grid'),queueList:document.getElementById('queue-list'),infoText:document.getElementById('info-text'),addSelectedBtn:document.getElementById('add-selected-btn'),progressFill:document.getElementById('progress-fill'),progressText:document.getElementById('progress-text'),searchInput:document.getElementById('search-input'),delayInput:document.getElementById('delay-input'),startBtn:document.getElementById('start-btn')};
|
||||
const api={
|
||||
recycle:()=>fetch('/recycle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({items:state.queue.map(habboId=>({h:habboId,a:state.amounts[habboId]})) ,delay:parseInt(dom.delayInput.value)})}),
|
||||
stop:()=>fetch('/stop',{method:'POST'}),
|
||||
getState:()=>fetch('/state').then(r=>r.json())
|
||||
};
|
||||
const ui={
|
||||
renderCard:item=>{
|
||||
const card=document.createElement('div');
|
||||
card.className=`card ${state.selected.has(item.h)?'selected':''}`;
|
||||
card.dataset.id=item.h;
|
||||
card.innerHTML=`<img src=""https://images.habbo.com/dcr/hof_furni/${item.r}/${item.h}_icon.png"" alt=""""><div class=""name"" title=""${item.n}"">${item.n}</div><div class=""count"">${item.c}x | ${state.amounts[item.h]}</div>`;
|
||||
return card;
|
||||
},
|
||||
renderQueueItem:habboId=>{
|
||||
const item=state.items.find(x=>x.h===habboId);
|
||||
if(!item)return null;
|
||||
const li=document.createElement('div');
|
||||
li.className='queue-item';li.dataset.id=habboId;
|
||||
li.innerHTML=`<img src=""https://images.habbo.com/dcr/hof_furni/${item.r}/${item.h}_icon.png"" alt=""""><span class=""name"">${item.n} (${state.amounts[habboId]})</span><button class=""remove-btn"">×</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);
|
||||
60
Furniture Position Debug.csx
Normal file
60
Furniture Position Debug.csx
Normal file
@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// DEBUG - ZEIGT ALLE POSITIONEN
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
const int PILLOW_ID = 893412986;
|
||||
const int GATE_ID = 2147418143;
|
||||
|
||||
HashSet<int> greenRollers = new HashSet<int> {
|
||||
2147418115, 2147418119, 2147418133,
|
||||
2147418134, 2147418135, 2147418136
|
||||
};
|
||||
|
||||
int GetId(dynamic item) { try { return (int)item.Id; } catch { return 0; } }
|
||||
|
||||
Log("═══════════════════════════════════════");
|
||||
Log(" DEBUG - POSITIONEN");
|
||||
Log("═══════════════════════════════════════");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(1000);
|
||||
|
||||
Log("--- SCAN ---");
|
||||
|
||||
// Meine Position
|
||||
try
|
||||
{
|
||||
Log($"ICH: ({Self.Location.X}, {Self.Location.Y})");
|
||||
}
|
||||
catch { Log("ICH: nicht gefunden"); }
|
||||
|
||||
// Kissen
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetId(item) == PILLOW_ID)
|
||||
{
|
||||
Log($"KISSEN: ({item.Location.X}, {item.Location.Y})");
|
||||
}
|
||||
}
|
||||
|
||||
// Alle grünen Roller
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
int id = GetId(item);
|
||||
if (greenRollers.Contains(id))
|
||||
{
|
||||
Log($"ROLLER {id}: ({item.Location.X}, {item.Location.Y})");
|
||||
}
|
||||
if (id == GATE_ID)
|
||||
{
|
||||
Log($"GATE: ({item.Location.X}, {item.Location.Y})");
|
||||
}
|
||||
}
|
||||
}
|
||||
97
Heal Bot.csx
Normal file
97
Heal Bot.csx
Normal file
@ -0,0 +1,97 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
// Globale Variablen für den State
|
||||
bool isBusy = false;
|
||||
Dictionary<int, int> userMap = new Dictionary<int, int>();
|
||||
|
||||
Log("Heal-Script gestartet. Bitte einmal den Raum neu betreten!");
|
||||
|
||||
// 1. Reset beim Raumwechsel
|
||||
OnIntercept(In.RoomReady, e => {
|
||||
userMap.Clear();
|
||||
isBusy = false;
|
||||
Log("Raum geladen. Tracking aktiv.");
|
||||
});
|
||||
|
||||
// 2. User-Liste aufbauen (Index zu ID Zuordnung)
|
||||
OnIntercept(In.Users, e => {
|
||||
try {
|
||||
var p = e.Packet;
|
||||
int count = p.ReadInt();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
// Standard Habbo User-Parsing
|
||||
int id = p.ReadInt(); // Entity ID (Wichtig für den Klick)
|
||||
string name = p.ReadString();
|
||||
string motto = p.ReadString();
|
||||
string look = p.ReadString();
|
||||
int index = p.ReadInt(); // Index (Wichtig für den Chat)
|
||||
|
||||
// Restliche Daten überspringen (Position etc.)
|
||||
p.ReadInt(); // x
|
||||
p.ReadInt(); // y
|
||||
p.ReadString(); // z
|
||||
p.ReadInt(); // dir
|
||||
int type = p.ReadInt(); // type
|
||||
|
||||
// In Map speichern
|
||||
userMap[index] = id;
|
||||
}
|
||||
} catch {
|
||||
// Parsing Fehler ignorieren (falls Server-Struktur abweicht)
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Auf Chat reagieren
|
||||
OnIntercept(In.Chat, async e => {
|
||||
// Wenn wir gerade beschäftigt sind, ignorieren wir alles
|
||||
if (isBusy) return;
|
||||
|
||||
var p = e.Packet;
|
||||
int index = p.ReadInt();
|
||||
string message = p.ReadString();
|
||||
|
||||
// Trigger-Wort prüfen
|
||||
if (message.ToLower().Contains("heal")) {
|
||||
|
||||
// Prüfen ob wir die ID zum User kennen
|
||||
if (userMap.ContainsKey(index)) {
|
||||
int targetId = userMap[index];
|
||||
|
||||
// Ablauf starten (in eigenem Task, damit der Chat nicht laggt)
|
||||
_ = RunHealRoutine(targetId);
|
||||
} else {
|
||||
Log($"[Bot] User Index {index} unbekannt. Bitte Raum neu laden!");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 4. Die Logik zum Senden der Befehle
|
||||
async Task RunHealRoutine(int targetId) {
|
||||
if (isBusy) return;
|
||||
isBusy = true; // Sperren
|
||||
|
||||
Log($"[Bot] Heile User ID: {targetId}");
|
||||
|
||||
try {
|
||||
// Schritt 1: :offer senden
|
||||
Send(Out.Chat, ":offer", 0, -1);
|
||||
await Task.Delay(600); // Wartezeit für das Menü (anpassen bei Lag)
|
||||
|
||||
// Schritt 2: 1 senden
|
||||
Send(Out.Chat, "1", 0, -1);
|
||||
await Task.Delay(600); // Wartezeit für die Zielauswahl
|
||||
|
||||
// Schritt 3: User anklicken
|
||||
// Header "GetSelectedBadges" ist der Standard-Klick auf einen User
|
||||
Send(Out.GetSelectedBadges, targetId);
|
||||
|
||||
} catch (Exception ex) {
|
||||
Log($"Fehler: {ex.Message}");
|
||||
} finally {
|
||||
// Sperre aufheben
|
||||
await Task.Delay(200);
|
||||
isBusy = false;
|
||||
}
|
||||
}
|
||||
373
Heart.csx
Normal file
373
Heart.csx
Normal file
@ -0,0 +1,373 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
public struct Step
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
public int DelayMs;
|
||||
}
|
||||
|
||||
const int START_X = 13;
|
||||
const int START_Y = 13;
|
||||
|
||||
string rawDump = @"
|
||||
1. (13,14) after 971ms
|
||||
2. (13,15) after 499ms
|
||||
3. (13,14) after 61ms
|
||||
4. (13,16) after 438ms
|
||||
5. (13,15) after 40ms
|
||||
6. (13,17) after 499ms
|
||||
7. (13,16) after 47ms
|
||||
8. (13,18) after 452ms
|
||||
9. (13,17) after 89ms
|
||||
10. (13,19) after 410ms
|
||||
11. (13,18) after 40ms
|
||||
12. (13,20) after 467ms
|
||||
13. (13,19) after 75ms
|
||||
14. (13,21) after 424ms
|
||||
15. (13,20) after 40ms
|
||||
16. (12,21) after 489ms
|
||||
17. (13,21) after 61ms
|
||||
18. (12,20) after 438ms
|
||||
19. (12,21) after 104ms
|
||||
20. (12,19) after 395ms
|
||||
21. (12,20) after 47ms
|
||||
22. (12,18) after 452ms
|
||||
23. (12,19) after 91ms
|
||||
24. (12,17) after 407ms
|
||||
25. (12,18) after 40ms
|
||||
26. (12,16) after 474ms
|
||||
27. (12,17) after 71ms
|
||||
28. (12,15) after 424ms
|
||||
29. (12,16) after 40ms
|
||||
30. (12,14) after 486ms
|
||||
31. (12,15) after 60ms
|
||||
32. (11,14) after 438ms
|
||||
33. (12,14) after 108ms
|
||||
34. (11,15) after 391ms
|
||||
35. (11,14) after 46ms
|
||||
36. (11,15) after 544ms
|
||||
37. (11,16) after 408ms
|
||||
38. (11,17) after 499ms
|
||||
39. (11,16) after 80ms
|
||||
40. (11,18) after 419ms
|
||||
41. (11,17) after 40ms
|
||||
42. (11,19) after 484ms
|
||||
43. (11,18) after 58ms
|
||||
44. (11,20) after 442ms
|
||||
45. (11,19) after 110ms
|
||||
46. (11,21) after 389ms
|
||||
47. (11,20) after 47ms
|
||||
48. (10,21) after 452ms
|
||||
49. (11,21) after 91ms
|
||||
50. (10,20) after 409ms
|
||||
51. (10,21) after 40ms
|
||||
52. (10,19) after 473ms
|
||||
53. (10,20) after 79ms
|
||||
54. (10,18) after 420ms
|
||||
55. (10,19) after 40ms
|
||||
56. (10,17) after 485ms
|
||||
57. (10,18) after 62ms
|
||||
58. (10,16) after 437ms
|
||||
59. (10,17) after 109ms
|
||||
60. (10,15) after 390ms
|
||||
61. (10,16) after 44ms
|
||||
62. (10,14) after 455ms
|
||||
63. (10,15) after 93ms
|
||||
64. (9,14) after 406ms
|
||||
65. (10,14) after 40ms
|
||||
66. (9,15) after 476ms
|
||||
67. (9,14) after 68ms
|
||||
68. (9,16) after 424ms
|
||||
69. (9,15) after 40ms
|
||||
70. (9,17) after 485ms
|
||||
71. (9,16) after 62ms
|
||||
72. (9,18) after 437ms
|
||||
73. (9,17) after 94ms
|
||||
74. (9,19) after 405ms
|
||||
75. (9,18) after 40ms
|
||||
76. (9,20) after 481ms
|
||||
77. (9,19) after 62ms
|
||||
78. (9,21) after 437ms
|
||||
79. (9,20) after 40ms
|
||||
80. (8,20) after 500ms
|
||||
81. (9,21) after 44ms
|
||||
82. (8,20) after 546ms
|
||||
83. (8,21) after 1407ms
|
||||
84. (8,20) after 500ms
|
||||
85. (8,21) after 62ms
|
||||
86. (8,19) after 437ms
|
||||
87. (8,20) after 104ms
|
||||
88. (8,18) after 394ms
|
||||
89. (8,19) after 42ms
|
||||
90. (8,17) after 457ms
|
||||
91. (8,18) after 94ms
|
||||
92. (8,16) after 406ms
|
||||
93. (8,17) after 40ms
|
||||
94. (8,15) after 469ms
|
||||
95. (8,16) after 77ms
|
||||
96. (8,14) after 422ms
|
||||
97. (8,15) after 40ms
|
||||
98. (9,14) after 492ms
|
||||
99. (8,14) after 55ms
|
||||
100. (9,14) after 441ms
|
||||
101. (8,14) after 499ms
|
||||
102. (8,15) after 1000ms
|
||||
103. (8,16) after 499ms
|
||||
104. (8,15) after 75ms
|
||||
105. (8,17) after 424ms
|
||||
106. (8,16) after 40ms
|
||||
107. (8,18) after 484ms
|
||||
108. (8,17) after 61ms
|
||||
109. (8,19) after 438ms
|
||||
110. (8,18) after 106ms
|
||||
111. (8,19) after 440ms
|
||||
112. (9,20) after 453ms
|
||||
113. (8,21) after 500ms
|
||||
114. (9,20) after 40ms
|
||||
115. (8,21) after 548ms
|
||||
116. (9,21) after 423ms
|
||||
117. (10,21) after 499ms
|
||||
118. (9,21) after 62ms
|
||||
119. (10,20) after 438ms
|
||||
120. (10,21) after 104ms
|
||||
121. (9,19) after 394ms
|
||||
122. (10,20) after 47ms
|
||||
123. (9,18) after 452ms
|
||||
124. (9,19) after 89ms
|
||||
125. (9,17) after 410ms
|
||||
126. (9,18) after 40ms
|
||||
127. (9,16) after 472ms
|
||||
128. (9,17) after 80ms
|
||||
129. (9,15) after 419ms
|
||||
130. (9,16) after 40ms
|
||||
131. (10,14) after 487ms
|
||||
132. (9,15) after 59ms
|
||||
133. (10,14) after 546ms
|
||||
134. (10,15) after 393ms
|
||||
135. (10,16) after 499ms
|
||||
136. (10,15) after 91ms
|
||||
137. (10,17) after 408ms
|
||||
138. (10,16) after 40ms
|
||||
139. (10,18) after 467ms
|
||||
140. (10,17) after 75ms
|
||||
141. (10,19) after 423ms
|
||||
142. (10,18) after 40ms
|
||||
143. (11,20) after 486ms
|
||||
144. (10,19) after 61ms
|
||||
145. (11,21) after 437ms
|
||||
146. (11,20) after 109ms
|
||||
147. (11,21) after 436ms
|
||||
148. (12,21) after 954ms
|
||||
149. (13,21) after 499ms
|
||||
150. (12,21) after 75ms
|
||||
151. (12,20) after 424ms
|
||||
152. (13,21) after 40ms
|
||||
153. (13,20) after 485ms
|
||||
154. (12,20) after 64ms
|
||||
155. (13,19) after 436ms
|
||||
156. (13,20) after 40ms
|
||||
157. (12,19) after 497ms
|
||||
158. (13,19) after 44ms
|
||||
159. (11,19) after 455ms
|
||||
160. (12,19) after 94ms
|
||||
161. (11,18) after 405ms
|
||||
162. (11,19) after 40ms
|
||||
163. (12,18) after 471ms
|
||||
164. (11,18) after 74ms
|
||||
165. (13,18) after 425ms
|
||||
166. (12,18) after 40ms
|
||||
167. (13,17) after 484ms
|
||||
168. (13,18) after 64ms
|
||||
169. (13,17) after 543ms
|
||||
170. (12,17) after 391ms
|
||||
171. (11,17) after 499ms
|
||||
172. (12,17) after 90ms
|
||||
173. (11,17) after 443ms
|
||||
174. (11,16) after 466ms
|
||||
175. (11,15) after 499ms
|
||||
176. (11,16) after 40ms
|
||||
177. (11,14) after 485ms
|
||||
178. (11,15) after 63ms
|
||||
179. (12,14) after 436ms
|
||||
180. (11,14) after 106ms
|
||||
181. (12,14) after 438ms
|
||||
182. (12,15) after 455ms
|
||||
183. (12,16) after 499ms
|
||||
184. (12,15) after 40ms
|
||||
185. (13,16) after 487ms
|
||||
186. (12,16) after 63ms
|
||||
187. (13,15) after 436ms
|
||||
188. (13,16) after 107ms
|
||||
189. (13,14) after 393ms
|
||||
190. (13,15) after 45ms
|
||||
191. (13,14) after 545ms
|
||||
192. (12,14) after 409ms
|
||||
193. (11,14) after 500ms
|
||||
194. (12,14) after 74ms
|
||||
195. (12,15) after 424ms
|
||||
196. (11,14) after 40ms
|
||||
197. (13,16) after 488ms
|
||||
198. (12,15) after 64ms
|
||||
199. (13,15) after 435ms
|
||||
200. (13,16) after 106ms
|
||||
201. (13,14) after 394ms
|
||||
202. (13,15) after 46ms
|
||||
203. (13,14) after 548ms
|
||||
204. (12,14) after 403ms
|
||||
205. (11,14) after 499ms
|
||||
206. (12,14) after 74ms
|
||||
207. (11,14) after 437ms
|
||||
208. (12,15) after 1487ms
|
||||
209. (13,14) after 500ms
|
||||
210. (12,15) after 95ms
|
||||
211. (13,14) after 435ms
|
||||
212. (13,15) after 468ms
|
||||
213. (13,16) after 500ms
|
||||
214. (13,15) after 40ms
|
||||
215. (13,17) after 487ms
|
||||
216. (13,16) after 60ms
|
||||
217. (13,18) after 440ms
|
||||
218. (13,17) after 105ms
|
||||
219. (13,19) after 392ms
|
||||
220. (13,18) after 48ms
|
||||
221. (13,20) after 451ms
|
||||
222. (13,19) after 92ms
|
||||
223. (13,21) after 407ms
|
||||
224. (13,20) after 40ms
|
||||
225. (12,21) after 471ms
|
||||
226. (13,21) after 80ms
|
||||
227. (11,21) after 420ms
|
||||
228. (12,21) after 40ms
|
||||
229. (12,20) after 487ms
|
||||
230. (11,21) after 61ms
|
||||
231. (13,19) after 439ms
|
||||
232. (12,20) after 108ms
|
||||
233. (13,20) after 390ms
|
||||
234. (13,19) after 47ms
|
||||
235. (13,21) after 452ms
|
||||
236. (13,20) after 95ms
|
||||
237. (13,21) after 438ms
|
||||
238. (12,21) after 2966ms
|
||||
239. (12,20) after 999ms
|
||||
240. (11,21) after 500ms
|
||||
241. (12,20) after 40ms
|
||||
242. (11,21) after 553ms
|
||||
243. (10,21) after 934ms
|
||||
244. (9,21) after 500ms
|
||||
245. (10,21) after 92ms
|
||||
246. (8,21) after 407ms
|
||||
247. (9,21) after 40ms
|
||||
248. (8,21) after 545ms
|
||||
249. (9,20) after 1924ms
|
||||
250. (8,21) after 999ms
|
||||
251. (8,20) after 999ms
|
||||
252. (8,19) after 500ms
|
||||
253. (8,20) after 64ms
|
||||
254. (8,18) after 435ms
|
||||
255. (8,19) after 109ms
|
||||
256. (8,18) after 435ms
|
||||
257. (8,17) after 454ms
|
||||
258. (8,18) after 999ms
|
||||
259. (8,17) after 1000ms
|
||||
260. (8,16) after 1999ms
|
||||
261. (8,15) after 500ms
|
||||
262. (8,16) after 78ms
|
||||
263. (8,14) after 421ms
|
||||
264. (8,15) after 40ms
|
||||
265. (9,15) after 486ms
|
||||
266. (8,14) after 63ms
|
||||
267. (9,15) after 546ms
|
||||
268. (8,14) after 891ms
|
||||
269. (9,14) after 997ms
|
||||
270. (10,14) after 500ms
|
||||
271. (9,14) after 40ms
|
||||
272. (10,15) after 484ms
|
||||
273. (10,14) after 58ms
|
||||
274. (11,15) after 440ms
|
||||
275. (10,15) after 111ms
|
||||
276. (12,16) after 387ms
|
||||
277. (11,15) after 49ms
|
||||
278. (12,16) after 546ms
|
||||
279. (11,16) after 404ms
|
||||
280. (10,16) after 500ms
|
||||
281. (11,16) after 75ms
|
||||
282. (9,16) after 424ms
|
||||
283. (10,16) after 40ms
|
||||
284. (9,17) after 483ms
|
||||
285. (9,16) after 60ms
|
||||
286. (10,17) after 439ms
|
||||
287. (9,17) after 40ms
|
||||
288. (11,17) after 498ms
|
||||
289. (10,17) after 45ms
|
||||
290. (12,17) after 453ms
|
||||
291. (11,17) after 96ms
|
||||
292. (12,18) after 403ms
|
||||
293. (12,17) after 40ms
|
||||
294. (11,18) after 467ms
|
||||
295. (12,18) after 82ms
|
||||
296. (10,18) after 417ms
|
||||
297. (11,18) after 40ms
|
||||
298. (9,18) after 482ms
|
||||
299. (10,18) after 65ms
|
||||
300. (9,19) after 435ms
|
||||
301. (9,18) after 107ms
|
||||
302. (10,19) after 392ms
|
||||
303. (9,19) after 47ms
|
||||
304. (11,19) after 452ms
|
||||
305. (10,19) after 89ms
|
||||
306. (12,19) after 409ms
|
||||
307. (11,19) after 40ms
|
||||
308. (11,20) after 471ms
|
||||
309. (12,19) after 81ms
|
||||
310. (10,20) after 418ms
|
||||
311. (11,20) after 40ms
|
||||
312. (10,20) after 545ms
|
||||
";
|
||||
|
||||
List<Step> ParseSteps(string text)
|
||||
{
|
||||
var steps = new List<Step>();
|
||||
var rx = new Regex(@"\((\d+),(\d+)\)\s+after\s+(\d+)ms", RegexOptions.Compiled);
|
||||
foreach (Match m in rx.Matches(text))
|
||||
{
|
||||
steps.Add(new Step {
|
||||
X = int.Parse(m.Groups[1].Value),
|
||||
Y = int.Parse(m.Groups[2].Value),
|
||||
DelayMs = int.Parse(m.Groups[3].Value)
|
||||
});
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
|
||||
Log("=== Play Path From Dump ===");
|
||||
var steps = ParseSteps(rawDump);
|
||||
if (steps.Count == 0)
|
||||
{
|
||||
Log("ERROR: No steps parsed.");
|
||||
return;
|
||||
}
|
||||
|
||||
Log($"Parsed {steps.Count} steps.");
|
||||
Log($"Waiting until you are on {START_X}:{START_Y}...");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
if (Self != null && Self.Location != null && Self.Location.X == START_X && Self.Location.Y == START_Y)
|
||||
break;
|
||||
Delay(100);
|
||||
}
|
||||
|
||||
if (!Run) return;
|
||||
|
||||
Log("Start reached. Replaying exact timed path...");
|
||||
for (int i = 0; i < steps.Count; i++)
|
||||
{
|
||||
var s = steps[i];
|
||||
if (s.DelayMs > 0) Delay(s.DelayMs);
|
||||
Move(s.X, s.Y);
|
||||
}
|
||||
|
||||
Log("Replay complete.");
|
||||
512
One-Way Door Auto Enter.csx
Normal file
512
One-Way Door Auto Enter.csx
Normal file
@ -0,0 +1,512 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
// Color Puzzle Solver v2
|
||||
// - Auto calibration of arrow -> move mapping
|
||||
// - Waits for real state change after every click
|
||||
|
||||
const int TILE_KIND = 3696;
|
||||
const int ARROW_KIND = 17851;
|
||||
const int GRID_X_MIN = 36;
|
||||
const int GRID_X_MAX = 39;
|
||||
const int GRID_Y_MIN = 27;
|
||||
const int GRID_Y_MAX = 30;
|
||||
|
||||
const int CLICK_SETTLE_DELAY_MS = 250;
|
||||
const int WAIT_CHANGE_TIMEOUT_MS = 6000;
|
||||
const int WAIT_CHANGE_POLL_MS = 120;
|
||||
const int MAX_STEPS = 140;
|
||||
const int BFS_MAX_NODES = 4_000_000;
|
||||
const int IDA_MAX_SEC = 12;
|
||||
|
||||
const bool AUTO_QUEUE_START = true;
|
||||
const long TRANSPORTER_ID = 759030883;
|
||||
const int QUEUE_CLICK_INTERVAL_MS = 5000;
|
||||
const int WAIT_PUZZLE_POLL_MS = 250;
|
||||
const int WAIT_PUZZLE_LOG_MS = 5000;
|
||||
const bool REQUIRE_SELF_IN_PLAYZONE = true;
|
||||
const int PLAY_X_MIN = 34;
|
||||
const int PLAY_X_MAX = 41;
|
||||
const int PLAY_Y_MIN = 26;
|
||||
const int PLAY_Y_MAX = 31;
|
||||
const bool REQUIRE_SELF_MIN_Z = true;
|
||||
const double SELF_MIN_Z = 17.0;
|
||||
const bool REQUIRE_PLAYER_QUEUE_CLEAR = true;
|
||||
const string WATCH_PLAYER_NAME = "gracie";
|
||||
const int WATCH_QUEUE_X = 33;
|
||||
const int WATCH_QUEUE_Y = 19;
|
||||
const double WATCH_QUEUE_Z = 4.5;
|
||||
const double WATCH_QUEUE_Z_TOL = 1.0;
|
||||
|
||||
int GetState(dynamic item)
|
||||
{
|
||||
try { return int.Parse(item.State?.ToString() ?? "0"); }
|
||||
catch { return 0; }
|
||||
}
|
||||
|
||||
int GetKind(dynamic item)
|
||||
{
|
||||
try { return (int)item.Kind; }
|
||||
catch { return -1; }
|
||||
}
|
||||
|
||||
uint EncodeGrid(int[,] g)
|
||||
{
|
||||
uint s = 0;
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
s |= ((uint)(g[r, c] & 3)) << (2 * (r * 4 + c));
|
||||
return s;
|
||||
}
|
||||
|
||||
bool TryReadGrid(out uint state, out string dump)
|
||||
{
|
||||
int[,] grid = new int[4, 4];
|
||||
bool[,] found = new bool[4, 4];
|
||||
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != TILE_KIND) continue;
|
||||
|
||||
int x = item.Location.X;
|
||||
int y = item.Location.Y;
|
||||
double z = item.Location.Z;
|
||||
|
||||
if (x < GRID_X_MIN || x > GRID_X_MAX) continue;
|
||||
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
|
||||
if (z < 18.4) continue;
|
||||
|
||||
int row = y - GRID_Y_MIN;
|
||||
int col = x - GRID_X_MIN;
|
||||
grid[row, col] = GetState(item);
|
||||
found[row, col] = true;
|
||||
}
|
||||
|
||||
int cnt = 0;
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
if (found[r, c]) cnt++;
|
||||
|
||||
if (cnt < 16)
|
||||
{
|
||||
state = 0;
|
||||
dump = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
state = EncodeGrid(grid);
|
||||
dump = string.Join(" | ", Enumerable.Range(0, 4).Select(r =>
|
||||
$"R{r}[{grid[r,0]},{grid[r,1]},{grid[r,2]},{grid[r,3]}]"));
|
||||
return true;
|
||||
}
|
||||
|
||||
uint RowLeft(uint s, int r)
|
||||
{
|
||||
int sh = r * 8;
|
||||
uint row = (s >> sh) & 0xFFu;
|
||||
uint rot = ((row >> 2) | (row << 6)) & 0xFFu;
|
||||
return (s & ~(0xFFu << sh)) | (rot << sh);
|
||||
}
|
||||
|
||||
uint RowRight(uint s, int r)
|
||||
{
|
||||
int sh = r * 8;
|
||||
uint row = (s >> sh) & 0xFFu;
|
||||
uint rot = ((row << 2) | (row >> 6)) & 0xFFu;
|
||||
return (s & ~(0xFFu << sh)) | (rot << sh);
|
||||
}
|
||||
|
||||
uint ColUp(uint s, int c)
|
||||
{
|
||||
int b = c * 2;
|
||||
uint v0 = (s >> b) & 3u;
|
||||
uint v1 = (s >> (b + 8)) & 3u;
|
||||
uint v2 = (s >> (b + 16)) & 3u;
|
||||
uint v3 = (s >> (b + 24)) & 3u;
|
||||
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
|
||||
return (s & mask) | (v1 << b) | (v2 << (b + 8)) | (v3 << (b + 16)) | (v0 << (b + 24));
|
||||
}
|
||||
|
||||
uint ColDown(uint s, int c)
|
||||
{
|
||||
int b = c * 2;
|
||||
uint v0 = (s >> b) & 3u;
|
||||
uint v1 = (s >> (b + 8)) & 3u;
|
||||
uint v2 = (s >> (b + 16)) & 3u;
|
||||
uint v3 = (s >> (b + 24)) & 3u;
|
||||
uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24));
|
||||
return (s & mask) | (v3 << b) | (v0 << (b + 8)) | (v1 << (b + 16)) | (v2 << (b + 24));
|
||||
}
|
||||
|
||||
uint ApplyMove(uint s, int m)
|
||||
{
|
||||
if (m < 4) return RowLeft(s, m);
|
||||
if (m < 8) return RowRight(s, m - 4);
|
||||
if (m < 12) return ColUp(s, m - 8);
|
||||
return ColDown(s, m - 12);
|
||||
}
|
||||
|
||||
int InverseMove(int m)
|
||||
{
|
||||
if (m < 4) return m + 4;
|
||||
if (m < 8) return m - 4;
|
||||
if (m < 12) return m + 4;
|
||||
return m - 4;
|
||||
}
|
||||
|
||||
string MoveName(int m)
|
||||
{
|
||||
if (m < 4) return $"Row{m} LEFT";
|
||||
if (m < 8) return $"Row{m - 4} RIGHT";
|
||||
if (m < 12) return $"Col{m - 8} UP";
|
||||
return $"Col{m - 12} DOWN";
|
||||
}
|
||||
|
||||
int DetectMove(uint before, uint after)
|
||||
{
|
||||
int hit = -1;
|
||||
for (int m = 0; m < 16; m++)
|
||||
{
|
||||
if (ApplyMove(before, m) != after) continue;
|
||||
if (hit != -1) return -2;
|
||||
hit = m;
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
List<int> SolveBfs(uint start, uint goal)
|
||||
{
|
||||
if (start == goal) return new List<int>();
|
||||
|
||||
var visited = new Dictionary<uint, (uint parent, int move)>();
|
||||
var queue = new Queue<uint>();
|
||||
visited[start] = (start, -1);
|
||||
queue.Enqueue(start);
|
||||
int nodes = 0;
|
||||
bool found = false;
|
||||
|
||||
while (queue.Count > 0 && nodes < BFS_MAX_NODES)
|
||||
{
|
||||
uint cur = queue.Dequeue();
|
||||
nodes++;
|
||||
|
||||
for (int m = 0; m < 16; m++)
|
||||
{
|
||||
uint nxt = ApplyMove(cur, m);
|
||||
if (visited.ContainsKey(nxt)) continue;
|
||||
visited[nxt] = (cur, m);
|
||||
if (nxt == goal)
|
||||
{
|
||||
found = true;
|
||||
queue.Clear();
|
||||
break;
|
||||
}
|
||||
queue.Enqueue(nxt);
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) return null;
|
||||
|
||||
var sol = new List<int>();
|
||||
uint s = goal;
|
||||
while (s != start)
|
||||
{
|
||||
var p = visited[s];
|
||||
sol.Add(p.move);
|
||||
s = p.parent;
|
||||
}
|
||||
sol.Reverse();
|
||||
return sol;
|
||||
}
|
||||
|
||||
List<int> SolveIda(uint start, uint goal)
|
||||
{
|
||||
if (start == goal) return new List<int>();
|
||||
var t0 = DateTime.Now;
|
||||
|
||||
int H(uint st)
|
||||
{
|
||||
int mis = 0;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
int a = (int)((st >> (i * 2)) & 3u);
|
||||
int b = (int)((goal >> (i * 2)) & 3u);
|
||||
if (a != b) mis++;
|
||||
}
|
||||
return (mis + 3) / 4;
|
||||
}
|
||||
|
||||
List<int> best = null;
|
||||
bool timeout = false;
|
||||
|
||||
bool Dfs(uint st, List<int> path, int maxDepth)
|
||||
{
|
||||
if (timeout) return false;
|
||||
if ((DateTime.Now - t0).TotalSeconds > IDA_MAX_SEC)
|
||||
{
|
||||
timeout = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (st == goal)
|
||||
{
|
||||
best = new List<int>(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
int h = H(st);
|
||||
if (path.Count + h > maxDepth) return false;
|
||||
|
||||
int block = path.Count > 0 ? InverseMove(path[path.Count - 1]) : -1;
|
||||
for (int m = 0; m < 16; m++)
|
||||
{
|
||||
if (m == block) continue;
|
||||
path.Add(m);
|
||||
if (Dfs(ApplyMove(st, m), path, maxDepth)) return true;
|
||||
path.RemoveAt(path.Count - 1);
|
||||
if (timeout) return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int d0 = H(start);
|
||||
for (int d = d0; d <= 22 && !timeout; d++)
|
||||
{
|
||||
if (Dfs(start, new List<int>(), d)) break;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
List<int> Solve(uint start, uint goal)
|
||||
{
|
||||
var bfs = SolveBfs(start, goal);
|
||||
if (bfs != null) return bfs;
|
||||
return SolveIda(start, goal);
|
||||
}
|
||||
|
||||
bool ClickAndWaitChange(long furniId, uint before, out uint after, out string dumpAfter)
|
||||
{
|
||||
Send(Out["ClickFurni"], (int)furniId, 0);
|
||||
Delay(CLICK_SETTLE_DELAY_MS);
|
||||
|
||||
int waited = 0;
|
||||
while (waited < WAIT_CHANGE_TIMEOUT_MS)
|
||||
{
|
||||
if (TryReadGrid(out after, out dumpAfter) && after != before)
|
||||
return true;
|
||||
Delay(WAIT_CHANGE_POLL_MS);
|
||||
waited += WAIT_CHANGE_POLL_MS;
|
||||
}
|
||||
|
||||
after = before;
|
||||
dumpAfter = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
Dictionary<string, long> ReadArrowIds()
|
||||
{
|
||||
var arrowIds = new Dictionary<string, long>();
|
||||
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != ARROW_KIND) continue;
|
||||
int x = item.Location.X;
|
||||
int y = item.Location.Y;
|
||||
|
||||
if (y == GRID_Y_MIN - 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
|
||||
arrowIds[$"up_{x - GRID_X_MIN}"] = item.Id;
|
||||
else if (y == GRID_Y_MAX + 1 && x >= GRID_X_MIN && x <= GRID_X_MAX)
|
||||
arrowIds[$"down_{x - GRID_X_MIN}"] = item.Id;
|
||||
else if (x == GRID_X_MIN - 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
|
||||
arrowIds[$"left_{y - GRID_Y_MIN}"] = item.Id;
|
||||
else if (x == GRID_X_MAX + 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX)
|
||||
arrowIds[$"right_{y - GRID_Y_MIN}"] = item.Id;
|
||||
}
|
||||
|
||||
return arrowIds;
|
||||
}
|
||||
|
||||
bool IsSelfInPlayZone()
|
||||
{
|
||||
try
|
||||
{
|
||||
int x = Self.Location.X;
|
||||
int y = Self.Location.Y;
|
||||
double z = Self.Location.Z;
|
||||
bool inRect = x >= PLAY_X_MIN && x <= PLAY_X_MAX && y >= PLAY_Y_MIN && y <= PLAY_Y_MAX;
|
||||
bool inZ = !REQUIRE_SELF_MIN_Z || z >= SELF_MIN_Z;
|
||||
return inRect && inZ;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsWatchedPlayerAtQueueSpot()
|
||||
{
|
||||
try
|
||||
{
|
||||
var u = Users.FirstOrDefault(x =>
|
||||
x != null &&
|
||||
x.Name != null &&
|
||||
x.Name.Equals(WATCH_PLAYER_NAME, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (u == null || u.Location == null) return false;
|
||||
|
||||
int x = u.Location.X;
|
||||
int y = u.Location.Y;
|
||||
double z = u.Location.Z;
|
||||
|
||||
return x == WATCH_QUEUE_X && y == WATCH_QUEUE_Y && Math.Abs(z - WATCH_QUEUE_Z) <= WATCH_QUEUE_Z_TOL;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Log("=== Color Puzzle Auto-Solver (AutoCalib + WaitChange) ===");
|
||||
|
||||
Dictionary<string, long> arrowIds = null;
|
||||
uint current;
|
||||
string dumpNow;
|
||||
|
||||
int sinceQueueClick = QUEUE_CLICK_INTERVAL_MS;
|
||||
int sinceLog = WAIT_PUZZLE_LOG_MS;
|
||||
|
||||
while (true)
|
||||
{
|
||||
bool hasGrid = TryReadGrid(out current, out dumpNow);
|
||||
var probeArrows = ReadArrowIds();
|
||||
bool hasArrows = probeArrows.Count == 16;
|
||||
bool inPlayZone = !REQUIRE_SELF_IN_PLAYZONE || IsSelfInPlayZone();
|
||||
bool queueClear = !REQUIRE_PLAYER_QUEUE_CLEAR || !IsWatchedPlayerAtQueueSpot();
|
||||
|
||||
if (hasGrid && hasArrows && inPlayZone && queueClear)
|
||||
{
|
||||
arrowIds = probeArrows;
|
||||
break;
|
||||
}
|
||||
|
||||
if (AUTO_QUEUE_START && sinceQueueClick >= QUEUE_CLICK_INTERVAL_MS)
|
||||
{
|
||||
Send(Out["ClickFurni"], (int)TRANSPORTER_ID, 0);
|
||||
Log($"Queue: Klick Transporter {TRANSPORTER_ID}...");
|
||||
sinceQueueClick = 0;
|
||||
}
|
||||
|
||||
if (sinceLog >= WAIT_PUZZLE_LOG_MS)
|
||||
{
|
||||
string selfPos = "?";
|
||||
try { selfPos = $"{Self.Location.X},{Self.Location.Y},{Self.Location.Z:F2}"; } catch { }
|
||||
Log($"Warte auf Spielstart... Grid={(hasGrid ? "ok" : "no")}, Pfeile={probeArrows.Count}/16, InZone={(inPlayZone ? "yes" : "no")}, QueueClear={(queueClear ? "yes" : "no")}, Self={selfPos}");
|
||||
sinceLog = 0;
|
||||
}
|
||||
|
||||
Delay(WAIT_PUZZLE_POLL_MS);
|
||||
sinceQueueClick += WAIT_PUZZLE_POLL_MS;
|
||||
sinceLog += WAIT_PUZZLE_POLL_MS;
|
||||
}
|
||||
|
||||
Log("Puzzle erkannt. Starte Solver...");
|
||||
Log($"Pfeile: {arrowIds.Count}/16");
|
||||
|
||||
int[] targetRows = new int[4];
|
||||
bool targetFound = false;
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetKind(item) != TILE_KIND) continue;
|
||||
if (item.Location.X != 41) continue;
|
||||
int y = item.Location.Y;
|
||||
if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue;
|
||||
|
||||
targetRows[y - GRID_Y_MIN] = GetState(item);
|
||||
targetFound = true;
|
||||
}
|
||||
if (!targetFound) targetRows = new[] { 1, 2, 3, 0 };
|
||||
|
||||
int[,] tgt = new int[4, 4];
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
tgt[r, c] = targetRows[r];
|
||||
|
||||
uint goal = EncodeGrid(tgt);
|
||||
Log($"Ziel: R0={targetRows[0]}, R1={targetRows[1]}, R2={targetRows[2]}, R3={targetRows[3]}");
|
||||
|
||||
Log($"Start: {dumpNow}");
|
||||
|
||||
var moveToKey = new Dictionary<int, string>();
|
||||
var keyToMove = new Dictionary<string, int>();
|
||||
var allKeys = arrowIds.Keys.OrderBy(k => k).ToList();
|
||||
|
||||
for (int step = 1; step <= MAX_STEPS; step++)
|
||||
{
|
||||
if (current == goal)
|
||||
{
|
||||
Log("=== Geloest: alle 4 Reihen korrekt ===");
|
||||
return;
|
||||
}
|
||||
|
||||
var plan = Solve(current, goal);
|
||||
if (plan == null || plan.Count == 0)
|
||||
{
|
||||
Log("ERROR: Kein Plan vom aktuellen Zustand.");
|
||||
return;
|
||||
}
|
||||
|
||||
int wanted = plan[0];
|
||||
string key;
|
||||
bool probing = false;
|
||||
|
||||
if (moveToKey.ContainsKey(wanted))
|
||||
{
|
||||
key = moveToKey[wanted];
|
||||
}
|
||||
else
|
||||
{
|
||||
key = allKeys.FirstOrDefault(k => !keyToMove.ContainsKey(k));
|
||||
if (key == null)
|
||||
{
|
||||
key = allKeys[0];
|
||||
}
|
||||
probing = true;
|
||||
}
|
||||
|
||||
long id = arrowIds[key];
|
||||
Log($"[{step}] want {MoveName(wanted)} | click {key}" + (probing ? " (probe)" : ""));
|
||||
|
||||
if (!ClickAndWaitChange(id, current, out uint after, out string dumpAfter))
|
||||
{
|
||||
Log(" Kein Move erkannt (Timeout), gleicher Schritt nochmal.");
|
||||
continue;
|
||||
}
|
||||
|
||||
int actual = DetectMove(current, after);
|
||||
if (actual >= 0)
|
||||
{
|
||||
moveToKey[actual] = key;
|
||||
keyToMove[key] = actual;
|
||||
if (actual != wanted)
|
||||
Log($" AutoCalib: {key} == {MoveName(actual)} (nicht {MoveName(wanted)})");
|
||||
}
|
||||
else if (actual == -1)
|
||||
{
|
||||
Log($" Unbekannter Transition-Delta, weiter mit Re-Plan. State: {dumpAfter}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($" Mehrdeutiger Delta, weiter mit Re-Plan. State: {dumpAfter}");
|
||||
}
|
||||
|
||||
current = after;
|
||||
|
||||
if (step % 10 == 0)
|
||||
Log($" Calib: {moveToKey.Count}/16 Moves gemappt");
|
||||
}
|
||||
|
||||
Log("Nicht fertig in MAX_STEPS. Script einfach nochmal starten.");
|
||||
33
Pet Level Scanner.csx
Normal file
33
Pet Level Scanner.csx
Normal file
@ -0,0 +1,33 @@
|
||||
string suchName = "Goki";
|
||||
int tempLevel = 0;
|
||||
bool gotPetInfo = false;
|
||||
|
||||
OnIntercept(In["PetInfo"], e => {
|
||||
e.Packet.ReadInt();
|
||||
e.Packet.ReadString();
|
||||
tempLevel = e.Packet.ReadInt();
|
||||
gotPetInfo = true;
|
||||
});
|
||||
|
||||
Log("Suche: " + suchName);
|
||||
|
||||
int gefunden = 0;
|
||||
|
||||
foreach (var entity in Entities) {
|
||||
if (entity.Name == suchName) {
|
||||
gefunden++;
|
||||
|
||||
gotPetInfo = false;
|
||||
Send(Out["GetPetInfo"], entity.Id);
|
||||
|
||||
int waited = 0;
|
||||
while (!gotPetInfo && waited < 20 && Run) {
|
||||
Delay(100);
|
||||
waited++;
|
||||
}
|
||||
|
||||
Log("[" + gefunden + "] Level: " + tempLevel);
|
||||
}
|
||||
}
|
||||
|
||||
Log("Ende");
|
||||
2177
Pet Trainer Auto v84.csx
Normal file
2177
Pet Trainer Auto v84.csx
Normal file
File diff suppressed because it is too large
Load Diff
12
Random Walker Bot.csx
Normal file
12
Random Walker Bot.csx
Normal file
@ -0,0 +1,12 @@
|
||||
Random rnd = new Random();
|
||||
|
||||
while(Run)
|
||||
{
|
||||
if(Self?.Location == null) { Delay(100); continue; }
|
||||
|
||||
int x = Self.Location.X + rnd.Next(-1, 2);
|
||||
int y = Self.Location.Y + rnd.Next(-1, 2);
|
||||
|
||||
Move(x, y);
|
||||
Delay(100);
|
||||
}
|
||||
122
Roller Gate Bot.csx
Normal file
122
Roller Gate Bot.csx
Normal file
@ -0,0 +1,122 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
// ════════════════════════════════════════════════════
|
||||
// FIXED ROLLER BOT (Ohne Schnickschnack)
|
||||
// ════════════════════════════════════════════════════
|
||||
|
||||
// IDs
|
||||
int pillowId = 893412986;
|
||||
int gateId = 2147418143;
|
||||
List<int> rollers = new List<int> {
|
||||
2147418115, 2147418119, 2147418133,
|
||||
2147418134, 2147418135, 2147418136
|
||||
};
|
||||
|
||||
// Einstellungen
|
||||
int delay = 130; // Wartezeit auf dem Roller (ms)
|
||||
|
||||
// Speicher für Koordinaten: Key=ItemID, Value=[x, y]
|
||||
Dictionary<int, int[]> coords = new Dictionary<int, int[]>();
|
||||
bool active = false;
|
||||
|
||||
Log("Bot gestartet. BITTE RAUM NEU LADEN!");
|
||||
|
||||
// 1. Alles scannen beim Raum betreten
|
||||
OnIntercept(In.RoomReady, e => { coords.Clear(); active = false; });
|
||||
OnIntercept(In.Users, e => { coords.Clear(); active = false; });
|
||||
|
||||
OnIntercept(In.FloorItems, e => {
|
||||
var p = e.Packet;
|
||||
int n = p.ReadInt();
|
||||
|
||||
for(int i=0; i<n; i++) {
|
||||
int id = p.ReadInt();
|
||||
int type = p.ReadInt();
|
||||
int x = p.ReadInt();
|
||||
int y = p.ReadInt();
|
||||
p.ReadInt(); // rot
|
||||
p.ReadString(); // z
|
||||
p.ReadString(); p.ReadInt(); p.ReadInt(); p.ReadString(); // skip rest
|
||||
|
||||
coords[id] = new int[] { x, y };
|
||||
}
|
||||
|
||||
if (coords.ContainsKey(pillowId)) {
|
||||
active = true;
|
||||
Log("Kissen gefunden. Bot bereit.");
|
||||
}
|
||||
});
|
||||
|
||||
// 2. Bewegung erkennen
|
||||
OnIntercept(In.SlideObjectBundle, e => {
|
||||
if (!active) return;
|
||||
|
||||
var p = e.Packet;
|
||||
int oldX = p.ReadInt();
|
||||
int oldY = p.ReadInt();
|
||||
int newX = p.ReadInt(); // Ziel X
|
||||
int newY = p.ReadInt(); // Ziel Y
|
||||
int count = p.ReadInt();
|
||||
|
||||
bool pillowMoved = false;
|
||||
|
||||
for(int i=0; i<count; i++) {
|
||||
int id = p.ReadInt();
|
||||
p.ReadString(); // z1
|
||||
p.ReadString(); // z2
|
||||
|
||||
// Position updaten
|
||||
if (coords.ContainsKey(id)) {
|
||||
coords[id][0] = newX;
|
||||
coords[id][1] = newY;
|
||||
}
|
||||
|
||||
if (id == pillowId) pillowMoved = true;
|
||||
}
|
||||
|
||||
if (pillowMoved) {
|
||||
// Logik in separaten Task auslagern damit Packet nicht blockiert
|
||||
_ = RunLogic(newX, newY);
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Die Logik
|
||||
async Task RunLogic(int px, int py) {
|
||||
// 4 Richtungen prüfen: Oben, Unten, Links, Rechts
|
||||
int[][] checks = new int[][] {
|
||||
new int[]{0, 1}, new int[]{0, -1}, new int[]{1, 0}, new int[]{-1, 0}
|
||||
};
|
||||
|
||||
foreach(var offset in checks) {
|
||||
int cx = px + offset[0];
|
||||
int cy = py + offset[1];
|
||||
|
||||
// Prüfen ob an cx/cy ein erlaubter Roller oder das Gate liegt
|
||||
int foundId = -1;
|
||||
|
||||
foreach(var kvp in coords) {
|
||||
if (kvp.Value[0] == cx && kvp.Value[1] == cy) {
|
||||
if (rollers.Contains(kvp.Key) || kvp.Key == gateId) {
|
||||
foundId = kvp.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundId != -1) {
|
||||
if (foundId == gateId) {
|
||||
Log("Gate gefunden! Benutze es.");
|
||||
Send(Out.UseFurniture, gateId, 0);
|
||||
return;
|
||||
} else {
|
||||
// Es ist ein Roller -> Hin und Zurück
|
||||
Send(Out.MoveAvatar, cx, cy);
|
||||
await Task.Delay(delay);
|
||||
Send(Out.MoveAvatar, px, py);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
172
Roller Surf Game Bot.csx
Normal file
172
Roller Surf Game Bot.csx
Normal file
@ -0,0 +1,172 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// ROLLER SURF BOT
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
const int PILLOW_ID = 893412986;
|
||||
const int GATE_ID = 2147418143;
|
||||
const int MARBLE_TILE_ID = 2147418142;
|
||||
const int TICK_DELAY = 50;
|
||||
const int MOVE_COOLDOWN = 150;
|
||||
|
||||
HashSet<int> greenRollers = new HashSet<int> {
|
||||
2147418115, 2147418119, 2147418133,
|
||||
2147418134, 2147418135, 2147418136
|
||||
};
|
||||
|
||||
bool gameStarted = false;
|
||||
DateTime lastMoveCmd = DateTime.MinValue;
|
||||
|
||||
int GetId(dynamic item) { try { return (int)item.Id; } catch { return 0; } }
|
||||
|
||||
(int x, int y) GetPillowPos()
|
||||
{
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetId(item) == PILLOW_ID)
|
||||
return (item.Location.X, item.Location.Y);
|
||||
}
|
||||
return (-1, -1);
|
||||
}
|
||||
|
||||
(int x, int y) GetMarbleTilePos()
|
||||
{
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (GetId(item) == MARBLE_TILE_ID)
|
||||
return (item.Location.X, item.Location.Y);
|
||||
}
|
||||
return (-1, -1);
|
||||
}
|
||||
|
||||
(int x, int y) GetMyPos()
|
||||
{
|
||||
try { return (Self.Location.X, Self.Location.Y); }
|
||||
catch { return (-1, -1); }
|
||||
}
|
||||
|
||||
bool IsAdjacent(int x1, int y1, int x2, int y2)
|
||||
{
|
||||
int dx = Math.Abs(x1 - x2);
|
||||
int dy = Math.Abs(y1 - y2);
|
||||
return (dx == 1 && dy == 0) || (dx == 0 && dy == 1);
|
||||
}
|
||||
|
||||
int GetTargetIdAt(int x, int y)
|
||||
{
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (item.Location.X != x || item.Location.Y != y) continue;
|
||||
|
||||
int id = GetId(item);
|
||||
if (id == GATE_ID || greenRollers.Contains(id))
|
||||
{
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
(int x, int y, int id) FindAdjacentTarget(int px, int py)
|
||||
{
|
||||
int[] dX = { 1, -1, 0, 0 };
|
||||
int[] dY = { 0, 0, 1, -1 };
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int checkX = px + dX[i];
|
||||
int checkY = py + dY[i];
|
||||
|
||||
int targetId = GetTargetIdAt(checkX, checkY);
|
||||
if (targetId != -1)
|
||||
{
|
||||
return (checkX, checkY, targetId);
|
||||
}
|
||||
}
|
||||
return (-1, -1, -1);
|
||||
}
|
||||
|
||||
Log("═══════════════════════════════════════");
|
||||
Log(" ROLLER SURF BOT");
|
||||
Log("═══════════════════════════════════════");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(TICK_DELAY);
|
||||
|
||||
var pillow = GetPillowPos();
|
||||
var myPos = GetMyPos();
|
||||
var marbleTile = GetMarbleTilePos();
|
||||
|
||||
if (pillow.x == -1 || myPos.x == -1) continue;
|
||||
|
||||
bool onPillow = (myPos.x == pillow.x && myPos.y == pillow.y);
|
||||
bool onMarbleTile = (marbleTile.x != -1 && myPos.x == marbleTile.x && myPos.y == marbleTile.y);
|
||||
|
||||
if (!gameStarted)
|
||||
{
|
||||
if (onMarbleTile)
|
||||
{
|
||||
Log("Start!");
|
||||
Send(Out["MoveAvatar"], pillow.x, pillow.y);
|
||||
gameStarted = true;
|
||||
lastMoveCmd = DateTime.Now;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (onPillow)
|
||||
{
|
||||
gameStarted = true;
|
||||
Log("GO!");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsAdjacent(myPos.x, myPos.y, pillow.x, pillow.y))
|
||||
{
|
||||
Send(Out["MoveAvatar"], pillow.x, pillow.y);
|
||||
gameStarted = true;
|
||||
lastMoveCmd = DateTime.Now;
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!onPillow)
|
||||
{
|
||||
if ((DateTime.Now - lastMoveCmd).TotalMilliseconds > MOVE_COOLDOWN)
|
||||
{
|
||||
Send(Out["MoveAvatar"], pillow.x, pillow.y);
|
||||
lastMoveCmd = DateTime.Now;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var target = FindAdjacentTarget(pillow.x, pillow.y);
|
||||
|
||||
if (target.id == -1) continue;
|
||||
|
||||
if (target.id == GATE_ID)
|
||||
{
|
||||
Log("GATE!");
|
||||
Send(Out["UseFurniture"], GATE_ID, 0);
|
||||
gameStarted = false;
|
||||
Delay(300);
|
||||
continue;
|
||||
}
|
||||
|
||||
Send(Out["MoveAvatar"], target.x, target.y);
|
||||
Delay(TICK_DELAY);
|
||||
|
||||
var newPillow = GetPillowPos();
|
||||
if (newPillow.x != -1)
|
||||
{
|
||||
Send(Out["MoveAvatar"], newPillow.x, newPillow.y);
|
||||
lastMoveCmd = DateTime.Now;
|
||||
}
|
||||
}
|
||||
31
Room Furni Scanner.csx
Normal file
31
Room Furni Scanner.csx
Normal file
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xabbo.Core;
|
||||
|
||||
Log("=== Color Puzzle Scanner ===");
|
||||
Log("Scanning all floor items in the room...");
|
||||
|
||||
var furniByName = new Dictionary<string, List<(long Id, int X, int Y, double Z, int State, int Dir)>>();
|
||||
|
||||
foreach (IFloorItem item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
string name = item.GetName();
|
||||
if (!furniByName.ContainsKey(name))
|
||||
furniByName[name] = new List<(long, int, int, double, int, int)>();
|
||||
furniByName[name].Add((item.Id, item.Location.X, item.Location.Y, item.Location.Z, item.State, item.Direction));
|
||||
}
|
||||
|
||||
Log($"\nFound {furniByName.Count} unique furni types:\n");
|
||||
|
||||
foreach (var kvp in furniByName.OrderByDescending(x => x.Value.Count))
|
||||
{
|
||||
Log($"--- {kvp.Key} (x{kvp.Value.Count}) ---");
|
||||
foreach (var f in kvp.Value.OrderBy(x => x.Y).ThenBy(x => x.X))
|
||||
{
|
||||
Log($" ID={f.Id} Pos=({f.X},{f.Y}) Z={f.Z:F2} State={f.State} Dir={f.Dir}");
|
||||
}
|
||||
}
|
||||
|
||||
Log("\n=== Scan Complete ===");
|
||||
50
Room Queue Auto Joiner.csx
Normal file
50
Room Queue Auto Joiner.csx
Normal file
@ -0,0 +1,50 @@
|
||||
// Room Queue Joiner - Versucht solange bis man in der Queue landet
|
||||
int roomId = 62909254;
|
||||
int delayMs = 500; // Wartezeit zwischen Versuchen
|
||||
|
||||
bool inQueue = false;
|
||||
bool success = false;
|
||||
int attempts = 0;
|
||||
|
||||
// Wenn wir in die Queue kommen oder den Raum betreten
|
||||
OnIntercept(In["RoomQueueStatus"], e => {
|
||||
Log(">>> IN DER QUEUE!");
|
||||
inQueue = true;
|
||||
success = true;
|
||||
});
|
||||
|
||||
// Wenn wir den Raum betreten haben
|
||||
OnIntercept(In["RoomReady"], e => {
|
||||
Log(">>> IM RAUM!");
|
||||
success = true;
|
||||
});
|
||||
|
||||
// Error abfangen (Raum voll, Queue voll, etc.)
|
||||
OnIntercept(In["CantConnect"], e => {
|
||||
// Raum/Queue voll - weiter versuchen
|
||||
});
|
||||
|
||||
Log("=== ROOM QUEUE JOINER ===");
|
||||
Log("Room ID: " + roomId);
|
||||
Log("Versuche in den Raum zu kommen...");
|
||||
|
||||
while (Run && !success)
|
||||
{
|
||||
attempts++;
|
||||
Log("Versuch #" + attempts + "...");
|
||||
|
||||
// GetGuestRoom senden
|
||||
Send(Out["GetGuestRoom"], roomId, 0, 1);
|
||||
|
||||
Delay(delayMs);
|
||||
|
||||
if (success)
|
||||
break;
|
||||
}
|
||||
|
||||
if (inQueue)
|
||||
Log("ERFOLG! Du bist jetzt in der Room Queue.");
|
||||
else if (success)
|
||||
Log("ERFOLG! Du bist im Raum.");
|
||||
else
|
||||
Log("Script gestoppt.");
|
||||
399
SOLVER.csx
Normal file
399
SOLVER.csx
Normal file
@ -0,0 +1,399 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
const int MAIN_ROOM = 80262160;
|
||||
const int SOURCE_MIN_X = 18;
|
||||
const int SOURCE_MAX_X = 29;
|
||||
const int SOURCE_MIN_Y = 25;
|
||||
const int SOURCE_MAX_Y = 36;
|
||||
const int FIELD_MIN_X = 18;
|
||||
const int FIELD_MAX_X = 29;
|
||||
const int FIELD_MIN_Y = 9;
|
||||
const int FIELD_MAX_Y = 20;
|
||||
|
||||
var gameRooms = new[] { 80262165, 80262159, 80262161, 80262162, 80262164, 80262166, 80262167, 80262163 };
|
||||
var completed = new HashSet<int>();
|
||||
|
||||
int GetColorNumber(string name)
|
||||
{
|
||||
var match = Regex.Match(name, @"\d+$");
|
||||
return match.Success ? int.Parse(match.Value) : -1;
|
||||
}
|
||||
|
||||
bool InArea(int x, int y, int minX, int maxX, int minY, int maxY) => x >= minX && x <= maxX && y >= minY && y <= maxY;
|
||||
bool InField(int x, int y) => InArea(x, y, FIELD_MIN_X, FIELD_MAX_X, FIELD_MIN_Y, FIELD_MAX_Y);
|
||||
bool InWaitingLine(int x, int y) => x == 14 && y >= 16 && y <= 33;
|
||||
bool AtChair(int x, int y) => x == 14 && y == 15;
|
||||
|
||||
int GetRoomId()
|
||||
{
|
||||
try { return (int)Room.Id; } catch { return 0; }
|
||||
}
|
||||
|
||||
(int x, int y) GetSelfPos()
|
||||
{
|
||||
try { return (Self.Location.X, Self.Location.Y); } catch { return (-1, -1); }
|
||||
}
|
||||
|
||||
void WalkToAndWait(int tx, int ty)
|
||||
{
|
||||
for (int retry = 0; retry < 10; retry++)
|
||||
{
|
||||
Send(Out["MoveAvatar"], tx, ty);
|
||||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
Delay(100);
|
||||
var pos = GetSelfPos();
|
||||
if (pos.x == tx && pos.y == ty)
|
||||
{
|
||||
Delay(200);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WalkToAreaAndWait(int minX, int maxX, int minY, int maxY)
|
||||
{
|
||||
for (int retry = 0; retry < 10; retry++)
|
||||
{
|
||||
var pos = GetSelfPos();
|
||||
if (pos.x >= 0 && InArea(pos.x, pos.y, minX, maxX, minY, maxY))
|
||||
{
|
||||
Delay(200);
|
||||
return;
|
||||
}
|
||||
Send(Out["MoveAvatar"], minX, minY);
|
||||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
Delay(100);
|
||||
pos = GetSelfPos();
|
||||
if (pos.x >= 0 && InArea(pos.x, pos.y, minX, maxX, minY, maxY))
|
||||
{
|
||||
Delay(200);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClickUntilTeleport(int id, int targetX, int targetY)
|
||||
{
|
||||
for (int retry = 0; retry < 20; retry++)
|
||||
{
|
||||
Send(Out["ClickFurni"], id, 0);
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
Delay(100);
|
||||
var pos = GetSelfPos();
|
||||
if (pos.x == targetX && pos.y == targetY)
|
||||
{
|
||||
Delay(200);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UseUntilRoom(int id, int roomId)
|
||||
{
|
||||
for (int retry = 0; retry < 20; retry++)
|
||||
{
|
||||
Send(Out["UseFurniture"], id, 0);
|
||||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
Delay(100);
|
||||
if (GetRoomId() == roomId)
|
||||
{
|
||||
Delay(2000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WaitForMainRoom()
|
||||
{
|
||||
for (int i = 0; i < 200; i++)
|
||||
{
|
||||
Delay(100);
|
||||
if (GetRoomId() == MAIN_ROOM)
|
||||
{
|
||||
Delay(2000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WaitUntilInField()
|
||||
{
|
||||
Log("Waiting for field...");
|
||||
|
||||
while (true)
|
||||
{
|
||||
var pos = GetSelfPos();
|
||||
if (pos.x < 0) { Delay(100); continue; }
|
||||
|
||||
if (InField(pos.x, pos.y))
|
||||
{
|
||||
Log($"In field at ({pos.x},{pos.y})");
|
||||
Delay(300);
|
||||
return;
|
||||
}
|
||||
|
||||
if (AtChair(pos.x, pos.y))
|
||||
{
|
||||
Delay(100);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (InWaitingLine(pos.x, pos.y))
|
||||
{
|
||||
Send(Out["MoveAvatar"], 14, 15);
|
||||
Delay(200);
|
||||
continue;
|
||||
}
|
||||
|
||||
Send(Out["MoveAvatar"], 14, 20);
|
||||
Delay(300);
|
||||
}
|
||||
}
|
||||
|
||||
void DoPixelArt()
|
||||
{
|
||||
WaitUntilInField();
|
||||
|
||||
var selectors = new Dictionary<int, int>();
|
||||
try
|
||||
{
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
string name = item.GetName();
|
||||
if (name.Contains("Cylinder Block") || name.Contains("Hemisphere Block"))
|
||||
{
|
||||
int colorNum = GetColorNumber(name);
|
||||
if (colorNum > 0 && !selectors.ContainsKey(colorNum))
|
||||
selectors[colorNum] = (int)item.Id;
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
|
||||
var sourcePixels = new Dictionary<(int x, int y), int>();
|
||||
try
|
||||
{
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
int x = item.Location.X;
|
||||
int y = item.Location.Y;
|
||||
double z = item.Location.Z;
|
||||
if (x < SOURCE_MIN_X || x > SOURCE_MAX_X || y < SOURCE_MIN_Y || y > SOURCE_MAX_Y) continue;
|
||||
if (z < 0.7 || z > 1.0) continue;
|
||||
int colorNum = GetColorNumber(item.GetName());
|
||||
if (colorNum > 0)
|
||||
sourcePixels[(x, y)] = colorNum;
|
||||
}
|
||||
} catch { }
|
||||
|
||||
int offsetY = SOURCE_MIN_Y - FIELD_MIN_Y;
|
||||
int changed = 0;
|
||||
|
||||
for (int attempt = 1; attempt <= 3; attempt++)
|
||||
{
|
||||
var fieldTiles = new Dictionary<(int x, int y), (int id, int colorNum)>();
|
||||
try
|
||||
{
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
int x = item.Location.X;
|
||||
int y = item.Location.Y;
|
||||
double z = item.Location.Z;
|
||||
if (x < FIELD_MIN_X || x > FIELD_MAX_X || y < FIELD_MIN_Y || y > FIELD_MAX_Y) continue;
|
||||
|
||||
int colorNum = GetColorNumber(item.GetName());
|
||||
|
||||
if (z > 0.5)
|
||||
fieldTiles[(x, y)] = ((int)item.Id, colorNum);
|
||||
else if (!fieldTiles.ContainsKey((x, y)))
|
||||
fieldTiles[(x, y)] = ((int)item.Id, -1);
|
||||
}
|
||||
} catch { }
|
||||
|
||||
var toChange = new List<(int fieldId, int srcColor)>();
|
||||
foreach (var src in sourcePixels)
|
||||
{
|
||||
int fieldX = src.Key.x;
|
||||
int fieldY = src.Key.y - offsetY;
|
||||
int srcColor = src.Value;
|
||||
|
||||
if (!fieldTiles.TryGetValue((fieldX, fieldY), out var field)) continue;
|
||||
if (field.colorNum == srcColor) continue;
|
||||
if (!selectors.ContainsKey(srcColor)) continue;
|
||||
|
||||
toChange.Add((field.id, srcColor));
|
||||
}
|
||||
|
||||
if (toChange.Count == 0) break;
|
||||
|
||||
var sorted = toChange.OrderBy(x => x.srcColor).ToList();
|
||||
int lastColor = -1;
|
||||
|
||||
foreach (var item in sorted)
|
||||
{
|
||||
if (item.srcColor != lastColor)
|
||||
{
|
||||
Send(Out["ClickFurni"], selectors[item.srcColor], 0);
|
||||
Delay(50);
|
||||
lastColor = item.srcColor;
|
||||
}
|
||||
|
||||
Send(Out["ClickFurni"], item.fieldId, 0);
|
||||
Delay(50);
|
||||
changed++;
|
||||
}
|
||||
|
||||
Delay(500);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var badge = FloorItems.FirstOrDefault(f => f != null && f.GetName() == "Badge Display Case");
|
||||
if (badge != null)
|
||||
Send(Out["ClickFurni"], (int)badge.Id, 0);
|
||||
} catch { }
|
||||
|
||||
Log($"Done: {changed}");
|
||||
completed.Add(GetRoomId());
|
||||
}
|
||||
|
||||
void GoToRoom1()
|
||||
{
|
||||
if (completed.Contains(80262165)) return;
|
||||
Log("Room 1");
|
||||
WalkToAreaAndWait(5, 6, 25, 27);
|
||||
ClickUntilTeleport(902905683, 11, 27);
|
||||
ClickUntilTeleport(902906203, 11, 24);
|
||||
UseUntilRoom(902906203, 80262165);
|
||||
DoPixelArt();
|
||||
WaitForMainRoom();
|
||||
}
|
||||
|
||||
void GoToRoom2()
|
||||
{
|
||||
if (completed.Contains(80262159)) return;
|
||||
Log("Room 2");
|
||||
WalkToAreaAndWait(16, 20, 34, 35);
|
||||
ClickUntilTeleport(902906461, 20, 34);
|
||||
ClickUntilTeleport(902906426, 20, 32);
|
||||
UseUntilRoom(902906426, 80262159);
|
||||
DoPixelArt();
|
||||
WaitForMainRoom();
|
||||
}
|
||||
|
||||
void GoToRoom3()
|
||||
{
|
||||
if (completed.Contains(80262161)) return;
|
||||
Log("Room 3");
|
||||
WalkToAreaAndWait(25, 25, 17, 21);
|
||||
Send(Out["ClickFurni"], 902906152, 0);
|
||||
Delay(500);
|
||||
ClickUntilTeleport(902907470, 25, 19);
|
||||
UseUntilRoom(902907470, 80262161);
|
||||
DoPixelArt();
|
||||
WaitForMainRoom();
|
||||
}
|
||||
|
||||
void GoToRoom4()
|
||||
{
|
||||
if (completed.Contains(80262162)) return;
|
||||
Log("Room 4");
|
||||
WalkToAreaAndWait(30, 31, 15, 16);
|
||||
Send(Out["ClickFurni"], 902907718, 0);
|
||||
Delay(500);
|
||||
ClickUntilTeleport(902907623, 31, 15);
|
||||
UseUntilRoom(902907623, 80262162);
|
||||
DoPixelArt();
|
||||
WaitForMainRoom();
|
||||
}
|
||||
|
||||
void GoToRoom5()
|
||||
{
|
||||
if (completed.Contains(80262164)) return;
|
||||
Log("Room 5");
|
||||
WalkToAreaAndWait(30, 31, 15, 16);
|
||||
Send(Out["ClickFurni"], 902907718, 0);
|
||||
Delay(500);
|
||||
WalkToAndWait(36, 16);
|
||||
ClickUntilTeleport(902905838, 36, 16);
|
||||
UseUntilRoom(902905838, 80262164);
|
||||
DoPixelArt();
|
||||
WaitForMainRoom();
|
||||
}
|
||||
|
||||
void GoToRoom6()
|
||||
{
|
||||
if (completed.Contains(80262166)) return;
|
||||
Log("Room 6");
|
||||
WalkToAreaAndWait(30, 31, 15, 16);
|
||||
Send(Out["ClickFurni"], 902907718, 0);
|
||||
Delay(500);
|
||||
WalkToAndWait(44, 14);
|
||||
ClickUntilTeleport(902907457, 44, 14);
|
||||
UseUntilRoom(902907457, 80262166);
|
||||
DoPixelArt();
|
||||
WaitForMainRoom();
|
||||
}
|
||||
|
||||
void GoToRoom7()
|
||||
{
|
||||
if (completed.Contains(80262167)) return;
|
||||
Log("Room 7");
|
||||
WalkToAreaAndWait(30, 31, 15, 16);
|
||||
Send(Out["ClickFurni"], 902907718, 0);
|
||||
Delay(500);
|
||||
WalkToAndWait(50, 12);
|
||||
ClickUntilTeleport(902906400, 50, 12);
|
||||
UseUntilRoom(902906400, 80262167);
|
||||
DoPixelArt();
|
||||
WaitForMainRoom();
|
||||
}
|
||||
|
||||
void GoToRoom8()
|
||||
{
|
||||
if (completed.Contains(80262163)) return;
|
||||
Log("Room 8");
|
||||
WalkToAreaAndWait(50, 51, 21, 21);
|
||||
Send(Out["ClickFurni"], 902906802, 0);
|
||||
Delay(500);
|
||||
ClickUntilTeleport(902906273, 50, 21);
|
||||
UseUntilRoom(902906273, 80262163);
|
||||
DoPixelArt();
|
||||
WaitForMainRoom();
|
||||
}
|
||||
|
||||
Log("Starting");
|
||||
|
||||
int currentRoom = GetRoomId();
|
||||
|
||||
if (gameRooms.Contains(currentRoom))
|
||||
{
|
||||
Log($"In game room {currentRoom}");
|
||||
DoPixelArt();
|
||||
WaitForMainRoom();
|
||||
}
|
||||
|
||||
GoToRoom1();
|
||||
GoToRoom2();
|
||||
GoToRoom3();
|
||||
GoToRoom4();
|
||||
GoToRoom5();
|
||||
GoToRoom6();
|
||||
GoToRoom7();
|
||||
GoToRoom8();
|
||||
|
||||
Log($"All done: {completed.Count}/8");
|
||||
WalkToAreaAndWait(41, 41, 32, 33);
|
||||
3
Seed trade (simple).csx
Normal file
3
Seed trade (simple).csx
Normal file
@ -0,0 +1,3 @@
|
||||
EnsureInventory();
|
||||
var items = Inventory.GetFloorItems().Named("Plant Seed").Where(seed => (seed.Data as MapData)?["rarity"] == "7").Take(20);
|
||||
Send(Out.TradeAddItems, items.Select(x => (int)x.ItemId).ToList());
|
||||
9
Seed trade (split).csx
Normal file
9
Seed trade (split).csx
Normal file
@ -0,0 +1,9 @@
|
||||
EnsureInventory();
|
||||
var items = Inventory.GetFloorItems()
|
||||
.Named("Plant Seed")
|
||||
.GroupBy(seed => (seed.Data as MapData)?["rarity"] as string) // Gruppiert nach rarity
|
||||
.Where(group => int.TryParse(group.Key, out int r) && r >= 0 && r <= 11) // Prüft, ob rarity zwischen 0-11 liegt
|
||||
.SelectMany(group => group.Take(150)) // Nimmt 10 aus jeder Gruppe
|
||||
.ToList();
|
||||
|
||||
Send(Out.TradeAddItems, items.Select(x => (int)x.ItemId).ToList());
|
||||
9
Sit Spam.csx
Normal file
9
Sit Spam.csx
Normal file
@ -0,0 +1,9 @@
|
||||
// Sit Spam - Setzt dich dauerhaft hin
|
||||
|
||||
Log("=== Sit Spam gestartet ===");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Sit();
|
||||
Delay(100);
|
||||
}
|
||||
404
Snake Auto (AutoCalib).csx
Normal file
404
Snake Auto (AutoCalib).csx
Normal file
@ -0,0 +1,404 @@
|
||||
/// @name Snake Auto (AutoCalib)
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
const int GAME_MIN_X = 9;
|
||||
const int GAME_MAX_X = 28;
|
||||
const int GAME_MIN_Y = 11;
|
||||
const int GAME_MAX_Y = 30;
|
||||
|
||||
const int BLOCKED_MIN_X = 17;
|
||||
const int BLOCKED_MAX_X = 20;
|
||||
const int BLOCKED_MIN_Y = 19;
|
||||
const int BLOCKED_MAX_Y = 22;
|
||||
|
||||
const int KIND_SNAKE = 7100;
|
||||
const int KIND_FOOD = 5068;
|
||||
|
||||
// Fallback controls for this room layout
|
||||
const int FALLBACK_UP = 769407748;
|
||||
const int FALLBACK_RIGHT = 769407103;
|
||||
const int FALLBACK_DOWN = 769407216;
|
||||
const int FALLBACK_LEFT = 769407068;
|
||||
|
||||
int CTRL_UP = FALLBACK_UP;
|
||||
int CTRL_RIGHT = FALLBACK_RIGHT;
|
||||
int CTRL_DOWN = FALLBACK_DOWN;
|
||||
int CTRL_LEFT = FALLBACK_LEFT;
|
||||
|
||||
List<(int x, int y)> body = new List<(int x, int y)>(256);
|
||||
HashSet<(int x, int y)> bodySet = new HashSet<(int x, int y)>();
|
||||
(int x, int y) food = (-1, -1);
|
||||
|
||||
string dir = "DOWN";
|
||||
string lastSentDir = "";
|
||||
DateTime lastCmdTime = DateTime.MinValue;
|
||||
bool ate = false;
|
||||
|
||||
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
|
||||
|
||||
bool Wall(int x, int y) => x < GAME_MIN_X || x > GAME_MAX_X || y < GAME_MIN_Y || y > GAME_MAX_Y;
|
||||
bool Blk(int x, int y) => x >= BLOCKED_MIN_X && x <= BLOCKED_MAX_X && y >= BLOCKED_MIN_Y && y <= BLOCKED_MAX_Y;
|
||||
|
||||
void Step(int x, int y, int d, out int nx, out int ny)
|
||||
{
|
||||
nx = x + (d == 3 ? 1 : d == 2 ? -1 : 0);
|
||||
ny = y + (d == 1 ? 1 : d == 0 ? -1 : 0);
|
||||
}
|
||||
|
||||
int Opp(int d) => d ^ 1;
|
||||
int DirToIdx(string d) => d == "UP" ? 0 : d == "DOWN" ? 1 : d == "LEFT" ? 2 : 3;
|
||||
|
||||
bool Blocked(int x, int y, bool ignoreTail)
|
||||
{
|
||||
if (Wall(x, y) || Blk(x, y)) return true;
|
||||
if (body.Count == 0) return false;
|
||||
|
||||
int checkLen = ignoreTail && !ate ? body.Count - 1 : body.Count;
|
||||
for (int i = 0; i < checkLen; i++)
|
||||
if (body[i].x == x && body[i].y == y) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void SendDir(int dIdx)
|
||||
{
|
||||
string d = DIRS[dIdx];
|
||||
if (d == lastSentDir && (DateTime.Now - lastCmdTime).TotalMilliseconds < 80) return;
|
||||
|
||||
int id = dIdx == 0 ? CTRL_UP : dIdx == 1 ? CTRL_DOWN : dIdx == 2 ? CTRL_LEFT : CTRL_RIGHT;
|
||||
Send(Out["ClickFurni"], id, 0);
|
||||
|
||||
lastCmdTime = DateTime.Now;
|
||||
lastSentDir = d;
|
||||
dir = d;
|
||||
}
|
||||
|
||||
int OpenSpace(int sx, int sy, int cap)
|
||||
{
|
||||
if (Blocked(sx, sy, false)) return 0;
|
||||
var seen = new HashSet<(int, int)>();
|
||||
var q = new Queue<(int, int)>();
|
||||
|
||||
q.Enqueue((sx, sy));
|
||||
seen.Add((sx, sy));
|
||||
int c = 0;
|
||||
|
||||
while (q.Count > 0 && c < cap)
|
||||
{
|
||||
var p = q.Dequeue();
|
||||
c++;
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Step(p.Item1, p.Item2, d, out int nx, out int ny);
|
||||
if (!Blocked(nx, ny, false) && seen.Add((nx, ny)))
|
||||
q.Enqueue((nx, ny));
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
int DistTo(int sx, int sy, int tx, int ty, int cap = 180)
|
||||
{
|
||||
if (sx == tx && sy == ty) return 0;
|
||||
var seen = new HashSet<(int, int)>();
|
||||
var q = new Queue<((int x, int y) pos, int dist)>();
|
||||
|
||||
q.Enqueue(((sx, sy), 0));
|
||||
seen.Add((sx, sy));
|
||||
|
||||
while (q.Count > 0)
|
||||
{
|
||||
var cur = q.Dequeue();
|
||||
if (cur.dist >= cap) return 999;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Step(cur.pos.x, cur.pos.y, d, out int nx, out int ny);
|
||||
if (nx == tx && ny == ty) return cur.dist + 1;
|
||||
if (!Blocked(nx, ny, false) && seen.Add((nx, ny)))
|
||||
q.Enqueue(((nx, ny), cur.dist + 1));
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BestMove(int hx, int hy, int fx, int fy, int curDir)
|
||||
{
|
||||
int best = curDir;
|
||||
int bestScore = int.MinValue;
|
||||
int minSpace = Math.Max(body.Count / 4, 8);
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (d == Opp(curDir)) continue;
|
||||
Step(hx, hy, d, out int nx, out int ny);
|
||||
if (Blocked(nx, ny, true)) continue;
|
||||
|
||||
int space = OpenSpace(nx, ny, 180);
|
||||
if (space < minSpace) continue;
|
||||
|
||||
int score = 0;
|
||||
if (nx == fx && ny == fy) score += 10000;
|
||||
|
||||
int distFood = DistTo(nx, ny, fx, fy, 180);
|
||||
if (distFood > 0 && distFood < 999) score += (220 - distFood) * 7;
|
||||
else if (distFood == -1) score -= 150;
|
||||
|
||||
score += Math.Min(space, 100) * 4;
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
best = d;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
int Emergency(int hx, int hy, int curDir)
|
||||
{
|
||||
int best = curDir;
|
||||
int bestSpace = -1;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (d == Opp(curDir)) continue;
|
||||
Step(hx, hy, d, out int nx, out int ny);
|
||||
if (Blocked(nx, ny, true)) continue;
|
||||
|
||||
int space = OpenSpace(nx, ny, 80);
|
||||
if (space > bestSpace)
|
||||
{
|
||||
bestSpace = space;
|
||||
best = d;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
List<List<(long id, int x, int y)>> GetComponents(List<(long id, int kind, int x, int y)> points)
|
||||
{
|
||||
var comps = new List<List<(long id, int x, int y)>>();
|
||||
var used = new HashSet<long>();
|
||||
|
||||
foreach (var p in points)
|
||||
{
|
||||
if (!used.Add(p.id)) continue;
|
||||
|
||||
var comp = new List<(long id, int x, int y)>();
|
||||
var q = new Queue<(long id, int x, int y)>();
|
||||
q.Enqueue((p.id, p.x, p.y));
|
||||
|
||||
while (q.Count > 0)
|
||||
{
|
||||
var cur = q.Dequeue();
|
||||
comp.Add(cur);
|
||||
|
||||
foreach (var n in points)
|
||||
{
|
||||
if (used.Contains(n.id)) continue;
|
||||
if (Math.Abs(cur.x - n.x) <= 1 && Math.Abs(cur.y - n.y) <= 1)
|
||||
{
|
||||
used.Add(n.id);
|
||||
q.Enqueue((n.id, n.x, n.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
comps.Add(comp);
|
||||
}
|
||||
|
||||
return comps;
|
||||
}
|
||||
|
||||
bool TryAutoCalibrateControls()
|
||||
{
|
||||
if (Self == null) return false;
|
||||
|
||||
int sx = Self.Location.X;
|
||||
int sy = Self.Location.Y;
|
||||
|
||||
var nearby = FloorItems
|
||||
.Where(i => i != null)
|
||||
.Where(i => Math.Abs(i.Location.X - sx) <= 14 && Math.Abs(i.Location.Y - sy) <= 14)
|
||||
.Where(i => i.Kind != KIND_SNAKE && i.Kind != KIND_FOOD)
|
||||
.Select(i => (id: (long)i.Id, kind: (int)i.Kind, x: i.Location.X, y: i.Location.Y))
|
||||
.ToList();
|
||||
|
||||
if (nearby.Count == 0) return false;
|
||||
|
||||
List<(long id, int x, int y)> best = null;
|
||||
double bestScore = double.MaxValue;
|
||||
|
||||
foreach (var g in nearby.GroupBy(p => p.kind))
|
||||
{
|
||||
if (g.Count() < 4) continue;
|
||||
|
||||
var components = GetComponents(g.ToList());
|
||||
foreach (var comp in components)
|
||||
{
|
||||
if (comp.Count < 4) continue;
|
||||
|
||||
int minX = comp.Min(p => p.x);
|
||||
int maxX = comp.Max(p => p.x);
|
||||
int minY = comp.Min(p => p.y);
|
||||
int maxY = comp.Max(p => p.y);
|
||||
|
||||
int w = maxX - minX;
|
||||
int h = maxY - minY;
|
||||
if (w > 6 || h > 6) continue;
|
||||
|
||||
double cx = comp.Average(p => p.x);
|
||||
double cy = comp.Average(p => p.y);
|
||||
double dist = Math.Abs(cx - sx) + Math.Abs(cy - sy);
|
||||
double score = dist + Math.Abs(comp.Count - 4) * 0.5;
|
||||
|
||||
if (score < bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
best = comp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best == null || best.Count < 4) return false;
|
||||
|
||||
double centerX = best.Average(p => p.x);
|
||||
double centerY = best.Average(p => p.y);
|
||||
|
||||
var used = new HashSet<long>();
|
||||
|
||||
long Pick(IEnumerable<(long id, int x, int y)> list)
|
||||
{
|
||||
foreach (var p in list)
|
||||
{
|
||||
if (used.Add(p.id)) return p.id;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
var upList = best.OrderBy(p => p.y).ThenBy(p => Math.Abs(p.x - centerX)).ToList();
|
||||
var downList = best.OrderByDescending(p => p.y).ThenBy(p => Math.Abs(p.x - centerX)).ToList();
|
||||
var leftList = best.OrderBy(p => p.x).ThenBy(p => Math.Abs(p.y - centerY)).ToList();
|
||||
var rightList = best.OrderByDescending(p => p.x).ThenBy(p => Math.Abs(p.y - centerY)).ToList();
|
||||
|
||||
long up = Pick(upList);
|
||||
long down = Pick(downList);
|
||||
long left = Pick(leftList);
|
||||
long right = Pick(rightList);
|
||||
|
||||
if (up <= 0 || down <= 0 || left <= 0 || right <= 0) return false;
|
||||
|
||||
CTRL_UP = (int)up;
|
||||
CTRL_RIGHT = (int)right;
|
||||
CTRL_DOWN = (int)down;
|
||||
CTRL_LEFT = (int)left;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Log("Snake Auto (AutoCalib) started");
|
||||
|
||||
bool calibrated = TryAutoCalibrateControls();
|
||||
if (calibrated)
|
||||
Log($"Auto-calibrated controls: U={CTRL_UP} R={CTRL_RIGHT} D={CTRL_DOWN} L={CTRL_LEFT}");
|
||||
else
|
||||
Log($"Auto-calibration failed, using fallback: U={CTRL_UP} R={CTRL_RIGHT} D={CTRL_DOWN} L={CTRL_LEFT}");
|
||||
|
||||
Log("Waiting for snake + food spawn...");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(15);
|
||||
|
||||
var curSnake = new HashSet<(int x, int y)>();
|
||||
food = (-1, -1);
|
||||
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
int k;
|
||||
try { k = (int)item.Kind; } catch { continue; }
|
||||
|
||||
if (k == KIND_SNAKE && !Wall(item.Location.X, item.Location.Y))
|
||||
curSnake.Add((item.Location.X, item.Location.Y));
|
||||
else if (k == KIND_FOOD)
|
||||
food = (item.Location.X, item.Location.Y);
|
||||
}
|
||||
|
||||
int curLen = curSnake.Count;
|
||||
if (curLen == 0)
|
||||
{
|
||||
body.Clear();
|
||||
bodySet.Clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (body.Count == 0)
|
||||
{
|
||||
var sorted = curSnake.OrderBy(p => p.y).ThenBy(p => p.x).ToList();
|
||||
body = new List<(int x, int y)>(sorted);
|
||||
bodySet = new HashSet<(int x, int y)>(sorted);
|
||||
continue;
|
||||
}
|
||||
|
||||
var head = body[0];
|
||||
(int x, int y) newHead = (-1, -1);
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Step(head.x, head.y, d, out int nx, out int ny);
|
||||
if (curSnake.Contains((nx, ny)) && !bodySet.Contains((nx, ny)))
|
||||
{
|
||||
newHead = (nx, ny);
|
||||
if ((DateTime.Now - lastCmdTime).TotalMilliseconds > 400)
|
||||
dir = DIRS[d];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (newHead.x != -1)
|
||||
{
|
||||
int prevLen = body.Count;
|
||||
body.Insert(0, newHead);
|
||||
bodySet.Add(newHead);
|
||||
ate = curLen > prevLen;
|
||||
|
||||
while (body.Count > curLen)
|
||||
{
|
||||
var tail = body[body.Count - 1];
|
||||
bodySet.Remove(tail);
|
||||
body.RemoveAt(body.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (body.Count == 0 || food.x == -1) continue;
|
||||
|
||||
head = body[0];
|
||||
int curDir = DirToIdx(dir);
|
||||
|
||||
Step(head.x, head.y, curDir, out int fx, out int fy);
|
||||
if (Blocked(fx, fy, true))
|
||||
{
|
||||
SendDir(Emergency(head.x, head.y, curDir));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (d == Opp(curDir)) continue;
|
||||
Step(head.x, head.y, d, out int nx, out int ny);
|
||||
if (nx == food.x && ny == food.y && !Blocked(nx, ny, true))
|
||||
{
|
||||
SendDir(d);
|
||||
goto nextLoop;
|
||||
}
|
||||
}
|
||||
|
||||
SendDir(BestMove(head.x, head.y, food.x, food.y, curDir));
|
||||
|
||||
nextLoop:;
|
||||
}
|
||||
116
Snake Auto (Campaign) v1.csx
Normal file
116
Snake Auto (Campaign) v1.csx
Normal file
@ -0,0 +1,116 @@
|
||||
/// @name Snake Auto (Campaign) v1
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
// Fixed from your room
|
||||
const long HEAD_ID = 769407982;
|
||||
const int TARGET_KIND = 3666; // Color Tile kind
|
||||
|
||||
// Pad button IDs (as confirmed by you)
|
||||
const int PAD_TOP_LEFT = 769407085;
|
||||
const int PAD_TOP_RIGHT = 769406820;
|
||||
const int PAD_BOTTOM_LEFT = 769407385;
|
||||
const int PAD_BOTTOM_RIGHT = 769407116;
|
||||
|
||||
// Direction mapping (tuned for this room)
|
||||
const int CTRL_UP = PAD_TOP_LEFT;
|
||||
const int CTRL_RIGHT = PAD_TOP_RIGHT;
|
||||
const int CTRL_DOWN = PAD_BOTTOM_LEFT;
|
||||
const int CTRL_LEFT = PAD_BOTTOM_RIGHT;
|
||||
|
||||
DateTime lastCmd = DateTime.MinValue;
|
||||
DateTime lastInfo = DateTime.MinValue;
|
||||
int lastDir = -1; // 0=UP 1=DOWN 2=LEFT 3=RIGHT
|
||||
|
||||
bool TryGetButton(int dir, out int id, out int x, out int y)
|
||||
{
|
||||
id = dir == 0 ? CTRL_UP : dir == 1 ? CTRL_DOWN : dir == 2 ? CTRL_LEFT : CTRL_RIGHT;
|
||||
var btn = GetFloorItem(id);
|
||||
if (btn == null)
|
||||
{
|
||||
x = 0;
|
||||
y = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
x = btn.Location.X;
|
||||
y = btn.Location.Y;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SendDir(int dir)
|
||||
{
|
||||
if ((DateTime.Now - lastCmd).TotalMilliseconds < 140 && dir == lastDir)
|
||||
return;
|
||||
|
||||
if (TryGetButton(dir, out int id, out int x, out int y))
|
||||
{
|
||||
// This game mode reacts when user steps on the plate.
|
||||
Move(x, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
int fallbackId = dir == 0 ? CTRL_UP : dir == 1 ? CTRL_DOWN : dir == 2 ? CTRL_LEFT : CTRL_RIGHT;
|
||||
Send(Out["ClickFurni"], fallbackId, 0);
|
||||
}
|
||||
|
||||
lastCmd = DateTime.Now;
|
||||
lastDir = dir;
|
||||
}
|
||||
|
||||
int ToDir(int dx, int dy)
|
||||
{
|
||||
if (Math.Abs(dx) >= Math.Abs(dy))
|
||||
return dx < 0 ? 2 : 3;
|
||||
return dy < 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
Log("Snake Auto (Campaign) v3 started");
|
||||
Log($"HeadId={HEAD_ID}, TargetKind={TARGET_KIND}");
|
||||
Log($"Buttons: U={CTRL_UP} R={CTRL_RIGHT} D={CTRL_DOWN} L={CTRL_LEFT}");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(40);
|
||||
|
||||
var head = GetFloorItem(HEAD_ID);
|
||||
if (head == null)
|
||||
{
|
||||
if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500)
|
||||
{
|
||||
Log("waiting: head not visible");
|
||||
lastInfo = DateTime.Now;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var targets = FloorItems
|
||||
.Where(i => i != null && i.Kind == TARGET_KIND)
|
||||
.Where(i => Math.Abs(i.Location.X - head.Location.X) + Math.Abs(i.Location.Y - head.Location.Y) <= 45)
|
||||
.ToList();
|
||||
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500)
|
||||
{
|
||||
int allTargets = FloorItems.Count(i => i != null && i.Kind == TARGET_KIND);
|
||||
Log($"waiting: no nearby target tiles (all kind {TARGET_KIND}: {allTargets})");
|
||||
lastInfo = DateTime.Now;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var target = targets
|
||||
.OrderBy(t => Math.Abs(t.Location.X - head.Location.X) + Math.Abs(t.Location.Y - head.Location.Y))
|
||||
.ThenBy(t => t.State)
|
||||
.First();
|
||||
|
||||
int dx = target.Location.X - head.Location.X;
|
||||
int dy = target.Location.Y - head.Location.Y;
|
||||
|
||||
if (dx == 0 && dy == 0)
|
||||
continue;
|
||||
|
||||
int dir = ToDir(dx, dy);
|
||||
SendDir(dir);
|
||||
}
|
||||
286
Snake Auto (Campaign) v10.csx
Normal file
286
Snake Auto (Campaign) v10.csx
Normal file
@ -0,0 +1,286 @@
|
||||
/// @name Snake Auto (Campaign) v10
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
const long HEAD_ID = 769407982;
|
||||
const int SNAKE_KIND = 5478;
|
||||
static readonly int[] TARGET_KINDS = { 6559, 3666 };
|
||||
|
||||
const int GAME_MIN_X = 20;
|
||||
const int GAME_MAX_X = 65;
|
||||
const int GAME_MIN_Y = 20;
|
||||
const int GAME_MAX_Y = 65;
|
||||
|
||||
// Direction -> plate ids (verified from logs)
|
||||
const int CTRL_UP = 769406820;
|
||||
const int CTRL_DOWN = 769407116;
|
||||
const int CTRL_LEFT = 769407385;
|
||||
const int CTRL_RIGHT = 769407085;
|
||||
|
||||
// Known static obstacle (blue block cluster)
|
||||
const int BLOCK_MIN_X = 20;
|
||||
const int BLOCK_MAX_X = 22;
|
||||
const int BLOCK_MIN_Y = 20;
|
||||
const int BLOCK_MAX_Y = 21;
|
||||
|
||||
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
|
||||
|
||||
DateTime lastCmd = DateTime.MinValue;
|
||||
DateTime lastDbg = DateTime.MinValue;
|
||||
int curDir = -1;
|
||||
string lastSentDir = "";
|
||||
(int x, int y) prevHead = (-1, -1);
|
||||
|
||||
int lastAttemptDir = -1;
|
||||
long lastTargetId = 0;
|
||||
DateTime lastAttemptAt = DateTime.MinValue;
|
||||
|
||||
Dictionary<(int x, int y), DateTime> tempBlocked = new Dictionary<(int, int), DateTime>();
|
||||
Dictionary<long, DateTime> targetBlacklist = new Dictionary<long, DateTime>();
|
||||
|
||||
HashSet<(int x, int y)> fillVisited = new HashSet<(int, int)>();
|
||||
Queue<(int x, int y)> fillQueue = new Queue<(int, int)>();
|
||||
|
||||
bool InBoard(int x, int y)
|
||||
=> x >= GAME_MIN_X && x <= GAME_MAX_X && y >= GAME_MIN_Y && y <= GAME_MAX_Y;
|
||||
|
||||
bool IsStaticBlock(int x, int y)
|
||||
=> x >= BLOCK_MIN_X && x <= BLOCK_MAX_X && y >= BLOCK_MIN_Y && y <= BLOCK_MAX_Y;
|
||||
|
||||
void Dbg(string m, bool force = false)
|
||||
{
|
||||
if (!force && (DateTime.Now - lastDbg).TotalMilliseconds < 220) return;
|
||||
Log($"dbg: {m}");
|
||||
lastDbg = DateTime.Now;
|
||||
}
|
||||
|
||||
void Next(int x, int y, int d, out int nx, out int ny)
|
||||
{
|
||||
nx = x + (d == 3 ? 1 : d == 2 ? -1 : 0);
|
||||
ny = y + (d == 1 ? 1 : d == 0 ? -1 : 0);
|
||||
}
|
||||
|
||||
int Opp(int d) => d ^ 1;
|
||||
|
||||
int DirFromDelta(int dx, int dy)
|
||||
{
|
||||
if (Math.Abs(dx) >= Math.Abs(dy)) return dx < 0 ? 2 : 3;
|
||||
return dy < 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
int BtnForDir(int d)
|
||||
=> d == 0 ? CTRL_UP : d == 1 ? CTRL_DOWN : d == 2 ? CTRL_LEFT : CTRL_RIGHT;
|
||||
|
||||
bool TryGetBtnPos(int id, out int x, out int y)
|
||||
{
|
||||
var b = GetFloorItem(id);
|
||||
if (b == null) { x = 0; y = 0; return false; }
|
||||
x = b.Location.X;
|
||||
y = b.Location.Y;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CleanupMaps()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
foreach (var k in tempBlocked.Where(kv => kv.Value <= now).Select(kv => kv.Key).ToList())
|
||||
tempBlocked.Remove(k);
|
||||
|
||||
foreach (var k in targetBlacklist.Where(kv => kv.Value <= now).Select(kv => kv.Key).ToList())
|
||||
targetBlacklist.Remove(k);
|
||||
}
|
||||
|
||||
bool IsTempBlocked(int x, int y)
|
||||
=> tempBlocked.TryGetValue((x, y), out var until) && until > DateTime.Now;
|
||||
|
||||
bool Bad(int x, int y, HashSet<(int x, int y)> body)
|
||||
{
|
||||
if (!InBoard(x, y)) return true;
|
||||
if (IsStaticBlock(x, y)) return true;
|
||||
if (IsTempBlocked(x, y)) return true;
|
||||
return body.Contains((x, y));
|
||||
}
|
||||
|
||||
int Fill(int sx, int sy, HashSet<(int x, int y)> body, int cap)
|
||||
{
|
||||
if (Bad(sx, sy, body)) return 0;
|
||||
|
||||
fillVisited.Clear();
|
||||
fillQueue.Clear();
|
||||
fillVisited.Add((sx, sy));
|
||||
fillQueue.Enqueue((sx, sy));
|
||||
|
||||
int c = 0;
|
||||
while (fillQueue.Count > 0 && c < cap)
|
||||
{
|
||||
var p = fillQueue.Dequeue();
|
||||
c++;
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Next(p.x, p.y, d, out int nx, out int ny);
|
||||
if (!Bad(nx, ny, body) && fillVisited.Add((nx, ny)))
|
||||
fillQueue.Enqueue((nx, ny));
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
int Emergency((int x, int y) head, HashSet<(int x, int y)> body)
|
||||
{
|
||||
int bestDir = curDir >= 0 ? curDir : 0;
|
||||
int bestSpace = -1;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (curDir >= 0 && d == Opp(curDir)) continue;
|
||||
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body)) continue;
|
||||
|
||||
int space = Fill(nx, ny, body, 260);
|
||||
if (space > bestSpace)
|
||||
{
|
||||
bestSpace = space;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
int PickDir((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body)
|
||||
{
|
||||
int desired = DirFromDelta(target.x - head.x, target.y - head.y);
|
||||
var dirs = new List<int> { desired, 0, 1, 2, 3 };
|
||||
|
||||
int bestDir = curDir >= 0 ? curDir : desired;
|
||||
int bestScore = int.MinValue;
|
||||
|
||||
foreach (int d in dirs.Distinct())
|
||||
{
|
||||
if (curDir >= 0 && d == Opp(curDir)) continue;
|
||||
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body)) continue;
|
||||
|
||||
int dist = Math.Abs(target.x - nx) + Math.Abs(target.y - ny);
|
||||
int space = Fill(nx, ny, body, 260);
|
||||
|
||||
int score = -dist * 6 + space * 2;
|
||||
if (d == desired) score += 25;
|
||||
if (curDir >= 0 && d == curDir) score += 4;
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
void Cmd(int d, string reason, (int x, int y) head, (int x, int y)? target, long targetId)
|
||||
{
|
||||
string nd = DIRS[d];
|
||||
if (nd == lastSentDir && (DateTime.Now - lastCmd).TotalMilliseconds < 220)
|
||||
return;
|
||||
|
||||
int id = BtnForDir(d);
|
||||
if (TryGetBtnPos(id, out int bx, out int by)) Move(bx, by);
|
||||
Send(Out["ClickFurni"], id, 0);
|
||||
|
||||
lastCmd = DateTime.Now;
|
||||
lastSentDir = nd;
|
||||
|
||||
lastAttemptDir = d;
|
||||
lastAttemptAt = DateTime.Now;
|
||||
lastTargetId = targetId;
|
||||
|
||||
if (target.HasValue)
|
||||
Dbg($"{reason} h=({head.x},{head.y}) t=({target.Value.x},{target.Value.y}) d={nd} b={id}", true);
|
||||
else
|
||||
Dbg($"{reason} h=({head.x},{head.y}) d={nd} b={id}", true);
|
||||
}
|
||||
|
||||
Log("Snake Auto (Campaign) v10 started");
|
||||
Log($"target kinds: {string.Join(",", TARGET_KINDS)}");
|
||||
Log($"buttons U={CTRL_UP} D={CTRL_DOWN} L={CTRL_LEFT} R={CTRL_RIGHT}");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(22);
|
||||
CleanupMaps();
|
||||
|
||||
var headItem = GetFloorItem(HEAD_ID);
|
||||
if (headItem == null)
|
||||
{
|
||||
Dbg("head missing");
|
||||
continue;
|
||||
}
|
||||
|
||||
var head = (headItem.Location.X, headItem.Location.Y);
|
||||
if (!InBoard(head.Item1, head.Item2))
|
||||
continue;
|
||||
|
||||
// infer direction from movement
|
||||
if (prevHead.x != -1)
|
||||
{
|
||||
int hdx = head.Item1 - prevHead.x;
|
||||
int hdy = head.Item2 - prevHead.y;
|
||||
if (hdx != 0 || hdy != 0)
|
||||
curDir = DirFromDelta(hdx, hdy);
|
||||
}
|
||||
|
||||
// stuck detection: command sent but head did not move
|
||||
if (prevHead.x == head.Item1 && prevHead.y == head.Item2 &&
|
||||
lastAttemptDir >= 0 && (DateTime.Now - lastAttemptAt).TotalMilliseconds > 420)
|
||||
{
|
||||
Next(head.Item1, head.Item2, lastAttemptDir, out int bx, out int by);
|
||||
tempBlocked[(bx, by)] = DateTime.Now.AddMilliseconds(1800);
|
||||
|
||||
if (lastTargetId != 0)
|
||||
targetBlacklist[lastTargetId] = DateTime.Now.AddMilliseconds(3000);
|
||||
|
||||
Dbg($"stuck: block ({bx},{by}) and skip target {lastTargetId}", true);
|
||||
lastAttemptDir = -1;
|
||||
}
|
||||
|
||||
prevHead = head;
|
||||
|
||||
var body = FloorItems
|
||||
.Where(i => i != null && i.Kind == SNAKE_KIND)
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (i.Location.X, i.Location.Y))
|
||||
.Where(p => !(p.Item1 == head.Item1 && p.Item2 == head.Item2))
|
||||
.ToHashSet();
|
||||
|
||||
var targetsAll = FloorItems
|
||||
.Where(i => i != null && TARGET_KINDS.Contains((int)i.Kind))
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (x: i.Location.X, y: i.Location.Y, id: (long)i.Id, kind: (int)i.Kind))
|
||||
.ToList();
|
||||
|
||||
if (targetsAll.Count == 0)
|
||||
{
|
||||
int safe = Emergency(head, body);
|
||||
Cmd(safe, "no_target_safe", head, null, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
var targets = targetsAll
|
||||
.Where(t => !targetBlacklist.TryGetValue(t.id, out var until) || until <= DateTime.Now)
|
||||
.ToList();
|
||||
|
||||
if (targets.Count == 0)
|
||||
targets = targetsAll;
|
||||
|
||||
var target = targets
|
||||
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
|
||||
.First();
|
||||
|
||||
int dsel = PickDir(head, (target.x, target.y), body);
|
||||
Cmd(dsel, $"seek k={target.kind} id={target.id}", head, (target.x, target.y), target.id);
|
||||
}
|
||||
293
Snake Auto (Campaign) v11.csx
Normal file
293
Snake Auto (Campaign) v11.csx
Normal file
@ -0,0 +1,293 @@
|
||||
/// @name Snake Auto (Campaign) v11
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
const long HEAD_ID = 769407982;
|
||||
const int SNAKE_KIND = 5478;
|
||||
static readonly int[] TARGET_KINDS = { 3666 };
|
||||
|
||||
const int GAME_MIN_X = 20;
|
||||
const int GAME_MAX_X = 65;
|
||||
const int GAME_MIN_Y = 20;
|
||||
const int GAME_MAX_Y = 65;
|
||||
|
||||
// Direction -> plate ids (verified from logs)
|
||||
const int CTRL_UP = 769406820;
|
||||
const int CTRL_DOWN = 769407116;
|
||||
const int CTRL_LEFT = 769407385;
|
||||
const int CTRL_RIGHT = 769407085;
|
||||
|
||||
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
|
||||
|
||||
DateTime lastCmd = DateTime.MinValue;
|
||||
DateTime lastDbg = DateTime.MinValue;
|
||||
int curDir = -1;
|
||||
string lastSentDir = "";
|
||||
(int x, int y) prevHead = (-1, -1);
|
||||
|
||||
int lastCmdDir = -1;
|
||||
(int x, int y) lastCmdHead = (-999, -999);
|
||||
|
||||
int lastAttemptDir = -1;
|
||||
(int x, int y) lastAttemptHead = (-999, -999);
|
||||
DateTime lastAttemptAt = DateTime.MinValue;
|
||||
long lastTargetId = 0;
|
||||
|
||||
Dictionary<(int x, int y), DateTime> tempBlocked = new Dictionary<(int, int), DateTime>();
|
||||
Dictionary<long, DateTime> targetBlacklist = new Dictionary<long, DateTime>();
|
||||
|
||||
HashSet<(int x, int y)> fillVisited = new HashSet<(int, int)>();
|
||||
Queue<(int x, int y)> fillQueue = new Queue<(int, int)>();
|
||||
|
||||
bool InBoard(int x, int y)
|
||||
=> x >= GAME_MIN_X && x <= GAME_MAX_X && y >= GAME_MIN_Y && y <= GAME_MAX_Y;
|
||||
|
||||
void Dbg(string m, bool force = false)
|
||||
{
|
||||
if (!force && (DateTime.Now - lastDbg).TotalMilliseconds < 220) return;
|
||||
Log($"dbg: {m}");
|
||||
lastDbg = DateTime.Now;
|
||||
}
|
||||
|
||||
void Next(int x, int y, int d, out int nx, out int ny)
|
||||
{
|
||||
nx = x + (d == 3 ? 1 : d == 2 ? -1 : 0);
|
||||
ny = y + (d == 1 ? 1 : d == 0 ? -1 : 0);
|
||||
}
|
||||
|
||||
int Opp(int d) => d ^ 1;
|
||||
|
||||
int DirFromDelta(int dx, int dy)
|
||||
{
|
||||
if (Math.Abs(dx) >= Math.Abs(dy)) return dx < 0 ? 2 : 3;
|
||||
return dy < 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
int BtnForDir(int d)
|
||||
=> d == 0 ? CTRL_UP : d == 1 ? CTRL_DOWN : d == 2 ? CTRL_LEFT : CTRL_RIGHT;
|
||||
|
||||
bool TryGetBtnPos(int id, out int x, out int y)
|
||||
{
|
||||
var b = GetFloorItem(id);
|
||||
if (b == null) { x = 0; y = 0; return false; }
|
||||
x = b.Location.X;
|
||||
y = b.Location.Y;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CleanupMaps()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
foreach (var k in tempBlocked.Where(kv => kv.Value <= now).Select(kv => kv.Key).ToList())
|
||||
tempBlocked.Remove(k);
|
||||
|
||||
foreach (var k in targetBlacklist.Where(kv => kv.Value <= now).Select(kv => kv.Key).ToList())
|
||||
targetBlacklist.Remove(k);
|
||||
}
|
||||
|
||||
bool IsTempBlocked(int x, int y)
|
||||
=> tempBlocked.TryGetValue((x, y), out var until) && until > DateTime.Now;
|
||||
|
||||
bool Bad(int x, int y, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
|
||||
{
|
||||
if (!InBoard(x, y)) return true;
|
||||
if (blocks.Contains((x, y))) return true;
|
||||
if (IsTempBlocked(x, y)) return true;
|
||||
return body.Contains((x, y));
|
||||
}
|
||||
|
||||
int Fill(int sx, int sy, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks, int cap)
|
||||
{
|
||||
if (Bad(sx, sy, body, blocks)) return 0;
|
||||
|
||||
fillVisited.Clear();
|
||||
fillQueue.Clear();
|
||||
fillVisited.Add((sx, sy));
|
||||
fillQueue.Enqueue((sx, sy));
|
||||
|
||||
int c = 0;
|
||||
while (fillQueue.Count > 0 && c < cap)
|
||||
{
|
||||
var p = fillQueue.Dequeue();
|
||||
c++;
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Next(p.x, p.y, d, out int nx, out int ny);
|
||||
if (!Bad(nx, ny, body, blocks) && fillVisited.Add((nx, ny)))
|
||||
fillQueue.Enqueue((nx, ny));
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
int Emergency((int x, int y) head, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
|
||||
{
|
||||
int bestDir = curDir >= 0 ? curDir : 0;
|
||||
int bestSpace = -1;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (curDir >= 0 && d == Opp(curDir)) continue;
|
||||
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body, blocks)) continue;
|
||||
|
||||
int space = Fill(nx, ny, body, blocks, 260);
|
||||
if (space > bestSpace)
|
||||
{
|
||||
bestSpace = space;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
int PickDir((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
|
||||
{
|
||||
int desired = DirFromDelta(target.x - head.x, target.y - head.y);
|
||||
var dirs = new List<int> { desired, 0, 1, 2, 3 };
|
||||
|
||||
int bestDir = curDir >= 0 ? curDir : desired;
|
||||
int bestScore = int.MinValue;
|
||||
|
||||
foreach (int d in dirs.Distinct())
|
||||
{
|
||||
if (curDir >= 0 && d == Opp(curDir)) continue;
|
||||
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body, blocks)) continue;
|
||||
|
||||
int dist = Math.Abs(target.x - nx) + Math.Abs(target.y - ny);
|
||||
int space = Fill(nx, ny, body, blocks, 260);
|
||||
|
||||
int score = -dist * 6 + space * 2;
|
||||
if (d == desired) score += 25;
|
||||
if (curDir >= 0 && d == curDir) score += 4;
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
void Cmd(int d, string reason, (int x, int y) head, (int x, int y)? target, long targetId)
|
||||
{
|
||||
string nd = DIRS[d];
|
||||
|
||||
if (d == lastCmdDir && head == lastCmdHead && (DateTime.Now - lastCmd).TotalMilliseconds < 450)
|
||||
return;
|
||||
|
||||
if (nd == lastSentDir && (DateTime.Now - lastCmd).TotalMilliseconds < 180)
|
||||
return;
|
||||
|
||||
int id = BtnForDir(d);
|
||||
if (TryGetBtnPos(id, out int bx, out int by)) Move(bx, by);
|
||||
Send(Out["ClickFurni"], id, 0);
|
||||
|
||||
lastCmd = DateTime.Now;
|
||||
lastSentDir = nd;
|
||||
lastCmdDir = d;
|
||||
lastCmdHead = head;
|
||||
|
||||
lastAttemptDir = d;
|
||||
lastAttemptHead = head;
|
||||
lastAttemptAt = DateTime.Now;
|
||||
lastTargetId = targetId;
|
||||
|
||||
if (target.HasValue)
|
||||
Dbg($"{reason} h=({head.x},{head.y}) t=({target.Value.x},{target.Value.y}) d={nd} b={id}", true);
|
||||
else
|
||||
Dbg($"{reason} h=({head.x},{head.y}) d={nd} b={id}", true);
|
||||
}
|
||||
|
||||
Log("Snake Auto (Campaign) v11 started");
|
||||
Log($"target kinds: {string.Join(",", TARGET_KINDS)}");
|
||||
Log($"buttons U={CTRL_UP} D={CTRL_DOWN} L={CTRL_LEFT} R={CTRL_RIGHT}");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(24);
|
||||
CleanupMaps();
|
||||
|
||||
var headItem = GetFloorItem(HEAD_ID);
|
||||
if (headItem == null)
|
||||
{
|
||||
Dbg("head missing");
|
||||
continue;
|
||||
}
|
||||
|
||||
var head = (headItem.Location.X, headItem.Location.Y);
|
||||
if (!InBoard(head.Item1, head.Item2))
|
||||
continue;
|
||||
|
||||
if (prevHead.x != -1)
|
||||
{
|
||||
int hdx = head.Item1 - prevHead.x;
|
||||
int hdy = head.Item2 - prevHead.y;
|
||||
if (hdx != 0 || hdy != 0)
|
||||
curDir = DirFromDelta(hdx, hdy);
|
||||
}
|
||||
|
||||
// stuck detection
|
||||
if (head == lastAttemptHead && lastAttemptDir >= 0 && (DateTime.Now - lastAttemptAt).TotalMilliseconds > 520)
|
||||
{
|
||||
Next(head.Item1, head.Item2, lastAttemptDir, out int bx, out int by);
|
||||
tempBlocked[(bx, by)] = DateTime.Now.AddMilliseconds(2200);
|
||||
|
||||
if (lastTargetId != 0)
|
||||
targetBlacklist[lastTargetId] = DateTime.Now.AddMilliseconds(3200);
|
||||
|
||||
Dbg($"stuck: block ({bx},{by}) skip target {lastTargetId}", true);
|
||||
lastAttemptDir = -1;
|
||||
}
|
||||
|
||||
prevHead = head;
|
||||
|
||||
var blocks = FloorItems
|
||||
.Where(i => i != null && i.Kind == 9109)
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (i.Location.X, i.Location.Y))
|
||||
.ToHashSet();
|
||||
|
||||
var body = FloorItems
|
||||
.Where(i => i != null && i.Kind == SNAKE_KIND)
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (i.Location.X, i.Location.Y))
|
||||
.Where(p => !(p.Item1 == head.Item1 && p.Item2 == head.Item2))
|
||||
.ToHashSet();
|
||||
|
||||
var targetsAll = FloorItems
|
||||
.Where(i => i != null && TARGET_KINDS.Contains((int)i.Kind))
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Where(i => !blocks.Contains((i.Location.X, i.Location.Y)))
|
||||
.Select(i => (x: i.Location.X, y: i.Location.Y, id: (long)i.Id, kind: (int)i.Kind))
|
||||
.ToList();
|
||||
|
||||
if (targetsAll.Count == 0)
|
||||
{
|
||||
int safe = Emergency(head, body, blocks);
|
||||
Cmd(safe, "no_target_safe", head, null, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
var targets = targetsAll
|
||||
.Where(t => !targetBlacklist.TryGetValue(t.id, out var until) || until <= DateTime.Now)
|
||||
.ToList();
|
||||
|
||||
if (targets.Count == 0)
|
||||
targets = targetsAll;
|
||||
|
||||
var target = targets
|
||||
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
|
||||
.First();
|
||||
|
||||
int dsel = PickDir(head, (target.x, target.y), body, blocks);
|
||||
Cmd(dsel, $"seek k={target.kind} id={target.id}", head, (target.x, target.y), target.id);
|
||||
}
|
||||
285
Snake Auto (Campaign) v12.csx
Normal file
285
Snake Auto (Campaign) v12.csx
Normal file
@ -0,0 +1,285 @@
|
||||
/// @name Snake Auto (Campaign) v12
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
const long HEAD_ID = 769407982;
|
||||
const int SNAKE_KIND = 5478;
|
||||
static readonly int[] TARGET_KINDS = { 6559, 3666 };
|
||||
|
||||
const int GAME_MIN_X = 20;
|
||||
const int GAME_MAX_X = 65;
|
||||
const int GAME_MIN_Y = 20;
|
||||
const int GAME_MAX_Y = 65;
|
||||
|
||||
// Direction -> plate ids
|
||||
const int CTRL_UP = 769406820;
|
||||
const int CTRL_DOWN = 769407116;
|
||||
const int CTRL_LEFT = 769407385;
|
||||
const int CTRL_RIGHT = 769407085;
|
||||
|
||||
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
|
||||
|
||||
DateTime lastCmd = DateTime.MinValue;
|
||||
DateTime lastDbg = DateTime.MinValue;
|
||||
string lastSentDir = "";
|
||||
int curDir = -1;
|
||||
(int x, int y) prevHead = (-1, -1);
|
||||
int lastCmdDir = -1;
|
||||
(int x, int y) lastCmdHead = (-999, -999);
|
||||
|
||||
int lastAttemptDir = -1;
|
||||
(int x, int y) lastAttemptHead = (-999, -999);
|
||||
DateTime lastAttemptAt = DateTime.MinValue;
|
||||
long lastTargetId = 0;
|
||||
|
||||
Dictionary<(int x, int y), DateTime> tempBlocked = new Dictionary<(int, int), DateTime>();
|
||||
Dictionary<long, DateTime> targetBlacklist = new Dictionary<long, DateTime>();
|
||||
|
||||
HashSet<(int x, int y)> fillVisited = new HashSet<(int, int)>();
|
||||
Queue<(int x, int y)> fillQueue = new Queue<(int, int)>();
|
||||
|
||||
bool InBoard(int x, int y)
|
||||
=> x >= GAME_MIN_X && x <= GAME_MAX_X && y >= GAME_MIN_Y && y <= GAME_MAX_Y;
|
||||
|
||||
void Dbg(string m, bool force = false)
|
||||
{
|
||||
if (!force && (DateTime.Now - lastDbg).TotalMilliseconds < 220) return;
|
||||
Log($"dbg: {m}");
|
||||
lastDbg = DateTime.Now;
|
||||
}
|
||||
|
||||
void Next(int x, int y, int d, out int nx, out int ny)
|
||||
{
|
||||
nx = x + (d == 3 ? 1 : d == 2 ? -1 : 0);
|
||||
ny = y + (d == 1 ? 1 : d == 0 ? -1 : 0);
|
||||
}
|
||||
|
||||
int Opp(int d) => d ^ 1;
|
||||
|
||||
int DirFromDelta(int dx, int dy)
|
||||
{
|
||||
if (Math.Abs(dx) >= Math.Abs(dy)) return dx < 0 ? 2 : 3;
|
||||
return dy < 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
int BtnForDir(int d)
|
||||
=> d == 0 ? CTRL_UP : d == 1 ? CTRL_DOWN : d == 2 ? CTRL_LEFT : CTRL_RIGHT;
|
||||
|
||||
bool TryGetBtnPos(int id, out int x, out int y)
|
||||
{
|
||||
var b = GetFloorItem(id);
|
||||
if (b == null) { x = 0; y = 0; return false; }
|
||||
x = b.Location.X;
|
||||
y = b.Location.Y;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CleanupMaps()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
foreach (var k in tempBlocked.Where(kv => kv.Value <= now).Select(kv => kv.Key).ToList())
|
||||
tempBlocked.Remove(k);
|
||||
foreach (var k in targetBlacklist.Where(kv => kv.Value <= now).Select(kv => kv.Key).ToList())
|
||||
targetBlacklist.Remove(k);
|
||||
}
|
||||
|
||||
bool IsTempBlocked(int x, int y)
|
||||
=> tempBlocked.TryGetValue((x, y), out var until) && until > DateTime.Now;
|
||||
|
||||
bool Bad(int x, int y, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
|
||||
{
|
||||
if (!InBoard(x, y)) return true;
|
||||
if (blocks.Contains((x, y))) return true;
|
||||
if (IsTempBlocked(x, y)) return true;
|
||||
return body.Contains((x, y));
|
||||
}
|
||||
|
||||
int Fill(int sx, int sy, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks, int cap)
|
||||
{
|
||||
if (Bad(sx, sy, body, blocks)) return 0;
|
||||
|
||||
fillVisited.Clear();
|
||||
fillQueue.Clear();
|
||||
fillVisited.Add((sx, sy));
|
||||
fillQueue.Enqueue((sx, sy));
|
||||
|
||||
int c = 0;
|
||||
while (fillQueue.Count > 0 && c < cap)
|
||||
{
|
||||
var p = fillQueue.Dequeue();
|
||||
c++;
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Next(p.x, p.y, d, out int nx, out int ny);
|
||||
if (!Bad(nx, ny, body, blocks) && fillVisited.Add((nx, ny)))
|
||||
fillQueue.Enqueue((nx, ny));
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
int Emergency((int x, int y) head, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
|
||||
{
|
||||
int bestDir = curDir >= 0 ? curDir : 0;
|
||||
int bestSpace = -1;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (curDir >= 0 && d == Opp(curDir)) continue;
|
||||
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body, blocks)) continue;
|
||||
|
||||
int space = Fill(nx, ny, body, blocks, 260);
|
||||
if (space > bestSpace)
|
||||
{
|
||||
bestSpace = space;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
int PickDir((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
|
||||
{
|
||||
int desired = DirFromDelta(target.x - head.x, target.y - head.y);
|
||||
var dirs = new List<int> { desired, 0, 1, 2, 3 };
|
||||
|
||||
int bestDir = curDir >= 0 ? curDir : desired;
|
||||
int bestScore = int.MinValue;
|
||||
|
||||
foreach (int d in dirs.Distinct())
|
||||
{
|
||||
if (curDir >= 0 && d == Opp(curDir)) continue;
|
||||
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body, blocks)) continue;
|
||||
|
||||
int dist = Math.Abs(target.x - nx) + Math.Abs(target.y - ny);
|
||||
int space = Fill(nx, ny, body, blocks, 260);
|
||||
|
||||
int score = -dist * 6 + space * 2;
|
||||
if (d == desired) score += 25;
|
||||
if (curDir >= 0 && d == curDir) score += 4;
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
void Cmd(int d, string reason, (int x, int y) head, (int x, int y)? target, long targetId)
|
||||
{
|
||||
string nd = DIRS[d];
|
||||
|
||||
if (d == lastCmdDir && head == lastCmdHead && (DateTime.Now - lastCmd).TotalMilliseconds < 450)
|
||||
return;
|
||||
|
||||
if (nd == lastSentDir && (DateTime.Now - lastCmd).TotalMilliseconds < 180)
|
||||
return;
|
||||
|
||||
int id = BtnForDir(d);
|
||||
if (TryGetBtnPos(id, out int bx, out int by)) Move(bx, by);
|
||||
Send(Out["ClickFurni"], id, 0);
|
||||
|
||||
lastCmd = DateTime.Now;
|
||||
lastSentDir = nd;
|
||||
lastCmdDir = d;
|
||||
lastCmdHead = head;
|
||||
|
||||
lastAttemptDir = d;
|
||||
lastAttemptHead = head;
|
||||
lastAttemptAt = DateTime.Now;
|
||||
lastTargetId = targetId;
|
||||
|
||||
if (target.HasValue)
|
||||
Dbg($"{reason} h=({head.x},{head.y}) t=({target.Value.x},{target.Value.y}) d={nd} b={id}", true);
|
||||
else
|
||||
Dbg($"{reason} h=({head.x},{head.y}) d={nd} b={id}", true);
|
||||
}
|
||||
|
||||
Log("Snake Auto (Campaign) v12 started");
|
||||
Log($"target kinds: {string.Join(",", TARGET_KINDS)}");
|
||||
Log($"buttons U={CTRL_UP} D={CTRL_DOWN} L={CTRL_LEFT} R={CTRL_RIGHT}");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(24);
|
||||
CleanupMaps();
|
||||
|
||||
var headItem = GetFloorItem(HEAD_ID);
|
||||
if (headItem == null)
|
||||
{
|
||||
Dbg("head missing");
|
||||
continue;
|
||||
}
|
||||
|
||||
var head = (headItem.Location.X, headItem.Location.Y);
|
||||
if (!InBoard(head.Item1, head.Item2))
|
||||
continue;
|
||||
|
||||
if (prevHead.x != -1)
|
||||
{
|
||||
int hdx = head.Item1 - prevHead.x;
|
||||
int hdy = head.Item2 - prevHead.y;
|
||||
if (hdx != 0 || hdy != 0)
|
||||
curDir = DirFromDelta(hdx, hdy);
|
||||
}
|
||||
|
||||
if (head == lastAttemptHead && lastAttemptDir >= 0 && (DateTime.Now - lastAttemptAt).TotalMilliseconds > 520)
|
||||
{
|
||||
Next(head.Item1, head.Item2, lastAttemptDir, out int bx, out int by);
|
||||
tempBlocked[(bx, by)] = DateTime.Now.AddMilliseconds(2200);
|
||||
if (lastTargetId != 0) targetBlacklist[lastTargetId] = DateTime.Now.AddMilliseconds(3200);
|
||||
Dbg($"stuck: block ({bx},{by}) skip target {lastTargetId}", true);
|
||||
lastAttemptDir = -1;
|
||||
}
|
||||
|
||||
prevHead = head;
|
||||
|
||||
var blocks = FloorItems
|
||||
.Where(i => i != null && i.Kind == 9109)
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (i.Location.X, i.Location.Y))
|
||||
.ToHashSet();
|
||||
|
||||
var body = FloorItems
|
||||
.Where(i => i != null && i.Kind == SNAKE_KIND)
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (i.Location.X, i.Location.Y))
|
||||
.Where(p => !(p.Item1 == head.Item1 && p.Item2 == head.Item2))
|
||||
.ToHashSet();
|
||||
|
||||
var targetsAll = FloorItems
|
||||
.Where(i => i != null && TARGET_KINDS.Contains((int)i.Kind))
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Where(i => !blocks.Contains((i.Location.X, i.Location.Y)))
|
||||
.Select(i => (x: i.Location.X, y: i.Location.Y, id: (long)i.Id, kind: (int)i.Kind))
|
||||
.ToList();
|
||||
|
||||
if (targetsAll.Count == 0)
|
||||
{
|
||||
int safe = Emergency(head, body, blocks);
|
||||
Cmd(safe, "no_target_safe", head, null, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
var targets = targetsAll
|
||||
.Where(t => !targetBlacklist.TryGetValue(t.id, out var until) || until <= DateTime.Now)
|
||||
.ToList();
|
||||
|
||||
if (targets.Count == 0)
|
||||
targets = targetsAll;
|
||||
|
||||
var target = targets
|
||||
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
|
||||
.First();
|
||||
|
||||
int dsel = PickDir(head, (target.x, target.y), body, blocks);
|
||||
Cmd(dsel, $"seek k={target.kind} id={target.id}", head, (target.x, target.y), target.id);
|
||||
}
|
||||
287
Snake Auto (Campaign) v13.csx
Normal file
287
Snake Auto (Campaign) v13.csx
Normal file
@ -0,0 +1,287 @@
|
||||
/// @name Snake Auto (Campaign) v13
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
const long HEAD_ID = 769407982;
|
||||
const int SNAKE_KIND = 5478;
|
||||
static readonly int[] TARGET_KINDS = { 6559, 3666 };
|
||||
|
||||
const int GAME_MIN_X = 20;
|
||||
const int GAME_MAX_X = 65;
|
||||
const int GAME_MIN_Y = 20;
|
||||
const int GAME_MAX_Y = 65;
|
||||
|
||||
const int CTRL_UP = 769406820;
|
||||
const int CTRL_DOWN = 769407116;
|
||||
const int CTRL_LEFT = 769407385;
|
||||
const int CTRL_RIGHT = 769407085;
|
||||
|
||||
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
|
||||
|
||||
DateTime lastCmd = DateTime.MinValue;
|
||||
DateTime lastDbg = DateTime.MinValue;
|
||||
int curDir = -1;
|
||||
string lastSentDir = "";
|
||||
(int x, int y) prevHead = (-1, -1);
|
||||
|
||||
int lastCmdDir = -1;
|
||||
(int x, int y) lastCmdHead = (-999, -999);
|
||||
|
||||
int lastAttemptDir = -1;
|
||||
(int x, int y) lastAttemptHead = (-999, -999);
|
||||
DateTime lastAttemptAt = DateTime.MinValue;
|
||||
long lastTargetId = 0;
|
||||
|
||||
Dictionary<(int x, int y), DateTime> tempBlocked = new Dictionary<(int, int), DateTime>();
|
||||
Dictionary<long, DateTime> targetBlacklist = new Dictionary<long, DateTime>();
|
||||
|
||||
HashSet<(int x, int y)> fillVisited = new HashSet<(int, int)>();
|
||||
Queue<(int x, int y)> fillQueue = new Queue<(int, int)>();
|
||||
|
||||
bool InBoard(int x, int y)
|
||||
=> x >= GAME_MIN_X && x <= GAME_MAX_X && y >= GAME_MIN_Y && y <= GAME_MAX_Y;
|
||||
|
||||
void Dbg(string m, bool force = false)
|
||||
{
|
||||
if (!force && (DateTime.Now - lastDbg).TotalMilliseconds < 220) return;
|
||||
Log($"dbg: {m}");
|
||||
lastDbg = DateTime.Now;
|
||||
}
|
||||
|
||||
void Next(int x, int y, int d, out int nx, out int ny)
|
||||
{
|
||||
nx = x + (d == 3 ? 1 : d == 2 ? -1 : 0);
|
||||
ny = y + (d == 1 ? 1 : d == 0 ? -1 : 0);
|
||||
}
|
||||
|
||||
int Opp(int d) => d ^ 1;
|
||||
|
||||
int DirFromDelta(int dx, int dy)
|
||||
{
|
||||
if (Math.Abs(dx) >= Math.Abs(dy)) return dx < 0 ? 2 : 3;
|
||||
return dy < 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
int BtnForDir(int d)
|
||||
=> d == 0 ? CTRL_UP : d == 1 ? CTRL_DOWN : d == 2 ? CTRL_LEFT : CTRL_RIGHT;
|
||||
|
||||
bool TryGetBtnPos(int id, out int x, out int y)
|
||||
{
|
||||
var b = GetFloorItem(id);
|
||||
if (b == null) { x = 0; y = 0; return false; }
|
||||
x = b.Location.X;
|
||||
y = b.Location.Y;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CleanupMaps()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
foreach (var k in tempBlocked.Where(kv => kv.Value <= now).Select(kv => kv.Key).ToList())
|
||||
tempBlocked.Remove(k);
|
||||
foreach (var k in targetBlacklist.Where(kv => kv.Value <= now).Select(kv => kv.Key).ToList())
|
||||
targetBlacklist.Remove(k);
|
||||
}
|
||||
|
||||
bool IsTempBlocked(int x, int y)
|
||||
=> tempBlocked.TryGetValue((x, y), out var until) && until > DateTime.Now;
|
||||
|
||||
bool Bad(int x, int y, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
|
||||
{
|
||||
if (!InBoard(x, y)) return true;
|
||||
if (blocks.Contains((x, y))) return true;
|
||||
if (IsTempBlocked(x, y)) return true;
|
||||
return body.Contains((x, y));
|
||||
}
|
||||
|
||||
int Fill(int sx, int sy, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks, int cap)
|
||||
{
|
||||
if (Bad(sx, sy, body, blocks)) return 0;
|
||||
|
||||
fillVisited.Clear();
|
||||
fillQueue.Clear();
|
||||
fillVisited.Add((sx, sy));
|
||||
fillQueue.Enqueue((sx, sy));
|
||||
|
||||
int c = 0;
|
||||
while (fillQueue.Count > 0 && c < cap)
|
||||
{
|
||||
var p = fillQueue.Dequeue();
|
||||
c++;
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Next(p.x, p.y, d, out int nx, out int ny);
|
||||
if (!Bad(nx, ny, body, blocks) && fillVisited.Add((nx, ny)))
|
||||
fillQueue.Enqueue((nx, ny));
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
int Emergency((int x, int y) head, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
|
||||
{
|
||||
int bestDir = curDir >= 0 ? curDir : 0;
|
||||
int bestSpace = -1;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (curDir >= 0 && d == Opp(curDir)) continue;
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body, blocks)) continue;
|
||||
|
||||
int space = Fill(nx, ny, body, blocks, 260);
|
||||
if (space > bestSpace)
|
||||
{
|
||||
bestSpace = space;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
int PickDir((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body, HashSet<(int x, int y)> blocks)
|
||||
{
|
||||
int desired = DirFromDelta(target.x - head.x, target.y - head.y);
|
||||
var dirs = new List<int> { desired, 0, 1, 2, 3 };
|
||||
|
||||
int bestDir = curDir >= 0 ? curDir : desired;
|
||||
int bestScore = int.MinValue;
|
||||
|
||||
foreach (int d in dirs.Distinct())
|
||||
{
|
||||
if (curDir >= 0 && d == Opp(curDir)) continue;
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body, blocks)) continue;
|
||||
|
||||
int dist = Math.Abs(target.x - nx) + Math.Abs(target.y - ny);
|
||||
int space = Fill(nx, ny, body, blocks, 260);
|
||||
|
||||
int score = -dist * 6 + space * 2;
|
||||
if (d == desired) score += 25;
|
||||
if (curDir >= 0 && d == curDir) score += 4;
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
void Cmd(int d, string reason, (int x, int y) head, (int x, int y)? target, long targetId)
|
||||
{
|
||||
string nd = DIRS[d];
|
||||
|
||||
if (d == lastCmdDir && head == lastCmdHead && (DateTime.Now - lastCmd).TotalMilliseconds < 500)
|
||||
return;
|
||||
|
||||
if (nd == lastSentDir && (DateTime.Now - lastCmd).TotalMilliseconds < 200)
|
||||
return;
|
||||
|
||||
int id = BtnForDir(d);
|
||||
if (TryGetBtnPos(id, out int bx, out int by)) Move(bx, by);
|
||||
Send(Out["ClickFurni"], id, 0);
|
||||
|
||||
lastCmd = DateTime.Now;
|
||||
lastSentDir = nd;
|
||||
lastCmdDir = d;
|
||||
lastCmdHead = head;
|
||||
|
||||
// Do NOT reset attempt timer for repeated same head+dir.
|
||||
if (lastAttemptDir != d || lastAttemptHead != head)
|
||||
{
|
||||
lastAttemptDir = d;
|
||||
lastAttemptHead = head;
|
||||
lastAttemptAt = DateTime.Now;
|
||||
lastTargetId = targetId;
|
||||
}
|
||||
|
||||
if (target.HasValue)
|
||||
Dbg($"{reason} h=({head.x},{head.y}) t=({target.Value.x},{target.Value.y}) d={nd} b={id}", true);
|
||||
else
|
||||
Dbg($"{reason} h=({head.x},{head.y}) d={nd} b={id}", true);
|
||||
}
|
||||
|
||||
Log("Snake Auto (Campaign) v13 started");
|
||||
Log($"target kinds: {string.Join(",", TARGET_KINDS)}");
|
||||
Log($"buttons U={CTRL_UP} D={CTRL_DOWN} L={CTRL_LEFT} R={CTRL_RIGHT}");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(24);
|
||||
CleanupMaps();
|
||||
|
||||
var headItem = GetFloorItem(HEAD_ID);
|
||||
if (headItem == null)
|
||||
{
|
||||
Dbg("head missing");
|
||||
continue;
|
||||
}
|
||||
|
||||
var head = (headItem.Location.X, headItem.Location.Y);
|
||||
if (!InBoard(head.Item1, head.Item2))
|
||||
continue;
|
||||
|
||||
if (prevHead.x != -1)
|
||||
{
|
||||
int hdx = head.Item1 - prevHead.x;
|
||||
int hdy = head.Item2 - prevHead.y;
|
||||
if (hdx != 0 || hdy != 0)
|
||||
curDir = DirFromDelta(hdx, hdy);
|
||||
}
|
||||
|
||||
// Stuck: head did not move after attempted direction.
|
||||
if (head == lastAttemptHead && lastAttemptDir >= 0 && (DateTime.Now - lastAttemptAt).TotalMilliseconds > 560)
|
||||
{
|
||||
Next(head.Item1, head.Item2, lastAttemptDir, out int bx, out int by);
|
||||
tempBlocked[(bx, by)] = DateTime.Now.AddMilliseconds(2500);
|
||||
if (lastTargetId != 0) targetBlacklist[lastTargetId] = DateTime.Now.AddMilliseconds(3000);
|
||||
Dbg($"stuck: blocked ({bx},{by}), blacklisted target {lastTargetId}", true);
|
||||
|
||||
lastAttemptDir = -1;
|
||||
}
|
||||
|
||||
prevHead = head;
|
||||
|
||||
var blocks = FloorItems
|
||||
.Where(i => i != null && i.Kind == 9109)
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (i.Location.X, i.Location.Y))
|
||||
.ToHashSet();
|
||||
|
||||
var body = FloorItems
|
||||
.Where(i => i != null && i.Kind == SNAKE_KIND)
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (i.Location.X, i.Location.Y))
|
||||
.Where(p => !(p.Item1 == head.Item1 && p.Item2 == head.Item2))
|
||||
.ToHashSet();
|
||||
|
||||
var targetsAll = FloorItems
|
||||
.Where(i => i != null && TARGET_KINDS.Contains((int)i.Kind))
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Where(i => !blocks.Contains((i.Location.X, i.Location.Y)))
|
||||
.Select(i => (x: i.Location.X, y: i.Location.Y, id: (long)i.Id, kind: (int)i.Kind))
|
||||
.ToList();
|
||||
|
||||
if (targetsAll.Count == 0)
|
||||
{
|
||||
int safe = Emergency(head, body, blocks);
|
||||
Cmd(safe, "no_target_safe", head, null, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
var targets = targetsAll
|
||||
.Where(t => !targetBlacklist.TryGetValue(t.id, out var until) || until <= DateTime.Now)
|
||||
.ToList();
|
||||
if (targets.Count == 0) targets = targetsAll;
|
||||
|
||||
var target = targets
|
||||
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
|
||||
.First();
|
||||
|
||||
int dsel = PickDir(head, (target.x, target.y), body, blocks);
|
||||
Cmd(dsel, $"seek k={target.kind} id={target.id}", head, (target.x, target.y), target.id);
|
||||
}
|
||||
282
Snake Auto (Campaign) v4.csx
Normal file
282
Snake Auto (Campaign) v4.csx
Normal file
@ -0,0 +1,282 @@
|
||||
/// @name Snake Auto (Campaign) v4
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
const long HEAD_ID = 769407982;
|
||||
const int TARGET_KIND = 3666;
|
||||
const int SNAKE_KIND = 5478;
|
||||
|
||||
const int GAME_MIN_X = 37;
|
||||
const int GAME_MAX_X = 59;
|
||||
const int GAME_MIN_Y = 29;
|
||||
const int GAME_MAX_Y = 51;
|
||||
|
||||
// Confirmed controls
|
||||
const int CTRL_UP = 769407385;
|
||||
const int CTRL_RIGHT = 769406820;
|
||||
const int CTRL_DOWN = 769407085;
|
||||
const int CTRL_LEFT = 769407116;
|
||||
|
||||
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
|
||||
|
||||
DateTime lastCmd = DateTime.MinValue;
|
||||
DateTime lastInfo = DateTime.MinValue;
|
||||
string dir = "UP";
|
||||
string lastSentDir = "";
|
||||
(int x, int y) prevHead = (-1, -1);
|
||||
|
||||
HashSet<(int x, int y)> fillVisited = new HashSet<(int, int)>();
|
||||
Queue<(int x, int y)> fillQueue = new Queue<(int, int)>();
|
||||
HashSet<(int x, int y)> pathVisited = new HashSet<(int, int)>();
|
||||
Queue<((int x, int y) pos, int dist)> pathQueue = new Queue<((int, int), int)>();
|
||||
|
||||
bool InBoard(int x, int y)
|
||||
=> x >= GAME_MIN_X && x <= GAME_MAX_X && y >= GAME_MIN_Y && y <= GAME_MAX_Y;
|
||||
|
||||
bool IsInQueueArea()
|
||||
{
|
||||
if (Self == null) return false;
|
||||
return Self.Location.X == 59 && Self.Location.Y >= 29 && Self.Location.Y <= 51;
|
||||
}
|
||||
|
||||
void Next(int x, int y, int d, out int nx, out int ny)
|
||||
{
|
||||
nx = x + (d == 3 ? 1 : d == 2 ? -1 : 0);
|
||||
ny = y + (d == 1 ? 1 : d == 0 ? -1 : 0);
|
||||
}
|
||||
|
||||
int Opp(int d) => d ^ 1;
|
||||
int DirToIdx(string d) => d == "UP" ? 0 : d == "DOWN" ? 1 : d == "LEFT" ? 2 : 3;
|
||||
|
||||
int IdForDir(int d)
|
||||
=> d == 0 ? CTRL_UP : d == 1 ? CTRL_DOWN : d == 2 ? CTRL_LEFT : CTRL_RIGHT;
|
||||
|
||||
bool TryGetButtonPos(int id, out int x, out int y)
|
||||
{
|
||||
var b = GetFloorItem(id);
|
||||
if (b == null)
|
||||
{
|
||||
x = 0;
|
||||
y = 0;
|
||||
return false;
|
||||
}
|
||||
x = b.Location.X;
|
||||
y = b.Location.Y;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Cmd(int d)
|
||||
{
|
||||
string nd = DIRS[d];
|
||||
if (nd == lastSentDir && (DateTime.Now - lastCmd).TotalMilliseconds < 120) return;
|
||||
|
||||
int id = IdForDir(d);
|
||||
if (TryGetButtonPos(id, out int bx, out int by))
|
||||
Move(bx, by);
|
||||
|
||||
Send(Out["ClickFurni"], id, 0);
|
||||
|
||||
lastCmd = DateTime.Now;
|
||||
lastSentDir = nd;
|
||||
dir = nd;
|
||||
}
|
||||
|
||||
bool Bad(int x, int y, HashSet<(int x, int y)> body)
|
||||
{
|
||||
if (!InBoard(x, y)) return true;
|
||||
return body.Contains((x, y));
|
||||
}
|
||||
|
||||
int Fill(int sx, int sy, HashSet<(int x, int y)> body, int cap)
|
||||
{
|
||||
if (Bad(sx, sy, body)) return 0;
|
||||
|
||||
fillVisited.Clear();
|
||||
fillQueue.Clear();
|
||||
|
||||
fillVisited.Add((sx, sy));
|
||||
fillQueue.Enqueue((sx, sy));
|
||||
int c = 0;
|
||||
|
||||
while (fillQueue.Count > 0 && c < cap)
|
||||
{
|
||||
var p = fillQueue.Dequeue();
|
||||
c++;
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Next(p.x, p.y, d, out int nx, out int ny);
|
||||
if (!Bad(nx, ny, body) && fillVisited.Add((nx, ny)))
|
||||
fillQueue.Enqueue((nx, ny));
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
int Dist(int sx, int sy, int tx, int ty, HashSet<(int x, int y)> body, int cap)
|
||||
{
|
||||
if (sx == tx && sy == ty) return 0;
|
||||
|
||||
pathVisited.Clear();
|
||||
pathQueue.Clear();
|
||||
|
||||
pathVisited.Add((sx, sy));
|
||||
pathQueue.Enqueue(((sx, sy), 0));
|
||||
|
||||
while (pathQueue.Count > 0)
|
||||
{
|
||||
var cur = pathQueue.Dequeue();
|
||||
if (cur.dist >= cap) return 999;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Next(cur.pos.x, cur.pos.y, d, out int nx, out int ny);
|
||||
if (nx == tx && ny == ty) return cur.dist + 1;
|
||||
if (!Bad(nx, ny, body) && pathVisited.Add((nx, ny)))
|
||||
pathQueue.Enqueue(((nx, ny), cur.dist + 1));
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Decide((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body, int curDir)
|
||||
{
|
||||
int bestDir = curDir;
|
||||
int bestScore = int.MinValue;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (d == Opp(curDir)) continue;
|
||||
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body)) continue;
|
||||
|
||||
int space = Fill(nx, ny, body, 180);
|
||||
if (space < 8) continue;
|
||||
|
||||
int dist = Dist(nx, ny, target.x, target.y, body, 180);
|
||||
|
||||
int score = 0;
|
||||
if (nx == target.x && ny == target.y) score += 10000;
|
||||
if (dist > 0 && dist < 999) score += (220 - dist) * 7;
|
||||
else if (dist == -1) score -= 200;
|
||||
|
||||
score += Math.Min(space, 100) * 4;
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
int Emergency((int x, int y) head, HashSet<(int x, int y)> body, int curDir)
|
||||
{
|
||||
int bestDir = curDir;
|
||||
int bestSpace = -1;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (d == Opp(curDir)) continue;
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body)) continue;
|
||||
|
||||
int space = Fill(nx, ny, body, 100);
|
||||
if (space > bestSpace)
|
||||
{
|
||||
bestSpace = space;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
Log("Snake Auto (Campaign) v4 started");
|
||||
Log($"Buttons: U={CTRL_UP} R={CTRL_RIGHT} D={CTRL_DOWN} L={CTRL_LEFT}");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(20);
|
||||
|
||||
if (IsInQueueArea())
|
||||
{
|
||||
if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500)
|
||||
{
|
||||
Log("waiting: in queue");
|
||||
lastInfo = DateTime.Now;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var headItem = GetFloorItem(HEAD_ID);
|
||||
if (headItem == null)
|
||||
{
|
||||
if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500)
|
||||
{
|
||||
Log("waiting: head not visible");
|
||||
lastInfo = DateTime.Now;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var head = (headItem.Location.X, headItem.Location.Y);
|
||||
if (!InBoard(head.Item1, head.Item2)) continue;
|
||||
|
||||
// Infer current movement direction from head delta.
|
||||
if (prevHead.x != -1)
|
||||
{
|
||||
int dx = head.Item1 - prevHead.x;
|
||||
int dy = head.Item2 - prevHead.y;
|
||||
if (dx != 0 || dy != 0)
|
||||
dir = Math.Abs(dx) >= Math.Abs(dy)
|
||||
? (dx < 0 ? "LEFT" : "RIGHT")
|
||||
: (dy < 0 ? "UP" : "DOWN");
|
||||
}
|
||||
prevHead = head;
|
||||
|
||||
// Body = all snake tiles in board except current head position.
|
||||
var body = FloorItems
|
||||
.Where(i => i != null && i.Kind == SNAKE_KIND)
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (i.Location.X, i.Location.Y))
|
||||
.Where(p => !(p.Item1 == head.Item1 && p.Item2 == head.Item2))
|
||||
.ToHashSet();
|
||||
|
||||
var targets = FloorItems
|
||||
.Where(i => i != null && i.Kind == TARGET_KIND)
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (x: i.Location.X, y: i.Location.Y, state: (int)i.State))
|
||||
.ToList();
|
||||
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500)
|
||||
{
|
||||
Log("waiting: no color tile in board");
|
||||
lastInfo = DateTime.Now;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var target = targets
|
||||
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
|
||||
.ThenBy(t => t.state)
|
||||
.First();
|
||||
|
||||
int curDir = DirToIdx(dir);
|
||||
|
||||
// Never force reverse into body.
|
||||
Next(head.Item1, head.Item2, curDir, out int fx, out int fy);
|
||||
if (Bad(fx, fy, body))
|
||||
{
|
||||
int e = Emergency(head, body, curDir);
|
||||
Cmd(e);
|
||||
continue;
|
||||
}
|
||||
|
||||
int best = Decide(head, (target.x, target.y), body, curDir);
|
||||
Cmd(best);
|
||||
}
|
||||
269
Snake Auto (Campaign) v5.csx
Normal file
269
Snake Auto (Campaign) v5.csx
Normal file
@ -0,0 +1,269 @@
|
||||
/// @name Snake Auto (Campaign) v5
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
const long HEAD_ID = 769407982;
|
||||
const int TARGET_KIND = 3666;
|
||||
const int SNAKE_KIND = 5478;
|
||||
|
||||
const int GAME_MIN_X = 37;
|
||||
const int GAME_MAX_X = 59;
|
||||
const int GAME_MIN_Y = 29;
|
||||
const int GAME_MAX_Y = 51;
|
||||
|
||||
// Confirmed controls
|
||||
const int CTRL_UP = 769407385;
|
||||
const int CTRL_RIGHT = 769406820;
|
||||
const int CTRL_DOWN = 769407085;
|
||||
const int CTRL_LEFT = 769407116;
|
||||
|
||||
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
|
||||
|
||||
DateTime lastCmdTime = DateTime.MinValue;
|
||||
DateTime lastInfo = DateTime.MinValue;
|
||||
string lastSentDir = "";
|
||||
int curDir = -1; // 0=UP 1=DOWN 2=LEFT 3=RIGHT
|
||||
(int x, int y) prevHead = (-1, -1);
|
||||
|
||||
HashSet<(int x, int y)> fillVisited = new HashSet<(int, int)>();
|
||||
Queue<(int x, int y)> fillQueue = new Queue<(int, int)>();
|
||||
HashSet<(int x, int y)> pathVisited = new HashSet<(int, int)>();
|
||||
Queue<((int x, int y) pos, int dist)> pathQueue = new Queue<((int, int), int)>();
|
||||
|
||||
bool InBoard(int x, int y)
|
||||
=> x >= GAME_MIN_X && x <= GAME_MAX_X && y >= GAME_MIN_Y && y <= GAME_MAX_Y;
|
||||
|
||||
void Next(int x, int y, int d, out int nx, out int ny)
|
||||
{
|
||||
nx = x + (d == 3 ? 1 : d == 2 ? -1 : 0);
|
||||
ny = y + (d == 1 ? 1 : d == 0 ? -1 : 0);
|
||||
}
|
||||
|
||||
int Opp(int d) => d ^ 1;
|
||||
|
||||
int DirFromDelta(int dx, int dy)
|
||||
{
|
||||
if (Math.Abs(dx) >= Math.Abs(dy))
|
||||
return dx < 0 ? 2 : 3;
|
||||
return dy < 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
int IdForDir(int d)
|
||||
=> d == 0 ? CTRL_UP : d == 1 ? CTRL_DOWN : d == 2 ? CTRL_LEFT : CTRL_RIGHT;
|
||||
|
||||
bool TryGetButtonPos(int id, out int x, out int y)
|
||||
{
|
||||
var b = GetFloorItem(id);
|
||||
if (b == null)
|
||||
{
|
||||
x = 0;
|
||||
y = 0;
|
||||
return false;
|
||||
}
|
||||
x = b.Location.X;
|
||||
y = b.Location.Y;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Cmd(int d)
|
||||
{
|
||||
string nd = DIRS[d];
|
||||
if (nd == lastSentDir && (DateTime.Now - lastCmdTime).TotalMilliseconds < 110)
|
||||
return;
|
||||
|
||||
int id = IdForDir(d);
|
||||
|
||||
if (TryGetButtonPos(id, out int bx, out int by))
|
||||
Move(bx, by);
|
||||
|
||||
Send(Out["ClickFurni"], id, 0);
|
||||
|
||||
lastCmdTime = DateTime.Now;
|
||||
lastSentDir = nd;
|
||||
}
|
||||
|
||||
bool Bad(int x, int y, HashSet<(int x, int y)> body)
|
||||
{
|
||||
if (!InBoard(x, y)) return true;
|
||||
return body.Contains((x, y));
|
||||
}
|
||||
|
||||
int Fill(int sx, int sy, HashSet<(int x, int y)> body, int cap)
|
||||
{
|
||||
if (Bad(sx, sy, body)) return 0;
|
||||
|
||||
fillVisited.Clear();
|
||||
fillQueue.Clear();
|
||||
|
||||
fillVisited.Add((sx, sy));
|
||||
fillQueue.Enqueue((sx, sy));
|
||||
|
||||
int c = 0;
|
||||
while (fillQueue.Count > 0 && c < cap)
|
||||
{
|
||||
var p = fillQueue.Dequeue();
|
||||
c++;
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Next(p.x, p.y, d, out int nx, out int ny);
|
||||
if (!Bad(nx, ny, body) && fillVisited.Add((nx, ny)))
|
||||
fillQueue.Enqueue((nx, ny));
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
int Dist(int sx, int sy, int tx, int ty, HashSet<(int x, int y)> body, int cap)
|
||||
{
|
||||
if (sx == tx && sy == ty) return 0;
|
||||
|
||||
pathVisited.Clear();
|
||||
pathQueue.Clear();
|
||||
|
||||
pathVisited.Add((sx, sy));
|
||||
pathQueue.Enqueue(((sx, sy), 0));
|
||||
|
||||
while (pathQueue.Count > 0)
|
||||
{
|
||||
var cur = pathQueue.Dequeue();
|
||||
if (cur.dist >= cap) return 999;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Next(cur.pos.x, cur.pos.y, d, out int nx, out int ny);
|
||||
if (nx == tx && ny == ty) return cur.dist + 1;
|
||||
if (!Bad(nx, ny, body) && pathVisited.Add((nx, ny)))
|
||||
pathQueue.Enqueue(((nx, ny), cur.dist + 1));
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Emergency((int x, int y) head, HashSet<(int x, int y)> body)
|
||||
{
|
||||
int bestDir = curDir >= 0 ? curDir : 0;
|
||||
int bestSpace = -1;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (curDir >= 0 && d == Opp(curDir)) continue;
|
||||
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body)) continue;
|
||||
|
||||
int space = Fill(nx, ny, body, 120);
|
||||
if (space > bestSpace)
|
||||
{
|
||||
bestSpace = space;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
int Decide((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body)
|
||||
{
|
||||
int bestDir = curDir >= 0 ? curDir : 0;
|
||||
int bestScore = int.MinValue;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (curDir >= 0 && d == Opp(curDir)) continue;
|
||||
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body)) continue;
|
||||
|
||||
int space = Fill(nx, ny, body, 200);
|
||||
if (space < 10) continue;
|
||||
|
||||
int dist = Dist(nx, ny, target.x, target.y, body, 200);
|
||||
|
||||
int score = 0;
|
||||
if (nx == target.x && ny == target.y) score += 15000;
|
||||
if (dist > 0 && dist < 999) score += (240 - dist) * 8;
|
||||
else if (dist == -1) score -= 300;
|
||||
|
||||
score += Math.Min(space, 120) * 4;
|
||||
if (curDir >= 0 && d == curDir) score += 2;
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
Log("Snake Auto (Campaign) v5 started");
|
||||
Log($"Buttons: U={CTRL_UP} R={CTRL_RIGHT} D={CTRL_DOWN} L={CTRL_LEFT}");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(18);
|
||||
|
||||
var headItem = GetFloorItem(HEAD_ID);
|
||||
if (headItem == null)
|
||||
{
|
||||
if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500)
|
||||
{
|
||||
Log("waiting: head not visible");
|
||||
lastInfo = DateTime.Now;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var head = (headItem.Location.X, headItem.Location.Y);
|
||||
if (!InBoard(head.Item1, head.Item2))
|
||||
continue;
|
||||
|
||||
if (prevHead.x != -1)
|
||||
{
|
||||
int dx = head.Item1 - prevHead.x;
|
||||
int dy = head.Item2 - prevHead.y;
|
||||
if (dx != 0 || dy != 0)
|
||||
curDir = DirFromDelta(dx, dy);
|
||||
}
|
||||
prevHead = head;
|
||||
|
||||
var body = FloorItems
|
||||
.Where(i => i != null && i.Kind == SNAKE_KIND)
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (i.Location.X, i.Location.Y))
|
||||
.Where(p => !(p.Item1 == head.Item1 && p.Item2 == head.Item2))
|
||||
.ToHashSet();
|
||||
|
||||
var targets = FloorItems
|
||||
.Where(i => i != null && i.Kind == TARGET_KIND)
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (x: i.Location.X, y: i.Location.Y))
|
||||
.ToList();
|
||||
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
// No tile visible: stay safe, do not random reverse.
|
||||
int safe = Emergency(head, body);
|
||||
Cmd(safe);
|
||||
continue;
|
||||
}
|
||||
|
||||
var target = targets
|
||||
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
|
||||
.First();
|
||||
|
||||
if (curDir >= 0)
|
||||
{
|
||||
Next(head.Item1, head.Item2, curDir, out int fx, out int fy);
|
||||
if (Bad(fx, fy, body))
|
||||
{
|
||||
int emergency = Emergency(head, body);
|
||||
Cmd(emergency);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int best = Decide(head, target, body);
|
||||
Cmd(best);
|
||||
}
|
||||
287
Snake Auto (Campaign) v6.csx
Normal file
287
Snake Auto (Campaign) v6.csx
Normal file
@ -0,0 +1,287 @@
|
||||
/// @name Snake Auto (Campaign) v6
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
const long HEAD_ID = 769407982;
|
||||
const int TARGET_KIND = 3666;
|
||||
const int SNAKE_KIND = 5478;
|
||||
|
||||
const int GAME_MIN_X = 37;
|
||||
const int GAME_MAX_X = 59;
|
||||
const int GAME_MIN_Y = 29;
|
||||
const int GAME_MAX_Y = 51;
|
||||
|
||||
// Confirmed controls
|
||||
const int CTRL_UP = 769407385;
|
||||
const int CTRL_RIGHT = 769406820;
|
||||
const int CTRL_DOWN = 769407085;
|
||||
const int CTRL_LEFT = 769407116;
|
||||
|
||||
const bool DEBUG = true;
|
||||
|
||||
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
|
||||
|
||||
DateTime lastCmdTime = DateTime.MinValue;
|
||||
DateTime lastInfo = DateTime.MinValue;
|
||||
DateTime lastDebug = DateTime.MinValue;
|
||||
string lastSentDir = "";
|
||||
int curDir = -1; // 0=UP 1=DOWN 2=LEFT 3=RIGHT
|
||||
(int x, int y) prevHead = (-1, -1);
|
||||
|
||||
HashSet<(int x, int y)> fillVisited = new HashSet<(int, int)>();
|
||||
Queue<(int x, int y)> fillQueue = new Queue<(int, int)>();
|
||||
HashSet<(int x, int y)> pathVisited = new HashSet<(int, int)>();
|
||||
Queue<((int x, int y) pos, int dist)> pathQueue = new Queue<((int, int), int)>();
|
||||
|
||||
bool InBoard(int x, int y)
|
||||
=> x >= GAME_MIN_X && x <= GAME_MAX_X && y >= GAME_MIN_Y && y <= GAME_MAX_Y;
|
||||
|
||||
void Dbg(string msg, bool force = false)
|
||||
{
|
||||
if (!DEBUG) return;
|
||||
if (!force && (DateTime.Now - lastDebug).TotalMilliseconds < 120) return;
|
||||
Log($"dbg: {msg}");
|
||||
lastDebug = DateTime.Now;
|
||||
}
|
||||
|
||||
void Next(int x, int y, int d, out int nx, out int ny)
|
||||
{
|
||||
nx = x + (d == 3 ? 1 : d == 2 ? -1 : 0);
|
||||
ny = y + (d == 1 ? 1 : d == 0 ? -1 : 0);
|
||||
}
|
||||
|
||||
int Opp(int d) => d ^ 1;
|
||||
|
||||
int DirFromDelta(int dx, int dy)
|
||||
{
|
||||
if (Math.Abs(dx) >= Math.Abs(dy))
|
||||
return dx < 0 ? 2 : 3;
|
||||
return dy < 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
int IdForDir(int d)
|
||||
=> d == 0 ? CTRL_UP : d == 1 ? CTRL_DOWN : d == 2 ? CTRL_LEFT : CTRL_RIGHT;
|
||||
|
||||
bool TryGetButtonPos(int id, out int x, out int y)
|
||||
{
|
||||
var b = GetFloorItem(id);
|
||||
if (b == null)
|
||||
{
|
||||
x = 0;
|
||||
y = 0;
|
||||
return false;
|
||||
}
|
||||
x = b.Location.X;
|
||||
y = b.Location.Y;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Cmd(int d, string reason, (int x, int y) head, (int x, int y)? target)
|
||||
{
|
||||
string nd = DIRS[d];
|
||||
if (nd == lastSentDir && (DateTime.Now - lastCmdTime).TotalMilliseconds < 110)
|
||||
return;
|
||||
|
||||
int id = IdForDir(d);
|
||||
|
||||
if (TryGetButtonPos(id, out int bx, out int by))
|
||||
Move(bx, by);
|
||||
|
||||
Send(Out["ClickFurni"], id, 0);
|
||||
|
||||
lastCmdTime = DateTime.Now;
|
||||
lastSentDir = nd;
|
||||
|
||||
if (target.HasValue)
|
||||
Dbg($"reason={reason} head=({head.x},{head.y}) target=({target.Value.x},{target.Value.y}) dir={nd} btn={id}", true);
|
||||
else
|
||||
Dbg($"reason={reason} head=({head.x},{head.y}) dir={nd} btn={id}", true);
|
||||
}
|
||||
|
||||
bool Bad(int x, int y, HashSet<(int x, int y)> body)
|
||||
{
|
||||
if (!InBoard(x, y)) return true;
|
||||
return body.Contains((x, y));
|
||||
}
|
||||
|
||||
int Fill(int sx, int sy, HashSet<(int x, int y)> body, int cap)
|
||||
{
|
||||
if (Bad(sx, sy, body)) return 0;
|
||||
|
||||
fillVisited.Clear();
|
||||
fillQueue.Clear();
|
||||
|
||||
fillVisited.Add((sx, sy));
|
||||
fillQueue.Enqueue((sx, sy));
|
||||
|
||||
int c = 0;
|
||||
while (fillQueue.Count > 0 && c < cap)
|
||||
{
|
||||
var p = fillQueue.Dequeue();
|
||||
c++;
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Next(p.x, p.y, d, out int nx, out int ny);
|
||||
if (!Bad(nx, ny, body) && fillVisited.Add((nx, ny)))
|
||||
fillQueue.Enqueue((nx, ny));
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
int Dist(int sx, int sy, int tx, int ty, HashSet<(int x, int y)> body, int cap)
|
||||
{
|
||||
if (sx == tx && sy == ty) return 0;
|
||||
|
||||
pathVisited.Clear();
|
||||
pathQueue.Clear();
|
||||
|
||||
pathVisited.Add((sx, sy));
|
||||
pathQueue.Enqueue(((sx, sy), 0));
|
||||
|
||||
while (pathQueue.Count > 0)
|
||||
{
|
||||
var cur = pathQueue.Dequeue();
|
||||
if (cur.dist >= cap) return 999;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Next(cur.pos.x, cur.pos.y, d, out int nx, out int ny);
|
||||
if (nx == tx && ny == ty) return cur.dist + 1;
|
||||
if (!Bad(nx, ny, body) && pathVisited.Add((nx, ny)))
|
||||
pathQueue.Enqueue(((nx, ny), cur.dist + 1));
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Emergency((int x, int y) head, HashSet<(int x, int y)> body)
|
||||
{
|
||||
int bestDir = curDir >= 0 ? curDir : 0;
|
||||
int bestSpace = -1;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (curDir >= 0 && d == Opp(curDir)) continue;
|
||||
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body)) continue;
|
||||
|
||||
int space = Fill(nx, ny, body, 120);
|
||||
if (space > bestSpace)
|
||||
{
|
||||
bestSpace = space;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
int Decide((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body)
|
||||
{
|
||||
int bestDir = curDir >= 0 ? curDir : 0;
|
||||
int bestScore = int.MinValue;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (curDir >= 0 && d == Opp(curDir)) continue;
|
||||
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body)) continue;
|
||||
|
||||
int space = Fill(nx, ny, body, 200);
|
||||
if (space < 10) continue;
|
||||
|
||||
int dist = Dist(nx, ny, target.x, target.y, body, 200);
|
||||
|
||||
int score = 0;
|
||||
if (nx == target.x && ny == target.y) score += 15000;
|
||||
if (dist > 0 && dist < 999) score += (240 - dist) * 8;
|
||||
else if (dist == -1) score -= 300;
|
||||
|
||||
score += Math.Min(space, 120) * 4;
|
||||
if (curDir >= 0 && d == curDir) score += 2;
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
Log("Snake Auto (Campaign) v6 started");
|
||||
Log($"Buttons: U={CTRL_UP} R={CTRL_RIGHT} D={CTRL_DOWN} L={CTRL_LEFT}");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(18);
|
||||
|
||||
var headItem = GetFloorItem(HEAD_ID);
|
||||
if (headItem == null)
|
||||
{
|
||||
if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500)
|
||||
{
|
||||
Log("waiting: head not visible");
|
||||
lastInfo = DateTime.Now;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var head = (headItem.Location.X, headItem.Location.Y);
|
||||
if (!InBoard(head.Item1, head.Item2))
|
||||
continue;
|
||||
|
||||
if (prevHead.x != -1)
|
||||
{
|
||||
int dx = head.Item1 - prevHead.x;
|
||||
int dy = head.Item2 - prevHead.y;
|
||||
if (dx != 0 || dy != 0)
|
||||
curDir = DirFromDelta(dx, dy);
|
||||
}
|
||||
prevHead = head;
|
||||
|
||||
var body = FloorItems
|
||||
.Where(i => i != null && i.Kind == SNAKE_KIND)
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (i.Location.X, i.Location.Y))
|
||||
.Where(p => !(p.Item1 == head.Item1 && p.Item2 == head.Item2))
|
||||
.ToHashSet();
|
||||
|
||||
var targets = FloorItems
|
||||
.Where(i => i != null && i.Kind == TARGET_KIND)
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (x: i.Location.X, y: i.Location.Y))
|
||||
.ToList();
|
||||
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
if ((DateTime.Now - lastInfo).TotalMilliseconds > 500)
|
||||
{
|
||||
Dbg($"reason=no_target_hold head=({head.Item1},{head.Item2})", true);
|
||||
lastInfo = DateTime.Now;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var target = targets
|
||||
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
|
||||
.First();
|
||||
|
||||
if (curDir >= 0)
|
||||
{
|
||||
Next(head.Item1, head.Item2, curDir, out int fx, out int fy);
|
||||
if (Bad(fx, fy, body))
|
||||
{
|
||||
int emergency = Emergency(head, body);
|
||||
Cmd(emergency, "front_blocked", head, target);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int best = Decide(head, target, body);
|
||||
Cmd(best, "seek_target", head, target);
|
||||
}
|
||||
336
Snake Auto (Campaign) v7.csx
Normal file
336
Snake Auto (Campaign) v7.csx
Normal file
@ -0,0 +1,336 @@
|
||||
/// @name Snake Auto (Campaign) v7
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
const long HEAD_ID = 769407982;
|
||||
const int TARGET_KIND = 3666;
|
||||
const int SNAKE_KIND = 5478;
|
||||
|
||||
const int GAME_MIN_X = 37;
|
||||
const int GAME_MAX_X = 59;
|
||||
const int GAME_MIN_Y = 29;
|
||||
const int GAME_MAX_Y = 51;
|
||||
|
||||
// Initial guess; script auto-corrects while running.
|
||||
const int BTN_TL = 769407085;
|
||||
const int BTN_TR = 769406820;
|
||||
const int BTN_BL = 769407385;
|
||||
const int BTN_BR = 769407116;
|
||||
|
||||
// dirs: 0=UP 1=DOWN 2=LEFT 3=RIGHT
|
||||
Dictionary<int, int> dirBtn = new Dictionary<int, int>
|
||||
{
|
||||
[0] = BTN_TR,
|
||||
[1] = BTN_BR,
|
||||
[2] = BTN_BL,
|
||||
[3] = BTN_TL
|
||||
};
|
||||
|
||||
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
|
||||
|
||||
DateTime lastCmdTime = DateTime.MinValue;
|
||||
DateTime lastInfo = DateTime.MinValue;
|
||||
DateTime lastDebug = DateTime.MinValue;
|
||||
string lastSentDir = "";
|
||||
int curDir = -1;
|
||||
(int x, int y) prevHead = (-1, -1);
|
||||
|
||||
int pendingBtn = 0;
|
||||
(int x, int y) pendingHead = (-1, -1);
|
||||
DateTime pendingAt = DateTime.MinValue;
|
||||
|
||||
HashSet<(int x, int y)> fillVisited = new HashSet<(int, int)>();
|
||||
Queue<(int x, int y)> fillQueue = new Queue<(int, int)>();
|
||||
HashSet<(int x, int y)> pathVisited = new HashSet<(int, int)>();
|
||||
Queue<((int x, int y) pos, int dist)> pathQueue = new Queue<((int, int), int)>();
|
||||
|
||||
bool InBoard(int x, int y)
|
||||
=> x >= GAME_MIN_X && x <= GAME_MAX_X && y >= GAME_MIN_Y && y <= GAME_MAX_Y;
|
||||
|
||||
void Dbg(string msg, bool force = false)
|
||||
{
|
||||
if (!force && (DateTime.Now - lastDebug).TotalMilliseconds < 120) return;
|
||||
Log($"dbg: {msg}");
|
||||
lastDebug = DateTime.Now;
|
||||
}
|
||||
|
||||
void Next(int x, int y, int d, out int nx, out int ny)
|
||||
{
|
||||
nx = x + (d == 3 ? 1 : d == 2 ? -1 : 0);
|
||||
ny = y + (d == 1 ? 1 : d == 0 ? -1 : 0);
|
||||
}
|
||||
|
||||
int Opp(int d) => d ^ 1;
|
||||
|
||||
int DirFromDelta(int dx, int dy)
|
||||
{
|
||||
if (Math.Abs(dx) >= Math.Abs(dy))
|
||||
return dx < 0 ? 2 : 3;
|
||||
return dy < 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
bool TryGetButtonPos(int id, out int x, out int y)
|
||||
{
|
||||
var b = GetFloorItem(id);
|
||||
if (b == null)
|
||||
{
|
||||
x = 0;
|
||||
y = 0;
|
||||
return false;
|
||||
}
|
||||
x = b.Location.X;
|
||||
y = b.Location.Y;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LearnMappingFromMovement((int x, int y) head)
|
||||
{
|
||||
if (pendingBtn == 0) return;
|
||||
if ((DateTime.Now - pendingAt).TotalMilliseconds > 1200)
|
||||
{
|
||||
pendingBtn = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int dx = head.x - pendingHead.x;
|
||||
int dy = head.y - pendingHead.y;
|
||||
if (dx == 0 && dy == 0) return;
|
||||
|
||||
int movedDir = DirFromDelta(dx, dy);
|
||||
int oldBtn = dirBtn[movedDir];
|
||||
|
||||
if (oldBtn != pendingBtn)
|
||||
{
|
||||
dirBtn[movedDir] = pendingBtn;
|
||||
|
||||
// keep mapping one-to-one by swapping previous owner
|
||||
foreach (var kv in dirBtn.ToList())
|
||||
{
|
||||
if (kv.Key != movedDir && kv.Value == pendingBtn)
|
||||
{
|
||||
dirBtn[kv.Key] = oldBtn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Dbg($"calib: {DIRS[movedDir]} now={pendingBtn} swapped_old={oldBtn}", true);
|
||||
Dbg($"map: U={dirBtn[0]} D={dirBtn[1]} L={dirBtn[2]} R={dirBtn[3]}", true);
|
||||
}
|
||||
|
||||
pendingBtn = 0;
|
||||
}
|
||||
|
||||
void Cmd(int d, string reason, (int x, int y) head, (int x, int y)? target)
|
||||
{
|
||||
string nd = DIRS[d];
|
||||
if (nd == lastSentDir && (DateTime.Now - lastCmdTime).TotalMilliseconds < 110)
|
||||
return;
|
||||
|
||||
int id = dirBtn[d];
|
||||
if (TryGetButtonPos(id, out int bx, out int by))
|
||||
Move(bx, by);
|
||||
|
||||
Send(Out["ClickFurni"], id, 0);
|
||||
|
||||
pendingBtn = id;
|
||||
pendingHead = head;
|
||||
pendingAt = DateTime.Now;
|
||||
|
||||
lastCmdTime = DateTime.Now;
|
||||
lastSentDir = nd;
|
||||
|
||||
if (target.HasValue)
|
||||
Dbg($"reason={reason} head=({head.x},{head.y}) target=({target.Value.x},{target.Value.y}) dir={nd} btn={id}", true);
|
||||
else
|
||||
Dbg($"reason={reason} head=({head.x},{head.y}) dir={nd} btn={id}", true);
|
||||
}
|
||||
|
||||
bool Bad(int x, int y, HashSet<(int x, int y)> body)
|
||||
{
|
||||
if (!InBoard(x, y)) return true;
|
||||
return body.Contains((x, y));
|
||||
}
|
||||
|
||||
int Fill(int sx, int sy, HashSet<(int x, int y)> body, int cap)
|
||||
{
|
||||
if (Bad(sx, sy, body)) return 0;
|
||||
|
||||
fillVisited.Clear();
|
||||
fillQueue.Clear();
|
||||
|
||||
fillVisited.Add((sx, sy));
|
||||
fillQueue.Enqueue((sx, sy));
|
||||
|
||||
int c = 0;
|
||||
while (fillQueue.Count > 0 && c < cap)
|
||||
{
|
||||
var p = fillQueue.Dequeue();
|
||||
c++;
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Next(p.x, p.y, d, out int nx, out int ny);
|
||||
if (!Bad(nx, ny, body) && fillVisited.Add((nx, ny)))
|
||||
fillQueue.Enqueue((nx, ny));
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
int Dist(int sx, int sy, int tx, int ty, HashSet<(int x, int y)> body, int cap)
|
||||
{
|
||||
if (sx == tx && sy == ty) return 0;
|
||||
|
||||
pathVisited.Clear();
|
||||
pathQueue.Clear();
|
||||
|
||||
pathVisited.Add((sx, sy));
|
||||
pathQueue.Enqueue(((sx, sy), 0));
|
||||
|
||||
while (pathQueue.Count > 0)
|
||||
{
|
||||
var cur = pathQueue.Dequeue();
|
||||
if (cur.dist >= cap) return 999;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Next(cur.pos.x, cur.pos.y, d, out int nx, out int ny);
|
||||
if (nx == tx && ny == ty) return cur.dist + 1;
|
||||
if (!Bad(nx, ny, body) && pathVisited.Add((nx, ny)))
|
||||
pathQueue.Enqueue(((nx, ny), cur.dist + 1));
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Emergency((int x, int y) head, HashSet<(int x, int y)> body)
|
||||
{
|
||||
int bestDir = curDir >= 0 ? curDir : 0;
|
||||
int bestSpace = -1;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (curDir >= 0 && d == Opp(curDir)) continue;
|
||||
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body)) continue;
|
||||
|
||||
int space = Fill(nx, ny, body, 120);
|
||||
if (space > bestSpace)
|
||||
{
|
||||
bestSpace = space;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
int Decide((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body)
|
||||
{
|
||||
int bestDir = curDir >= 0 ? curDir : 0;
|
||||
int bestScore = int.MinValue;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (curDir >= 0 && d == Opp(curDir)) continue;
|
||||
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body)) continue;
|
||||
|
||||
int space = Fill(nx, ny, body, 200);
|
||||
if (space < 10) continue;
|
||||
|
||||
int dist = Dist(nx, ny, target.x, target.y, body, 200);
|
||||
|
||||
int score = 0;
|
||||
if (nx == target.x && ny == target.y) score += 15000;
|
||||
if (dist > 0 && dist < 999) score += (240 - dist) * 8;
|
||||
else if (dist == -1) score -= 300;
|
||||
|
||||
score += Math.Min(space, 120) * 4;
|
||||
if (curDir >= 0 && d == curDir) score += 2;
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
Log("Snake Auto (Campaign) v7 started");
|
||||
Log($"init map: U={dirBtn[0]} D={dirBtn[1]} L={dirBtn[2]} R={dirBtn[3]}");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(18);
|
||||
|
||||
var headItem = GetFloorItem(HEAD_ID);
|
||||
if (headItem == null)
|
||||
{
|
||||
if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500)
|
||||
{
|
||||
Log("waiting: head not visible");
|
||||
lastInfo = DateTime.Now;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var head = (headItem.Location.X, headItem.Location.Y);
|
||||
if (!InBoard(head.Item1, head.Item2))
|
||||
continue;
|
||||
|
||||
LearnMappingFromMovement(head);
|
||||
|
||||
if (prevHead.x != -1)
|
||||
{
|
||||
int dx = head.Item1 - prevHead.x;
|
||||
int dy = head.Item2 - prevHead.y;
|
||||
if (dx != 0 || dy != 0)
|
||||
curDir = DirFromDelta(dx, dy);
|
||||
}
|
||||
prevHead = head;
|
||||
|
||||
var body = FloorItems
|
||||
.Where(i => i != null && i.Kind == SNAKE_KIND)
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (i.Location.X, i.Location.Y))
|
||||
.Where(p => !(p.Item1 == head.Item1 && p.Item2 == head.Item2))
|
||||
.ToHashSet();
|
||||
|
||||
var targets = FloorItems
|
||||
.Where(i => i != null && i.Kind == TARGET_KIND)
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (x: i.Location.X, y: i.Location.Y))
|
||||
.ToList();
|
||||
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
if ((DateTime.Now - lastInfo).TotalMilliseconds > 500)
|
||||
{
|
||||
Dbg($"reason=no_target_hold head=({head.Item1},{head.Item2})", true);
|
||||
lastInfo = DateTime.Now;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var target = targets
|
||||
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
|
||||
.First();
|
||||
|
||||
if (curDir >= 0)
|
||||
{
|
||||
Next(head.Item1, head.Item2, curDir, out int fx, out int fy);
|
||||
if (Bad(fx, fy, body))
|
||||
{
|
||||
int emergency = Emergency(head, body);
|
||||
Cmd(emergency, "front_blocked", head, target);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int best = Decide(head, target, body);
|
||||
Cmd(best, "seek_target", head, target);
|
||||
}
|
||||
171
Snake Auto (Campaign) v8.csx
Normal file
171
Snake Auto (Campaign) v8.csx
Normal file
@ -0,0 +1,171 @@
|
||||
/// @name Snake Auto (Campaign) v8
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
const long HEAD_ID = 769407982;
|
||||
const int SNAKE_KIND = 5478;
|
||||
static readonly int[] TARGET_KINDS = { 6559, 3666 };
|
||||
|
||||
const int GAME_MIN_X = 20;
|
||||
const int GAME_MAX_X = 65;
|
||||
const int GAME_MIN_Y = 20;
|
||||
const int GAME_MAX_Y = 65;
|
||||
|
||||
// Direction -> plate ids (kept from your latest calibration)
|
||||
const int CTRL_UP = 769406820;
|
||||
const int CTRL_DOWN = 769407116;
|
||||
const int CTRL_LEFT = 769407385;
|
||||
const int CTRL_RIGHT = 769407085;
|
||||
|
||||
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
|
||||
|
||||
DateTime lastCmd = DateTime.MinValue;
|
||||
DateTime lastDbg = DateTime.MinValue;
|
||||
int curDir = -1;
|
||||
(int x, int y) prevHead = (-1, -1);
|
||||
|
||||
bool InBoard(int x, int y)
|
||||
=> x >= GAME_MIN_X && x <= GAME_MAX_X && y >= GAME_MIN_Y && y <= GAME_MAX_Y;
|
||||
|
||||
void Dbg(string m, bool force = false)
|
||||
{
|
||||
if (!force && (DateTime.Now - lastDbg).TotalMilliseconds < 220) return;
|
||||
Log($"dbg: {m}");
|
||||
lastDbg = DateTime.Now;
|
||||
}
|
||||
|
||||
void Next(int x, int y, int d, out int nx, out int ny)
|
||||
{
|
||||
nx = x + (d == 3 ? 1 : d == 2 ? -1 : 0);
|
||||
ny = y + (d == 1 ? 1 : d == 0 ? -1 : 0);
|
||||
}
|
||||
|
||||
int Opp(int d) => d ^ 1;
|
||||
|
||||
int DirFromDelta(int dx, int dy)
|
||||
{
|
||||
if (Math.Abs(dx) >= Math.Abs(dy)) return dx < 0 ? 2 : 3;
|
||||
return dy < 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
int BtnForDir(int d)
|
||||
=> d == 0 ? CTRL_UP : d == 1 ? CTRL_DOWN : d == 2 ? CTRL_LEFT : CTRL_RIGHT;
|
||||
|
||||
bool TryGetBtnPos(int id, out int x, out int y)
|
||||
{
|
||||
var b = GetFloorItem(id);
|
||||
if (b == null) { x = 0; y = 0; return false; }
|
||||
x = b.Location.X;
|
||||
y = b.Location.Y;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Cmd(int d, string reason, (int x, int y) head, (int x, int y)? target)
|
||||
{
|
||||
if ((DateTime.Now - lastCmd).TotalMilliseconds < 120) return;
|
||||
|
||||
int id = BtnForDir(d);
|
||||
if (TryGetBtnPos(id, out int bx, out int by)) Move(bx, by);
|
||||
Send(Out["ClickFurni"], id, 0);
|
||||
|
||||
lastCmd = DateTime.Now;
|
||||
|
||||
if (target.HasValue)
|
||||
Dbg($"{reason} head=({head.x},{head.y}) target=({target.Value.x},{target.Value.y}) dir={DIRS[d]} btn={id}", true);
|
||||
else
|
||||
Dbg($"{reason} head=({head.x},{head.y}) dir={DIRS[d]} btn={id}", true);
|
||||
}
|
||||
|
||||
bool Bad(int x, int y, HashSet<(int x, int y)> body)
|
||||
{
|
||||
if (!InBoard(x, y)) return true;
|
||||
return body.Contains((x, y));
|
||||
}
|
||||
|
||||
int PickDir((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body)
|
||||
{
|
||||
var order = new List<int>();
|
||||
|
||||
int dx = target.x - head.x;
|
||||
int dy = target.y - head.y;
|
||||
|
||||
if (Math.Abs(dx) >= Math.Abs(dy))
|
||||
{
|
||||
order.Add(dx < 0 ? 2 : 3);
|
||||
order.Add(dy < 0 ? 0 : 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
order.Add(dy < 0 ? 0 : 1);
|
||||
order.Add(dx < 0 ? 2 : 3);
|
||||
}
|
||||
|
||||
order.AddRange(new[] { 0, 1, 2, 3 });
|
||||
|
||||
foreach (int d in order.Distinct())
|
||||
{
|
||||
if (curDir >= 0 && d == Opp(curDir)) continue;
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (!Bad(nx, ny, body)) return d;
|
||||
}
|
||||
|
||||
return curDir >= 0 ? curDir : 0;
|
||||
}
|
||||
|
||||
Log("Snake Auto (Campaign) v8 started");
|
||||
Log($"target kinds: {string.Join(",", TARGET_KINDS)}");
|
||||
Log($"buttons U={CTRL_UP} D={CTRL_DOWN} L={CTRL_LEFT} R={CTRL_RIGHT}");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(20);
|
||||
|
||||
var headItem = GetFloorItem(HEAD_ID);
|
||||
if (headItem == null)
|
||||
{
|
||||
Dbg("head missing");
|
||||
continue;
|
||||
}
|
||||
|
||||
var head = (headItem.Location.X, headItem.Location.Y);
|
||||
if (!InBoard(head.Item1, head.Item2))
|
||||
{
|
||||
Dbg($"head out of board ({head.Item1},{head.Item2})");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prevHead.x != -1)
|
||||
{
|
||||
int hdx = head.Item1 - prevHead.x;
|
||||
int hdy = head.Item2 - prevHead.y;
|
||||
if (hdx != 0 || hdy != 0) curDir = DirFromDelta(hdx, hdy);
|
||||
}
|
||||
prevHead = head;
|
||||
|
||||
var body = FloorItems
|
||||
.Where(i => i != null && i.Kind == SNAKE_KIND)
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (i.Location.X, i.Location.Y))
|
||||
.Where(p => !(p.Item1 == head.Item1 && p.Item2 == head.Item2))
|
||||
.ToHashSet();
|
||||
|
||||
var targets = FloorItems
|
||||
.Where(i => i != null && TARGET_KINDS.Contains((int)i.Kind))
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (x: i.Location.X, y: i.Location.Y, kind: (int)i.Kind, id: (long)i.Id))
|
||||
.ToList();
|
||||
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
Dbg("no_target_hold", true);
|
||||
continue;
|
||||
}
|
||||
|
||||
var target = targets
|
||||
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
|
||||
.First();
|
||||
|
||||
int dsel = PickDir(head, (target.x, target.y), body);
|
||||
Cmd(dsel, $"seek k={target.kind} id={target.id}", head, (target.x, target.y));
|
||||
}
|
||||
233
Snake Auto (Campaign) v9.csx
Normal file
233
Snake Auto (Campaign) v9.csx
Normal file
@ -0,0 +1,233 @@
|
||||
/// @name Snake Auto (Campaign) v9
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
const long HEAD_ID = 769407982;
|
||||
const int SNAKE_KIND = 5478;
|
||||
static readonly int[] TARGET_KINDS = { 6559, 3666 };
|
||||
|
||||
const int GAME_MIN_X = 20;
|
||||
const int GAME_MAX_X = 65;
|
||||
const int GAME_MIN_Y = 20;
|
||||
const int GAME_MAX_Y = 65;
|
||||
|
||||
// Hard obstacle (blue block area)
|
||||
const int BLOCK_MIN_X = 20;
|
||||
const int BLOCK_MAX_X = 22;
|
||||
const int BLOCK_MIN_Y = 20;
|
||||
const int BLOCK_MAX_Y = 21;
|
||||
|
||||
// Direction -> plate ids
|
||||
const int CTRL_UP = 769406820;
|
||||
const int CTRL_DOWN = 769407116;
|
||||
const int CTRL_LEFT = 769407385;
|
||||
const int CTRL_RIGHT = 769407085;
|
||||
|
||||
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
|
||||
|
||||
DateTime lastCmd = DateTime.MinValue;
|
||||
DateTime lastDbg = DateTime.MinValue;
|
||||
int curDir = -1;
|
||||
(int x, int y) prevHead = (-1, -1);
|
||||
|
||||
HashSet<(int x, int y)> fillVisited = new HashSet<(int, int)>();
|
||||
Queue<(int x, int y)> fillQueue = new Queue<(int, int)>();
|
||||
|
||||
bool InBoard(int x, int y)
|
||||
=> x >= GAME_MIN_X && x <= GAME_MAX_X && y >= GAME_MIN_Y && y <= GAME_MAX_Y;
|
||||
|
||||
bool IsBlock(int x, int y)
|
||||
=> x >= BLOCK_MIN_X && x <= BLOCK_MAX_X && y >= BLOCK_MIN_Y && y <= BLOCK_MAX_Y;
|
||||
|
||||
void Dbg(string m, bool force = false)
|
||||
{
|
||||
if (!force && (DateTime.Now - lastDbg).TotalMilliseconds < 220) return;
|
||||
Log($"dbg: {m}");
|
||||
lastDbg = DateTime.Now;
|
||||
}
|
||||
|
||||
void Next(int x, int y, int d, out int nx, out int ny)
|
||||
{
|
||||
nx = x + (d == 3 ? 1 : d == 2 ? -1 : 0);
|
||||
ny = y + (d == 1 ? 1 : d == 0 ? -1 : 0);
|
||||
}
|
||||
|
||||
int Opp(int d) => d ^ 1;
|
||||
|
||||
int DirFromDelta(int dx, int dy)
|
||||
{
|
||||
if (Math.Abs(dx) >= Math.Abs(dy)) return dx < 0 ? 2 : 3;
|
||||
return dy < 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
int BtnForDir(int d)
|
||||
=> d == 0 ? CTRL_UP : d == 1 ? CTRL_DOWN : d == 2 ? CTRL_LEFT : CTRL_RIGHT;
|
||||
|
||||
bool TryGetBtnPos(int id, out int x, out int y)
|
||||
{
|
||||
var b = GetFloorItem(id);
|
||||
if (b == null) { x = 0; y = 0; return false; }
|
||||
x = b.Location.X;
|
||||
y = b.Location.Y;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Cmd(int d, string reason, (int x, int y) head, (int x, int y)? target)
|
||||
{
|
||||
if ((DateTime.Now - lastCmd).TotalMilliseconds < 120) return;
|
||||
|
||||
int id = BtnForDir(d);
|
||||
if (TryGetBtnPos(id, out int bx, out int by)) Move(bx, by);
|
||||
Send(Out["ClickFurni"], id, 0);
|
||||
|
||||
lastCmd = DateTime.Now;
|
||||
|
||||
if (target.HasValue)
|
||||
Dbg($"{reason} h=({head.x},{head.y}) t=({target.Value.x},{target.Value.y}) d={DIRS[d]} b={id}", true);
|
||||
else
|
||||
Dbg($"{reason} h=({head.x},{head.y}) d={DIRS[d]} b={id}", true);
|
||||
}
|
||||
|
||||
bool Bad(int x, int y, HashSet<(int x, int y)> body)
|
||||
{
|
||||
if (!InBoard(x, y)) return true;
|
||||
if (IsBlock(x, y)) return true;
|
||||
return body.Contains((x, y));
|
||||
}
|
||||
|
||||
int Fill(int sx, int sy, HashSet<(int x, int y)> body, int cap)
|
||||
{
|
||||
if (Bad(sx, sy, body)) return 0;
|
||||
|
||||
fillVisited.Clear();
|
||||
fillQueue.Clear();
|
||||
fillVisited.Add((sx, sy));
|
||||
fillQueue.Enqueue((sx, sy));
|
||||
|
||||
int c = 0;
|
||||
while (fillQueue.Count > 0 && c < cap)
|
||||
{
|
||||
var p = fillQueue.Dequeue();
|
||||
c++;
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Next(p.x, p.y, d, out int nx, out int ny);
|
||||
if (!Bad(nx, ny, body) && fillVisited.Add((nx, ny)))
|
||||
fillQueue.Enqueue((nx, ny));
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
int Emergency((int x, int y) head, HashSet<(int x, int y)> body)
|
||||
{
|
||||
int bestDir = curDir >= 0 ? curDir : 0;
|
||||
int bestSpace = -1;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (curDir >= 0 && d == Opp(curDir)) continue;
|
||||
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body)) continue;
|
||||
|
||||
int space = Fill(nx, ny, body, 260);
|
||||
if (space > bestSpace)
|
||||
{
|
||||
bestSpace = space;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
int PickDir((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body)
|
||||
{
|
||||
int desired = DirFromDelta(target.x - head.x, target.y - head.y);
|
||||
|
||||
var dirs = new List<int> { desired, 0, 1, 2, 3 };
|
||||
int bestDir = curDir >= 0 ? curDir : desired;
|
||||
int bestScore = int.MinValue;
|
||||
|
||||
foreach (int d in dirs.Distinct())
|
||||
{
|
||||
if (curDir >= 0 && d == Opp(curDir)) continue;
|
||||
|
||||
Next(head.x, head.y, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, body)) continue;
|
||||
|
||||
int dist = Math.Abs(target.x - nx) + Math.Abs(target.y - ny);
|
||||
int space = Fill(nx, ny, body, 260);
|
||||
|
||||
int score = 0;
|
||||
score += -dist * 6;
|
||||
score += space * 2;
|
||||
if (d == desired) score += 30;
|
||||
if (curDir >= 0 && d == curDir) score += 5;
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
Log("Snake Auto (Campaign) v9 started");
|
||||
Log($"target kinds: {string.Join(",", TARGET_KINDS)}");
|
||||
Log($"buttons U={CTRL_UP} D={CTRL_DOWN} L={CTRL_LEFT} R={CTRL_RIGHT}");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(20);
|
||||
|
||||
var headItem = GetFloorItem(HEAD_ID);
|
||||
if (headItem == null)
|
||||
{
|
||||
Dbg("head missing");
|
||||
continue;
|
||||
}
|
||||
|
||||
var head = (headItem.Location.X, headItem.Location.Y);
|
||||
if (!InBoard(head.Item1, head.Item2))
|
||||
continue;
|
||||
|
||||
if (prevHead.x != -1)
|
||||
{
|
||||
int hdx = head.Item1 - prevHead.x;
|
||||
int hdy = head.Item2 - prevHead.y;
|
||||
if (hdx != 0 || hdy != 0) curDir = DirFromDelta(hdx, hdy);
|
||||
}
|
||||
prevHead = head;
|
||||
|
||||
var body = FloorItems
|
||||
.Where(i => i != null && i.Kind == SNAKE_KIND)
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (i.Location.X, i.Location.Y))
|
||||
.Where(p => !(p.Item1 == head.Item1 && p.Item2 == head.Item2))
|
||||
.ToHashSet();
|
||||
|
||||
var targets = FloorItems
|
||||
.Where(i => i != null && TARGET_KINDS.Contains((int)i.Kind))
|
||||
.Where(i => InBoard(i.Location.X, i.Location.Y))
|
||||
.Select(i => (x: i.Location.X, y: i.Location.Y, kind: (int)i.Kind, id: (long)i.Id))
|
||||
.ToList();
|
||||
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
int safe = Emergency(head, body);
|
||||
Cmd(safe, "no_target_safe", head, null);
|
||||
continue;
|
||||
}
|
||||
|
||||
var target = targets
|
||||
.OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2))
|
||||
.First();
|
||||
|
||||
int dir = PickDir(head, (target.x, target.y), body);
|
||||
Cmd(dir, $"seek k={target.kind} id={target.id}", head, (target.x, target.y));
|
||||
}
|
||||
115
Snake Auto (Campaign).csx
Normal file
115
Snake Auto (Campaign).csx
Normal file
@ -0,0 +1,115 @@
|
||||
/// @name Snake Auto (Campaign)
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
// Fixed from your room
|
||||
const long HEAD_ID = 769407982;
|
||||
const int TARGET_KIND = 3666; // Color Tile kind
|
||||
|
||||
// Pad button IDs (as confirmed by you)
|
||||
const int PAD_TOP_LEFT = 769407085;
|
||||
const int PAD_TOP_RIGHT = 769406820;
|
||||
const int PAD_BOTTOM_LEFT = 769407385;
|
||||
const int PAD_BOTTOM_RIGHT = 769407116;
|
||||
|
||||
// Direction mapping (tuned for this room)
|
||||
const int CTRL_UP = PAD_BOTTOM_LEFT;
|
||||
const int CTRL_RIGHT = PAD_TOP_RIGHT;
|
||||
const int CTRL_DOWN = PAD_TOP_LEFT;
|
||||
const int CTRL_LEFT = PAD_BOTTOM_RIGHT;
|
||||
|
||||
DateTime lastCmd = DateTime.MinValue;
|
||||
DateTime lastInfo = DateTime.MinValue;
|
||||
int lastDir = -1; // 0=UP 1=DOWN 2=LEFT 3=RIGHT
|
||||
|
||||
bool TryGetButton(int dir, out int id, out int x, out int y)
|
||||
{
|
||||
id = dir == 0 ? CTRL_UP : dir == 1 ? CTRL_DOWN : dir == 2 ? CTRL_LEFT : CTRL_RIGHT;
|
||||
var btn = GetFloorItem(id);
|
||||
if (btn == null)
|
||||
{
|
||||
x = 0;
|
||||
y = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
x = btn.Location.X;
|
||||
y = btn.Location.Y;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SendDir(int dir)
|
||||
{
|
||||
if ((DateTime.Now - lastCmd).TotalMilliseconds < 140 && dir == lastDir)
|
||||
return;
|
||||
|
||||
if (TryGetButton(dir, out int id, out int x, out int y))
|
||||
{
|
||||
// This game mode reacts when user steps on the plate.
|
||||
Move(x, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
int fallbackId = dir == 0 ? CTRL_UP : dir == 1 ? CTRL_DOWN : dir == 2 ? CTRL_LEFT : CTRL_RIGHT;
|
||||
Send(Out["ClickFurni"], fallbackId, 0);
|
||||
}
|
||||
|
||||
lastCmd = DateTime.Now;
|
||||
lastDir = dir;
|
||||
}
|
||||
|
||||
int ToDir(int dx, int dy)
|
||||
{
|
||||
if (Math.Abs(dx) >= Math.Abs(dy))
|
||||
return dx < 0 ? 2 : 3;
|
||||
return dy < 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
Log("Snake Auto (Campaign) v4 started");
|
||||
Log($"HeadId={HEAD_ID}, TargetKind={TARGET_KIND}");
|
||||
Log($"Buttons: U={CTRL_UP} R={CTRL_RIGHT} D={CTRL_DOWN} L={CTRL_LEFT}");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(40);
|
||||
|
||||
var head = GetFloorItem(HEAD_ID);
|
||||
if (head == null)
|
||||
{
|
||||
if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500)
|
||||
{
|
||||
Log("waiting: head not visible");
|
||||
lastInfo = DateTime.Now;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var targets = FloorItems
|
||||
.Where(i => i != null && i.Kind == TARGET_KIND)
|
||||
.ToList();
|
||||
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500)
|
||||
{
|
||||
Log($"waiting: no target tiles kind {TARGET_KIND}");
|
||||
lastInfo = DateTime.Now;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
var target = targets
|
||||
.OrderBy(t => Math.Abs(t.Location.X - head.Location.X) + Math.Abs(t.Location.Y - head.Location.Y))
|
||||
.ThenBy(t => t.State)
|
||||
.First();
|
||||
|
||||
int dx = target.Location.X - head.Location.X;
|
||||
int dy = target.Location.Y - head.Location.Y;
|
||||
|
||||
if (dx == 0 && dy == 0)
|
||||
continue;
|
||||
|
||||
int dir = ToDir(dx, dy);
|
||||
SendDir(dir);
|
||||
}
|
||||
37
Snake Debug Watch.csx
Normal file
37
Snake Debug Watch.csx
Normal file
@ -0,0 +1,37 @@
|
||||
/// @name Snake Debug Watch
|
||||
long TARGET_ID = 769407982;
|
||||
long[] BTN_IDS = { 769407085, 769407116, 769407385, 769406820 };
|
||||
|
||||
int tick = 0;
|
||||
int lastX = int.MinValue, lastY = int.MinValue, lastState = int.MinValue;
|
||||
|
||||
Log("Snake Debug Watch started");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(100);
|
||||
tick++;
|
||||
|
||||
var target = GetFloorItem(TARGET_ID);
|
||||
if (target == null)
|
||||
{
|
||||
if (tick % 10 == 0)
|
||||
Log("target missing");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (target.Location.X != lastX || target.Location.Y != lastY || target.State != lastState)
|
||||
{
|
||||
Log($"target: ({target.Location.X},{target.Location.Y}) state={target.State} kind={target.Kind}");
|
||||
lastX = target.Location.X;
|
||||
lastY = target.Location.Y;
|
||||
lastState = target.State;
|
||||
}
|
||||
|
||||
if (tick % 20 == 0)
|
||||
{
|
||||
int k7100 = FloorItems.Count(i => i != null && i.Kind == 7100);
|
||||
int k5068 = FloorItems.Count(i => i != null && i.Kind == 5068);
|
||||
Log($"k7100={k7100}, k5068={k5068}");
|
||||
}
|
||||
}
|
||||
47
Snake Target Probe.csx
Normal file
47
Snake Target Probe.csx
Normal file
@ -0,0 +1,47 @@
|
||||
/// @name Snake Target Probe
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
const int MIN_X = 37, MAX_X = 59, MIN_Y = 29, MAX_Y = 51;
|
||||
|
||||
bool InBoard(dynamic i)
|
||||
=> i != null && i.Location.X >= MIN_X && i.Location.X <= MAX_X && i.Location.Y >= MIN_Y && i.Location.Y <= MAX_Y;
|
||||
|
||||
var map = new Dictionary<long, (int kind, int x, int y, int state)>();
|
||||
foreach (var i in FloorItems)
|
||||
{
|
||||
if (!InBoard(i)) continue;
|
||||
map[i.Id] = ((int)i.Kind, i.Location.X, i.Location.Y, (int)i.State);
|
||||
}
|
||||
|
||||
Log("Snake Target Probe started");
|
||||
Log("Watching board item state/move changes...");
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(100);
|
||||
|
||||
foreach (var i in FloorItems)
|
||||
{
|
||||
if (!InBoard(i)) continue;
|
||||
|
||||
int kind = (int)i.Kind;
|
||||
int x = i.Location.X;
|
||||
int y = i.Location.Y;
|
||||
int state = (int)i.State;
|
||||
|
||||
if (!map.TryGetValue(i.Id, out var prev))
|
||||
{
|
||||
map[i.Id] = (kind, x, y, state);
|
||||
Log($"add id={i.Id} kind={kind} pos=({x},{y}) state={state}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prev.x != x || prev.y != y || prev.state != state)
|
||||
{
|
||||
Log($"chg id={i.Id} kind={kind} ({prev.x},{prev.y})/{prev.state} -> ({x},{y})/{state}");
|
||||
map[i.Id] = (kind, x, y, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
343
Snake [28.12.25] V1.0.csx
Normal file
343
Snake [28.12.25] V1.0.csx
Normal file
@ -0,0 +1,343 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
// --- KONFIGURATION ---
|
||||
const int GAME_MIN_X = 9;
|
||||
const int GAME_MAX_X = 28;
|
||||
const int GAME_MIN_Y = 11;
|
||||
const int GAME_MAX_Y = 30;
|
||||
|
||||
const int BLOCKED_MIN_X = 17;
|
||||
const int BLOCKED_MAX_X = 20;
|
||||
const int BLOCKED_MIN_Y = 19;
|
||||
const int BLOCKED_MAX_Y = 22;
|
||||
|
||||
const int KIND_SNAKE = 7100;
|
||||
const int KIND_FOOD = 5068;
|
||||
|
||||
const int CTRL_UP = 2147418172;
|
||||
const int CTRL_RIGHT = 2147418173;
|
||||
const int CTRL_DOWN = 2147418174;
|
||||
const int CTRL_LEFT = 2147418175;
|
||||
|
||||
// --- GLOBALE VARIABLEN ---
|
||||
List<(int x, int y)> body = new List<(int x, int y)>(200);
|
||||
HashSet<(int x, int y)> bodySet = new HashSet<(int x, int y)>(200);
|
||||
(int x, int y) food = (-1, -1);
|
||||
|
||||
string dir = "DOWN";
|
||||
string lastSentDir = "";
|
||||
DateTime lastCmdTime = DateTime.MinValue;
|
||||
|
||||
int len = 0;
|
||||
bool ate = false;
|
||||
|
||||
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
|
||||
HashSet<(int, int)> fillVisited = new HashSet<(int, int)>(300);
|
||||
Queue<(int, int)> fillQueue = new Queue<(int, int)>(300);
|
||||
HashSet<(int, int)> pathVisited = new HashSet<(int, int)>(200);
|
||||
Queue<((int x, int y) pos, int dist)> pathQueue = new Queue<((int, int), int)>(200);
|
||||
|
||||
// --- HILFSFUNKTIONEN ---
|
||||
bool Wall(int x, int y) => x < GAME_MIN_X || x > GAME_MAX_X || y < GAME_MIN_Y || y > GAME_MAX_Y;
|
||||
bool Blk(int x, int y) => x >= BLOCKED_MIN_X && x <= BLOCKED_MAX_X && y >= BLOCKED_MIN_Y && y <= BLOCKED_MAX_Y;
|
||||
|
||||
void Nxt(int x, int y, int d, out int nx, out int ny)
|
||||
{
|
||||
nx = x + (d == 3 ? 1 : d == 2 ? -1 : 0);
|
||||
ny = y + (d == 1 ? 1 : d == 0 ? -1 : 0);
|
||||
}
|
||||
|
||||
int OppIdx(int d) => d ^ 1;
|
||||
|
||||
bool Bad(int x, int y, bool ignoreTail)
|
||||
{
|
||||
if (Wall(x, y) || Blk(x, y)) return true;
|
||||
if (body.Count == 0) return false;
|
||||
|
||||
int checkLen = ignoreTail && !ate ? body.Count - 1 : body.Count;
|
||||
for (int i = 0; i < checkLen; i++)
|
||||
if (body[i].x == x && body[i].y == y) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void Cmd(int dIdx)
|
||||
{
|
||||
string d = DIRS[dIdx];
|
||||
if (d == lastSentDir && (DateTime.Now - lastCmdTime).TotalMilliseconds < 80) return;
|
||||
|
||||
int id = dIdx == 0 ? CTRL_UP : dIdx == 1 ? CTRL_DOWN : dIdx == 2 ? CTRL_LEFT : CTRL_RIGHT;
|
||||
Send(Out["ClickFurni"], id, 0);
|
||||
|
||||
lastCmdTime = DateTime.Now;
|
||||
lastSentDir = d;
|
||||
dir = d;
|
||||
}
|
||||
|
||||
int Fill(int sx, int sy, int limit)
|
||||
{
|
||||
if (Bad(sx, sy, false)) return 0;
|
||||
|
||||
fillVisited.Clear();
|
||||
fillQueue.Clear();
|
||||
|
||||
fillQueue.Enqueue((sx, sy));
|
||||
fillVisited.Add((sx, sy));
|
||||
int c = 0;
|
||||
|
||||
while (fillQueue.Count > 0 && c < limit)
|
||||
{
|
||||
var (x, y) = fillQueue.Dequeue();
|
||||
c++;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Nxt(x, y, d, out int nx, out int ny);
|
||||
if (!Bad(nx, ny, false) && fillVisited.Add((nx, ny)))
|
||||
fillQueue.Enqueue((nx, ny));
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
// Pathfinding mit höherem Default-Limit
|
||||
int GetPathDistance(int sx, int sy, int tx, int ty, int limit = 150)
|
||||
{
|
||||
if (sx == tx && sy == ty) return 0;
|
||||
|
||||
pathVisited.Clear();
|
||||
pathQueue.Clear();
|
||||
|
||||
pathQueue.Enqueue(((sx, sy), 0));
|
||||
pathVisited.Add((sx, sy));
|
||||
|
||||
while (pathQueue.Count > 0)
|
||||
{
|
||||
var curr = pathQueue.Dequeue();
|
||||
if (curr.dist >= limit) return 999;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Nxt(curr.pos.x, curr.pos.y, d, out int nx, out int ny);
|
||||
|
||||
if (nx == tx && ny == ty) return curr.dist + 1;
|
||||
if (!Bad(nx, ny, false) && pathVisited.Add((nx, ny)))
|
||||
pathQueue.Enqueue(((nx, ny), curr.dist + 1));
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int DecideMove(int hx, int hy, int fx, int fy, int curDirIdx)
|
||||
{
|
||||
int bestDir = curDirIdx;
|
||||
int bestScore = int.MinValue;
|
||||
|
||||
// Höhere Limits für besseres Pathfinding
|
||||
int fillLimit = len > 100 ? 100 : len > 50 ? 150 : 200;
|
||||
int pathLimit = len > 100 ? 80 : len > 50 ? 120 : 150;
|
||||
int minSpace = Math.Max(len / 4, 10); // Noch toleranter
|
||||
|
||||
var tail = body.Count > 0 ? body[body.Count - 1] : (-1, -1);
|
||||
|
||||
// Prüfe ob Food am Rand ist
|
||||
bool foodAtEdge = fx <= GAME_MIN_X + 1 || fx >= GAME_MAX_X - 1 ||
|
||||
fy <= GAME_MIN_Y + 1 || fy >= GAME_MAX_Y - 1;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (d == OppIdx(curDirIdx)) continue;
|
||||
|
||||
Nxt(hx, hy, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, true)) continue;
|
||||
|
||||
int space = Fill(nx, ny, fillLimit);
|
||||
if (space < minSpace) continue;
|
||||
|
||||
int score = 0;
|
||||
|
||||
// ESSEN DIREKT DANEBEN = HÖCHSTE PRIORITÄT!
|
||||
if (nx == fx && ny == fy)
|
||||
{
|
||||
score += 10000;
|
||||
}
|
||||
else
|
||||
{
|
||||
int distFood = GetPathDistance(nx, ny, fx, fy, pathLimit);
|
||||
if (distFood > 0 && distFood < 999)
|
||||
{
|
||||
// Stärkerer Food-Bonus - Essen ist wichtiger!
|
||||
score += (200 - distFood) * 8;
|
||||
}
|
||||
else if (distFood == -1)
|
||||
{
|
||||
// Kein Weg zum Essen gefunden - leichter Malus aber nicht zu hart
|
||||
score -= 100;
|
||||
}
|
||||
}
|
||||
|
||||
// Platz-Bonus (weniger gewichtet als vorher)
|
||||
score += Math.Min(space, 100) * 5;
|
||||
|
||||
// Safety: Schwanz erreichbar?
|
||||
if (tail.Item1 != -1 && space < fillLimit)
|
||||
{
|
||||
int distTail = GetPathDistance(nx, ny, tail.Item1, tail.Item2, 30);
|
||||
if (distTail > 0) score += 150;
|
||||
else if (space < 40) score -= 300;
|
||||
}
|
||||
|
||||
// Wandnähe - NUR wenn Food NICHT am Rand ist!
|
||||
// Wenn Food am Rand -> kein Penalty für Randnähe
|
||||
if (!foodAtEdge && len > 50)
|
||||
{
|
||||
if (nx <= GAME_MIN_X + 1 || nx >= GAME_MAX_X - 1) score -= 20;
|
||||
if (ny <= GAME_MIN_Y + 1 || ny >= GAME_MAX_Y - 1) score -= 20;
|
||||
}
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
int EmergencyMove(int hx, int hy, int curDirIdx)
|
||||
{
|
||||
int bestDir = curDirIdx;
|
||||
int bestSpace = -1;
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (d == OppIdx(curDirIdx)) continue;
|
||||
Nxt(hx, hy, d, out int nx, out int ny);
|
||||
if (Bad(nx, ny, true)) continue;
|
||||
|
||||
int space = Fill(nx, ny, 60);
|
||||
if (space > bestSpace)
|
||||
{
|
||||
bestSpace = space;
|
||||
bestDir = d;
|
||||
}
|
||||
}
|
||||
return bestDir;
|
||||
}
|
||||
|
||||
int DirToIdx(string d) => d == "UP" ? 0 : d == "DOWN" ? 1 : d == "LEFT" ? 2 : 3;
|
||||
|
||||
Log("SNAKE V16 - EDGE FOOD FIX");
|
||||
|
||||
DateTime lastFullCalc = DateTime.MinValue;
|
||||
int cachedMove = -1;
|
||||
|
||||
while (Run)
|
||||
{
|
||||
Delay(15);
|
||||
|
||||
var curSnake = new HashSet<(int x, int y)>();
|
||||
food = (-1, -1);
|
||||
|
||||
foreach (var item in FloorItems)
|
||||
{
|
||||
if (item == null) continue;
|
||||
int k;
|
||||
try { k = (int)item.Kind; } catch { continue; }
|
||||
|
||||
if (k == KIND_SNAKE && !Wall(item.Location.X, item.Location.Y))
|
||||
curSnake.Add((item.Location.X, item.Location.Y));
|
||||
else if (k == KIND_FOOD)
|
||||
food = (item.Location.X, item.Location.Y);
|
||||
}
|
||||
|
||||
int curLen = curSnake.Count;
|
||||
if (curLen == 0) { body.Clear(); bodySet.Clear(); len = 0; continue; }
|
||||
|
||||
if (body.Count == 0)
|
||||
{
|
||||
var sorted = curSnake.OrderBy(p => p.y).ThenBy(p => p.x).ToList();
|
||||
body = new List<(int x, int y)>(sorted);
|
||||
bodySet = new HashSet<(int x, int y)>(sorted);
|
||||
len = curLen;
|
||||
continue;
|
||||
}
|
||||
|
||||
var head = body[0];
|
||||
(int x, int y) newHead = (-1, -1);
|
||||
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
Nxt(head.x, head.y, d, out int nx, out int ny);
|
||||
if (curSnake.Contains((nx, ny)) && !bodySet.Contains((nx, ny)))
|
||||
{
|
||||
newHead = (nx, ny);
|
||||
if ((DateTime.Now - lastCmdTime).TotalMilliseconds > 400) dir = DIRS[d];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (newHead.x != -1)
|
||||
{
|
||||
body.Insert(0, newHead);
|
||||
bodySet.Add(newHead);
|
||||
ate = curLen > len;
|
||||
while (body.Count > curLen)
|
||||
{
|
||||
var tail = body[body.Count - 1];
|
||||
bodySet.Remove(tail);
|
||||
body.RemoveAt(body.Count - 1);
|
||||
}
|
||||
len = curLen;
|
||||
cachedMove = -1;
|
||||
}
|
||||
|
||||
if (body.Count == 0) continue;
|
||||
head = body[0];
|
||||
int curDirIdx = DirToIdx(dir);
|
||||
|
||||
// NOTFALL: Wand direkt vor uns?
|
||||
Nxt(head.x, head.y, curDirIdx, out int frontX, out int frontY);
|
||||
if (Bad(frontX, frontY, true))
|
||||
{
|
||||
int emergency = EmergencyMove(head.x, head.y, curDirIdx);
|
||||
Cmd(emergency);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (food.x == -1) continue;
|
||||
|
||||
// SOFORT ESSEN wenn direkt daneben!
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
if (d == OppIdx(curDirIdx)) continue;
|
||||
Nxt(head.x, head.y, d, out int nx, out int ny);
|
||||
|
||||
if (nx == food.x && ny == food.y && !Bad(nx, ny, true))
|
||||
{
|
||||
int spaceAfter = Fill(nx, ny, 80);
|
||||
if (spaceAfter >= Math.Max(len / 4, 8))
|
||||
{
|
||||
Cmd(d);
|
||||
goto nextLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normale KI - öfter neu berechnen für bessere Reaktion
|
||||
double msSinceCalc = (DateTime.Now - lastFullCalc).TotalMilliseconds;
|
||||
int recalcInterval = len > 100 ? 100 : len > 50 ? 70 : 50;
|
||||
|
||||
if (cachedMove == -1 || msSinceCalc > recalcInterval)
|
||||
{
|
||||
cachedMove = DecideMove(head.x, head.y, food.x, food.y, curDirIdx);
|
||||
lastFullCalc = DateTime.Now;
|
||||
}
|
||||
|
||||
if (cachedMove != curDirIdx)
|
||||
Cmd(cachedMove);
|
||||
|
||||
nextLoop:;
|
||||
}
|
||||
1115
Tetris [28.12.25] V1.0.csx
Normal file
1115
Tetris [28.12.25] V1.0.csx
Normal file
File diff suppressed because it is too large
Load Diff
35
Trade Spam.csx
Normal file
35
Trade Spam.csx
Normal file
@ -0,0 +1,35 @@
|
||||
// Trade Spam - Klicke auf einen User um ihn zu spammen
|
||||
|
||||
int targetIndex = 0;
|
||||
string targetName = "";
|
||||
|
||||
Log("=== Trade Spam Script ===");
|
||||
Log("Klicke auf einen User um ihn zu spammen!");
|
||||
|
||||
// Erkennt wenn du auf einen User klickst
|
||||
OnIntercept(Out["GetSelectedBadges"], e => {
|
||||
var userId = e.Packet.ReadInt();
|
||||
var user = Users.FirstOrDefault(u => u.Id == userId);
|
||||
if (user != null)
|
||||
{
|
||||
targetIndex = user.Index;
|
||||
targetName = user.Name;
|
||||
Log($">>> Ziel gesetzt: {targetName} <<<");
|
||||
}
|
||||
});
|
||||
|
||||
while (Run)
|
||||
{
|
||||
if (targetIndex > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
Send(Out["OpenTrading"], targetIndex);
|
||||
Delay(80);
|
||||
Send(Out["CloseTrading"]);
|
||||
Delay(80);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
Delay(50);
|
||||
}
|
||||
95
User Collector FI Server.csx
Normal file
95
User Collector FI Server.csx
Normal file
@ -0,0 +1,95 @@
|
||||
// Automatisch durch Räume gehen und User in TXT speichern (nur Namen)
|
||||
|
||||
using System.IO;
|
||||
|
||||
var minUsers = 1;
|
||||
var delayBetweenRooms = 3000;
|
||||
|
||||
var desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
||||
var filePath = Path.Combine(desktopPath, "HabboUserList_fi.txt");
|
||||
|
||||
var allUsers = new HashSet<string>();
|
||||
|
||||
// Existierende User laden
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
foreach (var line in File.ReadAllLines(filePath))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
allUsers.Add(line.Trim());
|
||||
}
|
||||
}
|
||||
Log($"Bestehende Liste geladen: {allUsers.Count} User");
|
||||
}
|
||||
|
||||
void SaveToFile()
|
||||
{
|
||||
File.WriteAllLines(filePath, allUsers.OrderBy(x => x));
|
||||
Log($"[GESPEICHERT] {allUsers.Count} User");
|
||||
}
|
||||
|
||||
var categories = new[] { "popular", "recommended" };
|
||||
var allRooms = new Dictionary<long, dynamic>();
|
||||
|
||||
Log("=== Suche Räume... ===");
|
||||
|
||||
foreach (var category in categories)
|
||||
{
|
||||
try
|
||||
{
|
||||
var rooms = SearchNav(category).Where(x => x.Users >= minUsers);
|
||||
foreach (var room in rooms)
|
||||
{
|
||||
if (!allRooms.ContainsKey(room.Id))
|
||||
allRooms[room.Id] = room;
|
||||
}
|
||||
Delay(1000);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
Log($"Gefunden: {allRooms.Count} Räume");
|
||||
|
||||
int roomCount = 0;
|
||||
|
||||
foreach (var room in allRooms.Values)
|
||||
{
|
||||
if (!Run) break;
|
||||
|
||||
roomCount++;
|
||||
Log($"[{roomCount}/{allRooms.Count}] {room.Name}");
|
||||
|
||||
try
|
||||
{
|
||||
if (EnsureEnterRoom(room.Id) == RoomEntryResult.Success)
|
||||
{
|
||||
Delay(1500);
|
||||
|
||||
int newUsers = 0;
|
||||
foreach (var user in Users)
|
||||
{
|
||||
if (allUsers.Add(user.Name))
|
||||
newUsers++;
|
||||
}
|
||||
|
||||
if (newUsers > 0)
|
||||
{
|
||||
Log($" +{newUsers} neue User");
|
||||
SaveToFile();
|
||||
}
|
||||
|
||||
LeaveRoom();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
Delay(delayBetweenRooms);
|
||||
}
|
||||
|
||||
SaveToFile();
|
||||
|
||||
Log($"");
|
||||
Log($"=== FERTIG ===");
|
||||
Log($"Gesamt: {allUsers.Count} User");
|
||||
Log($"Datei: {filePath}");
|
||||
245
User Collector Hopper Advanced.csx
Normal file
245
User Collector Hopper Advanced.csx
Normal file
@ -0,0 +1,245 @@
|
||||
/// @name User Collector + Auto Hopper
|
||||
/// @desc Sammelt User-Namen und hoppt automatisch durch Raeume
|
||||
/// @author OpenCode
|
||||
/// @scripter 1.0.0-beta.136
|
||||
|
||||
// ============ EINSTELLUNGEN ============
|
||||
int minUsers = 5; // Minimum User im Raum
|
||||
int waitInRoom = 6000; // Wie lange in jedem Raum bleiben (ms)
|
||||
int delayBetweenRooms = 2000; // Pause zwischen Raumwechsel (ms)
|
||||
// ========================================
|
||||
|
||||
// Pfad zur Ausgabedatei auf dem Desktop
|
||||
var desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
||||
var outputFile = Path.Combine(desktopPath, "HabboUserList.txt");
|
||||
|
||||
// HashSet um Duplikate zu vermeiden
|
||||
var collectedUsers = new HashSet<string>();
|
||||
|
||||
// Besuchte Raeume tracken
|
||||
var visitedRooms = new HashSet<long>();
|
||||
|
||||
// Raeume Queue - sortiert nach User-Anzahl
|
||||
var roomQueue = new List<(long Id, string Name, int UserCount)>();
|
||||
|
||||
// Lade bereits gesammelte User aus der Datei (falls vorhanden)
|
||||
if (File.Exists(outputFile))
|
||||
{
|
||||
var lines = File.ReadAllLines(outputFile);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(line))
|
||||
collectedUsers.Add(line.Trim());
|
||||
}
|
||||
Log($"Loaded: {collectedUsers.Count} users from file");
|
||||
}
|
||||
|
||||
Log("=================================");
|
||||
Log("User Collector + Auto Hopper");
|
||||
Log($"Min. users per room: {minUsers}");
|
||||
Log("=================================");
|
||||
Log("HOW TO USE:");
|
||||
Log("1. Open Navigator in game");
|
||||
Log("2. Click 'Rooms' -> 'Popular'");
|
||||
Log("3. Script catches the rooms");
|
||||
Log("4. Type :start to begin hopping");
|
||||
Log("=================================");
|
||||
Log("Commands: :start :stop :status :reset");
|
||||
Log("=================================");
|
||||
|
||||
// Hopping aktiv?
|
||||
bool isHopping = false;
|
||||
|
||||
// Event: Wenn jemand den Raum betritt
|
||||
OnEntityAdded(e => {
|
||||
if (e.Entity is IRoomUser user)
|
||||
{
|
||||
var userName = user.Name;
|
||||
if (collectedUsers.Add(userName))
|
||||
{
|
||||
File.AppendAllText(outputFile, userName + "\n");
|
||||
Log($"+ {userName}");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Intercept Navigator Suchergebnisse (Navigator2)
|
||||
OnIntercept(In.Navigator2SearchResultBlocks, e => {
|
||||
try
|
||||
{
|
||||
var packet = e.Packet;
|
||||
var searchCode = packet.ReadString();
|
||||
var filterText = packet.ReadString();
|
||||
var blockCount = packet.ReadInt();
|
||||
|
||||
int foundRooms = 0;
|
||||
|
||||
for (int b = 0; b < blockCount; b++)
|
||||
{
|
||||
var blockCode = packet.ReadString();
|
||||
var blockText = packet.ReadString();
|
||||
var actionAllowed = packet.ReadInt();
|
||||
var forceClosed = packet.ReadBool();
|
||||
var roomCount = packet.ReadInt();
|
||||
|
||||
for (int r = 0; r < roomCount; r++)
|
||||
{
|
||||
var roomId = packet.ReadLong();
|
||||
var roomName = packet.ReadString();
|
||||
var ownerId = packet.ReadLong();
|
||||
var ownerName = packet.ReadString();
|
||||
var doorMode = packet.ReadInt();
|
||||
var userCount = packet.ReadInt();
|
||||
var maxUsers = packet.ReadInt();
|
||||
var description = packet.ReadString();
|
||||
var tradeMode = packet.ReadInt();
|
||||
var score = packet.ReadInt();
|
||||
var ranking = packet.ReadInt();
|
||||
var categoryId = packet.ReadInt();
|
||||
|
||||
// Tags
|
||||
var tagCount = packet.ReadInt();
|
||||
for (int t = 0; t < tagCount; t++)
|
||||
packet.ReadString();
|
||||
|
||||
// Flags
|
||||
var flags = packet.ReadInt();
|
||||
if ((flags & 1) != 0) packet.ReadString();
|
||||
if ((flags & 2) != 0) packet.ReadString();
|
||||
if ((flags & 4) != 0) { packet.ReadString(); packet.ReadLong(); packet.ReadString(); }
|
||||
|
||||
// Raum hinzufuegen
|
||||
if (userCount >= minUsers && !visitedRooms.Contains(roomId))
|
||||
{
|
||||
if (!roomQueue.Any(x => x.Id == roomId))
|
||||
{
|
||||
roomQueue.Add((roomId, roomName, userCount));
|
||||
foundRooms++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundRooms > 0)
|
||||
{
|
||||
// Sort by user count (most first)
|
||||
roomQueue = roomQueue.OrderByDescending(x => x.UserCount).ToList();
|
||||
Log($"+ {foundRooms} new rooms found! (Total: {roomQueue.Count})");
|
||||
Status($"Queue: {roomQueue.Count} rooms | Users: {collectedUsers.Count}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"Parse-Fehler: {ex.Message}");
|
||||
}
|
||||
});
|
||||
|
||||
// Chat-Befehl zum Starten
|
||||
OnIntercept(Out.Chat, e => {
|
||||
var msg = e.Packet.ReadString(0);
|
||||
if (msg == ":start")
|
||||
{
|
||||
e.Block();
|
||||
if (roomQueue.Count > 0)
|
||||
{
|
||||
isHopping = true;
|
||||
Log($"Starting hopping through {roomQueue.Count} rooms...");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("No rooms in queue! Open Navigator -> Popular");
|
||||
}
|
||||
}
|
||||
else if (msg == ":stop")
|
||||
{
|
||||
e.Block();
|
||||
isHopping = false;
|
||||
Log("Hopping stopped.");
|
||||
}
|
||||
else if (msg == ":status")
|
||||
{
|
||||
e.Block();
|
||||
Log($"Queue: {roomQueue.Count} | Visited: {visitedRooms.Count} | Users: {collectedUsers.Count}");
|
||||
}
|
||||
else if (msg == ":reset")
|
||||
{
|
||||
e.Block();
|
||||
visitedRooms.Clear();
|
||||
roomQueue.Clear();
|
||||
Log("Reset! Open Navigator again.");
|
||||
}
|
||||
});
|
||||
|
||||
// Hauptschleife
|
||||
while (Run)
|
||||
{
|
||||
// User im aktuellen Raum sammeln
|
||||
if (IsInRoom)
|
||||
{
|
||||
foreach (var user in Users)
|
||||
{
|
||||
if (collectedUsers.Add(user.Name))
|
||||
{
|
||||
File.AppendAllText(outputFile, user.Name + "\n");
|
||||
Log($"+ {user.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hopping wenn aktiv
|
||||
if (isHopping && roomQueue.Count > 0)
|
||||
{
|
||||
var room = roomQueue[0];
|
||||
roomQueue.RemoveAt(0);
|
||||
|
||||
if (!visitedRooms.Contains(room.Id))
|
||||
{
|
||||
visitedRooms.Add(room.Id);
|
||||
Log($">>> {room.Name} ({room.UserCount} User)");
|
||||
|
||||
Send(Out.GoToFlat, room.Id, "", -1);
|
||||
Delay(3500);
|
||||
|
||||
if (IsInRoom)
|
||||
{
|
||||
// User sammeln
|
||||
foreach (var user in Users)
|
||||
{
|
||||
if (collectedUsers.Add(user.Name))
|
||||
{
|
||||
File.AppendAllText(outputFile, user.Name + "\n");
|
||||
Log($"+ {user.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
Status($"Users: {collectedUsers.Count} | Rooms: {visitedRooms.Count} | Queue: {roomQueue.Count}");
|
||||
|
||||
// Warten
|
||||
Delay(waitInRoom);
|
||||
|
||||
// Nochmal sammeln (fuer Joiner)
|
||||
foreach (var user in Users)
|
||||
{
|
||||
if (collectedUsers.Add(user.Name))
|
||||
{
|
||||
File.AppendAllText(outputFile, user.Name + "\n");
|
||||
Log($"+ {user.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Delay(delayBetweenRooms);
|
||||
}
|
||||
}
|
||||
else if (isHopping && roomQueue.Count == 0)
|
||||
{
|
||||
isHopping = false;
|
||||
Log($"=== DONE! ===");
|
||||
Log($"Visited: {visitedRooms.Count} rooms");
|
||||
Log($"Collected: {collectedUsers.Count} users");
|
||||
Log("Open Navigator for more rooms, then :start");
|
||||
}
|
||||
|
||||
Status($"Users: {collectedUsers.Count} | Queue: {roomQueue.Count}");
|
||||
Delay(1000);
|
||||
}
|
||||
95
User Collector NL Server.csx
Normal file
95
User Collector NL Server.csx
Normal file
@ -0,0 +1,95 @@
|
||||
// Automatisch durch Räume gehen und User in TXT speichern (nur Namen)
|
||||
|
||||
using System.IO;
|
||||
|
||||
var minUsers = 1;
|
||||
var delayBetweenRooms = 3000;
|
||||
|
||||
var desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
||||
var filePath = Path.Combine(desktopPath, "HabboUserList_nl.txt");
|
||||
|
||||
var allUsers = new HashSet<string>();
|
||||
|
||||
// Existierende User laden
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
foreach (var line in File.ReadAllLines(filePath))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
allUsers.Add(line.Trim());
|
||||
}
|
||||
}
|
||||
Log($"Bestehende Liste geladen: {allUsers.Count} User");
|
||||
}
|
||||
|
||||
void SaveToFile()
|
||||
{
|
||||
File.WriteAllLines(filePath, allUsers.OrderBy(x => x));
|
||||
Log($"[GESPEICHERT] {allUsers.Count} User");
|
||||
}
|
||||
|
||||
var categories = new[] { "popular", "recommended" };
|
||||
var allRooms = new Dictionary<long, dynamic>();
|
||||
|
||||
Log("=== Suche Räume... ===");
|
||||
|
||||
foreach (var category in categories)
|
||||
{
|
||||
try
|
||||
{
|
||||
var rooms = SearchNav(category).Where(x => x.Users >= minUsers);
|
||||
foreach (var room in rooms)
|
||||
{
|
||||
if (!allRooms.ContainsKey(room.Id))
|
||||
allRooms[room.Id] = room;
|
||||
}
|
||||
Delay(1000);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
Log($"Gefunden: {allRooms.Count} Räume");
|
||||
|
||||
int roomCount = 0;
|
||||
|
||||
foreach (var room in allRooms.Values)
|
||||
{
|
||||
if (!Run) break;
|
||||
|
||||
roomCount++;
|
||||
Log($"[{roomCount}/{allRooms.Count}] {room.Name}");
|
||||
|
||||
try
|
||||
{
|
||||
if (EnsureEnterRoom(room.Id) == RoomEntryResult.Success)
|
||||
{
|
||||
Delay(1500);
|
||||
|
||||
int newUsers = 0;
|
||||
foreach (var user in Users)
|
||||
{
|
||||
if (allUsers.Add(user.Name))
|
||||
newUsers++;
|
||||
}
|
||||
|
||||
if (newUsers > 0)
|
||||
{
|
||||
Log($" +{newUsers} neue User");
|
||||
SaveToFile();
|
||||
}
|
||||
|
||||
LeaveRoom();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
Delay(delayBetweenRooms);
|
||||
}
|
||||
|
||||
SaveToFile();
|
||||
|
||||
Log($"");
|
||||
Log($"=== FERTIG ===");
|
||||
Log($"Gesamt: {allUsers.Count} User");
|
||||
Log($"Datei: {filePath}");
|
||||
28
Wired Message Blocker.csx
Normal file
28
Wired Message Blocker.csx
Normal file
@ -0,0 +1,28 @@
|
||||
using Xabbo.Interceptor;
|
||||
using Xabbo.Messages;
|
||||
|
||||
// Funktion zum Prüfen der Nachricht
|
||||
void BlockWired(Intercept e)
|
||||
{
|
||||
try
|
||||
{
|
||||
e.Packet.Position = 0;
|
||||
e.Packet.ReadInt(); // User Index
|
||||
e.Packet.ReadString(); // Nachricht
|
||||
e.Packet.ReadInt(); // Geste
|
||||
|
||||
// Die Bubble ID lesen
|
||||
int bubbleStyle = e.Packet.ReadInt();
|
||||
|
||||
// 34 ist die Standard-Wired-Bubble.
|
||||
// Falls dein Retro eine andere ID für Wired nutzt, ändere die 34.
|
||||
if (bubbleStyle == 34)
|
||||
{
|
||||
e.Block();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// Intercept nur auf Flüstern (Whisper), da Wireds meistens flüstern
|
||||
Intercept(In.Whisper, BlockWired);
|
||||
2
hello-once.csx
Normal file
2
hello-once.csx
Normal file
@ -0,0 +1,2 @@
|
||||
/// @name hello-once
|
||||
Log("hello once");
|
||||
12
room-scan-basic.csx
Normal file
12
room-scan-basic.csx
Normal file
@ -0,0 +1,12 @@
|
||||
/// @name room-scan-basic
|
||||
Log("Room scan basic start");
|
||||
|
||||
if (!IsInRoom || Room == null)
|
||||
{
|
||||
Log("Not in room.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"Room: {Room.Name} ({Room.Id})");
|
||||
Log($"Users: {Users.Count()} | FloorItems: {FloorItems.Count()} | WallItems: {WallItems.Count()}");
|
||||
}
|
||||
50
room-scan-starter.csx
Normal file
50
room-scan-starter.csx
Normal file
@ -0,0 +1,50 @@
|
||||
using System.Linq;
|
||||
|
||||
Log("=== Room Scan Starter ===");
|
||||
|
||||
if (!IsInRoom || Room == null)
|
||||
{
|
||||
Log("Not in a room. Enter a room and run again.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"Room: {Room.Name} (ID: {Room.Id})");
|
||||
Log($"Owner: {Room.OwnerName} ({Room.OwnerId})");
|
||||
Log($"Users: {Users.Count()} | FloorItems: {FloorItems.Count()} | WallItems: {WallItems.Count()}");
|
||||
|
||||
var topFloorKinds = FloorItems
|
||||
.GroupBy(i => i.Kind)
|
||||
.OrderByDescending(g => g.Count())
|
||||
.ThenBy(g => g.Key)
|
||||
.Take(5)
|
||||
.ToList();
|
||||
|
||||
Log("Top 5 floor kinds:");
|
||||
if (topFloorKinds.Count == 0)
|
||||
{
|
||||
Log("- none");
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var g in topFloorKinds)
|
||||
Log($"- kind {g.Key}: {g.Count()}x");
|
||||
}
|
||||
|
||||
var topWallKinds = WallItems
|
||||
.GroupBy(i => i.Kind)
|
||||
.OrderByDescending(g => g.Count())
|
||||
.ThenBy(g => g.Key)
|
||||
.Take(5)
|
||||
.ToList();
|
||||
|
||||
Log("Top 5 wall kinds:");
|
||||
if (topWallKinds.Count == 0)
|
||||
{
|
||||
Log("- none");
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var g in topWallKinds)
|
||||
Log($"- kind {g.Key}: {g.Count()}x");
|
||||
}
|
||||
}
|
||||
20
room-scan-topk.csx
Normal file
20
room-scan-topk.csx
Normal file
@ -0,0 +1,20 @@
|
||||
/// @name room-scan-topk
|
||||
using System.Linq;
|
||||
|
||||
Log("Top kinds test start");
|
||||
|
||||
if (!IsInRoom || Room == null)
|
||||
{
|
||||
Log("Not in room.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var topFloorKinds = FloorItems
|
||||
.GroupBy(i => i.Kind)
|
||||
.OrderByDescending(g => g.Count())
|
||||
.Take(5)
|
||||
.ToList();
|
||||
|
||||
foreach (var g in topFloorKinds)
|
||||
Log($"kind {g.Key}: {g.Count()}x");
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user