IdlePixel Breeding Overhaul

Redesigned breeding UI and QoL features.

当前为 2024-11-10 提交的版本,查看 最新版本

// ==UserScript==
// @name         IdlePixel Breeding Overhaul
// @namespace    com.anwinity.idlepixel
// @version      1.0.11
// @description  Redesigned breeding UI and QoL features.
// @author       Anwinity
// @license      MIT
// @match        *://idle-pixel.com/login/play*
// @grant        none
// @require      https://gf.qytechs.cn/scripts/441206-idlepixel/code/IdlePixel+.js?anticache=20220905
// ==/UserScript==

(function() {
    'use strict';

    const IMAGE_URL = "https://cdn.idle-pixel.com/images/";

    const ANIMAL_LIST = ["chicken", "sheep", "spider", "horse", "ant", "camel", "beaver", "dove", "pig", "haron", "toysoldier"];
    const ANIMAL_SORT = ANIMAL_LIST.reduce((acc, value, index) => {
        acc[value] = index;
        return acc;
    }, {});

    const FOOD_MAP = {
        Seeds: "breeding_food_seeds",
        Leaves: "breeding_food_leaves",
        // breeding_food_strange_leaves
        Seafood: "breeding_food_fish",
        // breeding_food_fruits
        Insects: "breeding_food_insects",
        Bark: "breeding_food_wood",
        Metal: "breeding_food_metal",
    };

    class BreedingOverhaulPlugin extends IdlePixelPlusPlugin {
        constructor() {
            super("breedingoverhaul", {
                about: {
                    name: GM_info.script.name,
                    version: GM_info.script.version,
                    author: GM_info.script.author,
                    description: GM_info.script.description
                }
            });
        }

        parseAnimals(raw) {
            if(!raw) {
                return [];
            }
            return raw.split("=").map((rawAnimal) => {
                const result = {
                    raw: rawAnimal
                };
                const [
                    slug,
                    animal,
                    gender,
                    tier,
                    millis,
                    isGold,
                    sick,
                    foodPerDay,
                    foodConsumed,
                    lootItemReady,
                    lootItemAmount,
                    xpGained,
                    traitLabel,
                    extraData1,
                    extraData2,
                    extraData3,
                    extraData4,
                    extraData5
                ] = rawAnimal.split(",");

                let food = Breeding.getBreedFoodLabel(animal);
                if(food == "null") {
                    food = null;
                }

                result.slug = slug;
                result.animal = animal;
                result.sex = (gender==1 ? "M" : (gender==2 ? "F" : "?"));
                //result.tier = parseInt(tier);
                result.birth = parseInt(millis);
                //result.gold = isGold!=0;
                result.sick = sick!=0;
                result.food = food;
                result.foodPerDay = parseInt(foodPerDay);
                //result.foodConsumed = parseInt(foodConsumed);
                result.lootItem = lootItemReady == "none" ? null : lootItemReady;
                result.lootCount = parseInt(lootItemAmount);
                result.xp = parseInt(xpGained);
                result.trait = traitLabel;
                //result.hornyRate = Breeding.getReproductionRate(animal, null);

                return result;
            }).sort((a, b) => {
                const aSort = ANIMAL_SORT[a.animal] ?? 9999;
                const bSort = ANIMAL_SORT[b.animal] ?? 9999;
                if(aSort == bSort) {
                    return a.birth - b.birth;
                }
                return aSort - bSort;
            });
        }

        calculateReproductionRate(animalData, animal) {
            if(["toysoldier"].includes(animal)) {
                return Number.POSITIVE_INFINITY;
            }
            // calculates expected # of ticks for this species to reproduce
            let rate = Breeding.getReproductionRate(animal, null);
            let males = 0;
            let females = 0;
            let infertile = 0;
            let fertile = 0;
            for(const data of animalData) {
                if(data.animal != animal) {
                    continue;
                }
                if(data.sick) {
                    continue;
                }
                if(data.sex == "M") {
                    males++;
                }
                else if(data.sex == "F") {
                    females++;
                }
                if(data.trait == "Fertile") {
                    fertile++;
                }
                else if(data.trait == "Less Fertile") {
                    infertile++;
                }
            }
            let couples = Math.min(males, females);
            if(infertile > 0) {
                rate = Math.floor(rate * Math.pow(1.1, infertile));
            }
            if(fertile > 0) {
                rate = Math.floor(rate * Math.pow(0.9, fertile));
            }
            rate = 600 * Math.floor(1 + (rate / 600 / couples));
            return rate;
        }

        summarizeAnimals(animalData) {
            let xp = 0;
            const animals = [];
            const loot = {};
            const dailyFood = {};
            const reproduction = {};
            for(const animal of animalData) {
                xp += animal.xp || 0;

                if(!animals.includes(animal.animal)) {
                    animals.push(animal.animal);
                }

                if(animal.lootItem) {
                    if(animal.lootCount) {
                        loot[animal.lootItem] ??= 0;
                        loot[animal.lootItem] += animal.lootCount;
                    }
                }

                if(animal.food && animal.foodPerDay) {
                    dailyFood[animal.food] ??= 0;
                    dailyFood[animal.food] += animal.foodPerDay;
                }
            }
            let result = { xp, animals, loot, dailyFood, reproduction };
            for(const animal of animals) {
                const rate = this.calculateReproductionRate(animalData, animal);
                result.reproduction[animal] = rate;
            }

            // temporary hack until var_ant_capacity_used is fixed
            let antCount = animalData.filter(animal => animal.animal=="ant").length;
            window.var_ant_capacity_used = `${antCount}`;

            return result;
        }

        updateAnimalSummary(summary) {
            if(!summary) {
                return;
            }

            const totalXPElement = document.getElementById("breeding-overhaul-total-xp");
            if(totalXPElement) {
                totalXPElement.innerHTML = `${summary.xp?.toLocaleString(navigator.language)}`;
            }

            const dailyFoodElement = document.getElementById("breeding-overhaul-daily-food");
            const foodDaysElement = document.getElementById("breeding-overhaul-food-days");
            if(dailyFoodElement && summary.dailyFood) {
                let content = "";
                let contentDays = "";
                const foodSpans = [];
                const foodDaysSpans = [];
                for(const food in summary.dailyFood) {
                    const amount = summary.dailyFood[food]?.toLocaleString(navigator.language);
                    const foodVar = FOOD_MAP[food];
                    const img = `<img src="${IMAGE_URL}${foodVar}.png" class="inline" />`;

                    let danger = false;
                    let owned = "";
                    let days = 0;
                    if(foodVar) {
                        owned = IdlePixelPlus.getVarOrDefault(foodVar, 0, "int");
                        if(owned < amount) {
                            danger = true;
                        }
                        days = amount==0 ? 0 : owned/amount;
                        owned = `/${owned.toLocaleString(navigator.language)}`;
                    }

                    foodSpans.push(`<span class="${danger?'danger':''}" title="${food}: ${amount}/day">&nbsp;${img}&nbsp;${amount}${owned}&nbsp;</span>`);
                    foodDaysSpans.push(`<span class="${danger?'danger':''}" title="${food}: ${days.toFixed(2)} day${days==1?'':'s'}">&nbsp;${img}&nbsp;${days.toFixed(1)} day${days==1?'':'s'}&nbsp;</span>`);
                }

                content += foodSpans.join(" ");
                if(!content) {
                    content = "None";
                }
                if(dailyFoodElement) {
                    dailyFoodElement.innerHTML = content;
                }

                contentDays += foodDaysSpans.join(" ");
                if(!contentDays) {
                    contentDays = "None";
                }
                if(foodDaysElement) {
                    foodDaysElement.innerHTML = contentDays;
                }
            }

            const reproductionElement = document.getElementById("breeding-overhaul-reproduction");
            if(reproductionElement && summary.reproduction && summary.animals) {
                let content = "";
                const hornySpans = [];
                for(const animal of summary.animals) {
                    const img = `<img src="${IMAGE_URL}${animal}_male_1.png" class="inline" />`;
                    let rate = summary.reproduction[animal];
                    if(Number.isFinite(rate)) {
                        rate = format_time(rate);
                        if(rate < 600) {
                            // animals can reproduce at most once every 10 minutes
                            rate = 600;
                        }
                        rate = rate.replace(/, 0:00$/, "");
                    }
                    else {
                        rate = "Never";
                    }
                    hornySpans.push(`<span title="${capitalizeFirstLetter(animal)} Expected Reproduction: ${rate}">&nbsp;${img}&nbsp;${rate}&nbsp;</span>`);
                }
                content += hornySpans.join(" ");
                if(!content) {
                    content = "None";
                }
                reproductionElement.innerHTML = content;
            }

            const lootAvailableElement = document.getElementById("breeding-overhaul-loot-available");
            if(lootAvailableElement && summary.loot) {
                let content = "";
                const lootSpans = [];
                for(const loot in summary.loot) {
                    const amount = summary.loot[loot]?.toLocaleString(navigator.language);
                    const img = `<img src="${IMAGE_URL}${loot.toLowerCase()}.png" class="inline" />`;
                    lootSpans.push(`<span title="${loot}: ${amount}">&nbsp;${img}&nbsp;${amount}&nbsp;</span>`);
                }
                content += lootSpans.join(" ");
                let anyLoot = true;
                if(!content) {
                    content = "None";
                    anyLoot = false;
                }
                lootAvailableElement.innerHTML = content;
                const lootAllButton = document.getElementById("breeding-overhaul-loot-all");
                if(lootAllButton) {
                    lootAllButton.disabled = !anyLoot;
                }
            }
        }

        clickLootAll() {
            const animalData = IdlePixelPlus.getVar("animal_data");
            if(!animalData) {
                return;
            }
            this.parseAnimals(animalData)
                .filter(animal => animal.lootCount)
                .forEach(animal => IdlePixelPlus.sendMessage(`COLLECT_ALL_LOOT_ANIMAL`));
        }

        updateAnimalData(animals) {
            if(!animals) {
                return;
            }

            const summary = this.summarizeAnimals(animals);
            this.updateAnimalSummary(summary);
        }

        initUI() {
            const style = document.createElement("style");
            style.id = "styles-breeding-overhaul";
            style.textContent = `
            #panel-breeding #breeding-overhaul-breeding-summary {
              display: flex;
              flex-direction: column;
            }
            #panel-breeding #breeding-overhaul-breeding-summary .danger {
              color: red;
            }
            #panel-breeding #breeding-overhaul-breeding-summary img.inline {
              height: 1em;
              width: auto;
            }
            `;
            document.head.appendChild(style);

            const panel = document.getElementById("panel-breeding");
            if(!panel) {
                console.error("Breeding Overhaul: Failed to initialize UI - #panel-breeding not found");
                return;
            }
            const craftableButtons = panel.querySelector(".craftable-btns");
            if(craftableButtons) {
                craftableButtons.insertAdjacentHTML("afterend", `
                <div id="breeding-overhaul-breeding-summary">
                  <div>
                    <strong>Total XP: </strong>
                    <span id="breeding-overhaul-total-xp"></span>
                  </div>
                  <div>
                    <strong>Daily Food: </strong>
                    <span id="breeding-overhaul-daily-food"></span>
                  </div>
                  <div>
                    <strong>Food Days: </strong>
                    <span id="breeding-overhaul-food-days"></span>
                  </div>
                  <div>
                    <strong>Reproduction: </strong>
                    <span id="breeding-overhaul-reproduction"></span>
                  </div>
                  <div>
                    <strong>Loot Available: </strong>
                    <span id="breeding-overhaul-loot-available"></span>
                  </div>
                  <div>
                    <button id="breeding-overhaul-loot-all" type="button" onclick="IdlePixelPlus.plugins.breedingoverhaul.clickLootAll()">Loot All</button>
                  </div>
                </div>
                `);
            }
            else {
                console.error("Breeding Overhaul: Failed to fully initialize UI - .craftable-btns not found");
            }

        }

        onLogin() {
            // Initialize new ui components
            this.initUI();

            // override refresh function
            const original_refresh_animals_data = Breeding.refresh_animals_data;
            Breeding.refresh_animals_data = (...args) => {
                const animalArea = document.getElementById("breeding-chicken-area");

                let raw = args[0] || null;
                let animals = this.parseAnimals(raw);

                // rejoin (sorted) for original function
                if(animals?.length) {
                    raw = animals.map(animal => animal.raw).join("=");
                }
                // run original smitty function
                try {
                    original_refresh_animals_data.apply(this, [raw]);
                }
                catch(err) {
                    console.error("Error running original Breeding.refresh_animals_data: ", err);
                    throw err;
                }

                // update ui
                try {
                    this.updateAnimalData(animals);
                }
                catch(err) {
                    console.error("Error running BreedingOverhaulPlugin.updateAnimalData: ", err);
                }

            }
        }

    }

    const plugin = new BreedingOverhaulPlugin();
    IdlePixelPlus.registerPlugin(plugin);

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址