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 '';  }