Skip to content

Commit 1551c17

Browse files
authored
Merge pull request #1722 from ada-tv/fix/chat-bubbles1
Chat bubble and simplifiedNametag fixes
2 parents 2817cdc + 914bf69 commit 1551c17

2 files changed

Lines changed: 67 additions & 24 deletions

File tree

scripts/communityScripts/chatBubbles/chatBubbles.js

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,29 @@
66
//
77
// Distributed under the Apache License, Version 2.0.
88
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
912
"use strict";
1013

1114
const CHAT_CHANNEL = "chat";
1215

1316
const TYPING_NOTIFICATION_CHANNEL = "Chat-Typing";
1417
const CONFIG_UPDATE_CHANNEL = "ChatBubbles-Config";
1518

16-
const BUBBLE_LIFETIME_SECS = 10;
19+
const BUBBLE_MIN_LIFETIME_SECS = 10;
20+
const BUBBLE_MAX_LIFETIME_SECS = 60; // failsafe to prevent really long perma-messages
21+
const BUBBLE_LINE_LIFETIME_SECS = 3.5;
22+
const BUBBLE_IMAGE_LIFETIME_SECS = 10;
1723
const BUBBLE_FADE_TIME = 1;
1824
const BUBBLE_ANIM_FPS = 15;
1925
const BUBBLE_LINE_HEIGHT = 0.07;
2026
const BUBBLE_WIDTH = 1.3;
21-
const BUBBLE_WIDTH_MAX_CHARS = 24; // roughly 18 ems per meter
27+
const BUBBLE_WIDTH_MAX_CHARS = 36;
28+
const BUBBLE_BG_ALPHA = 0.6;
2229
const MAX_DISTANCE = 20;
30+
const TYPING_NOTIF_HEAD_OFFSET_HEIGHT = 0.48;
31+
const MSG_HEAD_OFFSET_HEIGHT = 0.6;
2332
const SELF_BUBBLES = false;
2433

2534
const NOTIFY_SOUND = SoundCache.getSound(Script.resolvePath("./assets/notify.wav"));
@@ -98,7 +107,10 @@ function ChatBubbles_SpawnBubble(data, senderID) {
98107
currentBubbles[senderID] = {};
99108
}
100109

101-
const scale = AvatarList.getAvatar(senderID).scale;
110+
const avatar = AvatarList.getAvatar(senderID);
111+
const scale = avatar.scale;
112+
const headIndex = avatar.getJointIndex("Head");
113+
const headPos = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex);
102114

103115
let link;
104116
let linkIsImage = false;
@@ -109,19 +121,19 @@ function ChatBubbles_SpawnBubble(data, senderID) {
109121

110122
if (
111123
(maybeURL.startsWith("https://") || maybeURL.startsWith("http://")) &&
112-
!/\s+/g.test(maybeURL) &&
113-
/[A-Za-z0-9-._~:/?#\[\]@!$&'()*+,;%=]+/g.test(maybeURL)
124+
!/\s+/g.test(maybeURL) &&
125+
/[A-Za-z0-9-._~:/?#\[\]@!$&'()*+,;%=]+/g.test(maybeURL)
114126
) {
115127
link = maybeURL;
116128

117129
const chunkBeforeQuery = maybeURL.split("?", 2)[0];
118130

119131
if (
120132
chunkBeforeQuery.endsWith(".jpg") ||
121-
chunkBeforeQuery.endsWith(".png") ||
122-
chunkBeforeQuery.endsWith(".gif") ||
123-
chunkBeforeQuery.endsWith(".svg") ||
124-
chunkBeforeQuery.endsWith(".webp")
133+
chunkBeforeQuery.endsWith(".png") ||
134+
chunkBeforeQuery.endsWith(".gif") ||
135+
chunkBeforeQuery.endsWith(".svg") ||
136+
chunkBeforeQuery.endsWith(".webp")
125137
) {
126138
linkIsImage = true;
127139
}
@@ -131,32 +143,40 @@ function ChatBubbles_SpawnBubble(data, senderID) {
131143
let height = lineCount * BUBBLE_LINE_HEIGHT;
132144

133145
let bubbleEntity;
146+
let lifetime;
147+
134148
if (link !== undefined && linkIsImage) {
135149
height = BUBBLE_WIDTH / 3;
150+
height *= scale;
151+
lifetime = BUBBLE_IMAGE_LIFETIME_SECS;
136152
bubbleEntity = Entities.addEntity({
137153
type: "Image",
138154
parentID: senderID,
139155
imageURL: link,
140156
emissive: true,
141157
keepAspectRatio: true,
142158
ignorePickIntersection: true,
143-
dimensions: [BUBBLE_WIDTH, height, 0.01],
144-
localPosition: [0, scale + (height / 2) + 0.1, 0],
159+
dimensions: Vec3.multiply(scale, [BUBBLE_WIDTH, height, 0.01]),
160+
localPosition: [0, MSG_HEAD_OFFSET_HEIGHT + headPos.y + (height / 2), 0],
145161
canCastShadow: false,
146162
billboardMode: "yaw",
147163
grab: {grabbable: false},
164+
renderLayer: "front",
148165
}, "local");
149166
} else {
167+
height *= scale;
168+
lifetime = BUBBLE_LINE_LIFETIME_SECS * lineCount;
150169
bubbleEntity = Entities.addEntity({
151170
type: "Text",
152171
parentID: senderID,
172+
visible: false,
153173
text: text,
154174
unlit: true,
155175
ignorePickIntersection: (link === undefined),
156-
lineHeight: BUBBLE_LINE_HEIGHT,
157-
dimensions: [BUBBLE_WIDTH, height + 0.04, 0.01],
158-
localPosition: [0, scale + (height / 2) + 0.1, 0],
159-
backgroundAlpha: 0.5,
176+
lineHeight: BUBBLE_LINE_HEIGHT * scale,
177+
dimensions: Vec3.multiply(scale, [BUBBLE_WIDTH, height + 0.04, 0.01]),
178+
localPosition: [0, MSG_HEAD_OFFSET_HEIGHT + headPos.y + (height / 2), 0],
179+
backgroundAlpha: BUBBLE_BG_ALPHA,
160180
textColor: (link === undefined) ? [255, 255, 255] : [128, 240, 255],
161181
textEffect: "outline fill",
162182
textEffectColor: "#000",
@@ -165,7 +185,9 @@ function ChatBubbles_SpawnBubble(data, senderID) {
165185
billboardMode: "yaw",
166186
alignment: "center",
167187
verticalAlignment: "center",
188+
topMargin: -0.004 * scale, // center isn't exactly centered?
168189
grab: {grabbable: false},
190+
renderLayer: "front",
169191
script: (link === undefined && !linkIsImage) ? undefined :
170192
`(function() {
171193
this.mousePressOnEntity = function(entity, event) {
@@ -176,11 +198,21 @@ function ChatBubbles_SpawnBubble(data, senderID) {
176198
};
177199
})`
178200
}, "local");
201+
202+
Script.setTimeout(() => {
203+
const size = Entities.textSize(bubbleEntity, text);
204+
Entities.editEntity(bubbleEntity, {
205+
visible: true,
206+
dimensions: [size.width + (0.06 * scale), size.height + (0.04 * scale), 0.01],
207+
});
208+
}, 100);
209+
// this wait time is annoyingly inconsistent,
210+
// so it needs a decent chunk of time to work properly on all messages
179211
}
180212

181213
for (const bubble of Object.values(currentBubbles[senderID])) {
182214
let { localPosition } = Entities.getEntityProperties(bubble.entity, "localPosition");
183-
localPosition = Vec3.sum(localPosition, [0, height + 0.05, 0]);
215+
localPosition = Vec3.sum(localPosition, [0, height + (0.05 * scale), 0]);
184216
Entities.editEntity(bubble.entity, { localPosition: localPosition });
185217
}
186218

@@ -195,7 +227,7 @@ function ChatBubbles_SpawnBubble(data, senderID) {
195227
if (linkIsImage) {
196228
Entities.editEntity(bubble.entity, { alpha: fade });
197229
} else {
198-
Entities.editEntity(bubble.entity, { textAlpha: fade, backgroundAlpha: fade * 0.5 });
230+
Entities.editEntity(bubble.entity, { textAlpha: fade, backgroundAlpha: fade * BUBBLE_BG_ALPHA });
199231
}
200232
fade -= (1 / BUBBLE_ANIM_FPS) / BUBBLE_FADE_TIME;
201233
}, 1000 / BUBBLE_ANIM_FPS);
@@ -205,7 +237,7 @@ function ChatBubbles_SpawnBubble(data, senderID) {
205237
Entities.deleteEntity(bubble.entity);
206238
delete currentBubbles[senderID][bubbleIndex];
207239
}, BUBBLE_FADE_TIME * 1000);
208-
}, BUBBLE_LIFETIME_SECS * 1000),
240+
}, Math.min(BUBBLE_MAX_LIFETIME_SECS, Math.max(BUBBLE_MIN_LIFETIME_SECS, lifetime)) * 1000),
209241
};
210242

211243
currentBubbles[senderID][bubbleIndex] = bubble;
@@ -233,26 +265,31 @@ function ChatBubbles_IndicatorTick(senderID) {
233265
function ChatBubbles_ShowTypingIndicator(senderID) {
234266
if (typingIndicators[senderID]) { return; }
235267

236-
const scale = AvatarList.getAvatar(senderID).scale;
268+
const avatar = AvatarList.getAvatar(senderID);
269+
const scale = avatar.scale;
270+
const headIndex = avatar.getJointIndex("Head");
271+
const headPos = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex);
237272

238273
const indicatorEntity = Entities.addEntity({
239274
type: "Text",
240275
parentID: senderID,
241276
text: "•••",
242277
unlit: true,
243-
lineHeight: 0.15,
244-
dimensions: [0.18, 0.08, 0.01],
245-
localPosition: [0, scale, 0],
246-
backgroundAlpha: 0.8,
278+
lineHeight: 0.15 * scale,
279+
localDimensions: Vec3.multiply(scale, [0.18, 0.08, 0.01]),
280+
localPosition: [0, (TYPING_NOTIF_HEAD_OFFSET_HEIGHT * scale) + headPos.y, 0],
281+
backgroundAlpha: BUBBLE_BG_ALPHA,
247282
canCastShadow: false,
248283
billboardMode: "full",
249284
alignment: "center",
250285
verticalAlignment: "center",
251286
textEffect: "outline fill",
252287
textEffectColor: "#000",
253288
textEffectThickness: 0.3,
254-
topMargin: -0.06,
289+
topMargin: -0.06 * scale,
255290
grab: {grabbable: false},
291+
ignorePickIntersection: true,
292+
renderLayer: "front",
256293
}, "local");
257294

258295
const indicatorInterval = Script.setInterval(() => ChatBubbles_IndicatorTick(senderID), 1000 / BUBBLE_ANIM_FPS);

scripts/simplifiedUI/ui/simplifiedNametag/resources/modules/nameTagListManager.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,12 @@ function makeNameTag(uuid) {
388388
.add("parentID", parentID)
389389
.add("localPosition", localPosition)
390390
.add("grab", { grabbable: false })
391+
.add("ignorePickIntersection", true)
392+
.add("renderLayer", "front")
393+
.add("alignment", "center")
394+
.add("textEffect", "outline fill")
395+
.add("textEffectColor", [0, 0, 0])
396+
.add("textEffectThickness", 0.4)
391397
.create(CLEAR_ENTITY_EDIT_PROPS);
392398

393399
Script.setTimeout(function () {

0 commit comments

Comments
 (0)