int maxDetectionDistance = 40; int speechCooldownMs = 5000; int movementCooldownMs = 600; bool showDetectionsAsShouts = true; bool showVisualIndication = true; int visualIndicationSign = 9; Dictionary lastSpokeTime = new Dictionary(); Dictionary userStates = new Dictionary(); Dictionary roomIndexToName = new Dictionary(); 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();