Advanced Lorebook v2 — by Icehellionx
Advanced Lorebook v2
— by Icehellionx
The Advanced Lorebook v2 builds upon what you can accomplish with the Dynamic Lorebook. It is constructed to give you access to the full suite of tools available to you, as a creator.
Features:
- Shifts
— Additional keywords may be used used alongside the base keywords. This allows you to assign a second part of the personality/scenario to be added.
F/e: If 'water' and 'magic' are in the same user response, you can make 'water' a shift. This allows you to keep the base information about 'magic' while allowing information specific to 'water magic' be present.
- Fallbacks
— In case you do not specify a value in a field, there is one already present.
The defaults are as follows:
- Probability: 100%
- Priority: 3
- Scenario "" (empty)
If you are looking for a way to more intricately control your worlds, this script is a flat upgrade for you!
/**
* Advanced Lore Book System v2
* Created by Icehellionx
*/
var forever = 9999;
var DEBUG = 0; // Set to 1 to emit [DBG] lines into personality
var APPLY_LIMIT = 6; // Cap how many entries apply per turn (after sort)
// 🟢🟢🟢 Your Lore Entries 🟢🟢🟢
var dynamicLore = [
// === Fantasy/Magic lore ===
{
keywords: ['magic', 'spell', 'wizard'],
priority: 10,
probability: 1,
personality: ', knowledgeable about magical arts and ancient spells',
scenario: ' {{char}} has studied magic for years and can sense magical energies around them.',
triggers: ['knowledge', 'study', 'monster'],
Shifts: [
{
keywords: ['ritual', 'circle', 'glyph'],
personality: ' Their instincts sharpen when ritual geometry is involved.',
scenario: ' Chalk lines and faint glyphs prickle against {{char}}’s senses.'
},
{
keywords: ['wand', 'staff'], Block: ['broken', 'snapped'],
personality: ' Implements focus their casting—and their temper.',
scenario: ' A subtle static gathers around the wand’s tip.'
}
]
},
{
keywords: ['dragon', 'beast', 'monster'],
priority: 10,
probability: 1,
personality: ', experienced with dangerous creatures and their behaviors',
scenario: ' {{char}} has encountered many mythical beasts and knows their weaknesses.',
Shifts: [
{
keywords: ['fire', 'flame', 'burn'],
personality: ' Fire-breathers demand patience and altitude discipline.',
scenario: ' Heat ripples in the air; the scent of scorched stone hangs close.'
}
]
},
// === Historical/Background lore ===
{
keywords: ['war', 'battle', 'soldier'],
priority: 10,
probability: 1,
personality: ', haunted by memories of past conflicts',
scenario: ' {{char}} served in the Great War and bears both visible and invisible scars.',
triggers: ['war', 'battle'],
Shifts: [
{
keywords: ['trench', 'mud', 'artillery'],
personality: ' Old reflexes return—counting seconds between the thuds.',
scenario: ' The world narrows to cover, angles, and breath.'
}
]
},
{
keywords: ['family', 'parent', 'childhood']
// defaults will apply
},
// === Location/World lore ===
{
keywords: ['forest', 'woods', 'tree'],
priority: 10,
probability: 1,
personality: ', deeply connected to nature and forest spirits',
scenario: ' {{char}} spent their youth in the Whispering Woods, learning druidic ways.',
Shifts: [
{
keywords: ['river', 'brook', 'stream'],
personality: ' Waterways make the spirits’ whispers clearer.',
scenario: ' Water chatters over stone; a kingfisher flashes blue.'
}
]
},
{
keywords: ['city', 'town', 'street'],
priority: 10,
personality: ', street-smart and familiar with urban politics',
scenario: ' {{char}} knows every alley and hidden passage in the capital city.',
triggers: ['street', 'alley'],
Shifts: [
{
keywords: ['guard', 'patrol', 'watch'],
personality: ' They clock insignias and patrol timings without looking.',
scenario: ' Bootsteps sync in pairs somewhere out of sight.'
}
]
},
// === Profession/Skill lore ===
{
keywords: ['sword', 'fight', 'weapon'],
priority: 10,
personality: ', disciplined in the ancient fighting arts',
scenario: ' {{char}} trained under Master Korin, learning the Seven Sacred Stances.',
Shifts: [
{
keywords: ['duel', 'spar', 'practice'],
personality: ' Their stance narrows; breath settles into measured beats.',
scenario: ' Dust motes hang between you like a drawn line.'
}
]
},
{
keywords: ['book', 'knowledge', 'study'],
priority: 10,
personality: ', scholarly and well-versed in ancient texts',
scenario: ' {{char}} spent decades in the Great Library, mastering forbidden knowledge.',
triggers: ['knowledge', 'study'],
Shifts: [
{
keywords: ['index', 'catalog', 'archive'],
personality: ' They think in taxonomies and cross-references.',
scenario: ' The mental card catalog flips itself to the right shelf.'
}
]
},
// === Mysterious/Secret lore ===
{
keywords: ['secret', 'hidden', 'truth'],
minMessages: 0, maxMessages: 15,
priority: 10,
personality: ', keeper of ancient secrets is a myth',
scenario: ' {{char}} knows the truth about the Sundering, but will not speak about it.'
},
{
keywords: ['secret', 'hidden', 'truth'],
minMessages: 16, maxMessages: 30,
priority: 10,
personality: ', keeper of ancient secrets that could change everything',
scenario: ' {{char}} knows the truth about the Sundering, but speaks of it only in whispers.'
}
];
// === OUTPUT GUARDS ===
context.character = context.character || {};
context.character.personality = context.character.personality || "";
context.character.scenario = context.character.scenario || "";
// === SAFE DEFAULTS ===
var forever = (typeof forever === "number") ? forever : 9999;
var DEBUG = (typeof DEBUG === "number") ? DEBUG : 0;
var APPLY_LIMIT = (typeof APPLY_LIMIT === "number") ? APPLY_LIMIT : 6;
// === Input normalization ===
var rawMessage = String((context.chat && (context.chat.lastMessage || context.chat.last_message)) || "");
var last = " " + rawMessage.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ") + " ";
// === Helpers ===
function hasWord(h, w){ w = String(w||"").toLowerCase().trim(); if(!w) return false; return h.indexOf(" " + w + " ") !== -1; }
function dbg(s){ if(DEBUG){ context.character.personality += "\n\n[DBG] " + s; } }
function getMinMessages(e){ return (e && typeof e.minMessages==="number") ? e.minMessages : 0; }
function getMaxMessages(e){ return (e && typeof e.maxMessages==="number") ? e.maxMessages : forever; }
function getPersonality(e){ return (e && typeof e.personality==="string") ? e.personality : ""; }
function getScenario(e){ return (e && typeof e.scenario==="string") ? e.scenario : ""; }
function getPriority(e){ var p=(e&&typeof e.priority==="number")?e.priority:3; if(p<1)p=1; if(p>5)p=5; return p; }
function getProbability(e){
var prob=1.0, v=e?e.probability:void 0;
if(typeof v==="number"){ prob=v; }
else if(typeof v==="string"){
var s=v.toLowerCase(), isPct=(s.indexOf("%")!==-1), n=s;
if(isPct){ var cleaned="",i; for(i=0;i<s.length;i++){var ch=s.charAt(i); if(ch!=="%")cleaned+=ch;} n=cleaned; }
var f=parseFloat(n); if(!isNaN(f)){ prob=isPct?(f/100):f; }
}
if(prob<0)prob=0; if(prob>1)prob=1; return prob;
}
function getTriggers(e){ return (e&&e.triggers&&e.triggers.length)?e.triggers:[]; }
function getBlock(s){ return (s&&s.Block&&s.Block.length)?s.Block:[]; }
function getKeywords(e){ return (e&&e.keywords&&e.keywords.length)?e.keywords:[]; }
// === Dedupe helper ===
function dedupe(arr){
var out=[],seen=[],i,j,x,seenIt;
for(i=0;i<arr.length;i++){
x=arr[i]; seenIt=false;
for(j=0;j<seen.length;j++){ if(seen[j]===x){ seenIt=true; break; } }
if(!seenIt){ seen[seen.length]=x; out[out.length]=x; }
}
return out;
}
// === State ===
var messageCount=(context.chat&&context.chat.message_count)||0;
var activatedEntries=[], triggeredKeywords=[];
// === First Pass ===
for(var i=0;i<dynamicLore.length;i++){
var entry=dynamicLore[i];
var minA=getMinMessages(entry), maxA=getMaxMessages(entry);
if(messageCount>=minA && messageCount<=maxA){
var kws=getKeywords(entry), hasKeyword=false;
for(var j=0;j<kws.length;j++){ if(hasWord(last,kws[j])){ hasKeyword=true; break; } }
if(hasKeyword){
if(Math.random()>getProbability(entry)) continue;
activatedEntries[activatedEntries.length]=entry;
var t=getTriggers(entry);
for(var k=0;k<t.length;k++){ triggeredKeywords[triggeredKeywords.length]=t[k]; }
dbg("hit entry["+i+"] p="+getPriority(entry));
}
}
}
// === Second Pass (trigger activation) ===
if(triggeredKeywords.length>0){
for(var i2=0;i2<dynamicLore.length;i2++){
var entry2=dynamicLore[i2], isTriggered=false, already=false;
for(var l=0;l<activatedEntries.length;l++){ if(activatedEntries[l]===entry2){ already=true; break; } }
if(already) continue;
var kws2=getKeywords(entry2);
for(var j2=0;j2<kws2.length;j2++){
for(var k2=0;k2<triggeredKeywords.length;k2++){
if(String(kws2[j2])===String(triggeredKeywords[k2])){ isTriggered=true; break; }
}
if(isTriggered) break;
}
if(isTriggered){
var minB=getMinMessages(entry2), maxB=getMaxMessages(entry2);
if(messageCount>=minB && messageCount<=maxB){
if(Math.random()>getProbability(entry2)) continue;
activatedEntries[activatedEntries.length]=entry2;
dbg("triggered entry["+i2+"] p="+getPriority(entry2));
}
}
}
}
// === Dedupe → Priority select → Cap ===
activatedEntries=dedupe(activatedEntries);
// Manual priority pass (since .sort unsafe)
if(activatedEntries.length>1){
var sorted=[], maxP, idx, loopGuard=0;
while(activatedEntries.length>0 && loopGuard<50){
maxP=-1; idx=-1;
for(var z=0;z<activatedEntries.length;z++){
var pr=getPriority(activatedEntries[z]);
if(pr>maxP){ maxP=pr; idx=z; }
}
if(idx>=0){ sorted[sorted.length]=activatedEntries[idx];
var tmp=[]; for(var q=0;q<activatedEntries.length;q++){ if(q!==idx) tmp[tmp.length]=activatedEntries[q]; }
activatedEntries=tmp;
}
loopGuard++;
}
activatedEntries=sorted;
}
// Cap
if(activatedEntries.length>APPLY_LIMIT){
var trimmed=[]; for(var tIdx=0;tIdx<APPLY_LIMIT;tIdx++){ trimmed[trimmed.length]=activatedEntries[tIdx]; }
activatedEntries=trimmed;
dbg("APPLY_LIMIT reached");
}
// === Apply entries + shifts ===
for(var m=0;m<activatedEntries.length;m++){
var apply=activatedEntries[m];
context.character.personality+="\n\n"+getPersonality(apply);
context.character.scenario+="\n\n"+getScenario(apply);
if(apply.Shifts && apply.Shifts.length){
for(var s=0;s<apply.Shifts.length;s++){
var shift=apply.Shifts[s], hit=false, sk=shift.keywords||[];
for(var si=0;si<sk.length;si++){ if(hasWord(last,sk[si])){ hit=true; break; } }
if(!hit) continue;
var blocked=false, bl=getBlock(shift);
for(var bi=0;bi<bl.length;bi++){ if(hasWord(last,bl[bi])){ blocked=true; break; } }
if(blocked) continue;
context.character.personality+="\n\n"+getPersonality(shift);
context.character.scenario+="\n\n"+getScenario(shift);
}
}
}
Updated on: 06/09/2025
Thank you!