Last active
June 11, 2022 02:59
-
-
Save nabbynz/b2c81a69808ee2f9d847c3b1bffdace3 to your computer and use it in GitHub Desktop.
Maps Rater
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Maps Rater | |
// @description Rate maps from the /maps page. Ratings get updated on the server when you next play that map. Shows Win % for each map. | |
// @version 0.0.12 | |
// @include https://tagpro.koalabeast.com/maps | |
// @include https://tagpro.koalabeast.com/game | |
// @include https://tagpro.koalabeast.com/game?* | |
// @updateURL https://gist.github.com/nabbynz/b2c81a69808ee2f9d847c3b1bffdace3/raw/Maps_Rater.user.js | |
// @downloadURL https://gist.github.com/nabbynz/b2c81a69808ee2f9d847c3b1bffdace3/raw/Maps_Rater.user.js | |
// @grant GM_setValue | |
// @grant GM_getValue | |
// @grant GM_deleteValue | |
// @grant GM_addStyle | |
// @author nabby | |
// ==/UserScript== | |
console.log('START: ' + GM_info.script.name + ' (v' + GM_info.script.version + ' by ' + GM_info.script.author + ')'); | |
tagpro.ready(function() { | |
//-------------------------- | |
// Options... | |
//-------------------------- | |
const showMyLastPlayed = true; //Show when each map was last played | |
const showMyPlays = true; //Not entirely sure (How many times each map has been played by me? This data comes from the server) | |
const showMyWins = true; //Win % on each map (since this script was installed) | |
const showTimeline = true; //Win/Loss timeline for the maps currently in rotation (since this script was installed) | |
const timelineGames = 50; //# of games to show on the timeline | |
const minimumGameTime = 30; //Minimum # of seconds for the game to count for timeline save | |
const showSaveAttempt = true; //Shows a "Save Attempt" text in the top-right corner in game | |
//-------------------------- | |
let mapRatings = GM_getValue('mapRatings', {}); | |
let joinTime; | |
if (location.pathname === '/game') { | |
let mapName, mapAuthor, tpRating = null, tpPlays = null; | |
let isSaveAttempt = false; | |
tagpro.socket.on('map', function(data) { | |
if (!data.info.name.includes('Mirrored')) { | |
mapName = data.info.name.trim(); | |
mapAuthor = data.info.author.trim(); | |
} | |
}); | |
tagpro.socket.on('mapRating', function(data) { | |
tpRating = data.rating; | |
tpPlays = data.plays; | |
}); | |
tagpro.socket.on('time', function(data) { | |
if (data.state === 3) { //before the actual start | |
joinTime = Date.now(); | |
} else if (data.state === 1) { //game has started | |
if (!joinTime) joinTime = Date.now(); //joined mid-game | |
} else if (data.state === 5) { //overtime | |
if (!joinTime) joinTime = Date.now(); //joined in overtime | |
} | |
}); | |
tagpro.socket.on('spectator', function(spectator) { | |
if (!spectator.type) joinTime = Date.now(); //joined from spec | |
}); | |
tagpro.socket.on('end', function() { | |
let endTime = Date.now(); //actual end of game time | |
let myTimePlayed = (endTime - (joinTime || endTime)) / 1000; //how long we played for | |
if (mapName && !tagpro.spectator && (myTimePlayed > minimumGameTime) && (!tagpro.group.socket || tagpro.group.socket && !tagpro.group.socket.connected)) { | |
if (!mapRatings.hasOwnProperty(mapName)) { | |
mapRatings[mapName] = { mapAuthor:'Unknown', rating:null, plays:null, lastRated:null, lastPlayed:null, lastResults:[], wins:0, losses:0 }; | |
} | |
if (tpRating !== null && tpPlays !== null) { // these values were only received if we're logged in (or possibly if the map is new and doesn't appear on the /maps page yet???). tpRating is `undefined` if our rating is expired. | |
if (+tpRating !== +mapRatings[mapName].rating) { | |
if (mapRatings[mapName].rating !== null && (+mapRatings[mapName].rating === -1 || +mapRatings[mapName].rating === 0 || +mapRatings[mapName].rating === 1)) { //need to update our rating to the server (note: +null === 0, +undefined === NaN) | |
tagpro.socket.emit('mapRating', +mapRatings[mapName].rating); //don't rely on just changing the val() - it won't always work as expected :( | |
$("select#mapRating").val(mapRatings[mapName].rating); | |
mapRatings[mapName].serverRating = mapRatings[mapName].rating; | |
mapRatings[mapName].lastRated = Date.now(); | |
} else if (+tpRating === -1 || +tpRating === 0 || +tpRating === 1) { //save the rating from server so we can use it on the maps page | |
mapRatings[mapName].rating = tpRating; | |
mapRatings[mapName].serverRating = tpRating; | |
} | |
} else { | |
if (+tpRating === -1 || +tpRating === 0 || +tpRating === 1) { | |
mapRatings[mapName].serverRating = tpRating; | |
} | |
} | |
mapRatings[mapName].plays = tpPlays; | |
} | |
mapRatings[mapName].lastPlayed = Date.now(); | |
if (!mapRatings[mapName].hasOwnProperty('lastResults')) mapRatings[mapName].lastResults = []; | |
if (!mapRatings[mapName].hasOwnProperty('mapAuthor')) mapRatings[mapName].mapAuthor = mapAuthor || 'Unknown'; | |
if (tagpro.winner === 'red' && tagpro.players[tagpro.playerId].team === 1 || tagpro.winner === 'blue' && tagpro.players[tagpro.playerId].team === 2) { | |
mapRatings[mapName].wins++; | |
mapRatings[mapName].lastResults.push({ timePlayed:Date.now(), result:1, team:tagpro.players[tagpro.playerId].team, scoreRed:tagpro.score.r, scoreBlue:tagpro.score.b, saveAttempt:isSaveAttempt }); | |
} else { | |
if (!isSaveAttempt) mapRatings[mapName].losses++; | |
mapRatings[mapName].lastResults.push({ timePlayed:Date.now(), result:0, team:tagpro.players[tagpro.playerId].team, scoreRed:tagpro.score.r, scoreBlue:tagpro.score.b, saveAttempt:isSaveAttempt }); | |
} | |
while (mapRatings[mapName].lastResults.length > 5) { | |
mapRatings[mapName].lastResults.shift(); | |
} | |
GM_setValue('mapRatings', mapRatings); | |
} | |
}); | |
$("select#mapRating").change(function() { | |
if (mapName) { | |
mapRatings[mapName].rating = $(this).val(); | |
mapRatings[mapName].serverRating = $(this).val(); | |
mapRatings[mapName].lastRated = Date.now(); | |
GM_setValue('mapRatings', mapRatings); | |
} | |
}); | |
//this listens to the chat for the "This is a save attempt!" message upon joining a game (for 1.5s) then removes itself. Or if we're speccing we keep listening until we join. | |
let clearable_removeChatListener; | |
function handleChat(data) { | |
if (data.from === null && data.message.startsWith('This is a save attempt!')) { | |
isSaveAttempt = true; | |
removeChatListener(); | |
if (showSaveAttempt && !tagpro.ui.sprites.saveAttempt) { | |
tagpro.ui.sprites.saveAttempt = tagpro.renderer.prettyText("Save Attempt"); | |
tagpro.ui.sprites.saveAttempt.tint = 0xFF5500; | |
tagpro.ui.sprites.saveAttempt.anchor.x = 1; | |
tagpro.ui.sprites.saveAttempt.x = tagpro.renderer.vpWidth - 6; | |
tagpro.ui.sprites.saveAttempt.y = 10; | |
//tagpro.ui.sprites.saveAttempt.alpha = 0.7; | |
tagpro.renderer.layers.ui.addChild(tagpro.ui.sprites.saveAttempt); | |
} | |
} | |
} | |
function removeChatListener(delay=100) { | |
setTimeout(function() { | |
if (!tagpro.spectator) { | |
tagpro.rawSocket.removeListener('chat', handleChat); | |
clearInterval( clearable_removeChatListener ); | |
} | |
}, delay); | |
} | |
tagpro.rawSocket.on('chat', handleChat); | |
clearable_removeChatListener = setInterval(function() { | |
if (!tagpro.spectator) { | |
removeChatListener(1500); | |
} | |
}, 2000); | |
} else if (location.pathname === '/maps') { | |
const serverRatingUnknown = '<span class="MR_RatingText" style="color:#777; opacity:1;" title="Waiting for vote">●</span>'; | |
const serverRatingLike = '<span class="MR_RatingText" style="color:#7bd117; opacity:1;" title="Like">●</span>'; //8bc34a | |
const serverRatingNeutral = '<span class="MR_RatingText" style="color:#0e8ae0; opacity:1;" title="Neutral">●</span>'; // | |
const serverRatingDislike = '<span class="MR_RatingText" style="color:#c73939; opacity:1;" title="Dislike">●</span>'; //c34a4a | |
const serverRatingsMatch = '<span class="MR_RatingMatch" style="color:#0f0; opacity:0.5;" title="This rating matches the rating on the server">✔</span>'; //tick:✔ thumbsup:👍 | |
const serverRatingsDontMatch = '<span class="MR_RatingMatch" style="color:#f90; opacity:0.75; font-size:13px;" title="This rating will be updated the next time the map is played">↺</span>'; //cross:✗ hourglass:⏳ watch:⏱ | |
$('table').hide(); | |
$('.map-detail').hide(0); | |
$.getJSON('/maps.json', function(mapsJSON) { | |
//console.log('mapsJSON:', mapsJSON); | |
//bind events... | |
$('.js-map-prev, .js-map-next').on('click', function() { | |
let currentTab = $('ul.tab-list').find('li.active').data('target'); | |
let id = $(currentTab).find('.dot.active').attr('id'); | |
$(currentTab).find('table tr').css('background', ''); | |
$(currentTab).find('a#'+id).parent('td').parent('tr').css('background', '#111'); | |
updateMapDetails(); | |
}); | |
$('table tbody a').on('click', function(e) { | |
$(this).parents('table').find('tr').css('background', ''); | |
$(this).parent('p').parent('td').parent('tr').css('background', '#111'); | |
//updateMapDetails(); //this gets fired by the 'circle' click listener | |
}); | |
$('circle').on('click', function(e) { | |
let currentTab = $('ul.tab-list').find('li.active').data('target'); | |
let id = $(currentTab).find('.dot.active').attr('id'); | |
$(currentTab).find('table tr').css('background', ''); | |
$(currentTab).find('a#'+id).parent('td').parent('tr').css('background', '#111'); | |
updateMapDetails(); | |
}); | |
$('circle').on('mouseenter', function() { | |
let currentTab = $('ul.tab-list').find('li.active').data('target'); | |
let isActive = $(this).attr('class').includes('active'); //the version of jQuery on the server doesn't support hasClass on svg elements so we're doing it this way | |
if (!isActive) { | |
$(this).attr('r', 7); | |
$(currentTab).find('a#'+this.id).parent('td').parent('tr').css('background', '#131'); | |
} | |
}).on('mouseleave', function() { | |
let currentTab = $('ul.tab-list').find('li.active').data('target'); | |
let isActive = $(this).attr('class').includes('active'); | |
if (!isActive) { | |
$(this).attr('r', 5); | |
$(currentTab).find('a#'+this.id).parent('td').parent('tr').css('background', ''); | |
} | |
}); | |
$('table tbody tr').on('mouseenter', function() { | |
let currentTab = $('ul.tab-list').find('li.active').data('target'); | |
let id = $(this).find('a').attr('id'); | |
$(currentTab).find('circle#'+id).attr('r', 7); | |
}).on('mouseleave', function() { | |
let currentTab = $('ul.tab-list').find('li.active').data('target'); | |
let id = $(this).find('a').attr('id'); | |
let $circle = $(currentTab).find('circle#'+id); | |
let isActive = $circle.attr('class').includes('active'); | |
if (isActive) $circle.attr('r', 10); | |
else $circle.attr('r', 5); | |
}); | |
$('table').on('click', 'th', function(e, preventReverse) { | |
let order = GM_getValue('order', true); | |
let sortby = GM_getValue('sortby', 2); | |
let $table = $(this).parents('table'); | |
GM_setValue('sortby', $(this).index()); | |
$table.find('th').css('text-decoration', 'none'); | |
$(this).css('text-decoration', 'underline'); | |
let tbody = $table.find('tbody'); | |
let rows = tbody.find('tr').toArray().sort(comparer($(this).index())); | |
if ( (sortby === $(this).index() && order && preventReverse !== true) || (sortby !== $(this).index() && !order && preventReverse !== true) || (preventReverse && !order) ) rows = rows.reverse(); | |
if (sortby === $(this).index() && preventReverse !== true) GM_setValue('order', !order); | |
for (let i=0; i<rows.length; i++) { tbody.append(rows[i]); } | |
}); | |
//begin table enhancement... | |
$('table').each(function() { | |
let $tbody = $(this).find('tbody'); | |
let $rows = $tbody.find('tr'); | |
if (showMyWins) $(this).find('thead tr').find('th:eq(6)').after('<th style="text-align:center; background:#6f9c34;">Win %</th>'); | |
if (showMyLastPlayed) $(this).find('thead tr').find('th:eq(6)').after('<th style="text-align:center; background:#6f9c34;">Last</th>'); | |
if (showMyPlays) $(this).find('thead tr').find('th:eq(6)').after('<th style="text-align:center; background:#6f9c34;">#Plays</th>'); | |
$(this).find('thead tr').find('th:eq(6)').after('<th style="text-align:center; background:#6f9c34;">My Vote</th>'); | |
$rows.each(function(index) { | |
let mapName = $(this).find('.likes').prev().prev().text().trim(); | |
let isMirrored = mapName.endsWith(' Mirrored'); | |
if (isMirrored) { //we can't rate mirrored maps and they just take up space | |
$(this).remove(); | |
} else { | |
let likes = parseInt( $(this).find('.likes').text().replace(/,/g, ''), 10 ); | |
let indifferents = parseInt( $(this).find('.indifferents').text().replace(/,/g, ''), 10 ); | |
let dislikes = parseInt( $(this).find('.dislikes').text().replace(/,/g, ''), 10 ); | |
let plays = parseInt( $(this).find('.dislikes').next('td').text().replace(/,/g, ''), 10 ); | |
let totalUsers = likes + indifferents + dislikes; | |
let likes_pc = likes / (totalUsers || 1) * 100; | |
let indifferents_pc = indifferents / (totalUsers || 1) * 100; | |
let dislikes_pc = dislikes / (totalUsers || 1) * 100; | |
let score_pc = likes / ((likes + dislikes) || 1) * 100; | |
if (!mapRatings.hasOwnProperty(mapName)) { | |
mapRatings[mapName] = { mapAuthor:'Unknown', rating:null, plays:null, lastRated:null, lastPlayed:null, lastResults:[], wins:0, losses:0, serverRating:null }; | |
} | |
$(this).find('a.map-list-entry').parent('td').data('sortby', mapName); | |
$(this).find('td.likes').prev('td').html((score_pc).toFixed(1)+'%').data('sortby', score_pc).attr('title', 'Score = Likes / (Likes + Dislikes)'); | |
$(this).find('td.likes').html(likes_pc.toFixed(1)+'%').data('sortby', likes_pc).attr('title', likes); | |
$(this).find('td.indifferents').html(indifferents_pc.toFixed(1)+'%').data('sortby', indifferents_pc).attr('title', indifferents); | |
$(this).find('td.dislikes').html(dislikes_pc.toFixed(1)+'%').data('sortby', dislikes_pc).attr('title', dislikes); | |
$(this).find('td.dislikes').next('td').data('sortby', plays); | |
$(this).find('td.dislikes').next('td').next('td').data('sortby', totalUsers); | |
$(this).find('span.ratio-bar').parent('td').data('sortby', likes_pc); | |
$(this).find('a.map-list-entry').before('<p class="MR_MapName" title="' + mapName + (mapRatings[mapName].mapAuthor ? ' by ' + mapRatings[mapName].mapAuthor : '') + '"></p>'); | |
$(this).find('.MR_MapName').append( $(this).find('a.map-list-entry') ); | |
let lastPlayed = mapRatings[mapName].lastPlayed ? new Date(mapRatings[mapName].lastPlayed).toDateString() + ' @ ' + new Date(mapRatings[mapName].lastPlayed).toLocaleTimeString() : ''; | |
//check our rating matches the rating from the server... | |
let serverRatingText = serverRatingUnknown; | |
let serverRatingMatchText = '<span class="MR_RatingMatch" style="font-size:13px;" title="Waiting for rating from server (next play)">?</span>'; | |
if (mapRatings[mapName].hasOwnProperty('rating') && mapRatings[mapName].hasOwnProperty('serverRating')) { | |
if (mapRatings[mapName].rating !== null) { | |
if (+mapRatings[mapName].rating === 1) serverRatingText = serverRatingLike; | |
else if (+mapRatings[mapName].rating === 0) serverRatingText = serverRatingNeutral; | |
else if (+mapRatings[mapName].rating === -1) serverRatingText = serverRatingDislike; | |
else serverRatingText = serverRatingUnknown; | |
} | |
if (mapRatings[mapName].rating !== null && mapRatings[mapName].serverRating !== null && (+mapRatings[mapName].serverRating === -1 || +mapRatings[mapName].serverRating === 0 || +mapRatings[mapName].serverRating === 1)) { | |
if (+mapRatings[mapName].serverRating === +mapRatings[mapName].rating) serverRatingMatchText = serverRatingsMatch; | |
else serverRatingMatchText = serverRatingsDontMatch; | |
} | |
} | |
//add new columns... | |
if (showMyWins) { | |
let sortby = mapRatings[mapName].wins > 0 ? mapRatings[mapName].wins / ((mapRatings[mapName].wins + mapRatings[mapName].losses) || 1) : mapRatings[mapName].losses > 0 ? -mapRatings[mapName].losses : -999999; | |
$(this).find('td:eq(6)').after('<td class="lastResult" style="text-align:center;" data-sortby="' + sortby + '" data-sortby2="' + mapRatings[mapName].wins + '"><div title="' + (mapRatings[mapName].wins || 0) + ' Wins, ' + (mapRatings[mapName].losses || 0) + ' Losses">' + (mapRatings[mapName].wins >= 0 ? (mapRatings[mapName].wins / ((mapRatings[mapName].wins + mapRatings[mapName].losses) || 1) * 100).toFixed(1) + '%' : '-') + '</div></td>'); | |
addLastResults(this, mapName); | |
} | |
if (showMyLastPlayed) $(this).find('td:eq(6)').after('<td class="lastPlayed" style="text-align:center; font-size:10px; width:90px;" data-sortby="' + (mapRatings[mapName].lastPlayed ? mapRatings[mapName].lastPlayed : -1) + '" title="' + lastPlayed + '">' + (mapRatings[mapName].lastPlayed ? dayjs(mapRatings[mapName].lastPlayed).from() : '-') + '</td>'); | |
if (showMyPlays) $(this).find('td:eq(6)').after('<td style="text-align:center;"data-sortby="' + ($.isNumeric(mapRatings[mapName].plays) ? mapRatings[mapName].plays : 0) + '">' + ($.isNumeric(mapRatings[mapName].plays) ? mapRatings[mapName].plays + 1 : '-') + '</td>'); | |
$(this).find('td:eq(6)').after('<td style="text-align:center;" data-sortby="' + ($.isNumeric(mapRatings[mapName].rating) ? mapRatings[mapName].rating : 999) + '"><div class="MR_RatingContainer">' + serverRatingText + '<select class="MR_Rating" value="" data-mapname="' + mapName + '" data-serverrating="' + mapRatings[mapName].serverRating + '"><option value="1">Like</option><option value="0">Neutral</option><option value="-1">Dislike</option></select>' + serverRatingMatchText + '</div></td>'); | |
$('.MR_Rating[data-mapname="' + mapName + '"]').val(mapRatings[mapName].rating); | |
if (totalUsers === 0) { //new map... | |
$(this).find('span.likes').attr('title', 'New Map!').css({ 'width':'100%', 'background':'rebeccapurple', 'text-align':'center' }).text('Waiting for 100 Votes'); | |
} else { //adjust the widths of the ratio bar with precision... | |
$(this).find('span.likes').width(likes_pc + '%'); | |
$(this).find('span.indifferents').width(indifferents_pc + '%'); | |
$(this).find('span.dislikes').width(dislikes_pc + '%'); | |
} | |
} | |
}); | |
//sort the table by last saved... | |
$(this).find('th:eq(' + GM_getValue('sortby', 1) + ')').trigger('click', true); | |
GM_setValue('mapRatings', mapRatings); | |
}); | |
updateTotals(); | |
$('.MR_Rating').on('change', function() { | |
mapRatings[this.dataset.mapname].rating = this.value; | |
mapRatings[this.dataset.mapname].lastRated = Date.now(); | |
GM_setValue('mapRatings', mapRatings); | |
if ($(this).data('serverrating') !== null && +this.value === +$(this).data('serverrating')) { | |
$(this).next('.MR_RatingMatch').remove(); | |
$(this).after(serverRatingsMatch); | |
} else { | |
$(this).next('.MR_RatingMatch').remove(); | |
$(this).after(serverRatingsDontMatch); | |
} | |
if (+this.value === -1) { | |
$(this).prev('.MR_RatingText').remove(); | |
$(this).before(serverRatingDislike); | |
} else if (+this.value === 0) { | |
$(this).prev('.MR_RatingText').remove(); | |
$(this).before(serverRatingNeutral); | |
} else if (+this.value === 1) { | |
$(this).prev('.MR_RatingText').remove(); | |
$(this).before(serverRatingLike); | |
} else { | |
$(this).prev('.MR_RatingText').remove(); | |
$(this).before(serverRatingUnknown); | |
} | |
updateTotals(); | |
}); | |
//select the first row map as default... | |
$('#rotation').find('table.table tr:eq(1)').find('td:eq(0) a')[0].click(); | |
updateMapDetails('#retired'); | |
//make it a bit smaller so it fits better... | |
GM_addStyle('.table { width:90%; font-size:14px; margin:0 auto; }'); | |
GM_addStyle('.table th { padding:2px 0.5em; user-select:none; cursor:pointer; }'); | |
GM_addStyle('.table td { padding:2px 0.5em; text-shadow:1px 1px 1px #111; }'); | |
GM_addStyle('.maps .ratio-bar { display:flex; border:1px solid #585858; height:14px; width:160px; font-size:10px; margin:2px 0 -1px; }'); | |
GM_addStyle('table td:nth-child(1) { width: 140px; } '); | |
//row highlight on hover... | |
GM_addStyle('.table.table-stripped tbody tr:hover, .table.table-stripped tbody tr:nth-child(2n):hover { background:#131; }'); | |
GM_addStyle('.MR_MapName { margin:0; max-width:140px !important; white-space:nowrap; overflow:hidden; }'); | |
GM_addStyle('.MR_RatingContainer { display:flex; width:100px; }'); | |
GM_addStyle('.MR_Rating { color:black; width:85px; }'); | |
GM_addStyle('.MR_Totals { margin:5px; font-size:14px; font-weight:bold; text-align:center; }'); | |
GM_addStyle('.MR_Totals_Label { margin:0 10px; font-size:12px; font-weight:normal; }'); | |
GM_addStyle('.MR_RatingText { font-size:14px; width:13px; text-align:center; cursor:default; }'); | |
GM_addStyle('.MR_RatingMatch { color:#d2d2d2; font-size:12px; width:15px; text-align:center; cursor:default; }'); | |
GM_addStyle('.MR_Timeline { margin:0 0 5px 0; font-size:14px; font-weight:bold; text-align:center; }'); | |
GM_addStyle('.MR_TimelineWin { display:inline-block; width:6px; height:6px; margin:0 1px 0 0; opacity:0.8; border:3px outset #0f0; }'); | |
GM_addStyle('.MR_TimelineLoss { display:inline-block; width:6px; height:6px; margin:0 1px 0 0; opacity:0.8; border:3px outset #f00; }'); | |
GM_addStyle('.MR_TimelineSSA { display:inline-block; width:6px; height:6px; margin:0 1px 0 0; opacity:0.8; background:#222; border:2px outset #aaff00; }'); | |
GM_addStyle('.MR_TimelineUSA { display:inline-block; width:6px; height:6px; margin:0 1px 0 0; opacity:0.8; background:#222; border:2px outset #8c8c8c; }'); | |
GM_addStyle('.MR_TimelineNewDay { display:inline-block; position:relative; width:1px; height:12px; opacity:0.8; background:none; border-right:1px solid #bbb; margin:0px 2px -3px 1px; }'); | |
GM_addStyle('.MR_LastGameWin { width:6px; height:6px; margin-left:1px; opacity:0.8; border:3px outset #0f0; }'); | |
GM_addStyle('.MR_LastGameLoss { width:6px; height:6px; margin-left:1px; opacity:0.8; border:3px outset #f00; }'); | |
GM_addStyle('.MR_LastGameSSA { width:6px; height:6px; margin-left:1px; opacity:0.8; background:#222; border:2px outset #aaff00; }'); | |
GM_addStyle('.MR_LastGameUSA { width:6px; height:6px; margin-left:1px; background:#222; border:2px outset #8c8c8c; }'); | |
GM_addStyle('.MR_Divider { display:inline-block; padding:0 5px; font-size:14px; color:#666; }'); | |
$('table').fadeIn(400); | |
$('.map-detail').fadeIn(400); | |
}); //getJSON | |
//show a large map preview... | |
$('body').on('click', '.preview', function() { | |
const src = $(this).attr('src').replace('-small', ''); | |
let img = new Image(); | |
$('body').append('<div id="MR_Large_Preview_Loading" style="position:fixed; top:50%; left:50%; width:240px; height:120px; line-height:120px; transform:translate(-50%, -50%); text-align:center; color:dodgerblue; background:#fff; border:1px solid red; border-radius:6px; box-shadow:0px 0px 25px 10px black; z-index:999;">Loading Image...</div>'); | |
img.onload = function() { | |
$('#MR_Large_Preview').remove(); | |
$('body').append('<div id="MR_Large_Preview" style="position:fixed; padding:10px; top:50%; left:50%; transform:translate(-50%, -50%); background:#111; border:1px solid red; border-radius:6px; box-shadow:0px 0px 25px 10px black; z-index:999;"></div>'); | |
$('#MR_Large_Preview_Loading').remove(); | |
$('#MR_Large_Preview').append(img).fadeIn(400); | |
}; | |
img.onerror = function() { | |
$('#MR_Large_Preview_Loading').text('Could not load image.'); | |
setTimeout(function() { | |
$('#MR_Large_Preview_Loading').remove(); | |
}, 1800); | |
}; | |
img.style.maxWidth = '90vw'; | |
img.style.maxHeight = '90vh'; | |
img.crossOrigin = 'anonymous'; | |
img.src = src; | |
}); | |
$('body').on('click', '#MR_Large_Preview', function() { | |
$(this).fadeOut(50, function() { | |
$(this).remove(); | |
}); | |
}); | |
function updateTotals() { | |
$('.MR_Totals').remove(); | |
$('.MR_Timeline').remove(); | |
$('table').each(function() { | |
let $tbody = $(this).find('tbody'); | |
let $rows = $tbody.find('tr'); | |
let rotationType = capitalize($(this).closest('div.tab-pane').attr('id')); | |
let totals = { likes:0, neutrals:0, dislikes:0, plays:0, wins:0, losses:0 }; | |
let timeline = []; | |
$rows.each(function(index) { | |
let mapName = $(this).find('.likes').prev().prev().text().trim(); | |
if (!mapName) return false; | |
if (mapRatings[mapName].rating !== null && (+mapRatings[mapName].rating === 1 || +mapRatings[mapName].rating === -1 || +mapRatings[mapName].rating === 0)) { | |
let rating = +mapRatings[mapName].rating === 1 ? 'likes' : +mapRatings[mapName].rating === -1 ? 'dislikes' : 'neutrals'; | |
totals[rating]++; | |
} | |
if (mapRatings[mapName].plays > 0) totals.plays += +mapRatings[mapName].plays; | |
if (mapRatings[mapName].wins > 0) totals.wins += +mapRatings[mapName].wins; | |
if (mapRatings[mapName].losses > 0) totals.losses += +mapRatings[mapName].losses; | |
if (showTimeline) { | |
if (mapRatings.hasOwnProperty(mapName) && mapRatings[mapName].lastResults) { | |
for (let i=0; i<mapRatings[mapName].lastResults.length; i++) { | |
if (mapRatings[mapName].lastResults[i].timePlayed) { //data format script version >= 0.0.7 | |
timeline.push({ mapName:mapName, timePlayed:mapRatings[mapName].lastResults[i].timePlayed, result:mapRatings[mapName].lastResults[i].result, team:mapRatings[mapName].lastResults[i].team, scoreRed:mapRatings[mapName].lastResults[i].scoreRed, scoreBlue:mapRatings[mapName].lastResults[i].scoreBlue, saveAttempt:mapRatings[mapName].lastResults[i].saveAttempt }); | |
} else if (mapRatings[mapName].lastPlayed && i === mapRatings[mapName].lastResults.length - 1) { //old data format (can only use the last game) | |
timeline.push( { mapName:mapName, timePlayed:mapRatings[mapName].lastPlayed, result:mapRatings[mapName].lastResults[i] } ); | |
} | |
} | |
} | |
} | |
}); | |
let score = ((totals.likes / (totals.likes + totals.dislikes) || 1) * 100).toFixed(2); | |
let total = (totals.likes + totals.dislikes + totals.neutrals) || 1; | |
$(this).after('<div class="MR_Totals">' + | |
'<span class="MR_Totals_Label"># Maps: ' + $rows.length + '</span>' + | |
'<span class="MR_Totals_Label" title="Score = Likes / (Likes + Dislikes)">My ' + rotationType + ' Score: ' + score + '%</span>' + | |
'<span class="MR_Totals_Label">' + serverRatingLike + 'Likes: ' + totals.likes + ' (' + (totals.likes / total * 100).toFixed(1) + '%)</span>' + | |
'<span class="MR_Totals_Label">' + serverRatingNeutral + 'Neutrals: ' + totals.neutrals + ' (' + (totals.neutrals / total * 100).toFixed(1) + '%)</span>' + | |
'<span class="MR_Totals_Label">' + serverRatingDislike + 'Dislikes: ' + totals.dislikes + ' (' + (totals.dislikes / total * 100).toFixed(1) + '%)</span>' + | |
(showMyPlays ? '<span class="MR_Totals_Label">Plays: ' + totals.plays + '</span>' : '') + | |
(showMyWins ? '<span class="MR_Totals_Label" title="' + totals.wins + ' Wins, ' + totals.losses + ' Losses">Win: ' + (totals.wins / ((totals.wins + totals.losses) || 1) * 100).toFixed(1) + '%</span>' : '') + | |
'<div style="font-weight:normal; font-style:italic; font-size:12px; margin:3px 0;">(Note: Map Ratings get updated to the server when you next play that map)</div></div>'); | |
if (showTimeline && timeline.length) { | |
timeline.sort(function(a, b) { | |
return a.timePlayed - b.timePlayed; | |
}); | |
const timelineGamesToShow = Math.min(timeline.length, timelineGames); | |
let games = ''; | |
let wins = 0; | |
let capsFor = 0; | |
let capsAgainst = 0; | |
let redTotals = { count:0, wins:0, losses:0 }; | |
let blueTotals = { count:0, wins:0, losses:0 }; | |
let first = Math.max(0, timeline.length - timelineGamesToShow); | |
let lastDatePlayed = new Date(timeline[first].timePlayed).toDateString(); | |
let lastDatePlayedPosition = first; // :) | |
for (let i=first; i<timeline.length; i++) { | |
if (timeline[i].timePlayed > 0) { | |
let thisGame = timeline[i]; | |
let timePlayed = new Date(timeline[i].timePlayed); | |
let timePlayedDate = timePlayed.toDateString() | |
if (i > first && lastDatePlayed !== timePlayedDate) { | |
lastDatePlayed = timePlayedDate; | |
lastDatePlayedPosition = i; | |
games += '<span class="MR_TimelineNewDay" title="A new day begins..."></span>'; | |
} | |
games += '<span class="' + (thisGame.result === 1 ? (thisGame.saveAttempt ? 'MR_TimelineSSA' : 'MR_TimelineWin') : (thisGame.saveAttempt ? 'MR_TimelineUSA' : 'MR_TimelineLoss')) + '" title="Map: ' + thisGame.mapName + (thisGame.mapAuthor ? '\nAuthor: ' + thisGame.mapAuthor : '') + '\nPlayed: ' + timePlayed.toDateString() + ', ' + timePlayed.toLocaleTimeString() + '\nResult: ' + (thisGame.team === 1 ? 'Red ' : 'Blue ') + (thisGame.team === 1 && thisGame.scoreRed > thisGame.scoreBlue || thisGame.team === 2 && thisGame.scoreBlue > thisGame.scoreRed ? 'Won ' : 'Lost ') + thisGame.scoreRed + ':' + thisGame.scoreBlue + (thisGame.saveAttempt ? ' (Save Attempt)' : '') + '"></span>'; | |
if (timeline[i].result === 1) wins++; | |
if (timeline[i].scoreRed >= 0) { | |
capsFor += timeline[i].team === 1 ? timeline[i].scoreRed : timeline[i].scoreBlue; | |
capsAgainst += timeline[i].team === 1 ? timeline[i].scoreBlue : timeline[i].scoreRed; | |
if (timeline[i].team === 1) { | |
redTotals.count++; | |
if (timeline[i].result === 1) redTotals.wins++; | |
else if (!timeline[i].saveAttempt) redTotals.losses++; | |
} else { | |
blueTotals.count++; | |
if (timeline[i].result === 1) blueTotals.wins++; | |
else if (!timeline[i].saveAttempt) blueTotals.losses++; | |
} | |
} | |
} | |
} | |
let todayWins = 0, todayGames = 0; | |
let todayLabel = 'Last Day: '; | |
for (let i=lastDatePlayedPosition; i<timeline.length; i++) { | |
if (timeline[i].timePlayed > 0) { | |
if (timeline[i].result === 1) todayWins++; | |
if (timeline[i].result === 1 || (timeline[i].result === 0 && !timeline[i].saveAttempt)) todayGames++; | |
} | |
} | |
let lastDay = new Date(timeline[lastDatePlayedPosition].timePlayed); | |
let today = new Date(); | |
let divider = '<span class="MR_Divider">|</span>'; | |
if (lastDay.toDateString() === today.toDateString()) todayLabel = 'Today: '; | |
else if (lastDay.getFullYear() === today.getFullYear() && lastDay.getMonth() === today.getMonth() && lastDay.getDate() === today.getDate() - 1) todayLabel = 'Yesterday: '; | |
$(this).before('<div class="MR_Timeline"><div style="margin:-4px;"><span title="' + timeline.length + ' available">Last ' + timelineGamesToShow + ' Games (for this rotation)</span></div><div>' + games + '</div><div style="font-size:12px;">Win: ' + ((wins / timelineGamesToShow) * 100).toFixed(2) + '%' + divider + 'Caps F/A: ' + capsFor + '/' + capsAgainst + divider + 'Red W%: ' + ((redTotals.wins / (redTotals.wins + redTotals.losses)) * 100).toFixed(0) + '% (' + redTotals.count + ' games)' + divider + 'Blue W%: ' + ((blueTotals.wins / (blueTotals.wins + blueTotals.losses)) * 100).toFixed(0) + '% (' + blueTotals.count + ' games)' + divider + '' + todayLabel + ((todayWins / todayGames) * 100).toFixed(2) + '% </div></div>'); | |
} | |
}); | |
} | |
function addLastResults(row, mapName) { | |
if (!mapRatings[mapName].hasOwnProperty('lastResults') || !mapRatings[mapName].lastResults.length) { | |
return; | |
} | |
let divid = 'MR_Results_' + mapName.replace(/[^a-z0-9]/gi, ''); | |
$(row).find('.lastResult').append('<div id="' + divid + '" style="display:flex; flex-flow:row nowrap; justify-content:center;"></div>'); | |
for (let i = 0; i < mapRatings[mapName].lastResults.length; i++) { | |
if (mapRatings[mapName].lastResults[i].hasOwnProperty('result')) { | |
let thisGame = mapRatings[mapName].lastResults[i]; | |
let timePlayed = new Date(thisGame.timePlayed); | |
$('#' + divid).append('<span class="' + (thisGame.result === 1 ? (thisGame.saveAttempt ? 'MR_LastGameSSA' : 'MR_LastGameWin') : (thisGame.saveAttempt ? 'MR_LastGameUSA' : 'MR_LastGameLoss')) + '" title="Map: ' + mapName + (thisGame.mapAuthor ? '\nAuthor: ' + thisGame.mapAuthor : '') + '\nPlayed: ' + timePlayed.toDateString() + ', ' + timePlayed.toLocaleTimeString() + '\nResult: ' + (thisGame.team === 1 ? 'Red ' : 'Blue ') + (thisGame.team === 1 && thisGame.scoreRed > thisGame.scoreBlue || thisGame.team === 2 && thisGame.scoreBlue > thisGame.scoreRed ? 'Won ' : 'Lost ') + thisGame.scoreRed + ':' + thisGame.scoreBlue + (thisGame.saveAttempt ? ' (Save Attempt)' : '') + '"></span>'); | |
} else { | |
$('#' + divid).append('<span class="' + (mapRatings[mapName].lastResults[i] === 1 ? 'MR_LastGameWin' : 'MR_LastGameLoss') + '" title="' + (mapRatings[mapName].lastResults[i] === 1 ? 'Win' : 'Loss') + '"></span>'); | |
} | |
} | |
} | |
function updateLastPlayed() { | |
$('table').each(function() { | |
let $tbody = $(this).find('tbody'); | |
let $rows = $tbody.find('tr'); | |
$rows.each(function(index) { | |
let mapName = $(this).find('.likes').prev().prev().text().trim(); | |
$(this).find('td.lastPlayed').text(mapRatings[mapName].lastPlayed ? dayjs(mapRatings[mapName].lastPlayed).from() : '-'); | |
}); | |
}); | |
} | |
if (showMyLastPlayed) { | |
setInterval(updateLastPlayed, 60000); //so "5 minutes ago" is always accurate | |
} | |
function updateMapDetails(currentTab='#rotation') { | |
let mapDetail = $(currentTab).find('.map-detail'); | |
mapDetail.find('.likes').hide(0); | |
mapDetail.find('.indifferents').hide(0); | |
mapDetail.find('.dislikes').hide(0); | |
mapDetail.find('.percent').hide(0); | |
mapDetail.find('.plays').hide(0); | |
mapDetail.find('.votes').hide(0); | |
setTimeout(function() { | |
let likes = parseInt( mapDetail.find('.likes').text().replace(/,/g, ''), 10 ); | |
let indifferents = parseInt( mapDetail.find('.indifferents').text().replace(/,/g, ''), 10 ); | |
let dislikes = parseInt( mapDetail.find('.dislikes').text().replace(/,/g, ''), 10 ); | |
let totalUsers = likes + indifferents + dislikes; | |
let score = likes / ((likes + dislikes) || 1) * 100; | |
mapDetail.find('.likes').html((likes/(totalUsers||1)*100).toFixed(1)+'%').attr('title', likes).fadeIn(100); | |
mapDetail.find('.indifferents').text((indifferents/(totalUsers||1)*100).toFixed(1)+'%').attr('title', indifferents).fadeIn(100); | |
mapDetail.find('.dislikes').text((dislikes/(totalUsers||1)*100).toFixed(1)+'%').attr('title', dislikes).fadeIn(100); | |
mapDetail.find('.percent').text((score).toFixed(1)+'%').attr('title', 'Score = Likes / (Likes + Dislikes)').fadeIn(100); | |
if (totalUsers === 0) { | |
mapDetail.find('.plays').text('0'); | |
mapDetail.find('.votes').text('0'); | |
} | |
mapDetail.find('.plays').fadeIn(100); | |
mapDetail.find('.votes').fadeIn(100); | |
}, 300); | |
} | |
//helpers... | |
function comparer(index) { | |
return function(a, b) { | |
let valA = $(a).children('td').eq(index).data('sortby'); | |
let valB = $(b).children('td').eq(index).data('sortby'); | |
if ($.isNumeric(valA) && $.isNumeric(valB)) { | |
if (valA === valB) { | |
let valA2 = $(a).children('td').eq(index).data('sortby2'); | |
let valB2 = $(b).children('td').eq(index).data('sortby2'); | |
if (valB2 && valA2) return valB2 - valA2; | |
} | |
return valB - valA; | |
} else { | |
valA.localeCompare(valB); | |
} | |
}; | |
} | |
function capitalize(value) { | |
return value.charAt(0).toUpperCase() + value.slice(1); | |
} | |
} | |
}); | |
//dayjs (and the dayjs "relativeTime" plugin) | |
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.dayjs=e()}(this,function(){"use strict";var t="millisecond",e="second",n="minute",r="hour",i="day",s="week",u="month",a="quarter",o="year",f="date",h=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[^0-9]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,c=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,d={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_")},$=function(t,e,n){var r=String(t);return!r||r.length>=e?t:""+Array(e+1-r.length).join(n)+t},l={s:$,z:function(t){var e=-t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),i=n%60;return(e<=0?"+":"-")+$(r,2,"0")+":"+$(i,2,"0")},m:function t(e,n){if(e.date()<n.date())return-t(n,e);var r=12*(n.year()-e.year())+(n.month()-e.month()),i=e.clone().add(r,u),s=n-i<0,a=e.clone().add(r+(s?-1:1),u);return+(-(r+(n-i)/(s?i-a:a-i))||0)},a:function(t){return t<0?Math.ceil(t)||0:Math.floor(t)},p:function(h){return{M:u,y:o,w:s,d:i,D:f,h:r,m:n,s:e,ms:t,Q:a}[h]||String(h||"").toLowerCase().replace(/s$/,"")},u:function(t){return void 0===t}},y="en",M={};M[y]=d;var m=function(t){return t instanceof S},D=function(t,e,n){var r;if(!t)return y;if("string"==typeof t)M[t]&&(r=t),e&&(M[t]=e,r=t);else{var i=t.name;M[i]=t,r=i}return!n&&r&&(y=r),r||!n&&y},v=function(t,e){if(m(t))return t.clone();var n="object"==typeof e?e:{};return n.date=t,n.args=arguments,new S(n)},g=l;g.l=D,g.i=m,g.w=function(t,e){return v(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var S=function(){function d(t){this.$L=D(t.locale,null,!0),this.parse(t)}var $=d.prototype;return $.parse=function(t){this.$d=function(t){var e=t.date,n=t.utc;if(null===e)return new Date(NaN);if(g.u(e))return new Date;if(e instanceof Date)return new Date(e);if("string"==typeof e&&!/Z$/i.test(e)){var r=e.match(h);if(r){var i=r[2]-1||0,s=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)}}return new Date(e)}(t),this.$x=t.x||{},this.init()},$.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds()},$.$utils=function(){return g},$.isValid=function(){return!("Invalid Date"===this.$d.toString())},$.isSame=function(t,e){var n=v(t);return this.startOf(e)<=n&&n<=this.endOf(e)},$.isAfter=function(t,e){return v(t)<this.startOf(e)},$.isBefore=function(t,e){return this.endOf(e)<v(t)},$.$g=function(t,e,n){return g.u(t)?this[e]:this.set(n,t)},$.unix=function(){return Math.floor(this.valueOf()/1e3)},$.valueOf=function(){return this.$d.getTime()},$.startOf=function(t,a){var h=this,c=!!g.u(a)||a,d=g.p(t),$=function(t,e){var n=g.w(h.$u?Date.UTC(h.$y,e,t):new Date(h.$y,e,t),h);return c?n:n.endOf(i)},l=function(t,e){return g.w(h.toDate()[t].apply(h.toDate("s"),(c?[0,0,0,0]:[23,59,59,999]).slice(e)),h)},y=this.$W,M=this.$M,m=this.$D,D="set"+(this.$u?"UTC":"");switch(d){case o:return c?$(1,0):$(31,11);case u:return c?$(1,M):$(0,M+1);case s:var v=this.$locale().weekStart||0,S=(y<v?y+7:y)-v;return $(c?m-S:m+(6-S),M);case i:case f:return l(D+"Hours",0);case r:return l(D+"Minutes",1);case n:return l(D+"Seconds",2);case e:return l(D+"Milliseconds",3);default:return this.clone()}},$.endOf=function(t){return this.startOf(t,!1)},$.$set=function(s,a){var h,c=g.p(s),d="set"+(this.$u?"UTC":""),$=(h={},h[i]=d+"Date",h[f]=d+"Date",h[u]=d+"Month",h[o]=d+"FullYear",h[r]=d+"Hours",h[n]=d+"Minutes",h[e]=d+"Seconds",h[t]=d+"Milliseconds",h)[c],l=c===i?this.$D+(a-this.$W):a;if(c===u||c===o){var y=this.clone().set(f,1);y.$d[$](l),y.init(),this.$d=y.set(f,Math.min(this.$D,y.daysInMonth())).$d}else $&&this.$d[$](l);return this.init(),this},$.set=function(t,e){return this.clone().$set(t,e)},$.get=function(t){return this[g.p(t)]()},$.add=function(t,a){var f,h=this;t=Number(t);var c=g.p(a),d=function(e){var n=v(h);return g.w(n.date(n.date()+Math.round(e*t)),h)};if(c===u)return this.set(u,this.$M+t);if(c===o)return this.set(o,this.$y+t);if(c===i)return d(1);if(c===s)return d(7);var $=(f={},f[n]=6e4,f[r]=36e5,f[e]=1e3,f)[c]||1,l=this.$d.getTime()+t*$;return g.w(l,this)},$.subtract=function(t,e){return this.add(-1*t,e)},$.format=function(t){var e=this;if(!this.isValid())return"Invalid Date";var n=t||"YYYY-MM-DDTHH:mm:ssZ",r=g.z(this),i=this.$locale(),s=this.$H,u=this.$m,a=this.$M,o=i.weekdays,f=i.months,h=function(t,r,i,s){return t&&(t[r]||t(e,n))||i[r].substr(0,s)},d=function(t){return g.s(s%12||12,t,"0")},$=i.meridiem||function(t,e,n){var r=t<12?"AM":"PM";return n?r.toLowerCase():r},l={YY:String(this.$y).slice(-2),YYYY:this.$y,M:a+1,MM:g.s(a+1,2,"0"),MMM:h(i.monthsShort,a,f,3),MMMM:h(f,a),D:this.$D,DD:g.s(this.$D,2,"0"),d:String(this.$W),dd:h(i.weekdaysMin,this.$W,o,2),ddd:h(i.weekdaysShort,this.$W,o,3),dddd:o[this.$W],H:String(s),HH:g.s(s,2,"0"),h:d(1),hh:d(2),a:$(s,u,!0),A:$(s,u,!1),m:String(u),mm:g.s(u,2,"0"),s:String(this.$s),ss:g.s(this.$s,2,"0"),SSS:g.s(this.$ms,3,"0"),Z:r};return n.replace(c,function(t,e){return e||l[t]||r.replace(":","")})},$.utcOffset=function(){return 15*-Math.round(this.$d.getTimezoneOffset()/15)},$.diff=function(t,f,h){var c,d=g.p(f),$=v(t),l=6e4*($.utcOffset()-this.utcOffset()),y=this-$,M=g.m(this,$);return M=(c={},c[o]=M/12,c[u]=M,c[a]=M/3,c[s]=(y-l)/6048e5,c[i]=(y-l)/864e5,c[r]=y/36e5,c[n]=y/6e4,c[e]=y/1e3,c)[d]||y,h?M:g.a(M)},$.daysInMonth=function(){return this.endOf(u).$D},$.$locale=function(){return M[this.$L]},$.locale=function(t,e){if(!t)return this.$L;var n=this.clone(),r=D(t,e,!0);return r&&(n.$L=r),n},$.clone=function(){return g.w(this.$d,this)},$.toDate=function(){return new Date(this.valueOf())},$.toJSON=function(){return this.isValid()?this.toISOString():null},$.toISOString=function(){return this.$d.toISOString()},$.toString=function(){return this.$d.toUTCString()},d}(),p=S.prototype;return v.prototype=p,[["$ms",t],["$s",e],["$m",n],["$H",r],["$W",i],["$M",u],["$y",o],["$D",f]].forEach(function(t){p[t[1]]=function(e){return this.$g(e,t[0],t[1])}}),v.extend=function(t,e){return t.$i||(t(e,S,v),t.$i=!0),v},v.locale=D,v.isDayjs=m,v.unix=function(t){return v(1e3*t)},v.en=M[y],v.Ls=M,v.p={},v}); | |
!function(r,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):r.dayjs_plugin_relativeTime=t()}(this,function(){"use strict";return function(r,t,e){r=r||{};var n=t.prototype,o={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"};function i(r,t,e,o){return n.fromToBase(r,t,e,o)}e.en.relativeTime=o,n.fromToBase=function(t,n,i,d,u){for(var a,f,s,l=i.$locale().relativeTime||o,h=r.thresholds||[{l:"s",r:44,d:"second"},{l:"m",r:89},{l:"mm",r:44,d:"minute"},{l:"h",r:89},{l:"hh",r:21,d:"hour"},{l:"d",r:35},{l:"dd",r:25,d:"day"},{l:"M",r:45},{l:"MM",r:10,d:"month"},{l:"y",r:17},{l:"yy",d:"year"}],m=h.length,c=0;c<m;c+=1){var y=h[c];y.d&&(a=d?e(t).diff(i,y.d,!0):i.diff(t,y.d,!0));var p=(r.rounding||Math.round)(Math.abs(a));if(s=a>0,p<=y.r||!y.r){p<=1&&c>0&&(y=h[c-1]);var v=l[y.l];u&&(p=u(""+p)),f="string"==typeof v?v.replace("%d",p):v(p,n,y.l,s);break}}if(n)return f;var M=s?l.future:l.past;return"function"==typeof M?M(f):M.replace("%s",f)},n.to=function(r,t){return i(r,t,this,!0)},n.from=function(r,t){return i(r,t,this)};var d=function(r){return r.$u?e.utc():e()};n.toNow=function(r){return this.to(d(this),r)},n.fromNow=function(r){return this.from(d(this),r)}}}); | |
dayjs.extend(dayjs_plugin_relativeTime); |
I think line 520 should initialize
lastDatePlayedPosition
tofirst
like:let lastDatePlayedPosition = first;
Updated, thanks!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I think line 520 should initialize
lastDatePlayedPosition
tofirst
like:let lastDatePlayedPosition = first;
I was getting an error trying to run this script before that. The error was for the line:
It said that it can't find property
timePlayed
of undefined. Once I made sure thatlastDatePlayedPosition
was initialized tofirst
to begin with though, it worked fine as far as I can tell. @nabbynz