Transformania Time Main Page Remake

Reshapes, restyles the Transformania Time main page a bit.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==UserScript==
// @name         Transformania Time Main Page Remake
// @namespace   http://steamcommunity.com/id/siggo/
// @version      1.0.6
// @description  Reshapes, restyles the Transformania Time main page a bit.
// @author       Prios
// @match        https://www.transformaniatime.com/
// @match        https://test.transformaniatime.com/
// @copyright 2019, Prios (https://openuserjs.org/users/Prios)
// @grant        none
// @license      MIT
// jshint multistr: true
// ==/UserScript==

// PROBLEM: Browser widths between 1200px and 1400px look terrible. All things considered, best to let the components on the left shrink a little.

/*
function linkifyLogEntries( i ) {

  var logActionsMatch = /\b([\wü]*)(?: '?.*')?( .*?)(?: entered from | left toward | cleansed here\.| meditated here\.| searched here\.| cast | dropped | picked up | threw a | shouted |, a | tamed | released a | consumed a )/;
  var logSubject = $( this ).text().match(logActionsMatch); // [1] is first name, [2] is anything beyond the quote-designated nickname (if any) but before the matched action

  if ( logSubject !== null ) {
        logSubject = logSubject.slice(1).join(''); // squash the two subgroups together
    // if ( ( logSubject != 'You' )) {
      $(this).wrapInner('<a style="font-weight:normal;color:inherit !important;" href="https://www.transformaniatime.com/PvP/LookAtPlayerItem?vicname=' + logSubject + '"></a>');
    // }
  }
  if ( i >= 300 ) { return false; }
}
*/

function pseudoShoutClass() { // just simulates a:hover color:black, a color:white
    var $this = $(this);
    var whichEvent = event.type;
    if (whichEvent === 'mouseover') {
        $this.attr('style', $this.attr('style').replace('white', 'black')); // Using .attr because .css doesn't support !important
    }
    else if (whichEvent === 'mouseout') {
        $this.attr('style', $this.attr('style').replace('black', 'white'));
    }
}

// constructor
// storage: key used by TfT to store the setting in localStorage; required
// span: the span tag itself as a jquery object; required
// friendlyName: string, the public 'end user' name for the setting; required
// switches: an object with 'true' and 'false' key entries, and corresponding span tag display text as paired values; optional, uses 'ON' and 'OFF' as default
function DynamicMenuItem(storage, span, friendlyName, switches) {
    this.storageKey = storage;
    this.$spanJQO = span;
    this.myFriendlyName = friendlyName;
    this.switchSettings = switches || {
        'true': 'ON',
        'false': 'OFF'
    };
    this.updateSpan = function () {
        var curSetting = localStorage.getItem(this.storageKey) || 'false';
        this.$spanJQO.html(this.switchSettings[curSetting]);
    };
    this.alertStatus = function () {
        var curSetting = localStorage.getItem(this.storageKey) || 'false';
        alert(this.myFriendlyName + ' now ' + this.switchSettings[curSetting] + '!');
    };
    this.flipSwitch = function () {
        var locStoKey = this.storageKey;
        var curSetting = localStorage.getItem(locStoKey) || 'false';
        if (curSetting === 'false') { // this setting is stored as a string in localStorage by TfT
            localStorage.setItem(locStoKey, 'true');
        }
        else {
            localStorage.setItem(locStoKey, 'false');
        }
        this.updateSpan();
        this.alertStatus(); // curSetting will be determined all over again twice but that's okay, this isn't really a super time sensitive operation
        // Plus we'll find out if .setItem did something unexpected, instead of assuming it worked exactly as desired
    };
    this.updateSpan(); // initialize
}

$(function () {

    // var startTime = Date.now();
    'use strict';

    // ACTION BUTTONS - DETACH, AND CANCEL SCRIPT IF NOT PRESENT
    var $playerActions = $('#playerActionBox').detach(); // The original action buttons
    if ($playerActions.length === 0) return; // Stop the script right here if we're not on the right kind of page

    // MAJOR COMPONENT EXTRACTION

    var $tftSite = $('body').detach(); // Detaching the page to avoid making lots of updates to the DOM itself. Counterintuitively, it's fastest and least intensive to do .finds() from here, when possible.
    var $siteTop = $tftSite.find('#siteTop'); // everything in the body except the footer
    var $siteFooter = $tftSite.find('footer'); // footer tag inside a container-class div

    // SUBCOMPONENT VARIABLES

    var $settingsWrenchListitem = $tftSite.find('.glyphicon-wrench').parent().parent();
    var $newSettingsMenu = $('<li id="settingsMenuItem" class="dropdown">\
<a id="settingsMenuAnchor" href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Settings <span class="caret"></span></a>\
<ul id="settingsMenuList" class="dropdown-menu">\
<li><a id="settingsTurnAudio" href="#">Turn Alerts are <span id="dynTurnSpan">UNKNOWN (error!)</span></a></li>\
<li><a id="settingsAttackAudio" href="#">Attack Alerts are <span id="dynAttackSpan">UNKNOWN (error!)</span></a></li>\
<li><a id="settingsMsgAudio" href="#">Message Alerts are <span id="dynMsgSpan">UNKNOWN (error!)</span></a></li>\
<li><a id="settingsPopups" href="#">Popup Notifications are <span id="dynPopSpan">UNKNOWN (error!)</span></a></li>\
<li><a id="settingsBioItem" href="/Settings/SetBio">Update Bio</a></li>\
<li><a id="settingsBlacklistItem" href="/Settings/MyBlacklistEntries">Manage Blacklist</a></li>\
<li><a id="settingsReserveNameItem" href="/PvP/ReserveName">Reserve Name</a></li>\
<li><a id="settingsNicknameItem" href="/Settings/SetNickname">Set your Nickname</a></li>\
<li><a id="settingsFullPageItem" href="/Settings/Settings">More Settings...</a></li>\
</ul>\
</li>'); // ids make finding these elements much faster

    var $specialBox = $tftSite.find('.specialBox'); // stats on people playing, boss announcements, chaos round announcement

    var $containerInner = $tftSite.find('.containerInner'); //  Area with: Avatar info, self portrait, stat bars, action buttons, movement grid, area description

    var $innerRows = $containerInner.find('.row'); // Row two starts before the movement grid, row one ends before the action buttons (action buttons are not in a row)
    var $frontOuters = $tftSite.find('.frontOuter'); // the three columns in first row, avatar info/portrait/stat bars
    var $avatarText = $tftSite.find('.avatarText'); // First column's single child, Name/Form/Covenant avatar info box
    var $actionCounts = $frontOuters.find('.avatarCount').slice(0, 2); // first and second, this is number/max of attacks and restorations

    var $allIcons = $tftSite.find('.col-sm-12').find('.icon'); // The attack, meditation, and money icons
    var $attackIcon = $tftSite.find('.icon-timesattacking');
    var $recoverIcon = $tftSite.find('.icon-cleansemeditate');
    var $moneyIcon = $tftSite.find('.icon-money');

    var $moveGrid = $tftSite.find('.tableLines'); // Movement grid
    var $moveGridCells = $moveGrid.find('td');

    var $knownSpells = $tftSite.find('.knownspells'); // spells known in this area

    var $covController = $tftSite.find('.covController'); // The sentence stating which covenant has enchanted the area

    var $activityLog = $tftSite.find('#RecentActivityLog'); // Original log of room's events, just above footer
    var $activityLogWide; // Widescreen-only clone to place on right side -- cloned later, after some alterations to original

    var $lindellaLatest = $activityLog.find('li:contains(Lindella the Soul Vendor )').first();
    var $wuffieLatest = $activityLog.find('li:contains(Wüffie the Soul Pet Vendor )').first();
    var lindellaTrail;
    var wuffieTrail;

    // UTILITY VARIABLES

    var selfLookURL = $tftSite.find('a:contains(Look at Yourself)').attr('href');

    var busStop = $tftSite.find('.bus').length; // 1/true if at a bus stop, 0/false if not
    var covSafeground = $tftSite.find('.covSafeground').length; // 1/true if a safeground, 0/false if not

    var barButtonsData = [{
        'target': 'Cleanse',
        'text': '',
        'isBlocked': false
    },
                          {
                              'target': 'Meditate',
                              'text': '',
                              'isBlocked': false
                          },
                          {
                              'target': 'Search',
                              'text': '',
                              'isBlocked': false
                          }
                         ];

    // creating objects associated with the on/off settings menu items, for setting and updating them
    var switchTurnSound = new DynamicMenuItem('play_updateSoundEnabled', $newSettingsMenu.find('#dynTurnSpan'), 'Audible Turn Alerts');
    var switchAttackSound = new DynamicMenuItem('play_AttackSoundEnabled', $newSettingsMenu.find('#dynAttackSpan'), 'Audible Attack Alerts');
    var switchMsgSound = new DynamicMenuItem('play_MessageSoundEnabled', $newSettingsMenu.find('#dynMsgSpan'), 'Audible Message Alerts');
    var switchPopups = new DynamicMenuItem('play_html5PushEnabled', $newSettingsMenu.find('#dynPopSpan'), 'HTML5 Popup Notifications');

    // -----------
    // MAIN SCRIPT

    // attaching click listeners to dynamic settings menu items
    $newSettingsMenu.find('#settingsTurnAudio').click(function () {
        switchTurnSound.flipSwitch();
        playUpdateSound = localStorage.getItem(switchTurnSound.storageKey) === 'true'; // converting the 'true' or 'false' string into a real boolean
    });
    $newSettingsMenu.find('#settingsAttackAudio').click(function () {
        switchAttackSound.flipSwitch();
        playAttackSound = localStorage.getItem(switchAttackSound.storageKey) === 'true';
    });
    $newSettingsMenu.find('#settingsMsgAudio').click(function () {
        switchMsgSound.flipSwitch();
        playMessageSound = localStorage.getItem(switchMsgSound.storageKey) === 'true';
    });
    $newSettingsMenu.find('#settingsPopups').click(function () {
        switchPopups.flipSwitch();
        notificationsEnabled = localStorage.getItem(switchPopups.storageKey) === 'true';
    });

    // Replacing the wrench with the new settings menu
    $settingsWrenchListitem.replaceWith($newSettingsMenu);

    // populate barButtonsData[0-2].text with actionButton texts
    $playerActions.find('.actionButton').each(function (i) {
        barButtonsData[i].text = $(this).text();
    });

    // Checking whether player has insufficient AP for these actions
    if ((cleanseCost > ap) || (playerMana < 3.0)) {
        barButtonsData[0].isBlocked = true;
    }
    if (meditateCost > ap) {
        barButtonsData[1].isBlocked = true;
    }
    if (searchCost > ap) {
        barButtonsData[2].isBlocked = true;
    }

    // General Activity Log alterations
    if ($lindellaLatest.length) { // if Lindella has at least one log entry
        $lindellaLatest.css({
            'color': 'cyan'
        });
        lindellaTrail = $lindellaLatest.text();
        if (lindellaTrail.includes(" left toward ")) { // check that it's a leaving entry
            lindellaTrail = lindellaTrail.match(/Lindella the Soul Vendor left toward (.*)\n/)[1]; // Now make it just the location she's leaving towards
        }
        else {
            lindellaTrail = null;
        }
        // alert('lindellaTrail is ' + lindellaTrail);
    }

    if ($wuffieLatest.length) { // if Wüffie has at least one log entry
        $wuffieLatest.css({
            'color': 'deeppink'
        });
        wuffieTrail = $wuffieLatest.text();
        if (wuffieTrail.includes(" left toward ")) { // check that it's a leaving entry
            wuffieTrail = wuffieTrail.match(/Wüffie the Soul Pet Vendor left toward (.*)\n/)[1]; // Now make it just the location she's leaving towards
        }
        else {
            wuffieTrail = null;
        }
        // alert('wuffieTrail is ' + wuffieTrail);
    }

    // $activityLog.find('li').each( linkifyLogEntries );

    $activityLogWide = $activityLog.clone();

    // Activity Log alterations (original only, post-cloning)
    $activityLog
        .addClass('hidden-lg');

    // Activity Log alterations (sidebar only)
    $activityLogWide
        .attr('id', 'RecentActivityLogSide') // giving the clone a unique ID, unfortunately this removes the id-targeted style rules and forces me to re-add them with the .css method
        .addClass('visible-lg-block') // Bootstrap class thingy that makes this only show up when the page is at least 1200px wide.
        .css({
        'background': '#C2A9AF',
        'text-align': 'justify',
        'overflow-y': 'scroll',
        'padding': 10,
        'width': '31%',
        'position': 'fixed',
        'resize': 'none',
        'height': 'initial',
        'top': 15,
        'bottom': 15
    }) // the 31% width is a kludge.
    ;

    // $specialBox
    //  .css({'font-size': 'small'})
    // ;

    $siteFooter
        .unwrap() // stripping the footer's container div, footer will be moved into another container later
    ;

    $tftSite.find('.offlinePlayersWrapperBG') // this is the inventory row
        .css({
        'border-bottom': 0,
        'background': '#ebe8e0'
    });

    /*
  $tftSite.find('.formerPlayer') // links to player profiles of ground items
    .wrap(function() {
      var $this = $(this);
      var victimName = $this.text().slice(10, -1);
      return ('<a href="https://www.transformaniatime.com/PvP/LookAtPlayerItem?vicname=' + victimName + '">');
    })
  ;
  */

    $actionCounts
        .each(function (i) {
        var $this = $(this);
        if ($this.text() === ' 3 / 3') {
            $this.css({
                'color': 'red',
                'font-weight': 'bold'
            }); // warning when tapped out on limited-per-turn actions
            if (i === 1) { // if it's the second one, i.e. recoveries. Kind of confusing and clunky.
                barButtonsData[0].isBlocked = true; // blocking both cleansing and meditation
                barButtonsData[1].isBlocked = true;
            }
        }
    })
        .css({
        'font-size': 'larger'
    });

    $allIcons
        .css({
        'font-size': 'x-large'
    })
        .removeClass();

    $attackIcon
        .html('⚔️');

    $recoverIcon
        .html('🔮');

    $moneyIcon
        .html('💰');

    // preventing style rule freakout
    $containerInner
        .css({
        'background': '#ebe8e0'
    });

    // gross rearrangement of page layout -- this unfortunately causes a marked visual 'jump' once the page is reattached
    $siteTop
        .attr('class', 'container-fluid')

        .children()

        .wrapAll('<div class="col-lg-8">') // wrap the ENTIRE existing contents in new column
        .parent() // traverse to newly-made column
        .wrap('<div class="row">') // wrap the new column in a new row
        .append($specialBox)
        .append($siteFooter) // move footer to bottom of column

        .after('<div class="col-lg-4">') // add another column to the row
        .next() // traverse to newly-made column
        .append($activityLogWide) // add side-log to the new column

    ;

    $moveGrid // direct alteration before moving to new spot
        .css({
        'width': 'inherit',
        'height': 'inherit',
        'box-shadow': '-5px 5px 5px gray',
        'table-layout': 'fixed'
    })

        .find('td')
        .css({
        'font-size': 12,
    })
        .end()

        .find('tr')
        .css({
        'height': '33%'
    })

        .find('a')
        .css({
        'display': 'table',
        'width': '100%',
        'height': '100%'
    }) // expands the anchor to fill the cell; this requires removing the display:table-cell property, which unfortunately messes up the text's vertical alignment
        .append(function () { // moves the anchor's text to a newly-created nested div element which itself has display:table-cell, fixing the vertical alignment
        var $myText = $(this).text();
        $(this).text('');
        return '<div style="display: table-cell; vertical-align: middle">' + $myText + '</div>';
    })
        .end()

        .find('div:contains(' + wuffieTrail + ')') // if wuffieTrail is null, this finds nothing and nothing happens
        .each(function () {
        // alert('text is ' + $(this).text());
        if ($(this).text() === wuffieTrail) { // ensures a precise match, not just containing
            $(this).css({
                'color': 'deeppink'
            });
            return false; // stops looping
        }
    })

        .end()

        .find('div:contains(' + lindellaTrail + ')') // if lindellaTrail is null, this finds nothing and nothing happens
    // will overwrite Wuffie's highlighting if they both coincide. A little wasteful
        .each(function () {
        // alert('text is ' + $(this).text());
        if ($(this).text() === lindellaTrail) { // ensures a precise match, not just containing
            $(this).css({
                'color': 'cyan'
            });
            return false; // stops looping
        }
    })
        .end();

    $moveGridCells // this part is a little repetitious, but not too much so, and the individual parts might change in the future

        .eq(8) // ninth cell
        .css({
        'background': '#A16969'
    })

        .append('<a href="/pvp/EnchantLocation" style="font-weight: bold; font-size: larger; position: initial; color: white !important">Enchant</a>') // I don't have a better alternative than !important here unfortunately
        .find('a') // selects just-created anchor
        .hover(pseudoShoutClass)
        .end()

        .end()

        .eq(2) // third cell
        .css({
        'background': '#A16969'
    })

        .append('<a href="/pvp/shout" class="shout" style="font-weight: bold; font-size: larger; position: initial; color: white !important">Shout</a>')
        .find('a') // selects just-created anchor
        .hover(pseudoShoutClass)
        .end()

        .end()

        .eq(6) // seventh cell
        .css({
        'background': '#A16969'
    })

        .append($knownSpells.html()) // simply sticks the html for $knownSpells into the cell unmodified
    ;

    if (busStop) {
        $moveGridCells

            .eq(0)
            .css({
            'background': '#A16969'
        })

            .append('<a href="/pvp/Bus" style="font-weight: bold; font-size: larger; position: initial; color: white !important">Take Bus</a>')
            .find('a') // selects just-created anchor
            .hover(pseudoShoutClass);
    }

    $avatarText // direct alteration before moving
        .css({
        'margin-bottom': 10,
        'display': 'inline-block'
    });

    $innerRows.eq(0) // Row with player portrait and stats
        .css({
        'margin-bottom': 20,
        'height': 301
    })

        .find($frontOuters) // the three columns
        .css({
        'border': 0,
        'height': 301
    })

        .eq(0) // left column, original location of player name and form name, new location of moveGrid
        .css({
        'width': 301
    })
        .prepend($moveGrid) // moves it from its original location, already has shadow set
        .end()

        .eq(1) // center column, location of player portrait
        .css({
        'width': 301
    })

        .find('.portraitFront')
        .css({
        'border': 0,
        'width': 'inherit',
        'height': 'inherit',
        'box-shadow': '1px 5px 5px gray'
    })
        .wrap('<a href="' + selfLookURL + '" style="font-weight: normal; width: inherit; height: inherit"></a>')
        .end()

        .end()

        .eq(2) // right column, location of player stats

        .find('.avatarBars')
        .css({
        'background': '#d9d9d9',
        'box-shadow': '5px 5px 5px gray'
    })
        .prepend($avatarText) // pulled over here from left column

        .find('.barWrapper') // 'backgrounds' of the bars, empty bars
        .css({
        'height': 35
    })

        .find('.barText') // what it says on the tin
        .css({
        'line-height': '35px'
    })
        .each(function (i) { // this will break if the order of the vital stat bars is changed
        var $this = $(this);
        $this
            .wrap('<a href="/PvP/' + barButtonsData[i].target + '" style="font-weight: normal"></a>') // inherits .barWrapper size
            .hover(
            function () {
                $this // mouseenter
                    .data('initialText', $this.text())
                    .text(barButtonsData[i].text)
                    .css({
                    'color': '#e4e0d4',
                    'font-weight': 'bold'
                });
                if (barButtonsData[i].isBlocked && (secondsToUpdate > 0)) {
                    $this
                        .css({
                        'text-decoration': 'line-through'
                    })
                        .parent()
                        .attr('href', null);
                }
            },
            function () {
                $this // mouseleave
                    .text($this.data('initialText'))
                    .css({
                    'color': '',
                    'font-weight': '',
                    'text-decoration': ''
                })
                    .parent()
                    .attr('href', '/PvP/' + barButtonsData[i].target);
            });
    })
        .end()

        .find('.barData') // 'fullness' of bars, widths are percentages of bar wrappers
        .css({
        'height': 'inherit'
    })

        .filter('.barWP') // possibly I should be doing this with an array-like object instead?
        .css({
        'background': 'linear-gradient(red, darkred)'
    })
        .end()

        .filter('.barMP')
        .css({
        'background': 'linear-gradient(blue, darkblue)'
    })
        .end()

        .filter('.barAP')
        .css({
        'background': 'linear-gradient(orange, darkorange)'
    })
        .end()

        .end()

        .end()

        .find('.avatarXPAmt')
        .css({
        'background': 'linear-gradient(violet, purple)'
    })
        .wrap('<a href="/PvP/MyPerks"></a>') // inherits size from .avatarXPWrapper
    ;

    $innerRows.eq(1) // second row, with now-empty movegrid column and room description

        .find('.col-md-4') // former movegrid column
        .remove()
        .end()

        .find('.covenDescription') // room description and title
        .removeClass('col-md-8');

    if (covSafeground) {

        $covController

            .css({
            'color': '#a13d2d'
        })

            .contents()

            .first() // The text that says 'Enchanted by the '
            .replaceWith('SAFEGROUND for the ');
    }

    // Finally update the real site with the changes
    $('head').after($tftSite);

    // console.log(Date.now() - startTime);

});