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);
}
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 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;
}
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");
        //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 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}");
        }
    }
}
/**
 * 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 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 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 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;
}