The MACRO / Token Action !torpedo --selected @{selected|token_id} --target @{target|token_id} __________________ The SCRIPT //spawn torpedo on('chat:message', function(msg) { if (msg.type !== 'api' || !msg.content.startsWith('!torpedo')) return; const args = msg.content.split(' '); if (args.length !== 3) { sendChat('System', '/w ' + msg.who + ' Invalid arguments. Usage: !torpedo casterTokenId targetTokenId'); return; } const casterTokenId = args[1]; const targetTokenId = args[2]; const casterToken = getObj('graphic', casterTokenId); const targetToken = getObj('graphic', targetTokenId); if (!casterToken || !targetToken) { sendChat('System', 'Invalid token IDs provided.'); return; } const casterCharacter = getObj('character', casterToken.get('represents')); if (!casterCharacter) { sendChat('System', 'This token does not represent a character.'); return; } // Check current player page const currentPageId = Campaign().get('playerpageid'); const currentPage = getObj('page', currentPageId); const pageName = currentPage.get('name'); if (!['GHUD', 'PlanetaryNav', 'SystemNav'].includes(pageName)) { sendChat('System', 'Ship to ship weapons cannot be fired from this screen.'); return; } // Check if character has "Torpedo Launcher" const torpedoWeapon = findObjs({ _type: 'attribute', _characterid: casterCharacter.id, name: 'repeating_inventory_$X_itemname', current: 'Torpedo Launcher' })[0]; if (!torpedoWeapon) { sendChat('System', 'This token does not have a torpedo launcher.'); return; } // Check ammo const ammoAttribute = findObjs({ _type: 'attribute', _characterid: casterCharacter.id, name: 'repeating_inventory_$X_resource' }).find(attr => attr.get('current') > 0); if (!ammoAttribute || ammoAttribute.get('current') <= 0) { sendChat('System', 'This token is out of torpedoes.'); return; } // Deduct ammo ammoAttribute.set('current', ammoAttribute.get('current') - 1); // Get locations const casterLocation = { left: casterToken.get('left'), top: casterToken.get('top') }; const targetLocation = { left: targetToken.get('left'), top: targetToken.get('top') }; // Instantiate a token at caster location representing the torpedo const torpedoToken = createObj('graphic', { _pageid: casterToken.get('_pageid'), imgsrc: ' <a href="https://s3.amazonaws.com/files.d20.io/images/366403456/xvVdL5KBgB6TMpyNb6-jTQ/med.webm?1699199637" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/366403456/xvVdL5KBgB6TMpyNb6-jTQ/med.webm?1699199637</a> ', layer: 'objects', left: casterLocation.left, top: casterLocation.top, width: 24, // Assuming the hex size corresponds to 24 height: 24, name: 'weapon_torpedo', bar2_value: targetTokenId, bar2_max: JSON.stringify(targetLocation), // Store the target location in bar2_max gmnotes: '20' // Adds “20” to gmnotes }); // Send a red ping to the caster location sendPing(casterLocation.left, casterLocation.top, casterToken.get('_pageid'), null, true, 'red'); // Play sound using !roll20AM //sendChat('System', `!roll20AM --audio,play|${randomTrackFromPlaylist('GloriaTorpedoIncoming')}`); }); // Helper function to randomly select a track from a playlist function randomTrackFromPlaylist(playlistName) { const playlist = findObjs({ _type: 'jukeboxtrack', playlist: playlistName }); if (playlist.length > 0) { const track = playlist[Math.floor(Math.random() * playlist.length)]; return track.get('title'); } return ''; }