Fix visual novel mode for new expression thingy
| @@ -12,7 +12,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; | |||
| 12 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; | 12 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; |
| 13 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; | 13 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; |
| 14 | import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; | 14 | import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; |
| 15 | import { commonEnumProviders, enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; | 15 | import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 16 | import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandReturnHelper.js'; | 16 | import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandReturnHelper.js'; |
| 17 | import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js'; | 17 | import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js'; |
| 18 | import { Popup, POPUP_RESULT } from '../../popup.js'; | 18 | import { Popup, POPUP_RESULT } from '../../popup.js'; |
| @@ -128,20 +128,20 @@ async function forceUpdateVisualNovelMode() { | |||
| 128 | const updateVisualNovelModeDebounced = debounce(forceUpdateVisualNovelMode, debounce_timeout.quick); | 128 | const updateVisualNovelModeDebounced = debounce(forceUpdateVisualNovelMode, debounce_timeout.quick); |
| 129 | 129 | ||
| 130 | async function updateVisualNovelMode(name, expression) { | 130 | async function updateVisualNovelMode(name, expression) { |
| 131 | const container = $('#visual-novel-wrapper'); | 131 | const vnContainer = $('#visual-novel-wrapper'); |
| 132 | 132 | ||
| 133 | await visualNovelRemoveInactive(container); | 133 | await visualNovelRemoveInactive(vnContainer); |
| 134 | 134 | ||
| 135 | const setSpritePromises = await visualNovelSetCharacterSprites(container, name, expression); | 135 | const setSpritePromises = await visualNovelSetCharacterSprites(vnContainer, name, expression); |
| 136 | 136 | ||
| 137 | // calculate layer indices based on recent messages | 137 | // calculate layer indices based on recent messages |
| 138 | await visualNovelUpdateLayers(container); | 138 | await visualNovelUpdateLayers(vnContainer); |
| 139 | 139 | ||
| 140 | await Promise.allSettled(setSpritePromises); | 140 | await Promise.allSettled(setSpritePromises); |
| 141 | 141 | ||
| 142 | // update again based on new sprites | 142 | // update again based on new sprites |
| 143 | if (setSpritePromises.length > 0) { | 143 | if (setSpritePromises.length > 0) { |
| 144 | await visualNovelUpdateLayers(container); | 144 | await visualNovelUpdateLayers(vnContainer); |
| 145 | } | 145 | } |
| 146 | } | 146 | } |
| 147 | 147 | ||
| @@ -172,53 +172,53 @@ async function visualNovelRemoveInactive(container) { | |||
| 172 | await Promise.allSettled(removeInactiveCharactersPromises); | 172 | await Promise.allSettled(removeInactiveCharactersPromises); |
| 173 | } | 173 | } |
| 174 | 174 | ||
| 175 | async function visualNovelSetCharacterSprites(container, name, expression) { | 175 | /** |
| 176 | * Sets the character sprites for visual novel mode based on the provided container, name, and expression. | ||
| 177 | * | ||
| 178 | * @param {JQuery<HTMLElement>} vnContainer - The container element where the sprites will be set | ||
| 179 | * @param {string} spriteFolderName - The name of the sprite folder | ||
| 180 | * @param {string} expression - The expression to set for the characters | ||
| 181 | * @returns {Promise<Array>} - An array of promises that resolve when the sprites are set | ||
| 182 | */ | ||
| 183 | async function visualNovelSetCharacterSprites(vnContainer, spriteFolderName, expression) { | ||
| 176 | const context = getContext(); | 184 | const context = getContext(); |
| 177 | const group = context.groups.find(x => x.id == context.groupId); | 185 | const group = context.groups.find(x => x.id == context.groupId); |
| 178 | const labels = await getExpressionsList(); | ||
| 179 | 186 | ||
| 180 | const createCharacterPromises = []; | ||
| 181 | const setSpritePromises = []; | 187 | const setSpritePromises = []; |
| 182 | 188 | ||
| 183 | for (const avatar of group.members) { | 189 | for (const avatar of group.members) { |
| 184 | const isDisabled = group.disabled_members.includes(avatar); | ||
| 185 | |||
| 186 | // skip disabled characters | 190 | // skip disabled characters |
| 191 | const isDisabled = group.disabled_members.includes(avatar); | ||
| 187 | if (isDisabled && hideMutedSprites) { | 192 | if (isDisabled && hideMutedSprites) { |
| 188 | continue; | 193 | continue; |
| 189 | } | 194 | } |
| 190 | 195 | ||
| 191 | const character = context.characters.find(x => x.avatar == avatar); | 196 | const character = context.characters.find(x => x.avatar == avatar); |
| 192 | |||
| 193 | if (!character) { | 197 | if (!character) { |
| 194 | continue; | 198 | continue; |
| 195 | } | 199 | } |
| 196 | 200 | ||
| 197 | const spriteFolderName = getSpriteFolderName({ original_avatar: character.avatar }, character.name); | 201 | const expressionImage = vnContainer.find(`.expression-holder[data-avatar="${avatar}"]`); |
| 202 | |||
| 203 | const memberSpriteFolderName = getSpriteFolderName({ original_avatar: character.avatar }, character.name); | ||
| 198 | 204 | ||
| 199 | // download images if not downloaded yet | 205 | // download images if not downloaded yet |
| 200 | if (spriteCache[spriteFolderName] === undefined) { | 206 | if (spriteCache[memberSpriteFolderName] === undefined) { |
| 201 | spriteCache[spriteFolderName] = await getSpritesList(spriteFolderName); | 207 | spriteCache[memberSpriteFolderName] = await getSpritesList(memberSpriteFolderName); |
| 202 | } | 208 | } |
| 203 | 209 | ||
| 204 | const sprites = spriteCache[spriteFolderName]; | 210 | const prevExpressionSrc = expressionImage.find('img').attr('src') || null; |
| 205 | const expressionImage = container.find(`.expression-holder[data-avatar="${avatar}"]`); | ||
| 206 | const defaultExpression = getFallbackExpression(); | ||
| 207 | // TODO: Visual novel sprites need fixing, currently do not update based on multiple sprites, etc | ||
| 208 | const defaultSpritePath = sprites.find(x => x.label === defaultExpression)?.path; | ||
| 209 | const noSprites = sprites.length === 0; | ||
| 210 | 211 | ||
| 211 | if (expressionImage.length > 0) { | 212 | const spriteFile = chooseSpriteForExpression(memberSpriteFolderName, expression, { prevExpressionSrc: prevExpressionSrc }); |
| 212 | if (name == spriteFolderName) { | 213 | if (expressionImage.length) { |
| 213 | await validateImages(spriteFolderName, true); | 214 | if (spriteFolderName == memberSpriteFolderName) { |
| 215 | await validateImages(memberSpriteFolderName, true); | ||
| 214 | setExpressionOverrideHtml(true); // <= force clear expression override input | 216 | setExpressionOverrideHtml(true); // <= force clear expression override input |
| 215 | const currentSpritePath = labels.includes(expression) ? sprites.find(x => x.label === expression)?.path : ''; | 217 | const path = spriteFile?.imageSrc || ''; |
| 216 | |||
| 217 | const path = currentSpritePath || defaultSpritePath || ''; | ||
| 218 | const img = expressionImage.find('img'); | 218 | const img = expressionImage.find('img'); |
| 219 | await setImage(img, path); | 219 | await setImage(img, path); |
| 220 | } | 220 | } |
| 221 | expressionImage.toggleClass('hidden', noSprites); | 221 | expressionImage.toggleClass('hidden', !spriteFile); |
| 222 | } else { | 222 | } else { |
| 223 | const template = $('#expression-holder').clone(); | 223 | const template = $('#expression-holder').clone(); |
| 224 | template.attr('id', `expression-${avatar}`); | 224 | template.attr('id', `expression-${avatar}`); |
| @@ -226,18 +226,19 @@ async function visualNovelSetCharacterSprites(container, name, expression) { | |||
| 226 | template.find('.drag-grabber').attr('id', `expression-${avatar}header`); | 226 | template.find('.drag-grabber').attr('id', `expression-${avatar}header`); |
| 227 | $('#visual-novel-wrapper').append(template); | 227 | $('#visual-novel-wrapper').append(template); |
| 228 | dragElement($(template[0])); | 228 | dragElement($(template[0])); |
| 229 | template.toggleClass('hidden', noSprites); | 229 | template.toggleClass('hidden', !spriteFile); |
| 230 | await setImage(template.find('img'), defaultSpritePath || ''); | 230 | await setImage(template.find('img'), spriteFile?.imageSrc || ''); |
| 231 | const fadeInPromise = new Promise(resolve => { | 231 | const fadeInPromise = new Promise(resolve => { |
| 232 | template.fadeIn(250, () => resolve()); | 232 | template.fadeIn(250, () => resolve()); |
| 233 | }); | 233 | }); |
| 234 | createCharacterPromises.push(fadeInPromise); | 234 | setSpritePromises.push(fadeInPromise); |
| 235 | const setSpritePromise = setLastMessageSprite(template.find('img'), avatar, labels); | ||
| 236 | setSpritePromises.push(setSpritePromise); | ||
| 237 | } | 235 | } |
| 236 | |||
| 237 | if (spriteFile) console.info(`Expression set for group member ${character.name}`, { expression: spriteFile.expression, file: spriteFile.fileName }); | ||
| 238 | else if (expressionImage.length) console.info(`Expression unset for group member ${character.name} - No sprite found`, { expression: expression }); | ||
| 239 | else console.info(`Expression not available for group member ${character.name}`, { expression: expression }); | ||
| 238 | } | 240 | } |
| 239 | 241 | ||
| 240 | await Promise.allSettled(createCharacterPromises); | ||
| 241 | return setSpritePromises; | 242 | return setSpritePromises; |
| 242 | } | 243 | } |
| 243 | 244 | ||
| @@ -276,11 +277,11 @@ async function visualNovelUpdateLayers(container) { | |||
| 276 | const containerWidth = container.width(); | 277 | const containerWidth = container.width(); |
| 277 | const pivotalPoint = containerWidth * 0.5; | 278 | const pivotalPoint = containerWidth * 0.5; |
| 278 | 279 | ||
| 279 | let images = $('#visual-novel-wrapper .expression-holder'); | 280 | let images = Array.from($('#visual-novel-wrapper .expression-holder')).sort(sortFunction); |
| 280 | let imagesWidth = []; | 281 | let imagesWidth = []; |
| 281 | 282 | ||
| 282 | images.sort(sortFunction).each(function () { | 283 | images.forEach(image => { |
| 283 | imagesWidth.push($(this).width()); | 284 | imagesWidth.push($(image).width()); |
| 284 | }); | 285 | }); |
| 285 | 286 | ||
| 286 | let totalWidth = imagesWidth.reduce((a, b) => a + b, 0); | 287 | let totalWidth = imagesWidth.reduce((a, b) => a + b, 0); |
| @@ -294,7 +295,7 @@ async function visualNovelUpdateLayers(container) { | |||
| 294 | currentPosition = 0; // Reset the initial position to 0 | 295 | currentPosition = 0; // Reset the initial position to 0 |
| 295 | } | 296 | } |
| 296 | 297 | ||
| 297 | images.sort(sortFunction).each((index, current) => { | 298 | images.forEach((current, index) => { |
| 298 | const element = $(current); | 299 | const element = $(current); |
| 299 | const elementID = element.attr('id'); | 300 | const elementID = element.attr('id'); |
| 300 | 301 | ||
| @@ -327,23 +328,6 @@ async function visualNovelUpdateLayers(container) { | |||
| 327 | await Promise.allSettled(setLayerIndicesPromises); | 328 | await Promise.allSettled(setLayerIndicesPromises); |
| 328 | } | 329 | } |
| 329 | 330 | ||
| 330 | async function setLastMessageSprite(img, avatar, labels) { | ||
| 331 | const context = getContext(); | ||
| 332 | const lastMessage = context.chat.slice().reverse().find(x => x.original_avatar == avatar || (x.force_avatar && x.force_avatar.includes(encodeURIComponent(avatar)))); | ||
| 333 | |||
| 334 | if (lastMessage) { | ||
| 335 | const text = lastMessage.mes || ''; | ||
| 336 | const spriteFolderName = getSpriteFolderName(lastMessage, lastMessage.name); | ||
| 337 | const sprites = spriteCache[spriteFolderName] || []; | ||
| 338 | const label = await getExpressionLabel(text); | ||
| 339 | const path = labels.includes(label) ? sprites.find(x => x.label === label)?.path : ''; | ||
| 340 | |||
| 341 | if (path) { | ||
| 342 | setImage(img, path); | ||
| 343 | } | ||
| 344 | } | ||
| 345 | } | ||
| 346 | |||
| 347 | async function setImage(img, path) { | 331 | async function setImage(img, path) { |
| 348 | // Cohee: If something goes wrong, uncomment this to return to the old behavior | 332 | // Cohee: If something goes wrong, uncomment this to return to the old behavior |
| 349 | /* | 333 | /* |
| @@ -1062,28 +1046,28 @@ function removeExpression() { | |||
| 1062 | 1046 | ||
| 1063 | /** | 1047 | /** |
| 1064 | * Validate a character's sprites, and redraw the sprites list if not done before or forced to redraw. | 1048 | * Validate a character's sprites, and redraw the sprites list if not done before or forced to redraw. |
| 1065 | * @param {string} character - The character to validate | 1049 | * @param {string} spriteFolderName - The character sprite folder to validate |
| 1066 | * @param {boolean} [forceRedrawCached=false] - Whether to force redrawing the sprites list even if it's already been drawn before | 1050 | * @param {boolean} [forceRedrawCached=false] - Whether to force redrawing the sprites list even if it's already been drawn before |
| 1067 | */ | 1051 | */ |
| 1068 | async function validateImages(character, forceRedrawCached = false) { | 1052 | async function validateImages(spriteFolderName, forceRedrawCached = false) { |
| 1069 | if (!character) { | 1053 | if (!spriteFolderName) { |
| 1070 | return; | 1054 | return; |
| 1071 | } | 1055 | } |
| 1072 | 1056 | ||
| 1073 | const labels = await getExpressionsList(); | 1057 | const labels = await getExpressionsList(); |
| 1074 | 1058 | ||
| 1075 | if (spriteCache[character]) { | 1059 | if (spriteCache[spriteFolderName]) { |
| 1076 | if (forceRedrawCached && $('#image_list').data('name') !== character) { | 1060 | if (forceRedrawCached && $('#image_list').data('name') !== spriteFolderName) { |
| 1077 | console.debug('force redrawing character sprites list'); | 1061 | console.debug('force redrawing character sprites list'); |
| 1078 | await drawSpritesList(character, labels, spriteCache[character]); | 1062 | await drawSpritesList(spriteFolderName, labels, spriteCache[spriteFolderName]); |
| 1079 | } | 1063 | } |
| 1080 | 1064 | ||
| 1081 | return; | 1065 | return; |
| 1082 | } | 1066 | } |
| 1083 | 1067 | ||
| 1084 | const sprites = await getSpritesList(character); | 1068 | const sprites = await getSpritesList(spriteFolderName); |
| 1085 | let validExpressions = await drawSpritesList(character, labels, sprites); | 1069 | let validExpressions = await drawSpritesList(spriteFolderName, labels, sprites); |
| 1086 | spriteCache[character] = validExpressions; | 1070 | spriteCache[spriteFolderName] = validExpressions; |
| 1087 | } | 1071 | } |
| 1088 | 1072 | ||
| 1089 | /** | 1073 | /** |
| @@ -1106,20 +1090,20 @@ function getExpressionImageData(sprite) { | |||
| 1106 | 1090 | ||
| 1107 | /** | 1091 | /** |
| 1108 | * Populate the character expression list with sprites for the given character. | 1092 | * Populate the character expression list with sprites for the given character. |
| 1109 | * @param {string} character - The name of the character to populate the list for | 1093 | * @param {string} spriteFolderName - The name of the character to populate the list for |
| 1110 | * @param {string[]} labels - An array of expression labels that are valid | 1094 | * @param {string[]} labels - An array of expression labels that are valid |
| 1111 | * @param {Expression[]} sprites - An array of sprites | 1095 | * @param {Expression[]} sprites - An array of sprites |
| 1112 | * @returns {Promise<Expression[]>} An array of valid expression labels | 1096 | * @returns {Promise<Expression[]>} An array of valid expression labels |
| 1113 | */ | 1097 | */ |
| 1114 | async function drawSpritesList(character, labels, sprites) { | 1098 | async function drawSpritesList(spriteFolderName, labels, sprites) { |
| 1115 | /** @type {Expression[]} */ | 1099 | /** @type {Expression[]} */ |
| 1116 | let validExpressions = []; | 1100 | let validExpressions = []; |
| 1117 | 1101 | ||
| 1118 | $('#no_chat_expressions').hide(); | 1102 | $('#no_chat_expressions').hide(); |
| 1119 | $('#open_chat_expressions').show(); | 1103 | $('#open_chat_expressions').show(); |
| 1120 | $('#image_list').empty(); | 1104 | $('#image_list').empty(); |
| 1121 | $('#image_list').data('name', character); | 1105 | $('#image_list').data('name', spriteFolderName); |
| 1122 | $('#image_list_header_name').text(character); | 1106 | $('#image_list_header_name').text(spriteFolderName); |
| 1123 | 1107 | ||
| 1124 | if (!Array.isArray(labels)) { | 1108 | if (!Array.isArray(labels)) { |
| 1125 | return []; | 1109 | return []; |
| @@ -1331,23 +1315,24 @@ export async function getExpressionsList() { | |||
| 1331 | } | 1315 | } |
| 1332 | 1316 | ||
| 1333 | /** | 1317 | /** |
| 1334 | * Set the expression of a character. | 1318 | * Selects a sprite from the given sprite folder for the given expression. |
| 1335 | * @param {string} character - The name of the character | 1319 | * |
| 1336 | * @param {string} expression - The expression or sprite name to set | 1320 | * If multiple sprites are allowed for the expression, it will randomly select one. |
| 1337 | * @param {Object} options - Optional parameters | 1321 | * If the rerollIfSame option is enabled, it will only select a different sprite if the previous sprite was the same. |
| 1338 | * @param {boolean} [options.force=false] - Whether to force the expression change even if Visual Novel mode is on | 1322 | * If the overrideSpriteFile option is set, it will look for the sprite with the given file name instead of randomly selecting one. |
| 1339 | * @param {string?} [options.overrideSpriteFile=null] - Set if a specific sprite file should be used. Must be sprite file name. | 1323 | * |
| 1340 | * @returns {Promise<void>} A promise that resolves when the expression has been set. | 1324 | * @param {string} spriteFolderName - The name of the sprite folder |
| 1325 | * @param {string} expression - The expression to find the sprite for | ||
| 1326 | * @param {object} [options] - Options to select the sprite | ||
| 1327 | * @param {string} [options.prevExpressionSrc=null] - The source of the previous expression | ||
| 1328 | * @param {string} [options.overrideSpriteFile=null] - The file name of the sprite to select | ||
| 1329 | * @returns {ExpressionImage} - The selected sprite | ||
| 1341 | */ | 1330 | */ |
| 1342 | async function setExpression(character, expression, { force = false, overrideSpriteFile = null } = {}) { | 1331 | function chooseSpriteForExpression(spriteFolderName, expression, { prevExpressionSrc = null, overrideSpriteFile = null } = {}) { |
| 1343 | await validateImages(character); | 1332 | const sprite = (spriteCache[spriteFolderName] && spriteCache[spriteFolderName].find(x => x.label === expression)); |
| 1344 | const img = $('img.expression'); | 1333 | if (!(sprite?.files.length > 0)) |
| 1345 | const prevExpressionSrc = img.attr('src'); | 1334 | return null; |
| 1346 | const expressionClone = img.clone(); | ||
| 1347 | 1335 | ||
| 1348 | /** @type {Expression} */ | ||
| 1349 | const sprite = (spriteCache[character] && spriteCache[character].find(x => x.label === expression)); | ||
| 1350 | if (sprite && sprite.files.length > 0) { | ||
| 1351 | let spriteFile = sprite.files[0]; | 1336 | let spriteFile = sprite.files[0]; |
| 1352 | 1337 | ||
| 1353 | // If a specific sprite file should be set, we are looking it up here | 1338 | // If a specific sprite file should be set, we are looking it up here |
| @@ -1360,28 +1345,47 @@ async function setExpression(character, expression, { force = false, overrideSpr | |||
| 1360 | else if (extension_settings.expressions.allowMultiple && sprite.files.length > 1) { | 1345 | else if (extension_settings.expressions.allowMultiple && sprite.files.length > 1) { |
| 1361 | let possibleFiles = sprite.files; | 1346 | let possibleFiles = sprite.files; |
| 1362 | if (extension_settings.expressions.rerollIfSame) { | 1347 | if (extension_settings.expressions.rerollIfSame) { |
| 1363 | possibleFiles = possibleFiles.filter(x => x.imageSrc !== prevExpressionSrc); | 1348 | possibleFiles = possibleFiles.filter(x => !prevExpressionSrc || x.imageSrc !== prevExpressionSrc); |
| 1364 | } | 1349 | } |
| 1365 | spriteFile = possibleFiles[Math.floor(Math.random() * possibleFiles.length)]; | 1350 | spriteFile = possibleFiles[Math.floor(Math.random() * possibleFiles.length)]; |
| 1366 | } | 1351 | } |
| 1367 | 1352 | ||
| 1353 | return spriteFile; | ||
| 1354 | |||
| 1355 | } | ||
| 1356 | |||
| 1357 | /** | ||
| 1358 | * Set the expression of a character. | ||
| 1359 | * @param {string} spriteFolderName - The name of the character (folder name - can also be a costume override) | ||
| 1360 | * @param {string} expression - The expression or sprite name to set | ||
| 1361 | * @param {Object} options - Optional parameters | ||
| 1362 | * @param {boolean} [options.force=false] - Whether to force the expression change even if Visual Novel mode is on | ||
| 1363 | * @param {string?} [options.overrideSpriteFile=null] - Set if a specific sprite file should be used. Must be sprite file name. | ||
| 1364 | * @returns {Promise<void>} A promise that resolves when the expression has been set. | ||
| 1365 | */ | ||
| 1366 | async function setExpression(spriteFolderName, expression, { force = false, overrideSpriteFile = null } = {}) { | ||
| 1367 | await validateImages(spriteFolderName); | ||
| 1368 | const img = $('img.expression'); | ||
| 1369 | const prevExpressionSrc = img.attr('src'); | ||
| 1370 | const expressionClone = img.clone(); | ||
| 1371 | |||
| 1372 | const spriteFile = chooseSpriteForExpression(spriteFolderName, expression, { prevExpressionSrc: prevExpressionSrc, overrideSpriteFile: overrideSpriteFile }); | ||
| 1373 | if (spriteFile) { | ||
| 1368 | if (force && isVisualNovelMode()) { | 1374 | if (force && isVisualNovelMode()) { |
| 1369 | const context = getContext(); | 1375 | const context = getContext(); |
| 1370 | const group = context.groups.find(x => x.id === context.groupId); | 1376 | const group = context.groups.find(x => x.id === context.groupId); |
| 1371 | 1377 | ||
| 1372 | for (const member of group.members) { | 1378 | // If it's a folder, make sure we find the group member based on the actual name |
| 1373 | const groupMember = context.characters.find(x => x.avatar === member); | 1379 | const memberName = spriteFolderName.split('/')[0] ?? spriteFolderName; |
| 1374 | |||
| 1375 | if (!groupMember) { | ||
| 1376 | continue; | ||
| 1377 | } | ||
| 1378 | 1380 | ||
| 1379 | if (groupMember.name == character) { | 1381 | const groupMember = group.members |
| 1380 | await setImage($(`.expression-holder[data-avatar="${member}"] img`), spriteFile.imageSrc); | 1382 | .map(member => context.characters.find(x => x.avatar === member)) |
| 1383 | .find(groupMember => groupMember && groupMember.name === memberName); | ||
| 1384 | if (groupMember) { | ||
| 1385 | await setImage($(`.expression-holder[data-avatar="${groupMember.avatar}"] img`), spriteFile.imageSrc); | ||
| 1381 | return; | 1386 | return; |
| 1382 | } | 1387 | } |
| 1383 | } | 1388 | } |
| 1384 | } | ||
| 1385 | 1389 | ||
| 1386 | //only swap expressions when necessary | 1390 | //only swap expressions when necessary |
| 1387 | if (prevExpressionSrc !== spriteFile.imageSrc | 1391 | if (prevExpressionSrc !== spriteFile.imageSrc |
| @@ -1456,7 +1460,7 @@ async function setExpression(character, expression, { force = false, overrideSpr | |||
| 1456 | } else { | 1460 | } else { |
| 1457 | setNone(); | 1461 | setNone(); |
| 1458 | } | 1462 | } |
| 1459 | console.debug('Expression unset'); | 1463 | console.debug('Expression unset - No sprite found', { expression: expression }); |
| 1460 | } | 1464 | } |
| 1461 | 1465 | ||
| 1462 | function setDefault() { | 1466 | function setDefault() { |
| @@ -1969,7 +1973,7 @@ function migrateSettings() { | |||
| 1969 | $(document).on('click', '.expression_list_item', onClickExpressionImage); | 1973 | $(document).on('click', '.expression_list_item', onClickExpressionImage); |
| 1970 | $(document).on('click', '.expression_list_upload', onClickExpressionUpload); | 1974 | $(document).on('click', '.expression_list_upload', onClickExpressionUpload); |
| 1971 | $(document).on('click', '.expression_list_delete', onClickExpressionDelete); | 1975 | $(document).on('click', '.expression_list_delete', onClickExpressionDelete); |
| 1972 | $(window).on('resize', updateVisualNovelModeDebounced); | 1976 | $(window).on('resize', () => updateVisualNovelModeDebounced()); |
| 1973 | $('#open_chat_expressions').hide(); | 1977 | $('#open_chat_expressions').hide(); |
| 1974 | 1978 | ||
| 1975 | await renderAdditionalExpressionSettings(); | 1979 | await renderAdditionalExpressionSettings(); |