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