Filter inclusion groups by timed effects (#2765) * Filter inclusion groups by timed effects Closes #2762 * Skip group scoring check if sticky entries are present * Optimize sticky checks
Signed| @@ -3953,7 +3953,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 3953 | 3953 | let newContent = ''; |
| 3954 | 3954 | const textToScanTokens = await getTokenCountAsync(allActivatedText); |
| 3955 | 3955 | |
| 3956 | 3956 | filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState, timedEffects); |
| 3957 | 3957 | |
| 3958 | 3958 | console.debug('[WI] --- PROBABILITY CHECKS ---'); |
| 3959 | 3959 | for (const entry of newEntries) { |
| @@ -4143,8 +4143,9 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 4143 | 4143 | * @param {WorldInfoBuffer} buffer The buffer to use for scoring |
| 4144 | 4144 | * @param {(entry: WIScanEntry) => void} removeEntry The function to remove an entry |
| 4145 | 4145 | * @param {number} scanState The current scan state |
| 4146 | + * @param {Map<string, boolean>} hasStickyMap The sticky entries map | |
| 4146 | 4147 | */ |
| 4147 | 4148 | function filterGroupsByScoring(groups, buffer, removeEntry, scanState, hasStickyMap) { |
| 4148 | 4149 | for (const [key, group] of Object.entries(groups)) { |
| 4149 | 4150 | // Group scoring is disabled both globally and for the group entries |
| 4150 | 4151 | if (!world_info_use_group_scoring && !group.some(x => x.useGroupScoring)) { |
| @@ -4152,6 +4153,13 @@ function filterGroupsByScoring(groups, buffer, removeEntry, scanState) { | ||
| 4152 | 4153 | continue; |
| 4153 | 4154 | } |
| 4154 | 4155 | |
| 4156 | + // If the group has any sticky entries, the rest are already removed by the timed effects filter | |
| 4157 | + const hasAnySticky = hasStickyMap.get(key); | |
| 4158 | + if (hasAnySticky) { | |
| 4159 | + console.debug(`[WI] Skipping group scoring check, group '${key}' has sticky entries`); | |
| 4160 | + continue; | |
| 4161 | + } | |
| 4162 | + | |
| 4155 | 4163 | const scores = group.map(entry => buffer.getScore(entry, scanState)); |
| 4156 | 4164 | const maxScore = Math.max(...scores); |
| 4157 | 4165 | console.debug(`[WI] Group '${key}' max score:`, maxScore); |
| @@ -4176,13 +4184,64 @@ function filterGroupsByScoring(groups, buffer, removeEntry, scanState) { | ||
| 4176 | 4184 | } |
| 4177 | 4185 | |
| 4178 | 4186 | /** |
| 4187 | + * Removes entries on cooldown and forces sticky entries as winners. | |
| 4188 | + * @param {Record<string, WIScanEntry[]>} groups The groups to filter | |
| 4189 | + * @param {WorldInfoTimedEffects} timedEffects The timed effects to use | |
| 4190 | + * @param {(entry: WIScanEntry) => void} removeEntry The function to remove an entry | |
| 4191 | + * @returns {Map<string, boolean>} If any sticky entries were found | |
| 4192 | + */ | |
| 4193 | +function filterGroupsByTimedEffects(groups, timedEffects, removeEntry) { | |
| 4194 | + /** @type {Map<string, boolean>} */ | |
| 4195 | + const hasStickyMap = new Map(); | |
| 4196 | + | |
| 4197 | + for (const [key, group] of Object.entries(groups)) { | |
| 4198 | + hasStickyMap.set(key, false); | |
| 4199 | + | |
| 4200 | + // If the group has any sticky entries, leave only the sticky entries | |
| 4201 | + const stickyEntries = group.filter(x => timedEffects.isEffectActive('sticky', x)); | |
| 4202 | + if (stickyEntries.length) { | |
| 4203 | + for (const entry of group) { | |
| 4204 | + if (stickyEntries.includes(entry)) { | |
| 4205 | + continue; | |
| 4206 | + } | |
| 4207 | + | |
| 4208 | + console.debug(`[WI] Entry ${entry.uid}`, `removed as a non-sticky loser from inclusion group '${key}'`, entry); | |
| 4209 | + removeEntry(entry); | |
| 4210 | + } | |
| 4211 | + | |
| 4212 | + hasStickyMap.set(key, true); | |
| 4213 | + } | |
| 4214 | + | |
| 4215 | + // It should not be possible for an entry on cooldown/delay to event get into the grouping phase but @Wolfsblvt told me to leave it here. | |
| 4216 | + const cooldownEntries = group.filter(x => timedEffects.isEffectActive('cooldown', x)); | |
| 4217 | + if (cooldownEntries.length) { | |
| 4218 | + console.debug(`[WI] Inclusion group '${key}' has entries on cooldown. They will be removed.`, cooldownEntries); | |
| 4219 | + for (const entry of cooldownEntries) { | |
| 4220 | + removeEntry(entry); | |
| 4221 | + } | |
| 4222 | + } | |
| 4223 | + | |
| 4224 | + const delayEntries = group.filter(x => timedEffects.isEffectActive('delay', x)); | |
| 4225 | + if (delayEntries.length) { | |
| 4226 | + console.debug(`[WI] Inclusion group '${key}' has entries with delay. They will be removed.`, delayEntries); | |
| 4227 | + for (const entry of delayEntries) { | |
| 4228 | + removeEntry(entry); | |
| 4229 | + } | |
| 4230 | + } | |
| 4231 | + } | |
| 4232 | + | |
| 4233 | + return hasStickyMap; | |
| 4234 | +} | |
| 4235 | + | |
| 4236 | +/** | |
| 4179 | 4237 | * Filters entries by inclusion groups. |
| 4180 | 4238 | * @param {object[]} newEntries Entries activated on current recursion level |
| 4181 | 4239 | * @param {Set<object>} allActivatedEntries Set of all activated entries |
| 4182 | 4240 | * @param {WorldInfoBuffer} buffer The buffer to use for scanning |
| 4183 | 4241 | * @param {number} scanState The current scan state |
| 4242 | + * @param {WorldInfoTimedEffects} timedEffects The timed effects currently active | |
| 4184 | 4243 | */ |
| 4185 | 4244 | function filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState, timedEffects) { |
| 4186 | 4245 | console.debug('[WI] --- INCLUSION GROUP CHECKS ---'); |
| 4187 | 4246 | |
| 4188 | 4247 | const grouped = newEntries.filter(x => x.group).reduce((acc, item) => { |
| @@ -4212,11 +4271,19 @@ function filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanSt | ||
| 4212 | 4271 | } |
| 4213 | 4272 | } |
| 4214 | 4273 | |
| 4215 | 4274 | filterGroupsByScoringconst hasStickyMap = filterGroupsByTimedEffects(grouped, buffertimedEffects, removeEntry, scanState); |
| 4275 | + filterGroupsByScoring(grouped, buffer, removeEntry, scanState, hasStickyMap); | |
| 4216 | 4276 | |
| 4217 | 4277 | for (const [key, group] of Object.entries(grouped)) { |
| 4218 | 4278 | console.debug(`[WI] Checking inclusion group '${key}' with ${group.length} entries`, group); |
| 4219 | 4279 | |
| 4280 | + // If the group has any sticky entries, the rest are already removed by the timed effects filter | |
| 4281 | + const hasAnySticky = hasStickyMap.get(key); | |
| 4282 | + if (hasAnySticky) { | |
| 4283 | + console.debug(`[WI] Skipping inclusion group check, group '${key}' has sticky entries`); | |
| 4284 | + continue; | |
| 4285 | + } | |
| 4286 | + | |
| 4220 | 4287 | if (Array.from(allActivatedEntries).some(x => x.group === key)) { |
| 4221 | 4288 | console.debug(`[WI] Skipping inclusion group check, group '${key}' was already activated`); |
| 4222 | 4289 | // We need to forcefully deactivate all other entries in the group |