using System; using System.Collections.Generic; using System.Linq; using System.Globalization; public struct Point : IEquatable { 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 trail { get; set; } = new Queue(10); public double spd { get; set; } } HashSet tiles = new HashSet(); Dictionary> adj = new Dictionary>(); Point[] dirs = { (0,1), (0,-1), (1,0), (-1,0), (1,1), (1,-1), (-1,1), (-1,-1) }; Dictionary ducks = new Dictionary(); Tile tgt = null; Point lastcmd = default(Point); DateTime cmdtime = DateTime.MinValue; Point prev = default(Point); Point curr = default(Point); Queue hist = new Queue(5); int stuck = 0; HashSet danger = new HashSet(); Point dest = default(Point); bool forcedest = false; DateTime desttime = DateTime.MinValue; bool floorPlanParsed = false; void ParseFloorPlan() { if (floorPlanParsed) return; try { dynamic floorPlan = FloorPlan; if (floorPlan == null) return; int width = floorPlan.Width; int length = floorPlan.Length; tiles.Clear(); IReadOnlyList tilesData = null; string heightmapString = null; try { tilesData = floorPlan.Tiles; } catch { } try { heightmapString = floorPlan.Heightmap; } catch { } if (heightmapString != null) { heightmapString = heightmapString.Replace("\r", "").Replace("\n", ""); for (int y = 0; y < length; y++) { for (int x = 0; x < width; x++) { if (heightmapString[y * width + x] != 'x') { tiles.Add(new Point(x, y)); } } } } else if (tilesData != null) { for (int y = 0; y < length; y++) { for (int x = 0; x < width; x++) { int tileIndex = y * width + x; if (tileIndex < tilesData.Count && tilesData[tileIndex] >= 0 && tilesData[tileIndex] < 250) { tiles.Add(new Point(x, y)); } } } } BuildAdjacencyMap(); floorPlanParsed = true; } catch { } } void BuildAdjacencyMap() { adj.Clear(); foreach(var t in tiles) { var n = new List(); 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; } } 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 predict(int frames) { var zones = new HashSet(); 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 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 d1, HashSet d2, HashSet 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 d1, HashSet d2, HashSet 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); floorPlanParsed = false; ParseFloorPlan(); }); 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 { if(!floorPlanParsed) ParseFloorPlan(); if(!floorPlanParsed) { Delay(50); continue; } Point me = getpos(); if(me.Equals(default(Point))) { Delay(20); continue; } var toRemove = ducks.Where(d => (DateTime.UtcNow - d.Value.lastseen).TotalSeconds > 5) .Select(d => d.Key) .ToList(); foreach(var id in toRemove) { ducks.Remove(id); } 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); }