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.

296 lines
11 KiB

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