I haven't really played around with Universal Chat Menu, but I think this should work for the shaped sheet: !chatmenu @{selected|character_id} @{selected|character_name} --title:Ranged Attacks --repeating_offense|name|roll|attack_type=RANGED One of the sheet authors will know better, but I believe someone said that creating a dynamic drop-down on the sheet is either very difficult, or impossible. That's also going to require customising the sheet itself, you can't do that with an API script. I could be misremembering though, maybe it can be done. You could add that functionality into an API reloading script instead, it could search the repeating_ammo section, grab all the results and make a button from each one. Or you could do a Query pop-up to choose from, which would give you a drop down list. So, player runs the reload script by clicking a button, it then gives them a list of their weapons to choose from, then another dialog asking which ammo to use. It posts the result to chat and does the math with the ammo. Is that kind of what you're looking for? Changing ammo types is mechanically the same as reloading, so the script would cover both. The ammo part of the Companion script looks like it starts on line 5416. It's not overly long, but it has a _.chain function which can be difficult to follow. I'm not sure which bits you're hoping to steal: consumeAmmo(options) { if (!this.roll20.checkCharacterFlag(options.character.id, 'ammo_auto_use')) { return; } const ammoAttr = _.chain(this.roll20.findObjs({ type: 'attribute', characterid: options.character.id })) .filter(attribute => attribute.get('name').indexOf('repeating_ammo') === 0) .groupBy(attribute => attribute.get('name').replace(/(repeating_ammo_[^_]+).*/, '$1')) .find(attributeList => _.find(attributeList, attribute => attribute.get('name').match(/.*name$/) && attribute.get('current') === options.ammoName) ) .find(attribute => attribute.get('name').match(/.*qty$/)) .value(); if (!ammoAttr) { this.logger.error('No ammo attribute found corresponding to name $$$', options.ammoName); return; } if (options.ammo) { const ammoRemaining = parseInt(options.ammo, 10); if (ammoRemaining >= 0) { const current = parseInt(ammoAttr.get('current'), 10); ammoAttr.setWithWorker('current', ammoRemaining); const ammoTracking = this.getAmmoTracking(); if (ammoTracking) { ammoTracking[ammoAttr.id] = (ammoTracking[ammoAttr.id] || 0) + current - ammoRemaining; } } else { this.reportResult('Ammo Police', `${options.characterName} can't use ${options.title} because ` + `they don't have enough ${options.ammoName} left`, options); } } } getAmmoTracking() { if (this.roll20.getCampaign().get('initiativepage')) { this.myState.ammoTracking = this.myState.ammoTracking || {}; return this.myState.ammoTracking; } return null; } reportTotalAmmoUse() { if (!this.myState.config.sheetEnhancements.ammoRecovery) { return; } const recoveryStrings = _.chain(this.myState.ammoTracking) .map((used, id) => { const ammoAttr = this.roll20.getObj('attribute', id); if (!ammoAttr) { return null; } const ammoName = this.roll20.getAttrByName(ammoAttr.get('characterid'), ammoAttr.get('name').replace(/_qty/, '_name')); const char = this.roll20.getObj('character', ammoAttr.get('characterid')); return `${char.get('name')} used ${used} ${ammoName}.&nbsp;<a href="!shaped-recover-ammo ` + `--ammoAttr ${id} --qty ?{Quantity to recover|${Math.floor(used / 2)}}">Recover</a>`; }) .compact() .value(); if (!_.isEmpty(recoveryStrings)) { const msg = `<ul><li>${recoveryStrings.join('</li><li>')}</li></ul>`; this.reportPlayer('Ammo Recovery', msg); } } recoverAmmo(options) { const ammoName = this.roll20.getAttrByName(options.ammoAttr.get('characterid'), options.ammoAttr.get('name').replace(/_qty/, '_name')); options.ammoAttr.setWithWorker({ current: options.ammoAttr.get('current') + options.qty }); this.reportCharacter('Ammo Recovery', `You recover ${options.qty} ${ammoName}`, options.ammoAttr.get('characterid')); } }