diff --git a/CHANGELOG.md b/CHANGELOG.md index a676306..45860a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.37.1 + +- added compatibility with the `Disable Game Canvas` setting + # 0.37.0 - added a new `Conditional Icons Size` client setting which offers the possibility to change the size in pixel for the conditional icons shown on mouse over diff --git a/module.json b/module.json index 80c35f5..4e9d808 100644 --- a/module.json +++ b/module.json @@ -19,12 +19,12 @@ "minimum": "11.315", "verified": "11" }, - "version": "0.37.0", + "version": "0.37.1", "manifestPlusVersion": "1.2.0", "url": "https://github.com/reonZ/pf2e-perception", "readme": "https://github.com/reonZ/pf2e-perception/blob/master/README.md", "manifest": "https://raw.githubusercontent.com/reonZ/pf2e-perception/master/module.json", - "download": "https://github.com/reonZ/pf2e-perception/releases/download/0.37.0/module.zip", + "download": "https://github.com/reonZ/pf2e-perception/releases/download/0.37.1/module.zip", "changelog": "https://github.com/reonZ/pf2e-perception/blob/master/CHANGELOG.md", "bugs": "https://github.com/reonZ/pf2e-perception/issues", "styles": [ diff --git a/scripts/main.js b/scripts/main.js index a8205f8..c138896 100644 --- a/scripts/main.js +++ b/scripts/main.js @@ -1,8 +1,8 @@ -(()=>{var It=Object.defineProperty;var wi=(t,e,i)=>e in t?It(t,e,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[e]=i;var a=(t,e)=>It(t,"name",{value:e,configurable:!0});var Oe=(t,e,i)=>(wi(t,typeof e!="symbol"?e+"":e,i),i),At=(t,e,i)=>{if(!e.has(t))throw TypeError("Cannot "+i)};var Wt=(t,e,i)=>(At(t,e,"read from private field"),i?i.call(t):e.get(t)),ue=(t,e,i)=>{if(e.has(t))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(t):e.set(t,i)};var q=(t,e,i)=>(At(t,e,"access private method"),i);var Pe="Compendium.pf2e.other-effects.Item.I9lfZUiCwMiGogVi",P={[void 0]:0,observed:0,concealed:1,hidden:2,undetected:3,unnoticed:4},de=["observed","concealed","hidden","undetected","unnoticed"],U=["none","lesser","standard","greater","greater-prone"],x={[void 0]:0,none:0,lesser:1,standard:2,greater:3,"greater-prone":4},V={cover:"none",visibility:"observed"},ke=["attack-roll","spell-attack-roll"],Dt=[...ke,"skill-check","perception-check"],Ge={cover:"modules/pf2e-perception/images/cover.webp",concealed:"systems/pf2e/icons/conditions/concealed.webp",hidden:"systems/pf2e/icons/conditions/hidden.webp",undetected:"systems/pf2e/icons/conditions/undetected.webp",unnoticed:"systems/pf2e/icons/conditions/unnoticed.webp"};var yt="#000000",ht="#656665",wt="#809e71",$t=["darkness","dance-of-darkness","ravenous-darkness"],Mt=["obscuring-mist","stinking-cloud","noxious-vapors"];var f="pf2e-perception";function X(t){return`modules/${f}/templates/${t}.hbs`}a(X,"templatePath");function h(...t){let e=t.at(-1),i=typeof e=="object",n=i?t.slice(0,-1):t;return n.unshift(f),game.i18n[i?"format":"localize"](n.join("."),e)}a(h,"localize");function w(t,e){return t.getFlag(f,e)}a(w,"getFlag");function vt(t,e,i){return t.setFlag(f,e,i)}a(vt,"setFlag");function _t(t,e){return t.unsetFlag(f,e,!0)}a(_t,"unsetFlag");function zt(t){return getProperty(t,`flags.${f}`)??{}}a(zt,"getFlags");function O(t){return game.settings.get(f,t)}a(O,"getSetting");function Ue(t,e){return game.settings.set(f,t,e)}a(Ue,"setSetting");function Lt(){return game.user.role>=O("permission")}a(Lt,"hasPermission");function pe(t){return game.i18n.localize(`PF2E.Actions.${t}.Title`)}a(pe,"getActionName");function Vt(t){let i=game.pf2e.ConditionManager.getCondition("off-guard").toObject();return i.name+=` (${game.i18n.localize(`PF2E.condition.${t}.name`)})`,i}a(Vt,"createFlatFootedSource");function Se(t,e){return e??=x[t]===3?4:x[t],{_id:"I9lfZUiCwMiGogVi",img:"systems/pf2e/icons/conditions-2/status_acup.webp",name:h("cover",t),system:{description:{gm:"",value:"
When you're behind an obstacle that could block weapons, guard you against explosions, and make you harder to detect, you're behind cover. Standard cover gives you a +2 circumstance bonus to AC, to Reflex saves against area effects, and to Stealth checks to Hide, Sneak, or otherwise avoid detection. You can increase this to greater cover using the Take Cover basic action, increasing the circumstance bonus to +4. If cover is especially light, typically when it's provided by a creature, you have lesser cover, which grants a +1 circumstance bonus to AC. A creature with standard cover or greater cover can attempt to use Stealth to Hide, but lesser cover isn't sufficient.
"},rules:[{domain:"all",key:"RollOption",option:`self:cover-bonus:${e}`},{domain:"all",key:"RollOption",option:`self:cover-level:${t}`},{key:"FlatModifier",predicate:[{or:[{and:["self:condition:prone","item:ranged"]},{not:"self:cover-level:greater-prone"}]}],selector:"ac",type:"circumstance",value:e},{key:"FlatModifier",predicate:["area-effect",{not:"self:cover-level:greater-prone"}],selector:"reflex",type:"circumstance",value:e},{key:"FlatModifier",predicate:[{or:["action:hide","action:sneak","avoid-detection"]},{not:"self:cover-level:greater-prone"}],selector:"stealth",type:"circumstance",value:e},{key:"FlatModifier",predicate:["action:avoid-notice",{not:"self:cover-level:greater-prone"}],selector:"initiative",type:"circumstance",value:e}],slug:"effect-cover"},type:"effect",flags:{core:{sourceId:Pe},[f]:{level:t,bonus:e}}}}a(Se,"createCoverSource");function Be(t,e=void 0){if(t)return t.system.rules.find(i=>i.key==="ChoiceSet"&&(!e||i.flag===e))}a(Be,"findChoiceSetRule");function Ie(t,e=!1){if(!t)return;let i=t.id,n=t.isToken;return(e?game.user.targets:canvas.tokens.controlled).find(r=>n?r.actor===t:r.actor.id===i)??t.getActiveTokens().shift()??null}a(Ie,"getActorToken");function Z(t){return t.itemTypes.condition.some(e=>e.slug==="prone")}a(Z,"isProne");function te(t,e=!1){let i=t.itemTypes.effect.find(n=>n.sourceId===Pe);return e?Be(i)?.selection.level:i}a(te,"getCoverEffect");function jt(t,e){return!t||!e||!t.system.perception?.senses?!1:(e=e.toLowerCase(),!!t.system.perception.senses.find(({type:i})=>i===e))}a(jt,"hasSense");function He(t){return jt(t,"greater-darkvision")}a(He,"hasGreaterDarkvision");function Ke(t){return jt(t,"see-invisibility")}a(Ke,"seeInvisibility");var qe={LOWER_BY_TWO:-2,LOWER:-1,INCREASE:1,INCREASE_BY_TWO:2,TO_CRITICAL_FAILURE:"criticalFailure",TO_FAILURE:"failure",TO_SUCCESS:"success",TO_CRITICAL_SUCCESS:"criticalSuccess"},Gt=["criticalFailure","failure","success","criticalSuccess"],Qe,Ut,be,Ye,ge,Ae,Xe,Bt,ie=class{constructor(e,i,n=null){ue(this,Qe);ue(this,be);ue(this,ge);ue(this,Xe);e instanceof Roll?(this.dieResult=(e.isDeterministic?e.terms.find(o=>o instanceof NumericTerm):e.dice.find(o=>o instanceof Die&&o.faces===20))?.total??1,this.rollTotal=e.total):(this.dieResult=e.dieValue,this.rollTotal=e.dieValue+e.modifier),this.dc=typeof i=="number"?{value:i}:i,this.unadjusted=q(this,Xe,Bt).call(this),this.adjustment=q(this,Qe,Ut).call(this,this.unadjusted,n),this.value=this.adjustment?q(this,be,Ye).call(this,this.adjustment.amount,this.unadjusted):this.unadjusted}},M=ie;a(M,"DegreeOfSuccess"),Qe=new WeakSet,Ut=a(function(e,i){if(!i)return null;for(let n of["all",...Gt]){let{label:o,amount:r}=i[n]??{};if(r&&o&&!(e===ie.CRITICAL_SUCCESS&&r===qe.INCREASE)&&!(e===ie.CRITICAL_FAILURE&&r===qe.LOWER)&&(n==="all"||Gt.indexOf(n)===e))return{label:o,amount:r}}return null},"#getDegreeAdjustment"),be=new WeakSet,Ye=a(function(e,i){switch(e){case"criticalFailure":return 0;case"failure":return 1;case"success":return 2;case"criticalSuccess":return 3;default:return Math.clamped(i+e,0,3)}},"#adjustDegreeOfSuccess"),ge=new WeakSet,Ae=a(function(e){return this.dieResult===20?q(this,be,Ye).call(this,qe.INCREASE,e):this.dieResult===1?q(this,be,Ye).call(this,qe.LOWER,e):e},"#adjustDegreeByDieValue"),Xe=new WeakSet,Bt=a(function(){let e=this.dc.value;return this.rollTotal-e>=10?q(this,ge,Ae).call(this,ie.CRITICAL_SUCCESS):e-this.rollTotal>=10?q(this,ge,Ae).call(this,ie.CRITICAL_FAILURE):this.rollTotal>=e?q(this,ge,Ae).call(this,ie.SUCCESS):q(this,ge,Ae).call(this,ie.FAILURE)},"#calculateDegreeOfSuccess"),Oe(M,"CRITICAL_FAILURE",0),Oe(M,"FAILURE",1),Oe(M,"SUCCESS",2),Oe(M,"CRITICAL_SUCCESS",3);function Ht(t,e){let i="",n=["standard","npc-vision"];for(let r of n){let s=Ee(t.object,r);i+=`When you're behind an obstacle that could block weapons, guard you against explosions, and make you harder to detect, you're behind cover. Standard cover gives you a +2 circumstance bonus to AC, to Reflex saves against area effects, and to Stealth checks to Hide, Sneak, or otherwise avoid detection. You can increase this to greater cover using the Take Cover basic action, increasing the circumstance bonus to +4. If cover is especially light, typically when it's provided by a creature, you have lesser cover, which grants a +1 circumstance bonus to AC. A creature with standard cover or greater cover can attempt to use Stealth to Hide, but lesser cover isn't sufficient.
"},rules:[{domain:"all",key:"RollOption",option:`self:cover-bonus:${e}`},{domain:"all",key:"RollOption",option:`self:cover-level:${t}`},{key:"FlatModifier",predicate:[{or:[{and:["self:condition:prone","item:ranged"]},{not:"self:cover-level:greater-prone"}]}],selector:"ac",type:"circumstance",value:e},{key:"FlatModifier",predicate:["area-effect",{not:"self:cover-level:greater-prone"}],selector:"reflex",type:"circumstance",value:e},{key:"FlatModifier",predicate:[{or:["action:hide","action:sneak","avoid-detection"]},{not:"self:cover-level:greater-prone"}],selector:"stealth",type:"circumstance",value:e},{key:"FlatModifier",predicate:["action:avoid-notice",{not:"self:cover-level:greater-prone"}],selector:"initiative",type:"circumstance",value:e}],slug:"effect-cover"},type:"effect",flags:{core:{sourceId:Pe},[f]:{level:t,bonus:e}}}}a(be,"createCoverSource");function Be(t,e=void 0){if(t)return t.system.rules.find(i=>i.key==="ChoiceSet"&&(!e||i.flag===e))}a(Be,"findChoiceSetRule");function Ie(t,e=!1){if(!t)return;let i=t.id,n=t.isToken;return(e?game.user.targets:canvas.tokens?.controlled??[]).find(r=>n?r.actor===t:r.actor.id===i)??t.getActiveTokens().shift()??null}a(Ie,"getActorToken");function Z(t){return t.itemTypes.condition.some(e=>e.slug==="prone")}a(Z,"isProne");function te(t,e=!1){let i=t.itemTypes.effect.find(n=>n.sourceId===Pe);return e?Be(i)?.selection.level:i}a(te,"getCoverEffect");function jt(t,e){return!t||!e||!t.system.perception?.senses?!1:(e=e.toLowerCase(),!!t.system.perception.senses.find(({type:i})=>i===e))}a(jt,"hasSense");function He(t){return jt(t,"greater-darkvision")}a(He,"hasGreaterDarkvision");function Ke(t){return jt(t,"see-invisibility")}a(Ke,"seeInvisibility");var qe={LOWER_BY_TWO:-2,LOWER:-1,INCREASE:1,INCREASE_BY_TWO:2,TO_CRITICAL_FAILURE:"criticalFailure",TO_FAILURE:"failure",TO_SUCCESS:"success",TO_CRITICAL_SUCCESS:"criticalSuccess"},Gt=["criticalFailure","failure","success","criticalSuccess"],Qe,Ut,Se,Ye,ge,Ae,Xe,Bt,ie=class{constructor(e,i,n=null){ue(this,Qe);ue(this,Se);ue(this,ge);ue(this,Xe);e instanceof Roll?(this.dieResult=(e.isDeterministic?e.terms.find(o=>o instanceof NumericTerm):e.dice.find(o=>o instanceof Die&&o.faces===20))?.total??1,this.rollTotal=e.total):(this.dieResult=e.dieValue,this.rollTotal=e.dieValue+e.modifier),this.dc=typeof i=="number"?{value:i}:i,this.unadjusted=q(this,Xe,Bt).call(this),this.adjustment=q(this,Qe,Ut).call(this,this.unadjusted,n),this.value=this.adjustment?q(this,Se,Ye).call(this,this.adjustment.amount,this.unadjusted):this.unadjusted}},M=ie;a(M,"DegreeOfSuccess"),Qe=new WeakSet,Ut=a(function(e,i){if(!i)return null;for(let n of["all",...Gt]){let{label:o,amount:r}=i[n]??{};if(r&&o&&!(e===ie.CRITICAL_SUCCESS&&r===qe.INCREASE)&&!(e===ie.CRITICAL_FAILURE&&r===qe.LOWER)&&(n==="all"||Gt.indexOf(n)===e))return{label:o,amount:r}}return null},"#getDegreeAdjustment"),Se=new WeakSet,Ye=a(function(e,i){switch(e){case"criticalFailure":return 0;case"failure":return 1;case"success":return 2;case"criticalSuccess":return 3;default:return Math.clamped(i+e,0,3)}},"#adjustDegreeOfSuccess"),ge=new WeakSet,Ae=a(function(e){return this.dieResult===20?q(this,Se,Ye).call(this,qe.INCREASE,e):this.dieResult===1?q(this,Se,Ye).call(this,qe.LOWER,e):e},"#adjustDegreeByDieValue"),Xe=new WeakSet,Bt=a(function(){let e=this.dc.value;return this.rollTotal-e>=10?q(this,ge,Ae).call(this,ie.CRITICAL_SUCCESS):e-this.rollTotal>=10?q(this,ge,Ae).call(this,ie.CRITICAL_FAILURE):this.rollTotal>=e?q(this,ge,Ae).call(this,ie.SUCCESS):q(this,ge,Ae).call(this,ie.FAILURE)},"#calculateDegreeOfSuccess"),Oe(M,"CRITICAL_FAILURE",0),Oe(M,"FAILURE",1),Oe(M,"SUCCESS",2),Oe(M,"CRITICAL_SUCCESS",3);function Ht(t,e){let i="",n=["standard","npc-vision"];for(let r of n){let s=Ee(t.object,r);i+=`${h(`settings.${r}.short`)}
-';return i+=`${h("dialog.seek.hint")}
`,i+=Pi("create-cone","fa-thin fa-cubes",game.i18n.format("PF2E.TemplateLabel",{size:30,unit:e,shape:game.i18n.localize(CONFIG.PF2E.areaTypes.cone)})),i+=Pi("create-burst","fa-thin fa-cubes",game.i18n.format("PF2E.TemplateLabel",{size:15,unit:e,shape:game.i18n.localize(CONFIG.PF2E.areaTypes.burst)})),i+="
",Dialog.wait({title:`${t.name} - ${game.i18n.localize("PF2E.Actions.Seek.Title")}`,content:i,buttons:{ok:{icon:'',label:h("dialog.seek.accept"),callback:()=>!0},no:{icon:'',label:h("dialog.seek.cancel"),callback:n=>!1}},close:()=>!1,render:n=>{n.filter(".dialog-content").find("[data-action=create-cone], [data-action=create-burst]").on("click",r=>{let{action:s}=r.currentTarget.dataset;J(t),Ze({type:s==="create-cone"?"cone":"burst",token:t})})}},{width:300,left:10})}a(Hi,"seek");function Ki(t,e){class i extends e{async use(r={}){let s=pe("Sneak"),c=Ne(r,s);if(c)return r.actors=[c.actor],super.use(r)}}a(i,"SneakVariant");class n extends t{constructor(){super({cost:1,description:"PF2E.Actions.Sneak.Description",name:"PF2E.Actions.Sneak.Title",notes:[{outcome:["success","criticalSuccess"],text:"PF2E.Actions.Sneak.Notes.success"},{outcome:["failure"],text:"PF2E.Actions.Sneak.Notes.failure"},{outcome:["criticalFailure"],text:"PF2E.Actions.Sneak.Notes.criticalFailure"}],rollOptions:["action:sneak"],slug:"sneak",traits:["move","secret"]})}toActionVariant(r){return new i(this,r)}}a(n,"Sneak")}a(Ki,"setupSneak");function qi(t,e){class i extends e{async use(r={}){let s=pe("CreateADiversion"),c=Ne(r,s);if(c)return r.actors=[c.actor],super.use(r)}}a(i,"CreateADiversionVariant");class n extends t{constructor(){super({cost:1,description:"PF2E.Actions.CreateADiversion.Description",name:"PF2E.Actions.CreateADiversion.Title",notes:[{outcome:["criticalSuccess","success"],text:"PF2E.Actions.CreateADiversion.Notes.success"},{outcome:["criticalFailure","failure"],text:"PF2E.Actions.CreateADiversion.Notes.failure"}],section:"skill",slug:"create-a-diversion",statistic:"deception",traits:["mental"],variants:[{name:"PF2E.Actions.CreateADiversion.DistractingWords.Title",rollOptions:["action:create-a-diversion","action:create-a-diversion:distracting-words"],slug:"distracting-words",traits:["auditory","linguistic","mental"]},{name:"PF2E.Actions.CreateADiversion.Gesture.Title",rollOptions:["action:create-a-diversion","action:create-a-diversion:gesture"],slug:"gesture",traits:["manipulate","mental"]},{name:"PF2E.Actions.CreateADiversion.Trick.Title",rollOptions:["action:create-a-diversion","action:create-a-diversion:trick"],slug:"trick",traits:["manipulate","mental"]}]})}toActionVariant(r){return new i(this,r)}}a(n,"CreateADiversion"),game.pf2e.actions.set("create-a-diversion",new n)}a(qi,"setupCreateADiversion");function Yi(t,e){class i extends e{async use(r={}){let s=pe("Hide"),c=Ne(r,s);if(c)return r.actors=[c.actor],super.use(r)}}a(i,"HideVariant");class n extends t{constructor(){super({cost:1,description:"PF2E.Actions.Hide.Description",name:"PF2E.Actions.Hide.Title",rollOptions:["action:hide"],slug:"hide",statistic:"stealth",traits:["secret"],notes:[{outcome:["success","criticalSuccess"],text:"PF2E.Actions.Hide.Notes.success"}]})}toActionVariant(r){return new i(this,r)}}a(n,"Hide"),game.pf2e.actions.set("hide",new n)}a(Yi,"setupHide");function Qi(t,e){class i extends e{async use(r={}){let s=h("action.take-cover"),c=Ne(r,s);c&&Xi(c)}}a(i,"TakeCoverVariant");class n extends t{constructor(){super({cost:1,description:"PF2E.Actions.TakeCover.Description",img:"systems/pf2e/icons/conditions-2/status_acup.webp",name:"PF2E.Actions.TakeCover.Title",slug:"take-cover"})}toActionVariant(r){return new i(this,r)}}a(n,"TakeCover"),game.pf2e.actions.set("take-cover",new n)}a(Qi,"setupCover");async function Xi(t){let e=t.actor,i=te(e),n=ne(t,game.user.targets.ids);if(i&&!n.length)return i.delete();let o=z(t)??{},r=Object.entries(o).reduce((l,[p,{cover:d}])=>(d&&(l[p]=d),l),{}),s=await renderTemplate(X("covers-dialog"),{i18n:h,hasTargets:!!n.length,hasCovers:!isEmpty(r),hasTargetCover:n.some(l=>l in r),isProne:Z(e)}),c=new Dialog({title:`${t.name} - ${h("action.take-cover")}`,content:s,buttons:{},render:l=>{l.find("button").on("click",async p=>{let{level:d}=p.currentTarget.dataset,u=O("skip-cover"),g=a(async(m,y)=>{let k=y?n:void 0,R=m===V.cover?k?"remove":"remove-all":"take";if(await Nt({content:h(`message.cover.${R}`,{cover:h(`cover.${m}`)}),flags:{selected:k,cover:m,skipWait:u},token:t}),u){if(m===V.cover&&!k)return ai(t);let E=deepClone(z(t))??{};for(let S of n)setProperty(E,`${S}.cover`,m);return ot(t,E)}},"process");if(d==="remove-all")g(V.cover);else if(d==="remove")g(V.cover,!0);else if(n.length)g(d,!0);else{let m=Se(d);e.createEmbeddedDocuments("Item",[m])}c.close()})}}).render(!0)}a(Xi,"takeCover");function Ne(t,e){let i=t.tokens?.filter(r=>r.actor)??[];Array.isArray(i)||(i=[i]);let n=t.actors??[];if(Array.isArray(n)||(n=[n]),!i.length&&n.length===1&&(i=[Ie(n[0])].filter(Boolean)),i.length||(i=canvas.tokens.controlled.filter(r=>r.actor)),i.length||(i=[Ie(game.user.character)].filter(Boolean)),i.length>1){ui.notifications.warn(h("action.only-one",{action:e}));return}if(!i.length){ui.notifications.warn(h("action.must-one",{action:e}));return}let o=i[0];if(!o?.actor?.isOfType("creature")){ui.notifications.warn(h("action.must-creature",{action:e}));return}return o}a(Ne,"getSelectedToken");function Pi(t,e,i){return``}a(Pi,"createButton");var Si={geometry:{clearDebug:me,getRectEdges:at,lineIntersectWall:De,pointToTokenIntersectWall:st},token:{getCreatureCover:ut,getWallCover:lt,getVisibility:oe,clearConditionals:ye,showConditionals:Tt,showAllConditionals:pt,getTokenData:z,getCover:dt,openHUD:Et},lighting:{getLightExposure:ct},actor:{isProne:Z,getCoverEffect:te,seeInvisibility:Ke,hasGreaterDarkvision:He},scene:{getValidTokens:j,validateTokens:ne,getSceneSetting:Ee},template:{createSeekTemplate:Ze,createDarknessTemplate:Xt,createMistTemplate:Zt,getDarknessTemplates:Je,getMistTemplates:et,getSeekTemplateTokens:tt,deleteSeekTemplate:J,getTemplateTokens:Te},ruleElement:{perceptionRules:re,getPerception:B,updateFromPerceptionRules:Fe}};async function bi(t,...e){let i=e[1];if(!i)return t(...e);Array.isArray(i.options)&&(i.options=new Set(i.options));let{actor:n,createMessage:o="true",type:r,token:s,target:c,isReroll:l}=i,p=s??Ie(n),d=c?.token,u=ke.includes(r),g=O("flat-check");if(l||!o||!p||n.isOfType("hazard")||!Dt.includes(r)||u&&(!d||g==="none"))return t(...e);if(u&&d.actor){let m=e[2],y=re(p,d,{extraOptions:i.options.filter(A=>A.startsWith("item:"))}),k=oe(d,p,{perception:y,affects:"target"});if(!k)return t(...e);let R=(()=>{let A=B(y,"target","visibility","dc",k)?.first(),b=rt(A);if(!b)return b;let N=A[0];return["-","+"].includes(N)?(k==="concealed"?5:11)+b:b})();if(R===0)return t(...e);let E=P[k]>=P.undetected,S=m?.ctrlKey||m?.metaKey,T=(await new p.actor.saves.reflex.constructor(p.actor,{slug:"visibility-check",label:`${game.i18n.localize("PF2E.FlatCheck")}: ${game.i18n.localize(`PF2E.condition.${k}.name`)}`,check:{type:"flat-check"}}).roll({dc:{value:R??(k==="concealed"?5:11)},target:d.actor,rollMode:E||S?game.user.isGM?"gmroll":"blindroll":"roll"})).degreeOfSuccess>1;if(E&&(i.options.add("secret"),i.pf2ePerception={isSuccess:T,visibility:k}),g!=="roll"&&!E&&!T)return}else if(i.options.has("action:hide"))setProperty(i,"pf2ePerception.selected",game.user.targets.ids);else if(i.options.has("action:create-a-diversion"))setProperty(i,"pf2ePerception.selected",game.user.targets.ids);else if(i.options.has("action:seek")){let m=tt(p),y=m??Array.from(game.user.targets),k=ne(p,y).filter(R=>!R.document.hidden).map(R=>R.id);setProperty(i,"pf2ePerception.selected",k),setProperty(i,"pf2ePerception.fromTemplate",!!m)}return t(...e)}a(bi,"checkRoll");function Ei(t,e){let{createMessage:i="true",type:n,token:o,target:r,isReroll:s,options:c,dc:l}=t.context,p=o,d=r?.token,u=r?.actor;if(s||!i||!p||!d||!u||!ke.includes(n))return;let g=te(u),m=g?Be(g)?.selection.level??w(g,"level"):void 0,y=t[f]?.coverOverride??m,k='${h("settings.encounter.short")}
-When you're behind an obstacle that could block weapons, guard you against explosions, and make you harder to detect, you're behind cover. Standard cover gives you a +2 circumstance bonus to AC, to Reflex saves against area effects, and to Stealth checks to Hide, Sneak, or otherwise avoid detection. You can increase this to greater cover using the Take Cover basic action, increasing the circumstance bonus to +4. If cover is especially light, typically when it's provided by a creature, you have lesser cover, which grants a +1 circumstance bonus to AC. A creature with standard cover or greater cover can attempt to use Stealth to Hide, but lesser cover isn't sufficient.
\",\r\n },\r\n rules: [\r\n { domain: 'all', key: 'RollOption', option: `self:cover-bonus:${bonus}` },\r\n { domain: 'all', key: 'RollOption', option: `self:cover-level:${level}` },\r\n {\r\n key: 'FlatModifier',\r\n predicate: [\r\n { or: [{ and: ['self:condition:prone', 'item:ranged'] }, { not: 'self:cover-level:greater-prone' }] },\r\n ],\r\n selector: 'ac',\r\n type: 'circumstance',\r\n value: bonus,\r\n },\r\n {\r\n key: 'FlatModifier',\r\n predicate: ['area-effect', { not: 'self:cover-level:greater-prone' }],\r\n selector: 'reflex',\r\n type: 'circumstance',\r\n value: bonus,\r\n },\r\n {\r\n key: 'FlatModifier',\r\n predicate: [\r\n { or: ['action:hide', 'action:sneak', 'avoid-detection'] },\r\n { not: 'self:cover-level:greater-prone' },\r\n ],\r\n selector: 'stealth',\r\n type: 'circumstance',\r\n value: bonus,\r\n },\r\n {\r\n key: 'FlatModifier',\r\n predicate: ['action:avoid-notice', { not: 'self:cover-level:greater-prone' }],\r\n selector: 'initiative',\r\n type: 'circumstance',\r\n value: bonus,\r\n },\r\n ],\r\n slug: 'effect-cover',\r\n },\r\n type: 'effect',\r\n flags: {\r\n core: { sourceId: COVER_UUID },\r\n [MODULE_ID]: {\r\n level,\r\n bonus,\r\n },\r\n },\r\n }\r\n}\r\n\r\nexport function findChoiceSetRule(item, flag = undefined) {\r\n if (!item) return undefined\r\n return item.system.rules.find(rule => rule.key === 'ChoiceSet' && (!flag || rule.flag === flag))\r\n}\r\n", "import { COVER_UUID, VISION_LEVELS } from './constants.js'\r\nimport { findChoiceSetRule } from './effect.js'\r\n\r\nexport function getActorToken(actor, target = false) {\r\n if (!actor) return undefined\r\n const actorId = actor.id\r\n const isToken = actor.isToken\r\n const tokens = target ? game.user.targets : canvas.tokens.controlled\r\n return (\r\n tokens.find(token => (isToken ? token.actor === actor : token.actor.id === actorId)) ??\r\n actor.getActiveTokens().shift() ??\r\n null\r\n )\r\n}\r\n\r\nexport function isProne(actor) {\r\n return actor.itemTypes.condition.some(item => item.slug === 'prone')\r\n}\r\n\r\nexport function getCoverEffect(actor, selection = false) {\r\n const effect = actor.itemTypes.effect.find(x => x.sourceId === COVER_UUID)\r\n return selection ? findChoiceSetRule(effect)?.selection.level : effect\r\n}\r\n\r\nexport function getFeatWithUUID(actor, uuid) {\r\n return actor.itemTypes.feat.find(f => f.sourceId === uuid)\r\n}\r\n\r\nfunction hasSense(actor, sense) {\r\n if (!actor || !sense || !actor.system.perception?.senses) return false\r\n sense = sense.toLowerCase()\r\n return !!actor.system.perception.senses.find(({ type }) => type === sense)\r\n}\r\n\r\nexport function hasGreaterDarkvision(actor) {\r\n return hasSense(actor, 'greater-darkvision')\r\n}\r\n\r\nexport function seeInvisibility(actor) {\r\n return hasSense(actor, 'see-invisibility')\r\n}\r\n", "const DEGREE_ADJUSTMENT_AMOUNTS = {\r\n LOWER_BY_TWO: -2,\r\n LOWER: -1,\r\n INCREASE: 1,\r\n INCREASE_BY_TWO: 2,\r\n TO_CRITICAL_FAILURE: 'criticalFailure',\r\n TO_FAILURE: 'failure',\r\n TO_SUCCESS: 'success',\r\n TO_CRITICAL_SUCCESS: 'criticalSuccess',\r\n}\r\n\r\nconst DEGREE_OF_SUCCESS_STRINGS = ['criticalFailure', 'failure', 'success', 'criticalSuccess']\r\n\r\nexport class DegreeOfSuccess {\r\n constructor(roll, dc, dosAdjustments = null) {\r\n if (roll instanceof Roll) {\r\n this.dieResult =\r\n (roll.isDeterministic\r\n ? roll.terms.find(t => t instanceof NumericTerm)\r\n : roll.dice.find(d => d instanceof Die && d.faces === 20)\r\n )?.total ?? 1\r\n this.rollTotal = roll.total\r\n } else {\r\n this.dieResult = roll.dieValue\r\n this.rollTotal = roll.dieValue + roll.modifier\r\n }\r\n\r\n this.dc = typeof dc === 'number' ? { value: dc } : dc\r\n\r\n this.unadjusted = this.#calculateDegreeOfSuccess()\r\n this.adjustment = this.#getDegreeAdjustment(this.unadjusted, dosAdjustments)\r\n this.value = this.adjustment ? this.#adjustDegreeOfSuccess(this.adjustment.amount, this.unadjusted) : this.unadjusted\r\n }\r\n\r\n static CRITICAL_FAILURE = 0\r\n static FAILURE = 1\r\n static SUCCESS = 2\r\n static CRITICAL_SUCCESS = 3\r\n\r\n #getDegreeAdjustment(degree, adjustments) {\r\n if (!adjustments) return null\r\n\r\n for (const outcome of ['all', ...DEGREE_OF_SUCCESS_STRINGS]) {\r\n const { label, amount } = adjustments[outcome] ?? {}\r\n if (\r\n amount &&\r\n label &&\r\n !(degree === DegreeOfSuccess.CRITICAL_SUCCESS && amount === DEGREE_ADJUSTMENT_AMOUNTS.INCREASE) &&\r\n !(degree === DegreeOfSuccess.CRITICAL_FAILURE && amount === DEGREE_ADJUSTMENT_AMOUNTS.LOWER) &&\r\n (outcome === 'all' || DEGREE_OF_SUCCESS_STRINGS.indexOf(outcome) === degree)\r\n ) {\r\n return { label, amount }\r\n }\r\n }\r\n\r\n return null\r\n }\r\n\r\n #adjustDegreeOfSuccess(amount, degreeOfSuccess) {\r\n switch (amount) {\r\n case 'criticalFailure':\r\n return 0\r\n case 'failure':\r\n return 1\r\n case 'success':\r\n return 2\r\n case 'criticalSuccess':\r\n return 3\r\n default:\r\n return Math.clamped(degreeOfSuccess + amount, 0, 3)\r\n }\r\n }\r\n\r\n /**\r\n * @param degree The current success value\r\n * @return The new success value\r\n */\r\n #adjustDegreeByDieValue(degree) {\r\n if (this.dieResult === 20) {\r\n return this.#adjustDegreeOfSuccess(DEGREE_ADJUSTMENT_AMOUNTS.INCREASE, degree)\r\n } else if (this.dieResult === 1) {\r\n return this.#adjustDegreeOfSuccess(DEGREE_ADJUSTMENT_AMOUNTS.LOWER, degree)\r\n }\r\n\r\n return degree\r\n }\r\n\r\n #calculateDegreeOfSuccess() {\r\n const dc = this.dc.value\r\n\r\n if (this.rollTotal - dc >= 10) {\r\n return this.#adjustDegreeByDieValue(DegreeOfSuccess.CRITICAL_SUCCESS)\r\n } else if (dc - this.rollTotal >= 10) {\r\n return this.#adjustDegreeByDieValue(DegreeOfSuccess.CRITICAL_FAILURE)\r\n } else if (this.rollTotal >= dc) {\r\n return this.#adjustDegreeByDieValue(DegreeOfSuccess.SUCCESS)\r\n }\r\n\r\n return this.#adjustDegreeByDieValue(DegreeOfSuccess.FAILURE)\r\n }\r\n}\r\n", "import { getFlag, getSetting, localize } from './module.js'\r\n\r\nexport function renderSceneConfig(config, html) {\r\n let settings = ''\r\n\r\n const list = ['standard', 'npc-vision']\r\n for (const setting of list) {\r\n const checked = getSceneSetting(config.object, setting)\r\n\r\n settings += `${localize(`settings.${setting}.short`)}
\r\n'\r\n content += `${localize('dialog.seek.hint')}
`\r\n\r\n content += createButton(\r\n 'create-cone',\r\n 'fa-thin fa-cubes',\r\n game.i18n.format('PF2E.TemplateLabel', {\r\n size: 30,\r\n unit,\r\n shape: game.i18n.localize(CONFIG.PF2E.areaTypes.cone),\r\n })\r\n )\r\n\r\n content += createButton(\r\n 'create-burst',\r\n 'fa-thin fa-cubes',\r\n game.i18n.format('PF2E.TemplateLabel', {\r\n size: 15,\r\n unit,\r\n shape: game.i18n.localize(CONFIG.PF2E.areaTypes.burst),\r\n })\r\n )\r\n\r\n content += '
'\r\n\r\n return Dialog.wait(\r\n {\r\n title: `${token.name} - ${game.i18n.localize('PF2E.Actions.Seek.Title')}`,\r\n content,\r\n buttons: {\r\n ok: {\r\n icon: '',\r\n label: localize('dialog.seek.accept'),\r\n callback: () => true,\r\n },\r\n no: {\r\n icon: '',\r\n label: localize('dialog.seek.cancel'),\r\n callback: html => false,\r\n },\r\n },\r\n close: () => false,\r\n render: html => {\r\n const content = html.filter('.dialog-content')\r\n content.find('[data-action=create-cone], [data-action=create-burst]').on('click', event => {\r\n const { action } = event.currentTarget.dataset\r\n deleteSeekTemplate(token)\r\n createSeekTemplate({ type: action === 'create-cone' ? 'cone' : 'burst', token })\r\n })\r\n },\r\n },\r\n { width: 300, left: 10 }\r\n )\r\n}\r\n\r\nfunction setupSneak(SingleCheckAction, SingleCheckActionVariant) {\r\n class SneakVariant extends SingleCheckActionVariant {\r\n async use(options = {}) {\r\n const action = getActionName('Sneak')\r\n const token = getSelectedToken(options, action)\r\n if (!token) return\r\n\r\n options.actors = [token.actor]\r\n return super.use(options)\r\n }\r\n }\r\n\r\n class Sneak extends SingleCheckAction {\r\n constructor() {\r\n super({\r\n cost: 1,\r\n description: 'PF2E.Actions.Sneak.Description',\r\n name: 'PF2E.Actions.Sneak.Title',\r\n notes: [\r\n { outcome: ['success', 'criticalSuccess'], text: 'PF2E.Actions.Sneak.Notes.success' },\r\n { outcome: ['failure'], text: 'PF2E.Actions.Sneak.Notes.failure' },\r\n { outcome: ['criticalFailure'], text: 'PF2E.Actions.Sneak.Notes.criticalFailure' },\r\n ],\r\n rollOptions: ['action:sneak'],\r\n slug: 'sneak',\r\n traits: ['move', 'secret'],\r\n })\r\n }\r\n\r\n toActionVariant(data) {\r\n return new SneakVariant(this, data)\r\n }\r\n }\r\n\r\n // game.pf2e.actions.set('sneak', new Sneak())\r\n}\r\n\r\nfunction setupCreateADiversion(SingleCheckAction, SingleCheckActionVariant) {\r\n class CreateADiversionVariant extends SingleCheckActionVariant {\r\n async use(options = {}) {\r\n const action = getActionName('CreateADiversion')\r\n const token = getSelectedToken(options, action)\r\n if (!token) return\r\n\r\n options.actors = [token.actor]\r\n return super.use(options)\r\n }\r\n }\r\n\r\n class CreateADiversion extends SingleCheckAction {\r\n constructor() {\r\n super({\r\n cost: 1,\r\n description: 'PF2E.Actions.CreateADiversion.Description',\r\n name: 'PF2E.Actions.CreateADiversion.Title',\r\n notes: [\r\n { outcome: ['criticalSuccess', 'success'], text: 'PF2E.Actions.CreateADiversion.Notes.success' },\r\n { outcome: ['criticalFailure', 'failure'], text: 'PF2E.Actions.CreateADiversion.Notes.failure' },\r\n ],\r\n section: 'skill',\r\n slug: 'create-a-diversion',\r\n statistic: 'deception',\r\n traits: ['mental'],\r\n variants: [\r\n {\r\n name: 'PF2E.Actions.CreateADiversion.DistractingWords.Title',\r\n rollOptions: ['action:create-a-diversion', 'action:create-a-diversion:distracting-words'],\r\n slug: 'distracting-words',\r\n traits: ['auditory', 'linguistic', 'mental'],\r\n },\r\n {\r\n name: 'PF2E.Actions.CreateADiversion.Gesture.Title',\r\n rollOptions: ['action:create-a-diversion', 'action:create-a-diversion:gesture'],\r\n slug: 'gesture',\r\n traits: ['manipulate', 'mental'],\r\n },\r\n {\r\n name: 'PF2E.Actions.CreateADiversion.Trick.Title',\r\n rollOptions: ['action:create-a-diversion', 'action:create-a-diversion:trick'],\r\n slug: 'trick',\r\n traits: ['manipulate', 'mental'],\r\n },\r\n ],\r\n })\r\n }\r\n\r\n toActionVariant(data) {\r\n return new CreateADiversionVariant(this, data)\r\n }\r\n }\r\n\r\n game.pf2e.actions.set('create-a-diversion', new CreateADiversion())\r\n}\r\n\r\nfunction setupHide(SingleCheckAction, SingleCheckActionVariant) {\r\n class HideVariant extends SingleCheckActionVariant {\r\n async use(options = {}) {\r\n const action = getActionName('Hide')\r\n const token = getSelectedToken(options, action)\r\n if (!token) return\r\n\r\n options.actors = [token.actor]\r\n return super.use(options)\r\n }\r\n }\r\n\r\n class Hide extends SingleCheckAction {\r\n constructor() {\r\n super({\r\n cost: 1,\r\n description: 'PF2E.Actions.Hide.Description',\r\n name: 'PF2E.Actions.Hide.Title',\r\n rollOptions: ['action:hide'],\r\n slug: 'hide',\r\n statistic: 'stealth',\r\n traits: ['secret'],\r\n notes: [{ outcome: ['success', 'criticalSuccess'], text: 'PF2E.Actions.Hide.Notes.success' }],\r\n })\r\n }\r\n\r\n toActionVariant(data) {\r\n return new HideVariant(this, data)\r\n }\r\n }\r\n\r\n game.pf2e.actions.set('hide', new Hide())\r\n}\r\n\r\nfunction setupCover(BaseAction, BaseActionVariant) {\r\n class TakeCoverVariant extends BaseActionVariant {\r\n async use(options = {}) {\r\n const action = localize('action.take-cover')\r\n const token = getSelectedToken(options, action)\r\n if (token) takeCover(token)\r\n }\r\n }\r\n\r\n class TakeCover extends BaseAction {\r\n constructor() {\r\n super({\r\n cost: 1,\r\n description: 'PF2E.Actions.TakeCover.Description',\r\n img: 'systems/pf2e/icons/conditions-2/status_acup.webp',\r\n name: 'PF2E.Actions.TakeCover.Title',\r\n slug: 'take-cover',\r\n })\r\n }\r\n\r\n toActionVariant(data) {\r\n return new TakeCoverVariant(this, data)\r\n }\r\n }\r\n\r\n game.pf2e.actions.set('take-cover', new TakeCover())\r\n}\r\n\r\nasync function takeCover(token) {\r\n const actor = token.actor\r\n const cover = getCoverEffect(actor)\r\n\r\n const targets = validateTokens(token, game.user.targets.ids)\r\n if (cover && !targets.length) return cover.delete()\r\n\r\n const data = getTokenData(token) ?? {}\r\n const covers = Object.entries(data).reduce((covers, [tokenId, { cover }]) => {\r\n if (cover) covers[tokenId] = cover\r\n return covers\r\n }, {})\r\n\r\n const content = await renderTemplate(templatePath('covers-dialog'), {\r\n i18n: localize,\r\n hasTargets: !!targets.length,\r\n hasCovers: !isEmpty(covers),\r\n hasTargetCover: targets.some(id => id in covers),\r\n isProne: isProne(actor),\r\n })\r\n\r\n const dialog = new Dialog({\r\n title: `${token.name} - ${localize('action.take-cover')}`,\r\n content,\r\n buttons: {},\r\n render: html => {\r\n html.find('button').on('click', async event => {\r\n const { level } = event.currentTarget.dataset\r\n const skip = getSetting('skip-cover')\r\n\r\n const process = async (cover, isSelected) => {\r\n const selected = isSelected ? targets : undefined\r\n\r\n const flavor = cover === defaultValues.cover ? (selected ? 'remove' : 'remove-all') : 'take'\r\n await createTokenMessage({\r\n content: localize(`message.cover.${flavor}`, { cover: localize(`cover.${cover}`) }),\r\n flags: { selected, cover, skipWait: skip },\r\n token,\r\n })\r\n\r\n if (skip) {\r\n if (cover === defaultValues.cover && !selected) return clearTokenData(token)\r\n const data = deepClone(getTokenData(token)) ?? {}\r\n for (const tokenId of targets) {\r\n setProperty(data, `${tokenId}.cover`, cover)\r\n }\r\n return setTokenData(token, data)\r\n }\r\n }\r\n\r\n if (level === 'remove-all') process(defaultValues.cover)\r\n else if (level === 'remove') process(defaultValues.cover, true)\r\n else if (targets.length) process(level, true)\r\n else {\r\n const source = createCoverSource(level)\r\n actor.createEmbeddedDocuments('Item', [source])\r\n }\r\n\r\n dialog.close()\r\n })\r\n },\r\n }).render(true)\r\n}\r\n\r\nfunction getSelectedToken(options, action) {\r\n let tokens = options.tokens?.filter(t => t.actor) ?? []\r\n if (!Array.isArray(tokens)) tokens = [tokens]\r\n\r\n let actors = options.actors ?? []\r\n if (!Array.isArray(actors)) actors = [actors]\r\n\r\n if (!tokens.length && actors.length === 1) tokens = [getActorToken(actors[0])].filter(Boolean)\r\n if (!tokens.length) tokens = canvas.tokens.controlled.filter(t => t.actor)\r\n if (!tokens.length) tokens = [getActorToken(game.user.character)].filter(Boolean)\r\n\r\n if (tokens.length > 1) {\r\n ui.notifications.warn(localize('action.only-one', { action }))\r\n return\r\n }\r\n\r\n if (!tokens.length) {\r\n ui.notifications.warn(localize('action.must-one', { action }))\r\n return\r\n }\r\n\r\n const token = tokens[0]\r\n if (!token?.actor?.isOfType('creature')) {\r\n ui.notifications.warn(localize('action.must-creature', { action }))\r\n return\r\n }\r\n\r\n return token\r\n}\r\n\r\nfunction createButton(action, icon, label) {\r\n return `${localize('settings.encounter.short')}
\r\nWhen you're behind an obstacle that could block weapons, guard you against explosions, and make you harder to detect, you're behind cover. Standard cover gives you a +2 circumstance bonus to AC, to Reflex saves against area effects, and to Stealth checks to Hide, Sneak, or otherwise avoid detection. You can increase this to greater cover using the Take Cover basic action, increasing the circumstance bonus to +4. If cover is especially light, typically when it's provided by a creature, you have lesser cover, which grants a +1 circumstance bonus to AC. A creature with standard cover or greater cover can attempt to use Stealth to Hide, but lesser cover isn't sufficient.
\",\r\n },\r\n rules: [\r\n { domain: 'all', key: 'RollOption', option: `self:cover-bonus:${bonus}` },\r\n { domain: 'all', key: 'RollOption', option: `self:cover-level:${level}` },\r\n {\r\n key: 'FlatModifier',\r\n predicate: [\r\n { or: [{ and: ['self:condition:prone', 'item:ranged'] }, { not: 'self:cover-level:greater-prone' }] },\r\n ],\r\n selector: 'ac',\r\n type: 'circumstance',\r\n value: bonus,\r\n },\r\n {\r\n key: 'FlatModifier',\r\n predicate: ['area-effect', { not: 'self:cover-level:greater-prone' }],\r\n selector: 'reflex',\r\n type: 'circumstance',\r\n value: bonus,\r\n },\r\n {\r\n key: 'FlatModifier',\r\n predicate: [\r\n { or: ['action:hide', 'action:sneak', 'avoid-detection'] },\r\n { not: 'self:cover-level:greater-prone' },\r\n ],\r\n selector: 'stealth',\r\n type: 'circumstance',\r\n value: bonus,\r\n },\r\n {\r\n key: 'FlatModifier',\r\n predicate: ['action:avoid-notice', { not: 'self:cover-level:greater-prone' }],\r\n selector: 'initiative',\r\n type: 'circumstance',\r\n value: bonus,\r\n },\r\n ],\r\n slug: 'effect-cover',\r\n },\r\n type: 'effect',\r\n flags: {\r\n core: { sourceId: COVER_UUID },\r\n [MODULE_ID]: {\r\n level,\r\n bonus,\r\n },\r\n },\r\n }\r\n}\r\n\r\nexport function findChoiceSetRule(item, flag = undefined) {\r\n if (!item) return undefined\r\n return item.system.rules.find(rule => rule.key === 'ChoiceSet' && (!flag || rule.flag === flag))\r\n}\r\n", "import { COVER_UUID } from './constants.js'\r\nimport { findChoiceSetRule } from './effect.js'\r\n\r\nexport function getActorToken(actor, target = false) {\r\n if (!actor) return undefined\r\n const actorId = actor.id\r\n const isToken = actor.isToken\r\n const tokens = target ? game.user.targets : canvas.tokens?.controlled ?? []\r\n return (\r\n tokens.find(token => (isToken ? token.actor === actor : token.actor.id === actorId)) ??\r\n actor.getActiveTokens().shift() ??\r\n null\r\n )\r\n}\r\n\r\nexport function isProne(actor) {\r\n return actor.itemTypes.condition.some(item => item.slug === 'prone')\r\n}\r\n\r\nexport function getCoverEffect(actor, selection = false) {\r\n const effect = actor.itemTypes.effect.find(x => x.sourceId === COVER_UUID)\r\n return selection ? findChoiceSetRule(effect)?.selection.level : effect\r\n}\r\n\r\nexport function getFeatWithUUID(actor, uuid) {\r\n return actor.itemTypes.feat.find(f => f.sourceId === uuid)\r\n}\r\n\r\nfunction hasSense(actor, sense) {\r\n if (!actor || !sense || !actor.system.perception?.senses) return false\r\n sense = sense.toLowerCase()\r\n return !!actor.system.perception.senses.find(({ type }) => type === sense)\r\n}\r\n\r\nexport function hasGreaterDarkvision(actor) {\r\n return hasSense(actor, 'greater-darkvision')\r\n}\r\n\r\nexport function seeInvisibility(actor) {\r\n return hasSense(actor, 'see-invisibility')\r\n}\r\n", "const DEGREE_ADJUSTMENT_AMOUNTS = {\r\n LOWER_BY_TWO: -2,\r\n LOWER: -1,\r\n INCREASE: 1,\r\n INCREASE_BY_TWO: 2,\r\n TO_CRITICAL_FAILURE: 'criticalFailure',\r\n TO_FAILURE: 'failure',\r\n TO_SUCCESS: 'success',\r\n TO_CRITICAL_SUCCESS: 'criticalSuccess',\r\n}\r\n\r\nconst DEGREE_OF_SUCCESS_STRINGS = ['criticalFailure', 'failure', 'success', 'criticalSuccess']\r\n\r\nexport class DegreeOfSuccess {\r\n constructor(roll, dc, dosAdjustments = null) {\r\n if (roll instanceof Roll) {\r\n this.dieResult =\r\n (roll.isDeterministic\r\n ? roll.terms.find(t => t instanceof NumericTerm)\r\n : roll.dice.find(d => d instanceof Die && d.faces === 20)\r\n )?.total ?? 1\r\n this.rollTotal = roll.total\r\n } else {\r\n this.dieResult = roll.dieValue\r\n this.rollTotal = roll.dieValue + roll.modifier\r\n }\r\n\r\n this.dc = typeof dc === 'number' ? { value: dc } : dc\r\n\r\n this.unadjusted = this.#calculateDegreeOfSuccess()\r\n this.adjustment = this.#getDegreeAdjustment(this.unadjusted, dosAdjustments)\r\n this.value = this.adjustment ? this.#adjustDegreeOfSuccess(this.adjustment.amount, this.unadjusted) : this.unadjusted\r\n }\r\n\r\n static CRITICAL_FAILURE = 0\r\n static FAILURE = 1\r\n static SUCCESS = 2\r\n static CRITICAL_SUCCESS = 3\r\n\r\n #getDegreeAdjustment(degree, adjustments) {\r\n if (!adjustments) return null\r\n\r\n for (const outcome of ['all', ...DEGREE_OF_SUCCESS_STRINGS]) {\r\n const { label, amount } = adjustments[outcome] ?? {}\r\n if (\r\n amount &&\r\n label &&\r\n !(degree === DegreeOfSuccess.CRITICAL_SUCCESS && amount === DEGREE_ADJUSTMENT_AMOUNTS.INCREASE) &&\r\n !(degree === DegreeOfSuccess.CRITICAL_FAILURE && amount === DEGREE_ADJUSTMENT_AMOUNTS.LOWER) &&\r\n (outcome === 'all' || DEGREE_OF_SUCCESS_STRINGS.indexOf(outcome) === degree)\r\n ) {\r\n return { label, amount }\r\n }\r\n }\r\n\r\n return null\r\n }\r\n\r\n #adjustDegreeOfSuccess(amount, degreeOfSuccess) {\r\n switch (amount) {\r\n case 'criticalFailure':\r\n return 0\r\n case 'failure':\r\n return 1\r\n case 'success':\r\n return 2\r\n case 'criticalSuccess':\r\n return 3\r\n default:\r\n return Math.clamped(degreeOfSuccess + amount, 0, 3)\r\n }\r\n }\r\n\r\n /**\r\n * @param degree The current success value\r\n * @return The new success value\r\n */\r\n #adjustDegreeByDieValue(degree) {\r\n if (this.dieResult === 20) {\r\n return this.#adjustDegreeOfSuccess(DEGREE_ADJUSTMENT_AMOUNTS.INCREASE, degree)\r\n } else if (this.dieResult === 1) {\r\n return this.#adjustDegreeOfSuccess(DEGREE_ADJUSTMENT_AMOUNTS.LOWER, degree)\r\n }\r\n\r\n return degree\r\n }\r\n\r\n #calculateDegreeOfSuccess() {\r\n const dc = this.dc.value\r\n\r\n if (this.rollTotal - dc >= 10) {\r\n return this.#adjustDegreeByDieValue(DegreeOfSuccess.CRITICAL_SUCCESS)\r\n } else if (dc - this.rollTotal >= 10) {\r\n return this.#adjustDegreeByDieValue(DegreeOfSuccess.CRITICAL_FAILURE)\r\n } else if (this.rollTotal >= dc) {\r\n return this.#adjustDegreeByDieValue(DegreeOfSuccess.SUCCESS)\r\n }\r\n\r\n return this.#adjustDegreeByDieValue(DegreeOfSuccess.FAILURE)\r\n }\r\n}\r\n", "import { getFlag, getSetting, localize } from './module.js'\r\n\r\nexport function renderSceneConfig(config, html) {\r\n let settings = ''\r\n\r\n const list = ['standard', 'npc-vision']\r\n for (const setting of list) {\r\n const checked = getSceneSetting(config.object, setting)\r\n\r\n settings += `${localize(`settings.${setting}.short`)}
\r\n'\r\n content += `${localize('dialog.seek.hint')}
`\r\n\r\n content += createButton(\r\n 'create-cone',\r\n 'fa-thin fa-cubes',\r\n game.i18n.format('PF2E.TemplateLabel', {\r\n size: 30,\r\n unit,\r\n shape: game.i18n.localize(CONFIG.PF2E.areaTypes.cone),\r\n })\r\n )\r\n\r\n content += createButton(\r\n 'create-burst',\r\n 'fa-thin fa-cubes',\r\n game.i18n.format('PF2E.TemplateLabel', {\r\n size: 15,\r\n unit,\r\n shape: game.i18n.localize(CONFIG.PF2E.areaTypes.burst),\r\n })\r\n )\r\n\r\n content += '
'\r\n\r\n return Dialog.wait(\r\n {\r\n title: `${token.name} - ${game.i18n.localize('PF2E.Actions.Seek.Title')}`,\r\n content,\r\n buttons: {\r\n ok: {\r\n icon: '',\r\n label: localize('dialog.seek.accept'),\r\n callback: () => true,\r\n },\r\n no: {\r\n icon: '',\r\n label: localize('dialog.seek.cancel'),\r\n callback: html => false,\r\n },\r\n },\r\n close: () => false,\r\n render: html => {\r\n const content = html.filter('.dialog-content')\r\n content.find('[data-action=create-cone], [data-action=create-burst]').on('click', event => {\r\n const { action } = event.currentTarget.dataset\r\n deleteSeekTemplate(token)\r\n createSeekTemplate({ type: action === 'create-cone' ? 'cone' : 'burst', token })\r\n })\r\n },\r\n },\r\n { width: 300, left: 10 }\r\n )\r\n}\r\n\r\nfunction setupSneak(SingleCheckAction, SingleCheckActionVariant) {\r\n class SneakVariant extends SingleCheckActionVariant {\r\n async use(options = {}) {\r\n const action = getActionName('Sneak')\r\n const token = getSelectedToken(options, action)\r\n if (!token) return\r\n\r\n options.actors = [token.actor]\r\n return super.use(options)\r\n }\r\n }\r\n\r\n class Sneak extends SingleCheckAction {\r\n constructor() {\r\n super({\r\n cost: 1,\r\n description: 'PF2E.Actions.Sneak.Description',\r\n name: 'PF2E.Actions.Sneak.Title',\r\n notes: [\r\n { outcome: ['success', 'criticalSuccess'], text: 'PF2E.Actions.Sneak.Notes.success' },\r\n { outcome: ['failure'], text: 'PF2E.Actions.Sneak.Notes.failure' },\r\n { outcome: ['criticalFailure'], text: 'PF2E.Actions.Sneak.Notes.criticalFailure' },\r\n ],\r\n rollOptions: ['action:sneak'],\r\n slug: 'sneak',\r\n traits: ['move', 'secret'],\r\n })\r\n }\r\n\r\n toActionVariant(data) {\r\n return new SneakVariant(this, data)\r\n }\r\n }\r\n\r\n // game.pf2e.actions.set('sneak', new Sneak())\r\n}\r\n\r\nfunction setupCreateADiversion(SingleCheckAction, SingleCheckActionVariant) {\r\n class CreateADiversionVariant extends SingleCheckActionVariant {\r\n async use(options = {}) {\r\n const action = getActionName('CreateADiversion')\r\n const token = getSelectedToken(options, action)\r\n if (!token) return\r\n\r\n options.actors = [token.actor]\r\n return super.use(options)\r\n }\r\n }\r\n\r\n class CreateADiversion extends SingleCheckAction {\r\n constructor() {\r\n super({\r\n cost: 1,\r\n description: 'PF2E.Actions.CreateADiversion.Description',\r\n name: 'PF2E.Actions.CreateADiversion.Title',\r\n notes: [\r\n { outcome: ['criticalSuccess', 'success'], text: 'PF2E.Actions.CreateADiversion.Notes.success' },\r\n { outcome: ['criticalFailure', 'failure'], text: 'PF2E.Actions.CreateADiversion.Notes.failure' },\r\n ],\r\n section: 'skill',\r\n slug: 'create-a-diversion',\r\n statistic: 'deception',\r\n traits: ['mental'],\r\n variants: [\r\n {\r\n name: 'PF2E.Actions.CreateADiversion.DistractingWords.Title',\r\n rollOptions: ['action:create-a-diversion', 'action:create-a-diversion:distracting-words'],\r\n slug: 'distracting-words',\r\n traits: ['auditory', 'linguistic', 'mental'],\r\n },\r\n {\r\n name: 'PF2E.Actions.CreateADiversion.Gesture.Title',\r\n rollOptions: ['action:create-a-diversion', 'action:create-a-diversion:gesture'],\r\n slug: 'gesture',\r\n traits: ['manipulate', 'mental'],\r\n },\r\n {\r\n name: 'PF2E.Actions.CreateADiversion.Trick.Title',\r\n rollOptions: ['action:create-a-diversion', 'action:create-a-diversion:trick'],\r\n slug: 'trick',\r\n traits: ['manipulate', 'mental'],\r\n },\r\n ],\r\n })\r\n }\r\n\r\n toActionVariant(data) {\r\n return new CreateADiversionVariant(this, data)\r\n }\r\n }\r\n\r\n game.pf2e.actions.set('create-a-diversion', new CreateADiversion())\r\n}\r\n\r\nfunction setupHide(SingleCheckAction, SingleCheckActionVariant) {\r\n class HideVariant extends SingleCheckActionVariant {\r\n async use(options = {}) {\r\n const action = getActionName('Hide')\r\n const token = getSelectedToken(options, action)\r\n if (!token) return\r\n\r\n options.actors = [token.actor]\r\n return super.use(options)\r\n }\r\n }\r\n\r\n class Hide extends SingleCheckAction {\r\n constructor() {\r\n super({\r\n cost: 1,\r\n description: 'PF2E.Actions.Hide.Description',\r\n name: 'PF2E.Actions.Hide.Title',\r\n rollOptions: ['action:hide'],\r\n slug: 'hide',\r\n statistic: 'stealth',\r\n traits: ['secret'],\r\n notes: [{ outcome: ['success', 'criticalSuccess'], text: 'PF2E.Actions.Hide.Notes.success' }],\r\n })\r\n }\r\n\r\n toActionVariant(data) {\r\n return new HideVariant(this, data)\r\n }\r\n }\r\n\r\n game.pf2e.actions.set('hide', new Hide())\r\n}\r\n\r\nfunction setupCover(BaseAction, BaseActionVariant) {\r\n class TakeCoverVariant extends BaseActionVariant {\r\n async use(options = {}) {\r\n const action = localize('action.take-cover')\r\n const token = getSelectedToken(options, action)\r\n if (token) takeCover(token)\r\n }\r\n }\r\n\r\n class TakeCover extends BaseAction {\r\n constructor() {\r\n super({\r\n cost: 1,\r\n description: 'PF2E.Actions.TakeCover.Description',\r\n img: 'systems/pf2e/icons/conditions-2/status_acup.webp',\r\n name: 'PF2E.Actions.TakeCover.Title',\r\n slug: 'take-cover',\r\n })\r\n }\r\n\r\n toActionVariant(data) {\r\n return new TakeCoverVariant(this, data)\r\n }\r\n }\r\n\r\n game.pf2e.actions.set('take-cover', new TakeCover())\r\n}\r\n\r\nasync function takeCover(token) {\r\n const actor = token.actor\r\n const cover = getCoverEffect(actor)\r\n\r\n const targets = validateTokens(token, game.user.targets.ids)\r\n if (cover && !targets.length) return cover.delete()\r\n\r\n const data = getTokenData(token) ?? {}\r\n const covers = Object.entries(data).reduce((covers, [tokenId, { cover }]) => {\r\n if (cover) covers[tokenId] = cover\r\n return covers\r\n }, {})\r\n\r\n const content = await renderTemplate(templatePath('covers-dialog'), {\r\n i18n: localize,\r\n hasTargets: !!targets.length,\r\n hasCovers: !isEmpty(covers),\r\n hasTargetCover: targets.some(id => id in covers),\r\n isProne: isProne(actor),\r\n })\r\n\r\n const dialog = new Dialog({\r\n title: `${token.name} - ${localize('action.take-cover')}`,\r\n content,\r\n buttons: {},\r\n render: html => {\r\n html.find('button').on('click', async event => {\r\n const { level } = event.currentTarget.dataset\r\n const skip = getSetting('skip-cover')\r\n\r\n const process = async (cover, isSelected) => {\r\n const selected = isSelected ? targets : undefined\r\n\r\n const flavor = cover === defaultValues.cover ? (selected ? 'remove' : 'remove-all') : 'take'\r\n await createTokenMessage({\r\n content: localize(`message.cover.${flavor}`, { cover: localize(`cover.${cover}`) }),\r\n flags: { selected, cover, skipWait: skip },\r\n token,\r\n })\r\n\r\n if (skip) {\r\n if (cover === defaultValues.cover && !selected) return clearTokenData(token)\r\n const data = deepClone(getTokenData(token)) ?? {}\r\n for (const tokenId of targets) {\r\n setProperty(data, `${tokenId}.cover`, cover)\r\n }\r\n return setTokenData(token, data)\r\n }\r\n }\r\n\r\n if (level === 'remove-all') process(defaultValues.cover)\r\n else if (level === 'remove') process(defaultValues.cover, true)\r\n else if (targets.length) process(level, true)\r\n else {\r\n const source = createCoverSource(level)\r\n actor.createEmbeddedDocuments('Item', [source])\r\n }\r\n\r\n dialog.close()\r\n })\r\n },\r\n }).render(true)\r\n}\r\n\r\nfunction getSelectedToken(options, action) {\r\n let tokens = options.tokens?.filter(t => t.actor) ?? []\r\n if (!Array.isArray(tokens)) tokens = [tokens]\r\n\r\n let actors = options.actors ?? []\r\n if (!Array.isArray(actors)) actors = [actors]\r\n\r\n if (!tokens.length && actors.length === 1) tokens = [getActorToken(actors[0])].filter(Boolean)\r\n if (!tokens.length) tokens = canvas.tokens.controlled.filter(t => t.actor)\r\n if (!tokens.length) tokens = [getActorToken(game.user.character)].filter(Boolean)\r\n\r\n if (tokens.length > 1) {\r\n ui.notifications.warn(localize('action.only-one', { action }))\r\n return\r\n }\r\n\r\n if (!tokens.length) {\r\n ui.notifications.warn(localize('action.must-one', { action }))\r\n return\r\n }\r\n\r\n const token = tokens[0]\r\n if (!token?.actor?.isOfType('creature')) {\r\n ui.notifications.warn(localize('action.must-creature', { action }))\r\n return\r\n }\r\n\r\n return token\r\n}\r\n\r\nfunction createButton(action, icon, label) {\r\n return `${localize('settings.encounter.short')}
\r\n