Widget:Tempest Trial Tracker

// Wait for load (function {		"use strict";		function defer {			var dependenciesLoaded = false;			try { // Catch ReferenceErrors				dependenciesLoaded = mw.loader.using				&& jQuery				// add more variables or conditions here if needed				;			} catch (e) {}			if (dependenciesLoaded) {				main;			} else {				setTimeout(defer, 1000);			}		}		defer;	});

  



 

#ttt { background-color: #265FA5 ; color: white ; }

.prize-table { /*width:275px ;*/ text-align:left ; margin:auto ; }

.prize-table td:nth-child(2) { text-align:right ; background-color: #8AB3E0 ; width:75px ; border-bottom: 1px solid #265FA5 ; }

.char-poster { height:500px; background-size: contain; background-repeat: no-repeat; background-position: center; padding:0; margin:0; }

.full-prizes { text-align:center; /*margin:auto;*/ }

.full-prizes td { padding: 5px; }

.full-prize-rows td { border:1px solid black; }

/* Strike Through */ .full-prizes { border-collapse: collapse; }

.full-prizes td { position: relative; padding: 5px 10px; }

.full-prizes tr { height:55px; }

.full-prizes tr.strikeout td:nth-child(1):before { content: " "; position: absolute; top: 50%; left: 10px; right: 0; border-bottom: 3px solid #900; }

.full-prizes tr.strikeout td:nth-child(2):before { content: " "; position: absolute; top: 50%; left: 0; right: 10px; border-bottom: 3px solid #900; }

.fp-large tr.projected td:nth-child(2):after { content: "Projected"; position:absolute; font-style: italic;

background: #0a0; color:white; height:100%; top:0; width:100px; left:100%; display: flex; align-items: center; justify-content: center; }

.projected { border:3px solid #0a0; }

.countdown { font-size: 20px; line-height:20px; height:80px; margin-top:25px; }

// Apologies to anyone actually familiar with angular that's looking at this. // It's probably not done the best way, but it's done how I got it to work

//--//		// Module  // //--//		var app = angular.module('fe', ['ngRoute']);

//--//		// Config  // //--//		app.config(function($routeProvider) {			$routeProvider.			when('/', { template:trialHomeTemplate, controller: 'defaultCont' }).			otherwise({ redirectTo: '/' });		}) ;

//---//		// Controllers  // //---//

// Default controller, runs main functions app.controller('defaultCont', function($scope, $rootScope, $location, trialData) {			$scope.trialData = trialData ;			$scope.showAll = false ;			$scope.alt = false ;

$scope.$watch(				function { return trialData.points; },				function(newValue, oldValue) {					if ( newValue !== oldValue ) {						var remaining = trialData.end.getTime - new Date.getTime ;						trialData.calcPPDs(remaining) ;						trialData.updatePrizeCounts ;					}				}			);

$scope.getBackgroundURL = function { if(trialData.charName == null) return;

var backStyle = 'url(\'' ;

var baseURL = 'https://feheroes.gamepedia.com/Special:FilePath/';

//https://feheroes.gamepedia.com/Special:FilePath/Hinata_Samurai_Groom_Face.webp //https://feheroes.gamepedia.com/Special:FilePath/Hinata_Samurai_Groom_BtlFace.webp //https://feheroes.gamepedia.com/Special:FilePath/Hinata_Samurai_Groom_BtlFace C.webp //https://feheroes.gamepedia.com/Special:FilePath/Hinata_Samurai_Groom_BtlFace D.webp if(trialData.points >= trialData.majorPrizes[4].points) imgURL = baseURL + trialData.charName + '_BtlFace D.webp' ; else if(trialData.points >= trialData.majorPrizes[3].points) imgURL = baseURL + trialData.charName + '_BtlFace C.webp' ; else if(trialData.points >= trialData.majorPrizes[1].points) imgURL = baseURL + trialData.charName + '_BtlFace.webp' ; else var imgURL = baseURL + trialData.charName + '_Face.webp' ;

backStyle += imgURL + '\')' ;				return {'background-image': backStyle};			}

$scope.charAllFilter = function(item) { return item.type == 'char' || item.type == 'all' ; }

$scope.getImg = function(item) { return 'img/' + item.img ; }

$scope.gotoProjected = function { $scope.showAll = true ; setTimeout(function{					var proj = document.getElementById('projectedAnchor');					proj.scrollIntoView;				}, 50); }

$scope.isProjected = function(prize, next) { // If my points is less than projected, but the next prize is over the projection... var isProjected = prize.points <= trialData.projected && (next == null || next.points > trialData.projected) ;

// ...or I'm the highest prize and the projection is > 99999 //var isHighest = trialData.projected > 99999 && prize.points == 99999 ;

//return isProjected || isHighest ; return isProjected ; }		});

// Table controller, for the prize tables app.controller('tableCont', function($scope, $location, trialData) {			$scope.trialData = trialData ;		});

// Countdown controller, to run the countdown to the start/end of the trial app.controller('countdownCont', function($scope, $location, trialData) {			var startDate = trialData.start ;			var endDate = trialData.end ;

if(updateCountdown(false)) return ;

var update = setInterval(function {				updateCountdown(true) ;			}, 1000);

function updateCountdown(apply) { startDate = trialData.start; endDate = trialData.end; if(startDate == null) return;

// Get the elapsed and remaining times var now = new Date.getTime ; var elapsed = now - startDate.getTime ; var remaining = endDate.getTime - now ;

// Trials are over :(				if (remaining < 0) {					clearInterval(update);					$scope.time = "Trials over :(" ; if(apply) $scope.$apply ; return true ; }

$scope.time = '' ; var target = remaining ; if(startDate > now) { target = startDate.getTime - now ; $scope.time = 'Trials start in: ' ; }

// Get the date parts for the countdown var rDays = Math.floor(target / (1000 * 60 * 60 * 24)) ; var rHours = Math.floor((target % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) ; var rMinutes = Math.floor((target % (1000 * 60 * 60)) / (1000 * 60)) ; var rSeconds = Math.floor((target % (1000 * 60)) / 1000) ;

var d = rDays == 0 ? '' : rDays == 1 ? ' day ' : ' days ' ; var h = rHours == 0 ? '' : rHours == 1 ? ' hour ' : ' hours ' ; var m = rMinutes == 0 ? '' : rMinutes == 1 ? ' minute ' : ' minutes ' ; var s = rSeconds == 0 ? '' : rSeconds == 1 ? ' second ' : ' seconds ' ;

if(rDays == 0) rDays = '' ; if(rHours == 0) rHours = '' ; if(rMinutes == 0) rMinutes = '' ; if(rSeconds == 0) rSeconds = '' ;

// Draw countdown $scope.time += rDays + d + rHours + h + rMinutes + m + rSeconds + s ;

if(startDate < now) trialData.calcPPDs(remaining) ;

if(apply) $scope.$apply ; }		});

//--//		// Directives  // //--//		app.directive('prizeTable', function {			return {				template: prizeTableTemplate,				controller: 'tableCont',				replace: true,				scope: {					data: '='				}			};		});

app.directive('countdown', function {			return {				template: countdownTemplate,				controller: 'countdownCont',				replace: true			};		});

app.directive("ngMobileClick", [function {			return function (scope, elem, attrs) {				elem.bind("touchstart click", function (e) { //e.preventDefault; e.stopPropagation;

scope.$apply(attrs["ngMobileClick"]); });			}		}]);

//--//		// Trial Data  // //--//		app.factory('trialData',function($rootScope, $q) {			var trialName =  ;			//$rootScope.title = 'Tempest Trials: \ + trialName + '\' Progress Tracker' ;			getTrialPrizes.then(function(data) { console.log('got the data?'); console.log(data); });

var startDate, endDate, charNameWithUnderscores;

// Normal trial // var majorPrizes = [ // 	{type:'char', points:2000, name:'4* ' + charName}, // 	{type:'seal', points:12000, name:sealName1}, // 	{type:'seal', points:20000, name:sealName2}, // 	{type:'char', points:30000, name:'5* ' + charName}, // 	{type:'seal', points:40000, name:sealName3}, // 	{type:'all', points:99999, name:'Everything'} // ] ;

//Mini trial var majorPrizes = [] ;

var allPrizes = [] ;

// Stores the total amount of each prize var prizeAmounts = { orb:0, feather:0, crystal:0, coin:0 } ;

// Stores how many of each prize I've gotten var gotPrizeAmounts = { orb:0, feather:0, crystal:0, coin:0 }

var points = 0 ; var ppd = 0 ; var projected = 0 ;

return { name: getName, start: getStartDate, end: getEndDate, majorPrizes: majorPrizes, allPrizes: allPrizes, charName: getCharName, // seals: seals, points: points, ppd: ppd, projected: projected, calcPPDs: calcPPDs, updatePrizeCounts: updatePrizeCounts, prizeAmounts: prizeAmounts, gotPrizeAmounts: gotPrizeAmounts }

function getTrialInfo { var deferredTrialInfo = $q.defer;

(new mw.Api).get( {					action: "cargoquery",					limit: "1",					tables: "TempestTrials",					fields: "Name,StartTime,EndTime",					format: "json",					order_by:"StartTime DESC",					maxlag: 5				} ).done( function (data,jqXHR) {					trialName = data.cargoquery[0].title.Name ;					$rootScope.title = 'Tempest Trials: \'' + trialName + '\' Progress Tracker';					$rootScope.$apply;

startDate = new Date(data.cargoquery[0].title.StartTime); endDate = new Date(data.cargoquery[0].title.EndTime); deferredTrialInfo.resolve(trialName); } ).fail( function(errorcode,data,_,jqXHR) { if (errorcode === "maxlag" || errorcode === "ratelimited") { setTimeout(getHeroes, parseInt(jqXHR.getResponseHeader("Retry-After"))*1000); } else { }				} );

return deferredTrialInfo.promise; }

function getTrialPrizes { var deferredTrialRewards = $q.defer;

getTrialInfo.then(function(trialName) {

(new mw.Api).get( {						action: "cargoquery",						limit: "500",						tables: "TempestTrialsRewards",						fields: "Score,Kind,Amount,Item,Unit,SacredSeal,Rarity",						format: "json",						order_by:"Score ASC",						where: "_pageName = '" + trialName + "'",						maxlag: 5					} ).done( function (data,jqXHR) {						var prizeList = data.cargoquery;						for(var i=0; i<prizeList.length; i++) {							var prize = prizeList[i].title;							if(prize.Score == '') continue;

if(prize.Kind == 'item') { var prizeObj = { points:prize.Score, amount:prize.Amount, img:'https://feheroes.gamepedia.com/Special:FilePath/' + prize.Item.replace(/ /g, '_').replace(/:/g, '') + '.png?width=40' }								allPrizes.push(prizeObj);

// This isn't a seal, but I don't care enough to change it right now if(prize.Item.includes('Divine Code')) { var majorPrizeObj = { type:'seal', points:parseInt(prize.Score), name:prize.Item }									majorPrizes.push(majorPrizeObj); }							}							else if(prize.Kind == 'unit') { var prizeObj = { points:prize.Score, amount:prize.Amount, img:'https://feheroes.gamepedia.com/Special:FilePath/' + prize.Unit.replace(/ /g, '_') + '_Face_FC.webp?width=40' }								allPrizes.push(prizeObj);

var majorPrizeObj = { type:'char', points:parseInt(prize.Score), name:prize.Rarity + '* ' + prize.Unit }								majorPrizes.push(majorPrizeObj);

charNameWithUnderscores = prize.Unit.replace(/ /g, '_'); }							else if(prize.Kind == 'sacredseal') { // This ugly regex puts a space for things like AtkSpd Bond, as the '/' isn't present in the DB								// But the image path is expecting a space (or underscore) there var sealName = prize.SacredSeal.replace(/(Atk|Spd|Def|Res)(Atk|Spd|Def|Res)/,"$1/$2");

var prizeObj = { points:prize.Score, amount:sealName, img:'https://feheroes.gamepedia.com/Special:FilePath/' + sealName.replace(/ /g, '_').replace(/[/]/g, '_') + '.png?width=40' }								allPrizes.push(prizeObj);

var majorPrizeObj = { type:'seal', points:parseInt(prize.Score), name:prize.SacredSeal }								majorPrizes.push(majorPrizeObj); }						}						majorPrizes.push({type:'all', points:50000, name:'Everything'});

populatePrizeAmounts ; } ).fail( function(errorcode,data,_,jqXHR) { if (errorcode === "maxlag" || errorcode === "ratelimited") { setTimeout(getHeroes, parseInt(jqXHR.getResponseHeader("Retry-After"))*1000); } else { }					} );				});

return deferredTrialRewards.promise; }

function getName { return trialName; }

function getStartDate { return startDate; }

function getEndDate { return endDate; }

function getCharName { return charNameWithUnderscores; }

function populatePrizeAmounts { for(var i=0; i= 0) { prizeAmounts.orb += parseInt(p.amount.replace(/,/g, '')) ; }					if(p.img.indexOf('Feather') >= 0) { prizeAmounts.feather += parseInt(p.amount.replace(/,/g, '')) ; }					if(p.img.indexOf('Shard') >= 0 || p.img.indexOf('Crystal') >= 0) { prizeAmounts.crystal += parseInt(p.amount.replace(/,/g, '')) ; }					if(p.img.indexOf('Coin') >= 0) { prizeAmounts.coin += parseInt(p.amount.replace(/,/g, '')) ; }				}			}

function updatePrizeCounts { // Reset all prizes first gotPrizeAmounts.orb = 0 ; gotPrizeAmounts.feather = 0 ; gotPrizeAmounts.crystal = 0 ; gotPrizeAmounts.coin = 0 ;

for(var i=0; i this.points) break ;

if(p.img.indexOf('Orb') >= 0) { gotPrizeAmounts.orb += parseInt(p.amount.replace(/,/g, '')) ; }					if(p.img.indexOf('Feather') >= 0) { gotPrizeAmounts.feather += parseInt(p.amount.replace(/,/g, '')) ; }					if(p.img.indexOf('Shard') >= 0 || p.img.indexOf('Crystal') >= 0) { gotPrizeAmounts.crystal += parseInt(p.amount.replace(/,/g, '')) ; }					if(p.img.indexOf('Coin') >= 0) { gotPrizeAmounts.coin += parseInt(p.amount.replace(/,/g, '')) ; }				}			}

function calcPPDs(remaining) { if(startDate == null) return;

var points = this.points ;

var now = new Date.getTime ; var elapsed = now - startDate.getTime ;

if(startDate.getTime > now || remaining < 0) return ;

this.ppd = Math.round((this.points / (elapsed / (1000 * 60 * 60 * 24)))*100)/100 ; this.projected = Math.floor(this.points + (this.ppd*(remaining / (1000 * 60 * 60 * 24)))) ;

var prizes = this.majorPrizes ; for(var i=0; i= prizes[i].points) { prizes[i].ppdLeft = 0 ; prizes[i].onTrack = 'Got it!' ; prizes[i].color = '#0000CC' ; }					else { prizes[i].ppdLeft = Math.round(((prizes[i].points-points) / (remaining / (1000 * 60 * 60 * 24)))*100)/100 ; //if(prizes[i].ppdLeft < 0) prizes[i].ppdLeft = 0 ; prizes[i].onTrack = (this.ppd > prizes[i].ppdLeft) ? 'Yes' : 'No' ; prizes[i].color = (prizes[i].onTrack == 'Yes') ? '#00CC00' : '#CC0000' ; }				}			}		});

function main { angular.bootstrap(document.getElementById('ttt'), ['fe']); }

// Templates are stored like this because I couldn't have them as separate HTML files // I needed this to be a single page app // They were originally separate files, I swear, I'm not that bad. All the javascript was too!

//---//		// TRIAL HOME TEMPLATE  // //---//		var trialHomeTemplate = ` 		Current TT Score:  0">				Current Points per Day:				Projected Final Score:				







</prize-table>

<button ng-click="showAll = !showAll"> Toggle All Prizes Orbs: <span ng-bind="trialData.gotPrizeAmounts.orb + '/' + trialData.prizeAmounts.orb"/> Crystals: <span ng-bind="trialData.gotPrizeAmounts.crystal + '/' + trialData.prizeAmounts.crystal"/> Feathers: <span ng-bind="trialData.gotPrizeAmounts.feather + '/' + trialData.prizeAmounts.feather"/> Sacred Coins: <span ng-bind="trialData.gotPrizeAmounts.coin + '/' + trialData.prizeAmounts.coin"/>

<div style="display:none" ng-if="trialData.charName != null"> <img ng-src="https://feheroes.gamepedia.com/Special:FilePath/_Face.webp"> <img ng-src="https://feheroes.gamepedia.com/Special:FilePath/_BtlFace.webp"> <img ng-src="https://feheroes.gamepedia.com/Special:FilePath/_BtlFace C.webp"> <img ng-src="https://feheroes.gamepedia.com/Special:FilePath/_BtlFace D.webp"> `

////		// PRIZE TABLE TEMPLATE  // ////		var prizeTableTemplate = ` `

//--//		// COUNTDOWN TEMPLATE  // //--//		var countdownTemplate = ` 	Tempest Trials: <span ng-bind="trialData.name"/> `