| 
 | 1 | +/**  | 
 | 2 | +* Storage Management  | 
 | 3 | + *   !storageconfig — Opens the configuration menu.  | 
 | 4 | + | 
 | 5 | +*Configuration Commands  | 
 | 6 | +*    !setbagmax [number] — Sets the max weight for the Dimension Bag.  | 
 | 7 | +*    !setsackmax [number] — Sets the max weight for the Sack.  | 
 | 8 | +*    !setholemax — Resets the Moveable Hole to unlimited weight.  | 
 | 9 | +*    !setcoins [comma-separated values] — Updates the order of coin types (e.g., !setcoins PP, GP, SP, CP).  | 
 | 10 | +*    !setcolor [color name or hex] — Changes the text color for item quantity.  | 
 | 11 | + | 
 | 12 | +*Storage Commands  | 
 | 13 | +*    !dimensionbag add "Bag Name" [quantity] [item] [weight] — Adds an item to a Dimension Bag.  | 
 | 14 | +*    !dimensionbag remove "Bag Name" [quantity] [item] — Removes an item from a Dimension Bag.  | 
 | 15 | +*    !sack add "Sack Name" [quantity] [item] [weight] — Adds an item to a Sack.  | 
 | 16 | +*    !sack remove "Sack Name" [quantity] [item] — Removes an item from a Sack.  | 
 | 17 | +*    !hole add "Hole Name" [quantity] [item] [weight] — Adds an item to a Moveable Hole.  | 
 | 18 | +*    !hole remove "Hole Name" [quantity] [item] — Removes an item from a Moveable Hole.  | 
 | 19 | +*Each storage type has its own max weight (except the hole, which is unlimited).  | 
 | 20 | +*/  | 
 | 21 | +on('ready', () => {  | 
 | 22 | +    let CONFIG = {  | 
 | 23 | +        bagMaxWeight: 500,  | 
 | 24 | +        sackMaxWeight: 900,  | 
 | 25 | +        holeMaxWeight: Infinity,  | 
 | 26 | +        coinOrder: ['PP', 'GP', 'EP', 'SP', 'CP'],  | 
 | 27 | +        quantityColor: 'blue'  | 
 | 28 | +    };  | 
 | 29 | + | 
 | 30 | +    const showConfigMenu = () => {  | 
 | 31 | +        let menu = `/w gm &{template:default} ` +  | 
 | 32 | +            `{{name=Storage Configuration}} ` +  | 
 | 33 | +            `{{Bag Max Weight=[${CONFIG.bagMaxWeight}](!setbagmax ?{New Max Weight})}} ` +  | 
 | 34 | +            `{{Sack Max Weight=[${CONFIG.sackMaxWeight}](!setsackmax ?{New Max Weight})}} ` +  | 
 | 35 | +            `{{Hole Max Weight=[Unlimited](!setholemax)}} ` +  | 
 | 36 | +            `{{Coin Order=[${CONFIG.coinOrder.join(', ')}](!setcoins ?{Enter Coins Separated by Commas})}} ` +  | 
 | 37 | +            `{{Quantity Color=[${CONFIG.quantityColor}](!setcolor ?{Enter Color Name or Hex})}}`;  | 
 | 38 | +        sendChat('Storage', menu);  | 
 | 39 | +    };  | 
 | 40 | + | 
 | 41 | +    on('chat:message', (msg) => {  | 
 | 42 | +        if (msg.type !== 'api') return;  | 
 | 43 | + | 
 | 44 | +        if (msg.content === '!storageconfig') {  | 
 | 45 | +            showConfigMenu();  | 
 | 46 | +            return;  | 
 | 47 | +        }  | 
 | 48 | + | 
 | 49 | +        let match;  | 
 | 50 | +        if (match = msg.content.match(/^!setbagmax (\d+)$/)) {  | 
 | 51 | +            CONFIG.bagMaxWeight = parseInt(match[1], 10);  | 
 | 52 | +        } else if (match = msg.content.match(/^!setsackmax (\d+)$/)) {  | 
 | 53 | +            CONFIG.sackMaxWeight = parseInt(match[1], 10);  | 
 | 54 | +        } else if (match = msg.content.match(/^!setcoins (.+)$/)) {  | 
 | 55 | +            CONFIG.coinOrder = match[1].split(',').map(c => c.trim());  | 
 | 56 | +        } else if (match = msg.content.match(/^!setcolor (.+)$/)) {  | 
 | 57 | +            CONFIG.quantityColor = match[1];  | 
 | 58 | +        } else {  | 
 | 59 | +            return;  | 
 | 60 | +        }  | 
 | 61 | +        showConfigMenu();  | 
 | 62 | +    });  | 
 | 63 | + | 
 | 64 | +    const getOrCreateHandout = (handoutName) => {  | 
 | 65 | +        let handout = findObjs({ type: 'handout', name: handoutName })[0];  | 
 | 66 | +        if (!handout) {  | 
 | 67 | +            handout = createObj('handout', {   | 
 | 68 | +                name: handoutName,   | 
 | 69 | +                inplayerjournals: 'all',  | 
 | 70 | +                notes: '<b>Total Weight: 0 lbs</b><br><br>'  | 
 | 71 | +            });  | 
 | 72 | +        }  | 
 | 73 | +        return handout;  | 
 | 74 | +    };  | 
 | 75 | + | 
 | 76 | +    const parseHandoutContent = (notes) => {  | 
 | 77 | +        let items = {};  | 
 | 78 | +        (notes || '').split('<br>').forEach(line => {  | 
 | 79 | +            let match = line.match(/<b>(.+?)<\/b>: <span style='color:(.*?)'>(\d+)<\/span> \((.*?) lbs\)/);  | 
 | 80 | +            if (match) {  | 
 | 81 | +                let [, item, , quantity, weight] = match;  | 
 | 82 | +                items[item] = { quantity: parseInt(quantity, 10), weight: parseFloat(weight) };  | 
 | 83 | +            }  | 
 | 84 | +        });  | 
 | 85 | +        return items;  | 
 | 86 | +    };  | 
 | 87 | + | 
 | 88 | +    const updateHandout = (handoutName, items) => {  | 
 | 89 | +        let totalWeight = Object.entries(items).reduce((sum, [_, { quantity, weight }]) => sum + (quantity * weight), 0);  | 
 | 90 | +        let sortedItems = Object.entries(items).sort(([a], [b]) => {  | 
 | 91 | +            let aIndex = CONFIG.coinOrder.indexOf(a);  | 
 | 92 | +            let bIndex = CONFIG.coinOrder.indexOf(b);  | 
 | 93 | +            if (aIndex !== -1 && bIndex !== -1) return aIndex - bIndex;  | 
 | 94 | +            if (aIndex !== -1) return -1;  | 
 | 95 | +            if (bIndex !== -1) return 1;  | 
 | 96 | +            return a.localeCompare(b);  | 
 | 97 | +        });  | 
 | 98 | + | 
 | 99 | +        let content = `<b>Total Weight: ${totalWeight.toFixed(2)} lbs</b><br><br>`;  | 
 | 100 | +        sortedItems.forEach(([item, { quantity, weight }]) => {  | 
 | 101 | +            content += `<b>${item}</b>: <span style='color:${CONFIG.quantityColor}'>${quantity}</span> (${weight} lbs)<br>`;  | 
 | 102 | +        });  | 
 | 103 | + | 
 | 104 | +        let handout = getOrCreateHandout(handoutName);  | 
 | 105 | +        handout.set('notes', content);  | 
 | 106 | +    };  | 
 | 107 | + | 
 | 108 | +    const addItem = (count, item, weight, handoutName, maxWeight) => {  | 
 | 109 | +        let handout = getOrCreateHandout(handoutName);  | 
 | 110 | +        handout.get('notes', (notes) => {  | 
 | 111 | +            let items = parseHandoutContent(notes);  | 
 | 112 | +            count = parseInt(count, 10);  | 
 | 113 | +            weight = parseFloat(weight);  | 
 | 114 | +            let currentWeight = Object.entries(items).reduce((sum, [_, { quantity, weight }]) => sum + (quantity * weight), 0);  | 
 | 115 | +            if (currentWeight + (count * weight) > maxWeight) {  | 
 | 116 | +                sendChat(handoutName, `&{template:default} {{name=${handoutName}}} {{Warning=The ${handoutName} is overloaded and its contents are lost.}}`);  | 
 | 117 | +                return;  | 
 | 118 | +            }  | 
 | 119 | +            items[item] = items[item] || { quantity: 0, weight };  | 
 | 120 | +            items[item].quantity += count;  | 
 | 121 | +            updateHandout(handoutName, items);  | 
 | 122 | +        });  | 
 | 123 | +    };  | 
 | 124 | + | 
 | 125 | +    const removeItem = (count, item, handoutName) => {  | 
 | 126 | +        let handout = getOrCreateHandout(handoutName);  | 
 | 127 | +        handout.get('notes', (notes) => {  | 
 | 128 | +            let items = parseHandoutContent(notes);  | 
 | 129 | +            count = parseInt(count, 10);  | 
 | 130 | +            if (!items[item] || items[item].quantity < count) {  | 
 | 131 | +                sendChat(handoutName, `/w gm &{template:default} {{name=${handoutName}}} {{Warning=Not enough ${item} to remove!}}`);  | 
 | 132 | +                return;  | 
 | 133 | +            }  | 
 | 134 | +            items[item].quantity -= count;  | 
 | 135 | +            if (items[item].quantity <= 0) delete items[item];  | 
 | 136 | +            updateHandout(handoutName, items);  | 
 | 137 | +        });  | 
 | 138 | +    };  | 
 | 139 | + | 
 | 140 | +    on('chat:message', (msg) => {  | 
 | 141 | +        if (msg.type !== 'api') return;  | 
 | 142 | +        let args = msg.content.match(/^!(dimensionbag|sack|hole) (add|remove) "(.+?)" (.+)$/);  | 
 | 143 | +        if (!args) return;  | 
 | 144 | + | 
 | 145 | +        let type = args[1];  | 
 | 146 | +        let action = args[2];  | 
 | 147 | +        let storageName = args[3];  | 
 | 148 | +        let commandArgs = args[4].split(' ');  | 
 | 149 | +        let maxWeight = type === 'dimensionbag' ? CONFIG.bagMaxWeight : type === 'sack' ? CONFIG.sackMaxWeight : CONFIG.holeMaxWeight;  | 
 | 150 | + | 
 | 151 | +        if (action === 'add' && commandArgs.length >= 3) {  | 
 | 152 | +            let count = commandArgs.shift();  | 
 | 153 | +            let weight = commandArgs.pop();  | 
 | 154 | +            let item = commandArgs.join(' ');  | 
 | 155 | +            addItem(count, item, weight, storageName, maxWeight);  | 
 | 156 | +        } else if (action === 'remove' && commandArgs.length >= 2) {  | 
 | 157 | +            let count = commandArgs.shift();  | 
 | 158 | +            let item = commandArgs.join(' ');  | 
 | 159 | +            removeItem(count, item, storageName);  | 
 | 160 | +        }  | 
 | 161 | +    });  | 
 | 162 | +});  | 
0 commit comments