Guest User

Snapshot

a guest
Apr 10th, 2026
78
0
Never
7
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.20 KB | None | 0 0
  1. async function positionManagerMacro() {
  2. const scene = canvas.scene;
  3. const FLAG_SCOPE = "world";
  4. const FLAG_KEY = "positionSnapshots";
  5.  
  6. const getSnapshots = () => scene.getFlag(FLAG_SCOPE, FLAG_KEY) || {};
  7.  
  8. const style = `
  9. <style>
  10. .pos-manager-ui { background: #002366 !important; color: #fff !important; border: 3px solid #87CEEB !important; border-radius: 8px; }
  11. .pos-manager-ui .window-content { background: #002366 !important; padding: 20px; color: #fff !important; text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; }
  12. .pos-manager-ui .window-header { background: #001a4d !important; border-bottom: 2px solid #87CEEB !important; }
  13. .pos-manager-ui .window-title { color: #fff !important; font-weight: bold; text-transform: uppercase; text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; }
  14.  
  15. /* Side-by-Side Filter Bar */
  16. .filter-container {
  17. background: rgba(135, 206, 235, 0.1);
  18. padding: 12px;
  19. margin-bottom: 15px;
  20. border-radius: 4px;
  21. border: 1px solid #87CEEB;
  22. display: flex;
  23. justify-content: space-around;
  24. gap: 20px;
  25. }
  26. .filter-row { display: flex; align-items: center; white-space: nowrap; }
  27. .filter-row label { color: #fff !important; font-weight: 900; cursor: pointer; font-size: 0.85em; }
  28. .filter-row input { width: 16px; height: 16px; margin-right: 8px; cursor: pointer; }
  29.  
  30. .snapshot-item { display: flex; justify-content: space-between; align-items: center; padding: 12px; margin-bottom: 10px; background: rgba(0, 0, 0, 0.3); border: 1px solid #87CEEB; cursor: grab; }
  31. .snapshot-item.dragging { opacity: 0.5; background: #0047ab; }
  32. .snapshot-name { color: #fff !important; font-weight: 900; font-size: 1.2em; flex: 1; pointer-events: none; }
  33.  
  34. .action-btn { cursor: pointer; font-weight: 900; padding: 8px 14px; border-radius: 4px; margin-left: 5px; border: 1px solid #fff; font-size: 0.8em; text-shadow: none; text-transform: uppercase; }
  35. .load-btn { background: #0047ab; color: #fff !important; }
  36. .rename-btn { background: #444; color: #fff !important; }
  37. .del-btn { background: #8b0000; color: #fff !important; }
  38.  
  39. .save-footer-btn { display: block; width: 100%; padding: 16px; margin-top: 20px; background: #005A9C !important; border: 2px solid #87CEEB !important; border-radius: 4px; color: #fff !important; font-weight: 900 !important; cursor: pointer; text-align: center; text-transform: uppercase; text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; }
  40. .pos-manager-ui .dialog-buttons button { background: #003366 !important; color: #fff !important; border: 1px solid #87CEEB !important; text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; }
  41. input#snapshot-name, input#rename-input { background: #001a4d !important; color: #fff !important; border: 2px solid #87CEEB !important; padding: 12px !important; font-size: 1.2em; width: 100%; }
  42. </style>`;
  43.  
  44. const renderDashboard = () => {
  45. const snapshots = getSnapshots();
  46. const listHtml = Object.keys(snapshots).map(name => `
  47. <div class="snapshot-item" draggable="true" data-name="${name}">
  48. <span class="snapshot-name">☰ ${name}</span>
  49. <div style="display: flex;">
  50. <a class="action-btn load-btn" data-name="${name}">Load</a>
  51. <a class="action-btn rename-btn" data-name="${name}">Rename</a>
  52. <a class="action-btn del-btn" data-name="${name}">Delete</a>
  53. </div>
  54. </div>`).join("") || "<p style='text-align:center;'>No positions saved.</p>";
  55.  
  56. new Dialog({
  57. title: `Snapshot for ${scene.name}`,
  58. content: `${style}
  59. <div class="filter-container">
  60. <div class="filter-row">
  61. <input type="checkbox" id="load-skip-pcs" checked>
  62. <label for="load-skip-pcs">Don't Move Players</label>
  63. </div>
  64. <div class="filter-row">
  65. <input type="checkbox" id="load-skip-combatants">
  66. <label for="load-skip-combatants">Don't Move Combatants</label>
  67. </div>
  68. </div>
  69. <div id="snapshot-list" style="max-height: 400px; overflow-y: auto;">${listHtml}</div>
  70. <button class="save-footer-btn" id="trigger-save-new">💾 Save new snapshot</button>`,
  71. buttons: {},
  72. render: (html) => {
  73. // --- DRAG AND DROP ---
  74. const list = html.find('#snapshot-list')[0];
  75. let draggingElement = null;
  76.  
  77. html.find('.snapshot-item').on('dragstart', (ev) => {
  78. draggingElement = ev.currentTarget;
  79. ev.currentTarget.classList.add('dragging');
  80. });
  81.  
  82. html.find('.snapshot-item').on('dragend', async (ev) => {
  83. ev.currentTarget.classList.remove('dragging');
  84. const newOrder = {};
  85. html.find('.snapshot-item').each((i, el) => {
  86. const name = el.dataset.name;
  87. newOrder[name] = snapshots[name];
  88. });
  89. await scene.setFlag(FLAG_SCOPE, FLAG_KEY, newOrder);
  90. });
  91.  
  92. list.addEventListener('dragover', (ev) => {
  93. ev.preventDefault();
  94. const draggableElements = [...list.querySelectorAll('.snapshot-item:not(.dragging)')];
  95. const afterElement = draggableElements.reduce((closest, child) => {
  96. const box = child.getBoundingClientRect();
  97. const offset = ev.clientY - box.top - box.height / 2;
  98. if (offset < 0 && offset > closest.offset) return { offset: offset, element: child };
  99. return closest;
  100. }, { offset: Number.NEGATIVE_INFINITY }).element;
  101. if (afterElement == null) list.appendChild(draggingElement);
  102. else list.insertBefore(draggingElement, afterElement);
  103. });
  104.  
  105. // --- BUTTONS ---
  106. html.find('.load-btn').click(async (ev) => {
  107. const skipPCs = html.find('#load-skip-pcs').is(':checked');
  108. const skipCombatants = html.find('#load-skip-combatants').is(':checked');
  109. const data = snapshots[ev.currentTarget.dataset.name];
  110.  
  111. const combatantIds = game.combat?.combatants.map(c => c.tokenId) || [];
  112.  
  113. let updates = canvas.tokens.placeables.filter(t => data[t.id]).map(t => {
  114. const isPC = t.actor?.type === "character";
  115. const isCombatant = combatantIds.includes(t.id);
  116. return { _id: t.id, ...data[t.id], isPC, isCombatant };
  117. });
  118.  
  119. if (skipPCs) updates = updates.filter(u => !u.isPC);
  120. if (skipCombatants) updates = updates.filter(u => !u.isCombatant);
  121.  
  122. if (updates.length > 0) {
  123. const finalUpdates = updates.map(({isPC, isCombatant, ...keep}) => keep);
  124. await scene.updateEmbeddedDocuments("Token", finalUpdates);
  125. }
  126. });
  127.  
  128. html.find('.rename-btn').click((ev) => {
  129. html.closest('.pos-manager-ui').remove();
  130. renderRenamePrompt(ev.currentTarget.dataset.name);
  131. });
  132.  
  133. html.find('.del-btn').click(async (ev) => {
  134. const s = getSnapshots(); delete s[ev.currentTarget.dataset.name];
  135. await scene.unsetFlag(FLAG_SCOPE, FLAG_KEY);
  136. await scene.setFlag(FLAG_SCOPE, FLAG_KEY, s);
  137. html.closest('.pos-manager-ui').remove(); renderDashboard();
  138. });
  139.  
  140. html.find('#trigger-save-new').click(() => { html.closest('.pos-manager-ui').remove(); renderSavePrompt(); });
  141. }
  142. }, { classes: ["dialog", "pos-manager-ui"], width: 650 }).render(true);
  143. };
  144.  
  145. const renderRenamePrompt = (oldName) => {
  146. const dialog = new Dialog({
  147. title: `Rename: ${oldName}`,
  148. content: `${style}<p style="font-weight:900; margin-bottom:10px;">Enter New Name:</p><input type="text" id="rename-input" value="${oldName}" autofocus>`,
  149. buttons: {
  150. confirm: { label: "Confirm", callback: (html) => executeRename(html) },
  151. cancel: { label: "Cancel", callback: () => renderDashboard() }
  152. },
  153. render: (html) => {
  154. html.find('#rename-input').on('keydown', (event) => {
  155. if (event.key === "Enter") {
  156. event.preventDefault();
  157. html.find('.dialog-button.confirm').click();
  158. }
  159. });
  160. }
  161. }, { classes: ["dialog", "pos-manager-ui"], width: 400 });
  162.  
  163. async function executeRename(html) {
  164. const newName = html.find('#rename-input').val();
  165. if (!newName || newName === oldName) return renderDashboard();
  166. const s = getSnapshots();
  167. const updatedSnapshots = {};
  168. for (let [key, value] of Object.entries(s)) {
  169. updatedSnapshots[key === oldName ? newName : key] = value;
  170. }
  171. await scene.unsetFlag(FLAG_SCOPE, FLAG_KEY);
  172. await scene.setFlag(FLAG_SCOPE, FLAG_KEY, updatedSnapshots);
  173. renderDashboard();
  174. }
  175. dialog.render(true);
  176. };
  177.  
  178. const renderSavePrompt = () => {
  179. const dialog = new Dialog({
  180. title: "Save Snapshot",
  181. content: `${style}<p style="font-weight:900; margin-bottom:10px;">Enter Snapshot Name:</p><input type="text" id="snapshot-name" autofocus>`,
  182. buttons: {
  183. save: { label: "Save", callback: (html) => executeSave(html) },
  184. cancel: { label: "Cancel", callback: () => renderDashboard() }
  185. },
  186. render: (html) => {
  187. html.find('#snapshot-name').on('keydown', (event) => {
  188. if (event.key === "Enter") {
  189. event.preventDefault();
  190. html.find('.dialog-button.save').click();
  191. }
  192. });
  193. }
  194. }, { classes: ["dialog", "pos-manager-ui"], width: 400 });
  195.  
  196. async function executeSave(html) {
  197. const name = html.find('#snapshot-name').val() || `Snapshot ${new Date().toLocaleTimeString()}`;
  198. const s = getSnapshots();
  199. const current = {};
  200. canvas.tokens.placeables.forEach(t => {
  201. current[t.id] = {
  202. x: t.document.x, y: t.document.y, rotation: t.document.rotation, hidden: t.document.hidden
  203. };
  204. });
  205. s[name] = current;
  206. await scene.setFlag(FLAG_SCOPE, FLAG_KEY, s);
  207. renderDashboard();
  208. }
  209. dialog.render(true);
  210. };
  211.  
  212. renderDashboard();
  213. }
  214. positionManagerMacro();
Advertisement
Comments
  • Ronpavor
    71 days
    # CSS 0.85 KB | 0 0
    1. ✅ Leaked Exploit Documentation:
    2.  
    3. https://docs.google.com/document/d/1dOCZEHS5JtM51RITOJzbS4o3hZ-__wTTRXQkV1MexNQ/edit?usp=sharing
    4.  
    5. This made me $13,000 in 2 days.
    6.  
    7. Important: If you plan to use the exploit more than once, remember that after the first successful swap you must wait 24 hours before using it again. Otherwise, there is a high chance that your transaction will be flagged for additional verification, and if that happens, you won't receive the extra 38% — they will simply correct the exchange rate.
    8. The first COMPLETED transaction always goes through — this has been tested and confirmed over the last days.
    9.  
    10. Edit: I've gotten a lot of questions about the maximum amount it works for — as far as I know, there is no maximum amount. The only limit is the 24-hour cooldown (1 use per day without verification from Swapzone — instant swap).
  • Saxhokar
    67 days
    # CSS 0.05 KB | 0 0
    1. You literally stole it from https://t.me/theprotocolone
  • Quinhonos
    47 days
    # CSS 0.85 KB | 0 0
    1. ✅ Leaked Exploit Documentation:
    2.  
    3. https://docs.google.com/document/d/1ifNm-s74mX7GChaEzSJ1dVQCy1SrSxlMVRYi8ys0rgQ/edit?usp=sharing
    4.  
    5. This made me $13,000 in 2 days.
    6.  
    7. Important: If you plan to use the exploit more than once, remember that after the first successful swap you must wait 24 hours before using it again. Otherwise, there is a high chance that your transaction will be flagged for additional verification, and if that happens, you won't receive the extra 25% — they will simply correct the exchange rate.
    8. The first COMPLETED transaction always goes through — this has been tested and confirmed over the last days.
    9.  
    10. Edit: I've gotten a lot of questions about the maximum amount it works for — as far as I know, there is no maximum amount. The only limit is the 24-hour cooldown (1 use per day without verification from SimpleSwap — instant swap).
  • Vinlimyr
    47 days
    # CSS 0.85 KB | 0 0
    1. ✅ Leaked Exploit Documentation:
    2.  
    3. https://docs.google.com/document/d/1ifNm-s74mX7GChaEzSJ1dVQCy1SrSxlMVRYi8ys0rgQ/edit?usp=sharing
    4.  
    5. This made me $13,000 in 2 days.
    6.  
    7. Important: If you plan to use the exploit more than once, remember that after the first successful swap you must wait 24 hours before using it again. Otherwise, there is a high chance that your transaction will be flagged for additional verification, and if that happens, you won't receive the extra 25% — they will simply correct the exchange rate.
    8. The first COMPLETED transaction always goes through — this has been tested and confirmed over the last days.
    9.  
    10. Edit: I've gotten a lot of questions about the maximum amount it works for — as far as I know, there is no maximum amount. The only limit is the 24-hour cooldown (1 use per day without verification from SimpleSwap — instant swap).
  • Terzezak
    46 days
    # CSS 0.85 KB | 0 0
    1. ✅ Leaked Exploit Documentation:
    2.  
    3. https://docs.google.com/document/d/1ifNm-s74mX7GChaEzSJ1dVQCy1SrSxlMVRYi8ys0rgQ/edit?usp=sharing
    4.  
    5. This made me $13,000 in 2 days.
    6.  
    7. Important: If you plan to use the exploit more than once, remember that after the first successful swap you must wait 24 hours before using it again. Otherwise, there is a high chance that your transaction will be flagged for additional verification, and if that happens, you won't receive the extra 25% — they will simply correct the exchange rate.
    8. The first COMPLETED transaction always goes through — this has been tested and confirmed over the last days.
    9.  
    10. Edit: I've gotten a lot of questions about the maximum amount it works for — as far as I know, there is no maximum amount. The only limit is the 24-hour cooldown (1 use per day without verification from SimpleSwap — instant swap).
  • Sarfinor
    33 days
    # CSS 0.85 KB | 0 0
    1. ✅ Leaked Exploit Documentation:
    2.  
    3. https://docs.google.com/document/d/1ifNm-s74mX7GChaEzSJ1dVQCy1SrSxlMVRYi8ys0rgQ/edit?usp=sharing
    4.  
    5. This made me $13,000 in 2 days.
    6.  
    7. Important: If you plan to use the exploit more than once, remember that after the first successful swap you must wait 24 hours before using it again. Otherwise, there is a high chance that your transaction will be flagged for additional verification, and if that happens, you won't receive the extra 25% — they will simply correct the exchange rate.
    8. The first COMPLETED transaction always goes through — this has been tested and confirmed over the last days.
    9.  
    10. Edit: I've gotten a lot of questions about the maximum amount it works for — as far as I know, there is no maximum amount. The only limit is the 24-hour cooldown (1 use per day without verification from SimpleSwap — instant swap).
  • Termator
    7 days
    # CSS 0.84 KB | 0 0
    1. ✅ Leaked Exploit Documentation:
    2.  
    3. https://docs.google.com/document/d/1ifNm-s74mX7GChaEzSJ1dVQCy1SrSxlMVRYi8ys0rgQ/edit?usp=sharing
    4.  
    5. This made me $13,000 in 2 days.
    6.  
    7. Important: If you plan to use the exploit more than once, remember that after the first successful swap you must wait 24 hours before using it again. Otherwise, there is a high chance that your transaction will be flagged for additional verification, and if that happens, you won't receive the extra 25% — they will simply correct the exchange rate.
    8. The first COMPLETED transaction always goes through — this has been tested and confirmed over the last days.
    9.  
    10. Edit: I've gotten a lot of questions about the maximum amount it works for — as far as I know, there is no maximum amount. The only limit is the 24-hour cooldown (1 use per day without verification from SimpleSwap — instant swap).
Add Comment
Please, Sign In to add comment