There are 52 previous versions of this script.
Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)
// ==UserScript==
// @name Quake Live New Alt Browser
// @version 1.65
// @namespace http://userscripts.org/scripts/show/73076
// @homepage http://userscripts.org/scripts/show/73076
// @description A new redesign of the Quake Live server browser.
// @icon https://phob.net/ql_new_alt_browser/thumb.png
// @screenshot https://phob.net/ql_new_alt_browser/large.png https://phob.net/ql_new_alt_browser/thumb.png
// @author wn
// @include http://*.quakelive.com/*
// @exclude http://*.quakelive.com/forum*
// @updateURL https://userscripts.org/scripts/source/73076.meta.js
// ==/UserScript==
/**
* Set up some stuff for user script updating and menu commands.
*/
var SCRIPT_NAME = "Quake Live New Alt Browser"
, SCRIPT_VER = "1.65"
GM_updatingEnabled = "GM_updatingEnabled" in window ? GM_updatingEnabled : false;
// Set up some utility things
var DEBUG = false
, DOLITTLE = function() {}
, logMsg = DEBUG ? function(aMsg) {GM_log(aMsg)} : DOLITTLE
, logError = function(aMsg) {GM_log("ERROR: " + aMsg)};
/**
* Evaluate code in the page context.
* Source: http://wiki.greasespot.net/Content_Script_Injection
*/
function contentEval(source) {
// Check for function input.
if ("function" == typeof(source)) {
source = "(" + source + ")();";
}
// Create a script node holding this source code.
var script = document.createElement("script");
script.setAttribute("type", "application/javascript");
script.textContent = source;
// Insert the script node into the page, so it will run, and immediately
// remove it to clean up.
document.body.appendChild(script);
document.body.removeChild(script);
}
// Don't bother if we're missing critical GM_ functions
if ("undefined" == typeof(GM_xmlhttpRequest)) {
alert("Your browser/add-on doesn't support GM_xmlhttpRequest, which is "
+ "required for " + SCRIPT_NAME + " to operate.");
return;
}
/**
* Based on:
* GM_ API emulation for Chrome; 2009, 2010 James Campos
* cc-by-3.0; http://creativecommons.org/licenses/by/3.0/
*/
if (window.chrome) {
GM_getValue = function(aName, aDefaultValue) {
var value = localStorage.getItem(aName);
if (!value) return aDefaultValue;
var type = value[0];
value = value.substring(1);
switch (type) {
case "b":
return value == "true";
case "n":
return Number(value);
default:
return value;
}
}
GM_setValue = function(aName, aValue) {
aValue = (typeof aValue)[0] + aValue;
localStorage.setItem(aName, aValue);
}
}
if (window.chrome || "undefined" == typeof(GM_registerMenuCommand)) {
GM_registerMenuCommand = DOLITTLE;
}
/**
* Use an auto-update script if integrated updating isn't enabled
* http://userscripts.org/scripts/show/38017
* NOTE: Modified to add the new version number to the upgrade prompt
* and custom messages for Chrome users (requires a manual update).
*/
if (!GM_updatingEnabled) {
var AutoUpdater_73076={id:73076,days:1,name:SCRIPT_NAME,version:SCRIPT_VER,time:new Date().getTime(),call:function(response,secure){GM_xmlhttpRequest({method:"GET",url:"http"+(secure?"s":"")+"://userscripts.org/scripts/source/"+this.id+".meta.js",onload:function(xpr){AutoUpdater_73076.compare(xpr,response)},onerror:function(xpr){if(secure){AutoUpdater_73076.call(response,false)}}})},enable:function(){GM_registerMenuCommand(this.name+": Enable updates",function(){GM_setValue("updated_73076",new Date().getTime()+"");AutoUpdater_73076.call(true,true)})},compareVersion:function(r_version,l_version){var r_parts=r_version.split("."),l_parts=l_version.split("."),r_len=r_parts.length,l_len=l_parts.length,r=l=0;for(var i=0,len=(r_len>l_len?r_len:l_len);i<len&&r==l;++i){r=+(r_parts[i]||"0");l=+(l_parts[i]||"0")}return(r!==l)?r>l:false},compare:function(xpr,response){this.xversion=/\/\/\s*@version\s+(.+)\s*\n/i.exec(xpr.responseText);this.xname=/\/\/\s*@name\s+(.+)\s*\n/i.exec(xpr.responseText);if((this.xversion)&&(this.xname[1]==this.name)){this.xversion=this.xversion[1];this.xname=this.xname[1]}else{if((xpr.responseText.match("the page you requested doesn't exist"))||(this.xname[1]!=this.name)){GM_setValue("updated_73076","off")}return false}var updated=this.compareVersion(this.xversion,this.version);if(updated&&confirm("A new version of "+this.xname+" is available.\nDo you wish to install the latest version ("+this.xversion+")?")){var path="http://userscripts.org/scripts/source/"+this.id+".user.js";if(window.chrome){prompt("This script can't be updated automatically in Chrome.\nPlease uninstall the old version, and navigate to the URL provided below.",path)}else{try{window.parent.location.href=path}catch(e){}}}else{if(this.xversion&&updated){if(confirm("Do you want to turn off auto updating for this script?")){GM_setValue("updated_73076","off");this.enable();if(window.chrome){alert("You will need to reinstall this script to enable auto-updating.")}else{alert("Automatic updates can be re-enabled for this script from the User Script Commands submenu.")}}}else{if(response){alert("No updates available for "+this.name)}}}},check:function(){if(GM_getValue("updated_73076",0)=="off"){this.enable()}else{if(+this.time>(+GM_getValue("updated_73076",0)+1000*60*60*24*this.days)){GM_setValue("updated_73076",this.time+"");this.call(false,true)}GM_registerMenuCommand(this.name+": Check for updates",function(){GM_setValue("updated_73076",new Date().getTime()+"");AutoUpdater_73076.call(true,true)})}}};AutoUpdater_73076.check();
}
// Don't bother if Quake Live is down for maintenance
if (/offline/i.test(document.title)) {
return;
}
// Make sure we're on top
if (window.self != window.top) {
return;
}
/**
* Set up content-side utilities and initialize listener for GM-side things
* NOTE: 'var' left off intentionally so other sections can hit it.
*/
contentEval(function() {
// Content-side definition of GM_addStyle
if ("undefined" == typeof(GM_addStyle)) {
GM_addStyle = function(css) {
var head = document.getElementsByTagName("head")[0];
if (head) {
var style = document.createElement("style");
style.textContent = css;
style.type = "text/css";
head.appendChild(style);
}
}
}
// The content<->GM mediator (and other useful stuff)
QLNAB = {
// Logging
logMsg: function(aMsg) {
if ("platform" in window || !(console && console.log)) {
QLNAB.logMsg = function() {};
}
else {
QLNAB.logMsg = function(aMsg) {QLNAB.PREFS["debug"] && console.log("QLNAB: " + aMsg)};
QLNAB.logMsg(aMsg);
}
},
logError: function(aMsg) {
if ("platform" in window || !(console && console.log)) {
QLNAB.logError = function() {};
}
else {
QLNAB.logError = function(aMsg) {QLNAB.logMsg("ERROR: " + aMsg)};
QLNAB.logError(aMsg);
}
},
PREFS: {},
prefHandlers: {},
addHandler: function(aPref, aHandler) {
if (!(aPref in QLNAB.prefHandlers)) {
QLNAB.prefHandlers[aPref] = [];
}
QLNAB.prefHandlers[aPref].push(aHandler);
},
// Trigger pref handler (or handlers) with the current pref value
trigger: function(aPref) {
if (aPref in QLNAB.PREFS && aPref in QLNAB.prefHandlers) {
for (var i = 0, e = QLNAB.prefHandlers[aPref].length; i < e; ++i) {
QLNAB.prefHandlers[aPref][i].call(null, QLNAB.PREFS[aPref]);
}
}
},
// Message prefix
RE_msg: /^QLNAB:/,
handleMessage: function(aEvent) {
if (!(aEvent && aEvent.data)) {
return;
}
var response;
try {
response = JSON.parse(aEvent.data);
}
catch(e) {
return QLNAB.logError("handleMessage: " + e);
}
// Make sure we have everything required for a QLNAB message
if (!("type" in response && "name" in response && "value" in response)) {
return;
}
// Make sure this is a QLNAB message
if (!QLNAB.RE_msg.test(response.type)) {
return;
}
switch (response.type.replace(QLNAB.RE_msg, "")) {
case "setPref":
QLNAB.logMsg("setting preference '" + response.name + "' => '" + response.value + "'");
QLNAB.PREFS[response.name] = response.value;
QLNAB.trigger(response.name);
break;
}
}
};
// Toggle gametype icon display
QLNAB.addHandler("ql_alt_gametypeIcon", function(newVal) {
// show gametype image rather than text
if (newVal) {
$("#qlv_postlogin_matches tbody span.agameicon").each(function() {
$(this).replaceWith("<img class='agameicon' src='" + $(this).attr("src")
+ "' title='" + $(this).text() + "' />");
});
}
// show gametype text rather than image
else {
$("#qlv_postlogin_matches tbody img.agameicon").each(function() {
$(this).replaceWith("<span class='agameicon' src='"
+ $(this).attr("src") + "'>"
+ $(this).attr("title") + "</span>");
});
}
});
// Toggle best pick highlighting
QLNAB.addHandler("ql_alt_bestPickHighlight", function(newVal) {
$("#qlv_postlogin_matches tbody tr.bestpick").toggleClass("noHighlight", !newVal);
});
window.addEventListener("message", QLNAB.handleMessage, false);
}); // end of call to contentEval
/**
* This does the following:
* - Sets the GM preference
* - Tells the content script to set the preference and notify any handlers
*/
function changePref(aName, aValue) {
GM_setValue(aName, aValue);
window.postMessage(JSON.stringify({
"type": "QLNAB:setPref",
"name": aName,
"value": aValue
}), "*");
}
/**
* Create our menu commands
*/
GM_registerMenuCommand(SCRIPT_NAME + ": toggle gametype icon", function() {
changePref("ql_alt_gametypeIcon", !GM_getValue("ql_alt_gametypeIcon", true));
});
GM_registerMenuCommand(SCRIPT_NAME + ": toggle recommended server highlighting", function() {
changePref("ql_alt_bestPickHighlight", !GM_getValue("ql_alt_bestPickHighlight", true));
});
/**
* Initialize the content side with our preferences
*/
changePref("debug", DEBUG);
changePref("ql_alt_bestPickHighlight", !!window.chrome ? false : GM_getValue("ql_alt_bestPickHighlight", true));
changePref("ql_alt_gametypeIcon", GM_getValue("ql_alt_gametypeIcon", true));
/**
* Important one-time notifications
*/
if (!GM_getValue("ql_alt_notified_newFilter", false)) {
GM_setValue("ql_alt_notified_newFilter", true);
contentEval(function() {
// Poll every 2 seconds
var notifier = setInterval(function() {
// Unlikely, but don't interfere if the game is running.
if ($("#qlv_user_data").length && !quakelive.IsGameRunning()) {
clearInterval(notifier);
var msg = "Hello! This latest update includes some nice changes you might not immediately notice:<br/><br/>"
+ "<ul><li style='margin-bottom:1em'><b>Server filtering:</b> Filtering has been improved. "
+ "For example:<br/><br/>An Include filter of <code>'New York + CTF, Frankfurt + qlnab.open'</code> means "
+ "<code>'Include New York CTF servers OR any Frankfurt server with open slots'</code>.<br/><br/>"
+ "An Include filter of <code>'Paris + Duel, CA + Asylum + qlnab.open'</code> means "
+ "<code>'Include any Paris duel server OR any clan arena server on Asylum with open slots'</code>.</li>"
+ "<li><b>Sort order persistence:</b> If you sort the list a certain way, the order will be remembered across page loads. "
+ "You can sort on multiple columns by holding down the <code>Shift</code> key while clicking.</li></ul>";
qlPrompt({
title: "Quake Live New Alt Browser Notice",
body: msg,
fatal: false,
alert: true
});
}
}, 2E3);
}); // end of call to contentEval
}
/**
* Stylin'
*/
GM_addStyle(".postlogin_nav ul li { font-weight:bold; }" +
"#ql_alt_filters_include, #ql_alt_filters_exclude { width: 90%; border:1px solid black; margin-top:1em; }" +
"#ql_alt_filters_exclude { margin-bottom: 1em; }" +
"#qlv_postlogin_matches p { background: transparent; }" +
// width: 1280+ = 100%, 1152-1279 = 94%, 1024-1151 = 85%, < 1024 = 52%
"#ql_alt_browser { width: " + (screen.width < 1280 ? screen.width < 1024 ? "52%" : screen.width < 1152 ? "85%" : "94%" : "100%") + "; color: black; background-color: rgba(255, 255, 255, 0.4); }" +
"#ql_alt_browser thead th { padding: 6px 0; cursor: pointer; color: white; font-weight: bold; text-shadow: 1px 1px 5px #666; filter: dropshadow(color=#666, offx=1, offy=1); }" +
"#ql_alt_browser thead th.headerSortUp, .bestpick, .filter_enabled { text-shadow: 1px 1px 5px #f00; filter: dropshadow(color=#f00, offx=1, offy=1); }" +
"#ql_alt_browser thead th.headerSortDown { text-shadow: 1px 1px 5px #fb4; filter: dropshadow(color=#fb4, offx=1, offy=1); }" +
".noHighlight { text-shadow: none; filter: none; }" +
".alocation_flag { width: 16px; height: 11px }" +
".agameicon { padding: 1px 0 }" +
".agameicon span { display: none; }" +
".agameicon + .agamemods { position: static; display: none; height: 21px; }" +
"span.agameicon + .agamemods { height: auto; position: relative; bottom: -1px; }" +
".alocation_text, .agameicon, .amapname, .agamelabel, .aplayers { cursor: default; }" +
".agamerank { float: left; height: 18px; width: 18px; }" +
".aplayers { padding-left: 4px; }" +
".header, .headerSortUp, .headerSortDown { background: transparent; font-size: 100%; }" +
".normalZebraOff,.blueZebraOff,.redZebraOff,.normalZebraOn,.blueZebraOn,.redZebraOn,.teamZebraOff,.teamZebraOn { background-image: none; }" +
".odd, .normalZebraOn { background-color: rgba(204, 204, 204, 0.35); }" +
".odd:hover, .normalZebraOn:hover { background-color: rgba(219, 219, 219, 1); }" +
".even, .normalZebraOff { background-color: rgba(255, 255, 255, 0.35); }" +
".even:hover, .normalZebraOff:hover { background-color: rgba(235, 235, 235, 1); }" +
".link { cursor: pointer; }" +
".link.share_link_img { float: left; margin-top: 3px; margin-left: 4px; }" +
".cond { letter-spacing: -1px; }" +
".bold { font-weight: 600; }"
);
// NOTE: this is required to get access to 'quakelive.resource'
contentEval(function() {
// Chrome doesn't let a background image cross multiple cells, so make it black.
GM_addStyle("#ql_alt_browser thead tr { background: "
+ (!!window.chrome ? "black" : ("url('"
+ quakelive.resource('/images/postlogin_navbar/nav_bar.png')
+ "') -10px 0px")) + "; }");
});
/**
* Create tablesorter parsers
*/
contentEval(function() {
// Player sorting (e.g. 7/8)
$.tablesorter.addParser({
id: "players",
is: function(s) {
return false;
},
format: function(s) {
var sp = s.split("/");
return parseFloat(sp[0] + "." + (sp[1] << 2));
},
type: "numeric"
});
$.tablesorter.addParser({
id: "rank",
is: function(s) {
return false;
},
format: function(s) {
s = s.substr(s.indexOf("rank_") + 5, 1);
return (s == "p" ? -9 : s);
},
type: "numeric"
});
// Sort persistence
$.tablesorter.addWidget({
id: "sortPersist",
format: function(table) {
if (table.config.debug) {
var time = new Date();
}
var key = "ql_alt_browser_sort";
var val = localStorage.getItem(key);
var data = {};
var sortList = table.config.sortList;
var tableId = $(table).attr("id");
var valExists = (typeof(val) != "undefined" && val != null);
if (sortList.length > 0) {
if (valExists) {
data = JSON.parse(val);
}
data[tableId] = sortList;
localStorage.setItem(key, JSON.stringify(data));
}
else {
if (valExists) {
var data = JSON.parse(val);
if (typeof(data[tableId]) != "undefined" && data[tableId] != null) {
sortList = data[tableId];
if (sortList.length > 0) {
$(table).trigger("sorton", [sortList]);
}
}
}
}
if (table.config.debug) {
$.tablesorter.benchmark("Applying sortPersist widget", time);
}
}
});
}); // end of call to contentEval
/**
* Set up the prototype for a server manager listener
*/
contentEval(function() {
function qlnabServerManagerListener() {
qlnabServerManagerListener.prototype.OnRefreshServersSuccess = function() {};
qlnabServerManagerListener.prototype.OnRefreshServersError = function() {};
qlnabServerManagerListener.prototype.OnStartRefreshServers = function() {};
qlnabServerManagerListener.prototype.OnSaveFilterSuccess = function() {};
qlnabServerManagerListener.prototype.OnSaveFilterError = function() {};
}
/**
* Set up the prototype for the server list view
*/
var cols = 6;
var o = {
target: "ql_alt_browser tbody",
max: 0,
group_cache_size: 6
}
function qlnabServerListView() {}
qlnabServerListView.prototype = new qlnabServerManagerListener;
qlnabServerListView.prototype.SetDisplayProps = function(d) {
this.props = $.extend({}, o, d);
}
qlnabServerListView.prototype.GetContainerNodeId = function() {
return this.props.target;
}
qlnabServerListView.prototype.GetServerNodeId = function(d) {
return "match_" + d.public_id;
}
qlnabServerListView.prototype.UpdateServerNode = function(server, $row) {
var gametype = mapdb.getGameTypeByID(server.game_type);
var gtTitle = gametype.name.toUpperCase() + " - " + gametype.title;
var gameLabel = " (" + (server.premium ? "Premium " : "") + server.host_name + ")";
var rank = GetSkillRankInfo(server.skillDelta);
var max_players = server.teamsize > 0 ? server.teamsize * (gametype.name == "ffa" ? 1 : 2) : server.max_clients;
var players = server.num_clients + "/" + max_players;
var map = mapdb.getBySysName(server.map.toLowerCase());
map = map ? map.name : "Unknown";
var locName, locObj, flagURL;
if (locObj = locdb.GetByID(server.location_id)) {
// From /user/load locations
locName = locObj.GetCityState() + (InArray(server.location_id, [14,27,29,32,37,43]) ?
" #1" : InArray(server.location_id, [30,33,34,36,39,42,44]) ?
" #2" : InArray(server.location_id, [51]) ?
" #3" : "");
flagURL = locObj.GetFlagIcon();
}
else {
locName = "QUAKE LIVE";
flagURL = "/images/flags3cc/usa.gif";
}
if ($row.find(".qlv_inner_line").length == 0) {
$row.html("<td class='qlv_inner_line'><img class='alocation_flag' src='' /></td>" +
"<td align='left' class='alocation_text'></td>" +
"<td align='left'><img src='' class='agameicon' /><div class='agamemods'></div></td>" +
"<td align='left'><span class='amapname bold'></span><span class='agamelabel cond'></span></td>" +
"<td align='left'><img src='' class='agamerank' /> <a href='" + quakelive.siteConfig.baseUrl + "/r/join/" + server.public_id + "' onclick='qlPrompt({title: \"Server Link\", body: \"Link to this game\", input: true, inputLabel: $(this).attr(\"href\"), inputReadOnly: true, alert: true}); return false;' class='link share_link_img' title='Link to this game'></a></td>" +
"<td align='left' class='aplayers'></td>");
}
$row.find(".alocation_flag").attr({"src": quakelive.resource(flagURL), "title": locObj.country || "United States"});
$row.find(".alocation_text").text(locName);
$row.find(".agameicon").attr({"src": quakelive.resource("/images/gametypes/sm/" + gametype.name + ".png"), "title": gtTitle});
var mods = []
, useMods = server.owner && (server.ruleset != 1 || server.g_customSettings != 0);
if (useMods) {
mods = server.GetModifiedSettings();
$row.find(".agamemods")
.html("<div class='modified_icon'></div>")
.css("display", "inline-block")
.find(".modified_icon")
.data("mods", mods)
.hover(function(e) {
var $tip = $("#agamemods_tip");
if (0 == $tip.length) {
$("body").append("<div id='agamemods_tip'></div>");
$tip = $("#agamemods_tip");
$tip.css({
position: "absolute",
width: "auto",
background: "rgba(235, 235, 235, 1)",
"background-position": "-7px",
color: "#000",
border: "1px solid #000",
padding: "10px",
"z-index": 999
})
.mouseleave(function() { $(this).hide(); });
}
$tip.css({
left: (e.pageX - 0) + "px",
top: (e.pageY + 10) + "px",
})
.html("<b>Server Modifications:</b><br />" + $(this).data("mods").join("<br />"))
.show();
}, function() {
$("#agamemods_tip").hide();
});
}
else {
$row.find(".agamemods").hide();
}
$row.find(".amapname").text(map);
$row.find(".agamelabel").html(gameLabel);
$row.find(".agamerank").attr({"src": quakelive.resource((server.g_needpass ? "/images/lgi/server_details_ranked.png" : "/images/sf/login/rank_" + rank.delta + ".png")), "title": rank.desc.replace(/(<([^>]+)>)/ig, '')});
$row.find(".aplayers").text(players).attr("title", server.max_clients + " slots");
server.ordinal < 3 ? $row.addClass("bestpick") : $row.removeClass("bestpick");
var ql_alt_filters_exclude = localStorage.getItem("ql_alt_filters_exclude"),
ql_alt_filters_include = localStorage.getItem("ql_alt_filters_include"),
show_entry = true,
hide_entry = false,
RE_comma = /\s*,\s*/,
RE_plus = /\s*\+\s*/,
RE_escapeMe = /([\^\$\\\/\(\)\|\?\+\*\[\]\{\}\,\.])/g,
modsFilter = mods.slice(0),
freg;
// Add mod keywords "mods" and "modifications"
modsFilter.push("mods");
modsFilter.push("modifications");
modsFilter = modsFilter.join("\t");
// Include filtering
if (ql_alt_filters_include) {
ql_alt_filters_include = ql_alt_filters_include.split(RE_comma);
if (ql_alt_filters_include[0]) {
// Assume we will hide the entry
show_entry = false;
// Split each filter value on +'s to get subfilters
var subfilters = [];
for (var i = 0, j = ql_alt_filters_include.length; i < j; ++i) {
if (0 == ql_alt_filters_include[i].length) {
continue;
}
subfilters.push(ql_alt_filters_include[i].split(RE_plus));
}
// Check each subfilter...
for (var i = 0, j = subfilters.length; i < j; ++i) {
var submatch = true;
// Check this subfilter's components...
for (var x = 0, y = subfilters[i].length; x < y; ++x) {
if (0 == subfilters[i][x].length) {
continue;
}
freg = new RegExp(subfilters[i][x].replace(RE_escapeMe, "\\$1"), "i");
// If nothing matches, this is not part of an acceptable subfilter.
if (!(("qlnab.open" == subfilters[i][x] && server.num_players < max_players)
|| players.substr(players.indexOf("/")) == subfilters[i][x]
|| locName.match(freg)
|| gtTitle.match(freg)
|| (useMods && modsFilter.match(freg))
|| gameLabel.match(freg)
|| ((server.map.match(freg) || map.match(freg)) && server.num_clients !== 0)
)) {
submatch = false;
break;
}
}
// Show entry if this subfilter was completely matched.
show_entry = show_entry || submatch;
}
}
}
// Exclude filtering
if (ql_alt_filters_exclude) {
ql_alt_filters_exclude = ql_alt_filters_exclude.split(RE_comma);
if (ql_alt_filters_exclude[0]) {
// Assume we will not hide the entry
hide_entry = false;
// Split each filter value on +'s to get subfilters
var subfilters = [];
for (var i = 0, j = ql_alt_filters_exclude.length; i < j; ++i) {
if (0 == ql_alt_filters_exclude[i].length) {
continue;
}
subfilters.push(ql_alt_filters_exclude[i].split(RE_plus));
}
// Check each subfilter...
for (var i = 0, j = subfilters.length; i < j; ++i) {
var submatch = true;
// Check this subfilter's components...
for (var x = 0, y = subfilters[i].length; x < y; ++x) {
if (0 == subfilters[i][x].length) {
continue;
}
freg = new RegExp(subfilters[i][x].replace(RE_escapeMe, "\\$1"), "i");
// If nothing matches, this is not part of an acceptable subfilter.
if (!(("qlnab.open" == subfilters[i][x] && server.num_players < max_players)
|| players.substr(players.indexOf("/")) == subfilters[i][x]
|| locName.match(freg)
|| gtTitle.match(freg)
|| (useMods && modsFilter.match(freg))
|| gameLabel.match(freg)
|| ((server.map.match(freg) || map.match(freg)) && server.num_clients !== 0)
)) {
submatch = false;
break;
}
}
// Hide entry if one of the subfilters matched.
if (submatch) {
hide_entry = true;
break;
}
}
}
}
$row.css("display", show_entry && !hide_entry ? "table-row" : "none");
return $row;
};
qlnabServerListView.prototype.OnRefreshServersSuccess = function(d) {
quakelive.SendModuleMessage("OnServerListReload", d);
this.DisplayServerList(d);
this.SortServerList(d);
$("#agamemods_tip").remove();
}
qlnabServerListView.prototype.OnRefreshServersError = function() {
var d = $("#" + this.GetContainerNodeId()).empty();
d.append("<tr><td colspan='" + cols + "'><p class='tc TwentyPxTxt midGrayTxt' style='width: 70%; margin: 20% auto; color: #f00'>We've encountered an error loading the list of games. Please wait and we will try to reload the list.</p></td></tr>");
}
qlnabServerListView.prototype.OnBeforeDisplayServerList = function(d, e) {
e.length > 0 && $("#qlv_welcome .welcome_matches_header").css("visibility", "visible");
}
qlnabServerListView.prototype.OnAfterDisplayServerList = function(d, e) {
var $tbody = $("#ql_alt_browser tbody");
if (e.length == 0) {
d.append("<tr><td colspan='" + cols + "'><p class='tc thirtyPxTxt midGrayTxt'>No Games Available</p></td></tr>");
quakelive.siteConfig.realm == "focus" ?
d.append("<tr><td colspan='" + cols + "'><p class='tc TwentyPxTxt midGrayTxt'>A focus test may not be active at this time.<br />Please check the News Feed for scheduled test times.</p></td></tr>")
: d.append("<tr><td colspan='" + cols + "'><p class='tc TwentyPxTxt midGrayTxt'>Check Your Customize Settings</p></td></tr>");
}
// If there are servers in the list...
else {
if (0 == $tbody.find("tr:visible").length) {
$tbody.append("<tr><td colspan='" + cols + "'><p class='tc TwentyPxTxt midGrayTxt'>All servers have been hidden by the Alt Browser. Check your filters.</p></td></tr>");
}
}
}
qlnabServerListView.prototype.OnBeforeUpdateServerNode = function(d, e) {}
qlnabServerListView.prototype.OnRemoveServer = function(d, e) {
d = $("#" + this.GetServerNodeId(e));
d.length > 0 && d.remove();
quakelive.matchtip.HideMatchTooltip(e.public_id);
}
qlnabServerListView.prototype.OnUpdateServer = function(d, e) {
d = $("#" + this.GetServerNodeId(e));
if (d.length == 0) {
d = $("<tr id='" + this.GetServerNodeId(e) + "'></tr>");
e.node = d;
}
this.SetupContextMenu(e, d);
this.UpdateServerNode(e, d);
}
qlnabServerListView.prototype.DisplayServerList = function(g) {
var m = $("#" + this.GetContainerNodeId());
g = g.GetServers();
m.empty();
this.OnBeforeDisplayServerList(m, g);
if (g.length > 0) {
for (var x = this, p = [], z = 0, t = 0; t < g.length; t += this.props.group_cache_size) {
for (var o = [], l = 0; l < this.props.group_cache_size && t + l < g.length; l++) {
var u = g[t + l];
o[l] = u.public_id;
p[z] = o
}
z++;
}
var k = function() {
$(".contextMenu").destroyContextMenu().hide();
}
z = {
onHover: k,
onClick: k,
onDblClick: k,
target: this.props.target
};
o = this.props.max == 0 ? g.length : this.props.max;
for (t = 0; t < o; ++t) {
u = g[t];
z.cachedServerIds = p[parseInt(t / this.props.group_cache_size)];
quakelive.matchtip.BindMatchTooltip(u.node, u.public_id, z);
m.append(u.node)
}
m = m.find("tr");
QLNAB.trigger("ql_alt_bestPickHighlight");
QLNAB.trigger("ql_alt_gametypeIcon");
}
this.OnAfterDisplayServerList(m, g)
}
qlnabServerListView.prototype.SortServerList = function() {
var $qab = $("#ql_alt_browser");
var sortList;
try {
sortList = $qab.length ? $qab.get(0).config.sortList : [[5,1]];
}
catch(e) {
QLNAB.logError("SortServerList: " + e);
sortList = [[5,1]];
}
// Append the Players column to the sort list if it isn't already there.
var hasPlayers = false;
for (var i = 0, e = sortList.length; i < e; ++i) {
if (sortList[i][0] == 5) {
hasPlayers = true;
break;
}
}
if (!hasPlayers) {
sortList[sortList.length] = [5,1];
}
$qab.trigger("update");
$qab.trigger("sorton", [sortList]);
}
qlnabServerListView.prototype.MatchContextMenuHandler = function(e, g) {
var k = g.data("info"), module = quakelive.mod_home;
switch (e) {
case "copy":
qlPrompt({
input: true,
readonly: true,
alert: true,
title: "Link to this match",
body: "Use the URL below to link to this match directly.",
inputLabel: quakelive.siteConfig.baseUrl + "/r/join/" + k.public_id
});
break;
case "join":
g.dblclick();
break;
case "filter_map":
module.filter.filters.arena = k.map;
module.ReloadServerList();
break;
case "filter_location":
module.filter.filters.location = k.location_id;
module.ReloadServerList();
break;
case "filter_gametype":
module.filter.filters.game_type = k.game_type;
module.ReloadServerList();
break;
case "filter_none":
break;
default:
break;
}
}
qlnabServerListView.prototype.SetupContextMenu = function(d, e) {
var Q = {
menu: "serverContext",
inSpeed: 0,
outSpeed: 0
};
e.data("info", {public_id: d.public_id, map: d.map, location_id: d.location_id, game_type: d.game_type});
e.contextMenu(Q, this.MatchContextMenuHandler);
}
/**
* Set up the prototype for launch game parameters, which is used
* in ql_alt_parameters() and ql_alt_demo()
*/
function qlnabLaunchGameParams(a) {
a = $.extend({password: null, isTraining: false}, a);
var isStandard = quakelive.siteConfig.premiumStatus == "standard";
var canShow = quakelive.config("forceVideoAds", false)
|| isStandard && quakelive.userstatus == "ACTIVE" && !a.isTraining;
this.password = a.password;
this.noInputGrab = !quakelive.IsLinux() && canShow;
this.noAudio = canShow;
this.noAds = !isStandard;
this.shrinkViewport = isStandard;
this.hasFullscreen = quakelive.cvars.GetIntegerValue("r_fullscreen", 0) != 0;
this.browserMode = quakelive.cvars.GetIntegerValue("r_inbrowsermode", 12);
this.cmdStrings = [];
}
qlnabLaunchGameParams.prototype.Append = function(a) {
this.cmdStrings.push(a);
}
qlnabLaunchGameParams.prototype.Prepend = function(a) {
this.cmdStrings.shift(a);
}
qlnabLaunchGameParams.prototype.GetCommandLine = function() {
var a = quakelive.cvars.Get("model");
quakelive.cvars.Set("headmodel", a.value);
quakelive.cvars.Set("team_model", a.value);
quakelive.cvars.Set("team_headmodel", a.value);
quakelive.cfgUpdater.StoreConfig(quakelive.cfgUpdater.CFG_BIT_REP);
a = "";
if (this.noInputGrab) a += "+set in_nograb 1 ";
if (this.noAudio) a += "+set s_volume 0 ";
if (this.noAds) a += "+set g_advertDelay 0 ";
a += "+set r_fullscreen " + quakelive.cvars.GetIntegerValue("r_fullscreen", 0) + " ";
if (this.shrinkViewport) a += "+set r_inbrowsermode 0; +set vid_xpos 0; +set vid_ypos 0; ";
a += '+set gt_user "' + pluginx.username + '" ';
a += '+set gt_pass "' + pluginx.password + '" ';
a += '+set gt_realm "' + quakelive.siteConfig.realm + '" ';
if (typeof this.password == "string") a += '+set password "' + this.password + '" ';
a += this.cmdStrings.join(" ");
return a;
}
/**
* Override mod_home's ShowContent to change the view type
*/
quakelive.mod_home.ShowContent = function(v) {
if (quakelive.IsLoggedIn() && quakelive.userstatus != "ACTIVE") {
quakelive.Goto("welcome");
}
else {
quakelive.ShowContent(v);
if (quakelive.IsLoggedIn()) {
quakelive.MakeHomeChooser("home");
v = new qlnabServerListView;
v.SetDisplayProps({
target: "ql_alt_browser tbody"
});
$(".postlogin_nav ul li:last")
.after("<li id='ql_alt_parameters'>Parameters</li>" +
"<li id='ql_alt_filters'>Filters</li>" +
"<li id='ql_alt_demo'>Demo</li>" +
"<li id='ql_alt_refresh'>Refresh</li>");
// Set up custom game parameters
$("#ql_alt_parameters").click(function ql_alt_parameters() {
qlPrompt({
title: "Parameters",
body: "Enter custom parameters to start QL (e.g. +exec mycfg.cfg +devmap campgrounds).",
input: true,
inputLabel: localStorage.getItem("ql_alt_parameters"),
inputReadOnly: false,
alert: false,
ok: function() {
var params = $("#modal-input > input").val();
localStorage.setItem("ql_alt_parameters", params);
$("#prompt").jqmHide();
if (params) {
var k = new qlnabLaunchGameParams;
k.Append(params);
LaunchGame(k);
}
}
});
return false;
});
// Set up server filtering
$("#ql_alt_filters").click(function ql_alt_filters() {
var tmp_exclude = localStorage.getItem("ql_alt_filters_exclude"),
tmp_include = localStorage.getItem("ql_alt_filters_include"),
content = "Enter items to include or exclude separated by commas. Possible values:" +
"<ul><li><b>Location:</b> \"New York\" or \"Warsaw #2\"</li>" +
"<li><b>Map name:</b> \"Campgrounds\"</li>" +
"<li><b>Game mode:</b> \"ffa\"</li>" +
"<li><b>Server size:</b> \"/16\"</li>" +
"<li><b>Keywords:</b> \"qlnab.open\" (playable slots available), \"mods\" (modified rules)</li>" +
"<li><b>Combination:</b> \"New York + Campgrounds, Warsaw #2 + qlnab.open, ffa, /16\"</li></ul>" +
"<label for='ql_alt_filters_include'>Include:</label> <input type='text' id='ql_alt_filters_include'/> <br />" +
"<label for='ql_alt_filters_exclude'>Exclude:</label> <input type='text' id='ql_alt_filters_exclude' /> <br />";
qlPrompt({
customWidth: 600,
title: "Filters",
body: content,
input: true,
inputReadOnly: false,
alert: false,
ok: function() {
var filters_exclude = $("#ql_alt_filters_exclude").val(),
filters_include = $("#ql_alt_filters_include").val();
$("#prompt").jqmHide();
localStorage.setItem("ql_alt_filters_exclude", filters_exclude.toLowerCase());
localStorage.setItem("ql_alt_filters_include", filters_include.toLowerCase());
if (tmp_exclude != filters_exclude || tmp_include != filters_include) {
(filters_exclude || filters_include) ? $("#ql_alt_filters").addClass("filter_enabled") : $("#ql_alt_filters").removeClass("filter_enabled");
quakelive.serverManager.FlushCache();
quakelive.mod_home.ReloadServerList();
}
}
});
$("#prompt td").attr("align", "left");
$("#modal-input").css("display", "none");
$("#ql_alt_filters_exclude").val(tmp_exclude);
$("#ql_alt_filters_include").val(tmp_include);
return false;
}).toggleClass("filter_enabled", !!(localStorage.getItem("ql_alt_filters_exclude") || localStorage.getItem("ql_alt_filters_include")));
// Set up demo launching
$("#ql_alt_demo").click(function ql_alt_demo() {
qlPrompt({
title: "Demo",
body: "Enter a demo name to play. \"demo.cfg\" will automatically be executed, if it exists.",
input: true,
inputLabel: localStorage.getItem("ql_alt_demo"),
inputReadOnly: false,
alert: false,
ok: function() {
var demo = $("#modal-input > input").val();
$("#prompt").jqmHide();
localStorage.setItem("ql_alt_demo", demo);
if (demo) {
var k = new qlnabLaunchGameParams;
k.Append("+exec demo.cfg +demo \"" + demo + "\"");
LaunchGame(k);
}
}
});
return false;
});
// Set up list refreshing
$("#ql_alt_refresh").click(function() {
quakelive.serverManager.FlushCache();
quakelive.mod_home.ReloadServerList();
}).attr("title", "Shift + R");
$("#qlv_postlogin_matches").append("<table border='0' id='ql_alt_browser'>" +
"<tbody></tbody>" +
"<thead><tr>" +
"<th> </th>" +
"<th>Location</th>" +
"<th>Mode </th>" +
"<th>Map</th>" +
"<th>Skill</th>" +
"<th>Players</th>" +
"</tr></thead>" +
"</table>");
$("#ql_alt_browser").find("tbody")
// Dummy row for tablesorter to inspect
.append("<tr><td><img class='alocation_flag' src='a.gif' /></td>" +
"<td class='alocation_text'>a</td><td><img src='a.gif' class='agameicon' /></td>" +
"<td><span class='amapname'>campgrounds</span><span class='agamelabel'>a</span></td>" +
"<td><img src='#' class='agamerank' /><a href='#' class='link share_link_img'></a></td>" +
"<td class='aplayers'>0/128</td></tr>")
.end()
.tablesorter({debug: false,
sortList: JSON.parse(localStorage.getItem("ql_alt_browser_sort")) || [[5,1]],
headers: {4: {"sorter": "rank"}, 5: {"sorter": "players"}},
widgets: ["zebra","sortPersist"]})
.bind("sortEnd", function(){ $(".contextMenu").destroyContextMenu().hide(); quakelive.HideTooltip(); })
.find("tbody")
.empty();
quakelive.serverManager.listener = v;
quakelive.params.showgamesettings && quakelive.mod_prefs.ShowOverlay();
quakelive.mod_home.InitFilters();
quakelive.mod_home.ReloadServerList();
quakelive.mod_home.LoadQuickStats();
}
// User is not logged in... show the pretty pictures.
else {
var q = $("#home_carousel");
if (q.length > 0) {
for (var c = q.find("li").length, v = [], r = [], A = 0; A < c; ++A) {
v.push(".carousel_item_" + A);
r.push("<span class='carousel_nav_item carousel_item_" + A +
(A == 0 ? " carousel_nav_selitem" : "") + '">' + (A + 1) + "</span>");
}
r.push("<div class='cl'><div>");
$("#home_carousel_nav .inner").html(r.join(""));
q.jCarouselLite({
auto: 15E3,
speed: 500,
visible: 1,
circluar: true,
btnGo: v,
afterEnd: function(G, H) {
var E = (H - 1) % c;
$(".carousel_nav_selitem").removeClass("carousel_nav_selitem");
$(".carousel_item_" + E).addClass("carousel_nav_selitem")
},
beforeStart: function() {}
})
}
}
}
}
/**
* Override matchtip's BindMatchTooltip to use hoverIntent
*/
quakelive.matchtip.BindMatchTooltip = function(b, k, e) {
e = $.extend({
onHover: null,
onHoverOff: null,
onClick: null,
onDblClick: null,
targetContext: null
}, e);
this.target = e.target;
b.unbind("hover");
b.unbind("click");
b.unbind("dblclick");
e.node = b;
e.public_id = k;
var g = this;
b.click(function() {
e.onClick && e.onClick(b, k);
if (e.targetContext) g.activeContext = e.targetContext;
g.OnClickMatchTooltip(b, k, e);
return false
});
b.dblclick(function() {
e.onDblClick && e.onDblClick(b, k);
if (e.targetContext) g.activeContext = e.targetContext;
return g.OnDblClickMatchTooltip(b, k)
});
b.hoverIntent(function() {
if (!(g.pinnedServer && k == g.pinnedServer.public_id
&& (g.hoverNode == null || b == g.hoverNode))) {
e.onHover && e.onHover(b, k);
if (e.targetContext) g.activeContext = e.targetContext;
g.UnPinMatchTooltip();
g.OnHoverMatchTooltip(b, k, e.cachedServerIds)
}
}, function() {
if (!g.pinnedServer) {
e.onHoverOff && e.onHoverOff(b, k);
g.HideMatchTooltip(k);
g.activeContext = null
}
})
}
}); // end of call to contentEval
/**
* Override matchtip's PinMatchTooltip to avoid pinning if the tip is hidden
*/
contentEval(function() {
var oldPinMatchTooltip = quakelive.matchtip.PinMatchTooltip;
quakelive.matchtip.PinMatchTooltip = function() {
var tipOffset = $("#lgi_tip").offset();
if (!(tipOffset.left || tipOffset.top)) {
return;
}
oldPinMatchTooltip.apply(quakelive.matchtip, arguments);
}
}); // end of call to contentEval
/**
* Override matchtip's GetTooltipOffset and DisplayMatchPlayers to display
* match list tips to the right, friends list tips to the left
*/
contentEval(function() {
quakelive.matchtip.GetTooltipOffset = function(e, g, k) {
var d = e;
var l = {};
var n = {
left: $(document).scrollLeft(),
top: $(document).scrollTop(),
right: $(document).scrollLeft() + $("body").width(),
bottom: $(document).scrollTop() + $("body").height(),
width: $("body").width(),
height: $("body").height()
};
var u = 23, B = 2, x = 120, p = 28, z = 28;
var q = {
width: g.innerWidth(),
height: g.innerHeight()
};
e = quakelive.matchtip.GetDimensions(e);
e = {
left: e.left,
top: e.top,
right: e.left + e.innerWidth,
bottom: e.top + e.innerHeight,
width: e.innerWidth,
height: e.innerHeight
};
if (!/^match_\d+$/.test(d.attr("id"))) {
l.left = e.left - q.width - u + 4;
l.arrowDirection = "right";
l.arrowLeft = e.left - u;
g.orientation = "left";
g.addClass("lefto")
}
else {
l.left = e.left + e.width + u;
l.arrowDirection = "left";
l.arrowLeft = e.right + B;
g.orientation = "right";
g.removeClass("lefto")
}
l.arrowTop = e.top + e.height / 2 - x / 2;
if (l.arrowTop < n.top) {
g = n.top - l.arrowTop;
if (g > e.height / 2) g = e.height / 2;
l.arrowTop += g
}
l.top = l.arrowTop - (q.height - p - z) / 3;
if (l.top + q.height > n.bottom)
l.top -= l.top + q.height - n.bottom;
l.arrowLeft -= l.left;
l.arrowTop = l.arrowTop - l.top - p;
return l;
}
quakelive.matchtip.DisplayMatchPlayers = function(e) {
var d = {
Free: 0,
Red: 1,
Blue: 2,
Spec: 3
};
var g = $("#lgi_tip"), k = $("#lgi_cli");
// Avoid showing player list (lgi_cli) if the tip is off-screen
var gOff = g.offset();
if (!gOff.left && !gOff.top) return;
if (k.length == 0) {
k = $("<div id='lgi_cli'><div id='lgi_cli_top'><div class='lgi_headcol_1'>Player Name</div><div class='lgi_headcol_2'>Score</div></div><div id='lgi_cli_fill'><div id='lgi_cli_content'></div></div><div id='lgi_cli_bot'></div></div>");
k.appendTo("body")
}
var l = k.find("#lgi_cli_content");
l.empty();
if (e.players.length > 0) for (var n = 0; n < e.players.length; ++n) {
var q = e.players[n], u, B, x, p, z;
p = n % 2 == 0 ? "lgi_med lgi_cli_row_1" : "lgi_med lgi_cli_row_2";
if (q.friend) {
p += " lgi_is_friend";
}
else if (q.blocked) {
p += " lgi_is_blocked";
}
u = q.clan ? StripColors(q.clan) + " " : "";
B = StripColors(q.name);
u += B;
if (q.bot) {
u += " <i>(Bot)</i>";
p += " lgi_is_bot"
}
x = q.team == d.Spec ? "SPEC" : q.score;
if (q.model) {
var K = q.model.toLowerCase().split("/");
z = K[0] + "_";
z += K[1] ? K[1] : "default"
}
else {
z = "sarge_default";
}
if (q.team == d.Red) {
z = ChangeModelSkin(z, "red");
}
else if (q.team == d.Blue) {
z = ChangeModelSkin(z, "blue");
}
K = "<a href='javascript:;' onclick='quakelive.Goto(\"profile/summary/" + StripColors(q.name) + "\"); return false'>";
B = quakelive.mod_friends.IsBlocked(B) ? "<img src='" + quakelive.resource("/images/players/icon_gray_sm/" + z + ".png") + "' class='lgi_bordercolor_" + q.team + "' width='18' height='18' />" : "<img src='" + quakelive.resource("/images/players/icon_sm/" + z + ".png") + "' class='lgi_bordercolor_" + q.team + "' width='18' height='18' />";
u = u;
if (!q.bot) {
B = K + B + "</a>";
u = K + u + "</a>"
}
l.append("<div class='" + p + "'><div class='lgi_cli_col_1'>" + B + "<span>" + u + "</span><div class='cl'></div></div><div class='lgi_cli_col_2'>" + x + "</div></div>")
}
else {
l.append("<center>No Players in Game</center>");
}
e = quakelive.matchtip.GetDimensions(g);
e = {
left: e.left,
top: e.top,
right: e.left + e.innerWidth,
bottom: e.top + e.innerHeight,
width: e.innerWidth,
height: e.innerHeight
};
l = {
width: 236
};
if (g.hasClass("lefto")) {
l.left = e.left - k.width() + 8;
}
else {
l.left = e.right
}
l.top = e.top;
k.css("left", l.left + "px");
k.css("top", l.top + "px");
k.show();
}
/**
* Create keyboard shortcuts
*/
$(document).keypress(function(e) {
if (e.which == 82 && quakelive.activeModule.TITLE == "Home") {
// shift+R on the home page
quakelive.serverManager.FlushCache();
quakelive.mod_home.ReloadServerList();
}
});
}); // end of call to contentEval