KoL Detective Helper

Helps with solving the detective school puzzles in Kingdom of Loathing

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name           KoL Detective Helper
// @namespace      kol.interface.unfinished
// @description    Helps with solving the detective school puzzles in Kingdom of Loathing
// @include        https://*kingdomofloathing.com/wham.php*
// @grant          GM_setValue
// @grant          GM_getValue
// @version        1.2
// ==/UserScript==

// Version 1.2
// - highlight areas on hover over questions
// Version 1.1
// - include possible responses in lists of people and jobs, so they can be recognized in answers prior to visiting
// Version 1.0.1
// - highlight active row better
// Version 1.0

var titleKey;
var places;
var person;
var people;
var jobs;
var personPlaceJob = {};
var personSays = {};

function getTitle() {
	var t = document.evaluate( '//b[contains(.,"Who killed ")]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
	if (t.singleNodeValue) {
        var title = t.singleNodeValue.innerHTML;
        // console.log('title is '+title);
        titleKey = title;
	    var tt = document.evaluate( '//td[contains(.,"You have been on this case for ")]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
	    if (tt.singleNodeValue) {
            var re = tt.singleNodeValue.innerHTML.match(/You have been on this case for ([0-9][0-9]*) minute/);
            if (re) {
                var mins = re[1];
                // console.log('minutes is '+mins);
                if (mins!=0) 
                    getTruthAndLies();
            }
        }
    }
}

function gatherLocations() {
    var rc = [];
	var locs = document.evaluate( '//a[contains(@href,"wham.php?visit=")]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);
    for (var i=0;i<locs.snapshotLength;i++) {
        var loc = locs.snapshotItem(i);
        var aloc = loc.firstChild.firstChild.innerHTML;
        rc[rc.length] = aloc;
        // console.log('location: '+aloc);
    }
    return rc;
}

function gatherPeople() {
    var rc = [];

    for (var p in personPlaceJob) {
        rc[rc.length] = p;
    }

    return rc;
}

function gatherJobs() {
    var rc = [];

    for (var p in personPlaceJob) {
        rc[rc.length] = personPlaceJob[p].job;
    }

    return rc;
}

function highlightHover(e) {
    var tgt = this.getAttribute('tgt');
    var tgtnode = document.evaluate( '//a[@href="wham.php?visit='+tgt+'"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE,null);
    if (tgtnode.singleNodeValue) {
        tgtnode.singleNodeValue.setAttribute('style','color:blue;');
        tgtnode.singleNodeValue.firstChild.setAttribute('size','3');
    }
}

function unhighlightHover(e) {
    var tgt = this.getAttribute('tgt');
    var tgtnode = document.evaluate( '//a[@href="wham.php?visit='+tgt+'"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE,null);
    if (tgtnode.singleNodeValue) {
        tgtnode.singleNodeValue.setAttribute('style','');
        tgtnode.singleNodeValue.firstChild.setAttribute('size','2');
    }
}

function gatherMorePeople(people) {
    var re = /^[A-Z][a-z][A-Za-z]* [A-Z][a-z][A-Za-z]*(\s[A-Z][a-z][A-Za-z]*)?$/;
    var rea = /^wham\.php\?ask=([1-9])/;
    var asks = document.evaluate( '//a[contains(@href,"wham.php?ask=")]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);
    for (var i=0;i<asks.snapshotLength;i++) {
        var a = asks.snapshotItem(i);
        var subj = a.innerHTML;
        if (subj.match(re)) {
            if (people.indexOf(subj)<0) {
                people[people.length] = subj;
            }
            //console.log('Extra person: '+subj);
        }
        var m = a.getAttribute('href').match(rea);
        if (m) {
            a.setAttribute('tgt',m[1]);
            a.addEventListener('mouseover',highlightHover,false);
            a.addEventListener('mouseout',unhighlightHover,false);
        }
    }
}

function gatherMoreJobs(jobs) {
    var re = /^the victim's ([-a-z ]*)$/;
    var rea = /^wham\.php\?ask=rel&w=([1-9])/;
    var asks = document.evaluate( '//a[contains(@href,"wham.php?ask=rel")]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);
    for (var i=0;i<asks.snapshotLength;i++) {
        var a = asks.snapshotItem(i);
        var subj = a.innerHTML;
        var w = subj.match(re);
        if (w) {
            if (jobs.indexOf(w[1])<0) {
                jobs[jobs.length] = w[1];
                //console.log('Extra job: '+w[1]);
            }
        }
        var m = a.getAttribute('href').match(rea);
        if (m) {
            a.setAttribute('tgt',m[1]);
            a.addEventListener('mouseover',highlightHover,false);
            a.addEventListener('mouseout',unhighlightHover,false);
        }
    }
}

// -------------------------------------------------

function gatherPerson() {
	var p = document.evaluate( '//input[@value="Accuse!"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
	if (p.singleNodeValue) {
	    p = document.evaluate( './/b', p.singleNodeValue.parentNode.parentNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
        if (p.singleNodeValue) {
            person = p.singleNodeValue.innerHTML;
            var role = String(p.singleNodeValue.nextSibling.nextSibling.nextSibling.nodeValue).trim();
            var loc = String(p.singleNodeValue.nextSibling.nextSibling.nextSibling.nextSibling.innerHTML).trim().replace(")","").replace("(","");
            //console.log(person+', '+role+' is at '+loc);

            var peep = personPlaceJob[person];
            if (peep) {
                peep.job = role;
                peep.place = loc;
            } else {
                personPlaceJob[person] = {
                    job:role,
                    place:loc
                };
            }
            if (people.indexOf(person)<0)
                people[people.length] = person;
            if (jobs.indexOf(role)<0)
                jobs[jobs.length] = role;
            //console.log('Person: '+person);
        } 
    } 
}

function gatherAnswer(person) {
    if (!person) {
        return;
    }
 	var a = document.evaluate( '//td[@colspan="2" and @width="500"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
 	if (a.singleNodeValue) {
        var t = a.singleNodeValue.innerHTML;
        var p,l,j;

        var accused = checkAccused(t);
        //console.log('accused? ' + accused);

        for (var i in people) {
            var re = new RegExp('\\b'+people[i]+'\\b');
            if (re.test(t)) {
                p = people[i];
                //console.log('Talking about person '+p);
            }
        }

        for (var i in places) {
            var re = new RegExp('\\b'+places[i]+'\\b');
            if (re.test(t)) {
                if (!l || l.length<places[i].length) {
                    l = places[i];
                }
                //console.log('Talking about location '+l);
            }
        }

        var ts = t.replace(/I don't know how s*he got stuck with [a-z -]* like me, either\./,'');
        // console.log('looking for jobs');
        for (var i in jobs) {
            var re = new RegExp('\\b'+jobs[i]+'\\b');
            var w = ts.search(re);
            if (w>=3 && ts.substr(w-3,3).toLowerCase()!="ex-") {
                if (!j || j.length<jobs[i].length) {
                    j = jobs[i];
                }
                // console.log('Talking about job '+j);
            }
        }

        var map = personSays[person];
        if (!map) {
            map = new Object();
            personSays[person] = map;
        }
        if (p && l) {
            //console.log(p + ' -> ' + l);
            map[p+'.place'] = l;
        }
        if (p && j) {
            //console.log(p + ' -> ' + j);
            map[p+'.job'] = j;
        }
        if (l && j) {
            //console.log(l + ' -> ' + j);
            map[l] = j;
        }
        if (accused) {
            if (p) map.accused = p;
            else if (j) map.accused = j;
            else map.accused = 'n/a';
        }
    } 
}

function checkAccused(text) {
    var re = /("Do you have any thoughts on who did it\?" you ask.|"Help me out here," you say\. "You know all the people in this house. Who's the most likely killer\?"|"Who do you think is the most likely to be the killer\?" you ask\.|"Who do you think the killer is\?" you ask.|"Does anyone else here seem particularly suspicious to you\?" you ask\.|"If you had to guess," you ask, "who would you say did it\?")/;

    return re.test(text);
}

// -------------------------------------------------

function saveTruthAndLies() {
    GM_setValue('truth',JSON.stringify(personPlaceJob));
    GM_setValue('lies',JSON.stringify(personSays));
    // var i = 1;
    // // console.log('Truth:');
    // for (var p in personPlaceJob) {
    //     console.log(i+'. '+p+': '+personPlaceJob[p].job+', '+personPlaceJob[p].place);
    //     i = i+1;
    // }
    // // console.log('Lies:');
    // i = 1;
    // for (var p in personSays) {
    //     var ps = personSays[p];
    //     var s = '';
    //     for (var rel in ps) {
    //         if (s)
    //             s += ', ';
    //         s += rel+':'+ps[rel];
    //     }
    //     console.log(i+'. '+p+' says '+s);
    //     i = i+1;
    // }
}

function getTruthAndLies() {
    var t = GM_getValue('truth','');
    if (t) {
        personPlaceJob = JSON.parse(t);
    }
    var lies = GM_getValue('lies','');
    if (lies) {
        personSays = JSON.parse(lies);
    }
}

function abbreviateName(name) {
    var re = /^([A-Z])[a-zA-Z]* ([A-Z])[a-zA-Z]*(\s?([A-Z]))?/;
    var m = name.match(re);
    if (m) {
        if (m[4])
            return m[1]+m[2]+m[4];
        return m[1]+m[2];
    }
    return name;
}

function display() {
    var tbl = document.createElement('table');
    tbl.setAttribute('style','width:95%;font-size:9pt;border-collapse:collapse;border:1px solid black;');
    tbl.setAttribute('cellpadding','3');
    tbl.setAttribute('cellspacing','2');

    // headers
    var c,r = tbl.insertRow();
    r.setAttribute('style','border:1px solid black;');
    c = r.insertCell();
    c.appendChild(document.createTextNode('Perp'));

    for (var ppl in people) {
        var name = abbreviateName(people[ppl]);
        c = r.insertCell();
        c.appendChild(document.createTextNode(name));
        // c = r.insertCell();
        // c.appendChild(document.createTextNode(name));
    }
    for (var loc in places) {
        c = r.insertCell();
        c.appendChild(document.createTextNode(places[loc]));
    }
    c = r.insertCell();
    c.appendChild(document.createTextNode('Acc'));

    for (var p in personSays) {
        r = tbl.insertRow();
        var rattrib = "border:1px solid black;";
        
        c = r.insertCell();
        if (p==person) {
            var b = document.createElement('b');
            b.appendChild(document.createTextNode(p));
            c.appendChild(b);
            rattrib = rattrib + "background-color:rgb(250,230,230);";
        } else {
            c.appendChild(document.createTextNode(p));
        }
        r.setAttribute('style',rattrib);
        c.appendChild(document.createElement('br'));
        if (personPlaceJob[p] && personPlaceJob[p].place) {
            var b = document.createElement('div');
            b.setAttribute('style','font-size:smaller;');
            if (personPlaceJob[p].job) {
                b.appendChild(document.createTextNode(personPlaceJob[p].job));
                b.appendChild(document.createElement('br'));
            }
            b.appendChild(document.createTextNode(personPlaceJob[p].place));
            c.appendChild(b);
        }
        
        for (var ppl in people) {
            c = r.insertCell();
            var place = personSays[p][people[ppl]+'.place'];
            if (place) {
                var d = document.createElement('div');
                // check truth
                if (personPlaceJob[people[ppl]] && personPlaceJob[people[ppl]].place) {
                    // we know this association
                    if (personPlaceJob[people[ppl]].place==place) {
                        d.setAttribute('style','color:green;border-bottom:1px solid black;');
                    } else {
                        d.setAttribute('style','color:red;border-bottom:1px solid black;');
                    }
                } else {
                    d.setAttribute('style','color:gray;border-bottom:1px solid black;');
                }
                d.appendChild(document.createTextNode(place));
                c.appendChild(d);
            } else {
                c.appendChild(document.createElement('br'));
            }

            //c = r.insertCell();
            //c.appendChild(document.createElement('br'));
            var job = personSays[p][people[ppl]+'.job'];
            if (job) {
                var d = document.createElement('div');
                // check truth
                if (personPlaceJob[people[ppl]] && personPlaceJob[people[ppl]].job) {
                    if (personPlaceJob[people[ppl]] && personPlaceJob[people[ppl]].job==job) {
                        d.setAttribute('style','color:green;border-top:1px solid black;');
                    } else {
                        d.setAttribute('style','color:red;border-top:1px solid black;');
                    }
                } else {
                    d.setAttribute('style','color:gray;border-top:1px solid black;');
                }
                d.appendChild(document.createTextNode(job));
                c.appendChild(d);
            } else {
                c.appendChild(document.createElement('br'));
            }
        }
        for (var loc in places) {
            c = r.insertCell();
            var job = personSays[p][places[loc]];
            if (job) {
                var d = document.createElement('div');
                d.setAttribute('style','color:gray;');
                // check truth
                for (var others in personPlaceJob) {
                    var other = personPlaceJob[others];
                    if (other.job==job && other.place) {
                        if (other.place==places[loc]) {
                            d.setAttribute('style','color:green;');
                        } else {
                            d.setAttribute('style','color:red;');
                        }
                        break;
                    } else if (other.place==places[loc] && other.job) {
                        if (other.job==job) {
                            d.setAttribute('style','color:green;');
                        } else {
                            d.setAttribute('style','color:red;');
                        }
                        break;
                    }
                }
                d.appendChild(document.createTextNode(job));
                c.appendChild(d);
            }
        }
        c = r.insertCell();
        if (personSays[p].accused) {
            c.appendChild(document.createTextNode(personSays[p].accused));
        }
    }
    document.body.appendChild(tbl);
}
    
// -------------------------------------------------

getTitle();
places = gatherLocations();
people = gatherPeople();
jobs = gatherJobs();
gatherPerson();
gatherMorePeople(people);
gatherMoreJobs(jobs);
gatherAnswer(person);
saveTruthAndLies();
display();