Skip to content

Instantly share code, notes, and snippets.

@xram64
Last active December 16, 2024 18:57
Show Gist options
  • Select an option

  • Save xram64/6c16d6acb60aa9a9841c93f932fd743e to your computer and use it in GitHub Desktop.

Select an option

Save xram64/6c16d6acb60aa9a9841c93f932fd743e to your computer and use it in GitHub Desktop.
Melvor Idle Mods

Notes for Item Placeholder Mod (Github | Mod.io)

Bug list

  • Mod allows bank to go over capacity if items which have placeholders are added.
  • "Sell all unlocked items in Tab" shows a count that includes all placeholders in the tab.

[Bug] Items added past bank capacity

When an item is received from a skill or from combat loot, the Bank.hasItem() method is not used to detect whether there is room in the bank for the new item. Instead, the Bank.addItem() method is called directly from Rewards and CombatLoot objects.

The Bank.addItem() method checks bankItem !== undefined first to determine whether there is an existing slot that a newly looted item can go into. If this returns true, the bank assumes there is room for the item, since it should only increase the quantity of the item already occupying a bank slot. However, when a placeholder exists for an item, bankItem !== undefined still returns true. This is because an item with a placeholder still has an entry in the Bank.items map, only its quantity is set to 0. As long as this condition returns true, the bank carries on adding the item whether it has space or not, skipping over the free space check (this.occupiedSlots < this.maximumSlots).

While this could be fixed at the "skill" level, patching the Rewards.giveRewards() and CombatLoot.lootItem() methods (manually bypassing the addItem() call when the quantity of the existing item in the map is 0), this would require a re-implementation of the rest of the addItem() functionality. Thus, patching addItem() itself seems to be the most straightforward and centralized approach.

Reference

assets/js/built/bank2.js : Bank.addItem()

    /**
     * @description Adds the given quantity of the item to the bank
     * @param item The item to add
     * @param quantity The quantity of the item to add. Must be positive.
     * @param logLost If the item should be logged as lost if it does not fit
     * @param found If the item should contribute to item statistics
     * @param ignoreSpace If the item should ignore conventional bank space limits
     * @param notify Whether to show a notification when the item is added
     * @param itemSource The source of the item, where it came from
     * @returns True if the item was successfully added to the bank
     */
    addItem(item, quantity, logLost, found, ignoreSpace = false, notify = true, itemSource = 'Game.Unknown') {
        var _a, _b;
        if (quantity <= 0)
            throw new Error(`Tried to add negative or zero quantity to bank.`);
        let success = false;
        let bankItem = this.items.get(item);
        let oldQuantity = 0;
        let newQuantity = quantity;
        if (bankItem !== undefined) {
            oldQuantity = bankItem.quantity;
            bankItem.quantity += quantity;
            newQuantity = bankItem.quantity;
            success = true;
        }
        else if (this.occupiedSlots < this.maximumSlots || ignoreSpace) {
            const tab = this.getItemDefaultTab(item);
            bankItem = new BankItem(this, item, quantity, tab, this.itemsByTab[tab].length);
            this.items.set(item, bankItem);
            this.itemsByTab[tab].push(bankItem);
            if (this.game.settings.bankSortOrder === 5 /* BankSortOrderSetting.Custom */ && !this.customSortOrder.includes(item))
                this.storeCustomSortOrder();
            success = true;
            this.renderQueue.bankSearch = true;
            this.newItemsAdded = true;
            if (bankItem.tabPosition === 0)
                this.renderQueue.tabIcons.add(tab);
        }
        if (success) {
            if (found) {
                const newItem = this.game.stats.itemFindCount(item) === 0;
                this.game.stats.Items.add(item, ItemStats.TimesFound, quantity);
                if (newItem) {
                    const event = new ItemFoundEvent(item);
                    this._events.emit('itemFound', event);
                    if (this.emitItemEvents)
                        item.emit('found', event);
                    this.game.completion.updateItem(item);
                    this.glowingItems.add(item);
                    if (item instanceof EquipmentItem)
                        this.game.minibar.addItemOnDiscovery(item);
                    this.game.renderQueue.birthdayEventProgress = true;
                }
            }
            if (!loadingOfflineProgress)
                this.game.telemetry.createItemGainedEvent(item, quantity, itemSource);
            this.renderQueue.items.add(item);
            (_a = this.game.archaeology) === null || _a === void 0 ? void 0 : _a.renderQueue.museumArtefacts.add(item);
            this.queueQuantityUpdates(item);
            if (notify)
                this.game.combat.notifications.add({
                    type: 'Item',
                    args: [item, quantity],
                });
            const event = new ItemQuantityChangedEvent(item, oldQuantity, newQuantity);
            this._events.emit('itemChanged', event);
            if (this.emitItemEvents)
                item.emit('bankQuantityChanged', event);
        }
        else {
            if (notify)
                this.game.combat.notifications.add({
                    type: 'BankFull',
                    args: [],
                });
            if (logLost) {
                this.lostItems.set(item, ((_b = this.lostItems.get(item)) !== null && _b !== void 0 ? _b : 0) + quantity);
            }
        }
        return success;
    }

assets/js/built/skills.js : Rewards.giveRewards()

    giveRewards(ignoreBankSpace = false) {
        let notAllItemsGiven = false;
        this._items.forEach((quantity, item) => {
            notAllItemsGiven =
                !this.game.bank.addItem(item, quantity, true, true, ignoreBankSpace, true, this.source) || notAllItemsGiven;
        });
        this._currencies.forEach((quantity, currency) => {
            currency.add(quantity);
            if (currency === this.game.gp)
                this.game.telemetry.createGPAdjustedEvent(quantity, currency.amount, this.source);
            if (currency === this.game.abyssalPieces)
                this.game.telemetry.createAPAdjustedEvent(quantity, currency.amount, this.source);
        });
        this._xp.forEach((xp, skill) => {
            const xpBefore = skill.xp;
            const levelBefore = skill.level;
            if (xp.noAction > 0)
                skill.addXP(xp.noAction);
            xp.action.forEach((amount, action) => {
                skill.addXP(amount, action);
            });
            if (skill.xp > xpBefore) {
                this.game.telemetry.createOnlineXPGainEvent(skill, this.actionInterval, xpBefore, skill.xp, levelBefore, skill.level);
            }
        });
        this._abyssalXP.forEach((xp, skill) => {
            const xpBefore = skill.abyssalXP;
            const levelBefore = skill.abyssalLevel;
            if (xp.noAction > 0)
                skill.addAbyssalXP(xp.noAction);
            xp.action.forEach((amount, action) => {
                skill.addAbyssalXP(amount, action);
            });
            if (skill.abyssalXP > xpBefore) {
                this.game.telemetry.createOnlineAXPGainEvent(skill, this.actionInterval, xpBefore, skill.abyssalXP, levelBefore, skill.abyssalLevel);
            }
        });
        return notAllItemsGiven;
    }

assets/js/built/combatLoot.js : CombatLoot.lootItem()

    lootItem(id) {
        const drop = this.drops[id];
        if (this.game.bank.addItem(drop.item, drop.quantity, false, true, false, true, 'Other.CombatLootContainer')) {
            this.game.stats.Combat.add(CombatStats.ItemsLooted, drop.quantity);
            this.drops.splice(id, 1);
            this.renderRequired = true;
            this.render();
        }
        else {
            notifyPlayer(this.game.attack, getLangString('TOASTS_NO_BANK_ROOM'), 'danger');
        }
    }

assets/js/built/combatLoot.js : CombatLoot.lootAll()

    lootAll() {
        this.drops = this.drops.filter((drop) => {
            const fit = this.game.bank.addItem(drop.item, drop.quantity, false, true, false, true, 'Other.CombatLootContainer');
            if (fit)
                this.game.stats.Combat.add(CombatStats.ItemsLooted, drop.quantity);
            return !fit;
        });
        if (this.drops.length > 0)
            notifyPlayer(this.game.attack, getLangString('TOASTS_NO_BANK_ROOM_EVERYTHING'), 'danger');
        this.renderRequired = true;
        this.render();
    }

Melvor Idle - Mod Ideas

  • Quick level stats. A mod that pulls all current levels, level progress, mastery stats, pool XP, total level, etc. into one easily shareable screen/modal.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment