overte/scripts/system/domainChat/domainChat.js
2025-02-12 09:51:13 -06:00

264 lines
10 KiB
JavaScript

//
// domainChat.js
//
// Created by Armored Dragon, May 17th, 2024.
// 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
// TODO: Message trimming
(() => {
("use strict");
Script.include([
"./formatting.js"
])
var appIsVisible = false;
var settings = {
external_window: false,
maximum_messages: 200,
join_notification: true
};
// Global vars
var tablet;
var chatOverlayWindow;
var appButton;
var quickMessage;
const channels = ["domain", "local"];
var messageHistory = Settings.getValue("ArmoredChat-Messages", []) || [];
var maxLocalDistance = 20; // Maximum range for the local chat
var palData = AvatarManager.getPalData().data;
Controller.keyPressEvent.connect(keyPressEvent);
Messages.subscribe("chat");
Messages.messageReceived.connect(receivedMessage);
AvatarManager.avatarAddedEvent.connect((sessionId) => { _avatarAction("connected", sessionId); });
AvatarManager.avatarRemovedEvent.connect((sessionId) => { _avatarAction("left", sessionId); });
startup();
function startup() {
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
appButton = tablet.addButton({
icon: Script.resolvePath("./img/icon_white.png"),
activeIcon: Script.resolvePath("./img/icon_black.png"),
text: "CHAT",
sortOrder: 8,
isActive: appIsVisible,
});
// When script ends, remove itself from tablet
Script.scriptEnding.connect(function () {
console.log("Shutting Down");
tablet.removeButton(appButton);
chatOverlayWindow.close();
});
// Overlay button toggle
appButton.clicked.connect(toggleMainChatWindow);
quickMessage = new OverlayWindow({
source: Script.resolvePath("./domainChatQuick.qml"),
});
_openWindow();
}
function toggleMainChatWindow() {
appIsVisible = !appIsVisible;
appButton.editProperties({ isActive: appIsVisible });
chatOverlayWindow.visible = appIsVisible;
// External window was closed; the window does not exist anymore
if (chatOverlayWindow.title == "" && appIsVisible) {
_openWindow();
}
}
function _openWindow() {
chatOverlayWindow = new Desktop.createWindow(
Script.resolvePath("./domainChat.qml"),
{
title: "Chat",
size: { x: 550, y: 400 },
additionalFlags: Desktop.ALWAYS_ON_TOP,
visible: appIsVisible,
presentationMode: Desktop.PresentationMode.VIRTUAL,
}
);
chatOverlayWindow.closed.connect(toggleMainChatWindow);
chatOverlayWindow.fromQml.connect(fromQML);
quickMessage.fromQml.connect(fromQML);
}
async function receivedMessage(channel, message) {
// Is the message a chat message?
channel = channel.toLowerCase();
if (channel !== "chat") return;
if ((message = formatting.toJSON(message)) == null) return; // Make sure we are working with a JSON object we expect, otherwise kill
message = formatting.addTimeAndDateStringToPacket(message);
if (!message.channel) message.channel = "domain"; // We don't know where to put this message. Assume it is a domain wide message.
message.channel = message.channel.toLowerCase(); // Only recognize channel names as lower case.
if (!channels.includes(message.channel)) return; // Check the channel. If the channel is not one we have, do nothing.
if (message.channel == "local" && isTooFar(message.position)) return; // If message is local, and if player is too far away from location, do nothing.
let formattedMessagePacket = { ...message };
formattedMessagePacket.message = await formatting.parseMessage(message.message)
_emitEvent({ type: "show_message", ...formattedMessagePacket }); // Update qml view of to new message.
_notificationCoreMessage(message.displayName, message.message) // Show a new message on screen.
// Create a new variable based on the message that will be saved.
let trimmedPacket = formatting.trimPacketToSave(message);
messageHistory.push(trimmedPacket);
while (messageHistory.length > settings.maximum_messages) {
messageHistory.shift();
}
Settings.setValue("ArmoredChat-Messages", messageHistory);
function isTooFar(messagePosition) {
// Check to see if the message is close enough to the user
return Vec3.distance(MyAvatar.position, messagePosition) > maxLocalDistance;
}
}
function fromQML(event) {
switch (event.type) {
case "send_message":
_sendMessage(event.message, event.channel);
break;
case "setting_change":
// Set the setting value, and save the config
settings[event.setting] = event.value; // Update local settings
_saveSettings(); // Save local settings
// Extra actions to preform.
switch (event.setting) {
case "external_window":
chatOverlayWindow.presentationMode = event.value
? Desktop.PresentationMode.NATIVE
: Desktop.PresentationMode.VIRTUAL;
break;
}
break;
case "action":
switch (event.action) {
case "erase_history":
Settings.setValue("ArmoredChat-Messages", null);
messageHistory = [];
_emitEvent({
type: "clear_messages",
});
break;
}
break;
case "initialized":
// https://github.com/overte-org/overte/issues/824
chatOverlayWindow.visible = appIsVisible; // The "visible" field in the Desktop.createWindow does not seem to work. Force set it to the initial state (false)
_loadSettings();
break;
}
}
function keyPressEvent(event) {
switch (JSON.stringify(event.key)) {
case "16777220": // Enter key
if (HMD.active) return; // Don't allow in VR
quickMessage.sendToQml({
type: "change_visibility",
value: true,
});
}
}
function _sendMessage(message, channel) {
if (message.length == 0) return;
Messages.sendMessage(
"chat",
JSON.stringify({
position: MyAvatar.position,
message: message,
displayName: MyAvatar.sessionDisplayName,
channel: channel,
action: "send_chat_message",
})
);
}
function _avatarAction(type, sessionId) {
Script.setTimeout(async () => {
if (type == "connected") {
palData = AvatarManager.getPalData().data;
}
// Get the display name of the user
let displayName = "";
displayName = AvatarManager.getPalData([sessionId])?.data[0]?.sessionDisplayName || null;
if (displayName == null) {
for (let i = 0; i < palData.length; i++) {
if (palData[i].sessionUUID == sessionId) {
displayName = palData[i].sessionDisplayName;
}
}
}
// Format the packet
let message = addTimeAndDateStringToPacket({});
message.message = `${displayName} ${type}`;
// Show new message on screen
if (settings.join_notification){
_notificationCoreMessage(displayName, type)
}
// Format notification message
let formattedMessagePacket = {...message};
formattedMessagePacket.message = await formatting.parseMessage(message.message);
_emitEvent({ type: "notification", ...formattedMessagePacket });
}, 1500);
}
async function _loadSettings() {
settings = Settings.getValue("ArmoredChat-Config", settings);
if (messageHistory) {
// Load message history
for (message of messageHistory) {
messagePacket = { ...message }; // Create new variable
messagePacket = formatting.addTimeAndDateStringToPacket(messagePacket); // Add timestamp
messagePacket.message = await formatting.parseMessage(messagePacket.message); // Parse the message for the UI
_emitEvent({ type: "show_message", ...messagePacket }); // Send message to UI
}
}
_emitEvent({ type: "initial_settings", settings: settings }); // Send current settings to the app
}
function _saveSettings() {
console.log("Saving config");
Settings.setValue("ArmoredChat-Config", settings);
}
function _notificationCoreMessage(displayName, message){
Messages.sendLocalMessage(
"Floof-Notif",
JSON.stringify({ sender: displayName, text: message })
);
}
/**
* Emit a packet to the HTML front end. Easy communication!
* @param {Object} packet - The Object packet to emit to the HTML
* @param {("show_message"|"clear_messages"|"notification"|"initial_settings")} packet.type - The type of packet it is
*/
function _emitEvent(packet = { type: "" }) {
chatOverlayWindow.sendToQml(packet);
}
})();