Jump to content

Community Scripts [Links in OP]


DvDivXXX
 Share

Recommended Posts

Some results of Booster Simulator in different situations:
Shield vs. Shield (Superior opponent)

Spoiler

SvS3.thumb.jpg.9bdd1dc4d7bc3f56eb934c60b07b255b.jpg

Shield vs. Reflect (Superior opponent)

Spoiler

SvR3.thumb.jpg.6b768d57894f670bcc1c9a45c9dce2fa.jpg

Shield vs. Stun (Superior opponent)

Spoiler

SvSt3.thumb.jpg.abdc65dba09040bd762ee919a75eb5ff.jpg

Shield vs. Execute (Superior enemy)

Spoiler

SvE3.thumb.jpg.893d88200969ae81896ceef70c1593a9.jpg

Shield vs. Shield (Equal Opponent)

Spoiler

SvS2.thumb.jpg.fde4beac172993ce8216ac293b518bf5.jpg

Shield vs. Reflect (Equal Opponent)

Spoiler

 

Shield vs. Stun (Equal Opponent)

Spoiler

SvSt2.thumb.jpg.a4cd694e579a51e6d22c146f0c3b3bc9.jpg

Shield vs. Execute (Equal Opponent)

Spoiler

SvE2.thumb.jpg.6c1b3380eca4000811149c28173a8b13.jpg

Shield vs. Shield (Weaker opponent)

Spoiler

SvS1.thumb.jpg.4524a761863b1a4fdba1febc847051fd.jpg

Shield vs. Reflect (Weaker opponent)

Spoiler

SvR1.thumb.jpg.6adcb59ad3c69d908a088a25e61a0e54.jpg

Shield vs. Stun (Weaker opponent)

Spoiler

SvSt1.thumb.jpg.2980c5d2c1bfd6fad23538cd65e2cc17.jpg

Shield vs. Execute (Weaker opponent)

Spoiler

SvE1.thumb.jpg.3901ea703b909dfd6188925c6aff1cdb.jpg

P.S. Some results are not yet available because there are no suitable opponents. Available results are highlighted in bold

Edited by Master-17
Added new results
  • Like 1
  • Thanks 4
Link to comment
Share on other sites

IMPORTANT UPDATE!!!

Updated HH++ BDSM to v1.38.9. Had to remove the auto-refresh module,

image.png?ex=654a2c58&is=6537b758&hm=8a8a3a0a01e8a6b834b925d81f3774bae714dceb28c39b0f2e9d07fab0c5b596&

as I found out it was causing some players to be flagged as cheaters and could have resulted in a ban. I talked with Sandman and he said the module should have been fine if you were just on a single tab, but it could trigger their cheat detection otherwise. So while auto-refreshing is technically allowed I'm going to be on the safe side and remove it and I advise anyone else to not use any similar tools. If you think you have been flagged as a cheater or banned because of this module please tell me and I'll work with Sandman to reverse any flag/ban caused by this false positive. You'll know if you've been flagged as a cheater if you can't see yourself on any of the leaderboards.

  • Like 5
  • Thanks 4
Link to comment
Share on other sites

  • Moderator

Just discovered the booster simulator update.  Thanks @renalove!

Here's a few sims vs. 3 reflect opponents.  I'm only using 1 M6 shield user, and 6 other unskilled M6s this week.

The last reflect user doesn't have the skill maxed out, and in that case, 3 cords and 1 chlorella was suggested, instead of 3 chlorella and 1 cord.  Most likely b/c I'd do less dmg to myself, which makes sense.

Spoiler

2023-10-25_18-20-19.png2023-10-25_18-19-34.png2023-10-25_18-23-38.png

Shield vs Shield is the other sim that I find helpful, since it's the most common tier 5 skill atm.  I'm kinda disappointed that 4 chlorella isn't effective, but I guess I'll wait to attack those players w/ cords.  Might earn an extra 12 points per shield user.  14.91 (4 chlo + LM) vs. 18.93 (4 cords + LM).

Spoiler

image.png

4 Chlorella suffices for the majority of clueless players, but this will be helpful vs. stronger tier 5 users.  Arigato!

  • Like 2
Link to comment
Share on other sites

 

I made few change to the script, here is the updated version and what has changed:
  • [new] Guesstimate the minimum number of lost points per user. For this to work the league data gather module needs to be enabled (it is per default). The number of lost points is shown when hovering over the points for a given opponent. The estimation will only start when you install this updated version. Previous data will not be taken into account due to a previous bug in the league data collection module. The lost points are computed by comparing the points for every opponent between two league page reloads - as such it is beneficial to reload the league page often (but this will fill up your browser's local storage, so dont overdo it). The data is not synchronized between devices. The estimated value is the minimum number of lost points, but not necessarily the real value. For example if a player has gained 25 points since the last league table refresh, the script will assume that the player has not lost any points, while this might not be the case (e.g. by achieving 10, 10, 5 pts).
  • Show the number of invested light bulbs for every player in the league table. Highlight the number if the player has put in even a single point in the fifth skill for the first girl. The number is not really correct, as the game does not report the amount of points put into the first skill, but a good approximation. The column is not sortable. Can be disabled via settings.
  • Optionally show the girl power and the new "aggregated" power in the league table. Disabled per default. Can be enabled via settings. Not sortable.
  • Load the opponents' profile data when selecting them in the table - this will display their best D3 results in the league table next to the player's name. Configurable from the settings.

  • A check on the pre-battle page whether you have a suitable not-equipped mythic equipment in your inventory. It will show a small mythic equipment icon next to your opponents class. Works with and without League++, but it might display stale data when League++ is on due to the issue above.

  • Module for gathering league data. Whenever the league standings change a new snapshot will be stored. Afterwards you can export all stored snapshots as csv file.

  • Additional harem filter(s): filter for Legendary Days girls ('filter by' dropdown in the native harem)

 

Spoiler
// ==UserScript==
// @name            Hentai Heroes+++
// @description     Few QoL improvement and additions for competitive PvP players.
// @version         0.18.11
// @match           https://*.hentaiheroes.com/*
// @match           https://nutaku.haremheroes.com/*
// @run-at          document-body
// @grant           none
// @author          430i
// ==/UserScript==


const {$, location, localStorage: storage} = window

// localStorage keys
const LS_CONFIG_NAME = 'HHPlusPlusPlus'
const LEAGUE_BASE_KEY = LS_CONFIG_NAME + ".League";
const LEAGUE_SNAPSHOT_BASE_KEY = LEAGUE_BASE_KEY + ".Snapshot";
const CURRENT_LEAGUE_SNAPSHOT_KEY = LEAGUE_SNAPSHOT_BASE_KEY + ".Current";
const PREVIOUS_LEAGUE_SNAPSHOT_KEY = LEAGUE_SNAPSHOT_BASE_KEY + ".Previous";
const LEAGUE_PLAYERS_KEY = LEAGUE_BASE_KEY + ".Players";
const EQUIPMENT_KEY = LS_CONFIG_NAME + ".Equipment";
const EQUIPMENT_CURRENT_KEY = EQUIPMENT_KEY + ".Current";
const EQUIPMENT_BEST_MYTHIC_KEY = EQUIPMENT_KEY + ".Mythic";

// 3rd party localStorage keys
const LS_CONFIG_HHPLUSPLUS_NAME = 'HHPlusPlus'
const HHPLUSPLUS_OPPONENT_FILTER = LS_CONFIG_HHPLUSPLUS_NAME + "OpponentFilter"

// icon paths
const PATH_GROUPS = '<path d="M4,13c1.1,0,2-0.9,2-2c0-1.1-0.9-2-2-2s-2,0.9-2,2C2,12.1,2.9,13,4,13z M5.13,14.1C4.76,14.04,4.39,14,4,14 c-0.99,0-1.93,0.21-2.78,0.58C0.48,14.9,0,15.62,0,16.43V18l4.5,0v-1.61C4.5,15.56,4.73,14.78,5.13,14.1z M20,13c1.1,0,2-0.9,2-2 c0-1.1-0.9-2-2-2s-2,0.9-2,2C18,12.1,18.9,13,20,13z M24,16.43c0-0.81-0.48-1.53-1.22-1.85C21.93,14.21,20.99,14,20,14 c-0.39,0-0.76,0.04-1.13,0.1c0.4,0.68,0.63,1.46,0.63,2.29V18l4.5,0V16.43z M16.24,13.65c-1.17-0.52-2.61-0.9-4.24-0.9 c-1.63,0-3.07,0.39-4.24,0.9C6.68,14.13,6,15.21,6,16.39V18h12v-1.61C18,15.21,17.32,14.13,16.24,13.65z M8.07,16 c0.09-0.23,0.13-0.39,0.91-0.69c0.97-0.38,1.99-0.56,3.02-0.56s2.05,0.18,3.02,0.56c0.77,0.3,0.81,0.46,0.91,0.69H8.07z M12,8 c0.55,0,1,0.45,1,1s-0.45,1-1,1s-1-0.45-1-1S11.45,8,12,8 M12,6c-1.66,0-3,1.34-3,3c0,1.66,1.34,3,3,3s3-1.34,3-3 C15,7.34,13.66,6,12,6L12,6z"/>';
const PATH_GROUP = '<path d="M9 13.75c-2.34 0-7 1.17-7 3.5V19h14v-1.75c0-2.33-4.66-3.5-7-3.5zM4.34 17c.84-.58 2.87-1.25 4.66-1.25s3.82.67 4.66 1.25H4.34zM9 12c1.93 0 3.5-1.57 3.5-3.5S10.93 5 9 5 5.5 6.57 5.5 8.5 7.07 12 9 12zm0-5c.83 0 1.5.67 1.5 1.5S9.83 10 9 10s-1.5-.67-1.5-1.5S8.17 7 9 7zm7.04 6.81c1.16.84 1.96 1.96 1.96 3.44V19h4v-1.75c0-2.02-3.5-3.17-5.96-3.44zM15 12c1.93 0 3.5-1.57 3.5-3.5S16.93 5 15 5c-.54 0-1.04.13-1.5.35.63.89 1 1.98 1 3.15s-.37 2.26-1 3.15c.46.22.96.35 1.5.35z"/>';
const PATH_CLEAR = '<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>';

class EquipmentCollector {
    static collect() {
        if (!HHPlusPlus.Helpers.isCurrentPage('shop')) {
            return;
        }

        HHPlusPlus.Helpers.defer(() => {
            setTimeout(() => {
                EquipmentCollector.collectEquipmentData();
            }, 250);

            HHPlusPlus.Helpers.onAjaxResponse(/action=market_equip_armor*/, EquipmentCollector.onEquipmentChange);
        });
    }

    static onEquipmentChange(data) {
        // We need to delay the execution a bit to give chance to the native callback to run.
        setTimeout(() => {
            // The is a bug in HH (quelle surprise) when swapping equipments - the native callbacks adds the
            // unequipped armor to the back of the inventory ('player_inventory'), but the equipped armor is not
            // removed, which leaves an inconsistent state, so we need to remove it ourselves.
            EquipmentCollector.removeEquippedArmorFromInventory(data.equipped_armor);
            EquipmentCollector.collectEquipmentData();
        }, 100);
    }

    static removeEquippedArmorFromInventory(equipped) {
        const idx = player_inventory.armor.findIndex(e => {
            return e.item.rarity == "mythic" &&
                   e.resonance_bonuses.class.bonus == equipped.resonance_bonuses.class.bonus &&
                   e.resonance_bonuses.class.identifier == equipped.resonance_bonuses.class.identifier &&
                   e.resonance_bonuses.class.resonance == equipped.resonance_bonuses.class.resonance &&
                   e.resonance_bonuses.theme.bonus == equipped.resonance_bonuses.theme.bonus &&
                   e.resonance_bonuses.theme.identifier == equipped.resonance_bonuses.theme.identifier &&
                   e.resonance_bonuses.theme.resonance == equipped.resonance_bonuses.theme.resonance &&
                   e.skin.id_item_skin === equipped.skin.id_item_skin &&
                   e.skin.id_skin_set === equipped.skin.id_skin_set &&
                   e.skin.identifier === equipped.skin.identifier &&
                   e.skin.name === equipped.skin.name &&
                   e.skin.subtype === equipped.skin.subtype;
        });

        // player_inventory.armor[idx] = data.unequipped_armor;
        player_inventory.armor.splice(idx, 1);
    }

    static collectEquipmentData() {
        EquipmentCollector.collectPlayerEquipment();
        EquipmentCollector.collectBestMythicEquipment();
    }

    static collectPlayerEquipment() {
        const eqElements = $("div#equiped.armor-container div.slot:not(:empty)[subtype!='0']");
        if (eqElements.length != 6) {
            console.log("Did not find 6 equipment elements.");
            return;
        }

        const equipment = eqElements.map(function() { return $(this).data("d")}).get();
        const equipmentStripped = equipment.map((e) => {
            return {
                id: e.id_member_armor_equipped || e.id_member_armor, // unique item identifier?
                rarity: e.item.rarity, // legendary, mythic
                type: e.item.type, // always "armor"
                skin_id: e.skin.identifier, // EH13, ET21 etc
                subtype: parseInt(e.skin.subtype), // 1, 2, 3, 4, 5 or 6
                carac1: parseInt(e.caracs.carac1),
                carac2: parseInt(e.caracs.carac2),
                carac3: parseInt(e.caracs.carac3),
                harmony: parseInt(e.caracs.chance),
                endurance: parseInt(e.caracs.endurance),
                bonuses: e.resonance_bonuses,
            };
        });

        window.localStorage.setItem(EQUIPMENT_CURRENT_KEY, JSON.stringify(equipmentStripped));
    }

    static collectBestMythicEquipment() {
        const equipment = player_inventory.armor
            .filter(a => a.item.rarity == "mythic")
            .filter(a => parseInt(a.resonance_bonuses.class.identifier) == Hero.infos.class)
            .filter(a => a.resonance_bonuses.class.resonance == "damage")
            .filter(a => a.resonance_bonuses.theme.resonance == "defense");

        window.localStorage.setItem(EQUIPMENT_BEST_MYTHIC_KEY, JSON.stringify(equipment));
    }

    static getCurrent() {
        return JSON.parse(window.localStorage.getItem(EQUIPMENT_CURRENT_KEY)) || [];
    }

    static getBestMythic() {
        return JSON.parse(window.localStorage.getItem(EQUIPMENT_BEST_MYTHIC_KEY)) || [];
    }
}

class LeaguePlayersCollector {
    static collect() {
        if (!HHPlusPlus.Helpers.isCurrentPage('tower-of-fame')) {
            return;
        }

        HHPlusPlus.Helpers.defer(() => {
            HHPlusPlus.Helpers.onAjaxResponse(/action=fetch_hero&id=profile/, LeaguePlayersCollector.collectPlayerPlacementsFromAjaxResponse);
            LeaguePlayersCollector.collectPlayerData();
        });
    }

    static collectPlayerPlacementsFromAjaxResponse(response, opt) {
        // If you are reading this, please look away, ugly code below
        // The mythic equipment data is actually not in the html, but in the form of a script that we have to eval
        const html = $("<div/>").html(response.html);
        $.globalEval(html.find('script').text()); // creates 'hero_items'

        const id = html.find("div.ranking_stats .id").text().match(/\d+/)[0];
        const username = html.find(".hero_info h3 .hero-name").text();
        const level = html.find('div[hero="level"]').text().trim();
        const number_mythic_equipment = Object.values(hero_items).filter(i => i.item.rarity == "mythic").length;
        const d3_placement = $("<div/>")
                            .html(html)
                            .find('div.history-independent-tier:has(img[src*="/9.png"]) span') // 9.png is D3
                            .map(function() {return parseInt($(this).text().trim().match(/\d+/));})
                            .get();

        if (!id || !username || !level) {
            window.popup_message("Error when parsing player data.");
            return;
        }

        if (!d3_placement || d3_placement.length != 2) {
            // make sure our parser is working by checking the D2 data
            const d2_placement = $("<div/>")
                                    .html(html)
                                    .find('div.history-independent-tier:has(img[src*="/8.png"]) span') // 8.png is D2
                                    .map(function() {return parseInt($(this).text().trim().match(/\d+/));})
                                    .get();

            if (d2_placement.length != 2) {
                window.popup_message("Error when parsing D2 player data.");
            }

            d3_placement.push(-1, 0);
        }

        const data = {
            id: parseInt(id),
            number_mythic_equipment,
            best_placement: d3_placement[0],
            placement_count: d3_placement[1],
        };

        LeaguePlayersCollector.storePlayerData(data);
        $(document).trigger('player:update-profile-data', {id: data.id})
    }

    static collectPlayerData() {
        for (var r = 0, n = window.opponents_list.length; r < n; r++) {
            const player = window.opponents_list[r];

            const girls = player.player.team.girls;
            const girl_levels = girls.map(g => g.level);
            const girl_levels_max = Math.max(...girl_levels);
            const girl_levels_total = girl_levels.reduce((a, b) => a + b, 0);
            const girl_levels_avg = Math.floor(girl_levels_total / girl_levels.length);

            const data = {
                id: parseInt(player.player.id_fighter),
                username: player.player.nickname,
                level: parseInt(player.player.level),
                damage: player.player.damage,
                defense: player.player.defense,
                harmony: player.player.chance,
                ego: player.player.remaining_ego,
                power: player.player.team.total_power,
                club_id: player.player.club?.id_club,
                club_name: `"${player.player.club?.name || ''}"`,
                girl_levels_avg,
                girl_levels_max,
            }

            LeaguePlayersCollector.storePlayerData(data);
        }
    }

    static storePlayerData(data) {
        const players = JSON.parse(storage.getItem(LEAGUE_PLAYERS_KEY)) || {};
        if (players[data.id] == undefined) {
            players[data.id] = {};
        }

        Object.assign(players[data.id], data);

        storage.setItem(LEAGUE_PLAYERS_KEY, JSON.stringify(players));
    }

    static export() {
        const columns = [
            "id",
            "username",
            "level",
            "damage",
            "defense",
            "harmony",
            "ego",
            "power",
            "club_id",
            "club_name",
            "girl_levels_max",
            "girl_levels_avg",
            "expected_points",
            "number_mythic_equipment",
            "best_placement",
            "placement_count",
        ]

        const players = JSON.parse(storage.getItem(LEAGUE_PLAYERS_KEY)) || {};
        const data = Object.values(players).map(player => columns.map(column => player[column]));

        console.log([columns].concat(data).map(t => t.join(",")).join("\n"));
    }

    static clear() {
        storage.removeItem(LEAGUE_PLAYERS_KEY);
    }
}

class MyModule {
    constructor ({name, configSchema}) {
        this.group = '430i'
        this.name = name
        this.configSchema = configSchema
        this.hasRun = false

        this.insertedRuleIndexes = []
        this.sheet = HHPlusPlus.Sheet.get()
    }

    insertRule (rule) {
        this.insertedRuleIndexes.push(this.sheet.insertRule(rule))
    }

    tearDown () {
        this.insertedRuleIndexes.sort((a, b) => b-a).forEach(index => {
            this.sheet.deleteRule(index)
        })

        this.insertedRuleIndexes = []
        this.hasRun = false
    }
}

class LeagueScoutModule extends MyModule {
    constructor () {
        const baseKey = 'leagueScout'
        const configSchema = {
            baseKey,
            default: true,
            label: `Gather information about league opponents`,
        }
        super({name: baseKey, configSchema})
    }

    shouldRun() {return HHPlusPlus.Helpers.isCurrentPage('tower-of-fame')}

    run () {
        if (this.hasRun || !this.shouldRun()) {return}

        $(document).on('league:rollover', () => {
            const data = JSON.parse(storage.getItem(CURRENT_LEAGUE_SNAPSHOT_KEY)) || [];

            storage.setItem(PREVIOUS_LEAGUE_SNAPSHOT_KEY, JSON.stringify(data));
            storage.removeItem(CURRENT_LEAGUE_SNAPSHOT_KEY);
        })

        HHPlusPlus.Helpers.defer(() => {
            // read and store data
            const playerData = this.readPlayerData();
            const snapshot = this.createSnapshot(playerData);
            this.storeSnapshot(snapshot);

            // create ui elements
            HHPlusPlus.Helpers.doWhenSelectorAvailable('.league_buttons_block', () => {
                const parent = $('div.league_buttons');
                this.createDownloadButton(parent, PREVIOUS_LEAGUE_SNAPSHOT_KEY, PATH_GROUPS);
                this.createClearButton(parent, PREVIOUS_LEAGUE_SNAPSHOT_KEY);
                this.createDownloadButton(parent, CURRENT_LEAGUE_SNAPSHOT_KEY, PATH_GROUP);
                this.createClearButton(parent, CURRENT_LEAGUE_SNAPSHOT_KEY);
            });
        });

        this.hasRun = true;
    }

    readPlayerData() {
        const data = [];

        for (var r = 0, n = window.opponents_list.length; r < n; r++) {
            const player = window.opponents_list[r];

            const id = parseInt(player.player.id_fighter);
            const rank = player.place;
            const name = player.player.nickname;
            const country = player.country;
            const level = parseInt(player.player.level);
            const points = parseInt(player.player_league_points);
            const elements = player.player.team.theme;

            const damage = player.player.damage;
            const defense = player.player.defense;
            const ego = player.player.remaining_ego;
            const chance = player.player.chance;

            // Take only the first two chars of the booster names, as those should be unique. Assume all are legendary.
            const boosters = player.boosters.filter(b => b.expiration > 0).map(b => b.item.name.slice(0, 2))

            // Create player snapshot and validate it.
            const entry = {id, name, rank, level, elements, points, country, damage, defense, ego, chance, boosters};
            if (Object.values(entry).some(x => x == undefined || (typeof x !== 'string' && !Array.isArray(x) && isNaN(x)))) {
                console.log('Some player data is missing, maybe the opponents_list data structure changed?');
                console.log(entry);
            }

            data.push(entry);
        }

        // Sort the parsed data by rank.
        data.sort((a, b) => a.rank > b.rank);

        return data;
    }

    createSnapshot(playerData) {
        const numPlayers = playerData.length;
        const currentDate = new Date(window.server_now_ts * 1000);
        const leagueEndDate = new Date(window.server_now_ts * 1000 + window.season_end_at * 1000);

        return {
            date: currentDate,
            league_end: leagueEndDate,
            num_players: numPlayers,
            player_data: playerData,
        }
    }

    storeSnapshot(snapshot) {
        const data = JSON.parse(storage.getItem(CURRENT_LEAGUE_SNAPSHOT_KEY)) || [];

        if (data.length && JSON.stringify(data[data.length - 1].player_data) === JSON.stringify(snapshot.player_data)) {
            return;
        }

        data.push(snapshot);
        storage.setItem(CURRENT_LEAGUE_SNAPSHOT_KEY, JSON.stringify(data));
    }

    createButton(id, path) {
        return `<svg id="${id}" class="blue_button_L" width="32" height="32" style="padding: 5px" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24" fill="#FFFFFF"><g><rect fill="none" height="24" width="24"/></g><g>${path}</g></svg>`
    }

    createDownloadButton(parent, what, icon) {
        if (!storage.getItem(what)) {
            return;
        }

        const friendlyId = what.toLowerCase().replaceAll(".", "-");
        const buttonId = `download-${friendlyId}`;

        const downloadButton = this.createButton(buttonId, icon);
        parent.append(downloadButton);

        $(document.body).on('click', `#${buttonId}`, () => {
            const data = JSON.parse(storage.getItem(what)) || [];

            const separator = ","
            const columns = ["date", "player_id", "player_name", "player_rank", "player_points"];
            const values = data.flatMap((e) => e.player_data.map((p) => [e.date, p.id, p.name, p.rank, p.points].join(separator)));

            let csvContent = `sep=${separator}\n` + columns.join(separator) + "\n" + values.join("\n");

            var element = document.createElement('a');
            element.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(csvContent));
            element.setAttribute('download', `${friendlyId}.csv`);

            element.style.display = 'none';
            document.body.appendChild(element);

            element.click();

            document.body.removeChild(element);
        });
    }

    createClearButton(parent, what) {
        if (!storage.getItem(what)) {
            return;
        }

        const friendlyId = what.toLowerCase().replaceAll(".", "-");
        const buttonId = `clear-${friendlyId}`;

        const clearButton = this.createButton(buttonId, PATH_CLEAR);
        parent.append(clearButton);

        $(document.body).on('click', `#${buttonId}`, () => {
            storage.removeItem(what);
        });
    }
}

class LeagueTableModule extends MyModule {
    constructor () {
        const baseKey = 'leagueTable'
        const configSchema = {
            baseKey,
            default: true,
            label: `Extend league table with additional opponents' information`,
            subSettings: [
                {
                    key: 'girl_power',
                    label: 'Show girl power in the league table',
                    default: false
                },
                {
                    key: 'kinkoid_power',
                    label: 'Show the new power stat in the league table',
                    default: false
                },
                {
                    key: 'number_of_bulbs',
                    label: 'Show the number of invested bulbs in the league table',
                    default: true
                },
                {
                    key: 'load_player_data',
                    label: 'Load player data on league table row click',
                    default: true
                },
            ],
        }
        super({name: baseKey, configSchema})

        this.all_new_columns = ['kinkoid_power', 'girl_power', 'number_of_bulbs'];
        this.anchor_column = 'power';
    }

    shouldRun() {return HHPlusPlus.Helpers.isCurrentPage('tower-of-fame')}

    run(config) {
        if (this.hasRun || !this.shouldRun()) {return}

        HHPlusPlus.Helpers.defer(() => {
            HHPlusPlus.Helpers.doWhenSelectorAvailable('.league_table', () => {
                this.extendLeagueDataModel();
                this.addPlayerSelectHandler(config);
                this.showPlayersPlacementBadge(config);
                this.showAdditionalTableHeaders(config);
                this.showAdditionalTableColumns(config);
                this.showMaxPointsTooltip();
                this.makeCompatibleWithLeaguePlusPlus();
            });

            $(document).on('player:update-profile-data', (event, data) => {
                this.extendLeagueDataModel();
                this.showPlayersPlacementBadge(config);
            });

            $(document).on('league:table-sorted', () => {
                this.showPlayersPlacementBadge(config);
                this.showAdditionalTableColumns(config);
                this.showMaxPointsTooltip(config);
                this.makeCompatibleWithLeaguePlusPlus();
            });
        });

        this.hasRun = true;
    }

    extendLeagueDataModel() {
        const players_data = JSON.parse(storage.getItem(LEAGUE_PLAYERS_KEY)) || {};

        // add power to the existing `opponents_list` data model
        for (var r = 0, n = opponents_list.length; r < n; r++) {
            const player = opponents_list[r];
            const id = parseInt(player.player.id_fighter);

            const player_data = players_data[id];
            const best_placement = player_data != undefined ? player_data.best_placement : -1;
            const placement_count = player_data != undefined ? player_data.placement_count : -1;

            player.best_placement = best_placement;
            player.placement_count = placement_count;

            player.kinkoid_power = number_reduce(player.player.team.power_display);
            player.girl_power = player.player.team.total_power.toFixed();
            player.number_of_bulbs = player.player.team.girls.flatMap(g => Object.values(g.skill_tiers_info)).reduce((a,g)=>a+g.skill_points_used, 0)
        }
    }

    showAdditionalTableHeaders(config) {
        // Additional CSS classes
        const row_styles = {kinkoid_power: '2rem', girl_power: '2.2rem', number_of_bulbs: '0.9rem'}
        for (const [clazz, min_width] of Object.entries(row_styles)) {
            // this.insertRule(`.league_table .data-row .head-column[column="${clazz}"] {display: flex; align-items: center; justify-content: center}`);
            this.insertRule(`.league_table .data-row .data-column[column="${clazz}"] {min-width: ${min_width}}`);
        }

        const columns = this.all_new_columns.filter(c => config[c]);

        const headers = {
            kinkoid_power: `<span>${GT.design.caracs_sum}</span>`,
            girl_power: `<span>${GT.design.total_power}</span>`, // <span class="upDownArrows_mix_icn">
            number_of_bulbs: '<span class="scrolls_legendary_icn"></span>',
        }

        $(`div.league_table div.head-row div.head-column[column=${this.anchor_column}]`).after(
            columns.map(c => `<div class="data-column head-column" column="${c}">${headers[c]}</div>`).join('')
        );

    }

    showAdditionalTableColumns(config) {
        this.insertRule(`.active_skill {color: red; text-shadow: 1px 1px 0 #000, -1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000;}`);

        const context = this;

        $('div.league_table')
            .find('div.body-row')
            .each(function(index) {
                const opponent = window.opponents_list[index];
                const columns = context.all_new_columns.filter(c => config[c]);

                $(this).find(`div.data-column[column=${context.anchor_column}]`).after(
                    columns.map(c => `<div class="data-column" column="${c}"><div class="${context.tableColumnClass(c, opponent)}">${opponent[c]}</div></div>`).join('')
                );
            })
    }

    tableColumnClass(column, opponent) {
        if (!column.includes('bulb')) {
            return '';
        }

        const clazz = 'active_skill'; // or active_skills_icn

        const active_skills = opponent.player.team.girls[0].skill_tiers_info[5];
        return active_skills && active_skills.skill_points_used > 0 ? clazz : '';
    }

    showMaxPointsTooltip() {
        const data = JSON.parse(storage.getItem(CURRENT_LEAGUE_SNAPSHOT_KEY)) || [];

        if (data.length == 0) {
            return;
        }

        $('.league_table .body-row').each(function (idx) {
            const opponent = opponents_list[idx];
            const opponent_id = parseInt(opponent.player.id_fighter);

            const points = data.map(d => d.player_data.find(p => p.id == opponent_id).points).reverse();

            var lost_points = 0;

            for (let i = 0; i < points.length - 1; i+=1) {
                const diff = points[i] - points[i+1];
                const remainder = diff % 25;
                lost_points += (remainder == 0 ? 0 : 25 - remainder);
            }

            const element = $(this).find('.data-column[column=player_league_points]');
            element.attr('tooltip', `Lost points: ${lost_points}`);
        });
    }

    showPlayersPlacementBadge(config) {
        if (!config.load_player_data) {
            return;
        }

        // Additional CSS classes
        this.insertRule(`.scriptLeagueInfoIcon.top1 {background-color:#ec0039}`);
        this.insertRule('.scriptLeagueInfoIcon.top1::after {content:"1"}');

        const context = this;

        $('.league_table .body-row').each(function (idx) {
            const opponent = opponents_list[idx];
            if (opponent.best_placement != undefined) {
                context.updatePlayerPlacementBadge($(this), opponent);
            }
        });
    }

    addPlayerSelectHandler(config) {
        if (!config.load_player_data) {
            return;
        }

        // Remove the go_pre_battle class to allow users to select the row (inspired by Leagues++)
        $('.league_table .data-column[column=can_fight] .go_pre_battle').removeClass('go_pre_battle');

        $('.league_table .body-row').on('click', function() {
            const element_nickname = $(this).find('.data-column[column=nickname] span.nickname')
            const player_id = element_nickname.attr('id-member');

            const opponent = window.opponents_list.find(x => parseInt(x.player.id_fighter) == player_id);
            if (opponent.best_placement != undefined) {
                return false;
            }

            window.$.post({
                url: '/ajax.php',
                data: {
                    action: 'fetch_hero',
                    id: 'profile',
                    preview: false,
                    player_id: parseInt(player_id),
                },
                success: (data) => {}
            });

            return false;
        });
    }

    updatePlayerPlacementBadge(row, player_data) {
        const nicknameElement = row.find('div.data-column[column=nickname]');

        var badgeContainer = nicknameElement.find('.badge-container');
        if (!badgeContainer.length) {
            badgeContainer = $('<div class="badge-container" />').appendTo(nicknameElement);
        }

        // best placement indicator next to the nickname
        badgeContainer.html(this.createBestPlacementBadge(player_data));
    }

    createBestPlacementBadge(player) {
        var clazz = ''
        if (player.best_placement == 1) {
            clazz = 'top1';
        } else if (player.best_placement >= 2 && player.best_placement <= 4) {
            clazz = 'top4';
        }

        return clazz ? `<span class="best-placement"><span class="scriptLeagueInfoIcon ${clazz}"></span>${player.placement_count}</span>` : '';
    }

    makeCompatibleWithLeaguePlusPlus() {
        HHPlusPlus.Helpers.doWhenSelectorAvailable('div#leagues div.league_buttons a#change_team', () => {
            // Remove the avatars
            $('div.league_table div.data-row div.data-column[column=nickname] div.square-avatar-wrapper').remove();

            // Hide row when opponent has been fought
            const context = this;
            $('body').on('DOMSubtreeModified', '.league_table .body-row .data-column[column=match_history_sorting]', function() {
                context.hideUnhideRow($(this).parent('div.body-row'), context.isHideOpponents());
            });
        });
    }

    hideUnhideRow(row, hide) {
        const results = row.find('div.data-column[column=match_history_sorting]').find('div[class!="result "]').length;
        const fought_all = results == 3;
        if (fought_all && hide) {
            row.hide();
        } else if (fought_all && !hide) {
            row.show();
        }
    }

    isHideOpponents() {
        const filter = JSON.parse(storage.getItem(HHPLUSPLUS_OPPONENT_FILTER)) || {fought_opponent: false};
        return filter.fought_opponent;
    }
}

class PrebattleFlightCheckModule extends MyModule {
    constructor () {
        const baseKey = 'prebattleFlightCheck'
        const configSchema = {
            baseKey,
            default: true,
            label: `Run team and equipment checks before league battles`,
        }
        super({name: baseKey, configSchema})
    }

    shouldRun() {return HHPlusPlus.Helpers.isCurrentPage('leagues-pre-battle') || HHPlusPlus.Helpers.isCurrentPage('tower-of-fame')}

    run() {
        if (this.hasRun || !this.shouldRun()) {return}

        HHPlusPlus.Helpers.defer(() => {
            if (HHPlusPlus.Helpers.isCurrentPage('tower-of-fame')) {
                $(document).ajaxComplete((evt, xhr, opt) => {
                    if (xhr.status == 200 && ~opt.url.search(/\/leagues-pre-battle.html\?id_opponent=\d+/)) {
                        const me = opponents_list.find(p => parseInt(p.player.id_fighter) == Hero.infos.id);
                        const themes = me.player.team.theme_elements.map(x => x.type);

                        this.checkMythicEquipment(themes);
                    }
                });
            }

            if (HHPlusPlus.Helpers.isCurrentPage('leagues-pre-battle')) {
                HHPlusPlus.Helpers.doWhenSelectorAvailable('div.player-panel div.player-team', () => {
                    const synergies = JSON.parse($('div.player-panel div.player-team div.icon-area').attr('synergy-data'));
                    const themes = synergies.filter(x => x.team_girls_count >=3).map(x => x.element.type);

                    this.checkMythicEquipment(themes);
                });
            }
        });

        this.hasRun = true;
    }

    checkMythicEquipment(themes_or_empty) {
        // Additional CSS classes
        this.insertRule(`.slot.size_xxs {width:1.5rem;height:1.5rem;-webkit-border-radius:.2rem;-moz-border-radius:.2rem;border-radius:.2rem}`);

        const me = EquipmentCollector.getBestMythic();
        const equipment_themes = me.map(x => x.resonance_bonuses.theme.identifier || 'balanced');

        const themes = themes_or_empty.length ? themes_or_empty : ['balanced'];

        const has_matching_me = themes.some(t => equipment_themes.includes(t));
        if (has_matching_me) {
            const tooltip = "You have a perfect mythic equipment for your team in your inventory.";
            $('div.opponent div.player_details').append(
                `<div class="slot size_xxs mythic random_equipment mythic" rarity="mythic" tooltip="${tooltip}">
                    <span class="mythic_equipment_icn"></span>
                </div>`
            );
        }
    }
}

class HaremFiltersModule extends MyModule {
    constructor () {
        const baseKey = 'haremFilters'
        const configSchema = {
            baseKey,
            default: true,
            label: `Show additional harem filters`,
        }
        super({name: baseKey, configSchema})
    }

    shouldRun() {return HHPlusPlus.Helpers.isCurrentPage('harem') && !HHPlusPlus.Helpers.isCurrentPage('hero')}

    run () {
        if (this.hasRun || !this.shouldRun()) {return}

        HHPlusPlus.Helpers.defer(() => {
            // figure out the event ids for LD
            const ld_event_ids = $("select[name=event] option")
                                    .filter((idx, opt) => opt.text.includes("Legendary Days"))
                                    .map((idx, opt) => opt.value)
                                    .get()
                                    .map(id => parseInt(id));

            // mark the girls as LD girls
            Object.values(window.girlsDataList)
                  .filter(e => e.source_selectors.event)
                  .filter(e => e.source_selectors.event.filter(id => ld_event_ids.includes(id)).length)
                  .forEach(g => g.source_selectors.legendary_days = [0]); // TODO fill id, optional

            // add dropdown option
            $("select[name=lists]").append(new Option("Legendary Days", "legendary_days"));
        });

        this.hasRun = true;
    }
}

setTimeout(() => {
    const {hhPlusPlusConfig, HHPlusPlus, location} = window;

    if (!$) {
        console.log('No jQuery found. Probably an error page. Ending the script here')
        return;
    } else if (!hhPlusPlusConfig || !HHPlusPlus) {
        console.log("HH++ is not available");
        return;
    } else if (location.pathname === '/' && (location.hostname.includes('www') || location.hostname.includes('test'))) {
        console.log("iframe container, do nothing");
        return;
    }

    // collectors
    EquipmentCollector.collect();
    LeaguePlayersCollector.collect();

    // modules
    const modules = [
        new LeagueScoutModule(),
        new LeagueTableModule(),
        new HaremFiltersModule(),
        new PrebattleFlightCheckModule(),
    ]

    // register our own window hooks
    window.HHPlusPlusPlus = {
        exportLeagueData: LeaguePlayersCollector.export,
        clearLeagueData: LeaguePlayersCollector.clear,
    };

    hhPlusPlusConfig.registerGroup({
        key: '430i',
        name: '430i\'s Scripts'
    })

    modules.forEach(module => hhPlusPlusConfig.registerModule(module))
    hhPlusPlusConfig.loadConfig()
    hhPlusPlusConfig.runModules()

    HHPlusPlus.Helpers.runDeferred()
}, 1)

 

 

  • Thanks 2
  • Hearts 1
Link to comment
Share on other sites

15 minutes ago, mdnoria said:

Thanks @430i but I'm now losing the timer bars around the boosters once I sort.

That's very weird, I dont have this problem - I tried sorting every single column back and forth and it all works fine. And the script isnt changing anything booster related, so thats even weirder. Anyone else having this problem?

 

For you you can revert to the old version:

https://forum.kinkoid.com/index.php?/topic/22072-community-scripts-links-in-op/&do=findComment&comment=298110

Link to comment
Share on other sites

On 10/23/2023 at 4:59 PM, renalove said:

How to use:

  1. Remove my old simulator
  2. Install v4
  3. Go to the market page
  4. Go to every team editing page (not team selecting page)
  5. Go to Pantheon or League opponent page
  6. Click on the booster icon nearby your team

Please let me know if you find any bugs 😉

Thanks @renalove for creating this awesome addon! 

I have observed that the booster simulator is having some issues with updating, when i am switching between different sets of player gear. Do i have to repeat the whole process (every single time) for the script to update? Or maybe it is my fault and i am doing something wrong here?

Edited by Basniowy
Link to comment
Share on other sites

I am using the same set of scripts + Harem++ and it works here. I am using ViolentMonkey on Opera (Chromium-based) where you can define the order in which scripts are loaded (though they internally run party in parallel/other orders). Not sure if this might make a difference?

GS5 highlight works now. But the new lost points feature seems to not work here. I get a related browser console error:

Hentai Heroes++ BDSM version.user.js:64 Error in deferred function TypeError: Cannot read properties of undefined (reading 'points')
    at Hentai Heroes+++.user.js:595:86
    at Array.map (<anonymous>)
    at HTMLDivElement.<anonymous> (Hentai Heroes+++.user.js:595:33)
    at Function.each (jquery.min.js:2:3003)
    at S.fn.init.each (jquery.min.js:2:1481)
    at LeagueTableModule.showMaxPointsTooltip (Hentai Heroes+++.user.js:591:38)
    at Hentai Heroes+++.user.js:493:22
    at C.doWhenSelectorAvailable (Hentai Heroes++ BDSM version.user.js:64:158493)
    at Hentai Heroes+++.user.js:487:32
    at Hentai Heroes++ BDSM version.user.js:64:158357

I thought it might be an issue when League++ or Rena's simulator alter the table in a bad moment, but changing the order of the scripts or even disabling them does not help.

EDIT: Ah or it this the scripts own data, and the reason for the error simply is that no changes were recorded yet?

Edited by Horsting
Link to comment
Share on other sites

On 10/24/2023 at 12:47 AM, DvDivXXX said:

PS: Rena, the one thing that's a bit inconvenient is that I need the "Go" button to work in order to access your Booster Sim, but unfortunately @430i's Hentai Heroes +++ script still breaks the "Go" button. I love using it to have an overview of the field's top past performances, but I need to disable it to use your new sim. It's a problem with 430's script, not yours though.

OK. Now my script adds sims to @-MM-'s HH Leagues++.

On 10/24/2023 at 6:27 AM, Master-17 said:

Does not work in CxH and PsH. I visited the Market, went to edit all 6 commands, and still a message appears that there was an error with a suggestion to repeat all the steps. Tried it several times.

 

Booster icon overlays the "!" icon

1.jpg.6ff2b4c47640bf2dc61a6a67656a533c.jpg2.jpg.95fb068c23c79f1e11ef2f98a5fa008b.jpg3.jpg.f0db567e6e91fd98423d19612945dae5.jpg

Thank you for your report. I fixed an error that occurred when not joining a club and changed the position of the booster icon. If you are still having problems, please let me know.

4 hours ago, Basniowy said:

I have observed that the booster simulator is having some issues with updating, when i am switching between different sets of player gear. Do i have to repeat the whole process (every single time) for the script to update?

I checked and found that you need to reopen the team editing page after you change a hero gear. So in the current version, yes. This behavior is different from what I expect. I will look into having the script automatically load the team editing page and update the data.

Edited by renalove
  • Thanks 4
  • Hearts 1
Link to comment
Share on other sites

Many thanks, this worked. One suggestion: For the very first estimated lost points, the 25 remainder of the current score could be used, as best possible starting point.

EDIT @430i this change works nicely:

            const points = data.map(d => d.player_data.find(p => p.id == opponent_id).points).reverse();

-            var lost_points = 0;
+            var remainder = points[0] % 25;
+            var lost_points = (remainder == 0 ? 0 : 25 - remainder);

            for (let i = 0; i < points.length - 1; i+=1) {

So using the same method to obtain the minimum lost points between zero and the first snapshot, to start with. Thanks to the way this is re-calculated each time, it works OOTB for the current league.

I guess it could be also merged into the loop, starting with index -1, and replacing points[-1] with 0 within the loop.

Edited by Horsting
Link to comment
Share on other sites

On 10/27/2023 at 1:47 PM, Horsting said:

EDIT @430i this change works nicely:

You sure? I havent tested it, but I think points is reversed in the sense that the first element is the newest league data. The original data array is sorted from oldest (index 0) to newest (index n). I will try it later.

  • Like 1
Link to comment
Share on other sites

Indeed, I missed that the array is reversed, here is what works, generating the lost points chronologically:

            const points = data.map(d => d.player_data.find(p => p.id == opponent_id).points);

            const remainder = points[0] % 25;
            var lost_points = (remainder == 0 ? 0 : 25 - remainder);

            for (let i = 0; i < points.length - 1; i+=1) {
                const diff = points[i+1] - points[i];
                const remainder = diff % 25;
                lost_points += (remainder == 0 ? 0 : 25 - remainder);
            }

So removed the .reverse(), switched the diff arguments to keep it positive, now points[0] really is the points of the first snapshot, not the ones of the last. Or does the reverse order serve any other purpose I missed? At least the cases I checked which were wrong before are all correct now.

Edited by Horsting
Link to comment
Share on other sites

I would like that when you modify settings of the script in one game, they were changed also for the other kinkoid games you were playing too, or a setting option to have only one set of options valid for all kinkoid games.

I know that not all options are valid for all games, but a subset of them could be common.

Link to comment
Share on other sites

HH Leagues++ v0.12.0 released

You may receive an update message from TamperMonkey as I have changed the @match slightly.  This update will save you some life time as i assume most people only use x1 now. Have fun! 😄

 

 

  • Like 1
  • Thanks 5
  • Hearts 1
Link to comment
Share on other sites

@zoopokemon since today's update (I guess), when sorting the league, the booster timer and highlights, as well as opponent filter are gone. Browser console throws:

Hentai Heroes++ BDSM version.user.js:64 Error in deferred function DOMException: Failed to execute 'setItem' on 'Storage': Setting the value of 'HHPlusPlusLeagueResults' exceeded the quota.
    at C.lsSetRaw (chrome-extension://jinjaccalgkegednnccohejagnlnfdag/Hentai%20Heroes%2B%2B%20BDSM%20version.user.js#1:64:157291)
    at C.lsSet (chrome-extension://jinjaccalgkegednnccohejagnlnfdag/Hentai%20Heroes%2B%2B%20BDSM%20version.user.js#1:64:157331)
    at Ia.collectFromOpponentsList (chrome-extension://jinjaccalgkegednnccohejagnlnfdag/Hentai%20Heroes%2B%2B%20BDSM%20version.user.js#1:64:242767)
    at chrome-extension://jinjaccalgkegednnccohejagnlnfdag/Hentai%20Heroes%2B%2B%20BDSM%20version.user.js#1:64:241286
    at chrome-extension://jinjaccalgkegednnccohejagnlnfdag/Hentai%20Heroes%2B%2B%20BDSM%20version.user.js#1:64:158357
    at Array.forEach (<anonymous>)
    at HTMLDocument.<anonymous> (chrome-extension://jinjaccalgkegednnccohejagnlnfdag/Hentai%20Heroes%2B%2B%20BDSM%20version.user.js#1:64:158341)
    at e (https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js:2:30038)
    at t (https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js:2:30340)

The quote for one local storage key in my browser is 5200000 like it is default since years in all Chromium-based browsers, so I guess the data has increased? I will try to find out more tomorrow.

EDIT: Ah, it is actually your script @430i when having opponent data collection enabled. In local storage, the old snapshots were still stored in the current snapshot key, and I guess this key became too large now, breaking the script in the middle, so that also the data download and clear buttons were never added. After manually removing the current opponent data key via developer tools, the error is gone and all HH++ and HH+++ features work again as normal. Not sure whether the league end/start also had its impact somehow, or whether it was a coincident and only the amount of snapshots (size of the key) was the issue? Not sure if there is a clever way to prevent this issue, without e.g. automatically removing old data automatically once a too large number of snapshots has been stored? Probably automatically splitting the data across multiple keys, each with X timestamps/snapshots max?

Edited by Horsting
  • Thanks 1
Link to comment
Share on other sites

@Horsting Oh your the 2nd person I've seen that mentioned that their local storage became full recently. Local storage, at least on chrome, has a limit of 5MB. With just HH++ that normally shouldn't be exceeded. There is a risk of that with the Zoo's Script's pachinko log and girl data recorder both on.

You can use this code to check the size of all the local storage variables

Quote

var _lsTotal=0,_xLen,_x;for(_x in localStorage){ if(!localStorage.hasOwnProperty(_x)){continue;} _xLen= ((localStorage[_x].length + _x.length)* 2);_lsTotal+=_xLen; console.log(_x.substr(0,50)+" = "+ (_xLen/1024).toFixed(2)+" KB")};console.log("Total = " + (_lsTotal / 1024).toFixed(2) + " KB");

The other person said they had a variable called gmap that was 8.8MB. That variable isn't from HH++, so I don't know what other script made that. They suggested that it might be a variable from one of @430i's scripts.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Indeed, see my edit above. In my case it was the key "HHPlusPlusPlus.League.Snapshot.Current". I did not check its size, but naturally it can become very large as it stores all points of all opponents every time you visit the league, at least if anything has changed. Searching around for the error message, I thought that it was the single key which exceeded its single max size, but since it failed to store "HHPlusPlusLeagueResults" (according to error message) it makes much more sense that the whole local storage exceeded its shared limit.

Then splitting the data across multiple keys would not solve it. Hmm, the only thing I can think of then is to throw a warning after X number of snapshots have been stored, that old snapshots will be removed automatically, and suggest users to download the data now, if needed. Or we find a way to compress it somehow. Saving only data from opponents which did actually had their points changed would be an idea, but the csv would not be usable without further processing then 🤔.

EDIT: Actually it does not make sense to blame any particular script or key. It is the overall amount, including also e.g. villains, champs, Pachinko recorders etc, and keys of scripts I do not use anymore (like HH++ Seasons version in parallel). I just did some cleanup of my local storage.

Edited by Horsting
  • Like 1
Link to comment
Share on other sites

I also had my quota exceeded 2 times lately on Firefox but it was because I enabled Tom's OCD script at the same time of yours @zoopokemon and also having the villain and Pachinko collection enabled.

What I tested/needed was the quick navigation in champion fights he added lately (I disabled most OCD's options since they are already covered by BDSM) but they were too much.

The first time I lost patience and just cleared the entire localStorage but lost some time to reconfigure everything.
The second time, I upped my quota to 20MB to not bother anymore. My computer can easily handle this much in sqlite db/disk/memory per tab.

Now the biggest offender in my cache is this one from @430i HH+++ leagues script :

HHPlusPlusPlus.League.Snapshot.Previous = 10338.02 KB
[...]
Total = 11725.78 KB

The array contains 267 snapshots from last week's league with some less than 5 seconds from the following.

  • Like 1
Link to comment
Share on other sites

  • Moderator

Thank you all for this.

I suspected there was some kind of wrong interaction with all the scripts I'm using concurrently, but I couldn't pinpoint what exactly. For the past couple of days, all the indicators from @430i's script (mainly number of past wins) disappeared every time I sorted the table and I couldn't figure out why. I've cleared the script's own cache (or something? I've pressed the X button ^^) and now it's working normally again.

Link to comment
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
 Share

×
×
  • Create New...