# FVTT Marco 指令紀錄 ###### tags: `FVTT` `Macro` ```javascript= //讀取特定ID的 actors 資料,get中的ID可以藉由點取內建的複製ID獲得 let Actor = game.actors.get('eQWaJFpubH0ueT27'); //開啟特定ID角色卡。 Actor.sheet.render(true); //關閉特定ID角色卡。(只能關閉用上面方法開啟的角色表) Actor.sheet.close(); //讓使用者主動切換遊戲場景 game.scenes.getName('場景名稱').view() //重新更新房間內的所有縮圖(通常用於導入資料後沒有縮圖的情況) (async () => { for (const scene of game.scenes) { const t = await scene.createThumbnail({img: scene.img || undefined}); if (t?.thumb) { console.log(`Regenerated thumbnail for ${scene.name}`); await scene.update({thumb: t.thumb}); } } })(); //如果有使用 Multiface Tiles 並且想用 Macro 來一次替換場景內的圖(僅限只設定一張圖的情況) //替換成下一張圖 const tiles = canvas.scene.tiles.filter(t => t.getFlag("multiface-tiles", "altImages")); const updates = tiles.map(t => ({_id: t.id, img: t.getFlag("multiface-tiles", "altImages")[0]})); await canvas.scene.updateEmbeddedDocuments("Tile", updates); //替換回原圖 const tiles = canvas.scene.tiles.filter(t => t.getFlag("multiface-tiles", "originalImage")); const updates = tiles.map(t => ({ _id: t.id, img: t.getFlag("multiface-tiles", "originalImage")})); await canvas.scene.updateEmbeddedDocuments("Tile", updates); ``` # Cypher System ```javascript= //CypherSystem marco 快速設定token for (let actor of game.actors) { if (["pc", "community", "vehicle"].includes(actor.type)) { actor.update({ "prototypeToken.displayName": CONST.TOKEN_DISPLAY_MODES.HOVER, "prototypeToken.disposition": CONST.TOKEN_DISPOSITIONS.NEUTRAL, "prototypeToken.actorLink": true }); } else if (actor.type == "npc") { actor.update({ "prototypeToken.bar1": {"attribute": "pools.health"}, "prototypeToken.bar2": {"attribute": "basic.level"}, "prototypeToken.displayName": CONST.TOKEN_DISPLAY_MODES.OWNER_HOVER, "prototypeToken.displayBars": CONST.TOKEN_DISPLAY_MODES.OWNER, "prototypeToken.disposition": CONST.TOKEN_DISPOSITIONS.NEUTRAL }); } else if (actor.type == "companion") { actor.update({ "prototypeToken.bar1": {"attribute": "pools.health"}, "prototypeToken.bar2": {"attribute": "basic.level"}, "prototypeToken.displayName": CONST.TOKEN_DISPLAY_MODES.OWNER_HOVER, "prototypeToken.displayBars": CONST.TOKEN_DISPLAY_MODES.OWNER, "prototypeToken.disposition": CONST.TOKEN_DISPOSITIONS.NEUTRAL, "prototypeToken.actorLink": true }); } else if (actor.type == "marker") { actor.update({ "prototypeToken.bar1": {"attribute": "pools.quantity"}, "prototypeToken.bar2": {"attribute": "basic.level"}, "prototypeToken.displayName": CONST.TOKEN_DISPLAY_MODES.HOVER, "prototypeToken.displayBars": CONST.TOKEN_DISPLAY_MODES.ALWAYS, "prototypeToken.disposition": CONST.TOKEN_DISPOSITIONS.NEUTRAL }); } } //運用PACK的表格來配合取物 (async () => { const rollTablePack = game.packs.get("cyphersystem-compendium.cypher-roll-tables"); rollTablePack.getIndex(); console.log(rollTablePack); ManifestRollTable = rollTablePack.index.find(t => t.name === 'Manifest Cyphers')._id; FantasticRollTable = rollTablePack.index.find(t => t.name === 'Fantastic Cyphers')._id; SubtleRollTable = rollTablePack.index.find(t => t.name === 'Subtle Cyphers')._id; let Manifest_roll = await rollTablePack.getDocument(ManifestRollTable).then(table => table.roll()); let Fantastic_roll = await rollTablePack.getDocument(FantasticRollTable).then(table => table.roll()); let Subtle_roll = await rollTablePack.getDocument(SubtleRollTable).then(table => table.roll()); const compendiumName = Manifest_roll.results[0].documentCollection; let results_html = `<h2>隨機秘具</h2> <strong>有形秘具:</strong>@Compendium[${compendiumName}.${Manifest_roll.results[0].documentId}]<br> <strong>無形秘具:</strong>@Compendium[${compendiumName}.${Fantastic_roll.results[0].documentId}]<br> <strong>奇異秘具:</strong>@Compendium[${compendiumName}.${Subtle_roll.results[0].documentId}]<br>`; let chatData = { user: game.user._id, speaker: ChatMessage.getSpeaker(), content: results_html, whisper: game.users.filter(u => u.isGM).map(u => u._id) }; ChatMessage.create(chatData, {}); })(); ``` # Invisible sun 用來替換 Dice so Nice! 擲骰內容的巨集 ```javascript= game.dice3d.addDicePreset({ type: 'd10', labels: ['👁️', '1', '2', '3', '4', '5', '6', '7', '8', '9'], values: {min:0,max:9}, system: 'standard' }, "d10"); ``` 擲骰巨集(中文化) V12 ```javascript= const content = ` <form> <div class="form-group"> <label>骰子數</label> <input type="number" value="1" name="rolls"> </div> <div class="form-group"> <label>挑戰等級</label> <input type="number" value="5" name="diff"> </div> </form> `; const data = await Dialog.wait({ title: "擲骰器", content, buttons: { ok: { label: "擲骰!", callback: ([html]) => new FormDataExtended(html.querySelector("form")).object } }, default: "none" // hacky way to avoid the v11 event bug passing to the button causing it to auto close. }); let message = ``; let results = ``; let suxCount = 0; let fluxCount = 0; let numRolls=parseInt(`${data.rolls}`); let diff=parseInt(`${data.diff}`); if (numRolls < 1) { numRolls = 1; } else if (numRolls > 5) { numRolls = 5; } if (diff < 1) { diff = 1; } else if (diff > 17) { diff = 17; } for (let i=0; i < numRolls; i++) { let roll = await new Roll(`1d10`).roll(); let tot = roll.total if (roll.total === 10) { tot = 0 } if (i > 0 && tot === 0) { fluxCount++; } results += `${tot}` if (tot >= diff) { suxCount++; } if (i < numRolls - 1) { results += `, ` } if (i === 0) { game.dice3d.showForRoll(roll, game.user, true) } if (i > 0) { roll.dice[0].options.appearance = { colorset: "custom", foreground: "#59CBE8", background: "#000000", outline: "#000000", edge: "#59CBE8", material: "glass", font: "Luminari", emissive: "#59CBE8", system: "standard" }; game.dice3d.showForRoll(roll, game.user, true) } } let fluxType = ''; switch(fluxCount) { case 0: fluxType = '無影響'; break; case 1: fluxType = '弱效'; break; case 2: fluxType = '強效'; break; default: fluxType = '宏大'; } message = `難度:${diff}<br>擲骰結果:${results}<br>成功次數: ${suxCount}<br>魔法流:${fluxType}` ChatMessage.create({ user: game.user._id, speaker: ChatMessage.getSpeaker({token: actor}), content: message}); ``` # Cyberpunk Red ```javascript= /* * 其他對應形狀 types: { circle: "Circle", cone: "Cone", rect: "Rectangle", ray: "Ray" }, */ //創建10米方塊爆炸模板 let templateData = { t: "rect", user: game.user._id, x: 500, y: 500, direction: 44.9, distance: 14.1, borderColor: "#000000", fillColor: "#cc2865", }; let theTemplate= await MeasuredTemplateDocument.create(templateData, {parent: canvas.scene}); //爆炸偏移macro,網格50 const content = ` <form> <div class="form-group"> <label>異動ID</label> <input value="" name="id"> </div> </form> `; new Dialog({ title: "爆炸偏移調整", content, buttons: { roll: { label: "確定", callback: (html) => { let dataId = html.find("[name=id]")[0].value; let getData = canvas.scene.getEmbeddedDocument('MeasuredTemplate',dataId); console.log(getData,dataId); let message = ``; let results = ``; let r1 = new Roll("1d10").evaluate({async: false}); let xyr1 = new Roll("1d2").evaluate({async: false}); let r2 = new Roll("1d10").evaluate({async: false}); let xyr2 = new Roll("1d2").evaluate({async: false}); let Xroll = r1.total; let Yroll = r2.total; let Xrollpixel = ''; let Yrollpixel = ''; let Xstr =''; let Ystr =''; if(xyr1.total==1){ Xrollpixel=Xroll*25; Xstr ='向右偏移'; }else{ Xrollpixel=Xroll*-25; Xstr ='向左偏移'; } if(xyr2.total==1){ Yrollpixel =Yroll*25; Ystr ='向下偏移'; }else{ Yrollpixel =Yroll*-25; Ystr ='向上偏移'; } const updates = [{_id: dataId, x: getData.x+Xrollpixel , y: getData.y+Yrollpixel }]; const updated = canvas.scene.updateEmbeddedDocuments('MeasuredTemplate',updates); console.log(getData,dataId); message = `<h2>爆炸偏移</h2> <strong>X軸偏移:</strong>${Xstr}${Xroll}米<br> <strong>Y軸偏移:</strong>${Ystr}${Yroll}米<br>`; ChatMessage.create({ user: game.user._id, speaker: ChatMessage.getSpeaker({token: actor}), content: message}); } }, cancel: { label: "取消" } }, default: "cancel" }).render(true); //清除所有測量模板 canvas.scene.deleteEmbeddedDocuments('MeasuredTemplate',canvas.scene.templates.map(i=>i.id)); ``` # RoleMaster Unified ```javascript= /r {3d100kh2, 3d100kh2, 3d100kh2, 3d100kh2, 3d100kh2, 3d100kh2, 3d100kh2, 3d100kh2, 3d100kh2, 3d100kh2} # Character Ability Scores //通用檢定擲骰介面 const content = ` <form> <div class="form-group"> <label>檢定類型</label> <select name="check_method"> <option>絕對檢定</option> <option>百分比檢定</option> <option>施法檢定</option> </select> </div> <div class="form-group"> <label>難度</label> <select name="difficulty"> <option value="+70">休閒 (+70)</option> <option value="+50">輕鬆 (+50)</option> <option value="+30">常規 (+30)</option> <option value="+20">簡單 (+20)</option> <option value="+10">還行 (+10)</option> <option value="+0" selected>中等 (+0)</option> <option value="-10">困難 (-10)</option> <option value="-20">非常難 (-20)</option> <option value="-30">極其困難 (-30)</option> <option value="-50">異想天開 (-50)</option> <option value="-70">難以置信 (-70)</option> <option value="-100">幾乎不可能 (-100)</option> </select> </div> <div class="form-group"> <label>修改(填上每個±)</label> <input value="+0" type="text" name="total"> </div> </form> `; new Dialog({ title: "技能擲骰 1d100 OE", content, buttons: { roll: { label: "確定", callback: (html) => { let r = new Roll("1d100").evaluate({async: false}); game.dice3d.showForRoll(r, game.user, true); let Xroll = r.total; let RExplode = new Roll("1d100>=96x").evaluate({async: false}); RExplode.dice[0].options.appearance = { colorset: "custom", foreground: "#59CBE8", background: "#000000", outline: "#000000", edge: "#59CBE8", material: "glass", font: "Luminari", emissive: "#59CBE8", system: "standard" }; let html_total = html.find("[name=total]")[0].value; let html_check_method = html.find("[name=check_method]")[0].value; let html_difficulty = html.find("[name=difficulty]")[0].value; let total_number = 0; let total_string = ""; let total_string_css = ""; let result_string = ""; if(html_total==="") html_total=0; if(Xroll<=5){ game.dice3d.showForRoll(RExplode, game.user, true); total_number = parseInt(Xroll)-parseInt(RExplode.total)+parseInt(html_difficulty)+eval(html_total); total_string = `${Xroll}(擲骰)-${RExplode.total}(擲骰)${html_difficulty}(難度)${html_total}(修改)`; total_string_css="color:red;"; } else if (Xroll>=96) { game.dice3d.showForRoll(RExplode, game.user, true); total_number = parseInt(Xroll)+parseInt(RExplode.total)+parseInt(html_difficulty)+eval(html_total); total_string = `${Xroll}(擲骰)+${RExplode.total}(擲骰)${html_difficulty}(難度)${html_total}(修改)`; total_string_css="color:green;"; } else{ total_number = parseInt(Xroll)+parseInt(html_difficulty)+eval(html_total); total_string = `${Xroll}(擲骰)${html_difficulty}(難度)${html_total}(修改)`; } if(html_check_method!="百分比檢定"){ if(html_check_method=="施法檢定"){ if(total_number<=0){ result_string = "法術失敗"; }else { result_string = "法術成功"; } } else{ if(total_number<1){ result_string = "絕對失敗"; }else if (total_number <= 75){ result_string = "失敗"; }else if (total_number <= 100){ result_string = "部份成功"; }else if (total_number <= 175){ result_string = "成功"; }else if (total_number > 175){ result_string = "絕對成功"; } if (Xroll == 66){ result_string += " (異常事件)"; } else if (Xroll == 33 || Xroll == 77){ result_string += " (檢查損壞檢定)"; } } }else{ if(total_number<=-100){ result_string = "E 嚴重度,查看相應重擊表"; }else if (total_number <= -80){ result_string = "D 嚴重度,查看相應重擊表"; }else if (total_number <= -60){ result_string = "C 嚴重度,查看相應重擊表"; }else if (total_number <= -40){ result_string = "B 嚴重度,查看相應重擊表"; }else if (total_number <= -20){ result_string = "A 嚴重度,查看相應重擊表"; }else if (total_number <= 0){ result_string = "行動失敗"; }else if (total_number <= 10){ result_string = "5% 進展"; }else if (total_number <= 20){ result_string = "10% 進展"; }else if (total_number <= 30){ result_string = "20% 進展"; }else if (total_number <= 40){ result_string = "30% 進展"; }else if (total_number <= 50){ result_string = "40% 進展"; }else if (total_number <= 60){ result_string = "50% 進展"; }else if (total_number <= 70){ result_string = "60% 進展"; }else if (total_number <= 80){ result_string = "70% 進展"; }else if (total_number <= 90){ result_string = "80% 進展"; }else if (total_number <= 100){ result_string = "90% 進展"; }else if (total_number <= 130){ result_string = "100% 進展"; }else if (total_number <= 160){ result_string = "110% 進展"; }else if (total_number <= 190){ result_string = "120% 進展"; }else if (total_number <= 220){ result_string = "130% 進展"; }else if (total_number <= 250){ result_string = "140% 進展"; }else if (total_number <= 280){ result_string = "150% 進展"; }else if (total_number > 280){ result_string = "卓越進展"; } } message = `<h2>檢定</h2> <strong>檢定類型:${html_check_method}<br> <strong style="${total_string_css}">過程:${total_string}</strong><br> <strong>總計:${total_number}<br> <hr> <strong style="font-size:18px;">結果:${result_string}<br>`; ChatMessage.create({ user: game.user._id, speaker: ChatMessage.getSpeaker({token: actor}), content: message}); } }, cancel: { label: "取消" } }, default: "cancel" }).render(true); //抵抗檢定 const content = ` <form> <div class="form-group"> <label>RR DC(預設50)</label> <input value="50" type="text" name="rr_check"> </div> <div class="form-group"> <label>修改(填上每個±)</label> <input value="+0" type="text" name="total"> </div> </form> `; new Dialog({ title: "抵抗檢定 1d100 OE", content, buttons: { roll: { label: "確定", callback: (html) => { let r = new Roll("1d100").evaluate({async: false}); game.dice3d.showForRoll(r, game.user, true); let Xroll = r.total; let RExplode = new Roll("1d100>=96x").evaluate({async: false}); RExplode.dice[0].options.appearance = { colorset: "custom", foreground: "#59CBE8", background: "#000000", outline: "#000000", edge: "#59CBE8", material: "glass", font: "Luminari", emissive: "#59CBE8", system: "standard" }; let html_total = html.find("[name=total]")[0].value; let html_rr_check = html.find("[name=rr_check]")[0].value; let total_number = 0; let total_string = ""; let total_string_css = ""; let result_string = ""; if(html_total==="") html_total=0; if(Xroll<=5){ game.dice3d.showForRoll(RExplode, game.user, true); total_number = parseInt(Xroll)-parseInt(RExplode.total)+eval(html_total); total_string = `${Xroll}(擲骰)-${RExplode.total}(擲骰)${html_total}(修改)`; total_string_css="color:red;"; } else if (Xroll>=96) { game.dice3d.showForRoll(RExplode, game.user, true); total_number = parseInt(Xroll)+parseInt(RExplode.total)+eval(html_total); total_string = `${Xroll}(擲骰)+${RExplode.total}(擲骰)${html_total}(修改)`; total_string_css="color:green;"; } else{ total_number = parseInt(Xroll)+eval(html_total); total_string = `${Xroll}(擲骰)${html_total}(修改)`; } var compareNumber = Math.abs(total_number-parseInt(html_rr_check)); if (total_number < parseInt(html_rr_check)){ if(compareNumber > 100){ result_string = "極端失敗"; } else if(compareNumber > 50){ result_string = "嚴重失敗"; } else if(compareNumber > 25){ result_string = "中等失敗"; } else if(compareNumber > 0){ result_string = "輕微失敗"; } }else{ result_string = "抵抗成功!" } message = `<h2>抵抗檢定</h2> <strong>RR DC:${html_rr_check}<br> <strong style="${total_string_css}">過程:${total_string}</strong><br> <strong>差額比較:${total_number} vs. ${html_rr_check} = ${compareNumber}<br> <hr> <strong style="font-size:18px;">結果:${result_string}<br>`; ChatMessage.create({ user: game.user._id, speaker: ChatMessage.getSpeaker({token: actor}), content: message}); } }, cancel: { label: "取消" } }, default: "cancel" }).render(true); //攻擊檢定 const content = ` <form> <div class="form-group"> <label>難度</label> <select name="difficulty"> <option value="+70">休閒 (+70)</option> <option value="+50">輕鬆 (+50)</option> <option value="+30">常規 (+30)</option> <option value="+20">簡單 (+20)</option> <option value="+10">還行 (+10)</option> <option value="+0" selected>中等 (+0)</option> <option value="-10">困難 (-10)</option> <option value="-20">非常難 (-20)</option> <option value="-30">極其困難 (-30)</option> <option value="-50">異想天開 (-50)</option> <option value="-70">難以置信 (-70)</option> <option value="-100">幾乎不可能 (-100)</option> </select> </div> <div class="form-group"> <label>攻擊(填上每個±)</label> <input value="+0" type="text" name="total"> </div> <div class="form-group"> <label>重擊(填上每個±)</label> <input value="+0" type="text" name="cri_total"> </div> </form> `; new Dialog({ title: "攻擊檢定", content, buttons: { roll: { label: "確定", callback: (html) => { let r = new Roll("1d100").evaluate({async: false}); game.dice3d.showForRoll(r, game.user, true); let Xroll = r.total; let RExplode = new Roll("1d100>=96x").evaluate({async: false}); RExplode.dice[0].options.appearance = { colorset: "custom", foreground: "#59CBE8", background: "#000000", outline: "#000000", edge: "#59CBE8", material: "glass", font: "Luminari", emissive: "#59CBE8", system: "standard" }; let html_total = html.find("[name=total]")[0].value; let html_difficulty = html.find("[name=difficulty]")[0].value; let total_number = 0; let total_string = ""; let total_string_css = ""; let result_string = ""; if (html_total === "") html_total = 0; if (Xroll >= 96) { game.dice3d.showForRoll(RExplode, game.user, true); total_number = parseInt(Xroll) + parseInt(RExplode.total) + parseInt(html_difficulty) + eval(html_total); total_string = `${Xroll}(擲骰)+${RExplode.total}(擲骰)${html_difficulty}(難度)${html_total}(修改)`; total_string_css = "color:green;"; } else { total_number = parseInt(Xroll) + parseInt(html_difficulty) + eval(html_total); total_string = `${Xroll}(擲骰)${html_difficulty}(難度)${html_total}(修改)`; } let cri_bouns = 0; if (total_number >= 180) { cri_bouns = Math.floor((total_number - 175) / 5); } let total_cri_string = ""; let html_cri_total = html.find("[name=cri_total]")[0].value; if (html_cri_total === "") html_cri_total = 0; let hitroll = new Roll("1d100").evaluate({async: false}).total; let hitdice = hitroll + eval(html_cri_total) + cri_bouns; total_cri_string = `${hitroll}(擲骰)${html_cri_total}(修改)+${cri_bouns}(加重攻擊)`; const HitLocation = { "Head": "命中頭部", "Chest": "命中胸部", "Abdomen": "命中腹部", "LegLeft": "命中左腿部", "LegRight": "命中右腿部", "ArmLeft": "命中左手臂", "ArmRight": "命中右手臂", } hitdice = (hitdice >= 100) ? 100 : hitdice; if ( hitdice == 1 || (hitdice >= 16 && hitdice <= 20) || (hitdice >= 81 && hitdice <= 85) || hitdice >= 100 ) { result_string = HitLocation.Head; } else if ( (hitdice >= 2 && hitdice <= 3) || (hitdice >= 21 && hitdice <= 25) || (hitdice >= 76 && hitdice <= 80) || (hitdice >= 98 && hitdice <= 99) ) { result_string = HitLocation.Chest; } else if ( (hitdice >= 4 && hitdice <= 5) || (hitdice >= 26 && hitdice <= 35) || (hitdice == 66) || (hitdice >= 96 && hitdice <= 97) ) { result_string = HitLocation.Abdomen; } else if ( (hitdice >= 6 && hitdice <= 10) || (hitdice >= 36 && hitdice <= 45) || (hitdice >= 67 && hitdice <= 75) || (hitdice >= 91 && hitdice <= 95) ) { if (hitdice % 2 == 1) { result_string = HitLocation.LegLeft; } else { result_string = HitLocation.LegRight; } } else if ( (hitdice >= 11 && hitdice <= 15) || (hitdice >= 46 && hitdice <= 55) || (hitdice >= 56 && hitdice <= 65) || (hitdice >= 86 && hitdice <= 90) ) { if (hitdice % 2 == 1) { result_string = HitLocation.ArmLeft; } else { result_string = HitLocation.ArmRight; } } if (Xroll == 66) { result_string += " (異常事件)"; } else if (Xroll == 33 || Xroll == 77) { result_string += " (檢查損壞檢定)"; } if(total_number <= 67){ result_string = "完全失誤/除非是範圍法術"; } message = `<h2>攻擊</h2> <strong style="${total_string_css}">過程:${total_string}</strong><br> <strong>攻擊結果:${total_number}<br> <hr> <strong>過程:${total_cri_string}</strong><br> <strong>重擊結果:${hitdice}<br> <hr> <strong style="font-size:18px;">結果:${result_string}<br>`; ChatMessage.create({ user: game.user._id, speaker: ChatMessage.getSpeaker({token: actor}), content: message }); } } }, cancel: { label: "取消" }, default: "cancel" }).render(true); ``` 暗影之痕擲骰(V12) ```javascript= const content = `<form> <div class="form-group"> <label>檢定骰池</label> <div class="form-fields"> <input type="number" name="amount" value="1"> </div> </div> </form>`; const data = await Dialog.wait({ title: "擲骰器", content, buttons: { ok: { label: "擲骰!", callback: ([html]) => new FormDataExtended(html.querySelector("form")).object } }, default: "none" // hacky way to avoid the v11 event bug passing to the button causing it to auto close. }); let message = ``; let results = ``; const formula = `${data.amount}d6`; const r = await new Roll(formula).roll(); game.dice3d.showForRoll(r, game.user, true) const sequence = r.dice[0].results.map(e => e.result); const total = r.dice[0].results.reduce((total, e, i) => { if (e.result === 6) { total += 2; } else if (e.result > 3) { total += 1; } return total; // 直接返回累加後的總和 }, 0); // 初始值為 0 message = `擲骰結果:${sequence}<br>成功次數: ${total}` ChatMessage.create({ user: game.user._id, speaker: ChatMessage.getSpeaker({token: actor}), content: message}); ```