Keeps the repo root clean - only README.md visible on landing page. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
156 lines
5.7 KiB
C#
156 lines
5.7 KiB
C#
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"); |