xabbo-scripts/Scripts/clickdetector.csx
Administrator 7a548130a3 Move all scripts into Scripts/ subfolder
Keeps the repo root clean - only README.md visible on landing page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 09:49:37 +01:00

187 lines
6.8 KiB
C#

int maxDetectionDistance = 40;
int speechCooldownMs = 5000;
int movementCooldownMs = 600;
bool showDetectionsAsShouts = true;
bool showVisualIndication = true;
int visualIndicationSign = 9;
Dictionary<string, DateTime> lastSpokeTime = new Dictionary<string, DateTime>();
Dictionary<int, (int HeadRot, int BodyRot, int X, int Y, DateTime LastMoved)> userStates = new Dictionary<int, (int, int, int, int, DateTime)>();
Dictionary<int, string> roomIndexToName = new Dictionary<int, string>();
Log($"=== Starting click detection - No Moving Users ===");
Log($"Self position: ({Self.X}, {Self.Y}), Self index: {Self.Index}");
foreach (var entity in Entities) {
roomIndexToName[entity.Index] = entity.Name;
Log($"MAPPED USER: RoomIndex={entity.Index}, Name={entity.Name}, Position=({entity.X}, {entity.Y})");
}
OnChat(e => {
lastSpokeTime[e.Entity.Name] = DateTime.Now;
Log($"CHAT: {e.Entity.Name} spoke at {DateTime.Now}");
});
OnEntityAdded(e => {
roomIndexToName[e.Entity.Index] = e.Entity.Name;
Log($"NEW USER: RoomIndex={e.Entity.Index}, Name={e.Entity.Name}");
});
bool IsRotationPossiblyFacingMe(int userX, int userY, int myX, int myY, int rotation) {
int dx = myX - userX;
int dy = myY - userY;
if (rotation == 6) return true;
if (rotation == 2) return true;
if (rotation == 5) return true;
if (rotation == 0) return true;
if (rotation == 4) return true;
if (dx < 0 && dy > 0) return rotation == 1 || rotation == 0 || rotation == 2 || rotation == 3 || rotation == 5;
if (dx > 0 && dy > 0) return rotation == 3 || rotation == 2 || rotation == 4 || rotation == 5 || rotation == 1;
if (dx < 0 && dy < 0) return rotation == 7 || rotation == 0 || rotation == 1 || rotation == 6 || rotation == 5;
if (dx > 0 && dy < 0) return rotation == 5 || rotation == 6 || rotation == 7 || rotation == 4 || rotation == 3;
if (Math.Abs(dx) <= 1 && Math.Abs(dy) <= 1) return true;
if (dx == 0 && dy > 0) return true;
if (dx == 0 && dy < 0) return true;
if (dx < 0 && dy == 0) return true;
if (dx > 0 && dy == 0) return true;
return true;
}
bool HasRecentlySpoken(string userName) {
return lastSpokeTime.ContainsKey(userName) &&
(DateTime.Now - lastSpokeTime[userName]).TotalMilliseconds < speechCooldownMs;
}
bool HasBeenStationaryLongEnough(int userRoomIndexId) {
if (!userStates.ContainsKey(userRoomIndexId)) {
return false;
}
var state = userStates[userRoomIndexId];
var timeSinceLastMove = DateTime.Now - state.LastMoved;
return timeSinceLastMove.TotalMilliseconds >= movementCooldownMs;
}
OnIntercept(In["UserUpdate"], e => {
int packetId = e.Packet.ReadInt();
int userRoomIndexId = e.Packet.ReadInt();
int userXPos = e.Packet.ReadInt();
int userYPos = e.Packet.ReadInt();
string height = e.Packet.ReadString();
int headRotation = e.Packet.ReadInt();
int bodyRotation = e.Packet.ReadInt();
string status = e.Packet.ReadString();
string userName = "Unknown";
if (roomIndexToName.ContainsKey(userRoomIndexId)) {
userName = roomIndexToName[userRoomIndexId];
} else {
var entity = Entities.FirstOrDefault(u => u.Index == userRoomIndexId);
if (entity != null) {
userName = entity.Name;
roomIndexToName[userRoomIndexId] = userName;
} else {
return;
}
}
if (userName == Self.Name) {
Log($"SELF: My position=({userXPos},{userYPos}), Head={headRotation}, Body={bodyRotation}");
return;
}
double distance = Math.Sqrt(Math.Pow(userXPos - Self.X, 2) + Math.Pow(userYPos - Self.Y, 2));
if (distance > maxDetectionDistance) {
return;
}
bool isFirstAppearance = !userStates.ContainsKey(userRoomIndexId);
bool positionChanged = false;
bool rotationChanged = false;
if (isFirstAppearance) {
userStates[userRoomIndexId] = (headRotation, bodyRotation, userXPos, userYPos, DateTime.Now);
Log($"FIRST: {userName} at ({userXPos},{userYPos}), rotation={headRotation}/{bodyRotation}");
return;
}
var previousState = userStates[userRoomIndexId];
positionChanged = (previousState.X != userXPos || previousState.Y != userYPos);
bool headRotationChanged = previousState.HeadRot != headRotation;
bool bodyRotationChanged = previousState.BodyRot != bodyRotation;
rotationChanged = headRotationChanged || bodyRotationChanged;
// Update last moved time if position changed
if (positionChanged) {
userStates[userRoomIndexId] = (headRotation, bodyRotation, userXPos, userYPos, DateTime.Now);
Log($"MOVE: {userName} moved to ({userXPos},{userYPos})");
return; // User is moving, do not detect clicks
} else if (rotationChanged) {
// Only update rotation if position didn't change
userStates[userRoomIndexId] = (headRotation, bodyRotation, userXPos, userYPos, previousState.LastMoved);
} else {
// Neither position nor rotation changed
return;
}
// If we got here, rotation changed but position didn't
// Check if stationary long enough
if (!HasBeenStationaryLongEnough(userRoomIndexId)) {
Log($"TOO RECENT: {userName} moved too recently, ignoring rotation change");
return;
}
// Check if speaking recently
if (HasRecentlySpoken(userName)) {
Log($"TALKING: {userName} recently spoke, ignoring rotation change");
return;
}
int dx = Self.X - userXPos;
int dy = Self.Y - userYPos;
bool headIsFacingMe = IsRotationPossiblyFacingMe(userXPos, userYPos, Self.X, Self.Y, headRotation);
bool bodyIsFacingMe = IsRotationPossiblyFacingMe(userXPos, userYPos, Self.X, Self.Y, bodyRotation);
bool isClickDetected = false;
string clickType = "";
if (bodyRotationChanged) {
if (bodyIsFacingMe || headIsFacingMe) {
isClickDetected = true;
clickType = "body";
}
} else if (headRotationChanged) {
if (headIsFacingMe) {
isClickDetected = true;
clickType = "head";
}
}
if (isClickDetected) {
string message = $"CLICK DETECTED from {userName} ({clickType})";
Log(message);
if (showDetectionsAsShouts) {
Talk(message);
}
if (showVisualIndication) {
Sign(visualIndicationSign);
}
Log($"CLICK: {userName} at ({userXPos},{userYPos}), Me at ({Self.X},{Self.Y})");
Log($"CLICK: Distance: {distance}, Relative: dx={dx}, dy={dy}");
Log($"CLICK: Rotation changed from {previousState.HeadRot}/{previousState.BodyRot} to {headRotation}/{bodyRotation}");
}
});
Log("Click detection is now running - Only detecting stationary users! Detection range: " + maxDetectionDistance);
Wait();