Scroll Buttons

Adds scroll buttons to Dead Frontier Marketplace which lets you quickly jump up or down to the next item with different name. Scrolling happens from the top item perspective - it logs the first visible item inside the marketplace list and scrolls until the top item becomes something else, based on different item name or different data-type.

// ==UserScript==
// @name         Scroll Buttons
// @namespace    tampermonkey.net/
// @version      1.0
// @description  Adds scroll buttons to Dead Frontier Marketplace which lets you quickly jump up or down to the next item with different name. Scrolling happens from the top item perspective - it logs the first visible item inside the marketplace list and scrolls until the top item becomes something else, based on different item name or different data-type.
// @author       Lucky11
// @match        https://fairview.deadfrontier.com/onlinezombiemmo/index.php?page=35
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';
    const ignoreBulletCount = false;//if it's set to true and Ignore_Colour_Stats set to false then while scrolling it will ignore bullet count next to the bullets name
    const ignore_GC_MC_tags = false;//if it's set to true and Ignore_Colour_Stats set to false then while scrolling it will ignore (GC) (MC) tags next to the gun names

    const Ignore_Colour_Stats = true;//if set to true it ignores whatever ignoreBulletCount or ignore_GC_MC_tags is set to. Then it skips acts as if both of them are set to true plus ignoring different colours. It only doesn't ignore and stops if item is renamed with different name.
    const smoothScroll = true;//set to false to disable scroll animation
    // Toggle controls     //KEYBOARD CONTROL
    const animation_when_keyboard_scroll = true; // Set to false to disable button color changing animation
    const disableArrowKeys = false; // Set to true to disable scrolling with UP and DOWN arrow keys
    const disableWSKeys = true; // Set to true to disable scrolling with W and S keys
    function Main() {
        // Check if the button already exists to avoid duplicates
        if (document.getElementById('jumpDownButton')) return;

        // Create buttons
        const logButton = document.createElement('button');
        logButton.id = 'logButton'; // Set an ID for the button
        logButton.innerText = 'Log First Visible Item';
        logButton.style.margin = '10px';
        logButton.style.padding = '5px 10px';
        logButton.style.cursor = 'pointer';

        const jumpDownButton = document.createElement('button');
        jumpDownButton.id = 'jumpDownButton'; // Set an ID for the button
        jumpDownButton.style.top = '272px'; // Move the button lower by 150px
        jumpDownButton.style.left = '667px';
        jumpDownButton.style.padding = '0'; // Remove padding for a cleaner look
        jumpDownButton.style.cursor = 'pointer';
        jumpDownButton.style.position = 'absolute';
        jumpDownButton.style.height = '138px'; // Adjust height as needed
        jumpDownButton.style.width = '18px'; // Adjust width as needed
        //jumpDownButton.style.border = 'none';
        jumpDownButton.style.border = '1px solid #990000'; // Border with color
        jumpDownButton.style.backgroundColor = 'rgba(153, 0, 0, 0.3)';//For semi-transparent fill:
        //jumpDownButton.style.backgroundColor = 'transparent'; // No fill color
        //jumpDownButton.style.backgroundColor = '#007BFF'; // Button color
        //jumpDownButton.style.borderRadius = '5px'; // Rounded corners

        // Create the arrow Down element
        const arrowdown = document.createElement('div');
        arrowdown.style.position = 'absolute';
        arrowdown.style.top = '50%'; // Center the arrow vertically
        arrowdown.style.left = '50%'; // Center the arrow horizontally
        arrowdown.style.transform = 'translate(-50%, -50%)'; // Center the arrow
        arrowdown.style.width = '0';
        arrowdown.style.height = '0';
        arrowdown.style.borderLeft = '5px solid transparent';
        arrowdown.style.borderRight = '5px solid transparent';
        arrowdown.style.borderTop = '10px solid white'; // Arrow color

        // Append the arrow Down to the button
        jumpDownButton.appendChild(arrowdown);

        const jumpUpButton = document.createElement('button');
        jumpUpButton.id = 'jumpUpButton'; // Set an ID for the button
        jumpUpButton.style.margin = '130px';
        jumpUpButton.style.left = '537px';
        jumpUpButton.style.padding = '0'; // Remove padding for a cleaner look
        jumpUpButton.style.cursor = 'pointer';
        jumpUpButton.style.position = 'absolute';
        jumpUpButton.style.height = '138px'; // Adjust height as needed
        jumpUpButton.style.width = '18px'; // Adjust width as needed
        //jumpUpButton.style.border = 'none';
        jumpUpButton.style.border = '1px solid #990000'; // Border with color
        jumpUpButton.style.backgroundColor = 'rgba(153, 0, 0, 0.3)';//For semi-transparent fill:
        //jumpUpButton.style.borderRadius = '5px'; // Rounded corners

        // Create the arrow Up element
        const arrow = document.createElement('div');
        arrow.style.position = 'absolute';
        arrow.style.top = '50%'; // Center the arrow vertically
        arrow.style.left = '50%'; // Center the arrow horizontally
        arrow.style.transform = 'translate(-50%, -50%)'; // Center the arrow
        arrow.style.width = '0';
        arrow.style.height = '0';
        arrow.style.borderLeft = '5px solid transparent';
        arrow.style.borderRight = '5px solid transparent';
        arrow.style.borderBottom = '10px solid white'; // Arrow color

        // Append the arrow Up to the button
        jumpUpButton.appendChild(arrow);

        // Append buttons to the invController div
        const invController = document.getElementById('invController');
        //invController.appendChild(logButton); //enable only for developing - logs in console the top visible item info ect.
        invController.appendChild(jumpDownButton);
        invController.appendChild(jumpUpButton);


        //KEYBOARD CONTROL
        // Track key press states to prevent multiple triggers on hold
        const keyStates = {
            ArrowUp: false,
            ArrowDown: false,
            W: false,
            S: false
        };
        let isAnimatingButton = false;

        // Function to animate button style
        function animateButton(button) {
            if (!animation_when_keyboard_scroll || isAnimatingButton) return;
            isAnimatingButton = true; // Lock
            //button.disabled = true; // Disable button
            // Save original styles
            const originalBorderColor = button.style.borderColor || '';
            const originalBackgroundColor = button.style.backgroundColor || '';

            // Change styles to indicate activation
            button.style.borderColor = '#FFCC00'; // Bright border
            button.style.backgroundColor = 'rgba(204, 100, 0, 0.5)'; // Less transparent

            // Revert after 200ms
            setTimeout(() => {
                button.style.borderColor = originalBorderColor;
                button.style.backgroundColor = originalBackgroundColor;
                //button.disabled = false; // Re-enable button
                isAnimatingButton = false; // Unlock
            }, 100);
        }
        // Function to handle keydown events
        function handleKeyDown(e) {
            // Prevent default if needed
            if (disableArrowKeys && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) {
                e.preventDefault();
            }

            // Handle arrow keys with one activation per press
            if (e.key === 'ArrowUp') {
                if (!keyStates.ArrowUp) {
                    keyStates.ArrowUp = true;
                    //console.log('Arrow Up pressed');
                    jumpUp();
                }
                e.preventDefault();
            } else if (e.key === 'ArrowDown') {
                if (!keyStates.ArrowDown) {
                    keyStates.ArrowDown = true;
                    //console.log('Arrow Down pressed');
                    jumpDown();
                }
                e.preventDefault();
            } else if (!disableWSKeys) {
                // W/S keys as Up/Down
                if (e.key === 'w' || e.key === 'W') {
                    if (!keyStates.W) {
                        keyStates.W = true;
                        //console.log('W pressed (treated as Arrow Up)');
                        jumpUp();
                    }
                } else if (e.key === 's' || e.key === 'S') {
                    if (!keyStates.S) {
                        keyStates.S = true;
                        //console.log('S pressed (treated as Arrow Down)');
                        jumpDown();
                    }
                }
            }
        }

        function handleKeyUp(e) {
            // Reset key states
            if (e.key === 'ArrowUp') {
                keyStates.ArrowUp = false;
                //console.log('Arrow Up released');
            } else if (e.key === 'ArrowDown') {
                keyStates.ArrowDown = false;
                //console.log('Arrow Down released');
            } else if (!disableWSKeys) {
                if (e.key === 'w' || e.key === 'W') {
                    keyStates.W = false;
                    console.log('W released');
                } else if (e.key === 's' || e.key === 'S') {
                    keyStates.S = false;
                    console.log('S released');
                }
            }
        }

        // Attach event listeners
        window.addEventListener('keydown', handleKeyDown);
        window.addEventListener('keyup', handleKeyUp);
        //KEYBOARD CONTROL

        function cleanItemNameAmmo(itemName) {
            const lastParenIndex = itemName.lastIndexOf(' (');
            const closingParenIndex = itemName.indexOf(')', lastParenIndex);

            if (lastParenIndex !== -1 && closingParenIndex !== -1) {
                const countInParentheses = itemName.substring(lastParenIndex + 2, closingParenIndex).trim();
                if (/^\d+$/.test(countInParentheses)) {
                    return itemName.substring(0, lastParenIndex).trim();
                }
            }
            return itemName;
        }
        function cleanItemNameTags(itemName) {
            const tagsToRemove = ['(MC)', '(GC)', '(HC)', '(NGC)','(AC)'];
            for (const tag of tagsToRemove) {
                const tagIndex = itemName.lastIndexOf(tag);
                if (tagIndex !== -1) {
                    return itemName.substring(0, tagIndex).trim();
                }
            }
            return itemName;
        }

        function getStandardizedItemName(item) {
            let itemName = item.innerText.split('\n')[0].trim();

            if (Ignore_Colour_Stats) {
                const dataType = item.getAttribute('data-type');
                if (dataType) {
                    if (dataType.includes('_name')) {
                        // itemName remains unchanged
                    } else {
                        const dataTypeParts = dataType.split('_');
                        if (dataTypeParts[dataTypeParts.length - 1] === 'cooked') {
                            itemName = dataTypeParts.join('_');
                        } else {
                            itemName = dataTypeParts[0];
                            if (itemName === 'credits' && item.innerText) {
                                itemName = item.innerText.split('\n')[0].trim();
                            }
                        }
                    }
                } else {
                    //console.log('data-type attribute is null or undefined for item:', item);
                    itemName = item.innerText.split('\n')[0].trim();
                }
            }
            if (ignoreBulletCount && !Ignore_Colour_Stats) {
                itemName = cleanItemNameAmmo(itemName);
            }
            if (ignore_GC_MC_tags && !Ignore_Colour_Stats) {
                itemName = cleanItemNameTags(itemName);
            }
            //if services then return level not name and data-type is null
            const levelElement = item.querySelector('.level'); // Select the level element
            const dataLevel = levelElement ? levelElement.textContent : null; // Get the text content
            if (dataLevel) {
                itemName = dataLevel;
            }
            return itemName;
        }

        // Function to log the first visible item in the itemDisplay div
        logButton.addEventListener('click', () => {
            const itemDisplay = document.getElementById('itemDisplay');
            const items = itemDisplay.children;

            let firstVisibleItem = null;
            let lastVisibleItem = null;

            for (let i = 0; i < items.length; i++) {
                const item = items[i];
                const rect = item.getBoundingClientRect();
                const displayRect = itemDisplay.getBoundingClientRect();

                if (rect.top >= displayRect.top && rect.bottom <= displayRect.bottom) {
                    firstVisibleItem = item;
                    break;
                }
            }
            // If the first visible item is found, look for the last visible item
            if (firstVisibleItem) {
                const firstVisibleIndex = Array.from(items).indexOf(firstVisibleItem);
                const lastVisibleIndex = Math.min(firstVisibleIndex + 13, items.length - 1); // Limit to 13 items below

                // Find the last visible item within the specified range
                for (let i = firstVisibleIndex + 1; i <= lastVisibleIndex; i++) {
                    const item = items[i];
                    const rect = item.getBoundingClientRect();
                    const displayRect = itemDisplay.getBoundingClientRect();

                    if (rect.top >= displayRect.top && rect.bottom <= displayRect.bottom) {
                        lastVisibleItem = item; // Update last visible item
                    }
                }

                console.log('First Visible Item:', firstVisibleItem.innerText);
                const dataType = firstVisibleItem.getAttribute('data-type');
                console.log('Data Type:', dataType);

                console.log('Last Visible Item:', lastVisibleItem.innerText);
                const dataTypeLast = lastVisibleItem.getAttribute('data-type');
                console.log('Data Type of Last Visible Item:', dataTypeLast);

                // Get all child elements inside itemDisplay
                const items123 = itemDisplay.children;
                // Check if there are any child elements
                if (items123.length > 0) {
                    // Get the last child element
                    const lastItem123 = items123[items123.length - 1];
                    // Log the inner text of the last item
                    console.log('Inner Text:', lastItem123.innerText);
                    // Log the data-type attribute of the last item
                    console.log('Data-type:', lastItem123.getAttribute('data-type'));
                } else {
                    console.log('No items found inside itemDisplay.');
                }

                if (ignoreBulletCount) {
                    let itemName = getStandardizedItemName(firstVisibleItem);
                    console.log(itemName);
                }
            } else {
                console.log('No items currently visible.');
            }
        });
        function smoothScrollTo(element, targetTop) {
            return new Promise((resolve) => {
                const startTop = element.scrollTop;
                const distance = targetTop - startTop;
                const duration = 200; // duration in ms
                const startTime = performance.now();

                function scroll() {
                    const now = performance.now();
                    const elapsed = now - startTime;
                    const progress = Math.min(elapsed / duration, 1);
                    element.scrollTop = startTop + distance * progress;
                    if (progress < 1) {
                        requestAnimationFrame(scroll);
                    } else {
                        resolve();
                    }
                }
                requestAnimationFrame(scroll);
            });
        }

        let isScrolling = false;
        async function jumpDown() {
            if (isScrolling) return;
            isScrolling = true;
            jumpDownButton.disabled = true; // Disable button
            // Animate button if enabled
            animateButton(jumpDownButton);
            const itemDisplay = document.getElementById('itemDisplay');
            //const items = itemDisplay.children;
            let items;
            try {
                items = itemDisplay.children;
            } catch (error) {
                console.error('An error occurred while accessing itemDisplay.children:', error);
                isScrolling = false; // Reset scrolling state if no items are found
                jumpDownButton.disabled = false; // Re-enable button
                return; // Exit the function if an error occurs
            }
            let firstVisibleItem = null;
            let firstVisibleItemName = '';

            // Find the first visible item
            for (let i = 0; i < items.length; i++) {
                const item = items[i];
                const rect = item.getBoundingClientRect();
                const displayRect = itemDisplay.getBoundingClientRect();

                if (rect.top >= displayRect.top && rect.bottom <= displayRect.bottom) {
                    firstVisibleItem = item;
                    firstVisibleItemName = getStandardizedItemName(firstVisibleItem);
                    break;
                }
            }

            if (firstVisibleItem) {
                let foundFirstVisible = false;
                for (let i = 0; i < items.length; i++) {
                    const item = items[i];
                    const itemName = getStandardizedItemName(item);
                    if (foundFirstVisible) {
                        if (itemName !== firstVisibleItemName) {
                            const itemTop = item.offsetTop;

                            // Check if we are at the bottom of the itemDisplay
                            const isAtBottom = itemDisplay.scrollTop + itemDisplay.clientHeight >= itemDisplay.scrollHeight - 1;

                            if (isAtBottom) {
                                console.log("Reached the bottom, stopping scroll.");
                                isScrolling = false; // Reset scrolling state
                                jumpDownButton.disabled = false; // Re-enable button
                                return; // Exit if at the bottom
                            }

                            if (smoothScroll) {
                                // Use smoothScrollTo for smooth scrolling
                                await smoothScrollTo(itemDisplay, itemTop); // Await the smooth scroll
                                isScrolling = false; // Reset scrolling state after completion
                                jumpDownButton.disabled = false; // Re-enable button
                            } else {
                                itemDisplay.scrollTop = itemTop; // Set scroll position directly
                                isScrolling = false; // Reset scrolling state
                                jumpDownButton.disabled = false; // Re-enable button
                            }
                            break;
                        }
                    }

                    if (item === firstVisibleItem) {
                        foundFirstVisible = true;
                    }
                }
            } else {
                console.log('No items currently visible to jump down from.');
                isScrolling = false; // Reset scrolling state if no items are found
                jumpDownButton.disabled = false; // Re-enable button
            }
            isScrolling = false; // Reset scrolling state if no items are found
            jumpDownButton.disabled = false; // Re-enable button
        }

        // Function to jump up to the next item above with a different name
        async function jumpUp() {
            if (isScrolling) return;
            isScrolling = true;
            jumpUpButton.disabled = true; // Disable button
            // Animate button if enabled
            animateButton(jumpUpButton);
            const itemDisplay = document.getElementById('itemDisplay');
            //const items = itemDisplay.children;
            let items;
            try {
                items = itemDisplay.children;
            } catch (error) {
                console.error('An error occurred while accessing itemDisplay.children:', error);
                isScrolling = false;
                jumpUpButton.disabled = false; // Disable button
                return; // Exit the function if an error occurs
            }
            let firstVisibleItem = null;
            let firstVisibleItemName = '';

            for (let i = 0; i < items.length; i++) {
                const item = items[i];
                const rect = item.getBoundingClientRect();
                const displayRect = itemDisplay.getBoundingClientRect();

                if (rect.top >= displayRect.top && rect.bottom <= displayRect.bottom) {
                    firstVisibleItem = item;
                    firstVisibleItemName = getStandardizedItemName(firstVisibleItem);
                    break;
                }
            }

            if (firstVisibleItem) {
                for (let i = Array.from(items).indexOf(firstVisibleItem) - 1; i >= 0; i--) {
                    const item = items[i];
                    const itemName = getStandardizedItemName(item);

                    if (itemName !== firstVisibleItemName) {
                        let foundIndex = -1;
                        for (let j = 0; j < items.length; j++) {
                            if (getStandardizedItemName(items[j]) === itemName) {
                                foundIndex = j;
                                break;
                            }
                        }
                        if (foundIndex !== -1) {
                            const targetTop = items[foundIndex].offsetTop;
                            if (smoothScroll) {
                                await smoothScrollTo(itemDisplay, targetTop);
                            }else{
                                const itemTop = items[foundIndex].offsetTop; // Get the top position of the item
                                itemDisplay.scrollTop = itemTop; // Set the scroll position directly
                            }
                            isScrolling = false;
                            jumpUpButton.disabled = false; // Disable button
                            return;
                        }
                        //return;
                    }
                    if (Array.from(items).indexOf(item) === 0) {
                        if (smoothScroll) {
                            await smoothScrollTo(itemDisplay, items[0]);
                        }else{
                            const itemTop = items[0].offsetTop; // Get the top position of the item
                            itemDisplay.scrollTop = itemTop; // Set the scroll position directly
                        }
                        isScrolling = false;
                        jumpUpButton.disabled = false; // Disable button
                        return;
                    }
                }
            } else {
                console.log('No items currently visible to jump up from.');
            }
            isScrolling = false;
            jumpUpButton.disabled = false; // Disable button
        }
        // button event listeners
        jumpDownButton.addEventListener('click', jumpDown);
        jumpUpButton.addEventListener('click', jumpUp);
    }
    //AddButtons();

    /*
     * Part of the Code below of this script is derived from the "Dead Frontier - Fast Services" script by Shrike00
     * Original source: https://update.greasyfork.org/scripts/472536/Dead%20Frontier%20-%20Fast%20Services.user.js
     * Copyright (c) Shrike00
     * Licensed under the MIT License.
     */
    // Initial button creation and observer setup
    function waitForItemDisplay(callback, timeout) {
        const start = performance.now();
        const check = setInterval(function() {
            if (document.getElementById("itemDisplay") !== null) {
                clearInterval(check);
                callback();
            } else if (performance.now() - start > timeout) {
                clearInterval(check);
            }
        }, 100);
    }
    function Start() {
        waitForItemDisplay(function() {
            // Initial addition of button.
            Main();
            // Mutation observer to check for changes to the marketplace child nodes, re-adding the event listener
            // whenever the selectMarket element is re-added (which happens when the market tab is changed).
            const callback = function(mutationList, observer) {
                for (const record of mutationList) {
                    const element = record.addedNodes[0];
                    if (element !== undefined && element.id === "selectMarket") {
                        //createSortButton();
                        checkAndCreateSortButton();
                    }
                }
            }
            const observer = new MutationObserver(callback);
            const marketplace = document.getElementById("marketplace");
            observer.observe(marketplace, {childList: true});
        }, 5000);
    }
    function removeButtons() {
        const logButton = document.getElementById('logButton');
        const jumpDownButton = document.getElementById('jumpDownButton');
        const jumpUpButton = document.getElementById('jumpUpButton');

        if (logButton) {
            logButton.remove(); // Remove the log button
        }
        if (jumpDownButton) {
            jumpDownButton.remove(); // Remove the jump down button
        }
        if (jumpUpButton) {
            jumpUpButton.remove(); // Remove the jump up button
        }
    }
    function checkAndCreateSortButton() {
        const pageLogo = document.getElementById("pageLogo");
        const marketType = pageLogo ? pageLogo.getAttribute("data-market-type") : null;
        const innerText = pageLogo ? pageLogo.innerText.trim() : "";
        //console.log(innerText);
        //console.log(marketType);

        if (marketType === "buy" || innerText === "ITEM-FOR-ITEM") {
            //console.log("added buttons");
            Main();
        } else {//if not buy or item for item section of marketplace then remove buttons.
            //console.log("Removing buttons");
            removeButtons(); // Call the function to remove buttons
        }
    }
    Start();
})();