function loadsettings()
{
    global $settings;
    if (!is_array($settings)) {
        $settings = datacache('game-settings');
        if (!is_array($settings)) {
            $settings = [];
            $sql = db_query("SELECT * FROM " . db_prefix('settings'));
            while ($row = db_fetch_assoc($sql)) {
                $settings[$row['setting']] = $row['value'];
            }
            db_free_result($sql);
            updatedatacache('game-settings', $settings);
        }
    }
}
/**
 * Execute a command and cache the results.
 * @return array
 */
function &db_query_cached(string $sql, string $name, int $duration = 900) : array
{
    global $dbinfo;
    $data = datacache($name, $duration);
    if (is_array($data)) {
        reset($data);
        $dbinfo['affected_rows'] = -1;
        return $data;
    } else {
        $result = db_query($sql);
        $data = array();
        while ($row = db_fetch_assoc($result)) {
            $data[] = $row;
        }
        updatedatacache($name, $data);
        reset($data);
        return $data;
    }
}
function &db_query_cached($sql, $name, $duration = 900, $dir = false)
{
    //this function takes advantage of the data caching library to make
    //all of the other db_functions act just like MySQL queries but rely
    //instead on disk cached data.
    //if (getsetting("usedatacache", 0) == 1) debug("DataCache: $name");
    //standard is 15 minutes, als hooks don't need to be cached *that* often, normally you invalidate the cache properly
    global $dbinfo, $cachedqueries, $allqueriesbyfile;
    $starttime = getmicrotime();
    $data = datacache($name, $duration, $dir);
    if (is_array($data)) {
        reset($data);
        $dbinfo['affected_rows'] = -1;
        $dbinfo['cache_success'] += 1;
        // debug("Successful DC lookup: ".$sql,true);
        $endtime = getmicrotime();
        $extime = $endtime - $starttime;
        $thisquery['time'] = round($extime, 5);
        $thisquery['sql'] = $sql;
        $cachedqueries[] = $thisquery;
        $trace = debug_backtrace();
        $thisquery['file1'] = $trace[0]['file'];
        $allqueriesbyfile[$thisquery['file1']]['time'] += $thisquery['time'];
        $allqueriesbyfile[$thisquery['file1']]['hits'] += 1;
        $dbinfo['cachetime'] += $extime;
        return $data;
    } else {
        $result = db_query($sql);
        $data = array();
        while ($row = db_fetch_assoc($result)) {
            $data[] = $row;
        }
        updatedatacache($name, $data, $dir);
        reset($data);
        $dbinfo['cache_fail'] += 1;
        // debug("Failed DC lookup: ".$sql,true);
        return $data;
    }
}
function &db_query_cached($sql, $name, $duration = 900)
{
    //this function takes advantage of the data caching library to make
    //all of the other db_functions act just like MySQL queries but rely
    //instead on disk cached data.
    //if (getsetting("usedatacache", 0) == 1) debug("DataCache: $name");
    //standard is 15 minutes, als hooks don't need to be cached *that* often, normally you invalidate the cache properly
    global $dbinfo;
    $data = datacache($name, $duration);
    if (is_array($data)) {
        reset($data);
        $dbinfo['affected_rows'] = -1;
        return $data;
    } else {
        $result = db_query($sql);
        $data = array();
        while ($row = db_fetch_assoc($result)) {
            $data[] = $row;
        }
        updatedatacache($name, $data);
        reset($data);
        return $data;
    }
}
function loadsettings()
{
    global $settings;
    // as this seems to be a common complaint, examine the execution path
    // of this function, it will only load the settings once per page hit,
    // in subsequent calls to this function, $settings will be an array,
    // thus this function will do nothing.
    // slight change in 1.1.1 ... let's store a serialized array instead of a cached query
    // we need it too often and the for/while construct necessary is just too much for it.
    if (!is_array($settings)) {
        $settings = datacache("game-settings");
        if (!is_array($settings)) {
            $settings = array();
            $sql = "SELECT * FROM " . db_prefix("settings");
            $result = db_query($sql);
            //db_query_cached($sql,"game-settings");
            while ($row = db_fetch_assoc($result)) {
                $settings[$row['setting']] = $row['value'];
            }
            db_free_result($result);
            updatedatacache("game-settings", $settings);
        }
    }
}
function nasty_word_list() : array
{
    $search = datacache('nastywordlist', 86400);
    if ($search !== false && is_array($search)) {
        return $search;
    }
    $nastyWords = db_prefix('nastywords');
    $sql = db_query("SELECT * FROM {$nastyWords} WHERE type = 'nasty'");
    $row = db_fetch_assoc($sql);
    $search = $row['words'];
    $search = preg_replace('/(?<=.)(?<!\\\\)\'(?=.)/', '\\\'', $search);
    $search = str_replace('a', '[a4@ªÀÁÂÃÄÅàáâãäå]', $search);
    $search = str_replace('b', '[bß]', $search);
    $search = str_replace('d', '[dÐÞþ]', $search);
    $search = str_replace('e', '[e3ÉÊËÈèéêë]', $search);
    $search = str_replace('n', '[nÑñ]', $search);
    $search = str_replace('o', '[o°º0ÒÓÔÕÖØðòóôõöø¤]', $search);
    $search = str_replace('p', '[pÞþ¶]', $search);
    $search = str_replace('r', '[r®]', $search);
    $search = preg_replace('/(?<!\\\\)s/', '[sz$§]', $search);
    $search = str_replace('t', '[t7+]', $search);
    $search = str_replace('u', '[uÛÜÙÚùúûüµ]', $search);
    $search = str_replace('x', '[xפ]', $search);
    $search = str_replace('y', '[yÝ¥ýÿ]', $search);
    $search = str_replace('l', '[l1!£]', $search);
    $search = str_replace('i', '[li1!¡ÌÍÎÏìíîï]', $search);
    $search = str_replace('k', 'c', $search);
    $search = str_replace('c', '[c\\(kç©¢]', $search);
    $start = "'\\b";
    $end = "\\b'iU";
    $ws = "[^[:space:]\\t]*";
    $search = preg_replace('\'(?<!\\*) \'', ")+{$end} ", $search);
    $search = preg_replace('\' (?!\\*)\'', " {$start}(", $search);
    $search = str_replace('* ', ")+{$ws}{$end} ", $search);
    $search = str_replace(' *', " {$start}{$ws}(", $search);
    $search = "{$start}(" . trim($search) . ")+{$end}";
    $search = str_replace("{$start}()+{$end}", '', $search);
    $search = explode(' ', $search);
    updatedatacache('nastywordlist', $search);
    return $search;
}
function homepage_dohook($hookname, $args)
{
    global $session;
    switch ($hookname) {
        case "onlinecharlist":
            $args['handled'] = 1;
            $args['list'] = "";
            $nav = "";
            $op = httpget('op');
            if (httpget('r')) {
                $referer = httpget('r');
            }
            //Output current game time
            $args['list'] .= appoencode(sprintf(translate_inline("`0`bCurrent game time:`b`n%s`n`n"), getgametime()));
            //Output time to new day
            $secstonewday = secondstonextgameday();
            $args['list'] .= appoencode(sprintf(translate_inline("`0`bNext Game Day in:`b`n`\$%s`0`n`n"), date("G\\" . translate_inline("h", "datetime") . ", i\\" . translate_inline("m", "datetime") . ", s\\" . translate_inline("s", "datetime"), $secstonewday)));
            //Output newest player
            if (getsetting("homenewestplayer", 1)) {
                $name = "";
                $newplayer = getsetting("newestplayer", "");
                if ($newplayer != 0) {
                    $sql = "SELECT name FROM " . db_prefix("accounts") . " WHERE acctid='{$newplayer}'";
                    $result = db_query_cached($sql, "newest", 120);
                    $row = db_fetch_assoc($result);
                    $name = $row['name'];
                } else {
                    $name = $newplayer;
                }
                if ($name != "") {
                    $args['list'] .= appoencode(sprintf(translate_inline("`0`bNewest Player:`b`n`&%s`0`n`n"), $name));
                }
            }
            //Output online characters list
            $sql = "SELECT name,alive,location,sex,level,laston,loggedin,lastip,uniqueid FROM " . db_prefix("accounts") . " WHERE locked=0 AND loggedin=1 AND laston>'" . date("Y-m-d H:i:s", strtotime("-" . getsetting("LOGINTIMEOUT", 900) . " seconds")) . "' ORDER BY level DESC";
            $result = db_query_cached($sql, "charlisthomepage", 300);
            $args['list'] .= appoencode(sprintf(translate_inline("`bOnline Characters (%s players):`b`n"), db_num_rows($result)));
            while ($row = db_fetch_assoc($result)) {
                $args['list'] .= appoencode("`^{$row['name']}`n");
                $onlinecount++;
            }
            db_free_result($result);
            if ($onlinecount == 0) {
                $args['list'] .= appoencode(translate_inline("`iNone`i"));
            }
            savesetting("OnlineCount", $onlinecount);
            savesetting("OnlineCountLast", strtotime("now"));
            updatedatacache("charlisthomepage", $args['list']);
            break;
        case "index":
            page_header("Welcome to Improbable Island");
            $r = httpget('r');
            //block all standard navs
            blocknav("create.php");
            blocknav("create.php?op=forgot");
            blocknav("news.php");
            blocknav("about.php");
            blocknav("about.php?op=setup");
            blocknav("list.php");
            blocknav("logdnet.php?op=list");
            //block navs with referral links, too
            blocknav("create.php?r=" . $r);
            blocknav("create.php?op=forgot&r=" . $r);
            blocknav("news.php?r=" . $r);
            blocknav("about.php?r=" . $r);
            blocknav("about.php?op=setup&r=" . $r);
            blocknav("list.php?r=" . $r);
            blocknav("logdnet.php?op=list&r=" . $r);
            if (httpget('op') == "timeout") {
                $nav .= translate_inline("Your session has timed out, you must log in again.");
            }
            if (!isset($_COOKIE['lgi'])) {
                $nav .= translate_inline("It appears that you may be blocking cookies from this site.  At least session cookies must be enabled in order to use this site.`n");
                $nav .= translate_inline("`b`#If you are not sure what cookies are, please <a href='http://en.wikipedia.org/wiki/WWW_browser_cookie'>read this article</a> about them, and how to enable them.`b`n");
            }
            rawoutput("<script language='JavaScript' src='lib/md5.js'></script>");
            rawoutput("<script language='JavaScript'>\r\n\t\t\t<!--\r\n\t\t\tfunction md5pass(){\r\n\t\t\t\t//encode passwords before submission to protect them even from network sniffing attacks.\r\n\t\t\t\tvar passbox = document.getElementById('password');\r\n\t\t\t\tif (passbox.value.substring(0, 5) != '!md5!') {\r\n\t\t\t\t\tpassbox.value = '!md5!' + hex_md5(passbox.value);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t//-->\r\n\t\t\t</script>");
            addnav("Return to the Island");
            $msg = $session['message'];
            $nav .= "{$msg}<form action='login.php' method='POST' onSubmit=\"md5pass();\">Character Name:<br /><input name='name' id=\"name\" accesskey='u' size='12'><br />Password:<br /><input name='password' id=\"password\" accesskey='p' size='12' type='password'\"><br /><input type='submit' value='Log in' class='button'></form>";
            addnav(array("%s", $nav), "", true);
            addnav("Recover a lost password", "create.php?op=forgot&or=1");
            addnav("Game Credits");
            addnav("Credits and License Info", "runmodule.php?module=homepage&op=gamecredits&r=" . $r);
            $skins = array();
            $handle = @opendir("templates");
            // Template directory open failed
            if ($handle) {
                addnav("Skin selection");
                while (false != ($file = @readdir($handle))) {
                    if (strpos($file, ".htm") > 0) {
                        array_push($skins, $file);
                    }
                }
                // No templates installed!
                if (count($skins) == 0) {
                    output("None available");
                    break;
                }
                natcasesort($skins);
                //sort them in natural order
                foreach ($skins as $skin) {
                    if ($skin == $_COOKIE['template']) {
                        addnav(array("%s (selected)", htmlentities(substr($skin, 0, strpos($skin, ".htm")), ENT_COMPAT, getsetting("charset", "ISO-8859-1"))), "");
                    } else {
                        addnav(array("%s", htmlentities(substr($skin, 0, strpos($skin, ".htm")), ENT_COMPAT, getsetting("charset", "ISO-8859-1"))), "runmodule.php?module=homepage&op=changeskin&skin={$skin}&r={$r}");
                    }
                }
            }
            //Output The Story...
            output("`i`#How did I end up here?`0`i`n`nIt's a question that you must have asked yourself at one point in your life or another. Probably after pulling your head out of the toilet bowl, remembering the little rhyme about drinking wine on top of beer, and then feeling rather poorly again. You might have asked that question while standing naked in the pouring rain holding a coathanger in one hand and a rather large purple sex toy in the other, trying desperately to break into your own car while two policemen stroll up the street towards you. Perhaps you asked it while flushing the contents of your pockets and incinerating your hard drive, or after putting your life savings on \"red 36,\" or while washing the urine out of your clown suit.`n`nHowever, this time, you mean it literally.`n`nOr rather, you would, if you had gotten around to asking that question. You haven't, yet. To be truthful, you're rather reluctant to open your eyes.`n`nYou know that you're lying in grass. You know that you're naked. You know that you can hear birds singing, and the sun on your body, and a warm, gentle breeze. That's okay. That's manageable. You've woken up in less desirable circumstances before.`n`nThe unmistakable roar of a low-flying jet plane going overhead, and the muffled thumps of explosions in the distance... now, that's not so good. That's the sort of thing that you really should pay attention to.`n`nReluctantly, you open your eyes.`n`n`i`#How did I end up here?`0`i`n`nYou're lying on your back in the middle of a grassy clearing. As suspected, a quick glance down confirms that you're as naked as the day you were born. A bulky video camera, mounted in a tree directly above you, pans disinterestedly down your body. It lets out a little whirring noise, one that seems to say \"yeah, whatever. I've seen better.\"`n`nAbout twenty paces ahead, the clearing gradually gives way into dense jungle. You look around you and confirm that this is the case for all directions.`n`nExcept one. A crudely-built wooden fort stands ten paces to your right. The wooden stakes in the ground extend about forty feet in the air, and you can't quite tell how far they go along horizontal dimensions.`n`n`i`#Think. Think. Where am I?`i`n`n`#`iOkay. I know who I am. I know my name. I know my parents' names. I know my address. I even know what I did last night! That rules out long-term memory loss, so what the hell am I doing here?`i`0`n`nYou try to get up, to see just how big this wooden fort thing is, but a sharp pain in your head tells you in no uncertain terms to sit the hell back down again.`n`nAs you collapse, the gates open, and a woman walks out. She looks around herself, and in short order her piercing blue eyes lock on to yours.  She walks over to you, shaking her head.`n`nShe wears a black skirt below her knees, a red turtleneck sweater, and round, copper-framed glasses. Blonde hair tied back in a bun, combined with her clear disdain for your presence, give her a rather severe appearance. When she comes closer, you notice that she's wearing heavy black steel-toe boots, spattered with something reddish-brown.`n`nShe leans over you, stares at you for a moment, and sighs. Her first words to you come in a Southern British accent:`n`n\"`&I don't really have time for this, you know.`0\"`n`nSomething about her voice doesn't sound quite right. It resonates in the center of your skull, almost the way that voices do when you hear them through a pair of headphones.`n`nShe sits down heavily on the grass beside you. Is that `irust`i on her boots?`n`n\"`&Long story short,`0\" she says, making herself comfortable. \"`&Because like I say, I really `idon't`i have time for this.`0\" She points to the wooden fort. \"`&That's an outpost.`0\" She points to the jungle. \"`&That's the Jungle.`0\" She points to herself. \"`&I am `\$The Watcher.`0\" She points down at you. \"`&You are a plonker. No, really, I mean it. You're clearly depriving a village somewhere of an idiot. Sorry to belabour the point, but it's important that we get the Watcher-Plonker relationship established properly, straight away. Otherwise you might get ideas.`0\"`n`nYou stare up at her accusing finger. \"`#What?`0\"`n`nShe presses gently on your nose. \"`&Case in point. Don't ask questions, and don't piss me off today, and you'll be fine.`0\"`n`nYou frown. \"`#What do you mean, I'll be fine?  I can't even remember how I got here!`0\"`n`n`\$The Watcher`0 smiles. \"`&Oh, they must have drugged you up something awful, poor thing.`0\" She looks down at her watch. \"`&Okay, I'll give you a run-down but I've gotta make this really, really quick, now. Don't interrupt.`0\"`n`nShe takes a deep breath. Then she talks very quickly.`n`n\"`&You've been drafted into a war against a machine called the Improbability Drive. It lives somewhere in the jungle, over there. Improbability is leaking out of this bloody thing like radiation, so we've got to blow it up. The whole war is being televised, you've noticed the cameras already, so try not to do anything stupid while the world watches. Your head hurts because the guys who burst into your living room with sticks and a great big sack probably hit you a bit too hard, and you might have landed badly when they tossed you out of the plane. You survived the fall without a parachute because of the Improbability Bubble surrounding the island, which makes the air notably denser about forty feet above sea level. You're naked and unarmed because everything that penetrates the Improbability Bubble gets changed in rather amusing ways, and we didn't want to take that risk. There's blood on my boots because I came across some monsters on the way over here - yes, monsters, stop gawping, you'll get used to them - and you'll either pick up the rest as you go along, or you'll die in a very entertaining fashion.`0\" She smiles. \"`&Either way, it'll make for great television.`0\"`n`nYou open your mouth to ask `\$The Watcher`0 what the hell she's blithering on about, but your words are drowned out by the roar of a passing jet plane and an accompanying female scream.`n`n`\$The Watcher`0 looks up, just in time to see a naked woman make a very undignified landing in a tree, folding herself neatly over a branch. `\$The Watcher`0 grins. \"`&I love it when they land with their arses poking out like that. It makes me feel better about my job.`0\" She stares for a moment, head cocked to the side. \"`&And my arse, too, come to think of it.`0\"`n`nShe takes hold of your hand and yanks you to your feet. \"`&Go through the gate over there. The bloke guarding it is trained to recognise naked newbies like you, and sort out the forms and the implants.\"`n`n\"`#Wait a minute, \"implants?\" What?`0\" you ask, even more scared now than you were two seconds ago.`n`n\"`&What did I tell you about questions? There are other people waiting. Come on, off you go.`0\" She takes you by the shoulders, swivels you towards the gate, and gives you a firm slap on your behind.  You see no other choice but to start walking.`n`n\"`&You there!`0\" calls `\$The Watcher`0 behind you. \"`&Yes, you, with the cellulite! You're in a tree because you've just been thrown out of an aeroplane! You've got to blow up an insane, reality-warping machine before we'll let you go home! You're naked because it's funnier that way! You desperately need a bikini wax, and you can buy weapons in that outpost over there! Stop crying, you're on television!`0\"`n`nYou shudder, and keep walking.`n`nWithin a few paces, you're at the gate of the Outpost. A man with a huge, bushy blonde beard sits in a little hut, writing on a newspaper with a pencil.`n`nYou clear your throat. \"`#Um, excuse me...`0\"`n`nThe man holds up a hand, still looking at his newspaper. \"`6Four-letter word, starts with N, the clue is \"Clad only in skin and innocence.\" Any ideas?`0\"`n`nYou shrug. \"`#Nude?`0\"`n`nThe man leans forward, cupping a hand to his ear. \"`6What was that? You'll have to speak up, they're buggers around here with their loud bloody grenades at every hour of the day and night.`0\"`n`n\"`#Nude,`0\" you respond, a little louder.`n`n\"`6Of course! Newb!`0\" He chuckles heartily, and writes in his paper. \"`6Enn, oh, oh, bee. Newb. Thanks for that. Now, what can I do for you?`0\"`n`n\"`#I honestly have no idea.`0\"`n`n\"`6Ah, so you're a newb yourself?`0\"`n`n\"`#Apparently. At least, according to the blonde woman over there.`0\"`n`nThe hairy man smiles, not unkindly. \"`6Well, let's fill you in on things.  First of all, what should I call you?`0\"`n`n");
            rawoutput("<form action='runmodule.php?module=homepage&op=0&r={$r}' method='POST'\">\"<span class=\"colLtCyan\">You can call me <input name='name' id=\"name\" accesskey='u' size='12'>, I guess.</span>\"  It's as good a name as any.");
            rawoutput("<br /><div align=\"center\"><input type='submit' value='Carry On' class='button'></div><br /><br /></form>");
            addnav("", "runmodule.php?module=homepage&op=0&r=" . $r);
            break;
    }
    return $args;
}
function translate_loadnamespace($namespace, $language = false)
{
    if ($language === false) {
        $language = LANGUAGE;
    }
    $page = translator_page($namespace);
    $uri = translator_uri($namespace);
    //new routine
    $out = datacache("translations/translations-" . $namespace . "-" . $language, 1200);
    if (!is_array($out)) {
        if ($page == $uri) {
            $where = "uri = '{$page}'";
        } else {
            $where = "(uri='{$page}' OR uri='{$uri}')";
        }
        $sql = "\r\n\t\t\tSELECT intext,outtext\r\n\t\t\tFROM " . db_prefix("translations") . "\r\n\t\t\tWHERE language='{$language}'\r\n\t\t\t\tAND {$where}";
        /*	debug(nl2br(htmlentities($sql, ENT_COMPAT, getsetting("charset", "ISO-8859-1")))); */
        $result = db_query($sql);
        $out = array();
        while ($row = db_fetch_assoc($result)) {
            $out[$row['intext']] = $row['outtext'];
        }
        updatedatacache("translations/translations-" . $namespace . "-" . $language, $out);
    }
    return $out;
}
/**
 * Returns the current character stats or (if the character isn't logged in) the currently online players
 * Hooks provided:
 *		charstats
 *
 * @return array The current stats for this character or the list of online players
 */
function charstats()
{
    global $session, $playermount, $companions;
    wipe_charstats();
    $u =& $session['user'];
    if ($session['loggedin']) {
        $u['hitpoints'] = round($u['hitpoints'], 0);
        $u['experience'] = round($u['experience'], 0);
        $u['maxhitpoints'] = round($u['maxhitpoints'], 0);
        // $spirits=array(-6=>"Resurrected",-2=>"Very Low",-1=>"Low","0"=>"Normal",1=>"High",2=>"Very High");
        // if ($u['alive']){ }else{ $spirits[(int)$u['spirits']] = "DEAD"; }
        //calculate_buff_fields();
        reset($session['bufflist']);
        $atk = $u['attack'];
        $def = $u['defense'];
        $buffcount = 0;
        $buffs = "";
        while (list($key, $val) = each($session['bufflist'])) {
            if (isset($val['suspended']) && $val['suspended']) {
                continue;
            }
            if (isset($val['atkmod'])) {
                $atk *= $val['atkmod'];
            }
            if (isset($val['defmod'])) {
                $def *= $val['defmod'];
            }
            // Short circuit if the name is blank
            if ($val['name'] > "" || $session['user']['superuser'] & SU_DEBUG_OUTPUT) {
                tlschema($val['schema']);
                if ($val['name'] == "") {
                    $val['name'] = "DEBUG: {$key}";
                }
                if (is_array($val['name'])) {
                    $val['name'][0] = str_replace("`%", "`%%", $val['name'][0]);
                    $val['name'] = call_user_func_array("sprintf_translate", $val['name']);
                } else {
                    //in case it's a string
                    $val['name'] = translate_inline($val['name']);
                }
                if ($val['rounds'] >= 0) {
                    // We're about to sprintf, so, let's makes sure that
                    // `% is handled.
                    //$n = translate_inline(str_replace("`%","`%%",$val['name']));
                    $b = translate_inline("`#%s `7(%s rounds left)`n", "buffs");
                    $b = sprintf($b, $val['name'], $val['rounds']);
                    $buffs .= appoencode($b, true);
                } else {
                    $buffs .= appoencode("`#{$val['name']}`n", true);
                }
                tlschema();
                $buffcount++;
            }
        }
        if ($buffcount == 0) {
            $buffs .= appoencode(translate_inline("`^None`0"), true);
        }
        $atk = round($atk, 2);
        $def = round($def, 2);
        if ($atk < $u['attack']) {
            $atk = round($u['attack'], 1) . "`\$" . round($atk - $u['attack'], 1);
        } else {
            if ($atk > $u['attack']) {
                $atk = round($u['attack'], 1) . "`@+" . round($atk - $u['attack'], 1);
            } else {
                // They are equal, display in the 1 signifigant digit format.
                $atk = round($atk, 1);
            }
        }
        if ($def < $u['defense']) {
            $def = round($u['defense'], 1) . "`\$" . round($def - $u['defense'], 1);
        } else {
            if ($def > $u['defense']) {
                $def = round($u['defense'], 1) . "`@+" . round($def - $u['defense'], 1);
            } else {
                // They are equal, display in the 1 signifigant digit format.
                $def = round($def, 1);
            }
        }
        addcharstat("Vital Info");
        //health bar
        if ($u['alive']) {
            $cur = $u['hitpoints'];
            $realmax = $u['maxhitpoints'];
            $cur_adjustment = check_temp_stat("hitpoints", 1);
            $max_adjustment = check_temp_stat("maxhitpoints", 1);
        } else {
            $cur = $u['soulpoints'];
            $realmax = $u['level'] * 5 + 50;
            $cur_adjustment = check_temp_stat("soulpoints", 1);
            $max_adjustment = "";
        }
        if ($pct > 60) {
            $ccode = "`@";
        } elseif ($pct > 25) {
            $ccode = "`^";
        } else {
            $ccode = "`\$";
        }
        $hicode = "`&";
        if (!$u['alive']) {
            $ccode = "`7";
        }
        require_once "lib/bars.php";
        $hpbar = fadebar($cur, $realmax);
        $stat = "{$ccode} {$cur} {$cur_adjustment} `0/ {$realmax} {$max_adjustment}<br />" . $hpbar;
        if ($u['alive']) {
            addcharstat("Hitpoints", $stat);
            addcharstat("Attack", $atk . check_temp_stat("attack", 1));
            addcharstat("Defence", $def . check_temp_stat("defense", 1));
        } else {
            addcharstat("Adrenaline", $stat);
            addcharstat("Attack", 10 + round(($u['level'] - 1) * 1.5));
            addcharstat("Defence", 10 + round(($u['level'] - 1) * 1.5));
        }
        // addcharstat("Turns", $u['turns'].check_temp_stat("turns",1));
        // addcharstat("Attack", $atk.check_temp_stat("attack",1));
        // addcharstat("Defence", $def.check_temp_stat("defense",1));
        if (count($companions) > 0) {
            addcharstat("Companions");
            foreach ($companions as $name => $companion) {
                if ($companion['hitpoints'] > 0 || isset($companion['cannotdie']) && $companion['cannotdie'] == true) {
                    if ($companion['hitpoints'] < 0) {
                        $companion['hitpoints'] = 0;
                    }
                    if ($companion['hitpoints'] < $companion['maxhitpoints']) {
                        $color = "`\$";
                    } else {
                        $color = "`@";
                    }
                    if (isset($companion['suspended']) && $companion['suspended'] == true) {
                        $suspcode = "`7 *";
                    } else {
                        $suspcode = "";
                    }
                    addcharstat($companion['name'], $color . $companion['hitpoints'] . "`7/`&" . $companion['maxhitpoints'] . "{$suspcode}`0");
                }
            }
        }
        addcharstat("Personal Info");
        if ($u['alive']) {
            addcharstat("Requisition", number_format($u['gold'] . check_temp_stat("gold", 1)));
            addcharstat("Cigarettes", number_format($u['gems'] . check_temp_stat("gems", 1)));
        } else {
            addcharstat("Cage Fights", $u['gravefights'] . check_temp_stat("gravefights", 1));
            addcharstat("Favour", number_format($u['deathpower'] . check_temp_stat("deathpower", 1)));
        }
        if ($u['alive']) {
            addcharstat("Level", "`b" . $u['level'] . check_temp_stat("level", 1) . "`b");
            //exp bar
            require_once "lib/experience.php";
            $min = exp_for_next_level($u['level'] - 1, $u['dragonkills']);
            $req = exp_for_next_level($u['level'], $u['dragonkills']);
            $exp = round($session['user']['experience'], 0) . check_temp_stat("experience", 1);
            if ($exp < $min) {
                $min = $exp;
            }
            if ($req - $min > 0) {
                $nonpct = floor(($req - $exp) / ($req - $min) * 100);
            } else {
                $nonpct = 0;
            }
            $pct = 100 - $nonpct;
            if ($pct > 100) {
                $pct = 100;
                $nonpct = 0;
            }
            if ($pct < 0) {
                $pct = 0;
                $nonpct = 100;
            }
            if ($exp >= $req) {
                $color = "blue";
                if ($session['user']['level'] == 1 && $session['user']['dragonkills'] == 0) {
                    $expmsg = "<br />You have enough experience to level up!  Challenge your master in the Dojo!";
                }
            } else {
                $color = "white";
            }
            addcharstat("Experience", number_format($u['experience'] . check_temp_stat("experience", 1)) . "/{$req}<br /><table style='border: solid 1px #000000;' bgcolor='red'  cellpadding='0' cellspacing='0' width='70' height='5'><tr><td width='{$pct}%' bgcolor='{$color}'></td><td width='{$nonpct}%'></td></tr></table>{$expmsg}");
            addcharstat("Equipment Info");
            addcharstat("Weapon", $u['weapon']);
            addcharstat("Armor", $u['armor']);
            if ($u['hashorse']) {
                addcharstat("Creature", $playermount['mountname'] . "`0");
            }
        }
        require_once "lib/datetime.php";
        $gt = gametimedetails();
        addcharstat("Game State");
        addcharstat("Game Time", gmdate("g:i a", $gt['gametime']));
        addcharstat("New day in:", date("H:i:s", secondstonextgameday()));
        modulehook("charstats");
        $charstat = getcharstats($buffs);
        if (!is_array($session['bufflist'])) {
            $session['bufflist'] = array();
        }
        return $charstat;
    } else {
        $ret = "";
        if ($ret = datacache("charlisthomepage")) {
        } else {
            $onlinecount = 0;
            // If a module wants to do it's own display of the online chars,
            // let it.
            $list = modulehook("onlinecharlist", array());
            if (isset($list['handled']) && $list['handled']) {
                $onlinecount = $list['count'];
                $ret = $list['list'];
            } else {
                $sql = "SELECT name,alive,location,sex,level,laston,loggedin,lastip,uniqueid FROM " . db_prefix("accounts") . " WHERE locked=0 AND loggedin=1 AND laston>'" . date("Y-m-d H:i:s", strtotime("-" . getsetting("LOGINTIMEOUT", 900) . " seconds")) . "' ORDER BY level DESC";
                $result = db_query($sql);
                $ret .= appoencode(sprintf(translate_inline("`bOnline Characters (%s players):`b`n"), db_num_rows($result)));
                while ($row = db_fetch_assoc($result)) {
                    $ret .= appoencode("`^{$row['name']}`n");
                    $onlinecount++;
                }
                db_free_result($result);
                if ($onlinecount == 0) {
                    $ret .= appoencode(translate_inline("`iNone`i"));
                }
            }
            savesetting("OnlineCount", $onlinecount);
            savesetting("OnlineCountLast", strtotime("now"));
            updatedatacache("charlisthomepage", $ret);
        }
        return $ret;
    }
}
function load_all_module_prefs($user = false)
{
    global $module_prefs, $session;
    if ($user === false) {
        $user = $session['user']['acctid'];
    }
    if (!isset($module_prefs[$user])) {
        $start = microtime(true);
        $module_prefs[$user] = datacache("moduleprefs/moduleprefs-{$user}");
        if (!is_array($module_prefs[$user])) {
            //debug("Getting from db");
            $sql = "SELECT setting,value,modulename FROM " . db_prefix("module_userprefs") . " WHERE userid='{$user}'";
            $result = db_query($sql);
            $module_prefs[$user] = array();
            while ($row = db_fetch_assoc($result)) {
                $module_prefs[$user][$row['modulename']][$row['setting']] = $row['value'];
            }
            db_free_result($result);
            updatedatacache("moduleprefs/moduleprefs-{$user}", $module_prefs[$user]);
        }
        $end = microtime(true);
        $tot = $end - $start;
        //debug("Loaded module prefs in: ".$tot);
        // $module_prefs[$user] = array();
        // $start = microtime(true);
        // $sql = "SELECT setting,value,modulename FROM " . db_prefix("module_userprefs") . " WHERE userid='$user'";
        // $result = db_query_cached($sql,"moduleprefs/moduleprefs-$user",86400);
        // $end1 = microtime(true);
        // while ($row = db_fetch_assoc($result)){
        // $module_prefs[$user][$row['modulename']][$row['setting']] = $row['value'];
        // }
        // $end2 = microtime(true);
        // $time1 = $end1-$start;
        // $time2 = $end2-$start;
        //debug("All prefs load time: ".$time2.", of which ".$time1." spent in db_query_cached");
    }
    //debug($module_prefs);
}
/**
 * Returns the current character stats or (if the character isn't logged in) the currently online players
 * Hooks provided:
 *		charstats
 *
 * @return array The current stats for this character or the list of online players
 */
function charstats()
{
    global $session, $playermount, $companions;
    wipe_charstats();
    $u =& $session['user'];
    if ($session['loggedin']) {
        $u['hitpoints'] = round($u['hitpoints'], 0);
        $u['experience'] = round($u['experience'], 0);
        $u['maxhitpoints'] = round($u['maxhitpoints'], 0);
        $spirits = array(-6 => "Resurrected", -2 => "Very Low", -1 => "Low", "0" => "Normal", 1 => "High", 2 => "Very High");
        if ($u['alive']) {
        } else {
            $spirits[(int) $u['spirits']] = "DEAD";
        }
        //calculate_buff_fields();
        reset($session['bufflist']);
        $atk = $u['attack'];
        $def = $u['defense'];
        $buffcount = 0;
        $buffs = "";
        while (list($key, $val) = each($session['bufflist'])) {
            if (isset($val['suspended']) && $val['suspended']) {
                continue;
            }
            if (isset($val['atkmod'])) {
                $atk *= $val['atkmod'];
            }
            if (isset($val['defmod'])) {
                $def *= $val['defmod'];
            }
            // Short circuit if the name is blank
            if ($val['name'] > "" || $session['user']['superuser'] & SU_DEBUG_OUTPUT) {
                tlschema($val['schema']);
                if ($val['name'] == "") {
                    $val['name'] = "DEBUG: {$key}";
                }
                if (is_array($val['name'])) {
                    $val['name'][0] = str_replace("`%", "`%%", $val['name'][0]);
                    $val['name'] = call_user_func_array("sprintf_translate", $val['name']);
                } else {
                    //in case it's a string
                    $val['name'] = translate_inline($val['name']);
                }
                if ($val['rounds'] >= 0) {
                    // We're about to sprintf, so, let's makes sure that
                    // `% is handled.
                    //$n = translate_inline(str_replace("`%","`%%",$val['name']));
                    $b = translate_inline("`#%s `7(%s rounds left)`n", "buffs");
                    $b = sprintf($b, $val['name'], $val['rounds']);
                    $buffs .= appoencode($b, true);
                } else {
                    $buffs .= appoencode("`#{$val['name']}`n", true);
                }
                tlschema();
                $buffcount++;
            }
        }
        if ($buffcount == 0) {
            $buffs .= appoencode(translate_inline("`^None`0"), true);
        }
        $atk = round($atk, 2);
        $def = round($def, 2);
        if ($atk < $u['attack']) {
            $atk = round($u['attack'], 1) . "`\$" . round($atk - $u['attack'], 1);
        } else {
            if ($atk > $u['attack']) {
                $atk = round($u['attack'], 1) . "`@+" . round($atk - $u['attack'], 1);
            } else {
                // They are equal, display in the 1 signifigant digit format.
                $atk = round($atk, 1);
            }
        }
        if ($def < $u['defense']) {
            $def = round($u['defense'], 1) . "`\$" . round($def - $u['defense'], 1);
        } else {
            if ($def > $u['defense']) {
                $def = round($u['defense'], 1) . "`@+" . round($def - $u['defense'], 1);
            } else {
                // They are equal, display in the 1 signifigant digit format.
                $def = round($def, 1);
            }
        }
        addcharstat("Vital Info");
        addcharstat("Name", $u['name']);
        addcharstat("Level", "`b" . $u['level'] . check_temp_stat("level", 1) . "`b");
        if ($u['alive']) {
            addcharstat("Hitpoints", $u['hitpoints'] . check_temp_stat("hitpoints", 1) . "`0/" . $u['maxhitpoints'] . check_temp_stat("maxhitpoints", 1));
            addcharstat("Turns", $u['turns'] . check_temp_stat("turns", 1));
            addcharstat("Attack", $atk . check_temp_stat("attack", 1));
            addcharstat("Defense", $def . check_temp_stat("defense", 1));
        } else {
            $maxsoul = $u['level'] * 5 + 50;
            addcharstat("Soulpoints", $u['soulpoints'] . check_temp_stat("soulpoints", 1) . "`0/" . $maxsoul);
            addcharstat("Torments", $u['gravefights'] . check_temp_stat("gravefights", 1));
            addcharstat("Psyche", 10 + round(($u['level'] - 1) * 1.5));
            addcharstat("Spirit", 10 + round(($u['level'] - 1) * 1.5));
        }
        addcharstat("Spirits", translate_inline("`b" . $spirits[(int) $u['spirits']] . "`b"));
        if ($u['race'] != RACE_UNKNOWN) {
            addcharstat("Race", translate_inline($u['race'], "race"));
        } else {
            addcharstat("Race", translate_inline(RACE_UNKNOWN, "race"));
        }
        if (count($companions) > 0) {
            addcharstat("Companions");
            foreach ($companions as $name => $companion) {
                if ($companion['hitpoints'] > 0 || isset($companion['cannotdie']) && $companion['cannotdie'] == true) {
                    if ($companion['hitpoints'] < 0) {
                        $companion['hitpoints'] = 0;
                    }
                    if ($companion['hitpoints'] < $companion['maxhitpoints']) {
                        $color = "`\$";
                    } else {
                        $color = "`@";
                    }
                    if (isset($companion['suspended']) && $companion['suspended'] == true) {
                        $suspcode = "`7 *";
                    } else {
                        $suspcode = "";
                    }
                    addcharstat($companion['name'], $color . $companion['hitpoints'] . "`7/`&" . $companion['maxhitpoints'] . "{$suspcode}`0");
                }
            }
        }
        addcharstat("Personal Info");
        if ($u['alive']) {
            addcharstat("Gold", $u['gold'] . check_temp_stat("gold", 1));
        } else {
            addcharstat("Favor", $u['deathpower'] . check_temp_stat("deathpower", 1));
        }
        addcharstat("Gems", $u['gems'] . check_temp_stat("gems", 1));
        addcharstat("Experience", $u['experience'] . check_temp_stat("experience", 1));
        addcharstat("Equipment Info");
        addcharstat("Weapon", $u['weapon']);
        addcharstat("Armor", $u['armor']);
        if ($u['hashorse']) {
            addcharstat("Creature", $playermount['mountname'] . "`0");
        }
        modulehook("charstats");
        $charstat = getcharstats($buffs);
        if (!is_array($session['bufflist'])) {
            $session['bufflist'] = array();
        }
        return $charstat;
    } else {
        $ret = "";
        if ($ret = datacache("charlisthomepage")) {
        } else {
            $onlinecount = 0;
            // If a module wants to do it's own display of the online chars,
            // let it.
            $list = modulehook("onlinecharlist", array());
            if (isset($list['handled']) && $list['handled']) {
                $onlinecount = $list['count'];
                $ret = $list['list'];
            } else {
                $sql = "SELECT name,alive,location,sex,level,laston,loggedin,lastip,uniqueid FROM " . db_prefix("accounts") . " WHERE locked=0 AND loggedin=1 AND laston>'" . date("Y-m-d H:i:s", strtotime("-" . getsetting("LOGINTIMEOUT", 900) . " seconds")) . "' ORDER BY level DESC";
                $result = db_query($sql);
                $ret .= appoencode(sprintf(translate_inline("`bOnline Characters (%s players):`b`n"), db_num_rows($result)));
                while ($row = db_fetch_assoc($result)) {
                    $ret .= appoencode("`^{$row['name']}`n");
                    $onlinecount++;
                }
                db_free_result($result);
                if ($onlinecount == 0) {
                    $ret .= appoencode(translate_inline("`iNone`i"));
                }
            }
            savesetting("OnlineCount", $onlinecount);
            savesetting("OnlineCountLast", strtotime("now"));
            updatedatacache("charlisthomepage", $ret);
        }
        return $ret;
    }
}
function do_forced_nav($anonymous, $overrideforced)
{
    global $baseaccount, $session, $REQUEST_URI;
    rawoutput("<!--\nAllowAnonymous: " . ($anonymous ? "True" : "False") . "\nOverride Forced Nav: " . ($overrideforced ? "True" : "False") . "\n-->");
    //debug($session,true);
    if (isset($session['loggedin']) && $session['loggedin']) {
        $acctid = $session['user']['acctid'];
        $session['user'] = datacache("accounts/account_" . $acctid, 60);
        if (!is_array($session['user'])) {
            $sql = "SELECT *  FROM " . db_prefix("accounts") . " WHERE acctid = '" . $acctid . "'";
            $result = db_query($sql);
            if (db_num_rows($result) == 1) {
                $session['user'] = db_fetch_assoc($result);
                updatedatacache("accounts/account_" . $session['user']['acctid'], $session['user']);
            } else {
                debug($session, true);
                $session = array();
                $session['message'] = translate_inline("`4Your login information is incorrect!`0", "login");
                redirect("index.php", "Account Disappeared!");
            }
            db_free_result($result);
        }
        $baseaccount = $session['user'];
        $session['bufflist'] = unserialize($session['user']['bufflist']);
        if (!is_array($session['bufflist'])) {
            $session['bufflist'] = array();
        }
        $session['user']['dragonpoints'] = unserialize($session['user']['dragonpoints']);
        $session['user']['prefs'] = unserialize($session['user']['prefs']);
        if (!is_array($session['user']['dragonpoints'])) {
            $session['user']['dragonpoints'] = array();
        }
        //get allowednavs
        /*
        accounts_everypage table includes:
        	acctid (primary key, unique)
        	allowednavs
        	laston
        	gentime
        	gentimecount
        	gensize
        */
        $sql = "SELECT allowednavs,laston,gentime,gentimecount,gensize FROM " . db_prefix("accounts_everypage") . " WHERE acctid = '" . $session['user']['acctid'] . "'";
        $result = db_query($sql);
        if (db_num_rows($result) == 1) {
            //debug("Getting fresh info from accounts_everypage");
            $row = db_fetch_assoc($result);
            $session['user']['allowednavs'] = $row['allowednavs'];
            $session['user']['laston'] = $row['laston'];
            $session['user']['gentime'] = $row['gentime'];
            $session['user']['gentimecount'] = $row['gentimecount'];
            $session['user']['gensize'] = $row['gensize'];
        } else {
            $sql = "INSERT INTO " . db_prefix("accounts_everypage") . " (acctid,allowednavs,laston,gentime,gentimecount,gensize) VALUES ('" . $session['user']['acctid'] . "','" . $session['user']['allowednavs'] . "','" . $session['user']['laston'] . "','" . $session['user']['gentime'] . "','" . $session['user']['gentimecount'] . "','" . $session['user']['gensize'] . "')";
            db_query($sql);
        }
        if (is_array(unserialize($session['user']['allowednavs']))) {
            $session['allowednavs'] = unserialize($session['user']['allowednavs']);
        } else {
            $session['allowednavs'] = array($session['user']['allowednavs']);
        }
        if (!$session['user']['loggedin'] || date("U") - strtotime($session['user']['laston']) > getsetting("LOGINTIMEOUT", 900)) {
            $session = array();
            redirect("index.php?op=timeout", "Account not logged in but session thinks they are.");
        }
        db_free_result($result);
        //old stuff from here
        // if (db_num_rows($result)==1){
        // $session['user']=db_fetch_assoc($result);
        // $baseaccount = $session['user'];
        // $session['bufflist']=unserialize($session['user']['bufflist']);
        // if (!is_array($session['bufflist'])) $session['bufflist']=array();
        // $session['user']['dragonpoints']=unserialize($session['user']['dragonpoints']);
        // $session['user']['prefs']=unserialize($session['user']['prefs']);
        // if (!is_array($session['user']['dragonpoints'])) $session['user']['dragonpoints']=array();
        // if (is_array(unserialize($session['user']['allowednavs']))){
        // $session['allowednavs']=unserialize($session['user']['allowednavs']);
        // }else{
        // $session['allowednavs']=array($session['user']['allowednavs']);
        // }
        // if (!$session['user']['loggedin'] || ( (date("U") - strtotime($session['user']['laston'])) > getsetting("LOGINTIMEOUT",900)) ){
        // $session=array();
        // redirect("index.php?op=timeout","Account not logged in but session thinks they are.");
        // }
        // }else{
        // $session=array();
        // $session['message']=translate_inline("`4Error, your login was incorrect`0","login");
        // redirect("index.php","Account Disappeared!");
        // }
        // db_free_result($result);
        //debug($session);
        //check the nav exists in the session's allowednavs array
        if (isset($session['allowednavs'][$REQUEST_URI]) && $session['allowednavs'][$REQUEST_URI] && $overrideforced !== true) {
            //The nav is fine
            //clear the navs - more navs will be added as the script the player is currently viewing loads and executes
            $session['allowednavs'] = array();
        } else {
            if ($overrideforced !== true) {
                //This nav is not fine at all.  Redirect the player to badnav.php.
                //$session['badnav'] = 1;
                redirect("badnav.php", "Navigation not allowed to {$REQUEST_URI}");
            }
        }
    } else {
        if (!$anonymous) {
            $session['message'] = translate_inline("You are not logged in, this may be because your session timed out.", "login");
            redirect("index.php?op=timeout&nli=true", "Not logged in: {$REQUEST_URI}");
        }
    }
}
function getcommentary($section, $limit = 25, $talkline, $customsql = false, $showmodlink = false, $returnlink = false)
{
    //$gcstart = getmicrotime(true);
    global $session, $REQUEST_URI, $translation_namespace;
    global $chatloc, $bottomcid;
    if (!$returnlink) {
        $returnlink = URLEncode($_SERVER['REQUEST_URI']);
    }
    if ($showmodlink) {
        $link = buildcommentarylink("&bulkdelete=true&comscroll=" . $com);
        addnav("", $link);
        rawoutput("<form action=" . $link . " id='bulkdelete' method='post'>");
        $del = "Del";
        $undel = "UNDel";
    }
    //stops people from clicking on Bio links in the MoTD
    $nobios = array("motd.php" => true, "runmodule.php?module=global_banter" => true);
    if (!array_key_exists(basename($_SERVER['SCRIPT_NAME']), $nobios)) {
        $nobios[basename($_SERVER['SCRIPT_NAME'])] = false;
    }
    if ($nobios[basename($_SERVER['SCRIPT_NAME'])]) {
        $linkbios = false;
    } else {
        $linkbios = true;
    }
    // Needs to be here because scrolling through the commentary pages, entering a bio, then scrolling again forward
    // then re-entering another bio will lead to $com being smaller than 0 and this will lead to an SQL error later on.
    $com = (int) httpget("comscroll");
    if ($com < 0) {
        $com = 0;
    }
    if (httpget("comscroll") !== false && (int) $session['lastcom'] == $com + 1) {
        $cid = (int) $session['lastcommentid'];
    } else {
        $cid = 0;
    }
    $session['lastcom'] = $com;
    if (!$cid) {
        $cid = 1;
    }
    if ($customsql) {
        $sql = $customsql;
    } else {
        if ($section == "all") {
            $sql = "SELECT * FROM " . db_prefix("commentary") . " WHERE section NOT LIKE 'dwelling%' AND section NOT LIKE 'clan%' AND section NOT LIKE 'pet-%' ORDER BY commentid DESC LIMIT " . $com * $limit . ",{$limit}";
            $result = db_query($sql);
            $viewingallsections = 1;
        } else {
            $start = microtime(true);
            $sql = "SELECT * FROM " . db_prefix("commentary") . " WHERE section='{$section}' ORDER BY commentid DESC LIMIT " . $com * $limit . ",{$limit}";
            if (!$com) {
                //save doing db_fetch_assoc on commentary that's already cached; just unserialize and load it in one chunk
                $commentbuffer = datacache("commentary/latestcommentary_" . $section, 60);
                //debug($commentbuffer);
            }
            if (!is_array($commentbuffer) || $com) {
                $commentbuffer = array();
                $result = db_query($sql);
                while ($row = db_fetch_assoc($result)) {
                    $row['info'] = @stripslashes($row['info']);
                    $row['info'] = @unserialize($row['info']);
                    if (!is_array($row['info'])) {
                        $row['info'] = array();
                    }
                    $row['info']['link'] = buildcommentarylink("&commentid=" . $row['commentid']);
                    $commentbuffer[] = $row;
                }
                if (!$com) {
                    updatedatacache("commentary/latestcommentary_" . $section, $commentbuffer);
                }
            }
        }
    }
    $end = microtime(true);
    $tot = $end - $start;
    //debug($tot);
    //pre-formatting
    $commentbuffer = modulehook("commentbuffer-preformat", $commentbuffer);
    $rowcount = count($commentbuffer);
    if ($rowcount > 0) {
        $session['lastcommentid'] = $commentbuffer[0]['commentid'];
    }
    //figure out whether to handle absolute or relative time
    if (!array_key_exists('timestamp', $session['user']['prefs'])) {
        $session['user']['prefs']['timestamp'] = 0;
    }
    $session['user']['prefs']['timeoffset'] = round($session['user']['prefs']['timeoffset'], 1);
    if (!array_key_exists('commentary_reverse', $session['user']['prefs'])) {
        $session['user']['prefs']['commentary_reverse'] = 0;
    }
    //this array of userids means that with a single query we can figure out who's online and nearby
    $acctidstoquery = array();
    //prepare the actual comment line part of the comment - is it hidden, is it an action, is it a game comment, should we show a moderation link, clan rank colours, posting date abs/rel
    $loop1start = getmicrotime(true);
    $bioretlink = $returnlink;
    $bioretlink = URLEncode(buildcommentarylink("&frombio=true", $returnlink));
    $restorelink = buildcommentarylink("&comscroll=" . $com . "&restorecomment=", $returnlink);
    $removelink = buildcommentarylink("&comscroll=" . $com . "&removecomment=", $returnlink);
    for ($i = 0; $i < $rowcount; $i++) {
        if (!$commentbuffer[$i]['info']['hidecomment'] || $showmodlink) {
            $thiscomment = "";
            if ($viewingallsections) {
                $thiscomment .= "`b" . $row['section'] . "`0`b: ";
            }
            $row = $commentbuffer[$i];
            $row['acctid'] = $row['author'];
            $acctidstoquery[] = $row['author'];
            //$row['comment'] = comment_sanitize($row['comment']);
            if (substr($row['comment'], 0, 1) == ":" || substr($row['comment'], 0, 3) == "/me") {
                $row['skiptalkline'] = true;
                //remove beginning /me
                //$length = strlen($row['comment']);
                if (substr($row['comment'], 0, 3) == "/me") {
                    //debug("Match on ".$row['comment']);
                    $row['comment'] = substr($row['comment'], 3);
                } else {
                    if (substr($row['comment'], 0, 2) == "::") {
                        //debug("Match on ".$row['comment']);
                        $row['comment'] = substr($row['comment'], 2);
                    } else {
                        if (substr($row['comment'], 0, 1) == ":") {
                            //debug("Match on ".$row['comment']);
                            $row['comment'] = substr($row['comment'], 1);
                        }
                    }
                }
            }
            if ($row['info']['gamecomment'] || substr($row['comment'], 0, 5) == "/game" && !$row['name']) {
                //debug("Game Comment: ".$row['comment']);
                $row['gamecomment'] = true;
                $row['skiptalkline'] = true;
                $row['info']['icons'] = array();
                //$length = strlen($row['comment']);
                $row['comment'] = str_replace("/game", "", $row['comment']);
            }
            if ($linkbios && !isset($row['biolink'])) {
                $row['biolink'] = true;
            }
            if ($showmodlink) {
                if ($row['info']['hidecomment']) {
                    //$link = buildcommentarylink("&restorecomment=".$row['commentid']."&comscroll=".$com,$returnlink);
                    $thiscomment .= "`0[<a href='{$restorelink}" . $row['commentid'] . "'>{$undel}</a>]`0 <del>";
                    addnav("", $restorelink . $row['commentid']);
                } else {
                    //$link = buildcommentarylink("&removecomment=".$row['commentid']."&comscroll=".$com,$returnlink);
                    $thiscomment .= "`0[<a href='{$removelink}" . $row['commentid'] . "'>{$del}</a>] <input type='checkbox' name='deletecomment_" . $row['commentid'] . "'> `0 ";
                    addnav("", $removelink . $row['commentid']);
                }
            }
            if (!$row['gamecomment'] && ($row['info']['clanid'] || $row['info']['clanid'] === 0) && $row['info']['clanrank']) {
                $clanrankcolors = array(CLAN_APPLICANT => "`!", CLAN_MEMBER => "`3", CLAN_OFFICER => "`^", CLAN_LEADER => "`&", CLAN_FOUNDER => "`\$");
                $thiscomment .= "`0<a title=\"" . $row['info']['clanname'] . "\">&lt;" . $clanrankcolors[$row['info']['clanrank']] . $row['info']['clanshort'] . "`0&gt;</a>";
            }
            if (!$row['gamecomment']) {
                if ($row['biolink']) {
                    $bio = "bio.php?char=" . $row['acctid'] . "&ret=" . $bioretlink;
                    if (!$row['skiptalkline']) {
                        $thiscomment .= "<a href=\"{$bio}\" style=\"text-decoration: none\">`&" . $row['name'] . "</a>`& ";
                    } else {
                        $thiscomment .= "<a href=\"{$bio}\" style=\"text-decoration: none\">`&" . $row['name'] . "</a>`&";
                    }
                    addnav("", $bio);
                } else {
                    if (!$row['skiptalkline']) {
                        $thiscomment .= "`&" . $row['name'] . "`& ";
                    } else {
                        $thiscomment .= "`&" . $row['name'] . "`&";
                    }
                }
            }
            // if ($row['skiptalkline']){
            // $thiscomment.="`&";
            // }
            if (!$row['skiptalkline']) {
                $thiscomment .= $talkline . " \"";
                if (!isset($row['info']['talkcolour']) || $row['info']['talkcolour'] === false) {
                    $thiscomment .= "`#";
                } else {
                    $thiscomment .= "`" . $row['info']['talkcolour'];
                }
            }
            $thiscomment .= str_replace("&amp;", "&", htmlentities($row['comment'], ENT_COMPAT, getsetting("charset", "ISO-8859-1")));
            $thiscomment .= "`0";
            if (!$row['skiptalkline']) {
                $thiscomment .= "\"";
            }
            if ($row['info']['hidecomment']) {
                $thiscomment .= "</del>";
            }
            $commentbuffer[$i]['comment'] = $thiscomment;
            $commentbuffer[$i]['icons'] = $row['info']['icons'];
            $commentbuffer[$i]['time'] = strtotime($row['postdate']);
            if ($session['user']['prefs']['timestamp'] == 1) {
                if (!isset($session['user']['prefs']['timeformat'])) {
                    $session['user']['prefs']['timeformat'] = "[m/d h:ia]";
                }
                $time = strtotime($row['postdate']) + $session['user']['prefs']['timeoffset'] * 60 * 60;
                $s = date("`7" . $session['user']['prefs']['timeformat'] . "`0 ", $time);
                $commentbuffer[$i]['displaytime'] = $s;
            } elseif ($session['user']['prefs']['timestamp'] == 2) {
                $s = reltime(strtotime($row['postdate']));
                $commentbuffer[$i]['displaytime'] = "<span style='font-family: Courier New, Courier, monospace;'>[{$s}]`0</span> ";
            }
        } else {
            unset($commentbuffer[$i]);
        }
        $bottomcid = $commentbuffer[$i]['commentid'];
    }
    $loop1end = getmicrotime(true);
    $loop1tot = $loop1end - $loop1start;
    //debug("Loop 1: ".$loop1tot);
    //send through a modulehook for additional processing by modules
    $commentbuffer = modulehook("commentbuffer", $commentbuffer);
    //get offline/online/nearby status
    $acctids = join(',', $acctidstoquery);
    $onlinesql = "SELECT acctid, laston, loggedin, chatloc FROM " . db_prefix("accounts") . " WHERE acctid IN ({$acctids})";
    //cache it for 30 seconds
    if (!$com) {
        $onlineresult = db_query_cached($onlinesql, "commentary/whosonline_" . $section, 30);
    } else {
        $onlineresult = db_query($onlinesql);
    }
    $onlinestatus = array();
    $offline = date("Y-m-d H:i:s", strtotime("-" . getsetting("LOGINTIMEOUT", 900) . " seconds"));
    while ($row = db_fetch_assoc($onlineresult)) {
        $onlinestatus[$row['acctid']] = $row;
    }
    $onlinestatus[$session['user']['acctid']]['chatloc'] = $chatloc;
    $commentbuffer = array_values($commentbuffer);
    $rowcount = count($commentbuffer);
    //second loop through - add basic status icons for online/offline/nearby/afk/dnd
    $loop2start = getmicrotime(true);
    for ($i = 0; $i < $rowcount; $i++) {
        if (isset($commentbuffer[$i])) {
            $row = $commentbuffer[$i];
            if ($onlinestatus[$row['author']]['chatloc'] == "AFK") {
                $commentbuffer[$i]['info']['online'] = -1;
                $icon = array('icon' => "images/icons/onlinestatus/afk.png", 'mouseover' => "Away from Keyboard");
                $commentbuffer[$i]['info']['icons']['online'] = $icon;
                continue;
            }
            if ($onlinestatus[$row['author']]['chatloc'] == "DNI") {
                $commentbuffer[$i]['info']['online'] = -1;
                $icon = array('icon' => "images/icons/onlinestatus/dni.png", 'mouseover' => "DNI (please don't try to talk to this player right now!)");
                $commentbuffer[$i]['info']['icons']['online'] = $icon;
                continue;
            }
            if ($onlinestatus[$row['author']]['laston'] < $offline || !$onlinestatus[$row['author']]['loggedin']) {
                $commentbuffer[$i]['info']['online'] = 0;
                $icon = array('icon' => "images/icons/onlinestatus/offline.png", 'mouseover' => "Offline");
            } else {
                if ($onlinestatus[$row['author']]['chatloc'] == $chatloc) {
                    $commentbuffer[$i]['info']['online'] = 2;
                    $icon = array('icon' => "images/icons/onlinestatus/nearby.png", 'mouseover' => "Nearby");
                } else {
                    $commentbuffer[$i]['info']['online'] = 1;
                    $icon = array('icon' => "images/icons/onlinestatus/online.png", 'mouseover' => "Online");
                }
            }
            $commentbuffer[$i]['info']['icons']['online'] = $icon;
        }
    }
    $loop2end = getmicrotime(true);
    $loop2tot = $loop2end - $loop2start;
    //debug("Loop 2: ".$loop2tot);
    //debug($commentbuffer);
    // $gcend = getmicrotime(true);
    // $gctotal = $gcend - $gcstart;
    // debug("getcommentary execution time: ".$gctotal);
    return $commentbuffer;
}
function nasty_word_list()
{
    $search = datacache("nastywordlist", 600);
    if ($search !== false && is_array($search)) {
        return $search;
    }
    $sql = "SELECT * FROM " . db_prefix("nastywords") . " WHERE type='nasty'";
    $result = db_query($sql);
    $row = db_fetch_assoc($result);
    $search = " " . $row['words'] . " ";
    $search = preg_replace('/(?<=.)(?<!\\\\)\'(?=.)/', '\\\'', $search);
    $search = str_replace("a", '[a4@ªÀÁÂÃÄÅàáâãäå]', $search);
    $search = str_replace("b", '[bß]', $search);
    $search = str_replace("d", '[dÐÞþ]', $search);
    $search = str_replace("e", '[e3ÉÊËÈèéêë]', $search);
    $search = str_replace("n", '[nÑñ]', $search);
    $search = str_replace("o", '[o°º0ÒÓÔÕÖØðòóôõöø¤]', $search);
    $search = str_replace("p", '[pÞþ¶]', $search);
    $search = str_replace("r", '[r®]', $search);
    //	$search = str_replace("s",'[sz$§]',$search);
    $search = preg_replace('/(?<!\\\\)s/', '[sz$§]', $search);
    $search = str_replace("t", '[t7+]', $search);
    $search = str_replace("u", '[uÛÜÙÚùúûüµ]', $search);
    $search = str_replace("x", '[xפ]', $search);
    $search = str_replace("y", '[yÝ¥ýÿ]', $search);
    //these must happen in exactly this order:
    $search = str_replace("l", '[l1!£]', $search);
    $search = str_replace("i", '[li1!¡ÌÍÎÏìíîï]', $search);
    $search = str_replace("k", 'c', $search);
    $search = str_replace("c", '[c\\(kç©¢]', $search);
    $start = "'\\b";
    $end = "\\b'iU";
    $ws = "[^[:space:]\\t]*";
    //whitespace (\w is not hungry enough)
    //space not preceeded by a star
    $search = preg_replace("'(?<!\\*) '", ")+{$end} ", $search);
    //space not anteceeded by a star
    $search = preg_replace("' (?!\\*)'", " {$start}(", $search);
    //space preceeded by a star
    $search = str_replace("* ", ")+{$ws}{$end} ", $search);
    //space anteceeded by a star
    $search = str_replace(" *", " {$start}{$ws}(", $search);
    $search = "{$start}(" . trim($search) . ")+{$end}";
    $search = str_replace("{$start}()+{$end}", "", $search);
    $search = explode(" ", $search);
    updatedatacache("nastywordlist", $search);
    return $search;
}
function load_item_settings()
{
    //load settings for all items, put them in $itemsettings
    //we don't cache this because the datacache is lousy at getting large amounts of data via db_fetch_assoc.
    global $itemsettings;
    if (!isset($itemsettings) || !is_array($itemsettings)) {
        $itemsettings = datacache("item-settings");
    }
    if (!isset($itemsettings) || !is_array($itemsettings)) {
        $itemsettings = array();
        $ssql = "SELECT * FROM " . db_prefix("items_settings");
        $sresult = db_query($ssql);
        //debug("Loading item settings from database");
        while ($srow = db_fetch_assoc($sresult)) {
            $itemsettings[$srow['item']][$srow['setting']] = stripslashes($srow['value']);
        }
        updatedatacache("item-settings", $itemsettings);
    }
}
function stamina_minihof_old($action, $userid = false)
{
    global $session;
    if ($userid === false) {
        $userid = $session['user']['acctid'];
    }
    $st = microtime(true);
    $boardfilename = str_replace(" ", "", $action);
    $boardinfo = datacache("staminaboardinfo_" . $boardfilename, 20);
    $en = microtime(true);
    $to = $en - $st;
    debug("Cache: " . $to);
    if (!is_array($boardinfo)) {
        $board = array();
        $staminasql = "SELECT setting,value,userid FROM " . db_prefix("module_userprefs") . " WHERE modulename='staminasystem' AND setting='actions'";
        $staminaresult = db_query($staminasql);
        $scount = db_num_rows($staminaresult);
        for ($i = 0; $i < $scount; $i++) {
            $row = db_fetch_assoc($staminaresult);
            $actions_array = @unserialize($row['value']);
            $actiondetails = $actions_array[$action];
            if (!$actiondetails['exp']) {
                continue;
            }
            $board[$row['userid']]['xp'] = $actiondetails['exp'];
            $board[$row['userid']]['id'] = $row['userid'];
            //$board[$row['userid']]['lvl'] = $actiondetails['lvl'];
        }
        $boardinfo = stamina_minihof_assignranks($board);
        updatedatacache("staminaboardinfo_" . $boardfilename, $boardinfo);
    }
    //set the player's entry in the board with brand-new data
    $player_action = get_player_action($action);
    $boardinfo['board'][$userid]['xp'] = $player_action['exp'];
    $smallboard = stamina_minihof_smallboard($boardinfo, $userid);
    debug($smallboard);
}
function logd_error_notify($errno, $errstr, $errfile, $errline, $backtrace)
{
    global $session;
    $sendto = explode(";", getsetting("notify_address", ""));
    $howoften = getsetting("notify_every", 30);
    reset($sendto);
    $data = datacache("error_notify", 86400);
    if (!is_array($data)) {
        $data = array('firstrun' => true, 'errors' => array());
    } else {
        $data['firstrun'] = false;
    }
    $do_notice = false;
    if (!array_key_exists($errstr, $data['errors'])) {
        $do_notice = true;
    } elseif (strtotime("now") - $data['errors'][$errstr] > $howoften * 60) {
        $do_notice = true;
    }
    if ($data['firstrun']) {
        debug("First run, not notifying users.");
    } else {
        if ($do_notice) {
            /***
             * Set up the mime bits
             **/
            require_once "sanitize.php";
            $notice_text = "This is a multi-part message in MIME format.";
            $userstr = "";
            if ($session && isset($session['user']['name']) && isset($sesson['user']['acctid'])) {
                $userstr = "Error triggered by user " . $session['user']['name'] . " (" . $session['user']['acctid'] . ")\n";
            }
            $plain_text = "{$userstr}{$errstr} in {$errfile} ({$errline})\n" . sanitize_html($backtrace);
            $html_text = "<html><body>{$errstr} in {$errfile} ({$errline})<hr>{$backtrace}</body></html>";
            $semi_rand = md5(time());
            $mime_boundary = "==MULTIPART_BOUNDARY_{$semi_rand}";
            $mime_boundary_header = chr(34) . $mime_boundary . chr(34);
            $subject = "{$_SERVER['HTTP_HOST']} {$level}";
            $body = "{$notice_text}\r\n\r\n--{$mime_boundary}\r\nContent-Type: text/plain; charset=us-ascii\r\nContent-Transfer-Encoding: 7bit\r\n\r\n{$plain_text}\r\n\r\n--{$mime_boundary}\r\nContent-Type: text/html; charset=us-ascii\r\nContent-Transfer-Encoding: 7bit\r\n\r\n{$html_text}\r\n\r\n--{$mime_boundary}--";
            /***
             * Mime bits are set up,
             **/
            while (list($key, $email) = each($sendto)) {
                debug("Notifying {$email} of this error.");
                mail($email, $subject, $body, "From: " . $from . "\n" . "MIME-Version: 1.0\n" . "Content-Type: multipart/alternative;\n" . "     boundary=" . $mime_boundary_header);
            }
            //mark the time that notice was last sent for this error.
            $data['errors'][$errstr] = strtotime("now");
        } else {
            debug("Not notifying users for this error, it's only been " . round((strtotime("now") - $data['errors'][$errstr]) / 60, 2) . " minutes.");
        }
    }
    updatedatacache("error_notify", $data);
    debug($data);
}