All user data for FoundryVTT. Includes worlds, systems, modules, and any asset in the "foundryuserdata" directory. Does NOT include the FoundryVTT installation itself.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

281 lines
10 KiB

1 year ago
  1. class SmartTarget {
  2. static handleTargeting(token,shift) {
  3. const isTargeted = token.isTargeted;
  4. const release = shift ? !SmartTarget.settings().release : SmartTarget.settings().release;
  5. token.setTarget(!isTargeted, { releaseOthers: release });
  6. }
  7. static _tokenOnClickLeft(wrapped, ...args) {
  8. const mode = SmartTarget.settings().mode;
  9. switch (mode) {
  10. case 0:
  11. return wrapped(...args);
  12. break;
  13. case 1:
  14. if (game.smartTarget.altModifier) {
  15. SmartTarget.handleTargeting(this,game.keyboard.isModifierActive(KeyboardManager.MODIFIER_KEYS.SHIFT));
  16. return
  17. }else{
  18. return wrapped(...args);
  19. }
  20. break;
  21. case 2:
  22. if ((!game.user.isGM && !this.isOwner) || (this.isOwner && game.smartTarget.altModifier)) {
  23. SmartTarget.handleTargeting(this,game.keyboard.isModifierActive(KeyboardManager.MODIFIER_KEYS.SHIFT));
  24. return
  25. } else {
  26. return wrapped(...args);
  27. }
  28. break;
  29. }
  30. super._onClickLeft(...args);
  31. }
  32. static canvasOnClickLeft(wrapped, ...args) {
  33. const canvasMousePos = args[0].data.origin
  34. if (game.smartTarget.altModifier){
  35. let distance = Infinity
  36. let closestTemplate = null
  37. for(let template of canvas.templates.placeables){
  38. if(!template.owner) continue
  39. const inTemplate = template.shape.contains(canvasMousePos.x-template.x,canvasMousePos.y-template.y)
  40. const d = Math.sqrt(Math.pow(template.x-canvasMousePos.x,2)+Math.pow(template.y-canvasMousePos.y,2))
  41. if(inTemplate && d<distance){
  42. distance = d
  43. closestTemplate = template
  44. }
  45. }
  46. if(closestTemplate){
  47. const release = game.keyboard.isModifierActive(KeyboardManager.MODIFIER_KEYS.SHIFT) ? !SmartTarget.settings().release : SmartTarget.settings().release;
  48. if (release)canvas.tokens.placeables[0]?.setTarget(false, { releaseOthers: true });
  49. for(let token of canvas.tokens.placeables){
  50. if(closestTemplate.shape.contains(token.center.x-closestTemplate.x,token.center.y-closestTemplate.y)){
  51. token.setTarget(!token.isTargeted, { releaseOthers: false });
  52. }
  53. }
  54. }
  55. }
  56. return wrapped(...args);
  57. }
  58. static _canControl(wrapped,...args){
  59. if(!args[1]) return wrapped(...args);
  60. const mode = SmartTarget.settings().mode;
  61. if(mode==1 && game.smartTarget.altModifier) return true;
  62. if(mode==2 && !game.user.isGM && !this.isOwner) return true;
  63. return wrapped(...args);
  64. }
  65. static getOffset(token, length) {
  66. const width = token.w;
  67. const height = token.h;
  68. const position = game.settings.get(SMARTTARGET_MODULE_NAME, "pipPosition")
  69. const circleR = game.settings.get(SMARTTARGET_MODULE_NAME, "pipScale") || 12;
  70. let circleOffsetMult = game.settings.get(SMARTTARGET_MODULE_NAME, "pipOffset") || 16;
  71. let insidePip = game.settings.get(SMARTTARGET_MODULE_NAME, "insidePips") ? circleR : 0;
  72. const totalHeight = circleR*2;
  73. const totalWidth = (circleR*2)*length - circleOffsetMult*(length-1);
  74. const offset = {
  75. x: 0,
  76. y: 0,
  77. };
  78. switch (position) {
  79. case "topleft":
  80. break;
  81. case "topright":
  82. offset.x = width - totalWidth;
  83. break;
  84. case "bottomleft":
  85. offset.y = height - totalHeight;
  86. break;
  87. case "bottomright":
  88. offset.x = width - totalWidth;
  89. offset.y = height - totalHeight;
  90. break;
  91. case "centertop":
  92. offset.x = (width - totalWidth) / 2;
  93. break;
  94. case "centerbottom":
  95. offset.x = (width - totalWidth) / 2;
  96. offset.y = height - totalHeight;
  97. break;
  98. case "random":
  99. offset.x = Math.floor(Math.random() * (width - totalWidth));
  100. offset.y = Math.floor(Math.random() * (height - totalHeight));
  101. break;
  102. }
  103. return offset;
  104. }
  105. /**
  106. * Creates a sprite from the selected avatar and positions around the container
  107. * @param {User} u -- the user to get
  108. * @param {int} i -- the current row count
  109. * @param {token} target -- PIXI.js container for height & width (the token)
  110. */
  111. static buildCharacterPortrait(u, i, target, token, totalOffset) {
  112. let color = Color.from(u.color);
  113. let circleR = game.settings.get(SMARTTARGET_MODULE_NAME, "pipScale") || 12;
  114. let circleOffsetMult =
  115. game.settings.get(SMARTTARGET_MODULE_NAME, "pipOffset") || 16;
  116. let scaleMulti =
  117. game.settings.get(SMARTTARGET_MODULE_NAME, "pipImgScale") || 1;
  118. let insidePip = game.settings.get(SMARTTARGET_MODULE_NAME, "insidePips")
  119. ? circleR
  120. : 0;
  121. let pTex;
  122. if (!u.isGM) {
  123. let character = u.character;
  124. if (!character) {
  125. character = u.character;
  126. }
  127. if (character) {
  128. pTex = game.settings.get(SMARTTARGET_MODULE_NAME, "useToken")
  129. ? character.prototypeToken.texture.src || character.img
  130. : character.img || character.prototypeToken.texture.src;
  131. } else {
  132. pTex = u.avatar;
  133. }
  134. }
  135. const gmTexSetting = game.settings.get(SMARTTARGET_MODULE_NAME, "useTokenGm")
  136. let gmTexture = gmTexSetting ? token.document.getFlag(SMARTTARGET_MODULE_NAME,"gmtargetimg") || u.avatar : u.avatar
  137. function redraw(){
  138. token._refreshTarget()
  139. }
  140. let texture = u.isGM
  141. ? new PIXI.Texture.from(gmTexture)
  142. : new PIXI.Texture.from(pTex);
  143. if (!texture.baseTexture.valid) texture.once("update", redraw);
  144. let newTexW = scaleMulti * (2 * circleR);
  145. let newTexH = scaleMulti * (2 * circleR);
  146. let borderThic = game.settings.get(SMARTTARGET_MODULE_NAME, "borderThicc");
  147. let portraitCenterOffset =
  148. scaleMulti >= 1 ? (16 + circleR / 12) * Math.log2(scaleMulti) : 0;
  149. portraitCenterOffset +=
  150. game.settings.get(SMARTTARGET_MODULE_NAME, "pipOffsetManualY") || 0;
  151. let portraitXoffset =
  152. game.settings.get(SMARTTARGET_MODULE_NAME, "pipOffsetManualX") || 0;
  153. let matrix = new PIXI.Matrix(
  154. (scaleMulti * (2 * circleR + 2)) / texture.width,
  155. 0,
  156. 0,
  157. (scaleMulti * (2 * circleR + 2)) / texture.height,
  158. newTexW / 2 + 4 + i * circleOffsetMult + portraitXoffset + insidePip + totalOffset.x,
  159. newTexH / 2 + portraitCenterOffset + insidePip + totalOffset.y
  160. );
  161. token.target
  162. .beginFill(color)
  163. .drawCircle(2 + i * circleOffsetMult + insidePip + totalOffset.x, 0 + insidePip + totalOffset.y, circleR)
  164. .beginTextureFill({
  165. texture: texture,
  166. alpha: 1,
  167. matrix: matrix,
  168. })
  169. .lineStyle(borderThic, 0x0000000)
  170. .drawCircle(2 + i * circleOffsetMult + insidePip + totalOffset.x, 0 + insidePip + totalOffset.y, circleR)
  171. .endFill()
  172. .lineStyle(borderThic / 2, color)
  173. .drawCircle(2 + i * circleOffsetMult + insidePip + totalOffset.x, 0 + insidePip + totalOffset.y, circleR)
  174. }
  175. // Draw custom crosshair and pips
  176. static async _refreshTarget(reticule = {}) {
  177. if (!this.target) return;
  178. this.target.clear();
  179. if (!this.targeted.size) return;
  180. // Determine whether the current user has target and any other users
  181. const [others, user] = Array.from(this.targeted).partition(
  182. (u) => u === game.user
  183. );
  184. const userTarget = user.length;
  185. // For the current user, draw the target arrows
  186. if (userTarget) {
  187. let textColor = game.settings.get(
  188. SMARTTARGET_MODULE_NAME,
  189. "crossairColor"
  190. )
  191. ? game.settings
  192. .get(SMARTTARGET_MODULE_NAME, "crossairColor")
  193. .replace("#", "0x")
  194. : this._getBorderColor({hover: true});
  195. if (game.settings.get(SMARTTARGET_MODULE_NAME, "use-player-color")) {
  196. textColor = Color.from(game.user["color"]);
  197. }
  198. let p = 4;
  199. let aw = 12;
  200. let h = this.h;
  201. let hh = h / 2;
  202. let w = this.w;
  203. let hw = w / 2;
  204. let ah = canvas.dimensions.size / 3;
  205. let selectedIndicator = game.settings.get(
  206. SMARTTARGET_MODULE_NAME,
  207. "target-indicator"
  208. );
  209. switch (selectedIndicator) {
  210. case "0":
  211. reticule.color = textColor;
  212. this._drawTarget(reticule)//{color: textColor})//drawDefault(this, textColor, p, aw, h, hh, w, hw, ah);
  213. break;
  214. case "1":
  215. drawCrossHairs1(this, textColor, p, aw, h, hh, w, hw, ah);
  216. break;
  217. case "2":
  218. drawCrossHairs2(this, textColor, p, aw, h, hh, w, hw, ah);
  219. break;
  220. case "3":
  221. drawBullsEye1(this, textColor, p, aw, h, hh, w, hw, ah);
  222. break;
  223. case "4":
  224. drawBullsEye2(this, textColor, p, aw, h, hh, w, hw, ah);
  225. break;
  226. case "5":
  227. drawBetterTarget(this, textColor, p, aw, h, hh, w, hw, ah);
  228. break;
  229. default:
  230. drawDefault(this, textColor, p, aw, h, hh, w, hw, ah);
  231. break;
  232. }
  233. }
  234. // For other users, draw offset pips
  235. if (game.settings.get(SMARTTARGET_MODULE_NAME, "portraitPips")) {
  236. for (let [i, u] of others.entries()) {
  237. const offset = SmartTarget.getOffset(this, others.length);
  238. SmartTarget.buildCharacterPortrait(u, i, this.target,this, offset);
  239. }
  240. } else {
  241. for (let [i, u] of others.entries()) {
  242. let color = Color.from(u.color);
  243. this.target
  244. .beginFill(color, 1.0)
  245. .lineStyle(2, 0x0000000)
  246. .drawCircle(2 + i * 8, 0, 6);
  247. }
  248. }
  249. }
  250. static settings() {
  251. const settings = {
  252. mode: game.settings.get(SMARTTARGET_MODULE_NAME, "targetingMode"),
  253. release: !game.settings.get(SMARTTARGET_MODULE_NAME, "release"),
  254. };
  255. return settings;
  256. }
  257. }
  258. Hooks.on("targetToken", (user,token,targeted) =>{
  259. const gmTexSetting = game.settings.get(SMARTTARGET_MODULE_NAME, "useTokenGm")
  260. if(!game.user.isGM || !targeted || !gmTexSetting) return
  261. let flag
  262. if(gmTexSetting == 1) flag = _token?.document.actor?.img || _token?.document.texture.src
  263. if(gmTexSetting == 2) flag = _token?.document.texture.src || _token?.document.actor?.img
  264. flag && flag != token.document.getFlag(SMARTTARGET_MODULE_NAME,"gmtargetimg") && token.document.setFlag(SMARTTARGET_MODULE_NAME,"gmtargetimg",flag)
  265. })