"use strict";//// Chat.js//// By Don Hopkins (dhopkins@donhopkins.com) on May 5th, 2017// Copyright 2017 High Fidelity, Inc.// Copyright 2024 Overte e.V.////// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// (function() { var webPageURL = Script.resolvePath("ChatPage.html"); // URL of tablet web page. var randomizeWebPageURL = true; // Set to true for debugging. var lastWebPageURL = ""; // Last random URL of tablet web page. var onChatPage = false; // True when chat web page is opened. var webHandlerConnected = false; // True when the web handler has been connected. var channelName = "Chat"; // Unique name for channel that we listen to. var tabletButtonName = "CHAT"; // Tablet button label. var tabletButtonIcon = "icons/tablet-icons/menu-i.svg"; // Icon for chat button. var tabletButtonActiveIcon = "icons/tablet-icons/menu-a.svg"; // Active icon for chat button. var tabletButton = null; // The button we create in the tablet. var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); // The awesome tablet. var chatLog = []; // Array of chat messages in the form of [avatarID, displayName, message, data]. var avatarIdentifiers = {}; // Map of avatar ids to dict of identifierParams. var speechBubbleShowing = false; // Is the speech bubble visible? var speechBubbleMessage = null; // The message shown in the speech bubble. var speechBubbleData = null; // The data of the speech bubble message. var speechBubbleTextID = null; // The id of the speech bubble local text entity. var speechBubbleTimer = null; // The timer to pop down the speech bubble. var speechBubbleParams = null; // The params used to create or edit the speech bubble. // Persistent variables saved in the Settings. var chatName = ''; // The user's name shown in chat. var chatLogMaxSize = 100; // The maximum number of chat messages we remember. var sendTyping = true; // Send typing begin and end notification. var identifyAvatarDuration = 10; // How long to leave the avatar identity line up, in seconds. var identifyAvatarLineColor = { red: 0, green: 255, blue: 0 }; // The color of the avatar identity line. var identifyAvatarMyJointName = 'Head'; // My bone from which to draw the avatar identity line. var identifyAvatarYourJointName = 'Head'; // Your bone to which to draw the avatar identity line. var speechBubbleDuration = 10; // How long to leave the speech bubble up, in seconds. var speechBubbleTextColor = {red: 255, green: 255, blue: 255}; // The text color of the speech bubble. var speechBubbleBackgroundColor = {red: 0, green: 0, blue: 0}; // The background color of the speech bubble. var speechBubbleOffset = {x: 0, y: 0.3, z: 0.0}; // The offset from the joint to whic the speech bubble is attached. var speechBubbleJointName = 'Head'; // The name of the joint to which the speech bubble is attached. var speechBubbleLineHeight = 0.05; // The height of a line of text in the speech bubble. var SPEECH_BUBBLE_MAX_WIDTH = 1; // meters var textSizeOverlay = Entities.addEntity({ "type": "Text", "position": MyAvatar.position, "lineHeight": speechBubbleLineHeight, "leftMargin": 0, "topMargin": 0, "rightMargin": 0, "bottomMargin": 0, "unlit": true, "alignment": "center", "ignorePickIntersection": true, "visible": false }, "local"); // Load the persistent variables from the Settings, with defaults. function loadSettings() { chatName = Settings.getValue('Chat_chatName', MyAvatar.displayName); if (!chatName) { chatName = randomAvatarName(); } chatLogMaxSize = Settings.getValue('Chat_chatLogMaxSize', 100); sendTyping = Settings.getValue('Chat_sendTyping', true); identifyAvatarDuration = Settings.getValue('Chat_identifyAvatarDuration', 10); identifyAvatarLineColor = Settings.getValue('Chat_identifyAvatarLineColor', { red: 0, green: 255, blue: 0 }); identifyAvatarMyJointName = Settings.getValue('Chat_identifyAvatarMyJointName', 'Head'); identifyAvatarYourJointName = Settings.getValue('Chat_identifyAvatarYourJointName', 'Head'); speechBubbleDuration = Settings.getValue('Chat_speechBubbleDuration', 10); speechBubbleTextColor = Settings.getValue('Chat_speechBubbleTextColor', {red: 255, green: 255, blue: 255}); speechBubbleBackgroundColor = Settings.getValue('Chat_speechBubbleBackgroundColor', {red: 0, green: 0, blue: 0}); speechBubbleOffset = Settings.getValue('Chat_speechBubbleOffset', {x: 0.0, y: 0.3, z:0.0}); speechBubbleJointName = Settings.getValue('Chat_speechBubbleJointName', 'Head'); speechBubbleLineHeight = Settings.getValue('Chat_speechBubbleLineHeight', 0.05); Entities.editEntity(textSizeOverlay, { "lineHeight": speechBubbleLineHeight }); saveSettings(); } // Save the persistent variables to the Settings. function saveSettings() { Settings.setValue('Chat_chatName', chatName); Settings.setValue('Chat_chatLogMaxSize', chatLogMaxSize); Settings.setValue('Chat_sendTyping', sendTyping); Settings.setValue('Chat_identifyAvatarDuration', identifyAvatarDuration); Settings.setValue('Chat_identifyAvatarLineColor', identifyAvatarLineColor); Settings.setValue('Chat_identifyAvatarMyJointName', identifyAvatarMyJointName); Settings.setValue('Chat_identifyAvatarYourJointName', identifyAvatarYourJointName); Settings.setValue('Chat_speechBubbleDuration', speechBubbleDuration); Settings.setValue('Chat_speechBubbleTextColor', speechBubbleTextColor); Settings.setValue('Chat_speechBubbleBackgroundColor', speechBubbleBackgroundColor); Settings.setValue('Chat_speechBubbleOffset', speechBubbleOffset); Settings.setValue('Chat_speechBubbleJointName', speechBubbleJointName); Settings.setValue('Chat_speechBubbleLineHeight', speechBubbleLineHeight); } // Reset the Settings and persistent variables to the defaults. function resetSettings() { Settings.setValue('Chat_chatName', null); Settings.setValue('Chat_chatLogMaxSize', null); Settings.setValue('Chat_sendTyping', null); Settings.setValue('Chat_identifyAvatarDuration', null); Settings.setValue('Chat_identifyAvatarLineColor', null); Settings.setValue('Chat_identifyAvatarMyJointName', null); Settings.setValue('Chat_identifyAvatarYourJointName', null); Settings.setValue('Chat_speechBubbleDuration', null); Settings.setValue('Chat_speechBubbleTextColor', null); Settings.setValue('Chat_speechBubbleBackgroundColor', null); Settings.setValue('Chat_speechBubbleOffset', null); Settings.setValue('Chat_speechBubbleJointName', null); Settings.setValue('Chat_speechBubbleLineHeight', null); loadSettings(); } // Update anything that might depend on the settings. function updateSettings() { updateSpeechBubble(); trimChatLog(); updateChatPage(); } // Trim the chat log so it is no longer than chatLogMaxSize lines. function trimChatLog() { if (chatLog.length > chatLogMaxSize) { chatLog.splice(0, chatLogMaxSize - chatLog.length); } } // Clear the local chat log. function clearChatLog() { //print("clearChatLog"); chatLog = []; updateChatPage(); } // We got a chat message from the channel. // Trim the chat log, save the latest message in the chat log, // and show the message on the tablet, if the chat page is showing. function handleTransmitChatMessage(avatarID, displayName, message, data) { //print("receiveChat", "avatarID", avatarID, "displayName", displayName, "message", message, "data", data); trimChatLog(); chatLog.push([avatarID, displayName, message, data]); if (onChatPage) { tablet.emitScriptEvent( JSON.stringify({ type: "ReceiveChatMessage", avatarID: avatarID, displayName: displayName, message: message, data: data })); } } // Trim the chat log, save the latest log message in the chat log, // and show the message on the tablet, if the chat page is showing. function logMessage(message, data) { //print("logMessage", message, data); trimChatLog(); chatLog.push([null, null, message, data]); if (onChatPage) { tablet.emitScriptEvent( JSON.stringify({ type: "LogMessage", message: message, data: data })); } } // An empty chat message was entered. // Hide our speech bubble. function emptyChatMessage(data) { popDownSpeechBubble(); } // Notification that we typed a keystroke. function type() { //print("type"); } // Notification that we began typing. // Notify everyone that we started typing. function beginTyping() { //print("beginTyping"); if (!sendTyping) { return; } Messages.sendMessage( channelName, JSON.stringify({ type: 'AvatarBeginTyping', avatarID: MyAvatar.sessionUUID, displayName: chatName })); } // Notification that somebody started typing. function handleAvatarBeginTyping(avatarID, displayName) { //print("handleAvatarBeginTyping:", "avatarID", avatarID, displayName); } // Notification that we stopped typing. // Notify everyone that we stopped typing. function endTyping() { //print("endTyping"); if (!sendTyping) { return; } Messages.sendMessage( channelName, JSON.stringify({ type: 'AvatarEndTyping', avatarID: MyAvatar.sessionUUID, displayName: chatName })); } // Notification that somebody stopped typing. function handleAvatarEndTyping(avatarID, displayName) { //print("handleAvatarEndTyping:", "avatarID", avatarID, displayName); } // Identify an avatar by drawing a line from our head to their head. // If the avatar is our own, then just draw a line up into the sky. function identifyAvatar(yourAvatarID) { //print("identifyAvatar", yourAvatarID); unidentifyAvatars(); var myAvatarID = MyAvatar.sessionUUID; var myJointIndex = MyAvatar.getJointIndex(identifyAvatarMyJointName); var myJointRotation = Quat.multiply( MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(myJointIndex)); var myJointPosition = Vec3.sum( MyAvatar.position, Vec3.multiplyQbyV( MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(myJointIndex))); var yourJointIndex = -1; var yourJointPosition; if (yourAvatarID == myAvatarID) { // You pointed at your own name, so draw a line up from your head. yourJointPosition = { x: myJointPosition.x, y: myJointPosition.y + 1000.0, z: myJointPosition.z }; } else { // You pointed at somebody else's name, so draw a line from your head to their head. var yourAvatar = AvatarList.getAvatar(yourAvatarID); if (!yourAvatar) { return; } yourJointIndex = yourAvatar.getJointIndex(identifyAvatarMyJointName) var yourJointRotation = Quat.multiply( yourAvatar.orientation, yourAvatar.getAbsoluteJointRotationInObjectFrame(yourJointIndex)); yourJointPosition = Vec3.sum( yourAvatar.position, Vec3.multiplyQbyV( yourAvatar.orientation, yourAvatar.getAbsoluteJointTranslationInObjectFrame(yourJointIndex))); } var identifierParams = { "type": "PolyLine", "parentID": myAvatarID, "parentJointIndex": myJointIndex, "lifetime": identifyAvatarDuration, "linePoints": [ myJointPosition, yourJointPosition ], "strokeWidths": [ 0.02, 0.02], //endParentID: yourAvatarID, //Currently doesn't work. Never implemented. //endParentJointIndex: yourJointIndex, //Currently doesn't work. Never implemented. "color": identifyAvatarLineColor }; avatarIdentifiers[yourAvatarID] = identifierParams; identifierParams.lineID = Entities.addEntity(identifierParams, "local"); //print("ADDOVERLAY lineID", lineID, "myJointPosition", JSON.stringify(myJointPosition), "yourJointPosition", JSON.stringify(yourJointPosition), "lineData", JSON.stringify(lineData)); identifierParams.timer = Script.setTimeout(function() { //print("DELETEOVERLAY lineID"); unidentifyAvatar(yourAvatarID); }, identifyAvatarDuration * 1000); } // Stop identifying an avatar. function unidentifyAvatar(yourAvatarID) { //print("unidentifyAvatar", yourAvatarID); var identifierParams = avatarIdentifiers[yourAvatarID]; if (!identifierParams) { return; } if (identifierParams.timer) { Script.clearTimeout(identifierParams.timer); } if (identifierParams.lineID) { Entities.deleteEntity(identifierParams.lineID); } delete avatarIdentifiers[yourAvatarID]; } // Stop identifying all avatars. function unidentifyAvatars() { var ids = []; for (var avatarID in avatarIdentifiers) { ids.push(avatarID); } for (var i = 0, n = ids.length; i < n; i++) { var avatarID = ids[i]; unidentifyAvatar(avatarID); } } // Turn to face another avatar. function faceAvatar(yourAvatarID, displayName) { //print("faceAvatar:", yourAvatarID, displayName); var myAvatarID = MyAvatar.sessionUUID; if (yourAvatarID == myAvatarID) { // You clicked on yourself. return; } var yourAvatar = AvatarList.getAvatar(yourAvatarID); if (!yourAvatar) { logMessage(displayName + ' is not here!', null); return; } // Project avatar positions to the floor and get the direction between those points, // then face my avatar towards your avatar. var yourPosition = yourAvatar.position; yourPosition.y = 0; var myPosition = MyAvatar.position; myPosition.y = 0; var myOrientation = Quat.lookAtSimple(myPosition, yourPosition); MyAvatar.orientation = myOrientation; } // Make a hopefully unique random anonymous avatar name. function randomAvatarName() { return 'Anon_' + Math.floor(Math.random() * 1000000); } // Change the avatar size to bigger. function biggerSize() { //print("biggerSize"); logMessage("Increasing avatar size", null); MyAvatar.increaseSize(); } // Change the avatar size to smaller. function smallerSize() { //print("smallerSize"); logMessage("Decreasing avatar size", null); MyAvatar.decreaseSize(); } // Set the avatar size to normal. function normalSize() { //print("normalSize"); logMessage("Resetting avatar size to normal!", null); MyAvatar.resetSize(); } // Send out a "Who" message, including our avatarID as myAvatarID, // which will be sent in the response, so we can tell the reply // is to our request. function transmitWho() { //print("transmitWho"); logMessage("Who is here?", null); Messages.sendMessage( channelName, JSON.stringify({ type: 'Who', myAvatarID: MyAvatar.sessionUUID })); } // Send a reply to a "Who" message, with a friendly message, // our avatarID and our displayName. myAvatarID is the id // of the avatar who send the Who message, to whom we're // responding. function handleWho(myAvatarID) { var avatarID = MyAvatar.sessionUUID; if (myAvatarID == avatarID) { // Don't reply to myself. return; } var message = "I'm here!"; var data = {}; Messages.sendMessage( channelName, JSON.stringify({ type: 'ReplyWho', myAvatarID: myAvatarID, avatarID: avatarID, displayName: chatName, message: message, data: data })); } // Receive the reply to a "Who" message. Ignore it unless we were the one // who sent it out (if myAvatarIS is our avatar's id). function handleReplyWho(myAvatarID, avatarID, displayName, message, data) { if (myAvatarID != MyAvatar.sessionUUID) { return; } handleTransmitChatMessage(avatarID, displayName, message, data); } // Handle input form the user, possibly multiple lines separated by newlines. // Each line may be a chat command starting with "/", or a chat message. function handleChatMessage(message, data) { var messageLines = message.trim().split('\n'); for (var i = 0, n = messageLines.length; i < n; i++) { var messageLine = messageLines[i]; if (messageLine.substr(0, 1) == '/') { handleChatCommand(messageLine, data); } else { transmitChatMessage(messageLine, data); } } } // Handle a chat command prefixed by "/". function handleChatCommand(message, data) { var commandLine = message.substr(1); var tokens = commandLine.trim().split(' '); var command = tokens[0]; var rest = commandLine.substr(command.length + 1).trim(); //print("commandLine", commandLine, "command", command, "tokens", tokens, "rest", rest); switch (command) { case '?': case 'help': logMessage('Type "/?" or "/help" for help', null); logMessage('Type "/name <name>" to set your chat name, or "/name" to use your display name. If your display name is not defined, a random name will be used.', null); logMessage('Type "/close" to close your overhead chat message.', null); logMessage('Type "/say <something>" to display a new message.', null);Lines 1–500 of 1019. Load more
"use strict"; // // Chat.js // // By Don Hopkins (dhopkins@donhopkins.com) on May 5th, 2017 // Copyright 2017 High Fidelity, Inc. // Copyright 2024 Overte e.V. // // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // (function() { var webPageURL = Script.resolvePath("ChatPage.html"); // URL of tablet web page. var randomizeWebPageURL = true; // Set to true for debugging. var lastWebPageURL = ""; // Last random URL of tablet web page. var onChatPage = false; // True when chat web page is opened. var webHandlerConnected = false; // True when the web handler has been connected. var channelName = "Chat"; // Unique name for channel that we listen to. var tabletButtonName = "CHAT"; // Tablet button label. var tabletButtonIcon = "icons/tablet-icons/menu-i.svg"; // Icon for chat button. var tabletButtonActiveIcon = "icons/tablet-icons/menu-a.svg"; // Active icon for chat button. var tabletButton = null; // The button we create in the tablet. var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); // The awesome tablet. var chatLog = []; // Array of chat messages in the form of [avatarID, displayName, message, data]. var avatarIdentifiers = {}; // Map of avatar ids to dict of identifierParams. var speechBubbleShowing = false; // Is the speech bubble visible? var speechBubbleMessage = null; // The message shown in the speech bubble. var speechBubbleData = null; // The data of the speech bubble message. var speechBubbleTextID = null; // The id of the speech bubble local text entity. var speechBubbleTimer = null; // The timer to pop down the speech bubble. var speechBubbleParams = null; // The params used to create or edit the speech bubble. // Persistent variables saved in the Settings. var chatName = ''; // The user's name shown in chat. var chatLogMaxSize = 100; // The maximum number of chat messages we remember. var sendTyping = true; // Send typing begin and end notification. var identifyAvatarDuration = 10; // How long to leave the avatar identity line up, in seconds. var identifyAvatarLineColor = { red: 0, green: 255, blue: 0 }; // The color of the avatar identity line. var identifyAvatarMyJointName = 'Head'; // My bone from which to draw the avatar identity line. var identifyAvatarYourJointName = 'Head'; // Your bone to which to draw the avatar identity line. var speechBubbleDuration = 10; // How long to leave the speech bubble up, in seconds. var speechBubbleTextColor = {red: 255, green: 255, blue: 255}; // The text color of the speech bubble. var speechBubbleBackgroundColor = {red: 0, green: 0, blue: 0}; // The background color of the speech bubble. var speechBubbleOffset = {x: 0, y: 0.3, z: 0.0}; // The offset from the joint to whic the speech bubble is attached. var speechBubbleJointName = 'Head'; // The name of the joint to which the speech bubble is attached. var speechBubbleLineHeight = 0.05; // The height of a line of text in the speech bubble. var SPEECH_BUBBLE_MAX_WIDTH = 1; // meters var textSizeOverlay = Entities.addEntity({ "type": "Text", "position": MyAvatar.position, "lineHeight": speechBubbleLineHeight, "leftMargin": 0, "topMargin": 0, "rightMargin": 0, "bottomMargin": 0, "unlit": true, "alignment": "center", "ignorePickIntersection": true, "visible": false }, "local"); // Load the persistent variables from the Settings, with defaults. function loadSettings() { chatName = Settings.getValue('Chat_chatName', MyAvatar.displayName); if (!chatName) { chatName = randomAvatarName(); } chatLogMaxSize = Settings.getValue('Chat_chatLogMaxSize', 100); sendTyping = Settings.getValue('Chat_sendTyping', true); identifyAvatarDuration = Settings.getValue('Chat_identifyAvatarDuration', 10); identifyAvatarLineColor = Settings.getValue('Chat_identifyAvatarLineColor', { red: 0, green: 255, blue: 0 }); identifyAvatarMyJointName = Settings.getValue('Chat_identifyAvatarMyJointName', 'Head'); identifyAvatarYourJointName = Settings.getValue('Chat_identifyAvatarYourJointName', 'Head'); speechBubbleDuration = Settings.getValue('Chat_speechBubbleDuration', 10); speechBubbleTextColor = Settings.getValue('Chat_speechBubbleTextColor', {red: 255, green: 255, blue: 255}); speechBubbleBackgroundColor = Settings.getValue('Chat_speechBubbleBackgroundColor', {red: 0, green: 0, blue: 0}); speechBubbleOffset = Settings.getValue('Chat_speechBubbleOffset', {x: 0.0, y: 0.3, z:0.0}); speechBubbleJointName = Settings.getValue('Chat_speechBubbleJointName', 'Head'); speechBubbleLineHeight = Settings.getValue('Chat_speechBubbleLineHeight', 0.05); Entities.editEntity(textSizeOverlay, { "lineHeight": speechBubbleLineHeight }); saveSettings(); } // Save the persistent variables to the Settings. function saveSettings() { Settings.setValue('Chat_chatName', chatName); Settings.setValue('Chat_chatLogMaxSize', chatLogMaxSize); Settings.setValue('Chat_sendTyping', sendTyping); Settings.setValue('Chat_identifyAvatarDuration', identifyAvatarDuration); Settings.setValue('Chat_identifyAvatarLineColor', identifyAvatarLineColor); Settings.setValue('Chat_identifyAvatarMyJointName', identifyAvatarMyJointName); Settings.setValue('Chat_identifyAvatarYourJointName', identifyAvatarYourJointName); Settings.setValue('Chat_speechBubbleDuration', speechBubbleDuration); Settings.setValue('Chat_speechBubbleTextColor', speechBubbleTextColor); Settings.setValue('Chat_speechBubbleBackgroundColor', speechBubbleBackgroundColor); Settings.setValue('Chat_speechBubbleOffset', speechBubbleOffset); Settings.setValue('Chat_speechBubbleJointName', speechBubbleJointName); Settings.setValue('Chat_speechBubbleLineHeight', speechBubbleLineHeight); } // Reset the Settings and persistent variables to the defaults. function resetSettings() { Settings.setValue('Chat_chatName', null); Settings.setValue('Chat_chatLogMaxSize', null); Settings.setValue('Chat_sendTyping', null); Settings.setValue('Chat_identifyAvatarDuration', null); Settings.setValue('Chat_identifyAvatarLineColor', null); Settings.setValue('Chat_identifyAvatarMyJointName', null); Settings.setValue('Chat_identifyAvatarYourJointName', null); Settings.setValue('Chat_speechBubbleDuration', null); Settings.setValue('Chat_speechBubbleTextColor', null); Settings.setValue('Chat_speechBubbleBackgroundColor', null); Settings.setValue('Chat_speechBubbleOffset', null); Settings.setValue('Chat_speechBubbleJointName', null); Settings.setValue('Chat_speechBubbleLineHeight', null); loadSettings(); } // Update anything that might depend on the settings. function updateSettings() { updateSpeechBubble(); trimChatLog(); updateChatPage(); } // Trim the chat log so it is no longer than chatLogMaxSize lines. function trimChatLog() { if (chatLog.length > chatLogMaxSize) { chatLog.splice(0, chatLogMaxSize - chatLog.length); } } // Clear the local chat log. function clearChatLog() { //print("clearChatLog"); chatLog = []; updateChatPage(); } // We got a chat message from the channel. // Trim the chat log, save the latest message in the chat log, // and show the message on the tablet, if the chat page is showing. function handleTransmitChatMessage(avatarID, displayName, message, data) { //print("receiveChat", "avatarID", avatarID, "displayName", displayName, "message", message, "data", data); trimChatLog(); chatLog.push([avatarID, displayName, message, data]); if (onChatPage) { tablet.emitScriptEvent( JSON.stringify({ type: "ReceiveChatMessage", avatarID: avatarID, displayName: displayName, message: message, data: data })); } } // Trim the chat log, save the latest log message in the chat log, // and show the message on the tablet, if the chat page is showing. function logMessage(message, data) { //print("logMessage", message, data); trimChatLog(); chatLog.push([null, null, message, data]); if (onChatPage) { tablet.emitScriptEvent( JSON.stringify({ type: "LogMessage", message: message, data: data })); } } // An empty chat message was entered. // Hide our speech bubble. function emptyChatMessage(data) { popDownSpeechBubble(); } // Notification that we typed a keystroke. function type() { //print("type"); } // Notification that we began typing. // Notify everyone that we started typing. function beginTyping() { //print("beginTyping"); if (!sendTyping) { return; } Messages.sendMessage( channelName, JSON.stringify({ type: 'AvatarBeginTyping', avatarID: MyAvatar.sessionUUID, displayName: chatName })); } // Notification that somebody started typing. function handleAvatarBeginTyping(avatarID, displayName) { //print("handleAvatarBeginTyping:", "avatarID", avatarID, displayName); } // Notification that we stopped typing. // Notify everyone that we stopped typing. function endTyping() { //print("endTyping"); if (!sendTyping) { return; } Messages.sendMessage( channelName, JSON.stringify({ type: 'AvatarEndTyping', avatarID: MyAvatar.sessionUUID, displayName: chatName })); } // Notification that somebody stopped typing. function handleAvatarEndTyping(avatarID, displayName) { //print("handleAvatarEndTyping:", "avatarID", avatarID, displayName); } // Identify an avatar by drawing a line from our head to their head. // If the avatar is our own, then just draw a line up into the sky. function identifyAvatar(yourAvatarID) { //print("identifyAvatar", yourAvatarID); unidentifyAvatars(); var myAvatarID = MyAvatar.sessionUUID; var myJointIndex = MyAvatar.getJointIndex(identifyAvatarMyJointName); var myJointRotation = Quat.multiply( MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(myJointIndex)); var myJointPosition = Vec3.sum( MyAvatar.position, Vec3.multiplyQbyV( MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(myJointIndex))); var yourJointIndex = -1; var yourJointPosition; if (yourAvatarID == myAvatarID) { // You pointed at your own name, so draw a line up from your head. yourJointPosition = { x: myJointPosition.x, y: myJointPosition.y + 1000.0, z: myJointPosition.z }; } else { // You pointed at somebody else's name, so draw a line from your head to their head. var yourAvatar = AvatarList.getAvatar(yourAvatarID); if (!yourAvatar) { return; } yourJointIndex = yourAvatar.getJointIndex(identifyAvatarMyJointName) var yourJointRotation = Quat.multiply( yourAvatar.orientation, yourAvatar.getAbsoluteJointRotationInObjectFrame(yourJointIndex)); yourJointPosition = Vec3.sum( yourAvatar.position, Vec3.multiplyQbyV( yourAvatar.orientation, yourAvatar.getAbsoluteJointTranslationInObjectFrame(yourJointIndex))); } var identifierParams = { "type": "PolyLine", "parentID": myAvatarID, "parentJointIndex": myJointIndex, "lifetime": identifyAvatarDuration, "linePoints": [ myJointPosition, yourJointPosition ], "strokeWidths": [ 0.02, 0.02], //endParentID: yourAvatarID, //Currently doesn't work. Never implemented. //endParentJointIndex: yourJointIndex, //Currently doesn't work. Never implemented. "color": identifyAvatarLineColor }; avatarIdentifiers[yourAvatarID] = identifierParams; identifierParams.lineID = Entities.addEntity(identifierParams, "local"); //print("ADDOVERLAY lineID", lineID, "myJointPosition", JSON.stringify(myJointPosition), "yourJointPosition", JSON.stringify(yourJointPosition), "lineData", JSON.stringify(lineData)); identifierParams.timer = Script.setTimeout(function() { //print("DELETEOVERLAY lineID"); unidentifyAvatar(yourAvatarID); }, identifyAvatarDuration * 1000); } // Stop identifying an avatar. function unidentifyAvatar(yourAvatarID) { //print("unidentifyAvatar", yourAvatarID); var identifierParams = avatarIdentifiers[yourAvatarID]; if (!identifierParams) { return; } if (identifierParams.timer) { Script.clearTimeout(identifierParams.timer); } if (identifierParams.lineID) { Entities.deleteEntity(identifierParams.lineID); } delete avatarIdentifiers[yourAvatarID]; } // Stop identifying all avatars. function unidentifyAvatars() { var ids = []; for (var avatarID in avatarIdentifiers) { ids.push(avatarID); } for (var i = 0, n = ids.length; i < n; i++) { var avatarID = ids[i]; unidentifyAvatar(avatarID); } } // Turn to face another avatar. function faceAvatar(yourAvatarID, displayName) { //print("faceAvatar:", yourAvatarID, displayName); var myAvatarID = MyAvatar.sessionUUID; if (yourAvatarID == myAvatarID) { // You clicked on yourself. return; } var yourAvatar = AvatarList.getAvatar(yourAvatarID); if (!yourAvatar) { logMessage(displayName + ' is not here!', null); return; } // Project avatar positions to the floor and get the direction between those points, // then face my avatar towards your avatar. var yourPosition = yourAvatar.position; yourPosition.y = 0; var myPosition = MyAvatar.position; myPosition.y = 0; var myOrientation = Quat.lookAtSimple(myPosition, yourPosition); MyAvatar.orientation = myOrientation; } // Make a hopefully unique random anonymous avatar name. function randomAvatarName() { return 'Anon_' + Math.floor(Math.random() * 1000000); } // Change the avatar size to bigger. function biggerSize() { //print("biggerSize"); logMessage("Increasing avatar size", null); MyAvatar.increaseSize(); } // Change the avatar size to smaller. function smallerSize() { //print("smallerSize"); logMessage("Decreasing avatar size", null); MyAvatar.decreaseSize(); } // Set the avatar size to normal. function normalSize() { //print("normalSize"); logMessage("Resetting avatar size to normal!", null); MyAvatar.resetSize(); } // Send out a "Who" message, including our avatarID as myAvatarID, // which will be sent in the response, so we can tell the reply // is to our request. function transmitWho() { //print("transmitWho"); logMessage("Who is here?", null); Messages.sendMessage( channelName, JSON.stringify({ type: 'Who', myAvatarID: MyAvatar.sessionUUID })); } // Send a reply to a "Who" message, with a friendly message, // our avatarID and our displayName. myAvatarID is the id // of the avatar who send the Who message, to whom we're // responding. function handleWho(myAvatarID) { var avatarID = MyAvatar.sessionUUID; if (myAvatarID == avatarID) { // Don't reply to myself. return; } var message = "I'm here!"; var data = {}; Messages.sendMessage( channelName, JSON.stringify({ type: 'ReplyWho', myAvatarID: myAvatarID, avatarID: avatarID, displayName: chatName, message: message, data: data })); } // Receive the reply to a "Who" message. Ignore it unless we were the one // who sent it out (if myAvatarIS is our avatar's id). function handleReplyWho(myAvatarID, avatarID, displayName, message, data) { if (myAvatarID != MyAvatar.sessionUUID) { return; } handleTransmitChatMessage(avatarID, displayName, message, data); } // Handle input form the user, possibly multiple lines separated by newlines. // Each line may be a chat command starting with "/", or a chat message. function handleChatMessage(message, data) { var messageLines = message.trim().split('\n'); for (var i = 0, n = messageLines.length; i < n; i++) { var messageLine = messageLines[i]; if (messageLine.substr(0, 1) == '/') { handleChatCommand(messageLine, data); } else { transmitChatMessage(messageLine, data); } } } // Handle a chat command prefixed by "/". function handleChatCommand(message, data) { var commandLine = message.substr(1); var tokens = commandLine.trim().split(' '); var command = tokens[0]; var rest = commandLine.substr(command.length + 1).trim(); //print("commandLine", commandLine, "command", command, "tokens", tokens, "rest", rest); switch (command) { case '?': case 'help': logMessage('Type "/?" or "/help" for help', null); logMessage('Type "/name <name>" to set your chat name, or "/name" to use your display name. If your display name is not defined, a random name will be used.', null); logMessage('Type "/close" to close your overhead chat message.', null); logMessage('Type "/say <something>" to display a new message.', null);